Skip to content

Commit

Permalink
Merge pull request #54 from mvnmgrx/feature_libary_id_sch_symbol
Browse files Browse the repository at this point in the history
Feature: Consistent library id APIs for Footprint, Symbol and SchematicSymbol & new lib_name token
  • Loading branch information
mvnmgrx authored Feb 20, 2023
2 parents 5f86fc2 + 517bb1e commit b311b2a
Show file tree
Hide file tree
Showing 18 changed files with 638 additions and 88 deletions.
19 changes: 6 additions & 13 deletions docs/usage/development.rst
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
Development
===========

To start developing, clone the repository and install ``kiutils`` from source while being in the
repository root folder:
To start developing, clone the repository:

.. code-block:: text
git clone https://github.com/mvnmgrx/kiutils.git
cd kiutils
pip install -e .
Doing it this way, changes in the source will be reflected to the current Python environment
automatically. No need to reinstall after making changes.

For generating test reports as well as the documentation, install development requirements:
For generating test reports as well as the documentation, install the development requirements:

.. code-block:: text
Expand All @@ -22,17 +17,15 @@ For generating test reports as well as the documentation, install development re
Tests
-----

To run the test framework and generate an HTML report, start the test script:
Unittests are used to test ``kiutils``. To run the test framework and generate an HTML report, start
the test script:

.. code-block:: text
python3 test.py
To only run the unittests, use:

.. code-block:: text
python3 -m unittest
When adding a feature to ``kiutils``, be sure to provide unittests that explicitly test the
functionality you want to implement.

Generate documentation
----------------------
Expand Down
53 changes: 46 additions & 7 deletions src/kiutils/footprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import calendar
import datetime
import re
from dataclasses import dataclass, field
from typing import Optional, List, Dict
from os import path
Expand Down Expand Up @@ -643,9 +644,47 @@ class Footprint():
https://dev-docs.kicad.org/en/file-formats/sexpr-intro/index.html#_footprint
"""

libraryLink: str = ""
"""The ``libraryLink`` attribute defines the link to footprint library of the footprint.
This only applies to footprints defined in the board file format."""
@property
def libId(self) -> str:
"""The ``lib_id`` token defines the link to footprint library of the footprint.
This only applies to footprints defined in the board file format, in a regular footprint
file this id defines the footprint's name. In ``kiutils``, the token is a combination of
both the ``libraryNickname`` and ``entryName`` token. Setting the ``lib_id`` token will
update those tokens accordingly.
Returns:
- Symbol id in the following format: ``<libraryNickname>:<entryName>`` or ``<entryName>``,
if ``libraryNickname`` token is not set.
"""
if self.libraryNickname:
return f'{self.libraryNickname}:{self.entryName}'
else:
return f'{self.entryName}'

@libId.setter
def libId(self, symbol_id: str):
"""Sets the ``lib_id`` token and parses its contents into the ``libraryNickname`` and
``entryName`` token.
Args:
- symbol_id (str): The symbol id in the following format: ``<libraryNickname>:<entryName>``
or only ``<entryName>``
"""
parse_symbol_id = re.match(r"^(.+?):(.+?)$", symbol_id)
if parse_symbol_id:
self.libraryNickname = parse_symbol_id.group(1)
self.entryName = parse_symbol_id.group(2)
else:
self.libraryNickname = None
self.entryName = symbol_id

libraryNickname: Optional[str] = None
"""The optional ``libraryNickname`` token defines which symbol library this symbol belongs to
and is a part of the ``id`` token"""

entryName: str = None
"""The ``entryName`` token defines the actual name of the symbol and is a part of the ``id``
token"""

version: Optional[str] = None
"""The ``version`` token attribute defines the symbol library version using the YYYYMMDD date format"""
Expand Down Expand Up @@ -779,7 +818,7 @@ def from_sexpr(cls, exp: list) -> Footprint:
raise Exception("Expression does not have the correct type")

object = cls()
object.libraryLink = exp[1]
object.libId = exp[1]
for item in exp[2:]:
if not isinstance(item, list):
if item == 'locked': object.locked = True
Expand Down Expand Up @@ -864,7 +903,7 @@ def from_file(cls, filepath: str, encoding: Optional[str] = None) -> Footprint:
return cls.from_sexpr(fpData)

@classmethod
def create_new(cls, library_link: str, value: str,
def create_new(cls, library_id: str, value: str,
type: str = 'other', reference: str = 'REF**') -> Footprint:
"""Creates a new empty footprint with its attributes set as KiCad would create it
Expand All @@ -883,10 +922,10 @@ def create_new(cls, library_link: str, value: str,
raise Exception("Unsupported type was given")

fp = cls(
libraryLink = library_link,
version = KIUTILS_CREATE_NEW_VERSION_STR,
generator = 'kiutils'
)
fp.libId = library_id

# Create text items that are created when adding a new footprint to a library
fp.graphicItems.extend(
Expand Down Expand Up @@ -955,7 +994,7 @@ def to_sexpr(self, indent=0, newline=True, layerInFirstLine=False) -> str:
generator = f' (generator {self.generator})' if self.generator is not None else ''
tstamp = f' (tstamp {self.tstamp})' if self.tstamp is not None else ''

expression = f'{indents}(footprint "{dequote(self.libraryLink)}"{locked}{placed}{version}{generator}'
expression = f'{indents}(footprint "{dequote(self.libId)}"{locked}{placed}{version}{generator}'
if layerInFirstLine:
expression += f' (layer "{dequote(self.layer)}")\n'
else:
Expand Down
77 changes: 61 additions & 16 deletions src/kiutils/items/schitems.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

from __future__ import annotations

import re
from dataclasses import dataclass, field
from typing import Optional, List, Dict

Expand Down Expand Up @@ -741,34 +742,76 @@ def to_sexpr(self, indent=2, newline=True) -> str:
@dataclass
class SchematicSymbol():
"""The ``symbol`` token in the symbol section of the schematic defines an instance of a symbol
from the library symbol section of the schematic
from the library symbol section of the schematic
Documentation:
https://dev-docs.kicad.org/en/file-formats/sexpr-schematic/#_symbol_section
"""

libraryIdentifier: str = ""
"""The ``libraryIdentifier`` defines which symbol in the library symbol section of the schematic
that this schematic symbol references"""
@property
def libId(self) -> str:
"""The ``lib_id`` token defines which symbol in the library symbol section of the schematic
this schematic symbol references. In ``kiutils``, the ``lib_id`` token is a combination of
both the ``libraryNickname`` and ``entryName`` token. Setting the ``lib_id`` token will
update those tokens accordingly.
Returns:
- Symbol id in the following format: ``<libraryNickname>:<entryName>`` or ``<entryName>``,
if ``libraryNickname`` token is not set.
"""
if self.libraryNickname:
return f'{self.libraryNickname}:{self.entryName}'
else:
return f'{self.entryName}'

@libId.setter
def libId(self, symbol_id: str):
"""Sets the ``lib_id`` token and parses its contents into the ``libraryNickname`` and
``entryName`` token.
Args:
- symbol_id (str): The symbol id in the following format: ``<libraryNickname>:<entryName>``
or only ``<entryName>``
"""
parse_symbol_id = re.match(r"^(.+?):(.+?)$", symbol_id)
if parse_symbol_id:
self.libraryNickname = parse_symbol_id.group(1)
self.entryName = parse_symbol_id.group(2)
else:
self.libraryNickname = None
self.entryName = symbol_id

libraryNickname: Optional[str] = None
"""The optional ``libraryNickname`` token defines which symbol library this symbol belongs to
and is a part of the ``id`` token"""

entryName: str = None
"""The ``entryName`` token defines the actual name of the symbol and is a part of the ``id``
token"""

libName: Optional[str] = None
"""The optional ``lib_name`` token is only set when the symbol was edited in the schematic.
It may be set to ``<entryName>_X`` where X is a unique number that specifies which variation
this symbol is of its original."""

position: Position = field(default_factory=lambda: Position())
"""The ``position`` defines the X and Y coordinates and angle of rotation of the symbol"""

unit: Optional[int] = None
"""The optional ``unit`` token attribute defines which unit in the symbol library definition that the
schematic symbol represents"""
"""The optional ``unit`` token attribute defines which unit in the symbol library definition
that the schematic symbol represents"""

inBom: bool = False
"""The ``in_bom`` token attribute determines whether the schematic symbol appears in any bill
of materials output"""
of materials output"""

onBoard: bool = False
"""The on_board token attribute determines if the footprint associated with the symbol is
exported to the board via the netlist"""
"""The ``on_board`` token attribute determines if the footprint associated with the symbol is
exported to the board via the netlist"""

fieldsAutoplaced: bool = False
"""The ``fields_autoplaced`` is a flag that indicates that any PROPERTIES associated
with the global label have been place automatically"""
with the global label have been place automatically"""

uuid: Optional[str] = ""
"""The optional `uuid` defines the universally unique identifier"""
Expand All @@ -778,12 +821,12 @@ class SchematicSymbol():

pins: Dict[str, str] = field(default_factory=dict)
"""The ``pins`` token defines a dictionary with pin numbers in form of strings as keys and
uuid's as values"""
uuid's as values"""

mirror: Optional[str] = None
"""The ``mirror`` token defines if the symbol is mirrored in the schematic. Accepted values: ``x`` or ``y``.
When mirroring around the x and y axis at the same time use some additional rotation to get the correct
orientation of the symbol."""
"""The ``mirror`` token defines if the symbol is mirrored in the schematic. Accepted values:
``x`` or ``y``. When mirroring around the x and y axis at the same time use some additional
rotation to get the correct orientation of the symbol."""

@classmethod
def from_sexpr(cls, exp: list) -> SchematicSymbol:
Expand All @@ -808,7 +851,8 @@ def from_sexpr(cls, exp: list) -> SchematicSymbol:
object = cls()
for item in exp[1:]:
if item[0] == 'fields_autoplaced': object.fieldsAutoplaced = True
if item[0] == 'lib_id': object.libraryIdentifier = item[1]
if item[0] == 'lib_id': object.libId = item[1]
if item[0] == 'lib_name': object.libName = item[1]
if item[0] == 'uuid': object.uuid = item[1]
if item[0] == 'unit': object.unit = item[1]
if item[0] == 'in_bom': object.inBom = True if item[1] == 'yes' else False
Expand Down Expand Up @@ -838,8 +882,9 @@ def to_sexpr(self, indent=2, newline=True) -> str:
onBoard = 'yes' if self.onBoard else 'no'
mirror = f' (mirror {self.mirror})' if self.mirror is not None else ''
unit = f' (unit {self.unit})' if self.unit is not None else ''
lib_name = f' (lib_name "{dequote(self.libName)}")' if self.libName is not None else ''

expression = f'{indents}(symbol (lib_id "{dequote(self.libraryIdentifier)}") (at {self.position.X} {self.position.Y}{posA}){mirror}{unit}\n'
expression = f'{indents}(symbol{lib_name} (lib_id "{dequote(self.libId)}") (at {self.position.X} {self.position.Y}{posA}){mirror}{unit}\n'
expression += f'{indents} (in_bom {inBom}) (on_board {onBoard}){fa}\n'
if self.uuid:
expression += f'{indents} (uuid {self.uuid})\n'
Expand Down
Loading

0 comments on commit b311b2a

Please # to comment.