dxf Erzeugung aus den .json Skeletons ermöglicht, um anzuzeigen welche Elemente zusammengehören
This commit is contained in:
@@ -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()
|
||||
Reference in New Issue
Block a user