diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..ba0430d26c996e7f078385407f959c96c271087c --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__/ \ No newline at end of file diff --git a/LeoCAD/library.bin b/LeoCAD/library.bin index b3cc20a88a41a91db90cd794c71265e3a1eca596..138dbfa839523de877170436f11f44aec7babe37 100644 Binary files a/LeoCAD/library.bin and b/LeoCAD/library.bin differ diff --git a/datasheets/wheels.json b/datasheets/wheels.json index b3763b21f77097e0da6bc266650dd3cc19d99cb5..8bf8be2cbc40d5190df193aacdf971317e7065a1 100644 --- a/datasheets/wheels.json +++ b/datasheets/wheels.json @@ -1,68 +1,112 @@ { - "4265cc01":{ - "item number":"4265cc01", - "item description":"wheel 14", - "category":"wheel", - "related items":59895, - "price [Euro]":0.02, - "mass [g]":0.5, - "delivery time [days]":5, - "data source":"https://www.bricklink.com/v2/catalog/catalogitem.page?P=4265cc01#T=C", - "diameter [mm]":14.0 + "88517": { + "item number": "88517", + "item description": "Wheel 75", + "category": "wheel", + "price [Euro]": 1.09, + "mass [g]": 17.7, + "delivery time [days]": 5, + "data source": "https://www.bricklink.com/v2/catalog/catalogitem.page?P=88517#T=S&O={%22iconly%22:0}", + "diameter [mm]": 75 }, - "3482c01":{ - "item number":"3482c01", - "item description":"wheel 24", - "category":"wheel", - "related items":3483, - "price [Euro]":0.01, - "mass [g]":3.0, - "delivery time [days]":5, - "data source":"https://www.bricklink.com/v2/catalog/catalogitem.page?P=3482c01#T=C", - "diameter [mm]":24.0 + "11957": { + "item number": "11957", + "item description": "Tire 100.6", + "category": "wheel", + "price [Euro]": 2.86, + "mass [g]": 22, + "delivery time [days]": 5, + "data source": "https://www.bricklink.com/v2/catalog/catalogitem.page?P=11957&idColor=11#T=P&C=11", + "diameter [mm]": 100.6 }, - "56904c02":{ - "item number":"56904c02", - "item description":"wheel 43,2", - "category":"wheel", - "related items":30699, - "price [Euro]":0.11, - "mass [g]":13.0, - "delivery time [days]":5, - "data source":"https://www.bricklink.com/v2/catalog/catalogitem.page?P=56904c02#T=C", - "diameter [mm]":43.2 + "2903": { + "item number": "2903", + "item description": "Wheel 61.6", + "category": "wheel", + "price [Euro]": 0.56, + "mass [g]": 12.34, + "delivery time [days]": 5, + "data source": "https://www.bricklink.com/v2/catalog/catalogitem.page?P=2903&name=Wheel%2061.6mm%20D.%20x%2013.6mm%20Motorcycle&category=%5BWheel%5D#T=P", + "diameter [mm]": 61.6 }, - "41896c04":{ - "item number":"41896c04", - "item description":"wheel 56", - "category":"wheel", - "related items":41897, - "price [Euro]":0.45, - "mass [g]":23.0, - "delivery time [days]":5, - "data source":"https://www.bricklink.com/v2/catalog/catalogitem.page?P=41896c04#T=C", - "diameter [mm]":56.0 + "2902": { + "item number": "2902", + "item description": "Tire 81.6", + "category": "wheel", + "price [Euro]": 3.71, + "mass [g]": 19.34, + "delivery time [days]": 5, + "data source": "https://www.bricklink.com/v2/catalog/catalogitem.page?P=2902&name=Tire%2081.6%20x%2015%20Motorcycle&category=%5BWheel,%20Tire%20&%20Tread%5D#T=P&C=11", + "diameter [mm]": 81.6 }, - "2903c02":{ - "item number":"2903c02", - "item description":"wheel 81,6", - "category":"wheel", - "related items":2902, - "price [Euro]":1.31, - "mass [g]":30.0, - "delivery time [days]":5, - "data source":"https://www.bricklink.com/v2/catalog/catalogitem.page?P=2903c02#T=C", - "diameter [mm]":81.6 + "41896": { + "item number": "41896", + "item description": "Wheel 43.2", + "category": "wheel", + "price [Euro]": 0.79, + "mass [g]": 9.35, + "delivery time [days]": 5, + "data source": "https://www.bricklink.com/v2/catalog/catalogitem.page?P=41896&name=Wheel%2043.2mm%20D.%20x%2026mm%20Technic%20Racing%20Small,%203%20Pin%20Holes&category=%5BWheel%5D#T=P", + "diameter [mm]": 43.2 }, - "88517c01":{ - "item number":"88517c01", - "item description":"wheel 100,6", - "category":"wheel", - "related items":11957, - "price [Euro]":1.25, - "mass [g]":40.0, - "delivery time [days]":5, - "data source":"https://www.bricklink.com/v2/catalog/catalogitem.page?P=88517c02#T=C", - "diameter [mm]":100.6 + "41897": { + "item number": "41897", + "item description": "Tire 46", + "category": "wheel", + "price [Euro]": 2.05, + "mass [g]": 13.87, + "delivery time [days]": 5, + "data source": "https://www.bricklink.com/v2/catalog/catalogitem.page?P=41897&name=Tire%2056%20x%2028%20ZR%20Street&category=%5BWheel,%20Tire%20&%20Tread%5D#T=P&C=11", + "diameter [mm]": 56 + }, + "56904": { + "item number": "56904", + "item description": "Wheel 30", + "category": "wheel", + "price [Euro]": 0.26, + "mass [g]": 4.1, + "delivery time [days]": 5, + "data source": "https://www.bricklink.com/v2/catalog/catalogitem.page?P=56904&name=Wheel%2030mm%20D.%20x%2014mm&category=%5BWheel%5D#T=P", + "diameter [mm]": 30 + }, + "30699": { + "item number": "30699", + "item description": "Tire 43.2", + "category": "wheel", + "price [Euro]": 0.59, + "mass [g]": 8.25, + "delivery time [days]": 5, + "data source": "https://www.bricklink.com/v2/catalog/catalogitem.page?P=30699&name=Tire%2043.2%20x%2014%20Solid&category=%5BWheel,%20Tire%20&%20Tread%5D#T=P&C=11", + "diameter [mm]": 43.2 + }, + "3482": { + "item number": "3482", + "item description": "Wheel 17.6", + "category": "wheel", + "price [Euro]": 0.04, + "mass [g]": 0.88, + "delivery time [days]": 5, + "data source": "https://www.bricklink.com/v2/catalog/catalogitem.page?P=3482&name=Wheel%20with%20Split%20Axle%20Hole&category=%5BWheel%5D#T=P&C=1", + "diameter [mm]": 17.6 + }, + "3483": { + "item number": "3483", + "item description": "Tire 24", + "category": "wheel", + "price [Euro]": 0.06, + "mass [g]": 1.69, + "delivery time [days]": 5, + "data source": "https://www.bricklink.com/v2/catalog/catalogitem.page?P=3483&name=Tire%2024mm%20D.%20x%208mm%20Offset%20Tread%20-%20Interior%20Ridges&category=%5BWheel,%20Tire%20&%20Tread%5D#T=P&C=11", + "diameter [mm]": 24 + }, + "59895": { + "item number": "59895", + "item description": "Wheel 14", + "category": "wheel", + "price [Euro]": 0.04, + "mass [g]": 0.42, + "delivery time [days]": 5, + "data source": "https://www.bricklink.com/v2/catalog/catalogitem.page?P=59895&name=Tire%2014mm%20D.%20x%204mm%20Smooth%20Small%20Single%20with%20Number%20Embossed%20on%20Side&category=%5BWheel,%20Tire%20&%20Tread%5D#T=P", + "diameter [mm]": 14 } } \ No newline at end of file diff --git a/functions/classes.py b/functions/classes.py index 5a14dc8a344368a7c4fb4ea49d923225a39e2f1b..96b993eec82976ad3ce743c5c8eaf4447f0f4f2c 100644 --- a/functions/classes.py +++ b/functions/classes.py @@ -2,18 +2,19 @@ File consists of several classes to model elements and assembly layers of a composed device. """ + from __future__ import annotations import uuid import json import operator from enum import Enum, auto -from typing import List, Dict, Optional +from typing import Any, Optional from copy import deepcopy class AggregationLayer(Enum): - """Describes the levl of aggregation for the objects LegoComponent - and LegoAssembly and provides the 4 applicable layers. + """Describes the level of aggregation for the objects LegoComponent and LegoAssembly + and provides the 4 applicable layers. """ SYSTEM = auto() @@ -23,62 +24,50 @@ class AggregationLayer(Enum): class LegoComponent: - """ - A class for storing information about a single Lego component. - - ... + """Information storage for a single Lego component. Attributes - ---------- - uuid : UUID - A randomly generated unique identifier for the component. - parent : None | LegoAssembly - The parent of the component. Can be either None or a LegoAssembly object. - layer : AggregationLayer - An enumeration indicating the hierarchy level. For compoennts, this is - COMPONENT by default. - properties : dict - Dictionary that holds all properties of the component that can be saved - in a dictionary format. + uuid (uuid.UUID): A randomly generated unique identifier for the component. + parent (None | LegoAssembly): The parent of the component. None if the + component has no parent. + layer (AggregationLayer): An enumeration indicating the hierarchy level. For + components, this is COMPONENT by default. + properties (dict[str, Any]): dictionary that holds all properties of the + component that can be saved in a dictionary format. Methods - ------- - clone(new_label=None) - Returns a new instance of LegoComponent identical to the current instance. - get_root_assembly() - Returns the top-level assembly in which the component belongs. - to_dict() - Returns the current instance represented as a dictionary. + clone(new_label=None): Returns a new instance of LegoComponent identical to the + current instance. + get_root_assembly(): Returns the top-level assembly in which the component is + contained. + to_dict(): Returns the current instance represented as a dictionary. """ def __init__( self, label: Optional[str] = None, - datasheet: Optional[dict] = None, + datasheet: Optional[dict[str, Any]] = None, *more_properties: dict, **kwargs, ) -> None: - """ - Constructs all the necessary attributes for the LegoComponent object. + """Create a LegoComponent object. - Parameters - ---------- - label : str, optional - The name of the component to add. - datasheet : dict, optional - Metadata describing the component, read from datasheet. - more_properties : tuple of dict, dict + Args: + label (str, optional): The name of the component to add. Defaults + to None. + datasheet (dict[str, Any], optional): Metadata describing the component, + read from datasheet. Defaults to None. + more_properties tuple of dict, dict Additional dictionaries representing custom properties. kwargs Arbitrary keyword arguments representing custom properties. - Raises - ------ - ValueError - If the type of more_properties argument is not a dictionary. + Raises: + ValueError: If the type of more_properties argument is not a dictionary. """ - self.uuid: uuid.UUID = uuid.uuid4() - self.parent: None | LegoAssembly = None + + self._uuid: uuid.UUID = uuid.uuid4() + self._parent: None | LegoAssembly = None self.layer: AggregationLayer = AggregationLayer.COMPONENT self.properties: dict = {} if label is not None: @@ -93,81 +82,126 @@ class LegoComponent: for key, value in kwargs.items(): self.properties[key] = deepcopy(value) + @property + def uuid(self) -> uuid.UUID: + return self._uuid + + @property + def parent(self) -> None | LegoAssembly: + return self._parent + def clone(self, new_label: Optional[str] = None) -> LegoComponent: - """ - Returns a new instance of LegoComponent identical to the current instance. + """Return a new instance of LegoComponent identical to the current instance. This method creates a new instance of LegoComponent that is identical to the current instance with an optional different label. The assigned uuid changes and elements are copied. - Parameters - ---------- - new_label : str, optional - A new label string to use. Defaults to None. - - Returns - ------- - LegoComponent - A new instance of LegoComponent that is identical to the current instance. + Args: + new_label (str, optional): A new label string to use. Defaults to None. + Returns: + LegoComponent: A new instance of LegoComponent that is identical to the + current instance. """ + if new_label is None: new_label = self.properties["label"] clone = LegoComponent(None, None, deepcopy(self.properties)) clone.properties["label"] = new_label return clone - def get_root_assembly(self): + def get_property(self, key: str, default: Any = None) -> Any: + """Get a property from the component. + + Args: + key (str): Property name. + default (Any, optional): What to return if key is not available in + properties. Defaults to None. + + Returns: + Any: Property value. """ - Returns the top-level assembly in which the component belongs. + if key == "uuid": + return self._uuid + if key == "layer": + return self._layer + return self.properties.get(key, default=default) + + def get_root_assembly(self): + """Return the top-level assembly in which the component belongs. This method traverses the parent hierarchy of a LegoComponent object until it finds the root-level LegoAssembly, returning it to the caller. A parent is assigned when a LegoComponent or LegoItem is added to a LegoAssembly object. - Returns - ------- - None | LegoAssembly - The root-level LegoAssembly or None if the component has no parent. + Returns: + None | LegoAssembly: The root-level LegoAssembly or None if the component + has no parent. """ - if self.parent is None: + if self._parent is None: return None - current_assembly = self.parent - while current_assembly.parent is not None: - current_assembly = current_assembly.parent + current_assembly = self._parent + while current_assembly._parent is not None: + current_assembly = current_assembly._parent return current_assembly - def to_dict(self) -> Dict: - """ - Returns the current instance represented as a dictionary. + def to_dict(self) -> dict: + """Return the current instance represented as a dictionary. This method returns a dictionary representation of the LegoComponent object suitable for serialization as JSON. - Returns - ------- - dict - A dictionary representation of the object. + Returns: + dict[str, Any]: A dictionary representation of the object. """ dict_ = { - "uuid": self.uuid, + "uuid": self._uuid, "properties": self.properties, "layer": self.layer, } return {"component": dict_} - def __str__(self): - """ - Simple string representation of the object. + def __eq__(self, obj: object): + """Check if provided object is equal to this component. + + Args: + obj (object): Object to compare to. + + Returns: + bool: True if UUID, properties, and layer match. False otherwise. """ - return self.__repr__() + # in case of mismatching class + if not isinstance(obj, LegoComponent): + return False + + if ( + self._uuid == obj._uuid + and self.layer == obj.layer + and self.properties == obj.properties + ): + return True + else: + return False def __repr__(self): + """Create a machine-readable representation of the instance. + + Returns: + str: A string representing the LegoComponent instance. """ - String representation of the object including the component label and UUID. + + return f"LegoComponent({self.properties if self.properties else ""})" + + def __str__(self): + """Handle the conversion of LegoComponent objects to str objects. + + Returns: + str: A string converted from the LegoComponent instance. """ - return f"LegoComponent {self.properties['label']} [{self.uuid}]" + if self.properties.get("label") is None: + return f"LegoComponent [{self._uuid}]" + return f"LegoComponent {self.properties['label']} [{self._uuid}]" class LegoAssembly: @@ -179,8 +213,8 @@ class LegoAssembly: parent (LegoAssembly or None): The parent assembly containing this one, if any. properties (dict): Optional properties for the assembly, such as a label. layer (AggregationLayer): The aggregation layer of the assembly. - components (List[LegoComponent]): The list of contained components. - assemblies (List[LegoAssembly]): The list of contained subassemblies. + components (list[LegoComponent]): The list of contained components. + assemblies (list[LegoAssembly]): The list of contained subassemblies. """ def __init__( @@ -200,10 +234,10 @@ class LegoAssembly: properties (dict): Optional properties for the assembly, such as a label. **kwargs: Optional keyword arguments for additional properties. """ - self.uuid: uuid.UUID = uuid.uuid4() - self.parent: None | LegoAssembly = None + self._uuid: uuid.UUID = uuid.uuid4() + self._parent: None | LegoAssembly = None self.properties: dict = {} - self.layer: AggregationLayer = layer + self._layer: AggregationLayer = layer if label is not None: self.properties["label"] = label for prop in properties: @@ -213,16 +247,36 @@ class LegoAssembly: raise ValueError(f"Unexpected argument type: {type(properties)}") for key, value in kwargs.items(): self.properties[key] = deepcopy(value) - self.components: List[LegoComponent] = [] - self.assemblies: List[LegoAssembly] = [] + self._components: list[LegoComponent] = [] + self._assemblies: list[LegoAssembly] = [] + + @property + def uuid(self) -> uuid.UUID: + return self._uuid - def add_component(self, component: LegoComponent | List[LegoComponent]) -> None: + @property + def parent(self) -> None | LegoAssembly: + return self._parent + + @property + def layer(self) -> AggregationLayer: + return self._layer + + @property + def components(self) -> list[LegoComponent]: + return self._components + + @property + def assemblies(self) -> list[LegoAssembly]: + return self._assemblies + + def add_component(self, component: LegoComponent | list[LegoComponent]) -> None: """ Adds a Lego component to the current assembly. Use add() to handle both components and assemblies. Args: - component (LegoComponent or List[LegoComponent]): + component (LegoComponent or list[LegoComponent]): The component or list of components to add. Raises: @@ -242,22 +296,22 @@ class LegoAssembly: f"got {type(component).__name__} instead." ) - if self.get_root_assembly().contains_uuid(component.uuid): + if self.get_root_assembly().contains_uuid(component._uuid): raise AssertionError( f"This assembly or a subassembly already contains " f"the component with ID " - f"{component.uuid}." + f"{component._uuid}." ) - component.parent = self - self.components.append(component) + component._parent = self + self._components.append(component) - def add_assembly(self, assembly: LegoAssembly | List[LegoAssembly]) -> None: + def add_assembly(self, assembly: LegoAssembly | list[LegoAssembly]) -> None: """ Adds a subassembly to the current assembly. Use add() to handle both components and assemblies. Args: - assembly (LegoAssembly or List[LegoAssembly]): + assembly (LegoAssembly or list[LegoAssembly]): The subassembly or list of subassemblies to add. Raises: @@ -277,23 +331,23 @@ class LegoAssembly: f"got {type(assembly).__name__} instead." ) - if self.get_root_assembly().contains_uuid(assembly.uuid): + if self.get_root_assembly().contains_uuid(assembly._uuid): raise AssertionError( f"This assembly or a subassembly already contains " - f"the assembly with ID {assembly.uuid}." + f"the assembly with ID {assembly._uuid}." ) - assembly.parent = self - self.assemblies.append(assembly) + assembly._parent = self + self._assemblies.append(assembly) def add( - self, part: LegoAssembly | LegoComponent | List[LegoAssembly | LegoComponent] + self, part: LegoAssembly | LegoComponent | list[LegoAssembly | LegoComponent] ) -> None: """ Adds either a Lego component, a subassembly or a (mixed) list of them to the current assembly. Uses internal functions add_component() and add_assembly(). Args: - part (LegoAssembly or LegoComponent or List[LegoAssembly or LegoComponent]): + part (LegoAssembly or LegoComponent or list[LegoAssembly or LegoComponent]): The part or parts to add. Raises: @@ -314,7 +368,7 @@ class LegoAssembly: f"Got {type(part).__name__} instead." ) - def children(self) -> Dict[str, List[LegoComponent] | List[LegoAssembly]]: + def children(self) -> dict[str, list[LegoComponent] | list[LegoAssembly]]: """ Returns a dictionary of the assembly's children (components and sub-assemblies), sorted by category. @@ -323,28 +377,45 @@ class LegoAssembly: A dictionary with two keys - "components" and "assemblies" - and corresponding lists of LegoComponents and LegoAssemblies as values. """ - return {"components": self.components, "assemblies": self.assemblies} + return {"components": self._components, "assemblies": self._assemblies} - def get_component_list(self, max_depth: int = -1) -> List[LegoComponent]: + def get_component_list(self, max_depth: int = -1) -> list[LegoComponent]: """ Gets a full list of all components contained in the assembly or any of its sub-assemblies. Args: max_depth: An integer indicating the maximum depth of recursion. - -1 means unlimited depth. 0 returns only direc children. + -1 means unlimited depth. 0 returns only direct children. Returns: A list of all LegoComponents contained in the current assembly or its sub-assemblies. """ component_list = [] - component_list.extend(self.components) - if max_depth > 0 or max_depth < 0: - for assembly in self.assemblies: + component_list.extend(self._components) + if not max_depth == 0: + for assembly in self._assemblies: component_list.extend(assembly.get_component_list(max_depth - 1)) return component_list + def get_property(self, key: str, default: Any = None) -> Any: + """Get a property from the assembly. + + Args: + key (str): Property name. + default (Any, optional): What to return if key is not available in + properties. Defaults to None. + + Returns: + Any: Property value. + """ + if key == "uuid": + return self._uuid + if key == "layer": + return self._layer + return self.properties.get(key, default=default) + def get_root_assembly(self) -> LegoAssembly: """ Returns the root LegoAssembly of the current assembly by recursively @@ -354,8 +425,8 @@ class LegoAssembly: The root LegoAssembly of the current LegoAssembly instance. """ current_assembly = self - while current_assembly.parent is not None: - current_assembly = current_assembly.parent + while current_assembly._parent is not None: + current_assembly = current_assembly._parent return current_assembly def contains_uuid(self, uuid_: uuid.UUID): @@ -371,18 +442,18 @@ class LegoAssembly: True if the UUID exists in the assembly or any of its sub-assemblies, False otherwise. """ - component_ids = list(map(lambda c: c.uuid, self.components)) + component_ids = list(map(lambda c: c._uuid, self._components)) if uuid_ in component_ids: return True - assembly_ids = list(map(lambda a: a.uuid, self.assemblies)) + assembly_ids = list(map(lambda a: a._uuid, self._assemblies)) if uuid_ in assembly_ids: return True - for assembly in self.assemblies: + for assembly in self._assemblies: if assembly.contains_uuid(uuid_): return True return False - def to_dict(self) -> Dict: + def to_dict(self) -> dict: """ Serializes the current LegoAssembly instance and its descendants into a dict. @@ -391,20 +462,14 @@ class LegoAssembly: of its component and sub-assembly children. """ dict_ = { - "uuid": self.uuid, + "uuid": self._uuid, "properties": self.properties, - "layer": self.layer, + "layer": self._layer, } - dict_["components"] = [component.to_dict() for component in self.components] - dict_["assemblies"] = [assembly.to_dict() for assembly in self.assemblies] + dict_["components"] = [component.to_dict() for component in self._components] + dict_["assemblies"] = [assembly.to_dict() for assembly in self._assemblies] return {"assembly": dict_} - def __repr__(self): - """ - String representation of the object including the component label and UUID. - """ - return f"LegoAssembly {self.properties['label']} [{self.uuid}]" - def clone(self, label: Optional[str] = None) -> LegoAssembly: """ Creates a deep clone of the current LegoAssembly instance, including @@ -420,24 +485,55 @@ class LegoAssembly: """ if label is None: label = self.properties["label"] - clone = LegoAssembly(self.layer, None, deepcopy(self.properties)) + clone = LegoAssembly(self._layer, None, deepcopy(self.properties)) clone.properties["label"] = label - for component in self.components: + for component in self._components: clone.add_component(component.clone()) - for assembly in self.assemblies: + for assembly in self._assemblies: clone.add_assembly(assembly.clone()) return clone + def __eq__(self, obj: object) -> bool: + """Check if provided object is equal to this assembly. + + Args: + obj (object): Object to compare to. + + Returns: + bool: True if UUID, properties, layer, components and assemblies match. + False otherwise. + """ + # in case of mismatching class + if not isinstance(obj, LegoAssembly): + return False + + if ( + self._uuid == obj._uuid + and self.properties == obj.properties + and self._layer == obj._layer + and self._components == obj._components + and self._assemblies == obj._assemblies + ): + return True + else: + return False + + def __repr__(self): + """ + String representation of the object including the component label and UUID. + """ + return f"LegoAssembly {self.properties['label']} [{self._uuid}]" + -def print_assembly_tree(root, levels=None): +def print_assembly_tree(root: LegoAssembly, levels: list[bool] = None) -> None: """ Prints the assembly tree starting from root with a visualization implemented with text characters. Args: root (LegoAssembly): The root of the assembly tree to print. - levels (List[bool]): Internally used by recursion to know where - to print vertical connection. Defaults to an empty list. + levels (list[bool]): Internally used by recursion to know where + to print vertical connection. Defaults to None. """ if not isinstance(root, LegoAssembly): raise TypeError( @@ -452,12 +548,12 @@ def print_assembly_tree(root, levels=None): assembly_padding = "├── " if levels[-1] else "└── " print(f"{connection_padding[:-4]}{assembly_padding}{root}") """ Recursively print child components. """ - for i, assembly in enumerate(root.assemblies): - is_last = i == len(root.assemblies) - 1 and len(root.components) == 0 + for i, assembly in enumerate(root._assemblies): + is_last = i == len(root._assemblies) - 1 and len(root._components) == 0 print_assembly_tree(assembly, [*levels, not is_last]) """ Print the components. """ - for i, component in enumerate(root.components): - component_padding = "├── " if i < len(root.components) - 1 else "└── " + for i, component in enumerate(root._components): + component_padding = "├── " if i < len(root._components) - 1 else "└── " print(f"{connection_padding}{component_padding}{component}") @@ -482,11 +578,11 @@ def correct_aggregation_hierarchy(root: LegoAssembly, strict: bool = False): higher_level = operator.le if strict: higher_level = operator.lt - for component in root.components: - if not higher_level(root.layer.value, component.layer.value): + for component in root._components: + if not higher_level(root._layer.value, component.layer.value): return False - for assembly in root.assemblies: - if not higher_level(root.layer.value, assembly.layer.value): + for assembly in root._assemblies: + if not higher_level(root._layer.value, assembly._layer.value): return False if not correct_aggregation_hierarchy(assembly, strict): return False @@ -498,12 +594,12 @@ class KPIEncoder(json.JSONEncoder): JSON encoder that handles special class types for KPI serialization. """ - def default(self, o): + def default(self, o: Any): """ Overrides default method to handle special conversion cases. Args: - o : Object to be converted. + o (Any): Object to be converted. Returns: Converted object or super method if no applicable case is found.