Files
plant2dxf/lib/plant2dxf.py
T
lertlmaier 4340618a2a Transformation der Koordinaten.
Berücksictigung des Winkels aus Merkmalen
Mapping zur Zuordnung von RD-Namen zu Blocknamen aus Bibliothek-dxf
2025-07-23 13:27:48 +02:00

232 lines
8.7 KiB
Python
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
placeblocks.py
Erzeugt DXF-Elemente aus einer RuleDesigner-CSV.
Einfache Formen (z.B. "ILS 2.0 Kreisel") werden direkt konstruiert,
komplexe per Blockreferenz aus einer DXF-Bibliothek eingefügt.
"""
import os
import sys
import csv
import json
import re
import argparse
import configparser
import ezdxf
from pathlib import Path
import math
# --------------------------------------------------------- Mapping TeileArt → Blockname
BLOCKNAME_MAPPING = {
"ILS 2.0 Kreisel": ["SP8", "AN8"]
# Weitere Zuordnungen nach Bedarf
}
# --------------------------------------------------------- Konstante Parameter
ATTR_TAG = "TeileId" # Attributtag im Block
RADIUS = 400 # Radius der Kreiselkreise (mm)
# --------------------------------------------------------- Hilfsfunktionen
def check_environment_var(env_str: str) -> Path:
"""Liefert Path aus Umgebungsvariable oder beendet mit Fehlermeldung."""
out_path = os.environ.get(env_str)
if out_path:
return Path(out_path)
print(f"Umgebungsvariable {env_str} ist nicht gesetzt oder leer.")
sys.exit(1)
def extract_coords(planquadrat: str) -> tuple[float, float]:
"""Extrahiert X/Y Koordinaten aus PlanquadratString."""
m = re.search(r"X:(\d+[\.,]?\d*)\s+Y:(\d+[\.,]?\d*)", planquadrat)
if not m:
raise ValueError(f"Koordinaten nicht gefunden in: '{planquadrat}'")
x, y = m.groups()
return float(x.replace(",", ".")), float(y.replace(",", "."))
def parse_merkmale(merkmale_str: str) -> dict:
"""Parst Merkmale-JSON-String in dict; bei Fehler → leeres Dict."""
try:
return json.loads(merkmale_str)
except json.JSONDecodeError:
return {}
def import_block(block_name: str, from_doc, to_doc) -> None:
"""Importiert Blockdefinition block_name von from_doc nach to_doc."""
if block_name in to_doc.blocks:
return
if block_name not in from_doc.blocks:
raise ValueError(f"Block '{block_name}' nicht in Bibliothek gefunden.")
src = from_doc.blocks[block_name]
tgt = to_doc.blocks.new(name=block_name)
for ent in src:
tgt.add_entity(ent.copy())
def draw_kreisel_lines(msp, pos1, pos2):
"""Zeichnet tangentiale Linien zwischen zwei Kreiselblöcken, unabhängig vom Winkel."""
x1, y1 = pos1
x2, y2 = pos2
# Verbindungsvektor
dx = x2 - x1
dy = y2 - y1
# Länge
length = math.hypot(dx, dy)
if length == 0:
return # keine Linie bei identischen Punkten
# Normalenvektor (senkrecht, normiert, Länge = RADIUS)
nx = -dy / length * RADIUS
ny = dx / length * RADIUS
# Tangentialpunkte
p1a = (x1 + nx, y1 + ny)
p1b = (x1 - nx, y1 - ny)
p2a = (x2 + nx, y2 + ny)
p2b = (x2 - nx, y2 - ny)
# Linien zeichnen
msp.add_line(p1a, p2a)
msp.add_line(p1b, p2b)
def transform_coords(x: float, y: float, height: float) -> tuple[float, float]:
"""Transformiert Bildschirmkoordinaten (0,0 oben links) ins DXF-KoSy (0,0 unten links)."""
return x, height - y
def handle_kreisel(msp, blocknames, teileid, merkmale, row, x, y, height, lib_doc, doc, verbose):
abstand_m = merkmale.get(
"Abstand (Kreiselachse A - Kreiselachse) in Meter", "20"
).replace(",", ".")
try:
abstand = float(abstand_m) * 1000 # Meter → mm
except ValueError:
abstand = 10000 # Fallback 10m
# Drehung (Winkel in Grad, Standard 0) aus Merkmale
try:
winkel = float(merkmale.get("Drehung", 0))
except (ValueError, TypeError):
winkel = 0.0
winkel_rad = math.radians(winkel)
# Die Koordinaten (x, y) sind die Mitte zwischen den beiden Blöcken (bereits transformiert)
halbabstand = abstand / 2
dx = halbabstand * math.cos(winkel_rad)
dy = halbabstand * math.sin(winkel_rad)
pos1 = (x - dx, y - dy)
pos2 = (x + dx, y + dy)
positions = [pos1, pos2]
for blockname, pos in zip(blocknames, positions):
import_block(blockname, lib_doc, doc)
bref = msp.add_blockref(blockname, pos)
bref.add_auto_attribs({ATTR_TAG: teileid})
if verbose:
print(f"[INFO] Block '{blockname}' (CSV: 'ILS 2.0 Kreisel') → {teileid} "
f"({pos[0]:.1f}, {pos[1]:.1f})")
# Linien zeichnen
draw_kreisel_lines(msp, pos1, pos2)
def handle_standard(msp, blocknames, teileid, x, y, lib_doc, doc, verbose):
for blockname in blocknames:
import_block(blockname, lib_doc, doc)
bref = msp.add_blockref(blockname, (x, y))
bref.add_auto_attribs({ATTR_TAG: teileid})
if verbose:
print(f"[INFO] Block '{blockname}' (Standard) → {teileid} "
f"({x:.1f}, {y:.1f})")
# --------------------------------------------------------- Hauptfunktion
def main(csv_path: Path, lib_path: Path, cfg_path: Path,
output_path: Path, verbose=False):
# Bibliothek nur laden, wenn Datei existiert
lib_doc = None
if lib_path.exists():
try:
lib_doc = ezdxf.readfile(lib_path)
if verbose:
print(f"[INFO] Bibliothek geladen: {lib_path}")
except Exception as e:
sys.exit(f"Fehler beim Lesen der Bibliothek '{lib_path}': {e}")
else:
print(f"[INFO] Keine Bibliothek gefunden unter {lib_path}. "
"Komplexe Formen werden übersprungen.")
# Neue Ziel­zeichnung (DXF R2018)
doc = ezdxf.new(dxfversion="R2018")
msp = doc.modelspace()
# CSV einlesen
# Zuerst alle Y-Werte sammeln, um die Höhe zu bestimmen
y_values = []
with csv_path.open(newline="", encoding="utf-8") as fh:
reader = csv.DictReader(fh, delimiter=';')
for row in reader:
planquadrat = row["Planquadrat"]
try:
_, y = extract_coords(planquadrat)
y_values.append(y)
except Exception:
pass
if not y_values:
raise ValueError("Keine Y-Koordinaten in der CSV gefunden!")
height = max(y_values)
# Jetzt eigentliche Verarbeitung
with csv_path.open(newline="", encoding="utf-8") as fh:
reader = csv.DictReader(fh, delimiter=';')
for row in reader:
teileart = row["TeileArt"].strip()
teileid = row["TeileId"].strip()
planquadrat = row["Planquadrat"]
merkmale = parse_merkmale(row.get("Merkmale", ""))
try:
x_screen, y_screen = extract_coords(planquadrat)
x, y = transform_coords(x_screen, y_screen, height)
except Exception as e:
print(f"[WARN] {teileid}: {e}")
continue
# Blocknamen immer aus Mapping holen (ggf. Fallback auf Teileart)
blocknames = BLOCKNAME_MAPPING.get(teileart)
if not blocknames:
print(f"[WARN] Keine Blockzuordnung für TeileArt '{teileart}'. Überspringe '{teileid}'.")
continue
if isinstance(blocknames, str):
blocknames = [blocknames]
# Spezialfall: ILS 2.0 Kreisel
if teileart == "ILS 2.0 Kreisel":
handle_kreisel(msp, blocknames, teileid, merkmale, row, x, y, height, lib_doc, doc, verbose)
continue
# Standardfall
handle_standard(msp, blocknames, teileid, x, y, lib_doc, doc, verbose)
# DXF speichern
doc.saveas(output_path)
print(f"[DONE] DXF gespeichert unter: {output_path}")
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Plaziert Anlagenkomponenten aus RuleDesigner CSV.")
parser.add_argument("-f", "--file", required=True, help="CSV-Datei (Name oder Pfad)", metavar="input.csv")
parser.add_argument("-c", "--config", help="CFG mit einfachen Formen", metavar="shapes.cfg")
parser.add_argument("-l", "--lib", help="DXF-Bibliothek mit Blöcken", metavar="bibliothek.dxf")
parser.add_argument("-o", "--output", help="Ziel-DXF (Standard: PROJECT_WORK/anlage.dxf)", metavar="anlage.dxf")
parser.add_argument("-v", "--verbose", action="store_true", help="mehr Ausgaben anzeigen")
args = parser.parse_args()
# Verzeichnisse aus Umgebungs­variablen
data_dir = check_environment_var("PROJECT_DATA")
work_dir = check_environment_var("PROJECT_WORK")
config_dir = check_environment_var("PROJECT_CFG")
# CSVPfad: nur Dateiname → im WORKOrdner suchen
if os.sep not in args.file and "/" not in args.file:
csv_path = work_dir / args.file
else:
csv_path = Path(args.file)
cfg_path = Path(args.config) if args.config else config_dir / "shapes.cfg"
lib_path = Path(args.lib) if args.lib else data_dir / "blocks.dxf"
output_path = Path(args.output) if args.output else (work_dir / f"{csv_path.stem}.dxf")
main(csv_path, lib_path, cfg_path, output_path, verbose=args.verbose)