# SCL Generation from Parameterised Templates — Concept **Status:** steps 1–3 and 5 of §11 done — templates for all 7 types with a reference call site (`1Sep`, `1Sep1Swi`, `1Sep2Swi`, `1Sep_SSCC`, `Vario`, `Vario_workStation`, `PinStore_Auto`) usable with `--templates`, default output unchanged; the line decisions (§6) are proposals still to be confirmed by a PLC engineer · **As of:** 2026-10-12 This is the plan for moving `scl_skeleton.py` from hard-coded SCL (Python f-strings in the `emit_*` functions) to **parameterised templates**: the SCL structure for each FB lives in a template file, and Python only supplies the data. It is written so that it fits the existing main path unchanged: ``` export.csv ─► tro_flow --json ─► _tro.json ─► scl_skeleton ─► _FB_Main.scl (dxfmakros (derive) (TroDocument, (render) (skeleton with EXPORTCSV) sps_skel-tro-v4) TODO(E-Planung)) ``` Nothing upstream of `scl_skeleton` changes. The TRO-JSON stays the only input, and the CLI, output file name, `TODO(E-Planung)` marker and exit codes stay compatible. A workflow that calls the chain today keeps working, and gets the new behaviour only by passing the new optional switches. --- ## 1. Why Today the knowledge about each FB call site is copied into Python by hand. That has two problems: 1. **It drifts.** Compared with the real call site of `FB_ILS_MTRO_1Sep1Swi` (TRO107), the generated code already differs: the priority-manager call parameters (`stInPriorityManagerDir2`/`stOutPriorityManagerDir2`, `stInSettings.xPrioDir2`), `xInCarouselRun1` and the jam sensors `stInSenJam2/3` are missing; the separator times differ (600/200 ms instead of 100/50 ms); the parameter order differs. 2. **It is in the wrong place for the people who own it.** A PLC engineer can correct an SCL file; they will not edit Python f-strings. With templates, the SCL structure is SCL again, the reference call sites become testable, and the project-specific names can be plugged in through a config (§7). --- ## 2. Reference material that exists today `doc/TRO_Katalog/scl_templates/` holds two **different kinds** of files. Both stay **read-only** (see `CLAUDE.md`); they are the reference, not the templates we render. | Kind | What it is | Use in this concept | |---|---|---| | **Call site** (43–138 lines) | A real `REGION` from an `FB_Main`: parameter lines plus the instance call, with real values (`TRO107`, `cSep107.1`, `MB3306`) | Source for the parameterised template **and** the expected output of its round-trip test (§8.1) | | **FB source** (800–1550 lines) | The full `FUNCTION_BLOCK` with `VAR_INPUT`/`VAR_OUTPUT`/`VAR_IN_OUT` | The FB **interface**; every parameter a template writes must exist there (§8.2) | Coverage per catalog type (`lib/tro_catalog.py`): | Catalog type | FB | Call site | FB source | Template possible | |---|---|---|---|---| | `1Sep` | `FB_ILS_MTRO_1Sep` | yes (TRO 100) | — | ✅ done | | `1Sep1Swi` | `FB_ILS_MTRO_1Sep1Swi` | yes (TRO 107) | — | ✅ done (first pilot) | | `1Sep2Swi` | `FB_ILS_MTRO_1Sep2Swi` | yes (TRO 267) | — | ✅ done | | `1Sep_SSCC` | `FB_ILS_MTRO_1Sep_SSCC` | yes (TRO 105) | — | ✅ done | | `Vario` | `FB_ILS_MTRO_Vario` | yes (TRO 115) | — | ✅ done | | `PinStore_Auto` | `FB_ILS_MTRO_PinStore_Auto` | yes (TRO 208) | — | ✅ done (outlier, largest) | | `Vario_workStation` | `FB_ILS_MTRO_Vario_workStation` | yes (TRO 421, `_CallSite.scl`) | yes | ✅ done (interface check §8.2 still open) | | `2Sep1Swi` | `FB_ILS_MTRO_2Sep1Swi` | **no** | yes | from interface only — needs a real call site | | `EmptyCarrBuffer` | `FB_EmptyCarrBuffer` | **no** | yes | from interface only — needs a real call site | | `LoadingBoom` | `FB_LoadingBoom_INBOUND` | **no** | yes | from interface only — needs a real call site | | `2Sep2Swi` | `FB_ILS_MTRO_2Sep2Swi` | **no** | **no** | no — FB does not exist yet (Mubea needs it) | So 7 types can be done with a test against real code. 3 can only be checked against the interface until someone supplies a real call site. `2Sep2Swi` stays on the fallback (§9). --- ## 3. Design principles 1. **Templates hold structure, Python holds data.** No SCL text in Python except the file header and the fallback. 2. **Keep the item-based composition.** `doc/HundM/suggestion.md` shows that all `FB_ILS_MTRO_*` share one skeleton and differ only in how many separators, switches, directions and jam areas they repeat. The templates mirror that: shared macros for the repeated blocks, one thin template per FB that arranges them in that FB's order. 3. **Never invent a value.** Anything without a source stays `TODO(E-Planung)`, as it does today. A plausible-looking wrong address is worse than an obvious gap. 4. **Every value has exactly one origin**, visible in the template (§6): layout, naming rule, reference default, E-Planung, or condition. 5. **The TRO-JSON is the contract.** The render context is built only from `model.TroDocument`; templates never read the CSV, the DXF or the catalog directly. 6. **Fail loudly in templates.** An undefined variable is an error (Jinja2 `StrictUndefined`), not an empty string in the SCL. 7. **Additive migration.** The existing `emit_*` code stays as the fallback until every type has a template; with no new switch the output is byte-identical to today. --- ## 4. Files and folders ``` sps_skel/ ├── doc/TRO_Katalog/scl_templates/*.scl reference, read-only (unchanged) ├── lib/ │ ├── scl_skeleton.py CLI + order + context + render (changed) │ ├── scl_context.py NEW: TroDocument -> render context (pydantic) │ ├── scl_naming.py NEW: reads the naming config, resolves names │ └── scl_templates/ NEW: the parameterised templates │ ├── _macros.scl.j2 shared blocks (separator, switch, jam, call, direction) │ ├── FB_ILS_MTRO_1Sep.scl.j2 ✅ │ ├── FB_ILS_MTRO_1Sep1Swi.scl.j2 pilot ✅ │ ├── FB_ILS_MTRO_1Sep2Swi.scl.j2 ✅ │ ├── FB_ILS_MTRO_1Sep_SSCC.scl.j2 ✅ │ ├── FB_ILS_MTRO_Vario.scl.j2 ✅ │ ├── FB_ILS_MTRO_Vario_workStation.scl.j2 ✅ │ └── FB_ILS_MTRO_PinStore_Auto.scl.j2 ✅ (one per FB, named exactly like fbType) ├── cfg/ │ ├── scl_naming.ini NEW: default naming rules (from the reference plant) │ └── _scl_naming.ini NEW, optional: per-project override └── tests/ ├── test_scl_templates.py round trip against the reference call sites ✅ ├── test_scl_skeleton_golden.py whole-file regression on mubea/HundM05 ✅ ├── test_scl_context.py the derivations one by one ✅ └── fixtures/scl/ ├── _tro.json fixed inputs (mubea, HundM05) ├── _FB_Main.scl expected output, generic └── _FB_Main_templates.scl expected output, with --templates ``` The file frame (header, `VAR` list, REGION loop) stays in Python (`render_context`) for now; only the REGIONs come from templates. A `_file.scl.j2` is only worth it once every REGION does. The reference values of a round trip are set in the test itself (`tests/test_scl_templates.py`) instead of a JSON fixture, so new context fields need no fixture update. **Lookup:** the template file name is the TRO's `fbType` from the catalog (`FB_ILS_MTRO_1Sep1Swi` → `FB_ILS_MTRO_1Sep1Swi.scl.j2`). No second mapping table, so the catalog stays the only place that links a type to its FB. **Dependency:** `jinja2 >= 3.1` is added to `requirements.txt`. --- ## 5. Template structure ### 5.1 Jinja2 settings | Setting | Value | Reason | |---|---|---| | `undefined` | `StrictUndefined` | A misspelled variable stops the run instead of writing empty SCL | | `trim_blocks`, `lstrip_blocks` | `True` | `{% for %}` lines leave no blank lines/indent in the SCL | | `keep_trailing_newline` | `True` | Stable files, clean diffs | | `autoescape` | `False` | SCL, not HTML | SCL syntax does not clash with Jinja2: SCL uses `#name`, `{ S7_... }` and `//`, never `{{`, `{%` or `{#`. The one thing to avoid is writing `{#` directly (e.g. `{#TRO…`). ### 5.2 Shared macros (`_macros.scl.j2`) One macro per repeated block. Each takes the TRO context and the block's own data: ```jinja {% macro separator(t, s) %} #{{ t.id }}.stInSeparator{{ s.n }}.Settings.nSeparatorNo := "{{ s.no }}";{{ s.no_note }} #{{ t.id }}.stInSeparator{{ s.n }}.Settings.tDelayToNextItem := "TimeForSepWaiting_Short"; #{{ t.id }}.stInSeparator{{ s.n }}.Settings.tTrailingTime := {{ d.sep_trailing }}; // {{ TODO }}: pruefen #{{ t.id }}.stInSeparator{{ s.n }}.Settings.tHandlingTime := {{ d.sep_handling }}; // {{ TODO }}: pruefen #{{ t.id }}.stInSeparator{{ s.n }}.Settings.tJam := "TimeForSepJam"; #{{ t.id }}.stInSeparator{{ s.n }}.Settings.xPluggedJam := TRUE; #{{ t.id }}.stInSeparator{{ s.n }}.Settings.xPluggedPart := FALSE; #{{ t.id }}.stInSeparator{{ s.n }}.Settings.xPluggedScanner := {{ 'TRUE' if t.has_scanner else 'FALSE' }}; #{{ t.id }}.stInSeparator{{ s.n }}.stSenInSep := "DB_Inputs".Sensors["{{ s.sensor }}"];{{ s.sensor_note }} {% endmacro %} {% macro jam(kind, j) %} arInOutJam{{ kind }}{{ j.n }} := {{ j.db }}.arCarrier, // {{ j.note }} stInOutJam{{ kind }}{{ j.n }} := {{ j.db }}.stData, {% endmacro %} ``` `s.no` is either a resolved name (`cSep107.1`) or the placeholder `cSep_____`, and `s.no_note` is then ` // TODO(E-Planung)` or empty. Python decides both (§7.3); the template only places them. ### 5.3 One template per FB Each FB template is the reference call site with values replaced by variables and repeated parts replaced by loops. Sketch for `FB_ILS_MTRO_1Sep1Swi.scl.j2`: ```jinja {% import "_macros.scl.j2" as m %} REGION {{ t.region_title }} {{ m.comments(t) }} {% for s in t.separators %}{{ m.separator(t, s) }}{% endfor %} {% for w in t.switches %}{{ m.switch(t, w) }}{% endfor %} #{{ t.id }}.stInSettings.xPrioDir2 := {{ t.prio_dir2 }};{{ t.prio_dir2_note }} #{{ t.id }}(stInPriorityManagerDir2 := "DB_Prozessdaten".stPriorityManager.udt_{{ t.id }}.TroIn, stOutPriorityManagerDir2 => "DB_Prozessdaten".stPriorityManager.udt_{{ t.id }}.TroOut, nInMainTroNo := "{{ t.main_tro }}",{{ t.main_tro_note }} xInSftyOk := {{ t.safety }},{{ t.safety_note }} xInAllRdyToStart := TRUE, {% if t.carousel_run %} xInCarouselRun1 := {{ t.carousel_run }}, {% endif %} xInRelease := {{ t.release }}, ... {% for j in t.jam_entries %}{{ m.jam("Entr", j) }}{% endfor %} {% for j in t.jam_exits %}{{ m.jam("Exit", j) }}{% endfor %} stInOutHMI := "DB_Interface_HMI".stTRO.{{ t.id }}, nOutStateLast => "DB_GLOB_TroState".arTroState["{{ t.main_tro }}"], {% for s in t.separators %} xOutStopper{{ s.n }} => "{{ s.stopper }}"{{ "," if not loop.last or t.switches else ");" }}{{ s.stopper_note }} {% endfor %} ...); {{ m.direction(t) }} END_REGION ``` The order of the lines in the template **is** the parameter order of the reference call site. That replaces the order that is hard-coded in `emit_call` today. ### 5.4 File frame and whitespace The file header (source, timestamp, start reason, open-TODO note), `FUNCTION_BLOCK "FB_Main"`, the `VAR` list of all instances and the REGION loop are written by `render_context` in Python. Each REGION is rendered from the TRO's own template and inserted there; TROs without a template get the generic text (§9). With `--templates` the header names which FB types came from a template and which did not. Templates do **not** use Jinja whitespace control (`{%- … -%}`) except inside the one-line macros `todo`/`note`. Instead the renderer drops trailing blanks and collapses runs of blank lines (`SclTemplates.render`). That keeps the templates readable as SCL and the indentation intact. Rule for every template: **each line that contains a placeholder carries the `TODO(E-Planung)` marker** — also the second line of a pair such as `stInOutJamExit … .stData`. `tests/test_scl_templates.py` checks this. --- ## 6. Where each value comes from Every line in a template belongs to exactly one of these classes. The class decides who supplies the value and what happens when it is missing. | Class | Source | Example | If missing | |---|---|---|---| | **L — layout** | TRO-JSON (`TroDocument`) | `#TRO107`, number of separators/switches, jam entries/exits per neighbour, destination list per switch exit, `nTo1Destinations` (single switch) | cannot be missing — the JSON is validated | | **N — naming rule** | naming config (§7) | `cSep107.1`, `cSwi107.1`, `cMainTro107`, `udt_TRO107` | `TODO(E-Planung)` placeholder | | **R — reference default** | the reference call site, written in the template or in `[defaults]` | `tTrailingTime t#100ms`, `tJam "TimeForSepJam"`, `xPluggedJam TRUE` | always present; marked `pruefen` where plant-specific | | **E — E-Planung** | I/O list (not available yet) | `cInBG3306`, `MB3306`, `MA…`, safety zone, `DB_JamArea0102.stJam0102_1` | `TODO(E-Planung)` placeholder | | **C — condition** | topology flag in the context | `xInCarouselRun1` only next to a carousel; `stInSenJam2/3` only with extra jam sensors | line left out, or written as a TODO | Class **C** is the real design work, not the conversion. Each reference call site is one concrete location and contains lines that only exist because of that location. They have to be decided once per template, together with a PLC engineer, and recorded in the template as a comment. For the pilot `1Sep1Swi` (TRO107): | Line in reference | Implemented in the pilot | Context field | Status | |---|---|---|---| | `stInSenJam2`, `stInSenJam3` | one per jam exit, sensor `TODO(E-Planung)` | `jam_exits[].sensor` | to confirm | | `stInSettings.xPrioDir2 := TRUE` | written as `TRUE`, marked `TODO(E-Planung)` (priority is a planning decision) | `prio_dir2` | to confirm | | `stIn/OutPriorityManagerDir2` | always; UDT `udt_____` + TODO until the naming rule `udt_{id}` (step 4) | `priority_udt` | to confirm | | `xInCarouselRun1 := #fbCarousel0102.xOutConveyorFullSpeed` | **always written, marked TODO "nur am Kreisel, sonst Zeile loeschen"**. The proposed condition (a connection of kind `umlauf`) does not work: `tro_flow` emits `bahn`/`normal`/`weiche`/`transfer` in practice, `umlauf` never occurs in mubea or HundM05, so the TRO-JSON cannot tell whether a TRO sits at a carousel. Needs either a carousel flag from `tro_flow` or a manual decision. | `carousel_run` | to confirm | | `xInRelease := TRUE` (generic: `"DB_TRO_TEST".`) | reference value `TRUE` | — (literal) | to confirm | | `stInOutMachineState := #stInOutControlUnitCabinetOF1` | placeholder `#stInOutControlUnitCabinet` + TODO (cabinet is plant-specific) | `machine_state` | to confirm | | separator times `t#100ms` / `t#50ms` | reference values, marked `pruefen`; `tSenFree`/`tSenWait` of the generic path are **not** written (not in the reference) | — (literal) | to confirm | | `nTo1Destinations := 3` (TRO107 has **2** exits) | number of exits, marked `pruefen` — the reference suggests it counts destinations, not exits (§13) | `switches[].to1_destinations` | open | | `stInPriorityManager.xReleaseOk` (generic only) | not written — not in the reference | — | to confirm | The same decisions were made for the other six templates (step 5). Each template's header comment lists them in full; this is the summary of what deviates from "copy the reference line": | Template (reference) | Decisions to confirm | |---|---| | `1Sep` (TRO100) | times 600/200 ms and `tSenFree`/`tSenWait` 1500/4000 ms as in the reference (`pruefen`); `xInCarouselRun` always written as TODO; `xInRelease := "DB_TRO_TEST".` as in the reference | | `Vario` (TRO115) | times 800/300 ms, `xPluggedJam FALSE`, `tSenFree`/`tSenWait` 2000/5000 ms, `tRasterHigh/Low` 5 s (`pruefen`); `stInSettings.xIsBorner := TRUE` marked TODO (meaning unclear); `JamEntr` = the TRO's own entry jam area (TRO-JSON v4: several predecessors merge into it), `JamFinger` = the entry jam area of the first successor; `xInErrorQuit`/`xInAutomatic` open | | `Vario_workStation` (TRO421) | `xPluggedScanner FALSE` although the catalog lists a scanner (it sits at the work station, not the separator — `pruefen`); work-station sensor, release and `xBeladenError` sensor open | | `1Sep2Swi` (TRO267) | separator without times, `xPluggedPart TRUE` + `stSenPart` as in the reference; **switch 2 has no `nSwitchNo` in the reference** — written as a commented-out proposal; `nTo1Destinations` per switch open (split of the exits); `bSepType.%X2 := TRUE` marked `pruefen`; one `xInCarouselRun` per switch; **no `FC_Direction`** at this place in the reference — only the destination list as a comment; no `nOutStateLast` (not in the reference) | | `1Sep_SSCC` (TRO105) | second (SSCC) camera with its own interface structure, end-measurement sensor and HMI key open; `xInRelease` written as `"DB_TRO_TEST".` with a TODO (the reference releases via the WCS connection); `FC_CARR_GLOB_CORRECTION` with `nInStation` open; the WCS telegram region of the reference is only noted as TODO | | `PinStore_Auto` (TRO208) | **number of lines from the layout** (one per storage lane, `nNoOfLines`), not the 19 of the reference; all plant constants (encoder, destinations, stations, thresholds, storage DB, removal handshake) open; pin separator times 0/50 ms (`pruefen`); simulation settings `stSimu` of the reference not written; the FB has one `JamEntr`/`JamExit` — since v4 several feeds merge into the one entry jam area (mubea: 2 feeds → 1 entry); a store with more than one entry or exit jam area (mubea: 2 exits) gets a TODO in the REGION header | New context fields for these templates (`lib/scl_context.py`): Cognex wiring per scanner (`interface`, `tag`, `hw_results`), `part_sensor` per separator, `carriers`/`data` per jam area (handover jams use their own variables), `jam_in`/`jam_out` (same objects as the first entry/exit), `error_quit`, `automatic`, `is_border`, `correction_station`, and the blocks `sscc`, `work_station`, `store` (with one `SclStoreLine` per lane). The generic path does not use them — its golden files are unchanged. Effect on the open-value count with `--templates`: with the pilot only, mubea 282 → 302 and HundM05 323 → 377; with all seven templates mubea → 489 and HundM05 → 475 `TODO(E-Planung)` (HundM05 now comes completely from templates, mubea except the `2Sep2Swi`). Since TRO-JSON v4 (one entry jam area per TRO instead of one per predecessor) it is: mubea 277 generic → 482 with templates, HundM05 316 → 461. More reference lines are written and every open value is marked; the count drops again with the naming rules (step 4). --- ## 7. Naming config ### 7.1 Purpose Some names follow a rule in a given plant (`cSep107.1` = separator 1 of TRO 107). Others do not (`cInBG3306` is whatever E-Planung assigned). The naming config holds the rules, so that: - rule-based names are generated instead of left as TODOs, - a project with a different naming scheme changes a file, not code, - anything without a rule stays `TODO(E-Planung)`, exactly as today. ### 7.2 File and lookup INI, like `connect.ini` and the other files in `%SKEL_CFG%`. Lookup order (later wins): 1. `%SKEL_CFG%/scl_naming.ini` — defaults taken from the reference plant 2. `%SKEL_CFG%/_scl_naming.ini` — per project, `` = stem of the TRO-JSON (`mubea_tro.json` → `mubea_scl_naming.ini`) 3. `--naming FILE` — explicit file A key that is **absent or empty** in all of them means "no rule" → placeholder + `TODO(E-Planung)`. ### 7.3 Syntax ```ini ; scl_naming.ini - Namensregeln fuer das FB_Main-Geruest ; Platzhalter: {id} TRO-ID (TRO107), {nr} Nummernteil (107), {n} laufende Nummer ; des Bauteils im TRO (1, 2 ...), {sep} Separatornummer aus dem Layout [names] separator_no = cSep{nr}.{n} switch_no = cSwi{nr}.{n} main_tro = cMainTro{nr} priority_udt = udt_{id} ; ohne Regel -> TODO(E-Planung) sensor_sep = stopper = switch_valve = safety = jam_area = [defaults] sep_trailing = t#100ms sep_handling = t#50ms release = TRUE ``` - `{nr}` is the numeric part of the ID **as written** in the JSON (`TRO01` → `01`, `TRO107` → `107`); padding is not changed. - `{sep}` is the separator's layout number from `separators[]` (`0040`) — useful if a project names separators after the layout instead of the TRO. - An unknown placeholder in a rule is an error (exit 1), not a silent literal. ### 7.4 Later: E-Planung data When an I/O list exists in machine-readable form, a further section can point to it (e.g. `[eplanung] file = …csv`) and fill class **E** values per separator/switch. That is a separate step; the config format leaves room for it but this concept does not build it. --- ## 8. Tests All under `tests/`, run with `python -m pytest tests/` like the existing ones. ### 8.1 Round trip per template (the main safety net) For each template that has a reference call site (`tests/test_scl_templates.py`): 1. The test builds the context for a TRO with the reference topology and sets the **real** values of the reference (ID `TRO107`, `cSep107.1`, `cInBG3306`, `MB3306`, the real jam DBs, `carousel_run` …). 2. The test renders the template with that context. 3. It compares the result with the `REGION … END_REGION` part of the reference file, **normalised**: whitespace collapsed, comments removed, case kept. If they match, the parameterisation lost nothing: same lines, same order, same values. When someone later replaces a reference file with a newer call site, this test shows exactly where the template must follow. ### 8.2 Interface check (for FBs with source) For `Vario_workStation`, `2Sep1Swi`, `EmptyCarrBuffer`, `LoadingBoom`: parse `VAR_INPUT`/`VAR_OUTPUT`/`VAR_IN_OUT` of the FB source and check that every parameter name the template writes exists there, with the right direction (`:=` for in/in-out, `=>` for out). This covers the types that have no call site. ### 8.3 Golden files for the whole output `tests/test_scl_skeleton_golden.py` renders `mubea_tro.json` and `HundM05_tro.json` (copied into `tests/fixtures/`) and compares with checked-in expected SCL, with the timestamp line masked. - **During migration:** the golden files are today's output, so switching the generator internals without enabling templates must give byte-identical files. - **When a template is switched on:** the diff in the golden file is reviewed once (it is the intended change) and then committed as the new golden file. ### 8.4 Naming and context Unit tests for `scl_naming.py` (placeholders, lookup order, empty = TODO, unknown placeholder = error) and `scl_context.py` (jam numbering, destination lists, flags such as `carousel_run` from connection kinds). --- ## 9. Fallback and errors | Situation | Behaviour | Exit | |---|---|---| | Template exists for the TRO's `fbType` | render template | 0 | | No template (e.g. `2Sep2Swi`) | render with the current `emit_*` code; header lines `// Aus Template : …` / `// Generisch : … (kein Template - von Hand pruefen)` and an entry in the console summary | 0 | | No template **and** `--strict` | stop, write nothing | 1 | | Template uses an undefined variable / syntax error | stop, write nothing, show template file and line | 1 | | Naming rule with unknown placeholder | stop, write nothing | 1 | | Invalid TRO-JSON | unchanged from today | 1 | With `--templates` the console summary gets a `Templates` line (implemented); the `Namensregeln` line follows with step 4. Real output for mubea today: ``` Templates = 4 von 5 Typen (generisch: FB_ILS_MTRO_2Sep2Swi) Offene Stellen = 482 (Marker 'TODO(E-Planung)') ``` --- ## 10. CLI — additive only Existing switches (`--json`, `--out`, `--start`) and defaults are unchanged. | New switch | Argument | Default | Effect | Status | |---|---|---|---|---| | `--templates` | `[DIR]` (optional) | off; without `DIR`: `lib/scl_templates` | Render REGIONs from the FB templates; types without template stay generic | ✅ | | `--strict` | — | off | With `--templates`: exit 1, write nothing, if any FB type of the plant has no template | ✅ | | `--naming` | `FILE` | lookup per §7.2 | Explicit naming config | step 4 | Templates are **opt-in** (`--templates`) until step 6. Without the switch the output is byte-identical to before (golden files). When templates become the default in step 6, a `--no-templates` switch keeps the old path for one release. --- ## 11. Migration plan Each step ends with all tests green and is usable on its own. | Step | Work | Done when | |---|---|---| | 1 ✅ | Golden-file tests on today's output (mubea, HundM05) — `tests/test_scl_skeleton_golden.py` | today's SCL is pinned | | 2 ✅ | `scl_context.py`: build the render context from `TroDocument`; `emit_*` read from it — `tests/test_scl_context.py` | golden files byte-identical (also checked on dematic and showroom, with and without `--start`) | | 3 ✅ | Pilot `FB_ILS_MTRO_1Sep1Swi.scl.j2` + `_macros`; switches `--templates`, `--strict`; class-C table (§6) filled with proposals — **still to be confirmed by a PLC engineer**; round-trip test | round trip against TRO107 passes; only the 1Sep1Swi REGIONs (+ header) differ from the generic golden files | | 4 | `scl_naming.py` + `cfg/scl_naming.ini`; switch `--naming` | naming tests pass; Mubea renders with names instead of TODOs where a rule exists | | 5 ✅ | Remaining call-site templates (`1Sep`, `Vario`, `1Sep2Swi`, `1Sep_SSCC`, `Vario_workStation`, `PinStore_Auto`), each with round trip against its reference (`PinStore_Auto`: the parts the reference abbreviates are left out of the comparison); decisions in §6 — **still to be confirmed by a PLC engineer**. Interface check (§8.2) for `Vario_workStation` still open | every round trip passes; HundM05 fully from templates, mubea except `2Sep2Swi` | | 6 | Templates on by default; new `--no-templates` keeps the old path for one release | golden files updated and reviewed; one import into TIA Portal (as for Mubea) | | 7 | Remove `emit_*` once every type in use has a template (`2Sep2Swi` needs a call site first) | fallback only for types without FB | Docs touched along the way: `doc/Python_Scripts.md` §5b, `doc/Commandlines.md` §2.5, `CLAUDE.md` (roadmap), this file. --- ## 12. What your workflow can rely on For a workflow that runs **export CSV → SCL**: **Calls (unchanged):** ```bat bin\tro_flow.bat --file .csv --json bin\scl_skeleton.bat --json _tro.json [--templates [DIR]] [--strict] [--naming ] ``` `--templates` and `--strict` exist today; `--naming` follows with step 4. **Inputs:** | File | Where | Who writes it | |---|---|---| | `.csv` | `%SKEL_DATA%` | dxfmakros `EXPORTCSV` | | `_tro.json` | `%SKEL_RESULTS%` | `tro_flow --json` (schema `sps_skel-tro-v4`) | | `scl_naming.ini`, `_scl_naming.ini` | `%SKEL_CFG%` | you, once per project (optional) | | `lib/scl_templates/*.scl.j2` | repo | maintained with the PLC engineers | **Outputs:** `%SKEL_RESULTS%/_FB_Main.scl` (same name as today). **Exit codes:** `0` ok (also when some TROs used the fallback) · `1` input, template or naming error, or a missing template with `--strict`. `tro_flow` keeps its own codes (`3` = TRO validation errors → stop the workflow before `scl_skeleton`). **Quality gates a workflow can check:** - exit code of each step; - `Offene Stellen` in the summary (count of `TODO(E-Planung)`), e.g. track it per run; - `Templates = x von y` — any TRO on the fallback needs a manual look; - `grep "TODO(E-Planung)"` in the SCL lists every open value with its line. **What stays manual:** class **E** values (sensor/actuator addresses, safety signals, jam-area DBs) until an E-Planung export exists, and the `pruefen` timings. --- ## 13. Open questions (to clarify with the PLC engineers) 1. The class-C decisions per template (§6) — starting with `1Sep1Swi`. 2. Is the reference naming (`cSep{nr}.{n}`, `cMainTro{nr}`, `udt_{id}`) the standard for new projects, or plant-specific? 3. Separator timings: reference values (100/50 ms) or the current skeleton values (600/200 ms) as the default? 4. `xInRelease`: `TRUE` as in the reference, or the `"DB_TRO_TEST"` test release used today? 5. Real call sites for `2Sep1Swi`, `EmptyCarrBuffer`, `LoadingBoom` — available from another plant? 6. `FB_ILS_MTRO_2Sep2Swi` (Mubea): will it be created in the ILSLib, or should the two transfer points be modelled with existing types? 7. `nTo1Destinations`: TRO107 has 2 exits but the value 3 ("Info noetig fuer HMI"). Does it count the destinations behind the switch rather than its exits? If so, which destinations — the per-exit target lists in the skeleton could supply them. 8. `xInCarouselRun1`: how do we know a TRO sits at a carousel? `tro_flow` would need to mark it (e.g. a carousel neighbour per TRO); today the line is always written as TODO. 9. `1Sep2Swi` (TRO267): switch 2 has no `nSwitchNo` and the REGION has no `FC_Direction` call — omission in the reference, or intended (direction elsewhere)? 10. Meaning of `stInSettings.xIsBorner` (Vario) and `stInSettings.bSepType.%X2` (`1Sep2Swi`) — always `TRUE`, or depending on the location? 11. `PinStore_Auto` with more than one feed/exit (mubea: 2/2): the FB has one `JamEntr`/`JamExit` plus `JamBypass` — how are the others connected? 12. `Vario_workStation`: is `xPluggedScanner FALSE` right in general (scanner at the work station, not at the separator)?