From 58195eebfa76c84692d377af0fec9bab254a7fb3 Mon Sep 17 00:00:00 2001 From: lertlmaier Date: Wed, 7 May 2025 12:29:35 +0200 Subject: [PATCH] Getpositions angepasst, sodass Jsons geschrieben werden. Ausserdem Anpassung, sodass Leichen (=Bloecke ohne pos) nicht erfasst werden --- .vscode/launch.json | 17 ++++- cfg/getpositions.cfg | 10 ++- cfg/routing.cfg | 18 ++++++ lib/.requirements.txt.un~ | Bin 0 -> 977 bytes lib/getpositions.py | 67 +++++++------------ lib/model.py | 46 +++++++------ lib/requirements.txt~ | 21 ++++++ lib/routing.py | 133 ++++++++++++++++++++++++++++++++++++++ 8 files changed, 248 insertions(+), 64 deletions(-) create mode 100644 cfg/routing.cfg create mode 100644 lib/.requirements.txt.un~ create mode 100644 lib/requirements.txt~ create mode 100644 lib/routing.py diff --git a/.vscode/launch.json b/.vscode/launch.json index 2f7b420..c4a5083 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -32,7 +32,22 @@ "easy.dxf", "-s", "-d", - "-r" + "-r", + "-c" + ], + }, + { + "name": "use easy.dxf, nur Pritsche", + "type": "debugpy", + "request": "launch", + "program": "${file}", + "console": "integratedTerminal", + "justMyCode": true, + "args": [ + "--filename", + "easy.dxf", + "-r", + "-c" ], }, { diff --git a/cfg/getpositions.cfg b/cfg/getpositions.cfg index 13e01cf..b9e4820 100644 --- a/cfg/getpositions.cfg +++ b/cfg/getpositions.cfg @@ -7,4 +7,12 @@ MOTOR [Pritsche] PRITSCHE_100-60-SCHRAFF -PRITSCHE_200-60 \ No newline at end of file +PRITSCHE_200-60 + +[Unterverteiler] +Breite = 320 +Hoehe = 350 + +[Sensor_Marker] +Breite = 80 +Hoehe = 90 diff --git a/cfg/routing.cfg b/cfg/routing.cfg new file mode 100644 index 0000000..b9e4820 --- /dev/null +++ b/cfg/routing.cfg @@ -0,0 +1,18 @@ +[Eingänge] +0-0_ILS_EINGANG + +[Ausgänge] +AUSGANG +MOTOR + +[Pritsche] +PRITSCHE_100-60-SCHRAFF +PRITSCHE_200-60 + +[Unterverteiler] +Breite = 320 +Hoehe = 350 + +[Sensor_Marker] +Breite = 80 +Hoehe = 90 diff --git a/lib/.requirements.txt.un~ b/lib/.requirements.txt.un~ new file mode 100644 index 0000000000000000000000000000000000000000..308745e67b0c066a59300be2a04b620502e8a07e GIT binary patch literal 977 zcmWH`%$*;a=aT=Ff$13I%N>cw{=bk{xujg=HLu_mBL~xmx9MlMK1*Y3QdzZ&fq_93 zh>-y!ki!HO0MRhakRc@ck_93ge*sAWl*s^+Vux}50|8hBCNN4w@faK(3=Cf- zfM&A+F)a8&Q6ZF@SW=LaUy_rVWNT}zXK0{jVA=?dIFKwd0L3sDOkgzPU?D*(8W?de hp!o Rotation -> Translation - m = ( - Matrix44.scale(xscale, yscale, 1) @ - Matrix44.z_rotate(rotation_deg * math.pi / 180) @ - Matrix44.translate(block_pos.x, block_pos.y, block_pos.z) - ) - - # Lokale Position transformieren - return m.transform(local_pos) - def merge_two_dicts(x, y): z = x.copy() z.update(y) @@ -96,16 +63,22 @@ def get_input_positions(msp: ezdxf.document.Drawing.modelspace): #print(f"-- coord {attrib.dxf.text} --: {attrib.dxf.insert}") if attrib.dxf.tag == "REALE_POSITION" and attrib.dxf.text == "x": #print(f"-- coord real --: {attrib.dxf.insert}") - world_pos = get_world_position_of_attribute(insert, attrib) - #print(f"-- world --: {world_pos}") - ld["block_pos"] = attrib.dxf.insert - ld["world_pos"] = world_pos + pos = attrib.dxf.insert #Position Ecke unten links von "x"-Marker auslesen + + # Hoehe und Breite von "x" addieren, um Mittelpunkt zu finden + breite_marker = config.getfloat("Sensor_Marker", "Breite") + hoehe_marker = config.getfloat("Sensor_Marker", "Hoehe") + pos_midx = pos.x + breite_marker*0.5 + pos_midy = pos.y + hoehe_marker*0.5 - if not id == "": - if not id in allIds: - allIds[id] = ld - else: - allIds[id] = merge_two_dicts(allIds[id], ld) + ld["pos"] = (pos_midx, pos_midy) + + # Nur wenn eine ID vorhanden ist, und eine gültige Position existiert + if id and "pos" in ld and isinstance(ld["pos"], tuple) and len(ld["pos"]) == 2: + if id in allIds: + allIds[id] = merge_two_dicts(allIds[id], ld) #Kombiniert alle infos aus dxf und "pos" + else: + allIds[id] = ld return allIds @@ -172,7 +145,7 @@ def get_rack_positions(msp): return ret def to_json(d, pretty: bool = True) -> str: - return json.dumps(d, indent=2 if pretty else None, default=str) + return json.dumps(d, indent=2 if pretty else None, ensure_ascii=False, default=str) #ensure_ascii false für darstellung von "ue" def get_dxf_file(filepath): """hole das dxf file @@ -199,6 +172,7 @@ if __name__ == '__main__': out_dir = os.environ.get('PROJECT_DATA') work_dir = os.environ.get('PROJECT_WORK') + config_dir = os.environ.get("PROJECT_CFG") filename = args.filename doc = get_dxf_file(os.path.join(work_dir, filename)) # type: ignore @@ -208,18 +182,25 @@ if __name__ == '__main__': res_dist = dict() res_rac = dict() if args.sensors or args.dists or args.rack: + + config = configparser.ConfigParser(allow_no_value=True) + config.read(os.path.join(config_dir, "getpositions.cfg")) + if args.sensors: res_pos = get_input_positions(msp) if args.console: print(to_json(res_pos)) + write_results(to_json(res_pos), work_dir, "sensors.json") if args.dists: res_dist = get_subdistributor_positions(msp) if args.console: print(to_json(res_dist)) + write_results(to_json(res_dist), work_dir, "subdistributors.json") if args.rack: res_rac = get_rack_positions(msp) if args.console: print(to_json(res_rac)) + write_results(to_json(res_rac), work_dir, "racks.json") else: parser.print_help() diff --git a/lib/model.py b/lib/model.py index a162b7b..926cb79 100644 --- a/lib/model.py +++ b/lib/model.py @@ -3,30 +3,36 @@ from dacite import from_dict from typing import List import json - @dataclass -class Sensor: - id: str +class Punkt: x: float y: float + @dataclass -class Aktor: +class Ziel: + #Sensor oder Aktor id: str - x: float - y: float + pos: Punkt + +@dataclass +class Quelle: + #Unterverteiler + id: str + pos: Punkt @dataclass class Pritsche: id: str - x: float - y: float + cords: List[Punkt] + @dataclass class Anlage: name: str - sensoren: List[Sensor] - aktoren: List[Aktor] + sensoren: List[Ziel] + aktoren: List[Ziel] + unterverteiler: List[Quelle] kabelpritschen: List[Pritsche] def to_json(self, pretty: bool = True) -> str: @@ -41,20 +47,22 @@ class Anlage: return result -if __name__ == '__main__': +if __name__ == '__main__': + json_string = '''{ "name": "H&M", "sensoren": [ - {"id": "AP4321", "x": 14, "y": 50}, - {"id": "AP4322", "x": 22, "y": 100} - ], + {"id": "S1", "pos": {"x": 3, "y": 5}} + ], "aktoren": [ - {"id": "AP4321", "x": 14, "y": 50}, - {"id": "AP4322", "x": 22, "y": 100} - ], + {"id": "A1", "pos": {"x": 3, "y": 5}} + ], + "unterverteiler": [ + {"id": "U1", "pos": {"x": 3, "y": 5}} + ], "kabelpritschen": [ - {"id": "p1", "x": 1, "y": 0}, - {"id": "p2", "x": 22, "y": 10} + {"id": "p1", "cords": [{"x": 3, "y": 5},{"x": 3, "y": 5}]}, + {"id": "p2", "cords": [{"x": 3, "y": 5},{"x": 3, "y": 5}]} ] } ''' diff --git a/lib/requirements.txt~ b/lib/requirements.txt~ new file mode 100644 index 0000000..a0ba30d --- /dev/null +++ b/lib/requirements.txt~ @@ -0,0 +1,21 @@ +contourpy==1.3.1 +cycler==0.12.1 +et-xmlfile==1.1.0 +ezdxf==1.4.0 +fonttools==4.57.0 +kiwisolver==1.4.8 +numpy==1.26.1 +openpyxl==3.1.2 +packaging==24.2 +pillow==11.1.0 +PyMuPDF==1.25.5 +pyparsing==3.2.3 +PySide6==6.9.0 +PySide6_Addons==6.9.0 +PySide6_Essentials==6.9.0 +python-dateutil==2.8.2 +pytz==2023.3.post1 +shiboken6==6.9.0 +six==1.16.0 +typing_extensions==4.13.1 +tzdata==2023.3 diff --git a/lib/routing.py b/lib/routing.py new file mode 100644 index 0000000..b8d1b2c --- /dev/null +++ b/lib/routing.py @@ -0,0 +1,133 @@ +import os +import json +import argparse +import heapq +import math + +# Hilfsfunktionen +def load_json(filepath): + with open(filepath, 'r', encoding='utf-8') as f: + return json.load(f) + +def parse_pos(pos_str): + """ Konvertiert '(x, y)' oder '(x, y, z)' in ein Tuple """ + try: + return tuple(map(float, pos_str.strip('()').split(','))) + except Exception: + raise ValueError(f"Ungültiges Positionsformat: {pos_str}") + +def distance(p1, p2): + """ Euklidische Distanz in 2D """ + return math.sqrt((p1[0]-p2[0])**2 + (p1[1]-p2[1])**2) + +def add_edge(graph, node1, node2, dist): + """ Fügt eine Kante zwischen zwei Knoten im Graphen hinzu """ + if node1 not in graph: + graph[node1] = [] + if node2 not in graph: + graph[node2] = [] + graph[node1].append((node2, dist)) + graph[node2].append((node1, dist)) + +def project_point_on_segment(p, a, b): + """Projektion eines Punktes p auf ein Liniensegment a-b""" + ax, ay = a + bx, by = b + px, py = p + + dx = bx - ax + dy = by - ay + if dx == dy == 0: + return a + + t = ((px - ax) * dx + (py - ay) * dy) / (dx * dx + dy * dy) + t = max(0, min(1, t)) # Begrenze t auf [0,1] + return (ax + t * dx, ay + t * dy) + +def dijkstra(graph, start): + """ Dijkstra-Algorithmus, um die kürzesten Wege im Graphen zu berechnen """ + distances = {node: float('inf') for node in graph} + distances[start] = 0 + priority_queue = [(0, start)] # (Distanz, Knoten) + + while priority_queue: + current_distance, current_node = heapq.heappop(priority_queue) + + if current_distance > distances[current_node]: + continue + + for neighbor, weight in graph[current_node]: + distance = current_distance + weight + + if distance < distances[neighbor]: + distances[neighbor] = distance + heapq.heappush(priority_queue, (distance, neighbor)) + + return distances + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Berechne Wege von Sensoren zu Verteilern über Kabeltrassen') + parser.add_argument('-c', '--console', action='store_true', help='Ausgabe auf Konsole') + + args = parser.parse_args() + + # Umgebungsvariablen + work_dir = os.environ.get("PROJECT_WORK") + config_dir = os.environ.get("PROJECT_CFG") + + # Pfade zu JSON-Dateien + sensors_path = os.path.join(work_dir, "sensors.json") + subdist_path = os.path.join(work_dir, "subdistributors.json") + racks_path = os.path.join(work_dir, "racks.json") + + # Einlesen + sensors = load_json(sensors_path) + subdists = {k: parse_pos(v) for k, v in load_json(subdist_path).items()} + racks = load_json(racks_path) + + # Graph erstellen + graph = {} + + # Sensoren zu Kabeltrassen verbinden + for sensor_id, sensor_info in sensors.items(): #über alle Sensoren und alle deren Infos laufen + sensor_pos = tuple(sensor_info['pos']) #sensor position als tuple übergeben + for rack in racks: + for segment_start, segment_end in zip(rack[:-1], rack[1:]): + # Berechne Distanz von Sensor zur Kabeltrasse + px, py = project_point_on_segment(sensor_pos, segment_start, segment_end) + dist = distance(sensor_pos, (px, py)) + add_edge(graph, sensor_id, f"rack_{rack}", dist) + + # Unterverteiler zu Kabeltrassen verbinden + for uc_id, uc_pos in subdists.items(): + for rack in racks: + for segment_start, segment_end in zip(rack[:-1], rack[1:]): + # Berechne Distanz von UC zur Kabeltrasse + px, py = project_point_on_segment(uc_pos, segment_start, segment_end) + dist = distance(uc_pos, (px, py)) + add_edge(graph, uc_id, f"rack_{rack}", dist) + + # Sensor zu UC verbinden (Routing von Sensoren zu den zugehörigen Unterverteilern) + for sensor_id, sensor_info in sensors.items(): + subdist_id = None + if 'KENNZEICHNUNG' in sensor_info: + for uc_id in subdists: + if uc_id in sensor_info['KENNZEICHNUNG']: + subdist_id = uc_id + break + if subdist_id: + # Verbinde den Sensor mit dem zugehörigen Unterverteiler + sensor_pos = tuple(sensor_info['pos']) + uc_pos = subdists[subdist_id] + dist = distance(sensor_pos, uc_pos) + add_edge(graph, sensor_id, subdist_id, dist) + + # Berechnung der kürzesten Wege mit Dijkstra + routing_result = {} + for sensor_id in sensors: + distances = dijkstra(graph, sensor_id) + routing_result[sensor_id] = distances + + if args.console: + print(json.dumps(routing_result, indent=2))