e165beeaa8
A bank of gravity lanes between the same two Kreisel (same slope) was drawn as one box per lane, making large storage banks unreadable. render_dot now collapses such lanes into a single Gefaellestrecke-styled block with a double border, labelled with the id range and combined Separator/Scanner ranges. Purely visual - the graph, --doc report and --connect output still list every lane individually.
499 lines
27 KiB
Markdown
499 lines
27 KiB
Markdown
# Python Scripts — CLI Tooling Overview
|
||
|
||
**As of:** 2026-08-20
|
||
|
||
`lib/` currently holds eight Python modules (five of them runnable CLI tools,
|
||
three supporting libraries), plus one documentation-generator script that lives
|
||
under `doc/TRO_Katalog/tro_graphs/`. Together the CLI tools turn a CSV export
|
||
of the mechanical layout (ILS 2.0) into a material-flow graph, a derived TRO
|
||
list/diagram, an annotated copy of the BricsCAD drawing, a TRO-JSON read back
|
||
out of that drawing, and an `FB_Main` SCL skeleton generated from that JSON.
|
||
|
||
This document is the practical "what does each script do and which switches
|
||
does it take" reference. For the underlying domain model (TRO types, JSON
|
||
layout concept, I/O-list analysis) see `TRO_Typen.md`, `Json_Layout-Konzept.md`,
|
||
`EA-Listen-Analyse.md`, `BricsCAD_TRO_Symbol.md` and
|
||
`doc/TRO_Katalog/TRO_Katalog.md`.
|
||
|
||
## 1. How the pieces fit together
|
||
|
||
```
|
||
CSV export (in %SKEL_DATA%)
|
||
│
|
||
▼
|
||
material_flow.py ──► <name>_material_flow.dot / .svg / .md
|
||
│ (read_elements, build_graph — reused by tro_flow.py)
|
||
▼
|
||
tro_flow.py ──► <name>_tro_flow.dot / .svg, _tro_doc.md, _tro_errors.md
|
||
│ (derives TROs, using the type catalog in tro_catalog.py)
|
||
▼
|
||
tro_annotate.py ──► <name>_annotated.dxf, <name>_registration.json
|
||
│ (also needs the BricsCAD DXF; uses dxf_registration.py for the CSV↔DXF
|
||
│ coordinate offset and tro_catalog.py for marker shape/color per TRO type)
|
||
▼ (drawing may be hand-edited in BricsCAD before the next step)
|
||
tro_extract.py ──► <drawing>_tro.json
|
||
│ (reads only the drawing; resolves plant coords via the registration)
|
||
▼
|
||
scl_skeleton.py ──► <source>_FB_Main.scl
|
||
(one REGION per TRO; E-planning values left as TODO(E-Planung) gaps)
|
||
```
|
||
|
||
- `material_flow.py` models the material flow of the **mechanical** objects only
|
||
(Gefällestrecke, Strecke, Kreisel).
|
||
- `tro_flow.py` is its sister program: it derives the **control objects**
|
||
(TROs) from the same graph and draws their flow instead.
|
||
- `tro_annotate.py` takes the TROs from `tro_flow.py` and burns them into a
|
||
**copy** of the BricsCAD drawing as attributed blocks (never touches the
|
||
original).
|
||
|
||
Supporting libraries (no CLI of their own, imported by the tools above):
|
||
|
||
- **`lib/tro_catalog.py`** — the single source of truth for TRO types: short
|
||
name, Siemens FB block, sub-components and counts, color group, CAD marker
|
||
shape.
|
||
- **`lib/dxf_registration.py`** — derives, verifies and persists the rigid
|
||
(rotation + offset) transform between the CSV export's coordinate system and
|
||
the DXF drawing's.
|
||
- **`lib/tro_overrides.py`** — reads the hand-maintained
|
||
`%SKEL_CFG%\tro_overrides.ini` (type corrections, open points, TRO merges)
|
||
that survive re-runs of `tro_flow.py`. See §6a.
|
||
|
||
Separate, documentation-only tool (not part of the generator pipeline):
|
||
|
||
- **`doc/TRO_Katalog/tro_graphs/gen_tro_graphs.py`** — regenerates the
|
||
schematic per-type SVGs, the edge-type legend and the per-controller
|
||
(UH01–UH05) topology graphs embedded in `TRO_Katalog.md`, built from
|
||
`doc/TRO_Katalog/connect.ini`.
|
||
|
||
## 2. Running the scripts
|
||
|
||
Each CLI tool is invoked through its `bin/<name>.bat` (Windows) / `bin/<name>.sh`
|
||
(Linux/macOS) wrapper, which:
|
||
|
||
1. calls `setenv` to set `SPS_SKEL`, `SKEL_DATA`, `SKEL_RESULTS`, `SKEL_CFG`,
|
||
`SKEL_LIB` and prepend `SKEL_LIB` to `PYTHONPATH`;
|
||
2. activates `.venv` if it exists (`bin/install_py` creates it);
|
||
3. runs the module with `py` (falling back to `python`).
|
||
|
||
```bat
|
||
bin\material_flow.bat --file mubea.csv --tosvg --use-cords --doc
|
||
bin\tro_flow.bat --file mubea.csv --tosvg --use-cords --doc
|
||
bin\tro_annotate.bat --file mubea.csv --dxf 500573_60_1.dxf --flow --fb --legend
|
||
```
|
||
|
||
(`--use-cords` lays both graphs out at the real plant coordinates; drop it for
|
||
Graphviz's computed left-to-right arrangement.)
|
||
|
||
Conventions shared by the CLI tools:
|
||
|
||
- `--file` / `--dxf` accept either a bare filename — resolved against
|
||
`%SKEL_DATA%` — or a full path.
|
||
- All generated output is written to `%SKEL_RESULTS%`.
|
||
- `tro_annotate.py` additionally persists the verified CSV↔DXF coordinate
|
||
offset to `%SKEL_CFG%\dxf_registration.json`.
|
||
- Every run prints a `====` bordered report to stdout (counts, warnings,
|
||
output file paths); non-fatal findings are printed as `?`/`!` lines and also
|
||
do not stop the run.
|
||
- Requires the packages pinned in `requirements.txt` (`pydantic>=2.0.0`;
|
||
`ezdxf>=1.4.0` for `tro_annotate.py` only) — run `bin\install_py.bat` once.
|
||
- `--tosvg` needs Graphviz's `dot` (or `neato`, for `tro_flow.py
|
||
--use-cords`) on `PATH`, or the `GRAPHVIZ_DOT` environment variable pointing
|
||
at the `dot` executable.
|
||
|
||
## 3. `material_flow.py` — material-flow graph of the mechanical layout
|
||
|
||
Reads the CSV export and builds a directed graph of the mechanical objects:
|
||
|
||
- **Gefällestrecke** / **Strecke** (conveyor segments) → one node each.
|
||
- **Kreisel** (rotary/turntable) → two nodes (`-L` / `-R`), linked by the
|
||
`Drehrichtung` feature (`UZS` = clockwise = right→left, `GUZ` =
|
||
counter-clockwise = left→right).
|
||
- **Separator** / **Scanner** → no node of their own; shown via `Zuordnung`
|
||
(assignment) on the label of their owning node.
|
||
- **Einschleuselement** / **Ausschleuselement** / **Weiche** (from the richer export)
|
||
→ no node of their own; they supply the **direction** of the Kreisel↔Strecke edges
|
||
(`Ausschleusung` = Kreisel→Strecke at the strecke's *Anfang*, `Einschleusung` =
|
||
Strecke→Kreisel at its *Ende*, read from their `Kreisel`/`Strecke`/`Streckenende`
|
||
`Merkmale`) and **mark the Weiche** on a Kreisel↔Kreisel link. A strecke whose two
|
||
ends touch the *same* Kreisel (return loop) is split across that Kreisel's two lanes.
|
||
This makes Kreisel↔Strecke direction deterministic instead of height-inferred.
|
||
- **BTMT Be-/Entladestation** (entry/exit points) → one node each, recognised by
|
||
`TeileArt`/`Bezeichnung` containing `Beladung`/`BTMT-Beladung` (→ **entry**, a
|
||
material *source*) or `Entladung`/`SC_Entladung`/`Abwurf` (→ **exit**, a *sink*).
|
||
An entry gets an outgoing edge, an exit an incoming edge, to the object named in
|
||
its `Nachbarn`; if the export lists no neighbour (dxfmakros does not export BTMT
|
||
yet — see below), the nearest object by 2-D distance is used and a note is
|
||
printed. Recognition is **sps_skel-side only** — nothing in dxfmakros changes;
|
||
it simply activates when such a row appears in the CSV.
|
||
|
||
> **Note on BTMT / entry-exit data.** dxfmakros' `EXPORTCSV` currently does **not**
|
||
> emit the BTMT stations (their block names match no export pattern; documented as
|
||
> *"Export noch offen"*). Until that is addressed, a freshly exported `mubea.csv`
|
||
> will contain the 61 conveyor items but **no** BTMT rows, so no entry/exit nodes
|
||
> appear. The recognition above is ready for when BTMT rows are present.
|
||
|
||
In the rendered graph, **parallel Gefällestrecken between the same two Kreisel
|
||
lanes with the same slope** (`Hoehe_Von_mm`/`Hoehe_Bis_mm`) — a typical
|
||
line-storage bank — are drawn as a **single** Gefällestrecke-styled box with a
|
||
double border (`peripheries=2`) instead of one box per line. Its label states
|
||
the id range and count of the merged lines plus the combined Separator/Scanner
|
||
id ranges, e.g. `Gefaellestrecke 0017 - 0036 (20x)` / `Separator (20): 0037 -
|
||
0056`. This is purely a rendering simplification (`_group_parallel_gefaelle`
|
||
in `lib/material_flow.py`) — the underlying graph, `--doc` report and
|
||
`--connect` output are unaffected and still list every line individually.
|
||
|
||
Flow direction between conveyor segments is derived from height change
|
||
(`Hoehe_Von_mm`/`Hoehe_Bis_mm`) or, failing that, from `Antriebfahrtrichtung`
|
||
("Auf"/"Ab"); connections whose direction can't be determined are drawn
|
||
bidirectional and reported as a warning. `--doc` additionally cross-checks
|
||
stated `Anzahl_Separator`/`Anzahl_Scanner` counts against what was actually
|
||
attached.
|
||
|
||
| Switch | Argument | Default | Meaning |
|
||
|---|---|---|---|
|
||
| `--file` | `NAME` | `export.csv` | CSV input file, resolved against `%SKEL_DATA%`, or a full path. |
|
||
| `--tosvg` | — | off | Also render the `.dot` file to `.svg` via Graphviz `dot`. |
|
||
| `--use-cords` (alias `--use-coords`) | — | off | Place every node at its plant coordinate and render with `neato -n` (fixed positions) instead of `dot`, so the material graph matches the plant layout — the same option `tro_flow.py` has. The two Kreisel lanes (`-L`/`-R`) are nudged apart by `LANE_NUDGE_MM`; scale is `COORD_SCALE` (0.25 pt/mm, identical to `tro_flow.py`). Only has an effect together with `--tosvg`; fails (exit 1) if any flow node has no coordinate. |
|
||
| `--doc` | — | off | Also write a Markdown report (`<name>_material_flow.md`) with object list, connections and plausibility findings. |
|
||
| `--connect` | — | off | Also write the topology as `<name>_connect.ini` (item/flow level): `[…nodes]` (Kreisel lanes, Strecke, Gefällestrecke), `[…connections]` with a `kind` per edge (`normal`/`umlauf`/`weiche`/`einschleusung`/`ausschleusung`), and `[…externals]` — the BTMT entry/exit if present, otherwise the open lane-ends listed as entry candidates. A hand-editable bridge into the TRO step, styled after `doc/TRO_Katalog/connect.ini`. |
|
||
|
||
**Output** (in `%SKEL_RESULTS%`): `<name>_material_flow.dot` (always, with `pos`
|
||
attributes when `--use-cords` was used), `<name>_material_flow.svg` (with `--tosvg`),
|
||
`<name>_material_flow.md` (with `--doc`), `<name>_connect.ini` (with `--connect`).
|
||
|
||
**Exit codes:** `0` ok · `1` input/CLI error · `2` SVG rendering failed
|
||
(Graphviz).
|
||
|
||
## 4. `tro_flow.py` — derive and draw the TROs
|
||
|
||
Sister program to `material_flow.py` — same `--file`/`--tosvg`/`--doc`
|
||
switches, but instead of the mechanical objects it derives the **TROs**
|
||
(Transfer Route Objects, i.e. control-logic units) from the same graph and
|
||
draws *their* flow. Derivation rules (see
|
||
`doc/500573_Mubea/TRO_Identifikation_500573.md` for the source analysis):
|
||
|
||
| Host of the separator | TRO type |
|
||
|---|---|
|
||
| Gefällestrecke, ≥ 2 lines sharing an in-/outfeed | `PinStore_Auto` (one block per line group) |
|
||
| Gefällestrecke, single line | `1Sep` |
|
||
| Strecke (driven) | `Vario` (one per segment) |
|
||
| Kreisel lane | `1Sep` |
|
||
| — additionally: 2 or 3 outgoing paths | upgraded to `1Sep1Swi` / `1Sep2Swi` |
|
||
|
||
A scanner mounted on a separator does **not** change the type (stays `1Sep`);
|
||
the `1Sep_SSCC` special case can't be recognized from the mechanical layout
|
||
alone and is only raised as a finding. TRO types, FB blocks, sub-components and
|
||
colors all come from `lib/tro_catalog.py` — nothing is hard-coded here.
|
||
|
||
| Switch | Argument | Default | Meaning |
|
||
|---|---|---|---|
|
||
| `--file` | `NAME` | `export.csv` | CSV input file, resolved against `%SKEL_DATA%`, or a full path. |
|
||
| `--tosvg` | — | off | Also render the TRO flow diagram to `.svg`. |
|
||
| `--use-cords` (alias `--use-coords`) | — | off | Place every TRO at the centroid of the components it was built from and render with `neato -n` (fixed positions) instead of `dot`'s computed layout, so the diagram matches the plant layout. Only has an effect together with `--tosvg`; fails if any TRO has no coordinate. |
|
||
| `--doc` | — | off | Also write a Markdown report (`<name>_tro_doc.md`) with the TRO list, connections, findings and the full type catalog. |
|
||
|
||
**Output** (in `%SKEL_RESULTS%`): `<name>_tro_flow.dot` (always, with `pos`
|
||
attributes when `--use-cords` was used), `<name>_tro_flow.svg` (with
|
||
`--tosvg`), `<name>_tro_doc.md` (with `--doc`), `<name>_tro_errors.md` (only
|
||
if TROs are unconnected or of an unknown type).
|
||
|
||
**Exit codes:** `0` ok · `1` input/CLI error · `2` SVG rendering failed
|
||
(Graphviz) · `3` TRO validation errors found (error file written).
|
||
|
||
## 5. `tro_annotate.py` — burn the TROs into a copy of the CAD drawing
|
||
|
||
Takes the CSV export, the original BricsCAD drawing (DXF) and the TROs
|
||
derived the same way as `tro_flow.py`, and writes a **copy** of the drawing
|
||
with:
|
||
|
||
- one colored marker block per TRO, each type with its own shape (circle,
|
||
triangle, square, diamond, …; see `TroSymbol` in `tro_catalog.py`) so types
|
||
are distinguishable even without color;
|
||
- visible attributes `ID`, `TYPE` (and `FB_BLOCK` with `--fb`); hidden
|
||
attributes `ITEMS`, `CONFIDENCE`, `SEPARATORS` for downstream tooling;
|
||
- optional directional flow arrows between TROs (`--flow`) and a legend
|
||
(`--legend`).
|
||
|
||
The original drawing is never modified. Everything this tool adds lives on its
|
||
own `TRO_`-prefixed layers and carries XDATA under the app id
|
||
`SPS_SKEL_TRO`; a re-run removes exactly those tagged objects (not by layer
|
||
name) and rebuilds them, so hand-drawn content on the same layers survives.
|
||
|
||
Once at least one marker of a given type has been annotated, the `dxfmakros`
|
||
BricsCAD menu (`SSG_LIB > TRO`) can insert further markers of that type by
|
||
hand — `TRO_INSERT` reuses the `TRO_SYM_<Typ>` block definition this tool
|
||
created and only asks for a point, an id and (optionally) a different type;
|
||
`TRO_EDIT` changes `ID`/`TYPE` on an existing marker. Neither command draws
|
||
new shapes — a type with no prior annotated instance in the drawing must be
|
||
run through this tool at least once first.
|
||
|
||
CSV and DXF live in different coordinate systems; the offset between them is
|
||
handled by `dxf_registration.py` — derived once, verified on every run, and
|
||
cached per CSV/DXF filename pair in `%SKEL_CFG%\dxf_registration.json` unless
|
||
overridden.
|
||
|
||
| Switch | Argument | Default | Meaning |
|
||
|---|---|---|---|
|
||
| `--file` | `NAME` | `export.csv` | CSV export, resolved against `%SKEL_DATA%`, or a full path. |
|
||
| `--dxf` | `NAME` | — | Original drawing as DXF (path, or name resolved against `%SKEL_DATA%`). Required unless `--emit-lisp` is used alone. DWG is not supported. |
|
||
| `--out` | `NAME` | `<csv-stem>_annotated.dxf` | Output filename in `%SKEL_RESULTS%`. |
|
||
| `--flow` | — | off | Draw directional flow arrows between the TROs. |
|
||
| `--fb` | — | off | Show the Siemens FB block name as a visible attribute on the marker. |
|
||
| `--legend` | — | off | Draw a legend of the color groups. |
|
||
| `--marker-size` | `MM` (float) | `300.0` | Marker radius in mm. |
|
||
| `--text-height` | `MM` (float) | `220.0` | Text height in mm. |
|
||
| `--layer-prefix` | `P` | `TRO_` | Prefix for the layers this tool creates. |
|
||
| `--offset` | `DX,DY` | — | Set the CSV↔DXF offset by hand instead of deriving/loading it. |
|
||
| `--re-register` | — | off | Re-derive the coordinate offset even if a verified one is already cached in `%SKEL_CFG%`. |
|
||
| `--max-residual` | `MM` (float) | `600.0` | Largest residual error of the coordinate registration that is still accepted; above it the run fails (exit 4). |
|
||
| `--check` | — | off | Dry run: only analyze and report, write no output file. |
|
||
| `--emit-lisp` | — | off | Regenerate `cad/tro_types.lsp` (the type list for the `TRO_INSERT`/`TRO_EDIT` dialogs) from `tro_catalog.py` and exit — no `--dxf` needed. Written under `%DXFM_LISP%` if set (next to the `dxfmakros` SSG_LIB modules that provide the dialogs), otherwise under `<SPS_SKEL>/cad`. |
|
||
|
||
**Output** (in `%SKEL_RESULTS%`, unless `--check`): `<name>_annotated.dxf`,
|
||
`<name>_registration.json` (the coordinate transform used, with its proof).
|
||
|
||
**Exit codes:** `0` ok · `1` input/CLI error · `4` coordinate registration
|
||
could not be proven (or exceeds `--max-residual`) — resolve with `--offset` or
|
||
by raising `--max-residual`.
|
||
|
||
## 5a. `tro_extract.py` — read the TROs back out of the drawing into JSON
|
||
|
||
The counter-direction to `tro_annotate.py`:
|
||
|
||
```
|
||
tro_annotate.py CSV + derivation -> markers in the drawing
|
||
tro_extract.py drawing -> JSON
|
||
```
|
||
|
||
It reads **only the drawing**, never the CSV. So what comes back is what is
|
||
actually in the DXF — including anything adjusted by hand in BricsCAD (markers
|
||
moved, type changed via `TRO_EDIT`). That is the point: after a review, the
|
||
drawing is the current state, not the export.
|
||
|
||
It finds the objects `tro_annotate.py` tagged with XDATA (AppID
|
||
`SPS_SKEL_TRO`): the `TRO_SYM_<type>` block references (attributes
|
||
`ID`/`TYPE`/`ITEMS`/`CONFIDENCE`/`SEPARATORS`, plus `FB_BLOCK` when annotated
|
||
with `--fb`), the flow arrows (`"<from>-><to>"`), and the open-point markers
|
||
(`"OFFEN:<name>"`). Each TRO also gets `predecessors`/`successors` computed
|
||
from the arrows.
|
||
|
||
Coordinates appear twice: `positionDxf` is the drawing coordinate,
|
||
`position` the plant coordinate. The conversion uses the registration written
|
||
by `tro_annotate.py` (`<stem>_registration.json`; the `_annotated` suffix and
|
||
the file's own `dxf_file` field are both taken into account when locating it).
|
||
If no registration is found, `position` is **left out rather than guessed**,
|
||
and a warning says so.
|
||
|
||
Switches: `--dxf NAME` (default `export_annotated.dxf`, looked up in
|
||
`%SKEL_RESULTS%`), `--out NAME` (default `<drawing>_tro.json`). Exit codes:
|
||
`0` ok, `1` input error, `2` no TRO markers in the drawing.
|
||
|
||
`fbType` is worth one note: unless the drawing was annotated with `--fb`, the
|
||
FB block is not an attribute in the DXF. It is then looked up from
|
||
`tro_catalog.py` and marked `"fbTypeFrom": "tro_catalog"` — so a consumer can
|
||
tell a value that came out of the drawing from one that was resolved.
|
||
|
||
## 5b. `scl_skeleton.py` — generate the FB_Main skeleton as SCL
|
||
|
||
Last step of the chain:
|
||
|
||
```
|
||
material_flow.py objects -> material flow
|
||
tro_flow.py flow -> TROs
|
||
tro_annotate.py TROs -> markers in the drawing
|
||
tro_extract.py drawing -> JSON
|
||
scl_skeleton.py JSON -> FB_Main.scl
|
||
```
|
||
|
||
Produces a **skeleton, not a running program**: one `REGION` per TRO with the
|
||
instance call and every parameter line in the right order, following the real
|
||
call sites in `doc/TRO_Katalog/scl_templates/*.scl`.
|
||
|
||
**It will not compile as delivered, and that is deliberate.** Every value that
|
||
has to come from the electrical planning is a visible gap marked
|
||
`TODO(E-Planung)` — never an invented value, because a plausible-looking wrong
|
||
sensor address is far worse than an empty one. Grep for that marker and nothing
|
||
is missed.
|
||
|
||
| filled from the layout | left as `TODO(E-Planung)` |
|
||
|---|---|
|
||
| REGION order (material flow), instance list, FB type per TRO | sensor/actuator addresses (`cInBG`, `MB`, `MA`) |
|
||
| number of separator / switch / scanner parameter blocks | separator and switch numbers, `cMainTro` |
|
||
| `nTo1Destinations` when the TRO has exactly one switch | jam areas (`DB_JamArea…`) |
|
||
| destination list per switch exit (graph reachability) | times marked "pruefen" |
|
||
| cross-reference to the layout parts (separator numbers, coordinate) | |
|
||
|
||
Two derivations worth knowing about. **The destination list per switch exit** is
|
||
computed by walking the TRO graph from each exit without going back through the
|
||
switch — that is exactly what `FC_Direction` needs to map destination to
|
||
direction, and it does *not* require the I/O list. And **`nTo1Destinations`** is
|
||
only auto-filled for a single-switch TRO; where several switches share the
|
||
exits, how they divide them is a planning decision, so it stays a gap rather
|
||
than a guess.
|
||
|
||
Switches: `--json NAME` (default `export_tro.json`), `--out NAME` (default
|
||
`<source>_FB_Main.scl`), `--start TRO`. The last one matters while the plant has
|
||
no loading/unloading station: the flow is then a closed loop with no entry, so
|
||
the order has to be broken somewhere arbitrary. The chosen start and the reason
|
||
are recorded in the file header.
|
||
|
||
## 6. `tro_catalog.py` — TRO type catalog (library, no CLI)
|
||
|
||
The single source of truth for everything needed to generate or draw a TRO
|
||
type: short name (as used in `FB_Main`/`connect.ini`), Siemens FB block,
|
||
sub-components and their counts, color group (for Graphviz/Mermaid/CAD) and
|
||
CAD marker shape. **Eleven** types are defined in `TRO_CATALOG`. Ten of them are
|
||
verified against `data/*.scl` where the source is vendored locally, and against
|
||
`TRO_Typen.md`'s mapping table otherwise (see the module docstring,
|
||
`CATALOG_AS_OF`, for provenance per type).
|
||
|
||
> **`2Sep2Swi` is the exception — it is not backed by any SCL.** It was added on
|
||
> 2026-08-12 for project 500573 (Mubea), to model the transfer point at the
|
||
> Kreisel2/Kreisel1 touch as a single control object with 2 separators,
|
||
> 2 scanners and 2 switches. **`FB_ILS_MTRO_2Sep2Swi` does not exist** in this
|
||
> repository or in `doc/TRO_Katalog/scl_templates/`, and has to be created in
|
||
> the ILSLib — otherwise a future SCL generator will emit a call to a missing
|
||
> block. The closest verified type is `2Sep1Swi` (2 separators, 2 scanners,
|
||
> **1** switch), which matches the hardware actually present in that export.
|
||
|
||
Everything is a Pydantic v2 model
|
||
(`TroDefinition`/`TroStyle`), so it validates on every assignment and comes
|
||
with `model_dump()`/`model_validate()` for the planned JSON layout model for
|
||
free. Consumed by `tro_flow.py` and `tro_annotate.py`; not run directly.
|
||
|
||
## 6a. `tro_overrides.py` — manual corrections to the derived TROs (library, no CLI)
|
||
|
||
`tro_flow.py` derives TRO types from the mechanical layout alone. Some things
|
||
are simply not in the CSV export: a switch that the export does not carry, a
|
||
scanner that lifts the type to `1Sep_SSCC`, or a spot that a walkthrough
|
||
identified as its own transfer point. Those findings come from a site review,
|
||
and they must survive the next run — the `.dot`/`.svg`/`.md`/`.ini` outputs are
|
||
all regenerated from scratch every time, so editing them is not an option.
|
||
|
||
They therefore live in **`%SKEL_CFG%\tro_overrides.ini`** (hand-maintained,
|
||
never generated). Section names are prefixed with the CSV file they apply to,
|
||
so several plants — and several exports of the same plant — can coexist:
|
||
|
||
```ini
|
||
[export.csv.tro.SEP0011] ; type correction, keyed on separator 0011
|
||
type = 1Sep1Swi ; a type from lib/tro_catalog.py
|
||
reason = Weiche vor Ort bestaetigt (Review 2026-08-10)
|
||
confidence = bestaetigt ; default when omitted
|
||
|
||
[mubea.csv.openpoint.Qualitaetskontrolle] ; explicitly NOT a TRO yet
|
||
x = -1026.00 ; plant coordinates in mm (CSV system)
|
||
y = 5250.05
|
||
z = 1478.50
|
||
expect = 1Sep1Swi ; type expected here
|
||
belongs_to = TRO09 ; TRO that owns the part today
|
||
note = ...
|
||
```
|
||
|
||
A fourth kind folds several derived TROs into one control object:
|
||
|
||
```ini
|
||
[export.csv.merge.Beruehrpunkt_K2_K1_gesamt]
|
||
type = 2Sep2Swi
|
||
separators = 0005, 0057 ; at least two, addressed by separator
|
||
host = 0002-R ; primary flow node; the others are kept
|
||
label = ...
|
||
reason = ...
|
||
```
|
||
|
||
A third kind creates a TRO the derivation missed entirely:
|
||
|
||
```ini
|
||
[export.csv.split.Beruehrpunkt_K2_K1] ; pull a separator out of its TRO
|
||
type = 1Sep1Swi
|
||
separators = 0057 ; comma-separated
|
||
scanners = ; optional, follow the separators
|
||
host = 0001-L ; flow node the new TRO sits on
|
||
label = ...
|
||
reason = ...
|
||
```
|
||
|
||
Three kinds of entry, deliberately different in weight:
|
||
|
||
- **`tro`** — changes the type of an already-derived TRO. Applied after the
|
||
TRO numbers are assigned (the sections address them by ID) and before the FB
|
||
block and component list are pulled from the catalog, so the type change
|
||
drags both along. A manually set type also suppresses the automatic
|
||
"check whether `1Sep_SSCC` is needed" hint for that TRO.
|
||
- **`split`** — creates a new TRO from separators taken out of an existing one.
|
||
For transfer points the derivation misses because the separator's
|
||
`Zuordnung` points at a conveyor (Strecke/Gefaellestrecke) instead of the
|
||
Kreisel lane it actually switches on. Applied *before* the numbers are
|
||
assigned, so the new TROs slot into the normal ordering — which means
|
||
**adding a split renumbers everything after it**. That is exactly why type
|
||
corrections should be keyed `SEP<number>`, not `TRO<number>`.
|
||
- **`merge`** — folds several TROs into one. Runs *after* the splits, so a
|
||
freshly split-out TRO can be part of a merge. The first affected TRO survives
|
||
and absorbs the others' separators, scanners and hosts; `host` picks which
|
||
flow node is primary, the rest stay attached so the linking still sees both
|
||
sides of a transition. Like splits, this renumbers.
|
||
- **`openpoint`** — a spot reported in a review that is **not** turned into a
|
||
TRO. The structure stays untouched; the point is only marked, in the doc
|
||
(section 6) and in the annotated drawing on layer `TRO_OPENPOINT` as a red
|
||
crossed circle with plain-text label. This keeps "derived" and "asserted"
|
||
distinguishable until the spot has been checked in BricsCAD.
|
||
|
||
Consumers: `tro_flow.py` (type corrections + doc section 6), `tro_annotate.py`
|
||
(corrected marker types + open-point markers), `material_flow.py` (writes the
|
||
`[<plant>.manual]` section into `connect.ini`). Unknown TRO IDs and types are
|
||
not applied silently — they are reported as warnings in the console output and
|
||
in doc section 6.3, which catches the common trap: **TRO IDs are running
|
||
numbers of one run and are only valid for the CSV named in the section.** Swap
|
||
the data basis and the numbers shift.
|
||
|
||
## 7. `dxf_registration.py` — CSV↔DXF coordinate registration (library, no CLI)
|
||
|
||
Derives the rigid transform (rotation + offset, no scaling) between the CSV
|
||
export's and the DXF drawing's coordinate systems, by voting on the offset
|
||
between same-role anchor points (`Separator` and `Scanner` blocks, see
|
||
`ANCHOR_BLOCKS`), refining the best candidates via a Procrustes fit, and
|
||
picking the candidate with the most exact matches. Raises `RegistrationError`
|
||
if fewer than `MIN_INLIERS` (5) exact matches are found for any candidate.
|
||
Persists/loads the verified transform per CSV/DXF filename pair under
|
||
`%SKEL_CFG%\dxf_registration.json`. Used exclusively by `tro_annotate.py`; not
|
||
run directly.
|
||
|
||
`ANCHOR_BLOCKS` holds **name patterns** (fnmatch, case-insensitive), not fixed
|
||
names, because two naming worlds occur in this project:
|
||
|
||
| Drawing | Separator blocks | Scanner blocks |
|
||
|---|---|---|
|
||
| original customer drawing (`500573_60_1.dxf`) | `S-SP`, `S-LP` | `SCAN` |
|
||
| macro-generated (`Mubea.dxf`, from dxfmakros) | `Separator_SP_2D`/`_3D` | `Scanner_2D`/`_3D` |
|
||
|
||
`Staustrecke_Separator_SP_*` is deliberately **not** an anchor: it is a
|
||
conveyor module with a built-in separator, so its insertion point is the module
|
||
origin rather than the separator, which would feed a second cluster — offset by
|
||
exactly that distance — into the vote.
|
||
|
||
Note that the two roles can carry slightly different systematic offsets (in
|
||
`Mubea.dxf`: separators land on +100/0, scanners on +400/0), so a mixed fit
|
||
shows a larger `residual_max` than either role alone. That is expected; the
|
||
mean residual is the number to watch, and only pairs within `FIT_TOL` (50 mm)
|
||
enter the Procrustes fit.
|
||
|
||
## 8. `gen_tro_graphs.py` — documentation image generator (doc tooling, not part of the generator)
|
||
|
||
Lives under `doc/TRO_Katalog/tro_graphs/`, not `lib/` — it produces the
|
||
pictures embedded in `TRO_Katalog.md`, not generator output. Two families:
|
||
|
||
1. one schematic SVG per TRO type showing the block with its *possible*
|
||
connection edges (`normal`/`dir2`/`dir3`/`bypass`/`finger`/`plc`/`extern`,
|
||
each with its own color and line style), plus a shared `edge_legend.svg`;
|
||
2. one Graphviz topology graph per controller (`uh01`..`uh05`), built from
|
||
`doc/TRO_Katalog/connect.ini`, rendered to SVG.
|
||
|
||
No command-line switches — run it directly and re-run after editing
|
||
`connect.ini`:
|
||
|
||
```
|
||
py doc/TRO_Katalog/tro_graphs/gen_tro_graphs.py
|
||
```
|
||
|
||
Needs Graphviz `dot` on `PATH` (or at the default Windows install location);
|
||
without it the `.dot` files are still written, just not rendered to `.svg`.
|