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
+58 -2
View File
@@ -101,6 +101,7 @@ from dxf_registration import (
ANCHOR_BLOCKS,
RegistrationError,
Transform,
derive_from_ids,
derive_transform,
load_transform,
save_transform,
@@ -229,14 +230,63 @@ def anchor_points_dxf(msp) -> dict[str, list[tuple[float, float]]]:
return dict(points)
def _role_of(block_name: str) -> str:
"""Rolle eines Blocks nach ANCHOR_BLOCKS ("" wenn keine)."""
name = block_name.upper()
for role, patterns in ANCHOR_BLOCKS.items():
if any(fnmatch.fnmatchcase(name, p.upper()) for p in patterns):
return role
return ""
def id_points_csv(elements) -> dict[str, tuple[str, float, float]]:
"""TeileId -> (Rolle, x, y) der punktartigen CSV-Objekte."""
return {e.teile_id: (e.kind, e.x, e.y) for e in elements
if e.kind in ANCHOR_BLOCKS and e.position and e.teile_id}
def id_points_dxf(msp) -> dict[str, tuple[str, float, float]]:
"""ID-Attribut -> (Rolle, x, y) der Anker-Bloecke im Modellbereich.
Nur Bloecke einer Rolle aus ANCHOR_BLOCKS mit einer Ziffern-ID; eine ID,
die zweimal vorkommt, ist nicht eindeutig und faellt weg.
"""
found: dict[str, tuple[str, float, float]] = {}
doppelt: set[str] = set()
for ref in msp.query("INSERT"):
role = _role_of(ref.dxf.name)
if not role:
continue
uid = next(((a.dxf.text or "").strip() for a in ref.attribs
if a.dxf.tag.upper() == "ID"), "")
if not uid.isdigit():
continue
if uid in found:
doppelt.add(uid)
found[uid] = (role, ref.dxf.insert.x, ref.dxf.insert.y)
return {k: v for k, v in found.items() if k not in doppelt}
def resolve_transform(
args, elements, msp, cfg_dir: Path, csv_name: str, dxf_name: str,
warnings: list[str],
) -> Transform:
"""Lagebezug bestimmen: Kommandozeile, Konfiguration oder Ermittlung."""
"""Lagebezug bestimmen: Kommandozeile, TeileId, Konfiguration oder Ermittlung.
Die TeileId (derive_from_ids) geht vor dem abgelegten und dem ermittelten
Bezug: sie ordnet eindeutig zu und gilt auch fuer eine verschobene Kopie
der Zeichnung. Sie wird jedes Mal neu gerechnet, nicht abgelegt.
"""
csv_points = anchor_points_csv(elements)
dxf_points = anchor_points_dxf(msp)
if not args.offset:
by_ids = derive_from_ids(id_points_csv(elements), id_points_dxf(msp),
csv_name, dxf_name)
if by_ids is not None:
verify_transform(by_ids, csv_points, dxf_points)
return by_ids
if args.offset:
try:
dx, dy = (float(v) for v in args.offset.split(","))
@@ -1241,7 +1291,13 @@ def main(argv: list[str] | None = None) -> int:
args.text_height, csv_file.name)
out_file = results / (args.out or f"{csv_file.stem}_annotated.dxf")
doc.saveas(str(out_file))
try:
doc.saveas(str(out_file))
except PermissionError:
print(f"FEHLER: {out_file} laesst sich nicht schreiben - ist die Zeichnung noch "
f"in BricsCAD geoeffnet? Schliessen und neu starten, oder --out <name>.",
file=sys.stderr)
return 1
reg_file = results / f"{csv_file.stem}_registration.json"
reg_file.write_text(