diff --git a/ausarbeitung.ipynb b/ausarbeitung.ipynb index 854341402f8d55bde092a8f8a27f999393f8272c..51ae5a03012e69a4b0124bf07468ba47dd0d71aa 100644 --- a/ausarbeitung.ipynb +++ b/ausarbeitung.ipynb @@ -29,29 +29,140 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "id": "1c702114", "metadata": {}, "source": [ "## Eigene Module und Minimalbeispiel\n", - "Für die Ausarbeitung nutzen wir zwei eigene Module (Python-Skripte), die im Ordner `functions` gespeichert sind.\n", + "Für die Ausarbeitung nutzen wir zwei eigene Module (Python-Dateien), die im Ordner `functions` gespeichert sind.\n", "Das Modul `calculation_rules` erweitern Sie während der Ausarbeitung. Um die Änderungen zu nutzen, müssen Sie das Notebook neu starten.\n", + "Im Modul `classes` befinden sich die Klassen `LegoComponent, LegoAssembly, AggergationLayer, KPIEncoder` und die Funktion `print_assembly_tree`.\n", + "`LegoComponent` bildet einzelne Komponenten ab, während `LegoAssembly` zusammengesetzte Aggregationsebenen abdeckt, also Bauteil, Baugruppe und System. Zur Unterscheidung dient die Klasse Aggregationslayer, diese ist für `LegoComponent` immer `Component`, muss für `LegoAssembly` aber entsprechend auf `SYSTEM, ASSEMBLY` oder `SUBASSEMBLY` gesetzt werden.\n", "\n", - "Mit einem Minimalbeispiel wird Ihnen gezeigt, wie sie die Module nutzen." + "\n", + "Mit einem Minimalbeispiel wird Ihnen gezeigt, wie sie die Module nutzen. Dabei wird nur aus Achse, Rahmen und Reifen ein Tretroller gebaut." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "b2778dee", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'item description': 'Axle 5 studs', 'category': 'axle', 'price [Euro]': 0.001, 'mass [g]': 0.66, 'delivery time [days]': 3, 'Abmessung [studs]': 5}\n", + "front axle\n", + "{'Abmessung [studs]': 5,\n", + " 'category': 'axle',\n", + " 'color': 'grey',\n", + " 'delivery time [days]': 3,\n", + " 'item description': 'Axle 5 studs',\n", + " 'mass [g]': 0.66,\n", + " 'name': 'front axle',\n", + " 'price [Euro]': 0.001}\n" + ] + } + ], "source": [ "# import modules\n", + "import json\n", + "import pprint\n", "from functions import calculation_rules\n", "from functions import classes\n", "\n", - "# TO DO import further modules if necessary\n" + "## ## Create the wheels and axles as single components first\n", + "# Look up the specific item you want from the provided json files, we can load the full file into a dict\n", + "with open('datasheets/axles.json') as json_file:\n", + " axles = json.load(json_file)\n", + "# Pick a specific axle via its 'item number'\n", + "print(axles['32073'])\n", + "\n", + "\n", + "# Create the component with the dict:\n", + "front_axle = classes.LegoComponent('front axle',axles['32073'])\n", + "# Both name and the data dict are optional parameters. You can view, add or edit the properties just any dict:\n", + "print(front_axle.properties['name'])\n", + "front_axle.properties['color']= 'grey'\n", + "# Viewing dicts in one line is not easy to read, a better output comes with pretty print (pprint): \n", + "pprint.pprint(front_axle.properties)\n", + "\n", + "# Create the second axle\n", + "back_axle = classes.LegoComponent()\n", + "back_axle.properties['name'] = 'back axle'\n", + "back_axle.properties.update(axles['32073']) \n", + "# Do not use = here, otherwise you'd overwrite existing properties.\n", + "back_axle.properties['color'] = 'grey'\n" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "b4a8e8c8", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'category': 'wheel',\n", + " 'color': 'yellow',\n", + " 'data source': 'https://www.bricklink.com/v2/catalog/catalogitem.page?P=2903c02#T=C',\n", + " 'delivery time [days]': 5,\n", + " 'diameter [mm]': 81.6,\n", + " 'item description': 'wheel 81,6',\n", + " 'mass [g]': 30.0,\n", + " 'name': 'front wheel',\n", + " 'paint': 'glossy',\n", + " 'price [Euro]': 1.31,\n", + " 'related items': 2902,\n", + " 'surface': 'rough'}\n" + ] + } + ], + "source": [ + "\n", + "# Now wheels\n", + "with open('datasheets/wheels.json') as json_file:\n", + " wheels = json.load(json_file)\n", + "# Adding the color here already as dict, and the surface as key-value argument.\n", + "# Multiple of both are supported, but only in this \n", + "#front_wheel = classes.LegoComponent('front wheel', wheels['2903'],{'color':'yellow'},winter='true')\n", + "\n", + "front_wheel = classes.LegoComponent('front wheel', wheels['2903'],surface='rough', paint = 'glossy')\n", + "# front_\n", + "pprint.pprint(front_wheel.properties)\n", + "\n", + "# We included a clone function, so you can easily create multiple items:\n", + "back_wheel = front_wheel.clone()\n", + "\n", + "\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bd3c7a6b", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "# Also need a frame\n", + "with open('datasheets/Gestell.json') as json_file:\n", + " Gestelle = json.load(json_file)\n", + "Gestellbeschreibung = \"Technic, Brick 1 x 8 with Holes\"\n", + "Gestell = Gestelle[Gestellbeschreibung]" ] }, { @@ -352,7 +463,7 @@ "metadata": { "hide_input": false, "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": ".venv", "language": "python", "name": "python3" }, @@ -366,7 +477,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.0" + "version": "3.11.0" }, "varInspector": { "cols": { @@ -396,6 +507,11 @@ "_Feature" ], "window_display": false + }, + "vscode": { + "interpreter": { + "hash": "386d359a8531ffdc4805ead3a16e7983e89a5ab7bba0cbec0e7ad9597b7a2b64" + } } }, "nbformat": 4, diff --git a/functions/classes.py b/functions/classes.py index 81d0f8b8e8b3ec83b6d2d817473a4ead07042861..309bb8792dd98f01f96373530cdfeaa8b59738cc 100644 --- a/functions/classes.py +++ b/functions/classes.py @@ -4,7 +4,7 @@ File consists of several classes for the different elements of a device. from __future__ import annotations from enum import Enum, auto import uuid -from typing import List, Dict +from typing import List, Dict, Optional import json import copy import operator @@ -12,13 +12,27 @@ import operator # 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 # - 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 + +class ComponentCategory(Enum): + BATTERY = auto() + MOTOR = auto() + FRAME = auto() + WHEEL = auto() + AXLE = auto() + GEAR = auto() + + class AggregationLayer(Enum): SYSTEM = auto() ASSEMBLY = auto() @@ -27,22 +41,31 @@ class AggregationLayer(Enum): class LegoComponent: - def __init__( - self, - label: str, - properties: dict, - layer: AggregationLayer = AggregationLayer.COMPONENT, - ) -> None: + 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 - self.label: str = label - 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) + self.layer: AggregationLayer = AggregationLayer.COMPONENT + self.properties: dict = {} + if label is not None: + self.properties['label'] = label + if datasheet is not None: + self.properties.update(datasheet) + self.properties[] + for prop in more_properties: + if isinstance(prop, dict): + self.properties.update(prop) + else: + raise ValueError(f"Unexpected argument type: {type(more_properties)}") + for key, value in kwargs.items(): + self.properties[key] = value + + + + + def clone(self, new_label: str = None) -> LegoComponent: + if new_label is None: + new_label = self.properties.label + clone = LegoComponent(None, self.properties, label=new_label) return clone def get_root_assembly(self): @@ -73,16 +96,18 @@ class LegoComponent: # TODO good repr representation def __repr__(self): - return f"LegoComponent {self.label} [{self.uuid}]" + return f"LegoComponent {self.properties['label']} [{self.uuid}]" class LegoAssembly: - def __init__(self, label: str, properties: dict, layer: AggregationLayer) -> None: + def __init__(self, layer: AggregationLayer, label: Optional[str] = None, *properties: dict , **kwargs) -> None: self.uuid: uuid.UUID = uuid.uuid4() self.parent: None | LegoAssembly = None - self.label: str = label - self.properties: dict = properties + self.properties: dict = {} + if label is not None: + self.properties['label'] = label self.layer: AggregationLayer = layer + self.properties.update(properties) self.components: List[LegoComponent] = [] self.assemblies: List[LegoAssembly] = [] @@ -161,7 +186,6 @@ class LegoAssembly: def to_dict(self) -> Dict: dict_ = { "uuid": self.uuid, - "label": self.label, "properties": self.properties, "layer": self.layer, } @@ -172,7 +196,7 @@ class LegoAssembly: # TODO find good string representation def __repr__(self): - return f"LegoAssembly {self.label} [{self.uuid}]" + return f"LegoAssembly {self.properties['label']} [{self.uuid}]" def clone(self, label: str = None) -> LegoAssembly: if label is None: @@ -234,8 +258,8 @@ class KPIEncoder(json.JSONEncoder): def default(self, o): if isinstance(o, uuid.UUID): return "kpi-" + str(o) - if isinstance(o, AggregationLayer): - return "kpi-" + o.name + if isinstance(o, (AggregationLayer)): + return "kpi-" + o.properties.label return super().default(o) pass \ No newline at end of file