diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d1174db..709b19fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,7 +52,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 (#103) - A mechanism to avoid running some outputs by default. (#112) - New preflights: - - Commands to replace tags in the schematic and PCB. (#93) + - Commands to replace tags in the schematic and PCB (KiCad 5). (#93) + Also a mechanism to define variables in KiCad 6. (#161) - Annotate power components. (#76) - Annotate according to PCB coordinates (#93) - Now you can compress files relative to the current working directory. diff --git a/README.md b/README.md index 1b476d8f..859fef0d 100644 --- a/README.md +++ b/README.md @@ -173,6 +173,9 @@ This section is used to specify tasks that will be executed before generating an - `tag_delimiter`: [string='@'] Character used to indicate the beginning and the end of a tag. Don't change it unless you really know about KiCad's file formats. - `text`: [string=''] Text to insert instead of the tag. +- `set_text_variables`: [dict|list(dict)] Defines KiCad 6 variables. + They are expanded using ${VARIABLE}, and stored in the project file. + This preflight replaces `pcb_replace` and `sch_replace` when using KiCad 6. - `update_qr`: [boolean=false] Update the QR codes. Complements the `qr_lib` output. The KiCad 6 files and the KiCad 5 PCB needs manual update, generating a new library isn't enough. diff --git a/docs/samples/generic_plot.kibot.yaml b/docs/samples/generic_plot.kibot.yaml index a32e4d39..5eea6563 100644 --- a/docs/samples/generic_plot.kibot.yaml +++ b/docs/samples/generic_plot.kibot.yaml @@ -45,7 +45,6 @@ preflight: command: 'git log -1 --format="%h" $KIBOT_PCB_NAME' before: 'Git hash: <' after: '>' - # [boolean=false] Runs the DRC (Distance Rules Check). To ensure we have a valid PCB. # The report file name is controlled by the global output pattern (%i=drc %x=txt). run_drc: true @@ -61,7 +60,14 @@ preflight: command: 'git log -1 --format="%h" $KIBOT_SCH_NAME' before: 'Git hash: <' after: '>' - + # [dict|list(dict)] Defines KiCad 6 variables. + # They are expanded using ${VARIABLE}, and stored in the project file. + # This preflight replaces `pcb_replace` and `sch_replace` when using KiCad 6. + set_text_variables: + - name: 'git_hash' + command: 'git log -1 --format="%h" $KIBOT_PCB_NAME' + before: 'Git hash: <' + after: '>' # [boolean=false] Update the QR codes. # Complements the `qr_lib` output. # The KiCad 6 files and the KiCad 5 PCB needs manual update, generating a new library isn't enough. diff --git a/kibot/__main__.py b/kibot/__main__.py index 0c461e3a..ac0909ad 100644 --- a/kibot/__main__.py +++ b/kibot/__main__.py @@ -145,8 +145,8 @@ def solve_schematic(a_schematic, a_board_file, config): # Look for a schematic with a PCB and/or project for sch in schematics: base = os.path.splitext(sch)[0] - if os.path.isfile(base+'.pro') or os.path.isfile(base+'.kicad_pro') or \ - os.path.isfile(base+'.kicad_pcb'): + if (os.path.isfile(base+'.pro') or os.path.isfile(base+'.kicad_pro') or + os.path.isfile(base+'.kicad_pcb')): schematic = sch break else: diff --git a/kibot/pre_any_replace.py b/kibot/pre_any_replace.py index ff61b598..bf3aad06 100644 --- a/kibot/pre_any_replace.py +++ b/kibot/pre_any_replace.py @@ -84,7 +84,7 @@ class Base_Replace(BasePreFlight): # noqa: F821 "\n - tag: '@git_hash@'" "\n command: 'git log -1 --format=\"%h\" $KIBOT_{}_NAME'" "\n before: 'Git hash: <'" - "\n after: '>'\n".format(cls._context, cls._context)) + "\n after: '>'".format(cls._context, cls._context)) def replace(self, file): logger.debug('Applying replacements to `{}`'.format(file)) diff --git a/kibot/pre_pcb_replace.py b/kibot/pre_pcb_replace.py index 9d100a4a..dd0c256a 100644 --- a/kibot/pre_pcb_replace.py +++ b/kibot/pre_pcb_replace.py @@ -28,6 +28,7 @@ class PCB_ReplaceOptions(Base_ReplaceOptions): @pre_class class PCB_Replace(Base_Replace): # noqa: F821 """ [dict] Replaces tags in the schematic. I.e. to insert the git hash or last revision date. + This is useful for KiCad 5, use `set_text_variables` when using KiCad 6. This pre-flight modifies the PCB. Even when a back-up is done use it carefully """ _context = 'PCB' diff --git a/kibot/pre_sch_replace.py b/kibot/pre_sch_replace.py index ae61c552..3824c933 100644 --- a/kibot/pre_sch_replace.py +++ b/kibot/pre_sch_replace.py @@ -32,6 +32,7 @@ class SCH_ReplaceOptions(Base_ReplaceOptions): @pre_class class SCH_Replace(Base_Replace): # noqa: F821 """ [dict] Replaces tags in the schematic. I.e. to insert the git hash or last revision date. + This is useful for KiCad 5, use `set_text_variables` when using KiCad 6. This pre-flight modifies the schematics. Even when a back-up is done use it carefully """ _context = 'SCH' diff --git a/kibot/pre_set_text_variables.py b/kibot/pre_set_text_variables.py new file mode 100644 index 00000000..3b8ecfc6 --- /dev/null +++ b/kibot/pre_set_text_variables.py @@ -0,0 +1,122 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2022 Salvador E. Tropea +# Copyright (c) 2022 Instituto Nacional de TecnologĂ­a Industrial +# License: GPL-3.0 +# Project: KiBot (formerly KiPlot) +import os +import sys +import json +from subprocess import run, PIPE +from .error import KiPlotConfigurationError +from .misc import FAILED_EXECUTE, W_EMPTREP +from .optionable import Optionable +from .pre_base import BasePreFlight +from .gs import GS +from .macros import macros, document, pre_class # noqa: F401 +from . import log + +logger = log.get_logger() + + +class KiCadVariable(Optionable): + """ KiCad variable definition """ + def __init__(self): + super().__init__() + self._unkown_is_error = True + with document: + self.name = '' + """ Name of the variable. The `version` variable will be expanded using `${version}` """ + self.variable = None + """ {name} """ + self.text = '' + """ Text to insert instead of the variable """ + self.command = '' + """ Command to execute to get the text, will be used only if `text` is empty """ + self.before = '' + """ Text to add before the output of `command` """ + self.after = '' + """ Text to add after the output of `command` """ + + def config(self, parent): + super().config(parent) + if not self.name: + raise KiPlotConfigurationError("Missing variable name ({})".format(str(self._tree))) + + +class Set_Text_VariablesOptions(Optionable): + """ A list of KiCad variables """ + def __init__(self): + super().__init__() + with document: + self.variables = KiCadVariable + """ [dict|list(dict)] Variables """ + + def config(self, parent): + super().config(parent) + if isinstance(self.variables, type): + self.variables = [] + elif isinstance(self.variables, KiCadVariable): + self.variables = [self.variables] + + +@pre_class +class Set_Text_Variables(BasePreFlight): # noqa: F821 + """ [dict|list(dict)] Defines KiCad 6 variables. + They are expanded using ${VARIABLE}, and stored in the project file. + This preflight replaces `pcb_replace` and `sch_replace` when using KiCad 6 """ + def __init__(self, name, value): + f = Set_Text_VariablesOptions() + f.set_tree({'variables': value}) + f.config(self) + super().__init__(name, f.variables) + + @classmethod + def get_example(cls): + """ Returns a YAML value for the example config """ + return ("\n - name: 'git_hash'" + "\n command: 'git log -1 --format=\"%h\" $KIBOT_PCB_NAME'" + "\n before: 'Git hash: <'" + "\n after: '>'") + + def apply(self): + o = self._value + if len(o) == 0: + 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): + raise KiPlotConfigurationError("Trying to define KiCad 6 variables but the project is missing ({})". + format(pro_name)) + # Get the current definitions + with open(pro_name, 'rt') as f: + pro_text = f.read() + data = json.loads(pro_text) + text_variables = data.get('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 + for r in o: + text = r.text + if not text: + cmd = ['/bin/bash', '-c', r.command] + result = run(cmd, stdout=PIPE, stderr=PIPE, universal_newlines=True) + if result.returncode: + logger.error('Failed to execute:\n{}\nreturn code {}'.format(r.command, result.returncode)) + sys.exit(FAILED_EXECUTE) + if not result.stdout: + logger.warning(W_EMPTREP+"Empty value from `{}` skipping it".format(r.command)) + continue + text = result.stdout.strip() + text = r.before + text + r.after + logger.debug(' - ' + r.name + ' -> ' + text) + text_variables[r.name] = text + logger.debug("- New list of variables: {}".format(text_variables)) + # Store the modified project + data['text_variables'] = text_variables + GS.make_bkp(pro_name) + with open(pro_name, 'wt') as f: + f.write(json.dumps(data, sort_keys=True, indent=2)) + # Force the PCB reload (will reload the project file) + GS.board = None diff --git a/tests/board_samples/kicad_6/light_control.kicad_pro b/tests/board_samples/kicad_6/light_control.kicad_pro index 18f219a2..943f5660 100644 --- a/tests/board_samples/kicad_6/light_control.kicad_pro +++ b/tests/board_samples/kicad_6/light_control.kicad_pro @@ -48,7 +48,13 @@ "min_clearance": 0.508 } }, - "diff_pair_dimensions": [], + "diff_pair_dimensions": [ + { + "gap": 0.0, + "via_gap": 0.0, + "width": 0.0 + } + ], "drc_exclusions": [], "meta": { "filename": "board_design_settings.json", @@ -207,5 +213,7 @@ "legacy_lib_list": [] }, "sheets": [], - "text_variables": {} -} + "text_variables": { + "PRUEBITA": "Hola!" + } +} \ No newline at end of file diff --git a/tests/test_plot/test_preflight.py b/tests/test_plot/test_preflight.py index 1ee55abf..0676b7c4 100644 --- a/tests/test_plot/test_preflight.py +++ b/tests/test_plot/test_preflight.py @@ -18,6 +18,7 @@ import os import sys import logging import re +import json from subprocess import run, PIPE # Look for the 'utils' module from where the script is running prev_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) @@ -231,3 +232,26 @@ def test_pcb_replace_1(test_dir): assert m.group(1) == text finally: os.rename(file_back, file) + + +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', '') + ctx.run(extra=[]) + 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() + "'" + with open(file, 'rt') as f: + c = f.read() + data = json.loads(c) + assert 'text_variables' in data + assert 'Comment4' in data['text_variables'] + assert data['text_variables']['Comment4'] == text + finally: + os.rename(file_back, file) diff --git a/tests/yaml_samples/set_text_variables_1.kibot.yaml b/tests/yaml_samples/set_text_variables_1.kibot.yaml new file mode 100644 index 00000000..38b7b0a8 --- /dev/null +++ b/tests/yaml_samples/set_text_variables_1.kibot.yaml @@ -0,0 +1,10 @@ +# Example KiBot config file +kibot: + version: 1 + +preflight: + set_text_variables: + - variable: "Comment4" + command: git log -1 --format="%h" $KIBOT_PCB_NAME + before: "Git_hash:'" + after: "'"