tro_annotate --read-tros: TRO-Handkorrekturen aus BricsCAD, Staubereiche vor dem TRO
- Marker tragen KEY (ID der ID-Datei) und SLOTS (Bauteil je Slot), die Zeichnung die Bauteiltabelle SPS_SKEL_PARTS (XRECORD) fuer "Im Plan waehlen" im Dialog TRO_EDIT (dxfmakros) - --read-tros schreibt geaenderte, neue und geloeschte Marker als Abschnitte edit/edit_add nach cfg/tro_overrides.ini; tro_flow.apply_edits wendet nur diese Arten an (Bauteile mit Lage, exportfest) - --emit-lisp schreibt dazu *TRO-SLOTS* und *TRO-SLOT-KINDS* - model: Lagerlinien zaehlen je TRO (Katalog nur Vorgabe), Mubea-Speicher jetzt 20 Linien (Golden-Datei) - Staubereich als stehendes graues Rechteck direkt vor seinem TRO-Marker (jam_abstand statt jam_versatz); Marker 150 mm, Text 120 mm Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+86
-8
@@ -82,6 +82,10 @@ __all__ = [
|
||||
"derive_slots",
|
||||
"derive_jam_areas",
|
||||
"storage_lines",
|
||||
"SLOT_PART_KINDS",
|
||||
"slot_kind",
|
||||
"slot_names",
|
||||
"has_lines",
|
||||
]
|
||||
|
||||
# Kennung der TRO-JSON. v1 war die Ausgabe von tro_extract (aus der Zeichnung),
|
||||
@@ -204,9 +208,17 @@ class TroBase(_Positioned):
|
||||
@computed_field # type: ignore[prop-decorator]
|
||||
@property
|
||||
def items(self) -> dict[str, int]:
|
||||
"""Bauteile laut Katalog, z. B. {'Separator': 1} ({} wenn Typ unbekannt)."""
|
||||
"""Bauteile laut Katalog, z. B. {'Separator': 1} ({} wenn Typ unbekannt).
|
||||
|
||||
Ausnahme Lagerlinien: der Katalog nennt nur die Vorgabe (PinStore_Auto
|
||||
19); hat der TRO Linien, gilt deren Zahl (Layout oder Handkorrektur im
|
||||
Dialog TRO_EDIT).
|
||||
"""
|
||||
definition = self.definition()
|
||||
return definition.get_items() if definition else {}
|
||||
if not definition:
|
||||
return {}
|
||||
return _with_line_count(definition.get_items(), self.separators,
|
||||
getattr(self, "hosts", []))
|
||||
|
||||
@model_validator(mode="before")
|
||||
@classmethod
|
||||
@@ -219,9 +231,14 @@ class TroBase(_Positioned):
|
||||
fb_block = data.pop("fbType", data.pop("fb_block", None))
|
||||
type_name = data.get("type", data.get("type_name"))
|
||||
definition = get_tro(type_name) if isinstance(type_name, str) else None
|
||||
expected_items = definition.get_items() if definition else {}
|
||||
expected_items = (_with_line_count(definition.get_items(),
|
||||
data.get("separators", []),
|
||||
data.get("hosts", []))
|
||||
if definition else {})
|
||||
# Dateien vor 2026-10-26 tragen bei Lagerlinien die Katalogvorgabe
|
||||
catalog_items = definition.get_items() if definition else {}
|
||||
expected_fb = definition.get_fb_block() if definition else ""
|
||||
if items is not None and dict(items) != expected_items:
|
||||
if items is not None and dict(items) not in (expected_items, catalog_items):
|
||||
raise ValueError(
|
||||
f"items {items} passt nicht zum Typ '{type_name}' laut tro_catalog "
|
||||
f"({expected_items}) - den Typ aendern, nicht die Bauteile"
|
||||
@@ -381,6 +398,11 @@ class Analysis(BaseModel):
|
||||
# Systemgrenzen (BTMT Be-/Entladestationen) - siehe derive_boundaries; kein
|
||||
# TRO, nur damit der tatsaechliche Materialein-/ausgang sichtbar wird.
|
||||
boundaries: list[DerivedTro] = Field(default_factory=list)
|
||||
# Handkorrekturen aus BricsCAD (tro_flow.apply_edits): umbenannte TROs
|
||||
# (neue ID -> ID aus <csv>_tro_ids.ini) und von Hand angelegte TROs - beide
|
||||
# gehoeren nicht unter ihrer neuen Kennung in die ID-Datei.
|
||||
renamed: dict[str, str] = Field(default_factory=dict)
|
||||
added: list[str] = Field(default_factory=list)
|
||||
|
||||
def by_id(self, tro_id: str) -> DerivedTro | None:
|
||||
for tro in self.tros + self.boundaries:
|
||||
@@ -388,6 +410,11 @@ class Analysis(BaseModel):
|
||||
return tro
|
||||
return None
|
||||
|
||||
def edit_key(self, tro_id: str) -> str:
|
||||
"""Schluessel des TRO in tro_overrides.ini (ID der ID-Datei bzw. Kennung
|
||||
des von Hand angelegten TRO)."""
|
||||
return self.renamed.get(tro_id, tro_id)
|
||||
|
||||
|
||||
class TroDocument(BaseModel):
|
||||
"""
|
||||
@@ -613,6 +640,12 @@ def _at(values: list[str], index: int) -> str:
|
||||
return values[index] if index < len(values) else ""
|
||||
|
||||
|
||||
def _lines_of(separators: list[str], hosts: list[str]) -> list[str]:
|
||||
seps, hosts = list(separators or []), list(hosts or [])
|
||||
count = len(hosts) if 0 < len(hosts) <= len(seps) else len(seps)
|
||||
return seps[len(seps) - count:]
|
||||
|
||||
|
||||
def storage_lines(tro: TroBase) -> list[str]:
|
||||
"""Separatoren der Lagerlinien eines Speicher-TRO.
|
||||
|
||||
@@ -620,10 +653,55 @@ def storage_lines(tro: TroBase) -> list[str]:
|
||||
letzten len(hosts) (eine je Speicherbahn). Fehlen Hosts, zaehlt jeder
|
||||
Separator als Linie - dieselbe Regel wie scl_context._store.
|
||||
"""
|
||||
hosts = list(getattr(tro, "hosts", []) or [])
|
||||
seps = list(tro.separators)
|
||||
count = len(hosts) if 0 < len(hosts) <= len(seps) else len(seps)
|
||||
return seps[len(seps) - count:]
|
||||
return _lines_of(tro.separators, getattr(tro, "hosts", []))
|
||||
|
||||
|
||||
def _with_line_count(items: dict[str, int], separators: list[str],
|
||||
hosts: list[str]) -> dict[str, int]:
|
||||
"""Katalog-Bauteile, Lagerlinien auf die Zahl der Linien des TRO gesetzt."""
|
||||
if items.get(TroItem.STORAGE_LINE) and (lines := _lines_of(separators, hosts)):
|
||||
items = {**items, TroItem.STORAGE_LINE: len(lines)}
|
||||
return items
|
||||
|
||||
|
||||
# Slot-Art (Name ohne Nummer) -> Bauteilarten (Element.kind im CSV), die in
|
||||
# diesen Slot passen. Gebraucht vom Dialog TRO_EDIT (Pruefung beim Waehlen im
|
||||
# Plan, tro_annotate --emit-lisp) und beim Anwenden der Handkorrekturen
|
||||
# (tro_flow.apply_edits). "drive" ist der tragende Foerderer.
|
||||
SLOT_PART_KINDS: dict[str, tuple[str, ...]] = {
|
||||
"separator": ("Separator",),
|
||||
"line": ("Separator",),
|
||||
"switch": ("Ausschleus", "Weiche"),
|
||||
"scanner": ("Scanner",),
|
||||
"drive": ("Strecke", "Gefaellestrecke", "Kreisel"),
|
||||
}
|
||||
|
||||
|
||||
def slot_kind(slot: str) -> str:
|
||||
"""'separator2' -> 'separator', 'line17' -> 'line', 'drive' -> 'drive'."""
|
||||
return slot.rstrip("0123456789")
|
||||
|
||||
|
||||
def slot_names(type_name: str) -> list[str]:
|
||||
"""Slots eines Typs in der Reihenfolge von derive_slots, ohne Lagerlinien.
|
||||
|
||||
Die Lagerlinien (line1..lineN) haengen am TRO, nicht am Typ - ob ein Typ
|
||||
sie hat, sagt :func:`has_lines`.
|
||||
"""
|
||||
definition = get_tro(type_name)
|
||||
items = definition.get_items() if definition else {}
|
||||
names = [f"separator{i}" for i in range(1, items.get(TroItem.SEPARATOR, 0) + 1)]
|
||||
names += [f"switch{i}" for i in range(1, items.get(TroItem.SWITCH, 0) + 1)]
|
||||
names += [f"scanner{i}" for i in range(1, items.get(TroItem.SCANNER, 0) + 1)]
|
||||
if items.get(TroItem.VARIO_DRIVE):
|
||||
names.append("drive")
|
||||
return names
|
||||
|
||||
|
||||
def has_lines(type_name: str) -> int:
|
||||
"""Vorgabe der Lagerlinien laut Katalog (0 = Typ hat keine)."""
|
||||
definition = get_tro(type_name)
|
||||
return definition.get_items().get(TroItem.STORAGE_LINE, 0) if definition else 0
|
||||
|
||||
|
||||
def derive_slots(tro: DerivedTro) -> list[Slot]:
|
||||
|
||||
Reference in New Issue
Block a user