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:
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
@@ -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
@@ -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:
|
||||
|
||||
@@ -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())
|
||||
Reference in new issue
Block a user