Skip to content
Snippets Groups Projects
classes.py 7.08 KiB
Newer Older
File consists of several classes for the different elements of a device.
Hock, Benedikt's avatar
Hock, Benedikt committed
from enum import Enum, auto
from typing import List, Dict
Hock, Benedikt's avatar
Hock, Benedikt committed
import json
Hock, Benedikt's avatar
Hock, Benedikt committed
# TODO
# - Docstrings
Hock, Martin's avatar
Hock, Martin committed
# - Minimalbeispiel für KPIs -> halb umgesetzt -> mit get_components veranschaulichen
# - Erlaube Clone bei Assembly (jedes child muss durch durch Klon ersetzt werden)
# - Änderungen an Beispiel umsetzen

# - Erlaube Listen bei add_component und add_assembly  (-> Nä Semester)
# - Gute String Darstellung -> Ist so schon ok bisher? -> Nä Semester
# - Export als GraphViz -> Nä Semeseter


Hock, Benedikt's avatar
Hock, Benedikt committed
class ComponentCategory(Enum):
    BATTERY = auto()
    MOTOR = auto()
    FRAME = auto()
    WHEEL = auto()
    AXLE = auto()
    GEAR = auto()


class AggregationLayer(Enum):
    SYSTEM = auto()
    ASSEMBLY = auto()
    SUBASSEMBLY = auto()
    COMPONENT = auto()


class LegoComponent:
            self,
            label: str,
            properties: dict,
            layer: AggregationLayer = AggregationLayer.COMPONENT,
Hock, Benedikt's avatar
Hock, Benedikt committed
        self.uuid: uuid.UUID = uuid.uuid4()
        self.parent: None | LegoAssembly = None
        self.label: str = label
Hock, Benedikt's avatar
Hock, Benedikt committed
        self.properties: dict = properties
        self.layer: AggregationLayer = layer
    def clone(self, new_description: str = None) -> LegoComponent:
        if new_description is None:
            new_description = self.label
        clone = LegoComponent(new_description, copy.deepcopy(self.properties), self.layer)
Hock, Benedikt's avatar
Hock, Benedikt committed
        return clone

    def get_root_assembly(self):
        if self.parent is None:
            return None
        current_assembly = self.parent
        while current_assembly.parent is not None:
            current_assembly = current_assembly.parent
        return current_assembly

    def to_dict(self) -> Dict:
        ATTRIBUTES = [
            "uuid",
            "name",
            "category",
            "lego_id",
            "cost",
            "mass",
            "delivery_time",
            "layer",
            "properties",
        ]
Hock, Benedikt's avatar
Hock, Benedikt committed
        dict_ = {}
        # store attributes
        for attr in ATTRIBUTES:
            dict_[attr] = getattr(self, attr)
Hock, Benedikt's avatar
Hock, Benedikt committed
        dict_ = {"component": dict_}
        return dict_

    # TODO good string representation
Hock, Benedikt's avatar
Hock, Benedikt committed
        return self.__repr__()
Hock, Benedikt's avatar
Hock, Benedikt committed
            f"Item(id={self.uuid}, item_number={self.lego_id}, "
            f"mass={self.mass}, delivery_time={self.delivery_time}, "
Hock, Benedikt's avatar
Hock, Benedikt committed
            f"parent_id={self.parent})"
Hock, Benedikt's avatar
Hock, Benedikt committed
    # TODO good repr representation
Hock, Benedikt's avatar
Hock, Benedikt committed
    def __repr__(self):
        return f"LegoComponent {self.label} [{self.uuid}]"
Hock, Benedikt's avatar
Hock, Benedikt committed
class LegoAssembly:
    def __init__(self, label: str, properties: dict, layer: AggregationLayer) -> None:
Hock, Benedikt's avatar
Hock, Benedikt committed
        self.uuid: uuid.UUID = uuid.uuid4()
        self.parent: None | LegoAssembly = None
        self.label: str = label
Hock, Benedikt's avatar
Hock, Benedikt committed
        self.properties: dict = properties
        self.layer: AggregationLayer = layer
Hock, Benedikt's avatar
Hock, Benedikt committed
        self.components: List[LegoComponent] = []
Hock, Benedikt's avatar
Hock, Benedikt committed
        self.assemblies: List[LegoAssembly] = []
Hock, Benedikt's avatar
Hock, Benedikt committed
    def add_component(self, component: LegoComponent) -> None:
        if not isinstance(component, LegoComponent):
            raise TypeError(
                f"Argument should be of type {LegoComponent.__name__}, "
                f"got {type(component).__name__} instead."
            )
Hock, Benedikt's avatar
Hock, Benedikt committed
        if self.get_root_assembly().contains_uuid(component.uuid):
            raise AssertionError(
                f"This assembly or a subassembly already contains the component with ID "
                f"{component.uuid}."
            )
Hock, Benedikt's avatar
Hock, Benedikt committed
        component.parent = self
Hock, Benedikt's avatar
Hock, Benedikt committed
        self.components.append(component)
Hock, Benedikt's avatar
Hock, Benedikt committed
    def add_assembly(self, assembly: LegoAssembly) -> None:
        if not isinstance(assembly, LegoAssembly):
            raise TypeError(
                f"Argument should be of type {LegoAssembly.__name__}, "
                f"got {type(assembly).__name__} instead."
            )
Hock, Benedikt's avatar
Hock, Benedikt committed
        if self.get_root_assembly().contains_uuid(assembly.uuid):
            raise AssertionError(
                f"This assembly or a subassembly already contains the assembly with ID "
                f"{assembly.uuid}."
            )
Hock, Benedikt's avatar
Hock, Benedikt committed
        assembly.parent = self
        self.assemblies.append(assembly)

    def children(self) -> Dict[str, List[LegoComponent] | List[LegoAssembly]]:
        return {"components": self.components, "assemblies": self.assemblies}
Hock, Benedikt's avatar
Hock, Benedikt committed

    def get_component_list(self, max_depth: int = -1) -> List[LegoComponent]:
        component_list = []
        component_list.extend(self.components)
        if max_depth > 0:
            for assembly in self.assemblies:
                component_list.extend(assembly.get_component_list(max_depth - 1))
        return component_list
Hock, Benedikt's avatar
Hock, Benedikt committed
    def get_root_assembly(self) -> LegoAssembly:
        current_assembly = self
        while current_assembly.parent is not None:
            current_assembly = current_assembly.parent
        return current_assembly
Hock, Benedikt's avatar
Hock, Benedikt committed
    def contains_uuid(self, uuid_: uuid.UUID):
        # check component ids
        component_ids = list(map(lambda c: c.uuid, self.components))
        if uuid_ in component_ids:
            return True
        # check assembly ids
        assembly_ids = list(map(lambda a: a.uuid, self.assemblies))
        if uuid_ in assembly_ids:
            return True
        # recursively check assemblies
        for assembly in self.assemblies:
            if assembly.contains_uuid(uuid_):
                return True
        return False

    def to_dict(self) -> Dict:
        ATTRIBUTES = ["uuid", "name", "layer", "properties"]
        dict_ = {}
        # store attributes
        for attr in ATTRIBUTES:
            dict_[attr] = getattr(self, attr)
        # store components
        dict_["components"] = [component.to_dict() for component in self.components]
        dict_["assemblies"] = [assembly.to_dict() for assembly in self.assemblies]

        return {"assembly": dict_}

    # TODO find good string representation
Hock, Benedikt's avatar
Hock, Benedikt committed
    def __repr__(self):
        return f"LegoAssembly {self.label} [{self.uuid}]"
Hock, Benedikt's avatar
Hock, Benedikt committed
def print_assembly_tree(root, level=0, is_last=False):
    # print component
Hock, Benedikt's avatar
Hock, Benedikt committed
    assembly_padding = ""
Hock, Benedikt's avatar
Hock, Benedikt committed
        assembly_padding += "" * (level - 1)
Hock, Benedikt's avatar
Hock, Benedikt committed
            assembly_padding += "└── "
Hock, Benedikt's avatar
Hock, Benedikt committed
            assembly_padding += "├── "
    print(f"{assembly_padding}{root}")
    # recursively print child components
Hock, Benedikt's avatar
Hock, Benedikt committed
    for i, assembly in enumerate(root.assemblies):
        is_last_ = i == len(root.assemblies) - 1 and len(root.components) == 0
        print_assembly_tree(assembly, level + 1, is_last_)
Hock, Benedikt's avatar
Hock, Benedikt committed
    for i, item in enumerate(root.components):
        component_padding = "" * level if not is_last else "    "
        component_padding += "├── " if i < len(root.components) - 1 else "└── "
        print(f"{component_padding}{item}")


class KPIEncoder(json.JSONEncoder):
    def default(self, o):
        if isinstance(o, uuid.UUID):
            return "kpi-" + str(o)
        if isinstance(o, (ComponentCategory, AggregationLayer)):
            return "kpi-" + o.name
        return super().default(o)