latest changes
This commit is contained in:
+230
-9
@@ -66,11 +66,14 @@ Each CLI tool is invoked through its `bin/<name>.bat` (Windows) / `bin/<name>.sh
|
||||
3. runs the module with `py` (falling back to `python`).
|
||||
|
||||
```bat
|
||||
bin\material_flow.bat --file mubea.csv --tosvg
|
||||
bin\tro_flow.bat --file mubea.csv --tosvg --doc
|
||||
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 all three tools:
|
||||
|
||||
- `--file` / `--dxf` accept either a bare filename — resolved against
|
||||
@@ -97,6 +100,27 @@ Reads the CSV export and builds a directed graph of the mechanical objects:
|
||||
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.
|
||||
|
||||
Flow direction between conveyor segments is derived from height change
|
||||
(`Hoehe_Von_mm`/`Hoehe_Bis_mm`) or, failing that, from `Antriebfahrtrichtung`
|
||||
@@ -109,11 +133,13 @@ attached.
|
||||
|---|---|---|---|
|
||||
| `--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),
|
||||
`<name>_material_flow.svg` (with `--tosvg`), `<name>_material_flow.md`
|
||||
(with `--doc`).
|
||||
**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).
|
||||
@@ -210,19 +236,195 @@ overridden.
|
||||
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. Ten types are defined in `TRO_CATALOG`, 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). Everything is a Pydantic v2 model
|
||||
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
|
||||
@@ -235,6 +437,25 @@ 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
|
||||
|
||||
Reference in New Issue
Block a user