K2 der Bögen wird erzeugt

This commit is contained in:
2026-05-12 17:28:11 +02:00
parent 69d71ff741
commit 89dccc139f
3 changed files with 475 additions and 210 deletions
+20 -173
View File
@@ -30,8 +30,11 @@ import os
import sys import sys
import ezdxf 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 # 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): def show_omniflo(data_dir, results_dir):
"""Erzeugt eine Uebersichts-DXF mit allen Omniflo-Elementen in Reihen.""" """Erzeugt eine Uebersichts-DXF mit allen Omniflo-Elementen in Reihen."""
boegen_path = os.path.join(data_dir, "json", "omniflo_boegen.json") sources, rows = build_row_layout(data_dir, results_dir)
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 = ezdxf.new(dxfversion='R2010')
target_msp = target.modelspace() target_msp = target.modelspace()
target.layers.add('ANNOTATION', color=7) target.layers.add('ANNOTATION', color=7)
target.layers.add('INSPOINT', color=1) target.layers.add('INSPOINT', color=1)
target.layers.add('INSLINE', color=5)
target.layers.add('ROW_LABEL', color=3) target.layers.add('ROW_LABEL', color=3)
cursor_y = 0.0
block_counter = 0 block_counter = 0
for label, source_key, filter_func in ROW_GROUPS: for row in rows:
items = [item for item in sources[source_key] if filter_func(item)] label_y = row['cursor_y'] + row['max_height'] / 2
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( target_msp.add_mtext(
label, row['label'],
dxfattribs={ dxfattribs={
'layer': 'ROW_LABEL', 'layer': 'ROW_LABEL',
'char_height': TEXT_HEIGHT * 1.2, 'char_height': TEXT_HEIGHT * 1.2,
} }
).set_location(insert=(-ROW_LABEL_WIDTH, label_y)) ).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_name = f"BLK_{block_counter}"
block_counter += 1 block_counter += 1
importer = Importer(elem['source'], target) import_element_as_block(elem['source'], target, block_name)
importer.import_tables() target_msp.add_blockref(block_name,
blk = target.blocks.new(name=block_name) insert=(elem['offset_x'], elem['offset_y']))
for entity in elem['source'].modelspace():
importer.import_entity(entity, blk)
importer.finalize()
offset_x = cursor_x - elem['extmin'][0] insbase = elem['source'].header.get("$INSBASE", (0, 0, 0))
offset_y = cursor_y - elem['extmin'][1] ipx = insbase[0] + elem['offset_x']
target_msp.add_blockref(block_name, insert=(offset_x, offset_y)) ipy = insbase[1] + elem['offset_y']
draw_cross(target_msp, ipx, ipy, CROSS_SIZE, 1, 'INSPOINT')
ipx = elem['insbase'][0] + offset_x text_x = elem['offset_x'] + elem['extmin'][0]
ipy = elem['insbase'][1] + offset_y text_y = row['cursor_y'] + elem['height'] + TEXT_MARGIN
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( target_msp.add_mtext(
elem['sivasnr'], elem['sivasnr'],
dxfattribs={ dxfattribs={
@@ -530,14 +381,10 @@ def show_omniflo(data_dir, results_dir):
} }
).set_location(insert=(text_x, text_y)) ).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") out_path = os.path.join(results_dir, "omniflo_uebersicht.dxf")
target.saveas(out_path) target.saveas(out_path)
print(f"Uebersicht gespeichert: {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")
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
+276 -37
View File
@@ -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: Jeder Block enthaelt drei Linien vom Ursprung:
- rot(1) X-Achse, Laenge 1 - rot(1) X-Achse, Laenge 1
- gruen(3) Y-Achse, Laenge 2 - gruen(3) Y-Achse, Laenge 2
- blau(5) Z-Achse, Laenge 3 - 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 math
import os
import sys
import ezdxf import ezdxf
from ezdxf.math import Matrix44 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 = [ KS_LINES = [
(1, 1.0, 0.0, 0.0), # rot, X-Achse, Laenge 1 (1, 1.0, 0.0, 0.0), # rot, X-Achse, Laenge 1
(3, 0.0, 2.0, 0.0), # gruen, Y-Achse, Laenge 2 (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) doc.layers.add(layer_name)
# ---------------------------------------------------------------------------
# insert / read / verify
# ---------------------------------------------------------------------------
def insert_ks(msp, name, point, rx=0, ry=0, rz=0): def insert_ks(msp, name, point, rx=0, ry=0, rz=0):
""" """
Erzeugt ein benanntes Koordinatensystem als Block-Referenz. 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"}, dxfattribs={"layer": "_KOORDINATENSYSTEME"},
) )
# 3D-Rotation via Transformationsmatrix
m = Matrix44.chain( m = Matrix44.chain(
Matrix44.translate(-point[0], -point[1], -point[2]), Matrix44.translate(-point[0], -point[1], -point[2]),
Matrix44.x_rotate(math.radians(rx)), Matrix44.x_rotate(math.radians(rx)),
@@ -83,28 +108,18 @@ def read_ks(doc, name):
""" """
Liest ein Koordinatensystem aus einer DXF-Datei zurueck. 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: Returns:
Dict mit 'name', 'point', 'rx', 'ry', 'rz' oder None. Dict mit 'name', 'point', 'rx', 'ry', 'rz' oder None.
""" """
msp = doc.modelspace() msp = doc.modelspace()
for entity in msp: for entity in msp:
if entity.dxftype() == "INSERT" and entity.dxf.name == name: 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"] lines = [e for e in entity.virtual_entities() if e.dxftype() == "LINE"]
if len(lines) != 3: if len(lines) != 3:
return None return None
# Gemeinsamer Startpunkt = Einfuegepunkt
point = (lines[0].dxf.start[0], lines[0].dxf.start[1], lines[0].dxf.start[2]) point = (lines[0].dxf.start[0], lines[0].dxf.start[1], lines[0].dxf.start[2])
# Richtungsvektoren aus den Endpunkten extrahieren
axes = {} axes = {}
for line in lines: for line in lines:
end = line.dxf.end end = line.dxf.end
@@ -114,18 +129,10 @@ def read_ks(doc, name):
length = (dx**2 + dy**2 + dz**2) ** 0.5 length = (dx**2 + dy**2 + dz**2) ** 0.5
axes[line.dxf.color] = (dx / length, dy / length, dz / length) 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)) x_axis = axes.get(1, (1, 0, 0))
y_axis = axes.get(3, (0, 1, 0)) y_axis = axes.get(3, (0, 1, 0))
z_axis = axes.get(5, (0, 0, 1)) 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]))) ry = math.asin(max(-1, min(1, -x_axis[2])))
cos_ry = math.cos(ry) 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) 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) rz = math.atan2(x_axis[1] / cos_ry, x_axis[0] / cos_ry)
else: else:
# Gimbal Lock: ry = +/-90, rx und rz nicht unabhaengig
rz = 0 rz = 0
rx = math.atan2(-z_axis[0], y_axis[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): def verify_ks(dxf_path, expected):
""" """Liest KS-Bloecke und vergleicht mit Erwartungswerten (Toleranz 0.01)."""
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).
"""
doc = ezdxf.readfile(dxf_path) doc = ezdxf.readfile(dxf_path)
all_ok = True all_ok = True
tol = 0.01 tol = 0.01
@@ -191,9 +188,197 @@ def verify_ks(dxf_path, expected):
return all_ok 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") doc = ezdxf.new(dxfversion="R2010")
msp = doc.modelspace() msp = doc.modelspace()
@@ -207,8 +392,6 @@ if __name__ == "__main__":
for tc in test_cases: for tc in test_cases:
insert_ks(msp, tc["name"], tc["point"], tc["rx"], tc["ry"], tc["rz"]) 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") out_path = os.path.join(results_dir, "ks_test.dxf")
doc.saveas(out_path) doc.saveas(out_path)
print(f"Testdatei gespeichert: {out_path}\n") print(f"Testdatei gespeichert: {out_path}\n")
@@ -216,3 +399,59 @@ if __name__ == "__main__":
print("Verifikation:") print("Verifikation:")
ok = verify_ks(out_path, test_cases) ok = verify_ks(out_path, test_cases)
print(f"\nErgebnis: {'ALLE OK' if ok else 'FEHLER GEFUNDEN'}") 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()
+179
View File
@@ -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})