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
+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:
- 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()