Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • philip.mahouttchi-hosseini/quality-kpi-final
  • boris.dasgupta/quality-kpi
  • hani.husseini/quality-kpi
  • tolga.guelec/quality-kpi
  • eva.schlott/quality-kpi
  • artashes.manukyan/quality-kpi
  • reinhart.tjahjadi/quality-kpi-reinhart-jason-tjahjadi
  • sotiris.giovannis/quality-kpi-giovanns
  • luis.gleich/quality-kpi
  • julius.damm/quality-kpi
  • gaganpreet.singh/quality-kpi
  • felix.fassler/aufgabe-1-felixf
  • benjamin.gross/quality-kpi
  • yixing.tao/quality-kpi-2024
  • louis.randa/quality-kpi
  • konstantinos.boiadjiev/quality-kpi
  • ammon.wuendisch/quality-kpi
  • tarish.kalra/quality-kpi
  • constantin.messingfeld/quality-kpi
  • manuel.kaster/quality-kpi
  • rafi.noordin/quality-kpi-rafinoordin
  • fst-tuda/public/lehre/quality-kpi
  • robert.schaefer1/quality-kpi
  • luca_tobias.nuecker/quality-kpi
  • calvin.weide/quality-kpi
  • maximilian.gross1/quality-kpi
  • christoph.froehlich/quality-kpi
  • young-min.kong/quality-kpi
  • thomas.gruber/quality-kpi
  • dennis.dibbern/quality-kpi
  • luca.sommer/quality-kpi
  • tushar.sharma/quality-kpi
  • philipp.schmidt2/quality-kpi
  • jakob.knoblach/quality-kpi
  • diogo.fernandes_costa/quality-kpi
  • zidane.buermann/quality-kpi
  • elias.rausch/quality-kpi
  • vincent.jestaedt/quality-kpi
  • santiago.ramirez_saldana/quality-kpi
  • jan.wech/quality-kpi
  • noah.michel1/lerneinheit-1-noah-michel
  • bastian.bickel/quality-kpi
  • linus.schuette/quality-kpi
  • henrik.buechs/quality-kpi
  • florian.weiss/quality-kpi
  • julie.kickstein/quality-kpi
  • paul.zuendel/quality-kpi
  • valentin.kaemmerer/quality-kpi
  • erwin.durasow/quality-kpi
  • david.buening/quality-kpi
  • keanu.engel/engel-keanu-auto
  • leo.mensler/quality-kpi
  • tahsin.ahmad/quality-kpi
  • moritz.roth/moritz-roth
  • jonathan.kollmann/quality-kpi
  • noah.waltmann/quality-kpi
  • daniel.burgos/quality-kpi
  • nilay.kuslugil/quality-kpi
  • yvan.ngoune/quality-kpi
  • eric.hahn/quality-kpi
  • andy.zheng/quality-kpi
  • junghyun.seo/quality-kpi
  • fabian.roesch1/quality-kpi
  • ole.quiring/quality-kpi
  • jonas.dietrich/quality-kpi
  • marius.stumpe/quality-kpi
  • maurizio.fell/quality-kpi
  • harry.kristanto/quality-kpi-prakdig-hk
  • paul.bobrinskoy/quality-kpi
  • vladimir.pascari/quality-kpi-vladimir-pascari
  • paul_konrad.braun/quality-kpi
  • leon.dawkins/quality-kpi
  • noel.schwibus/quality-kpi
  • bianca.beer/quality-kpi
  • pascal.pohlenz/quality-kpi
  • martin.cao/quality-kpi
  • malte.kramp/quality-kpi
  • jannik.hoffmann/quality-kpi
  • ishwor.kapri/quality-kpi
  • dhiraj.baral/quality-kpi
  • tobias.erdmann/quality-kpi
  • naveen.mohan/quality-kpi
  • filip.fetzer/quality-kpi
  • jakob.koch/quality-kpi
  • sascha.lamm/quality-kpi
  • lilly.laubenheimer/quality-kpi
  • amen.bouzouraa/quality-kpi
  • louis.senff/quality-kpi
  • francisco.flores/quality-kpi
  • benit.iberhysaj/quality-kpi
  • daniel.vratnjan/quality-kpi
  • henri.sprickmann/quality-kpi
  • tim.ostendorf/quality-kpi
  • maximilian.kopp1/lerneinheit-i-quality-kpi
  • zineb.karri/quality-kpi
  • bryan.lauren/quality-kpi
  • leon.herz/quality-kpi
  • zhibo.zhao/quality-kpi
  • samuel.neidert/quality-kpi
  • jiacheng.wang/quality-kpi
  • lucas.czarnecki/quality-kpi
  • lukas.ripp/quality-kpi
  • nikolai.brueckner/quality-kpi-nikolai-brueckner
  • niklas.ulmer/quality-kpi
  • lea_sophie.heering/quality-kpi
  • julia.kipp/quality-kpi
  • andres.rivera/quality-kpi
  • felix.meyners/quality-kpi
  • paul.sipp/quality-kpi
  • pascal.grym/quality-kpi
  • simon.peters/quality-kpi
  • justus.naewie/quality-kpi
  • alicia.sachs/quality-kpi
  • pascal.schraut/quality-kpi
  • jonas.dissinger/quality-kpi
  • mattheo.beyer/quality-kpi
  • adonay.johannes/quality-kpi
  • konrad.winkhaus/winkhaus-konrad-2809351-64-le-1
  • miko.boml/quality-kpi
  • bob.putz/quality-kpi
  • buesra.akkaya/quality-kpi
  • anton.littmann/quality-kpi
  • julian.volgmann/quality-kpi
  • soner.elbudak/quality-kpi
  • ilhan_mert.dumlupinar/quality-kpi
  • ibrahim.alsaleh/quality-kpi
  • amina.anizi/quality-kpi
  • Philipp.List/quality-kpi
  • greta.kirchhoff/quality-kpi
  • rowie.samuel/quality-kpi
130 results
Show changes
......@@ -2,22 +2,43 @@
File consists of several functions for the calculation rules of FAIR Quality KPIs
"""
from functions.classes import *
def test_function():
"""Test function to check module functionality"""
print("You called the test function.")
# Function to calculate KPIS that use sums
# Or function to calculate the mass with sum?
def kpi_sum(*args):
return sum(args[0])
# if arguments are handed over not as a list: sum(list(args))
def kpi_mass(system: LegoAssembly)->float:
"""
Calculates the total mass of the system
# KPI for calculating some real complicated metric
Args:
system (LegoAssembly): LegoAssembly object that represents the system
Returns:
total_mass (float): Sum of masses of all components in the system in g
Raises:
TypeError: If an argument with unsupported type is passed
(i.e. anything other than LegoAssembly).
"""
if not isinstance(system, LegoAssembly):
raise TypeError(f"Unsupported type {type(system)} passed to kpi_mass()")
total_mass = 0
for c in system.get_component_list(-1):
total_mass += c.properties["mass [g]"]
return total_mass # alternative: sum(c.properties["mass [g]"] for c in system.get_component_list(-1))
# Add new functions for calculating metrics
if __name__ == "__main__":
"""
Function to inform that functions in this module is
intended for import not usage as script
"""
print(
"This script contains functions for calculating the FAIR Quality KPIs. It is not to be executed independently."
"This script contains functions for calculating the FAIR Quality KPIs."
"It is not to be executed independently."
)
pass
"""
File consists of several classes for the different elements of a device.
File consists of several classes to model elements and assembly
layers of a composed device.
"""
from __future__ import annotations
from enum import Enum, auto
import uuid
from typing import List, Dict, Optional
import json
# TODO
# - Docstrings
# - Beschreibung von Teilen (-> properties) -> Raus aus dem Konstruktor rein in ein dict. (Deep-Copy)
# - Erstmal als Shallow Copy umgesetzt, wir verwenden momentan keine nested dicts
# - Deep Copy erstmal beibehalten auf branch dev-bh
# - Minimalbeispiel für KPIs -> halb umgesetzt -> mit get_components veranschaulichen
# - Änderungen an Beispiel umsetzen
# -> Integriere AggregationLayer und die Funktionen in die Klassen (evtl. per Vererbung?) -> Nä Semester
# - 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
import operator
from enum import Enum, auto
from typing import Any, Optional
from copy import deepcopy
class AggregationLayer(Enum):
"""Describes the level of aggregation for the objects LegoComponent and LegoAssembly
and provides the 4 applicable layers.
"""
SYSTEM = auto()
ASSEMBLY = auto()
SUBASSEMBLY = auto()
......@@ -30,82 +24,278 @@ class AggregationLayer(Enum):
class LegoComponent:
def __init__(self, label: Optional[str] = None, datasheet: Optional[dict] = None, *more_properties: dict, **kwargs) -> None:
self.uuid: uuid.UUID = uuid.uuid4()
self.parent: None | LegoAssembly = None
"""Information storage for a single Lego component.
Attributes
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 is
contained.
to_dict(): Returns the current instance represented as a dictionary.
"""
def __init__(
self,
label: Optional[str] = None,
datasheet: Optional[dict[str, Any]] = None,
*more_properties: dict,
**kwargs,
) -> None:
"""Create a LegoComponent object.
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 (dict[str, Any])
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.
"""
self._uuid: uuid.UUID = uuid.uuid4()
self._parent: None | LegoAssembly = None
self.layer: AggregationLayer = AggregationLayer.COMPONENT
self.properties: dict = {}
if label is not None:
self.properties['label'] = label
self.properties["label"] = label
if datasheet is not None:
self.properties.update(datasheet)
self.properties.update(deepcopy(datasheet))
for prop in more_properties:
if isinstance(prop, dict):
self.properties.update(prop)
self.properties.update(deepcopy(prop))
else:
raise ValueError(f"Unexpected argument type: {type(more_properties)}")
for key, value in kwargs.items():
self.properties[key] = value
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.
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.
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, self.properties)
clone.properties['label'] = new_label
new_label = self.properties["label"]
clone = LegoComponent(None, None, deepcopy(self.properties))
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):
if self.parent is None:
"""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.
"""
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:
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[str, Any]: A dictionary representation of the object.
"""
dict_ = {
"uuid": self.uuid,
"uuid": self._uuid,
"properties": self.properties,
"layer": self.layer,
}
return {"component": dict_}
# TODO good string representation
def __str__(self):
return self.__repr__()
return (
f"Item(id={self.uuid}, item_number={self.lego_id}, "
f"mass={self.mass}, delivery_time={self.delivery_time}, "
f"parent_id={self.parent})"
)
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.
"""
# 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
# TODO good repr representation
def __repr__(self):
return f"LegoComponent {self.properties['label']} [{self.uuid}]"
"""Create a machine-readable representation of the instance.
Returns:
str: A string representing the LegoComponent instance.
"""
label_str = ""
if self.properties["label"] is not None:
label_str = f"label='{self.properties['label']}', "
property_str = ", ".join(
[
f"'{k}': '{v}'" if isinstance(v, str) else f"'{k}': {v}"
for k, v in self.properties.items()
if not k == "label"
]
)
property_str = f"**{{{property_str}}}" if property_str else property_str
return f"LegoComponent({label_str}{property_str})"
def __str__(self):
"""Handle the conversion of LegoComponent objects to str objects.
Returns:
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}]"
class LegoAssembly:
def __init__(self, layer: AggregationLayer, label: Optional[str] = None, *properties: dict , **kwargs) -> None:
self.uuid: uuid.UUID = uuid.uuid4()
self.parent: None | LegoAssembly = None
"""
Represents a Lego assembly that can contain Lego Components and subassemblies.
Attributes:
uuid (uuid.UUID): The unique ID of the assembly.
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.
"""
def __init__(
self,
layer: AggregationLayer,
label: Optional[str] = None,
*properties: dict,
**kwargs,
) -> None:
"""
Initializes a new LegoAssembly instance.
Args:
layer (AggregationLayer): The aggregation layer of the assembly.
label (Optional[str], optional): Optional label for the assembly.
Defaults to None.
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.properties: dict = {}
self.layer: AggregationLayer = layer
self._layer: AggregationLayer = layer
if label is not None:
self.properties['label'] = label
self.properties["label"] = label
for prop in properties:
if isinstance(prop, dict):
self.properties.update(prop)
self.properties.update(deepcopy(prop))
else:
raise ValueError(f"Unexpected argument type: {type(properties)}")
for key, value in kwargs.items():
self.properties[key] = value
self.components: List[LegoComponent] = []
self.assemblies: List[LegoAssembly] = []
def add_component(self, component: LegoComponent | List[LegoComponent]) -> None:
self.properties[key] = deepcopy(value)
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:
"""
Adds a Lego component to the current assembly.
Use add() to handle both components and assemblies.
Args:
component (LegoComponent or list[LegoComponent]):
The component or list of components to add.
Raises:
TypeError: If the argument is not a LegoComponent instance
or a list of LegoComponent instances.
AssertionError: If the component or a component with the same UUID
is already contained in the assembly or any of its child assemblies.
"""
if isinstance(component, list):
for c in component:
self.add_component(c)
......@@ -117,15 +307,30 @@ 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 the component with ID "
f"{component.uuid}."
f"This assembly or a subassembly already contains "
f"the component with ID "
f"{component._uuid}."
)
component.parent = self
self.components.append(component)
def add_assembly(self, assembly: LegoAssembly | List[LegoAssembly]) -> None:
component._parent = self
self._components.append(component)
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]):
The subassembly or list of subassemblies to add.
Raises:
TypeError: If the argument is not a LegoAssembly instance
or a list of LegoAssembly instances.
AssertionError: If the subassembly or a subassembly with the same UUID
is already contained in the assembly or any of its child assemblies.
"""
if isinstance(assembly, list):
for a in assembly:
self.add_assembly(a)
......@@ -137,15 +342,29 @@ 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 the assembly with ID "
f"{assembly.uuid}."
f"This assembly or a subassembly already contains "
f"the assembly with ID {assembly._uuid}."
)
assembly.parent = self
self.assemblies.append(assembly)
def add(self, part: LegoAssembly | LegoComponent | List[LegoAssembly | LegoComponent]) -> None:
assembly._parent = self
self._assemblies.append(assembly)
def add(
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]):
The part or parts to add.
Raises:
TypeError: If the argument is not a LegoAssembly instance or a LegoComponent
instance, or a (mixed) list of them.
"""
if isinstance(part, LegoComponent):
self.add_component(part)
elif isinstance(part, LegoAssembly):
......@@ -155,93 +374,239 @@ class LegoAssembly:
self.add(p)
else:
raise TypeError(
f"Argument should be of types {LegoAssembly.__name__}, {LegoComponent.__name__} or a list of them, "
f"got {type(part).__name__} instead."
f"Argument should be of types {LegoAssembly.__name__}, "
f"{LegoComponent.__name__} or a list of them. "
f"Got {type(part).__name__} instead."
)
def children(self) -> Dict[str, List[LegoComponent] | List[LegoAssembly]]:
return {"components": self.components, "assemblies": self.assemblies}
def get_component_list(self, max_depth: int = -1) -> List[LegoComponent]:
def children(self) -> dict[str, list[LegoComponent] | list[LegoAssembly]]:
"""
Returns a dictionary of the assembly's children (components and
sub-assemblies), sorted by category.
Returns:
A dictionary with two keys - "components" and "assemblies" - and
corresponding lists of LegoComponents and LegoAssemblies as values.
"""
return {"components": self._components, "assemblies": self._assemblies}
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 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:
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
searching up the tree until the top-most parent (the root) is found.
Returns:
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):
# check component ids
component_ids = list(map(lambda c: c.uuid, self.components))
"""
Recursively searches through the assembly and component
tree to determine whether the specified UUID exists anywhere
within it.
Args:
uuid_: A UUID object representing the ID to search for.
Returns:
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))
if uuid_ in component_ids:
return True
# check assembly ids
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
# recursively check assemblies
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.
Returns:
A dictionary representation of the current assembly, including all
of its component and sub-assembly children.
"""
dict_ = {
"uuid": self.uuid,
"uuid": self._uuid,
"properties": self.properties,
"layer": self.layer,
"layer": self._layer,
}
# store components
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_}
# TODO find good string representation
def __repr__(self):
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
all of its component and sub-assembly children. The assigned uuid changes.
Optionally a new label can be passed.
def clone(self, label: str = None) -> LegoAssembly:
Args:
label: The label (name) for the cloned assembly. If none is passed,
uses the same label as the original assembly.
Returns:
The cloned LegoAssembly instance.
"""
if label is None:
label = self.label
clone = LegoAssembly(label, copy.deepcopy(self.properties), self.layer)
for component in self.components:
label = self.properties["label"]
clone = LegoAssembly(self._layer, None, deepcopy(self.properties))
clone.properties["label"] = label
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 print_assembly_tree(root, level=0, is_last=False):
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):
"""Create a machine-readable representation of the instance.
Returns:
str: A string representing the LegoAssembly instance.
"""
label_str = ""
if self.properties["label"] is not None:
label_str = f"label='{self.properties['label']}', "
layer_str = f"layer={self._layer}, "
property_str = ", ".join(
[
f"'{k}': '{v}'" if isinstance(v, str) else f"'{k}': {v}"
for k, v in self.properties.items()
if not k == "label"
]
)
property_str = f"**{{{property_str}}}" if property_str else property_str
return f"LegoAssembly({label_str}{layer_str}{property_str})"
def __str__(self):
"""Handle the conversion of LegoComponent objects to str objects.
Returns:
str: A string converted from the LegoComponent instance.
"""
if self.properties["label"] is None:
return f"LegoAssembly [{self._uuid}]"
return f"LegoAssembly {self.properties['label']} [{self._uuid}]"
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 None.
"""
if not isinstance(root, LegoAssembly):
raise TypeError(
f"Argument should be of type {LegoAssembly.__name__}, "
f"got {type(root).__name__} instead."
)
# print component
if levels is None:
levels = []
connection_padding = "".join(map(lambda draw: "" if draw else " ", levels))
assembly_padding = ""
if level > 0:
assembly_padding += "" * (level - 1)
if is_last:
assembly_padding += "└── "
else:
assembly_padding += "├── "
print(f"{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
print_assembly_tree(assembly, level + 1, is_last_)
# print items
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}")
if len(levels) > 0:
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
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 "└── "
print(f"{connection_padding}{component_padding}{component}")
def correct_aggregation_hierarchy(root: LegoAssembly, strict: bool = False):
"""
Recursively checks whether the aggregation hierarchy from `root` is correct.
Args:
root (LegoAssembly): The root of the assembly tree.
strict (bool): If True, the function will return False if any assembly
or component with a layer level equal to root's is found.
Defaults to False.
Returns:
True if the aggregation hierarchy is correct. False otherwise.
"""
if not isinstance(root, LegoAssembly):
raise TypeError(
f"Argument should be of type {LegoAssembly.__name__}, "
......@@ -250,11 +615,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
......@@ -262,9 +627,22 @@ def correct_aggregation_hierarchy(root: LegoAssembly, strict: bool = False):
class KPIEncoder(json.JSONEncoder):
def default(self, o):
"""
JSON encoder that handles special class types for KPI serialization.
"""
def default(self, o: Any):
"""
Overrides default method to handle special conversion cases.
Args:
o (Any): Object to be converted.
Returns:
Converted object or super method if no applicable case is found.
"""
if isinstance(o, uuid.UUID):
return "kpi-" + str(o)
if isinstance(o, (AggregationLayer)):
return "kpi-" + o.properties.label
return super().default(o)
\ No newline at end of file
return "kpi-" + o.name
return super().default(o)
"""
FAIR Quality KPIs
"""
def main():
print("Hello ;-)")
pass
if __name__ == "__main__":
main()
%% Cell type:markdown id:c9328cd1 tags:
# FAIRe Qualitäts-KPIs
%% Cell type:markdown id:2ee8746d tags:
## Einführung
FAIRe Qualitäts-KPIs schaffen eine transparente und nachvollziehbare Entscheidungsgrundlage. In dieser Lerneinheit sollen sie die Methodik erlernen, indem sie LEGO-Autos zusammenbauen, deren Quality KPIs bestimmen und miteinander vergleichen.
### Werkzeug für den Zusammenbau
Der Zusammenbau der Autos erfolgt virtuell. Als Werkzeug steht Ihnen das Modul `classes` zur Verfügung. In diesem sind unterschiedliche Klassen und Methoden implementiert (Objektorientierung). Sie erzeugen jeweils Objekte der Klassen. Ein Objekt ist dabei die virtuelle Abbildung eines realen Bauteiles. Die Eigenschaften des Bauteiles werden über die Eigenschaften des Objektes abgebildet.
### Berechnung der Quality KPIs
KPIs (Key Performance Indikatoren) sind Kenngrößen des Systems. Sie werden über Berechnungsvorschriften bestimmt. Diese Berechnungsvorschriften sind von Ihnen als python-Funktionen im Modul `calculation_rules` zu implementieren.
### Datenblätter
Für den Zusammenbau stehen Ihnen verschiedene Bauteile in unterschiedlichen Ausführungen zur Verfügung. Sie nutzen die Datenblätter, die als `json-Dateien` zur Verfügung gestellt werden, um Ihre Autos zusammenzubauen.
%% Cell type:markdown id:1c702114 tags:
## Eigene Module und Minimalbeispiel
Für die Ausarbeitung nutzen wir zwei eigene Module (Python-Dateien), die im Ordner `functions` gespeichert sind.
Das Modul `calculation_rules` erweitern Sie während der Ausarbeitung. Um die Änderungen zu nutzen, müssen Sie das Notebook neu starten.
Im Modul `classes` befinden sich die komplette Klassen und Funktionen zur Verwendung.
Mit einem Minimalbeispiel wird Ihnen gezeigt, wie sie die Module nutzen.
%% Cell type:markdown id:670118b3 tags:
#### Versuchsaufbau:
Die folgende Abbildung zeigt den Versuchsaufbau des Minimalbeispiels in LeoCAD. Die transparenten Bauteile sind dabei nicht Teil des hier erläuterten Zusammenbaus, können aber mit dem zur Verfügung gestellten Baukasten ergänzt werden:
![Minimalbeispiel aus LeoCAD](figures/leocad_screenshot_cut.png)
%% Cell type:markdown id:6d3310be tags:
### Modul classes
Enthält `LegoComponent, LegoAssembly, AggregationLayer, KPIEncoder` und die Funktion `print_assembly_tree`.
`LegoComponent` bildet einzelne Komponenten ab, während `LegoAssembly` zusammengesetzte Aggregationsebenen abdeckt, also Bauteil, Baugruppe und System. Zur Unterscheidung dient die Klasse `AggregationLayer`, diese ist für `LegoComponent` immer `Component` (Komponente), muss für `LegoAssembly` entsprechend auf `SYSTEM` (System) , `ASSEMBLY` (Baugruppe) oder `SUBASSEMBLY` (Bauteil) gesetzt werden.
Wir bauen aus Achse, Rahmen und Reifen einen Tretroller zusammen.
%% Cell type:code id:b2778dee tags:
``` python
# import modules
import json
import pprint
from functions import calculation_rules
# Importing all modules one by one to provide an overview
# The next commented line would provide the same result in one line
# from functions.classes import *
from functions.classes import LegoComponent
from functions.classes import LegoAssembly
from functions.classes import AggregationLayer
from functions.classes import KPIEncoder
from functions.classes import print_assembly_tree
# When you are writing code yourself later, you might want to copy
# these imports to avoid rerunning the full notebook after restart
```
%% Cell type:code id:0b1f9aff tags:
``` python
# Create the wheels and axles as single components first
# Look up the specific item you want from the provided json files,
# we can load the full file into a dict
with open("datasheets/axles.json") as json_file:
axles = json.load(json_file)
# Pick a specific axle via its 'item number'
print(axles["32073"])
# Create the component with the dict:
front_axle = LegoComponent("front axle", axles["32073"])
# Both label and the data dict are optional parameters.
# You can view, add or edit properties:
print(front_axle.properties["label"])
front_axle.properties["color"] = "light bluish grey"
# Create the second axle
back_axle = LegoComponent()
back_axle.properties["label"] = "back axle"
back_axle.properties.update(axles["32073"])
# Do not use = here, otherwise you'd overwrite existing properties.
# Instead use update to have all entries added to properties
back_axle.properties["color"] = "light bluish grey"
# Viewing dicts in one line is not easy to read,
# a better output comes with pretty print (pprint):
pprint.pprint(back_axle.properties)
```
%% Output
{'item number': 32073, 'item description': 'Axle 5 studs', 'category': 'axle', 'price [Euro]': 0.001, 'mass [g]': 0.66, 'environmental impact [kg CO2e /kg]': 11.03, 'delivery time [days]': 3, 'data source': 'https://www.bricklink.com/v2/catalog/catalogitem.page?P=32073', 'dimension [studs]': 5}
front axle
{'category': 'axle',
'color': 'grey',
'data source': 'https://www.bricklink.com/v2/catalog/catalogitem.page?P=32073',
'delivery time [days]': 3,
'dimension [studs]': 5,
'environmental impact [kg CO2e /kg]': 11.03,
'item description': 'Axle 5 studs',
'item number': 32073,
'label': 'back axle',
'mass [g]': 0.66,
'price [Euro]': 0.001}
%% Cell type:code id:b4a8e8c8 tags:
``` python
# Now wheels
# A wheel consists of a rim (Felge) and a tire (Reifen)
with open("datasheets/rims.json") as json_file:
rims = json.load(json_file)
with open("datasheets/tires.json") as json_file:
tires = json.load(json_file)
# Adding the color here already as dict, and the surface as key-value argument.
# Multiple of both parameters are supported, but only in this order.
# front_wheel = LegoComponent(
# 'front wheel', wheels['2903c02'], {'color':'yellow'},winter='true')
front_rim = LegoComponent("front rim", rims["56904"], surface="rough", paint="glossy")
front_tire = LegoComponent("front tire", tires["30699"], winter=True)
pprint.pprint(front_rim.properties)
# We included a clone function to both Lego classes, so you can easily
# create duplicate objects. Passing the new label is optional
back_rim = front_rim.clone("back rim")
back_tire = front_tire.clone("back tire")
```
%% Output
{'category': 'rim',
'data source': 'https://www.bricklink.com/v2/catalog/catalogitem.page?P=56904',
'delivery time [days]': 5,
'diameter [mm]': 30.0,
'environmental impact [kg CO2e /kg]': 32.06,
'item description': 'Wheel 30mm D. x 14mm',
'item number': 56904,
'label': 'front rim',
'mass [g]': 4.1,
'paint': 'glossy',
'price [Euro]': 0.01,
'related items': 30699,
'surface': 'rough'}
%% Cell type:code id:25bd06c5 tags:
``` python
# Create subassemblies and add the wheels and axles
# The AggregationLayer must be used, passing the label
# or additional properties is optional
front_wheel_assembly = LegoAssembly(
AggregationLayer.SUBASSEMBLY,
"front wheel assembly",
assembly_method="stick together like lego blocks",
)
# Add LegoComponents to the LegoAssembly, single or as list
front_wheel_assembly.add([front_rim, front_tire])
# You can access the components of an assembly like this:
print(front_wheel_assembly.components[1].properties["label"])
# Assemblies can be cloned as well (including their children),
# but don't forget to adjust labels or you might be stuck with
# a 'front wheel' in your 'back wheel assembly'
# Stick together back wheel parts
back_wheel_assembly = LegoAssembly(
AggregationLayer.SUBASSEMBLY, "back wheel assembly"
)
back_wheel_assembly.add([back_rim, back_tire])
# Now combine wheels and axles
front_axle_assembly = LegoAssembly(
AggregationLayer.ASSEMBLY, "front axle assembly"
)
front_axle_assembly.add([front_wheel_assembly, front_axle])
back_axle_assembly = LegoAssembly(
AggregationLayer.ASSEMBLY, "back axle assembly"
)
back_axle_assembly.add([back_wheel_assembly, back_axle])
```
%% Output
front tire
---------------------------------------------------------------------------
KeyboardInterrupt Traceback (most recent call last)
Cell In[6], line 35
30 front_axle_assembly.add([front_wheel_assembly, front_axle])
32 back_axle_assembly = LegoAssembly(
33 AggregationLayer.ASSEMBLY, "back axle assembly"
34 )
---> 35 back_axle_assembly.add([back_axle_assembly, back_axle])
File c:\Users\Lamm\Documents\02_Areas\Praktikum_Digitalisierung\01_quality-kpi_dev\functions\classes.py:374, in LegoAssembly.add(self, part)
372 elif isinstance(part, list):
373 for p in part:
--> 374 self.add(p)
375 else:
376 raise TypeError(
377 f"Argument should be of types {LegoAssembly.__name__}, "
378 f"{LegoComponent.__name__} or a list of them. "
379 f"Got {type(part).__name__} instead."
380 )
File c:\Users\Lamm\Documents\02_Areas\Praktikum_Digitalisierung\01_quality-kpi_dev\functions\classes.py:369, in LegoAssembly.add(self, part)
356 """
357 Adds either a Lego component, a subassembly or a (mixed) list of them to the
358 current assembly. Uses internal functions add_component() and add_assembly().
(...)
366 instance, or a (mixed) list of them.
367 """
368 if isinstance(part, LegoComponent):
--> 369 self.add_component(part)
370 elif isinstance(part, LegoAssembly):
371 self.add_assembly(part)
File c:\Users\Lamm\Documents\02_Areas\Praktikum_Digitalisierung\01_quality-kpi_dev\functions\classes.py:310, in LegoAssembly.add_component(self, component)
304 if not isinstance(component, LegoComponent):
305 raise TypeError(
306 f"Argument should be of type {LegoComponent.__name__}, "
307 f"got {type(component).__name__} instead."
308 )
--> 310 if self.get_root_assembly().contains_uuid(component._uuid):
311 raise AssertionError(
312 f"This assembly or a subassembly already contains "
313 f"the component with ID "
314 f"{component._uuid}."
315 )
316 component._parent = self
File c:\Users\Lamm\Documents\02_Areas\Praktikum_Digitalisierung\01_quality-kpi_dev\functions\classes.py:439, in LegoAssembly.get_root_assembly(self)
431 """
432 Returns the root LegoAssembly of the current assembly by recursively
433 searching up the tree until the top-most parent (the root) is found.
(...)
436 The root LegoAssembly of the current LegoAssembly instance.
437 """
438 current_assembly = self
--> 439 while current_assembly._parent is not None:
440 current_assembly = current_assembly._parent
441 return current_assembly
KeyboardInterrupt:
%% Cell type:code id:2b6648e1 tags:
``` python
# Create frame component and assemble the system
with open("datasheets/frame.json") as json_file:
frame = json.load(json_file)
scooter_frame = LegoComponent("scooter frame", frame["3703"], {"color": "red"})
# The scooter is our system level assembly, you can choose the AggregationLayer
# But the hiercarchy (SYSTEM > ASSEMBLY > SUBASSEMBLY) must be in order.
# Components can be added to all LegoAssembly objects
scooter = LegoAssembly(
AggregationLayer.SYSTEM,
"scooter",
manufacturer="FST",
comment="Faster! Harder! Scooter!",
)
# add frame and subassemblies
scooter.add([scooter_frame, front_wheel_assembly, back_wheel_assembly])
```
%% Cell type:code id:71324895 tags:
``` python
# Look at the assembly
# We can get all LegoComponents from this assembly.
# If parameter 'max_depth' is set to 0, only direct children will be listed:
print(scooter.get_component_list(max_depth=0))
# If it is set to -1 (default), all LegoComponents are listed:
print(scooter.get_component_list())
```
%% Output
[LegoComponent scooter frame [a90b291e-696c-41c7-b9b8-43d34724b460]]
[LegoComponent scooter frame [a90b291e-696c-41c7-b9b8-43d34724b460], LegoComponent front wheel [b03ce5b5-9c6c-4315-a67b-902c729c3d7b], LegoComponent front axle [64771daf-733a-4a2a-8520-9fa94259e50e], LegoComponent back wheel [8fcb7db9-6fec-4e70-b274-0c4fe403550b], LegoComponent back axle [2ce57b96-64b8-48b1-aac8-b0731adaa93b]]
%% Cell type:markdown id:001f1c77 tags:
### Modul calculation_rules
Um für unser System "Tretroller" einen KPI zu erzeugen, wird die Masse des Systems betrachtet. Die Masse der einzelnen Komponenten ist in den Datenblättern unter `mass [g]` enthalten.
%% Cell type:code id:7b60d0fb tags:
``` python
# test the import
calculation_rules.test_function()
```
%% Output
You called the test function.
%% Cell type:code id:fe4edad6 tags:
``` python
total_mass = calculation_rules.kpi_mass(scooter)
print("Gesamtmasse: ", total_mass, "g")
# Add KPI to system
scooter.properties["mass [g]"] = total_mass
# We can also add the mass to the subassemblies.
# children() returns a dict with a list of added
# components and assemblies.
for a in scooter.children()["assemblies"]:
a_mass = 0
for c in a.get_component_list(-1):
a_mass += c.properties["mass [g]"]
a.properties["mass [g]"] = a_mass
```
%% Output
Gesamtmasse: 67.19 g
%% Cell type:code id:c26b36c8 tags:
``` python
scooter.get_component_list(-1)
```
%% Output
[LegoComponent scooter frame [a90b291e-696c-41c7-b9b8-43d34724b460],
LegoComponent front wheel [b03ce5b5-9c6c-4315-a67b-902c729c3d7b],
LegoComponent front axle [64771daf-733a-4a2a-8520-9fa94259e50e],
LegoComponent back wheel [8fcb7db9-6fec-4e70-b274-0c4fe403550b],
LegoComponent back axle [2ce57b96-64b8-48b1-aac8-b0731adaa93b]]
%% Cell type:code id:4d56419f tags:
``` python
# Look at the full assembly with KPI
# Print the full assembly with all levels
print_assembly_tree(scooter)
```
%% Output
LegoAssembly scooter [86fdda66-adb2-48c0-b6f8-f6c59d488662]
├── LegoAssembly front wheel assembly [e02a87d2-cb0b-4695-b237-9ca17f9611da]
│ ├── LegoComponent front wheel [b03ce5b5-9c6c-4315-a67b-902c729c3d7b]
│ └── LegoComponent front axle [64771daf-733a-4a2a-8520-9fa94259e50e]
├── LegoAssembly back wheel assembly [db5db4c5-36c3-4b90-88eb-299c46a30049]
│ ├── LegoComponent back wheel [8fcb7db9-6fec-4e70-b274-0c4fe403550b]
│ └── LegoComponent back axle [2ce57b96-64b8-48b1-aac8-b0731adaa93b]
└── LegoComponent scooter frame [a90b291e-696c-41c7-b9b8-43d34724b460]
%% Cell type:code id:b31416d3 tags:
``` python
# Dump to json file with to_dict() and KPIEncoder
with open("scooter.json", "w") as fp:
json.dump(scooter.to_dict(), fp, cls=KPIEncoder, indent=4)
# full view is too big for Juypter (try it)
# pprint.pprint(scooter.to_dict())
```
%% Cell type:markdown id:53793ae8 tags:
In dieser exportierten json-Datei ('scooter.json') sind die Werte maschinen- und menschenlesbar hinterlegt.
Zusammen mit der Berechnungsvorschrift in `calculation_rules` ist auch die Entstehung des KPI nachvollziehbar und wiederverwendbar dokumentiert und damit 'FAIR'.
%% Cell type:markdown id:92c77051 tags:
#### Zusätzliche Details
%% Cell type:code id:b91fed73 tags:
``` python
# Each child knows its parent
first_child = scooter.children()["assemblies"][0]
print("Child:", first_child)
print("Parent:", first_child.parent)
# Also we can access the "top" parent
latest_child = first_child.children()["components"][0]
print("Top parent: ", latest_child.get_root_assembly())
# Each part created has a unique identifier (the long number in [])
# Don't try to add the identical part again.
```
%% Output
Child: LegoAssembly front wheel assembly [e02a87d2-cb0b-4695-b237-9ca17f9611da]
Parent: LegoAssembly scooter [86fdda66-adb2-48c0-b6f8-f6c59d488662]
Top parent: LegoAssembly scooter [86fdda66-adb2-48c0-b6f8-f6c59d488662]
%% Cell type:code id: tags:
``` python
# import functions.lego_classes as lego_classes
from functions.classes import LegoItem
from functions.classes import LegoComponent
```
%% Cell type:code id: tags:
``` python
# Test manually creating some item and components
item1 = LegoItem(1,0.10,10)
item2 = LegoItem(3,0.30,30)
item3 = LegoItem(2, 0.2,20)
component1 = LegoComponent()
# component1.add_item(item1)
component2 = LegoComponent(item2)
componentall = LegoComponent([component1,component2]) # This will be saved as items for some reasaon
# component3 = LegoComponent(item3,component1)
# component3.add(item2) # Can't really use an item twice
# component3.add(component2)
print(componentall.items)
```
%% Output
[<functions.lego_classes.LegoComponent object at 0x000002720F4BF7F0>, <functions.lego_classes.LegoComponent object at 0x000002720F4BF790>]
%% Cell type:code id: tags:
``` python
```
%% Cell type:code id: tags:
``` python
## Let's test the json stuff
import json
# import tkinter as tk
```
%% Cell type:code id: tags:
``` python
# First level list handling
def read_json(file_path):
with open(file_path, 'r') as f:
data = json.load(f)
return data
json_list_data = read_json('example_item_data_list.json')
# that produces a list, we'd prefer a dict
# Clever way to just create an item for each listed element
def create_all_items(data):
items = []
for item_data in data:
item_number = item_data.get('item_number')
mass = item_data.get('mass')
delivery_time = item_data.get('delivery_time')
# specific_data = item_data.get('specific_data', {})
item = LegoItem(item_number, mass, delivery_time)
items.append(item)
return items
all_items = create_all_items(json_list_data)
# How to get entries for a specific item
def get_item_by_number(data, item_number):
datasheet = [item for item in data if item['item_number' ] == item_number]
return datasheet
sheet1 = get_item_by_number(json_list_data, 2) # this is a list? meh
```
%% Cell type:code id: tags:
``` python
# vs first level dict handling
with open('example_item_data_dict.json') as json_file:
json_dict = json.load(json_file)
print(type(json_dict))
print(json_dict["1"])
sheet2_from_dict = json_dict["2"]
# Create an item for this:
def create_item_from_sheet(dict):
item_number = dict["item_number"]
mass = dict["mass"]
delivery_time =["delivery_time"]
item = LegoItem(item_number, mass, delivery_time)
return item
itemfromdict = create_item_from_sheet(sheet2_from_dict)
```
%% Output
<class 'dict'>
{'item_number': 1, 'mass': 10, 'delivery_time': 1}
# import standard libraries
import json
import pprint
# import classes from the classes module in functions package
from functions.classes import LegoComponent
from functions.classes import LegoAssembly
from functions.classes import ComponentCategory
from functions.classes import AggregationLayer
from functions.classes import KPIEncoder
from functions.classes import print_assembly_tree
from functions.calculation_rules import kpi_sum
# Test manually creating some assemblies and components
battery = LegoComponent("nice battery", ComponentCategory.BATTERY, "bat42", 1, 2, 3)
motor = LegoComponent("motor goes brrr", ComponentCategory.MOTOR, "motor", 1, 2, 3)
wheel = LegoComponent("much round wheel", ComponentCategory.WHEEL, "round", 1, 2, 3)
# Create subassemblies and combine to assembly (currently no parts present for this)
chassis = LegoAssembly("Chassis", AggregationLayer.ASSEMBLY)
door1 = LegoAssembly("Door 1", AggregationLayer.SUBASSEMBLY)
door2 = LegoAssembly("Door 2", AggregationLayer.SUBASSEMBLY)
chassis.add_assembly(door1)
chassis.add_assembly(door2)
chassis.properties
engine = LegoAssembly("Engine", AggregationLayer.ASSEMBLY)
# Showcase cloning - one motor for each axis?
engine.add_component(motor.clone())
fuel_tank = LegoAssembly("Fuel Tank", AggregationLayer.ASSEMBLY)
fuel_tank.add_component(battery.clone())
wheels = LegoAssembly("Wheels", AggregationLayer.ASSEMBLY)
for _ in range(4):
wheels.add_component(wheel.clone())
# Create and assemble the system level car
car = LegoAssembly("Car", AggregationLayer.SYSTEM)
car.add_assembly(chassis)
car.add_assembly(engine)
car.add_assembly(fuel_tank)
car.add_assembly(wheels)
## Printing
print_assembly_tree(car)
# Dump to json file
with open("car.json", "w") as fp:
json.dump(car.to_dict(), fp, cls=KPIEncoder)
pprint.pprint(car.to_dict())
pass
## Create components from datasheet
# Need to run the script from datasheets folder first
# Test loading dicts from datasheet jsons
with open('datasheets/Achsen.json') as json_file:
Achsen = json.load(json_file)
print((Achsen["Achse 5 studs"]))
# This is hard to read, prettier please!
pprint.pprint(Achsen)
# Okay I'll use the 5 stud one
Achse5 = Achsen["Achse 5 studs"] # I'd prefer to call it via "Designnummer"
print(Achsen["Achse 5 studs"]["Designnummer"])
with open('datasheets/Räder.json') as json_file:
Raeder = json.load(json_file)
# Get me the expensive one
Radpreis = []
for key in Raeder: # a list would work easier here than a dict
Radpreis.append(Raeder[key]["Preis [Euro]"])
Radpreis = max(Radpreis)
for key in Raeder:
if Raeder[key]["Preis [Euro]"] == Radpreis:
TeuresRad=Raeder[key]
# Also need a frame
with open('datasheets/Gestell.json') as json_file:
Gestelle = json.load(json_file)
Gestellbeschreibung = "Technic, Brick 1 x 8 with Holes"
Gestell = Gestelle[Gestellbeschreibung]
# Create Components from these items
# LegoComponent(name: str, category: ComponentCategory, lego_id: str, cost: float,
# mass: float, delivery_time: int, layer: AggregationLayer = AggregationLayer.COMPONENT, **properties)
scooterframe = LegoComponent("running board", ComponentCategory.FRAME,
Gestell["Designnummer"],
Gestell["Preis [Euro]"], Gestell["Gewicht [g]"],
0)
scooterwheel = LegoComponent("Vorderrad", ComponentCategory.WHEEL,
TeuresRad["Designnummer"],
TeuresRad["Preis [Euro]"], TeuresRad["Gewicht [g]"],
0)
# TODO component with copy of dict from json
# Cloning is necessary because each lego object gets a unique id so you can't use the same part twice
scooterwheel2 = scooterwheel.clone()
scooterwheel2.description = "Hinterrad"
print([scooterwheel.uuid, scooterwheel2.uuid])
# Lets Assembly, first the Assembly object, directly add the frame?
scooter = LegoAssembly("my first scooter", AggregationLayer.SYSTEM)
scooter.add_component(scooterframe) # only one per call for now
scooter.add_component(scooterwheel)
scooter.add_component(scooterwheel2)
# <Assembly>.add_assembly() works in the same way
# Look at our work:
print(scooter)
print(scooter.children())
## First KPI mass should be a sum
listofmasses= []
for c in scooter.components:
listofmasses.append(c.mass)
for a in scooter.assemblies:
# only checking one layer deep here
listofmasses.append(a.mass)
# TODO example with get_component_list
print(listofmasses)
print(kpi_sum(listofmasses))
print("theend")
\ No newline at end of file