From e24381f7ee72a2894158ab0e98cecbeede059a3d Mon Sep 17 00:00:00 2001 From: Marvin Mager <99667992+mvnmgrx@users.noreply.github.com> Date: Sat, 18 Feb 2023 13:50:45 +0100 Subject: [PATCH 01/10] SchematicSymbol: Added libId, libraryNickname and entryName tokens --- src/kiutils/items/schitems.py | 76 +++++++++++++++++++++++++++-------- tests/test_schematic.py | 2 +- 2 files changed, 61 insertions(+), 17 deletions(-) diff --git a/src/kiutils/items/schitems.py b/src/kiutils/items/schitems.py index 9435e17..f08790a 100644 --- a/src/kiutils/items/schitems.py +++ b/src/kiutils/items/schitems.py @@ -16,6 +16,7 @@ from __future__ import annotations from dataclasses import dataclass, field +import re from typing import Optional, List, Dict from kiutils.items.common import Position, ColorRGBA, Stroke, Effects, Property @@ -741,34 +742,70 @@ def to_sexpr(self, indent=2, newline=True) -> str: @dataclass class SchematicSymbol(): """The ``symbol`` token in the symbol section of the schematic defines an instance of a symbol - from the library symbol section of the schematic + from the library symbol section of the schematic Documentation: https://dev-docs.kicad.org/en/file-formats/sexpr-schematic/#_symbol_section """ - libraryIdentifier: str = "" - """The ``libraryIdentifier`` defines which symbol in the library symbol section of the schematic - that this schematic symbol references""" + libraryNickname: Optional[str] = None + """The optional ``libraryNickname`` token defines which symbol library this symbol belongs to + and is a part of the ``id`` token""" + + entryName: str = None + """The ``entryName`` token defines the actual name of the symbol and is a part of the ``id`` + token""" + + @property + def libId(self) -> str: + """The ``lib_id`` token defines which symbol in the library symbol section of the schematic + this schematic symbol references. It is combined from both the ``libraryNickname`` and + ``entryName``. + + Returns: + - Symbol id in the following format: ``:`` or ````, + if ``libraryNickname`` token is not set. + """ + if self.libraryNickname: + return f'{self.libraryNickname}:{self.entryName}' + else: + return f'{self.entryName}' + + @libId.setter + def libId(self, symbol_id: str): + """Sets the ``lib_id`` token and parses its contents into the ``libraryNickname`` and + ``entryName`` token. + + Args: + - symbol_id (str): The symbol id in the following format: ``:`` + or only ```` + """ + parse_symbol_id = re.match(r"^(.+?):(.+?)$", symbol_id) + if parse_symbol_id: + self.libraryNickname = parse_symbol_id.group(1) + self.entryName = parse_symbol_id.group(2) + else: + self.libraryNickname = None + self.entryName = symbol_id position: Position = field(default_factory=lambda: Position()) """The ``position`` defines the X and Y coordinates and angle of rotation of the symbol""" unit: Optional[int] = None - """The optional ``unit`` token attribute defines which unit in the symbol library definition that the - schematic symbol represents""" + """The optional ``unit`` token attribute defines which unit in the symbol library definition + that the schematic symbol represents""" inBom: bool = False """The ``in_bom`` token attribute determines whether the schematic symbol appears in any bill - of materials output""" + of materials output""" onBoard: bool = False - """The on_board token attribute determines if the footprint associated with the symbol is - exported to the board via the netlist""" + """The ``on_board`` token attribute determines if the footprint associated with the symbol is + exported to the board via the netlist""" fieldsAutoplaced: bool = False """The ``fields_autoplaced`` is a flag that indicates that any PROPERTIES associated - with the global label have been place automatically""" + with the global label have been place automatically""" uuid: Optional[str] = "" """The optional `uuid` defines the universally unique identifier""" @@ -778,12 +815,12 @@ class SchematicSymbol(): pins: Dict[str, str] = field(default_factory=dict) """The ``pins`` token defines a dictionary with pin numbers in form of strings as keys and - uuid's as values""" + uuid's as values""" mirror: Optional[str] = None - """The ``mirror`` token defines if the symbol is mirrored in the schematic. Accepted values: ``x`` or ``y``. - When mirroring around the x and y axis at the same time use some additional rotation to get the correct - orientation of the symbol.""" + """The ``mirror`` token defines if the symbol is mirrored in the schematic. Accepted values: + ``x`` or ``y``. When mirroring around the x and y axis at the same time use some additional + rotation to get the correct orientation of the symbol.""" @classmethod def from_sexpr(cls, exp: list) -> SchematicSymbol: @@ -808,7 +845,7 @@ def from_sexpr(cls, exp: list) -> SchematicSymbol: object = cls() for item in exp[1:]: if item[0] == 'fields_autoplaced': object.fieldsAutoplaced = True - if item[0] == 'lib_id': object.libraryIdentifier = item[1] + if item[0] == 'lib_id': object.libId = item[1] if item[0] == 'uuid': object.uuid = item[1] if item[0] == 'unit': object.unit = item[1] if item[0] == 'in_bom': object.inBom = True if item[1] == 'yes' else False @@ -839,7 +876,7 @@ def to_sexpr(self, indent=2, newline=True) -> str: mirror = f' (mirror {self.mirror})' if self.mirror is not None else '' unit = f' (unit {self.unit})' if self.unit is not None else '' - expression = f'{indents}(symbol (lib_id "{dequote(self.libraryIdentifier)}") (at {self.position.X} {self.position.Y}{posA}){mirror}{unit}\n' + expression = f'{indents}(symbol (lib_id "{dequote(self.libId)}") (at {self.position.X} {self.position.Y}{posA}){mirror}{unit}\n' expression += f'{indents} (in_bom {inBom}) (on_board {onBoard}){fa}\n' if self.uuid: expression += f'{indents} (uuid {self.uuid})\n' @@ -850,6 +887,13 @@ def to_sexpr(self, indent=2, newline=True) -> str: expression += f'{indents}){endline}' return expression + +test = SchematicSymbol() + +test.id = "2" +ftest = test.id + + @dataclass class HierarchicalPin(): """The ``pin`` token in a sheet object defines an electrical connection between the sheet in a diff --git a/tests/test_schematic.py b/tests/test_schematic.py index 7de49da..a44f893 100644 --- a/tests/test_schematic.py +++ b/tests/test_schematic.py @@ -72,5 +72,5 @@ def test_renameSymbolIdTokenInSchematic(self): self.testData.pathToTestFile = path.join(SCHEMATIC_BASE, 'test_renameSymbolIdTokenInSchematic') schematic = Schematic().from_file(self.testData.pathToTestFile) schematic.libSymbols[0].id = "RenamedSwitch:SW_Coded_New" - schematic.schematicSymbols[0].libraryIdentifier = "SwitchRenamed:SW_Coded_2" + schematic.schematicSymbols[0].libId = "SwitchRenamed:SW_Coded_2" self.assertTrue(to_file_and_compare(schematic, self.testData)) From b5defec75b1ec4d0c204edab025a1814af35b8e9 Mon Sep 17 00:00:00 2001 From: Marvin Mager <99667992+mvnmgrx@users.noreply.github.com> Date: Sat, 18 Feb 2023 13:59:09 +0100 Subject: [PATCH 02/10] Tests: Added more tests for unsetting library ids --- tests/test_schematic.py | 6 ++++-- .../test_renameSymbolIdTokenInSchematic | 16 ++++++++++++++++ .../test_renameSymbolIdTokenInSchematic.expected | 16 ++++++++++++++++ 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/tests/test_schematic.py b/tests/test_schematic.py index a44f893..2dde7bd 100644 --- a/tests/test_schematic.py +++ b/tests/test_schematic.py @@ -67,10 +67,12 @@ def test_hierarchicalSchematicWithAllPrimitives(self): self.assertTrue(to_file_and_compare(schematic, self.testData)) def test_renameSymbolIdTokenInSchematic(self): - """Tests if renaming schematic symbols as well as normal symbols using their ID token works - as expected. Checks that the ``Value`` property does not change.""" + """Tests if renaming (setting and unsetting) schematic symbols as well as normal symbols + using their ID token works as expected. Checks that the ``Value`` property does not change.""" self.testData.pathToTestFile = path.join(SCHEMATIC_BASE, 'test_renameSymbolIdTokenInSchematic') schematic = Schematic().from_file(self.testData.pathToTestFile) schematic.libSymbols[0].id = "RenamedSwitch:SW_Coded_New" + schematic.libSymbols[1].id = "Unset_Lib_Id" schematic.schematicSymbols[0].libId = "SwitchRenamed:SW_Coded_2" + schematic.schematicSymbols[1].libId = "Unset_Lib_Id" self.assertTrue(to_file_and_compare(schematic, self.testData)) diff --git a/tests/testdata/schematic/test_renameSymbolIdTokenInSchematic b/tests/testdata/schematic/test_renameSymbolIdTokenInSchematic index 7530705..921c41a 100644 --- a/tests/testdata/schematic/test_renameSymbolIdTokenInSchematic +++ b/tests/testdata/schematic/test_renameSymbolIdTokenInSchematic @@ -14,6 +14,15 @@ (symbol "SW_Coded_1_1" ) ) + (symbol "SomeLibNickName:Unset_Lib_Id" + (property "Value" "SW_Coded" (id 1) (at -6.35 -7.62 0) + (effects (font (size 1.27 1.27)) (justify left)) + ) + (symbol "SW_Coded_New_0_1" + ) + (symbol "SW_Coded_New_1_1" + ) + ) ) (symbol (lib_id "Switch:SW_Coded") (at 111.76 163.83 0) @@ -22,4 +31,11 @@ (effects (font (size 1.27 1.27)) (justify right)) ) ) + + (symbol (lib_id "Switch:Some_Component") (at 111.76 163.83 0) + (in_bom no) (on_board no) + (property "Value" "Some_Component" (id 1) (at 103.505 165.7349 0) + (effects (font (size 1.27 1.27)) (justify right)) + ) + ) ) diff --git a/tests/testdata/schematic/test_renameSymbolIdTokenInSchematic.expected b/tests/testdata/schematic/test_renameSymbolIdTokenInSchematic.expected index 38c3124..05a2472 100644 --- a/tests/testdata/schematic/test_renameSymbolIdTokenInSchematic.expected +++ b/tests/testdata/schematic/test_renameSymbolIdTokenInSchematic.expected @@ -14,6 +14,15 @@ (symbol "SW_Coded_New_1_1" ) ) + (symbol "Unset_Lib_Id" + (property "Value" "SW_Coded" (id 1) (at -6.35 -7.62 0) + (effects (font (size 1.27 1.27)) (justify left)) + ) + (symbol "Unset_Lib_Id_0_1" + ) + (symbol "Unset_Lib_Id_1_1" + ) + ) ) (symbol (lib_id "SwitchRenamed:SW_Coded_2") (at 111.76 163.83 0) @@ -22,4 +31,11 @@ (effects (font (size 1.27 1.27)) (justify right)) ) ) + + (symbol (lib_id "Unset_Lib_Id") (at 111.76 163.83 0) + (in_bom no) (on_board no) + (property "Value" "Some_Component" (id 1) (at 103.505 165.7349 0) + (effects (font (size 1.27 1.27)) (justify right)) + ) + ) ) From 8d5fc56c0762be0a6809887411e07cfe182d76db Mon Sep 17 00:00:00 2001 From: Marvin Mager <99667992+mvnmgrx@users.noreply.github.com> Date: Sat, 18 Feb 2023 14:15:45 +0100 Subject: [PATCH 03/10] Tests: Added unsetting ID token tests for symbols --- src/kiutils/symbol.py | 16 +++--- tests/test_schematic.py | 8 +-- tests/test_symbol.py | 19 ++++++- ...st_renameSymbolIdTokenInSchematic.expected | 1 + .../test_createNewTopLevelSymbolFromChild | 16 ++++++ ..._createNewTopLevelSymbolFromChild.expected | 25 +++++++++ .../symbol/test_renameParentIdUsingIdToken | 55 +++++++++++++++++++ .../test_renameParentIdUsingIdToken.expected | 55 +++++++++++++++++++ 8 files changed, 181 insertions(+), 14 deletions(-) create mode 100644 tests/testdata/symbol/test_createNewTopLevelSymbolFromChild create mode 100644 tests/testdata/symbol/test_createNewTopLevelSymbolFromChild.expected diff --git a/src/kiutils/symbol.py b/src/kiutils/symbol.py index c4a5254..87da582 100644 --- a/src/kiutils/symbol.py +++ b/src/kiutils/symbol.py @@ -184,7 +184,7 @@ def to_sexpr(self, indent: int = 4, newline: bool = True) -> str: @dataclass class Symbol(): """The ``symbol`` token defines a symbol or sub-unit of a parent symbol. There can be zero or more - ``symbol`` tokens in a symbol library file. + ``symbol`` tokens in a symbol library file. Documentation: https://dev-docs.kicad.org/en/file-formats/sexpr-intro/index.html#_symbols @@ -194,15 +194,15 @@ class Symbol(): "UNIT_ID" for each unit embedded in a parent symbol. Library identifiers are only valid it top level symbols and unit identifiers are on valid as unit symbols inside a parent symbol.""" @property - def id(self): + def libId(self): unit_style_ids = f"_{self.unitId}_{self.styleId}" if (self.unitId is not None and self.styleId is not None) else "" if self.libraryNickname: return f'{self.libraryNickname}:{self.entryName}{unit_style_ids}' else: return f'{self.entryName}{unit_style_ids}' - @id.setter - def id(self, symbol_id): + @libId.setter + def libId(self, symbol_id): # Split library id into nickname, entry name, unit id and style id (if any) parse_symbol_id = re.match(r"^(.+?):(.+?)_(\d+?)_(\d+?)$", symbol_id) if parse_symbol_id: @@ -286,7 +286,7 @@ def id(self, symbol_id): """The ``pins`` section is a list of pins that are used by the symbol. This section can be empty if the symbol does not have any pins.""" - units: List = field(default_factory=list) + units: List[Symbol] = field(default_factory=list) """The ``units`` can be one or more child symbol tokens embedded in a parent symbol""" unitId: Optional[int] = None @@ -316,7 +316,7 @@ def from_sexpr(cls, exp: list) -> Symbol: raise Exception("Expression does not have the correct type") object = cls() - object.id = exp[1] + object.libId = exp[1] for item in exp[2:]: if item[0] == 'extends': object.extends = item[1] if item[0] == 'pin_numbers': @@ -364,7 +364,7 @@ def create_new(cls, id: str, reference: str, value: str, symbol = cls() symbol.inBom = True symbol.onBoard = True - symbol.id = id + symbol.libId = id symbol.properties.extend( [ Property(key = "Reference", value = reference, id = 0, @@ -406,7 +406,7 @@ def to_sexpr(self, indent: int = 2, newline: bool = True) -> str: pinnumbers = f' (pin_numbers hide)' if self.hidePinNumbers else '' extends = f' (extends "{dequote(self.extends)}")' if self.extends is not None else '' - expression = f'{indents}(symbol "{dequote(self.id)}"{extends}{power}{pinnumbers}{pinnames}{inbom}{onboard}\n' + expression = f'{indents}(symbol "{dequote(self.libId)}"{extends}{power}{pinnumbers}{pinnames}{inbom}{onboard}\n' for item in self.properties: expression += item.to_sexpr(indent+2) for item in self.graphicItems: diff --git a/tests/test_schematic.py b/tests/test_schematic.py index 2dde7bd..b04b723 100644 --- a/tests/test_schematic.py +++ b/tests/test_schematic.py @@ -71,8 +71,8 @@ def test_renameSymbolIdTokenInSchematic(self): using their ID token works as expected. Checks that the ``Value`` property does not change.""" self.testData.pathToTestFile = path.join(SCHEMATIC_BASE, 'test_renameSymbolIdTokenInSchematic') schematic = Schematic().from_file(self.testData.pathToTestFile) - schematic.libSymbols[0].id = "RenamedSwitch:SW_Coded_New" - schematic.libSymbols[1].id = "Unset_Lib_Id" - schematic.schematicSymbols[0].libId = "SwitchRenamed:SW_Coded_2" - schematic.schematicSymbols[1].libId = "Unset_Lib_Id" + schematic.libSymbols[0].libId = "RenamedSwitch:SW_Coded_New" # Setting library nickname + schematic.libSymbols[1].libId = "Unset_Lib_Id" # Unsetting library nickname + schematic.schematicSymbols[0].libId = "SwitchRenamed:SW_Coded_2" # Setting library nickname + schematic.schematicSymbols[1].libId = "Unset_Lib_Id" # Unsetting library nickname self.assertTrue(to_file_and_compare(schematic, self.testData)) diff --git a/tests/test_symbol.py b/tests/test_symbol.py index d30d461..ea7c47e 100644 --- a/tests/test_symbol.py +++ b/tests/test_symbol.py @@ -13,6 +13,7 @@ from tests.testfunctions import to_file_and_compare, prepare_test, cleanup_after_test, TEST_BASE from kiutils.symbol import SymbolLib, Symbol from kiutils.misc.config import KIUTILS_CREATE_NEW_VERSION_STR +from kiutils.utils.sexpr import parse_sexp SYMBOL_BASE = path.join(TEST_BASE, 'symbol') @@ -93,8 +94,22 @@ def test_renameParentIdUsingIdToken(self): correctly as well as the ``Value`` property stayed the same.""" self.testData.pathToTestFile = path.join(SYMBOL_BASE, 'test_renameParentIdUsingIdToken') symbolLib = SymbolLib().from_file(self.testData.pathToTestFile) - symbol = symbolLib.symbols[0] - symbol.id = 'ExampleLibrary:AD2023' + symbolLib.symbols[0].libId = 'ExampleLibrary:AD2023' # Setting library nickname + symbolLib.symbols[1].libId = 'AD2023' # Unsetting library nickname + self.assertTrue(to_file_and_compare(symbolLib, self.testData)) + + def test_createNewTopLevelSymbolFromChild(self): + """Take a child symbol, rename its library id and make a new top-level symbol out of it. + Tests if resetting both ``unitId`` and ``styleId`` works.""" + self.testData.pathToTestFile = path.join(SYMBOL_BASE, 'test_createNewTopLevelSymbolFromChild') + symbolLib = SymbolLib().from_file(self.testData.pathToTestFile) + + # Copy the symbol + childSymbol = Symbol.from_sexpr(parse_sexp(symbolLib.symbols[0].units[0].to_sexpr())) + + # Rename it and save it as a new top-level symbol to the library + childSymbol.libId = "SomeNewName:AD2023" + symbolLib.symbols.append(childSymbol) self.assertTrue(to_file_and_compare(symbolLib, self.testData)) def test_mergeLibraries(self): diff --git a/tests/testdata/schematic/test_renameSymbolIdTokenInSchematic.expected b/tests/testdata/schematic/test_renameSymbolIdTokenInSchematic.expected index 05a2472..cb17373 100644 --- a/tests/testdata/schematic/test_renameSymbolIdTokenInSchematic.expected +++ b/tests/testdata/schematic/test_renameSymbolIdTokenInSchematic.expected @@ -14,6 +14,7 @@ (symbol "SW_Coded_New_1_1" ) ) + (symbol "Unset_Lib_Id" (property "Value" "SW_Coded" (id 1) (at -6.35 -7.62 0) (effects (font (size 1.27 1.27)) (justify left)) diff --git a/tests/testdata/symbol/test_createNewTopLevelSymbolFromChild b/tests/testdata/symbol/test_createNewTopLevelSymbolFromChild new file mode 100644 index 0000000..1af97f0 --- /dev/null +++ b/tests/testdata/symbol/test_createNewTopLevelSymbolFromChild @@ -0,0 +1,16 @@ +(kicad_symbol_lib (version 20211014) (generator kicad_symbol_editor) + (symbol "AD1853" (power) (pin_numbers hide) (pin_names (offset 0.635)) (in_bom no) (on_board no) + (property "Value" "AD1853" (id 1) (at 0 6.985 0) + (effects (font (size 1.27 1.27))) + ) + (symbol "AD1853_0_0" + (text "NormalStuff" (at 0 19.05 0) + (effects (font (size 1.27 1.27))) + ) + (pin input line (at 13.335 13.97 0) (length 2.54) + (name "NormalPin" (effects (font (size 1.27 1.27)))) + (number "123" (effects (font (size 1.27 1.27)))) + ) + ) + ) +) \ No newline at end of file diff --git a/tests/testdata/symbol/test_createNewTopLevelSymbolFromChild.expected b/tests/testdata/symbol/test_createNewTopLevelSymbolFromChild.expected new file mode 100644 index 0000000..12e15f2 --- /dev/null +++ b/tests/testdata/symbol/test_createNewTopLevelSymbolFromChild.expected @@ -0,0 +1,25 @@ +(kicad_symbol_lib (version 20211014) (generator kicad_symbol_editor) + (symbol "AD1853" (power) (pin_numbers hide) (pin_names (offset 0.635)) (in_bom no) (on_board no) + (property "Value" "AD1853" (id 1) (at 0 6.985 0) + (effects (font (size 1.27 1.27))) + ) + (symbol "AD1853_0_0" + (text "NormalStuff" (at 0 19.05 0) + (effects (font (size 1.27 1.27))) + ) + (pin input line (at 13.335 13.97 0) (length 2.54) + (name "NormalPin" (effects (font (size 1.27 1.27)))) + (number "123" (effects (font (size 1.27 1.27)))) + ) + ) + ) + (symbol "SomeNewName:AD2023" + (text "NormalStuff" (at 0 19.05 0) + (effects (font (size 1.27 1.27))) + ) + (pin input line (at 13.335 13.97 0) (length 2.54) + (name "NormalPin" (effects (font (size 1.27 1.27)))) + (number "123" (effects (font (size 1.27 1.27)))) + ) + ) +) diff --git a/tests/testdata/symbol/test_renameParentIdUsingIdToken b/tests/testdata/symbol/test_renameParentIdUsingIdToken index 9423ae9..b1165f6 100644 --- a/tests/testdata/symbol/test_renameParentIdUsingIdToken +++ b/tests/testdata/symbol/test_renameParentIdUsingIdToken @@ -54,4 +54,59 @@ ) ) ) + (symbol "Has_Library_Nick_Set:AD1853JRS" (power) (pin_numbers hide) (pin_names (offset 0.635)) (in_bom no) (on_board no) + (property "Value" "AD1853" (id 1) (at 0 13.97 0) + (effects (font (size 1.27 1.27))) + ) + (symbol "AD1853JRS_0_0" + (text "Top Unit" (at 0 8.255 0) + (effects (font (size 1.27 1.27))) + ) + ) + (symbol "AD1853JRS_1_0" + (text "Unit A" (at 0 8.255 0) + (effects (font (size 1.27 1.27))) + ) + ) + (symbol "AD1853JRS_1_1" + (text "Unit A, Normal" (at 0 8.255 0) + (effects (font (size 1.27 1.27))) + ) + ) + (symbol "AD1853JRS_1_2" + (text "Unit A, DeMorgan" (at -0.635 7.62 0) + (effects (font (size 1.27 1.27))) + ) + ) + (symbol "AD1853JRS_2_0" + (text "Unit B" (at 0 8.255 0) + (effects (font (size 1.27 1.27))) + ) + ) + (symbol "AD1853JRS_2_1" + (text "Unit B, Normal" (at 0 8.255 0) + (effects (font (size 1.27 1.27))) + ) + ) + (symbol "AD1853JRS_2_2" + (text "Unit B, DeMorgan" (at -0.635 7.62 0) + (effects (font (size 1.27 1.27))) + ) + ) + (symbol "AD1853JRS_3_0" + (text "Unit C" (at 0 8.255 0) + (effects (font (size 1.27 1.27))) + ) + ) + (symbol "AD1853JRS_3_1" + (text "Unit C, Normal" (at 0 8.255 0) + (effects (font (size 1.27 1.27))) + ) + ) + (symbol "AD1853JRS_3_2" + (text "Unit C, DeMorgan" (at -0.635 7.62 0) + (effects (font (size 1.27 1.27))) + ) + ) + ) ) diff --git a/tests/testdata/symbol/test_renameParentIdUsingIdToken.expected b/tests/testdata/symbol/test_renameParentIdUsingIdToken.expected index 04bf7b9..d4b1408 100644 --- a/tests/testdata/symbol/test_renameParentIdUsingIdToken.expected +++ b/tests/testdata/symbol/test_renameParentIdUsingIdToken.expected @@ -54,4 +54,59 @@ ) ) ) + (symbol "AD2023" (power) (pin_numbers hide) (pin_names (offset 0.635)) (in_bom no) (on_board no) + (property "Value" "AD1853" (id 1) (at 0 13.97 0) + (effects (font (size 1.27 1.27))) + ) + (symbol "AD2023_0_0" + (text "Top Unit" (at 0 8.255 0) + (effects (font (size 1.27 1.27))) + ) + ) + (symbol "AD2023_1_0" + (text "Unit A" (at 0 8.255 0) + (effects (font (size 1.27 1.27))) + ) + ) + (symbol "AD2023_1_1" + (text "Unit A, Normal" (at 0 8.255 0) + (effects (font (size 1.27 1.27))) + ) + ) + (symbol "AD2023_1_2" + (text "Unit A, DeMorgan" (at -0.635 7.62 0) + (effects (font (size 1.27 1.27))) + ) + ) + (symbol "AD2023_2_0" + (text "Unit B" (at 0 8.255 0) + (effects (font (size 1.27 1.27))) + ) + ) + (symbol "AD2023_2_1" + (text "Unit B, Normal" (at 0 8.255 0) + (effects (font (size 1.27 1.27))) + ) + ) + (symbol "AD2023_2_2" + (text "Unit B, DeMorgan" (at -0.635 7.62 0) + (effects (font (size 1.27 1.27))) + ) + ) + (symbol "AD2023_3_0" + (text "Unit C" (at 0 8.255 0) + (effects (font (size 1.27 1.27))) + ) + ) + (symbol "AD2023_3_1" + (text "Unit C, Normal" (at 0 8.255 0) + (effects (font (size 1.27 1.27))) + ) + ) + (symbol "AD2023_3_2" + (text "Unit C, DeMorgan" (at -0.635 7.62 0) + (effects (font (size 1.27 1.27))) + ) + ) + ) ) From 9db0bf524ccf5ca9f1a30c5d1e8a87365c74cd65 Mon Sep 17 00:00:00 2001 From: Marvin Mager <99667992+mvnmgrx@users.noreply.github.com> Date: Sat, 18 Feb 2023 14:55:49 +0100 Subject: [PATCH 04/10] Symbol: Renamed id to libId and reworked libId.setter --- src/kiutils/items/schitems.py | 5 +- src/kiutils/symbol.py | 112 +++++++++++++++++++++++----------- 2 files changed, 79 insertions(+), 38 deletions(-) diff --git a/src/kiutils/items/schitems.py b/src/kiutils/items/schitems.py index f08790a..7c3262a 100644 --- a/src/kiutils/items/schitems.py +++ b/src/kiutils/items/schitems.py @@ -759,8 +759,9 @@ class SchematicSymbol(): @property def libId(self) -> str: """The ``lib_id`` token defines which symbol in the library symbol section of the schematic - this schematic symbol references. It is combined from both the ``libraryNickname`` and - ``entryName``. + this schematic symbol references. In ``kiutils``, the ``lib_id`` token is a combination of + both the ``libraryNickname`` and ``entryName`` token. Setting the ``lib_id`` token will + update those tokens accordingly. Returns: - Symbol id in the following format: ``:`` or ````, diff --git a/src/kiutils/symbol.py b/src/kiutils/symbol.py index 87da582..a7abdf2 100644 --- a/src/kiutils/symbol.py +++ b/src/kiutils/symbol.py @@ -190,55 +190,101 @@ class Symbol(): https://dev-docs.kicad.org/en/file-formats/sexpr-intro/index.html#_symbols """ - """Each symbol must have a unique "LIBRARY_ID" for each top level symbol in the library or a unique - "UNIT_ID" for each unit embedded in a parent symbol. Library identifiers are only valid it top - level symbols and unit identifiers are on valid as unit symbols inside a parent symbol.""" + """Each symbol must have """ @property - def libId(self): - unit_style_ids = f"_{self.unitId}_{self.styleId}" if (self.unitId is not None and self.styleId is not None) else "" + def libId(self) -> str: + """The ``lib_id`` token defines a unique "LIBRARY_ID" for each top level symbol in the + library or a unique "UNIT_ID" for each unit embedded in a parent symbol. Library identifiers + are only valid it top level symbols and unit identifiers are on valid as unit symbols inside + a parent symbol. + + The following conventions apply: + - "LIBRARY_ID" (top-level symbol): ``[:]`` (the library + nickname part is optional here) + - "UNIT_ID" (child symbol): ``__`` + + In ``kiutils``, the ``lib_id`` token is a combination of ``libraryNickname``, ``entryName``, + ``unitId`` and ``styleId`` tokens. Setting the ``lib_id`` token will update all those tokens + accordingly. + + Returns: + - If the ``libraryNickname`` is set: ``:`` + - If the ``libraryNickname`` is ``None``: ```` or ``__``, + depending if these tokens are set. + """ + if (self.unitId is not None and self.styleId is not None): + unit_style_ids = f"_{self.unitId}_{self.styleId}" + else: + unit_style_ids = "" + if self.libraryNickname: - return f'{self.libraryNickname}:{self.entryName}{unit_style_ids}' + return f'{self.libraryNickname}:{self.entryName}' else: return f'{self.entryName}{unit_style_ids}' @libId.setter - def libId(self, symbol_id): + def libId(self, symbol_id: str): + """Sets the ``lib_id`` token and parses its contents into the ``libraryNickname``, + ``entryName``, ``unitId`` and ``styleId`` token. + + See self.libId property description for more information. + + Args: + - symbol_id (str): The symbol id in the following format: ``:``, + ``__`` or only ````, depending on if the symbol + is a top-level symbol or a child symbol + + Raises: + - Exception: If the given ID is neither a top-level nor a child symbol + """ # Split library id into nickname, entry name, unit id and style id (if any) + # Check if the given ID has the correct format parse_symbol_id = re.match(r"^(.+?):(.+?)_(\d+?)_(\d+?)$", symbol_id) if parse_symbol_id: - self.libraryNickname = parse_symbol_id.group(1) - self.entryName = parse_symbol_id.group(2) - self.unitId = int(parse_symbol_id.group(3)) - self.styleId = int(parse_symbol_id.group(4)) + raise Exception("Given ID is neither to a top-level nor to a child symbol") else: + # Try to parse the given ID parse_symbol_id = re.match(r"^(.+?):(.+?)$", symbol_id) if parse_symbol_id: + # The symbol is a top-level symbol with a library nickname self.libraryNickname = parse_symbol_id.group(1) - entryName_t = parse_symbol_id.group(2) + self.entryName = parse_symbol_id.group(2) + self.unitId = None + self.styleId = None else: - entryName_t = symbol_id - - parse_symbol_id = re.match(r"^(.+?)_(\d+?)_(\d+?)$", entryName_t) - if parse_symbol_id: - self.entryName = parse_symbol_id.group(1) - self.unitId = int(parse_symbol_id.group(2)) - self.styleId = int(parse_symbol_id.group(3)) - else: - if self.libraryNickname: - self.entryName = entryName_t + parse_symbol_id = re.match(r"^(.+?)_(\d+?)_(\d+?)$", symbol_id) + if parse_symbol_id: + # The symbol is a child symbol + self.libraryNickname = None + self.entryName = parse_symbol_id.group(1) + self.unitId = int(parse_symbol_id.group(2)) + self.styleId = int(parse_symbol_id.group(3)) else: + # The symbol is a top-level symbol without a library nickname + self.libraryNickname = None self.entryName = symbol_id + self.unitId = None + self.styleId = None # Update units id to match parent id for unit in self.units: unit.entryName = self.entryName libraryNickname: Optional[str] = None + """The optional ``libraryNickname`` token defines which symbol library this symbol belongs to + and is a part of the ``id`` token""" + entryName: str = None - """ The schematic symbol library and printed circuit board footprint library file formats use library identifiers. - Library identifiers are defined as a quoted string using the "LIBRARY_NICKNAME:ENTRY_NAME" format where - "LIBRARY_NICKNAME" is the nickname of the library in the symbol or footprint library table and - "ENTRY_NAME" is the name of the symbol or footprint in the library separated by a colon. """ + """The ``entryName`` token defines the actual name of the symbol and is a part of the ``id`` + token""" + + unitId: Optional[int] = None + """The ``unitId`` token identifies which unit the symbol represents and is a part of + the ``id`` token""" + + styleId: Optional[int] = None + """The ``styleId`` token indicates which body style the unit represents and is a part of the + ``id`` token""" extends: Optional[str] = None """The optional ``extends`` token attribute defines the "LIBRARY_ID" of another symbol inside the @@ -289,12 +335,6 @@ def libId(self, symbol_id): units: List[Symbol] = field(default_factory=list) """The ``units`` can be one or more child symbol tokens embedded in a parent symbol""" - unitId: Optional[int] = None - """Unit identifier: an integer that identifies which unit the symbol represents""" - - styleId: Optional[int] = None - """Style identifier: indicates which body style the unit represents""" - @classmethod def from_sexpr(cls, exp: list) -> Symbol: """Convert the given S-Expression into a Symbol object @@ -347,7 +387,7 @@ def from_sexpr(cls, exp: list) -> Symbol: return object @classmethod - def create_new(cls, id: str, reference: str, value: str, + def create_new(cls, id: str, reference: str, value: str, footprint: str = "", datasheet: str = "") -> Symbol: """Creates a new empty symbol as KiCad would create it @@ -446,7 +486,7 @@ def from_file(cls, filepath: str, encoding: Optional[str] = None) -> SymbolLib: Args: - filepath (str): Path or path-like object that points to the file - - encoding (str, optional): Encoding of the input file. Defaults to None (platform + - encoding (str, optional): Encoding of the input file. Defaults to None (platform dependent encoding). Raises: @@ -495,9 +535,9 @@ def to_file(self, filepath = None, encoding: Optional[str] = None): """Save the object to a file in S-Expression format Args: - - filepath (str, optional): Path-like string to the file. Defaults to None. If not set, + - filepath (str, optional): Path-like string to the file. Defaults to None. If not set, the attribute ``self.filePath`` will be used instead. - - encoding (str, optional): Encoding of the output file. Defaults to None (platform + - encoding (str, optional): Encoding of the output file. Defaults to None (platform dependent encoding). Raises: From f2f252ea574ae0bf2e7f313c47da1ebc0bf9bd0d Mon Sep 17 00:00:00 2001 From: Marvin Mager <99667992+mvnmgrx@users.noreply.github.com> Date: Sat, 18 Feb 2023 15:17:58 +0100 Subject: [PATCH 05/10] Footprint: Renamed libraryLink to libId, added tests --- src/kiutils/footprint.py | 53 ++++++++++++-- src/kiutils/items/schitems.py | 30 ++++---- tests/test_board.py | 10 ++- tests/test_footprint.py | 6 +- .../board/test_renameFootprintIdToken | 72 +++++++++++++++++++ .../test_renameFootprintIdToken.expected | 72 +++++++++++++++++++ 6 files changed, 217 insertions(+), 26 deletions(-) create mode 100644 tests/testdata/board/test_renameFootprintIdToken create mode 100644 tests/testdata/board/test_renameFootprintIdToken.expected diff --git a/src/kiutils/footprint.py b/src/kiutils/footprint.py index dc20ad8..ff02122 100644 --- a/src/kiutils/footprint.py +++ b/src/kiutils/footprint.py @@ -17,6 +17,7 @@ import calendar import datetime +import re from dataclasses import dataclass, field from typing import Optional, List, Dict from os import path @@ -643,9 +644,47 @@ class Footprint(): https://dev-docs.kicad.org/en/file-formats/sexpr-intro/index.html#_footprint """ - libraryLink: str = "" - """The ``libraryLink`` attribute defines the link to footprint library of the footprint. - This only applies to footprints defined in the board file format.""" + @property + def libId(self) -> str: + """The ``lib_id`` token defines the link to footprint library of the footprint. + This only applies to footprints defined in the board file format, in a regular footprint + file this id defines the footprint's name. In ``kiutils``, the token is a combination of + both the ``libraryNickname`` and ``entryName`` token. Setting the ``lib_id`` token will + update those tokens accordingly. + + Returns: + - Symbol id in the following format: ``:`` or ````, + if ``libraryNickname`` token is not set. + """ + if self.libraryNickname: + return f'{self.libraryNickname}:{self.entryName}' + else: + return f'{self.entryName}' + + @libId.setter + def libId(self, symbol_id: str): + """Sets the ``lib_id`` token and parses its contents into the ``libraryNickname`` and + ``entryName`` token. + + Args: + - symbol_id (str): The symbol id in the following format: ``:`` + or only ```` + """ + parse_symbol_id = re.match(r"^(.+?):(.+?)$", symbol_id) + if parse_symbol_id: + self.libraryNickname = parse_symbol_id.group(1) + self.entryName = parse_symbol_id.group(2) + else: + self.libraryNickname = None + self.entryName = symbol_id + + libraryNickname: Optional[str] = None + """The optional ``libraryNickname`` token defines which symbol library this symbol belongs to + and is a part of the ``id`` token""" + + entryName: str = None + """The ``entryName`` token defines the actual name of the symbol and is a part of the ``id`` + token""" version: Optional[str] = None """The ``version`` token attribute defines the symbol library version using the YYYYMMDD date format""" @@ -779,7 +818,7 @@ def from_sexpr(cls, exp: list) -> Footprint: raise Exception("Expression does not have the correct type") object = cls() - object.libraryLink = exp[1] + object.libId = exp[1] for item in exp[2:]: if not isinstance(item, list): if item == 'locked': object.locked = True @@ -864,7 +903,7 @@ def from_file(cls, filepath: str, encoding: Optional[str] = None) -> Footprint: return cls.from_sexpr(fpData) @classmethod - def create_new(cls, library_link: str, value: str, + def create_new(cls, library_id: str, value: str, type: str = 'other', reference: str = 'REF**') -> Footprint: """Creates a new empty footprint with its attributes set as KiCad would create it @@ -883,10 +922,10 @@ def create_new(cls, library_link: str, value: str, raise Exception("Unsupported type was given") fp = cls( - libraryLink = library_link, version = KIUTILS_CREATE_NEW_VERSION_STR, generator = 'kiutils' ) + fp.libId = library_id # Create text items that are created when adding a new footprint to a library fp.graphicItems.extend( @@ -955,7 +994,7 @@ def to_sexpr(self, indent=0, newline=True, layerInFirstLine=False) -> str: generator = f' (generator {self.generator})' if self.generator is not None else '' tstamp = f' (tstamp {self.tstamp})' if self.tstamp is not None else '' - expression = f'{indents}(footprint "{dequote(self.libraryLink)}"{locked}{placed}{version}{generator}' + expression = f'{indents}(footprint "{dequote(self.libId)}"{locked}{placed}{version}{generator}' if layerInFirstLine: expression += f' (layer "{dequote(self.layer)}")\n' else: diff --git a/src/kiutils/items/schitems.py b/src/kiutils/items/schitems.py index 7c3262a..b0ce82e 100644 --- a/src/kiutils/items/schitems.py +++ b/src/kiutils/items/schitems.py @@ -15,8 +15,8 @@ from __future__ import annotations -from dataclasses import dataclass, field import re +from dataclasses import dataclass, field from typing import Optional, List, Dict from kiutils.items.common import Position, ColorRGBA, Stroke, Effects, Property @@ -748,19 +748,11 @@ class SchematicSymbol(): https://dev-docs.kicad.org/en/file-formats/sexpr-schematic/#_symbol_section """ - libraryNickname: Optional[str] = None - """The optional ``libraryNickname`` token defines which symbol library this symbol belongs to - and is a part of the ``id`` token""" - - entryName: str = None - """The ``entryName`` token defines the actual name of the symbol and is a part of the ``id`` - token""" - @property def libId(self) -> str: """The ``lib_id`` token defines which symbol in the library symbol section of the schematic - this schematic symbol references. In ``kiutils``, the ``lib_id`` token is a combination of - both the ``libraryNickname`` and ``entryName`` token. Setting the ``lib_id`` token will + this schematic symbol references. In ``kiutils``, the ``lib_id`` token is a combination of + both the ``libraryNickname`` and ``entryName`` token. Setting the ``lib_id`` token will update those tokens accordingly. Returns: @@ -774,7 +766,7 @@ def libId(self) -> str: @libId.setter def libId(self, symbol_id: str): - """Sets the ``lib_id`` token and parses its contents into the ``libraryNickname`` and + """Sets the ``lib_id`` token and parses its contents into the ``libraryNickname`` and ``entryName`` token. Args: @@ -789,11 +781,19 @@ def libId(self, symbol_id: str): self.libraryNickname = None self.entryName = symbol_id + libraryNickname: Optional[str] = None + """The optional ``libraryNickname`` token defines which symbol library this symbol belongs to + and is a part of the ``id`` token""" + + entryName: str = None + """The ``entryName`` token defines the actual name of the symbol and is a part of the ``id`` + token""" + position: Position = field(default_factory=lambda: Position()) """The ``position`` defines the X and Y coordinates and angle of rotation of the symbol""" unit: Optional[int] = None - """The optional ``unit`` token attribute defines which unit in the symbol library definition + """The optional ``unit`` token attribute defines which unit in the symbol library definition that the schematic symbol represents""" inBom: bool = False @@ -819,8 +819,8 @@ def libId(self, symbol_id: str): uuid's as values""" mirror: Optional[str] = None - """The ``mirror`` token defines if the symbol is mirrored in the schematic. Accepted values: - ``x`` or ``y``. When mirroring around the x and y axis at the same time use some additional + """The ``mirror`` token defines if the symbol is mirrored in the schematic. Accepted values: + ``x`` or ``y``. When mirroring around the x and y axis at the same time use some additional rotation to get the correct orientation of the symbol.""" @classmethod diff --git a/tests/test_board.py b/tests/test_board.py index 131bd70..ce45a6c 100644 --- a/tests/test_board.py +++ b/tests/test_board.py @@ -75,4 +75,12 @@ def test_createEmptyBoard(self): self.testData.compareToTestFile = True self.testData.pathToTestFile = path.join(BOARD_BASE, 'test_createEmptyBoard') board = Board().create_new() - self.assertTrue(to_file_and_compare(board, self.testData)) \ No newline at end of file + self.assertTrue(to_file_and_compare(board, self.testData)) + + def test_footprintPadNewLines(self): + """Renames the libId token (setting and unsetting) of footprints on a board""" + self.testData.pathToTestFile = path.join(BOARD_BASE, 'test_renameFootprintIdToken') + board = Board().from_file(self.testData.pathToTestFile) + board.footprints[0].libId = "I_was_renamed:BUS_PCIexpress_x1" + board.footprints[1].libId = "I_was_added:BUS_PCIexpress_x1" + self.assertTrue(to_file_and_compare(board, self.testData)) diff --git a/tests/test_footprint.py b/tests/test_footprint.py index 93679d9..7e49d5c 100644 --- a/tests/test_footprint.py +++ b/tests/test_footprint.py @@ -55,7 +55,7 @@ def test_createNewFootprintTypeSMD(self): # Create footprint with correct type footprint = Footprint().create_new( type = 'smd', - library_link = 'empty-footprint-smd', + library_id = 'empty-footprint-smd', value = 'empty-footprint-smd' ) @@ -72,7 +72,7 @@ def test_createNewFootprintTypeTHT(self): # Create footprint with correct type footprint = Footprint().create_new( type = 'through_hole', - library_link = 'empty-footprint-through_hole', + library_id = 'empty-footprint-through_hole', value = 'empty-footprint-through_hole' ) @@ -89,7 +89,7 @@ def test_createNewFootprintTypeOther(self): # Create footprint with correct type footprint = Footprint().create_new( type = 'other', - library_link = 'empty-footprint-other', + library_id = 'empty-footprint-other', value = 'empty-footprint-other' ) # Set timestamps to be the same as in the expected test output diff --git a/tests/testdata/board/test_renameFootprintIdToken b/tests/testdata/board/test_renameFootprintIdToken new file mode 100644 index 0000000..d40bc36 --- /dev/null +++ b/tests/testdata/board/test_renameFootprintIdToken @@ -0,0 +1,72 @@ +(kicad_pcb (version 20211014) (generator pcbnew) + + (general + (thickness 1.6) + ) + + (paper "A4") + (layers + (0 "F.Cu" signal) + (31 "B.Cu" signal) + (32 "B.Adhes" user "B.Adhesive") + (33 "F.Adhes" user "F.Adhesive") + (34 "B.Paste" user) + (35 "F.Paste" user) + (36 "B.SilkS" user "B.Silkscreen") + (37 "F.SilkS" user "F.Silkscreen") + (38 "B.Mask" user) + (39 "F.Mask" user) + (40 "Dwgs.User" user "User.Drawings") + (41 "Cmts.User" user "User.Comments") + (42 "Eco1.User" user "User.Eco1") + (43 "Eco2.User" user "User.Eco2") + (44 "Edge.Cuts" user) + (45 "Margin" user) + (46 "B.CrtYd" user "B.Courtyard") + (47 "F.CrtYd" user "F.Courtyard") + (48 "B.Fab" user) + (49 "F.Fab" user) + (50 "User.1" user) + (51 "User.2" user) + (52 "User.3" user) + (53 "User.4" user) + (54 "User.5" user) + (55 "User.6" user) + (56 "User.7" user) + (57 "User.8" user) + (58 "User.9" user) + ) + + (setup + (pad_to_mask_clearance 0) + ) + + (net 0 "") + + (footprint "Connector_PCBEdge:BUS_PCIexpress_x1" (layer "F.Cu") + (tedit 62BC9CDF) (tstamp a6a16e4a-47c2-4244-ba11-78b40782f3f4) + (at 73.5 110.75) + (fp_text reference "REF**" (at 5 -3.5) (layer "F.SilkS") + (effects (font (size 1 1) (thickness 0.15))) + (tstamp 1fe1971a-7016-45bb-8345-401820ae3862) + ) + (fp_text value "BUS_PCIexpress_x1" (at 10.33 -8.01) (layer "F.Fab") + (effects (font (size 1 1) (thickness 0.15))) + (tstamp 4eab55a2-5545-47a3-b9b3-c4c469a39d25) + ) + ) + + (footprint "BUS_PCIexpress_x1" (layer "F.Cu") + (tedit 62BC9CDF) (tstamp a6a16e4a-47c2-4244-ba11-78b40782f3f4) + (at 73.5 110.75) + (fp_text reference "REF**" (at 5 -3.5) (layer "F.SilkS") + (effects (font (size 1 1) (thickness 0.15))) + (tstamp 1fe1971a-7016-45bb-8345-401820ae3862) + ) + (fp_text value "BUS_PCIexpress_x1" (at 10.33 -8.01) (layer "F.Fab") + (effects (font (size 1 1) (thickness 0.15))) + (tstamp 4eab55a2-5545-47a3-b9b3-c4c469a39d25) + ) + ) + +) diff --git a/tests/testdata/board/test_renameFootprintIdToken.expected b/tests/testdata/board/test_renameFootprintIdToken.expected new file mode 100644 index 0000000..bd4c9c7 --- /dev/null +++ b/tests/testdata/board/test_renameFootprintIdToken.expected @@ -0,0 +1,72 @@ +(kicad_pcb (version 20211014) (generator pcbnew) + + (general + (thickness 1.6) + ) + + (paper "A4") + (layers + (0 "F.Cu" signal) + (31 "B.Cu" signal) + (32 "B.Adhes" user "B.Adhesive") + (33 "F.Adhes" user "F.Adhesive") + (34 "B.Paste" user) + (35 "F.Paste" user) + (36 "B.SilkS" user "B.Silkscreen") + (37 "F.SilkS" user "F.Silkscreen") + (38 "B.Mask" user) + (39 "F.Mask" user) + (40 "Dwgs.User" user "User.Drawings") + (41 "Cmts.User" user "User.Comments") + (42 "Eco1.User" user "User.Eco1") + (43 "Eco2.User" user "User.Eco2") + (44 "Edge.Cuts" user) + (45 "Margin" user) + (46 "B.CrtYd" user "B.Courtyard") + (47 "F.CrtYd" user "F.Courtyard") + (48 "B.Fab" user) + (49 "F.Fab" user) + (50 "User.1" user) + (51 "User.2" user) + (52 "User.3" user) + (53 "User.4" user) + (54 "User.5" user) + (55 "User.6" user) + (56 "User.7" user) + (57 "User.8" user) + (58 "User.9" user) + ) + + (setup + (pad_to_mask_clearance 0) + ) + + (net 0 "") + + (footprint "I_was_renamed:BUS_PCIexpress_x1" (layer "F.Cu") + (tedit 62BC9CDF) (tstamp a6a16e4a-47c2-4244-ba11-78b40782f3f4) + (at 73.5 110.75) + (fp_text reference "REF**" (at 5 -3.5) (layer "F.SilkS") + (effects (font (size 1 1) (thickness 0.15))) + (tstamp 1fe1971a-7016-45bb-8345-401820ae3862) + ) + (fp_text value "BUS_PCIexpress_x1" (at 10.33 -8.01) (layer "F.Fab") + (effects (font (size 1 1) (thickness 0.15))) + (tstamp 4eab55a2-5545-47a3-b9b3-c4c469a39d25) + ) + ) + + (footprint "I_was_added:BUS_PCIexpress_x1" (layer "F.Cu") + (tedit 62BC9CDF) (tstamp a6a16e4a-47c2-4244-ba11-78b40782f3f4) + (at 73.5 110.75) + (fp_text reference "REF**" (at 5 -3.5) (layer "F.SilkS") + (effects (font (size 1 1) (thickness 0.15))) + (tstamp 1fe1971a-7016-45bb-8345-401820ae3862) + ) + (fp_text value "BUS_PCIexpress_x1" (at 10.33 -8.01) (layer "F.Fab") + (effects (font (size 1 1) (thickness 0.15))) + (tstamp 4eab55a2-5545-47a3-b9b3-c4c469a39d25) + ) + ) + +) From 9985fbecfa44f188289e97238546a494a3ae5611 Mon Sep 17 00:00:00 2001 From: Marvin Mager <99667992+mvnmgrx@users.noreply.github.com> Date: Sat, 18 Feb 2023 15:26:33 +0100 Subject: [PATCH 06/10] SchematicSymbol: Added lib_name token --- src/kiutils/items/schitems.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/kiutils/items/schitems.py b/src/kiutils/items/schitems.py index b0ce82e..5bf0b8d 100644 --- a/src/kiutils/items/schitems.py +++ b/src/kiutils/items/schitems.py @@ -789,6 +789,11 @@ def libId(self, symbol_id: str): """The ``entryName`` token defines the actual name of the symbol and is a part of the ``id`` token""" + libName: Optional[str] = None + """The optional ``lib_name`` token is only set when the symbol was edited in the schematic. + It may be set to ``_X`` where X is a unique number that specifies which variation + this symbol is of its original.""" + position: Position = field(default_factory=lambda: Position()) """The ``position`` defines the X and Y coordinates and angle of rotation of the symbol""" @@ -847,6 +852,7 @@ def from_sexpr(cls, exp: list) -> SchematicSymbol: for item in exp[1:]: if item[0] == 'fields_autoplaced': object.fieldsAutoplaced = True if item[0] == 'lib_id': object.libId = item[1] + if item[0] == 'lib_name': object.libName = item[1] if item[0] == 'uuid': object.uuid = item[1] if item[0] == 'unit': object.unit = item[1] if item[0] == 'in_bom': object.inBom = True if item[1] == 'yes' else False @@ -876,8 +882,9 @@ def to_sexpr(self, indent=2, newline=True) -> str: onBoard = 'yes' if self.onBoard else 'no' mirror = f' (mirror {self.mirror})' if self.mirror is not None else '' unit = f' (unit {self.unit})' if self.unit is not None else '' + lib_name = f' (lib_name {self.libName})' if self.libName is not None else '' - expression = f'{indents}(symbol (lib_id "{dequote(self.libId)}") (at {self.position.X} {self.position.Y}{posA}){mirror}{unit}\n' + expression = f'{indents}(symbol{lib_name} (lib_id "{dequote(self.libId)}") (at {self.position.X} {self.position.Y}{posA}){mirror}{unit}\n' expression += f'{indents} (in_bom {inBom}) (on_board {onBoard}){fa}\n' if self.uuid: expression += f'{indents} (uuid {self.uuid})\n' From a44b4cf76238afff269ffd07921bec8a68f5f8d8 Mon Sep 17 00:00:00 2001 From: Marvin Mager <99667992+mvnmgrx@users.noreply.github.com> Date: Mon, 20 Feb 2023 21:41:49 +0100 Subject: [PATCH 07/10] Docu: Simplified unittest description --- docs/usage/development.rst | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/docs/usage/development.rst b/docs/usage/development.rst index a5211b5..3563305 100644 --- a/docs/usage/development.rst +++ b/docs/usage/development.rst @@ -1,19 +1,14 @@ Development =========== -To start developing, clone the repository and install ``kiutils`` from source while being in the -repository root folder: +To start developing, clone the repository: .. code-block:: text git clone https://github.com/mvnmgrx/kiutils.git cd kiutils - pip install -e . -Doing it this way, changes in the source will be reflected to the current Python environment -automatically. No need to reinstall after making changes. - -For generating test reports as well as the documentation, install development requirements: +For generating test reports as well as the documentation, install the development requirements: .. code-block:: text @@ -22,17 +17,15 @@ For generating test reports as well as the documentation, install development re Tests ----- -To run the test framework and generate an HTML report, start the test script: +Unittests are used to test ``kiutils``. To run the test framework and generate an HTML report, start +the test script: .. code-block:: text python3 test.py -To only run the unittests, use: - -.. code-block:: text - - python3 -m unittest +When adding a feature to ``kiutils``, be sure to provide unittests that explicitly test the +functionality you want to implement. Generate documentation ---------------------- From b31bc1bd0783860fefd98193a13871fbe91ddbd4 Mon Sep 17 00:00:00 2001 From: Marvin Mager <99667992+mvnmgrx@users.noreply.github.com> Date: Mon, 20 Feb 2023 22:02:56 +0100 Subject: [PATCH 08/10] SchItems: Added dequote to lib_name token --- src/kiutils/items/schitems.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kiutils/items/schitems.py b/src/kiutils/items/schitems.py index 5bf0b8d..dd6c88d 100644 --- a/src/kiutils/items/schitems.py +++ b/src/kiutils/items/schitems.py @@ -882,7 +882,7 @@ def to_sexpr(self, indent=2, newline=True) -> str: onBoard = 'yes' if self.onBoard else 'no' mirror = f' (mirror {self.mirror})' if self.mirror is not None else '' unit = f' (unit {self.unit})' if self.unit is not None else '' - lib_name = f' (lib_name {self.libName})' if self.libName is not None else '' + lib_name = f' (lib_name "{dequote(self.libName)}")' if self.libName is not None else '' expression = f'{indents}(symbol{lib_name} (lib_id "{dequote(self.libId)}") (at {self.position.X} {self.position.Y}{posA}){mirror}{unit}\n' expression += f'{indents} (in_bom {inBom}) (on_board {onBoard}){fa}\n' From aea50bd4a9af663a296cb7cc656ffe5c66096eec Mon Sep 17 00:00:00 2001 From: Marvin Mager <99667992+mvnmgrx@users.noreply.github.com> Date: Mon, 20 Feb 2023 22:03:09 +0100 Subject: [PATCH 09/10] Tests: Added test for setting/unsetting the lib_name token --- tests/test_schematic.py | 8 +++++ .../schematic/test_setSymbolLibNameToken | 36 +++++++++++++++++++ .../test_setSymbolLibNameToken.expected | 36 +++++++++++++++++++ 3 files changed, 80 insertions(+) create mode 100644 tests/testdata/schematic/test_setSymbolLibNameToken create mode 100644 tests/testdata/schematic/test_setSymbolLibNameToken.expected diff --git a/tests/test_schematic.py b/tests/test_schematic.py index b04b723..8c141e7 100644 --- a/tests/test_schematic.py +++ b/tests/test_schematic.py @@ -76,3 +76,11 @@ def test_renameSymbolIdTokenInSchematic(self): schematic.schematicSymbols[0].libId = "SwitchRenamed:SW_Coded_2" # Setting library nickname schematic.schematicSymbols[1].libId = "Unset_Lib_Id" # Unsetting library nickname self.assertTrue(to_file_and_compare(schematic, self.testData)) + + def test_setSymbolLibNameToken(self): + """Tests if setting and unsetting the lib_name token generates the correct S-Expression""" + self.testData.pathToTestFile = path.join(SCHEMATIC_BASE, 'test_setSymbolLibNameToken') + schematic = Schematic().from_file(self.testData.pathToTestFile) + schematic.schematicSymbols[0].libName = f"{schematic.schematicSymbols[0].entryName}_1" + schematic.schematicSymbols[1].libName = None + self.assertTrue(to_file_and_compare(schematic, self.testData)) diff --git a/tests/testdata/schematic/test_setSymbolLibNameToken b/tests/testdata/schematic/test_setSymbolLibNameToken new file mode 100644 index 0000000..9a20963 --- /dev/null +++ b/tests/testdata/schematic/test_setSymbolLibNameToken @@ -0,0 +1,36 @@ +(kicad_sch (version 20211014) (generator kiutils) + (paper "A4") + (lib_symbols) + + (symbol (lib_id "symbols:AVR-JTAG-10") (at 92.71 127 0) (unit 1) + (in_bom no) (on_board yes) (fields_autoplaced) + (uuid 5527a2f2-4e47-44ad-aee4-e8fe938891bd) + (property "Reference" "X?" (id 0) (at 92.71 113.03 0)) + (property "Value" "AVR-JTAG-10" (id 1) (at 92.71 116.84 0)) + (property "Footprint" "footprints:Tag-Connect_TC2050-IDC-FP_2x05_P1.27mm_Vertical" (id 2) (at 92.71 140.335 0) + (effects (font (size 1.27 1.27)) hide) + ) + (property "Datasheet" " ~" (id 3) (at 92.075 137.795 0) + (effects (font (size 1.27 1.27)) hide) + ) + (pin "9" (uuid 8ee4bd36-d492-4ac0-be3e-4aebbd54e661)) + ) + + (symbol (lib_name "AVR-JTAG-10_1") (lib_id "symbols:AVR-JTAG-10") (at 92.71 127 0) (unit 1) + (in_bom no) (on_board yes) (fields_autoplaced) + (uuid 5527a2f2-4e47-44ad-aee4-e8fe938891bd) + (property "Reference" "X?" (id 0) (at 92.71 113.03 0)) + (property "Value" "AVR-JTAG-10" (id 1) (at 92.71 116.84 0)) + (property "Footprint" "footprints:Tag-Connect_TC2050-IDC-FP_2x05_P1.27mm_Vertical" (id 2) (at 92.71 140.335 0) + (effects (font (size 1.27 1.27)) hide) + ) + (property "Datasheet" " ~" (id 3) (at 92.075 137.795 0) + (effects (font (size 1.27 1.27)) hide) + ) + (pin "9" (uuid 8ee4bd36-d492-4ac0-be3e-4aebbd54e661)) + ) + + (sheet_instances + (path "/" (page "1")) + ) +) diff --git a/tests/testdata/schematic/test_setSymbolLibNameToken.expected b/tests/testdata/schematic/test_setSymbolLibNameToken.expected new file mode 100644 index 0000000..fa7495e --- /dev/null +++ b/tests/testdata/schematic/test_setSymbolLibNameToken.expected @@ -0,0 +1,36 @@ +(kicad_sch (version 20211014) (generator kiutils) + (paper "A4") + (lib_symbols) + + (symbol (lib_name "AVR-JTAG-10_1") (lib_id "symbols:AVR-JTAG-10") (at 92.71 127 0) (unit 1) + (in_bom no) (on_board yes) (fields_autoplaced) + (uuid 5527a2f2-4e47-44ad-aee4-e8fe938891bd) + (property "Reference" "X?" (id 0) (at 92.71 113.03 0)) + (property "Value" "AVR-JTAG-10" (id 1) (at 92.71 116.84 0)) + (property "Footprint" "footprints:Tag-Connect_TC2050-IDC-FP_2x05_P1.27mm_Vertical" (id 2) (at 92.71 140.335 0) + (effects (font (size 1.27 1.27)) hide) + ) + (property "Datasheet" " ~" (id 3) (at 92.075 137.795 0) + (effects (font (size 1.27 1.27)) hide) + ) + (pin "9" (uuid 8ee4bd36-d492-4ac0-be3e-4aebbd54e661)) + ) + + (symbol (lib_id "symbols:AVR-JTAG-10") (at 92.71 127 0) (unit 1) + (in_bom no) (on_board yes) (fields_autoplaced) + (uuid 5527a2f2-4e47-44ad-aee4-e8fe938891bd) + (property "Reference" "X?" (id 0) (at 92.71 113.03 0)) + (property "Value" "AVR-JTAG-10" (id 1) (at 92.71 116.84 0)) + (property "Footprint" "footprints:Tag-Connect_TC2050-IDC-FP_2x05_P1.27mm_Vertical" (id 2) (at 92.71 140.335 0) + (effects (font (size 1.27 1.27)) hide) + ) + (property "Datasheet" " ~" (id 3) (at 92.075 137.795 0) + (effects (font (size 1.27 1.27)) hide) + ) + (pin "9" (uuid 8ee4bd36-d492-4ac0-be3e-4aebbd54e661)) + ) + + (sheet_instances + (path "/" (page "1")) + ) +) From 517bb1e5aec19981c4255b731c1123eb16005c9e Mon Sep 17 00:00:00 2001 From: Marvin Mager <99667992+mvnmgrx@users.noreply.github.com> Date: Mon, 20 Feb 2023 22:04:10 +0100 Subject: [PATCH 10/10] SchItems: Removed accidental debug stuff --- src/kiutils/items/schitems.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/kiutils/items/schitems.py b/src/kiutils/items/schitems.py index dd6c88d..67f9908 100644 --- a/src/kiutils/items/schitems.py +++ b/src/kiutils/items/schitems.py @@ -790,8 +790,8 @@ def libId(self, symbol_id: str): token""" libName: Optional[str] = None - """The optional ``lib_name`` token is only set when the symbol was edited in the schematic. - It may be set to ``_X`` where X is a unique number that specifies which variation + """The optional ``lib_name`` token is only set when the symbol was edited in the schematic. + It may be set to ``_X`` where X is a unique number that specifies which variation this symbol is of its original.""" position: Position = field(default_factory=lambda: Position()) @@ -895,13 +895,6 @@ def to_sexpr(self, indent=2, newline=True) -> str: expression += f'{indents}){endline}' return expression - -test = SchematicSymbol() - -test.id = "2" -ftest = test.id - - @dataclass class HierarchicalPin(): """The ``pin`` token in a sheet object defines an electrical connection between the sheet in a