hw_import + merge_hw_layout: Hardwareplanung einlesen und mit dem Layout zusammenfuehren
Stufen 2 und 3 aus doc/Konzept_Layout_Hardware_SCL.md: - lib/hw_import.py: TIA-Import (*_TIA.xlsx, ohne openpyxl gelesen) + Positionsdatei (*_positionsconv.json) -> <projekt>_hw.json: je BMK Adresse, Index-Konstante, VERW, Station, Lage, Foerderer (K0102, #106 -> K0106); Antriebe MA/FC/GF; Gegenpruefung beider Quellen. - lib/merge_hw_layout.py: <csv>_tro.json + <projekt>_hw.json -> <csv>_anlage.json (model.AnlageDocument): bindings je TRO-ID + Slot + Rolle mit Konstante und Adresse, Foerderer per Mehrheit (Mubea: K0102 = Kreisel3, K0104 = Kreisel2, K0105 = Kreisel1), Handkorrekturen cfg/hw_overrides.ini. Zuordnungslogik aus e_planning.py hierher verschoben; e_planning re-exportiert sie, Ergebnis unveraendert. - TRO-JSON traegt optional `parts` (Lage/Huellbox, tro_flow --json), PinStore bekommt Slots line<n>. - Gespeicherter Lagebezug mit SHA-256 der Positionsdatei; Unterordner in Eingabenamen relativ zu SKEL_DATA. - Doku: Python_Scripts.md 5d/5e, Konzept, E-Planung_Bruecke (Abgleich mubea_layout.dxf / CSV / E-Planung). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
c4122b4497
commit
df24cf5adb
+161
-12
@@ -1,16 +1,20 @@
|
||||
# Python Scripts — CLI Tooling Overview
|
||||
|
||||
**As of:** 2026-10-10
|
||||
**As of:** 2026-10-15
|
||||
|
||||
`lib/` currently holds eleven Python modules (six of them runnable CLI tools,
|
||||
five 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, a TRO-JSON (shared model `lib/model.py`), an `FB_Main` SCL
|
||||
skeleton generated from that JSON, and an annotated copy of the BricsCAD
|
||||
drawing for visual checking. `e_planning.py` bridges to the electrical
|
||||
planning: it assigns the sensors/valves of the E-planning drawing to the
|
||||
layout parts, so the skeleton can carry their real names.
|
||||
`lib/` currently holds sixteen Python modules (eight of them runnable CLI
|
||||
tools, eight 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, a TRO-JSON (shared model `lib/model.py`), an
|
||||
`FB_Main` SCL skeleton generated from that JSON, and an annotated copy of the
|
||||
BricsCAD drawing for visual checking. `e_planning.py` bridges to the
|
||||
electrical planning: it assigns the sensors/valves of the E-planning drawing
|
||||
to the layout parts, so the skeleton can carry their real names.
|
||||
`hw_import.py` and `merge_hw_layout.py` are the new path for that bridge
|
||||
(`doc/Konzept_Layout_Hardware_SCL.md`, stages 2 and 3): TIA import +
|
||||
positions file → `<project>_hw.json`, merged with the TRO-JSON into
|
||||
`<csv>_anlage.json` (§5d, §5e).
|
||||
|
||||
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
|
||||
@@ -42,6 +46,12 @@ tro_flow.py ──► <name>_tro_flow.dot / .svg, _tro_doc.md, _tro_error
|
||||
│ (assigns BG/MB of the E-planning drawing to the layout
|
||||
│ parts by position + numbering rule; see §5c)
|
||||
│
|
||||
│ NEW PATH (stages 2/3, scl_skeleton does not read it yet):
|
||||
│ <name>_tro.json ──┐
|
||||
│ ├─► merge_hw_layout.py ──► <name>_anlage.json + .md (§5e)
|
||||
│ hw_import.py ─────┘ (TRO slots <- BMK, conveyors K0102 -> Kreisel3)
|
||||
│ ▲ *_TIA.xlsx + *_positionsconv.json (§5d)
|
||||
│
|
||||
└── same derivation ──► tro_annotate.py ──► <name>_annotated.dxf (VIEWER ONLY)
|
||||
(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)
|
||||
@@ -655,6 +665,141 @@ Tests: `tests/test_e_planning.py` (reading, rules, numbering vs. distance on a
|
||||
storage-line grid, target distance, uniqueness, registration, drives, the path
|
||||
into `scl_context` and `scl_skeleton --eplan`).
|
||||
|
||||
> **Since 2026-10-15** the E model (`EDevice`, `load_positionsdraw`,
|
||||
> `pair_drives`) lives in `hw_import.py` and the assignment logic (rules,
|
||||
> spatial, numbering rule, registration) in `merge_hw_layout.py`; `e_planning.py`
|
||||
> imports and re-exports them and keeps only `EplanDocument`, the CSV path,
|
||||
> report and CLI. Results are unchanged (Mubea: 43 names in the SCL). It stays
|
||||
> until `scl_skeleton` reads `<csv>_anlage.json`. The stored registration now
|
||||
> carries a SHA-256 of the positions file: a new file with the same name is
|
||||
> registered anew instead of silently reusing the old offset. `--eplan` (and
|
||||
> every `--file`-style input) also resolves a relative sub-folder against
|
||||
> `%SKEL_DATA%` (`Mubea/Mubea_positionsdraw.json`).
|
||||
|
||||
## 5d. `hw_import.py` — read the hardware planning (TIA import + positions file)
|
||||
|
||||
Stage 2 of [`Konzept_Layout_Hardware_SCL.md`](Konzept_Layout_Hardware_SCL.md).
|
||||
Two sources are enough (finding in [`E-Planung_Bruecke.md`](E-Planung_Bruecke.md),
|
||||
"Nachprüfung 2026-10-15"); the EA list and the WSCAD import add nothing once a
|
||||
positions file exists.
|
||||
|
||||
```
|
||||
*_TIA.xlsx (PLC Tags + Constants) ─┐
|
||||
├─► hw_import.py ──► <project>_hw.json (HardwareDocument, sps_skel-hw-v1)
|
||||
*_positionsconv.json (optional) ────┘ ──► <project>_hw.md (report: devices, drives, checks)
|
||||
```
|
||||
|
||||
- **TIA import**: sheets are recognised by their header row, not their name
|
||||
(English and German TIA exports): a sheet with `Logical Address` /
|
||||
`Logische Adresse` holds tags, one with `Value` / `Wert` constants. Read with
|
||||
`zipfile` + XML — no openpyxl dependency.
|
||||
- **Per BMK one `HwDevice`** (an `EDevice` plus): `direction` (`in`/`out` from
|
||||
`%E`/`%I` vs. `%A`/`%Q`), `address`, `dataType`, `comment`, the index constant
|
||||
`constant` (`cInBG0532` / `cOutMB0533` — what the SCL references) with its
|
||||
`constantValue`, `conveyor` + `conveyorKind`, and `sources` (`tia`,
|
||||
`positions`).
|
||||
- **Conveyor from the plain text**: `KREISEL-K0102`, `KR-K0104` → `K0102`,
|
||||
`K0104` (kind `Kreisel`); `CONVEYOR-#103` and `CONVEYOR-K0103` are the same
|
||||
conveyor and both become `K0103` (kind `Strecke`) — the four-digit conveyor
|
||||
number of the SCL naming scheme (`DB_JamArea0103`, `fbCarousel0102`).
|
||||
- **Drives**: motor `MA` ↔ motor protection `FC` (same number), conveyor and
|
||||
kind from their texts, encoder `GF…` of the same conveyor.
|
||||
- **Cross-check** (`checks`, also printed as `?` lines): BMK only in the
|
||||
positions file / field device (`BG`/`MB`/`MA`/`BX`) only in TIA, TIA comment ≠
|
||||
`BEZEICHNUNG`, signal without index constant, index constant without signal,
|
||||
duplicate addresses, duplicate constant values, field device without
|
||||
controller in `KENNZEICHNUNG`. The positions file's own `warnings` are passed
|
||||
through.
|
||||
|
||||
Switches: `--tia NAME` (required), `--positions NAME` (optional; without it no
|
||||
positions), `--project NAME` (default from the positions file name:
|
||||
`Mubea_positionsconv.json` → `Mubea`). Names resolve against `%SKEL_DATA%`,
|
||||
also with a sub-folder. Exit `0` (findings are in the report), `1` on input or
|
||||
read errors.
|
||||
|
||||
Result on Mubea (`mubea-1_TIA.xlsx` + `Mubea_positionsconv.json`): 238 devices,
|
||||
237 in both sources, 237 index constants, 158 field devices with position,
|
||||
11 drives all with motor protection and conveyor (encoder `GF20000` at K0102);
|
||||
checks: `QA7000` only in the positions file, `BG0514` without controller.
|
||||
|
||||
Tests: `tests/test_hw_import.py` (xlsx reader with shared/inline strings,
|
||||
header-based sheet detection, conveyor texts, merge per BMK, drives, every
|
||||
cross-check, TIA-only run, JSON round trip, CLI with a sub-folder in
|
||||
`%SKEL_DATA%`). The test builds its own small TIA xlsx.
|
||||
|
||||
## 5e. `merge_hw_layout.py` — merge layout and hardware planning into the plant JSON
|
||||
|
||||
Stage 3 of the concept. Reads **no CSV**: the TRO-JSON carries the TRO slots
|
||||
and, since 2026-10-15, the positions and bounding boxes of the layout parts
|
||||
(`parts`, written by `tro_flow --json`).
|
||||
|
||||
```
|
||||
<name>_tro.json + <project>_hw.json
|
||||
└──────┬──────┘
|
||||
merge_hw_layout.py rules %SKEL_CFG%\e_planning.ini, corrections %SKEL_CFG%\hw_overrides.ini
|
||||
│
|
||||
├─► <name>_anlage.json (AnlageDocument, sps_skel-anlage-v1:
|
||||
│ layout unchanged + bindings[] + conveyors[])
|
||||
└─► <name>_anlage.md (report)
|
||||
```
|
||||
|
||||
Steps:
|
||||
|
||||
1. **Registration** E drawing → layout as in §5c (`IN SEP` sensors against the
|
||||
layout separators), stored with the SHA-256 of the positions file. Warning
|
||||
when fewer than `registration_min_share` of the separators find a partner —
|
||||
a sign that layout and E drawing are different planning states.
|
||||
2. **Rules** from `e_planning.ini`, unchanged logic (spatial with maximum /
|
||||
target distance, numbering rule `MB = BG + 1`).
|
||||
3. **Slots**: each assignment goes to the slot role of the TRO that holds the
|
||||
part — key **TRO-ID + slot + role**. A `PinStore_Auto` now has a slot per
|
||||
storage line (`line1` … `lineN`, same split as `scl_context._store`);
|
||||
`separator1` and `line1` can be the same part and both get the binding.
|
||||
Parts without a slot (outfeed elements, switches — `tro_flow` does not keep
|
||||
them on the TRO yet) stay visible as `ohne_slot`.
|
||||
4. **Conveyors**: the E-planning names (`K0102`) and the layout names
|
||||
(`Kreisel3`) do not match, so every field device whose text names a conveyor
|
||||
votes for the nearest layout conveyor (distance to its bounding box, at most
|
||||
`conveyor_max_mm`). Only conveyors of the named kind vote — the bounding
|
||||
boxes overlap (storage lines reach the Kreisel). Assigned at
|
||||
`conveyor_min_votes` votes and `conveyor_min_share` share; one E conveyor per
|
||||
layout conveyor. This gives motor, motor protection and encoder per layout
|
||||
conveyor (`AnlageDocument.conveyor_for("0010-R")` → the TROs on that
|
||||
Kreisel, i.e. `xInCarouselRun`) and fills `drive.motor` /
|
||||
`drive.motorProtection` of Vario TROs.
|
||||
5. **Corrections** from `hw_overrides.ini` (last):
|
||||
`[<csv>.bindings] TRO01.separator1.sensor = BG0532` (or `= -` to clear) and
|
||||
`[<csv>.conveyors] K0102 = 0010`.
|
||||
6. **Status** per binding: `zugeordnet`; `offen` (required role without BMK →
|
||||
later `TODO(E-Planung)`; optional `?` roles are not listed); `widerspruch`
|
||||
(BMK not in the TIA import, unknown, or given to two parts); `ohne_slot`.
|
||||
Every binding carries `constant` and `address` from the TIA import.
|
||||
|
||||
Switches: `--tro NAME`, `--hw NAME` (both required, resolved against
|
||||
`%SKEL_RESULTS%`), `--rules FILE`, `--overrides FILE`, `--re-register`. Exit
|
||||
`0` also when not everything was assigned; `1` on input errors, an old
|
||||
TRO-JSON without `parts`, or an unprovable registration. Parameters:
|
||||
`cfg/parameter.ini [merge_hw_layout]`.
|
||||
|
||||
Result on Mubea (`mubea.csv` → `mubea_tro.json`, `Mubea_hw.json`): registration
|
||||
dx −151.4 / dy −696.6 mm (23/38, warning); 45 slot roles assigned (all 20
|
||||
storage-line sensors, 19 stoppers, separator sensor/stopper at TRO03/04/05/18),
|
||||
58 open, 0 contradictions; 41 assignments at outfeed elements/switch without a
|
||||
slot; conveyors **K0102 = Kreisel3 (21/23 votes), K0104 = Kreisel2 (8/8),
|
||||
K0105 = Kreisel1 (2/3)**, each with motor and motor protection. The other
|
||||
conveyors (Aufgabestation #100/#101, Vario #106–#109) are outside the CSV or
|
||||
have a single vote and stay open.
|
||||
|
||||
**Not yet:** `scl_skeleton` still reads `<csv>_eplan.json` (`--eplan`);
|
||||
switching it to `<csv>_anlage.json` is stage 4 of the concept, and only then
|
||||
`e_planning.py` becomes the thin wrapper and is removed.
|
||||
|
||||
Tests: `tests/test_merge_hw_layout.py` (sensor spatial / stopper by number,
|
||||
contradiction, open roles, part without slot, conveyor majority with
|
||||
overlapping boxes, Vario drive, too few votes, corrections incl. moving a BMK
|
||||
and clearing a conveyor, storage-line slots, missing `parts`, JSON round trip,
|
||||
fingerprint of the stored registration, CLI end to end with a real offset).
|
||||
|
||||
## 6. `tro_catalog.py` — TRO type catalog (library, no CLI)
|
||||
|
||||
The single source of truth for everything needed to generate or draw a TRO
|
||||
@@ -701,12 +846,16 @@ tro_flow.py (writes) · scl_skeleton.py (reads) · tro_annotate.py (draws)
|
||||
|---|---|
|
||||
| `TroBase` | Core of every TRO: `id`, `type`, `confidence`, `separators`, `predecessors`, `successors`, `position` (`{x,y,z}` in JSON). `definition()`, `fb_block` (JSON `fbType`) and `items` are **read from the catalog** on every access, never stored — so they follow a type change and cannot go stale. |
|
||||
| `DerivedTro` | `TroBase` + what `tro_flow.py` knows from the derivation: `label`, `hosts`, `scanners`, `reason`, `colocated`, `positionFrom`, `positionPoints`, `slots`. Also used for the system boundaries `SYSn` (no slots). |
|
||||
| `Slot` | A place on a TRO where hardware belongs: `slot` (`separator1`, `switch1`, `scanner1`, `drive` — the stable key together with the TRO-ID), `layoutId` (TeileId in the current export, for tracing only; empty for switches, whose element `tro_flow` does not keep yet), `roles` the hardware planning must fill (`sensor`, `stopper`, `jamSensor?` …; `?` = optional). Built by `derive_slots()` from the catalog type. |
|
||||
| `Slot` | A place on a TRO where hardware belongs: `slot` (`separator1`, `switch1`, `scanner1`, `drive`, `line<n>` for the storage lines of a store — the stable key together with the TRO-ID), `layoutId` (TeileId in the current export, for tracing only; empty for switches, whose element `tro_flow` does not keep yet), `roles` the hardware planning must fill (`sensor`, `stopper`, `jamSensor?` …; `?` = optional). Built by `derive_slots()` from the catalog type. |
|
||||
| `JamArea` | A jam area = the queue in front of a separator, the entry of exactly one TRO: `id` (`J<Separator>`, e.g. `J0042`), `tro`, `entry` (1, 2 for 2Sep types), `separator`, `host`, `predecessors` (all TROs/stations feeding it — merges have several), `note`, `position`. Built by `derive_jam_areas()`; the SCL name `"DB_JamArea<NNNN>".stJam<NNNN>_<k>` needs the conveyor number from the hardware planning and is not stored. |
|
||||
| `Connection` | Directed edge `from` → `to` with its `kind` (transfer / umlauf / weiche / unbestimmt / normal / bahn). |
|
||||
| `TroCandidate` | A spot reported in a review where a TRO is **expected** but not derived (formerly `OpenPoint`): `name`, `type` (the expected catalog type), `position`, `belongsTo` (the TRO that holds the part today), `note`. Not a TRO — no separators, no connections, no SCL. Built by `tro_overrides.py` from `[<csv>.openpoint.<Name>]`; empty in the TRO-JSON for now. |
|
||||
| `Analysis` | Working result of `tro_flow.analyse_tros` (moved here from `tro_flow.py`). |
|
||||
| `TroDocument` | The TRO-JSON: `schema` (`sps_skel-tro-v4`), `source`, `created`, `note`, `tros`, `boundaries`, `connections`, `candidates`, `jamAreas`, `findings`. `from_analysis()` (derives slots and jam areas), `to_json()`, `from_json()`, `jam_areas_or_derived()` (a v3 file without `jamAreas` still renders). |
|
||||
| `TroDocument` | The TRO-JSON: `schema` (`sps_skel-tro-v4`), `source`, `created`, `note`, `tros`, `boundaries`, `connections`, `candidates`, `jamAreas`, `parts`, `findings`. `from_analysis()` (derives slots and jam areas, takes `parts`), `to_json()`, `from_json()`, `jam_areas_or_derived()` (a v3 file without `jamAreas` still renders), `part()` (layout part by TeileId, lane suffix ignored). |
|
||||
| `LayoutPart` | A layout part with position (`layoutId`, `kind`, `name`, `position`, `bbox`) — the target of the spatial assignment in `merge_hw_layout.py`, so that it never reads the CSV. `distance_to_box()`. Optional in v4; a file without `parts` still reads. |
|
||||
| `Binding` | One assignment hardware → slot role: `tro`, `slot`, `role`, `layoutId`, `bmk`, `constant`, `address`, `source`, `rule` (`raeumlich`/`nummer`/`mehrheit`/`hand`), `distance`, `runnerUp`, `status` (`zugeordnet`/`offen`/`widerspruch`/`ohne_slot`), `note`. |
|
||||
| `ConveyorBinding` | A conveyor of the E-planning (`K0102`) at a layout conveyor: `eplanName`, `layoutId`, `layoutName`, `votes`/`total`, `motor`, `protection`, `encoder`, `rule`, `status`, `note`. |
|
||||
| `AnlageDocument` | The plant JSON `<csv>_anlage.json` (`sps_skel-anlage-v1`): `layout` (the TRO-JSON unchanged), `registration`, `bindings`, `conveyors`, `unassigned`, `warnings`. `binding(tro, slot, role)`, `conveyor_for(layout_id)`. Written by `merge_hw_layout.py`; the future single input of `scl_skeleton`. |
|
||||
|
||||
`TroBase` and `TroCandidate` share the `position` handling (JSON `{x,y,z}`,
|
||||
`x`/`y`/`z`, `describe_position()`) through the private mixin `_Positioned`.
|
||||
|
||||
Reference in New Issue
Block a user