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) <noreply@anthropic.com>
This commit is contained in:
2026-07-31 13:32:48 +02:00
parent 977b5c826e
commit 0f02fd83aa
6 changed files with 380 additions and 55 deletions
+61 -13
View File
@@ -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}