diff --git a/kibot/__main__.py b/kibot/__main__.py index ac0909ad..368c49a4 100644 --- a/kibot/__main__.py +++ b/kibot/__main__.py @@ -304,6 +304,18 @@ def detect_kicad(): logger.debug('KiCad config path {}'.format(GS.kicad_conf_path)) +def solve_project_file(): + if GS.pcb_file: + pro_name = GS.pcb_no_ext+GS.pro_ext + if os.path.isfile(pro_name): + return pro_name + if GS.sch_file: + pro_name = GS.sch_no_ext+GS.pro_ext + if os.path.isfile(pro_name): + return pro_name + return None + + def main(): set_locale() ver = 'KiBot '+__version__+' - '+__copyright__+' - License: '+__license__ @@ -357,6 +369,8 @@ def main(): GS.set_sch(solve_schematic(args.schematic, args.board_file, plot_config)) # Determine the PCB file GS.set_pcb(solve_board_file(GS.sch_file, args.board_file)) + # Determine the project file + GS.set_pro(solve_project_file()) # Read the config file cr = CfgYamlReader() diff --git a/kibot/gs.py b/kibot/gs.py index fe5a6b3b..5d24650f 100644 --- a/kibot/gs.py +++ b/kibot/gs.py @@ -4,6 +4,8 @@ # License: GPL-3.0 # Project: KiBot (formerly KiPlot) import os +import re +import json try: import pcbnew except ImportError: @@ -12,7 +14,7 @@ except ImportError: pass from datetime import datetime, date from sys import exit -from .misc import EXIT_BAD_ARGS, W_DATEFORMAT, KICAD_VERSION_5_99 +from .misc import EXIT_BAD_ARGS, W_DATEFORMAT, KICAD_VERSION_5_99, W_UNKVAR from .log import get_logger logger = get_logger(__name__) @@ -33,6 +35,14 @@ class GS(object): sch_no_ext = None # /.../dir/file sch_dir = None # /.../dir sch_basename = None # file + # Project and useful parts + pro_file = None # /.../dir/file.kicad_pro (or .pro) + pro_no_ext = None # /.../dir/file + pro_dir = None # /.../dir + pro_basename = None # file + pro_ext = '.pro' + pro_variables = None # KiCad 6 text variables defined in the project + vars_regex = re.compile(r'\$\{([^\}]+)\}') # Main output dir out_dir = None out_dir_in_cmd_line = False @@ -48,7 +58,6 @@ class GS(object): kicad_share_path = None kicad_dir = 'kicad' kicad_plugins_dirs = [] - pro_ext = '.pro' work_layer = 'Rescue' # KiCad version: major*1e6+minor*1e3+patch kicad_version_n = 0 @@ -127,16 +136,39 @@ class GS(object): GS.pcb_no_ext = os.path.splitext(name)[0] GS.pcb_dir = os.path.dirname(name) + @staticmethod + def set_pro(name): + if name: + name = os.path.abspath(name) + GS.pro_file = name + GS.pro_basename = os.path.splitext(os.path.basename(name))[0] + GS.pro_no_ext = os.path.splitext(name)[0] + GS.pro_dir = os.path.dirname(name) + + @staticmethod + def load_pro_variables(): + if GS.pro_variables is not None: + return GS.pro_variables + if GS.pro_file is None or GS.pro_ext == '.pro': + return {} + # Get the text_variables + with open(GS.pro_file, 'rt') as f: + pro_text = f.read() + data = json.loads(pro_text) + GS.pro_variables = data.get('text_variables', {}) + logger.debug("Current text variables: {}".format(GS.pro_variables)) + return GS.pro_variables + @staticmethod def load_sch_title_block(): if GS.sch_title is not None: return assert GS.sch is not None - GS.sch_title = GS.sch.title - GS.sch_date = GS.sch.date - GS.sch_rev = GS.sch.revision - GS.sch_comp = GS.sch.company - GS.sch_com = GS.sch.comment + GS.sch_title = GS.expand_text_variables(GS.sch.title) + GS.sch_date = GS.expand_text_variables(GS.sch.date) + GS.sch_rev = GS.expand_text_variables(GS.sch.revision) + GS.sch_comp = GS.expand_text_variables(GS.sch.company) + GS.sch_com = map(GS.expand_text_variables, GS.sch.comment) @staticmethod def format_date(d, fname, what): @@ -234,6 +266,29 @@ class GS(object): def ki5(): return GS.kicad_version_n < KICAD_VERSION_5_99 + @staticmethod + def expand_text_variables(text): + vars = GS.load_pro_variables() + new_text = '' + last = 0 + text_l = len(text) + for match in GS.vars_regex.finditer(text): + vname = match.group(1) + value = vars.get(vname, None) + if value is None: + value = '${'+vname+'}' + logger.warning(W_UNKVAR+"Unknown text variable `{}`".format(vname)) + if match.start(): + new_text += text[last:match.start()] + new_text += value + last = match.end() + if last < text_l: + new_text += text[last:text_l] + if new_text != text: + if GS.debug_level > 3: + logger.debug('Replacing KiCad text variables: {} -> {}'.format(text, new_text)) + return new_text + @staticmethod def load_pcb_title_block(): if GS.pcb_title is not None: @@ -243,15 +298,15 @@ class GS(object): GS.pcb_rev = '' GS.pcb_comp = '' # This is based on InterativeHtmlBom expansion - title_block = GS.board.GetTitleBlock() - GS.pcb_date = GS.format_date(title_block.GetDate(), GS.pcb_file, 'PCB') - GS.pcb_title = title_block.GetTitle() + title_block = GS.expand_text_variables(GS.board.GetTitleBlock()) + GS.pcb_date = GS.format_date(GS.expand_text_variables(title_block.GetDate()), GS.pcb_file, 'PCB') + GS.pcb_title = GS.expand_text_variables(title_block.GetTitle()) if not GS.pcb_title: GS.pcb_title = GS.pcb_basename - GS.pcb_rev = title_block.GetRevision() - GS.pcb_comp = title_block.GetCompany() + GS.pcb_rev = GS.expand_text_variables(title_block.GetRevision()) + GS.pcb_comp = GS.expand_text_variables(title_block.GetCompany()) for num in range(9): - GS.pcb_com[num] = GS.get_pcb_comment(title_block, num) + GS.pcb_com[num] = GS.expand_text_variables(GS.get_pcb_comment(title_block, num)) logger.debug("PCB title: `{}`".format(GS.pcb_title)) logger.debug("PCB date: `{}`".format(GS.pcb_date)) logger.debug("PCB revision: `{}`".format(GS.pcb_rev)) diff --git a/kibot/misc.py b/kibot/misc.py index d0b32f8f..23879b22 100644 --- a/kibot/misc.py +++ b/kibot/misc.py @@ -223,6 +223,7 @@ W_KICOSTFLD = '(W078) ' W_MIXVARIANT = '(W079) ' W_NOTPDF = '(W080) ' W_NOREF = '(W081) ' +W_UNKVAR = '(W082) ' # Somehow arbitrary, the colors are real, but can be different PCB_MAT_COLORS = {'fr1': "937042", 'fr2': "949d70", 'fr3': "adacb4", 'fr4': "332B16", 'fr5': "6cc290"} PCB_FINISH_COLORS = {'hal': "8b898c", 'hasl': "8b898c", 'imag': "8b898c", 'enig': "cfb96e", 'enepig': "cfb96e", diff --git a/kibot/optionable.py b/kibot/optionable.py index f403c38a..c6ca8ff3 100644 --- a/kibot/optionable.py +++ b/kibot/optionable.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2020-2021 Salvador E. Tropea -# Copyright (c) 2020-2021 Instituto Nacional de TecnologĂ­a Industrial +# Copyright (c) 2020-2022 Salvador E. Tropea +# Copyright (c) 2020-2022 Instituto Nacional de TecnologĂ­a Industrial # License: GPL-3.0 # Project: KiBot (formerly KiPlot) """ Base class for output options """ @@ -269,6 +269,8 @@ class Optionable(object): if GS.debug_level > 3: logger.debug('Expanding `{}` in {} context for {} parent: {}'. format(name, 'SCH' if is_sch else 'PCB', self, parent)) + # Replace KiCad 6 variables first + name = GS.expand_text_variables(name) # Determine if we need to expand SCH and/or PCB related data has_dep_exp = any(map(lambda x: x in name, ['%c', '%d', '%F', '%f', '%p', '%r', '%C1', '%C2', '%C3', '%C4'])) do_sch = is_sch and has_dep_exp @@ -304,6 +306,8 @@ class Optionable(object): name = name.replace('%r', _cl(GS.sch_rev)) for num, val in enumerate(GS.sch_com): name = name.replace('%C'+str(num+1), _cl(val)) + # Also replace KiCad 6 variables after it + name = GS.expand_text_variables(name) if make_safe: # sanitize the name to avoid characters illegal in file systems name = name.replace('\\', '/') diff --git a/kibot/out_any_pcb_print.py b/kibot/out_any_pcb_print.py index 35ed1b61..171932fc 100644 --- a/kibot/out_any_pcb_print.py +++ b/kibot/out_any_pcb_print.py @@ -66,7 +66,7 @@ class Any_PCB_PrintOptions(VariantOptions): @staticmethod def _copy_project(fname): - pro_name = GS.pcb_file.replace('.kicad_pcb', GS.pro_ext) + pro_name = GS.pro_file if not os.path.isfile(pro_name): return None pro_copy = fname.replace('.kicad_pcb', GS.pro_ext) diff --git a/kibot/out_pdf_sch_print.py b/kibot/out_pdf_sch_print.py index 8cfda592..e1fa4fd4 100644 --- a/kibot/out_pdf_sch_print.py +++ b/kibot/out_pdf_sch_print.py @@ -19,7 +19,7 @@ logger = log.get_logger() def copy_project(sch_dir): """ Copy the project file to the temporal dir """ ext = GS.pro_ext - source = GS.sch_no_ext+ext + source = GS.pro_file prj_file = os.path.join(sch_dir, GS.sch_basename+ext) if os.path.isfile(source): copy2(source, prj_file) diff --git a/kibot/pre_set_text_variables.py b/kibot/pre_set_text_variables.py index 66961172..d13d825e 100644 --- a/kibot/pre_set_text_variables.py +++ b/kibot/pre_set_text_variables.py @@ -86,8 +86,8 @@ class Set_Text_Variables(BasePreFlight): # noqa: F821 return if GS.ki5(): raise KiPlotConfigurationError("The `set_text_variables` preflight is for KiCad 6 or newer") - pro_name = GS.pcb_file.replace('.kicad_pcb', GS.pro_ext) - if not os.path.isfile(pro_name): + pro_name = GS.pro_file + if not pro_name or not os.path.isfile(pro_name): raise KiPlotConfigurationError("Trying to define KiCad 6 variables but the project is missing ({})". format(pro_name)) # Get the current definitions @@ -95,10 +95,13 @@ class Set_Text_Variables(BasePreFlight): # noqa: F821 pro_text = f.read() data = json.loads(pro_text) text_variables = data.get('text_variables', {}) + GS.pro_variables = text_variables logger.debug("- Current variables: {}".format(text_variables)) # Define the requested variables - os.environ['KIBOT_PCB_NAME'] = GS.pcb_file - os.environ['KIBOT_SCH_NAME'] = GS.sch_file + if GS.pcb_file: + os.environ['KIBOT_PCB_NAME'] = GS.pcb_file + if GS.sch_file: + os.environ['KIBOT_SCH_NAME'] = GS.sch_file for r in o: text = r.text if not text: @@ -112,10 +115,17 @@ class Set_Text_Variables(BasePreFlight): # noqa: F821 continue text = result.stdout.strip() text = r.before + text + r.after - if r.expand_kibot_patterns: - text = Optionable.expand_filename_both(self, text, make_safe=False) logger.debug(' - ' + r.name + ' -> ' + text) text_variables[r.name] = text + logger.debug("- Expanding %X patterns in variables") + # Now that we have the variables defined expand the %X patterns (they could use variables) + for r in o: + if r.expand_kibot_patterns: + text = text_variables[r.name] + new_text = Optionable.expand_filename_both(self, text, make_safe=False) + if text != new_text: + logger.debug(' - ' + r.name + ' -> ' + new_text) + text_variables[r.name] = new_text logger.debug("- New list of variables: {}".format(text_variables)) # Store the modified project data['text_variables'] = text_variables diff --git a/tests/test_plot/test_preflight.py b/tests/test_plot/test_preflight.py index 6501c256..1301369e 100644 --- a/tests/test_plot/test_preflight.py +++ b/tests/test_plot/test_preflight.py @@ -238,20 +238,21 @@ def test_pcb_replace_1(test_dir): def test_set_text_variables_1(test_dir): """ KiCad 6 variables """ - prj = 'light_control' - ctx = context.TestContext(test_dir, 'test_set_text_variables_1', prj, 'set_text_variables_1', '') + prj = 'test_vars' + ctx = context.TestContextSCH(test_dir, 'test_set_text_variables_1', prj, 'set_text_variables_1', '') if context.ki5(): ctx.run(EXIT_BAD_CONFIG) else: - ctx.run() + ctx.run(extra_debug=True) file = os.path.join(ctx.get_board_dir(), ctx.board_name+context.PRO_EXT) file_back = file + '-bak' assert os.path.isfile(file_back), file_back assert os.path.getsize(file_back) > 0 try: logging.debug(file) - cmd = ['/bin/bash', '-c', "git log -1 --format='%h' " + ctx.board_file] - text = "Git_hash:'"+run(cmd, stdout=PIPE, stderr=PIPE, universal_newlines=True).stdout.strip()+"' ({})".format(prj) + cmd = ['/bin/bash', '-c', "git log -1 --format='%h' " + ctx.sch_file] + hash = run(cmd, stdout=PIPE, stderr=PIPE, universal_newlines=True).stdout.strip() + text = "Git_hash:'{}' ({})".format(hash, prj) with open(file, 'rt') as f: c = f.read() data = json.loads(c) @@ -260,4 +261,5 @@ def test_set_text_variables_1(test_dir): assert data['text_variables']['Comment4'] == text finally: os.rename(file_back, file) + ctx.expect_out_file(prj+'-bom_'+hash+'.csv') ctx.clean_up(keep_project=True) diff --git a/tests/yaml_samples/set_text_variables_1.kibot.yaml b/tests/yaml_samples/set_text_variables_1.kibot.yaml index 30185b28..3f24bb8a 100644 --- a/tests/yaml_samples/set_text_variables_1.kibot.yaml +++ b/tests/yaml_samples/set_text_variables_1.kibot.yaml @@ -5,6 +5,19 @@ kibot: preflight: set_text_variables: - variable: "Comment4" - command: git log -1 --format="%h" $KIBOT_PCB_NAME + command: git log -1 --format="%h" $KIBOT_SCH_NAME before: "Git_hash:'" after: "' (%f)" + - variable: "git_hash" + command: git log -1 --format="%h" $KIBOT_SCH_NAME + +outputs: + - name: 'bom_internal' + comment: "Bill of Materials in CSV format" + type: bom + options: + csv: + hide_pcb_info: true + hide_stats_info: true + output: '%f-%i_%r.%x' + columns: [References, Value]