4ce2a02205
Die Streckenbloecke GF_N und VF_N tragen Winkel und Seite ihres AS-/ES-
Elements bereits als Attribute des gemeinsamen Schemas (WINKEL_AS/SEITE_AS/
WINKEL_ES/SEITE_ES, *strecke-attr-front* in ssg_core.lsp). Diese Information
landet jetzt auch im details-JSON des Sivas-Exports.
Zuordnung (Sivas-Hierarchie: 4431/4432 schalten Winkel und Seite frei):
4431/4432 AS-/ES-Element vorhanden Ja/Nein
4427/4429 Winkel "30 °"/"90 °"
4428/4430 Seite links/rechts
Die Zuordnung steht vollstaendig in cfg/sivas.cfg - mit denselben sechs
Zeilen in [gefaellestrecke] und [variofoerderer], jeweils direkt hinter 2950,
was auch die Reihenfolge im details-JSON bestimmt.
Ob ein Element vorhanden ist, entscheidet ausschliesslich WINKEL_AS/WINKEL_ES:
das Schema laesst den Winkel leer, wenn keines gewaehlt wurde, waehrend
SEITE_AS/SEITE_ES mit "rechts" immer einen Vorgabewert tragen. Ohne Element
bleiben Winkel UND Seite leer, damit in Sivas keine Seite unter einem
"Nein"-Knoten haengt.
Weitere Punkte:
- sivas_mapping: neuer Dekorator @braucht_attribs. Ein so markierter
Transform wird als fn(wert, attribs) statt fn(wert) aufgerufen und sieht
damit die uebrigen Attribute des Blocks - noetig, weil die Seite vom
Winkel abhaengt. Ohne diese Klammer muessten bedingte Merkmale wieder als
Python-Code aus der Config herauswandern.
- Die Winkel-Schreibweise der Sivas-Auswahlliste ("30 °" mit Leerzeichen vor
dem Gradzeichen) steht als Tabelle [as_es_winkel_anzeige] in sivas.cfg,
Bauart wie [geruest_typ_anzeige] - weicht Sivas davon ab, ist das die
einzige Stelle zum Nachziehen.
- gefaellestrecke_signature beruecksichtigt AS/ES: zwei sonst baugleiche
Gefaellestrecken mit AS links bzw. rechts bleiben getrennte Zeilen, sonst
zeigte die gemeinsame Zeile nur die Seite des ersten Blocks.
- Altbestand ohne diese Attribute meldet "Nein" statt abzubrechen.
- Merkmalsstamm_in_Sivas.xlsx auf den aktuellen Sivas-Stand gebracht
(enthaelt neben 4427-4432 auch 4002, 7010-7013 und Klartext-Korrekturen;
4339/4383 heissen jetzt eindeutig "... Gefaellestrecke"/"... Variofoerderer").
- 12 neue Tests in tests/test_export_sivas_as_es.py, ohne BricsCAD lauffaehig.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
151 lines
5.7 KiB
Python
151 lines
5.7 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
"""
|
|
sivas_mapping.py - Liest die Attribut->Sivas-Merkmal-Zuordnung aus
|
|
cfg/sivas.cfg und wendet sie auf ein Block-Attribut-Dict an.
|
|
|
|
Einzige Quelle fuer die "einfachen" build_*_details-Zuordnungen in
|
|
export_sivas.py (siehe cfg/sivas.cfg fuer das Zeilenformat und die
|
|
Begruendung, warum nur die einfachen Faelle hier landen).
|
|
"""
|
|
|
|
import configparser
|
|
import functools
|
|
import os
|
|
|
|
|
|
def cfg_path_from_env(default_dir=None):
|
|
"""Pfad zu sivas.cfg: DXFM_CFG env var, sonst <repo>/cfg."""
|
|
dxfm_cfg = os.environ.get("DXFM_CFG")
|
|
if dxfm_cfg:
|
|
return os.path.join(dxfm_cfg, "sivas.cfg")
|
|
base = default_dir or os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
return os.path.join(base, "cfg", "sivas.cfg")
|
|
|
|
|
|
def _parse_wert(roh):
|
|
"""Zerlegt eine Config-Wertzeile "attribut|alt_attribut:default|transform"
|
|
in (attribut, alt_attribut_oder_None, default, transform_oder_None).
|
|
|
|
Reihenfolge beim Parsen: erst am ":" in (attributteil, defaultteil)
|
|
trennen (nur der erste ":" zaehlt, ein Default-Text soll selbst keinen
|
|
":" enthalten muessen), danach im defaultteil am LETZTEN "|" den
|
|
Transform-Namen abtrennen (ein Default-Text wie "ohne HARTCOAT..."
|
|
enthaelt kein "|"). attributteil wird am ERSTEN "|" in Attribut/
|
|
Fallback-Attribut gesplittet.
|
|
"""
|
|
roh = roh.strip()
|
|
if ":" in roh:
|
|
attribut_teil, rest = roh.split(":", 1)
|
|
else:
|
|
attribut_teil, rest = roh, ""
|
|
|
|
transform = None
|
|
default = rest.strip()
|
|
if "|" in rest:
|
|
default_teil, transform_teil = rest.rsplit("|", 1)
|
|
default = default_teil.strip()
|
|
transform = transform_teil.strip() or None
|
|
|
|
attribut_teil = attribut_teil.strip()
|
|
alt_attribut = None
|
|
if "|" in attribut_teil:
|
|
attribut, alt_attribut = (p.strip() for p in attribut_teil.split("|", 1))
|
|
else:
|
|
attribut = attribut_teil
|
|
alt_attribut = alt_attribut or None
|
|
|
|
return (attribut or None, alt_attribut, default, transform)
|
|
|
|
|
|
@functools.lru_cache(maxsize=None)
|
|
def load_sivas_cfg(cfg_path):
|
|
"""Liest sivas.cfg einmal je Pfad (Prozess-Cache)."""
|
|
parser = configparser.ConfigParser(inline_comment_prefixes=None)
|
|
parser.optionxform = str # Keys (Merkmalnummern/Klartext-Keys) case-sensitive erhalten
|
|
parser.read(cfg_path, encoding="utf-8")
|
|
return parser
|
|
|
|
|
|
def load_section_mapping(section, cfg_path=None):
|
|
"""Liest einen Abschnitt aus sivas.cfg.
|
|
|
|
Rueckgabe: dict merkmal -> (attribut, alt_attribut, default, transform),
|
|
wie von _parse_wert geliefert. Bei fehlender Datei/Sektion: leeres dict -
|
|
Aufrufer bekommt dann einfach keine Merkmale aus der Config (bisheriges
|
|
Verhalten vor der Config-Einfuehrung, kein harter Fehler).
|
|
"""
|
|
if cfg_path is None:
|
|
cfg_path = cfg_path_from_env()
|
|
parser = load_sivas_cfg(cfg_path)
|
|
mapping = {}
|
|
if parser.has_section(section):
|
|
for merkmal, roh in parser.items(section):
|
|
mapping[merkmal] = _parse_wert(roh)
|
|
return mapping
|
|
|
|
|
|
def load_flat_mapping(section, cfg_path=None):
|
|
"""Liest einen Abschnitt als einfaches dict key -> value (z.B.
|
|
[geruest_typ_anzeige]: ASCII-Wert -> Sivas-Klartext), ohne das
|
|
attribut/default/transform-Format von load_section_mapping."""
|
|
if cfg_path is None:
|
|
cfg_path = cfg_path_from_env()
|
|
parser = load_sivas_cfg(cfg_path)
|
|
if parser.has_section(section):
|
|
return {k: v.strip() for k, v in parser.items(section)}
|
|
return {}
|
|
|
|
|
|
def braucht_attribs(fn):
|
|
"""Markiert einen Transform, der NEBEN seinem Rohwert auch das komplette
|
|
Attribut-Dict des Blocks braucht - er wird dann als fn(wert, attribs)
|
|
aufgerufen statt als fn(wert).
|
|
|
|
Noetig fuer Merkmale, deren Wert von einem ZWEITEN Attribut abhaengt: die
|
|
AS-/ES-Seite (4428/4430) darf nur gefuellt werden, wenn der zugehoerige
|
|
WINKEL_AS/WINKEL_ES ueberhaupt ein Element ausweist. Ohne diese Klammer
|
|
muessten solche Merkmale wieder aus der Config heraus in Python-Code
|
|
wandern - genau das soll sivas.cfg vermeiden.
|
|
"""
|
|
fn.braucht_attribs = True
|
|
return fn
|
|
|
|
|
|
def anwenden(section_mapping, attribs, transforms):
|
|
"""Wendet eine per load_section_mapping() gelesene Zuordnung auf ein
|
|
Block-Attribut-Dict an.
|
|
|
|
transforms: dict transform_name -> Funktion(wert) -> wert. Ein mit
|
|
@braucht_attribs markierter Transform wird stattdessen als
|
|
fn(wert, attribs) aufgerufen und sieht damit die uebrigen Attribute des
|
|
Blocks. Ein in der Config genannter, aber hier unbekannter Transform-Name
|
|
wird ignoriert (Rohwert bleibt unveraendert) statt abzubrechen - ein
|
|
Tippfehler in sivas.cfg darf den Export nicht zum Stillstand bringen.
|
|
|
|
Rueckgabe: dict merkmal -> wert. Ohne Transform wird der Rohwert zu str()
|
|
gewandelt (bisheriges Verhalten der build_*_details-Funktionen: alles
|
|
Merkmalnummer-Werte sind Strings). Ein Transform liefert seinen
|
|
Rueckgabewert UNVERAENDERT (er ist selbst fuer den Typ verantwortlich).
|
|
"""
|
|
ergebnis = {}
|
|
for merkmal, (attribut, alt_attribut, default, transform) in section_mapping.items():
|
|
if attribut is not None:
|
|
wert = attribs.get(attribut)
|
|
if wert is None and alt_attribut is not None:
|
|
wert = attribs.get(alt_attribut)
|
|
if wert is None:
|
|
wert = default
|
|
else:
|
|
wert = default
|
|
if transform:
|
|
fn = transforms.get(transform)
|
|
if fn is not None:
|
|
if getattr(fn, "braucht_attribs", False):
|
|
ergebnis[merkmal] = fn(wert, attribs)
|
|
else:
|
|
ergebnis[merkmal] = fn(wert)
|
|
continue
|
|
ergebnis[merkmal] = str(wert)
|
|
return ergebnis
|