hw_import --dxf: gemeinsame Zeichnung (Layout + E-Planung) als Hardwarequelle
hw_import liest mit --dxf statt der Positionsdatei eine DXF, in der Layout
und E-Planung zusammen liegen (Dematic): jeder Block mit Attribut IO ist ein
E-Bauteil (IO/VERW/BEZEICHNUNG/KENNZEICHNUNG, Einfuegepunkt), schon in
Layout-Koordinaten (HardwareDocument.layoutCoordinates). --conveyor-from-motor
nimmt den Foerderer aus der Motornummer, wenn kein K0102/#106 dasteht
("Motor KR-MA0055" -> K0055); "ILS-VARIO" zaehlt als Strecke.
merge_hw_layout: bei layoutCoordinates kein Lagebezug (Identitaet, nichts
abgelegt); --rules findet einen blossen Namen in SKEL_CFG. Neue Regeldatei
cfg/e_planning_dematic.ini (Sensor/Stopper gleiche Nummer, Weichenventil =
Nummer des Stausensors, Abstaende an dematic_merge..dxf gemessen).
Fehler behoben: zwei Antriebs-TROs auf einem Layout-Foerderer (Dematic VF_6,
TRO04/TRO06) bekamen beide denselben Vario-Motor. Jetzt Widerspruch mit
Vorschlag; scl_context faellt dann nicht auf den Motor des Foerderers zurueck.
Dematic: 115 Namen im FB_Main (alle im TIA-Import), offene Stellen 730 -> 615.
cfg/dematic_merge_tro_ids.ini: TRO-IDs der Dematic-Anlage.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -37,7 +37,8 @@ Template" below). What exists today is:
|
|||||||
- `cfg/dxf_registration.json` — persisted, verified CSV↔DXF coordinate transform;
|
- `cfg/dxf_registration.json` — persisted, verified CSV↔DXF coordinate transform;
|
||||||
`cfg/tro_overrides.ini` — hand-maintained corrections to the derived TROs (see `tro_overrides.py`);
|
`cfg/tro_overrides.ini` — hand-maintained corrections to the derived TROs (see `tro_overrides.py`);
|
||||||
`cfg/e_planning.ini` — project-specific rules for assigning E-planning symbols to layout parts
|
`cfg/e_planning.ini` — project-specific rules for assigning E-planning symbols to layout parts
|
||||||
(read by `merge_hw_layout.py`, also for `e_planning.py`);
|
(read by `merge_hw_layout.py`, also for `e_planning.py`); `cfg/e_planning_dematic.ini` — the
|
||||||
|
same for Dematic (`merge_hw_layout --rules e_planning_dematic.ini`);
|
||||||
`cfg/hw_overrides.ini` — hand corrections of `merge_hw_layout.py` (bindings per TRO slot role, conveyors);
|
`cfg/hw_overrides.ini` — hand corrections of `merge_hw_layout.py` (bindings per TRO slot role, conveyors);
|
||||||
`cfg/parameter.ini` — tolerances, thresholds, scales and drawing sizes of the `lib/` modules
|
`cfg/parameter.ini` — tolerances, thresholds, scales and drawing sizes of the `lib/` modules
|
||||||
(read by `lib/parameter.py`; no numeric settings in the code, see `doc/Python_Scripts.md` §7a);
|
(read by `lib/parameter.py`; no numeric settings in the code, see `doc/Python_Scripts.md` §7a);
|
||||||
@@ -221,7 +222,11 @@ byte — rewrite them (`SKEL_UPDATE_GOLDEN=1`) only for a reviewed, intended cha
|
|||||||
`*_positionsconv.json` → `<projekt>_hw.json` (`HardwareDocument`): per BMK address,
|
`*_positionsconv.json` → `<projekt>_hw.json` (`HardwareDocument`): per BMK address,
|
||||||
index constant `cIn…`/`cOut…`, VERW, station, position, conveyor (`K0102`; `#106` →
|
index constant `cIn…`/`cOut…`, VERW, station, position, conveyor (`K0102`; `#106` →
|
||||||
`K0106`); drives MA↔FC (+ encoder GF); cross-check of both sources. EA list and WSCAD
|
`K0106`); drives MA↔FC (+ encoder GF); cross-check of both sources. EA list and WSCAD
|
||||||
are not needed. See `doc/Python_Scripts.md` §5d.
|
are not needed. Instead of the positions file `--dxf` reads a **merged drawing** (layout +
|
||||||
|
E planning in one DXF, Dematic): every block with an `IO` attribute is a device, already
|
||||||
|
in layout coordinates (`layoutCoordinates`, no registration in the merge);
|
||||||
|
`--conveyor-from-motor` takes the conveyor from the motor number (`MA0055` → `K0055`).
|
||||||
|
See `doc/Python_Scripts.md` §5d.
|
||||||
7. **`lib/merge_hw_layout.py`** (implemented, stage 3) — `<csv>_tro.json` + `<projekt>_hw.json`
|
7. **`lib/merge_hw_layout.py`** (implemented, stage 3) — `<csv>_tro.json` + `<projekt>_hw.json`
|
||||||
→ `<csv>_anlage.json` (`model.AnlageDocument`: layout unchanged + `bindings[]` keyed by
|
→ `<csv>_anlage.json` (`model.AnlageDocument`: layout unchanged + `bindings[]` keyed by
|
||||||
TRO-ID + slot + role, `conveyors[]`). Reads no CSV: the TRO-JSON carries `parts`
|
TRO-ID + slot + role, `conveyors[]`). Reads no CSV: the TRO-JSON carries `parts`
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
; ============================================================
|
||||||
|
; TRO-IDs zu dematic_merge.csv - erzeugt von lib/tro_flow.py am 2026-10-21
|
||||||
|
;
|
||||||
|
; Je TRO: ID = x, y (mm) seines Anker-Separators im CSV-Koordinatensystem.
|
||||||
|
; Die IDs duerfen umbenannt werden (eindeutig, gueltiger SCL-Bezeichner);
|
||||||
|
; tro_flow findet jeden TRO ueber die Lage wieder. Neue TROs werden
|
||||||
|
; unten angehaengt, weggefallene bleiben stehen und werden gemeldet.
|
||||||
|
; Alles neu nummerieren: tro_flow --renumber (ueberschreibt diese Datei).
|
||||||
|
; Siehe doc/Konzept_Layout_Hardware_SCL.md, Abschnitt TRO-IDs.
|
||||||
|
; ============================================================
|
||||||
|
|
||||||
|
[tro]
|
||||||
|
TRO01 = 34716.5, 3406.7 ; Vario | VF_4 | Sep 0074
|
||||||
|
TRO02 = 20161.7, 3406.7 ; 1Sep | VF_4 | Sep 0075
|
||||||
|
TRO03 = 42164.0, 4329.5 ; 1Sep2Swi | Separator 0048 an Ausschleuselementen 0073, 0081 (Strecken 0008, 0010) | Sep 0048
|
||||||
|
TRO04 = 40437.8, 2448.3 ; Vario | VF_6 | Sep 0082
|
||||||
|
TRO05 = 33048.6, 2448.3 ; 1Sep | VF_6 | Sep 0083
|
||||||
|
TRO06 = 26860.8, 2448.3 ; Vario | VF_6 | Sep 0084
|
||||||
|
TRO07 = 20134.0, 2448.3 ; 1Sep | VF_6 | Sep 0085
|
||||||
|
TRO08 = 20161.9, 6635.8 ; Vario | VF_5 | Sep 0078
|
||||||
|
TRO09 = 20161.9, 11560.3 ; 1Sep | VF_5 | Sep 0079
|
||||||
|
TRO10 = 19604.0, 4982.3 ; 1Sep1Swi | Separator 0043 an Ausschleuselement 0077 (Strecke 0009, Anfang) | Sep 0043
|
||||||
|
TRO11 = 3098.7, 12119.7 ; 1Sep | Separator 0038 auf Kreisel2 Bahn L | Sep 0038
|
||||||
|
TRO12 = 34990.4, 12919.7 ; 1Sep | Separator 0046 auf Kreisel2 Bahn R | Sep 0046
|
||||||
|
TRO13 = 5034.1, 12919.7 ; PinStore_Auto | Speicher (10 Schwerkraftlinien) | Sep 0040, 0088, 0091, 0094, 0097, 0100, 0103, 0106, 0109, 0112, 0115
|
||||||
|
TRO14 = 35122.8, 25278.8 ; 1Sep | Separator 0047 auf Kreisel3 Bahn R | Sep 0047
|
||||||
|
TRO15 = 2703.7, 21969.7 ; Vario | VF_1 | Sep 0062
|
||||||
|
TRO16 = 2703.7, 13476.7 ; 1Sep | VF_1 | Sep 0063
|
||||||
|
TRO17 = 3542.3, 24478.8 ; 1Sep1Swi | Separator 0039 an Ausschleuselement 0061 (Strecke 0005, Anfang) | Sep 0039
|
||||||
|
TRO18 = 14674.7, 21969.7 ; Vario | VF_2 | Sep 0066
|
||||||
|
TRO19 = 14674.7, 13476.7 ; 1Sep | VF_2 | Sep 0067
|
||||||
|
TRO20 = 15472.4, 24478.8 ; 1Sep1Swi | Separator 0041 an Ausschleuselement 0065 (Strecke 0006, Anfang) | Sep 0041
|
||||||
|
TRO21 = 23232.7, 21969.7 ; Vario | VF_3 | Sep 0070
|
||||||
|
TRO22 = 23232.7, 13476.7 ; 1Sep | VF_3 | Sep 0071
|
||||||
|
TRO23 = 24062.0, 24478.8 ; 1Sep1Swi | Separator 0044 an Ausschleuselement 0069 (Strecke 0007, Anfang) | Sep 0044
|
||||||
|
TRO24 = 15934.1, 12919.8 ; PinStore_Auto | Speicher (7 Schwerkraftlinien) | Sep 0042, 0118, 0121, 0124, 0127, 0130, 0133, 0136
|
||||||
|
TRO25 = 24734.2, 12919.8 ; PinStore_Auto | Speicher (10 Schwerkraftlinien) | Sep 0045, 0139, 0142, 0145, 0148, 0151, 0154, 0157, 0160, 0163, 0166
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
; ================================================================
|
||||||
|
; e_planning_dematic.ini - Zuordnungsregeln fuer Dematic
|
||||||
|
; ================================================================
|
||||||
|
; Wie e_planning.ini (Aufbau und Schluessel siehe dort), aber fuer die
|
||||||
|
; gemeinsame Zeichnung Layout + E-Planung (hw_import --dxf). Aufruf:
|
||||||
|
; merge_hw_layout.bat --tro dematic_merge_tro.json --hw dematic_hw.json
|
||||||
|
; --rules e_planning_dematic.ini
|
||||||
|
;
|
||||||
|
; Die E-Symbole liegen schon in Layout-Koordinaten, aber neben dem Bauteil:
|
||||||
|
; die Abstaende unten sind Zeichenabstaende, gemessen 2026-10-21 an
|
||||||
|
; dematic_merge..dxf + dematic_merge_export.csv (Median je Rolle in Klammern).
|
||||||
|
; Unterschiede zu Mubea:
|
||||||
|
; - Sensor und Stopper haben dieselbe Nummer (BG3151 / MB3151)
|
||||||
|
; - Stausensor und Weichenventil einer Speicherlinie haben dieselbe Nummer
|
||||||
|
; (Stau 1 = BG3031 / Weiche Abzw. 1 = MB3031)
|
||||||
|
; - VERW-Texte "In Sep n", "Sep n", "Stau n", "Weiche Abzw. n"
|
||||||
|
; ================================================================
|
||||||
|
|
||||||
|
[Registrierung]
|
||||||
|
; wird bei der gemeinsamen Zeichnung nicht gebraucht (kein Lagebezug)
|
||||||
|
anker = IN SEP*
|
||||||
|
|
||||||
|
; Sensor im Stopper -> stInSeparator<n>.stSenInSep
|
||||||
|
; (Speicherlinien 568/609 mm, Kreisel/VF 220-1340 mm; VF_1 0062 1731 mm.)
|
||||||
|
; Zugeordnet wird eins zu eins, kleinster Abstand zuerst.
|
||||||
|
[Separator.sensor]
|
||||||
|
verw = IN SEP*
|
||||||
|
praefix = BG
|
||||||
|
max_mm = 1800
|
||||||
|
|
||||||
|
; Stopperventil -> xOutStopper<n>: gleiche Nummer wie der Sensor; das
|
||||||
|
; Symbol liegt an derselben Stelle
|
||||||
|
[Separator.stopper]
|
||||||
|
verw = SEP*
|
||||||
|
praefix = MB
|
||||||
|
nummer_von = Separator.sensor
|
||||||
|
nummer_versatz = 0
|
||||||
|
max_mm = 1800
|
||||||
|
|
||||||
|
; Stausensor am Anfang einer Speicherlinie (537-554 mm vom Ausschleuselement);
|
||||||
|
; haengt an keinem TRO-Slot, dient nur als Partner fuer die Nummernregel
|
||||||
|
[Ausschleus.stau]
|
||||||
|
verw = STAU*
|
||||||
|
praefix = BG
|
||||||
|
max_mm = 1000
|
||||||
|
soll_mm = 540
|
||||||
|
|
||||||
|
; Weichenventil am Ausschleuselement -> Slot switch<n>.valve (xOutSwitch).
|
||||||
|
; Speicherlinie: Nummer = Stausensor; Kreisel-Weichen ohne Stausensor:
|
||||||
|
; raeumlich (408-865 mm, Kreisel4 0081 1015 mm)
|
||||||
|
[Ausschleus.valve]
|
||||||
|
verw = WEICHE*
|
||||||
|
praefix = MB
|
||||||
|
nummer_von = Ausschleus.stau
|
||||||
|
nummer_versatz = 0
|
||||||
|
max_mm = 1400
|
||||||
+52
-9
@@ -746,9 +746,19 @@ positions file exists.
|
|||||||
```
|
```
|
||||||
*_TIA.xlsx (PLC Tags + Constants) ─┐
|
*_TIA.xlsx (PLC Tags + Constants) ─┐
|
||||||
├─► hw_import.py ──► <project>_hw.json (HardwareDocument, sps_skel-hw-v1)
|
├─► hw_import.py ──► <project>_hw.json (HardwareDocument, sps_skel-hw-v1)
|
||||||
*_positionsconv.json (optional) ────┘ ──► <project>_hw.md (report: devices, drives, checks)
|
*_positionsconv.json (optional) ────┤ ──► <project>_hw.md (report: devices, drives, checks)
|
||||||
|
or merged drawing *.dxf (--dxf) ───┘
|
||||||
```
|
```
|
||||||
|
|
||||||
|
- **Merged drawing** (`--dxf`, instead of `--positions`; Dematic): layout and
|
||||||
|
E planning in one DXF. Every model-space block with a BMK in the attribute
|
||||||
|
`IO` is an E device (`IO` → BMK, `VERW`, `BEZEICHNUNG`, `KENNZEICHNUNG`,
|
||||||
|
insert point → position). Blocks without `IO` (layout parts, bus modules,
|
||||||
|
the green motor labels `ILS_Motorkennzeichnung*`) are ignored — the motor
|
||||||
|
symbol is authoritative. The positions are already layout coordinates;
|
||||||
|
`layoutCoordinates: true` tells `merge_hw_layout` to skip the registration.
|
||||||
|
A BMK drawn twice is a warning (the entry with `VERW` wins).
|
||||||
|
|
||||||
- **TIA import**: sheets are recognised by their header row, not their name
|
- **TIA import**: sheets are recognised by their header row, not their name
|
||||||
(English and German TIA exports): a sheet with `Logical Address` /
|
(English and German TIA exports): a sheet with `Logical Address` /
|
||||||
`Logische Adresse` holds tags, one with `Value` / `Wert` constants. Read with
|
`Logische Adresse` holds tags, one with `Value` / `Wert` constants. Read with
|
||||||
@@ -762,6 +772,10 @@ positions file exists.
|
|||||||
`K0104` (kind `Kreisel`); `CONVEYOR-#103` and `CONVEYOR-K0103` are the same
|
`K0104` (kind `Kreisel`); `CONVEYOR-#103` and `CONVEYOR-K0103` are the same
|
||||||
conveyor and both become `K0103` (kind `Strecke`) — the four-digit conveyor
|
conveyor and both become `K0103` (kind `Strecke`) — the four-digit conveyor
|
||||||
number of the SCL naming scheme (`DB_JamArea0103`, `fbCarousel0102`).
|
number of the SCL naming scheme (`DB_JamArea0103`, `fbCarousel0102`).
|
||||||
|
With `--conveyor-from-motor` (Dematic) a text without `K…`/`#…` that names a
|
||||||
|
motor gives the conveyor by motor number: `Motor KR-MA0055`, `Rad dreht
|
||||||
|
ILS-KR (MA0046)` → `K0055`, `K0046`; `ILS-VARIO` counts as kind `Strecke`.
|
||||||
|
Not for Mubea, where motor `MA0203` drives conveyor `K0103`.
|
||||||
- **Drives**: motor `MA` ↔ motor protection `FC` (same number), conveyor and
|
- **Drives**: motor `MA` ↔ motor protection `FC` (same number), conveyor and
|
||||||
kind from their texts, encoder `GF…` of the same conveyor.
|
kind from their texts, encoder `GF…` of the same conveyor.
|
||||||
- **Cross-check** (`checks`, also printed as `?` lines): BMK only in the
|
- **Cross-check** (`checks`, also printed as `?` lines): BMK only in the
|
||||||
@@ -772,7 +786,8 @@ positions file exists.
|
|||||||
through.
|
through.
|
||||||
|
|
||||||
Switches: `--tia NAME` (required), `--positions NAME` (optional; without it no
|
Switches: `--tia NAME` (required), `--positions NAME` (optional; without it no
|
||||||
positions), `--project NAME` (default from the positions file name:
|
positions), `--dxf NAME` (merged drawing instead of `--positions`),
|
||||||
|
`--conveyor-from-motor`, `--project NAME` (default from the positions file name:
|
||||||
`Mubea_positionsconv.json` → `Mubea`). Names resolve against `%SKEL_DATA%`,
|
`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
|
also with a sub-folder. Exit `0` (findings are in the report), `1` on input or
|
||||||
read errors.
|
read errors.
|
||||||
@@ -782,10 +797,17 @@ Result on Mubea (`mubea-1_TIA.xlsx` + `Mubea_positionsconv.json`): 238 devices,
|
|||||||
11 drives all with motor protection and conveyor (encoder `GF20000` at K0102);
|
11 drives all with motor protection and conveyor (encoder `GF20000` at K0102);
|
||||||
checks: `QA7000` only in the positions file, `BG0514` without controller.
|
checks: `QA7000` only in the positions file, `BG0514` without controller.
|
||||||
|
|
||||||
|
Result on Dematic (`dematic_hw-2_TIA.xlsx` + `dematic_merge..dxf`,
|
||||||
|
`--conveyor-from-motor`): 1022 devices, 278 from the drawing, 267 in both
|
||||||
|
sources; 44 drives, all with motor protection and conveyor. Only in the drawing:
|
||||||
|
the network devices `BX2100–2108` (scanners), `GF2200` (encoder), `BC2050`
|
||||||
|
(RFID). 378 field devices only in TIA (areas not drawn).
|
||||||
|
|
||||||
Tests: `tests/test_hw_import.py` (xlsx reader with shared/inline strings,
|
Tests: `tests/test_hw_import.py` (xlsx reader with shared/inline strings,
|
||||||
header-based sheet detection, conveyor texts, merge per BMK, drives, every
|
header-based sheet detection, conveyor texts incl. motor numbers, merge per
|
||||||
cross-check, TIA-only run, JSON round trip, CLI with a sub-folder in
|
BMK, drives, every cross-check, TIA-only run, JSON round trip, CLI with a
|
||||||
`%SKEL_DATA%`). The test builds its own small TIA xlsx.
|
sub-folder in `%SKEL_DATA%`, merged drawing). The test builds its own small TIA
|
||||||
|
xlsx and DXF.
|
||||||
|
|
||||||
## 5e. `merge_hw_layout.py` — merge layout and hardware planning into the plant JSON
|
## 5e. `merge_hw_layout.py` — merge layout and hardware planning into the plant JSON
|
||||||
|
|
||||||
@@ -809,8 +831,14 @@ Steps:
|
|||||||
layout separators), stored with the SHA-256 of the positions file. Warning
|
layout separators), stored with the SHA-256 of the positions file. Warning
|
||||||
when fewer than `registration_min_share` of the separators find a partner —
|
when fewer than `registration_min_share` of the separators find a partner —
|
||||||
a sign that layout and E drawing are different planning states.
|
a sign that layout and E drawing are different planning states.
|
||||||
|
A merged drawing (`layoutCoordinates`, `hw_import --dxf`) needs none:
|
||||||
|
identity, nothing stored. The symbols' offset beside their part (Dematic
|
||||||
|
~600 mm) is drawing distance, covered by the rules' `max_mm`/`soll_mm`.
|
||||||
2. **Rules** from `e_planning.ini`, unchanged logic (spatial with maximum /
|
2. **Rules** from `e_planning.ini`, unchanged logic (spatial with maximum /
|
||||||
target distance, numbering rule `MB = BG + 1`).
|
target distance, numbering rule `MB = BG + 1`). Per project another file
|
||||||
|
via `--rules` — Dematic: `cfg/e_planning_dematic.ini` (`In Sep n` / `Sep n`
|
||||||
|
with the same number, `Weiche Abzw. n` = number of `Stau n`, distances
|
||||||
|
measured on the merged drawing).
|
||||||
3. **Slots**: each assignment goes to the slot role of the TRO that holds the
|
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
|
part — key **TRO-ID + slot + role**. A `PinStore_Auto` now has a slot per
|
||||||
storage line (`line1` … `lineN`, same split as `scl_context._store`);
|
storage line (`line1` … `lineN`, same split as `scl_context._store`);
|
||||||
@@ -828,7 +856,11 @@ Steps:
|
|||||||
layout conveyor. This gives motor, motor protection and encoder per layout
|
layout conveyor. This gives motor, motor protection and encoder per layout
|
||||||
conveyor (`AnlageDocument.conveyor_for("0010-R")` → the TROs on that
|
conveyor (`AnlageDocument.conveyor_for("0010-R")` → the TROs on that
|
||||||
Kreisel, i.e. `xInCarouselRun`) and fills `drive.motor` /
|
Kreisel, i.e. `xInCarouselRun`) and fills `drive.motor` /
|
||||||
`drive.motorProtection` of Vario TROs.
|
`drive.motorProtection` of Vario TROs. When several TROs have a drive slot
|
||||||
|
on the same layout conveyor (Dematic VF_6: two Vario TROs), the drive is a
|
||||||
|
`widerspruch` with the conveyor's motor as a suggestion — each TRO has its
|
||||||
|
own motor; set it in `hw_overrides.ini`. `scl_skeleton` then keeps
|
||||||
|
`MA____` instead of falling back to the conveyor's motor.
|
||||||
5. **Corrections** from `hw_overrides.ini` (last):
|
5. **Corrections** from `hw_overrides.ini` (last):
|
||||||
`[<csv>.bindings] TRO01.separator1.sensor = BG0532` (or `= -` to clear) and
|
`[<csv>.bindings] TRO01.separator1.sensor = BG0532` (or `= -` to clear) and
|
||||||
`[<csv>.conveyors] K0102 = 0010`.
|
`[<csv>.conveyors] K0102 = 0010`.
|
||||||
@@ -838,7 +870,8 @@ Steps:
|
|||||||
Every binding carries `constant` and `address` from the TIA import.
|
Every binding carries `constant` and `address` from the TIA import.
|
||||||
|
|
||||||
Switches: `--tro NAME`, `--hw NAME` (both required, resolved against
|
Switches: `--tro NAME`, `--hw NAME` (both required, resolved against
|
||||||
`%SKEL_RESULTS%`), `--rules FILE`, `--overrides FILE`, `--re-register`. Exit
|
`%SKEL_RESULTS%`), `--rules FILE` (a bare name resolves against `%SKEL_CFG%`),
|
||||||
|
`--overrides FILE`, `--re-register`. Exit
|
||||||
`0` also when not everything was assigned; `1` on input errors, an old
|
`0` also when not everything was assigned; `1` on input errors, an old
|
||||||
TRO-JSON without `parts`, or an unprovable registration. Parameters:
|
TRO-JSON without `parts`, or an unprovable registration. Parameters:
|
||||||
`cfg/parameter.ini [merge_hw_layout]`.
|
`cfg/parameter.ini [merge_hw_layout]`.
|
||||||
@@ -852,6 +885,14 @@ K0105 = Kreisel1 (2/3)**, each with motor and motor protection. The other
|
|||||||
conveyors (Aufgabestation #100/#101, Vario #106–#109) are outside the CSV or
|
conveyors (Aufgabestation #100/#101, Vario #106–#109) are outside the CSV or
|
||||||
have a single vote and stay open.
|
have a single vote and stay open.
|
||||||
|
|
||||||
|
Result on Dematic (`dematic_merge_tro.json` + `dematic_hw.json`, rules
|
||||||
|
`e_planning_dematic.ini`): 113 slot roles assigned (all 27 storage lines with
|
||||||
|
sensor and stopper, 23 of 25 separators, 5 of 6 switch valves, 6 Vario drives),
|
||||||
|
39 open (Vario sensors, scanners, VF_4/VF_6 where mechanical parts are still
|
||||||
|
missing), 4 contradictions (the two Vario TROs on VF_6). Then
|
||||||
|
`scl_skeleton --templates --strict`: 115 names inserted, all known in TIA,
|
||||||
|
open gaps 730 → 615.
|
||||||
|
|
||||||
**Stage 4:** `scl_skeleton --json <csv>_anlage.json` reads the plant JSON
|
**Stage 4:** `scl_skeleton --json <csv>_anlage.json` reads the plant JSON
|
||||||
directly (§5b). `--eplan` still works for a TRO-JSON and is phased out together
|
directly (§5b). `--eplan` still works for a TRO-JSON and is phased out together
|
||||||
with `e_planning.py`.
|
with `e_planning.py`.
|
||||||
@@ -860,7 +901,9 @@ Tests: `tests/test_merge_hw_layout.py` (sensor spatial / stopper by number,
|
|||||||
contradiction, open roles, part without slot, conveyor majority with
|
contradiction, open roles, part without slot, conveyor majority with
|
||||||
overlapping boxes, Vario drive, too few votes, corrections incl. moving a BMK
|
overlapping boxes, Vario drive, too few votes, corrections incl. moving a BMK
|
||||||
and clearing a conveyor, storage-line slots, missing `parts`, JSON round trip,
|
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).
|
fingerprint of the stored registration, CLI end to end with a real offset,
|
||||||
|
merged drawing without registration, two drive TROs on one conveyor, Dematic
|
||||||
|
rules file).
|
||||||
|
|
||||||
## 6. `tro_catalog.py` — TRO type catalog (library, no CLI)
|
## 6. `tro_catalog.py` — TRO type catalog (library, no CLI)
|
||||||
|
|
||||||
|
|||||||
+117
-14
@@ -16,13 +16,19 @@ genuegen (Befund doc/E-Planung_Bruecke.md, "Nachpruefung 2026-10-15"):
|
|||||||
Kabellaengen-Tools (optional) - je BMK die Lage in der E-Zeichnung, die
|
Kabellaengen-Tools (optional) - je BMK die Lage in der E-Zeichnung, die
|
||||||
Verwendung (VERW, z. B. "IN SEP"), Klartext und E/A-Station.
|
Verwendung (VERW, z. B. "IN SEP"), Klartext und E/A-Station.
|
||||||
|
|
||||||
|
* **Gemeinsame Zeichnung** ``*.dxf`` (statt der Positionsdatei) - Layout
|
||||||
|
und E-Planung in einer DXF (Dematic): jeder Block mit Attribut ``IO`` ist
|
||||||
|
ein E-Bauteil (IO = BMK, VERW, BEZEICHNUNG, KENNZEICHNUNG, Einfuegepunkt).
|
||||||
|
Die Lage steht schon in Layout-Koordinaten; merge_hw_layout ermittelt
|
||||||
|
dann keinen Lagebezug (``layoutCoordinates``).
|
||||||
|
|
||||||
EA-Liste und WSCAD-Import bringen bei vorhandener Positionsdatei nichts
|
EA-Liste und WSCAD-Import bringen bei vorhandener Positionsdatei nichts
|
||||||
dazu und werden nicht gelesen.
|
dazu und werden nicht gelesen.
|
||||||
|
|
||||||
Ergebnis ``HardwareDocument`` (Schema sps_skel-hw-v1): je BMK ein Geraet mit
|
Ergebnis ``HardwareDocument`` (Schema sps_skel-hw-v1): je BMK ein Geraet mit
|
||||||
beiden Seiten, die Antriebe (Motor MA <-> Motorschutz FC, Geber GF) je
|
beiden Seiten, die Antriebe (Motor MA <-> Motorschutz FC, Geber GF) je
|
||||||
Foerderer der E-Planung (K0102; "#106" wird K0106), und die Gegenpruefung
|
Foerderer der E-Planung (K0102; "#106" wird K0106; mit --conveyor-from-motor
|
||||||
beider Quellen.
|
auch "MA0055" -> K0055), und die Gegenpruefung beider Quellen.
|
||||||
Die Zuordnung zum Layout macht merge_hw_layout.py.
|
Die Zuordnung zum Layout macht merge_hw_layout.py.
|
||||||
|
|
||||||
Die xlsx-Datei wird ohne Fremdbibliothek gelesen (zipfile + XML): der
|
Die xlsx-Datei wird ohne Fremdbibliothek gelesen (zipfile + XML): der
|
||||||
@@ -59,6 +65,7 @@ __all__ = [
|
|||||||
"read_xlsx",
|
"read_xlsx",
|
||||||
"load_tia",
|
"load_tia",
|
||||||
"load_positionsdraw",
|
"load_positionsdraw",
|
||||||
|
"load_dxf",
|
||||||
"pair_drives",
|
"pair_drives",
|
||||||
"conveyor_of",
|
"conveyor_of",
|
||||||
"conveyor_kind_of",
|
"conveyor_kind_of",
|
||||||
@@ -79,10 +86,13 @@ _ADDRESS = re.compile(r"^%?([EIAQM])\s*(\d+)\.(\d)$", re.IGNORECASE)
|
|||||||
_CONVEYOR_K = re.compile(r"(?<![A-Z0-9])K(\d{4})(?!\d)")
|
_CONVEYOR_K = re.compile(r"(?<![A-Z0-9])K(\d{4})(?!\d)")
|
||||||
# ... bzw. "CONVEYOR #100", "CONVEYOR-#106", "CONVEYOR#110"
|
# ... bzw. "CONVEYOR #100", "CONVEYOR-#106", "CONVEYOR#110"
|
||||||
_CONVEYOR_HASH = re.compile(r"#\s*(\d{3})(?!\d)")
|
_CONVEYOR_HASH = re.compile(r"#\s*(\d{3})(?!\d)")
|
||||||
|
# ... bzw. ueber den Motor (Dematic, nur mit motor=True): "Motor KR-MA0055",
|
||||||
|
# "Rad dreht ILS-KR (MA0046)", "ILS-VARIO-MA0043" -> K0055 / K0046 / K0043
|
||||||
|
_CONVEYOR_MA = re.compile(r"(?<![A-Z0-9])MA(\d{4})(?!\d)")
|
||||||
# Art des Foerderers im Klartext: "KREISEL-K0102", "KR-K0104" -> Kreisel;
|
# Art des Foerderers im Klartext: "KREISEL-K0102", "KR-K0104" -> Kreisel;
|
||||||
# "CONVEYOR #106", "KLINKENFOERDERER" -> Strecke
|
# "CONVEYOR #106", "KLINKENFOERDERER", "ILS-VARIO" -> Strecke
|
||||||
_KIND_KREISEL = re.compile(r"(?<![A-Z])(KREISEL|KR)(?![A-Z])")
|
_KIND_KREISEL = re.compile(r"(?<![A-Z])(KREISEL|KR)(?![A-Z])")
|
||||||
_KIND_STRECKE = re.compile(r"CONVEYOR|CONVEYER|F(OE|Ö)RDERER")
|
_KIND_STRECKE = re.compile(r"CONVEYOR|CONVEYER|F(OE|Ö)RDERER|(?<![A-Z])VARIO(?![A-Z])")
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -207,6 +217,9 @@ class HardwareDocument(BaseModel):
|
|||||||
tia_sha256: str = Field(default="", alias="tiaSha256")
|
tia_sha256: str = Field(default="", alias="tiaSha256")
|
||||||
positions_file: str = Field(default="", alias="positionsFile")
|
positions_file: str = Field(default="", alias="positionsFile")
|
||||||
positions_sha256: str = Field(default="", alias="positionsSha256")
|
positions_sha256: str = Field(default="", alias="positionsSha256")
|
||||||
|
# True: die Lage kommt aus der gemeinsamen Zeichnung (load_dxf) und steht
|
||||||
|
# schon in Layout-Koordinaten - merge_hw_layout ermittelt keinen Lagebezug
|
||||||
|
layout_coordinates: bool = Field(default=False, alias="layoutCoordinates")
|
||||||
devices: list[HwDevice] = Field(default_factory=list)
|
devices: list[HwDevice] = Field(default_factory=list)
|
||||||
drives: list[Drive] = Field(default_factory=list)
|
drives: list[Drive] = Field(default_factory=list)
|
||||||
# Konstanten ohne passendes Signal (z. B. Projektkonstanten)
|
# Konstanten ohne passendes Signal (z. B. Projektkonstanten)
|
||||||
@@ -416,6 +429,44 @@ def load_positionsdraw(path: Path, warnings: list[str] | None = None) -> list[ED
|
|||||||
return list(devices.values())
|
return list(devices.values())
|
||||||
|
|
||||||
|
|
||||||
|
def load_dxf(path: Path, warnings: list[str] | None = None) -> list[EDevice]:
|
||||||
|
"""E-Bauteile aus einer gemeinsamen Zeichnung (Layout + E-Planung) lesen.
|
||||||
|
|
||||||
|
Jeder Block im Modellbereich mit einem BMK im Attribut ``IO`` ist ein
|
||||||
|
E-Bauteil; die Lage ist der Einfuegepunkt - schon in Layout-Koordinaten.
|
||||||
|
Bloecke ohne IO (Layout-Bauteile, Busverteiler, die Motorkennzeichnung
|
||||||
|
"ILS-Kreisel MA0053") zaehlen nicht: der Motor steht im Motorsymbol.
|
||||||
|
Steht ein BMK doppelt da, gewinnt der Eintrag mit VERW.
|
||||||
|
"""
|
||||||
|
import ezdxf
|
||||||
|
|
||||||
|
warnings = warnings if warnings is not None else []
|
||||||
|
doc = ezdxf.readfile(str(path))
|
||||||
|
devices: dict[str, EDevice] = {}
|
||||||
|
count: Counter = Counter()
|
||||||
|
for insert in doc.modelspace().query("INSERT"):
|
||||||
|
attrs = {a.dxf.tag.upper(): a.dxf.text.strip() for a in insert.attribs}
|
||||||
|
bmk = attrs.get("IO", "")
|
||||||
|
if not _BMK.match(bmk):
|
||||||
|
continue
|
||||||
|
count[bmk] += 1
|
||||||
|
dev = EDevice(
|
||||||
|
bmk=bmk, verw=attrs.get("VERW", ""), bezeichnung=attrs.get("BEZEICHNUNG", ""),
|
||||||
|
kennzeichnung=attrs.get("KENNZEICHNUNG", ""),
|
||||||
|
pos=(float(insert.dxf.insert.x), float(insert.dxf.insert.y)))
|
||||||
|
old = devices.get(bmk)
|
||||||
|
if old is None or (not old.verw and dev.verw):
|
||||||
|
devices[bmk] = dev
|
||||||
|
for bmk, n in sorted(count.items()):
|
||||||
|
if n > 1:
|
||||||
|
warnings.append(f"Zeichnung: {bmk} steht {n}x in der Zeichnung "
|
||||||
|
f"- verwendet wird ein Eintrag")
|
||||||
|
if not devices:
|
||||||
|
raise ValueError(f"{path.name}: kein Block mit BMK im Attribut 'IO' gefunden "
|
||||||
|
f"- ist das die Zeichnung mit der E-Planung?")
|
||||||
|
return list(devices.values())
|
||||||
|
|
||||||
|
|
||||||
def _positions_warnings(path: Path) -> list[str]:
|
def _positions_warnings(path: Path) -> list[str]:
|
||||||
"""Die Hinweise, die das Kabellaengen-Tool selbst in die Datei schreibt."""
|
"""Die Hinweise, die das Kabellaengen-Tool selbst in die Datei schreibt."""
|
||||||
data = json.loads(path.read_text(encoding="utf-8"))
|
data = json.loads(path.read_text(encoding="utf-8"))
|
||||||
@@ -433,13 +484,18 @@ def _positions_warnings(path: Path) -> list[str]:
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
def conveyor_of(*texts: str) -> str:
|
def conveyor_of(*texts: str, motor: bool = False) -> str:
|
||||||
"""Foerderer der E-Planung aus einem Klartext als "K<NNNN>", sonst "".
|
"""Foerderer der E-Planung aus einem Klartext als "K<NNNN>", sonst "".
|
||||||
|
|
||||||
Die E-Planung schreibt denselben Foerderer zweifach: "CONVEYOR-K0103"
|
Die E-Planung schreibt denselben Foerderer zweifach: "CONVEYOR-K0103"
|
||||||
(Motorschutz FC0203) und "CONVEYOR-#103" (Motor MA0203). Beides ist die
|
(Motorschutz FC0203) und "CONVEYOR-#103" (Motor MA0203). Beides ist die
|
||||||
vierstellige Foerderernummer des SCL-Namensschemas (DB_JamArea0103,
|
vierstellige Foerderernummer des SCL-Namensschemas (DB_JamArea0103,
|
||||||
fbCarousel0102) und wird zu "K0103".
|
fbCarousel0102) und wird zu "K0103".
|
||||||
|
|
||||||
|
``motor=True`` (Dematic): ohne K/# nennt der Klartext nur den Motor
|
||||||
|
("Motor KR-MA0055", "Rad dreht ILS-KR (MA0046)"); dann ist die
|
||||||
|
Motornummer die Foerderernummer -> "K0055". Bei Mubea waere das falsch
|
||||||
|
(Motor MA0203 treibt K0103), deshalb nur auf Wunsch.
|
||||||
"""
|
"""
|
||||||
for text in texts:
|
for text in texts:
|
||||||
upper = (text or "").upper()
|
upper = (text or "").upper()
|
||||||
@@ -449,6 +505,11 @@ def conveyor_of(*texts: str) -> str:
|
|||||||
m = _CONVEYOR_HASH.search(upper)
|
m = _CONVEYOR_HASH.search(upper)
|
||||||
if m:
|
if m:
|
||||||
return f"K{int(m.group(1)):04d}"
|
return f"K{int(m.group(1)):04d}"
|
||||||
|
if motor:
|
||||||
|
for text in texts:
|
||||||
|
m = _CONVEYOR_MA.search((text or "").upper())
|
||||||
|
if m:
|
||||||
|
return f"K{m.group(1)}"
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
@@ -497,10 +558,18 @@ def _constant_name(bmk: str, direction: str) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def build_hardware(tia_path: Path | None, positions_path: Path | None,
|
def build_hardware(tia_path: Path | None, positions_path: Path | None,
|
||||||
project: str = "") -> HardwareDocument:
|
project: str = "", dxf_path: Path | None = None,
|
||||||
"""Beide Quellen einlesen, je BMK zusammenfuehren und gegeneinander pruefen."""
|
conveyor_from_motor: bool = False) -> HardwareDocument:
|
||||||
if tia_path is None and positions_path is None:
|
"""Beide Quellen einlesen, je BMK zusammenfuehren und gegeneinander pruefen.
|
||||||
raise ValueError("weder TIA-Import noch Positionsdatei angegeben")
|
|
||||||
|
Die Lage kommt aus der Positionsdatei oder aus der gemeinsamen Zeichnung
|
||||||
|
``dxf_path`` (load_dxf), nicht aus beiden. ``conveyor_from_motor``: siehe
|
||||||
|
conveyor_of(motor=True).
|
||||||
|
"""
|
||||||
|
if tia_path is None and positions_path is None and dxf_path is None:
|
||||||
|
raise ValueError("weder TIA-Import noch Positionsdatei/Zeichnung angegeben")
|
||||||
|
if positions_path is not None and dxf_path is not None:
|
||||||
|
raise ValueError("Positionsdatei und Zeichnung zugleich - nur eine Quelle fuer die Lage")
|
||||||
warnings: list[str] = []
|
warnings: list[str] = []
|
||||||
checks: list[str] = []
|
checks: list[str] = []
|
||||||
|
|
||||||
@@ -512,6 +581,9 @@ def build_hardware(tia_path: Path | None, positions_path: Path | None,
|
|||||||
if positions_path is not None:
|
if positions_path is not None:
|
||||||
e_devices = load_positionsdraw(positions_path, warnings)
|
e_devices = load_positionsdraw(positions_path, warnings)
|
||||||
warnings.extend(_positions_warnings(positions_path))
|
warnings.extend(_positions_warnings(positions_path))
|
||||||
|
elif dxf_path is not None:
|
||||||
|
e_devices = load_dxf(dxf_path, warnings)
|
||||||
|
positions_path = dxf_path
|
||||||
|
|
||||||
tag_of = {}
|
tag_of = {}
|
||||||
for t in tags:
|
for t in tags:
|
||||||
@@ -546,7 +618,8 @@ def build_hardware(tia_path: Path | None, positions_path: Path | None,
|
|||||||
comment=tag.comment if tag else "",
|
comment=tag.comment if tag else "",
|
||||||
constant=cname if const else "",
|
constant=cname if const else "",
|
||||||
constant_value=value,
|
constant_value=value,
|
||||||
conveyor=conveyor_of(e.bezeichnung if e else "", tag.comment if tag else ""),
|
conveyor=conveyor_of(e.bezeichnung if e else "", tag.comment if tag else "",
|
||||||
|
motor=conveyor_from_motor),
|
||||||
conveyor_kind=conveyor_kind_of(e.bezeichnung if e else "",
|
conveyor_kind=conveyor_kind_of(e.bezeichnung if e else "",
|
||||||
tag.comment if tag else ""),
|
tag.comment if tag else ""),
|
||||||
sources=[s for s, ok in (("tia", tag is not None), ("positions", e is not None))
|
sources=[s for s, ok in (("tia", tag is not None), ("positions", e is not None))
|
||||||
@@ -561,6 +634,7 @@ def build_hardware(tia_path: Path | None, positions_path: Path | None,
|
|||||||
tia_sha256=file_fingerprint(tia_path) if tia_path else "",
|
tia_sha256=file_fingerprint(tia_path) if tia_path else "",
|
||||||
positions_file=positions_path.name if positions_path else "",
|
positions_file=positions_path.name if positions_path else "",
|
||||||
positions_sha256=file_fingerprint(positions_path) if positions_path else "",
|
positions_sha256=file_fingerprint(positions_path) if positions_path else "",
|
||||||
|
layout_coordinates=dxf_path is not None,
|
||||||
devices=devices,
|
devices=devices,
|
||||||
drives=pair_drives(devices),
|
drives=pair_drives(devices),
|
||||||
constants=[c for c in constants if c.name not in used_constants],
|
constants=[c for c in constants if c.name not in used_constants],
|
||||||
@@ -627,6 +701,9 @@ def render_report(doc: HardwareDocument) -> str:
|
|||||||
add(f"# Hardwareplanung: {doc.project}")
|
add(f"# Hardwareplanung: {doc.project}")
|
||||||
add("")
|
add("")
|
||||||
add(f"- TIA-Import: `{doc.tia_file or '-'}`")
|
add(f"- TIA-Import: `{doc.tia_file or '-'}`")
|
||||||
|
if doc.layout_coordinates:
|
||||||
|
add(f"- Gemeinsame Zeichnung (Lage in Layout-Koordinaten): `{doc.positions_file}`")
|
||||||
|
else:
|
||||||
add(f"- Positionsdatei: `{doc.positions_file or '-'}`")
|
add(f"- Positionsdatei: `{doc.positions_file or '-'}`")
|
||||||
add(f"- Erzeugt: {doc.created}")
|
add(f"- Erzeugt: {doc.created}")
|
||||||
add("")
|
add("")
|
||||||
@@ -699,20 +776,30 @@ def project_name(tia_path: Path | None, positions_path: Path | None) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def main(argv: list[str] | None = None) -> int:
|
def main(argv: list[str] | None = None) -> int:
|
||||||
|
import ezdxf
|
||||||
|
|
||||||
from material_flow import env_dir, resolve_input
|
from material_flow import env_dir, resolve_input
|
||||||
|
|
||||||
args = parse_args(argv)
|
args = parse_args(argv)
|
||||||
tia_path = resolve_input(args.tia) if args.tia else None
|
tia_path = resolve_input(args.tia) if args.tia else None
|
||||||
positions_path = resolve_input(args.positions) if args.positions else None
|
positions_path = resolve_input(args.positions) if args.positions else None
|
||||||
for label, path in (("TIA-Import", tia_path), ("Positionsdatei", positions_path)):
|
dxf_path = resolve_input(args.dxf) if args.dxf else None
|
||||||
|
if positions_path is not None and dxf_path is not None:
|
||||||
|
print("FEHLER: --positions und --dxf zugleich - nur eine Quelle fuer die Lage",
|
||||||
|
file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
for label, path in (("TIA-Import", tia_path), ("Positionsdatei", positions_path),
|
||||||
|
("Zeichnung", dxf_path)):
|
||||||
if path is not None and not path.is_file():
|
if path is not None and not path.is_file():
|
||||||
print(f"FEHLER: {label} nicht gefunden: {path}", file=sys.stderr)
|
print(f"FEHLER: {label} nicht gefunden: {path}", file=sys.stderr)
|
||||||
return 1
|
return 1
|
||||||
project = args.project or project_name(tia_path, positions_path)
|
project = args.project or project_name(tia_path, positions_path or dxf_path)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
doc = build_hardware(tia_path, positions_path, project)
|
doc = build_hardware(tia_path, positions_path, project, dxf_path,
|
||||||
except (OSError, ValueError, KeyError, zipfile.BadZipFile, ET.ParseError) as exc:
|
args.conveyor_from_motor)
|
||||||
|
except (OSError, ValueError, KeyError, zipfile.BadZipFile, ET.ParseError,
|
||||||
|
ezdxf.DXFError) as exc:
|
||||||
print(f"FEHLER: {exc}", file=sys.stderr)
|
print(f"FEHLER: {exc}", file=sys.stderr)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
@@ -730,6 +817,9 @@ def main(argv: list[str] | None = None) -> int:
|
|||||||
print("HARDWAREPLANUNG EINLESEN")
|
print("HARDWAREPLANUNG EINLESEN")
|
||||||
print("================================================================")
|
print("================================================================")
|
||||||
print(f"TIA-Import = {tia_path or '-'}")
|
print(f"TIA-Import = {tia_path or '-'}")
|
||||||
|
if dxf_path is not None:
|
||||||
|
print(f"Zeichnung = {dxf_path} (Lage in Layout-Koordinaten)")
|
||||||
|
else:
|
||||||
print(f"Positionsdatei = {positions_path or '-'}")
|
print(f"Positionsdatei = {positions_path or '-'}")
|
||||||
print(f"Projekt = {project}")
|
print(f"Projekt = {project}")
|
||||||
print(f"Geraete = {len(doc.devices)} (TIA {n_tia}, Positionsdatei {n_pos}, "
|
print(f"Geraete = {len(doc.devices)} (TIA {n_tia}, Positionsdatei {n_pos}, "
|
||||||
@@ -763,6 +853,8 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
|||||||
" hw_import.bat --tia Mubea/mubea-1_TIA.xlsx "
|
" hw_import.bat --tia Mubea/mubea-1_TIA.xlsx "
|
||||||
"--positions Mubea/Mubea_positionsconv.json\n"
|
"--positions Mubea/Mubea_positionsconv.json\n"
|
||||||
" hw_import.bat --tia mubea-1_TIA.xlsx --project Mubea\n"
|
" hw_import.bat --tia mubea-1_TIA.xlsx --project Mubea\n"
|
||||||
|
" hw_import.bat --tia dematic_hw-2_TIA.xlsx --dxf dematic_merge..dxf "
|
||||||
|
"--project dematic --conveyor-from-motor\n"
|
||||||
"\n"
|
"\n"
|
||||||
"Exit-Codes:\n"
|
"Exit-Codes:\n"
|
||||||
" 0 ok (Befunde der Gegenpruefung stehen im Bericht)\n"
|
" 0 ok (Befunde der Gegenpruefung stehen im Bericht)\n"
|
||||||
@@ -774,6 +866,17 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
|||||||
parser.add_argument("--positions", metavar="NAME",
|
parser.add_argument("--positions", metavar="NAME",
|
||||||
help="*_positionsconv.json bzw. *_positionsdraw.json in "
|
help="*_positionsconv.json bzw. *_positionsdraw.json in "
|
||||||
"%%SKEL_DATA%% (oder ein Pfad); ohne sie keine Lage")
|
"%%SKEL_DATA%% (oder ein Pfad); ohne sie keine Lage")
|
||||||
|
parser.add_argument("--dxf", metavar="NAME",
|
||||||
|
help="statt --positions: gemeinsame Zeichnung (Layout + "
|
||||||
|
"E-Planung, *.dxf) in %%SKEL_DATA%% (oder ein Pfad); "
|
||||||
|
"jeder Block mit Attribut IO ist ein E-Bauteil, die Lage "
|
||||||
|
"steht schon in Layout-Koordinaten")
|
||||||
|
parser.add_argument("--conveyor-from-motor", dest="conveyor_from_motor",
|
||||||
|
action="store_true",
|
||||||
|
help="Foerderer auch aus der Motornummer im Klartext "
|
||||||
|
"(\"Motor KR-MA0055\" -> K0055), wenn kein K0102/#106 "
|
||||||
|
"dasteht (Dematic). Nicht fuer Mubea: dort treibt MA0203 "
|
||||||
|
"den Foerderer K0103.")
|
||||||
parser.add_argument("--project", metavar="NAME",
|
parser.add_argument("--project", metavar="NAME",
|
||||||
help="Projektname fuer <projekt>_hw.json. Standard: aus dem "
|
help="Projektname fuer <projekt>_hw.json. Standard: aus dem "
|
||||||
"Namen der Positionsdatei (Mubea_positionsconv.json -> Mubea)")
|
"Namen der Positionsdatei (Mubea_positionsconv.json -> Mubea)")
|
||||||
|
|||||||
+48
-6
@@ -371,6 +371,19 @@ def resolve_transform(points, devices, anchors, cfg_dir: Path, csv_name: str,
|
|||||||
return transform
|
return transform
|
||||||
|
|
||||||
|
|
||||||
|
def _same_drawing(points: list[LayoutPoint], drawing: str) -> Transform:
|
||||||
|
"""Lagebezug einer gemeinsamen Zeichnung (hw_import --dxf): keiner.
|
||||||
|
|
||||||
|
Die E-Symbole stehen schon in Layout-Koordinaten. Ihr Versatz neben dem
|
||||||
|
Bauteil (Dematic ~600 mm) ist Zeichenabstand, keine Verschiebung - ein
|
||||||
|
eingepasster Bezug wuerde ihn nur verzerren. Er steckt in max_mm/soll_mm
|
||||||
|
der Regeln.
|
||||||
|
"""
|
||||||
|
n = sum(1 for p in points if p.kind == "Separator")
|
||||||
|
return Transform(dx=0.0, dy=0.0, source="zeichnung", anchor="gemeinsame Zeichnung",
|
||||||
|
matched=n, candidates=n, dxf_file=drawing)
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Slots
|
# Slots
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -529,9 +542,20 @@ def _conveyor_kinds(hw: HardwareDocument) -> dict[str, str]:
|
|||||||
|
|
||||||
def bind_drives(doc: TroDocument, conveyors: list[ConveyorBinding],
|
def bind_drives(doc: TroDocument, conveyors: list[ConveyorBinding],
|
||||||
hw: HardwareDocument) -> list[Binding]:
|
hw: HardwareDocument) -> list[Binding]:
|
||||||
"""Antriebs-Slots (Vario): Motor und Motorschutz des zugeordneten Foerderers."""
|
"""Antriebs-Slots (Vario): Motor und Motorschutz des zugeordneten Foerderers.
|
||||||
|
|
||||||
|
Tragen mehrere TROs einen Antriebs-Slot auf demselben Layout-Foerderer
|
||||||
|
(Dematic VF_6: zwei Vario-TROs, drei Vario-Motoren), hat jeder TRO seinen
|
||||||
|
eigenen Motor - welcher, sagt die Mehrheit je Foerderer nicht. Dann wird
|
||||||
|
der Antrieb ein Widerspruch (BMK als Vorschlag), Korrektur von Hand.
|
||||||
|
"""
|
||||||
devices = hw.by_bmk()
|
devices = hw.by_bmk()
|
||||||
by_layout = {c.layout_id: c for c in conveyors if c.status == "zugeordnet"}
|
by_layout = {c.layout_id: c for c in conveyors if c.status == "zugeordnet"}
|
||||||
|
sharing: dict[str, list[str]] = {}
|
||||||
|
for tro in doc.tros:
|
||||||
|
for s in tro.slots:
|
||||||
|
if s.layout_id and any(r.rstrip("?") in _DRIVE_ROLES for r in s.roles):
|
||||||
|
sharing.setdefault(s.layout_id.split("-", 1)[0], []).append(tro.tro_id)
|
||||||
bindings = []
|
bindings = []
|
||||||
for tro in doc.tros:
|
for tro in doc.tros:
|
||||||
for s in tro.slots:
|
for s in tro.slots:
|
||||||
@@ -541,17 +565,24 @@ def bind_drives(doc: TroDocument, conveyors: list[ConveyorBinding],
|
|||||||
if conv is None:
|
if conv is None:
|
||||||
continue
|
continue
|
||||||
roles = [r.rstrip("?") for r in s.roles]
|
roles = [r.rstrip("?") for r in s.roles]
|
||||||
|
shared = sharing.get(s.layout_id.split("-", 1)[0], [])
|
||||||
for role, attr in _DRIVE_ROLES.items():
|
for role, attr in _DRIVE_ROLES.items():
|
||||||
bmk = getattr(conv, attr)
|
bmk = getattr(conv, attr)
|
||||||
if role not in roles or not bmk:
|
if role not in roles or not bmk:
|
||||||
continue
|
continue
|
||||||
dev = devices.get(bmk)
|
dev = devices.get(bmk)
|
||||||
|
status = "zugeordnet" if dev and dev.in_tia else "widerspruch"
|
||||||
|
note = f"Antrieb von {conv.eplan_name}"
|
||||||
|
if len(shared) > 1:
|
||||||
|
status = "widerspruch"
|
||||||
|
note = (f"{conv.layout_name or conv.layout_id} traegt {len(shared)} "
|
||||||
|
f"Antriebs-TROs ({', '.join(shared)}) - Antrieb je TRO von Hand "
|
||||||
|
f"({OVERRIDES_FILE}); Vorschlag {conv.eplan_name}")
|
||||||
bindings.append(Binding(
|
bindings.append(Binding(
|
||||||
tro=tro.tro_id, slot=s.slot, role=role, layout_id=s.layout_id,
|
tro=tro.tro_id, slot=s.slot, role=role, layout_id=s.layout_id,
|
||||||
bmk=bmk, constant=dev.constant if dev else "",
|
bmk=bmk, constant=dev.constant if dev else "",
|
||||||
address=dev.address if dev else "", source="positionsdraw",
|
address=dev.address if dev else "", source="positionsdraw",
|
||||||
rule="mehrheit", status="zugeordnet" if dev and dev.in_tia else "widerspruch",
|
rule="mehrheit", status=status, note=note))
|
||||||
note=f"Antrieb von {conv.eplan_name}"))
|
|
||||||
return bindings
|
return bindings
|
||||||
|
|
||||||
|
|
||||||
@@ -690,7 +721,9 @@ def build_anlage(doc: TroDocument, hw: HardwareDocument, rules: list[Rule],
|
|||||||
assignments: list[Assignment] = []
|
assignments: list[Assignment] = []
|
||||||
conveyors: list[ConveyorBinding] = []
|
conveyors: list[ConveyorBinding] = []
|
||||||
if hw.field_devices():
|
if hw.field_devices():
|
||||||
if transform is None:
|
if transform is None and hw.layout_coordinates:
|
||||||
|
transform = _same_drawing(points, hw.positions_file)
|
||||||
|
elif transform is None:
|
||||||
transform = resolve_transform(points, devices, anchors, cfg_dir, doc.source,
|
transform = resolve_transform(points, devices, anchors, cfg_dir, doc.source,
|
||||||
hw.positions_file, re_register, warnings,
|
hw.positions_file, re_register, warnings,
|
||||||
hw.positions_sha256)
|
hw.positions_sha256)
|
||||||
@@ -856,6 +889,14 @@ def _in_results(name: str, results: Path) -> Path:
|
|||||||
return results / name
|
return results / name
|
||||||
|
|
||||||
|
|
||||||
|
def _in_cfg(name: str, cfg_dir: Path) -> Path:
|
||||||
|
"""Regeldatei: ein blosser Name, der hier nicht liegt, steht in %SKEL_CFG%."""
|
||||||
|
path = Path(name)
|
||||||
|
if path.is_absolute() or path.parent != Path(".") or path.is_file():
|
||||||
|
return path
|
||||||
|
return cfg_dir / name
|
||||||
|
|
||||||
|
|
||||||
def main(argv: list[str] | None = None) -> int:
|
def main(argv: list[str] | None = None) -> int:
|
||||||
from material_flow import env_dir
|
from material_flow import env_dir
|
||||||
|
|
||||||
@@ -864,7 +905,7 @@ def main(argv: list[str] | None = None) -> int:
|
|||||||
cfg_dir = env_dir("SKEL_CFG", "cfg")
|
cfg_dir = env_dir("SKEL_CFG", "cfg")
|
||||||
tro_path = _in_results(args.tro, results)
|
tro_path = _in_results(args.tro, results)
|
||||||
hw_path = _in_results(args.hw, results)
|
hw_path = _in_results(args.hw, results)
|
||||||
rules_path = Path(args.rules) if args.rules else cfg_dir / RULES_FILE
|
rules_path = _in_cfg(args.rules, cfg_dir) if args.rules else cfg_dir / RULES_FILE
|
||||||
overrides_path = Path(args.overrides) if args.overrides else cfg_dir / OVERRIDES_FILE
|
overrides_path = Path(args.overrides) if args.overrides else cfg_dir / OVERRIDES_FILE
|
||||||
for label, path in (("TRO-JSON", tro_path), ("Hardware-JSON", hw_path),
|
for label, path in (("TRO-JSON", tro_path), ("Hardware-JSON", hw_path),
|
||||||
("Regeln", rules_path)):
|
("Regeln", rules_path)):
|
||||||
@@ -955,7 +996,8 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
|||||||
help="Hardware-JSON <projekt>_hw.json in %%SKEL_RESULTS%% "
|
help="Hardware-JSON <projekt>_hw.json in %%SKEL_RESULTS%% "
|
||||||
"(oder ein Pfad)")
|
"(oder ein Pfad)")
|
||||||
parser.add_argument("--rules", metavar="DATEI",
|
parser.add_argument("--rules", metavar="DATEI",
|
||||||
help=f"Regeldatei. Standard: %%SKEL_CFG%%\\{RULES_FILE}")
|
help=f"Regeldatei (Name in %%SKEL_CFG%% oder ein Pfad), z. B. "
|
||||||
|
f"e_planning_dematic.ini. Standard: %%SKEL_CFG%%\\{RULES_FILE}")
|
||||||
parser.add_argument("--overrides", metavar="DATEI",
|
parser.add_argument("--overrides", metavar="DATEI",
|
||||||
help=f"Handkorrekturen. Standard: %%SKEL_CFG%%\\{OVERRIDES_FILE} "
|
help=f"Handkorrekturen. Standard: %%SKEL_CFG%%\\{OVERRIDES_FILE} "
|
||||||
f"(fehlt sie, keine)")
|
f"(fehlt sie, keine)")
|
||||||
|
|||||||
+14
-3
@@ -150,6 +150,13 @@ class HwSource:
|
|||||||
geraet = self.device(tro_id, slot, role, layout)
|
geraet = self.device(tro_id, slot, role, layout)
|
||||||
return _bekannt(fmt.format(bmk=geraet[0])) if geraet else _offen(offen)
|
return _bekannt(fmt.format(bmk=geraet[0])) if geraet else _offen(offen)
|
||||||
|
|
||||||
|
def conflict(self, tro_id: str, slot: str, role: str) -> bool:
|
||||||
|
"""True, wenn die Anlagen-JSON fuer die Slot-Rolle einen Widerspruch fuehrt."""
|
||||||
|
if self.anlage is None:
|
||||||
|
return False
|
||||||
|
b = self.anlage.binding(tro_id, slot, role)
|
||||||
|
return b is not None and b.status == "widerspruch"
|
||||||
|
|
||||||
def conveyor(self, host: str) -> ConveyorBinding | None:
|
def conveyor(self, host: str) -> ConveyorBinding | None:
|
||||||
"""Foerderer der Hardwareplanung (K0102) am Layout-Foerderer ("0010-R")."""
|
"""Foerderer der Hardwareplanung (K0102) am Layout-Foerderer ("0010-R")."""
|
||||||
if self.anlage is None or not host:
|
if self.anlage is None or not host:
|
||||||
@@ -643,7 +650,9 @@ def _drive(tro: TroBase, hw: HwSource) -> tuple[SclName, SclName]:
|
|||||||
|
|
||||||
Erst der Antriebs-Slot (Rollen motor/motorProtection); fehlt er, der
|
Erst der Antriebs-Slot (Rollen motor/motorProtection); fehlt er, der
|
||||||
Foerderer der Hardwareplanung am Host des TRO - der Vario-Antrieb ist
|
Foerderer der Hardwareplanung am Host des TRO - der Vario-Antrieb ist
|
||||||
dessen Motor (MA/FC-Paar aus hw_import).
|
dessen Motor (MA/FC-Paar aus hw_import). Ein Widerspruch im Slot (z. B.
|
||||||
|
zwei Vario-TROs auf einem Foerderer) bleibt offen, statt den Motor des
|
||||||
|
Foerderers zu erben.
|
||||||
"""
|
"""
|
||||||
motor = hw.output(tro.tro_id, "drive", "motor", offen="MA____")
|
motor = hw.output(tro.tro_id, "drive", "motor", offen="MA____")
|
||||||
schutz = hw.output(tro.tro_id, "drive", "motorProtection",
|
schutz = hw.output(tro.tro_id, "drive", "motorProtection",
|
||||||
@@ -651,9 +660,11 @@ def _drive(tro: TroBase, hw: HwSource) -> tuple[SclName, SclName]:
|
|||||||
hosts = list(getattr(tro, "hosts", []) or [])
|
hosts = list(getattr(tro, "hosts", []) or [])
|
||||||
conveyor = hw.conveyor(hosts[0]) if hosts else None
|
conveyor = hw.conveyor(hosts[0]) if hosts else None
|
||||||
if conveyor is not None:
|
if conveyor is not None:
|
||||||
if motor.todo and conveyor.motor:
|
if (motor.todo and conveyor.motor
|
||||||
|
and not hw.conflict(tro.tro_id, "drive", "motor")):
|
||||||
motor = _bekannt(conveyor.motor)
|
motor = _bekannt(conveyor.motor)
|
||||||
if schutz.todo and conveyor.protection:
|
if (schutz.todo and conveyor.protection
|
||||||
|
and not hw.conflict(tro.tro_id, "drive", "motorProtection")):
|
||||||
schutz = _bekannt(f'"{conveyor.protection}"')
|
schutz = _bekannt(f'"{conveyor.protection}"')
|
||||||
return motor, schutz
|
return motor, schutz
|
||||||
|
|
||||||
|
|||||||
@@ -191,6 +191,91 @@ def test_foerderer_aus_klartext(text, name, kind):
|
|||||||
assert conveyor_kind_of(text) == kind
|
assert conveyor_kind_of(text) == kind
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("text, name, kind", [
|
||||||
|
# Dematic nennt nur den Motor
|
||||||
|
("Motor KR-MA0055", "K0055", "Kreisel"),
|
||||||
|
("Rad dreht ILS-KR (MA0046)", "K0046", "Kreisel"),
|
||||||
|
("Motor ILS-VARIO-MA0043", "K0043", "Strecke"),
|
||||||
|
("Abfrage Finger in Position ILS-VARIO (MA0050)", "K0050", "Strecke"),
|
||||||
|
# K/# gehen vor: der Motor MA0202 treibt hier K0102
|
||||||
|
("ANTRIEB KREISEL-K0102 MA0202", "K0102", "Kreisel"),
|
||||||
|
("Separator Str. 1", "", ""),
|
||||||
|
])
|
||||||
|
def test_foerderer_aus_motornummer(text, name, kind):
|
||||||
|
assert conveyor_of(text, motor=True) == name
|
||||||
|
assert conveyor_kind_of(text) == kind
|
||||||
|
# ohne motor=True (Mubea) zaehlt die Motornummer nicht
|
||||||
|
assert conveyor_of(text) == ("K0102" if "K0102" in text else "")
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Gemeinsame Zeichnung (Layout + E-Planung in einer DXF, Dematic)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def dxf_file(tmp_path: Path) -> Path:
|
||||||
|
"""E-Symbole wie in dematic_merge..dxf: Bloecke mit IO/VERW/BEZEICHNUNG."""
|
||||||
|
import ezdxf
|
||||||
|
|
||||||
|
doc = ezdxf.new()
|
||||||
|
tags = ("SPS", "IO", "VERW", "BEZEICHNUNG", "KENNZEICHNUNG", "ID")
|
||||||
|
sym = doc.blocks.new("ILS_720002003-Separator")
|
||||||
|
for i, tag in enumerate(tags):
|
||||||
|
sym.add_attdef(tag, (0, -i * 10))
|
||||||
|
label = doc.blocks.new("ILS_Motorkennzeichnung-Kreisel")
|
||||||
|
label.add_attdef("MOTORKENNZEICHNUNG", (0, 0))
|
||||||
|
msp = doc.modelspace()
|
||||||
|
|
||||||
|
def place(x, y, **attrs):
|
||||||
|
msp.add_blockref("ILS_720002003-Separator", (x, y)).add_auto_attribs(attrs)
|
||||||
|
|
||||||
|
place(100.0, 200.0, SPS="2", IO="BG0532", VERW="In Sep 1",
|
||||||
|
BEZEICHNUNG="in Separator Str. 1", KENNZEICHNUNG="=A02+UC0103-KF1DI1")
|
||||||
|
place(100.0, 200.0, SPS="2", IO="MB0533", VERW="Sep 1",
|
||||||
|
BEZEICHNUNG="Separator Str. 1", KENNZEICHNUNG="=A02+UC0103-KF1DQ1")
|
||||||
|
place(900.0, 50.0, SPS="2", IO="MA0202", VERW="ILS-KR-MA0202",
|
||||||
|
BEZEICHNUNG="Motor KR-MA0202", KENNZEICHNUNG="=A02+UH01-KF1DQ2")
|
||||||
|
# doppelt: der Eintrag mit VERW gewinnt
|
||||||
|
place(5.0, 5.0, IO="BG0777")
|
||||||
|
place(7.0, 7.0, IO="BG0777", VERW="Stau 1")
|
||||||
|
# ohne BMK in IO: kein E-Bauteil
|
||||||
|
place(0.0, 0.0, IO="")
|
||||||
|
msp.add_blockref("ILS_Motorkennzeichnung-Kreisel", (0, 0)).add_auto_attribs(
|
||||||
|
{"MOTORKENNZEICHNUNG": "MA0999"})
|
||||||
|
path = tmp_path / "anlage_merge.dxf"
|
||||||
|
doc.saveas(path)
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
def test_zeichnung_liest_jeden_block_mit_bmk(tmp_path):
|
||||||
|
warnings: list[str] = []
|
||||||
|
devices = {d.bmk: d for d in hw_import.load_dxf(dxf_file(tmp_path), warnings)}
|
||||||
|
assert sorted(devices) == ["BG0532", "BG0777", "MA0202", "MB0533"]
|
||||||
|
s = devices["BG0532"]
|
||||||
|
assert (s.verw, s.bezeichnung, s.pos, s.station) == (
|
||||||
|
"In Sep 1", "in Separator Str. 1", (100.0, 200.0), "KF1DI1")
|
||||||
|
assert (devices["BG0777"].verw, devices["BG0777"].pos) == ("Stau 1", (7.0, 7.0))
|
||||||
|
assert warnings == ["Zeichnung: BG0777 steht 2x in der Zeichnung - verwendet wird ein Eintrag"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_zeichnung_mit_tia_lage_in_layout_koordinaten(tmp_path):
|
||||||
|
doc = build_hardware(tia_file(tmp_path), None, "X", dxf_path=dxf_file(tmp_path),
|
||||||
|
conveyor_from_motor=True)
|
||||||
|
assert doc.layout_coordinates and doc.positions_file == "anlage_merge.dxf"
|
||||||
|
s = doc.by_bmk()["BG0532"]
|
||||||
|
assert s.sources == ["tia", "positions"] and s.constant == "cInBG0532"
|
||||||
|
[drive] = doc.drives
|
||||||
|
# K0102 aus dem TIA-Kommentar geht der Motornummer vor
|
||||||
|
assert (drive.motor, drive.protection, drive.conveyor) == ("MA0202", "FC0202", "K0102")
|
||||||
|
assert HardwareDocument.from_json(doc.to_json()).layout_coordinates
|
||||||
|
|
||||||
|
|
||||||
|
def test_positionsdatei_und_zeichnung_schliessen_sich_aus(tmp_path):
|
||||||
|
with pytest.raises(ValueError, match="nur eine Quelle"):
|
||||||
|
build_hardware(tia_file(tmp_path), positions_file(tmp_path), "X",
|
||||||
|
dxf_path=dxf_file(tmp_path))
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Zusammenfuehren
|
# Zusammenfuehren
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -37,7 +37,8 @@ def _tro(tro_id, type_name, seps, hosts=()):
|
|||||||
separators=list(seps), scanners=[], reason="Test")
|
separators=list(seps), scanners=[], reason="Test")
|
||||||
|
|
||||||
|
|
||||||
def layout() -> TroDocument:
|
def layout(second_vario: bool = False) -> TroDocument:
|
||||||
|
"""``second_vario``: TRO06 ist ein zweiter Vario-TRO auf VF_1 (Dematic VF_6)."""
|
||||||
parts = [_part(i, "Separator", x, y) for i, (x, y) in SEPS.items()]
|
parts = [_part(i, "Separator", x, y) for i, (x, y) in SEPS.items()]
|
||||||
parts += [
|
parts += [
|
||||||
_part("0030", "Ausschleus", 0.0, 2000.0),
|
_part("0030", "Ausschleus", 0.0, 2000.0),
|
||||||
@@ -52,7 +53,8 @@ def layout() -> TroDocument:
|
|||||||
_tro("TRO03", "Vario", ["0003"], ["0020"]),
|
_tro("TRO03", "Vario", ["0003"], ["0020"]),
|
||||||
_tro("TRO04", "1Sep", ["0004"]),
|
_tro("TRO04", "1Sep", ["0004"]),
|
||||||
_tro("TRO05", "1Sep", ["0005"]),
|
_tro("TRO05", "1Sep", ["0005"]),
|
||||||
_tro("TRO06", "1Sep", ["0006"]),
|
_tro("TRO06", "Vario", ["0006"], ["0020"]) if second_vario
|
||||||
|
else _tro("TRO06", "1Sep", ["0006"]),
|
||||||
]
|
]
|
||||||
return TroDocument.from_analysis(Analysis(tros=tros), source="x.csv", parts=parts)
|
return TroDocument.from_analysis(Analysis(tros=tros), source="x.csv", parts=parts)
|
||||||
|
|
||||||
@@ -183,6 +185,37 @@ def test_vario_antrieb_aus_dem_foerderer():
|
|||||||
assert anlage.binding("TRO03", "drive", "senJam").status == "offen"
|
assert anlage.binding("TRO03", "drive", "senJam").status == "offen"
|
||||||
|
|
||||||
|
|
||||||
|
def test_zwei_antriebs_tros_auf_einem_foerderer_sind_widerspruch():
|
||||||
|
"""Jeder Vario-TRO hat seinen eigenen Motor; die Mehrheit je Foerderer
|
||||||
|
kennt nur einen - nicht beiden denselben Motor geben."""
|
||||||
|
anlage = run(doc=layout(second_vario=True))
|
||||||
|
for tro in ("TRO03", "TRO06"):
|
||||||
|
motor = anlage.binding(tro, "drive", "motor")
|
||||||
|
assert (motor.bmk, motor.status) == ("MA0210", "widerspruch")
|
||||||
|
assert "TRO03, TRO06" in motor.note and "von Hand" in motor.note
|
||||||
|
assert anlage.binding(tro, "drive", "motorProtection").status == "widerspruch"
|
||||||
|
|
||||||
|
|
||||||
|
def test_gemeinsame_zeichnung_ohne_lagebezug(tmp_path):
|
||||||
|
"""hw_import --dxf: E-Symbole stehen schon in Layout-Koordinaten - kein
|
||||||
|
Lagebezug wird ermittelt oder abgelegt."""
|
||||||
|
hw = hardware()
|
||||||
|
hw.layout_coordinates = True
|
||||||
|
anlage = run(hw=hw, transform=None, tmp_path=tmp_path)
|
||||||
|
reg = anlage.registration
|
||||||
|
assert (reg.dx, reg.dy, reg.rotation, reg.source) == (0.0, 0.0, 0.0, "zeichnung")
|
||||||
|
assert not list(tmp_path.iterdir())
|
||||||
|
assert anlage.binding("TRO01", "separator1", "sensor").bmk == "BG0101"
|
||||||
|
|
||||||
|
|
||||||
|
def test_dematic_regeln_gleiche_nummer():
|
||||||
|
rules, _ = load_rules(RULES.parent / "e_planning_dematic.ini")
|
||||||
|
by_role = {r.role: r for r in rules}
|
||||||
|
assert by_role["Separator.stopper"].number_from == "Separator.sensor"
|
||||||
|
assert by_role["Separator.stopper"].number_offset == 0
|
||||||
|
assert by_role["Ausschleus.valve"].number_offset == 0
|
||||||
|
|
||||||
|
|
||||||
def test_foerderer_mit_zu_wenig_stimmen_bleibt_offen():
|
def test_foerderer_mit_zu_wenig_stimmen_bleibt_offen():
|
||||||
hw = hardware()
|
hw = hardware()
|
||||||
hw.devices = [d for d in hw.devices if d.bmk != "BG0802"]
|
hw.devices = [d for d in hw.devices if d.bmk != "BG0802"]
|
||||||
|
|||||||
@@ -120,6 +120,20 @@ def test_vario_antrieb_ueber_den_foerderer():
|
|||||||
assert tro.vario_sensors["stSenFinger"].todo
|
assert tro.vario_sensors["stSenFinger"].todo
|
||||||
|
|
||||||
|
|
||||||
|
def test_widerspruch_im_antrieb_erbt_nicht_den_foerderermotor():
|
||||||
|
"""Zwei Vario-TROs auf einem Foerderer: merge_hw_layout fuehrt den Antrieb
|
||||||
|
als Widerspruch - dann nicht doch den Motor des Foerderers einsetzen."""
|
||||||
|
doc = anlage()
|
||||||
|
doc.bindings += [
|
||||||
|
_bind("TRO03", "drive", "motor", "MA0210", "cOutMA0210", status="widerspruch"),
|
||||||
|
_bind("TRO03", "drive", "motorProtection", "FC0210", "cInFC0210",
|
||||||
|
status="widerspruch"),
|
||||||
|
]
|
||||||
|
tro = _by_id(_kontext(doc), "TRO03")
|
||||||
|
assert (tro.vario_motor.value, tro.vario_motor.todo) == ("MA____", True)
|
||||||
|
assert tro.motor_protection.todo
|
||||||
|
|
||||||
|
|
||||||
def test_ohne_hardwareplanung_alles_offen():
|
def test_ohne_hardwareplanung_alles_offen():
|
||||||
doc = anlage()
|
doc = anlage()
|
||||||
ctx = build_context(doc.layout, "anlage_tro.json")
|
ctx = build_context(doc.layout, "anlage_tro.json")
|
||||||
|
|||||||
Reference in New Issue
Block a user