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:
+96
-8
@@ -33,9 +33,11 @@ Wert ersetzen, Merker loeschen.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import configparser
|
||||
import re
|
||||
from collections import deque
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
@@ -64,7 +66,13 @@ __all__ = [
|
||||
"HwSource",
|
||||
"build_context",
|
||||
"flow_order",
|
||||
"JAM_NAMES_SUFFIX",
|
||||
"jam_key",
|
||||
"jam_label",
|
||||
"jam_names",
|
||||
"jam_numbers",
|
||||
"load_jam_overrides",
|
||||
"save_jam_overrides",
|
||||
"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
|
||||
|
||||
|
||||
def jam_names(document: TroDocument, geordnet: list[TroBase],
|
||||
hw: HwSource) -> dict[str, str]:
|
||||
JAM_NAMES_SUFFIX = "_jam_names.ini"
|
||||
# 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').
|
||||
|
||||
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
|
||||
Foerderer, auf dem der Staubereich liegt (Nummer des Kreisel- bzw.
|
||||
Vario-Motors), <k> zaehlt auf diesem Foerderer in Fliessrichtung hoch.
|
||||
@@ -706,11 +792,11 @@ def jam_names(document: TroDocument, geordnet: list[TroBase],
|
||||
if nr:
|
||||
je_foerderer.setdefault(nr, []).append(area)
|
||||
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 k, area in enumerate(_entlang(liste, nachfolger), start=1):
|
||||
namen[area.jam_id] = f'"DB_JamArea{nr}".stJam{nr}_{k}'
|
||||
return namen
|
||||
nummern[area.jam_id] = (nr, k)
|
||||
return nummern
|
||||
|
||||
|
||||
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 = "",
|
||||
created: str | 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).
|
||||
|
||||
anlage: Anlagen-JSON (merge_hw_layout) - ihr Layout ist ``document``,
|
||||
ihre Zuordnungen liefern die Namen. eplan: alte Zuordnung Layout ->
|
||||
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:
|
||||
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():
|
||||
bereiche.setdefault(area.tro, []).append(area)
|
||||
geordnet, startgrund = flow_order(document.tros, start_id)
|
||||
namen = jam_names(document, geordnet, hw)
|
||||
namen = jam_names(document, geordnet, hw, jam_overrides)
|
||||
return SclFile(
|
||||
source=source,
|
||||
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
|
||||
|
||||
import argparse
|
||||
import configparser
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
@@ -60,7 +61,15 @@ from e_planning import EPLAN_SCHEMA, EplanDocument
|
||||
from hw_import import HardwareDocument
|
||||
from material_flow import env_dir
|
||||
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.
|
||||
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)
|
||||
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,
|
||||
anlage=anlage)
|
||||
anlage=anlage, jam_overrides=jam_overrides)
|
||||
mit, ohne = template_usage(context, templates)
|
||||
if args.strict and ohne:
|
||||
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")
|
||||
if hw_file is not None:
|
||||
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"SCL = {out_file}")
|
||||
print("================================================================")
|
||||
|
||||
+256
-6
@@ -85,6 +85,7 @@ from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import collections
|
||||
import configparser
|
||||
import csv as csv_module
|
||||
import fnmatch
|
||||
import json
|
||||
@@ -120,6 +121,17 @@ from tro_flow import (
|
||||
resolve_ids_file,
|
||||
)
|
||||
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
|
||||
@@ -131,12 +143,18 @@ BLOCK_PREFIX = "TRO_SYM_"
|
||||
# Verbindungsblock. Der Name beginnt mit CONNECTION_, damit der Dispatcher
|
||||
# SSG_BLOCKEDIT in dxfmakros ihn ueber "CONNECTION_*" erkennt.
|
||||
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,
|
||||
# Abschnitt [tro_annotate] (lib/parameter.py)
|
||||
DEFAULT_MARKER = get_float("tro_annotate", "default_marker")
|
||||
DEFAULT_TEXT = get_float("tro_annotate", "default_text")
|
||||
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]:
|
||||
@@ -335,6 +353,7 @@ def build_layers(doc, prefix: str) -> dict[str, str]:
|
||||
("weiche", "#cc8800"),
|
||||
("eingang", STATION_FARBE[BOUNDARY_ENTRY]),
|
||||
("ausgang", STATION_FARBE[BOUNDARY_EXIT]),
|
||||
("jam", "#7030a0"),
|
||||
("legend", "#808080")):
|
||||
name = f"{prefix}{key.upper()}"
|
||||
names[key] = name
|
||||
@@ -702,6 +721,150 @@ def draw_connections(
|
||||
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(
|
||||
msp, analysis: Analysis, placed: dict[str, tuple[float, float]],
|
||||
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"Verbindungen = {counts['flow']}"
|
||||
+ (" (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"):
|
||||
print(f"Interne IDs ab = {format_internal_id(counts['first_id'])} "
|
||||
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"
|
||||
" 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 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"
|
||||
"Exit-Codes:\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",
|
||||
help="Lagebezug neu ermitteln, auch wenn in %%SKEL_CFG%% "
|
||||
"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",
|
||||
help="Nur pruefen und berichten, keine Datei schreiben.")
|
||||
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)
|
||||
|
||||
|
||||
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:
|
||||
args = parse_args(argv)
|
||||
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.")
|
||||
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)
|
||||
if csv_file.suffix.lower() != ".csv":
|
||||
print(f"FEHLER: --file erwartet eine CSV-Datei, keine '{csv_file.suffix}'-Datei: "
|
||||
f"{csv_file}", file=sys.stderr)
|
||||
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():
|
||||
print(f"FEHLER: CSV nicht gefunden: {csv_file}", file=sys.stderr)
|
||||
return 1
|
||||
@@ -904,6 +1119,29 @@ def main(argv: list[str] | None = None) -> int:
|
||||
cfg_dir = env_dir("SKEL_CFG", "cfg")
|
||||
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
|
||||
read_warnings: list[str] = []
|
||||
try:
|
||||
@@ -955,7 +1193,16 @@ def main(argv: list[str] | None = None) -> int:
|
||||
return 4
|
||||
|
||||
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:
|
||||
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,
|
||||
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:
|
||||
draw_legend(msp, analysis, placed, layers, args.marker_size,
|
||||
args.text_height, csv_file.name)
|
||||
|
||||
Reference in New Issue
Block a user