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}