diff --git a/CLAUDE.md b/CLAUDE.md index 882a3e7..ab00d03 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -183,7 +183,7 @@ Abhaengigkeiten: `ezdxf`, Standard-Library. Benoetigen Umgebungsvariablen (`DXFM | `omniflo_utils.py` | Gemeinsame Hilfsfunktionen fuer Omniflo-Skripte: `ROW_GROUPS` (Reihen-Gruppierung), `SWITCH_FILTERS` (Schalter-Filter), `build_row_layout`, `import_element_as_block`, `draw_cross`, `load_omniflo_data` | | `set_einfuegepkt.py` | Setzt `$INSBASE` (Einfuegepunkt) in Omniflo-DXF-Dateien. Schalter je Typ: `--boegen`, `--weichen45`, `--weichen90`, `--weichenkoerper`, `--weichen-parallel`, `--delta`, `--dreifachweiche`, `--sternweiche`, `--show-omniflo` | | `set_koords.py` | Setzt Koordinatensystem-Bloecke (K1-K4) in Omniflo-DXF-Dateien. Schalter: `--k1set` bis `--k4set`, `--set-all`, `--force`, `--show-omniflo`, `--test`, `--number` | -| `export_sivas.py` | Erzeugt Sivas-Export-CSV mit Summierungszeilen (ILS Automation, Omniflo Sum). Omniflo-Elemente werden ueber das TEILEART-Attribut (`Omniflo *`) erkannt, ihre Sivas-Nummern kommen - wie beim Geruest - NUR aus dem kommagetrennten `ARTINR`-Attribut (eine Zeile je Nummer, TEF-Teile `0_B*` als `Omniflo TEF`); KEIN Katalog-Lookup mehr (den nutzt nur noch `export_csv.py` fuer die Merkmale). Aufruf: `python export_sivas.py ` | +| `export_sivas.py` | Erzeugt Sivas-Export-CSV mit Summierungszeilen (ILS Automation, Omniflo Sum). Omniflo-Elemente werden ueber das TEILEART-Attribut (`Omniflo *`) erkannt, ihre Sivas-Nummern kommen - wie beim Geruest - NUR aus dem kommagetrennten `ARTINR`-Attribut (eine Zeile je Nummer, TEF-Teile `0_B*` als `Omniflo TEF`); KEIN Katalog-Lookup mehr (den nutzt nur noch `export_csv.py` fuer die Merkmale). Schreibt zusaetzlich immer `_zusammengefasst.csv`: Zeilen, die sich nur in Nr/Anzahl/IDs unterscheiden (v.a. Geruest, je Block eine Zeile), zu einer Zeile zusammengefasst (`items_zusammenfassen`, Anzahl addiert, IDs vereinigt); die Hauptdatei bleibt unveraendert. Aufruf: `python export_sivas.py ` | | `export_csv.py` | Erzeugt einfache Item-Liste als CSV (ohne Summierung). Aufruf: `python export_csv.py ` | | `split_block_library.py` | Teilt `ils_library.dxf` in einzelne DXF-Dateien pro Block auf (mit Abhaengigkeiten). Export-Prefixe: Vario_, Staustrecke_, _3D_AS_, _3D_ES_ | | `vf_journal_export.py` | Liest die Eingabe-Journale der `VF_n`-Bloecke (XDATA-App `SSG_VF_EDIT`, Marker `"linienzug"`) aus einer Zeichnung und schreibt sie als Frage-Antwort-Protokoll im Schema von `tests/testdata/linienzug_tests.json` (Gegenstueck zu `vfl-entry->string`; kein ezdxf, reine Gruppencode-Lesung). Optional `--csv ` fuer Strecken-ID und Erwartungswerte je Kette. Aufruf: `python vf_journal_export.py [--csv ...] [--kein-startpunkt-fix]` — erzeugt `tests/testdata/hm_recformat.json` aus `data/polylines.dxf` (die Aufzeichnung, aus der `tests/testdata/hundm05.json` als Spec abgeleitet wird) | diff --git a/Lisp/export.lsp b/Lisp/export.lsp index 3f2e2a2..91a143e 100644 --- a/Lisp/export.lsp +++ b/Lisp/export.lsp @@ -118,7 +118,9 @@ ;; --- Sivas-Export zusaetzlich mit Klartext-Bezeichnung schreiben --- ;; Standard aus (nil): EXPORTSIVAS schreibt nur die eigentliche _sivas.csv -;; (fuer den Sivas-Import). Nur bei eingeschaltetem Schalter schreibt +;; (fuer den Sivas-Import) plus - immer, ohne Schalter - die Kopie +;; _sivas_zusammengefasst.csv (identische Zeilen zusammengefasst, Anzahl +;; addiert, siehe items_zusammenfassen in lib/export_sivas.py). Nur bei eingeschaltetem Schalter schreibt ;; lib/export_sivas.py zusaetzlich _sivas_mit_bezeichnung.csv (Merkmal- ;; Keys durch Klartext aus cfg/Merkmalsstamm_in_Sivas.xlsx ersetzt, reine ;; Lesehilfe zur manuellen Pruefung, wird von Sivas selbst nicht eingelesen). diff --git a/data/geruest/821004051.dwg b/data/geruest/821004051.dwg new file mode 100644 index 0000000..5f96208 Binary files /dev/null and b/data/geruest/821004051.dwg differ diff --git a/data/geruest/821004052.dwg b/data/geruest/821004052.dwg new file mode 100644 index 0000000..dc8a0fb Binary files /dev/null and b/data/geruest/821004052.dwg differ diff --git a/data/geruest/821004053.dwg b/data/geruest/821004053.dwg new file mode 100644 index 0000000..866664f Binary files /dev/null and b/data/geruest/821004053.dwg differ diff --git a/data/geruest/821004054.dwg b/data/geruest/821004054.dwg new file mode 100644 index 0000000..111546b Binary files /dev/null and b/data/geruest/821004054.dwg differ diff --git a/data/geruest/825002007.dwg b/data/geruest/825002007.dwg new file mode 100644 index 0000000..881bf45 Binary files /dev/null and b/data/geruest/825002007.dwg differ diff --git a/data/geruest/825002008.dwg b/data/geruest/825002008.dwg new file mode 100644 index 0000000..5a7d860 Binary files /dev/null and b/data/geruest/825002008.dwg differ diff --git a/data/geruest/825002009.dwg b/data/geruest/825002009.dwg new file mode 100644 index 0000000..b95b85b Binary files /dev/null and b/data/geruest/825002009.dwg differ diff --git a/data/geruest/825002019.dwg b/data/geruest/825002019.dwg new file mode 100644 index 0000000..69b322e Binary files /dev/null and b/data/geruest/825002019.dwg differ diff --git a/data/geruest/825002020.dwg b/data/geruest/825002020.dwg new file mode 100644 index 0000000..6ebfa26 Binary files /dev/null and b/data/geruest/825002020.dwg differ diff --git a/data/geruest/825002022.dwg b/data/geruest/825002022.dwg new file mode 100644 index 0000000..5fc9c61 Binary files /dev/null and b/data/geruest/825002022.dwg differ diff --git a/lib/export_sivas.py b/lib/export_sivas.py index df1120b..ccb3ead 100644 --- a/lib/export_sivas.py +++ b/lib/export_sivas.py @@ -48,6 +48,11 @@ Argumente: Pruefung, wird von Sivas selbst nicht eingelesen. Schalter im Dialog CSV_EXPORT_KONFIG ("Sivas Export mit Bezeichner"), Standard aus. + +Zusaetzlich wird IMMER "_zusammengefasst.csv" geschrieben: gleiches +Format, aber Zeilen, die sich nur in Nr/Anzahl/IDs unterscheiden, sind zu +einer Zeile zusammengefasst (Anzahl addiert, IDs vereinigt, Nr neu) - siehe +items_zusammenfassen. output.csv selbst bleibt unveraendert. """ import json @@ -921,6 +926,48 @@ def lesbarer_dateiname(output_csv): return f"{stamm}_mit_bezeichnung{endung}" +def zusammengefasster_dateiname(output_csv): + """Leitet den Dateinamen der zusammengefassten Kopie aus output_csv ab, + z.B. "500651_sivas.csv" -> "500651_sivas_zusammengefasst.csv".""" + stamm, endung = os.path.splitext(output_csv) + return f"{stamm}_zusammengefasst{endung}" + + +def items_zusammenfassen(items): + """Fasst Zeilen zusammen, die sich nur in Nr, Anzahl und IDs + unterscheiden: Anzahl wird addiert, die IDs werden (ohne Dubletten, in + Reihenfolge des Auftretens) vereinigt, Nr wird neu durchgezaehlt. + Reihenfolge der Gruppen = erstes Auftreten. + + Betrifft in der Praxis vor allem die Geruest-Zeilen (je Block und + Artikelnummer eine Zeile, siehe process_blocks) - die uebrigen Teilearten + sind dort bereits gruppiert, und ihre Zeilen unterscheiden sich meist in + Bezeichnung/details. Die Laenge gehoert zum Schluessel: Saeulen mit + unterschiedlicher Laenge bleiben getrennte Zeilen. + + Die Eingabe bleibt unveraendert (die Hauptdatei wird aus denselben + items geschrieben).""" + gruppen = {} + for item in items: + key = ( + item["teileart"], str(item["sivasnr"]), item["sivasnr_quoted"], + item["bezeichnung"], item["dispgruppe"], str(item["laenge_mm"]), + json.dumps(item["details"], ensure_ascii=False, sort_keys=True), + ) + g = gruppen.get(key) + if g is None: + g = dict(item, anzahl=0, ids=[]) + gruppen[key] = g + g["anzahl"] += safe_int(item["anzahl"], 0) + for i in item.get("ids", []): + if i and i not in g["ids"]: + g["ids"].append(i) + ergebnis = list(gruppen.values()) + for nr, g in enumerate(ergebnis, 1): + g["nr"] = nr + return ergebnis + + def main(): if len(sys.argv) < 4: print("Aufruf: python export_sivas.py [--mit-bezeichnung]") @@ -945,6 +992,17 @@ def main(): f.write(format_csv_line(item) + "\n") print(f"[export_sivas] CSV geschrieben: {output_csv} ({len(items)} Zeilen)") + # Zusaetzliche Datei mit zusammengefassten identischen Zeilen (siehe + # items_zusammenfassen) - output_csv selbst bleibt unveraendert. + items_zus = items_zusammenfassen(items) + output_csv_zus = zusammengefasster_dateiname(output_csv) + with open(output_csv_zus, "w", encoding="utf-8") as f: + f.write(header + "\n") + for item in items_zus: + f.write(format_csv_line(item) + "\n") + print(f"[export_sivas] Zusammengefasste CSV geschrieben: {output_csv_zus} " + f"({len(items_zus)} Zeilen)") + # Zweite, lesbare Kopie fuer die manuelle Pruefung (Merkmalnummer + # Bezeichnung aus cfg/Merkmalsstamm_in_Sivas.xlsx) - wird von Sivas # selbst NICHT eingelesen, nur output_csv ist der eigentliche Import. diff --git a/tests/test_unit.lsp b/tests/test_unit.lsp index e8485db..8991c61 100644 --- a/tests/test_unit.lsp +++ b/tests/test_unit.lsp @@ -789,6 +789,23 @@ ;; ------------------------------------------------------------ ;; TESTS: GeruestInsert.lsp - reine Hilfsfunktionen ;; ------------------------------------------------------------ +;; Anzahl der Artikel (Blatt-Eintraege, Gruppen "teile" rekursiv) ohne +;; sivas_id oder ohne farbig-Schluessel. assoc statt ssg-json-get: false +;; wird zu nil geparst, der Schluessel selbst muss aber vorhanden sein. +(defun tu-geruest-artikel-unvollstaendig (eintraege / n teile) + (setq n 0) + (foreach e eintraege + (setq teile (ssg-json-get e "teile")) + (if teile + (setq n (+ n (tu-geruest-artikel-unvollstaendig teile))) + (if (or (null (ssg-json-get e "sivas_id")) + (null (assoc "farbig" e))) + (setq n (1+ n))) + ) + ) + n +) + (defun tu-tests-geruest ( / _eintrag _gruppe _teile _konfig _rec _pfad _f _zurueck _bgtest _saeulen) (princ "\n[GeruestInsert]") @@ -938,16 +955,29 @@ (tu-eq "geruest/saeulen-vorhanden" T (if _saeulen T nil)) (if _saeulen (progn - (tu-eq "geruest/saeulen-varianten" 6 - (length (ssg-json-get _saeulen "geometrie_varianten"))) - (tu-eq "geruest/saeulen-anbauteile" 7 - (length (ssg-json-get _saeulen "anbaugruppen"))) + ;; Aufbau statt fester Anzahl - geruest.json wird noch gepflegt. + ;; Jeder Artikel braucht sivas_id und ein (auch false) farbig-Feld. + (tu-eq "geruest/saeulen-varianten" T + (if (ssg-json-get _saeulen "geometrie_varianten") T nil)) + (tu-eq "geruest/saeulen-varianten-artikel" 0 + (tu-geruest-artikel-unvollstaendig + (ssg-json-get _saeulen "geometrie_varianten"))) + (tu-eq "geruest/saeulen-anbauteile" T + (if (ssg-json-get _saeulen "anbaugruppen") T nil)) + (tu-eq "geruest/saeulen-anbauteile-artikel" 0 + (tu-geruest-artikel-unvollstaendig + (ssg-json-get _saeulen "anbaugruppen"))) (tu-eq "geruest/saeulen-titel" "Saeulen" (geruest-titel _saeulen)) - ;; R 1" ist die erste Variante und traegt die eigene TEILEART - (tu-eq "geruest/saeulen-1zoll-teileart" "Geruest Saeule 1 Zoll" - (geruest-variante-wert - (car (ssg-json-get _saeulen "geometrie_varianten")) - _saeulen "teileart")) + ;; Variante R 1" traegt die eigene TEILEART - per Name gesucht, + ;; die Reihenfolge in geruest.json ist nicht fest. + (setq _rec (geruest-name-index "R 1\"" + (ssg-json-get _saeulen "geometrie_varianten"))) + (tu-eq "geruest/saeulen-1zoll-vorhanden" T (if _rec T nil)) + (if _rec + (tu-eq "geruest/saeulen-1zoll-teileart" "Geruest Saeule 1 Zoll" + (geruest-variante-wert + (nth _rec (ssg-json-get _saeulen "geometrie_varianten")) + _saeulen "teileart"))) ) )