407 lines
13 KiB
Python
407 lines
13 KiB
Python
"""
|
|
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 omniflo_utils import (
|
|
ROW_GROUPS, TEXT_HEIGHT, TEXT_MARGIN, CROSS_SIZE, ROW_LABEL_WIDTH,
|
|
SWITCH_FILTERS,
|
|
build_row_layout, import_element_as_block, draw_cross,
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 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)
|
|
# Filter kommen aus SWITCH_FILTERS in utils.py.
|
|
# ---------------------------------------------------------------------------
|
|
|
|
_INSERTION_FUNCS = {
|
|
"boegen": boegen_insertion_point,
|
|
"weichen45": weichen_insertion_point,
|
|
"weichen90": weichen_insertion_point,
|
|
"weichenkoerper": weichenkoerper_insertion_point,
|
|
"weichen_parallel": weichenkoerper_insertion_point,
|
|
"sternweiche": horizontal_midpoint_insertion,
|
|
"delta": horizontal_midpoint_insertion,
|
|
"dreifachweiche": horizontal_midpoint_insertion,
|
|
}
|
|
|
|
SWITCH_DEFS = {
|
|
key: (*SWITCH_FILTERS[key], _INSERTION_FUNCS[key])
|
|
for key in SWITCH_FILTERS
|
|
}
|
|
|
|
|
|
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 show_omniflo(data_dir, results_dir):
|
|
"""Erzeugt eine Uebersichts-DXF mit allen Omniflo-Elementen in Reihen."""
|
|
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('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']))
|
|
|
|
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')
|
|
|
|
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, "omniflo_uebersicht.dxf")
|
|
target.saveas(out_path)
|
|
print(f"Uebersicht gespeichert: {out_path}")
|
|
print(f" {block_counter} Elemente in {len(rows)} 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()
|