diff --git a/functions/classes.py b/functions/classes.py index 7265d9370d1af4e7dd4a270d4a12f26130a191d5..96b993eec82976ad3ce743c5c8eaf4447f0f4f2c 100644 --- a/functions/classes.py +++ b/functions/classes.py @@ -66,8 +66,8 @@ class LegoComponent: 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: @@ -82,6 +82,14 @@ 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: """Return a new instance of LegoComponent identical to the current instance. @@ -103,6 +111,23 @@ class LegoComponent: clone.properties["label"] = new_label return clone + 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. + """ + 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. @@ -114,11 +139,11 @@ class LegoComponent: 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: @@ -131,7 +156,7 @@ class LegoComponent: dict[str, Any]: A dictionary representation of the object. """ dict_ = { - "uuid": self.uuid, + "uuid": self._uuid, "properties": self.properties, "layer": self.layer, } @@ -151,7 +176,7 @@ class LegoComponent: return False if ( - self.uuid == obj.uuid + self._uuid == obj._uuid and self.layer == obj.layer and self.properties == obj.properties ): @@ -175,8 +200,8 @@ class LegoComponent: str: A string converted from the LegoComponent instance. """ if self.properties.get("label") is None: - return f"LegoComponent [{self.uuid}]" - return f"LegoComponent {self.properties['label']} [{self.uuid}]" + return f"LegoComponent [{self._uuid}]" + return f"LegoComponent {self.properties['label']} [{self._uuid}]" class LegoAssembly: @@ -209,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: @@ -222,8 +247,28 @@ 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 + + @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: """ @@ -251,14 +296,14 @@ 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: """ @@ -286,13 +331,13 @@ 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] @@ -332,7 +377,7 @@ 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]: """ @@ -341,19 +386,36 @@ class LegoAssembly: 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 @@ -363,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): @@ -380,13 +442,13 @@ 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 @@ -400,12 +462,12 @@ 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 clone(self, label: Optional[str] = None) -> LegoAssembly: @@ -423,11 +485,11 @@ 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 @@ -446,11 +508,11 @@ class LegoAssembly: return False if ( - self.uuid == obj.uuid + self._uuid == obj._uuid and self.properties == obj.properties - and self.layer == obj.layer - and self.components == obj.components - and self.assemblies == obj.assemblies + and self._layer == obj._layer + and self._components == obj._components + and self._assemblies == obj._assemblies ): return True else: @@ -460,7 +522,7 @@ class LegoAssembly: """ String representation of the object including the component label and UUID. """ - return f"LegoAssembly {self.properties['label']} [{self.uuid}]" + return f"LegoAssembly {self.properties['label']} [{self._uuid}]" def print_assembly_tree(root: LegoAssembly, levels: list[bool] = None) -> None: @@ -486,12 +548,12 @@ def print_assembly_tree(root: LegoAssembly, levels: list[bool] = None) -> 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}") @@ -516,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