Merge remote-tracking branch 'origin/master'

This commit is contained in:
Yuelin Wang
2026-09-18 17:47:53 +02:00
39 changed files with 3487 additions and 824 deletions
+4
View File
@@ -190,6 +190,10 @@ data/block_libraries/ils_library.dxf
# Tool-Caches
.skylos/
# Eigene Geruest-Konfigurationen (vom Dialog GERUEST_* geschrieben).
# Benutzerdaten - die gepflegte Bibliothek steht in data/json/geruest.json.
cfg/geruest_user.json
# Grosse Test-Zeichnungen (Arbeitsstaende, gehoeren nicht ins Repo)
/tests/test.dwg
/tests/test.dxf
+4 -3
View File
@@ -75,7 +75,7 @@ Die `start_briscad.bat` ruft `setenv.bat` auf und startet BricsCAD.
| VarioFoerderer | `VarioFoerderer` | FOERDERANLAGE, VARIOFOERDERER_EDIT, Vario_Kette_Merge |
| Gefaellestrecke | `Gefaellestrecke` | GEFAELLESTRECKE, GEFAELLESTRECKE_EDIT |
| Omniflo (komplett) | `OmniModulInsert` | OMNI_APB_*, OMNI_W*_*, OMNI_AP*, OMNI_TEF_*, OMNI_TV_*, OMNI_EDIT |
| Geruest | `GeruestInsert` | GERUEST_ROHRABHAENGER, GERUEST_SAEULE_2ZOLL, GERUEST_SAEULE_1ZOLL, GERUEST_WANDBEFESTIGUNG |
| Geruest | `GeruestInsert` | GERUEST (ein Dialog fuer alle Bereiche) |
| Connections | `Connection_Edit` + `SSG_LIB_Commands` | CONNECTION_INSERT, CONNECTION_EDIT, ILS_EINGANG_INSERT, ILS_AUSGANG_INSERT |
| Export | `export` | EXPORTSIVAS, EXPORTCSV, CALLPYTHON |
| Konfiguration | `SSG_LIB_Commands` + `DrawPlanquadrate` | SSG_DIM_SWITCH, SSG_SPRACHE, PLANQUADRATE_TOGGLE (DRAW_PLANQUADRATE/DELETE_PLANQUADRATE) |
@@ -107,7 +107,7 @@ Detaillierte Dokumentation aller Funktionen und Befehle: siehe `Lisp/README.md`.
| `OmniModulInsert.lsp` | OMNI_LOAD, OMNI_APB_*, OMNI_W*_*, OMNI_AP60/AP110, OMNI_TEF_*, OMNI_TV_*, OMNI_APBW_*, OMNI_EDIT | Omniflo-Komponenten: Boegen, Weichen, Verbinder, Transferwagen, Edit-Dialog |
| `SSG_LIB_Commands.lsp` | ILS_BTMT_Beladung, ILS_BTMT_Entladung, ILS_SCANNER_INSERT, ILS_SEPARATOR_INSERT, ILS_EINGANG_INSERT, ILS_AUSGANG_INSERT, ILS_ETF_*, ILS_GK*_*, SSG_BLOCKEDIT, SSG_DIM_SWITCH, SSG_SPRACHE | BTMT Be-/Entladung (`ils-insert-station`: wiederholtes Einfuegen von `BTMT-Beladung` bzw. `SC_Entladung`, Drehung interaktiv, Layer aus `LAYER`-Attribut, Dimension via `ssg-ils-block-laden`) + Sensoren (`ils-insert-sensor`) + Eingang-/Ausgang-Symbol (frei platzierbar wie BTMT Be-/Entladung, ueber `ils-insert-station` mit den Rohnamen `Eingang`/`Ausgang`, Bloecke `Eingang_<DIM>.dwg`/`Ausgang_<DIM>.dwg` in data/ils) + Dummy-Befehle fuer Menue + Doppelklick/Kontextmenue-Dispatcher + Konfiguration (2D/3D, Sprache) |
| `Tro_Edit.lsp` | TRO_INSERT, TRO_EDIT | TRO-Marker (Block `TRO_SYM_<Typ>`) fuer sps_skel (lib/tro_annotate.py): TRO_INSERT fuegt eine bestehende Blockdefinition an einem gewaehlten Punkt ein und setzt ID/TYPE (bricht ab, wenn der Typ noch keine Blockdefinition in der Zeichnung hat - die entsteht nur ueber tro_annotate.py); TRO_EDIT aendert ID/TYPE eines bestehenden Markers per Dialog. Typliste aus `tro_types.lsp` (von `tro_annotate.py --emit-lisp` nach DXFM_LISP geschrieben). Der Dispatcher SSG_BLOCKEDIT leitet Blocknamen `TRO_*` zu TRO_EDIT. |
| `GeruestInsert.lsp` | GERUEST_ROHRABHAENGER, GERUEST_SAEULE_2ZOLL, GERUEST_SAEULE_1ZOLL, GERUEST_WANDBEFESTIGUNG | Geruest-Baugruppen aus `data/json/geruest.json` konfigurieren und einfuegen. Dialog (`dcl/geruest.dcl`): Basis-Variante aus `geometrie_varianten` (Dropdown), Anbauteile aus `anbaugruppen` (`list_box` mit `multiple_select` - im Projekt der erste), Laenge (gegen `attribute.Laenge` min/max geprueft, Retry-Schleife) und Farbe (`attribute.Farbe`, Freitext bei `frei_option`). Baugruppen ohne `attribute`-Block (Wandbefestigung) bekommen Laenge/Farbe gesperrt. Der Block wird - wie KREISEL_n/VF_n - SELBST zusammengebaut: Dummy-DWG aus `data/geruest` liefert nur Geometrie, das Attribut-Schema kommt aus `*geruest-attrib-defs*` (ARTINR, LAENGE, BEZEICHNUNG, TEILEART, FARBE, LAYER) und wird per `ssg-attrib-merge` mit den Dummy-Tags zusammengefuehrt - sonst verschluckt `ssg-attrib-set-on` stillschweigend jeden Tag, den die Dummy-DWG nicht als ATTDEF kennt. Blockname `GR_<id>_<ssg-timestamp>` (Kollisionszaehler wie in OmniModulInsert). **Das `GR_`-Praefix ist nicht kosmetisch**: `SSG_BLOCKEDIT` haelt JEDEN Block mit TEILEART-Attribut fuer ein Omniflo-Element, deshalb steht in `SSG_LIB_Commands.lsp` ein `GR_*`-Zweig VOR dem Omniflo-Zweig. ARTINR listet jede Sivas-ID so oft, wie `menge` es vorgibt (benannte Gruppen mit `teile` werden rekursiv aufgeloest); BEZEICHNUNG verkettet mit `" + "`, NICHT mit Komma - die Artikelbezeichnungen enthalten selbst Kommas ("ROHR R 1\" 33,7 x 2,6"). Zuordnung Menue -> JSON laeuft ueber das ASCII-Feld `id`, weil die aeusseren JSON-Schluessel Umlaute bzw. Anfuehrungszeichen tragen. CSV-/Sivas-Export: `pattern_geruest = GR_*` (cfg/export.cfg), `csv:collect-export-blocks`/`ssg-id-collect-blocks` sammeln GR_-Bloecke wie BTMT eigenstaendig; `*geruest-attrib-defs*` traegt zusaetzlich ein leeres `ID`-ATTDEF (Voraussetzung fuer `ssg-id-check-all`, das nur bestehende ATTRIBs beschreiben kann). ARTINR/BEZEICHNUNG/TEILEART/LAENGE landen in export_csv.py/export_sivas.py in festen Spalten, FARBE/LAYER (+ ARTINR bei export_csv.py) im Merkmale-/details-JSON. Noch NICHT enthalten: Edit-Pfad (GERUEST_EDIT) |
| `GeruestInsert.lsp` | GERUEST | Geruest-Baugruppen aus `data/json/geruest.json` konfigurieren und einfuegen. **Ein Dialog fuer alle Bereiche**: oben ein Dropdown `bereich` (Rohrabhaenger / Saeulen / Wandbefestigung, Reihenfolge = Dateireihenfolge, Anzeigename aus dem ASCII-Feld `titel`). DCL hat **kein Reiter-Tile**, und da sich die Teilezeilen je Bereich unterscheiden, schliesst ein Bereichswechsel den Dialog (`done_dialog 4`) und die Schleife in `c:GERUEST` baut ihn neu auf; der Stand jedes Bereichs bleibt in `*geruest-zustand*` erhalten, ein versehentlicher Wechsel verwirft also nichts. 1"- und 2"-Saeulen liegen in EINER Baugruppe `Saeulen` mit gemeinsamer Anbauteil-Liste; **`teileart` und `dummy` duerfen je Geometrie-Variante ueberschrieben werden** (`geruest-variante-wert`, Variante gewinnt vor Baugruppe) - so liefert `R 1"` weiterhin TEILEART `Geruest Saeule 1 Zoll` und die `R 2"`-Varianten `Geruest Saeule 2 Zoll`, die Groessen bleiben im Export also unterscheidbar. **Die Dialogdefinition wird zur Laufzeit erzeugt** (`geruest-dcl-schreiben` -> temporaere Datei per `vl-filename-mktemp`, nach `unload_dialog` wieder geloescht) - es gibt KEINE `dcl/geruest.dcl`. Grund: je Anbauteil wird ein eigenes Mengenfeld gebraucht, und eine DCL-Liste (`list_box`) kann keine Eingabefelder enthalten; DCL kann weder Dateien einbinden noch Tiles zur Laufzeit erzeugen. Inhalt: Basis-Variante aus `geometrie_varianten` (Dropdown), Laenge (gegen `attribute.Laenge` min/max geprueft, Retry-Schleife), Farbe (`attribute.Teilefarbe`, Freitext bei `frei_option`) und **zwei Befestigungslisten** ("oben"/"unten"), die BEIDE alle `anbaugruppen` enthalten - dasselbe Teil kann oben, unten oder gleichzeitig an beiden Positionen sitzen, je mit eigener Stueckzahl (vorbelegt aus `menge`). Baugruppen ohne `attribute`-Block (Wandbefestigung) bekommen Laenge/Farbe gesperrt, Baugruppen ohne Anbauteile bekommen gar keine Spalten (ein leerer DCL-Cluster ist unzulaessig). Im generierten Label wird `"` zu `''` ersetzt (`geruest-dcl-label`) - fuenf Artikelnamen enthalten ein Anfuehrungszeichen, das das DCL-Label sonst vorzeitig beendet und die Datei zerstoert. **Konfigurationen**: gepflegte Bibliothek je Baugruppe unter `konfigurationen` in `geruest.json` (schreibgeschuetzt) plus eigene in `cfg/geruest_user.json` (gitignored, `geruest-user-schreiben`; getrennte Datei, damit Benutzerdaten und versionierte Bibliothek nicht kollidieren). Eigene Eintraege sind in der Liste mit `*` markiert und nur diese loeschbar. Konfigurationen verweisen ueber den ANZEIGENAMEN auf Variante und Anbauteile; ein unbekannter Name wird gemeldet und uebersprungen, statt die Konfiguration scheitern zu lassen. Speichern/Loeschen laufen ueber `done_dialog 2`/`3` (Dialog schliessen, Aktion, neu zeigen) - `getstring` funktioniert aus einem `action_tile`-Rueckruf heraus nicht. Der Block wird - wie KREISEL_n/VF_n - SELBST zusammengebaut: Dummy-DWG aus `data/geruest` liefert nur Geometrie, das Attribut-Schema kommt aus `*geruest-attrib-defs*` (ID, ARTINR, LAENGE, BEZEICHNUNG, TEILEART, TEILEFARBE, LAYER) und wird per `ssg-attrib-merge` mit den Dummy-Tags zusammengefuehrt - sonst verschluckt `ssg-attrib-set-on` stillschweigend jeden Tag, den die Dummy-DWG nicht als ATTDEF kennt. Blockname `GR_<id>_<ssg-timestamp>` (Kollisionszaehler wie in OmniModulInsert). **Das `GR_`-Praefix ist nicht kosmetisch**: `SSG_BLOCKEDIT` haelt JEDEN Block mit TEILEART-Attribut fuer ein Omniflo-Element, deshalb steht in `SSG_LIB_Commands.lsp` ein `GR_*`-Zweig VOR dem Omniflo-Zweig. ARTINR listet jede Sivas-ID so oft, wie die gewaehlte Stueckzahl es vorgibt - erst Basis, dann Befestigung oben, dann unten; bei einer benannten Gruppe (`teile`) wird das ganze Set n-mal wiederholt, die Mengen INNERHALB des Sets bleiben dabei erhalten (`geruest-artikelnummern-n`). BEZEICHNUNG nennt Position und Menge je Teil ("oben 2x ...") und verkettet mit `" + "`, NICHT mit Komma - die Artikelbezeichnungen enthalten selbst Kommas ("ROHR R 1\" 33,7 x 2,6"). Zuordnung Menue -> JSON laeuft ueber das ASCII-Feld `id`, weil die aeusseren JSON-Schluessel Umlaute bzw. Anfuehrungszeichen tragen. CSV-/Sivas-Export: `pattern_geruest = GR_*` (cfg/export.cfg), `csv:collect-export-blocks`/`ssg-id-collect-blocks` sammeln GR_-Bloecke wie BTMT eigenstaendig; `*geruest-attrib-defs*` traegt zusaetzlich ein leeres `ID`-ATTDEF (Voraussetzung fuer `ssg-id-check-all`, das nur bestehende ATTRIBs beschreiben kann). ARTINR/BEZEICHNUNG/TEILEART/LAENGE landen in export_csv.py/export_sivas.py in festen Spalten, TEILEFARBE/LAYER (+ ARTINR bei export_csv.py) im Merkmale-/details-JSON. Noch NICHT enthalten: Edit-Pfad (GERUEST_EDIT) |
| `menu-inventory.lsp` | MenuInventory | BricsCAD Diagnostik (Menuegruppen, Plugins, Startdateien) |
| `DrawPlanquadrate.lsp` | DRAW_PLANQUADRATE, DELETE_PLANQUADRATE, PLANQUADRATE_TOGGLE | Zeichnet auf Z=0 ein Planquadrat-Linienraster (senkrecht/waagerecht), im Abstand/Zaehlschema der aktiven `[Planquadrate]`-Konfiguration aus `cfg/export.cfg` (dieselbe Quelle wie `lib/export_planquadrat.py` fuer den CSV-Export). Bereich kommt NICHT aus den Zeichnungs-Extents, sondern aus derselben Blockmenge wie EXPORTCSV (`csv:collect-export-blocks`, export.lsp; siehe `plq-export-bbox`): Ursprung (Index 1) UND obere Grenze kommen BEIDE aus der tatsaechlichen Bounding-Box jedes Blocks (`csv:get-bbox`, wie `bbox_columns()`/`planquadrat_column()`/`_bbox_min()` in `lib/export_csv.py` bzw. `lib/export_planquadrat.py`), NICHT aus dem reinen Einfuegepunkt - bei einem gedrehten/exzentrisch eingefuegten Block (z.B. ein um 90 Grad gedrehter Kreisel) kann das Bounding-Box-Zentrum weit vom Einfuegepunkt abweichen; Ursprung und Zellzuordnung muessen auf derselben Referenz beruhen, sonst driften sie auseinander (realer Bug, behoben). Reine Hilfsgeometrie (Kreisel-Umfahrungsboegen als eigene Linien, Texte) zaehlt bewusst nicht mit, sonst weicht das Raster vom CSV-Export ab. `x_origin`/`y_origin` ueberschreiben den Ursprung, Fallback auf EXTMIN/EXTMAX falls keine Export-Bloecke vorhanden sind bzw. `csv:get-bbox` fuer einen Block scheitert. Spalten-/Zeilenzahl wird IMMER aufgerundet auf volle Rasterschritte, damit alle senkrechten Linien auf der letzten waagerechten enden und umgekehrt - das Raster bildet immer ein geschlossenes Rechteck. Zaehlung (alphabetisch/numerisch je Achse) als Text am linken/unteren Rand. Das fertige Raster (Linien+Texte) wird per `_.-BLOCK`/`_.INSERT` zu einem einzigen Block `ZZ_PLANQUADRATE` bei (0,0,0) zusammengefasst. Layer aus `cfg/layer.cfg` (`[allgemein]` Key `planquadrate`, Fallback `ZZ_Planquadrate`). DRAW_PLANQUADRATE entfernt vor dem Neuaufbau zuerst einen evtl. vorhandenen Block (`plq-block-entfernen`: INSERTs loeschen + `_-PURGE _B`), DELETE_PLANQUADRATE ruft nur diese Entfernung auf. PLANQUADRATE_TOGGLE ("Planquadrate an/aus", Menue: Konfiguration) prueft per `tblsearch`/`ssget "X"`, ob der Block existiert, und dispatcht auf Zeichnen bzw. Loeschen. Lazy geladen ueber `(ssg-ensure "DrawPlanquadrate")`, nicht in der Core-Ladereihenfolge. Die Planquadrat-SPALTE im CSV-Export (`export_csv.py::planquadrat_column`/`resolve_origins`, `lib/export_planquadrat.py::compute_planquadrat`/`_bbox_min`) rechnet ebenfalls durchgaengig auf Bounding-Box-Basis (nicht nur Einfuegepunkt) und liefert eine SPANNE ("A/1-J/1") statt nur einer Zelle, wenn ein Element mehr als eine Zelle ueberdeckt (Kreisel, lange VF-/GF-Ketten). |
@@ -123,7 +123,6 @@ Detaillierte Dokumentation aller Funktionen und Befehle: siehe `Lisp/README.md`.
| `sprache.dcl` | SSG_SPRACHE (SSG_LIB_Commands.lsp) |
| `tro_insert.dcl` | TRO_INSERT (Tro_Edit.lsp) |
| `tro_edit.dcl` | TRO_EDIT (Tro_Edit.lsp) |
| `geruest.dcl` | Alle GERUEST_*-Befehle (GeruestInsert.lsp, `geruest-dialog`) - ein Dialog fuer alle Baugruppen, Listen zur Laufzeit gefuellt |
| `vfl_edit.dcl` | Linienzug-Editieren/Fortsetzen (vf_linienzug.lsp, `vfl-dlg-position`, aufgerufen aus `vfl-edit-ent`) |
| `vf_linienzug_wizard.dcl` | Linienzug-Assistent (Modus 1, 2 UND 3): zwei generische Einzel-Dialoge (Zahl-Eingabe, Mehrfachauswahl; `vflw-zahl`/`vflw-wahl`, aufgerufen aus `vfl-in-real`/`vfl-in-string`/`vfl-in-int` bei aktivem `*vfl-wizard-mode*`) PLUS sieben Gruppen-Dialoge, die mehrere immer unmittelbar aufeinanderfolgende Fragen in einem Bildschirm zusammenfassen (Kettenstart, AS-Element, GF-Bogen, ES-Element, Vario-Kurve, Gefaelle festlegen, Ziel-Hoehe, Horizontales Stueck - inkl. Punktwahl-Button mit X/Y/Z-Anzeige; `vflw-gruppe-*-impl` in vf_linienzug.lsp fuellen dafuer vorab eine kleine Pending-Queue `*vflw-pending*`, die einzelnen `vfl-in-*`-Aufrufe bleiben dabei unveraendert) PLUS drei Segment-Gruppen-Dialoge fuer die segmentweise Klassifizierung in Modus 2/3 (`vflw_seg_linie_m2` = Typ+GF-Neigung, `vflw_seg_linie_m3` = 4-fach-Typ+kontextabh. Wertfeld, `vflw_seg_bogen` = Typ+Variante; `vflw-seg-*-impl`). Beide Modi heben vor der Frage das aktuelle Pfad-Segment (LINE/ARC) per `vfl-seg-highlight` (`redraw`) in der Zeichnung hervor und zeigen "Segment i/n ..." im Dialogkopf. Details: `doc/variofoerderer.md` |
@@ -155,6 +154,8 @@ Werden via `bin/setenv.bat` gesetzt. Wichtigste:
- **AutoLISP `(and ...)`/`(or ...)` geben `T`/`nil` zurueck, NICHT den letzten Wert** (anders als Common Lisp/Scheme). Nie einen Nicht-Boolean-Wert (String, Entity, Liste) durch `and`/`or` faedeln - dafuer `if`/`cond` verwenden. Realer Bug-Fall: `(setq eff (and blockname (ssg-ils-block-laden blockname)))` setzte `eff` auf `T` statt auf den Blocknamen-String und liess ein spaeteres `_.INSERT`/`strcat` mit `bad argument type <T>` abstuerzen. Beim Debuggen mysterioeser Typfehler `(setq .* (and`/`(setq .* (or` durchsuchen.
- **Werte selbstbeschreibend fuehren, nicht als positionsabhaengige Indizes/Flags** - wenn ein spaeterer Konsument (Journal-Replay, Export, Edit) den Wert interpretiert. Beispiel: Winkel ueberall als ECHTER Wert `30`/`60`/`90` journalisieren, nicht als Auswahl-Index `1`/`2`/`3` - ein als Index missverstandener String `"30"` fuehrte real zum falschen 90-Grad-Bogen (VF-Linienzug). Gleiche Klasse wie die `and`/`or`-Typfalle: der Fehler faellt erst beim entfernten Konsumenten auf.
- **Wiederverwendung vor Neuschaffung bei LISP-Routinen**: Neue Funktionen moeglichst als Erweiterung/Ergaenzung bestehender Funktionen anlegen. Bevor eine NEUE Bibliothek (`.lsp`-Datei) geschaffen wird, immer zuerst pruefen, ob es bereits thematisch passende Routinen gibt, in die die Funktionalitaet gehoert (z.B. Einfuege-Primitiven in `ssg_ks_insert.lsp`, Kern-Helfer in `ssg_core.lsp`, Konstanten in `vf_konstanten.lsp`). Erst wenn nichts passt, ein neues Modul.
- **Neue LISP-Routinen nach Moeglichkeit in die LISP-Testsuite aufnehmen**: Jede neue LISP-Routine soll moeglichst einen Test bekommen. **Vorrang haben Routinen, die OHNE Zeichnung/Dateien testbar sind** (reine Berechnung, Parser, String-/Listen-Helfer, cfg-/JSON-Aufloesung) - die gehoeren als `tu-*`-Testblock in `tests/test_unit.lsp` (Befehl `TEST_UNIT`), der komplett file- und zeichnungsfrei laeuft. Zeichnungs-/geometrieabhaengige Routinen ueber ein Szenario-Modul (`tests/test_<name>.lsp` + Eintrag in `tests/alltests.json`, ausgefuehrt von `SSG_RUN_ALL_TESTS`) abdecken. Die reinen Berechnungspfade einer solchen Routine trotzdem, wo moeglich, zusaetzlich als file-freien Unit-Test herausziehen.
- **Headless-Testlauf**: Die Unit-Tests laufen ohne GUI ueber `bin/run_tests.bat --lisp` - das startet BricsCAD im Batch-Modus (`/nologo /b`) mit `tests/test_unit.scr` und fuehrt `TEST_UNIT` aus. **BricsCAD steht dafuer ueber die Umgebungsvariable `BRICSCAD` zur Verfuegung** (in `bin/setenv.bat` gesetzt, auf die installierte `bricscad.exe` zeigend); `run_tests.bat` bricht mit Hinweis ab, wenn sie fehlt. Ein neuer file-freier Unit-Test ist damit auch ohne interaktive BricsCAD-Sitzung fahrbar. (Python-seitige Tests siehe `.venv` + `pytest tests/`.)
- **Regelmaessiges Tote-Code-Review**: Wiederkehrend auf tote/nicht mehr verwendete LISP-Routinen pruefen und aufraeumen - vorzugsweise zu Wochenbeginn, wenn in der betreffenden Woche ohnehin an den LISP-Routinen gearbeitet wird.
- **Wiederherstellungs-/Editierdaten gehoeren in XDATA/Journal, nicht in `.dbg`**: Alles, was zum Neuaufbau/Editieren eines Blocks noetig ist (z.B. das VF-Linienzug-Eingabe-Journal `SSG_VF_EDIT`), wird als XDATA am Block gefuehrt. Die `.dbg`-Dateien (`ssg_dbg.lsp`) sind reine AUSGABE, werden von keinem Befehl gelesen und bei jedem Lauf neu angelegt - nie als Datenquelle nutzen.
- **Editierbarkeit: festes Schema vs. Sektions-Ruecksprung**: Konstrukte mit FESTER Feldstruktur (feste Anzahl/Typen von Werten, z.B. AS/ES/GF-Bogen/Vario-Kurve im VF-Linienzug) ueber ein deklaratives Schema als einzeln editierbare Records fuehren (Einzel-Glied-Edit per Feld-Splice). Konstrukte mit VARIABLEM Dialog-Fluss (datenabhaengige Feldzahl, z.B. Horizontal-Koerper/Auf-Ab-Foerderer) NICHT per fragilem "n Felder abschneiden"-Splice editieren, sondern per Sektions-Ruecksprung: ab dem gewaehlten Glied zuruecksetzen und live neu aufbauen. Ein fester Splice ueber eine datenabhaengige Feldgrenze ist genau das Muster des Winkel-Index-Bugs.
+930 -152
View File
File diff suppressed because it is too large Load Diff
+284 -202
View File
@@ -605,59 +605,20 @@
)
)
;;; ============================================================
;;; ENGINE - wird sowohl von ZAEHLE_SEP_SCAN als auch automatisch
;;; vor jedem CSV-/Sivas-Export aufgerufen (siehe export.lsp,
;;; csv:run-export).
;;;
;;; label = Anzeigepraefix fuer die Konsolenausgabe (z.B. "EXPORTCSV").
;;; verbose = T -> zusaetzlich je Carrier eine Detailzeile ausgeben
;;; (nur fuer den interaktiven Befehl sinnvoll - der
;;; automatische Aufruf aus csv:run-export laesst dies aus,
;;; damit die Export-Konsole kompakt bleibt).
;;;
;;; Setzt als Nebenwirkung:
;;; *cs-scanner-ergaenzt* - Liste (scanner-id carrier-id
;;; distanz-mm strittig-p
;;; zweiter-carrier-id zweite-distanz-mm)
;;; je per Abstand (nicht per BBox)
;;; zugeordnetem Scanner.
;;; *cs-scanner-warnung-by-handle* - Assoc-Liste (handle . warnungstext)
;;; fuer strittige Scanner-Zuordnungen,
;;; von csv:block-to-json ausgelesen.
;;; *cs-korrekturen* - Liste (typ-wort sensor-id alt-id
;;; neu-id) je Sensor, dessen gespeicherte
;;; ZUORDNUNG nicht zur Geometrie passte
;;; und ueberschrieben wurde (Report).
;;; ============================================================
(defun cs-zuordnung-lauf (label verbose /
mss ss i ent ed nm bb cx cy lbl carrier-id
carriers grid scanlist seplist sk pt
scanCounts sepCounts n-scan-unassigned n-sep-unassigned
n-strittig n-zuordnung-fehlt alt
s sent spt res mode rec best-d second-rec second-d
vorgabe-id vorgabe-rec
scanner-id contested handle
en nm2 ns nsep newS newP okS okP eintrag)
(setq carriers nil scanlist nil seplist nil
n-zuordnung-fehlt 0 n-scan-unassigned 0 n-sep-unassigned 0 n-strittig 0)
(setq *cs-scanner-ergaenzt* nil)
(setq *cs-scanner-warnung-by-handle* nil)
(setq *cs-korrekturen* nil)
(setq *senstol* (cs-senstol))
;; Alte Markierungskreise entfernen (Idempotenz - Sensoren, die in
;; einem frueheren Lauf unzuordenbar waren, koennen inzwischen einen
;; Carrier bekommen haben oder umgekehrt).
(setq mss (ssget "_X" (list (cons 0 "CIRCLE") (cons 8 *mark-layer*))))
(if mss (command "_.ERASE" mss ""))
(vl-catch-all-apply '(lambda () (command "_.REGEN")))
(princ (ssg-textf "sens-start" (list label)))
;;; ------------------------------------------------------------
;;; Alle INSERTs der Zeichnung in Carrier-, Scanner- und Separator-
;;; Listen einsortieren.
;;; Rueckgabe: (carriers scanlist seplist)
;;; carriers = Liste von Records (ename tagScan tagSep minx miny
;;; maxx maxy cx cy id label)
;;; scanlist/seplist = Liste von (ename einfuegepunkt)
;;; Ein Carrier ohne ermittelbare Bounding-Box wird gemeldet und
;;; uebersprungen (kann sonst nicht zugeordnet werden).
;;; ------------------------------------------------------------
(defun cs-sammle-bloecke ( / ss i ent ed nm bb cx cy lbl carrier-id
sk pt carriers scanlist seplist)
(setq ss (ssget "_X" '((0 . "INSERT"))))
(if (null ss)
(princ (ssg-textf "sens-no-inserts" (list label)))
(if ss
(progn
(setq i 0)
(while (< i (sslength ss))
@@ -693,167 +654,288 @@
)
(setq i (1+ i))
)
)
)
(list carriers scanlist seplist)
)
(setq grid (cs-grid-build carriers))
(setq scanCounts nil sepCounts nil)
;;; ------------------------------------------------------------
;;; Abweichende gespeicherte ZUORDNUNG als Korrektur in
;;; *cs-korrekturen* vermerken, BEVOR sie ueberschrieben wird
;;; (Selbstheilung eingefrorener/verwaister Zuordnungen). typ-wort
;;; ist "Scanner" oder "Separator"; neu-id die geometrisch/vorgegeben
;;; ermittelte Carrier-ID.
;;; ------------------------------------------------------------
(defun cs-korrektur-vermerken (sent typ-wort neu-id / alt)
(setq alt (cs-get-att-str sent *zuordnung-tag*))
(if (and alt (/= alt "") (/= alt neu-id))
(setq *cs-korrekturen*
(cons (list typ-wort (cs-get-att-str sent "ID") alt neu-id)
*cs-korrekturen*)))
)
;; --- Scanner zuordnen (inkl. Distanz-Fallback + Strittig-Erkennung) ---
(foreach s scanlist
(setq sent (car s) spt (cadr s))
;;; ------------------------------------------------------------
;;; Strittige Distanz-Zuordnung eines Scanners protokollieren:
;;; Eintrag in *cs-scanner-ergaenzt* (immer) und - bei zu knappem
;;; Abstand zum zweitnaechsten Carrier - eine Warnung in
;;; *cs-scanner-warnung-by-handle* (von csv:block-to-json gelesen).
;;; Rueckgabe: T, wenn die Zuordnung strittig ist.
;;; ------------------------------------------------------------
(defun cs-scanner-distanz-melden (sent rec best-d second-rec second-d /
scanner-id contested handle)
(setq scanner-id (cs-get-att-str sent "ID"))
(setq contested (and second-rec
(<= (- second-d best-d) (cs-strittig-diff-mm))))
(setq *cs-scanner-ergaenzt*
(cons (list scanner-id (nth 9 rec) (fix (+ best-d 0.5))
contested
(if contested (nth 9 second-rec) nil)
(if contested (fix (+ second-d 0.5)) nil))
*cs-scanner-ergaenzt*))
(if contested
(progn
(setq handle (cdr (assoc 5 (entget sent))))
(setq *cs-scanner-warnung-by-handle*
(cons (cons handle
(strcat "Distanz-Zuordnung unsicher: "
(nth 9 rec) " (" (itoa (fix (+ best-d 0.5))) "mm) vs. "
(nth 9 second-rec) " (" (itoa (fix (+ second-d 0.5))) "mm)"))
*cs-scanner-warnung-by-handle*))
)
)
contested
)
;;; ------------------------------------------------------------
;;; Scanner den Carriern zuordnen (rein geometrisch: BBox, sonst
;;; naechster Carrier). Setzt je zugeordnetem Scanner das ZUORDNUNG-
;;; Attribut und fuellt *cs-scanner-ergaenzt*/*cs-scanner-warnung-by-
;;; handle* (Distanz-Fallback + Strittig-Report).
;;; Rueckgabe: (scanCounts n-scan-unassigned n-strittig n-zuordnung-fehlt)
;;; ------------------------------------------------------------
(defun cs-scanner-zuordnen (scanlist carriers grid /
scanCounts n-scan-unassigned n-strittig
n-zuordnung-fehlt s sent spt res mode rec
best-d second-rec second-d)
(setq scanCounts nil n-scan-unassigned 0 n-strittig 0 n-zuordnung-fehlt 0)
(foreach s scanlist
(setq sent (car s) spt (cadr s))
(setq res (cs-resolve-sensor spt carriers grid))
(setq mode (nth 0 res) rec (nth 1 res)
best-d (nth 2 res) second-rec (nth 3 res) second-d (nth 4 res))
(cond
((eq mode 'none)
(setq n-scan-unassigned (1+ n-scan-unassigned))
(cs-mark-unassigned sent))
((null rec) nil) ;; Sicherheitsnetz - tritt bei bbox/distance nicht auf
(t
(setq scanCounts (cs-bump scanCounts (car rec)))
(cs-korrektur-vermerken sent "Scanner" (nth 9 rec))
(if (member mode '(bbox distance))
(if (not (cs-set-att sent *zuordnung-tag* (nth 9 rec)))
(setq n-zuordnung-fehlt (1+ n-zuordnung-fehlt))))
(if (eq mode 'distance)
(if (cs-scanner-distanz-melden sent rec best-d second-rec second-d)
(setq n-strittig (1+ n-strittig))))
)
)
)
(setq *cs-scanner-ergaenzt* (reverse *cs-scanner-ergaenzt*))
(list scanCounts n-scan-unassigned n-strittig n-zuordnung-fehlt)
)
;;; ------------------------------------------------------------
;;; Separatoren den Carriern zuordnen. Wie cs-scanner-zuordnen, aber
;;; ohne Strittig-Report; zusaetzlich hat eine festgenagelte Zuordnung
;;; (verpackter Separator, *cs-sep-fix-by-handle*) Vorrang vor der
;;; Geometrie - nur wenn der genannte Carrier nicht (mehr) existiert,
;;; wird geometrisch zugeordnet. Bei mode 'vorgabe wird der geschriebene
;;; Wert NICHT als Korrektur gemeldet (er ist die Vorgabe selbst).
;;; Rueckgabe: (sepCounts n-sep-unassigned n-zuordnung-fehlt)
;;; ------------------------------------------------------------
(defun cs-separatoren-zuordnen (seplist carriers grid /
sepCounts n-sep-unassigned n-zuordnung-fehlt
s sent spt res mode rec vorgabe-id vorgabe-rec)
(setq sepCounts nil n-sep-unassigned 0 n-zuordnung-fehlt 0)
(foreach s seplist
(setq sent (car s) spt (cadr s))
(setq vorgabe-id (cdr (assoc (cdr (assoc 5 (entget sent))) *cs-sep-fix-by-handle*)))
(setq vorgabe-rec (cs-carrier-by-id vorgabe-id carriers))
(if vorgabe-rec
(setq mode 'vorgabe rec vorgabe-rec)
(progn
(setq res (cs-resolve-sensor spt carriers grid))
(setq mode (nth 0 res) rec (nth 1 res)
best-d (nth 2 res) second-rec (nth 3 res) second-d (nth 4 res))
(cond
((eq mode 'none)
(setq n-scan-unassigned (1+ n-scan-unassigned))
(cs-mark-unassigned sent))
((null rec) nil) ;; Sicherheitsnetz - tritt bei bbox/distance nicht auf
(t
(setq scanCounts (cs-bump scanCounts (car rec)))
;; Abweichende gespeicherte ZUORDNUNG als Korrektur vermerken,
;; bevor sie unten ueberschrieben wird (Selbstheilung eingefrorener
;; oder verwaister Zuordnungen).
(setq alt (cs-get-att-str sent *zuordnung-tag*))
(if (and alt (/= alt "") (/= alt (nth 9 rec)))
(setq *cs-korrekturen*
(cons (list "Scanner" (cs-get-att-str sent "ID") alt (nth 9 rec))
*cs-korrekturen*)))
(if (member mode '(bbox distance))
(if (not (cs-set-att sent *zuordnung-tag* (nth 9 rec)))
(setq n-zuordnung-fehlt (1+ n-zuordnung-fehlt))))
(if (eq mode 'distance)
(progn
(setq scanner-id (cs-get-att-str sent "ID"))
(setq contested (and second-rec
(<= (- second-d best-d) (cs-strittig-diff-mm))))
(setq *cs-scanner-ergaenzt*
(cons (list scanner-id (nth 9 rec) (fix (+ best-d 0.5))
contested
(if contested (nth 9 second-rec) nil)
(if contested (fix (+ second-d 0.5)) nil))
*cs-scanner-ergaenzt*))
(if contested
(progn
(setq n-strittig (1+ n-strittig))
(setq handle (cdr (assoc 5 (entget sent))))
(setq *cs-scanner-warnung-by-handle*
(cons (cons handle
(strcat "Distanz-Zuordnung unsicher: "
(nth 9 rec) " (" (itoa (fix (+ best-d 0.5))) "mm) vs. "
(nth 9 second-rec) " (" (itoa (fix (+ second-d 0.5))) "mm)"))
*cs-scanner-warnung-by-handle*))
)
)
)
)
)
)
(setq mode (nth 0 res) rec (nth 1 res))
)
(setq *cs-scanner-ergaenzt* (reverse *cs-scanner-ergaenzt*))
;; --- Separatoren zuordnen (gleiche Logik, ohne Strittig-Report -
;; der Distanz-Fallback stellt hier nur sicher, dass die
;; ANZAHL_SEPARATOR-Summe auch bei Sonderfaellen stimmt). ---
(foreach s seplist
(setq sent (car s) spt (cadr s))
;; Festgenagelte Zuordnung (verpackter Separator, Carrier bekannt -
;; siehe *cs-sep-fix-by-handle*) hat Vorrang vor der Geometrie. Nur
;; wenn der genannte Carrier gar nicht (mehr) existiert, wird
;; geometrisch zugeordnet.
(setq vorgabe-id (cdr (assoc (cdr (assoc 5 (entget sent))) *cs-sep-fix-by-handle*)))
(setq vorgabe-rec (cs-carrier-by-id vorgabe-id carriers))
(if vorgabe-rec
(setq mode 'vorgabe rec vorgabe-rec)
(progn
(setq res (cs-resolve-sensor spt carriers grid))
(setq mode (nth 0 res) rec (nth 1 res))
)
)
(cond
((eq mode 'none)
(setq n-sep-unassigned (1+ n-sep-unassigned))
(cs-mark-unassigned sent))
((null rec) nil) ;; Sicherheitsnetz - tritt bei bbox/distance nicht auf
(t
(setq sepCounts (cs-bump sepCounts (car rec)))
;; Abweichung nur bei geometrischer Zuordnung als Korrektur melden -
;; bei mode 'vorgabe ist der geschriebene Wert die Vorgabe selbst,
;; keine Korrektur einer Fehl-Zuordnung.
(setq alt (cs-get-att-str sent *zuordnung-tag*))
(if (and (not (eq mode 'vorgabe)) alt (/= alt "") (/= alt (nth 9 rec)))
(setq *cs-korrekturen*
(cons (list "Separator" (cs-get-att-str sent "ID") alt (nth 9 rec))
*cs-korrekturen*)))
(if (member mode '(vorgabe bbox distance))
(if (not (cs-set-att sent *zuordnung-tag* (nth 9 rec)))
(setq n-zuordnung-fehlt (1+ n-zuordnung-fehlt))))
)
)
)
(cond
((eq mode 'none)
(setq n-sep-unassigned (1+ n-sep-unassigned))
(cs-mark-unassigned sent))
((null rec) nil) ;; Sicherheitsnetz - tritt bei bbox/distance nicht auf
(t
(setq sepCounts (cs-bump sepCounts (car rec)))
(if (not (eq mode 'vorgabe))
(cs-korrektur-vermerken sent "Separator" (nth 9 rec)))
(if (member mode '(vorgabe bbox distance))
(if (not (cs-set-att sent *zuordnung-tag* (nth 9 rec)))
(setq n-zuordnung-fehlt (1+ n-zuordnung-fehlt))))
)
)
)
(list sepCounts n-sep-unassigned n-zuordnung-fehlt)
)
;; --- ANZAHL_SCANNER/ANZAHL_SEPARATOR je Carrier schreiben ---
(foreach rec carriers
(setq en (car rec))
(setq nm2 (cdr (assoc 2 (entget en))))
(setq ns (cond ((assoc en scanCounts) (cdr (assoc en scanCounts))) (t 0)))
(setq nsep (cond ((assoc en sepCounts) (cdr (assoc en sepCounts))) (t 0)))
(setq newS ns)
;; ANZAHL_SEPARATOR = nur die tatsaechlich platzierten Separator-
;; Symbole (Separator_SP/S-LP), die diesem Carrier zugeordnet sind.
;; Der intern in der GF/VF-Kette gezeichnete 300-mm-Block
;; Staustrecke_Separator_SP ist nur ein Kettenglied (Foerder-/
;; Uebergabestueck), KEIN eigenstaendiger Separator - er wird bewusst
;; NICHT mitgezaehlt (Entscheidung: nur platzierte Symbole zaehlen).
(setq newP nsep)
(setq okS (cs-set-att en (nth 1 rec) (itoa newS)))
(setq okP (cs-set-att en (nth 2 rec) (itoa newP)))
(if verbose
(princ (ssg-textf "sens-carrier-zeile"
(list nm2 (nth 9 rec) (itoa newS) (itoa newP)))))
(if (not (and okS okP))
(princ (ssg-textf "sens-carrier-fehler" (list nm2))))
;;; ------------------------------------------------------------
;;; ANZAHL_SCANNER/ANZAHL_SEPARATOR je Carrier schreiben.
;;; scanCounts/sepCounts = Assoc-Listen (carrier-ename . anzahl).
;;; ANZAHL_SEPARATOR = nur die tatsaechlich platzierten Separator-
;;; Symbole (Separator_SP/S-LP); der intern in der GF/VF-Kette
;;; gezeichnete 300-mm-Block Staustrecke_Separator_SP ist nur ein
;;; Kettenglied und wird bewusst NICHT mitgezaehlt.
;;; verbose = T -> je Carrier eine Detailzeile ausgeben.
;;; ------------------------------------------------------------
(defun cs-anzahl-schreiben (carriers scanCounts sepCounts verbose /
rec en nm2 ns nsep okS okP)
(foreach rec carriers
(setq en (car rec))
(setq nm2 (cdr (assoc 2 (entget en))))
(setq ns (cond ((assoc en scanCounts) (cdr (assoc en scanCounts))) (t 0)))
(setq nsep (cond ((assoc en sepCounts) (cdr (assoc en sepCounts))) (t 0)))
(setq okS (cs-set-att en (nth 1 rec) (itoa ns)))
(setq okP (cs-set-att en (nth 2 rec) (itoa nsep)))
(if verbose
(princ (ssg-textf "sens-carrier-zeile"
(list nm2 (nth 9 rec) (itoa ns) (itoa nsep)))))
(if (not (and okS okP))
(princ (ssg-textf "sens-carrier-fehler" (list nm2))))
)
)
;;; ------------------------------------------------------------
;;; Abschliessende Konsolen-Zusammenfassung ausgeben: Gesamtzahlen,
;;; per Distanz ergaenzte Scanner (*cs-scanner-ergaenzt*), strittige
;;; Zuordnungen, korrigierte ZUORDNUNGen (*cs-korrekturen*) sowie
;;; markierte unzuordenbare und fehlgeschlagene Attributschreibungen.
;;; ------------------------------------------------------------
(defun cs-bericht-ausgeben (label n-carrier n-scan n-scan-unassigned
n-sep n-sep-unassigned n-strittig
n-zuordnung-fehlt / eintrag)
(princ (ssg-textf "sens-summary"
(list label (itoa n-carrier)
(itoa n-scan) (itoa n-scan-unassigned)
(itoa n-sep) (itoa n-sep-unassigned))))
(if *cs-scanner-ergaenzt*
(progn
(princ (ssg-textf "sens-ergaenzt-header"
(list label (itoa (length *cs-scanner-ergaenzt*)))))
(foreach eintrag *cs-scanner-ergaenzt*
(princ (ssg-textf "sens-ergaenzt-zeile"
(list (nth 0 eintrag) (nth 1 eintrag) (itoa (nth 2 eintrag)))))
(if (nth 3 eintrag)
(princ (ssg-textf "sens-strittig-suffix"
(list (nth 4 eintrag) (itoa (nth 5 eintrag))))))
)
)
)
(if (> n-strittig 0)
(princ (ssg-textf "sens-strittig-summary" (list label (itoa n-strittig)))))
;; --- Zusammenfassung ---
(princ (ssg-textf "sens-summary"
(list label (itoa (length carriers))
(itoa (length scanlist)) (itoa n-scan-unassigned)
(itoa (length seplist)) (itoa n-sep-unassigned))))
;; --- Korrigierte (nicht zur Geometrie passende) ZUORDNUNG melden ---
(if *cs-korrekturen*
(progn
(setq *cs-korrekturen* (reverse *cs-korrekturen*))
(princ (ssg-textf "sens-korrektur-header"
(list label (itoa (length *cs-korrekturen*)))))
(foreach eintrag *cs-korrekturen*
(princ (ssg-textf "sens-korrektur-zeile"
(list (nth 0 eintrag) (nth 1 eintrag)
(nth 2 eintrag) (nth 3 eintrag)))))
)
)
(if *cs-scanner-ergaenzt*
(progn
(princ (ssg-textf "sens-ergaenzt-header"
(list label (itoa (length *cs-scanner-ergaenzt*)))))
(foreach eintrag *cs-scanner-ergaenzt*
(princ (ssg-textf "sens-ergaenzt-zeile"
(list (nth 0 eintrag) (nth 1 eintrag) (itoa (nth 2 eintrag)))))
(if (nth 3 eintrag)
(princ (ssg-textf "sens-strittig-suffix"
(list (nth 4 eintrag) (itoa (nth 5 eintrag))))))
)
)
)
(if (> n-strittig 0)
(princ (ssg-textf "sens-strittig-summary" (list label (itoa n-strittig)))))
(if (> (+ n-scan-unassigned n-sep-unassigned) 0)
(princ (ssg-textf "sens-unassigned-marked"
(list label (itoa (+ n-scan-unassigned n-sep-unassigned)) *mark-layer*))))
(if (> n-zuordnung-fehlt 0)
(princ (ssg-textf "sens-zuordnung-fehlt" (list label (itoa n-zuordnung-fehlt)))))
)
;; --- Korrigierte (nicht zur Geometrie passende) ZUORDNUNG melden ---
(if *cs-korrekturen*
(progn
(setq *cs-korrekturen* (reverse *cs-korrekturen*))
(princ (ssg-textf "sens-korrektur-header"
(list label (itoa (length *cs-korrekturen*)))))
(foreach eintrag *cs-korrekturen*
(princ (ssg-textf "sens-korrektur-zeile"
(list (nth 0 eintrag) (nth 1 eintrag)
(nth 2 eintrag) (nth 3 eintrag)))))
)
)
;;; ============================================================
;;; ENGINE - wird sowohl von ZAEHLE_SEP_SCAN als auch automatisch
;;; vor jedem CSV-/Sivas-Export aufgerufen (siehe export.lsp,
;;; csv:run-export).
;;;
;;; label = Anzeigepraefix fuer die Konsolenausgabe (z.B. "EXPORTCSV").
;;; verbose = T -> zusaetzlich je Carrier eine Detailzeile ausgeben
;;; (nur fuer den interaktiven Befehl sinnvoll - der
;;; automatische Aufruf aus csv:run-export laesst dies aus,
;;; damit die Export-Konsole kompakt bleibt).
;;;
;;; Setzt als Nebenwirkung:
;;; *cs-scanner-ergaenzt* - Liste (scanner-id carrier-id
;;; distanz-mm strittig-p
;;; zweiter-carrier-id zweite-distanz-mm)
;;; je per Abstand (nicht per BBox)
;;; zugeordnetem Scanner.
;;; *cs-scanner-warnung-by-handle* - Assoc-Liste (handle . warnungstext)
;;; fuer strittige Scanner-Zuordnungen,
;;; von csv:block-to-json ausgelesen.
;;; *cs-korrekturen* - Liste (typ-wort sensor-id alt-id
;;; neu-id) je Sensor, dessen gespeicherte
;;; ZUORDNUNG nicht zur Geometrie passte
;;; und ueberschrieben wurde (Report).
;;; ============================================================
(defun cs-zuordnung-lauf (label verbose /
mss bloecke carriers scanlist seplist grid
scan-res sep-res scanCounts sepCounts
n-scan-unassigned n-sep-unassigned
n-strittig n-zuordnung-fehlt)
(setq *cs-scanner-ergaenzt* nil)
(setq *cs-scanner-warnung-by-handle* nil)
(setq *cs-korrekturen* nil)
(setq *senstol* (cs-senstol))
(if (> (+ n-scan-unassigned n-sep-unassigned) 0)
(princ (ssg-textf "sens-unassigned-marked"
(list label (itoa (+ n-scan-unassigned n-sep-unassigned)) *mark-layer*))))
(if (> n-zuordnung-fehlt 0)
(princ (ssg-textf "sens-zuordnung-fehlt" (list label (itoa n-zuordnung-fehlt)))))
;; Alte Markierungskreise entfernen (Idempotenz - Sensoren, die in
;; einem frueheren Lauf unzuordenbar waren, koennen inzwischen einen
;; Carrier bekommen haben oder umgekehrt).
(setq mss (ssget "_X" (list (cons 0 "CIRCLE") (cons 8 *mark-layer*))))
(if mss (command "_.ERASE" mss ""))
(vl-catch-all-apply '(lambda () (command "_.REGEN")))
(princ (ssg-textf "sens-start" (list label)))
;; INSERTs in Carrier/Scanner/Separator einsortieren.
(setq bloecke (cs-sammle-bloecke))
(setq carriers (nth 0 bloecke)
scanlist (nth 1 bloecke)
seplist (nth 2 bloecke))
(if (and (null carriers) (null scanlist) (null seplist))
(princ (ssg-textf "sens-no-inserts" (list label)))
(progn
(setq grid (cs-grid-build carriers))
;; Scanner zuordnen (BBox, sonst naechster Carrier; Distanz-Fallback
;; + Strittig-Report). Zaehler kommen als Liste zurueck.
(setq scan-res (cs-scanner-zuordnen scanlist carriers grid))
(setq scanCounts (nth 0 scan-res)
n-scan-unassigned (nth 1 scan-res)
n-strittig (nth 2 scan-res)
n-zuordnung-fehlt (nth 3 scan-res))
;; Separatoren zuordnen (Vorgabe hat Vorrang vor der Geometrie).
(setq sep-res (cs-separatoren-zuordnen seplist carriers grid))
(setq sepCounts (nth 0 sep-res)
n-sep-unassigned (nth 1 sep-res)
n-zuordnung-fehlt (+ n-zuordnung-fehlt (nth 2 sep-res)))
;; ANZAHL_SCANNER/ANZAHL_SEPARATOR je Carrier schreiben und Bericht.
(cs-anzahl-schreiben carriers scanCounts sepCounts verbose)
(cs-bericht-ausgeben label (length carriers)
(length scanlist) n-scan-unassigned
(length seplist) n-sep-unassigned
n-strittig n-zuordnung-fehlt)
)
)
(princ)
+173 -81
View File
@@ -297,43 +297,76 @@
)
;; --- Lokalen Punkt (relativ zum Block-Ursprung) unter Beruecksichtigung der
;; Eltern-Rotation (reine Z-Drehung um den Einfuegepunkt) in Weltkoordinaten
;; umrechnen. Rueckgabe: (x y z) ---
(defun csv:local-to-world (parent-pt parent-rot-rad local-pt / c s lx ly lz pz)
(setq c (cos parent-rot-rad) s (sin parent-rot-rad))
(setq lx (car local-pt) ly (cadr local-pt))
;; VOLLEN 3D-Orientierung des Elternblocks in Weltkoordinaten umrechnen ---
;; parent-R = volle 3x3-Rotationsmatrix (mat3-* in ssg_core.lsp), nicht nur
;; ein Z-Drehwinkel: ein Elternblock kann selbst eine gekippte Extrusion
;; tragen (z.B. ein Separator-Proxy, der aus dem geneigten Abschnitt einer
;; VF-/GF-Kette neu zusammengesetzt wurde, siehe csv:block-to-json) - eine
;; reine Z-Drehung wuerde dessen Sub-Koordinatensysteme (K1-K4/KS_EIN/
;; KS_AUS) an einen falschen, teils gespiegelten Weltpunkt legen. Fuer einen
;; flachen Elternblock (Extrusion (0 0 1)) ist parent-R = Rz(rotation) und
;; das Ergebnis identisch zur frueheren reinen Z-Drehung. Rueckgabe: (x y z).
(defun csv:local-to-world (parent-pt parent-R local-pt / lz pz rot-pt)
(setq lz (if (caddr local-pt) (caddr local-pt) 0.0))
(setq pz (if (caddr parent-pt) (caddr parent-pt) 0.0))
(setq rot-pt (mat3-mul-vec3 parent-R (list (car local-pt) (cadr local-pt) lz)))
(list
(+ (car parent-pt) (- (* lx c) (* ly s)))
(+ (cadr parent-pt) (+ (* lx s) (* ly c)))
(+ pz lz)
(+ (car parent-pt) (car rot-pt))
(+ (cadr parent-pt) (cadr rot-pt))
(+ pz (caddr rot-pt))
)
)
;; --- K1-K4-Koordinatensysteme eines Blocks als Positions-Strings lesen ---
;; ename = Entity-Name des platzierten INSERT (Omniflo Bogen/Weiche/Gerade).
;; pt/rot-rad = bereits ermittelte Welt-Position/-Rotation (radiant) des
;; Eltern-INSERT (siehe csv:block-to-json), keine erneute Abfrage.
;; ename = Entity-Name des platzierten INSERT (Omniflo Bogen/Weiche/Gerade).
;; pt/parent-R = bereits ermittelte Welt-Position/volle 3x3-Orientierung des
;; Eltern-INSERT (siehe csv:block-to-json), keine erneute Abfrage.
;; Sucht in der Block-DEFINITION (wie omni:get-block-hoehe) nach direkten
;; Sub-INSERTs "K1".."K4" und rechnet deren lokale Position ueber die
;; Eltern-Rotation (reine Z-Drehung) in Weltkoordinaten um. Es wird nur die
;; POSITION kodiert (12-Zeichen-String, csv:trans-encode) - keine Rotation.
;; Sub-INSERTs "K1".."K4" und rechnet deren lokale Position ueber die volle
;; Eltern-Orientierung in Weltkoordinaten um (csv:local-to-world). Es wird
;; nur die POSITION kodiert (12-Zeichen-String, csv:trans-encode) - keine
;; Rotation.
;; Zusaetzlich werden Strecken-Koordinatensysteme KS_EIN->K1 und KS_AUS->K2
;; (inkl. der KSYS_-Varianten, siehe ks-normalize-name in vf_core.lsp) gemappt,
;; sofern kein echter K1/K2 am Block vorhanden ist (echte K-Bloecke haben
;; Vorrang: sie werden vorne angehaengt, csv:k-kos liest per assoc den ersten).
;; Rueckgabe: Assoc-Liste (("K1" . "<pos-string>") ("K2" . ...) ...), nur
;; fuer tatsaechlich vorhandene K-Bloecke (nicht jedes Element hat alle 4).
(defun csv:get-k-kos-strings (ename pt rot-rad / ed bname blk-tbl sub-ent sub-ed
sub-bname sub-pt world-pt target is-real result)
;;
;; Gruppe-10 eines Sub-INSERTs ist OCS-lokal zu SEINER EIGENEN Extrusion
;; (Gruppe 210), nicht zwingend zur flachen XY-Ebene der Elterndefinition -
;; z.B. KS_AUS von SC_Entladung (BTMT-Entladung), das schraeg in 3D sitzt.
;; mat3-ocs-matrix/mat3-mul-vec3 (ssg_core.lsp, Arbitrary Axis Algorithm)
;; wandeln das erst in echte lokale Koordinaten um, bevor csv:local-to-world
;; mit der Eltern-Rotation weiterrechnet - derselbe Fix wie bereits in
;; ssg-collect-nested-inserts-rel (dort ausfuehrlich begruendet). Ohne diese
;; Umrechnung landete K2 tief unterhalb statt oberhalb des Elements (realer
;; Bug, sichtbar in der Materialfluss-Draufsicht sps_skel/HundM05_bbs.svg).
;; Fuer Extrusion=(0 0 1) (Normalfall, z.B. KS_EIN) reduziert sich das exakt
;; auf die alte reine Uebernahme - unveraendertes Verhalten dort. Denselben
;; Grund hat parent-R als VOLLE Matrix statt eines Z-Drehwinkels: ein
;; Elternblock (z.B. ein Separator-Proxy, ueber ssg-collect-nested-inserts
;; aus dem geneigten Abschnitt einer VF-/GF-Kette neu zusammengesetzt) kann
;; selbst eine gekippte Extrusion tragen - eine reine Z-Drehung des Eltern-
;; INSERT legte dessen K1/K2 dann an einen falschen, teils gespiegelten
;; Weltpunkt (realer Bug: K1 landete beim Vorzeichen von X/Y gespiegelt weit
;; ausserhalb der eigenen Boundingbox, sichtbar als fehlendes K-Kreuz in
;; sps_skel/HundM05_bbs.svg trotz gefuelltem K1 in der CSV).
(defun csv:get-k-kos-strings (ename pt parent-R / ed bname blk-tbl sub-ent sub-ed
sub-bname sub-pt sub-extrusion sub-pt-real world-pt
target is-real result)
(setq ed (entget ename))
(setq bname (cdr (assoc 2 ed)))
(setq blk-tbl (tblsearch "BLOCK" bname))
(setq result nil)
(if blk-tbl
(progn
(setq sub-ent (entnext (cdr (assoc -2 blk-tbl))))
;; (assoc -2) ist bereits das ERSTE Entity der Blockdefinition, NICHT
;; der BLOCK-Header - direkt hier starten, kein entnext davor (das
;; uebersprang sonst ein K1/KS_EIN, das zufaellig als erstes Entity in
;; der Blockdefinition steht - derselbe Bug wie frueher in
;; ssg-collect-nested-inserts-rel, siehe dortiger Kommentar).
(setq sub-ent (cdr (assoc -2 blk-tbl)))
(while sub-ent
(setq sub-ed (entget sub-ent))
(if (= (cdr (assoc 0 sub-ed)) "INSERT")
@@ -352,7 +385,11 @@
(if (and target (or is-real (not (assoc target result))))
(progn
(setq sub-pt (cdr (assoc 10 sub-ed)))
(setq world-pt (csv:local-to-world pt rot-rad sub-pt))
(if (null (caddr sub-pt))
(setq sub-pt (list (car sub-pt) (cadr sub-pt) 0.0)))
(setq sub-extrusion (cond ((cdr (assoc 210 sub-ed))) ('(0.0 0.0 1.0))))
(setq sub-pt-real (mat3-mul-vec3 (mat3-ocs-matrix sub-extrusion) sub-pt))
(setq world-pt (csv:local-to-world pt parent-R sub-pt-real))
(setq result (cons
(cons target
(csv:trans-encode
@@ -483,14 +520,17 @@
(setq ed (entget ename))
(setq bname (cdr (assoc 2 ed)))
(setq result nil)
;; Beide Enden im Wrapper-Frame holen und in Weltkoordinaten (reine
;; Z-Drehung) umrechnen. AS traegt das KS_EIN, ES das KS_AUS - jeweils aus
;; dem RICHTIGEN Element (csv:vfgf-ks-loc; beide Elemente fuehren beide
;; KS-Typen).
;; Beide Enden im Wrapper-Frame holen und in Weltkoordinaten umrechnen.
;; VF_n/GF_n-Wrapper stehen an ihrem eigenen top-level INSERT immer flach
;; (Extrusion (0 0 1) - siehe csv:sep-proxies-erzeugen), darum genuegt hier
;; weiterhin die reine Z-Drehungsmatrix (mat3-rz) statt der vollen
;; Elternorientierung, die csv:get-k-kos-strings inzwischen braucht (siehe
;; dort). AS traegt das KS_EIN, ES das KS_AUS - jeweils aus dem RICHTIGEN
;; Element (csv:vfgf-ks-loc; beide Elemente fuehren beide KS-Typen).
(setq as-loc (csv:vfgf-ks-loc bname "AS_ELEMENT*" "KS_EIN,KSYS_EIN"))
(setq es-loc (csv:vfgf-ks-loc bname "ES_ELEMENT*" "KS_AUS,KSYS_AUS"))
(if as-loc (setq as-world (csv:local-to-world pt rot-rad as-loc)))
(if es-loc (setq es-world (csv:local-to-world pt rot-rad es-loc)))
(if as-loc (setq as-world (csv:local-to-world pt (mat3-rz rot-rad) as-loc)))
(if es-loc (setq es-world (csv:local-to-world pt (mat3-rz rot-rad) es-loc)))
;; Eingang/Ausgang festlegen (siehe Richtung im Kopfkommentar).
(cond
;; GF: Richtung ueber die Hoehe - hoeheres Z = Eingang, tieferes Z = Ausgang.
@@ -609,7 +649,8 @@
;; "k1".."k4" (KOS-Strings der Koordinatensysteme im Block, siehe
;; csv:get-k-kos-strings) werden immer geschrieben, unabhaengig von
;; include-bbox -- Kodierung ueber csv:kos-encode (siehe dort).
(defun csv:block-to-json (ename include-bbox / ed blk-name layer pt rotation handle attribs bbox bbox-json
(defun csv:block-to-json (ename include-bbox / ed blk-name layer pt rotation extrusion R-world
handle attribs bbox bbox-json
insert-quat insert-kos k-list warnung-json warnung-eintrag
zuordnung-fix-json zuordnung-fix-eintrag sepliste-json sepliste-app sepliste
sepliste-teile sepliste-first)
@@ -620,12 +661,27 @@
(setq rotation (cdr (assoc 50 ed)))
(setq handle (cdr (assoc 5 ed)))
(if (null rotation) (setq rotation 0.0))
(if (null (caddr pt)) (setq pt (list (car pt) (cadr pt) 0.0)))
;; Gruppe 10 ist OCS-lokal zur EIGENEN Extrusion dieses INSERT (Gruppe
;; 210) - fuer die allermeisten Bloecke (0 0 1) = Welt, aber ein Separator-
;; Proxy, der ueber ssg-collect-nested-inserts aus dem geneigten Abschnitt
;; einer VF-/GF-Kette neu zusammengesetzt wurde (csv:sep-proxies-erzeugen),
;; kann eine echt gekippte Extrusion tragen. Erst in echte Weltkoordinaten/
;; -orientierung umrechnen (Arbitrary Axis Algorithm, ssg_core.lsp), bevor
;; irgendetwas darauf aufbaut - sonst zeigt "x"/"y"/"z" (und darueber K1-K4,
;; siehe csv:get-k-kos-strings) einen falschen, teils spiegelverkehrten
;; Weltpunkt (realer Bug: Separator :4/6/8/10/12 in HundM05, K1 landete am
;; Vorzeichen gespiegelt weit ausserhalb der eigenen Boundingbox). Fuer
;; Extrusion=(0 0 1) reduziert sich das exakt auf die alte Uebernahme.
(setq extrusion (cond ((cdr (assoc 210 ed))) ('(0.0 0.0 1.0))))
(setq pt (mat3-mul-vec3 (mat3-ocs-matrix extrusion) pt))
(setq R-world (mat3-from-normal-rotation extrusion rotation))
(setq attribs (csv:read-attribs ename))
(setq insert-quat (csv:z-angle-to-quat (* (/ rotation pi) 180.0)))
(setq insert-kos (csv:kos-encode
(car pt) (cadr pt) (if (caddr pt) (caddr pt) 0.0)
(nth 0 insert-quat) (nth 1 insert-quat) (nth 2 insert-quat) (nth 3 insert-quat)))
(setq k-list (csv:get-k-kos-strings ename pt rotation))
(setq k-list (csv:get-k-kos-strings ename pt R-world))
;; Omniflo-Geraden fuehren keine echten K-Bloecke - K1/K2 aus der Geometrie
;; (Anfang/Ende ueber Laenge + Rotation) synthetisieren, damit sie im Export
;; wie echte Anschluss-Koordinatensysteme erscheinen und in der Nachbar-
@@ -1088,6 +1144,13 @@
;; ils-insert-station in SSG_LIB_Commands.lsp). Anders als Separator/
;; Scanner werden sie von KEINEM anderen Element mitgezaehlt - export_csv.py
;; UND export_sivas.py fuehren sie beide als eigene TeileArt-Zeilen.
;; Eingang*/Ausgang* = Materialfluss-Ein-/Ausgangssymbole (siehe
;; pattern_eingang/pattern_ausgang in cfg/export.cfg, ils-insert-station in
;; SSG_LIB_Commands.lsp). Wie BTMT eigenstaendig, aber NUR fuer den
;; CSV-Export: export_csv.py gibt ihnen eigene TeileArt-Zeilen und traegt
;; sie - wie Separator/AS/ES - in die "TrackIds"-Spalte ein (auch auf
;; Kreiseln, siehe KREISEL_UMLAUF_TEILEARTEN in lib/export_neighbors.py);
;; export_sivas.py schliesst sie bewusst aus.
;; GR_* = Geruest-Baugruppen (siehe pattern_geruest in cfg/export.cfg,
;; GeruestInsert.lsp). Wie BTMT eigenstaendig und von keinem anderen
;; Element mitgezaehlt - export_csv.py UND export_sivas.py fuehren sie
@@ -1111,6 +1174,8 @@
(wcmatch bname (export:pattern "pattern_scanner" "Scanner*"))
(wcmatch bname (export:pattern "pattern_btmt_beladung" "BTMT-Beladung*"))
(wcmatch bname (export:pattern "pattern_btmt_entladung" "SC_Entladung*"))
(wcmatch bname (export:pattern "pattern_eingang" "Eingang*"))
(wcmatch bname (export:pattern "pattern_ausgang" "Ausgang*"))
(wcmatch bname (export:pattern "pattern_geruest" "GR_*"))
;; Rein numerische Namen = Omniflo SivasNr (Boegen/Weichen)
;; Rein LISP-intern (Python klassifiziert Omniflo stattdessen
@@ -1157,16 +1222,18 @@
;; --- Gemeinsame Export-Funktion ---
;; Sammelt relevante Bloecke, schreibt JSON, ruft Python auf.
;; label = Anzeigename (z.B. "EXPORTCSV")
;; py-name = Python-Skript-Name (z.B. "export_csv.py")
;; csv-name = Ergebnis-CSV-Name (z.B. "export.csv")
;; include-bbox = T -> Bounding-Box je Block ermitteln und mit exportieren
;; (siehe csv:block-to-json); fuer EXPORTSIVAS nil.
(defun csv:run-export (label py-name csv-name include-bbox / ss i ename fh out-pfad
py-skript ergebnis-pfad py-exe log-pfad rc first-block
count out-dir sep-proxies)
;; --- Export-Vorbereitung: Zeichnungszustand fuer den Export herrichten ---
;; Aus csv:run-export ausgelagert: alle Schritte, die VOR dem Sammeln/Schreiben
;; den Zeichnungszustand herrichten - Separator-Proxies einpacken, IDs
;; sicherstellen, Proxy-Zuordnung setzen, Omniflo-Attribute (HOEHE/DREHUNG)
;; aktualisieren, Separator-/Scanner-Zuordnung pruefen, global regenerieren und
;; die bbox-Fehlerdiagnose zuruecksetzen. Rueckgabe: die Liste der erzeugten
;; Separator-Proxies (der Aufrufer entfernt sie am Ende wieder). label dient
;; nur den Meldungen.
(defun csv:export-vorbereiten (label / sep-proxies)
;; Verpackte Separator_SP-Symbole (siehe csv:sep-proxies-erzeugen) VOR der
;; ID-Vergabe durch temporaere, echte Kopien ersetzen - sie werden am Ende
;; dieser Funktion wieder entfernt (csv:sep-proxies-loeschen).
;; von csv:run-export wieder entfernt (csv:sep-proxies-loeschen).
;;
;; HINWEIS: csv:sepliste-proxies-erzeugen (echte Kopien fuer die nur noch in
;; der sepliste-XDATA gefuehrten SEP/AS/ES-Eintraege der VF_n/GF_n-Wrapper)
@@ -1226,6 +1293,76 @@
;; bbox-Fehlerdiagnose fuer diesen Lauf zuruecksetzen (siehe csv:get-bbox).
(setq *csv-bbox-fail-count* 0)
(setq *csv-bbox-fail-first* nil)
sep-proxies)
;; --- Python-Export-Skript aufrufen und Ergebnis auswerten ---------------
;; Aus csv:run-export ausgelagert: das fertige raw-JSON (out-pfad) an das
;; Python-Skript py-name uebergeben, synchron ausfuehren und den Exitcode
;; auswerten (kein stiller Fehlschlag). Arbeitet ausschliesslich ueber die
;; uebergebenen Pfade/Namen und die Umgebung (DXFM_LIB/DXFM_DATA/DXFM_LOG) -
;; keine Zeichnungs-/Sammel-Logik mehr, daher gut fuer sich testbar.
;; label/out-dir dienen nur den Meldungen bzw. dem Ergebnis-Pfad.
(defun csv:python-aufrufen (label out-dir out-pfad py-name csv-name
/ py-skript ergebnis-pfad py-exe log-pfad rc)
;; Python-Skript aufrufen. DXFM_LIB muss gesetzt sein - fehlt es
;; (z.B. weil BricsCAD nicht ueber bin/start_briscad.bat gestartet
;; wurde und die Umgebungsvariablen daher nie in diesen Prozess
;; vererbt wurden), wuerde das folgende strcat sonst mit einem
;; kryptischen Lisp-Fehler ("bad argument type <NIL>") abbrechen.
(if (null (getenv "DXFM_LIB"))
(princ (ssg-textf "exp-dxfm-lib-missing" (list label)))
(progn
(setq py-skript (strcat (getenv "DXFM_LIB") "/" py-name))
(if (findfile py-skript)
(progn
(setq ergebnis-pfad (strcat out-dir "/" (export:dwg-praefix) csv-name))
(setq py-exe (export:python-exe))
(setq log-pfad (strcat (cond ((getenv "DXFM_LOG")) (out-dir))
"/export_python.log"))
(princ (ssg-textf "exp-python-exe" (list label py-exe)))
(princ (ssg-textf "exp-calling-python" (list label)))
;; Synchron ausfuehren und Exitcode auswerten - ein stiller
;; Fehlschlag (z.B. WindowsApps-Platzhalter statt Interpreter)
;; darf nicht mehr als "Export gestartet" durchgehen.
(setq rc (export:run-python py-exe py-skript
(list out-pfad (getenv "DXFM_DATA") ergebnis-pfad)
log-pfad))
(cond
((null rc)
(princ (ssg-textf "exp-python-call-failed" (list label))))
((/= rc 0)
(princ (ssg-textf "exp-python-error" (list label rc log-pfad)))
(export:log-ausgeben log-pfad 12)
;; Exitcode 49 (Store-Platzhalter direkt) bzw. 9009 (cmd.exe:
;; "Befehl nicht gefunden") = kein brauchbarer Interpreter.
;; Gezielter Hinweis, wie er korrekt gesetzt wird.
(if (or (= rc 49) (= rc 9009)
(export:python-store-stub-p py-exe))
(princ (ssg-text "exp-python-store-hint"))
))
((not (findfile ergebnis-pfad))
(princ (ssg-textf "exp-csv-missing" (list label ergebnis-pfad log-pfad)))
(export:log-ausgeben log-pfad 12))
(t
(princ (ssg-textf "exp-export-done" (list label ergebnis-pfad))))
)
)
(princ (ssg-textf "exp-python-not-found" (list label py-skript)))
)
)
)
)
;; py-name = Python-Skript-Name (z.B. "export_csv.py")
;; csv-name = Ergebnis-CSV-Name (z.B. "export.csv")
;; include-bbox = T -> Bounding-Box je Block ermitteln und mit exportieren
;; (siehe csv:block-to-json); fuer EXPORTSIVAS nil.
(defun csv:run-export (label py-name csv-name include-bbox / ss i ename fh out-pfad
first-block count out-dir sep-proxies)
;; Zeichnungszustand fuer den Export herrichten (Proxies, IDs, Zuordnung,
;; Attribut-Update, Sep/Scan-Zaehlung, REGENALL, bbox-Reset) - ausgelagert.
;; Rueckgabe: die erzeugten Separator-Proxies (unten wieder zu entfernen).
(setq sep-proxies (csv:export-vorbereiten label))
(princ (ssg-textf "exp-collect-blocks" (list label)))
(setq ss (csv:collect-export-blocks))
@@ -1285,53 +1422,8 @@
(list (itoa *csv-bbox-fail-count*)
(cond (*csv-bbox-fail-first*) ("?"))))))
;; Python-Skript aufrufen. DXFM_LIB muss gesetzt sein - fehlt es
;; (z.B. weil BricsCAD nicht ueber bin/start_briscad.bat gestartet
;; wurde und die Umgebungsvariablen daher nie in diesen Prozess
;; vererbt wurden), wuerde das folgende strcat sonst mit einem
;; kryptischen Lisp-Fehler ("bad argument type <NIL>") abbrechen.
(if (null (getenv "DXFM_LIB"))
(princ (ssg-textf "exp-dxfm-lib-missing" (list label)))
(progn
(setq py-skript (strcat (getenv "DXFM_LIB") "/" py-name))
(if (findfile py-skript)
(progn
(setq ergebnis-pfad (strcat out-dir "/" (export:dwg-praefix) csv-name))
(setq py-exe (export:python-exe))
(setq log-pfad (strcat (cond ((getenv "DXFM_LOG")) (out-dir))
"/export_python.log"))
(princ (ssg-textf "exp-python-exe" (list label py-exe)))
(princ (ssg-textf "exp-calling-python" (list label)))
;; Synchron ausfuehren und Exitcode auswerten - ein stiller
;; Fehlschlag (z.B. WindowsApps-Platzhalter statt Interpreter)
;; darf nicht mehr als "Export gestartet" durchgehen.
(setq rc (export:run-python py-exe py-skript
(list out-pfad (getenv "DXFM_DATA") ergebnis-pfad)
log-pfad))
(cond
((null rc)
(princ (ssg-textf "exp-python-call-failed" (list label))))
((/= rc 0)
(princ (ssg-textf "exp-python-error" (list label rc log-pfad)))
(export:log-ausgeben log-pfad 12)
;; Exitcode 49 (Store-Platzhalter direkt) bzw. 9009 (cmd.exe:
;; "Befehl nicht gefunden") = kein brauchbarer Interpreter.
;; Gezielter Hinweis, wie er korrekt gesetzt wird.
(if (or (= rc 49) (= rc 9009)
(export:python-store-stub-p py-exe))
(princ (ssg-text "exp-python-store-hint"))
))
((not (findfile ergebnis-pfad))
(princ (ssg-textf "exp-csv-missing" (list label ergebnis-pfad log-pfad)))
(export:log-ausgeben log-pfad 12))
(t
(princ (ssg-textf "exp-export-done" (list label ergebnis-pfad))))
)
)
(princ (ssg-textf "exp-python-not-found" (list label py-skript)))
)
)
)
;; Python-Export-Skript aufrufen (ausgelagert, siehe oben).
(csv:python-aufrufen label out-dir out-pfad py-name csv-name)
)
)
)
+314
View File
@@ -0,0 +1,314 @@
;; ============================================================
;; list_blocks.lsp
;; Liest die (Sub-)Block-Definitionen aller DWG-Dateien in
;; data/ils aus und schreibt sie als JSON-Report
;; "blocks_collected.json" nach data/ils.
;;
;; Hintergrund: 2D- und 3D-Bloecke (z.B.
;; Vario_Bogen_ab_27_TEF_rechts_2D.dwg /
;; Vario_Bogen_ab_27_TEF_rechts_3D.dwg)
;; enthalten teilweise gleichnamige Sub-Bloecke (z.B.
;; "200000317_TEF_Bogen_vertikal_abwaerts__15"). Werden beide
;; DWGs in dieselbe Zeichnung eingefuegt, kollidieren diese
;; Definitionen (nur eine Referenz gewinnt). Dieses Skript LIEST
;; die Bloecke nur aus, damit manuell entschieden werden kann,
;; welche spaeter mit "_2D"/"_3D" umbenannt werden.
;;
;; Ausgabe:
;; - JSON data/ils/blocks_collected.json:
;; { "<Dateiname ohne .dwg>": { "dim": "2D"|"3D"|"-",
;; "blocks": ["Name", ...] }, ... }
;; - Konsolen-Report inkl. Kollisionsliste (gleicher Blockname
;; kommt sowohl in einer _2D- als auch in einer _3D-Datei vor)
;;
;; Voraussetzung: DXFM_DATA ist gesetzt (siehe bin/setenv.bat).
;;
;; Aufruf in BricsCAD:
;; (load "C:/.../Lisp/list_blocks.lsp")
;; ILS_BLOCK_LIST
;; ============================================================
(vl-load-com)
;; ------------------------------------------------------------
;; Bereits offenes Dokument mit gegebenem Pfad suchen (oder nil)
;; ------------------------------------------------------------
(defun lb-find-open-doc (dwg-pfad / docs anzahl i doc full treffer)
(setq docs (vla-get-Documents (vlax-get-acad-object))
anzahl (vla-get-Count docs)
i 0
treffer nil
dwg-pfad (strcase (vl-string-translate "\\" "/" dwg-pfad))
)
(while (and (< i anzahl) (not treffer))
(setq doc (vla-item docs i))
(setq full (strcase (vl-string-translate "\\" "/" (vla-get-FullName doc))))
(if (= full dwg-pfad) (setq treffer doc))
(setq i (1+ i))
)
treffer
)
;; ------------------------------------------------------------
;; String fuer JSON escapen (Anfuehrungszeichen/Backslash)
;; ------------------------------------------------------------
(defun lb-json-escape (s)
(if (null s) (setq s ""))
(setq s (vl-string-subst "\\\\" "\\" s))
(vl-string-subst "\\\"" "\"" s)
)
;; ------------------------------------------------------------
;; Strings mit Trennzeichen verbinden
;; ------------------------------------------------------------
(defun lb-strjoin (liste sep / erg erste s)
(setq erg "" erste T)
(foreach s liste
(if erste (setq erg s erste nil) (setq erg (strcat erg sep s)))
)
erg
)
;; ------------------------------------------------------------
;; Dimension aus dem Dateinamen ableiten ("2D"/"3D"/"-")
;; ------------------------------------------------------------
(defun lb-dim-aus-name (basisname / n)
(setq n (strcase basisname))
(cond
((wcmatch n "*_2D") "2D")
((wcmatch n "*_3D") "3D")
(t "-")
)
)
;; ------------------------------------------------------------
;; Alle echten (Sub-)Block-Definitionen des aktiven Dokuments
;; einsammeln (Symboltabelle BLOCK): keine Layouts (Model/Paper-
;; Space, "*..."), keine anonymen Bloecke (Flag 1), keine XRefs
;; (Flag 4). Alphabetisch sortiert.
;; ------------------------------------------------------------
(defun lb-collect-blocks-in-current-doc ( / def name flags ergebnis)
(setq ergebnis nil
def (tblnext "BLOCK" T)
)
(while def
(setq name (cdr (assoc 2 def))
flags (cdr (assoc 70 def))
)
(if (null flags) (setq flags 0))
(if (and name
(> (strlen name) 0)
(/= (substr name 1 1) "*") ; kein Model/Paper-Space
(= (logand flags 1) 0) ; nicht anonym
(= (logand flags 4) 0) ; kein XRef
)
(setq ergebnis (cons name ergebnis))
)
(setq def (tblnext "BLOCK"))
)
(vl-sort ergebnis (function (lambda (a b) (< (strcase a) (strcase b)))))
)
;; ------------------------------------------------------------
;; Eine DWG-Datei oeffnen (falls noetig), Bloecke einsammeln und
;; die Datei wieder schliessen (falls durch diese Funktion geoeffnet).
;; Rueckgabe: ('ok . ("Name" ...)) oder ('fehler . meldung)
;; ------------------------------------------------------------
(defun lb-collect-dwg (dwg-pfad / bereits-offen ergebnis fehler)
(setq bereits-offen (lb-find-open-doc dwg-pfad))
(setq ergebnis nil)
(setq fehler
(vl-catch-all-apply
(function
(lambda ()
(if bereits-offen
(vlax-put-property (vlax-get-acad-object) 'ActiveDocument bereits-offen)
(command "_.OPEN" dwg-pfad)
)
(setq ergebnis (lb-collect-blocks-in-current-doc))
(if (not bereits-offen) (command "_.CLOSE"))
)
)
nil
)
)
(if (vl-catch-all-error-p fehler)
(cons 'fehler (vl-catch-all-error-message fehler))
(cons 'ok ergebnis)
)
)
;; ------------------------------------------------------------
;; Liste als JSON-Array-Text formatieren (["a", "b"]), eingerueckt
;; ------------------------------------------------------------
(defun lb-list-to-json (namen einruecken / innen zeilen)
(if (null namen)
"[]"
(progn
(setq innen (strcat einruecken " "))
(setq zeilen
(mapcar
(function (lambda (nm) (strcat innen "\"" (lb-json-escape nm) "\"")))
namen
)
)
(strcat "[\n"
(car zeilen)
(apply 'strcat (mapcar (function (lambda (z) (strcat ",\n" z))) (cdr zeilen)))
"\n" einruecken "]"
)
)
)
)
;; ------------------------------------------------------------
;; Kollisionen ermitteln: Bloecke, deren Name sowohl in einer
;; _2D- als auch in einer _3D-Datei vorkommt.
;; eintraege = (("<datei>" "2D"|"3D"|"-" ("blk" ...)) ...)
;; Rueckgabe: (("blockname" ("datei-2d" ...) ("datei-3d" ...)) ...)
;; ------------------------------------------------------------
(defun lb-finde-kollisionen (eintraege / tabelle e datei dim blks blk vorhanden coll)
;; tabelle: blockname -> (liste-2d-dateien . liste-3d-dateien)
(setq tabelle nil)
(foreach e eintraege
(setq datei (nth 0 e)
dim (nth 1 e)
blks (nth 2 e)
)
(foreach blk blks
(setq vorhanden (assoc blk tabelle))
(if (not vorhanden)
(setq vorhanden (list blk nil nil)
tabelle (cons vorhanden tabelle))
)
(cond
((= dim "2D") (setq vorhanden (subst (cons datei (nth 1 vorhanden)) (nth 1 vorhanden) vorhanden)))
((= dim "3D") (setq vorhanden (subst (cons datei (nth 2 vorhanden)) (nth 2 vorhanden) vorhanden)))
)
;; aktualisierten Eintrag zurueckschreiben
(setq tabelle (subst vorhanden (assoc blk tabelle) tabelle))
)
)
;; nur Bloecke mit mind. einer 2D- UND einer 3D-Datei
(setq coll nil)
(foreach e tabelle
(if (and (nth 1 e) (nth 2 e))
(setq coll (cons e coll))
)
)
(vl-sort coll (function (lambda (a b) (< (strcase (car a)) (strcase (car b))))))
)
;; ------------------------------------------------------------
;; Hauptbefehl - verarbeitet DXFM_DATA/ils
;; ------------------------------------------------------------
(defun c:ILS_BLOCK_LIST ( / data-dir dwg-dir json-pfad dateien datei dwg-pfad basisname
dim ergebnis orig-doc anzahl-ok anzahl-fehler fehler-liste
eintraege fh erste e kollisionen k)
(setq data-dir (getenv "DXFM_DATA"))
(if (not data-dir)
(progn
(alert "DXFM_DATA ist nicht gesetzt.\nBitte zuerst bin/setenv.bat ausfuehren.")
(exit)
)
)
(setq data-dir (vl-string-translate "\\" "/" data-dir)
dwg-dir (strcat data-dir "/ils")
json-pfad (strcat dwg-dir "/blocks_collected.json")
)
(setq dwg-dir (vl-string-right-trim "/" dwg-dir))
(if (not (findfile dwg-dir))
(progn
(princ (strcat "\n[BLOCK-LIST] Verzeichnis nicht gefunden: " dwg-dir))
(exit)
)
)
(setq dateien (vl-directory-files dwg-dir "*.dwg" 1))
(if (not dateien)
(progn
(princ (strcat "\n[BLOCK-LIST] Keine DWG-Dateien in: " dwg-dir))
(exit)
)
)
(setq orig-doc (vla-get-ActiveDocument (vlax-get-acad-object))
anzahl-ok 0
anzahl-fehler 0
fehler-liste nil
eintraege nil
)
(foreach datei dateien
(setq basisname (vl-filename-base datei)
dwg-pfad (strcat dwg-dir "/" datei)
dim (lb-dim-aus-name basisname)
)
(princ (strcat "\n[BLOCK-LIST] Lese " datei " ..."))
(setq ergebnis (lb-collect-dwg dwg-pfad))
(if (equal (car ergebnis) 'fehler)
(progn
(princ (strcat "\n[BLOCK-LIST] FEHLER bei " datei ": " (cdr ergebnis)))
(setq anzahl-fehler (1+ anzahl-fehler))
(setq fehler-liste (cons datei fehler-liste))
)
(progn
(setq eintraege (cons (list basisname dim (cdr ergebnis)) eintraege))
(setq anzahl-ok (1+ anzahl-ok))
(princ (strcat " " (itoa (length (cdr ergebnis))) " Block(e)."))
)
)
)
(vlax-put-property (vlax-get-acad-object) 'ActiveDocument orig-doc)
(setq eintraege (reverse eintraege))
;; JSON schreiben
(setq fh (open json-pfad "w"))
(if (null fh)
(princ (strcat "\n[BLOCK-LIST] FEHLER: Datei kann nicht geschrieben werden: " json-pfad))
(progn
(write-line "{" fh)
(setq erste T)
(foreach e eintraege
(if (not erste) (write-line "," fh))
(write-line (strcat " \"" (lb-json-escape (nth 0 e)) "\": {") fh)
(write-line (strcat " \"dim\": \"" (nth 1 e) "\",") fh)
(write-line (strcat " \"blocks\": " (lb-list-to-json (nth 2 e) " ")) fh)
(write-line " }" fh)
(setq erste nil)
)
(write-line "}" fh)
(close fh)
(princ (strcat "\n[BLOCK-LIST] " json-pfad " geschrieben ("
(itoa anzahl-ok) " Dateien)."))
)
)
;; Kollisionen ermitteln und ausgeben
(setq kollisionen (lb-finde-kollisionen eintraege))
(princ (strcat "\n[BLOCK-LIST] Fertig: " (itoa anzahl-ok) " gelesen, "
(itoa anzahl-fehler) " Fehler, "
(itoa (length kollisionen)) " Kollision(en) (Blockname in 2D UND 3D)."))
(if kollisionen
(progn
(princ "\n[BLOCK-LIST] --- Kollisionen (Kandidaten fuer _2D/_3D-Umbenennung) ---")
(foreach k kollisionen
(princ (strcat "\n * " (car k)))
(princ (strcat "\n 2D: " (lb-strjoin (reverse (nth 1 k)) ", ")))
(princ (strcat "\n 3D: " (lb-strjoin (reverse (nth 2 k)) ", ")))
)
)
)
(if fehler-liste
(progn
(princ "\n[BLOCK-LIST] WARNUNG: Fehler bei:")
(foreach datei (reverse fehler-liste)
(princ (strcat "\n - " datei))
)
)
)
(princ)
)
(princ "\nlist_blocks.lsp geladen. Befehl: ILS_BLOCK_LIST")
(princ)
+225
View File
@@ -0,0 +1,225 @@
;; ============================================================
;; purge_blocks.lsp
;; Entfernt in allen DWG-Dateien in data/ils die nicht
;; verwendeten Block-Definitionen und Layer (-PURGE Blocks/Layer).
;;
;; Ablauf je DWG:
;; - Datei oeffnen (falls noetig)
;; - Block- und Layer-Anzahl zaehlen
;; - -PURGE Blocks und -PURGE Layer in einer Schleife ausfuehren,
;; bis nichts mehr entfernt wird (verschachtelte Bloecke bzw.
;; nur von entfernten Bloecken benutzte Layer werden erst nach
;; dem ersten Durchlauf frei)
;; - nur speichern, wenn sich die Anzahl verringert hat
;; - schliessen (falls durch dieses Skript geoeffnet)
;;
;; Es werden AUSSCHLIESSLICH unbenutzte Bloecke und Layer entfernt.
;; Andere Objekttypen (Linientypen, Textstile, ...) bleiben
;; unangetastet.
;;
;; Voraussetzung: DXFM_DATA ist gesetzt (siehe bin/setenv.bat).
;;
;; Aufruf in BricsCAD:
;; (load "C:/.../Lisp/purge_blocks.lsp")
;; ILS_BLOCK_PURGE
;; ============================================================
(vl-load-com)
;; ------------------------------------------------------------
;; Bereits offenes Dokument mit gegebenem Pfad suchen (oder nil)
;; ------------------------------------------------------------
(defun pb-find-open-doc (dwg-pfad / docs anzahl i doc full treffer)
(setq docs (vla-get-Documents (vlax-get-acad-object))
anzahl (vla-get-Count docs)
i 0
treffer nil
dwg-pfad (strcase (vl-string-translate "\\" "/" dwg-pfad))
)
(while (and (< i anzahl) (not treffer))
(setq doc (vla-item docs i))
(setq full (strcase (vl-string-translate "\\" "/" (vla-get-FullName doc))))
(if (= full dwg-pfad) (setq treffer doc))
(setq i (1+ i))
)
treffer
)
;; ------------------------------------------------------------
;; Anzahl aller Eintraege einer Symboltabelle zaehlen
;; (z.B. "BLOCK" inkl. Model/Paper-Space/anonyme, "LAYER").
;; ------------------------------------------------------------
(defun pb-count-table (tabelle / n def)
(setq n 0 def (tblnext tabelle T))
(while def
(setq n (1+ n))
(setq def (tblnext tabelle))
)
n
)
;; ------------------------------------------------------------
;; Im aktiven Dokument unbenutzte Bloecke UND Layer purgen, bis
;; stabil. Erst Bloecke, dann Layer (ein nur von einem entfernten
;; Block benutzter Layer wird erst danach frei) - daher Schleife
;; ueber beide Typen, bis sich nichts mehr aendert.
;; ------------------------------------------------------------
(defun pb-purge-current-doc ( / vorher aktuell durchlauf max-durchlauf)
(setq vorher (+ (pb-count-table "BLOCK") (pb-count-table "LAYER"))
aktuell vorher
durchlauf 1
max-durchlauf 20
)
(while (and (<= durchlauf max-durchlauf)
(progn
(command "_.-PURGE" "_B" "*" "_N") ; unbenutzte Bloecke
(command "_.-PURGE" "_LA" "*" "_N") ; unbenutzte Layer
(setq aktuell (+ (pb-count-table "BLOCK") (pb-count-table "LAYER")))
;; weiter, solange noch etwas entfernt wurde
(if (< aktuell vorher)
(progn (setq vorher aktuell) T)
nil
)
)
)
(setq durchlauf (1+ durchlauf))
)
aktuell
)
;; ------------------------------------------------------------
;; Eine DWG-Datei oeffnen (falls noetig), purgen, speichern (nur
;; bei Aenderung) und schliessen (falls hier geoeffnet).
;; Rueckgabe: ('ok blk-entfernt . lay-entfernt) oder ('fehler . meldung)
;; ------------------------------------------------------------
(defun pb-purge-dwg (dwg-pfad / bereits-offen blk-vor blk-nach lay-vor lay-nach
blk-entfernt lay-entfernt fehler)
(setq bereits-offen (pb-find-open-doc dwg-pfad))
(setq blk-entfernt 0 lay-entfernt 0)
(setq fehler
(vl-catch-all-apply
(function
(lambda ()
(if bereits-offen
(vlax-put-property (vlax-get-acad-object) 'ActiveDocument bereits-offen)
(command "_.OPEN" dwg-pfad)
)
(setq blk-vor (pb-count-table "BLOCK")
lay-vor (pb-count-table "LAYER")
)
(pb-purge-current-doc)
(setq blk-nach (pb-count-table "BLOCK")
lay-nach (pb-count-table "LAYER")
blk-entfernt (- blk-vor blk-nach)
lay-entfernt (- lay-vor lay-nach)
)
(if (or (> blk-entfernt 0) (> lay-entfernt 0))
(command "_.QSAVE")
)
(if (not bereits-offen) (command "_.CLOSE"))
)
)
nil
)
)
(if (vl-catch-all-error-p fehler)
(cons 'fehler (vl-catch-all-error-message fehler))
(list 'ok blk-entfernt lay-entfernt)
)
)
;; ------------------------------------------------------------
;; Hauptbefehl - verarbeitet DXFM_DATA/ils
;; ------------------------------------------------------------
(defun c:ILS_BLOCK_PURGE ( / data-dir dwg-dir dateien datei dwg-pfad ergebnis
orig-doc anzahl-ok anzahl-fehler summe-blk summe-lay
blk lay fehler-liste geaendert-liste)
(setq data-dir (getenv "DXFM_DATA"))
(if (not data-dir)
(progn
(alert "DXFM_DATA ist nicht gesetzt.\nBitte zuerst bin/setenv.bat ausfuehren.")
(exit)
)
)
(setq data-dir (vl-string-translate "\\" "/" data-dir)
dwg-dir (vl-string-right-trim "/" (strcat data-dir "/ils"))
)
(if (not (findfile dwg-dir))
(progn
(princ (strcat "\n[BLOCK-PURGE] Verzeichnis nicht gefunden: " dwg-dir))
(exit)
)
)
(setq dateien (vl-directory-files dwg-dir "*.dwg" 1))
(if (not dateien)
(progn
(princ (strcat "\n[BLOCK-PURGE] Keine DWG-Dateien in: " dwg-dir))
(exit)
)
)
(setq orig-doc (vla-get-ActiveDocument (vlax-get-acad-object))
anzahl-ok 0
anzahl-fehler 0
summe-blk 0
summe-lay 0
fehler-liste nil
geaendert-liste nil
)
(foreach datei dateien
(setq dwg-pfad (strcat dwg-dir "/" datei))
(princ (strcat "\n[BLOCK-PURGE] Bearbeite " datei " ..."))
(setq ergebnis (pb-purge-dwg dwg-pfad))
(if (equal (car ergebnis) 'fehler)
(progn
(princ (strcat "\n[BLOCK-PURGE] FEHLER bei " datei ": " (cdr ergebnis)))
(setq anzahl-fehler (1+ anzahl-fehler))
(setq fehler-liste (cons datei fehler-liste))
)
(progn
(setq anzahl-ok (1+ anzahl-ok)
blk (nth 1 ergebnis)
lay (nth 2 ergebnis)
)
(if (or (> blk 0) (> lay 0))
(progn
(princ (strcat " " (itoa blk) " Block(e), " (itoa lay) " Layer entfernt, gespeichert."))
(setq summe-blk (+ summe-blk blk)
summe-lay (+ summe-lay lay)
)
(setq geaendert-liste (cons (strcat datei " (B:" (itoa blk) " L:" (itoa lay) ")") geaendert-liste))
)
(princ " keine unbenutzten Bloecke/Layer.")
)
)
)
)
(vlax-put-property (vlax-get-acad-object) 'ActiveDocument orig-doc)
(princ (strcat "\n[BLOCK-PURGE] Fertig: " (itoa anzahl-ok) " Datei(en) bearbeitet, "
(itoa summe-blk) " Block(e), "
(itoa summe-lay) " Layer entfernt, "
(itoa anzahl-fehler) " Fehler."))
(if geaendert-liste
(progn
(princ "\n[BLOCK-PURGE] Geaenderte Dateien:")
(foreach datei (reverse geaendert-liste)
(princ (strcat "\n - " datei))
)
)
)
(if fehler-liste
(progn
(princ "\n[BLOCK-PURGE] WARNUNG: Fehler bei:")
(foreach datei (reverse fehler-liste)
(princ (strcat "\n - " datei))
)
)
)
(princ)
)
(princ "\npurge_blocks.lsp geladen. Befehl: ILS_BLOCK_PURGE")
(princ)
+44 -3
View File
@@ -787,7 +787,7 @@
;; und Foerderer-Tests) fuehrte das zu minutenlangen Laufzeiten.
;; Zum erzwungenen Neu-Einlesen: ssg-block-refresh-reset.
;; Rueckgabe: effektiver (suffigierter) Blockname oder nil (Datei nicht gefunden).
(defun ssg-ils-block-laden-dim (blockname dim / eff datei base temp-obj osm areq adia)
(defun ssg-ils-block-laden-dim (blockname dim / eff datei base temp-obj osm areq adia n)
(setq eff (ssg-ils-blockname-dim blockname dim))
(setq datei (ssg-ils-block-datei-dim blockname dim))
(if (null datei)
@@ -825,8 +825,22 @@
(if (tblsearch "BLOCK" eff)
(command "_.-INSERT" (strcat eff "=" datei) "_Y") ; Redefine bestehender Block
(command "_.-INSERT" (strcat eff "=" datei))) ; neu (kein Redefine-Prompt)
(if (> (getvar "CMDACTIVE") 0) (command))
(if (> (getvar "CMDACTIVE") 0) (command))
;; Robust statt fixer 2 Versuche abbrechen: manche Block-DWGs (z.B.
;; abweichende INSUNITS) schieben eine zusaetzliche Rueckfrage ein,
;; die zwei (command)-Aufrufe nicht immer leeren. Bleibt danach ein
;; Befehl aktiv, verschluckt der NAECHSTE (command ...)-Aufruf des
;; Aufrufers (die eigentliche Platzierung, z.B. hundm05:bau-sensor/
;; ils-insert-station) dessen Argumente als Antwort auf DIESEN noch
;; offenen Prompt - Ursache eines real beobachteten kaputten Blocks
;; (fehlende Attribute) beim letzten Element eines Testlaufs.
(setq n 0)
(while (and (> (getvar "CMDACTIVE") 0) (< n 10))
(command)
(setq n (1+ n)))
(if (> (getvar "CMDACTIVE") 0)
(princ (strcat "\n[SSG] WARNUNG: Laden von '" eff
"' hat einen Befehl aktiv gelassen (CMDACTIVE > 0) -"
" nachfolgende Einfuegungen sind evtl. beschaedigt.")))
(setvar "OSMODE" osm)(setvar "ATTREQ" areq)(setvar "ATTDIA" adia)))
;; Nur merken, wenn die Definition danach wirklich vorhanden ist -
;; sonst wuerde ein fehlgeschlagener Ladeversuch dauerhaft gesperrt.
@@ -1767,6 +1781,33 @@
(cdr (assoc key obj))
)
;; String fuer die Ausgabe in JSON maskieren (Gegenstueck zu ssg-json-string).
;; Der Backslash MUSS zuerst geprueft werden, sonst wuerde die Maskierung der
;; Anfuehrungszeichen gleich wieder mitmaskiert.
;; Hinweis: csv:json-escape in export.lsp leistet dasselbe, ist aber modul-lokal
;; am Export-Modul - bewusst nicht zusammengelegt, das waere ein eigener Schritt.
(defun ssg-json-escape (s / i c erg)
(if (null s) (setq s ""))
(setq erg "" i 1)
(while (<= i (strlen s))
(setq c (substr s i 1))
(setq erg
(strcat erg
(cond
((= c "\\") "\\\\")
((= c "\"") "\\\"")
((= c (chr 10)) "\\n")
((= c (chr 13)) "\\r")
((= c (chr 9)) "\\t")
(T c)
)
)
)
(setq i (1+ i))
)
erg
)
;; Verschachteltes JSON-Objekt parsen (1 Ebene tief)
;; {"section": {"key": val, ...}, ...}
+6
View File
@@ -38,6 +38,11 @@
;; pattern_btmt_entladung in cfg/export.cfg) aus demselben Grund: sonst bleibt
;; ihre ID trotz ATTDEF leer und sie fehlen bei der zeichnungsweiten Duplikat-
;; pruefung/Maximum-Ermittlung (ssg-id-check-all).
;; Eingang*/Ausgang* (Materialfluss-Ein-/Ausgangssymbole, siehe
;; pattern_eingang/pattern_ausgang in cfg/export.cfg) ebenso: die DWGs haben
;; seit dem ID-Nachtrag (allocations.json) ein leeres ID-ATTDEF, das hier
;; gefuellt werden muss, damit sie eine stabile TeileId fuer die "TrackIds"-
;; Spalte (auch auf Kreiseln) bekommen.
;; GR_* (Geruest-Baugruppen, siehe pattern_geruest in cfg/export.cfg) ebenso -
;; das ID-ATTDEF steht seit *geruest-attrib-defs* zur Verfuegung.
(defun ssg-id-collect-blocks ( / ss-all ss-out i ename ed bname)
@@ -57,6 +62,7 @@
(wcmatch bname "Vario*,Staustrecke*,AUS_Element*,EIN_Element*,VF_*,GF_*")
(wcmatch bname "Separator_SP*,S-LP*,Scanner*")
(wcmatch bname "BTMT-Beladung*,SC_Entladung*")
(wcmatch bname "Eingang*,Ausgang*")
(wcmatch bname "GR_*")
(wcmatch bname "#*")
)
+20 -1
View File
@@ -413,13 +413,32 @@
(reverse liste)
)
;; Koordinate mit GENAU 2 Nachkommastellen formatieren - bewusst OHNE rtos.
;; rtos haengt an den Zeichnungs-Systemvariablen DIMZIN (Nullunterdrueckung),
;; LUPREC und LUNITS: bei dem hier verbreiteten DIMZIN=8 wuerde rtos die
;; abschliessenden Nullen wegwerfen (10.00 -> 10), und dieselben Koordinaten
;; serialisierten je nach Zeichnungsprofil unterschiedlich. Die Sepliste
;; landet als XDATA/Journal PERSISTENT am Block - die Serialisierung muss
;; darum reproduzierbar und profilunabhaengig sein. Rundung kaufmaennisch,
;; mit Uebertrag (9.999 -> 10.00), Vorzeichen bleibt erhalten.
(defun ssg-sepliste-fmt-koord (x / neg i f s)
(setq neg (< x 0.0) x (abs x))
(setq i (fix x))
(setq f (fix (+ 0.5 (* 100.0 (- x i)))))
(if (>= f 100) (progn (setq i (1+ i)) (setq f (- f 100))))
(setq s (strcat (itoa i) "."
(if (< f 10) (strcat "0" (itoa f)) (itoa f))))
(if neg (strcat "-" s) s)
)
;; Serialisiert die Sepliste zu "TYP:LFDNR:X:Y,TYP:LFDNR:X:Y,...".
(defun ssg-sepliste->string (liste / eintrag out first)
(setq out "" first t)
(foreach eintrag liste
(setq eintrag
(strcat (nth 0 eintrag) ":" (itoa (nth 1 eintrag)) ":"
(rtos (nth 2 eintrag) 2 2) ":" (rtos (nth 3 eintrag) 2 2)))
(ssg-sepliste-fmt-koord (nth 2 eintrag)) ":"
(ssg-sepliste-fmt-koord (nth 3 eintrag))))
(if first (progn (setq out eintrag) (setq first nil))
(setq out (strcat out "," eintrag)))
)
+1 -2
View File
@@ -35,12 +35,11 @@
(load (strcat base-path "VarioFoerderer.lsp") "VarioFoerderer.lsp nicht gefunden")
(load (strcat base-path "Gefaellestrecke.lsp") "Gefaellestrecke.lsp nicht gefunden")
(load (strcat base-path "export.lsp") "export.lsp nicht gefunden")
(load (strcat base-path "tests.lsp") "tests.lsp nicht gefunden")
(load (strcat base-path "OmniModulInsert.lsp") "OmniModulInsert.lsp nicht gefunden")
(load (strcat base-path "Tro_Edit.lsp") "Tro_Edit.lsp nicht gefunden")
(load (strcat base-path "Connection_Edit.lsp") "Connection_Edit.lsp nicht gefunden")
(load (strcat base-path "SSG_LIB_Commands.lsp") "SSG_LIB_Commands.lsp nicht gefunden")
(prompt "\nSSG-Bibliotheken geladen: ssg_core, ssg_lang, ssg_dbg, ssg_dialog, ssg_layer, KreiselInsert, VarioFoerderer, Gefaellestrecke, export, tests, OmniModulInsert, SSG_LIB_Commands")
(prompt "\nSSG-Bibliotheken geladen: ssg_core, ssg_lang, ssg_dbg, ssg_dialog, ssg_layer, KreiselInsert, VarioFoerderer, Gefaellestrecke, export, OmniModulInsert, SSG_LIB_Commands")
(princ)
)
+5 -34
View File
@@ -138,40 +138,11 @@
;; [ sin(hz)*cos(vert) cos(hz) sin(hz)*sin(vert) ]
;; [ -sin(vert) 0 cos(vert) ]
(defun insert-hz-incl-scaled-block (blockname startpunkt laenge hz-winkel vert-winkel /
rad-hz rad-vert chz shz cv sv scale
matrix block-obj endpunkt)
(setq scale (/ (float laenge) 1000.0))
(setq rad-hz (* (float hz-winkel) (/ pi 180.0)))
(setq rad-vert (* (float vert-winkel) (/ pi 180.0)))
(setq chz (cos rad-hz) shz (sin rad-hz))
(setq cv (cos rad-vert) sv (sin rad-vert))
(setq blockname (ensure-block-loaded blockname))
(if (not (tblsearch "BLOCK" blockname))
(progn
(princ (ssg-textf "vfe-block-fehlt-abbruch" (list blockname)))
(exit)
)
)
(setq block-obj (vla-InsertBlock modelspace
(vlax-3D-point '(0 0 0)) blockname scale 1.0 1.0 0))
;; Einzelteil auf die eigene Ebene der Blockdefinition legen - wie in den
;; Zwillingen in ssg_ks_insert.lsp; hier fehlte der Aufruf bisher.
(ssg-ils-block-auf-ebene block-obj blockname)
(setq matrix (vf-rot-matrix chz shz cv sv))
(vla-TransformBy block-obj (vlax-tmatrix matrix))
(vla-Move block-obj (vlax-3D-point '(0 0 0)) (vlax-3D-point startpunkt))
(setq endpunkt (list
(+ (car startpunkt) (* laenge chz cv))
(+ (cadr startpunkt) (* laenge shz cv))
(+ (caddr startpunkt) (* (- sv) laenge))))
(princ (ssg-textf "vfe-hz-incl-status"
(list blockname (rtos laenge 2 0)
(strcat (rtos hz-winkel 2 0) (chr 176))
(strcat (rtos vert-winkel 2 0) (chr 176))
(rtos (caddr endpunkt) 2 1))))
endpunkt
)
;; HINWEIS: insert-hz-incl-scaled-block wurde entfernt (2026-09-18) - toter Code,
;; kein Aufrufer im Repo. Die verbleibenden drei insert-hz-*-Funktionen bleiben,
;; weil sie sich in der Platzierungssemantik (KS_EIN vs. Block-Ursprung) bzw. der
;; Skalierung+KS+Vertikale-Kombination von den ssg_ks_insert-Primitiven
;; unterscheiden und ein Ersatz die Geometrie verschoebe (byte-genaue Pruefung).
(defun insert-hz-rotated-block-with-ks (blockname startpunkt hz-winkel vert-winkel block-dx block-dz /
rad-hz rad-vert chz shz cv sv
+11
View File
@@ -193,6 +193,17 @@
;; etage-foerderanlage-einfuegen (vf_etage.lsp).
(if (null *vfk-etage-auslauf-min-clamp*) (setq *vfk-etage-auslauf-min-clamp* 50.0))
;; Mindest-2D-Laenge (mm) eines Pfad-Segments (LINE-Laenge bzw. ARC-Sehne),
;; das der Linienzug-Modus 2/3 aus einer Objektauswahl akzeptiert. Kuerzere
;; Objekte sind Geister-/Artefakt-Elemente (z.B. ein von einer Rechteck-Auswahl
;; miterfasstes Mini-Segment) und werden VOR der Sortierung ausgeworfen, mit
;; Warnung an den Nutzer (vfl-in-segmente). Konservativ deutlich unter jedem
;; realen Bauteil (kleinstes Bauteil = Separator 300 mm; kleinster echter
;; Eck-Bogen R500/30 Grad hat eine Sehne von ~259 mm) und knapp ueber dem
;; allgemeinen Restlaengen-Clamp (100 mm), damit kein legitimes Segment
;; faelschlich verworfen wird.
(if (null *vfk-pfad-min-segmentlaenge*) (setq *vfk-pfad-min-segmentlaenge* 100.0))
;; ============================================================
;; TEIL 4: FALLBACK-VERSATZWERTE (mm) - AS-/ES-/Gefaellebogen-Masse, falls
;; die Blockdefinition/das Koordinatensystem nicht extrahierbar ist
+435 -165
View File
@@ -2013,6 +2013,59 @@
(setq s (substr s (+ pos 1 (strlen sep)))))
(reverse (cons s out)))
;; --- SEGS: Segment-Essenz einer Modus-3-Kette (selbst-enthalten) ---
;; Ein SEGS-Wert ist die AUS DEN PFAD-OBJEKTEN ABGELEITETE Zahlenliste, die der
;; Modus-3-Bau ohnehin allein verwendet (gf-analysiere-kette + Start/Endpunkte
;; aus gf-sortiere-objekte). Sie ersetzt im Journal die frueheren OBJS-Handles:
;; dadurch ist die Kette im XDATA vollstaendig beschrieben und beim Doppelklick-
;; Replay unabhaengig davon, ob die urspruenglichen LINE/ARC noch existieren
;; (robuster als der OBJS-Weg von Modus 2). Siehe doc/variofoerderer.md.
;;
;; Ein Segment ist eine 9er-Liste, die die "Bau-Essenz" und die fuer die
;; AS-Trimmung/das Soll-Ende noetigen absoluten Punkte zusammenfuehrt:
;; (typ hz laenge bwinkel bseite spx spy epx epy)
;; typ "Linie"/"Bogen" (delimiter-frei, daher unquotiert serialisiert)
;; hz Eintritts-Horizontalwinkel (Grad)
;; laenge 2D-Laenge / Sehnenmass (mm)
;; bwinkel Bogenwinkel (0 bei Linie)
;; bseite "links"/"rechts"
;; spx spy Startpunkt (XY, mm) des Segments \ aus gf-sortiere-objekte,
;; epx epy Endpunkt (XY, mm) des Segments / fuer Trimmung + Soll-Ende
;;
;; Serialisierung: Segmente per ";" getrennt, Felder je Segment per "|". Beide
;; Trenner sind kollisionsfrei - die einzigen String-Felder (typ/bseite) sind
;; feste Schluesselwoerter ohne ";" oder "|", alle uebrigen Felder sind Zahlen
;; (rtos-Mode-2, wie PT/REAL). Ein leeres SEGS ("") ist eine leere Liste.
(defun vfl-seg->string (seg)
(vfl-strjoin
(list (nth 0 seg) ; typ (String)
(rtos (float (nth 1 seg)) 2 6) ; hz
(rtos (float (nth 2 seg)) 2 6) ; laenge
(rtos (float (nth 3 seg)) 2 6) ; bwinkel
(nth 4 seg) ; bseite (String)
(rtos (float (nth 5 seg)) 2 6) ; spx
(rtos (float (nth 6 seg)) 2 6) ; spy
(rtos (float (nth 7 seg)) 2 6) ; epx
(rtos (float (nth 8 seg)) 2 6)) ; epy
"|"))
(defun vfl-string->seg (s / f)
(setq f (vfl-strsplit s "|"))
(list (nth 0 f) ; typ
(atof (nth 1 f)) ; hz
(atof (nth 2 f)) ; laenge
(fix (atof (nth 3 f))) ; bwinkel (Ganzzahl, wie gf-analysiere-kette)
(nth 4 f) ; bseite
(atof (nth 5 f)) ; spx
(atof (nth 6 f)) ; spy
(atof (nth 7 f)) ; epx
(atof (nth 8 f)))) ; epy
(defun vfl-segs->string (segs)
(vfl-strjoin (mapcar 'vfl-seg->string segs) ";"))
(defun vfl-string->segs (s)
(if (= (strlen s) 0)
'()
(mapcar 'vfl-string->seg (vfl-strsplit s ";"))))
;; --- Serialisierung (tagged, selbstbeschreibend) ---
;; Zeilenformat "kind:payload", Eintraege per "\n" verkettet. getstring/getreal
;; liefern nie ein Newline, daher ist "\n" ein sicherer Record-Trenner.
@@ -2020,6 +2073,8 @@
;; Entity-Handles (Hex-Strings, kollidieren nie mit dem Komma-Trenner).
;; Handles bleiben - anders als Enames - ueber Sitzungen/Undo hinweg stabil
;; und sind damit die richtige Persistenzform fuer ssget-Ergebnisse.
;; "SEGS" (Segment-Essenz, Modus 3): Payload = ";"-getrennte Segmente, Felder
;; per "|" (siehe vfl-segs->string oben).
(defun vfl-entry->string (e / k v)
(setq k (car e) v (cdr e))
(cond
@@ -2031,6 +2086,7 @@
((= k "STR") (strcat "STR:" v))
((= k "STEP") (strcat "STEP:" v))
((= k "OBJS") (strcat "OBJS:" (vfl-strjoin v ",")))
((= k "SEGS") (strcat "SEGS:" (vfl-segs->string v)))
(t "NIL:")))
(defun vfl-string->entry (ln / p kind pay c)
(setq p (vl-string-search ":" ln))
@@ -2048,6 +2104,7 @@
((= kind "STR") (cons "STR" pay))
((= kind "STEP") (cons "STEP" pay))
((= kind "OBJS") (cons "OBJS" (if (> (strlen pay) 0) (vfl-strsplit pay ",") nil)))
((= kind "SEGS") (cons "SEGS" (vfl-string->segs pay)))
(t (cons "NIL" nil))))))
;; forward-journal ist bereits in Bau-Reihenfolge (nicht die umgekehrte
;; *vfl-journal*-Rohform) - gemeinsame Serialisierung fuer Ketten- UND
@@ -2086,6 +2143,21 @@
((= k "OBJS") (strcat "[" (vfl-strjoin
(mapcar (function (lambda (h) (strcat "\"" (vfl-json-escape h) "\"")))
v) ",") "]"))
((= k "SEGS") (strcat "["
(vfl-strjoin
(mapcar
(function (lambda (seg)
(strcat "[\"" (vfl-json-escape (nth 0 seg)) "\","
(vfl-json-num (float (nth 1 seg))) ","
(vfl-json-num (float (nth 2 seg))) ","
(vfl-json-num (float (nth 3 seg))) ",\""
(vfl-json-escape (nth 4 seg)) "\","
(vfl-json-num (float (nth 5 seg))) ","
(vfl-json-num (float (nth 6 seg))) ","
(vfl-json-num (float (nth 7 seg))) ","
(vfl-json-num (float (nth 8 seg))) "]")))
v) ",")
"]"))
(t "null"))
"}"))
;; forward-journal (Bau-Reihenfolge) als kompakter JSON-Array-String.
@@ -2133,7 +2205,7 @@
;; Rueckgabe: (marker . journal) oder nil (kein/unbekanntes Journal).
(defun vfl-journal-xdata-lesen (ent / xd)
(setq xd (vfs-xdata-lesen ent))
(if (and xd (member (car xd) '("linienzug" "linienzug2")))
(if (and xd (member (car xd) '("linienzug" "linienzug2" "linienzug3")))
(cons (car xd) (vfl-string->journal (apply 'strcat (cdr xd))))
nil))
@@ -2306,6 +2378,47 @@
;; sich um einen frischen Bau oder einen Editier-Neuaufbau handelte (kein
;; Sonderfall mehr noetig). Aufgerufen aus dem *error*-Handler von
;; vf-linienzug-modus.
;; Den scharfen Abbruch-Wickel-Handler als *error* installieren - gemeinsam
;; fuer alle drei Linienzug-Modi (frueher 3x wortgleich ausgeschrieben, nur der
;; Marker-String "linienzug"/"linienzug2"/"linienzug3" unterschied sich).
;;
;; Der Handler faengt jeden Abbruch (ESC / (exit) / Laufzeitfehler) ab, wickelt
;; die bis dahin gebaute Teil-Geometrie per vfl-modus-abbruch-sichern zu einem
;; VF_n-Block mit dem vollen Journal (Marker als XDATA) und stellt *error*
;; wieder her. Details/Begruendung (dynamic scoping, *ssg-ils-dim*-Reset,
;; ssg-end) siehe die ausfuehrlichen Kommentare an der frueheren Aufrufstelle
;; in vf-linienzug-modus.
;;
;; VERTRAG (dynamic scoping): Der Aufrufer MUSS als Locals gebunden haben und
;; VOR diesem Aufruf gesetzt haben: old-error (Ruecksprung-*error*), lastEnt,
;; vfl-nummer, anzahl-gf, anzahl-vf, startpunkt, frame, as-seite, es-seite,
;; dbg-an. Sie werden vom Handler-Lambda zur ABBRUCHZEIT dynamisch aufgeloest -
;; also gegen den dann noch aktiven Modus-Frame, nicht gegen diesen Konstruktor
;; (der ist beim Abbruch laengst zurueckgekehrt). marker wird dagegen ZUR
;; INSTALL-ZEIT als String-Literal in den Lambda eingebacken (list-Konstruktion),
;; damit der Handler ihn ohne globalen Zwischenzustand selbst traegt.
(defun vfl-abbruch-handler-installieren (marker)
;; *error* auf eine LAMBDA-LISTE setzen, in die der marker als String-Literal
;; eingebaut ist (list-Konstruktion). AutoLISP ruft *error* als Funktion auf,
;; egal ob (lambda ...) oder (function (lambda ...)) - die reine Lambda-Liste
;; genuegt und vermeidet eval/function-Feinheiten. Alle uebrigen Symbole
;; (old-error, lastEnt, ...) bleiben ungequotet-frei und werden zur Aufrufzeit
;; dynamisch aufgeloest.
(setq *error*
(list 'lambda '(msg)
'(setq *error* old-error)
'(setq *ssg-ils-dim* nil)
(list 'vfl-modus-abbruch-sichern 'lastEnt 'vfl-nummer 'anzahl-gf 'anzahl-vf
'startpunkt 'frame 'as-seite 'es-seite marker)
'(if dbg-an
(progn
(dbgmsg (strcat "=== ABBRUCH: " (if msg msg "(exit)") " ==="))
(dbgreturn nil)
(dbgclose)))
'(if (and (boundp '*ssg-start-stack*) *ssg-start-stack*) (ssg-end))
'(princ)))
*error*)
(defun vfl-modus-abbruch-sichern (lastEnt vfl-nummer anzahl-gf anzahl-vf
startpunkt frame as-seite es-seite marker /
e cnt vfl-ins wickel-erg grund fwd n trunc)
@@ -5943,6 +6056,60 @@
(vf-set-es-masse winkel seite)
seite)
;; AS-Winkel (30/90) + Seite abfragen. Gemeinsame Schale fuer die frueher
;; wortgleich in vf-linienzug-modus/-modus2/-modus3 UND vfl-qc-linie-mit-gf
;; ausgeschriebene AS-Praeambel (Winkel-Frage 30/90, Seiten-Frage links/rechts,
;; vf-set-as-masse) - Gegenstueck zu vfl-frage-es-seite. Setzt *vfl-as-winkel*
;; und die AS-Masse (aus-dx/dz) fuer die Variante. Rueckgabe: Seite (links/rechts).
;;
;; seiten-headerkey ist der EINZIGE Unterschied zwischen den Aufrufstellen
;; (modus1: "vfl-aus-seite-header", modus2: "gf-seite-aus-header") und daher
;; Parameter. Die je Modus unterschiedliche VORANGEHENDE Wizard-Vorbereitung
;; (vflw-gruppe-as-impl "AS setzen?" bzw. vflw-gruppe-winkel-seite-impl) bleibt
;; bewusst an der Aufrufstelle - die abzudeckende Winkel/Seite-Frage selbst ist
;; ueberall identisch. vf-frage-element-winkel/vflw-wahl zeigen den Wizard-
;; Winkeldialog bei aktivem Wizard-Modus schon selbst.
(defun vfl-frage-as-winkel-seite (seiten-headerkey / antwort as-seite)
(princ (ssg-text "vf-winkel-aus-header"))
(princ (ssg-text "vf-winkel-90"))
(princ (ssg-text "vf-winkel-30"))
(setq *vfl-as-winkel*
(vfl-in-value "STR"
(function (lambda () (vf-frage-element-winkel "vf-winkel-aus-header"))))) ; 30/90 vor Seite
(princ (ssg-text seiten-headerkey))
(princ (ssg-text "gf-seite-links"))
(princ (ssg-text "gf-seite-rechts"))
(setq antwort (vfl-menu (ssg-text "prompt-wahl-1-2")
(list (ssg-text "gf-seite-links") (ssg-text "gf-seite-rechts")) 1
seiten-headerkey))
(setq as-seite (if (= antwort "2") "rechts" "links"))
(vf-set-as-masse *vfl-as-winkel* as-seite) ; Masse fuer Variante
as-seite)
;; ES-Winkel (30/90) + Seite abfragen - MARKER-FREIE Schale fuer die Modi 2/3,
;; die das ES-Element als eigene Praeambel (Start/Ziel bereits fest) abfragen
;; und ihr eigenes Journal-/Masse-Schema fuehren. Unterscheidet sich bewusst
;; von vfl-frage-es-seite (Modus 1), das zusaetzlich einen Journal-Marker "ES"
;; setzt und ueber vfl-es-masse-setzen laeuft - das ist Teil des Modus-1-
;; Kettenglied-Replays und darf hier NICHT feuern. Setzt *vfl-es-winkel* und
;; die ES-Masse (ein-dx/dz). Rueckgabe: Seite (links/rechts).
(defun vfl-frage-es-winkel-seite (seiten-headerkey / antwort es-seite)
(princ (ssg-text "vf-winkel-ein-header"))
(princ (ssg-text "vf-winkel-90"))
(princ (ssg-text "vf-winkel-30"))
(setq *vfl-es-winkel*
(vfl-in-value "STR"
(function (lambda () (vf-frage-element-winkel "vf-winkel-ein-header"))))) ; 30/90 vor Seite
(princ (ssg-text seiten-headerkey))
(princ (ssg-text "gf-seite-links"))
(princ (ssg-text "gf-seite-rechts"))
(setq antwort (vfl-menu (ssg-text "prompt-wahl-1-2")
(list (ssg-text "gf-seite-links") (ssg-text "gf-seite-rechts")) 1
seiten-headerkey))
(setq es-seite (if (= antwort "2") "rechts" "links"))
(vf-set-es-masse *vfl-es-winkel* es-seite) ; Masse fuer Variante
es-seite)
;; dialog-schon-gezeigt: T, wenn Winkel+Seite BEREITS ueber einen vorherigen
;; kombinierten Dialog (vflw-gruppe-as-impl, "ES-Element setzen?" + Winkel +
;; Seite in einem Bildschirm) abgefragt und in die Pending-Queue gelegt
@@ -6317,32 +6484,11 @@
(setq lastEnt (vf-lastent-ohne-attribute))
(setq anzahl-gf 0 anzahl-vf 0 frame nil)
(setq old-error *error*)
;; Abbruch-Handler: zuerst *error* zuruecksetzen (kein rekursiver
;; Wiedereintritt bei einem Fehler waehrend des Wickelns), dann wickeln.
;; Laeuft der Aufruf innerhalb einer ssg-start-Sitzung (Editier-Pfad via
;; c:VARIOFOERDERER_EDIT -> vfl-edit-ent), wird diese mit ssg-end sauber
;; geschlossen (Undo-Gruppe, gesicherte Systemvariablen, *error* aus dem
;; Sitzungs-Frame) - sonst bliebe die von ssg-start geoeffnete Undo-Gruppe
;; offen.
;; *ssg-ils-dim*: der Editier-/Konverter-Pfad setzt vor dem Replay einen
;; Dimensions-Override und raeumt ihn NACH der Rueckkehr wieder ab. Bei einem
;; Abbruch kehrt der Aufruf nie dorthin zurueck (der Handler beendet den
;; Befehl), daher MUSS der Override hier zurueckgesetzt werden - sonst bliebe
;; er fuer den Rest der Sitzung stehen und alle folgenden Einfuegungen liefen
;; in der falschen Dimension.
(setq *error*
(function (lambda (msg)
(setq *error* old-error)
(setq *ssg-ils-dim* nil)
(vfl-modus-abbruch-sichern lastEnt vfl-nummer anzahl-gf anzahl-vf
startpunkt frame as-seite es-seite "linienzug")
(if dbg-an
(progn
(dbgmsg (strcat "=== ABBRUCH: " (if msg msg "(exit)") " ==="))
(dbgreturn nil)
(dbgclose)))
(if (and (boundp '*ssg-start-stack*) *ssg-start-stack*) (ssg-end))
(princ))))
;; Scharfen Abbruch-Wickel-Handler installieren (gemeinsam fuer alle drei
;; Modi, siehe vfl-abbruch-handler-installieren). old-error/lastEnt/vfl-nummer/
;; anzahl-gf/anzahl-vf/startpunkt/frame/as-seite/es-seite/dbg-an sind hier als
;; Locals gebunden und werden vom Handler zur Abbruchzeit dynamisch gesehen.
(vfl-abbruch-handler-installieren "linienzug")
(if (vfl-wizard-aktiv)
(vl-catch-all-apply 'vflw-gruppe-punkt-hoehe-impl
@@ -6390,22 +6536,7 @@
;; tragen, wenn diesmal kein AS gewaehlt wird.
(setq *vfl-as-winkel* nil *vfl-es-winkel* nil)
(if as-vorhanden
(progn
(princ (ssg-text "vf-winkel-aus-header"))
(princ (ssg-text "vf-winkel-90"))
(princ (ssg-text "vf-winkel-30"))
(setq *vfl-as-winkel*
(vfl-in-value "STR"
(function (lambda () (vf-frage-element-winkel "vf-winkel-aus-header"))))) ; 30/90 vor Seite
(princ (ssg-text "vfl-aus-seite-header"))
(princ (ssg-text "gf-seite-links"))
(princ (ssg-text "gf-seite-rechts"))
(setq antwort (vfl-menu (ssg-text "prompt-wahl-1-2")
(list (ssg-text "gf-seite-links") (ssg-text "gf-seite-rechts")) 1
"vfl-aus-seite-header"))
(setq as-seite (if (= antwort "2") "rechts" "links"))
(vf-set-as-masse *vfl-as-winkel* as-seite) ; Masse fuer Variante
)
(setq as-seite (vfl-frage-as-winkel-seite "vfl-aus-seite-header"))
)
;; ES-Seite wird erst am Kettenende abgefragt (siehe vfl-frage-es-seite),
;; da das ES-Element erst dort eingefuegt wird.
@@ -8183,22 +8314,29 @@
;; und der Override sicher zurueckgesetzt wird. Rueckgabe: T bei Erfolg, sonst nil.
;; Bekannte Grenze (nur Modus 2): das Replay von vfl-in-selection braucht die
;; Original-Pfadobjekte; wurden sie geloescht, schlaegt der Konverter fuer diesen
;; Block sauber fehl (sichtbar am nicht erhoehten Batch-Zaehler).
;; Block sauber fehl (sichtbar am nicht erhoehten Batch-Zaehler). Modus 3
;; ("linienzug3") hat diese Grenze NICHT: es replayt die Segment-Essenz (SEGS)
;; aus dem Journal, nicht die Objekt-Handles - unabhaengig von den Original-LINE/ARC.
(defun vfl-konvertiere-ent (ent ziel-dim / bname xd marker journal ok)
(setq bname (cdr (assoc 2 (entget ent))))
(setq xd (vfl-journal-xdata-lesen ent)) ; (marker . journal) oder nil
(setq marker (car xd) journal (cdr xd))
(if (and (wcmatch bname "VF_*")
(member marker '("linienzug" "linienzug2"))
journal)
(member marker '("linienzug" "linienzug2" "linienzug3"))
journal
;; Alte OBJS-"linienzug2"-Bloecke (ohne SEGS) NICHT konvertieren -
;; ihr Replay ist mit dem SEGS-Umbau nicht mehr fahrbar (harter
;; Schnitt, wie im Doppelklick-Pfad vfl-edit-ent). Sie ueberspringen
;; (nil zurueck), damit die Batch-Schleife sauber weiterlaeuft.
(or (/= marker "linienzug2") (vfl-journal-hat-segs-p journal)))
(progn
(setq *ssg-ils-dim* ziel-dim) ; Zieldim erzwingen
(entdel ent) ; alten Block weg
(vfl-journal-replay-start journal) ; volle Queue, KEIN Truncate
(setq ok (vl-catch-all-apply
(if (= marker "linienzug2")
'vf-linienzug-modus2
'vf-linienzug-modus)))
(cond ((= marker "linienzug2") 'vf-linienzug-modus2)
((= marker "linienzug3") 'vf-linienzug-modus3)
(t 'vf-linienzug-modus))))
(setq *ssg-ils-dim* nil) ; Override zuruecksetzen
;; Fehlgeschlagene Konvertierung NICHT stumm verschlucken: der alte Block
;; ist per entdel schon weg, die Teil-Geometrie liegt frei in der
@@ -8224,8 +8362,17 @@
;; Doppelklick kein *ssg-ils-dim*-Override aktiv ist). Wird VOR dem entdel
;; gelesen; der Override greift beim anschliessenden Replay/Weiterbau.
(setq dim (ssg-dim-xdata-lesen ent))
(if (= marker "linienzug2")
(vfl-edit-ent2 ent journal)
(cond
((= marker "linienzug2")
;; Harter Schnitt: nur SEGS-Bloecke (neue Fassung) sind per Doppelklick
;; neu aufbaubar. Alte OBJS-Bloecke (Objekt-Handles statt Segment-Essenz)
;; wuerden im Replay falsche Daten in den Bau geben - klar abweisen statt
;; zu crashen.
(if (vfl-journal-hat-segs-p journal)
(vfl-edit-ent2 ent journal)
(alert (ssg-text "vfl-edit-m2-obsolet"))))
((= marker "linienzug3") (vfl-edit-ent3 ent journal))
(t
(progn
(setq glieder (vfl-journal-glieder journal))
(setq n (length glieder))
@@ -8281,15 +8428,27 @@
;; Handler selbst (dort ebenfalls (setq *ssg-ils-dim* nil)), denn
;; die naechste Zeile wird dann nicht mehr erreicht.
(vf-linienzug-modus)
(setq *ssg-ils-dim* nil)))))
(setq *ssg-ils-dim* nil))))))
(princ))
;; Modus-2-Block per Doppelklick zuruecksetzen: voller Reset - die komplette
;; gespeicherte Eingabe (inkl. der gewaehlten Pfad-Objekte, siehe
;; vfl-in-selection) wird 1:1 stumm abgespielt, identischer Neuaufbau. Kein
;; T, wenn das Journal einen SEGS-Eintrag traegt (Modus 2 NEU / Modus 3 - die
;; Kette ist per Segment-Essenz selbst-enthalten). Alte Modus-2-Bloecke tragen
;; hier stattdessen einen OBJS-Eintrag (Objekt-Handles) - fuer die ist der
;; SEGS-Replay nicht mehr fahrbar (harter Schnitt, siehe vfl-edit-ent).
(defun vfl-journal-hat-segs-p (journal)
(and (vl-some (function (lambda (e) (equal (car e) "SEGS"))) journal) t))
;; Modus-2/3-Block per Doppelklick zuruecksetzen: voller Reset - die komplette
;; gespeicherte Eingabe wird 1:1 stumm abgespielt, identischer Neuaufbau. Kein
;; Sektions-Dialog; der Nutzer kann den neu entstandenen Block danach ganz
;; normal weiterbearbeiten oder loeschen.
(defun vfl-edit-ent2 (ent journal / dim)
;;
;; bau-fn = die Modus-Bau-Funktion, die das Journal abspielt: 'vf-linienzug-modus2
;; fuer Marker "linienzug2" (Pfad-Objekte per OBJS-Handles) bzw.
;; 'vf-linienzug-modus3 fuer "linienzug3" (Segment-Essenz per SEGS - unabhaengig
;; von den Original-Objekten). Beide Modi haben eine flache Klassifizierungs-
;; Schleife ohne editierbare Einzelglieder, daher derselbe volle Reset.
(defun vfl-edit-ent-voll (ent journal bau-fn / dim)
;; Dimension des Bestandsblocks VOR dem entdel merken und beim Neubau als
;; Override erzwingen (dimensionstreuer Doppelklick-Neubau, sonst globale
;; DXFM_DIM). Danach Override zuruecksetzen.
@@ -8300,12 +8459,17 @@
(vfl-journal-replay-start journal)
;; DIREKTER Aufruf, KEIN vl-catch-all-apply - identische Begruendung wie im
;; Sektions-Zweig von vfl-edit-ent (siehe dortiger Kommentar): der alte Block
;; ist per entdel schon weg, bei einem Abbruch muss der *error*-Handler von
;; vf-linienzug-modus2 die Teil-Geometrie wickeln. Den Dim-Override raeumt im
;; Abbruchfall der Handler ab.
(vf-linienzug-modus2)
;; ist per entdel schon weg, bei einem Abbruch muss der *error*-Handler der
;; Modus-Funktion die Teil-Geometrie wickeln. Den Dim-Override raeumt im
;; Abbruchfall der Handler ab. bau-fn ist ein Funktions-SYMBOL (quoted beim
;; Aufruf) - ueber apply aufrufen, nicht als (bau-fn) (das riefe die Funktion
;; namens "bau-fn" auf, nicht den durchgereichten Wert).
(apply bau-fn nil)
(setq *ssg-ils-dim* nil)
(princ))
;; Rueckwaertskompatibler Wrapper (Modus 2). vfl-edit-ent3 fuer Modus 3.
(defun vfl-edit-ent2 (ent journal) (vfl-edit-ent-voll ent journal 'vf-linienzug-modus2))
(defun vfl-edit-ent3 (ent journal) (vfl-edit-ent-voll ent journal 'vf-linienzug-modus3))
;; ============================================================
;; RECOVERY: Kette aus der Sektions-XDATA der Zeichnung neu aufbauen
@@ -8701,14 +8865,115 @@
(foreach x liste (setq d (abs (- x w))) (if (< d bd) (setq bd d best x)))
best)
;; --- Segment-Essenz einer selektierten Pfad-Kette (Modus 3) --------------
;; Fuehrt die 5-Feld-Bau-Segmente aus gf-analysiere-kette mit den absoluten
;; Start-/Endpunkten aus gf-sortiere-objekte (kette: (ent sp ep umk)) zu der
;; selbstbeschreibenden 9-Feld-Form zusammen, die das SEGS-Journal traegt:
;; (typ hz laenge bwinkel bseite spx spy epx epy)
;; Beide Listen sind gleich lang und in derselben Reihenfolge (segmente wurde
;; genau aus kette abgeleitet). spx/spy/epx/epy sind die planaren XY der
;; Segment-Enden - sie ersetzen den spaeteren direkten kette-Zugriff (AS-
;; Trimmung am ersten Segment, Soll-Ende-Report), sodass der Bau OHNE die
;; Original-Entities auskommt und beim Replay aus dem Journal gespeist wird.
(defun vfl-kette->segmente9 (kette segmente / out i seg item sp ep)
(setq out '() i 0)
(foreach seg segmente
(setq item (nth i kette) sp (cadr item) ep (caddr item))
(setq out (cons (list (nth 0 seg) (nth 1 seg) (nth 2 seg) (nth 3 seg) (nth 4 seg)
(car sp) (cadr sp) (car ep) (cadr ep))
out))
(setq i (1+ i)))
(reverse out))
;; 2D-Laenge eines Pfad-Objekts: LINE = Abstand Start-Endpunkt, ARC = Sehne
;; (Abstand Start-Endpunkt, dasselbe Mass wie gf-analysiere-kette es fuer die
;; Segmentlaenge nutzt). Nicht-LINE/ARC liefern 0.0 (werden dadurch als "zu
;; kurz" ausgewertet und verworfen). Rein lesend, ohne Nebeneffekt.
(defun vfl-obj-2d-laenge (obj / objname sp ep)
(setq objname (vla-get-ObjectName obj))
(if (or (= objname "AcDbLine") (= objname "AcDbArc"))
(progn
(setq sp (vlax-safearray->list (vlax-variant-value (vla-get-StartPoint obj))))
(setq ep (vlax-safearray->list (vlax-variant-value (vla-get-EndPoint obj))))
(gf-abstand-2d sp ep))
0.0))
;; Aus einer Objektliste alle Elemente unter *vfk-pfad-min-segmentlaenge*
;; herauswerfen (Geister-/Artefakt-Segmente, z.B. von einer Rechteck-Auswahl
;; miterfasste Mini-Objekte). Rueckgabe: (behalten-liste . verworfen-anzahl).
;; Reine Filterung, keine Meldung - der Aufrufer entscheidet ueber die Warnung.
(defun vfl-filter-mini-objekte (obj-liste / behalten verworfen)
(setq behalten '() verworfen 0)
(foreach obj obj-liste
(if (< (vfl-obj-2d-laenge obj) *vfk-pfad-min-segmentlaenge*)
(setq verworfen (1+ verworfen))
(setq behalten (cons obj behalten))))
(cons (reverse behalten) verworfen))
;; Objektauswahl + Analyse (Live) ODER Segment-Essenz aus dem Journal (Replay).
;; Gegenstueck zu vfl-in-selection, aber persistiert NICHT die Entity-Handles,
;; sondern die abgeleitete 9-Feld-Segmentliste (Journal-Art "SEGS"). Damit ist
;; der Modus-3-Block im XDATA vollstaendig beschrieben und der Doppelklick-Neubau
;; unabhaengig davon, ob die urspruenglichen LINE/ARC noch existieren.
;; Rueckgabe: die 9-Feld-Segmentliste oder nil (Abbruch/leer). filter/startpunkt
;; wie bei der Live-Auswahl; ecke-tol wird nur im Live-Zweig fuer die
;; Eck-Vorpruefung gebraucht (Replay-Daten sind bereits geprueft eingefroren).
(defun vfl-in-segmente (filter startpunkt ecke-tol / popped ss k obj-liste kette segmente ecke-bad segs9 filter-erg verworfen)
(setq popped (if *vfl-replay-queue* (vfl-replay-pop) nil))
(if popped
;; --- Replay: fertige Segment-Essenz direkt aus dem Journal ---
(progn
(setq segs9 (car popped))
(if (null segs9)
(if (vfl-headless-p) (vfl-headless-abbruch "SEGS-leer")))
segs9)
;; --- Live: Objekte waehlen, sortieren, analysieren, dann journalisieren ---
(progn
(if (vfl-headless-p) (vfl-headless-abbruch "SEGS"))
(setq ss (ssget filter))
(if (null ss)
(progn (vfl-journal-record "NIL" nil) nil)
(progn
;; Auswahlsatz -> VLA-Objektliste (wie im frueheren vfl-in-selection).
(setq obj-liste '() k 0)
(while (< k (sslength ss))
(setq obj-liste (cons (vlax-ename->vla-object (ssname ss k)) obj-liste))
(setq k (1+ k)))
(setq obj-liste (reverse obj-liste))
;; Geister-/Mini-Objekte VOR der Sortierung ausfiltern: ein von einer
;; Rechteck-Auswahl miterfasstes winziges Segment (unter
;; *vfk-pfad-min-segmentlaenge*) wuerde sonst per Nearest-Neighbor
;; stumm in die Kette gehaengt und ein Geister-Bauteil erzeugen (realer
;; Bug: 14-mm-Bogen am Kettenende). Verworfene mit Grund melden.
(setq filter-erg (vfl-filter-mini-objekte obj-liste))
(setq obj-liste (car filter-erg) verworfen (cdr filter-erg))
(if (> verworfen 0)
(vfl-meldung (ssg-textf "vfl-m3-mini-objekte-verworfen"
(list (itoa verworfen) (rtos *vfk-pfad-min-segmentlaenge* 2 0)))))
(setq kette (if obj-liste (gf-sortiere-objekte obj-liste startpunkt) nil))
(if (null kette)
(progn (princ (ssg-text "vfl-m3-fehler-nicht-sortierbar"))
(vfl-journal-record "NIL" nil) nil)
(progn
(setq segmente (gf-analysiere-kette kette))
(setq ecke-bad (vfl2-pruefe-eckwinkel kette ecke-tol))
(if ecke-bad
(progn
(vfl-meldung (ssg-textf "vfl-vwnb-alert-eckwinkel" (list (rtos ecke-bad 2 1))))
(vfl-journal-record "NIL" nil) nil)
(progn
(setq segs9 (vfl-kette->segmente9 kette segmente))
(vfl-journal-record "SEGS" segs9)
segs9)))))))))
(defun vf-linienzug-modus3 ( / ss obj-liste startpunkt start-hoehe as-seite
es-seite kette segmente n i seg seg-ent typ hz laenge
es-seite kette segmente segmente9 n i seg seg-ent typ hz laenge
bwinkel bseite frame antwort winkel deltaL
anzahl-gf anzahl-vf vfl-nummer lastEnt hoehe-bis
ecke-bad first-line last-line letzt-winkel
aus ein soll-ende ist-ende run-typ richtn best-w
kvariante L_VF ent-fp rad3 just-open
dbg-an old-error)
dbg-an old-error dbg-old-error vfl-ins)
(princ "\n\n=========================================")
(princ (ssg-text "vfl-vwnb-titel"))
(princ "\n=========================================")
@@ -8720,34 +8985,40 @@
;; automatisch ueber vfl-journal-record mitgeloggt (siehe dort) - hier nur
;; noch Session-Start/-Ende + Zwischenergebnisse, die NICHT direkt aus einer
;; Nutzereingabe stammen (Analyse-Ergebnis, Segment-Header, Bau-Ergebnis).
;; *vfl-journal*/*vflw-pending* werden dabei nebenbei mitbefuellt, aber von
;; Modus 3 nirgends gelesen/persistiert (kein Replay/XDATA wie bei Modus 1/2
;; - das ist hier bewusst NICHT mit umgebaut, nur das Logging).
;; Der *error*-Handler schliesst die Datei bei Abbruch sauber.
;; *vfl-journal* wird - wie bei Modus 1/2 - als XDATA (Marker "linienzug3")
;; persistiert und ist damit per Doppelklick editierbar (vfl-edit-ent3, voller
;; Reset-Replay). Statt der Objekt-Handles traegt es die Segment-ESSENZ (SEGS,
;; siehe vfl-in-segmente): der Block ist selbst-enthalten und der Neubau
;; unabhaengig von den urspruenglichen LINE/ARC.
;; FRUEHER *error*-Handler: schliesst nur die dbg-Datei sauber, falls waehrend
;; der Pfad-/Praeambel-Phase (noch keine Geometrie) abgebrochen wird. Der
;; eigentliche Abbruch-WICKLER (Teil-Geometrie zu einem VF_n-Block sichern)
;; wird erst nach dem Setup scharf geschaltet (siehe unten), sobald
;; vfl-nummer/lastEnt feststehen - analog Modus 2.
(setq dbg-an (dbg-schalter-open "vfl-modus3" "vfl_modus3.dbg" "DXFM_LOG"))
(if dbg-an
(progn
(dbgf "vf-linienzug-modus3")
(dbgmsg "=== SESSION Modus 3 (Vorwaerts-Nachbau) START ===")
(dbgflush)))
(setq old-error *error*)
(setq dbg-old-error *error*)
(setq *error*
(function (lambda (msg)
(setq *error* old-error)
(setq *error* dbg-old-error)
(if dbg-an
(progn
(dbgmsg (strcat "=== ABBRUCH: " (if msg msg "(exit)") " ==="))
(dbgreturn nil)
(dbgclose)))
(if old-error (old-error msg) (princ)))))
(if dbg-old-error (dbg-old-error msg) (princ)))))
(vfl-gf-abhaengigkeit-sicherstellen "vfl-m3-alert-gf-modul")
;; --- 1. Pfad-Objekte waehlen (LINE/ARC) ---
(princ (ssg-text "vfl-m3-pfad-waehlen"))
(setq ss (vfl-in-selection '((0 . "LINE,ARC"))))
(if (null ss) (progn (princ (ssg-text "vfl-m3-keine-objekte")) (exit)))
(setq obj-liste (mapcar 'vlax-ename->vla-object ss))
;; --- 1./3. Pfad-Objekte + Segment-Essenz: siehe Schritt 3 unten. ---
;; Auswahl UND Ableitung laufen zusammen in vfl-in-segmente, weil die
;; Sortierung den Startpunkt braucht (Schritt 2) - die Objektauswahl steht
;; daher NACH der Startpunkt-/AS-Praeambel, nicht mehr davor. Journalisiert
;; wird nur noch die abgeleitete Segment-Essenz (SEGS), nicht die Handles.
;; --- 2. Startpunkt (hoehere Seite) + Hoehe + AS-Seite ---
;; Wizard: Startpunkt+Hoehe als EIN Gruppen-Dialog (analog Modus 2). Die
@@ -8784,24 +9055,25 @@
(setq as-seite (if (= (vfl-in-string (ssg-text "prompt-wahl-1-2")) "2") "rechts" "links"))
(vf-set-as-masse *vfl-as-winkel* as-seite) ; Masse fuer Variante
;; --- 3. Sortieren + Analysieren ---
(setq kette (gf-sortiere-objekte obj-liste startpunkt))
(if (null kette)
(progn (princ (ssg-text "vfl-m3-fehler-nicht-sortierbar")) (exit)))
(setq segmente (gf-analysiere-kette kette))
;; --- 3. Pfad waehlen + Sortieren + Analysieren + Eck-Vorpruefung ---
;; vfl-in-segmente kapselt Live (ssget -> gf-sortiere-objekte ->
;; gf-analysiere-kette -> Eck-Vorpruefung -> 9-Feld-Essenz) UND Replay
;; (fertige Essenz aus dem SEGS-Journal). Persistiert die Segment-Essenz
;; statt der Objekt-Handles - damit ist der Block selbst-enthalten und der
;; Doppelklick-Neubau (vfl-edit-ent3) braucht die Original-LINE/ARC nicht.
(princ (ssg-text "vfl-m3-pfad-waehlen"))
(setq segmente9 (vfl-in-segmente '((0 . "LINE,ARC")) startpunkt *vfk-eckwinkel-toleranz*))
(if (null segmente9) (progn (princ (ssg-text "vfl-m3-keine-objekte")) (exit)))
;; segmente = 5-Feld-Bausicht (typ hz laenge bwinkel bseite), abgeleitet aus
;; der 9-Feld-Essenz - der bestehende Bau unten arbeitet unveraendert damit.
(setq segmente (mapcar (function (lambda (s) (list (nth 0 s) (nth 1 s) (nth 2 s) (nth 3 s) (nth 4 s))))
segmente9))
(if dbg-an
(progn
(dbgmsg (strcat "ANALYSE: " (itoa (length segmente)) " Segment(e) erkannt"))
(dbg 'segmente)
(dbgflush)))
;; --- 4. Eck-Winkel-Vorpruefung (vor dem Bau) ---
(setq ecke-bad (vfl2-pruefe-eckwinkel kette *vfk-eckwinkel-toleranz*))
(if ecke-bad
(progn
(vfl-meldung (ssg-textf "vfl-vwnb-alert-eckwinkel" (list (rtos ecke-bad 2 1))))
(exit)))
;; --- 5. Setup ---
(setq aus (if aus-dx aus-dx *vfk-as-es-fallback-dx*) ein (if ein-dx ein-dx *vfk-as-es-fallback-dx*))
(setq rad3 (* (float (ssg-cfg-or "vario" "gefaelle_winkel" 3)) (/ pi 180.0)))
@@ -8811,7 +9083,19 @@
(setq vfl-nummer (vf-next-number))
(setq lastEnt (vf-lastent-ohne-attribute))
(vfl-acc-reset)
(setq anzahl-gf 0 anzahl-vf 0 frame nil n (length segmente))
(setq anzahl-gf 0 anzahl-vf 0 frame nil)
;; --- Abbruch-Sicherung ab hier scharf (analog Modus 1/2) ---
;; Ab Schritt 6 kann jederzeit Geometrie entstehen. Der Wickler sichert die
;; bis zum Abbruch gebaute Teil-Geometrie als VF_n-Block mit dem vollen
;; Journal (Marker "linienzug3") - sie bleibt stehen und ist per Doppelklick
;; fortsetz-/editierbar. Loest den fruehen dbg-Hook ab (Ruecksprungziel bleibt
;; dbg-old-error) und uebernimmt das dbgclose. *ssg-ils-dim* zuruecksetzen:
;; Begruendung siehe gleichlautende Stelle in vf-linienzug-modus.
(setq old-error dbg-old-error)
;; Scharfen Abbruch-Wickel-Handler installieren (gemeinsam, siehe
;; vfl-abbruch-handler-installieren).
(vfl-abbruch-handler-installieren "linienzug3")
(setq n (length segmente))
;; erste/letzte "Linie" fuer die Trimmung bestimmen
(setq first-line -1 last-line -1 i 0)
(while (< i n)
@@ -8830,13 +9114,19 @@
(setq i 0)
(while (< i n)
(setq seg (nth i segmente) typ (car seg) hz (cadr seg))
(setq seg-ent (car (nth i kette))) ; echte LINE/ARC-Entity dieses Segments
;; seg-ent: KEINE Live-Entity mehr - Modus 3 baut jetzt aus der Segment-
;; Essenz (SEGS), nicht aus durchgereichten LINE/ARC. Damit entfaellt das
;; kosmetische Pfad-Segment-Highlight (vfl-seg-highlight ist nil-sicher).
;; Der Wegfall ist bewusst: die Essenz macht den Bau/Replay unabhaengig von
;; den Original-Objekten, ein Highlight haette beim Doppelklick-Neubau ohnehin
;; keine Entity mehr zum Hervorheben.
(setq seg-ent nil)
;; Wizard: alle Fragen dieses Segments in EINEM Dialog erfassen (die
;; unveraenderten vfl-in-*-Aufrufe unten konsumieren die Antworten aus
;; *vflw-pending*). Segment vorher in der Zeichnung hervorheben. Bei einem
;; Bogen ist Vario-Kurve nur im offenen VF-Lauf erlaubt - deshalb die
;; Variante-Antwort nur mitschicken, wenn run-typ="VF" (sonst konsumiert
;; der Aufrufer sie nicht und sie wuerde ins naechste Segment lecken).
;; *vflw-pending*). Bei einem Bogen ist Vario-Kurve nur im offenen VF-Lauf
;; erlaubt - deshalb die Variante-Antwort nur mitschicken, wenn run-typ="VF"
;; (sonst konsumiert der Aufrufer sie nicht und sie wuerde ins naechste
;; Segment lecken).
(if (vfl-wizard-aktiv)
(progn
(vfl-seg-highlight seg-ent T)
@@ -8949,12 +9239,21 @@
(setq frame (vfl-insert-es-element "GF" frame (car (frame->hz-winkel frame))
(if letzt-winkel letzt-winkel *vfk-gefaelle-winkel*) (caddr (car frame)) es-seite)))
;; --- 8. Block + Ist-Ziel-Report ---
;; --- 8. Block + Journal-XDATA + Ist-Ziel-Report ---
(setq hoehe-bis (caddr (car frame)))
(vfl-block-erstellen vfl-nummer anzahl-gf anzahl-vf (caddr startpunkt) hoehe-bis
(vfl-planar-dist startpunkt (car frame)) as-seite es-seite startpunkt lastEnt)
(setq vfl-ins
(vfl-block-erstellen vfl-nummer anzahl-gf anzahl-vf (caddr startpunkt) hoehe-bis
(vfl-planar-dist startpunkt (car frame)) as-seite es-seite startpunkt lastEnt))
;; Eingabe-Journal am fertigen Block persistieren (Marker "linienzug3") ->
;; spaeter per Doppelklick editierbar (vfl-edit-ent3: voller Reset-Replay wie
;; Modus 2, aus der Segment-Essenz statt aus Objekt-Handles).
(if vfl-ins (vfl-journal-xdata-schreiben vfl-ins "linienzug3"))
(setq soll-ende (caddr (last kette))) ; Endpunkt des letzten gezeichneten Segments
;; Endpunkt des letzten gezeichneten Segments - aus der Segment-Essenz
;; (epx/epy des letzten Segments), nicht mehr aus den Original-Entities.
;; Damit stimmt der Soll-Ende-Report auch beim Doppelklick-Neubau, wenn die
;; urspruenglichen LINE/ARC laengst weg sind.
(setq soll-ende (list (nth 7 (last segmente9)) (nth 8 (last segmente9))))
(setq ist-ende (car frame)) ; ES-KS_AUS
(princ "\n\n=========================================")
(princ (ssg-text "vfl-vwnb-eingefuegt"))
@@ -9199,7 +9498,7 @@
100.0))))
(defun vf-linienzug-modus2 ( / ss k obj-liste startpunkt start-hoehe as-seite
endpunkt end-hoehe es-seite kette segmente n i seg
endpunkt end-hoehe es-seite kette segmente segmente9 n i seg
seg-ent
typ hz laenge bwinkel bseite antwort winkel klass
plan e ecke-bad vf-start vf-ende dz z-front
@@ -9264,11 +9563,14 @@
(vfl-gf-abhaengigkeit-sicherstellen "vfl-m3-alert-gf-modul")
;; --- 1. Pfad-Objekte waehlen ---
(princ (ssg-text "vfl-m3-pfad-waehlen"))
(setq ss (vfl-in-selection '((0 . "LINE,ARC"))))
(if (null ss) (progn (princ (ssg-text "vfl-m3-keine-objekte")) (exit)))
(setq obj-liste (mapcar 'vlax-ename->vla-object ss))
;; --- 1./4. Pfad-Objekte + Segment-Essenz: siehe Schritt 4 unten. ---
;; Auswahl UND Ableitung laufen zusammen in vfl-in-segmente (Live: ssget ->
;; gf-sortiere-objekte -> gf-analysiere-kette -> Eck-Vorpruefung -> SEGS;
;; Replay: fertige Essenz aus dem Journal). Persistiert die Segment-Essenz
;; statt der Objekt-Handles - der Block ist damit selbst-enthalten und der
;; Doppelklick-Neubau unabhaengig von den Original-LINE/ARC (behebt den
;; frueheren OBJS-Replay-Drift). Da vfl-in-segmente den startpunkt fuer die
;; Sortierung braucht, steht die Auswahl NACH der Startpunkt-Praeambel.
;; --- 2. Startpunkt + Z + AS-Seite ---
(if (vfl-wizard-aktiv)
@@ -9292,22 +9594,7 @@
;; Globals pro Lauf zuruecksetzen (siehe Kommentar in vf-linienzug-modus).
(setq *vfl-as-winkel* nil *vfl-es-winkel* nil)
(if as-vorhanden
(progn
(princ (ssg-text "vf-winkel-aus-header"))
(princ (ssg-text "vf-winkel-90"))
(princ (ssg-text "vf-winkel-30"))
(setq *vfl-as-winkel*
(vfl-in-value "STR"
(function (lambda () (vf-frage-element-winkel "vf-winkel-aus-header"))))) ; 30/90 vor Seite
(princ (ssg-text "gf-seite-aus-header"))
(princ (ssg-text "gf-seite-links"))
(princ (ssg-text "gf-seite-rechts"))
(setq antwort (vfl-menu (ssg-text "prompt-wahl-1-2")
(list (ssg-text "gf-seite-links") (ssg-text "gf-seite-rechts")) 1
"gf-seite-aus-header"))
(setq as-seite (if (= antwort "2") "rechts" "links"))
(vf-set-as-masse *vfl-as-winkel* as-seite) ; Masse fuer Variante
)
(setq as-seite (vfl-frage-as-winkel-seite "gf-seite-aus-header"))
)
;; --- 3. Endpunkt + Z + ES-Seite (NEU in Modus 3) ---
@@ -9330,34 +9617,19 @@
"vfl-es-setzen-frage"))
(setq es-vorhanden (/= antwort "2"))
(if es-vorhanden
(progn
(princ (ssg-text "vf-winkel-ein-header"))
(princ (ssg-text "vf-winkel-90"))
(princ (ssg-text "vf-winkel-30"))
(setq *vfl-es-winkel*
(vfl-in-value "STR"
(function (lambda () (vf-frage-element-winkel "vf-winkel-ein-header"))))) ; 30/90 vor Seite
(princ (ssg-text "gf-seite-ein-header"))
(princ (ssg-text "gf-seite-links"))
(princ (ssg-text "gf-seite-rechts"))
(setq antwort (vfl-menu (ssg-text "prompt-wahl-1-2")
(list (ssg-text "gf-seite-links") (ssg-text "gf-seite-rechts")) 1
"gf-seite-ein-header"))
(setq es-seite (if (= antwort "2") "rechts" "links"))
(vf-set-es-masse *vfl-es-winkel* es-seite) ; Masse fuer Variante
)
(setq es-seite (vfl-frage-es-winkel-seite "gf-seite-ein-header"))
)
;; --- 4. Sortieren + Analysieren + Eck-Vorpruefung ---
(setq kette (gf-sortiere-objekte obj-liste startpunkt))
(if (null kette)
(progn (princ (ssg-text "vfl-m3-fehler-nicht-sortierbar")) (exit)))
(setq segmente (gf-analysiere-kette kette))
(setq ecke-bad (vfl2-pruefe-eckwinkel kette *vfk-eckwinkel-toleranz*))
(if ecke-bad
(progn
(vfl-meldung (ssg-textf "vfl-m3-alert-eckwinkel" (list (rtos ecke-bad 2 1))))
(exit)))
;; --- 4. Pfad waehlen + Sortieren + Analysieren + Eck-Vorpruefung ---
;; vfl-in-segmente kapselt Live + Replay und persistiert die Segment-Essenz
;; (SEGS) statt der Objekt-Handles - siehe Schritt 1 oben und Modus 3.
(princ (ssg-text "vfl-m3-pfad-waehlen"))
(setq segmente9 (vfl-in-segmente '((0 . "LINE,ARC")) startpunkt *vfk-eckwinkel-toleranz*))
(if (null segmente9) (progn (princ (ssg-text "vfl-m3-keine-objekte")) (exit)))
;; segmente = 5-Feld-Bausicht (typ hz laenge bwinkel bseite) aus der 9-Feld-
;; Essenz - die Klassifizierungs-Schleife unten arbeitet unveraendert damit.
(setq segmente (mapcar (function (lambda (s) (list (nth 0 s) (nth 1 s) (nth 2 s) (nth 3 s) (nth 4 s))))
segmente9))
;; --- 5. Klassifizierung (Phase A: nur speichern, nichts bauen) ---
;; Plan-Eintrag Gerade: ("Linie" hz laenge klass winkel) klass=GF/VF
@@ -9365,13 +9637,18 @@
(setq n (length segmente) i 0 plan '())
(while (< i n)
(setq seg (nth i segmente) typ (car seg) hz (cadr seg) laenge (caddr seg))
(setq seg-ent (car (nth i kette))) ; echte LINE/ARC-Entity dieses Segments
;; seg-ent: KEINE Live-Entity mehr - Modus 2 baut jetzt aus der Segment-
;; Essenz (SEGS), nicht aus durchgereichten LINE/ARC (siehe Modus 3). Damit
;; entfaellt das kosmetische Pfad-Segment-Highlight (vfl-seg-highlight ist
;; nil-sicher). Bewusst: die Essenz macht Bau/Replay unabhaengig von den
;; Original-Objekten, ein Highlight haette beim Doppelklick-Neubau ohnehin
;; keine Entity mehr.
(setq seg-ent nil)
;; Wizard: alle Fragen dieses Segments in EINEM Dialog erfassen (die
;; unveraenderten vfl-menu/vfl-in-*-Aufrufe unten konsumieren die Antworten
;; aus *vflw-pending*). Segment vorher in der Zeichnung hervorheben. Der
;; nach-kurve-Fall stellt keine Frage (auto-VF) -> kein Dialog. Ebenso
;; nach-vf bei einem Bogen: Vario-Kurve ist dann erzwungen (eine VF-
;; Einheit kann nicht in einen GF-Bogen auslaufen), auch kein Dialog.
;; aus *vflw-pending*). Der nach-kurve-Fall stellt keine Frage (auto-VF) ->
;; kein Dialog. Ebenso nach-vf bei einem Bogen: Vario-Kurve ist dann
;; erzwungen (eine VF-Einheit kann nicht in einen GF-Bogen auslaufen).
(if (and (vfl-wizard-aktiv)
(not (and (= typ "Linie") nach-kurve))
(not (and (= typ "Bogen") nach-vf)))
@@ -9470,9 +9747,14 @@
(if (/= vf-count 1)
(progn
;; Konsolen-Diagnose (fuer den Log / dbg-Nachvollzug) ...
(princ (ssg-textf "vfl-m3-vf-laeufe" (list (itoa vf-count))))
(princ (ssg-text "vfl-m3-vf-lauf-noetig"))
(princ (ssg-text "vfl-m3-vf-lauf-m3b"))
;; ... UND als sichtbare Meldung, damit die Abweisung nicht in der
;; Konsole untergeht (vfl-meldung zeigt bei erlaubter GUI ein alert,
;; headless nur princ - blockiert also einen Batch-Lauf nicht).
(vfl-meldung (ssg-textf "vfl-m3-vf-lauf-alert" (list (itoa vf-count))))
(exit)))
;; Traeger-Stueck = laengste VF-Gerade im Lauf (traegt die ganze Hoehe).
(setq carrier-idx -1 i vf-start)
@@ -9626,22 +9908,10 @@
;; dbg-Hook selbst) und uebernehmen das dbgclose gleich mit, sonst wuerde
;; bei einem Abbruch ab hier doppelt geschlossen.
(setq old-error dbg-old-error)
;; *ssg-ils-dim* hier zuruecksetzen - Begruendung siehe gleichlautende Stelle
;; im Handler von vf-linienzug-modus (Abbruch kehrt nie zum Aufrufer zurueck,
;; der den Override abraeumen wuerde).
(setq *error*
(function (lambda (msg)
(setq *error* old-error)
(setq *ssg-ils-dim* nil)
(vfl-modus-abbruch-sichern lastEnt vfl-nummer anzahl-gf anzahl-vf
startpunkt frame as-seite es-seite "linienzug2")
(if dbg-an
(progn
(dbgmsg (strcat "=== ABBRUCH: " (if msg msg "(exit)") " ==="))
(dbgreturn nil)
(dbgclose)))
(if (and (boundp '*ssg-start-stack*) *ssg-start-stack*) (ssg-end))
(princ))))
;; Scharfen Abbruch-Wickel-Handler installieren (loest den fruehen dbg-Hook ab,
;; Ruecksprungziel bleibt dbg-old-error via old-error). Gemeinsam fuer alle
;; Modi - siehe vfl-abbruch-handler-installieren.
(vfl-abbruch-handler-installieren "linienzug2")
;; erste/letzte Gerade fuer AS/ES-Trimmung bestimmen
(setq first-line -1 last-line -1 i 0)
(while (< i n)
+13
View File
@@ -37,6 +37,17 @@
# in den Summierungszaehlern "Anzahl aller BTBeladung"/"Anzahl aller
# Abwurfstationen" der ILS-Automation-Zeile).
#
# pattern_eingang/pattern_ausgang: Eingangs-/Ausgangssymbole des Material-
# flusses (siehe ils-insert-station in SSG_LIB_Commands.lsp, Bloecke
# Eingang_<DIM>/Ausgang_<DIM>). NUR fuer den CSV-Export: export_csv.py fuehrt
# sie als eigene TeileArt-Zeilen ("Eingang"/"Ausgang", Merkmale wie BTMT) UND
# als frei platzierte Umlauf-Punkte auf einem Kreisel (KREISEL_UMLAUF_TEILEARTEN
# in lib/export_neighbors.py), damit sie - wie Separator/AS/ES - in die
# "TrackIds"-Spalte gelangen. export_sivas.py schliesst sie bewusst aus (kein
# Sivas-Eintrag). Voraussetzung fuer eine stabile TeileId ist ein ID-ATTDEF in
# den DWGs (ssg-id-check-all fuellt es vor jedem Export, siehe
# ssg-id-collect-blocks in ssg_id.lsp).
#
# pattern_geruest: Geruest-Baugruppen (GeruestInsert.lsp, GR_<id>_<timestamp>).
# Eigenstaendig wie BTMT, kein ANZAHL_*-Attribut an einem Carrier - export_csv.py
# UND export_sivas.py fuehren sie als eigene TeileArt-Zeilen.
@@ -54,6 +65,8 @@ pattern_separator= Separator_SP*, S-LP*
pattern_scanner= Scanner*
pattern_btmt_beladung= BTMT-Beladung*
pattern_btmt_entladung= SC_Entladung*
pattern_eingang= Eingang*
pattern_ausgang= Ausgang*
pattern_geruest= GR_*
pattern_ks_subblocks= KS_EIN, KS_AUS, K1, K2, K3, K4
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+14
View File
@@ -0,0 +1,14 @@
{
"Eingang_2D": {
"ID": ""
},
"Eingang_3D": {
"ID": ""
},
"Ausgang_2D": {
"ID": ""
},
"Ausgang_3D": {
"ID": ""
}
}
+107 -45
View File
@@ -1,6 +1,7 @@
{
"Rohrabhänger": {
"id": "Rohrabhaenger",
"titel": "Rohrabhaenger",
"teileart": "Geruest Rohrabhaenger",
"dummy": "Rohrabhaenger_dummy.dwg",
"attribute": {
@@ -10,11 +11,11 @@
"max": 6000,
"einheit": "mm"
},
"Farbe": {
"Teilefarbe": {
"typ": "auswahl",
"optionen": [
"RAL7035",
"RAL7030"
"RAL 7035 Lichtgrau",
"RAL 7030 Steingrau"
],
"frei_erlaubt": true,
"frei_option": "Individuell"
@@ -133,50 +134,39 @@
"sivas_id": 922021003,
"menge": 1
}
]
},
"1\" Säulen": {
"id": "Saeulen_1Zoll",
"teileart": "Geruest Saeule 1 Zoll",
"dummy": "R2-Saeule_dummy.dwg",
"attribute": {
"Laenge": {
"typ": "number",
"min": 0,
"max": 6000,
"einheit": "mm"
},
"Farbe": {
"typ": "auswahl",
"optionen": [
"RAL7035",
"RAL7030"
],
"frei_erlaubt": true,
"frei_option": "Individuell"
}
},
"geometrie_varianten": [
],
"konfigurationen": [
{
"name": "R 1\"",
"teile": [
{
"artikel": "ROHR R 1\" 33,7 x 2,6",
"sivas_id": 821006026,
"menge": 1
},
{
"artikel": "CAD - BODENSÄULE R 1\" - BODENPLATTE KPL.",
"sivas_id": 821004200,
"menge": 1
}
"name": "Abhaengung Spannpratze / Rohrklammer",
"variante": "ROHR R 1\" 33,7 x 2,6",
"laenge": 2000,
"teilefarbe": "RAL 7035 Lichtgrau",
"oben": [
{ "artikel": "SPANNPRATZE SELBSTSICHERND KPL.", "menge": 1 },
{ "artikel": "Schraubenset", "menge": 1 }
],
"unten": [
{ "artikel": "ROHRKLAMMER 1\" KPL. VERTIKAL", "menge": 2 }
]
},
{
"name": "Abhaengung Duebel / T-Verbindung",
"variante": "ROHR R 1\" 33,7 x 2,6 einseitig gequetscht",
"laenge": 1500,
"teilefarbe": "RAL 7035 Lichtgrau",
"oben": [
{ "artikel": "DURCHSTECKANKER HST M12X115X20", "menge": 2 }
],
"unten": [
{ "artikel": "ROHRKLAMMER 1\" T-VERBINDUNG KPL.", "menge": 1 }
]
}
]
},
"2\" Säulen": {
"id": "Saeulen_2Zoll",
"teileart": "Geruest Saeule 2 Zoll",
"Säulen": {
"id": "Saeulen",
"titel": "Saeulen",
"teileart": "Geruest Saeule",
"dummy": "R2-Saeule_dummy.dwg",
"attribute": {
"Laenge": {
@@ -185,11 +175,11 @@
"max": 6000,
"einheit": "mm"
},
"Farbe": {
"Teilefarbe": {
"typ": "auswahl",
"optionen": [
"RAL7035",
"RAL7030"
"RAL 7035 Lichtgrau",
"RAL 7030 Steingrau"
],
"frei_erlaubt": true,
"frei_option": "Individuell"
@@ -208,8 +198,25 @@
]
},
"geometrie_varianten": [
{
"name": "R 1\"",
"teileart": "Geruest Saeule 1 Zoll",
"teile": [
{
"artikel": "ROHR R 1\" 33,7 x 2,6",
"sivas_id": 821006026,
"menge": 1
},
{
"artikel": "CAD - BODENSÄULE R 1\" - BODENPLATTE KPL.",
"sivas_id": 821004200,
"menge": 1
}
]
},
{
"name": "R 2\"",
"teileart": "Geruest Saeule 2 Zoll",
"teile": [
{
"artikel": "BODENSÄULE R 2\" 60,3 MIT FUSSPLATTE",
@@ -225,6 +232,7 @@
},
{
"name": "R 2\" Haltearm 1-ARMIG",
"teileart": "Geruest Saeule 2 Zoll",
"teile": [
{
"artikel": "BODENSÄULE R 2\" 60,3 MIT FUSSPLATTE",
@@ -245,6 +253,7 @@
},
{
"name": "R 2\" Haltearm 2-ARMIG 90 Grad",
"teileart": "Geruest Saeule 2 Zoll",
"teile": [
{
"artikel": "BODENSÄULE R 2\" 60,3 MIT FUSSPLATTE",
@@ -265,6 +274,7 @@
},
{
"name": "R 2\" Haltearm 2-ARMIG 180 Grad",
"teileart": "Geruest Saeule 2 Zoll",
"teile": [
{
"artikel": "BODENSÄULE R 2\" 60,3 MIT FUSSPLATTE",
@@ -285,6 +295,7 @@
},
{
"name": "R 2\" Haltearm 3-ARMIG",
"teileart": "Geruest Saeule 2 Zoll",
"teile": [
{
"artikel": "BODENSÄULE R 2\" 60,3 MIT FUSSPLATTE",
@@ -340,10 +351,43 @@
"sivas_id": 922083009,
"menge": 2
}
],
"konfigurationen": [
{
"name": "Bodensaeule 1 Zoll, Standardhoehe",
"variante": "R 1\"",
"laenge": 1000,
"teilefarbe": "RAL 7035 Lichtgrau",
"oben": [],
"unten": []
},
{
"name": "Bodensaeule 2 Zoll, Haltearm 1-armig",
"variante": "R 2\" Haltearm 1-ARMIG",
"laenge": 1200,
"teilefarbe": "RAL 7035 Lichtgrau",
"oben": [
{ "artikel": "KLEMMRING MIT MUFFE EINFACH KPL.", "menge": 2 }
],
"unten": []
},
{
"name": "Bodensaeule 2 Zoll, Haltearm 2-armig 90 Grad",
"variante": "R 2\" Haltearm 2-ARMIG 90 Grad",
"laenge": 1200,
"teilefarbe": "RAL 7035 Lichtgrau",
"oben": [
{ "artikel": "KLEMMRING MIT MUFFE DOPPELT 90 Grad, KPL.", "menge": 2 }
],
"unten": [
{ "artikel": "QUERSCHELLE F. 1\" UND 2\" ROHR GESCHW./VZ. M. 4X M10X30 KPL.", "menge": 2 }
]
}
]
},
"Wandbefestigung": {
"id": "Wandbefestigung",
"titel": "Wandbefestigung",
"teileart": "Geruest Wandbefestigung",
"dummy": "Rohrabhaenger_dummy.dwg",
"geometrie_varianten": [
@@ -414,6 +458,24 @@
}
]
}
],
"konfigurationen": [
{
"name": "Wandflansch mit Duebeln",
"variante": "WANDFLANSCH EINZELTEIL",
"oben": [
{ "artikel": "Nylondübel 12x60 inkl. Holzschrauben", "menge": 1 }
],
"unten": []
},
{
"name": "Winkel 300 mit Schwerlastanker",
"variante": "WINKEL 60X60X6X300",
"oben": [
{ "artikel": "HST M12X115X20", "menge": 1 }
],
"unten": []
}
]
}
}
-61
View File
@@ -1,61 +0,0 @@
// ============================================================
// geruest.dcl - Konfiguration einer Geruest-Baugruppe
// Ein Dialog fuer ALLE Baugruppen (Rohrabhaenger, Saeulen, Wandbefestigung).
// Kopfzeile, Varianten-Dropdown, Farbliste und Anbauteil-Liste werden zur
// Laufzeit aus data/json/geruest.json befuellt - siehe
// Lisp/GeruestInsert.lsp, Abschnitt "DIALOG".
// Baugruppen ohne "attribute"-Block (z.B. Wandbefestigung) bekommen die
// Laengen- und Farbfelder per mode_tile gesperrt.
// ============================================================
geruest : dialog {
label = "Geruest-Baugruppe konfigurieren";
: text {
key = "kopf";
label = "";
width = 64;
}
: boxed_column {
label = "Basis";
: popup_list {
key = "variante";
label = "Variante:";
width = 56;
}
: row {
: edit_box {
key = "laenge";
label = "Laenge (mm):";
edit_width = 10;
}
: popup_list {
key = "farbe";
label = "Farbe:";
width = 18;
}
: edit_box {
key = "farbe_frei";
label = "eigene:";
edit_width = 12;
}
}
}
: boxed_column {
label = "Anbauteile (Mehrfachauswahl mit Strg)";
: list_box {
key = "anbau";
label = "";
multiple_select = true;
width = 64;
height = 12;
}
}
ok_cancel;
}
+122
View File
@@ -0,0 +1,122 @@
# TODO: Modus-2-Solver auf achsenweise Laengen-Bilanz umstellen
Status: **Vorschlag / noch nicht umgesetzt** (2026-09-18). Mit dem Entwickler zu
besprechen, bevor der Solver angefasst wird. Reiner Plan - kein Code geaendert.
## Kurzfassung
Der Modus-2-Solver (`vf-linienzug-modus2`, Phase B, `Lisp/vf_linienzug.lsp`
ca. Zeile 9915-10145) modelliert den VarioFoerderer-Lauf als EINE gerade
Bruecke. Bei einer gefalteten Kette (U-Form: hoch, quer, runter) zieht er die
festen Fussabdruecke aus der falschen Achse ab: **die erste GF wird zu lang,
die Kletterstrecke zu kurz, Motor/ES landen falsch bzw. fehlen.** Der Fix:
Laengen pro Schenkelrichtung bilanzieren statt als eine skalare Spanne.
Das ist ein **dokumentiertes v1-Naeherungs-Limit**, kein Regressionsfehler -
die Umbauten SEGS/Alert-Box/Mini-Filter haben Phase B nicht angefasst.
## Symptom (echter Testfall: results/test_modus2.dxf, logs/vfl_modus2.dbg)
U-Form-Kette, 6 Segmente, EIN VF-Lauf (Seg 2-6; die zwei Vario-Kurven zaehlen
als VF-Mitglieder):
```
^ Seg2 VF (1500 mm)
| Seg1 GF (2500 mm)
start Seg4 VF (2257 mm)
| +-----------------------+
| Seg3 Kurve Seg5 Kurve
| |
| Seg6 VF (3800 mm)
+ v
Ziel (3520, 180)
```
Ergebnis: `SOLL-ENDE=(3520, 180, 2000)`, `IST-ENDE=(3333, 2825, 2080)` ->
`dY=+2645 mm`, `dZ=+80 mm`. Die erste GF wird zu lang erzeugt; die
Kletterstrecke (Seg 6) endet ~2645 mm zu frueh; Motorstation, das
VF-Kurven-Zwischenstueck und das ES-Element sitzen falsch bzw. fehlen.
## Ursache, praezise lokalisiert
Der Solver waehlt die laengste VF-Gerade (Seg 6, 3800) als Kletterer, der die
ganze Hoehe traegt, und rechnet die geneigte Laenge skalar:
```lisp
;; vf_linienzug.lsp:9990 - Solver bekommt die SKALARE Spanne
(setq wahl3 (vfl3-waehle-winkel climber-span dH-adj feste-vf))
;; feste-vf = Umlenk + Separator (+ Motor) -- ein fester Fussabdruck
;; vf_linienzug.lsp:10070 - Kletterer bekommt br-lvf proportional
(setq this-lvf (max clamp (* br-lvf (/ (caddr seg) climber-span))))
;; br-lvf = climber-span MINUS feste-vf
;; ABER: Umlenk/GF1/Motor sitzen physisch in ANDEREN Schenkeln
;; (GF1+Umlenk am Eingang entlang hz=90, nicht in Seg6/hz=-90)
```
**Der Modellbruch:** `br-lvf` = `climber-span` minus die festen Fussabdruecke
(Umlenk 500 + Separator 300 + GF1 `gf-total` + Motor). Diese Teile liegen aber
in den ANDEREN U-Schenkeln - nicht in Seg 6. Der Solver zieht sie trotzdem von
Seg 6s 3800er-Spanne ab. Weil GF1 = "alles am Einlauf" gewaehlt war, ist
`gf-total` gross -> Seg 6 wird um genau diese Summe (~2645 mm) zu kurz. Bei
einer GERADEN Bruecke liegen alle Teile auf derselben Achse - dann stimmt die
skalare Rechnung. Bei der U-Form nicht.
Zusaetzlich: die flache Zone nimmt GENAU EIN Uebergangspaar an
(`hor-pairs = 1`, Zeile 9950), obwohl die U-Form zwei 90-Grad-Kurven plus
Kletterstrecke mischt -> daraus die Rest-dZ von +80 mm.
## Beleg: dokumentiertes Limit, kein Regressionsfehler
| Kommentar-Fundstelle | Aussage |
|---|---|
| `vf_linienzug.lsp:9073-9075` | "v1-Naeherung Laenge ... feinere Laengen-Reservierung -> spaeter" |
| `vf_linienzug.lsp:9923-9926` | Modell explizit "EIN Vario mit Horizontal-Mitte" - eine gerade Bruecke ist die Design-Annahme |
| `vf_linienzug.lsp:9948-9950` | "flache Zone hat GENAU EIN Uebergangspaar ... unabhaengig von der Zahl der Fueller/Kurven" |
Verifiziert: SEGS-Umbau, Alert-Box und Mini-Filter haben Phase B (den Solver)
nachweislich nicht veraendert. Die Abweichung ist vorbestehend.
## Vorgeschlagener Umbau in sicheren Etappen
Nach jeder Etappe eine BricsCAD-Sichtpruefung (Geometrie ist nur interaktiv
pruefbar).
1. **Regressionsnetz zuerst.** Eine bekannt-gute GERADE Modus-2-Kette
(GF...VF...GF, eine Achse) bauen und mit `lib/dxf_vf_abbild.py
--schreibe-referenz` als Referenz einfrieren. Schuetzt den funktionierenden
Normalfall, bevor die Bilanz umgebaut wird.
2. **Schenkel erkennen.** Den VF-Lauf (Seg `vf-start`..`vf-ende`) an den
Vario-Kurven in Schenkel aufteilen - jeder Schenkel eine Fahrtrichtung (hz).
Pro Schenkel: Summe der VF-Geraden-Laengen, welche festen Teile darin liegen.
Neue Datenstruktur statt der skalaren `climber-span`. (ersetzt die
climber/nonclimber-Schleife 9935-9947)
3. **Fussabdruecke dem richtigen Schenkel zuordnen** (Risiko-Kern). Umlenk/GF1
nur vom Eingangs-Schenkel abziehen, Motor/GF2/ES nur vom Ausgangs-Schenkel -
nicht pauschal von der Kletterstrecke. Reicht ein Schenkel nicht, verteilt
sich die Restneigung auf weitere Schenkel. (feste-vf/dH-adj 9978-9989,
this-lvf 10070, fill-len 10094-10104)
4. **Uebergangspaare pro Kurve** statt pauschal eins. `hor-pairs` aus der
tatsaechlichen Schenkel-/Kurvenzahl ableiten - behebt die dZ-Restabweichung.
(9950, dH-adj-Bildung 9982-9989)
5. **Verifikation.** Regressionsnetz aus Schritt 1 muss gruen bleiben (gerader
Fall unveraendert). U-Form interaktiv: erste GF korrekt kurz,
Kletterstrecke trifft den Zielpunkt, Motor + Kurven-Zwischenstueck + ES an
der richtigen Stelle. SEGS-Journal bleibt formgleich.
## Risiko-Einschaetzung
- Der GERADE Fall funktioniert heute - er ist der Normalfall, fuer den der
Solver gebaut ist. Ein Umbau der Kern-Bilanz kann ihn brechen -> Schritt 1
(Referenznetz) ist nicht optional.
- Geometrie nur interaktiv in BricsCAD pruefbar -> mehrere Runden noetig.
- Gut eingegrenzt: der Umbau bleibt in Phase B von `vf-linienzug-modus2`
(~9915-10145). Klassifizierung, SEGS-Journal, Doppelklick-Replay und die
anderen Modi bleiben unberuehrt.
## Zwischenloesung (falls der Umbau nicht sofort kommt)
Gefaltete VF-Laeufe (mehr als eine Achsenrichtung im VF-Bereich, z.B. U-Form)
erkennen und mit klarer Meldung abweisen, statt still falsche Geometrie zu
bauen - analog zur bereits vorhandenen "nur EIN VF-Lauf"-Abweisung
(`vfl-m3-vf-lauf-alert`). Macht das Limit ehrlich sichtbar, kleiner Eingriff.
+25 -5
View File
@@ -70,8 +70,8 @@ Bounding-Boxen braucht).
`vla-update` auf, das allein reichte in diesem Fall nicht aus.
7. **`csv:collect-export-blocks`** — sammelt alle relevanten top-level
INSERTs (Kreisel, Omniflo, VF/GF-Wrapper, Separator/Scanner,
BTMT-Stationen) ueber die Blockname-Muster aus `cfg/export.cfg`
`[blockpattern]`.
BTMT-Stationen, Eingang/Ausgang) ueber die Blockname-Muster aus
`cfg/export.cfg` `[blockpattern]`.
8. **`csv:block-to-json`** je Fund — siehe naechster Abschnitt.
### `csv:block-to-json` — Inhalt eines JSON-Eintrags
@@ -277,7 +277,8 @@ Zu beachten:
Eine grosse Schleife über alle rohen JSON-Blöcke ordnet jeden Block anhand
der `cfg/export.cfg [blockpattern]`-Muster einer TeileArt zu (Kreisel,
Eckrad, Omniflo Bogen/Weiche/Gerade, VF-Strecke, Gefaellestrecke,
Strecke-Modul, Separator, Scanner, BTMT Be-/Entladung) und baut daraus ein
Strecke-Modul, Separator, Scanner, BTMT Be-/Entladung, Eingang/Ausgang) und
baut daraus ein
Item-dict mit `teileart`, `teileid`, `bezeichnung`, `planquadrat`,
`merkmale` und den bbox-/KOS-Spalten (`bbox_columns`). VF_n/GF_n-Items
bekommen dabei zusaetzlich `x`/`y` (Kettenanfang, fuer die Schleuselement-
@@ -299,6 +300,24 @@ Default 210x150x14mm) und der bekannten Position/Rotation eine synthetische
`_bbox`, damit der Separator trotzdem an der Nachbarschaftserkennung
teilnimmt.
#### Eingang/Ausgang — nur im CSV-Export
Die Materialfluss-Ein-/Ausgangssymbole (Bloecke `Eingang_<DIM>`/`Ausgang_<DIM>`,
eingefuegt ueber `ils-insert-station`, Befehle `ILS_EINGANG_INSERT`/
`ILS_AUSGANG_INSERT`) werden **nur im CSV-Export** als eigene Zeile gefuehrt
(TeileArt `"Eingang"`/`"Ausgang"`, Merkmale wie BTMT: ARTINR/Hoehe/Drehung).
`export_sivas.py` ueberspringt sie bewusst — ein Fluss-Symbol hat keinen
eigenen Sivas-Artikel.
Wie Separator/AS/ES nehmen sie als **freie Umlauf-Punkte**
(`KREISEL_UMLAUF_TEILEARTEN`) an der `"TrackIds"`-Spalte teil und tauchen so
auch auf einem Kreisel auf, auf dessen Bahn sie liegen. Voraussetzung fuer
eine ueber Exportlaeufe stabile TeileId ist ein `ID`-ATTDEF in den DWGs
(nachgetragen, dokumentiert in `data/ils/allocations.json`); `ssg-id-check-all`
fuellt es vor jedem Export, und `ssg-id-collect-blocks` (`Lisp/ssg_id.lsp`)
erfasst die Bloecke dafuer. Fehlt die ID (noch), greift wie bei BTMT eine
lauf-lokale Fallback-Nummer (`EIN-`/`AUS-`-Praefix).
### 2. `compute_neighbor_ids` — die generische Bounding-Box-Nachbarschaft
Datei `lib/export_neighbors.py`. Testet drei Gruppen gegeneinander (NICHT
@@ -347,8 +366,9 @@ Weichen zu einer widerspruchsfreien Nachbarschaft zusammenfuehrt. Fuer jeden
echten Kreisel werden alle Punkte gesammelt, die dort geometrisch liegen:
- freie Punkte (`KREISEL_UMLAUF_TEILEARTEN` in `lib/export_neighbors.py`:
Separator, BTMT Beladung, BTMT Entladung — alles Stationen direkt auf der
Bahn/dem Track), deren Bounding-Box die Kreisel-Box ueberschneidet,
Separator, BTMT Beladung, BTMT Entladung, Eingang, Ausgang — alles
Stationen/Markierungen direkt auf der Bahn/dem Track), deren Bounding-Box
die Kreisel-Box ueberschneidet,
- die Kreisel-Kreisel-Weichen aus Schritt 3 (in BEIDEN beteiligten
Kreisel-Umlaeufen, da eine Weiche der gemeinsame Verzweigungspunkt ist —
man kann von Kreisel A nach B und zurueck),
+19 -3
View File
@@ -111,6 +111,7 @@
"vfl-glied-vario": "Vario-Kurve (horizontale Kurve im VF)",
"vfl-glied-offen": "(offen)",
"vfl-edit-kein-journal": "Dieser Linienzug-Block traegt kein Eingabe-Journal (Altbestand) und kann nicht schrittweise editiert werden.",
"vfl-edit-m2-obsolet": "Dieser Modus-2-Block wurde mit dem alten Objekt-Referenz-Journal gebaut und kann nicht mehr per Doppelklick neu aufgebaut werden. Bitte die Kette mit Modus 2 neu erzeugen (die neue Fassung speichert die Segment-Daten direkt am Block).",
"vfl-edit-kette-header": "\n\nLinienzug mit %1 Glied(ern):",
"vfl-edit-abgespielt": "\n>>> %1 Glied(er) werden abgespielt, %2 zurueckgenommen - danach interaktiv weiter.",
"vfl-edit2-reset-info": "\n>>> Linienzug (Modus 2) wird mit den gespeicherten Angaben identisch neu aufgebaut...",
@@ -183,6 +184,8 @@
"vfl-m3-vf-laeufe": "\n\n[M3a] Gefundene VF-Laeufe: %1",
"vfl-m3-vf-lauf-noetig": "\n[M3a] Genau EIN VF-Lauf noetig (GF...VF...GF; Vario-Kurven duerfen im Lauf liegen).",
"vfl-m3-vf-lauf-m3b": "\n[M3a] Mehrere/keine VF-Laeufe -> M3b spaeter.",
"vfl-m3-vf-lauf-alert": "VF-Linienzug Modus 2 kann diese Kette nicht bauen.\n\nGefunden: %1 VarioFoerderer-Laeufe.\nModus 2 baut zurzeit nur Ketten mit GENAU EINEM VF-Lauf (GF...VF...GF; Vario-Kurven duerfen innerhalb des Laufs liegen).\n\nBitte den Pfad so aufteilen, dass nur ein zusammenhaengender VF-Bereich entsteht.",
"vfl-m3-mini-objekte-verworfen": "%1 zu kurze(s) Objekt(e) wurden aus der Auswahl ausgeschlossen (unter %2 mm 2D-Laenge).\n\nSolche Mini-Segmente sind meist Geister-/Artefakt-Elemente, die eine Rechteck-Auswahl versehentlich miterfasst - ein reales Bauteil (kleinstes: Separator 300 mm) ist nie so kurz. Sie werden ignoriert, damit sie keine Geister-Bausteine in die Kette bringen.",
"vfl-m3-vf-lauf-ohne-gerade": "\n[M3a] VF-Lauf ohne VF-Gerade - Abbruch.",
"vfl-m3-anker-vorschau-header": "\n M3a ANKER-VORSCHAU (grob geschaetzt; exakte Werte beim Bau)",
"vfl-m3-start-hoehe": "\n Start-Hoehe Z: %1 mm",
@@ -350,7 +353,6 @@
"vfe-gefaellebogen-links-mass": "\n Gefaellebogen links: dx=%1 dz=%2 mm",
"vfe-gefaellebogen-rechts-mass": "\n Gefaellebogen rechts: dx=%1 dz=%2 mm",
"vfe-gefaellebogen-seite-dx": "\n Gefaellebogen %1: dx=%2 mm",
"vfe-hz-incl-status": "\n HzIncl %1 L=%2 hz=%3 v=%4 -> Z=%5",
"vfe-hz-rot-status": "\n HzRot %1 hz=%2 v=%3 -> Z=%4",
"vfe-hz-rotks-status": "\n HzRotKS %1 hz=%2 -> Z=%3",
"vfe-hz-scaleks-status": "\n HzScaleKS %1 %2mm hz=%3 -> Z=%4",
@@ -968,7 +970,7 @@
"vfl-vwnb-typ-gerade": "\n Typ? 1 = GF (<=3 Grad) 2 = VF-Ab 3 = VF-Auf 4 = VF-Horizontal",
"vfl-vwnb-prompt-vario-winkel": "\n Vario-Winkel (3..51 Grad) [3]: ",
"vfl-vwnb-kurve-nur-vf": "\n >>> Vario-Kurve nur innerhalb eines VF-Laufs zulaessig - uebersprungen.",
"vfl-vwnb-eingefuegt": "\n>>> VF-Linienzug (Modus 2, GF+VF) eingefuegt! <<<",
"vfl-vwnb-eingefuegt": "\n>>> VF-Linienzug (Modus 3, GF+VF) eingefuegt! <<<",
"vfl-vwnb-soll-ende": "\n Soll-Ende (Pfad): X=%1 Y=%2",
"vfl-vwnb-ist-ende": "\n Ist-Ende (ES): X=%1 Y=%2 Z=%3",
"vfl-vwnb-abweichung-xy": "\n Abweichung XY: dX=%1 dY=%2 mm",
@@ -988,6 +990,20 @@
"gr-alert-laenge": "Laenge ungueltig: '%1' mm. Erlaubt: %2 bis %3 mm.",
"gr-prompt-einfuegepunkt": "\nEinfuegepunkt waehlen: ",
"gr-abgebrochen": "\nAbgebrochen.",
"gr-fertig": "\nGeruest-Baugruppe '%1' eingefuegt (Block %2)."
"gr-fertig": "\nGeruest-Baugruppe '%1' eingefuegt (Block %2).",
"gr-alert-menge": "Ungueltige Stueckzahl. Erlaubt ist eine ganze Zahl ab 1; ein leeres Feld uebernimmt den Vorgabewert.",
"gr-position-oben": "oben",
"gr-position-unten": "unten",
"gr-konfig-keine": "<keine>",
"gr-konfig-name": "\nName der Konfiguration: ",
"gr-konfig-gespeichert": "\nKonfiguration '%1' gespeichert.",
"gr-konfig-geloescht": "\nKonfiguration '%1' geloescht.",
"gr-konfig-loeschen-frage": "\nKonfiguration '%1' wirklich loeschen? [Ja/Nein] <Nein>: ",
"gr-konfig-nichts-gewaehlt": "\nKeine Konfiguration gewaehlt.",
"gr-konfig-bibliothek-fest": "\nKonfigurationen der Bibliothek koennen nicht geloescht werden - nur eigene (mit * markiert).",
"gr-konfig-teil-unbekannt": "\nGeruest: Anbauteil '%1' aus Konfiguration '%2' ist bei dieser Baugruppe unbekannt - uebersprungen.",
"gr-konfig-variante-unbekannt": "\nGeruest: Variante '%1' aus Konfiguration '%2' ist bei dieser Baugruppe unbekannt - Auswahl unveraendert.",
"gr-fehler-schreiben": "\nGeruest: Datei konnte nicht geschrieben werden: %1",
"gr-fehler-keine-baugruppen": "\nGeruest: Die Datendatei enthaelt keine Baugruppen."
}
}
+19 -3
View File
@@ -111,6 +111,7 @@
"vfl-glied-vario": "Vario curve (horizontal curve in VF)",
"vfl-glied-offen": "(open)",
"vfl-edit-kein-journal": "This chain block carries no input journal (legacy) and cannot be edited step by step.",
"vfl-edit-m2-obsolet": "This mode-2 block was built with the old object-reference journal and can no longer be rebuilt by double-click. Please recreate the chain with mode 2 (the new version stores the segment data directly on the block).",
"vfl-edit-kette-header": "\n\nChain with %1 segment(s):",
"vfl-edit-abgespielt": "\n>>> %1 segment(s) replayed, %2 undone - then continue interactively.",
"vfl-edit2-reset-info": "\n>>> Rebuilding this Modus-2 chain identically from the saved inputs...",
@@ -183,6 +184,8 @@
"vfl-m3-vf-laeufe": "\n\n[M3a] VF runs found: %1",
"vfl-m3-vf-lauf-noetig": "\n[M3a] Exactly ONE VF run required (GF...VF...GF; Vario curves may lie within the run).",
"vfl-m3-vf-lauf-m3b": "\n[M3a] Several/no VF runs -> M3b later.",
"vfl-m3-vf-lauf-alert": "VF line path mode 2 cannot build this chain.\n\nFound: %1 VarioFoerderer runs.\nMode 2 currently builds only chains with EXACTLY ONE VF run (GF...VF...GF; Vario curves may lie within the run).\n\nPlease split the path so that only one contiguous VF section remains.",
"vfl-m3-mini-objekte-verworfen": "%1 object(s) too short were excluded from the selection (below %2 mm 2D length).\n\nSuch mini segments are usually ghost/artefact elements a window selection caught by accident - a real part (smallest: separator 300 mm) is never that short. They are ignored so they cannot introduce ghost parts into the chain.",
"vfl-m3-vf-lauf-ohne-gerade": "\n[M3a] VF run without a VF straight - aborting.",
"vfl-m3-anker-vorschau-header": "\n M3a ANCHOR PREVIEW (rough estimate; exact values during build)",
"vfl-m3-start-hoehe": "\n Start height Z: %1 mm",
@@ -350,7 +353,6 @@
"vfe-gefaellebogen-links-mass": "\n Slope arc left: dx=%1 dz=%2 mm",
"vfe-gefaellebogen-rechts-mass": "\n Slope arc right: dx=%1 dz=%2 mm",
"vfe-gefaellebogen-seite-dx": "\n Slope arc %1: dx=%2 mm",
"vfe-hz-incl-status": "\n HzIncl %1 L=%2 hz=%3 v=%4 -> Z=%5",
"vfe-hz-rot-status": "\n HzRot %1 hz=%2 v=%3 -> Z=%4",
"vfe-hz-rotks-status": "\n HzRotKS %1 hz=%2 -> Z=%3",
"vfe-hz-scaleks-status": "\n HzScaleKS %1 %2mm hz=%3 -> Z=%4",
@@ -968,7 +970,7 @@
"vfl-vwnb-typ-gerade": "\n Type? 1 = GF (<=3 deg) 2 = VF-down 3 = VF-up 4 = VF-horizontal",
"vfl-vwnb-prompt-vario-winkel": "\n Vario angle (3..51 deg) [3]: ",
"vfl-vwnb-kurve-nur-vf": "\n >>> Vario curve only allowed within a VF run - skipped.",
"vfl-vwnb-eingefuegt": "\n>>> VF line path (mode 2, GF+VF) inserted! <<<",
"vfl-vwnb-eingefuegt": "\n>>> VF line path (mode 3, GF+VF) inserted! <<<",
"vfl-vwnb-soll-ende": "\n Target end (path): X=%1 Y=%2",
"vfl-vwnb-ist-ende": "\n Actual end (ES): X=%1 Y=%2 Z=%3",
"vfl-vwnb-abweichung-xy": "\n Deviation XY: dX=%1 dY=%2 mm",
@@ -988,6 +990,20 @@
"gr-alert-laenge": "Invalid length: '%1' mm. Allowed: %2 to %3 mm.",
"gr-prompt-einfuegepunkt": "\nSelect insertion point: ",
"gr-abgebrochen": "\nCancelled.",
"gr-fertig": "\nScaffold assembly '%1' inserted (block %2)."
"gr-fertig": "\nScaffold assembly '%1' inserted (block %2).",
"gr-alert-menge": "Invalid quantity. Enter a whole number of 1 or more; an empty field uses the default.",
"gr-position-oben": "top",
"gr-position-unten": "bottom",
"gr-konfig-keine": "<none>",
"gr-konfig-name": "\nConfiguration name: ",
"gr-konfig-gespeichert": "\nConfiguration '%1' saved.",
"gr-konfig-geloescht": "\nConfiguration '%1' deleted.",
"gr-konfig-loeschen-frage": "\nReally delete configuration '%1'? [Ja/Nein] <Nein>: ",
"gr-konfig-nichts-gewaehlt": "\nNo configuration selected.",
"gr-konfig-bibliothek-fest": "\nLibrary configurations cannot be deleted - only your own (marked with *).",
"gr-konfig-teil-unbekannt": "\nScaffold: add-on part '%1' from configuration '%2' is unknown for this assembly - skipped.",
"gr-konfig-variante-unbekannt": "\nScaffold: variant '%1' from configuration '%2' is unknown for this assembly - selection unchanged.",
"gr-fehler-schreiben": "\nScaffold: could not write file: %1",
"gr-fehler-keine-baugruppen": "\nScaffold: the data file contains no assemblies."
}
}
+40
View File
@@ -886,6 +886,8 @@ def process_blocks(blocks, lookup, dbg=None):
scanner_count = 0
btmt_beladung_count = 0
btmt_entladung_count = 0
eingang_count = 0
ausgang_count = 0
geruest_count = 0
# Zaehlung fuer die Omniflo-Sum-Zeile (nur Bogen/Weiche/Gerade)
@@ -1159,6 +1161,44 @@ def process_blocks(blocks, lookup, dbg=None):
})
continue
# Eingangssymbol (Materialfluss-Eingang der Anlage) - eigene
# TeileArt-Zeile, analog zu BTMT. Wie BTMT von keinem Carrier-Attribut
# mitgezaehlt; anders als BTMT NUR im CSV-Export (export_sivas.py
# ueberspringt Eingang/Ausgang). Die TeileId kommt aus dem ID-Attribut
# (ssg-id-check-all fuellt es vor jedem Export, ID-ATTDEF seit dem
# allocations.json-Nachtrag) - dieselbe TeileId, die die
# "TrackIds"-Spalte an Kreisel/Strecke referenziert (KREISEL_UMLAUF_
# TEILEARTEN in export_neighbors.py). Fallback-Nummer wie bei BTMT.
if matches_any(bname, BLOCKPATTERNS.get("pattern_eingang", [])):
elem_nr += 1
eingang_count += 1
items.append({
"nr": elem_nr,
"teileart": "Eingang",
"teileid": block.get("attribs", {}).get("ID") or format_sensor_id("EIN", eingang_count),
"bezeichnung": f"Eingang :{eingang_count}",
"planquadrat": planquadrat_column(block, planquadrat_cfg),
"merkmale": build_btmt_merkmale(block),
**bbox_columns(block),
})
continue
# Ausgangssymbol (Materialfluss-Ausgang der Anlage) - analog zum
# Eingangssymbol oben.
if matches_any(bname, BLOCKPATTERNS.get("pattern_ausgang", [])):
elem_nr += 1
ausgang_count += 1
items.append({
"nr": elem_nr,
"teileart": "Ausgang",
"teileid": block.get("attribs", {}).get("ID") or format_sensor_id("AUS", ausgang_count),
"bezeichnung": f"Ausgang :{ausgang_count}",
"planquadrat": planquadrat_column(block, planquadrat_cfg),
"merkmale": build_btmt_merkmale(block),
**bbox_columns(block),
})
continue
# Geruest-Baugruppe (GR_<id>_<timestamp>) - eigene TeileArt-Zeile,
# analog zu BTMT: kein ANZAHL_*-Attribut an einem Carrier zaehlt sie
# mit. TeileId ist das echte ID-Attribut (ssg-id-check-all fuellt es
+60 -15
View File
@@ -84,8 +84,15 @@ SENSOR_TEILEARTEN = {"ILS 2.0 Separator", "ILS 2.0 Scanner"}
# Sensoren - eigene Konstante statt Erweiterung von SENSOR_TEILEARTEN, damit
# deren Sensor-spezifische Weiterverarbeitung (z.B. compute_scanner_nearest_
# separator) unberuehrt bleibt.
# Eingang/Ausgang (Materialfluss-Ein-/Ausgangssymbole, siehe pattern_eingang/
# pattern_ausgang in cfg/export.cfg) werden hier wie BTMT als frei platzierte
# Umlauf-Punkte gefuehrt - so tauchen sie, wenn sie auf einer Kreiselbahn
# liegen, in dessen "TrackIds"-Spalte auf (Nutzeranforderung: wie
# Separator/AS/ES behandeln). Die TeileArt-Strings sind bewusst OHNE "ILS 2.0"-
# Praefix (siehe export_csv.py) - dieselben Strings muessen hier stehen.
KREISEL_UMLAUF_TEILEARTEN = {
"ILS 2.0 Separator", "ILS 2.0 BTMT Beladung", "ILS 2.0 BTMT Entladung", "ILS Weiche",
"Eingang", "Ausgang",
}
# Nur "ILS 2.0 Kreisel" (echte Kreisel-Bloecke mit AN8/Antrieb + SP8/Spann-
@@ -1514,26 +1521,64 @@ def compute_kreisel_umlauf(items, sep_kette, tolerance_mm, durchmesser_mm=800.0,
acx = (cap["a"][0] + cap["b"][0]) / 2.0
acy = (cap["a"][1] + cap["b"][1]) / 2.0
achs_winkel = math.atan2(cap["b"][1] - cap["a"][1], cap["b"][0] - cap["a"][0])
cos_achs, sin_achs = math.cos(achs_winkel), math.sin(achs_winkel)
# Halbe Achslaenge (Mitte -> a bzw. b) und Radius der Kapsel = die
# beiden Halbachsen der Kreisel-BB im lokalen (gedrehten) Koordinaten-
# system - siehe kreisel_bounds oben, dieselbe "maximale Ausdehnung".
halblaenge = math.hypot(cap["b"][0] - cap["a"][0], cap["b"][1] - cap["a"][1]) / 2.0
radius = cap["r"]
uzs = (drehrichtung or "UZS").strip().upper() != "GUZ"
def _winkel(pxy):
# Rohwinkel relativ zur Achsrichtung, auf [0, 2*pi) normiert IN
# UMLAUFRICHTUNG. Vorher (reine Vorgaenger-/Nachfolger-Ermittlung
# ohne Startpunkt-Anspruch) wurde UZS durch reverse=True auf dem
# rohen mathematischen Winkel erreicht (= "absteigend sortieren");
# absteigend sortieren auf theta ist aequivalent zu aufsteigend
# sortieren auf -theta - darum wird HIER fuer UZS negiert (NICHT
# fuer GUZ, das dem unveraenderten mathematischen Winkel bereits
# aufsteigend entspricht). Ohne dieses Vorzeichen liefe der UZS-
# Umlauf in die falsche (zu GUZ spiegelverkehrte) Richtung - siehe
# Abfolge ueber die BB des Kreisels statt ueber einen Winkel um den
# Mittelpunkt: Punkt zuerst ins lokale (um achs_winkel gedrehte)
# Koordinatensystem der Kreiselachse bringen (lx = Achsrichtung,
# ly = quer dazu), dann auf die BB-Halbachsen normieren
# (u = lx/halblaenge, v = ly/radius). Je nachdem, welche der beiden
# Groessen (u oder v) betragsmaessig ueberwiegt, liegt der Punkt auf
# der rechten/linken Stirnseite (u dominiert) oder der oberen/
# unteren Langseite (v dominiert) der BB - dort bestimmt NUR die
# jeweils andere (die entlang der Seite laufende) Koordinate die
# Position, der ueberwiegende Anteil legt nur noch die Seite fest.
# Ein grosser, asymmetrischer Block (z.B. BTMT-Beladung/-Entladung)
# hat einen Bbox-Mittelpunkt, der deutlich weiter quer zur Achse
# absteht als der Belt-Radius - beim reinen Winkel-um-Mittelpunkt
# (frueherer Ansatz) blies das seinen Winkel unverhaeltnismaessig
# auf und liess ihn mehrere eng benachbarte Punkte in der engen
# Sortierung ueberholen, obwohl er entlang der Achse klar der
# letzte ist (realer Bug-Fall: HundM05, BTMT-Entladung "0023").
# Hier wirkt sich dieselbe Ueberhoehung nicht mehr aus, solange sie
# nicht die Seiten-Zuordnung selbst umkippt.
#
# t liegt in [0,4) und laeuft GEGEN den Uhrzeigersinn (mathematische
# Standardrichtung) einmal um die BB, beginnend bei t=0 auf der
# rechten Stirnseite in Achsrichtung (lx=halblaenge, ly=0) - das
# ist derselbe Nullpunkt wie beim frueheren "0 Grad = Achsrichtung".
# 0..1 rechte Stirnseite (ly steigend), 1..2 obere Langseite (lx
# fallend), 2..3 linke Stirnseite (ly fallend), 3..4 untere
# Langseite (lx steigend).
dx0 = pxy[1] - acx
dy0 = pxy[2] - acy
lx = dx0 * cos_achs + dy0 * sin_achs
ly = -dx0 * sin_achs + dy0 * cos_achs
u = lx / halblaenge if halblaenge > 1e-9 else 0.0
v = ly / radius if radius > 1e-9 else 0.0
if u >= abs(v):
t = v / 2.0
elif v >= abs(u):
t = 1.0 - u / 2.0
elif u <= -abs(v):
t = 2.0 - v / 2.0
else:
t = 3.0 + u / 2.0
# UZS (Uhrzeigersinn, Default) negiert t (= absteigend sortieren
# auf t ist aequivalent zu aufsteigend auf -t) - identisches Muster
# wie beim vorherigen Winkel-Ansatz, siehe
# tests/test_export_kreisel_umlauf.py::TestKreiselUmlaufStartetBeiNullGrad.
# Die Normierung auf [0, 2*pi) sorgt zusaetzlich dafuer, dass
# Index 0 nach dem Sortieren der kleinste Winkel >= 0 Grad ist
# (der gewuenschte Start bei 0 Grad).
roh = math.atan2(pxy[2] - acy, pxy[1] - acx) - achs_winkel
roh = -roh if uzs else roh
return roh % (2.0 * math.pi)
t = -t if uzs else t
return t % 4.0
sortiert = sorted(punkte, key=lambda p: _winkel(p))
n = len(sortiert)
+10
View File
@@ -504,6 +504,16 @@ def process_blocks(blocks, lookup):
counters["anzahl_abwurfstationen"] += 1
continue
# --- Eingangs-/Ausgangssymbol (Materialfluss-Ein-/Ausgang) ---
# Bewusst NUR im CSV-Export als eigene Zeile (siehe export_csv.py) -
# hier ohne Sivas-Zeile und ohne eigenen Zaehler ueberspringen. Ein
# Eingangs-/Ausgangssymbol ist eine reine Fluss-Markierung ohne Sivas-
# Artikel; ein eigener Sivas-Eintrag wuerde die Stueckliste verfaelschen
# (Nutzeranforderung: Eingang/Ausgang nur im CSV-Export).
if matches_any(bname, BLOCKPATTERNS.get("pattern_eingang", [])) or \
matches_any(bname, BLOCKPATTERNS.get("pattern_ausgang", [])):
continue
# --- Geruest-Baugruppe ---
# Anders als BTMT/Separator je Block eine EIGENE Zeile statt gruppiert:
# ARTINR (Sivas-IDs) und Laenge/Farbe koennen von Block zu Block
+1 -6
View File
@@ -110,12 +110,7 @@
[~APBW 110 R550/67,5 208x347]^C^C(ssg-ensure "OmniModulInsert") OMNI_APBW_R550_675_208
[<-~APBW 110 R550/67,5 58x497]^C^C(ssg-ensure "OmniModulInsert") OMNI_APBW_R550_675_58
[<-Element bearbeiten]^C^C(ssg-ensure "OmniModulInsert") OMNI_EDIT
[->Geruest]
[Rohrabhaenger]^C^C(ssg-ensure "GeruestInsert") GERUEST_ROHRABHAENGER
[->Saeulen]
[2 Zoll Saeulen]^C^C(ssg-ensure "GeruestInsert") GERUEST_SAEULE_2ZOLL
[<-1 Zoll Saeulen]^C^C(ssg-ensure "GeruestInsert") GERUEST_SAEULE_1ZOLL
[<-Wandbefestigung]^C^C(ssg-ensure "GeruestInsert") GERUEST_WANDBEFESTIGUNG
[Geruest...]^C^C(ssg-ensure "GeruestInsert") GERUEST
[->SPS]
[->TRO]
[Marker einfuegen]^C^C(ssg-ensure "Tro_Edit") TRO_INSERT
+90
View File
@@ -0,0 +1,90 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
test_export_eingang_ausgang.py - Eingangs-/Ausgangssymbole im Export.
Laeuft komplett ohne BricsCAD (reine Python-Logik).
Kundenwunsch: die Materialfluss-Ein-/Ausgangssymbole (Bloecke
Eingang_<DIM>/Ausgang_<DIM>, eingefuegt ueber ils-insert-station) sollen
- NUR im CSV-Export - als eigene Zeile erscheinen, inhaltlich aehnlich den
BTMT Be-/Entladestationen, und
- wie Separator/AS/ES in die "TrackIds" eingebaut werden (auch auf Kreiseln,
siehe test_export_kreisel_umlauf.py).
Im Sivas-Export duerfen sie NICHT auftauchen.
"""
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "lib"))
import export_csv # noqa: E402
import export_sivas # noqa: E402
def _block(block_name, teileid, artinr="12345", x=0.0, y=0.0):
return {
"block_name": block_name,
"x": x, "y": y, "z": 0.0,
"rotation": 0.0,
"attribs": {"ID": teileid, "ARTINR": artinr, "LAYER": "ILS_Flow",
"ZUORDNUNG": ""},
"_bbox": {"cx": x, "cy": y, "cz": 0.0, "dx": 200.0, "dy": 150.0},
}
class TestCsvKlassifikation:
"""export_csv.process_blocks gibt Eingang/Ausgang je eine eigene
TeileArt-Zeile ("Eingang"/"Ausgang") mit der Block-ID als TeileId."""
def test_eingang_und_ausgang_bekommen_eigene_zeile(self):
blocks = [
_block("Eingang_2D", "0050", x=0.0),
_block("Ausgang_3D", "0051", x=1000.0),
]
items = export_csv.process_blocks(blocks, lookup={})
nach_art = {it["teileart"]: it for it in items}
assert "Eingang" in nach_art, "Eingang-Zeile fehlt im CSV-Export"
assert "Ausgang" in nach_art, "Ausgang-Zeile fehlt im CSV-Export"
ein = nach_art["Eingang"]
aus = nach_art["Ausgang"]
# TeileId kommt aus dem ID-Attribut (stabil ueber Exportlaeufe).
assert ein["teileid"] == "0050"
assert aus["teileid"] == "0051"
# Merkmale wie BTMT: ARTINR als Merkmal gefuehrt.
assert ein["merkmale"].get("ARTINR") == "12345"
def test_teileid_fallback_ohne_id_attribut(self):
"""Ohne (noch) gefuelltes ID-Attribut greift der Fallback (EIN-/AUS-
Praefix), damit die Zeile trotzdem eine TeileId traegt - wie bei BTMT."""
blocks = [
{"block_name": "Eingang_2D", "x": 0.0, "y": 0.0, "z": 0.0,
"rotation": 0.0, "attribs": {},
"_bbox": {"cx": 0.0, "cy": 0.0, "cz": 0.0, "dx": 200.0, "dy": 150.0}},
]
items = export_csv.process_blocks(blocks, lookup={})
ein = next(it for it in items if it["teileart"] == "Eingang")
assert ein["teileid"].startswith("EIN-")
class TestSivasSchliesstAus:
"""export_sivas.process_blocks fuehrt Eingang/Ausgang bewusst NICHT als
eigene Zeile und zaehlt sie in keinem Summenzaehler mit."""
def test_keine_sivas_zeile_und_kein_zaehler(self):
blocks = [
_block("Eingang_2D", "0050"),
_block("Ausgang_3D", "0051"),
]
items, counters = export_sivas.process_blocks(blocks, lookup={})
arten = {it.get("teileart", "") for it in items}
assert "Eingang" not in arten
assert "Ausgang" not in arten
# Kein bestehender Zaehler darf durch die zwei Symbole hochgezaehlt
# worden sein (alle Zaehler bleiben 0, da nur Eingang/Ausgang im Satz).
assert all(v == 0 for v in counters.values()), (
f"Eingang/Ausgang haben einen Sivas-Zaehler beeinflusst: "
f"{{k: v for k, v in counters.items() if v}}")
+33
View File
@@ -64,6 +64,17 @@ def _btmt(teileart, teileid, x, y):
}
def _flusssymbol(teileart, teileid, x, y):
"""Eingangs-/Ausgangssymbol ("Eingang"/"Ausgang") - frei auf der Bahn
platziert wie ein Separator/BTMT, siehe KREISEL_UMLAUF_TEILEARTEN. Die
TeileArt-Strings sind bewusst OHNE "ILS 2.0"-Praefix (siehe export_csv.py)."""
return {
"teileart": teileart,
"teileid": teileid,
"_bbox": {"cx": x, "cy": y, "cz": 0.0, "dx": 200.0, "dy": 150.0},
}
def _zwei_beruehrende_kreisel():
"""Zwei Kreisel, deren AN8-Enden sich exakt beruehren (End-an-End, wie im
Mubea-Fall: 'Kreis/Kreis'-Beruehrung an der jeweiligen AN8-Kappe).
@@ -422,3 +433,25 @@ class TestKreiselTrackidsSpalte:
write_kreisel_separatorliste_merkmale(
items, kreisel_umlauf, sep_kette_by_key={}, weiche_by_key={})
assert k["trackids"] == "0031, 0020, 0030"
def test_eingang_und_ausgang_zaehlen_als_freie_punkte(self):
"""Eingangs-/Ausgangssymbole ("Eingang"/"Ausgang") sind Fluss-
Markierungen AUF der Bahn wie ein Separator/BTMT - sie muessen (auf
Kundenwunsch) im Kreisel-Umlauf UND in der TrackIds-Spalte des
Kreisels auftauchen, genau wie Separator/AS/ES."""
k = _kreisel("0001", 0.0, 0.0, 0.0, abstand=1000.0)
k["merkmale"] = {}
sep = _separator("0020", 300.0, 300.0)
ein = _flusssymbol("Eingang", "0040", 900.0, 400.0)
aus = _flusssymbol("Ausgang", "0041", 1400.0, -50.0)
items = [k, sep, ein, aus]
kreisel_umlauf = compute_kreisel_umlauf(
items, sep_kette={}, tolerance_mm=200.0, durchmesser_mm=DURCHMESSER_MM)
keys = [p[0] for p in kreisel_umlauf["0001"]]
assert set(keys) == {"0020", "0040", "0041"}, (
f"Eingang/Ausgang fehlen im Kreisel-Umlauf: {keys}")
write_kreisel_separatorliste_merkmale(
items, kreisel_umlauf, sep_kette_by_key={}, weiche_by_key={})
assert k["trackids"] == "0041, 0020, 0040"
+41 -9
View File
@@ -135,7 +135,7 @@
;; jeder ueber ssg-ils-block-laden aufloesbare Blockname funktioniert. "sensor"
;; bleibt als Sammelbegriff fuer den "kind"-Wert (= "kein Kreisel"), auch wenn
;; inzwischen nicht mehr nur Separator/Scanner darueberlaufen.
(defun hundm05:bau-sensor (obj / id blockname x y z rotation pt eff n ent ed rec)
(defun hundm05:bau-sensor (obj / id blockname x y z rotation pt eff n vorher ent ed attribs rec)
(setq id (ssg-val obj "id"))
(setq blockname (ssg-val obj "block"))
(setq x (ssg-val obj "x")
@@ -172,6 +172,7 @@
(princ (strcat "\n [SENSOR] " id " '" blockname "' -> ("
(rtos x 2 0) ", " (rtos y 2 0) ", " (rtos z 2 0)
") R=" (rtos rotation 2 1)))
(setq vorher (entlast))
(command "_.INSERT" eff pt "" "" rotation)
;; Verbleibende Prompts mit Enter abfangen, aber GEZAEHLT - siehe
;; mubea:build-separator-one (ein Prompt, der Leer-Antworten
@@ -186,14 +187,45 @@
(princ (strcat "\n WARNUNG: INSERT von '" eff
"' nicht sauber beendet - abgebrochen."))))
(setq ent (entlast))
(if (atoms-family 1 '("ssg-id-generate")) (ssg-id-generate ent))
(setq ed (entget ent))
(princ (strcat " -> Block " (cdr (assoc 2 ed))))
(setq rec (vsp-set rec "block_name" (cdr (assoc 2 ed))))
(setq rec (vsp-set rec "block_handle" (cdr (assoc 5 ed))))
(setq rec (vsp-set rec "insert_point" (cdr (assoc 10 ed))))
(setq rec (vsp-set rec "attribs" (ssg-attrib-read ent)))
(vsp-set rec "status" "executed"))))))
;; Nicht blind (entlast) vertrauen: eine verschluckte Rueckfrage beim
;; Laden ODER Platzieren (siehe ssg-ils-block-laden-dim) kann eine
;; unvollstaendige/falsche Blockreferenz ohne jedes Attribut hinterlassen
;; (realer Fall: HundM05, letztes Zusatzobjekt = SC_Entladung, erste
;; Verwendung dieses Blocknamens im Testlauf). Nur eine ECHT NEUE
;; INSERT-Referenz gilt als Erfolg; alles andere wird als Fehler
;; gemeldet und die kaputte Restgeometrie sofort entfernt statt sie
;; unbemerkt in der Zeichnung zu belassen.
(cond
((or (null ent) (eq ent vorher)
(/= (cdr (assoc 0 (entget ent))) "INSERT"))
(princ (strcat "\n [SENSOR] " id " '" blockname
"' - FEHLER: INSERT hat keine gueltige neue Blockreferenz"
" erzeugt (verschluckte Rueckfrage?)."))
(if (and ent (not (eq ent vorher))) (entdel ent))
(setq rec (vsp-set (vsp-set rec "status" "abbruch") "fehler_text"
"INSERT lieferte keine gueltige neue Blockreferenz")))
(T
(setq attribs (ssg-attrib-read ent))
(setq ed (entget ent))
(princ (strcat " -> Block " (cdr (assoc 2 ed))))
(setq rec (vsp-set rec "block_name" (cdr (assoc 2 ed))))
(setq rec (vsp-set rec "block_handle" (cdr (assoc 5 ed))))
(setq rec (vsp-set rec "insert_point" (cdr (assoc 10 ed))))
(setq rec (vsp-set rec "attribs" attribs))
;; ID nur vergeben, wenn der Block ueberhaupt ein ID-Attribut fuehrt
;; (siehe ssg-id-set-and-verify in ssg_id.lsp) - sonst faellt hier
;; dieselbe "unsichtbare" Luecke auf, statt sie in der ID-Vergabe zu
;; verstecken.
(cond
((not (assoc "ID" attribs))
(princ (strcat "\n [SENSOR] " id " '" blockname
"' - WARNUNG: Block hat kein ID-Attribut (ATTDEF"
" fehlt/veraltet, ATTSYNC noetig) - keine ID vergeben."))
(setq rec (vsp-set (vsp-set rec "status" "warnung") "fehler_text"
"Block ohne ID-ATTDEF - ATTSYNC noetig")))
(T
(if (atoms-family 1 '("ssg-id-generate")) (ssg-id-generate ent))
(vsp-set rec "status" "executed"))))))))))
;; Alle Zusatzobjekte der Datei bauen. Eigener ssg-start-Rahmen, weil
;; vsp-bau-datei seinen schon geschlossen hat; ATTREQ/ATTDIA 0 ist Pflicht -
+419 -25
View File
@@ -19,6 +19,20 @@
;; Ergebnis:
;; Konsole: PASS/FAIL je Test + Zusammenfassung
;; Datei: tests/output/unit_results.txt (von run_tests.bat gelesen)
;;
;; Ladearchitektur (EINHEITLICH):
;; Das Laden der zu testenden Module passiert AUSSCHLIESSLICH auf .scr-
;; Top-Level in tests/test_unit.scr - die Core-Module ueber ssg_load.lsp,
;; die lazy Feature-Module (GeruestInsert, count_sep_scan) per explizitem
;; load. Die Testbloecke hier laden NICHTS nach; sie pruefen nur per
;; atoms-family, ob ihr Modul vorhanden ist, und ueberspringen sich sonst
;; mit einer FAIL-Meldung.
;; Grund: In BricsCAD werden per load definierte Funktionen erst nach
;; Rueckkehr zum Top-Level sichtbar. Ein load INNERHALB von c:TEST_UNIT
;; oder eines per vl-catch-all-apply gecatchten Testblocks bindet die
;; frisch definierten Funktionen im laufenden Durchlauf NICHT (beobachtet:
;; geruest-id-string/cs-in-box blieben ungebunden). Darum gehoert alles
;; Laden vor den (c:TEST_UNIT)-Aufruf ins .scr.
;; ============================================================
@@ -300,11 +314,13 @@
(tu-eq "csv:b64-encode-ints/0" "AAAA" (csv:b64-encode-ints '(0)))
(tu-eq "csv:trans-encode/0-0-0" "AAAAAAAAAAAA" (csv:trans-encode 0.0 0.0 0.0))
;; local-to-world: reine Z-Drehung um den Elternpunkt
;; local-to-world: Eltern-Orientierung als 3x3-Rotationsmatrix (mat3-rz
;; liefert die Z-Drehmatrix aus dem Winkel - Signatur seit dem
;; Extrusions-Fix in export.lsp: parent-R statt parent-rot-rad).
(tu-eqf "csv:local-to-world/keine-drehung"
'(1.0 0.0 0.0) (csv:local-to-world '(0.0 0.0 0.0) 0.0 '(1.0 0.0 0.0)))
'(1.0 0.0 0.0) (csv:local-to-world '(0.0 0.0 0.0) (mat3-rz 0.0) '(1.0 0.0 0.0)))
(tu-eqf "csv:local-to-world/90-grad"
'(0.0 1.0 0.0) (csv:local-to-world '(0.0 0.0 0.0) (/ pi 2.0) '(1.0 0.0 0.0)))
'(0.0 1.0 0.0) (csv:local-to-world '(0.0 0.0 0.0) (mat3-rz (/ pi 2.0)) '(1.0 0.0 0.0)))
;; json-escape: Anfuehrungszeichen maskieren
(tu-eq "csv:json-escape/quote" "a\\\"b" (csv:json-escape "a\"b"))
@@ -401,6 +417,17 @@
(tu-eq "ssg-sepliste-split-feld/normal" '("SEP" "1" "10.00" "20.00")
(ssg-sepliste-split-feld "SEP:1:10.00:20.00" ":"))
;; ssg-sepliste-fmt-koord: GENAU 2 Nachkommastellen, DIMZIN-unabhaengig.
;; Ersetzt rtos (das bei DIMZIN=8 abschliessende Nullen verschluckt und die
;; XDATA-Serialisierung zeichnungsprofilabhaengig gemacht hat).
(tu-eq "sepliste-fmt/ganz" "10.00" (ssg-sepliste-fmt-koord 10.0))
(tu-eq "sepliste-fmt/eine-stelle" "30.50" (ssg-sepliste-fmt-koord 30.5))
(tu-eq "sepliste-fmt/rundung-ab" "5.01" (ssg-sepliste-fmt-koord 5.007))
(tu-eq "sepliste-fmt/uebertrag" "10.00" (ssg-sepliste-fmt-koord 9.999))
(tu-eq "sepliste-fmt/negativ" "-5.00" (ssg-sepliste-fmt-koord -5.0))
(tu-eq "sepliste-fmt/negativ-rund" "-5.01" (ssg-sepliste-fmt-koord -5.007))
(tu-eq "sepliste-fmt/null" "0.00" (ssg-sepliste-fmt-koord 0.0))
;; ssg-sepliste->string: Serialisierung eines Eintrags
(tu-eq "ssg-sepliste->string/ein-eintrag"
"SEP:1:10.00:20.00"
@@ -471,12 +498,27 @@
(tu-eq "json/escape-newline" (chr 10)
(ssg-json-get (ssg-json-parse "{\"a\": \"\\n\"}") "a"))
;; ssg-json-escape: Gegenstueck zum Parser
(tu-eq "json-escape/quote" "a\\\"b" (ssg-json-escape "a\"b"))
(tu-eq "json-escape/backslash" "a\\\\b" (ssg-json-escape "a\\b"))
(tu-eq "json-escape/newline" "a\\nb" (ssg-json-escape (strcat "a" (chr 10) "b")))
(tu-eq "json-escape/leer" "" (ssg-json-escape nil))
;; Rundlauf: maskieren -> parsen ergibt den Originaltext zurueck
(tu-eq "json-escape/rundlauf" "ROHRKLAMMER 1\" KPL. VERTIKAL"
(ssg-json-get
(ssg-json-parse
(strcat "{\"a\": \""
(ssg-json-escape "ROHRKLAMMER 1\" KPL. VERTIKAL") "\"}"))
"a"))
;; Echte Datendatei: der eigentliche Praxisfall
(setq _datei (strcat (getenv "DXFMAKRO") "/data/json/geruest.json"))
(if (findfile _datei)
(progn
(setq _daten (ssg-json-laden _datei))
(tu-eq "json/geruest-baugruppen" 4 (length _daten))
;; Drei Bereiche: Rohrabhaenger, Saeulen (1 und 2 Zoll zusammengefuehrt),
;; Wandbefestigung
(tu-eq "json/geruest-baugruppen" 3 (length _daten))
;; Baugruppe ueber das ASCII-Feld "id" finden (aeusserer Schluessel
;; traegt Umlaute bzw. Anfuehrungszeichen)
(setq _bg nil)
@@ -509,20 +551,15 @@
;; ------------------------------------------------------------
;; TESTS: GeruestInsert.lsp - reine Hilfsfunktionen
;; ------------------------------------------------------------
(defun tu-tests-geruest ( / _eintrag _gruppe)
(defun tu-tests-geruest ( / _eintrag _gruppe _teile _konfig _rec _pfad _f
_zurueck _bgtest _saeulen)
(princ "\n[GeruestInsert]")
(if (and (null (atoms-family 1 '("GERUEST-ARTIKELNUMMERN")))
(atoms-family 1 '("SSG-ENSURE")))
(ssg-ensure "GeruestInsert")
)
;; GeruestInsert ist ein lazy Feature-Modul (nicht in der Core-Ladeliste).
;; Geladen wird es zentral in test_unit.scr auf .scr-Top-Level; hier nur
;; pruefen, ob es da ist (siehe Kopfkommentar zur Ladearchitektur).
(if (null (atoms-family 1 '("GERUEST-ARTIKELNUMMERN")))
(tu-record nil "geruest/modul" "GeruestInsert.lsp nicht geladen")
(progn
;; Indizes aus get_tile bei multiple_select
(tu-eq "geruest-indizes/leer" '() (geruest-indizes ""))
(tu-eq "geruest-indizes/eins" '(0) (geruest-indizes "0"))
(tu-eq "geruest-indizes/drei" '(0 3 5) (geruest-indizes "0 3 5"))
(tu-eq "geruest-id-string/int" "821006026" (geruest-id-string 821006026))
(tu-eq "geruest-id-string/str" "0_B10071" (geruest-id-string "0_B10071"))
(tu-eq "geruest-id-string/nil" nil (geruest-id-string nil))
@@ -547,6 +584,247 @@
(tu-eq "geruest-artnr/ohne-menge" '("333")
(geruest-artikelnummern
(ssg-json-parse "{\"artikel\": \"C\", \"sivas_id\": 333}")))
;; --- Mengenfeld ---
(tu-eq "geruest-zahl-p/ziffern" T (geruest-zahl-p "12"))
(tu-eq "geruest-zahl-p/text" nil (geruest-zahl-p "1a"))
(tu-eq "geruest-zahl-p/leer" nil (geruest-zahl-p ""))
;; Leeres Feld uebernimmt die JSON-Vorgabe, ungueltiges liefert nil
(tu-eq "geruest-menge-wert/leer" 2 (geruest-menge-wert "" 2))
(tu-eq "geruest-menge-wert/zahl" 5 (geruest-menge-wert "5" 2))
(tu-eq "geruest-menge-wert/null" nil (geruest-menge-wert "0" 2))
(tu-eq "geruest-menge-wert/text" nil (geruest-menge-wert "abc" 2))
(tu-eq "geruest-menge-wert/negativ" nil (geruest-menge-wert "-3" 2))
;; --- Stueckzahl ersetzt die JSON-Menge (Einzelteil) ---
(tu-eq "geruest-artnr-n/einzelteil" '("825002019" "825002019" "825002019")
(geruest-artikelnummern-n _eintrag 3))
;; --- Gruppe: ganzes Set n-mal, Mengen INNERHALB bleiben erhalten ---
(tu-eq "geruest-artnr-n/gruppe" '("111" "222" "222" "111" "222" "222")
(geruest-artikelnummern-n _gruppe 2))
;; --- DCL-Label: Anfuehrungszeichen wuerde die Datei zerstoeren ---
(tu-eq "geruest-dcl-label/quote" "ROHRKLAMMER 1'' KPL. VERTIKAL"
(geruest-dcl-label "ROHRKLAMMER 1\" KPL. VERTIKAL"))
(tu-eq "geruest-dcl-label/ohne" "WINKEL 60X60X6X60"
(geruest-dcl-label "WINKEL 60X60X6X60"))
;; --- Hilfslisten ---
(tu-eq "geruest-nil-liste" '(nil nil nil) (geruest-nil-liste 3))
(tu-eq "geruest-liste-setzen" '(nil 7 nil)
(geruest-liste-setzen (geruest-nil-liste 3) 1 7))
(tu-eq "geruest-alist-setzen/neu" '(("a" . 1))
(geruest-alist-setzen nil "a" 1))
(tu-eq "geruest-alist-setzen/ersetzt" '(("a" . 2))
(geruest-alist-setzen '(("a" . 1)) "a" 2))
(tu-eq "geruest-zahl-string/int" "2000" (geruest-zahl-string 2000))
(tu-eq "geruest-zahl-string/leer" "" (geruest-zahl-string nil))
;; --- Konfiguration auf Anbauteile abbilden ---
(setq _teile (list _eintrag _gruppe))
(tu-eq "geruest-name-index/treffer" 1
(geruest-name-index "Schraubenset" _teile))
(tu-eq "geruest-name-index/fehlt" nil
(geruest-name-index "gibt es nicht" _teile))
;; Bekanntes Teil wird uebernommen, unbekanntes uebersprungen (nicht
;; die ganze Konfiguration verworfen)
(setq _konfig (ssg-json-parse
(strcat "{\"name\": \"K1\", \"oben\": ["
"{\"artikel\": \"Schraubenset\", \"menge\": 3},"
"{\"artikel\": \"unbekannt\", \"menge\": 1}]}")))
(tu-eq "geruest-konfig-position" '(nil 3)
(geruest-konfig-position _konfig "oben" _teile))
;; --- Variante gewinnt vor Baugruppe (TEILEART/dummy) ---
;; Die zusammengefuehrte Saeulen-Baugruppe unterscheidet 1 Zoll und
;; 2 Zoll nur noch ueber das teileart-Feld der Variante.
(setq _bgtest (ssg-json-parse
"{\"teileart\": \"Basis\", \"dummy\": \"basis.dwg\"}"))
(tu-eq "geruest-variante-wert/variante-gewinnt" "Speziell"
(geruest-variante-wert
(ssg-json-parse "{\"teileart\": \"Speziell\"}") _bgtest "teileart"))
(tu-eq "geruest-variante-wert/rueckfall" "Basis"
(geruest-variante-wert
(ssg-json-parse "{\"name\": \"X\"}") _bgtest "teileart"))
(tu-eq "geruest-variante-wert/ohne-variante" "basis.dwg"
(geruest-variante-wert nil _bgtest "dummy"))
;; --- Zusammengefuehrte Saeulen-Baugruppe ---
(setq _saeulen nil)
(foreach _e (geruest-baugruppen-liste)
(if (equal (car _e) "Saeulen") (setq _saeulen (cdr _e))))
(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")))
(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"))
)
)
;; --- Rundlauf Schreiber -> Parser ---
;; Prueft den Konfigurations-Schreiber ohne BricsCAD-Dialog. Bewusst in
;; eine temporaere Datei: ein Test darf cfg/geruest_user.json nicht
;; ueberschreiben. Die Testdaten enthalten absichtlich Anfuehrungs-
;; zeichen - genau daran wuerde eine fehlende Maskierung scheitern.
(setq _rec
(list (cons "name" "Mein \"Satz\"")
(cons "variante" "ROHR R 1\" 33,7 x 2,6")
(cons "laenge" "2000")
(cons "teilefarbe" "RAL 7035 Lichtgrau")
(cons "oben" (list (list (cons "artikel" "ROHRKLAMMER 1\" KPL. VERTIKAL")
(cons "menge" 2))))
(cons "unten" nil)))
(setq _pfad (vl-filename-mktemp "grtest" nil ".json"))
(setq _f (open _pfad "w"))
(write-line "{ \"Test\": [" _f)
(geruest-konfig-liste-schreiben _f (list _rec))
(write-line "] }" _f)
(close _f)
(setq _zurueck (car (ssg-json-get (ssg-json-laden _pfad) "Test")))
(vl-file-delete _pfad)
(tu-eq "geruest-schreiber/name" "Mein \"Satz\""
(ssg-json-get _zurueck "name"))
(tu-eq "geruest-schreiber/variante" "ROHR R 1\" 33,7 x 2,6"
(ssg-json-get _zurueck "variante"))
(tu-eq "geruest-schreiber/laenge" "2000"
(ssg-json-get _zurueck "laenge"))
(tu-eq "geruest-schreiber/farbe" "RAL 7035 Lichtgrau"
(ssg-json-get _zurueck "teilefarbe"))
(tu-eq "geruest-schreiber/oben-artikel" "ROHRKLAMMER 1\" KPL. VERTIKAL"
(ssg-json-get (car (ssg-json-get _zurueck "oben")) "artikel"))
(tu-eq "geruest-schreiber/oben-menge" 2
(ssg-json-get (car (ssg-json-get _zurueck "oben")) "menge"))
(tu-eq "geruest-schreiber/unten-leer" nil
(ssg-json-get _zurueck "unten"))
)
)
)
;; ------------------------------------------------------------
;; TESTS: count_sep_scan.lsp - geometrische Sensor-Zuordnung
;;
;; Testet die durch die Zerlegung von cs-zuordnung-lauf isolierte,
;; rein funktionale Kernlogik: Carrier-Records sind plain Listen
;; (ename tagS tagP minx miny maxx maxy cx cy id label), Sensoren
;; sind Punkte (x y). Kein ssget/entget/entmod - der ename an
;; Position 0 wird von den Geometrie-Routinen nie dereferenziert,
;; darum genuegt ein Symbol-Platzhalter. *senstol* wird fuer
;; deterministische Ergebnisse explizit gesetzt (sonst cfg-abhaengig).
;; ------------------------------------------------------------
;; Record-Konstruktor fuers Testen: baut einen Carrier-Record so, wie
;; ihn cs-sammle-bloecke liefert - nur die Box-/Zentrum-/ID-Felder
;; (Index 3-9), die die Geometrie-Routinen lesen, sind relevant; ename
;; und label sind reine Platzhalter.
;; (name minx miny maxx maxy id) -> voller Carrier-Record
(defun tu-mk-carr (name minx miny maxx maxy id)
(list name "ANZAHL_SCANNER" "ANZAHL_SEPARATOR"
minx miny maxx maxy
(/ (+ minx maxx) 2.0) (/ (+ miny maxy) 2.0)
id (strcat "L-" name)))
(defun tu-tests-sepscan ( / _senstol-alt _a _b _c _carr _grid _res)
(princ "\n[count_sep_scan]")
;; count_sep_scan ist ein lazy Feature-Modul (nicht in der Core-Ladeliste).
;; Geladen wird es zentral in test_unit.scr auf .scr-Top-Level; hier nur
;; pruefen, ob es da ist (siehe Kopfkommentar zur Ladearchitektur).
(if (null (atoms-family 1 '("CS-RESOLVE-SENSOR")))
(tu-record nil "sepscan/modul" "count_sep_scan.lsp nicht geladen")
(progn
(setq _senstol-alt (if (boundp '*senstol*) *senstol*))
(setq *senstol* 0.0)
;; --- cs-in-box: Punkt in Carrier-Box (X/Y, Z ignoriert) ---
(setq _a (tu-mk-carr "A" 0.0 0.0 100.0 100.0 "0001"))
(tu-true "cs-in-box/mitte" (cs-in-box '(50.0 50.0) _a))
(tu-true "cs-in-box/rand" (cs-in-box '(0.0 0.0) _a))
(tu-nil "cs-in-box/ausserhalb-x" (cs-in-box '(150.0 50.0) _a))
(tu-nil "cs-in-box/ausserhalb-y" (cs-in-box '(50.0 -1.0) _a))
;; --- *senstol* vergroessert die Box ---
(setq *senstol* 10.0)
(tu-true "cs-in-box/toleranz-innen" (cs-in-box '(105.0 50.0) _a))
(tu-nil "cs-in-box/toleranz-aussen" (cs-in-box '(111.0 50.0) _a))
(setq *senstol* 0.0)
;; --- cs-bump: Zaehler je ename hochzaehlen ---
(tu-eq "cs-bump/neu" '(("k" . 1)) (cs-bump nil "k"))
(tu-eq "cs-bump/erhoeht" '(("k" . 2)) (cs-bump '(("k" . 1)) "k"))
(tu-eq "cs-bump/zweiter-key"
'(("m" . 1) ("k" . 1)) (cs-bump '(("k" . 1)) "m"))
;; --- cs-carrier-by-id: Record ueber ID (Feld 9) finden ---
(setq _b (tu-mk-carr "B" 200.0 0.0 300.0 100.0 "0002"))
(setq _carr (list _a _b))
(tu-eq "cs-carrier-by-id/treffer" _b (cs-carrier-by-id "0002" _carr))
(tu-nil "cs-carrier-by-id/fehlt" (cs-carrier-by-id "9999" _carr))
(tu-nil "cs-carrier-by-id/leer" (cs-carrier-by-id "" _carr))
(tu-nil "cs-carrier-by-id/nil" (cs-carrier-by-id nil _carr))
;; --- cs-assign: BBox-Zuordnung ueber das Grid ---
;; Zwei getrennte Boxen: Punkt in genau einer -> diese.
(setq _grid (cs-grid-build _carr))
(tu-eq "cs-assign/box-a" _a (cs-assign '(50.0 50.0) _grid))
(tu-eq "cs-assign/box-b" _b (cs-assign '(250.0 50.0) _grid))
(tu-nil "cs-assign/keine-box" (cs-assign '(1000.0 1000.0) _grid))
;; Ueberlappende Boxen -> naechstes Box-Zentrum gewinnt (Tiebreak).
;; C: 0..100, D: 40..200; Punkt (45,50) liegt in beiden,
;; C-Zentrum (50,50) ist naeher als D-Zentrum (120,50).
(setq _a (tu-mk-carr "C" 0.0 0.0 100.0 100.0 "0003"))
(setq _b (tu-mk-carr "D" 40.0 0.0 200.0 100.0 "0004"))
(setq _grid (cs-grid-build (list _a _b)))
(tu-eq "cs-assign/overlap-naechstes-zentrum"
_a (cs-assign '(45.0 50.0) _grid))
;; --- cs-nearest-two: die zwei naechsten Carrier nach Zentrum ---
;; Zentren: A(50,50) B(250,50) C(50,250). Punkt (60,60):
;; naechster A, zweiter B (dx 190 < dy 190? -> Abstand B=~194, C=~190).
(setq _a (tu-mk-carr "A" 0.0 0.0 100.0 100.0 "0001"))
(setq _b (tu-mk-carr "B" 200.0 0.0 300.0 100.0 "0002"))
(setq _c (tu-mk-carr "C" 0.0 200.0 100.0 300.0 "0003"))
(setq _res (cs-nearest-two '(60.0 60.0) (list _a _b _c)))
(tu-eq "cs-nearest-two/naechster" _a (nth 0 _res))
(tu-true "cs-nearest-two/zweiter-vorhanden" (nth 2 _res))
;; Abstand zum naechsten (A-Zentrum 50,50) = sqrt(10^2+10^2) ~ 14.14
(tu-eqf "cs-nearest-two/best-dist"
(distance '(60.0 60.0) '(50.0 50.0)) (nth 1 _res))
;; Nur ein Carrier -> zweiter ist nil.
(setq _res (cs-nearest-two '(60.0 60.0) (list _a)))
(tu-eq "cs-nearest-two/einzeln-best" _a (nth 0 _res))
(tu-nil "cs-nearest-two/einzeln-kein-zweiter" (nth 2 _res))
;; --- cs-resolve-sensor: der Entscheider bbox/distance/none ---
;; Punkt in einer Box -> modus 'bbox, kein Distanzfeld.
(setq _grid (cs-grid-build (list _a _b _c)))
(setq _res (cs-resolve-sensor '(50.0 50.0) (list _a _b _c) _grid))
(tu-eq "cs-resolve/bbox-modus" 'bbox (nth 0 _res))
(tu-eq "cs-resolve/bbox-rec" _a (nth 1 _res))
(tu-nil "cs-resolve/bbox-kein-dist" (nth 2 _res))
;; Punkt ausserhalb aller Boxen, aber Carrier vorhanden -> 'distance.
(setq _res (cs-resolve-sensor '(1000.0 1000.0) (list _a _b _c) _grid))
(tu-eq "cs-resolve/distance-modus" 'distance (nth 0 _res))
(tu-true "cs-resolve/distance-rec" (nth 1 _res))
(tu-true "cs-resolve/distance-dist" (nth 2 _res))
;; Gar keine Carrier -> 'none.
(setq _grid (cs-grid-build nil))
(setq _res (cs-resolve-sensor '(50.0 50.0) nil _grid))
(tu-eq "cs-resolve/none-modus" 'none (nth 0 _res))
(tu-nil "cs-resolve/none-rec" (nth 1 _res))
;; *senstol* wieder herstellen
(setq *senstol* _senstol-alt)
)
)
)
@@ -577,26 +855,142 @@
)
;; ------------------------------------------------------------
;; TESTS: vf_linienzug.lsp - SEGS-Journal-Serialisierung (Modus 3)
;; ------------------------------------------------------------
;; Roundtrip der Segment-Essenz: die Zahlenliste, die Modus 3 aus den Pfad-
;; Objekten ableitet, muss verlustfrei durch String<->Liste und durch die
;; Journal-Eintrags-Serialisierung laufen - das ist die Grundlage dafuer, dass
;; ein Modus-3-Block per Doppelklick 1:1 aus dem XDATA-Journal nachgebaut wird.
(defun tu-tests-vfl-segs ( / _seg _segs _s _e)
(princ "\n[vf_linienzug/SEGS]")
(if (null (atoms-family 1 '("VFL-SEGS->STRING")))
(tu-record nil "vfl-segs/modul" "vf_linienzug.lsp nicht geladen (VFL-SEGS->STRING fehlt)")
(progn
;; Einzelsegment (Bogen, alle Felder gesetzt) Roundtrip
(setq _seg '("Bogen" 102.5 707.106781 90 "rechts" 1000.0 2000.0 1500.5 2500.25))
(tu-eqf "vfl-seg/roundtrip-bogen"
_seg (vfl-string->seg (vfl-seg->string _seg)))
;; Linie (bwinkel 0, bseite default) Roundtrip
(setq _seg '("Linie" 30.0 4200.0 0 "links" 0.0 0.0 3637.31 2100.0))
(tu-eqf "vfl-seg/roundtrip-linie"
_seg (vfl-string->seg (vfl-seg->string _seg)))
;; Ganze Segmentliste (Linie + Bogen + Linie) Roundtrip
(setq _segs (list '("Linie" 0.0 3000.0 0 "links" 0.0 0.0 3000.0 0.0)
'("Bogen" 0.0 707.106781 90 "rechts" 3000.0 0.0 3500.0 -500.0)
'("Linie" 270.0 2500.0 0 "links" 3500.0 -500.0 3500.0 -3000.0)))
(tu-eqf "vfl-segs/roundtrip-liste"
_segs (vfl-string->segs (vfl-segs->string _segs)))
;; Leere Liste -> "" -> leere Liste
(tu-eq "vfl-segs/leer-string" "" (vfl-segs->string '()))
(tu-nil "vfl-segs/leer-parse" (vfl-string->segs ""))
;; Einbettung als Journal-Eintrag: (SEGS . liste) durch
;; vfl-entry->string / vfl-string->entry
(setq _e (cons "SEGS" _segs))
(setq _s (vfl-entry->string _e))
(tu-true "vfl-segs/entry-praefix" (= "SEGS:" (substr _s 1 5)))
(tu-eqf "vfl-segs/entry-roundtrip"
_e (vfl-string->entry _s))
;; Delimiter-Sicherheit: die serialisierte Form enthaelt genau die
;; erwarteten Trenner und KEIN Newline (sonst zerrisse der Record-Trenner)
(tu-nil "vfl-segs/kein-newline"
(vl-string-search "\n" (vfl-segs->string _segs)))
;; vfl-journal-hat-segs-p: unterscheidet neue SEGS-Ketten von alten
;; OBJS-"linienzug2"-Bloecken (harter Schnitt beim Doppelklick).
(if (atoms-family 1 '("VFL-JOURNAL-HAT-SEGS-P"))
(progn
;; SEGS-Journal (Modus 2 neu / Modus 3): erkannt
(tu-true "vfl-hat-segs/mit-segs"
(vfl-journal-hat-segs-p
(list (cons "PT" '(0.0 0.0 0.0)) (cons "SEGS" _segs))))
;; Altes OBJS-Journal (Objekt-Handles): NICHT als SEGS erkannt
(tu-nil "vfl-hat-segs/nur-objs"
(vfl-journal-hat-segs-p
(list (cons "PT" '(0.0 0.0 0.0)) (cons "OBJS" '("A1" "A2")))))
;; Leeres Journal: nil
(tu-nil "vfl-hat-segs/leer" (vfl-journal-hat-segs-p '())))
(tu-record nil "vfl-hat-segs/modul" "VFL-JOURNAL-HAT-SEGS-P fehlt"))
)
)
)
;; ------------------------------------------------------------
;; TESTS: vf_linienzug.lsp - gemeinsamer Abbruch-Handler-Konstruktor
;; ------------------------------------------------------------
;; vfl-abbruch-handler-installieren baut *error* als Lambda-Liste, in die der
;; Marker ("linienzug"/"linienzug2"/"linienzug3") als String-Literal eingebaut
;; ist. Der wortgleiche Wickel-Handler steht damit nur noch EINMAL statt 3x.
;; Getestet wird die KONSTRUKTION (Marker an der richtigen Stelle, Struktur
;; aufrufbar) - NICHT ein echter Abbruch (der braeuchte Zeichnung/Geometrie).
;; *error* wird gesichert und wiederhergestellt, damit der Testlauf unberuehrt
;; bleibt.
(defun tu-tests-vfl-abbruch ( / _save _h _call)
(princ "\n[vf_linienzug/Abbruch-Handler]")
(if (null (atoms-family 1 '("VFL-ABBRUCH-HANDLER-INSTALLIEREN")))
(tu-record nil "vfl-abbruch/modul"
"vf_linienzug.lsp nicht geladen (VFL-ABBRUCH-HANDLER-INSTALLIEREN fehlt)")
(progn
(setq _save *error*)
(setq _h (vfl-abbruch-handler-installieren "linienzug2"))
;; Rueckgabe = das gesetzte *error* (identisch)
(tu-true "vfl-abbruch/setzt-error" (equal _h *error*))
;; Struktur: (lambda (msg) ...) - erstes Element lambda, zweites (msg)
(tu-eq "vfl-abbruch/ist-lambda" 'lambda (car _h))
(tu-eq "vfl-abbruch/arg-msg" '(msg) (cadr _h))
;; Der vfl-modus-abbruch-sichern-Aufruf im Rumpf traegt GENAU den
;; uebergebenen Marker als letztes Argument (nicht einen anderen Modus).
(setq _call (car (vl-member-if
(function (lambda (f)
(and (listp f) (eq (car f) 'vfl-modus-abbruch-sichern))))
(cddr _h))))
(tu-true "vfl-abbruch/ruft-sichern" (not (null _call)))
(tu-eq "vfl-abbruch/marker-linienzug2" "linienzug2" (last _call))
;; Anderer Marker -> anderes Literal an derselben Position
(vfl-abbruch-handler-installieren "linienzug3")
(setq _call (car (vl-member-if
(function (lambda (f)
(and (listp f) (eq (car f) 'vfl-modus-abbruch-sichern))))
(cddr *error*))))
(tu-eq "vfl-abbruch/marker-linienzug3" "linienzug3" (last _call))
;; *error* wiederherstellen - Testlauf darf nicht mit einem Wickel-Handler
;; ohne gebundene Modus-Locals zurueckbleiben.
(setq *error* _save)
)
)
)
;; ------------------------------------------------------------
;; BEFEHL: TEST_UNIT
;; ------------------------------------------------------------
;; Testblock geschuetzt aufrufen: ein harter LISP-Fehler in einem Block
;; wird als FAIL erfasst, statt c:TEST_UNIT abzubrechen und damit den
;; Report zu verhindern. Der Blockname (String) wird per read zur Funktion.
(defun tu-run-block (fn / res)
(setq res (vl-catch-all-apply (read fn) nil))
(if (vl-catch-all-error-p res)
(tu-record nil (strcat fn "/ausfuehrung")
(strcat "harter Fehler: " (vl-catch-all-error-message res))))
)
(defun c:TEST_UNIT ( / out-dir report)
(tu-reset)
(princ "\n================================================================")
(princ "\n TEST_UNIT - LISP-Standardfunktionen")
(princ "\n================================================================")
(tu-tests-core)
(tu-tests-lang)
(tu-tests-id)
(tu-tests-vfcore)
(tu-tests-export)
(tu-tests-gf)
(tu-tests-omni)
(tu-tests-kreisel)
(tu-tests-sepliste)
(tu-tests-json)
(tu-tests-geruest)
(foreach fn '("tu-tests-core" "tu-tests-lang" "tu-tests-id" "tu-tests-vfcore"
"tu-tests-export" "tu-tests-gf" "tu-tests-omni" "tu-tests-kreisel"
"tu-tests-sepliste" "tu-tests-json" "tu-tests-geruest"
"tu-tests-sepscan" "tu-tests-vfl-segs" "tu-tests-vfl-abbruch")
(tu-run-block fn))
(princ "\n\n----------------------------------------------------------------")
(princ (strcat "\n Gesamt: " (itoa (+ *tu-pass* *tu-fail*))
+8
View File
@@ -2,6 +2,14 @@
; Laedt SSG_LIB + die Unit-Tests, fuehrt TEST_UNIT aus und beendet BricsCAD.
; Pfadunabhaengig ueber DXFMAKRO (von setenv.bat gesetzt).
(load (strcat (getenv "DXFMAKRO") "/Lisp/ssg_load.lsp"))
; Lazy Feature-Module, die einzelne Unit-Tests brauchen, hier auf .scr-Top-
; Level laden. In BricsCAD werden per load definierte Funktionen erst nach
; Rueckkehr zum Top-Level sichtbar - ein load INNERHALB von c:TEST_UNIT
; (oder eines gecatchten Testblocks) bindet sie im selben Durchlauf nicht.
; DIES ist die einzige Ladestelle: Testet ein neuer Block ein weiteres lazy
; Modul, gehoert dessen load-Zeile HIER dazu (nicht in test_unit.lsp).
(load (strcat (getenv "DXFM_LISP") "/GeruestInsert.lsp") "GeruestInsert.lsp nicht gefunden")
(load (strcat (getenv "DXFM_LISP") "/count_sep_scan.lsp") "count_sep_scan.lsp nicht gefunden")
(load (strcat (getenv "DXFMAKRO") "/tests/test_unit.lsp"))
(c:TEST_UNIT)
(command "_.QUIT")
+1
View File
@@ -173,6 +173,7 @@ EINGABE_ERLAUBT = {
"vfl-in-int": {"getint": 1},
"vfl-in-real": {"getreal": 1},
"vfl-in-selection": {"ssget": 1},
"vfl-in-segmente": {"ssget": 1}, # Modus-3-Wrapper: Live-Auswahl -> Segment-Essenz (SEGS), Replay aus Journal
"vfl-winkel-live": {"getint": 1}, # livefn fuer vfl-in-value
# --- Wizard-Schicht (DCL); bei ssg-gui-aus greift sie nicht ---
"vflw-zahl": {"getreal": 1}, # Fallback ohne Dialog
+9 -9
View File
@@ -323,7 +323,7 @@
"comment": "Separator 3 (Nachtrag HundM05, reale Koordinate laut Anwender)",
"function": "insert",
"block": "S-LP",
"x": 7831.8,
"x": 8549.28,
"y": 1169.5,
"z": 2154,
"rotation": 180
@@ -333,7 +333,7 @@
"comment": "Scanner zu Separator 3 (Nachtrag HundM05, reale Koordinate laut Anwender)",
"function": "insert",
"block": "Scanner",
"x": 7874.5,
"x": 8598.69,
"y": 1167.7,
"z": 2154,
"rotation": 270
@@ -343,7 +343,7 @@
"comment": "Separator 4 (Nachtrag HundM05, reale Koordinate laut Anwender)",
"function": "insert",
"block": "S-LP",
"x": 12191.0,
"x": 12619.88,
"y": 1168.2,
"z": 2154,
"rotation": 180
@@ -353,7 +353,7 @@
"comment": "Scanner zu Separator 4 (Nachtrag HundM05, reale Koordinate laut Anwender)",
"function": "insert",
"block": "Scanner",
"x": 12233.7,
"x": 12669.1,
"y": 1166.4,
"z": 2154,
"rotation": 270
@@ -363,7 +363,7 @@
"comment": "Separator 5 (Nachtrag HundM05, reale Koordinate laut Anwender)",
"function": "insert",
"block": "S-LP",
"x": 16488.4,
"x": 16785.77,
"y": 1171.7,
"z": 2154,
"rotation": 180
@@ -373,7 +373,7 @@
"comment": "Scanner zu Separator 5 (Nachtrag HundM05, reale Koordinate laut Anwender)",
"function": "insert",
"block": "Scanner",
"x": 16531.1,
"x": 16839.52,
"y": 1169.8,
"z": 2154,
"rotation": 270
@@ -435,7 +435,7 @@
"block": "BTMT-Beladung",
"x": 5290.9,
"y": 370.2,
"z": 0,
"z": 2154,
"rotation": 179.03
},
{
@@ -445,7 +445,7 @@
"block": "BTMT-Beladung",
"x": 4261.2,
"y": 374.2,
"z": 0,
"z": 2154,
"rotation": 179.18
},
{
@@ -455,7 +455,7 @@
"block": "SC_Entladung",
"x": 24768.7,
"y": 1170.3,
"z": 0,
"z": 2154,
"rotation": 0.97
}
]