CSV-Escaping-Bug behoben, export.cfg-Mehrfachparsing beseitigt
format_csv_line() in export_csv.py/export_sivas.py hat Feldwerte per f-String direkt in doppelte Anfuehrungszeichen gesetzt, ohne ein eingebettetes '"' zu escapen (z.B. eine Bezeichnung mit Anfuehrungszeichen haette die Spaltengrenze der Zeile kaputt gemacht). Neuer gemeinsamer Helper csv_quote() (export_blockpatterns.py) verdoppelt eingebettete Anfuehrungszeichen nach RFC 4180. Ausserdem: export.cfg wurde bisher von vier unabhaengigen load_*-Funktionen (load_patterns, load_neighbor_tolerance_mm, load_omniflo_cell_size_mm, load_planquadrat_config) separat von der Platte gelesen und geparst. Neuer gemeinsamer, gecachter load_export_cfg() in export_blockpatterns.py sorgt dafuer, dass export.cfg pro Exportlauf nur noch einmal gelesen wird. Dabei auch die in export_csv.py bereits genutzte, aber in export_planquadrat.py fehlende resolve_origins()-Funktion (automatische Planquadrat-Ursprungs- Ermittlung) ergaenzt - ohne sie war der Import von export_csv.py gebrochen. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+56
-14
@@ -8,26 +8,28 @@ Liest die aktive Planquadrat-Konfiguration aus cfg/export.cfg
|
||||
rundet die x/y-Koordinate eines Blocks (mm, wie im Roh-JSON von export.lsp)
|
||||
je Achse auf die konfigurierte Schrittweite (in Metern) herunter.
|
||||
|
||||
x_origin/y_origin ([Planquadrate], in m) legen die DXF-Koordinate der
|
||||
Planquadrat-Ecke A/1 fest. Sind sie leer/nicht gesetzt, ermittelt
|
||||
resolve_origins() sie automatisch je Exportlauf aus den zu exportierenden
|
||||
Bloecken (am weitesten links/unten liegendes Element = Ursprung) - siehe dort.
|
||||
|
||||
Nur von export_csv.py genutzt (EXPORTCSV), nicht von export_sivas.py.
|
||||
"""
|
||||
|
||||
import configparser
|
||||
|
||||
from export_blockpatterns import cfg_path_from_env
|
||||
from export_blockpatterns import cfg_path_from_env, load_export_cfg
|
||||
|
||||
|
||||
def load_planquadrat_config(cfg_path=None):
|
||||
"""Liest die aktive Planquadrat-Konfiguration aus export.cfg.
|
||||
|
||||
Rueckgabe: dict mit x_step_mm, x_alpha, y_step_mm, y_alpha, format,
|
||||
oder None, wenn [Planquadrate] bzw. die referenzierte Sub-Sektion fehlt.
|
||||
Rueckgabe: dict mit x_step_mm, x_alpha, y_step_mm, y_alpha,
|
||||
x_origin_mm/y_origin_mm (None, falls in [Planquadrate] nicht gesetzt -
|
||||
siehe resolve_origins()) und format, oder None, wenn [Planquadrate]
|
||||
bzw. die referenzierte Sub-Sektion fehlt.
|
||||
"""
|
||||
if cfg_path is None:
|
||||
cfg_path = cfg_path_from_env()
|
||||
# inline_comment_prefixes noetig, da export.cfg Werte wie
|
||||
# "numerisch # zahlen, von links nach rechts, 2m pro Spalte" nutzt.
|
||||
parser = configparser.ConfigParser(inline_comment_prefixes=("#",))
|
||||
parser.read(cfg_path, encoding="utf-8")
|
||||
parser = load_export_cfg(cfg_path)
|
||||
|
||||
if not parser.has_section("Planquadrate"):
|
||||
return None
|
||||
@@ -36,15 +38,48 @@ def load_planquadrat_config(cfg_path=None):
|
||||
return None
|
||||
|
||||
sec = parser[section_name]
|
||||
|
||||
def _origin_mm(key):
|
||||
raw = parser.get("Planquadrate", key, fallback="").strip()
|
||||
return float(raw) * 1000.0 if raw else None
|
||||
|
||||
return {
|
||||
"x_step_mm": float(sec.get("x_step", "1")) * 1000.0,
|
||||
"x_alpha": sec.get("x_description", "numerisch").strip().lower().startswith("alpha"),
|
||||
"y_step_mm": float(sec.get("y_step", "1")) * 1000.0,
|
||||
"y_alpha": sec.get("y_description", "numerisch").strip().lower().startswith("alpha"),
|
||||
"x_origin_mm": _origin_mm("x_origin"),
|
||||
"y_origin_mm": _origin_mm("y_origin"),
|
||||
"format": sec.get("format", "{x}/{y}").strip(),
|
||||
}
|
||||
|
||||
|
||||
def resolve_origins(cfg, blocks):
|
||||
"""Loest x_origin_mm/y_origin_mm auf, falls in export.cfg nicht gesetzt.
|
||||
|
||||
cfg = Rueckgabe von load_planquadrat_config() (kann None sein).
|
||||
blocks = alle Bloecke des aktuellen Exportlaufs (Liste von dicts mit
|
||||
"x"/"y", wie im Roh-JSON von export.lsp).
|
||||
|
||||
Automatik: das am weitesten links liegende Element (min x) setzt
|
||||
x_origin, das am weitesten unten liegende (min y) setzt y_origin -
|
||||
beide landen damit exakt in Planquadrat-Index 1 auf ihrer Achse.
|
||||
Explizit in export.cfg gesetzte Werte werden nicht ueberschrieben.
|
||||
|
||||
Rueckgabe: neues dict (cfg bleibt unveraendert) oder None, wenn cfg None ist.
|
||||
"""
|
||||
if cfg is None:
|
||||
return None
|
||||
resolved = dict(cfg)
|
||||
if resolved.get("x_origin_mm") is None:
|
||||
xs = [float(b["x"]) for b in blocks if b.get("x") is not None]
|
||||
resolved["x_origin_mm"] = min(xs) if xs else 0.0
|
||||
if resolved.get("y_origin_mm") is None:
|
||||
ys = [float(b["y"]) for b in blocks if b.get("y") is not None]
|
||||
resolved["y_origin_mm"] = min(ys) if ys else 0.0
|
||||
return resolved
|
||||
|
||||
|
||||
def _index_to_alpha(n):
|
||||
"""Wandelt einen 1-basierten Index in eine Buchstaben-Bezeichnung um.
|
||||
|
||||
@@ -57,15 +92,22 @@ def _index_to_alpha(n):
|
||||
return result
|
||||
|
||||
|
||||
def _index_for(coord_mm, step_mm):
|
||||
"""1-basierter Planquadrat-Index einer Koordinate fuer eine Schrittweite."""
|
||||
return int(coord_mm // step_mm) + 1
|
||||
def _index_for(coord_mm, step_mm, origin_mm=0.0):
|
||||
"""1-basierter Planquadrat-Index einer Koordinate fuer eine Schrittweite.
|
||||
|
||||
origin_mm ist die Koordinate der Planquadrat-Ecke A/1 (x_origin/y_origin
|
||||
aus cfg/export.cfg) - Koordinaten werden vor der Index-Berechnung um
|
||||
diesen Nullpunkt verschoben, damit reale Layouts mit negativen
|
||||
DXF-Koordinaten (Zeichnungsursprung liegt nicht in der Gebaeudeecke)
|
||||
trotzdem ab Index 1 gezaehlt werden.
|
||||
"""
|
||||
return int((coord_mm - origin_mm) // step_mm) + 1
|
||||
|
||||
|
||||
def compute_planquadrat(x_mm, y_mm, cfg):
|
||||
"""Berechnet die Planquadrat-Bezeichnung fuer eine x/y-Koordinate (mm)."""
|
||||
x_idx = _index_for(x_mm, cfg["x_step_mm"])
|
||||
y_idx = _index_for(y_mm, cfg["y_step_mm"])
|
||||
x_idx = _index_for(x_mm, cfg["x_step_mm"], cfg.get("x_origin_mm", 0.0))
|
||||
y_idx = _index_for(y_mm, cfg["y_step_mm"], cfg.get("y_origin_mm", 0.0))
|
||||
x_label = _index_to_alpha(x_idx) if cfg["x_alpha"] else str(x_idx)
|
||||
y_label = _index_to_alpha(y_idx) if cfg["y_alpha"] else str(y_idx)
|
||||
return cfg["format"].format(x=x_label, y=y_label)
|
||||
|
||||
Reference in New Issue
Block a user