Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,6 @@ __pycache__

# VSCode
.vscode/*

# Poetry lock file
poetry.lock
28 changes: 26 additions & 2 deletions DipTraceGenerator/Component.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,20 @@
# This program is distributed under the MIT license.
# Glory to Ukraine!

try:
from typing import Self # python>=3.11
except ImportError:
from typing_extensions import Self # type: ignore # python<3.11
from typing import Optional, List
from lxml.etree import SubElement
from copy import deepcopy
from DipTraceGenerator.Mixins import RootMixin, Order
from DipTraceGenerator.Mixins import RootMixin, Order, IdMixin
from DipTraceGenerator.Part import Part, PartsMixin
from DipTraceGenerator.Pattern import Pattern
from DipTraceGenerator.PatternLibrary import PatternLibrary


class Component(PartsMixin):
class Component(PartsMixin, IdMixin):
_order = Order(subs=["parts"])

@property
Expand Down Expand Up @@ -61,10 +65,30 @@ def components(self, value: Optional[List[Component]]):
tag.append(deepcopy(component.root))

def find(self, name: str) -> Optional[Component]:
"""
Find component by its name.

Args:
name (str): Component name.

Returns:
Component or None if not found.
"""
for tag in self._root.findall("./Components/Component/Part/Name"):
if tag.text == name:
return Component(tag.getparent().getparent())
return None

def renumerate_ids(self) -> Self:
"""
Renumerate component IDs starting from 0.

Returns:
Self
"""
for i, component in enumerate(self.components, start=0):
component.id = i
return self


if __name__ == "__main__":
Expand Down
76 changes: 70 additions & 6 deletions DipTraceGenerator/ComponentLibrary.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,14 @@
# This program is distributed under the MIT license.
# Glory to Ukraine!

try:
from typing import Self # python>=3.11
except ImportError:
from typing_extensions import Self # type: ignore # python<3.11
from typing import Optional
from pathlib import Path
from lxml.etree import Element, fromstring, tostring, parse
from copy import deepcopy

try:
from typing import Self # python>=3.11
except ImportError:
from typing_extensions import Self # python<3.11

from DipTraceGenerator.Mixins import NameMixin, HintMixin, VersionMixin, UnitsMixin, Order
from DipTraceGenerator.Component import ComponentsMixin
from DipTraceGenerator.PatternLibrary import PatternLibrary
Expand All @@ -35,6 +33,15 @@ def __init__(self, root: Optional[Element] = None, *args, **kwargs):

@classmethod
def load(cls, path: Path) -> Self:
"""
Load a component library from an XML file.

Args:
path (Path): Path to the XML file.

Returns:
Self
"""
if not path.is_file():
path = path.with_suffix(cls.extension)
if not path.is_file():
Expand Down Expand Up @@ -63,23 +70,80 @@ def __str__(self) -> str:

return tostring(self._root, xml_declaration=True, pretty_print=True, encoding="utf-8").decode("utf-8")


def renumerate_all_ids(self) -> Self:
"""
Renumerate all IDs in the library (components and their pins).

Returns:
Self
"""
self.renumerate_ids() # renumerate component IDs
for component in self.components:
component.renumerate_part_ids() # renumerate part IDs
for part in component.parts:
part.renumerate_pin_ids() # renumerate pin IDs
part.renumerate_shape_ids() # renumerate shape IDs
return self

def sort_all(self) -> Self:
"""
Sort all elements and attributes in the library according to their defined order.

Returns:
Self
"""
self.sort() # sort component in library
for component in self.components:
component.sort() # sort parts in component
for part in component.parts:
part.sort() # sort part
for pin in part.pins:
pin.sort() # sort pin attributes
for shape in part.shapes:
shape.sort() # sort shape attributes
return self

def save(self, path: Path) -> Self:
"""
Save the component library to an XML file.

Args:
path (Path): Path to save the library file.

Returns:
Self
"""
if self.root is None:
raise ValueError("Library root is None, cannot save.")
if path.suffix == self.extension:
path = path.with_suffix(self.extension)
path.parent.mkdir(parents=True, exist_ok=True)
self.sort_all()
self.renumerate_all_ids()
path.write_text(str(self), encoding="utf-8")
return self

@property
def pattern_library(self) -> Optional[PatternLibrary]:
"""
Get the pattern library associated with this component library.

Returns:
PatternLibrary or None if not found.
"""
if (tag := self._root.find("./Library")) is not None:
return PatternLibrary(tag)
return None

@pattern_library.setter
def pattern_library(self, value: Optional[PatternLibrary]) -> None:
"""
Set the pattern library associated with this component library.

Args:
value (PatternLibrary or None): The pattern library to set.
"""
for tag in self._root.findall("./Library"):
self._root.remove(tag)
if value is not None:
Expand Down
15 changes: 12 additions & 3 deletions DipTraceGenerator/ComponentShape.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,21 @@
# This program is distributed under the MIT license.
# Glory to Ukraine!

try:
from typing import Self # python>=3.11
except ImportError:
from typing_extensions import Self # type: ignore # python<3.11
from typing import Optional, List
from lxml.etree import SubElement
from copy import deepcopy
from lxml.etree import Element
from .Mixins import RootMixin, LockedMixin, LineWidthMixin, GroupMixin, Order
from .Mixins import RootMixin, LockedMixin, LineWidthMixin, GroupMixin, Order, IdMixin
from .Point import PointsMixin
from .Enums import ShapeType, Boolean


class ComponentShape(LockedMixin, LineWidthMixin, PointsMixin, GroupMixin):
_order = Order(args=["Type", "LineWidth", "Locked", "Group"])
class ComponentShape(IdMixin, LockedMixin, LineWidthMixin, PointsMixin, GroupMixin):
_order = Order(args=["Id", "Type", "LineWidth", "Locked", "Group"])

def __init__(self, root: Optional[Element] = None, *args, **kwargs):
if root is None:
Expand Down Expand Up @@ -65,6 +69,11 @@ def shapes(self, value: Optional[List[ComponentShape]]):
for shape in value:
tag.append(deepcopy(shape.root))

def renumerate_shape_ids(self) -> Self:
for i, shape in enumerate(self.shapes, start=0):
shape.id = i
return self


if __name__ == "__main__":
pass
57 changes: 27 additions & 30 deletions DipTraceGenerator/Mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from dataclasses import dataclass, field
from copy import deepcopy
from DipTraceGenerator.Enums import Units, Side, MountType, Boolean, Layer
from DipTraceGenerator.PrivateUtils import sort_children_by_tag_order, sort_attributes_by_order


@dataclass
Expand Down Expand Up @@ -46,36 +47,11 @@ def __str__(self) -> str:
return tostring(self._root, xml_declaration=False, pretty_print=True, encoding="utf-8").decode("utf-8")

def sort(self) -> None:
if hasattr(self, "_order") and isinstance(self._order, Order):
temp = {}
for key in self._order.args:
if key in self._root.attrib:
temp[key] = self._root.attrib.pop(key)
for key, value in temp.items():
self._root.set(key, value)

temp = {}
for key in self._order.tags:
temp[key] = deepcopy(self._root.find(f"./{key}"))
for tag in self._root.findall(f"./{key}"):
self._root.remove(tag)

for key, value in temp.items():
if value is not None:
self._root.append(value)

for key in self._order.subs:
if hasattr(self, key):
obj = getattr(self, key)

if hasattr(obj, "reorder"):
if callable(obj.sort):
obj.sort()

elif hasattr(obj, "__iter__"):
for o in obj:
if callable(o.sort):
o.sort()
if hasattr(self, "_order"):
if len(self._order.args) > 0:
sort_attributes_by_order(self._root, self._order.args)
if len(self._order.tags) > 0:
sort_children_by_tag_order(self._root, self._order.tags)


class NameMixin(RootMixin):
Expand Down Expand Up @@ -120,6 +96,27 @@ def name(self, value: Optional[str] = None) -> None:
tag.text = value


class IdMixin(RootMixin):
@property
def id(self) -> Optional[int]:
try:
if "Id" in self._root.attrib:
return int(self._root.get("Id"))
return None
except (TypeError, ValueError, AttributeError):
return None

@id.setter
def id(self, value: Optional[int]) -> None:
if value is not None:
if value < 0:
raise ValueError("Id must be non-negative integer.")
# TODO: Check for uniqueness in parent group "Patterns"
self._root.set("Id", str(value))
elif "Id" in self._root.attrib:
self._root.attrib.pop("Id")


class NameDescriptionTagMixin(RootMixin):
@property
def name_description(self) -> Optional[str]:
Expand Down
24 changes: 13 additions & 11 deletions DipTraceGenerator/Part.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,27 @@
# This program is distributed under the MIT license.
# Glory to Ukraine!

try:
from typing import Self # python>=3.11
except ImportError:
from typing_extensions import Self # type: ignore # python<3.11
from typing import Optional, List, Tuple
from lxml.etree import SubElement
from copy import deepcopy
from .Mixins import (
RootMixin,
RefDesMixin,
NameTagMixin,
ValueTagMixin,
DatasheetTagMixin,
ManufacturerTagMixin,
Order,
WidthMixin,
HeightMixin,
)
from .ComponentOrigin import ComponentOriginMixin
from .ComponentShape import ComponentShapesMixin
from .SpiceModel import SpiceModelMixin
from .Pin import PinsMixin
from .Enums import PartType, Boolean, PartStyle, ShapeType
from .Category import CategoryMixin
from .Mixins import RootMixin, RefDesMixin, NameTagMixin, ValueTagMixin, DatasheetTagMixin, ManufacturerTagMixin, \
Order, WidthMixin, HeightMixin, IdMixin


class Part(
RefDesMixin,
NameTagMixin,
IdMixin,
ValueTagMixin,
ComponentOriginMixin,
SpiceModelMixin,
Expand All @@ -43,6 +39,7 @@ class Part(
):
_order = Order(
args=[
"Id",
"RefDes",
"PartType",
"ShowNumbers",
Expand Down Expand Up @@ -198,6 +195,11 @@ def parts(self, value: List[Part]):
for part in value:
self._root.append(deepcopy(part.root))

def renumerate_part_ids(self) -> Self:
for i, part in enumerate(self.parts, start=0):
part.id = i
return self


if __name__ == "__main__":
pass
Loading