dxf Erzeugung aus den .json Skeletons ermöglicht, um anzuzeigen welche Elemente zusammengehören

This commit is contained in:
2026-07-07 09:54:25 +02:00
parent cbed41da66
commit 2957e24a5f
4 changed files with 4378 additions and 0 deletions
+259
View File
@@ -0,0 +1,259 @@
# -*- coding: utf-8 -*-
"""create_dxf.py — erzeugt eine DXF-Uebersicht aller TROs mit ihren Sensoren.
Quelle sind die vom Skeleton-Generator erzeugten results/skeleton_UH0x.json
(Sensor-Positionen aus der Positions-JSON, Einheiten mit IO-Zuordnung).
Je TRO-Einheit wird ein Block "TRO_BOX" (Rechteck + eingerahmter Name)
am Schwerpunkt seiner Sensoren eingefuegt; die TRO-Namen entsprechen der
Nummerierung des SCL-Grundgeruests (create_skel.py). Jeder Sensor wird als
Block "SENSOR" (Kreis + Name) an seiner x/y-Position eingefuegt und per
Linie mit seinem TRO verbunden.
Block-Attribute (fuer Identifikation im CAD, teils unsichtbar):
TRO_BOX: NAME, FBTYPE (sichtbar) — UNIT, PLC, AREAS, SENSORS, IO (unsichtbar)
SENSOR: NAME (sichtbar) — ROLE, TRO, PLC (unsichtbar)
Pfade ueber Umgebungsvariablen aus bin\\setenv.bat:
PV_RESULTS Eingabe (skeleton_UH0x.json) und Ausgabe (DXF)
Mit --colors werden die TRO-Kaesten zusaetzlich nach FB-Typ eingefaerbt
(SOLID-Fuellung auf Layer TRO_FUELL, gleiche Palette wie im Topologie-
Graphen doc/TRO_Graph_UH01-UH05.dot); ohne den Schalter bleiben die
Kaesten ungefuellt.
Aufruf: bin\\create_dxf.bat [--uh 1..5] [--out DATEI.dxf] [--colors]
"""
import argparse
import json
import os
import sys
from pathlib import Path
try:
import ezdxf
from ezdxf.enums import TextEntityAlignment
except ImportError:
sys.exit("ezdxf fehlt — bitte bin\\install_py.bat ausfuehren.")
# ============================================================================
# GLOBALE DEFINITIONEN (Geometrie in mm, Layer, Farben)
# ============================================================================
OUT_NAME = "ST500592_TROs.dxf"
BOX_W = 2500 # TRO-Kasten Breite
BOX_H = 1200 # TRO-Kasten Hoehe
BOX_TEXT_H = 380 # Texthoehe TRO-Name
BOX_SUB_H = 220 # Texthoehe FB-Typ
SEN_RADIUS = 120 # Sensor-Kreisradius
SEN_TEXT_H = 150 # Texthoehe Sensorname
ATTR_MAXLEN = 250 # DXF-Attributwerte kuerzen
LAYERS = {
"TRO": 7, # Kaesten (Farbe je PLC am Insert)
"TRO_FUELL": 7, # Kasten-Fuellung (Truecolor je FB-Typ)
"SENSOR": 8, # Sensoren mit TRO-Zuordnung
"SENSOR_FREI": 9, # Sensoren ohne Einheit (nur Position)
"VERBINDUNG": 253, # Linien Sensor -> TRO
}
#: ACI-Farbe je Steuerung
PLC_COLOR = {1: 1, 2: 2, 3: 3, 4: 4, 5: 6} # rot, gelb, gruen, cyan, magenta
#: Fuellfarbe je FB-Typ (Truecolor) — gleiche Palette wie im
#: Topologie-Graphen doc/TRO_Graph_UH01-UH05.dot
TYPE_COLOR = {
"FB_ILS_MTRO_1Sep": 0xD4E8FF, # hellblau
"FB_ILS_MTRO_1Sep_SSCC": 0xFFD4D4, # rot
"FB_ILS_MTRO_1Sep1Swi": 0xFFE4A0, # gelb (Weiche)
"FB_ILS_MTRO_1Sep2Swi": 0xFFC060, # orange (Weiche)
"FB_ILS_MTRO_2Sep1Swi": 0xFFE4A0, # gelb (Weiche)
"FB_ILS_MTRO_Vario": 0xE8D4F0, # violett
"FB_ILS_MTRO_Vario_workStation": 0xC0F0F0, # tuerkis
"FB_ILS_MTRO_PinStore_Auto": 0xD4F0D4, # gruen (Lager)
"FB_EmptyCarrBuffer": 0xD4F0D4, # gruen (Puffer)
"FB_Conveyor": 0xF0F0F0, # grau
}
TYPE_COLOR_DEFAULT = 0xF0F0F0 # grau (Sonstige/UNBEKANNT)
def type_color(fbtype):
for key in sorted(TYPE_COLOR, key=len, reverse=True):
if fbtype.startswith(key):
return TYPE_COLOR[key]
return TYPE_COLOR_DEFAULT
# ============================================================================
def project_paths():
root = Path(os.environ.get("PROJECT", Path(__file__).resolve().parent.parent))
results = Path(os.environ.get("PV_RESULTS", root / "results"))
return results
def build_blocks(doc):
"""Blockdefinitionen TRO_BOX und SENSOR anlegen."""
tro = doc.blocks.new(name="TRO_BOX")
w2, h2 = BOX_W / 2, BOX_H / 2
tro.add_lwpolyline([(-w2, -h2), (w2, -h2), (w2, h2), (-w2, h2)],
close=True)
att = tro.add_attdef("NAME", height=BOX_TEXT_H)
att.set_placement((0, BOX_H * 0.12), align=TextEntityAlignment.MIDDLE_CENTER)
att = tro.add_attdef("FBTYPE", height=BOX_SUB_H)
att.set_placement((0, -BOX_H * 0.28), align=TextEntityAlignment.MIDDLE_CENTER)
for tag in ("UNIT", "PLC", "AREAS", "SENSORS", "IO"):
att = tro.add_attdef(tag, height=BOX_SUB_H)
att.set_placement((0, 0), align=TextEntityAlignment.MIDDLE_CENTER)
att.dxf.invisible = 1
sen = doc.blocks.new(name="SENSOR")
sen.add_circle((0, 0), SEN_RADIUS)
att = sen.add_attdef("NAME", height=SEN_TEXT_H)
att.set_placement((SEN_RADIUS * 1.4, SEN_RADIUS * 1.2),
align=TextEntityAlignment.BOTTOM_LEFT)
for tag in ("ROLE", "TRO", "PLC"):
att = sen.add_attdef(tag, height=SEN_TEXT_H)
att.set_placement((0, 0), align=TextEntityAlignment.MIDDLE_CENTER)
att.dxf.invisible = 1
def instance_names(skel, uh_digit):
"""TRO-Instanznamen je Einheit — identische Nummerierung wie
render_scl() in create_skel.py (Conveyor verbraucht eine lfd. Nummer)."""
names = {}
seq = 0
for u in skel["units"]:
if u["fbType"] == "UNBEKANNT":
names[u["unit"]] = u["unit"] # kein TRO-Name vergeben
continue
seq += 1
if u["fbType"] == "FB_Conveyor":
names[u["unit"]] = f"fbConveyor{u['unit'][1:]}"
else:
names[u["unit"]] = f"TRO{uh_digit}{seq:02d}"
return names
def cut(s):
return s if len(s) <= ATTR_MAXLEN else s[:ATTR_MAXLEN - 3] + "..."
def draw_plc(msp, skel, n, colors=False):
"""Alle Einheiten + Sensoren einer Steuerung zeichnen."""
uh = f"UH0{n}"
color = PLC_COLOR[n]
names = instance_names(skel, str(n))
# Sensoren je Einheit sammeln (nur mit Position)
unit_sensors = {}
free_sensors = []
for s in skel["sensors"]:
if not s.get("pos"):
continue
if s.get("unit"):
unit_sensors.setdefault(s["unit"], []).append(s)
else:
free_sensors.append(s)
boxes = lines = 0
for u in skel["units"]:
sens = unit_sensors.get(u["unit"], [])
if sens: # Schwerpunkt der Sensoren
cx = sum(s["pos"][0] for s in sens) / len(sens)
cy = sum(s["pos"][1] for s in sens) / len(sens)
elif u.get("pos"):
cx, cy = u["pos"][0], u["pos"][1]
else:
print(f" {uh} {u['unit']}: keine Position — uebersprungen")
continue
io_txt = ";".join(f"{role}={','.join(v)}"
for role, v in sorted(u["io"].items()))
if colors:
# Fuellung in FB-Typ-Farbe (vor dem Insert -> liegt dahinter)
w2, h2 = BOX_W / 2, BOX_H / 2
msp.add_solid(
[(cx - w2, cy - h2), (cx + w2, cy - h2),
(cx - w2, cy + h2), (cx + w2, cy + h2)],
dxfattribs={"layer": "TRO_FUELL",
"true_color": type_color(u["fbType"])})
blk = msp.add_blockref("TRO_BOX", (cx, cy),
dxfattribs={"layer": "TRO", "color": color})
blk.add_auto_attribs({
"NAME": names[u["unit"]],
"FBTYPE": u["fbType"].replace("FB_ILS_MTRO_", "").replace("FB_", ""),
"UNIT": u["unit"],
"PLC": uh,
"AREAS": cut(",".join(a["id"] for a in u["areas"])),
"SENSORS": cut(",".join(s["id"] for s in sens)),
"IO": cut(io_txt),
})
boxes += 1
for s in sens:
sx, sy = s["pos"][0], s["pos"][1]
sb = msp.add_blockref("SENSOR", (sx, sy),
dxfattribs={"layer": "SENSOR",
"color": color})
sb.add_auto_attribs({
"NAME": s["id"],
"ROLE": s.get("role") or "",
"TRO": names[u["unit"]],
"PLC": uh,
})
msp.add_line((sx, sy), (cx, cy),
dxfattribs={"layer": "VERBINDUNG"})
lines += 1
for s in free_sensors:
sb = msp.add_blockref("SENSOR", (s["pos"][0], s["pos"][1]),
dxfattribs={"layer": "SENSOR_FREI"})
sb.add_auto_attribs({
"NAME": s["id"],
"ROLE": s.get("role") or "",
"TRO": "",
"PLC": uh,
})
print(f" {uh}: {boxes} TRO-Kaesten, {lines} Sensor-Verbindungen, "
f"{len(free_sensors)} freie Sensoren")
def main():
ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument("--uh", type=int, choices=range(1, 6),
help="nur diese Steuerung (1..5), sonst alle")
ap.add_argument("--out", default=OUT_NAME,
help=f"Ausgabedatei (Default: {OUT_NAME})")
ap.add_argument("--colors", action="store_true",
help="TRO-Kaesten nach FB-Typ einfaerben "
"(Palette wie doc/TRO_Graph_UH01-UH05.dot)")
args = ap.parse_args()
results = project_paths()
doc = ezdxf.new("R2010", setup=True)
doc.header["$INSUNITS"] = 4 # Millimeter
for name, aci in LAYERS.items():
doc.layers.add(name, color=aci)
build_blocks(doc)
msp = doc.modelspace()
print("Erzeuge DXF aus Skeleton-Dateien:")
for n in ([args.uh] if args.uh else range(1, 6)):
skel_file = results / f"skeleton_UH0{n}.json"
if not skel_file.exists():
sys.exit(f"FEHLER: {skel_file} fehlt — zuerst bin\\create_skel.bat "
f"ausfuehren.")
skel = json.loads(skel_file.read_text(encoding="utf-8"))
draw_plc(msp, skel, n, colors=args.colors)
out = results / args.out
doc.saveas(out)
print(f"DXF geschrieben: {out}")
if __name__ == "__main__":
main()