""" Setzt den Einfuegepunkt ($INSBASE) von Omniflo DXF-Dateien. Kriterien je Typ: --boegen Schnittpunkt der Tangenten am Anfangs- und Endpunkt der Bogenkurve (bei 180-Grad: Mittelpunkt). --weichen45 Schnittpunkt der laengsten vertikalen Linie mit einer --weichen90 Horizontalen durch den Endpunkt der Bogenkurve. --weichenkoerper Schnittpunkt der laengsten Diagonale mit der laengsten vertikalen Linie (Konvergenzpunkt aller Linien). --weichen-parallel Wie --weichenkoerper: Schnittpunkt der laengsten Diagonale mit der laengsten vertikalen Linie. --delta Mittelpunkt der laengsten horizontalen Linie. --dreifachweiche Mittelpunkt der laengsten horizontalen Linie. --sternweiche Zentrum: Mittelpunkt der laengsten horizontalen Linie. --number SIVASNR Nur diese eine 9-stellige Sivasnr verarbeiten. --show-omniflo Uebersichts-DXF mit allen Elementen erzeugen. """ import argparse import json import math import os import sys import ezdxf from ezdxf import bbox as ezdxf_bbox from ezdxf.addons.importer import Importer # --------------------------------------------------------------------------- # Geometrie-Hilfsfunktionen # --------------------------------------------------------------------------- def find_arc(doc): """Findet die erste ARC-Entity im Modelspace.""" msp = doc.modelspace() for entity in msp: if entity.dxftype() == "ARC": return entity return None def tangent_intersection(arc): """Berechnet den Schnittpunkt der Tangenten am Start- und Endpunkt des Bogens. Bei 180-Grad-Boegen wird der Mittelpunkt zwischen Start und Ende verwendet. """ cx, cy, _ = arc.dxf.center r = arc.dxf.radius a_start = math.radians(arc.dxf.start_angle) a_end = math.radians(arc.dxf.end_angle) p1x = cx + r * math.cos(a_start) p1y = cy + r * math.sin(a_start) p2x = cx + r * math.cos(a_end) p2y = cy + r * math.sin(a_end) d1x = -math.sin(a_start) d1y = math.cos(a_start) d2x = -math.sin(a_end) d2y = math.cos(a_end) det = d1x * (-d2y) - (-d2x) * d1y if abs(det) < 1e-9: return (p1x + p2x) / 2, (p1y + p2y) / 2 dx = p2x - p1x dy = p2y - p1y t = (-dx * d2y + dy * d2x) / det ix = p1x + t * d1x iy = p1y + t * d1y return ix, iy def find_longest_vertical_line(doc): """Findet die laengste vertikale Linie im Modelspace.""" msp = doc.modelspace() longest = None longest_len = 0 for entity in msp: if entity.dxftype() != "LINE": continue start = entity.dxf.start end = entity.dxf.end dx = abs(end[0] - start[0]) if dx > 0.01: continue length = abs(end[1] - start[1]) if length > longest_len: longest_len = length longest = entity return longest def find_longest_horizontal_line(doc): """Findet die laengste horizontale Linie im Modelspace.""" msp = doc.modelspace() longest = None longest_len = 0 for entity in msp: if entity.dxftype() != "LINE": continue start = entity.dxf.start end = entity.dxf.end dy = abs(end[1] - start[1]) if dy > 0.01: continue length = abs(end[0] - start[0]) if length > longest_len: longest_len = length longest = entity return longest def arc_endpoint(arc): """Gibt den Endpunkt (end_angle) des Bogens zurueck.""" cx, cy, _ = arc.dxf.center r = arc.dxf.radius a_end = math.radians(arc.dxf.end_angle) return cx + r * math.cos(a_end), cy + r * math.sin(a_end) # --------------------------------------------------------------------------- # Einfuegepunkt-Berechnungen (je Typ) # --------------------------------------------------------------------------- def boegen_insertion_point(doc): """Tangentenschnittpunkt der Bogenkurve.""" arc = find_arc(doc) if arc is None: return None return tangent_intersection(arc) def weichen_insertion_point(doc): """Schnittpunkt der laengsten vertikalen Linie mit der Tangente am oberen Arc-Endpunkt.""" vline = find_longest_vertical_line(doc) if vline is None: return None vx = vline.dxf.start[0] # Alle Arcs sammeln, oberen Punkt (max Y aus Start/Ende) waehlen best_angle = None best_y = -float('inf') best_arc = None for entity in doc.modelspace(): if entity.dxftype() != "ARC": continue cx, cy, _ = entity.dxf.center r = entity.dxf.radius for angle_deg in (entity.dxf.start_angle, entity.dxf.end_angle): a = math.radians(angle_deg) py = cy + r * math.sin(a) if py > best_y: best_y = py best_angle = a best_arc = entity if best_arc is None: return None cx, cy, _ = best_arc.dxf.center r = best_arc.dxf.radius ex = cx + r * math.cos(best_angle) ey = cy + r * math.sin(best_angle) tdx = -math.sin(best_angle) tdy = math.cos(best_angle) if abs(tdx) < 1e-9: return vx, ey t = (vx - ex) / tdx return vx, ey + t * tdy def weichenkoerper_insertion_point(doc): """Schnittpunkt der laengsten Diagonale mit der vertikalen Linie.""" vline = find_longest_vertical_line(doc) if vline is None: return None vx = vline.dxf.start[0] best_point = None best_len = 0 for entity in doc.modelspace(): if entity.dxftype() != "LINE": continue s = entity.dxf.start e = entity.dxf.end dx = abs(e[0] - s[0]) dy = abs(e[1] - s[1]) if dx < 0.01 or dy < 0.01: continue length = (dx ** 2 + dy ** 2) ** 0.5 if abs(s[0] - vx) < 0.01 and length > best_len: best_point = (s[0], s[1]) best_len = length if abs(e[0] - vx) < 0.01 and length > best_len: best_point = (e[0], e[1]) best_len = length return best_point def horizontal_midpoint_insertion(doc): """Mittelpunkt der laengsten horizontalen Linie.""" hline = find_longest_horizontal_line(doc) if hline is None: return None sx = hline.dxf.start[0] ex = hline.dxf.end[0] y = hline.dxf.start[1] return (sx + ex) / 2, y # --------------------------------------------------------------------------- # Schalter-Definitionen: (Label, JSON-Datei, Filter, Einfuegepunkt-Funktion) # --------------------------------------------------------------------------- SWITCH_DEFS = { "boegen": ( "Boegen", "omniflo_boegen.json", lambda item: True, boegen_insertion_point, ), "weichen45": ( "Weichen 45 Grad", "omniflo_weichen.json", lambda item: item["KurvenWinkel"] == 45 and item["WeichenTyp"] not in ("Dreifachweiche", "Deltaweiche"), weichen_insertion_point, ), "weichen90": ( "Weichen 90 Grad", "omniflo_weichen.json", lambda item: item["KurvenWinkel"] == 90 and item["WeichenTyp"] not in ("Dreifachweiche", "Deltaweiche"), weichen_insertion_point, ), "weichenkoerper": ( "Weichenkoerper", "omniflo_weichen.json", lambda item: item["KurvenWinkel"] == 22.5, weichenkoerper_insertion_point, ), "weichen_parallel": ( "Weichen Parallel", "omniflo_weichen.json", lambda item: item["KurvenWinkel"] == 0 and item["WeichenTyp"] != "Sternweiche", weichenkoerper_insertion_point, ), "sternweiche": ( "Sternweiche", "omniflo_weichen.json", lambda item: item["WeichenTyp"] == "Sternweiche", horizontal_midpoint_insertion, ), "delta": ( "Deltaweichen", "omniflo_weichen.json", lambda item: item["WeichenTyp"] == "Deltaweiche", horizontal_midpoint_insertion, ), "dreifachweiche": ( "Dreifachweichen", "omniflo_weichen.json", lambda item: item["WeichenTyp"] == "Dreifachweiche", horizontal_midpoint_insertion, ), } def process_dxf(data_dir, label, json_file, filter_func, insertion_func, results_dir, number=None): """Generische Verarbeitung: filtert IDs aus JSON, berechnet Einfuegepunkt, speichert.""" json_path = os.path.join(data_dir, "json", json_file) if not os.path.exists(json_path): print(f"FEHLER: {json_path} nicht gefunden.") sys.exit(1) with open(json_path, "r", encoding="utf-8") as f: items = json.load(f) omniflo_dir = os.path.join(data_dir, "omniflo") filtered = [item for item in items if filter_func(item)] if number: filtered = [item for item in filtered if str(item["Sivasnr"]) == str(number)] if not filtered: print(f"Keine Elemente fuer '{label}' gefunden.") return print(f"=== {label} ===") for item in filtered: sivasnr = str(item["Sivasnr"]) dxf_path = os.path.join(omniflo_dir, f"{sivasnr}.dxf") if not os.path.exists(dxf_path): print(f"WARNUNG: {dxf_path} nicht gefunden, ueberspringe.") continue doc = ezdxf.readfile(dxf_path) result = insertion_func(doc) if result is None: print(f"WARNUNG: Kein Einfuegepunkt in {sivasnr}.dxf bestimmbar, ueberspringe.") continue ix, iy = result old_base = doc.header.get("$INSBASE", (0, 0, 0)) doc.header["$INSBASE"] = (ix, iy, 0) out_path = os.path.join(results_dir, f"{sivasnr}.dxf") doc.saveas(out_path) print( f"{sivasnr}: INSBASE ({old_base[0]:.2f}, {old_base[1]:.2f}) " f"-> ({ix:.2f}, {iy:.2f}) [{item['ProfilTyp']}]" ) print(f"\nErgebnisse in: {results_dir}") # --------------------------------------------------------------------------- # 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") 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 target_msp.add_mtext( 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: 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() 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)) 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 target_msp.add_mtext( elem['sivasnr'], dxfattribs={ 'layer': 'ANNOTATION', 'char_height': TEXT_HEIGHT, } ).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") # --------------------------------------------------------------------------- # main # --------------------------------------------------------------------------- def main(): parser = argparse.ArgumentParser( description="Setzt Einfuegepunkte fuer Omniflo DXF-Dateien" ) for key, (label, _, _, _) in SWITCH_DEFS.items(): arg_name = f"--{key.replace('_', '-')}" parser.add_argument(arg_name, action="store_true", help=f"Einfuegepunkt fuer {label} setzen") parser.add_argument( "--number", type=int, help="Nur diese 9-stellige Sivasnr verarbeiten", ) parser.add_argument( "--show-omniflo", action="store_true", help="Uebersichts-DXF mit allen Omniflo-Elementen erzeugen", ) 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) active = [key for key in SWITCH_DEFS if getattr(args, key)] if not active and not args.show_omniflo: 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: print("FEHLER: Umgebungsvariablen DXFM_DATA und DXFM_RESULTS muessen gesetzt sein.") print("Bitte zuerst setenv.bat ausfuehren.") sys.exit(1) os.makedirs(results_dir, exist_ok=True) for key in active: label, json_file, filter_func, insertion_func = SWITCH_DEFS[key] process_dxf(data_dir, label, json_file, filter_func, insertion_func, results_dir, args.number) if args.show_omniflo: print("=== Omniflo Uebersicht ===") show_omniflo(data_dir, results_dir) if __name__ == "__main__": main()