From 89dccc139f0fe2f199c63d60c85f46d8e88491c7 Mon Sep 17 00:00:00 2001 From: Michael Stangl Date: Tue, 12 May 2026 17:28:11 +0200 Subject: [PATCH] =?UTF-8?q?K2=20der=20B=C3=B6gen=20wird=20erzeugt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/set_einfuegepkt.py | 193 +++---------------------- lib/set_koords.py | 313 ++++++++++++++++++++++++++++++++++++----- lib/utils.py | 179 +++++++++++++++++++++++ 3 files changed, 475 insertions(+), 210 deletions(-) create mode 100644 lib/utils.py diff --git a/lib/set_einfuegepkt.py b/lib/set_einfuegepkt.py index 8ed4147..359cac9 100644 --- a/lib/set_einfuegepkt.py +++ b/lib/set_einfuegepkt.py @@ -30,8 +30,11 @@ import os import sys import ezdxf -from ezdxf import bbox as ezdxf_bbox -from ezdxf.addons.importer import Importer + +from utils import ( + ROW_GROUPS, TEXT_HEIGHT, TEXT_MARGIN, CROSS_SIZE, ROW_LABEL_WIDTH, + build_row_layout, import_element_as_block, draw_cross, +) # --------------------------------------------------------------------------- @@ -333,195 +336,43 @@ def process_dxf(data_dir, label, json_file, filter_func, insertion_func, # show-omniflo # --------------------------------------------------------------------------- -def find_min_y_point(doc): - """Findet den Punkt mit dem geringsten Y-Wert im Modelspace.""" - min_y = float('inf') - min_point = None - for e in doc.modelspace(): - points = [] - if e.dxftype() == 'LINE': - points = [(e.dxf.start[0], e.dxf.start[1]), - (e.dxf.end[0], e.dxf.end[1])] - elif e.dxftype() == 'ARC': - cx, cy = e.dxf.center[0], e.dxf.center[1] - r = e.dxf.radius - a_s = math.radians(e.dxf.start_angle) - a_e = math.radians(e.dxf.end_angle) - points = [ - (cx + r * math.cos(a_s), cy + r * math.sin(a_s)), - (cx + r * math.cos(a_e), cy + r * math.sin(a_e)), - ] - for p in points: - if p[1] < min_y: - min_y = p[1] - min_point = p - return min_point - - -ROW_GROUPS = [ - ("Boegen 22.5", "boegen", - lambda b: b["KurvenWinkel"] == 22.5), - ("Boegen 45", "boegen", - lambda b: b["KurvenWinkel"] == 45), - ("Boegen 67.5", "boegen", - lambda b: b["KurvenWinkel"] == 67.5), - ("Boegen 90", "boegen", - lambda b: b["KurvenWinkel"] == 90), - ("Boegen 180", "boegen", - lambda b: b["KurvenWinkel"] == 180), - ("Weichenkoerper Einzel", "weichen", - lambda w: w["WeichenTyp"] == "Einzelweiche" and w["KurvenWinkel"] == 22.5), - ("Weichenkoerper Doppel", "weichen", - lambda w: w["WeichenTyp"] == "Doppelweiche" and w["KurvenWinkel"] == 22.5), - ("Weichenkoerper Dreiwege", "weichen", - lambda w: w["WeichenTyp"] == "Dreiwegeweiche" and w["KurvenWinkel"] == 22.5), - ("Einzelweiche 45", "weichen", - lambda w: w["WeichenTyp"] == "Einzelweiche" and w["KurvenWinkel"] == 45), - ("Einzelweiche 90", "weichen", - lambda w: w["WeichenTyp"] == "Einzelweiche" and w["KurvenWinkel"] == 90), - ("Einzelweiche Parallel", "weichen", - lambda w: w["WeichenTyp"] == "Einzelweiche" and w["KurvenWinkel"] == 0), - ("Doppelweiche 45", "weichen", - lambda w: w["WeichenTyp"] == "Doppelweiche" and w["KurvenWinkel"] == 45), - ("Doppelweiche 90", "weichen", - lambda w: w["WeichenTyp"] == "Doppelweiche" and w["KurvenWinkel"] == 90), - ("Doppelweiche Parallel", "weichen", - lambda w: w["WeichenTyp"] == "Doppelweiche" and w["KurvenWinkel"] == 0), - ("Dreiwegeweiche 45", "weichen", - lambda w: w["WeichenTyp"] == "Dreiwegeweiche" and w["KurvenWinkel"] == 45), - ("Dreiwegeweiche 90", "weichen", - lambda w: w["WeichenTyp"] == "Dreiwegeweiche" and w["KurvenWinkel"] == 90), - ("Dreiwegeweiche Parallel", "weichen", - lambda w: w["WeichenTyp"] == "Dreiwegeweiche" and w["KurvenWinkel"] == 0), - ("Dreifachweiche", "weichen", - lambda w: w["WeichenTyp"] == "Dreifachweiche"), - ("Deltaweiche", "weichen", - lambda w: w["WeichenTyp"] == "Deltaweiche"), - ("Sternweiche", "weichen", - lambda w: w["WeichenTyp"] == "Sternweiche"), -] - -PADDING_X = 200 -PADDING_Y = 400 -TEXT_HEIGHT = 30 -CROSS_SIZE = 40 -TEXT_MARGIN = 20 -ROW_LABEL_WIDTH = 600 - - def show_omniflo(data_dir, results_dir): """Erzeugt eine Uebersichts-DXF mit allen Omniflo-Elementen in Reihen.""" - boegen_path = os.path.join(data_dir, "json", "omniflo_boegen.json") - weichen_path = os.path.join(data_dir, "json", "omniflo_weichen.json") - - with open(boegen_path, "r", encoding="utf-8") as f: - boegen_data = json.load(f) - with open(weichen_path, "r", encoding="utf-8") as f: - weichen_data = json.load(f) - - sources = {"boegen": boegen_data, "weichen": weichen_data} - omniflo_dir = os.path.join(data_dir, "omniflo") + sources, rows = build_row_layout(data_dir, results_dir) target = ezdxf.new(dxfversion='R2010') target_msp = target.modelspace() target.layers.add('ANNOTATION', color=7) target.layers.add('INSPOINT', color=1) - target.layers.add('INSLINE', color=5) target.layers.add('ROW_LABEL', color=3) - cursor_y = 0.0 block_counter = 0 - for label, source_key, filter_func in ROW_GROUPS: - items = [item for item in sources[source_key] if filter_func(item)] - if not items: - continue - - row_elements = [] - for item in items: - sivasnr = str(item["Sivasnr"]) - dxf_path_result = os.path.join(results_dir, f"{sivasnr}.dxf") - dxf_path_orig = os.path.join(omniflo_dir, f"{sivasnr}.dxf") - dxf_path = dxf_path_result if os.path.exists(dxf_path_result) else dxf_path_orig - - if not os.path.exists(dxf_path): - print(f" WARNUNG: {sivasnr}.dxf nicht gefunden, ueberspringe.") - continue - - source_doc = ezdxf.readfile(dxf_path) - bb = ezdxf_bbox.extents(source_doc.modelspace()) - if not bb.has_data: - continue - - min_y_pt = find_min_y_point(source_doc) - insbase = source_doc.header.get("$INSBASE", (0, 0, 0)) - - row_elements.append({ - 'sivasnr': sivasnr, - 'source': source_doc, - 'extmin': bb.extmin, - 'extmax': bb.extmax, - 'width': bb.extmax[0] - bb.extmin[0], - 'height': bb.extmax[1] - bb.extmin[1], - 'insbase': insbase, - 'min_y_point': min_y_pt, - }) - - if not row_elements: - continue - - max_height = max(e['height'] for e in row_elements) - - label_y = cursor_y + max_height / 2 + for row in rows: + label_y = row['cursor_y'] + row['max_height'] / 2 target_msp.add_mtext( - label, + row['label'], dxfattribs={ 'layer': 'ROW_LABEL', 'char_height': TEXT_HEIGHT * 1.2, } ).set_location(insert=(-ROW_LABEL_WIDTH, label_y)) - cursor_x = 0.0 - for elem in row_elements: + for elem in row['elements']: block_name = f"BLK_{block_counter}" block_counter += 1 - importer = Importer(elem['source'], target) - importer.import_tables() - blk = target.blocks.new(name=block_name) - for entity in elem['source'].modelspace(): - importer.import_entity(entity, blk) - importer.finalize() + import_element_as_block(elem['source'], target, block_name) + target_msp.add_blockref(block_name, + insert=(elem['offset_x'], elem['offset_y'])) - offset_x = cursor_x - elem['extmin'][0] - offset_y = cursor_y - elem['extmin'][1] - target_msp.add_blockref(block_name, insert=(offset_x, offset_y)) + insbase = elem['source'].header.get("$INSBASE", (0, 0, 0)) + ipx = insbase[0] + elem['offset_x'] + ipy = insbase[1] + elem['offset_y'] + draw_cross(target_msp, ipx, ipy, CROSS_SIZE, 1, 'INSPOINT') - ipx = elem['insbase'][0] + offset_x - ipy = elem['insbase'][1] + offset_y - - target_msp.add_line( - (ipx - CROSS_SIZE, ipy), - (ipx + CROSS_SIZE, ipy), - dxfattribs={'layer': 'INSPOINT', 'color': 1} - ) - target_msp.add_line( - (ipx, ipy - CROSS_SIZE), - (ipx, ipy + CROSS_SIZE), - dxfattribs={'layer': 'INSPOINT', 'color': 1} - ) - - if elem['min_y_point']: - low_x = elem['min_y_point'][0] + offset_x - low_y = elem['min_y_point'][1] + offset_y - target_msp.add_line( - (ipx, ipy), - (low_x, low_y), - dxfattribs={'layer': 'INSLINE', 'color': 5} - ) - - text_x = cursor_x - text_y = cursor_y + elem['height'] + TEXT_MARGIN + text_x = elem['offset_x'] + elem['extmin'][0] + text_y = row['cursor_y'] + elem['height'] + TEXT_MARGIN target_msp.add_mtext( elem['sivasnr'], dxfattribs={ @@ -530,14 +381,10 @@ def show_omniflo(data_dir, results_dir): } ).set_location(insert=(text_x, text_y)) - cursor_x += elem['width'] + PADDING_X - - cursor_y -= max_height + TEXT_HEIGHT + TEXT_MARGIN * 2 + PADDING_Y - out_path = os.path.join(results_dir, "omniflo_uebersicht.dxf") target.saveas(out_path) print(f"Uebersicht gespeichert: {out_path}") - print(f" {block_counter} Elemente in {sum(1 for l, s, f in ROW_GROUPS if any(f(i) for i in sources[s]))} Reihen") + print(f" {block_counter} Elemente in {len(rows)} Reihen") # --------------------------------------------------------------------------- diff --git a/lib/set_koords.py b/lib/set_koords.py index 373172c..5ba932f 100644 --- a/lib/set_koords.py +++ b/lib/set_koords.py @@ -1,20 +1,42 @@ """ -Erzeugt benannte Koordinatensystem-Bloecke in DXF-Dateien. +Setzt Koordinatensystem-Bloecke (K1, K2, ...) in Omniflo DXF-Dateien. Jeder Block enthaelt drei Linien vom Ursprung: - rot(1) X-Achse, Laenge 1 - gruen(3) Y-Achse, Laenge 2 - blau(5) Z-Achse, Laenge 3 -Die 3D-Rotation erfolgt ueber die Transformation des INSERT-Entities. +Schalter: + --k1set K1 an alle Boegen und Weichen setzen. + Boegen: Beginn der obersten horizontalen Linie, + x rechts, y oben, z aus Zeichenebene. + Weichen: Beginn der untersten vertikalen Linie, + x oben, y rechts, z aus Zeichenebene. + --show-omniflo Uebersichts-DXF mit K-Positionen als farbige Kreuze. + --test Erzeugt Testdatei mit 4 KOS und verifiziert diese. + --number SIVA Nur diese eine 9-stellige Sivasnr verarbeiten. """ +import argparse +import json import math +import os +import sys + import ezdxf from ezdxf.math import Matrix44 +from utils import ( + ROW_GROUPS, TEXT_HEIGHT, TEXT_MARGIN, CROSS_SIZE, ROW_LABEL_WIDTH, + load_omniflo_data, build_row_layout, import_element_as_block, + draw_cross, is_bogen, +) + + +# --------------------------------------------------------------------------- +# Block-Definition +# --------------------------------------------------------------------------- -# Block-Linien: (color, dx, dy, dz) KS_LINES = [ (1, 1.0, 0.0, 0.0), # rot, X-Achse, Laenge 1 (3, 0.0, 2.0, 0.0), # gruen, Y-Achse, Laenge 2 @@ -41,6 +63,10 @@ def _ensure_layer(doc, layer_name="_KOORDINATENSYSTEME"): doc.layers.add(layer_name) +# --------------------------------------------------------------------------- +# insert / read / verify +# --------------------------------------------------------------------------- + def insert_ks(msp, name, point, rx=0, ry=0, rz=0): """ Erzeugt ein benanntes Koordinatensystem als Block-Referenz. @@ -66,7 +92,6 @@ def insert_ks(msp, name, point, rx=0, ry=0, rz=0): dxfattribs={"layer": "_KOORDINATENSYSTEME"}, ) - # 3D-Rotation via Transformationsmatrix m = Matrix44.chain( Matrix44.translate(-point[0], -point[1], -point[2]), Matrix44.x_rotate(math.radians(rx)), @@ -83,28 +108,18 @@ def read_ks(doc, name): """ Liest ein Koordinatensystem aus einer DXF-Datei zurueck. - Bestimmt Position und Rotationswinkel (rx, ry, rz) aus den - tatsaechlichen Linienendpunkten des aufgeloesten INSERT-Blocks. - - Args: - doc: ezdxf DXF-Dokument. - name: Block-Name (z.B. "K1"). - Returns: Dict mit 'name', 'point', 'rx', 'ry', 'rz' oder None. """ msp = doc.modelspace() for entity in msp: if entity.dxftype() == "INSERT" and entity.dxf.name == name: - # Linien aus Block-Referenz aufloesen (virtuelle Entities) lines = [e for e in entity.virtual_entities() if e.dxftype() == "LINE"] if len(lines) != 3: return None - # Gemeinsamer Startpunkt = Einfuegepunkt point = (lines[0].dxf.start[0], lines[0].dxf.start[1], lines[0].dxf.start[2]) - # Richtungsvektoren aus den Endpunkten extrahieren axes = {} for line in lines: end = line.dxf.end @@ -114,18 +129,10 @@ def read_ks(doc, name): length = (dx**2 + dy**2 + dz**2) ** 0.5 axes[line.dxf.color] = (dx / length, dy / length, dz / length) - # X-Achse (color=1), Y-Achse (color=3), Z-Achse (color=5) x_axis = axes.get(1, (1, 0, 0)) y_axis = axes.get(3, (0, 1, 0)) z_axis = axes.get(5, (0, 0, 1)) - # Rotationswinkel aus Rotationsmatrix R = Rz * Ry * Rx - # Spalten von R: x_axis, y_axis, z_axis - # R20 = -sin(ry) = x_axis[2] - # R21 = cos(ry)*sin(rx) = y_axis[2] - # R22 = cos(ry)*cos(rx) = z_axis[2] - # R10 = sin(rz)*cos(ry) = x_axis[1] - # R00 = cos(rz)*cos(ry) = x_axis[0] ry = math.asin(max(-1, min(1, -x_axis[2]))) cos_ry = math.cos(ry) @@ -133,7 +140,6 @@ def read_ks(doc, name): rx = math.atan2(y_axis[2] / cos_ry, z_axis[2] / cos_ry) rz = math.atan2(x_axis[1] / cos_ry, x_axis[0] / cos_ry) else: - # Gimbal Lock: ry = +/-90, rx und rz nicht unabhaengig rz = 0 rx = math.atan2(-z_axis[0], y_axis[0]) @@ -148,16 +154,7 @@ def read_ks(doc, name): def verify_ks(dxf_path, expected): - """ - Liest KS-Bloecke aus einer DXF-Datei und vergleicht mit Erwartungswerten. - - Args: - dxf_path: Pfad zur DXF-Datei. - expected: Liste von Dicts mit 'name', 'point', 'rx', 'ry', 'rz'. - - Returns: - True wenn alle Werte uebereinstimmen (Toleranz 0.01). - """ + """Liest KS-Bloecke und vergleicht mit Erwartungswerten (Toleranz 0.01).""" doc = ezdxf.readfile(dxf_path) all_ok = True tol = 0.01 @@ -191,9 +188,197 @@ def verify_ks(dxf_path, expected): return all_ok -if __name__ == "__main__": - import os +# --------------------------------------------------------------------------- +# K1-Positionsbestimmung +# --------------------------------------------------------------------------- +def find_topmost_horizontal_line(doc): + """Findet die am weitesten oben liegende horizontale Linie.""" + best = None + best_y = -float('inf') + for entity in doc.modelspace(): + if entity.dxftype() != "LINE": + continue + s, e = entity.dxf.start, entity.dxf.end + if abs(e[1] - s[1]) > 0.01: + continue + if s[1] > best_y: + best_y = s[1] + best = entity + return best + + +def find_bottommost_vertical_line(doc): + """Findet die am weitesten unten liegende rein vertikale Linie.""" + best = None + best_min_y = float('inf') + for entity in doc.modelspace(): + if entity.dxftype() != "LINE": + continue + s, e = entity.dxf.start, entity.dxf.end + if abs(e[0] - s[0]) > 0.01: + continue + min_y = min(s[1], e[1]) + if min_y < best_min_y: + best_min_y = min_y + best = entity + return best + + +def k1_point_bogen(doc): + """K1-Position fuer Boegen: Beginn der obersten horizontalen Linie. + Rotation: x rechts, y oben, z aus Zeichenebene (Standard, rz=0). + """ + hline = find_topmost_horizontal_line(doc) + if hline is None: + return None + s, e = hline.dxf.start, hline.dxf.end + left_x = min(s[0], e[0]) + y = s[1] + return (left_x, y, 0), 0, 0, 0 + + +def k1_point_weiche(doc): + """K1-Position fuer Weichen: Beginn der untersten vertikalen Linie. + Rotation: x oben, y links, z aus Zeichenebene (rz=90). + """ + vline = find_bottommost_vertical_line(doc) + if vline is None: + return None + s, e = vline.dxf.start, vline.dxf.end + x = s[0] + bottom_y = min(s[1], e[1]) + return (x, bottom_y, 0), 0, 0, 90 + + +# --------------------------------------------------------------------------- +# --k1set +# --------------------------------------------------------------------------- + +def process_k1set(data_dir, results_dir, number=None): + """Setzt K1-Block an alle Boegen und Weichen.""" + sources = load_omniflo_data(data_dir) + omniflo_dir = os.path.join(data_dir, "omniflo") + + all_items = [] + for b in sources["boegen"]: + all_items.append((str(b["Sivasnr"]), b["ProfilTyp"], True)) + for w in sources["weichen"]: + all_items.append((str(w["Sivasnr"]), w["ProfilTyp"], False)) + + if number: + all_items = [(s, p, ib) for s, p, ib in all_items if s == str(number)] + + if not all_items: + print("Keine Elemente gefunden.") + return + + print("=== K1 setzen ===") + for sivasnr, profil, is_bog in all_items: + dxf_path = os.path.join(results_dir, f"{sivasnr}.dxf") + if not os.path.exists(dxf_path): + dxf_path = os.path.join(omniflo_dir, f"{sivasnr}.dxf") + if not os.path.exists(dxf_path): + continue + + doc = ezdxf.readfile(dxf_path) + + # Bestehende K1-Referenzen entfernen + msp = doc.modelspace() + for entity in list(msp): + if entity.dxftype() == "INSERT" and entity.dxf.name == "K1": + msp.delete_entity(entity) + + if is_bog: + result = k1_point_bogen(doc) + else: + result = k1_point_weiche(doc) + + if result is None: + print(f"WARNUNG: K1 in {sivasnr}.dxf nicht bestimmbar, ueberspringe.") + continue + + point, rx, ry, rz = result + msp = doc.modelspace() + insert_ks(msp, "K1", point, rx, ry, rz) + + out_path = os.path.join(results_dir, f"{sivasnr}.dxf") + doc.saveas(out_path) + print(f"{sivasnr}: K1 at ({point[0]:.2f},{point[1]:.2f},{point[2]:.2f}) " + f"rx={rx} ry={ry} rz={rz} [{profil}]") + + print(f"\nErgebnisse in: {results_dir}") + + +# --------------------------------------------------------------------------- +# --show-omniflo +# --------------------------------------------------------------------------- + +# Farben: K1=rot(1), K2=orange(30), K3=gelb(2), K4=gruen(3) +K_COLORS = {"K1": 1, "K2": 30, "K3": 2, "K4": 3} + + +def show_omniflo(data_dir, results_dir): + """Uebersichts-DXF mit K-Positionen als farbige Kreuze.""" + sources, rows = build_row_layout(data_dir, results_dir) + + target = ezdxf.new(dxfversion='R2010') + target_msp = target.modelspace() + target.layers.add('ANNOTATION', color=7) + target.layers.add('K_POINTS', color=1) + target.layers.add('ROW_LABEL', color=3) + + block_counter = 0 + + for row in rows: + label_y = row['cursor_y'] + row['max_height'] / 2 + target_msp.add_mtext( + row['label'], + dxfattribs={ + 'layer': 'ROW_LABEL', + 'char_height': TEXT_HEIGHT * 1.2, + } + ).set_location(insert=(-ROW_LABEL_WIDTH, label_y)) + + for elem in row['elements']: + block_name = f"BLK_{block_counter}" + block_counter += 1 + + import_element_as_block(elem['source'], target, block_name) + target_msp.add_blockref(block_name, + insert=(elem['offset_x'], elem['offset_y'])) + + # K-Positionen aus dem Quell-DXF auslesen und anzeigen + for k_name, k_color in K_COLORS.items(): + k_data = read_ks(elem['source'], k_name) + if k_data is None: + continue + kx = k_data['point'][0] + elem['offset_x'] + ky = k_data['point'][1] + elem['offset_y'] + draw_cross(target_msp, kx, ky, CROSS_SIZE, k_color, 'K_POINTS') + + text_x = elem['offset_x'] + elem['extmin'][0] + text_y = row['cursor_y'] + elem['height'] + TEXT_MARGIN + target_msp.add_mtext( + elem['sivasnr'], + dxfattribs={ + 'layer': 'ANNOTATION', + 'char_height': TEXT_HEIGHT, + } + ).set_location(insert=(text_x, text_y)) + + out_path = os.path.join(results_dir, "koords_uebersicht.dxf") + target.saveas(out_path) + print(f"Uebersicht gespeichert: {out_path}") + print(f" {block_counter} Elemente in {len(rows)} Reihen") + + +# --------------------------------------------------------------------------- +# --test +# --------------------------------------------------------------------------- + +def run_test(results_dir): + """Erzeugt Testdatei mit 4 KOS und verifiziert Ruecklesen.""" doc = ezdxf.new(dxfversion="R2010") msp = doc.modelspace() @@ -207,8 +392,6 @@ if __name__ == "__main__": for tc in test_cases: insert_ks(msp, tc["name"], tc["point"], tc["rx"], tc["ry"], tc["rz"]) - results_dir = os.environ.get("DXFM_RESULTS", "results") - os.makedirs(results_dir, exist_ok=True) out_path = os.path.join(results_dir, "ks_test.dxf") doc.saveas(out_path) print(f"Testdatei gespeichert: {out_path}\n") @@ -216,3 +399,59 @@ if __name__ == "__main__": print("Verifikation:") ok = verify_ks(out_path, test_cases) print(f"\nErgebnis: {'ALLE OK' if ok else 'FEHLER GEFUNDEN'}") + return ok + + +# --------------------------------------------------------------------------- +# main +# --------------------------------------------------------------------------- + +def main(): + parser = argparse.ArgumentParser( + description="Setzt Koordinatensystem-Bloecke in Omniflo DXF-Dateien" + ) + parser.add_argument("--k1set", action="store_true", + help="K1-Block an alle Boegen und Weichen setzen") + parser.add_argument("--show-omniflo", action="store_true", + help="Uebersichts-DXF mit K-Positionen erzeugen") + parser.add_argument("--test", action="store_true", + help="Testdatei mit 4 KOS erzeugen und verifizieren") + parser.add_argument("--number", type=int, + help="Nur diese 9-stellige Sivasnr verarbeiten") + args = parser.parse_args() + + if args.number and len(str(args.number)) != 9: + print("FEHLER: --number muss eine 9-stellige Ganzzahl sein.") + sys.exit(1) + + if not args.k1set and not args.show_omniflo and not args.test: + parser.print_help() + sys.exit(1) + + data_dir = os.environ.get("DXFM_DATA") + results_dir = os.environ.get("DXFM_RESULTS") + + if not data_dir or not results_dir: + if args.test: + results_dir = results_dir or "results" + os.makedirs(results_dir, exist_ok=True) + run_test(results_dir) + return + print("FEHLER: Umgebungsvariablen DXFM_DATA und DXFM_RESULTS muessen gesetzt sein.") + sys.exit(1) + + os.makedirs(results_dir, exist_ok=True) + + if args.test: + run_test(results_dir) + + if args.k1set: + process_k1set(data_dir, results_dir, args.number) + + if args.show_omniflo: + print("=== Koordinaten Uebersicht ===") + show_omniflo(data_dir, results_dir) + + +if __name__ == "__main__": + main() diff --git a/lib/utils.py b/lib/utils.py new file mode 100644 index 0000000..49dbd11 --- /dev/null +++ b/lib/utils.py @@ -0,0 +1,179 @@ +""" +Gemeinsame Hilfsfunktionen fuer DXF-Makros. + +Enthaelt die Reihen-Gruppierung fuer Omniflo-Elemente und Layout-Funktionen +fuer die Uebersichts-DXF Erzeugung. +""" + +import json +import os + +import ezdxf +from ezdxf import bbox as ezdxf_bbox +from ezdxf.addons.importer import Importer + + +# --------------------------------------------------------------------------- +# Reihen-Gruppen: (Label, Quell-Key, Filter-Funktion) +# --------------------------------------------------------------------------- + +ROW_GROUPS = [ + ("Boegen 22.5", "boegen", + lambda b: b["KurvenWinkel"] == 22.5), + ("Boegen 45", "boegen", + lambda b: b["KurvenWinkel"] == 45), + ("Boegen 67.5", "boegen", + lambda b: b["KurvenWinkel"] == 67.5), + ("Boegen 90", "boegen", + lambda b: b["KurvenWinkel"] == 90), + ("Boegen 180", "boegen", + lambda b: b["KurvenWinkel"] == 180), + ("Weichenkoerper Einzel", "weichen", + lambda w: w["WeichenTyp"] == "Einzelweiche" and w["KurvenWinkel"] == 22.5), + ("Weichenkoerper Doppel", "weichen", + lambda w: w["WeichenTyp"] == "Doppelweiche" and w["KurvenWinkel"] == 22.5), + ("Weichenkoerper Dreiwege", "weichen", + lambda w: w["WeichenTyp"] == "Dreiwegeweiche" and w["KurvenWinkel"] == 22.5), + ("Einzelweiche 45", "weichen", + lambda w: w["WeichenTyp"] == "Einzelweiche" and w["KurvenWinkel"] == 45), + ("Einzelweiche 90", "weichen", + lambda w: w["WeichenTyp"] == "Einzelweiche" and w["KurvenWinkel"] == 90), + ("Einzelweiche Parallel", "weichen", + lambda w: w["WeichenTyp"] == "Einzelweiche" and w["KurvenWinkel"] == 0), + ("Doppelweiche 45", "weichen", + lambda w: w["WeichenTyp"] == "Doppelweiche" and w["KurvenWinkel"] == 45), + ("Doppelweiche 90", "weichen", + lambda w: w["WeichenTyp"] == "Doppelweiche" and w["KurvenWinkel"] == 90), + ("Doppelweiche Parallel", "weichen", + lambda w: w["WeichenTyp"] == "Doppelweiche" and w["KurvenWinkel"] == 0), + ("Dreiwegeweiche 45", "weichen", + lambda w: w["WeichenTyp"] == "Dreiwegeweiche" and w["KurvenWinkel"] == 45), + ("Dreiwegeweiche 90", "weichen", + lambda w: w["WeichenTyp"] == "Dreiwegeweiche" and w["KurvenWinkel"] == 90), + ("Dreiwegeweiche Parallel", "weichen", + lambda w: w["WeichenTyp"] == "Dreiwegeweiche" and w["KurvenWinkel"] == 0), + ("Dreifachweiche", "weichen", + lambda w: w["WeichenTyp"] == "Dreifachweiche"), + ("Deltaweiche", "weichen", + lambda w: w["WeichenTyp"] == "Deltaweiche"), + ("Sternweiche", "weichen", + lambda w: w["WeichenTyp"] == "Sternweiche"), +] + +PADDING_X = 200 +PADDING_Y = 400 +TEXT_HEIGHT = 30 +CROSS_SIZE = 40 +TEXT_MARGIN = 20 +ROW_LABEL_WIDTH = 600 + + +def load_omniflo_data(data_dir): + """Laedt Boegen- und Weichen-JSON und gibt ein Dict {boegen, weichen} zurueck.""" + boegen_path = os.path.join(data_dir, "json", "omniflo_boegen.json") + weichen_path = os.path.join(data_dir, "json", "omniflo_weichen.json") + + with open(boegen_path, "r", encoding="utf-8") as f: + boegen_data = json.load(f) + with open(weichen_path, "r", encoding="utf-8") as f: + weichen_data = json.load(f) + + return {"boegen": boegen_data, "weichen": weichen_data} + + +def is_bogen(sivasnr, sources): + """Prueft ob eine Sivasnr ein Bogen ist.""" + return any(str(b["Sivasnr"]) == str(sivasnr) for b in sources["boegen"]) + + +def build_row_layout(data_dir, results_dir): + """ + Berechnet das Layout aller Omniflo-Elemente in Reihen. + + Returns: + (sources, rows) wobei rows eine Liste von Dicts ist: + [{ + 'label': str, + 'source_key': str, + 'elements': [{'sivasnr', 'source', 'extmin', 'extmax', + 'width', 'height', 'offset_x', 'offset_y'}], + 'cursor_y': float, + 'max_height': float, + }] + """ + sources = load_omniflo_data(data_dir) + omniflo_dir = os.path.join(data_dir, "omniflo") + + rows = [] + cursor_y = 0.0 + + for label, source_key, filter_func in ROW_GROUPS: + items = [item for item in sources[source_key] if filter_func(item)] + if not items: + continue + + row_elements = [] + for item in items: + sivasnr = str(item["Sivasnr"]) + dxf_path_result = os.path.join(results_dir, f"{sivasnr}.dxf") + dxf_path_orig = os.path.join(omniflo_dir, f"{sivasnr}.dxf") + dxf_path = dxf_path_result if os.path.exists(dxf_path_result) else dxf_path_orig + + if not os.path.exists(dxf_path): + continue + + source_doc = ezdxf.readfile(dxf_path) + bb = ezdxf_bbox.extents(source_doc.modelspace()) + if not bb.has_data: + continue + + row_elements.append({ + 'sivasnr': sivasnr, + 'source': source_doc, + 'extmin': bb.extmin, + 'extmax': bb.extmax, + 'width': bb.extmax[0] - bb.extmin[0], + 'height': bb.extmax[1] - bb.extmin[1], + }) + + if not row_elements: + continue + + max_height = max(e['height'] for e in row_elements) + + cursor_x = 0.0 + for elem in row_elements: + elem['offset_x'] = cursor_x - elem['extmin'][0] + elem['offset_y'] = cursor_y - elem['extmin'][1] + cursor_x += elem['width'] + PADDING_X + + rows.append({ + 'label': label, + 'source_key': source_key, + 'elements': row_elements, + 'cursor_y': cursor_y, + 'max_height': max_height, + }) + + cursor_y -= max_height + TEXT_HEIGHT + TEXT_MARGIN * 2 + PADDING_Y + + return sources, rows + + +def import_element_as_block(source_doc, target_doc, block_name): + """Importiert alle Modelspace-Entities eines Quell-Dokuments als Block.""" + importer = Importer(source_doc, target_doc) + importer.import_tables() + blk = target_doc.blocks.new(name=block_name) + for entity in source_doc.modelspace(): + importer.import_entity(entity, blk) + importer.finalize() + return block_name + + +def draw_cross(msp, x, y, size, color, layer): + """Zeichnet ein Kreuz an (x, y).""" + msp.add_line((x - size, y), (x + size, y), + dxfattribs={'layer': layer, 'color': color}) + msp.add_line((x, y - size), (x, y + size), + dxfattribs={'layer': layer, 'color': color})