tro_annotate --anlage: Staubereiche in der Zeichnung, --read-jams zurueck
Mit --anlage <csv>_anlage.json zeichnet tro_annotate je Staubereich einen Block JAM_AREA (Layer TRO_JAM): Rechteck mit dem Namen Jam<NNNN>_<k> wie in der SCL, jam_versatz vor dem Separator Richtung Vorgaenger. Attribute CONVEYOR, NUMBER, TRO + ENTRY (stabiler Schluessel), AREA, AUTO. Doppelklick oeffnet den Dialog JAM_EDIT (dxfmakros). --read-jams uebernimmt im Dialog geaenderte Nummern aus der beschrifteten Zeichnung nach cfg/<csv>_jam_names.ini (nur Abweichungen von AUTO); scl_skeleton wendet die Datei mit der Anlagen-JSON an (scl_context: jam_numbers + Korrekturen, load/save_jam_overrides). Dematic: 25 Staubereiche gezeichnet; Rundreise TRO20 -> Jam0054_9 bis in die SCL geprueft. Gemeinsame Zeichnung mit --offset 0,0 beschriften (automatischer Lagebezug waehlte dy +360 mm). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -204,7 +204,10 @@ byte — rewrite them (`SKEL_UPDATE_GOLDEN=1`) only for a reviewed, intended cha
|
|||||||
Switches `--json`, `--out`, `--start` (the last picks an entry point while the plant is a
|
Switches `--json`, `--out`, `--start` (the last picks an entry point while the plant is a
|
||||||
closed loop with no loading/unloading station). See `doc/Python_Scripts.md` §5b.
|
closed loop with no loading/unloading station). See `doc/Python_Scripts.md` §5b.
|
||||||
3. **`lib/tro_annotate.py`** — **viewer only**: draws the same TROs (same `analyse_tros`
|
3. **`lib/tro_annotate.py`** — **viewer only**: draws the same TROs (same `analyse_tros`
|
||||||
call as `tro_flow.py`) into a copy of the DXF for a visual check in BricsCAD.
|
call as `tro_flow.py`) into a copy of the DXF for a visual check in BricsCAD. With
|
||||||
|
`--anlage <csv>_anlage.json` also the jam areas (block `JAM_AREA`, name `Jam<NNNN>_<k>`,
|
||||||
|
double-click → dialog `JAM_EDIT` in dxfmakros); `--read-jams` takes numbers edited there
|
||||||
|
into `cfg/<csv>_jam_names.ini`, which `scl_skeleton` applies.
|
||||||
4. **`lib/tro_extract.py`** — **parked**: reads the annotated DXF back into the old
|
4. **`lib/tro_extract.py`** — **parked**: reads the annotated DXF back into the old
|
||||||
`sps_skel-tro-extract-v1` JSON, which `scl_skeleton.py` no longer accepts. Not deleted, not
|
`sps_skel-tro-extract-v1` JSON, which `scl_skeleton.py` no longer accepts. Not deleted, not
|
||||||
extended; if drawing edits (`TRO_INSERT`/`TRO_EDIT`) are ever needed again, it should
|
extended; if drawing edits (`TRO_INSERT`/`TRO_EDIT`) are ever needed again, it should
|
||||||
|
|||||||
@@ -178,6 +178,11 @@ default_text = 220.0
|
|||||||
; Restfehler des Lagebezugs, ab dem er abgelehnt wird.
|
; Restfehler des Lagebezugs, ab dem er abgelehnt wird.
|
||||||
default_max_residual = 600.0
|
default_max_residual = 600.0
|
||||||
|
|
||||||
|
; Staubereich (--anlage): das Rechteck sitzt so weit vor seinem Separator,
|
||||||
|
; Richtung Vorgaenger-TRO (gegen den Fluss) - er ist die Strecke, auf der
|
||||||
|
; die Traeger vor dem Separator warten.
|
||||||
|
jam_versatz = 700.0
|
||||||
|
|
||||||
|
|
||||||
[merge_hw_layout]
|
[merge_hw_layout]
|
||||||
; Zulaessige Abweichung vom Sollabstand (soll_mm), wenn eine Regel in
|
; Zulaessige Abweichung vom Sollabstand (soll_mm), wenn eine Regel in
|
||||||
|
|||||||
@@ -456,6 +456,26 @@ overridden.
|
|||||||
| `--re-register` | — | off | Re-derive the coordinate offset even if a verified one is already cached in `%SKEL_CFG%`. |
|
| `--re-register` | — | off | Re-derive the coordinate offset even if a verified one is already cached in `%SKEL_CFG%`. |
|
||||||
| `--check` | — | off | Dry run: only analyze and report, write no output file. |
|
| `--check` | — | off | Dry run: only analyze and report, write no output file. |
|
||||||
| `--emit-lisp` | — | off | Regenerate `cad/tro_types.lsp` (the type list for the `TRO_INSERT`/`TRO_EDIT` dialogs) from `tro_catalog.py` and exit — no `--dxf` needed. Written under `%DXFM_LISP%` if set (next to the `dxfmakros` SSG_LIB modules that provide the dialogs), otherwise under `<SPS_SKEL>/cad`. |
|
| `--emit-lisp` | — | off | Regenerate `cad/tro_types.lsp` (the type list for the `TRO_INSERT`/`TRO_EDIT` dialogs) from `tro_catalog.py` and exit — no `--dxf` needed. Written under `%DXFM_LISP%` if set (next to the `dxfmakros` SSG_LIB modules that provide the dialogs), otherwise under `<SPS_SKEL>/cad`. |
|
||||||
|
| `--anlage` | `NAME` | — | Plant JSON (`<csv>_anlage.json` from `merge_hw_layout`, in `%SKEL_RESULTS%` or a path): also draw the **jam areas** (see below). Must belong to the same CSV. |
|
||||||
|
| `--read-jams` | `[DXF]` | `<csv>_annotated.dxf` | Read the jam-area numbers edited in BricsCAD back from the annotated drawing (saved as DXF) into `%SKEL_CFG%\<csv>_jam_names.ini`, then exit — no `--dxf` needed. |
|
||||||
|
|
||||||
|
**Jam areas (`--anlage`).** One block `JAM_AREA` per jam area of the plant
|
||||||
|
JSON on layer `TRO_JAM`: a small rectangle with the name inside
|
||||||
|
(`Jam0054_3`, the same name the SCL uses — `scl_context.jam_numbers` plus the
|
||||||
|
corrections file). It sits `jam_versatz` mm (`cfg/parameter.ini
|
||||||
|
[tro_annotate]`) in front of its separator, towards the predecessor TRO,
|
||||||
|
where the carriers wait. Hidden attributes: `CONVEYOR` (NNNN), `NUMBER` (k),
|
||||||
|
`TRO` + `ENTRY` (the stable key, `scl_context.jam_key`), `AREA` (J0039) and
|
||||||
|
`AUTO` (the derived NNNN_k). A jam area without a conveyor shows
|
||||||
|
`Jam????_?`. Double-click opens the dialog **`JAM_EDIT`** (dxfmakros:
|
||||||
|
`Lisp/Jam_Edit.lsp`, `dcl/jam_edit.dcl`, dispatched by `SSG_BLOCKEDIT` on
|
||||||
|
`JAM_*`): conveyor and running number, a button to go back to the derived
|
||||||
|
value, and a warning if the name already exists. The dialog only changes the
|
||||||
|
drawing; `--read-jams` takes every block whose `CONVEYOR_NUMBER` differs from
|
||||||
|
`AUTO` into `<csv>_jam_names.ini` (`[jam] TRO17.1 = 0054_3`) and drops entries
|
||||||
|
that are back at `AUTO`. `scl_skeleton` with a plant JSON reads that file, and
|
||||||
|
the next `--anlage` run draws it, so drawing, file and SCL agree. Invalid
|
||||||
|
values (conveyor not four digits, number < 1) are reported and skipped.
|
||||||
|
|
||||||
Layers always carry the prefix `TRO_`. Like `tro_flow.py`, it never reads
|
Layers always carry the prefix `TRO_`. Like `tro_flow.py`, it never reads
|
||||||
`cfg/tro_overrides.ini`, so the markers always match the flow diagram.
|
`cfg/tro_overrides.ini`, so the markers always match the flow diagram.
|
||||||
@@ -463,6 +483,12 @@ Layers always carry the prefix `TRO_`. Like `tro_flow.py`, it never reads
|
|||||||
**Output** (in `%SKEL_RESULTS%`, unless `--check`): `<name>_annotated.dxf`,
|
**Output** (in `%SKEL_RESULTS%`, unless `--check`): `<name>_annotated.dxf`,
|
||||||
`<name>_registration.json` (the coordinate transform used, with its proof).
|
`<name>_registration.json` (the coordinate transform used, with its proof).
|
||||||
|
|
||||||
|
**Merged drawing (Dematic).** The automatic registration only sees the
|
||||||
|
top-level 3D blocks and their insert points, which sit up to ~360 mm away
|
||||||
|
from the CSV positions; on `dematic_merge..dxf` it chose a wrong dy = +360 mm
|
||||||
|
(18/59 points). CSV and merged drawing share coordinates, so annotate with
|
||||||
|
`--offset 0,0`.
|
||||||
|
|
||||||
**Exit codes:** `0` ok · `1` input/CLI error · `4` coordinate registration
|
**Exit codes:** `0` ok · `1` input/CLI error · `4` coordinate registration
|
||||||
could not be proven (or its residual error exceeds 600 mm) — resolve with
|
could not be proven (or its residual error exceeds 600 mm) — resolve with
|
||||||
`--offset`.
|
`--offset`.
|
||||||
|
|||||||
+96
-8
@@ -33,9 +33,11 @@ Wert ersetzen, Merker loeschen.
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import configparser
|
||||||
import re
|
import re
|
||||||
from collections import deque
|
from collections import deque
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from pydantic import BaseModel, ConfigDict, Field
|
from pydantic import BaseModel, ConfigDict, Field
|
||||||
@@ -64,7 +66,13 @@ __all__ = [
|
|||||||
"HwSource",
|
"HwSource",
|
||||||
"build_context",
|
"build_context",
|
||||||
"flow_order",
|
"flow_order",
|
||||||
|
"JAM_NAMES_SUFFIX",
|
||||||
|
"jam_key",
|
||||||
|
"jam_label",
|
||||||
"jam_names",
|
"jam_names",
|
||||||
|
"jam_numbers",
|
||||||
|
"load_jam_overrides",
|
||||||
|
"save_jam_overrides",
|
||||||
"reachable_via",
|
"reachable_via",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -631,10 +639,88 @@ def _jam_db(area: JamArea | None, hw: HwSource,
|
|||||||
return (f'"DB_JamArea{nr}".stJam{nr}_____' if nr else _JAM_DB), False
|
return (f'"DB_JamArea{nr}".stJam{nr}_____' if nr else _JAM_DB), False
|
||||||
|
|
||||||
|
|
||||||
def jam_names(document: TroDocument, geordnet: list[TroBase],
|
JAM_NAMES_SUFFIX = "_jam_names.ini"
|
||||||
hw: HwSource) -> dict[str, str]:
|
# Wert in der Korrekturdatei: Foerderer und laufende Nummer, "0054_3"
|
||||||
|
_JAM_WERT = re.compile(r"^(\d{4})_(\d+)$")
|
||||||
|
|
||||||
|
|
||||||
|
def jam_key(area: JamArea) -> str:
|
||||||
|
"""Stabiler Schluessel eines Staubereichs: TRO-ID + Eingang ("TRO17.1").
|
||||||
|
|
||||||
|
Die Kennung J<Separator> folgt der TeileId und aendert sich mit jedem
|
||||||
|
CSV-Export; die TRO-ID bleibt (cfg/<csv>_tro_ids.ini).
|
||||||
|
"""
|
||||||
|
return f"{area.tro}.{area.entry}"
|
||||||
|
|
||||||
|
|
||||||
|
def jam_label(nr: str, k: int) -> str:
|
||||||
|
"""Kurzname fuer Zeichnung und Dialog: Jam0054_3."""
|
||||||
|
return f"Jam{nr}_{k}"
|
||||||
|
|
||||||
|
|
||||||
|
def load_jam_overrides(path: Path) -> dict[str, tuple[str, int]]:
|
||||||
|
"""Korrekturen der Staubereichs-Nummern aus ``<csv>_jam_names.ini``.
|
||||||
|
|
||||||
|
Abschnitt [jam], je Zeile ``<TRO-ID>.<Eingang> = <NNNN>_<k>``
|
||||||
|
(``TRO17.1 = 0054_3``). Geschrieben von tro_annotate --read-jams aus den
|
||||||
|
im Dialog JAM_EDIT geaenderten Staubereichen; von Hand pflegbar.
|
||||||
|
Fehlt die Datei, gibt es keine Korrekturen.
|
||||||
|
"""
|
||||||
|
if not path.is_file():
|
||||||
|
return {}
|
||||||
|
parser = configparser.ConfigParser(inline_comment_prefixes=(";", "#"),
|
||||||
|
interpolation=None)
|
||||||
|
parser.optionxform = str
|
||||||
|
with path.open(encoding="utf-8") as fh:
|
||||||
|
parser.read_file(fh)
|
||||||
|
result: dict[str, tuple[str, int]] = {}
|
||||||
|
if parser.has_section("jam"):
|
||||||
|
for key, value in parser["jam"].items():
|
||||||
|
m = _JAM_WERT.match(value.strip())
|
||||||
|
if not m:
|
||||||
|
raise ValueError(f"{path.name} [jam] {key} = {value!r}: erwartet "
|
||||||
|
f"<NNNN>_<k>, z. B. 0054_3")
|
||||||
|
result[key.strip()] = (m.group(1), int(m.group(2)))
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def save_jam_overrides(path: Path, overrides: dict[str, tuple[str, int]]) -> None:
|
||||||
|
"""Korrekturdatei neu schreiben (sortiert nach TRO-ID); leer = Datei weg."""
|
||||||
|
if not overrides:
|
||||||
|
if path.is_file():
|
||||||
|
path.unlink()
|
||||||
|
return
|
||||||
|
zeilen = [
|
||||||
|
"; Staubereichs-Nummern, die von der Ableitung abweichen",
|
||||||
|
"; (scl_context.jam_names). Geschrieben von tro_annotate --read-jams aus",
|
||||||
|
"; dem Dialog JAM_EDIT; von Hand pflegbar.",
|
||||||
|
"; <TRO-ID>.<Eingang> = <Foerderer NNNN>_<laufende Nummer k>",
|
||||||
|
"",
|
||||||
|
"[jam]",
|
||||||
|
]
|
||||||
|
zeilen += [f"{key} = {nr}_{k}" for key, (nr, k) in sorted(overrides.items())]
|
||||||
|
path.write_text("\n".join(zeilen) + "\n", encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def jam_names(document: TroDocument, geordnet: list[TroBase], hw: HwSource,
|
||||||
|
overrides: dict[str, tuple[str, int]] | None = None) -> dict[str, str]:
|
||||||
"""Staubereichs-Namen je Kennung (J0048 -> '"DB_JamArea0042".stJam0042_1').
|
"""Staubereichs-Namen je Kennung (J0048 -> '"DB_JamArea0042".stJam0042_1').
|
||||||
|
|
||||||
|
Die abgeleiteten Nummern (jam_numbers), ersetzt durch ``overrides``
|
||||||
|
(load_jam_overrides) fuer die dort genannten Staubereiche.
|
||||||
|
"""
|
||||||
|
nummern = jam_numbers(document, geordnet, hw)
|
||||||
|
for area in document.jam_areas_or_derived():
|
||||||
|
if overrides and jam_key(area) in overrides:
|
||||||
|
nummern[area.jam_id] = overrides[jam_key(area)]
|
||||||
|
return {jam_id: f'"DB_JamArea{nr}".stJam{nr}_{k}'
|
||||||
|
for jam_id, (nr, k) in nummern.items()}
|
||||||
|
|
||||||
|
|
||||||
|
def jam_numbers(document: TroDocument, geordnet: list[TroBase],
|
||||||
|
hw: HwSource) -> dict[str, tuple[str, int]]:
|
||||||
|
"""Abgeleitete Staubereichs-Nummern je Kennung (J0048 -> ("0042", 1)).
|
||||||
|
|
||||||
Regel der SPS-Programmierer (Dematic, 2026-10-22): <NNNN> ist der
|
Regel der SPS-Programmierer (Dematic, 2026-10-22): <NNNN> ist der
|
||||||
Foerderer, auf dem der Staubereich liegt (Nummer des Kreisel- bzw.
|
Foerderer, auf dem der Staubereich liegt (Nummer des Kreisel- bzw.
|
||||||
Vario-Motors), <k> zaehlt auf diesem Foerderer in Fliessrichtung hoch.
|
Vario-Motors), <k> zaehlt auf diesem Foerderer in Fliessrichtung hoch.
|
||||||
@@ -706,11 +792,11 @@ def jam_names(document: TroDocument, geordnet: list[TroBase],
|
|||||||
if nr:
|
if nr:
|
||||||
je_foerderer.setdefault(nr, []).append(area)
|
je_foerderer.setdefault(nr, []).append(area)
|
||||||
nachfolger = {t.tro_id: list(t.successors) for t in geordnet}
|
nachfolger = {t.tro_id: list(t.successors) for t in geordnet}
|
||||||
namen: dict[str, str] = {}
|
nummern: dict[str, tuple[str, int]] = {}
|
||||||
for nr, liste in je_foerderer.items():
|
for nr, liste in je_foerderer.items():
|
||||||
for k, area in enumerate(_entlang(liste, nachfolger), start=1):
|
for k, area in enumerate(_entlang(liste, nachfolger), start=1):
|
||||||
namen[area.jam_id] = f'"DB_JamArea{nr}".stJam{nr}_{k}'
|
nummern[area.jam_id] = (nr, k)
|
||||||
return namen
|
return nummern
|
||||||
|
|
||||||
|
|
||||||
def _entlang(areas: list[JamArea], nachfolger: dict[str, list[str]]) -> list[JamArea]:
|
def _entlang(areas: list[JamArea], nachfolger: dict[str, list[str]]) -> list[JamArea]:
|
||||||
@@ -908,13 +994,15 @@ def _tro_context(tro: TroBase, alle: dict[str, TroBase],
|
|||||||
def build_context(document: TroDocument, source: str, start_id: str = "",
|
def build_context(document: TroDocument, source: str, start_id: str = "",
|
||||||
created: str | None = None,
|
created: str | None = None,
|
||||||
eplan: EplanDocument | None = None,
|
eplan: EplanDocument | None = None,
|
||||||
anlage: AnlageDocument | None = None) -> SclFile:
|
anlage: AnlageDocument | None = None,
|
||||||
|
jam_overrides: dict[str, tuple[str, int]] | None = None) -> SclFile:
|
||||||
"""TroDocument -> SclFile (TROs in Fliessrichtung).
|
"""TroDocument -> SclFile (TROs in Fliessrichtung).
|
||||||
|
|
||||||
anlage: Anlagen-JSON (merge_hw_layout) - ihr Layout ist ``document``,
|
anlage: Anlagen-JSON (merge_hw_layout) - ihr Layout ist ``document``,
|
||||||
ihre Zuordnungen liefern die Namen. eplan: alte Zuordnung Layout ->
|
ihre Zuordnungen liefern die Namen. eplan: alte Zuordnung Layout ->
|
||||||
E-Planung (e_planning.py), nur ohne ``anlage``. Ohne beide bleiben alle
|
E-Planung (e_planning.py), nur ohne ``anlage``. Ohne beide bleiben alle
|
||||||
Namen offen und die Ausgabe ist die bisherige.
|
Namen offen und die Ausgabe ist die bisherige. jam_overrides: Korrekturen
|
||||||
|
der Staubereichs-Nummern (load_jam_overrides).
|
||||||
"""
|
"""
|
||||||
if anlage is not None and eplan is not None:
|
if anlage is not None and eplan is not None:
|
||||||
raise ValueError("anlage und eplan schliessen sich aus")
|
raise ValueError("anlage und eplan schliessen sich aus")
|
||||||
@@ -924,7 +1012,7 @@ def build_context(document: TroDocument, source: str, start_id: str = "",
|
|||||||
for area in document.jam_areas_or_derived():
|
for area in document.jam_areas_or_derived():
|
||||||
bereiche.setdefault(area.tro, []).append(area)
|
bereiche.setdefault(area.tro, []).append(area)
|
||||||
geordnet, startgrund = flow_order(document.tros, start_id)
|
geordnet, startgrund = flow_order(document.tros, start_id)
|
||||||
namen = jam_names(document, geordnet, hw)
|
namen = jam_names(document, geordnet, hw, jam_overrides)
|
||||||
return SclFile(
|
return SclFile(
|
||||||
source=source,
|
source=source,
|
||||||
created=created or datetime.now().strftime("%Y-%m-%d %H:%M"),
|
created=created or datetime.now().strftime("%Y-%m-%d %H:%M"),
|
||||||
|
|||||||
+22
-2
@@ -43,6 +43,7 @@ Aufruf ueber bin/scl_skeleton.bat bzw. bin/scl_skeleton.sh.
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import configparser
|
||||||
import json
|
import json
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -60,7 +61,15 @@ from e_planning import EPLAN_SCHEMA, EplanDocument
|
|||||||
from hw_import import HardwareDocument
|
from hw_import import HardwareDocument
|
||||||
from material_flow import env_dir
|
from material_flow import env_dir
|
||||||
from model import ANLAGE_SCHEMA, SCHEMA, AnlageDocument, TroDocument
|
from model import ANLAGE_SCHEMA, SCHEMA, AnlageDocument, TroDocument
|
||||||
from scl_context import TODO, SclFile, SclName, SclTro, build_context
|
from scl_context import (
|
||||||
|
JAM_NAMES_SUFFIX,
|
||||||
|
TODO,
|
||||||
|
SclFile,
|
||||||
|
SclName,
|
||||||
|
SclTro,
|
||||||
|
build_context,
|
||||||
|
load_jam_overrides,
|
||||||
|
)
|
||||||
|
|
||||||
# Parametrisierte Aufrufstellen je FB (doc/SCL_Templates.md); Dateiname = fbType.
|
# Parametrisierte Aufrufstellen je FB (doc/SCL_Templates.md); Dateiname = fbType.
|
||||||
TEMPLATE_DIR = Path(__file__).resolve().parent / "scl_templates"
|
TEMPLATE_DIR = Path(__file__).resolve().parent / "scl_templates"
|
||||||
@@ -466,8 +475,17 @@ def main(argv: list[str] | None = None) -> int:
|
|||||||
f"TRO-JSON zu '{document.source}'.", file=sys.stderr)
|
f"TRO-JSON zu '{document.source}'.", file=sys.stderr)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
# Staubereichs-Nummern, im Dialog JAM_EDIT geaendert und mit
|
||||||
|
# tro_annotate --read-jams uebernommen (nur mit Anlagen-JSON wirksam)
|
||||||
|
jam_file = env_dir("SKEL_CFG", "cfg") / f"{Path(document.source).stem}{JAM_NAMES_SUFFIX}"
|
||||||
|
try:
|
||||||
|
jam_overrides = load_jam_overrides(jam_file) if anlage is not None else {}
|
||||||
|
except (OSError, ValueError, configparser.Error) as exc:
|
||||||
|
print(f"FEHLER: Staubereichs-Korrekturen nicht lesbar: {exc}", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
context = build_context(document, json_file.name, args.start, eplan=eplan,
|
context = build_context(document, json_file.name, args.start, eplan=eplan,
|
||||||
anlage=anlage)
|
anlage=anlage, jam_overrides=jam_overrides)
|
||||||
mit, ohne = template_usage(context, templates)
|
mit, ohne = template_usage(context, templates)
|
||||||
if args.strict and ohne:
|
if args.strict and ohne:
|
||||||
print(f"FEHLER: kein Template fuer {', '.join(ohne)} (--strict) - "
|
print(f"FEHLER: kein Template fuer {', '.join(ohne)} (--strict) - "
|
||||||
@@ -530,6 +548,8 @@ def main(argv: list[str] | None = None) -> int:
|
|||||||
print(f"Eingesetzt = {len(known_names(context))} Namen aus der Anlagen-JSON")
|
print(f"Eingesetzt = {len(known_names(context))} Namen aus der Anlagen-JSON")
|
||||||
if hw_file is not None:
|
if hw_file is not None:
|
||||||
print(f"Gegenpruefung = {len(unbekannt)} unbekannte Namen ({hw_file.name})")
|
print(f"Gegenpruefung = {len(unbekannt)} unbekannte Namen ({hw_file.name})")
|
||||||
|
if jam_overrides:
|
||||||
|
print(f"Staubereiche = {len(jam_overrides)} Korrekturen aus {jam_file}")
|
||||||
print(f"Offene Stellen = {offen} (Marker '{TODO}')")
|
print(f"Offene Stellen = {offen} (Marker '{TODO}')")
|
||||||
print(f"SCL = {out_file}")
|
print(f"SCL = {out_file}")
|
||||||
print("================================================================")
|
print("================================================================")
|
||||||
|
|||||||
+256
-6
@@ -85,6 +85,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import collections
|
import collections
|
||||||
|
import configparser
|
||||||
import csv as csv_module
|
import csv as csv_module
|
||||||
import fnmatch
|
import fnmatch
|
||||||
import json
|
import json
|
||||||
@@ -120,6 +121,17 @@ from tro_flow import (
|
|||||||
resolve_ids_file,
|
resolve_ids_file,
|
||||||
)
|
)
|
||||||
from tro_ids import TroIds
|
from tro_ids import TroIds
|
||||||
|
from model import ANLAGE_SCHEMA, AnlageDocument, JamArea
|
||||||
|
from scl_context import (
|
||||||
|
JAM_NAMES_SUFFIX,
|
||||||
|
HwSource,
|
||||||
|
flow_order,
|
||||||
|
jam_key,
|
||||||
|
jam_label,
|
||||||
|
jam_numbers,
|
||||||
|
load_jam_overrides,
|
||||||
|
save_jam_overrides,
|
||||||
|
)
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Konstanten
|
# Konstanten
|
||||||
@@ -131,12 +143,18 @@ BLOCK_PREFIX = "TRO_SYM_"
|
|||||||
# Verbindungsblock. Der Name beginnt mit CONNECTION_, damit der Dispatcher
|
# Verbindungsblock. Der Name beginnt mit CONNECTION_, damit der Dispatcher
|
||||||
# SSG_BLOCKEDIT in dxfmakros ihn ueber "CONNECTION_*" erkennt.
|
# SSG_BLOCKEDIT in dxfmakros ihn ueber "CONNECTION_*" erkennt.
|
||||||
CONNECTION_BLOCK = "CONNECTION_ARROW"
|
CONNECTION_BLOCK = "CONNECTION_ARROW"
|
||||||
|
# Staubereich (--anlage). Der Name beginnt mit JAM_, damit SSG_BLOCKEDIT ihn
|
||||||
|
# ueber "JAM_*" an den Dialog JAM_EDIT (dxfmakros/Lisp/Jam_Edit.lsp) gibt.
|
||||||
|
JAM_BLOCK = "JAM_AREA"
|
||||||
|
# Anzeige eines Staubereichs ohne Foerderer (Nummer nicht ableitbar)
|
||||||
|
JAM_OFFEN = "Jam????_?"
|
||||||
|
|
||||||
# Zeichnungsgroessen und Restfehler-Grenze des Lagebezugs aus cfg/parameter.ini,
|
# Zeichnungsgroessen und Restfehler-Grenze des Lagebezugs aus cfg/parameter.ini,
|
||||||
# Abschnitt [tro_annotate] (lib/parameter.py)
|
# Abschnitt [tro_annotate] (lib/parameter.py)
|
||||||
DEFAULT_MARKER = get_float("tro_annotate", "default_marker")
|
DEFAULT_MARKER = get_float("tro_annotate", "default_marker")
|
||||||
DEFAULT_TEXT = get_float("tro_annotate", "default_text")
|
DEFAULT_TEXT = get_float("tro_annotate", "default_text")
|
||||||
DEFAULT_MAX_RESIDUAL = get_float("tro_annotate", "default_max_residual")
|
DEFAULT_MAX_RESIDUAL = get_float("tro_annotate", "default_max_residual")
|
||||||
|
JAM_VERSATZ = get_float("tro_annotate", "jam_versatz")
|
||||||
|
|
||||||
|
|
||||||
def hex_to_rgb(value: str) -> tuple[int, int, int]:
|
def hex_to_rgb(value: str) -> tuple[int, int, int]:
|
||||||
@@ -335,6 +353,7 @@ def build_layers(doc, prefix: str) -> dict[str, str]:
|
|||||||
("weiche", "#cc8800"),
|
("weiche", "#cc8800"),
|
||||||
("eingang", STATION_FARBE[BOUNDARY_ENTRY]),
|
("eingang", STATION_FARBE[BOUNDARY_ENTRY]),
|
||||||
("ausgang", STATION_FARBE[BOUNDARY_EXIT]),
|
("ausgang", STATION_FARBE[BOUNDARY_EXIT]),
|
||||||
|
("jam", "#7030a0"),
|
||||||
("legend", "#808080")):
|
("legend", "#808080")):
|
||||||
name = f"{prefix}{key.upper()}"
|
name = f"{prefix}{key.upper()}"
|
||||||
names[key] = name
|
names[key] = name
|
||||||
@@ -702,6 +721,150 @@ def draw_connections(
|
|||||||
return drawn
|
return drawn
|
||||||
|
|
||||||
|
|
||||||
|
def build_jam_symbol(doc, text: float) -> str:
|
||||||
|
"""
|
||||||
|
Blockdefinition fuer einen Staubereich: Rechteck, der Name mittig darin.
|
||||||
|
|
||||||
|
Attribute:
|
||||||
|
NAME sichtbar - Jam0054_3 (Foerderer + laufende Nummer)
|
||||||
|
CONVEYOR unsichtbar - Foerderer NNNN (Kreisel-/Vario-Motor), im Dialog
|
||||||
|
NUMBER unsichtbar - laufende Nummer k auf dem Foerderer, im Dialog
|
||||||
|
TRO unsichtbar - TRO, in den der Staubereich fuehrt (TRO17)
|
||||||
|
ENTRY unsichtbar - Eingang des TRO (1, bei 2Sep-Typen auch 2)
|
||||||
|
AREA unsichtbar - Kennung der TRO-JSON (J0039)
|
||||||
|
AUTO unsichtbar - abgeleitete Nummer NNNN_k; --read-jams schreibt
|
||||||
|
nur, was davon abweicht
|
||||||
|
TRO + ENTRY ist der stabile Schluessel (scl_context.jam_key).
|
||||||
|
"""
|
||||||
|
if JAM_BLOCK in doc.blocks:
|
||||||
|
return JAM_BLOCK
|
||||||
|
blk = doc.blocks.new(name=JAM_BLOCK)
|
||||||
|
w, h = text * 4.2, text * 1.2
|
||||||
|
blk.add_lwpolyline([(-w, -h), (w, -h), (w, h), (-w, h)], close=True,
|
||||||
|
dxfattribs={"color": 256})
|
||||||
|
name = blk.add_attdef("NAME", insert=(0, 0), height=text,
|
||||||
|
dxfattribs={"style": "Standard", "color": 256,
|
||||||
|
"text": JAM_OFFEN})
|
||||||
|
name.set_placement((0, 0), align=TextEntityAlignment.MIDDLE_CENTER)
|
||||||
|
offset = -h - text
|
||||||
|
for tag_name in ("CONVEYOR", "NUMBER", "TRO", "ENTRY", "AREA", "AUTO"):
|
||||||
|
blk.add_attdef(tag_name, insert=(-w, offset), height=text * 0.7,
|
||||||
|
dxfattribs={"style": "Standard", "color": 256, "text": "",
|
||||||
|
"invisible": 1})
|
||||||
|
offset -= text
|
||||||
|
return JAM_BLOCK
|
||||||
|
|
||||||
|
|
||||||
|
def _jam_position(area: JamArea, anlage: AnlageDocument) -> tuple[float, float] | None:
|
||||||
|
"""Lage eines Staubereichs (CSV-Koordinaten): vor seinem Separator.
|
||||||
|
|
||||||
|
Ausgangspunkt ist der Separator (sonst die TRO-Position); von dort
|
||||||
|
JAM_VERSATZ in Richtung des ersten Vorgaenger-TRO - gegen den Fluss, wo
|
||||||
|
die Traeger warten. Liegt der Vorgaenger naeher, nur die halbe Strecke.
|
||||||
|
"""
|
||||||
|
layout = anlage.layout
|
||||||
|
part = layout.part(area.separator) if area.separator else None
|
||||||
|
if part is not None and part.position is not None:
|
||||||
|
x, y = part.position[0], part.position[1]
|
||||||
|
elif area.position is not None:
|
||||||
|
x, y = area.position[0], area.position[1]
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
for vor in area.predecessors:
|
||||||
|
tro = layout.by_id(vor)
|
||||||
|
if tro is None or not tro.position:
|
||||||
|
continue
|
||||||
|
vx, vy = tro.position[0] - x, tro.position[1] - y
|
||||||
|
dist = math.hypot(vx, vy)
|
||||||
|
if dist > 1e-6:
|
||||||
|
schritt = min(JAM_VERSATZ, dist / 2)
|
||||||
|
return x + vx / dist * schritt, y + vy / dist * schritt
|
||||||
|
return x, y
|
||||||
|
|
||||||
|
|
||||||
|
def place_jams(
|
||||||
|
msp, doc, anlage: AnlageDocument, overrides: dict[str, tuple[str, int]],
|
||||||
|
transform: Transform, layers: dict[str, str], text: float, warnings: list[str],
|
||||||
|
) -> int:
|
||||||
|
"""
|
||||||
|
Je Staubereich der Anlagen-JSON einen JAM_AREA-Block einfuegen.
|
||||||
|
|
||||||
|
Die Nummern sind dieselben wie in der SCL (scl_context.jam_numbers, dann
|
||||||
|
die Korrekturen aus <csv>_jam_names.ini). Ein Staubereich ohne Foerderer
|
||||||
|
zeigt Jam????_? - im Dialog nachtragen.
|
||||||
|
"""
|
||||||
|
layout = anlage.layout
|
||||||
|
geordnet, _grund = flow_order(layout.tros)
|
||||||
|
automatisch = jam_numbers(layout, geordnet, HwSource(anlage=anlage))
|
||||||
|
block = build_jam_symbol(doc, text)
|
||||||
|
gezeichnet = 0
|
||||||
|
for area in layout.jam_areas_or_derived():
|
||||||
|
lage = _jam_position(area, anlage)
|
||||||
|
if lage is None:
|
||||||
|
warnings.append(f"Staubereich {area.jam_id} ({area.tro}): keine Lage - "
|
||||||
|
f"nicht gezeichnet")
|
||||||
|
continue
|
||||||
|
auto = automatisch.get(area.jam_id)
|
||||||
|
nr, k = overrides.get(jam_key(area), auto or ("", 0))
|
||||||
|
ref = msp.add_blockref(block, transform.to_dxf(*lage),
|
||||||
|
dxfattribs={"layer": layers["jam"]})
|
||||||
|
ref.add_auto_attribs({
|
||||||
|
"NAME": jam_label(nr, k) if nr else JAM_OFFEN,
|
||||||
|
"CONVEYOR": nr,
|
||||||
|
"NUMBER": str(k) if nr else "",
|
||||||
|
"TRO": area.tro,
|
||||||
|
"ENTRY": str(area.entry),
|
||||||
|
"AREA": area.jam_id,
|
||||||
|
"AUTO": f"{auto[0]}_{auto[1]}" if auto else "",
|
||||||
|
})
|
||||||
|
tag(ref, area.tro)
|
||||||
|
gezeichnet += 1
|
||||||
|
return gezeichnet
|
||||||
|
|
||||||
|
|
||||||
|
def read_jams(dxf_file: Path, ini_file: Path) -> tuple[dict[str, tuple[str, int]], list[str]]:
|
||||||
|
"""Im Dialog JAM_EDIT geaenderte Staubereichs-Nummern uebernehmen.
|
||||||
|
|
||||||
|
Liest die JAM_AREA-Bloecke der beschrifteten Zeichnung. Weicht
|
||||||
|
CONVEYOR_NUMBER von AUTO ab, kommt der Staubereich in die Korrekturdatei
|
||||||
|
``<csv>_jam_names.ini``; stimmt er (wieder) mit AUTO ueberein, faellt sein
|
||||||
|
Eintrag heraus. Eintraege zu Staubereichen, die nicht in der Zeichnung
|
||||||
|
stehen, bleiben. Rueckgabe: (Korrekturen danach, Meldungen).
|
||||||
|
"""
|
||||||
|
meldungen: list[str] = []
|
||||||
|
overrides = load_jam_overrides(ini_file)
|
||||||
|
doc = ezdxf.readfile(str(dxf_file))
|
||||||
|
namen: dict[str, str] = {}
|
||||||
|
for ref in doc.modelspace().query(f'INSERT[name=="{JAM_BLOCK}"]'):
|
||||||
|
a = {att.dxf.tag.upper(): (att.dxf.text or "").strip() for att in ref.attribs}
|
||||||
|
key = f"{a.get('TRO', '')}.{a.get('ENTRY', '')}"
|
||||||
|
nr, k = a.get("CONVEYOR", ""), a.get("NUMBER", "")
|
||||||
|
if not a.get("TRO") or not a.get("ENTRY"):
|
||||||
|
meldungen.append(f"Staubereich ohne TRO/ENTRY ({a.get('NAME', '?')}) - uebersprungen")
|
||||||
|
continue
|
||||||
|
if not nr and not k:
|
||||||
|
continue
|
||||||
|
if not (nr.isdigit() and len(nr) == 4 and k.isdigit() and int(k) > 0):
|
||||||
|
meldungen.append(f"{key} ({a.get('AREA', '')}): Foerderer '{nr}' / Nummer '{k}' "
|
||||||
|
f"ungueltig (erwartet NNNN und k >= 1) - uebersprungen")
|
||||||
|
continue
|
||||||
|
wert = f"{nr}_{int(k)}"
|
||||||
|
if wert in namen.values():
|
||||||
|
andere = next(s for s, w in namen.items() if w == wert)
|
||||||
|
meldungen.append(f"{key}: Jam{wert} ist schon {andere} - doppelt vergeben")
|
||||||
|
namen[key] = wert
|
||||||
|
alt = overrides.get(key)
|
||||||
|
if wert == a.get("AUTO", ""):
|
||||||
|
if alt is not None:
|
||||||
|
del overrides[key]
|
||||||
|
meldungen.append(f"{key}: wieder abgeleitet (Jam{wert}) - Korrektur entfernt")
|
||||||
|
elif alt != (nr, int(k)):
|
||||||
|
overrides[key] = (nr, int(k))
|
||||||
|
meldungen.append(f"{key}: Jam{wert} (abgeleitet: Jam{a.get('AUTO') or '-'})")
|
||||||
|
save_jam_overrides(ini_file, overrides)
|
||||||
|
return overrides, meldungen
|
||||||
|
|
||||||
|
|
||||||
def draw_legend(
|
def draw_legend(
|
||||||
msp, analysis: Analysis, placed: dict[str, tuple[float, float]],
|
msp, analysis: Analysis, placed: dict[str, tuple[float, float]],
|
||||||
layers: dict[str, str], marker: float, text: float, source: str,
|
layers: dict[str, str], marker: float, text: float, source: str,
|
||||||
@@ -790,6 +953,9 @@ def report(
|
|||||||
print(f"TROs = {len(analysis.tros)}, beschriftet: {counts['marker']}")
|
print(f"TROs = {len(analysis.tros)}, beschriftet: {counts['marker']}")
|
||||||
print(f"Verbindungen = {counts['flow']}"
|
print(f"Verbindungen = {counts['flow']}"
|
||||||
+ (" (Block CONNECTION_ARROW mit FROM_UID/TO_UID)" if counts["flow"] else ""))
|
+ (" (Block CONNECTION_ARROW mit FROM_UID/TO_UID)" if counts["flow"] else ""))
|
||||||
|
if counts.get("jams"):
|
||||||
|
print(f"Staubereiche = {counts['jams']} (Block {JAM_BLOCK}, Doppelklick -> "
|
||||||
|
f"JAM_EDIT; uebernehmen mit --read-jams)")
|
||||||
if counts.get("first_id"):
|
if counts.get("first_id"):
|
||||||
print(f"Interne IDs ab = {format_internal_id(counts['first_id'])} "
|
print(f"Interne IDs ab = {format_internal_id(counts['first_id'])} "
|
||||||
f"(ID-Raum von SSG_LIB, siehe IDSCHECK)")
|
f"(ID-Raum von SSG_LIB, siehe IDSCHECK)")
|
||||||
@@ -820,6 +986,9 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
|||||||
epilog="Beispiele:\n"
|
epilog="Beispiele:\n"
|
||||||
" tro_annotate.bat --file mubea.csv --dxf 500573_60_1.dxf --check\n"
|
" tro_annotate.bat --file mubea.csv --dxf 500573_60_1.dxf --check\n"
|
||||||
" tro_annotate.bat --file mubea.csv --dxf 500573_60_1.dxf --flow --fb\n"
|
" tro_annotate.bat --file mubea.csv --dxf 500573_60_1.dxf --flow --fb\n"
|
||||||
|
" tro_annotate.bat --file dematic_merge.csv --dxf dematic_merge..dxf --flow "
|
||||||
|
"--anlage dematic_merge_anlage.json\n"
|
||||||
|
" tro_annotate.bat --file dematic_merge.csv --read-jams\n"
|
||||||
"\n"
|
"\n"
|
||||||
"Exit-Codes:\n"
|
"Exit-Codes:\n"
|
||||||
" 0 ok\n"
|
" 0 ok\n"
|
||||||
@@ -855,6 +1024,17 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
|||||||
parser.add_argument("--re-register", action="store_true",
|
parser.add_argument("--re-register", action="store_true",
|
||||||
help="Lagebezug neu ermitteln, auch wenn in %%SKEL_CFG%% "
|
help="Lagebezug neu ermitteln, auch wenn in %%SKEL_CFG%% "
|
||||||
"einer abgelegt ist.")
|
"einer abgelegt ist.")
|
||||||
|
parser.add_argument("--anlage", metavar="NAME",
|
||||||
|
help="Anlagen-JSON (<csv>_anlage.json aus merge_hw_layout, in "
|
||||||
|
"%%SKEL_RESULTS%% oder ein Pfad): zusaetzlich die "
|
||||||
|
"Staubereiche als JAM_AREA-Bloecke einzeichnen "
|
||||||
|
"(Jam<NNNN>_<k>, Doppelklick -> Dialog JAM_EDIT).")
|
||||||
|
parser.add_argument("--read-jams", nargs="?", const="", default=None, metavar="DXF",
|
||||||
|
help="Im Dialog JAM_EDIT geaenderte Staubereichs-Nummern aus der "
|
||||||
|
"beschrifteten Zeichnung uebernehmen (Standard: "
|
||||||
|
"<csv>_annotated.dxf in %%SKEL_RESULTS%%) und nach "
|
||||||
|
"%%SKEL_CFG%%\\<csv>_jam_names.ini schreiben, dann beenden. "
|
||||||
|
"scl_skeleton liest die Datei.")
|
||||||
parser.add_argument("--check", action="store_true",
|
parser.add_argument("--check", action="store_true",
|
||||||
help="Nur pruefen und berichten, keine Datei schreiben.")
|
help="Nur pruefen und berichten, keine Datei schreiben.")
|
||||||
parser.add_argument("--emit-lisp", action="store_true",
|
parser.add_argument("--emit-lisp", action="store_true",
|
||||||
@@ -863,6 +1043,38 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
|||||||
return parser.parse_args(argv)
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
|
def _main_read_jams(args, csv_file: Path) -> int:
|
||||||
|
"""--read-jams: Nummern aus der beschrifteten Zeichnung -> <csv>_jam_names.ini."""
|
||||||
|
results = env_dir("SKEL_RESULTS", "results")
|
||||||
|
name = args.read_jams or args.out or f"{csv_file.stem}_annotated.dxf"
|
||||||
|
dxf_file = Path(name)
|
||||||
|
if not dxf_file.is_absolute() and dxf_file.parent == Path("."):
|
||||||
|
dxf_file = results / name
|
||||||
|
if not dxf_file.is_file():
|
||||||
|
print(f"FEHLER: beschriftete Zeichnung nicht gefunden: {dxf_file}", file=sys.stderr)
|
||||||
|
print(" In BricsCAD als DXF speichern (Dateiname wie beim Beschriften).",
|
||||||
|
file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
ini_file = env_dir("SKEL_CFG", "cfg") / f"{csv_file.stem}{JAM_NAMES_SUFFIX}"
|
||||||
|
try:
|
||||||
|
overrides, meldungen = read_jams(dxf_file, ini_file)
|
||||||
|
except (OSError, ValueError, configparser.Error, ezdxf.DXFError) as exc:
|
||||||
|
print(f"FEHLER: {exc}", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
print("")
|
||||||
|
print("================================================================")
|
||||||
|
print("STAUBEREICHE UEBERNEHMEN")
|
||||||
|
print("================================================================")
|
||||||
|
print(f"Zeichnung = {dxf_file}")
|
||||||
|
print(f"Korrekturen = {len(overrides)} -> {ini_file if overrides else '- (keine, Datei entfernt)'}")
|
||||||
|
for m in meldungen:
|
||||||
|
print(f" {m}")
|
||||||
|
print("Danach: scl_skeleton mit der Anlagen-JSON neu laufen lassen.")
|
||||||
|
print("================================================================")
|
||||||
|
print("")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def main(argv: list[str] | None = None) -> int:
|
def main(argv: list[str] | None = None) -> int:
|
||||||
args = parse_args(argv)
|
args = parse_args(argv)
|
||||||
warnings: list[str] = []
|
warnings: list[str] = []
|
||||||
@@ -875,16 +1087,19 @@ def main(argv: list[str] | None = None) -> int:
|
|||||||
"SSG_LIB-Module (dxfmakros/Lisp), damit TRO_EDIT sie findet.")
|
"SSG_LIB-Module (dxfmakros/Lisp), damit TRO_EDIT sie findet.")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
if not args.dxf:
|
|
||||||
print("FEHLER: --dxf fehlt (nur --emit-lisp geht ohne Zeichnung).",
|
|
||||||
file=sys.stderr)
|
|
||||||
return 1
|
|
||||||
|
|
||||||
csv_file = resolve_input(args.file)
|
csv_file = resolve_input(args.file)
|
||||||
if csv_file.suffix.lower() != ".csv":
|
if csv_file.suffix.lower() != ".csv":
|
||||||
print(f"FEHLER: --file erwartet eine CSV-Datei, keine '{csv_file.suffix}'-Datei: "
|
print(f"FEHLER: --file erwartet eine CSV-Datei, keine '{csv_file.suffix}'-Datei: "
|
||||||
f"{csv_file}", file=sys.stderr)
|
f"{csv_file}", file=sys.stderr)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
if args.read_jams is not None:
|
||||||
|
return _main_read_jams(args, csv_file)
|
||||||
|
|
||||||
|
if not args.dxf:
|
||||||
|
print("FEHLER: --dxf fehlt (nur --emit-lisp und --read-jams gehen ohne "
|
||||||
|
"Zeichnung).", file=sys.stderr)
|
||||||
|
return 1
|
||||||
if not csv_file.is_file():
|
if not csv_file.is_file():
|
||||||
print(f"FEHLER: CSV nicht gefunden: {csv_file}", file=sys.stderr)
|
print(f"FEHLER: CSV nicht gefunden: {csv_file}", file=sys.stderr)
|
||||||
return 1
|
return 1
|
||||||
@@ -904,6 +1119,29 @@ def main(argv: list[str] | None = None) -> int:
|
|||||||
cfg_dir = env_dir("SKEL_CFG", "cfg")
|
cfg_dir = env_dir("SKEL_CFG", "cfg")
|
||||||
results.mkdir(parents=True, exist_ok=True)
|
results.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# Anlagen-JSON fuer die Staubereiche (optional)
|
||||||
|
anlage = None
|
||||||
|
jam_overrides: dict[str, tuple[str, int]] = {}
|
||||||
|
if args.anlage:
|
||||||
|
anlage_file = Path(args.anlage)
|
||||||
|
if not anlage_file.is_absolute() and anlage_file.parent == Path("."):
|
||||||
|
anlage_file = results / args.anlage
|
||||||
|
try:
|
||||||
|
text = anlage_file.read_text(encoding="utf-8")
|
||||||
|
if json.loads(text).get("schema") != ANLAGE_SCHEMA:
|
||||||
|
raise ValueError(f"kein Schema '{ANLAGE_SCHEMA}' - erwartet wird die "
|
||||||
|
f"Anlagen-JSON aus merge_hw_layout")
|
||||||
|
anlage = AnlageDocument.from_json(text)
|
||||||
|
jam_overrides = load_jam_overrides(
|
||||||
|
cfg_dir / f"{csv_file.stem}{JAM_NAMES_SUFFIX}")
|
||||||
|
except (OSError, ValueError, configparser.Error) as exc:
|
||||||
|
print(f"FEHLER: Anlagen-JSON {anlage_file} nicht lesbar: {exc}", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
if Path(anlage.layout.source).name != csv_file.name:
|
||||||
|
print(f"FEHLER: {anlage_file.name} gehoert zu '{anlage.layout.source}', "
|
||||||
|
f"nicht zu '{csv_file.name}'.", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
# TROs aus dem CSV
|
# TROs aus dem CSV
|
||||||
read_warnings: list[str] = []
|
read_warnings: list[str] = []
|
||||||
try:
|
try:
|
||||||
@@ -955,7 +1193,16 @@ def main(argv: list[str] | None = None) -> int:
|
|||||||
return 4
|
return 4
|
||||||
|
|
||||||
counts = {"marker": 0, "flow": 0, "removed": 0, "layers": 0, "blocks": 0,
|
counts = {"marker": 0, "flow": 0, "removed": 0, "layers": 0, "blocks": 0,
|
||||||
"first_id": 0}
|
"first_id": 0, "jams": 0}
|
||||||
|
if anlage is not None:
|
||||||
|
aus_csv = {t.tro_id for t in analysis.tros}
|
||||||
|
aus_json = {t.tro_id for t in anlage.layout.tros}
|
||||||
|
if aus_csv != aus_json:
|
||||||
|
warnings.append(
|
||||||
|
f"TROs der Zeichnung und der Anlagen-JSON weichen ab (nur CSV: "
|
||||||
|
f"{', '.join(sorted(aus_csv - aus_json)) or '-'}; nur JSON: "
|
||||||
|
f"{', '.join(sorted(aus_json - aus_csv)) or '-'}) - tro_flow --json und "
|
||||||
|
f"merge_hw_layout neu laufen lassen")
|
||||||
|
|
||||||
if args.check:
|
if args.check:
|
||||||
counts["marker"] = sum(1 for t in analysis.tros if t.position)
|
counts["marker"] = sum(1 for t in analysis.tros if t.position)
|
||||||
@@ -986,6 +1233,9 @@ def main(argv: list[str] | None = None) -> int:
|
|||||||
msp, doc, analysis, {**placed, **stationen}, internal, layers,
|
msp, doc, analysis, {**placed, **stationen}, internal, layers,
|
||||||
args.marker_size, args.text_height, next_id=next_id + len(placed),
|
args.marker_size, args.text_height, next_id=next_id + len(placed),
|
||||||
)
|
)
|
||||||
|
if anlage is not None:
|
||||||
|
counts["jams"] = place_jams(msp, doc, anlage, jam_overrides, transform, layers,
|
||||||
|
args.text_height, warnings)
|
||||||
if args.legend:
|
if args.legend:
|
||||||
draw_legend(msp, analysis, placed, layers, args.marker_size,
|
draw_legend(msp, analysis, placed, layers, args.marker_size,
|
||||||
args.text_height, csv_file.name)
|
args.text_height, csv_file.name)
|
||||||
|
|||||||
@@ -0,0 +1,131 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""Tests fuer die Staubereiche in tro_annotate.py (--anlage, --read-jams).
|
||||||
|
|
||||||
|
Kleine Anlage: ein Kreisel mit zwei TROs hintereinander (K0042). Die Bloecke
|
||||||
|
JAM_AREA tragen Name, Foerderer, Nummer und den stabilen Schluessel TRO +
|
||||||
|
Eingang; --read-jams uebernimmt im Dialog JAM_EDIT geaenderte Nummern nach
|
||||||
|
<csv>_jam_names.ini, und scl_context setzt sie in die SCL ein.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import ezdxf
|
||||||
|
|
||||||
|
import tro_annotate
|
||||||
|
from dxf_registration import Transform
|
||||||
|
from model import (
|
||||||
|
AnlageDocument,
|
||||||
|
Analysis,
|
||||||
|
ConveyorBinding,
|
||||||
|
DerivedTro,
|
||||||
|
LayoutPart,
|
||||||
|
TroDocument,
|
||||||
|
)
|
||||||
|
from scl_context import build_context, load_jam_overrides
|
||||||
|
|
||||||
|
IDENT = Transform(dx=0.0, dy=0.0)
|
||||||
|
|
||||||
|
|
||||||
|
def _tro(tro_id, sep, x, vor, nach):
|
||||||
|
return DerivedTro(tro_id=tro_id, type_name="1Sep", label=tro_id, hosts=["0004-L"],
|
||||||
|
separators=[sep], scanners=[], reason="Test",
|
||||||
|
predecessors=vor, successors=nach, position=(x, 0.0, 0.0))
|
||||||
|
|
||||||
|
|
||||||
|
def anlage() -> AnlageDocument:
|
||||||
|
tros = [_tro("TRO01", "0101", 0.0, ["SYS1"], ["TRO02"]),
|
||||||
|
_tro("TRO02", "0102", 5000.0, ["TRO01"], ["SYS2"])]
|
||||||
|
parts = [
|
||||||
|
LayoutPart(layout_id="0004", kind="Kreisel", name="Kreisel4",
|
||||||
|
position=(2500.0, 0.0, 0.0), bbox=(8000.0, 800.0, 1.0)),
|
||||||
|
LayoutPart(layout_id="0101", kind="Separator", name="S1", position=(0.0, 0.0, 0.0)),
|
||||||
|
LayoutPart(layout_id="0102", kind="Separator", name="S2", position=(5000.0, 0.0, 0.0)),
|
||||||
|
]
|
||||||
|
layout = TroDocument.from_analysis(Analysis(tros=tros), source="x.csv", parts=parts)
|
||||||
|
return AnlageDocument(layout=layout, conveyors=[
|
||||||
|
ConveyorBinding(eplan_name="K0042", layout_id="0004", motor="MA0042")])
|
||||||
|
|
||||||
|
|
||||||
|
def _zeichnen(overrides=None):
|
||||||
|
doc = ezdxf.new()
|
||||||
|
tro_annotate.ensure_appid(doc)
|
||||||
|
layers = tro_annotate.build_layers(doc, tro_annotate.LAYER_PREFIX)
|
||||||
|
warnings: list[str] = []
|
||||||
|
n = tro_annotate.place_jams(doc.modelspace(), doc, anlage(), overrides or {}, IDENT,
|
||||||
|
layers, 200.0, warnings)
|
||||||
|
return doc, n, warnings
|
||||||
|
|
||||||
|
|
||||||
|
def _jams(doc) -> dict[str, dict]:
|
||||||
|
out = {}
|
||||||
|
for ref in doc.modelspace().query(f'INSERT[name=="{tro_annotate.JAM_BLOCK}"]'):
|
||||||
|
a = {t.dxf.tag: t.dxf.text for t in ref.attribs}
|
||||||
|
a["pos"] = (round(ref.dxf.insert.x), round(ref.dxf.insert.y))
|
||||||
|
out[a["TRO"]] = a
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def test_staubereiche_als_block_mit_name_und_schluessel():
|
||||||
|
doc, n, warnings = _zeichnen()
|
||||||
|
assert n == 2 and warnings == []
|
||||||
|
jams = _jams(doc)
|
||||||
|
assert {k: v["NAME"] for k, v in jams.items()} == {"TRO01": "Jam0042_1",
|
||||||
|
"TRO02": "Jam0042_2"}
|
||||||
|
t2 = jams["TRO02"]
|
||||||
|
assert (t2["CONVEYOR"], t2["NUMBER"], t2["ENTRY"], t2["AREA"], t2["AUTO"]) == (
|
||||||
|
"0042", "2", "1", "J0102", "0042_2")
|
||||||
|
# vor dem Separator, Richtung Vorgaenger TRO01 (gegen den Fluss)
|
||||||
|
assert t2["pos"] == (5000 - round(tro_annotate.JAM_VERSATZ), 0)
|
||||||
|
# TRO01 hat nur eine Station als Vorgaenger: am Separator
|
||||||
|
assert jams["TRO01"]["pos"] == (0, 0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_korrektur_wird_gezeichnet():
|
||||||
|
doc, _, _ = _zeichnen({"TRO02.1": ("0042", 7)})
|
||||||
|
t2 = _jams(doc)["TRO02"]
|
||||||
|
assert (t2["NAME"], t2["NUMBER"], t2["AUTO"]) == ("Jam0042_7", "7", "0042_2")
|
||||||
|
|
||||||
|
|
||||||
|
def test_geaenderte_nummer_zurueck_in_die_korrekturdatei(tmp_path):
|
||||||
|
doc, _, _ = _zeichnen()
|
||||||
|
# wie der Dialog JAM_EDIT: NUMBER und NAME neu
|
||||||
|
for ref in doc.modelspace().query(f'INSERT[name=="{tro_annotate.JAM_BLOCK}"]'):
|
||||||
|
a = {t.dxf.tag: t for t in ref.attribs}
|
||||||
|
if a["TRO"].dxf.text == "TRO02":
|
||||||
|
a["NUMBER"].dxf.text = "7"
|
||||||
|
a["NAME"].dxf.text = "Jam0042_7"
|
||||||
|
dxf = tmp_path / "x_annotated.dxf"
|
||||||
|
doc.saveas(dxf)
|
||||||
|
ini = tmp_path / "x_jam_names.ini"
|
||||||
|
|
||||||
|
overrides, meldungen = tro_annotate.read_jams(dxf, ini)
|
||||||
|
assert overrides == {"TRO02.1": ("0042", 7)}
|
||||||
|
assert load_jam_overrides(ini) == overrides
|
||||||
|
assert any("TRO02.1: Jam0042_7" in m for m in meldungen)
|
||||||
|
|
||||||
|
# in der SCL: Ein- und Ausgang tragen die korrigierte Nummer
|
||||||
|
a = anlage()
|
||||||
|
ctx = build_context(a.layout, "x_anlage.json", anlage=a, jam_overrides=overrides)
|
||||||
|
tro1, tro2 = ctx.tros
|
||||||
|
assert tro2.jam_entries[0].db.value == '"DB_JamArea0042".stJam0042_7'
|
||||||
|
assert tro1.jam_exits[0].db.value == '"DB_JamArea0042".stJam0042_7'
|
||||||
|
|
||||||
|
# zurueck auf die abgeleitete Nummer -> Eintrag (und Datei) weg
|
||||||
|
for ref in doc.modelspace().query(f'INSERT[name=="{tro_annotate.JAM_BLOCK}"]'):
|
||||||
|
a2 = {t.dxf.tag: t for t in ref.attribs}
|
||||||
|
if a2["TRO"].dxf.text == "TRO02":
|
||||||
|
a2["NUMBER"].dxf.text = "2"
|
||||||
|
doc.saveas(dxf)
|
||||||
|
overrides, _ = tro_annotate.read_jams(dxf, ini)
|
||||||
|
assert overrides == {} and not ini.exists()
|
||||||
|
|
||||||
|
|
||||||
|
def test_ungueltige_nummer_wird_nicht_uebernommen(tmp_path):
|
||||||
|
doc, _, _ = _zeichnen()
|
||||||
|
for ref in doc.modelspace().query(f'INSERT[name=="{tro_annotate.JAM_BLOCK}"]'):
|
||||||
|
a = {t.dxf.tag: t for t in ref.attribs}
|
||||||
|
if a["TRO"].dxf.text == "TRO01":
|
||||||
|
a["CONVEYOR"].dxf.text = "42"
|
||||||
|
dxf = tmp_path / "x_annotated.dxf"
|
||||||
|
doc.saveas(dxf)
|
||||||
|
overrides, meldungen = tro_annotate.read_jams(dxf, tmp_path / "x_jam_names.ini")
|
||||||
|
assert overrides == {}
|
||||||
|
assert any("TRO01.1" in m and "ungueltig" in m for m in meldungen)
|
||||||
Reference in New Issue
Block a user