From b42f0366f534dadfb558089d816b288f6fea7638 Mon Sep 17 00:00:00 2001 From: eeintech Date: Tue, 21 Feb 2023 16:51:36 -0500 Subject: [PATCH] KiUtils is now a PIP package, more fixes --- .coveragerc | 2 - kintree/gui/views/common.py | 2 +- kintree/gui/views/main.py | 25 +- kintree/kicad/kicad_symbol.py | 6 +- kintree/kicad/kiutils/__init__.py | 10 - kintree/kicad/kiutils/board.py | 319 ------ kintree/kicad/kiutils/dru.py | 305 ------ kintree/kicad/kiutils/footprint.py | 1016 ------------------ kintree/kicad/kiutils/items/brditems.py | 1071 ------------------- kintree/kicad/kiutils/items/common.py | 857 --------------- kintree/kicad/kiutils/items/dimensions.py | 338 ------ kintree/kicad/kiutils/items/fpitems.py | 733 ------------- kintree/kicad/kiutils/items/gritems.py | 641 ------------ kintree/kicad/kiutils/items/schitems.py | 1162 --------------------- kintree/kicad/kiutils/items/syitems.py | 491 --------- kintree/kicad/kiutils/items/zones.py | 657 ------------ kintree/kicad/kiutils/libraries.py | 200 ---- kintree/kicad/kiutils/misc/config.py | 17 - kintree/kicad/kiutils/re_test.py | 32 - kintree/kicad/kiutils/schematic.py | 308 ------ kintree/kicad/kiutils/symbol.py | 531 ---------- kintree/kicad/kiutils/utils/sexpr.py | 43 - kintree/kicad/kiutils/utils/strings.py | 36 - kintree/kicad/kiutils/wks.py | 962 ----------------- poetry.lock | 366 ++++++- pyproject.toml | 3 +- requirements.txt | 2 + 27 files changed, 374 insertions(+), 9761 deletions(-) delete mode 100644 kintree/kicad/kiutils/__init__.py delete mode 100644 kintree/kicad/kiutils/board.py delete mode 100644 kintree/kicad/kiutils/dru.py delete mode 100644 kintree/kicad/kiutils/footprint.py delete mode 100644 kintree/kicad/kiutils/items/brditems.py delete mode 100644 kintree/kicad/kiutils/items/common.py delete mode 100644 kintree/kicad/kiutils/items/dimensions.py delete mode 100644 kintree/kicad/kiutils/items/fpitems.py delete mode 100644 kintree/kicad/kiutils/items/gritems.py delete mode 100644 kintree/kicad/kiutils/items/schitems.py delete mode 100644 kintree/kicad/kiutils/items/syitems.py delete mode 100644 kintree/kicad/kiutils/items/zones.py delete mode 100644 kintree/kicad/kiutils/libraries.py delete mode 100644 kintree/kicad/kiutils/misc/config.py delete mode 100644 kintree/kicad/kiutils/re_test.py delete mode 100644 kintree/kicad/kiutils/schematic.py delete mode 100644 kintree/kicad/kiutils/symbol.py delete mode 100644 kintree/kicad/kiutils/utils/sexpr.py delete mode 100644 kintree/kicad/kiutils/utils/strings.py delete mode 100644 kintree/kicad/kiutils/wks.py diff --git a/.coveragerc b/.coveragerc index a1f37cea..b79790ad 100644 --- a/.coveragerc +++ b/.coveragerc @@ -3,8 +3,6 @@ omit = # Do not run coverage on virtual environment files env-kintree/* .venv/* - # Skip KiCad library tool - kintree/kicad/kiutils/* # Skip UI coverage kintree/kintree_gui.py kintree/common/progress.py diff --git a/kintree/gui/views/common.py b/kintree/gui/views/common.py index 0949bf41..aed48bea 100644 --- a/kintree/gui/views/common.py +++ b/kintree/gui/views/common.py @@ -265,7 +265,7 @@ def on_search(self, e): self.dropdown.options = self.update_option_list(self.search_field.value) if len(self.dropdown.options) == 1: self.dropdown.value = self.dropdown.options[0].key - self.on_change(e) + self.on_change(e, label=self.label, value=self.value) else: self.dropdown.value = None else: diff --git a/kintree/gui/views/main.py b/kintree/gui/views/main.py index d20beaca..8353d62b 100644 --- a/kintree/gui/views/main.py +++ b/kintree/gui/views/main.py @@ -208,7 +208,7 @@ def process_enable(self, e, ignore=['enable']): def sanitize_data(self): return - def push_data(self, e=None): + def push_data(self, e=None, label=None, value=None): for key, field in self.fields.items(): try: self.data[key] = field.value @@ -304,8 +304,11 @@ def run_search(self, e): supplier=self.fields['supplier'].value, part_info=part_supplier_info, ) + # Stitch parameters + if part_supplier_info.get('parameters', None): + self.data['parameters'] = part_supplier_info['parameters'] - if part_supplier_info: + if part_supplier_form: for field_idx, field_name in enumerate(self.fields['search_form'].keys()): # print(field_idx, field_name, get_default_search_keys()[field_idx], search_form_field[field_name]) try: @@ -477,13 +480,17 @@ def update_footprint_options(self, library: str): def push_data(self, e=None, label=None, value=None): super().push_data(e) if label or e: - if 'Footprint Library' in [label, e.control.label]: - if value: - selected_footprint_library = value - else: - selected_footprint_library = e.data - self.fields['Footprint'].options = self.update_footprint_options(selected_footprint_library) - self.fields['Footprint'].update() + try: + if 'Footprint Library' in [label, e.control.label]: + if value: + selected_footprint_library = value + else: + selected_footprint_library = e.data + self.fields['Footprint'].options = self.update_footprint_options(selected_footprint_library) + self.fields['Footprint'].update() + except AttributeError: + # Handles condition where search field tries to reset dropdown + pass def build_library_options(self, type: str): found_libraries = self.find_libraries(type) diff --git a/kintree/kicad/kicad_symbol.py b/kintree/kicad/kicad_symbol.py index 788a40cb..271f4ed8 100644 --- a/kintree/kicad/kicad_symbol.py +++ b/kintree/kicad/kicad_symbol.py @@ -25,8 +25,8 @@ def __init__(self, library_path): def is_symbol_in_library(self, symbol_id): ''' Check if symbol already exists in library ''' for symbol in self.kicad_lib.symbols: - cprint(f'[DBUG]\t{symbol.id} ?= {symbol_id}', silent=settings.HIDE_DEBUG) - if symbol.id == symbol_id: + cprint(f'[DBUG]\t{symbol.libId} ?= {symbol_id}', silent=settings.HIDE_DEBUG) + if symbol.libId == symbol_id: cprint(f'[KCAD]\tWarning: Component {symbol_id} already in library', silent=settings.SILENT) return True @@ -84,7 +84,7 @@ def add_symbol_to_library_from_inventree(self, symbol_data, template_path=None, return part_in_lib, new_part # Update name/ID - new_symbol.id = symbol_id + new_symbol.libId = symbol_id # Update properties for property in new_symbol.properties: diff --git a/kintree/kicad/kiutils/__init__.py b/kintree/kicad/kiutils/__init__.py deleted file mode 100644 index b3ebde02..00000000 --- a/kintree/kicad/kiutils/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -"""Init script for kiutils - -Authors: - (C) Marvin Mager - @mvnmgrx - 2022 - -License identifier: - GPL-3.0 -""" - -# Intentionally left blank .. \ No newline at end of file diff --git a/kintree/kicad/kiutils/board.py b/kintree/kicad/kiutils/board.py deleted file mode 100644 index d1a13f31..00000000 --- a/kintree/kicad/kiutils/board.py +++ /dev/null @@ -1,319 +0,0 @@ -"""Class to manage KiCad boards - -Author: - (C) Marvin Mager - @mvnmgrx - 2022 - -License identifier: - GPL-3.0 - -Major changes: - 20.02.2022 - created - -Documentation taken from: - https://dev-docs.kicad.org/en/file-formats/sexpr-pcb/ -""" - -from __future__ import annotations - -from dataclasses import dataclass, field -from typing import Optional, List, Dict -from os import path - -from kiutils.items.common import Group, Net, PageSettings, TitleBlock -from kiutils.items.zones import Zone -from kiutils.items.brditems import * -from kiutils.items.gritems import * -from kiutils.items.dimensions import Dimension -from kiutils.utils.strings import dequote -from kiutils.utils import sexpr -from kiutils.footprint import Footprint -from kiutils.misc.config import KIUTILS_CREATE_NEW_VERSION_STR, KIUTILS_CREATE_NEW_GENERATOR_STR - -@dataclass -class Board(): - """The ``board`` token defines a KiCad layout according to the board file format used in - ``.kicad_pcb`` files. - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-pcb/ - """ - version: str = "" - """The ``version`` token defines the board version using the YYYYMMDD date format""" - - generator: str = "" - """The ``generator`` token defines the program used to write the file""" - - general: GeneralSettings = field(default_factory=lambda: GeneralSettings()) - """The ``general`` token defines general information about the board""" - - paper: PageSettings = field(default_factory=lambda: PageSettings()) - """The ``paper`` token defines informations about the page itself""" - - titleBlock: Optional[TitleBlock] = None - """The ``titleBlock`` token defines author, date, revision, company and comments of the board""" - - layers: List[LayerToken] = field(default_factory=list) - """The ``layers`` token defines all of the layers used by the board""" - - setup: SetupData = field(default_factory=lambda: SetupData()) - """The ``setup`` token is used to store the current settings used by the board""" - - properties: Dict[str, str] = field(default_factory=dict) - """The ``properties`` token holds a list of key-value properties of the board as a dictionary""" - - nets: List[Net] = field(default_factory=list) - """The ``nets`` token defines a list of nets used in the layout""" - - footprints: List[Footprint] = field(default_factory=list) - """The ``footprints`` token defines a list of footprints used in the layout""" - - graphicalItems: List = field(default_factory=list) # as in gritems.py - """The ``graphicalItems`` token defines a list of graphical items (as listed in `gritems.py`) used - in the layout""" - - traceItems: List = field(default_factory=list) - """The ``traceItems`` token defines a list of segments, arcs and vias used in the layout""" - - zones: List[Zone] = field(default_factory=list) - """The ``zones`` token defines a list of zones used in the layout""" - - dimensions: List[Dimension] = field(default_factory=list) - """The ``dimensions`` token defines a list of dimensions on the PCB""" - - targets: List[Target] = field(default_factory=list) - """The ``targets`` token defines a list of target markers on the PCB""" - - groups: List[Group] = field(default_factory=list) - """The ``groups`` token defines a list of groups used in the layout""" - - filePath: Optional[str] = None - """The ``filePath`` token defines the path-like string to the board file. Automatically set when - ``self.from_file()`` is used. Allows the use of ``self.to_file()`` without parameters.""" - - @classmethod - def from_sexpr(cls, exp: list) -> Board: - """Convert the given S-Expresstion into a Board object - - Args: - - exp (list): Part of parsed S-Expression ``(kicad_pcb ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not kicad_pcb - - Returns: - - Board: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'kicad_pcb': - raise Exception("Expression does not have the correct type") - - object = cls() - for item in exp: - if item[0] == 'version': object.version = item[1] - if item[0] == 'generator': object.generator = item[1] - if item[0] == 'general': object.general = GeneralSettings().from_sexpr(item) - if item[0] == 'paper': object.paper = PageSettings().from_sexpr(item) - if item[0] == 'title_block': object.titleBlock = TitleBlock().from_sexpr(item) - if item[0] == 'layers': - for layer in item[1:]: - object.layers.append(LayerToken().from_sexpr(layer)) - if item[0] == 'setup': object.setup = SetupData().from_sexpr(item) - if item[0] == 'property': object.properties.update({item[1]: item[2]}) - if item[0] == 'net': object.nets.append(Net().from_sexpr(item)) - if item[0] == 'footprint': object.footprints.append(Footprint().from_sexpr(item)) - if item[0] == 'gr_text': object.graphicalItems.append(GrText().from_sexpr(item)) - if item[0] == 'gr_text_box': object.graphicalItems.append(GrTextBox().from_sexpr(item)) - if item[0] == 'gr_line': object.graphicalItems.append(GrLine().from_sexpr(item)) - if item[0] == 'gr_rect': object.graphicalItems.append(GrRect().from_sexpr(item)) - if item[0] == 'gr_circle': object.graphicalItems.append(GrCircle().from_sexpr(item)) - if item[0] == 'gr_arc': object.graphicalItems.append(GrArc().from_sexpr(item)) - if item[0] == 'gr_poly': object.graphicalItems.append(GrPoly().from_sexpr(item)) - if item[0] == 'gr_curve': object.graphicalItems.append(GrCurve().from_sexpr(item)) - if item[0] == 'dimension': object.dimensions.append(Dimension().from_sexpr(item)) - if item[0] == 'target': object.targets.append(Target().from_sexpr(item)) - if item[0] == 'segment': object.traceItems.append(Segment().from_sexpr(item)) - if item[0] == 'arc': object.traceItems.append(Arc().from_sexpr(item)) - if item[0] == 'via': object.traceItems.append(Via().from_sexpr(item)) - if item[0] == 'zone': object.zones.append(Zone().from_sexpr(item)) - if item[0] == 'group': object.groups.append(Group().from_sexpr(item)) - - return object - - @classmethod - def from_file(cls, filepath: str, encoding: Optional[str] = None) -> Board: - """Load a board directly from a KiCad board file (`.kicad_pcb`) and sets the - ``self.filePath`` attribute to the given file path. - - 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 - dependent encoding). - - Raises: - - Exception: If the given path is not a file - - Returns: - - Footprint: Object of the Schematic class initialized with the given KiCad schematic - """ - if not path.isfile(filepath): - raise Exception("Given path is not a file!") - - with open(filepath, 'r', encoding=encoding) as infile: - item = cls.from_sexpr(sexpr.parse_sexp(infile.read())) - item.filePath = filepath - return item - - @classmethod - def create_new(cls) -> Board: - """Creates a new empty board with its attributes set as KiCad would create it - - Returns: - - Board: Empty board - """ - board = cls( - version = KIUTILS_CREATE_NEW_VERSION_STR, - generator = KIUTILS_CREATE_NEW_GENERATOR_STR - ) - - # Add all standard layers to board - board.layers.extend([ - LayerToken(ordinal=0, name='F.Cu', type='signal'), - LayerToken(ordinal=31, name='B.Cu', type='signal'), - LayerToken(ordinal=32, name='B.Adhes', type='user', userName="B.Adhesive"), - LayerToken(ordinal=33, name='F.Adhes', type='user', userName="F.Adhesive"), - LayerToken(ordinal=34, name='B.Paste', type='user'), - LayerToken(ordinal=35, name='F.Paste', type='user'), - LayerToken(ordinal=36, name='B.SilkS', type='user', userName="B.Silkscreen"), - LayerToken(ordinal=37, name='F.SilkS', type='user', userName="F.Silkscreen"), - LayerToken(ordinal=38, name='B.Mask', type='user'), - LayerToken(ordinal=39, name='F.Mask', type='user'), - LayerToken(ordinal=40, name='Dwgs.User', type='user', userName="User.Drawings"), - LayerToken(ordinal=41, name='Cmts.User', type='user', userName="User.Comments"), - LayerToken(ordinal=42, name='Eco1.User', type='user', userName="User.Eco1"), - LayerToken(ordinal=43, name='Eco2.User', type='user', userName="User.Eco2"), - LayerToken(ordinal=44, name='Edge.Cuts', type='user'), - LayerToken(ordinal=45, name='Margin', type='user'), - LayerToken(ordinal=46, name='B.CrtYd', type='user', userName="B.Courtyard"), - LayerToken(ordinal=47, name='F.CrtYd', type='user', userName="F.Courtyard"), - LayerToken(ordinal=48, name='B.Fab', type='user'), - LayerToken(ordinal=49, name='F.Fab', type='user'), - LayerToken(ordinal=50, name='User.1', type='user'), - LayerToken(ordinal=51, name='User.2', type='user'), - LayerToken(ordinal=52, name='User.3', type='user'), - LayerToken(ordinal=53, name='User.4', type='user'), - LayerToken(ordinal=54, name='User.5', type='user'), - LayerToken(ordinal=55, name='User.6', type='user'), - LayerToken(ordinal=56, name='User.7', type='user'), - LayerToken(ordinal=57, name='User.8', type='user'), - LayerToken(ordinal=58, name='User.9', type='user') - ]) - - # Append net0 to netlist - board.nets.append(Net()) - - return board - - 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, - the attribute ``self.filePath`` will be used instead. - - encoding (str, optional): Encoding of the output file. Defaults to None (platform - dependent encoding). - - Raises: - - Exception: If no file path is given via the argument or via `self.filePath` - """ - if filepath is None: - if self.filePath is None: - raise Exception("File path not set") - filepath = self.filePath - - with open(filepath, 'w', encoding=encoding) as outfile: - outfile.write(self.to_sexpr()) - - def to_sexpr(self, indent=0, newline=True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 0. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - addNewLine = False - - expression = f'{indents}(kicad_pcb (version {self.version}) (generator {self.generator})\n\n' - expression += self.general.to_sexpr(indent+2) + '\n' - expression += self.paper.to_sexpr(indent+2) - if self.titleBlock is not None: - expression += self.titleBlock.to_sexpr(indent+2) + '\n' - expression += f'{indents} (layers\n' - for layer in self.layers: - expression += layer.to_sexpr(indent+4) - expression += f'{indents} )\n\n' - expression += self.setup.to_sexpr(indent+2) + '\n' - # Properties, if any - if len(self.properties) > 0: - for key, value in self.properties.items(): - expression += f' (property "{dequote(key)}" "{dequote(value)}")\n' - expression += '\n' - - # Nets - if len(self.nets) > 0: - for net in self.nets: - expression += net.to_sexpr(indent=indent+2, newline=True) - expression += '\n' - - # Footprints - for footprint in self.footprints: - expression += footprint.to_sexpr(indent+2, layerInFirstLine=True) + '\n' - - # Lines, Texts, Arcs and other graphical items - if len(self.graphicalItems) > 0: - addNewLine = True - for item in self.graphicalItems: - if isinstance(item, GrPoly): - expression += item.to_sexpr(indent+2, pts_newline=True) - else: - expression += item.to_sexpr(indent+2) - - # Dimensions - if len(self.dimensions) > 0: - addNewLine = True - for dimension in self.dimensions: - expression += dimension.to_sexpr(indent+2) - - # Target markers: - if len(self.targets) > 0: - addNewLine = True - for target in self.targets: - expression += target.to_sexpr(indent+2) - - if addNewLine: - expression += '\n' - - # Segments, vias and arcs - if len(self.traceItems) > 0: - for item in self.traceItems: - expression += item.to_sexpr(indent+2) - expression += '\n' - - # Zones - for zone in self.zones: - expression += zone.to_sexpr(indent+2) - - # Groups - for group in self.groups: - expression += group.to_sexpr(indent+2) - - expression += f'{indents}){endline}' - return expression diff --git a/kintree/kicad/kiutils/dru.py b/kintree/kicad/kiutils/dru.py deleted file mode 100644 index ab959b9a..00000000 --- a/kintree/kicad/kiutils/dru.py +++ /dev/null @@ -1,305 +0,0 @@ -"""Classes for custom design rules (.kicad_dru) and its contents - -Author: - (C) Marvin Mager - @mvnmgrx - 2022 - -License identifier: - GPL-3.0 - -Major changes: - 26.06.2022 - created - -Documentation taken from: - ??? Syntax help in Pcbnew -""" - -from __future__ import annotations - -from dataclasses import dataclass, field -from typing import Optional, List -from os import path - -from kiutils.utils import sexpr -from kiutils.utils.strings import dequote - -@dataclass -class Constraint(): - """The ``Constraint`` token defines a design rule's constraint""" - - type: str = "clearance" - """The ``type`` token defines the type of constraint. Defaults to ``clearance``. Allowed types: - - ``annular_width`` - Width of an annular ring - - ``clearance`` - Clearance between two items - - ``courtyard_clearance`` - Clearance between two courtyards - - ``diff_pair_gap`` - Gap between differential pairs - - ``diff_pair_uncoupled`` - ??? - - ``disallow`` - ??? Do not allow this rule - - ``edge_clearance`` - Clearance between the item and board edges - - ``length`` - Length of the item - - ``hole_clearance`` - Clearance between the item and holes - - ``hole_size`` - Size of the holes associated with this item - - ``silk_clearance`` - Clearance to silk screen - - ``skew`` - Difference in length between the items associated with this constraint - - ``track_width`` - Width of the tracks associated with this constraint - - ``via_count`` - Number of vias - - ``via_diameter`` - Diameter of vias associated with this constraint - """ - - min: Optional[str] = None - """The ``min`` token defines the minimum allowed in this constraint""" - - opt: Optional[str] = None - """The ``opt`` token defines the optimum allowed in this constraint""" - - max: Optional[str] = None - """The ``max`` token defines the maximum allowed in this constraint""" - - elements: List[str] = field(default_factory=list) - """The ``items`` token defines a list of zero or more element types to include in this constraint. - The following element types are available: - - ``buried_via`` - - ``micro_via`` - - ``via`` - - ``graphic`` - - ``hole`` - - ``pad`` - - ``text`` - - ``track`` - - ``zone`` - """ - - @classmethod - def from_sexpr(cls, exp: list) -> Constraint: - """Convert the given S-Expresstion into a Constraint object - - Args: - - exp (list): Part of parsed S-Expression ``(constraint ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the list's first parameter is not the ``(constraint ..)`` token - - Returns: - - Constraint: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'constraint': - raise Exception("Expression does not have the correct type") - - object = cls() - object.type = exp[1] - for item in exp[2:]: - if type(item) != type([]): - object.elements.append(item) - if item[0] == 'min': object.min = item[1] - if item[0] == 'opt': object.opt = item[1] - if item[0] == 'max': object.max = item[1] - return object - - def to_sexpr(self, indent=2, newline=True): - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 2. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - min = f' (min "{dequote(self.min)}")' if self.min is not None else '' - opt = f' (opt "{dequote(self.opt)}")' if self.opt is not None else '' - max = f' (max "{dequote(self.max)}")' if self.max is not None else '' - - elements = ' '+' '.join(self.elements) if (len(self.elements) > 0) else '' - - return f'{indents}(constraint {self.type}{min}{opt}{max}{elements}){endline}' - -@dataclass -class Rule(): - """The ``Rule`` token defines a custom design rule""" - - name: str = "" - """The ``name`` token defines the name of the custom design rule""" - - constraints: List[Constraint] = field(default_factory=list) - """The ``constraints`` token defines a list of constraints for this custom design rule""" - - condition: str = "" - """The ``condition`` token defines the conditions that apply for this rule. Check KiCad syntax - reference for more information. Example rule: - - `A.inDiffPair('*') && !AB.isCoupledDiffPair()`""" - - layer: Optional[str] = None - """The optional ``layer`` token defines the canonical layer the rule applys to""" - - @classmethod - def from_sexpr(cls, exp: list) -> Rule: - """Convert the given S-Expresstion into a Rule object - - Args: - - exp (list): Part of parsed S-Expression ``(rule ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the list's first parameter is not the ``(rule ..)`` token - - Returns: - - Rule: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'rule': - raise Exception("Expression does not have the correct type") - - object = cls() - object.name = exp[1] - for item in exp[2:]: - if item[0] == 'constraint': object.constraints.append(Constraint().from_sexpr(item)) - if item[0] == 'condition': object.condition = item[1] - if item[0] == 'layer': object.layer = item[1] - return object - - def to_sexpr(self, indent=0): - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 0. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - - expression = f'{indents}(rule "{dequote(self.name)}"\n' - if self.layer is not None: - expression += f'{indents} (layer "{dequote(self.layer)}")\n' - for item in self.constraints: - expression += f'{indents}{item.to_sexpr(indent+2)}' - expression += f'{indents} (condition "{dequote(self.condition)}"))\n' - return expression - -@dataclass -class DesignRules(): - """The ``DesignRules`` token defines a set of custom design rules (`.kicad_dru` files)""" - - version: int = 1 - """The ``version`` token defines the version of the file for the KiCad parser. Defaults to 1.""" - - rules: List[Rule] = field(default_factory=list) - """The ``rules`` token defines a list of custom design rules""" - - filePath: Optional[str] = None - """The ``filePath`` token defines the path-like string to the schematic file. Automatically set when - ``self.from_file()`` is used. Allows the use of ``self.to_file()`` without parameters.""" - - @classmethod - def from_sexpr(cls, exp: list) -> DesignRules: - """Convert the given S-Expresstion into a DesignRules object - - Args: - - exp (list): Part of parsed S-Expression ``(version ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the list's first parameter is not the ``(version ..)`` token - - Returns: - - DesignRules: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if not isinstance(exp[0], list): - raise Exception("Expression does not have the correct type") - - if exp[0][0] != 'version': - raise Exception("Expression does not have the correct type") - - object = cls() - for item in exp: - if item[0] == 'version': object.version = item[0] - if item[0] == 'rule': object.rules.append(Rule().from_sexpr(item)) - return object - - @classmethod - def from_file(cls, filepath: str, encoding: Optional[str] = None) -> DesignRules: - """Load a custom design rules set directly from a KiCad design rules file (`.kicad_dru`) and - sets the ``self.filePath`` attribute to the given file path. - - 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 - dependent encoding). - Raises: - - Exception: If the given path is not a file - - Returns: - - Footprint: Object of the DesignRules class initialized with the given KiCad file - """ - if not path.isfile(filepath): - raise Exception("Given path is not a file!") - - with open(filepath, 'r', encoding=encoding) as infile: - # This dirty fix adds opening and closing brackets `(..)` to the read input to enable - # the S-Expression parser to work for the DRU-format as well. - data = f'({infile.read()})' - item = cls.from_sexpr(sexpr.parse_sexp(data)) - item.filePath = filepath - return item - - @classmethod - def create_new(cls) -> DesignRules: - """Creates a new empty design rules set as KiCad would create it - - Returns: - - DesignRules: Empty design rules set - """ - return cls(version=1) - - 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, - the attribute ``self.filePath`` will be used instead. - - encoding (str, optional): Encoding of the output file. Defaults to None (platform - dependent encoding). - - Raises: - - Exception: If no file path is given via the argument or via `self.filePath` - """ - if filepath is None: - if self.filePath is None: - raise Exception("File path not set") - filepath = self.filePath - - with open(filepath, 'w', encoding=encoding) as outfile: - outfile.write(self.to_sexpr()) - - def to_sexpr(self, indent=0, newline=False): - """Generate the S-Expression representing this object - - Args: - indent (int, optional): Number of whitespaces used to indent the output. Defaults to 0. - newline (bool, optional): Adds a newline to the end of the output. Defaults to False. - - Returns: - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - expression = f'{indents}(version {self.version})\n' - - if len(self.rules): - expression += f'{indents}\n' - for rule in self.rules: - expression += f'{indents}{rule.to_sexpr(indent=indent)}' - - return expression + endline \ No newline at end of file diff --git a/kintree/kicad/kiutils/footprint.py b/kintree/kicad/kiutils/footprint.py deleted file mode 100644 index dc20ad8e..00000000 --- a/kintree/kicad/kiutils/footprint.py +++ /dev/null @@ -1,1016 +0,0 @@ -"""Classes to manage KiCad footprints - -Author: - (C) Marvin Mager - @mvnmgrx - 2022 - -License identifier: - GPL-3.0 - -Major changes: - 02.02.2022 - created - -Documentation taken from: - https://dev-docs.kicad.org/en/file-formats/sexpr-footprint/ -""" - -from __future__ import annotations - -import calendar -import datetime -from dataclasses import dataclass, field -from typing import Optional, List, Dict -from os import path - -from kiutils.items.zones import Zone -from kiutils.items.common import Position, Coordinate, Net, Group, Font -from kiutils.items.fpitems import * -from kiutils.items.gritems import * -from kiutils.utils import sexpr -from kiutils.utils.strings import dequote, remove_prefix -from kiutils.misc.config import KIUTILS_CREATE_NEW_VERSION_STR - -@dataclass -class Attributes(): - """The ``attr`` token defines the list of attributes of a footprint. - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-intro/index.html#_footprint_attributes - """ - - type: Optional[str] = None - """The optional ``type`` token defines the type of footprint. Valid footprint types are ``smd`` and - ``through_hole``. May be none when no attributes are set.""" - - boardOnly: bool = False - """The optional ``boardOnly`` token indicates that the footprint is only defined in the board - and has no reference to any schematic symbol""" - - excludeFromPosFiles: bool = False - """The optional ``excludeFromPosFiles`` token indicates that the footprint position information - should not be included when creating position files""" - - excludeFromBom: bool = False - """The optional ``excludeFromBom`` token indicates that the footprint should be excluded when - creating bill of materials (BOM) files""" - - @classmethod - def from_sexpr(cls, exp: list) -> Attributes: - """Convert the given S-Expresstion into a Attributes object - - Args: - - exp (list): Part of parsed S-Expression ``(attr ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not attr - - Returns: - - Attributes: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'attr': - raise Exception("Expression does not have the correct type") - - object = cls() - if len(exp) > 1: - # Attributes token may be set with no other items (empty attributes) - # Test case for this: test_fp_empty_attr.kicad_mod - if exp[1] == 'through_hole' or exp[1] == 'smd': - object.type = exp[1] - - for item in exp: - if item == 'board_only': object.boardOnly = True - if item == 'exclude_from_pos_files': object.excludeFromPosFiles = True - if item == 'exclude_from_bom': object.excludeFromBom = True - return object - - def to_sexpr(self, indent=0, newline=False) -> str: - """Generate the S-Expression representing this object. Will return an empty string, if the - following attributes are selected: - - ``type``: None - - ``boardOnly``: False - - ``excludeFromBom``: False - - ``excludeFromPosFiles``: False - - KiCad won't add the ``(attr ..)`` token to a footprint when this combination is selected. - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 0. - - newline (bool): Adds a newline to the end of the output. Defaults to False. - - Returns: - - str: S-Expression of this object - """ - if (self.type == None - and self.boardOnly == False - and self.excludeFromBom == False - and self.excludeFromPosFiles == False): - return '' - - indents = ' '*indent - endline = '\n' if newline else '' - type = f' {self.type}' if self.type is not None else '' - - expression = f'{indents}(attr{type}' - if self.boardOnly is not None: - if self.boardOnly: - expression += ' board_only' - if self.excludeFromPosFiles is not None: - if self.excludeFromPosFiles: - expression += ' exclude_from_pos_files' - if self.excludeFromBom is not None: - if self.excludeFromBom: - expression += ' exclude_from_bom' - expression += f'){endline}' - return expression - -@dataclass -class Model(): - """The ``model`` token defines the 3D model associated with a footprint. - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-intro/index.html#_footprint_3d_model - """ - - path: str = "" - """The ``path`` attribute is the path and file name of the 3D model""" - - pos: Coordinate = field(default_factory=lambda: Coordinate(0.0, 0.0, 0.0)) - """The ``pos`` token specifies the 3D position coordinates of the model relative to the footprint""" - - scale: Coordinate = field(default_factory=lambda: Coordinate(1.0, 1.0, 1.0)) - """The ``scale`` token specifies the model scale factor for each 3D axis""" - - rotate: Coordinate = field(default_factory=lambda: Coordinate(0.0, 0.0, 0.0)) - """The ``rotate`` token specifies the model rotation for each 3D axis relative to the footprint""" - - @classmethod - def from_sexpr(cls, exp: list) -> Model: - """Convert the given S-Expresstion into a Model object - - Args: - - exp (list): Part of parsed S-Expression ``(model ...)`` - - Raises: - - Exception: When given parameter's type is not a list or the list is not 5 long - - Exception: When the first item of the list is not model - - Returns: - - Model: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list) or len(exp) != 5: - raise Exception("Expression does not have the correct type") - - if exp[0] != 'model': - raise Exception("Expression does not have the correct type") - - object = cls() - object.path = exp[1] - object.pos = Coordinate.from_sexpr(exp[2][1]) - object.scale = Coordinate.from_sexpr(exp[3][1]) - object.rotate = Coordinate.from_sexpr(exp[4][1]) - return object - - def to_sexpr(self, indent=2, newline=True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 2. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - expression = f'{indents}(model "{dequote(self.path)}"\n' - expression += f'{indents} (offset {self.pos.to_sexpr()})\n' - expression += f'{indents} (scale {self.scale.to_sexpr()})\n' - expression += f'{indents} (rotate {self.rotate.to_sexpr()})\n' - expression += f'{indents}){endline}' - return expression - -@dataclass -class DrillDefinition(): - """The ``drill`` token defines the drill attributes for a footprint pad. - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-intro/index.html#_pad_drill_definition - """ - - oval: bool = False - """The ``oval`` token defines if the drill is oval instead of round""" - - diameter: float = 0.0 - """The ``diameter`` attribute defines the drill diameter""" - - width: Optional[float] = None - """The optional ``width`` attribute defines the width of the slot for oval drills""" - - offset: Optional[Position] = None - """The optional ``offset`` token defines the drill offset coordinates from the center of the pad""" - - @classmethod - def from_sexpr(cls, exp: list) -> DrillDefinition: - """Convert the given S-Expresstion into a DrillDefinition object - - Args: - - exp (list): Part of parsed S-Expression ``(drill ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not drill - - Returns: - - DrillDefinition: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'drill': - raise Exception("Expression does not have the correct type") - - object = cls() - # Depending on the ``oval`` token, the fields may be shifted .. - if exp[1] == 'oval': - object.oval = True - object.diameter = exp[2] - object.width = exp[3] - else: - object.diameter = exp[1] - if len(exp) > 2: - object.width = exp[2] - - # The ``offset`` token may not be given - for item in exp: - if type(item) != type([]): continue - if item[0] == 'offset': object.offset = Position().from_sexpr(item) - return object - - def to_sexpr(self, indent: int = 0, newline: bool = False) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 0. - - newline (bool): Adds a newline to the end of the output. Defaults to False. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - oval = f' oval' if self.oval else '' - width = f' {self.width}' if self.oval and self.width is not None else '' - offset = f' (offset {self.offset.X} {self.offset.Y})' if self.offset is not None else '' - - return f'{indents}(drill{oval} {self.diameter}{width}{offset}){endline}' - -@dataclass -class PadOptions(): - """The ``options`` token attributes define the settings used for custom pads. This token is - only used when a custom pad is defined. - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-intro/index.html#_custom_pad_options - """ - clearance: str = "outline" - """The ``clearance`` token defines the type of clearance used for a custom pad. Valid clearance - types are ``outline`` and ``convexhull``.""" - - anchor: str = "rect" - """The ``anchor`` token defines the anchor pad shape of a custom pad. Valid anchor pad shapes - are rect and circle.""" - - @classmethod - def from_sexpr(cls, exp: list) -> PadOptions: - """Convert the given S-Expresstion into a PadOptions object - - Args: - - exp (list): Part of parsed S-Expression ``(options ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not options - - Returns: - - PadOptions: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'options': - raise Exception("Expression does not have the correct type") - - object = cls() - for item in exp: - if item[0] == 'clearance': object.clearance = item[1] - if item[0] == 'anchor': object.anchor = item[1] - return object - - def to_sexpr(self, indent: int = 0, newline: bool = False) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 0. - - newline (bool): Adds a newline to the end of the output. Defaults to False. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - return f'{indents}(options (clearance {self.clearance}) (anchor {self.anchor})){endline}' - -@dataclass -class Pad(): - """The ``pad`` token defines a pad in a footprint definition. - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-intro/index.html#_footprint_pad - """ - - number: str = "x" - """The ``number`` attribute is the pad number""" - - type: str = "smd" - """The pad ``type`` can be defined as ``thru_hole``, ``smd``, ``connect``, or ``np_thru_hole``""" - - shape: str = "rect" - """The pad ``shape`` can be defined as ``circle``, ``rect``, ``oval``, ``trapezoid``, ``roundrect``, or - ``custom``""" - - position: Position = field(default_factory=lambda: Position()) - """The ``position`` defines the X and Y coordinates and optional orientation angle of the pad""" - - locked: bool = False - """The optional ``locked`` token defines if the footprint pad can be edited""" - - size: Position = field(default_factory=lambda: Position()) # Size uses Position class for simplicity for now - """The ``size`` token defines the width and height of the pad""" - - drill: Optional[DrillDefinition] = None - """The optional pad ``drill`` token defines the pad drill requirements""" - - # TODO: Test case for one-layer pad?? - layers: List[str] = field(default_factory=list) - """The ``layers`` token defines the layer or layers the pad reside on""" - - property: Optional[str] = None - """The optional ``property`` token defines any special properties for the pad. Valid properties - are ``pad_prop_bga``, ``pad_prop_fiducial_glob``, ``pad_prop_fiducial_loc``, ``pad_prop_testpoint``, - ``pad_prop_heatsink``, ``pad_prop_heatsink``, and ``pad_prop_castellated``""" - - removeUnusedLayers: bool = False - """The optional ``removeUnusedLayers`` token specifies that the copper should be removed from - any layers the pad is not connected to""" - - keepEndLayers: bool = False - """The optional ``keepEndLayers`` token specifies that the top and bottom layers should be - retained when removing the copper from unused layers""" - - roundrectRatio: Optional[float] = None - """The optional ``roundrectRatio`` token defines the scaling factor of the pad to corner radius - for rounded rectangular and chamfered corner rectangular pads. The scaling factor is a - number between 0 and 1.""" - - chamferRatio: Optional[float] = None # Adds a newline before - """The optional ``chamferRatio`` token defines the scaling factor of the pad to chamfer size. - The scaling factor is a number between 0 and 1.""" - - chamfer: List[str] = field(default_factory=list) - """The optional ``chamfer`` token defines a list of one or more rectangular pad corners that - get chamfered. Valid chamfer corner attributes are ``top_left``, ``top_right``, ``bottom_left``, - and ``bottom_right``.""" - - net: Optional[Net] = None - """The optional ``net`` token defines the integer number and name string of the net connection - for the pad.""" - - tstamp: Optional[str] = None # Used since KiCad 6 - """The optional ``tstamp`` token defines the unique identifier of the pad object""" - - pinFunction: Optional[str] = None - """The optional ``pinFunction`` token attribute defines the associated schematic symbol pin name""" - - pinType: Optional[str] = None - """The optional ``pinType`` token attribute defines the associated schematic pin electrical type""" - - dieLength: Optional[float] = None # Adds a newline before - """The optional ``dieLength`` token attribute defines the die length between the component pad - and physical chip inside the component package""" - - solderMaskMargin: Optional[float] = None - """The optional ``solderMaskMargin`` token attribute defines the distance between the pad and - the solder mask for the pad. If not set, the footprint solder_mask_margin is used.""" - - solderPasteMargin: Optional[float] = None - """The optional ``solderPasteMargin`` token attribute defines the distance the solder paste - should be changed for the pad""" - - solderPasteMarginRatio: Optional[float] = None - """The optional ``solderPasteMarginRatio`` token attribute defines the percentage to reduce the - pad outline by to generate the solder paste size""" - - clearance: Optional[float] = None - """The optional ``clearance`` token attribute defines the clearance from all copper to the pad. - If not set, the footprint clearance is used.""" - - zoneConnect: Optional[int] = None - """The optional ``zoneConnect`` token attribute defines type of zone connect for the pad. If - not defined, the footprint zone_connection setting is used. Valid connection types are - integers values from 0 to 3 which defines: - - 0: Pad is not connect to zone - - 1: Pad is connected to zone using thermal relief - - 2: Pad is connected to zone using solid fill - - 3: Only through hold pad is connected to zone using thermal relief - """ - - thermalWidth: Optional[float] = None - """The optional ``thermalWidth`` token attribute defines the thermal relief spoke width used for - zone connection for the pad. This only affects a pad connected to a zone with a thermal - relief. If not set, the footprint thermal_width setting is used.""" - - thermalGap: Optional[float] = None - """The optional ``thermalGap`` token attribute defines the distance from the pad to the zone of - the thermal relief connection for the pad. This only affects a pad connected to a zone - with a thermal relief. If not set, the footprint thermal_gap setting is used.""" - - customPadOptions: Optional[PadOptions] = None - """The optional ``customPadOptions`` token defines the options when a custom pad is defined""" - - # Documentation seems wrong about primitives here. It seems like its just a list - # of graphical objects, but the docu suggests, besides the list, two other params - # for the primitive token: width and fill - # These two however are note generated under the primitive token from the KiCad - # generator. These two params may be found in gr_poly or gr_XX only. - # So for now, the custom pad primitives are only a list of graphical objects - customPadPrimitives: List = field(default_factory=list) - """The optional ``customPadPrimitives`` defines the drawing objects and options used to define - a custom pad""" - - @classmethod - def from_sexpr(cls, exp: list) -> Pad: - """Convert the given S-Expresstion into a Pad object - - Args: - - exp (list): Part of parsed S-Expression ``(pad ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not pad - - Returns: - - Pad: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'pad': - raise Exception("Expression does not have the correct type") - - object = cls() - object.number = exp[1] - object.type = exp[2] - object.shape = exp[3] - - for item in exp[3:]: - if type(item) != type([]): - if item == 'locked': object.locked = True - - if item[0] == 'at': object.position = Position().from_sexpr(item) - if item[0] == 'size': object.size = Position().from_sexpr(item) - if item[0] == 'drill': object.drill = DrillDefinition().from_sexpr(item) - if item[0] == 'layers': - for layer in item[1:]: - object.layers.append(layer) - if item[0] == 'property': object.property = item[1] - if item[0] == 'remove_unused_layers': object.removeUnusedLayers = True - if item[0] == 'keep_end_layers': object.keepEndLayers = True - if item[0] == 'roundrect_rratio': object.roundrectRatio = item[1] - if item[0] == 'chamfer_ratio': object.chamferRatio = item[1] - if item[0] == 'chamfer': - for chamfer in item[1:]: - object.chamfer.append(chamfer) - if item[0] == 'net': object.net = Net().from_sexpr(item) - if item[0] == 'tstamp': object.tstamp = item[1] - if item[0] == 'pinfunction': object.pinFunction = item[1] - if item[0] == 'pintype': object.pinType = item[1] - if item[0] == 'die_length': object.dieLength = item[1] - if item[0] == 'solder_mask_margin': object.solderMaskMargin = item[1] - if item[0] == 'solder_paste_margin': object.solderPasteMargin = item[1] - if item[0] == 'solder_paste_margin_ratio': object.solderPasteMarginRatio = item[1] - if item[0] == 'clearance': object.clearance = item[1] - if item[0] == 'zone_connect': object.zoneConnect = item[1] - if item[0] == 'thermal_width': object.thermalWidth = item[1] - if item[0] == 'thermal_gap': object.thermalGap = item[1] - if item[0] == 'options': object.customPadOptions = PadOptions().from_sexpr(item) - if item[0] == 'primitives': - for primitive in item[1:]: - if primitive[0] == 'gr_text': object.customPadPrimitives.append(GrText().from_sexpr(primitive)) - if primitive[0] == 'gr_text_box': object.customPadPrimitives.append(GrTextBox().from_sexpr(primitive)) - if primitive[0] == 'gr_line': object.customPadPrimitives.append(GrLine().from_sexpr(primitive)) - if primitive[0] == 'gr_rect': object.customPadPrimitives.append(GrRect().from_sexpr(primitive)) - if primitive[0] == 'gr_circle': object.customPadPrimitives.append(GrCircle().from_sexpr(primitive)) - if primitive[0] == 'gr_arc': object.customPadPrimitives.append(GrArc().from_sexpr(primitive)) - if primitive[0] == 'gr_poly': object.customPadPrimitives.append(GrPoly().from_sexpr(primitive)) - if primitive[0] == 'gr_curve': object.customPadPrimitives.append(GrCurve().from_sexpr(primitive)) - - # XXX: Are dimentions even implemented here? - if primitive[0] == 'dimension': raise NotImplementedError("Dimensions are not yet handled! Please report this bug along with the file being parsed.") - return object - - def to_sexpr(self, indent: int = 2, newline: bool = True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 2. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - champferFound, marginFound, schematicSymbolAssociated = False, False, False - c, cr, smm, spm, spmr, cl, zc, tw, tg = '', '', '', '', '', '', '', '', '' - - layers = ' (layers' - for layer in self.layers: - # For some reason KiCad does not escape a layer with double-quotes if it has a - # wildcard (*) or an ampersant (&) in it - if "*." in layer or "&" in layer: - layers += f' {layer}' - else: - layers += f' "{dequote(layer)}"' - - layers += ')' - - locked = ' locked' if self.locked else '' - drill = f' {self.drill.to_sexpr()}' if self.drill is not None else '' - ppty = f' (property {self.property})' if self.property is not None else '' - rul = ' (remove_unused_layers)' if self.removeUnusedLayers else '' - kel = ' (keep_end_layers)' if self.keepEndLayers else '' - rrr = f' (roundrect_rratio {self.roundrectRatio})' if self.roundrectRatio is not None else '' - - net = f' {self.net.to_sexpr()}' if self.net is not None else '' - pf = f' (pinfunction "{dequote(self.pinFunction)}")' if self.pinFunction is not None else '' - pt = f' (pintype "{dequote(self.pinType)}")' if self.pinType is not None else '' - - # Check if a schematic symbol is associated with this footprint. This is usually set, if the - # footprint is used in a board file. - if net != '' or pf != '' or pt != '': - schematicSymbolAssociated = True - - tstamp = f' (tstamp {self.tstamp})' if self.tstamp is not None else '' - - if len(self.chamfer) > 0: - champferFound = True - c = ' (chamfer' - for chamfer in self.chamfer: - c += f' {chamfer}' - c += ')' - if self.chamferRatio is not None: - champferFound = True - cr = f' (chamfer_ratio {self.chamferRatio})' - - if self.position.angle is not None: - position = f'(at {self.position.X} {self.position.Y} {self.position.angle})' - else: - position = f'(at {self.position.X} {self.position.Y})' - - if self.solderMaskMargin is not None: - marginFound = True - smm = f' (solder_mask_margin {self.solderMaskMargin})' - - if self.solderPasteMargin is not None: - marginFound = True - spm = f' (solder_paste_margin {self.solderPasteMargin})' - - if self.solderPasteMarginRatio is not None: - marginFound = True - spmr = f' (solder_paste_margin_ratio {self.solderPasteMarginRatio})' - - if self.clearance is not None: - marginFound = True - cl = f' (clearance {self.clearance})' - - if self.zoneConnect is not None: - marginFound = True - zc = f' (zone_connect {self.zoneConnect})' - - if self.thermalWidth is not None: - marginFound = True - tw = f' (thermal_width {self.thermalWidth})' - - if self.thermalGap is not None: - marginFound = True - tg = f' (thermal_gap {self.thermalGap})' - - expression = f'{indents}(pad "{dequote(str(self.number))}" {self.type} {self.shape}{locked} {position} (size {self.size.X} {self.size.Y}){drill}{ppty}{layers}{rul}{kel}{rrr}' - if champferFound: - # Only one whitespace here as all temporary strings have at least one leading whitespace - expression += f'\n{indents} {cr}{c}' - - if self.dieLength is not None: - expression += f'\n{indents} (die_length {self.dieLength})' - - if marginFound or schematicSymbolAssociated: - # Only one whitespace here as all temporary strings have at least one leading whitespace - expression += f'\n{indents} {net}{pf}{pt}{smm}{spm}{spmr}{cl}{zc}{tw}{tg}' - - if self.customPadOptions is not None: - expression += f'\n{indents} {self.customPadOptions.to_sexpr()}' - - if self.customPadPrimitives is not None: - if len(self.customPadPrimitives) > 0: - expression += f'\n{indents} (primitives' - for primitive in self.customPadPrimitives: - expression += f'\n{primitive.to_sexpr(newline=False,indent=indent+4)}' - expression += f'\n{indents} )' - - expression += f'{tstamp}){endline}' - return expression - -@dataclass -class Footprint(): - """The ``footprint`` token defines a footprint. - - Documentation: - 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.""" - - version: Optional[str] = None - """The ``version`` token attribute defines the symbol library version using the YYYYMMDD date format""" - - generator: Optional[str] = None - """The ``generator`` token attribute defines the program used to write the file""" - - locked: bool = False - """The optional ``locked`` token defines a flag to indicate the footprint cannot be edited""" - - placed: bool = False - """The optional ``placed`` token defines a flag to indicate that the footprint has not been placed""" - - layer: str = "F.Cu" - """The ``layer`` token defines the canonical layer the footprint is placed""" - - tedit: str = remove_prefix(hex(calendar.timegm(datetime.datetime.now().utctimetuple())), '0x') - """The ``tedit`` token defines a the last time the footprint was edited""" - - tstamp: Optional[str] = None - """The ``tstamp`` token defines the unique identifier for the footprint. This only applies - to footprints defined in the board file format.""" - - position: Optional[Position] = None - """The ``position`` token defines the X and Y coordinates and rotational angle of the - footprint. This only applies to footprints defined in the board file format.""" - - description: Optional[str] = None - """The optional ``description`` token defines a string containing the description of the footprint""" - - tags: Optional[str] = None - """The optional ``tags`` token defines a string of search tags for the footprint""" - - properties: Dict = field(default_factory=dict) - """The ``properties`` token defines dictionary of properties as key / value pairs where key being - the name of the property and value being the description of the property""" - - path: Optional[str] = None - """The ``path`` token defines the hierarchical path of the schematic symbol linked to the footprint. - This only applies to footprints defined in the board file format.""" - - autoplaceCost90: Optional[int] = None - """The optional ``autoplaceCost90`` token defines the vertical cost of when using the automatic - footprint placement tool. Valid values are integers 1 through 10. This only applies to footprints - defined in the board file format.""" - - autoplaceCost180: Optional[int] = None - """The optional ``autoplaceCost180`` token defines the horizontal cost of when using the automatic - footprint placement tool. Valid values are integers 1 through 10. This only applies to footprints - defined in the board file format.""" - - solderMaskMargin: Optional[float] = None - """The optional ``solderMaskMargin`` token defines the solder mask distance from all pads in the - footprint. If not set, the board solder_mask_margin setting is used.""" - - solderPasteMargin: Optional[float] = None - """The optional ``solderPasteMargin`` token defines the solder paste distance from all pads in - the footprint. If not set, the board solder_paste_margin setting is used.""" - - solderPasteRatio: Optional[float] = None - """The optional ``solderPasteRatio`` token defines the percentage of the pad size used to define - the solder paste for all pads in the footprint. If not set, the board solder_paste_ratio setting - is used.""" - - clearance: Optional[float] = None - """The optional ``clearance`` token defines the clearance to all board copper objects for all pads - in the footprint. If not set, the board clearance setting is used.""" - - zoneConnect: Optional[int] = None - """The optional ``zoneConnect`` token defines how all pads are connected to filled zone. If not - defined, then the zone connect_pads setting is used. Valid connection types are integers values - from 0 to 3 which defines: - - 0: Pads are not connect to zone - - 1: Pads are connected to zone using thermal reliefs - - 2: Pads are connected to zone using solid fill - - 3: Only through hold pads are connected to zone using thermal reliefs - """ - - thermalWidth: Optional[float] = None - """The optional ``thermalWidth`` token defined the thermal relief spoke width used for zone connections - for all pads in the footprint. This only affects pads connected to zones with thermal reliefs. If - not set, the zone thermal_width setting is used.""" - - thermalGap: Optional[float] = None - """The optional ``thermalGap`` is the distance from the pad to the zone of thermal relief connections - for all pads in the footprint. If not set, the zone thermal_gap setting is used. If not set, the - zone thermal_gap setting is used.""" - - attributes: Attributes = field(default_factory=lambda: Attributes()) - """The optional ``attributes`` section defines the attributes of the footprint""" - - graphicItems: List = field(default_factory=list) - """The ``graphic`` objects section is a list of one or more graphical objects in the footprint. At a - minimum, the reference designator and value text objects are defined. All other graphical objects - are optional.""" - - pads: List[Pad] = field(default_factory=list) - """The optional ``pads`` section is a list of pads in the footprint""" - - zones: List[Zone] = field(default_factory=list) - """The optional ``zones`` section is a list of keep out zones in the footprint""" - - groups: List[Group] = field(default_factory=list) - """The optional ``groups`` section is a list of grouped objects in the footprint""" - - models: List[Model] = field(default_factory=list) - """The ``3D model`` section defines the 3D model object associated with the footprint""" - - filePath: Optional[str] = None - """The ``filePath`` token defines the path-like string to the library file. Automatically set when - ``self.from_file()`` is used. Allows the use of ``self.to_file()`` without parameters.""" - - @classmethod - def from_sexpr(cls, exp: list) -> Footprint: - """Convert the given S-Expresstion into a Footprint object - - Args: - - exp (list): Part of parsed S-Expression ``(footprint ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not footprint - - Returns: - - Footprint: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'module' and exp[0] != 'footprint': - raise Exception("Expression does not have the correct type") - - object = cls() - object.libraryLink = exp[1] - for item in exp[2:]: - if not isinstance(item, list): - if item == 'locked': object.locked = True - if item == 'placed': object.placed = True - continue - - if (item[0] == 'version'): object.version = item[1] - if (item[0] == 'generator'): object.generator = item[1] - if (item[0] == 'layer'): object.layer = item[1] - if (item[0] == 'tedit'): object.tedit = item[1] - if (item[0] == 'tstamp'): object.tstamp = item[1] - if (item[0] == 'descr'): object.description = item[1] - if (item[0] == 'tags'): object.tags = item[1] - if (item[0] == 'path'): object.path = item[1] - if (item[0] == 'at'): object.position = Position().from_sexpr(item) - if (item[0] == 'autoplace_cost90'): object.autoplaceCost90 = item[1] - if (item[0] == 'autoplace_cost180'): object.autoplaceCost180 = item[1] - if (item[0] == 'solder_mask_margin'): object.solderMaskMargin = item[1] - if (item[0] == 'solder_paste_margin'): object.solderPasteMargin = item[1] - if (item[0] == 'solder_paste_ratio'): object.solderPasteRatio = item[1] - if (item[0] == 'clearance'): object.clearance = item[1] - if (item[0] == 'zone_connect'): object.zoneConnect = item[1] - if (item[0] == 'thermal_width'): object.thermalWidth = item[1] - if (item[0] == 'thermal_gap'): object.thermalGap = item[1] - - if item[0] == 'attr': - object.attributes = Attributes.from_sexpr(item) - if item[0] == 'model': - object.models.append(Model.from_sexpr(item)) - if item[0] == 'fp_text': - object.graphicItems.append(FpText.from_sexpr(item)) - if item[0] == 'fp_text_box': - object.graphicItems.append(FpTextBox.from_sexpr(item)) - if item[0] == 'fp_line': - object.graphicItems.append(FpLine.from_sexpr(item)) - if item[0] == 'fp_rect': - object.graphicItems.append(FpRect.from_sexpr(item)) - if item[0] == 'fp_circle': - object.graphicItems.append(FpCircle.from_sexpr(item)) - if item[0] == 'fp_arc': - object.graphicItems.append(FpArc.from_sexpr(item)) - if item[0] == 'fp_poly': - object.graphicItems.append(FpPoly.from_sexpr(item)) - if item[0] == 'fp_curve': - object.graphicItems.append(FpCurve.from_sexpr(item)) - if item[0] == 'dimension': - raise NotImplementedError("Dimensions are not yet handled! Please report this bug along with the file being parsed.") - if item[0] == 'pad': - object.pads.append(Pad.from_sexpr(item)) - if item[0] == 'zone': - object.zones.append(Zone.from_sexpr(item)) - if item[0] == 'property': - object.properties.update({ item[1]: item[2] }) - if item[0] == 'group': - object.groups.append(Group.from_sexpr(item)) - - return object - - @classmethod - def from_file(cls, filepath: str, encoding: Optional[str] = None) -> Footprint: - """Load a footprint directly from a KiCad footprint file (`.kicad_mod`) and sets the - ``self.filePath`` attribute to the given file path. - - 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 - dependent encoding). - - Raises: - - Exception: If the given path is not a file - - Returns: - - Footprint: Object of the Footprint class initialized with the given KiCad footprint - """ - if not path.isfile(filepath): - raise Exception("Given path is not a file!") - - with open(filepath, 'r', encoding=encoding) as infile: - rawFootprint = infile.read() - - fpData = sexpr.parse_sexp(rawFootprint) - return cls.from_sexpr(fpData) - - @classmethod - def create_new(cls, library_link: str, value: str, - type: str = 'other', reference: str = 'REF**') -> Footprint: - """Creates a new empty footprint with its attributes set as KiCad would create it - - Args: - - library_link (str): Denotes the name of the library as well as the footprint. Like `Connector:Conn01x02`) - - value (str): The value text item (printed on the fabrication layer as ``value`` attribute) - - type (str): Type of footprint (``smd``, ``through_hole`` or ``other``). Defaults to 'other'. - - reference (str): Reference of the footprint. Defaults to `REF**`. - Raises: - - Exception: When the given type is something other than listed above - - Returns: - - Footprint: Empty footprint - """ - if type not in ['smd', 'through_hole', 'other']: - raise Exception("Unsupported type was given") - - fp = cls( - libraryLink = library_link, - version = KIUTILS_CREATE_NEW_VERSION_STR, - generator = 'kiutils' - ) - - # Create text items that are created when adding a new footprint to a library - fp.graphicItems.extend( - [ - FpText( - type = 'reference', text = reference, layer = 'F.SilkS', - effects = Effects(font=Font(thickness=0.15)), - position = Position(X=0, Y=-0.5, unlocked=True) - ), - FpText( - type = 'value', text = value, layer ='F.Fab', - effects = Effects(font=Font(thickness=0.15)), - position = Position(X=0, Y=1, unlocked=True) - ), - FpText( - type = 'user', text = '${REFERENCE}', layer = 'F.Fab', - effects = Effects(font=Font(thickness=0.15)), - position = Position(X=0, Y=2.5, unlocked=True) - ) - ] - ) - - # The type ``other`` does not set the attributes type token - if type != 'other': - fp.attributes.type = type - - return fp - - 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, - the attribute ``self.filePath`` will be used instead. - - encoding (str, optional): Encoding of the output file. Defaults to None (platform - dependent encoding). - - Raises: - - Exception: If no file path is given via the argument or via `self.filePath` - """ - if filepath is None: - if self.filePath is None: - raise Exception("File path not set") - filepath = self.filePath - - with open(filepath, 'w', encoding=encoding) as outfile: - outfile.write(self.to_sexpr()) - - def to_sexpr(self, indent=0, newline=True, layerInFirstLine=False) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 0. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - layerInFirstLine (bool): Prints the ``layer`` token in the first line. Defaults to False - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - locked = ' locked' if self.locked else '' - placed = ' placed' if self.placed else '' - version = f' (version {self.version})' if self.version is not None else '' - 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}' - if layerInFirstLine: - expression += f' (layer "{dequote(self.layer)}")\n' - else: - expression += f'\n{indents} (layer "{dequote(self.layer)}")\n' - expression += f'{indents} (tedit {self.tedit}){tstamp}\n' - - if self.position is not None: - angle = f' {self.position.angle}' if self.position.angle is not None else '' - expression += f'{indents} (at {self.position.X} {self.position.Y}{angle})\n' - if self.description is not None: - expression += f'{indents} (descr "{dequote(self.description)}")\n' - if self.tags is not None: - expression += f'{indents} (tags "{dequote(self.tags)}")\n' - for item in self.properties: - expression += f'{indents} (property "{dequote(item)}" "{dequote(self.properties[item])}")\n' - if self.path is not None: - expression += f'{indents} (path "{dequote(self.path)}")\n' - - # Additional parameters used in board - if self.autoplaceCost90 is not None: - expression += f'{indents} (autoplace_cost90 {self.autoplaceCost90})\n' - if self.autoplaceCost180 is not None: - expression += f'{indents} (autoplace_cost180 {self.autoplaceCost180})\n' - if self.solderMaskMargin is not None: - expression += f'{indents} (solder_mask_margin {self.solderMaskMargin})\n' - if self.solderPasteMargin is not None: - expression += f'{indents} (solder_paste_margin {self.solderPasteMargin})\n' - if self.solderPasteRatio is not None: - expression += f'{indents} (solder_paste_ratio {self.solderPasteRatio})\n' - if self.clearance is not None: - expression += f'{indents} (clearance {self.clearance})\n' - if self.zoneConnect is not None: - expression += f'{indents} (zone_connect {self.zoneConnect})\n' - if self.thermalWidth is not None: - expression += f'{indents} (thermal_width {self.thermalWidth})\n' - if self.thermalGap is not None: - expression += f'{indents} (thermal_gap {self.thermalGap})\n' - - if self.attributes is not None: - # Note: If the attribute object has only standard values in it, it will return an - # empty string. Therefore, it should create its own newline and indentations only - # when needed. - expression += self.attributes.to_sexpr(indent=indent+2, newline=True) - - for item in self.graphicItems: - expression += item.to_sexpr(indent=indent+2) - for item in self.pads: - expression += item.to_sexpr(indent=indent+2) - for item in self.zones: - expression += item.to_sexpr(indent=indent+2) - for item in self.models: - expression += item.to_sexpr(indent=indent+2) - for item in self.groups: - expression += item.to_sexpr(indent=indent+2) - - expression += f'{indents}){endline}' - return expression - diff --git a/kintree/kicad/kiutils/items/brditems.py b/kintree/kicad/kiutils/items/brditems.py deleted file mode 100644 index fd2dc66d..00000000 --- a/kintree/kicad/kiutils/items/brditems.py +++ /dev/null @@ -1,1071 +0,0 @@ -"""Classes to manage KiCad board items - -Author: - (C) Marvin Mager - @mvnmgrx - 2022 - -License identifier: - GPL-3.0 - -Major changes: - 20.02.2022 - created - -Documentation taken from: - https://dev-docs.kicad.org/en/file-formats/sexpr-pcb/ -""" - -from __future__ import annotations - -from dataclasses import dataclass, field -from typing import Optional, List - -from kiutils.items.common import Position -from kiutils.utils.strings import dequote - -@dataclass -class GeneralSettings(): - """The ``general`` token define general information about the board - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-pcb/#_general_section - """ - - thickness: float = 1.6 - """The ``thickness`` token attribute defines the overall board thickness""" - - @classmethod - def from_sexpr(cls, exp: list) -> GeneralSettings: - """Convert the given S-Expresstion into a GeneralSettings object - - Args: - - exp (list): Part of parsed S-Expression ``(general ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not general - - Returns: - - GeneralSettings: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'general': - raise Exception("Expression does not have the correct type") - - object = cls() - for item in exp: - if item[0] == 'thickness': object.thickness = item[1] - return object - - def to_sexpr(self, indent=2, newline=True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 2. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - expression = f'{indents}(general\n' - expression += f'{indents} (thickness {self.thickness})\n' - expression += f'{indents}){endline}' - return expression - - -@dataclass -class LayerToken(): - """Intermediate type used for the ``layers`` token in a board - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-pcb/#_layers_section - """ - - ordinal: int = 0 - """The layer ``ordinal`` is an integer used to associate the layer stack ordering. This is mostly - to ensure correct mapping when the number of layers is increased in the future""" - - name: str = "F.Cu" - """The ``name`` is the layer name defined for internal board use""" - - type: str = "signal" - """The layer ``type`` defines the type of layer and can be defined as ``jumper``, ``mixed``, ``power``, - ``signal``, or ``user``.""" - - userName: Optional[str] = None - """The optional ``userName`` attribute defines the custom user name""" - - @classmethod - def from_sexpr(cls, exp: list) -> LayerToken: - """Convert the given S-Expresstion into a LayerToken object - - Args: - - exp (list): Part of parsed S-Expression ``( "" )`` - - Raises: - - Exception: When given parameter's type is not a list or the length of the list is not 3 - 4 - - Exception: When the first item of the list is not kicad_pcb - - Returns: - - LayerToken: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list) or len(exp) < 3 or len(exp) > 4: - raise Exception("Expression does not have the correct type") - - object = cls() - object.ordinal = exp[0] - object.name = exp[1] - object.type = exp[2] - if len(exp) == 4: - object.userName = exp[3] - return object - - def to_sexpr(self, indent=4, newline=True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 4. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - username = f' "{dequote(self.userName)}"' if self.userName is not None else '' - - return f'{indents}({self.ordinal} "{dequote(self.name)}" {self.type}{username}){endline}' - - -@dataclass -class StackupSubLayer(): - """The ``StackupSubLayer`` token defines a sublayer used when stacking dielectrics in a PCB""" - - thickness: float = 0.1 - """The ``thickness`` token defines the thickness of the sublayer. Defaults to 0.1""" - - material: Optional[str] = None - """The optional ``material`` token defines a string that describes the sublayer material""" - - epsilonR: Optional[float] = None - """The optional ``epsilonR`` token defines the dielectric constant of the sublayer material""" - - lossTangent: Optional[float] = None - """The optional layer ``lossTangent`` token defines the dielectric loss tangent of the sublayer""" - - @classmethod - def from_sexpr(cls, exp: list) -> StackupSubLayer: - """This class cannot be derived from an S-Expression as the format currently used in KiCad - board files does not match the usual convention. Assign member values manually when using - this object. - - Raises: - - NotImplementedError""" - raise NotImplementedError("This class cannot be derived from an S-Expression!") - - def to_sexpr(self, indent=0, newline=False) -> str: - """Generate the S-Expression representing this object. The representation differs from the - normal form of an S-Expression as this uses no opening and closing parenthesis. - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 0. - - newline (bool): Adds a newline to the end of the output. Defaults to False. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - mat = f' (material "{dequote(self.material)}")' if self.material is not None else '' - er = f' (epsilon_r {self.epsilonR})' if self.epsilonR is not None else '' - lt = f' (loss_tangent {self.lossTangent})' if self.lossTangent is not None else '' - - return f'{indents}addsublayer (thickness {self.thickness}){mat}{er}{lt}{endline}' - - -@dataclass -class StackupLayer(): - """The ``layer`` token defines the stack up setting of a single layer in the board stack up - settings. - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-pcb/#_stack_up_settings - """ - - name: str = "" - """The ``name`` attribute is either one of the canonical copper or technical layer names - or ``dielectric ID`` if it is dielectric layer""" - - # Not found in example project ... - #number: int = 0 - """The ``number`` attribute defines the stack order of the layer""" - - type: str = "" - """The ``type`` token defines a string that describes the layer""" - - color: Optional[str] = None - """The optional ``color`` token defines a string that describes the layer color. This is - only used on solder mask and silkscreen layers""" - - thickness: Optional[float] = None - """The optional ``thickness`` token defines the thickness of the layer where appropriate""" - - material: Optional[str] = None - """The optional ``material`` token defines a string that describes the layer material - where appropriate""" - - epsilonR: Optional[float] = None - """The optional ``epsilonR`` token defines the dielectric constant of the layer material""" - - lossTangent: Optional[float] = None - """The optional layer ``lossTangent`` token defines the dielectric loss tangent of the layer""" - - subLayers: List[StackupSubLayer] = field(default_factory=list) - """The ``sublayers`` token defines a list of zero or more sublayers that are used to create - stacks of dielectric layers. Does not apply to copper-type layers.""" - - @classmethod - def from_sexpr(cls, exp: list) -> StackupLayer: - """Convert the given S-Expresstion into a StackupLayer object - - Args: - - exp (list): Part of parsed S-Expression ``(layer ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not layer - - Returns: - - StackupLayer: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'layer': - raise Exception("Expression does not have the correct type") - - parsingSublayer = False - tempSublayer = StackupSubLayer() - object = cls() - object.name = exp[1] - for item in exp[2:]: - if type(item) != type([]): - # Start parsing the layer's sublayer if the first sublayer token was found - if item == 'addsublayer': - if parsingSublayer: - # When the ``addsublayer`` token was found a second time, the previously - # parsed sublayer will be appended to the list of sublayers - object.subLayers.append(tempSublayer) - tempSublayer = StackupSubLayer() - else: - # Change state of the parser to look for StackupSubLayer tokens - parsingSublayer = True - continue - - # Parse the tokens of StackupSubLayer for the current sublayer - if parsingSublayer: - if item[0] == 'thickness': tempSublayer.thickness = item[1] - if item[0] == 'material': tempSublayer.material = item[1] - if item[0] == 'epsilon_r': tempSublayer.epsilonR = item[1] - if item[0] == 'loss_tangent': tempSublayer.lossTangent = item[1] - continue - - # Parse the normal tokens of StackupLayer token - if item[0] == 'type': object.type = item[1] - if item[0] == 'thickness': object.thickness = item[1] - if item[0] == 'material': object.material = item[1] - if item[0] == 'epsilon_r': object.epsilonR = item[1] - if item[0] == 'loss_tangent': object.lossTangent = item[1] - if item[0] == 'color': object.color = item[1] - - # Add the last parsed sublayer to the list, if any - if parsingSublayer: - object.subLayers.append(tempSublayer) - - return object - - def to_sexpr(self, indent=6, newline=True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 6. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - color = f' (color "{dequote(self.color)}")' if self.color is not None else '' - material = f' (material "{dequote(self.material)}")' if self.material is not None else '' - thickness = f' (thickness {self.thickness})' if self.thickness is not None else '' - epsilon_r = f' (epsilon_r {self.epsilonR})' if self.epsilonR is not None else '' - loss_tangent = f' (loss_tangent {self.lossTangent})' if self.lossTangent is not None else '' - - expression = f'{indents}(layer "{dequote(self.name)}" (type "{self.type}"){color}{thickness}' - expression +=f'{material}{epsilon_r}{loss_tangent}' - for layer in self.subLayers: - expression += f'\n{layer.to_sexpr(indent+2)}' - expression += f'){endline}' - return expression - -@dataclass -class Stackup(): - """The ``stackup`` token defines the board stack up settings and is defined in the setup - section. - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-pcb/#_stack_up_settings - """ - - layers: List[StackupLayer] = field(default_factory=list) - """The ``layers``token is a list of layer settings for each layer required to manufacture - a board including the dielectric material between the actual layers defined in the board - editor.""" - - copperFinish: Optional[str] = None - """The optional ``copperFinish`` token is a string that defines the copper finish used to - manufacture the board""" - - dielectricContraints: Optional[str] = None - """The optional ``dielectricContraints`` token define if the board should meet all - dielectric requirements. Valid values are ``yes`` and ``no``.""" - - edgeConnector: Optional[str] = None - """The optional ``edgeConnector`` token defines if the board has an edge connector - (value: ``yes``) and if the edge connector is bevelled (value: ``bevelled``)""" - - castellatedPads: bool = False - """The ``castellatedPads`` token defines if the board edges contain castellated pads""" - - edgePlating: bool = False - """The ``edgePlating`` token defines if the board edges should be plated.""" - - @classmethod - def from_sexpr(cls, exp: list) -> Stackup: - """Convert the given S-Expresstion into a Stackup object - - Args: - - exp (list): Part of parsed S-Expression ``(stackup ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not stackup - - Returns: - - Stackup: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'stackup': - raise Exception("Expression does not have the correct type") - - object = cls() - for item in exp: - if item[0] == 'layer': object.layers.append(StackupLayer().from_sexpr(item)) - if item[0] == 'copper_finish': object.copperFinish = item[1] - if item[0] == 'dielectric_constraints': object.dielectricContraints = item[1] - if item[0] == 'edge_connector': object.edgeConnector = item[1] - if item[0] == 'castellated_pads': object.castellatedPads = True - if item[0] == 'edge_plating': object.edgePlating = True - return object - - def to_sexpr(self, indent=4, newline=True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 4. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - expression = f'{indents}(stackup\n' - for layer in self.layers: - expression += layer.to_sexpr(indent+2) - if self.copperFinish is not None: expression += f'{indents} (copper_finish "{dequote(self.copperFinish)}")\n' - if self.dielectricContraints is not None: expression += f'{indents} (dielectric_constraints {self.dielectricContraints})\n' - if self.edgeConnector is not None: expression += f'{indents} (edge_connector {self.edgeConnector})\n' - if self.castellatedPads: expression += f'{indents} (castellated_pads yes)\n' - if self.edgePlating: expression += f'{indents} (edge_plating yes)\n' - expression += f'{indents}){endline}' - return expression - -@dataclass -class PlotSettings(): - """The ``pcbplotparams`` token defines the plotting and printing settings used for the last - plot and is defined in the set up section. - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-pcb/#_plot_settings - """ - - layerSelection: str = "" - """The ``layerSelection`` token defines a hexadecimal bit set of the layers to plot""" - - disableApertMacros: bool = False - """The ``disableApertMacros`` token defines if aperture macros are to be used in gerber plots""" - - useGerberExtensions: bool = False - """The ``useGerberExtensions`` token defines if the Protel layer file name extensions are to - be used in gerber plots""" - - useGerberAttributes: bool = False - """The ``useGerberAttributes`` token defines if the X2 extensions are used in gerber plots""" - - useGerberAdvancedAttributes: bool = False - """The ``useGerberAdvancedAttributes`` token defines if the netlist information should be - included in gerber plots""" - - createGerberJobFile: bool = False - """The ``createGerberJobFile`` token defines if a job file should be created when - plotting gerber files""" - - svgUseInch: bool = False - """The ``svgUseInch`` token defines if inch units should be use when plotting SVG files""" - - svgPrecision: float = 0.0 - """The ``svgPrecision`` token defines the units precision used when plotting SVG files""" - - excludeEdgeLayer: bool = False - """The ``excludeEdgeLayer`` token defines if the board edge layer is plotted on all layers""" - - plotFameRef: bool = False - """The ``plotFameRef`` token defines if the border and title block should be plotted""" - - viasOnMask: bool = False - """The ``viasOnMask`` token defines if the vias are to be tented""" - - mode: int = 1 - """The ``mode`` token defines the plot mode. An attribute of 1 plots in the normal - mode and an attribute of 2 plots in the outline (sketch) mode.""" - - useAuxOrigin: bool = False - """The ``useAuxOrigin`` token determines if all coordinates are offset by the defined user origin""" - - hpglPenNumber: int = 0 - """The ``hpglPenNumber`` token defines the integer pen number used for HPGL plots""" - - hpglPenSpeed: int = 0 - """The ``hpglPenSpeed`` token defines the integer pen speed used for HPGL plots""" - - hpglPenDiameter: float = 0.0 - """The ``hpglPenDiameter`` token defines the floating point pen size for HPGL plots""" - - dxfPolygonMode: bool = False - """The ``dxfPolygonMode`` token defines if the polygon mode should be used for DXF plots""" - - dxfImperialUnits: bool = False - """The ``dxfImperialUnits`` token defines if imperial units should be used for DXF plots""" - - dxfUsePcbnewFont: bool = False - """The ``dxfUsePcbnewFont`` token defines if the Pcbnew font (vector font) or the default - font should be used for DXF plots""" - - psNegative: bool = False - """The ``psNegative`` token defines if the output should be the negative for PostScript plots""" - - psA4Output: bool = False - """The ``psA4Output`` token defines if the A4 page size should be used for PostScript plots""" - - plotReference: bool = False - """The ``plotReference`` token defines if hidden reference field text should be plotted""" - - plotValue: bool = False - """The ``plotValue`` token defines if hidden value field text should be plotted""" - - plotInvisibleText: bool = False - """The ``plotInvisibleText`` token defines if hidden text other than the reference and - value fields should be plotted""" - - sketchPadsOnFab: bool = False - """The ``sketchPadsOnFab`` token defines if pads should be plotted in the outline (sketch) mode""" - - subtractMaskFromSilk: bool = False - """The ``subtractMaskFromSilk`` token defines if the solder mask layers should be subtracted from - the silk screen layers for gerber plots""" - - outputFormat: int = 0 - """The ``outputFormat`` token defines the last plot type. The following values are defined: - - 0: gerber - - 1: PostScript - - 2: SVG - - 3: DXF - - 4: HPGL - - 5: PDF""" - - mirror: bool = False - """The ``mirror`` token defines if the plot should be mirrored""" - - drillShape: int = 0 - """The ``drillShape`` token defines the type of drill marks used for drill files""" - - scaleSelection: int = 1 - """The ``scaleSelection`` is not documented yet (as of 20.02.2022)""" - - outputDirectory: str = "" - """The ``drillShape`` token defines the path relative to the current project path - where the plot files will be saved""" - - @classmethod - def from_sexpr(cls, exp: list) -> PlotSettings: - """Convert the given S-Expresstion into a PlotSettings object - - Args: - - exp (list): Part of parsed S-Expression ``(pcbplotparams ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not pcbplotparams - - Returns: - - PlotSettings: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'pcbplotparams': - raise Exception("Expression does not have the correct type") - - object = cls() - for item in exp: - if item[0] == 'layerselection': object.layerSelection = item[1] - if item[0] == 'disableapertmacros': object.disableApertMacros = True if item[1] == 'true' else False - if item[0] == 'usegerberextensions' : object.useGerberExtensions = True if item[1] == 'true' else False - if item[0] == 'usegerberattributes' : object.useGerberAttributes = True if item[1] == 'true' else False - if item[0] == 'usegerberadvancedattributes' : object.useGerberAdvancedAttributes = True if item[1] == 'true' else False - if item[0] == 'creategerberjobfile' : object.createGerberJobFile = True if item[1] == 'true' else False - if item[0] == 'svguseinch' : object.svgUseInch = True if item[1] == 'true' else False - if item[0] == 'svgprecision' : object.svgPrecision = item[1] - if item[0] == 'excludeedgelayer' : object.excludeEdgeLayer = True if item[1] == 'true' else False - if item[0] == 'plotframeref' : object.plotFameRef = True if item[1] == 'true' else False - if item[0] == 'viasonmask' : object.viasOnMask = True if item[1] == 'true' else False - if item[0] == 'mode' : object.mode = item[1] - if item[0] == 'useauxorigin' : object.useAuxOrigin = True if item[1] == 'true' else False - if item[0] == 'hpglpennumber' : object.hpglPenNumber = item[1] - if item[0] == 'hpglpenspeed' : object.hpglPenSpeed = item[1] - if item[0] == 'hpglpendiameter' : object.hpglPenDiameter = item[1] - if item[0] == 'dxfpolygonmode' : object.dxfPolygonMode = True if item[1] == 'true' else False - if item[0] == 'dxfimperialunits' : object.dxfImperialUnits = True if item[1] == 'true' else False - if item[0] == 'dxfusepcbnewfont' : object.dxfUsePcbnewFont = True if item[1] == 'true' else False - if item[0] == 'psnegative' : object.psNegative = True if item[1] == 'true' else False - if item[0] == 'psa4output' : object.psA4Output = True if item[1] == 'true' else False - if item[0] == 'plotreference' : object.plotReference = True if item[1] == 'true' else False - if item[0] == 'plotvalue' : object.plotValue = True if item[1] == 'true' else False - if item[0] == 'plotinvisibletext' : object.plotInvisibleText = True if item[1] == 'true' else False - if item[0] == 'sketchpadsonfab' : object.sketchPadsOnFab = True if item[1] == 'true' else False - if item[0] == 'subtractmaskfromsilk' : object.subtractMaskFromSilk = True if item[1] == 'true' else False - if item[0] == 'outputformat' : object.outputFormat = item[1] - if item[0] == 'mirror' : object.mirror = True if item[1] == 'true' else False - if item[0] == 'drillshape' : object.drillShape = item[1] - if item[0] == 'scaleselection' : object.scaleSelection = item[1] - if item[0] == 'outputdirectory' : object.outputDirectory = item[1] - return object - - def to_sexpr(self, indent=4, newline=True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 4. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - expression = f'{indents}(pcbplotparams\n' - expression += f'{indents} (layerselection {self.layerSelection})\n' - expression += f'{indents} (disableapertmacros {str(self.disableApertMacros).lower()})\n' - expression += f'{indents} (usegerberextensions {str(self.useGerberExtensions).lower()})\n' - expression += f'{indents} (usegerberattributes {str(self.useGerberAttributes).lower()})\n' - expression += f'{indents} (usegerberadvancedattributes {str(self.useGerberAdvancedAttributes).lower()})\n' - expression += f'{indents} (creategerberjobfile {str(self.createGerberJobFile).lower()})\n' - expression += f'{indents} (svguseinch {str(self.svgUseInch).lower()})\n' - expression += f'{indents} (svgprecision {self.svgPrecision})\n' - expression += f'{indents} (excludeedgelayer {str(self.excludeEdgeLayer).lower()})\n' - expression += f'{indents} (plotframeref {str(self.plotFameRef).lower()})\n' - expression += f'{indents} (viasonmask {str(self.viasOnMask).lower()})\n' - expression += f'{indents} (mode {self.mode})\n' - expression += f'{indents} (useauxorigin false)\n' - expression += f'{indents} (hpglpennumber {self.hpglPenNumber})\n' - expression += f'{indents} (hpglpenspeed {self.hpglPenSpeed})\n' - expression += f'{indents} (hpglpendiameter {float(self.hpglPenDiameter):.6f})\n' - expression += f'{indents} (dxfpolygonmode {str(self.dxfPolygonMode).lower()})\n' - expression += f'{indents} (dxfimperialunits {str(self.dxfImperialUnits).lower()})\n' - expression += f'{indents} (dxfusepcbnewfont {str(self.dxfUsePcbnewFont).lower()})\n' - expression += f'{indents} (psnegative {str(self.psNegative).lower()})\n' - expression += f'{indents} (psa4output {str(self.psA4Output).lower()})\n' - expression += f'{indents} (plotreference {str(self.plotReference).lower()})\n' - expression += f'{indents} (plotvalue {str(self.plotValue).lower()})\n' - expression += f'{indents} (plotinvisibletext {str(self.plotInvisibleText).lower()})\n' - expression += f'{indents} (sketchpadsonfab {str(self.sketchPadsOnFab).lower()})\n' - expression += f'{indents} (subtractmaskfromsilk {str(self.subtractMaskFromSilk).lower()})\n' - expression += f'{indents} (outputformat {self.outputFormat})\n' - expression += f'{indents} (mirror {str(self.mirror).lower()})\n' - expression += f'{indents} (drillshape {self.drillShape})\n' - expression += f'{indents} (scaleselection {self.scaleSelection})\n' - expression += f'{indents} (outputdirectory "{dequote(self.outputDirectory)}")\n' - expression += f'{indents}){endline}' - return expression - - -@dataclass -class SetupData(): - """The setup token is used to store the current settings such as default item sizes and - other options used by the board - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-pcb/#_setup_section - """ - - stackup: Optional[Stackup] = None - """The optional ``stackup`` define the parameters required to manufacture the board""" - - packToMaskClearance: float = 0.0 - """The ``packToMaskClearance`` token defines the clearance between footprint pads and - the solder mask""" - - solderMaskMinWidth: Optional[float] = None - """The optional ``solderMaskMinWidth`` defines the minimum solder mask width. If not - defined, the minimum width is zero.""" - - padToPasteClearance: Optional[float] = None - """The optional ``padToPasteClearance`` defines the clearance between footprint pads - and the solder paste layer. If not defined, the clearance is zero""" - - padToPasteClearanceRatio: Optional[float] = None - """The optional ``padToPasteClearanceRatio`` is the percentage (from 0 to 100) of the - footprint pad to make the solder paste. If not defined, the ratio is 100% (the same - size as the pad).""" - - auxAxisOrigin: Optional[Position] = None - """The optional ``auxAxisOrigin`` defines the auxiliary origin if it is set to anything - other than (0,0).""" - - gridOrigin: Optional[Position] = None - """The optional ``gridOrigin`` defines the grid original if it is set to anything other - than (0,0).""" - - plotSettings: Optional[PlotSettings] = None - """The optional ``plotSettings`` define how the board was last plotted.""" - - @classmethod - def from_sexpr(cls, exp: list) -> SetupData: - """Convert the given S-Expresstion into a SetupData object - - Args: - - exp (list): Part of parsed S-Expression ``(setup ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not setup - - Returns: - - SetupData: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'setup': - raise Exception("Expression does not have the correct type") - - object = cls() - for item in exp: - if item[0] == 'stackup': object.stackup = Stackup().from_sexpr(item) - if item[0] == 'pcbplotparams': object.plotSettings = PlotSettings().from_sexpr(item) - if item[0] == 'pad_to_mask_clearance': object.packToMaskClearance = item[1] - if item[0] == 'solder_mask_min_width': object.solderMaskMinWidth = item[1] - if item[0] == 'pad_to_paste_clearance': object.padToPasteClearance = item[1] - if item[0] == 'pad_to_paste_clearance_ratio': object.padToPasteClearanceRatio = item[1] - if item[0] == 'aux_axis_origin': object.auxAxisOrigin = Position().from_sexpr(item) - if item[0] == 'grid_origin': object.gridOrigin = Position().from_sexpr(item) - if item[0] == 'pcbplotparams': object.plotSettings = PlotSettings().from_sexpr(item) - return object - - def to_sexpr(self, indent=2, newline=True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 2. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - expression = f'{indents}(setup\n' - if self.stackup is not None: expression += self.stackup.to_sexpr(indent+2) - expression += f'{indents} (pad_to_mask_clearance {self.packToMaskClearance})\n' - if self.solderMaskMinWidth is not None: expression += f'{indents} (solder_mask_min_width {self.solderMaskMinWidth})\n' - if self.padToPasteClearance is not None: expression += f'{indents} (pad_to_paste_clearance {self.padToPasteClearance})\n' - if self.padToPasteClearanceRatio is not None: expression += f'{indents} (pad_to_paste_clearance_ratio {self.padToPasteClearanceRatio})\n' - if self.auxAxisOrigin is not None: expression += f'{indents} (aux_axis_origin {self.auxAxisOrigin.X} {self.auxAxisOrigin.Y})\n' - if self.gridOrigin is not None: expression += f'{indents} (grid_origin {self.gridOrigin.X} {self.gridOrigin.Y})\n' - if self.plotSettings is not None: expression += self.plotSettings.to_sexpr(indent+2) - expression += f'{indents}){endline}' - return expression - - -@dataclass -class Segment(): - """The ``segment`` token defines a track segment in a KiCad board - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-pcb/#_track_segment - """ - - start: Position = field(default_factory=lambda: Position()) - """The ``start`` token defines the coordinates of the beginning of the line""" - - end: Position = field(default_factory=lambda: Position()) - """The ``end`` token defines the coordinates of the end of the line""" - - width: float = 0.1 - """The ``width`` token defines the line width""" - - layer: str = "F.Cu" - """The ``layer`` token defines the canonical layer the track segment resides on""" - - locked: bool = False - """The ``locked`` token defines if the line cannot be edited""" - - net: int = 0 - """The ``net`` token defines by the net ordinal number which net in the net - section that the segment is part of""" - - tstamp: str = "" - """The ``tstamp`` token defines the unique identifier of the line object""" - - @classmethod - def from_sexpr(cls, exp: list) -> Segment: - """Convert the given S-Expresstion into a Segment object - - Args: - - exp (list): Part of parsed S-Expression ``(segment ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not segment - - Returns: - - Segment: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'segment': - raise Exception("Expression does not have the correct type") - - object = cls() - for item in exp: - if type(item) != type([]): - if item == 'locked': object.locked = True - continue - if item[0] == 'start': object.start = Position().from_sexpr(item) - if item[0] == 'end': object.end = Position().from_sexpr(item) - if item[0] == 'width': object.width = item[1] - if item[0] == 'layer': object.layer = item[1] - if item[0] == 'net': object.net = item[1] - if item[0] == 'tstamp': object.tstamp = item[1] - return object - - def to_sexpr(self, indent=2, newline=True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 2. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - locked = ' locked' if self.locked else '' - - return f'{indents}(segment{locked} (start {self.start.X} {self.start.Y}) (end {self.end.X} {self.end.Y}) (width {self.width}) (layer "{dequote(self.layer)}") (net {self.net}) (tstamp {self.tstamp})){endline}' - -@dataclass -class Via(): - """The ``via`` token defines a track via in a KiCad board - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-pcb/#_track_via - """ - - type: Optional[str] = None - """The optional ``type`` attribute specifies the via type. Valid via types are ``blind`` and - ``micro``. If no type is defined, the via is a through hole type""" - - locked: bool = False - """The ``locked`` token defines if the line cannot be edited""" - - position: Position = field(default_factory=lambda: Position()) - """The ``position`` token define the coordinates of the center of the via""" - - size: float = 0.0 - """The ``size`` token define the diameter of the via annular ring""" - - drill: float = 0.0 - """The ``drill`` token define the drill diameter of the via""" - - layers: List[str] = field(default_factory=list) - """The ``layers`` token define the canonical layer set the via connects as a list - of strings""" - - removeUnusedLayers: bool = False - """The ``removeUnusedLayers`` token is undocumented (as of 20.02.2022)""" - - keepEndLayers: bool = False - """The ``keepEndLayers`` token is undocumented (as of 20.02.2022)""" - - free: bool = False - """The ``free`` token indicates that the via is free to be moved outside it's assigned net""" - - net: int = 0 - """The ``net`` token defines by net ordinal number which net in the net section that - the via is part of""" - - tstamp: Optional[str] = None - """The ``tstamp`` token defines the unique identifier of the via""" - - @classmethod - def from_sexpr(cls, exp: list) -> Via: - """Convert the given S-Expresstion into a Via object - - Args: - - exp (list): Part of parsed S-Expression ``(via ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not via - - Returns: - - Via: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'via': - raise Exception("Expression does not have the correct type") - - object = cls() - for item in exp: - if type(item) != type([]): - if item == 'locked': object.locked = True - if item == 'micro' or item == 'blind': object.type = item - continue - if item[0] == 'at': object.position = Position().from_sexpr(item) - if item[0] == 'size': object.size = item[1] - if item[0] == 'drill': object.drill = item[1] - if item[0] == 'layers': - for layer in item[1:]: - object.layers.append(layer) - if item[0] == 'remove_unused_layers': object.removeUnusedLayers = True - if item[0] == 'keep_end_layers': object.keepEndLayers = True - if item[0] == 'free': object.free = True - if item[0] == 'net': object.net = item[1] - if item[0] == 'tstamp': object.tstamp = item[1] - return object - - def to_sexpr(self, indent=2, newline=True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 2. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - type = f' {self.type}' if self.type is not None else '' - locked = f' locked' if self.locked else '' - - layers = '' - for layer in self.layers: - layers += f' "{dequote(layer)}"' - rum = f' (remove_unused_layers)' if self.removeUnusedLayers else '' - kel = f' (keep_end_layers)' if self.keepEndLayers else '' - free = f' (free)' if self.free else '' - tstamp = f' (tstamp {self.tstamp})' if self.tstamp is not None else '' - - return f'{indents}(via{type}{locked} (at {self.position.X} {self.position.Y}) (size {self.size}) (drill {self.drill}) (layers{layers}){rum}{kel}{free} (net {self.net}){tstamp}){endline}' - -@dataclass -class Arc(): - """The ``arc`` token defines a track arc, which will be generated when using the length-matching - feature on differential pairs. - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-pcb/#_track_arc - """ - - start: Position = field(default_factory=lambda: Position()) - """The ``start`` token defines the coordinates of the beginning of the arc""" - - mid: Position = field(default_factory=lambda: Position()) - """The ``mid`` token defines the coordinates of the mid point of the radius of the arc""" - - end: Position = field(default_factory=lambda: Position()) - """The ``end`` token defines the coordinates of the end of the arc""" - - width: float = 0.2 - """The ``width`` token defines the line width of the arc. Defaults to 0,2.""" - - layer: str = "F.Cu" - """The ``layer`` token defiens the canonical layer the track arc resides on. Defaults to `F.Cu`.""" - - locked: bool = False - """The ``locked`` token defines if the arc cannot be edited. Defaults to False.""" - - net: int = 0 - """The ``net`` token defines the net ordinal number which net in the net section that arc is part - of. Defaults to 0.""" - - tstamp: Optional[str] = None - """The optional ``tstamp`` token defines the unique identifier of the arc""" - - @classmethod - def from_sexpr(cls, exp: list) -> Arc: - """Convert the given S-Expresstion into a Arc object - - Args: - - exp (list): Part of parsed S-Expression ``(arc ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not ``arc`` - - Returns: - - Arc: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'arc': - raise Exception("Expression does not have the correct type") - - object = cls() - for item in exp: - if type(item) != type([]): - if item == 'locked': object.locked = True - continue - if item[0] == 'start': object.start = Position().from_sexpr(item) - elif item[0] == 'mid': object.mid = Position().from_sexpr(item) - elif item[0] == 'end': object.end = Position().from_sexpr(item) - elif item[0] == 'width': object.width = item[1] - elif item[0] == 'layer': object.layer = item[1] - elif item[0] == 'net': object.net = item[1] - elif item[0] == 'tstamp': object.tstamp = item[1] - return object - - def to_sexpr(self, indent=2, newline=True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 2. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - locked = f' locked' if self.locked else '' - tstamp = f' (tstamp {self.tstamp})' if self.tstamp is not None else '' - - expression = f'{indents}(arc{locked} (start {self.start.X} {self.start.Y}) ' - expression += f'(mid {self.mid.X} {self.mid.Y}) (end {self.end.X} {self.end.Y}) ' - expression += f'(width {self.width}) (layer "{dequote(self.layer)}") ' - expression += f'(net {self.net}){tstamp}){endline}' - return expression - - -@dataclass -class Target(): - """The ``target`` token defines a target marker on the PCB - - Documentation: - Not found in KiCad docu - 15.06.2022 - """ - - type: str = "plus" - """The ``type`` token specifies the shape of the marker. Valid types are ``plus`` and ``x``.""" - - position: Position = field(default_factory=lambda: Position()) - """The ``position`` token specifies the position of the target marker""" - - size: float = 0 - """The ``size`` token sets the marker's size""" - - width: float = 0.1 - """The ``width`` token sets the marker's line width""" - - layer: str = "F.Cu" - """The ``layer`` token sets the canonical layer where the target marker resides""" - - tstamp: Optional[str] = None - """The ``tstamp`` token defines the unique identifier of the target""" - - @classmethod - def from_sexpr(cls, exp: list) -> Target: - """Convert the given S-Expresstion into a Target object - - Args: - - exp (list): Part of parsed S-Expression ``(target ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not target - - Returns: - - Target: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'target': - raise Exception("Expression does not have the correct type") - - object = cls() - object.type = exp[1] - for item in exp[2:]: - if item[0] == 'at': object.position = Position().from_sexpr(item) - if item[0] == 'size': object.size = item[1] - if item[0] == 'width': object.width = item[1] - if item[0] == 'layer': object.layer = item[1] - if item[0] == 'tstamp': object.tstamp = item[1] - return object - - def to_sexpr(self, indent=2, newline=True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 2. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - return f'{indents}(target {self.type} (at {self.position.X} {self.position.Y}) (size {self.size}) (width {self.width}) (layer "{self.layer}") (tstamp {self.tstamp})){endline}' diff --git a/kintree/kicad/kiutils/items/common.py b/kintree/kicad/kiutils/items/common.py deleted file mode 100644 index bca15f86..00000000 --- a/kintree/kicad/kiutils/items/common.py +++ /dev/null @@ -1,857 +0,0 @@ -"""Defines all syntax that is shared across the symbol library, footprint library, - schematic, board and work sheet file formats. - -Author: - (C) Marvin Mager - @mvnmgrx - 2022 - -License identifier: - GPL-3.0 - -Major changes: - 02.02.2022 - created - -Documentation taken from: - https://dev-docs.kicad.org/en/file-formats/sexpr-intro/index.html#_common_syntax -""" - -from __future__ import annotations - -from dataclasses import dataclass, field -from typing import Optional, List, Dict - -from kiutils.utils.strings import dequote - -@dataclass -class Position(): - """The ``position`` token defines the positional coordinates and rotation of an object. - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-intro/index.html#_position_identifier - """ - - X: float = 0.0 - """The ``X`` attribute defines the horizontal position of the object""" - - Y: float = 0.0 - """The ``Y`` attribute defines the vertical position of the object""" - - angle: Optional[float] = None - """The optional ``angle`` attribute defines the rotational angle of the object. Not all - objects have rotational position definitions. Symbol text angles are stored in tenths - of a degree. All other angles are stored in degrees.""" - - # TODO: What is this? Documentation does not tell .. - unlocked: bool = False - """The ``unlocked`` token's description has to be defined yet ..""" - - @classmethod - def from_sexpr(cls, exp: list) -> Position: - """Convert the given S-Expresstion into a Position object - - Args: - - exp (list): Part of parsed S-Expression ``(xxx ...)`` - - Raises: - - Exception: When the given expression is not of type ``list`` or the list is less than - 3 items long - - Returns: - - Position: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list) or len(exp) < 3: - raise Exception("Expression does not have the correct type") - - object = cls() - object.X = exp[1] - object.Y = exp[2] - if len(exp) >= 4: - # More than four components means X, Y, and either angle or unlocked are present - if exp[3] != 'unlocked': - object.angle = exp[3] - - for item in exp: - if item == 'unlocked': object.unlocked = True - - return object - - def to_sexpr(self) -> str: - """This object does not have a direct S-Expression representation.""" - raise NotImplementedError("This object does not have a direct S-Expression representation") - - -@dataclass -class Coordinate(): - """The ``coordinate`` token defines a three-dimentional position""" - - X: float = 0.0 - """The ``X`` token defines the position of the object on the x-axis""" - - Y: float = 0.0 - """The ``Y`` token defines the position of the object on the y-axis""" - - Z: float = 0.0 - """The ``Z`` token defines the position of the object on the z-axis""" - - @classmethod - def from_sexpr(cls, exp: list) -> Coordinate: - """Convert the given S-Expresstion into a Coordinate object - - Args: - - exp (list): Part of parsed S-Expression ``(xyz ...)`` - - Raises: - - Exception: When given parameter's type is not a list or the list is not 4 items long - - Exception: When the first item of the list is not xyz - - Returns: - - Coordinate: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list) or len(exp) != 4: - raise Exception("Expression does not have the correct type") - - if exp[0] != 'xyz': - raise Exception("Expression does not have the correct type") - - object = cls() - object.X = exp[1] - object.Y = exp[2] - object.Z = exp[3] - return object - - def to_sexpr(self, indent=0, newline=False) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 0. - - newline (bool): Adds a newline to the end of the output. Defaults to False. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - return f'{indents}(xyz {self.X} {self.Y} {self.Z}){endline}' - - -@dataclass -class ColorRGBA(): - """The ``color`` token defines a RGBA color""" - - R: int = 0 - """The ``R`` token defines the red channel of the color""" - - G: int = 0 - """The ``G`` token defines the green channel of the color""" - - B: int = 0 - """The ``B`` token defines the blue channel of the color""" - - A: int = 0 - """The ``A`` token defines the alpha channel of the color""" - - precision: Optional[int] = None - """Wether the output of ``to_sexpr()`` should have a set number of precision after the decimal - point of the ``self.A`` attribute""" - - @classmethod - def from_sexpr(cls, exp: list) -> ColorRGBA: - """Convert the given S-Expresstion into a ColorRGBA object - - Args: - - exp (list): Part of parsed S-Expression ``(color ...)`` - - Raises: - - Exception: When given parameter's type is not a list or the list is not 5 items long - - Exception: When the first item of the list is not color - - Returns: - - ColorRGBA: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list) or len(exp) != 5: - raise Exception("Expression does not have the correct type") - - if exp[0] != 'color': - raise Exception("Expression does not have the correct type") - - object = cls() - object.R = exp[1] - object.G = exp[2] - object.B = exp[3] - object.A = exp[4] - return object - - def to_sexpr(self, indent=0, newline=False) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 0. - - newline (bool): Adds a newline to the end of the output. Defaults to False. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - if self.precision is not None: - alpha = f'{self.A:.{self.precision}f}' - else: - alpha = f'{self.A}' - - return f'{indents}(color {self.R} {self.G} {self.B} {alpha}){endline}' - -@dataclass -class Stroke(): - """The ``stroke`` token defines how the outlines of graphical objects are drawn. - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-intro/#_stroke_definition - """ - - width: float = 0.0 - """The ``width`` token attribute defines the line width of the graphic object""" - - type: str = "dash" - """The ``type`` token attribute defines the line style of the graphic object. Valid stroke line styles are: - - ``dash``, ``dash_dot``, ``dash_dot_dot`` (version 7), ``dot``, ``default``, ``solid`` - """ - - color: ColorRGBA = field(default_factory=lambda: ColorRGBA()) - """The ``color`` token attributes define the line red, green, blue, and alpha color settings""" - - @classmethod - def from_sexpr(cls, exp: list) -> Stroke: - """Convert the given S-Expresstion into a Stroke object - - Args: - - exp (list): Part of parsed S-Expression ``(stroke ...)`` - - Raises: - - Exception: When given parameter's type is not a list or the list is not 4 items long - - Exception: When the first item of the list is not stroke - - Returns: - - Stroke: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list) or len(exp) != 4: - raise Exception("Expression does not have the correct type") - - if exp[0] != 'stroke': - raise Exception("Expression does not have the correct type") - - object = cls() - for item in exp: - if type(item) != type([]): - continue - if item[0] == 'width': object.width = item[1] - if item[0] == 'type': object.type = item[1] - if item[0] == 'color': object.color = ColorRGBA.from_sexpr(item) - return object - - def to_sexpr(self, indent=2, newline=True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 2. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - expression = f'{indents}(stroke (width {self.width}) (type {self.type}) {self.color.to_sexpr()}){endline}' - return expression - - - -@dataclass -class Font(): - """The ``font`` token attributes define how text is shown. - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-intro/#_text_effects - """ - face: Optional[str] = None - """The optional 'face' token indicates the font family. It should be a TrueType font family - name or "KiCad Font" for the KiCad stroke font. (Kicad version 7)""" - - height: float = 1.0 - """The 'height' token attributes define the font's height""" - - width: float = 1.0 - """The 'width' token attributes define the font's width""" - - thickness: Optional[float] = None - """The 'thickness' token attribute defines the line thickness of the font""" - - bold: bool = False - """The 'bold' token specifies if the font should be bold""" - - italic: bool = False - """The 'italic' token specifies if the font should be italicized""" - - lineSpacing: Optional[float] = None - """The 'line_spacing' token specifies the spacing between lines as a ratio of standard - line-spacing. (Not yet supported)""" - - @classmethod - def from_sexpr(cls, exp: list) -> Font: - """Convert the given S-Expresstion into a Font object - - Args: - - exp (list): Part of parsed S-Expression ``(font ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not font - - Returns: - - Font: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'font': - raise Exception("Expression does not have the correct type") - - object = cls() - for item in exp: - if type(item) != type([]): - if item == 'bold': object.bold = True - if item == 'italic': object.italic = True - continue - if item[0] == 'face': object.face = item[1] - if item[0] == 'size': - object.height = item[1] - object.width = item[2] - if item[0] == 'thickness': object.thickness = item[1] - if item[0] == 'line_spacing': object.lineSpacing = item[1] - return object - - def to_sexpr(self, indent=0, newline=False) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 0. - - newline (bool): Adds a newline to the end of the output. Defaults to False. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - face_name, thickness, bold, italic, linespacing = '', '', '', '', '' - - if self.face is not None: face_name = f'(face {self.face}) ' - if self.thickness is not None: thickness = f' (thickness {self.thickness})' - if self.bold == True: bold = ' bold' - if self.italic == True: italic = ' italic' - if self.lineSpacing is not None: linespacing = f' (line_spacing {self.lineSpacing})' - - expression = f'{indents}(font {face_name}(size {self.height} {self.width}){thickness}{bold}{italic}{linespacing}){endline}' - return expression - -@dataclass -class Justify(): - """The ``justify`` token defines the justification of a text object - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-intro/#_text_effects - """ - - horizontally: Optional[str] = None - """The ``horizontally`` token sets the horizontal justification. Valid values are ``right`` or ``left``""" - - vertically: Optional[str] = None - """The ``vertically`` token sets the vertical justification. Valid values are ``top`` or ``bottom``""" - - mirror: bool = False - """The ``mirror`` token defines if the text is mirrored or not""" - - @classmethod - def from_sexpr(cls, exp: list) -> Justify: - """Convert the given S-Expresstion into a Justify object - - Args: - - exp (list): Part of parsed S-Expression ``(justify ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not justify - - Returns: - - Justify: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'justify': - raise Exception("Expression does not have the correct type") - - object = cls() - for item in exp: - # 'center' is the standard on vertical but not on horizontal in work sheets - if item == 'left' or item == 'right' or item == 'center': object.horizontally = item - if item == 'top' or item == 'bottom': object.vertically = item - if item == 'mirror': object.mirror = True - return object - - def to_sexpr(self, indent=0, newline=False) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 0. - - newline (bool): Adds a newline to the end of the output. Defaults to False. - - Returns: - - str: S-Expression of this object or an empty string (depending on given indentation - and newline settings) if no justification is given. This will cause the text to be - horizontally and vertically aligend - """ - indents = ' '*indent - endline = '\n' if newline else '' - - if self.horizontally is None and self.vertically is None and self.mirror == False: - return f'{indents}{endline}'; - - horizontally, vertically, mirror = '', '', '' - - if self.horizontally is not None: horizontally = f' {self.horizontally}' - if self.vertically is not None: vertically = f' {self.vertically}' - if self.mirror: mirror = f' mirror' - - expression = f'{indents}(justify{horizontally}{vertically}{mirror}){endline}' - return expression - -@dataclass -class Effects(): - """All text objects can have an optional effects section that defines how the text is displayed. - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-intro/#_text_effects - """ - - font: Font = field(default_factory=lambda: Font()) - """The ``font`` token defines how the text is shown""" - - justify: Justify = field(default_factory=lambda: Justify()) - """The ``justify`` token defines the justification of the text""" - - hide: bool = False - """The optional ``hide`` token defines if the text is hidden""" - - @classmethod - def from_sexpr(cls, exp: list) -> Effects: - """Convert the given S-Expresstion into a Effects object - - Args: - - exp (list): Part of parsed S-Expression ``(effects ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not effects - - Returns: - - Effects: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'effects': - raise Exception("Expression does not have the correct type") - - object = cls() - for item in exp: - if type(item) != type([]): - if item == 'hide': object.hide = True - else: continue - if item[0] == 'font': object.font = Font().from_sexpr(item) - if item[0] == 'justify': object.justify = Justify().from_sexpr(item) - return object - - def to_sexpr(self, indent=0, newline=True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 0. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - justification = f' {self.justify.to_sexpr()}' if self.justify.to_sexpr() != '' else '' - hide = f' hide' if self.hide else '' - - expression = f'{indents}(effects {self.font.to_sexpr()}{justification}{hide}){endline}' - return expression - - -@dataclass -class Net(): - """The ``net`` token defines the number and name of a net""" - - number: int = 0 - """The ``number`` token defines the integer number of the net""" - - name: str = "" - """The ``name`` token defines the name of the net""" - - @classmethod - def from_sexpr(cls, exp: list) -> Net: - """Convert the given S-Expresstion into a Net object - - Args: - - exp (list): Part of parsed S-Expression ``(net ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not net - - Returns: - - Net: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'net': - raise Exception("Expression does not have the correct type") - - object = cls() - object.number = exp[1] - object.name = exp[2] - return object - - def to_sexpr(self, indent: int = 0, newline: bool = False) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 0. - - newline (bool): Adds a newline to the end of the output. Defaults to False. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - return f'{indents}(net {self.number} "{dequote(self.name)}"){endline}' - -@dataclass -class Group(): - """The ``group`` token defines a group of items. - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-intro/index.html#_group - """ - - name: str = "" - """The ``name`` attribute defines the name of the group""" - - locked: bool = False - """The ``locked`` token defines if the group may be moved or not""" - - id: str = "" - """The ``id`` token attribute defines the unique identifier of the group""" - - members: List[str] = field(default_factory=list) - """The ``members`` token attributes define a list of unique identifiers of the objects belonging to the group""" - - @classmethod - def from_sexpr(cls, exp: list) -> Group: - """Convert the given S-Expresstion into a Group object - - Args: - - exp (list): Part of parsed S-Expression ``(group ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not group - - Returns: - - Group: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'group': - raise Exception("Expression does not have the correct type") - - object = cls() - object.name = exp[1] - for item in exp: - if type(item) != type([]): - if item == 'locked': object.locked = True - continue - if item[0] == 'id': object.id = item[1] - if item[0] == 'members': - for member in item[1:]: - object.members.append(member) - return object - - def to_sexpr(self, indent: int = 2, newline: bool = True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 2. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - locked = f' locked' if self.locked else '' - - expression = f'{indents}(group "{dequote(self.name)}"{locked} (id {self.id})\n' - expression += f'{indents} (members\n' - for member in self.members: - expression += f'{indents} {member}\n' - - expression += f'{indents} )\n' - expression += f'{indents}){endline}' - return expression - -@dataclass -class PageSettings(): - """The ``paper`` token defines the drawing page size and orientation. - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-intro/#_page_settings - """ - paperSize: str = "A4" - """The ``paperSize`` token defines the size of the paper. Valid sizes are `A0`, `A1`, `A2`, - `A3`, `A4`, `A5`, ``A``, ``B``, ``C``, ``D`` and ``E``. When using user-defines page sizes, set - this to ``User``""" - - width: Optional[float] = None - """The ``width`` token sets the width of a user-defines page size""" - - height: Optional[float] = None - """The ``height`` token sets the height of a user-defines page size""" - - portrait: bool = False - """The ``portrait`` token defines if the page is in portrait or landscape mode""" - - @classmethod - def from_sexpr(cls, exp: list) -> PageSettings: - """Convert the given S-Expresstion into a PageSettings object - - Args: - - exp (list): Part of parsed S-Expression ``(paper ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not paper - - Exception: When the paper type is set to ``User`` and the list's length is not 4 - - Returns: - - PageSettings: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'paper': - raise Exception("Expression does not have the correct type") - - object = cls() - object.paperSize = exp[1] - if object.paperSize == "User": - if len(exp) < 4: - raise Exception("PageSettings: Expected more data for paper type 'User'") - - object.width = exp[2] - object.height = exp[3] - for item in exp: - if type(item) != type([]): - if item == 'portrait': object.portrait = True - continue - return object - - def to_sexpr(self, indent: int = 2, newline: bool = True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 2. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Raises: - - Exception: When paper size is set to ``User`` and width or height is not specified - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - width, height = '', '' - portrait = ' portrait' if self.portrait else '' - if self.paperSize == 'User': - if self.width is None or self.height is None: - raise Exception("Page size set to 'User' but width or height not specified") - width = f' {self.width}' - height = f' {self.height}' - return f'{indents}(paper "{dequote(self.paperSize)}"{width}{height}{portrait}){endline}' - -@dataclass -class TitleBlock(): - """The ``title_block`` token defines the contents of the title block. - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-intro/#_title_block - """ - - title: Optional[str] = None - """The optional ``title`` token attribute is a quoted string that defines the document title""" - - date: Optional[str] = None - """The optional ``date`` token attribute is a quoted string that defines the document date using the YYYY-MM-DD format""" - - revision: Optional[str] = None - """The optional ``revision`` token attribute is a quoted string that defines the document revision""" - - company: Optional[str] = None - """The optional ``company`` token attribute is a quoted string that defines the document company name""" - - comments: Dict[int, str] = field(default_factory=dict) - """The ``comments`` token attributes define a dictionary of document comments where the key is - a number from 1 to 9 and the value is a comment string""" - - @classmethod - def from_sexpr(cls, exp: list) -> TitleBlock: - """Convert the given S-Expresstion into a TitleBlock object - - Args: - - exp (list): Part of parsed S-Expression ``(title_block ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not title_block - - Returns: - - TitleBlock: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'title_block': - raise Exception("Expression does not have the correct type") - - object = cls() - for item in exp: - if item[0] == 'title': object.title = item[1] - if item[0] == 'date': object.date = item[1] - if item[0] == 'rev': object.revision = item[1] - if item[0] == 'company': object.company = item[1] - if item[0] == 'comment': object.comments.update({item[1]: item[2]}) - return object - - def to_sexpr(self, indent: int = 2, newline: bool = True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 2. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - expression = f'{indents}(title_block\n' - if self.title is not None: - expression += f'{indents} (title "{dequote(self.title)}")\n' - - if self.date is not None: - expression += f'{indents} (date "{dequote(self.date)}")\n' - - if self.revision is not None: - expression += f'{indents} (rev "{dequote(self.revision)}")\n' - - if self.company is not None: - expression += f'{indents} (company "{dequote(self.company)}")\n' - - for number, comment in self.comments.items(): - expression += f'{indents} (comment {number} "{dequote(comment)}")\n' - expression += f'{indents}){endline}' - return expression - -@dataclass -class Property(): - """The ``property`` token defines a symbol property when used inside a ``symbol`` definition. - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-intro/index.html#_symbol_property - """ - - key: str = "" - """The ``key`` string defines the name of the property and must be unique""" - - value: str = "" - """The ``value`` string defines the value of the property""" - - id: int = 0 - """The id token defines an integer ID for the property and must be unique""" - - position: Position = field(default_factory=lambda: Position(angle=0)) - """The ``position`` defines the X and Y coordinates as well as the rotation angle of the property. - All three items will initially be set to zero.""" - - effects: Optional[Effects] = None - """The ``effects`` section defines how the text is displayed""" - - @classmethod - def from_sexpr(cls, exp: list) -> Property: - """Convert the given S-Expresstion into a Property object - - Args: - - exp (list): Part of parsed S-Expression ``(property ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not property - - Returns: - - Property: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'property': - raise Exception("Expression does not have the correct type") - - object = cls() - object.key = exp[1] - object.value = exp[2] - for item in exp[3:]: - if item[0] == 'id': object.id = item[1] - if item[0] == 'at': object.position = Position().from_sexpr(item) - if item[0] == 'effects': object.effects = Effects().from_sexpr(item) - return object - - def to_sexpr(self, indent: int = 4, newline: bool = True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 4. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - posA = f' {self.position.angle}' if self.position.angle is not None else '' - - expression = f'{indents}(property "{dequote(self.key)}" "{dequote(self.value)}" (id {self.id}) (at {self.position.X} {self.position.Y}{posA})' - if self.effects is not None: - expression += f'\n{self.effects.to_sexpr(indent+2)}' - expression += f'{indents}){endline}' - else: - expression += f'){endline}' - return expression diff --git a/kintree/kicad/kiutils/items/dimensions.py b/kintree/kicad/kiutils/items/dimensions.py deleted file mode 100644 index 47b38e99..00000000 --- a/kintree/kicad/kiutils/items/dimensions.py +++ /dev/null @@ -1,338 +0,0 @@ -"""The dimensions are used to mark spots in the board file for their dimensions (units, metric, -imperial, etc) - -Author: - (C) Marvin Mager - @mvnmgrx - 2022 - -License identifier: - GPL-3.0 - -Major changes: - 14.06.2022 - created - -Documentation taken from: - https://dev-docs.kicad.org/en/file-formats/sexpr-intro/index.html#_dimension -""" - -from __future__ import annotations - -from dataclasses import dataclass, field -from typing import Optional, List - -from kiutils.items.common import Position -from kiutils.items.gritems import GrText -from kiutils.utils.strings import dequote - -@dataclass -class DimensionFormat(): - """The ``format`` token defines the text formatting of a dimension - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-intro/index.html#_dimension_format - """ - - prefix: Optional[str] = None - """The optional ``prefix`` token defines the string to add to the beginning of the dimension text""" - - suffix: Optional[str] = None - """The optional ``suffix`` token defines the string to add to the end of the dimension text""" - - units: int = 3 - """The ``units`` token defines the dimension units used to display the dimension text. Valid units - are as follows: - - 0: Inches - - 1: Mils - - 2: Millimeters - - 3: Automatic""" - - unitsFormat: int = 1 - """The ``unitsFormat`` token defines how the unit's suffix is formatted. Valid units formats are - as follows: - - 0: No suffix - - 1: Bare suffix - - 2: Wrap suffix in parenthesis""" - - precision: int = 4 - """The ``precision`` token defines the number of significant digits to display""" - - overrideValue: Optional[str] = None - """The optional ``overrideValue`` token defines the text to substitute for the actual physical - dimension""" - - suppressZeroes: bool = False - """The ``suppressZeroes`` token removes all trailing zeros from the dimension text""" - - @classmethod - def from_sexpr(cls, exp: list) -> DimensionFormat: - """Convert the given S-Expresstion into a DimensionFormat object - - Args: - - exp (list): Part of parsed S-Expression ``(format ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not format - - Returns: - - DimensionFormat: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'format': - raise Exception("Expression does not have the correct type") - - object = cls() - for item in exp[1:]: - if type(item) != type([]): - if item == 'suppress_zeroes': object.suppressZeroes = True - continue - if item[0] == 'prefix': object.prefix = item[1] - if item[0] == 'suffix': object.suffix = item[1] - if item[0] == 'units': object.units = item[1] - if item[0] == 'units_format': object.unitsFormat = item[1] - if item[0] == 'precision': object.precision = item[1] - if item[0] == 'override_value': object.overrideValue = item[1] - pass - return object - - def to_sexpr(self, indent: int = 4, newline: bool = True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 4. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - prefix = f' (prefix "{dequote(self.prefix)}")' if self.prefix is not None else '' - suffix = f' (suffix "{dequote(self.suffix)}")' if self.suffix is not None else '' - overwrite_val = f' (override_value "{dequote(self.overrideValue)}")' if self.overrideValue is not None else '' - suppress_zeroes = f' suppress_zeroes' if self.suppressZeroes else '' - - expression = f'{indents}(format{prefix}{suffix} (units {self.units}) (units_format {self.unitsFormat}) (precision {self.precision}){overwrite_val}{suppress_zeroes}){endline}' - return expression - -@dataclass -class DimensionStyle(): - """The ``style`` token defines the style of a dimension - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-intro/index.html#_dimension_style - """ - - thickness: float = 0.0 - """The ``thickness`` token defines the line thickness of the dimension""" - - arrowLength: float = 0.0 - """The ``arrowLength`` token defines the length of the dimension arrows""" - - textPositionMode: int = 0 - """The ``textPositionMode`` token defines the position mode of the dimension text. Valid position - modes are as follows: - - 0: Text is outside the dimension line - - 1: Text is in line with the dimension line - - 2: Text has been manually placed by the user""" - - extensionHeight: Optional[float] = None - """The optional ``extensionHeight`` token defines the length of the extension lines past the - dimension crossbar""" - - textFrame: Optional[int] = None - """The optional ``textFrame`` token defines the style of the frame around the dimension text. This - only applies to leader dimensions. Valid text frames are as follows: - - 0: No text frame - - 1: Rectangle - - 2: Circle - - 3:Rounded rectangle""" - - extensionOffset: Optional[float] = None - """The optional ``extensionOffset`` token defines the distance from feature points to extension - line start""" - - keepTextAligned: bool = False - """The ``keepTextAligned`` token indicates that the dimension text should be kept in line with the - dimension crossbar. When false, the dimension text is shown horizontally regardless of the - orientation of the dimension.""" - - @classmethod - def from_sexpr(cls, exp: list) -> DimensionStyle: - """Convert the given S-Expresstion into a DimensionStyle object - - Args: - - exp (list): Part of parsed S-Expression ``(style ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not style - - Returns: - - DimensionStyle: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'style': - raise Exception("Expression does not have the correct type") - - object = cls() - for item in exp[1:]: - if type(item) != type([]): - if item == 'keep_text_aligned': object.keepTextAligned = True - continue - if item[0] == 'thickness': object.thickness = item[1] - if item[0] == 'arrow_length': object.arrowLength = item[1] - if item[0] == 'text_position_mode': object.textPositionMode = item[1] - if item[0] == 'extension_height': object.extensionHeight = item[1] - if item[0] == 'text_frame': object.textFrame = item[1] - if item[0] == 'extension_offset': object.extensionOffset = item[1] - return object - - def to_sexpr(self, indent: int = 4, newline: bool = True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 4. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - extension_height = f' (extension_height {self.extensionHeight})' if self.extensionHeight is not None else '' - text_frame = f' (text_frame {self.textFrame})' if self.textFrame is not None else '' - extension_offset = f' (extension_offset {self.extensionOffset})' if self.extensionOffset is not None else '' - keep_aligned = f' keep_text_aligned' if self.keepTextAligned else '' - - expression = f'{indents}(style (thickness {self.thickness}) (arrow_length {self.arrowLength}) (text_position_mode {self.textPositionMode}){extension_height}{text_frame}{extension_offset}{keep_aligned}){endline}' - return expression - -@dataclass -class Dimension(): - """The ``dimension`` token defines a dimension in the PCB - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-intro/index.html#_dimension - """ - - locked: bool = False - """The optional ``locked`` token specifies if the dimension can be moved""" - - type: str = "aligned" - """The ``type`` token defines the type of dimension. Valid dimension types are ``aligned``, - ``leader``, ``center``, ``orthogonal`` (and ``radial`` in KiCad version 7)""" - - layer: str = "F.Cu" - """The ``layer`` token defines the canonical layer the polygon resides on""" - - tstamp: Optional[str] = None - """The ``tstamp`` token defines the unique identifier for the footprint. This only applies - to footprints defined in the board file format.""" - - pts: List[Position] = field(default_factory=list) - """The ``pts`` token define the list of xy coordinates of the dimension""" - - height: Optional[float] = None - """The optional ``height`` token defines the height of aligned dimensions""" - - orientation: Optional[float] = None - """The optional ``orientation`` token defines the rotation angle for orthogonal dimensions""" - - leaderLength: Optional[float] = None - """The optional ``leaderLength`` token attribute defines the distance from the marked radius to - the knee for radial dimensions.""" - - grText: Optional[GrText] = None - """The optional ``grText`` token define the dimension text formatting for all dimension types - except center dimensions""" - - format: Optional[DimensionFormat] = None - """The optional ``format`` token define the dimension formatting for all dimension types except - center dimensions""" - - style: DimensionStyle = field(default_factory=lambda: DimensionStyle()) - """The ``style`` token defines the dimension style information""" - - @classmethod - def from_sexpr(cls, exp: list) -> Dimension: - """Convert the given S-Expresstion into a Dimension object - - Args: - - exp (list): Part of parsed S-Expression ``(dimension ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not dimension - - Returns: - - Dimension: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'dimension': - raise Exception("Expression does not have the correct type") - - object = cls() - for item in exp[1:]: - if type(item) != type([]): - if item == 'locked': object.locked = True - continue - if item[0] == 'type': object.type = item[1] - if item[0] == 'layer': object.layer = item[1] - if item[0] == 'tstamp': object.tstamp = item[1] - if item[0] == 'height': object.height = item[1] - if item[0] == 'orientation': object.orientation = item[1] - if item[0] == 'leader_length': object.leaderLength = item[1] - if item[0] == 'gr_text': object.grText = GrText().from_sexpr(item) - if item[0] == 'format': object.format = DimensionFormat().from_sexpr(item) - if item[0] == 'style': object.style = DimensionStyle().from_sexpr(item) - if item[0] == 'pts': - for point in item[1:]: - object.pts.append(Position().from_sexpr(point)) - - return object - - def to_sexpr(self, indent: int = 2, newline: bool = True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 2. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Raises: - - Exception: When number of coordinate points of the dimension equals 0 - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - points = '' - for point in self.pts: - points = f'{points} (xy {point.X} {point.Y})' - if len(points) == 0: - raise Exception("Number of points must not be zero") - - expression = f'{indents}(dimension (type {self.type}) (layer "{self.layer}") (tstamp {self.tstamp})\n' - expression += f'{indents} (pts{points})\n' - if self.height is not None: - expression += f'{indents} (height {self.height})\n' - if self.orientation is not None: - expression += f'{indents} (orientation {self.orientation})\n' - if self.leaderLength is not None: - expression += f'{indents} (leader_length {self.leaderLength})\n' - if self.grText is not None: - expression += self.grText.to_sexpr(indent+2) - if self.format is not None: - expression += self.format.to_sexpr(indent+2) - expression += self.style.to_sexpr(indent+2) - expression += f'{indents}){endline}' - return expression \ No newline at end of file diff --git a/kintree/kicad/kiutils/items/fpitems.py b/kintree/kicad/kiutils/items/fpitems.py deleted file mode 100644 index b136e6a1..00000000 --- a/kintree/kicad/kiutils/items/fpitems.py +++ /dev/null @@ -1,733 +0,0 @@ -"""Footprint graphical items define all of the drawing items that are used in the footprint -definition. This includes text, text boxes, lines, rectangles, circles, arcs, polygons, curves -and dimensions. - -Author: - (C) Marvin Mager - @mvnmgrx - 2022 - -License identifier: - GPL-3.0 - -Major changes: - 08.02.2022 - created - -Documentation taken from: - https://dev-docs.kicad.org/en/file-formats/sexpr-intro/index.html#_footprint_graphics_items -""" - -from __future__ import annotations - -from dataclasses import dataclass, field -from typing import Optional, List - -from kiutils.items.common import Stroke, Position, Effects -from kiutils.utils.strings import dequote - -# FIXME: Several classes have a ``stroke`` member. This feature will be introduced in KiCad 7 and -# has yet to be tested here. - -@dataclass -class FpText(): - """The ``fp_text`` token defines a graphic line in a footprint definition. - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-intro/index.html#_footprint_text - """ - - type: str = "reference" - """The ``type`` attribute defines the type of text. Valid types are ``reference``, ``value``, and - ``user``""" - - text: str = "%REF" - """The ``text`` attribute is a string that defines the text""" - - position: Position = field(default_factory=lambda: Position()) - """The ``position`` defines the X and Y position coordinates and optional orientation angle of - the text""" - - layer: str = "F.Cu" - """The ``layer`` token defines the canonical layer the text resides on""" - - hide: bool = False - """The optional ``hide`` token, defines if the text is hidden""" - - effects: Effects = field(default_factory=lambda: Effects()) - """The ``effects`` token defines how the text is displayed""" - - tstamp: Optional[str] = None # Used since KiCad 6 - """The ``tstamp`` token defines the unique identifier of the text object""" - - @classmethod - def from_sexpr(cls, exp: list) -> FpText: - """Convert the given S-Expresstion into a FpText object - - Args: - - exp (list): Part of parsed S-Expression ``(fp_text ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not fp_text - - Returns: - - FpText: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'fp_text': - raise Exception("Expression does not have the correct type") - - object = cls() - object.type = exp[1] - object.text = exp[2] - for item in exp[3:]: - if type(item) != type([]): - if item == 'hide': object.hide = True - continue - if item[0] == 'at': object.position = Position().from_sexpr(item) - if item[0] == 'layer': object.layer = item[1] - if item[0] == 'effects': object.effects = Effects().from_sexpr(item) - if item[0] == 'tstamp': object.tstamp = item[1] - return object - - def to_sexpr(self, indent: int = 2, newline: bool = True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 2. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - hide = ' hide' if self.hide else '' - unlocked = ' unlocked' if self.position.unlocked else '' - posA = f' {self.position.angle}' if self.position.angle is not None else '' - - expression = f'{indents}(fp_text {self.type} "{dequote(self.text)}" (at {self.position.X} {self.position.Y}{posA}{unlocked}) (layer "{dequote(self.layer)}"){hide}\n' - expression += f'{indents} {self.effects.to_sexpr()}' - if self.tstamp is not None: - expression += f'{indents} (tstamp {self.tstamp})\n' - expression += f'{indents}){endline}' - return expression - -@dataclass -class FpLine(): - """The ``fp_line`` token defines a graphic line in a footprint definition. - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-intro/index.html#_footprint_line - """ - - start: Position = field(default_factory=lambda: Position()) - """The ``start`` token defines the coordinates of the upper left corner of the line""" - - end: Position = field(default_factory=lambda: Position()) - """The ``end`` token defines the coordinates of the low right corner of the line""" - - layer: str = "F.Cu" - """The ``layer`` token defines the canonical layer the line resides on""" - - width: Optional[float] = 0.12 # Used for KiCad < 7 - """The ``width`` token defines the line width of the line. (prior to version 7)""" - - stroke: Optional[Stroke] = None # Used for KiCad >= 7 - """The ``stroke`` describes the line width and style of the line. (version 7)""" - - # FIXME: This is not implemented in to_sexpr() because it does not seem to be used on lines - # in footprints. Further testing required .. - locked: bool = False - """The optional ``locked`` token defines if the line cannot be edited""" - - tstamp: Optional[str] = None # Used since KiCad 6 - """The ``tstamp`` token defines the unique identifier of the line object""" - - @classmethod - def from_sexpr(cls, exp: list) -> FpLine: - """Convert the given S-Expresstion into a FpLine object - - Args: - - exp (list): Part of parsed S-Expression ``(fp_line ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not fp_line - - Returns: - - FpLine: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'fp_line': - raise Exception("Expression does not have the correct type") - - object = cls() - for item in exp: - if type(item) != type([]): - if item == 'locked': object.locked = True - else: continue - - if item[0] == 'start': object.start = Position.from_sexpr(item) - if item[0] == 'end': object.end = Position.from_sexpr(item) - if item[0] == 'layer': object.layer = item[1] - if item[0] == 'tstamp': object.tstamp = item[1] - if item[0] == 'width': - object.width = item[1] - object.stroke = None - if item[0] == 'stroke': - object.stroke = Stroke.from_sexpr(item) - object.width = None - - return object - - def to_sexpr(self, indent: int = 2, newline: bool = True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 2. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - tstamp = f' (tstamp {self.tstamp})' if self.tstamp is not None else '' - if self.width is not None: - width = f' (width {self.width})' - elif self.stroke is not None: - width = f' {self.stroke.to_sexpr(indent=0, newline=False)}' - else: - width = '' - - return f'{indents}(fp_line (start {self.start.X} {self.start.Y}) (end {self.end.X} {self.end.Y}) (layer "{dequote(self.layer)}"){width}{tstamp}){endline}' - -@dataclass -class FpRect(): - """The ``fp_rect`` token defines a graphic rectangle in a footprint definition. - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-intro/index.html#_footprint_rectangle - """ - - start: Position = field(default_factory=lambda: Position()) - """The ``start`` token defines the coordinates of the upper left corner of the rectangle""" - - end: Position = field(default_factory=lambda: Position()) - """The ``end`` token defines the coordinates of the low right corner of the rectangle""" - - layer: str = "F.Cu" - """The ``layer`` token defines the canonical layer the rectangle resides on""" - - width: Optional[float] = 0.12 # Used for KiCad < 7 - """The ``width`` token defines the line width of the rectangle. (prior to version 7)""" - - stroke: Optional[Stroke] = None # Used for KiCad >= 7 - """The ``stroke`` describes the line width and style of the rectangle. (version 7)""" - - fill: Optional[str] = None - """The optional ``fill`` toke defines how the rectangle is filled. Valid fill types are solid - and none. If not defined, the rectangle is not filled.""" - - locked: bool = False - """The optional ``locked`` token defines if the rectangle cannot be edited""" - - tstamp: Optional[str] = None # Used since KiCad 6 - """The ``tstamp`` token defines the unique identifier of the rectangle object""" - - @classmethod - def from_sexpr(cls, exp: list) -> FpRect: - """Convert the given S-Expresstion into a FpRect object - - Args: - - exp (list): Part of parsed S-Expression ``(fp_rect ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not fp_rect - - Returns: - - FpRect: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'fp_rect': - raise Exception("Expression does not have the correct type") - - object = cls() - for item in exp: - if type(item) != type([]): - if item == 'locked': object.locked = True - else: continue - - if item[0] == 'start': object.start = Position.from_sexpr(item) - if item[0] == 'end': object.end = Position.from_sexpr(item) - if item[0] == 'layer': object.layer = item[1] - if item[0] == 'tstamp': object.tstamp = item[1] - if item[0] == 'fill': object.fill = item[1] - if item[0] == 'width': - object.width = item[1] - object.stroke = None - if item[0] == 'stroke': - object.stroke = Stroke.from_sexpr(item) - object.width = None - - return object - - def to_sexpr(self, indent: int = 2, newline: bool = True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 2. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - tstamp = f' (tstamp {self.tstamp})' if self.tstamp is not None else '' - locked = ' locked' if self.locked else '' - fill = f' (fill {self.fill})' if self.fill is not None else '' - - if self.width is not None: - width = f' (width {self.width})' - elif self.stroke is not None: - width = f' {self.stroke.to_sexpr(indent=0, newline=False)}' - else: - width = '' - - return f'{indents}(fp_rect (start {self.start.X} {self.start.Y}) (end {self.end.X} {self.end.Y}) (layer "{dequote(self.layer)}"){width}{fill}{locked}{tstamp}){endline}' - -@dataclass -class FpTextBox(): - """TBD when KiCad 7 is released - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-intro/index.html#_footprint_text_box - """ - - locked: bool = False - text: str = "text" - start: Optional[Position] = None - end: Optional[Position] = None - pts: List[Position] = field(default_factory=list) - angle: Optional[float] = None - layer: str = "F.Cu" - tstamp: Optional[str] = None - effects: Effects = field(default_factory=lambda: Effects()) - stroke: Stroke = field(default_factory=lambda: Stroke()) - renderCache: Optional[str] = None - - @classmethod - def from_sexpr(cls, exp: list) -> FpTextBox: - """Not implemented yet""" - raise NotImplementedError("FpTextBoxes are not yet handled! Please report this bug along with the file being parsed.") - - def to_sexpr(self, indent: int = 2, newline: bool = True) -> str: - """Not implemented yet""" - raise NotImplementedError("FpTextBoxes are not yet handled! Please report this bug along with the file being parsed.") - -@dataclass -class FpCircle(): - """The ``fp_circle `` token defines a graphic circle in a footprint definition. - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-intro/index.html#_footprint_circle - """ - - center: Position = field(default_factory=lambda: Position()) - """The ``center`` token defines the coordinates of the center of the circle""" - - end: Position = field(default_factory=lambda: Position()) - """The ``end`` token defines the coordinates of the low right corner of the circle""" - - layer: str = "F.Cu" - """The ``layer`` token defines the canonical layer the circle resides on""" - - width: Optional[float] = 0.12 # Used for KiCad < 7 - """The ``width`` token defines the line width of the circle. (prior to version 7)""" - - stroke: Optional[Stroke] = None # Used for KiCad >= 7 - """The ``stroke`` describes the line width and style of the circle. (version 7)""" - - fill: Optional[str] = None - """The optional ``fill`` toke defines how the circle is filled. Valid fill types are solid and none. If not defined, the circle is not filled.""" - - locked: bool = False - """The optional ``locked`` token defines if the circle cannot be edited""" - - tstamp: Optional[str] = None # Used since KiCad 6 - """The ``tstamp`` token defines the unique identifier of the circle object""" - - @classmethod - def from_sexpr(cls, exp: list) -> FpCircle: - """Convert the given S-Expresstion into a FpCircle object - - Args: - - exp (list): Part of parsed S-Expression ``(fp_circle ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not fp_circle - - Returns: - - FpCircle: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'fp_circle': - raise Exception("Expression does not have the correct type") - - object = cls() - for item in exp: - if type(item) != type([]): - if item == 'locked': object.locked = True - else: continue - - if item[0] == 'center': object.center = Position.from_sexpr(item) - if item[0] == 'end': object.end = Position.from_sexpr(item) - if item[0] == 'layer': object.layer = item[1] - if item[0] == 'tstamp': object.tstamp = item[1] - if item[0] == 'fill': object.fill = item[1] - if item[0] == 'width': - object.width = item[1] - object.stroke = None - if item[0] == 'stroke': - object.stroke = Stroke.from_sexpr(item) - object.width = None - - return object - - def to_sexpr(self, indent: int = 2, newline: bool = True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 2. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - tstamp = f' (tstamp {self.tstamp})' if self.tstamp is not None else '' - locked = ' locked' if self.locked else '' - fill = f' (fill {self.fill})' if self.fill is not None else '' - - if self.width is not None: - width = f' (width {self.width})' - elif self.stroke is not None: - width = f' {self.stroke.to_sexpr(indent=0, newline=False)}' - else: - width = '' - - return f'{indents}(fp_circle (center {self.center.X} {self.center.Y}) (end {self.end.X} {self.end.Y}) (layer "{dequote(self.layer)}"){width}{fill}{locked}{tstamp}){endline}' - -@dataclass -class FpArc(): - """The ``fp_arc`` token defines a graphic arc in a footprint definition. - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-intro/index.html#_footprint_arc - """ - - start: Position = field(default_factory=lambda: Position()) - """The ``start`` token defines the coordinates of the start position of the arc radius""" - - mid: Position = field(default_factory=lambda: Position()) - """The ``mid`` token defines the coordinates of the midpoint along the arc""" - - end: Position = field(default_factory=lambda: Position()) - """The ``end`` token defines the coordinates of the end position of the arc radius""" - - layer: str = "F.Cu" - """The ``layer`` token defines the canonical layer the arc resides on""" - - width: Optional[float] = 0.12 # Used for KiCad < 7 - """The ``width`` token defines the line width of the arc. (prior to version 7)""" - - stroke: Optional[Stroke] = None # Used for KiCad >= 7 - """The ``stroke`` describes the line width and style of the arc. (version 7)""" - - locked: bool = False - """The optional ``locked`` token defines if the arc cannot be edited""" - - tstamp: Optional[str] = None # Used since KiCad 6 - """The ``tstamp`` token defines the unique identifier of the arc object""" - - @classmethod - def from_sexpr(cls, exp: list) -> FpArc: - """Convert the given S-Expresstion into a FpArc object - - Args: - - exp (list): Part of parsed S-Expression ``(fp_arc ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not fp_arc - - Returns: - - FpArc: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'fp_arc': - raise Exception("Expression does not have the correct type") - - object = cls() - for item in exp: - if type(item) != type([]): - if item == 'locked': object.locked = True - else: continue - - if item[0] == 'start': object.start = Position.from_sexpr(item) - if item[0] == 'mid': object.mid = Position.from_sexpr(item) - if item[0] == 'end': object.end = Position.from_sexpr(item) - if item[0] == 'layer': object.layer = item[1] - if item[0] == 'tstamp': object.tstamp = item[1] - if item[0] == 'width': - object.width = item[1] - object.stroke = None - if item[0] == 'stroke': - object.stroke = Stroke.from_sexpr(item) - object.width = None - - return object - - def to_sexpr(self, indent: int = 2, newline: bool = True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 2. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - tstamp = f' (tstamp {self.tstamp})' if self.tstamp is not None else '' - locked = ' locked' if self.locked else '' - - if self.width is not None: - width = f' (width {self.width})' - elif self.stroke is not None: - width = f' {self.stroke.to_sexpr(indent=0, newline=False)}' - else: - width = '' - - return f'{indents}(fp_arc (start {self.start.X} {self.start.Y}) (mid {self.mid.X} {self.mid.Y}) (end {self.end.X} {self.end.Y}) (layer "{dequote(self.layer)}"){width}{locked}{tstamp}){endline}' - -@dataclass -class FpPoly(): - """The ``fp_poly`` token defines a graphic polygon in a footprint definition. - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-intro/index.html#_footprint_polygon - """ - - layer: str = "F.Cu" - """The ``layer`` token defines the canonical layer the polygon resides on""" - - coordinates: List[Position] = field(default_factory=list) - """The ``coordinates`` define the list of X/Y coordinates of the polygon outline""" - - width: Optional[float] = 0.12 # Used for KiCad < 7 - """The ``width`` token defines the line width of the polygon. (prior to version 7)""" - - stroke: Optional[Stroke] = None # Used for KiCad >= 7 - """The ``stroke`` describes the line width and style of the polygon. (version 7)""" - - fill: Optional[str] = None - """The optional ``fill`` toke defines how the polygon is filled. Valid fill types are solid - and none. If not defined, the rectangle is not filled.""" - - locked: bool = False - """The optional ``locked`` token defines if the polygon cannot be edited""" - - tstamp: Optional[str] = None # Used since KiCad 6 - """The ``tstamp`` token defines the unique identifier of the polygon object""" - - @classmethod - def from_sexpr(cls, exp: list) -> FpPoly: - """Convert the given S-Expresstion into a FpPoly object - - Args: - - exp (list): Part of parsed S-Expression ``(fp_poly ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not fp_poly - - Returns: - - FpPoly: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'fp_poly': - raise Exception("Expression does not have the correct type") - - object = cls() - - for item in exp: - if type(item) != type([]): - if item == 'locked': object.locked = True - else: continue - - if item[0] == 'pts': - for point in item[1:]: - object.coordinates.append(Position().from_sexpr(point)) - if item[0] == 'layer': object.layer = item[1] - if item[0] == 'tstamp': object.tstamp = item[1] - if item[0] == 'fill': object.fill = item[1] - if item[0] == 'width': - object.width = item[1] - object.stroke = None - if item[0] == 'stroke': - object.stroke = Stroke.from_sexpr(item) - object.width = None - - return object - - def to_sexpr(self, indent: int = 2, newline: bool = True) -> str: - """Generate the S-Expression representing this object. When no coordinates are set - in the polygon, the resulting S-Expression will be left empty. - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 2. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - if len(self.coordinates) == 0: - return f'{indents}{endline}' - - tstamp = f' (tstamp {self.tstamp})' if self.tstamp is not None else '' - locked = ' locked' if self.locked else '' - fill = f' (fill {self.fill})' if self.fill is not None else '' - - if self.width is not None: - width = f' (width {self.width})' - elif self.stroke is not None: - width = f' {self.stroke.to_sexpr(indent=0, newline=False)}' - else: - width = '' - - expression = f'{indents}(fp_poly (pts\n' - for point in self.coordinates: - expression += f'{indents} (xy {point.X} {point.Y})\n' - expression += f'{indents} ) (layer "{dequote(self.layer)}"){width}{fill}{locked}{tstamp}){endline}' - return expression - -@dataclass -class FpCurve(): - """The ``fp_curve`` token defines a graphic Cubic Bezier curve in a footprint definition. - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-intro/index.html#_footprint_curve - """ - - coordinates: List[Position] = field(default_factory=list) - """The ``coordinates`` define the list of X/Y coordinates of the curve outline""" - - layer: str = "F.Cu" - """The ``layer`` token defines the canonical layer the curve resides on""" - - width: Optional[float] = 0.12 # Used for KiCad < 7 - """The ``width`` token defines the line width of the curve. (prior to version 7)""" - - stroke: Optional[Stroke] = None # Used for KiCad >= 7 - """The ``stroke`` describes the line width and style of the curve. (version 7)""" - - locked: bool = False - """The optional ``locked`` token defines if the curve cannot be edited""" - - tstamp: Optional[str] = None # Used since KiCad 6 - """The ``tstamp`` token defines the unique identifier of the curve object""" - - @classmethod - def from_sexpr(cls, exp: list) -> FpCurve: - """Convert the given S-Expresstion into a FpCurve object - - Args: - - exp (list): Part of parsed S-Expression ``(fp_curve ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not fp_curve - - Returns: - - FpCurve: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'fp_curve': - raise Exception("Expression does not have the correct type") - - object = cls() - for item in exp: - if type(item) != type([]): - if item == 'locked': object.locked = True - else: continue - - if item[0] == 'pts': - for point in item[1:]: - object.coordinates.append(Position().from_sexpr(point)) - if item[0] == 'layer': object.layer = item[1] - if item[0] == 'tstamp': object.tstamp = item[1] - if item[0] == 'width': - object.width = item[1] - object.stroke = None - if item[0] == 'stroke': - object.stroke = Stroke.from_sexpr(item) - object.width = None - - return object - - def to_sexpr(self, indent: int = 2, newline: bool = True) -> str: - """Generate the S-Expression representing this object. When no coordinates are set - in the curve, the resulting S-Expression will be left empty. - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 2. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - if len(self.coordinates) == 0: - return f'{indents}{endline}' - - tstamp = f' (tstamp {self.tstamp})' if self.tstamp is not None else '' - locked = ' locked' if self.locked else '' - - if self.width is not None: - width = f' (width {self.width})' - elif self.stroke is not None: - width = f' {self.stroke.to_sexpr(indent=0, newline=False)}' - else: - width = '' - - expression = f'{indents}(fp_curve (pts\n' - for point in self.coordinates: - expression += f'{indents} (xy {point.X} {point.Y})\n' - expression += f'{indents}) (layer "{dequote(self.layer)}"){width}{locked}{tstamp}){endline}' - return expression diff --git a/kintree/kicad/kiutils/items/gritems.py b/kintree/kicad/kiutils/items/gritems.py deleted file mode 100644 index 11614870..00000000 --- a/kintree/kicad/kiutils/items/gritems.py +++ /dev/null @@ -1,641 +0,0 @@ -"""The graphical items are footprint and board items that are outside of the connectivity items. - This includes graphical items on technical, user, and copper layers. Graphical items are also - used to define complex pad geometries. - -Author: - (C) Marvin Mager - @mvnmgrx - 2022 - -License identifier: - GPL-3.0 - -Major changes: - 10.02.2022 - created - -Documentation taken from: - https://dev-docs.kicad.org/en/file-formats/sexpr-intro/index.html#_graphic_items -""" - -from __future__ import annotations - -from dataclasses import dataclass, field -from typing import Optional, List - -from kiutils.items.common import Effects, Position, Stroke -from kiutils.utils.strings import dequote - -@dataclass -class GrText(): - """The ``gr_text`` token defines a graphical text. - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-intro/index.html#_graphical_text - """ - - text: str = "" - """The ``text`` attribute is a string that defines the text""" - - position: Position = field(default_factory=lambda: Position()) - """The ``position`` defines the X and Y position coordinates and optional orientation angle of the text""" - - layer: Optional[str] = None - """The ``layer`` token defines the canonical layer the text resides on""" - - effects: Effects = field(default_factory=lambda: Effects()) - """The ``effects`` token defines how the text is displayed""" - - tstamp: Optional[str] = None # Used since KiCad 6 - """The ``tstamp`` token defines the unique identifier of the text object""" - - locked: bool = False - """The ``locked`` token defines if the object may be moved or not""" - - @classmethod - def from_sexpr(cls, exp: list) -> GrText: - """Convert the given S-Expresstion into a GrText object - - Args: - - exp (list): Part of parsed S-Expression ``(gr_text ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not gr_text - - Returns: - - GrText: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'gr_text': - raise Exception("Expression does not have the correct type") - - object = cls() - object.text = exp[1] - for item in exp[2:]: - if type(item) != type([]): - if item == 'locked': object.locked = True - continue - if item[0] == 'at': object.position = Position().from_sexpr(item) - if item[0] == 'layer': object.layer = item[1] - if item[0] == 'effects': object.effects = Effects().from_sexpr(item) - if item[0] == 'tstamp': object.tstamp = item[1] - return object - - def to_sexpr(self, indent: int = 2, newline: bool = True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 2. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - posA = f' {self.position.angle}' if self.position.angle is not None else '' - layer = f' (layer "{dequote(self.layer)}")' if self.layer is not None else '' - tstamp = f' (tstamp {self.tstamp})' if self.tstamp is not None else '' - locked = f' locked' if self.locked else '' - - expression = f'{indents}(gr_text{locked} "{dequote(self.text)}" (at {self.position.X} {self.position.Y}{posA}){layer}{tstamp}\n' - expression += f'{indents} {self.effects.to_sexpr()}' - expression += f'{indents}){endline}' - return expression - -@dataclass -class GrTextBox(): - """TBD when KiCad 7 is released - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-intro/index.html#_graphical_text_box - """ - - locked: bool = False - text: str = "text" - start: Optional[Position] = None - end: Optional[Position] = None - pts: List[Position] = field(default_factory=list) - angle: Optional[float] = None - layer: str = "F.Cu" - tstamp: Optional[str] = None - effects: Effects = field(default_factory=lambda: Effects()) - stroke: Stroke = field(default_factory=lambda: Stroke()) - renderCache: Optional[str] = None - - @classmethod - def from_sexpr(cls, exp: list) -> GrTextBox: - """Not implemented yet""" - raise NotImplementedError("GrTextBoxes are not yet handled! Please report this bug along with the file being parsed.") - - def to_sexpr(self, indent: int = 2, newline: bool = True) -> str: - """Not implemented yet""" - raise NotImplementedError("GrTextBoxes are not yet handled! Please report this bug along with the file being parsed.") - -@dataclass -class GrLine(): - """The ``gr_line`` token defines a graphical line. - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-intro/index.html#_graphical_line - """ - - start: Position = field(default_factory=lambda: Position()) - """The ``start`` token defines the coordinates of the start of the line""" - - end: Position = field(default_factory=lambda: Position()) - """The ``end`` token defines the coordinates of the end of the line""" - - angle: Optional[float] = None - """The optional ``angle`` token defines the rotational angle of the line""" - - layer: Optional[str] = None - """The ``layer`` token defines the canonical layer the rectangle resides on""" - - width: Optional[float] = 0.12 # Used for KiCad < 7 - """The ``width`` token defines the line width of the rectangle. (prior to version 7)""" - - tstamp: Optional[str] = None # Used since KiCad 6 - """The ``tstamp`` token defines the unique identifier of the rectangle object""" - - locked: bool = False - """The ``locked`` token defines if the object may be moved or not""" - - @classmethod - def from_sexpr(cls, exp: list) -> GrLine: - """Convert the given S-Expresstion into a GrLine object - - Args: - - exp (list): Part of parsed S-Expression ``(gr_line ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not gr_line - - Returns: - - GrLine: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'gr_line': - raise Exception("Expression does not have the correct type") - - object = cls() - for item in exp: - if type(item) != type([]): - if item == 'locked': object.locked = True - continue - if item[0] == 'start': object.start = Position.from_sexpr(item) - if item[0] == 'end': object.end = Position.from_sexpr(item) - if item[0] == 'layer': object.layer = item[1] - if item[0] == 'tstamp': object.tstamp = item[1] - if item[0] == 'width': object.width = item[1] - return object - - def to_sexpr(self, indent: int = 2, newline: bool = True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 2. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - locked = f' locked' if self.locked else '' - - tstamp = f' (tstamp {self.tstamp})' if self.tstamp is not None else '' - layer = f' (layer "{dequote(self.layer)}")' if self.layer is not None else '' - angle = f' (angle {self.angle}' if self.angle is not None else '' - - return f'{indents}(gr_line{locked} (start {self.start.X} {self.start.Y}) (end {self.end.X} {self.end.Y}){angle}{layer} (width {self.width}){tstamp}){endline}' - -@dataclass -class GrRect(): - """The ``gr_rect`` token defines a graphical rectangle. - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-intro/index.html#_graphical_rectangle - """ - - start: Position = field(default_factory=lambda: Position()) - """The ``start`` token defines the coordinates of the upper left corner of the rectangle""" - - end: Position = field(default_factory=lambda: Position()) - """The ``end`` token defines the coordinates of the low right corner of the rectangle""" - - layer: Optional[str] = None - """The ``layer`` token defines the canonical layer the rectangle resides on""" - - width: Optional[float] = 0.12 # Used for KiCad < 7 - """The ``width`` token defines the line width of the rectangle. (prior to version 7)""" - - fill: Optional[str] = None - """The optional ``fill`` toke defines how the rectangle is filled. Valid fill types are solid and none. If not defined, the rectangle is not filled""" - - tstamp: Optional[str] = None # Used since KiCad 6 - """The ``tstamp`` token defines the unique identifier of the rectangle object""" - - locked: bool = False - """The ``locked`` token defines if the object may be moved or not""" - - @classmethod - def from_sexpr(cls, exp: list) -> GrRect: - """Convert the given S-Expresstion into a GrRect object - - Args: - - exp (list): Part of parsed S-Expression ``(gr_rect ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not gr_rect - - Returns: - - GrRect: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'gr_rect': - raise Exception("Expression does not have the correct type") - - object = cls() - for item in exp: - if type(item) != type([]): - if item == 'locked': object.locked = True - continue - if item[0] == 'start': object.start = Position.from_sexpr(item) - if item[0] == 'end': object.end = Position.from_sexpr(item) - if item[0] == 'layer': object.layer = item[1] - if item[0] == 'tstamp': object.tstamp = item[1] - if item[0] == 'fill': object.fill = item[1] - if item[0] == 'width': object.width = item[1] - return object - - def to_sexpr(self, indent: int = 2, newline: bool = True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 2. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - locked = f' locked' if self.locked else '' - - tstamp = f' (tstamp {self.tstamp})' if self.tstamp is not None else '' - layer = f' (layer "{dequote(self.layer)}")' if self.layer is not None else '' - fill = f' (fill {self.fill})' if self.fill is not None else '' - - return f'{indents}(gr_rect{locked} (start {self.start.X} {self.start.Y}) (end {self.end.X} {self.end.Y}){layer} (width {self.width}){fill}{tstamp}){endline}' - -@dataclass -class GrCircle(): - """The ``gr_circle `` token defines a graphical circle. - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-intro/index.html#_graphical_circle - """ - - center: Position = field(default_factory=lambda: Position()) - """The ``center`` token defines the coordinates of the center of the circle""" - - end: Position = field(default_factory=lambda: Position()) - """The ``end`` token defines the coordinates of the low right corner of the circle""" - - layer: Optional[str] = None - """The ``layer`` token defines the canonical layer the circle resides on""" - - width: Optional[float] = 0.12 # Used for KiCad < 7 - """The ``width`` token defines the line width of the circle. (prior to version 7)""" - - fill: Optional[str] = None - """The optional ``fill`` toke defines how the circle is filled. Valid fill types are solid and none. If not defined, the rectangle is not filled""" - - tstamp: Optional[str] = None # Used since KiCad 6 - """The ``tstamp`` token defines the unique identifier of the circle object""" - - locked: bool = False - """The ``locked`` token defines if the object may be moved or not""" - - @classmethod - def from_sexpr(cls, exp: list) -> GrCircle: - """Convert the given S-Expresstion into a GrCircle object - - Args: - - exp (list): Part of parsed S-Expression ``(gr_circle ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not gr_circle - - Returns: - - GrCircle: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'gr_circle': - raise Exception("Expression does not have the correct type") - - object = cls() - for item in exp: - if type(item) != type([]): - if item == 'locked': object.locked = True - continue - if item[0] == 'center': object.center = Position.from_sexpr(item) - if item[0] == 'end': object.end = Position.from_sexpr(item) - if item[0] == 'layer': object.layer = item[1] - if item[0] == 'tstamp': object.tstamp = item[1] - if item[0] == 'fill': object.fill = item[1] - if item[0] == 'width': object.width = item[1] - - return object - - def to_sexpr(self, indent: int = 2, newline: bool = True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 2. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - locked = f' locked' if self.locked else '' - - tstamp = f' (tstamp {self.tstamp})' if self.tstamp is not None else '' - layer = f' (layer "{dequote(self.layer)}")' if self.layer is not None else '' - fill = f' (fill {self.fill})' if self.fill is not None else '' - - return f'{indents}(gr_circle{locked} (center {self.center.X} {self.center.Y}) (end {self.end.X} {self.end.Y}){layer} (width {self.width}){fill}{tstamp}){endline}' - -@dataclass -class GrArc(): - """The ``gr_arc`` token defines a graphic arc. - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-intro/index.html#_graphical_arc - """ - - start: Position = field(default_factory=lambda: Position()) - """The ``start`` token defines the coordinates of the start position of the arc radius""" - - mid: Position = field(default_factory=lambda: Position()) - """The ``mid`` token defines the coordinates of the midpoint along the arc""" - - end: Position = field(default_factory=lambda: Position()) - """The ``end`` token defines the coordinates of the end position of the arc radius""" - - layer: Optional[str] = None - """The ``layer`` token defines the canonical layer the arc resides on""" - - width: Optional[float] = 0.12 # Used for KiCad < 7 - """The ``width`` token defines the line width of the arc. (prior to version 7)""" - - tstamp: Optional[str] = None # Used since KiCad 6 - """The ``tstamp`` token defines the unique identifier of the arc object.""" - - locked: bool = False - """The ``locked`` token defines if the object may be moved or not""" - - @classmethod - def from_sexpr(cls, exp: list) -> GrArc: - """Convert the given S-Expresstion into a GrArc object - - Args: - - exp (list): Part of parsed S-Expression ``(gr_arc ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not gr_arc - - Returns: - - GrArc: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'gr_arc': - raise Exception("Expression does not have the correct type") - - object = cls() - for item in exp: - if type(item) != type([]): - if item == 'locked': object.locked = True - continue - if item[0] == 'start': object.start = Position.from_sexpr(item) - if item[0] == 'mid': object.mid = Position.from_sexpr(item) - if item[0] == 'end': object.end = Position.from_sexpr(item) - if item[0] == 'layer': object.layer = item[1] - if item[0] == 'tstamp': object.tstamp = item[1] - if item[0] == 'width': object.width = item[1] - - return object - - def to_sexpr(self, indent: int = 2, newline: bool = True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 2. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - locked = f' locked' if self.locked else '' - - tstamp = f' (tstamp {self.tstamp})' if self.tstamp is not None else '' - layer = f' (layer "{dequote(self.layer)}")' if self.layer is not None else '' - - return f'{indents}(gr_arc{locked} (start {self.start.X} {self.start.Y}) (mid {self.mid.X} {self.mid.Y}) (end {self.end.X} {self.end.Y}){layer} (width {self.width}){tstamp}){endline}' - -@dataclass -class GrPoly(): - """The ``gr_poly`` token defines a graphic polygon in a footprint definition. - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-intro/index.html#_graphical_polygon - """ - - layer: Optional[str] = None - """The ``coordinates`` define the list of X/Y coordinates of the polygon outline""" - - coordinates: List[Position] = field(default_factory=list) - """The ``layer`` token defines the canonical layer the polygon resides on""" - - width: Optional[float] = 0.12 # Used for KiCad < 7 - """The ``width`` token defines the line width of the polygon. (prior to version 7)""" - - fill: Optional[str] = None - """The optional ``fill`` toke defines how the polygon is filled. Valid fill types are solid and none. If not defined, the rectangle is not filled""" - - tstamp: Optional[str] = None # Used since KiCad 6 - """The ``tstamp`` token defines the unique identifier of the polygon object""" - - locked: bool = False - """The ``locked`` token defines if the object may be moved or not""" - - @classmethod - def from_sexpr(cls, exp: list) -> GrPoly: - """Convert the given S-Expresstion into a GrPoly object - - Args: - - exp (list): Part of parsed S-Expression ``(gr_poly ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not gr_poly - - Returns: - - GrPoly: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'gr_poly': - raise Exception("Expression does not have the correct type") - - object = cls() - - for item in exp: - if type(item) != type([]): - if item == 'locked': object.locked = True - continue - if item[0] == 'pts': - for point in item[1:]: - object.coordinates.append(Position().from_sexpr(point)) - if item[0] == 'layer': object.layer = item[1] - if item[0] == 'tstamp': object.tstamp = item[1] - if item[0] == 'fill': object.fill = item[1] - if item[0] == 'width': object.width = item[1] - - return object - - def to_sexpr(self, indent: int = 2, newline: bool = True, pts_newline: bool = False) -> str: - """Generate the S-Expression representing this object. When no coordinates are set - in the polygon, the resulting S-Expression will be left empty. - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 2. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - pts_newline (bool): Adds a newline for the ``(pts ..)`` token as KiCad treats - this different in Board files than Footprint files. Defaults to - False. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - if len(self.coordinates) == 0: - return f'{indents}{endline}' - - tstamp = f' (tstamp {self.tstamp})' if self.tstamp is not None else '' - layer = f' (layer "{dequote(self.layer)}")' if self.layer is not None else '' - fill = f' (fill {self.fill})' if self.fill is not None else '' - locked = f' locked' if self.locked else '' - - if pts_newline: - expression = f'{indents}(gr_poly{locked}\n' - expression += f'{indents} (pts\n' - else: - expression = f'{indents}(gr_poly{locked} (pts\n' - - for point in self.coordinates: - expression += f'{indents} (xy {point.X} {point.Y})\n' - expression += f'{indents} ){layer} (width {self.width}){fill}{tstamp}){endline}' - return expression - -@dataclass -class GrCurve(): - """The ``gr_curve`` token defines a graphic Cubic Bezier curve in a footprint definition. - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-intro/index.html#_graphical_curve - """ - coordinates: List[Position] = field(default_factory=list) - """The ``coordinates`` define the list of X/Y coordinates of the curve outline""" - - layer: Optional[str] = None - """The ``layer`` token defines the canonical layer the curve resides on""" - - width: Optional[float] = 0.12 # Used for KiCad < 7 - """The ``width`` token defines the line width of the curve. (prior to version 7)""" - - tstamp: Optional[str] = None # Used since KiCad 6 - """The ``tstamp`` token defines the unique identifier of the curve object""" - - locked: bool = False - """The ``locked`` token defines if the object may be moved or not""" - - @classmethod - def from_sexpr(cls, exp: list) -> GrCurve: - """Convert the given S-Expresstion into a GrCurve object - - Args: - - exp (list): Part of parsed S-Expression ``(gr_curve ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not gr_curve - - Returns: - - GrCurve: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'gr_curve': - raise Exception("Expression does not have the correct type") - - object = cls() - for item in exp: - if type(item) != type([]): - if item == 'locked': object.locked = True - continue - if item[0] == 'pts': - for point in item[1:]: - object.coordinates.append(Position().from_sexpr(point)) - if item[0] == 'layer': object.layer = item[1] - if item[0] == 'tstamp': object.tstamp = item[1] - if item[0] == 'width': object.width = item[1] - - return object - - def to_sexpr(self, indent: int = 2, newline: bool = True) -> str: - """Generate the S-Expression representing this object. When no coordinates are set - in the curve, the resulting S-Expression will be left empty. - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 2. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - if len(self.coordinates) == 0: - return f'{indents}{endline}' - - tstamp = f' (tstamp {self.tstamp})' if self.tstamp is not None else '' - layer = f' (layer "{dequote(self.layer)}")' if self.layer is not None else '' - locked = f' locked' if self.locked else '' - - expression = f'{indents}(gr_curve{locked} (pts\n' - for point in self.coordinates: - expression += f'{indents} (xy {point.X} {point.Y})\n' - expression += f'{indents}){layer} (width {self.width}{tstamp}){endline}' - return expression diff --git a/kintree/kicad/kiutils/items/schitems.py b/kintree/kicad/kiutils/items/schitems.py deleted file mode 100644 index 9435e17e..00000000 --- a/kintree/kicad/kiutils/items/schitems.py +++ /dev/null @@ -1,1162 +0,0 @@ -"""Defines items used in KiCad schematic files - -Author: - (C) Marvin Mager - @mvnmgrx - 2022 - -License identifier: - GPL-3.0 - -Major changes: - 19.02.2022 - created - -Documentation taken from: - https://dev-docs.kicad.org/en/file-formats/sexpr-schematic/ -""" - -from __future__ import annotations - -from dataclasses import dataclass, field -from typing import Optional, List, Dict - -from kiutils.items.common import Position, ColorRGBA, Stroke, Effects, Property -from kiutils.utils.strings import dequote - -@dataclass -class Junction(): - """The ``junction`` token defines a junction in the schematic - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-schematic/#_junction_section - """ - - position: Position = field(default_factory=lambda: Position()) - """The ``position`` defines the X and Y coordinates of the junction""" - - diameter: float = 0 - """The ``diameter`` token attribute defines the DIAMETER of the junction. A diameter of 0 - is the default diameter in the system settings.""" - - color: ColorRGBA = field(default_factory=lambda: ColorRGBA()) - """The ``color`` token attributes define the Red, Green, Blue, and Alpha transparency of - the junction. If all four attributes are 0, the default junction color is used.""" - - uuid: str = "" - """The ``uuid`` defines the universally unique identifier""" - - @classmethod - def from_sexpr(cls, exp: list) -> Junction: - """Convert the given S-Expresstion into a Junction object - - Args: - - exp (list): Part of parsed S-Expression ``(junction ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not junction - - Returns: - - Junction: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'junction': - raise Exception("Expression does not have the correct type") - - object = cls() - for item in exp: - if item[0] == 'at': object.position = Position().from_sexpr(item) - if item[0] == 'color': object.color = ColorRGBA().from_sexpr(item) - if item[0] == 'diameter': object.color = item[1] - if item[0] == 'uuid': object.uuid = item[1] - return object - - def to_sexpr(self, indent=2, newline=True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 2. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - expression = f'{indents}(junction (at {self.position.X} {self.position.Y}) (diameter {self.diameter}) {self.color.to_sexpr()}\n' - expression += f'{indents} (uuid {self.uuid})\n' - expression += f'{indents}){endline}' - return expression - -@dataclass -class NoConnect(): - """The ``no_connect`` token defines a unused pin connection in the schematic - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-schematic/#_no_connect_section - """ - - position: Position = field(default_factory=lambda: Position()) - """The ``position`` defines the X and Y coordinates of the no connect""" - - uuid: str = "" - """The ``uuid`` defines the universally unique identifier""" - - @classmethod - def from_sexpr(cls, exp: list) -> NoConnect: - """Convert the given S-Expresstion into a NoConnect object - - Args: - - exp (list): Part of parsed S-Expression ``(no_connect ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not no_connect - - Returns: - - NoConnect: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'no_connect': - raise Exception("Expression does not have the correct type") - - object = cls() - for item in exp: - if item[0] == 'at': object.position = Position().from_sexpr(item) - if item[0] == 'uuid': object.uuid = item[1] - return object - - def to_sexpr(self, indent=2, newline=True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 2. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - return f'{indents}(no_connect (at {self.position.X} {self.position.Y}) (uuid {self.uuid})){endline}' - -@dataclass -class BusEntry(): - """The ``bus_entry`` token defines a bus entry in the schematic - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-schematic/#_bus_entry_section - """ - - position: Position = field(default_factory=lambda: Position()) - """The ``position`` defines the X and Y coordinates of the bus entry""" - - uuid: str = "" - """The ``uuid`` defines the universally unique identifier""" - - size: Position = field(default_factory=lambda: Position()) # Re-using Position class here - """The ``size`` token attributes define the X and Y distance of the end point from - the position of the bus entry""" - - stroke: Stroke = field(default_factory=lambda: Stroke()) - """The ``stroke`` defines how the bus entry is drawn""" - - @classmethod - def from_sexpr(cls, exp: list) -> BusEntry: - """Convert the given S-Expresstion into a BusEntry object - - Args: - - exp (list): Part of parsed S-Expression ``(bus_entry ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not bus_entry - - Returns: - - BusEntry: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'bus_entry': - raise Exception("Expression does not have the correct type") - - object = cls() - for item in exp: - if item[0] == 'at': object.position = Position().from_sexpr(item) - if item[0] == 'stroke': object.stroke = Stroke().from_sexpr(item) - if item[0] == 'size': object.size = Position().from_sexpr(item) - if item[0] == 'uuid': object.uuid = item[1] - return object - - def to_sexpr(self, indent=2, newline=True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 2. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - expression = f'{indents}(bus_entry (at {self.position.X} {self.position.Y}) (size {self.size.X} {self.size.Y})\n' - expression += self.stroke.to_sexpr(indent+2) - expression += f'{indents} (uuid {self.uuid})\n' - expression += f'{indents}){endline}' - return expression - -@dataclass -class Connection(): - """The ``wire`` and ``bus`` tokens define wires and buses in the schematic - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-schematic/#_wire_and_bus_section - """ - - type: str = "wire" - """The ``type`` token defines wether the connection is a ``bus`` or a ``wire``""" - - points: List[Position] = field(default_factory=list) - """The ``points`` token defines the list of X and Y coordinates of start and end points - of the wire or bus""" - - stroke: Stroke = field(default_factory=lambda: Stroke()) - """The ``stroke`` defines how the connection is drawn""" - - uuid: str = "" - """The ``uuid`` defines the universally unique identifier""" - - @classmethod - def from_sexpr(cls, exp: list) -> Connection: - """Convert the given S-Expresstion into a Connection object - - Args: - - exp (list): Part of parsed S-Expression ``(wire ...)`` or ``(bus ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not wire or bus - - Returns: - - Connection: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if not (exp[0] == 'wire' or exp[0] == 'bus'): - raise Exception("Expression does not have the correct type") - - object = cls() - object.type = exp[0] - for item in exp: - if item[0] == 'pts': - for point in item[1:]: - object.points.append(Position().from_sexpr(point)) - if item[0] == 'stroke': object.stroke = Stroke().from_sexpr(item) - if item[0] == 'uuid': object.uuid = item[1] - return object - - def to_sexpr(self, indent=2, newline=True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 2. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - points = '' - for point in self.points: - points += f' (xy {point.X} {point.Y})' - - expression = f'{indents}({self.type} (pts{points})\n' - expression += self.stroke.to_sexpr(indent+2) - expression += f'{indents} (uuid {self.uuid})\n' - expression += f'{indents}){endline}' - return expression - -@dataclass -class Image(): - """The ``image`` token defines on or more embedded images in a schematic - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-schematic/#_image_section - """ - - position: Position = field(default_factory=lambda: Position()) - """The ``position`` defines the X and Y coordinates of the image""" - - scale: Optional[float] = None - """The optional ``scale`` token attribute defines the scale factor (size) of the image""" - - data: List[str] = field(default_factory=list) - """The ``data`` token attribute defines the image data in the portable network graphics - format (PNG) encoded with MIME type base64 as a list of strings""" - - uuid: str = "" - """The ``uuid`` defines the universally unique identifier""" - - @classmethod - def from_sexpr(cls, exp: list) -> Image: - """Convert the given S-Expresstion into a Image object - - Args: - - exp (list): Part of parsed S-Expression ``(image ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not image - - Returns: - - Image: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'image': - raise Exception("Expression does not have the correct type") - - object = cls() - for item in exp: - if item[0] == 'at': object.position = Position().from_sexpr(item) - if item[0] == 'scale': object.scale = item[1] - if item[0] == 'uuid': object.uuid = item[1] - if item[0] == 'data': - for b64part in item[1:]: - object.data.append(b64part) - return object - - def to_sexpr(self, indent=2, newline=True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 2. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - scale = f' (scale {self.scale})' if self.scale is not None else '' - - expression = f'{indents}(image (at {self.position.X} {self.position.Y}){scale}\n' - expression += f'{indents} (uuid {self.uuid})\n' - expression += f'{indents} (data\n' - for b64part in self.data: - expression += f'{indents} {b64part}\n' - expression += f'{indents} )\n' - expression += f'{indents}){endline}' - return expression - -@dataclass -class PolyLine(): - """The ``polyline`` token defines one or more lines that may or may not represent a polygon - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-schematic/#_graphical_line_section - """ - - points: List[Position] = field(default_factory=list) - """The ``points`` token defines the list of X/Y coordinates of to draw line(s) - between. A minimum of two points is required.""" - - stroke: Stroke = field(default_factory=lambda: Stroke()) - """The ``stroke`` defines how the graphical line is drawn""" - - uuid: str = "" - """The ``uuid`` defines the universally unique identifier""" - - @classmethod - def from_sexpr(cls, exp: list) -> PolyLine: - """Convert the given S-Expresstion into a PolyLine object - - Args: - - exp (list): Part of parsed S-Expression ``(polyline ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not polyline - - Returns: - - PolyLine: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'polyline': - raise Exception("Expression does not have the correct type") - - object = cls() - for item in exp: - if item[0] == 'pts': - for point in item[1:]: - object.points.append(Position().from_sexpr(point)) - if item[0] == 'stroke': object.stroke = Stroke().from_sexpr(item) - if item[0] == 'uuid': object.uuid = item[1] - return object - - def to_sexpr(self, indent=2, newline=True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 2. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - points = '' - for point in self.points: - points += f' (xy {point.X} {point.Y})' - - expression = f'{indents}(polyline (pts{points})\n' - expression += self.stroke.to_sexpr(indent+2) - expression += f'{indents} (uuid {self.uuid})\n' - expression += f'{indents}){endline}' - return expression - -@dataclass -class Text(): - """The ``text`` token defines graphical text in a schematic - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-schematic/#_graphical_text_section - """ - - text: str = "" - """The ``text`` token defines the text string""" - - position: Position = field(default_factory=lambda: Position()) - """The ``position`` token defines the X and Y coordinates and rotation angle of the text""" - - effects: Effects = field(default_factory=lambda: Effects()) - """The ``effects`` token defines how the text is drawn""" - - uuid: str = "" - """The ``uuid`` defines the universally unique identifier""" - - @classmethod - def from_sexpr(cls, exp: list) -> Text: - """Convert the given S-Expresstion into a Text object - - Args: - - exp (list): Part of parsed S-Expression ``(text ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not text - - Returns: - - Text: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'text': - raise Exception("Expression does not have the correct type") - - object = cls() - object.text = exp[1] - for item in exp[2:]: - if item[0] == 'at': object.position = Position().from_sexpr(item) - if item[0] == 'effects': object.effects = Effects().from_sexpr(item) - if item[0] == 'uuid': object.uuid = item[1] - return object - - def to_sexpr(self, indent=2, newline=True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 2. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - posA = f' {self.position.angle}' if self.position.angle is not None else '' - - expression = f'{indents}(text "{dequote(self.text)}"' - - # Strings longer or equal than 50 chars have the position in the next line - if len(self.text) >= 50: - expression += f'\n{indents} ' - else: - expression += ' ' - expression += f'(at {self.position.X} {self.position.Y}{posA})\n' - expression += self.effects.to_sexpr(indent+2) - expression += f'{indents} (uuid {self.uuid})\n' - expression += f'{indents}){endline}' - return expression - -@dataclass -class LocalLabel(): - """The ``label`` token defines an wire or bus label name in a schematic - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-schematic/#local_label_section - """ - - text: str = "" - """The ``text`` token defines the text in the label""" - - position: Position = field(default_factory=lambda: Position()) - """The ``position`` token defines the X and Y coordinates and rotation angle of the label""" - - effects: Effects = field(default_factory=lambda: Effects()) - """The ``effects`` token defines how the label is drawn""" - - uuid: str = "" - """The ``uuid`` defines the universally unique identifier""" - - @classmethod - def from_sexpr(cls, exp: list) -> LocalLabel: - """Convert the given S-Expresstion into a LocalLabel object - - Args: - - exp (list): Part of parsed S-Expression ``(label ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not label - - Returns: - - LocalLabel: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'label': - raise Exception("Expression does not have the correct type") - - object = cls() - object.text = exp[1] - for item in exp[2:]: - if item[0] == 'at': object.position = Position().from_sexpr(item) - if item[0] == 'effects': object.effects = Effects().from_sexpr(item) - if item[0] == 'uuid': object.uuid = item[1] - return object - - def to_sexpr(self, indent=2, newline=True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 2. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - posA = f' {self.position.angle}' if self.position.angle is not None else '' - - expression = f'{indents}(label "{dequote(self.text)}" (at {self.position.X} {self.position.Y}{posA})\n' - expression += self.effects.to_sexpr(indent+2) - expression += f'{indents} (uuid {self.uuid})\n' - expression += f'{indents}){endline}' - return expression - -@dataclass -class GlobalLabel(): - """The ``global_label`` token defines a label name that is visible across all schematics in a design - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-schematic/#_global_label_section - """ - - text: str = "" - """The ``text`` token defines the text in the label""" - - shape: str = "input" - """The ``shape`` token defines the way the global label is drawn. Possible values are: - ``input``, ``output``, ``bidirectional``, ``tri_state``, ``passive``.""" - - fieldsAutoplaced: bool = False - """The ``fields_autoplaced`` is a flag that indicates that any PROPERTIES associated - with the global label have been place automatically""" - - position: Position = field(default_factory=lambda: Position()) - """The ``position`` token defines the X and Y coordinates and rotation angle of the label""" - - effects: Effects = field(default_factory=lambda: Effects()) - """The ``effects`` token defines how the label is drawn""" - - uuid: str = "" - """The ``uuid`` defines the universally unique identifier""" - - properties: List[Property] = field(default_factory=list) - """ The ``properties`` token defines a list of properties of the global label. Currently, the - only supported property is the inter-sheet reference""" - - @classmethod - def from_sexpr(cls, exp: list) -> GlobalLabel: - """Convert the given S-Expresstion into a GlobalLabel object - - Args: - - exp (list): Part of parsed S-Expression ``(global_label ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not global_label - - Returns: - - GlobalLabel: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'global_label': - raise Exception("Expression does not have the correct type") - - object = cls() - object.text = exp[1] - for item in exp[2:]: - if item[0] == 'fields_autoplaced': object.fieldsAutoplaced = True - if item[0] == 'at': object.position = Position().from_sexpr(item) - if item[0] == 'effects': object.effects = Effects().from_sexpr(item) - if item[0] == 'property': object.properties.append(Property().from_sexpr(item)) - if item[0] == 'shape': object.shape = item[1] - if item[0] == 'uuid': object.uuid = item[1] - return object - - def to_sexpr(self, indent=2, newline=True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 2. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - posA = f' {self.position.angle}' if self.position.angle is not None else '' - fa = ' (fields_autoplaced)' if self.fieldsAutoplaced else '' - - expression = f'{indents}(global_label "{dequote(self.text)}" (shape {self.shape}) (at {self.position.X} {self.position.Y}{posA}){fa}\n' - expression += self.effects.to_sexpr(indent+2) - expression += f'{indents} (uuid {self.uuid})\n' - for property in self.properties: - expression += property.to_sexpr(indent+2) - expression += f'{indents}){endline}' - return expression - -@dataclass -class HierarchicalLabel(): - """The ``hierarchical_label`` token defines a label that are used by hierarchical sheets to - define connections between sheet in hierarchical designs - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-schematic/#_hierarchical_label_section - """ - - text: str = "" - """The ``text`` token defines the text in the label""" - - shape: str = "input" - """The ``shape`` token defines the way the global label is drawn. Possible values are: - ``input``, ``output``, ``bidirectional``, ``tri_state``, ``passive``.""" - - position: Position = field(default_factory=lambda: Position()) - """The ``position`` token defines the X and Y coordinates and rotation angle of the label""" - - effects: Effects = field(default_factory=lambda: Effects()) - """The ``effects`` token defines how the label is drawn""" - - uuid: str = "" - """The ``uuid`` defines the universally unique identifier""" - - @classmethod - def from_sexpr(cls, exp: list) -> HierarchicalLabel: - """Convert the given S-Expresstion into a HierarchicalLabel object - - Args: - - exp (list): Part of parsed S-Expression ``(hierarchical_label ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not hierarchical_label - - Returns: - - HierarchicalLabel: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'hierarchical_label': - raise Exception("Expression does not have the correct type") - - object = cls() - object.text = exp[1] - for item in exp[2:]: - if item[0] == 'at': object.position = Position().from_sexpr(item) - if item[0] == 'effects': object.effects = Effects().from_sexpr(item) - if item[0] == 'shape': object.shape = item[1] - if item[0] == 'uuid': object.uuid = item[1] - return object - - def to_sexpr(self, indent=2, newline=True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 2. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - posA = f' {self.position.angle}' if self.position.angle is not None else '' - - expression = f'{indents}(hierarchical_label "{dequote(self.text)}" (shape {self.shape}) (at {self.position.X} {self.position.Y}{posA})\n' - expression += self.effects.to_sexpr(indent+2) - expression += f'{indents} (uuid {self.uuid})\n' - expression += f'{indents}){endline}' - return expression - -@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 - - 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""" - - 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""" - - inBom: bool = False - """The ``in_bom`` token attribute determines whether the schematic symbol appears in any bill - 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""" - - fieldsAutoplaced: bool = False - """The ``fields_autoplaced`` is a flag that indicates that any PROPERTIES associated - with the global label have been place automatically""" - - uuid: Optional[str] = "" - """The optional `uuid` defines the universally unique identifier""" - - properties: List[Property] = field(default_factory=list) - """The ``properties`` section defines a list of symbol properties of the schematic symbol""" - - 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""" - - 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.""" - - @classmethod - def from_sexpr(cls, exp: list) -> SchematicSymbol: - """Convert the given S-Expresstion into a SchematicSymbol object - - Args: - - exp (list): Part of parsed S-Expression ``(symbol ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not symbol - - Returns: - - SchematicSymbol: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'symbol': - raise Exception("Expression does not have the correct type") - - 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] == '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 - if item[0] == 'on_board': object.onBoard = True if item[1] == 'yes' else False - if item[0] == 'at': object.position = Position().from_sexpr(item) - if item[0] == 'property': object.properties.append(Property().from_sexpr(item)) - if item[0] == 'pin': object.pins.update({item[1]: item[2][1]}) - if item[0] == 'mirror': object.mirror = item[1] - return object - - def to_sexpr(self, indent=2, newline=True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 2. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - posA = f' {self.position.angle}' if self.position.angle is not None else '' - fa = f' (fields_autoplaced)' if self.fieldsAutoplaced else '' - inBom = 'yes' if self.inBom else 'no' - 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 '' - - expression = f'{indents}(symbol (lib_id "{dequote(self.libraryIdentifier)}") (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' - for property in self.properties: - expression += property.to_sexpr(indent+2) - for number, uuid in self.pins.items(): - expression += f'{indents} (pin "{dequote(number)}" (uuid {uuid}))\n' - expression += f'{indents}){endline}' - return expression - -@dataclass -class HierarchicalPin(): - """The ``pin`` token in a sheet object defines an electrical connection between the sheet in a - schematic with the hierarchical label defined in the associated schematic file - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-schematic/#_hierarchical_sheet_pin_definition - """ - - name: str = "" - """ The ``name`` attribute defines the name of the sheet pin. It must have an identically named - hierarchical label in the associated schematic file.""" - - connectionType: str = "input" - """The electrical connect type token defines the type of electrical connect made by the - sheet pin""" - - position: Position = field(default_factory=lambda: Position()) - """The ``position`` defines the X and Y coordinates and angle of rotation of the pin""" - - effects: Effects = field(default_factory=lambda: Effects()) - """The ``effects`` section defines how the pin name text is drawn""" - - uuid: str = "" - """The ``uuid`` defines the universally unique identifier""" - - @classmethod - def from_sexpr(cls, exp: list) -> HierarchicalPin: - """Convert the given S-Expresstion into a HierarchicalPin object - - Args: - - exp (list): Part of parsed S-Expression ``(pin ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not pin - - Returns: - - HierarchicalPin: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'pin': - raise Exception("Expression does not have the correct type") - - object = cls() - object.name = exp[1] - object.connectionType = exp[2] - for item in exp[3:]: - if item[0] == 'at': object.position = Position().from_sexpr(item) - if item[0] == 'effects': object.effects = Effects().from_sexpr(item) - if item[0] == 'uuid': object.uuid = item[1] - return object - - def to_sexpr(self, indent=4, newline=True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 4. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - posA = f' {self.position.angle}' if self.position.angle is not None else '' - - expression = f'{indents}(pin "{dequote(self.name)}" {self.connectionType} (at {self.position.X} {self.position.Y}{posA})\n' - expression += self.effects.to_sexpr(indent+2) - expression += f'{indents} (uuid {self.uuid})\n' - expression += f'{indents}){endline}' - return expression - -@dataclass -class HierarchicalSheet(): - """The ``sheet`` token defines a hierarchical sheet of the schematic - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-schematic/#_hierarchical_sheet_section - """ - - position: Position = field(default_factory=lambda: Position()) - """The ``position`` defines the X and Y coordinates and angle of rotation of the sheet in the schematic""" - - width: float = 0 - """The ``width`` token defines the width of the sheet""" - - height: float = 0 - """The ``height`` token defines the height of the sheet""" - - fieldsAutoplaced: bool = False - """The ``fields_autoplaced`` is a flag that indicates that any PROPERTIES associated - with the global label have been place automatically""" - - stroke: Stroke = field(default_factory=lambda: Stroke()) - """The ``stroke`` defines how the sheet outline is drawn""" - - fill: ColorRGBA = field(default_factory=lambda: ColorRGBA()) - """The fill defines the color how the sheet is filled""" - - uuid: str = "" - """The ``uuid`` defines the universally unique identifier""" - - sheetName: Property = field(default_factory=lambda: Property(key="Sheet name")) - """The ``sheetName`` is a property that defines the name of the sheet. The property's - key should therefore be set to `Sheet name`""" - - fileName: Property = field(default_factory=lambda: Property(key="Sheet file")) - """The ``fileName`` is a property that defines the file name of the sheet. The property's - key should therefore be set to `Sheet file`""" - - pins: List[HierarchicalPin] = field(default_factory=list) - """The ``pins`` section is a list of hierarchical pins that map a hierarchical label defined in - the associated schematic file""" - - @classmethod - def from_sexpr(cls, exp: list) -> HierarchicalSheet: - """Convert the given S-Expresstion into a HierarchicalSheet object - - Args: - - exp (list): Part of parsed S-Expression ``(sheet ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not sheet - - Returns: - - HierarchicalSheet: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'sheet': - raise Exception("Expression does not have the correct type") - - object = cls() - for item in exp[1:]: - if item[0] == 'fields_autoplaced': object.fieldsAutoplaced = True - if item[0] == 'at': object.position = Position().from_sexpr(item) - if item[0] == 'stroke': object.stroke = Stroke().from_sexpr(item) - if item[0] == 'size': - object.width = item[1] - object.height = item[2] - if item[0] == 'fill': - object.fill = ColorRGBA().from_sexpr(item[1]) - object.fill.precision = 4 - if item[0] == 'uuid': object.uuid = item[1] - if item[0] == 'property': - if item[1] == 'Sheet name': object.sheetName = Property().from_sexpr(item) - if item[1] == 'Sheet file': object.fileName = Property().from_sexpr(item) - if item[0] == 'pin': object.pins.append(HierarchicalPin().from_sexpr(item)) - return object - - def to_sexpr(self, indent=2, newline=True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 2. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - fa = ' (fields_autoplaced)' if self.fieldsAutoplaced else '' - - expression = f'{indents}(sheet (at {self.position.X} {self.position.Y}) (size {self.width} {self.height}){fa}\n' - expression += self.stroke.to_sexpr(indent+2) - expression += f'{indents} (fill {self.fill.to_sexpr()})\n' - expression += f'{indents} (uuid {self.uuid})\n' - expression += self.sheetName.to_sexpr(indent+2) - expression += self.fileName.to_sexpr(indent+2) - for pin in self.pins: - expression += pin.to_sexpr(indent+2) - expression += f'{indents}){endline}' - return expression - -@dataclass -class HierarchicalSheetInstance(): - """The sheet_instance token defines the per sheet information for the entire schematic. This - section will only exist in schematic files that are the root sheet of a project - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-schematic/#_hierarchical_sheet_instance_section - """ - - instancePath: str = "/" - """The ``instancePath`` attribute is the path to the sheet instance""" - - page: str = "1" - """The ``page`` token defines the page number of the schematic represented by the sheet - instance information. Page numbers can be any valid string.""" - - @classmethod - def from_sexpr(cls, exp: list) -> HierarchicalSheetInstance: - """Convert the given S-Expresstion into a HierarchicalSheetInstance object - - Args: - - exp (list): Part of parsed S-Expression ``(path ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not path - - Returns: - - HierarchicalSheetInstance: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'path': - raise Exception("Expression does not have the correct type") - - object = cls() - object.instancePath = exp[1] - for item in exp[2:]: - if item[0] == 'page': object.page = item[1] - return object - - def to_sexpr(self, indent=4, newline=True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 4. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - return f'{indents}(path "{dequote(self.instancePath)}" (page "{dequote(self.page)}")){endline}' - -@dataclass -class SymbolInstance(): - """The ``symbol_instance`` token defines the per symbol information for the entire schematic - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-schematic/#_symbol_instance_section - """ - - path: str = "/" - """The ``path`` attribute is the path to the sheet instance""" - - reference: str = "" - """The ``reference`` token attribute is a string that defines the reference designator for - the symbol instance""" - - unit: int = 0 - """The unit token attribute is a integer ordinal that defines the symbol unit for the - symbol instance. For symbols that do not define multiple units, this will always be 1.""" - - value: str = "" - """The value token attribute is a string that defines the value field for the symbol instance""" - - footprint: str = "" - """The ``footprint`` token attribute is a string that defines the LIBRARY_IDENTIFIER for footprint associated with the symbol instance""" - - @classmethod - def from_sexpr(cls, exp: list) -> SymbolInstance: - """Convert the given S-Expresstion into a SymbolInstance object - - Args: - - exp (list): Part of parsed S-Expression ``(path ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not path - - Returns: - - SymbolInstance: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'path': - raise Exception("Expression does not have the correct type") - - object = cls() - object.path = exp[1] - for item in exp[2:]: - if item[0] == 'reference': object.reference = item[1] - if item[0] == 'unit': object.unit = item[1] - if item[0] == 'value': object.value = item[1] - if item[0] == 'footprint': object.footprint = item[1] - return object - - def to_sexpr(self, indent=4, newline=True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 4. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - expression = f'{indents}(path "{dequote(self.path)}"\n' - expression += f'{indents} (reference "{dequote(self.reference)}") (unit {self.unit}) (value "{dequote(self.value)}") (footprint "{dequote(self.footprint)}")\n' - expression += f'{indents}){endline}' - return expression diff --git a/kintree/kicad/kiutils/items/syitems.py b/kintree/kicad/kiutils/items/syitems.py deleted file mode 100644 index 57746436..00000000 --- a/kintree/kicad/kiutils/items/syitems.py +++ /dev/null @@ -1,491 +0,0 @@ -"""Symbol graphical items define all of the drawing items that are used in the symbol -definition. This includes text, text boxes, lines, rectangles, circles, arcs, polygons -and curves. - -Author: - (C) Marvin Mager - @mvnmgrx - 2022 - -License identifier: - GPL-3.0 - -Major changes: - 16.02.2022 - created - -Documentation taken from: - https://dev-docs.kicad.org/en/file-formats/sexpr-intro/index.html#_symbol_graphic_items -""" - -from __future__ import annotations - -from dataclasses import dataclass, field -from typing import List - -from kiutils.items.common import Position, Stroke, Effects -from kiutils.utils.strings import dequote - -@dataclass -class SyFill(): - """The ``fill`` token defines how schematic and symbol library graphical items are filled. - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-intro/index.html#_fill_definition - """ - - type: str = "none" - """The ``type`` attribute defines how the graphical item is filled. Possible values are: - - ``none``: Graphic is not filled - - ``outline``: Graphic item filled with the line color - - ``background``: Graphic item filled with the theme background color - """ - - @classmethod - def from_sexpr(cls, exp: list) -> SyFill: - """Convert the given S-Expresstion into a SyFill object - - Args: - - exp (list): Part of parsed S-Expression ``(fill ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not fill - - Returns: - - SyFill: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'fill': - raise Exception("Expression does not have the correct type") - - object = cls() - for item in exp: - if item[0] == 'type': object.type = item[1] - return object - - def to_sexpr(self, indent: int = 2, newline: bool = True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 2. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - return f'{indents}(fill (type {self.type})){endline}' - - -@dataclass -class SyArc(): - """The ``arc`` token defines a graphical arc in a symbol definition. - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-intro/index.html#_symbol_arc - """ - - start: Position = field(default_factory=lambda: Position()) - """The ``start`` token defines the coordinates of start point of the arc""" - - mid: Position = field(default_factory=lambda: Position()) - """The ``mid`` token defines the coordinates of mid point of the arc""" - - end: Position = field(default_factory=lambda: Position()) - """The ``end`` token defines the coordinates of end point of the arc""" - - stroke: Stroke = field(default_factory=lambda: Stroke()) - """The ``stroke`` defines how the arc outline is drawn""" - - fill: SyFill = field(default_factory=lambda: SyFill()) - """The ``fill`` token attributes define how the arc is filled""" - - @classmethod - def from_sexpr(cls, exp: list) -> SyArc: - """Convert the given S-Expresstion into a SyArc object - - Args: - - exp (list): Part of parsed S-Expression ``(arc ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not arc - - Returns: - - SyArc: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'arc': - raise Exception("Expression does not have the correct type") - - object = cls() - for item in exp: - if item[0] == 'start': object.start = Position().from_sexpr(item) - if item[0] == 'mid': object.mid = Position().from_sexpr(item) - if item[0] == 'end': object.end = Position().from_sexpr(item) - if item[0] == 'stroke': object.stroke = Stroke().from_sexpr(item) - if item[0] == 'fill': object.fill = SyFill().from_sexpr(item) - return object - - def to_sexpr(self, indent: int = 6, newline: bool = True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 6. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - startA = f' {self.start.angle}' if self.start.angle is not None else '' - midA = f' {self.mid.angle}' if self.mid.angle is not None else '' - endA = f' {self.end.angle}' if self.end.angle is not None else '' - - expression = f'{indents}(arc (start {self.start.X} {self.start.Y}{startA}) (mid {self.mid.X} {self.mid.Y}{midA}) (end {self.end.X} {self.end.Y}{endA})\n' - expression += f'{indents}{self.stroke.to_sexpr()}' - expression += f'{indents}{self.fill.to_sexpr()}' - expression += f'{indents}){endline}' - return expression - -@dataclass -class SyCircle(): - """The ``circle`` token defines a graphical circle in a symbol definition. - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-intro/index.html#_symbol_circle - """ - - center: Position = field(default_factory=lambda: Position()) - """The ``center`` token defines the coordinates of center point of the circle""" - - radius: float = 0.0 - """The ``radius`` token defines the length of the radius of the circle""" - - stroke: Stroke = field(default_factory=lambda: Stroke()) - """The ``stroke`` defines how the circle outline is drawn""" - - fill: SyFill = field(default_factory=lambda: SyFill()) - """The ``fill`` token attributes define how the circle is filled""" - - @classmethod - def from_sexpr(cls, exp: list) -> SyCircle: - """Convert the given S-Expresstion into a SyCircle object - - Args: - - exp (list): Part of parsed S-Expression ``(circle ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not circle - - Returns: - - SyCircle: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'circle': - raise Exception("Expression does not have the correct type") - - object = cls() - for item in exp: - if item[0] == 'center': object.center = Position().from_sexpr(item) - if item[0] == 'radius': object.radius = item[1] - if item[0] == 'stroke': object.stroke = Stroke().from_sexpr(item) - if item[0] == 'fill': object.fill = SyFill().from_sexpr(item) - return object - - def to_sexpr(self, indent: int = 6, newline: bool = True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 6. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - expression = f'{indents}(circle (center {self.center.X} {self.center.Y}) (radius {self.radius})\n' - expression += f'{indents}{self.stroke.to_sexpr()}' - expression += f'{indents}{self.fill.to_sexpr()}' - expression += f'{indents}){endline}' - return expression - -@dataclass -class SyCurve(): - """The ``curve`` token defines a graphical Qubic Bezier curve. - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-intro/index.html#_symbol_curve - """ - - points: List[Position] = field(default_factory=list) - """The ``points`` token defines the four X/Y coordinates of each point of the curve""" - - stroke: Stroke = field(default_factory=lambda: Stroke()) - """The ``stroke`` defines how the curve outline is drawn""" - - fill: SyFill = field(default_factory=lambda: SyFill()) - """The ``fill`` token attributes define how curve arc is filled""" - - @classmethod - def from_sexpr(cls, exp: list) -> SyCurve: - """Convert the given S-Expresstion into a SyCurve object - - Args: - - exp (list): Part of parsed S-Expression ``(curve ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not curve - - Returns: - - SyCurve: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'curve': - raise Exception("Expression does not have the correct type") - - object = cls() - for item in exp: - if item[0] == 'stroke': object.stroke = Stroke().from_sexpr(item) - if item[0] == 'fill': object.fill = SyFill().from_sexpr(item) - if item[0] == 'pts': - for point in item[1:]: - object.points.append(Position().from_sexpr(point)) - return object - - def to_sexpr(self, indent: int = 6, newline: bool = True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 6. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - expression = f'{indents}(curve\n' - expression = f'{indents} (pts\n' - for point in self.points: - expression = f'{indents} (xy {point.X} {point.Y})\n' - expression = f'{indents} )\n' - expression += f'{indents}{self.stroke.to_sexpr()}' - expression += f'{indents}{self.fill.to_sexpr()}' - expression += f'{indents}){endline}' - return expression - -@dataclass -class SyPolyLine(): - """The ``polyline`` token defines one or more graphical lines that may or may not define a polygon. - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-intro/index.html#_symbol_line - """ - - points: List[Position] = field(default_factory=list) - """The ``points`` token defines the four X/Y coordinates of each point of the polyline""" - - stroke: Stroke = field(default_factory=lambda: Stroke()) - """The ``stroke`` defines how the polyline outline is drawn""" - - fill: SyFill = field(default_factory=lambda: SyFill()) - """The ``fill`` token attributes define how polyline arc is filled""" - - @classmethod - def from_sexpr(cls, exp: list) -> SyPolyLine: - """Convert the given S-Expresstion into a SyPolyLine object - - Args: - - exp (list): Part of parsed S-Expression ``(polyline ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not polyline - - Returns: - - SyPolyLine: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'polyline': - raise Exception("Expression does not have the correct type") - - object = cls() - for item in exp: - if item[0] == 'stroke': object.stroke = Stroke().from_sexpr(item) - if item[0] == 'fill': object.fill = SyFill().from_sexpr(item) - if item[0] == 'pts': - for point in item[1:]: - object.points.append(Position().from_sexpr(point)) - return object - - def to_sexpr(self, indent: int = 6, newline: bool = True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 6. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - expression = f'{indents}(polyline\n' - expression += f'{indents} (pts\n' - for point in self.points: - expression += f'{indents} (xy {point.X} {point.Y})\n' - expression += f'{indents} )\n' - expression += f'{indents}{self.stroke.to_sexpr()}' - expression += f'{indents}{self.fill.to_sexpr()}' - expression += f'{indents}){endline}' - return expression - -@dataclass -class SyRect(): - """The ``rectangle`` token defines a graphical rectangle in a symbol definition. - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-intro/index.html#_symbol_rectangle - """ - - start: Position = field(default_factory=lambda: Position()) - """The ``start`` token attributes define the coordinates of the start point of the rectangle""" - - end: Position = field(default_factory=lambda: Position()) - """The ``end`` token attributes define the coordinates of the end point of the rectangle""" - - stroke: Stroke = field(default_factory=lambda: Stroke()) - """The ``stroke`` defines how the rectangle outline is drawn""" - - fill: SyFill = field(default_factory=lambda: SyFill()) - """The ``fill`` token attributes define how rectangle arc is filled""" - - @classmethod - def from_sexpr(cls, exp: list) -> SyRect: - """Convert the given S-Expresstion into a SyRect object - - Args: - - exp (list): Part of parsed S-Expression ``(rectangle ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not rectangle - - Returns: - - SyRect: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'rectangle': - raise Exception("Expression does not have the correct type") - - object = cls() - for item in exp: - if item[0] == 'start': object.start = Position().from_sexpr(item) - if item[0] == 'end': object.end = Position().from_sexpr(item) - if item[0] == 'stroke': object.stroke = Stroke().from_sexpr(item) - if item[0] == 'fill': object.fill = SyFill().from_sexpr(item) - return object - - def to_sexpr(self, indent: int = 6, newline: bool = True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 6. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - expression = f'{indents}(rectangle (start {self.start.X} {self.start.Y}) (end {self.end.X} {self.end.Y})\n' - expression += f'{indents}{self.stroke.to_sexpr()}' - expression += f'{indents}{self.fill.to_sexpr()}' - expression += f'{indents}){endline}' - return expression - -@dataclass -class SyText(): - """The ``text`` token defines a graphical text in a symbol definition. - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-intro/index.html#_symbol_text - """ - - text: str = "" - """The ``text`` attribute is a quoted string that defines the text""" - - position: Position = field(default_factory=lambda: Position()) - """The ``position`` defines the X and Y coordinates and rotation angle of the text""" - - effects: Effects = field(default_factory=lambda: Effects()) - """The ``effects`` token defines how the text is displayed""" - - @classmethod - def from_sexpr(cls, exp: list) -> SyText: - """Convert the given S-Expresstion into a SyText object - - Args: - - exp (list): Part of parsed S-Expression ``(text ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not text - - Returns: - - SyText: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'text': - raise Exception("Expression does not have the correct type") - - object = cls() - object.text = exp[1] - for item in exp[2:]: - if item[0] == 'at': object.position = Position().from_sexpr(item) - if item[0] == 'effects': object.effects = Effects().from_sexpr(item) - return object - - def to_sexpr(self, indent: int = 6, newline: bool = True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 6. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - posA = f' {self.position.angle}' if self.position.angle is not None else '' - - expression = f'{indents}(text "{dequote(self.text)}" (at {self.position.X} {self.position.Y}{posA})\n' - expression += f'{indents} {self.effects.to_sexpr()}' - expression += f'{indents}){endline}' - return expression \ No newline at end of file diff --git a/kintree/kicad/kiutils/items/zones.py b/kintree/kicad/kiutils/items/zones.py deleted file mode 100644 index eb2682e4..00000000 --- a/kintree/kicad/kiutils/items/zones.py +++ /dev/null @@ -1,657 +0,0 @@ -"""The zone token defines a zone on the board or footprint. Zones serve two purposes in - KiCad: filled copper zones and keep out areas. - -Author: - (C) Marvin Mager - @mvnmgrx - 2022 - -License identifier: - GPL-3.0 - -Major changes: - 11.02.2022 - created - -Documentation taken from: - https://dev-docs.kicad.org/en/file-formats/sexpr-intro/index.html#_footprint_graphics_items -""" - -from __future__ import annotations - -from dataclasses import dataclass, field -from typing import Optional, List - -from kiutils.items.common import Position -from kiutils.utils.strings import dequote - -@dataclass -class KeepoutSettings(): - """The ``keepout `` token attributes define which objects should be kept out of the - zone. This section only applies to keep out zones. - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-intro/index.html#_zone_keep_out_settings - """ - - tracks: str = "allowed" - """The ``tracks`` token attribute defines whether or not tracks should be excluded - from the keep out area. Valid attributes are ``allowed`` and ``not_allowed``.""" - - vias: str = "allowed" - """The ``vias`` token attribute defines whether or not vias should be excluded from - the keep out area. Valid attributes are ``allowed`` and ``not_allowed``.""" - - pads: str = "allowed" - """The ``pads`` token attribute defines whether or not pads should be excluded from - the keep out area. Valid attributes are ``allowed`` and ``not_allowed``.""" - - copperpour: str = "not-allowed" - """The ``copperpour`` token attribute defines whether or not copper pours should be - excluded from the keep out area. Valid attributes are ``allowed`` and ``not_allowed``.""" - - footprints: str = "not-allowed" - """The ``footprints`` token attribute defines whether or not footprints should be - excluded from the keep out area. Valid attributes are ``allowed`` and ``not_allowed``.""" - - @classmethod - def from_sexpr(cls, exp: list) -> KeepoutSettings: - """Convert the given S-Expresstion into a KeepoutSettings object - - Args: - - exp (list): Part of parsed S-Expression ``(keepout ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not keepout - - Returns: - - KeepoutSettings: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'keepout': - raise Exception("Expression does not have the correct type") - - object = cls() - for item in exp: - if type(item) != type([]): - continue - - if item[0] == 'tracks': object.tracks = item[1] - if item[0] == 'vias': object.vias = item[1] - if item[0] == 'pads': object.pads = item[1] - if item[0] == 'copperpour': object.copperpour = item[1] - if item[0] == 'footprints': object.footprints = item[1] - - return object - - def to_sexpr(self, indent: int = 0, newline: bool = False) -> str: - """Generate the S-Expression representing this object. When no coordinates are set - in the curve, the resulting S-Expression will be left empty. - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 0. - - newline (bool): Adds a newline to the end of the output. Defaults to False. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - # KiCad seems to add a whitespace to the pad token here - return f'{indents}(keepout (tracks {self.tracks}) (vias {self.vias}) (pads {self.pads} ) (copperpour {self.copperpour}) (footprints {self.footprints})){endline}' - -@dataclass -class FillSettings(): - """The ``fill`` token attributes define how the zone is to be filled. - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-intro/index.html#_zone_fill_settings - """ - - yes: bool = False - """The ``yes`` token specifies if the zone should be filled. If not specified, the zone is - not filled and no additional attributes are required.""" - - mode: Optional[str] = None - """The optional ``mode`` token attribute defines how the zone is filled. The only valid fill - mode is ``hatched``. When not defined, the fill mode is solid.""" - - thermalGap: Optional[float] = None - """The optional ``thermalGap`` token attribute defines the distance from the zone to all - pad thermal relief connections to the zone.""" - - thermalBridgeWidth: Optional[float] = None - """The optional ``thermalBridgeWidth`` token attribute defines the spoke width for all - pad thermal relief connection to the zone.""" - - smoothingStyle: Optional[str] = None - """The optional ``smoothingStyle`` token attributes define the style of corner smoothing. Valid - smoothing styles are ``chamfer`` and ``fillet``""" - - smoothingRadius: Optional[float] = None - """The optional ``smoothingRadius`` token attributes define the radius of corner smoothing""" - - islandRemovalMode: Optional[int] = None - """The optional ``islandRemovalMode`` token attribute defines the island removal mode. - Valid island removal modes are: - - 0: Always remove islands. - - 1: Never remove islands. - - 2: Minimum area island to allow. - """ - - islandAreaMin: Optional[float] = None - """The optional ``islandAreaMin`` token attribute defines the minimum allowable zone - island. This only valid when the remove islands mode is set to 2.""" - - hatchThickness: Optional[float] = None - """The optional ``hatchThickness`` token attribute defines the thickness for hatched fills""" - - hatchGap: Optional[float] = None - """The optional ``hatchGap`` token attribute defines the distance between lines for hatched - fills""" - - hatchOrientation: Optional[float] = None - """The optional ``hatchOrientation`` token attribute defines the line angle for hatched fills""" - - hatchSmoothingLevel: Optional[int] = None - """The optional ``hatchSmoothingLevel`` token attribute defines how hatch outlines are - smoothed. Valid hatch smoothing levels are: - - 0: No smoothing - - 1: Fillet - - 2: Arc minimum - - 3: Arc maximum - """ - - hatchSmoothingValue: Optional[float] = None - """The optional ``hatchSmoothingValue`` token attribute defines the ratio between the hole - and the chamfer/fillet size""" - - hatchBorderAlgorithm: Optional[int] = None - """The optional ``hatchBorderAlgorithm`` token attribute defines the if the zone line - thickness is used when performing a hatch fill. Valid values for the hatch border - algorithm are: - - 0: Use zone minimum thickness. - - 1: Use hatch thickness. - """ - - hatchMinHoleArea: Optional[float] = None - """The optional ``hatchMinHoleArea`` token attribute defines the minimum area a hatch file hole can be""" - - @classmethod - def from_sexpr(cls, exp: list) -> FillSettings: - """Convert the given S-Expresstion into a FillSettings object - - Args: - - exp (list): Part of parsed S-Expression ``(fill ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not fill - - Returns: - - FillSettings: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'fill': - raise Exception("Expression does not have the correct type") - - object = cls() - for item in exp: - if type(item) != type([]): - if item == 'yes': object.yes = True - else: continue - - if item[0] == 'mode': object.mode = item[1] - if item[0] == 'thermal_gap': object.thermalGap = item[1] - if item[0] == 'thermal_bridge_width': object.thermalBridgeWidth = item[1] - if item[0] == 'smoothing': object.smoothingStyle = item[1] - if item[0] == 'radius': object.smoothingRadius = item[1] - if item[0] == 'island_removal_mode': object.islandRemovalMode = item[1] - if item[0] == 'island_area_min': object.islandAreaMin = item[1] - if item[0] == 'hatch_thickness': object.hatchThickness = item[1] - if item[0] == 'hatch_gap': object.hatchGap = item[1] - if item[0] == 'hatch_orientation': object.hatchOrientation = item[1] - if item[0] == 'hatch_smoothing_level': object.hatchSmoothingLevel = item[1] - if item[0] == 'hatch_smoothing_value': object.hatchSmoothingValue = item[1] - if item[0] == 'hatch_border_algorithm': object.hatchBorderAlgorithm = item[1] - if item[0] == 'hatch_min_hole_area': object.hatchMinHoleArea = item[1] - - return object - - def to_sexpr(self, indent: int = 0, newline: bool = False) -> str: - """Generate the S-Expression representing this object. When no coordinates are set - in the curve, the resulting S-Expression will be left empty. - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 0. - - newline (bool): Adds a newline to the end of the output. Defaults to False. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - yes = ' yes' if self.yes else '' - mode = f' (mode {self.mode})' if self.mode is not None else '' - smoothing = f' (smoothing {self.smoothingStyle})' if self.smoothingStyle is not None else '' - radius = f' (radius {self.smoothingRadius})' if self.smoothingRadius is not None else '' - irm = f' (island_removal_mode {self.islandRemovalMode})' if self.islandRemovalMode is not None else '' - iam = f' (island_area_min {self.islandAreaMin})' if self.islandAreaMin is not None else '' - ht = f'\n{indents} (hatch_thickness {self.hatchThickness})' if self.hatchThickness is not None else '' - hg = f' (hatch_gap {self.hatchGap})' if self.hatchGap is not None else '' - ho = f' (hatch_orientation {self.hatchOrientation})' if self.hatchOrientation is not None else '' - hsl = f'\n{indents} (hatch_smoothing_level {self.hatchSmoothingLevel})' if self.hatchSmoothingLevel is not None else '' - hsv = f' (hatch_smoothing_value {self.hatchSmoothingValue})' if self.hatchSmoothingValue is not None else '' - hba = f'\n{indents} (hatch_border_algorithm {self.hatchBorderAlgorithm})' if self.hatchBorderAlgorithm is not None else '' - hmha = f' (hatch_min_hole_area {self.hatchMinHoleArea})' if self.hatchMinHoleArea is not None else '' - - return f'{indents}(fill{yes}{mode} (thermal_gap {self.thermalGap}) (thermal_bridge_width {self.thermalBridgeWidth}){smoothing}{radius}{irm}{iam}{ht}{hg}{ho}{hsl}{hsv}{hba}{hmha}){endline}' - -@dataclass -class ZonePolygon(): - """The ``polygon`` token defines a list of coordinates that define part of a zone""" - - coordinates: List[Position] = field(default_factory=list) - """The ``coordinates`` defines the list of polygon X/Y coordinates used to define the zone polygon""" - - @classmethod - def from_sexpr(cls, exp: list) -> ZonePolygon: - """Convert the given S-Expresstion into a ZonePolygon object - - Args: - - exp (list): Part of parsed S-Expression ``(polygon ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not polygon - - Returns: - - ZonePolygon: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'polygon': - raise Exception("Expression does not have the correct type") - - object = cls() - for item in exp: - if type(item) != type([]): - continue - if item[0] == 'pts': - for position in item[1:]: - object.coordinates.append(Position().from_sexpr(position)) - - return object - - def to_sexpr(self, indent: int = 4, newline: bool = True) -> str: - """Generate the S-Expression representing this object. When no coordinates are set - in the polygon, the resulting S-Expression will be left empty. - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 4. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object. If the polygon has no coordinates, an empty - expression is returned. - """ - indents = ' '*indent - endline = '\n' if newline else '' - if len(self.coordinates) == 0: - return f'{indents}{endline}' - - expression = f'{indents}(polygon\n' - expression += f'{indents} (pts\n' - for point in self.coordinates: - expression += f'{indents} (xy {point.X} {point.Y})\n' - expression += f'{indents} )\n' - expression += f'{indents})\n' - return expression - -@dataclass -class FilledPolygon(): - """The ``filled_polygon`` token defines the polygons used to fill a zone - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-intro/index.html#_zone_fill_polygons - """ - - layer: str = "F.Cu" - """The ``layer`` token attribute defines the canonical layer the zone fill resides on""" - - # TODO: What is the definiton of this token? - island: bool = False - """The ``island`` token's definition has to be defined ..""" - - coordinates: List[Position] = field(default_factory=list) - """The ``coordinates`` defines the list of polygon X/Y coordinates used to fill the zone""" - - @classmethod - def from_sexpr(cls, exp: list) -> FilledPolygon: - """Convert the given S-Expresstion into a FilledPolygon object - - Args: - - exp (list): Part of parsed S-Expression ``(filled_polygon ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not filled_polygon - - Returns: - - FilledPolygon: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'filled_polygon': - raise Exception("Expression does not have the correct type") - - object = cls() - for item in exp: - if type(item) != type([]): - continue - - if item[0] == 'layer': object.layer = item[1] - if item[0] == 'island': object.island = True - if item[0] == 'pts': - for position in item[1:]: - object.coordinates.append(Position().from_sexpr(position)) - - return object - - def to_sexpr(self, indent: int = 4, newline: bool = True) -> str: - """Generate the S-Expression representing this object. When no coordinates are set - in the filled polygon, the resulting S-Expression will be left empty. - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 4. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object. If the filled polygon has no coordinates, an empty - expression is returned. - """ - indents = ' '*indent - endline = '\n' if newline else '' - if len(self.coordinates) == 0: - return f'{indents}{endline}' - - expression = f'{indents}(filled_polygon\n' - expression += f'{indents} (layer "{dequote(self.layer)}")\n' - if self.island: - expression += f'{indents} (island)\n' - expression += f'{indents} (pts\n' - for point in self.coordinates: - expression += f'{indents} (xy {point.X} {point.Y})\n' - expression += f'{indents} )\n' - expression += f'{indents})\n' - return expression - -# TODO: This is KiCad 4 stuff, has to be tested yet .. -@dataclass -class FillSegments(): - """The ``fill_polygon`` token defines the segments used to fill the zone. This is only - used when loading boards prior to version 4 which filled zones with segments. - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-intro/index.html#_zone_fill_segments - """ - - layer: str = "F.Cu" - """The ``layer`` token attribute defines the canonical layer the zone fill resides on""" - - coordinates: List[Position] = field(default_factory=list) - """The ``coordinates`` defines the list of polygon X/Y coordinates used to fill the zone.""" - - @classmethod - def from_sexpr(cls, exp: list) -> FillSegments: - """Convert the given S-Expresstion into a FillSegments object - - Args: - - exp (list): Part of parsed S-Expression ``(fill_segments ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not fill_segments - - Returns: - - FillSegments: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'fill_segments': - raise Exception("Expression does not have the correct type") - - object = cls() - for item in exp: - if type(item) != type([]): - continue - - if item[0] == 'layer': object.layer = item[1] - if item[0] == 'pts': - for position in item[1:]: - object.coordinates.append(Position().from_sexpr(position)) - - return object - - def to_sexpr(self, indent: int = 4, newline: bool = True) -> str: - """Generate the S-Expression representing this object. When no coordinates are set - in the curve, the resulting S-Expression will be left empty. - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 4. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object. If the fill segments has no coordinates, an empty - expression is returned. - """ - indents = ' '*indent - endline = '\n' if newline else '' - if len(self.coordinates) == 0: - return f'{indents}{endline}' - - expression = f'{indents}(fill_segments\n' - expression += f'{indents} (layer "{dequote(self.layer)}")\n' - expression += f'{indents} (pts\n' - for point in self.coordinates: - expression += f'{indents} (xy {point.X} {point.Y})\n' - expression += f'{indents} )\n' - expression += f'{indents})\n' - return expression - -@dataclass -class Hatch(): - """Data wrapper for Zone class hatching attribute""" - - style: str = "none" - """The ``style`` token defines the style of the hatching. Valid hatch styles are ``none``, ``edge`` - and ``full``""" - - pitch: float = 0.0 - """The ``pitch`` token defines the pitch of the hatch""" - -@dataclass -class Zone(): - """The ``zone`` token defines a zone on the board or footprint. Zones serve two purposes - in KiCad: filled copper zones and keep out areas. - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-intro/index.html#_zone - - """ - locked: bool = False - """The ``locked`` token defines if the zone may be edited or not (Missing in KiCad - docu as of 11.02.2022)""" - - net: int = 0 - """The ``net`` token attribute defines by the net ordinal number which net in the nets - section that the zone is part of""" - - netName: str = "unknown" - """The ``net_name`` token attribute defines the name of the net if the zone is not a keep - out area. The net name attribute will be an empty string if the zone is a keep out area.""" - - layers: List[str] = field(default_factory=list) - """The ``layers`` token define the canonical layer set the zone connects as a list of - strings. When the zone only resides on one layer, the output of ``self.to_sexpr()`` will - change into ``(layer "xyz")`` instead of ``(layers ..)`` automatically.""" - - tstamp: Optional[str] = None # Used since KiCad 6 - """The ``tstamp`` token defines the unique identifier of the zone object""" - - name: Optional[str] = None - """The optional ``name`` token attribute defines the name of the zone if one has been assigned""" - - hatch: Hatch = field(default_factory=lambda: Hatch()) - """The ``hatch`` token attributes define the zone outline display hatch style and pitch""" - - priority: Optional[int] = None - """The optional ``priority`` attribute defines the zone priority if it is not zero""" - - connectPads: Optional[str] = None # This refers to CONNECTION_TYPE in the docu - """The ``connectPads`` token attributes define the pad connection type and clearance. Valid - pad connection types are ``thru_hole_only``, ``full`` and ``no``. If the pad connection type is not - defined, thermal relief pad connections are used""" - - clearance: float = 0.254 - """The ``clearance`` token defines the thermal relief for pad connections. The usage of this - token is depending on the value of ``connectPads``.""" - - minThickness: float = 0.254 - """The ``minThickness`` token attributed defines the minimum fill width allowed in the zone""" - - filledAreasThickness: Optional[str] = None - """The optional ``filledAreasThickness`` attribute no specifies if the zone like width is - not used when determining the zone fill area. This is to maintain compatibility with older - board files that included the line thickness when performing zone fills when it is not defined.""" - - keepoutSettings: Optional[KeepoutSettings] = None - """The optional ``keepoutSettings`` section defines the keep out items if the zone - defines as a keep out area""" - - fillSettings: Optional[FillSettings] = None - """The optional ``fillSettings`` section defines how the zone is to be filled""" - - polygons: List[ZonePolygon] = field(default_factory=list) - """The ``polygon`` token defines a list of zone polygons that define the shape of the zone""" - - filledPolygons: List[FilledPolygon] = field(default_factory=list) - """The ``filledPolygons`` token defines a list of filled polygons in the zone""" - - # TODO: This is KiCad 4 only stuff, needs to be tested yet .. - fillSegments: Optional[FillSegments] = None - """The optional ``fillSegments`` section defines a list of track segments used to fill - the zone""" - - @classmethod - def from_sexpr(cls, exp: list) -> Zone: - """Convert the given S-Expresstion into a Zone object - - Args: - - exp (list): Part of parsed S-Expression ``(zone ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not zone - - Returns: - - Zone: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'zone': - raise Exception("Expression does not have the correct type") - - object = cls() - for item in exp: - if type(item) != type([]): - if item == 'locked': object.locked = True - else: continue - - if item[0] == 'net': object.net = item[1] - if item[0] == 'net_name': object.netName = item[1] - if item[0] == 'layers' or item[0] == 'layer': - for layer in item[1:]: - object.layers.append(layer) - if item[0] == 'tstamp': object.tstamp = item[1] - if item[0] == 'name': object.name = item[1] - if item[0] == 'hatch': - object.hatch = Hatch(style=item[1], pitch=item[2]) - if item[0] == 'priority': object.priority = item[1] - if item[0] == 'connect_pads': - if len(item) == 2: - object.clearance = item[1][1] - else: - object.connectPads = item[1] - object.clearance = item[2][1] - if item[0] == 'min_thickness': object.minThickness = item[1] - if item[0] == 'filled_areas_thickness': object.filledAreasThickness = item[1] - if item[0] == 'keepout': object.keepoutSettings = KeepoutSettings().from_sexpr(item) - if item[0] == 'fill': object.fillSettings = FillSettings().from_sexpr(item) - if item[0] == 'polygon': object.polygons.append(ZonePolygon().from_sexpr(item)) - if item[0] == 'filled_polygon': object.filledPolygons.append(FilledPolygon().from_sexpr(item)) - if item[0] == 'fill_segments': object.fillSegments = FillSegments().from_sexpr(item) - - return object - - def to_sexpr(self, indent: int = 2, newline: bool = True) -> str: - """Generate the S-Expression representing this object. - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 2. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Raises: - - Exception: When the zone has no elements in its layer list - - Returns: - - str: S-Expression of this object. - """ - indents = ' '*indent - endline = '\n' if newline else '' - - locked = f' locked' if self.locked else '' - tstamp = f' (tstamp {self.tstamp})' if self.tstamp is not None else '' - name = f' (name "{dequote(self.name)}")' if self.name is not None else '' - contype = f' {self.connectPads}' if self.connectPads is not None else '' - fat = f' (filled_areas_thickness {self.filledAreasThickness})' if self.filledAreasThickness is not None else '' - layers, layer_token = '', '' - for layer in self.layers: - layers += f' "{dequote(layer)}"' - - if len(self.layers) == 1: - layer_token = f' (layer{layers})' - elif len(self.layers) > 1: - layer_token = f' (layers{layers})' - else: - raise Exception("Zone: No layers set for this zone") - - expression = f'{indents}(zone{locked} (net {self.net}) (net_name "{dequote(self.netName)}"){layer_token}{tstamp}{name} (hatch {self.hatch.style} {self.hatch.pitch})\n' - if self.priority is not None: - expression += f'{indents} (priority {self.priority})\n' - expression += f'{indents} (connect_pads{contype} (clearance {self.clearance}))\n' - expression += f'{indents} (min_thickness {self.minThickness}){fat}\n' - if self.keepoutSettings is not None: - expression += f'{indents} {self.keepoutSettings.to_sexpr()}\n' - if self.fillSettings is not None: - expression += self.fillSettings.to_sexpr(indent+2, True) - - for polygon in self.polygons: - expression += polygon.to_sexpr(indent+2) - - for polygon in self.filledPolygons: - expression += polygon.to_sexpr(indent+2) - - # TODO: This is KiKad 4 stuff... - if self.fillSegments is not None: - expression += self.fillSegments.to_sexpr() - expression += f'{indents}){endline}' - return expression diff --git a/kintree/kicad/kiutils/libraries.py b/kintree/kicad/kiutils/libraries.py deleted file mode 100644 index bad7201c..00000000 --- a/kintree/kicad/kiutils/libraries.py +++ /dev/null @@ -1,200 +0,0 @@ -"""Classes to manage KiCad footprint and symbol library tables - -Author: - (C) Marvin Mager - @mvnmgrx - 2022 - -License identifier: - GPL-3.0 - -Major changes: - 19.02.2022 - created -""" - -from __future__ import annotations - -from dataclasses import dataclass, field -from typing import Optional, List -from os import path - -from kiutils.utils.strings import dequote -from kiutils.utils import sexpr - -@dataclass -class Library(): - """The ``library`` token defines either a symbol library or a footprint library in - a library table file (``fp_lib_table`` or ``sym_lib_table``)""" - - name: str = "" - """The ``name`` token defines the name of the library as displayed in the project""" - - type: str = "KiCad" - """The ``type`` token defines the type of the library, usually ``KiCad``""" - - uri: str = "" - """The ``uri`` token defines the path to the library files""" - - options: str = "" - """The ``options`` token (..) TBD""" - - description: str = "" - """The ``description`` token (..) TBD""" - - @classmethod - def from_sexpr(cls, exp: list) -> Library: - """Convert the given S-Expresstion into a Library object - - Args: - - exp (list): Part of parsed S-Expression ``(lib ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not lib - - Returns: - - Library: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'lib': - raise Exception("Expression does not have the correct type") - - object = cls() - for item in exp: - if item[0] == 'name': object.name = item[1] - if item[0] == 'type': object.type = item[1] - if item[0] == 'uri': object.uri = item[1] - if item[0] == 'options': object.options = item[1] - if item[0] == 'descr': object.description = item[1] - return object - - def to_sexpr(self, indent=2, newline=True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 2. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - return f'{indents}(lib (name "{dequote(self.name)}")(type "{dequote(self.type)}")(uri "{dequote(self.uri)}")(options "{dequote(self.options)}")(descr "{dequote(self.description)}")){endline}' - -@dataclass -class LibTable(): - """The ``libtable`` token defines the ``fp_lib_table`` or ``sym_lib_table`` file of KiCad""" - - type: str = 'sym_lib_table' - """The ``type`` token defines the type of the library table. Valid values are ``fp_lib_table`` or - ``sym_lib_table``.""" - - libs: List[Library] = field(default_factory=list) - """The ``libs`` token holds a list of librarys that this library table object holds""" - - filePath: Optional[str] = None - """The ``filePath`` token defines the path-like string to the library file. Automatically set when - ``self.from_file()`` is used. Allows the use of ``self.to_file()`` without parameters.""" - - @classmethod - def from_sexpr(cls, exp: list) -> LibTable: - """Convert the given S-Expresstion into a LibTable object - - Args: - - exp (list): Part of parsed S-Expression ``(sym_lib_table ...)`` or ``(fp_lib_table ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not lib - - Returns: - - LibTable: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if not (exp[0] == 'fp_lib_table' or exp[0] == 'sym_lib_table'): - raise Exception("Expression does not have the correct type") - - object = cls() - object.type = exp[0] - for item in exp: - if item[0] == 'lib': object.libs.append(Library().from_sexpr(item)) - return object - - @classmethod - def from_file(cls, filepath: str, encoding: Optional[str] = None) -> LibTable: - """Load a library table directly from a KiCad library table file and sets the - ``self.filePath`` attribute to the given file path. - - 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 - dependent encoding). - - Raises: - - Exception: If the given path is not a file - - Returns: - - LibTable: Object of the LibTable class initialized with the given KiCad library table - """ - if not path.isfile(filepath): - raise Exception("Given path is not a file!") - - with open(filepath, 'r', encoding=encoding) as infile: - item = cls.from_sexpr(sexpr.parse_sexp(infile.read())) - item.filePath = filepath - return item - - @classmethod - def create_new(cls, type: str = 'sym_lib_table') -> LibTable: - """Creates a new empty library table with its attributes set as KiCad would create it - - Args: - - type (str): ``fp_lib_table`` or ``sym_lib_table``. Defaults to the latter. - - Returns: - - Library: Empty library table of given type - """ - return cls(type=type) - - 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, - the attribute ``self.filePath`` will be used instead. - - encoding (str, optional): Encoding of the output file. Defaults to None (platform - dependent encoding). - - Raises: - - Exception: If no file path is given via the argument or via `self.filePath` - """ - if filepath is None: - if self.filePath is None: - raise Exception("File path not set") - filepath = self.filePath - - with open(filepath, 'w', encoding=encoding) as outfile: - outfile.write(self.to_sexpr()) - - def to_sexpr(self, indent=0, newline=True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 0. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - expression = f'{indents}({self.type}\n' - for lib in self.libs: - expression += lib.to_sexpr() - expression += f'{indents}){endline}' - return expression \ No newline at end of file diff --git a/kintree/kicad/kiutils/misc/config.py b/kintree/kicad/kiutils/misc/config.py deleted file mode 100644 index eb244164..00000000 --- a/kintree/kicad/kiutils/misc/config.py +++ /dev/null @@ -1,17 +0,0 @@ -"""Configuration variables - -Author: - (C) Marvin Mager - @mvnmgrx - 2022 - -License identifier: - GPL-3.0 - -Major changes: - 19.09.2022 - created -""" - -KIUTILS_CREATE_NEW_VERSION_STR = '20211014' -"""Version string used in ``create_new()`` class functions""" - -KIUTILS_CREATE_NEW_GENERATOR_STR = 'kiutils' -"""Generator string used in ``create_new()`` class functions""" \ No newline at end of file diff --git a/kintree/kicad/kiutils/re_test.py b/kintree/kicad/kiutils/re_test.py deleted file mode 100644 index 29d75050..00000000 --- a/kintree/kicad/kiutils/re_test.py +++ /dev/null @@ -1,32 +0,0 @@ -import re - -test_s = ['Diodes:1F90-86', '34567_90', '1F90-86_1_1', 'Dio-des:GT45-789_0_0', 'Test_diode:TH4567:1', 'Test_diode:TH4567:1_0_0'] - -result = [] -for s in test_s: - find1 = re.match(r"^(.+?):(.+?)_(\d+?)_(\d+?)$", s) - if find1: - lib = find1.group(1) - symbol = find1.group(2) - unit_id = find1.group(3) - style_id = find1.group(4) - result.append(f'{s} [1]> {lib} | {symbol} | {unit_id} | {style_id}') - - find2 = re.match(r"^(.+?):(.+?)$", s) - if find2: - lib = find2.group(1) - symbol = find2.group(2) - result.append(f'{s} [2]> {lib} | {symbol}') - - find3 = re.match(r"^(.+?)_(\d+?)_(\d+?)$", s) - if find3: - symbol = find3.group(1) - unit_id = find3.group(2) - style_id = find3.group(3) - result.append(f'{s} [3]> {symbol} | {unit_id} | {style_id}') - - if not find1 and not find2 and not find3: - result.append(f'{s} >> {s}') - -for i in result: - print(i) \ No newline at end of file diff --git a/kintree/kicad/kiutils/schematic.py b/kintree/kicad/kiutils/schematic.py deleted file mode 100644 index 8d1797ce..00000000 --- a/kintree/kicad/kiutils/schematic.py +++ /dev/null @@ -1,308 +0,0 @@ -"""Class to manage KiCad schematics - -Author: - (C) Marvin Mager - @mvnmgrx - 2022 - -License identifier: - GPL-3.0 - -Major changes: - 19.02.2022 - created - -Documentation taken from: - https://dev-docs.kicad.org/en/file-formats/sexpr-schematic/ -""" - -from __future__ import annotations - -from dataclasses import dataclass, field -from typing import Optional, List -from os import path - -from kiutils.items.common import PageSettings, TitleBlock -from kiutils.items.schitems import * -from kiutils.symbol import Symbol -from kiutils.utils import sexpr -from kiutils.misc.config import KIUTILS_CREATE_NEW_GENERATOR_STR, KIUTILS_CREATE_NEW_VERSION_STR - -@dataclass -class Schematic(): - """The ``schematic`` token represents a KiCad schematic as defined by the schematic file format - - Documenatation: - https://dev-docs.kicad.org/en/file-formats/sexpr-schematic/ - """ - - version: str = "20211123" - """The ``version`` token attribute defines the schematic version using the YYYYMMDD date format""" - - generator: str = "kicad-python-tools" - """The ``generator`` token attribute defines the program used to write the file""" - - uuid: Optional[str] = None - """The ``uuid`` defines the universally unique identifier""" - - paper: PageSettings = field(default_factory=lambda: PageSettings()) - """The ``paper`` token defines the drawing page size and orientation""" - - titleBlock: Optional[TitleBlock] = None - """The ``titleBlock`` token defines author, date, revision, company and comments of the schematic""" - - libSymbols: List[Symbol] = field(default_factory=list) - """The ``libSymbols`` token defines a list of symbols that are used in the schematic""" - - schematicSymbols: List[SchematicSymbol] = field(default_factory=list) - """The ``schematicSymbols`` token defines a list of instances of symbols used in the schematic""" - - junctions: List[Junction] = field(default_factory=list) - """The ``junctions`` token defines a list of junctions used in the schematic""" - - noConnects: List[NoConnect] = field(default_factory=list) - """The ``noConnect`` token defines a list of no_connect markers used in the schematic""" - - busEntries: List[BusEntry] = field(default_factory=list) - """The ``busEntries`` token defines a list of bus_entry used in the schematic""" - - graphicalItems: List = field(default_factory=list) - """The ``graphicalItems`` token defines a list of ``bus``, ``wire`` or ``polyline`` elements used in - the schematic""" - - images: List[Image] = field(default_factory=list) - """The ``images`` token defines a list of images used in the schematic""" - - texts: List[Text] = field(default_factory=list) - """The ``text`` token defines a list of texts used in the schematic""" - - labels: List[LocalLabel] = field(default_factory=list) - """The ``labels`` token defines a list of local labels used in the schematic""" - - globalLabels: List[GlobalLabel] = field(default_factory=list) - """The ``globalLabels`` token defines a list of global labels used in the schematic""" - - hierarchicalLabels: List[HierarchicalLabel] = field(default_factory=list) - """The ``herarchicalLabels`` token defines a list of hierarchical labels used in the schematic""" - - sheets: List[HierarchicalSheet] = field(default_factory=list) - """The ``sheets`` token defines a list of hierarchical sheets used in the schematic""" - - sheetInstances: List[HierarchicalSheetInstance] = field(default_factory=list) - """The ``sheetInstances`` token defines a list of instances of hierarchical sheets used in - the schematic""" - - symbolInstances: List[SymbolInstance] = field(default_factory=list) - """The ``symbolInstances`` token defines a list of instances of symbols from ``libSymbols`` token - used in the schematic""" - - filePath: Optional[str] = None - """The ``filePath`` token defines the path-like string to the schematic file. Automatically set when - ``self.from_file()`` is used. Allows the use of ``self.to_file()`` without parameters.""" - - @classmethod - def from_sexpr(cls, exp: list) -> Schematic: - """Convert the given S-Expresstion into a Schematic object - - Args: - - exp (list): Part of parsed S-Expression ``(kicad_sch ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not kicad_sch - - Returns: - - Schematic: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'kicad_sch': - raise Exception("Expression does not have the correct type") - - object = cls() - for item in exp: - if item[0] == 'version': object.version = item[1] - if item[0] == 'generator': object.generator = item[1] - if item[0] == 'uuid': object.uuid = item[1] - if item[0] == 'paper': object.paper = PageSettings().from_sexpr(item) - if item[0] == 'title_block': object.titleBlock = TitleBlock().from_sexpr(item) - if item[0] == 'lib_symbols': - for symbol in item[1:]: - object.libSymbols.append(Symbol().from_sexpr(symbol)) - if item[0] == 'junction': object.junctions.append(Junction().from_sexpr(item)) - if item[0] == 'no_connect': object.noConnects.append(NoConnect().from_sexpr(item)) - if item[0] == 'bus_entry': object.busEntries.append(BusEntry().from_sexpr(item)) - if item[0] == 'wire': object.graphicalItems.append(Connection().from_sexpr(item)) - if item[0] == 'bus': object.graphicalItems.append(Connection().from_sexpr(item)) - if item[0] == 'polyline': object.graphicalItems.append(PolyLine().from_sexpr(item)) - if item[0] == 'image': object.images.append(Image().from_sexpr(item)) - if item[0] == 'text': object.texts.append(Text().from_sexpr(item)) - if item[0] == 'label': object.labels.append(LocalLabel().from_sexpr(item)) - if item[0] == 'global_label': object.globalLabels.append(GlobalLabel().from_sexpr(item)) - if item[0] == 'hierarchical_label': object.hierarchicalLabels.append(HierarchicalLabel().from_sexpr(item)) - if item[0] == 'symbol': object.schematicSymbols.append(SchematicSymbol().from_sexpr(item)) - if item[0] == 'sheet': object.sheets.append(HierarchicalSheet().from_sexpr(item)) - if item[0] == 'sheet_instances': - for instance in item[1:]: - object.sheetInstances.append(HierarchicalSheetInstance().from_sexpr(instance)) - if item[0] == 'symbol_instances': - for instance in item[1:]: - object.symbolInstances.append(SymbolInstance().from_sexpr(instance)) - return object - - @classmethod - def from_file(cls, filepath: str, encoding: Optional[str] = None) -> Schematic: - """Load a schematic directly from a KiCad schematic file (`.kicad_sch`) and sets the - ``self.filePath`` attribute to the given file path. - - 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 - dependent encoding). - - Raises: - - Exception: If the given path is not a file - - Returns: - - Schematic: Object of the Schematic class initialized with the given KiCad schematic - """ - if not path.isfile(filepath): - raise Exception("Given path is not a file!") - - with open(filepath, 'r', encoding=encoding) as infile: - item = cls.from_sexpr(sexpr.parse_sexp(infile.read())) - item.filePath = filepath - return item - - @classmethod - def create_new(cls) -> Schematic: - """Creates a new empty schematic page with its attributes set as KiCad would create it - - Returns: - - Schematic: Empty schematic - """ - schematic = cls( - version = KIUTILS_CREATE_NEW_VERSION_STR, - generator = KIUTILS_CREATE_NEW_GENERATOR_STR - ) - schematic.sheetInstances.append(HierarchicalSheetInstance(instancePath='/', page='1')) - return schematic - - 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, - the attribute ``self.filePath`` will be used instead. - - encoding (str, optional): Encoding of the output file. Defaults to None (platform - dependent encoding). - - Raises: - - Exception: If no file path is given via the argument or via `self.filePath` - """ - if filepath is None: - if self.filePath is None: - raise Exception("File path not set") - filepath = self.filePath - - with open(filepath, 'w', encoding=encoding) as outfile: - outfile.write(self.to_sexpr()) - - def to_sexpr(self, indent=0, newline=True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 0. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - expression = f'{indents}(kicad_sch (version {self.version}) (generator {self.generator})\n' - if self.uuid is not None: - expression += f'\n{indents} (uuid {self.uuid})\n\n' - expression += f'{self.paper.to_sexpr(indent+2)}' - if self.titleBlock is not None: - expression += f'\n{self.titleBlock.to_sexpr(indent+2)}' - - if self.libSymbols: - expression += f'\n{indents} (lib_symbols' - for item in self.libSymbols: - expression += '\n' - expression += item.to_sexpr(indent+4) - expression += f'{indents} )\n' - else: - expression += f'{indents} (lib_symbols)\n' - - if self.junctions: - expression += '\n' - for item in self.junctions: - expression += item.to_sexpr(indent+2) - - if self.noConnects: - expression += '\n' - for item in self.noConnects: - expression += item.to_sexpr(indent+2) - - if self.busEntries: - expression += '\n' - for item in self.busEntries: - expression += item.to_sexpr(indent+2) - - if self.graphicalItems: - expression += '\n' - for item in self.graphicalItems: - expression += item.to_sexpr(indent+2) - - if self.images: - expression += '\n' - for item in self.images: - expression += item.to_sexpr(indent+2) - - if self.texts: - expression += '\n' - for item in self.texts: - expression += item.to_sexpr(indent+2) - - if self.labels: - expression += '\n' - for item in self.labels: - expression += item.to_sexpr(indent+2) - - if self.globalLabels: - expression += '\n' - for item in self.globalLabels: - expression += item.to_sexpr(indent+2) - - if self.hierarchicalLabels: - expression += '\n' - for item in self.hierarchicalLabels: - expression += item.to_sexpr(indent+2) - - if self.schematicSymbols: - for item in self.schematicSymbols: - expression += '\n' - expression += item.to_sexpr(indent+2) - - if self.sheets: - for item in self.sheets: - expression += '\n' - expression += item.to_sexpr(indent+2) - - if self.sheetInstances: - expression += '\n' - expression += ' (sheet_instances\n' - for item in self.sheetInstances: - expression += item.to_sexpr(indent+4) - expression += ' )\n' - - if self.symbolInstances: - expression += '\n' - expression += ' (symbol_instances\n' - for item in self.symbolInstances: - expression += item.to_sexpr(indent+4) - expression += ' )\n' - - expression += f'{indents}){endline}' - return expression diff --git a/kintree/kicad/kiutils/symbol.py b/kintree/kicad/kiutils/symbol.py deleted file mode 100644 index c4a52543..00000000 --- a/kintree/kicad/kiutils/symbol.py +++ /dev/null @@ -1,531 +0,0 @@ -""" -Author: - (C) Marvin Mager - @mvnmgrx - 2022 - -License identifier: - GPL-3.0 - -Major changes: - 14.02.2022 - created - -Documentation taken from: - https://dev-docs.kicad.org/en/file-formats/sexpr-intro/index.html#_symbols -""" - -from __future__ import annotations - -from dataclasses import dataclass, field -from typing import Optional, List -from os import path -import re - -from kiutils.items.common import Effects, Position, Property, Font -from kiutils.items.syitems import * -from kiutils.utils import sexpr -from kiutils.utils.strings import dequote - -@dataclass -class SymbolAlternativePin(): - pinName: str = "" - """The ``pinName`` token defines the name of the alternative pin function""" - - electricalType: str = "input" - """The ``electricalType`` defines the pin electrical connection. See symbol documentation for - valid pin electrical connection types and descriptions.""" - - graphicalStyle: str = "line" - """The ``graphicalStyle`` defines the graphical style used to draw the pin. See symbol - documentation for valid pin graphical styles and descriptions.""" - - @classmethod - def from_sexpr(cls, exp: list) -> SymbolAlternativePin: - """Convert the given S-Expresstion into a SymbolAlternativePin object - - Args: - - exp (list): Part of parsed S-Expression ``(alternate ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not alternate - - Returns: - - SymbolAlternativePin: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'alternate': - raise Exception("Expression does not have the correct type") - - object = cls() - object.pinName = exp[1] - object.electricalType = exp[2] - object.graphicalStyle = exp[3] - return object - - def to_sexpr(self, indent: int = 8, newline: bool = True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 8. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - return f'{indents}(alternate "{dequote(self.pinName)}" {self.electricalType} {self.graphicalStyle}){endline}' - -@dataclass -class SymbolPin(): - """The ``pin`` token defines a pin in a symbol definition. - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-intro/index.html#_symbol_pin - """ - - electricalType: str = "input" - """The ``electricalType`` defines the pin electrical connection. See documentation below for - valid pin electrical connection types and descriptions.""" - - graphicalStyle: str = "line" - """The ``graphicalStyle`` defines the graphical style used to draw the pin. See documentation - below for valid pin graphical styles and descriptions.""" - - position: Position = field(default_factory=lambda: Position()) - """The ``position`` defines the X and Y coordinates and rotation angle of the connection point - of the pin relative to the symbol origin position""" - - length: float = 0.254 - """The ``length`` token attribute defines the LENGTH of the pin""" - - name: str = "" - """The ``name`` token defines a string containing the name of the pin""" - - nameEffects: Effects = field(default_factory=lambda: Effects()) - """The ``nameEffects`` token define how the pin's name is displayed""" - - number: str = "0" - """The ``number`` token defines a string containing the NUMBER of the pin""" - - numberEffects: Effects = field(default_factory=lambda: Effects()) - """The ``nameEffects`` token define how the pin's number is displayed""" - - hide: bool = False # Missing in documentation - """The 'hide' token defines if the pin should be hidden""" - - alternatePins: List[SymbolAlternativePin] = field(default_factory=list) - """The 'alternate' token defines one or more alternative definitions for the symbol pin""" - - @classmethod - def from_sexpr(cls, exp: list) -> SymbolPin: - """Convert the given S-Expresstion into a SymbolPin object - - Args: - - exp (list): Part of parsed S-Expression ``(pin ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not pin - - Returns: - - SymbolPin: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'pin': - raise Exception("Expression does not have the correct type") - - object = cls() - object.electricalType = exp[1] - object.graphicalStyle = exp[2] - for item in exp[3:]: - if type(item) != type([]): - if item == 'hide': object.hide = True - else: continue - if item[0] == 'at': object.position = Position().from_sexpr(item) - if item[0] == 'length': object.length = item[1] - if item[0] == 'name': - object.name = item[1] - object.nameEffects = Effects().from_sexpr(item[2]) - if item[0] == 'number': - object.number = item[1] - object.numberEffects = Effects().from_sexpr(item[2]) - if item[0] == 'alternate': object.alternatePins.append(SymbolAlternativePin().from_sexpr(item)) - return object - - def to_sexpr(self, indent: int = 4, newline: bool = True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 4. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - hide = ' hide' if self.hide else '' - posA = f' {self.position.angle}' if self.position.angle is not None else '' - - expression = f'{indents}(pin {self.electricalType} {self.graphicalStyle} (at {self.position.X} {self.position.Y}{posA}) (length {self.length}){hide}\n' - expression += f'{indents} (name "{dequote(self.name)}" {self.nameEffects.to_sexpr(newline=False)})\n' - expression += f'{indents} (number "{dequote(self.number)}" {self.numberEffects.to_sexpr(newline=False)})\n' - for alternativePin in self.alternatePins: - expression += alternativePin.to_sexpr(indent+2) - expression += f'{indents}){endline}' - return expression - -@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. - - Documentation: - 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.""" - @property - def id(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): - # 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: - 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)) - else: - parse_symbol_id = re.match(r"^(.+?):(.+?)$", symbol_id) - if parse_symbol_id: - self.libraryNickname = parse_symbol_id.group(1) - entryName_t = parse_symbol_id.group(2) - 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 - else: - self.entryName = symbol_id - - # Update units id to match parent id - for unit in self.units: - unit.entryName = self.entryName - - libraryNickname: Optional[str] = None - 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. """ - - extends: Optional[str] = None - """The optional ``extends`` token attribute defines the "LIBRARY_ID" of another symbol inside the - current library from which to derive a new symbol. Extended symbols currently can only have - different symbol properties than their parent symbol.""" - - hidePinNumbers: bool = False - """The ``pin_numbers`` token defines the visibility setting of the symbol pin numbers for - the entire symbol. If set to False, the all of the pin numbers in the symbol are visible.""" - - pinNames: bool = False - """The optional ``pinNames`` token defines the attributes for all of the pin names of the symbol. - If the ``pinNames`` token is not defined, all symbol pins are shown with the default offset.""" - - pinNamesHide: bool = False - """The optional ``pinNamesOffset`` token defines the pin name of all pins should be hidden""" - - pinNamesOffset: Optional[float] = None - """The optional ``pinNamesOffset`` token defines the pin name offset for all pin names of the - symbol. If not defined, the pin name offset is 0.508mm (0.020")""" - - inBom: Optional[bool] = None - """The optional ``inBom`` token, defines if a symbol is to be include in the bill of material - output. If undefined, the token will not be generated in `self.to_sexpr()`.""" - - onBoard: Optional[bool] = None - """The ``onBoard`` token, defines if a symbol is to be exported from the schematic to the printed - circuit board. If undefined, the token will not be generated in `self.to_sexpr()`.""" - - # TODO: Describe this token - isPower: bool = False # Missing in documentation, added when "Als Spannungssymbol" is checked - """The ``isPower`` token's documentation was not done yet ..""" - - properties: List[Property] = field(default_factory=list) - """The ``properties`` is a list of properties that define the symbol. The following properties are - mandatory when defining a parent symbol: "Reference", "Value", "Footprint", and "Datasheet". - All other properties are optional. Unit symbols cannot have any properties.""" - - graphicItems: List = field(default_factory=list) - """The ``graphicItems`` section is list of graphical arcs, circles, curves, lines, polygons, rectangles - and text that define the symbol drawing. This section can be empty if the symbol has no graphical - items.""" - - pins: List[SymbolPin] = field(default_factory=list) - """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) - """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 - - Args: - - exp (list): Part of parsed S-Expression ``(symbol ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not symbol - - Returns: - - Symbol: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'symbol': - raise Exception("Expression does not have the correct type") - - object = cls() - object.id = exp[1] - for item in exp[2:]: - if item[0] == 'extends': object.extends = item[1] - if item[0] == 'pin_numbers': - if item[1] == 'hide': - object.hidePinNumbers = True - if item[0] == 'pin_names': - object.pinNames = True - for property in item[1:]: - if type(property) == type([]): - if property[0] == 'offset': object.pinNamesOffset = property[1] - else: - if property == 'hide': object.pinNamesHide = True - if item[0] == 'in_bom': object.inBom = True if item[1] == 'yes' else False - if item[0] == 'on_board': object.onBoard = True if item[1] == 'yes' else False - if item[0] == 'power': object.isPower = True - - if item[0] == 'symbol': object.units.append(Symbol().from_sexpr(item)) - if item[0] == 'property': object.properties.append(Property().from_sexpr(item)) - - if item[0] == 'pin': object.pins.append(SymbolPin().from_sexpr(item)) - if item[0] == 'arc': object.graphicItems.append(SyArc().from_sexpr(item)) - if item[0] == 'circle': object.graphicItems.append(SyCircle().from_sexpr(item)) - if item[0] == 'curve': object.graphicItems.append(SyCurve().from_sexpr(item)) - if item[0] == 'polyline': object.graphicItems.append(SyPolyLine().from_sexpr(item)) - if item[0] == 'rectangle': object.graphicItems.append(SyRect().from_sexpr(item)) - if item[0] == 'text': object.graphicItems.append(SyText().from_sexpr(item)) - - return object - - @classmethod - 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 - - Args: - - id (str): ID token of the symbol - - reference (str): Reference designator - - value (str): Value of the ``value`` property - - footprint (str): Value of the ``footprint`` property. Defaults to "" (empty string). - - datasheet (str): Value of the ``datasheet`` property. Defaults to "" (empty string). - - Returns: - - Symbol: New symbol initialized with default values - """ - symbol = cls() - symbol.inBom = True - symbol.onBoard = True - symbol.id = id - symbol.properties.extend( - [ - Property(key = "Reference", value = reference, id = 0, - effects = Effects(font=Font(width=1.27, height=1.27))), - Property(key = "Value", value = value, id = 1, - effects = Effects(font=Font(width=1.27, height=1.27))), - Property(key = "Footprint", value = footprint, id = 2, - effects = Effects(font=Font(width=1.27, height=1.27), hide=True)), - Property(key = "Datasheet", value = datasheet, id = 3, - effects = Effects(font=Font(width=1.27, height=1.27), hide=True)) - ] - ) - return symbol - - def to_sexpr(self, indent: int = 2, newline: bool = True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 2. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - obtext, ibtext = '', '' - - if self.inBom is not None: - ibtext = 'yes' if self.inBom else 'no' - inbom = f' (in_bom {ibtext})' if self.inBom is not None else '' - if self.onBoard is not None: - obtext = 'yes' if self.onBoard else 'no' - onboard = f' (on_board {obtext})' if self.onBoard is not None else '' - power = f' (power)' if self.isPower else '' - pnhide = f' hide' if self.pinNamesHide else '' - pnoffset = f' (offset {self.pinNamesOffset})' if self.pinNamesOffset is not None else '' - pinnames = f' (pin_names{pnoffset}{pnhide})' if self.pinNames else '' - 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' - for item in self.properties: - expression += item.to_sexpr(indent+2) - for item in self.graphicItems: - expression += item.to_sexpr(indent+2) - for item in self.pins: - expression += item.to_sexpr(indent+2) - for item in self.units: - expression += item.to_sexpr(indent+2) - expression += f'{indents}){endline}' - return expression - -@dataclass -class SymbolLib(): - """A symbol library defines the common format of ``.kicad_sym`` files. A symbol library may contain - zero or more symbols. - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-symbol-lib/ - """ - version: Optional[str] = None - """The ``version`` token attribute defines the symbol library version using the YYYYMMDD date format""" - - generator: Optional[str] = None - """The ``generator`` token attribute defines the program used to write the file""" - - symbols: List[Symbol] = field(default_factory=list) - """The ``symbols`` token defines a list of zero or more symbols that are part of the symbol library""" - - filePath: Optional[str] = None - """The ``filePath`` token defines the path-like string to the library file. Automatically set when - ``self.from_file()`` is used. Allows the use of ``self.to_file()`` without parameters.""" - - @classmethod - def from_file(cls, filepath: str, encoding: Optional[str] = None) -> SymbolLib: - """Load a symbol library directly from a KiCad footprint file (`.kicad_sym`) and sets the - ``self.filePath`` attribute to the given file path. - - 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 - dependent encoding). - - Raises: - - Exception: If the given path is not a file - - Returns: - - SymbolLib: Object of the SymbolLib class initialized with the given KiCad symbol library - """ - if not path.isfile(filepath): - raise Exception("Given path is not a file!") - - with open(filepath, 'r', encoding=encoding) as infile: - item = cls.from_sexpr(sexpr.parse_sexp(infile.read())) - item.filePath = filepath - return item - - @classmethod - def from_sexpr(cls, exp: list) -> SymbolLib: - """Convert the given S-Expresstion into a SymbolLib object - - Args: - - exp (list): Part of parsed S-Expression ``(kicad_symbol_lib ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not kicad_symbol_lib - - Returns: - - SymbolLib: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'kicad_symbol_lib': - raise Exception("Expression does not have the correct type") - - object = cls() - - for item in exp[1:]: - if item[0] == 'version': object.version = item[1] - if item[0] == 'generator': object.generator = item[1] - if item[0] == 'symbol': object.symbols.append(Symbol().from_sexpr(item)) - return object - - 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, - the attribute ``self.filePath`` will be used instead. - - encoding (str, optional): Encoding of the output file. Defaults to None (platform - dependent encoding). - - Raises: - - Exception: If no file path is given via the argument or via `self.filePath` - """ - if filepath is None: - if self.filePath is None: - raise Exception("File path not set") - filepath = self.filePath - - with open(filepath, 'w', encoding=encoding) as outfile: - outfile.write(self.to_sexpr()) - - def to_sexpr(self, indent: int = 0, newline: bool = True) -> str: - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 0. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - expression = f'{indents}(kicad_symbol_lib (version {self.version}) (generator {self.generator})\n' - for item in self.symbols: - expression += f'{indents}{item.to_sexpr(indent+2)}' - expression += f'{indents}){endline}' - return expression diff --git a/kintree/kicad/kiutils/utils/sexpr.py b/kintree/kicad/kiutils/utils/sexpr.py deleted file mode 100644 index 7c8fa0ef..00000000 --- a/kintree/kicad/kiutils/utils/sexpr.py +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env python -# code extracted from: http://rosettacode.org/wiki/S-Expressions -# Originally taken from: https://gitlab.com/kicad/libraries/kicad-library-utils/-/blob/master/common/sexpr.py - -import re - -dbg = False - -term_regex = r'''(?mx) - \s*(?: - (?P\()| - (?P\))| - (?P[+-]?\d+\.\d+(?=[\ \)])|\-?\d+(?=[\ \)]))| - (?P"([^"]|(?<=\\)")*")| - (?P[^(^)\s]+) - )''' - -def parse_sexp(sexp): - stack = [] - out = [] - if dbg: print("%-6s %-14s %-44s %-s" % tuple("term value out stack".split())) - for termtypes in re.finditer(term_regex, sexp): - term, value = [(t,v) for t,v in termtypes.groupdict().items() if v][0] - if dbg: print("%-7s %-14s %-44r %-r" % (term, value, out, stack)) - if term == 'brackl': - stack.append(out) - out = [] - elif term == 'brackr': - assert stack, "Trouble with nesting of brackets" - tmpout, out = out, stack.pop(-1) - out.append(tmpout) - elif term == 'num': - v = float(value) - if v.is_integer(): v = int(v) - out.append(v) - elif term == 'sq': - out.append(value[1:-1].replace(r'\"', '"')) - elif term == 's': - out.append(value) - else: - raise NotImplementedError("Error: %r" % (term, value)) - assert not stack, "Trouble with nesting of brackets" - return out[0] \ No newline at end of file diff --git a/kintree/kicad/kiutils/utils/strings.py b/kintree/kicad/kiutils/utils/strings.py deleted file mode 100644 index e2e97a7d..00000000 --- a/kintree/kicad/kiutils/utils/strings.py +++ /dev/null @@ -1,36 +0,0 @@ -"""Functions for string manipulation - -Author: - (C) Marvin Mager - @mvnmgrx - 2022 - -License identifier: - GPL-3.0 - -Major changes: - 28.02.2022 - created -""" - -def dequote(input: str) -> str: - """Escapes double-quotes in a string using a backslash - - Args: - - input (str): String to replace double-quotes - - Returns: - - str: String with replaced double-quotes - """ - return str(input).replace("\"", "\\\"") - - -def remove_prefix(input: str, prefix: str) -> str: - """Removes the given prefix from a string (to remove incompatibility of ``str.removeprefix()`` - for Python versions < 3.9) - - Args: - - input (str): String to remove the prefix from - - prefix (str): The prefix - - Returns: - - str: String with removed prefix, or the ``input`` string as is, if the prefix was not found - """ - return input[len(prefix):] if input.startswith(prefix) else input \ No newline at end of file diff --git a/kintree/kicad/kiutils/wks.py b/kintree/kicad/kiutils/wks.py deleted file mode 100644 index d19ea616..00000000 --- a/kintree/kicad/kiutils/wks.py +++ /dev/null @@ -1,962 +0,0 @@ -"""Classes for worksheets (.kicad_wks) and its contents - -Author: - (C) Marvin Mager - @mvnmgrx - 2022 - -License identifier: - GPL-3.0 - -Major changes: - 24.06.2022 - created - -Documentation taken from: - https://dev-docs.kicad.org/en/file-formats/sexpr-worksheet/ -""" - -from __future__ import annotations - -from dataclasses import dataclass, field -from typing import Optional, List -from os import path - -from kiutils.items.common import Justify -from kiutils.utils.strings import dequote -from kiutils.utils import sexpr -from kiutils.misc.config import KIUTILS_CREATE_NEW_GENERATOR_STR, KIUTILS_CREATE_NEW_VERSION_STR - -@dataclass -class WksFontSize(): - """The ``WksFontSize`` token defines the size of a font in a worksheet""" - - width: float = 1.0 - """The ``width`` token defines the width of the font. Defaults to 1.""" - - height: float = 1.0 - """The ``height`` token defines the height of the font. Defaults to 1.""" - - @classmethod - def from_sexpr(cls, exp: list) -> WksFontSize: - """Convert the given S-Expresstion into a WksFontSize object - - Args: - - exp (list): Part of parsed S-Expression ``(size ...)`` - - Raises: - - Exception: When given parameter's type is not a list or its length is not equal to 3 - - Exception: When the first item of the list is not ``size`` - - Returns: - - WksFontSize: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list) or len(exp) != 3: - raise Exception("Expression does not have the correct type") - - if exp[0] != 'size': - raise Exception("Expression does not have the correct type") - - object = cls() - object.width = exp[1] - object.height = exp[2] - return object - - def to_sexpr(self, indent=0, newline=False): - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 0. - - newline (bool): Adds a newline to the end of the output. Defaults to False. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - return f'{indents}(size {self.width} {self.height}){endline}' - -@dataclass -class WksFont(): - """The ``WksFont`` token defines how a text is drawn""" - - linewidth: Optional[float] = None - """The optional ``linewidth`` token defines the width of the font's lines""" - - size: Optional[WksFontSize] = None - """The optional ``size`` token defines the size of the font""" - - bold: bool = False - """The ``bold`` token defines if the font is drawn bold. Defaults to False.""" - - italic: bool = False - """The ``italic`` token defines if the font is drawn italic. Defaults to False.""" - - @classmethod - def from_sexpr(cls, exp: list) -> WksFont: - """Convert the given S-Expresstion into a WksFont object - - Args: - - exp (list): Part of parsed S-Expression ``(font ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not ``font`` - - Returns: - - WksFont: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'font': - raise Exception("Expression does not have the correct type") - - object = cls() - for item in exp: - if type(item) != type([]): - if item == 'bold': object.bold = True - if item == 'italic': object.italic = True - continue - if item[0] == 'linewidth': object.linewidth = item[1] - if item[0] == 'size': object.size = WksFontSize().from_sexpr(item) - return object - - def to_sexpr(self, indent=0, newline=False): - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 0. - - newline (bool): Adds a newline to the end of the output. Defaults to False. - - Returns: - - str: S-Expression of this object. Will return an empty string, if all members of this - class are set to ``None``. - """ - indents = ' '*indent - endline = '\n' if newline else '' - - lw = f' (linewidth {self.linewidth})' if self.linewidth is not None else '' - size = f' {self.size.to_sexpr()}' if self.size is not None else '' - bold = f' bold' if self.bold else '' - italic = f' italic' if self.italic else '' - - if lw == '' and size == '' and bold == '' and italic == '': - return '' - else: - return f'{indents}(font{lw}{size}{bold}{italic}){endline}' - -@dataclass -class WksPosition(): - """The ``WksPosition`` token defines the positional coordinates and rotation of an worksheet - object. - """ - - X: float = 0.0 - """The ``X`` attribute defines the horizontal position of the object. Defaults to 0.""" - - Y: float = 0.0 - """The ``Y`` attribute defines the vertical position of the object. Defaults to 0.""" - - corner: Optional[str] = None - """The optional ``corner`` token is used to define the initial corner for repeating""" - - @classmethod - def from_sexpr(cls, exp: list) -> WksPosition: - """Convert the given S-Expresstion into a WksPosition object - - Args: - - exp (list): Part of parsed S-Expression ``(xxx ...)`` - - Raises: - - Exception: When the given expression is not of type ``list`` or the list is less than - 3 items long - - Returns: - - WksPosition: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list) or len(exp) < 3: - raise Exception("Expression does not have the correct type") - - object = cls() - object.X = exp[1] - object.Y = exp[2] - - # The last parameter refers to the corner token, if any is present - if len(exp) > 3: - object.corner = exp[3] - - return object - - def to_sexpr(self) -> str: - """This object does not have a direct S-Expression representation.""" - raise NotImplementedError("This object does not have a direct S-Expression representation") - -@dataclass -class Line(): - """The ``Line`` token defines how a line is drawn in a work sheet - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-worksheet/#_graphical_line""" - - name: str = "" - """The ``name`` token defines the name of the line object""" - - start: WksPosition = field(default_factory=lambda: WksPosition()) - """The ``start`` token defines the start position of the line""" - - end: WksPosition = field(default_factory=lambda: WksPosition()) - """The ``end`` token defines the end position of the line""" - - option: Optional[str] = None - """The optional ``option`` token defines on which pages the line shall be shown. Possible values - are: - - None: Item will be shown on all pages - - `notonpage1`: On all pages except page 1 - - `page1only`: Only visible on page 1""" - - lineWidth: Optional[float] = None - """The optional ``lineWidth`` token attribute defines the width of the rectangle lines""" - - repeat: Optional[int] = None - """The optional ``repeat`` token defines the count for repeated incremental lines""" - - incrx: Optional[float] = None - """The optional ``incrx`` token defines the repeat distance on the X axis""" - - incry: Optional[float] = None - """The optional ``incry`` token defines the repeat distance on the Y axis""" - - comment: Optional[str] = None - """The optional ``comment`` token is a comment for the line object""" - - @classmethod - def from_sexpr(cls, exp: list) -> Line: - """Convert the given S-Expresstion into a Line object - - Args: - - exp (list): Part of parsed S-Expression ``(line ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not ``tbtext`` - - Returns: - - Line: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'line': - raise Exception("Expression does not have the correct type") - - object = cls() - for item in exp[1:]: - if item[0] == 'name': object.name = item[1] - if item[0] == 'start': object.start = WksPosition().from_sexpr(item) - if item[0] == 'end': object.end = WksPosition().from_sexpr(item) - if item[0] == 'option': object.option = item[1] - if item[0] == 'linewidth': object.lineWidth = item[1] - if item[0] == 'repeat': object.repeat = item[1] - if item[0] == 'incrx': object.incrx = item[1] - if item[0] == 'incry': object.incry = item[1] - if item[0] == 'comment': object.comment = item[1] - return object - - def to_sexpr(self, indent=2, newline=True): - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 2. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - start_corner = f' {self.start.corner}' if self.start.corner is not None else '' - end_corner = f' {self.end.corner}' if self.end.corner is not None else '' - option = f' (option {self.option})' if self.option is not None else '' - repeat = f' (repeat {self.repeat})' if self.repeat is not None else '' - incrx = f' (incrx {self.incrx})' if self.incrx is not None else '' - incry = f' (incry {self.incry})' if self.incry is not None else '' - comment = f' (comment "{dequote(self.comment)}")\n' if self.comment is not None else '' - lw = f' (linewidth {self.lineWidth})' if self.lineWidth is not None else '' - - expression = f'{indents}(line (name "{dequote(self.name)}") ' - expression += f'(start {self.start.X} {self.start.Y}{start_corner}) ' - expression += f'(end {self.end.X} {self.end.Y}{end_corner})' - expression += f'{option}{lw}{repeat}{incrx}{incry}{comment}){endline}' - return expression - -@dataclass -class Rect(): - """The ``Rect`` token defines how a rectangle is drawn in a work sheet - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-worksheet/#_graphical_rectangle""" - - name: str = "" - """The ``name`` token defines the name of the rectangle object""" - - start: WksPosition = field(default_factory=lambda: WksPosition()) - """The ``start`` token defines the start position of the rectangle""" - - end: WksPosition = field(default_factory=lambda: WksPosition()) - """The ``end`` token defines the end position of the rectangle""" - - option: Optional[str] = None - """The optional ``option`` token defines on which pages the rectangle shall be shown. Possible values - are: - - None: Item will be shown on all pages - - `notonpage1`: On all pages except page 1 - - `page1only`: Only visible on page 1""" - - lineWidth: Optional[float] = None - """The optional ``lineWidth`` token attribute defines the width of the rectangle lines""" - - repeat: Optional[int] = None - """The optional ``repeat`` token defines the count for repeated incremental rectangles""" - - incrx: Optional[float] = None - """The optional ``incrx`` token defines the repeat distance on the X axis""" - - incry: Optional[float] = None - """The optional ``incry`` token defines the repeat distance on the Y axis""" - - comment: Optional[str] = None - """The optional ``comment`` token is a comment for the rectangle object""" - - @classmethod - def from_sexpr(cls, exp: list) -> Rect: - """Convert the given S-Expresstion into a Rect object - - Args: - - exp (list): Part of parsed S-Expression ``(rect ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not ``rect`` - - Returns: - - Rect: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'rect': - raise Exception("Expression does not have the correct type") - - object = cls() - for item in exp[1:]: - if item[0] == 'name': object.name = item[1] - if item[0] == 'start': object.start = WksPosition().from_sexpr(item) - if item[0] == 'end': object.end = WksPosition().from_sexpr(item) - if item[0] == 'option': object.option = item[1] - if item[0] == 'linewidth': object.lineWidth = item[1] - if item[0] == 'repeat': object.repeat = item[1] - if item[0] == 'incrx': object.incrx = item[1] - if item[0] == 'incry': object.incry = item[1] - if item[0] == 'comment': object.comment = item[1] - return object - - def to_sexpr(self, indent=2, newline=True): - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 2. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - start_corner = f' {self.start.corner}' if self.start.corner is not None else '' - end_corner = f' {self.end.corner}' if self.end.corner is not None else '' - option = f' (option {self.option})' if self.option is not None else '' - repeat = f' (repeat {self.repeat})' if self.repeat is not None else '' - incrx = f' (incrx {self.incrx})' if self.incrx is not None else '' - incry = f' (incry {self.incry})' if self.incry is not None else '' - comment = f' (comment "{dequote(self.comment)}")\n' if self.comment is not None else '' - lw = f' (linewidth {self.lineWidth})' if self.lineWidth is not None else '' - - expression = f'{indents}(rect (name "{dequote(self.name)}") ' - expression += f'(start {self.start.X} {self.start.Y}{start_corner}) ' - expression += f'(end {self.end.X} {self.end.Y}{end_corner})' - expression += f'{option}{lw}{repeat}{incrx}{incry}{comment}){endline}' - return expression - -@dataclass -class Polygon(): - """The ``Polygon`` token defines a graphical polygon in a worksheet - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-worksheet/#_graphical_polygon - """ - - name: str = "" - """The ``name`` token defines the name of the polygon""" - - position: WksPosition = field(default_factory=lambda: WksPosition()) - """The ``position`` token defines the coordinates of the polygon""" - - option: Optional[str] = None - """The optional ``option`` token defines on which pages the polygon shall be shown. Possible values - are: - - None: Item will be shown on all pages - - `notonpage1`: On all pages except page 1 - - `page1only`: Only visible on page 1""" - - rotate: Optional[float] = None - """The optional ``rotate`` token defines the rotation angle of the polygon object""" - - coordinates: List[WksPosition] = field(default_factory=list) - """The ``coordinates`` token defines a list of X/Y coordinates that forms the polygon""" - - repeat: Optional[int] = None - """The optional ``repeat`` token defines the count for repeated incremental polygons""" - - incrx: Optional[float] = None - """The optional ``incrx`` token defines the repeat distance on the X axis""" - - incry: Optional[float] = None - """The optional ``incry`` token defines the repeat distance on the Y axis""" - - comment: Optional[str] = None - """The optional ``comment`` token is a comment for the polygon object""" - - @classmethod - def from_sexpr(cls, exp: list) -> Polygon: - """Convert the given S-Expresstion into a Polygon object - - Args: - - exp (list): Part of parsed S-Expression ``(polygon ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not ``polygon`` - - Returns: - - Polygon: Object of the class initialized with the given S-Expression - """ - # TODO: Polygons seem to not be available in the WKS editor GUI. Are those still a feature? - raise NotImplementedError("Polygons are not yet handled! Please report this bug along with the file being parsed.") - - def to_sexpr(self, indent=0, newline=False): - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 0. - - newline (bool): Adds a newline to the end of the output. Defaults to False. - - Returns: - - str: S-Expression of this object - """ - raise NotImplementedError("Polygons are not yet handled! Please report this bug along with the file being parsed.") - -@dataclass -class Bitmap(): - """The ``Polygon`` token defines on or more embedded images - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-worksheet/#_image - """ - - name: str = "" - """The ``name`` token defines the name of the bitmap""" - - position: WksPosition = field(default_factory=lambda: WksPosition()) - """The ``position`` token defines the coordinates of the bitmap""" - - option: Optional[str] = None - """The optional ``option`` token defines on which pages the image shall be shown. Possible values - are: - - None: Item will be shown on all pages - - `notonpage1`: On all pages except page 1 - - `page1only`: Only visible on page 1""" - - scale: float = 1.0 - """The ``scale`` token defines the scale of the bitmap object""" - - repeat: Optional[int] = None - """The optional ``repeat`` token defines the count for repeated incremental bitmaps""" - - incrx: Optional[float] = None - """The optional ``incrx`` token defines the repeat distance on the X axis""" - - incry: Optional[float] = None - """The optional ``incry`` token defines the repeat distance on the Y axis""" - - # Comments seem to be buggy as of 25.06.2022 .. - comment: Optional[str] = None - """The optional ``comment`` token is a comment for the bitmap object""" - - # TODO: Parse this nonesense as a binary struct to make it more useful - pngdata: List[str] = field(default_factory=list) - """The ``pngdata`` token defines a list of strings representing up to 32 bytes per entry of - the image being saved. - - Format: - - "xx xx xx xx xx (..) xx " - - The list must be 32byte aligned, leaving a space after the last byte as shown in the format - example. - """ - - @classmethod - def from_sexpr(cls, exp: list) -> Bitmap: - """Convert the given S-Expresstion into a Bitmap object - - Args: - - exp (list): Part of parsed S-Expression ``(bitmap ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not ``bitmap`` - - Returns: - - Bitmap: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'bitmap': - raise Exception("Expression does not have the correct type") - - object = cls() - for item in exp[1:]: - if item[0] == 'name': object.name = item[1] - if item[0] == 'pos': object.position = WksPosition().from_sexpr(item) - if item[0] == 'option': object.option = item[1] - if item[0] == 'scale': object.scale = item[1] - if item[0] == 'repeat': object.repeat = item[1] - if item[0] == 'incrx': object.incrx = item[1] - if item[0] == 'incry': object.incry = item[1] - if item[0] == 'comment': object.comment = item[1] - if item[0] == 'pngdata': - if len(item) < 2: continue - for data in item[1:]: - if data[0] != 'data': continue - object.pngdata.append(data[1]) - return object - - def to_sexpr(self, indent=2, newline=True): - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 2. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - repeat = f' (repeat {self.repeat})' if self.repeat is not None else '' - incrx = f' (incrx {self.incrx})' if self.incrx is not None else '' - incry = f' (incry {self.incry})' if self.incry is not None else '' - option = f' (option {self.option})' if self.option is not None else '' - corner = f' {self.position.corner}' if self.position.corner is not None else '' - - expression = f'{indents}(bitmap (name "{dequote(self.name)}") ' - expression += f'(pos {self.position.X} {self.position.Y}{corner}){option} (scale {self.scale})' - expression += f'{repeat}{incrx}{incry}\n' - if self.comment is not None: - # Here KiCad decides to only use 1 space for some unknown reason .. - expression += f' (comment "{dequote(self.comment)}")\n' - expression += f'{indents}(pngdata\n' - for data in self.pngdata: - expression += f'{indents} (data "{data}")\n' - expression += f'{indents} )\n' - expression += f'{indents}){endline}' - return expression - - -@dataclass -class TbText(): - """The ``TbText`` token define text used in the title block of a work sheet - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-worksheet/#_title_block_text""" - - text: str = "" - """The ``text`` token defines the text itself""" - - name: str = "" - """The ``name`` token defines the name of the text object""" - - position: WksPosition = field(default_factory=lambda: WksPosition()) - """The ``position`` token defines the position of the text""" - - option: Optional[str] = None - """The optional ``option`` token defines on which pages the text shall be shown. Possible values - are: - - None: Item will be shown on all pages - - `notonpage1`: On all pages except page 1 - - `page1only`: Only visible on page 1""" - - rotate: Optional[float] = None - """The optional ``rotate`` token defines the rotation of the text in degrees""" - - font: WksFont = field(default_factory=lambda: WksFont()) - """The ``font`` token define how the text is drawn""" - - justify: Optional[Justify] = None - """The optional ``justify`` token defines the justification of the text""" - - maxlen: Optional[float] = None - """The optional ``maxlen`` token defines the maximum length of the text""" - - maxheight: Optional[float] = None - """The optional ``maxheight`` token defines the maximum height of the text""" - - repeat: Optional[int] = None - """The optional ``repeat`` token defines the count for repeated incremental text""" - - incrx: Optional[float] = None - """The optional ``incrx`` token defines the repeat distance on the X axis""" - - incry: Optional[float] = None - """The optional ``incry`` token defines the repeat distance on the Y axis""" - - incrlabel: Optional[int] = None - """The optional ``incrlabel`` token defines the amount of characters that are moved with every - repeated incremental text""" - - comment: Optional[str] = None - """The optional ``comment`` token is a comment for the text object""" - - @classmethod - def from_sexpr(cls, exp: list) -> TbText: - """Convert the given S-Expresstion into a TbText object - - Args: - - exp (list): Part of parsed S-Expression ``(tbtext ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not ``tbtext`` - - Returns: - - TbText: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'tbtext': - raise Exception("Expression does not have the correct type") - - object = cls() - object.text = exp[1] - for item in exp[2:]: - if item[0] == 'name': object.name = item[1] - if item[0] == 'pos': object.position = WksPosition().from_sexpr(item) - if item[0] == 'option': object.option = item[1] - if item[0] == 'rotate': object.rotate = item[1] - if item[0] == 'font': object.font = WksFont().from_sexpr(item) - if item[0] == 'justify': object.justify = Justify().from_sexpr(item) - if item[0] == 'maxlen': object.maxlen = item[1] - if item[0] == 'maxheight': object.maxheight = item[1] - if item[0] == 'repeat': object.repeat = item[1] - if item[0] == 'incrx': object.incrx = item[1] - if item[0] == 'incry': object.incry = item[1] - if item[0] == 'incrlabel': object.incrlabel = item[1] - if item[0] == 'comment': object.comment = item[1] - return object - - def to_sexpr(self, indent=2, newline=True): - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 2. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - corner = f' {self.position.corner}' if self.position.corner is not None else '' - repeat = f' (repeat {self.repeat})' if self.repeat is not None else '' - incrx = f' (incrx {self.incrx})' if self.incrx is not None else '' - incry = f' (incry {self.incry})' if self.incry is not None else '' - option = f' (option {self.option})' if self.option is not None else '' - rotate = f' (rotate {self.rotate})' if self.rotate is not None else '' - justify = f' {self.justify.to_sexpr()}' if self.justify is not None else '' - maxlen = f' (maxlen {self.maxlen})' if self.maxlen is not None else '' - maxheight = f' (maxheight {self.maxheight})' if self.maxheight is not None else '' - incrlabel = f' (incrlabel {self.incrlabel})' if self.incrlabel is not None else '' - font = f' {self.font.to_sexpr()}' if self.font.to_sexpr() != '' else '' - - expression = f'{indents}(tbtext "{dequote(self.text)}" (name "{dequote(self.name)}") ' - expression += f'(pos {self.position.X} {self.position.Y}{corner}){option}{rotate}' - expression += f'{font}{justify}{maxlen}{maxheight}{repeat}{incrx}{incry}{incrlabel}' - if self.comment is not None: - expression += f' (comment "{dequote(self.comment)}")\n' - expression += f'){endline}' - return expression - - -@dataclass -class TextSize(): - """The ``TextSize`` define the default width and height of text""" - - width: float = 1.5 - """The ``width`` token defines the default width of a text element. Defaults to 1,5.""" - - height: float = 1.5 - """The ``height`` token defines the default height of a text element. Defaults to 1,5.""" - - @classmethod - def from_sexpr(cls, exp: list) -> TextSize: - """Convert the given S-Expresstion into a TextSize object - - Args: - - exp (list): Part of parsed S-Expression ``(textsize ...)`` - - Raises: - - Exception: When given parameter's type is not a list or when its not exactly 3 long - - Exception: When the first item of the list is not ``textsize`` - - Returns: - - TextSize: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list) or len(exp) != 3: - raise Exception("Expression does not have the correct type") - - if exp[0] != 'textsize': - raise Exception("Expression does not have the correct type") - - object = cls() - object.width = exp[1] - object.height = exp[2] - return object - - def to_sexpr(self, indent=0, newline=False): - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 0. - - newline (bool): Adds a newline to the end of the output. Defaults to False. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - return f'{indents}(textsize {self.width} {self.height}){endline}' - -@dataclass -class Setup(): - """The ``setup`` token defines the configuration information for the work sheet - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-worksheet/#_set_up_section""" - - textSize: TextSize = field(default_factory=lambda: TextSize()) - """The ``textSize`` token defines the default width and height of text""" - - lineWidth: float = 0.15 - """The ``lineWidth`` token attribute defines the default width of lines. Defaults to 0,15.""" - - textLineWidth: float = 0.15 - """The ``textLineWidth`` token attribute define the default width of the lines used to draw - text. Defaults to 0,15.""" - - leftMargin: float = 10.0 - """The ``leftMargin`` token defines the distance from the left edge of the page""" - - rightMargin: float = 10.0 - """The ``rightMargin`` token defines the distance from the right edge of the page""" - - topMargin: float = 10.0 - """The ``topMargin`` token defines the distance from the top edge of the page""" - - bottomMargin: float = 10.0 - """The ``bottomMargin`` token defines the distance from the bottom edge of the page""" - - @classmethod - def from_sexpr(cls, exp: list) -> Setup: - """Convert the given S-Expresstion into a Setup object - - Args: - - exp (list): Part of parsed S-Expression ``(setup ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not ``setup`` - - Returns: - - Setup: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'setup': - raise Exception("Expression does not have the correct type") - - object = cls() - for item in exp[1:]: - if item[0] == 'textsize': object.textSize = TextSize().from_sexpr(item) - if item[0] == 'linewidth': object.lineWidth = item[1] - if item[0] == 'textlinewidth': object.textLineWidth = item[1] - if item[0] == 'left_margin': object.leftMargin = item[1] - if item[0] == 'right_margin': object.rightMargin = item[1] - if item[0] == 'top_margin': object.topMargin = item[1] - if item[0] == 'bottom_margin': object.bottomMargin = item[1] - return object - - def to_sexpr(self, indent=2, newline=True): - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 2. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - # KiCad puts no spaces between tokens here - expression = f'{indents}(setup {self.textSize.to_sexpr()}(linewidth {self.lineWidth})' - expression += f'(textlinewidth {self.textLineWidth})\n{indents}' - expression += f'(left_margin {self.leftMargin})(right_margin {self.rightMargin})' - expression += f'(top_margin {self.topMargin})(bottom_margin {self.bottomMargin})' - expression += f'){endline}' - - return expression - -@dataclass -class WorkSheet(): - """The ``WorkSheet`` token defines a KiCad worksheet (.kicad_wks file) - - Documentation: - https://dev-docs.kicad.org/en/file-formats/sexpr-worksheet/#_header_section""" - - version: str = KIUTILS_CREATE_NEW_VERSION_STR - """The ``version`` token defines the work sheet version using the YYYYMMDD date format""" - - generator: str = KIUTILS_CREATE_NEW_GENERATOR_STR - """The ``generator`` token defines the program used to write the file""" - - setup: Setup = field(default_factory=lambda: Setup()) - """The ``setup`` token defines the configuration information for the work sheet""" - - drawingObjects: List = field(default_factory=list) - """The ``drawingObjects`` token can contain zero or more texts, lines, rectangles, polys or images""" - - filePath: Optional[str] = None - """The ``filePath`` token defines the path-like string to the board file. Automatically set when - ``self.from_file()`` is used. Allows the use of ``self.to_file()`` without parameters.""" - - @classmethod - def from_sexpr(cls, exp: list) -> WorkSheet: - """Convert the given S-Expresstion into a WorkSheet object - - Args: - - exp (list): Part of parsed S-Expression ``(kicad_wks ...)`` - - Raises: - - Exception: When given parameter's type is not a list - - Exception: When the first item of the list is not ``kicad_wks`` - - Returns: - - WorkSheet: Object of the class initialized with the given S-Expression - """ - if not isinstance(exp, list): - raise Exception("Expression does not have the correct type") - - if exp[0] != 'kicad_wks': - raise Exception("Expression does not have the correct type") - - object = cls() - for item in exp[1:]: - if item[0] == 'version': object.version = item[1] - if item[0] == 'generator': object.generator = item[1] - if item[0] == 'setup': object.setup = Setup().from_sexpr(item) - if item[0] == 'rect': object.drawingObjects.append(Rect().from_sexpr(item)) - if item[0] == 'line': object.drawingObjects.append(Line().from_sexpr(item)) - if item[0] == 'polygon': object.drawingObjects.append(Polygon().from_sexpr(item)) - if item[0] == 'tbtext': object.drawingObjects.append(TbText().from_sexpr(item)) - if item[0] == 'bitmap': object.drawingObjects.append(Bitmap().from_sexpr(item)) - return object - - @classmethod - def from_file(cls, filepath: str, encoding: Optional[str] = None) -> WorkSheet: - """Load a worksheet directly from a KiCad worksheet file (`.kicad_wks`) and sets the - ``self.filePath`` attribute to the given file path. - - 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 - dependent encoding). - - Raises: - - Exception: If the given path is not a file - - Returns: - - WorkSheet: Object of the WorkSheet class initialized with the given KiCad worksheet - """ - if not path.isfile(filepath): - raise Exception("Given path is not a file!") - - with open(filepath, 'r', encoding=encoding) as infile: - item = cls.from_sexpr(sexpr.parse_sexp(infile.read())) - item.filePath = filepath - return item - - @classmethod - def create_new(cls) -> WorkSheet: - """Creates a new empty worksheet as KiCad would create it - - Returns: - WorkSheet: A empty worksheet - """ - return cls( - version = KIUTILS_CREATE_NEW_VERSION_STR, - generator = KIUTILS_CREATE_NEW_GENERATOR_STR - ) - - def to_file(self, filepath = 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, - the attribute ``self.filePath`` will be used instead. - - Raises: - - Exception: If no file path is given via the argument or via `self.filePath` - """ - if filepath is None: - if self.filePath is None: - raise Exception("File path not set") - filepath = self.filePath - - with open(filepath, 'w') as outfile: - outfile.write(self.to_sexpr()) - - def to_sexpr(self, indent=0, newline=True): - """Generate the S-Expression representing this object - - Args: - - indent (int): Number of whitespaces used to indent the output. Defaults to 0. - - newline (bool): Adds a newline to the end of the output. Defaults to True. - - Returns: - - str: S-Expression of this object - """ - indents = ' '*indent - endline = '\n' if newline else '' - - expression = f'{indents}(kicad_wks (version {self.version}) (generator {self.generator})\n' - expression += self.setup.to_sexpr(indent+2) - for item in self.drawingObjects: - expression += item.to_sexpr(indent+2) - expression += f'{indents}){endline}' - - return expression \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index fafa9214..94d4970e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,3 +1,20 @@ +[[package]] +name = "anyio" +version = "3.6.2" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +category = "main" +optional = false +python-versions = ">=3.6.2" + +[package.dependencies] +idna = ">=2.8" +sniffio = ">=1.1" + +[package.extras] +doc = ["packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] +test = ["contextlib2", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (<0.15)", "uvloop (>=0.15)"] +trio = ["trio (>=0.16,<0.22)"] + [[package]] name = "certauth" version = "1.3.0" @@ -158,6 +175,34 @@ python-versions = ">=3.7" docs = ["furo (>=2022.12.7)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"] testing = ["covdefaults (>=2.2.2)", "coverage (>=7.0.1)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-timeout (>=2.1)"] +[[package]] +name = "flet" +version = "0.4.2" +description = "Flet for Python - easily build interactive multi-platform apps in Python" +category = "main" +optional = false +python-versions = ">=3.7,<4.0" + +[package.dependencies] +flet-core = "0.4.2" +httpx = ">=0.23.3,<0.24.0" +oauthlib = ">=3.2.2,<4.0.0" +packaging = ">=23.0,<24.0" +watchdog = ">=2.2.1,<3.0.0" +websocket-client = ">=1.4.2,<2.0.0" +websockets = ">=10.4,<11.0" + +[[package]] +name = "flet-core" +version = "0.4.2" +description = "Flet core library" +category = "main" +optional = false +python-versions = ">=3.7,<4.0" + +[package.dependencies] +repath = ">=0.9.0,<0.10.0" + [[package]] name = "fuzzywuzzy" version = "0.18.0" @@ -169,6 +214,52 @@ python-versions = "*" [package.extras] speedup = ["python-levenshtein (>=0.12)"] +[[package]] +name = "h11" +version = "0.14.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "httpcore" +version = "0.16.3" +description = "A minimal low-level HTTP client." +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +anyio = ">=3.0,<5.0" +certifi = "*" +h11 = ">=0.13,<0.15" +sniffio = ">=1.0.0,<2.0.0" + +[package.extras] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (>=1.0.0,<2.0.0)"] + +[[package]] +name = "httpx" +version = "0.23.3" +description = "The next generation HTTP client." +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +certifi = "*" +httpcore = ">=0.15.0,<0.17.0" +rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]} +sniffio = "*" + +[package.extras] +brotli = ["brotli", "brotlicffi"] +cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<13)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (>=1.0.0,<2.0.0)"] + [[package]] name = "idna" version = "3.4" @@ -204,6 +295,14 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "kiutils" +version = "1.3.0" +description = "Simple and SCM-friendly KiCad file parser for KiCad 6.0 and up" +category = "main" +optional = false +python-versions = ">=3.7" + [[package]] name = "mouser" version = "0.1.2" @@ -227,6 +326,27 @@ python-versions = ">=3.7" [package.dependencies] dill = ">=0.3.6" +[[package]] +name = "oauthlib" +version = "3.2.2" +description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +rsa = ["cryptography (>=3.0.0)"] +signals = ["blinker (>=1.4.0)"] +signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"] + +[[package]] +name = "packaging" +version = "23.0" +description = "Core utilities for Python packages" +category = "main" +optional = false +python-versions = ">=3.7" + [[package]] name = "pycparser" version = "2.21" @@ -250,14 +370,6 @@ cryptography = ">=38.0.0,<40" docs = ["sphinx (!=5.2.0,!=5.2.0.post0)", "sphinx-rtd-theme"] test = ["flaky", "pretend", "pytest (>=3.0.1)"] -[[package]] -name = "pysimplegui" -version = "4.60.4" -description = "Python GUIs for Humans. Launched in 2018. It's 2022 & PySimpleGUI is an ACTIVE & supported project. Super-simple to create custom GUI's. 325+ Demo programs & Cookbook for rapid start. Extensive documentation. Main docs at www.PySimpleGUI.org. Fun & your success are the focus. Examples using Machine Learning (GUI, OpenCV Integration), Rainmeter Style Desktop Widgets, Matplotlib + Pyplot, PIL support, add GUI to command line scripts, PDF & Image Viewers. Great for beginners & advanced GUI programmers." -category = "main" -optional = false -python-versions = "*" - [[package]] name = "python-levenshtein" version = "0.12.2" @@ -277,6 +389,17 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +[[package]] +name = "repath" +version = "0.9.0" +description = "Generate regular expressions form ExpressJS path patterns" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +six = ">=1.9.0" + [[package]] name = "requests" version = "2.28.2" @@ -318,9 +441,23 @@ python-versions = "*" [package.dependencies] six = ">=1.7.0" +[[package]] +name = "rfc3986" +version = "1.5.0" +description = "Validating URI References per RFC 3986" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +idna = {version = "*", optional = true, markers = "extra == \"idna2008\""} + +[package.extras] +idna2008 = ["idna"] + [[package]] name = "setuptools" -version = "67.3.2" +version = "67.4.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" category = "main" optional = false @@ -339,6 +476,14 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +[[package]] +name = "sniffio" +version = "1.3.0" +description = "Sniff out which async library your code is running under" +category = "main" +optional = false +python-versions = ">=3.7" + [[package]] name = "tldextract" version = "3.4.0" @@ -380,6 +525,38 @@ decorator = ">=3.4.0" [package.extras] test = ["flake8 (>=2.4.0)", "isort (>=4.2.2)", "pytest (>=2.2.3)"] +[[package]] +name = "watchdog" +version = "2.2.1" +description = "Filesystem events monitoring" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +watchmedo = ["PyYAML (>=3.10)"] + +[[package]] +name = "websocket-client" +version = "1.5.1" +description = "WebSocket client for Python with low level API options" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["Sphinx (>=3.4)", "sphinx-rtd-theme (>=0.5)"] +optional = ["python-socks", "wsaccel"] +test = ["websockets"] + +[[package]] +name = "websockets" +version = "10.4" +description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" +category = "main" +optional = false +python-versions = ">=3.7" + [[package]] name = "wrapt" version = "1.14.1" @@ -391,9 +568,13 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [metadata] lock-version = "1.1" python-versions = ">=3.8,<3.11" -content-hash = "b326703afa515a51bfc26ff8d57c973957d0965e4221fa6a7208c0824322e42b" +content-hash = "3c9bb6da6eca57dd56f2362ddf0738903cd0ae83c895ed82468fa25168b230ee" [metadata.files] +anyio = [ + {file = "anyio-3.6.2-py3-none-any.whl", hash = "sha256:fbbe32bd270d2a2ef3ed1c5d45041250284e31fc0a4df4a5a6071842051a51e3"}, + {file = "anyio-3.6.2.tar.gz", hash = "sha256:25ea0d673ae30af41a0c442f81cf3b38c7e79fdc7b60335a4c14e05eb0947421"}, +] certauth = [ {file = "certauth-1.3.0-py2.py3-none-any.whl", hash = "sha256:f84b8c7075d0e445614d5ec4662056511453f19228cf4fcf8278cccae17b316b"}, {file = "certauth-1.3.0.tar.gz", hash = "sha256:7862d5deff0b33d2fb28d36861ba63d91c82d700bfdfc4bd848a8711ca72b8fb"}, @@ -666,10 +847,38 @@ filelock = [ {file = "filelock-3.9.0-py3-none-any.whl", hash = "sha256:f58d535af89bb9ad5cd4df046f741f8553a418c01a7856bf0d173bbc9f6bd16d"}, {file = "filelock-3.9.0.tar.gz", hash = "sha256:7b319f24340b51f55a2bf7a12ac0755a9b03e718311dac567a0f4f7fabd2f5de"}, ] +flet = [ + {file = "flet-0.4.2-py3-none-any.whl", hash = "sha256:e2f92e45fe79ac2ad0694024204357f675538b9b44c9fbd6d540d7ca696e7644"}, + {file = "flet-0.4.2-py3-none-macosx_10_14_x86_64.whl", hash = "sha256:7b863c9fb6a6a3cb30584910679d9dd5a14ccdb22d5b9c7a30b97af8c5d539c1"}, + {file = "flet-0.4.2-py3-none-macosx_12_0_arm64.whl", hash = "sha256:c075928d16f42d1dd0837a1c356a70cdea424e8668094703b6996cd6697a2ca3"}, + {file = "flet-0.4.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16a3350fb737ff9be9cf25d9a4a36a008d077a94cfd61456f68d3697ca2ea087"}, + {file = "flet-0.4.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac336d590da4cd46c254ba4af6cdc35ee01aec872b82b1448242498960831e0b"}, + {file = "flet-0.4.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e632d373f62f2e521ec533dd611442c32216af6387d845c19e5a03787b19370"}, + {file = "flet-0.4.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:720d5bf28670fe9d8bb3d690b89c18cec7914f94f0233bca4ba401a89260173b"}, + {file = "flet-0.4.2-py3-none-win32.whl", hash = "sha256:be231306ab04a80c2c915412a5579fa99223dd6f822fd87323c6f4145b90e167"}, + {file = "flet-0.4.2-py3-none-win_amd64.whl", hash = "sha256:7195cdbc3ce6cdf39fb7fd4ef105a9ccc7d6150c5927672024eaeaab9704a376"}, + {file = "flet-0.4.2.tar.gz", hash = "sha256:0f135d3cc670a5dc1122f04da9295ae3721025dbba3529f010eebdf1f1ee66a0"}, +] +flet-core = [ + {file = "flet_core-0.4.2-py3-none-any.whl", hash = "sha256:6b2d38eaf0bba45b58b293421c68cd03f9f540171f9ec81ff1027ac66d149d76"}, + {file = "flet_core-0.4.2.tar.gz", hash = "sha256:6f6e4c00ba8de8d169826a15ad8da171cd30b3cd518f7f49c6de929d00be1cb2"}, +] fuzzywuzzy = [ {file = "fuzzywuzzy-0.18.0-py2.py3-none-any.whl", hash = "sha256:928244b28db720d1e0ee7587acf660ea49d7e4c632569cad4f1cd7e68a5f0993"}, {file = "fuzzywuzzy-0.18.0.tar.gz", hash = "sha256:45016e92264780e58972dca1b3d939ac864b78437422beecebb3095f8efd00e8"}, ] +h11 = [ + {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, +] +httpcore = [ + {file = "httpcore-0.16.3-py3-none-any.whl", hash = "sha256:da1fb708784a938aa084bde4feb8317056c55037247c787bd7e19eb2c2949dc0"}, + {file = "httpcore-0.16.3.tar.gz", hash = "sha256:c5d6f04e2fc530f39e0c077e6a30caa53f1451096120f1f38b954afd0b17c0cb"}, +] +httpx = [ + {file = "httpx-0.23.3-py3-none-any.whl", hash = "sha256:a211fcce9b1254ea24f0cd6af9869b3d29aba40154e947d2a07bb499b3e310d6"}, + {file = "httpx-0.23.3.tar.gz", hash = "sha256:9818458eb565bb54898ccb9b8b251a28785dd4a55afbc23d0eb410754fe7d0f9"}, +] idna = [ {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, @@ -686,6 +895,10 @@ invoke = [ {file = "invoke-1.7.3-py3-none-any.whl", hash = "sha256:d9694a865764dd3fd91f25f7e9a97fb41666e822bbb00e670091e3f43933574d"}, {file = "invoke-1.7.3.tar.gz", hash = "sha256:41b428342d466a82135d5ab37119685a989713742be46e42a3a399d685579314"}, ] +kiutils = [ + {file = "kiutils-1.3.0-py3-none-any.whl", hash = "sha256:274f43bad4295c68a9e31282305a29b23013fa62cfd8bbe83bc137e08beee648"}, + {file = "kiutils-1.3.0.tar.gz", hash = "sha256:0c60e40ea2c37d3a58825b02300e7b9110b84ab943f603127542dafe36a25834"}, +] mouser = [ {file = "mouser-0.1.2-py3-none-any.whl", hash = "sha256:560fb2d248d98487665fb1f6e010ff23d62ed99b6e9cc06e7f7f0e8f66f06c29"}, {file = "mouser-0.1.2.tar.gz", hash = "sha256:6d9712ad0402c3b127fb2f8f94e131ab82b8a6909031d2a192674f4fd55c5bda"}, @@ -706,6 +919,14 @@ multiprocess = [ {file = "multiprocess-0.70.14-py39-none-any.whl", hash = "sha256:63cee628b74a2c0631ef15da5534c8aedbc10c38910b9c8b18dcd327528d1ec7"}, {file = "multiprocess-0.70.14.tar.gz", hash = "sha256:3eddafc12f2260d27ae03fe6069b12570ab4764ab59a75e81624fac453fbf46a"}, ] +oauthlib = [ + {file = "oauthlib-3.2.2-py3-none-any.whl", hash = "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca"}, + {file = "oauthlib-3.2.2.tar.gz", hash = "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918"}, +] +packaging = [ + {file = "packaging-23.0-py3-none-any.whl", hash = "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2"}, + {file = "packaging-23.0.tar.gz", hash = "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"}, +] pycparser = [ {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, @@ -714,10 +935,6 @@ pyopenssl = [ {file = "pyOpenSSL-23.0.0-py3-none-any.whl", hash = "sha256:df5fc28af899e74e19fccb5510df423581047e10ab6f1f4ba1763ff5fde844c0"}, {file = "pyOpenSSL-23.0.0.tar.gz", hash = "sha256:c1cc5f86bcacefc84dada7d31175cae1b1518d5f60d3d0bb595a67822a868a6f"}, ] -pysimplegui = [ - {file = "PySimpleGUI-4.60.4-py3-none-any.whl", hash = "sha256:e133fbd21779f0f125cebbc2a4e1f5a931a383738661013ff33ad525d5611eda"}, - {file = "PySimpleGUI-4.60.4.tar.gz", hash = "sha256:f88c82c301a51aea35be605dc060bcceb0dcb6682e16280544884701ab4b23ba"}, -] python-levenshtein = [ {file = "python-Levenshtein-0.12.2.tar.gz", hash = "sha256:dc2395fbd148a1ab31090dd113c366695934b9e85fe5a4b2a032745efd0346f6"}, ] @@ -752,6 +969,10 @@ pyyaml = [ {file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"}, {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"}, ] +repath = [ + {file = "repath-0.9.0-py3-none-any.whl", hash = "sha256:ee079d6c91faeb843274d22d8f786094ee01316ecfe293a1eb6546312bb6a318"}, + {file = "repath-0.9.0.tar.gz", hash = "sha256:8292139bac6a0e43fd9d70605d4e8daeb25d46672e484ed31a24c7ce0aef0fb7"}, +] requests = [ {file = "requests-2.28.2-py3-none-any.whl", hash = "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa"}, {file = "requests-2.28.2.tar.gz", hash = "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf"}, @@ -764,14 +985,22 @@ retrying = [ {file = "retrying-1.3.4-py3-none-any.whl", hash = "sha256:8cc4d43cb8e1125e0ff3344e9de678fefd85db3b750b81b2240dc0183af37b35"}, {file = "retrying-1.3.4.tar.gz", hash = "sha256:345da8c5765bd982b1d1915deb9102fd3d1f7ad16bd84a9700b85f64d24e8f3e"}, ] +rfc3986 = [ + {file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"}, + {file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"}, +] setuptools = [ - {file = "setuptools-67.3.2-py3-none-any.whl", hash = "sha256:bb6d8e508de562768f2027902929f8523932fcd1fb784e6d573d2cafac995a48"}, - {file = "setuptools-67.3.2.tar.gz", hash = "sha256:95f00380ef2ffa41d9bba85d95b27689d923c93dfbafed4aecd7cf988a25e012"}, + {file = "setuptools-67.4.0-py3-none-any.whl", hash = "sha256:f106dee1b506dee5102cc3f3e9e68137bbad6d47b616be7991714b0c62204251"}, + {file = "setuptools-67.4.0.tar.gz", hash = "sha256:e5fd0a713141a4a105412233c63dc4e17ba0090c8e8334594ac790ec97792330"}, ] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] +sniffio = [ + {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, + {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, +] tldextract = [ {file = "tldextract-3.4.0-py3-none-any.whl", hash = "sha256:47aa4d8f1a4da79a44529c9a2ddc518663b25d371b805194ec5ce2a5f615ccd2"}, {file = "tldextract-3.4.0.tar.gz", hash = "sha256:78aef13ac1459d519b457a03f1f74c1bf1c2808122a6bcc0e6840f81ba55ad73"}, @@ -783,6 +1012,111 @@ urllib3 = [ validators = [ {file = "validators-0.19.0.tar.gz", hash = "sha256:dec45f4381f042f1e705cfa74949505b77f1e27e8b05409096fee8152c839cbe"}, ] +watchdog = [ + {file = "watchdog-2.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a09483249d25cbdb4c268e020cb861c51baab2d1affd9a6affc68ffe6a231260"}, + {file = "watchdog-2.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5100eae58133355d3ca6c1083a33b81355c4f452afa474c2633bd2fbbba398b3"}, + {file = "watchdog-2.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e618a4863726bc7a3c64f95c218437f3349fb9d909eb9ea3a1ed3b567417c661"}, + {file = "watchdog-2.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:102a60093090fc3ff76c983367b19849b7cc24ec414a43c0333680106e62aae1"}, + {file = "watchdog-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:748ca797ff59962e83cc8e4b233f87113f3cf247c23e6be58b8a2885c7337aa3"}, + {file = "watchdog-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6ccd8d84b9490a82b51b230740468116b8205822ea5fdc700a553d92661253a3"}, + {file = "watchdog-2.2.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6e01d699cd260d59b84da6bda019dce0a3353e3fcc774408ae767fe88ee096b7"}, + {file = "watchdog-2.2.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8586d98c494690482c963ffb24c49bf9c8c2fe0589cec4dc2f753b78d1ec301d"}, + {file = "watchdog-2.2.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:adaf2ece15f3afa33a6b45f76b333a7da9256e1360003032524d61bdb4c422ae"}, + {file = "watchdog-2.2.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:83a7cead445008e880dbde833cb9e5cc7b9a0958edb697a96b936621975f15b9"}, + {file = "watchdog-2.2.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f8ac23ff2c2df4471a61af6490f847633024e5aa120567e08d07af5718c9d092"}, + {file = "watchdog-2.2.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d0f29fd9f3f149a5277929de33b4f121a04cf84bb494634707cfa8ea8ae106a8"}, + {file = "watchdog-2.2.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:967636031fa4c4955f0f3f22da3c5c418aa65d50908d31b73b3b3ffd66d60640"}, + {file = "watchdog-2.2.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:96cbeb494e6cbe3ae6aacc430e678ce4b4dd3ae5125035f72b6eb4e5e9eb4f4e"}, + {file = "watchdog-2.2.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:61fdb8e9c57baf625e27e1420e7ca17f7d2023929cd0065eb79c83da1dfbeacd"}, + {file = "watchdog-2.2.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4cb5ecc332112017fbdb19ede78d92e29a8165c46b68a0b8ccbd0a154f196d5e"}, + {file = "watchdog-2.2.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a480d122740debf0afac4ddd583c6c0bb519c24f817b42ed6f850e2f6f9d64a8"}, + {file = "watchdog-2.2.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:978a1aed55de0b807913b7482d09943b23a2d634040b112bdf31811a422f6344"}, + {file = "watchdog-2.2.1-py3-none-manylinux2014_armv7l.whl", hash = "sha256:8c28c23972ec9c524967895ccb1954bc6f6d4a557d36e681a36e84368660c4ce"}, + {file = "watchdog-2.2.1-py3-none-manylinux2014_i686.whl", hash = "sha256:c27d8c1535fd4474e40a4b5e01f4ba6720bac58e6751c667895cbc5c8a7af33c"}, + {file = "watchdog-2.2.1-py3-none-manylinux2014_ppc64.whl", hash = "sha256:d6b87477752bd86ac5392ecb9eeed92b416898c30bd40c7e2dd03c3146105646"}, + {file = "watchdog-2.2.1-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:cece1aa596027ff56369f0b50a9de209920e1df9ac6d02c7f9e5d8162eb4f02b"}, + {file = "watchdog-2.2.1-py3-none-manylinux2014_s390x.whl", hash = "sha256:8b5cde14e5c72b2df5d074774bdff69e9b55da77e102a91f36ef26ca35f9819c"}, + {file = "watchdog-2.2.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:e038be858425c4f621900b8ff1a3a1330d9edcfeaa1c0468aeb7e330fb87693e"}, + {file = "watchdog-2.2.1-py3-none-win32.whl", hash = "sha256:bc43c1b24d2f86b6e1cc15f68635a959388219426109233e606517ff7d0a5a73"}, + {file = "watchdog-2.2.1-py3-none-win_amd64.whl", hash = "sha256:17f1708f7410af92ddf591e94ae71a27a13974559e72f7e9fde3ec174b26ba2e"}, + {file = "watchdog-2.2.1-py3-none-win_ia64.whl", hash = "sha256:195ab1d9d611a4c1e5311cbf42273bc541e18ea8c32712f2fb703cfc6ff006f9"}, + {file = "watchdog-2.2.1.tar.gz", hash = "sha256:cdcc23c9528601a8a293eb4369cbd14f6b4f34f07ae8769421252e9c22718b6f"}, +] +websocket-client = [ + {file = "websocket-client-1.5.1.tar.gz", hash = "sha256:3f09e6d8230892547132177f575a4e3e73cfdf06526e20cc02aa1c3b47184d40"}, + {file = "websocket_client-1.5.1-py3-none-any.whl", hash = "sha256:cdf5877568b7e83aa7cf2244ab56a3213de587bbe0ce9d8b9600fc77b455d89e"}, +] +websockets = [ + {file = "websockets-10.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d58804e996d7d2307173d56c297cf7bc132c52df27a3efaac5e8d43e36c21c48"}, + {file = "websockets-10.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc0b82d728fe21a0d03e65f81980abbbcb13b5387f733a1a870672c5be26edab"}, + {file = "websockets-10.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ba089c499e1f4155d2a3c2a05d2878a3428cf321c848f2b5a45ce55f0d7d310c"}, + {file = "websockets-10.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33d69ca7612f0ddff3316b0c7b33ca180d464ecac2d115805c044bf0a3b0d032"}, + {file = "websockets-10.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62e627f6b6d4aed919a2052efc408da7a545c606268d5ab5bfab4432734b82b4"}, + {file = "websockets-10.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38ea7b82bfcae927eeffc55d2ffa31665dc7fec7b8dc654506b8e5a518eb4d50"}, + {file = "websockets-10.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e0cb5cc6ece6ffa75baccfd5c02cffe776f3f5c8bf486811f9d3ea3453676ce8"}, + {file = "websockets-10.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ae5e95cfb53ab1da62185e23b3130e11d64431179debac6dc3c6acf08760e9b1"}, + {file = "websockets-10.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7c584f366f46ba667cfa66020344886cf47088e79c9b9d39c84ce9ea98aaa331"}, + {file = "websockets-10.4-cp310-cp310-win32.whl", hash = "sha256:b029fb2032ae4724d8ae8d4f6b363f2cc39e4c7b12454df8df7f0f563ed3e61a"}, + {file = "websockets-10.4-cp310-cp310-win_amd64.whl", hash = "sha256:8dc96f64ae43dde92530775e9cb169979f414dcf5cff670455d81a6823b42089"}, + {file = "websockets-10.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:47a2964021f2110116cc1125b3e6d87ab5ad16dea161949e7244ec583b905bb4"}, + {file = "websockets-10.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e789376b52c295c4946403bd0efecf27ab98f05319df4583d3c48e43c7342c2f"}, + {file = "websockets-10.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7d3f0b61c45c3fa9a349cf484962c559a8a1d80dae6977276df8fd1fa5e3cb8c"}, + {file = "websockets-10.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f55b5905705725af31ccef50e55391621532cd64fbf0bc6f4bac935f0fccec46"}, + {file = "websockets-10.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00c870522cdb69cd625b93f002961ffb0c095394f06ba8c48f17eef7c1541f96"}, + {file = "websockets-10.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f38706e0b15d3c20ef6259fd4bc1700cd133b06c3c1bb108ffe3f8947be15fa"}, + {file = "websockets-10.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f2c38d588887a609191d30e902df2a32711f708abfd85d318ca9b367258cfd0c"}, + {file = "websockets-10.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:fe10ddc59b304cb19a1bdf5bd0a7719cbbc9fbdd57ac80ed436b709fcf889106"}, + {file = "websockets-10.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:90fcf8929836d4a0e964d799a58823547df5a5e9afa83081761630553be731f9"}, + {file = "websockets-10.4-cp311-cp311-win32.whl", hash = "sha256:b9968694c5f467bf67ef97ae7ad4d56d14be2751000c1207d31bf3bb8860bae8"}, + {file = "websockets-10.4-cp311-cp311-win_amd64.whl", hash = "sha256:a7a240d7a74bf8d5cb3bfe6be7f21697a28ec4b1a437607bae08ac7acf5b4882"}, + {file = "websockets-10.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:74de2b894b47f1d21cbd0b37a5e2b2392ad95d17ae983e64727e18eb281fe7cb"}, + {file = "websockets-10.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3a686ecb4aa0d64ae60c9c9f1a7d5d46cab9bfb5d91a2d303d00e2cd4c4c5cc"}, + {file = "websockets-10.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0d15c968ea7a65211e084f523151dbf8ae44634de03c801b8bd070b74e85033"}, + {file = "websockets-10.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00213676a2e46b6ebf6045bc11d0f529d9120baa6f58d122b4021ad92adabd41"}, + {file = "websockets-10.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:e23173580d740bf8822fd0379e4bf30aa1d5a92a4f252d34e893070c081050df"}, + {file = "websockets-10.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:dd500e0a5e11969cdd3320935ca2ff1e936f2358f9c2e61f100a1660933320ea"}, + {file = "websockets-10.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4239b6027e3d66a89446908ff3027d2737afc1a375f8fd3eea630a4842ec9a0c"}, + {file = "websockets-10.4-cp37-cp37m-win32.whl", hash = "sha256:8a5cc00546e0a701da4639aa0bbcb0ae2bb678c87f46da01ac2d789e1f2d2038"}, + {file = "websockets-10.4-cp37-cp37m-win_amd64.whl", hash = "sha256:a9f9a735deaf9a0cadc2d8c50d1a5bcdbae8b6e539c6e08237bc4082d7c13f28"}, + {file = "websockets-10.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5c1289596042fad2cdceb05e1ebf7aadf9995c928e0da2b7a4e99494953b1b94"}, + {file = "websockets-10.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0cff816f51fb33c26d6e2b16b5c7d48eaa31dae5488ace6aae468b361f422b63"}, + {file = "websockets-10.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:dd9becd5fe29773d140d68d607d66a38f60e31b86df75332703757ee645b6faf"}, + {file = "websockets-10.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45ec8e75b7dbc9539cbfafa570742fe4f676eb8b0d3694b67dabe2f2ceed8aa6"}, + {file = "websockets-10.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f72e5cd0f18f262f5da20efa9e241699e0cf3a766317a17392550c9ad7b37d8"}, + {file = "websockets-10.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:185929b4808b36a79c65b7865783b87b6841e852ef5407a2fb0c03381092fa3b"}, + {file = "websockets-10.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7d27a7e34c313b3a7f91adcd05134315002aaf8540d7b4f90336beafaea6217c"}, + {file = "websockets-10.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:884be66c76a444c59f801ac13f40c76f176f1bfa815ef5b8ed44321e74f1600b"}, + {file = "websockets-10.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:931c039af54fc195fe6ad536fde4b0de04da9d5916e78e55405436348cfb0e56"}, + {file = "websockets-10.4-cp38-cp38-win32.whl", hash = "sha256:db3c336f9eda2532ec0fd8ea49fef7a8df8f6c804cdf4f39e5c5c0d4a4ad9a7a"}, + {file = "websockets-10.4-cp38-cp38-win_amd64.whl", hash = "sha256:48c08473563323f9c9debac781ecf66f94ad5a3680a38fe84dee5388cf5acaf6"}, + {file = "websockets-10.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:40e826de3085721dabc7cf9bfd41682dadc02286d8cf149b3ad05bff89311e4f"}, + {file = "websockets-10.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:56029457f219ade1f2fc12a6504ea61e14ee227a815531f9738e41203a429112"}, + {file = "websockets-10.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f5fc088b7a32f244c519a048c170f14cf2251b849ef0e20cbbb0fdf0fdaf556f"}, + {file = "websockets-10.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fc8709c00704194213d45e455adc106ff9e87658297f72d544220e32029cd3d"}, + {file = "websockets-10.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0154f7691e4fe6c2b2bc275b5701e8b158dae92a1ab229e2b940efe11905dff4"}, + {file = "websockets-10.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c6d2264f485f0b53adf22697ac11e261ce84805c232ed5dbe6b1bcb84b00ff0"}, + {file = "websockets-10.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9bc42e8402dc5e9905fb8b9649f57efcb2056693b7e88faa8fb029256ba9c68c"}, + {file = "websockets-10.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:edc344de4dac1d89300a053ac973299e82d3db56330f3494905643bb68801269"}, + {file = "websockets-10.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:84bc2a7d075f32f6ed98652db3a680a17a4edb21ca7f80fe42e38753a58ee02b"}, + {file = "websockets-10.4-cp39-cp39-win32.whl", hash = "sha256:c94ae4faf2d09f7c81847c63843f84fe47bf6253c9d60b20f25edfd30fb12588"}, + {file = "websockets-10.4-cp39-cp39-win_amd64.whl", hash = "sha256:bbccd847aa0c3a69b5f691a84d2341a4f8a629c6922558f2a70611305f902d74"}, + {file = "websockets-10.4-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:82ff5e1cae4e855147fd57a2863376ed7454134c2bf49ec604dfe71e446e2193"}, + {file = "websockets-10.4-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d210abe51b5da0ffdbf7b43eed0cfdff8a55a1ab17abbec4301c9ff077dd0342"}, + {file = "websockets-10.4-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:942de28af58f352a6f588bc72490ae0f4ccd6dfc2bd3de5945b882a078e4e179"}, + {file = "websockets-10.4-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9b27d6c1c6cd53dc93614967e9ce00ae7f864a2d9f99fe5ed86706e1ecbf485"}, + {file = "websockets-10.4-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:3d3cac3e32b2c8414f4f87c1b2ab686fa6284a980ba283617404377cd448f631"}, + {file = "websockets-10.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:da39dd03d130162deb63da51f6e66ed73032ae62e74aaccc4236e30edccddbb0"}, + {file = "websockets-10.4-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:389f8dbb5c489e305fb113ca1b6bdcdaa130923f77485db5b189de343a179393"}, + {file = "websockets-10.4-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09a1814bb15eff7069e51fed0826df0bc0702652b5cb8f87697d469d79c23576"}, + {file = "websockets-10.4-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff64a1d38d156d429404aaa84b27305e957fd10c30e5880d1765c9480bea490f"}, + {file = "websockets-10.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:b343f521b047493dc4022dd338fc6db9d9282658862756b4f6fd0e996c1380e1"}, + {file = "websockets-10.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:932af322458da7e4e35df32f050389e13d3d96b09d274b22a7aa1808f292fee4"}, + {file = "websockets-10.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6a4162139374a49eb18ef5b2f4da1dd95c994588f5033d64e0bbfda4b6b6fcf"}, + {file = "websockets-10.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c57e4c1349fbe0e446c9fa7b19ed2f8a4417233b6984277cce392819123142d3"}, + {file = "websockets-10.4-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b627c266f295de9dea86bd1112ed3d5fafb69a348af30a2422e16590a8ecba13"}, + {file = "websockets-10.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:05a7233089f8bd355e8cbe127c2e8ca0b4ea55467861906b80d2ebc7db4d6b72"}, + {file = "websockets-10.4.tar.gz", hash = "sha256:eef610b23933c54d5d921c92578ae5f89813438fded840c2e9809d378dc765d3"}, +] wrapt = [ {file = "wrapt-1.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3"}, {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef"}, diff --git a/pyproject.toml b/pyproject.toml index e3e69b0f..d6909652 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,11 +13,12 @@ keywords = ["inventree", "kicad", "component", "part", "create"] [tool.poetry.dependencies] python = ">=3.8,<3.11" digikey-api = "^1.0.0" +Flet = "^0.4.2" fuzzywuzzy = "^0.18.0" inventree = "^0.9.2" +kiutils = "^1.3.0" mouser = "^0.1.2" multiprocess = "^0.70.14" -Flet = "^0.4.0" PyYAML = "^5.4.1" validators = "^0.19.0" wrapt = "^1.14.1" diff --git a/requirements.txt b/requirements.txt index 686cc42d..1934a37a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,8 @@ digikey-api>=1.0.0,<2.0 +Flet>=0.4.2,<1.0 fuzzywuzzy>=0.18.0,<1.0 inventree>=0.9.2,<1.0 +kiutils>=1.3.0,<2.0 mouser>=0.1.2,<1.0 multiprocess>=0.70.14,<0.71 PySimpleGUI>=4.60.4,<5.0