""" 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 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 links, z aus Zeichenebene. --k2set K2 an alle Boegen und Weichen setzen. Nutzt dieselben Schalter-Filter wie set_einfuegepkt.py (SWITCH_FILTERS aus utils.py). Boegen: Position am anderen Ende der Kette (Gerade-Bogen-Gerade). x-Achse tangential zur Linie, zur Bogenmitte zeigend. y senkrecht dazu, z wie K1 (aus Zeichenebene). 180-Grad-Sonderfall: x nach rechts (rz=0). Weichen (weichen45, weichen90, weichen_parallel): Folgt dem Bogenzweig bis zum Ende. x tangential zur Linie, zur Kurve zeigend. Bei Doppelweichen: linker Zweig. y senkrecht dazu, z wie K1. --k3set K3 an Weichen mit 2+ Boegen setzen (rechter Zweig). Gleiche Logik wie K2, aber rechter Ast (groesstes x). Nur bei: Doppelweichen, Dreiwegeweichen, Dreifachweiche, Delta, Stern, Weichenkoerper Doppel/Dreiwege. --k4set K4 an Weichen setzen (oberes Ende der K1-Linie). K4 liegt K1 gegenueber, am oberen Ende derselben V-Linie. Rotation wie K1: x oben, y links, z aus Zeichenebene. Kein K4 bei: Boegen, Doppelweiche, Dreifachweiche, Delta. --set-all Alle Koordinatensysteme (K1-K4) in einem Durchgang setzen. --force Bestehende Kx ueberschreiben (sonst skip wenn vorhanden). Duplikate werden immer bereinigt (letztes bleibt). --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 omniflo_utils import ( ROW_GROUPS, TEXT_HEIGHT, TEXT_MARGIN, CROSS_SIZE, ROW_LABEL_WIDTH, SWITCH_FILTERS, load_omniflo_data, build_row_layout, import_element_as_block, draw_cross, is_bogen, ) # --------------------------------------------------------------------------- # Block-Definition # --------------------------------------------------------------------------- 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 (5, 0.0, 0.0, 3.0), # blau, Z-Achse, Laenge 3 ] def _ensure_block(doc, block_name): """Erzeugt den Block falls er noch nicht existiert.""" if block_name not in doc.blocks: blk = doc.blocks.new(name=block_name) for color, dx, dy, dz in KS_LINES: blk.add_line( start=(0, 0, 0), end=(dx, dy, dz), dxfattribs={"color": color}, ) return block_name def _ensure_layer(doc, layer_name="_KOORDINATENSYSTEME"): """Stellt sicher, dass der Layer existiert.""" if layer_name not in doc.layers: 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. Args: msp: Modelspace oder Block des Ziel-Dokuments. name: Block-Name (z.B. "K1", "K2", "K3", "K4"). point: Einfuegepunkt als (x, y, z) Tupel. rx: Rotation um X-Achse in Grad. ry: Rotation um Y-Achse in Grad. rz: Rotation um Z-Achse in Grad. Returns: Das erzeugte INSERT-Entity. """ doc = msp.doc _ensure_layer(doc) _ensure_block(doc, name) insert = msp.add_blockref( name, insert=point, dxfattribs={"layer": "_KOORDINATENSYSTEME"}, ) m = Matrix44.chain( Matrix44.translate(-point[0], -point[1], -point[2]), Matrix44.x_rotate(math.radians(rx)), Matrix44.y_rotate(math.radians(ry)), Matrix44.z_rotate(math.radians(rz)), Matrix44.translate(point[0], point[1], point[2]), ) insert.transform(m) return insert def read_ks(doc, name): """ Liest ein Koordinatensystem aus einer DXF-Datei zurueck. 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: lines = [e for e in entity.virtual_entities() if e.dxftype() == "LINE"] if len(lines) != 3: return None point = (lines[0].dxf.start[0], lines[0].dxf.start[1], lines[0].dxf.start[2]) axes = {} for line in lines: end = line.dxf.end dx = end[0] - point[0] dy = end[1] - point[1] dz = end[2] - point[2] length = (dx**2 + dy**2 + dz**2) ** 0.5 axes[line.dxf.color] = (dx / length, dy / length, dz / length) x_axis = axes.get(1, (1, 0, 0)) y_axis = axes.get(3, (0, 1, 0)) z_axis = axes.get(5, (0, 0, 1)) ry = math.asin(max(-1, min(1, -x_axis[2]))) cos_ry = math.cos(ry) if abs(cos_ry) > 1e-6: 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: rz = 0 rx = math.atan2(-z_axis[0], y_axis[0]) return { "name": name, "point": point, "rx": math.degrees(rx), "ry": math.degrees(ry), "rz": math.degrees(rz), } return None def verify_ks(dxf_path, expected): """Liest KS-Bloecke und vergleicht mit Erwartungswerten (Toleranz 0.01).""" doc = ezdxf.readfile(dxf_path) all_ok = True tol = 0.01 for exp in expected: result = read_ks(doc, exp["name"]) if result is None: print(f"FEHLER: Block '{exp['name']}' nicht gefunden") all_ok = False continue errors = [] for i, axis in enumerate(("x", "y", "z")): if abs(result["point"][i] - exp["point"][i]) > tol: errors.append(f"{axis}={result['point'][i]:.3f} (erwartet {exp['point'][i]:.3f})") for angle in ("rx", "ry", "rz"): diff = abs(result[angle] - exp[angle]) if diff > 360 - tol: diff = abs(diff - 360) if diff > tol: errors.append(f"{angle}={result[angle]:.2f} (erwartet {exp[angle]:.2f})") if errors: print(f"FEHLER {exp['name']}: {', '.join(errors)}") all_ok = False else: print(f"OK {exp['name']}: point=({result['point'][0]:.2f},{result['point'][1]:.2f},{result['point'][2]:.2f}) " f"rx={result['rx']:.2f} ry={result['ry']:.2f} rz={result['rz']:.2f}") return all_ok # --------------------------------------------------------------------------- # 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 # --------------------------------------------------------------------------- # K2-Positionsbestimmung (nur Boegen) # --------------------------------------------------------------------------- def k4_point_weiche(doc): """K4-Position fuer Weichen: oberes Ende der K1-Vertikallinie. K4 liegt K1 gegenueber, am oberen Ende derselben vertikalen Linie. Rotation wie K1: 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] top_y = max(s[1], e[1]) return (x, top_y, 0), 0, 0, 90 # --------------------------------------------------------------------------- # K2-Positionsbestimmung (nur Boegen) # --------------------------------------------------------------------------- def k2_point_bogen(doc): """K2-Position fuer Boegen: anderes Ende der Kette (Gerade-Bogen-Gerade). Sucht den Bogen, bestimmt welches Ende zur K1-Seite (oberste H-Linie) gehoert, findet die laengste Linie am anderen Bogenende und setzt K2 an deren aeusseres Ende. x-Achse tangential zur Linie (Richtung weg vom Bogen), y senkrecht dazu, z aus Zeichenebene (wie K1). Sonderfall 180-Grad-Bogen: x nach rechts (rz=0). """ msp = doc.modelspace() # Bogen finden arc = None for e in msp: if e.dxftype() == "ARC": arc = e break if arc is None: return None cx, cy = arc.dxf.center[0], arc.dxf.center[1] r = arc.dxf.radius sa, ea = arc.dxf.start_angle, arc.dxf.end_angle arc_start = (cx + r * math.cos(math.radians(sa)), cy + r * math.sin(math.radians(sa))) arc_end = (cx + r * math.cos(math.radians(ea)), cy + r * math.sin(math.radians(ea))) # K1-Seite = oberste horizontale Linie hline = find_topmost_horizontal_line(doc) if hline is None: return None h_y = hline.dxf.start[1] # Welches Bogenende liegt an der K1-H-Linie? tol = 1.0 if abs(arc_end[1] - h_y) < tol: k2_junction = arc_start else: k2_junction = arc_end # Laengste Linie am K2-Bogenende suchen best_line_len = 0 k2_pos = None junc_pt = None for e in msp: if e.dxftype() != "LINE": continue s = (e.dxf.start[0], e.dxf.start[1]) end = (e.dxf.end[0], e.dxf.end[1]) ds = ((s[0] - k2_junction[0])**2 + (s[1] - k2_junction[1])**2) ** 0.5 de = ((end[0] - k2_junction[0])**2 + (end[1] - k2_junction[1])**2) ** 0.5 if ds > tol and de > tol: continue ln = ((end[0] - s[0])**2 + (end[1] - s[1])**2) ** 0.5 if ln > best_line_len: best_line_len = ln if ds < tol: k2_pos = end junc_pt = s else: k2_pos = s junc_pt = end if k2_pos is None: return None # Bogenspanne berechnen span = (ea - sa) % 360 # Tangenten-Richtung: von K2 zur Junction (zur Bogenmitte) dx = junc_pt[0] - k2_pos[0] dy = junc_pt[1] - k2_pos[1] rz = math.degrees(math.atan2(dy, dx)) # 180-Grad-Sonderfall: x nach rechts, wie K1 if abs(span - 180) < 1: rz = 0 return (k2_pos[0], k2_pos[1], 0), 0, 0, rz # --------------------------------------------------------------------------- # K2-Positionsbestimmung Weichen # --------------------------------------------------------------------------- def _find_weiche_arc_branches(doc): """Findet alle Bogenzweig-Endpunkte einer Weiche. Returns: Liste von (k2_pos, junc_pt) Tupeln, sortiert nach x (links zuerst), oder None falls keine Boegen gefunden. """ msp = doc.modelspace() # K1-Position bestimmen (unterste vertikale Linie) vline = find_bottommost_vertical_line(doc) if vline is None: return None s, e = vline.dxf.start, vline.dxf.end k1_x = s[0] k1_y = min(s[1], e[1]) # Alle Boegen sammeln arcs = [e for e in msp if e.dxftype() == "ARC"] if not arcs: return None tol = 1.0 # Fuer jeden Bogen: K2-seitiges Ende bestimmen und Endpunkt der Linie finden candidates = [] for arc in arcs: cx, cy = arc.dxf.center[0], arc.dxf.center[1] r = arc.dxf.radius sa, ea = arc.dxf.start_angle, arc.dxf.end_angle arc_start = (cx + r * math.cos(math.radians(sa)), cy + r * math.sin(math.radians(sa))) arc_end = (cx + r * math.cos(math.radians(ea)), cy + r * math.sin(math.radians(ea))) # K1-Seite = naeher an K1 d_start = ((arc_start[0] - k1_x)**2 + (arc_start[1] - k1_y)**2) ** 0.5 d_end = ((arc_end[0] - k1_x)**2 + (arc_end[1] - k1_y)**2) ** 0.5 if d_start < d_end: k2_junction = arc_end else: k2_junction = arc_start # Laengste Linie am K2-Bogenende suchen best_len = 0 k2_pos = None junc_pt = None for ent in msp: if ent.dxftype() != "LINE": continue ls = (ent.dxf.start[0], ent.dxf.start[1]) le = (ent.dxf.end[0], ent.dxf.end[1]) ds = ((ls[0] - k2_junction[0])**2 + (ls[1] - k2_junction[1])**2) ** 0.5 de = ((le[0] - k2_junction[0])**2 + (le[1] - k2_junction[1])**2) ** 0.5 if ds > tol and de > tol: continue ln = ((le[0] - ls[0])**2 + (le[1] - ls[1])**2) ** 0.5 if ln > best_len: best_len = ln if ds < tol: k2_pos = le junc_pt = ls else: k2_pos = ls junc_pt = le if k2_pos is not None: candidates.append((k2_pos, junc_pt)) if not candidates: return None # Sortieren nach x: links (kleinstes x) zuerst, rechts (groesstes x) zuletzt candidates.sort(key=lambda c: c[0][0]) return candidates def _pick_branch(candidates, branch): """Waehlt linken oder rechten Zweig aus und berechnet rz.""" if branch == "left": k2_pos, junc_pt = candidates[0] else: k2_pos, junc_pt = candidates[-1] # x-Achse tangential zur Linie, zur Kurve zeigend dx = junc_pt[0] - k2_pos[0] dy = junc_pt[1] - k2_pos[1] rz = math.degrees(math.atan2(dy, dx)) return (k2_pos[0], k2_pos[1], 0), 0, 0, rz def k2_point_weiche_arc(doc): """K2 fuer Weichen: Ende des linken Bogenzweigs.""" candidates = _find_weiche_arc_branches(doc) if candidates is None: return None return _pick_branch(candidates, "left") def k3_point_weiche_arc(doc): """K3 fuer Weichen: Ende des rechten Bogenzweigs. Nur bei Weichen mit 2+ Boegen (Doppel-, Dreiwegeweichen etc.). Bei Einzelweichen (nur 1 Bogen) gibt es keinen rechten Zweig. """ candidates = _find_weiche_arc_branches(doc) if candidates is None or len(candidates) < 2: return None return _pick_branch(candidates, "right") # --------------------------------------------------------------------------- # K2/K3 fuer Weichenkoerper (keine ARCs, nur Diagonalen) # --------------------------------------------------------------------------- def _find_weichenkoerper_diag_branches(doc): """Findet die Enden der langen Diagonalen eines Weichenkoerpers. Weichenkoerper haben keine Boegen, sondern Diagonalen die sich an einem Punkt nahe der K1-Vertikallinie treffen. K2/K3 liegen an den aeusseren Enden der langen Diagonalen (Laenge > 100). Returns: Liste von (outer_pt, junction_pt) Tupeln, sortiert nach x (links zuerst), oder None falls keine langen Diagonalen gefunden. """ msp = doc.modelspace() # K1-Position bestimmen (unterste vertikale Linie) vline = find_bottommost_vertical_line(doc) if vline is None: return None s, e = vline.dxf.start, vline.dxf.end k1_x = s[0] k1_y = min(s[1], e[1]) # Lange Diagonalen finden (nicht horizontal, nicht vertikal, Laenge > 100) candidates = [] for ent in msp: if ent.dxftype() != "LINE": continue ls = (ent.dxf.start[0], ent.dxf.start[1]) le = (ent.dxf.end[0], ent.dxf.end[1]) dx = abs(le[0] - ls[0]) dy = abs(le[1] - ls[1]) if dx < 0.01 or dy < 0.01: continue ln = (dx**2 + dy**2) ** 0.5 if ln < 100: continue # Das Ende naeher an K1 ist die Junction, das andere ist der K2/K3-Punkt d_start = ((ls[0] - k1_x)**2 + (ls[1] - k1_y)**2) ** 0.5 d_end = ((le[0] - k1_x)**2 + (le[1] - k1_y)**2) ** 0.5 if d_start < d_end: candidates.append((le, ls)) else: candidates.append((ls, le)) if not candidates: return None # Sortieren nach x: links (kleinstes x) zuerst candidates.sort(key=lambda c: c[0][0]) return candidates def k2_point_weichenkoerper(doc): """K2 fuer Weichenkoerper: Ende der linken langen Diagonale.""" candidates = _find_weichenkoerper_diag_branches(doc) if candidates is None: return None outer_pt, junc_pt = candidates[0] dx = junc_pt[0] - outer_pt[0] dy = junc_pt[1] - outer_pt[1] rz = math.degrees(math.atan2(dy, dx)) return (outer_pt[0], outer_pt[1], 0), 0, 0, rz def k3_point_weichenkoerper(doc): """K3 fuer Weichenkoerper: Ende der rechten langen Diagonale. Nur bei Doppel- und Dreiwegeweichenkoerpern (2+ Diagonalen). """ candidates = _find_weichenkoerper_diag_branches(doc) if candidates is None or len(candidates) < 2: return None outer_pt, junc_pt = candidates[-1] dx = junc_pt[0] - outer_pt[0] dy = junc_pt[1] - outer_pt[1] rz = math.degrees(math.atan2(dy, dx)) return (outer_pt[0], outer_pt[1], 0), 0, 0, rz # --------------------------------------------------------------------------- # K2/K3 fuer Dreifach-, Delta-, Sternweiche (Enden der laengsten H-Linie) # --------------------------------------------------------------------------- def _find_longest_hline(doc): """Findet die laengste horizontale Linie im Modelspace.""" best = None best_len = 0 for e in doc.modelspace(): if e.dxftype() != "LINE": continue s, end = e.dxf.start, e.dxf.end if abs(end[1] - s[1]) > 0.01: continue ln = abs(end[0] - s[0]) if ln > best_len: best_len = ln best = e return best def k2_point_hline(doc): """K2 am linken Ende der laengsten horizontalen Linie. x zeigt nach rechts.""" hline = _find_longest_hline(doc) if hline is None: return None s, e = hline.dxf.start, hline.dxf.end lx = min(s[0], e[0]) y = s[1] return (lx, y, 0), 0, 0, 0 def k3_point_hline(doc): """K3 am rechten Ende der laengsten horizontalen Linie. x zeigt nach links.""" hline = _find_longest_hline(doc) if hline is None: return None s, e = hline.dxf.start, hline.dxf.end rx = max(s[0], e[0]) y = s[1] return (rx, y, 0), 0, 0, 180 # --------------------------------------------------------------------------- # K2/K3 Schalter-Zuordnung: SWITCH_FILTERS-Key -> Funktion # K2 = linker Zweig, K3 = rechter Zweig (nur bei 2+ Boegen/Diagonalen) # --------------------------------------------------------------------------- K2_FUNCS = { "boegen": k2_point_bogen, "weichen45": k2_point_weiche_arc, "weichen90": k2_point_weiche_arc, "weichenkoerper": k2_point_weichenkoerper, "weichen_parallel": k2_point_weiche_arc, "sternweiche": k2_point_hline, "delta": k2_point_hline, "dreifachweiche": k2_point_hline, } K3_FUNCS = { "weichen45": k3_point_weiche_arc, "weichen90": k3_point_weiche_arc, "weichenkoerper": k3_point_weichenkoerper, "weichen_parallel": k3_point_weiche_arc, "sternweiche": k3_point_hline, "delta": k3_point_hline, "dreifachweiche": k3_point_hline, } # K4: oberes Ende der K1-Linie. Kein K4 bei: Boegen, Doppelweiche, # Dreifachweiche, Deltaweiche (inkl. Weichenkoerper Doppel). K4_FUNCS = { "weichen45": k4_point_weiche, "weichen90": k4_point_weiche, "weichenkoerper": k4_point_weiche, "weichen_parallel": k4_point_weiche, "sternweiche": k4_point_weiche, } _K4_EXCLUDE_TYPES = ("Doppelweiche", "Dreifachweiche", "Deltaweiche") # --------------------------------------------------------------------------- # Existenz-Pruefung und Duplikat-Bereinigung # --------------------------------------------------------------------------- def _check_and_clean_ks(msp, k_name, sivasnr, force=False): """Prueft ob ein Kx bereits existiert und bereinigt Duplikate. - Mehrfach vorhandene Kx: alle bis auf das letzte entfernen (immer). - Genau ein Kx vorhanden und kein --force: skip (return False). - Genau ein Kx vorhanden und --force: entfernen (return True). - Kein Kx vorhanden: return True (neu erzeugen). Returns: True = Kx soll (neu) erzeugt werden. False = Kx existiert bereits, skip. """ existing = [e for e in msp if e.dxftype() == "INSERT" and e.dxf.name == k_name] if len(existing) > 1: # Duplikate: alle bis auf das letzte entfernen for e in existing[:-1]: msp.delete_entity(e) print(f" {sivasnr}: {len(existing)-1} Duplikat(e) von {k_name} entfernt.") existing = existing[-1:] if len(existing) == 1: if force: msp.delete_entity(existing[0]) return True else: print(f" {sivasnr}: skipping {k_name} (bereits vorhanden)") return False return True # --------------------------------------------------------------------------- # --k1set # --------------------------------------------------------------------------- def process_k1set(data_dir, results_dir, number=None, force=False): """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) msp = doc.modelspace() if not _check_and_clean_ks(msp, "K1", sivasnr, force): continue 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 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}") # --------------------------------------------------------------------------- # --k2set / --k3set # --------------------------------------------------------------------------- def _process_kset(k_name, funcs, data_dir, results_dir, number=None, force=False): """Generische Verarbeitung fuer K2/K3: iteriert ueber SWITCH_FILTERS.""" omniflo_dir = os.path.join(data_dir, "omniflo") for key, k_func in funcs.items(): label, json_file, filter_func = SWITCH_FILTERS[key] json_path = os.path.join(data_dir, "json", json_file) if not os.path.exists(json_path): continue with open(json_path, "r", encoding="utf-8") as f: all_items = json.load(f) filtered = [item for item in all_items if filter_func(item)] if number: filtered = [item for item in filtered if str(item["Sivasnr"]) == str(number)] if not filtered: continue print(f"=== {k_name} setzen: {label} ===") for item in filtered: sivasnr = str(item["Sivasnr"]) 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) msp = doc.modelspace() if not _check_and_clean_ks(msp, k_name, sivasnr, force): continue result = k_func(doc) if result is None: continue point, rx, ry, rz = result insert_ks(msp, k_name, point, rx, ry, rz) out_path = os.path.join(results_dir, f"{sivasnr}.dxf") doc.saveas(out_path) print(f" {sivasnr}: {k_name} at ({point[0]:.2f},{point[1]:.2f},{point[2]:.2f}) " f"rx={rx} ry={ry} rz={rz:.1f} [{item['ProfilTyp']}]") print(f"\nErgebnisse in: {results_dir}") def process_k2set(data_dir, results_dir, number=None, force=False): """Setzt K2-Block (linker Bogenzweig) an alle Boegen und Weichen.""" _process_kset("K2", K2_FUNCS, data_dir, results_dir, number, force) def process_k3set(data_dir, results_dir, number=None, force=False): """Setzt K3-Block (rechter Bogenzweig) an Weichen mit 2+ Boegen.""" _process_kset("K3", K3_FUNCS, data_dir, results_dir, number, force) def process_k4set(data_dir, results_dir, number=None, force=False): """Setzt K4-Block (oberes K1-Linienende) an Weichen ohne Doppel/Dreifach/Delta.""" omniflo_dir = os.path.join(data_dir, "omniflo") for key, k4_func in K4_FUNCS.items(): label, json_file, filter_func = SWITCH_FILTERS[key] json_path = os.path.join(data_dir, "json", json_file) if not os.path.exists(json_path): continue with open(json_path, "r", encoding="utf-8") as f: all_items = json.load(f) # SWITCH_FILTERS-Filter + K4-Ausschluss (Doppelweiche etc.) filtered = [item for item in all_items if filter_func(item) and item.get("WeichenTyp", "") not in _K4_EXCLUDE_TYPES] if number: filtered = [item for item in filtered if str(item["Sivasnr"]) == str(number)] if not filtered: continue print(f"=== K4 setzen: {label} ===") for item in filtered: sivasnr = str(item["Sivasnr"]) 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) msp = doc.modelspace() if not _check_and_clean_ks(msp, "K4", sivasnr, force): continue result = k4_func(doc) if result is None: continue point, rx, ry, rz = result insert_ks(msp, "K4", point, rx, ry, rz) out_path = os.path.join(results_dir, f"{sivasnr}.dxf") doc.saveas(out_path) print(f" {sivasnr}: K4 at ({point[0]:.2f},{point[1]:.2f},{point[2]:.2f}) " f"rx={rx} ry={ry} rz={rz:.1f} [{item['ProfilTyp']}]") 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() test_cases = [ {"name": "K1", "point": (0, 0, 0), "rx": 0, "ry": 0, "rz": 0}, {"name": "K2", "point": (10, 0, 0), "rx": 0, "ry": 0, "rz": 90}, {"name": "K3", "point": (20, 0, 0), "rx": 0, "ry": 45, "rz": 0}, {"name": "K4", "point": (30, 0, 0), "rx": 30, "ry": 45, "rz": 60}, ] for tc in test_cases: insert_ks(msp, tc["name"], tc["point"], tc["rx"], tc["ry"], tc["rz"]) out_path = os.path.join(results_dir, "ks_test.dxf") doc.saveas(out_path) print(f"Testdatei gespeichert: {out_path}\n") 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("--k2set", action="store_true", help="K2-Block setzen (linker Bogenzweig)") parser.add_argument("--k3set", action="store_true", help="K3-Block setzen (rechter Bogenzweig, nur Doppel-/Dreiwegeweichen)") parser.add_argument("--k4set", action="store_true", help="K4-Block setzen (oberes K1-Linienende, nicht Doppel/Dreifach/Delta)") parser.add_argument("--set-all", action="store_true", help="Alle Koordinatensysteme (K1-K4) in einem Durchgang setzen") parser.add_argument("--force", action="store_true", help="Bestehende Kx ueberschreiben (sonst skip)") 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.k2set and not args.k3set and not args.k4set and not args.set_all 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.set_all: process_k1set(data_dir, results_dir, args.number, args.force) process_k2set(data_dir, results_dir, args.number, args.force) process_k3set(data_dir, results_dir, args.number, args.force) process_k4set(data_dir, results_dir, args.number, args.force) if args.k1set: process_k1set(data_dir, results_dir, args.number, args.force) if args.k2set: process_k2set(data_dir, results_dir, args.number, args.force) if args.k3set: process_k3set(data_dir, results_dir, args.number, args.force) if args.k4set: process_k4set(data_dir, results_dir, args.number, args.force) if args.show_omniflo: print("=== Koordinaten Uebersicht ===") show_omniflo(data_dir, results_dir) if __name__ == "__main__": main()