tro_annotate: Lagebezug ueber die TeileId, Ablaufbeschreibung CSV/DXF -> SCL

dxf_registration.derive_from_ids: Zeichnungen aus dxfmakros tragen an jedem
Layout-Block das Attribut ID = TeileId des CSV. tro_annotate paart CSV-
Separatoren mit den Separator-Bloecken gleicher ID (sonst Scanner) und nimmt
den Median-Versatz - eindeutig statt Abstimmung, bei jedem Lauf neu, gilt
auch fuer eine verschobene Kopie der Zeichnung. Vorrang vor abgelegtem und
abgestimmtem Bezug; --offset bleibt. Parameter id_spread_mm, id_min_share in
cfg/parameter.ini [dxf_registration].

Dematic: gemeinsame Zeichnung 0/0, Mechanik-Zeichnung dematic.dxf
+466,1/-163549,3 mm. Die Abstimmung rastete vorher auf den Scanner-Bloecken
ein (Einfuegepunkt 360 mm neben der CSV-Position) und lag 360 mm daneben.

Gesperrte Ausgabedatei (in BricsCAD geoeffnet) gibt eine Meldung statt
Traceback. Neu: doc/Workflow_CSV_DXF_to_SCL.md - der ganze Ablauf vom
CSV-Export und der Zeichnung bis zur SCL.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-23 14:59:16 +02:00
parent e5d1fce41f
commit 9ec1022fb1
6 changed files with 512 additions and 10 deletions
+89
View File
@@ -0,0 +1,89 @@
# -*- coding: utf-8 -*-
"""Tests fuer den Lagebezug ueber die TeileId (dxf_registration.derive_from_ids).
Nachbau der Dematic-Befunde: dieselbe Anlage einmal als gemeinsame Zeichnung
(Versatz 0) und einmal als verschobene Mechanik-Zeichnung (~163 m in y). Die
Separator-Bloecke sitzen je nach Drehung +-60 mm neben der CSV-Position, die
Scanner-Bloecke 360 mm - die automatische Abstimmung rastete dort auf den
Scannern ein und lag 360 mm daneben.
"""
import ezdxf
import pytest
import tro_annotate
from dxf_registration import MIN_INLIERS, derive_from_ids
SHIFT = (-466.1, 163549.3) # DXF = CSV + SHIFT (Dematic Mechanik)
def _csv(n=11):
seps = {f"{38 + i:04d}": ("Separator", 3000.0 + i * 800.0, 12120.0) for i in range(n)}
scans = {f"{60 + i:04d}": ("Scanner", 3000.0 + i * 900.0, 25000.0) for i in range(7)}
return {**seps, **scans}
def _dxf(csv_points, shift=SHIFT):
out = {}
for i, (tid, (rolle, x, y)) in enumerate(csv_points.items()):
if rolle == "Separator":
bias = (-60.0 if i % 3 else 60.0, 0.0)
else:
bias = (0.0, -360.0)
out[tid] = (rolle, x + shift[0] + bias[0], y + shift[1] + bias[1])
return out
def test_verschobene_zeichnung_ueber_die_separatoren():
csv_points = _csv()
t = derive_from_ids(csv_points, _dxf(csv_points), "x.csv", "mech.dxf")
assert t is not None and t.source == "ids"
# DXF -> CSV: Gegenrichtung des Verschubs, Separator-Bias hoechstens 60 mm
assert abs(t.dx - (-SHIFT[0])) <= 60.0 + 1e-6
assert t.dy == pytest.approx(-SHIFT[1])
assert "Separator" in t.anchor
def test_gemeinsame_zeichnung_versatz_null():
csv_points = _csv()
t = derive_from_ids(csv_points, _dxf(csv_points, shift=(0.0, 0.0)))
assert abs(t.dx) <= 60.0 + 1e-6 and t.dy == pytest.approx(0.0)
def test_ohne_genug_separatoren_die_scanner():
csv_points = _csv(n=MIN_INLIERS - 1)
t = derive_from_ids(csv_points, _dxf(csv_points))
assert t is not None and "Scanner" in t.anchor
assert t.dy == pytest.approx(-SHIFT[1] + 360.0) # Scanner-Bias bleibt drin
def test_ohne_ids_kein_ergebnis():
assert derive_from_ids(_csv(), {}) is None
def test_uneinheitlicher_versatz_wird_nicht_genommen():
csv_points = _csv()
dxf = _dxf(csv_points)
# die Haelfte der Separatoren weit daneben (falsche IDs) -> keine Rolle passt
for tid in list(dxf)[:6]:
rolle, x, y = dxf[tid]
dxf[tid] = (rolle, x + 5000.0, y)
for tid in [k for k, v in dxf.items() if v[0] == "Scanner"]:
del dxf[tid]
assert derive_from_ids(csv_points, dxf) is None
def test_ids_aus_der_zeichnung():
doc = ezdxf.new()
blk = doc.blocks.new("Separator_SP_3D")
blk.add_attdef("ID", (0, 0))
andere = doc.blocks.new("KREISEL_1_STANDARD_3619")
andere.add_attdef("ID", (0, 0))
msp = doc.modelspace()
msp.add_blockref("Separator_SP_3D", (10, 20)).add_auto_attribs({"ID": "0038"})
msp.add_blockref("Separator_SP_3D", (30, 40)).add_auto_attribs({"ID": "0039"})
msp.add_blockref("Separator_SP_3D", (50, 60)).add_auto_attribs({"ID": "0039"})
msp.add_blockref("Separator_SP_3D", (70, 80)).add_auto_attribs({"ID": ""})
msp.add_blockref("KREISEL_1_STANDARD_3619", (0, 0)).add_auto_attribs({"ID": "0001"})
# nur eindeutige Ziffern-IDs von Anker-Bloecken
assert tro_annotate.id_points_dxf(msp) == {"0038": ("Separator", 10.0, 20.0)}