From 0f02fd83aaa5b0908465dbc5e113a3ca00ba76b2 Mon Sep 17 00:00:00 2001 From: Samer Ayadi Date: Fri, 31 Jul 2026 13:32:48 +0200 Subject: [PATCH] Give each TRO type its own CAD symbol and add a TROEDIT dialog Replaces the generic marker with one shape per type, so the types are distinguishable in the drawing without relying on colour. The shape belongs to the type, so it sits in tro_catalog next to the colour - colour groups (6 groups), shape identifies (10 types): 1Sep circle 1Sep1Swi triangle 1Sep2Swi pentagon 1Sep_SSCC 2 circles Vario rectangle Vario_workStation hexagon PinStore_Auto square EmptyCarrBuffer diamond LoadingBoom arrow 2Sep1Swi triangle down The visible attributes are now ID and TYPE, as those are what the dialog edits; FB_BLOCK stays visible under --fb and ITEMS/CONFIDENCE/SEPARATORS stay hidden data. Note this renames the former TRO_ID/TRO_TYPE tags, so drawings annotated with an earlier version need regenerating. Marker layers now take the *stroke* colour instead of the fill. A CAD symbol is line work, and the pastel fills of the palette were nearly invisible as lines - LoadingBoom in particular came out almost white on white. cad/tro_edit.dcl and cad/tro_edit.lsp add the TROEDIT command: pick a TRO_SYM_* block, edit ID and TYPE, write back on OK. TYPE is a picklist rather than free text so it cannot drift from the catalogue; the list comes from cad/tro_types.lsp, which "tro_annotate.py --emit-lisp" generates from TRO_CATALOG. If that file is missing the dialog degrades to the block's current type instead of failing. The dialog deliberately changes attributes only. The marker shape belongs to the block definition of the type and FB_BLOCK is derived from it, so both follow on the next annotation run - the dialog says so, and TROEDIT prints a reminder when the type was changed. Co-Authored-By: Claude Opus 5 (1M context) --- cad/tro_edit.dcl | 35 +++++++++ cad/tro_edit.lsp | 160 ++++++++++++++++++++++++++++++++++++++ cad/tro_types.lsp | 4 + cfg/dxf_registration.json | 14 ---- lib/tro_annotate.py | 148 ++++++++++++++++++++++++++++------- lib/tro_catalog.py | 74 ++++++++++++++---- 6 files changed, 380 insertions(+), 55 deletions(-) create mode 100644 cad/tro_edit.dcl create mode 100644 cad/tro_edit.lsp create mode 100644 cad/tro_types.lsp diff --git a/cad/tro_edit.dcl b/cad/tro_edit.dcl new file mode 100644 index 0000000..20a8b02 --- /dev/null +++ b/cad/tro_edit.dcl @@ -0,0 +1,35 @@ +// ================================================================ +// SPS_SKEL - Dialog zum Bearbeiten eines TRO-Markers +// ================================================================ +// Wird von cad/tro_edit.lsp geladen (Befehl TROEDIT). +// Bearbeitet werden die Attribute ID und TYPE eines TRO_SYM_*-Blocks. +// ================================================================ + +tro_edit : dialog { + label = "TRO bearbeiten"; + + : boxed_column { + label = "Block"; + : text { key = "blockname"; label = ""; } + : text { key = "position"; label = ""; } + } + + : boxed_column { + label = "Attribute"; + : edit_box { + key = "id"; + label = "&ID"; + edit_width = 24; + } + : popup_list { + key = "type"; + label = "&Typ"; + width = 26; + } + } + + : text { key = "hint"; label = ""; } + + spacer; + ok_cancel; +} diff --git a/cad/tro_edit.lsp b/cad/tro_edit.lsp new file mode 100644 index 0000000..797bed3 --- /dev/null +++ b/cad/tro_edit.lsp @@ -0,0 +1,160 @@ +;; ================================================================ +;; SPS_SKEL - TROEDIT: Attribute eines TRO-Markers bearbeiten +;; ================================================================ +;; Befehl: TROEDIT -> Marker waehlen -> Dialog -> OK schreibt zurueck +;; +;; Bearbeitet die Attribute ID und TYPE eines Blocks TRO_SYM_*, die von +;; lib/tro_annotate.py eingefuegt wurden. Die Auswahlliste der Typen kommt aus +;; cad/tro_types.lsp (erzeugt mit "tro_annotate.py --emit-lisp"); fehlt die +;; Datei, ist TYPE ein freies Eingabefeld. +;; +;; Laden in BricsCAD: +;; (load "/cad/tro_edit.lsp") +;; oder cad/ in die Supportpfade aufnehmen und per APPLOAD dauerhaft laden. +;; +;; Doppelklick statt Befehl: in der CUI unter "Doppelklick-Aktionen" fuer den +;; Objekttyp "Block-Referenz" den Befehl TROEDIT hinterlegen. +;; +;; Hinweis: der Dialog aendert nur die Attribute. Die *Form* des Markers gehoert +;; zur Blockdefinition des Typs - nach einer Typaenderung passt sie erst wieder, +;; wenn tro_annotate.py neu laeuft. Dasselbe gilt fuer FB_BLOCK, das aus dem Typ +;; abgeleitet ist. +;; ================================================================ + +(setq TRO:PREFIX "TRO_SYM_") + +;; --- Hilfsfunktionen ------------------------------------------------------- + +(defun tro:attribs (blk / ent typ res) + "Alle ATTRIB-Unterobjekte einer Blockreferenz als ((Tag . Entity) ...)." + (setq ent (entnext blk) res '()) + (while (and ent + (setq typ (cdr (assoc 0 (entget ent)))) + (= typ "ATTRIB")) + (setq res (cons (cons (strcase (cdr (assoc 2 (entget ent)))) ent) res)) + (setq ent (entnext ent))) + (reverse res)) + +(defun tro:get (attlist tag / pair) + "Wert eines Attributs lesen, sonst \"\"." + (if (setq pair (assoc (strcase tag) attlist)) + (cdr (assoc 1 (entget (cdr pair)))) + "")) + +(defun tro:put (attlist tag value / pair data) + "Wert eines Attributs schreiben. Gibt T zurueck, wenn das Attribut existiert." + (if (setq pair (assoc (strcase tag) attlist)) + (progn + (setq data (entget (cdr pair))) + (entmod (subst (cons 1 value) (assoc 1 data) data)) + (entupd (cdr pair)) + T) + nil)) + +(defun tro:index (item lst / i found) + "Position von item in lst, sonst nil." + (setq i 0 found nil) + (foreach x lst + (if (and (not found) (= (strcase x) (strcase item))) (setq found i)) + (setq i (1+ i))) + found) + +(defun tro:types () + "Gueltige Typen aus cad/tro_types.lsp, sonst nil." + (if (not *TRO-TYPES*) + (if (setq f (findfile "tro_types.lsp")) (load f))) + *TRO-TYPES*) + +;; --- Befehl ---------------------------------------------------------------- + +(defun c:TROEDIT (/ sel blk data name attlist types dcl dlg + cur-id cur-type new-id new-type idx result) + + ;; 1. Marker waehlen + (setq sel (entsel "\nTRO-Marker waehlen: ")) + (if (not sel) + (progn (princ "\nAbgebrochen.") (exit))) + (setq blk (car sel) + data (entget blk)) + + (if (/= (cdr (assoc 0 data)) "INSERT") + (progn (princ "\nDas ist keine Blockreferenz.") (exit))) + + (setq name (cdr (assoc 2 data))) + (if (/= (substr name 1 (strlen TRO:PREFIX)) TRO:PREFIX) + (progn + (princ (strcat "\nBlock \"" name "\" ist kein TRO-Marker (erwartet " + TRO:PREFIX "*).")) + (exit))) + + (setq attlist (tro:attribs blk)) + (if (not (assoc "ID" attlist)) + (progn + (princ "\nBlock hat kein Attribut ID - bitte neu beschriften lassen.") + (exit))) + + (setq cur-id (tro:get attlist "ID") + cur-type (tro:get attlist "TYPE") + types (tro:types)) + + ;; Typ des Blocks ergaenzen, falls er nicht in der Liste steht + (if (and types (/= cur-type "") (not (tro:index cur-type types))) + (setq types (append types (list cur-type)))) + (if (not types) (setq types (list cur-type))) + + ;; 2. Dialog laden + (setq dcl (findfile "tro_edit.dcl")) + (if (not dcl) + (progn + (princ "\ntro_edit.dcl nicht gefunden - cad/ in die Supportpfade legen.") + (exit))) + (setq dlg (load_dialog dcl)) + (if (not (new_dialog "tro_edit" dlg)) + (progn (unload_dialog dlg) (princ "\nDialog nicht ladbar.") (exit))) + + ;; 3. Felder fuellen + (set_tile "blockname" (strcat "Block: " name)) + (set_tile "position" + (strcat "Position: " + (rtos (car (cdr (assoc 10 data))) 2 1) " / " + (rtos (cadr (cdr (assoc 10 data))) 2 1))) + (set_tile "hint" "Form und FB_BLOCK folgen erst beim naechsten Lauf.") + (set_tile "id" cur-id) + + (start_list "type") + (foreach t types (add_list t)) + (end_list) + (setq idx (tro:index cur-type types)) + (set_tile "type" (itoa (if idx idx 0))) + + ;; 4. Eingaben abholen + (setq new-id cur-id new-type cur-type) + (action_tile "id" "(setq new-id (get_tile \"id\"))") + (action_tile "type" "(setq new-type (nth (atoi (get_tile \"type\")) types))") + (action_tile "accept" + "(setq new-id (get_tile \"id\") + new-type (nth (atoi (get_tile \"type\")) types)) + (done_dialog 1)") + (action_tile "cancel" "(done_dialog 0)") + + (setq result (start_dialog)) + (unload_dialog dlg) + + ;; 5. Zurueckschreiben + (if (= result 1) + (progn + (if (= (vl-string-trim " " new-id) "") + (princ "\nID darf nicht leer sein - nichts geaendert.") + (progn + (tro:put attlist "ID" new-id) + (tro:put attlist "TYPE" new-type) + (entupd blk) + (princ (strcat "\n" name ": ID = " new-id ", TYPE = " new-type)) + (if (/= new-type cur-type) + (princ "\nTyp geaendert - tro_annotate.py neu laufen lassen, damit Form und FB passen.")))) + ) + (princ "\nAbgebrochen.")) + (princ)) + +(princ "\nTROEDIT geladen. Befehl: TROEDIT") +(princ) diff --git a/cad/tro_types.lsp b/cad/tro_types.lsp new file mode 100644 index 0000000..e6c7142 --- /dev/null +++ b/cad/tro_types.lsp @@ -0,0 +1,4 @@ +;; Automatisch erzeugt von lib/tro_annotate.py --emit-lisp +;; Quelle: TRO_CATALOG in lib/tro_catalog.py - nicht manuell aendern. +(setq *TRO-TYPES* (list "1Sep" "1Sep1Swi" "1Sep2Swi" "1Sep_SSCC" "Vario" "PinStore_Auto" "Vario_workStation" "EmptyCarrBuffer" "LoadingBoom" "2Sep1Swi")) +(princ) diff --git a/cfg/dxf_registration.json b/cfg/dxf_registration.json index 4866bee..fce46b7 100644 --- a/cfg/dxf_registration.json +++ b/cfg/dxf_registration.json @@ -12,19 +12,5 @@ "csv_file": "mubea.csv", "dxf_file": "500573_60_1.dxf", "created": "2026-07-30 15:32" - }, - "mubea.csv|Mubea.dxf": { - "dx": 50905.67832743315, - "dy": 8454.490373798795, - "rotation": 0.0, - "source": "auto", - "anchor": "14 exakte Treffer (<= 50 mm) ueber die Rollen Scanner:4, Separator:31; 14 Stimmen im 100-mm-Raster, 6 Kandidaten geprueft; ACHTUNG mehrdeutig - Kandidat 2 hat ebenfalls 14 exakte Treffer (Restfehler 0.0 mm)", - "matched": 27, - "candidates": 35, - "residual_max": 544.8224945439861, - "residual_mean": 122.81360370388875, - "csv_file": "mubea.csv", - "dxf_file": "Mubea.dxf", - "created": "2026-07-30 15:38" } } diff --git a/lib/tro_annotate.py b/lib/tro_annotate.py index ea37539..a8ea757 100644 --- a/lib/tro_annotate.py +++ b/lib/tro_annotate.py @@ -21,16 +21,21 @@ denselben Layern unangetastet bleiben. Marker als Block mit Attributen ------------------------------- Je TRO-Typ wird eine Blockdefinition angelegt (TRO_SYM_) und an der -Anlagenkoordinate eingefuegt. Die Daten stehen als Attribute am Block: +Anlagenkoordinate eingefuegt. **Jeder Typ hat seine eigene Form** (Kreis, +Dreieck, Rechteck, Raute ...; siehe TroSymbol in tro_catalog), sodass sich die +Typen auch ohne Farbe unterscheiden. Die Daten stehen als Attribute am Block: - TRO_ID TRO03 - TRO_TYPE 1Sep - FB_BLOCK FB_ILS_MTRO_1Sep - ITEMS 1x Separator - CONFIDENCE mittel + ID TRO03 sichtbar, im Dialog bearbeitbar + TYPE 1Sep sichtbar, im Dialog bearbeitbar + FB_BLOCK FB_ILS_... sichtbar nur mit --fb + ITEMS 1x Separator unsichtbar, reine Daten + CONFIDENCE mittel unsichtbar + SEPARATORS 0010 unsichtbar -Damit sind sie in BricsCAD auswertbar und aenderbar - und ein korrigiertes -TRO_ID kann spaeter zurueck in die Generierung laufen. +Damit sind sie in BricsCAD auswertbar und aenderbar - und ein korrigiertes ID +kann spaeter zurueck in die Generierung laufen. Zum Bearbeiten gibt es den +Dialog cad/tro_edit.dcl mit dem Befehl TROEDIT (cad/tro_edit.lsp); die Liste der +gueltigen Typen dafuer schreibt --emit-lisp aus dem Katalog. Lagebezug --------- @@ -72,7 +77,7 @@ from dxf_registration import ( verify_transform, ) from material_flow import build_graph, env_dir, read_elements, resolve_input -from tro_catalog import STYLES, get_tro +from tro_catalog import STYLES, TRO_CATALOG, TroSymbol, get_tro from tro_flow import Analysis, analyse_tros # --------------------------------------------------------------------------- @@ -96,6 +101,27 @@ def hex_to_rgb(value: str) -> tuple[int, int, int]: return (int(text[0:2], 16), int(text[2:4], 16), int(text[4:6], 16)) +def emit_lisp(cad_dir: Path) -> Path: + """ + Typliste fuer den DCL-Dialog schreiben. + + Der Dialog TROEDIT bietet TYPE als Auswahlliste an. Damit sie nicht von der + Typdefinition abweicht, wird sie hier aus TRO_CATALOG erzeugt statt in der + LISP-Datei gepflegt. + """ + cad_dir.mkdir(parents=True, exist_ok=True) + path = cad_dir / "tro_types.lsp" + types = " ".join(f'"{d.get_name()}"' for d in TRO_CATALOG) + path.write_text( + ";; Automatisch erzeugt von lib/tro_annotate.py --emit-lisp\n" + ";; Quelle: TRO_CATALOG in lib/tro_catalog.py - nicht manuell aendern.\n" + f"(setq *TRO-TYPES* (list {types}))\n" + "(princ)\n", + encoding="utf-8", + ) + return path + + # --------------------------------------------------------------------------- # Lagebezug # --------------------------------------------------------------------------- @@ -190,8 +216,10 @@ def build_layers(doc, prefix: str) -> dict[str, str]: name = f"{prefix}MARKER_{style.group}" names[style.group] = name if name not in doc.layers: + # Strichfarbe, nicht Fuellfarbe: im CAD ist das Symbol eine Linie. + # Die pastelligen Fuellfarben waeren als Linien kaum zu sehen. doc.layers.add(name, dxfattribs={ - "true_color": ezdxf.rgb2int(hex_to_rgb(style.fill)), + "true_color": ezdxf.rgb2int(hex_to_rgb(style.stroke)), }) for key, colour in (("label", "#404040"), ("flow", "#2f5597"), ("legend", "#808080")): @@ -204,29 +232,80 @@ def build_layers(doc, prefix: str) -> dict[str, str]: return names +def _polygon(sides: int, radius: float, start_deg: float) -> list[tuple[float, float]]: + """Eckpunkte eines regelmaessigen Vielecks um den Ursprung.""" + return [ + (radius * math.cos(math.radians(start_deg + i * 360 / sides)), + radius * math.sin(math.radians(start_deg + i * 360 / sides))) + for i in range(sides) + ] + + +def draw_shape(blk, symbol: str, r: float) -> None: + """ + Symbolgeometrie in eine Blockdefinition zeichnen. + + Farbe 256 = BYLAYER, damit die Farbe des INSERT-Layers (Farbgruppe des Typs) + durchschlaegt. Alle Formen liegen um den Ursprung, damit der Einfuegepunkt + des Blocks die Anlagenkoordinate ist. + """ + attr = {"color": 256} + if symbol == TroSymbol.CIRCLE: + blk.add_circle((0, 0), r, dxfattribs=attr) + elif symbol == TroSymbol.DOUBLE_CIRCLE: + blk.add_circle((0, 0), r, dxfattribs=attr) + blk.add_circle((0, 0), r * 0.6, dxfattribs=attr) + elif symbol == TroSymbol.TRIANGLE: + blk.add_lwpolyline(_polygon(3, r, 90), close=True, dxfattribs=attr) + elif symbol == TroSymbol.TRIANGLE_DOWN: + blk.add_lwpolyline(_polygon(3, r, 270), close=True, dxfattribs=attr) + elif symbol == TroSymbol.SQUARE: + blk.add_lwpolyline(_polygon(4, r, 45), close=True, dxfattribs=attr) + elif symbol == TroSymbol.DIAMOND: + blk.add_lwpolyline(_polygon(4, r, 90), close=True, dxfattribs=attr) + elif symbol == TroSymbol.PENTAGON: + blk.add_lwpolyline(_polygon(5, r, 90), close=True, dxfattribs=attr) + elif symbol == TroSymbol.HEXAGON: + blk.add_lwpolyline(_polygon(6, r, 0), close=True, dxfattribs=attr) + elif symbol == TroSymbol.RECT: + w, h = r * 1.45, r * 0.72 + blk.add_lwpolyline([(-w, -h), (w, -h), (w, h), (-w, h)], + close=True, dxfattribs=attr) + elif symbol == TroSymbol.ARROW: + blk.add_lwpolyline( + [(-r * 0.55, -r), (r, 0), (-r * 0.55, r), (-r * 0.55, r * 0.35), + (-r, r * 0.35), (-r, -r * 0.35), (-r * 0.55, -r * 0.35)], + close=True, dxfattribs=attr) + else: + # unbekanntes Symbol: Kreis mit Kreuz, faellt auf + blk.add_circle((0, 0), r, dxfattribs=attr) + blk.add_line((-r, -r), (r, r), dxfattribs=attr) + blk.add_line((-r, r), (r, -r), dxfattribs=attr) + + def build_symbol(doc, type_name: str, marker: float, text: float, show_fb: bool) -> str: - """Blockdefinition fuer einen TRO-Typ anlegen (Marker + Attributdefinitionen).""" + """ + Blockdefinition fuer einen TRO-Typ anlegen: Symbol + Attributdefinitionen. + + Je Typ eine eigene Form (siehe TroSymbol), damit sich die Typen in der + Zeichnung auch ohne Farbe unterscheiden. Die Attribute ID und TYPE sind + sichtbar und werden vom DCL-Dialog TROEDIT bearbeitet. + """ name = f"{BLOCK_PREFIX}{type_name}" if name in doc.blocks: return name blk = doc.blocks.new(name=name) - # Marker: Ring mit Mittelpunkt, Farbe kommt vom Layer des INSERT - blk.add_circle((0, 0), marker, dxfattribs={"color": 256}) - blk.add_circle((0, 0), marker * 0.18, dxfattribs={"color": 256}) - blk.add_line((-marker, 0), (marker, 0), dxfattribs={"color": 256}) - blk.add_line((0, -marker), (0, marker), dxfattribs={"color": 256}) + definition = get_tro(type_name) + draw_shape(blk, definition.get_symbol() if definition else "", marker) - gap = marker * 1.35 - rows = [ - ("TRO_ID", type_name and "TRO??", text * 1.35, 0), - ("TRO_TYPE", type_name, text, 0), - ] + gap = marker * 1.5 + rows = [("ID", "TRO??", text * 1.35), ("TYPE", type_name, text)] if show_fb: - rows.append(("FB_BLOCK", "FB", text * 0.85, 0)) + rows.append(("FB_BLOCK", "FB", text * 0.85)) offset = 0.0 - for tag_name, default, height, _ in rows: + for tag_name, default, height in rows: blk.add_attdef( tag_name, insert=(gap, offset), @@ -235,7 +314,7 @@ def build_symbol(doc, type_name: str, marker: float, text: float, show_fb: bool) ) offset -= height * 1.55 - # nicht sichtbare Attribute: reine Daten + # nicht sichtbare Attribute: reine Daten, nicht im Dialog for tag_name in ("ITEMS", "CONFIDENCE", "SEPARATORS"): blk.add_attdef( tag_name, @@ -271,8 +350,8 @@ def place_markers( block = build_symbol(doc, tro.type_name, marker, text, show_fb) values = { - "TRO_ID": tro.tro_id, - "TRO_TYPE": tro.type_name, + "ID": tro.tro_id, + "TYPE": tro.type_name, "ITEMS": ", ".join(f"{c}x {i}" for i, c in sorted(tro.items.items())), "CONFIDENCE": tro.confidence, "SEPARATORS": ", ".join(tro.separators), @@ -425,9 +504,9 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace: parser.add_argument("--file", default="export.csv", metavar="NAME", help="CSV-Export in %%SKEL_DATA%% oder ein Pfad. " "Standard: %(default)s") - parser.add_argument("--dxf", required=True, metavar="NAME", + parser.add_argument("--dxf", metavar="NAME", help="Originalzeichnung als DXF (Pfad oder Name in " - "%%SKEL_DATA%%).") + "%%SKEL_DATA%%). Ausser bei --emit-lisp erforderlich.") parser.add_argument("--out", metavar="NAME", help="Name der Ausgabedatei in %%SKEL_RESULTS%%. " "Standard: _annotated.dxf") @@ -456,6 +535,9 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace: "Standard: %(default)s") parser.add_argument("--check", action="store_true", help="Nur pruefen und berichten, keine Datei schreiben.") + parser.add_argument("--emit-lisp", action="store_true", + help="cad/tro_types.lsp aus dem Typkatalog neu schreiben " + "(Auswahlliste des Dialogs TROEDIT) und beenden.") return parser.parse_args(argv) @@ -463,6 +545,16 @@ def main(argv: list[str] | None = None) -> int: args = parse_args(argv) warnings: list[str] = [] + if args.emit_lisp: + path = emit_lisp(Path(__file__).resolve().parent.parent / "cad") + print(f"Typliste geschrieben: {path} ({len(TRO_CATALOG)} Typen)") + return 0 + + if not args.dxf: + print("FEHLER: --dxf fehlt (nur --emit-lisp geht ohne Zeichnung).", + file=sys.stderr) + return 1 + csv_file = resolve_input(args.file) if not csv_file.is_file(): print(f"FEHLER: CSV nicht gefunden: {csv_file}", file=sys.stderr) diff --git a/lib/tro_catalog.py b/lib/tro_catalog.py index 12215f1..57ba402 100644 --- a/lib/tro_catalog.py +++ b/lib/tro_catalog.py @@ -8,14 +8,15 @@ Dieses Modul ist die **einzige** Quelle fuer alles, was zur Erzeugung eines TRO gebraucht wird. Es liest keine Dateien und braucht kein Markdown zur Laufzeit - die Herkunft der Daten steht nur noch in den Kommentaren. -Ein TRO-Typ wird durch vier Dinge beschrieben: +Ein TRO-Typ wird durch fuenf Dinge beschrieben: 1. Name - Kurztyp, wie in FB_Main und connect.ini verwendet ("1Sep") 2. FB-Baustein - der Siemens-Funktionsbaustein ("FB_ILS_MTRO_1Sep") 3. Bauteile - welche Elemente in diesem TRO stecken und wie viele (1Sep = 1x Separator, 1Sep1Swi = 1x Separator + 1x Weiche) - 4. Darstellung - Farbe/Gruppe fuer Zeichnungen (Graphviz, Mermaid, spaeter - das BricsCAD-Symbol) + 4. Farbe - Farbgruppe fuer Zeichnungen (Graphviz, Mermaid, CAD) + 5. Symbol - Form des CAD-Markers, je Typ eine eigene (Kreis, Dreieck, + Rechteck, Raute ...) Herkunft der Daten (Stand :data:`CATALOG_AS_OF`) ----------------------------------------------- @@ -66,6 +67,7 @@ __all__ = [ "CATALOG_AS_OF", "TroItem", "TroStyle", + "TroSymbol", "TroDefinition", "ITEM_FB", "STYLES", @@ -130,6 +132,36 @@ ITEM_FB: dict[str, str] = { } +class TroSymbol: + """ + Zeichensymbole fuer die Marker in der Zeichnung. + + Die Farbe gruppiert (siehe TroStyle), das Symbol unterscheidet den einzelnen + Typ. Alle TROs eines Typs bekommen dasselbe Symbol. Die Formen sind absichtlich + einfach - Kreis, Vielecke, Rechteck - damit sie in jedem Zoom erkennbar + bleiben und als LWPOLYLINE bzw. CIRCLE in den Block passen. + """ + + CIRCLE = "circle" # Kreis + DOUBLE_CIRCLE = "double_circle" # Kreis mit zweitem Kreis innen + TRIANGLE = "triangle" # Dreieck, Spitze oben + TRIANGLE_DOWN = "triangle_down" # Dreieck, Spitze unten + SQUARE = "square" # Quadrat + DIAMOND = "diamond" # Raute + PENTAGON = "pentagon" # Fuenfeck + HEXAGON = "hexagon" # Sechseck + RECT = "rect" # liegendes Rechteck + ARROW = "arrow" # Pfeil/Fahne + + @classmethod + def all(cls) -> tuple[str, ...]: + return tuple( + value + for key, value in vars(cls).items() + if key.isupper() and isinstance(value, str) + ) + + class TroStyle(BaseModel): """ Darstellung einer TRO-Typgruppe. @@ -247,6 +279,11 @@ class TroDefinition(BaseModel): default=STYLE_EXT, description="Farbgruppe fuer Zeichnungen (Graphviz, Mermaid, CAD-Symbol)", ) + symbol: str = Field( + default=TroSymbol.CIRCLE, + min_length=1, + description="Zeichensymbol des Markers im CAD, z. B. 'triangle'", + ) def __init__( self, @@ -254,6 +291,7 @@ class TroDefinition(BaseModel): fb_block: str | None = None, items: dict[str, int] | None = None, style: TroStyle | None = None, + symbol: str | None = None, **data, ) -> None: # Positionsargumente auf Feldnamen abbilden, damit die bisherige @@ -266,6 +304,8 @@ class TroDefinition(BaseModel): data["items"] = items if style is not None: data["style"] = style + if symbol is not None: + data["symbol"] = symbol super().__init__(**data) @field_validator("items", mode="after") @@ -319,6 +359,10 @@ class TroDefinition(BaseModel): """Darstellung (Farbgruppe) dieses Typs.""" return self.style + def get_symbol(self) -> str: + """Zeichensymbol des Markers, z. B. 'triangle'.""" + return self.symbol + def get_group(self) -> str: """Kurzname der Farbgruppe, z. B. 'vario'.""" return self.style.group @@ -348,6 +392,10 @@ class TroDefinition(BaseModel): """Darstellung (Farbgruppe) setzen.""" self.style = style + def set_symbol(self, symbol: str) -> None: + """Zeichensymbol des Markers setzen.""" + self.symbol = symbol + def set_item(self, item: str, count: int) -> None: """ Anzahl einer Bauteilart setzen. @@ -394,29 +442,29 @@ TRO_CATALOG: tuple[TroDefinition, ...] = ( # (PriorityManager, JamArea Entry/Exit, HMI, Safety, MachineState). TroDefinition("1Sep", "FB_ILS_MTRO_1Sep", { TroItem.SEPARATOR: 1, - }, STYLE_SEP), + }, STYLE_SEP, TroSymbol.CIRCLE), # laut TRO_Typen.md: Basis + 1x Switch-Modul TroDefinition("1Sep1Swi", "FB_ILS_MTRO_1Sep1Swi", { TroItem.SEPARATOR: 1, TroItem.SWITCH: 1, - }, STYLE_SWI), + }, STYLE_SWI, TroSymbol.TRIANGLE), # laut TRO_Typen.md: Basis + 2x Switch-Modul + Scanner-Modul TroDefinition("1Sep2Swi", "FB_ILS_MTRO_1Sep2Swi", { TroItem.SEPARATOR: 1, TroItem.SWITCH: 2, TroItem.SCANNER: 1, - }, STYLE_SWI), + }, STYLE_SWI, TroSymbol.PENTAGON), # laut TRO_Typen.md: Basis + Scanner-Modul + SSCC-Modul (Messsensor + WCS) TroDefinition("1Sep_SSCC", "FB_ILS_MTRO_1Sep_SSCC", { TroItem.SEPARATOR: 1, TroItem.SCANNER: 1, TroItem.SSCC: 1, - }, STYLE_SSCC), + }, STYLE_SSCC, TroSymbol.DOUBLE_CIRCLE), # laut TRO_Typen.md: Basis + Vario-Antriebsmodul TroDefinition("Vario", "FB_ILS_MTRO_Vario", { TroItem.SEPARATOR: 1, TroItem.VARIO_DRIVE: 1, - }, STYLE_VARIO), + }, STYLE_VARIO, TroSymbol.RECT), # laut TRO_Typen.md: Basis (Pin-Separator) + 19x Storage-Line + Scanner # + Encoder + StorageRemoval-Schnittstelle TroDefinition("PinStore_Auto", "FB_ILS_MTRO_PinStore_Auto", { @@ -424,7 +472,7 @@ TRO_CATALOG: tuple[TroDefinition, ...] = ( TroItem.STORAGE_LINE: 19, TroItem.SCANNER: 1, TroItem.ENCODER: 1, - }, STYLE_STORE), + }, STYLE_STORE, TroSymbol.SQUARE), # aus SCL belegt (FB_ILS_MTRO_Vario_workStation.scl, VAR ab Zeile 77): # fbSeparator1, fbConvVario, fbSepWait, fbBarcodeReader. # Der Vario-Antrieb ist hier die WorkStation-Variante des STRO-Bausteins. @@ -434,7 +482,7 @@ TRO_CATALOG: tuple[TroDefinition, ...] = ( TroItem.WORK_STATION: 1, TroItem.ACCUMULATE: 1, TroItem.SCANNER: 1, - }, STYLE_VARIO), + }, STYLE_VARIO, TroSymbol.HEXAGON), # aus SCL belegt (FB_EmptyCarrBuffer.scl): arfbSeparatorStorage als # Array[1..5] of FB_ILS_STRO_Sep + 5x fbCarrierWaitStore. # Kein eigener Eingangs-Separator - der "inTro" ist ein separater 1Sep-TRO @@ -442,7 +490,7 @@ TRO_CATALOG: tuple[TroDefinition, ...] = ( TroDefinition("EmptyCarrBuffer", "FB_EmptyCarrBuffer", { TroItem.STORAGE_LINE: 5, TroItem.ACCUMULATE: 5, - }, STYLE_STORE), + }, STYLE_STORE, TroSymbol.DIAMOND), # aus SCL belegt (FB_LoadingBoom_INBOUND.scl): fbTiltSensor, # fbDistanceFoot, fbWorkSation. Bewusst KEIN Separator und KEINE Weiche - # der einzige nicht additive Typ. @@ -450,7 +498,7 @@ TRO_CATALOG: tuple[TroDefinition, ...] = ( TroItem.BOOM: 1, TroItem.FOOT: 1, TroItem.TILT_SENSOR: 1, - }, STYLE_EXT), + }, STYLE_EXT, TroSymbol.ARROW), # aus SCL belegt (FB_ILS_MTRO_2Sep1Swi.scl, VAR ab Zeile 115): # fbSeparator1, fbSeparator2, fbSwitch1, fbBarcodeReader1, fbBarcodeReader2. # Die Mapping-Tabelle in TRO_Typen.md nennt die beiden Scanner nicht - @@ -459,7 +507,7 @@ TRO_CATALOG: tuple[TroDefinition, ...] = ( TroItem.SEPARATOR: 2, TroItem.SWITCH: 1, TroItem.SCANNER: 2, - }, STYLE_SWI), + }, STYLE_SWI, TroSymbol.TRIANGLE_DOWN), ) _BY_NAME: dict[str, TroDefinition] = {t.get_name(): t for t in TRO_CATALOG}