feat: HW Planung - Symbole erzeugen, Nummerierung, Ausgabeziel-Dialog

- Symbole erzeugen (HW_SYMBOLE, Einzelbefehle HW_SYM_*): Bauteile nach
  cfg/io_regeln.json (reduzierte Kopie aus kabellaengen), Symbole aus
  cfg/hw_symbole.json katalog-identisch nachgebaut (lib/hw_symbole.py),
  REALE_POSITION mit x auf dem Bauteil, Paare uebereinander, Ausweichen
  in y ohne Ueberlappung
- Symbole nummerieren (HW_NUMMERIEREN): lib/create_numbers.py samt
  error_collector.py und cfg/enumerate.cfg aus kabellaengen uebernommen
- Ausgabeziel per Dialog dcl/hw_ausgabe.dcl: aktuelle Zeichnung,
  bestehende Datei ergaenzen oder neue Datei
- Materialfluss mit XDATA SSG_HW_MF, ergaenzt vorhandene Datei
- Doku doc/hw_planung.md

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Michael StanglandClaude Opus 5.5 committed 2026-11-03 16:39:37 +01:00
1 parent d2729140ff
commit 8abb6b3c65
20 files changed
+4176 -183

No files matched your search

File diff suppressed because it is too large. Load diff
+121
View File
@@ -0,0 +1,121 @@
"""
ErrorCollector - Gemeinsame Klasse zum Sammeln von Fehlern und Warnungen
Diese Klasse wird von getpositions.py und drawdxf.py verwendet, um Fehler
und Warnungen während der Verarbeitung zu sammeln und zu verwalten.
"""
import json
from pathlib import Path
class ErrorCollector:
"""Sammelt alle Fehler und Warnungen während der Verarbeitung."""
def __init__(self):
self.errors = {}
self.warnings = {}
def add_errors(self, error_dict):
"""Fügt Fehler aus einem Dictionary hinzu."""
for key, value in error_dict.items():
if key in self.errors:
# Key existiert bereits - Verhalten hängt vom Typ ab
existing = self.errors[key]
if isinstance(existing, dict) and isinstance(value, dict):
# Beide sind Dictionaries → Merge/Update
existing.update(value)
elif isinstance(existing, list):
# Existierender Wert ist Liste
if isinstance(value, list):
existing.extend(value)
else:
existing.append(value)
else:
# Existierender Wert ist weder dict noch list → Mache Liste daraus
if isinstance(value, list):
self.errors[key] = [existing] + value
else:
self.errors[key] = [existing, value]
else:
# Neuer Key - behalte Typ des Wertes
if isinstance(value, (dict, list)):
# Dictionary oder Liste → direkt speichern
self.errors[key] = value
else:
# Primitiver Wert (String, etc.) → als Liste speichern
self.errors[key] = [value]
def add_warnings(self, warning_dict):
"""Fügt Warnungen aus einem Dictionary hinzu."""
for key, value in warning_dict.items():
if key in self.warnings:
# Key existiert bereits - Verhalten hängt vom Typ ab
existing = self.warnings[key]
if isinstance(existing, dict) and isinstance(value, dict):
# Beide sind Dictionaries → Merge/Update
existing.update(value)
elif isinstance(existing, list):
# Existierender Wert ist Liste
if isinstance(value, list):
existing.extend(value)
else:
existing.append(value)
else:
# Existierender Wert ist weder dict noch list → Mache Liste daraus
if isinstance(value, list):
self.warnings[key] = [existing] + value
else:
self.warnings[key] = [existing, value]
else:
# Neuer Key - behalte Typ des Wertes
if isinstance(value, (dict, list)):
# Dictionary oder Liste → direkt speichern
self.warnings[key] = value
else:
# Primitiver Wert (String, etc.) → als Liste speichern
self.warnings[key] = [value]
def has_errors_or_warnings(self):
"""Prüft ob Fehler oder Warnungen vorhanden sind."""
return bool(self.errors) or bool(self.warnings)
def has_errors(self):
"""Prüft ob Fehler vorhanden sind."""
return bool(self.errors)
def get_all_issues(self):
"""Gibt alle Fehler und Warnungen als Dictionary zurück."""
result = {}
if self.errors:
result["errors"] = self.errors
if self.warnings:
result["warnings"] = self.warnings
return result
def write_errorfile(self, work_dir: Path, filename: str):
"""Schreibt Fehlerdatei nur wenn Fehler oder Warnungen vorhanden sind."""
if self.has_errors():
print("Fehler gefunden. Schreibe Error-Datei.")
all_issues = self.get_all_issues()
write_json_file(all_issues, work_dir, filename)
else:
print("Keine Fehler gefunden. Keine Error-Datei geschrieben.")
def write_json_file(data: dict, out_dir: Path, filename: str) -> None:
"""
Schreibt Daten in eine JSON-Datei.
Args:
data: Dictionary mit zu schreibenden Daten
out_dir: Ausgabeverzeichnis
filename: Dateiname für die JSON-Datei
"""
print("writing data to a json file ...")
outfile = out_dir / filename if isinstance(out_dir, Path) else Path(out_dir) / filename
with open(outfile, 'w', encoding='utf-8') as fh:
json.dump(data, fh, indent=2, ensure_ascii=False, default=str)
print("done")
+55 -5
View File
@@ -9,7 +9,9 @@ Aufruf:
Gestartet vom Befehl HW_MATERIALFLUSS (Lisp/HwPlanung.lsp, Menue HW Planung ->
Materialfluss erzeugen) direkt nach EXPORTCSV; die Ausgabe heisst wie die
Zeichnung mit Anhang "_HW.dxf". Alles wird flach in die XY-Ebene (Z = 0)
gezeichnet - es geht um den Grundriss des Materialflusses.
gezeichnet - es geht um den Grundriss des Materialflusses. Gibt es die Datei
schon (z.B. mit den Symbolen aus hw_symbole.py), wird nur der Inhalt der
HW_*-Layer ersetzt, alles andere bleibt.
Was gezeichnet wird:
* Kreisel/Eckrad: geschlossene Polylinie als Rechteck um die Laufbahn
@@ -200,10 +202,45 @@ def _xy(punkt):
return (punkt[0], punkt[1]) if punkt else None
def zeichnen(elemente, ausgabe, layer):
doc = ezdxf.new("R2018", setup=True)
doc.header["$INSUNITS"] = 4 # mm
# Markierung aller erzeugten Materialfluss-Objekte - daran erkennt LISP sie,
# wenn die Ausgabe in die AKTUELLE Zeichnung geht (HwPlanung.lsp,
# hw-alte-ausgabe), und ein Folgelauf, was er ersetzen darf.
XDATA_APP = "SSG_HW_MF"
def hw_dokument_oeffnen(pfad, einheitenlos=False):
"""Vorhandene Ausgabe-DXF oeffnen oder neu anlegen (R2018, mm).
Materialfluss (dieses Skript) und Symbole (hw_symbole.py) schreiben in
DIESELBE Datei und ersetzen dort nur ihren eigenen Anteil - was der
Anwender ergaenzt hat (Renamer-Rahmen, Polylinien), bleibt erhalten.
einheitenlos: $INSUNITS = 0 - fuer die Zwischendatei, die LISP als Block
in die aktuelle Zeichnung einfuegt; mit Einheit wuerde BricsCAD sie beim
Einfuegen in eine Zeichnung mit anderer Einheit skalieren.
"""
if os.path.isfile(pfad):
doc = ezdxf.readfile(pfad)
else:
doc = ezdxf.new("R2018", setup=True)
doc.header["$INSUNITS"] = 4 # mm
if einheitenlos:
doc.header["$INSUNITS"] = 0
return doc
def _ist_materialfluss(e, eigene_layer):
return e.has_xdata(XDATA_APP) or e.dxf.layer in eigene_layer
def zeichnen(elemente, ausgabe, layer, einheitenlos=False):
doc = hw_dokument_oeffnen(ausgabe, einheitenlos)
msp = doc.modelspace()
# Materialfluss des vorigen Laufs entfernen (markiert oder - aeltere
# Dateien ohne Markierung - auf den HW-Layern)
eigene = {name for name, _farbe in layer.values()}
for e in [e for e in msp if _ist_materialfluss(e, eigene)]:
msp.delete_entity(e)
vorher = {e.dxf.handle for e in msp}
for name, farbe in layer.values():
if name not in doc.layers:
doc.layers.add(name, color=farbe)
@@ -267,6 +304,12 @@ def zeichnen(elemente, ausgabe, layer):
msp.add_line(k1, k2, dxfattribs=ly("hw_entladung"))
zaehler["entladung"] += 1
if XDATA_APP not in doc.appids:
doc.appids.new(XDATA_APP)
for e in msp:
if e.dxf.handle not in vorher:
e.set_xdata(XDATA_APP, [(1000, "materialfluss")])
doc.saveas(ausgabe)
return zaehler, warnungen
@@ -277,6 +320,8 @@ def main(argv=None):
ap.add_argument("ausgabe")
ap.add_argument("--layer-cfg", default=None,
help="Pfad zu layer.cfg (Vorgabe: $DXFM_CFG/layer.cfg)")
ap.add_argument("--einheitenlos", action="store_true",
help="$INSUNITS = 0 (Zwischendatei fuer die aktuelle Zeichnung)")
args = ap.parse_args(argv)
cfg = args.layer_cfg
@@ -284,7 +329,12 @@ def main(argv=None):
cfg = os.path.join(os.environ["DXFM_CFG"], "layer.cfg")
elemente = load_elements(args.csv)
zaehler, warnungen = zeichnen(elemente, args.ausgabe, layer_cfg_lesen(cfg))
try:
zaehler, warnungen = zeichnen(elemente, args.ausgabe, layer_cfg_lesen(cfg),
args.einheitenlos)
except PermissionError:
print(f"FEHLER: {args.ausgabe} ist gesperrt (in BricsCAD geoeffnet?) - bitte schliessen.")
return 1
print(f"HW-Materialfluss geschrieben: {args.ausgabe}")
print(" " + ", ".join(f"{k}: {v}" for k, v in zaehler.items()))
for w in warnungen:
+356
View File
@@ -0,0 +1,356 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
hw_symbole.py - E-Geraete-Symbole der HW-Planung in die _HW.dxf setzen.
Aufruf:
python hw_symbole.py <plan.json> <ausgabe_HW.dxf> --symbole <hw_symbole.json>
Gestartet von HW_SYMBOLE / HW_SYM_* (Lisp/HwPlanung.lsp). Den Plan schreibt
LISP aus der Ursprungszeichnung (Bauteile nach cfg/io_regeln.json):
{"praefixe": ["MB", "BG"],
"symbole": [{"praefix": "MB", "io": "MB1@@@", "block": "ILS_Separator-Ausgang",
"x": 400.0, "y": 13920.0, "rolle": "sep_ventil",
"key": "1A2/3B4", "bauteil": "separator"}, ...]}
x/y ist der ZIELPUNKT des Bauteils (Sensor, AS/ES-Element, Motor- bzw.
Kreiselzentrum), nicht der Einfuegepunkt. Der Plan enthaelt immer ALLE
Symbole; "praefixe" sagt, welche dieser Lauf einfuegt bzw. ersetzt - die
Lage wird trotzdem aus dem ganzen Plan berechnet, damit ein Einzelbefehl
(HW_SYM_VENTILE) dieselbe Lage liefert wie HW_SYMBOLE.
Die Symbole selbst (Rahmen, Layer, Attribute mit Lage/Hoehe/Vorgabewerten)
stehen in cfg/hw_symbole.json - es gibt keine Symbol-Bibliothek als
Referenzdokument. Ablauf:
* die _HW.dxf oeffnen oder neu anlegen (hw_materialfluss.hw_dokument_oeffnen)
* alle frueher erzeugten Symbole der Praefixe in "praefixe" entfernen
(erkannt an der XDATA SSG_HW_SYM) - andere Symbole und alles, was der
Anwender gezeichnet hat, bleiben stehen
* die benoetigten Blockdefinitionen aus der Konfiguration (neu) aufbauen -
so wirkt jede Aenderung an hw_symbole.json beim naechsten Lauf
* Lage berechnen (platzieren): die Marke REALE_POSITION jedes Symbols
liegt mit ihrem x genau auf dem Zielpunkt, der Kasten daneben; die
Symbole eines Bauteils (z.B. Ventil + Sensor am Separator) stehen
luekenlos uebereinander, als Gruppe um den Zielpunkt zentriert.
Ueberlappt eine Gruppe (2D-Bounding-Boxen der Rahmen) mit einer schon
gesetzten, wird sie nur in y verschoben - x bleibt auf dem Ziel.
* je Plan-Eintrag den Block einfuegen; IO = Vorlage (z.B. MB1@@@), die
Platzhalter-ID des Symbols (Wert von IO, z.B. "MA0") wird in VERW/
BEZEICHNUNG ebenfalls durch die Vorlage ersetzt - create_numbers.py
ersetzt spaeter alle '@' in einem Zug.
"""
import argparse
import json
import os
import sys
from collections import Counter
from ezdxf.enums import TextEntityAlignment
from hw_materialfluss import hw_dokument_oeffnen
XDATA_APP = "SSG_HW_SYM"
ERSETZEN_IN = ("VERW", "BEZEICHNUNG")
# Kurzzeichen in hw_symbole.json -> ezdxf (fehlt = links auf der Grundlinie)
AUSRICHTUNG = {
"C": TextEntityAlignment.CENTER,
"R": TextEntityAlignment.RIGHT,
"ML": TextEntityAlignment.MIDDLE_LEFT,
"MC": TextEntityAlignment.MIDDLE_CENTER,
"MR": TextEntityAlignment.MIDDLE_RIGHT,
}
# ------------------------------------------------------------------
# Reine Kerne
# ------------------------------------------------------------------
def symbol_attribute(sdef, vorlage):
"""Attribut-Beschreibungen eines Symbols: die der Vorlage ohne "ohne",
je Tag mit den Abweichungen aus sdef["attribute"] (Wert null = Vorlagen-
Eintrag entfernen, z.B. "stil": null -> Standard-Textstil)."""
ohne = set(sdef.get("ohne", []))
abw = sdef.get("attribute", {})
erg = []
for a in vorlage["attribute"]:
if a["tag"] in ohne:
continue
a = dict(a)
for k, v in abw.get(a["tag"], {}).items():
if v is None:
a.pop(k, None)
else:
a[k] = v
erg.append(a)
return erg
def symbol_vorgaben(sdef, vorlage):
"""{TAG: Vorgabewert} eines Symbols: Vorlagen-"wert", vom Symbol ueberschrieben."""
werte = {a["tag"]: a.get("wert", "") for a in symbol_attribute(sdef, vorlage)}
werte.update({k: v for k, v in sdef.get("werte", {}).items() if k in werte})
return werte
def attribut_werte(vorgaben, io):
"""Werte fuer add_auto_attribs: IO = Vorlage, Platzhalter-ID in VERW/
BEZEICHNUNG ersetzt (z.B. "Motor MA0" -> "Motor MA1@@@")."""
alt = vorgaben.get("IO", "")
werte = {"IO": io}
for tag in ERSETZEN_IN:
if tag in vorgaben and alt:
werte[tag] = vorgaben[tag].replace(alt, io)
return werte
def symbol_geometrie(konfig, name):
"""Rahmen-Box (x0, y0, x1, y1) und Marke (x, y) von REALE_POSITION eines
Symbols, beides relativ zum Einfuegepunkt. Ohne REALE_POSITION gilt die
Mitte der rechten Rahmenkante als Marke."""
sdef = konfig["symbole"][name]
vorlage = konfig["vorlagen"][sdef["vorlage"]]
dx, dy = sdef.get("versatz", [0.0, 0.0])
breite, hoehe = vorlage["rahmen"]
box = (dx, dy, dx + breite, dy + hoehe)
marke = (dx + breite, dy + hoehe / 2.0)
for a in symbol_attribute(sdef, vorlage):
if a["tag"] == "REALE_POSITION":
marke = (a["x"] + dx, a["y"] + dy)
return {"box": box, "marke": marke}
def _gruppe_auslegen(ziel, geometrien):
"""Symbole einer Gruppe uebereinander, Gruppe in y um das Ziel zentriert,
Marke jedes Symbols mit x auf dem Ziel. Rueckgabe: [(einfuegepunkt, box_welt)]."""
oben = ziel[1] + sum(g["box"][3] - g["box"][1] for g in geometrien) / 2.0
erg = []
for g in geometrien:
x0, y0, x1, y1 = g["box"]
unten = oben - (y1 - y0)
ex, ey = ziel[0] - g["marke"][0], unten - y0
erg.append(((ex, ey), (ex + x0, unten, ex + x1, oben)))
oben = unten
return erg
def _ueberlappt(a, b, abstand):
return (a[0] < b[2] + abstand and b[0] < a[2] + abstand
and a[1] < b[3] + abstand and b[1] < a[3] + abstand)
def _verschiebungen(schritt, maximum):
yield 0.0
k = 1
while k * schritt <= maximum:
yield k * schritt
yield -k * schritt
k += 1
def platzieren(gruppen, abstand=20.0, schritt=50.0, max_verschiebung=3000.0):
"""Einfuegepunkte fuer Symbolgruppen ohne Ueberlappung der Rahmen.
gruppen: Liste (ziel (x, y), [geometrie, ...]) in Bearbeitungsfolge,
geometrie wie symbol_geometrie. Jede Gruppe wird ausgelegt
(_gruppe_auslegen) und, falls ihre Rahmen eine schon gesetzte Box
beruehren (Mindestabstand abstand), in y um +-schritt, +-2*schritt, ...
bis max_verschiebung verschoben; x bleibt auf dem Ziel. Findet sich keine
freie Lage, bleibt sie unverschoben und zaehlt als Ueberlappung.
Rueckgabe: (Liste der Einfuegepunkte je Gruppe, Anzahl Ueberlappungen)."""
gesetzt = []
punkte = []
ueberlappungen = 0
for ziel, geometrien in gruppen:
basis = _gruppe_auslegen(ziel, geometrien)
wahl = None
for dy in _verschiebungen(schritt, max_verschiebung):
boxen = [(b[0], b[1] + dy, b[2], b[3] + dy) for _p, b in basis]
if not any(_ueberlappt(b, g, abstand) for b in boxen for g in gesetzt):
wahl = dy
break
if wahl is None:
wahl = 0.0
ueberlappungen += 1
gesetzt.extend((b[0], b[1] + wahl, b[2], b[3] + wahl) for _p, b in basis)
punkte.append([(p[0], p[1] + wahl) for p, _b in basis])
return punkte, ueberlappungen
def plan_gruppen(symbole):
"""Plan-Eintraege nach Bauteil (key) gruppieren, Reihenfolge des Plans.
Rueckgabe: Liste (ziel, [eintrag, ...])."""
gruppen = {}
for s in symbole:
gruppen.setdefault(s.get("key", ""), ((s["x"], s["y"]), []))[1].append(s)
return list(gruppen.values())
# ------------------------------------------------------------------
# Zeichnung
# ------------------------------------------------------------------
def symbol_xdata(insert):
"""(praefix, rolle, key) eines erzeugten Symbols oder None."""
if not insert.has_xdata(XDATA_APP):
return None
werte = [v for code, v in insert.get_xdata(XDATA_APP) if code == 1000]
return tuple((werte + ["", "", ""])[:3])
def alte_symbole_entfernen(msp, praefixe):
"""Erzeugte Symbole der Praefixe loeschen. Rueckgabe: Anzahl."""
weg = [e for e in msp.query("INSERT")
if (x := symbol_xdata(e)) is not None and x[0] in praefixe]
for e in weg:
msp.delete_entity(e)
return len(weg)
def _layer(doc, name):
if name not in doc.layers:
doc.layers.add(name)
def _textstile(doc, konfig):
"""Textstile aus der Konfiguration anlegen. Rueckgabe: Name des Standardstils."""
ts = konfig.get("textstile", {})
for name, schrift in ts.get("schriften", {}).items():
if name not in doc.styles:
doc.styles.add(name, font=schrift)
return ts.get("standard", "Standard")
def block_aufbauen(doc, name, konfig):
"""Blockdefinition name aus der Symbol-Konfiguration (neu) aufbauen.
Der Einfuegepunkt liegt bei (0, 0); Rahmen und Attribute sitzen um
sdef["versatz"] verschoben (Katalog: Eingaenge/Motoren haengen nach unten)."""
sdef = konfig["symbole"][name]
vorlage = konfig["vorlagen"][sdef["vorlage"]]
standard_stil = _textstile(doc, konfig)
dx, dy = sdef.get("versatz", [0.0, 0.0])
layer = sdef["layer"]
_layer(doc, layer)
if name in doc.blocks:
blk = doc.blocks.get(name)
blk.delete_all_entities()
else:
blk = doc.blocks.new(name)
breite, hoehe = vorlage["rahmen"]
ecken = [(dx, dy), (dx + breite, dy), (dx + breite, dy + hoehe), (dx, dy + hoehe)]
blk.add_lwpolyline(ecken, close=True, dxfattribs={"layer": layer, "color": 7})
schraffur = vorlage.get("schraffur")
if schraffur and not sdef.get("ohne_schraffur"):
h_layer = sdef.get("schraffur_layer", layer)
_layer(doc, h_layer)
farbe = sdef.get("schraffur_farbe", 256) # fehlt = VONLAYER
hatch = blk.add_hatch(color=farbe, dxfattribs={"layer": h_layer})
hatch.set_pattern_fill(schraffur["muster"], color=farbe,
scale=schraffur.get("massstab", 1.0))
hatch.paths.add_polyline_path(ecken, is_closed=True)
vorgaben = symbol_vorgaben(sdef, vorlage)
for a in symbol_attribute(sdef, vorlage):
attr_layer = a.get("layer", layer)
_layer(doc, attr_layer)
dxf = {"layer": attr_layer, "height": a["hoehe"], "style": a.get("stil", standard_stil)}
if "breite" in a:
dxf["width"] = a["breite"]
farbe = a.get("farbe")
if farbe == "symbol":
farbe = sdef.get("farbe")
if isinstance(farbe, int):
dxf["color"] = farbe
punkt = (a["x"] + dx, a["y"] + dy)
attdef = blk.add_attdef(a["tag"], punkt, text=vorgaben.get(a["tag"], ""), dxfattribs=dxf)
if a.get("unsichtbar"):
attdef.is_invisible = True
if a.get("ausrichtung") in AUSRICHTUNG:
attdef.set_placement(punkt, align=AUSRICHTUNG[a["ausrichtung"]])
return vorgaben
def symbole_setzen(doc, plan, konfig):
"""Symbole aus dem Plan in doc setzen (nur die Praefixe aus plan
"praefixe", Lage aus dem ganzen Plan). Rueckgabe: (gesetzt je Praefix,
entfernt, Bloecke ohne Definition in der Konfiguration, Anzahl Gruppen
mit Ueberlappung)."""
msp = doc.modelspace()
praefixe = set(plan.get("praefixe") or [])
symbole = plan.get("symbole") or []
entfernt = alte_symbole_entfernen(msp, praefixe)
bekannt = konfig.get("symbole", {})
namen = sorted({s["block"] for s in symbole})
fehlend = [n for n in namen if n not in bekannt]
vorgaben_je_block = {n: block_aufbauen(doc, n, konfig) for n in namen if n in bekannt}
if XDATA_APP not in doc.appids:
doc.appids.new(XDATA_APP)
p = konfig.get("platzierung", {})
geometrie = {n: symbol_geometrie(konfig, n) for n in vorgaben_je_block}
gruppen = [(ziel, [s for s in eintraege if s["block"] in geometrie])
for ziel, eintraege in plan_gruppen(symbole)]
gruppen = [g for g in gruppen if g[1]]
punkte, ueberlappungen = platzieren(
[(ziel, [geometrie[s["block"]] for s in eintraege]) for ziel, eintraege in gruppen],
abstand=p.get("abstand", 20.0), schritt=p.get("schritt", 50.0),
max_verschiebung=p.get("max_verschiebung", 3000.0))
gesetzt = Counter()
for (_ziel, eintraege), gruppen_punkte in zip(gruppen, punkte):
for s, (x, y) in zip(eintraege, gruppen_punkte):
if s["praefix"] not in praefixe:
continue
ref = msp.add_blockref(s["block"], (x, y, 0.0),
dxfattribs={"layer": bekannt[s["block"]]["layer"]})
ref.add_auto_attribs(attribut_werte(vorgaben_je_block[s["block"]], s["io"]))
ref.set_xdata(XDATA_APP, [(1000, s["praefix"]), (1000, s["rolle"]),
(1000, s.get("key", ""))])
gesetzt[s["praefix"]] += 1
return gesetzt, entfernt, fehlend, ueberlappungen
def main(argv=None):
ap = argparse.ArgumentParser(description=__doc__.split("\n")[1])
ap.add_argument("plan")
ap.add_argument("ausgabe")
ap.add_argument("--symbole", required=True, help="Symboldefinitionen (cfg/hw_symbole.json)")
ap.add_argument("--einheitenlos", action="store_true",
help="$INSUNITS = 0 (Zwischendatei fuer die aktuelle Zeichnung)")
args = ap.parse_args(argv)
if not os.path.isfile(args.symbole):
print(f"FEHLER: Symboldefinitionen nicht gefunden: {args.symbole}")
return 1
with open(args.symbole, encoding="utf-8") as fh:
konfig = json.load(fh)
with open(args.plan, encoding="utf-8") as fh:
plan = json.load(fh)
doc = hw_dokument_oeffnen(args.ausgabe, args.einheitenlos)
gesetzt, entfernt, fehlend, ueberlappungen = symbole_setzen(doc, plan, konfig)
try:
doc.saveas(args.ausgabe)
except PermissionError:
print(f"FEHLER: {args.ausgabe} ist gesperrt (in BricsCAD geoeffnet?) - bitte schliessen.")
return 1
print(f"HW-Symbole geschrieben: {args.ausgabe}")
if entfernt:
print(f" {entfernt} vorhandene Symbole ersetzt")
for p in plan.get("praefixe") or []:
print(f" {p}: {gesetzt.get(p, 0)} Symbole")
for b in fehlend:
print(f" WARNUNG: Symbol nicht in {os.path.basename(args.symbole)} definiert: {b}")
if ueberlappungen:
print(f" WARNUNG: {ueberlappungen} Bauteil(e) ohne freie Lage - Symbole ueberlappen dort")
return 0
if __name__ == "__main__":
sys.exit(main())