diff --git a/CLAUDE.md b/CLAUDE.md index 698128a..bce4e08 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -78,6 +78,7 @@ Die `start_briscad.bat` ruft `setenv.bat` auf und startet BricsCAD. | Geruest | `GeruestInsert` | GERUEST_SAEULEN, GERUEST_ROHRABHAENGER, GERUEST_WANDBEFESTIGUNG (derselbe Dialog, im jeweiligen Bereich geoeffnet), GERUEST_TRAEGER (eigener Dialog), GERUEST_TRAEGER_BESCHRIFTEN (Untermenue Geruest -> Funktionen -> Traeger beschriften; alle Traeger der Zeichnung neu beschriften); Gattertraeger noch Platzhalter auf GERUEST | | Connections | `Connection_Edit` + `SSG_LIB_Commands` | CONNECTION_INSERT, CONNECTION_EDIT, ILS_EINGANG_INSERT, ILS_AUSGANG_INSERT | | Export | `export` | EXPORTSIVAS, EXPORTCSV, CALLPYTHON | +| Werkzeuge | `ssg_blocktools`, `ssg_texttools` | SSG_BLOCK_COUNT, SSG_BLOCK_DELETE, SSG_ATTR_ERSETZEN, SSG_TEXT_ERSETZEN, SSG_TEXT_PARALLEL, SSG_TEXT_HOEHE, SSG_TEXT_STIL | | Konfiguration | `SSG_LIB_Commands` + `DrawPlanquadrate` | SSG_DIM_SWITCH, SSG_SPRACHE, PLANQUADRATE_TOGGLE (DRAW_PLANQUADRATE/DELETE_PLANQUADRATE) | | Doppelklick/Kontextmenue | `SSG_LIB_Commands` | SSG_BLOCKEDIT (dispatcht Kreisel->KreiselEdit, Omniflo->OMNI_EDIT, GF_*->GEFAELLESTRECKE_EDIT, VF_*->VARIOFOERDERER_EDIT) | @@ -99,9 +100,12 @@ Detaillierte Dokumentation aller Funktionen und Befehle: siehe `Lisp/README.md`. | `ssg_dbg.lsp` | - | Debug-Logging (dbgopen, dbgmsg, dbgflush, dbgclose) | | `ssg_dialog.lsp` | - | DCL-Dialog-Vorlagen und Hilfsfunktionen | | `ssg_layer.lsp` | - | Layer-Verwaltung. `ssg-layer-visible-p` (aus ODER gefroren), `ssg-layer-gefroren-p`/`ssg-ent-layer-gefroren-p` (NUR gefroren, fuer den Export) | +| `ssg_input.lsp` | - | Generische Eingabe-Wrapper `ssg-in-real/-int/-string/-point/-dist/-keyword/-yesno` (Vorgabe in `<..>`, Enter = Vorgabe, optionaler Hook `*ssg-in-hook*` fuer Journale). Steht NEBEN den VF-spezifischen `vfl-in-*` (diese bleiben unveraendert). `ssg-in-yesno` liefert T bei Ja (anders als `ssg-ques`, das T fuer die nicht vorgegebene Antwort liefert); Schluesselwoerter aus `ssg-text "in-yesno-keys"` | +| `ssg_blocktools.lsp` | SSG_BLOCK_COUNT, SSG_BLOCK_DELETE, SSG_ATTR_ERSETZEN | Block-Werkzeuge fuer das aktuelle Layout (Ursprung ACADSST `TOOLS.LSP`/`ssg.lsp`): Einfuegungen zaehlen (Pick, wcmatch-Muster oder Statistik aller Bloecke), alle Einfuegungen loeschen (Rueckfrage, gesperrte Layer bleiben), Attributwerte zeichnungsweit/in Auswahl suchen+ersetzen (Vorschau mit Anzahl, Teilstring oder Gesamtwert). Wirksamer Blockname (dynamische Bloecke). Reine Kerne `ssg-bt-name-passt-p`, `ssg-bt-zaehle-namen`, `ssg-bt-wert-neu` in `tests/test_unit.lsp` getestet | +| `ssg_texttools.lsp` | SSG_TEXT_ERSETZEN, SSG_TEXT_PARALLEL, SSG_TEXT_HOEHE, SSG_TEXT_STIL | Text-Werkzeuge (Ursprung ACADSST `CHGTEXT`/`PARTEXT`/`ALTHOEHE`/`ALTSTIL`): Suchen/Ersetzen in TEXT, MTEXT und Block-Attributen (nutzt `ssg-bt-*`, daher NACH `ssg_blocktools` laden), Text parallel zu Linie/Polylinie/Bogen (lesbar ausgerichtet, Abstand), Hoehe/Stil aendern. Arbeitet in der XY-Ebene des Welt-KS. Reine Kerne `ssg-tt-lesbarer-winkel`, `ssg-tt-versetzt` getestet | | `KreiselInsert.lsp` | KreiselInsert, KreiselConnect, KreiselRedraw, KreiselQuick, KreiselEdit, KreiselParams, KreiselLabelSetup, KreiselLabelPos, KreiselLabelHoehe, ILS_Eckrad | ILS Kreisel und Eckrad komplett in AutoLISP | -| `VarioFoerderer.lsp` (Wrapper fuer `vf_core.lsp`/`vf_standard.lsp`/`vf_etage.lsp`/`vf_linienzug.lsp`) | FOERDERANLAGE, VARIOFOERDERER_EDIT, VARIOFOERDERER_NACHRUESTEN, Vario_Kette_Merge, VF_SEKTION_RESTORE, VF_WIZARD_AN, VF_WIZARD_AUS | Vario-Foerderanlagen Generator (Typen Standard/Etage/Linienzug); Standard- UND Etage-Typ: Werteingabe per DCL-Dialog. Der Dialog-Ablauf ist zentral in vf_core (`vf-dialog-ablauf`/`vf-dialog-berechnen-einfuegen`, parametrisiert ueber Typ-Registry `*vf-typ-registry*` + Flag `horizontal-p`); `vfs-standard-dialog-ablauf`/`vfe-etage-dialog-ablauf` sind nur noch duenne Wrapper darauf. Beide nutzen dieselben Dialoge `vfs-dialog-eingabe-basis`/`vfs-dialog-winkel-verteilung` und den gemeinsamen L_GF/L_VF-Solve `vf-winkel-solve` (vf_core; berechne-alle-winkel/berechne-winkel-etage teilen ihn); Doppelklick auf VF_n-Block ruft VARIOFOERDERER_EDIT, das per SSG_VF_EDIT-XDATA-Marker ("standard"/"etage"/"linienzug") in den passenden Neuaufbau-Zweig dispatcht (`vfe-edit-ent` fuer Etage, `vfl-edit-ent` fuer Linienzug); Altbestand-Bloecke ohne Marker werden abgewiesen. Linienzug (Modus 1, `vf-linienzug-modus`) zeichnet jede interaktive Eingabe in einem Journal auf (`vfl-in-point`/`-string`/`-real`/`-int`) und schreibt es als XDATA auf den fertigen `VF_n`-Block; ein Abbruch (ESC) loescht dadurch nichts mehr, sondern wickelt die bis dahin gebaute Teil-Geometrie ebenso zu einem Block (`vfl-modus-abbruch-sichern`) - sie bleibt stehen und ist sofort per Doppelklick weiter editierbar/fortsetzbar. Der komplette Linienzug-Hauptstrang (Menue-Fragen von AS-Element bis Kettenende, inkl. GF-Bogen/Vario-Kurve/VF-Einheit-Fortsetzung) laeuft per Default ueber DCL-Dialoge statt Konsole (`*vfl-wizard-mode*`, Befehle VF_WIZARD_AN/VF_WIZARD_AUS): `vfl-in-string`/`-int`/`-real` zeigen bei aktivem Wizard-Modus generische Dialoge aus `dcl/vf_linienzug_wizard.dcl` (Zahl-Eingabe bzw. Mehrfachauswahl aus `vfl-menu`/`vfl-menu-int`) statt `getstring`/`getint`/`getreal` aufzurufen; die ORIGINALEN Konsolen-Fragen (princ + get*, de/en ueber ssg-text) bleiben unveraendert im Code (Fallback bei Fehlern via `vl-catch-all-apply`, siehe `doc/variofoerderer.md`). `vfl-edit-ent` liest das Journal, zeigt die Sektionen ueber einen DCL-Dialog (`vfl-dlg-position`, `dcl/vfl_edit.dcl`) an, kuerzt bei Bedarf auf eine gewaehlte Sektion (stummer Replay der behaltenen Eingaben, umgeht den Wizard-Zweig komplett) und baut danach interaktiv weiter. Linienzug Modus 2 (Pfad+Zielhoehe-Solver, `vf-linienzug-modus2`) nutzt dieselben Wrapper PLUS die neue Objektauswahl-Wrapper-Funktion `vfl-in-selection` (Pfad-Objekte als stabile Entity-Handles journalisiert, neue Journal-Art `"OBJS"`) und schreibt sein Journal unter dem eigenen XDATA-Marker `"linienzug2"`; Doppelklick baut hier NICHT abschnittsweise, sondern per vollem Reset 1:1 aus dem Journal neu auf (`vfl-edit-ent2`, dispatcht aus `vfl-edit-ent` per Marker) - Modus 3 (Vorwaerts-Nachbau) bleibt bewusst reine Konsole (im Code selbst als Legacy/"wird umgebaut" markiert). `Vario_Kette_Merge` (in vf_linienzug.lsp) fuehrt mehrere einzeln gebaute VF_n-Bloecke ab einem gewaehlten Start-Baustein ueber die reale KS_AUS->KS_EIN-Nachbarschaft zu einem Gesamt-Block zusammen (aggregierte Attribute, Luecken-Erkennung). Die interaktiven Editier-Pfade (`vfl-edit-ent` Sektions-Zweig, `vfl-edit-ent2`) rufen den Neuaufbau BEWUSST direkt auf, NICHT ueber `vl-catch-all-apply`: der alte Block ist beim Neuaufbau schon per `entdel` weg, und ein Catch faengt den Abbruch VOR `*error*` ab - der Abbruch-Wickler `vfl-modus-abbruch-sichern` kaeme nie zum Zug und die ganze Kette bliebe als lose Einzelteile ohne Block liegen (Fehlerbild "VF-Block aufgebrochen"). Nur der nicht-interaktive Batch-Konverter `vfl-konvertiere-ent` behaelt den Catch (die Batch-Schleife muss weiterlaufen) und meldet den betroffenen Block. Die Abbruch-Handler setzen zusaetzlich `*ssg-ils-dim*` zurueck, weil der Aufrufer, der das sonst tut, nach einem Abbruch nicht mehr erreicht wird. Recovery-Pfad fuer bereits aufgebrochene Ketten: `VF_SEKTION_RESTORE` (vf_linienzug.lsp) setzt das Ketten-Journal aus der XDATA der losen Objekte zusammen - `SSG_VF_EDIT_SEG` traegt pro Entity den Journal-Abschnitt DER ITERATION, in der es entstand (indiziert mit dem ersten Glied-Index darin; eine VF-Einheit mit eingebetteten Vario-Kurven erzeugt mehrere Glieder in einer Iteration), `SSG_VF_EDIT_PRE` auf den Entities des ersten Gliedes die Praeambel (Startpunkt/Starthoehe/AS). Die `.dbg`-Dateien sind reine AUSGABE und werden von keinem Befehl gelesen (dbgopen legt sie bei jedem Lauf neu an) - Wiederherstellungs-Daten gehoeren in die XDATA. `vf_konstanten.lsp` (von vf_core.lsp vor den drei Submodulen geladen) sammelt alle Laengen-/Winkel-/Toleranz-Magic-Numbers aus dem VarioFoerderer-Cluster als dokumentierte `*vfk-*`-Konstanten. Die Migration ist abgeschlossen: vf_core/vf_standard/vf_etage/vf_linienzug referenzieren durchgaengig die zentralen Konstanten statt roher Literale (Winkel, Stations-/Separator-Laengen, Mindestlaengen, AS/ES-Fallback-Versaetze, Text-/Label-Masse) - Ausnahmen sind in `vf_konstanten.lsp` selbst dokumentiert (Werte mit fuehrender Config-Anbindung via `ssg-cfg-or`, itoa-/Integer-Kontexte, Blocknamen-Strings, die 2000.0-mm/m-Umrechnung). `*vfk-gf-bogen-winkel*` (30/60/90, GF-Bogen + Vario-Kurve) und `*vfk-as-es-winkel*` ("30"/"90", AS/ES; bewusst STRING, da 2-wertig und im Journal als STR gefuehrt) sind Beispiele; vf_linienzug hat fuer beide einen Fallback, falls vf_konstanten nicht geladen wurde. **Nicht-interaktiver Betrieb**: `*vfl-headless*` (+ `vfl-headless-p`/`-abbruch`/`-notausgang`, Diagnose `*vfl-headless-fehler*`, optionaler Antwort-Hook `*vfl-headless-antwort-fn*`) verbietet jede Live-Eingabe - eine erschoepfte Replay-Queue ist damit ein harter Abbruch MIT Fundstelle (Art der Eingabe, Glied- und Eingabe-Nummer) statt eines stillen Rueckfalls auf `getpoint`/`getreal`; eingebaut in `vfl-in-value`/`-value-p`/`-selection`/`-abstand`. Die Bau-Pfade melden abgewiesene Sektionen ueber `vfl-meldung` (sammelt nach `*vfl-meldungen*` + `dbgmsg`, `alert` nur bei erlaubter GUI, sonst `princ`) statt per blockierendem `alert`; reine Interaktiv-Alerts (fehlendes DCL, "nicht editierbar", "kein Journal") bleiben `alert`. Genutzt von `tests/test_hm_recformat.lsp` (Replay der echten Aufzeichnungen) und von `vf_spec.lsp`; Fahrplan in `doc/TODO-plan-vf-interactive.md`. Details/Entscheidungsbaum: `doc/variofoerderer.md` | -| `vf_spec.lsp` (von vf_core.lsp NACH vf_linienzug.lsp geladen) | VF_SPEC_BAU | Linienzug-Ketten aus **Eingabedaten** bauen, ohne Dialog und ohne Konsolenfrage (Stufe 1 aus `doc/TODO-plan-vf-interactive.md`). Eine **Spec** beschreibt eine Kette in Domaenenwerten (`"links"`, `"winkel"`, `"aussen"`, ja/nein) statt in Menue-Codes; `vsp-journal` uebersetzt sie in ein Eingabe-Journal, `vsp-bau-aus-spec` spielt es per `vfl-journal-replay-start` durch den UNVERAENDERTEN `vf-linienzug-modus`. Damit traegt der fertige Block dasselbe `SSG_VF_EDIT`-Journal wie eine handgebaute Kette und bleibt per Doppelklick editier- und 2D/3D-konvertierbar - kein Journal-Konsument muss etwas von Specs wissen. Wichtige Regeln, die der Uebersetzer kennt: der Menue-Code haengt am Frame (erste Sektion 4 Optionen ohne GF-Bogen, danach 5), `hz` steht nur beim ERSTEN Segment im Journal, und `erst-hor` unterscheidet den horizontalen Erstkoerper einer VF-Einheit (Separator-/Endpunktfragen vorab) vom gewinkelten (keine Frage). Fehlerverhalten: fail fast - `vsp-pruefen` sammelt ALLE Spec-Fehler, gebaut wird dann nichts (nichts wird geraten). Ergebnis-Record je Kette mit `status` (`executed`/`warnung`/`desync`/`abbruch`/`spec-fehler`), Block/Handle/Einfuegepunkt/Attributen, `prompts` (muss 0 sein), `glieder_soll` vs. `glieder_ist` (Desync-Detektor, der auch bei aufgehender Queue greift) und den gesammelten `meldungen`. Headless-Rahmen `vsp-headless-an`/`-aus` (Save/Restore von `*vfl-headless*`, `*vfl-wizard-mode*`, `*ssg-gui-aus*` + zaehlende Eingabe-Stubs). Spec-Datei flach als JSON (`ssg-load-json` liest zeilenweise), erzeugt von `lib/vf_spec_export.py`. Gegenstueck/Referenz in Python: derselbe Emitter, gegen die 5 echten HundM-Ketten rundlaufgeprueft | +| `VarioFoerderer.lsp` (Wrapper fuer `vf_core.lsp`/`vf_standard.lsp`/`vf_etage.lsp`/`vf_linienzug.lsp`) | FOERDERANLAGE, VARIOFOERDERER_EDIT, VARIOFOERDERER_NACHRUESTEN, Vario_Kette_Merge, VF_SEKTION_RESTORE, VF_WIZARD_AN, VF_WIZARD_AUS | Vario-Foerderanlagen Generator (Typen Standard/Etage/Linienzug); Standard- UND Etage-Typ: Werteingabe per DCL-Dialog, inkl. der vorgelagerten Eingabemodus-Wahl 3D-Linie/Werte manuell (`vfc_eingabemodus`, `vfc-dialog-eingabemodus-waehlen`) - beide Modi laufen seitdem in denselben Basis-/Winkel-Dialog, der Konsolen-Pfad ist nur noch Rueckfall bei abgeschalteter GUI/anderer Sprache. Der Dialog-Ablauf ist zentral in vf_core (`vf-dialog-ablauf`/`vf-dialog-berechnen-einfuegen`, parametrisiert ueber Typ-Registry `*vf-typ-registry*` + Flag `horizontal-p`); `vfs-standard-dialog-ablauf`/`vfe-etage-dialog-ablauf` sind nur noch duenne Wrapper darauf. Beide nutzen dieselben Dialoge `vfs-dialog-eingabe-basis`/`vfs-dialog-winkel-verteilung` und den gemeinsamen L_GF/L_VF-Solve `vf-winkel-solve` (vf_core; berechne-alle-winkel/berechne-winkel-etage teilen ihn); Doppelklick auf VF_n-Block ruft VARIOFOERDERER_EDIT, das per SSG_VF_EDIT-XDATA-Marker ("standard"/"etage"/"linienzug") in den passenden Neuaufbau-Zweig dispatcht (`vfe-edit-ent` fuer Etage, `vfl-edit-ent` fuer Linienzug); Altbestand-Bloecke ohne Marker werden abgewiesen. Linienzug (Modus 1, `vf-linienzug-modus`) zeichnet jede interaktive Eingabe in einem Journal auf (`vfl-in-point`/`-string`/`-real`/`-int`) und schreibt es als XDATA auf den fertigen `VF_n`-Block; ein Abbruch (ESC) loescht dadurch nichts mehr, sondern wickelt die bis dahin gebaute Teil-Geometrie ebenso zu einem Block (`vfl-modus-abbruch-sichern`) - sie bleibt stehen und ist sofort per Doppelklick weiter editierbar/fortsetzbar. Der komplette Linienzug-Hauptstrang (Menue-Fragen von AS-Element bis Kettenende, inkl. GF-Bogen/Vario-Kurve/VF-Einheit-Fortsetzung) laeuft per Default ueber DCL-Dialoge statt Konsole (`*vfl-wizard-mode*`, Befehle VF_WIZARD_AN/VF_WIZARD_AUS): `vfl-in-string`/`-int`/`-real` zeigen bei aktivem Wizard-Modus generische Dialoge aus `dcl/vf_linienzug_wizard.dcl` (Zahl-Eingabe bzw. Mehrfachauswahl aus `vfl-menu`/`vfl-menu-int`) statt `getstring`/`getint`/`getreal` aufzurufen; die ORIGINALEN Konsolen-Fragen (princ + get*, de/en ueber ssg-text) bleiben unveraendert im Code (Fallback bei Fehlern via `vl-catch-all-apply`, siehe `doc/variofoerderer.md`). `vfl-edit-ent` liest das Journal, zeigt die Sektionen ueber einen DCL-Dialog (`vfl-dlg-position`, `dcl/vfl_edit.dcl`) an, kuerzt bei Bedarf auf eine gewaehlte Sektion (stummer Replay der behaltenen Eingaben, umgeht den Wizard-Zweig komplett) und baut danach interaktiv weiter. Linienzug Modus 2 (Pfad+Zielhoehe-Solver, `vf-linienzug-modus2`) nutzt dieselben Wrapper PLUS die neue Objektauswahl-Wrapper-Funktion `vfl-in-selection` (Pfad-Objekte als stabile Entity-Handles journalisiert, neue Journal-Art `"OBJS"`) und schreibt sein Journal unter dem eigenen XDATA-Marker `"linienzug2"`; Doppelklick baut hier NICHT abschnittsweise, sondern per vollem Reset 1:1 aus dem Journal neu auf (`vfl-edit-ent2`, dispatcht aus `vfl-edit-ent` per Marker) - Modus 3 (Vorwaerts-Nachbau) bleibt bewusst reine Konsole (im Code selbst als Legacy/"wird umgebaut" markiert). `Vario_Kette_Merge` (in vf_linienzug.lsp) fuehrt mehrere einzeln gebaute VF_n-Bloecke ab einem gewaehlten Start-Baustein ueber die reale KS_AUS->KS_EIN-Nachbarschaft zu einem Gesamt-Block zusammen (aggregierte Attribute, Luecken-Erkennung). Die interaktiven Editier-Pfade (`vfl-edit-ent` Sektions-Zweig, `vfl-edit-ent2`) rufen den Neuaufbau BEWUSST direkt auf, NICHT ueber `vl-catch-all-apply`: der alte Block ist beim Neuaufbau schon per `entdel` weg, und ein Catch faengt den Abbruch VOR `*error*` ab - der Abbruch-Wickler `vfl-modus-abbruch-sichern` kaeme nie zum Zug und die ganze Kette bliebe als lose Einzelteile ohne Block liegen (Fehlerbild "VF-Block aufgebrochen"). Nur der nicht-interaktive Batch-Konverter `vfl-konvertiere-ent` behaelt den Catch (die Batch-Schleife muss weiterlaufen) und meldet den betroffenen Block. Die Abbruch-Handler setzen zusaetzlich `*ssg-ils-dim*` zurueck, weil der Aufrufer, der das sonst tut, nach einem Abbruch nicht mehr erreicht wird. Recovery-Pfad fuer bereits aufgebrochene Ketten: `VF_SEKTION_RESTORE` (vf_linienzug.lsp) setzt das Ketten-Journal aus der XDATA der losen Objekte zusammen - `SSG_VF_EDIT_SEG` traegt pro Entity den Journal-Abschnitt DER ITERATION, in der es entstand (indiziert mit dem ersten Glied-Index darin; eine VF-Einheit mit eingebetteten Vario-Kurven erzeugt mehrere Glieder in einer Iteration), `SSG_VF_EDIT_PRE` auf den Entities des ersten Gliedes die Praeambel (Startpunkt/Starthoehe/AS). Die `.dbg`-Dateien sind reine AUSGABE und werden von keinem Befehl gelesen (dbgopen legt sie bei jedem Lauf neu an) - Wiederherstellungs-Daten gehoeren in die XDATA. `vf_konstanten.lsp` (von vf_core.lsp vor den drei Submodulen geladen) sammelt alle Laengen-/Winkel-/Toleranz-Magic-Numbers aus dem VarioFoerderer-Cluster als dokumentierte `*vfk-*`-Konstanten. Die Migration ist abgeschlossen: vf_core/vf_standard/vf_etage/vf_linienzug referenzieren durchgaengig die zentralen Konstanten statt roher Literale (Winkel, Stations-/Separator-Laengen, Mindestlaengen, AS/ES-Fallback-Versaetze, Text-/Label-Masse) - Ausnahmen sind in `vf_konstanten.lsp` selbst dokumentiert (Werte mit fuehrender Config-Anbindung via `ssg-cfg-or`, itoa-/Integer-Kontexte, Blocknamen-Strings, die 2000.0-mm/m-Umrechnung). `*vfk-gf-bogen-winkel*` (30/60/90, GF-Bogen + Vario-Kurve) und `*vfk-as-es-winkel*` ("30"/"90", AS/ES; bewusst STRING, da 2-wertig und im Journal als STR gefuehrt) sind Beispiele; vf_linienzug hat fuer beide einen Fallback, falls vf_konstanten nicht geladen wurde. **Nicht-interaktiver Betrieb**: `*vfl-headless*` (+ `vfl-headless-p`/`-abbruch`/`-notausgang`, Diagnose `*vfl-headless-fehler*`, optionaler Antwort-Hook `*vfl-headless-antwort-fn*`) verbietet jede Live-Eingabe - eine erschoepfte Replay-Queue ist damit ein harter Abbruch MIT Fundstelle (Art der Eingabe, Glied- und Eingabe-Nummer) statt eines stillen Rueckfalls auf `getpoint`/`getreal`; eingebaut in `vfl-in-value`/`-value-p`/`-selection`/`-abstand`. Die Bau-Pfade melden abgewiesene Sektionen ueber `vfl-meldung` (sammelt nach `*vfl-meldungen*` + `dbgmsg`, `alert` nur bei erlaubter GUI, sonst `princ`) statt per blockierendem `alert`; reine Interaktiv-Alerts (fehlendes DCL, "nicht editierbar", "kein Journal") bleiben `alert`. Genutzt von `tests/test_hm_recformat.lsp` (Replay der echten Aufzeichnungen) und von `vf_spec.lsp`; Fahrplan in `doc/todo/plan-vf-interactive.md`. Details/Entscheidungsbaum: `doc/variofoerderer.md` | +| `vf_spec.lsp` (von vf_core.lsp NACH vf_linienzug.lsp geladen) | VF_SPEC_BAU | Linienzug-Ketten aus **Eingabedaten** bauen, ohne Dialog und ohne Konsolenfrage (Stufe 1 aus `doc/todo/plan-vf-interactive.md`). Eine **Spec** beschreibt eine Kette in Domaenenwerten (`"links"`, `"winkel"`, `"aussen"`, ja/nein) statt in Menue-Codes; `vsp-journal` uebersetzt sie in ein Eingabe-Journal, `vsp-bau-aus-spec` spielt es per `vfl-journal-replay-start` durch den UNVERAENDERTEN `vf-linienzug-modus` (Replay-Kern `vsp-bau-journal` - nimmt auch ein unveraendert uebernommenes, aufgezeichnetes Journal, z.B. aus `dxf_anlage_export.py`). Damit traegt der fertige Block dasselbe `SSG_VF_EDIT`-Journal wie eine handgebaute Kette und bleibt per Doppelklick editier- und 2D/3D-konvertierbar - kein Journal-Konsument muss etwas von Specs wissen. Wichtige Regeln, die der Uebersetzer kennt: der Menue-Code haengt am Frame (erste Sektion 4 Optionen ohne GF-Bogen, danach 5), `hz` steht nur beim ERSTEN Segment im Journal, und `erst-hor` unterscheidet den horizontalen Erstkoerper einer VF-Einheit (Separator-/Endpunktfragen vorab) vom gewinkelten (keine Frage). Fehlerverhalten: fail fast - `vsp-pruefen` sammelt ALLE Spec-Fehler, gebaut wird dann nichts (nichts wird geraten). Ergebnis-Record je Kette mit `status` (`executed`/`warnung`/`desync`/`abbruch`/`spec-fehler`), Block/Handle/Einfuegepunkt/Attributen, `prompts` (muss 0 sein), `glieder_soll` vs. `glieder_ist` (Desync-Detektor, der auch bei aufgehender Queue greift) und den gesammelten `meldungen`. Headless-Rahmen `vsp-headless-an`/`-aus` (Save/Restore von `*vfl-headless*`, `*vfl-wizard-mode*`, `*ssg-gui-aus*` + zaehlende Eingabe-Stubs). Spec-Datei flach als JSON (`ssg-load-json` liest zeilenweise), erzeugt von `lib/vf_spec_export.py`. Gegenstueck/Referenz in Python: derselbe Emitter, gegen die 5 echten HundM-Ketten rundlaufgeprueft | | `Gefaellestrecke.lsp` | GEFAELLESTRECKE, GEFAELLESTRECKE_EDIT | Gefaelle-Foerderanlage (AUS -> Staustrecke skaliert -> Separator -> EIN); Modus 1 per DCL-Dialog (Einfuegehoehe, Element-Winkel 30/90); Doppelklick auf GF_n-Block ruft GEFAELLESTRECKE_EDIT (Dialog vorbelegt, Neuaufbau) | | `export.lsp` | EXPORTSIVAS, EXPORTCSV, OMNI_UPDATE_ATTRIBS, CSV_EXPORT_KONFIG | JSON-Sammlung und Python-Export (Omniflo-Merkmale/Sum-Zeile via lib/export_csv.py). **Gefrorene Layer**: `csv:collect-export-blocks` laesst Bloecke auf gefrorenen Layern aus (ausgeschaltete Layer zaehlen NICHT), Schalter `*csv-gefrorene-ausschliessen*` (Standard an, Toggle im Dialog CSV_EXPORT_KONFIG). Die Hilfsobjekte eines gefrorenen VF_n/GF_n (Separator-/Element-Kopien, Einheiten-/AS-ES-Marker) liegen auf eigenen Layern und werden darum ueber `*csv-ausschluss-enames*` mit ausgelassen (gesetzt in `csv:export-vorbereiten` per `csv:hilfskopien-ausschluss`); erzeugt und nummeriert werden sie trotzdem, damit IDs stabil bleiben. DrawPlanquadrate nutzt dieselbe Sammlung und folgt dem Filter | | `OmniModulInsert.lsp` | OMNI_LOAD, OMNI_APB_*, OMNI_W*_*, OMNI_AP60/AP110, OMNI_TEF_*, OMNI_TV_*, OMNI_APBW_*, OMNI_LADESCHLEIFE_AP110, OMNI_EDIT | Omniflo-Komponenten: Boegen, Weichen, Verbinder, Transferwagen, Ladeschleifen (`data/json/omniflo_ladeschleifen.json`, per `ssg-json-laden`, da die Beschreibungen Kommas enthalten; Blockdatei darf `.dwg` statt `.dxf` sein), Edit-Dialog | @@ -170,7 +174,7 @@ Werden via `bin/setenv.bat` gesetzt. Wichtigste: - Die **Farbe** wirkt nur bei der Neuanlage des Layers (`ssg-make-layer` laesst bestehende Layer unveraendert, damit Kundenzeichnungen nicht umgefaerbt werden). Layer, die eine Blockdatei aus `data/ils` selbst mitbringt (z.B. `ILS_SP`/`_3D_ILS_SP`), legt BricsCAD schon beim Einfuegen des Blocks mit der Farbe **aus der DWG** an - dort greift die cfg-Farbe nicht, wohl aber der Layername des Wrappers. Fuer Layer ohne Baugruppen-Key gibt es zusaetzlich den Abschnitt `[farben]` (Layername = Farbe), gelesen von `ssg-layer-farbe-fuer-name`. - Komponentendaten (Masse, Sivas-IDs) liegen als JSON in `data/json/` - Sprache im Code und Kommentaren: Deutsch -- **Benutzermeldungen sind ZWEISPRACHIG (de/en)**: Jede an den Anwender gerichtete Meldung (`princ`/`alert`/`getstring`/`getint`/`getreal`/`getkword`-Prompts, auch per `strcat` zusammengesetzte) laeuft ueber `(ssg-text "key")` bzw. `(ssg-textf "key" (list args))`. Die Texte stehen in `lang/de_DE.json` UND `lang/en_GB.json` unter `"texts"` und muessen key-fuer-key synchron bleiben (Paritaets-Check vor dem Commit). Konventionen: Key-Prefix je Modul (`vfc-` vf_core, `gf-` Gefaellestrecke), geteilte Prompts ohne Prefix (`prompt-wahl-1-2` usw.) - vor dem Anlegen eines neuen Keys erst die JSONs nach dem deutschen String grep'en und wiederverwenden. Sonderzeichen wie Grad `(chr 176)` / Delta `(chr 916)` als `%n`-Platzhalter uebergeben, nicht in die (ASCII-)JSON einbetten. **Untranslatiert bleiben** (bewusst): Bracket-Diagnosen (`[vf_core]` o.ae.), die VOR dem garantierten Laden von `ssg_lang.lsp` feuern (Bootstrap-Fallbacks am Dateikopf - `ssg-text` existiert dort noch nicht), Lade-Banner (`>>> ... geladen`), reine `====`/`---`-Trenner und reine Entwickler-Diagnosebefehle (nicht im Menue). Details und Entscheidungsbaum: `doc/translate.md`. +- **Benutzermeldungen sind ZWEISPRACHIG (de/en)**: Jede an den Anwender gerichtete Meldung (`princ`/`alert`/`getstring`/`getint`/`getreal`/`getkword`-Prompts, auch per `strcat` zusammengesetzte) laeuft ueber `(ssg-text "key")` bzw. `(ssg-textf "key" (list args))`. Die Texte stehen in `lang/de_DE.json` UND `lang/en_GB.json` unter `"texts"` und muessen key-fuer-key synchron bleiben (Paritaets-Check vor dem Commit). Konventionen: Key-Prefix je Modul (`vfc-` vf_core, `gf-` Gefaellestrecke), geteilte Prompts ohne Prefix (`prompt-wahl-1-2` usw.) - vor dem Anlegen eines neuen Keys erst die JSONs nach dem deutschen String grep'en und wiederverwenden. Sonderzeichen wie Grad `(chr 176)` / Delta `(chr 916)` als `%n`-Platzhalter uebergeben, nicht in die (ASCII-)JSON einbetten. **Untranslatiert bleiben** (bewusst): Bracket-Diagnosen (`[vf_core]` o.ae.), die VOR dem garantierten Laden von `ssg_lang.lsp` feuern (Bootstrap-Fallbacks am Dateikopf - `ssg-text` existiert dort noch nicht), Lade-Banner (`>>> ... geladen`), reine `====`/`---`-Trenner und reine Entwickler-Diagnosebefehle (nicht im Menue). Details und Entscheidungsbaum: `doc/plan/translate.md`. - **Subagenten** sollten von Claude nur mit Rückfrage an den Nutzer gestartet werden. Dabei sollten wenn möglich die leichteren Modelle gewählt werden, da es sich dabei in der Regel um Code recherche handelt. ## Python-Bibliotheken @@ -190,7 +194,8 @@ Abhaengigkeiten: `ezdxf`, Standard-Library. Benoetigen Umgebungsvariablen (`DXFM | `vf_journal_export.py` | Liest die Eingabe-Journale der `VF_n`-Bloecke (XDATA-App `SSG_VF_EDIT`, Marker `"linienzug"`) aus einer Zeichnung und schreibt sie als Frage-Antwort-Protokoll im Schema von `tests/testdata/linienzug_tests.json` (Gegenstueck zu `vfl-entry->string`; kein ezdxf, reine Gruppencode-Lesung). Optional `--csv ` fuer Strecken-ID und Erwartungswerte je Kette. Aufruf: `python vf_journal_export.py [--csv ...] [--kein-startpunkt-fix]` — erzeugt `tests/testdata/hm_recformat.json` aus `data/polylines.dxf` (die Aufzeichnung, aus der `tests/testdata/hundm05.json` als Spec abgeleitet wird) | | `dxf_vf_abbild.py` | Abbild der `VF_`/`GF_`-Wrapperbloecke einer Testzeichnung: Attribute UND Bausteinzusammensetzung je Kette, Ketten ueber den Einfuegepunkt der Spec zugeordnet (der Blockname taugt nicht - die VF-Nummer beginnt in jeder Zeichnung neu bei 1). Regressionsnetz fuer Umbauten am Bau-Ablauf: `--referenz` vergleicht gegen `tests/reference/hundm05_attribute.json` (Exit 1 bei Abweichung), `--schreibe-referenz` frischt sie nach einem GEPRUEFTEN Lauf auf. Faengt genau das, was der Ergebnis-Record nicht sieht - eine vertauschte `vfl-acc-*`-Reihenfolge (Komma-Listen `L_VF_m`/`L_GF_m`/`GF_WINKEL`, in der Zeichnung unsichtbar, im Sivas-Export falsch) und eine falsche Blockvariante. Liest ASCII-DXF streamend; binaere DXF kann `dxf_scan_components.py`. Genutzt von `tests/test_vf_geometrie.py` | | `vf_spec_export.py` | Uebersetzt ein Eingabe-Journal in eine **Spec** und zurueck (`spec_aus_journal` / `journal_aus_spec`) - Gegenstueck zu `vsp-journal` in `Lisp/vf_spec.lsp`, beide muessen dieselbe Tokenfolge liefern. Der Rundlauf `journal_aus_spec(spec_aus_journal(tok)) == tok` laeuft in `tests/test_vf_spec.py` gegen die 5 echten HundM-Ketten und beweist, dass die Spec das Journal verlustfrei abbildet. Schreibt das flache Spec-JSON (vom LISP-Parser lesbar). Objekte der Ziel-Datei, die keine Kette beschreiben (von Hand ergaenzte Anlagenteile mit `function`, z.B. Kreisel), werden beim Neuschreiben unveraendert ans Dateiende uebernommen - sonst waeren sie beim naechsten Lauf weg. Aufruf: `python vf_spec_export.py ` — erzeugt `tests/testdata/hundm05.json` (Spec, Eingabe fuer `TEST_HUNDM05`/`VF_SPEC_BAU`) aus `tests/testdata/hm_recformat.json` (Aufzeichnung) | -| `dxf_scan_components.py` | Erkennt ILS-/Omniflo-Komponenten in einer **fremden** Projektzeichnung (ohne unsere Wrapper-Bloecke) und schreibt sie im JSON-Schema aus `tests/testdata/object_data.md` — Grundlage fuer Nachbau-Testfaelle wie `tests/testdata/mubea.json`. Liest binaere DXF im GB-Bereich streamend per mmap (ezdxf-DOM waere zu gross), loest INSERTs inkl. OCS/Extrusionsrichtung rekursiv in Weltkoordinaten auf, paart AN8/SP8 zu Kreiseln und Antrieb/Umlenkung zu Foerderern. Aufruf: `python dxf_scan_components.py [--fenster X0 Y0 X1 Y1]` — ohne `--fenster` liefert eine Anlagenzeichnung leicht ueber 1300 Eintraege, zuviel fuer einen Testlauf | +| `dxf_anlage_export.py` | Gegenstueck zum Scanner: liest eine mit unseren Makros GEBAUTE (und von Hand nachbearbeitete) Anlage aus der Zeichnung zurueck in Nachbau-Testdaten - je Wrapper genau die Parameter, die auch dessen Edit-Befehl liest (KREISEL_n: Einfuegepunkt, `abstand` exakt aus der Blockdefinition, `drehrichtung` aus DREHRICHTUNG; GF_n: SSG_GF_EDIT + SEITE_AS/ES, endet die GF auf einer Kreisel-Kette, wird dessen Hoehe `hoehe_ende_mm`; VF_n `standard`/`etage`: mubea-Schema wie `vfs-edit-basis-lesen`; VF_n `linienzug`: Journal UNVERAENDERT als `"typ": "linienzug_journal"` - bewusst keine Spec, weil der Bau-Dialog neuer ist als die Spec-Grammatik; uebrige Bloecke als Einzel-Insert ohne `_2D`/`_3D`-Suffix). Reihenfolge Kreisel, VF, GF, Einzelbloecke (VF vor GF wie mubea). Aufruf: `python dxf_anlage_export.py [--projekt NAME]` — erzeugt `tests/testdata/dematic_arvato.json` aus `Dematic_TST.dxf`. Test `tests/test_dxf_anlage_export.py` | +| `dxf_scan_components.py` | Erkennt ILS-/Omniflo-Komponenten in einer **fremden** Projektzeichnung (ohne unsere Wrapper-Bloecke) und schreibt sie im JSON-Schema aus `tests/testdata/object_data.md` — Grundlage fuer Nachbau-Testfaelle wie `tests/testdata/mubea.json`. Liest binaere DXF im GB-Bereich streamend per mmap (ezdxf-DOM waere zu gross), loest INSERTs inkl. OCS/Extrusionsrichtung rekursiv in Weltkoordinaten auf, paart AN8/SP8 zu Kreiseln und Antrieb/Umlenkung zu Foerderern. Gefaellestrecken, die als 3D-LINE auf einem Staustrecken-Layer gezeichnet sind (`GF_LINIE_LAYER`, z.B. `6-SP`), werden in einem zweiten Lesedurchgang zu Modus-1-Eintraegen: Start/Ende werden auf die Ketten der anschliessenden Kreisel verlaengert (gezeichnet ist nur die Staustrecke; `deltaL` reicht von AS- bis ES-Ursprung), AS-Seite aus dem Ausschleuselement am oberen Ende; dieses und Separatoren auf der Achse entfallen als Einzel-Insert (die GF baut beide selbst). Sitzt der AS am UNTEREN Ende, ist es ein Steigfoerderer und wird nur gemeldet (`gf_eintraege_aus_linien`, Test `tests/test_dxf_scan_gf.py`). `--ohne-hoehen` setzt alle Z auf 0, `--kreisel-hoehe X Y Z` gibt einem Kreisel seine Hoehe - die daran haengenden GF bekommen daraus `start_mm.z`/`hoehe_ende_mm`, den Winkel rechnet der Treiber (`dematic_arvato:gf-geometrie`: atan ueber die HORIZONTALE Laenge, ES-Laenge UND -Abfall bei der Einlaufneigung gemessen und iteriert, Staustrecke daraus - die flache ES-Laenge liess steile GF ~144 mm vor der Kette enden - NICHT `gf-winkel-berechnen`, dessen asin bei steilen GF wie im Showroom ~50 statt ~37 Grad liefert; `dematic_arvato:gf-endhoehe-pruefen` meldet `warnung` bei > 5 mm Abweichung von HOEHE_BIS). Flache 2D-Plaene: `--flach` (waagrechte Staustrecke mit AS an genau einem Ende = GF, zerstueckelte Linien werden vereint, ES am unteren Ende wird verbraucht, AS/ES-Seite ohne Namensangabe aus der Lage zur Fahrtrichtung), `--kreisel-achse X1 Y1 X2 Y2` (Kreisel ohne AN8/SP8), Einzelteile auf einer Kreisel-Kette erben dessen Anwender-Hoehe (`sensor_hoehen_von_kreiseln`). Kreisel-Paarung mit Tausch-Nachbesserung auf minimale Abstandssumme (zwei Kreisel auf einer Achse). Genutzt fuer `tests/testdata/showroom_ils.json` (`TEST_SHOWROOM_ILS`). Aufruf: `python dxf_scan_components.py [--fenster X0 Y0 X1 Y1]` — ohne `--fenster` liefert eine Anlagenzeichnung leicht ueber 1300 Eintraege, zuviel fuer einen Testlauf | ### Entwickler-Tools (`lib/`) - kein ezdxf, reine Text-/AutoLISP-Verarbeitung @@ -206,7 +211,7 @@ Abhaengigkeiten: `ezdxf`, Standard-Library. Benoetigen Umgebungsvariablen (`DXFM ## Entwicklungsstand -Siehe `doc/Entwicklungsplan.md` fuer Meilensteinplanung und Aufwandsschaetzung. +Siehe `doc/plan/Entwicklungsplan.md` fuer Meilensteinplanung und Aufwandsschaetzung. Fertig (DONE): ILS Kreisel, ILS Eckrad, ILS Variofoerderer, ILS BTMT Be-/Entladung (Einfuegen), Omniflo Boegen, Omniflo Weichen. ## Debug diff --git a/Lisp/Gefaellestrecke.lsp b/Lisp/Gefaellestrecke.lsp index 162b30b..c03d27e 100644 --- a/Lisp/Gefaellestrecke.lsp +++ b/Lisp/Gefaellestrecke.lsp @@ -714,16 +714,161 @@ prev-hz ) +;; Dialog fuer Modus 3 (Linienzug), Teil 1: AUS-/EIN-Element Winkel+Seite - +;; ersetzt die vier Konsolenfragen (AUS-Winkel, AUS-Seite, EIN-Winkel, +;; EIN-Seite). Muss VOR der Segmentlaengen-Anpassung/Winkelberechnung +;; laufen (die braucht aus-dx/ein-dx aus vf-set-as-masse/-es-masse), darum +;; eigener, frueherer Dialog statt zusammen mit dem Winkel-Vorschlag. +;; prefill: Alist (("as-winkel" . "90"|"30") ("as-seite" . "links"|"rechts") +;; ("es-winkel" . ...) ("es-seite" . ...)) fuer den Edit-Nachbau, oder nil +;; fuer die Defaults (90/links) beim Neu-Bauen. +;; Rueckgabe: (as-winkel as-seite es-winkel es-seite) bei OK, 'ABBRUCH wenn +;; der Nutzer bewusst abbricht (Aufrufer bricht dann den GANZEN Befehl ab, +;; KEIN Rueckfall auf die Konsole), nil wenn der Dialog gar nicht gezeigt +;; werden konnte/durfte (GUI aus) - NUR dann Konsolen-Rueckfall. +(defun gf-dialog-linienzug-aus-ein (prefill / dcl-pfad dat ergebnis + dlg-aus-winkel dlg-aus dlg-ein-winkel dlg-ein + as-seite es-seite as-winkel es-winkel) + (if (or (not (car (atoms-family 1 '("SSG-GUI-P")))) (ssg-gui-p)) + (progn + (setq dcl-pfad (strcat (getenv "DXFM_DCL") "/gefaellestrecke.dcl")) + (setq dat (load_dialog dcl-pfad)) + (if (not (new_dialog "gefaellestrecke_linienzug" dat)) + (progn (if (and dat (>= dat 0)) (unload_dialog dat)) nil) + (progn + (set_tile "zusammenfassung" (ssg-text "gf-dlg-linienzug-vorlauf")) + (mode_tile "winkel" 1) + + (start_list "aus_winkel") + (add_list "90") + (add_list "30") + (end_list) + (set_tile "aus_winkel" + (if (and prefill (equal (cdr (assoc "as-winkel" prefill)) "30")) "1" "0")) + (start_list "aus_seite") + (add_list (ssg-text "gf-dlg-links")) + (add_list (ssg-text "gf-dlg-rechts")) + (end_list) + (set_tile "aus_seite" + (if (and prefill (equal (cdr (assoc "as-seite" prefill)) "rechts")) "1" "0")) + + (start_list "ein_winkel") + (add_list "90") + (add_list "30") + (end_list) + (set_tile "ein_winkel" + (if (and prefill (equal (cdr (assoc "es-winkel" prefill)) "30")) "1" "0")) + (start_list "ein_seite") + (add_list (ssg-text "gf-dlg-links")) + (add_list (ssg-text "gf-dlg-rechts")) + (end_list) + (set_tile "ein_seite" + (if (and prefill (equal (cdr (assoc "es-seite" prefill)) "rechts")) "1" "0")) + + (action_tile "accept" + (strcat + "(setq dlg-aus-winkel (get_tile \"aus_winkel\"))" + "(setq dlg-aus (get_tile \"aus_seite\"))" + "(setq dlg-ein-winkel (get_tile \"ein_winkel\"))" + "(setq dlg-ein (get_tile \"ein_seite\"))" + "(done_dialog 1)" + ) + ) + (action_tile "cancel" "(done_dialog 0)") + (setq ergebnis (start_dialog)) + (unload_dialog dat) + + (cond + ((= ergebnis 1) + (setq as-winkel (if (= dlg-aus-winkel "1") "30" "90")) + (setq as-seite (if (= dlg-aus "1") "rechts" "links")) + (setq es-winkel (if (= dlg-ein-winkel "1") "30" "90")) + (setq es-seite (if (= dlg-ein "1") "rechts" "links")) + (list as-winkel as-seite es-winkel es-seite) + ) + (t 'ABBRUCH) + ) + ) + ) + ) + nil + ) +) + +;; Dialog fuer Modus 3, Teil 2: Gefaellewinkel-Bestaetigung VOR der +;; iterativen AS/ES-Korrekturschleife (die braucht den vom Nutzer +;; gewaehlten/bestaetigten Ausgangswinkel, berechnet selbst aber nicht +;; interaktiv weiter) - ersetzt die "Berechneten Winkel verwenden?"-Frage +;; samt optionalem manuellem Winkel. Ersetzt zugleich die SPAETERE finale +;; Einfuegen-Bestaetigung: nach diesem OK baut der Aufrufer direkt, ohne +;; weitere Rueckfrage (analog "dialog-bestaetigt" bei Modus 1/2) - die +;; Zusammenfassung mit dem Vorschlag genuegt als Entscheidungsgrundlage, +;; eine zweite Bestaetigung NACH der (rein automatischen) Korrekturschleife +;; waere redundant. Das AS/ES-Auswahlpaar wird hier nur noch ANGEZEIGT +;; (disabled), nicht erneut editierbar - es ist schon in die Segmentlaengen +;; eingerechnet. +;; zusammenfassung-zeilen: Liste bereits fertig formatierter Strings. +;; Rueckgabe: Gefaellewinkel (Grad) bei OK, 'ABBRUCH wenn der Nutzer bewusst +;; abbricht (Aufrufer bricht dann den GANZEN Befehl ab, KEIN Rueckfall auf +;; die Konsole), nil wenn der Dialog gar nicht gezeigt werden konnte/durfte +;; (GUI aus) - NUR dann Konsolen-Rueckfall (inkl. der spaeteren finalen +;; Einfuegen-Bestaetigung, die in diesem Fall unveraendert bleibt). +(defun gf-dialog-linienzug-winkel (zusammenfassung-zeilen winkel-vorschlag + as-winkel as-seite es-winkel es-seite / + dcl-pfad dat ergebnis dlg-winkel grade) + (if (or (not (car (atoms-family 1 '("SSG-GUI-P")))) (ssg-gui-p)) + (progn + (setq dcl-pfad (strcat (getenv "DXFM_DCL") "/gefaellestrecke.dcl")) + (setq dat (load_dialog dcl-pfad)) + (if (not (new_dialog "gefaellestrecke_linienzug" dat)) + (progn (if (and dat (>= dat 0)) (unload_dialog dat)) nil) + (progn + (set_tile "zusammenfassung" (ssg-join zusammenfassung-zeilen " | ")) + + (start_list "aus_winkel") (add_list as-winkel) (end_list) + (set_tile "aus_winkel" "0") + (start_list "aus_seite") (add_list as-seite) (end_list) + (set_tile "aus_seite" "0") + (start_list "ein_winkel") (add_list es-winkel) (end_list) + (set_tile "ein_winkel" "0") + (start_list "ein_seite") (add_list es-seite) (end_list) + (set_tile "ein_seite" "0") + (mode_tile "aus_winkel" 1) (mode_tile "aus_seite" 1) + (mode_tile "ein_winkel" 1) (mode_tile "ein_seite" 1) + + (set_tile "winkel" (rtos winkel-vorschlag 2 3)) + + (action_tile "accept" + "(setq dlg-winkel (get_tile \"winkel\")) (done_dialog 1)") + (action_tile "cancel" "(done_dialog 0)") + (setq ergebnis (start_dialog)) + (unload_dialog dat) + + (cond + ((= ergebnis 1) + (setq grade (atof dlg-winkel)) + (if (<= grade 0) (setq grade winkel-vorschlag)) + grade + ) + (t 'ABBRUCH) + ) + ) + ) + ) + nil + ) +) + ;; Hauptfunktion Modus 3 (defun gf-linienzug-modus ( / ss n i j obj obj-liste kette segmente seg startpunkt endpunkt-ref deltaH grade antwort idx as-seite es-seite as-block es-block - gf-as-winkel gf-es-winkel + gf-as-winkel gf-es-winkel dlg-aus-ein dlg-winkel-erg entry-hz exit-hz first-idx last-idx neu-l aktuell-frame nach-aus-pt - gf-nummer last-ent + gf-nummer last-ent gf-lz-insert L-gf-str delta-l-gf laengen-liste first-l l-item bL90 bL60 bL30 bR90 bR60 bR30 ein-hz-as rad-h-as rad-v-as xu-entry @@ -787,29 +932,41 @@ (setq entry-hz (cadr (car segmente))) (setq exit-hz (gf-exit-hz segmente)) - ;; AUS-Element: 30/90 (vor Seite), dann Seite (vor Winkelberechnung, da die - ;; Laengenanpassung von der AS/ES-Masse abhaengt). Masse fuer Variante setzen. - (princ (ssg-text "vf-winkel-aus-header")) - (princ (ssg-text "vf-winkel-90")) - (princ (ssg-text "vf-winkel-30")) - (setq gf-as-winkel (vf-frage-element-winkel "vf-winkel-aus-header")) - (princ (ssg-text "gf-seite-aus-header")) - (princ (ssg-text "gf-seite-links")) - (princ (ssg-text "gf-seite-rechts")) - (setq antwort (getstring (ssg-text "prompt-wahl-1-2"))) - (setq as-seite (if (= antwort "2") "rechts" "links")) - (vf-set-as-masse gf-as-winkel as-seite) + ;; AUS-/EIN-Element: 30/90 + Seite, in EINEM Dialog (vor Winkelberechnung, + ;; da die Laengenanpassung von der AS/ES-Masse abhaengt). Rueckfall auf die + ;; alten Konsolenfragen, wenn der Dialog nicht gezeigt werden kann/darf. + (setq dlg-aus-ein (gf-dialog-linienzug-aus-ein nil)) + (if (eq dlg-aus-ein 'ABBRUCH) + (progn (princ (ssg-text "vfc-vorgang-abgebrochen")) (exit)) + ) + (if dlg-aus-ein + (setq gf-as-winkel (nth 0 dlg-aus-ein) + as-seite (nth 1 dlg-aus-ein) + gf-es-winkel (nth 2 dlg-aus-ein) + es-seite (nth 3 dlg-aus-ein)) + (progn + (princ (ssg-text "vf-winkel-aus-header")) + (princ (ssg-text "vf-winkel-90")) + (princ (ssg-text "vf-winkel-30")) + (setq gf-as-winkel (vf-frage-element-winkel "vf-winkel-aus-header")) + (princ (ssg-text "gf-seite-aus-header")) + (princ (ssg-text "gf-seite-links")) + (princ (ssg-text "gf-seite-rechts")) + (setq antwort (getstring (ssg-text "prompt-wahl-1-2"))) + (setq as-seite (if (= antwort "2") "rechts" "links")) - ;; EIN-Element: 30/90 + Seite - (princ (ssg-text "vf-winkel-ein-header")) - (princ (ssg-text "vf-winkel-90")) - (princ (ssg-text "vf-winkel-30")) - (setq gf-es-winkel (vf-frage-element-winkel "vf-winkel-ein-header")) - (princ (ssg-text "gf-seite-ein-header")) - (princ (ssg-text "gf-seite-links")) - (princ (ssg-text "gf-seite-rechts")) - (setq antwort (getstring (ssg-text "prompt-wahl-1-2"))) - (setq es-seite (if (= antwort "2") "rechts" "links")) + (princ (ssg-text "vf-winkel-ein-header")) + (princ (ssg-text "vf-winkel-90")) + (princ (ssg-text "vf-winkel-30")) + (setq gf-es-winkel (vf-frage-element-winkel "vf-winkel-ein-header")) + (princ (ssg-text "gf-seite-ein-header")) + (princ (ssg-text "gf-seite-links")) + (princ (ssg-text "gf-seite-rechts")) + (setq antwort (getstring (ssg-text "prompt-wahl-1-2"))) + (setq es-seite (if (= antwort "2") "rechts" "links")) + ) + ) + (vf-set-as-masse gf-as-winkel as-seite) (vf-set-es-masse gf-es-winkel es-seite) (setq as-block (strcat "AS_Element_" gf-as-winkel "_" as-seite)) @@ -881,14 +1038,30 @@ (princ (ssg-text "gf-status-berechne-winkel")) (setq grade (gf-winkel-berechnen segmente deltaH 1)) - ;; Manuelle Anpassung - (setq antwort - (getstring (ssg-textf "gf-prompt-winkel-verwenden" - (list (rtos grade 2 3) grad-zeichen)))) - (if (= (strcase antwort) "N") + ;; Manuelle Anpassung per Dialog (ersetzt zugleich die SPAETERE finale + ;; Einfuegen-Bestaetigung, siehe gf-dialog-linienzug-winkel) - Rueckfall + ;; auf die alten Konsolenfragen, wenn der Dialog nicht gezeigt werden + ;; kann/darf; dann bleibt auch die spaetere Einfuegen-Bestaetigung aktiv. + (setq dlg-winkel-erg + (gf-dialog-linienzug-winkel + (list (ssg-textf "gf-status-zus-segmente" (list (length segmente))) + (ssg-textf "gf-status-zus-deltah" (list delta-sym (rtos deltaH 2 2)))) + grade gf-as-winkel as-seite gf-es-winkel es-seite)) + (if (eq dlg-winkel-erg 'ABBRUCH) + (progn (princ (ssg-text "vfc-vorgang-abgebrochen")) (exit)) + ) + (if dlg-winkel-erg + (setq grade dlg-winkel-erg) (progn - (setq grade (getreal (ssg-text "gf-prompt-gefaellewinkel"))) - (if (or (null grade) (<= grade 0)) (setq grade 3.0)) + (setq antwort + (getstring (ssg-textf "gf-prompt-winkel-verwenden" + (list (rtos grade 2 3) grad-zeichen)))) + (if (= (strcase antwort) "N") + (progn + (setq grade (getreal (ssg-text "gf-prompt-gefaellewinkel"))) + (if (or (null grade) (<= grade 0)) (setq grade 3.0)) + ) + ) ) ) (setq grade (float grade)) @@ -953,8 +1126,9 @@ (princ (ssg-textf "gf-status-zus-ein-element" (list es-block))) (princ "\n=========================================") - ;; Bestaetigung - (setq antwort (getstring (ssg-text "gf-prompt-einfuegen"))) + ;; Bestaetigung - entfaellt, wenn der Dialog oben bereits bestaetigt hat + ;; (dlg-winkel-erg ist dann eine Zahl, kein nil/'ABBRUCH). + (setq antwort (if dlg-winkel-erg "1" (getstring (ssg-text "gf-prompt-einfuegen")))) (if (not (= antwort "2")) (progn ;; GF-Nummer und Entity-Grenze vor erster Einfuegung sichern. @@ -1004,15 +1178,20 @@ (- (sin rad-v-es)))) (setq sep-frame (make-frame-from-dir sep-endpunkt xu-exit)) - ;; EIN-Element: KS_EIN bleibt in X/Y auf dem Ketten-Pfad (sep-frame), - ;; seine eigene Achse verlaeuft damit trivial durch diesen Punkt. - ;; Die Z-Hoehe wird jedoch an KS_AUS gemessen, damit KS_AUS.Z exakt - ;; auf endpunkt-ref.Z landet (garantiert die gewuenschte DeltaH). + ;; EIN-Element: reine KS-zu-KS-Verkettung (wie bei Modus 1/2 in + ;; gefaellestrecke-einfuegen) statt insert-block-mixed-to-ks mit + ;; erzwungener Ziel-Z. sep-frame richtet KS_EIN des ES-Elements exakt + ;; am Ketten-Pfad (Position UND Neigung) aus; die Endhoehe ergibt sich + ;; danach von selbst aus KS_AUS, es wird nichts nachtraeglich + ;; verschoben. Zwei vorherige Versuche waren beide falsch: endpunkt-ref + ;; (Mausklick-Z, meist = Starthoehe) UND z-ref="KS_AUS" bei + ;; insert-block-mixed-to-ks (zwang den BLOCKAUSGANG statt -eingang auf + ;; die Zielhoehe, Rest-Versatz = Blockinnenhoehe KS_EIN<->KS_AUS, + ;; ~65mm). endpunkt-ref/der zweite Mausklick dient weiterhin nur zur + ;; groben deltaH-Schaetzung fuer den Winkel-Vorschlag im Dialog. + (setq es-aus-frame (insert-block-ks-to-ks es-block sep-frame)) (princ (ssg-textf "gf-status-ein-element-detail" - (list es-block (rtos (caddr endpunkt-ref) 2 1)))) - (setq es-aus-frame - (insert-block-mixed-to-ks es-block sep-frame - (caddr endpunkt-ref) "KS_EIN" "KS_AUS")) + (list es-block (rtos (caddr sep-endpunkt) 2 1)))) (princ (ssg-textf "gf-status-z-hoehe-ks-aus" (list (rtos (caddr (car es-aus-frame)) 2 1)))) ;; --- GF_N-Block erstellen --- @@ -1052,27 +1231,37 @@ ) ;; delta-l-gf bereits vor der Zusammenfassung berechnet - ;; Block mit allen Attributen erstellen. Geruest fuer Einzelmodul/ - ;; Geruestoption sind in diesem Linienzug-Ablauf (Modus 3, keine DCL- - ;; Eingabe) nicht abfragbar - bleiben auf Schema-Default. - (gf-block-erstellen - "gruppe" as-seite gf-nummer - (caddr startpunkt) - (caddr endpunkt-ref) - deltaH - delta-l-gf - L-gf-str - grade - ;; n-separator/n-scanner = 0: interne Ketten-Separatoren zaehlen nicht; - ;; die echte Anzahl setzt der Export aus platzierten Symbolen. - 0 - 0 - bL90 bL60 bL30 - bR90 bR60 bR30 - startpunkt last-ent nach-aus-pt entry-hz - (caddr (nth first-idx segmente)) - es-seite "0" "Schoenenberger Geruest" - gf-as-winkel gf-es-winkel) + ;; Block mit allen Attributen erstellen. HOEHE_BIS_mm kommt aus der + ;; TATSAECHLICH gebauten Endhoehe (es-aus-frame), nicht aus der + ;; geklickten endpunkt-ref.Z - sonst wiche das Attribut von der + ;; echten Geometrie ab (derselbe Bug wie bei der ES-Platzierung oben). + ;; Geruest fuer Einzelmodul/Geruestoption sind in diesem Linienzug- + ;; Ablauf (Modus 3, keine DCL-Eingabe) nicht abfragbar - bleiben auf + ;; Schema-Default. + (setq gf-lz-insert + (gf-block-erstellen + "gruppe" as-seite gf-nummer + (caddr startpunkt) + (caddr (car es-aus-frame)) + deltaH + delta-l-gf + L-gf-str + grade + ;; n-separator/n-scanner = 0: interne Ketten-Separatoren zaehlen nicht; + ;; die echte Anzahl setzt der Export aus platzierten Symbolen. + 0 + 0 + bL90 bL60 bL30 + bR90 bR60 bR30 + startpunkt last-ent nach-aus-pt entry-hz + (caddr (nth first-idx segmente)) + es-seite "0" "Schoenenberger Geruest" + gf-as-winkel gf-es-winkel)) + + ;; Segmentliste + Startpunkt fuer den Doppelklick-Nachbau sichern (siehe + ;; c:GEFAELLESTRECKE_EDIT) - ohne diese XDATA war eine per Linienzug + ;; gebaute Streckengruppe bisher nicht editierbar. + (gf-lz-xdata-schreiben gf-lz-insert segmente startpunkt) (princ "\n\n=========================================") (princ (ssg-text "gf-status-fertig")) @@ -1082,6 +1271,217 @@ ) ) +;; Doppelklick-Nachbau fuer eine per GEFAELLESTRECKE_LINIENZUG (Modus 3) +;; gebaute Streckengruppe - Gegenstueck zu gf-rebuild (Modus 1/2), aufgerufen +;; aus c:GEFAELLESTRECKE_EDIT. Anders als beim Neu-Bauen (gf-linienzug-modus) +;; sind die Segmentlaengen hier bereits FINAL (AS-/ES-dx und Separator- +;; Abzuege sind laengst eingerechnet, siehe gf-lz-xdata-schreiben) - die +;; Segmentlisten-Anpassungsschleife aus gf-linienzug-modus entfaellt darum +;; komplett, nur AS-/ES-Winkel+Seite und der Gefaellewinkel bleiben ueber +;; dieselben zwei Dialoge editierbar (gf-dialog-linienzug-aus-ein/-winkel, +;; diesmal mit Prefill aus den Block-Attributen). Baut am gleichen +;; startpunkt neu, loescht den alten Block NACH erfolgreichem Neuaufbau. +;; segmente/startpunkt: aus gf-lz-xdata-lesen. alt-ent: der zu ersetzende +;; GF_n-Block. prefill: Alist wie gf-dialog-linienzug-aus-ein (AS/ES aus den +;; aktuellen Attributen des Blocks). +(defun gf-linienzug-rebuild (segmente startpunkt alt-ent prefill / + deltaH entry-hz exit-hz as-seite es-seite + gf-as-winkel gf-es-winkel as-block es-block + dlg-aus-ein dlg-winkel-erg grade + first-idx j seg end-z endpunkt-ref delta-l-gf + aktuell-frame nach-aus-pt ein-hz-as rad-h-as xu-entry + sep-endpunkt rad-h-es rad-v-es xu-exit sep-frame es-aus-frame + gf-nummer last-ent gf-lz-insert + L-gf-str laengen-liste first-l l-item + bL90 bL60 bL30 bR90 bR60 bR30 + rad-v-korr ein-hz-as-k rad-h-as-k xu-as-meas + rad-h-es-k xu-es-meas dz-as-ist dz-es-ist + dH-korr grade-prev iter) + (if (or (null segmente) (< (length segmente) 1)) + (progn (alert (ssg-text "gf-alert-keine-kette")) (exit)) + ) + + (setq entry-hz (cadr (car segmente))) + (setq exit-hz (gf-exit-hz segmente)) + + ;; AUS-/EIN-Element, vorbelegt aus den aktuellen Attributen + (setq dlg-aus-ein (gf-dialog-linienzug-aus-ein prefill)) + (if (eq dlg-aus-ein 'ABBRUCH) + (progn (princ (ssg-text "vfc-vorgang-abgebrochen")) (exit)) + ) + (if (null dlg-aus-ein) + (progn (alert (ssg-text "gf-alert-lz-edit-kein-dialog")) (exit)) + ) + (setq gf-as-winkel (nth 0 dlg-aus-ein) + as-seite (nth 1 dlg-aus-ein) + gf-es-winkel (nth 2 dlg-aus-ein) + es-seite (nth 3 dlg-aus-ein)) + (vf-set-as-masse gf-as-winkel as-seite) + (vf-set-es-masse gf-es-winkel es-seite) + + (setq as-block (strcat "AS_Element_" gf-as-winkel "_" as-seite)) + (setq es-block (strcat "ES_Element_" gf-es-winkel "_" es-seite)) + + ;; Index des ersten "Linie"-Segments (fuer die Label-Laenge) - KEINE + ;; Laengenanpassung, die Segmentlaengen sind bereits final. + (setq first-idx -1 j 0) + (while (and (< j (length segmente)) (< first-idx 0)) + (if (= (car (nth j segmente)) "Linie") (setq first-idx j)) + (setq j (1+ j)) + ) + (if (< first-idx 0) (setq first-idx 0)) + + ;; Ziel-Endhoehe aus dem Z-Attribut HOEHE_BIS_mm des alten Blocks (X/Y des + ;; wirklichen Endpunkts wird erst NACH dem Bau aus der Geometrie selbst + ;; bestimmt, siehe unten - sie ist nicht separat in der XDATA gespeichert + ;; und nur fuer die Zusammenfassung/das Label relevant, nicht fuer den Bau + ;; selbst, der komplett der Segmentliste folgt). + (setq end-z (atof (cond ((cdr (assoc "HOEHE_BIS_mm" (ssg-attrib-read alt-ent)))) ("0")))) + (setq deltaH (abs (- (caddr startpunkt) end-z))) + + (princ (ssg-text "gf-status-berechne-winkel")) + (setq grade (gf-winkel-berechnen segmente deltaH 1)) + + (setq dlg-winkel-erg + (gf-dialog-linienzug-winkel + (list (ssg-textf "gf-status-zus-segmente" (list (length segmente))) + (ssg-textf "gf-status-zus-deltah" (list delta-sym (rtos deltaH 2 2)))) + grade gf-as-winkel as-seite gf-es-winkel es-seite)) + (if (eq dlg-winkel-erg 'ABBRUCH) + (progn (princ (ssg-text "vfc-vorgang-abgebrochen")) (exit)) + ) + (if (null dlg-winkel-erg) + (progn (alert (ssg-text "gf-alert-lz-edit-kein-dialog")) (exit)) + ) + (setq grade (float dlg-winkel-erg)) + + ;; Winkelkorrektur (identisch zu gf-linienzug-modus) + (if (and *gf-L-ohne-as-es* (> *gf-L-ohne-as-es* 0.1)) + (progn + (princ (ssg-text "gf-status-messe-absenkung")) + (setq grade-prev -999.0 iter 0) + (while (and (< iter 4) (> (abs (- grade grade-prev)) 0.001)) + (setq grade-prev grade) + (setq rad-v-korr (* grade (/ pi 180.0))) + (setq ein-hz-as-k (- entry-hz + (vf-element-plan-turn (strcat "AS_Element_" gf-as-winkel "_" as-seite)))) + (setq rad-h-as-k (* (float ein-hz-as-k) (/ pi 180.0))) + (setq xu-as-meas (list (cos rad-h-as-k) (sin rad-h-as-k) 0.0)) + (setq rad-h-es-k (* (float exit-hz) (/ pi 180.0))) + (setq xu-es-meas (list (* (cos rad-h-es-k)(cos rad-v-korr)) + (* (sin rad-h-es-k)(cos rad-v-korr)) + (- (sin rad-v-korr)))) + (setq dz-as-ist (gf-messe-dz-block as-block xu-as-meas)) + (setq dz-es-ist (gf-messe-dz-block es-block xu-es-meas)) + (setq dH-korr (- (float deltaH) dz-as-ist dz-es-ist (float *gf-sum-dz-bogen*))) + (if (and (> dH-korr 0.0) (< (/ dH-korr *gf-L-ohne-as-es*) 1.0)) + (setq grade (* (asin (/ dH-korr *gf-L-ohne-as-es*)) (/ 180.0 pi))) + (progn + (princ (ssg-text "gf-warnung-korrektur-bereich")) + (setq grade-prev grade) + ) + ) + (setq iter (1+ iter)) + ) + (princ (ssg-textf "gf-status-korrigierter-winkel" + (list (rtos grade 2 3) grad-zeichen (itoa iter)))) + ) + ) + ;; Alten Block ERST NACH erfolgreichem Neuaufbau loeschen (siehe unten) - + ;; die GF-Nummer/last-ent-Grenze muss aber VOR dem Neu-Einfuegen stehen. + (setq gf-nummer (gf-next-number)) + (setq last-ent (ssg-lastent-ohne-attribute)) + + (setq ein-hz-as (- entry-hz + (vf-element-plan-turn (strcat "AS_Element_" gf-as-winkel "_" as-seite)))) + (setq rad-h-as (* (float ein-hz-as) (/ pi 180.0))) + (setq xu-entry (list (cos rad-h-as) (sin rad-h-as) 0.0)) + (setq aktuell-frame + (insert-block-mixed-to-ks as-block (make-frame-from-dir startpunkt xu-entry) + (caddr startpunkt) nil "KS_EIN")) + (setq nach-aus-pt (car aktuell-frame)) + + (setq aktuell-frame (gf-linienzug-einfuegen segmente grade aktuell-frame)) + + (setq sep-endpunkt + (gf-insert-hz-with-ks "Staustrecke_Separator_SP_300_mm" + (car aktuell-frame) exit-hz grade 300 0)) + (setq rad-h-es (* (float exit-hz) (/ pi 180.0))) + (setq rad-v-es (* (float grade) (/ pi 180.0))) + (setq xu-exit (list (* (cos rad-h-es)(cos rad-v-es)) + (* (sin rad-h-es)(cos rad-v-es)) + (- (sin rad-v-es)))) + (setq sep-frame (make-frame-from-dir sep-endpunkt xu-exit)) + ;; Reine KS-zu-KS-Verkettung wie in gf-linienzug-modus (siehe dortiger + ;; Kommentar) - end-z (gespeicherte Soll-Endhoehe) wird nur zur + ;; deltaH-Schaetzung fuer die Winkelberechnung oben gebraucht, nicht hier. + (setq es-aus-frame (insert-block-ks-to-ks es-block sep-frame)) + + ;; Echter Endpunkt (fuer DELTA_L-Attribut/Label) erst jetzt aus der + ;; gebauten Geometrie bekannt - KS_AUS des ES-Elements. + (setq endpunkt-ref (car es-aus-frame)) + (setq delta-l-gf + (sqrt (+ (expt (- (car endpunkt-ref) (car startpunkt)) 2) + (expt (- (cadr endpunkt-ref) (cadr startpunkt)) 2)))) + + (setq laengen-liste '() first-l t) + (foreach seg segmente + (if (= (car seg) "Linie") + (setq laengen-liste + (append laengen-liste (list (rtos (/ (caddr seg) 1000.0) 2 3)))) + ) + ) + (setq L-gf-str "") + (foreach l-item laengen-liste + (if first-l + (progn (setq L-gf-str l-item) (setq first-l nil)) + (setq L-gf-str (strcat L-gf-str "," l-item)) + ) + ) + + (setq bL90 0 bL60 0 bL30 0 bR90 0 bR60 0 bR30 0) + (foreach seg segmente + (if (= (car seg) "Bogen") + (cond + ((and (= (nth 4 seg) "links") (= (nth 3 seg) 90)) (setq bL90 (1+ bL90))) + ((and (= (nth 4 seg) "links") (= (nth 3 seg) 60)) (setq bL60 (1+ bL60))) + ((and (= (nth 4 seg) "links") (= (nth 3 seg) 30)) (setq bL30 (1+ bL30))) + ((and (= (nth 4 seg) "rechts") (= (nth 3 seg) 90)) (setq bR90 (1+ bR90))) + ((and (= (nth 4 seg) "rechts") (= (nth 3 seg) 60)) (setq bR60 (1+ bR60))) + ((and (= (nth 4 seg) "rechts") (= (nth 3 seg) 30)) (setq bR30 (1+ bR30))) + ) + ) + ) + + (setq gf-lz-insert + (gf-block-erstellen + "gruppe" as-seite gf-nummer + (caddr startpunkt) + (caddr endpunkt-ref) + deltaH + delta-l-gf + L-gf-str + grade + 0 0 + bL90 bL60 bL30 + bR90 bR60 bR30 + startpunkt last-ent nach-aus-pt entry-hz + (caddr (nth first-idx segmente)) + es-seite "0" "Schoenenberger Geruest" + gf-as-winkel gf-es-winkel)) + + (gf-lz-xdata-schreiben gf-lz-insert segmente startpunkt) + + ;; Alten Block erst jetzt entfernen - der Neuaufbau ist bereits vollstaendig + ;; abgeschlossen, ein Fehler davor haette ihn stehen gelassen. + (if (and alt-ent (entget alt-ent)) (entdel alt-ent)) + + (princ "\n\n=========================================") + (princ (ssg-text "gf-status-fertig")) + (princ "\n=========================================") + gf-lz-insert +) + ;; ============================================================ ;; TEIL 4.5: GF-BLOCK-SYSTEM (Nummerierung, Label, Attribute) ;; ============================================================ @@ -1704,6 +2104,124 @@ ) ) +;; ============================================================ +;; XDATA fuer den Linienzug-Nachbau (Modus 3, App "SSG_GF_LZ"). +;; Die Segmentliste (gf-analysiere-kette) hat variable Laenge - anders als +;; gf-xdata-schreiben (feste Skalare fuer Modus 1/2) braucht sie ein eigenes +;; String-Format + Chunking (255-Byte-Limit je 1000-Gruppe), analog zur +;; Sepliste-XDATA in ssg_ks_insert.lsp (ssg-sepliste-chunk-string/-split-feld/ +;; -explode-chars werden von dort wiederverwendet statt dupliziert). +;; AS-/ES-Winkel+Seite stehen bereits als Attribute (WINKEL_AS/SEITE_AS/ +;; WINKEL_ES/SEITE_ES) auf dem Block - nur die Segmentliste UND der gebaute +;; Startpunkt (fuer den Neuaufbau-Ursprung) fehlen dort und muessen hier +;; gesichert werden. +;; ============================================================ +(if (null *gf-lz-xdata-app*) (setq *gf-lz-xdata-app* "SSG_GF_LZ")) + +;; Ein Segment "(Typ hz laenge winkel seite)" -> "Typ:hz:laenge:winkel:seite". +(defun gf-segment->string (seg) + (strcat (nth 0 seg) ":" (rtos (float (nth 1 seg)) 2 4) ":" + (rtos (float (nth 2 seg)) 2 4) ":" (itoa (fix (nth 3 seg))) ":" + (nth 4 seg)) +) + +;; Gegenstueck: "Typ:hz:laenge:winkel:seite" -> (Typ hz laenge winkel seite). +(defun gf-string->segment (s / feld) + (setq feld (ssg-sepliste-split-feld s ":")) + (if (= (length feld) 5) + (list (nth 0 feld) (atof (nth 1 feld)) (atof (nth 2 feld)) + (atoi (nth 3 feld)) (nth 4 feld)) + nil + ) +) + +;; Segmentliste -> ein einzelner kommagetrennter String. +(defun gf-segmente->string (segmente / out first) + (setq out "" first t) + (foreach seg segmente + (if first + (progn (setq out (gf-segment->string seg)) (setq first nil)) + (setq out (strcat out "," (gf-segment->string seg))) + ) + ) + out +) + +;; Gegenstueck zu gf-segmente->string - nutzt denselben Zeichen-Explode wie +;; die Sepliste (ssg_ks_insert.lsp), da Segment-Trenner "," mit dem inneren +;; Feld-Trenner ":" nicht kollidiert. +(defun gf-string->segmente (s / out teile eintraege seg) + (setq out '()) + (if (> (strlen s) 0) + (progn + (setq eintraege '() teile "") + (foreach c (ssg-sepliste-explode-chars s) + (if (= c ",") + (progn (setq eintraege (cons teile eintraege)) (setq teile "")) + (setq teile (strcat teile c)) + ) + ) + (setq eintraege (reverse (cons teile eintraege))) + (foreach eintrag eintraege + (if (setq seg (gf-string->segment eintrag)) (setq out (cons seg out))) + ) + ) + ) + (reverse out) +) + +;; Segmentliste + Nachbau-Startpunkt als XDATA schreiben. Marker "linienzug" +;; als erster Wert (Platz fuer kuenftige Varianten, analog SSG_VF_EDIT). +(defun gf-lz-xdata-schreiben (ent segmente startpunkt / chunks appentry) + (if (and ent segmente) + (progn + (regapp *gf-lz-xdata-app*) + (setq chunks + (ssg-sepliste-chunk-string + (strcat (rtos (car startpunkt) 2 4) ";" (rtos (cadr startpunkt) 2 4) ";" + (rtos (caddr startpunkt) 2 4) ";" (gf-segmente->string segmente)) + 250)) + (setq appentry + (cons *gf-lz-xdata-app* + (cons (cons 1000 "linienzug") + (mapcar (function (lambda (c) (cons 1000 c))) chunks)))) + (entmod (append (entget ent) (list (list -3 appentry)))) + ) + ) + ent +) + +;; XDATA lesen -> (startpunkt segmente) oder nil (kein/unbekanntes Linienzug- +;; XDATA am Entity). +(defun gf-lz-xdata-lesen (ent / xd app-data werte roh teile sp-x sp-y sp-z rest) + (setq xd (entget ent (list *gf-lz-xdata-app*))) + (setq app-data (cdr (assoc -3 xd))) + (if app-data + (progn + (setq werte (mapcar 'cdr (cdr (car app-data)))) + (if (and werte (= (car werte) "linienzug")) + (progn + (setq roh (apply 'strcat (cdr werte))) + (setq teile (ssg-sepliste-split-feld roh ";")) + (if (>= (length teile) 4) + (progn + (setq sp-x (atof (nth 0 teile)) sp-y (atof (nth 1 teile)) + sp-z (atof (nth 2 teile))) + ;; Rest nach dem 3. ";" wieder zusammenfuegen (Segmentliste + ;; selbst enthaelt kein ";"). + (setq rest (nth 3 teile)) + (list (list sp-x sp-y sp-z) (gf-string->segmente rest)) + ) + nil + ) + ) + nil + ) + ) + nil + ) +) + ;; ============================================================ ;; TEIL 6: HAUPTBEFEHL ;; ============================================================ @@ -1994,7 +2512,8 @@ (defun c:GEFAELLESTRECKE_EDIT ( / ss ent ed bname startpunkt attribs xd hz winkel as-winkel es-winkel z-start deltaL - as-seite hoehe-bis prefill dlg-werte) + as-seite hoehe-bis prefill dlg-werte + lz-xd lz-segmente lz-startpunkt lz-prefill) ;; Implied Selection VOR ssg-start pruefen (ssg-start hebt Selektion auf) (setq ss (ssget "I")) (if (and ss (= (sslength ss) 1) @@ -2022,9 +2541,37 @@ (setq startpunkt (cdr (assoc 10 ed))) (setq attribs (ssg-attrib-read ent)) + ;; Streckengruppe (mit Bogen) aus GEFAELLESTRECKE_LINIENZUG: eigener + ;; Rebuild-Pfad ueber die Segmentliste in SSG_GF_LZ-XDATA (siehe + ;; gf-linienzug-rebuild), NICHT ueber den einfachen gefaellestrecke-Dialog + ;; unten - der kennt keine Segmentliste/Boegen. Streckengruppe OHNE diese + ;; XDATA (Altbestand/VF-Kombi) bleibt wie bisher nicht editierbar. + (if (= (cond ((cdr (assoc "TYP" attribs))) ("")) "Streckengruppe") + (progn + (setq lz-xd (gf-lz-xdata-lesen ent)) + (cond + (lz-xd + (setq lz-startpunkt (nth 0 lz-xd)) + (setq lz-segmente (nth 1 lz-xd)) + (setq lz-prefill + (list (cons "as-winkel" (cond ((cdr (assoc "WINKEL_AS" attribs))) ("90"))) + (cons "as-seite" (cond ((cdr (assoc "SEITE_AS" attribs))) ("links"))) + (cons "es-winkel" (cond ((cdr (assoc "WINKEL_ES" attribs))) ("90"))) + (cons "es-seite" (cond ((cdr (assoc "SEITE_ES" attribs))) ("links"))))) + (gf-linienzug-rebuild lz-segmente lz-startpunkt ent lz-prefill) + ) + (t + (alert (ssg-textf "gf-edit-nur-einfach" + (list (cond ((cdr (assoc "TYP" attribs))) ("?"))))) + ) + ) + (ssg-end) + (exit) + ) + ) + ;; Nur einfache Gefaellestrecken (TYP "Gefaellestrecke") lassen sich ueber den - ;; Dialog verlustfrei nachbauen. Streckengruppen (mit Bogen / Linienzug / VF) - ;; wuerden beim Neuaufbau ihre Bogengeometrie verlieren -> abbrechen. + ;; Dialog verlustfrei nachbauen. (if (/= (cond ((cdr (assoc "TYP" attribs))) ("")) "Gefaellestrecke") (progn (alert (ssg-textf "gf-edit-nur-einfach" diff --git a/Lisp/KreiselInsert.lsp b/Lisp/KreiselInsert.lsp index a2b2c40..7625716 100644 --- a/Lisp/KreiselInsert.lsp +++ b/Lisp/KreiselInsert.lsp @@ -691,21 +691,25 @@ ;; ============================================================ ;; Kreisel per Script einfuegen (Insert) -;; pt : Einfuegepunkt (x y z) -;; abstand : Abstand in mm -;; rotation : Drehwinkel in Grad -;; typ : Kreiselart ("STANDARD", "PIN", ...) -;; hoehe : Hoehe in mm (oder nil fuer Z aus pt) +;; pt : Einfuegepunkt (x y z) +;; abstand : Abstand in mm +;; rotation : Drehwinkel in Grad +;; typ : Kreiselart ("STANDARD", "PIN", ...) +;; hoehe : Hoehe in mm (oder nil fuer Z aus pt) +;; drehrichtung : "GUZ"/"UZS" (optional, nil -> Default aus *kreisel-attrib-defs*) ;; Rueckgabe: Entity-Name des erzeugten Blocks oder nil -(defun kreisel-insert-script (pt abstand rotation typ hoehe / z blockEnt) +(defun kreisel-insert-script (pt abstand rotation typ hoehe drehrichtung + / z blockEnt attribs) (ssg-start (ssg-text "kreisel-start-script-insert") '(("OSMODE") ("CECOLOR"))) (if (null typ) (setq typ "STANDARD")) (setq z (if hoehe hoehe (if (and pt (caddr pt)) (caddr pt) *kreisel-default-hoehe*))) + (setq attribs (list (cons "KREISELART" typ) + (cons "HOEHE" (rtos z 2 0)))) + (if drehrichtung + (setq attribs (cons (cons "DREHRICHTUNG" drehrichtung) attribs))) (setq blockEnt - (draw-module (list (car pt) (cadr pt) z) abstand rotation - (list (cons "KREISELART" typ) - (cons "HOEHE" (rtos z 2 0))))) + (draw-module (list (car pt) (cadr pt) z) abstand rotation attribs)) (ssg-end) blockEnt ) diff --git a/Lisp/README.md b/Lisp/README.md index 1f38fb2..48c6d1f 100644 --- a/Lisp/README.md +++ b/Lisp/README.md @@ -15,14 +15,17 @@ _MENULOAD → menu/SSG_LIB.mnu auswaehlen ``` BricsCAD laedt daraufhin automatisch `SSG_LIB.mnl`, welches alle Module -per `ssg-ensure` initialisiert (Kern: ssg_core/lang/dbg/dialog/layer, dann -alle Feature-Module). Details: siehe `../CLAUDE.md` Abschnitt "Lademechanismus". +per `ssg-ensure` initialisiert. Kern-Reihenfolge (muss vor den Feature-Modulen +stehen): `ssg_core`, `ssg_lang`, `ssg_dbg`, `ssg_dialog`, `ssg_layer`, `ssg_id`, +`ssg_ks_insert` (Letzteres wird von Gefaellestrecke UND VarioFoerderer genutzt). +Details: siehe `../CLAUDE.md` Abschnitt "Lademechanismus". -**Manuell laden (Notfall / Entwicklung):** +**Manuell laden (Notfall / Entwicklung, z.B. fuer .scr-Testskripte):** ```lisp (load (strcat (getenv "DXFMAKRO") "/Lisp/ssg_load.lsp")) ``` -`ssg_load.lsp` laedt alle Module in der richtigen Reihenfolge ohne Menueabhaengigkeit. +`ssg_load.lsp` ist der Headless-Loader (genutzt von `tests/test_unit.scr` u.ae.) +und laedt dieselbe Kern-Reihenfolge wie `SSG_LIB.mnl`, ohne Menueabhaengigkeit. --- @@ -35,16 +38,22 @@ am Label-Anfang, bei Submenue-Schliessern hinter dem `<-` (`[<-~Label]`). | Menuebereich | Eintraege | Definiert in | | --- | --- | --- | -| ILS â–¸ Foerderer | Durch TEF angetriebene Strecke; Kurven 90/60/30 links/rechts angetrieben Aussen + Innen (12) | `SSG_LIB_Commands.lsp` | -| ILS â–¸ Gefaellestrecke | Kurven 90/60/30 links/rechts Gefaellestrecke (6) | `SSG_LIB_Commands.lsp` | -| Omniflo â–¸ TEF Elemente | UmlenkR/L, AntriebL/R, TEF Gerade, KettenFoerderer (6) | `OmniModulInsert.lsp` | +| Omniflo â–¸ Anbaugruppen | `OMNI_KettenFoerderer` | `OmniModulInsert.lsp` | | Omniflo â–¸ Verbinder | alle `OMNI_TV_*` (18) | `OmniModulInsert.lsp` | | Omniflo â–¸ Boegen fuer Weichen | alle `OMNI_APBW_*` (14) | `OmniModulInsert.lsp` | +| ILS â–¸ Zusatzmodule/Kurven | `ILS_ETF_Etagenfoerderer`, `ILS_K{90,60,30}{L,R}{A,I}` (12), `ILS_Gefaellestrecke` | `SSG_LIB_Commands.lsp` | +| Geruest | `GERUEST` (Gattertraeger-Platzhalter, derselbe Befehl wie Saeulen/Rohrabhaenger/Wandbefestigung) | `GeruestInsert.lsp` | -Aktiv (implementiert) sind u.a.: Kreisel, Eckrad, VarioFoerderer, EtageVarioFoerderer, -Gefaellestrecke, BTMT Be-/Entladung, Scanner/Separator, Omniflo Boegen (APB), -Aluprofil-Geraden (AP), alle Weichen (W90/W45/WP/WK/WKomb), Export (Sivas/CSV) -und die Tests. +`OMNI_TEF_*` (UmlenkR/L, AntriebL/R, TEF Gerade) sind entgegen frueherem Stand +**implementiert** (echtes `omni:insert-dxf`), nur `OMNI_KettenFoerderer` ist +weiterhin Dummy. + +Aktiv (implementiert) sind u.a.: Kreisel, Eckrad, VarioFoerderer (Standard/Etage/ +Linienzug), Gefaellestrecke, Geruest (Saeulen/Rohrabhaenger/Wandbefestigung/ +Rohr-Geruest-Traeger), TRO-Marker, Connections, BTMT Be-/Entladung, Scanner/ +Separator, Omniflo Boegen (APB)/TEF-Elemente/Aluprofil-Geraden (AP)/alle Weichen +(W90/W45/WP/WK/WKomb)/Ladeschleifen, Export (Sivas/CSV), Werkzeuge (Block-/ +Text-Tools, Planquadrate) und die Tests. --- @@ -181,7 +190,7 @@ Interne Parser-Funktionen (nicht direkt aufrufen): ### `ssg_lang.lsp` – Sprachumschaltung (Deutsch/Englisch) -Zentrale Textverwaltung fuer benutzersichtbare Meldungen. Kern-Modul, wird direkt nach `ssg_core` geladen. Wird schrittweise auf weitere Module ausgerollt (siehe `doc/translate.md`). +Zentrale Textverwaltung fuer benutzersichtbare Meldungen. Kern-Modul, wird direkt nach `ssg_core` geladen. Wird schrittweise auf weitere Module ausgerollt (siehe `doc/plan/translate.md`). | Funktion | Beschreibung | | --- | --- | @@ -323,50 +332,155 @@ Benutzerfreundliche BricsCAD-Befehle für Layer-Operationen. Alle Befehle sicher --- -### `ssg_load.lsp` – Laderoutine +### `ssg_input.lsp` – Generische Eingabe-Wrapper -Lädt alle Module in der richtigen Reihenfolge. Ermittelt den Pfad automatisch über `findfile`. Ruft direkt nach `ssg_core.lsp` die Funktion `ssg-load-config` auf, damit die JSON-Konfiguration für alle nachfolgenden Module verfügbar ist. - ---- - -### `VarioFoerderer.lsp` – Vario-Förderer - -Kompatibilitäts-Wrapper, der `vf_core.lsp` lädt (Plugin-Dispatcher-Architektur; lädt seinerseits `vf_standard.lsp`, `vf_etage.lsp`, `vf_linienzug.lsp`). Parametrischer Generator für ILS Vario-Förderanlagen. Liest Block-Geometrie aus einer DWG-Bibliothek (`DXFM_BLOCKS`). Alle Konstanten (Abstände, Winkel, Skalierung) werden via `ssg-cfg-or` aus `component_defaults.json` (Sektion `vario`) gelesen. - -**Befehl:** `c:FOERDERANLAGE` / `c:VarioFoerderer` – wählt zuerst den Anlagen-Typ (Standard/Etage/Linienzug): - -- **Typ „standard“ + Werteingabe:** läuft über den DCL-Dialog - [`dcl/variofoerderer.dcl`](../dcl/variofoerderer.dcl) (zwei Dialoge, `vfs-standard-dialog-ablauf` - in `vf_standard.lsp`): Dialog 1 (`vfs-dialog-eingabe-basis`) erfasst deltaL, deltaH, - Förderrichtung (Auf/Ab), Einfügehöhe, Fahrtrichtung und Seite; anschließend berechnet - `berechne-standard` die gültigen Winkel-Varianten, und Dialog 2 - (`vfs-dialog-winkel-verteilung`) lässt Winkel und L_GF-Verteilung (vorne/hinten) wählen. - Startpunkt (X/Y) wird wie bei der Gefällestrecke vorher in der Zeichnung gepickt. -- **Typ „standard“ + 3D-Linie**, sowie **Typ „etage“** und **„linienzug“** bleiben - vollständig interaktiv (Kommandozeile), unverändert gegenüber dem bisherigen Ablauf. - -**Bearbeiten (`c:VARIOFOERDERER_EDIT`):** Doppelklick auf einen `VF_n`-Block – bzw. -Rechtsklick-Kontextmenü/`SSG_BLOCKEDIT` – öffnet dieselben Dialoge, vorbelegt mit den -aktuellen Parametern, und baut die Strecke bei OK am selben Startpunkt neu auf (neue -`VF_n`-Nummer). Da `VF_n`-Blöcke auch von Etage- und Linienzug-Anlagen erzeugt werden -(deren komplexere Geometrie ein einfacher Neuaufbau zerstören würde), markiert -`vfs-xdata-schreiben` nur per Standard-Dialog erstellte Blöcke mit XDATA (App -`SSG_VF_EDIT`, Fahrtrichtung `hz`); Blöcke ohne diese Markierung weist -`c:VARIOFOERDERER_EDIT` mit einer Meldung ab, statt sie zu verändern. Winkel und L_GF1 -werden aus den Sivas-Attributen (`VF_WINKEL`, `L_GF_m`) vorbelegt (Verteilung stets als -„Eigene Werte“ mit dem exakten aktuellen L_GF1). - -**Wichtige interne Funktionen** (werden auch von `Gefaellestrecke.lsp` genutzt): +Abfragen mit Vorgabewert (`<..>` im Prompt, Enter = Vorgabe). Steht neben den VF-spezifischen `vfl-in-*` (`vf_linienzug.lsp`); diese bleiben unveraendert. Kein Journal, aber ein optionaler Hook. ESC bricht wie ueblich ueber `*error*` ab. | Funktion | Beschreibung | | --- | --- | -| `init-bibliothek` | Block-Bibliothek aus DWG laden und Geometriedaten extrahieren | -| `ensure-block-loaded name` | Block aus Bibliothek in aktives Dokument laden (einmalig) | -| `insert-block-by-ks name ks-nr pt` | Block anhand Koordinatensystem-Punkt einfügen | -| `insert-inclined-scaled-block name laenge winkel pt` | Geneigten, skalierten Block einfügen | -| `insert-rotated-block-with-ks name rot pt` | Rotierten Block mit KS-Offset einfügen | -| `get-line-start-end-points ename` | Start- und Endpunkt einer LINE-Entity ermitteln | -| `punkt-differenz p1 p2` | Differenzvektor zweier Punkte | +| `ssg-in-real text default` | Reelle Zahl (`getreal`), Vorgabe ohne ueberfluessige Nullen angezeigt | +| `ssg-in-int text default` | Ganzzahl (`getint`) | +| `ssg-in-string text default` | String mit Leerzeichen; Enter ohne Eingabe liefert die Vorgabe (ohne Vorgabe `nil`) | +| `ssg-in-point text default base` | Punkt, `base` = Gummiband-Basispunkt oder `nil` | +| `ssg-in-dist text default base` | Abstand (`getdist`) | +| `ssg-in-keyword text schluessel default` | Schluesselwort im `initget`-Format, z.B. `"Alle Auswahl"` | +| `ssg-in-yesno text default` | Ja/Nein, **T = Ja**; `default` T = Enter bedeutet Ja. Woerter aus `ssg-text "in-yesno-keys"` | +| `ssg-in-default wert default`, `ssg-in-fmt-real`, `ssg-in-fmt-pt`, `ssg-in-prompt`, `ssg-in-first-word`, `ssg-in-second-word` | Reine Hilfsfunktionen (in `tests/test_unit.lsp` getestet) | + +Hook (z.B. fuer ein Eingabe-Journal): `(setq *ssg-in-hook* 'meine-funktion)` mit `(defun meine-funktion (art prompt wert) ...)`; `art` ist `"REAL"`, `"INT"`, `"STRING"`, `"POINT"`, `"DIST"`, `"KEYWORD"` oder `"YESNO"`. Ein Hook, der einen Fehler wirft, wird abgeschaltet. + +--- + +### `ssg_id.lsp` – Eindeutige Block-IDs + +Jedes exportierbare Element bekommt ein Attribut `ID` mit einer eindeutigen, +vierstelligen Nummer. Kern-Modul (nach `ssg_layer`, vor `ssg_ks_insert`). + +| Befehl / Funktion | Beschreibung | +| --- | --- | +| `c:IDGENERATE` | Weist dem zuletzt eingefuegten Block eine neue ID zu | +| `c:IDSCHECK` | Prueft alle Bloecke auf doppelte IDs und korrigiert sie | +| `ssg-id-format n` | Zahl als 4-stelligen String formatieren (`1` → `"0001"`) | +| `ssg-id-max` | Hoechste bereits vergebene ID in der Zeichnung | +| `ssg-id-generate ent` | Neue ID erzeugen und auf den Block setzen | +| `ssg-id-check-all` | Zeichnungsweit auf Doubletten pruefen und korrigieren | +| `ssg-id-collect-blocks` | Alle ID-faehigen INSERT-Entities sammeln (gleiche Muster wie `csv:collect-export-blocks`, inkl. Separator/Scanner/BTMT) | + +--- + +### `ssg_ks_insert.lsp` – KS-Extraktion und Block-Einfuegeprimitiven + +Gemeinsame Basis fuer VarioFoerderer (`vf_core.lsp`) UND Gefaellestrecke – +frueher in beiden Modulen dupliziert, jetzt einzige Quelle. Muss laut MNL **vor** +Gefaellestrecke/`vf_core` geladen sein; Kern-Modul. + +| Funktion | Beschreibung | +| --- | --- | +| `vec-length v` | Laenge eines 3er-Vektors | +| `ks-line-axis laenge` | Achsenkennung (X/Y/Z) aus Linienlaenge der KS-Hilfslinie | +| `ks-normalize-name nm` | Historische KS-Blocknamen (`KSYS_EIN`/`-AUS`) auf `KS_EIN`/`KS_AUS` normalisieren | +| `ks-relativize` / `ks-absolutize` | KS-Punkte zwischen relativem und Welt-Koordinatensystem umrechnen | +| `ensure-block-loaded name` | Block aus der Bibliothek einmalig ins aktive Dokument laden | +| `extract-ks-from-block[-raw] name` | KS_EIN/KS_AUS-Punkte eines Blocks ermitteln (Cache `*ks-cache*`) | +| `insert-block-by-ks name ks-nr pt` | Block anhand KS-Punkt einfuegen (nur Rz-Rotation) | +| `insert-inclined-scaled-block` / `insert-rotated-block-with-ks` | Geneigten/rotierten Block mit KS-Offset einfuegen (Rz*Ry) | + +--- + +### `ssg_blocktools.lsp` – Block-Werkzeuge + +Ursprung: ACADSST `TOOLS.LSP` (`BLKANZAHL`, `BLKENTF`), `DINGEN2` (`g-bli`), `ssg.lsp` (`suchen-aendern`). Arbeitet im **aktuellen Layout-Reiter**. Blocknamen werden ueber den wirksamen Namen verglichen (dynamische Bloecke); Muster mit `wcmatch` (`*`, `?`, `#`, ohne Gross-/Kleinschreibung). + +| Befehl | Beschreibung | +| --- | --- | +| `SSG_BLOCK_COUNT` | Block anklicken, `Name` fuer ein Muster, oder Enter fuer eine Statistik aller Bloecke | +| `SSG_BLOCK_DELETE` | Alle Einfuegungen eines Blocks/Musters loeschen; Rueckfrage (Vorgabe Nein), Einfuegungen auf gesperrten Layern bleiben stehen; eine Undo-Gruppe | +| `SSG_ATTR_ERSETZEN` | Attributwerte suchen/ersetzen: Bereich (Alle mit Blockname-Muster / Auswahl), Tag-Muster, Suchtext, Ersatz, Modus Teil (Teilstring) oder Gesamt (ganzer Wert). Zeigt vorab die Anzahl und fragt nach | + +| Funktion | Beschreibung | +| --- | --- | +| `ssg-bt-inserts muster` | Entity-Liste aller Einfuegungen im Layout (optional nach Muster) | +| `ssg-bt-statistik muster` | `((NAME . ANZAHL) ...)` alphabetisch | +| `ssg-bt-attr-ersetzen-ent ent tag suche ersatz teil-p probe-p` | Attribute EINER Einfuegung ersetzen bzw. nur zaehlen (`probe-p`); Rueckgabe Anzahl | +| `ssg-bt-attr-ersetzen-liste ents tag suche ersatz teil-p probe-p` | Dasselbe fuer eine Entity-Liste | +| `ssg-bt-wert-neu wert suche ersatz teil-p` | Reiner Kern: neuer Wert oder `nil` (unveraendert); beachtet Gross-/Kleinschreibung | +| `ssg-bt-name-passt-p`, `ssg-bt-zaehle-namen`, `ssg-bt-muster-escape` | Reine Hilfsfunktionen (getestet) | + +--- + +### `ssg_texttools.lsp` – Text-Werkzeuge + +Ursprung: ACADSST `CHGTEXT.LSP`, `PARTEXT.LSP`, `ALTHOEHE.LSP`, `ALTSTIL.LSP`. Laden **nach** `ssg_blocktools` (nutzt `ssg-bt-wert-neu` und `ssg-bt-attr-ersetzen-liste`). Arbeitet in der XY-Ebene des Welt-KS. + +| Befehl | Beschreibung | +| --- | --- | +| `SSG_TEXT_ERSETZEN` | Suchen/Ersetzen in TEXT, MTEXT **und** Block-Attributen; Bereich Alle (Layout) oder Auswahl; Vorschau mit Anzahl, Rueckfrage | +| `SSG_TEXT_PARALLEL` | Linie/Polylinie/Bogen anklicken, Text wird parallel (lesbar, nie auf dem Kopf) mit Abstand gesetzt; Hoehe, Abstand und letzter Text werden fuer die Sitzung gemerkt; Enter beendet | +| `SSG_TEXT_HOEHE` | Texthoehe gewaehlter TEXT/MTEXT aendern | +| `SSG_TEXT_STIL` | Textstil gewaehlter TEXT/MTEXT aendern (Stil muss existieren) | + +Reine Kerne (getestet): `ssg-tt-lesbarer-winkel` (Winkel 90 Grad bis 270 Grad um 180 Grad drehen, Ergebnis in [0, 2 pi)), `ssg-tt-versetzt`. + +Nicht uebernommen aus Walz: `Export`/`Import` von Texten (besser per Python/ezdxf), `KREISTXT`, `TXTBOXED`, `CHTEXT`/`ASCTEXT`/`PTEXT` (Autodesk 1990, durch BricsCAD-Standard ersetzt). + +--- + +### `ssg_load.lsp` – Laderoutine + +Headless-Loader fuer .scr-Testskripte (z.B. `tests/test_unit.scr`). Laedt alle +Kern- und Feature-Module in derselben Reihenfolge wie `SSG_LIB.mnl`, ohne +Menueabhaengigkeit. Ermittelt den Pfad automatisch über `findfile`. Ruft direkt +nach `ssg_core.lsp` die Funktion `ssg-load-config` auf, damit die JSON-Konfiguration +für alle nachfolgenden Module verfügbar ist. + +--- + +### `VarioFoerderer.lsp` – Vario-Förderer (Wrapper) + +Kompatibilitäts-Wrapper, der `vf_core.lsp` lädt (Plugin-Dispatcher-Architektur +über `*vf-typ-registry*`; lädt seinerseits `vf_konstanten.lsp`, `vf_standard.lsp`, +`vf_etage.lsp`, `vf_linienzug.lsp`, danach `vf_spec.lsp`). Parametrischer Generator +für ILS Vario-Förderanlagen (Typen Standard/Etage/Linienzug). Liest Block-Geometrie +aus einer DWG-Bibliothek (`DXFM_BLOCKS`) über die Primitiven aus `ssg_ks_insert.lsp`. +Alle Konstanten (Abstände, Winkel, Skalierung) werden über `ssg-cfg-or` +(`component_defaults.json`, Sektion `vario`) bzw. die zentralen `*vfk-*`-Konstanten +aus `vf_konstanten.lsp` gelesen. + +**Befehl:** `c:FOERDERANLAGE` / `c:VarioFoerderer` – wählt zuerst den Anlagen-Typ (Standard/Etage/Linienzug): + +- **Typ „standard“ und „etage“ + Werteingabe:** laufen über denselben, + typ-parametrisierten Dialog-Ablauf (`vf-dialog-ablauf` in `vf_core.lsp`; + `vfs-standard-dialog-ablauf`/`vfe-etage-dialog-ablauf` sind nur noch duenne + Wrapper darauf) mit den Dialogen `vfs-dialog-eingabe-basis`/ + `vfs-dialog-winkel-verteilung` ([`dcl/variofoerderer.dcl`](../dcl/variofoerderer.dcl)): + Dialog 1 erfasst deltaL, Förderrichtung, Einfügehöhe/deltaH, Fahrtrichtung und + Seite/Motorseite; Dialog 2 lässt Winkel und L_GF-Verteilung (vorne/hinten) wählen. + Startpunkt (X/Y) wird vorher in der Zeichnung gepickt. +- **Typ „standard“/„etage“ + 3D-Linie** laufen ueber denselben Basis-Dialog statt + rein interaktiv auf der Konsole. +- **Typ „linienzug“** (Streckengruppe, `vf-linienzug-modus` in `vf_linienzug.lsp`) + hat drei Modi (manuell/Wizard, 3D-Objekte+Zielhoehe, Vorwaerts-Nachbau) und + zeichnet jede Eingabe in einem Journal auf (XDATA `SSG_VF_EDIT` am `VF_n`-Block); + Standard laeuft per Default ueber DCL-Dialoge (`VF_WIZARD_AN`/`VF_WIZARD_AUS` + schalten um). Details, Journal-Format, Headless-Modus (`*vfl-headless*`) und + Spec-basierter Bau ohne Dialog (`vf_spec.lsp`, `VF_SPEC_BAU`): siehe + [`../doc/variofoerderer.md`](../doc/variofoerderer.md) und `../CLAUDE.md`. + +**Bearbeiten (`c:VARIOFOERDERER_EDIT`):** Doppelklick auf einen `VF_n`-Block – bzw. +Rechtsklick-Kontextmenü/`SSG_BLOCKEDIT` – dispatcht per XDATA-Marker +(`"standard"`/`"etage"`/`"linienzug"`) in den passenden Neuaufbau-Zweig +(`vfe-edit-ent` fuer Etage, `vfl-edit-ent` fuer Linienzug) und öffnet dieselben +Dialoge, vorbelegt mit den aktuellen Parametern; der Block wird am selben +Startpunkt neu aufgebaut (neue `VF_n`-Nummer). Altbestand-Blöcke ohne +Marker werden mit einer Meldung abgewiesen, statt sie zu verändern. Winkel und +L_GF1 werden aus den Sivas-Attributen (`VF_WINKEL`, `L_GF_m`) vorbelegt. + +Die Block-Einfuege-Primitiven (`ensure-block-loaded`, `insert-block-by-ks`, +`insert-inclined-scaled-block`, `insert-rotated-block-with-ks` u.a.) sind seit +der Konsolidierung zentral in [`ssg_ks_insert.lsp`](#ssg_ks_insertlsp--ks-extraktion-und-block-einfuegeprimitiven) +definiert (von `vf_core.lsp`, `Gefaellestrecke.lsp` und `TEFInsert.lsp` genutzt). +Weitere interne Helfer: `init-bibliothek` (Block-Bibliothek aus DWG laden), +`get-line-start-end-points ename`, `punkt-differenz p1 p2`. --- @@ -414,6 +528,19 @@ Der Neuaufbau läuft über `gf-rebuild` (nicht-interaktiv, ohne Konsolen-Rückfr --- +### `TEFInsert.lsp` – TEF-Geradenstuecke entlang einer Linie + +**Befehl:** `c:TEF_FOERDERER_LINIE`. Zeichnet eine Linie zwischen zwei gewaehlten +Punkten und reiht darauf 1000-mm-TEF-Geradenelemente (`0_B10030` aus +`DXFM_OMNIFLO`) aneinander; ist die Linienlaenge kein exaktes Vielfaches, +wird ein zusaetzliches, zurueckverschobenes Element eingefuegt, sodass der +Foerderer exakt am Linienende endet (keine Luecke, kein abgeschnittenes +Element). Die Linie darf geneigt sein (3D) – Elemente werden dann analog zu +`vf_core.lsp`/`ssg_ks_insert.lsp` (Rz*Ry-Rotation) gekippt eingefuegt. Nicht +zu verwechseln mit den einzelnen `OMNI_TEF_*`-Befehlen in `OmniModulInsert.lsp`. + +--- + ### `export.lsp` – Export-Funktionen Sammelt INSERT-Blöcke aus der Zeichnung und exportiert sie als JSON oder ruft Python-Skripte für den Sivas-Export auf. @@ -572,11 +699,16 @@ AutoLISP-Implementierung fuer alle Omniflo-Komponenten: Boegen, Weichen, Verbind | Weichen | `OMNI_W90_*`, `OMNI_W45_*`, `OMNI_WP_*`, `OMNI_WK_*`, `OMNI_WKomb_*` | Omniflo-Weichen (Einfach/Doppel/Dreiwege, Delta, Stern) | | Geraden | `OMNI_AP60`, `OMNI_AP110`, `OMNI_APG110` | Omniflo-Geradenstuecke | | Ladeschleifen | `OMNI_LADESCHLEIFE_AP110` | Ladeschleife aus `omniflo_ladeschleifen.json` ueber DCL-Dialog (`dcl/omniflo_ladeschleifen.dcl`) einfuegen; Daten per `ssg-json-laden` (Beschreibungen enthalten Kommas). Blockdatei `.dwg` unter `DXFM_OMNIFLO` (`omni:insert-dxf` faellt auf `.dwg` zurueck, wenn keine `.dxf` existiert). Per `OMNI_EDIT` editierbar | -| TEF âš ï¸� | `OMNI_TEF_UmlenkR/L`, `OMNI_TEF_AntriebL/R`, `OMNI_TEF_Gerade`, `OMNI_KettenFoerderer` | **Dummy** (noch nicht implementiert, im Menue ausgegraut) | +| TEF Elemente | `OMNI_TEF_UmlenkR/L`, `OMNI_TEF_AntriebL/R`, `OMNI_TEF_Gerade` | Feste TEF-Bauteile ohne Sivas-Auswahl, direkt per `omni:insert-dxf` an gewaehltem Punkt eingefuegt (Dateien unter `DXFM_OMNIFLO`) | +| Anbaugruppen âš ï¸� | `OMNI_KettenFoerderer` | **Dummy** (noch nicht implementiert, im Menue ausgegraut); `OMNI_STOPPER`/`OMNI_SCANNER`/`OMNI_BUERSTENBREMSE` sind implementiert | | Transferwagen âš ï¸� | `OMNI_TV_*` | **Dummy** (noch nicht implementiert, im Menue ausgegraut) | | Bogenweichen âš ï¸� | `OMNI_APBW_*` | **Dummy** (noch nicht implementiert, im Menue ausgegraut) | | Bearbeiten | `OMNI_EDIT` | Omniflo-Block selektieren und Attribute bearbeiten | +Daneben zeichnet [`TEFInsert.lsp`](#tefinsertlsp--tef-geradenstuecke-entlang-einer-linie) (`TEF_FOERDERER_LINIE`) eine +Kette von TEF-Geradenelementen (1000 mm) entlang einer frei gewaehlten Linie – +nicht zu verwechseln mit den einzelnen `OMNI_TEF_*`-Befehlen oben. + --- ### `SSG_LIB_Commands.lsp` – BTMT, Sensoren, Dummy-Befehle, Konfiguration und SSG_BLOCKEDIT @@ -623,6 +755,10 @@ Entladung" bzw. "BT - Beladung ..."/"Abwurf fuer Klammerbuegel ..."); im Sivas-Export zusaetzlich in den Summenzaehlern "Anzahl aller BTBeladung"/ "Anzahl aller Abwurfstationen" der ILS-Automation-Zeile. +**Eingang-/Ausgang-Symbol:** `ILS_EINGANG_INSERT`/`ILS_AUSGANG_INSERT` nutzen +dieselbe `ils-insert-station`-Routine wie BTMT Be-/Entladung (frei platzierbar, +Blöcke `Eingang_.dwg`/`Ausgang_.dwg` in `data/ils`). + Registrierte Dummy-Befehle: | Gruppe | Befehle | @@ -643,11 +779,86 @@ Registrierte Dummy-Befehle: **SSG_BLOCKEDIT** – Doppelklick/Kontextmenue-Dispatcher: -Erkennt den Blocktyp des selektierten INSERT-Blocks und ruft den passenden Bearbeitungsbefehl auf: +Erkennt den Blocktyp des selektierten INSERT-Blocks und ruft den passenden Bearbeitungsbefehl auf (Reihenfolge wichtig: `GR_*` **vor** dem Omniflo-Zweig, da Geruest-Bloecke ebenfalls ein TEILEART-Attribut tragen): | Blockmuster | Aktion | | --- | --- | | `KREISEL_*` | `KreiselEdit` | | `ECKRAD_*` | `BEDIT` (Block-Editor) | +| `GF_*` | `GEFAELLESTRECKE_EDIT` | +| `VF_*` | `VARIOFOERDERER_EDIT` | +| `GR_*` | Geruest – `BEDIT` (kein eigener Edit-Pfad; siehe `GeruestInsert.lsp`) | +| `TRO_*` | `TRO_EDIT` | | Omniflo (TEILEART-Attribut, AP110*, numerisch) | `OMNI_EDIT` | | Sonstige | `BEDIT` (Block-Editor) | + +--- + +### `GeruestInsert.lsp` – Geruest-Baugruppen + +Rohr-Geruest-Baugruppen (Saeulen, Rohrabhaenger, Wandbefestigung, Rohr-Geruest-Traeger) +aus [`data/json/geruest.json`](../data/json/geruest.json) bzw. +[`data/json/geruest_traeger.json`](../data/json/geruest_traeger.json) konfigurieren +und einfuegen. Die Dialogdefinition wird **zur Laufzeit erzeugt** +(`geruest-dcl-schreiben`, temporaere DCL-Datei) – es gibt keine feste +`dcl/geruest.dcl`, weil je Anbauteil ein eigenes Mengenfeld gebraucht wird. +Ausfuehrliche Beschreibung (Konfigurationen, Attribut-Schema, Querstreben/Muffe, +Traeger-Beschriftung): siehe `../CLAUDE.md` Abschnitt "AutoLISP-Module". + +| Befehl | Beschreibung | +| --- | --- | +| `c:GERUEST` | Dialog ohne Vorgabe-Bereich (Dropdown Rohrabhaenger/Saeulen/Wandbefestigung) | +| `c:GERUEST_SAEULEN` / `c:GERUEST_ROHRABHAENGER` / `c:GERUEST_WANDBEFESTIGUNG` | Dialog mit vorgewaehltem Bereich (derselbe Dialog wie `GERUEST`) | +| `c:GERUEST_TRAEGER` | Eigener fester Dialog (`dcl/geruest_traeger.dcl`): Rohr-Geruest-Traeger mit Linien-Logik (Start-/Endpunkt oder Laenge, Laengenabzuege, Querstreben/Muffe-Verteilung) | +| `c:GERUEST_TRAEGER_BESCHRIFTEN` | Alle Traeger der Zeichnung neu beschriften (eigenes TEXT-Objekt, verknuepft per XDATA `SSG_GR_BESCHR`) | + +Block-Praefix `GR__` – **nicht kosmetisch**: `SSG_BLOCKEDIT` muss +`GR_*` vor dem Omniflo-Zweig abfangen, da Geruest-Bloecke ebenfalls ein +TEILEART-Attribut tragen. Export-Pattern `pattern_geruest = GR_*` (`cfg/export.cfg`). + +--- + +### `Tro_Edit.lsp` – TRO-Marker + +TRO-Marker (Block `TRO_SYM_`) fuer `sps_skel`/`lib/tro_annotate.py`. + +| Befehl | Beschreibung | +| --- | --- | +| `c:TRO_INSERT` | Bestehende Blockdefinition an gewaehltem Punkt einfuegen, setzt ID/TYPE; bricht ab, wenn der Typ noch keine Blockdefinition in der Zeichnung hat (entsteht nur ueber `tro_annotate.py`) | +| `c:TRO_EDIT` | ID/TYPE eines bestehenden Markers per Dialog aendern | + +Typliste kommt aus [`tro_types.lsp`](../Lisp/tro_types.lsp) (von +`tro_annotate.py --emit-lisp` nach `DXFM_LISP` geschrieben). `SSG_BLOCKEDIT` +leitet Blocknamen `TRO_*` zu `TRO_EDIT`. + +--- + +### `Connection_Edit.lsp` – Verbindungen (SPS) + +Verbindungslinien zwischen zwei Punkten/Bausteinen fuer SPS-Dokumentation. + +| Befehl | Beschreibung | +| --- | --- | +| `c:CONNECTION_INSERT` | Verbindung zwischen zwei Punkten einfuegen (Laenge/Winkel/Mitte werden berechnet) | +| `c:CONNECTION_EDIT` | Bestehende Verbindung bearbeiten | + +--- + +### `DrawPlanquadrate.lsp` – Planquadrat-Raster + +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 und Ursprung kommen aus der **Bounding-Box** derselben +Blockmenge wie `EXPORTCSV` (`csv:collect-export-blocks`), nicht aus dem reinen +Einfuegepunkt oder den Zeichnungs-Extents. Lazy geladen ueber +`(ssg-ensure "DrawPlanquadrate")`, nicht Teil der Core-Ladereihenfolge. + +| Befehl | Beschreibung | +| --- | --- | +| `c:DRAW_PLANQUADRATE` | Raster neu aufbauen (entfernt zuerst einen evtl. vorhandenen Block) | +| `c:DELETE_PLANQUADRATE` | Raster-Block entfernen | +| `c:PLANQUADRATE_TOGGLE` | Dispatcht auf Zeichnen bzw. Loeschen, je nachdem ob der Block existiert | + +Das fertige Raster (Linien + Zaehl-Texte) wird zu einem einzigen Block +`ZZ_PLANQUADRATE` bei (0,0,0) zusammengefasst; Layer aus `cfg/layer.cfg`. diff --git a/Lisp/ssg_blocktools.lsp b/Lisp/ssg_blocktools.lsp new file mode 100644 index 0000000..7b5baa7 --- /dev/null +++ b/Lisp/ssg_blocktools.lsp @@ -0,0 +1,311 @@ +;; ============================================================ +;; SSG_BLOCKTOOLS.LSP - Block-Werkzeuge fuer die aktuelle Zeichnung +;; +;; Ursprung: ACADSST/MENU/TOOLS.LSP (BLKANZAHL, BLKENTF), DINGEN2 +;; (g-bli) und ssg.lsp (suchen-aendern) - neu geschrieben im ssg-Stil +;; (Undo-Gruppe, i18n, Ja/Nein-Rueckfrage, keine Globals). +;; +;; Befehle: +;; SSG_BLOCK_COUNT Einfuegungen zaehlen (ein Block, Namensmuster +;; oder Statistik aller Bloecke) +;; SSG_BLOCK_DELETE Alle Einfuegungen eines Blocks/Namensmusters +;; loeschen (mit Rueckfrage) +;; SSG_ATTR_ERSETZEN Attributwerte zeichnungsweit oder in einer +;; Auswahl suchen und ersetzen (mit Vorschau) +;; +;; Blocknamen werden ueber den WIRKSAMEN Namen verglichen (dynamische +;; Bloecke: EffectiveName statt "*U12"). Muster arbeiten mit wcmatch +;; (Platzhalter * ? #, ohne Beachtung der Gross-/Kleinschreibung). +;; Es wird nur der aktuelle Layout-Reiter (Modell ODER Papierbereich) +;; durchsucht. +;; +;; Setzt ssg_core.lsp, ssg_lang.lsp und ssg_input.lsp voraus. +;; ============================================================ + +(vl-load-com) + +;; ------------------------------------------------------------ +;; REINE HILFSFUNKTIONEN (in test_unit getestet) +;; ------------------------------------------------------------ + +;; T, wenn name auf das wcmatch-Muster passt (ohne Gross-/Kleinschreibung). +;; muster nil oder "" -> alle Namen passen. +(defun ssg-bt-name-passt-p (name muster) + (if (or (null muster) (= muster "")) + T + (if (wcmatch (strcase name) (strcase muster)) T nil) + ) +) + +;; Liste von Namen -> Alist ((NAME . ANZAHL) ...), alphabetisch sortiert +;; (ohne Beachtung der Gross-/Kleinschreibung). +(defun ssg-bt-zaehle-namen (namen / alist eintrag) + (foreach name namen + (setq eintrag (assoc name alist)) + (if eintrag + (setq alist (subst (cons name (1+ (cdr eintrag))) eintrag alist)) + (setq alist (cons (cons name 1) alist)) + ) + ) + (vl-sort alist '(lambda (a b) (< (strcase (car a)) (strcase (car b))))) +) + +;; Sonderzeichen eines Blocknamens fuer wcmatch maskieren (Backquote), damit +;; ein per Pick gewaehlter Name nur sich selbst trifft. +(defun ssg-bt-muster-escape (name / res i c) + (setq res "" i 1) + (while (<= i (strlen name)) + (setq c (substr name i 1)) + (if (vl-string-search c "#@.*?~[],`") + (setq res (strcat res "`")) + ) + (setq res (strcat res c) + i (1+ i)) + ) + res +) + +;; Muster ohne Maskier-Backquotes (fuer Meldungen) +(defun ssg-bt-muster-anzeige (muster) + (ssg-str-replace-all "" "`" muster) +) + +;; Neuer Attributwert oder nil, wenn der Wert unveraendert bleibt. +;; teil-p = T : suche wird als Teilstring ueberall ersetzt +;; teil-p = nil: nur ersetzen, wenn der GESAMTE Wert gleich suche ist +;; Leere suche aendert nichts. Gross-/Kleinschreibung wird beachtet. +(defun ssg-bt-wert-neu (wert suche ersatz teil-p) + (cond + ((or (null suche) (= suche "") (null wert)) nil) + (teil-p + (if (vl-string-search suche wert) + (ssg-str-replace-all ersatz suche wert) + nil + ) + ) + ((= wert suche) ersatz) + (t nil) + ) +) + +;; ------------------------------------------------------------ +;; ZEICHNUNGSZUGRIFF +;; ------------------------------------------------------------ + +;; Wirksamer Blockname einer Einfuegung (dynamische Bloecke beachten) +(defun ssg-bt-effname (ent / res) + (setq res (vl-catch-all-apply 'vla-get-effectivename + (list (vlax-ename->vla-object ent)))) + (if (vl-catch-all-error-p res) + (cdr (assoc 2 (entget ent))) + res + ) +) + +;; T, wenn der Layer der Entity gesperrt ist +(defun ssg-bt-layer-gesperrt-p (ent / lay) + (setq lay (tblsearch "LAYER" (cdr (assoc 8 (entget ent))))) + (and lay (= 4 (logand 4 (cdr (assoc 70 lay))))) +) + +;; Alle Einfuegungen im aktuellen Layout, optional gefiltert nach Namensmuster. +;; Rueckgabe: Liste von Entity-Namen (kann leer sein). +(defun ssg-bt-inserts (muster / ss i ent res) + (setq ss (ssget "_X" (list '(0 . "INSERT") (cons 410 (getvar "CTAB"))))) + (if ss + (progn + (setq i 0) + (while (setq ent (ssname ss i)) + (if (ssg-bt-name-passt-p (ssg-bt-effname ent) muster) + (setq res (cons ent res)) + ) + (setq i (1+ i)) + ) + ) + ) + (reverse res) +) + +;; Statistik: ((NAME . ANZAHL) ...) fuer alle Einfuegungen passend zum Muster +(defun ssg-bt-statistik (muster) + (ssg-bt-zaehle-namen (mapcar 'ssg-bt-effname (ssg-bt-inserts muster))) +) + +;; Block per Pick oder Namensmuster waehlen. +;; Rueckgabe: Namensmuster (String) oder nil bei Enter ("alle"). +(defun ssg-bt-muster-waehlen (text / sel ed) + (initget "Name") + (setq sel (entsel text)) + (cond + ((null sel) nil) + ((= (type sel) 'STR) + (setq sel (getstring T (ssg-text "bt-prompt-muster"))) + (if (= sel "") nil sel) + ) + (t + (setq ed (entget (car sel))) + (if (= (cdr (assoc 0 ed)) "INSERT") + (ssg-bt-muster-escape (ssg-bt-effname (car sel))) + (progn (prompt (ssg-text "bt-kein-block")) nil) + ) + ) + ) +) + +;; Attribute EINER Einfuegung ersetzen (oder nur zaehlen, wenn probe-p). +;; tag = wcmatch-Muster fuer den Attribut-Tag ("*" = alle). +;; Rueckgabe: Anzahl betroffener Attribute. +(defun ssg-bt-attr-ersetzen-ent (ent tag suche ersatz teil-p probe-p + / e ed neu n) + (setq n 0 + e (entnext ent)) + (while (and e + (= "ATTRIB" (cdr (assoc 0 (setq ed (entget e)))))) + (if (and (ssg-bt-name-passt-p (cdr (assoc 2 ed)) tag) + (setq neu (ssg-bt-wert-neu (cdr (assoc 1 ed)) suche ersatz teil-p))) + (progn + (setq n (1+ n)) + (if (not probe-p) + (entmod (subst (cons 1 neu) (assoc 1 ed) ed)) + ) + ) + ) + (setq e (entnext e)) + ) + (if (and (> n 0) (not probe-p)) (entupd ent)) + n +) + +;; Attribute einer Liste von Einfuegungen ersetzen. Rueckgabe: Anzahl. +(defun ssg-bt-attr-ersetzen-liste (ents tag suche ersatz teil-p probe-p / n) + (setq n 0) + (foreach ent ents + (setq n (+ n (ssg-bt-attr-ersetzen-ent ent tag suche ersatz teil-p probe-p))) + ) + n +) + +;; Auswahlsatz -> Liste von Entity-Namen (nur INSERT) +(defun ssg-bt-ss-inserts (ss / i ent res) + (if ss + (progn + (setq i 0) + (while (setq ent (ssname ss i)) + (if (= (cdr (assoc 0 (entget ent))) "INSERT") + (setq res (cons ent res)) + ) + (setq i (1+ i)) + ) + ) + ) + (reverse res) +) + +;; ------------------------------------------------------------ +;; BEFEHLE +;; ------------------------------------------------------------ + +(defun c:SSG_BLOCK_COUNT ( / muster stat summe) + (setq muster (ssg-bt-muster-waehlen (ssg-text "bt-prompt-waehlen-count"))) + (setq stat (ssg-bt-statistik muster)) + (cond + ((null stat) + (prompt (if muster + (ssg-textf "bt-count-keine" (list (ssg-bt-muster-anzeige muster))) + (ssg-text "bt-count-keine-alle"))) + ) + ((and muster (null (cdr stat))) + (prompt (ssg-textf "bt-count-ergebnis" (list (cdar stat) (caar stat)))) + ) + (t + (setq summe 0) + (foreach z stat + (prompt (ssg-textf "bt-count-zeile" (list (car z) (cdr z)))) + (setq summe (+ summe (cdr z))) + ) + (prompt (ssg-textf "bt-count-summe" (list summe (length stat)))) + ) + ) + (princ) +) + +(defun c:SSG_BLOCK_DELETE ( / muster ents frei gesperrt n) + (setq muster (ssg-bt-muster-waehlen (ssg-text "bt-prompt-waehlen-delete"))) + (if (null muster) + (prompt (ssg-text "bt-delete-kein-muster")) + (progn + (setq ents (ssg-bt-inserts muster)) + (foreach ent ents + (if (ssg-bt-layer-gesperrt-p ent) + (setq gesperrt (cons ent gesperrt)) + (setq frei (cons ent frei)) + ) + ) + (cond + ((null ents) + (prompt (ssg-textf "bt-count-keine" (list (ssg-bt-muster-anzeige muster))))) + ((null frei) + (prompt (ssg-textf "bt-delete-alle-gesperrt" (list (length gesperrt))))) + ((ssg-in-yesno (ssg-textf "bt-delete-frage" (list (length frei) (ssg-bt-muster-anzeige muster))) nil) + (ssg-start (ssg-text "bt-delete-titel") nil) + (setq n 0) + (foreach ent frei + (if (entdel ent) (setq n (1+ n))) + ) + (ssg-end) + (prompt (ssg-textf "bt-delete-ergebnis" (list n))) + (if gesperrt + (prompt (ssg-textf "bt-delete-gesperrt" (list (length gesperrt))))) + ) + (t (prompt (ssg-text "bt-abgebrochen"))) + ) + ) + ) + (princ) +) + +(defun c:SSG_ATTR_ERSETZEN ( / k-bereich k-modus bereich ents muster tag suche ersatz modus teil-p n) + (setq k-bereich (ssg-text "in-keys-bereich") + k-modus (ssg-text "in-keys-modus") + bereich (ssg-in-keyword (ssg-text "bt-attr-bereich") k-bereich + (ssg-in-first-word k-bereich))) + (if (= bereich (ssg-in-first-word k-bereich)) + (progn + (setq muster (ssg-in-string (ssg-text "bt-attr-muster") "*")) + (setq ents (ssg-bt-inserts muster)) + ) + (progn + (prompt (ssg-text "bt-attr-auswahl")) + (setq ents (ssg-bt-ss-inserts (ssget '((0 . "INSERT"))))) + ) + ) + (cond + ((null ents) (prompt (ssg-text "bt-attr-keine-bloecke"))) + (t + (setq tag (ssg-in-string (ssg-text "bt-attr-tag") "*") + suche (ssg-in-string (ssg-text "bt-attr-suche") nil)) + (if (or (null suche) (= suche "")) + (prompt (ssg-text "bt-abgebrochen")) + (progn + (setq ersatz (ssg-in-string (ssg-text "bt-attr-ersatz") "") + modus (ssg-in-keyword (ssg-text "bt-attr-modus") k-modus + (ssg-in-first-word k-modus)) + teil-p (= modus (ssg-in-first-word k-modus))) + (setq n (ssg-bt-attr-ersetzen-liste ents tag suche ersatz teil-p T)) + (cond + ((= n 0) (prompt (ssg-text "bt-attr-nichts"))) + ((ssg-in-yesno (ssg-textf "bt-attr-frage" (list n (length ents))) T) + (ssg-start (ssg-text "bt-attr-titel") nil) + (setq n (ssg-bt-attr-ersetzen-liste ents tag suche ersatz teil-p nil)) + (ssg-end) + (prompt (ssg-textf "bt-attr-ergebnis" (list n)))) + (t (prompt (ssg-text "bt-abgebrochen"))) + ) + ) + ) + ) + ) + (princ) +) + +(princ) diff --git a/Lisp/ssg_core.lsp b/Lisp/ssg_core.lsp index 298e43c..cfe95c8 100644 --- a/Lisp/ssg_core.lsp +++ b/Lisp/ssg_core.lsp @@ -1622,6 +1622,44 @@ ) +;; ------------------------------------------------------------ +;; DCL ZUR LAUFZEIT GENERIEREN +;; ------------------------------------------------------------ +;; Escaped Anfuehrungszeichen in einem DCL-Label-String ("->''), damit ein +;; Label mit Anfuehrungszeichen (z.B. aus Nutzerdaten oder Zoll-Angaben) die +;; DCL-Zeile nicht vorzeitig beendet. Fachlich identisch zu +;; geruest-dcl-label (GeruestInsert.lsp), aber hier als generischer +;; Kernel-Helfer fuer jeden Aufrufer, der DCL-Text zusammenbaut. +(defun ssg-dcl-label-escape (text / i c out) + (setq i 0 out "") + (while (< i (strlen text)) + (setq c (substr text (1+ i) 1)) + (setq out (strcat out (if (= c "\"") "''" c))) + (setq i (1+ i)) + ) + out +) + +;; Schreibt eine Liste von Textzeilen als temporaere DCL-Datei und liefert +;; deren Pfad zurueck (oder nil bei Fehler, z.B. wenn open scheitert). Der +;; Aufrufer ist fuer load_dialog/unload_dialog UND das Loeschen der Datei +;; (vl-file-delete) danach selbst verantwortlich - analog zum bestehenden +;; Muster in GeruestInsert.lsp (geruest-dcl-schreiben), hier nur als +;; generischer Baustein fuer WEITERE Aufrufer (GeruestInsert.lsp selbst +;; bleibt unveraendert, um dessen Code nicht anzufassen). +(defun ssg-dcl-temp-schreiben (praefix zeilen / pfad f) + (setq pfad (vl-filename-mktemp praefix nil ".dcl")) + (if (setq f (open pfad "w")) + (progn + (foreach zeile zeilen (write-line zeile f)) + (close f) + pfad + ) + nil + ) +) + + ;; ------------------------------------------------------------ ;; REKURSIVER JSON-PARSER ;; ------------------------------------------------------------ diff --git a/Lisp/ssg_input.lsp b/Lisp/ssg_input.lsp new file mode 100644 index 0000000..93c59dc --- /dev/null +++ b/Lisp/ssg_input.lsp @@ -0,0 +1,162 @@ +;; ============================================================ +;; SSG_INPUT.LSP - Generische Eingabe-Wrapper (ssg-in-*) +;; +;; Allgemeingueltige Abfragen mit Vorgabewert, Anzeige "" im +;; Prompt und Enter = Vorgabe (Idee von g_in/g_ky aus den ACADSST- +;; Makros). Steht NEBEN den VF-spezifischen vfl-in-* aus +;; vf_linienzug.lsp; diese bleiben unveraendert. +;; +;; Alle Funktionen liefern den eingegebenen Wert oder die Vorgabe. +;; ESC bricht wie ueblich ueber *error* ab (z.B. ssg-errhan). +;; Ist kein Wert eingegeben und keine Vorgabe da, ist die Rueckgabe nil. +;; +;; Optionaler Hook (z.B. fuer ein Eingabe-Journal): +;; (setq *ssg-in-hook* 'meine-funktion) +;; (defun meine-funktion (art prompt wert) ...) ;; art = "REAL" "INT" +;; ;; "STRING" "POINT" +;; ;; "DIST" "KEYWORD" +;; ;; "YESNO" +;; Ein Hook, der einen Fehler wirft, wird abgeschaltet. +;; +;; Setzt ssg_core.lsp (und fuer ssg-in-yesno ssg_lang.lsp) voraus. +;; ============================================================ + +(vl-load-com) + +;; ------------------------------------------------------------ +;; REINE HILFSFUNKTIONEN (ohne Zeichnung, in test_unit getestet) +;; ------------------------------------------------------------ + +;; Vorgabe einsetzen, wenn kein Wert eingegeben wurde +(defun ssg-in-default (wert default) + (if wert wert default) +) + +;; Reelle Zahl ohne ueberfluessige Nullen: 5.0 -> "5", 2.50 -> "2.5" +(defun ssg-in-fmt-real (v / s) + (setq s (rtos v 2 6)) + (if (vl-string-position 46 s) + (progn + (while (= (substr s (strlen s)) "0") + (setq s (substr s 1 (1- (strlen s)))) + ) + (if (= (substr s (strlen s)) ".") + (setq s (substr s 1 (1- (strlen s)))) + ) + ) + ) + s +) + +;; Punkt als "x,y,z" (fehlende Z-Werte als 0) +(defun ssg-in-fmt-pt (p) + (strcat (ssg-in-fmt-real (car p)) "," + (ssg-in-fmt-real (cadr p)) "," + (ssg-in-fmt-real (if (caddr p) (caddr p) 0.0))) +) + +;; Prompt aufbauen: " : " bzw. ": " ohne Vorgabe +(defun ssg-in-prompt (text default-str) + (strcat text + (if default-str (strcat " <" default-str ">") "") + ": ") +) + +;; Erstes Wort eines Schluesselwort-Strings: "Ja Nein" -> "Ja" +(defun ssg-in-first-word (schluessel / pos) + (if (setq pos (vl-string-search " " schluessel)) + (substr schluessel 1 pos) + schluessel + ) +) + +;; Zweites Wort: "Ja Nein" -> "Nein" +(defun ssg-in-second-word (schluessel / pos) + (if (setq pos (vl-string-search " " schluessel)) + (substr schluessel (+ pos 2)) + "" + ) +) + +;; ------------------------------------------------------------ +;; HOOK +;; ------------------------------------------------------------ +(defun ssg-in-notify (art text wert / res) + (if *ssg-in-hook* + (progn + (setq res (vl-catch-all-apply *ssg-in-hook* (list art text wert))) + (if (vl-catch-all-error-p res) (setq *ssg-in-hook* nil)) + ) + ) + wert +) + +;; ------------------------------------------------------------ +;; ABFRAGEN +;; text = Prompt ohne Doppelpunkt, z.B. "\nHoehe" +;; ------------------------------------------------------------ + +(defun ssg-in-real (text default / w) + (setq w (getreal (ssg-in-prompt text (if default (ssg-in-fmt-real default))))) + (ssg-in-notify "REAL" text (ssg-in-default w default)) +) + +(defun ssg-in-int (text default / w) + (setq w (getint (ssg-in-prompt text (if default (itoa default))))) + (ssg-in-notify "INT" text (ssg-in-default w default)) +) + +;; String (mit Leerzeichen). Enter ohne Eingabe -> Vorgabe. +(defun ssg-in-string (text default / w) + (setq w (getstring T (ssg-in-prompt text default))) + (if (= w "") (setq w nil)) + (ssg-in-notify "STRING" text (ssg-in-default w default)) +) + +;; Punkt. base = Gummiband-Basispunkt oder nil, default = Punkt oder nil. +(defun ssg-in-point (text default base / w) + (setq w (if base + (getpoint base (ssg-in-prompt text (if default (ssg-in-fmt-pt default)))) + (getpoint (ssg-in-prompt text (if default (ssg-in-fmt-pt default)))) + )) + (ssg-in-notify "POINT" text (ssg-in-default w default)) +) + +;; Abstand. base = Basispunkt oder nil. +(defun ssg-in-dist (text default base / w) + (setq w (if base + (getdist base (ssg-in-prompt text (if default (ssg-in-fmt-real default)))) + (getdist (ssg-in-prompt text (if default (ssg-in-fmt-real default)))) + )) + (ssg-in-notify "DIST" text (ssg-in-default w default)) +) + +;; Schluesselwort ohne Hook (intern, auch von ssg-in-yesno genutzt). +(defun ssg-in-keyword-raw (text schluessel default / w) + (initget 0 schluessel) + (setq w (getkword + (ssg-in-prompt + (strcat text " [" (vl-string-translate " " "/" schluessel) "]") + default))) + (ssg-in-default w default) +) + +;; Schluesselwort. schluessel im initget-Format, z.B. "Alle Auswahl". +;; Rueckgabe: gewaehltes Schluesselwort (volle Schreibweise) oder default. +(defun ssg-in-keyword (text schluessel default) + (ssg-in-notify "KEYWORD" text (ssg-in-keyword-raw text schluessel default)) +) + +;; Ja/Nein-Abfrage, Rueckgabe T bei Ja, nil bei Nein (anders als ssg-ques, +;; das T fuer die NICHT-vorgegebene Antwort liefert). +;; default = T -> Enter bedeutet Ja, nil -> Enter bedeutet Nein. +;; Die Schluesselwoerter kommen aus ssg-text "in-yesno-keys" (z.B. "Ja Nein"). +(defun ssg-in-yesno (text default / schluessel ja nein w) + (setq schluessel (ssg-text "in-yesno-keys") + ja (ssg-in-first-word schluessel) + nein (ssg-in-second-word schluessel)) + (setq w (ssg-in-keyword-raw text schluessel (if default ja nein))) + (ssg-in-notify "YESNO" text (= w ja)) +) + +(princ) diff --git a/Lisp/ssg_load.lsp b/Lisp/ssg_load.lsp index 9228d76..c806db1 100644 --- a/Lisp/ssg_load.lsp +++ b/Lisp/ssg_load.lsp @@ -28,6 +28,7 @@ (load (strcat base-path "ssg_dbg.lsp") "ssg_dbg.lsp nicht gefunden") (load (strcat base-path "ssg_dialog.lsp") "ssg_dialog.lsp nicht gefunden") (load (strcat base-path "ssg_layer.lsp") "ssg_layer.lsp nicht gefunden") + (load (strcat base-path "ssg_input.lsp") "ssg_input.lsp nicht gefunden") ;; Gemeinsame KS-Extraktion + Einfuegeprimitiven - MUSS vor VarioFoerderer ;; und Gefaellestrecke geladen werden (beide nutzen sie). (load (strcat base-path "ssg_ks_insert.lsp") "ssg_ks_insert.lsp nicht gefunden") @@ -39,7 +40,10 @@ (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, OmniModulInsert, SSG_LIB_Commands") + ;; Werkzeuge: ssg_texttools nutzt ssg_blocktools, beide nutzen ssg_input + (load (strcat base-path "ssg_blocktools.lsp") "ssg_blocktools.lsp nicht gefunden") + (load (strcat base-path "ssg_texttools.lsp") "ssg_texttools.lsp nicht gefunden") + (prompt "\nSSG-Bibliotheken geladen: ssg_core, ssg_lang, ssg_dbg, ssg_dialog, ssg_layer, ssg_input, KreiselInsert, VarioFoerderer, Gefaellestrecke, export, OmniModulInsert, SSG_LIB_Commands, ssg_blocktools, ssg_texttools") (princ) ) diff --git a/Lisp/ssg_texttools.lsp b/Lisp/ssg_texttools.lsp new file mode 100644 index 0000000..7b8866b --- /dev/null +++ b/Lisp/ssg_texttools.lsp @@ -0,0 +1,285 @@ +;; ============================================================ +;; SSG_TEXTTOOLS.LSP - Text-Werkzeuge +;; +;; Ursprung: ACADSST/MENU/CHGTEXT.LSP (Suchen/Ersetzen), PARTEXT.LSP +;; (Tbpl: parallel beschriften), ALTHOEHE.LSP (Tsz), ALTSTIL.LSP (Ts). +;; Neu geschrieben im ssg-Stil (Undo-Gruppe, i18n, ssg-in-*). +;; +;; Befehle: +;; SSG_TEXT_ERSETZEN Zeichenketten in TEXT, MTEXT und Block- +;; Attributen suchen/ersetzen (mit Vorschau) +;; SSG_TEXT_PARALLEL Text parallel zu Linie/Polylinie/Bogen setzen +;; (lesbar ausgerichtet, mit Abstand zur Linie) +;; SSG_TEXT_HOEHE Texthoehe von TEXT/MTEXT aendern +;; SSG_TEXT_STIL Textstil von TEXT/MTEXT aendern +;; +;; Arbeitet in der XY-Ebene des Welt-KS (Textrotation = WKS-Winkel). +;; Setzt ssg_core.lsp, ssg_lang.lsp, ssg_input.lsp und ssg_blocktools.lsp +;; (ssg-bt-wert-neu, ssg-bt-attr-ersetzen-ent) voraus. +;; ============================================================ + +(vl-load-com) + +;; ------------------------------------------------------------ +;; REINE HILFSFUNKTIONEN (in test_unit getestet) +;; ------------------------------------------------------------ + +;; Winkel (Bogenmass) so drehen, dass ein Text lesbar bleibt: Winkel im +;; Bereich (90 Grad, 270 Grad] werden um 180 Grad gedreht. Ergebnis in +;; [0, 2*pi). +(defun ssg-tt-lesbarer-winkel (a) + (setq a (rem a (* 2.0 pi))) + (if (< a 0.0) (setq a (+ a (* 2.0 pi)))) + (if (and (> a (+ (/ pi 2.0) 1e-9)) + (<= a (+ (* 1.5 pi) 1e-9))) + (setq a (- a pi)) + ) + (if (< a 0.0) (setq a (+ a (* 2.0 pi)))) + a +) + +;; Punkt um abstand in Richtung winkel verschieben (2D, Z bleibt erhalten) +(defun ssg-tt-versetzt (p winkel abstand) + (list (+ (car p) (* abstand (cos winkel))) + (+ (cadr p) (* abstand (sin winkel))) + (if (caddr p) (caddr p) 0.0)) +) + +;; ------------------------------------------------------------ +;; AUSWAHL +;; ------------------------------------------------------------ + +;; Auswahlsatz der angegebenen Typen holen (z.B. "TEXT,MTEXT,INSERT"). +;; alle-p = T -> alle im aktuellen Layout, nil -> Benutzerwahl +(defun ssg-tt-ss-holen (alle-p typen) + (if alle-p + (ssget "_X" (list (cons 0 typen) (cons 410 (getvar "CTAB")))) + (ssget (list (cons 0 typen))) + ) +) + +;; Auswahlsatz -> Liste von Entity-Namen mit Typ-Filter ("TEXT" "MTEXT" ...) +(defun ssg-tt-ss-liste (ss typen / i ent res) + (if ss + (progn + (setq i 0) + (while (setq ent (ssname ss i)) + (if (member (cdr (assoc 0 (entget ent))) typen) + (setq res (cons ent res)) + ) + (setq i (1+ i)) + ) + ) + ) + (reverse res) +) + +;; ------------------------------------------------------------ +;; SUCHEN / ERSETZEN +;; ------------------------------------------------------------ + +;; TEXT/MTEXT-Inhalte ersetzen (oder nur zaehlen). Rueckgabe: Anzahl Texte. +(defun ssg-tt-text-ersetzen-liste (ents suche ersatz teil-p probe-p + / n obj alt neu) + (setq n 0) + (foreach ent ents + (setq obj (vlax-ename->vla-object ent) + alt (vla-get-textstring obj) + neu (ssg-bt-wert-neu alt suche ersatz teil-p)) + (if neu + (if probe-p + (setq n (1+ n)) + (if (not (vl-catch-all-error-p + (vl-catch-all-apply 'vla-put-textstring (list obj neu)))) + (setq n (1+ n)) + ) + ) + ) + ) + n +) + +;; Texte + Attribute in einem Auswahlsatz ersetzen/zaehlen. +;; Rueckgabe: (anzahl-texte . anzahl-attribute) +(defun ssg-tt-ersetzen-ss (ss suche ersatz teil-p probe-p) + (cons + (ssg-tt-text-ersetzen-liste + (ssg-tt-ss-liste ss '("TEXT" "MTEXT")) suche ersatz teil-p probe-p) + (ssg-bt-attr-ersetzen-liste + (ssg-tt-ss-liste ss '("INSERT")) "*" suche ersatz teil-p probe-p) + ) +) + +(defun c:SSG_TEXT_ERSETZEN ( / k-bereich k-modus alle-p ss suche ersatz modus teil-p z) + (setq k-bereich (ssg-text "in-keys-bereich") + k-modus (ssg-text "in-keys-modus") + alle-p (= (ssg-in-keyword (ssg-text "tt-bereich") k-bereich + (ssg-in-first-word k-bereich)) + (ssg-in-first-word k-bereich))) + (if (not alle-p) (prompt (ssg-text "tt-auswahl"))) + (setq ss (ssg-tt-ss-holen alle-p "TEXT,MTEXT,INSERT")) + (if (null ss) + (prompt (ssg-text "tt-keine-objekte")) + (progn + (setq suche (ssg-in-string (ssg-text "tt-suche") nil)) + (if (or (null suche) (= suche "")) + (prompt (ssg-text "tt-abgebrochen")) + (progn + (setq ersatz (ssg-in-string (ssg-text "tt-ersatz") "") + modus (ssg-in-keyword (ssg-text "tt-modus") k-modus + (ssg-in-first-word k-modus)) + teil-p (= modus (ssg-in-first-word k-modus))) + (setq z (ssg-tt-ersetzen-ss ss suche ersatz teil-p T)) + (cond + ((= 0 (+ (car z) (cdr z))) (prompt (ssg-text "tt-nichts"))) + ((ssg-in-yesno (ssg-textf "tt-frage" (list (car z) (cdr z))) T) + (ssg-start (ssg-text "tt-ersetzen-titel") nil) + (setq z (ssg-tt-ersetzen-ss ss suche ersatz teil-p nil)) + (ssg-end) + (prompt (ssg-textf "tt-ergebnis" (list (car z) (cdr z))))) + (t (prompt (ssg-text "tt-abgebrochen"))) + ) + ) + ) + ) + ) + (princ) +) + +;; ------------------------------------------------------------ +;; PARALLEL BESCHRIFTEN +;; ------------------------------------------------------------ + +;; Winkel der Kurve (Linie, Polylinie, Bogen, ...) am Punkt pt (WKS). +;; Rueckgabe: Winkel in Bogenmass oder nil, wenn nicht ermittelbar. +(defun ssg-tt-kurven-winkel (ent pt / obj cp param deriv) + (setq obj (vlax-ename->vla-object ent)) + (setq cp (vl-catch-all-apply 'vlax-curve-getclosestpointto (list obj pt))) + (if (or (vl-catch-all-error-p cp) (null cp)) + nil + (progn + (setq param (vl-catch-all-apply 'vlax-curve-getparamatpoint (list obj cp))) + (if (or (vl-catch-all-error-p param) (null param)) + nil + (progn + (setq deriv (vl-catch-all-apply 'vlax-curve-getfirstderiv (list obj param))) + (if (or (vl-catch-all-error-p deriv) (null deriv)) + nil + (list (atan (cadr deriv) (car deriv)) cp) + ) + ) + ) + ) + ) +) + +;; TEXT erzeugen (unten mittig, Gruppe 73 = 1 damit Gruppe 11 gilt) +(defun ssg-tt-text-machen (p hoehe string winkel) + (entmake (list '(0 . "TEXT") + (cons 8 (getvar "CLAYER")) + (cons 7 (getvar "TEXTSTYLE")) + (cons 10 p) + (cons 11 p) + (cons 40 hoehe) + (cons 1 string) + (cons 50 winkel) + (cons 72 1) + (cons 73 1))) +) + +(defun c:SSG_TEXT_PARALLEL ( / sel info a ar p text) + (ssg-start (ssg-text "tt-parallel-titel") nil) + (if (or (null *ssg-tt-hoehe*) (<= *ssg-tt-hoehe* 0.0)) + (setq *ssg-tt-hoehe* (if (> (getvar "TEXTSIZE") 0.0) (getvar "TEXTSIZE") 2.5)) + ) + (setq *ssg-tt-hoehe* (ssg-in-real (ssg-text "tt-hoehe") *ssg-tt-hoehe*)) + (if (null *ssg-tt-abstand*) + (setq *ssg-tt-abstand* (* 0.5 *ssg-tt-hoehe*)) + ) + (setq *ssg-tt-abstand* (ssg-in-real (ssg-text "tt-abstand") *ssg-tt-abstand*)) + (while (setq sel (entsel (ssg-text "tt-parallel-waehlen"))) + (setq info (ssg-tt-kurven-winkel (car sel) (trans (cadr sel) 1 0))) + (if (null info) + (prompt (ssg-text "tt-parallel-keine-kurve")) + (progn + (setq a (car info) + ar (ssg-tt-lesbarer-winkel a) + p (ssg-tt-versetzt (cadr info) (+ ar (/ pi 2.0)) *ssg-tt-abstand*) + text (ssg-in-string (ssg-text "tt-text") *ssg-tt-text*)) + (if (and text (/= text "")) + (progn + (setq *ssg-tt-text* text) + (if (not (ssg-tt-text-machen p *ssg-tt-hoehe* text ar)) + (prompt (ssg-text "tt-parallel-fehler")) + ) + ) + ) + ) + ) + ) + (ssg-end) + (princ) +) + +;; ------------------------------------------------------------ +;; HOEHE / STIL AENDERN +;; ------------------------------------------------------------ + +(defun c:SSG_TEXT_HOEHE ( / ents hoehe n) + (prompt (ssg-text "tt-auswahl")) + (setq ents (ssg-tt-ss-liste (ssget '((0 . "TEXT,MTEXT"))) '("TEXT" "MTEXT"))) + (if (null ents) + (prompt (ssg-text "tt-keine-objekte")) + (progn + (setq hoehe (ssg-in-real (ssg-text "tt-hoehe") + (if (> (getvar "TEXTSIZE") 0.0) (getvar "TEXTSIZE") nil))) + (if (or (null hoehe) (<= hoehe 0.0)) + (prompt (ssg-text "tt-abgebrochen")) + (progn + (ssg-start (ssg-text "tt-hoehe-titel") nil) + (setq n 0) + (foreach ent ents + (if (not (vl-catch-all-error-p + (vl-catch-all-apply 'vla-put-height + (list (vlax-ename->vla-object ent) hoehe)))) + (setq n (1+ n)) + ) + ) + (ssg-end) + (prompt (ssg-textf "tt-geaendert" (list n))) + ) + ) + ) + ) + (princ) +) + +(defun c:SSG_TEXT_STIL ( / ents stil n) + (prompt (ssg-text "tt-auswahl")) + (setq ents (ssg-tt-ss-liste (ssget '((0 . "TEXT,MTEXT"))) '("TEXT" "MTEXT"))) + (if (null ents) + (prompt (ssg-text "tt-keine-objekte")) + (progn + (setq stil (ssg-in-string (ssg-text "tt-stil") (getvar "TEXTSTYLE"))) + (cond + ((or (null stil) (null (tblsearch "STYLE" stil))) + (prompt (ssg-textf "tt-stil-fehlt" (list (if stil stil ""))))) + (t + (ssg-start (ssg-text "tt-stil-titel") nil) + (setq n 0) + (foreach ent ents + (if (not (vl-catch-all-error-p + (vl-catch-all-apply 'vla-put-stylename + (list (vlax-ename->vla-object ent) stil)))) + (setq n (1+ n)) + ) + ) + (ssg-end) + (prompt (ssg-textf "tt-geaendert" (list n)))) + ) + ) + ) + (princ) +) + +(princ) diff --git a/Lisp/vf_core.lsp b/Lisp/vf_core.lsp index ec18f05..4b6420e 100644 --- a/Lisp/vf_core.lsp +++ b/Lisp/vf_core.lsp @@ -80,6 +80,11 @@ ;; Von den Menue-Wrappern gesetzt, damit die Konsolen-/Dialog-Frage ;; entfaellt. nil = interaktiv fragen (Direktaufruf VarioFoerderer). (if (null *vf-vorauswahl-eingabemodus*) (setq *vf-vorauswahl-eingabemodus* nil)) +;; Vorauswahl des Linienzug-Modus (1-5): von jedem der fuenf Linienzug- +;; Menue-Wrapper (c:LinienzugVarioFoerdererManuell/-Zielhoehe/-Stueckweise, +;; c:QcLinieMitGf/-UForm) gesetzt - der frueher hier noetige Modus-Dialog/ +;; Konsolen-Fallback ist dadurch komplett entfallen (siehe c:VarioFoerderer). +(if (null *vf-vorauswahl-linienzug-modus*) (setq *vf-vorauswahl-linienzug-modus* nil)) ;; Registriert einen Foerderanlagen-Typ. ;; berechne-fn: '(lambda (deltaL deltaH richtung seite) ...) @@ -703,36 +708,23 @@ (setq i (1+ i))) eingabe) -(defun vf-eingabe-abfragen ( / eingabe-modus line-points startpunkt endpunkt differenz - deltaL deltaH deltaX deltaY richtung antwort hz-winkel) - ;; Eingabemodus: von einem Menue-Wrapper vorbelegt (dann keine Frage) oder - ;; interaktiv. Die Vorauswahl gilt nur EINMAL und wird hier verbraucht, - ;; damit ein spaeterer Direktaufruf wieder fragt. - (if *vf-vorauswahl-eingabemodus* - (setq antwort *vf-vorauswahl-eingabemodus* - *vf-vorauswahl-eingabemodus* nil) +;; 3D-Linie picken und daraus deltaL/deltaH/richtung/hz/startpunkt (inkl. Z) +;; ableiten - fuer den GUI-Ablauf (vf-dialog-ablauf): ersetzt dort die +;; separate Startpunkt-Auswahl UND liefert die Geometrie-Felder als Prefill +;; fuer den Basis-Dialog (vfs-dialog-eingabe-basis). Rueckgabe: (deltaL +;; deltaH richtung startpunkt hz) oder nil, wenn keine gueltige Linie +;; gewaehlt wurde (Aufrufer bricht dann ab, statt wie frueher per (exit) +;; die ganze Befehlssitzung zu beenden). +(defun vfc-3dlinie-erfassen ( / line-points startpunkt endpunkt differenz + deltaL deltaH deltaX deltaY richtung hz-winkel) + (princ (ssg-text "vfc-modus-3dlinie-header")) + (command "BKS" "W") + (princ (ssg-text "vfc-bks-weltkoordinaten")) + (setq line-points + (get-line-start-end-points (ssg-text "vfc-bitte-3dlinie-foerderstrecke"))) + (if (null line-points) + (progn (alert (ssg-text "vfc-alert-keine-gueltige-linie")) nil) (progn - (princ (ssg-text "vfc-eingabemodus-header")) - (princ (ssg-text "vfc-eingabemodus-3dlinie")) - (princ (ssg-text "vfc-eingabemodus-werte")) - (setq antwort (getstring (ssg-text "vfc-prompt-wahl-1-2-ohne-default"))) - ) - ) - (cond - ((= antwort "1") (setq eingabe-modus "Linie")) - ((= antwort "2") (setq eingabe-modus "Wert")) - (t (setq eingabe-modus "Linie") (princ (ssg-text "vfc-modus-3dlinie-fallback"))) - ) - (if (= eingabe-modus "Linie") - (progn - (princ (ssg-text "vfc-modus-3dlinie-header")) - (command "BKS" "W") - (princ (ssg-text "vfc-bks-weltkoordinaten")) - (setq line-points - (get-line-start-end-points (ssg-text "vfc-bitte-3dlinie-foerderstrecke"))) - (if (null line-points) - (progn (alert (ssg-text "vfc-alert-keine-gueltige-linie")) (exit)) - ) (setq startpunkt (car line-points) endpunkt (cadr line-points)) (setq differenz (punkt-differenz startpunkt endpunkt)) @@ -750,41 +742,14 @@ (progn (setq richtung "Ab") (setq deltaH (abs deltaH)) (princ (ssg-text "vfc-foerderrichtung-ab"))) ) - ) - (progn - (princ (ssg-text "vfc-modus-werteingabe-header")) - (setq deltaL (getreal (ssg-textf "vfc-prompt-delta-l" (list (chr 916))))) - (if (null deltaL) (setq deltaL 15000)) - (setq deltaH (getreal (ssg-textf "vfc-prompt-delta-h" (list (chr 916))))) - (if (null deltaH) (setq deltaH 3000)) - ;; Foerderrichtung bleibt eine einfache Auf/Ab-Frage - die horizontale - ;; Zwischenstrecke ist geometrisch NUR bei "Ab" ueberhaupt erreichbar - ;; (die festen 3-Grad-Elemente wirken immer absenkend) und wird daher - ;; erst SPAETER, nach Bibliotheks-Init, als Zusatzoption in der - ;; Winkel-Auswahl angeboten (siehe c:VarioFoerderer) - einheitlich fuer - ;; Werteingabe UND 3D-Linie. - (princ (ssg-text "vfc-foerderrichtung-menu")) - (setq antwort (getstring (ssg-text "vfc-prompt-wahl-1-2-ohne-default"))) - (if (= antwort "2") (setq richtung "Ab") (setq richtung "Auf")) - (setq startpunkt (getpoint (ssg-text "vfc-prompt-startpunkt-aus"))) - (if (null startpunkt) (setq startpunkt '(0 0 0))) - ;; Horizontale Fahrtrichtung waehlen (analog Gefaellestrecke Modus 1) - (princ (ssg-text "vfc-fahrtrichtung-header")) - (princ (ssg-textf "vfc-fahrtrichtung-0" (list grad-zeichen))) - (princ (ssg-textf "vfc-fahrtrichtung-90" (list grad-zeichen))) - (princ (ssg-textf "vfc-fahrtrichtung-180" (list grad-zeichen))) - (princ (ssg-textf "vfc-fahrtrichtung-270" (list grad-zeichen))) - (setq antwort (getstring (ssg-text "prompt-wahl-1-2-3-4"))) - (cond - ((= antwort "2") (setq hz-winkel 90.0)) - ((= antwort "3") (setq hz-winkel 180.0)) - ((= antwort "4") (setq hz-winkel 270.0)) - (t (setq hz-winkel 0.0)) - ) - (princ (ssg-textf "vfc-fahrtrichtung-ergebnis" (list (rtos hz-winkel 2 1) grad-zeichen))) + ;; Einfuegehoehe (Z des Startpunkts) wird NICHT mehr hier auf der + ;; Konsole abgefragt - sie geht als Prefill "einfuegehoehe" in den + ;; Basis-Dialog (vfs-dialog-eingabe-basis) ein, der dasselbe Feld + ;; ohnehin per GUI anzeigt und ueberschreibbar macht (Vorschlag = Z + ;; des Linien-Startpunkts, wie zuvor beim getreal-Prompt). + (list deltaL deltaH richtung startpunkt hz-winkel) ) ) - (list deltaL deltaH richtung startpunkt hz-winkel eingabe-modus) ) ;; ============================================================ @@ -1055,26 +1020,144 @@ (list vf-insert vf-endpunkt) ) -;; Neuanlage (Menue/Konsole -> Dialog) fuer einen Registry-Typ: Startpunkt -;; (X/Y) picken, Basis-Dialog, dann vf-dialog-berechnen-einfuegen. +;; Startpunkt der Anlage per Dialog waehlen (Punktwahl-Button + Anzeige, +;; dcl/variofoerderer.dcl Tile vfc_startpunkt) - dasselbe "Schliessen- +;; Picken-Neu-Zeigen"-Muster wie der Linienzug-Wizard +;; (vflw-gruppe-punkt-hoehe-impl, vf_linienzug.lsp:697), aber ohne dessen +;; Wizard-Pending-Queue: der Punkt wird direkt zurueckgegeben. kopf/ +;; pt_anzeige kommen per set_tile aus ssg-text - anders als +;; vfc-dialog-typ-waehlen also KEIN *ssg-lang*-Gate noetig, der Dialog +;; funktioniert in jeder Sprache. Rueckgabe: Punkt (Liste X Y Z) oder nil, +;; wenn kein Dialog gezeigt werden konnte/durfte (GUI aus) ODER abgebrochen +;; wurde - der Aufrufer (vf-dialog-ablauf) faellt dann auf den alten +;; Konsolen-getpoint zurueck. +(defun vfc-dialog-startpunkt-waehlen (kopf-key / dcl-pfad dat ergebnis gpunkt fertig) + (if (or (not (car (atoms-family 1 '("SSG-GUI-P")))) (ssg-gui-p)) + (progn + (setq fertig nil) + (while (not fertig) + (setq dcl-pfad (strcat (getenv "DXFM_DCL") "/variofoerderer.dcl")) + (setq dat (load_dialog dcl-pfad)) + (if (not (new_dialog "vfc_startpunkt" dat)) + (progn + (if (and dat (>= dat 0)) (unload_dialog dat)) + (setq fertig t gpunkt nil) + ) + (progn + (set_tile "kopf" (ssg-text "vfc-dlg-startpunkt-kopf")) + (if gpunkt + (set_tile "pt_anzeige" + (strcat "X=" (rtos (car gpunkt) 2 1) + " Y=" (rtos (cadr gpunkt) 2 1) + " Z=" (rtos (caddr gpunkt) 2 1))) + ) + (action_tile "pick" "(done_dialog 2)") + (action_tile "accept" "(done_dialog 1)") + (action_tile "cancel" "(done_dialog 0)") + (setq ergebnis (start_dialog)) + (unload_dialog dat) + (cond + ((= ergebnis 2) (setq gpunkt (getpoint (ssg-text kopf-key)))) + ((= ergebnis 1) (setq fertig t)) + (t (setq fertig t gpunkt nil)) + ) + ) + ) + ) + gpunkt + ) + nil + ) +) + +;; Neuanlage (Menue/Konsole -> Dialog) fuer einen Registry-Typ. Zwei +;; Eingabemodi fuer die Geometrie (deltaL/deltaH/richtung/hz/Startpunkt): +;; "1" 3D-Linie: Linie picken (vfc-3dlinie-erfassen), die abgeleiteten +;; Werte gehen als Prefill in den Basis-Dialog - NUR Seite/ +;; Motorseite/Darstellung/Geruest bleiben dort fuer den Anwender offen. +;; "2" Werte: wie bisher Startpunkt (X/Y) picken, Basis-Dialog ohne +;; Geometrie-Prefill (Defaults). +;; Eingabemodus kommt per Vorauswahl (Menue-Wrapper c:StandardVarioFoerderer3D +;; / c:EtageVarioFoerderer3D setzen *vf-vorauswahl-eingabemodus* "1"/"2") oder +;; wird - wie zuvor im Konsolen-Ablauf - interaktiv erfragt; die Vorauswahl +;; gilt nur einmal. Ersetzt den frueheren Rueckfall auf den rein interaktiven +;; Konsolen-Ablauf (vf-eingabe-abfragen, entfernt) fuer die 3D-Linie: dieser +;; lief komplett an der GUI vorbei und fragte Seite/Fahrtrichtung/etc. erneut +;; auf der Kommandozeile ab, obwohl der Basis-Dialog dieselben Felder laengst +;; per GUI anbietet. (defun vf-dialog-ablauf (typ vf-nummer horizontal-p / eingabe deltaL deltaH richtung z-start hz seite startpunkt startpunkt-xy dim - geruest-einzelmodul geruest-typ motorseite) - (setq startpunkt-xy (getpoint (ssg-text "vfc-prompt-startpunkt-aus"))) - (if (null startpunkt-xy) (setq startpunkt-xy '(0 0 0))) - (setq eingabe (vfs-dialog-eingabe-basis nil)) - (if (null eingabe) + geruest-einzelmodul geruest-typ motorseite + antwort dlg-modus linie-erg basis-prefill abbruch) + (setq abbruch nil) + (if *vf-vorauswahl-eingabemodus* + (setq antwort *vf-vorauswahl-eingabemodus* + *vf-vorauswahl-eingabemodus* nil) + (progn + (setq dlg-modus (vfc-dialog-eingabemodus-waehlen)) + (cond + ((eq dlg-modus 'ABBRUCH) (setq antwort "2" abbruch t)) + (dlg-modus (setq antwort dlg-modus)) + (t + ;; Dialog nicht verfuegbar/nicht erlaubt (GUI aus, andere Sprache) + ;; - alte Konsolen-Abfrage unveraendert. + (princ (ssg-text "vfc-eingabemodus-header")) + (princ (ssg-text "vfc-eingabemodus-3dlinie")) + (princ (ssg-text "vfc-eingabemodus-werte")) + (setq antwort (getstring (ssg-text "vfc-prompt-wahl-1-2-ohne-default"))) + ) + ) + ) + ) + (if (not abbruch) + (if (= antwort "1") + (progn + (setq linie-erg (vfc-3dlinie-erfassen)) + (if (null linie-erg) + (setq abbruch t) + (progn + (setq deltaL (nth 0 linie-erg) + deltaH (nth 1 linie-erg) + richtung (nth 2 linie-erg) + startpunkt (nth 3 linie-erg) + hz (nth 4 linie-erg)) + (setq basis-prefill + (list (cons "deltaL" deltaL) (cons "deltaH" deltaH) (cons "richtung" richtung) + (cons "einfuegehoehe" (caddr startpunkt)) (cons "hz" hz))) + ) + ) + ) + (progn + (setq startpunkt-xy (vfc-dialog-startpunkt-waehlen "vfc-prompt-startpunkt-aus")) + (if (null startpunkt-xy) + (setq startpunkt-xy (getpoint (ssg-text "vfc-prompt-startpunkt-aus")))) + (if (null startpunkt-xy) (setq startpunkt-xy '(0 0 0))) + (setq startpunkt startpunkt-xy) + (setq basis-prefill nil) + ) + ) + ) + (if abbruch (princ (ssg-text "vfc-vorgang-abgebrochen")) (progn - (vf-basis-eingabe-entpacken eingabe) - (setq startpunkt (list (car startpunkt-xy) (cadr startpunkt-xy) z-start)) - ;; Gewaehlte Dimension (2D/3D) + Motorseite fuer den Bau erzwingen, danach zurueck. - (setq *ssg-ils-dim* dim) - (setq *vf-motorseite* motorseite) - (vf-dialog-berechnen-einfuegen typ vf-nummer deltaL deltaH richtung startpunkt hz seite nil - geruest-einzelmodul geruest-typ horizontal-p) - (setq *ssg-ils-dim* nil) - (setq *vf-motorseite* nil) + (setq eingabe (vfs-dialog-eingabe-basis basis-prefill)) + (if (null eingabe) + (princ (ssg-text "vfc-vorgang-abgebrochen")) + (progn + (vf-basis-eingabe-entpacken eingabe) + ;; X/Y kommen aus dem Linien-Startpunkt (Modus 1) oder der + ;; separaten Punktwahl (Modus 2); Z immer aus dem Basis-Dialog + ;; (dort vorbelegt, aber ueberschreibbar). + (setq startpunkt (list (car startpunkt) (cadr startpunkt) z-start)) + ;; Gewaehlte Dimension (2D/3D) + Motorseite fuer den Bau erzwingen, danach zurueck. + (setq *ssg-ils-dim* dim) + (setq *vf-motorseite* motorseite) + (vf-dialog-berechnen-einfuegen typ vf-nummer deltaL deltaH richtung startpunkt hz seite nil + geruest-einzelmodul geruest-typ horizontal-p) + (setq *ssg-ils-dim* nil) + (setq *vf-motorseite* nil) + ) + ) ) ) ) @@ -1108,14 +1191,14 @@ ) ;; ============================================================ -;; TEIL 11b: DIALOG-AUSWAHL FUER TYP/LINIENZUG-MODUS (Radio-Buttons) +;; TEIL 11b: DIALOG-AUSWAHL FUER TYP (Radio-Buttons) ;; ============================================================ -;; Ersetzen die beiden Konsolen-Listen am Anfang von c:VarioFoerderer durch -;; Radio-Button-Dialoge (dcl/variofoerderer.dcl, vfc_typ/vfc_linienzug_modus) -;; - alle Optionen sofort sichtbar statt Dropdown. WICHTIG: Radio-Button- +;; Ersetzt die Konsolen-Liste am Anfang von c:VarioFoerderer durch einen +;; Radio-Button-Dialog (dcl/variofoerderer.dcl, vfc_typ) - alle Optionen +;; sofort sichtbar statt Dropdown. WICHTIG: Radio-Button- ;; Labels sind im DCL fest auf Deutsch einprogrammiert (set_tile kann das ;; Label eines radio_button nicht setzen, nur den Zustand - siehe DCL- -;; Kommentar). Beide Funktionen geben deshalb nur dann etwas anderes als nil +;; Kommentar). Die Funktion gibt deshalb nur dann etwas anderes als nil ;; zurueck, wenn GUI erlaubt ist (Tests: ssg-gui-aus) UND Deutsch aktiv ist - ;; sonst faellt der Aufrufer automatisch auf die alte, vollstaendig ;; uebersetzte Konsolen-Liste zurueck (unveraendert im Code stehen @@ -1161,35 +1244,30 @@ ) ) -;; "Zurueck" springt zur Anlagen-Typ-Frage zurueck - Rueckgabe 'ZURUECK, vom -;; Aufrufer (c:VarioFoerderer) ausgewertet (dort erneuter Aufruf von -;; vfc-dialog-typ-waehlen statt hier lokal aufzuloesen). -(defun vfc-dialog-linienzug-modus-waehlen ( / dcl-pfad dat gmodus ergebnis) +;; Eingabemodus (3D-Linie / Werte manuell) per Dialog waehlen - ersetzt die +;; fruehere getstring-Konsolenabfrage in vf-dialog-ablauf. Gleiches Muster/ +;; gleiche Einschraenkung wie vfc-dialog-typ-waehlen: nur bei aktiver GUI und +;; Deutsch, sonst nil (Aufrufer faellt dann auf die alte Konsolen-Abfrage +;; zurueck). Keine "Zurueck"-Taste noetig (direkt nach der Typ-Wahl, die +;; selbst schon ein "Zurueck" hat). Rueckgabe: "1" (3D-Linie), "2" (Werte) +;; oder 'ABBRUCH. +(defun vfc-dialog-eingabemodus-waehlen ( / dcl-pfad dat gmodus ergebnis) (if (and (or (not (car (atoms-family 1 '("SSG-GUI-P")))) (ssg-gui-p)) (= *ssg-lang* "de_DE")) (progn (setq dcl-pfad (strcat (getenv "DXFM_DCL") "/variofoerderer.dcl")) (setq dat (load_dialog dcl-pfad)) - (if (not (new_dialog "vfc_linienzug_modus" dat)) + (if (not (new_dialog "vfc_eingabemodus" dat)) (progn (if (and dat (>= dat 0)) (unload_dialog dat)) nil) (progn - (set_tile "modus_1" "1") + (set_tile "modus_3dlinie" "1") (action_tile "accept" - "(setq gmodus (cond ((= (get_tile \"modus_2\") \"1\") 2) - ((= (get_tile \"modus_3\") \"1\") 3) - ((= (get_tile \"modus_4\") \"1\") 4) - ((= (get_tile \"modus_5\") \"1\") 5) - (t 1))) + "(setq gmodus (if (= (get_tile \"modus_werte\") \"1\") \"2\" \"1\")) (done_dialog 1)") (action_tile "cancel" "(done_dialog 0)") - (action_tile "back" "(done_dialog 2)") (setq ergebnis (start_dialog)) (unload_dialog dat) - (cond - ((= ergebnis 1) gmodus) - ((= ergebnis 2) 'ZURUECK) - (t 'ABBRUCH) - ) + (if (= ergebnis 1) gmodus 'ABBRUCH) ) ) ) @@ -1200,20 +1278,14 @@ ;; ============================================================ ;; TEIL 12: DISPATCHER c:VarioFoerderer ;; ============================================================ -;; Erzwungener QC-Modus (Linienzug-Modus 4/5, vfc-dialog-linienzug-modus- -;; waehlen/Konsolen-Fallback unten): "4"/"5" waehrend des naechsten +;; Erzwungener QC-Modus (Linienzug-Modus 4/5, siehe *vf-vorauswahl-linienzug- +;; modus* oben/c:QcLinieMitGf*): "4"/"5" waehrend des naechsten ;; vf-linienzug-modus-Laufs, sonst nil. Von vf-linienzug-modus (vf_linienzug. ;; lsp, spaeter geladen) gelesen/geleert - globale Vorab-Deklaration hier, ;; damit sie unabhaengig von der Lade-Reihenfolge existiert. (if (not (boundp '*vfl-forciere-qc-modus*)) (setq *vfl-forciere-qc-modus* nil)) (defun c:VarioFoerderer ( / typ-eintr anlage-typ berechne-fn einfuege-fn - eingabe deltaL deltaH richtung startpunkt seite hz - eingabe-modus ergebnis ergebnis-liste gueltige-winkel - horizontal-info - best-winkel L_GF L_GF1 L_GF2 L_VF - idx eintrag wahl antwort verteilung-modus - n-scanner vf-nummer hoehe-von hoehe-bis lastEnt - staustrecke-basis vf-insert dlg-typ dlg-modus zurueck-zu-typ) + idx wahl vf-nummer dlg-typ zurueck-zu-typ) (princ "\n=========================================") (princ (ssg-text "vfc-banner-titel")) @@ -1272,24 +1344,13 @@ ;; Kette mit gemischten GF/VF-Segmenten), passt nicht in das berechne-fn/ ;; einfuege-fn-Schema fuer ein einzelnes durchgehendes Segment - siehe ;; vf_linienzug.lsp. Dispatcht hier direkt und beendet den Standard-Ablauf. + ;; Jeder Aufrufer (Menue-Wrapper c:LinienzugVarioFoerderer*/c:QcLinieMitGf*) + ;; setzt *vf-vorauswahl-linienzug-modus* VOR dem Aufruf - der frueher + ;; hier gefuehrte Modus-Dialog/Konsolen-Fallback ist dadurch entfallen. (if (= anlage-typ "linienzug") (progn - (setq dlg-modus (vfc-dialog-linienzug-modus-waehlen)) - (cond - ((eq dlg-modus 'ABBRUCH) (exit)) - ((eq dlg-modus 'ZURUECK) (setq zurueck-zu-typ t)) - (dlg-modus (setq wahl dlg-modus)) - (t - ;; Dialog nicht verfuegbar/nicht erlaubt - alte Konsolen-Liste. - (princ (ssg-text "vfc-linienzug-modus-header")) - (princ (ssg-text "vfc-linienzug-modus-1")) - (princ (ssg-text "vfc-linienzug-modus-2")) - (princ (ssg-text "vfc-linienzug-modus-3")) - (princ (ssg-text "vfc-linienzug-modus-4")) - (princ (ssg-text "vfc-linienzug-modus-5")) - (setq wahl (getint (ssg-text "prompt-wahl-1-5"))) - ) - ) + (setq wahl *vf-vorauswahl-linienzug-modus* + *vf-vorauswahl-linienzug-modus* nil) (if (not zurueck-zu-typ) (progn ;; Konsolen-Nummer -> interne Funktion: @@ -1321,215 +1382,26 @@ (setq vf-nummer (vf-next-number)) (princ (ssg-textf "vfc-naechste-vf-nummer" (list (itoa vf-nummer)))) - ;; 2b. Standard- ODER Etage-Typ + Werteingabe -> DCL-Dialog - ;; (dcl/variofoerderer.dcl; Standard: vfs-standard-dialog-ablauf in - ;; vf_standard.lsp, Etage: vfe-etage-dialog-ablauf in vf_etage.lsp - beide - ;; nutzen dieselben Dialoge vfs-dialog-eingabe-basis/-winkel-verteilung). - ;; 3D-Linie-Eingabe und der Typ Linienzug bleiben unveraendert interaktiv - ;; (fallen unten in den Konsolen-Ablauf, der die Eingabemodus-Frage erneut - ;; stellt - siehe vf-eingabe-abfragen). + ;; 2b. Standard- ODER Etage-Typ -> DCL-Dialog (dcl/variofoerderer.dcl; + ;; Standard: vfs-standard-dialog-ablauf in vf_standard.lsp, Etage: + ;; vfe-etage-dialog-ablauf in vf_etage.lsp - beide duenne Wrapper um + ;; vf-dialog-ablauf). Die Eingabemodus-Frage (3D-Linie vs. Werte) stellt + ;; vf-dialog-ablauf selbst, VOR dem Basis-Dialog - bei 3D-Linie gehen + ;; deltaL/deltaH/Richtung/Einfuegehoehe als Prefill in denselben + ;; Basis-Dialog ein, den auch die Werteingabe nutzt (siehe dortiger + ;; Kopfkommentar). Damit entfaellt der fruehere rein interaktive + ;; Konsolen-Ablauf (Schritte 3-11, vf-eingabe-abfragen) komplett - er lief + ;; bei 3D-Linie an der GUI vorbei und fragte Seite/Fahrtrichtung/Verteilung + ;; erneut auf der Kommandozeile ab, obwohl der Dialog dieselben Felder + ;; laengst per GUI anbietet. (if (member anlage-typ '("standard" "etage")) (progn - ;; Eingabemodus: Vorauswahl (Menue-Wrapper) hat Vorrang, sonst fragen. - (if *vf-vorauswahl-eingabemodus* - (setq antwort *vf-vorauswahl-eingabemodus*) - (progn - (princ (ssg-text "vfc-eingabemodus-header")) - (princ (ssg-text "vfc-eingabemodus-3dlinie")) - (princ (ssg-text "vfc-eingabemodus-werte")) - (setq antwort (getstring (ssg-text "vfc-prompt-wahl-1-2-ohne-default"))) - ) - ) - (if (= antwort "2") - (progn - (if (= anlage-typ "etage") - (vfe-etage-dialog-ablauf vf-nummer) - (vfs-standard-dialog-ablauf vf-nummer)) - (exit) - ) - ) - ) - ) - - ;; 3. Gemeinsame Eingabe (deltaL, deltaH, richtung, startpunkt) - richtung - ;; steht bei beiden Eingabemodi (Werteingabe UND 3D-Linie) bereits fest. - (setq eingabe (vf-eingabe-abfragen)) - (setq deltaL (nth 0 eingabe) - deltaH (nth 1 eingabe) - richtung (nth 2 eingabe) - startpunkt (nth 3 eingabe) - hz (nth 4 eingabe) - eingabe-modus (nth 5 eingabe)) - - ;; 4. Seite-Auswahl (vor Berechnung, weil Etage-Typ sie fuer Geometrie braucht) - (princ (ssg-text "vfc-seite-header")) - (princ (ssg-text "vfc-seite-rechts")) - (princ (ssg-text "vfc-seite-links")) - (setq antwort (getstring (ssg-text "prompt-wahl-1-2"))) - (if (= antwort "2") (setq seite "links") (setq seite "rechts")) - (princ (ssg-textf "vfc-seite-gewaehlt" (list seite))) - - ;; 5. Typ-spezifische Berechnung (init + Winkelsuche) - (setq ergebnis (apply berechne-fn (list deltaL deltaH richtung seite))) - (setq ergebnis-liste (cadddr ergebnis)) - - ;; 5b. Horizontale Zwischenstrecke als Zusatzoption: geometrisch NUR bei - ;; "Ab" ueberhaupt erreichbar (die festen 3-Grad-Elemente wirken immer - ;; absenkend) - "Auf" wird deshalb gar nicht erst geprueft. Gilt einheitlich - ;; fuer Werteingabe UND 3D-Linie, da richtung hier in jedem Fall feststeht. - ;; Bibliothek ist durch den berechne-fn-Aufruf oben bereits initialisiert. - (if (and (= anlage-typ "standard") (= richtung "Ab")) - (progn - (setq horizontal-info (berechne-horizontale-mitte deltaL deltaH "Ab" nil)) - (if (and horizontal-info (caddr horizontal-info)) - (progn - (setq ergebnis-liste - (append ergebnis-liste - (list (list 0 (car horizontal-info) (cadr horizontal-info) T)))) - (princ (ssg-textf "vfc-hinweis-horizontal-moeglich" (list (chr 176) (chr 916)))) - ) - (setq horizontal-info nil) - ) - ) - ) - - ;; Konfiguration (nach ssg_core-Init verfuegbar). - (setq staustrecke-basis (ssg-cfg-or "vario" "staustrecke_basis" 1000)) - - ;; 7. Gueltige Winkel ermitteln - (setq gueltige-winkel - (mapcar 'car - (vl-remove-if-not - (function (lambda (e) (and (nth 3 e) - (numberp (nth 1 e)) (numberp (nth 2 e)) - (>= (nth 1 e) 0) (>= (nth 2 e) 0)))) - ergebnis-liste))) - (if (null gueltige-winkel) - (progn - (alert (ssg-textf "vfc-alert-kein-winkel" - (list (chr 916) (rtos deltaL 2 0) (rtos deltaH 2 0) richtung))) + (if (= anlage-typ "etage") + (vfe-etage-dialog-ablauf vf-nummer) + (vfs-standard-dialog-ablauf vf-nummer)) (exit) ) ) - - ;; 8. Winkelauswahl (automatisch oder interaktiv) - ;; Sentinel-Winkel 0 = horizontale Zwischenstrecke (Schritt 6/11 bei 0 - ;; Grad, Boegen fest auf_3/ab_3) - siehe berechne-horizontale-mitte. - (if (= (length gueltige-winkel) 1) - (progn - (setq best-winkel (car gueltige-winkel)) - (setq eintrag (assoc best-winkel ergebnis-liste)) - (setq L_GF (nth 1 eintrag) L_VF (nth 2 eintrag)) - (if (= best-winkel 0) - (princ (ssg-text "vfc-einzige-variante-horizontal")) - (princ (ssg-textf "vfc-einziger-winkel" (list (itoa best-winkel) (chr 176)))) - ) - ) - (progn - (princ (ssg-text "vfc-mehrere-winkel-header")) - (setq idx 1) - (foreach w gueltige-winkel - (setq eintrag (assoc w ergebnis-liste)) - (if (= w 0) - (princ (ssg-textf "vfc-winkel-option-horizontal" - (list idx (chr 176) - (rtos (nth 1 eintrag) 2 1) (rtos (nth 2 eintrag) 2 1)))) - (princ (ssg-textf "vfc-winkel-option" - (list idx (itoa w) (chr 176) - (rtos (nth 1 eintrag) 2 1) (rtos (nth 2 eintrag) 2 1)))) - ) - (setq idx (1+ idx)) - ) - (setq wahl (getint (ssg-textf "vfc-prompt-winkel-waehlen" - (list (length gueltige-winkel))))) - (if (or (null wahl) (< wahl 1) (> wahl (length gueltige-winkel))) - (setq wahl 1)) - (setq best-winkel (nth (1- wahl) gueltige-winkel)) - (setq eintrag (assoc best-winkel ergebnis-liste)) - (setq L_GF (nth 1 eintrag) L_VF (nth 2 eintrag)) - ) - ) - (princ "\n\n=========================================") - (if (= best-winkel 0) - (princ (ssg-textf "vfc-zus-horizontal" - (list (rtos L_GF 2 2) (rtos L_VF 2 2)))) - (princ (ssg-textf "vfc-zus-winkel" - (list (itoa best-winkel) (chr 176) (rtos L_GF 2 2) (rtos L_VF 2 2)))) - ) - (princ "\n=========================================") - - ;; 9. L_GF Verteilung - (princ (ssg-text "vfc-gf-verteilung-header")) - (princ (ssg-text "vfc-gf-verteilung-1")) - (princ (ssg-text "vfc-gf-verteilung-2")) - (princ (ssg-text "vfc-gf-verteilung-3")) - (princ (ssg-text "vfc-gf-verteilung-4")) - (setq verteilung-modus (getstring (ssg-text "prompt-wahl-1-2-3-4"))) - (if (= verteilung-modus "") (setq verteilung-modus "1")) - (cond - ((= verteilung-modus "1") - (setq L_GF1 (/ L_GF 2.0) L_GF2 (/ L_GF 2.0))) - ((= verteilung-modus "2") - (setq L_GF1 (float staustrecke-basis)) - (setq L_GF2 (max 0.0 (- L_GF (float staustrecke-basis))))) - ((= verteilung-modus "3") - (setq L_GF2 (float staustrecke-basis)) - (setq L_GF1 (max 0.0 (- L_GF (float staustrecke-basis))))) - ((= verteilung-modus "4") - (setq L_GF1 (getreal (ssg-textf "vfc-prompt-lgf1-vorne" - (list (rtos (/ L_GF 2.0) 2 2))))) - (if (null L_GF1) (setq L_GF1 (/ L_GF 2.0))) - (setq L_GF2 (max 0.0 (- L_GF L_GF1))) - (princ (ssg-textf "vfc-lgf2-hinten" (list (rtos L_GF2 2 2))))) - (t (setq L_GF1 (/ L_GF 2.0) L_GF2 (/ L_GF 2.0))) - ) - (princ (ssg-textf "vfc-zus-lgf" (list (rtos L_GF1 2 2) (rtos L_GF2 2 2)))) - - ;; Scanner-Anzahl wird nicht mehr abgefragt (Attribut ANZAHL_SCANNER bleibt 0). - (setq n-scanner 0) - - ;; 11. Bestaetigung - (princ (ssg-text "vfc-einfuegen-frage")) - (princ (ssg-text "vfc-einfuegen-optionen")) - (setq antwort (getstring (ssg-text "vfc-prompt-wahl-1-2-ohne-default"))) - - (if (= antwort "1") - (progn - ;; Hoehenangaben fuer Attribute und Beschriftung - (setq hoehe-von (caddr startpunkt)) - (setq hoehe-bis (cond - ((= richtung "Auf") (+ hoehe-von deltaH)) - ((= richtung "Ab") (- hoehe-von deltaH)) - (t hoehe-von))) - - ;; lastEnt VOR der Einfuegung merken - (setq lastEnt (ssg-lastent-ohne-attribute)) - - ;; Typ-spezifische Einfuegung - (apply einfuege-fn - (list deltaL deltaH richtung best-winkel L_GF1 L_GF2 L_VF startpunkt seite hz)) - - ;; Gemeinsame VF_*-Block-Erstellung - ;; Geruest fuer Einzelmodul/Geruestoption sind in diesem interaktiven - ;; Konsolen-Ablauf (Etage/Linienzug) nicht abfragbar - bleiben auf - ;; Schema-Default (analog Kreisel: nur ueber Dialog editierbar). - (setq vf-insert - (vf-block-erstellen anlage-typ seite vf-nummer n-scanner - hoehe-von hoehe-bis deltaH deltaL L_VF L_GF1 L_GF2 - richtung best-winkel startpunkt lastEnt hz - "0" "Schoenenberger Geruest")) - - ;; Standard- UND Etage-Bloecke aus dem interaktiven Konsolen-Ablauf mit der - ;; SSG_VF_EDIT-XDATA markieren - sie sind (wie die Dialog-Variante) aus - ;; Attributen + hz verlustfrei rekonstruierbar, also per Doppelklick - ;; (c:VARIOFOERDERER_EDIT) editierbar. Linienzug bleibt unmarkiert. - (if (and vf-insert (member anlage-typ '("standard" "etage"))) - (vf-edit-xdata-schreiben vf-insert anlage-typ hz)) - - (princ "\n=========================================") - ) - (princ (ssg-text "vfc-vorgang-abgebrochen")) - ) nil ) @@ -1540,7 +1412,6 @@ ;; Menue-Wrapper: jeder feste Endpunkt der VarioFoerderer-Erzeugung ist ;; direkt aufrufbar (Typ + Eingabemodus vorbelegt, keine Rueckfrage). -;; Nur der Linienzug fragt intern noch Modus 1/2/3 (komplexer Fall). ;; Eingabemodus-Vorauswahl: "2" = Werte manuell (DCL-Dialog), ;; "1" = aus 3D-Linie (Linie picken). @@ -1568,9 +1439,34 @@ (c:VarioFoerderer) ) -;; --- Linienzug (bleibt interaktiv: fragt Modus 1/2/3) --- -(defun c:LinienzugVarioFoerderer () - (setq *vf-vorauswahl-typ* "linienzug") +;; --- Linienzug/Streckengruppe (Modus 1/2/3, Typ+Modus vorbelegt) --- +(defun c:LinienzugVarioFoerdererManuell () + (setq *vf-vorauswahl-typ* "linienzug" + *vf-vorauswahl-linienzug-modus* 1) + (c:VarioFoerderer) +) +(defun c:LinienzugVarioFoerdererZielhoehe () + (setq *vf-vorauswahl-typ* "linienzug" + *vf-vorauswahl-linienzug-modus* 2) + (c:VarioFoerderer) +) +(defun c:LinienzugVarioFoerdererStueckweise () + (setq *vf-vorauswahl-typ* "linienzug" + *vf-vorauswahl-linienzug-modus* 3) + (c:VarioFoerderer) +) + +;; --- QC-Linie (Linienzug-Modus 4/5, Typ+Modus vorbelegt) --- +;; Menue: ILS -> Gefaellestrecke (gehoeren inhaltlich dorthin, technisch +;; weiterhin ueber den Linienzug-Ablauf/*vfl-forciere-qc-modus* gebaut). +(defun c:QcLinieMitGf () + (setq *vf-vorauswahl-typ* "linienzug" + *vf-vorauswahl-linienzug-modus* 4) + (c:VarioFoerderer) +) +(defun c:QcLinieMitGfUForm () + (setq *vf-vorauswahl-typ* "linienzug" + *vf-vorauswahl-linienzug-modus* 5) (c:VarioFoerderer) ) diff --git a/Lisp/vf_linienzug.lsp b/Lisp/vf_linienzug.lsp index 3dbd70c..9670e33 100644 --- a/Lisp/vf_linienzug.lsp +++ b/Lisp/vf_linienzug.lsp @@ -1033,7 +1033,17 @@ ;; "Neue Linie: GF" - Separator VOR/NACH (2 Ja/Nein-Toggles in einem ;; Bildschirm). Siehe dcl/vf_linienzug_wizard.dcl (vflw_sep_gf). -(defun vflw-gruppe-sep-gf-impl ( / dat ergebnis gsepvor gsepnach) +;; es-folgt-sep: T, wenn dieses GF-Stueck das Kettenende MIT ES-Element ist +;; (gf-kettenende-antwort steht beim Aufruf schon fest, siehe Aufrufer) - +;; dann baut vfl-insert-es-element ohnehin IMMER seinen eigenen Separator +;; VOR dem ES-Block, ein zusaetzlicher "Separator NACH" waere ein zweiter, +;; direkt anschliessender Separator. Statt die Frage zu stellen und die +;; Antwort hinterher zu verwerfen (Nutzer-Feedback 2026-10-08: verwirrend), +;; wird die Kachel hier direkt deaktiviert+abgehakt - der bestehende +;; Konsolen-/Sicherheitsnetz-Pfad (siehe Aufrufer, "ueberschrieben"-Meldung) +;; bleibt UNVERAENDERT bestehen (deckt weiterhin den reinen Konsolen-Fall ab, +;; wo diese Dialog-Funktion gar nicht laeuft). +(defun vflw-gruppe-sep-gf-impl (es-folgt-sep / dat ergebnis gsepvor gsepnach) (setq dat (load_dialog (vflw-dcl-pfad))) (if (not (new_dialog "vflw_sep_gf" dat)) (if (and dat (>= dat 0)) (unload_dialog dat)) @@ -1041,6 +1051,7 @@ (set_tile "kopf" (vflw-clean (ssg-text "vfl-sep-gf-header"))) (set_tile "sepvor" "0") (set_tile "sepnach" "0") + (if es-folgt-sep (mode_tile "sepnach" 1)) (action_tile "accept" "(setq gsepvor (get_tile \"sepvor\")) (setq gsepnach (get_tile \"sepnach\")) (done_dialog 1)") (action_tile "cancel" "(done_dialog 0)") @@ -1516,11 +1527,11 @@ (princ)) ;; "Naechstes Element waehlen" - Hauptmenue des Linienzug-Aufbaus (Modus 1). -;; Kettenanfang (frame=nil): alle 6 Optionen waehlbar, Vario-Kurve inklusive -;; (kann nur als allererstes Kettenglied gewaehlt werden - siehe Kommentar an -;; der Aufrufstelle). Sonst (Frame vorhanden): Vario-Kurve per mode_tile -;; deaktiviert, die uebrigen 5 Optionen behalten dieselbe Reihenfolge/ -;; Bedeutung wie im Konsolen-Menue, nur ohne die Luecke. Pusht die +;; Kettenanfang (frame=nil): alle 5 Radio-Optionen waehlbar, Vario-Kurve +;; inklusive (kann nur als allererstes Kettenglied gewaehlt werden - siehe +;; Kommentar an der Aufrufstelle). Sonst (Frame vorhanden): Vario-Kurve per +;; mode_tile deaktiviert, die uebrigen 4 Optionen behalten dieselbe +;; Reihenfolge/Bedeutung wie im Konsolen-Menue, nur ohne die Luecke. Pusht die ;; POSITIONELLE Antwort ("1".."6" bzw. "1".."5"), identisch zur bisherigen ;; Konsolen-Nummerierung, damit Journal/Replay unveraendert bleiben - die ;; Radio-Buttons selbst tragen daher STABILE, kontextunabhaengige Keys @@ -1531,9 +1542,20 @@ ;; - eindeutig als VF erkannt (Startpunkt liegt auf einer bestehenden ;; VF-Kette) macht "GF-Bogen"/"Neue Linie: GF" als DIREKTE Fortsetzung keinen ;; Sinn (kein GF-Baustein wird an dieser Stelle einfach an ein VF-Teil -;; angeflanscht) - diese beiden Optionen werden dann ausgegraut. "Neue Linie -;; BIS Kettenende" bleibt immer waehlbar (hat ihre eigene GF/VF-Erkennung). -(defun vflw-gruppe-naechstes-element-impl (kettenanfang familie / dat ergebnis slot gwahl) +;; angeflanscht) - diese beiden Optionen werden dann ausgegraut. +;; as-eben-eingefuegt: T, wenn der Anwender die AS-Frage ("AS-Element +;; setzen?") explizit mit Ja beantwortet hat (as-vorhanden in +;; vf-linienzug-modus) - das AS-Element braucht danach zuerst ein GERADES +;; Segment (Linie-GF/Linie-VF/Horizontal-VF), eine horizontale Kurve direkt +;; im Anschluss (GF-Bogen/Vario-Kurve) ergibt keinen Sinn und wird ebenfalls +;; ausgegraut. Greift nur, wenn familie nicht schon "VF" ist (dort ist +;; wahl_gfbogen bereits deaktiviert); nil, wenn der Startpunkt automatisch +;; auf einer bestehenden Kette erkannt wurde (as-vorhanden dann ohnehin nil). +;; "Neue Linie BIS Kettenende" ist kein Radio mehr, sondern der eigene Button +;; "abschliessen" (done_dialog 3) - er setzt slot direkt auf 6 und ignoriert +;; die aktuelle Radio-Auswahl (hat ihre eigene GF/VF-Erkennung, bleibt daher +;; immer waehlbar, auch wenn GF-Bogen/Neue Linie: GF ausgegraut sind). +(defun vflw-gruppe-naechstes-element-impl (kettenanfang familie as-eben-eingefuegt / dat ergebnis slot gwahl) (setq dat (load_dialog (vflw-dcl-pfad))) (if (not (new_dialog "vflw_naechstes_element" dat)) (if (and dat (>= dat 0)) (unload_dialog dat)) @@ -1546,20 +1568,31 @@ (mode_tile "wahl_gfbogen" 1) (mode_tile "wahl_liniegf" 1) (set_tile "wahl_variokurve" "1"))) + ;; Unmittelbar nach einem explizit eingefuegten AS-Element ergibt eine + ;; horizontale Kurve (GF-Bogen/Vario-Kurve) als DIREKTE Fortsetzung + ;; keinen Sinn - das AS-Element braucht zuerst ein gerades Segment + ;; (Linie-GF/Linie-VF/Horizontal-VF). Nur relevant am Kettenanfang und + ;; nur, wenn nicht schon durch familie="VF" abgedeckt (dort ist + ;; wahl_gfbogen bereits deaktiviert). + (if (and kettenanfang as-eben-eingefuegt (not (equal familie "VF"))) + (progn + (mode_tile "wahl_gfbogen" 1) + (mode_tile "wahl_variokurve" 1) + (set_tile "wahl_liniegf" "1"))) (action_tile "accept" "(setq slot (cond ((= (get_tile \"wahl_gfbogen\") \"1\") 1) ((= (get_tile \"wahl_variokurve\") \"1\") 2) ((= (get_tile \"wahl_linievf\") \"1\") 4) ((= (get_tile \"wahl_horizontalvf\") \"1\") 5) - ((= (get_tile \"wahl_linieende\") \"1\") 6) (t 3))) (done_dialog 1)") + (action_tile "abschliessen" "(setq slot 6) (done_dialog 3)") (action_tile "cancel" "(done_dialog 0)") (action_tile "back" "(done_dialog 2)") (setq ergebnis (start_dialog)) (unload_dialog dat) (cond - ((= ergebnis 1) + ((or (= ergebnis 1) (= ergebnis 3)) (setq gwahl (if (or kettenanfang (<= slot 2)) slot (1- slot))) (vflw-pending-push-all (list (itoa gwahl)))) ((= ergebnis 2) (setq *vfl-wizard-abbruch-grund* "zurueck")) @@ -6594,7 +6627,8 @@ (progn (if (vfl-wizard-aktiv) (vl-catch-all-apply 'vflw-gruppe-naechstes-element-impl - (list (null frame) (if (null frame) startpunkt-familie nil)))) + (list (null frame) (if (null frame) startpunkt-familie nil) + (if (null frame) as-vorhanden nil)))) (if frame (progn (princ (ssg-text "vfl-menu-gf-bogen-1")) @@ -7008,7 +7042,7 @@ (setq hz-neu (car (frame->hz-winkel frame))) ) (setq rad3 (* gf-max-winkel (/ pi 180.0))) - (if (vfl-wizard-aktiv) (vl-catch-all-apply 'vflw-gruppe-sep-gf-impl nil)) + (if (vfl-wizard-aktiv) (vl-catch-all-apply 'vflw-gruppe-sep-gf-impl (list (= gf-kettenende-antwort "1")))) (princ (ssg-text "vfl-sep-vor-gf-frage")) (princ (ssg-text "vfl-ja")) (princ (ssg-text "vfl-nein")) @@ -7050,9 +7084,16 @@ (if (> sep-anzahl 0) (setq deltaH (max 0.0 (- deltaH (* sep-anzahl *vfk-separator-laenge* (sin rad3)))))) ;; ES-Fussabdruck VORAB reservieren, siehe Kommentar bei - ;; gf-kettenende-antwort oben. + ;; gf-kettenende-antwort oben. ES bringt IMMER seinen eigenen + ;; Separator mit (vfl-insert-es-element, siehe Kommentar oben + ;; "bringt seinen eigenen Separator VOR dem ES-Block schon + ;; mit") - dessen Hoehenanteil fehlte hier bisher komplett + ;; (realer Bug, Nutzer-Meldung 2026-10-08: Kette baute zu lang/ + ;; zu tief, weil der automatische Kettenende-Separator anders + ;; als die optionalen vor/nach-Separatoren oben NICHT reserviert + ;; wurde). (if (= gf-kettenende-antwort "1") - (setq deltaH (max 0.0 (- deltaH (abs ein-dz))))) + (setq deltaH (max 0.0 (- deltaH (* *vfk-separator-laenge* (sin rad3)) (abs ein-dz))))) (if (vfl-wizard-aktiv) (vl-catch-all-apply 'vflw-gruppe-gefaelle-impl (list (vfl-gf-hoehe-vorschlag (caddr p-aktuell)) t nil))) @@ -7116,7 +7157,7 @@ ;; gepickten deltaL abgezogen, damit die Kette exakt am ;; gepickten Punkt endet statt danach ueberzustehen. (setq rad3 (* gf-max-winkel (/ pi 180.0))) - (if (vfl-wizard-aktiv) (vl-catch-all-apply 'vflw-gruppe-sep-gf-impl nil)) + (if (vfl-wizard-aktiv) (vl-catch-all-apply 'vflw-gruppe-sep-gf-impl (list (= gf-kettenende-antwort "1")))) (princ (ssg-text "vfl-sep-vor-gf-frage")) (princ (ssg-text "vfl-ja")) (princ (ssg-text "vfl-nein")) @@ -7139,9 +7180,15 @@ (if (> sep-anzahl 0) (setq deltaL (- deltaL (* sep-anzahl *vfk-separator-laenge* (cos rad3))))) ;; ES-Fussabdruck VORAB reservieren, siehe Kommentar bei - ;; gf-kettenende-antwort oben. + ;; gf-kettenende-antwort oben. ES bringt IMMER seinen + ;; eigenen Separator mit (vfl-insert-es-element) - dessen + ;; Laenge fehlte hier bisher komplett (realer Bug, + ;; Nutzer-Meldung 2026-10-08: Kette baute ca. 300mm*cos(w) + ;; zu lang, weil der automatische Kettenende-Separator + ;; anders als die optionalen vor/nach-Separatoren oben + ;; NICHT reserviert wurde). (if (= gf-kettenende-antwort "1") - (setq deltaL (- deltaL (abs ein-dx)))) + (setq deltaL (- deltaL (* *vfk-separator-laenge* (cos rad3)) (abs ein-dx)))) (if (< deltaL 1.0) (progn (princ (ssg-text "vfl-fehler-linie-zu-kurz")) (setq gf-ok nil))) (if gf-ok @@ -8985,6 +9032,12 @@ (princ (ssg-text "vfl-vwnb-titel")) (princ "\n=========================================") + ;; "Zurueck"/"Abbrechen"-Flag ganz am Anfang zuruecksetzen - identischer Fix + ;; wie in vf-linienzug-modus/-modus2 (siehe dortiger Kommentar): sonst + ;; wuerde ein von einem VORHERIGEN Lauf noch gesetztes Flag den allerersten + ;; vfl-in-*-Aufruf dieses NEUEN Laufs sofort wieder abbrechen. + (setq *vfl-wizard-abbruch-grund* nil) + ;; --- Debug-Session (Schalter "vfl-modus3", DEFAULT AN) ------------------- ;; Bei Bedarf abschaltbar: (dbg-schalter-off "vfl-modus3") in der Konsole. ;; Alle interaktiven Eingaben (Punkte, Hoehen, Seiten, Winkel, Menue- @@ -9503,6 +9556,7 @@ nonclimber-cnt nkurve kurve-chords run-span hor-koerper-len hor-pairs flat-p flat-zone-erste fill-len wahl3 rad3v feste-vf dH-adj dir-x dir-y fill-D end-hz + laengs-wert quer-wert soll-korrigiert es-vek-fix es-along-fix gf-total br-gf-mode br-gf2-exp filler-A-len filler-a-done as-vorhanden es-vorhanden dbg-an dbg-old-error @@ -9512,6 +9566,16 @@ (princ (ssg-text "vfl-m3-titel")) (princ "\n=========================================") + ;; "Zurueck"/"Abbrechen"-Flag ganz am Anfang zuruecksetzen (auch schon vor + ;; der ersten Praeambel-Frage) - sonst wuerde ein von einem VORHERIGEN Lauf + ;; (egal ob Modus 1/2/3) noch gesetztes Flag (z.B. nach "Abbrechen") den + ;; allerersten vfl-in-*-Aufruf DIESES NEUEN Laufs sofort wieder abbrechen + ;; (siehe zentraler Check in vfl-in-value/-value-p) - identischer Fix wie in + ;; vf-linienzug-modus (Modus 1). Realer Bug: ein zweiter Modus-2-Aufruf in + ;; derselben Zeichnung/Session brach sofort mit "ABBRUCH-TRIGGER: + ;; *vfl-wizard-abbruch-grund*=abbrechen" ab, ohne ueberhaupt die + ;; Pfad-Auswahl anzuzeigen (Livetest 2026-09-21). + (setq *vfl-wizard-abbruch-grund* nil) ;; *vfl-journal* VOR dem allerersten vfl-in-*-Aufruf zuruecksetzen (nicht ;; *vfl-replay-queue*, siehe Kommentar bei vfl-journal-reset-lauf und die ;; analoge Korrektur in vf-linienzug-modus). Frueher stand dieser Reset erst @@ -10304,12 +10368,30 @@ ;; ~0 sein (Endpunkt liegt auf der KS_AUS-Achse), Laengs = ES-Ausladung. (setq end-hz (car (frame->hz-winkel frame))) ; KS_AUS-Achsrichtung (setq dir-x (cos (* end-hz (/ pi 180.0))) dir-y (sin (* end-hz (/ pi 180.0)))) - (princ (ssg-textf "vfl-m3-laengs-quer" - (list (rtos (+ (* (- (car ist-ende) (car soll-ende)) dir-x) - (* (- (cadr ist-ende) (cadr soll-ende)) dir-y)) 2 1) - (rtos (+ (* (- (car ist-ende) (car soll-ende)) (- dir-y)) - (* (- (cadr ist-ende) (cadr soll-ende)) dir-x)) 2 1)))) + (setq laengs-wert (+ (* (- (car ist-ende) (car soll-ende)) dir-x) + (* (- (cadr ist-ende) (cadr soll-ende)) dir-y))) + (setq quer-wert (+ (* (- (car ist-ende) (car soll-ende)) (- dir-y)) + (* (- (cadr ist-ende) (cadr soll-ende)) dir-x))) + (princ (ssg-textf "vfl-m3-laengs-quer" (list (rtos laengs-wert 2 1) (rtos quer-wert 2 1)))) (princ (ssg-text "vfl-m3-achse-hinweis")) + ;; Soll (ES-korrigiert): der gepickte Zielpunkt, verschoben um den bereits + ;; oben gemessenen Laengs-Wert entlang der KS_AUS-Achse - zeigt, dass die + ;; Abweichung oben keine Unsicherheit ist, sondern GENAU der ES-eigene + ;; Fussabdruck (90/30-Grad-Schwenk, siehe doc/VarioFoerderer_Linienzug_ + ;; Prinzipien.md Abschnitt 6/14.11: "seitliche ES-Ausladung, kein Fehler"). + ;; Bewusst aus dem bereits gemessenen Laengs-Wert abgeleitet statt ueber + ;; eine zweite, eigenstaendige vf-element-ks-info-Neuberechnung - das waere + ;; ein zweiter Rechenweg fuer dieselbe Information mit eigenem Bug-Risiko, + ;; hier reicht die Umkehrung der bereits validierten Laengs/Quer-Zerlegung. + (setq soll-korrigiert (list (+ (car soll-ende) (* laengs-wert dir-x)) + (+ (cadr soll-ende) (* laengs-wert dir-y)) + (caddr soll-ende))) + (princ (ssg-textf "vfl-m3-ziel-soll-es-korrigiert" + (list (rtos (car soll-korrigiert) 2 1) (rtos (cadr soll-korrigiert) 2 1) (rtos (caddr soll-korrigiert) 2 1)))) + (princ (ssg-textf "vfl-m3-abweichung-es-korrigiert" + (list (rtos (- (car ist-ende) (car soll-korrigiert)) 2 1) + (rtos (- (cadr ist-ende) (cadr soll-korrigiert)) 2 1) + (rtos (- (caddr ist-ende) (caddr soll-korrigiert)) 2 1)))) (princ "\n=========================================") (if dbg-an diff --git a/Lisp/vf_spec.lsp b/Lisp/vf_spec.lsp index 67b48ec..bd8fe61 100644 --- a/Lisp/vf_spec.lsp +++ b/Lisp/vf_spec.lsp @@ -1,7 +1,7 @@ ;; ============================================================ ;; vf_spec.lsp - VF-Linienzug aus Eingabedaten bauen (ohne GUI/Konsole) ;; ============================================================ -;; Stufe 1 des Fahrplans in doc/TODO-plan-vf-interactive.md. +;; Stufe 1 des Fahrplans in doc/todo/plan-vf-interactive.md. ;; ;; Eine SPEC beschreibt eine Kette in Domaenenwerten ("links", "winkel", ;; "aussen", ja/nein) statt in Menue-Codes: lesbar, schreibbar, versionierbar. @@ -903,15 +903,28 @@ ;; abgebrochene Kette bleibt also als lose Geometrie liegen und ist per ;; c:VF_SEKTION_RESTORE rettbar (vfl-segment-xdata-sichern hat pro Iteration ;; schon SSG_VF_EDIT_SEG/_PRE geschrieben). -(defun vsp-bau-aus-spec (spec / kopf sid journal rec vor-ent alt-error err - offen hl meld ist-glieder soll-glieder diff - ist-journal jdiff ent ed dim) +(defun vsp-bau-aus-spec (spec / kopf journal) (setq kopf (vsp-kopf spec)) - (setq sid (vsp-spec-id kopf)) + ;; vsp-journal sammelt seine Fehler in *vsp-fehler* - darum erst danach lesen. (setq journal (vsp-journal spec)) + (vsp-bau-journal (vsp-spec-id kopf) journal (ssg-val kopf "dim") *vsp-fehler*)) + +;; Eine Kette aus einem fertigen Eingabe-Journal bauen (Liste (art . wert) in +;; Bau-Reihenfolge, wie vfl-journal-replay-start sie erwartet). Gemeinsamer +;; Kern fuer Specs (vsp-bau-aus-spec, Journal per vsp-journal uebersetzt) und +;; fuer UNVERAENDERT uebernommene Aufzeichnungen - z.B. die SSG_VF_EDIT-XDATA +;; eines von Hand gebauten VF_n (tests/test_dematic_arvato.lsp, Journal per +;; vfl-string->entry gelesen). Letztere brauchen keinen Uebersetzer und +;; laufen darum auch dann, wenn der Dialog neuer ist als die Spec-Grammatik. +;; sid = Kennung im Record, dim = "2D"/"3D" oder nil (Sitzung), +;; fehler = Liste der Spec-Fehler (bei nil-Journal die Begruendung). +;; Erwartet den Headless-Rahmen (vsp-headless-an) beim Aufrufer. +(defun vsp-bau-journal (sid journal dim fehler / rec vor-ent alt-error err + offen hl meld ist-glieder soll-glieder diff + ist-journal jdiff ent ed) (setq soll-glieder (if journal (vfl-journal-glieder journal) nil)) (setq rec (vsp-rec-neu sid)) - (setq rec (vsp-set rec "spec_fehler" *vsp-fehler*)) + (setq rec (vsp-set rec "spec_fehler" fehler)) (setq rec (vsp-set rec "glieder_soll" soll-glieder)) (setq rec (vsp-set rec "eingaben_gesamt" (if journal (length journal) 0))) (princ (strcat "\n [SPEC] " sid)) @@ -919,9 +932,9 @@ (cond ;; --- Spec fehlerhaft: nichts bauen --- ((null journal) - (princ (strcat "\n " (itoa (length *vsp-fehler*)) + (princ (strcat "\n " (itoa (length fehler)) " Spec-Fehler, nichts gebaut:")) - (foreach f *vsp-fehler* (princ (strcat "\n - " f))) + (foreach f fehler (princ (strcat "\n - " f))) (setq rec (vsp-set rec "status" "spec-fehler")) (setq rec (vsp-set rec "dimension" (vsp-dim-text))) rec) @@ -934,7 +947,7 @@ ;; Feld in der Spec. Ist keines gesetzt, gilt die Sitzung. (setq dim (if (and (boundp '*vsp-dim-override*) *vsp-dim-override*) *vsp-dim-override* - (ssg-val kopf "dim"))) + dim)) (if dim (setq *ssg-ils-dim* dim)) (setq rec (vsp-set rec "dimension" (vsp-dim-text))) (princ (strcat " -> " (itoa (length journal)) " Journal-Eintraege, " diff --git a/Lisp/vf_standard.lsp b/Lisp/vf_standard.lsp index 99e8daf..658bf1f 100644 --- a/Lisp/vf_standard.lsp +++ b/Lisp/vf_standard.lsp @@ -803,11 +803,26 @@ ;; ============================================================ ;; DCL-DIALOG (Standardfall) - dcl/variofoerderer.dcl -;; Ersetzt die interaktive Werteingabe aus vf-eingabe-abfragen -;; (Konsolen-Modus "Werteingabe") fuer anlage-typ "standard" durch -;; zwei DCL-Dialoge: Basis-Eingabe und Winkel/Verteilung. Die -;; 3D-Linie-Eingabe sowie die Typen "etage"/"linienzug" bleiben -;; unveraendert interaktiv (siehe Verzweigung in c:VarioFoerderer). +;; Ersetzt die frueher komplett interaktive Werteingabe fuer anlage-typ +;; "standard" durch zwei DCL-Dialoge: Basis-Eingabe und Winkel/Verteilung. +;; Beide Eingabemodi (3D-Linie UND Werteingabe, siehe vf-dialog-ablauf in +;; vf_core.lsp) laufen seitdem ueber denselben Basis-Dialog - bei 3D-Linie +;; nur mit deltaL/deltaH/Richtung/Einfuegehoehe vorbelegt. Nur der Typ +;; "linienzug" bleibt unveraendert interaktiv (siehe Verzweigung in +;; c:VarioFoerderer). +;; +;; Beide Dialoge existieren je in zwei Varianten: +;; -popup (Dropdowns, urspruengliche Implementierung, unveraendert) und +;; -radio (Radio-Buttons statt Dropdown). Radio-Button-Labels lassen sich +;; nach new_dialog nicht per set_tile aendern (DCL-Limitierung) - die +;; -radio-Varianten loesen das, indem sie ihre DCL-Datei bei JEDEM Aufruf +;; frisch per ssg-dcl-temp-schreiben (ssg_core.lsp) schreiben und die +;; Labels dabei direkt mit dem schon von ssg-text/ssg-textf aufgeloesten +;; String befuellen - funktioniert dadurch in jeder Sprache, keine +;; Sprachweiche noetig (anders als vfc-dialog-typ-waehlen). Die +;; namenlosen Dispatcher-Funktionen (ohne Suffix) bevorzugen -radio und +;; fallen nur bei einem technischen Fehler (Temp-Datei nicht schreibbar/ +;; ladbar, Sentinel 'FEHLER) auf -popup zurueck. ;; ============================================================ ;; Bau-Parameter eines bestehenden VF_n-Blocks aus XDATA + Attributen lesen - @@ -846,14 +861,76 @@ (cons "hz" hz) (cons "z-start" z-start))) -;; Basis-Eingabe (Dialog 1): deltaL, deltaH, Richtung, Einfuegehoehe, -;; Fahrtrichtung, Seite, Geruest fuer Einzelmodul, Geruestoption. +;; Baut die DCL-Zeilen einer boxed_radio_column mit einem radio_button pro +;; Eintrag in "labels" (Tile-Keys "_0".."_(n-1)"). labels +;; sind bereits fertige, uebersetzte Strings (ssg-text/ssg-textf) - werden +;; hier nur noch fuer die DCL-Syntax escaped (ssg-dcl-label-escape). +;; Genutzt von vfs-dialog-eingabe-basis-radio fuer die drei verbleibenden +;; Radio-Gruppen (Seite, Motorseite, Geruestoption). +(defun vfs-dcl-radio-gruppe-zeilen (prefix box-label labels / zeilen i) + (setq zeilen (list + ": boxed_radio_column {" + (strcat " label = \"" (ssg-dcl-label-escape box-label) "\";") + (strcat " key = \"" prefix "\";"))) + (setq i 0) + (foreach lbl labels + (setq zeilen (append zeilen (list + (strcat " : radio_button { key = \"" prefix "_" (itoa i) "\"; label = \"" + (ssg-dcl-label-escape lbl) "\"; }")))) + (setq i (1+ i)) + ) + (append zeilen (list "}")) +) + +;; Baut das (cond ...)-Fragment (als String), das innerhalb eines +;; action_tile-Callbacks den gewaehlten Index 0..(n-1) einer per +;; vfs-dcl-radio-gruppe-zeilen erzeugten Gruppe ermittelt. n ist die +;; tatsaechliche Options-Anzahl zum Generierungszeitpunkt - dadurch bleibt +;; z.B. die Geruesttyp- oder Winkel-Gruppe korrekt, auch wenn deren Anzahl +;; sich einmal aendert (kein hartkodiertes "_1"/"_2"/"_3" im Aufrufer). +(defun vfs-dcl-radio-idx-cond (prefix n / s i) + (setq s "(cond " i 1) + (while (< i n) + (setq s (strcat s "((= (get_tile \"" prefix "_" (itoa i) "\") \"1\") " (itoa i) ")")) + (setq i (1+ i)) + ) + (strcat s "(t 0))") +) + +;; L_GF1/L_GF2 aus dem gewaehlten Verteilungsmodus ("0".."3") berechnen - +;; gemeinsam genutzt von der Popup- UND der Radio-Variante von +;; vfs-dialog-winkel-verteilung (frueher inline in beiden dupliziert). +;; Rueckgabe: (L_GF1 L_GF2). +(defun vfs-verteilung-berechnen (verteilung-modus L_GF staustrecke-basis lgf1-manuell-str / + L_GF1 L_GF2) + (cond + ((= verteilung-modus "0") + (setq L_GF1 (/ L_GF 2.0) L_GF2 (/ L_GF 2.0))) + ((= verteilung-modus "1") + (setq L_GF1 (float staustrecke-basis)) + (setq L_GF2 (max 0.0 (- L_GF (float staustrecke-basis))))) + ((= verteilung-modus "2") + (setq L_GF2 (float staustrecke-basis)) + (setq L_GF1 (max 0.0 (- L_GF (float staustrecke-basis))))) + ((= verteilung-modus "3") + (setq L_GF1 (atof lgf1-manuell-str)) + (if (or (null L_GF1) (< L_GF1 0)) (setq L_GF1 (/ L_GF 2.0))) + (if (> L_GF1 L_GF) (setq L_GF1 L_GF)) + (setq L_GF2 (max 0.0 (- L_GF L_GF1)))) + (t (setq L_GF1 (/ L_GF 2.0) L_GF2 (/ L_GF 2.0))) + ) + (list L_GF1 L_GF2) +) + +;; Basis-Eingabe (Dialog 1, Popup-Variante): deltaL, deltaH, Richtung, +;; Einfuegehoehe, Fahrtrichtung, Seite, Geruest fuer Einzelmodul, +;; Geruestoption. ;; prefill: Assoziationsliste zur Vorbelegung (fuer VARIOFOERDERER_EDIT) mit ;; Schluesseln "deltaL" "deltaH" "richtung" "einfuegehoehe" "hz" "seite" ;; "geruest-einzelmodul" "geruest-typ" - oder nil fuer Neuanlage mit Defaults. ;; Rueckgabe: (deltaL deltaH richtung einfuegehoehe hz seite ;; geruest-einzelmodul geruest-typ) oder nil bei Abbruch. -(defun vfs-dialog-eingabe-basis (prefill / dcl-pfad dat ergebnis +(defun vfs-dialog-eingabe-basis-popup (prefill / dcl-pfad dat ergebnis dlg-deltal dlg-deltah dlg-richtung dlg-hoehe dlg-fahrtrichtung dlg-seite dlg-motorseite dlg-dimension dlg-geruest-einzelmodul dlg-geruest-typ @@ -973,17 +1050,189 @@ ) ) -;; Winkel/Verteilung-Eingabe (Dialog 2): Winkel-Popup aus den vorher +;; Basis-Eingabe (Dialog 1, dynamische Variante) - siehe Kopfkommentar +;; des DCL-DIALOG-Abschnitts. Nur "Seite", "Motorseite" und "Geruestoption" +;; bleiben als Radio-Button-Gruppen (alle Optionen sofort sichtbar); alle +;; anderen Felder (Foerderrichtung, Fahrtrichtung, Darstellung 2D/3D) sind +;; wieder Comboboxen (popup_list), wie der uebrige Dialog. Gleiche +;; prefill-Struktur/Rueckgabe wie -popup; 'FEHLER bei technischem Fehler +;; (Dispatcher faellt dann zurueck). +(defun vfs-dialog-eingabe-basis-radio (prefill / zeilen dcl-pfad dat ergebnis + dlg-deltal dlg-deltah dlg-hoehe + dlg-richtung-idx dlg-fahrt-idx dlg-seite-idx + dlg-motorseite-idx dlg-dim-idx + dlg-geruest-einzelmodul dlg-geruest-typ-idx + deltaL deltaH richtung hz seite z-start dim + motorseite geruest-einzelmodul geruest-typ + sel-richtung sel-fahrt sel-seite sel-motorseite + sel-dim sel-geruest-typ n-geruest-typ) + ;; Vorbelegte Auswahl-Indizes aus prefill ableiten - gleiche Logik wie + ;; -popup, nur als Index statt als Popup-Tile-Wert. + (setq sel-richtung + (if (and prefill (equal (cdr (assoc "richtung" prefill)) "Ab")) 1 0)) + (setq sel-fahrt + (if prefill + (cond ((equal (cdr (assoc "hz" prefill)) 90.0 0.1) 1) + ((equal (cdr (assoc "hz" prefill)) 180.0 0.1) 2) + ((equal (cdr (assoc "hz" prefill)) 270.0 0.1) 3) + (t 0)) + 0)) + (setq sel-seite + (if (and prefill (equal (cdr (assoc "seite" prefill)) "links")) 1 0)) + (setq sel-motorseite + (if (and prefill (equal (cdr (assoc "motorseite" prefill)) "links")) 1 0)) + (setq sel-dim + (if (equal (cond ((cdr (assoc "dim" prefill))) ((ssg-ils-dim-aktuell))) "2D") 1 0)) + (setq n-geruest-typ (length *ssg-geruest-optionen*)) + (setq sel-geruest-typ + (ssg-geruest-typ-to-idx (if prefill (cdr (assoc "geruest-typ" prefill)) nil))) + + (setq zeilen (list + "vfs_basis_dyn : dialog {" + " label = \"VarioFoerderer (Standard) - Eingabe\";" + " : column {" + " : boxed_column {" + " label = \"Geometrie\";" + " : row {" + " : edit_box { key = \"deltal\"; label = \"Horizontale Distanz deltaL (mm):\"; edit_width = 10; }" + " : popup_list { key = \"richtung\"; label = \"Foerderrichtung:\"; width = 16; }" + " }" + " : row {" + " : edit_box { key = \"einfuegehoehe\"; label = \"Einfuegehoehe (mm):\"; edit_width = 10; }" + " : edit_box { key = \"deltah\"; label = \"Hoehenunterschied deltaH (mm):\"; edit_width = 10; }" + " }" + " }" + " : boxed_column {" + " label = \"Ausrichtung\";" + " : popup_list { key = \"fahrtrichtung\"; label = \"Fahrtrichtung:\"; width = 30; }" + " : row {")) + (setq zeilen (append zeilen + (vfs-dcl-radio-gruppe-zeilen "seite" "Seite:" + (list (ssg-text "gf-dlg-rechts") (ssg-text "gf-dlg-links"))))) + (setq zeilen (append zeilen + (vfs-dcl-radio-gruppe-zeilen "motorseite" "Motorseite:" + (list (ssg-text "gf-dlg-rechts") (ssg-text "gf-dlg-links"))))) + (setq zeilen (append zeilen (list + " }" + " : popup_list { key = \"dimension\"; label = \"Darstellung (2D/3D):\"; width = 20; }" + " }" + " : boxed_column {" + " label = \"Geruest\";" + " : toggle { key = \"geruest_einzelmodul\"; label = \"Geruest fuer Einzelmodul\"; }"))) + (setq zeilen (append zeilen + (vfs-dcl-radio-gruppe-zeilen "geruest_typ" "Geruestoption:" *ssg-geruest-optionen*))) + (setq zeilen (append zeilen (list + " }" + " }" + " ok_cancel;" + "}"))) + + (setq dcl-pfad (ssg-dcl-temp-schreiben "vfsbasis" zeilen)) + (if (null dcl-pfad) + 'FEHLER + (progn + (setq dat (load_dialog dcl-pfad)) + (if (not (new_dialog "vfs_basis_dyn" dat)) + (progn + (if (and dat (>= dat 0)) (unload_dialog dat)) + (vl-file-delete dcl-pfad) + 'FEHLER + ) + (progn + (set_tile "deltal" (if prefill (rtos (cdr (assoc "deltaL" prefill)) 2 0) "15000")) + (set_tile "deltah" (if prefill (rtos (cdr (assoc "deltaH" prefill)) 2 0) "3000")) + (set_tile "einfuegehoehe" (if prefill (rtos (cdr (assoc "einfuegehoehe" prefill)) 2 1) "0")) + + (start_list "richtung") + (add_list (ssg-text "vfs-dlg-richtung-auf")) + (add_list (ssg-text "vfs-dlg-richtung-ab")) + (end_list) + (set_tile "richtung" (itoa sel-richtung)) + + (start_list "fahrtrichtung") + (add_list (ssg-textf "gf-dlg-richtung-0" (list grad-zeichen))) + (add_list (ssg-textf "gf-dlg-richtung-90" (list grad-zeichen))) + (add_list (ssg-textf "gf-dlg-richtung-180" (list grad-zeichen))) + (add_list (ssg-textf "gf-dlg-richtung-270" (list grad-zeichen))) + (end_list) + (set_tile "fahrtrichtung" (itoa sel-fahrt)) + + (set_tile (strcat "seite_" (itoa sel-seite)) "1") + (set_tile (strcat "motorseite_" (itoa sel-motorseite)) "1") + + (start_list "dimension") + (add_list "3D") + (add_list "2D") + (end_list) + (set_tile "dimension" (itoa sel-dim)) + + (set_tile "geruest_einzelmodul" + (if (or (null prefill) (equal (cdr (assoc "geruest-einzelmodul" prefill)) "1")) "1" "0")) + (set_tile (strcat "geruest_typ_" (itoa sel-geruest-typ)) "1") + + (action_tile "accept" + (strcat + "(setq dlg-deltal (get_tile \"deltal\"))" + "(setq dlg-deltah (get_tile \"deltah\"))" + "(setq dlg-hoehe (get_tile \"einfuegehoehe\"))" + "(setq dlg-richtung-idx (atoi (get_tile \"richtung\")))" + "(setq dlg-fahrt-idx (atoi (get_tile \"fahrtrichtung\")))" + "(setq dlg-seite-idx " (vfs-dcl-radio-idx-cond "seite" 2) ")" + "(setq dlg-motorseite-idx " (vfs-dcl-radio-idx-cond "motorseite" 2) ")" + "(setq dlg-dim-idx (atoi (get_tile \"dimension\")))" + "(setq dlg-geruest-einzelmodul (get_tile \"geruest_einzelmodul\"))" + "(setq dlg-geruest-typ-idx " (vfs-dcl-radio-idx-cond "geruest_typ" n-geruest-typ) ")" + "(done_dialog 1)" + ) + ) + (action_tile "cancel" "(done_dialog 0)") + + (setq ergebnis (start_dialog)) + (unload_dialog dat) + (vl-file-delete dcl-pfad) + + (if (= ergebnis 1) + (progn + (setq deltaL (atof dlg-deltal)) + (setq deltaH (atof dlg-deltah)) + (setq richtung (nth dlg-richtung-idx '("Auf" "Ab"))) + (setq z-start (atof dlg-hoehe)) + (setq hz (nth dlg-fahrt-idx '(0.0 90.0 180.0 270.0))) + (setq seite (nth dlg-seite-idx '("rechts" "links"))) + (setq motorseite (nth dlg-motorseite-idx '("rechts" "links"))) + (setq dim (nth dlg-dim-idx '("3D" "2D"))) + (setq geruest-einzelmodul dlg-geruest-einzelmodul) + (setq geruest-typ (ssg-geruest-idx-to-typ dlg-geruest-typ-idx)) + (list deltaL deltaH richtung z-start hz seite dim geruest-einzelmodul geruest-typ motorseite) + ) + nil + ) + ) + ) + ) + ) +) + +;; Dispatcher: bevorzugt die dynamisch generierte Variante (Seite/ +;; Motorseite/Geruestoption als Radio, Rest als Combobox - siehe +;; Kopfkommentar des DCL-DIALOG-Abschnitts), Rueckfall auf die reine +;; Popup-Variante nur bei technischem Fehler. +(defun vfs-dialog-eingabe-basis (prefill / ergebnis) + (setq ergebnis (vfs-dialog-eingabe-basis-radio prefill)) + (if (eq ergebnis 'FEHLER) (vfs-dialog-eingabe-basis-popup prefill) ergebnis) +) + +;; Winkel/Verteilung-Eingabe (Dialog 2, Popup-Variante): Winkel-Popup aus den vorher ;; berechneten gueltigen Varianten (gueltige-winkel/ergebnis-liste, siehe ;; berechne-alle-winkel), plus L_GF-Verteilung vorne/hinten (analog Schritt ;; 8+9 in c:VarioFoerderer). ;; prefill: Assoziationsliste ("winkel" . best-winkel) ("verteilung-idx" . "0".."3") ;; ("lgf1" . mm) - oder nil fuer Vorbelegung mit dem kleinsten gueltigen Winkel. ;; Rueckgabe: (best-winkel L_GF L_VF L_GF1 L_GF2 verteilung-modus) oder nil bei Abbruch. -(defun vfs-dialog-winkel-verteilung (gueltige-winkel ergebnis-liste staustrecke-basis prefill / +(defun vfs-dialog-winkel-verteilung-popup (gueltige-winkel ergebnis-liste staustrecke-basis prefill / dcl-pfad dat ergebnis w eintrag idx sel-idx init-verteilung dlg-winkel dlg-verteilung dlg-lgf1 - best-winkel L_GF L_VF verteilung-modus L_GF1 L_GF2) + best-winkel L_GF L_VF verteilung-modus L_GF1 L_GF2 lgf-paar) (setq dcl-pfad (strcat (getenv "DXFM_DCL") "/variofoerderer.dcl")) (setq dat (load_dialog dcl-pfad)) (if (not (new_dialog "variofoerderer_winkel" dat)) @@ -1052,22 +1301,8 @@ (setq eintrag (assoc best-winkel ergebnis-liste)) (setq L_GF (nth 1 eintrag) L_VF (nth 2 eintrag)) (setq verteilung-modus dlg-verteilung) - (cond - ((= verteilung-modus "0") - (setq L_GF1 (/ L_GF 2.0) L_GF2 (/ L_GF 2.0))) - ((= verteilung-modus "1") - (setq L_GF1 (float staustrecke-basis)) - (setq L_GF2 (max 0.0 (- L_GF (float staustrecke-basis))))) - ((= verteilung-modus "2") - (setq L_GF2 (float staustrecke-basis)) - (setq L_GF1 (max 0.0 (- L_GF (float staustrecke-basis))))) - ((= verteilung-modus "3") - (setq L_GF1 (atof dlg-lgf1)) - (if (or (null L_GF1) (< L_GF1 0)) (setq L_GF1 (/ L_GF 2.0))) - (if (> L_GF1 L_GF) (setq L_GF1 L_GF)) - (setq L_GF2 (max 0.0 (- L_GF L_GF1)))) - (t (setq L_GF1 (/ L_GF 2.0) L_GF2 (/ L_GF 2.0))) - ) + (setq lgf-paar (vfs-verteilung-berechnen verteilung-modus L_GF staustrecke-basis dlg-lgf1)) + (setq L_GF1 (car lgf-paar) L_GF2 (cadr lgf-paar)) (list best-winkel L_GF L_VF L_GF1 L_GF2 verteilung-modus) ) nil @@ -1076,6 +1311,14 @@ ) ) +;; Winkel/Verteilung-Eingabe: Dialog nutzt durchgaengig Comboboxen +;; (variofoerderer_winkel in dcl/variofoerderer.dcl) - keine Radio-Variante +;; mehr noetig, da weder "winkel" noch "verteilung" als Radio bleiben +;; (siehe Kopfkommentar des DCL-DIALOG-Abschnitts). +(defun vfs-dialog-winkel-verteilung (gueltige-winkel ergebnis-liste staustrecke-basis prefill) + (vfs-dialog-winkel-verteilung-popup gueltige-winkel ergebnis-liste staustrecke-basis prefill) +) + ;; ============================================================ ;; XDATA-Metadaten fuer den Edit-Nachbau (App "SSG_VF_EDIT"). ;; Markiert ein VF_n-INSERT als "mit dem Standard-Dialog erstellt" und @@ -1310,6 +1553,38 @@ ;; Neuanlage 0). Wer hz nicht kennt: der Wert steht in der urspruenglichen ;; Bau-Eingabe (z.B. mubea.json "hz") bzw. ergibt sich aus der Ausrichtung. ;; Bereits markierte Bloecke und Nicht-VF-Bloecke werden uebersprungen. + +;; Baurichtung (hz) fuer EINEN Fund per Dialog abfragen - ersetzt den +;; frueheren getreal-Prompt in c:VARIOFOERDERER_NACHRUESTEN. "kopf" per +;; set_tile aus ssg-text befuellt (Blockname + erkannter Typ), deshalb +;; sprachunabhaengig. Rueckgabe: hz (Real) bei OK, 'ABBRUCH wenn der Nutzer +;; bewusst abbricht (Aufrufer uebernimmt dann default-hz, OHNE nochmal auf +;; der Konsole zu fragen), nil wenn der Dialog gar nicht gezeigt werden +;; konnte/durfte (GUI aus) - NUR dann faellt der Aufrufer auf den alten +;; getreal-Prompt zurueck. +(defun vfs-dialog-nachruesten-hz (bname typ default-hz / dcl-pfad dat ergebnis hz) + (if (or (not (car (atoms-family 1 '("SSG-GUI-P")))) (ssg-gui-p)) + (progn + (setq dcl-pfad (strcat (getenv "DXFM_DCL") "/variofoerderer.dcl")) + (setq dat (load_dialog dcl-pfad)) + (if (not (new_dialog "vfs_nachruesten_hz" dat)) + (progn (if (and dat (>= dat 0)) (unload_dialog dat)) nil) + (progn + (set_tile "kopf" (ssg-textf "vfs-nachruesten-erkannt" (list bname typ))) + (set_tile "hz" (rtos default-hz 2 1)) + (action_tile "accept" + "(setq hz (distof (get_tile \"hz\") 2)) (done_dialog 1)") + (action_tile "cancel" "(done_dialog 0)") + (setq ergebnis (start_dialog)) + (unload_dialog dat) + (cond ((and (= ergebnis 1) hz) hz) (t 'ABBRUCH)) + ) + ) + ) + nil + ) +) + (defun c:VARIOFOERDERER_NACHRUESTEN ( / ss i ent ed bname attribs xd typ hz n-neu n-skip default-hz) (ssg-start "VARIOFOERDERER_NACHRUESTEN" nil) @@ -1337,8 +1612,14 @@ (if (or (assoc "GF_Bogen_R_60" attribs) (assoc "GF_Bogen_L_60" attribs)) "etage" "standard")) (princ (ssg-textf "vfs-nachruesten-erkannt" (list bname typ))) - (setq hz (getreal (ssg-textf "vfs-nachruesten-prompt-hz" (list (rtos default-hz 2 1))))) - (if (null hz) (setq hz default-hz)) + (setq hz (vfs-dialog-nachruesten-hz bname typ default-hz)) + (cond + ((eq hz 'ABBRUCH) (setq hz default-hz)) + ((null hz) + (setq hz (getreal (ssg-textf "vfs-nachruesten-prompt-hz" (list (rtos default-hz 2 1))))) + (if (null hz) (setq hz default-hz)) + ) + ) (setq default-hz hz) (vf-edit-xdata-schreiben ent typ hz) (princ (ssg-textf "vfs-nachruesten-markiert" (list (rtos hz 2 1)))) diff --git a/README.md b/README.md index 9f9bdfc..9715b25 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # dxfmakros -AutoLISP-Makros fuer BricsCAD Pro zur automatisierten Erstellung von Foerderanlagen- und Kreisel-Layouts. +AutoLISP-Makros (SSG_LIB) fuer BricsCAD Pro zur automatisierten Erstellung von Foerderanlagen-Layouts (ILS Kreisel, Omniflo, VarioFoerderer, Gefaellestrecke, Geruest). ## Menustruktur (SSG_LIB) @@ -14,164 +14,70 @@ SSG_LIB │ │ └── Eckrad → ILS_Eckrad │ ├── Zusatzmodul │ │ ├── BTMT-Beladung → ILS_BTMT_Beladung -│ │ └── BTMT-Entladung → ILS_BTMT_Entladung +│ │ ├── BTMT-Entladung → ILS_BTMT_Entladung +│ │ ├── Scanner einfuegen → ILS_SCANNER_INSERT +│ │ └── Separator einfuegen → ILS_SEPARATOR_INSERT │ ├── Foerderer -│ │ ├── VarioFoerderer → FOERDERANLAGE -│ │ └── Gefaellestrecke → ILS_G{K90,K60,K30}{L,R} -│ └── ILS Schliessen +│ │ ├── Standard → StandardVarioFoerderer / StandardVarioFoerderer3D +│ │ ├── Etage → EtageVarioFoerderer / EtageVarioFoerderer3D +│ │ ├── Streckengruppe (Linienzug) → LinienzugVarioFoerdererManuell / -Zielhoehe / -Stueckweise +│ │ └── Vario_Kette_Merge / VARIOFOERDERER_NACHRUESTEN +│ └── Gefaellestrecke +│ ├── einfach → GEFAELLESTRECKE_EINFACH +│ ├── aus 3D-Linie → GEFAELLESTRECKE_3DLINIE +│ └── aus Linienzug → GEFAELLESTRECKE_LINIENZUG ├── Omniflo -│ ├── Boegen -│ │ ├── 90 → OMNI_APB_630_90 -│ │ ├── 67.5 → OMNI_APB_550_675 -│ │ ├── 45 → OMNI_APB_630_45 -│ │ ├── 22.5 → OMNI_APB_550_225 -│ │ └── 180 → OMNI_APB_650_180 -│ ├── Aluprofil Gerade -│ │ ├── AP 60 → OMNI_AP60 -│ │ ├── AP 110 → OMNI_AP110 -│ │ └── APG 110 → OMNI_APG110 -│ ├── Weiche 90 Grad -│ │ ├── Einfach → OMNI_W90_Einfach -│ │ ├── Doppel → OMNI_W90_Doppel -│ │ └── Dreiwege → OMNI_W90_Dreiwege -│ ├── Weiche 45 Grad -│ │ ├── Einfach → OMNI_W45_Einfach -│ │ ├── Doppel → OMNI_W45_Doppel -│ │ └── Dreiwege → OMNI_W45_Dreiwege -│ ├── Weichen Parallel -│ │ ├── Einfach → OMNI_WP_Einfach -│ │ ├── Doppel → OMNI_WP_Doppel -│ │ └── Dreiwege → OMNI_WP_Dreiwege -│ ├── Weichenkoerper -│ │ ├── Einfach → OMNI_WK_Einfach -│ │ ├── Doppel → OMNI_WK_Doppel -│ │ └── Dreiwege → OMNI_WK_Dreiwege -│ ├── Weichenkombinationen -│ │ ├── Delta 1400/700 → OMNI_WKomb_Delta_1400 -│ │ ├── Delta 1600/800 → OMNI_WKomb_Delta_1600 -│ │ └── Star → OMNI_WKomb_Star -│ ├── TEF Elemente -│ │ ├── Umlenkspannst. rechts → OMNI_TEF_UmlenkR -│ │ ├── Umlenkspannst. links → OMNI_TEF_UmlenkL -│ │ ├── Antriebst. links → OMNI_TEF_AntriebL -│ │ ├── Antriebst. rechts → OMNI_TEF_AntriebR -│ │ └── TEF Gerade → OMNI_TEF_Gerade -│ ├── KettenFoerderer → OMNI_KettenFoerderer -│ └── Ladeschleifen -│ └── AP 110 → OMNI_LADESCHLEIFE_AP110 +│ ├── Boegen APB110/APB60 +│ ├── Aluprofil Gerade (AP60/AP110/APG110) +│ ├── Weichen (90/45/Parallel/Weichenkoerper/Weichenkombinationen) +│ ├── TEF Elemente, Anbaugruppen, Verbinder, Boegen fuer Weichen +│ ├── Element bearbeiten → OMNI_EDIT +│ └── Ladeschleifen (AP110) → OMNI_LADESCHLEIFE_AP110 +├── Geruest +│ ├── Rohr-Geruest-Traeger → GERUEST_TRAEGER +│ ├── Saeulen / Rohrabhaenger / Wandbefestigung → GERUEST +│ └── Funktionen â–¸ Traeger beschriften → GERUEST_TRAEGER_BESCHRIFTEN +├── SPS +│ ├── TRO (Marker einfuegen/bearbeiten) → TRO_INSERT / TRO_EDIT +│ └── Connections (einfuegen/bearbeiten, Eingang/Ausgang) → CONNECTION_INSERT / CONNECTION_EDIT / ILS_EINGANG_INSERT / ILS_AUSGANG_INSERT +├── Export +│ ├── Export Sivas → EXPORTSIVAS +│ └── Export CSV → EXPORTCSV ├── Konfiguration │ ├── Umschalten 2D und 3D → SSG_DIM_SWITCH -│ └── Sprache → SSG_SPRACHE -└── External - ├── Call Python → CALLPYTHON - ├── Export Sivas → EXPORTSIVAS - └── Export CSV → EXPORTCSV +│ ├── Sprache → SSG_SPRACHE +│ └── Planquadrate an/aus → PLANQUADRATE_TOGGLE +├── Werkzeuge +│ ├── Bloecke zaehlen/loeschen, Attribute ersetzen → ssg_blocktools +│ └── Text ersetzen/parallel/Hoehe/Stil → ssg_texttools +└── Tests (Testsuiten ausfuehren und exportieren) ``` -## Befehle nach Modul +Vollstaendige, laufend aktualisierte Befehls-/Modulzuordnung: siehe `CLAUDE.md` (Abschnitt "Modulzuordnung im Menue") und `menu/SSG_LIB.mnu`. -### KreiselInsert.lsp - Kreisel Modul-Einfuegesystem +## Lademechanismus -Zeichnet ILS-Kreiselmodule als Compound-Bloecke mit konfigurierbarem Abstand, Typ und Attributen. +Module werden per Lazy-Loading ueber das Menuesystem geladen (`ssg-ensure` in `SSG_LIB.mnl`). Einmalige Einrichtung pro BricsCAD-Installation: -| Befehl | Beschreibung | -| --- | --- | -| `KreiselInsert` | Modul an manuell gewaehltem Punkt einfuegen (Abstand, Drehung, Hoehe, Typ interaktiv) | -| `KreiselConnect` | Smart Connect: Kreisel zwischen zwei parallelen Linien einfuegen, berechnet Position/Abstand/Rotation automatisch | -| `KreiselEdit` | Bestehenden Kreisel per DCL-Dialog bearbeiten (Name, Abstand, Hoehe, Ausrichtung, Drehrichtung, Typ) | -| `KreiselRedraw` | Kreisel mit optionalem neuem Abstand/Hoehe neu zeichnen, Attribute bleiben erhalten | -| `KreiselQuick` | Schnelleinfuegen mit Standardparametern (horizontal) | -| `KreiselParams` | Aktuelle Kreisel-Parameterkonfiguration anzeigen | -| `ILS_Eckrad` | Einzelnen AN8-Block (Antriebs-/Eckrad) an gewaehltem Punkt einfuegen | -| `KreiselLabelPos` | Label-Position (X/Y-Offset) konfigurieren | -| `KreiselLabelHoehe` | Label-Texthoehe konfigurieren | -| `KreiselLabelSetup` | Label-Darstellung konfigurieren (Hoehe, Farbe, Abstand, Schriftart) | +1. `bin/setenv.bat` ausfuehren (setzt `DXFM_LISP` u.a.) +2. BricsCAD starten +3. In der Kommandozeile `_MENULOAD` eingeben, `menu/SSG_LIB.mnu` auswaehlen -### OmniModulInsert.lsp - Omniflo Komponenten - -Einfuegen von Omniflo-Standardkomponenten (Boegen, Geraden, Weichen, TEF-Elemente). - -| Befehl | Beschreibung | -| --- | --- | -| `OMNI_LOAD` | Omniflo-Komponentendaten aus JSON-Dateien laden | -| `OMNI_INFO_BOGEN` | Info zu einem Omniflo-Bogen nach Sivas-ID anzeigen | -| `OMNI_INFO_WEICHE` | Info zu einer Omniflo-Weiche nach Sivas-ID anzeigen | -| `OMNI_APB_*` | Boegen einfuegen (90, 67.5, 45, 22.5, 180 Grad) | -| `OMNI_AP60/AP110/APG110` | Aluprofil-Geraden einfuegen | -| `OMNI_W90/W45/WP/WK_*` | Weichen einfuegen (Einfach, Doppel, Dreiwege) | -| `OMNI_WKomb_Delta_1400/1600, OMNI_WKomb_Star` | Weichenkombinationen einfuegen | -| `OMNI_TEF_*` | TEF-Elemente einfuegen (Umlenk-/Antriebsstationen, Gerade) | -| `OMNI_KettenFoerderer` | Kettenfoerderer einfuegen | -| `OMNI_LADESCHLEIFE_AP110` | Ladeschleife (AP 110) ueber DCL-Dialog einfuegen | - -### VarioFoerderer.lsp - Foerderanlage Generator (v24.4) - -Generiert komplette Vario-Foerderanlagen mit Vertikalboegen, Motor-/Spannstationen, Staustrecken und Ein-/Ausschleus-Elementen. - -| Befehl | Beschreibung | -| --- | --- | -| `FOERDERANLAGE` | Interaktiver Generator: Zwei Punkte oder manuelle Distanz/Hoehe, berechnet optimalen Bogenwinkel | -| `FOERDERTEST` | Automatischer Testlauf (Distanz=6000, Hoehe=2000, Richtung=Auf), speichert als DXF | - -Unterstuetzte Bogenwinkel: 3, 9, 12, 15, 24, 36, 48 Grad - -### ssg_layer.lsp - Layerverwaltung - -| Befehl | Beschreibung | -| --- | --- | -| `SSG-LayerSet` | Objekt anklicken, dessen Layer wird aktiv | -| `SSG-LayerInfo` | Layer eines angeklickten Objekts anzeigen | -| `SSG-LayerMove` | Selektierte Objekte auf Ziel-Layer verschieben | -| `SSG-LayerCopy` | Selektierte Objekte auf Ziel-Layer kopieren | -| `SSG-LayerMoveType` | Alle Objekte gleichen Typs von Referenz- auf Ziel-Layer verschieben | -| `SSG-LayerOff` | Layer per Klick ausschalten (mehrfach moeglich) | -| `SSG-LayerFreeze` | Layer per Klick einfrieren (mehrfach moeglich) | -| `SSG-FreezeAll` | Alle Layer ausser dem angeklickten einfrieren | -| `SSG-ThawAll` | Alle eingefrorenen Layer auftauen | -| `SSG-LayerOnAll` | Alle ausgeschalteten Layer einschalten | -| `SSG-LayerDelete` | Layer und alle Objekte darauf loeschen (mit Bestaetigung) | - -### SSG_LIB_Commands.lsp - Konfiguration - -| Befehl | Beschreibung | -| --- | --- | -| `SSG_DIM_SWITCH` | Schaltet zwischen 2D- und 3D-Bloecken um | -| `SSG_SPRACHE` | Sprache umschalten (Deutsch/Englisch), persistiert ueber `DXFM_LANG` | - -### tests.lsp - Export und Python-Integration - -| Befehl | Beschreibung | -| --- | --- | -| `CALLPYTHON` | Python-Skripte via CLI aufrufen, Ausgabe loggen | -| `EXPORTSIVAS` | Daten im Sivas-Format exportieren | -| `EXPORTCSV` | Zeichnungsdaten als CSV exportieren | - -## Kernbibliotheken (Lisp/) - -| Datei | Beschreibung | -| --- | --- | -| `ssg_core.lsp` | Kernel: Umgebung, Layer-Ops, Block-Ops, Attribut-Ops, Fehlerbehandlung | -| `ssg_lang.lsp` | Sprachumschaltung Deutsch/Englisch (`ssg-text`, `ssg-textf`, `*ssg-lang*`) | -| `ssg_dialog.lsp` | Dialog-Vorlagen: Slide-Preview, Modul-Dialog, Block-Einfuegesequenz | -| `ssg_layer.lsp` | Layer-Verwaltungsbefehle | -| `ssg_load.lsp` | Loader: Laedt alle SSG-Bibliotheken in korrekter Reihenfolge | -| `on_start.lsp` | Startup-Skript: Setzt Umgebung und laedt Libraries | +Details und die Core-Ladereihenfolge: siehe `CLAUDE.md` Abschnitt "Lademechanismus (Lazy Loading via MNL)". ## Projektstruktur ```text dxfmakros/ -├── Blocks/ # DWG-Bloecke (AN8, SP8, Vertikalboegen, etc.) -├── Lisp/ # AutoLISP-Makros und Kernbibliotheken -├── bin/ # Hilfs-Skripte (setenv, create_tests, etc.) -├── cfg/ # Konfigurationsdateien (export.cfg, block_suffix.cfg) -├── data/ # Quelldaten (JSON, DXF) - nicht im Git -├── dcl/ # DCL-Dialogdefinitionen (kreisel_edit.dcl) -├── doc/ # Dokumentation und Referenz-DXFs -├── examples/ # Beispieldateien -├── lib/ # Python-Skripte (export_csv.py, etc.) -├── logs/ # Logdateien - nicht im Git -├── menu/ # BricsCAD-Menudefinition (SSG_LIB.mnu) +├── Lisp/ # AutoLISP-Makros und Kernbibliotheken (Details: Lisp/README.md) +├── bin/ # Hilfs-Skripte (setenv.bat, start_briscad.bat) +├── cfg/ # Konfigurationsdateien +├── data/ # JSON-Daten, DXF-Quellen, Block-Libraries - nicht im Git +├── dcl/ # DCL-Dialogdefinitionen +├── doc/ # Dokumentation (doc/plan Architektur/Analysen, doc/todo offene Fahrplaene) +├── lib/ # Python-Skripte (DXF-Bearbeitung, Export) +├── log/ # Logdateien - nicht im Git +├── menu/ # BricsCAD-Menudefinition (SSG_LIB.mnu, SSG_LIB.mnl) ├── results/ # Ergebnisse / Exporte - nicht im Git ├── tests/ # Testskripte und Referenzdaten ├── LICENSE @@ -181,33 +87,34 @@ dxfmakros/ ## Umgebungsvariablen +Werden via `bin/setenv.bat` gesetzt. Wichtigste: + | Variable | Beschreibung | | --- | --- | | `DXFMAKRO` | Wurzelverzeichnis des Projekts | -| `DXFM_BLOCKS` | Pfad zum Blocks-Verzeichnis | -| `DXFM_LISP` | Pfad zu den LISP-Dateien | -| `DXFM_RESULTS` | Pfad fuer DXF-Exporte | -| `DXFM_BIN` | Skriptverzeichnis | -| `DXFM_LIB` | Python-Quellcode | -| `DXFM_CFG` | Konfigurationsdateien | -| `DXFM_TESTS` | Testverzeichnis | -| `DXFM_DATA` | Datenverzeichnis | +| `DXFM_LISP` | Pfad zu den LISP-Dateien (benoetigt fuer `ssg-ensure`) | +| `DXFM_BLOCKS` | Pfad zu DWG-Bloecken | +| `DXFM_DATA` | Datenverzeichnis (JSON, DXF) | +| `DXFM_LOG` | Log-Verzeichnis | +| `DXFM_DCL` | Pfad zu DCL-Dialogdateien | +| `DXFM_CFG` | Konfigurationsverzeichnis | | `DXFM_LANG` | Aktive Sprache (`de_DE`/`en_GB`), persistiert von `SSG_SPRACHE`; Default `de_DE` | -| `BRICSCAD` | Pfad zur BricsCAD-Executable | - -## Einrichtung - -1. `bin\setenv.bat` ausfuehren (setzt alle Umgebungsvariablen) -2. BricsCAD starten - `on_start.lsp` laedt alle Libraries automatisch via `startup.scr` -3. Menu `SSG_LIB` nutzen oder Befehle in der Kommandozeile ausfuehren +| `DXFM_VF_SPEC` | Spec-Datei fuer `VF_SPEC_BAU` (optional) | +| `BRICSCAD` | Pfad zur BricsCAD-Executable (fuer headless Testlauf) | ## Tests +Headless-Unit-Tests (file- und zeichnungsfrei): + ```bat -bin\create_tests.bat +bin\run_tests.bat --lisp ``` -Erzeugt eine Test-DXF, fuehrt Kreisel-Einfuege- und Exportbefehle aus, und validiert die Ergebnisse gegen Referenzdateien in `tests/references/`. +Zeichnungs-/Szenario-Tests laufen interaktiv in BricsCAD ueber das Menue `Tests` bzw. `SSG_RUN_ALL_TESTS`. Details: `doc/LispTests.md`, `tests/README.md`. + +## Dokumentation + +Ausfuehrliche Modul- und Befehlsdokumentation: `CLAUDE.md` (Projektanweisungen) und `Lisp/README.md`. Weitere Anwender-/Entwickler-Themen direkt in `doc/` (u.a. `variofoerderer.md`, `csv-export.md`, `faq.md`, `kommandos.md`). Architektur-Analysen und Planungsdokumente in `doc/plan/` (u.a. `translate.md`, `Entwicklungsplan.md`), offene Fahrplaene/TODOs in `doc/todo/`. ## Lizenz diff --git a/bin/dxf_anlage_export.bat b/bin/dxf_anlage_export.bat new file mode 100644 index 0000000..40952ca --- /dev/null +++ b/bin/dxf_anlage_export.bat @@ -0,0 +1,13 @@ +@echo off +REM ================================================================ +REM Mit unseren Makros gebaute (und von Hand nachbearbeitete) Anlage +REM aus einer Zeichnung in Nachbau-Testdaten zurueckschreiben. +REM Siehe doc/kommandos.md, doc/json-input-lisp.md. +REM +REM Aufruf: +REM bin\dxf_anlage_export.bat [--projekt NAME] +REM ================================================================ + +call "%~dp0setenv.bat" + +python "%DXFM_LIB%\dxf_anlage_export.py" %* diff --git a/bin/dxf_scan_components.bat b/bin/dxf_scan_components.bat new file mode 100644 index 0000000..b8abe93 --- /dev/null +++ b/bin/dxf_scan_components.bat @@ -0,0 +1,14 @@ +@echo off +REM ================================================================ +REM Erkennt ILS-/Omniflo-Komponenten in einer FREMDEN Projektzeichnung +REM (ohne unsere Wrapper-Bloecke) und schreibt sie als Nachbau-JSON. +REM Siehe doc/kommandos.md, doc/json-input-lisp.md. +REM +REM Aufruf: +REM bin\dxf_scan_components.bat +REM [--fenster X0 Y0 X1 Y1] [--projekt NAME] +REM ================================================================ + +call "%~dp0setenv.bat" + +python "%DXFM_LIB%\dxf_scan_components.py" %* diff --git a/bin/dxf_vf_abbild.bat b/bin/dxf_vf_abbild.bat new file mode 100644 index 0000000..ee5fd21 --- /dev/null +++ b/bin/dxf_vf_abbild.bat @@ -0,0 +1,14 @@ +@echo off +REM ================================================================ +REM Attribut-/Bausteinabbild der VF_/GF_-Wrapperbloecke einer +REM Testzeichnung - Regressionsnetz fuer Umbauten am Bau-Ablauf. +REM Siehe doc/kommandos.md. +REM +REM Aufruf: +REM bin\dxf_vf_abbild.bat [--spec S] [--json J] [--referenz R] +REM [--schreibe-referenz] [--praefix VF_,GF_] +REM ================================================================ + +call "%~dp0setenv.bat" + +python "%DXFM_LIB%\dxf_vf_abbild.py" %* diff --git a/bin/set_coords.bat b/bin/set_coords.bat index 4bc3eee..7354666 100644 --- a/bin/set_coords.bat +++ b/bin/set_coords.bat @@ -1,6 +1,6 @@ @echo off REM ================================================================ -REM Setzt Einfuegepunkte fuer Omniflo DXF-Dateien +REM Setzt Koordinatensystem-Bloecke (K1-K4) in Omniflo DXF-Dateien REM ================================================================ call "%~dp0setenv.bat" diff --git a/bin/show_exportcsv.bat b/bin/show_exportcsv.bat index 37f9b67..e1fee54 100644 --- a/bin/show_exportcsv.bat +++ b/bin/show_exportcsv.bat @@ -6,11 +6,12 @@ REM Zeichnet jede Zeile der Export-CSV (EXPORTCSV) als achsparalleles REM Rechteck aus Position + Boundingbox, mit Legende und Tooltip je REM Rechteck sowie den Anschluss-Koordinatensystemen K1-K4 (rotes Kreuz, REM blaue Linie K1->K2->K3->K4), und oeffnet das Ergebnis im Browser. -REM Optional zusaetzlich Materialfluss- und Bahnzusammenhang-Graph. +REM Optional zusaetzlich Materialfluss-, Bahnzusammenhang- und TRO-Fluss-Graph. REM REM Die Darstellung uebernimmt sps_skel (bin\material_flow.bat, -REM Option --show-bbs). sps_skel wird neben dxfmakros erwartet -REM (..\sps_skel) oder ueber die Umgebungsvariable SPS_SKEL_HOME. +REM Option --show-bbs, bzw. bin\tro_flow.bat fuer 'tro'). sps_skel wird +REM neben dxfmakros erwartet (..\sps_skel) oder ueber die Umgebungs- +REM variable SPS_SKEL_HOME. REM REM Aufruf: REM bin\show_exportcsv.bat results\export.csv, alle Zeilen @@ -18,17 +19,51 @@ REM bin\show_exportcsv.bat meine_export.csv andere CSV in results\ (oder Pf REM bin\show_exportcsv.bat export.csv flow ohne VF/GF-Einzelbausteine REM bin\show_exportcsv.bat export.csv mflow zusaetzlich Materialfluss-Graph REM bin\show_exportcsv.bat export.csv track zusaetzlich Bahnzusammenhang-Graph -REM bin\show_exportcsv.bat export.csv flow mflow track alles kombiniert +REM bin\show_exportcsv.bat export.csv tro zusaetzlich TRO-Fluss-Graph (lagerichtig) +REM bin\show_exportcsv.bat export.csv flow mflow track tro alles kombiniert REM -REM mflow/track brauchen Graphviz ('dot' im PATH). +REM mflow/track/tro brauchen Graphviz ('dot' bzw. 'neato' im PATH). REM REM Ergebnis: %SKEL_RESULTS%\_bbs.svg REM %SKEL_RESULTS%\_material_flow.svg (nur mit 'mflow') REM %SKEL_RESULTS%\_track.svg (nur mit 'track') +REM %SKEL_RESULTS%\_tro_flow.svg (nur mit 'tro') REM ================================================================ setlocal +if /i "%~1"=="--help" goto :help +if /i "%~1"=="-h" goto :help +if "%~1"=="/?" goto :help +goto :main + +:help +echo Aufruf: +echo bin\show_exportcsv.bat results\export.csv, alle Zeilen +echo bin\show_exportcsv.bat meine_export.csv andere CSV in results\ ^(oder Pfad^) +echo bin\show_exportcsv.bat export.csv flow ohne VF/GF-Einzelbausteine +echo bin\show_exportcsv.bat export.csv mflow zusaetzlich Materialfluss-Graph +echo bin\show_exportcsv.bat export.csv track zusaetzlich Bahnzusammenhang-Graph +echo bin\show_exportcsv.bat export.csv tro zusaetzlich TRO-Fluss-Graph ^(lagerichtig^) +echo bin\show_exportcsv.bat export.csv flow mflow track tro alles kombiniert +echo. +echo Schalter ^(2.-5. Argument, Reihenfolge beliebig^): +echo all Darstellungsmodus: alle Zeilen ^(Default^) +echo flow Darstellungsmodus: ohne VF/GF-Einzelbausteine +echo mflow zusaetzlich Materialfluss-SVG erzeugen und oeffnen +echo track zusaetzlich Bahnzusammenhang-SVG erzeugen und oeffnen +echo tro zusaetzlich TRO-Fluss-SVG ^(lagerichtig^) erzeugen und oeffnen +echo. +echo mflow/track/tro brauchen Graphviz ^('dot' bzw. 'neato' im PATH^). +echo. +echo Ergebnis: +echo %%SKEL_RESULTS%%\^_bbs.svg +echo %%SKEL_RESULTS%%\^_material_flow.svg ^(nur mit 'mflow'^) +echo %%SKEL_RESULTS%%\^_track.svg ^(nur mit 'track'^) +echo %%SKEL_RESULTS%%\^_tro_flow.svg ^(nur mit 'tro'^) +endlocal & exit /b 0 + +:main REM Umgebung dxfmakros (DXFM_RESULTS ...) call "%~dp0setenv.bat" >nul @@ -58,20 +93,24 @@ for %%F in ("%CSV%") do ( set "SVG=%SKEL_RESULTS%\%%~nF_bbs.svg" set "MF_SVG=%SKEL_RESULTS%\%%~nF_material_flow.svg" set "TRACK_SVG=%SKEL_RESULTS%\%%~nF_track.svg" + set "TRO_SVG=%SKEL_RESULTS%\%%~nF_tro_flow.svg" ) REM Weitere Argumente (Reihenfolge beliebig): REM all (Default) / flow - Darstellungsmodus der Boundingbox-Ansicht REM mflow - zusaetzlich Materialfluss-SVG erzeugen und oeffnen REM track - zusaetzlich Bahnzusammenhang-SVG erzeugen und oeffnen +REM tro - zusaetzlich TRO-Fluss-SVG (lagerichtig) erzeugen und oeffnen set "MODE=all" set "MFLOW=" set "TRACK=" -for %%A in (%2 %3 %4) do ( +set "TRO=" +for %%A in (%2 %3 %4 %5) do ( if /i "%%~A"=="all" set "MODE=all" if /i "%%~A"=="flow" set "MODE=flow" if /i "%%~A"=="mflow" set "MFLOW=1" if /i "%%~A"=="track" set "TRACK=1" + if /i "%%~A"=="tro" set "TRO=1" ) REM Beide Graphen entstehen erst mit --tosvg (Graphviz 'dot') @@ -83,10 +122,15 @@ REM Alte SVGs entfernen, damit nie eine veraltete Ansicht geoeffnet wird if exist "%SVG%" del "%SVG%" if defined MFLOW if exist "%MF_SVG%" del "%MF_SVG%" if defined TRACK if exist "%TRACK_SVG%" del "%TRACK_SVG%" +if defined TRO if exist "%TRO_SVG%" del "%TRO_SVG%" call "%SPS_SKEL_HOME%\bin\material_flow.bat" --file "%CSV%" --show-bbs %MODE% --show-kx %OPTS% set "RC=%ERRORLEVEL%" +if defined TRO ( + call "%SPS_SKEL_HOME%\bin\tro_flow.bat" --file "%CSV%" --tosvg --use-cords +) + if not exist "%SVG%" ( echo FEHLER: SVG wurde nicht erzeugt ^(material_flow Exit-Code %RC%^). endlocal & exit /b 1 @@ -115,6 +159,14 @@ if defined TRACK ( echo WARNUNG: Bahnzusammenhang-SVG wurde nicht erzeugt ^(Graphviz 'dot' vorhanden?^). ) ) +if defined TRO ( + if exist "%TRO_SVG%" ( + echo TRO-Fluss: %TRO_SVG% + start "" "%TRO_SVG%" + ) else ( + echo WARNUNG: TRO-Fluss-SVG wurde nicht erzeugt ^(Graphviz 'neato' vorhanden?^). + ) +) endlocal & exit /b 0 diff --git a/cfg/sivas.cfg b/cfg/sivas.cfg index d3b1aa8..8a19a35 100644 --- a/cfg/sivas.cfg +++ b/cfg/sivas.cfg @@ -64,14 +64,14 @@ # Code (bedingte Umbenennung bzw. Umrechnung mm->m). [eckrad] -# Bewusst (noch) beschreibende Klartext-Keys statt Merkmalnummern (kein -# bekanntes Sivas-Merkmal fuer Eckrad-Geruest hinterlegt) - details_mit_ -# bezeichnung() in export_sivas.py laesst nicht-numerische Keys unveraendert. -# "ist_eins" liefert ein ECHTES bool (True/False), NICHT "Ja"/"Nein" wie -# ja_nein bei Kreisel/Gefaellestrecke - json.dumps schreibt dann true/false -# ohne Anfuehrungszeichen (bisheriges Verhalten von build_eckrad_details). -Geruest fuer Einzelmodul= GERUEST_EINZELMODUL : 0 | ist_eins -Geruestoption fuer Einzelmodul= GERUEST_TYP : Schoenenberger Geruest +# Dieselben Sivas-Merkmalnummern wie bei Kreisel (siehe oben), aus +# Merkmalsstamm_in_Sivas.xlsx: 3992 "Geruest fuer Einzelmodul", 3993 +# "Geruestoption fuer Einzelmodul". "ist_eins" liefert ein ECHTES bool +# (True/False), NICHT "Ja"/"Nein" wie ja_nein bei Kreisel/Gefaellestrecke - +# json.dumps schreibt dann true/false ohne Anfuehrungszeichen (bisheriges +# Verhalten von build_eckrad_details). +3992= GERUEST_EINZELMODUL : 0 | ist_eins +3993= GERUEST_TYP : Schoenenberger Geruest | geruest_typ_anzeige [geruest] Farbe= FARBE diff --git a/dcl/gefaellestrecke.dcl b/dcl/gefaellestrecke.dcl index bfb6c12..a6610ec 100644 --- a/dcl/gefaellestrecke.dcl +++ b/dcl/gefaellestrecke.dcl @@ -6,6 +6,14 @@ // Eingaben entsprechen dem interaktiven Skript Gefaellestrecke.lsp: // Abstand deltaL, Gefaellewinkel, Fahrtrichtung, // Seite AUS-Element, Seite EIN-Element. +// +// gefaellestrecke_linienzug: Modus 3 (aus Linienzug LINE+ARC) - ersetzt die +// Konsolenfragen AUS-/EIN-Element Winkel+Seite, Winkelbestaetigung und die +// finale Einfuegen-Bestaetigung in EINEM Dialog (gf-dialog-linienzug in +// Gefaellestrecke.lsp). Die Objektauswahl (ssget LINE/ARC) UND die +// Start-/Endpunktwahl bleiben davor Zeichnung/Konsole - der automatisch +// berechnete Vorschlagswinkel wird als Vorbelegung des Winkel-Feldes +// gezeigt, mit einer kurzen Zusammenfassung der Segmentanalyse darueber. // ============================================================ gefaellestrecke : dialog { @@ -108,3 +116,64 @@ gefaellestrecke : dialog { ok_cancel; } + +// Modus 3 (aus Linienzug): AUS-/EIN-Element Winkel+Seite, Gefaellewinkel +// (Vorschlag vorbelegt, ueberschreibbar) und eine kurze Zusammenfassung der +// automatisch analysierten Segmentkette. "zusammenfassung" wird per +// set_tile aus mehreren ssg-textf-Zeilen zusammengesetzt (gf-dialog- +// linienzug, Gefaellestrecke.lsp) - sprachunabhaengig, nur das Label-Chrome +// bleibt hart deutsch wie der Rest dieser Datei. +gefaellestrecke_linienzug : dialog { + label = "Gefaellestrecke aus Linienzug"; + + : column { + + : boxed_column { + label = "Segmentanalyse"; + : text { key = "zusammenfassung"; label = ""; width = 55; } + } + + : boxed_row { + label = "AUS-Element"; + + : popup_list { + key = "aus_winkel"; + label = "Winkel:"; + width = 12; + } + : popup_list { + key = "aus_seite"; + label = "Seite:"; + width = 14; + } + } + + : boxed_row { + label = "EIN-Element"; + + : popup_list { + key = "ein_winkel"; + label = "Winkel:"; + width = 12; + } + : popup_list { + key = "ein_seite"; + label = "Seite:"; + width = 14; + } + } + + : boxed_column { + label = "Gefaellewinkel"; + + : edit_box { + key = "winkel"; + label = "Gefaellewinkel (Grad):"; + edit_width = 10; + } + } + + } + + ok_cancel; +} diff --git a/dcl/variofoerderer.dcl b/dcl/variofoerderer.dcl index ef475a9..6af94de 100644 --- a/dcl/variofoerderer.dcl +++ b/dcl/variofoerderer.dcl @@ -3,14 +3,47 @@ // Dialoge fuer c:VarioFoerderer (vf_core.lsp): // vfc_typ: Anlagen-Typ waehlen (Standard/Etage/Linienzug) - ersetzt die // Konsolen-Liste am Befehlsanfang. Radio-Buttons statt Dropdown, daher -// Labels FEST auf Deutsch (siehe Kommentar bei vfc_linienzug_modus). -// vfc_linienzug_modus: NUR bei Typ "Linienzug" - Modus 1/2/3 waehlen. -// variofoerderer_basis/variofoerderer_winkel: Werteingabe fuer den -// Standardfall (anlage-typ "standard"): deltaL, deltaH, Foerderrichtung, -// Einfuegehoehe, Fahrtrichtung, Seite, Winkel, GF-Verteilung. -// Die 3D-Linie-Eingabe (Modus "3D-Linie"), der Etage-Typ und die -// Linienzug-Kette selbst (nach der Modus-Wahl) bleiben interaktiv -// (Kommandozeile bzw. eigener Wizard in vf_linienzug_wizard.dcl). +// Labels FEST auf Deutsch. +// vfc_startpunkt: Startpunkt der Anlage picken (Punktwahl-Button + +// X/Y/Z-Anzeige) - ersetzt den reinen Konsolen-getpoint fuer +// Standard UND Etage (vfc-dialog-startpunkt-waehlen, vf_core.lsp). +// variofoerderer_basis/variofoerderer_winkel: Popup-Fallback-Varianten +// der Werteingabe (deltaL, deltaH, Foerderrichtung, Einfuegehoehe, +// Fahrtrichtung, Seite, Motorseite, Dimension, Geruest, Winkel, +// GF-Verteilung) - gemeinsam genutzt von Standard UND Etage. Die +// bevorzugte Variante (vfs_basis_dyn) wird zur Laufzeit generiert +// (vfs-dialog-eingabe-basis-radio in vf_standard.lsp) und steht NICHT +// in dieser Datei - NUR "Seite", "Motorseite" und "Geruestoption" +// bleiben dort Radio-Button-Gruppen (sofort sichtbare Optionen), alle +// anderen Felder sind Comboboxen wie hier. variofoerderer_basis hier +// ist nur noch der Fallback, falls das Schreiben/Laden der Temp-DCL +// mal scheitert. variofoerderer_winkel (Winkel/Verteilung) hat KEINE +// generierte Variante mehr und wird direkt verwendet (komplett +// Combobox, siehe vfs-dialog-winkel-verteilung in vf_standard.lsp). +// vfc_eingabemodus: Eingabemodus (3D-Linie / Werte manuell) waehlen - +// ersetzt die Konsolen-Abfrage am Anfang von vf-dialog-ablauf fuer +// Standard UND Etage (vfc-dialog-eingabemodus-waehlen, vf_core.lsp). +// Die 3D-Linie SELBST (Objektauswahl) bleibt danach Zeichnung/Konsole +// (get-line-start-end-points) - eine Linie laesst sich nicht durch ein +// Formularfeld ersetzen; alle abgeleiteten Werte (deltaL/deltaH/hz/ +// Einfuegehoehe) landen danach als Prefill im bestehenden Basis-Dialog +// (variofoerderer_basis), der sie ganz normal ueberschreibbar anzeigt. +// Der Etage-Typ und die Linienzug-Kette selbst bleiben interaktiv +// (Kommandozeile bzw. eigener Wizard in vf_linienzug_wizard.dcl). Der +// Linienzug-Modus (1/2/3/4/5) wird seit den Menue-Wrappern +// c:LinienzugVarioFoerderer*/c:QcLinieMitGf* nicht mehr per Dialog erfragt, +// sondern ist je Menuepunkt fest vorbelegt - der frueher hier gefuehrte +// Dialog vfc_linienzug_modus ist dadurch entfallen. +// +// Dialog fuer c:VARIOFOERDERER_NACHRUESTEN (vf_standard.lsp): +// vfs_nachruesten_hz: Baurichtung (hz) je unmarkiertem VF_n-Fund abfragen +// - ersetzt den fruehren getreal-Prompt. Die Objektauswahl selbst +// (ssget) bleibt Zeichnung/Konsole; je Fund OHNE SSG_VF_EDIT-Marker +// zeigt der Befehl diesen Dialog einmal, mit dem zuletzt eingegebenen +// Wert als Vorgabe (vfs-dialog-nachruesten-hz in vf_standard.lsp). +// "Abbrechen" uebernimmt fuer diesen Fund den Vorgabewert, OHNE auf die +// alte Konsolenfrage zurueckzufallen (die bleibt reiner Rueckfall fuer +// den Fall, dass der Dialog gar nicht gezeigt werden kann/darf). // ============================================================ // Anlagen-Typ waehlen (Standard/Etage/Linienzug). Radio-Buttons statt @@ -39,7 +72,7 @@ vfc_typ : dialog { } : radio_button { key = "typ_linienzug"; - label = "3D-Linienzug (gemischte GF/VF-Kette)"; + label = "Streckengruppe (gemischte GF/VF-Kette)"; } } @@ -57,50 +90,75 @@ vfc_typ : dialog { } } -// Linienzug-Modus waehlen (nur wenn Anlagen-Typ = Linienzug). Radio-Buttons, -// gleicher Deutsch-fest-Grund wie bei vfc_typ oben. -vfc_linienzug_modus : dialog { - label = "VarioFoerderer - Linienzug Modus"; +// Eingabemodus waehlen (3D-Linie / Werte manuell) - Radio-Buttons, gleiches +// Muster und dieselbe Sprach-/GUI-Einschraenkung wie vfc_typ oben (Labels +// hart deutsch, Rueckfall auf Konsolen-Liste bei DXFM_LANG=en_GB oder +// abgeschalteter GUI - siehe vfc-dialog-eingabemodus-waehlen, vf_core.lsp). +vfc_eingabemodus : dialog { + label = "VarioFoerderer - Eingabemodus"; : boxed_radio_column { - label = "Linienzug-Modus waehlen:"; + label = "Eingabemodus waehlen:"; key = "modus"; : radio_button { - key = "modus_1"; - label = "Manuelle Eingabe (Punkte klicken)"; + key = "modus_3dlinie"; + label = "3D-Linie auswaehlen (Start- und Endpunkt mit Hoehe)"; value = "1"; } : radio_button { - key = "modus_2"; - label = "3D-Objekte + Ziel-Hoehe (Randwert-Solver, fertig)"; - } - : radio_button { - key = "modus_3"; - label = "3D-Objekte stueckweise (Vorwaerts-Nachbau, in Arbeit)"; - } - : radio_button { - key = "modus_4"; - label = "QC Linie mit GF (automatische Auf-Ab-Berechnung)"; - } - : radio_button { - key = "modus_5"; - label = "QC Linie mit GF und U-Form"; + key = "modus_werte"; + label = "Werte manuell eingeben"; } } - // "Zurueck" springt hier sinnvoll zur Anlagen-Typ-Frage zurueck (siehe - // vfc-dialog-linienzug-modus-waehlen/c:VarioFoerderer). : row { alignment = centered; - : button { key = "back"; label = "Zurueck"; fixed_width = true; width = 12; } - spacer; : button { key = "accept"; label = "Ok Weiter"; is_default = true; fixed_width = true; width = 12; } spacer; : button { key = "cancel"; label = "Abbrechen"; is_cancel = true; fixed_width = true; width = 12; } } } +// Startpunkt der Anlage waehlen - Punktwahl-Button + Anzeige, analog +// vflw_punkt_zahl (dcl/vf_linienzug_wizard.dcl): der Button schliesst den +// Dialog (done_dialog 2), DANACH laeuft getpoint auf der Kommandozeile +// (aus einem action_tile-Callback heraus funktioniert getpoint in BricsCAD +// nicht), der Dialog wird danach mit der Anzeige erneut aufgebaut. "kopf" +// und "pt_anzeige" werden per set_tile aus ssg-text befuellt (siehe +// vfc-dialog-startpunkt-waehlen, vf_core.lsp) - deshalb sprachunabhaengig +// nutzbar. Button-Chrome bleibt hart deutsch, wie im Rest dieser Datei. +vfc_startpunkt : dialog { + label = "VarioFoerderer - Startpunkt"; + + : column { + : text { + key = "kopf"; + label = ""; + width = 50; + } + : row { + : button { + key = "pick"; + label = "Punkt waehlen..."; + fixed_width = true; + width = 18; + } + : text { + key = "pt_anzeige"; + label = "(kein Punkt gewaehlt)"; + } + } + } + + : row { + alignment = centered; + : button { key = "accept"; label = "Ok Weiter"; is_default = true; fixed_width = true; width = 12; } + spacer; + : button { key = "cancel"; label = "Abbrechen"; is_cancel = true; fixed_width = true; width = 12; } + } +} + variofoerderer_basis : dialog { label = "VarioFoerderer (Standard) - Eingabe"; @@ -115,24 +173,24 @@ variofoerderer_basis : dialog { label = "Horizontale Distanz deltaL (mm):"; edit_width = 10; } - : edit_box { - key = "deltah"; - label = "Hoehenunterschied deltaH (mm):"; - edit_width = 10; - } - } - - : row { : popup_list { key = "richtung"; label = "Foerderrichtung:"; width = 16; } + } + + : row { : edit_box { key = "einfuegehoehe"; label = "Einfuegehoehe (mm):"; edit_width = 10; } + : edit_box { + key = "deltah"; + label = "Hoehenunterschied deltaH (mm):"; + edit_width = 10; + } } } @@ -144,15 +202,17 @@ variofoerderer_basis : dialog { label = "Fahrtrichtung:"; width = 30; } - : popup_list { - key = "seite"; - label = "Seite:"; - width = 16; - } - : popup_list { - key = "motorseite"; - label = "Motorseite:"; - width = 16; + : row { + : popup_list { + key = "seite"; + label = "Seite:"; + width = 16; + } + : popup_list { + key = "motorseite"; + label = "Motorseite:"; + width = 16; + } } : popup_list { key = "dimension"; @@ -211,3 +271,25 @@ variofoerderer_winkel : dialog { ok_cancel; } + +// Baurichtung (hz) je unmarkiertem VF_n-Fund abfragen (VARIOFOERDERER_ +// NACHRUESTEN). "kopf" per set_tile aus ssg-text befuellt (Blockname + +// erkannter Typ), deshalb sprachunabhaengig nutzbar - Button-Chrome bleibt +// hart deutsch wie der Rest dieser Datei. "Abbrechen" bricht nur den +// aktuellen Fund ab: der vorbelegte default-hz wird fuer DIESEN Fund +// uebernommen (Aufrufer sieht 'ABBRUCH, kein Rueckfall auf die Konsole) - +// die ganze Nachruesten-Schleife laeuft weiter, kein Gesamtabbruch. +vfs_nachruesten_hz : dialog { + label = "VarioFoerderer nachruesten - Baurichtung"; + + : column { + : text { key = "kopf"; label = ""; width = 50; } + : edit_box { + key = "hz"; + label = "Baurichtung hz (Grad):"; + edit_width = 10; + } + } + + ok_cancel; +} diff --git a/dcl/vf_linienzug_wizard.dcl b/dcl/vf_linienzug_wizard.dcl index 6447aea..3a31dc8 100644 --- a/dcl/vf_linienzug_wizard.dcl +++ b/dcl/vf_linienzug_wizard.dcl @@ -523,12 +523,18 @@ vflw_horizontal : dialog { } // "Naechstes Element waehlen" - Hauptmenue des Linienzug-Aufbaus (Modus 1). -// IMMER alle 6 Optionen im Dialog vorhanden (stabile Keys) - "Vario-Kurve" +// IMMER alle 5 Optionen im Dialog vorhanden (stabile Keys) - "Vario-Kurve" // wird per mode_tile deaktiviert, wenn kein echter Kettenanfang vorliegt // (siehe vflw-gruppe-naechstes-element-impl, das dort auch die Positions- // Nummerierung passend umrechnet). Radio-Buttons statt Dropdown, Labels fest // deutsch, OHNE Nummern-Prefix (anders als die Konsolen-Variante - hier -// braucht es keine Zifferneingabe). +// braucht es keine Zifferneingabe). GF-Optionen stehen zuerst, VF-Optionen +// danach (nur Reihenfolge, kein echtes 2-Spalten-Layout: radio_button-Tiles +// muessen DIREKTE Kinder EINES Clusters sein, sonst ist die gegenseitige +// Ausschliesslichkeit kaputt - siehe Warnung bei vflw_winkel_hoehen unten, +// per Test bestaetigt). "Neue Linie BIS Kettenende" ist kein Radio mehr, +// sondern der eigene Button "OK und Abschliessen" unten (ruft direkt Slot 6 +// auf, ohne vorher eine Radio-Auswahl zu verlangen). vflw_naechstes_element : dialog { label = "VarioFoerderer Assistent"; @@ -542,27 +548,29 @@ vflw_naechstes_element : dialog { key = "wahl"; label = ""; : radio_button { key = "wahl_gfbogen"; label = "GF-Bogen (horizontale Kurve)"; } - : radio_button { key = "wahl_variokurve"; label = "Vario-Kurve (horizontale Kurve, 30/60/90 Grad)"; } : radio_button { key = "wahl_liniegf"; label = "Neue Linie: GF (Gefaellestrecke)"; value = "1"; } + : radio_button { key = "wahl_variokurve"; label = "Vario-Kurve (horizontale Kurve, 30/60/90 Grad)"; } : radio_button { key = "wahl_linievf"; label = "Neue Linie: Ab/Auf VF (VarioFoerderer-Einheit)"; } : radio_button { key = "wahl_horizontalvf"; label = "Neue VF mit horizontalem Anfang (Horizontal-Stueck)"; } - : radio_button { key = "wahl_linieende"; label = "Neue Linie BIS Kettenende (danach Separator + ES-Element)"; } } } // PROTOTYP "Zurueck"-Button (siehe Plan curious-prancing-parasol.md): ersetzt - // ok_cancel durch eine eigene 3-Button-Zeile. done_dialog-Codes: 0=Abbrechen + // ok_cancel durch eine eigene 4-Button-Zeile. done_dialog-Codes: 0=Abbrechen // (unveraendert, is_cancel -> ESC/Leertaste wirkt weiterhin), 1=Weiter - // (unveraendert, is_default), NEU 2=Zurueck (verwirft das zuletzt gebaute + // (unveraendert, is_default), 2=Zurueck (verwirft das zuletzt gebaute // Kettenglied und fragt es neu ab, siehe vflw-gruppe-naechstes-element-impl/ - // vfl-modus-abbruch-sichern). + // vfl-modus-abbruch-sichern), NEU 3=Abschliessen (entspricht der fruehen + // Radio-Option "Neue Linie BIS Kettenende" - ignoriert die Radio-Auswahl). : row { alignment = centered; - : button { key = "back"; label = "Zurueck"; fixed_width = true; width = 12; } + : button { key = "back"; label = "Zurueck"; fixed_width = true; width = 12; } spacer; - : button { key = "accept"; label = "Ok Weiter"; is_default = true; fixed_width = true; width = 12; } + : button { key = "accept"; label = "Ok Weiter"; is_default = true; fixed_width = true; width = 12; } spacer; - : button { key = "cancel"; label = "Abbrechen"; is_cancel = true; fixed_width = true; width = 12; } + : button { key = "abschliessen"; label = "OK und Abschliessen"; fixed_width = true; width = 18; } + spacer; + : button { key = "cancel"; label = "Abbrechen"; is_cancel = true; fixed_width = true; width = 12; } } } diff --git a/doc/faq.md b/doc/faq.md index aa0cbcc..31c0dc1 100644 --- a/doc/faq.md +++ b/doc/faq.md @@ -1,85 +1,364 @@ # FAQ - Dokumentation +- [FAQ - Dokumentation](#faq---dokumentation) + - [Allgemein](#allgemein) + - [Darf ich Baugruppen kopieren?](#darf-ich-baugruppen-kopieren) + - [Darf ich Baugruppen auflösen?](#darf-ich-baugruppen-auflösen) + - [Darf ich Attribute in den Baugruppen manuell ändern?](#darf-ich-attribute-in-den-baugruppen-manuell-ändern) + - [Darf ich Baugruppen mustern?](#darf-ich-baugruppen-mustern) + - [1. Kreisel](#1-kreisel) + - [1.1 Zusammensetzung, Abhängigkeiten, Randbedingungen](#11-zusammensetzung-abhängigkeiten-randbedingungen) + - [Zusammensetzung einer Kreiselbaugruppe](#zusammensetzung-einer-kreiselbaugruppe) + - [Einfügepunkt und Koordinatensystem](#einfügepunkt-und-koordinatensystem) + - [Höhe (Laufprofil und Unterkante)](#höhe-laufprofil-und-unterkante) + - [1.2 Funktionen](#12-funktionen) + - [Wie füge ich einen Kreisel ein?](#wie-füge-ich-einen-kreisel-ein) + - [Wie drehe ich einen Kreisel?](#wie-drehe-ich-einen-kreisel) + - [Wie ändere ich die Höhe eines Kreisels?](#wie-ändere-ich-die-höhe-eines-kreisels) + - [Wie ändere ich die Länge eines Kreisels?](#wie-ändere-ich-die-länge-eines-kreisels) + - [2. Gefällestrecke](#2-gefällestrecke) + - [2.1 Zusammensetzung, Abhängigkeiten, Randbedingungen](#21-zusammensetzung-abhängigkeiten-randbedingungen) + - [Zusammensetzung einer Gefällestrecke](#zusammensetzung-einer-gefällestrecke) + - [Einfügepunkt und Koordinatensystem](#einfügepunkt-und-koordinatensystem-1) + - [Höhe und Übergang zu Kreiselkoordinatensystem (Unterkante SP \<-\> Oberkante LP)](#höhe-und-übergang-zu-kreiselkoordinatensystem-unterkante-sp---oberkante-lp) + - [2.2 Funktionen](#22-funktionen) + - [Wie füge ich eine Gefällestrecke ein?](#wie-füge-ich-eine-gefällestrecke-ein) + - [Wie drehe ich eine Gefällestrecke?](#wie-drehe-ich-eine-gefällestrecke) + - [Wie ändere ich die Länge einer Gefällestrecke?](#wie-ändere-ich-die-länge-einer-gefällestrecke) + - [Wie baue ich eine Gefällestrecke mit einer bestimmten Kapazität als Puffer (freie Länge des Stauprofils)](#wie-baue-ich-eine-gefällestrecke-mit-einer-bestimmten-kapazität-als-puffer-freie-länge-des-stauprofils) + - [Wie baue ich eine Gefällestrecke zwischen zwei Kreiseln mit definiertem Abstand?](#wie-baue-ich-eine-gefällestrecke-zwischen-zwei-kreiseln-mit-definiertem-abstand) + - [Wie baue ich eine Gefällestrecke mit Kurven als Übergang zwischen zwei Kreiseln mit unterschiedlicher Höhe?](#wie-baue-ich-eine-gefällestrecke-mit-kurven-als-übergang-zwischen-zwei-kreiseln-mit-unterschiedlicher-höhe) + - [Wie baue ich eine Gefällestrecke mit manueller Höhenüberwindung ohne VF (QS-Platz Mubea)](#wie-baue-ich-eine-gefällestrecke-mit-manueller-höhenüberwindung-ohne-vf-qs-platz-mubea) + - [3. Varioförderer](#3-varioförderer) + - [3.1 Zusammensetzung, Abhängigkeiten, Randbedingungen](#31-zusammensetzung-abhängigkeiten-randbedingungen) + - [Zusammensetzung eines Varioförderer mit allen möglichen Komponenten](#zusammensetzung-eines-varioförderer-mit-allen-möglichen-komponenten) + - [Verfügbare Steigungen](#verfügbare-steigungen) + - [Einfügepunkt und Koordinatensystem](#einfügepunkt-und-koordinatensystem-2) + - [Höhe und Übergang zu Kreiselkoordinatensystem (Unterkante SP \<-\> Oberkante LP)](#höhe-und-übergang-zu-kreiselkoordinatensystem-unterkante-sp---oberkante-lp-1) + - [3.2 Funktionen](#32-funktionen) + - [Welche Einfügemodi gibt es und wie unterscheiden sich diese?](#welche-einfügemodi-gibt-es-und-wie-unterscheiden-sich-diese) + - [Wie baue ich einen VF in einem Puffer mit Gefällestrecken als Umschleusung?](#wie-baue-ich-einen-vf-in-einem-puffer-mit-gefällestrecken-als-umschleusung) + - [Wie baue ich einen VF zur Anfahrung einer bestimmten Höhe (Arbeitsplatz)?](#wie-baue-ich-einen-vf-zur-anfahrung-einer-bestimmten-höhe-arbeitsplatz) + - [Wie baue ich einen VF als Übergang zwischen zwei verschiedenen Kreiseln (unterschiedliche Höhe und Position x/y/z)](#wie-baue-ich-einen-vf-als-übergang-zwischen-zwei-verschiedenen-kreiseln-unterschiedliche-höhe-und-position-xyz) + - [Wie baue ich einen Förderer zu Ebenenüberquerung zwischen zwei Pufferblöcken, welche übereinander positioniert sind?](#wie-baue-ich-einen-förderer-zu-ebenenüberquerung-zwischen-zwei-pufferblöcken-welche-übereinander-positioniert-sind) + - [Wie baue ich eine Strecke mit mehreren VF und Gefällestrecken hintereinander zur Überbrückung von großen Entfernungen?](#wie-baue-ich-eine-strecke-mit-mehreren-vf-und-gefällestrecken-hintereinander-zur-überbrückung-von-großen-entfernungen) + - [4. Scanner und Separator](#4-scanner-und-separator) + - [4.1 Zusammensetzung, Abhängigkeiten, Randbedingungen](#41-zusammensetzung-abhängigkeiten-randbedingungen) + - [Einfügepunkt und Koordinatensystem](#einfügepunkt-und-koordinatensystem-3) + - [In welchen Baugruppen sind Scanner und Separatoren bereits eingebaut (Blöcke)?](#in-welchen-baugruppen-sind-scanner-und-separatoren-bereits-eingebaut-blöcke) + - [Zuordnung der frei platzierten Scanner und Separatoren zu einem Kreisel, Gefällestrecke oder VF](#zuordnung-der-frei-platzierten-scanner-und-separatoren-zu-einem-kreisel-gefällestrecke-oder-vf) + - [4.2 Funktionen](#42-funktionen) + - [Wie füge ich einen Scanner ein?](#wie-füge-ich-einen-scanner-ein) + - [Wie füge ich einen Separator ein?](#wie-füge-ich-einen-separator-ein) + - [5. Be- und Entladung](#5-be--und-entladung) + - [5.1 Zusammensetzung, Abhängigkeiten, Randbedingungen](#51-zusammensetzung-abhängigkeiten-randbedingungen) + - [Einfügepunkt und Koordinatensystem](#einfügepunkt-und-koordinatensystem-4) + - [Übergang zu Rutschstange (Oberkante LP \<-\> Oberkante Rutschprofil)](#übergang-zu-rutschstange-oberkante-lp---oberkante-rutschprofil) + - [5.2 Funktionen](#52-funktionen) + - [Wie füge ich eine Beladung oder Entladung ein?](#wie-füge-ich-eine-beladung-oder-entladung-ein) + - [Wie binde ich einen FLEX Förderer an die Entladung an?](#wie-binde-ich-einen-flex-förderer-an-die-entladung-an) + ## Allgemein -- Darf ich Baugruppen kopieren? -- Darf ich Baugruppen auflösen? -- Darf ich Attribute in den Baugruppen manuell ändern? -- Darf ich Baugruppen mustern? +### Darf ich Baugruppen kopieren? + +Natürlich darf man eine erzeugte Baugruppe kopieren. Beim Export erhält es dann eine neue eigene ID, so dass keine Verwechslungen im CSV File auftreten können. + +### Darf ich Baugruppen auflösen? + +**Nein**. Der Export erkennt Baugruppen anhand ihres Blocknamens. Wird eine Baugruppe aufgelöst (EXPLODE), fällt sie komplett aus dem Export heraus: keine ID-Vergabe, keine Export-Zeile, keine Scanner-/Separator-Zuordnung. Zusätzlich gehen die Attribute dabei verloren, da sie nicht mehr mit einem Block verknüpft sind. + +### Darf ich Attribute in den Baugruppen manuell ändern? + +Grundsätzlich **ja**, dafür gibt es teilweise auch eigene Dialoge (z.B. zum Bearbeiten eines Kreisels). Zwei Punkte sind dabei zu beachten: + +- **ID**: Wird eine ID manuell auf einen bereits vergebenen Wert gesetzt, wird das beim nächsten Export als Duplikat erkannt und der zweiten gefundenen Baugruppe automatisch eine neue ID zugewiesen – der manuell gesetzte Wert kann also überschrieben werden. +- **Artikelnummer / Teileart**: Hier gibt es keine automatische Prüfung. Ein falsch gesetzter Wert führt zu einer falschen Merkmalszuordnung im Sivas-Export, ohne dass das vorher auffällt. + +Andere Attribute (Bezeichnung, Höhe, Abstand etc.) können bedenkenlos manuell angepasst werden, sollten aber nach Möglichkeit über die vorgesehenen Edit-Befehle statt direkt am Attribut geändert werden, damit die Geometrie konsistent bleibt. + +![Attribute](images/Kreisel-attribute.png) +*Abbildung: Attribute eines Kreisels* + + +### Darf ich Baugruppen mustern? + +**Ja**. Gemusterte Kopien tragen zunächst dieselbe ID wie das Original, beim nächsten Export-Lauf wird das als Duplikat erkannt und den weiteren Instanzen automatisch eine neue, eindeutige ID zugewiesen. Die Eindeutigkeit ist also am Ende gewährleistet, sobald einmal der Export (oder die ID-Prüfung) gelaufen ist. ## 1. Kreisel ### 1.1 Zusammensetzung, Abhängigkeiten, Randbedingungen -- Zusammensetzung einer Kreiselbaugruppe -- Einfügepunkt und Koordinatensystem -- Höhe (Laufprofil und Unterkante) +#### Zusammensetzung einer Kreiselbaugruppe + +Eine Kreiselbaugruppe besteht aus: + +- einem **AN8-Kreis** (Antriebsstation) +- einem **SP8-Kreis** (Spannstation) +- zwei **Tangentenlinien** (oben/unten) +- optional zwei **PIN-Linien** (nur bei Kreiselart "PIN") +- einem **Beschriftungstext** ("KREISEL n: Art_Abstand") +- Attributen wie ID, Bezeichnung, Artikelnummer, Montagehöhe, Kreiselart, Abstand, Anzahl Separator/Scanner/Rampen, Gerüsttyp, Drehrichtung, Drehung, Nummer + +Das verwandte Eckrad besteht nur aus einem AN8-Kreis plus eigenen Attributen – ohne Tangenten, PIN-Linien oder Beschriftung. + +#### Einfügepunkt und Koordinatensystem + +Bei Rotation 0 (horizontal) liegt der Einfügepunkt auf der linken Kreistangente der AN-Seite (Antriebsstation); die Spannstation liegt im Abstand "Abstand" dahinter. Die Rotation dreht den gesamten Kreisel um diesen Punkt. + +Beim Verbinden zweier Kreisel ist der Startpunkt explizit der äusserste AN-Punkt (Antriebsstation) und der Endpunkt der äusserste SP-Punkt (Spannstation); Abstand und Rotation werden aus Distanz und Winkel der beiden Klickpunkte berechnet. Beim Eckrad ist der Einfügepunkt der berechnete Kreismittelpunkt (Berührpunkt + Radius in Klickrichtung). + +#### Höhe (Laufprofil und Unterkante) + +Die Höhe wird über die Z-Koordinate des Einfügepunkts gesteuert (ein separates Laufprofil/Unterkante-Konzept gibt es beim Kreisel nicht). Wird beim Einfügen ein Punkt mit Höhe > 0 gewählt, gilt diese; ansonsten greift ein konfigurierbarer Standardwert (Default 2000 mm). Über den Bearbeiten-Dialog kann die Höhe jederzeit nachträglich geändert werden – der Kreisel wird dann mit der neuen Höhe neu aufgebaut. + +> **Hinweis**: Eine getrennte Betrachtung von "Laufprofil" und "Unterkante" (zwei unterschiedliche Bezugsebenen) ist technisch nicht hinterlegt – nur ein einziger Höhenwert steuert die Position. Der Zusammenhang zu Gefällestrecke/VF-Übergängen (siehe unten) ist eine reine Konstruktionsfrage. ### 1.2 Funktionen -- Wie füge ich einen Kreisel ein? -- Wie drehe ich einen Kreisel? -- Wie ändere ich die Höhe eines Kreisels? -- Wie ändere ich die Länge eines Kreisels? +#### Wie füge ich einen Kreisel ein? + +Unter *ILS -> Kreisel-Module -> Einfuegen*. Dann die Fragen nach Anfangspunkt, Länge und Höhe des Kreisels beantworten. Alternativ *ILS -> Kreisel-Module -> Verbinden*, um einen Kreisel direkt an einen bestehenden anzuschliessen. + +#### Wie drehe ich einen Kreisel? + +Am einfachsten durch einen Doppelklick auf den Kreisel und der Auswahl des Dropdowns Ausrichtung (Horizontal, Vertikal). Alternativ über *ILS -> Kreisel-Module -> Bearbeiten* (oder per Rechtsklick auf den Kreisel -> *Kreisel bearbeiten*). +![Oberfläche Kreisel](images/Kreisel-mit-gui.png) +*Abbildung: Kreisel-Dialog mit GUI-Eingabe* + +#### Wie ändere ich die Höhe eines Kreisels? + +Durch einen Doppelklick auf den Kreisel und der Eingabe der neuen Höhe unter Hoehe (mm). Alternativ über *ILS -> Kreisel-Module -> Bearbeiten* (oder per Rechtsklick auf den Kreisel -> *Kreisel bearbeiten*). + +#### Wie ändere ich die Länge eines Kreisels? + +Durch einen Doppelklick auf den Kreisel und der Eingabe des neuen Abstands im Bearbeiten-Dialog. Alternativ über *ILS -> Kreisel-Module -> Bearbeiten* (oder per Rechtsklick auf den Kreisel -> *Kreisel bearbeiten*). ## 2. Gefällestrecke +- einfach +- aus 3D Linie +- aus Linienzug +- QC mit 30 Grad ES/AS +- QC mit 90 Grad ES/AS + ### 2.1 Zusammensetzung, Abhängigkeiten, Randbedingungen -- Zusammensetzung einer Gefällestrecke -- Einfügepunkt und Koordinatensystem -- Höhe und Übergang zu Kreiselkoordinatensystem (Unterkante SP <-> Oberkante LP) +#### Zusammensetzung einer Gefällestrecke + +Im Standardfall besteht eine Gefällestrecke aus vier in Reihe eingefügten Bausteinen: + +1. **AUS-Element** +2. **Staustrecke** (auf die gewünschte Länge skaliert, um den Gefällewinkel geneigt) +3. **Separator** +4. **EIN-Element** + +Beim Aufbau entlang eines frei gezeichneten Linienzugs kommen zusätzlich Gefällebogen-Bausteine hinzu; vor jedem Bogen wird automatisch ein weiterer Separator eingefügt. + +![Gefaellestrecke](images/gefaellestrecke.png) +*Abbildung: Gefällestrecke* + + +#### Einfügepunkt und Koordinatensystem + +Jeder Baustein hat ein Ein- und ein Ausgangskoordinatensystem, über die die Bausteine beim Verketten passgenau aneinandergesetzt werden. Beim Verketten kommt das Eingangskoordinatensystem (nicht der Block-Ursprung) auf den Zielpunkt zu liegen. + +Der vom Anwender gepickte Startpunkt der Gefällestrecke entspricht dem Eingangskoordinatensystem des AUS-Elements; die Förderstrecken-Mittelachse läuft durch diesen Punkt. Am Kettenende wird die Höhe hingegen am Ausgangskoordinatensystem des EIN-Elements gemessen, damit dieses exakt der Zielhöhe entspricht. + +#### Höhe und Übergang zu Kreiselkoordinatensystem (Unterkante SP <-> Oberkante LP) + +> **Hinweis**: Eine feste Höhenbeziehung zwischen "Unterkante SP" (Stauprofil) und "Oberkante LP" (Laufprofil eines Kreisels) ist nicht hinterlegt – das ist eine reine Konstruktionsfrage. Die Anschlusshöhe wird vom Konstrukteur über die Parameter Einfügehöhe/Zielhöhe gesteuert. ### 2.2 Funktionen -- Wie füge ich eine Gefällestrecke ein? -- Wie drehe ich eine Gefällestrecke? -- Wie ändere ich die Länge einer Gefällestrecke? -- Wie baue ich eine Gefällestrecke mit einer bestimmten Kapazität als Puffer (freie Länge des Stauprofils) -- Wie baue ich eine Gefällestrecke zwischen zwei Kreiseln mit definiertem Abstand? -- Wie baue ich eine Gefällestrecke mit Kurven als Übergang zwischen zwei Kreiseln mit unterschiedlicher Höhe? -- Wie baue ich eine Gefällestrecke mit manueller Höhenüberwindung ohne VF (QS-Platz Mubea) +#### Wie füge ich eine Gefällestrecke ein? + +Unter *ILS -> Gefaellestrecke -> einfach* für den Standardfall (Anfangspunkt, Länge, Höhe, Winkel über Dialog). Für eine Gefällestrecke entlang einer vorgezeichneten 3D-Linie *ILS -> Gefaellestrecke -> aus 3D-Linie*, für einen Linienzug aus mehreren Linien/Bögen *ILS -> Gefaellestrecke -> aus Linienzug (LINE + ARC)*. + +#### Wie drehe ich eine Gefällestrecke? + +Durch einen Doppelklick auf die Gefällestrecke (öffnet den Bearbeiten-Dialog mit denselben Eingaben wie beim Einfügen) oder per Rechtsklick -> *Gefaellestrecke bearbeiten*. + +#### Wie ändere ich die Länge einer Gefällestrecke? + +Durch einen Doppelklick auf die Gefällestrecke und Eingabe der neuen Länge im Bearbeiten-Dialog, oder per Rechtsklick -> *Gefaellestrecke bearbeiten*. + +#### Wie baue ich eine Gefällestrecke mit einer bestimmten Kapazität als Puffer (freie Länge des Stauprofils) + +#### Wie baue ich eine Gefällestrecke zwischen zwei Kreiseln mit definiertem Abstand? + +#### Wie baue ich eine Gefällestrecke mit Kurven als Übergang zwischen zwei Kreiseln mit unterschiedlicher Höhe? + +#### Wie baue ich eine Gefällestrecke mit manueller Höhenüberwindung ohne VF (QS-Platz Mubea) + +#### Was sind "QC mit 30/90 Grad ES/AS" und "QC mit U-Form"? + +Zwei Spezialbefehle fürs Zeichnen eines Linienzugs vorab, bei denen die +komplette Kette danach **automatisch ohne weitere Rückfragen** gebaut wird +(`QcLinieMitGf`/`QcLinieMitGfUForm`, technisch über den Linienzug-Ablauf +(Streckengruppe) mit einem erzwungenen Modus gebaut, im Menü aber bewusst +unter *ILS -> Gefaellestrecke* eingeordnet, da das Ergebnis eine reine +Gefällestrecke von AS bis ES ist): Statt bei jedem Schritt zu fragen "Welches +Element als nächstes?", wird direkt die Gefällestrecke zwischen den beiden +gewählten Punkten gebaut. "QC mit U-Form" (`QcLinieMitGfUForm`) löst +zusätzlich eine gefaltete (U-förmige) Linienführung: zwei Gefällestrecken- +Abschnitte mit unterschiedlicher Neigung (3°/9°) werden über ein +Gleichungssystem aus Höhen- und Lagebilanz passend zur Zielgeometrie +berechnet, nur am Kettenanfang nutzbar. ## 3. Varioförderer +Varioförderer können auf verschiedene Arten gebaut werden: + +1. Standard +2. Etage +3. Streckengruppe + - Manuelle Eingabe + - 3D-OBjekte mit Ziel-höhe + - 3D Objekte Stückweise (wird gerade noch implementiert bzw. ist expirementell) + +Der **"Standard Förderer"** ist die Verbindung von zwei Kreiseln mit einem definierten Abstand und (wenn nötig) einer Höhendifferenz. (z.B. für eine Umschleusung in einem Gefällepuffer) + +Ein **Etagenförderer** hat 30° Aussschleuselemente und wird verwendet, um zwei Kreisel über eine Etage zu verbinden. Diese beiden Kreisel müssen dabei übereinander liegen. + +Standard und Etage können aufgerufen werden mit _"Werte manuell"_ oder _"3D - Linie"_. +- Bei "_Werte Manuell_" definiert man den Verlauf durch Höhe und Länge über die Eingabe in einer Oberfläche. +- bei "_3D Linie_" ist die Auswahl einer Gerade im Raum möglich. Damit wird Richtung, Länge und Höhendifferenz angegeben. Dadurch werden diese Werte in die Oberfläche übernommen. Der beliebige Winkel in der Ebene kann durch die Linie festgelegt werden und ist nicht auf die Raster 0,90,270 Grad festgelegt + +Die **Variablen Förderer** könne auf drei verschiedene Arten definiert werden: + +- die _manuelle Eingabe_ frägt nach der Definition des AS Elementes ab, ob man abwechselnd eine GF oder einen VF definieren möchte. Mit einem Knopf unten kann man den Assistenten abschliessen und das ES erzeugen +- _Eine 3D Linie_ muss aus Geraden und Bögen (Winkel 30° mit dem Radius 500mm) bestehen. Wenn eine 3D Linie für Verlauf mit der korrekten Zielhöhe existiert, kann man iterativ die Einzelelemente (oder auch mehrere) dieser Einzellinien Linie anwählen, um den nächsten Förderer oder den nöchsten Gefälle-Anteil zu definieren. + + ### 3.1 Zusammensetzung, Abhängigkeiten, Randbedingungen -- Zusammensetzung eines Varioförderer mit allen möglichen Komponenten -- Verfügbare Steigungen -- Einfügepunkt und Koordinatensystem -- Höhe und Übergang zu Kreiselkoordinatensystem (Unterkante SP <-> Oberkante LP) +#### Zusammensetzung eines Varioförderer mit allen möglichen Komponenten + +Es gibt drei Anlagentypen: + +- **Standard**: gerade AS-/ES-Elemente + Staustrecke + Umlenk-/Motorstation + Separatoren, feste horizontale Blöcke. +- **Etage**: wie Standard, aber mit Etagen-Schrägsektion/-Bögen statt gerader AS-/ES-Elemente. +- **Linienzug**: frei aus der Zeichnung gebaute Kette aus AS-/ES-Element, Gefälle-Bogen, Vario-Kurve, horizontalem/geneigtem Förderer-Segment, Separatoren und Umlenk-/Motorstation. + +Der Bauteilablauf folgt dabei grundsätzlich: Startpunkt → optionales AS-Element → beliebige Folge aus Bogen / Gerade / Auf-Ab-Förderer / Horizontal-Förderer (mit Vario-Kurve bzw. horizontalem Zwischenstück) → Kettenende mit optionalem ES-Element. Jede Förderer-Einheit besteht aus Umlenkstation + Motorstation plus Separator(en). +![Variofoerderer](images/variofoerderer.png) +*Abbildung: Varioförderer Standard* + +#### Verfügbare Steigungen + +Zwei getrennte Winkel-Systeme: + +1. **Vertikale Neigung** (Gefälle/Steigung in Fahrtrichtung): aus einer festen Winkel-Tabelle (3, 6, 9, 12, 15, 18, 21, 27, 33, 39, 45, 51 Grad), maximal 51 Grad. +2. **Horizontale Kurven** (Gefälle-Bogen und Vario-Kurve): 30, 60 oder 90 Grad. + +**Wichtige Ausnahme**: AS-/ES-Elemente haben nur **30 und 90 Grad**, explizit **keine** 60-Grad-Variante. Die 30/60/90-Grad-Dreiteilung gilt also für Bögen/Vario-Kurve, nicht für AS-/ES-Elemente. + +#### Einfügepunkt und Koordinatensystem + +Identisch zum Gefällestrecke-Mechanismus: Die Verkettung erfolgt über Ein- und Ausgangskoordinatensysteme jedes Bausteins, das Eingangskoordinatensystem kommt dabei auf den Zielpunkt zu liegen. + +Beim Standard-Typ wird vor dem ersten Dialog der Startpunkt (X/Y) gepickt, die Z-Höhe kommt separat aus dem Dialogfeld "Einfügehöhe". Bei Etage ebenso. Im Linienzug-Modus ist der Startpunkt der erste Punktwahl-Schritt, die Starthöhe ein separates Zahlenfeld mit Default = Z des Punkts. + +#### Höhe und Übergang zu Kreiselkoordinatensystem (Unterkante SP <-> Oberkante LP) + +> **Hinweis**: Wie bei der Gefällestrecke gibt es keine feste Höhenbeziehung zwischen "Unterkante SP" und "Oberkante LP" eines Kreisels. Anschlusshöhen werden über die Parameter Einfügehöhe/Zielhöhe gesteuert – das ist eine reine Konstruktionsfrage. ### 3.2 Funktionen -- Welche Einfügemodi gibt es und wie unterscheiden sich diese? -- Wie baue ich einen VF in einem Puffer mit Gefällestrecken als Umschleusung? -- Wie baue ich einen VF zur Anfahrung einer bestimmten Höhe (Arbeitsplatz)? -- Wie baue ich einen VF als Übergang zwischen zwei verschiedenen Kreiseln (unterschiedliche Höhe und Position x/y/z) -- Wie baue ich einen Förderer zu Ebenenüberquerung zwischen zwei Pufferblöcken, welche übereinander positioniert sind? -- Wie baue ich eine Strecke mit mehreren VF und Gefällestrecken hintereinander zur Überbrückung von großen Entfernungen? +#### Welche Einfügemodi gibt es und wie unterscheiden sich diese? + +Unter *ILS -> Foerderer* stehen folgende Einfügemodi zur Verfügung: + +- **Standard -> Werte manuell**: alle Werte (Längen, Winkel, Höhen) werden über einen Dialog abgefragt. +- **Standard -> aus 3D-Linie**: wie Standard, aber entlang einer vorgezeichneten 3D-Linie. +- **Etage -> Werte manuell** / **Etage -> aus 3D-Linie**: wie Standard, jedoch mit Etagen-Schrägsektion/-Bögen statt gerader AS-/ES-Elemente. +- **Streckengruppe**: frei aus der Zeichnung gebaute Kette, Schritt für Schritt über Dialoge/Konsole abgefragt (AS-Element, Bögen, Vario-Kurven, horizontale/geneigte Abschnitte, ES-Element). + +![Variofoerderer dialog1](images/vf-gui1.png) +*Abbildung: Varioförderer Dialog zur Erzeugung* + +![Variofoerderer dialog2](images/vf-gui2.png) +*Abbildung: Varioförderer Dialog zur Winkelbestimmung* + + +#### Wie baue ich einen VF in einem Puffer mit Gefällestrecken als Umschleusung? +Ich baue eine Gefällestrecke und kann dann mit einem Klick einen Varioförderer daneben bauen. Dazu die Gefällestrecke doppelklicken und dann auf den Knopf "VarioFoerderer daneben bauen" klicken. +![Variofoerderer dialog2](images/gf-vf-kombination.png) +*Abbildung: Erzeugung eines Varioförderers passend zur Gefällestrecke* + +#### Wie baue ich einen VF zur Anfahrung einer bestimmten Höhe (Arbeitsplatz)? + +Das macht man aus einer Streckengruppe aus aus mindestens einer GF und einer VF und den entsprechend nötigen Bögen. Eventuell noch die Verwendung eines horizontalen Anteils beim Varioförderer, um die Zielhöhe zu erreichen. + +#### Wie baue ich einen VF als Übergang zwischen zwei verschiedenen Kreiseln (unterschiedliche Höhe und Position x/y/z) + +Standardfall. Höhenunterschiede und Einfügehöhe einfach in Interface angeben. +![Variofoerderer dialog1](images/vf-gui1.png) +*Abbildung: Varioförderer Dialog zur Erzeugung* + + +#### Wie baue ich einen Förderer zu Ebenenüberquerung zwischen zwei Pufferblöcken, welche übereinander positioniert sind? + +Dafür ist der sogenannte Etagenförderer vorgesehen. +*ILS -> Foerderer-> Etage* + +#### Wie baue ich eine Strecke mit mehreren VF und Gefällestrecken hintereinander zur Überbrückung von großen Entfernungen? + +Dies kann nur Stückweise erfolgen, indem man ausprobiert, welche GF VF Kombination welche Länge erreicht und dabei eine gewünschte Höhe nicht überschreitet. Danach kann man die einzelnen Einheiten zu Einem Mergen. ## 4. Scanner und Separator ### 4.1 Zusammensetzung, Abhängigkeiten, Randbedingungen -- Einfügepunkt und Koordinatensystem -- In welchen Baugruppen sind Scanner und Separatoren bereits eingebaut (Blöcke)? -- Zuordnung der frei platzierten Scanner und Separatoren zu einem Kreisel, Gefällestrecke oder VF +#### Einfügepunkt und Koordinatensystem + +Scanner und Separatoren werden als frei platzierbare Einzel-Blöcke eingefügt. Der Einfügepunkt ist ein frei gewählter Punkt, die Rotation wird im Anschluss interaktiv am Cursor festgelegt. + +Es gibt hier **kein** Koordinatensystem-Verkettungssystem wie bei Gefällestrecke/Varioförderer – der Block wird direkt an seinem Ursprung auf dem gewählten Punkt platziert. + +#### In welchen Baugruppen sind Scanner und Separatoren bereits eingebaut (Blöcke)? + +Innerhalb von **Gefällestrecken-** und **Varioförderer**-Ketten wird intern automatisch ein Separator eingefügt (als Trennsteg zwischen den Staustrecken-Segmenten). Diese verpackten Separatoren werden beim Export erkannt und in den Attributen "Anzahl Separator"/"Anzahl Scanner" der jeweiligen Baugruppe mitgezählt. + +Reine Scanner-Blöcke sind dagegen **nicht** standardmässig in Gefällestrecke/Varioförderer/Kreisel verbaut – sie kommen nur als frei platzierte Einzelblöcke vor. + +#### Zuordnung der frei platzierten Scanner und Separatoren zu einem Kreisel, Gefällestrecke oder VF + +Es gibt **keine feste Verknüpfung** zum Zeitpunkt des Einfügens. Die Zuordnung erfolgt erst **nachträglich beim Export** über die geometrische Nähe: Scanner außerhalb einer Baugruppen-Umgrenzung werden per Abstand der nächstgelegenen Baugruppe zugeordnet; ist der Abstand zu mehreren Kandidaten nahezu gleich, wird das als "strittig" markiert und im Export entsprechend vermerkt. + +Für Separatoren, die bereits in einer Kette verbaut sind, ist die Zuordnung dagegen von vornherein eindeutig. + +**Fazit**: Die Zuordnung frei platzierter Scanner/Separatoren ist rein geometrisch (Abstand), keine feste Verknüpfung. ### 4.2 Funktionen -- Wie füge ich einen Scanner ein? -- Wie füge ich einen Separator ein? +#### Wie füge ich einen Scanner ein? + +Unter *ILS -> Zusatzmodul -> Scanner einfuegen*. Dann Anfangspunkt auswählen und über den zweiten Klick den Winkel festsetzen. + +#### Wie füge ich einen Separator ein? + +Unter *ILS -> Zusatzmodul -> Separator einfuegen*. Dann Anfangspunkt auswählen und über den zweiten Klick den Winkel festsetzen. ## 5. Be- und Entladung ### 5.1 Zusammensetzung, Abhängigkeiten, Randbedingungen -- Einfügepunkt und Koordinatensystem -- Übergang zu Rutschstange (Oberkante LP <-> Oberkante Rutschprofil) +#### Einfügepunkt und Koordinatensystem + +Be- und Entladung werden als frei platzierbare Einzel-Blöcke eingefügt. Wie bei Scanner/Separator ist der Einfügepunkt ein frei gewählter Punkt, die Rotation wird interaktiv am Cursor festgelegt – es gibt keine Koordinatensystem-Verkettung zu einer vorgelagerten Strecke, die Station steht für sich allein in der Zeichnung. + +Bemerkenswert: Der Entladungsblock sitzt intern schräg im Raum, sodass bei der Koordinatenberechnung im Export eine zusätzliche Korrektur nötig ist, damit die Weltkoordinaten nicht verzerrt bzw. gespiegelt werden. + +#### Übergang zu Rutschstange (Oberkante LP <-> Oberkante Rutschprofil) + +> **Hinweis**: Eine feste Höhenbeziehung zwischen "Oberkante LP" und "Oberkante Rutschprofil" ist nicht hinterlegt. Diese Frage ist eine reine Konstruktionsfrage und sollte entsprechend beantwortet werden. ### 5.2 Funktionen -- Wie füge ich eine Beladung oder Entladung ein? -- Wie binde ich einen FLEX Förderer an die Entladung an? +#### Wie füge ich eine Beladung oder Entladung ein? +Unter *ILS -> Zusatzmodul -> BTMT-Beladung* bzw. *BTMT-Entladung*. Dann Anfangspunkt auswählen und über den zweiten Klick den Winkel festsetzen. + +#### Wie binde ich einen FLEX Förderer an die Entladung an? diff --git a/doc/faq.pdf b/doc/faq.pdf new file mode 100644 index 0000000..0596dd4 --- /dev/null +++ b/doc/faq.pdf @@ -0,0 +1,7834 @@ +%PDF-1.4 +%Óëéá +1 0 obj +<> +endobj +3 0 obj +<> +endobj +5 0 obj +<> +endobj +7 0 obj +<> +endobj +8 0 obj +<> +endobj +9 0 obj +<> +endobj +10 0 obj +<> +endobj +11 0 obj +<> +endobj +12 0 obj +<> +endobj +13 0 obj +<> +endobj +14 0 obj +<> +endobj +15 0 obj +<> +endobj +16 0 obj +<> +endobj +17 0 obj +<> +endobj +18 0 obj +<> +endobj +19 0 obj +<> +endobj +20 0 obj +<> +endobj +21 0 obj +<> +endobj +22 0 obj +<> +endobj +23 0 obj +<> +endobj +24 0 obj +<> +endobj +25 0 obj +<> +endobj +26 0 obj +<> +endobj +27 0 obj +<> +endobj +28 0 obj +<> +endobj +29 0 obj +<> +endobj +30 0 obj +<> +endobj +31 0 obj +<> +endobj +32 0 obj +<> +endobj +33 0 obj +<> +endobj +34 0 obj +<> +endobj +35 0 obj +<> +endobj +36 0 obj +<> +endobj +37 0 obj +<> +endobj +38 0 obj +<> +endobj +39 0 obj +<> +endobj +40 0 obj +<> +endobj +41 0 obj +<> +endobj +42 0 obj +<> +endobj +43 0 obj +<> +endobj +44 0 obj +<> +endobj +45 0 obj +<> +endobj +46 0 obj +<> +endobj +47 0 obj +<> +endobj +48 0 obj +<> +endobj +49 0 obj +<> +endobj +50 0 obj +<> stream +xœí\m�· þ¾¿BŸ tLQ”D†�»ó]Z )�ä€~ï¥vPÔ)ì¦ÿ¿ ^©Ù—ñÞø’ ©�î93’ø�"rfvA—ò?Ìõ_&»$››§‡�kýнAô°1‡6,!%4ŸþqøÛÌ�‡�·Xôy¼òM_ùôáùз_™òåÓû믜yÿŸÃ«?Yóúõ«¯ïþüÖ€yóæöíÝÁk[ce†w‡û¯ï¯þúï¿ûéÓŸ~êçÛzþíãáÕdóøî`‹RÖ ŠæñÃá5¦7æñŸ‹ ¸²ÎÇï�È À8 å#€Gºð à€äÓø@T?ïÊyÞæ±ïË2ÿò÷õb] ±I°xóá@qÉP°³Þ¢™ Þ³Iv±È’ÈRH(2ò!¤`žäêÀÈE¦Èr¦´Œ&‰vòOdb ^dù›-WËq¡\ÉPÈÃp>Q†¶AdäC'2Y{¹8”¥E‘æå’!_TY&ÿPdÞ$'WøÅ¦8¥…)ùhõ(´xrXÖ0æ£]ÔËrK +!”cùn‰ÑÙYO·PŠl5 nq6Zdç›DK °[ 8’‡)ð„É\1èÓa:šõ•£mNYß@}6NE[,³ÕUqÊÚö•3ge=*™Û·|eƒ9_ÎTĘˠ¨€e–É1a¶š€¹-s˜Š¹X�¹X°ÚWÄÝÔuÝU¦ºS©µ4ßSëíN:iÖÝYaÐݾáÔwÇgßG +öxÂ<ÜŒ÷Ãá]ŽLK'©Ñ©üçÓûsQÇÍQÇÒu˜æ¨°>¼XÔ9Z½„Ö£pDuåÌÆZÌñˆcÛš"©ÞÍÈ B×¶wÌEØw WÛ¸,®[‹»­¥¶¹Ú;eaß­\=ƒ³¸nl..”\ÖÀÝÛDØ#ÇD›Å5¨pqݲ€ª!‹c[›¯ñÍôqj^£¢æ`ó¤²ÙBÁm,°îJšTéxV»mu�P�i²Žõa´…ð”-]µõÓa>^õ×#Ù¦ÿ˜ÓŽí¥Wg³öœÏlj@V^"�ÒòW¤r}GZÐ8B…9ä¸Ò0ïö�a•%¡™Љ›€Ñ>2.Uþ¤§Q¾7–¤¼t,^y´VTyÿEí“�žÚSéXèûoØ$ž²wëþpøîB,«lëLHóCš0Ör j@õ囹¬rpº˜Ž"R¨[ÁÅ¡ñÆÆwuTŽ—U ÈU…O=¥eéè¶Æc_Ïᢦœ›ã²+-6çkïkLnãp»žƒx$¬!ñ´’´ IÀ—‚¤�—ЧDñŒðLH,hLbØ�‰µÛ 0]JÍËùÏ×ã ˜Xn×upâ Ö5Àæ­F•pÝvu;ÊñPçésøçŽðÄ{¿L—p„ðB^(`åcaãs9G ¬bPˆk%I9Š?!æìì’ +õ"“DÎ{iÎøŠ{¨nP-ãl§Z·…”˦lnða&ïJ_{S©nÚ‡Óp:ݯj5нâ«�ß]©Hèìh#¤çÖ!ݶ •Ž}j4ãžq0hËû=!7B¼X>´ïnF•š#Èjóœ"ÝGQf3×_wU‚:»¸'ãv8vŒçÀ‰JÁÛ/ ÐU€8f]ÚfÝv˜% 3€œª/Ï‚çkOÅœ#àÂ6pòý:ðt�%»'Ⱥí K^¼‡{zAÏÕ¿NÕ¿DëúW$ì,±*€‰JÍ©™Tgn*€E*µ©˜(W{¨ +‘ÉW7À"•‹¬*€EËí„Z̈L–ᦠ+X¤ÉJ£΢”¤¨”ª‰h®€Õá^v©Qz�6Í×K9µ²^ò) zq8éÚªHJ/6xµ(�@îÅ«2ž0›«VÕ5°HDé`ô0¥ójºR¨ÑT‹TTV÷ ˆråçyR3׈–§X¤RMF£�+U§� .õé|‹@¤²Z4ÚhЬ9ÕÀY®œ¡_©ÜFÍ¡¬¯F9b_µrY¥ŸrÚ0µ]´jcuĦânÉ=5°Û®�‰:aòª®J¸E¸S7 T3®§ÐJÀk"º®�)ì!On»>¦+%wõ±Ó„Œx!sÛ„ŒÒ‹×Ç/í¡¤ ›‡=„�¶ ›·¿±º˜4e󸇲Ñ6eóîR]¼†'ðØdˆµ«ß5_ÜìÅÜïuÔîŸ]KÐÎÕgPÊs(X£·Ã•~Gc4C^@Ô÷Æu¦kª~ÒU¿�{â;mWýž�âûKUý¤«~ŸöÄQÚ®úœ-lŸQõ??nîì�N>ÁÛÉ'à¹â÷¹Ý€=À]”×I'¸=QÓo'�@¿h¡»+“_ ¬NG!ìéBúít$w.Ïx nÝÿLÛ6Ó­»Á΢§Œ âd¥ko­)1ëÑžy;æz.‹œšœz®$ÍóK +Ì-k•j?'U…*U�t½84+É3�Ýâ‚C¶ƒÅažs�ó<È5¹Ð“ò½{R†ßÈ… &Ú³)ã—ð=¡Q7Š6…yûSíœ:÷f÷Ëžñë8ÖÙuP�s­)W«Ûº¿«ìºà¢9AÄ=éÍos‚èΦ·_ApÁöü³›fC¯ƒÁgóð/î@ÛA%ú–×ú,}ZÙ�*ßf LyoU©\çršQŸ+Ÿm3ªÍùìÒ#u——ó3…V.Ø]­Æ�u;"'ƒÛY—J�ÑË8*å™…m7cè·õ\]1�fH½>×»Bt9ýœëý½¶aÕû‰4‘õËl£ôT1ªf¦ÈÈË‹+º÷/Ò¤™;Ú£l9·¹UUDb ©õ/Rºõ/²ÀtWd ÉÛ©õ/RiÚ�Ζ¤”¨ôŠÙ¦©ó¯÷fó¤w¥§ÙzÿZ­«÷¹ÕúÇ»>ZÓþRPG¤7Øpíý¢ðü’€²ž°˜«Õm‘ˆÆF�âEaJj2/ú–W*Ô¢¼èËêÉ7–*B(7 ºŽt¢ë/R韓Ѩ‘Œ <áK'ºþ"]wýÙvK’îúg¹ò:êúOs(ßê«Q>ØW­¼•NtýÊý;`j£dhÛss}KuÄcKq·ãž¦Ø(ü cãˆ6Œ¦¿+¯;!.À!ÙÑ4ì�ôÞ 6œ÷Ç¢÷×¼> ŸSf·‡8…íç”™h¥õïî.@Ð¥û=ý…°]Šp/u L.kƒ[R"Kö™>{¦  ‘P…Glf=Åðz‘\6”µ‹�„0wá¡–˜S'RûÆ5vÕó:¶+æN þõB ¥a‡W·)½2Koc—ì’É[˜+qÛgŸÛk¶œ`O7:l³ådá·u/'ê®dÂ= Œ¸Ý•Lÿ/ç‹ÞËIžöÜˉ:KËË ;Ì¿�¥S ë,ýR÷r¢Î†)퉚q;Êë6­ˆn¿fá ôstØÌžf¯¿H f¸8º�xø¹íÛòæzùUY¿Ùãh‘yÕÎç\¸?ÐvÛU½Ž¨“›»ë5ÓíìfÏ>jûŒn‡ïìzt ¦— ×çÃv§ü¨éöÌnʹƜkn¥MÞÆkcª„<ÌùÍá›ÃÇl$¶ zã<ù<ÿS3òíÒÖ)³¹‚I“µ\\"Ëý=ž ”¨>ü��ŠS„\-,L.ªQòMdW?S‰Çùqòú{ùóöâ¨NªPÈ? cÁöGÒ/_#¿d‚6¿Þ¯É9åsãZé`ÄSÓÑâ\$@×£ÌX‡˜ë}äÑD +endstream +endobj +53 0 obj +<> +endobj +54 0 obj +<> +endobj +55 0 obj +<> +endobj +56 0 obj +<> +endobj +57 0 obj +<> +endobj +58 0 obj +<> +endobj +59 0 obj +<> +endobj +60 0 obj +<> +endobj +61 0 obj +<> +endobj +62 0 obj +<> +endobj +63 0 obj +<> +endobj +64 0 obj +<> +endobj +65 0 obj +<> +endobj +66 0 obj +<> +endobj +67 0 obj +<> +endobj +68 0 obj +<> +endobj +69 0 obj +<> +endobj +70 0 obj +<> +endobj +71 0 obj +<> +endobj +72 0 obj +<> +endobj +73 0 obj +<> +endobj +74 0 obj +<> +endobj +75 0 obj +<> +endobj +76 0 obj +<> stream +xœí\[«$Ç ~Ÿ_ÑÏ÷J¥ºÂbØ™sÆâ€ã…¼gÛ„ØÁçÿÕUU}�ïKì…åÌTw«T’ªôéÒ3+ +ñßL_Ìâ«×8 ÁO~¼ü|A4³UfBg&¥ Ì ½ŸH¡�mjúø�Ë_ÿ0ýtùùB3*é¥OåÉ/4ÍÎ0=ˆ×ÿòÕ”>|üþòæ+š¾ÿÏåÍŸþö¯éíÛ7_ßþø2Áôå—×—Û-Mä'3žè»øüˆ¨ÐH_>~yýúvyóçÿôí/ÿûá—J +3©ëûË›»žPOï¿»`Z5NèaBÐvzÿãå-½Ð7퀴ÐÀx1Æ×-€¾æï>�LŸù/�SP!}6/‰†1é»Òùþ�è3-¾fòóúÝm#øâïÀ¸|äÿ®çC]Ü-ÍQÖ×̃i´" —iò÷¼v—ø‡€R_Nïÿyy}¿¢Ç(©DÕ)ÑÂJ¤J´þ3+1*êžÄß­Š0IiZ÷ +ŒŠÏüðseN9G½'óÇB£Û=—×¥…’;e¿f^¼˜Óg¾±(oOŒ²ÙFŠ=V›ÚcçÞ‹µˆzÍþª�»,fЬ¡Ç<?ÁôþïÓ[£ò„7aèJÈî +€üý–ÿFC}ÀHµ4Rý„‘šc#ETŸÁHÙ@YÂQñBSòÄ('P¼_® å±7h#øY;�>Õ¸)[@<ý„¡Ç¹ïÙZ˜Ö« u?a¼H:‹·Ny_ä¸ÌN¬ºf'>Ú·Å¢Ê(¦lÔõÞLï¡ÃÓvv©Ÿñ€î„]š_ÓFÛ´£ÌZ…¡mi'Ä.´EM¼õ»oþ±³ì5?;œ+ª˜A°·l÷Þw®ñRh—3n<O™Lõùg,×ïµð*&ý�V›Y\lÄПŠ<-QøÐ/IÉ#]zìé–gö¬öÍW†¡ÛÂx}¶8ï'Ä g3ýxñnŽS¥!O¨Õăd��ƒfFÊñ ‡`CÔÆ=}`Ö+¯( ;«|$`@¡�÷ڈ☲ÎÙ�ÆøszˆHû4jƒ™¼e:!P´^YŃÚXyÐC0‘€eþ´Â4ÈêÉ^ˆÉ³‡¼ç1†¾!>cf´^»þ={ŒÕ’�ž�&UÖQçÔ³"›…Vù£9XkµîVB³#¥ûEÓ¬ƒó(¥C3¡Ã$�*Fš1 §,éD²*G­©‘Šž?\†xùvêH/_9)ñê!¤I{Ä«OìÕe(^¼¬ø3úô|ŽâÇl”x£â @õ"WÌ +Ø$ò¦Ål“™:=ªªb•T\M ÚN3’ú´4(1“4¾Ê•4ÓÊ¿´h±ViüU.r›TÊ-%„-·_U‹[S oþáòíŽÊá܆/ +û¾`b—�BtXNù‚/òi;FD D™�¯ÉÀÝÍ(y*ŸpÆXãQ«ÂgP"¤šE Ô ÔyYÅ©Q@s’Q,eÙN8 â)î-øŒæšœb=ñoùïk6–y0ƒÓ+Ñ §ønÍ›<"ò.zTdŸù‰ðQé•ôí!;ÞwÍšê²^NqÇkF?8ŽgtÐ(�Õ%Šp½"ÍMDø<�°‹l”y*‡r"´Q¶@Huð‡W4"áå;fdÌÝ…žþ<ÇÂ×â×r,kk*.áÍÉì‚åŸÉ¬à‰è@ùšZ)Gаà8F± X˜÷‘a ©ÆÓFu8ÿ3«ZFÈ‹HŽÓ+§"TÂâÜ>54�‰Ž"NaÐ#Vä¥S#õ”S;ðëìÔ|N­dCÃòTí‚ëG�‡êòǤŸqêD™ŒÞ +ŸòPîñÀàås�%f;ßJö× NøVr›Ù…gÅs°¿ÓV0«´ˆf)¨1šå¡E4K�–Ñ,®D³<¼ˆf)èE4ËcËh–G (ð2šåAë•Ó2øàÁ•h–‡‡h6‰h–‚é£YqC‹—¡Zus¶0Lð×6±’ÜÉE×0°I§…‹BŒ%°ìÞBP¡µ¦F*zî¢YB뉦ŽŽÑ,ñê±�fy˜W¯'¹ XF³<¸Íòð"šåÁE4˃CÍòð"š¥UÅÐE³ñŠ4XF³ÝLÒø`Í +þ¥EÃZ4+ä"·I ÜRBØrûá2š +ôMÃÏD³ês8ËÕŒìö| gUöì„ÌÒe�ÁVZ�œ÷Vzz�ê™óþ/²§× +†%ÿæÂW奋Õô ¸RàŠ]¬Öêÿ+|¥Âió „£N[½•€ß*ëúfO%Ô“U°ºtÝÛQ I ó¼ +¨çDýôž*füïéÈP�¤±É#µBA,ä@ä¡*©îxqÏÀI:€“ñxñv<^>W A]ÅÀS= '’(6›>)KG³îD¶ÃÉcôdB]ègœ�Hb‚½§È+}Jù�7)‹Œ®çKòe£Ö„‰àé´øõIѯÍ÷BSØ M±Ì¯¢E†¢x�2)óoÍ6:1;{6øaV‹XfuÉá¬�GZYëxÅϤ¼wÊ5•òúf¶‘ж'B¾ÀÉdDS/@ê=ѳö-6_§Ó!È\›©TÛv³±h‰'®ñB˜•Õ`½=žC™üy +N© ôsdHfKÁ£ŽE²IÊP!¥ÈY}8®}fÖX°Ö‹Z?>*ýšI!9  V¡2)ÐŽ™ÚTß&·›¶óðúÚ~ÜØv�¬¸´+'–@XìŒY¶[ÕS¤¤BJK 'ÍŠ“¨)×—ü¹dãÝàH2õ‘Ökö·]:·œ˜cTZë©wÝ6¢›¦Ò¹¶|iwÍ2Àý5×SÜ÷r¨a„)gé] #"¼«ZÞÑ¢ Y‹•3+¸}]çŽÒiå ïf«½0Å,–’>h3Šd¥…£8³*f›²gš³*È¯Š¢¨:ô©è1+Ï p·&î¦æ£ 0‚òm‡àZÒüw‡ð›s�¯{K±ú¾¡?wÛò-I±vÚ‚Öá³z%7§à–‡‰k�êøøÒûQ•Á9#é×cz-.8:¢E]±�{kÔ|í³5˜ËŽ@+èßs`"&§–ÖºUŽ‘> ˆ<ý†QZÂlŒS—¹5Ú)®i_Ø\øæ2 +aÏ&NÚš{/<.ÛO€PnþŠ0íwÜ:$W;PÏB@m×%â݉I¶EµÅï&Ôݼ°¹{ì§×Ù ×ííŒp7õúé`z?ÙQëD+V÷~“;ã"�8°ûpé¡gÔÝ8Ñ/ÕÑ{P-$ó}U£lNh©V—7ÍŽ]Ö V2rîûRÁ26Ý´òAϽu%yÂÊ»ö]ó‘i²�Û®îßvÈFYIƒÈĦÖȤÞVÝ™ÔáÚ™Ôà:™ÔßÚ™ÔÞª:·�»[{ “z[;“:[mç�rcë€cR_«îpLnk-ÎÏûÇÔëÂ&:=ŽI3v8¦2&¼vá_¸w±Î†ª@\H‚óV$›Æ$UPcªÊ¨¨´‡1©“¹‡1¹åYΗ<À˜ÔFÝÁ˜ÔnÝØܙ=À˜ÔÃíŒI�Þ½l#j@1¹y¼C1žª"i@1|IÚ-QŒ˜GÚWfHaáYZk[�´kZ‚˜*.¹SôˆI +èALU”oŠÜ1æ �¾€¢9)–hf�žLiŠaæ¦V7ƒ1¶Kú”ßÇi!e-eÿ„ù<£„•¼ŸÑt™˜¾X Odg‹¤< +–Ž×~ÁN)šy)…‚¸”ø]ž³ÑýVIãY�‰¬ë…Ù¤ $~ë¾âvW}.#4þQGZoû®ÒJyíÇ“¯>ô¹ +°tÅ-€§Ù_N(……Åd ôì«–RI„†%c»ð&,xTeiHÕ¯u« µþîˆéK|‹BèJ²yòo.ß\~Ž}‹J{œ•™È€å¿Û?-ÇŸö~MŽ÷UÔ �B§@r³óLAÉidV'ûŒŠp�ŒDÂìc†­Ráæ`¤ü7䂟Îc�ÿ^w©ûEˆqBÝÕfÿ™€³Vž¡>Ó¬ƒgýœÊþb:�³ÀDNƒ¢º‰PKuý=Ím× +endstream +endobj +79 0 obj +<> stream +xœ}�½JÃ…¿Ô‚(Šƒ28¸h“¦iRpi"×V¡©S’¦AìOHSôtspu+.¾€èc(âà#ˆ ³¤AR�xàÂÇáÀ½÷@®�— ?ˆÂFÝ[V[œG@`*ÛdK€ï—$û¼õO.K oä@¶¬6`ÍOø,f'á˘O£ asxÐ0A¸6ývfØ Â8ÿìô{c7½›%opØZÀ:u† ñéáQ¤É ÇØÑ0QÙ£F …*åéÔ�(£SÁÀÀÄDGACAa•jÜg²rxúÌ]¥žs PxM½� ¬œÃýcê¥vhO­<�ëváó–-X}‚Å£ßb3~ÿü*²Ï—mDJHÈT~…ÍK½ +endstream +endobj +78 0 obj +<> stream +xœí�»Ž+ǵ†_CÎ+ì`gHÁŠxA‚8ÚŽ ö p0‘eè lÓ� =¹·Bëì`ȾÕeեɮ^ìîoðacv±XÝì5ü¹ºXµþ�~÷?µ|ñÅoî~?”Oþ÷3«åáùðZmœnχÃó}ý¯Ùbþbÿ¾©^O�ošß϶�nª×—m?x¤§3fä$¯Õ®}è¸0ž¸­^ë÷Ð�ûCûßmõ꿜ùà±áó/w_ª}n æ£" +lÿT» ¸Ö·~ç)u{Ó9Ü3[�ßÜí^š1«—ƒ­Àµ>w²lº±íhKQàO¿Úu?oÿô±ú,’ñfAP‚  + @ "€£(0\90D-P` ˆZ À@´@�� hq +ÜmF;æî&›ç¾³[P¡˜Ÿw +gèd_ÕÁÚt|ÚäÛ×jp@�ÇÆ âÇoëÉÔ…(H)>‰äñд ó¸?´}òu9ˆüó[ $…øþéxxz4«–‰Õr6UW¾ç¸ßz…w ïzžíQÎݼZm�ÄN�ÿð§¿R¥ e¸ÍlC5ÊúßýX*ùØýr·Û× +üÚô9 ¯“›å%_­:–0$ˆR|jùëÃõO`Qà˜À¦:X3 »—ZK{–†rS\IÿahåYˆ�Þ¢À£QB�Í gN ­À–Øvùp_øô_W¥6Ž‹_Ä7¶(0ÀhP`CT¥o’9pŸÓ¶9p;x3ç æÀ''‹ú‰äÀ×ñL§ÀŸ}Ý.Š “"–îµ*ÚMÏÖ_« ™¾ªš>Ûªwܸk‡:¯X‹Ù ÁÐ Ö|öõîíŸÿP'À_}þ†y`€±]�ýE¼ÝŠˆn–àå):Qì[ÿl»5­ \?ÇaNGm†`H?ÿÒ�ph[Þ~ý%90ÀxÜÀž8Ї ¨€AЂ  + @ \Š´@�´@�´@�´@�´˜‡óãû×××?~¥}¹F$®Àÿøç¿ÞI?ÿøç¿¢ +üýOÍžá_¾5Ú¿ýùÔôÓw±“¯þþëé៿¿@�ã#Üq~÷îÝþû_ŸwïÞÅø»_º² q½ÍÕI–H >«ß¯?|w–ÍŸ¿¯›„¶ÿùå¯åÛZºþþ¬áÆþå§M×÷ÿË'®2ÿå‡çã~#ëÛ®sýÓž€"ø<qRÅ^ë‡Ò9p-¹~}ïr#žõPưFþ¦ÿ8¨õ¹Ñp=’ +üÛoÿþôÿþfòÛoÿŽ(°©oŽÖe+p;ÛàæÀM{?l¶[ÿí†Õ¾ø°rrØ'¬ÀvÞk+g®wÚ8¶[?¬¬m’ +üAú *°9×Úÿf&W`¾˜€›b\ö§X#“BK–×KÝêunþï�»#;«)¾ùñ=³ Í¨ë�ݯÞ>é³âZ$ëæRa»%®À^Rm/±øõ½utïXÎ (0h3�=qKÐÐÐЧN ˆZ À@´@�� h�AЂ Åø +¼­Ú}¿Õæáù`ì~Ùê¿^‘x÷ÒÅqpc×âô‹"9ðIxÏï»î—æ÷×ÃÓ£úK/ˆ�ûCû‰¹­^«Ý°Æ‡ç—6Ð÷OÇ×Ãó½ö ¸M¦Sà&=&#ºE¼ î^¬�Îú÷üF±ÎLªÀ§wëqÿ ÿªÁÁ ¢{ó"ÞÑ„ È�"L«ÀÆ+Ü1î>7óû¡NÂD 90Ë�OòËÌ?@æ�¡Ì<0Ù/@š‰×Bð–¼MF^ �üäÁz`=pÿ7@èb°'"€(0D-P` ˆZ À@´À¥@ r` ˆZ À@´@�� h�AÐb® +üð| æáxLDÂpf|w'rè­Ê[xT¢Õ)O•²jüæ¥é‰™¬�2 +lÖ(óÎB�G%ÄÍ)†»‘�X‡3#X=eØO¥N?M‰àÓ›®Ï€vbCi»‡Îùع}_ �ÅÆû;ûw±ç�c1¹¢RÆ¡ ZÂØ_´:¦Á`mÚÂT°„5R~¢s3Œ»Q»„»Û§�0toQK(°%(pµql†œ‹ +–1_tÕXÏîRcf+¥¬÷3 ^{¦¿?½ ôE�M‘Ï”e¸w9i³Ó`,¥ŸXˆ÷$û…U3Ql‹í¦:wpoW¥>fZåÌPàþb“æÏZ‰ÉüW3‰`mžªæºµ¦Eá°"¿°v +Ï›"f,fhߘáµm‹¸¢Ú P`cÑÅËS¢go¯s8®ê«ùè�L©³ç”÷ª,Ó¦*·§ó­ÀZ˜ëž¸Ò¬l½ñ2ƒpó À&FZ¾¢xaA˜(0D-P` ˆZàR 90D-P` ˆZ À@´@�� h�AТ˜{Ö6bÕJ·pV»ÂÜÙzîcUè2ŠÆ°¡uä ö{´ûºÒŽ•®=Ù¥ø´«÷h·‰»‰ÕÏz¥=žïÆ4vHëÀ¢P2]6ê ìw¥x�àRH�‡§G«ød¼þyB�OYq3”¨À++&9:^åj¢R¼Œž‰Fp)£À�ûƒWˆÛ)x˜´ s•öq°…7> \ÄSáÐcïËç•Oï/¸h ô +‹" +l•”4í0œZ”öS +|’…“ˆóÀÜç^‡ ÀÖý…3ÍîR–Lˆ¸=È „[&È]%Úïvä(p�{í$À×#åÀr1v×Tˆà + +(°‘÷ÊnA¦%±Ñ-­Àç4¸ª$eÆhì*¢9°©Àb>Ì<0À…ñÈpmp½;Sß(S�·1ÑâV÷r¤oâÌ›—úÚŠ¦B¬…¸œÑØpŸoh2([c{÷±G�ÝõÀÎC¾2{ÂA<Ó¯þíµT6b=0À…°'"€(0D-P` ˆZ À@´À¥@ r` ˆZ À@´@�� h�AЂ E‘ºv¹ÈMeoM56·Ú%¶ÒVDžEŽç…tÆ*ÎVŸ‰?Nµs·BwF»ç™;ŽØY;âYA À˜”Q`ÃT¨y/¥Ôí³fùÁŠÈÖ“ß É+±©„"ò˜NÉ÷àyf�3Ÿ²`~%C"™2 +\ퟎ]9—¾ž¤Y2ë®yÔ*³ã[ÙÃ:j&x!Éõ»†*pê<3ÇÉWàö¢uo6M>QÁ·Ø,Ä|>GfG!Þ´5*7Õq¿�(’S»Ò³"r†µž›ðBÎ*ÞSNÑýá‚q˜“0çÙ�¾Ù$ùgD�É�ÊQJ�yܽžïÊvV›�‘;¬Ýâ{!õ’UW6‹ {•…Iæœó4Ž×Y¼ŒÎ‡ˆø{I +¸§€1(¦Àç)‚ÃÑsvKæ–¶‘7l×÷BrŸuû9ð-*°mHcSN�{o…¡ó«ÖÔ±7lCÀ ÉÉÙ.S`•yà›S`²_€âT`©%o��aE6ä…äâBž|-ÄÍ)0ò 0+ð›Ø:[ÉŠ(0HÐ ©{T˜�È^Ç›8Ïìqò×ßš ‰`LØ@ xRä-~ú.Ï@"€(0D-p)Ђ"€(0D-P` ˆZ À@´@�� hQ|WòŠ­fD¶KQ×~Y#XL©Àk³þ™y.EFɸmÕ~ðå7€Ë„ +bÌ*…«¹Ô�ûƒ-¼îÓÍÏÁì�Ř ]ŠBSy]1É�ß�wƒŸ³m-�ÄûâÎä·}{Šgb�' +¸ò‘«¼ÔçnuT`£˜p~È"ƒÌ‡b³²ÁMxjÂÁn<Ýff[�9„ïœ{a!F�Eº+¾JñKý¦Nƒ3rà!#†EPð›¸sê⥵§\%f�›Rˆ¦»¡û⮃{÷jœIâÐË'ºÍK/…ü3~©ÏipU¥æ�‡„Œy`XE×Bô¶DîÄBÞ7qÝ—8ÉgÅî‹k¤opº1#ã¬ß#C¸ò¡«”s©ë5f�e ÞDFÈBƒÌŠÒ.E§i¯Æè™F™Óë�;§›×þF8¸ØjÚ?�iÞæëuñL'¿ÄoâÜ+¸J÷y—Ú^HæMþg�#þñ ¿0SØ@ "€(0D-P` ˆZàR 90D-P` ˆZ À@´@�� h�AТ˜çyÍhºá@2ˆRÅ›nÛx‚n ³³9°©ZJ)p®=�žäÑÖU©¤§Q9m[¦TfÁÉ5–ÚÈ¡�'¼fâ>D%Ýp ĺ4ÙîÅrò/ ÑhòíkìP; HÎðšiÞ¡Zn8b'›†Àžœ§ŽÝÄŽP§7¬ÀÜ}ˆQà¤×L`x:7ˆѸŰØÊ‡ý‚ùígœÕRB�Sž5RÑïiÝp ĺšºùÓêª1 ,ïu‹óó5(@‚ +œáY#§£“¹á@2ˆ¡¸Ë“…yàà`SÄ##ßkFÅ ’A´.²5ó`ÜwÔÁ’ÖBô0ctŽzÍØwµw:n8� ¢�“Áöñ2®§¿¸ŸÇà²D`OD-P` ˆZ À@´@�� h�K€äÀ@´@�� h�AЂ  + @‹‰]Šü‚íN­óÞÖ]t»RDN¥w“S¤jg7ú5Šó ˜2:»‡èÍ}œkUv{µDs·8uuJ1­K‘ä%da™%¬Ž.Q`É)ä…dZt$4Ë€)ÐY:Ä›î@Á28�ñGÂ/¯ôÒê¿äLã0µK‘ï%d`õOZ] À)±•½Aѳ²ª �ƒÇõŠsZ%†äñÇ"6 ‘™Ü¥Èójñr­„ÕÑ›Ks`W ‡)p†Sÿ*¤Î¡ã +§!”ß,”‘F˜ ÓºI^BÝSüâÀ)«£~–ò¢yà&áôÆtÌw6•15�<+³~¦ØÙ:“¤w'#�?Ñê”T˜(…†K‘í%t'[f¤­Ž.û&®£3E +æÀݱû 0~VNº(wöO^üï��—KG +õQ€«Ñq)2½„œ$3�+¸“ýüÉáôY™–�ÎÁCäÌ—²œ¸Õ“ý”EÇ¥Èô¿ÜÏd¸‹¦Hƒ¾žË7` +u"k-„hrz5âj4ä 4Óº ^B›êÕý©v¹ƒX +,-¯u‘L‘‚ë�ÝA2ϪÒ}¨³ò&ÁõÀîøã.NpxëF5({†h±L–÷��ó¾n1ˆ+`™ + !ˆ* À@´À¥@ r` ˆZ À@´@�� h�AЂ .Eu»yÆÞáÜWo—¶<ßÖ†ßPežèo¸\Š„’8féõÌWk�–ý)ã:tu�BmÛªPQbÀ¥(âû3àUÄÛc§QÄuèê î^&M0"¸…Ÿ;äU$Ú:â�x %FQ`€òàRtºË5pЫˆµ{eË»]ÄÛËÒ .E¡™Øa¯"ÝnPÞuèÚ ’L.E�oà«Èi÷O»˜ëÐÕAd` +p)’×B }YíÞisº6ˆ¬…˜\ŠúÓ6¦j‡¾Šh»¸¸¬ëЕA´¯ 0@)Ø@‹e*0.E¹Å ¬€e*0 „ ¨€AЗ"-È�� h�AЂ  + @ "€ër)ŠÙÉ'lîM>o ö÷›Ä Èá +ó7»’TX—KQÄ(yÂ�"¹‡ë+L‹ð$_µ>TæPaÅ.EVQÊбöOÇNˆäš“9¥€£¯ú :%€ +ër) +Ú…�Õ¶lªã~+.«{ðUßThPa].E!{ Ø ×-mÑ`ïp}yù !QøUß ¸¨°.—¢€=PüX§ ŠÃ1èþÊÏÝFÿUßäÀ*¬Ë¥H¶J«ËÏóÜß‚�ö«¾)˜Pa].E¢=Pæ±r§|#�Æ«¾1X  Âº\Š${ }æ±²X\,¹Ý¬P�=q@´X¦ãR4�[ "À +X¦Ã@"€ +(0D-p)Ђ"€(0D-P` ˆZ À@´@�� hQ®B»µ97jâc•sL”ßñF+•É;2"[•½a·Uÿ¬AþJ9{“ãH5ý 8‡ö.×õ‹‚ +lÔ瀫NÙ¿s7U^5›®fÂ0=±Œ�šc‰ã‡õ±k£åú+eí‰X ¹ðô�PœÓü\( Àm½  ØözëHKÓEÐâ"3šgû+ Pà€’_-9ôÙtágVN»ˆ À)R�2.h�Æ åeFlºËe�Ì N9 YõÞ‡(pÚ©�bÁÌ‘Šv¢ÀJŒ¯ÀÛêµwü1îdƒ&>ö¤î�y`cÞØ›‡‰ këXŽ¿RðeŠÓ# ¤–ÐG›-£Àó¤xœ7‡K#óõätWî™Í�–çù+ Ê�ƒHäÀ« øôÓÏÊú£J P`áYýÕØíO‘/\Š’�ë?Ñ^¨Ð>OâóÀík÷Ê‹’bew�Ä™Ï5nvEÊîé4öùmêé~‡ä³rþ¼ÆÔë5?ñX2¸¥; ç³´[ÂØZˆîªŠ5�}I±ÔÃt²3¢ºšßÓ’zñcÚ{ºuzæÄWôY¹áygž|½ÆlÏùã^,¸¥;‡ÎgADW£ü@c’O2£OÏï™Î�ãrz�¿ðœC›óW‘×ÛÒf&(ðbÁ¥(Ýy… +,_ÕsbæÌ Ê–š6—ÇÄçEã=í§t'–xº©ŸÆLêYò_xΙ‡‹\™û§ªù3ÞVõëÚT¡ïþ`ÞàR”î¼B^Uc-D¾Ð-vÛɸPösc©i^ÏàtPôé¦Ôáè9†Ÿ•ü Ϥ{ÖËSôõš&YÎ%]ÊÄÔ°'ÖDvÕ�6+R`\ŠÂÌ&ˆc`˜�.í^fÇŠÂDP` ˆZàR 90D-P` ˆZ À@´@�� h�AТLm´þÇ.`âµglUÕžê+Ø«ëÝí¢ùžG¯míp»Z‚³ŸÚÞX:+2 ÜY/ÓÙ{Û_tv™äR¶:¥©Zf{È�È!TÛ§Án·ícv/�­[´ªC¼ƒuôãá�<èž5ã“[bÔ¢+q“giÅëËb0$G�««õîvyÀTqž«ød–á} Dh´Ìîà�ûƒ}¬EèÏP¶-J£FQ¦¼K. –!‘õ#ù)H>G®[Ù¹ƒëí"­Ç2L�ÛÚ¹ÂAÏš91L�å:Ìöü<d0}3$= t=âM1,ž×Jk|a·ˆêÁCØ3"1ñ�¢ ŒÆq¦gPŽÇ�= ‡BFHÚWxˆ«Ñ�_€ÒLçÔy—ç14@�öF–I +â)jÞ4»öI,öÄAЂ  + @‹ÿÆÐ5° +endstream +endobj +81 0 obj +<> stream +xœ­\[�d7~Ÿ_qž‘âuUùR–V‘vz¦#$‚¬Ï„$€H¢,ÉÿG¾—/§»' Òj:>¾ÔÍŸ?—mRHÿ;ô¡�/”øO6 „ÀÇ7?<ýü`•C{€·¢ÕJ惜r!àñéÛ§¿þîøñéç'R€6õ—Õ–_8t¨lìN§ÏþêÈ?>}ÿôî+:¾ÿïÓ»?þôã_~ùôë7¿ïß¿ûúòû—C_~ùüryzþøôîj0ÇÇïž Ë êµwÇÇžÞk�Fk Z¯µa­­Í5•o¾ü‹å­�ÕÚr.«õô³ÖXÚÙ—ü7ÕuZ›çÜjÇù µ¾–þŸµö—\'õuíã¦q\GÈ¿›:¾Ó@k})对¿4”zP¾ùšž�ë·ßW­-æ1c}«û¸QöÔöšÿ¥þL×'õQË\—;õóAØUŒ]uMu±÷m[�/��ÿ~zýxË‹�‹�鯑RÏPÊ‹bYtV²ø‡2¢ðRµF,«Ò3¬ý&-.Ýû6ËYa WúzÞ@îýıbq� DÛ«GFƒï2¶~jûWñÍdO>Í©~‘ß]ÖYñ¨G�ïãfýî{PWONÖÅ:–yÒ€{ì§:P¬+6-m·l³BôB‹¹?Eø2Ž•·EL�€Œ3<«obÒë×—§w×}ÿë§o(A¥wó|¼ü”†øÜKc¬Ø‡7±Ï} quÚhª +ŸôF²7Ökw¤ØPÄ÷ÞiE/S=tÍ^•/5&“Þ+O`L´ÅÇïe ¦ +`”&>øVc ßT IYGÆ‹JÜÓ\Ä<,(‡¯mËÊ . �se¬bBM@c�k«qnG3yÅ)б¨y™ V7;f¥”ñ >Á²ÕX2{ìêVU” ®·å 9€nçF6àÀ󦤂36ÑUÖÕ+ëÀ"‰ÏéCPèŒvìî��¶´ ¦àP�OÆ0ÊGÁ[‹÷MrÚ•¥Ú’wæn¹á"¬uÚ9în*KØ bu‹ö~@Úì§‚qÚ1º/Š[ãÑ> ï©©ÎA¼÷VƽÅô[ckÀ¿UtðÎ:šÃá|wôˆT”­ `Z_t�¹fØ‘çák¶ƒVDZ{á,è2.óm¡2‚Ôo/�$‹n]ÚJqí”$ u‰P!–f¹(úNÊýA±p~�h +±»Š²¢Ÿîtj'çLkìs¶E¥D�Þ\‹,…âHy+•ØÉ\eë{N#¬ó <‹uI½×Nž�¶¢FšÐ,)™ôà‡‘0ÊíC³È¥SIåë6¡ÑsÓ‰X£:EÖJe$‘�´Fx¹ÓõæMY×÷:µï™K4•m‘ûa�ÆØg›J-Zð‡ê©G¤ÛPsIðÆ8É\è/�cHº5se#f'r8Ĺßl\l[nЪNÕ“æ:E‘½=gêÆ2�1µYHrñZråzië¦�çeÔÛLsrˆH¡{'Æ'ìGFXî+ò2¯bßr‘ wg@~˜P9¯ÛÀeác4Æø¾ŒõHYÃÁê¾Tž¶8]Áé¥RrèsðstÎN¿¼�¤œËuÚÕã°/…2 ÎzxÀ(gR�ò<}=ñá9ûÌö­¬eIp++�yÞ¿u—3G½Y‹sÖ}Úâµ ä5…šÇæ�1~c” +ý wEÕê>€C·ŸÖ›Œë…©ÌÞ­>Ð}톎¼¶Xð̃ûdWŸ9©îxéLÃ}–îÛ¶”^:Q®ëkKÌ€`~ä¸iýÙmÅ­\ +ò”ôA‘Ñ,#p^šCÉ+ÖÁŠ=¿é´¥tHZaÚ» »——$_9±ÜO({³å3|o™ëü¡‹„ðÞ'�-Sú17nöð>6J¦ÌFó^¸0ØÖ{B¶ms‚÷QT<¤³¼¯~ô>û±ùÙ§˜i‘ÐZ‹˜ãˆèjõ lR‹hú‰°n–áß &&J·¬˜QÍ~ã(îŽüçÓw§¨t;Õë"*qMw,¨«ÈþÝ^@ƒbRþ·ó¿·…Ïeãß�Ý…ÀÞnpýž(su’$¦êƒc–‚´£9Ó÷õòØ ±M+I r” /çHj’²†IcÑŒ¤¬qAÒXfbºk@ÒX:#)kš‘4-H #é¡‹£““"–E!F �¥Q\‰¤¬M™|ñ×¢éSŸº½uŸärœ‚]œMêŽ,R»AÍ +¨º±*žI£vÜëæÇ�›¨ºqÀÏX4ã'k=ág,Yð3"e°¬øBT˜ü a‡Ÿ!LøŠŸ!DpÂÏ¢¨vÀÏ*~fø? ül­~Šq~6‰:~6©~ +ý~6KÈ ¯“ÓC¯øÙà7ŽâîÈ[øy;kñ“µÿ,üô�ãgŠ\NâìðÓ(ïÀÃéÕ YæŒÉ  F‘™$©%jçû°Ih¾4ƒÍx`7�&Ò +šh˜†¨@³MŒB;€&Ú4Ñn@íB?cÙL?cë@j¢�ég*êô3¢Ýˆœí»€ÏÚ�@O1^EÏ&–@Ï&¾€O¡g‡Ïj�SÍl ΄yì5GàÆaT:Â'RÔøDœáqŸˆ3|2BÔF-!ž‡>c© žŽn4ˆ`åGÛ†~ÆÒ™~2Bó#Œð™>ÉH€…~ÊqdtU‰DV©e´výdXWKÈðÇ>»eåŒÂ>›£¸;ò|âíMq‚O¬›â”Ç%w¡á6t�Ý“/ F äxœo)AKsE—i¦!GÚ¯B5¬o'z÷¾ÍÆ!¸t³C=$øÅ±`Oš l稊$Q5Å鈪9\GT�˜Q5Ç쌪9fGTM1›éi Ôü›FXM�&XÍ��°šÅ˜võœ¦W³b W O¸Ú¾‹)Ýú“_ŒX1¢ &€¤) GhÚ±©™D@X3]C:ab�ˆÍ¸qU§ŽÈšu‘5ë,‘5kòMêP/³šúbéöåÏè…2×=¼³=£\/CÆ|Õ5Od «UÒw+.£�ð^•T¼Yb‘½Nÿ.¹M«ë…¥žEt¸~ábëѹݦm={oa¥»×O‚%qñY^Ÿ¶¡kžâXøq¸ÛÎEÃ×îã»z²l¼ÄU”í•ñ"m=)cTS¿Ä½ëSfö†‹×o˜ßÏåºpÊÄ=�WwJjÑVÚÊ;ÿûÔâzoÛßp±·bË¿Ü"Þ’ÃÊ× ½{£—ÌÝKa¤Áî.=…rùX¼ mC—ÛŠT›$#Ý]¹//.ŒW>nøÅõ8¬‘_~¡rD~¸¼7>o(ô•ŒÒë+¯v uÉ0¨È0Üøà«\²½a:{o¾’¶nbõ5IAíÆš%D£Ægnñ¸�:�6ÏâIê¥$€æã*îƒ_{,¦ ÁüâAÜUÌÍë½´úæc¹Ë.çÃë4‰7€0,F›¹·•ÛôùW—ùjãÿsSDkx½;�)bÈþ©ë›�Yoyî2Âì­çäióuo%^æ¤eþ2!žÝ½CÚE�¬}8¤Í¼¢ž<Ä^]W0?MºÁòèógWV¼ ŽOw£GÑÄû}ö «]ü{þhÝ{Ì›BÆa|»æ•gHG5jBQ²<£H4b|b=wâA×gºµ°1Òß�gFz˜Aåwº€p³Wb§ò³:]s±àn·   ³l“Ð eÞä¤-«”‘Ùr�œÈ�-ÁHwýþžl +endstream +endobj +83 0 obj +<> +endobj +84 0 obj +<> +endobj +85 0 obj +<> stream +xœÍZY«\7~ï_qž¢[R•60†^ÃÒ*êÃôñÓéËIk«œ±“öv2Æ‚ +aB£�r1šéëO§ÿ6ývúrB¥�ÍôÊ;Þù]¤ •±‰ž¢¦öß?¿Ÿ†'_9½|púå÷ÓËå×Ïÿûá�Ïß~šÞ½{ùáú÷ÛÓû÷—Ûõdì„èíä\œœKBü|‚I‘4©LW|*4)Ñä}VìJyCúËk3ûû×ÓË?>ÿö¯o_ÿøøm–@W .¯§—Mš¦×ŸOº¨QO&Ôà¦×O§wˆ6X @wë´~?½þçtÝb`Ž�© Œ @W €32ŠÒIazý÷ô®,³i¹­Ë“L¾>×e«½Øs#É{Òwæ^÷3;Wß?Ê÷ü,�1öý~tY,­²!šKˆÊÂôÞ®(-LÏ.åûÄ43¼Õ× ¦îZi¹&pæSMŸóá5@Öø”uéy–Ó6%‘m<æóø*›© +¾V%‡¦ü^fÆŽå±å•M�½Q芎´‘Öek°Vô±ô½iœì°^heÔí`´J{Bƒ�Ùaˆ ÏÙ\Þóžª�,”ïq<ì¶!ÔYøÒ}ð‡?cÖÀƒ„‡�Ò&dà“«æX1›‘iÍ^›ö\êÚÀѨÒ^ $&>¶hcí�·› 4BzeK< ×gäŸx%:%2xmÒß’ÿ±… „j¡„dùd—êH[Îrg7-ÒµÇkkÓÒV|¾ÔS1¯su®£Ü‚2·X³zRŠb.þZ¸±žóéÍs’mó© ʃW€V['ܳ|¡�œóÑ·|ëy¾8Ôf”#c}X&´»pTϹ]Ô¤»Åp=Ðf^fåøý³ÉV”e9<Øv¬ ;} …·{ͽ¤LÊqú˜gDLcO£!°G¹·yåŒÍs£Ãô³õCKÛŒ +=–çDƒ-ÍK9gÙVQUÎ)±¢�ÄÆ Ês Çž�Z³I‹º½ò>šèb¯îšfUy¡škÍe²Rq}ëëA)•M0¸zqï!í\­òÙ3·n²Jsmå{L·ðn* �lÎ¥öÈk•ÆcÐßcHצÇlÊ»HÙ\gñ~N–O˜‘Óí¹·Dž_×LÖ†iÚÌëÏU[¢œ‹lj´Ç"ØE­…°,‚¹*§f^YÁtÅ(ç´(æZ’.Ö.W´K•XTd¢ a«ÑV4€û�:W…šÃ}+ÜÙBË®‡+”Á)4N{áÐòQßm)æàà +ƒbº4pT¯Ġ¹PŠožÏ}•HÖm Õmh0ñµ*h­È] Gv +ñVaî@Õûqˆ+o ÎmdÓ5Lºi€›·9‹Ë‘εԇÇ�7û�OÓ$r!U»Mâ¿èëVÔ*¯ieæ·z‰u¤ÜÅ<£|](ätž ˜;¡‘ÍÈkäì›Ö ÄnTc`çÂVˆKEì€5ò SÞqÌuškÃ9ˆ™gyú‰’BÓqær·2—ŸQG¨ÒèØ ÉGºòR�ŠýEäšÝS˜¶‡9Ì`7£ Å¡9tC}‰Íg2zž9«¯ëì•ýmånaìñW'@GsŠÍkMâGçzÞËÊ}™¸‹ãF×òùM ¤Z¬“twïHŽ½Âƒ‘·<1e¯—Ís¡íÛ|‹cD�ZhëÌòê�ã8÷Þyóõ-9`íá¨n?/òõfU¨á/–ö§�…¦h£1½ú£ÕØ =­#åó %H¸U4+ã6ì?×ûko”§ä{=H¼ºÿ‡�>�}ÃMDÓø‘ÎÂ>¤0²'ê2£Â|×â߀™wðàP�äýŸ„Ã~k¢É)�efØŽÖ_Á~8}8}É?/2õ÷OhÁ¥×íŸL¥w?íÿ&)K䦨«Ú+Ÿ vÃ÷Ôs)M%Àä™KÓ=ñT„ÎTæ;„ôZ')šê3¨¯—]ªœ"È?!Ó0�W÷%Á¨™ŒÄž ŒÄåÆÞ b:„dGu¾€è LCSý¢šë\¦1 +endstream +endobj +87 0 obj +<> stream +xœí�k�Õaï»ê~NqôD�2R,ŒËFˆkVH~ »ÊüH>b17Ö*•¹.=ð‡`X¾eyHº¾NÛસîÞ¼V2ÁÛyȱ ’X±ŽÉµ×Á¹²œÕ®VnfÎÌ™ÓݧŸÓÝçtŸß¿~³=§ß=}~:çLÏ7nú ÊNB!„RtV­¾ÀnÖ€M¿Œ`¼ +0Žñ�!”ŠñЦ"V®Ðcüâ,ã7€Æƒ½Ô\ ÀBŒ×`Àp˜¿“€µ Z90~÷€Z€h$ÁÈ(ȃµ¢A®`(-?ÈUM¹¢q?¤Pˆ@wüʸ?$°b%ƒñSY!Æ?VX€–²ýÊx„À€�ÿ8Tào(8Ln¿B�¬Æé²ÀUrøvà +…ˆ–î‘Õ¯*×É!Z ÊÇÈéWÆïð³‡ñ‹ÊR°,€2 Áª£\¯šrcüã“Õ²R‰–n`­_¯Ýò³|Ô•Eaú"¬^ÕrY–ñ @I¤oÂòùÕU¿ûžkßÿ��7|覑Í[¶~¸R¶@Õ~oÙ¼ñ†]ûþ\õ»ïÉjY4d@ÓÉãW×¼ïýýØ-‡¾öõç_øÞìOöëß,€ƒüä§?{þ…ïþÚ×?ú±[®yßûCÍY4d€³dð+yó¼á†Ýý¥½'_›5~{K8õÚìÝ_Ú{à  +õbYà&iËçW×¼ïýwi¯ñû9XÈÝÿcoÖ¶, šH¢_·_õ»ïùèÇþ€ö+ÐrêµÙ�~쮺ê=™ÀcYÐ8²µ_­XyŵïÿÀ¡¯}Ýøm¬åð×¾~íµÐ}Í0±-Ëø- 2ûÕŠ•Wl¼áCÏ¿ð=ã÷p°–ç_øÞFÿˆ¬,£ß�߆$ówÁM#›ùþ Äð“Ÿþ즑Íy‹æ,¨5©†¸kŸ+¸eë‡�ßÀÀr¶lýpôSIÓ(–u$íS´ÏZG± ‘®biž'OC4šÜMX«Q,wxÏÚuÏ9žþì‘£W¯û½øyûŠ•Ç²P,¨-%*ÖÕW¯}ù•SòõçwîJ¼�ß²m{šb¿þÍâcßx(V]ƒb ƒbô‰ó«Ä±î(–ÁT¡XgÚ¼ù¡3¥¯†4'‰Šµ<áï‘–…b@ݨ�bM�mÚ46UÑÊjŸb©.td—çy»Ž¼—”î,vdÆEbƒbô)E±.4Ñò<¯5¡½éö“Ò�P,ôŠuæ¡Í^q¤(VgÁžWà²I#ƒbô)S±¦Æ6yÅYŠå�N±Žìò%ÚüÐC²A«gH}Iê»S`rT1ÿZ‰ аjõ•±ÝckEÅšhI¿êÚјlÐꨗH·@ß�“õÅ‹òdûØ à d«µIè½ÙkI ®£h¬0!ÅÚ¼YíìÊRÿÏ#»äK¡Iò¿ýéGví:UÌ·‹Ä%•bEXVºïæïœ‰”©X›6©ýƒ]±éÿ9Ñ’/kêü·?}¢Õšˆ+6(”#1“´¦N7eÿEÏúüËl’by¢ÑÊïHƒwÕì:2hõò|³è‹)kA±HlP,€rË-M!Ÿñ79y²�©ç?žom±À¢‚í]]¥R§«/ÂËÔV5‰v,Ö ]* X5òMé‹VB1‹$Å(¿£ðÜ ]JëE2¾)}ÑŠ*^”Ú (V¬Z¶Wéñ�Âþx÷�Dù¾cØïÜ¥ŽÜ:Q,êOBBA±*P¬A'\H¢|íG}ÿiù<)²˜F±㪢+¼ê&uú…jó®]A;’­wƒAV¢ýJù¦ ¾Xð�„'B‡ƒb$=×]ûh÷à× —¯X•ðÐ1ªÕò5éûõ&ZÊ´Èbº19goE‘ŠZfc‹;«X«Ò}©0î¾dú¶ �†‹T‹X Ū{P,baP,«îA±ˆ…A±ò+–âWËW¬ºyËVí•”‹X˜(źyËVqÇ�xÞg°,¨?(V½ƒb ƒb XuŠE, Š€bÕ=; ±2ÚËÅgX“¤XáçŽ:ŠbBR& XÃ�xDzÀfP,BHuA±ÀP,BHuA±ÀP,BHuA±À Ö$)Vª€F±²Æô0d31}ÔÍÄôQ7“˜¢Q¬ˆïé~ Ë;A±ÌÄÁÇ,¸¬Xï: Å2ª]w¹Å7@±Ì„j×�p®A±À P,3¡Úu'œë@P,p€5Ã(VÀ¯–/ϨXSc›6�Me˜!Kô ŸÛäy­‰l‹šhy^ñªv�ìòd6?tF�²ëH°LÌ{S€bå¹fÌŸôà¹>óÐfÿ™ví\wkùª4xG± ¶T¬XÝÊNV\iªËܦ›±³ú¬U­ÈD«pÉÒ)–¯ê”w*äî«4SlŽZíúÎNú³œýz°á¤Ïõ‘]ýsudWW²š}®ÃA±ÀªU¬NýØjÉ:²jŦÊÌ_QG%I±”?Ï<´ÙWÇL±:&ËŠ“ÝQ(N[ÃÏu8(8@¥Š%ªÇA�×3.Ñ÷1˜æ &øþü! +w7ÖêMêU…J¡`Õ*W¬¬·5Ñ›¡3{Ôteãû«( u#¦£°[ªÆ%^§™bu’kpúúÞA$�ôÐy±ã¤G*VXŸšx®ÃA±ÀR)VʱîIŠÕ¯¯ÔZÏó×`J³ÁD«ûBÛj1Ñê”R‡Øô–Ù@3¨vý3jªK9ÞFÌ5]]i‰Š%#:�^íLx GƒÓ׿äAïŸ?ýI÷—97ˆ%'=â\÷z ¯Óá„kù°Š…e€mT¨XƒÚ®_‡©b¯N�hùZÂe”9ð–vŠvýj›‰ú"jzćŒã�GšV¬ðõ žqÙÎ>é�2çÎ|ÎôI×�kéWÍïŨN±5a¯0¨X"Šhùº�üƒ¸*V,¥±¤�D�î�fnöè´Š0œø“Š%']÷�BŸ$5û\‡ƒb€T§X¾‘ò�F‘&Ñ÷× + …—ê(¬*ß8¯Ø±X™kÛÒÇb)_äW¾nÖØ/òëÆbi; +£š¥4sibÇIœkåT«Cï{®ÃA±Àý*ƒb-‹U,SC·æ›MŸh&ô'‰qò½·Ä·µmVýBê7}+�ÊWÛ*­l¥�Åjl†îÑ;ÿ;Nºãç:œ›·l]V¬8ËB± vT§X6ĆG$ÉPíVN:ç: À-Å:çûbêTôt÷æÇÐè˜?éœë@P,p×Ë¢PíºÎu (8Še,T»î„sŠPˆbõ¾Nˆbe +Õ®;á\ÒS¬ xG± v XÆBµëN8×� Xà(–±ìt2¦�º™˜>êfs@P,p‹RuP,p€d¿ŠR¬ð£ÝQ,BHšh+ßÞQ,°‹RuP,p‹RuP,p«øÌÍ͵ÛíiBÜN»Ýž››;§ ŠïWZÅŠëŽb‰´Ûíùùùwq;óóóív[ûŠ¥XVàé£ZÅJ´,ã·S«øLOO›®Ü±"ÓÓÓÚÏŠ€b‹ Å*>(!"(8LmkjlÓ¦±©ŠV–-(!"(8LίfW¬©±Mž—OŠ´6•~bå±Q±fÆGFÆgLoq-©kUŒb%~©Ë+©J±:òÓj(@5Q,ŸØ¤ñœa\(fÞÀ[3ã#žç�NÊ “£ž' +ˆ·ºAÊÈ�A±Àa*R,á>­~;–êBý×Ýv®n6�Mu'޵<ÏkM +O´úµkLèþ9qÎ·ÕæÄìSáòêý¶Î”a-m(Å&™kdtTNêü9Òýkrt V“£Šƒ¢fqqqvv6<}vvvqqQþ‰b€ÃT£X}I’ŽV¬ŽÛôÄg¢ÕÕ*U„:…•};’3«_~ |GØü«¨J±-E]‰éNíþÙ+£´%‰B‘³¨“­OÁ¶(�b�Oö§MŽz£ãã}Å«HröìÙsüøquâñãÇ÷ìÙsáÂ9ÅgÉ>Ö=îÑîźY§X¾f¨~‹U°K´PõJëHªûèÚÁ+qÊPb5ˆO±<zÓó˜Ž�N +9ê™MÀ…z­J±³È–'m+–�&�bõ„J¼î˜H!19þ¼jY¯Ο?¯–‰T¬›õŠ•ïï(XH5Š¥vÇõzä"õ¦/Z(Ö Û²LÅ +‹�j;B˜S|ï†Ú bf S¼N¯X=¡êµ’ÔÑ"I‘–¥õ+ \¦Åòuð ±Q:ýz]sSc­žãtug"lP¡ŽB­b †e—¯k7Û¤®³¢ŽÂ´ŠÕk¯zGãNIŠ%ÝL»Ò`ÉAÏ£¿ŒNC’aYZ¿B±ÀeªQ,QOldÓVhú`({d3—Ò&†»‡Û ÄRúc¯”åk8Xg•ÃÝu½~!_R«×¨”8‹Ö—z¶¥X�)3ã#r¥<ç�¤Ë|7Ú·P,p–œŠáWË–¯Ô�Ų?-¥�lØäîî÷¥ÉÑ�ü³GÍòŽìߟ‘ó÷¾8˜R±‚ý‹ÅVÅĹÄ*ÖÊžb¥ñž¨X}Ë2S8®XJ#Vq‚eç£G 1 œÅqÅ*)(!"(8 ŠUFP,BDP,p–ìŠ÷ÜQKÅ"D$Q±R>}Å€Ú�b•‘v»õ+BÜÉüü|»ÝÖ~FP,h<(V™››;vìØ „¸�cÇŽÍÍÍi?#(4‹R}P,h<(!¤ú XÐtr}�0òÑî+Q,BHšÄ(Ör šŠE1 šŠÕ¨ÌÍ͵Ûíi‘v»5úºÀpÒj šŠÕ¨ð°ˆøÄ}Ôø}À„bmjµ¤št,eS¤¨Q¬ô³ÔA±FFÆgä3ãÝ?gÆGT§è–P¦©s45Áz¿„Sé; ƒ£=:ùŽz´G'»¯ǼwŽš ¦zÅ›èWh-¯5ÖÿcjlS¯Þ�\ƒ¿;ïÞíÖ‰c-åOµ"òÝîšïö§–Ô¶ý"½�ôÍ¢.TqÆ^e^Ex#{SŠ·´$ÅR29Ú­Ý{ÿëMêZ@M³¸¸8;;ž>;;»¸¸(ÿŒU¬ÎYé·³N´#Ï@}+ç¡öÅ€FcD±z-D²i*`)JS•Æa|mM†ã+¯F7oüí*+qJLÅ3\R*–¿éjЋU÷œ?^­úE¥þüyµLRGaϱzÿó½+ ;ÏYèô +jZ±:/z’UÅÊy¨ýA± Ñ˜Q¬ G•ªX�*шb©5³ Š¥ +–Œ0­ú‹–¬úµ•~š±X]¹ê7f ¯Xþ�oïè«÷¢VŠ•çPû“C±ú–¯XXØ€!Å +Ön…ƒ›PG¡®ÖëõÞßU¿Â˜RÉB…ZÅôùGèhÊ+U³ùŽB­`‰4¤ÓPTýÚJ?Õp÷nãç઒ïªÝÄy: +û +V÷åèxÍ+ó¡öÅ€Fc‰b…‡»Ÿ“#Σ†»ûϨ#ÏÃmP¡ÁðÉ­^Ê\ƒ1ùþUËÍ“e5 _†ÏW7Ü}Ôÿe5aOAÁêT퓃×u«â£2ß�ö­ß(T&ùúš•q}E+V¯±†Ç?áöÅ€FSµbu~ñ=‘I}Bà KÍ褮P³“üèÑ¡ÇÎÅ)Và¡ ê»1í‹õ Š“S±–£X£4b•)X<ô2EËgÁC+–Ë©^±Ä=Íô}Åj`¨ÜÃèT ÅjZ¨ÜƒbU ÅjZ¨ÜƒbU \eM.ÅZ­X+Q,ãi·ÛQ_ï"âëoív›³PAµ¢X+£KûôQ ,Åj`æææŽ;ö‰È±cÇæææ8 $ñP£XÐ\P,Bˆ± XÐ\P,Bˆ± XÐ\P,Bˆ± XÐ\ëŠTŠåÿ:á²e(!$9ÅZæW¬å(4Šuà;íËîxÁ»ý¹0—ÝñÂîo¼\í=žb,(4—ªëŽÇNhåJåÀwJÿB=!Ć XÐ\ªV¬¨ö+[Öw+¿ÓG¤ÿ³‚åþ¦`šL=xã�fý™¼„y‡Y&!EÅ€æ2¼bií£X‰~%Pg™zðFùóÉe�^6&ZC®PÝt±ùy­Å"ML@±–ùkyœbEZ–¼­™¾»€ãX¤X·ÿå+aÅêHÊ@¦l•£ZÙè¬{¸æ«Å"M ŠÍÅÅúæ÷~û_N‡Kߎ4hê¾ÛU…[Ý=Uäë@ùà„ÐÛç”>ÂÀQËT·AÝÎÀæ‹)ƒÂ²Ò×àÕ™¢[~wÆVëF¯¿«J'æ |Íá#ƒbÓA± ¹S¬O?<åWªbEôß)!TA5� iË÷¦L´ú‚£mÅ’6•j™¡6¯@G¡´µp 7²÷§nù½»¥;ØÑ+ß›¡¿sƒ)¡B±ˆA± ¹$+Vøgî;w<ýsG3(Vë/_ùÆ÷¦õ+�bõe¥ï'J"Z®üòã+ßk¡ŠmnŠW¬ømˆYl”íÈײÍJ»üð&i¨=&(±/ÑŠµ*F±âG¼£X`ÆKXÖ?ÿêBد"; +£Ä Q±ô"¡ˆVÅŠÙ†˜‰qŠ¥4®%Έb‘FÅ€æbR±¼ÛŸû¯»õÏp�å}ÃÝ�tþ.¹°`øæÕ”ï�šïÏ©Ùîv•¼Ì!Ë¿ Ë�R,}G¡¢nþîÑÁ‹ ŠÍ¥jÅÊñ\,ÍCbr+ov†‡¦{R�ÃØÅ$Ÿq(‹M^fäwýƒ±&¢K]ÿ®ŽÅÒ+–n¸{‘r›ƒ{�bÓA± ¹T­X¾ÓNT¬Ý��¨üNO1 J¢_¯XçÎ�ÛýØË1¿Qxà¯ùõB\ÉÍ7o½<^±VdV,ð`F±!DÅ€†‚bBLÅ€†‚bBLÅ€†’O±VG+ÖJ‹’>ÉÃÝãðŽb€µ X¶dnn®ÝnOÒÄ´Ûí¹¹¹sº XÐPP,[Òn·çççß!¤‰™ŸŸo·õßF± ¡ X¶dzzÚt5HH‰™žžÖ^ù(4”Tв¬ÎínyŒb-[y9Š•1(ivb«3Ü]±, ŠeKP,Òì Xà(–-A±H³ƒb€cP¬©±MòÇ�7�•óÄSc›2-Z–Ï:cqÉ X3ã##ã3åÖ‡&ÖE #Çp÷ÕZÅZ–N±ü^55Ö*Çgr+–¹økf|¤o¡Ái®b)û­Ûñ¢Ö�4šH‚b-÷+Öò�b)¿½:»baY` +ŠåyÁ©ƒ†­®èt…g¬åy^kB•µ­Éß æŸõWk¢·�V+8©_¾Õ +­BÎ%6INe +33U±F~19::¬«ªX¿¬kf|´œõ¢XEgqqqvv6<}vvvqqQþ‰b€cP¬óÿþ¿eM´úÎÒ“¨®Þ 4&¨XÚò½)­�MõÞ{¯WJõ"ùf°£P;×`Þ@ÃJR¬°WMŽö›wFÇ;’0Þÿ»WtÐÔŸw0¥«3ñsE”éLñ7,EÎÕ]j¸)*¾ü`uê3¯*Kòu |pBèm2töìÙsüøquâñãÇ÷ìÙsáÂ9ÅÇ0£X²zÍLêØ,/ºåÊ/`¾ò]#’t.jl•°%_Ÿ`ß–åãç*§WQU¬žN ¤#þ¿úöĤ3‡˜Ò/:9ªºÖÈøŒ~®¤2JÓRÿeì\Ê–ôµ'¾¼²Sé‰�W£XÚòr‡úˆ]�óçÏ«–%üêüùój Øb Ë +úLØa´Š¥7E´j®X½Ó� ZAX-SD �ODum>)ËäXrúµhvª?CÊ-Œ_×à êŽ).Ò²´~…b€{˜T,aYj¯Ü¹°Ã(�€ý~9Mùþ¨ùþœƒ¹t]~rá¾.?ÕÍbçò +¥£PI·Y&ŸbD"~®¢–¬v÷¥Y‹oO•©é·0¾ü`áJ?&ŠUN„eiý*Ó7 +—ù¿Q˜O±øR!˜Æ°bõÆežãào&’oêÆ¢{Rºõí)r¸»¯“ñœ¯|ìp÷ÊkD6ºô| ÔQ¨é wú;ãbçŠ,£LIµdÕ”Ò¬Å?9¨g‰[¨Ž)‹(?êßšÀ±$Åf¾í[(8†yÅ +�~/9<ŸAU±4�.PºÀÄp÷èaÞááîýN·¸¹"Êh£3—o`~x¸»®¼ÍÚbç•ov†äk×¥l�ÚÒÆp÷êƒb€I^ýrå+µB±*µ¬:(VC2©ûf$q5(˜¤jÅÊ÷h÷ÀÓÝWezº{”bUÝ–e_£XJ#‚E)çéî<àÒá¤byþ”ï2–¦1ŠEˆ6(˜Ä%ÅR_”ï/5ŠEš LâŒb ­Â²Ô´Ûí¨¯bR÷ÌÏÏ·Ûmí•�b@ÔR±V+~•í7 +Q,5sssÇŽ{��&æØ±csssÚ+_?Ü]ÿèQ òâ†bþIJq9(T�ŠŠEˆãA±  +ÜS,,‹ǃb@ X„Ç‚b@X©X!¿*X±cà !N%·b%>à]ÞâÌßÛÀ8®*!ÄÙ XP(Ñeú�·¶>sÙWNzÚÕóÉ¿š�~ã­¢öåìÙ³§OŸžžž>QÛÌÌÌœ={¶¨BP,¨‹„2ýÆ[—}åä½ß}ó­ùKïTž·æ/ýÅ÷uÙ½' ±¬³gÏNOOÿ⿸tÉÀ¾’K—.½ùæ›ÓÓÓXVQA±  +¬T¬�euïu+üеÅ*+Ÿ|lvüûgßyç�ßú­ß2õß?;ú¯[Ÿ~_fff~ùË_š¶¤277wúôé"N/‰P¬ZÅÒŽxG± ( Å;Ð~kþ’°Syk~é²{O¿/'NœXZZ2¸#Eeii)ê÷HÖ XP( Å;Ð6«Âî¼ú˜Ë”'N˜Ý—sâĉ"N/A± P,ŠP,³­X(V8(VQA±  +L(V¬_¥Q¬U w/5޶bÍŒ�ŒŒÏÄOÉ”!gÅ**©†»ëF¼'>z”§�À‹„’£kf|Äë§§ª`LŽzž7:™Í(ÊW¬îVË�D±œ ŠU€b‘P²¶buLeà�êü%ÃÿvšTÔŠÕÙÂÑQ©A(–3A±  +P,JÆV¬ÉÑ€A µè FèÝÔ)[±Ä¶¯g\JSœ~Êø¸l“4Þù캗…½A#^`–]™ˆ XEÅ€*@±H(ÙZ±4m5“£ÞèdgúÈHöþÁªZ±úÛ-Kimë½ÔO‘{ÔÝMßÂ:oöW-Ü[KhM™È XEÅ€*¨Z±®Ì¯X<ݽªdkÅŠQ,O4Ðä�¬wJW¬Áf÷ÍFÝáJÉSÔøß}'´À¾PEÎ’Ô±ˆb•,OwG± /( %ãX¬øŽÂ@ËNªTЊ�ßà±ôŠ?vK«X1³ XUÅ€*@±H(Y¿Q˜0Ü=Çx÷nÊT,¿÷ IT §÷vì±§~y uõ)4 Še"(TŠEBÉñ\,µQh0®;ì]éRz+VPfºŽ5zðDøQávªèYÄp÷ >fA±LÅ€*¨‡b]¡Q¬�e¡X‡§»Û«¨ôkù@±–G>Ý=ühw Ò�b‘P}º»õA±Š +ŠUP_Å¢£°´\ö•“oÍ_2ÛŠõÖüR!Š5==½´´dpGŠÊÒÒŠUTü…«P,(ŠueαXñ?ݵ,«�l9|æÏ޾)[“Œü÷/Žýê“5;ü¾œ>}znnδ�7ß|sff¦ˆÓK2µb1 òb¥bi›°¢; +{–uù²•7þþM¯¿^@½ìx¦ß8{ÙWNþÅ÷õÖü¥ê]â­ùKvô_/ûÊÉé7ο/gÏž�žž~óÍ7ëÛ–µ´´4777==}öl„¼þúì�¿SÄX÷U¡çŽæS,Ó7v°�)Ö²e+7\¿ñùç�š¾…7!ÓoœýäáÞ¾çÝõbÅ\ö¥¿Ý2þO…ø•ÈÙ³güã÷»ß=ZÏüÍßüÍñãÇñ«¢òÜsÏo¸~c>Åê[Š)¨‡bÅw(Ö5×\{èÐaÓ·pBˆ½9tèð5×\¯XQc±Ò)–é»:X‚•ŠÙ�•¤XW¬¹ê#ùØ«'Oš¾‹BlÌ«¯¾ú‘�|ìŠ5W¥nÅÊ1ÜÝô],¡®Š¥ûRaײ޻þ}_¼ûnÓ7rBˆ�ùâÝw¿wýû–%ŽuG±`xjõІhÅò=ºáúëoøâÝwÓ–E‘yõÕW¿x÷Ý×_CôsÝQ,(”z)Vâp¬î÷ +/_¶ò½ï½æÃùèÁC‡ž{î9¾cHˆ³yýõÙçŸþà¡CþÈGßûÞkü~•8 Å€!°Q±†úRáå=V\±æªõ×\»aÃÆo¼ióÍ[6oNÉÍå1b#Ð8Œ_T +}jÒruܼåÆoÚ°aãúk®½bÍU—/[!îAÅZ‘I±n_æïêÖãß5d�zPÅJ7âÝoY=._ñ;–Ïïde™–ßn¿]—› ˆ-7~ +ª"ꪎü”ñIŒ¤óñÜ +„_E*VB/!ŠP 1Õ¿2¤Xiú +Ó*VlC–T¬ËmQ,çÕË*œ·£LdV) ËïWzÅJ? Å(–(•B±† (ÅÊÒ�µ¬´†¬Ê•ÌxU �§˜8ÿ¸¥ð«Â‹çŽd"lSÍñ+sŠ•dY©¿TØU¬å)K´eILßüQ/¨Ž¦(S2��¹F±–'4a ýuB ¡j”_Õ^±²5dù+ +óÕD±êÝ¿Cïd])¬Ï®Æ6¥ÿü&øUŠ&¬a‹€È�Ôª¦ù•ÝŠeY©k`Y+ô\®§êU¢�eó4ã²ÑÊU¦f«T¤VùkY\–_±âü +Å(!W(VA ¥Xé²,«õ2^Y«dÅ4©BqmD&,¨iʔݚ²©TZ¿ÊÛ„…b”BýªIе"¦!+‹ee½Õë}Ìx-VÆ¥¢1?•UQ¦Jéý*[ÖŠ˜�îib¡Xy@±Š£ÅŠí.ÌØ–UŽ�Õ¸ #™ÿmRÆ'4¯¢+黄(@Y XÅ‘ê¹ ©Ýþj¡¶»°Ë*W½ð10B† Ò•Šõ+í(÷,½„(@Ù XÅ‘fÄ{ìÓ±´ƒÞ£²˪\·ŠV²Œ>†’9E¶k£�nqóŸ�VIB~7 ++æwsR?tÅÈIckêŸþaÀù?êŸUmCZÅÊÜ�•Ͳ´¯#rúXvõBÉêKÉ*eü"ϨRZRøMX¶Ð$Åü©´bU¨X)û +Ó?æ=­eIÒé–•>VTãC¹Mgø[‡«ô3kÞ”²YSZ­Š÷«ŒÝi¨«P†Q¬ô Y‘–U�bÙ¤^uV8C~¨·#ã\752 R©+äWùš°P,HO½+ˈ,ŸeU'@ø¼± df0.3 ±šR©J™Ò ÿ¼'´_¥……bØHûÞ/WÉÛµ+Ìñ]ÿKåŠ'?5øó¿«ŸžšÜõµ0ÿ0à +þ~g�¿ÛyøïvþÑÎC?Úy臟üÁ|é�ýþ?rì¿ þÛVÏ;çyÿæyoyÞYÏû•çýÒóþÕó~áysž÷sÏû¿ž÷/ž÷†çýÔóþÙó~ây³ž÷ºçÍxÞkžwÚóNyÞ«ž×ö¼W¦#8‘…Š`*‚g与ÌÈ?ÁßÄß9CQGLKÔiÊzmh/°(²^½QŸ‚¨O�ö#örQŸ_õ3Þî~ðOuo¯uo¯wo?éÞ(~Ú½iüK÷òóîÍäÝË/»7™³Ýοuo>ç<ïמwÞó~ãyÿÞeÞóº,zÞÅ>K].uy[á…ÿñ.´S�×à«V_A¦ËÖªÎ,ÇwÉw­lÅŠiÈJê.ŒzŒC>rüƒº¬¦0ã­Ðxìi€mÿ`Ö.ÂôMX´béIT¦˜Ê ×#¤Wý(ÅZÕï( ¹öäÉj¯ä!û +³*ÖЖ5¼b•ö�…B² +¶,Ô @GÙŸ‚!TÊœ_9®XUTRÕn ˜¢JÅ*öj)D±Êذâ²â-+Ôi8œnUå]ø”�©kxh› +¢# Þ ýŠ^Âêë‚¢…hÕ—Ê«ð+¤@Å*z Ó+VbCÖài-+5V2TÍŒ_Ty•©•JíW±ß"ÌÛ„å–bUZI™^TCŠU’�®X…nj� Y1_0,²†§�ž†¿9oAeÙQñ~•ø¬_$t°—Ð@%eÓvBy”­Xå]e(VŽmîü|„üM‰K}Ä�Nˆ Xì"~˜âB—ù.ÿÞýåŠßtÂâ×]Ô_Ô‘?ªófõ§u~îy?óÿÀŽäŸ#øIf3òzg²0S¯•Éiˆ ÔÞõÈtáE]½Qdýth?bQŸÓ(ÔÏøý_ÈùYÿn $GÜ+ÔŸÊ¿–óÿü¿–ó›þ¯åÌ÷ïH ýÛ”ú›9KÊ -åÏæXóû95¨¤ì[,”A©ŠeìJN±ŠØò5» SÁ0®Ó°(êÚf +ãí6 i jNCSž–¨¬¬Ž¿JlÂJ{/rª «¦ºRÓÍv�òËä50´b­Z}åaYÅ)VÅ–e½’¹¤p®`ü"©ÊŽªô«Lýƒ®)V­E¥Öï%)–á³_„b mY…7dù-+òk†öc¼–�ÿ8äò«•™üŠ&, P”ìBã)C±ÌŸ÷‚k8ËÊ¡XîXVzŒWÁ`-Æ/ΊX 4¿rG±ÌWR술XñTÞ�•í;†²Ó0ãuDÝ0.ÍÆøùµ”4Ÿå,ƒ¯rûUÃË| Åî8CáŠeÅé.N± +±¬|Š•dYºF­(RÞ½ñ1€BÉôÑKûqŽ•«Ü~åH–5;å Å*–-'ºPÅ2Ø]X¤e¥'c½`¼¨Œ¬ŸŽâ?žI7„¡ýªÉŠeK Å®9C�ŠeÑ).Z±¬·¬Ê5¬B…Ãë§� ¦Û©ZŸ²øMXA,ª¡ØAg(J±ì:¹%(Öð–•õŽW´eÕÓÇLø[Q—óVÓ4ÊAuŸÜ"üª±ŠeW Ån:C!ŠeÝiµR±jeYÎ(™=`5õÀøÇ ¿Êƒu•»é(V&*oȪ‹eái`?Æ/ò²Ư\P,ëj(vÖ†W,O¨­Š•ײÜ­ò0^¿C"Æ/’ú1¤\¹àW–VR쬠XYÁ²Àð«4ØXC±ËÎ0¤bYz*›©Xˆ\’«œ~…b5wÙZP¬¬˜xÞ{¤e!ZNQ”\áW ÆÍ½¶“aËÞóX¦bÙaYAÑŠÇx½‰dúPyqÁ¯¬®¤Øq7@±r0œbhYÙDkHŒW@ÖRå'qøû†#~eu%ÅŽ»AnŲú –¬X–Y–ãBÕ ÿ8TãTø•ƒ8¾û–€båchÅ*ϲš¦d…`¼*‡DŒ_$å`öî®_Ù^I±ûn�O±*>wéW×+Y¾b5Ȳð7€0Æ?>Ń_U¶êè�+/E(Ö•ÊMÏü¸V¯‚¡2Œ_lMÀ)¿2eR¨~eJ´P,ãÔE±Ò¬qP¬NŠ…e@‰¨w˜*ïÛf©Þ.¢lJþij“À (VnŠS,, JÁM¿2å3Q+•öÕøƒÁS€bå¥PÅ ZP ~¹r˯,¬¤ŒŒËB±Œƒb –â²\ÙÖ„åÚ¡ßñ¯‰b%®ÔW¦ÆŠueàßžÆïÕP#o¼ XøŽM+M3lSK€hYÁÞ}û·m»50ñOî¸sï¾ýk×­ß¶íÖ½ûö¸idK` Z>xÝõ+W­ùàu×ËׂµëÖßvÛQæSŸþ¬º(íêöîÛÛm;b¶6†µëÖïÝ·?MI±�1{*¶M-Þ’ÛnÛ!75ÓºÔãsÓÈ–ðDÁ§>ýÙ½ûöÿÉw®]·>ý[�¹’ XøŽvÅ2uõZ¨X%Šà=Óø=ÜA.\¸��‡~ôÂ… BöîÛ/ +HÄ»?ü¨º„'Ÿ|*J±¶m»õÂ… ÒFn»mÇ… ¾ñÍo‰2¯ž<ùêÉ“Òd« (Vxkã[›¦¤ØÎ‡~T®ôÏÿü~uO÷îÛ¿°°Ð+¼ýã Ý|æÉ'Ÿzòɧ×õ©OvaaAÝÁëú6uÛm;î»ï�‡~taaA¶‡~ôä©S{÷íòɧTý‹y« WÕÞ™#×h¡ÏX¸INQ/ÅJü¾F³K‚kCÔõòÏ@Í®z…D‚¬ÇK ldÛö�‹6¥………;vߥxòɧNž:³º˜­�aíºõb_â¨ÝÎÀD±§ZÅRU3½b  +O¿nÃÆÅÅEyðïØ}×â⢰¸ÛnÛ±¸¸x݆�â­o~ëñ§žzZ¼Žy«î„ï ß™­ÅìC±¬ÂÂMr +kÈ «ð8 ZX\\Ü·ÿ€xýÈ#Õ*~åª5ûöX\\ŒŸK}fÛö�/.. +uQ_«äìQ«Ó®7†ë6l}êôiY8¥b]¼xñŽÝwݱû®}û|ú3(§?òÈÁ§Ÿ~F-ùÒK?k?ýÚkꎯ]·þâÅ‹bkcÞ²�øOw•ÞŠk� ?�cü Xr@ ^еöà»MV,I¾r~ŒW@F¸xñâ¾ý>÷¹ÛÅ‹À»ûö¸xñb`â»ïºxñâÈæ­r O?ý̾ýˆw·mÿ¸Zã?òÈÁ‹Ý|ëñ'îØ}—\ˆººð¢äìÚ�ŒA»ýZÛ)‡El¤º(YX¼�›ôôÓÏ)Ìu6Š#pÿýcûö8ýÚk/½ôáœáÙå”ðŽË)1oå úÏr•ãFa›hÙ³%R#ÅŠêìVåÊ%Å’¿Õ JÅzé¥\¼xñôk¯]¼x1оváHj žI±„·|ëñ'.öóôÓÏHÑ2®X�÷¹ÛG6o]ZZ +TñûÜ#êëýî¹ÿþ±¥¥¥×ff¤È%ÄTèÛoýÄÒÒÒö[?¡}K.S]]üÖf’‡Ä¶óÑG‰�ÝàžÝw~Ạµ‹RwJ8’8naGJÃý÷�‰#POÅ2þqvŽ47íÊî۬Ξ�ÍÙùÏÜ{o�x÷æyÐVÂF&)Vðw6ÝjÅjÕÔw™YZZºÿþ1ñúsŸ»=P;EhØ£�J_¡Ç(–ºRQÀ¸bÅlg”b­Z½æÓŸùÃ¥¥¥Ýw~!Ÿbí?pÏ¥K—V­¾òàÁÃÏ<ó¬zÙüð‡?êîï•3gΈ‚µëÖ_ºt©»qoA#±ížl|¼mÄ)¼ã»²Î¾r,0ÍU‰R7 põ¢XP,—.]R+èÇŸø¶ZAËÚ_²ûÎ/f ü`û­Ÿ� ܱãö;n�)^]üÖ&’¸@íf$.*\X·™3gޤ]Q`<üÃþHÛÀþŠ#öøß~ü‰o¶¶ÛÈ÷4 ïÉf›’,< î€b ¿mO”M@ZÖ®[?s挬£µŠòÌ3Ï^ºt©Û]¨YB€°AÉŸùìÍœ9#^×W±®Û°ñR7iK�wdóVéQb!»ïü‚xK×Úuë…�ª‡îñ'¾-Wó4;ïÉ-ËÎâ P¬°q5ô¹X`€°´ˆJ_´«hE˜€( –ðÌ3ÏÊ!La£8xððjÃãO|[”yü‰oÏœ9#õ@¬.¼(¹…ÚuÅ´Ø„·_Û’6¼b­Z}å�wýéÛo¿ý쳓‰ÇüàÁÃo¿ýö<øÀ¾ýöÛV6¯%Þevìhæ:pÏ—Ÿ}vòí·ßVM5æ-h$Þ™M)–…‡Â)ê¨Xú!îê¶¡XPîùrØ+>óÙ?:pÏ—G6oÝ~ë'Ü£¹ØDá6îù²±Øë6l”%×mظcGK”ùÌgÿH]¬X�¹µab+¼ý;v´ToQ·*ý¢¢ +ßyןjfdóVõ(iß +/_ö;ïúÓu¿wMú· yØyg6bYv +w¨�b…Ÿ‚…b€ÄÎ;3Šå ùŒ(æ¬U¬X�-A±,G´qEÁ@t;ïÌ(–ƒÄQÌ#³‚b£”D± † ×ߣX®¿ÁøBݱóÎŒb9H¼bEY–YÅÒübNĆ• W/€mØygF±$ÞˆÒ(V L•Šõ¤÷jàÒ°“êÿ¹�hPÕ+•”qê«XÚëÅSíEÚõšú!*)ã$QÀ ÄŸ(W/€µ|•ö'y(–›ÔT±bþ±PÙ¡ãê°ƒ�R�9ìÎAI&Åêù•bYážD ÌböþhÂró @]ËB¸tlÆñ»´ã»o iŒHz”ªX=QG±À>¿K;¾û–�^±¢š³r,°îpéØ�³÷jgwÜ6†T¬ðyD±Àœ½W;»ã¶�be…K .8xÇvp—­%¥~L'<î=ëë W/@]pðŽíà.[KzÅŠšâ”bqéÔ §îÛNí¬ý ¯X�sj¡b½{ï½CÐî)Ø�S÷m§vÖ~ +1"Ë+^¢ð+€fãÈÝۑݬE‘<³v*V”J¥÷+®^€šâÈÝۑݬ‘8¹Ö*VX¨ð+Ghü=¼ñ;XGŠ5¢w=¯‘ŠÅ¥ Pw|'oð®Õ§KÕ*š°\£‘7óFîT3(܈,W,!Wø€›4ì–Þ°Ýi�'Šv•£0Œ&Õ.gQ,®^€†Ñ¤{“öRbíIÏÔQhí^@nsooÌŽ@V,<õ™†»[¸ýP ¸Ã7` 7ž} µ¾É×z㡬º2=zÔª-€2¨é­¾¦› …SÞ•�iÉ™~@'Ó’½w=°‡:VRÆ 5E\õe,6mÉÔ?]Ò¦€µ¯¤Ìn'4€2.6ËuÁò:… +â)ÖÀ‹]W/€ãØYIQCAzŠºZŠZ—.H쩤¨¡ …\{Æ—�ÄxC Ã0¤Ÿ9/W/Ä`ª’¢†‚¢È}-åž‹KRRe%E e Ÿh’i–R— (µ’¢†‚jP —X²¨E¤!}µ’¾’2¾Sà ñ—_Ìt.Z(•ĺ†J +j�öGÌÿH@4Ú©Æ«TÈÊÿù(V +endstream +endobj +88 0 obj +<> stream +xœå[Ûn$·}Ÿ¯èç¦êÂ"‹Àbi$âN$ÏÙØ›Y»±ÿ?`7/ÕÍžžÑú$ HSݬ*žª:¼Ž#NóÏL_8óQ=º„)éôîÃéã Q\ ™0ÊD$àÀ«NL\H‰¦Oßœþò»é»ÓÇ!©C’E%¢ðü ã"E.�ß}8ÁüΟ¾œ–>½?Ý}ÉÓûÿœîžÿùþÇOßLoÞÜ}uþýãÓÛ·�çÓÝ_5N�ߟ¾>}¸¬¾§b{§³sÖh�Ϫ½”ö³fÿýÖÝø±¶j[¶6Jfëú}xX²<ÿBÎꪻVƒÑ3ûá‹,û_ðQìþ—Ô¸h�B–-‘>Ð3—ä›k‡DvžRPýL +È8Ìà”Äš“BwLÖ û’6 âb‰Ù…Ìß´²$x9ÎŽ3hZðh'axý™˜"¹èüš jå¨Jøåª¢yŠpœ!^£“ –”ØqŒ‰RüŒd˜ûu=È��"û_ãxcIâHqå¹$¯èÕëƒIùÕ“g£ôþuаK‰1ü Jº: „$¿µ%\PJ»üÞLò¾Û­mg;p vÝGÓ¬× 85è—£1–¨®‡¥ˆá˜y„jkã·3@!THn!¯Îÿï…t%S0¹M¢üOŒPx< �›¸iˆB¿VL8n¨ AC eÉ! uQøÜélN 1iò4¬¾þð·w?ê<Ëëäc'Ó‡“�n^£‡E¤Œ¹ƒ.q™_C‡>ËRH´È¼™ÞåÖAI).Òæÿœ¡Îo’^db ²È(İ´fö‹P ›YIÒòbPÂ,ó"ð"ËnäÆav ã"M&/³ÿŠ(%ɲYç&â0¨�«çÞ©OѨñNõ�õjœI5Rßr(×d¤”(é OÊŠR�A»•²wÿ¿.ªZÎò`Éc‡QP¢ó€!àuP.yUfµÉN1_ejy¾Ë¡¢_'¢É˜�j‚8Ú#㇠>…ó¥@½ºþ\z‘‚—”niñTŒ#GàDûaRƒ�‹ñ»-yXñ•ÑHØUqE=& ö«@¢¿Üh òûa͇W{·RAn@ð«ðYU?±V^]‰Ò›†ë7=RàJô�}!:/JË5¯¶-§ŸóÚãúžåîíyÍ‘ÜÜ¢è”�,)Õsºåt‰HeÈáޝÑLyÐ2çóõ¶B¬(¸âƒåÃõU‡v®ÍYÁóxþÚ®Ih?žÞîóÏ1×ÇÚíúF2; b®eàê,�óžºm‘�îy'µ|–íº¬jlîÉàãå¥ÇñvlžÜÅù.�@ÛUA_Ï„¯¥G:®‘Vm[’›‡Ns'‚ëL§Þò­±²/v‘�wŒlÛ¯{_»+‹7t±¯'CÔSb?¦ýX·¬N¢`’Ÿ±nùxŒ¼‡I½B²{³Ã\:«™+÷Kæ6~37G®]Pk7K¼¹]Ro"åÒ¶·±à “£2¿7Uò´¹Òqßõ´*-ÕçËiv|_æY›ÿÏ{7¥™¿!Íú²¢Ž&‹Zš×ÑÞqñ}=Q*‡òþüò�ƒ’’4Ü ^u÷ø–¬é.B=øÅxkw¯ ®¹»]-•o0ÞÊ_Ïß±ÈÊÈ+:’‰BþÛ¿é±ýFþïÚ×.fwÔVÎrt ÔŽµÊ@&…Hg'›s»J"‚SÏÑh™9ŠËß´t}¦ær{jþûp¨•58ó7_Ú•=n“oƒªÚ6ó‚•ld/´Í»ÄqmΗÙ-sô`FÈò „ë¿*¢ê +endstream +endobj +90 0 obj +<> stream +xœíÝOŽÇžpŽñFšH,AxgK^‚ØÂ0Ò¬Z¬Aì�³lqä 7€34zËÙÒWéÑóÏ +Ç‹ˆŒŒÌªêÊêú„>²ìvVUfDþùVdDÖ?þã?ÿ´üßÿ¾ßˆ£WÀ©¸wï&ÜÜÜ}e8!)I +“,%L°Â½{7E˜<ú*׿Âô½{y�}•¶¦H�º%“¨é c*LÊNÂ$À,=“S„I€YyX2û&'LÌ&§“³$Éñ*:úšl�09%U‚0 0eê6÷ÑWl;Ô À”f·ä9§›}”£oÀq�m·d§’agŽ0 0Õ3yôµºÍnÆN'd&õLa²ç:ÅÈüî¿0 ÎsÀ䊞ɔ*SE “çÙ-9&GªH˜HÎsö�ÙÜ»[Ú-y—”0 °£¥Ý’w)A &Ãúé@ns=“E˜ì÷CÖO:úV‘0¹¨¤0ÙïÌ8ÂdÑ39µdñ ÝÂ$ÀЇÝ¥UßÝî,Yÿ¨â]ª +€E¦œ8û»” úw´§*m°3à®ê<‚{°}­ØÆ¼g²˜†pnš�ï–<‡09•'có…Ià åAhÝOÞ“çöóå!åÀÕ£%Ï-Læy2ï¼u§8C»'É3“©¢tNçf/Ý’g&‹»Û:'€ó´—$yža2¶º\š&¿ûiEàÎëtK.Í?ÂdsZ÷yV pÆopO=R²Y޾]·_�õ�îüf÷ÙÖ p·�ßà^4¡ûèÛuûÕ˜ßæ.¢õßw½ÿ\êèk °‹º%½çÑ7í(•Yü*wY½Yí}mv4ž$…ÉÁ /~tòïiÝU}mvÔI’«»%Ï9LÖ?_þ×Èɉ:>ú +ì¢ß-)L.Õ|Vy>TR˜î’¿RÍÄ/q¯žš-LFzü·�:'€»%…™:+ƒyr0IÊ w@¿}‹V‰”uÿ6°»Earê%Ë>q¢Ìx‹Ûšý9¢Â]ÒO’õ�ë~¤ìì67Š¢(Š¢¬-‘ ‹¿¤æ©_¸ìƒ¦³_'L9¡-$É»ª“$›ûÉÍÍÍx’Œ²öèQEQe}˜l¾¶÷A­Ù“)Ä¿tR�$y·Íî*ͯ¤¢(Š¢ÜBÙ%LN½C{±�RÇÈÙT0õ\k09u×»ÞU„IEQEÙWÙ=LN½O¹ÀXI1 3þ-_R’<Ñô#sµòWÍ�&EQe—²¯09»d¿w(¿O=;3·~ gb$INõoë™TEQ”C”ý†ÉNe§+²Xlð1AbäÙZ)ó¸ÿ"L*Š¢(ÊË�z&o[õCæ½FE§eÑ¿$LR[ú¥#ÿ—b·Lû!�¹D®xÎdç­š?�ó÷fWíüÎãT˜ì†£×-1›$›ßbН3ÿZ\˜€ÊÈ%r_a2–¹¹×xžù_ÿ+K¶ÍY”©ÎÉ£×*[3&§²¥žIhÚ`Ïdú—ÎÅ}ªsòèõÉ– vK6Ëà(EQEQêr”1“#z÷µYa¶O2€0©(Š¢(»—cÍæn^Ó›yòèù„“3r�;ßw;†EQå¬Ë-?grQ9z&áÔ îi;@Š¢(Šrîek¿€#F²w¤¢(Š¢®lê·¹ÅHG˜TEQ”C”]Âä`Œüka1’-&EQe/eu˜\4+§9€S·â9“õK8O‹Â¤ @n0LŠ‘Ôša²üD1NÖý/.^üøÇׯ߾~ýöéó—7oß=zü¤^òòòC,3¥ùþOŸ=ÿúõÛÏ¿üzô-]çé³çQ9Ÿ>¹¸x=»üÅÅë¯_¿õ—üù—_¿~ývyùáÖùÍÛw÷<oÇbÍ—nþ¦Üð06êÅËW뫳ck…[k…Eâàš­ÒÁZ}ôøIj©ºÓ¾yû.–¹¼üÐ{•|rç¶é·÷¿ßL”:ê|ýúmjá(õûßððêêûÍÍMsÞ¾�ÿ(¶ñêê{ÿÐŽ*ýíýï�ež>{~ss³:¥,]ç××ùٲߎùš¯ØüMyóö]Zíu�5µck…Ûl…E..^ÏVé`­6ߪ?ÿòk½Ìö/Ð{¬ó|ONoجºÙjI׋ñ½=ÕçsÿÁÃ××Åöæq —§æÎ“_‚?}þÒÜ´×׋>¨¹CWó/_Es¼xùêêêûáº58ét_ºŸ>{þôÙó襌¿üøG¾|œ4Ò’µâýó3Ã)†É8ââ5Ó<0 #géû>}öü_‡Ó©)šéÅËWÑjypýù—_ë¶KWütÞ^·ù›»ß§Ï_š§ñÃ…I­°ßVXäòòCó4•÷Œ�Ôê£ÇOÒi016¤ˆÄñÇèYŠÞžxÕÆû'÷&£Âc«ãËrÔgT~º¦ô¿EÛ¥w»¸x�ª·¹üà%&}z¾½±b·Ö×ëðæí»âﱑu‹ÿgƒEGúýS4�2�‹òmŒ+~ü{„ÏCo>wXºl5�£æ×½8i wÅ7ÓS “q€çwRµtN‰û½2®[çü’�¾•w®ni™¼™ÖmþvÄÚÆ}œú›Ñ`c­ “Za¿­°Hœ¦ú•3R«qõÏ/å)º¤ÆFñ²~áí=Læ;m38EÌîwN6œÎ1˜w/O]bò+Q½½‹.j»ˆÕ(ºcë>}þR¬FÔØÔà�ñO‰k}~èåwÕc½ùÜa³/Î6Å8xÜ¥cèÅËW1,§H–�_J[�.=÷<Œ·Ýq|`ê¡J×£:`(Lj…ý¶Â"�ûé‹jµþ‹HÜ<ÔïŸö¼-R=䕟WN +˜·EÊy[¯ÈB “Ñë^4t|bÝ5W´]}¸E€¯ŽîåßÞÿÞ¹ÄD[Äua$L®;”F¤Žîzâvs½z+¾3>zü¤è„OÅùÅôãÇ?Ò¾yûné�tHb×mFµ$âM¾[ŽÇÂ8㵫¿úÅ9¤¸øÖ'êtg$/ùé=N#³Cqb=Óá`Ý‘W“N·C|\ºÛ•JþVŘÉÔ%5¾zãâkig|f:Ëå7¸ù±Vé¦a*¤ë?æoþÛûß‹F¹¼üPøYÝÓèÔW€¥a²?­º´Â.­�’Õìì¿XóÈWqݯ³Ö`­6g4S +«Qóu®ˆÃ95e4}q¦Š¼¨ü<GµÔƒ_¼|Õüã¢v©óþ±b÷ëŸáÓ�ï~4Š=$ošØÒ~CtV ª÷Ñã'ƒarÝ¡4(­L±½�?‰¯Zyë{ÚÔ´²Ù9D�?©oåG_Í�ëë««ïÅXnX$NkK;Æca�‚V„Éæˆ‘â�i@H ΉoŽñ—t€¤e~\__\¼Î‡‘ä_“ãÛt:Ò§N>³sg:W$Õô&qJIîÒH¡ÎêÍŠ“O|n>`3¦n~úˆOŸ¿ãÊ:ÒÒ•%vÂ¼Š®®¾_]}/†Ÿ­¸Ÿ—ƒ´žñŸu‡ÕÒ0yÓ-õûh…½´Bª`ÞFS³oò|2R«�Ã<ÿ¸b÷(Z3�ôò=$o‹h ¢òS,Ì›#o‹Hªu[/jš‘:ïÏ+v¿©3|„–zZS„œX¸ÿÂ<ÆO­@lcÔç¢0¹ôPT_v\_§œ‘޲ø”â’Ñ)õæ_^~H_Uš×ú¸¦,=¨!·.àÅ«®®¾w¾¥î볦FË_îŠÓoˆl–ú]Ó99¿Æg3á.a²îbJÛÒ Å7ÇEѱ–¹þq}=ÕqÑì[cò.îÔ©•'á81¦W¥ÚȯéB™¯I=†pP=@«Ž•63®\MÅ85õ¬žâ¡–¶B?ØÄý»ŽX¬˜Á`Šª8V˜ÌÛ" YÏ«"N\iÅÒΙ·EŠ:õγ¨iFê¼9G,IkÞ9çsx”‘+õ·†ßÞÿÞ<.Ò~Õ\�¢ÿyQ˜\z( гAZ¥xç¢c$mT1Š2®õw½¼ÓûãÇ?Nbˆ5'gêðoÞ×(^Õ,�Ãj—ÎÅä¢ç?NõWò"…ÆAZÄÝÁL¸:LÖ/Ìï5o›yuõýÍÛwûºãÁ&"µÑl‚âúµtó›�;•¥‹SÔa³™Öí<Íl¡¢¸¹9Ûßµâ2­ÖµÂ^Æï5Gë¥Ê¯ÕC„Éú³ŠsW±bÍõL÷a§ViÐAÇL&÷<Œ¶H]|ýOŒ!ÝÓiÎæ®G6¿Œ9|Q˜\z( Jkÿ_(RSé·9¿{‘8ùüü˯q}9Гè8sS‡3.ÿ·ùh NÚ%LÃÝÓ`þøÏ8úš�¢ù˜íæiäÐa²Ô§Âd1DíÇõõìï:öwš 9ç4Ä'ÖŸµ{Œé¼ªcš—Î¥;O={qjtše6õ}Ç0©Æ[á @È`o'Lö+¼-6&sSÝïý}# +�.»^^ôÖÖ+P?‡j÷09Û|ãuGz¬dÑíQ3êª?©a‘úñ°SGVq_£&×Ý_=6#_‡b~ešA0õ¡���=CVŠ6Ÿp’ÜÔžf˜Œ³G=À»ÿË,ÅÂýn^n¦Æ°�oþ6cÌÔ™3¿*u>´³-«'àh…]Za|Δ|Fj5Ín6bê\šê>*&æœh˜Ü㜑%›½âÅÝáæ¸”Øü—°øK¾L~YÿÎm†Éü^vý/=~›\Œ™_7g¼¹a…ÁGëo!LFÄzñòUz`]ú_õý޿𝠓S Ì•ù¨ï\œ(¦Âdòâå«ËËir÷Tÿd=œ©?$¦y¹)zzWlþcLgߎ¾²zÂìá&àh…½´Âøœ)ùs™kµù¶#}ÂS»ÍlËn-LîeÎøŽ4Òc<»äÝ #Çé-‡Ét7­Ù_s..^Ç9!χ+&à,mnX'ŽšNÛFÂdúZZO…›ºoÇ]º�­ “S�ùj>°(W?Ÿ3Æï6òé+~š!¾½Öm:5¸´¿-#›¿ÁÓydtj…b¦ÿx˜™€£öÞ +ãpš¿e\OQ©Õæ#£‹äß|âDýàµÁ4²µ0¹zN³: S³ZòËæn�ž}ß©›sUÒC‡Š‰r·&S‡dlTóÁwivQ{#p¢3yj›ÛÜB:ÁÖ?1óRçXúûQÂdº—T<º-óü‘ŒiàMÿœ5ºxóΣ‹�ËpT€)¢lÑÍ‹ÈHOÂÓgÏ‹ßÓi¾jä|8»ù[‹1ýQ^y‰}é@aR+¬h…©²ôÔQŸšÙ9´ï?xXL‹k6b±þõÇͶE³òo!Lî¥Î›«ôèñ“ÙVh>ì÷ÅËW#ÎÒýs;a²îÍ.6?þïê‡ Ç�oæ¸ôÛì"’1µLón³Ö½ªÐ_±|,ÍÔÿ*ŽÍØÞüëüÄL¼Ãàñ Çö6?ºùéù:4·bé/àt*¤¿‹6¿Ù¸õçŸÕüèf]-ÝyúÊê_S 4¾ãi…}µÂøÓ˜G¤Zÿå#‡vz«‘­n~Ül[4+¼-Vì¥{¬óþs&;o8»KžrGöÏ©#½Xùu‡ÒR�:ÜÙÆ?e—_¾¸»ß{²òg+͂Խù`NaàXò±‘'ô$Ã]Æå°/ùã(Oh„Þîãräÿ5ÔÑ +endstream +endobj +91 0 obj +<> stream +xœÝZÛŽ¹ }ﯨç–IŠÔ0 ôôL/ò°6 ywv�\¼†ûÿ©$¥®KO²vcÃîî*](’:<¤dÈÆüg‚ ¦WFý Œ&bŒaz÷áôé„(Æ‘Lèe"0À!L–Ð#MŸ<ýùwÓϧO'k�$�7«=_¡D ÆKÐp Óòß¿›†'ŸßŸ^ìôþ_§×ÿüøî?|ýøåÇéÍ›×ß_~ÿ8ÁôöíÃãåD2‰G;9'ç’?�`2ìO&�«~Ícr³öÕ+ýÍÒßÜ6OÿôýåôúþÓ—Ï_ß}i`‘àáùôúÊòôüÓ g=âD0 :7=8½ àÀ°Ä’žENïËïô.µÀ×òø¼¼ÏcaiëÊó j®üì\Ú<.ÏÙðƒêï—9³lW5þEÉóXd>«9Ÿ„æu¥zœÚ>µu—yž,ǵ—#ÏYäLkHms›ky&óm-�¦ ¥·4G]‡ÖM–ñayVe§·ÓóßOOÏ{ ¡XPbY¥”«6«æªUW$Íš8 dî“Ǩ’ûEƒù™/m]¯!½²6f}Vå�å3µ±®ü®óÉ2G·­5e‰P~§öˆUc?œ>�)´�þJ¢3Á'x˜F™7ÿ»ëú·÷_?/ûšÊ®zý—ÓãÇ<Ó]€òÒíkõö °f}&¬û—Ë•â·×ÙŠ�áã±WÉšDr†‚GHxûü×éMé™l6{'FÃ^$8Õ aca¬&´º0צͮÌ]]¦-p'XcÙziÒHqåÖ®ü¦¹={ã).âgŸ£¦"ã¬õA)À+‰²£æ†– 0E×k*ïç¤ÓKÕí<- +œíÃ�æäѨ*nÜÿ²ìÿ=DjhÄ‹òÆ÷Ú(ë +¾é£æÌT#?{Òº_Só–F¾]ç¹²“ 8SÇkˆ;ü¾©W"¥�ÙpÄÄèÁ÷V•yq5Œ- 9†w!®ð^áÑ)¥ž  Ñy1M“è¼lø&Ù£Ú/gÕV©Q©k] ÈXfoïTÃz”n¾ç—…�¾÷ÍüÈ@eš�h™BÈ}rDZ®%úIóâ‹v²¶|W¸ —>'ì¤(´�‹Gçy:xT¦}TîrU}4wÁ(rÇP Ô¨Š&š¨|oaKc~3ÉÓb¶™JÌ!)8ãÉ*p¿p‹ì{š¹ÁÊ»"¥Ž‰ +'CÖ–Ýõ'øßúx›×û±n\cµÆ‹ V–€—9ѹL|ƒRܪŸ6`8øË€;²¯î†Ò®­T³ÁwINšÊ�2ѵ�þbbm©�¥é­’µáfÝ|+.…:ôUÓVÞ&ic` .Ž›a“§�J|˜•˜ãx¸Ã¶T1Jç!�e™ómŽøM£zç©Ë¡ €ÅûÜ­ƒP;X›ñ:DåN›æwj·Ö´‡(#FóK-Å5ß»_‘ÜÓqײÌÞ³Ö™Ÿ š}Í� `BG£Ï·U™è©õö`C >¹,VÈšœ7–u&D+"¿B¿p Ûì5sEk ['IÜ5f2ÖEdì×ì9AOúŽ×²*$QÏ‚WF"ã-1ÑÍȩ’G¾øOØ÷†Iãì0Èå”χÝQmp†!_fG¨X„q¿ODÔî«>ÙaI;ëFß`bZ„žŽK×ZÏ@‹”Å\ÿ¤9Ðe +endstream +endobj +93 0 obj +<> stream +xœÝ\]�,¹m}ï_ÑϬ+‰¤$‹fzfâŽ/à÷\Ç6‚¬ƒÝ8ÿ?Ð7õQU=ûe¸ØiU•D‘Ô!u¨*e�Ów}×÷ß(ñ3 Ql˜ÃýÛ�·ŸnÆ�r–îÆÓÝZÒJcw°Æ)Çlï?ÿÇíÿtÿëí§(c)õ—ÿªOþư¯(ö§ÓõûážÿøùÏ·/?ÀýÏÿsûò¯ÿý×ßÿíçÿýö·ûwß}ùíãŸßîúþý÷¯o�Ûë×Û—²wƒ÷¯º™,±¹[}w†Ýýë�·ï´†­ñ¡5­5h�ôýýëÞ (mØ»¾ýãý;­µ¦ î‹Ï8­ñµü¹�L¿Ç¾”vÖÚÅû?Ê¿ 59­ÉkMy<ƒ¨Ø±·¾�Êïék[È7ÄaH—¡ý^¢:µqèt�.ÿ|¼BÁ�bÄžÒ„Þ‹" Ý�¤´c7jé­‹ã¤X˜g‡�¿ŠF¹ü¢ê×ÜVµc­b 0Î]šÍ~”Áâœ_„™|ŸsÔåµVÚ8§{�I#ÅÈmh å�Ñ`¿Ñ¦ A:çYhŠ‹Lu’�tãû×Ûûo;G5׎jÁÿŠõMk*ÓˆÝV±¤‹å{RGl•ålX¬^}±Úh1f›¤+ˆæåâûä›bÕó¤ +ËÀHÂ]ÒRÀ@„ÓzÑEFÌ«®­8,Ý{­­½ðû„wø_“wLvIî6pFYp ¼�8]ð +Éox Y3ê¸ €0÷ìAƒð³a«—_伯¥ ÷ÿXï¥2|ñGŽqÖ²›”Q¬ÊZOÀ¦ëPΤøOZ£oˆ^Þ¿žº$sqIûªµôÇ/®Pÿz>ý.^WE†.ÁqVl¼?˜Å­Mˆù˜1Ð�ý"æê»ï4·¹S© `­-÷¨ ƒ¨ÖÉ¡‡E'oå]&PŠ×/ƒ!JëãÝšÕø‹ñMÀT³áAÐàÍhy*3¶ÙšQm©Íw•Ž®} �Òm\msÍŠK.&º—>%QñÏ÷r� �À¶šA%û‰,êpa‡É׋Lä§¾ë ‘UG}²ŠçÌÛ Åù÷ÿjús§úCRäCÌýRÍÌ V#_ØÆŸöí´ì´äNqˆ±Ä¬é_YÍVþpNáé9ùPçDOΉ¯çÔ:µ¡‹ŸÍv(²ÑÏÊLZ/‹í*�5—B÷^ŸZ$ˆD>êð_~ ¸ó['W3¦Stÿñ¼JT››ºÇ6päR›W™c[Ð츴!9sÿŸvÁ ¹Õ;kã�¤­ éΠ4ä6ë¼wœÛ¬óåi r+is.v ¹Éãb’óº´Íéa§ i´&·2¸{ $˜™)6EcŽO�2. ®£ +ÈäPô‚Šl•¿‡1½*zª‚�bç¢�”kÁŒ…샑Æ›¬‘¨9ãc›a UÃäò£1MÊV[Ø�Í ÚôÛmºn\¸Ý� +œŽ¡¥<\‹RFy—ÒÇhNaœ&*YÁR2÷Ao¨À85ŒÊæ¬án +Œâ&óu“a³&fk6cã}ð†ú°ô›>Œô°*�tÄ*¸tÙ>EéÜUrT�ÉåÒµ+Vµ‚_�º-ÿrû}¥rŽ`NaÅèˆ+ÖV\¹H¢RÌ“9ß‹„™KˆA 1ˆ� h7@ ptktÜ€Ð.pY( +¨ã‚òƒ½PÇ¿Ý1¨ã¥cPÇÞÍà+¨wƒzƘØ"0Í„1íºpìÚ‹Xb8±Vš`bMµ ˆÕ'¦Ú—iÓ‰XÍIwxʪO:vf™f »±T«Ž !Κ”gmäx>Nzð È€[AÜdÀ­ np;�·‚L¶0å¿�7€L{X8ŽF¸XH¸b\8­˜¢pï¦ ¹ ü +2I¹ È4#ø�±B7æ(sžág”A¹±¾ì‚¬Ö6¢Ç[A›÷žMï¶yð&2ò�Ü[ºKr…œ^PÈéhV3 �3k¢ãÌ.ÑqfMtœ]QÈÙ +9Ÿ@ÈÙ„œUA7��³39;€�ƒ „Úuáøµ±BÒpì&Ê‚�‰N•_,N1Ñ¾Š›FÄbošk˜ 4,°£ÙÂnlÕ¦#9½$:Äs¢C¼Ktˆ ¢°b…QX1ˆÂŠA"8ЄA”X°‚â¨Ù˜FŠW„3ägGÊ£ÌÔä~Øä+f(|»éB¬�¦2±Z„rźjFð«­B7åt¾�ÍäÜ„@ QlÉ_¨lzƒV„F7lzsäÊCuÓ/ÈÇJü4ò¨lú%I@…$@ {¦�’l«'¬–îB­J5£ŽC�­¼O•QV½èµôýDÊÜ¿H¯+4ç{üÈûDâ¶ìý¤n®zæs¶cú žäq.µ€… +cALÑ uýQ–/ªm£ *‹|`í†Cf�òo™øå(ÜU=ãœ{0ž•Ñ‘…—3ÞÚ�ÌoaÈ*@ë=,’!]Оœ=udñw|ý' KsV?SpZ©¸³*’,ÔßmµV¯7o¿÷b[S›ø„ˆµµô8¯,ïéf¦©¼Û¤zëk9I\~[kœ3>52tCÖCÑì@xVl©„ýdñÚÇ™•N‹G¥ë+<±çY²„Ó‚×ë’^õ|Q.D«,@F™Ã¢‡*òld…ê9T¸’ùbg�FyF‘“É^{Í!þN %h@pÚÕôîÜ5Z88»¶ …÷þ2-ƒÊ�ˆþô/›x},öœÔ±6æmc†'@!T–³›á´±ÿ®dIŠá q3Ž“L@àúµ¶€ýD?öZ4„“üU‹]v¹ Œ¹re¼\ØÞ„_^„Ø;â‘�Ÿ-Éü­µÉzöA¡g�vWêv×AÏúÔ*—à…šRºSªéAi�v®ñ×ÓAJ;B+Žâ”j8*ÖÎÒ¾öz@^g2Õ¬ÈYå*Š˜L­¯~&I´þ\mè�"N >ªMîL hkºq=Õó˜Ð)Ë. v›‹d.lÍzãÍcæO±ìœ\nJ»`Ì;,J·qÞ-cߋŶ²¯Æ²kó¹5í¿±ïî<®Ë>�r[©\`Ù1æÆXáÀ¼¯ å¾TÁ¾�m…ÎļW5>·Æs@)mCÆ•¶HM`ÚízJÏ”�ðp=ï—½‘ý”�5�#Æ=¸—‚•�zò÷½œgÛúw…†€¤â*“ ܇n +»1T“~»M×ãfxèÆç=³®Ñ{ƒX…Òcá å4±1‹RX8Ȧ4,D¥Ô-6JSZ ù)­…®]6d34&i¾Ðž^¯³Æì5Í¿²Dɳ«6©…»Šù Çnš�  *L.•®Z¹¨ª üj©Ð ù—ÛŸ�ëœïçb¹×SÝUØ)8 l�¬?›á å´ªgJŽûù�Õ þÃ!ÑñH‰Õ!: +Óº£üX_´�%�œÇ¼7ÚÆñƒÓ«ÛDoÚþ³žØëÐy˜lèÛÙ ;%Tœ5á¦sb'øFâ?ÛÿÙ®øÏ°â?ÃÿVüg\ñŸ±ºbå1æê“\ Œ™¹–K™qç,a!6 üOܨD”v] OíF@”¯"YKÀ]_£˜gGЦ´Mq �…‚p7SØ�É štĶÊ8OC`36»Àf ¬×ÀzXO€õXWÚz0ƒN´õ`-Ý ©'ü�—¤+èÿó0<áHx¡^ñ_LO:¶^ñ¿éK.³Ãÿf¿±Tè–< ö:pM +çÝÙœ¥î³ÓçÀa¿_!ðµïi«úºRÕ)È3pºÇ_è±ù°ì°ÆÍAÏ 4öA3Qùq*zH¹†ôÐŽ*þRâ®m$…ÉÁ'€ÇAòl£‰|BIt\Þ+íK³57ý Õ“‹°¶ ȵÈQ_£ÙÎûÉ@ÐÎkE²Õ?^¤„_¿�ŒÉ2cé÷“•$mÐr,ñìÎbËÜ­_TÉë1·â5WçJw)ÓŒ m\±~vgdë×µíñþŒµ\; yuš‡ø;¢ÛKù]�F¢Ìóš¹:3N¡FÞ‰Û™K;Ìýªg¼ ”Q%â�ç#�AÙ@&VêÇ#�õ3gûò çÇhõ©<åH¡užÂß™ˆ†‹Ã.F÷H#‰ÙñlGNGSü»Ö~vãóùƒÛàDÆÎ çŒ=6¥š"¹`0)’iHlCr4fì±uÎØl>B*R Ø¶06±qblbÓÌØÄ¶rÔDdi±uflR[ÏØ`ÌØûõž*ö~zR)G,¹g¬'¨MþžÉÊy¶”·+¤gÆYqÆ÷ :+˜Ü˜±'SL{7T“{lš¶<%ì±eMØcë”°³�Ç+rS›¦ ›„=¶ {l˜öض26±ufl؆jHÆŒ=]¾Ðž^#ÆþÕ$ê^ؤî*æ'»iB,€¦0±T„jÅ¢j&ð«¥B7äYÂ~ý¦DÜ|–$d +”Wo'=ÅEpÆ÷˜)É, †LZ0®½Él7 �ë¸ NÈxRð«ÏTÙ[Ñ®¤‰[©Vèk¯IÈtJNÔ¡¾h5 +®“¦¶Íš·:�Òê½_E‹¹Êã§´*s<‚–{âÌC*ðÕdÄ+K³öM�¬s*zEÛYÂÇ”ÒíÆç*†ò°ØlÌÅÆ% õ=}¬ÞUÊ{ç ZXŒÊù&,…é^z<ŽËAÆevK\f·0iÌ~aÒbÛʤÅÖ%.sXã2‡M\æ°0i±mfÒbÛ&0sX3‡!0gXPß®‹�ÐúÁCŒXcLL¢6²ÄL{lk*!°©®…J¡bR›1ìÆhP�:FfvùlóÐå3Ðb<*ѱh‰ÌŒ •Û6‘™qŠÌŒkdfÜPi±u¢Ò˜±§À/I_(K§é£H÷Â%.7™¥»â..7=È@k\fÚÅ妿±Sèv< Ì—¯ûÅlŽÕàqɤø”§bäF¥Á×#v±¬ããY ]ß>a'æ;Gßd˜¸š”H™MÈ}è= |”Y°Cb~æ‰÷2¸¯�íÞL± r9Æ¡ýžs§/Ÿ³‹CˆPµîÙ8ëö«€<>¡\/T…{³z†ëÙ yMOhðHƒùdWÿÏ0Hua¥4½þzš7ؾ�õ&Ž|ãZ°kaØwðÞ˜y 7dhƃrÆ~¦äQ>#g¬K†I<;= +$ñôÓ¯äì²Aœ^e˜‰žM={ˆ¿yéšøZ[Ì”åwñúË]•Œë9WÆå ´�‹µ—¦>?h�,¼|§¡noêG¢hL‡çZLMj:-·3É#…µZê%¿»ýîöSú:¥Å`”¥;�vñÿÇß·Œ�}Ò2j!ÍÔÝy<%íUþ:‹mtW+ßÊIÖOÌ�x£Ë›evø* ”ÿs>1œ¾¾SN§ÿ¿žö +ñ%ÍtÒÚmj¿/çϰQhMò™´£³²>yðldwü8˜ð¨Åb,йþW½I� +endstream +endobj +95 0 obj +<> stream +xœíÝ¿ŽÔÊ—ÀñÑîKp¥›�D8£¹H„ RñˆD7ñ#^ÐOúe+ Ï0£Ÿv“ÝpúUzuçr8œª:.Ûí*w÷×ú3Ýmw¹\Ž«l÷½ß~¿‡ÿ÷¡p ˆ<Б'Ú ò@DžhƒÈmy  "O´Aä €6ˆ<Б'Ú ò@DžhƒÈmy  "O´Aä €6ˆ<Б'Ú ò@DžhƒÈmy  "O´Aä €6ˆ<Б'Ú ò@DžhƒÈmy  "O´Aä €6ˆ<Б'Ú ò@DžhƒÈmy  "O´Aä €6ˆ<Б'Ú ò@DžhƒÈmy`1ÿý?·?ýç?í¿ÝÓ€CòK„iÆ<‰<°[DžhƒÈà ýïŸÖæ?þñÏŸÿþ×ïù××dT†ÿñ¯“?þu’þ-ÿv/€ý"]I¶g¡[jö˜gyºWˆI¯QŠ<éV€AÇyº?ˆÅ‘gú"g_"Ï?~,Û1‹Tÿôoù7û±Òr²=9`vpì*£>?ê(³°ÕÒ v¯dã}•ò_:‹lÏâú‘Ánå¯Ï˜Ezºî½-°´5?Ïó�Ü2ª‰®Œ<³pK÷fpé6véU*??êø²°ÛÒ¦v¯dã}•A}äY3¬‘íæˆ?qDVóF¥Ê¸hä¿Þ½\º�]z"O–ùK›Ú½’�÷U:£"Ïô3þ­péÞ‹ëyžlO‚ +Ø&ò,µÝ›Á¥ÛØ¥W©ùü¨ƒËÂrlK³Ú½’�÷U: +"ÏàÝ8ò”ådË%£8\ý"O­ìƒupTC=9òÌ~ {K¸h»ô*Dž,,3—fµ{%ï«tæDžé°Feä)ú ÀÎõˆ<]e�+à½ß~ßn·éF²/N~ž§~Ò®" lÿcT—¥c?_ÊÀ`ûõ«Œú¼~lT’€c U»²v,ZUG¥Ä~~_Ò [¼·=^ë—UÆDžÄŸ8@m#ÏÒ™¦ 5ýTû‰¯w­´»ÈSOK]c²O æÉ¸Áñƒ�KþÇjû‘Û@3?#Ϻ:¾Æy„‘­SÏ¥ÐMŽ<·'™>È~¡íìJa'ñ'M«ÈspšC‡7ÝÒr¶=]¥ûìÏ¢óJK¯Âl; ËÌ¥Yí^ÉÆû*…ù×yþ²J2©g_/¥M?ì½&‘g\Ùå3i•ÔeTC=-òÌÞdÔ½\º�]z"O–™K³Ú½’�÷U: +“#ÏlÏRº®L:»RÚì'�ýÖ$ò,�Ê餺•®xÕPO{ªRö­îÍàÒmìÒ«ðT%–ùKƒÚ=öGº7_ )‚i‘g©g)_Ýùó¾—0�bï-y×®Èl#Ö%òäIòË­BäÉÂ2iS»W²ñ¾J‡`lä?L>ˆ K÷ê¹æ¦£A+4ê×3‡çßðSh/ß ß?„&X&ò ª‰T¥å®f©¹·Ý½¾s®õüðüÏÔ| ÆœoÛÈÏÉà8•êÎÌz7¿jÇ«ïdǧåÕé¦jîmÛ­Ä‘gÍŸØW DžA©øÔˆ ÍKæÛ'=I~‡&·�ØUL÷*¹~¥ì +^´ÿÆíOúÀçšçyîä»éB‚O‚]Gž5ag)øìŸ³MØ‘î-ö +u?ŽÀªt¯’ëaÏî³#–ÙÆ$h[VÕþØ}Lc…àbáÈS^Ô°³ô„¥þù° Óö…ë<'dpœêçQõ®ãužÙ(1�ÁæDž5)\U¤ÉH#ÌRðÙ=ÍÀ»‹<³w¦gÃNyöÏ�Ý™ybm-?°NÝëfwiPgKe˼Âö§4¶™Ÿ«J60lG‘§ÖÙRØé–ƒ¬&Óª÷–|…ºJ`�º×ͽ“¶ÌÙ¦f�íO)øLÇpV•l`À®#O¡¿·^ª8i¹ m!»ï,Ì—�k^©óLÌÁ׻牕�9³¿s´¶”E»ˆ<³å¿ô²þ»¼¤¥#Oš‡dlT93òLWISÒ=Oœì¤aö…Ý“ +TY2òÔÁÏ# ;57Ú&m €G•ñìy}äY3±¾æøÍ…�¶“%øÄþ™y–ÂÎôâÏþ;ÛÊÎ[šié1Ïý½Ã(H¡(ý2K÷¤æEž¥bäçb;ßå£Ê=Çc ‘ç^tU¥.õ˜»Zì«]Gž„�qþLÖ}_`çDžƒÁç¾4¶ÙaO‚O쟑g¶šS•ÚDžÁTÔõV õà@ì4ò¤êípbhp¶(øÌ¾ÜÅI·‹C05ò, xÖ}D³1Ïì—ºhsýV}ÏÛ=©@Ñî"Oμ£ì*ò îxüj÷J÷<©Ï±4l¦óŘyvæËFPéK(%¦{žŒÊ4&±ÆGž¥Ñ~ιcí<ŒœÜíK·EŒý¶‹È“OÀzväIð‰ý62ò,…�xÀz[äÉöÆŽ"OJ;`UæOܯY)åû»G8c"O<Xƒú� zd¬Kuä™-Ì„�tA§Œ½4#òÜþ{¡„Ð#BØKu‘g©xyÐ 7_`ÿL�< ;èŽà{¦"òd<€u +"O7FÔ=©À½©‘'ž¬DÐM»Îº{R�Áȳ4àIØ ÀdÃN‚O¬Tyrcë—Žyb¥&Ež„�¬G¶§&øÄ•#O<ØÙÈS^'òÄŠŒ�< ;X¡¿ûåŸ}6ÞXŸBä<¥�È€úÙ5ÿøËÎTêÝÓ‰£VŠ<œ.ÙÑο_'ì`•ü°ç‰¿É½{ +qì‚Ùö_Çê³WxöO?øá—nÚŸò®þtFž…šæŒ‰È€•û{xó×[–šàS? ,b(òüsžøÈ³âÀ¯´×v‘§¾[³z÷½ÀÒh³4ÛÞ=…ÀÉFžõÁ'‘'zÉFžvOÈš3ìIä‰.´ÄÊ¿zÍ'€•›3ìIG�.¤ÄÚ‘Oý— ÀÊ•‚Oûn°tO?ŽŠ½¥ÝxvO¨aoÎ{ÆkuO?ŽŠŽsv°§çÜK«y¢%wˆ–Ûî õ& {y¢=WÞ;ØSc‡=‰<±œÁ‹ŠÓ§*uO3˜ û“FÜa„–â2¦yº_Ý{gì„{÷ãð Ÿðü¢§pê'Ü»'‡§>òdÌ€Ã0|Òãc!•‘§]º§Ì‘>UÞ¡ÇÇBˆ<86Œy¢—8ªÌÞ×=Í`ŽQ?iìPå8'wp0ˆ<Ñ˨v"O‘'z©Œ<Ó(ì)"O¬S|ãØGõ¿¤ ´á~½¨{zÀ®¤‘g÷$áÈyp¨óÄÚyp¨ˆ<±6Dž*"O¬ ‘'‡ÊöïôõX"OUy2æ €åy %"O´¡‘çöÏ?»'€Ô—¯ß¾|ý–}ëòò“¼u}ý=uyùI>vÿÁÃËËO××ß¿|ýöâå«î{tqñúË×o××߯®>Ÿž�Û·^¼|%o½ÿðñþƒ‡ö­7oßÉ~]\¼v ÞZHé»=~r}ýýÑã'õ›Š�Ni×ìZOŸ=wi¸ºúœÍÞÆ$…. ¥,º¼ü¤%¶Ò—¯ßÜv‚}ŠVðVcr@Ý‹¯¯¯¿OØÚýKå'Ȩiy¸´/_]_Ï6_R¢^¼|%•ÊÖǧϞËn¦5(Í|K�T*Û4�-Õ•GV[ÑëëïoÞ¾³‡cT>-Iº/ÙÝ”ÕÓ~G2Yyyù)(0õi˜LŽõ¨�?züDÿæí»øXhÎØzá²ÅfÎýßø(Ÿ¯LLp¸w®²jùðåë·f}±³žÆ|ß½ÿðq»Ý¦MîéÙùv»½ºú|ï·ßå�§Ïž[ÒÎÜððææV*ÝÅÅëív×£¥]]}Þn·¯¥QÚn·ÚJò..^¿xùêææöææÖ®uss+�Ëv»µ�[ðÖr¶Ûm¶Ñ¸ÿàáÓgÏë ||tJ»&kI³àŠÇ£ÇO$m𽽂Ïûnÿ½¸Œzúìùv»M4Û­×$»�`߃¢¼ÕØ‹—¯$Ç\æÈ!ž�ÿ²;iù 2jZ6+NÙ/•’pzv.åšÁl}”}‘Ý”µJ¥F,J^w_q}ýÝ6e63§�5=~r·ÙH(í†ûöú|Z’4JÍìÝfsyùÉõ;Zžå-Ùx¶-ªOÃdÒWÞÜÜJwYI2ùé³çAjk™Ö ÉçG�ŸØÌ¿¼ü¤Åìêê³ô×_¾~«éŽe³r¸å,-l»R_5Ò–Á)“w›�fE3ëiÌ€�0­7oßiñ+Bú1{"6¡ÛiŠmÃ"mŽüm÷B:)ó²–móµ ÞZT�á£G'Ø5·Öûï6ù[N÷tû2rÕåXK"//?iÚìŽÌ‰<ï?x(A‘ÛN°ï¥¢¿Õ˜Ìn¤ýã´ÈSJ…i“¥ü5-ÛÐÓ½.]Þ¨Œr±SÐ*ÞÜÜÖt)ÒinÜÜÜî<ò”œO+õ—¯ß´¢ÕçCÐ’XA[$éÉŽŒéqÑ×m‹*Ó0‡lSª@},TÓÈ»ÄKn¤Á¤;j:zÿâå«Á¯È&ûn³)Í„ÎT_5,91±¯HÜÒx˜«{u`¤ŸueφôA9=;·'G¶Q*Í{¾ÿðÑ�ÚWt(þêê³®+›’³¤`”>”Ïkkf£3�iCªE:xKfouì]^Ôy(�~å�a”Y!�$*åªTÏtgm–ÚÙ‡ËËOÙx88:Á®¹9Y›u®²ëFôÉ�»Ó³s=”zPd,ER.ç°:7”În$‚JÏ5æGžÛíön³‘6ßn§´ïAÑ +Þª,õ_N í¤ëË&Gž.7lÕ+eÔ´T¸k4g¤•×õ˜ÊñµÍˆûRíÚ(Lf[f×2PyJÙ¸ÛlË›ëû¤`Ÿž�§5H¶©…ÜžÖ$IzºÒ襾’žOé+ñ[6‹¤›K“g«j:£Wˆs“hWWŸeã¶)s#ÿñõŸéÑ v-ÛVKó’míåB#»–^ã²Ú횻ң4¦ì�4¶Å“&ÅÅKú½’W)Wn]b‚}ŠÖà[ƒ£>cc6>·ÑT°‘ £4“¥TØkó É´»±Jwõ…ëŒì.ÈêõßT�ô“7J³#Õ.[ì8§ô†.Vtã6îŒÁ"´Q˜Ìuß®°é¼@z¦’¹-éÔk�=7ÔK8ÒÑl×gÙ“åÁj«çAòï‘§›çr]€�<Ó/Í®eW·£Rš3¶îÈ\[åÕòéÑY"ò,Åi.ò´·ÿÖ·ö2½%» ~tÉÅKn^rP›Ès°`Ôgl@²È^’Þ0RŸ3š ÛDhŸ»PäÙ&£\ÓçZEÙÈàýæNéòÑtt%�¦ÒWÜ…y¥fgrår[p·±KJêóa~äémh'1¹KÞm6"Ï™ÙåÎMä_{åØ#OHI'åÇÆ{¥�_^~Òèîn³‘lÆ–Óþzì5´AÕÈzñò•Ľ’ÈëëïZ ²á« Dß¼}'g1Ò º:"Ï^ì8¤ BÚ•½·=ØÎà àÚ€ØïM/é ú¬¸ÚjØœÔÄ‘§}œTÍ[;�<ûn½XH‡M*�²^-PÚµl[-±\6a.$^:òL˧ÄÛս핑ç£ÇOÖy¦—ñÛøjräé2yl!™–‡KGž6¯Ü…Ù{ºã~SúÖú«—]š^u)UXO"–Ž<%Àp“õùPjIÒ½.µEÙmÊtL¢wšP“†ùÙ%™�a‘¹´‘É‘§„�i„™^è>“ÚjewPò\‹„]wTK2¶jX§gçRO%KwLÜðÐv‘n[äˆ<;ÒQôô>²Qu¤¦‹×€ÓžÃêE_ú±i‘gvf¯?—ë¢õÁhö­»ÍF6¼µt䙽k&͇ӳs}ìIMû£ßìš»ßV/*Kï3•Ã7ªßŸßÚÛé-› {õÑœë<³‰ ö=(ZÁ[-ªtTDš}© s®ót…V®a+eÔ´6!ò Z+nfÓ[_í�éö»²Ã¤AffWöZ÷¸§à"±ÊÈ3;ã3…JÙ+%ìCKÒa™æN/� ~•Æ‘Qagéžw/e‚‹Hí ‰"Ï �ÂLÒŽ¥�CPGÜuÝ¥)�ÒŠ®¥M/ݱO§©‰<³agºq;Öê†f‡Ô‚·–Ž<µnJ>¸Û7NÏÎí³›JÏy ŽN°k6,‘HÞÍÌJÂìšEžîE¥ƒx Ͷû­à­f•Þ¦çÈu“çŒìÍ2«å®©ÎfÔ„dï¸ÔG9ÉgJ'_¥£ïµä¤ ÛºJ»ê¾tç‘gpž›ÞÝ •*]gguIv;Ù}�‹Vé­�TÙGj-KsLÏn²7ŒÔœË늲wö`•2jB¶É(GŠ“ëVâîI(ÑBš¥’?îi�ÒKº:XüL‹</UžC%í�û´Æ8\ö–Z—†R[äz»…Ó³sÛHºûÜíÆKi˜Ù•îË–3ûœ@ œÜhíוʤ>ë©T7ë§ìƒ»á¤j¿é¶Y:’ERtÓC�ýº¸jƒþº‡dEÚ—Ù n5ýR¨4yRn³·9Ø"ÔŽ �Ç¥‡ó ^H¯s5[Sz‰Ž#3J."­y:™›ŠÊÞ %_šMUpçTv;.@u½†$/½}Õþ[zŒ›¬.{äƒfû„£3¸×²V6yƒÈýk÷:}’°}°®%MM¶(ôNÆì£°ÇþÞ}v;é¾×­ô­Ê‚1*c³‘gœc§gçÙZS™W£ +É´œ-fÙJWÊd·…ùmQ©áµße›qyÆHºz©ur?‘™ÖÍÊÇfJpHêw…!{f$o¹üÉ&,®¥|NKH):ÍþŠ«Ý)Íg—á¶4ÆÅ;nçì;Ò;JÍ/å¡{FÉRßXCök -rÏÀœ¯þú�£ÊgûK~U¤{2öˆ\ÚÔ=ë×7£ÞøØ}’n iØ£<Œ¶hç¿V¿óÈó0òÀ^c:ƒì:¼ŒZÃaZCök Ç–È“�O:@>4öÿìH[ +endstream +endobj +96 0 obj +<> stream +xœÍ\Y�·~Ÿ_ÑÏL±Èªb ìÌîâN$ÏQlç°lرÿÀ³‹ìžî‘± íL7�b_]\çcþo±‹]>3ê« ˜1ÊòþÃåÇ v´@ Å9²Æ¢Èâ°áÝòÓ×—¿ünùþòãÅp”×+ŸÚÌÏœóÄÆQZÐ`”eýçO_,Ó“Ÿ¾½¼ùBüòí.o®ßýðþß_ýòÃÏ_/oß¾ùòöûçÅ.Ÿ~}¾]-bC\˜ÓßDÅ7» ‚‹ÉëªoeMLk¶y¤f¥?yBú“Çæí_¾¼]Þüáoßõ½¡î�²笡åÃɌãòH¼å%=óL”‡�Œ˜ž‰�]y†D¼¼O³Yœ¸Pž†�F’u y¤3Ö;—ž9�©‘ ÊMv—ÕÓÊÒ4.³!ÇØœuwzõgw¼RçßaçG¥W^éJømR¿ÉÉn�ì¡Õ¸$ê×ìþw4›µ0çu“”¬[›«ïžT¸Æð¨[âS=©âV:5×�)i¾Ž–Ø,»Æj.c:·�Õ¨.¢ÅñÒ‰ººš9g–ú-‹µÛ±ÊC+Të÷C¿®ø‡´ªl‡‹‰yw‘ac¹ÇÆ–E:0ºöŠ'}Œo>qaþ|bXÃ:ø£ëÚë6²®˜å�÷Qðã1«Š*÷”,¬>ûHtj¤å¤Âú®"Õ²ÖRˆ½5µy@œ±ag]¼»®ÒÍ>¦eÏ`d?Cyȸ>è“+]s¬aÔú3€v¢"ˆë +!^hµÒWWõ~² {`üp­íêtÙq¼6:ÎEŒ°bÏ]�UÊ•iu_]~¼8pÒ›TŸ!Õ>U(ÁE›Vï?œ4…^ÿùí/?­=)h�¡7�´<ÿ�÷z¨v¼ÍŽ3*×b÷F›M4OCÊk…1~ë^¤ÀF˜cœRõu†8‰°¾Íb-o­‘@10¯o»2—lˆ=#ºŠ—ÑxÙÆ†4º8gØ2ªª:•ª:¤`=ø‘øm]Þ“¡€NosV—ã'–³oDe£tÀÎÇ)gMyÚ‘_R%‚•TÈ�V'»ñÆb‹aØ`(Ô犨PyEÆO/†°Q©ÆèSï±OëTJ­Üiî` +}:F«àqnDø'…}/k$Q×rf“T½ê6¢aCdx¾[›:C46xM‹Qs0×åÇÞX�0UïhgÖ�ÛêW\0oÀ[¡8˜M+%v%Ó턈Ùm<ï;LÔXÔ¶�´v¨¸Å&‚aëh Ö†xY‰0qxMÉ ªÏ7õ9)%MßLJƒ‹Zµ¯j*φïOê;OÔñDUhTÔïR׫±¸ŽÃ­š—-7‰ãZÙiWå>[ç  ò°µSƒ±|䇬=>`í}Ù5;¨Ja"X }îYs±GôÅ¢ ‘$*›Š­�M‡{÷î8›†TjÍŽX�ë!¸W‰Ù+!Ä +|d¼CPòhŠ£L<ë§­)¯9À\KºÜÞŸèÙûà<˜Òáÿi‹T›dY·ÞA¡�} _äܱ4)ݶ)Ê߉j|ûµ rþ¢;EMÚ°ÊëÔÀÜIpæ} + ‹>®'`%áI·îK÷Œ’ã8Säsmv%¤7̤ÀZK�š�¯©ÝÙæ'mE”hh$¶Œ™�ÄTèŸãÁâäζ>î9’C˜x€6³¬.³ÙnäÌœ8(oQþÚq«’k†rr�#™›Ø8éEî%FõùvLªÓ7^nÛBán¹¼½S™yþÎuZP‘’]Œœ{›9zªUêHŸZñy]MoÆ›]BþwMÑ… +ç)U‹¼ëªN¢d®w–HÚ×[T:ÂÜÃ�ZNÂ`R›Võ˾W9ig8;¡1±½­`\Ó.�Æ¢‹<ûÛ*òž¦”mÁìØ$Ù3ÖÇ2™s±—ºoÊ·�ªÕ¨2]æLíNõZ×�?ºÿÕJGýùUeéíøvufÝÏt[ë›–R»Ðmêm¿VóÛX_w§DŸ5hª³j-;kÌ9ÆDpª$ÓN˜õWê¨rkU±=mº§8£Éå“NUF]7ŸoìiÌ'½÷ú o9ñ[ÑŸ÷mëØú÷¬�27`ú>HÇ£ªOôÎÄëÝŽI· ë§n†FæS ;nçfÊÖ«@Ú¿´â:7°` �]ÒQY'[“I¦jI‹ŒZÚJ+*RT•VÍe;ÚÖ^Üí¸y:§lrn<·z +Þí\¹=niåtÃ_«{¼õžÂວkó´ÊkÀy®žíZÝïU™Ø5¥ƒRõXWx@={/f÷=–¶jD«÷–:ý±³Äùt¤Â©ÉØö.oõêMîÔ:JyÏ/C¬.£¥aC%FÇ� +a¹ªƒðÑ*4/¸§Ûy£�ùô´&b¥Ôs*f±ýÀ|ÿ­Å”‡JZ/©³qΦÛ6÷Ž»‘óTÕ:Ó^íH2\ªcöÒŽþm.6§x«€Ø©â >`;?»ÞžÆ—b‰TMff×+²VûZ”µÖ�•!.œ¼åìGr`^‹6ÖZqÛŒŒcK„êÙHªçÓÿG„ªà·Ç•¥íà [IƒÑϔծz^DŸSÈÖ)o…V7Fn; ƒK·‚�¾%Ñ )¯uåÛ™�Ôp0)PÔ +Ô«§ÆhÇ¢E¶Ê÷lïã‚MË 0EAïðMw|7!Ô|÷åý!Å%¨½_FÔéóvêèÖoÍ�t�®ƒ¶ë6à§“;Í;® +{O…vÔ�fH8ú¤½BgJ»£ž´Åì!¸™Š +2 +>ªº!LyUIo}E÷úÊÌæí¥OgUêYh�!qÖo ¨Š³Z~D“ÿ)¦Ç< .ßj =xÔÑ­¨È *úÕ©cãAȲcƒèÔóZ�HõuBô#ÚÃh‘tç’|–i\YÜtî3ç_NcãÓ!¬l\'HÿÝ®ÍKqˆéúp#¹n—û–‘ì +Å@wfÔ(¦û­À°æ­égbLwˆU”W$�c´,ª~w^´ +‘¬�ú]훇 <Ç­ž#2Rº=¼Ê¤îA‚v¨÷7—º9±e–ÍŒÑày8“~ɳuZϘ£óÃå‚f<;‡6›íü䟃§ù7Oh 9Ã)¤½Óúv.�W àñ¶…¨<æ6V¹4D5Ètn½;”nô$Ž:Hww<Ù|‡çþo¿§Og7|²Ìx‰cÏ"˜Òqtý [Œ\odh=ͼHH—rr$ÐWÉ]ËÖ°­ss·–¶óÏëáª^Ø Íÿ7°½z2'‚A—ÊjŽí—"Oæ¦[,aÜ+xк ë‹*®ÿæA@ +endstream +endobj +98 0 obj +<> stream +xœí�ß�Ç�àù: ²•Øp`9ë•mXÛðÌzù×Ú~8äåþƒ4`ÑNp‡A´A?,¨·ŒXÁŽðÃàCd[ñeéWfv–¢elfc`ì;àaF<BÝÕ]]ÕUÝl6›ýƒýùà ‰¬®êœ®þð[ÝÍ>ñ•{O|åwŸ ¬ñU‚ ˆFéÏ*†Tž÷ž¨™Kß©jwÝMÍ�»Ëв;~Qº/ÒÛð^U‚÷}íþ‡ùÆc�?ñäÖé3O=]ã8CAä¥Û—ˆ'·N?öø?ò�û¾v¿™Š’S=òìsÏ_øÉ?½ù×ãßÿáàÿþ?‚ ‚X�ÿþ—.ÿúÂOÞ~ö¹çO=ôˆšŠ�?þĹïÿàêoÇ¥¯*AA¬.®ýöwç¾ÿƒÇB&=õÐ#ç¾ÿƒÒ×� ‚ Љsÿ™Þ÷µûŸ}îï®þöw¥¯AA×~û»gŸû»ûîsÏ>üÈ7.üäŸJ_%‚ ‚(2.üäí‡þÆÝ'îyìñ'Þ¿üëÒׇ ‚ ŠŒ÷/ÿú1ï,á“[§¹:” ‚hZŒÿ‡'·Nß}âž3O=]úÊADñqæ©§ñ AÑØð<øUðü /ª¦S‡æÄÁßêÁ—ξbŽ=ÊBi¢V«%f¸wåº_}þ…Õ#³œ�(.�3‰ÌS¬Œ(Œ¬�Üd±PõÓs[qòäêÀ£(yçÝ÷D¡l(?1ñ)‰�ÂÚV¬­Xy¹ Èú¤Ü+LFòÊÈÒåJªid{åþi«nNúŒ• "ÇL/A׃gRyPMŽ"Ɉ”š80FL§žÏÇØÈùA1Už‰S›¨Çð—ξ"½ ¥,ÄœÅk5Õ’ó‘â0ç)VÆ@ ¹{P•nŠ$‰ y0á’« €ÔäîAí:c4À6.jõ ¥­lcó µ>@ ¹{Ð~ß„Yd¹NÆêAûm®í‚ëdtpÛ¬«ð @]ÀƒÐdð 4<M@“ÁƒÐdð T™³�¤ìOÖ™²÷îrHþLð T™³gÏþ¥aàAX)ô)<U†> �/ô)<U†> �/ô)<U†> �/ô)“uöàngc£SÝß�[½V{·³±ø¯�»¿ÙZ¹ÏËÒg/¾,~;öô›Ÿhï[/_ŒÔH*©.9x°â{~æ5LhµÂÞ$g´¾}ê/^ zTûÔ*<¨íxiöÃeöÕ ]C­`>ªpåjÞ eyÖMs7+Û#7ºNÕzm´Ïº�Oëw_ößòæiñ*MI•QûìÂ]iÑšÈeææL–e¥\†%=(+ÌýºW.Óñ²w÷é³êwV¿@vÂOÞ<íNLSRiêÁ%[­Öƒëܧ|®±æ}ÊJq “/ñ %·¼ãˆÇÓ‹:ZzæVŠm¢ùo7:»J{o‘–Eë;³uÿ +õFó·b­ˆ^Õ)ÖÕ3>�¤M3‘]Ö­ã8­à#V>Ukyä#q§”ÿEVï³r(FÇ(ýÑ�¦¤ÒÌ÷`¤_ÄìBú™¢NäkaÐz\ü<õîë϶Ì©åtâ—nöV³Ë¨ŸIî½Éì\ëܧ¬\Ç>ee%lé¸yåÁ»â둚Ev<¯Kl¢í™ÆžÕuüúþ|”ÃEÒïÕ”G€pQ¡…cd®Ø�í­ú˜à¸ÕKþ«mšK§“')ÄÊÄ•«ó¯hŸÕ¿…¦í¡5º’uŠî�)êX=˜ÔãçíAzZ»t³·š]F’_o²|Î!kݧð �œóÁè¤Ì•:áTãKZBclSûÚ§ùÓ¾B'{ÐÿîSÙº „m‰›‰uõÒošíÃ×úld>qG†¸õ¯±ôûíš�á¤íJq»PšÝ,nÎq;¶%q³Í3Òƒ¾‚ªËJ³žÉ3Y¾7]`­ûTã¢6Jò ò¥qQªÒ‰ô¬¸�0q\ÔCéËs”°-Ö%Æ­^úMKÞ¦l}VùB]ŒóƒÁÉüàôüzŸÓ_¸+©5ãZ¥©“Òƒ óô zPzÎ]Ïl›™¾7åîÁj÷)Egëݧ¬u~Ð6ÈiìÞº9R5 Û(Sý=ÝXtCyáyŠÝŽ£u•4 JØI8\tGcõØ4æøÕ"}V×xùßb�>ë_Ú^ܶÖ×x§êJ »�­UlýKhì@GXmÎ<õdŒ‹ÚföV³Ë$LÊ£7™�kÍûTØ©”ÓîkÛ§¬”{�ŒÙU[‘:sšÈoœ�]ÙÞ=eÙ9ÕauÝZÆceÔ¯us”°-’`ŠßÖ\½”›7Ùi³ôYóP˜ðw.‚†ßó›ª+%îB¶V±ud•øû@Ý-“æiö ¥²¸Nƞʙ½Õì2’¼zS²éS5§Ñ÷Ñ7•õ¾×iíá÷d*}ªÖàÁF²ö¿}±ÎàÁJBŸª1xj } _èS&xª } _èS&xª } _èS&xª } _èS&xªÌÙFRö§ëLÙ{w9$&‰ü +€õ@“ÁƒÐdð 4+Q*O£?ýæ'^Ï£÷Yƃʣҗ> ,Ú‘­õÓ–JFöÚâƒn÷ò·¨Ôwì�N7aÊûñ«Eyp�8 V¢¼ør`²‹/{&¼ø²ï6×�Þ«4%U¦æØV—ÖÀƒû;[­ÖÖÎ~Òqº×žW–Aó`ú&»NâgX„—‘YîÛ +!~\ô“7OŸ~󩸠à/iJ*MU<è�µhéaXâÕö:lÇ *„Sƒá–„úÆkoéÁ°T«åtd…–¹€ Ç WX4ß5ëÇ­Rž*�zpgk«ÝÞJÔœ«JÁ,Ìf³ñxl–�ÇãÙl&ß&y0vo ÷½poQ­Ù™Ûá­ûäÜnemeì½RƒnCÇÙh�*hW®o_î]r$Öƒ¦ãü×iJ*M5ÆEÝÂè,ÁWÆ®£ö\q0 ;TX?èZ‰õ#¯ÝúúWSµ‚ÖÓõÖ™ûõÍUZ¹] îì‡ùžx¯N“C¢­¹i#D¹uëÖööö`0P ƒÁöööÑÑ‘,‰ hÛ[÷.sg^t‡7öÉTÝ*lYzˆEdòÄŸhW~,"\!z“ªßG_AV6ø£zP·›?4*“Äð2R<¸bŠþ=™²‡¹píÁƒ&x0%ÊöU§ø=™ +RŒ Ø»RÃïɬ9xÐVÕVne¥F 0Ù :[ûü#õ]«ê‹HÚhq<žíyZBä~Ñ|Päz�è¬"SS6[F9ǃsKb7DîTGýÈX6,Àƒá(-€�Ü=¨�g +û(cœîK¯ÄñEä9©kjεz0ùŸ¬>]GI5 Ñ4ǃJ:ˆÀ§90ÁƒÐdð 4<M®šÉd2�†P£Ñh2™Ä€WÍh4:<<¼%qxx8�âŽÒxpÕ ‡Ã²w€¦3ãŽÒxpÕàA€ÒÁƒ%‚J'³ñ‹_,Ôj>»���N>¿í™ã¬V (�Â<¸¡>rÉúóesåe­°da©,áÁý�­V«ÝËwg˜O¯Ýjmíì½X€ÕQœç:(›Ó׬º]ÇxH½y²kYÌãNh÷¼²r¯�É‹Á2¬ËQ*Éi½6&€u"³ÿë?üÃB­LívdŽèMó<Õq”Œ1,qºJõ Ç ËC»…¿–ítŒYÍwåJltvÍEëë'VÐ+ÉS¥ª¥Æ|ÇyE¾pä‹°®_ e± *¯Üe…uâ“=mVá +T™Ùl6�Íòñx<›ÍäÛJŒ‹Š§yÞñ't�–ГZU*O-÷ Ý"­fdV‘榣õ]«ê‹X­C�)nÿG…¦edÞ›žMyó=h¤vA“0ItßêïR  |nݺµ½½= ÔÂÁ`°½½}tt$K +»^Ô>P©&\V=©­’Ef­im•Ü<®$îcZ»õÔЯ´hÑšÆÍÕ•¥‚·h%áëµ½‘š‡`Šf:�ª*œN§j�Ò<¨æVidT°eY¦íÌó <Ï’b´S]´˜ƒ²‘š$„P¤ +­\ƃ?ÿùÏjeñ ¢Á´ù <ו”1.jõ`xªP9hÏþ–5.ƒ>Ý3—õÚ•%ÆEƒ©� É ÎZ%X¦•ëZü Wæz0hb¹ÐEe×ɘٜßÜ?˜¸hý)õå\'‡¢¯ý�­àšš¹×ÉèÅXŠ‚Är§Î^ž/ gO:µãÐÃ:©0ÖqÏ +˜wßD�Õ|ÎÔgâå5j-iû }8²ªÞ-¼åzQX+ð`J:¸ .q}å5˜Éýƒ°^àÁ)ò÷däêRð{2°vð;Û%Âï‹”,<P:x°Dx=@¹ð<úr™L&ý~ÿ2”D¿ßŸL&qGi<M@“ÁƒÐdæxð›xÖ™X~³fœL&£Ñh 3�²\'S7r{XÉxßDÝ<ÈíêG–ûèñ ¬ xš €&Sœ•çÅÛŸé®>Y>ϧ¾/íAž¿°¾dõà‰E=è„öë:ɶ]¥ǯ§ŽžÕƒÈ`-(Ѓ†ü ÑÓž¯?½4RÇoµ˜&5Jy¥¦;¨!³Ùl<›åãñx6›É·…y°ëx6 eØ Í( h5êäèA÷Ùê�½§»ëI¢ûäuA{ÇUÞNðÞk£5°´€²¹uëÖööö`0P ƒÁöööÑÑ‘,)̃>B‡NW=]è Òô Ygû…Ø=ØkÚÒ…èê¹¢ç9ÿ­l%óA¥n¯�.Á€B˜N§ª +…§Ó©Z§hYžyÐêÁÖÆƒÀƒÐdð 4<M.Äd2�FC¨£Ñˆëdò‚»3jGCî›(îÖ¨#M¸�¾ð @Áƒy�êÌ <PG +ó ûXy§+ߺOœ�{ª¼ò`ú¥�óÉk†‰,ëAžfPÅypÃq¤�\1mĺ)wmîÁðéöéŸQoõ rX1z°Ó tÔuZNGIÖ|eˆt1|ïN�LõŒÖqü·zU㽯¿¸*RŽMM@ó ”×þÎV«ÝKó—Àƒ¹2›ÍÆã±Y>�g³™|[¤w=v…ž|I¹ã£¡ÿä+ßKÆTÏbþðjØà ë8]1óƒƒp–qQ£Îª=x§×–t�I{í olï¸ÊÛ Þ{m´–ÖÏ­[·¶··ƒ�Z8 ¶··�ŽŽdI±ôÄ&s9UUª×¬çõÄTm„3œÙ�– ù£9³Î¢›ƒÝƒ½v -]ˆn¡ž+zžóßÊV2TêöÚéL€Æ3�NU + N§SµNÁŒÊn) +š“¬\Ù‰BûùAm€T¥Ý‹ŒyªoUý…mµR±@€T¡U‚%xP’<.꿲�‹*ft”Ën:»ae}öjÕV4.êº+’*,æA6X¡B«+àAó:™™ãÅ]'£�‹*—¼¨Ãžz²i›¡zuÍÊΆ*T¥(0ÆE­ N¶ý‰\:°0‡ÖIÜGŸ1×ɈQï½:4jN»N&êÁ T® á2€<ÁƒyÁïÉÔ<˜x ŽàÁ¼Àƒuæ¨#x0/x=@íàyô92™Lúýþe¨ý~2™ÄØñ 4<M@“ÁƒÐdðàú1™LF£Ñ¨�‹ÞÁƒ°&HZ%X’ƒ1IýLY4Üí8÷„#¥Ê¨æ�R%Ô«2¶jÏþâÕ9?±42Vsp5\qK>¨­yÉç\çí0. +ë‰P¡U‚%Ý7út‹ÇÈûÔüÉrÿ‚‘ ʉ–ÙÊ×ÊÍ †‚BF†`ÝښŬxärŸÈ’LaÇý)rCõàþN;𜙺—’’ ÀÚpèa�”уÇ+vý2Ø'½¥!Ëgé÷ê·Mè÷M� +@Sh¬#I›ÁBw9.®A~O 4ÖƒÍDÀƒ�DÀƒ�DÀƒ�‚çѨd=¬'“ɤßï_�~¿?™L⎙xš €&ƒ ÉàAh2xšLf¬<°ÞàAh2xš €&ƒ ÉàAh2xš €&³ŒwÏo¶6Ïï®`í¼e¬jÖ®¿œÉƒ®CCížwVc+<«$³k +¦ˆÞTÏbç�V«åtU£ÉבúÑc2@®${0á÷ÙliZ×i9]ñR˜Îó˜_dñ µ¾_Òu¼F>XøÏÑÀš�̓qÝŒ$p�õbr@Ý’Z}׌-iÎh+€¬äîAm\Tz-⬹´;N±!€<È݃Úu2á8§šËîS†H[1õƒËm‚–J+€ÌäîAû}‘"=›“7ǸN¦%͹0F‘À2ð·W;8žKzP_…˜ùÇyÐ>.ªøU Kð @"U¸°´çö*³�?ÏØ+Nõ„Ý8ª—åΨçí´]'ÌR®st+ð À?öÚµ}xãóÃ/îÎç‡_üøã?ûѵ\TxóæÍápøÇ?þñ‹/JØ–\øâ‹/nܸ1Q!Ô<5å[?ï||óÎ�;_úÒ—Êú÷‡üÇ™·>Y~[ö÷÷ÿô§?•­²˜L&ׯ_ÏãÏ PxjJëÕÑç‡_%•Å燷�ýèÚòÛ²··wûöí7$/nß¾=óøó„šÒzuTî1_(¸õêhùmÙÛÛ+w[rdoo/�?/@qàA¨)ÂƒåæƒxÐBíÀƒPSšîïlmíì'—,Ä’Í ð Ô<5%C>¸¿³Õ +ð�ýªzíV«Õî-vØ_½½µ–+‰òBMY4tuð]ç¹ï¤ôÉi((t×°Ý–®Âƒyƒ¡¦,˜öÚ͉ã¿ocjjVíA±‚áúùZT’Z{ÉÎŽÌnÃ4XO}=Ú;²r+L‡#MömubÀƒP;ð Ô”ÅòAKÖÓk·Ú=·|kkñáТòÁ`½¥•¼Õi/‘[äm¦63wr¸½je)FK�Xð Ô<5e±|0Áƒ-‘êd0á�•{0\í@?ꆡÍ/QѧÞ1fX/¶É¼qT<µBMYðü`ò¸h$GJEù`ÄHÚ ÍôL>Ÿhõ`B<k„š²èõ¢s®“Ép¡ŒÇ*=¨ËY˜\Ñ�?9±Dl©nxc\4ê8£ „µBMÉpÿ š^…„˜rLÇÊóÁ¨q<öŒ{?Ì»AÌŒ/¾‰¸N&ê¸H<k „šÂïÉT<µBMièïÉT<µBM9öÚµ*™LÊ–Xܸqc?�?/@qnþ퓟~šCw(Œág7�½víÇÿ¹¬çÑÿðƒÿ8öÚµág9<~]<�þÆ�õÍ +oß¾=™Lx=ÔŽO?oþí“Ç�Ÿø›G{ÿýÊ^€Å~vó[oíµþÇÿiý÷� +Žcßÿç3;ÿ’‹7oÞüÍo~óá‡~PO~õ«_ $µãÒ¥÷ÿæÑÇŽ?qêÔÃ.¼UöêÊ… o�:õðñã'î¹÷¾gžyîêµke¯@A\½zõ™gž»çÞûŽ?qüø‰ÿú¡ï�;WöJÄ÷Î�{ð¯r%x—ëÁ/?ñè£�ïÜ9²BXo®^½ú½sç}ôñ/{™ ëÁ»\~ùø‰<õô3Ïþã… —.]â +RX'>ýtüþûïÿã… O?ó샞R%øÿ–q +endstream +endobj +99 0 obj +<> stream +xœí�MoÜFšÇûø°qâÙd²˜‹c{ŒíDqz-�#ä˜/ÑõiwØžCN‰ù&A{³6 £,�„Ø–µXHR´BC$Jl èŒpœÈ6ä1²Û]äÇõÖ$›/Möÿ‡BB‹UOU=ÿªb±E¿qìÍ7ôáŸÓ¯¿�€Pª��j j „ß¾ý»“§Þ=söý«ç‡†. " ô^ºðaõü™³ïŸ<õîoßþ�*ä'~?<üñ�_,..>|øðg@¯òðáÃÅÅÅ7¾þøÄ‰ßs!Ÿ}oàҥ˛[[yÛˆÀÖÖÖåË:ûÞÍÈ—/ÿ)o£1¹t鲘�‡‡?ÆŒ @qÙÚÚþøÿŸ�ÿr㋼mtÅ_n|qòÔ»wó6ÐwÏœ}»Ö�‡~X=Ÿ·€º�· €€–ènÞ¼élo«ñÎööÍ›7í÷ýÑruzzzii‰ÇLMM-//[nY^^žœœtG{ÕqœI†)Y$¦§§í&Å`aaazzšª#H$ç©©©¨µž��]j355ò–¥¥%a¿€ª#Ý:;;Ë#%´êB‹ÍâXt:¿º¼¼¾.&&''EIuM/àlo·<$(gm¤Š]Ë’K,//[z?”©ŽÓ}'J¤¡eáÜZÇq„çtY\l-Ké÷ðééi~Æù»Ô²ÔËÒ¤²"!EZ&-«Ê )äŽZ–jvvÖÔwaí,‚–Åœ%Æ.q�`qÝh9ÒXÊ휚š¢Ó�™t£å¥¥%)žšTrIÖŸZæú /ä0Zæ#ªðC¾ì—DO ‡áj]XX�–ÓZ-ÏÎΪëXŠ$˜œœ:mÄÕé6äu”�ˆÎF™Hy +cD¤dUYÊ[Cä j155EfP---‰Hº‘ZL´’h +í½ÂZa<$Ù¦[¹ÐDïЩé@ØC½C­:==M‘ÚæU‹VÇ+¾Ìæƒ 5‚êHT–è,jU2†JÑ.¢Ä±(‹ú�¬¢rÔáº*/ä0Zæ“”4žS‹‰v–ÔÊŸï„3KÏËâ*”£[¸“ÌÎÎ’oS$¹“ÈYs¢|È…Ô<…1êbOš’„_QŒv¢Há‡Ü{Å�0Œ7”ö^ò+1&ÐíÒpjï2jsa‰ +蔊ãZ&«hèÉfÛ¨E«ÝÄËÕªƒÊ¥¡IZ9¨í£•?Mú!µL­G²•*Õ?Z¦¶ZXX �’¦'¾‚¢f”ž‰DÇ©ó²$ ÑA|X& k{–ç<Äqîljž¦¿X]¨� z M¦ñD·ªZæO‘–{ÅhÆÛ?ƼLû¼ ~ÚQËb^°´•VË–ýñ„ PŸ,ZÖ[˦{�ôö¾hé«úžxÈ2iYZc‹cÕ%¤5¶8–Þ¡¨gùªUZcKs“䥦g ÒŽXšÒí´Bãwd�IËô|Á×�ï¥Hi�iŸGtoþœÒQËËË˼ÑìÍK%ZvGÅøÀ×ö-2jÞ†êUºD^Až©Õ2/·×֨齓"¸;ÑâPlS˜´,-�Ô«mMðK|I,u·�ï}ñu � Ú�+Ú@Ój™WGÝ¡E‹Él˼ÌÛ�ï}Ùï¥í2¾÷õ� (¤q/¤–©wø~£©yí“25�¡ UG¢¢E ÷%Ѫ–¥v6i™—Ûk{_éýV¤oéÍבÞ/gO®W;byE^8 e-ÚE~¾Dú݃´n):Ð2è7Ôåz9€–(Ð2åZ @Ë”h€r-P eÊ´ @9úã@ñ„–(Ð2å Zn6›ëëë úžõõõf³ùDG!´¼¾¾~ppð +€¾çàà`}}]+“Bh¹ÑhäÝ„ô +�FC+h€b-PR×òêØÀÀØj7 Ì@Ë‘´ì‹nul B'3µJmÆ$ÉÂhÙ™¨V*õùä2y3_¯TªNÞfdE$-·4Kú`'f1f­åù:õ^[�/΋Rz¸uÁ2»¯��Ÿ·° +ÞOLe´\*x££Í!Gt&ª~¦í“ù(µ#ûeËìÍ»ºn€ùzÿ¨9’–g<1ÏÔj335W‚â@H²ýß±šÛ­Ä$Õ™ZELå­9]à�Š�¹>Ž–]`òÅëQò8Ùý.oe½½>áüßKêfÉ ß +7† ÜÙì$aŠÃµ{¥iäÍ®ÔWÓ&z h}‹/^ìîîªñ»»»/^¼ ÓHZöd·:V5ì œ´\ñ&ï™ZÅSw;Þ¼ë\þ‰hù•<ï(±ž„åT¾Ïè¼'ž–ÝCräWžˆYL‡œƒ—[gõzULf®€\ehã;f*Z#¢–õfÛš]­¯Ú&†¼¤lcµ@ŒúõÏŸ?]YYá‘+++£££Ïž=£˜h{_«c­…µøoðDæ“€º+\¿��ùA;A-û‘ú5vàºövÍ*”V‰¦x‘ßÖx²¹.´b Ç@‘·)Þ”­W¦ûÿhµ34²­Ùµ£™Ô&ª‰º5v¼°¶qQxúô)—³òÓ§OyšˆûØíYµµÂº¬�­zKm‹–ÆfhZV•›º–;/ÔtZfª�2²{ŽÃvÒb ÎË6?SÌ&3¤S|�‰ªgi*ó2Ù¯«¯Ü&û4»‘ñZ s’³VÈ1ÞIÍÔjµš«½™Zk LœXµ¼ê­¸Åc³7IµœÊ;Ô—ñ ÍöD©›¹Ô�%�ì±­KýnµÜ^\Ï»R6$îr^6Ì‹aKÆk�RÌË!g­�ch9 8þtÜIËþæ_f§ú¼ÜƲÁ\/pSäÝ¡–\Ç‘7]üŒ5{_ò6º®¼8žlÈ«­TßÎdçe¥Ló^Kê_3·a,-—eZv9h£½T¦ß}ñ…›XDòy¥ÃLؼVÞI…÷õ}Š7ű<”72Ìi�fÇñdÓ²$üþ›¹~ºgC³+õUÛDÿR°û§Œ2ìc‡¤LZî†rvy‰–—1ÁûåþÓ²f§¥øxÛ[ý +~÷Õ¦ÿ´ @±�–(…Ö2¾+€ èßi6›÷ïß_ ï¹ÿ~¡¿÷è´ @9€–(Ð2åZ @Ë”ƒžÐòêÕ>¸ïO›c2ã~Þ(ðW™Z„mi[˜@þ«W?SŸnK¨¤Ô­ÉØ ÊF -3¯ ç‚–TI+E¸›ÀœåL-¼ÛDµ0^]ºn�VÅS²°±V“íìØ¹á ˆÒ-@%†–ý +ÙY“%¤å–?óq¦fö±ð~_ -g!ƒ™ÚWg¢Ø¹R He&†–ý.jõîê“ÀtØŽh§¸ZkØà5]ÊUU/,æ*ÿÚ§mê5ø³T"-¯Û3~fl€2Y¨ÉÍ\½öæ5‚æFnIíª—?-5�Mª{4 ªÔà}>ÑÏÊ;%Ã:´0oOñut%½±§:÷»Z�ì¶Ê„IË;:Ükž�ÈÿçäþÆÅ¯¦´k™í³u•}zHêx½(%šRº ­êkJúJ!Õ{©U3‚s�œ›µI•A‹Wȿӯ‘÷ 6‘=ÏJßÂJ‹Qnjz‹yö~×V‡%49!ð•Ä¢em|°_¥ØeM*3¥´ÎËb4íãùL DéFi–ïh¡œ›˜[™ÿiGŒ0MÄó±§WÆ¢@„?²u…Ÿ­9+cY®¦ÑæÙ±ßM6˜=X´KËÞ£ ÷öýË„UË| —s NW–çÜ€�ŸýÃ[(µ‡:PØ]T×D34-ÛÓ۵쎱ÞóO -kêèÅÆÖrÈêçe %Y- çe Hé“›ê¤CKG]Je½êŠLÓãL°:—V”®”hð1ï^«…º«5ɪ…xù�W¥ ?ý|ðÕªþ#Ø%Öòƒ•Fî-�€�xx°¢ÿÇ) e„bh¡¡Gµ¼:600–Ò/z e„R† ´<üídë—wu𣖷&ªÕ‰­� )qFÁoÿkÑy›QÒ íñ¹Ö¿æ<¾•³mYhy V#e¶D:ÐY§jy«åú.ÕqÇ(Om|–ZWV«:õù‰¥º¾PŸóÏ�D­i‚6°ÈVš\lðRJ \{æê•¼GõL´<6ãI³õ›û1ïdul@ú“?¦=wsE{Ç~?“h²çZnu¢ßÎx}b«Øóòüˆ˜ B¾î®«§ å°6ÌÕGæ‚ÌÞ†v˜«·KרÓZy‘ ‡gÏ_ìììªñ;;»Ïž¿ Ól´ì~JèÑU%ûƒ÷±é3SO)ƒ1òDzºÒ²Æ1¨ý±WRÇüˆ7ÞŒŒ·"ÇëÞéœHV©ŠSg¼êůP©ž¦]VàTc˜¢/š:û°N–»bŒZ‰Û ´VMVË‘l˜ ¬Uö¸SvÒB~ù�WÏž�ŽŽ./¯ðÈåå•ÑÑÑ_že®eöM­NIé’råyYùXVt¸–ÍËfÖk"Æ�¿mY¹§BG­wè®Ð�ª¸Öó¬®/f®^ŸSO]ü—óÝ©£›Lkl}†þ°ú–.lÐß’¡ nÇnW¯&žüô”ËYùÉOOyšÌ´L³owZt¥h�–Ý ÔŸþÔÞ (ZוRíŸO5#Ü’\1xª�Œ–•º‡÷y{H܆¨ ì¤m˜«K«è =)?p‘œµBÎTË„m�í&uÎlEM15¶‰æ=?'´ÆæbQû¥K-Ó|mÊ*à!:E‹ô|–ïFËZ?Œ¬åîæÄh6˜„œ™ Òò^±'Íy™ËY+ä\µ¬Ûûò¾B;ߔċñ¿”}L-ö=ýÈ–²¯PÖØáµì¥ô +Õ1â>pµÝfN>õ Ž ¼C#üÞý´ @9€–(Ð2åZÅe¤/1µ´ Š‹Å±Ë +´ J ´Ì�–Aq�–9Ð2(.Ð2ZæÜºu+³»@÷(ŽýõÔ¦¾Î˦”É@Ë!ýùC:å9ä¢Q诿þû^�1’c=åjxûo.«šchùüàP¤ô·nÝê¨ !îâ�Àãiªº)4ƒAбIÊm1ÿùoÛy™•&½ eí¬gºK¯�¸¥9�¦{u  M¯5O]9¨÷ª™H6€ 0j9p\*zDËZyš§ML‘â˜Oô’ĤIŸkœÇX2çùÓíÚx)ÌÑ™-s²×²4»ýÄž›$I­º)íU/BM¬˜âÕ ålÀ›“˼Lºˆ´Æ¶hJ½d¹Êіؽ–A6`ï‹“ýÞOUËêƒm -«Æ$«eêÚX}FVSjWøêFœš8¤–M# HüV„“”–¥—Mc¤dÚÄö|¤x)1¿ª=6e®}à•Lñ’~¡ål€–9 j™sK!´±yÒÍï¾ ßì�–9)iYš!oÌ—xv¥v%Z椧eÒZæ@Ë ¸@Ëhh™-ƒâ2Ò—˜ZZ @Ë”h€r-P eÊA -z“HZ h€r-P eÊ´ @9€–(Ð2åZ¶Ðxôxðúö‘+•O׳Ÿüu·ñèqRuÙßßßÜÜl4k…Åqœýýý¤¤|@Ë&�¹²ñùݽÇ/_eÎプã~<òùF"rÞßßo4ßÿýË—9Ô%^¾|¹··×h4 gв‰OþswâÁ~¾üÙ�¿^OàŸepç‡~È·.‰Ðl6777“èÞ-›¨|ºžËŒÌy|pxäó�îë²¶¶vxx˜o]áðð°Ñh$ѽ%Z6Qùt=oÏmQùt½ûº¬­­å]�ÄX[[K¢{K´lZîM eв‰>Õ²3Q­N8ö˜Hty»´lZ6CËÎDµâáú/÷äùz¥R©ÏGË3}-·­&#¡åÂ-›ˆªå–$|§mé¶uFž¼žÔµÜ²°^'½AË…Z6QËóuIªÂ‡]OV®†&m- }û\i³Å…>fb‚Vþr$¸iSŸ ÄY"ÝâèÒ€–M@Ë&¢iY3ûÌ×+õùV|µ}iM¤¬eÏn3[?¸‡úªQ»š�ÌZ—ýúòÄn)Ê-š4F eв‰Ä´\SNL5§«eßlOB¼"B”�c8Á«¯” =åoé´&‡–M@Ë&]cKsURÕ²¤ªÀ~x-ÛŸ¯µZ¶Ü-ÇZ6‘ðÞWÜͯ4µ`ÄhĤä^¶ÆˆšG)e�-ëT¹ZNhÙD—ï¤üMUàQHQ˲jÚbžWÞ«©oÚÔ™×|‹Øû’u*Ý-'´l¢O+Òó@Ë& eÐro-›€–M¹²Ñ '•ˆ–�FiþN +Z6-›¼¾ýÙ�½|]wüþ�Ÿüu·ûºlnn6›Í|ë’{{{Žã$ѽ%äüàÐÎnÞR>�ö�\Ùðc^ßùìÎß�\ÙhóÞÑ£ÇZZnËùøñ“>¾víú—_ÞÞÙÁÎ6½ËÎÎîíÛw®]»~á£áãÇOúBnkùµ£ÇÞ|ëíwNœúÃé³çÎUÏŸŠþõü ‚=t#±sçª8}ö�§Þ|ëí׎ó´ü.çvxïùáŸR ¯# ”+$/.À<Û‚e“òoþ]O4ì +endstream +endobj +100 0 obj +<> stream +xœÕ[m‹e· þ~Åù\ˆW–,ˆ%0sgn(4…6 íçn“´¥IÈ6ùÿÅï²Ï¹/a³ ,3£slË’ü<–tb�bþoƒ ¶ÏŒú38k¢�1lï¿;ýx²–�GÞ¬ð†È`À…°Zo|Œ¸}øúô—ßmߟ~<‘±Èy¾ò[ù2 §Ù ?ýó[ùå÷§7_ÐöíNoþøÃ÷_ýôáç÷?moß¾ùòüû— ¶Ï?~9Ÿžß�Þ\ÜfÝö-êÚ a È~{÷Ýé-¶,.ýÎ@Ÿoïþuz}wzýò|´€Õ XWà`(íB-ƒg¼�OÓX@[~ÇÀØ–ÏK{o¬sLiÊwßÞ8™^°ÑPðbãxa<¼®8Þ´Œe6Þ�ŸUO*²rÏu±ª¾kê_ÜÓxÖ4Î[LãÎU‚UïB/å�¼Þ woPÌCã<\v=OÊM�'û2Yäûw·U[¸° ÞðöÝɉɱì‹(�µ[’‘gί‰± .ÉD±È{»½O£}À€R¤â­¤7Іüf0@ˆI†^Äs‘¡_F¹"d°›óF<ÆPßóÁú$sì¨È„<Ög½¬i$æÍqÒŸ­¯²“(ÏÓ6Ö'ú±3ÁE«gq†aÓ¡­ç ’hµÈDï}±eÕžŒ"Ùy—d\”`µ9È�[ÌÑÌFÆÆ²Éa^2àÉ• ›#ðÀaÔúþ´<·>lÓ4l‚ ^/dž–Õ†Vl�ØmZygbÚî´Kg„¬+ƒ‡9\d·a3gÈz7›Ö"Ó'¸¤jvåð•ëntÅ�ÃËn›¡�Ö!3ÖÑÁÕ4R!Ø´Ö±:ö§£ºYBG3˜>'ݲú@5ÈÞOa¸ñ§o®AšÓ�ƸbšOh±¡½Ïp ÆÅH®ãH7§€Ëu4M±áCt±I†ÞÏì’„ »$ÑÊ.I¶g—$]Ù%Ë:»¤MMìÒ`³ ÔëU¤j 8íÚÜÕ»ì=Ì1p|˜­Á½6ï …á=R��GãŒôy�°"}×h„`×ZŪڟŠên ýÝ`êœ ËªÕ {?…áÆHoᨧ^Ï ãÞ\I „Œg hó”ÀÚÛÈo½ œîöj£ySøHM oÛ1šÜ<û#füU¹ÏÒM�XcņIÉOC~�œ¤¦• ºƒÏežV=[­”æÎòŠÜ¸¤\9=ñuþ×:ö\ ÒZh–:¶DwÍO\“±§™6{]ЕŸMc§4§«—uG®hž¦IÚ¤¶¿1ŒÌ3[b©ÐÞ bë4‹Ûq±¸= ïjzIvÀÅ êf.Ÿ næbñûš^®\,~ÏÅâ�².ñ{.N²ÁÅ" ·ÇŠú,Š0Ôz�WºZŠ|šöŠ¥Ô.�us(Öëfëä¨Ì«H´;FÍ¡3‹Ûq±ÐÊÅ’@–.Úe]‚û¬K0ý¾r±àÂÅ‚{.<âbÁ= v7âÂÅé‘Üg]j\¸ã⮵ŽÕ±?ոϺºÁô9é–ÕŠö\Üü†oq1?ÀÅI»ÏB¾\¸¥p•‘óµô¬‰ˆ�—ÞBë)”i ¡CÕvÉÌDåß}öñ·û/Þåî—Ú[/ɵêSM’•W�¥+ù�î�h°v¬æÔÍ›f khßrõr+í-œÔ¸¬³CmÖtEï¼TÒ»VºË �Cé” óêzUI0SWk[…ûdè •I© ö*¨{ŒØzÕóˆh›Yk ¬!!P§ì†u±×ÑAKÉñ:Y¶5ªq/Ê4¢jÀÕžSg¯ÕÓ(Õ0ì—„û|'�²~*ûÖÿSê,áêÈ æPÚÆâƒ¹Ó/ÎGóòÏoþðõ8—­Ìóæ¯1l/?ä5>¢'}íøß®¼øÔ™†~ó¾‡&ñvÚ�JΘµùÈU¿g›Ëµ¦é•™³¢Ò›*±âoá)Ïe.‹†ÅbÐOs´6tÂQ¼Oc9m¼7ìÉMo<Ö¿_é_›Í8&�Î8+Ñ+¼¯ š¯©Ù<Å'¤ sqþµ…(ºüç/�P„¡ñÓE(ÞÎɹ-DõeÀ½Ï ðv„RºK”³>¡$Šü¿ÇÜÜ +“[2 ð<{6G#C¯’;¨ÿvyÍ9t'a‡-Ä(í�/ɯã–üÂÙþA½wžsÛk­8ŠåC˜6¦]²¾õ}ªëÄúOƒ×üYµ?ϽÍfÂ~ÁcãÈ/·À‡®‰ºsÚ®dÔ¾oQŒØWn««¸F¼ç´;ín„-ýNÛ¡h+€Äj–çqlke¾E?Pæ¿øRO}:”$!zhm+ë‹óX¡ßY|½Û_T•ã¥ÜIî{ëN“½%á·8bK ú ¯Øþ\JYTÊô´ïÈd¿7ôÒa®ËDù�Šj¹íd®kö¿Ú‡^&x�öZÍq¾iL ŸGeMßX{¼zG@ÂN—^>»Œ�)LZ Ö`óÑ@Ïþcõ¼Ìnì7êƒhígE¦[ÚqtYèYr!ëT·Goç„È�ˆ×•¼ÞsÉø\æè¿ùþ~ä[ÿ£ÈÇ…Ø­5˜¯;Z#vÂ3Ôõ/yisÎZ—uÆ¥¥*ÅjC·K sâÀ!�=> stream +xœí[ÛŠ$Ç}ﯨgƒrã’‘Xfº§…d�= �m�A2»–ÿßTV^¢²®=»²ÁÚ…¥»'«"3ãrâDT–!Žéß ßõ3X4c Ã�?_>^Å8’½ Dl:ãb¤áÓß.?üaøåòñÂI’¼é[¹ó +>²ñ2 +„tÁŸ¿¦/Ÿ~º¼û–‡Ÿþuy÷§þò—_?ýûÇ_‡÷ïß}wýãm€áÇçÛõòüzyw·áðú÷ N+Æ�`ˆaxýùò="0;;þ·‚ÓD¦q ùo^]?þ�ó=’?=€ÐøûÃðú�ËËëåå»ëÚBq¾Pt†GÑ fòuÁIh0�¬µÞ 0¼þu0 °¢µ»qp°Æ‹mà[ˆ†Ùr¨ò9 .4QâÒ€3Ñ:°6Ö[&g +Á“ow@@k ÆH1œ±! Î…6‹àÃëÚ5éWŒXO›eRŠ7ЉÇJÙZ•}ÊŽ£isÀ}Æ›¦¢I¿F$D5…”)ˆ½k×Ûç�5¹ë–¡Þ…½æ]Dg%Æ3w¼äÉ‘=p¤u3{bŽMû�sø 5"6Q\´î#:rëQ ÞžP®Wª²ëfõ‘�w7äANhpK3óiQŸ³UXiLIƒ©Ð€v‰¢Îe¥0* ÃyãçSƒó +õÙ1Ù°e¯vì×SM×[o<A¥ šå"ã˜ýÌNjE¶ú1[–¢ÓÈ•³“�kþœ.¶h(Dp<¿4Ç]Z*ÎBI?_>G¶¥ûŸš¼¤56ÞgïÓŽÓïØîU—îçéš:Çm«2E­#d£)gHׇIÆ8-ÉôÝ_ó¾_Ú}V;Q^·v&QÜ‚ü\Oç°9›­Ö^οÔG1úŽU)f«Ö™ŸÆD0f‰¬énÕ¼¶²{žÝ7M§k'OGö&²óóXðÍFiÔùÀµù@Ý;dÝÙÉ&eµ!ëÇ©{p¾‹äC/jÞØ|«ÊwY–dyÔ40êØ†f‡j÷{þ¬v?:{tNZÕMe匎\áyîä9?mO.ûXÉŒxM¢‘¡Ð·‚1“ªfu»[FCÑ?›U�*¢qhI|XAÕ5ÝÂÊ‹S„sœ¤“5H.JžÒb>á‰WðÔümMºöåŠ^‘Â�y÷æKE^�i…?Huå$Ñ0!iÒ�oÊ�5 +÷×TeÚϤÂXUÀ$'/`¤'À&”R U¶¥!Î*pÄ*µ²OÎß•Yª¬¬ÚÙÎ@EÒèáè�H5§`À³©µ]5HNÆ÷&8&ÍÒ +dT[!Qjyªl$ˆ Îêš&ë•»ôì”=tŠ%˜/8Ù—îÛh9<µ TäK-½à"rqqùj‹Æùà—øåÊ®¥öI€G˜€q¾ÞО×xÃTˆ:Ã,‘Ãlv¸xJ8LâBç(ÚÉ2ƒ²Å4qžyߊFãýæÎ(EÁ3½¤Ët܌ġ3¸_Æî&•èh�¦˜Å”I1ÐÑE9Ö(غ{–:Wj´÷e¥6¶ØçÎb@gUh+Æ]s£oì%©í¦TçÖ±¤gç<-e 0ñ +‘*×;éEE‰I V«g¼vÜúÚ¼´äZ‰ÊÀ¥~pŠÿ…Ç wºNQ9¾rÊqü9ƒ*µQœ¡ò ¢r"­:¤lð>îf•A·B²ŠÍˆ"”a©™q •qß²sª<íò21–S²¶R‰t{)¿o-�­'̺ª¿™ÿ–©.YZJ¨2nKšp˜Þã!h;[@;LÞDÇÁyZ!تºÚÑÔQݺ–ÄmŽÑ +F3Íõ±á€ÅXk³oÛM³_¶4oßš¥o—w“xoȉc=IÙn… l[-¡;Û]•/ŽÓÑ— ê Å®s6 Ò˜ç}�J”ïKܳ²ä'…ê×͆8ã¡Ö�-5jtN±UÎ_ªcSðh¯Sc÷¼=6:ŒW<ÑEôXsì-o·‘QF£na{¥]žô8gƒÎ�jžWØ”õÅP½nÙçÈ"Ö¯RåÏeò ÇÔ7øm¨¼d}/”ªê¦à˜ºËÞŠ+Hª#æþHxç§-Їq˜Ñ|B‰‚Ìg†œû{Ës…yæq—Ö žjÍd÷cÀ‰IöSó�è ©Ȭ…6¹¥˜(„6,ôUº3Ñ'Ävµ93J™söCë  ¬Â'êø;Ú¿ìkšœé¶¯}³v(nj÷~§©ë›6Ù®¯ß2gãè)ºËИÜüiIH[³ùHenße$ØÔQd­µÒ¨k`šÕâv×a–¢6€ÙsñÇS |òÙÑ›Ë*wvë÷™Æ‡ÉEêŠÆ$Svm·¾k^MM*»*–ÞWcÉ쨘éH8nçà¼ð‚ ùÂJ—`µº5xÀ¦kM—C£\WÝù¶tϽڪʕcÎâcm ÛŒ+Tp-KÑhqÍ�nYÝ›êŒ[»Æ®n±>¾(:êVÀ£Xä`Ÿ:�ïJûS^9ÛÇKG›¯Š>÷–AUÏ….Y?)�)½æ'ì»V XÚŸ«Íœ²×|Z3N�Žœ_[å[‰¾(�Ø–VzºF�ªñ±5KÓºÕ]©ÖÒý Pz9Y}âqÏ0HA(Êɦ²’G:"ÏmçW‹6«îQl¡L¦T<ö•œw›=¿3¥Ï¢ô½ïÖ[2ën½l¸êâ$Éy^QØš‚Jð…¹+»D<.pÆB½øDn"NϺ²õV zt2vÝPÀÈD(ÛÄ%…½™á|F¡9 €­ =Þ2ì}lÕÆ¤ÿú¨aô¯ÊÇ_Îðñsí€ë��˜©7R¼‚Þsñ.Kç�IFþŒc’òè1I*[�‘ç-öÇÏCm€Û>ζy|‹òÑëƒCÛ“¿ýÌÕ‰vIôåÐ÷Ï»ò¦ÉÖ–GZú¾ÃGktX[�,¶z|ÿ8ẩ šÕ–ÒTTë‰Æ§xtŸö:î)õu±Â|À ñˆÑs„Î(—Zu¹ñ ªQ8ø€Bm.ÊÅŽg½œÐç½#½ìÛŒ¥–ÒŠ™Õ¿j½ %T³ f*éZ«Áõéî4À,{wm³ ë{VêÑDO›ÅÖ•îË!úbêì©Ã:.ž”™ +àß×�¾tÁj2,õÚ¡ÑÊýi�>þVq¯Zކ!H´oh9¾)öKÿ´q$ø,¤g‘¢MôßG +eO�РŶV)ë0¢ÊŽÊ0HùÌš&cã%zç>röÊ1¡�8öÕþÝCZú_B¾Ôƒ€¡–AY,kkë| +é ÍåAqÿSýb>ÍOˆ¸.ña‚úõ=ž¯ïñ|}�çë{<ÿßïñðqM‰@N?êüý½ÉóýåûËÇôR,Ù€†d`7~n¿W;~Û{“v´GÒ¹âœù\ƒQ9VS™Ž�òljUÍO«õB<‚ IóU +Ž�4ΟqÊÐhóß >ïJåÀf:q€Å%&û÷E6‘0}_"º¤)úê½Ф�̦ÌHÂì-P3[È&ûá±y +endstream +endobj +104 0 obj +<> stream +xœ¥YÙ®G }ï¯èçHÔµ]v-Bšå!‘H®” €”þ?ªjWµ»§gAáJÌôR./ÇÇv�#Ÿë¿F_8s™]ÆœÓøöãðe@HFŒ2 8à”FO\șƯï†?~? _ï�¤Ê›¾µ•/<’“"ÏqNãüßo¯ÇÕ�¯†§×É�þžöÿ|~û÷›ïŸ¿½_¾|úåðóq„ñÕ«ýñ0ñ cyd.Z¼`t�® +6W“P.BûB1ËÊ_]QþêËU�Ó/‡áé×ÏŸ~ÿöõûÛo]TöÏÃÓ™Gäñùý€“#q$<†ñùãð€€xI‚§kÒk‰|`žžõ÷vºn7É™ž³>/Ïêš3€½Îú^àƒ®÷³ü²—§g]¦è3]×t+ïTýŽ*ï<ÙRí:Ä"#ði^Çf«ÞE§¤×eTÝýnÖ£®]éíƒÊgÕg�‘ôyRûšþÍFš®«ÜÃäQ¿wÊ«ñù¯áô|3œ>µpZÓŒIM…¶“Ùʬ¹ž.cŠ;öÒ8›[ݱµÇÙ˜d\é'Ó(–œ )�0>ÿ9¾T¿ÄÙOU9š�IÃZ÷.ª¸;yÆ£(Þ)¨Â¢þ·1É*KãÒ±rÖÏÝìÄ=ÍÎì8oØãìÓ +鑸hñ=Õ×£‹1Syé32ù×ä³ñ ª-kõ>L9ãsŽÄ)dñ]z÷¨j_Ñ»ÅÇéÞE–ˆ”(tM­GÙÀL–ZO™6Á½‹$bL�¯6Õ¯ánÙyŠÈi!è*挿 +ø{<5ɺuA7 Ó{–ÿ:nl²Ùï?”ó‰þ8‹mÛÖP›à 6‘WŠÝ/ŒiU4ŠÍÐh […�šp+Æx¤ÀT§*¡— ´([‡·¢!7Á³{J”Ñvf4†R@¨­ Q�(¸&˜¤_3çÚÞ)ƒ’à ì8ƒÛ Èo„|6æ41ƒ© ²×jAsü¶RÚ¦|±?˜8ÔXDö¢=Ù�‚.D‰ —cØÉ�LK/œJzKÑ…¡a£Þ(Õ7„lòÁnÒ(‰“”R\°^/r§Ç#Ô]$dœÙ¯Ãñh¾[7ë3ÙÍ}HgÆ ½Äa®AVÏ[½Ø]ð–„¶‘’P‘’cíÑ¥8ñìÃÝÜmU‰’bÒ*ŠMË`øØ41e ì<­2ádz°¨•B +/«X…àyjTÊwlýb«2÷å"‘Æ2\¬%_µÆwE0€¯w’óBÌxY\�|¸4¾4.AjA£½¡Æ øž…$"Kà׌º³·Ü¬]Nì£,4è…`ƒL.:®±½vòû 4“BõõVO²›)ÂRËÖ·#¿�žFI«‰éGdÝÆ‹ †+ÓÂ%ÛÜæü ì5$¼ß®—ëF·‘•`\Ó&^&ìÅp³UGdû¾Ö òä"e^u¼8 õå¢ã7ïðåÖñëÅý¼qzw�s¼Ä_Öa^Éë �‰Á£-®­§þ_ìVÉœ/"!]’¸$nÙ˜”ƒ1s ݽº�wÂ) Ì\‡reøú€'Æ€s  ¬CKΛŠ?mž†�fQ‚WðI7GÁVÄ‹ŽÄh¨ù väÀ’sX³ov’1.F©«›7_I€ÒŊ롉Ö@½¡_Ÿ9¬ù–R‹c+uòn�ÛÐF‹úà™�Ý“èŽ Òý#„FH>]’‘ÂëúÙî�`½…$ç뱟هÊçÚ¼/:�ÞØÖ}:òÁÓ<.Ú£ª¢_s¥¨Ë×I':BJyNƒ’¦µ‰æìêjÜîxá¶£³8�~á‡í®óÞNxÛáÞƒkå߸\Û çE§>^¸‹Â{úÝžiÙ£c”ê-Jœ9Ó«÷Z/š—}i¯[‡k<�Gëóż°ª /šëÛ\HÒªùz9ÎE¥ÍAö9�VÓÚi5¹®ŽB›‰lp¾nÜZWЧÁG‹>2ƒD’kT)FÇø8]v_ä‰(é”*.-'ý~~³N¢ÒšhŸ8S-–§9„.JЦjXÊmpÚšO¦c^Sñ›áÍð¥þ~@œÐ‘Œ^ ”Ï뿊”oo?ÞþÑ¡Æ&Œy™îÑÅT$P;&êyÂS^ÕA|Ù™®…DlœÑ¥`9I÷ú™'n@Ö{ Ÿû›R}bGž}¡Ï!¬íŠ÷‘�æ1Q\–½+çÞ +«\O–47×2 ƒ\)ñ‡)�üÖÓ�­ +endstream +endobj +2 0 obj +<> +/Font <>>> +/MediaBox [0 0 595.91998 841.91998] +/Annots [7 0 R 8 0 R 9 0 R 10 0 R 11 0 R 12 0 R 13 0 R 14 0 R 15 0 R 16 0 R 17 0 R 18 0 R 19 0 R 20 0 R 21 0 R 22 0 R 23 0 R 24 0 R 25 0 R 26 0 R 27 0 R 28 0 R 29 0 R 30 0 R 31 0 R 32 0 R 33 0 R 34 0 R 35 0 R 36 0 R 37 0 R 38 0 R 39 0 R 40 0 R 41 0 R 42 0 R 43 0 R 44 0 R 45 0 R 46 0 R 47 0 R 48 0 R 49 0 R] +/Contents 50 0 R +/StructParents 0 +/Tabs /S +/Parent 105 0 R>> +endobj +51 0 obj +<> +/Font <>>> +/MediaBox [0 0 595.91998 841.91998] +/Annots [53 0 R 54 0 R 55 0 R 56 0 R 57 0 R 58 0 R 59 0 R 60 0 R 61 0 R 62 0 R 63 0 R 64 0 R 65 0 R 66 0 R 67 0 R 68 0 R 69 0 R 70 0 R 71 0 R 72 0 R 73 0 R 74 0 R 75 0 R] +/Contents 76 0 R +/StructParents 44 +/Tabs /S +/Parent 105 0 R>> +endobj +77 0 obj +<> +/XObject <> +/Font <>>> +/MediaBox [0 0 595.91998 841.91998] +/Contents 81 0 R +/StructParents 68 +/Tabs /S +/Parent 105 0 R>> +endobj +82 0 obj +<> +/Font <>>> +/MediaBox [0 0 595.91998 841.91998] +/Contents 85 0 R +/StructParents 69 +/Tabs /S +/Parent 105 0 R>> +endobj +86 0 obj +<> +/XObject <> +/Font <>>> +/MediaBox [0 0 595.91998 841.91998] +/Contents 88 0 R +/StructParents 70 +/Tabs /S +/Parent 105 0 R>> +endobj +89 0 obj +<> +/XObject <> +/Font <>>> +/MediaBox [0 0 595.91998 841.91998] +/Contents 91 0 R +/StructParents 71 +/Tabs /S +/Parent 105 0 R>> +endobj +92 0 obj +<> +/Font <>>> +/MediaBox [0 0 595.91998 841.91998] +/Contents 93 0 R +/StructParents 72 +/Tabs /S +/Parent 105 0 R>> +endobj +94 0 obj +<> +/XObject <> +/Font <>>> +/MediaBox [0 0 595.91998 841.91998] +/Contents 96 0 R +/StructParents 73 +/Tabs /S +/Parent 105 0 R>> +endobj +97 0 obj +<> +/XObject <> +/Font <>>> +/MediaBox [0 0 595.91998 841.91998] +/Contents 100 0 R +/StructParents 74 +/Tabs /S +/Parent 106 0 R>> +endobj +101 0 obj +<> +/Font <>>> +/MediaBox [0 0 595.91998 841.91998] +/Contents 102 0 R +/StructParents 75 +/Tabs /S +/Parent 106 0 R>> +endobj +103 0 obj +<> +/Font <>>> +/MediaBox [0 0 595.91998 841.91998] +/Contents 104 0 R +/StructParents 76 +/Tabs /S +/Parent 106 0 R>> +endobj +105 0 obj +<> +endobj +106 0 obj +<> +endobj +107 0 obj +<> +endobj +108 0 obj +<> +endobj +112 0 obj +<> +endobj +111 0 obj +<> +endobj +115 0 obj +<> +endobj +117 0 obj +<> +endobj +116 0 obj +<>]>> +endobj +120 0 obj +<> +endobj +122 0 obj +<> +endobj +121 0 obj +<>]>> +endobj +125 0 obj +<> +endobj +127 0 obj +<> +endobj +126 0 obj +<>]>> +endobj +124 0 obj +<> +endobj +129 0 obj +<> +endobj +131 0 obj +<> +endobj +130 0 obj +<>]>> +endobj +128 0 obj +<> +endobj +133 0 obj +<> +endobj +135 0 obj +<> +endobj +134 0 obj +<>]>> +endobj +132 0 obj +<> +endobj +137 0 obj +<> +endobj +139 0 obj +<> +endobj +138 0 obj +<>]>> +endobj +136 0 obj +<> +endobj +123 0 obj +<>]>> +endobj +119 0 obj +<> +endobj +141 0 obj +<> +endobj +143 0 obj +<> +endobj +142 0 obj +<>]>> +endobj +146 0 obj +<> +endobj +148 0 obj +<> +endobj +147 0 obj +<>]>> +endobj +151 0 obj +<> +endobj +153 0 obj +<> +endobj +152 0 obj +<>]>> +endobj +150 0 obj +<> +endobj +155 0 obj +<> +endobj +157 0 obj +<> +endobj +156 0 obj +<>]>> +endobj +154 0 obj +<> +endobj +159 0 obj +<> +endobj +161 0 obj +<> +endobj +160 0 obj +<>]>> +endobj +158 0 obj +<> +endobj +149 0 obj +<>]>> +endobj +145 0 obj +<> +endobj +163 0 obj +<> +endobj +165 0 obj +<> +endobj +164 0 obj +<>]>> +endobj +168 0 obj +<> +endobj +170 0 obj +<> +endobj +169 0 obj +<>]>> +endobj +167 0 obj +<> +endobj +172 0 obj +<> +endobj +174 0 obj +<> +endobj +173 0 obj +<>]>> +endobj +171 0 obj +<> +endobj +176 0 obj +<> +endobj +178 0 obj +<> +endobj +177 0 obj +<>]>> +endobj +175 0 obj +<> +endobj +180 0 obj +<> +endobj +182 0 obj +<> +endobj +181 0 obj +<>]>> +endobj +179 0 obj +<> +endobj +166 0 obj +<>]>> +endobj +162 0 obj +<> +endobj +144 0 obj +<>]>> +endobj +140 0 obj +<> +endobj +184 0 obj +<> +endobj +186 0 obj +<> +endobj +185 0 obj +<>]>> +endobj +189 0 obj +<> +endobj +191 0 obj +<> +endobj +190 0 obj +<>]>> +endobj +194 0 obj +<> +endobj +196 0 obj +<> +endobj +195 0 obj +<>]>> +endobj +193 0 obj +<> +endobj +198 0 obj +<> +endobj +200 0 obj +<> +endobj +199 0 obj +<>]>> +endobj +197 0 obj +<> +endobj +202 0 obj +<> +endobj +204 0 obj +<> +endobj +203 0 obj +<> <>]>> +endobj +201 0 obj +<> +endobj +192 0 obj +<>]>> +endobj +188 0 obj +<> +endobj +206 0 obj +<> +endobj +208 0 obj +<> +endobj +207 0 obj +<>]>> +endobj +211 0 obj +<> +endobj +213 0 obj +<> +endobj +212 0 obj +<>]>> +endobj +210 0 obj +<> +endobj +215 0 obj +<> +endobj +217 0 obj +<> +endobj +216 0 obj +<>]>> +endobj +214 0 obj +<> +endobj +219 0 obj +<> +endobj +221 0 obj +<> +endobj +220 0 obj +<>]>> +endobj +218 0 obj +<> +endobj +223 0 obj +<> +endobj +225 0 obj +<> +endobj +224 0 obj +<> <>]>> +endobj +222 0 obj +<> +endobj +227 0 obj +<> +endobj +229 0 obj +<> +endobj +228 0 obj +<>]>> +endobj +226 0 obj +<> +endobj +231 0 obj +<> +endobj +233 0 obj +<> +endobj +232 0 obj +<> <>]>> +endobj +230 0 obj +<> +endobj +235 0 obj +<> +endobj +237 0 obj +<> +endobj +236 0 obj +<> <>]>> +endobj +234 0 obj +<> +endobj +209 0 obj +<>]>> +endobj +205 0 obj +<> +endobj +187 0 obj +<>]>> +endobj +183 0 obj +<> +endobj +239 0 obj +<> +endobj +241 0 obj +<> +endobj +240 0 obj +<>]>> +endobj +244 0 obj +<> +endobj +246 0 obj +<> +endobj +245 0 obj +<>]>> +endobj +249 0 obj +<> +endobj +251 0 obj +<> +endobj +250 0 obj +<>]>> +endobj +248 0 obj +<> +endobj +253 0 obj +<> +endobj +255 0 obj +<> +endobj +254 0 obj +<>]>> +endobj +252 0 obj +<> +endobj +257 0 obj +<> +endobj +259 0 obj +<> +endobj +258 0 obj +<>]>> +endobj +256 0 obj +<> +endobj +261 0 obj +<> +endobj +263 0 obj +<> +endobj +262 0 obj +<> <>]>> +endobj +260 0 obj +<> +endobj +247 0 obj +<>]>> +endobj +243 0 obj +<> +endobj +265 0 obj +<> +endobj +267 0 obj +<> +endobj +266 0 obj +<>]>> +endobj +270 0 obj +<> +endobj +272 0 obj +<> +endobj +271 0 obj +<>]>> +endobj +269 0 obj +<> +endobj +274 0 obj +<> +endobj +276 0 obj +<> +endobj +275 0 obj +<>]>> +endobj +273 0 obj +<> +endobj +278 0 obj +<> +endobj +280 0 obj +<> +endobj +279 0 obj +<>]>> +endobj +277 0 obj +<> +endobj +282 0 obj +<> +endobj +284 0 obj +<> +endobj +283 0 obj +<> <>]>> +endobj +281 0 obj +<> +endobj +286 0 obj +<> +endobj +288 0 obj +<> +endobj +287 0 obj +<> <>]>> +endobj +285 0 obj +<> +endobj +290 0 obj +<> +endobj +292 0 obj +<> +endobj +291 0 obj +<> <>]>> +endobj +289 0 obj +<> +endobj +268 0 obj +<>]>> +endobj +264 0 obj +<> +endobj +242 0 obj +<>]>> +endobj +238 0 obj +<> +endobj +294 0 obj +<> +endobj +296 0 obj +<> +endobj +295 0 obj +<>]>> +endobj +299 0 obj +<> +endobj +301 0 obj +<> +endobj +300 0 obj +<>]>> +endobj +304 0 obj +<> +endobj +306 0 obj +<> +endobj +305 0 obj +<>]>> +endobj +303 0 obj +<> +endobj +308 0 obj +<> +endobj +310 0 obj +<> +endobj +309 0 obj +<>]>> +endobj +307 0 obj +<> +endobj +312 0 obj +<> +endobj +314 0 obj +<> +endobj +313 0 obj +<> <>]>> +endobj +311 0 obj +<> +endobj +302 0 obj +<>]>> +endobj +298 0 obj +<> +endobj +316 0 obj +<> +endobj +318 0 obj +<> +endobj +317 0 obj +<>]>> +endobj +321 0 obj +<> +endobj +323 0 obj +<> +endobj +322 0 obj +<>]>> +endobj +320 0 obj +<> +endobj +325 0 obj +<> +endobj +327 0 obj +<> +endobj +326 0 obj +<>]>> +endobj +324 0 obj +<> +endobj +319 0 obj +<>]>> +endobj +315 0 obj +<> +endobj +297 0 obj +<>]>> +endobj +293 0 obj +<> +endobj +329 0 obj +<> +endobj +331 0 obj +<> +endobj +330 0 obj +<>]>> +endobj +334 0 obj +<> +endobj +336 0 obj +<> +endobj +335 0 obj +<>]>> +endobj +339 0 obj +<> +endobj +341 0 obj +<> +endobj +340 0 obj +<>]>> +endobj +338 0 obj +<> +endobj +343 0 obj +<> +endobj +345 0 obj +<> +endobj +344 0 obj +<>]>> +endobj +342 0 obj +<> +endobj +337 0 obj +<>]>> +endobj +333 0 obj +<> +endobj +347 0 obj +<> +endobj +349 0 obj +<> +endobj +348 0 obj +<>]>> +endobj +352 0 obj +<> +endobj +354 0 obj +<> +endobj +353 0 obj +<>]>> +endobj +351 0 obj +<> +endobj +356 0 obj +<> +endobj +358 0 obj +<> +endobj +357 0 obj +<>]>> +endobj +355 0 obj +<> +endobj +350 0 obj +<>]>> +endobj +346 0 obj +<> +endobj +332 0 obj +<>]>> +endobj +328 0 obj +<> +endobj +118 0 obj +<>]>> +endobj +114 0 obj +<> +endobj +113 0 obj +<>]>> +endobj +360 0 obj +<> +endobj +359 0 obj +<> +endobj +362 0 obj +<> +endobj +361 0 obj +<> +endobj +364 0 obj +<> +endobj +363 0 obj +<> +endobj +366 0 obj +<> +endobj +365 0 obj +<> +endobj +369 0 obj +<> +endobj +368 0 obj +<> +endobj +370 0 obj +<> +endobj +367 0 obj +<> +endobj +372 0 obj +<> +endobj +371 0 obj +<> +endobj +374 0 obj +<> +endobj +376 0 obj +<> +endobj +375 0 obj +<> +endobj +377 0 obj +<> +endobj +373 0 obj +<> +endobj +380 0 obj +<> +endobj +382 0 obj +<> +endobj +381 0 obj +<> +endobj +383 0 obj +<> +endobj +379 0 obj +<> +endobj +385 0 obj +<> +endobj +387 0 obj +<> +endobj +386 0 obj +<> +endobj +388 0 obj +<> +endobj +384 0 obj +<> +endobj +378 0 obj +<>]>> +endobj +390 0 obj +<> +endobj +389 0 obj +<> +endobj +392 0 obj +<> +endobj +393 0 obj +<> +endobj +395 0 obj +<> +endobj +394 0 obj +<> +endobj +391 0 obj +<> +endobj +397 0 obj +<> +endobj +396 0 obj +<> +endobj +400 0 obj +<> +endobj +399 0 obj +<> +endobj +401 0 obj +<> +endobj +398 0 obj +<> +endobj +403 0 obj +<> +endobj +402 0 obj +<> +endobj +405 0 obj +<> +endobj +404 0 obj +<> +endobj +407 0 obj +<> +endobj +406 0 obj +<> +endobj +409 0 obj +<> +endobj +408 0 obj +<> +endobj +412 0 obj +<> +endobj +413 0 obj +<> +endobj +415 0 obj +<> +endobj +414 0 obj +<> +endobj +416 0 obj +<> +endobj +411 0 obj +<> +endobj +418 0 obj +<> +endobj +419 0 obj +<> +endobj +421 0 obj +<> +endobj +420 0 obj +<> +endobj +422 0 obj +<> +endobj +417 0 obj +<> +endobj +424 0 obj +<> +endobj +425 0 obj +<> +endobj +427 0 obj +<> +endobj +426 0 obj +<> +endobj +428 0 obj +<> +endobj +423 0 obj +<> +endobj +430 0 obj +<> +endobj +431 0 obj +<> +endobj +433 0 obj +<> +endobj +432 0 obj +<> +endobj +434 0 obj +<> +endobj +429 0 obj +<> +endobj +436 0 obj +<> +endobj +437 0 obj +<> +endobj +439 0 obj +<> +endobj +438 0 obj +<> +endobj +440 0 obj +<> +endobj +435 0 obj +<> +endobj +442 0 obj +<> +endobj +443 0 obj +<> +endobj +441 0 obj +<> +endobj +410 0 obj +<>]>> +endobj +445 0 obj +<> +endobj +444 0 obj +<> +endobj +447 0 obj +<> +endobj +446 0 obj +<> +endobj +449 0 obj +<>]>> +endobj +448 0 obj +<> +endobj +451 0 obj +<> +endobj +450 0 obj +<> +endobj +453 0 obj +<> +endobj +452 0 obj +<> +endobj +455 0 obj +<> +endobj +454 0 obj +<> +endobj +459 0 obj +<> +endobj +458 0 obj +<> +endobj +460 0 obj +<> +endobj +457 0 obj +<> +endobj +456 0 obj +<> +endobj +462 0 obj +<> +endobj +461 0 obj +<> +endobj +464 0 obj +<> +endobj +463 0 obj +<> +endobj +466 0 obj +<> +endobj +468 0 obj +<> +endobj +467 0 obj +<> +endobj +469 0 obj +<> +endobj +471 0 obj +<> +endobj +470 0 obj +<> +endobj +472 0 obj +<> +endobj +465 0 obj +<> +endobj +474 0 obj +<> +endobj +473 0 obj +<> +endobj +476 0 obj +<> +endobj +478 0 obj +<> +endobj +477 0 obj +<> +endobj +479 0 obj +<> +endobj +481 0 obj +<> +endobj +480 0 obj +<> +endobj +482 0 obj +<> +endobj +483 0 obj +< +/P 475 0 R +/Pg 86 0 R +/K 0>> +endobj +485 0 obj +<> +endobj +484 0 obj +<> +endobj +475 0 obj +<> +endobj +487 0 obj +<> +endobj +486 0 obj +<> +endobj +489 0 obj +<> +endobj +491 0 obj +<> +endobj +490 0 obj +<> +endobj +492 0 obj +<> +endobj +494 0 obj +<> +endobj +493 0 obj +<> +endobj +495 0 obj +<> +endobj +488 0 obj +<> +endobj +497 0 obj +<> +endobj +496 0 obj +<> +endobj +499 0 obj +<> +endobj +501 0 obj +<> +endobj +500 0 obj +<> +endobj +502 0 obj +<> +endobj +504 0 obj +<> +endobj +503 0 obj +<> +endobj +505 0 obj +<> +endobj +498 0 obj +<> +endobj +507 0 obj +<> +endobj +506 0 obj +<> +endobj +510 0 obj +<> +endobj +511 0 obj +<> +endobj +509 0 obj +<> +endobj +513 0 obj +<> +endobj +514 0 obj +<> +endobj +512 0 obj +<> +endobj +516 0 obj +<> +endobj +517 0 obj +<> +endobj +515 0 obj +<> +endobj +519 0 obj +<> +endobj +520 0 obj +<> +endobj +518 0 obj +<> +endobj +522 0 obj +<> +endobj +523 0 obj +<> +endobj +521 0 obj +<> +endobj +508 0 obj +<>]>> +endobj +525 0 obj +<> +endobj +524 0 obj +<> +endobj +527 0 obj +<> +endobj +526 0 obj +<> +endobj +529 0 obj +<> +endobj +528 0 obj +<> +endobj +532 0 obj +<> +endobj +534 0 obj +<> +endobj +533 0 obj +<> +endobj +531 0 obj +<> +endobj +536 0 obj +<> +endobj +538 0 obj +<> +endobj +537 0 obj +<> +endobj +539 0 obj +<> +endobj +535 0 obj +<> +endobj +541 0 obj +<> +endobj +543 0 obj +<> +endobj +542 0 obj +<> +endobj +540 0 obj +<> +endobj +545 0 obj +<> +endobj +547 0 obj +<> +endobj +546 0 obj +<> +endobj +544 0 obj +<> +endobj +530 0 obj +<>]>> +endobj +549 0 obj +<> +endobj +548 0 obj +<> +endobj +551 0 obj +<> +endobj +553 0 obj +<> +endobj +552 0 obj +<> +endobj +550 0 obj +<> +endobj +555 0 obj +<> +endobj +554 0 obj +<> +endobj +557 0 obj +<> +endobj +556 0 obj +<> +endobj +559 0 obj +<> +endobj +558 0 obj +<> +endobj +561 0 obj +<> +endobj +560 0 obj +<> +endobj +565 0 obj +<> +endobj +564 0 obj +<> +endobj +566 0 obj +<> +endobj +563 0 obj +<> +endobj +562 0 obj +<> +endobj +568 0 obj +<> +endobj +567 0 obj +<> +endobj +570 0 obj +<> +endobj +569 0 obj +<> +endobj +572 0 obj +<> +endobj +574 0 obj +<> +endobj +573 0 obj +<> +endobj +575 0 obj +<> +endobj +577 0 obj +<> +endobj +576 0 obj +<> +endobj +578 0 obj +<> +endobj +580 0 obj +<> +endobj +579 0 obj +<> +endobj +581 0 obj +<> +endobj +571 0 obj +<> +endobj +583 0 obj +<> +endobj +582 0 obj +<> +endobj +585 0 obj +<> +endobj +587 0 obj +<> +endobj +586 0 obj +<> +endobj +588 0 obj +<> +endobj +584 0 obj +<> +endobj +590 0 obj +<> +endobj +589 0 obj +<> +endobj +592 0 obj +<> +endobj +594 0 obj +<> +endobj +593 0 obj +<> +endobj +595 0 obj +<> +endobj +591 0 obj +<> +endobj +597 0 obj +<> +endobj +596 0 obj +<> +endobj +599 0 obj +<> +endobj +598 0 obj +<> +endobj +601 0 obj +<> +endobj +600 0 obj +<> +endobj +603 0 obj +<> +endobj +602 0 obj +<> +endobj +605 0 obj +<> +endobj +604 0 obj +<> +endobj +607 0 obj +<> +endobj +606 0 obj +<> +endobj +610 0 obj +<> +endobj +611 0 obj +<> +endobj +609 0 obj +<> +endobj +613 0 obj +<> +endobj +614 0 obj +<> +endobj +612 0 obj +<> +endobj +616 0 obj +<> +endobj +617 0 obj +<> +endobj +620 0 obj +<> +endobj +621 0 obj +<> +endobj +619 0 obj +<> +endobj +623 0 obj +<> +endobj +624 0 obj +<> +endobj +622 0 obj +<> +endobj +626 0 obj +<> +endobj +627 0 obj +<> +endobj +625 0 obj +<> +endobj +618 0 obj +<>]>> +endobj +615 0 obj +<> +endobj +608 0 obj +<>]>> +endobj +629 0 obj +<> +endobj +631 0 obj +<> +endobj +630 0 obj +<> +endobj +632 0 obj +<> +endobj +628 0 obj +<> +endobj +634 0 obj +<> +endobj +636 0 obj +<> +endobj +635 0 obj +<> +endobj +637 0 obj +<> +endobj +633 0 obj +<> +endobj +639 0 obj +<> +endobj +641 0 obj +<> +endobj +640 0 obj +<> +endobj +642 0 obj +<> +endobj +644 0 obj +<> +endobj +643 0 obj +<> +endobj +645 0 obj +<> +endobj +638 0 obj +<> +endobj +648 0 obj +<> +endobj +649 0 obj +<> +endobj +647 0 obj +<> +endobj +651 0 obj +<> +endobj +652 0 obj +<> +endobj +650 0 obj +<> +endobj +646 0 obj +<>]>> +endobj +654 0 obj +<> +endobj +656 0 obj +<> +endobj +655 0 obj +<> +endobj +657 0 obj +<> +endobj +653 0 obj +<> +endobj +660 0 obj +<> +endobj +661 0 obj +<> +endobj +659 0 obj +<> +endobj +663 0 obj +<> +endobj +664 0 obj +<> +endobj +662 0 obj +<> +endobj +658 0 obj +<>]>> +endobj +666 0 obj +<> +endobj +665 0 obj +<> +endobj +668 0 obj +<> +endobj +667 0 obj +<> +endobj +670 0 obj +<> +endobj +669 0 obj +<> +endobj +673 0 obj +<> +endobj +675 0 obj +<> +endobj +674 0 obj +<> +endobj +676 0 obj +<> +endobj +672 0 obj +<> +endobj +678 0 obj +<> +endobj +680 0 obj +<> +endobj +679 0 obj +<> +endobj +681 0 obj +<> +endobj +677 0 obj +<> +endobj +683 0 obj +<> +endobj +685 0 obj +<> +endobj +684 0 obj +<> +endobj +686 0 obj +<> +endobj +682 0 obj +<> +endobj +671 0 obj +<>]>> +endobj +688 0 obj +<> +endobj +689 0 obj +<> +endobj +691 0 obj +<> +endobj +690 0 obj +<> +endobj +687 0 obj +<> +endobj +693 0 obj +<> +endobj +692 0 obj +<> +endobj +695 0 obj +<> +endobj +694 0 obj +<> +endobj +698 0 obj +<> +endobj +700 0 obj +<> +endobj +699 0 obj +<> +endobj +701 0 obj +<> +endobj +697 0 obj +<> +endobj +703 0 obj +<> +endobj +705 0 obj +<> +endobj +704 0 obj +<> +endobj +706 0 obj +<> +endobj +702 0 obj +<> +endobj +696 0 obj +<>]>> +endobj +709 0 obj +<> +endobj +708 0 obj +<> +endobj +710 0 obj +<> +endobj +712 0 obj +<> +endobj +711 0 obj +<> +endobj +713 0 obj +<> +endobj +715 0 obj +<> +endobj +714 0 obj +<> +endobj +716 0 obj +<> +endobj +707 0 obj +<> +endobj +718 0 obj +<> +endobj +717 0 obj +<> +endobj +720 0 obj +<> +endobj +719 0 obj +<> +endobj +722 0 obj +<> +endobj +721 0 obj +<> +endobj +724 0 obj +<> +endobj +723 0 obj +<> +endobj +728 0 obj +<> +endobj +727 0 obj +<> +endobj +729 0 obj +<> +endobj +726 0 obj +<> +endobj +725 0 obj +<> +endobj +731 0 obj +<> +endobj +730 0 obj +<> +endobj +733 0 obj +<> +endobj +732 0 obj +<> +endobj +735 0 obj +<> +endobj +737 0 obj +<> +endobj +736 0 obj +<> +endobj +738 0 obj +<> +endobj +734 0 obj +<> +endobj +741 0 obj +<> +endobj +743 0 obj +<> +endobj +742 0 obj +<> +endobj +744 0 obj +<> +endobj +740 0 obj +<> +endobj +746 0 obj +<> +endobj +748 0 obj +<> +endobj +747 0 obj +<> +endobj +749 0 obj +<> +endobj +745 0 obj +<> +endobj +751 0 obj +<> +endobj +753 0 obj +<> +endobj +752 0 obj +<> +endobj +754 0 obj +<> +endobj +756 0 obj +<> +endobj +755 0 obj +<> +endobj +757 0 obj +<> +endobj +750 0 obj +<> +endobj +759 0 obj +<> +endobj +761 0 obj +<> +endobj +760 0 obj +<> +endobj +762 0 obj +<> +endobj +758 0 obj +<> +endobj +739 0 obj +<>]>> +endobj +764 0 obj +<> +endobj +765 0 obj +<> +endobj +767 0 obj +<> +endobj +766 0 obj +<> +endobj +763 0 obj +<> +endobj +769 0 obj +<> +endobj +770 0 obj +<> +endobj +772 0 obj +<> +endobj +771 0 obj +<> +endobj +768 0 obj +<> +endobj +774 0 obj +<> +endobj +773 0 obj +<> +endobj +776 0 obj +<> +endobj +775 0 obj +<> +endobj +778 0 obj +<> +endobj +777 0 obj +<> +endobj +780 0 obj +<> +endobj +779 0 obj +<> +endobj +782 0 obj +<> +endobj +781 0 obj +<> +endobj +784 0 obj +<>]>> +endobj +783 0 obj +<> +endobj +786 0 obj +<> +endobj +785 0 obj +<> +endobj +788 0 obj +<> +endobj +787 0 obj +<> +endobj +790 0 obj +<> +endobj +789 0 obj +<> +endobj +792 0 obj +<> +endobj +794 0 obj +<> +endobj +793 0 obj +<> +endobj +795 0 obj +<> +endobj +791 0 obj +<> +endobj +797 0 obj +<> +endobj +796 0 obj +<> +endobj +799 0 obj +<> +endobj +798 0 obj +<> +endobj +801 0 obj +<> +endobj +803 0 obj +<> +endobj +802 0 obj +<> +endobj +804 0 obj +<> +endobj +800 0 obj +<> +endobj +806 0 obj +<> +endobj +805 0 obj +<> +endobj +808 0 obj +<> +endobj +810 0 obj +<> +endobj +809 0 obj +<> +endobj +811 0 obj +<> +endobj +813 0 obj +<> +endobj +812 0 obj +<> +endobj +814 0 obj +<> +endobj +807 0 obj +<> +endobj +816 0 obj +<> +endobj +818 0 obj +<> +endobj +817 0 obj +<> +endobj +819 0 obj +<> +endobj +815 0 obj +<> +endobj +821 0 obj +<> +endobj +820 0 obj +<> +endobj +823 0 obj +<> +endobj +822 0 obj +<> +endobj +825 0 obj +<> +endobj +827 0 obj +<> +endobj +826 0 obj +<> +endobj +828 0 obj +<> +endobj +824 0 obj +<> +endobj +830 0 obj +<> +endobj +829 0 obj +<> +endobj +832 0 obj +<> +endobj +834 0 obj +<> +endobj +833 0 obj +<> +endobj +835 0 obj +<> +endobj +831 0 obj +<> +endobj +837 0 obj +<> +endobj +836 0 obj +<> +endobj +839 0 obj +<> +endobj +838 0 obj +<> +endobj +841 0 obj +<> +endobj +840 0 obj +<> +endobj +843 0 obj +<> +endobj +842 0 obj +<> +endobj +845 0 obj +<> +endobj +844 0 obj +<> +endobj +847 0 obj +<> +endobj +846 0 obj +<> +endobj +851 0 obj +<> +endobj +850 0 obj +<> +endobj +852 0 obj +<> +endobj +849 0 obj +<> +endobj +848 0 obj +<> +endobj +854 0 obj +<> +endobj +853 0 obj +<> +endobj +856 0 obj +<> +endobj +855 0 obj +<> +endobj +858 0 obj +<> +endobj +860 0 obj +<> +endobj +859 0 obj +<> +endobj +861 0 obj +<> +endobj +863 0 obj +<> +endobj +862 0 obj +<> +endobj +864 0 obj +<> +endobj +857 0 obj +<> +endobj +866 0 obj +<> +endobj +865 0 obj +<> +endobj +110 0 obj +<> +endobj +867 0 obj +[111 0 R 112 0 R 115 0 R 117 0 R 120 0 R 122 0 R 125 0 R 127 0 R 129 0 R 131 0 R 133 0 R 135 0 R 137 0 R 139 0 R 141 0 R 143 0 R 146 0 R 148 0 R 151 0 R 153 0 R 155 0 R 157 0 R 159 0 R 161 0 R 163 0 R 165 0 R 168 0 R 170 0 R 172 0 R 174 0 R 176 0 R 178 0 R 180 0 R 182 0 R 184 0 R 186 0 R 189 0 R 191 0 R 194 0 R 196 0 R 198 0 R 200 0 R 202 0 R 204 0 R 206 0 R 208 0 R 211 0 R 213 0 R 215 0 R 217 0 R 219 0 R 221 0 R 223 0 R 225 0 R 227 0 R 229 0 R 231 0 R 233 0 R 235 0 R 237 0 R 239 0 R 241 0 R 244 0 R 246 0 R 249 0 R 251 0 R 253 0 R 255 0 R 257 0 R 259 0 R 261 0 R 263 0 R 265 0 R 267 0 R 270 0 R 272 0 R 274 0 R 276 0 R] +endobj +868 0 obj +[278 0 R 280 0 R 282 0 R 284 0 R 286 0 R 288 0 R 290 0 R 292 0 R 294 0 R 296 0 R 299 0 R 301 0 R 304 0 R 306 0 R 308 0 R 310 0 R 312 0 R 314 0 R 316 0 R 318 0 R 321 0 R 323 0 R 325 0 R 327 0 R 329 0 R 331 0 R 334 0 R 336 0 R 339 0 R 341 0 R 343 0 R 345 0 R 347 0 R 349 0 R 352 0 R 354 0 R 356 0 R 358 0 R 360 0 R 362 0 R 364 0 R 366 0 R 369 0 R 370 0 R 372 0 R 374 0 R 376 0 R 377 0 R 380 0 R 382 0 R 383 0 R 385 0 R 387 0 R 388 0 R] +endobj +869 0 obj +[390 0 R 392 0 R 393 0 R 395 0 R 397 0 R 400 0 R 401 0 R 403 0 R 405 0 R 407 0 R 409 0 R 412 0 R 413 0 R 415 0 R 416 0 R 418 0 R 419 0 R 421 0 R 422 0 R 424 0 R 425 0 R 427 0 R 428 0 R 430 0 R 431 0 R 433 0 R 434 0 R 436 0 R 437 0 R 439 0 R 440 0 R 442 0 R 443 0 R 445 0 R 447 0 R 449 0 R] +endobj +870 0 obj +[456 0 R 449 0 R 451 0 R 453 0 R 455 0 R 459 0 R 460 0 R 462 0 R 464 0 R 466 0 R 468 0 R 469 0 R 471 0 R 472 0 R 474 0 R 476 0 R 478 0 R 479 0 R 481 0 R 482 0 R] +endobj +871 0 obj +[483 0 R 485 0 R 487 0 R 489 0 R 491 0 R 492 0 R 494 0 R 495 0 R 497 0 R 499 0 R 501 0 R 502 0 R 504 0 R 505 0 R 507 0 R 510 0 R 511 0 R 513 0 R 514 0 R 516 0 R 517 0 R 519 0 R 520 0 R 522 0 R 523 0 R 525 0 R 527 0 R 529 0 R 532 0 R 534 0 R 536 0 R 538 0 R 539 0 R 541 0 R 543 0 R 545 0 R 547 0 R] +endobj +872 0 obj +[562 0 R 549 0 R 551 0 R 553 0 R 555 0 R 557 0 R 559 0 R 561 0 R 565 0 R 566 0 R 568 0 R 570 0 R 572 0 R 574 0 R 575 0 R 577 0 R 578 0 R 580 0 R 581 0 R 583 0 R 585 0 R 587 0 R 588 0 R 590 0 R 592 0 R 594 0 R 595 0 R] +endobj +873 0 obj +[597 0 R 599 0 R 601 0 R 603 0 R 605 0 R 607 0 R 610 0 R 611 0 R 613 0 R 614 0 R 616 0 R 617 0 R 620 0 R 621 0 R 623 0 R 624 0 R 626 0 R 627 0 R 629 0 R 631 0 R 632 0 R 634 0 R 636 0 R 637 0 R 639 0 R 641 0 R 642 0 R 644 0 R 645 0 R 648 0 R 649 0 R 651 0 R 652 0 R 654 0 R 656 0 R 657 0 R 660 0 R 661 0 R 663 0 R 664 0 R 666 0 R 668 0 R 670 0 R] +endobj +874 0 obj +[725 0 R 673 0 R 675 0 R 676 0 R 678 0 R 680 0 R 681 0 R 683 0 R 685 0 R 686 0 R 688 0 R 689 0 R 691 0 R 693 0 R 695 0 R 698 0 R 700 0 R 701 0 R 703 0 R 705 0 R 706 0 R 709 0 R 710 0 R 712 0 R 713 0 R 715 0 R 716 0 R 718 0 R 720 0 R 722 0 R 724 0 R 728 0 R 729 0 R 731 0 R 733 0 R] +endobj +875 0 obj +[735 0 R 737 0 R 738 0 R 741 0 R 743 0 R 744 0 R 746 0 R 748 0 R 749 0 R 751 0 R 753 0 R 754 0 R 756 0 R 757 0 R 759 0 R 761 0 R 762 0 R 764 0 R 765 0 R 767 0 R 769 0 R 770 0 R 772 0 R 774 0 R 776 0 R 778 0 R 780 0 R 782 0 R 784 0 R] +endobj +876 0 obj +[784 0 R 786 0 R 788 0 R 790 0 R 792 0 R 794 0 R 795 0 R 797 0 R 799 0 R 801 0 R 803 0 R 804 0 R 806 0 R 808 0 R 810 0 R 811 0 R 813 0 R 814 0 R 816 0 R 818 0 R 819 0 R 821 0 R 823 0 R 825 0 R 827 0 R 828 0 R 830 0 R 832 0 R 834 0 R 835 0 R 837 0 R 839 0 R 841 0 R] +endobj +877 0 obj +[848 0 R 843 0 R 845 0 R 847 0 R 851 0 R 852 0 R 854 0 R 856 0 R 858 0 R 860 0 R 861 0 R 863 0 R 864 0 R 866 0 R] +endobj +878 0 obj +<> +endobj +109 0 obj +<> +endobj +879 0 obj +<> +/StructTreeRoot 109 0 R +/ViewerPreferences <> +/Lang (de-DE)>> +endobj +880 0 obj +<> stream +xœì½ |EÚü¯¾æ¾3É$“�™ G'™$C ƒ‘4 A ¹Ô �KE.Ea•Ȫ(Q‰®g–eÕÕáX ˆŠ+*Þ÷ ˆŠÊz²�t[53!€»ï¾ß÷¾ßõ›§3OUWWw×sÔSO=U“`'€\8&X2Ü~ÇÛiÐpñ ÚºEuKßÄV@Ü?õ²És/x(XpÛ¦.Zàû_ÿ1àz0æLŸ{éeC}p)�u0N¸tòü¹ð"3}Ë¥sOÏ0÷ÜÌn´Ïh¼ìê?\µþyàá ÀÓ×Θ6¹Qk¾­ Ëô�1cÚä«ëG€LÐ}Æe ®ž÷Y^Ð +äïšsÅÔÉ�]û´ è9Hm¸lòÕsíšå@2ø.Ÿ|Ù´~¼äR€{àÞ›{ÅüÚ(\�rz}î¼isoÿ®ÛÀ´ +Ð+ ࡇ.è4 6ÀävÀ‡‡ ;‚¸­Æ XmÚúÌßèìPÛO§fŸ|Ó¦Ò’®` +²ñ¯Ï–/¹u’­ügxõìÊã?—?NÓ/…1¯ŸšÝ¾Ý¦§0t>�.$·B„^lCñÆRþMLçœz‘3‰œÀ Ç/èÇ—xgíŸ +|âÛê(bÑ?Èq>�^ ¶PÎ Gß <{k +ÜO儿}ÈA/T`&áDñ >Ç!ü‚vŸÝçòßÖQ¡iìY9è 1 “£ÖMÓ¾8ãx[[«=¬Ý¨]¯­Þ�ý÷}s·ïl¾ýKÐam­``:ÚÏØ16žç`Åàxž‡yñ¼Ð¥Ž¬ñ¼+€1˜†Kq¦á" Å؆y˜�™¸—ÇR¢Š»Ôò±z>Œf% 1“1דּ³kÿ>” %(…µ˜‰©˜‡+0W`:À‡*\�y˜Ëðd,ˆ¿¿> ÄÌaK1 0?þöù¬­‹0 �¬¦Fö‚i˜‚y˜†«àÃ…˜‹i¸c±˜å|ŽÉXŒ+°�½y®À¥¬E>LŘ‹Å˜×ù._gÛ‹bkÇdÌÁ\Ì€C0—³gLÅìxÝa¸3'‡`!Å1ºÆbf2ZæüËöLgüð¡31sXédÆ�3iŒ=çŠ8¥>ö–…˜‡©ŒÞ—¯b²¡% q9÷|X€¬¬C1–qg&»ïrÆßóØýÓX�i¸ S·öÅ[”¨ëcåó™lgbn§OÓA¯/ÀdÌÄÌG!³#„Ì8¡× ‡ASa€QSadØ“¦Â ³Ö ¬Z¬°ií°Á®µÃ‡ÖœÚ)8‘¢�‚‹á¤i§à†G;‰T†Ó�®�„ÚI¤#Sû2�¥ý^tÓþ�Ldkÿ@|Ú tƒ_;�läh'àCwíWøÎA®ö+ÈÓ~EwôÐ~E.zj¿ ½´_нµ_¨=Ð~F/äk?£7 +µŸ!#¨ý„|i?¡ÅÚO(D‰ö‚ ¡�ö#ŠQªýˆôÕ~Dý´ÑeÚ(EXû}Ñ_ûýP®ý€2†Ã8_û;úc€öwœ‡ +íï(Ç@í8ÎG¥v0H;Ž +TkÇ¡ Zû1Xû•¸@ûU¢}�Aª}‡j Ó¾Ã`Ôhßá ×¾ÃÔjÇ0jÇ0 #µc¨Á(í†c”vµ­ÅŒÕŽâB\¤ÅH\¬Å(\¢}‹Ñ¨Ó¾ÅD´o1ã´#¸ãµ#¸´#¸µ#¨c8‚IÚ7‡ÉÚ7ÏðLÕ£�ÚaLÄ4í0&aºö5p©ö5ÕíkLax*fi_£³µ¯¨Fj_a:.×¾¢öFû +30Wû3q¥ö%fažö%fc¾ö%æ`¡v—a‘v—3|®ÒÑÞ®•X¬}�yX¢}�ù /ÀµÚXˆ¥ÚA,Â2íà?ÇÁåÚA\�åÚ,Æï´¸†á%X¥ÀµX­ÀR\¯íÇ2Ü íÇu¸QÛ�åX£}ŽhÒ>Çï°Vû+ñ{ís¬ÂÍÚgX�›µÏq=nÑ>à ¸Uû 7â6í3¬ÁíÚ§hÂzíSÜ„fíS¬ÅÚ'ø=îÔ>ÁÍØ }‚[Т}‚[Ñ¢}Œu¸[û·áícÜŽ{µ�±÷i¡÷ká< }„;ñ öîÂCڇ؀VíC´à�Ú‡¸µqþ¤}€?`“öîÅŸµpÑ>ÀýxD{à/Úûx�iïã!<®½�VlÖÞÃÞˆ-Ú{ø¶jïa¶jïâÏØ®½‹‡ñ„ö.A›ö.E›öþ‚Ú;x ;µwð8žÔÞFOkoc3Ã[ðŒö6¶b·ö6¶áYí-üÏioa;ž×ÞÂxQ{ mxQ{;°G{;ñ’ö&žÄ^íMìÂËÚx +¯hoài†ŸÁ>í ìÆ>íu<‹×µ×ñÞÐ^Çó ¿€7µ×ð"ÞÖ^ü£½†¿á]í5¼„÷´}Ø‹÷µ}xhûð +>ÔöáU|¤½Š}øD{¯1ü:>Õ^ÅøL{ob¿ö +ÞÂí¼�ƒÚ+x_h/ã]Ò^Æ{øR{ïã+íe|€¯µ½ø‡µ½øG´½øßj{ñ ¾Õ^§8¦½„Ïð�ö>gx?ŽkÃü]ûâGm¾`ø~ÒöàKü¬íÁWøEÛƒ¯qBÛƒÃ8¡½ˆopR{GpJ{ß2|Ú 8Æðwдð=!Ú 8Nˆö<þNxíyüÀð�DОÇODÔžÃÏD§=‡_ˆ^{¿ƒöN£ö,þALÚ³8IÌÚ³8E,Ú³h'6m7:ˆ]Û •a�8´Ý;à#¶‘!Ê)–ÓVngøG†ÿÎðq†¿gøÃGþ„á�þ€á·ÞÇð« ¿Ìð^†_bxÃ/0üû~ŠáXË63ü8Ãk¾‰á&†×0\Æp?†W1¼’áë^ÆðR†§0<’á! [)>-Á…ÂQ(·Êdƒ%üùþÔ´ÌwÞMMË\rmªwɵéo¾•š–¹èªÔ´ÌËæ¦¦eι"5-söå©ÞÙ—_7/cÁÂw楳RÜ™Óg¦¸3§ÍHñN›±úÊŒôù©×T¥ûW¥ûÏ Cî†n’Ü%|… Èƒù­.-{ÿÓ¯ ØÏ°O8¾Õâ+m‘-¦”ðm·ðýVoN¸b Eø ·?‚ (Ž¿gm>¼Õd=E.ú§¼œb2vks÷ìŠgHå?ï°‘�¸Ÿ §íßö÷^rXi#ÊÖó«bi÷ž4­ØšŒ¥i™4=_IÍ“Ã_}ÍËÊ×ù…aåkofX›�nÓv+©¯aåÃ^½ÃcFsò胜싚,á„#DñrrG»Q>õ¸(ÿp�“Ÿ‘“•�ÒÒÃää6m÷ÖƒEÅì!ŽƒYÝÂÊ{iiáoŸæä§[8¹�¬Ú²Á(·‘±dy,¹N±m0Êwo0ÊZ8¹¥Y”Û´ýO|—’¾mOóŠù'Wjøh³ ¯kîžM LS=éáéSÉÍ\¬BsnÏpY?ÈýViÙm„l_ÊÉíåd)ßÂÉJ)ß’Ý=ÜF‚[–òr)ÜºŠ—ßh#ù%Êié¬Á†rrÃÊsiiôlëî /MŸØmw†÷½JÛ±û‰½�@øå—X^I=n²„�,ãä¢)f³TµùqN~|YŒoÛœìOõèÞE®ÇÈdõ–&#»3sm·ná5M‚Ü´Ê(ß´Š—W\Gä¥ËyÙª¹§xÒÃSVùÆUD¾a‘W¯"òÊU‚üͪ¬âf®"=Vo?·§¯Û]êvöqÛBns‰ÛPì–ŠÜ|Ð�B÷À<2ŒÔÀ�‘d8æ’a ¤?96RF°’¾¤¬0‘2œGÊPóÏÏ+¤ L¤/Î#}1žô;éB¤-¼–=ÐOŒÄѬD":X1›èA`"zœGô¨ùgú$Ñã[¢Ç)¢‡1b<1â&bO$%‡×²ózX{ö°•öµ†úÚzËÖ|Ù–°vغe[}Ù60(þˆ6ÒŸ––UÏlh#e´;ïËÎûbÏêÒ +2ÁÂsšvn;) Kþ¿‚ùóåù ÔÑ+�z¢5c~ƒó7(×GŽ®¬‰êG×Dõ#ÇG3•5Ñ—F×DûŽ5*7ƒ«»™£HâªÆŽ_7ÐM ‘”£‘ôA#)A# ¢‘¢‘䣑˜ÑH4�„(6j�jc{ã��o<Þø}ã±Æ#�Ÿ4~ÔøAãÛ�û_m|¹qoãK�{_h|®qwãS�Û77>Þ¸¶ñ¦Æ¦Æ5�«W6^׸¬qiã”Æ‘�C­�ÿ)'NCä¿‹,‹G‘ +¹ñ$pW+ÑkkNcu„ÚKû™Õ‡º3V“Û§€…¢}ËG`´o»>I;&„àD+ÁMX�«ÔÇWôñ�;mŠÏ�§³\‰å˜òo"7ïá½ÿ0ÆÓ¢Ø„uñü&´v–ÓüݘÑy~'ÖÆ[¶’¥ëÑÔ¥ö¿‡ýØ�ûqˆ7Êç›ìkŒÚGÖùéV24Š‘uôÓ¦,ó{£(óG¼QŒ®‹vKœF"¿ÕÈ€¶û¬fŽ MöÍæôªAQ¤l†ù`nZíx¢(�ö”£\®=Šrö4£$å‡(qE‰»6 +癯 ·í/û T7Î +T7ÎŒ¦W56œæéñGý¾&_Óè:GÈë÷³F×D_U·Ùd¬ +TM3F +òÁ +°Ùhª +T™hAÔ˜»™˜–áÌÕý7sÐ[ +ò£NÚÜjú™Unjˆr¹�A~¿¿ ?ê:}¥MÛ½¶ë%D�r"çŠåb�ˆJUQ]¬¾™Qer7ù6çïnZÛfÇ”ÙÜhœ<¡.ÊOŽäoŸ[=cl4³f为(Ÿ[M? 3|T܃¢ÂóUÏð5Ñk3¢|n`úå�3¦5P5! �AùQCUÝ þÝÞ¨³ªî†ê¨CŽZƒ¢–kyù¦jÏL=mjºÁ½T]׫~Š#‘ˆ§ ß×T¨žÑ0¨ ¿zV%I°SlL‡62á(7MöE—O™Ó½Ékúïo²GÍ¿ø½~¿ßOÅÃnŒ³²±amò¬É”ÌêY¾¦›¦1R×2Òü~¯¯zÖ ú¡7úª£¸(ê©WW=#P}ú…¾¦Àd_”Ï=û^¿?š.Ó›šªi'7VäÇšM—O·Ÿö ¯L¢\nUTËŒe2ðUG•Ƀ"ñ¢x…qô6z¥aP$â�É�ÏõEu¹7ˆ…_}¢.7š"Ûý/ø òwä׌®«äeÔG¹ªºó�y¼Ç"ù5#;‹‰§ft]Sð˜7Æ£š1�šQ1-˜‘@ cc˜ë”¼ß‰×gOÝçñî‹ä nhjð njhšÜ¦-ŸðÙM›Íæ¦¹Õ >ÖóÉä6mçMÞè൑¨½aé_�ï£ú6xtMÔ5j<Ï`ߌÉ1cQð—yýŽH¢ÎÈu9ÞÏ¢ºª¨ÄúY“ýhA~Ô\Uç÷úSóÒ¦/óFíe´›ú¢¸¨®ÍŽ©LgšŘº(ç¥=…�äVÏg�ןPj÷FÅK#ù~?íC7µ)˜R�ï�.U;÷aŠw ” ‰r ôÊîÄ÷EôÊòÄ•ÎÛþ‚| ?ý[�îªÏMŽ€Ó2þ3sÛÝ=¶®M;QÕ—ÅÅíªªã½\<Çyyš3Ê»¯<š&³)O¢=å&{À÷F j—£bUÝnoyÄgwDá$ùÑ!2í5\®ý�À^Bm'RìQR%©´®(a&�O+‹W§òøª›âÚÕ•¬øÐ8ã·i‹ +¹ö@Ôü‹7Vßá P +_e&-n©sÓ¾äõÇj ‹D­ÔG­G*È�z«ê|QÒPÅ(–ñUûfPaG} ƒ˜ˆx»·iûQ³W竦U¼qµŽxcª}–®ýç¾ü&otÅÚÈŒþùQ¥·¿ ßW% ¬·Œ­‹s©ÌïEô]C))g^ïäb¢Î¹Ü­{ÆY—çÒÁ_3¶ó„>7:XN<*v~�ìíz:ä¬ËC—år}Ž¡”©mÚî2ïecê¢J¬¨ftÝRï5t<áHåæ¹qÔf…Ü8f\Ýf•;ì€ïƱu[8ÂU5TF6w'7ŽªÛáVÊÑRZHO|ô5¤ftÝNÏê{w(ÀrvU`ì|j+Ó'ʦ¶q±2{ìEyìE +8LmbW”DmSÛô±²å¬Œ \Ö)FQÑ+ÅÌY8ïfB‹¶ˆŠ~' [ÍÄB¼›—sU£YqY¾Ù xc5–Ã@”X o¼èô«/W·Õ ñ2‰D*)üSTË£\ÕÈ(¡lµS!úö¨/c¯·ÉNÍu4"S“ö%µƒ3äoÎ%7ŽìJÓ„hJÍèñÞ(‰ðwt9Và<ƒvR@†’ËÈò +ù‰ÉíáÍüþ#Á-<"¦‹õâ_Ä/%—T#µImºJÝúˆ~�!ÕÐ`øÊØß8Âø“Ù4ÉtÔ<Ýü±e°åAëyÖc¶ +Û]ötûqǵŽ×œáÎc„s„ó]×(W[J(å w¦{gj õÒÔÓ +Ò.NûÉSïy5}Hú³¦Œ{2:¼³½/fNÉlÏš—õÉo?ÇŽnÖÿâèÑy(Ý”n+OÙ¦.Çàÿ‘c|òHÉ#y$�ä‘<’GòHÉ#y$�ä‘<’GòHÉ#yüÿìX&öÆÆÄ¶®8–ò<ô[›'¸YXËÍ¢ûaq1“]èÁ‡p+Ù…¹|7í�0 #AÓË„‘X"Œ„U‰Va$®FbŠ0ã„‘˜HëÓ{ÅÅÄNŸCSþŽØîQ±R…;°Kø Å{°K0c»Ä¥˜(>†]œ»¸�‘"´bÿv邨%¶c—¤ÇDaI<Ý‹]‚‚ÅB1z‰×áa᜺ÅÈÚ�!Ì�S¸þ³iãGÇRÁ‰qÂ6lä· AØŽ!ˆnJYþl$Çq+9®õçO²üFé6Òr!Šz­Ç½„�ü4Ìà¶CnÀ­ü»°Š/ÁÃ?Š þsXù{Ï}ÿ¿Úþ®ç´}ÿ齿´�ç”Ý€[¶“ôÜ{Æ�Ö‘®À�ÄÒÿ+mùŸáÿé$! IHB’�„$$! IHB’�„$$! IHB’�„$$! IHB’�„$$! IHB’�„$$áß‚ô5¾G÷¥ +«ÄEà¡C†bÒ^/ŠÁ}Îpp‚û*ö…~G®ßáßÈÖ¾�ÛÞ1L\t²i½0â?æ–‹-ì %Å&d AááuA/q:žGEh_ :ÃÁúâ"â8üŽ€ƒôYG>]'¶¨É3ôCIf­º“»Xl�Y´›H-¡¨Œ#ŸxD"G“^o–jõv“T«?ÂÓ„ÊdÙxï**Š‹H½,“v‚ÃÎ�½M}™ô]¯îQw’)œŽ3�ËÕUªN5Ñw¦þ³ð {ç°�´ã[M¤mÚqÅd"µý ìÍ|›vü¯ôEGômÚ ÅB_­7鈈#„¾Õá Ó6t¾ÜÕùò‡o#eêžõ¤LÝI‘ŸÉ?ȵêm¿vüƒ¾û0®Ö 2Lªøy« ·ê#t:áéˆMG,¼V"ˆ•¢QGžÚ´ý[ÍR­”„Ãûdb?OpÒÄzÙáD8œá±³+."”¹þR¿#äð ëÕ9«ÕËÈí«ÉzÎI3דÛÕË@ÐC=AÊq<ª•¬0O œÂoà3Kµèi’j±œSéC¸6í}+M·›¤Z.(’ú ,Ëûdö_2öQŠësù€‹”ß¿pá!5…¥´ÝJNr[¸%à‘©Ø92&r!GèoyÁG Ö묗isKýÜ–ŽÏ¸rr½o®v„ŒÄ+0!Sq@ŠŽ‡Ëðx6ä9>h!õÁ+ B..JËÉ+íÓ7T’êN‘>/8°0XY™ZYXXUUXX í�æä ˜^¦+–«�Ý„Zú³h\0ˆŠ`q‘ë ¾ cäî1±åWK7‚ÃÅÚ×|»¸vxÐKqc�Ñá^£m®–ˆM'¦µDÄtTÈðTÈÄÌ8æ )×S8I +ääåq¥}œ}û†|¼ÝïsØý>¾}à’­óæüyAyÅ5]Hžß¤~ªî#¤÷ö6õÈ3S'm'†GvßóS;\cǺŽ'ApàÞwŠ,ÅbÑ¡‰èu:bÑ**œá`ˆ�í ù+¯+À÷ qï´º†ŽŸVÔwñÂy„÷]ÅE½Ím¡Šj?åêePÜ‚Þ(Wºus­1ú�ü¬5ŠYV_Ö†ˆÏc4ŠV±%bÕ¡Bv ä V0½JÐè +)."} +¹@ޕӕöí*鯹S¬œÎÊ»ýniŸ\¿ŽxîõõêxŽ/¿âÊË+´¯yUÜú`°ÒÝiÄ¿¿¯œ¾F1fæµD223‹lNgŠ-…uê¢.�šµžÚóp¬—Ðî'Z]ïà%inÖnwˆ5Yïî”n¥ŠW]½qòœG®0fë×�»bICËåç SM¼svÿMy̨<Æp¹÷ð™Ê€éCz’¿ÍŒ.¿`ÜŸÚð$)þpQ¯»Ãÿá³·* ¼ºæê±…Ζ”~¸Ã«:ÞZu.- ¥EB:i‘ˆ$‘.´pñ÷pAù… :ƒBJÙAiáVu\ËOà‚êÈfR&‘Æ;Ônà°RûŽ/f¶&„ÁJœÄ.:l)¶æHŠ€üîùÑñÝ]¾ïù¢Ç¯ëIzöt¤¯2KÔÎp„ƒõÞ9À”̾'†‹‹&M¬'Ì 13‘ÀS¬‚;%5�Ïé‘WÚ§5ê©î�D.4XCÃÇ å*cƒ¾vCëðë§õïsá„ û¤š.ÈÏ¡ÿh¶Àï÷ç�›3ÆôR.7æ¢ú²¢a%é«£ =/\4bØ‚ÉcÇŒë[X“¡6ø|…Ál¿?;XèóQ)ÑßBqŠ-ÐÁ†7”|�Žè%b°›QËé F1Y¬6^0ó1bç ½"xL2œ´i‡·ÑŒ¹MÛÍ2&Êý0ÍY-£ šÌz‹TkVœ©CÌ’¢·q¼�_±Ù$žˆz›ÙÄsVƒÅh¯ÉÕ b›vX±šÌÃpˆAG((Ëõõδ0‚ÁŠpP†Ýó‚ý… û§²ü‚3�ŠO&²,ÓqÃîÝÖÝ»í7Xwïwï&r}½?Àûù ¹ø¼IÇ‹Î�wuÜË“\Þc÷0™£åsr›JÝŽ[¸©Ý*Ïtü�Ž,»!(¶À†4dãÅl°ÉDx�Øy�¶²Ð„Z½Çáð¬‹8ééë"T%ÖEoH7K”h‰vk‰vë¥YmÚñ­©–¥æXªMRmV–H«‰K¯s7�Cºõ&©Öíö;Ò)ãÒiQ:-J_†6í}Ú´_è3è¹b ³"¿bB-5ò7²,Sç@–뿑ÉGøÜ�ŽÎ5@YVrø™ѹSSÝ)B€w„Jú–öñ'Ò]ä®�­Z°î1õÄÞ“�6Ý«{öÐú‡Ô�bËöÛ®Ù–'8v¬o;$rjñêÅov´t´¯½F¥ÁDí°°VÜ76(¶j# +2Œ'¶4bæ TIÌfÔòj�&¸iA• µn·‹s­‹p.˜ˆ™7™,z˺ˆ^€©ˆªT…‰˜LivÊ\;åš}©h·Hµ"e’è21 Â(¦³z9ÔÅí‹çä�#ÄÔ…Žúú\)àƒÃÌs¢ÙPI?aí}ÇÔ×Ô[ɽdÀ«×·ìüUÝG²¿ùàÞò±¤‰(¤Žl:oëÅêvõ¨zRÝÛ@{Ð.@ïAw*yà xba`J¡§J!Pq +´¥§°ÔÒ¦ýDÅh‰«†%.VK›vDñ˜¤Z‹Åa ¤(©ªG†eLö1Pã: n¥*�5“},Û9¨QjAÅ죤òŽ¥Ù¿‹¬å~QÇ«÷½óÉ ý;¶‹-Õê6õ2‘;u)$n®¥Œß&¶À„¹Jɰ."qDZ'IFŽ×Eˆ“×E8^Ð zÀ"iƒ�´ÁFÚ`ã2§Ž´i'Ù8BSONkk(D§¿�*Ô‹‹ü~‡ßÿðÛ:ôÜopRÇIn¤Ør—¸S5ÆÛ·žEJ.Uòi[l1ñ§×9õd]DÏ `â$Ú.‰¶K¢í’–éÚ´S´]º8×iJÇ[]Ð %t¶-Þ2g˜…5X,ƒ6)àðóëc ÚÄ} rªýÎŽÕôÇ¡bšO=­t<¦`$6�ØE¦óç™QëI[ñxôHhºÍ”m⌧u=-Ím7™ P) TÄ´ÁXænÓ¾£ ¥©’Ãl„÷Ìž@;ëö3{Bœ68»K„œaÆk0ë™+ù£;°øèê uŸºšü‘T¹ï±ïþ®¾Jºý¼i‰º‡œr YK“ZòÈð�—«ÛÔêquo¹í®˜|„˜þ,V†èùu½$ê×ÉEÄ):�¼Ä@i€tšÐë!” â¤êB,z�Öh �Ö–‰qC(Æ{�<ËðÉ´ïWTT$)Ü£ñÂ�ðÃ¦ŽŒM›¸¯6q7wÌ[:nâØüe@YdƾËqr<**Hð»=D7m[@�¢æd±f\¦¤Ñ2LˆÍ@ì¢$éyýºï2ñ"µí™´}"h§íôÐ3âŒÚ?â:glÓNR�3­‡³ÓZ—e6S¢rJtÞz’’˜�:!Nþà¯cûö½ø‹M§©_z6ö +ø–8·[`Ç=JæR+ée [¹e„ô&ý g÷˜-d¸�/­‰q›à +Ê�V;EÚmx«ÍFìsA@/¦S +à4ŠT +±±ŠJA¤=èx¼�P ´ éYÿIPA‡íúp(¤²`Ã4¤åúz:&“¼&’Pß~$D„�t¼—­6m¬ÎófÇîÖ\KDZ¥cbª—»²cd\F|«Ø×+"0ëø½ÕΔáx\)ØËRKo5²tÿVKw+cöQÔ�£Y$®Y¤Mû�ÑDÄ®¶*n\™m­ˆY¨�ÃÏ·vx6qŠØrR�·Rš'¶À‹�Šûš ²ÐEò\}]ܼT’Ê8mI0Ÿy;ýh.5ƒˆ‹ät8�¼pŽÒdЂ Ú/¨ö"Åí(�þ’,åšEªuYºØJ�}™£Mû�ÒãˆkœƒN!dY—ÈÎ0�PÔ‡aᘈ¨xXä´Þpôé�t„I*ÕMeERÓ˜´¤y² +—š:ZMs§ +ú?#m!›$g�gß⟛÷ÇɽOmM½øùvEli÷Ì/êÏŠqFTĸðÅr•™Ì3‘«9²”í1Fjmú8_N(AæC:‰(Œ¼�ØÎµâ üp8(«ôžt“Tëp›D=­«§uõ´®~™!ÎCBWcÃiB®á’87ØIÁ3X8¶S]O3!¯Ô•×:ºe:%ý¦�¸× +,‚q“p•,¼óÔObË©»g hæˆÍº‹‹[ØÌt…Ò Îu¸$óºˆ$œ=¸/uÇUØŸÒ”9‰>w‘›s»Ómô½ÃFï°-å©¡ä©å�¯9¤Ÿ¼˜t–' ÆÌ;Ã$nß)æTõuuYD*I/2[]«¾sPýЏ>=Jìê ¹�T’qäu®ú”ú€:C,Qwª¿ª¿ªO‘>ÄM2I߸$ô[`„·*½K„³³`ƺˆÙÌsNÞ´.Âó:ªË:}§¹Xj§ý“ª-í ݨêRJìö3¡ÔÆ”�RK–qñÞɵi?1)r þŸá÷v!˜ž1'—-ðP/q÷Öî'�›HÓWê×ß?}ì 5Êuë8(¶ìõUµ�;Ô±í¾u$“ɯBÊ· ÝÑWÉpÙ‡ÙìO\vèõnæ›f­‹8áBÅžN†‘BÁ�}›\оy{¿¾~_ZªÃ®“tÄŸ+ñ9:ÉaOK •ôëë°÷ÈãTõ�O<¹‹ $=·oß™_BnW_W˜5ïà₩éÓ£7Þ¸j•gï^RIÆï{í¥—¨ ^-¨Ÿ¿ìKzHýU=ñÐ--ÄM²ZîNHc·Ø‚LôÀ“Jÿjá+±qÄ,¦uÓë»­‹èõÆ,SÖºˆ‰'Æ4ƒƒÊ%Ö“¨\º/u,æÈ5„pgŒy,—É9:<0;Õ«Û9Nª1ÞÛŒqŸÏ÷ùŒT<Ôü”Ø÷Èõi%t0`ªŠ`è¬Ù �×O¢Ó·B>ÀÂÆ‰Ð¾»ŸF‰0­°û¥ãþk‚M›È£ï[XY0fáÐî¥EÁ¬ŽÍ‡*f�Èoþ=¹­ìÂ’´Ž{Å–`Ã- 5K§T»{¯² ‚ü˜Žy\ªÌ_‚ÅÚaþ3q Jñ–â®)%à +Éà\2(ƒ v“>3j{SM5£¶'aP›N9r±µzÕ�ïÊ<Å ð3ïõ(ž¬u�BL<).–ÖEŠ]^o~åZetetÎR—‹äÓ²|Z–OËò—¨A´Ñžo C?¢��B¬KÇç[Ì9µ¿Àü9dxìû&M¬w„Jñyq|À­ïWÚ�g³¹B®G! Ñ%‚Å)ݸ´Ó±ÌÅ9“fÎ*¸{ChÜ’ j~×Ðïâµ[ëß™|Ýßú]~qß^#ç×Ô6Í8ÌïÛ¦ç4Ψ￧[‘ß9Nÿ‹/Ø=oÄÄ«FNY7±0ôT]ZŸÑçõY9 7oìôkGκ}B/“;½ÁÌæIËï$¢$®‹Ht g +‚6í¤ÂÖùDX9^¬|]¤> GÙÃQöp”Ü2ž:Ç|bÕ“ç zƒBEЦíÞBg²£Óíe,#öƒž`!3 Ô«ª8½ )˜Õ)›Ô©ü"wR9ê±>¬Þ[`Á5J1gt9NïÔs&�3PwÝÌ‹ü¹c‘‰˜Ì©Öd¢¤™¥Z“MG+éh%­¤[&´iªb ‡[1�701ðP—µÓ^Ñ•:GÈK„w;>ïøeY@.ÝÄ êhåªùËÚïU/ óW‚À _1û%ŸÄDt\5µiol5“Zu|,±TÉ1‘Z¡ÈLjŸ…"© ›H_�,Ð]OÚ»©[Çœ¾ÓFÚ¤ãxž3Q)˜Ú´vÚ¥iÊ‚—¦žÔPÇ’þÆ)‰­“Bj�*¦'èõ<á鬜Æ#¨kXBIV8Ãa¹Óï�KA‡ðUDZ'ž$¿w}:i[NN[NÝÒoHyµpõ¹2]õ¹È@Å·ÄKg�E)d‘…Ì7“…²„#^Ÿµ¥Ðnk0¡ÖEƒD­™rÇhF­QOO†X +tIµ,´Hg²ŠÁŒZÐø—7Î]oœ»^zƒËDjÍ”»fÊ]³ÇD¨»ù’’C›VF±�á4 nc˜ÍÙÒâC¿�¾$‹jm®4Éhâ�).‰pi£-ÍG]$úBèû•3jm”(›”Æ›€,S0‹ÔÓˆYÌ›“¡Pì/áÓÄ î‹Ÿ õ1 1Ÿ�ù~�>_<Õ5¨³_T?t ‚”¢¾ÿ¼:a)v‰¢˜A΀Ùõ‚�Fr©xOՅßúJl9µ­º¦t¼0üÔ£áÚ>ô?É1i -b ÜØ­¤/q‘kœd¡…,0“z²PGsd !.JŸƒÅ}ë}ñQh¿b¤9—>&µL&F*!¶ÂfF-¡×lž„�ÍÆ/ÆÙlK|ŽL¹+L¼Ám4¥°Ux Íd`sPÇpÕÑ=ŒN0®+Ó⼊»†ŒW]˜%´ìS·Ûœ‚Ñ£>úÙ¯~�äf¥ +>)~V=ŸËM/L+êø˜{“Û«êKJ*óÈOáx?n`–gšâŸ/Á@ˆÈ;-¼È8jM8jM8)fã š*©Ì Š–¥€MbtK¢ÏFêƒ!Ú×¾q$¬!õó©[´�.ÞÔê�îf j�Mjè2PÊâ�™d¤ØÒ~uÇwœ•_ÛqµyxN_îFú?î„FoÙ¨Xͱ&‰Š¦˜æJQ އ ðýÅa"g3Å|‘OãE"d]DpB¯ÓëhxFb¤¡æEJxvÄlŽŸ¥Ð¡ÏLéÓéˆ ç4¬îL ƒ‰ÙXÌw¨8í;PE–ejI Ý@„Æö6¾Ï©7ù�œqÞ©¼»‚ÂÛwQûõ}~´6Ù‘ãc\lLDvü¶:—WjÏ͵ÇÖ|�N’Ü)©Ô/àW¶ +³ßØð2š„ÞÞÑ|ç‹dfë¯óÌŽ\sßým^O²ƒ2—mjP·>Ç>êÒšI�¬¤^æFí°0V< +;²p�"ÃHìï&n›»9bKÕy ÞæˆÁ.àìxøJOÜ•÷PWÑÌÒ/” “TëñdKœ“VwÒêNZݹŠ�õ:èsTå ›ëdw Ô�áûÇ9Ð9ÖOšXŸpÇ¢ ¡§ÛÎrø8át¡_"#„ÖVañ7/~~òÃWæüñºæ?ß{Ã-�­_/íhxU=öµª©¯pCo¹nó¡Wyîo hÐóSÜX­Œ¯ã‰Á–nãLp3Ï"ØÍ.•ôš#z;|fR ˜¨MœIª�k"&Zf:{¾¾RL¡ŠV]‚xÔC�éú[—Ùx¯¸ˆúɹR ¥}wêbRíÇ7,zðcõG’óà —ÎûÝö>ußõ‹‚�¬/:H¨dÓÈOl{#B¥Ø ìq)Þ­”Ù$b—2™Bs^ÑK5˜¸­n“»9b²Ûà°œ XEĘ枈kîq¥7ÓÜl%ÐC ôP=+õ´wè­T™©´õ]…ØUŸ;…È’â"±�+1#4äñ1ñÅD)Ø[…yß=û5Á»o7­­­Ëîxô�7­yä±=ÄuL%%¹kN}qçµ�~òÜæ}{bTóŸ +8‘ŽÇÿjÓ»$ÑØP�É:„®¦“æHzº`3ÍCª-µ9b³_§#ºTêQñétXébë^f wÅýW| wQ€º².—7�Ù6BóäWš)ÌtÞn΢ýá¬)¬üM dž»hÙtfäO¥vwrÃéî‘GY¡ã?í(¢-YwÃgž ¶½{?ÙDn¸zÁƒ.òþ#;çµL!iß“µýëÒ›ïÝx=µ}¥w¿Ä!M;`×v+ý隌àôfÑb17G,6'×q�T5ƒ 0Ã,Z ”,%Ë@É2¬2Æçò,�Mü¾RŒ,™ê¦’ú.QÎP �ú×Ë¡àé=.Ž® âÎ ä¸Ý±ýB�Ò[äNãî/:ÿÑãÇzüñ�7Õ䋼޻¶}-¿`íÆžtR‰ªÝ»à�¹xY±ÂÛ¬Ä.ugº¬§ËÍÌájcDÚh�Ó5G¸LcÀhŽXRÓlî,WVsÄeÜé©ç¸ù« b<†D‰cs4™Òmè!øhU­ê£U}+íTÂvªèvªèö`�ÓŠÞÅr§…!ù3¬sl• +“êsí~ùõ9âêowRõ/=[ý;=}ó_þ@®~ûÌ7§>z½Qhmýã’»ýã�MÝñê�ÖrÛ܈ã+"’>oîxýÎkûôo�ì{�jÁF€?!…w(=9;áìÄ.·ÁmlU}#ΰNGK=ÿ„Ò�õ|—ùìÜ*cžgžº�…à\g.·œmÄ+pÀa§ÄÆ£Æ!Ÿƒ?ÑúÝÕ­­­Â¢�È8nQv¬ëØ%í˜õœ: ýï·dºø!Û‹1B @š:V›Å0…·˜ù)ZÑÙcÒ*K¼m–xÛ,‡=¦°t®ßÚZ±ç¬]"®€#t2À€ü‚ŠŠ@k«(”ž~aþ€“íôß×rZµk‡ÌVúp± ×Ôñ“�-4GøÌÔ)v#ìS´"é&_g$ÆxÃŒñ†�鱆ÕÓ–í‰5-14쉯øåv]1躗… {å©Á•Ã*÷·ŽŒ·[nW¿÷m( Óù㤳ý]µÂŒ»û5fr•‰zçK�d!æÄ|ˆ0÷™žåÐI8g‰[tmŒ&¡�"êÃA¡{˜ÍÔ,Òµó9Ë:«D!¾×¶³¡ÏÊ#6ºÓµíúøRBumâ« l—:áñÅ"ü‰¯Ôr:œ“ÇÞWsÉQ5E<Ú^AÞU�1zÈ0ñ(xôWR‰›ûM�VãºÌxMˆ(œV¶—š­ km�Æž(�<ÈÆ§Û‡šÉP�tkÓ?A'j~Âg¶io(ùt#E*1 ¢;Õ+X<–æH–Çóß½þ¨’Ißo5:�•)ZÑA+:hEÇ*' 4SÉJ=$ïÙA¸UF#e§Q±�Z›‘˜yŸß¸ª(6ŽÊ²�-xâT•ÈÄ~yÜè/Ô¿“Œã_¢~ûé½m;ï¹ï‘G<$ûáHŽúÕÉŸÕù�ï?µý­×žÞýõöÕ^Õõ3¼}oÂÛlRÜßÿïyûγ§¶ÿMoÿŒÑ3áíwJìßxû{>;ùáßævzûÍâûÛ~ÃÛ§~E/æWPº›•Ò˜kL¼|Ü9¶Ù,qçØâ°žë4üO;Çi¡sh–©ýëâŸã´ +³|ö‚ÞšH�‚ëÖ?ºqå­�¨½¸^"®c)ÞxsǾæe�}ü·Í{_ŒõLn—à„‘0k‡•”d�[oŒœ•-Ãi<{WÅ*]›ö£b¥ÁCÔ‰g.‡é,u_II°"áâuv-æÞí’GÍü}k«¸5¢äðãü{éøEÀÛs[©µ˜¡æm‚…x@I×å>•ð31еAbd;m¢“ò·”æŒeÌ×ëië&¤¤¥§¤7GRì Á +›xÂðÍ‘‚Ô´´žÝVÚlè¹’®³8ŠO,9Q,•2ã›k,ôNãî�A÷=± »Ç¾‡F󪂮“h¸=5Tò_Ûg\ÿÀ¹º´:PyùïkVϼòöÂa¥YYe#Cƒæ ïU5÷¶‘÷æF§ÞÚ+œpyûU×�7䊚¼Â�ÃÜy¥¾ž}{ç¤dô«7`øÜaÝ)‡dí·B "×*çñLKSRœœ“ÎF�F�U2étRsDg,tǘE²H&ýÙ‹Š«„øò�_IèÎS,b�pÝK‚A9áºÒ�·sJZ\$ÓMUæµ÷c;eØ\”[Ñ’úc4ÚJ8U ÁY(Oi"7·ÚÙ¾†ùØ î «uÃÝ&RËK—`²ØLà,&Žs›èNÌ-fº›Ô +ÒžÝu3Äo¯¸Ó|Œ1’ð·©›Èègöº2±÷¾§H½ºå™=©n�Põ:©JdxvÐP­n#¿pFõÁ½Èc”gVµÿ®¤ànÅhI!°‰#)TòÙÆ™X÷𱪭6³ÍÎÔÀNL�s)‰õk¢ÿÉÄ›S�&%ÑiR-ô¢Åï4æÓ�†QÛ¹Q. +ɉn_šJÌ«ú�^áßU{-;¤È¥ù×­ÎÝNÜ¢]m$ àÔõ9ËíÎõÏñjûQ[…§Ÿ‹£ósÀ€?*V£D$G÷Ä=¶bñ_†Æ•�ÿEÀ½9"¸YÀ½9¢·ýfý_ÐcpF�ŸÛñ W×þ7±ãc>•³ýɵ!~àZM‹­œJ‡¸<”D‡[H©Gšbá²ÒDK.G2Ä<È¥¥D.-…¦Å¢éâ \DÂ-S€ŒÍ<צTŒÁ^¶”\žËwtÞ¢i1›Ý1:ö<ÐwðzøyC.Ïuwœñ6¦°;êâwÜ»ƒ#:¿WïÈåH·.w€Ã~w‡ø0óâË?œD`•$óIà •î ¶x›�¤ñžtÞ õg“*ûûæqÃc?D{Ëß$9ít“ó¼þ‚‡œ?p¸�'NàW<´`þ}÷Ï[ðÙ6a`Õ¤†ÊÊñlv¤R$¹xP1ØrÄ.úA¬ ÚƒKL¨Í6�†;"žîÆæ”"�½9â!„ïÎ7Gº§¦¸³½ÀeYé¥Âë•Vúém4‚éwQ¥rÑà�‹šת”ÄÞ¨j§SâsCú½Þ L¿ÂðÝEžRÉqE‰©êsKãäõ(ís³Ž€#A¿Î-8R¤@ÙW3é²_Ú²è®Ë'ÝúÉÎ�{\óÙÓ¸û‹n¿öŽ'S~—²qÞœ«ÔWN=-ŒÛ}øé{®{¹‡X£>°ù88,žã^o`RéM¿ãã|ÖlØÍÛvÛÝÄÆ7ïæÓ=44 Ö×°@ð t‘ƒ¿Kž›Ò³YÏÞýËÔ‹{ŸîÕ«¼¿ð\™Ü»,Ü«WÿózÆRêå’“Ø7†mÛ9òm„;ce(6`Ò/ |MôÒh¾¤)FÂéy�ÃÓ@ð ÷Ñí.ú…™Ò~þ¯…�Kˆ¢î^|jw×ÕëÕm‚ºmý"úx`êc2éèwb?ÄóÐó/H�Àóz½^„N§ÓK’^Ï ’hÔ$½Nõ:“$Jô6’NÔI‚NÇÓm¶:�N'ˆ‚N4ˆ¢xú÷zú­û³¾…¯‹¡ØÛcEü—pöS’�„$$! IHB’�„ÿ-00›ôÔgŽyª‚=/òâ9>³^§§>³�t’É`Ðt’N¯3étÌé¥>³Ôé3›>³d¥ÿÌgOŸ$}æ$$! IHB’�„$ü¿ Œ,f= œö™ ÔgÖuÏ ‰úÌ�d0ð¢^2�:£^§3èÍú„ϬÓKz� �ûÌÔÍ–¨Ï,I]Ü[C§[|¨ýß÷™Ï~J’�„$$! IHB’ð¿&6«‘úÌñ}"è?1“ô�ÀóF£Q£Á`ÒëŒ&A4è,&“ÞdÐë�«AOaHÐt½hÐ ‚N°è a>‡Ž�1á#wv»>þöØÅÿ ˆ|öS’�„$$! IHB’�„ÿ-°°ÛÎô™MÔg6tÏ›èŽg£ÁhÒëM&A2è­f³Ál4èMF›ÁÀœ^]Âg6‚^°Òû$�hЛuú.>³é_ù̆øÛÙÉDNúÌIHB’�„$$! Iø¿ ¬ô‹€vÛ\Á +DfA'èNûÌ‚`6›õ0MfƒÞl%£Þf±-FƒÁl² 4x  F½Ñ �Ôg¶ýíݹrãFÐ[ï;ÐXHŠÒhÆVÍL•ÿÿãl,a&¦f÷€…×é­[�$cŒ•UÉjQׇx+öX|ÃÖãfæû)Ï¢‰È;qËÌeIò¥~©ØîåEJÙ�àB²FÊ¢â�UŠ+ΘŽó¹<¦šo8+9/Ц0|®¦ë’7jë¡Wò+#ðõXo_?| D¾Ÿð,†ˆbPËBòò ,IuQó]Q(¥RBjΔ.kÁ¼1ÂHΕ BÌå15ļ¢(Xá„¢j*ÁæÆùv›úÊÈb=ÖÛ×(‘ï§<‹#¢.™e¹byPUd +V4rWÆNZi+„±e£xrN:%¥ÑIÉ9#¡¸’µšY¼ŒJ)U³Z ËÅ!Þš¯Œ| Öc½]=V"ßOx–HDC¶sf^³m]ÓüÏî\ïÊÒ9'Èjë•ô¾bFv!è`”ò6=¯w'e„QµÑU%«VcÞh„<Ä[ÿ•‘æMzËÉˬGJäû)ÏÒÑ4øy‡xÛ‹h(”¢äfW–!IÞº Uˆ5³ªOÉ$«upƒ5––ÞW[iucMU©*k-̨(•¼ÝöX|³¼n¶Û—ýH‰|?àY2]NqÞ!Þö"¥JVÂîª*¥¤(º�ŒImýžºÎuÞÚNÞÍë$Éxí-ó®®M=:ï=WÜéVëC%œöX|3wÏä¶œ¼h’ôŸî§<ËHD¯çgæ¶’•t»ªjÛVSô¡µ¦máÍ)g—ƒ³m<{·„^EvÍÌ~ÉÌ~ÍÌÞtÚ*áv�Å7~=Ö̼äïG/î§<Ëœ™¿½uó÷î¶]bN¹Öµ +»ºÎ9[êbÛ{—{&£}Ç8¦àsû–¼Mš|²É‹›Æ5—˜R’FF7Xwˆ·ù+#ÌÝ3Å-'¢Ç/î§<Ë…ˆþü>Ì;Ä뺃”45¶1í®i¦iò4týãt⺠çKwÉ)Mý÷Üv´ô¾1ûœdî‹ì½Ë9k§ºpòáo§í[‡Gsަn»½%z¬D¾Ÿð,ïDôëó4ïo{ŠÎÌ1—wŒ�ÏçH§~¼´éò*Ì�þ¸¾õoc×]¦Ï!÷´ô¾í‡N =ç-ÿÑÃ`‚éÓkL‡x{ÙcñÍ0ý–“—ýH‰|?àY~Ñ_¿/óñ¶¡éÊ÷ÃŽóëõšè2�ßrw}—nê~~û?NC=ÿ>�syL‘òÔN½™F!²ø§i²ÑŽÝ{Ûâíu�Å7Ëëã–“—ýH‰|?àY>ÿ9þÀ°F +endstream +endobj +881 0 obj +<> +endobj +882 0 obj +<> +/W [0 [645.50781 0 0 273.92578 0 392.08984] 11 12 301.75781 14 [684.08203 216.79688 399.90234 216.79688 389.64844] 19 28 539.0625 29 30 216.79688 31 33 684.08203 34 [448.24219 0 645.01953 573.24219 619.14063 701.17188 505.85938 488.28125 686.03516 709.96094 266.11328 356.93359 580.07813 470.70313 897.94922 748.04688 753.90625 560.05859 753.90625 598.14453 531.25 523.92578 687.01172 621.09375 934.08203 589.84375 552.73438 570.3125] 66 [415.03906 0 508.78906 587.89063 461.91406 588.86719 522.94922 312.98828 588.86719 565.91797 242.1875 242.1875 497.07031 242.1875 861.32813 565.91797 585.9375 587.89063 588.86719 347.65625 424.31641 338.86719 565.91797 479.00391 722.65625 458.98438 483.88672 452.14844] 104 [687.01172 0 0 0 508.78906] 124 [585.9375] 129 [565.91797 0 376.95313] 137 [543.94531] 2862 [862.79297]] +/DW 500>> +endobj +883 0 obj +<> stream +xœ]’Ïnƒ0 ÆïyŠÛCE’ÒŽJi@‘8ì�Æú ‹4 +é�·ŸbÓVÚ�D?Çö÷%&Èʼ4ÚñàÓŽMŽwÚ´æñfàWèµaRñV7n%\›¡žX�•yµÌ†Òt#‹c΃/èõììÂ7¯íx…- >l V›žo.YµeAu›¦_À8.X’ð:doõô^À,Û•-§Ý²»dÕ3ã{™€+dInš±…yª°µé�ÅB‘ð¸(Š"a`Úçr-»vÍOm1}ŸðX%¤‘B +Ò¡@:FHyF”!�C¤¢âˆI"ÊŒH!%…èD]¨gê”�{O©:{’'…ÖW�áÝñó†)¶¤©"r~¾;Ç-Å  +‘ö9n¡¤[Qð@Á£\Ũ½2?ÚÇ<š›µ`Îgà__xü"Ó8ù*ÿýï¥ +endstream +endobj +4 0 obj +<> +endobj +884 0 obj +<> stream +xœí¼ xTEö7ü«º·—ôÞ�¥;é¦ûv:i–N$�ˆ¤ ˆ‘=Ä4C†„ve (®ÄŒ¨èß}EgtÔ†›lÀ˜uD\\‘ˆ èe!õYÕ A�÷ãûžÿó¾Ï¼Ï¿rûÔ©ªsOÕ­{¶{î6ÈÀäé…K>ùã0€D4ÔVN¬›r÷¢ƒÀ~OÓ¥�Ë褀º¬lº|¥²iç¡;Ý:@{Ù¼eó/ñDFÈFu~cË2dŸeþ’+ç�˜ð�(ï‚Ã̽tuàO‡^Ìßú 47Î=õʆ~i0|Á‚æFGQŠ ³ä,¸tåêiVí7€4 5K–65Þõcït=€ß]Ú¸z™¶Úú@<”Ë/mõÝÌy=@O-[Ú²’ Â})ããËV4/{¬ãØg€ÿ8`*�= °Cìà{3ƒnAö@ +¸Ð8MЀB‚(¬?çù…ºÑ½“0Ö†S›O]e=產Gg�µ~ê‡Ͷ–ýKïÖ‹�Ç?ê?ˆ×k,žÚ|f¾ ú©R’$iÙ ôš‡4EqÇjéuÌ£½†µ2åEæ´}§¬™8VA +6iÞì�JŠt£IG„1ÈA;3H@ãg¦A³¦AFƒ¨m� CA)1óp)–c#6qßóÍÿa/û蜿&Öô£}H”áÿ/�X‡¿�Sô~é}�K;V·Z¯¦¤v™†ü÷þYÒøŸu¢u¢Méûç0ÅþRÝÚ>—@KÿÍÕü/ +}´Ûù¹r fÈ-8$· LnA­Ü‚¬xßD¹�r ¦óöÿ÷Yþ÷¹õÁÇ!¶/©ÿŽ^û Öþàü ¹&`&—69 ^ÜÇ XÇ),XÇ%T"Çå>4¸`‰ãZXT`¢K05¨E3V  ±—AA c†c¢h/ÅJ\‰ehÆÈ>ç)˜†fÌÇ*,A#V@ÁLÄB4a–¢K1+1ðTgÏ + +1…5X€f(?šQÁX,Å +,°+ãk X_ÇBÌǬDK|U-âz.G3æ +J3 âw!š1+ÐŒ+ `²˜á2Ô$çº�¸K± ++¡` –b¾¸"MXŠe¸R¬>6—’¼‚!(‚‚`²U‚<±~•˰ +.D#.<š°8N{–b�Øí ± +sÅŽÅ®‹ïÅBq-Kþízæ‰ýP0 1KDo쮜{�1>KãWªˆYVašÄõ&îÒâÞðžU¸ sÅî)X™¼'P#vg¡8ï2±¿£ÄùÍ‚¢—bŽØí¹*ñ%hÑß"dc!–%ïâÙëàã+ш…X‚ 4Û‘©ÙŽ,Ío�)áØ';ÆëÞ…ìç5ý @4þžÂ&²›°&ÇáÂflCþ'*ñ0®Á=X -fâ/¸Ó0 Tâ’ɺP€Ç á1ì…—à:lGq±O±7Kob-n†Ù¨À,Åíäb¶ +³pH¾%¸—aieuìv7û5žÀ6é/ì ŒÈBš°—}¡y›½�|̽x‡ÈÝ)[Æ%hÅ6é—X�‡¤z™°ùìÔ÷+ðã +ì…Œ‰ØKvÑ.þ^>!.r�4Vó6ûSÙnHð  ð¶“ad<õkf±‰l/2��ÕhŃèÀVlEÀ»Ä¤9Î~ÍŽ#y˜€5èÂ_É.©÷Ìõ½å€° QŠ XŠ?â%¼FäOt©Æ¤)Ô„5W±ýHÃPÌÀ%ø ºð1ùš^G¯£k¤åql ,¸wñÝÆ ø�d‘2™ÔÒ�t)}DZ=ò0C1 q+ÀKø€„ÈVj¢û¤_É¿•¿ÓöëífhÄ/ðKü‰˜‰‹(¤…Ü@��èX:›þ‚––ßÐ5ƒŸãRÜŽßâkâ #ÈTò3²€\CÖ’»Èƒd/y�£´†.¦_J ¤åÒä1òyºÜ"ߨ¹Es›öXo]ïîÞ×{¿f…ìLÅ5¸wá^<‚.lÃ>¼ƒwp‡‰†‰…XˆBüd¹š\M®#·“ÇÉSäiÒE^#¯‘ÃäSòùùŽ‚‚j©›úi6ͦº‚^Aï¡Ó}t}�~N¿•œR¶’†IeRDZ*-—ÖJ¤ ÒéC9KÞ'3M¡¦PsŸæQÍSšßjþ¬9®5énÐCÿêé_�tæƒ^ô®ë½¯·£·‹}ˆtd" øP†©hD#a5îÃØŒ7‰‰¸HDF“‹ÉT2›,"ËÉjr¹‰î@¹ŸàIúˆ4Ps™v�6�¼LÊm4•t�ÊOóØ€äI“†›H½ô�öKúVaŸlÀÒïäçè>ú{i¢|\3�,À¥¸·`9»Wjêä7È|H¤¹r7îÁ5R¡ìÇ=Xƒ˜…l… ÛE…4+à‚“EdÄCx 2Vc!Òp .Ã_Ñ¥­¡QÌ×XH3>äWz§a&{²ù¸ŒÝ�|¶kÙ5ˆâ)Å�xŠÜÜ{5–Á‹wð¹X3ŽîÓŒcù´�¾C§Óûν¿É%.|†Ïð{£5;Ð&¿…é(gëÙß�ŽÈƃ˜ƒ‹p—â Å…Ò.õN¢ílœ´ Wja*û ó°%˜Œçð„NƒF]=²JÞÀV\�f:�­”š{â܉YâenÅ­òrùFù[¬Ç]¸¿ÀF<�g°Yè>/ü®Î¨×ÿàù@–$m²œgô§å‡Ãõ¢m2¦ü˜¯.YΓ¯Žq.FÑ6 HÌç+Ëÿ |­fãøjdYŸ,çÉWÏ�8³hÛ̦óMI–óä›Â�>|S`7›¹/ëS´Íÿ?¾š¾|Sm–ó5&Ëyò5ðm� �MìqºÍœ#N:­Ö”,çÉ×ô#¾®4‡¸�g‹^§3'Ëyò5–—4ð†;#-&ÊÉ’¢×[“å<ùZùç›!Úý23~À× ×Û’å<ùÚøç’)ÚŠÛ%nçÙbLIq$ËyòµóÃÃÝ¢íwgþ÷ðuôåë@®ÏƒDO¬˜ †ôd9O¾i@z‚‹¼1(Gbr¶XL&g²œ'_'?â“#Úƒƒàq²šÍYÉrž|³øa‰áAÑ.D¢'Vì‹'YΓ¯‡q.D{xþ@!~g‹Ãjõ&Ëyòõò#Î%_´GÍ‹©^²¤ÚlJ²œ'_…q.CE{li!ÎÍd¤¦æ&ËyòÍåGœK©hW—�ΧÌôôAÉrž|ñ#Î¥\´§�-Äälñ8�ƒ“å<ùæ‡+†�íY«„˜œ-¾¬¬¢d9O¾EüpÇð‰Š1·¦ZˆÉÙâ÷xF$ËyòÁ�~1¼@ Â?»yeËŠåË–^vé’Å‹.˜?¯yN}Ý%µ3j&Oª—�¾ lÔÈÒ%Ê‹ +‡)œŸ4p@ÿ`nN Û¯ø¼ý<î¬L—3#=-Õa·Y-f“Ñ�¢×i5²D òªã5Ø ÊÁÀ…æóv QQƒ�}:T¥QQÇ�K£* ‚L9—2ܨ¨ó~@ŽQ†“”Ħ”¡,?O© +(êÞÊ€%3§ÖõöÊ@DQ{>Qàn® Düþü?·§1ޣ͵ý år’5•4&p5R â"¢«jùŒíaùy—Gi °Ì¦D)ß>L©SIcdd�+?Ïïç7ø¶hsòóüjëÔºX[Áw¡ˆJøÈ®ÄHú >ÒšIžÞðççu‰ìvºª&«-#µjÁH•dü/†›cãÕÓÕSgÖ)Um ñ½­®9§‘‹cjêØ:ÉMãuKbTuŒ�•$æ�:“*çªr®VõܨN?µ.ÖC”qª­áÂŒüþó<)ÊŽó³Duö´ø2Õ‘¡sÛ£ÎiŸ³}¢� )Jc}¶ØDA1QMQ96NPËhŠêc}­1êqj=š¢6>²”b0VÚAÇÖÔ… %á‘áQáÑ´œºÛ ïê( �ÜN€Q�£I9q··Ò±ÓDw”´¶� +»· NÓâ”­Ex_k²/JÁÉú0ŠDbºnÆÙ+˜1³®s4ʉ[ÀH$2†niÇÖÔõÕ!a˜¸œ_ª3Ѷêéª䃆nCŸa…Ÿ¨’€:;°ÚϯN­ \éW騀ª(³êü~;Æ{"mmJ›ÒˆÚÐT[ƒ|ˆäy"nDm�“ u{"�>M“'Ò&äªÓÃmHr¶«³­\)�¶ÄtjÓOΦÊA•üŒCqˆå·G 6¿ŒOÚ6«mfÀð«ýøÄñuüªÅ£ì±"œS“ª4Ì㺤p#7'?/pQ;�5uÛE�ª¹*Íå¿Æ¹ê°±ÓêüÊܧ +p¥á‚ÿo‰H"îHó6Û¨D‹Ä[1õmSçŸÛ\�lŽã¿UÎ3ª*ëW¹Õ%‘P’¤‘_s›b Œä +>Rœ<žÿTMîxµµ©Q¥¹ª6wBS@Õä^¤¶6)usb;Èu�œšóóÄ.ÇgR/ �Ã2 ¨¤¦Î¯Ò\~9jë¥!¢44(*™Zç÷»U3µÎ¯ÌkTÃ�Fn7¦Ä®gÊÌ:~Õ�mÓëü*øms«ºš:e^cs€W•Ë{l÷ùåïW‡éu*Ümm�6•DT9wœ2¯QQ5AUœÀ+MP] +46óÈnìšc!‡ÒÛÎÍ]ðG›Uš+öR*QŠ94µñ¸±¾!¤jrímŽ6¥´­n;êmü•wSmC(àWlÊ8EÜêFwÀÏ7aoEFåçÅSr9¡ªÉGP½4Ô^¯Ë=Û#Ž¥¡±^pA„:%A¢‡&W]R©s„ŠiüâÉ´™Â/hs'ðÍÓäNhPÔð´:¿›Ÿ­¨´&î6bçO৺7,vu +û/€??¯=—¬›Ò×ÎRÕÓ~æVI$¿½ææ +£”Çÿh6úÁ'…¤A(ƒOÔ¡íç‹J:ƒ.ßkÏIÑ- •v„úù¶Iý¥~£|á¨èt¤Z+ò%*’‚¥’‚Í’‚�’³%/l’k$/Z%/6K^씼xMòòŽä£ŠäÅRÉ‹G%/ºùˆÔOòt(>[E)k¤LPX%'¾”œ`’|’’“%'fKNÜ)9ñ¨ä„VÐñž¥ßÓ®‘œØ)9q\Œ„%gÇÝEá¨äì¸MT�‹–Šfc¬9«^4;/‰Äê‰Scuå„ÙÈÙÐâX÷à1±º^¬vä¶òÚ`.ÜU‘!eà5)ˤ ºVBàÃF)ª”*iã=aÉÑ™,|t§$ƒHT"˜ Û%‘³½°Â@ýøè´'6B{:-öÂG+.¢‡±™ÆNz=LÓé‡XC»ùžÓn”Ón<ú}½“vcíÆ—´ZÚM»é!zˆ~@?€•D=ˆrz³éA<ú=¾“Ä—ô tô =}Ÿyr¼œ¾Jß§ïÃFß¡ïÑ÷`¥ï‚Ðwé»l}³£¤´p›@BqÄ—Gœî8âÈ(ŒÒ7:¾èÛ&%—¨R6F£HÊîÈê‹J®Ž²…¾(ý¨S ù6V ¡û¡ÒýüSº6º +Ý�)t?è~,£û¡E=€eôZél °‘€Jp)û¾Çö}�B÷@¡¯Bù~d=€0=€)ôôôµ%ä‹Ò}Á1¾Š úWúœðѽô/¢~•¾(êWè ¢~™¾/|t}±ÃëC…‘¾З`£/ÂF_B}ú§Î‡�UØéNøèNÐ�(§;1™îÄlºwÒ�ÐÒ�4»c®ÏQa¤;°Gøh>õ“x\�ð"_88vBaXá 8ò‚°2ò‚ÂG•Gƒ4¼ïÁ°ÂAðŽ» Ã +Á›Ö†‚W]_V8.¹¼0¬pœ»¨0¬pœ9»0¬pœ\SV&×Fé#Ïæô÷•L^L” ++½CèÓ+0…^™^Áÿð­Ì×ö‹ŽAƒ|QúP84p�¯u;i}Ž´N#­�“ÖfÒzi½ž´–‘ÖŸ“Öiõ�V/i “ÖdÄ÷ÂÔJÂ]ç4KÃ.Òº‡´n"­-¤5HZsIkiUHI8JýŠDU%ªÎ +®tÔßyÁèBk…•úQNýXCý�°“ú±ïû­0õw*Ù1âL/¯³;•ÇÚƒG.­¸ˆ>�5ôyÜIŸÇ!úú<$Xéó(ÿ~l6}»èóøò{zFŸ‡‡h6îÐJ³Q@³QN³1›fc ÍÆ—4Z±œ/©KãKÜ,Æ]_ødê‡LŸ§Ï‹·¨~ê÷³yl!Û…Ò�bõ’É^æ¥%ÈàIc‡]o�óÖ¯Íß|mFJE +½ƒÞÉM7ݯïìø¶Ÿ/JèîðU¤“ûá• |¤A’ �Ñ�ž×ÅðÐßÂG +;<µ¾(±vó|Û‰…ŸµÕ÷­çˆïSO”’­¾cž¾·”¨L:|óDéo·úö{nõ½\Õ“ßsÁ(!¾íŠ ÝæáÛ´G�^Œ’‡:|×ñj«ïZÏxßb�hŽ ü¼%*“°Õ7-8Ów¡çV_¥gŽ/ÜÕ“­¾rÏÏ}e1ªaüœ­¾!ž¾P ä©õ ôˆIÞ¨Lº|ÃfÌ(‰’á<Ý}º:ÝdÝp]¡.Oç×ùtýtn]šÞ¡·é-z“Þ ×ëµzYOõЧEYw8Ä?ÄKÓŠïñ´2‡²Àm”Cûr�=ÅEPS¥jZ=} ©Vw5¡zŽ¢žœˆÃÔ™ª&0†¨ŽjT׌QG„ª£:6M- U«º)?«k'䎈:"¤ÒuQ‚šº(a¼ëf7O±l!ö›owózÀÍ·G"pe\^î*wŒ¶—Ž«ü Ї¡³ÅuÞoŒz_õôºŽaÏ<ÓoLD-8cýÆDªÕÿ⩘mä+r¼ªrù¯"uÛ¤Ñ䫪i¼_]‰TGI­ ƒBþQU¹ A^Eê¶é½P8½7F÷PŒ.—|Åérx©Û–’‚\A—›’"èdÂéÚ[rª*Ûsr�SA‹ iq*}iöäVU¶çæ +šŒVì4{2Z9�:Z�xW Ÿk|x¼˜ BÔ§Ôµë1&2vV¬î¤Fƒcl]ƒÛ“a[6ZÈð(¿ë:÷vä)CÕ£šcÄP~E~’!†,uf]»NW¥†ø%©#}FcU”íŠuž^­Žä�’”$ä}e¼/%%Nøãû¿*^�åZÐJwt’°—¬DKDR½Õ5TuT×Ä“BÛñ¨ð-„V¶�iIðˆ/;B¬ ~͉ßÊUq,¾+ãuìÌB-‰-I¾Y¡äŽ­lÅv†fÕUX¤áR*à“†HȇOÊ— +PŸT(„AŸDK|)úŸÑPéÓi+} ®‘Ä7ÀáE#I„—æsã.|£gÐCÏz‘‚vÄ7`Fv&˜Ø˜afg`Ð + ;¬ì ì°²ÓpÀÎN#viHe§‘ŽTö2�ƾƒéì;¸�ÎN!Nö²�ÉNÁ�,v +ûÁÍNÁ û>ôcßÂûÙPØ·@aß ~ö r‘;AÙìkôG€}�Èa_c ‚ìk 0„þì$ò0€�D¾€ƒ1ˆ�DBì$† Ÿ�ÄP䳡ƒÙ¿P„ö/c;�aÇPv%(b'0ÅìŸ(p$†±b”€eÎþ‰ PÂþ‰ÑÁþ‰r”²¯ÆHö*0Š}…1(c_a,ÊØ?P‰ Ø?P…Ñì‡rvãfÇq!*ØqLÀv X�±ì8.F%;މǾÄ$'c<ûSp!ûS1�}�iNÇEì Ô šõ`&²Ô +x &±Ôa2ûLaŸc&¦°ü SÙ瘅éìsÔ£†}ŽŸ 83ØßÑ€Zöw4âöwÌÁ%ì34!Â>Ã\ÌdŸ¡?cŸaf±O1_À¨gŸb!~ÎŽaاX,à4²Où7–ìÿŽ”ã_’²cX†¹ì,G3û+0Ÿ}‚Wbû«°�}ŒË±ˆ}Œ+°ˆÅj,fGq%.eGq.cGqµ€×`);Šk±ŒÅuXÎŽ`�€­haGp=V²#¸«ÿ¶érönðf\Áã¬f‡±W²ÃX‡«ØaÜŠ«Ù‡hÃ5ìC܆kÙa¬ÇµìCÜŽëØ‡¸k؇¸׳±׳nÜ…X7îÆ�¬ÿ…›Ø!Ü#ླྀ™Â}XËá~¬cÝxëØ!<ˆ[Ù!<„6ö~�ÛØxëÙø¥€�àöÅ�ìlÄöÃv�ã.v¿ÂÝì ~�ÿbñîaïãIÜËÞÃop{Oá~öžð<ÀÞÃoñ {¿Ã/Ø{Ø$àïñ0{›ñKöT<ÂÞC;aï¢�²wщ�ì]táqö¶àWìmlðYüš½�(ž`ocždoc»€;ð{Ïáiöþ€gØ[ø£€;ñ[öváwì-ü ›Ø[ø3~ÏÞÂóØÌ`7Tv/ �ý / +ø:Øßðt²ýx]l?ö` Û�W°•íÇ«x–íÇ^DÙ~üÛØ~ìð5lgûñ:žcûñþÀÞÄ›ø{ûñGöþ†�ì À.ö:Þðmü™½Žwð<{ïb7{ï ø>^`¯ã ^d¯ã¼Ä^Ã!»ñ2Û‡±‡íÃa¼Âöá#�àU¶G±—íÃÇø+Û‡Oðû+Ž ø)^gÅgxƒíÅßñ&Û‹ÏìÁ~¶_à{_â-ö*Ž ø¼Í^ÅWx‡½Šâ]ö*Nø/¼Ï^ÁId¯àk|À^Á7ø€íÁ·8ÄöàºÙ|‡Ùœð >b/£GØË`8Ê^þ›þ¿Á¦ÿã?ܦÿý¼mú§ÿƦú#›~ìߨôO~dÓ?>›~$iÓWœcÓ?ú76ý#aÓ?ú‘M?,lúá>6ý°°é‡…M?ÜǦø#›Þ-lz·°éÝÿ�6ý�ÿC6}ÿÿØôÿ±éÿq6ý?=Nÿϵéÿ.Nÿ›þ?6ý§mú_þ/°éüChøgútÓEÉ­.J §B#‘`ÐÉG2õZÍ*=G‡"…rw"…'¿Êˇ �¤“"BžÞÐ[—©ùüT(f°Od»flèGzù +Úiìã¡,¯¬IóšÍΔ(;ÖeµÒ gšÍtFŠ&Þƒ “‰Î€‰÷¡  +í-…ö¢¼G\s»öÇœNtY­ZÎéã.³Y _„3�F-giã=°™Lò¾$˳<ÓdíZºÎ¸Îú²E“¢3ºhUêÅéeŽuפÎJŸ•9ͽX·ØØ”º$}qfƒûJz…örãUÖµÚt÷Ù^v½Khß³f%—Tac'x4A¨…“}Åã‹8þ �/H8l¯u¶¤„ý�â!))¶šRa`'’„)ìXŒðÙÚ” >»ÉdŠ’pW­Ýb4ƽÙ%áÎZ{ ¢lWØäóÿÆ€"A +}œ1Ò­µØà}é6.?¡P¨>Ô + ´~¹@ã[Aê—£^¥cÕð”º.­’ióDÙñªÿø½�ÏøÞþ;X7¬¬â3i2bÿÒÂÝnN‹J]KÌf9+*t,‘5p•‡ÊC\ÌmŽáE…ŽtÕ²ûSmE…Ãí¶` [§�±øÍ�—w¬³èÍÇö_y×¶§¯¹æé§¯»æ¢zú&‘É¿›ÝÙËÞííí}~ÓÏ’_öÞÿåq²€,úbá- 8Ðï4»` .a�†ä•'Cb·�@ ±½HnJØ_+…ÍöâÅòz'}P/ÿN&)Ðj¨”¢!&JöÄîø}áwDYw—ÍFg Ê> Û…¸z„¸Z„¸FÙñp&Æ„Ä éË2iÂfk±†ó²p^¢hªÉ4n'eäfÄLÅòØ¥~y(f•Ê�¥Ä^Êï êC$6è›(Iц5šbJá{]î(-ÈÚkw”ñìZ­nØðá%Eô»®Š7kî?\°R¾zô5¾ß�ß3e€¬Óì‚—öºÓ¨»ÍìJMÕÎ0s…²ÛòE8ÅfÓÎ0{Ó4^®¨NNàõòQ¯ÇbÓÎðšøz£tGØD N§â³Ù)U|vGiÁ~¾ ‚½(à*çpw!WašœÐäpP1a8Åj§‰yºÃFG*�áMã}œw58¹Á0é g”}»ýS³q­æóñÙÄdáñ£4£´;4;µ;t/é_öè&˜"¦ËbÓ\ËUŽ«Rou<ç8šuÔ}<Ë´Óøl*õlz­v�'+ÍãÉÒ{²$BõYÉìµEé¯;'Û‰=J\[ø:ÁÖI¨ÉpŽºú¨»!©îæZC‹óM€p•';èõü|�a“}K9�M—Ò5T¦Ûi|äÎv¡¤õ'zl'CÜþ +í,;SVÞs¦þˆÝÁåÁá,]k²\kÛMìŽÒ¡Câ*NqÛ<¶~6¯MûGv:Ö =;Žv }A=©_‰¸ù­5»u:3õF¥¢®%Ô”fÚ›×^{©½ÈQZ0dh$7Ý,>¼døðaÅÁ@¶V×8Wêô4­V§ÕêdÝéêÌýÕC_>õàÕ7–34»"'ûx c¦+Ìï¯ËÂU&d2Ód`À`¶š¬^ƒa`º×#{z4ͳɕIàPl\ ]�K 'p¿·€ÿÁQZ^në±õ8J z^´½è(µíò—�!s†¹Ê|‹Y®²_b¿Ü-MËXb[”67c•ùÊ´[Ìmi·ºŸ0Œ&³EÖ‘€ÙD¸ ðnwþOçÍdX—É”.»¶Ó_#“.§¤{=Ù;Ðì8G.}äÂÑÇ 8Zf+Kª¸¸)­ºsNÒõ9I×ç$]KPøŽ AФÁ(;ñ,??¸!ß%#:2ß$ÛÉ€í +“žaC^”Ü®P�¯¸ñ?ªOú€3G¸õØ„¬ÅD-)^Eвn2bD„›#²œ ‘&³Õ• +¶,±Z=å¨Tðì’�æL—Ë“.$Ê#$ª° ˆ UA¨¨°Ô^ZPĽCI÷Bªt%I4!`\Ât"�¬íòÝ»xÍæÇ¯-º8Íal‰Þ²háú´.ÿg¿_½gñ¼¹7lè=vàOŒÜèzp­zÃ5�¥=BW_ÛtÃM7)[^šß1wöý¸cWï¿>æÿ“O Û4Ûùÿ B=\òžƒ‰�Šm{W­Yw š„'Ñ&�”¤oI š„oÑ&�”¤·I :}œXŸ@t ï¬×'iâ®IŸ@4 D›@RHÜ�…Kju¦¦‡LO›^6i.–.6ß#KBõ0i%�Æ`”t0™Ìæ=’œ&I²d5™e�´ƒî€”l  Ë0™°Ç Gé¼g5C¸Ÿ¯Ø�ps†XL%�/Dpeˆ’’°YÎëZýÃt¬”ë¨ÑœV j£ +•(?™ŸC£ìÈV~Ýb‰’õBô>ç±÷r'¸O(³}lNÎv¢ìd™½”Ë[iéÚÁ!ùZÛn«Õ:t¯•ÍìƒG©9Êö‡�E¥Rv~©$÷ëW&^ÊÖ£~쬺pš)l,5µN)5…ƒ¥¦lO©)œ_{mK~â«„Ü[MrŠV2Ó¨Tø,]`’®4TTTó¥vÿ0Rd/JØ%;¡÷�¹‰þò¿^|±«w™ý„´õôEOô>Fezï™Å "êõkž„—êDD’š�GI5Åï¶#�¤šâ·Ôa2›·qE�Ám lWØÌ·‘x,ozºÇÁ�¬Ñ*Ë^�ÙB sEÙg"„ˆ0˜ÜýqƒÇÙQZpf·mwˆÛ¸b‡pÓV«³®ì×Öï¾Ôߤ>o:`zÏ­OIuYeI©†tGjê‹5Í’šf±š£ô×áT>uزÑB-k8�Ä—ñ¬U&or%®°�/È>Û¶Ô¶Æv§M¶�· s æ"pÙ\Ô•°a® Šã92 Vr/ÌdD‡eËOÙ2ß¹¶ìkV_á+ÅÔÛK êm=¶#kõƒCškm»Ñ×av¥ Ñ 1ngÝ�„]ã–my=ÕŸ´�9Õb1›åô˜…KO·zdîzÌVGT*êXb•³€ÿŠìEö˜ßìkÞ´ºþ©þt¿4¼¨éi:m ;8ãé.¹¡kÓúKÖxúúΙg'ßt×.¢_yû‰¿œ!­¶¶Ûv?þPÇäò ú�ßõ^>«÷äë/ÝÕÑ Š‰ì9]³ ý0ˆ|ÚÇsú¬ÄGf‰¸xÃfb6§i¼nM¶7ÍlðäÚx %žµl^§�‹ŽSøM§xÖrÆŒöîßk{!!Bõ=¶Ýõ\„òg’J]8½2³R™é¨QKsusõ‹s••úUž›õ·xè÷gØu +¿‡ýc&@;#Àƒ97Çüb€/kŠ™šÍinò&�E£Üc&I¸ï–Üsä'·�üäö‘ŸÜ›��Íf£¶(;þ,�¹mò Q2¢Ó›P:o {õfóÁÇKJÃærçlçRç§ì´Å œ6“0«–ZggåÌàkvFiNg(ùèó•}å­'æ8…Ã,¨ïI +×6€uõWŠ?š�.΀ûΈ{ !ƒy€�)³Ù�–-d*ÍìÖ—éÖœ•©Â˜4]°¿xjÒê¸wtðð+� »­„ûJ’ÖGÖ¤ï:]y×V̘C+ž›ßuæŠ×nú°÷È/o=¶éà™’ÉwLZñëǯ¾êyºeÑ�‰CFñ~SCï×o´õ\GªÉ5äé?=õçÓ량Dy`óf4r†æ70ã6‘�°ì6ÙLdª—S$3¸aB‰œb2·Hå·e²ˆj%šeÕ·¤ü“Él2›Jåd6YJÖ™dZâ +<Év¢~yÙÄ=“l'ù3Ï6ðh·Ô^ mÉòzwWŠI"ºF„®•Ç3(ZHZ]`¸ÃQÒ(mYßÛS=ܺMºáŸ·Ê§6­¿·×Ñû]ô½Mä3òÒÃ�0�}"gjvÁ‰†ÐÏêM— nï`îÆœ©©tÆàÁ¿W«àu˜½Üá‹$ʼn­"G²FÙqau­‰ŽˆA«KâƒÜoJ *)©rRNº‰“§ ŽéBåÒÏæ"ÎMtpÔSZšÌw<+¢M,D[È‘÷°&Ül|~Þ'EÙép6ïäÓò3Ó…íOWzöú“õ”–’‚ø?®õ‡e��2&?6}:D“2„\‹kÉ5òJýrã +Ó*óUÎÛÐFÖË·è¯7ÞdºÅ|»óUû‹©¼.˜ÜÞÁ“>›yŽ^{ûèµ7¡×[k½-;SHJ…ƒÎG¨u¨u¨�µXÃJ xˆ•Àj³Rk”ÜÕUèJ¨¾+¡ú®DÄÕ¢JDŠÒù�9 ¢œQN"©’Ó’žxTWÒÃé4}ÃЗ¾F8‘<9‘ô7ÉàÙQZ/¶’G1}Ì@6ëîð(YQÖÝ¡(¼ÊW‚QÖÝ>P!‰r!õ+–cy$âî„É=X˜·[ë ̂ìõ ³ ícJy„B‚ÁaÅñ‡±D¨ŒaÅÃSÓúXƒ¾¦�,Z¶äã�»>[|éÚÛ{O¾óNïÉ»æÜ²xÁͷΛ¿nä„ Ó¯jÓ k~#¹>°h㻇6λ`ÞîuÏ1²ëÎ?‘š7Ý8»iíM§ÙÄ “Ÿl½á™§Ï÷qÍòb�y6§ð¬Ñç"ȵ»¢ì¤Kîä…_pñDÉ.—.»L»È—Ø]ö¼�q€×jñY&[$‹% S�f›];ƒðP#›?|óÝÞª/·PlxA(Ä•ÈÆý×Á’y†>‹8.…‰xÉ.tñßÌzî\?˜ª ïDáñ#³.Î~–qI`ž´$ãÒ¬ù�«²®õ®ÏºÍûPÆÓYÏe}–ñ±rRI½ 㑌MÒÈ�sµt€w²e6�«<|ò攘7ìâÓú*ú÷‘}_Ù÷%dŸã¤Æ>tFv2IgìCg$#Âösƒ­ yÜ×nÁ–Ü„ä&´ 7¡¹-ö¤ØÃvjß:G zl'ã—ÿdÈuÖî@Ö�ëîô+Z%‘XNê#ÂÊFKÌZ,iÉ Jx¾Yˆ¤Œ…S£é°âþÜóÑaÅ(*ÌpØEf1H„x§ ¹_¶)ãšÆé×NN†ï¸tëi¢{ñΞ«¯úÇã¿{—¾òÄÊÕO_sícdºíªË.^óö2“«v1Ñ¿}ˆØêý¨÷«ÞOz;¿S*þÅÖݯ߼™Ë÷Eì˜ì‘GcJ¤láóRÌ)ƒ2ÍYƒš *5O/q�4aP½¹~Ð"óÂA CÚÌ· |(ãYO›ÓÄ<…‹ŽÅÓOf>3`k掻3÷ x#ýà}eñrWaçjàpp¨v{� &sÌçô¹ByƒŠKåÒ¼ ò…yµúHhž~aèrÓZÓ˦oÍ߆ì%Å"Û +rŠ�…þ4×ì�KÒ�žK¹åNË£fÑG¶Ã�“ÄW(t²ïâI¾e¶2q›zB=õüñ®L¼Î(åïµÆ^N³Ùµ):­^Kµ¶‡v­ÕMB$4èúëIhy=V¸·À�‘*Ѩ”ßµDÒ¬Q©°c‰�Ä„¢Bþ0Xd +V\ŸŒ�: ¤ó´]Ç£�¦fÝxùųÜ# +§UîÛ'=´~ùââq—8~i×0gýéyü.­´Ay4ÒR~—*îFjŸ˜ËvNêþ,nïƒ÷K80ÔÂÓw÷Á³úàœ>¡Âž>¸»žÕ7±Þ$nîƒ[úàÖ>8_·õÁ}p{<µ�yékj}p{Üäõ‰ˆ>%ÊÞO4š‹så#ò‘”�GÍß4'êÔ+�—[I‘¤€×£M÷�ZѲ2m†×rɆÜ�¹4×é̲än°»,¬”KX(áõ…•JaºxÉ-‡� +[e¶Jø{{”�ú�ÅŠ’úN—þG‚rÌ(™k]¹ÜÄ-fr'gr‹™Üüŧ�Ïä–ùLnŸÉe½< +ÑÎp›øœîDŒáŽ’ú­ E�Ä$�„Bâ +™VÈ%¯�lÀFPÊ18»~BãlBãl±Ïø´ÈˆëÜé„Î�§ ¥3�a2sr£du§üW(4éD«¸¢l"7“Gl}:c/zζÏLªj®üxù +pu,/ç™[�]¼],uÄÓbJK ¦™ìnâ0§» øûçëÅ‹èPè§’êñœ©ÁfÈ2À@´Æô¨4dë£.EÒz£Ò�Ž%ÚXÈPX*//�©mQºxÕãϰö€½8(”W`�l­6ݾö±Â']~¿ïº=�<Ó˜5zÙ=]us/¾~¤¼wÒì9uÛ7o=ÓŸþrÉì‘÷þúÌý´cõê)Ýuæîï/ì](uË£aƒ‡üQØß%F¢ƒ\£h5½Ò¤-O/ϬÎÜàÝèÕ§»Ë½•©•îé©ÓÝM©Mîo«w¿öoŽ�µŸš>sÙÒlS(½”3M ãL3éBúŽé=×GŸf~ì>M­D6§eyŒ:‹6Í#aqZŠx¸ûUŸp÷DŸp7‘ÁÈ©wÚVb³†­ ÖV«ì Mt†W8m«pÚÖ¤Ó¶ +§mÍcB¹ÌX3¸�ˆ|—An ¡•ö„$þð’°³Öžó#¿œx¡‹Ëí€Z]ŽpÕYÂUg W-R£º,áª3ûyWú‹Ï‘¿úå?tÓÜKóæ þ¦§§¼¼Ç^÷Îõ˱ÜÝ ‘ÓÄ;CÙhÑš�YQ)¿c‰1&*åå¡R.(ÄKv ?ìž9oÐý3þÐûåÒ7¯{aùãgü¿[ÝòäæËWýªw!Õ�šDÝÆÞŸ¼ãÔXiÓ޽Ͽ´ÿÀKþ áéP +endstream +endobj +885 0 obj +<> +endobj +886 0 obj +<> +/W [3 [277.83203] 16 [333.00781 277.83203 277.83203] 19 71 556.15234 73 [277.83203] 80 [833.00781 0 0 0 556.15234]] +/DW 750>> +endobj +887 0 obj +<> stream +xœ]‘Ïj„0ÆïyŠ9î–$Öº$P"‚‡þ¡v@“Ñj 1|ûbb·ÐC?f¾|ßL¨lªÆšôÝϪѱÚã2¯^!ô8KxÚ¨pP¼ÕÔ9BeSµÛpjì0“² 8š%ø NÏzîñLè›×è�át“í™Ðvuî'´4„ʗνv�²K£Ñ¶ËM¶Ÿ›CÈ"ó”FÍ×)ô�‘”Œ1& ¬ëº­þW/’ªÔWçc÷ƒ€’±Œ‰�òN¿Ïm,Þ?ÇÍnWíç—ŠŠ/ +endstream +endobj +6 0 obj +<> +endobj +888 0 obj +<> stream +xœí|y|EÚÿ·úžé9ºçÈLfrÌdHL’I2„0Ș1âű ˆ¢®â‘u½–õZ_mÐÕȺ+¸èŠÊz¬**YEe1éþmõLBD÷}ßß¿ß_[ýIUwÕSõ<õ(XgžÛ¶|)‚ˆÄF±œ»èây5áèÃ@Å�@æ·/¾èå•;³w `ywþܶvã«ãwd€¡óçÏmó8,gd&€ó¯¸hôøìƒ'LrÑ’9mþ‰Î€ù ‹Û.ZÊ °¿�¡óÛÏM<>5cæõ¥K–¯0&c@jhûÒ æ.=ï9`ɸR°�`…¢aÀ Ê›ë˜ß#„mÁ@A —̫֙àLh3é˜?“ ÝÝ£Îc?œwâE§nŽØ?]gÖðøKîžÞ9ÇYó-‚’Ùðð·5Óò“–�ÉÎëþ•S·Î�¥o–ëdž‰ïàã ¦KöÌc\ÏÈÃ1ðëú×—&L …B¨§–MŸLìÒÝ 1h¾ƒr> ÅÀÖÄꪃ+B(À ”¢q$PƒZÔ¡Óq ÞÅ|ŒïÐRzj ñQ‚↌Äé…éhûìHÃ0þÛëjãã +ãrcã`öý„“ÿ} +÷]�ý®Kp¯yíÂ~ìÇ$a^÷õ^ÌÌNóêbºØ{{'rí\;w·Ÿ·ó ø;øGùƒB‘°Tø³HÄQâzñC©AzÓB,•–ù–¿X-V‹uô�®Ý²[>K¾Sþ§­Á¶Ýö¹=hŸc¿Íþ¨#Ë1Áq•ã]ç`ç,ç‡J\Y¨Ü«òêLõQõ럻\ræŠüWMßuÖ)×3'/7UÁÁ¸§—YLUFžÆS?ÇLængŽÁÕ÷E1[ƒkÍ>W/qhèmã:°òÔþ\&q˜ØûÌßG˜Þ’ýFš÷wÂʇ±Í¼ÿÛ¸:lãa¯aœÆ6æJlc¯C˜¯À6·mâ}Ø&4a› b÷EºäØÆ]†™œ†|~/¶ðÓ`•¬pñ#!˜t¼ƒAÿNa¸‰Hpßa#ûÆpÇ0†›Š1¬ŠRóþldFc 3Ú¸”»Ø¼ß(NÅFî-lä>ÆÚ�±1ld7¢ŽÍÅ î-¬ä¦�Ú pçAâ&ÝìÈþß*0¥ÿTúþ·}v~1æ'uoýTV?¢AÀÆŸ«gw`Ó©¥à,¦˜�¹'PД¹g a\æžEå™{®ß=?ü™{ÔÏLÁ\œ‹%˜‹³0¥¨Ã,B;¦a..Àr,Àœ�ªP†jTôƒ™=B}=zËS!îG•(G%ªÂ,À\€%XŽ%˜‡¡Kp–šyVdp–!„Ó±‹Bà\ÌÇ +,7Ÿæb¹Iß…˜‹vÒ«ù7s1`.V!„3±sq>¦âbó.„ñhÃÅX‚•&æEX‚sMŠB˜ƒ%XŠ‹qA®PíÔ +‡PÔ÷T�“Ž6,ÂRÌGcІóÍ1æà¼ ì8,Á|“{c°íæŒÓóšŠùX`ÎeÑ¿¥gžÉÊߘ�Efm›É�Ï1=Î’ÌLC&–•¸sÌùöryÚÌ!¬Äùh7¹ +Ì7ë&`,¦šÜY`ö;ßäïp³ÿ\b.c¶Éív3e(ê… ™õËMÙ.ÀÒ>)žœm_�6,À",GÙSˆq] 8“; ‚$÷eò,‹=qàƒ,_Îëodùr.½,+xéeÙ¯¼šå˹pU–/gñÒ,_΢%Y¾œóÎÏ +žwþÚ +Vz¼9ç.ôxsæ-ðxsæÎ÷çÎ߸,�½<ë’úìðÅõÙáØé6îSÄx >à¾Éä!îè#v5‘ì侨!{O»¸¯ $jO·sÇ@p-÷ Ê3ùW&‰Ÿ="+‰Ú?‘ºµ;É鸓œž´3Ç¿g¢ßã£Ç¾ç¢�Æ®G¾�D�Æ®dî÷î¬Äg‡Øè¡O™hòS·71d7™òmz�‰ŽúY‚©d ²„,ÚaäŸ÷9„,&ç�A”,"çí`£�;Éy dmrÔm\ôŽ[ùèm·rÑ_ßÊD;n¢·nµFÛ~ÃE³…‰Þ¸…‹Þp=½~ ݲ՛¯Ì Íaθ�‰Þ²Õ™óV6zÓV&Úi|�tl-˜˜¶•ìÝJ¾ùN4éýÎL˜¥Ã™x‚, ó“ƒÙè—›¹è›ÙèU›¹è•›…èæ –è/Ö’èº5\tí.ºz�]³�5Ç>ÛŸ�˜½�D¯Ø@¢›6�èåøèÆ Btý1¬öú‡z½U^ׯ3îµUz-^¡ÜËÆ¼(óæŸn#g"FÎCÆ‘Fx1‰ŒÁR2.#Gÿá<ò•ãp—ã¼#D>2üHã‘?ùá/�qôª£?å³F~Q±c`±³HðGŸ óȹI·spÔQuD"μ|G(ß¹“´‘Ùdiò›SQm«lDÉÆr¼ „± ¬?ßÉÖ²g²Xî$¹v¿°{ŸÝÅyì± )©\3°¦¨f@MAM¨&¯&Xã¯ñÖ¸jœ5–¡†­AͤøT¢¹Ñ8µNs“F4N©ÓâÑÆN6Ô¤UF5ˤÍÛ ùUJ‹G5æŠN‚©wE'ƒ©š«~úŒæN’M›7Ÿ!Ð[7^“ŠFsµöÆ)ÍںܔVIo®ËM¡Q«œ¬#uÑSÓr3[~Jíö�E Úà†6­¤¡u” °B;Ü mXЦ�ŒÒŽ4,ÐŽ6´jG"éÖh¿ÈOpЄå+Nâë‡|y:ë­X]¾Ü¤fùŠåѨæ×j§ü ÑË·[(&5Õ5jRS£&Mš¡"u�ÚsM�ÚÐI34[¤æ~Ÿ€ÐŽK$X X V3—!:l°=°ÃaôÀ§Ñ '£ +T£*\ÆpÁcü·™{à3~€~ã²Ì܇lãü'��ãŸ ×ø'‚È3þ‰äÿD.BÆqä!lG> +ŒãÿŠcß#læ(4¾GEÆ÷€bã{b ñŠ0ÈøÅl|‡�ˆßbJŒo1eÆ·ˆ"fC Ê�c(E…q e¨4Ž!fæåb|ƒ +Tß C�oGµñ †`˜ñ5ª�0¾ÆPœf|�jÔ_ÓHÀø Œ0þ�Ó0Òø†£Öøjpºq#PgÅHŒ2Ž¢ ÆQ$Ñ`|…Ó1Úø +u8Ãø +õc|…QkAÆG0�Æœ�ñÆŒÁ£ cq¦Ñ…q˜dt¡“�.ŒÇdã0& É8Œ‰˜jÆ™8Ë8ŒI˜fÆdœm|‰&4_b +RÆ—˜ŠéÆ8 3Œ/0 3�/p6f_ ÙÌS8ÇøœÆ2Æç˜aæ31Çø -h7>Ã,Ì5>Ã9˜gB+Î5QÿbÂl3Ÿƒ…Æ!´ã<ãS꿌O1çŸÒ݉ñ)æc©ñ `™ñ â㜇åÆ'X„•ÆÇXŒ ��q¾™/Á*ãcº70>Æ2\lĸÔ8ˆåf¾—±«��p!Öý+^]g|„‹°Îøã—Ƈ¸ÄÌ/ÅãC\†�ƇX�Ë�°›Œ°W`®4àØlÀ/qµqëq�qð+ã}lįŒ¸×ïc®3ÞǸÁxWb‹ñ6ãFã=\…­Æ{¸7ïâÜl¼‹_áVã]\‹ã]\‡ã\�_ïàÜf¼ƒ-¸Ýx7âãïØŠ;�¿ã&Üeü7ãnã︿5öãVl3ö£¿3öã×¸ÇØ�Ûp¯ñ6~ƒûŒ·q;î7ÞÆø½ñ6îÄï�·pþËx wã!ã-üoa¶oâwf~voâ^Ç ã|�Œgð¥™F�±]f~†±_bìÁQBŒ¿à„5þ‚¯ÍüÂÁ1ÂOã["Oã;"Oã{b1žÆqb5vãŸD6vã±»ñ±»ÑMœÆ.ôÅØÝÌ ¢»¨n”e‘žŒðé ‰£g146J•‚H�xH"$A�$–Ù"‰’(’(‹ÓQ“ ò¢À‰"Ë +¬UE‘ã9Q°2²¢gAÂ)Ñ–Ø—õ•<`³I”&á$M·ËéRìUñ+&":œÓæ´[œQ´‹YN§Ói•-N»Ûn·�įöCߟÎÞ12¥ ƒ*¥)C£eØ@¡ÏЏá¶Á­Âãp¸½›ËðzT�Ë©x\AW pªvÕiUIrJÙŠªª²MVœYNg?"<ýÐ÷§³wŒôåNn®›ê•½ú#Ù`W3ª +xáµÃã†×éôfYìngŽ/Ë•åVT¯;×Ý{þ©¸.Ev©’¤HA—Ëå²Ùe—âSÇIüÞ~è{“«/ë+í@(ä¥z•¦úc±Ãáê…v>øðyáWU_¶ÕáUò³ý¿×åöyC^¸3Sô8=ªÍã¶X\–\·Çã±;ln5àRû1Æ×ÇŠó®wŒôee8üSšœî  ÛÝ�&¥ÉéUC�lOv–ÛíÏ +geÆ�Ë£x\v�‡Ò”ç¡49íWÀåêÇ?–üˆ&O?Fš4ú©®§�©N[�P¼¯   àGÐãäØ¿;’›ãËñ{³‚Ù…~deÆö¹|‡/K–½rA–Ïçs*Ž,OžÇÓ�ˆ`?ôýy×;Fú�²rðà Õ«4�T§enzVðû�<ä¹�D~VV^Èîz†C�PП�—38§÷,Øð|J ÛfóÙŠ²�€êR²}aŸ¯y}hO¦Í²û?P••åQ½ÊШ67<� @ `¾ñ ”‹¿?qzòü%"9‘¼@0œ_–‡œôpÙ¹Y9Ù®œ»=`œ““›ãò¸r² ³³³Nâ÷Cß›rú2¹½¬¬ Qc”¦:íð +7�› DÉB$„Â``@‘’ +”,Ê/ +çäW†ÍÉS¹äûóƒîü<‡#ÇQ–—ŸŸïÉrçæý'ñÈȯÊïe`ß嘊‰ï Tñðÿ¸—™šq(�7Æg€>ÈøÖ„‡¾ó$4ß73Æèb›¨Œ.ü¯’”ùãèÃð ^1«wdš�›p7ÞÀ¥ÿv€£diø·­ÿ6‘©¤žT�ˆy:©!Ѿû á†>8q:¾Æçøoákœ >Äq<�/~f`­Žåd‰�èÆw?�{Ϥ’(x Wàj¬ÆF,ÃkøŸöÃÇÔ7’ÖîUxâü"ÓxÚ3w×áü 2˜ï€Ê €ÊÂø +*c'nÃG¸çä¼þ?&z2ÝøïÅzNúíj¹}P…;©õd$>À�á@ãðƒèîßWŸ§Oäâ'M§ÑEF’ $±ÃìÇ5ú×ú=‹{î4ÖËø#ü»Ü‹¼ƒý5—�Mxoa=4|„£0þŸÍþ?é?é?é?é?é?éÿuZ�'q#n46¡ ƒÂhŒÖ›ùV\‹�؈™˜ŠÑD!v¬#ù„C#àÑSFyí¤Î|{<9½óðç'ɳ6¶Ï˜žšÚtæÄ ãÇ�sÆèúºÓ“µ#GÔ ?-1¬zhÕ�xeEy¬¬´$:xÐÀâ¢Â‘‚p(?/7'Èöû²¼·KUœ»M¶Z$Qà9–!(!~Í_ßܰPË®oÕl‘Q%¤Ù&�Óà +†#j(K•f 4>ªÁݨy&5oGrXJ¢§‚LÔØBåë°ׄ`¨Aã +5®02®­]ØÔŽ(oûÚS©Ò-Pß5¦Pc +Çjl¡ÆŽk µkʤæ0m0kÆj˜ÔLÿ:��†…ƒ†…SA MÍZ^ïc*õsD>»N!s"Ù¬l·e×�ÒàÙÛG¼ìè0h¨ÑF5¦PÑPcކ˜F<_kÄ­ï ®£ Ý>ö3±™Ú8¶ÝNòª¶�¶nö´îµ]Ý«ÿáÍŠfû. ‡Ãa*³c†•í­ )É Ûè4†6_5לêÕæÔÂá`¨aá(úG;†4œ¥ùë§77Ì�4œDÚi ilá©}Ãa-;J;nÞÜ@Ilk¯+-I“¬eGOÒO×D0J4¦°^KN5 L5ejÐ’m£R™ª ÀtÚ�¶´ŽJ¥Âi¹³…!M,ÜÄ—EB›éˆb¡æ‰*á=áÒ’]¥%�MÍ £‚æì5¦¾yD—?Ø•*-iœÔWMü�MÍ›c]Á4�§D'§µ`~oÖ:5½€™>ɇé ¼9ê>p_ª´dtdtëæÍ£#¡Ñ›[7·uëfGBJdóv›móÒ†Ö�¹òI[§±óª 6úꔦ´Î'§•–„¨¾�njÔÜ“gPñŒÍoK‹ÚHxX0¬¦za&ý»æÌ:ÓÄzM0×Ùfåpi‰f«oC£©yé4Ž jÊ0ºLCÎjîT0ÇÔY3kÓ0¥Yc‚t¥°©Â†S2 +†{†Ú½É™ÚTiI8L×ÐU�IÌ.- kë&7§ŸC˜Ü�d,šÒ˜VÚ²«·Å{mY×ÛÒ×½5.-ñÓ¯þ[�î¯Ï›Õˆ+”ˆ™ü7Ím»¶kjs§q|˜& ˈÛ]ßÌ™Ìdé�5QB5š/jv¤<ÑF7+‘ÐËM‰j|}ó®`M*¤¨\¤´D¥«†)T^Žì%Ôv£h¤F#Y´n�˜&�õ Óˆ»OyB ›[3ÚÕZÐ>ÿçç¦q…JD³}Lë®�ዦIËXêÂÑt-Ãiˆq)ÍAí±æ8lf¥%Z°¾9¤‘Ö “Í›PCh>¶ješ�T°u§ñAë(jöšC $˜QëT0­Ú§èÚÿ^Ã×]Ô~qujþi¥%Zrp¸´$T¥‘VsµLmÎpiX0³Š(®±t*?nïãb/ÌO¹Û8õGOýÆ¥!Ü8µï�Ž«�Žö•~>#ìÿ8æ”æ±½ÍИÂ�:–2µÓØ5,ø£º)ÍZ2]ÕØÔ¼:x õ' ©Û!WLÞž$WL™Þ¼�AÝ +ºbjó†0õ­u©íÈ“›ŸI³–¡µ´’>„èIcSóF2áƒO$�uf+gV˜Ïs: Ì:©·Ž`N'“®SÒˆŠLDI0˜ÓÉ¥[’½ÐætJéºuf�™¶ƒÎ?iå“RÒ’´1v&¸�Ъ|RÚI Á#6b'Áíë˜ú&³º“¬ÛnIÓë`!É4…WœuõYÓ›±ÁN‚fžJ¥êhú—¨ÖiLý$�P¶ÏÐò¨ÃE ö7+Ô\k©(5iŸP;¸`TiÉöBrŤþsš©y›f5’*C¿Åæ6ð‚…ˆ@R Ë�åy ‡Ø>W"¶±}µû*ÊãjX- «á{Ø÷»cëÇ_xbó�ÜD0xŠ=ÊÆùÍJ“Avï9XƉ3q_ÑCF ·WdVdQßWIb]®D¬eYWE9Q#æÅÆ?ÿígüƒú(ò¤>šìÁíúNæ ß %}„gyA !”Ó_ +ÈÒk!¶œeØXK—�µì£DÖî©('a¨Š™1u]ÿ©'Ð_Ñw’vF$ß’uúùz®žK¿�ré;™fsô¤›ðlLž‰í‹Çö©”Ê.Ô>Ûdâ2@*õ¿†ŒÒw’ËÉ'äCr£¾V·õôБ‹ †hè oÒÂ2ìv€ìeb]•±.ÄöU”²7Ѷ|7¯[È ÚãZò(³ƒ¹,|I™~�·H9aH¬Å•ˆ™}H¸*Ììèy�‰�Gß1^2\ì6¾,"Iòl*)cxòZ3”a˜^Æ€ +ÎB„ÝÖÓ Êtòÿ¼H¸‚þÞ¢Á8Ä�Åï€*âÉñJ(ŠÂȬ¢¸mW&ÞzkŠá¸5…lÔFá¯�,@wά–®Šr>¤*ÓŒ-P•x¥ª0 :ˆ—ˆä˜þÕ?öíݓͼDV’M=…ú�úeÜß{zô 9Fèפ+�CœÈï@V%ë²ìò_épä]™„æ½ÆÊ" [eë­)Y{Ãì­)…´ÖBH8Ûé"VÖåòz•[S^µQq?%¬6›ÕU]HøcfAÌêxŒÊ2Ð�u©‰X¢¢¼ZÍŠW­R‰TÅCUC"¢:4^™åUxoV–×Ã… V^·ëâM÷Þ°ñRâëº+nÞûî·úÁ[4ýïzÏû1â^0¿¥uþ×;n!Ìøgo×�Á»߸÷ ú]Ä$ãsn¿T£‹’µuW¹†]%yyéNI“vI¬KrI|‰2¥,T–,»®Œ·²ee|™„—�ÇGŒÈ·çº-•Ÿm?ã¶”]̰ŸÎF�»‰þrP‰Õ“„Ò¥tU”»‡Œdª‹Šª†Œdâ•yŒ×#ˆy¬×ã`"eL¡'�‰WŽdª†”1‘ã>åyRdô‚3_ÔpÉýíó¸tÔºe£Ž0nÓ—-ùã¦ñ$š7lByÙ„a!Z–N* –ÕFŒŒ±ÚEµ¥Ùä—5KÎiô½ÿܦ«ç%s7OžwïPcë²3o^\3|ÑÍVLš›7tbytb¢ ?1‘™PT_‘“SYWT\_‘“[QOõr¢qˆ«åw •hJ–äc3ìDfíö!ƒ6{“¢è-SîHÙQFllY ¸5MmÍîÇ(Sê ª²ñX@éåV¼¢œxAˆ›ò¯.(czÙU˜a„7îvê]ŸmÑÖ.»ã·�D8çéæ­‹GŒÛ¸sÙÊ�ëôÝ9•c¢ƒ*srâcJ�ª²®§ô×^^2ŒpãÞ•¹¦nåÝmËv^>vƃ',Õm£ ¨mŽ;»&?e_Ë2W•P]‰ŠòBRE¼ÄK¸ûº§³÷ôÌc:jÉw“§ŽèýL,ã¹û¸©Q›,à!0„ƒ"L˜]D"‚ç a3VÕ"™öîçqYH•yQ\Ìm=íì=ä;}Z‘9’wD#�ÏÙ¿Q Çøä §Dl‚7;™íz$•ÍaH²dˆ6£Ä=ìáò�,ëÍÛ²—Û»}„7fYêg^ï¢ ø•¿¥ jøÎ™ÕBÒú\=’M+ª ªC‡¦µÛÁˆÖëÉò±ÅEUCª‡Æ+éa‡(Ñ*» P”èÉ4|ÀÄû'<ÖÖrçªúœ¡“†Dª".»¼rRUUUÕ¤3‡ ‰!wNÏ-_?"/T]=|@~UQÖéu¤´¬lPó•­#æ4OŽæVVTGü•ª>®¼rÔèX,VvƘX ý‚¿žï€'®Læ:E�ØF²Xe"ÛN–³q²Õâàl¶Nãx²BÆ/0¬ät²×§œN›(°Ë ¢$ÛœO2Ö\åŠø¡<ÃÇZâÔ˜Wº| Ä|•µñD"F žW]‰¨ê‹—+›ø]»víR6Ñœò¬%fÃl˜„Y¶¨8"ˆ,½~ç<�™«ßÍXÉZW‡*J¼ç6R«ïæ;ºC>ÚX;RPëµ àöópÂGÓ‘,•üªêoK©j6Av[ +.¢´¥k¿Úë…munn�EÍ^“Ö¢XKWe¥+a:ø.5ñ£iæ>¢’£–)mh#¬jZâÞ²(²�±%Ìo.¹õqýÓcÿª}ùá‹^ºvõR¾C»sÅCœûÉMÏâÔl;ëöž'õËçOŸ6›ZŽm›) ±d,[(±Ò–a¹Õ!;±Û]–“df|»IŸI”éÏNAº™nýýwOÜùêW]oò= úv})·¥{y÷þ}_R¿½-óFg$ –-)A°2ĺ%E\ ¿%ŰœÄI™Åe¬kz½zePK<‹ý˜=åá°ÚwqèÞÆ×%ò†e²ùýŸúný„Þ™ÁÌ~hî^NK˜¸ItIdKJb9®w—ÄkBb¹Èˆ}(3] ŠŒô¡b?ÔòŠÓÿÄŽà¶èwê'z^ã;@0Î8Ä�àw 3“qX+#XˆÌˆÄÆù}[R~¿[bÜ7¤·,Û%û )‰óù¼Š,c�×TVói5H˜Î·×I÷›.ÕÔY-h)Â!PîWúÔ¢HãõdÅ+«)eüˆWõçô{ôeäQ²ðã‡î9º·çàî£ô™Ù­“µdi"Ô꯴ê?üý½¯ÃÄO·O&øÕ¦dɉ½>% ¼t}A9qñ.++°¬!Ä.qkB|¹¹ÀLþtÅQ[[K9tª,øÕú“=Ñý$ÓDF1cz:ùŽž=LM9dîÅ”Çè°.†Em-‰}˜‚¢ÝLn†�CÌ ¾6 JzAb¥ëS¬[fyÞŽ5V«Ã®fj3¼ª(' +2ÎJUÃÌ ýÛže­SÎ}�Ô1 ýýoÅ$VKưS3sÏw@ÁéÉ�ƒ’p�!V‡Bxž*ët*ÍpYûkµ- ªµ._¢¢œ´´´ "¤¨8bÎ=>´š„ ?^Ÿ¯Ï²xs2¶r5ù…nc–¸FôÔñ=Ós†0Óz2T°tŽ<–'-„3׋Nãè#ª0Á,³üìG¦´gJ›Y~ðˆœ)­™Òb–»’Ë€1€È�\FT�û~ƒQ[Û'3öD÷¤žqó?,ÊHHø14*ò õ0ÅV2P$ªËÅr*³\·]YRËUF�µt%ÒÊ�@mm/;Ô!ÕA$aÊ–,/EB²|&kDAßm“²=º¦×pä[ìú{d?)Œ(B0�’}ìô«îÛpZwûLñ]¯ßÙ}ˆïè~hÞ¹«šÙI½šúý†Ó‘j)–«e†ðNgµ¸¬,qb­ªze^Z²”[Ky‰x?�¥åu’°¢*5Ì?¤ïìÙ>ÄÃIúKd&“(s©`7ž9püm݇ùŽî›‡ŸV¹…ͦ–“®ó¿ò; ÃG£-¸®OÁ-Ø®O œeuÈK¼Þlçj6vŠÍäÓ‹ÖÌI�`®Zj;YFQ¿Œl& $N.ÕWé¯>ðÜswß»çÏÙäzRG¦’múùúãúCúlîÆ«oÿÇ›Žgì7÷¤iÕºneü&%Ë,ãb­¿I±¬°:ä(w0‡K&kBLy:Ê©ìGRƬQ&„T�iËCUCÒ+ð“§ïÕ»;ÖF×ÉÝÅwèÕoëß2�ê§ô^šµÂ~¼(ĈdÈåQ!IY×§$y”.ÕÃÙV‡ +HAAq`5—fF�šH<ÛÏŠ'èšåCÅEªR=4òe©Š(PÅ)ØQðz|Ô² ­Rlréé§ŸÙCF“øŸþüŒhÉyzÍŸg͹n{{ûÝ÷Ξ}άìçž'§“)¯½º÷9ý1ýá—E» ÿvw6÷‡uk_}ã²Ë.ºä�÷W_–ñ|g›ß •¬º‰ÛZM¿T# +a´¬Išö[f­nY’‚m)I +Xå@[Jf‰±xíOƒ¦@Ÿ£êr%ZèêbËØH$Ã_#zóXQÓŠðgë×È.‹ÅóÎÖÿÄ€±–M]56T3,îí¹ãêùµÉɲÁu±ìžwøý’ƵóÆy9kiMC˜…¾¬fÄô'éj˜iâ†ð;P…«“)kU Ša£DDølo6Ãù=~Fv‹ƒXÃ"‡È‚¥LÀeÅùYù7¦²8s–pqîÆTÜ” X­’²Õ! ±XªûÅÕ-]Êë44…§å‚ºû~7ý×/^°ô¿Æ8kzj`õô‘E£Ûk§]9{ÈŒÛ^Z~áÕ$9vlVqUþàºaC‚£Ÿ¿vé]ó+‚9úÙEÙ6oQUx@"^0yÞú³/úíÜ’� ô2n‹¹Ç)MúXá~KJ ¿à™5å,aÙÌ6¾+^ëõ¹µ'�=·E?WR_À­à¶ü°˜ÛBDÊ÷-Æ!¾Šï€ÅIc¹>Ÿm,Ïb�,;Å5!®œc¸´Áí¢.’®2%RÕ°š.øªž{z:É(¢™Ô3‹{.aV±5Ý»ômd;žb »ä‰¦G®Hæˤš%Ã@d‘aYÆÜî±CYºObcÔµ VëJd¼=¿èµ·üDݥߠûÉLõó‚è'ƒøŽóøŽîÀä‘õ“Ø�èÙ 7íýØdÅÐ ê!ÅRl'E2hfeª,¤X"ÃRLª 3Df­ט+›ø˘þÞd!áÌN:LÂîŒíO—nñ¸¾Qï8âæ8‹zL¿JßDÚž· —M82ýK»•ãÔg)‘ì-cR³k»çóÝ #Î�0ƒ}¼{DÕÜ3'³¢¢Ÿ½WóðbH2¿ØJŠRDˆÌZ<2g•�W°dhôYL]¾D,ÖGè ÒKŸ¥�°0á«õ_|ïprb฾–ìÕg‘+÷ùÝœ-ü1™¯7‘×£Ãõz³�Ù§7ÅGžBèI€ÐÿTÀ=hZâÆä`HŠÄÈ#s/íyŽç¶¤x+q,ƒôVB”3”‹§éÊØ“Ú̦Ÿ.¨p¸Šª¡—„¹XÍè™Æ�ï©`^ÙÉî'âºÃÔ +'ŒCì~.^äãúähIô‹ŒEÈ9à#¬�8YÙwF +²"3v^Îu/›ËçÞœâ³6:é㷤܊SÎÂÆôR'agàr)Ö•H/‰xß.4Úœtç;cÎZç™ÎsœKœk�×:ïpZZ‚)Ó”ÓE£ºçÌj‰‚†Så„nTÃ᪈)(ªR + é‘QÕСñ�ÊW‰çÇî×ßàÎ}~ëïIÉzìñßÕ}2‹<¦¿µ³£1™ZºåÁû¯!ƒË +ï[|¤`ˆÞ¸§ÁïY5´áR:ë�Æ!î!þ0ä¢!9�õ¯Ó»5åÌ‚–àÖ”Eá`[ò—û¿?Ÿq úŽ?+{`†èÞõ^ñ¦woñJ—Wa"lŸ{ŽäNßCj¸•o?ûæ‰Wþ´ð‚Ô¢U‹çž³zÕ*þpÏ%�èGПg’ æüòÞ[×^û+ªŸcŒCì5¦d†%ó¼^7ãÞšb²Ì�ÿÖ”¤È½Ê©¬Ooû[âývüéÍl¡)@Uß>?Í´jöšóž?¬¿KDøÃä9—\qÇ-/\V8’ä}DxRZ®wÇÕWüzåÓã·$çaÉ<6È·¦ø,âuxeïÖ”¬8¡¬w8òýë¥tð�÷ñÆ,¨3…ÈT A¼ÒWÃ$Í”4ƒ¸%únÑ+»¾ Ì+ÏNäH�þüŠ–^0·õâkî!ÎotRù+¦­[˜Ð¾áw×mº¡#CU.— >´$«Á0ÄÆó²AŽeö2j"Ö’ÙQ˜‘HÚ-W”S5$á¬peµ×d#¥Þå¥j(ˆ\nO»B,÷¯ûÕÁãú2ðí¾ÕßÙ1~‰�Üs`ÒÇã‰û;ƒ Ö¿ûxÄ»kfQY–lXààÁ´d +‘«W²ñv»í¦”Ý ÏÖÀ¸˜­)ÅrПÛx‡eCÈJ¬¦k¡Î¥¥ßjÊ,ÕÔ=óÌ&Ràõƽt³©ŠW ¡'3lxİ[¾øBæÁ?ÞÓšäNø·øóîÏYßçk~ÓcÊWÏç–qÙ -ÉÁÍ é¦›c/tÒõ�íôå{󷦼 +ïóYl�åA|ÁzW¬+N…íë#éGë�®vK¹�& ­×L•W*g«2ê ˜êPuŠ:,ÓÓÿúÒÅ·ßu§Æ-zmϧ¯ü±…ªÅsËg.¸è‚9³/^©Ôÿ:œXgÞýä‰ç3‘ÊëVõžÜvå¶­ë¶Þ Bÿ“ 7“? É ±xmœ“ÛšrÒŸbm Äm“6ôíî+3N6M¾©®*•±7�ÅC*7SfÛÔùæ +^ñW2“ýáš¶žNþpÏeÛõ™”�Kò2œ˜Ì²{Nsf°°Ö¶!}övr�¿é™rÊòz7 $ +'Í›7iòܹ�&¦»Ïi8cúô3ÆÌüa0Æ¥z~‡Ÿž.ªI—Ëãsz¶¦œ9¾ 6Ö¢ZXÕ¶!”UžÅd™¸2Ö‡1£Ù�}§ ö0‘‚⪬xeUÞ¥¡©.¾õG„,=¨Í;^ø'dx¼‰? É0á½V§€¨�Ø8k�\)3²ì°I܆“!¿ùÆŸ­T-tW¯(DH&ê%aÂmÒ7êO¤ %© «ôëI·Îñ‡»‡“ýz~#ÙÍ‹¬¤�x`C_ljÆŽƒˆ&»éüá4¼p€ËFæ&ÈUrY Õëræp[S9JÀcóÛ¶¦ü +Ù�„CR-ueTbeÕ€´!iÉY6 ,O¿†IŸgWF‰_9èJ+=UwÊØx¼×ßöiNŸyãôìÔGÕH8 ?³mâl}Ï]“ÛÌIVl_°Òʃd"3æÀåí=a&î_w~Ï_9ÜÙtîSOéíÈpy¶iëÒšluöêò¿ÓäÌrùG–ˆúäÞ™Ÿâ“•þ.ùõ¿îïyõ‰…Kg,ZõjÛ¥«zvðou<¢õ¥é‘‡4-¼üÞ[‰cKÚZâ–˜æbx2d÷’`¯Gt:ÓþÐîPY*˜“þÐï[~æ&'nÆÿ�¤ñ¼—wI˜—žœžvˆ3\´rVûÅú ¦ýbÿ†À´xŸ7µ_ñ»ÖßxSZOØ0ç‚“žÄŠ^Éi­ecï=�R­Â†~'P¦ËÛ×Riš¡¢þ:B½Fbü¹+LÞ4þ¥2‡9˜ó/u‡?¿lA�qˆ�Êe£%Kĵ€ËªØHWÁÝ’ªÈÚäfåÞ’ÊR�‚’�™�êR°A�uíéu·ýö„ò’ÀÚH:Œ�öÛ Ò6;]ÜI2‰´’¥Dh ¦T|‰òŠtt8Ô ËØÞàÐ[Tô“à°n⯦¶•44�h© 7®¾ëìQW�ª¹².¿z°ÀigDjç�->sýïgÞ$ˆ••Åòæù�¾ªÑ3k§]vf¡×s,‘½aoÎÀ\Ÿ-kÈèY§ÏøÅäBº*‡˜£|5ܘ�ædóYFܸ)åv«"£Ò}šÕ*:Y…›R¢°Ç"ØYrnq¤•[jxÏÞëu£*õè§žØ +‘�jzõB•zxO¼²š9:ú}Ç=÷�áŸ~º¶²ÊVHf�7>oº¾÷s}ö�æwúη‰CÆrxYo£šªHN§i:¬'"sï[9é-›pz(«Õ0פ?ïܣßF†?xÖ<2ü¶?�‡™…=;ö¯šÇŒ¡ßpS9úkÐdr`1K ©&d ã•Y§lBŒÈön\íÛg¾4w%ZZP[Ùšág&¢â¦êëõ[ô­¤iGωy�q臞+Ïk"8}¬ÌX½A·ì<%þ¬�Çki$ÕÒ? ý™ ÔÝ[ +“ôýú£7;A´ß­wêHÝ5V^àí)½�þ¨]ÞHieškΘXѳ�CO{¬~t9sKO{öŒÓNcn£´K¦u§¿›kJIØH‘� %d!¬ìõÚ�‚M%œÝ v¶yþ6ˆI0 Ãø³íÖÙEòP™‘c-]oš_´t=k¾¯ª­¤–?ÍG&üÓ•›­?D&é7ÞèÊæœ�­úV2]ÿ#9ÿ†\§·rЕž£Ä®\®Nt†Ñ?LÇâ„j”Ñ­ç›2ö`VrˆSÉW™SdYòðIA�l¬´5ÅæxlNžÈ•2)”‰¬(6O†ÑY¼Ý‹g^¬%ÔxK‹Jµ¾òä¶D¥ÕçÌ¢ L8½%©®î¯ +­7é¯.W;î’ò¡z+™õ”ËÊYü»9üð�¾,Ð]¸|-«÷\Ú™§M`–Qîf¬F?†ÁÄä`'Cd°æ‹B"Ý”"^ú/nJñN‰%¢Øû +‡p‰¥?^h‰Ç\‰úík3ÇT™ÀºŠÖ¬Ö½€aô +¶UÏ&_®gnüü¶ž�0Œô �X�" ˆˆk‰“¬†/igr}¼½�!¾Ѫ*­ª¢=ÌHœEצ@áY¦06Hò²L‰ú#xÓW›ðMi ¸ ™Ü€<ÖRÈ2á÷0-‚Ù£9Óã†t†pá ¤2$¯_êUŒCÜ ‡B,IŽ!¬?ÏbµZnNY­¬ßãÀnM Èò+[S~îü�–àú|úâ/¼Þâ Ñ «Ø½!ä)÷0žÌ@W¢+kéŠue^Q!½‡¢Oé.\ ,^Q^Øû½HqÕÐtmn«�Þ³Š^NõPžÒ2eÎŽ»—\šjìÔ÷×±ýÝ×.#çõ¼½@"BÕ‚™+6ñïIÍ™Úúí¹ìÄö΃—tp úc�þÓ¸»Ù©ü£½çðvÏ8—m÷ gÙŲÙÞ]åNâì;‰ËìvúïžÃýî™�õÓ¦Õן=­çëQÓšëF�uw÷Äá#ÆO¨©™pæi5&ÔŒ�ÿ.;0‚ +endstream +endobj +889 0 obj +<> +endobj +890 0 obj +<> +/W [0 [645.50781 0 0 275.87891 0 493.16406] 11 12 369.14063 15 [270.99609 404.29688 0 443.35938] 19 28 575.19531 31 33 707.03125 34 [437.98828 0 703.125 641.11328 0 737.30469 532.22656 520.01953 710.9375 766.11328 316.89453 445.3125 648.92578 511.23047 957.03125 790.03906 758.30078 614.25781 758.30078 652.83203 560.54688 585.9375 723.14453 666.99219 1004.88281 655.27344 0 606.93359] 68 [538.08594 620.11719 479.98047 619.14063 541.01563 383.30078 619.14063 602.05078 284.17969 284.17969 559.08203 284.17969 916.01563 604.98047 611.32813 620.11719 619.14063 397.94922 439.94141 389.16016 604.98047 541.99219 797.36328 552.24609 538.08594 479.00391] 104 [723.14453 0 0 0 538.08594] 124 [610.83984] 129 [604.98047] 137 [627.92969]] +/DW 0>> +endobj +891 0 obj +<> stream +xœ]’Ënƒ0E÷þ +/“Ed¡BJ!H,úPi>€ØE*gÁßWÌä!uÒ¹žkÝñŒÈ«¢²½çâÓ�ºÏÛÞóxuøºÞ2pÓk#üë¡™˜È«¢^fCeÛ‘¥)çâ º~önᛣ/°eâÃp½íøæœ×[&êë4ýÂÖsɲŒh™Èßšé½€ ´í*Ö÷~Ù�óúYñ½LÀdEiôh`ž ®±°TJ)3ž–eYf ¬ùw®$Ù.­þi–‡O¥ d†´' +�TŒ†D%QŽHû#Rœ tçH§é@TÆH‰"¢Êä…|ö–*¹g|öôŠe’n +ÊJy”$‘.T‰·®JɮȒ=P÷VQ<‘‘HÏ)TB·DÔUD•{z†XÝ Èëï ò˜ª¾:Öãá$×ö‹6�ÓêZ¿?ãϲ® +endstream +endobj +52 0 obj +<> +endobj +892 0 obj +<> stream +xœ¥z|•ÕýþsÞq÷xïÎÍ͸777ëf_² $—,BÂHÂJ˜ K@ÄT ˆD jµÔRj­}A+ÑZ‹R­‹:-œ8@)ŽR ÷ü{Þ{Ã’Ú~~ÿ÷ý¼ç=ã{ÎyÎw�ñ¾ $À¸ñy…�=–Ò }R֕͘­×¾˜ÞÄów,­5}ö €‹ðòì•+¼“2ê¼€zÀm�»tÞâ=¯—¼ØûíúyË—Â?@ ¬—y‹VÏÝÖEn2‸•]�‹¯|Ïõý1 ©оÛ5§£óäž{j²@qWל«dôd€Ô®Å+®øûþsÿÿíb˜þ¯u•úõÿõÏ¿¸—pP< +'˜§� …¬x8ÖX@?§7� �H&ý^¡Gä‰X[ûé1ñ0rõô+¾ F€~õS}GŒib�MvDz—ÇÞ ,ÃÚKá¼ë-¼õ¾Œ�è‰Åwâ�³ù,~7ºÎ¦ïÀ沨¥Ü~ã¿ã0îÇ'ä)Âý¨ìFÜàYìÃ:4` +šÅCâ!܈VlEë¿ûX{í+JÈFy%Vá*tà*¬‹•­Sx¥l±ò¾ëp+6á~ÜAÞÀh¬@'vžkLåÇ X€Å�ÌÇóØŽµ¸ Å>X¸TXxÐo°óþÍ÷ÿûu fcöFnˆ¬A':±×p?ÀÈFaýP‡XˆÑêù‘|‚'± 7`Y¸åƒÃNW›œ®„«¯qz®¾ÆýÚëNWÂÊUNWÂâ¥NW¢%NWÂÂËœž…—­¹<~ÅvG¼vGÂÜùvGœ.»gN× ËâÝË�WU»}««Ý¾Ê?“*˜ÉÜOF€£‡ýGf°,ÜOÂ� ¯Ž¾S3Ø»ò‘ì¼èÛ•ÀÞÃÃδ`ÙgGø`øHvnYøˆ'¡,òQ~�Òˆû£Ä¤²ð[.WÙWOqÁ§ú¸à]}\°¯W öÓÃ{¾¶;Ëníá•øw6gÙÑ^!ø$©#µà$µ�õj‚=½©ÉýôpX?;Î]6w6ÙÖË:8üHo £¬Ÿ�Ç®å‚gé‚�“ +R¾› †ûIùîäÔ²~’·ûZ>ØOrYÏ_í'Ù á·]n”ö/)�²ð3.K=²7ÞÃÞ{öJÖ²ý/30{÷¼à÷—½ø¼KÇõƲ/¯ã‚»~Ï]t|o˜­Jå?¥g(M&lNJ*»©[v¯×7­çƒëÖ�àµ× ÁëÖ+à WÌŠs—ÍZO‚דà†õ$xÃz¼~½übý¿Ösó×O‰#®Øá(rX‡8Ì!‡¡Ð¡-p¨ò|ž¹Ž‰d(3)%e0‘bRô¤ÃH)ÿý¼DJ!@OŠ1Œc*)‰ !ªÝç!‰Æ8u¼Ñ!¹ŒVÁnÌó�ìò¬òŒò´òÔò”royR¹§<®ÜQn-7—kËUå|9Ê›BˆlmDã„*ÙFÑ8¾Jûyo‹\l”µMS[w²¥Menc?ÁYØØÏa‚l­ž2µµŸ¸Yñ žÇAäÆönn åÎÆñ­òÚÄ6¹�E¶&¶¡Q.l–=þªàÅ×r%X>˜ŒíÊH«•³j;äìÚö¥`E?QÕÎï'ºÚùýDç¯é'šhº½Ÿhü5±êýd(Ë-­�ßÞOJ•’.VÒÅQ²³YqÅ�]-ÄÙØ¹v9N®lÿ£aîÒ2v6µT5Êš–FYÓ4UŽ÷W5ÊÏ·4ÊÅMSeƒ¿JÙ{vÁ€“ + ´4-t4�ê¡§` 0ÂD`‚™ž�= zXéiXa§§aSB;\ô4ˆ£§àTBÜôâOOÁ�ú/Ä#‘þ $Ñ!Éô_H„—žD|ô$’‘BO‹TúOø”0úOø‘Fÿ‰T¤Ó"€ úÒ�I@:²èl÷D¿G&²é÷ÈB.ýAäÑï��|úrP@¿C. +éwÈSÂ| ¡ß¢Eô[¢˜~‹Jè·‚RzE(£'PŒ¡ôJPNO T Ë0œþƒíÐè?0 •ô(ÇzÃQE�£5ô8*QK�#ŒZú F Ž~ƒ*Œ¤ß õôÔ`ýµh _£�ôkŒÄhú5ê1†Ã(Œ£ÇЀ&z �h¦Ç0Íô(Æ …ÅXL G1éQ4a=ŠfL¦_¡­ô+ŒGý +0…~‰‰˜J¿Ä$L£_b2fÐ/Ѫ„m˜I¿`»Jú¦*á4̦Ÿc::éç˜�9ôsÌÄ\zí˜G�°¹˜Á,%œ�ô:±�~†9XL?Ã\\F?c» úº°”~ŠùXF?Å\N?ÅB,§Ÿb® Ÿ`1VÒOp™.Á*ú –b5ý˰š~ŒËq5ýË•p®¡ã +\K?ÂJ\G?Â*¬¥áJ¬¥b5~F?ÄUJx5ÖÓq n âZÜHã:l ‡±éa¬ÅMô¬C7ý?Ãfú®ÇÍô¬Çú>nÀúnÄ-ô}lÀVú>6âVú>nÂmô=tãvú6¡—¾‡ÍØFßÅ͸ƒ¾‹-¸‹¾‹[ÐGßÅVôÑCèÁÝônÅ=ônÃ}ônÇÏéßÑ‹ûéß± ¿ ÇØNÿŽ;ñKúîÂôôáWôÜ�ô܃_Ó·q/vÒ·q~C߯Ïñ[ú6îÇoéAü¿£±Óƒø%~Oâì¢oáWJ¸»é[ø5¡oa'¡ð¤/á |D_›ø˜¾ˆø„¾ˆ·ð)}ñ}oã}ïàsúþŽ/é 8„¯è x_ÑçñŽÑçñ>¾¦Ïã%<Œãô¯øÿ ÅGø–>‡�•ð|GŸÃ§øž>‡Ïð}Gp’>‡Ïq’>‹/pŠ>‹/qš>‹¯”ð(è_pL ¿¥Á7„пà8!tþAxº'”ð["Ð}øŽˆô|OÔôü@4ôü“hé38Itôiü‹èéÓ8E ôiœ&Fú4Î3Ý‹"ѽˆ(!%º7Ü4¡¥iܘÑ� £êGÖUW�WV /6´¬´¤¸hH¨° ?/7';˜•™‘žHõ§ø¼ÉI‰ žxwœËé°Û¬Él2ô:­F­ž#È&qr\ukíÙ]Ý.ü5~É+Æ“'Ãêñù-ÞP^[NŒJƒ2l�²½©uÂ¥m²*x1ÉX™H'|2¬c<ÞZYÈBÀßÐÑ)g´´úüÒ[ž³åmm9Ùr|u«Ï瑹€ÌFÉ|@æ ÞNYjjõ±%g”Œ¦VöôÓ�J}¥¾6�Œ–V9i0ÙÖv)��tïE0Ç’ni—Á]]#þ †�d8ÙñRÈ(—3‚2�d”+­!O&ö2±ÉÄ1F†õÂ.XµÃ¥—àAmçmç|Ù]ÝÙ~Ž§Ç£õy»½Ý-­–�ÇçS@7ÊÏ7·îÒëªýÕstm9ÙP2°K§¯öWëY†lð/ÝE D‰p†Ú¡»8hŒ9Ù²•Á­eÏ9¼©]æþŸÏ—“-ÛΕôÓ½›Ï/‚l ÆlÑX„¬ª–ÕQÞùr¸CÆ&ï®ì½Ý›û%Ìj:ý�ÓZe¾£-'{ø@m×9¡±iJ«ÌjÙÓÞåeâ®Q&à¯aB¿ ¿³kN;SÒî¯ÉÉ–µÕ­|{=²µºuC­l ÊF�l¼êß]7ßË’ÝݼòýÍ­ç—úXØÖÖ—“íí®õ×vµ×äd×.¨b"É;+6EGu* oêðÊkg-ˆê^ÇæAý÷uK²áŸÇçóù˜x”Š1Vv¶/`�t°aÖ.ðvoš£ u³24ŸÏã­]PÃVÑ[+c¢W=¥µ¶Ë_{®Co·¿Ã+ó�‹ëú|²;È*vw×2ˆ�U9ÙQȲ;x?³ O�È\ ZOP^˜ ÈÀ[+‡;jÚbY1‚)¬+i¯ikóEåμ²:°AÌõ{»Y‹ê€lJ¾¿ør²÷æd7¶´ÖÖx”ÑË\uëðcqžcm9Ù�Mg³I\cKkwÞ1O”G�ãý�ÍQ-è Ú'D ˜;+yŸ¯-F¯´º?γ¿-'»Î_×ÞÝ]ç÷Öu·wwôÓµ³ü^Éß½Ë`è^ZÛîU,ŸtôÓ'6yäºÍm²ÔÞE†æd{™¾Õµ4ʶæ©LKÛ MÓ*ŽÙ™¬®–UŠ�uKGs²eCu«Ïã­c/õÈR)3S¯Œ‰­ýf+:«2Æ·Êœ‡Y +ߨ�?>Æ �oPa˜ßkŽå¶ådû|̆6õ‡1+'Û'¯mn�¦½˜åÙ�p^°MæÚYÉÞÁÇDV²v°älõv¿/';Žm�~R§Ï×çn‹ßê-ËSø¯¸ÛNyï„Ö~z²TÖ”ÆÄm«nå=\,ÆyxÓý’·\v•ŠŒ'rF°[ò{_õËRP«[÷zÊÛ¼’E†•ädËõAf5\@zÕÿa¾vI&å2q²|Ød¢¸tÞU*ÛYåñÖv·Ç´ëüaÅ&€Î®K�M’_6üà‰Ò[¬~6——óÔ�:fK_”¢¡M61,›Ž*AN¶ì©nõʤ½VF³ñÖz»˜°eo{�âÚ<çg÷ÓÃí5Ìíµzk‰'¦Ömž¨j_¤kÿ»†¯Ýä‘×mnëš“-‡³|9ÙÞ"™´+Ö2¡5Æ¥ROÌŠX_£ØP.,?ËÅAšs·q©óÚUÊJÏþ„V¹.8ØN4=2è9?YQñ¨ÁbÈ\ÀÂTl�W±‘p{¬Ù +š[åpÇ`Ï-­×z®bó +‡ª]~²±yW˜l?¥õq ðnœÐº›#\u{UÛ®T²±¹õq/Vr9–Ë2YÂËh$�-­»9�Bïy< ¬UJ%CIÏî'Pò4ƒy³û¹hž4˜Çav¿Í +yÊ2€…�Íêujö¥0z˜Ì³O¨Ñ¨*z*ÉE›Õ*µF5ࡵìðY%ª4jÝY2•JT‰¼JÅCäµ*• +‚ (Eâ…‡œ*üt:z¬mÐkÎahbÍĨù(&�Z£U©L*ûî¥V©4=û¦\jµ¨V j5¯S«ÔDA} Lêÿ “Ñ Q>±bÒÆ¢ês˜´ƒ˜Ø6�J¯c8Uj­Æp&ÕYLzµZ 1†é¢N/Ætq:: +³IÇ0EëŠ"t±¨æ& Õêôjµ´jƒžáTktZ“‚W¡Ö¨5jA£ Œ�¢JÔ\¢S ~:Å$™/Ĥ�Eµç0é�V§g² SÙÿZ�F¯3Ÿ%ÓjÕZ�¨Õ +Ð&­VËL{ LZüt:ÊY‹¤gØÎb2Äš‰Q3=3z�Þ Ñ B§1ï4Zƒ^Rxx1&³ŽaR«´—`Äÿ†Éf50LQ *Œ±ftç0™%LZ­"ôZÉÄx§Õ V…‡ +µN£ÓªtzZQÒëtPkTúKtªÃO§£œuØM ›f“)�uÆÞÄ,ÁhÖéLPÁ¨³HŒw:½ÉhWx¨Pëµz�Jo¡­½j�Úp‰NõøétSœSbïèxÔjH±fŒç0IÌ̃ÁLz;û–k2-f§‚W¡6ꌵѨ‚Aå0�Ðj5ÆKtjÄO§£Òö¸- SˆF[¬s”‚é™ °˜-6£Ñ5$£ÓÎpM6‹û,™Ùl05f³FµËl2C§×*E† ;4_ÀôcLZ ÑccØ¢@´ZØcÍHç0Ù«Åæ0™ìÐÀbŒs²9Àd¶[=ѹ€QKFɤ•$ L·$I “t FHøétTÚÞ$âÓÁk&ÖÓ}'`·Ú]’ä„6s<ûzj“,N{û&o±˜,’ÖbÕ@Òx¬ ôF�õŒ°à§ÓQiû’/ÄäŠa²žÃä6Gœ$¹ …ÝìqG1¹ÉŠ\j«É*é¬V-$m‚Íj…Á¨WZ¸HXÿVp‰ß @ %Žñ+:½îX3ö(3B7àrÄÅ[­nèà´$±3œV›Û•8¢dv»d·êí¬:¯Ãn‡Ñlp\‚vüt:*í¬tìшÄX3±˜=&ž8O’Ñ=ܶ/€8‡31>=öp¹¬.»ÁåÒáO�sº`¶˜”lvèº0ÉÔââË ä“¶(“ Þ?ÝQ +�ò¯ã}.§$8ÒØ?Wœ71Èþ#ŠR»ín§Éo€Ë�ïvC²š•"Ç…ºñÓé¨Úæz™&FÇc6#%ÆOO”‚Ùc +àMô¦ºãR`D’+3�ñΟ’œ åFíqzâÌž#ÜÆ`‚Ç«]J¸c<øétLÚûÇEX/®d‹#QÈÛŸ·¿ ?dñY>‹oÿþ™Ç¸ÇÄ•§ºoÆ‚#Vþ÷˜Ø§Ô1>JD^Í£2´¿° Ÿøù{¸‡VinŠZ¡ïæ‘?GF�?³o5k"ø±èqðz/*+ ò‰JÇ9ìIœËŸË ±Z‹Køœkþºy´#«<ýæ¬Ê,GÃÆ}«Å¾«"g"_Floúå½·‡«z﹯ñw$Dœ+Ah}ä9kWâU±ví§Rû‹ã¹¢!¹\z(‰''nÚ·ú×<¿y´#³<}SV8ËÉ(òrä«�•Wž8Ip{ÓöûnQ½íž{Ù÷9¬‚Ð#%,q&>¬RW©µàŸ§"*äM?VV¶?ˆø8i̱‚|bñ[|E>KÈâsðîë# ȶëÉmœ5²˜Üv#¹-²øF\ONqû¸«ÁC»›Œç�Ç*úJ|ÜŽ�÷9?9µŽñÉO�q7*Q¶Š©}�À0§äu§ÓÓ'¹‹úéñ°6¿ˆå÷ S£òXaÞôÒhh)Ë;VÆÆnâÔŽ$Þa7qþ”\.½$‰VpECÒüþ’h,—ó§˜8uI*LâOöŒ[ç”wŽL¯ÚðÒM% ¦�Mð{'ÏÌ›¿eRª¼{qòª®Ÿ/*IÖVšÞRçI‰��Q:gË´,aYýº®F« -nYP1uûUõfW¢ùkªÇ¿°tÕ¦›³Æ\VS–bŽK2? sXô¥S—ƒƒ‹~Av.Ø�i�(›%¿ì¶¡òXå±(~†»hHÇÐñ¥ÉkR†Ë•‘d±°wf’e·%)ó‚ ~' Y®3#ÙjMÎ8ÓçÊH²ZcÅ–¤L€�¬FC Û£¢$Šxœ�WZ˜WZ�("ìv�îú�kù)kzI©ŠÌº3’4 ½îû † ®°Žëq:Í‚ŠíFòò*­eeL¶>ÞÇûIÈFÒÒŠ† )ò›x5ññGF Üÿùsü$ùTäB^g0Š‘ûÈ7¤'²Dì;5Wؤ‹7š F­Öí‰× ÜžýÇ%ÌûàBùH›Ä¬žädk°ÇbMê±ò +ã.%}ñ&qj&nTx¦JT˜›6îê‰Ó+’›·>»<Ò@òȦIë§äÊqE‡¿Öùä¤u³FßzÇS ŽÌØØ:«¯î% º¢Ñ3‹—üæòab_þ„ei5EÉ‘‚ŒúÎá+ç¯8“9z!Óá›è±EÜ�r»䵻ÝÜgÓ^}2òØŽQìI.Ðß‚|’Ë3½d�A,Q«TQÉ[‹SC…N—˜“Ø8eáðmÿØ9µêªçU-šÖœ–Þüøò�Ü;iÕ!úãÚgk„‡l^û‚'Nߺjϵa³'à�éS~õù–'ÉÐ�®+ñ3¿×ˆ«Å>˜à†´ÇÞ£±Úz´ü9ýÎrŽqËWXÁ‰«ƒm›Ÿ9}Od() ·lþì—SäN:Aô¿¡¿�ÌOZðúSÛWU‹}ã¶¼ýõÏNo[ôäiÆ&·&±Oñ!V±'Ìé´\O0_Õ£µ’]´Ã âòââã}jå!>!oà`_ó¹ÑI’ǹˆëΈ‹iݹ–µp…õbÇiÔVÒ£Qš´–å�kÎmlÇù"7<Ä"çÝ”vn¢ŸŠsÅÝ"?l³÷8i.«¹'Þf“Ïö&ª´¨¨J§W+,´D;8_XÅ%6_‰�”W…À,Õ%Öù&Î]UÝûåöI#®úÝâÆÕC&Eõ‘“7OüÛšÛ>ÿÅÄü›û?Þаf~K¢°Ã™å³/Øsòæ+žøY­ÛN¶÷,Ì)ŸõÛ/6¬xûOw/nMð™c<åÒ•ùG÷±r¼âïC~úá!’÷0ûyN¡«Ä>X¡{DkÕEiñ“´´t?³I>T\\BBÜprâµÈX•UzYí[½[ÏÝö ÷Æ@w³Þ«.ö Ì0Äë¹e±vù±"Œzb½3K÷‘ññn’÷pä87Lì;ÍEéU[Å>�6Šv/o4ºSãyâF^¨2d-˳ºÊ +ò}~[qq‰âü|._äW©ÔŠçp:]Q bû¯_6¤¹CjºW=@[sf“^Pyì/o'ùÏl¶p*)7•ãßçŸ)ë7¬$[?ÐÊí0ä” uo�u&,ö LÊ›˜Ô”Ëíˆ�D¥û�¬pQŠrÈç'¹|:ÃÁz'I¼+TÁ—ØBÂ;O |Ãk4*¢"¼ rßì y/�Ó¢N§å"*î‹…k5®ø8µ.1)QúN±ïôÝÌ}i ^ŠA¨�ڜУü @|Ø z{$ÉšÜcãõVÆŒcÖóý—Óé²ùù4f}þ‹}˜Ð3âòû¦ç�t^Ù¿À5güµ×?Ä\×ümí¹rRE{Õ{—=7ÿ¶éYBÉôm‹Fhs{s¤ˆ<4£uà>±¯´cÝÔüæa¾�“…-‹A G(smJØ´éO>Äž¡‚/­ÇÃf¤ý•ÇöOW¤tž¾»ÿ*\8»ærél�¤\üîÚq“²šW%Æi)©è¨ñ›ÓkæßÞYº¨£91ÑÛ2mVþèÅ#} CÛ†´U§¿r×9m…}Voœ)§¶%5ot]]FNëôyå�¶)“ª2¤xŸô°+3ÙšZ9©0§¾vdÐßܾ¬ªå¶%#ô6—ûá¯Ê¬d~Œ·±GÅB!S~ñ³µ†°#Ò9ñ‘"3½ü'"w*"rw‚`'="ö‰}ðAÚß#Ú¼¼‰é„âM|Ô¨‹Kl!ÞÄŸõζ�ðAÒÔ[þ¶uÉÃ++ôÚ‡÷;A«ò'.¯žxãÜQq~ˆ m>´m\ÝU;:‚‹òùÀ™O]ÅÅùÖ¦•cÓóçÞw¿q§Ø3´»9ž OQCž©¾š) qv|ø>QàÑh±¨¹uO‹}§ïMoôú|&a6ÓægM¢bgÚÝ^7ØJZq 3%¢´§Šµw.&¶ìžÆ‰¼ K’ï¹|w'ˆ‚h’,.§o +ϱÍf5gú£�ž<6ÅÛ”xú ØwúÉ”±É¦Ôô4I¨9ýhÂèd)ð›„z€°ÿ—ÅtÅ+…S€ãA>Æ·àâÅ,‘3ˆä¨xFäDÒ#X¡Žºë²ÊceÓ—-›¾lsQ>µ¯„ÉIL<ýŸì;ýšN(ˆK8qÉéÌ;CÂ+w²ùk,="¸7¼(�3¬W9 ù½gjo¢¤L)ŠÀM¼Ã_Á3¯rÎ%;]ê´4ŠIˆÉP(áoÒ˜õꉠV Brxlûˆê%MÁ¸²™7ï½®éÁ òÏÈœ|Ã¬ê ‹=X�[8ytE¡_OêÓfNorÚKˇ: +&W¥åϼeæÐÛûn[QgY¥1”´­X½2¿ljØŸY7mÞÂBp¨¤G�xñÈC|ؘfÈìU;­ÞÞ8ÉæA Ù¬›y™M‘¯R¤*tª«¼ÓÉ š„Šö »–æÎš1ÉO†ì˜§wé9oýø™¥·Þ»Ó‘SWX¹¢½NªN™W2r区\×Ws÷][�0Æ¥8Å�gÁ¢¬PËÐäçÿX6-œÒpU_Säi[|^Ë¢ÕÊê{,=Âß ¸@vØâ•ò%N’$ÉØkwV‚ÄfC¾×+�› óJ§ä“A¶:ÙT¨¾häÛ³VÕ×.™T‘a»nqÕ–Jÿˆuõ36MËM©žµæž©Á±áB3_å7u-]’Ëí%Áüéá¥wµMøeÏ5m!ƒÝ¥�É|�x)(ƒ;lÔöZœ*GZo¢äSxÈØû� +îb¹ÛBäbÉß 6êÔŠj /¤Ö7M)®[:.+yDg÷îËJÛý]Û#ï¤�Ï›|ÃìêL‹-X“?(ûaÞ)S›œ–²Š2ëжaI…·Lo¸¿¯»k¸YC¾¸Io,ž²|õy¥ÓªR³êg,X\ÓWþ)…«…(|ìçŽx½©Þ"Y,&±7/·2—ËÍõ™zÎÌ^Ÿ4¸œ�Îæ„Ò Ó •JP«öÁµÆE fË4¦!…ÿTýŽÈñ繤ÊÙ5µ¿jL.¹sƲ»gfeŒš{Í–ÑãV7gýzú˜á—·×I·<ÝP^j¾aæ¢7èµÅíõYEÛ½ùuWÜ5aʯ·®š�›5ª½¢²®húºÆÄÒf¦ºÈC|‹J€î°ÁdК{­fˆ½q•ÏU æÅ& •ZRû‰ŸøKBÊËéâKt ^¿ÔÒæ +œð�ˆœÚA4r²œi«™±jd`¤[œ!j5âÂl:]"¼°é©ÖkƦ4àè{‘dÅÒÓQŽÌÇ¡¥{Ãvƒ¹^+iµ†^1ÁZÔç ö¦Ú=SÇ`A~€ ?-½D±®äÒN€;§ +-î– Ïm™ÕSž\¹¥¥íú™•¿�6ª©µ¨vé¸`zÃÂ�¿œ>ý‰±Jé(™qϲJ[戼’éc‡çyµ W¾Ô;>×{WÚ�!sn�qÿð¶a‰í[g´=Ô·nFQœyµÆ0¹÷¹ËЧU¥åŽî˜;;€`PôØÖ»jG¼Ôë•âb~ 2¶àý¶ +¾(&×èŠMa¤`Pkõâª× +•0²!¼ö²fiáRóÀ=B uöâ¡k×scQù0ûÀ+\«”•å×��Ô¸ê¶úˆ$H-ë™q8´ÓœC<| …{¶7™$ëÌÖ^yÇ*ßœ~¬ ?ÀT,-6é©T1˶Ùc¹¡B'¹ß]™<§S§ïݱM«Î)ˆO·Lž“=bDvv… ÑÍ™è®)äž(ÉÊ•2ƒ*•ðYN¸27wè°‚âQ˜& ZN¯rp½Y͘PYi±–•E—«±µ* ‘dÁSYXΆú¬@"¸ô�w¸Ä��Å£gf‘Ý Ê˜<â18`xT‡ØPÎ ‘³ã8‹�`G„ÆÐ‚vŸnˆ�ŽbU]!¸1Ší;¥ª>¥wäp©À¬v‘ +UV†Ž¹ +,5ቒ�J¥Žùõ$Î¥Tu…Z¯V?p­ Q 5õiÍcFËÒ-ܱ†¥ëêÒ[ÆŽ–©[¸ã>�dÒñ÷‹&Q¥R'̘[ž6ÜË&v,¾víyB·deúuã&úË›¦ÎÌ�èÎe ofîPúÄq5Îô‰©YCg�ÌH«�U^Ú12³e€àÜÈaZ¡v¸‰f¿Ô›©xûÊÿ¤›?˜àTëÏêè¨úªŸ]6ÎtÑüg/^3kD2ó�òa޳Ú:ºuì•[«/DYÒ1*+³¡+æGO îØ¹‰¿Wtº{ÍÒ87œ8×kü©1=¯ü¬õæÙEÍ·í¿fòÍ�%;½C[B­[f%—�Ò¶eÖî†�ßíYX:¿wÆÍßÿa^éüÞ¹#æÔ¦ÜöÇYsF¦µÝö$8ú×H@Ð(ke2xBl.7»Ózb|:V98•§¥Gybl ŸKPóÊÂY4ÞªÙ7>8oØòàܧԭj§¨ÑðÂÈÝóÙŒ´Ó–][8dêØp¡_[i-,.�«½búÈü8®¯áç}Ýó‡;Í·ptRZ9̺zU~ûÖ¹ÌãëgÎ�—9¨1êT¹ãFç÷À{ô)�êsôDp a›³^` ¨ýÁ¼REÚþ´ „lâckì#¼CLam`«Öj6ðÛy�Fà ުQ-ù %N¾Åàj¼ú¾ñ‘*ngÖ´iR’ª+C¦²i#Rõ̪$z„;)¸Q€ŒpœÏ”©l‡qøÞ\é7q'õÚœlû1ýÜþƒ�Q)Û§óÂýbe÷ÁŽ™˜è�.î@ÎÒÒól�²ÀÒÙÁ½ “êGU»}öaµ��ÎÙæ´Š¼¹¹S»§Õ¯ËáG'¦N™˜Ìp�¸iÒÐ%‡h$—ùS‚ÓÔ42>;;'¡bÃÄŠe“‡$8ÿfѯ¹×Ä<à KFÉhÔôZœ~’ê�ïM4£òX(T˜§ÌBÊDÌßœ’S\b‹ÎŠ%¼�Û‰,Wr + iéæd[FYSȥ׬|0yg‘PTYÄWŽ¿bàÃäzg(Gc0«¶š‰ÅcòÇx¹œM§8³M"‘×¢¸Ž•ìø:ºÖ§õZœ¹æ³²‡‰­‡Õþââ’âó&CnpC£,,Yb»Ö¬©‘}€gFZ^5»Ê§Ñ…UQ�4jTvëø†ò,ÝÂ_ú„±šhØÈŒ™+Ø©sc[êðæ©íÙä»èÞ€ÿ@`ßDRÿ ƹêÑO?] õ"éP÷j̱í@0xþV€?0®.ùÌc_&óqü+gžÜTć7QÝ ˆ¸4Ô(ߥnÁK@4Ÿ?¥ä·ÄòRNü +ûødqÌð#.lLvèŸ^Êoå93OxäMg§ÚÊzK™3¢~D¼ø<6yhfÆP)Þos> +endobj +894 0 obj +<> +/W [0 [645.50781 0 0 274.90234 0 374.02344] 11 12 304.19922 14 [685.05859 0 399.90234] 22 [539.0625] 29 [217.77344 0 0 0 685.05859 0 0 631.83594 573.24219 639.16016 711.91406 499.02344 488.76953 687.98828 0 266.11328 0 599.60938 462.89063 817.87109 750.97656 0 0 0 582.03125 497.07031 525.87891 688.96484 627.92969 929.19922 0 0 569.82422] 68 69 541.99219 70 [436.03516 541.99219 486.81641 294.92188 541.99219 557.12891 258.78906 0 490.23438 258.78906 857.91016 562.01172 535.15625 541.99219 0 354.98047 395.01953 323.24219 558.10547] 93 [452.14844] 108 [541.99219] 124 [535.15625]] +/DW 0>> +endobj +895 0 obj +<> stream +xœ]’Ënƒ0D÷þ +/›E„1�¤BJ H,úPi>€à E*gÁßWxò�ºéÀŒ=ãk/+óR÷–{Ÿfl*²¼íµ24�WÓ¿P×kæK®úÆÞȽ›¡ž˜—•yµÌ–†R·#Kν/êúÙš…¿Ôx¡ ó>Œ"Ó뎿œ³jüê:M¿4�¶\°4åŠZæeoõô^Ä=gÛ–Š´íí²=gÕSñ½LÄ¥cišQÑ<Õ ™ZwÄ!„HyRE‘2Òêß_Âvi›ŸÚ8y�òD)RGH‚N £#_€rPì(@9èàHú (s¾: + Œ Œ@;P å)t´±+rK¼»çö=:™€Zî±g:`kQ'@º­‚èžÜÅBñökư‡°G8°ööh�ò–éÖó_ïÉc¸ÍÕÒÖ]&7Ðu”½¦Ç}›Æiu­ÏÆÎ³ð +endstream +endobj +80 0 obj +<> +endobj +xref +0 896 +0000000000 65535 f +0000000015 00000 n +0000101209 00000 n +0000000286 00000 n +0000198745 00000 n +0000000323 00000 n +0000214405 00000 n +0000000399 00000 n +0000000548 00000 n +0000000691 00000 n +0000000853 00000 n +0000001025 00000 n +0000001217 00000 n +0000001379 00000 n +0000001520 00000 n +0000001712 00000 n +0000001885 00000 n +0000002064 00000 n +0000002239 00000 n +0000002385 00000 n +0000002560 00000 n +0000002722 00000 n +0000002914 00000 n +0000003107 00000 n +0000003267 00000 n +0000003460 00000 n +0000003640 00000 n +0000003821 00000 n +0000004049 00000 n +0000004274 00000 n +0000004420 00000 n +0000004610 00000 n +0000004785 00000 n +0000004990 00000 n +0000005254 00000 n +0000005519 00000 n +0000005743 00000 n +0000006012 00000 n +0000006280 00000 n +0000006529 00000 n +0000006777 00000 n +0000006937 00000 n +0000007131 00000 n +0000007351 00000 n +0000007519 00000 n +0000007702 00000 n +0000007933 00000 n +0000008160 00000 n +0000008306 00000 n +0000008510 00000 n +0000008728 00000 n +0000101769 00000 n +0000230011 00000 n +0000012008 00000 n +0000012223 00000 n +0000012486 00000 n +0000012749 00000 n +0000013034 00000 n +0000013317 00000 n +0000013606 00000 n +0000013894 00000 n +0000014050 00000 n +0000014243 00000 n +0000014424 00000 n +0000014643 00000 n +0000014882 00000 n +0000015121 00000 n +0000015267 00000 n +0000015442 00000 n +0000015616 00000 n +0000015771 00000 n +0000015964 00000 n +0000016145 00000 n +0000016351 00000 n +0000016497 00000 n +0000016686 00000 n +0000016884 00000 n +0000102206 00000 n +0000021192 00000 n +0000020823 00000 n +0000242506 00000 n +0000028802 00000 n +0000102499 00000 n +0000032601 00000 n +0000032642 00000 n +0000032683 00000 n +0000102791 00000 n +0000034926 00000 n +0000050522 00000 n +0000103084 00000 n +0000053361 00000 n +0000059607 00000 n +0000103401 00000 n +0000062072 00000 n +0000103679 00000 n +0000066673 00000 n +0000073688 00000 n +0000103996 00000 n +0000077434 00000 n +0000087896 00000 n +0000093808 00000 n +0000104302 00000 n +0000096713 00000 n +0000104572 00000 n +0000099459 00000 n +0000104866 00000 n +0000104988 00000 n +0000105078 00000 n +0000105146 00000 n +0000178360 00000 n +0000172977 00000 n +0000110241 00000 n +0000110162 00000 n +0000132517 00000 n +0000132431 00000 n +0000110323 00000 n +0000110475 00000 n +0000110396 00000 n +0000132281 00000 n +0000112342 00000 n +0000110584 00000 n +0000110736 00000 n +0000110657 00000 n +0000112208 00000 n +0000111106 00000 n +0000110845 00000 n +0000110997 00000 n +0000110918 00000 n +0000111446 00000 n +0000111184 00000 n +0000111336 00000 n +0000111257 00000 n +0000111788 00000 n +0000111524 00000 n +0000111678 00000 n +0000111598 00000 n +0000112130 00000 n +0000111866 00000 n +0000112020 00000 n +0000111940 00000 n +0000116164 00000 n +0000112428 00000 n +0000112582 00000 n +0000112502 00000 n +0000116046 00000 n +0000114108 00000 n +0000112692 00000 n +0000112846 00000 n +0000112766 00000 n +0000113982 00000 n +0000113220 00000 n +0000112956 00000 n +0000113110 00000 n +0000113030 00000 n +0000113562 00000 n +0000113298 00000 n +0000113452 00000 n +0000113372 00000 n +0000113904 00000 n +0000113640 00000 n +0000113794 00000 n +0000113714 00000 n +0000115960 00000 n +0000114194 00000 n +0000114348 00000 n +0000114268 00000 n +0000115826 00000 n +0000114722 00000 n +0000114458 00000 n +0000114612 00000 n +0000114532 00000 n +0000115064 00000 n +0000114800 00000 n +0000114954 00000 n +0000114874 00000 n +0000115406 00000 n +0000115142 00000 n +0000115296 00000 n +0000115216 00000 n +0000115748 00000 n +0000115484 00000 n +0000115638 00000 n +0000115558 00000 n +0000121188 00000 n +0000116250 00000 n +0000116404 00000 n +0000116324 00000 n +0000121070 00000 n +0000117968 00000 n +0000116514 00000 n +0000116668 00000 n +0000116588 00000 n +0000117842 00000 n +0000117042 00000 n +0000116778 00000 n +0000116932 00000 n +0000116852 00000 n +0000117384 00000 n +0000117120 00000 n +0000117274 00000 n +0000117194 00000 n +0000117764 00000 n +0000117462 00000 n +0000117616 00000 n +0000117536 00000 n +0000120984 00000 n +0000118054 00000 n +0000118208 00000 n +0000118128 00000 n +0000120826 00000 n +0000118582 00000 n +0000118318 00000 n +0000118472 00000 n +0000118392 00000 n +0000118924 00000 n +0000118660 00000 n +0000118814 00000 n +0000118734 00000 n +0000119266 00000 n +0000119002 00000 n +0000119156 00000 n +0000119076 00000 n +0000119646 00000 n +0000119344 00000 n +0000119498 00000 n +0000119418 00000 n +0000119988 00000 n +0000119724 00000 n +0000119878 00000 n +0000119798 00000 n +0000120368 00000 n +0000120066 00000 n +0000120220 00000 n +0000120140 00000 n +0000120748 00000 n +0000120446 00000 n +0000120600 00000 n +0000120520 00000 n +0000126219 00000 n +0000121274 00000 n +0000121428 00000 n +0000121348 00000 n +0000126101 00000 n +0000123342 00000 n +0000121538 00000 n +0000121692 00000 n +0000121612 00000 n +0000123208 00000 n +0000122066 00000 n +0000121802 00000 n +0000121956 00000 n +0000121876 00000 n +0000122408 00000 n +0000122144 00000 n +0000122298 00000 n +0000122218 00000 n +0000122750 00000 n +0000122486 00000 n +0000122640 00000 n +0000122560 00000 n +0000123130 00000 n +0000122828 00000 n +0000122982 00000 n +0000122902 00000 n +0000126015 00000 n +0000123428 00000 n +0000123582 00000 n +0000123502 00000 n +0000125865 00000 n +0000123956 00000 n +0000123692 00000 n +0000123846 00000 n +0000123766 00000 n +0000124298 00000 n +0000124034 00000 n +0000124188 00000 n +0000124108 00000 n +0000124641 00000 n +0000124376 00000 n +0000124530 00000 n +0000124450 00000 n +0000125023 00000 n +0000124719 00000 n +0000124873 00000 n +0000124793 00000 n +0000125405 00000 n +0000125101 00000 n +0000125255 00000 n +0000125175 00000 n +0000125787 00000 n +0000125483 00000 n +0000125637 00000 n +0000125557 00000 n +0000129402 00000 n +0000126305 00000 n +0000126459 00000 n +0000126379 00000 n +0000129284 00000 n +0000128037 00000 n +0000126570 00000 n +0000126726 00000 n +0000126645 00000 n +0000127911 00000 n +0000127104 00000 n +0000126837 00000 n +0000126993 00000 n +0000126912 00000 n +0000127449 00000 n +0000127182 00000 n +0000127338 00000 n +0000127257 00000 n +0000127833 00000 n +0000127527 00000 n +0000127683 00000 n +0000127602 00000 n +0000129198 00000 n +0000128123 00000 n +0000128279 00000 n +0000128198 00000 n +0000129080 00000 n +0000128657 00000 n +0000128390 00000 n +0000128546 00000 n +0000128465 00000 n +0000129002 00000 n +0000128735 00000 n +0000128891 00000 n +0000128810 00000 n +0000132195 00000 n +0000129488 00000 n +0000129644 00000 n +0000129563 00000 n +0000132077 00000 n +0000130830 00000 n +0000129755 00000 n +0000129911 00000 n +0000129830 00000 n +0000130712 00000 n +0000130289 00000 n +0000130022 00000 n +0000130178 00000 n +0000130097 00000 n +0000130634 00000 n +0000130367 00000 n +0000130523 00000 n +0000130442 00000 n +0000131991 00000 n +0000130916 00000 n +0000131072 00000 n +0000130991 00000 n +0000131873 00000 n +0000131450 00000 n +0000131183 00000 n +0000131339 00000 n +0000131258 00000 n +0000131795 00000 n +0000131528 00000 n +0000131684 00000 n +0000131603 00000 n +0000132704 00000 n +0000132623 00000 n +0000132853 00000 n +0000132772 00000 n +0000133002 00000 n +0000132921 00000 n +0000133150 00000 n +0000133069 00000 n +0000133452 00000 n +0000133299 00000 n +0000133218 00000 n +0000133371 00000 n +0000133610 00000 n +0000133529 00000 n +0000133993 00000 n +0000133678 00000 n +0000133840 00000 n +0000133759 00000 n +0000133912 00000 n +0000134868 00000 n +0000134387 00000 n +0000134078 00000 n +0000134234 00000 n +0000134153 00000 n +0000134306 00000 n +0000134782 00000 n +0000134473 00000 n +0000134629 00000 n +0000134548 00000 n +0000134701 00000 n +0000135064 00000 n +0000134984 00000 n +0000135453 00000 n +0000135131 00000 n +0000135225 00000 n +0000135385 00000 n +0000135305 00000 n +0000135618 00000 n +0000135538 00000 n +0000135918 00000 n +0000135766 00000 n +0000135686 00000 n +0000135838 00000 n +0000136075 00000 n +0000135995 00000 n +0000136223 00000 n +0000136143 00000 n +0000136371 00000 n +0000136291 00000 n +0000136520 00000 n +0000136439 00000 n +0000139241 00000 n +0000136977 00000 n +0000136587 00000 n +0000136662 00000 n +0000136824 00000 n +0000136743 00000 n +0000136896 00000 n +0000137461 00000 n +0000137071 00000 n +0000137146 00000 n +0000137308 00000 n +0000137227 00000 n +0000137380 00000 n +0000137945 00000 n +0000137555 00000 n +0000137630 00000 n +0000137792 00000 n +0000137711 00000 n +0000137864 00000 n +0000138429 00000 n +0000138039 00000 n +0000138114 00000 n +0000138276 00000 n +0000138195 00000 n +0000138348 00000 n +0000138913 00000 n +0000138523 00000 n +0000138598 00000 n +0000138760 00000 n +0000138679 00000 n +0000138832 00000 n +0000139163 00000 n +0000139007 00000 n +0000139082 00000 n +0000139470 00000 n +0000139389 00000 n +0000139618 00000 n +0000139537 00000 n +0000139803 00000 n +0000139686 00000 n +0000139950 00000 n +0000139870 00000 n +0000140097 00000 n +0000140017 00000 n +0000140245 00000 n +0000140165 00000 n +0000140621 00000 n +0000140544 00000 n +0000140392 00000 n +0000140312 00000 n +0000140464 00000 n +0000140792 00000 n +0000140712 00000 n +0000140940 00000 n +0000140860 00000 n +0000141548 00000 n +0000141008 00000 n +0000141169 00000 n +0000141088 00000 n +0000141237 00000 n +0000141399 00000 n +0000141318 00000 n +0000141467 00000 n +0000141730 00000 n +0000141649 00000 n +0000142648 00000 n +0000141798 00000 n +0000141960 00000 n +0000141879 00000 n +0000142028 00000 n +0000142190 00000 n +0000142109 00000 n +0000142258 00000 n +0000142339 00000 n +0000142580 00000 n +0000142500 00000 n +0000142845 00000 n +0000142765 00000 n +0000143449 00000 n +0000142913 00000 n +0000143073 00000 n +0000142993 00000 n +0000143141 00000 n +0000143301 00000 n +0000143221 00000 n +0000143369 00000 n +0000143630 00000 n +0000143550 00000 n +0000144238 00000 n +0000143698 00000 n +0000143859 00000 n +0000143778 00000 n +0000143927 00000 n +0000144089 00000 n +0000144008 00000 n +0000144157 00000 n +0000144420 00000 n +0000144339 00000 n +0000145658 00000 n +0000144644 00000 n +0000144488 00000 n +0000144563 00000 n +0000144878 00000 n +0000144722 00000 n +0000144797 00000 n +0000145112 00000 n +0000144956 00000 n +0000145031 00000 n +0000145346 00000 n +0000145190 00000 n +0000145265 00000 n +0000145580 00000 n +0000145424 00000 n +0000145499 00000 n +0000145879 00000 n +0000145798 00000 n +0000146028 00000 n +0000145947 00000 n +0000146177 00000 n +0000146096 00000 n +0000147557 00000 n +0000146472 00000 n +0000146244 00000 n +0000146400 00000 n +0000146319 00000 n +0000146859 00000 n +0000146550 00000 n +0000146706 00000 n +0000146625 00000 n +0000146778 00000 n +0000147173 00000 n +0000146945 00000 n +0000147101 00000 n +0000147020 00000 n +0000147479 00000 n +0000147251 00000 n +0000147407 00000 n +0000147326 00000 n +0000147772 00000 n +0000147692 00000 n +0000148087 00000 n +0000147839 00000 n +0000148019 00000 n +0000147939 00000 n +0000148244 00000 n +0000148164 00000 n +0000148392 00000 n +0000148312 00000 n +0000148539 00000 n +0000148459 00000 n +0000148686 00000 n +0000148606 00000 n +0000149063 00000 n +0000148986 00000 n +0000148834 00000 n +0000148754 00000 n +0000148906 00000 n +0000149235 00000 n +0000149154 00000 n +0000149384 00000 n +0000149303 00000 n +0000150223 00000 n +0000149452 00000 n +0000149614 00000 n +0000149533 00000 n +0000149682 00000 n +0000149844 00000 n +0000149763 00000 n +0000149912 00000 n +0000150074 00000 n +0000149993 00000 n +0000150142 00000 n +0000150421 00000 n +0000150340 00000 n +0000150800 00000 n +0000150489 00000 n +0000150651 00000 n +0000150570 00000 n +0000150719 00000 n +0000150966 00000 n +0000150885 00000 n +0000151345 00000 n +0000151034 00000 n +0000151196 00000 n +0000151115 00000 n +0000151264 00000 n +0000151510 00000 n +0000151430 00000 n +0000151658 00000 n +0000151578 00000 n +0000151806 00000 n +0000151726 00000 n +0000151954 00000 n +0000151874 00000 n +0000152102 00000 n +0000152022 00000 n +0000152250 00000 n +0000152170 00000 n +0000153851 00000 n +0000152471 00000 n +0000152317 00000 n +0000152391 00000 n +0000152703 00000 n +0000152549 00000 n +0000152623 00000 n +0000153765 00000 n +0000152781 00000 n +0000152856 00000 n +0000153639 00000 n +0000153093 00000 n +0000152937 00000 n +0000153012 00000 n +0000153327 00000 n +0000153171 00000 n +0000153246 00000 n +0000153561 00000 n +0000153405 00000 n +0000153480 00000 n +0000154293 00000 n +0000153978 00000 n +0000154140 00000 n +0000154059 00000 n +0000154212 00000 n +0000154693 00000 n +0000154378 00000 n +0000154540 00000 n +0000154459 00000 n +0000154612 00000 n +0000155319 00000 n +0000154778 00000 n +0000154940 00000 n +0000154859 00000 n +0000155008 00000 n +0000155170 00000 n +0000155089 00000 n +0000155238 00000 n +0000155888 00000 n +0000155576 00000 n +0000155420 00000 n +0000155495 00000 n +0000155810 00000 n +0000155654 00000 n +0000155729 00000 n +0000156319 00000 n +0000156004 00000 n +0000156166 00000 n +0000156085 00000 n +0000156238 00000 n +0000156872 00000 n +0000156560 00000 n +0000156404 00000 n +0000156479 00000 n +0000156794 00000 n +0000156638 00000 n +0000156713 00000 n +0000157069 00000 n +0000156988 00000 n +0000157218 00000 n +0000157137 00000 n +0000157367 00000 n +0000157286 00000 n +0000158610 00000 n +0000157740 00000 n +0000157434 00000 n +0000157588 00000 n +0000157508 00000 n +0000157660 00000 n +0000158132 00000 n +0000157826 00000 n +0000157980 00000 n +0000157900 00000 n +0000158052 00000 n +0000158524 00000 n +0000158218 00000 n +0000158372 00000 n +0000158292 00000 n +0000158444 00000 n +0000159064 00000 n +0000158734 00000 n +0000158815 00000 n +0000158996 00000 n +0000158915 00000 n +0000159230 00000 n +0000159149 00000 n +0000159379 00000 n +0000159298 00000 n +0000160236 00000 n +0000159755 00000 n +0000159446 00000 n +0000159602 00000 n +0000159521 00000 n +0000159674 00000 n +0000160150 00000 n +0000159841 00000 n +0000159997 00000 n +0000159916 00000 n +0000160069 00000 n +0000161057 00000 n +0000160436 00000 n +0000160355 00000 n +0000160508 00000 n +0000160670 00000 n +0000160589 00000 n +0000160742 00000 n +0000160904 00000 n +0000160823 00000 n +0000160976 00000 n +0000161247 00000 n +0000161166 00000 n +0000161396 00000 n +0000161315 00000 n +0000161544 00000 n +0000161463 00000 n +0000161692 00000 n +0000161611 00000 n +0000162071 00000 n +0000161994 00000 n +0000161841 00000 n +0000161760 00000 n +0000161913 00000 n +0000162243 00000 n +0000162162 00000 n +0000162392 00000 n +0000162311 00000 n +0000162768 00000 n +0000162460 00000 n +0000162620 00000 n +0000162540 00000 n +0000162688 00000 n +0000164676 00000 n +0000163159 00000 n +0000162853 00000 n +0000163007 00000 n +0000162927 00000 n +0000163079 00000 n +0000163551 00000 n +0000163245 00000 n +0000163399 00000 n +0000163319 00000 n +0000163471 00000 n +0000164179 00000 n +0000163637 00000 n +0000163792 00000 n +0000163711 00000 n +0000163864 00000 n +0000164026 00000 n +0000163945 00000 n +0000164098 00000 n +0000164590 00000 n +0000164281 00000 n +0000164437 00000 n +0000164356 00000 n +0000164509 00000 n +0000165146 00000 n +0000164808 00000 n +0000164916 00000 n +0000165078 00000 n +0000164997 00000 n +0000165569 00000 n +0000165231 00000 n +0000165339 00000 n +0000165501 00000 n +0000165420 00000 n +0000165735 00000 n +0000165654 00000 n +0000165884 00000 n +0000165803 00000 n +0000166033 00000 n +0000165952 00000 n +0000166182 00000 n +0000166101 00000 n +0000166331 00000 n +0000166250 00000 n +0000166517 00000 n +0000166399 00000 n +0000166666 00000 n +0000166585 00000 n +0000166815 00000 n +0000166734 00000 n +0000166964 00000 n +0000166883 00000 n +0000167346 00000 n +0000167031 00000 n +0000167193 00000 n +0000167112 00000 n +0000167265 00000 n +0000167512 00000 n +0000167431 00000 n +0000167661 00000 n +0000167580 00000 n +0000168045 00000 n +0000167728 00000 n +0000167891 00000 n +0000167809 00000 n +0000167963 00000 n +0000168212 00000 n +0000168130 00000 n +0000168834 00000 n +0000168280 00000 n +0000168444 00000 n +0000168362 00000 n +0000168516 00000 n +0000168680 00000 n +0000168598 00000 n +0000168752 00000 n +0000169253 00000 n +0000168935 00000 n +0000169099 00000 n +0000169017 00000 n +0000169171 00000 n +0000169420 00000 n +0000169338 00000 n +0000169570 00000 n +0000169488 00000 n +0000169952 00000 n +0000169638 00000 n +0000169802 00000 n +0000169720 00000 n +0000169870 00000 n +0000170119 00000 n +0000170037 00000 n +0000170501 00000 n +0000170187 00000 n +0000170351 00000 n +0000170269 00000 n +0000170419 00000 n +0000170668 00000 n +0000170586 00000 n +0000170818 00000 n +0000170736 00000 n +0000170968 00000 n +0000170886 00000 n +0000171117 00000 n +0000171036 00000 n +0000171265 00000 n +0000171184 00000 n +0000171413 00000 n +0000171332 00000 n +0000171792 00000 n +0000171715 00000 n +0000171562 00000 n +0000171481 00000 n +0000171634 00000 n +0000171965 00000 n +0000171884 00000 n +0000172114 00000 n +0000172033 00000 n +0000172726 00000 n +0000172182 00000 n +0000172344 00000 n +0000172263 00000 n +0000172412 00000 n +0000172576 00000 n +0000172494 00000 n +0000172644 00000 n +0000172909 00000 n +0000172827 00000 n +0000174019 00000 n +0000174662 00000 n +0000175113 00000 n +0000175420 00000 n +0000175599 00000 n +0000175914 00000 n +0000176149 00000 n +0000176512 00000 n +0000176811 00000 n +0000177062 00000 n +0000177345 00000 n +0000177476 00000 n +0000178456 00000 n +0000178673 00000 n +0000197075 00000 n +0000197318 00000 n +0000198344 00000 n +0000198886 00000 n +0000213499 00000 n +0000213736 00000 n +0000214061 00000 n +0000214546 00000 n +0000228403 00000 n +0000228651 00000 n +0000229595 00000 n +0000230158 00000 n +0000241039 00000 n +0000241294 00000 n +0000242091 00000 n +trailer +<> +startxref +242655 +%%EOF diff --git a/doc/images/Kreisel-attribute.png b/doc/images/Kreisel-attribute.png new file mode 100644 index 0000000..15e20f6 Binary files /dev/null and b/doc/images/Kreisel-attribute.png differ diff --git a/doc/images/Kreisel-mit-gui.png b/doc/images/Kreisel-mit-gui.png new file mode 100644 index 0000000..c8a57de Binary files /dev/null and b/doc/images/Kreisel-mit-gui.png differ diff --git a/doc/images/gefaellestrecke-gui.png b/doc/images/gefaellestrecke-gui.png new file mode 100644 index 0000000..0cae1e4 Binary files /dev/null and b/doc/images/gefaellestrecke-gui.png differ diff --git a/doc/images/gefaellestrecke.png b/doc/images/gefaellestrecke.png new file mode 100644 index 0000000..44f0165 Binary files /dev/null and b/doc/images/gefaellestrecke.png differ diff --git a/doc/images/gf-linienzug1.png b/doc/images/gf-linienzug1.png new file mode 100644 index 0000000..3f86d3d Binary files /dev/null and b/doc/images/gf-linienzug1.png differ diff --git a/doc/images/gf-linienzug2.png b/doc/images/gf-linienzug2.png new file mode 100644 index 0000000..8cd4a5d Binary files /dev/null and b/doc/images/gf-linienzug2.png differ diff --git a/doc/images/gf-linienzug3.png b/doc/images/gf-linienzug3.png new file mode 100644 index 0000000..b5e6af2 Binary files /dev/null and b/doc/images/gf-linienzug3.png differ diff --git a/doc/images/gf-vf-kombination.png b/doc/images/gf-vf-kombination.png new file mode 100644 index 0000000..f37f8f1 Binary files /dev/null and b/doc/images/gf-vf-kombination.png differ diff --git a/doc/images/variofoerderer.png b/doc/images/variofoerderer.png new file mode 100644 index 0000000..af3e473 Binary files /dev/null and b/doc/images/variofoerderer.png differ diff --git a/doc/images/vf-gui1.png b/doc/images/vf-gui1.png new file mode 100644 index 0000000..431947b Binary files /dev/null and b/doc/images/vf-gui1.png differ diff --git a/doc/images/vf-gui2.png b/doc/images/vf-gui2.png new file mode 100644 index 0000000..f34cbe4 Binary files /dev/null and b/doc/images/vf-gui2.png differ diff --git a/doc/json-input-lisp.md b/doc/json-input-lisp.md new file mode 100644 index 0000000..910a632 --- /dev/null +++ b/doc/json-input-lisp.md @@ -0,0 +1,449 @@ +# JSON-Eingabe fuer den LISP-Bau + +Welche Objekte sich aus einer JSON-Datei an einer gegebenen x/y/z-Koordinate +erzeugen lassen, in welchem Format sie beschrieben werden und welche +LISP-Routine sie baut. Grundlage sind die Testdaten in `tests/testdata/*.json` +und die Treiber in `tests/test_*.lsp`, die sie lesen. + +Die zugehoerigen LISP-Routinen mit Signaturen stehen in +[lisp_methods.md](lisp_methods.md). Diese Seite ersetzt das fruehere +`tests/testdata/object_data.md` (geloescht in Commit `e147b89`, auf das Code- +Kommentare teilweise noch verweisen). + +**Feldnamen als Python-Konstanten**: `lib/testdata_schema.py` buendelt die +Feldnamen/festen Werte der Abschnitte 1 (Kreisel), 2 (Einzelbloecke), 3 (VF +Standard/Etage) und 4.1 (Gefaellestrecke Modus 1) als benannte Konstanten +(`sch.F_ABSTAND`, `sch.SEITE_LINKS`, ...), genutzt von `lib/dxf_anlage_export.py` +(eigene, mit unseren Makros gebaute Zeichnungen). `lib/dxf_scan_components.py` +(fremde Zeichnungen) hat dieselben Feldnamen weiterhin als eigene String- +Literale - gleiches Schema, noch nicht auf das gemeinsame Modul umgestellt. + +--- + +## 0. Uebersicht + +| Objekt | Erkennungsmerkmal im JSON | Bau-Routine (Produktion) | Koordinaten-Eingang | Beispiel-Datei | +|---|---|---|---|---| +| [Kreisel (Insert)](#1-kreisel) | `"function": "insert"`, KEIN `block` | `kreisel-insert-script` | `x`,`y`,`z` = Einfuegepunkt | `kreisel_tests.json`, `mubea.json`, `hundm05.json`, `dematic_arvato.json` | +| [Kreisel (Connect)](#12-connect-start--und-endpunkt) | `"function": "connect"` | `kreisel-connect-script` | `start_*`, `end_*` | (nur `test_kreisel.lsp`, z.Z. kein Datensatz) | +| [Einzelblock](#2-einzelbloecke-separator-scanner-btmt-eingangausgang-vario-bausteine) (Separator, Scanner, BTMT, Eingang/Ausgang, Vario-Bausteine) | `"function": "insert"` + `"block"` | direktes `_.INSERT` nach `ssg-ils-block-laden` | `x`,`y`,`z` + `rotation` | `mubea.json`, `hundm05.json`, `dematic_arvato.json` | +| [VarioFoerderer Standard](#3-variofoerderer-standard-und-etage) | `test_id` beginnt mit `VF_`, `typ` fehlt oder `"standard"` | `berechne-alle-winkel` + `variofoerderer-einfuegen` + `vf-block-erstellen` | `x`,`y`,`z` = KS_EIN des AS-Elements | `mubea.json`, `foerderer_tests.json` | +| [VarioFoerderer Etage](#3-variofoerderer-standard-und-etage) | `test_id` `VF_*` + `"typ": "etage"` | `berechne-etage` + `etage-foerderanlage-einfuegen` + `vf-block-erstellen` | wie Standard | `mubea.json` | +| [Gefaellestrecke Modus 1](#4-gefaellestrecke) | `test_id` `GF_*` bzw. `"modus": 1` | `gefaellestrecke-einfuegen` (bzw. `gf-rebuild`) | `start_mm` = KS_EIN des AS-Elements | `mubea.json`, `gefaellestrecke_tests.json` | +| [Gefaellestrecke Modus 2](#42-modus-2---richtung-aus-zwei-punkten) | `"modus": 2` | wie Modus 1, `deltaL`/`hz` aus zwei Punkten | `start_mm` + `linie_start_mm`/`linie_ende_mm` | `gefaellestrecke_tests.json` | +| [Gefaellestrecke Modus 3](#43-modus-3---linienzug) | `"modus": 3` | nur ueber `test_gefaellestrecke.lsp` | `start_mm`, `ende_mm`, `bogen` | `gefaellestrecke_tests.json` | +| [VF-Linienzug-Kette (Spec)](#5-vf-linienzug-kette-spec) | Kopf mit `spec_id`, danach `glied`/`sub` | `vsp-bau-aus-spec` (Replay durch `vf-linienzug-modus`) | `start_punkt` = Kettenanfang | `hundm05.json` | +| [VF-Linienzug (Journal)](#6-vf-linienzug-eingabe-journal) | Kopf mit `test_id`, danach `typ` = `point_abs`/`real`/`string`/... | `vfl-journal-replay-start` + `vf-linienzug-modus` | erstes `point_abs` | `linienzug_tests.json`, `hm_recformat.json` | +| [Omniflo Bogen/Weiche](#71-bogen-und-weiche) | `"type": "bogen"` / `"weiche"` + `sivasnr` | `_.INSERT` der `.dxf` (Kern `omni:insert-dxf` fragt den Punkt interaktiv) | `x`,`y`, Z = `hoehe` | `omniflo_tests.json`, `omniflo_strecke_tests.json` | +| [Omniflo Gerade](#72-aluprofil-gerade) | `"type": "gerade"` + `profil` | `omni:make-gerade` | `x`,`y` -> `x_ende`,`y_ende`, Z = `hoehe` | `omniflo_strecke_tests.json` | +| [Waagrechte Foerderstrecke](#8-waagrechte-foerderstrecke-gerade---noch-ohne-bau-pfad) | `"typ": "gerade"` + `startpunkt` | **kein Bau-Pfad** (wird gemeldet und uebersprungen) | `startpunkt`, `endpunkt` | `dematic_arvato.json` | + +Nicht aus JSON baubar (es gibt keine Testdaten und keinen Treiber): Eckrad, +Geruest, TEF-Foerderer-Linie, TRO-Marker, Connections, Omniflo-Ladeschleife. +Fuer Eckrad und Geruest existiert ein skriptfaehiger Kern, siehe +[lisp_methods.md](lisp_methods.md). + +### Allgemeine Regeln + +- **Einheiten**: Laengen und Koordinaten in **mm**, Winkel in **Grad**. + Fahrtrichtung `hz`: 0 = +X (Ost), 90 = +Y (Nord), 180 = -X, 270 = -Y. +- **Z ist absolut**: `z` (bzw. Z in `start_mm`/`start_punkt`) ist die + Weltkoordinate des Einfuegepunkts, also die Einbauhoehe. +- **Dimension 2D/3D**: Gebaut wird in der aktuellen Dimension + (`ssg-ils-dim-aktuell`: `*ssg-ils-dim*` -> `DXFM_DIM`, Default 3D). + Das Feld `darstellung` in `dematic_arvato.json` ist reine Dokumentation und + wird nicht ausgewertet; `TEST_HUNDM05_2D`/`_3D` erzwingen die Dimension. +- **Flaches JSON**: Die meisten Dateien werden mit `ssg-load-json` gelesen + (zeilenweise, ein Objekt je `{`). Verschachtelte Objekte und mehrzeilige + Arrays darin funktionieren nicht - darum stehen z.B. die Glieder einer + Kette als eigene Objekte hinter ihrem Kopf-Objekt. Ausnahme: + `TEST_DEMATIC_ARVATO` liest mit dem rekursiven `ssg-json-laden`. +- **Reine Doku-Felder**: `comment`, `beschreibung`, `description`, `quelle`, + `row`, `seq`, `gruppe`, `assigned_to`, `hoehe_unbekannt`, `darstellung` + sowie alle `expect_*`/`erwartung_*`-Felder werden beim Bau nicht gelesen. + `expect_*` pruefen die Python-Tests gegen das Ergebnis. +- **Template-Felder** (nur `test_mubea.lsp`): `anzahl`, `delta_x`, `delta_y` + erzeugen aus einem Eintrag `anzahl` Kopien, jeweils um `delta_x`/`delta_y` + versetzt; `test_id` bekommt dann das Suffix `_01` ... `_nn`. Gilt fuer VF, + GF und Einzelbloecke. + +--- + +## 1. Kreisel + +Block `KREISEL_n`, gebaut von `KreiselInsert.lsp`. + +### 1.1 Insert (freier Punkt) + +```json +{ "id": "KR_Kreuz_Q1_0Grad", "function": "insert", + "x": 500, "y": 500, "z": 2500, + "abstand": 2000, "rotation": 0.0, "typ": "STANDARD" } +``` + +| Feld | Pflicht | Bedeutung | Default | +|---|---|---|---| +| `id` | ja (fuer den Treiber) | Kennung im Ergebnis; NICHT die Block-ID (die vergibt `ssg-id-generate`) | - | +| `function` | ja | `"insert"`; ohne `block` = Kreisel | - | +| `x`, `y` | ja | Einfuegepunkt. Liegt auf der Kreiselachse **einen Radius (400 mm) vor der Mitte des Antriebsrads (AN)**, also am aeussersten Punkt der AN-Seite | - | +| `z` | ja | Einbauhoehe; wird als `hoehe` uebergeben und in das Attribut `HOEHE` geschrieben | `kreisel.default_hoehe` (2000) | +| `abstand` | ja | Mittenabstand Antriebsrad - Spannrad | `kreisel.default_laenge` (2300) | +| `rotation` | nein | Drehung der Achse AN -> SP, frei (kein Raster) | 0.0 | +| `typ` | nein | `"STANDARD"` oder `"PIN"` -> Attribut `KREISELART` | `"STANDARD"` | +| `drehrichtung` | nein | `"GUZ"` oder `"UZS"` -> Attribut `DREHRICHTUNG` | `"UZS"` | + +Treiber: `kreisel:run-insert-test` (`test_kreisel.lsp`), `mubea:build-kreisel`, +`hundm05:bau-kreisel` (auch fuer `dematic_arvato.json`/`showroom_ils.json`). +Nur `hundm05:bau-kreisel` liest `drehrichtung` aus und reicht sie an +`kreisel-insert-script` weiter; `mubea:build-kreisel` setzt es (noch) nicht um. + +### 1.2 Connect (Start- und Endpunkt) + +```json +{ "id": "K2", "function": "connect", + "start_x": 0, "start_y": 0, "start_z": 2000, + "end_x": 5000, "end_y": 0, "end_z": 2000, "typ": "STANDARD" } +``` + +`start_*` und `end_*` sind die aeussersten Punkte der AN- bzw. SP-Seite. +`abstand` (= Distanz - Kreiseldurchmesser 800) und `rotation` werden daraus +berechnet. Die Einbauhoehe kommt aus `start_z`. Nur `test_kreisel.lsp` kennt +`connect`; die anderen Treiber bauen ausschliesslich `insert`. + +--- + +## 2. Einzelbloecke (Separator, Scanner, BTMT, Eingang/Ausgang, Vario-Bausteine) + +Jeder Eintrag mit `"function": "insert"` UND `"block"` wird als einzelner +Block eingefuegt, ohne Wrapper-Block und ohne Verkettung. + +```json +{ "id": "HundM05_SCANNER_02", "function": "insert", "block": "Scanner", + "x": 4095.5, "y": 1164.1, "z": 2154, "rotation": 270 } +``` + +| Feld | Pflicht | Bedeutung | Default | +|---|---|---|---| +| `block` | ja | Blockname OHNE `_2D`/`_3D`-Suffix | - | +| `x`, `y`, `z` | ja | Einfuegepunkt = **Basispunkt der Block-DWG** (nicht KS_EIN) | - | +| `rotation` | nein | Drehung um Z | 0 | +| `id` / `test_id` | nein | Kennung im Ergebnis | `SEPARATOR_` bzw. Blockname | + +Ablauf: `ssg-ils-block-laden` loest den Namen auf `data/ils/_.dwg` +auf (3D-Fallback), danach `_.INSERT eff pt "" "" rotation` und +`ssg-id-generate`. **Fehlt die DWG, wird `Separator_SP` eingefuegt.** +Treiber: `mubea:build-separator`, `hundm05:bau-sensor` (auch Dematic). +Das Layer-Attribut `LAYER` wird dabei nicht angewendet; der Block landet auf +dem aktuellen Layer. + +### In den Testdaten verwendete Blocknamen + +| `block` | DWG in `data/ils` | Verwendet in | Hinweis | +|---|---|---|---| +| `Scanner` | `Scanner_2D/_3D.dwg` | mubea, hundm05, dematic_arvato | | +| `Separator_SP` | `Separator_SP_2D/_3D.dwg` | (Fallback) | Block von `ILS_SEPARATOR_INSERT` | +| `S-LP` | **fehlt** | mubea, hundm05, dematic_arvato | wird zu `Separator_SP` | +| `S-SP` | **fehlt** | dematic_arvato | wird zu `Separator_SP` | +| `BTMT-Beladung` | `BTMT-Beladung_2D/_3D.dwg` | mubea, hundm05 | wie `ILS_BTMT_Beladung` | +| `SC_Entladung` | `SC_Entladung_2D/_3D.dwg` | mubea, hundm05 | wie `ILS_BTMT_Entladung` | +| `Eingang` / `Ausgang` | `Eingang_*/Ausgang_*.dwg` | mubea | Symbole wie `ILS_EINGANG_INSERT`/`ILS_AUSGANG_INSERT` | +| `AS_Element_90_links` / `_rechts` | vorhanden | dematic_arvato | loses AS-Element (sonst Teil eines VF/GF) | +| `ES_Element_90_rechts`, `ES_Element_30_rechts` | vorhanden | dematic_arvato | loses ES-Element | +| `Vario_Motorstation_500mm_rechts` | vorhanden | dematic_arvato | lose Motorstation | +| `Vario_Umlenkstation_500mm_rechts` | vorhanden | dematic_arvato | lose Umlenkstation | +| `Vario_Kurve_rechts_30_TEF_aussen` | vorhanden | dematic_arvato | lose Vario-Kurve | + +Grundsaetzlich ist **jeder** Blockname aus `data/ils` erlaubt (es gibt keine +Whitelist). Die losen AS/ES-/Vario-Bausteine bilden aber keine Kette und keinen +`VF_n`/`GF_n`-Block - fuer eine zusammenhaengende Strecke Abschnitt 3-5 nutzen. + +--- + +## 3. VarioFoerderer Standard und Etage + +Block `VF_n`. Eingang sind die **Gesamtmasse** der Anlage; Winkel und +Teillaengen berechnet der Solver. + +```json +{ "test_id": "VF_Schnitt_C", "richtung": "Auf", + "deltaL": 9110, "deltaH": 474, "winkel": 21, + "gf_verteilung": "manuell", "L_GF1": 2298, "seite": "links", + "hz": 0.0, "x": 400, "y": 14120, "z": 1554 } +``` + +| Feld | Pflicht | Bedeutung | Default | +|---|---|---|---| +| `test_id` | ja | muss mit `VF_` beginnen (steuert den Dispatcher in `test_mubea.lsp`) | - | +| `typ` | nein | `"standard"` (AS/ES 90 Grad) oder `"etage"` (AS/ES 30 Grad, uebereinander) | `"standard"` | +| `richtung` | ja | `"Auf"` oder `"Ab"` | - | +| `deltaL` | ja | horizontale Gesamtlaenge inkl. AS/ES, Boegen, GF-Vor-/Nachlauf | - | +| `deltaH` | ja | Hoehendifferenz (Betrag; Vorzeichen kommt aus `richtung`) | - | +| `winkel` | nein | Bogenwinkel erzwingen, nur aus der Kandidatenliste (`vario.bogen_winkel`: 3, 6, 9, 12, 15, 18, 21, 27, 33, 39, 45, 51). Ist er nicht loesbar, wird NICHT gebaut | kleinster gueltiger Winkel | +| `L_GF1` | nein (mubea) | Vorlauf-Laenge (GF vor dem Foerderer); Nachlauf = Rest von `L_GF` | - | +| `gf_verteilung` | nein (foerderer) | `"gleichmaessig"`, `"vorne"`, `"hinten"`; in `mubea.json` nur Doku (dort zaehlt `L_GF1`) | `"gleichmaessig"` | +| `seite` | nein | `"links"`/`"rechts"` (Seite der AS/ES-Elemente) | `"links"` | +| `hz` | nein | Fahrtrichtung | 0.0 | +| `x`, `y`, `z` | ja (mubea) | Startpunkt = **KS_EIN des AS-Elements**; `z` = Einbauhoehe am Anfang | - | + +Hinweise: +- `foerderer_tests.json` hat statt `x/y/z` einen Kopf-Eintrag + `{"y_abstand": 600}`: alle Foerderer starten bei x = 0, z = 0 und werden um + `y_abstand` versetzt. Dieses Format kennt nur `test_foerderer.lsp`. +- `"Ab"` mit `deltaH` < 1 ist geometrisch unmoeglich und wird abgewiesen. +- Der Treiber schreibt nach dem Bau `vf-edit-xdata-schreiben` (Typ + `hz`), + damit der Block per Doppelklick editierbar ist. + +--- + +## 4. Gefaellestrecke + +Block `GF_n`: AS-Element -> Staustrecke (skaliert) -> Separator -> ES-Element. +Eingang ist die **horizontale Laenge**, nicht die Hoehendifferenz - die ergibt +sich aus Laenge und Neigung. + +### 4.1 Modus 1 - Werteingabe + +```json +{ "test_id": "GF_Mubea", "modus": 1, + "deltaL_mm": 9110, "hz_grad": 180.0, "winkel_grad": 2.27, + "start_mm": [9510, 12300, 2028], + "as_seite": "links", "es_seite": "links", + "as_winkel": "90", "es_winkel": "90" } +``` + +| Feld | Pflicht | Bedeutung | Default | +|---|---|---|---| +| `test_id` | ja | in `mubea.json` muss sie mit `GF_` beginnen | - | +| `modus` | nein | `1` | - | +| `deltaL_mm` | ja | horizontale Gesamtlaenge AS + Staustrecke + Separator + ES | - | +| `winkel_grad` | nein | Neigung der Staustrecke | 3.0 | +| `hz_grad` | nein | Fahrtrichtung | 0.0 | +| `start_mm` | ja | `[x, y, z]` = **KS_EIN des AS-Elements**; Z = Einbauhoehe oben | - | +| `as_seite`, `es_seite` | nein | `"links"`/`"rechts"` | `"links"` | +| `as_winkel`, `es_winkel` | nein | `"90"` oder `"30"` (als STRING) | `"90"` | + +Die Staustrecke ergibt sich aus +`L_stau = (deltaL - aus-dx - ein-dx - 300 * cos(w)) / cos(w)` +(300 = `gefaelle.separator_breite`). Eine Ziel-Hoehe gibt es nicht. + +In `gefaellestrecke_tests.json` stehen `deltaL_mm`, `winkel_grad` und +`z_start_mm` in einem gemeinsamen Kopf-Eintrag ohne `test_id` und gelten fuer +alle Faelle der Datei. + +### 4.2 Modus 2 - Richtung aus zwei Punkten + +Wie Modus 1, aber `deltaL` und `hz` werden aus `linie_start_mm` -> +`linie_ende_mm` berechnet (nur X/Y). `start_mm` bleibt der Einfuegepunkt und +ist unabhaengig von der Linie. + +```json +{ "test_id": "M2_hz000", "modus": 2, "start_mm": [0, -35000, 5000], + "linie_start_mm": [0, -35000, 0], "linie_ende_mm": [10000, -35000, 0], + "as_seite": "links", "es_seite": "links", "as_winkel": "90", "es_winkel": "90" } +``` + +### 4.3 Modus 3 - Linienzug + +In der Produktion nicht skriptbar (`gf-linienzug-modus` waehlt vorgezeichnete +LINE/ARC-Objekte per Maus). `test_gefaellestrecke.lsp` zeichnet die Leitlinie +selbst und baut sie nicht-interaktiv nach. Zwei Formen: + +```json +{ "test_id": "M3_Bogen", "modus": 3, + "start_mm": [30000, -50000, 2000], "ende_mm": [38500, -43500, 1800], + "bogen": {"winkel_grad": 90, "radius_mm": 500, "seite": "links"}, + "as_seite": "links", "es_seite": "links", "as_winkel": "90", "es_winkel": "90" } +{ "test_id": "M3_hz270", "modus": 3, "richtung": "Sued", + "start_mm": [0, -50000, 5000], + "deltaH_quelle": "M2_hz000", "deltaL_quelle": "M2_hz000", ... } +``` + +`deltaH_quelle`/`deltaL_quelle` uebernehmen die Masse eines frueher gebauten +Falls. Dieses Format ist an den Treiber gebunden. Fuer neue Anlagen besser +eine VF-Linienzug-Spec (Abschnitt 5) mit `Linie-GF`/`GF-Bogen` verwenden. + +--- + +## 5. VF-Linienzug-Kette (Spec) + +Beliebige Ketten aus GF-Linien, GF-Boegen, Vario-Kurven und VF-Einheiten, +**ohne Dialog** gebaut (`VF_SPEC_BAU`, `TEST_HUNDM05`). Eine Kette = ein +Kopf-Objekt, danach ihre Glieder (`glied`) und deren Unterknoten (`sub`) in +Baureihenfolge. Ergebnis ist ein `VF_n`-Block mit vollem `SSG_VF_EDIT`-Journal +(per Doppelklick editierbar). + +```json +{ "spec_id": "VF_spec_LZ_01", "modus": 1, + "start_punkt": [4912.2, 1230.9, 2154.0], "start_hoehe": 2154.0, + "as": 1, "as_winkel": "90", "as_seite": "links" } +{ "glied": "Linie-GF", "nr": 1, "dl": 2249.3, "hz": 90.0, + "gefaelle": "winkel", "winkel": 3.0, "ende": "nein" } +{ "glied": "Horizontal-VF", "nr": 2, "dl": 3256.9, "gf_verteilung": "einlauf", + "vf_sep_vor": 0, "vf_sep_nach": 0, "vf_ende": "nein", "vf_separator": 0 } +{ "sub": "vario-kurve", "winkel": 90, "seite": "rechts", "variante": "innen" } +{ "sub": "motorstation" } +{ "glied": "ES", "winkel": "90", "seite": "links" } +``` + +**Kopf** + +| Feld | Bedeutung | +|---|---| +| `spec_id` | Kennung der Kette | +| `modus` | `1` (nur Modus 1 hat einen Uebersetzer) | +| `start_punkt` | `[x, y, z]` Kettenanfang (KS_EIN des AS-Elements bzw. erstes Glied) | +| `start_hoehe` | Einbauhoehe am Start | +| `as` | `1` = AS-Element setzen, `0` = nicht | +| `as_winkel` / `as_seite` | `"90"`/`"30"` bzw. `"links"`/`"rechts"` | + +**Glieder** (`glied`) + +| `glied` | wichtigste Felder | +|---|---| +| `Linie-GF` | `dl`, `hz` (nur beim ersten Glied der Kette), `laenge_modus` (`endpunkt`/`hoehe`/`steigend`), `ende` (`ja-mit-es`/`ja-ohne-es`/`nein`), `sep_vor`, `sep_nach`, `gefaelle` (`hoehe`/`winkel`) + `hoehe` bzw. `winkel` | +| `GF-Bogen` | `winkel` (30/60/90), `seite` | +| `Vario-Kurve` | als eigenes Glied (Kettenanfang) vom Uebersetzer noch NICHT unterstuetzt -> Spec-Fehler; Vario-Kurven als `sub` einer VF-Einheit angeben | +| `Horizontal-VF` | `dl`, `eingang_hoehe`, `gf_verteilung` (`haelfte`/`einlauf`), `vf_sep_vor`, `vf_sep_nach`, `vf_ende` (`ja-mit-es`/`nein`/`automatisch`/`zielpunkt-ohne-es`), `vf_separator` (nur bei `vf_ende` = `nein`) + `sub`-Knoten | +| `Linie-VF` | `dl`, `hoehe`, optional `winkel_idx` + `sub`-Knoten | +| `Linie` | `dl`, `hoehe`, `segment_typ` (`GF`/`VF`), optional `winkel_idx` | +| `ES` | `winkel` (`"90"`/`"30"`), `seite` | + +**Unterknoten einer VF-Einheit** (`sub`): `horizontal` (`sep_vor`, `sep_nach`), +`vario-kurve` (`winkel`, `seite`, `variante` `aussen`/`innen`), `auf-ab` +(`richtung` `auf`/`ab`, `hoehe`), `motorstation`, `kettenende` (`es`, +`es_winkel`, `es_seite`). + +Ja/Nein-Felder (`as`, `sep_vor`, `vf_sep_vor`, `es`, ...) als `1`/`0` oder +`"ja"`/`"nein"`; mehrwertige Felder (`ende`, `vf_ende`) nur mit den genannten +Woertern. Vollstaendige Regeln +(Menue-Codes je Frame, `hz` nur einmal, Verhalten bei `winkel_idx`): +[variofoerderer.md, Abschnitt 8b](variofoerderer.md#spec-pfad-kette-aus-eingabedaten-vf_spec_bau). +Die Spec wird nicht von Hand geschrieben, sondern mit +`python lib/vf_spec_export.py ` aus einer +aufgezeichneten Kette erzeugt. Fehlerhafte Specs werden komplett abgewiesen +(`vsp-pruefen` sammelt alle Fehler, gebaut wird dann nichts). + +**Andere Objekte in derselben Datei**: Neben den Ketten darf `hundm05.json` +Kreisel (Abschnitt 1) und Einzelbloecke (Abschnitt 2) enthalten. Erkennbar +sind sie an `function`; `vsp-gruppieren` ueberspringt sie, und +`hundm05:bau-zusatzobjekte` baut sie nach den Ketten. `vf_spec_export.py` +haengt sie beim Neuerzeugen unveraendert ans Dateiende. + +--- + +## 6. VF-Linienzug (Eingabe-Journal) + +Das Rohformat hinter der Spec: die Antworten auf die Fragen des interaktiven +Linienzug-Ablaufs, in Reihenfolge. Nur fuer Regressionstests gedacht - fuer +neue Anlagen die Spec (Abschnitt 5) verwenden. + +```json +{ "test_id": "VF_hundm05_LZ_01", "anlagetyp": "linienzug", "modus": 1, ... } +{ "typ": "point_abs", "wert": [4912.2, 1230.9, 2154.0], "kommentar": "Startpunkt" } +{ "typ": "real", "wert": 2154.0, "kommentar": "Hoehe (Z)" } +{ "typ": "string", "wert": "1", "kommentar": "AS-Element? Ja" } +{ "typ": "step", "wert": "Linie-GF", "kommentar": "Glied-Marker" } +{ "typ": "point_rel", "dL": 2249.3, "hz": 90.0, "kommentar": "Endpunkt" } +{ "typ": "int", "wert": 90, "kommentar": "Vario-Kurve Winkel" } +``` + +`typ`: `point_abs` (absoluter Punkt), `point_rel` (Abstand `dL` + Richtung +`hz`, `hz` nur beim ersten Segment), `real`, `int`, `string` (Menue-Code als +String), `step` (Glied-Marker, beim Replay uebersprungen). Kopf-Feld heisst +`anlagetyp`, nicht `typ`, damit Kopf und Eingabe unterscheidbar bleiben. + +| Datei | Treiber | +|---|---| +| `linienzug_tests.json`, `linienzug_tests_stufe2.json` | `TEST_LINIENZUG`, `TEST_LINIENZUG_STUFE2` | +| `hm_recformat.json` (5 echte HundM-Ketten, erzeugt mit `lib/vf_journal_export.py`) | `TEST_HM_RECFORMAT` (in `alltests.json` abgeschaltet) | + +--- + +## 7. Omniflo + +### 7.1 Bogen und Weiche + +```json +{ "id": "821104025", "type": "bogen", "sivasnr": "821104025", + "x": 219.4, "y": 223.06, "hoehe": 2000, "drehung": 0.0 } +``` + +| Feld | Pflicht | Bedeutung | Default | +|---|---|---|---| +| `type` | ja | `"bogen"` oder `"weiche"` | - | +| `sivasnr` | ja | Sivas-Nummer = Dateiname `%DXFM_OMNIFLO%/.dxf` | - | +| `x`, `y` | ja | Einfuegepunkt (`$INSBASE` der DXF, gesetzt mit `set_einfuegepkt.py`) | - | +| `hoehe` | nein | Z des Einfuegepunkts UND Attribut `HOEHE` | 2000 | +| `drehung` | nein | Drehung + Attribut `DREHUNG` | 0 | + +Es gibt kein `z`: die Hoehe ist zugleich die Z-Koordinate. Gueltige +Sivas-Nummern: `data/json/omniflo_boegen.json`, `omniflo_weichen.json`. +Treiber: `omni:test-insert` (`test_omniflo.lsp`), `omnistr:insert-bogen` +(`test_omniflo_strecke.lsp`). + +### 7.2 Aluprofil-Gerade + +```json +{ "seq": 1, "type": "gerade", "profil": "AP110", "laenge": 1000.0, + "x": 2000.0, "y": 0.0, "x_ende": 3000.0, "y_ende": 0.0, + "drehung": 0.0, "hoehe": 2000, "id": "0003" } +``` + +`profil` = `"AP60"`, `"AP110"` oder `"APG110"`. Die Gerade laeuft von +(`x`,`y`) nach (`x_ende`,`y_ende`) auf Z = `hoehe`; Laenge und Drehung folgen +aus den Punkten (`laenge` ist nur Doku). Gebaut mit `omni:make-gerade`. + +--- + +## 8. Waagrechte Foerderstrecke (`gerade`) - noch ohne Bau-Pfad + +```json +{ "test_id": "GERADE_Arvato_01", "typ": "gerade", "laenge": 8313.39, "hz": 90.0, + "startpunkt": [2334.97, 177818.67, 0.0], "endpunkt": [2334.96, 186132.06, 0.0] } +``` + +Ein Antriebs-/Umlenkstation-Paar ohne Hoehendifferenz, so von +`lib/dxf_scan_components.py` ausgegeben (ein VarioFoerderer ist bei +`deltaH` = 0 nicht loesbar). `TEST_DEMATIC_ARVATO` meldet diese Eintraege und +ueberspringt sie. Passende Primitive waere `insert-hz-scaled-block-by-ks` +(`vf_etage.lsp`) mit `Staustrecke_SP_1000_mm`, siehe +[lisp_methods.md](lisp_methods.md). + +--- + +## 9. Testdaten-Dateien im Ueberblick + +| Datei | Inhalt | Treiber / Befehl | +|---|---|---| +| `kreisel_tests.json` | 8 Kreisel (`insert`) | `TEST_KREISEL` | +| `foerderer_tests.json` | Kopf `y_abstand` + 17 VF Standard | `TEST_FOERDERER` | +| `gefaellestrecke_tests.json` | Kopf + GF Modus 1/2/3 | `TEST_GEFAELLESTRECKE` | +| `omniflo_tests.json` | 27 Boegen, 80 Weichen | `TEST_OMNIFLO` | +| `omniflo_strecke_tests.json` | Streckenzug aus Geraden und Boegen | `TEST_OMNIFLO_STRECKE` | +| `linienzug_tests.json` / `_stufe2.json` | Eingabe-Journal je einer Kette | `TEST_LINIENZUG` / `TEST_LINIENZUG_STUFE2` | +| `hm_recformat.json` | 5 HundM-Ketten als Journal | `TEST_HM_RECFORMAT` | +| `hundm05.json` | 5 HundM-Ketten als Spec + Kreisel, Separatoren, Scanner, BTMT | `TEST_HUNDM05`, `VF_SPEC_BAU` | +| `mubea.json` | Gesamtanlage: Kreisel, VF Standard/Etage, GF-Template (20 Stueck), Separatoren, Scanner, BTMT, Eingang/Ausgang | `TEST_MUBEA` | +| `dematic_arvato.json` | Vorlage aus `dxf_scan_components.py`: Kreisel, Einzelbloecke, `gerade` (Hoehen fehlen) | `TEST_DEMATIC_ARVATO` (abgeschaltet) | +| `showroom_ils.json` | ILS-3.0-Showroom-Vorlage aus `dxf_scan_components.py` (Kreisel, Gefaellestrecken; Hoehen teils Platzhalter), gleicher Treiber wie `dematic_arvato.json` | `TEST_SHOWROOM_ILS` | +| `hundm05_linienzug_polylines.json` | Polylinien aus `extract_polylines.py` | kein Treiber (Analyse-Daten) | +| `vfl_journal_hm05_abbruch.json` | rohes Journal (`kind`/`value`) eines abgebrochenen Baus | kein Treiber | + +--- + +## 10. Eigene Anlage beschreiben - Kurzanleitung + +1. Kreisel als `insert` (Abschnitt 1): `x/y` am aeussersten Punkt der AN-Seite, + `z` = Einbauhoehe. +2. Einfache Steig-/Fall-Strecken mit fester Form als VF Standard/Etage + (Abschnitt 3) bzw. Gefaellestrecke Modus 1 (Abschnitt 4.1), Startpunkt + jeweils = KS_EIN des AS-Elements. +3. Ketten mit Kurven, mehreren VF-Einheiten oder GF-Boegen als Spec + (Abschnitt 5) - am einfachsten eine Kette einmal interaktiv bauen, mit + `lib/vf_journal_export.py` + `lib/vf_spec_export.py` als Spec exportieren + und Startpunkt/Laengen anpassen. +4. Separatoren, Scanner, BTMT-Stationen, Eingang/Ausgang als Einzelblock + (Abschnitt 2). +5. Aus Kreisel, Einzelbloecken und Specs gemischt baut derzeit nur der + HundM05-Treiber (`TEST_HUNDM05`); fuer VF Standard/Etage + GF zusammen mit + Kreiseln den Mubea-Treiber (`TEST_MUBEA`) als Vorlage nehmen. diff --git a/doc/kommandos.md b/doc/kommandos.md new file mode 100644 index 0000000..b903441 --- /dev/null +++ b/doc/kommandos.md @@ -0,0 +1,304 @@ +# Kommandouebersicht + +Diese Seite listet alle Kommandos des Projekts auf einen Blick: + +1. [BricsCAD-Befehle (AutoLISP)](#1-bricscad-befehle-autolisp) - an der BricsCAD-Kommandozeile einzugeben +2. [Test-Befehle in BricsCAD](#2-test-befehle-in-bricscad) +3. [Batch-/Shell-Skripte (`bin/`)](#3-batch-shell-skripte-bin) +4. [Python-Kommandozeilen (`lib/`)](#4-python-kommandozeilen-lib) +5. [Python-Tests (`pytest`)](#5-python-tests-pytest) + +Ausfuehrliche Beschreibungen stehen in `Lisp/README.md`, `doc/variofoerderer.md`, +`doc/tools.md` (set_einfuegepunkt/set_koords), `doc/dbg2lsp.md`, `doc/csv-export.md` +und `doc/LispTests.md`. Diese Seite ist nur der Einstieg. + +**Voraussetzung fuer alle BricsCAD-Befehle**: Menue `menu/SSG_LIB.mnu` einmalig per +`_MENULOAD` geladen (siehe `CLAUDE.md`, "Menue einrichten"). Die MNL laedt die Module +dann beim Start. Befehle, die nicht im Menue stehen, lassen sich trotzdem an der +Kommandozeile aufrufen, sofern ihr Modul geladen ist - sonst vorher +`(ssg-ensure "Modulname")` eingeben. + +--- + +## 1. BricsCAD-Befehle (AutoLISP) + +Gross-/Kleinschreibung spielt an der Kommandozeile keine Rolle. + +### ILS Kreisel / Eckrad (`KreiselInsert.lsp`) + +| Befehl | Beschreibung | +|---|---| +| `KreiselInsert` | Kreisel einfuegen: Position und Parameter (Abstand, Typ, Rotation, Hoehe) manuell eingeben | +| `KreiselConnect` | Kreisel zwischen zwei gewaehlten Punkten einfuegen (Abstand und Drehung aus den Punkten) | +| `KreiselQuick` | Kreisel schnell mit Standardwerten (horizontal) einfuegen | +| `KreiselEdit` | Bestehenden Kreisel per Dialog (`kreisel_edit.dcl`) bearbeiten und neu aufbauen | +| `KreiselRedraw` | Kreisel aus seinen Block-Attributen neu zeichnen (z.B. mit neuem Abstand) | +| `KreiselParams` | Aktuelle Kreisel-Parameter anzeigen | +| `KreiselLabelSetup` | Beschriftung einstellen (Hoehe, Farbe, Abstand, Schriftart) | +| `KreiselLabelPos` | Versatz der Beschriftung (dx/dy) einstellen | +| `KreiselLabelHoehe` | Texthoehe der Beschriftung einstellen | +| `ILS_Eckrad` | Eckrad an einem Tangentenpunkt mit Richtung einfuegen | + +### VarioFoerderer (`VarioFoerderer.lsp` -> `vf_core`/`vf_standard`/`vf_etage`/`vf_linienzug`/`vf_spec`) + +| Befehl | Beschreibung | +|---|---| +| `FOERDERANLAGE` / `VarioFoerderer` | Hauptbefehl, fragt den Anlagentyp (Standard/Etage/Linienzug) ab | +| `StandardVarioFoerderer` | Standardtyp (AS/ES 90 Grad), Werte per DCL-Dialog | +| `StandardVarioFoerderer3D` | Standardtyp aus einer gewaehlten 3D-Linie | +| `EtageVarioFoerderer` | Etagentyp (AS/ES 30 Grad), Werte per DCL-Dialog | +| `EtageVarioFoerderer3D` | Etagentyp aus einer gewaehlten 3D-Linie | +| `LinienzugVarioFoerdererManuell` | Streckengruppe, Modus 1 (manuelle Eingabe, Punkte klicken) | +| `LinienzugVarioFoerdererZielhoehe` | Streckengruppe, Modus 2 (3D-Objekte + Ziel-Hoehe, Randwert-Solver) | +| `LinienzugVarioFoerdererStueckweise` | Streckengruppe, Modus 3 (3D-Objekte stueckweise, in Arbeit) | +| `QcLinieMitGf` | Streckengruppe Modus 4 (QC-Linie mit GF, automatische Auf-Ab-Berechnung); Menue unter Gefaellestrecke | +| `QcLinieMitGfUForm` | Streckengruppe Modus 5 (QC-Linie mit GF und U-Form); Menue unter Gefaellestrecke | +| `VARIOFOERDERER_EDIT` | VF_n-Block editieren (dispatcht per XDATA-Marker auf Standard/Etage/Linienzug); auch per Doppelklick | +| `VARIOFOERDERER_NACHRUESTEN` / `VF_NACHRUESTEN` | Altbestand-VF_n-Bloecke ohne `SSG_VF_EDIT`-Marker editierbar machen | +| `Vario_Kette_Merge` | Mehrere einzeln gebaute VF_n-Bloecke ab einem Start-Baustein zu einem Gesamt-Block zusammenfuehren | +| `VF_SEKTION_RESTORE` | Aufgebrochene Linienzug-Kette aus der XDATA der losen Objekte wieder zu einem Block mit Journal zusammensetzen | +| `VF_WIZARD_AN` / `VF_WIZARD_AUS` | Linienzug-Fragen per DCL-Assistent (Standard) bzw. per Konsole stellen | +| `VF_SPEC_BAU` | Linienzug-Ketten ohne Dialog aus einer Spec-Datei bauen (`DXFM_VF_SPEC`, Default `tests/testdata/hundm05.json`) | +| `VFL_KS_DIAG` | Diagnose: inneren Aufbau eines Blocks anzeigen (Lage von KS_EIN/KS_AUS) | + +### Gefaellestrecke (`Gefaellestrecke.lsp`) + +| Befehl | Beschreibung | +|---|---| +| `GEFAELLESTRECKE` | Fragt den Eingabemodus per Konsole ab | +| `GEFAELLESTRECKE_EINFACH` | Modus 1: Werteingabe per DCL-Dialog | +| `GEFAELLESTRECKE_3DLINIE` | Modus 2: aus einer 3D-Linie | +| `GEFAELLESTRECKE_LINIENZUG` | Modus 3: aus einem Linienzug (LINE + ARC) | +| `GEFAELLESTRECKE_EDIT` | GF_n-Block editieren (Dialog vorbelegt, Neuaufbau); auch per Doppelklick | + +### ILS-Zusatzmodule, Sensoren, Ein-/Ausgang (`SSG_LIB_Commands.lsp`) + +| Befehl | Beschreibung | +|---|---| +| `ILS_BTMT_Beladung` | BTMT-Beladung wiederholt einfuegen (Drehung interaktiv) | +| `ILS_BTMT_Entladung` | BTMT-Entladung wiederholt einfuegen | +| `ILS_EINGANG_INSERT` / `ILS_AUSGANG_INSERT` | Eingang-/Ausgang-Symbol frei platzieren | +| `ILS_SCANNER_INSERT` | Scanner einfuegen | +| `ILS_SEPARATOR_INSERT` | Separator einfuegen | +| `TEF_FOERDERER_LINIE` (`TEFInsert.lsp`) | Linie zwischen zwei Punkten zeichnen und mit 1000-mm-TEF-Geraden belegen | + +### Omniflo (`OmniModulInsert.lsp`) + +| Befehl(e) | Beschreibung | +|---|---| +| `OMNI_LOAD` | Omniflo-Komponentendaten laden | +| `OMNI_INFO_BOGEN` / `OMNI_INFO_WEICHE` | Daten eines Bogens / einer Weiche anzeigen | +| `OMNI_APB60_90`, `OMNI_APB_630_90`, `OMNI_APB_630_45`, `OMNI_APB_550_675`, `OMNI_APB_550_225`, `OMNI_APB_650_180` | Boegen einfuegen (Dialog `omniflo_boegen.dcl`) | +| `OMNI_AP60`, `OMNI_AP110`, `OMNI_APG110` | Gerade Profile einfuegen | +| `OMNI_W90_Einfach`/`_Doppel`/`_Dreiwege` | Weichen 90 Grad (Dialog `omniflo_weichen.dcl`) | +| `OMNI_W45_Einfach`/`_Doppel`/`_Dreiwege` | Weichen 45 Grad | +| `OMNI_WP_Einfach`/`_Doppel`/`_Dreiwege` | Parallelweichen | +| `OMNI_WK_Einfach`/`_Doppel`/`_Dreiwege` | Weichenkoerper | +| `OMNI_WKomb_Delta_1400`, `OMNI_WKomb_Delta_1600`, `OMNI_WKomb_Star` | Weichenkombinationen (Delta, Stern) | +| `OMNI_APBW_R400_45_140`, `OMNI_APBW_R400_675_260`, `OMNI_APBW_R550_45_77`, `OMNI_APBW_R550_45_194`, `OMNI_APBW_R550_225_360`, `OMNI_APBW_R550_225_410A`/`B`/`C`, `OMNI_APBW_R550_675_58`/`_125`/`_208`/`_360`, `OMNI_APBW_R650_90_300`, `OMNI_APBW_R750_90_200` | Boegen fuer Weichen (fest, ohne Dialog) | +| `OMNI_TEF_UmlenkL`/`R`, `OMNI_TEF_AntriebL`/`R`, `OMNI_TEF_Gerade` | TEF-Elemente (fest, ohne Drehung) | +| `OMNI_STOPPER`, `OMNI_SCANNER`, `OMNI_BUERSTENBREMSE` | Anbaugruppen | +| `OMNI_TV_W_W`, `OMNI_TV_W_AP`, `OMNI_TV_W_AP60`, `OMNI_TV_W_AP110`, `OMNI_TV_W_AP110_W`, `OMNI_TV_50`, `OMNI_TV_80`, `OMNI_TV_ENDPLATTE`, `OMNI_TV_ANSCHLAG`, `OMNI_TV_MUFFE`, `OMNI_TV_A_AP110_AP60`, `OMNI_TV_A_AP60_ST`, `OMNI_TV_A_AP110_ST`, `OMNI_TV_SW_II_SW_SW`, `OMNI_TV_SW_III_AP110`, `OMNI_TV_SW_III_AP110_SW`, `OMNI_TV_SW_IV_AP60`, `OMNI_TV_SW_IV_ST` | Verbinder | +| `OMNI_LADESCHLEIFE_AP110` | Ladeschleife (Dialog `omniflo_ladeschleifen.dcl`) | +| `OMNI_KettenFoerderer` | Platzhalter (noch ohne Funktion) | +| `OMNI_EDIT` | Omniflo-Element per Dialog bearbeiten; auch per Doppelklick | + +### Geruest (`GeruestInsert.lsp`) + +| Befehl | Beschreibung | +|---|---| +| `GERUEST` | Geruest-Dialog (Bereich frei waehlbar) | +| `GERUEST_SAEULEN` / `GERUEST_ROHRABHAENGER` / `GERUEST_WANDBEFESTIGUNG` | Derselbe Dialog, im jeweiligen Bereich geoeffnet | +| `GERUEST_TRAEGER` | Rohr-Geruest-Traeger: Dialog (`geruest_traeger.dcl`), danach Start-/Endpunkt oder Laenge bis ESC/Enter | +| `GERUEST_TRAEGER_BESCHRIFTEN` | Alle Traeger der Zeichnung neu beschriften, verwaiste Beschriftungen entfernen | + +### TRO und Connections (`Tro_Edit.lsp`, `Connection_Edit.lsp`) + +| Befehl | Beschreibung | +|---|---| +| `TRO_INSERT` | TRO-Marker (`TRO_SYM_`) einfuegen und ID/TYPE setzen (Blockdefinition muss existieren) | +| `TRO_EDIT` | ID/TYPE eines TRO-Markers per Dialog aendern | +| `CONNECTION_INSERT` | Verbindungspfeil (Fluss) zwischen zwei Elementen zeichnen | +| `CONNECTION_EDIT` | Quelle/Ziel einer Verbindung aendern | + +### Export und IDs (`export.lsp`, `ssg_id.lsp`, `count_sep_scan.lsp`, `check_hoehen.lsp`) + +| Befehl | Beschreibung | +|---|---| +| `EXPORTSIVAS` | Sivas-Export-CSV mit Summierungszeilen (ruft `lib/export_sivas.py`) | +| `EXPORTCSV` | Einfache Item-Liste aller Bloecke inkl. Bounding-Box/Planquadrat (ruft `lib/export_csv.py`) | +| `CSV_EXPORT_KONFIG` | Export-Einstellungen per Dialog (u.a. gefrorene Layer ausschliessen) | +| `OMNI_UPDATE_ATTRIBS` | HOEHE und DREHUNG aller Omniflo-Elemente aus der Geometrie aktualisieren | +| `IDSCHECK` | Alle Bloecke auf doppelte IDs pruefen und korrigieren | +| `IDGENERATE` | Gewaehltem Block eine neue ID zuweisen | +| `ZAEHLE_SEP_SCAN` | Scanner/Separatoren je Kreisel/VF/GF zaehlen und in ANZAHL_SCANNER/ANZAHL_SEPARATOR schreiben (laeuft auch automatisch vor jedem Export) | +| `HOEHEN_CHECK` | Hoehen von Kreiseln/VF/GF messen und in HOEHE/HOEHE_VON_mm/HOEHE_BIS_mm/MONTAGEHOEHE_m schreiben | +| `CALLPYTHON` (`external_call.lsp`) | Test des LISP->Python-Aufrufs (`lib/testpycall.py`) | + +### Konfiguration, Planquadrate, Doppelklick + +| Befehl | Beschreibung | +|---|---| +| `SSG_DIM_SWITCH` | Globalen Modus 2D/3D umschalten (`DXFM_DIM`) | +| `SSG_DIM_ALL_2D` / `SSG_DIM_ALL_3D` | Alle VF_n-/GF_n-Bloecke der Zeichnung nach 2D bzw. 3D umbauen | +| `SSG_SPRACHE` | Sprache Deutsch/Englisch waehlen (`DXFM_LANG`) | +| `DRAW_PLANQUADRATE` | Planquadrat-Raster (Block `ZZ_PLANQUADRATE`) zeichnen bzw. neu aufbauen | +| `DELETE_PLANQUADRATE` | Planquadrat-Raster entfernen | +| `PLANQUADRATE_TOGGLE` | Raster an/aus (zeichnen, falls keins existiert, sonst entfernen) | +| `SSG_BLOCKEDIT` | Doppelklick-/Kontextmenue-Dispatcher: Kreisel, Omniflo, GF_*, VF_*, GR_*, TRO_* auf den passenden Edit-Befehl | +| `EATTEDIT` | Ueberschreibt den nativen Befehl, leitet an `SSG_BLOCKEDIT` weiter (Fallback `_.EATTEDIT`) | + +### Layer-Werkzeuge (`ssg_layer.lsp`) + +| Befehl | Beschreibung | +|---|---| +| `SSG-LayerSet` | Layer eines angeklickten Objekts aktiv setzen | +| `SSG-LayerInfo` | Layer eines Objekts anzeigen | +| `SSG-LayerMove` | Objekte auf einen anderen Layer verschieben | +| `SSG-LayerCopy` | Objekte auf einen anderen Layer kopieren | +| `SSG-LayerMoveType` | Alle Objekte eines Typs (Referenzobjekt) vom gleichen Layer auf einen neuen Layer verschieben | +| `SSG-LayerOff` / `SSG-LayerFreeze` | Layer per Anklicken aus-/einfrieren | +| `SSG-FreezeAll` | Alle Layer ausser dem aktiven einfrieren | +| `SSG-ThawAll` / `SSG-LayerOnAll` | Alle Layer auftauen / einschalten | +| `SSG-LayerDelete` | Alle Objekte eines Layers und den Layer loeschen (mit Sicherheitsabfrage) | + +### Block-Pflege und Diagnose (Entwickler) + +Arbeiten auf den Block-DWGs in `data/ils` (Voraussetzung `DXFM_DATA`). + +| Befehl | Beschreibung | +|---|---| +| `ILS_ATTR_SYNC` (`addattribute.lsp`) | ATTDEFs der DWGs mit `data/ils/allocations.json` abgleichen; Modus Ergaenzen/Ersetzen | +| `ILS_ATTR_REPLACE` | Direkt Modus "Ersetzen" (Sicherheitsfrage bleibt) | +| `ILS_ATTR_COLLECT` (`collect_attributes.lsp`) | Aktuelle ATTDEF-Werte aller DWGs nach `allocations_collected.json` schreiben | +| `ILS_BLOCK_LIST` (`list_blocks.lsp`) | Sub-Block-Definitionen aller DWGs nach `blocks_collected.json` schreiben (Kollisionen 2D/3D finden) | +| `ILS_BLOCK_PURGE` (`purge_blocks.lsp`) | Unbenutzte Bloecke/Layer in allen DWGs entfernen | +| `KS_PRUEFEN` (`ks_pruefung.lsp`) | KS_EIN/KS_AUS der 2D- gegen die 3D-Variante vergleichen, Ergebnis als CSV | +| `MenuInventory` (`menu-inventory.lsp`) | Menuegruppen, Plugins und Startdateien von BricsCAD auflisten | + +--- + +## 2. Test-Befehle in BricsCAD + +Aus dem Menue "Tests" bzw. der Kommandozeile; Details in `doc/LispTests.md`. + +| Befehl | Beschreibung | +|---|---| +| `SSG_RUN_ALL_TESTS` | Alle in `tests/alltests.json` aktivierten Szenarien ausfuehren (fragt den Modus ab) | +| `SSG_RUN_ALL_TESTS_EXPORT` | Dito, Automatik: speichern + CSV/Sivas-Export + schliessen | +| `SSG_RUN_ALL_TESTS_OFFEN` | Dito, Kontrolle: Zeichnungen bleiben offen, kein Export | +| `TEST_EXPORT_ALL` | Jede Zeichnung aus `tests/output/` oeffnen und exportieren | +| `TEST_UNIT` | File- und zeichnungsfreie Unit-Tests (`tests/test_unit.lsp`), Ergebnis `tests/output/unit_results.txt` | +| `TEST_VF_SPEC` | Unit-Test Spec-Uebersetzer (`vsp-journal`) | +| `TEST_VFL_SCHEMA` | Unit-Test Glied-Schema des Linienzugs | +| `TEST_KREISEL` | Kreisel-Szenario | +| `TEST_FOERDERER` | VarioFoerderer-Szenario | +| `TEST_GEFAELLESTRECKE` | Gefaellestrecke-Szenario | +| `TEST_LINIENZUG` / `TEST_LINIENZUG_STUFE2` | Linienzug-Szenarien | +| `TEST_OMNIFLO` | Alle Omniflo-Testbloecke einfuegen | +| `TEST_OMNIFLO_STRECKE` | Omniflo-Streckenzug aufbauen | +| `TEST_HM_RECFORMAT` (`_2D`/`_3D`) | Echte HundM-Aufzeichnungen per Journal-Replay nachbauen | +| `TEST_HUNDM05` (`_2D`/`_3D`) | HundM05-Anlage aus der Spec bauen | +| `TEST_MUBEA` | Mubea-Nachbau aus `tests/testdata/mubea.json` | +| `TEST_DEMATIC_ARVATO` | Dematic/Arvato-Nachbau (Flowpuffer/Kreisel) | +| `TEST_SHOWROOM_ILS` | ILS-3.0-Showroom-Nachbau (Kreisel, Gefaellestrecken; Hoehen noch Platzhalter) | + +--- + +## 3. Batch-/Shell-Skripte (`bin/`) + +Alle Skripte rufen zuerst `setenv.bat` (bzw. `setenv.sh`) auf. + +| Skript | Beschreibung | +|---|---| +| `setenv.bat` / `setenv.sh` | Umgebungsvariablen setzen (`DXFMAKRO`, `DXFM_LISP`, `DXFM_DATA`, `BRICSCAD` ...); `.sh` muss gesourct werden | +| `start_briscad.bat` | Umgebung setzen und BricsCAD starten | +| `get_cmd.bat` / `get_cmd.sh` | Shell mit gesetzter Umgebung oeffnen | +| `install_py.bat` / `install_py.sh` | `.venv` anlegen und `requirements.txt` installieren (nur wenn noch nicht vorhanden) | +| `activate_venv.bat` / `activate_venv.sh` | `.venv` aktivieren, Python-Version und Pakete anzeigen | +| `run_tests.bat` | Testausfuehrung, siehe unten | +| `create_tests.bat` | Kompletter ILS-Testlauf: Basis-DXF erzeugen, BricsCAD mit `ILS_test.scr`, Ergebnisse validieren | +| `set_einfuegepunkt.bat [schalter]` | Wrapper fuer `lib/set_einfuegepkt.py` (siehe `doc/tools.md`) | +| `set_coords.bat [schalter]` | Wrapper fuer `lib/set_koords.py` (siehe `doc/tools.md`) | +| `dbg2lsp.bat [argumente]` | Wrapper fuer `lib/dbg2lsp.py` (siehe `doc/dbg2lsp.md`) | +| `show_exportcsv.bat [csv] [flow] [mflow] [track]` | Export-CSV mit sps_skel als SVG darstellen; `flow` ohne VF/GF-Einzelbausteine, `mflow` Materialfluss-Graph, `track` Bahnzusammenhang (beide brauchen Graphviz) | +| `dxf_anlage_export.bat [--projekt NAME]` | Wrapper fuer `lib/dxf_anlage_export.py` | +| `dxf_scan_components.bat [--fenster X0 Y0 X1 Y1] [--projekt NAME]` | Wrapper fuer `lib/dxf_scan_components.py` | +| `dxf_vf_abbild.bat [--spec S] [--json J] [--referenz R] [--schreibe-referenz] [--praefix VF_,GF_]` | Wrapper fuer `lib/dxf_vf_abbild.py` | + +### `run_tests.bat` + +``` +bin\run_tests.bat --lisp BricsCAD headless (/b tests\test_unit.scr), TEST_UNIT, Auswertung unit_results.txt +bin\run_tests.bat --check [test_xyz.py] pytest-Validierung (alle oder eine Datei) +bin\run_tests.bat --diff_references [datei] CSVs tests\reference vs. tests\output vergleichen, bei Unterschieden meld +bin\run_tests.bat Hilfe +``` + +--- + +## 4. Python-Kommandozeilen (`lib/`) + +Aufruf mit aktivierter `.venv` (bzw. ueber `setenv`, damit `DXFM_*` gesetzt sind). +`python --help` zeigt bei den argparse-Skripten alle Optionen. + +### Export (normalerweise von `EXPORTSIVAS`/`EXPORTCSV` aufgerufen) + +| Aufruf | Beschreibung | +|---|---| +| `python export_sivas.py [--mit-bezeichnung]` | Sivas-CSV mit Summierungszeilen, zusaetzlich `_zusammengefasst.csv` | +| `python export_csv.py ` | Einfache Item-Liste (Planquadrat, Bounding-Box, Nachbarn, Merkmale) | +| `python export_graph.py []` | Materialfluss-Graph aus dem CSV-Export als `.json`, `.dot` und `.mmd` | + +Reine Bibliotheken ohne eigene Kommandozeile: `export_blockpatterns.py`, +`export_neighbors.py`, `export_planquadrat.py`, `merkmalstamm.py`, `sivas_mapping.py`, +`omniflo_utils.py`, `vf_linienzug_grammatik.py`. + +### Omniflo-DXF-Aufbereitung + +| Aufruf | Beschreibung | +|---|---| +| `python set_einfuegepkt.py [--boegen] [--weichen45] [--weichen90] [--weichenkoerper] [--weichen-parallel] [--delta] [--dreifachweiche] [--sternweiche] [--number SIVASNR] [--show-omniflo]` | `$INSBASE` der Omniflo-DXFs setzen | +| `python set_koords.py [--k1set] [--k2set] [--k3set] [--k4set] [--set-all] [--force] [--test] [--number SIVASNR] [--show-omniflo]` | KS-Bloecke K1-K4 in die Omniflo-DXFs setzen | +| `python split_block_library.py` | `data/block_libraries/ils_library.dxf` in einzelne DXFs je Block aufteilen | + +### VarioFoerderer-Journal und Spec + +| Aufruf | Beschreibung | +|---|---| +| `python vf_journal_export.py [--csv ] [--kein-startpunkt-fix]` | `SSG_VF_EDIT`-Journale der VF_n-Bloecke als Frage-Antwort-Protokoll (z.B. `tests/testdata/hm_recformat.json`) | +| `python vf_spec_export.py ` | Protokoll in eine Spec uebersetzen (z.B. `tests/testdata/hundm05.json`) | +| `python dxf_vf_abbild.py [--spec S] [--json J] [--referenz R] [--schreibe-referenz] [--praefix VF_,GF_]` | Attribut-/Bausteinabbild der VF_/GF_-Bloecke; `--referenz` vergleicht (Exit 1 bei Abweichung) | +| `python dxf_anlage_export.py [--projekt NAME]` | Mit unseren Makros GEBAUTE (und von Hand nachbearbeitete) Anlage aus der Zeichnung in Nachbau-Testdaten zurueckschreiben (Gegenstueck zu `dxf_scan_components.py`); erzeugt z.B. `tests/testdata/dematic_arvato.json` | +| `python dump_vf_wrapper_inhalt.py ` | Diagnose: Sub-INSERTs je VF_n/GF_n-Wrapper in Baureihenfolge | +| `python dump_vf_kette_inhalt.py []` | Diagnose: Glied-Folge der Spec gegen die tatsaechliche Separatorliste | + +### Fremdzeichnungen auswerten + +| Aufruf | Beschreibung | +|---|---| +| `python dxf_scan_components.py [--fenster X0 Y0 X1 Y1] [--projekt NAME]` | ILS-/Omniflo-Komponenten einer fremden Zeichnung als Nachbau-JSON erkennen | +| `python dxf_abbild.py [--fenster X0 Y0 X1 Y1] [--max-tiefe N] [--ohne-geometrie-container]` | Getreue Abbildung aller Bauteile (ohne Interpretation) als JSON | +| `python extract_polylines.py [--layer Z-6]` | LWPOLYLINEs eines Layers mit Stuetzpunkten und Laengen als JSON | + +### Entwickler-Tools + +| Aufruf | Beschreibung | +|---|---| +| `python dbg2lsp.py --method NAME [--recursive] [--max-depth N] [--search-dir D] [--exclude NAME] [--add-open DATEI METHODE] [--dbg-file F] [--dbg-envvar V] [--force] [--dry-run]` | LISP-Funktionen mit `ssg_dbg.lsp`-Aufrufen instrumentieren (siehe `doc/dbg2lsp.md`) | +| `python testpycall.py []` | Gegenstueck zu `CALLPYTHON` | + +--- + +## 5. Python-Tests (`pytest`) + +``` +pytest tests/ alle Python-Tests +pytest tests/test_vf_spec.py einzelne Datei +bin\run_tests.bat --check dasselbe ueber das Batch-Skript +``` + +Die Szenario-Tests (`test_kreisel.py`, `test_foerderer.py`, `test_hundm05.py` ...) +validieren die Ausgaben der zugehoerigen BricsCAD-Testbefehle in `tests/output/` +gegen `tests/reference/`; reine Python-Tests wie `test_vf_spec.py`, +`test_vf_journal_grammatik.py` oder `test_export_graph.py` laufen ohne BricsCAD. diff --git a/doc/lisp_methods.md b/doc/lisp_methods.md new file mode 100644 index 0000000..72c3163 --- /dev/null +++ b/doc/lisp_methods.md @@ -0,0 +1,329 @@ +# LISP-Routinen zum Bau einzelner Einheiten + +Uebersicht ueber die LISP-Funktionen, die eine Einheit (Kreisel, Eckrad, +VarioFoerderer, Gefaellestrecke, Omniflo-Element, Geruest, Einzelblock) +erzeugen, mit Signatur, Parametern, Rueckgabe und Voraussetzungen. + +Zu jedem Element steht der interaktive **Befehl** (was der Anwender aufruft) +und der **skriptfaehige Kern** (ohne `getpoint`/Dialog, aus Code oder JSON +aufrufbar). Das JSON-Format, mit dem die Testtreiber diese Kerne aufrufen, +steht in [json-input-lisp.md](json-input-lisp.md); alle Befehle in +[kommandos.md](kommandos.md). + +Einheiten: mm und Grad. `hz` = Fahrtrichtung in der XY-Ebene, 0 = +X, +90 = +Y. Punkte sind Listen `(x y z)` in Weltkoordinaten. + +--- + +## 0. Uebersicht + +| Einheit | Befehl | Skriptfaehiger Kern | Ergebnis-Block | Modul | +|---|---|---|---|---| +| Kreisel | `KreiselInsert`, `KreiselConnect`, `KreiselQuick` | `kreisel-insert-script`, `kreisel-connect-script` | `KREISEL_n` | `KreiselInsert.lsp` | +| Eckrad | `ILS_Eckrad` | `ils-eckrad-insert` | `ECKRAD_n` | `KreiselInsert.lsp` | +| VF Standard | `StandardVarioFoerderer`, `FOERDERANLAGE` | `berechne-standard` + `variofoerderer-einfuegen` + `vf-block-erstellen` | `VF_n` | `vf_standard.lsp`, `vf_core.lsp` | +| VF Etage | `EtageVarioFoerderer` | `berechne-etage` + `etage-foerderanlage-einfuegen` + `vf-block-erstellen` | `VF_n` | `vf_etage.lsp`, `vf_core.lsp` | +| VF Linienzug | `LinienzugVarioFoerdererManuell`/`-Zielhoehe`/`-Stueckweise`, `QcLinieMitGf`/`-UForm` | `vsp-bau-aus-spec` (Spec) bzw. `vfl-journal-replay-start` + `vf-linienzug-modus` | `VF_n` | `vf_linienzug.lsp`, `vf_spec.lsp` | +| Gefaellestrecke | `GEFAELLESTRECKE_EINFACH`/`_3DLINIE` | `gf-rebuild` bzw. `gefaellestrecke-einfuegen` | `GF_n` | `Gefaellestrecke.lsp` | +| Gefaellestrecke Linienzug | `GEFAELLESTRECKE_LINIENZUG` | - (nur interaktiv; Kern `gf-linienzug-einfuegen` braucht analysierte Segmente) | `GF_n` | `Gefaellestrecke.lsp` | +| Separator, Scanner | `ILS_SEPARATOR_INSERT`, `ILS_SCANNER_INSERT` | - (Schleife mit `getpoint`); Kern: `ssg-ils-block-laden` + `_.INSERT` | Einzelblock | `SSG_LIB_Commands.lsp`, `ssg_core.lsp` | +| BTMT, Eingang/Ausgang | `ILS_BTMT_*`, `ILS_EINGANG_INSERT`/`ILS_AUSGANG_INSERT` | wie Separator | Einzelblock | `SSG_LIB_Commands.lsp` | +| Omniflo Bogen/Weiche/TEF | `OMNI_APB_*`, `OMNI_W*_*`, `OMNI_TEF_*` ... | `_.INSERT` der `.dxf` (Kern `omni:insert-dxf` fragt den Punkt) | Einzelblock | `OmniModulInsert.lsp` | +| Omniflo Gerade | `OMNI_AP60`/`AP110`/`APG110` | `omni:make-gerade` | Compound-Block | `OmniModulInsert.lsp` | +| TEF-Foerderer-Linie | `TEF_FOERDERER_LINIE` | `tefl-insert-element` (ein Element) | Einzelbloecke | `TEFInsert.lsp` | +| Geruest (Saeule, Rohrabhaenger, Wandbefestigung) | `GERUEST`, `GERUEST_SAEULEN` ... | `geruest-block-bauen` | `GR__` | `GeruestInsert.lsp` | +| Geruest-Traeger | `GERUEST_TRAEGER` | `geruest-traeger-block-bauen` | `GR__` | `GeruestInsert.lsp` | + +--- + +## 1. Kreisel und Eckrad (`KreiselInsert.lsp`) + +### `kreisel-insert-script (pt abstand rotation typ hoehe drehrichtung)` + +Kreisel an einem freien Punkt. Ruft `draw-module` auf. + +| Parameter | Bedeutung | +|---|---| +| `pt` | Einfuegepunkt: aeusserster Punkt der AN-Seite auf der Achse (einen Radius = 400 mm vor der Mitte des Antriebsrads) | +| `abstand` | Mittenabstand Antriebsrad - Spannrad | +| `rotation` | Drehung der Achse AN -> SP in Grad | +| `typ` | `"STANDARD"` oder `"PIN"` (nil = `"STANDARD"`) -> Attribut `KREISELART` | +| `hoehe` | Einbauhoehe; nil = Z von `pt`, sonst `*kreisel-default-hoehe*` | +| `drehrichtung` | `"GUZ"`/`"UZS"` -> Attribut `DREHRICHTUNG` (optional, nil = Default `"UZS"` aus `*kreisel-attrib-defs*`) | + +Rueckgabe: Entity des `KREISEL_n`-Blocks oder nil. Setzt `ssg-start`/`ssg-end` +selbst. + +### `kreisel-connect-script (ptStart ptEnd typ hoehe)` + +Kreisel zwischen zwei aeussersten Punkten (AN-Seite, SP-Seite). Berechnet +`abstand` = Distanz - `*kreisel-durchmesser*` (800) und `rotation` aus den +Punkten. Rueckgabe wie oben. + +### `draw-module (basePoint abstand rotation attribs)` + +Gemeinsamer Bau-Kern beider Funktionen: setzt Antriebs- und Spannstation, +Tangenten, Beschriftung, fasst alles zu `KREISEL_n` zusammen, schreibt die +Attribute (`attribs` = Alist, wird mit `*kreisel-attrib-defs*` gemischt). +Direkt nur aufrufen, wenn zusaetzliche Attribute gesetzt werden muessen. + +### `ils-eckrad-insert (pt rotation attribs)` + +Eckrad als Compound-Block `ECKRAD_n`. + +| Parameter | Bedeutung | +|---|---| +| `pt` | **Radmitte** `(x y hoehe)` | +| `rotation` | Drehung in Grad | +| `attribs` | Alist mit Ueberschreibungen, z.B. `(("GERUEST_EINZELMODUL" . "1") ("GERUEST_TYP" . "Schoenenberger Geruest"))`; nil = Defaults | + +`c:ILS_Eckrad` berechnet die Radmitte aus Beruehrpunkt + Radius in Richtung +des zweiten Klicks. Rueckgabe: Entity des Blocks. + +--- + +## 2. VarioFoerderer Standard und Etage (`vf_core.lsp`, `vf_standard.lsp`, `vf_etage.lsp`) + +Ablauf in drei Schritten: **loesen** (Winkel und Teillaengen aus den +Gesamtmassen), **einfuegen** (Bausteine verketten), **Block bilden** +(`VF_n` mit Attributen). Der Dialog-Ablauf `vf-dialog-ablauf` / +`vf-dialog-berechnen-einfuegen` macht dasselbe, oeffnet aber Dialog 2 und ist +darum nicht skriptfaehig. + +### Vorbereitung + +| Funktion | Zweck | +|---|---| +| `init-bibliothek ()` | Bogen-, AS-/ES- und Stationsmasse aus den Bloecken lesen. Setzt `*lib-initialized*`. Wird von `berechne-standard` aufgerufen | +| `init-bibliothek-etage ()` | dasselbe fuer Etage, aufgerufen von `berechne-etage` | +| `vf-set-as-masse (winkel seite)` / `vf-set-es-masse (winkel seite)` | AS-/ES-Masse (`aus-dx`/`aus-dz`, `ein-dx`/`ein-dz`) fuer die Variante `"90"`/`"30"` + `"links"`/`"rechts"` setzen | + +### Loesen + +| Funktion | Rueckgabe | +|---|---| +| `berechne-standard (deltaL deltaH richtung seite)` | `(best-winkel L_GF L_VF ergebnis-liste)` | +| `berechne-alle-winkel (deltaL deltaH richtung feste-hz schon-flach)` | wie oben; `feste-hz` nil = 1600 mm feste Horizontale, `schon-flach` nil | +| `berechne-etage (deltaL deltaH richtung seite)` | wie oben, Etage-Geometrie (setzt die Gefaellebogen-Masse je `seite`) | +| `vf-winkel-solve (...)` | gemeinsamer L_GF/L_VF-Solve beider Typen (intern) | + +- `richtung` = `"Auf"`/`"Ab"`, `deltaH` als Betrag. +- `ergebnis-liste` = je Kandidatenwinkel `(winkel L_GF L_VF gueltig)`. + Kandidaten aus `vario.bogen_winkel` (3 ... 51). Einen bestimmten Winkel + erzwingen: Eintrag mit diesem `winkel` aus der Liste nehmen und + `gueltig`, `L_GF` > 0, `L_VF` > 0 pruefen (so `mubea:build-vario-one`). +- `L_GF` wird danach auf Vorlauf `L_GF1` und Nachlauf `L_GF2` aufgeteilt + (gleichmaessig / vorne / hinten / manuell, `foerderer:split-gf`). + +### Einfuegen + +``` +(variofoerderer-einfuegen deltaL deltaH richtung best-winkel L_GF1 L_GF2 L_VF startpunkt seite hz) +(etage-foerderanlage-einfuegen deltaL deltaH richtung best-winkel L_GF1 L_GF2 L_VF startpunkt seite hz) +``` + +Identische Signatur (Registry-Vertrag `*vf-typ-registry*`). `startpunkt` = +Lage von **KS_EIN des AS-Elements**, Z = Einbauhoehe am Anfang. `seite` = +`"links"`/`"rechts"`. Rueckgabe: Endpunkt (KS_AUS des ES-Elements). +`variofoerderer-einfuegen` ist aus Teilen zusammengesetzt (`vfs-as-teil`, +VF-Eingang/Koerper, `vfs-es-teil`), die auch der Linienzug nutzt. + +### Block bilden + +``` +(setq lastEnt (ssg-lastent-ohne-attribute)) ; VOR dem Einfuegen merken +...einfuegen... +(vf-block-erstellen anlage-typ seite (vf-next-number) n-scanner + hoehe-von hoehe-bis deltaH deltaL L_VF L_GF1 L_GF2 + richtung best-winkel startpunkt lastEnt hz + geruest-einzelmodul geruest-typ) +(vf-edit-xdata-schreiben ent anlage-typ hz) ; editierbar per Doppelklick +``` + +- `anlage-typ` = `"standard"`/`"etage"`; `hoehe-bis` = +deltaH bei `"Auf"`, + -deltaH bei `"Ab"`; `geruest-einzelmodul` `"0"`/`"1"` (nil = `"1"`), + `geruest-typ` einer aus `*ssg-geruest-optionen*`. +- Sammelt alle Objekte seit `lastEnt` zum Block `VF_n`, setzt Layer, Attribute, + ID (`ssg-id-generate`) und Dimension. Rueckgabe: Entity des Blocks. +- `lastEnt` immer mit `ssg-lastent-ohne-attribute` holen, nicht mit + `(entlast)` (siehe `CLAUDE.md`, ssg_core). +- Blocknummer immer ueber `vf-next-number`: eine feste Nummer fuehrt bei der + zweiten Anlage zu einer Rueckfrage von `_.-BLOCK` und einem haengenden Befehl. + +--- + +## 3. VF-Linienzug (`vf_linienzug.lsp`, `vf_spec.lsp`) + +Ketten mit Vario-Kurven, GF-Boegen und mehreren VF-Einheiten. Der Bau-Ablauf +ist ein Frage-Antwort-Assistent; jede Eingabe wird journalisiert und als XDATA +am `VF_n`-Block gespeichert. Nicht-interaktiv baut man ihn, indem man ein +Journal **abspielt**. + +| Funktion | Zweck | +|---|---| +| `vf-linienzug-modus ()` | Modus 1, frei zeichnend. Liest alle Eingaben ueber `vfl-in-point`/`-string`/`-real`/`-int` - aus der Replay-Queue, falls eine aktiv ist | +| `vf-linienzug-modus2 ()` | Modus 2, Pfad + Zielhoehe (Objektauswahl, eigenes Journal `"linienzug2"`) | +| `vfl-journal-replay-start (forward-journal)` | Journal als Antwort-Queue setzen; danach `vf-linienzug-modus` aufrufen | +| `vsp-journal (spec)` | Spec -> Journal (nil bei Spec-Fehlern, Fehler in `*vsp-fehler*`) | +| `vsp-pruefen (spec)` | alle Spec-Fehler sammeln | +| `vsp-bau-aus-spec (spec)` | eine Kette aus einer Spec bauen (headless, Replay, Ergebnis-Record mit `status`, Block, `prompts`, `glieder_soll`/`glieder_ist`) | +| `vsp-bau-liste (specs)` | mehrere Specs, je mit `vl-catch-all-apply` | +| `vsp-bau-datei (datei)` | Spec-Datei lesen (`vsp-json-laden` + `vsp-gruppieren`) und alle Ketten bauen; das ist `VF_SPEC_BAU` | +| `vsp-headless-an ()` / `vsp-headless-aus ()` | Headless-Rahmen: `*vfl-headless*`, Wizard aus, `*ssg-gui-aus*`, zaehlende Eingabe-Stubs | + +Ein Abbruch im Headless-Modus nennt die Stelle (`*vfl-headless-fehler*`), +statt auf eine Live-Eingabe zu warten. Details zum Ablauf und zu den +Journal-Regeln: [variofoerderer.md](variofoerderer.md), Abschnitte 5-8b. + +--- + +## 4. Gefaellestrecke (`Gefaellestrecke.lsp`) + +### `gf-rebuild (deltaL winkel hz-winkel as-seite es-seite startpunkt as-winkel es-winkel geruest-einzelmodul geruest-typ)` + +Der bequemste skriptfaehige Einstieg fuer Modus 1/2: setzt die AS-/ES-Masse, +berechnet die Staustrecke +`L_stau = (deltaL - aus-dx - ein-dx - separator_breite * cos(w)) / cos(w)` +und ruft `gefaellestrecke-einfuegen`. Ohne Zusammenfassung und Rueckfrage +(eigentlich fuer `GEFAELLESTRECKE_EDIT` gedacht). + +| Parameter | Bedeutung | +|---|---| +| `deltaL` | horizontale Gesamtlaenge AS + Staustrecke + Separator + ES | +| `winkel` | Neigung der Staustrecke in Grad (Default im Dialog 3.0) | +| `hz-winkel` | Fahrtrichtung | +| `as-seite`, `es-seite` | `"links"`/`"rechts"` | +| `startpunkt` | **KS_EIN des AS-Elements**, Z = Einbauhoehe oben | +| `as-winkel`, `es-winkel` | `"90"`/`"30"` als STRING (nil = `"90"`) | +| `geruest-einzelmodul`, `geruest-typ` | wie beim VF | + +Rueckgabe: `(ename deltaH deltaL)` oder nil (Staustrecke <= 0.1 mm -> `alert`). + +### `gefaellestrecke-einfuegen (L_stau winkel startpunkt as-seite es-seite hz deltaL-total as-winkel es-winkel geruest-einzelmodul geruest-typ)` + +Eigentlicher Bau: AS-Element, skalierte Staustrecke, Separator, ES-Element, +Block `GF_n` mit Attributen, XDATA `SSG_GF_EDIT` und ID. Erwartet die +fertige Staustreckenlaenge `L_stau` und passend gesetzte AS-/ES-Masse +(`vf-set-as-masse`/`vf-set-es-masse`). Rueckgabe wie `gf-rebuild`. + +### Weitere + +| Funktion | Zweck | +|---|---| +| `gf-modus12-abschluss (... dialog-bestaetigt ...)` | Abschluss Modus 1/2 mit Zusammenfassung; fragt ohne `dialog-bestaetigt` = T per Konsole nach | +| `gf-init-bibliothek ()` | AS-/ES-Masse fuer die GF lesen | +| `gf-linienzug-modus ()` | Modus 3, interaktiv (Auswahl vorhandener LINE/ARC) | +| `gf-linienzug-einfuegen (segmente grade startframe)` | Bau-Kern von Modus 3, erwartet die von `gf-analysiere-kette` aufbereiteten Segmente | + +**Falle `*lib-initialized*`**: `gefaellestrecke-einfuegen` initialisiert die +GF-Masse nur, wenn `*lib-initialized*` nil ist - dasselbe Flag setzt +`init-bibliothek` des VarioFoerderers. Wer in einem Lauf erst VF und dann GF +baut, muss `gf-init-bibliothek` einmal selbst erzwingen (so +`mubea:ensure-gf-init` in `tests/test_mubea.lsp`), sonst rechnet die GF mit +den Vario-Massen und landet auf falscher Hoehe. + +--- + +## 5. Einzelbloecke: Separator, Scanner, BTMT, Eingang/Ausgang + +| Funktion | Zweck | +|---|---| +| `ils-insert-sensor (fname)` | Befehls-Kern Separator/Scanner: fuegt in einer Schleife ein, Punkt per `getpoint`, Drehung per `pause` - **nicht skriptfaehig** | +| `ils-insert-station (rohname)` | dasselbe fuer BTMT-Beladung, SC_Entladung, Eingang, Ausgang; setzt zusaetzlich den Layer aus dem `LAYER`-Attribut | +| `ssg-ils-block-laden (blockname)` | Blockdefinition `data/ils/_.dwg` einmal je Zeichnung laden; Rueckgabe: effektiver Blockname mit Suffix oder nil | +| `ssg-ils-block-datei (blockname)` | nur den Dateipfad aufloesen (3D-Fallback) | +| `ssg-block-layer-vorgabe` | `LAYER`-Attribut eines eingefuegten Blocks als Ziel-Layer anwenden | +| `ssg-id-generate (ent)` | eindeutige `ID` vergeben (nur wenn der Block ein ID-Attribut hat) | + +Skriptfaehig bauen (Muster aus `hundm05:bau-sensor`): + +```lisp +(setq eff (ssg-ils-block-laden "Scanner")) ; nicht per (and ...) verketten! +(command "_.INSERT" eff (list x y z) "" "" rotation) +(while (and (> (getvar "CMDACTIVE") 0) (< n 5)) (command "") (setq n (1+ n))) +(ssg-id-generate (entlast)) +``` + +`ATTREQ`/`ATTDIA` vorher auf 0 setzen. Restliche Prompts gezaehlt mit Enter +beantworten, nicht ungebremst: eine Rueckfrage, die Enter ablehnt, haelt +`CMDACTIVE` sonst dauerhaft > 0. + +--- + +## 6. Omniflo (`OmniModulInsert.lsp`) + +| Funktion | Zweck | +|---|---| +| `omni:insert-dxf (sivasnr-str hoehe drehung)` | `%DXFM_OMNIFLO%/.dxf` einfuegen, `HOEHE`/`DREHUNG` setzen, Layer. **Einfuegepunkt per Maus** (`pause`); Rueckgabe: Einfuegepunkt oder nil | +| `omni:insert-from-entry (dlg-result)` | Dialog-Ergebnis von Bogen/Weiche -> `omni:insert-dxf` | +| `omni:make-gerade (blockname pt pt2 srcAttribs textHeight textGap textEnt)` | **skriptfaehig**: Aluprofil-Gerade `"AP60"`/`"AP110"`/`"APG110"` von `pt` nach `pt2` als Compound-Block mit Text. `srcAttribs` per `omni:read-src-attribs`, `textEnt` nil. Kein `ssg-start` - Aufrufer setzt OSMODE/ATTREQ/ATTDIA = 0. Rueckgabe: Entity | +| `omni:insert-block (blockname laengemax)` | Befehls-Kern der Geraden (fragt `pt`/`pt2` per `getpoint`) | +| `omni:ladeschleifen-dialog (profiltyp init-hoehe)` | Dialog Ladeschleife, danach `omni:insert-dxf` | + +Skriptfaehig fuer Boegen/Weichen: `_.INSERT` der DXF direkt mit Punkt +`(x y hoehe)` und Drehung, danach `ssg-attrib-set-on` mit `HOEHE`/`DREHUNG` +(`omni:test-insert` in `tests/test_omniflo.lsp`). Die festen TEF-/Zubehoer- +Befehle (`OMNI_TEF_*`, `OMNI_STOPPER` ...) rufen `omni:insert-dxf` mit fester +Sivas-Nummer. `OMNI_TV_*` und `OMNI_APBW_*` sind Platzhalter ohne Bau-Logik. + +--- + +## 7. TEF-Foerderer-Linie (`TEFInsert.lsp`) + +| Funktion | Zweck | +|---|---| +| `c:TEF_FOERDERER_LINIE` | Start-/Endpunkt abfragen, Linie zeichnen, 1000-mm-TEF-Geraden (`0_B10030.dxf`) aneinanderreihen; das letzte Stueck wird zurueckversetzt, damit der Foerderer genau am Linienende endet | +| `tefl-insert-element (start richtung-einheit s hz winkel)` | ein Element an Position `s` (mm ab `start`) entlang der Linie, Rz(hz)*Ry(winkel) | +| `tefl-ensure-block-loaded (dxf-pfad)` | Blockdefinition einmal laden | + +--- + +## 8. Geruest (`GeruestInsert.lsp`) + +| Funktion | Zweck | +|---|---| +| `geruest-einfuegen (bg wahl)` | Befehls-Kern Saeulen/Rohrabhaenger/Wandbefestigung: baut aus der Dialog-Auswahl `wahl` = `(variante-index oben unten laenge farbe)` die Werte (ARTINR, BEZEICHNUNG, TEILEART, LAENGE, TEILEFARBE), fragt dann den Punkt per `getpoint` | +| `geruest-block-bauen (bg variante werte pt teile-dwgs)` | **skriptfaehiger Kern**: Dummy-DWG + Teile-DWGs bei `pt`, Block `GR__`. `bg` = Baugruppe aus `geruest.json`, `werte` = Attribut-Alist, `teile-dwgs` aus `geruest-teile-dwgs` | +| `geruest-traeger-einfuegen (bg wahl)` | Befehls-Kern Traeger: Schleife Startpunkt -> Endpunkt oder Laenge | +| `geruest-traeger-block-bauen (bg layer netto strebe strebe-pfad muffe-pfad werte pt winkel)` | **skriptfaehiger Kern** Traeger: LINE (0,0,0)-(netto,0,0), Streben, Muffe, Block bei `pt` mit `winkel` (Radiant). Rueckgabe: `(blockname . entity)` | +| `geruest-traeger-laenge`, `-start`, `-streben`, `-pruefen` | reine Berechnung (Netto-Laenge nach Abzuegen, Startpunkt, Strebenverteilung, min/max) - in `tests/test_unit.lsp` getestet | +| `geruest-traeger-beschriften (blockEnt)` | Beschriftungstext aus dem fertigen Block | +| `geruest-block-abschliessen (bg attribs werte lastEnt pt rot)` | gemeinsamer Schluss aller Geruest-Bloecke (ATTDEFs, Block bilden, einfuegen, ID) | + +Fuer Geruest gibt es noch keinen JSON-Treiber. + +--- + +## 9. Einfuegeprimitiven (`ssg_ks_insert.lsp`, `vf_etage.lsp`) + +Grundbausteine, auf denen VF, GF und Linienzug aufsetzen. Alle legen **KS_EIN** +des Blocks (nicht den Block-Ursprung) auf den Startpunkt und geben den +Weltpunkt von **KS_AUS** zurueck - damit lassen sich Bausteine verketten. + +| Funktion | Zweck | +|---|---| +| `ensure-block-loaded (name)` | Block einmal ins Dokument laden | +| `extract-ks-from-block (block-obj)` | KS_EIN/KS_AUS eines eingefuegten Blocks (VLA-Objekt) lesen, Cache `*ks-cache*` je Blockname | +| `insert-block-by-ks (blockname einfuegepunkt hz)` | Block nur um Z gedreht (AS/ES-Elemente, Stationen) | +| `insert-inclined-scaled-block (blockname startpunkt laenge winkel hz)` | Block in X auf `laenge` skaliert und um `winkel` geneigt, Rz(hz)*Ry(winkel) (Staustrecken) | +| `insert-rotated-block-with-ks (blockname startpunkt winkel block-dx block-dz hz)` | Block gedreht, `block-dx`/`block-dz` nur als Fallback ohne KS_AUS (Boegen) | +| `insert-hz-scaled-block-by-ks (blockname startpunkt laenge hz-winkel vert-winkel)` | waagrechter, auf `laenge` skalierter Block (`Staustrecke_SP_1000_mm`, Massstab `laenge/1000`); Kandidat fuer die noch fehlende waagrechte Foerderstrecke. Bricht bei fehlender Blockdatei mit `(exit)` ab - vorher mit `ssg-ils-block-laden` pruefen | +| `ssg-rot-matrix-zy (chz shz cv sv)` | 4x4-Rotationsmatrix Rz(hz)*Ry(vert) aus Cosinus/Sinus beider Winkel (`ssg_core.lsp`) | + +--- + +## 10. Gemeinsame Regeln fuer eigene Treiber + +- Systemvariablen ueber `ssg-start`/`ssg-end` sichern; `OSMODE` 0, + `ATTREQ` 0, `ATTDIA` 0 setzen, sonst fragt `_.INSERT` nach Attributen. +- Module vorher laden: `(ssg-ensure "KreiselInsert")`, `"VarioFoerderer"`, + `"Gefaellestrecke"`, `"SSG_LIB_Commands"`. +- Die Dimension folgt `ssg-ils-dim-aktuell`; fuer einen festen Lauf + `*ssg-ils-dim*` (bzw. `*vsp-dim-override*` beim Spec-Bau) setzen und danach + zuruecksetzen. +- Rueckgaben nicht mit `and`/`or` verketten - die liefern in AutoLISP nur + T/nil (siehe `CLAUDE.md`, Konventionen). +- Interaktive GUI abschalten: `ssg-gui-aus` bzw. `vsp-headless-an`. +- Vorbilder: `tests/test_mubea.lsp` (Kreisel, VF Standard/Etage, GF, + Einzelbloecke), `tests/test_hundm05.lsp` (Spec-Ketten, Kreisel, + Einzelbloecke), `tests/test_omniflo_strecke.lsp` (Omniflo). diff --git a/doc/Entwicklungsplan.md b/doc/plan/Entwicklungsplan.md similarity index 100% rename from doc/Entwicklungsplan.md rename to doc/plan/Entwicklungsplan.md diff --git a/doc/KS-Frame-Architektur.md b/doc/plan/KS-Frame-Architektur.md similarity index 100% rename from doc/KS-Frame-Architektur.md rename to doc/plan/KS-Frame-Architektur.md diff --git a/doc/translate.md b/doc/plan/translate.md similarity index 100% rename from doc/translate.md rename to doc/plan/translate.md diff --git a/doc/vf-parameter-struct-analyse.md b/doc/plan/vf-parameter-struct-analyse.md similarity index 100% rename from doc/vf-parameter-struct-analyse.md rename to doc/plan/vf-parameter-struct-analyse.md diff --git a/doc/TODO-modus2-achsenweise-bilanz.md b/doc/todo/modus2-achsenweise-bilanz.md similarity index 100% rename from doc/TODO-modus2-achsenweise-bilanz.md rename to doc/todo/modus2-achsenweise-bilanz.md diff --git a/doc/TODO-plan-vf-interactive.md b/doc/todo/plan-vf-interactive.md similarity index 98% rename from doc/TODO-plan-vf-interactive.md rename to doc/todo/plan-vf-interactive.md index f27da22..b63210a 100644 --- a/doc/TODO-plan-vf-interactive.md +++ b/doc/todo/plan-vf-interactive.md @@ -1,6 +1,9 @@ # TODO/Plan: VF-Linienzug ohne GUI/Konsole aufrufbar machen -**Stand:** 2026-09-02 · **Status:** Stufe 0 fertig (im CAD noch ungetestet) +**Stand:** 2026-09-02, Fortschrittsnotizen bis 2026-09-18 · +**Status:** Stufe 0 fertig und CAD-verifiziert; Stufe 1 (Modus 1) im CAD +verifiziert, `TEST_HUNDM05` aktiv in `tests/alltests.json`; Stufe 2 +Schritte 0-4 + 7-Assertion fertig, 5/6/8/9/10 offen, 11 zurueckgestellt. **Herkunft:** Planungslauf (Plan-Modus) vom 2026-09-02, hier eingecheckt zur Nachverfolgung. ## Fortschritt @@ -12,7 +15,7 @@ | P1 | `vfl-meldung` statt `alert` in den Bau-Pfaden | **erledigt** | | P2 | `*vfl-headless*`: leere Replay-Queue wird zum Fehler | **erledigt** | | P3 | Kleinkram (`*vfl-meldungen*`-Reset, `vfl-view-refresh`) | **erledigt** | -| S1 | Stufe 1: `Lisp/vf_spec.lsp` (Spec -> Journal -> Replay) | **Modus 1 fertig**, im CAD ungetestet | +| S1 | Stufe 1: `Lisp/vf_spec.lsp` (Spec -> Journal -> Replay) | **Modus 1 fertig, im CAD verifiziert** (`TEST_HUNDM05` aktiv in `tests/alltests.json`) | | S2 | Stufe 2: echte Funktionstrennung | **Schritte 0-4 + 7-Assertion fertig**, 5/6/8/9/10 offen, 11 zurueckgestellt | | PY | `lib/vf_journal_export.py`: Grammatik vervollstaendigen | **erledigt** | @@ -79,11 +82,11 @@ Block ist (danach entstehen noch Beschriftungstexte). Behoben: - `vfl-meldung` verkraftet jetzt `txt` = nil (fehlender i18n-Schluessel wuerde `strcat` genau dann abbrechen, wenn schon etwas schiefging). -**Noch offen** (braucht BricsCAD): `TEST_HUNDM05` muss 5x -`"status": "executed"` mit `prompts` 0 liefern, und eine interaktive -Gegenprobe (`FOERDERANLAGE` -> Linienzug Modus 1, Doppelklick-Edit, -`SSG_DIM_SWITCH`) muss unveraendert aussehen. Erst danach kann in -`tests/alltests.json` das `"disabled": true` der hundm05-Zeile weg. +**Erledigt**: `TEST_HUNDM05` liefert 5x `"status": "executed"` mit `prompts` +0; die interaktive Gegenprobe (`FOERDERANLAGE` -> Linienzug Modus 1, +Doppelklick-Edit, `SSG_DIM_SWITCH`) ist unveraendert. Das `"disabled": true` +der hundm05-Zeile in `tests/alltests.json` ist weg, der Test laeuft aktiv +mit. **Bewusst nicht umgestellt**: `vfl-konvertiere-ent` (Batch-2D/3D) laeuft weiter ohne `*vfl-headless*` - eine Live-Rueckfrage ist dort bisher gewolltes diff --git a/doc/tools.md b/doc/tools.md index 92efe5d..5e01b66 100644 --- a/doc/tools.md +++ b/doc/tools.md @@ -70,7 +70,7 @@ Die JSON-Dateien liefern die IDs (Sivasnr) und Metadaten zur Filterung. Die DXF- ### --boegen -Alle Eintraege aus `omniflo_boegen.json` (27 Items, KurvenWinkel 22.5/45/67.5/90/180). +Alle Eintraege aus `omniflo_boegen.json` (29 Items, KurvenWinkel 22.5/45/67.5/90/180). **Kriterium:** Schnittpunkt der Tangenten am Anfangs- und Endpunkt der Bogenkurve. Bei 180-Grad-Boegen wird der Mittelpunkt zwischen Start und Ende verwendet. @@ -103,19 +103,21 @@ Einzel-, Doppel- und Dreiwegeweichen mit KurvenWinkel 0, ohne Sternweiche (8 Ite ### --delta -Deltaweichen (1 Item). +Deltaweichen (7 Items). **Kriterium:** Mittelpunkt der laengsten horizontalen Linie. ### --dreifachweiche -Dreifachweichen (6 Items). +Dreifachweichen (aktuell 0 Items in `omniflo_weichen.json` - Schalter und +Filterkriterium existieren weiter in `lib/omniflo_utils.py` fuer den Fall, +dass wieder Dreifachweichen-Daten gepflegt werden). **Kriterium:** Mittelpunkt der laengsten horizontalen Linie. ### --sternweiche -Sternweiche (1 Item). +Sternweiche (2 Items). **Kriterium:** Zentrum, berechnet als Mittelpunkt der laengsten horizontalen Linie (entspricht dem Schnittpunkt der horizontalen und vertikalen Mittelachse). @@ -135,21 +137,24 @@ Hier die Weichen ## Abdeckung -Alle 107 Items (27 Boegen + 80 Weichen) sind exklusiv genau einem Schalter zugeordnet. Keine Ueberlappungen, keine Luecken. +Alle 110 Items (29 Boegen + 81 Weichen, Stand aktuelle `data/json/omniflo_*.json`) +sind exklusiv genau einem Schalter zugeordnet. Keine Ueberlappungen, keine +Luecken. Die Zahlen je Schalter verschieben sich mit der Datenbasis; bei +Zweifel per `SWITCH_FILTERS` (`lib/omniflo_utils.py`) nachrechnen. ## Uebersicht aller Schalter | Schalter | Items | WeichenTyp | KurvenWinkel | Einfuegepunkt-Kriterium | | --- | --- | --- | --- | --- | -| `--boegen` | 27 | - | 22.5, 45, 67.5, 90, 180 | Tangentenschnittpunkt am Bogen (180: Mittelpunkt) | +| `--boegen` | 29 | - | 22.5, 45, 67.5, 90, 180 | Tangentenschnittpunkt am Bogen (180: Mittelpunkt) | | `--weichen45` | 32 | Einzel-, Doppel-, Dreiwegeweiche | 45 | Tangente oberer Bogenpunkt x laengste Vertikale | | `--weichen90` | 24 | Einzel-, Doppel-, Dreiwegeweiche | 90 | Tangente oberer Bogenpunkt x laengste Vertikale | | `--weichenkoerper` | 8 | Einzel-, Doppel-, Dreiwegeweiche | 22.5 | Laengste Diagonale x laengste Vertikale | | `--weichen-parallel` | 8 | Einzel-, Doppel-, Dreiwegeweiche | 0 | Laengste Diagonale x laengste Vertikale | -| `--dreifachweiche` | 6 | Dreifachweiche | 90 | Mittelpunkt laengste Horizontale | -| `--delta` | 1 | Deltaweiche | 90 | Mittelpunkt laengste Horizontale | -| `--sternweiche` | 1 | Sternweiche | 0 | Zentrum (Mittelpunkt laengste Horizontale) | -| **Gesamt** | **107** | | | | +| `--dreifachweiche` | 0 | Dreifachweiche | 90 | Mittelpunkt laengste Horizontale | +| `--delta` | 7 | Deltaweiche | 90 | Mittelpunkt laengste Horizontale | +| `--sternweiche` | 2 | Sternweiche | 0 | Zentrum (Mittelpunkt laengste Horizontale) | +| **Gesamt** | **110** | | | | ## Umgebungsvariablen @@ -169,9 +174,11 @@ Setzt Koordinatensystem-Bloecke (K1, K2, K3, K4) in Omniflo DXF-Dateien. Jeder B ## Aufruf ``` -python lib/set_koords.py [--schalter] [--number SIVASNR] [--force] +bin\set_coords.bat [--schalter] [--number SIVASNR] [--force] ``` +Das Batch-Skript ruft `setenv.bat` auf und startet `lib/set_koords.py` mit den uebergebenen Argumenten. + ## Datenquellen | Pfad | Inhalt | diff --git a/doc/variofoerderer.md b/doc/variofoerderer.md index fd97fca..ad0d2a8 100644 --- a/doc/variofoerderer.md +++ b/doc/variofoerderer.md @@ -185,12 +185,18 @@ Winkelberechnung befuellt, da seine Optionsliste von Dialog 1 abhaengt. | Verteilung Staustrecken | `verteilung` | Dropdown | Gleichmaessig (L_GF1=L_GF2) / Vorne Minimum, hinten Rest / Hinten Minimum, vorne Rest / Eigene Werte | Gleichmaessig | | L_GF1 vorne (manuell) | `lgf1_manuell` | Zahl (mm) | nur aktiv bei Verteilung "Eigene Werte" | L_GF / 2 | -Vorgelagert (Konsole, vor Dialog 1): Eingabemodus-Frage "3D-Linie" vs. "Werte -manuell" — nur bei Wahl "2" oeffnen sich obige Dialoge, sonst laeuft der -komplette [Legacy-Konsolen-Pfad](#8-legacy-konsolen-pfad). Direkt vor Dialog 1 -wird zusaetzlich der Startpunkt (X/Y) per Punktwahl gewaehlt -(`vfs-standard-dialog-ablauf`, `Lisp/vf_standard.lsp:1069`); die Z-Hoehe kommt -aus dem Feld "Einfuegehoehe". +Vorgelagert: Eingabemodus-Frage "3D-Linie" vs. "Werte manuell" per DCL-Dialog +(`vfc_eingabemodus`, `vfc-dialog-eingabemodus-waehlen` in `Lisp/vf_core.lsp`; +Radio-Buttons wie `vfc_typ`, Rueckfall auf die alte Konsolenfrage bei +abgeschalteter GUI oder `DXFM_LANG=en_GB`). **Beide** Antworten fuehren zu +Dialog 1/2 oben: bei "3D-Linie" wird zuerst die Linie gewaehlt +(`vfc-3dlinie-erfassen`, Objektauswahl bleibt Zeichnung/Konsole — eine Linie +laesst sich nicht durch ein Formularfeld ersetzen) und deltaL/deltaH/hz/ +Einfuegehoehe gehen als Prefill in Dialog 1; bei "Werte manuell" wird nur der +Startpunkt per Punktwahl-Dialog (`vfc-dialog-startpunkt-waehlen`) gewaehlt und +Dialog 1 startet ohne Geometrie-Prefill. Der +[Legacy-Konsolen-Pfad](#8-legacy-konsolen-pfad) existiert nur noch als +Rueckfall, wenn keiner der beiden Dialoge gezeigt werden kann/darf. --- @@ -236,13 +242,62 @@ Das Winkel/Seite-Paar (`aus_winkel`/`aus_seite`, `ein_winkel`/`ein_seite`) taucht identisch auch im Linienzug als AS-/ES-Element-Baustein auf (siehe Abschnitt 5.2). -Zusaetzlich als Legacy-Konsole vorhanden (kein Dialog): +**Modus 2** — Startpunkt/Endpunkt waehlen (`GEFAELLESTRECKE_3DLINIE`): 2x +Punktwahl + Gefaellewinkel (Zahl, aus DeltaH automatisch vorgeschlagen). +Legacy-Konsole, kein Dialog. -- **Modus 2** — Startpunkt/Endpunkt waehlen: 2x Punktwahl + Gefaellewinkel - (Zahl, aus DeltaH automatisch vorgeschlagen). -- **Modus 3** — Bestehenden Linienzug (Linien/Boegen) auswaehlen: Objektauswahl, - Winkel wird aus der Geometrie vorgeschlagen und kann per j/n bestaetigt oder - ueberschrieben werden. +**Modus 3** — Bestehenden Linienzug (Linien/Boegen) auswaehlen +(`GEFAELLESTRECKE_LINIENZUG`): Objektauswahl (`ssget` LINE/ARC) und +Start-/Endpunktwahl bleiben Zeichnung/Konsole. AUS-/EIN-Element (Winkel +90/30 + Seite links/rechts) laufen danach ueber EINEN Dialog +(`gefaellestrecke_linienzug`, `gf-dialog-linienzug-aus-ein` in +`Lisp/Gefaellestrecke.lsp`) — muss vor der automatischen Segmentlaengen- +/Winkelberechnung laufen, die von der AS-/ES-Masse abhaengt. Danach zeigt +ein zweiter Dialog (`gf-dialog-linienzug-winkel`) den automatisch +berechneten Vorschlagswinkel ueberschreibbar zusammen mit einer kurzen +Segment-Zusammenfassung; dessen OK ersetzt zugleich die SPAETERE finale +Einfuegen-Bestaetigung (keine doppelte Rueckfrage nach der rein +automatischen AS/ES-Korrekturschleife). Rueckfall auf die alten +Konsolenfragen (inkl. der finalen Einfuegen-Bestaetigung), wenn einer der +beiden Dialoge nicht gezeigt werden kann/darf (GUI aus); ein bewusster +Dialog-Abbruch ('ABBRUCH) bricht dagegen den ganzen Befehl ab, OHNE auf die +Konsole zurueckzufallen. + +**Beispiel Modus 3 — Gefaellestrecke aus einem gezeichneten Linienzug:** + +1. Vorab eine Kette aus LINE-/ARC-Objekten entlang der gewuenschten Trasse + zeichnen (Geraden + Rundungen, hier ein abgerundeter Linienzug): + ![GF aus Linienzug - Ausgangslinie](images/gf-linienzug1.png) +2. `GEFAELLESTRECKE_LINIENZUG` aufrufen, die Objekte auswaehlen sowie Start- + und Endpunkt der Kette anklicken. Danach AS-/EIN-Element (Winkel/Seite) + im ersten Dialog waehlen; der zweite Dialog zeigt den automatisch aus + der Geometrie berechneten Gefaellewinkel-Vorschlag (hier 3°, AS/ES-Felder + nur noch zur Kontrolle, nicht mehr editierbar): + ![GF aus Linienzug - Dialog mit Vorschlagswinkel](images/gf-linienzug2.png) +3. Nach OK baut der Befehl die Gefaellestrecke direkt entlang der + Original-Linie (AUS-Element, Gefaellebogen an jeder Rundung, + Staustrecke, EIN-Element) — die urspruengliche Linie bleibt zur + Kontrolle in der Zeichnung stehen: + ![GF aus Linienzug - fertige Strecke neben der Ausgangslinie](images/gf-linienzug3.png) + +**Bearbeiten (`c:GEFAELLESTRECKE_EDIT`):** Doppelklick auf einen per +Linienzug gebauten `GF_n`-Block (TEILEART `"Streckengruppe"`, enthaelt +mindestens einen Bogen) dispatcht auf `gf-linienzug-rebuild` statt auf den +einfachen Gefaellestrecke-Dialog — der kennt keine Segmentliste/Boegen und +wuerde beim Neuaufbau die Bogengeometrie verlieren. Die Segmentliste +(Typ/Fahrtrichtung/Laenge/Winkel/Seite je Segment, bereits laengenfertig) +UND der Baustartpunkt stehen als eigene XDATA (App `SSG_GF_LZ`, +`gf-lz-xdata-schreiben`/`-lesen`, variable Laenge + Chunking wie die +Sepliste-XDATA in `ssg_ks_insert.lsp`) auf dem Block — ohne sie bleibt eine +Streckengruppe wie zuvor nicht editierbar (Meldung "Nur einfache +Gefaellestrecken..."). Der Edit-Ablauf nutzt dieselben zwei Dialoge wie das +Bauen (`gf-dialog-linienzug-aus-ein`/`-winkel`), diesmal mit den aktuellen +Attributen (`WINKEL_AS`/`SEITE_AS`/`WINKEL_ES`/`SEITE_ES`) vorbelegt statt +mit Defaults — AS-/ES-Element und Gefaellewinkel bleiben damit nachtraeglich +aenderbar, die Segmentlaengen/-form (Geraden, Boegen) selbst nicht (dafuer +muss die Kette per `GEFAELLESTRECKE_LINIENZUG` neu gebaut werden). Baut am +selben Startpunkt neu und loescht den alten Block erst NACH erfolgreichem +Neuaufbau. --- @@ -377,6 +432,18 @@ interaktive Linienzug-Ablauf weiter (Abschnitt 5). Einzige Eingabe — Rest laeuft automatisch (Attribute aggregiert, Luecken werden gemeldet). +**VARIOFOERDERER_NACHRUESTEN / VF_NACHRUESTEN** + +| Feld | Steuerelement | Werte | +|---|---|---| +| VF_n-Bloecke | Objektauswahl (`ssget`) | Altbestand ohne `SSG_VF_EDIT`-Marker | +| Baurichtung hz je Fund | Dialog `vfs_nachruesten_hz` (Zahl, Grad) | Vorgabe = zuletzt eingegebener Wert; Rueckfall auf Konsole nur, wenn der Dialog nicht gezeigt werden kann/darf | + +Typ (Standard/Etage) wird automatisch aus den Attributen erkannt +(`GF_Bogen_R/L_60` -> Etage). Nur die Baurichtung ist nicht rekonstruierbar +und wird je unmarkiertem Fund abgefragt; "Abbrechen" im Dialog uebernimmt +fuer diesen Fund den Vorgabewert, ohne die ganze Schleife abzubrechen. + **VF_SEKTION_RESTORE — lose Kette aus der Zeichnung wiederherstellen** | Feld | Steuerelement | Werte | @@ -419,9 +486,11 @@ Wiederherstellungs-Daten gehoeren ausschliesslich in die XDATA der Zeichnung. ## 8. Legacy-Konsolen-Pfad -Wird bei Standard/Etage durchlaufen, wenn auf die Eingabemodus-Frage *nicht* -"2" getippt wird (Enter/leer faellt ebenfalls hierher zurueck) — also aktuell -noch der Vorgabe-Pfad. Zwei Untermodi: +Reiner Rueckfall fuer Standard/Etage: greift nur noch, wenn der Dialog +`vfc_eingabemodus` nicht gezeigt werden kann/darf (GUI abgeschaltet, +`DXFM_LANG=en_GB`) — im Normalfall (GUI an, Deutsch) laufen beide +Eingabemodi inzwischen ueber den Dialog (Abschnitt 2, "Vorgelagert"). Zwei +Untermodi: **3D-Linie auswaehlen** @@ -448,7 +517,7 @@ Optionen, bei "Eigene Werte" zusaetzlich L_GF1 als Zahl) -> Bestaetigung ## 8b. Nicht-interaktiver Betrieb (Headless) Fuer Tests, Batch-Nachbau und den geplanten Spec-Bau (Fahrplan: -`doc/TODO-plan-vf-interactive.md`) muss eine Kette **ohne** Dialog und ohne +`doc/todo/plan-vf-interactive.md`) muss eine Kette **ohne** Dialog und ohne Konsolenfrage laufen. Drei Schalter greifen dabei ineinander: | Schalter | Datei | Wirkung | @@ -585,19 +654,13 @@ eine bereits genannte, kleinere Nummer (z.B. 34 -> 35 -> 34) sind Schleifen |---|---|---|---| | 1 | Befehl waehlen | FOERDERANLAGE / GEFAELLESTRECKE | 2, 40 | | 2 | Anlagen-Typ | Standard / Etage / Linienzug | 3, 3, 30 | -| 3 | Eingabemodus (nur Standard/Etage) | Konsole / Dialog | 4, 10 | -| 4 | Startpunkt waehlen | Punktwahl | 5 | +| 3 | Eingabemodus (nur Standard/Etage) | Dialog `vfc_eingabemodus`: 3D-Linie / Werte manuell | 11, 4 | +| 4 | Startpunkt waehlen | Punktwahl-Dialog | 5 | | 5 | Dialog "Basis" | DeltaL, DeltaH, Auf/Ab, Einfuegehoehe, Fahrtrichtung, Seite, Motorseite, 2D/3D, Geruest | 6 | | 6 | Winkelberechnung (automatisch) | - | 7 | | 7 | Dialog "Winkel/Verteilung" | Winkel, Verteilung, ggf. L_GF1 | 8 | | 8 | Bau (automatisch) | - | ENDE | -| 10 | Eingabemodus Konsole | 3D-Linie / Werte | 11, 12 | -| 11 | 3D-Linie waehlen | Objektwahl -> DeltaL/DeltaH/Fahrtrichtung automatisch | 13 | -| 12 | Werte eingeben | DeltaL, DeltaH, Auf/Ab, Startpunkt, Fahrtrichtung | 13 | -| 13 | Seite waehlen | rechts/links | 14 | -| 14 | Winkel waehlen | automatisch bei 1 Treffer, sonst Liste | 16 | -| 16 | Staustrecken-Verteilung | 4 Optionen, ggf. L_GF1 manuell | 17 | -| 17 | Bestaetigung "Einfuegen?" | Ja/Nein | 8 | +| 11 | 3D-Linie waehlen | Objektwahl (Zeichnung) -> DeltaL/DeltaH/Fahrtrichtung automatisch, Prefill fuer Dialog 5 | 5 | | 30 | Linienzug-Modus | Manuell / Pfad+Zielhoehe / Vorwaerts-Nachbau | 31, 50, 55 | | 31 | Startpunkt + Starthoehe | Punktwahl + Zahl | 32 | | 32 | AS-Element setzen? | Ja/Nein | 33, 34 | @@ -645,9 +708,9 @@ eine bereits genannte, kleinere Nummer (z.B. 34 -> 35 -> 34) sind Schleifen ### Beispiel-Abfolgen -- Standard, Dialog-Pfad: `1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8` -- Standard, Legacy-Konsole mit 3D-Linie: `1 -> 2 -> 3 -> 10 -> 11 -> 13 -> 14 -> 16 -> 17 -> 8` -- Etage, Dialog-Pfad: `1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8` +- Standard, Werte manuell: `1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8` +- Standard, aus 3D-Linie: `1 -> 2 -> 3 -> 11 -> 5 -> 6 -> 7 -> 8` +- Etage, Werte manuell: `1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8` - Linienzug — AS-Element, GF, Auf/Ab-VF mit Zwischenkurve, Kettenende: `1 -> 2 -> 30 -> 31 -> 32 -> 33 -> 34 -> 36 -> 41 -> 34 -> 37 -> 42 -> 46 -> 42 -> 45` - Linienzug — horizontaler Anfang direkt bis Kettenende: diff --git a/doc/vf_linienzug_modus1_anleitung.md b/doc/vf_linienzug_modus1_anleitung.md index 371b0ba..3645412 100644 --- a/doc/vf_linienzug_modus1_anleitung.md +++ b/doc/vf_linienzug_modus1_anleitung.md @@ -10,9 +10,8 @@ aufrufbar in BricsCAD ueber `TEST_LINIENZUG_STUFE2` (siehe diesen Testlauf jederzeit wiederholen. Fuer die Architektur-Details (Frame-Kette, Journal/XDATA, Wizard-Dialoge) -siehe `doc/variofoerderer.md` und `doc/VarioFoerderer_Linienzug_Prinzipien.md`. -Diese Anleitung konzentriert sich auf die Bedienung und die typischen -Stolperfallen. +siehe `doc/variofoerderer.md`. Diese Anleitung konzentriert sich auf die +Bedienung und die typischen Stolperfallen. ## 1. Was ist Modus 1? @@ -58,43 +57,48 @@ ist der interessante Teil (siehe Abschnitt 4, Stufe 1/Stufe 2). Diese Tabelle zitiert die tatsaechlichen Konsolen-Fragen aus dem Testfall (Kommentar-Spalte = `kommentar`-Feld im JSON) und erklaert, was jede Frage -bedeutet. +bedeutet. Die Datei hat aktuell 62 Eingabe-Eintraege (ohne das Kopf-Objekt); +mehrere Eintraege pro Zeile sind zu einem Schritt zusammengefasst, wo eine +Antwort mehrere JSON-Eintraege verbraucht (z.B. eine Vario-Kurve = Winkel + +Seite + Variante = 3 Eintraege). -| # | Frage (sinngemaess) | Antwort im Beispiel | Bedeutung | +| # (JSON-Eintraege) | Frage (sinngemaess) | Antwort im Beispiel | Bedeutung | |---|---|---|---| | 1 | Startpunkt der Kette | Klick (X/Y) | Beliebiger Punkt in der Zeichnung | | 2 | Hoehe (Z) Startpunkt | 2154 | Absolute Einbauhoehe des Kettenanfangs | | 3 | AS-Element setzen? | Ja | Umlenkstation am Anfang - "AS" = Ausgangs-/Anfangs-Element | | 4 | AS-Element Winkel | 90 Grad | Baugroesse des Umlenkstations-Blocks (90 oder 30 Grad Kurvenradius) | | 5 | AS-Seite | links | Auf welcher Seite die Umlenkstation seitlich ausschwenkt | -| 6 | Naechstes Element | Horizontal-VF | Kettenanfang-Menue hat 6 Optionen (GF-Bogen, Vario-Kurve, Linie-GF, Linie-VF, Horizontal-VF, Linienende) | -| 7 | Endpunkt Horizontal-VF | Klick | Laenge/Richtung der ersten VF-Einheit | -| 8 | Ziel-Hoehe Eingang GF1 | 1980 | Hoehe, auf die der Eingangs-GF-Anteil der VF-Einheit einregelt | -| 9 | Ist der Endpunkt der Foerderer? | Nein | Innerhalb der VF-Einheit: "geht die Kette hier zu Ende?" | -| 10-13 | Naechstes im VarioFoerderer -> Vario-Kurve 90 rechts innen | - | Grundriss-Schwenk INNERHALB der laufenden VF-Einheit | -| 14 | Ist der Endpunkt der Foerderer? | Nein | s.o. | -| 15-16 | Horizontaler Foerderer, Endpunkt | Klick | Weiteres gerades VF-Sub-Segment | -| 17 | Ist der Endpunkt der Foerderer? | Nein | s.o. | -| 18-21 | Vario-Kurve 90 rechts innen | - | Zweiter Grundriss-Schwenk | -| 22 | Ist der Endpunkt der Foerderer? | Ja, nur Motorstation | Schliesst die 1. VF-Einheit ab (Motorstation, kein ES) | -| 23 | Ist das Kettenende (ES-Element)? | Nein, weiterbauen | Nach der Motorstation: geht die Kette weiter? | -| 24 | Zusaetzlichen Separator hier? | Nein | Optionaler Separator direkt an der Motorstation | -| 25 | Naechstes Element | Neue Linie: GF | Jetzt folgt ein reines Gefaellestrecke-Element (kein VF) | -| 26 | Laenge bestimmen ueber | Endpunkt waehlen | Alternativ: Zielhoehe zuerst festlegen (siehe `vfl-gf-laenge-modus`) | -| 27 | Endpunkt Gefaellestrecke | Klick | Bestimmt deltaL der GF-Geraden | -| 28-29 | Separator VOR / NACH? | Ja / Ja | GF-Stuecke werden ueber Separatoren mechanisch begrenzt | -| 30-31 | Gefaelle festlegen -> Neigungswinkel | 3 Grad | Direkter Winkel (Alternative: Zielhoehe -> Winkel wird berechnet) | -| 32 | Ist das Kettenende? | Nein, weiterbauen | 3-Optionen-Menue: Ja+ES / Ja ohne ES / Nein | -| 33 | Naechstes Element | Horizontal-VF (2. Einheit) | Neue VF-Einheit beginnt direkt am GF-Ausgang (Separator als Eingang) | -| 34-49 | (wie oben: Vario-Kurven, Auf/Ab-Foerderer 27 Grad) | - | s.o. | -| 50 | Ist der Endpunkt der Foerderer? | Ja, Motorstation + Kettenende | Startet den automatischen Kettenende-Abschluss | -| 51-53 | ES-Element setzen? / Winkel / Seite | Ja / 90 / links | Ausgangs-Element, spiegelbildlich zum AS-Element | -| 54 | Endpunkt Kettenende | Klick | Letzter gewuenschter Punkt der Kette | -| 55 | Ziel-Hoehe Kettenende | 2154 | Muss wieder exakt die Starthoehe sein | -| 56 | (automatisch, keine Eingabe) | - | Stufe-1/Stufe-2-Loeser rechnet, siehe Abschnitt 4 | -| 57 | GF2=86mm unter Mindestlaenge - trotzdem bauen? | Ja | Sicherheitsfrage, siehe Abschnitt 4 | +| 6 | Naechstes Element | Neue VF mit horizontalem Anfang | Kettenanfang-Menue hat 6 Optionen (GF-Bogen, Vario-Kurve, Linie-GF, Linie-VF, Horizontal-VF, Linienende) | +| 7 | Endpunkt der Linie | Klick | Laenge/Richtung der ersten VF-Einheit | +| 8 | Ziel-Hoehe fuer den horizontalen VF-Anfang | 1980 | Hoehe, auf die der Eingangs-GF-Anteil der VF-Einheit einregelt | +| 9-10 | Zusaetzlichen Separator VOR/NACH dem horizontalen Stueck? | Nein / Nein | Optionale Separatoren direkt am VF-Einlauf | +| 11 | Ist der Endpunkt der Foerderer? | Nein | Innerhalb der VF-Einheit: "geht die Kette hier zu Ende?" | +| 12-15 | Naechstes im VarioFoerderer -> Vario-Kurve 90 rechts innen | - | Grundriss-Schwenk INNERHALB der laufenden VF-Einheit | +| 16 | Ist der Endpunkt der Foerderer? | Nein | s.o. | +| 17-18 | Horizontaler Foerderer, Endpunkt | Klick | Weiteres gerades VF-Sub-Segment | +| 19-20 | Zusaetzlichen Separator VOR/NACH? | Nein / Nein | s.o. | +| 21 | Ist der Endpunkt der Foerderer? | Nein | s.o. | +| 22-25 | Vario-Kurve 90 rechts innen | - | Zweiter Grundriss-Schwenk | +| 26 | Ist der Endpunkt der Foerderer? | Ja, nur Motorstation | Schliesst die 1. VF-Einheit ab (Motorstation, kein ES) | +| 27 | Ist das Kettenende (ES-Element)? | Nein, weiterbauen | Nach der Motorstation: geht die Kette weiter? | +| 28 | Zusaetzlichen Separator hier? | Nein | Optionaler Separator direkt an der Motorstation | +| 29 | Naechstes Element | Neue Linie: GF | Jetzt folgt ein reines Gefaellestrecke-Element (kein VF) | +| 30 | Laenge bestimmen ueber | Endpunkt waehlen | Alternativ: Zielhoehe zuerst festlegen (siehe `vfl-gf-laenge-modus`) | +| 31 | Ist das Kettenende? | Nein, weiterbauen | 3-Optionen-Menue: Ja+ES / Ja ohne ES / Nein - kommt VOR der Gefaelle-/Laengenberechnung | +| 32 | Endpunkt Gefaellestrecke | Klick | Bestimmt deltaL der GF-Geraden | +| 33-34 | Separator VOR / NACH? | Ja / Ja | GF-Stuecke werden ueber Separatoren mechanisch begrenzt | +| 35-36 | Gefaelle festlegen -> Neigungswinkel | 3 Grad | Direkter Winkel (Alternative: Zielhoehe -> Winkel wird berechnet) | +| 37 | Naechstes Element | Neue VF mit horizontalem Anfang (2. Einheit) | Neue VF-Einheit beginnt direkt am GF-Ausgang (Separator als Eingang) | +| 38-55 | (wie oben: Separatoren, Vario-Kurven, Auf/Ab-Foerderer 27 Grad) | - | s.o.; Auf/Ab-Foerderer nutzt Index 8 der gueltigen Winkeltabelle-Kandidaten = 27 Grad | +| 56 | Ist der Endpunkt der Foerderer? | Ja, Motorstation + Kettenende | Startet den automatischen Kettenende-Abschluss | +| 57-59 | ES-Element setzen? / Winkel / Seite | Ja / 90 / links | Ausgangs-Element, spiegelbildlich zum AS-Element | +| 60 | Endpunkt Kettenende | Klick | Letzter gewuenschter Punkt der Kette | +| 61 | Ziel-Hoehe Kettenende | 2154 | Muss wieder exakt die Starthoehe sein | +| 62 | GF2=319mm unter Mindestlaenge (400mm) - trotzdem bauen? | Ja | Sicherheitsfrage, siehe Abschnitt 4 - Stufe-1/Stufe-2-Loeser rechnet dazwischen automatisch | -**Wichtig zu Frage 9/14/17 ("Ist der Endpunkt der Foerderer?"):** Diese Frage +**Wichtig zu "Ist der Endpunkt der Foerderer?"** (Eintraege 11/16/21/26 usw.): +Diese Frage kommt IMMER, wenn Sie innerhalb einer laufenden VF-Einheit einen Punkt gepickt haben. "Nein" heisst: der gepickte Punkt ist nur eine Zwischen-Etappe, es geht in derselben VF-Einheit weiter (Vario-Kurve, @@ -103,7 +107,7 @@ Motorstation - optional zusaetzlich mit ES-Element (echtes Kettenende). ## 4. Stufe 1 / Stufe 2 - wie die letzte Strecke berechnet wird -Am Kettenende (Frage 55/56) ist die Aufgabe: von der aktuellen Position/ +Am Kettenende (Eintrag 61/62) ist die Aufgabe: von der aktuellen Position/ Hoehe zur gewuenschten Ziel-Hoehe kommen, UND dabei die feste Baugruppe Motorstation + Separator (+ ES-Element, falls gewaehlt) unterbringen. Das Programm versucht das in zwei Stufen (`vfl-winkel-stufe-entscheidung`, @@ -112,25 +116,25 @@ Programm versucht das in zwei Stufen (`vfl-winkel-stufe-entscheidung`, - **Stufe 1 - Winkeltabelle:** Es probiert der Reihe nach die festen Vario-Kurven-Winkel 3/6/9/12/15/18/21/27/33/39/45/51 Grad durch (`berechne-alle-winkel`) und prueft, ob einer davon eine PASSENDE, - positive VF-Laenge ergibt. Das ist der Normalfall (siehe die Zeilen - "34-49" in der Tabelle: der Auf/Ab-Foerderer in der 2. VF-Einheit findet + positive VF-Laenge ergibt. Das ist der Normalfall (siehe die Zeile + "38-55" in der Tabelle: der Auf/Ab-Foerderer in der 2. VF-Einheit findet so einen passenden Winkel, Auswahl-Index 8 = 27 Grad). - **Stufe 2 - Horizontale Mitte + GF-Ausgleich:** Wenn KEIN Tabellenwinkel - passt (wie im Beispiel: alle 12 Kandidaten ergeben eine negative - VF-Laenge, die Reststrecke ist einfach zu kurz fuer einen echten + passt (wie im Beispiel am Kettenende: alle 12 Kandidaten ergeben eine + negative VF-Laenge, die Reststrecke ist einfach zu kurz fuer einen echten Vertikalbogen), rechnet `berechne-horizontale-mitte` eine andere Loesung: ein HORIZONTALES VF-Stueck (winkel=0) plus eine kurze Gefaellestrecke (GF2), die exakt den fehlenden Hoehenunterschied ausgleicht. Im Beispiel: - GF2=86mm, VF=286mm. + GF2=319mm, L_VF=48mm. Die Formel dahinter ist simpel und lohnt sich zu verinnerlichen: bei einem FESTEN Neigungswinkel von 3 Grad braucht ein Hoehenausgleich von `H` -Millimetern eine GF-Laenge von `H / tan(3 Grad) ~= H * 19`. Ein winziger -Hoehenrest von z.B. 49mm braucht demnach bereits ~935mm GF-Laenge - siehe -Abschnitt 5.a. GF2 im Beispiel (86mm) ist kuerzer als die uebliche -Mindestlaenge (400mm, `*vfl-gf-min-laenge*`) - deshalb die Rueckfrage in -Schritt 57 ("trotzdem mit 86mm bauen?"). "Ja" baut trotzdem exakt mit dem -berechneten Wert; "Nein" wuerde den bis dahin gebauten Teil verwerfen (siehe +Millimetern eine GF-Laenge von `H / tan(3 Grad) ~= H * 19`. Ein Hoehenrest +von z.B. 17mm braucht demnach bereits ~320mm GF-Laenge - siehe Abschnitt +5.a. GF2 im Beispiel (319mm) liegt knapp unter der uebliche Mindestlaenge +(400mm, `*vfl-gf-min-laenge*`) - deshalb die Rueckfrage im letzten Eintrag +("trotzdem mit 319mm bauen?"). "Ja" baut trotzdem exakt mit dem berechneten +Wert; "Nein" wuerde den bis dahin gebauten Teil verwerfen (siehe `vfl-loesche-seit`/`vfl-modus-abbruch-sichern`). **Merksatz:** Stufe 1 sucht einen "sauberen" Standard-Winkel; Stufe 2 ist @@ -170,9 +174,11 @@ Fallback ausloest. Ein AS- oder ES-Element mit 90 Grad Bauform dreht die Fahrtrichtung nicht einfach "auf der Stelle" - sein Block hat eine reale Baugroesse mit einem -seitlichen (quer zur Fahrtrichtung liegenden) Versatz. Im Standard-Testfall -(`linienzug_tests.json`) betraegt dieser Versatz ca. 637mm (`dY`), im -Stufe-2-Testfall ca. 638mm (`dX`, da die Fahrtrichtung dort anders liegt). +seitlichen (quer zur Fahrtrichtung liegenden) Versatz. Sowohl im Standard- +Testfall (`linienzug_tests.json`) als auch im Stufe-2-Testfall +(`linienzug_tests_stufe2.json`) betraegt dieser Versatz ca. 638mm (dort als +`erwartung_dx_mm` dokumentiert, da die Fahrtrichtung am Kettenende auf der +Y-Achse liegt und sich der Versatz dadurch in X statt in Y zeigt). **Das ist kein Baufehler** - es ist die tatsaechliche Geometrie des 90-Grad-ES/AS-Blocks. Ein "Ist-Ziel-Report" mit einer Restabweichung von ca. 638mm quer zur letzten Fahrtrichtung, aber ~0mm in Laengs-/Hoehenrichtung, diff --git a/lang/de_DE.json b/lang/de_DE.json index 721c443..4d76bbf 100644 --- a/lang/de_DE.json +++ b/lang/de_DE.json @@ -245,6 +245,8 @@ "vfl-m3-abweichung": "\n Abweichung: dX=%1 dY=%2 dZ=%3 mm", "vfl-m3-laengs-quer": "\n Laengs KS_AUS-Achse: %1 mm | Quer zur Achse (soll ~0): %2 mm", "vfl-m3-achse-hinweis": "\n (dZ=Ziel-Hoehe; Quer zur KS_AUS-Achse ~0 => Endpunkt liegt auf der Achse)", + "vfl-m3-ziel-soll-es-korrigiert": "\n Soll (ES-korrigiert): X=%1 Y=%2 Z=%3 (Ziel + bekannter ES-Laengsversatz)", + "vfl-m3-abweichung-es-korrigiert": "\n Abweichung (ES-korrigiert): dX=%1 dY=%2 dZ=%3 mm", "gf-fehler-block-fehlt": "\n FEHLER: Block '%1' fehlt", "gf-fehler-block-fehlt-ausruf": "\n FEHLER: Block '%1' fehlt!", "gf-fehler-blockdatei-fehlt": "\n FEHLER: Block-Datei nicht gefunden: %1", @@ -330,7 +332,6 @@ "vfc-delta-l-h-richtung": "\n %1L=%2 mm %1H=%3 mm Fahrtrichtung=%4%5", "vfc-endpunkt-xyz": "\n KS_AUS: X=%1 Y=%2 Z=%3", "vfc-endpunkt-xyz-dxdz": "\n KS_AUS: X=%1 Y=%2 Z=%3 (dx=%4 dz=%5)", - "vfc-fahrtrichtung-ergebnis": "\n>>> Fahrtrichtung: %1%2", "vfc-fehler-block-datei-fehlt": "\n FEHLER: Block-Datei nicht gefunden: %1", "vfc-fehler-block-nicht-in-bibliothek": "\n FEHLER: Block '%1' nicht in Bibliothek", "vfc-fehler-block-nicht-in-bibliothek-abgebrochen": "\n FEHLER: Block '%1' nicht in Bibliothek - abgebrochen", @@ -500,32 +501,18 @@ "vfc-alert-keine-gueltige-linie": "Keine gueltige Linie ausgewaehlt!", "vfc-bitte-3dlinie-foerderstrecke": "\nBitte 3D-Linie fuer die Foerderstrecke auswaehlen:", "vfc-bks-weltkoordinaten": "\n BKS auf Weltkoordinaten gesetzt.", + "vfc-dlg-startpunkt-kopf": "Startpunkt der Anlage waehlen (X/Y):", "vfc-eingabemodus-3dlinie": "\n 1 - 3D-Linie auswaehlen (Start- und Endpunkt mit Hoehe)", "vfc-eingabemodus-header": "\n\nEingabemodus waehlen:", "vfc-eingabemodus-werte": "\n 2 - Werte manuell eingeben", - "vfc-fahrtrichtung-0": "\n 1 - 0%1 (X-Achse +, Ost)", - "vfc-fahrtrichtung-180": "\n 3 - 180%1 (X-Achse -, West)", - "vfc-fahrtrichtung-270": "\n 4 - 270%1 (Y-Achse -, Sued)", - "vfc-fahrtrichtung-90": "\n 2 - 90%1 (Y-Achse +, Nord)", - "vfc-fahrtrichtung-header": "\n\nFahrtrichtung waehlen:", "vfc-fehler-keine-linie": "\n FEHLER: Keine Linie oder Polylinie ausgewaehlt!", "vfc-foerderrichtung-ab": "\n Foerderrichtung: AB", "vfc-foerderrichtung-auf": "Auf", - "vfc-foerderrichtung-menu": "\nFoerderrichtung:\n 1 - AUF\n 2 - AB", - "vfc-gf-verteilung-header": "\n\nVerteilung der Gefaellestrecken (vorne/hinten):", - "vfc-gf-verteilung-1": "\n 1 - Gleichmaessig (L_GF1 = L_GF2 = L_GF/2)", - "vfc-gf-verteilung-2": "\n 2 - Vorne Minimum, hinten Rest", - "vfc-gf-verteilung-3": "\n 3 - Hinten Minimum, vorne Rest", - "vfc-gf-verteilung-4": "\n 4 - Eigene Werte eingeben", "vfc-kein-objekt-gewaehlt": "\n Kein Objekt gewaehlt.", "vfc-label-von-bis": ": von %1mm bis %2mm, ", "vfc-linie-polylinie-waehlen": "\n >> 3D-Linie oder Polylinie waehlen: ", - "vfc-modus-3dlinie-fallback": "Linie", "vfc-modus-3dlinie-header": "\n\n>>> MODUS: 3D-Linie auswaehlen <<<", - "vfc-modus-werteingabe-header": "\n\n>>> MODUS: Manuelle Werteingabe <<<", "vfc-objekt-block-waehlen": "\n >> Objekt (Block) waehlen: ", - "vfc-prompt-delta-h": "\n%1H - Hoehenunterschied (mm): ", - "vfc-prompt-delta-l": "\n%1L - Horizontale Distanz (mm): ", "vfc-prompt-startpunkt-aus": "\nStartpunkt fuer AUS_Element waehlen: ", "vfc-prompt-wahl-1-2-ohne-default": "\nIhre Wahl (1/2): ", "vfc-status-ok": " OK", @@ -540,25 +527,7 @@ "vfc-prompt-wahl-1-n": "\nIhre Wahl (1-%1) [1]: ", "vfc-typ-gewaehlt": "\n>>> Typ: %1", "vfc-naechste-vf-nummer": "\n>>> Naechste VF-Nummer: VF%1", - "vfc-seite-header": "\n\nSeite waehlen:", - "vfc-seite-rechts": "\n 1 - Rechts", - "vfc-seite-links": "\n 2 - Links", - "vfc-seite-gewaehlt": "\n>>> Seite: %1", - "vfc-hinweis-horizontal-moeglich": "\n\n>>> Hinweis: Ein horizontale Variofoerderer ( 0%1) ist bei diesem %2L/%2H zusaetzlich moeglich!", "vfc-alert-kein-winkel": "Kein passender Winkel gefunden!\n%1L=%2 mm, %1H=%3 mm, Richtung=%4", - "vfc-einzige-variante-horizontal": "\n\n>>> Einzige gueltige Variante: Horizontale Variofoerderer", - "vfc-einziger-winkel": "\n\n>>> Einziger gueltiger Winkel: %1%2", - "vfc-mehrere-winkel-header": "\n\nMehrere gueltige Winkel verfuegbar. Bitte waehlen:", - "vfc-winkel-option-horizontal": "\n %1 - Horizontale Variofoerderer (0%2) (L_GF=%3 mm, L_VF=%4 mm)", - "vfc-winkel-option": "\n %1 - %2%3 (L_GF=%4 mm, L_VF=%5 mm)", - "vfc-prompt-winkel-waehlen": "Winkel waehlen (1-%1) [1]: ", - "vfc-zus-horizontal": "\n>>> Horizontale Variofoerderer L_GF: %1 mm L_VF: %2 mm", - "vfc-zus-winkel": "\n>>> Winkel: %1%2 L_GF: %3 mm L_VF: %4 mm", - "vfc-prompt-lgf1-vorne": "\nL_GF1 vorne (mm) [%1]: ", - "vfc-lgf2-hinten": "\n L_GF2 hinten: %1 mm", - "vfc-zus-lgf": "\n>>> L_GF1=%1 mm L_GF2=%2 mm", - "vfc-einfuegen-frage": "\n\nMoechten Sie die Foerderanlage einfuegen?", - "vfc-einfuegen-optionen": "\n 1 - Ja, Module einfuegen\n 2 - Nein, abbrechen", "vfc-vorgang-abgebrochen": "\nVorgang abgebrochen.", "vfs-dlg-richtung-auf": "Auf", "vfs-dlg-richtung-ab": "Ab", @@ -808,6 +777,7 @@ "gf-status-deltah-3dpunkte": "\n %1H aus 3D-Punkten: %2 mm", "gf-status-sortiere-segmente": "\n Sortiere Segmente ...", "gf-alert-keine-kette": "Keine verbundene Kette gefunden!", + "gf-alert-lz-edit-kein-dialog": "Bearbeiten einer Streckengruppe (Linienzug) braucht die GUI - bitte GUI einschalten (ssg-gui-an) und erneut versuchen.", "gf-status-segmente-gefunden": " %1 Segmente gefunden.", "gf-status-segment-analyse-header": "\n\n--- Segment-Analyse (Laengen nach Kuerzung) ---", "gf-status-segment-linie": "\n %1. Linie L=%2 mm hz=%3%4", @@ -846,6 +816,7 @@ "gf-dlg-richtung-270": "270%1 (Y-, Sued)", "gf-dlg-links": "Links", "gf-dlg-rechts": "Rechts", + "gf-dlg-linienzug-vorlauf": "Linienzug ausgewaehlt - Winkel wird nach Bestaetigung berechnet.", "gf-status-gf-block-erstellt": "\n>>> GF-Block erstellt: %1 (%2)", "gf-status-schritt1-aus": "\n\n1/4: %1 Fahrtrichtung=%2%3 KS_EIN-hz=%4%5 v=%6%7", "gf-status-schritt2-staustrecke": "\n\n2/4: Staustrecke L=%1 mm hz=%2%3 v=%4%5", @@ -946,13 +917,11 @@ "vfc-linienzug-modus-1": "\n 1 - Manuelle Eingabe (Punkte klicken)", "vfc-linienzug-modus-2": "\n 2 - 3D-Objekte + Ziel-Hoehe (Randwert-Solver, fertig)", "vfc-linienzug-modus-3": "\n 3 - 3D-Objekte stueckweise (Vorwaerts-Nachbau, in Arbeit)", - "vfc-linienzug-modus-4": "\n 4 - QC Linie mit GF (automatische Auf-Ab-Berechnung)", - "vfc-linienzug-modus-5": "\n 5 - QC Linie mit GF und U-Form", - "prompt-wahl-1-5": "\nIhre Wahl (1/2/3/4/5) [1]: ", + "prompt-wahl-1-3": "\nIhre Wahl (1/2/3) [1]: ", "vfl-qc-uform-nicht-implementiert": "\nFEHLER: 'QC Linie mit GF und U-Form' ist noch nicht implementiert - bitte Option 4 oder einen anderen Modus waehlen.", "vfc-typ-standard-beschreibung": "Standard VarioFoerderer (AS/ES 90 Grad, horizontal versetzt)", "vfc-typ-etage-beschreibung": "Etage VarioFoerderer (AS/ES 30 Grad, direkt uebereinander)", - "vfc-typ-linienzug-beschreibung": "3D-Linienzug (gemischte GF/VF-Kette; Modus 1 manuell / Modus 2 Ziel-Hoehe fertig / Modus 3 stueckweise in Arbeit)", + "vfc-typ-linienzug-beschreibung": "Streckengruppe (gemischte GF/VF-Kette; Modus 1 manuell / Modus 2 Ziel-Hoehe fertig / Modus 3 stueckweise in Arbeit)", "vfs-schritt6-horizontal": "\n\n6/11: Horizontale Zwischenstrecke (0 Grad, L=%1 mm)", "vfs-schritt6-steigung": "\n\n6/11: Steigungsstrecke (%1 Grad, L=%2 mm)", "vfs-schritt6-gefaelle": "\n\n6/11: Gefaellestrecke (%1 Grad, L=%2 mm)", @@ -1042,5 +1011,59 @@ "gr-tr-beschr-ergebnis": "\n%1 Traeger beschriftet (davon %2 ohne bekannte Variante), %3 nicht beschriftbar, %4 verwaiste Beschriftungen entfernt.", "gr-tr-beschr-keine": "\nKeine Rohr-Geruest-Traeger in der Zeichnung.", "gr-tr-strebe-rand": "Traeger %1 mm: Der Randabstand der Querstreben betraegt %2 mm und liegt ausserhalb des erlaubten Bereichs %3 bis %4 mm (Strebenabstand %5 mm, %6 Streben). Der Traeger wird trotzdem eingefuegt." + "in-yesno-keys": "Ja Nein", + "in-keys-bereich": "Alle Auswahl", + "in-keys-modus": "Teil Gesamt", + "bt-prompt-muster": "\nBlockname oder Muster (z.B. GR_*): ", + "bt-kein-block": "\nDas gewaehlte Objekt ist keine Blockeinfuegung.", + "bt-prompt-waehlen-count": "\nBlock waehlen [Name/Muster] : ", + "bt-count-keine": "\nKeine Einfuegungen von %1 gefunden.", + "bt-count-keine-alle": "\nKeine Blockeinfuegungen im aktuellen Layout.", + "bt-count-ergebnis": "\n%1 Einfuegung(en) von %2.", + "bt-count-zeile": "\n %1: %2", + "bt-count-summe": "\nGesamt: %1 Einfuegung(en) in %2 Block(s).", + "bt-prompt-waehlen-delete": "\nZu loeschenden Block waehlen [Name/Muster]: ", + "bt-delete-kein-muster": "\nKein Block gewaehlt - nichts geloescht.", + "bt-delete-alle-gesperrt": "\nAlle %1 Einfuegung(en) liegen auf gesperrten Layern - nichts geloescht.", + "bt-delete-frage": "%1 Einfuegung(en) von %2 loeschen?", + "bt-delete-titel": "Bloecke loeschen", + "bt-delete-ergebnis": "\n%1 Einfuegung(en) geloescht.", + "bt-delete-gesperrt": "\n%1 Einfuegung(en) auf gesperrten Layern blieben stehen.", + "bt-abgebrochen": "\nAbgebrochen - nichts geaendert.", + "bt-attr-bereich": "\nBereich", + "bt-attr-muster": "\nBlockname-Muster", + "bt-attr-auswahl": "\nBloecke waehlen: ", + "bt-attr-keine-bloecke": "\nKeine passenden Blockeinfuegungen gefunden.", + "bt-attr-tag": "\nAttribut-Tag (Muster)", + "bt-attr-suche": "\nSuchen nach", + "bt-attr-ersatz": "\nErsetzen durch", + "bt-attr-modus": "\nErsetzen als", + "bt-attr-nichts": "\nKein Attributwert enthaelt den Suchtext.", + "bt-attr-frage": "%1 Attributwert(e) in %2 Block(s) aendern?", + "bt-attr-titel": "Attribute ersetzen", + "bt-attr-ergebnis": "\n%1 Attributwert(e) geaendert.", + "tt-bereich": "\nBereich", + "tt-auswahl": "\nObjekte waehlen: ", + "tt-keine-objekte": "\nKeine passenden Objekte gefunden.", + "tt-suche": "\nSuchen nach", + "tt-ersatz": "\nErsetzen durch", + "tt-modus": "\nErsetzen als", + "tt-nichts": "\nKein Text und kein Attributwert enthaelt den Suchtext.", + "tt-frage": "%1 Text(e) und %2 Attributwert(e) aendern?", + "tt-ersetzen-titel": "Text ersetzen", + "tt-ergebnis": "\n%1 Text(e) und %2 Attributwert(e) geaendert.", + "tt-abgebrochen": "\nAbgebrochen - nichts geaendert.", + "tt-parallel-titel": "Text parallel beschriften", + "tt-hoehe": "\nTexthoehe", + "tt-abstand": "\nAbstand zur Linie", + "tt-parallel-waehlen": "\nLinie, Polylinie oder Bogen waehlen (Enter = Ende): ", + "tt-parallel-keine-kurve": "\nWinkel am gewaehlten Objekt nicht ermittelbar.", + "tt-text": "\nText", + "tt-parallel-fehler": "\nText konnte nicht erzeugt werden.", + "tt-hoehe-titel": "Texthoehe aendern", + "tt-geaendert": "\n%1 Text(e) geaendert.", + "tt-stil": "\nTextstil", + "tt-stil-fehlt": "\nTextstil %1 existiert nicht in der Zeichnung.", + "tt-stil-titel": "Textstil aendern" } } diff --git a/lang/en_GB.json b/lang/en_GB.json index 7a94884..78f8f2e 100644 --- a/lang/en_GB.json +++ b/lang/en_GB.json @@ -245,6 +245,8 @@ "vfl-m3-abweichung": "\n Deviation: dX=%1 dY=%2 dZ=%3 mm", "vfl-m3-laengs-quer": "\n Along KS_AUS axis: %1 mm | across axis (should be ~0): %2 mm", "vfl-m3-achse-hinweis": "\n (dZ=target height; across KS_AUS axis ~0 => end point lies on the axis)", + "vfl-m3-ziel-soll-es-korrigiert": "\n Target (ES-corrected): X=%1 Y=%2 Z=%3 (target + known ES lateral offset)", + "vfl-m3-abweichung-es-korrigiert": "\n Deviation (ES-corrected): dX=%1 dY=%2 dZ=%3 mm", "gf-fehler-block-fehlt": "\n ERROR: block '%1' missing", "gf-fehler-block-fehlt-ausruf": "\n ERROR: block '%1' missing!", "gf-fehler-blockdatei-fehlt": "\n ERROR: block file not found: %1", @@ -330,7 +332,6 @@ "vfc-delta-l-h-richtung": "\n %1L=%2 mm %1H=%3 mm Travel direction=%4%5", "vfc-endpunkt-xyz": "\n KS_AUS: X=%1 Y=%2 Z=%3", "vfc-endpunkt-xyz-dxdz": "\n KS_AUS: X=%1 Y=%2 Z=%3 (dx=%4 dz=%5)", - "vfc-fahrtrichtung-ergebnis": "\n>>> Travel direction: %1%2", "vfc-fehler-block-datei-fehlt": "\n ERROR: block file not found: %1", "vfc-fehler-block-nicht-in-bibliothek": "\n ERROR: block '%1' not in library", "vfc-fehler-block-nicht-in-bibliothek-abgebrochen": "\n ERROR: block '%1' not in library - cancelled", @@ -500,32 +501,18 @@ "vfc-alert-keine-gueltige-linie": "No valid line selected!", "vfc-bitte-3dlinie-foerderstrecke": "\nPlease select a 3D line for the conveyor section:", "vfc-bks-weltkoordinaten": "\n UCS set to world coordinates.", + "vfc-dlg-startpunkt-kopf": "Select start point of the system (X/Y):", "vfc-eingabemodus-3dlinie": "\n 1 - Select 3D line (start and end point with height)", "vfc-eingabemodus-header": "\n\nSelect input mode:", "vfc-eingabemodus-werte": "\n 2 - Enter values manually", - "vfc-fahrtrichtung-0": "\n 1 - 0%1 (X axis +, East)", - "vfc-fahrtrichtung-180": "\n 3 - 180%1 (X axis -, West)", - "vfc-fahrtrichtung-270": "\n 4 - 270%1 (Y axis -, South)", - "vfc-fahrtrichtung-90": "\n 2 - 90%1 (Y axis +, North)", - "vfc-fahrtrichtung-header": "\n\nSelect travel direction:", "vfc-fehler-keine-linie": "\n ERROR: No line or polyline selected!", "vfc-foerderrichtung-ab": "\n Conveying direction: DOWN", "vfc-foerderrichtung-auf": "Up", - "vfc-foerderrichtung-menu": "\nConveying direction:\n 1 - UP\n 2 - DOWN", - "vfc-gf-verteilung-header": "\n\nDistribution of slope sections (front/rear):", - "vfc-gf-verteilung-1": "\n 1 - Even (L_GF1 = L_GF2 = L_GF/2)", - "vfc-gf-verteilung-2": "\n 2 - Front minimum, rear remainder", - "vfc-gf-verteilung-3": "\n 3 - Rear minimum, front remainder", - "vfc-gf-verteilung-4": "\n 4 - Enter custom values", "vfc-kein-objekt-gewaehlt": "\n No object selected.", "vfc-label-von-bis": ": from %1mm to %2mm, ", "vfc-linie-polylinie-waehlen": "\n >> Select 3D line or polyline: ", - "vfc-modus-3dlinie-fallback": "Line", "vfc-modus-3dlinie-header": "\n\n>>> MODE: Select 3D line <<<", - "vfc-modus-werteingabe-header": "\n\n>>> MODE: Manual value entry <<<", "vfc-objekt-block-waehlen": "\n >> Select object (block): ", - "vfc-prompt-delta-h": "\n%1H - height difference (mm): ", - "vfc-prompt-delta-l": "\n%1L - horizontal distance (mm): ", "vfc-prompt-startpunkt-aus": "\nSelect start point for AUS_Element: ", "vfc-prompt-wahl-1-2-ohne-default": "\nYour choice (1/2): ", "vfc-status-ok": " OK", @@ -540,25 +527,7 @@ "vfc-prompt-wahl-1-n": "\nYour choice (1-%1) [1]: ", "vfc-typ-gewaehlt": "\n>>> Type: %1", "vfc-naechste-vf-nummer": "\n>>> Next VF number: VF%1", - "vfc-seite-header": "\n\nSelect side:", - "vfc-seite-rechts": "\n 1 - Right", - "vfc-seite-links": "\n 2 - Left", - "vfc-seite-gewaehlt": "\n>>> Side: %1", - "vfc-hinweis-horizontal-moeglich": "\n\n>>> Note: a horizontal VarioFoerderer ( 0%1) is additionally possible for this %2L/%2H!", "vfc-alert-kein-winkel": "No suitable angle found!\n%1L=%2 mm, %1H=%3 mm, direction=%4", - "vfc-einzige-variante-horizontal": "\n\n>>> Only valid variant: horizontal VarioFoerderer", - "vfc-einziger-winkel": "\n\n>>> Only valid angle: %1%2", - "vfc-mehrere-winkel-header": "\n\nSeveral valid angles available. Please choose:", - "vfc-winkel-option-horizontal": "\n %1 - Horizontal VarioFoerderer (0%2) (L_GF=%3 mm, L_VF=%4 mm)", - "vfc-winkel-option": "\n %1 - %2%3 (L_GF=%4 mm, L_VF=%5 mm)", - "vfc-prompt-winkel-waehlen": "Select angle (1-%1) [1]: ", - "vfc-zus-horizontal": "\n>>> Horizontal VarioFoerderer L_GF: %1 mm L_VF: %2 mm", - "vfc-zus-winkel": "\n>>> Angle: %1%2 L_GF: %3 mm L_VF: %4 mm", - "vfc-prompt-lgf1-vorne": "\nL_GF1 front (mm) [%1]: ", - "vfc-lgf2-hinten": "\n L_GF2 rear: %1 mm", - "vfc-zus-lgf": "\n>>> L_GF1=%1 mm L_GF2=%2 mm", - "vfc-einfuegen-frage": "\n\nDo you want to insert the conveyor system?", - "vfc-einfuegen-optionen": "\n 1 - Yes, insert modules\n 2 - No, cancel", "vfc-vorgang-abgebrochen": "\nProcess cancelled.", "vfs-dlg-richtung-auf": "Up", "vfs-dlg-richtung-ab": "Down", @@ -808,6 +777,7 @@ "gf-status-deltah-3dpunkte": "\n %1H from 3D points: %2 mm", "gf-status-sortiere-segmente": "\n Sorting segments ...", "gf-alert-keine-kette": "No connected chain found!", + "gf-alert-lz-edit-kein-dialog": "Editing a chain group (path-built) requires the GUI - please enable the GUI (ssg-gui-an) and try again.", "gf-status-segmente-gefunden": " %1 segments found.", "gf-status-segment-analyse-header": "\n\n--- Segment analysis (lengths after trimming) ---", "gf-status-segment-linie": "\n %1. Line L=%2 mm hz=%3%4", @@ -846,6 +816,7 @@ "gf-dlg-richtung-270": "270%1 (Y-, South)", "gf-dlg-links": "Left", "gf-dlg-rechts": "Right", + "gf-dlg-linienzug-vorlauf": "Path selected - angle will be calculated after confirmation.", "gf-status-gf-block-erstellt": "\n>>> GF block created: %1 (%2)", "gf-status-schritt1-aus": "\n\n1/4: %1 travel dir=%2%3 KS_EIN-hz=%4%5 v=%6%7", "gf-status-schritt2-staustrecke": "\n\n2/4: Buffer section L=%1 mm hz=%2%3 v=%4%5", @@ -946,13 +917,11 @@ "vfc-linienzug-modus-1": "\n 1 - Manual entry (click points)", "vfc-linienzug-modus-2": "\n 2 - 3D objects + target height (boundary-value solver, finished)", "vfc-linienzug-modus-3": "\n 3 - 3D objects step by step (forward reconstruction, work in progress)", - "vfc-linienzug-modus-4": "\n 4 - QC line with GF (automatic up/down calculation)", - "vfc-linienzug-modus-5": "\n 5 - QC line with GF and U-shape", - "prompt-wahl-1-5": "\nYour choice (1/2/3/4/5) [1]: ", + "prompt-wahl-1-3": "\nYour choice (1/2/3) [1]: ", "vfl-qc-uform-nicht-implementiert": "\nERROR: 'QC line with GF and U-shape' is not yet implemented - please choose option 4 or a different mode.", "vfc-typ-standard-beschreibung": "Standard VarioFoerderer (AS/ES 90 degrees, offset horizontally)", "vfc-typ-etage-beschreibung": "Etage VarioFoerderer (AS/ES 30 degrees, directly stacked)", - "vfc-typ-linienzug-beschreibung": "3D line path (mixed GF/VF chain; mode 1 manual / mode 2 target height finished / mode 3 step by step work in progress)", + "vfc-typ-linienzug-beschreibung": "Route group (mixed GF/VF chain; mode 1 manual / mode 2 target height finished / mode 3 step by step work in progress)", "vfs-schritt6-horizontal": "\n\n6/11: Horizontal intermediate section (0 deg, L=%1 mm)", "vfs-schritt6-steigung": "\n\n6/11: Incline section (%1 deg, L=%2 mm)", "vfs-schritt6-gefaelle": "\n\n6/11: Slope section (%1 deg, L=%2 mm)", @@ -1042,5 +1011,59 @@ "gr-tr-beschr-ergebnis": "\n%1 beam(s) labelled (%2 without known variant), %3 not labellable, %4 orphaned label(s) removed.", "gr-tr-beschr-keine": "\nNo pipe scaffold beams in the drawing.", "gr-tr-strebe-rand": "Beam %1 mm: the edge distance of the cross braces is %2 mm, outside the allowed range %3 to %4 mm (brace spacing %5 mm, %6 braces). The beam is inserted anyway." + "in-yesno-keys": "Yes No", + "in-keys-bereich": "All Selection", + "in-keys-modus": "Part Whole", + "bt-prompt-muster": "\nBlock name or pattern (e.g. GR_*): ", + "bt-kein-block": "\nThe selected object is not a block reference.", + "bt-prompt-waehlen-count": "\nSelect block [Name/pattern] : ", + "bt-count-keine": "\nNo references of %1 found.", + "bt-count-keine-alle": "\nNo block references in the current layout.", + "bt-count-ergebnis": "\n%1 reference(s) of %2.", + "bt-count-zeile": "\n %1: %2", + "bt-count-summe": "\nTotal: %1 reference(s) in %2 block(s).", + "bt-prompt-waehlen-delete": "\nSelect block to delete [Name/pattern]: ", + "bt-delete-kein-muster": "\nNo block selected - nothing deleted.", + "bt-delete-alle-gesperrt": "\nAll %1 reference(s) are on locked layers - nothing deleted.", + "bt-delete-frage": "Delete %1 reference(s) of %2?", + "bt-delete-titel": "Delete blocks", + "bt-delete-ergebnis": "\n%1 reference(s) deleted.", + "bt-delete-gesperrt": "\n%1 reference(s) on locked layers were kept.", + "bt-abgebrochen": "\nCancelled - nothing changed.", + "bt-attr-bereich": "\nScope", + "bt-attr-muster": "\nBlock name pattern", + "bt-attr-auswahl": "\nSelect blocks: ", + "bt-attr-keine-bloecke": "\nNo matching block references found.", + "bt-attr-tag": "\nAttribute tag (pattern)", + "bt-attr-suche": "\nFind", + "bt-attr-ersatz": "\nReplace with", + "bt-attr-modus": "\nReplace as", + "bt-attr-nichts": "\nNo attribute value contains the search text.", + "bt-attr-frage": "Change %1 attribute value(s) in %2 block(s)?", + "bt-attr-titel": "Replace attributes", + "bt-attr-ergebnis": "\n%1 attribute value(s) changed.", + "tt-bereich": "\nScope", + "tt-auswahl": "\nSelect objects: ", + "tt-keine-objekte": "\nNo matching objects found.", + "tt-suche": "\nFind", + "tt-ersatz": "\nReplace with", + "tt-modus": "\nReplace as", + "tt-nichts": "\nNo text or attribute value contains the search text.", + "tt-frage": "Change %1 text(s) and %2 attribute value(s)?", + "tt-ersetzen-titel": "Replace text", + "tt-ergebnis": "\n%1 text(s) and %2 attribute value(s) changed.", + "tt-abgebrochen": "\nCancelled - nothing changed.", + "tt-parallel-titel": "Label parallel to line", + "tt-hoehe": "\nText height", + "tt-abstand": "\nDistance to line", + "tt-parallel-waehlen": "\nSelect line, polyline or arc (Enter = finish): ", + "tt-parallel-keine-kurve": "\nCannot determine the angle on the selected object.", + "tt-text": "\nText", + "tt-parallel-fehler": "\nText could not be created.", + "tt-hoehe-titel": "Change text height", + "tt-geaendert": "\n%1 text(s) changed.", + "tt-stil": "\nText style", + "tt-stil-fehlt": "\nText style %1 does not exist in the drawing.", + "tt-stil-titel": "Change text style" } } diff --git a/lib/dxf_anlage_export.py b/lib/dxf_anlage_export.py new file mode 100644 index 0000000..62ad022 --- /dev/null +++ b/lib/dxf_anlage_export.py @@ -0,0 +1,413 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +dxf_anlage_export.py - Eine mit unseren Makros GEBAUTE (und von Hand +nachbearbeitete) Anlage aus der Zeichnung zurueck in Nachbau-Testdaten +schreiben. + +Gegenstueck zu lib/dxf_scan_components.py: der Scanner liest FREMDE +Zeichnungen (Rohbloecke, Linien) und muss vieles raten. Hier stehen unsere +Wrapper-Bloecke in der Zeichnung, und jeder traegt seine Bau-Parameter +selbst - als Attribute und XDATA, genau die Werte, die auch der jeweilige +Edit-Befehl (Doppelklick) zum Neuaufbau liest. Typischer Ablauf: Scanner- +Vorlage nachbauen, in BricsCAD von Hand korrigieren/ergaenzen, Zeichnung als +DXF speichern und mit diesem Skript die Testdaten aktualisieren. + +Je Wrapper (Felder wie in tests/testdata/object_data.md bzw. mubea.json): + + KREISEL_n -> Kreisel ("function": "insert", x/y/z = Einfuegepunkt, + abstand = Mittenabstand AN8-SP8 aus der Blockdefinition, + rotation, typ = KREISELART, drehrichtung = DREHRICHTUNG) + GF_n -> Gefaellestrecke Modus 1 (XDATA SSG_GF_EDIT wie + gf-xdata-lesen + SEITE_AS/SEITE_ES wie GEFAELLESTRECKE_EDIT). + Endet die Strecke auf der Kette eines Kreisels, wird statt + des gebauten Winkels dessen Hoehe als hoehe_ende_mm + uebernommen - der Treiber rechnet den Winkel daraus neu. + VF_n "standard"/"etage" + -> VarioFoerderer im mubea-Schema (Parameter wie + vfs-edit-basis-lesen: Einfuegepunkt, hz aus XDATA, DELTA_L/ + DELTA_H/ANTRIEBFAHRTRICHTUNG/SEITE_AS/MOTORSEITE, dazu + VF_WINKEL und L_GF1 aus L_GF_m) + VF_n "linienzug" + -> das Eingabe-Journal UNVERAENDERT ("typ": + "linienzug_journal"). Bewusst KEINE Spec: der Bau-Dialog ist + neuer als die Spec-Grammatik (vf_linienzug_grammatik.py, + vf_spec.lsp), eine Uebersetzung wuerde desynchronisieren. + Der Treiber spielt das Journal per vsp-bau-journal ab. + sonstige Bloecke (Separator, Scanner, ...) + -> Einzel-Insert ("function": "insert", "block" ohne + _2D/_3D-Suffix, damit die Dimension der Sitzung gilt) + +Nicht uebernommen (gemeldet): "linienzug2" (Modus 2 - Journal verweist auf +Pfad-Objekte der Zeichnung per Handle) und Bloecke ohne Bau-Parameter. + +Aufruf: + python dxf_anlage_export.py [--projekt NAME] + +Beispiel (Dematic/Arvato, Nachbau-Daten fuer TEST_DEMATIC_ARVATO): + python lib/dxf_anlage_export.py C:/develop/sps_skel/data/Dematic_TST.dxf \\ + tests/testdata/dematic_arvato.json --projekt Arvato + +Bewusst OHNE ezdxf: reine Gruppencode-Lesung (vf_journal_export.gruppen), +ASCII-DXF. +""" + +import argparse +import collections +import json +import math +import os +import re +import sys + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from vf_journal_export import TRENNER, gruppen # noqa: E402 +from dxf_scan_components import arrays_einzeilig, kreisel_anschluss # noqa: E402 +import testdata_schema as sch # noqa: E402 + +PREFIX = "[dxf_anlage_export]" +RUND = 2 + +# Kreisel-Durchmesser (Radmitte bis Kette), wie component_defaults.json. +KREISEL_RADIUS = 400.0 + +# Bis zu diesem Abstand (mm) gilt ein GF-Ende als auf der Kette eines +# Kreisels (der GF-Bau legt AS-/ES-Ursprung genau auf die Kette). +GF_KREISEL_TOL = 50.0 + +DIM_SUFFIX = re.compile(r"_(2D|3D)$") + + +# ============================================================ +# DXF lesen +# ============================================================ + +def _float(v): + try: + return float(v) + except (TypeError, ValueError): + return 0.0 + + +def lese_zeichnung(dxf_pfad): + """Modelspace-INSERTs (mit Attributen und XDATA je App) und die INSERTs + jeder Blockdefinition. + + Rueckgabe: (inserts, block_inhalt) + inserts - Liste {name, handle, p, rot, attribs, xdata}; xdata ist + app -> Liste der 1000er-Werte in Reihenfolge + block_inhalt - blockname -> Liste {name, p} der direkt enthaltenen + INSERTs (fuer den Kreisel-Mittenabstand) + """ + paare = gruppen(dxf_pfad) + inserts = [] + block_inhalt = {} + section = None + block = None + i, n = 0, len(paare) + while i < n: + code, wert = paare[i] + if code != "0": + i += 1 + continue + if wert == "SECTION" and i + 1 < n: + section = paare[i + 1][1].strip() + elif wert == "ENDSEC": + section = None + elif wert == "BLOCK": + j = i + 1 + while j < n and paare[j][0] != "0": + if paare[j][0] == "2": + block = paare[j][1] + break + j += 1 + elif wert == "ENDBLK": + block = None + elif wert == "INSERT" and section in ("ENTITIES", "BLOCKS"): + e = {"name": None, "handle": None, "p": [0.0, 0.0, 0.0], + "rot": 0.0, "attribs": {}, "xdata": {}, "ps": 0} + app = None + j = i + 1 + while j < n and paare[j][0] != "0": + c, v = paare[j] + if c == "2" and e["name"] is None: + e["name"] = v + elif c == "5" and e["handle"] is None: + e["handle"] = v + elif c in ("10", "20", "30") and app is None: + e["p"][{"10": 0, "20": 1, "30": 2}[c]] = _float(v) + elif c == "50": + e["rot"] = _float(v) + elif c == "67": + e["ps"] = int(_float(v)) + elif c == "1001": + app = v + e["xdata"][app] = [] + elif c == "1000" and app: + e["xdata"][app].append(v) + j += 1 + # Attribute: ATTRIB-Entities bis SEQEND (Wert Code 1, Tag Code 2) + while j < n and paare[j] == ("0", "ATTRIB"): + k = j + 1 + tag = val = None + while k < n and paare[k][0] != "0": + if paare[k][0] == "2" and tag is None: + tag = paare[k][1] + elif paare[k][0] == "1" and val is None: + val = paare[k][1] + k += 1 + if tag: + e["attribs"][tag] = val if val is not None else "" + j = k + if section == "ENTITIES": + if e["ps"] != 1: + inserts.append(e) + elif block: + block_inhalt.setdefault(block, []).append( + {"name": e["name"], "p": e["p"]}) + i = j + continue + i += 1 + return inserts, block_inhalt + + +# ============================================================ +# Wrapper -> Eintraege +# ============================================================ + +def _nummer(name): + m = re.search(r"_(\d+)", name) + return int(m.group(1)) if m else 0 + + +def _r(v): + return round(float(v), RUND) + 0.0 + + +def _richtung(hz): + rad = math.radians(hz) + return (math.cos(rad), math.sin(rad)) + + +def kreisel_eintrag(e, block_inhalt, projekt): + """Kreisel wie hundm05:bau-kreisel ihn erwartet.""" + a = e["attribs"] + abstand = _float(a.get("ABSTAND")) + quelle = "Attribut ABSTAND (ganze mm)" + # Genauer aus der Blockdefinition: Mittenabstand der beiden Stationen + stationen = [s for s in block_inhalt.get(e["name"], []) + if s["name"] and s["name"].upper().startswith(("AN8", "SP8"))] + an = [s for s in stationen if s["name"].upper().startswith("AN8")] + sp = [s for s in stationen if s["name"].upper().startswith("SP8")] + if an and sp: + abstand = math.hypot(sp[0]["p"][0] - an[0]["p"][0], + sp[0]["p"][1] - an[0]["p"][1]) + quelle = "Blockdefinition (AN8 -> SP8)" + nr = int(_float(a.get("NUMMER"))) or _nummer(e["name"]) + return collections.OrderedDict([ + (sch.F_ID, "Kreisel_%s_%02d" % (projekt, nr)), + (sch.F_FUNCTION, sch.FUNC_INSERT), + (sch.F_X, _r(e["p"][0])), (sch.F_Y, _r(e["p"][1])), (sch.F_Z, _r(e["p"][2])), + (sch.F_ABSTAND, _r(abstand)), + (sch.F_ROTATION, _r(e["rot"])), + (sch.F_TYP, a.get("KREISELART") or sch.TYP_STANDARD), + # Umlaufsinn: bestimmt die Reihenfolge im Kreisel-Umlauf und damit den + # TRO-Fluss - ohne ihn kam jeder Nachbau als UZS heraus (Dematic 1/4: + # GUZ). hundm05:bau-kreisel reicht ihn an kreisel-insert-script weiter. + (sch.F_DREHRICHTUNG, a.get("DREHRICHTUNG") or sch.DREHRICHTUNG_UZS), + (sch.F_COMMENT, "aus Zeichnung: %s (ID %s), abstand aus %s" + % (e["name"], a.get("ID", "?"), quelle)), + ]) + + +def kreisel_geo(kreisel): + """kreisel_geo-Form (siehe dxf_scan_components.kreisel_anschluss) aus + einem Kreisel-Eintrag: Achse ab AN8-Radmitte = Einfuegepunkt + Radius.""" + u = _richtung(kreisel["rotation"]) + return {"id": kreisel["id"], + "a": (kreisel["x"] + KREISEL_RADIUS * u[0], + kreisel["y"] + KREISEL_RADIUS * u[1]), + "u": u, "laenge": kreisel["abstand"], "r": KREISEL_RADIUS, + "z": kreisel["z"]} + + +def kreisel_an_punkt(p, richtung, geo): + """Kreisel, auf dessen Kette p liegt (oder None).""" + ans = kreisel_anschluss(p, richtung, geo) + if ans and abs(ans[2]) <= GF_KREISEL_TOL: + return ans[0] + return None + + +def gf_eintrag(e, geo, projekt, hinweise): + """Gefaellestrecke Modus 1 - Felder wie GEFAELLESTRECKE_EDIT sie liest.""" + a = e["attribs"] + xd = e["xdata"].get("SSG_GF_EDIT") + if not xd or len(xd) < 6: + hinweise.append("%s ohne SSG_GF_EDIT-XDATA - uebersprungen" % e["name"]) + return None + hz, winkel = _float(xd[0]), _float(xd[1]) + z_start, deltaL = _float(xd[4]), _float(xd[5]) + start = (e["p"][0], e["p"][1]) + r = _richtung(hz) + ende = (start[0] + deltaL * r[0], start[1] + deltaL * r[1]) + eintrag = collections.OrderedDict([ + (sch.F_TEST_ID, "GF_%s_%02d" % (projekt, _nummer(e["name"]))), + (sch.F_MODUS, sch.GF_MODUS_WERTEINGABE), + (sch.F_DELTAL_MM, _r(deltaL)), + (sch.F_HZ_GRAD, _r(hz)), + (sch.F_START_MM, [_r(start[0]), _r(start[1]), _r(z_start)]), + (sch.F_AS_SEITE, a.get("SEITE_AS") or sch.SEITE_LINKS), + (sch.F_ES_SEITE, a.get("SEITE_ES") or sch.SEITE_LINKS), + (sch.F_AS_WINKEL, xd[2] or sch.SCHLEUS_WINKEL_90), + (sch.F_ES_WINKEL, xd[3] or sch.SCHLEUS_WINKEL_90), + ]) + k_start = kreisel_an_punkt(start, r, geo) + k_ende = kreisel_an_punkt(ende, (-r[0], -r[1]), geo) + if k_start: + eintrag["kreisel_start"] = k_start["id"] + if k_ende: + eintrag["kreisel_ende"] = k_ende["id"] + eintrag["hoehe_ende_mm"] = k_ende["z"] + kommentar = ("Endhoehe = Hoehe von %s (%.1f), Winkel rechnet der " + "Treiber neu; gebaut war %.4f Grad -> HOEHE_BIS %s" + % (k_ende["id"], k_ende["z"], winkel, + a.get("HOEHE_BIS_mm", "?"))) + else: + eintrag["winkel_grad"] = round(winkel, 4) + kommentar = "kein Kreisel am Ende - gebauter Winkel uebernommen" + eintrag[sch.F_COMMENT] = "aus Zeichnung: %s (ID %s); %s" % ( + e["name"], a.get("ID", "?"), kommentar) + return eintrag + + +def vf_standard_eintrag(e, projekt, typ): + """VarioFoerderer im mubea-Schema (mubea:vario-bauen).""" + a = e["attribs"] + xd = e["xdata"].get("SSG_VF_EDIT") or [] + l_gf = [t for t in (a.get("L_GF_m") or "").split(",") if t.strip()] + eintrag = collections.OrderedDict([ + (sch.F_TEST_ID, "VF_%s_%02d" % (projekt, _nummer(e["name"]))), + (sch.F_VF_TYP, typ), + (sch.F_RICHTUNG, a.get("ANTRIEBFAHRTRICHTUNG") or sch.RICHTUNG_AUF), + (sch.F_DELTA_L, _r(_float(a.get("DELTA_L_mm")))), + (sch.F_DELTA_H, _r(_float(a.get("DELTA_H_mm")))), + (sch.F_WINKEL, int(_float(a.get("VF_WINKEL")))), + (sch.F_L_GF1, _r(1000.0 * _float(l_gf[0])) if l_gf else 0.0), + (sch.F_SEITE, a.get("SEITE_AS") or sch.SEITE_RECHTS), + (sch.F_MOTORSEITE, (a.get("MOTORSEITE") or sch.SEITE_RECHTS).split(",")[0]), + (sch.F_HZ, _r(_float(xd[1]) if len(xd) > 1 else 0.0)), + (sch.F_X, _r(e["p"][0])), (sch.F_Y, _r(e["p"][1])), (sch.F_Z, _r(e["p"][2])), + (sch.F_COMMENT, "aus Zeichnung: %s (ID %s), Hoehe %s -> %s; deltaL/" + "deltaH aus den (ganzzahligen) Attributen" + % (e["name"], a.get("ID", "?"), a.get("HOEHE_VON_mm", "?"), + a.get("HOEHE_BIS_mm", "?"))), + ]) + return eintrag + + +def vf_journal_eintrag(e, projekt, nr): + """Linienzug: Journal unveraendert (Tokens 'ART:wert').""" + a = e["attribs"] + roh = "".join(e["xdata"]["SSG_VF_EDIT"][1:]) + tokens = [t for t in roh.split(TRENNER) if t] + return collections.OrderedDict([ + (sch.F_TEST_ID, "VF_%s_LZ_%02d" % (projekt, nr)), + (sch.F_VF_TYP, "linienzug_journal"), + ("journal", tokens), + (sch.F_COMMENT, "aus Zeichnung: %s (ID %s), %d Eingaben, Hoehe %s -> %s; " + "wird 1:1 abgespielt (vsp-bau-journal)" + % (e["name"], a.get("ID", "?"), len(tokens), + a.get("HOEHE_VON_mm", "?"), a.get("HOEHE_BIS_mm", "?"))), + ]) + + +def einzel_eintrag(e): + a = e["attribs"] + info = ", ".join("%s %s" % (k, a[k]) for k in ("ID", "ZUORDNUNG", "ARTINR") + if a.get(k)) + return collections.OrderedDict([ + (sch.F_FUNCTION, sch.FUNC_INSERT), + (sch.F_BLOCK, DIM_SUFFIX.sub("", e["name"])), + (sch.F_X, _r(e["p"][0])), (sch.F_Y, _r(e["p"][1])), (sch.F_Z, _r(e["p"][2])), + (sch.F_ROTATION, _r(e["rot"])), + (sch.F_COMMENT, "aus Zeichnung: %s%s" % (e["name"], + (" (%s)" % info) if info else "")), + ]) + + +def baue_eintraege(inserts, block_inhalt, projekt): + """Reihenfolge wie mubea.json: Kreisel, VF, GF, Einzelbloecke - die VF + vor den GF, weil gf-init-bibliothek globale Elementmasse umsetzt.""" + kreisel, vf, lz, gf, einzel, hinweise = [], [], [], [], [], [] + for e in sorted(inserts, key=lambda e: (e["name"] or "", e["p"][0], e["p"][1])): + name = e["name"] or "" + if name.startswith("KREISEL_"): + kreisel.append(kreisel_eintrag(e, block_inhalt, projekt)) + geo = [kreisel_geo(k) for k in kreisel] + for e in inserts: + name = e["name"] or "" + if name.startswith("KREISEL_"): + continue + if name.startswith("GF_"): + g = gf_eintrag(e, geo, projekt, hinweise) + if g: + gf.append(g) + elif name.startswith("VF_"): + xd = e["xdata"].get("SSG_VF_EDIT") + marker = xd[0] if xd else None + if marker in ("standard", "etage"): + vf.append(vf_standard_eintrag(e, projekt, marker)) + elif marker == "linienzug": + lz.append(vf_journal_eintrag(e, projekt, len(lz) + 1)) + else: + hinweise.append("%s: Marker %r nicht nachbaubar - uebersprungen" + % (name, marker)) + elif name.startswith("*") or name.startswith("ZZ_"): + continue # anonyme Bloecke, Export-Marker + else: + einzel.append(einzel_eintrag(e)) + gf.sort(key=lambda g: g["test_id"]) + vf.sort(key=lambda v: v["test_id"]) + kreisel.sort(key=lambda k: k["id"]) + return kreisel + vf + lz + gf + einzel, hinweise + + +def main(): + p = argparse.ArgumentParser( + description="Gebaute Anlage (Wrapper-Bloecke) aus einer Zeichnung in " + "Nachbau-Testdaten schreiben.") + p.add_argument("dxf", help="Zeichnung (ASCII-DXF)") + p.add_argument("output", help="Ziel-JSON") + p.add_argument("--projekt", help="Kennung in den IDs (Default: Dateiname)") + args = p.parse_args() + if not os.path.isfile(args.dxf): + print("%s FEHLER: DXF nicht gefunden: %s" % (PREFIX, args.dxf)) + sys.exit(1) + projekt = args.projekt or os.path.splitext(os.path.basename(args.dxf))[0] + + print("%s lese %s ..." % (PREFIX, args.dxf)) + inserts, block_inhalt = lese_zeichnung(args.dxf) + eintraege, hinweise = baue_eintraege(inserts, block_inhalt, projekt) + + zaehler = collections.Counter( + "Kreisel" if sch.F_ABSTAND in e else + "GF" if sch.F_MODUS in e else + "VF Linienzug" if e.get(sch.F_VF_TYP) == "linienzug_journal" else + "VF %s" % e[sch.F_VF_TYP] if sch.F_VF_TYP in e and sch.F_TEST_ID in e else + e.get(sch.F_BLOCK, "?") for e in eintraege) + for k, v in sorted(zaehler.items()): + print("%s %-22s %d" % (PREFIX, k, v)) + for h in hinweise: + print("%s ! %s" % (PREFIX, h)) + + with open(args.output, "w", encoding="utf-8") as fh: + fh.write(arrays_einzeilig(json.dumps(eintraege, ensure_ascii=False, + indent=2))) + fh.write("\n") + print("%s %d Eintraege geschrieben -> %s" + % (PREFIX, len(eintraege), args.output)) + + +if __name__ == "__main__": + main() diff --git a/lib/dxf_scan_components.py b/lib/dxf_scan_components.py index 2cb37f2..fc03960 100644 --- a/lib/dxf_scan_components.py +++ b/lib/dxf_scan_components.py @@ -11,6 +11,11 @@ also so, dass die Anlage per JSON-Treiber mit den Produktionsmakros nachgebaut werden kann (Vorbild: tests/testdata/mubea.json, das bisher von Hand aus einem DXF abgeleitet wurde). +Gefaellestrecken, die nicht als Blockpaar, sondern als 3D-LINE auf einem +Staustrecken-Layer gezeichnet sind (GF_LINIE_LAYER), werden in einem zweiten +Lesedurchgang erkannt und als Modus-1-Eintraege (Schema 2.1) ausgegeben, +siehe gf_eintraege_aus_linien. + Aufruf: python dxf_scan_components.py [--fenster X0 Y0 X1 Y1] @@ -44,6 +49,7 @@ import struct import sys from ezdxf.lldxf.types import BINARY_DATA, INT16, INT32, INT64, DOUBLE, BYTES +from ezdxf.lldxf.types import cast_tag_value from ezdxf.math import Matrix44, OCS sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) @@ -62,6 +68,10 @@ MAX_PAAR_ABSTAND = 60000.0 # 10 mm deckt Rundungs- und Zeichenungenauigkeiten der Fremdzeichnung ab. GERADE_MAX_DELTA_H = 10.0 +# Projektkennung in den erzeugten IDs (Kreisel__01 ...); main() +# setzt sie aus --projekt bzw. dem Namen der DXF-Datei. +PROJEKT = "HundM05" + # Toleranz fuer die Dedup-Rundung (Nachkommastellen). RUND = 2 @@ -123,6 +133,52 @@ ACHSE_MAX_QUER = 1500.0 # der Antriebsstationen, ohne benachbarte Bauteile zu verwechseln. ZWILLING_MAX_ABSTAND = 1500.0 +# --- Gefaellestrecken aus Staustrecken-Linien ------------------------------- +# Manche Zeichnungen (Dematic_arvato_reduced.dxf) fuehren die Gefaellestrecke +# nicht als Blockpaar, sondern als einfache 3D-LINE auf einem Staustrecken- +# Layer vom hohen zum tiefen Kreisel (dort: 27 Linien auf "6-SP", 10654 mm +# lang, 370 mm Gefaelle). Nur Modelspace-LINEs; Muster wie FAMILIEN. +GF_LINIE_LAYER = ["6-SP"] + +# Mindest-Hoehendifferenz (mm), ab der eine Staustrecken-Linie als Gefaelle +# gilt. Darunter ist sie eine waagrechte Strecke - die liegen auf denselben +# Layern (Rueckfuehrungen, Kreisel-Verbindungen) und sind keine GF. +GF_LINIE_MIN_DZ = 100.0 + +# Suchbereich fuer das Ausschleuselement am oberen Linienende, gemessen im +# Linien-KS: "entlang" ab Linienstart in Gefaellerichtung, "quer" seitlich. +# In Dematic_arvato_reduced.dxf sitzt es 1158 mm entlang und 467 mm quer - +# der Nachbar-AS der Linie daneben (800 mm Raster) liegt nur 433 mm quer auf +# der ANDEREN Seite. Darum entscheidet die Seite aus dem Blocknamen (links/ +# rechts), nicht der kleinste Abstand. +GF_AS_MAX_ENTLANG = 2000.0 +GF_AS_MAX_QUER = 700.0 +# Wie weit (mm) das AS/ES VOR dem Linienende liegen darf, also schon auf der +# Kreisel-Seite. In Dematic_arvato liegt es innerhalb der Linie; im +# Showroom_ILS_3_0-Plan endet die Staustrecke 305 mm vor dem Element, das auf +# der Kreisel-Kette sitzt. Kleiner als das Linienraster quer zur Kette +# (Nachbar-GF der Gegenrichtung lag dort 1200 mm entfernt). +GF_AS_MAX_VOR = 800.0 +# Toleranz (mm) fuer zusammenstossende Linienenden beim Zusammenfassen +# zerstueckelter Staustrecken (linien_zusammenfassen). +GF_LINIE_STOSS_TOL = 1.0 + +# Groesste Luecke (mm) zwischen Linienende und Kreisel-Kette, bis zu der die +# Linie an diesen Kreisel angeschlossen wird. Gezeichnet ist nur die +# Staustrecke: oben fehlt das Ausschleuselement (~430 mm), unten das +# Einschleuselement (~480 mm) bis zur Kette. +GF_KREISEL_MAX_LUECKE = 1500.0 + +# Separatoren auf der GF-Achse: die Gefaellestrecke baut ihren eigenen +# (Staustrecke_Separator_SP_300_mm), der gezeichnete waere doppelt. Gilt fuer +# Vorkommen bis GF_SEP_MAX_QUER (mm) neben der Achse. +GF_SEP_MUSTER = ["S-SP", "S-LP", "Separator*"] +GF_SEP_MAX_QUER = 300.0 + +# Abstand (mm), bis zu dem eine --kreisel-hoehe X Y Z dem Kreisel mit diesem +# Einfuegepunkt (Eintrag x/y) zugeordnet wird. +KREISEL_HOEHE_TOL = 50.0 + # --------------------------------------------------------------------------- # Hilfsfunktionen @@ -190,6 +246,48 @@ def iter_binary_tags(mm): yield code, value +def iter_ascii_tags(mm): + """Streamt (code, value)-Tags aus einem ASCII-DXF. + + Gegenstueck zu iter_binary_tags fuer Zeichnungen, die als Text-DXF + gespeichert wurden (z.B. Dematic_arvato.dxf). Die Werte werden ueber die + ezdxf-Typtabelle in float/int gewandelt, damit lese_inserts dieselben + Typen sieht wie beim binaeren Lesen. + """ + i = 0 + n = len(mm) + while i < n: + end = mm.find(b"\n", i) + if end < 0: + break + code_raw = mm[i:end].strip() + i = end + 1 + end = mm.find(b"\n", i) + if end < 0: + end = n + value_raw = mm[i:end].rstrip(b"\r") + i = end + 1 + if not code_raw: + continue + code = int(code_raw) + value = decode_str(value_raw) + if code == 0: + value = value.strip() + else: + try: + value = cast_tag_value(code, value) + except ValueError: + pass + yield code, value + + +def iter_tags(mm): + """Binaer- oder ASCII-DXF anhand der Kopfzeile erkennen.""" + if mm[:22] == b"AutoCAD Binary DXF\r\n\x1a\x00": + return iter_binary_tags(mm) + return iter_ascii_tags(mm) + + # --------------------------------------------------------------------------- # Familien-Erkennung (Blockname -> Bauteilfamilie) # --------------------------------------------------------------------------- @@ -197,8 +295,8 @@ def iter_binary_tags(mm): # mit Wildcards - die Blocknamen der Fremdzeichnungen enthalten Umlaute und # Sonderzeichen (z.B. "3D_Schraegfoerderer_Antrieb", "TEF_Bogen_90Grad_..."). FAMILIEN = [ - ("kreisel_an", ["AN8", "AN8_*"]), - ("kreisel_sp", ["SP8", "SP8_*"]), + ("kreisel_an", ["AN8", "AN8_*", "AN8 Pinband*"]), + ("kreisel_sp", ["SP8", "SP8_*", "SP8 Pinband*"]), ("foerderer_antrieb", ["3D_TEF_Antrieb*", "3D_FF_Antrieb*", "3D_Schr*rderer_Antrieb*", "Antrieb FF"]), ("foerderer_umlenk", ["3D_TEF_Umlenk*", "3D_FF_Umlenk*", @@ -214,8 +312,9 @@ FAMILIEN = [ ("omniflo_weiche", ["Weiche *", "Weichen-TEF*"]), ("bogen", ["3D_ILS_Bogen_*", "3D_FF_Bogen_*", "TEF_Bogen_*", "tef30*", "tef90*"]), + # "AE-D"/"EE-R": Kurznamen der aelteren ILS-Plaene (Showroom_ILS_3_0.dxf) ("schleuselement", ["Ausschleuselement*", "Einschleuselement*", - "EE_Neu*", "AE_Neu*"]), + "EE_Neu*", "AE_Neu*", "AE-*", "EE-*"]), ("sensor", ["S-LP", "S-SP", "SCAN", "SCAN_*", "Scanner*", "Separator*", "Stopper*", "Stopperschleuse*"]), ] @@ -310,12 +409,12 @@ def mappe_block(bname): geraten) # Aus-/Einschleuselement -> AS_Element / ES_Element - if "ausschleus" in low or low.startswith("ae_"): + if "ausschleus" in low or low.startswith(("ae_", "ae-")): winkel, geschaetzt = winkel_aus_name(bname, DEFAULT_SCHLEUS_WINKEL) if geschaetzt: geraten.append("Winkel=%d" % winkel) return "AS_Element_%d_%s" % (winkel, seite_aus()), geraten - if "einschleus" in low or low.startswith("ee_"): + if "einschleus" in low or low.startswith(("ee_", "ee-")): winkel, geschaetzt = winkel_aus_name(bname, DEFAULT_SCHLEUS_WINKEL) if geschaetzt: geraten.append("Winkel=%d" % winkel) @@ -443,7 +542,7 @@ def lese_inserts(dxf_path): fh.close() raise try: - for code, value in iter_binary_tags(mm): + for code, value in iter_tags(mm): if code == 0: # Ein ATTRIB endet immer am naechsten 0-Tag. Erst hier steht # sicher fest, dass Tag UND Wert gelesen sind - in einem ATTRIB @@ -496,6 +595,19 @@ def lese_inserts(dxf_path): block_merken(aktueller_block)["kinder"].add(value) elif aktuell == "ATTRIB": attrib_tag = value + elif aktuell == "CIRCLE" and aktueller_block and code in (10, 20, 30): + # Mittelpunkt des ERSTEN Kreises der Blockdefinition merken - + # bei AN8/SP8 ist das das Rad. Die Pinband-Varianten in + # Dematic_arvato.dxf haben ihr Rad ~15 m neben dem + # Einfuegepunkt; ohne diesen Wert laege der Kreisel dort. + # Ein zweiter Kreis beginnt mit Code 10, waehrend "kreis" schon + # existiert - ab da ist der erste fertig (Code 30 ist optional). + info = block_merken(aktueller_block) + if code == 10 and "kreis" in info: + info["kreis_fertig"] = True + if not info.get("kreis_fertig"): + kreis = info.setdefault("kreis", [0.0, 0.0, 0.0]) + kreis[{10: 0, 20: 1, 30: 2}[code]] = value elif aktuell == "BLOCK" and aktueller_block and code in (10, 20, 30): block_base[aktueller_block][{10: 0, 20: 1, 30: 2}[code]] = value elif aktuell == "INSERT" and rec is not None: @@ -533,6 +645,62 @@ def lese_inserts(dxf_path): return ms_inserts, block_inserts, block_base, statistik, block_info +def lese_linien(dxf_path, layer_muster): + """Liest alle Modelspace-LINEs auf den Layern aus layer_muster. + + Eigener Durchlauf statt Erweiterung von lese_inserts: dessen Rueckgabe + nutzt auch lib/dxf_abbild.py, und die Linien braucht nur die + Gefaellestrecken-Erkennung. Kostet einen zweiten Lesedurchgang. + + Rueckgabe: Liste {"layer", "a": [x, y, z], "b": [x, y, z]} in WCS + (eine LINE ist immer in WCS angegeben, keine OCS-Umrechnung noetig). + """ + linien = [] + section = None + warte_auf_sectionname = False + rec = None + + def ablegen(r): + if r and r["ps"] != 1 and matches_any(r["layer"] or "", layer_muster): + linien.append({"layer": r["layer"], "a": r["a"], "b": r["b"]}) + + fh = io.open(dxf_path, "rb") + try: + mm = mmap.mmap(fh.fileno(), 0, access=mmap.ACCESS_READ) + except ValueError: + fh.close() + raise + try: + for code, value in iter_tags(mm): + if code == 0: + ablegen(rec) + rec = None + if value == "SECTION": + warte_auf_sectionname = True + elif value == "ENDSEC": + section = None + elif value == "LINE" and section == "ENTITIES": + rec = {"layer": None, "a": [0.0, 0.0, 0.0], + "b": [0.0, 0.0, 0.0], "ps": 0} + elif code == 2 and warte_auf_sectionname: + section = value + warte_auf_sectionname = False + elif rec is not None: + if code == 8: + rec["layer"] = value + elif code in (10, 20, 30): + rec["a"][{10: 0, 20: 1, 30: 2}[code]] = value + elif code in (11, 21, 31): + rec["b"][{11: 0, 21: 1, 31: 2}[code]] = value + elif code == 67: + rec["ps"] = value + ablegen(rec) + finally: + mm.close() + fh.close() + return linien + + def insert_matrix(rec, block_base): """Transformationsmatrix eines INSERT (inkl. Basispunkt des Zielblocks). @@ -630,6 +798,13 @@ def sammle_vorkommen(ms_inserts, block_inserts, block_base, max_tiefe=12, xachse = mat.transform((1.0, 0.0, 0.0)) - ursprung rot = math.degrees(math.atan2(xachse.y, xachse.x)) % 360.0 fam = familie_von(rec["name"]) + # Kreisel-Stationen: Position = Radmitte, nicht Einfuegepunkt + # (siehe "kreis" in lese_inserts). Die Rotation oben bleibt die + # des Inserts. + kreis = ((block_info or {}).get(rec["name"], {}).get("kreis") + if fam in ("kreisel_an", "kreisel_sp") else None) + if kreis: + ursprung = mat.transform(tuple(kreis)) if fam: treffer.append({ "familie": fam, @@ -866,6 +1041,78 @@ def zwillinge_zusammenfuehren(treffer): # Paarbildung und Schema-Abbildung # --------------------------------------------------------------------------- +# Querversatz (mm), bis zu dem AN8 und SP8 noch auf derselben Kreisel-Achse +# liegen. Beide Stationen sitzen auf der Mittellinie des Kreisels; in +# Dematic_arvato.dxf liegen echte Paare auf 0,2 mm genau auf einer Achse. +KREISEL_MAX_QUER = 100.0 + + +def kreisel_paare_bilden(an_liste, sp_liste): + """AN8 und SP8 zu Kreiseln paaren. + + Global nach Abstand (kuerzestes Paar zuerst) statt AN8 fuer AN8 in + X-Reihenfolge: dort griff sich ein frueh sortierter AN8 die naechste noch + freie SP8 eines Nachbar-Kreisels, und alle folgenden Paare rutschten mit + (Dematic_arvato: ein 33,5-m-Kreisel wurde mit der SP8 eines 3,6-m-Kreisels + 19 m daneben gepaart). Zusaetzlich muessen beide auf einer gemeinsamen + Achse liegen (Querversatz <= KREISEL_MAX_QUER). + + Danach Tausch-Nachbesserung auf minimale Abstandssumme (siehe unten). + + Rueckgabe: (paare [(an, sp, abstand)] nach AN-Position sortiert, + AN8 ohne Partner, SP8 ohne Partner) + """ + def abstand(an, sp): + """Mittenabstand, oder None wenn nicht auf gemeinsamer Achse.""" + dx = abs(sp["x"] - an["x"]) + dy = abs(sp["y"] - an["y"]) + if min(dx, dy) > KREISEL_MAX_QUER: + return None + return math.hypot(dx, dy) + + kandidaten = [] + for ia, an in enumerate(an_liste): + for isp, sp in enumerate(sp_liste): + d = abstand(an, sp) + if d is not None: + kandidaten.append((d, ia, isp)) + kandidaten.sort() + an_frei = set(range(len(an_liste))) + sp_frei = set(range(len(sp_liste))) + paare = [] + for dist, ia, isp in kandidaten: + if ia in an_frei and isp in sp_frei: + an_frei.discard(ia) + sp_frei.discard(isp) + paare.append([an_liste[ia], sp_liste[isp], dist]) + + # Nachbesserung: zwei Paare tauschen ihre SP8, solange die Summe der + # Abstaende dadurch kleiner wird. "Kuerzestes Paar zuerst" allein + # scheitert an zwei Kreiseln auf DERSELBEN Achse: die Luecke zwischen + # SP8 des einen und AN8 des anderen ist kuerzer als beide Kreisel und + # wird zuerst gepaart (Showroom_ILS_3_0: 800 + 12832 statt 9033 + 3000). + # Die echte Zuordnung hat immer die kleinere Summe - jede vertauschte + # Paarung ueberspannt mindestens eine Luecke doppelt. + verbessert = True + while verbessert: + verbessert = False + for i in range(len(paare)): + for j in range(i + 1, len(paare)): + a1, s1, d1 = paare[i] + a2, s2, d2 = paare[j] + n1, n2 = abstand(a1, s2), abstand(a2, s1) + if n1 is None or n2 is None or n1 + n2 >= d1 + d2 - 1e-6: + continue + paare[i] = [a1, s2, n1] + paare[j] = [a2, s1, n2] + verbessert = True + paare = [tuple(p) for p in paare] + paare.sort(key=lambda p: (p[0]["x"], p[0]["y"])) + return (paare, + [an_liste[i] for i in sorted(an_frei)], + [sp_liste[i] for i in sorted(sp_frei)]) + + def paare_bilden(antriebe, umlenkungen): """Gegenseitig naechste Nachbarn paaren. @@ -978,7 +1225,7 @@ def foerderer_eintrag(a, u, dist_xy, nummer, gf_winkel_min): # Die echte Hoehe liefert der 3D-Zwilling (siehe Zusammenfuehrung). if a.get("darstellung") == "2D": return { - "test_id": "GERADE_HundM05_%02d" % nummer, + "test_id": "GERADE_%s_%02d" % (PROJEKT, nummer), "typ": "gerade", "darstellung": "2D", "laenge": round(dist_xy, RUND), @@ -993,7 +1240,7 @@ def foerderer_eintrag(a, u, dist_xy, nummer, gf_winkel_min): if abs(dz) < GERADE_MAX_DELTA_H: return { - "test_id": "GERADE_HundM05_%02d" % nummer, + "test_id": "GERADE_%s_%02d" % (PROJEKT, nummer), "typ": "gerade", "darstellung": a.get("darstellung", "?"), "laenge": round(dist_xy, RUND), @@ -1008,7 +1255,7 @@ def foerderer_eintrag(a, u, dist_xy, nummer, gf_winkel_min): if dz < 0 and neigung >= gf_winkel_min: return { - "test_id": "GF_HundM05_%02d" % nummer, + "test_id": "GF_%s_%02d" % (PROJEKT, nummer), "modus": 1, "darstellung": a.get("darstellung", "?"), "deltaL_mm": round(dist_xy, RUND), @@ -1025,7 +1272,7 @@ def foerderer_eintrag(a, u, dist_xy, nummer, gf_winkel_min): } return { - "test_id": "VF_HundM05_%02d" % nummer, + "test_id": "VF_%s_%02d" % (PROJEKT, nummer), "typ": "standard", "darstellung": a.get("darstellung", "?"), "richtung": "Auf" if dz > 0 else "Ab", @@ -1104,28 +1351,388 @@ def sensor_eintrag(t, bekannte_bloecke=None, hinweise=None, treffer=None): } +def as_am_ende(p, richtung, as_liste, belegt): + """Ausschleuselement an einem Linienende suchen. + + p = Linienende (x, y), richtung = Einheitsvektor (x, y) von dort in die + Linie hinein. Gesucht wird im Linien-KS (siehe GF_AS_MAX_ENTLANG/-QUER); + "quer" ist positiv LINKS der Richtung. Ein AS mit Seite im Blocknamen muss + auf dieser Seite liegen - das trennt ihn vom Nachbar-AS der Linie daneben. + Bereits vergebene AS (belegt = Menge von id()) zaehlen nicht. + + Funktioniert genauso fuer Einschleuselemente am unteren Ende (es_liste in + gf_eintraege_aus_linien), dann mit der Richtung vom unteren Ende in die + Linie hinein. + + Rueckgabe: (treffer, zielblock, geraten) oder None. + """ + beste = None + for t in as_liste: + if id(t) in belegt: + continue + vx, vy = t["x"] - p[0], t["y"] - p[1] + entlang = vx * richtung[0] + vy * richtung[1] + quer = richtung[0] * vy - richtung[1] * vx + if not (-GF_AS_MAX_VOR <= entlang <= GF_AS_MAX_ENTLANG + and abs(quer) <= GF_AS_MAX_QUER): + continue + block, geraten = mappe_block(t["block"]) + if "Seite=%s" % DEFAULT_SEITE not in (geraten or []): + if block.endswith("_links") and quer < 0: + continue + if block.endswith("_rechts") and quer > 0: + continue + if beste is None or abs(quer) < beste[0]: + beste = (abs(quer), t, block, geraten or []) + return beste[1:] if beste else None + + +def kreisel_anschluss(p, richtung, kreisel): + """Schnittpunkt einer GF-Achse mit der naechsten Kreisel-Kette. + + p = Linienende (x, y), richtung = Einheitsvektor (x, y) der GF-Achse. + Je Kreisel (siehe kreisel_geo in baue_eintraege: Achse von der AN8- zur + SP8-Radmitte, Kette im Abstand r beidseits) wird die Kette auf der Seite + von p genommen und die Achse bis dorthin verlaengert bzw. gekuerzt. Nur + Kreisel, an deren Laenge p liegt und deren Kette hoechstens + GF_KREISEL_MAX_LUECKE entfernt ist; bei mehreren gewinnt die kleinste + Verschiebung. + + Rueckgabe: (kreisel, punkt (x, y), verschiebung entlang richtung) oder None. + """ + beste = None + for k in kreisel: + ux, uy = k["u"] + nx, ny = -uy, ux + vx, vy = p[0] - k["a"][0], p[1] - k["a"][1] + t = vx * ux + vy * uy + q = vx * nx + vy * ny + if not (-k["r"] <= t <= k["laenge"] + k["r"]): + continue + if abs(abs(q) - k["r"]) > GF_KREISEL_MAX_LUECKE: + continue + dn = richtung[0] * nx + richtung[1] * ny + if abs(dn) < 1e-6: # GF parallel zum Kreisel + continue + kette = k["r"] if q >= 0 else -k["r"] + lam = (kette - q) / dn + if beste is None or abs(lam) < abs(beste[2]): + beste = (k, (p[0] + lam * richtung[0], p[1] + lam * richtung[1]), lam) + return beste + + +def linien_zusammenfassen(linien): + """Kollineare, aneinanderstossende Linien desselben Layers vereinen. + + Eine Staustrecke ist im Plan oft in Stuecke zerlegt (Showroom_ILS_3_0: + vier LINEs je GF, an den Separatoren getrennt). Als Einzelstuecke fehlte + jedem das AS am einen oder das ES am anderen Ende. Vereint wird nur bei + gleicher Richtung (in 3D) und Stoss innerhalb GF_LINIE_STOSS_TOL. + """ + def richtung(ln): + d = [ln["b"][i] - ln["a"][i] for i in range(3)] + n = math.sqrt(sum(c * c for c in d)) + return [c / n for c in d] if n else None + + def kollinear(r1, r2): + kreuz = (r1[1] * r2[2] - r1[2] * r2[1], r1[2] * r2[0] - r1[0] * r2[2], + r1[0] * r2[1] - r1[1] * r2[0]) + # 1e-3 rad (~0.06 Grad): die Plaene sind nicht exakt gezeichnet - + # im Showroom-Plan sitzt das Separatorstueck 0,3 mm neben der Achse, + # das verkippt das bis dahin vereinte Stueck um ~2.6e-4. + return math.sqrt(sum(c * c for c in kreuz)) < 1e-3 + + def gleich(p, q): + return math.dist(p, q) <= GF_LINIE_STOSS_TOL + + rest = [{"layer": ln["layer"], "a": list(ln["a"]), "b": list(ln["b"])} + for ln in linien] + fertig = [] + while rest: + akt = rest.pop(0) + gewachsen = True + while gewachsen: + gewachsen = False + ra = richtung(akt) + for ln in rest: + rb = richtung(ln) + if (ln["layer"] != akt["layer"] or ra is None or rb is None + or not kollinear(ra, rb)): + continue + # Die vier moeglichen Stoesse; das Ergebnis behaelt die + # beiden aeusseren Enden. + for p_akt, q_akt in (("a", "b"), ("b", "a")): + for p_ln, q_ln in (("a", "b"), ("b", "a")): + if gleich(akt[p_akt], ln[p_ln]): + akt = {"layer": akt["layer"], "a": akt[q_akt], + "b": ln[q_ln]} + gewachsen = True + break + if gewachsen: + break + if gewachsen: + rest.remove(ln) + break + fertig.append(akt) + return fertig + + +def seite_aus_lage(t, p, r): + """Seite eines AS/ES aus seiner Lage zur GF-Fahrtrichtung. + + t = Element, p = Punkt auf der Achse, r = Fahrtrichtung der GF (bergab). + Links der Fahrtrichtung -> "links", sonst "rechts" - dieselbe Zuordnung + wie in Dematic_arvato ("Ausschleuselement links" liegt links der + Fahrtrichtung, siehe as_am_ende). Nur fuer Bloecke ohne Seite im Namen + ("AE-D"/"EE-R"); gespiegelte Inserts landen dadurch automatisch auf der + Gegenseite. + """ + vx, vy = t["x"] - p[0], t["y"] - p[1] + return "links" if r[0] * vy - r[1] * vx > 0 else "rechts" + + +def gf_eintraege_aus_linien(linien, as_liste, kreisel=None, sep_liste=None, + winkel_default=3.0, es_liste=None, flach=False): + """Gefaellestrecken (Modus 1, Schema 2.1) aus Staustrecken-Linien. + + Eine Gefaellestrecke laeuft immer bergab: Start ist das HOEHERE + Linienende, unabhaengig von der Zeichenrichtung. Das Ausschleuselement + gehoert an dieses obere Ende. Sitzt es dagegen am UNTEREN Ende, faehrt + die Strecke vom tiefen Kreisel hinauf - das ist ein Steigfoerderer und + keine GF (Dematic_arvato_reduced.dxf: 3 solche Linien); sie wird + gemeldet und uebersprungen. + + Anschluss an die Kreisel: gezeichnet ist nur die Staustrecke. Das Makro + legt den Ursprung des AS-Elements auf start_mm und dessen Einlauf + (KS_EIN) seitlich daneben, auf gleicher Hoehe der Achse; ebenso liegt der + Auslauf des ES-Elements neben dessen Ursprung, und deltaL_mm reicht von + AS- bis ES-Ursprung (gemessen an GF_Mubea: 9510 -> 399.2 = 9110.8). Damit + beide die Kreisel-Kette treffen, werden Start und Ende auf die jeweils + naechste Kette geschoben (kreisel_anschluss). Ohne Kreisel in Reichweite + bleibt das Linienende stehen und es gibt einen Hinweis. + + Hoehen: tragen beide Kreisel eine Anwender-Hoehe ("z_anwender"), wird + start_mm.z die des oberen und hoehe_ende_mm die des unteren Kreisels - + den Winkel rechnet dann der LISP-Treiber mit gf-winkel-berechnen + (dieselbe Naeherung wie Modus 3, mit den Elementmassen der aktuellen + 2D/3D-Bibliothek). Sonst ist winkel_default ein Platzhalter. + + Rueckgabe: (eintraege, verbrauchte ids, hinweise). Verbraucht werden das + AS am oberen Ende und Separatoren auf der Achse (sep_liste) - beides baut + die Gefaellestrecke selbst mit, als Einzel-Insert stuenden sie doppelt in + der Zeichnung (und im Sivas-Export). Der AS eines uebersprungenen + Steigfoerderers bleibt unverbraucht. + + es_liste: Einschleuselemente; eines am unteren Ende liefert es_seite/ + es_winkel und wird ebenfalls verbraucht. Ohne es_liste bleiben beide + Defaults (Dematic_arvato hat kein ES im Plan). + + flach: 2D-Plan ohne Hoehen (alle Linien waagrecht, z.B. + Showroom_ILS_3_0). Dann ist eine waagrechte Linie eine GF, wenn genau an + EINEM Ende ein Ausschleuselement sitzt - dieses Ende ist oben. Ohne flach + werden waagrechte Linien wie bisher uebergangen. + """ + eintraege, hinweise = [], [] + belegt = set() + waagrecht = 0 + kandidaten = [] + for ln in linien_zusammenfassen(linien): + a, b = ln["a"], ln["b"] + if abs(a[2] - b[2]) < GF_LINIE_MIN_DZ: + lxy = math.hypot(b[0] - a[0], b[1] - a[1]) + if not (flach and lxy > 0.0): + waagrecht += 1 + continue + r_ab = ((b[0] - a[0]) / lxy, (b[1] - a[1]) / lxy) + as_a = as_am_ende(a, r_ab, as_liste, belegt) + as_b = as_am_ende(b, (-r_ab[0], -r_ab[1]), as_liste, belegt) + if (as_a is None) == (as_b is None): + waagrecht += 1 + continue + hoch, tief = (a, b) if as_a is not None else (b, a) + kandidaten.append((hoch, tief, lxy, ln["layer"])) + continue + hoch, tief = (a, b) if a[2] > b[2] else (b, a) + lxy = math.hypot(tief[0] - hoch[0], tief[1] - hoch[1]) + if lxy <= 0.0: + continue + kandidaten.append((hoch, tief, lxy, ln["layer"])) + kandidaten.sort(key=lambda k: (round(k[0][0], RUND), round(k[0][1], RUND))) + + for hoch, tief, lxy, layer in kandidaten: + r = ((tief[0] - hoch[0]) / lxy, (tief[1] - hoch[1]) / lxy) + oben = as_am_ende(hoch, r, as_liste, belegt) + if oben is None: + unten = as_am_ende(tief, (-r[0], -r[1]), as_liste, belegt) + if unten is not None: + # AS NICHT verbrauchen: er gehoert zum Steigfoerderer und + # bleibt als Einzel-Insert in der Ausgabe stehen. + hinweise.append( + "Staustrecken-Linie (%.0f, %.0f, %.0f) -> (%.0f, %.0f, " + "%.0f): Ausschleuselement am UNTEREN Ende - Steigfoerderer, " + "keine Gefaellestrecke, uebersprungen" + % (tief[0], tief[1], tief[2], hoch[0], hoch[1], hoch[2])) + continue + tid = "GF_%s_%02d" % (PROJEKT, len(eintraege) + 1) + kommentar = ("aus DXF: LINE auf Layer '%s' (%.2f, %.2f) -> (%.2f, " + "%.2f), Laenge XY %.2f" + % (layer, hoch[0], hoch[1], tief[0], tief[1], lxy)) + + # Start/Ende auf die Kreisel-Ketten schieben + start, ende = (hoch[0], hoch[1]), (tief[0], tief[1]) + k_start = k_ende = None + for wo, p, rr in (("oberen", hoch, r), ("unteren", tief, (-r[0], -r[1]))): + ans = kreisel_anschluss(p, rr, kreisel or []) + if ans is None: + hinweise.append("%s: kein Kreisel am %s Ende - Linienende " + "bleibt stehen" % (tid, wo)) + kommentar += "; am %s Ende kein Kreisel in Reichweite" % wo + continue + k, punkt, lam = ans + kommentar += ("; am %s Ende um %.1f mm bis zur Kette von %s " + "verlaengert" % (wo, -lam, k["id"])) + if wo == "oberen": + start, k_start = punkt, k + else: + ende, k_ende = punkt, k + laenge = (ende[0] - start[0]) * r[0] + (ende[1] - start[1]) * r[1] + + eintrag = { + "test_id": tid, + "modus": 1, + "deltaL_mm": round(laenge, RUND), + "hz_grad": round(math.degrees(math.atan2(r[1], r[0])) % 360.0, RUND) + 0.0, + "start_mm": [round(start[0], RUND) + 0.0, round(start[1], RUND) + 0.0, 0.0], + "as_seite": DEFAULT_SEITE, + "es_seite": "links", + "as_winkel": str(DEFAULT_SCHLEUS_WINKEL), + "es_winkel": str(DEFAULT_SCHLEUS_WINKEL), + } + if k_start: + eintrag["kreisel_start"] = k_start["id"] + if k_ende: + eintrag["kreisel_ende"] = k_ende["id"] + if (k_start and k_ende and k_start.get("z_anwender") is not None + and k_ende.get("z_anwender") is not None): + eintrag["start_mm"][2] = float(k_start["z_anwender"]) + eintrag["hoehe_ende_mm"] = float(k_ende["z_anwender"]) + eintrag["hoehe_quelle"] = "Anwender" + kommentar += ("; Hoehen vom Anwender (Kreisel %s / %s), den Winkel " + "rechnet der Treiber" % (k_start["id"], k_ende["id"])) + else: + eintrag["winkel_grad"] = winkel_default + eintrag["hoehe_unbekannt"] = True + kommentar += ("; Hoehen unbekannt - winkel_grad %.1f ist Platzhalter" + % winkel_default) + + if oben is not None: + t, block, geraten = oben + belegt.add(id(t)) + teile = block.split("_") # AS_Element__ + eintrag["as_winkel"], eintrag["as_seite"] = teile[2], teile[3] + if "Seite=%s" % DEFAULT_SEITE in geraten: + eintrag["as_seite"] = seite_aus_lage(t, hoch, r) + geraten = [g for g in geraten if not g.startswith("Seite=")] + block = "AS_Element_%s_%s" % (eintrag["as_winkel"], + eintrag["as_seite"]) + kommentar += ("; as_seite %s aus der Lage zur Fahrtrichtung" + % eintrag["as_seite"]) + kommentar += ("; AS aus Block '%s' bei (%.2f, %.2f) -> %s, als " + "Einzel-Insert entfernt (baut die GF selbst)" + % (t["block"], t["x"], t["y"], block)) + if geraten: + kommentar += " - geraten: %s" % ", ".join(geraten) + else: + kommentar += ("; KEIN Ausschleuselement am oberen Ende gefunden - " + "as_seite/as_winkel sind Defaults") + hinweise.append("%s ohne Ausschleuselement am oberen Ende" % tid) + + # Separatoren auf der Achse zwischen Start und Ende + weg = [] + for s in sep_liste or []: + if id(s) in belegt or not matches_any(s["block"], GF_SEP_MUSTER): + continue + vx, vy = s["x"] - start[0], s["y"] - start[1] + entlang = vx * r[0] + vy * r[1] + quer = r[0] * vy - r[1] * vx + if 0.0 <= entlang <= laenge and abs(quer) <= GF_SEP_MAX_QUER: + belegt.add(id(s)) + weg.append("%s bei (%.0f, %.0f)" % (s["block"], s["x"], s["y"])) + if weg: + kommentar += ("; Separator auf der Achse entfernt (die GF baut " + "ihren eigenen): %s" % ", ".join(weg)) + + unten = (as_am_ende(tief, (-r[0], -r[1]), es_liste, belegt) + if es_liste else None) + if unten is not None: + t, block, geraten = unten + belegt.add(id(t)) + teile = block.split("_") # ES_Element__ + eintrag["es_winkel"], eintrag["es_seite"] = teile[2], teile[3] + if "Seite=%s" % DEFAULT_SEITE in geraten: + eintrag["es_seite"] = seite_aus_lage(t, tief, r) + geraten = [g for g in geraten if not g.startswith("Seite=")] + block = "ES_Element_%s_%s" % (eintrag["es_winkel"], + eintrag["es_seite"]) + kommentar += ("; es_seite %s aus der Lage zur Fahrtrichtung" + % eintrag["es_seite"]) + kommentar += ("; ES aus Block '%s' bei (%.2f, %.2f) -> %s, als " + "Einzel-Insert entfernt (baut die GF selbst)" + % (t["block"], t["x"], t["y"], block)) + if geraten: + kommentar += " - geraten: %s" % ", ".join(geraten) + kommentar += "." + else: + kommentar += "; es_seite/es_winkel sind Defaults (kein ES im DXF)." + eintrag["comment"] = kommentar + eintraege.append(eintrag) + + if waagrecht: + hinweise.append("%d waagrechte Staustrecken-Linie(n) auf %s - keine " + "Gefaellestrecke, nicht ausgegeben" + % (waagrecht, "/".join(GF_LINIE_LAYER))) + return eintraege, belegt, hinweise + + def baue_eintraege(treffer, defaults, boegen, weichen, - bekannte_bloecke=None): - """Erkannte Vorkommen auf die Schemata aus object_data.md abbilden.""" + bekannte_bloecke=None, linien=None, kreisel_hoehen=None, + kreisel_achsen=None, flach=False): + """Erkannte Vorkommen auf die Schemata aus object_data.md abbilden. + + kreisel_hoehen: Liste (x, y, z) vom Anwender - z gilt fuer den Kreisel, + dessen Einfuegepunkt (Eintrag x/y) hoechstens KREISEL_HOEHE_TOL entfernt + liegt, und fuer die Gefaellestrecken, die an ihm anschliessen. + + kreisel_achsen: Liste (x1, y1, x2, y2) vom Anwender - Radmitten eines + Kreisels ohne AN8/SP8 im Plan (Showroom_ILS_3_0: Schleifen, die an beiden + Enden mit Weichenboegen schliessen). Wird als STANDARD-Kreisel + angenaehert, x1/y1 gilt als AN8-Seite. + + flach: siehe gf_eintraege_aus_linien. + """ nach_familie = {} for t in treffer: nach_familie.setdefault(t["familie"], []).append(t) eintraege = [] hinweise = [] + kreisel_geo = [] # Achse/Kette je Kreisel fuer kreisel_anschluss + offene_hoehen = list(kreisel_hoehen or []) # --- Kreisel: AN8 (Antriebsstation) + SP8 (Spannstation) -------------- durchmesser = defaults.get("kreisel", {}).get("durchmesser", 800.0) - an_liste = sorted(nach_familie.get("kreisel_an", []), key=lambda t: (t["x"], t["y"])) - sp_liste = list(nach_familie.get("kreisel_sp", [])) - for i, an in enumerate(an_liste, start=1): - if not sp_liste: - hinweise.append("AN8 bei (%.2f, %.2f) ohne SP8-Partner" % (an["x"], an["y"])) - eintraege.append(sensor_eintrag(an, bekannte_bloecke, hinweise, treffer)) - continue - sp = min(sp_liste, key=lambda s: math.hypot(s["x"] - an["x"], s["y"] - an["y"])) - sp_liste.remove(sp) - dist = math.hypot(sp["x"] - an["x"], sp["y"] - an["y"]) + kreisel_paare, an_rest, sp_liste = kreisel_paare_bilden( + nach_familie.get("kreisel_an", []), nach_familie.get("kreisel_sp", [])) + for an in an_rest: + hinweise.append("AN8 bei (%.2f, %.2f) ohne SP8-Partner" % (an["x"], an["y"])) + eintraege.append(sensor_eintrag(an, bekannte_bloecke, hinweise, treffer)) + for x1, y1, x2, y2 in kreisel_achsen or []: + an = {"block": "(manuell AN8)", "layer": "--kreisel-achse", + "x": x1, "y": y1, "z": 0.0, "darstellung": "2D"} + sp = {"block": "(manuell SP8)", "layer": "--kreisel-achse", + "x": x2, "y": y2, "z": 0.0, "darstellung": "2D"} + kreisel_paare.append((an, sp, math.hypot(x2 - x1, y2 - y1))) + for i, (an, sp, dist) in enumerate(kreisel_paare, start=1): rotation = math.degrees(math.atan2(sp["y"] - an["y"], sp["x"] - an["x"])) % 360.0 # AN8/SP8 sind flache 2D-Symbole auf Z=0 - die Einbauhoehe steht nicht # im DXF. Sie wird aus den 3D-Modellen in der Umgebung abgeleitet @@ -1144,24 +1751,76 @@ def baue_eintraege(treffer, defaults, boegen, weichen, hoehe_hinweis = (" ACHTUNG hoehe=%.1f ist NICHT belegt: " "AN8/SP8 sind flache 2D-Symbole auf Z=0 und " "%s." % (hoehe, grund)) + # an/sp sind Radmitten (siehe "kreis" in lese_inserts). draw-module + # (KreiselInsert.lsp) legt AN8 bei (radius, 0) und SP8 bei + # (radius + abstand, 0) in den Block: abstand ist also der + # Mittenabstand, und der Einfuegepunkt liegt einen Radius VOR der + # AN8-Mitte. Die fruehere "connect"-Ausgabe uebergab die Radmitten an + # kreisel-connect-script, das Aussenkanten erwartet - der Kreisel + # wurde dadurch an beiden Enden um einen Radius zu kurz. + radius = durchmesser / 2.0 + richtung = math.radians(rotation) eintraege.append({ - "id": "Kreisel_HundM05_%02d" % i, - "function": "connect", - "start_x": an["x"], "start_y": an["y"], "start_z": an["z"], - "end_x": sp["x"], "end_y": sp["y"], "end_z": sp["z"], + "id": "Kreisel_%s_%02d" % (PROJEKT, i), + "function": "insert", + "x": round(an["x"] - radius * math.cos(richtung), RUND) + 0.0, + "y": round(an["y"] - radius * math.sin(richtung), RUND) + 0.0, + "z": hoehe, + "abstand": round(dist, RUND), + "rotation": round(rotation, RUND) + 0.0, "typ": "STANDARD", "darstellung": an.get("darstellung", "?"), - "hoehe": hoehe, - "comment": ("aus DXF: AN8 (Layer %s) -> SP8 (Layer %s), Abstand " - "%.2f; daraus folgt produktionsseitig abstand = %.2f - " - "%.1f = %.2f und rotation = %.2f Grad." - % (an["layer"], sp["layer"], dist, dist, durchmesser, - dist - durchmesser, rotation)) + hoehe_hinweis, + "comment": ("aus DXF: Radmitte %s (Layer %s) (%.2f, %.2f) -> " + "%s (Layer %s) (%.2f, %.2f), Mittenabstand %.2f, " + "rotation %.2f Grad; x/y = AN8-Mitte minus Radius." + % (an["block"], an["layer"], an["x"], an["y"], + sp["block"], sp["layer"], sp["x"], sp["y"], dist, + rotation)) + hoehe_hinweis, }) + kreisel = eintraege[-1] + for h in offene_hoehen: + if math.hypot(h[0] - kreisel["x"], h[1] - kreisel["y"]) <= KREISEL_HOEHE_TOL: + kreisel["z"] = float(h[2]) + kreisel["hoehe_quelle"] = "Anwender" + kreisel["comment"] += " Hoehe %.1f vom Anwender." % h[2] + offene_hoehen.remove(h) + break + kreisel_geo.append({ + "id": kreisel["id"], + "a": (an["x"], an["y"]), + "u": (math.cos(richtung), math.sin(richtung)), + "laenge": dist, + "r": radius, + "z_anwender": kreisel["z"] if kreisel.get("hoehe_quelle") else None, + }) + for h in offene_hoehen: + hinweise.append("Kreisel-Hoehe %.1f bei (%.2f, %.2f): kein Kreisel-" + "Einfuegepunkt in %g mm - nicht verwendet" + % (h[2], h[0], h[1], KREISEL_HOEHE_TOL)) for sp in sp_liste: hinweise.append("SP8 bei (%.2f, %.2f) ohne AN8-Partner" % (sp["x"], sp["y"])) eintraege.append(sensor_eintrag(sp, bekannte_bloecke, hinweise, treffer)) + # --- Gefaellestrecken aus Staustrecken-Linien --------------------------- + # Nach den Kreiseln (Anschluss an deren Ketten) und vor dem Rest-Block: + # sie entnehmen ihre Ausschleuselemente und Separatoren aus den Familien + # (siehe gf_eintraege_aus_linien), der Rest-Block darf diese nicht mehr + # als Einzel-Insert ausgeben. + if linien: + as_liste = [t for t in nach_familie.get("schleuselement", []) + if mappe_block(t["block"])[0].startswith("AS_Element")] + es_liste = [t for t in nach_familie.get("schleuselement", []) + if mappe_block(t["block"])[0].startswith("ES_Element")] + gf, belegt, gf_hinweise = gf_eintraege_aus_linien( + linien, as_liste, kreisel_geo, nach_familie.get("sensor", []), + defaults.get("gefaelle", {}).get("default_winkel", 3.0), + es_liste, flach) + eintraege.extend(gf) + hinweise.extend(gf_hinweise) + for familie in ("schleuselement", "sensor"): + nach_familie[familie] = [t for t in nach_familie.get(familie, []) + if id(t) not in belegt] + # --- Foerderer: Antriebs- und Umlenkstation paaren --------------------- gf_winkel_min = defaults.get("gefaelle", {}).get("default_winkel", 3.0) paare, rest_a, rest_u = paare_bilden(nach_familie.get("foerderer_antrieb", []), @@ -1188,7 +1847,7 @@ def baue_eintraege(treffer, defaults, boegen, weichen, hinweise.append("Omniflo %s ohne Sivasnr: '%s' (%s)" % (typ, t["block"], grund)) eintraege.append({ - "id": sivasnr or ("%s_HundM05_%02d" % (typ.upper(), i)), + "id": sivasnr or ("%s_%s_%02d" % (typ.upper(), PROJEKT, i)), "type": typ, "sivasnr": sivasnr, "x": t["x"], "y": t["y"], "z": t["z"], @@ -1283,6 +1942,84 @@ def filtere_fenster(eintraege, fenster): return behalten, len(eintraege) - len(behalten), ohne_punkte +def hoehen_entfernen(eintraege): + """Alle Hoehen auf 0 setzen, ausser den vom Anwender vorgegebenen. + + Fuer Zeichnungen, deren Z-Werte (noch) nicht belastbar sind: lieber + gar keine Hoehe als eine, die nach Einbauhoehe aussieht und keine ist. + Ausgenommen sind Eintraege mit hoehe_quelle "Anwender" (--kreisel-hoehe + und die daran anschliessenden Gefaellestrecken). + Rueckgabe: Anzahl der geaenderten Eintraege. + """ + n = 0 + for e in eintraege: + if e.get("hoehe_quelle") == "Anwender": + continue + geaendert = False + for feld in ("z", "hoehe"): + if feld in e and e[feld] != 0.0: + e[feld] = 0.0 + geaendert = True + for feld in ("startpunkt", "start_mm", "endpunkt"): + p = e.get(feld) + if isinstance(p, list) and len(p) >= 3 and p[2] != 0.0: + p[2] = 0.0 + geaendert = True + if any(f in e for f in ("z", "hoehe", "startpunkt", "start_mm")): + e["hoehe_unbekannt"] = True + if geaendert: + e["comment"] = e.get("comment", "") + " [Hoehe entfernt: --ohne-hoehen]" + n += 1 + return n + + +# Querabstand (mm) zur Kreisel-Kette, bis zu dem ein Einzelteil (Separator, +# Scanner) als "auf dem Kreisel" gilt und dessen Anwender-Hoehe bekommt. +SENSOR_KETTE_MAX_QUER = 350.0 + + +def sensor_hoehen_von_kreiseln(eintraege, durchmesser=800.0): + """Einzelteile auf einer Kreisel-Kette bekommen dessen Anwender-Hoehe. + + Im flachen 2D-Plan (--ohne-hoehen) stehen Separatoren/Scanner sonst auf + Z=0, waehrend der Kreisel per --kreisel-hoehe auf seiner Hoehe sitzt. + Kette = Achse (Einfuegepunkt + Radius entlang rotation, Laenge abstand) + im Abstand r beidseits, plus die Halbkreise an den Enden. Nur Kreisel mit + hoehe_quelle "Anwender"; bei mehreren gewinnt der naechste. + + Rueckgabe: Anzahl gesetzter Hoehen. + """ + r = durchmesser / 2.0 + kreisel = [] + for e in eintraege: + if "abstand" in e and e.get("hoehe_quelle") == "Anwender": + w = math.radians(e.get("rotation", 0.0)) + u = (math.cos(w), math.sin(w)) + a = (e["x"] + r * u[0], e["y"] + r * u[1]) + kreisel.append((e, a, u, float(e["abstand"]))) + gesetzt = 0 + for e in eintraege: + if e.get("function") != "insert" or "block" not in e: + continue + beste = None + for k, a, u, laenge in kreisel: + vx, vy = e["x"] - a[0], e["y"] - a[1] + t = max(0.0, min(laenge, vx * u[0] + vy * u[1])) + # Abstand zur Achse (Strecke), davon r abgezogen = Abstand zur Kette + d = abs(math.hypot(vx - t * u[0], vy - t * u[1]) - r) + if d <= SENSOR_KETTE_MAX_QUER and (beste is None or d < beste[0]): + beste = (d, k) + if beste: + k = beste[1] + e["z"] = float(k["z"]) + e["hoehe_quelle"] = "Anwender" + e.pop("hoehe_unbekannt", None) + e["comment"] += " Hoehe %.1f von %s (liegt auf dessen Kette)." % ( + k["z"], k["id"]) + gesetzt += 1 + return gesetzt + + def arrays_einzeilig(text): """Zahlen-Arrays im JSON-Text auf eine Zeile ziehen. @@ -1346,7 +2083,7 @@ def main(): description="Erkennt ILS-/Omniflo-Komponenten in einem fremden DXF " "und schreibt sie im JSON-Schema aus " "tests/testdata/object_data.md.") - parser.add_argument("dxf", help="Quellzeichnung (binaeres DXF)") + parser.add_argument("dxf", help="Quellzeichnung (binaeres oder ASCII-DXF)") parser.add_argument("data_dir", help="Datenverzeichnis mit json/") parser.add_argument("output", help="Ziel-JSON") parser.add_argument("--fenster", nargs=4, type=float, @@ -1354,8 +2091,33 @@ def main(): help="Nur Bauteile in diesem XY-Fenster (mm) " "ausgeben - fuer den Nachbau eines einzelnen " "Anlagenabschnitts statt der ganzen Zeichnung") + parser.add_argument("--ohne-hoehen", action="store_true", + help="Alle Hoehen auf 0 setzen (Z der Zeichnung nicht " + "belastbar); ausgenommen --kreisel-hoehe") + parser.add_argument("--kreisel-hoehe", nargs=3, type=float, action="append", + metavar=("X", "Y", "Z"), + help="Hoehe Z fuer den Kreisel mit Einfuegepunkt X/Y " + "(wie im Ausgabe-JSON); gilt auch fuer Start/Ende " + "der daran anschliessenden Gefaellestrecken. " + "Mehrfach angebbar") + parser.add_argument("--kreisel-achse", nargs=4, type=float, + action="append", metavar=("X1", "Y1", "X2", "Y2"), + help="Kreisel ohne AN8/SP8 im Plan (z.B. Schleife mit " + "Weichenboegen an beiden Enden): Radmitten der " + "beiden Enden, X1/Y1 = AN8-Seite. Wird als " + "STANDARD-Kreisel angenaehert. Mehrfach angebbar") + parser.add_argument("--flach", action="store_true", + help="2D-Plan ohne Hoehen: waagrechte Staustrecken-" + "Linien mit Ausschleuselement an genau einem " + "Ende als Gefaellestrecke ausgeben (Hoehen dann " + "ueber --kreisel-hoehe)") + parser.add_argument("--projekt", + help="Projektkennung in den erzeugten IDs " + "(Default: Name der DXF-Datei ohne Endung)") args = parser.parse_args() dxf_path, data_dir, output_path = args.dxf, args.data_dir, args.output + global PROJEKT + PROJEKT = args.projekt or os.path.splitext(os.path.basename(dxf_path))[0] fenster = None if args.fenster: x0, y0, x1, y1 = args.fenster @@ -1407,8 +2169,23 @@ def main(): dim_stat["attribute_uebernommen"], dim_stat["nur_2d"], dim_stat["nur_3d"], len(treffer))) + linien = lese_linien(dxf_path, GF_LINIE_LAYER) + print("%s %d Staustrecken-Linien auf %s" % (PREFIX, len(linien), + "/".join(GF_LINIE_LAYER))) + eintraege, hinweise = baue_eintraege(treffer, defaults, boegen, weichen, - bekannte_bloecke) + bekannte_bloecke, linien, + args.kreisel_hoehe, args.kreisel_achse, + args.flach) + if args.ohne_hoehen: + print("%s --ohne-hoehen: Hoehe in %d Eintraegen auf 0 gesetzt" + % (PREFIX, hoehen_entfernen(eintraege))) + if args.kreisel_hoehe: + print("%s Einzelteile auf Kreisel-Ketten: Hoehe in %d Eintraegen vom " + "Kreisel uebernommen" + % (PREFIX, sensor_hoehen_von_kreiseln( + eintraege, + defaults.get("kreisel", {}).get("durchmesser", 800.0)))) if fenster: vorher = len(eintraege) diff --git a/lib/dxf_vf_abbild.py b/lib/dxf_vf_abbild.py index 88a62d6..6b1584f 100644 --- a/lib/dxf_vf_abbild.py +++ b/lib/dxf_vf_abbild.py @@ -5,7 +5,7 @@ dxf_vf_abbild.py - Abbild der VF_/GF_-Wrapperbloecke einer Testzeichnung: Attribute und Bausteinliste je Kette. Zweck: Regressionsnetz fuer Umbauten am Bau-Ablauf. Nach jedem Schritt der -Funktionstrennung (doc/TODO-plan-vf-interactive.md, Stufe 2) muss die +Funktionstrennung (doc/todo/plan-vf-interactive.md, Stufe 2) muss die gebaute Geometrie unveraendert sein - "unveraendert" heisst hier: gleiche Attribute (inklusive der reihenfolgeabhaengigen Komma-Listen L_VF_m/L_GF_m/ GF_WINKEL, die in der Zeichnung unsichtbar, im Sivas-Export aber falsch diff --git a/lib/export_neighbors.py b/lib/export_neighbors.py index 3b099fe..81d3e44 100644 --- a/lib/export_neighbors.py +++ b/lib/export_neighbors.py @@ -1287,6 +1287,11 @@ def compute_kreisel_umlauf(items, tolerance_mm, durchmesser_mm=800.0, punkte_je_kreisel = {tid: [] for tid in kreisel} + # Ein-/Auslaufpunkt je Strecke (K1 = KS_EIN des AS, K2 = KS_AUS des ES), + # fuer AS/ES, deren Marker-Box keinen Kreisel trifft (siehe unten). + strecken_kos = {it.get("teileid", ""): _asel_kos_xy(it) + for it in items if it.get("teileart", "") in GF_VF_TEILEARTEN} + for item in items: if item.get("teileart", "") not in KREISEL_UMLAUF_TEILEARTEN: continue @@ -1305,6 +1310,31 @@ def compute_kreisel_umlauf(items, tolerance_mm, durchmesser_mm=800.0, (teileid, bbox.get("cx", 0.0), bbox.get("cy", 0.0))) matched_tid = tid break + # Rueckfall fuer AS/ES: der ZZ_ASES_MARKER sitzt auf dem EINFUEGEPUNKT + # des Elements, der Kreisel beruehrt aber dessen Ein-/Auslauf (KS_EIN/ + # KS_AUS). Ein 90-Grad-Element hat den Einlauf ~610 mm seitlich - laeuft + # die Strecke quer vom Kreisel weg, liegt dieser Versatz entlang der + # Kette und der Marker trotzdem darauf; laeuft sie PARALLEL zum + # Kreisel, liegt er quer dazu und der Marker ~560 mm daneben (Dematic/ + # Arvato VF_5 an Kreisel1: Marker 557 mm, Einlauf 52 mm von der Kette, + # Review 2026-10-08). Nur wenn die Marker-Box nichts trifft - bereits + # zugeordnete AS/ES behalten Position und Reihenfolge. + if matched_tid is None and item.get("teileart", "") in ( + AUSSCHLEUS_TEILEART, EINSCHLEUS_TEILEART): + k1_xy, k2_xy = strecken_kos.get(item.get("_zuordnung", ""), (None, None)) + pt = k1_xy if item.get("teileart", "") == AUSSCHLEUS_TEILEART else k2_xy + if pt is not None: + pt_bounds = _bounds({"cx": pt[0], "cy": pt[1], "dx": 0.0, "dy": 0.0}, + half_tol) + for tid, kb in kreisel_bounds.items(): + if _overlaps(pt_bounds, kb): + punkte_je_kreisel[tid].append((teileid, pt[0], pt[1])) + matched_tid = tid + if dbg: + dbg(f" Umlauf-Kandidat {teileid!r}: Marker-Box ohne " + f"Kreisel, Ein-/Auslauf ({pt[0]:.0f},{pt[1]:.0f}) " + f"liegt auf Kreisel {tid}") + break if dbg: if matched_tid is not None: dbg(f" Umlauf-Kandidat {teileid!r} ({item.get('teileart', '')!r}) " diff --git a/lib/export_sivas.py b/lib/export_sivas.py index ccb3ead..e62012f 100644 --- a/lib/export_sivas.py +++ b/lib/export_sivas.py @@ -907,9 +907,9 @@ def format_csv_line(item, details=None): def details_mit_bezeichnung(details, merkmalstamm): """Kopie von details, bei der jeder Merkmalnummer-Key durch seine Klartext-Bezeichnung aus dem Merkmalstamm ersetzt wird (Wert bleibt - unveraendert). Keys, die keine reine Zahl sind (aktuell nur beim - Eckrad, siehe build_eckrad_details - dort noch beschreibende - Klartext-Keys statt Merkmalnummern), bleiben unveraendert.""" + unveraendert). Keys, die keine reine Zahl sind, bleiben unveraendert + (aktuell kein Normalfall mehr - alle Sektionen in cfg/sivas.cfg nutzen + echte Merkmalnummern).""" neu = {} for key, value in details.items(): if key.isdigit(): diff --git a/lib/testdata_schema.py b/lib/testdata_schema.py new file mode 100644 index 0000000..a1e5e11 --- /dev/null +++ b/lib/testdata_schema.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +testdata_schema.py - Feldnamen und feste Werte der JSON-Testdaten-Schemas aus +doc/json-input-lisp.md, als einzige Quelle fuer alle Erzeuger. + +Bisher standen dieselben Feldnamen ("function", "abstand", "deltaL_mm", +"as_seite", ...) als lose String-Literale unabhaengig in dxf_scan_components.py +(fremde Zeichnungen) UND dxf_anlage_export.py (eigene, mit unseren Makros +gebaute Zeichnungen) - ein Tippfehler oder eine Schema-Aenderung in nur einer +der beiden Dateien waere nicht aufgefallen. Dieses Modul buendelt sie an +einer Stelle; die LISP-Treiber (tests/test_*.lsp) lesen die JSON-Keys direkt +als Strings und sind davon nicht betroffen. + +Nur Feldnamen/feste Werte, KEINE Validierung oder Default-Logik - die bleibt +bewusst in den jeweiligen Erzeugern, deren Kontext (fremde vs. eigene +Zeichnung) unterschiedliche Fallbacks braucht. +""" + +# --- Gemeinsame Felder --- +F_FUNCTION = "function" +F_ID = "id" +F_TEST_ID = "test_id" +F_BLOCK = "block" +F_X = "x" +F_Y = "y" +F_Z = "z" +F_ROTATION = "rotation" +F_COMMENT = "comment" + +FUNC_INSERT = "insert" +FUNC_CONNECT = "connect" + +# --- Abschnitt 1 (json-input-lisp.md): Kreisel --- +F_ABSTAND = "abstand" +F_TYP = "typ" +F_DREHRICHTUNG = "drehrichtung" + +TYP_STANDARD = "STANDARD" +TYP_PIN = "PIN" +DREHRICHTUNG_UZS = "UZS" +DREHRICHTUNG_GUZ = "GUZ" + +# --- Abschnitt 3: VarioFoerderer Standard und Etage --- +F_VF_TYP = "typ" +F_RICHTUNG = "richtung" +F_DELTA_L = "deltaL" +F_DELTA_H = "deltaH" +F_WINKEL = "winkel" +F_L_GF1 = "L_GF1" +F_GF_VERTEILUNG = "gf_verteilung" +F_SEITE = "seite" +F_MOTORSEITE = "motorseite" +F_HZ = "hz" + +VF_TYP_STANDARD = "standard" +VF_TYP_ETAGE = "etage" +RICHTUNG_AUF = "Auf" +RICHTUNG_AB = "Ab" +SEITE_LINKS = "links" +SEITE_RECHTS = "rechts" + +# --- Abschnitt 4.1: Gefaellestrecke Modus 1 --- +F_MODUS = "modus" +F_DELTAL_MM = "deltaL_mm" +F_WINKEL_GRAD = "winkel_grad" +F_HZ_GRAD = "hz_grad" +F_START_MM = "start_mm" +F_AS_SEITE = "as_seite" +F_ES_SEITE = "es_seite" +F_AS_WINKEL = "as_winkel" +F_ES_WINKEL = "es_winkel" + +GF_MODUS_WERTEINGABE = 1 +GF_MODUS_RICHTUNG_AUS_PUNKTEN = 2 +GF_MODUS_LINIENZUG = 3 + +SCHLEUS_WINKEL_90 = "90" +SCHLEUS_WINKEL_30 = "30" diff --git a/lib/vf_spec_export.py b/lib/vf_spec_export.py index a52abc4..861b0a4 100644 --- a/lib/vf_spec_export.py +++ b/lib/vf_spec_export.py @@ -7,7 +7,7 @@ uebersetzen und zurueck. Eine Spec beschreibt eine Kette in Domaenenwerten ("links", "winkel", "aussen", ja/nein) statt in Menue-Codes und Journal-Tokens: lesbar, schreibbar und damit die Eingabeform fuer den nicht-interaktiven Bau -(Lisp/vf_spec.lsp, siehe doc/TODO-plan-vf-interactive.md Stufe 1). +(Lisp/vf_spec.lsp, siehe doc/todo/plan-vf-interactive.md Stufe 1). Zwei Richtungen, die einander pruefen: diff --git a/menu/SSG_LIB.cui b/menu/SSG_LIB.cui index 0cd7164..db2be1c 100644 --- a/menu/SSG_LIB.cui +++ b/menu/SSG_LIB.cui @@ -92,10 +92,22 @@ ^C^C(ssg-ensure "VarioFoerderer") EtageVarioFoerderer3D - + - 3D-Linienzug - ^C^C(ssg-ensure "VarioFoerderer") LinienzugVarioFoerderer + Werte manuell + ^C^C(ssg-ensure "VarioFoerderer") LinienzugVarioFoerdererManuell + + + + + aus 3D Linie mit Zielhoehe + ^C^C(ssg-ensure "VarioFoerderer") LinienzugVarioFoerdererZielhoehe + + + + + 3D Objekte stueckweise (in Arbeit) + ^C^C(ssg-ensure "VarioFoerderer") LinienzugVarioFoerdererStueckweise @@ -128,6 +140,18 @@ ^C^C(ssg-ensure "Gefaellestrecke") GEFAELLESTRECKE_LINIENZUG + + + QC Linie mit GF (automatische Auf-Ab-Berechnung) + ^C^C(ssg-ensure "VarioFoerderer") QcLinieMitGf + + + + + QC Linie mit GF und U-Form + ^C^C(ssg-ensure "VarioFoerderer") QcLinieMitGfUForm + + 90 @@ -669,114 +693,168 @@ + + Bloecke zaehlen + ^C^C(ssg-ensure "ssg_blocktools") SSG_BLOCK_COUNT + + + + + Bloecke loeschen + ^C^C(ssg-ensure "ssg_blocktools") SSG_BLOCK_DELETE + + + + + Attribute ersetzen + ^C^C(ssg-ensure "ssg_blocktools") SSG_ATTR_ERSETZEN + + + + + Text ersetzen + ^C^C(ssg-ensure "ssg_texttools") SSG_TEXT_ERSETZEN + + + + + Text parallel beschriften + ^C^C(ssg-ensure "ssg_texttools") SSG_TEXT_PARALLEL + + + + + Texthoehe aendern + ^C^C(ssg-ensure "ssg_texttools") SSG_TEXT_HOEHE + + + + + Textstil aendern + ^C^C(ssg-ensure "ssg_texttools") SSG_TEXT_STIL + + + alle Tests ausfuehren und exportieren ^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_run_all.lsp")) (c:SSG_RUN_ALL_TESTS_EXPORT) - + Tests ausfuehren und stehen lassen ^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_run_all.lsp")) (c:SSG_RUN_ALL_TESTS_OFFEN) - + Export CSV/Sivas ALL ^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_export_all.lsp")) (c:TEST_EXPORT_ALL) - + Kreisel ^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_kreisel.lsp")) (c:TEST_KREISEL) - + Foerderer ^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_foerderer.lsp")) (c:TEST_FOERDERER) - + Foerderer-Linienzug ^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_linienzug.lsp")) (c:TEST_LINIENZUG) (c:TEST_LINIENZUG_STUFE2) - + Omniflo ^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_omniflo.lsp")) (c:TEST_OMNIFLO) - + Omniflo Strecke ^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_omniflo_strecke.lsp")) (c:TEST_OMNIFLO_STRECKE) - + Gefaellestrecke ^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_gefaellestrecke.lsp")) (c:TEST_GEFAELLESTRECKE) - + Mubea Gesamtmodell ^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_mubea.lsp")) (c:TEST_MUBEA) - + HundM05 Kreisel-Abschnitt ^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_hundm05.lsp")) (c:TEST_HUNDM05) - + + + Dematic/Arvato Flowpuffer (Vorlage) + ^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_dematic_arvato.lsp")) (c:TEST_DEMATIC_ARVATO) + + + + + ILS 3.0 Showroom (Vorlage) + ^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_showroom_ils.lsp")) (c:TEST_SHOWROOM_ILS) + + + KS 2D/3D pruefen (CSV) ^C^C(load (strcat (getenv "DXFMAKRO") "/Lisp/ks_pruefung.lsp")) KS_PRUEFEN - + Kreisel bearbeiten (ssg-ensure "KreiselInsert") KreiselEdit - + Omniflo Element bearbeiten (ssg-ensure "OmniModulInsert") OMNI_EDIT - + Gefaellestrecke bearbeiten (ssg-ensure "Gefaellestrecke") GEFAELLESTRECKE_EDIT - + VarioFoerderer bearbeiten (ssg-ensure "VarioFoerderer") VARIOFOERDERER_EDIT - + TRO-Marker bearbeiten (ssg-ensure "Tro_Edit") TRO_EDIT - + Verbindung bearbeiten (ssg-ensure "Connection_Edit") CONNECTION_EDIT - + SSG Bearbeiten (Auto) (ssg-ensure "SSG_LIB_Commands") SSG_BLOCKEDIT @@ -798,6 +876,7 @@ + ILS @@ -865,12 +944,7 @@ Foerderer - - 3D-Linienzug - - - - + Vario_Kette_Merge @@ -914,6 +988,27 @@ + + Streckengruppe + + Werte manuell + + + + + + aus 3D Linie mit Zielhoehe + + + + + + 3D Objekte stueckweise (in Arbeit) + + + + + Gefaellestrecke @@ -934,6 +1029,18 @@ + + QC Linie mit GF (automatische Auf-Ab-Berechnung) + + + + + + QC Linie mit GF und U-Form + + + + Omniflo @@ -1559,125 +1666,183 @@ - Tests + Werkzeuge - alle Tests ausfuehren und exportieren + Bloecke zaehlen - Tests ausfuehren und stehen lassen + Bloecke loeschen - Export CSV/Sivas ALL + Attribute ersetzen - Kreisel + Text ersetzen - Foerderer + Text parallel beschriften - Foerderer-Linienzug + Texthoehe aendern - Omniflo + Textstil aendern + + + Tests - Omniflo Strecke - - - - - - Gefaellestrecke + alle Tests ausfuehren und exportieren - Mubea Gesamtmodell + Tests ausfuehren und stehen lassen - HundM05 Kreisel-Abschnitt + Export CSV/Sivas ALL + + + Kreisel + + + + + + Foerderer + + + + + + Foerderer-Linienzug + + + + + + Omniflo + + + + + + Omniflo Strecke + + + + + + Gefaellestrecke + + + + + + Mubea Gesamtmodell + + + + + + HundM05 Kreisel-Abschnitt + + + + + + Dematic/Arvato Flowpuffer (Vorlage) + + + + + + ILS 3.0 Showroom (Vorlage) + + + + KS 2D/3D pruefen (CSV) - + - + POP501 SSG_LIB Edit Context Kreisel bearbeiten - + Omniflo Element bearbeiten - + Gefaellestrecke bearbeiten - + VarioFoerderer bearbeiten - + TRO-Marker bearbeiten - + Verbindung bearbeiten - + SSG Bearbeiten (Auto) - + diff --git a/menu/SSG_LIB.mnl b/menu/SSG_LIB.mnl index c899576..aff32f1 100644 --- a/menu/SSG_LIB.mnl +++ b/menu/SSG_LIB.mnl @@ -2,7 +2,7 @@ ;; SSG_LIB.mnl - Automatisch geladen beim MENULOAD von SSG_LIB.mnu ;; ;; Definiert den Lazy-Loader (ssg-ensure) und laedt alle Module. -;; Kern-Module: ssg_core, ssg_lang, ssg_dbg, ssg_dialog, ssg_layer, ssg_id +;; Kern-Module: ssg_core, ssg_lang, ssg_dbg, ssg_dialog, ssg_layer, ssg_id, ssg_input ;; Feature-Module: KreiselInsert, Gefaellestrecke, VarioFoerderer, ... ;; ============================================================ @@ -144,6 +144,8 @@ (ssg-ensure "ssg_dialog") (ssg-ensure "ssg_layer") (ssg-ensure "ssg_id") + ;; Generische Eingabe-Wrapper (ssg-in-*), von den Werkzeug-Modulen genutzt + (ssg-ensure "ssg_input") ;; Gemeinsame KS-Extraktion + Block-Einfuegeprimitiven (von Gefaellestrecke ;; UND VarioFoerderer genutzt) - MUSS vor beiden geladen sein. (ssg-ensure "ssg_ks_insert") @@ -159,6 +161,9 @@ ;; Tro_Edit wird nicht vorgeladen - der Dispatcher SSG_BLOCKEDIT und die ;; Menuemakros holen es per (ssg-ensure "Tro_Edit") bei Bedarf nach. (ssg-ensure "SSG_LIB_Commands") + ;; Werkzeuge (Reihenfolge: ssg_texttools nutzt ssg_blocktools) + (ssg-ensure "ssg_blocktools") + (ssg-ensure "ssg_texttools") ;; Menue einhaengen (menucmd "P13=+SSG_LIB.POP1") diff --git a/menu/SSG_LIB.mnu b/menu/SSG_LIB.mnu index 578eece..19be501 100644 --- a/menu/SSG_LIB.mnu +++ b/menu/SSG_LIB.mnu @@ -21,13 +21,18 @@ [->Etage] [Werte manuell]^C^C(ssg-ensure "VarioFoerderer") EtageVarioFoerderer [<-aus 3D-Linie]^C^C(ssg-ensure "VarioFoerderer") EtageVarioFoerderer3D -[3D-Linienzug]^C^C(ssg-ensure "VarioFoerderer") LinienzugVarioFoerderer +[->Streckengruppe] +[Werte manuell]^C^C(ssg-ensure "VarioFoerderer") LinienzugVarioFoerdererManuell +[aus 3D Linie mit Zielhoehe]^C^C(ssg-ensure "VarioFoerderer") LinienzugVarioFoerdererZielhoehe +[<-3D Objekte stueckweise (in Arbeit)]^C^C(ssg-ensure "VarioFoerderer") LinienzugVarioFoerdererStueckweise [Vario_Kette_Merge]^C^C(ssg-ensure "VarioFoerderer") Vario_Kette_Merge [<-VarioFoerderer nachruesten (Marker)]^C^C(ssg-ensure "VarioFoerderer") VARIOFOERDERER_NACHRUESTEN [->Gefaellestrecke] [einfach]^C^C(ssg-ensure "Gefaellestrecke") GEFAELLESTRECKE_EINFACH [aus 3D-Linie]^C^C(ssg-ensure "Gefaellestrecke") GEFAELLESTRECKE_3DLINIE -[<-<-aus Linienzug (LINE + ARC)]^C^C(ssg-ensure "Gefaellestrecke") GEFAELLESTRECKE_LINIENZUG +[aus Linienzug (LINE + ARC)]^C^C(ssg-ensure "Gefaellestrecke") GEFAELLESTRECKE_LINIENZUG +[QC Linie mit GF (automatische Auf-Ab-Berechnung)]^C^C(ssg-ensure "VarioFoerderer") QcLinieMitGf +[<-QC Linie mit GF und U-Form]^C^C(ssg-ensure "VarioFoerderer") QcLinieMitGfUForm [->Omniflo] [->Boegen APB110] [90]^C^C(ssg-ensure "OmniModulInsert") OMNI_APB_630_90 @@ -141,6 +146,15 @@ [Sprache]^C^C(ssg-ensure "SSG_LIB_Commands") SSG_SPRACHE [Planquadrate an/aus]^C^C(ssg-ensure "DrawPlanquadrate") PLANQUADRATE_TOGGLE [<-CSV-Export]^C^C(ssg-ensure "export") CSV_EXPORT_KONFIG +[->Werkzeuge] +[Bloecke zaehlen]^C^C(ssg-ensure "ssg_blocktools") SSG_BLOCK_COUNT +[Bloecke loeschen]^C^C(ssg-ensure "ssg_blocktools") SSG_BLOCK_DELETE +[Attribute ersetzen]^C^C(ssg-ensure "ssg_blocktools") SSG_ATTR_ERSETZEN +[--] +[Text ersetzen]^C^C(ssg-ensure "ssg_texttools") SSG_TEXT_ERSETZEN +[Text parallel beschriften]^C^C(ssg-ensure "ssg_texttools") SSG_TEXT_PARALLEL +[Texthoehe aendern]^C^C(ssg-ensure "ssg_texttools") SSG_TEXT_HOEHE +[<-Textstil aendern]^C^C(ssg-ensure "ssg_texttools") SSG_TEXT_STIL [->Tests] [alle Tests ausfuehren und exportieren]^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_run_all.lsp")) (c:SSG_RUN_ALL_TESTS_EXPORT) [Tests ausfuehren und stehen lassen]^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_run_all.lsp")) (c:SSG_RUN_ALL_TESTS_OFFEN) @@ -154,6 +168,8 @@ [Gefaellestrecke]^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_gefaellestrecke.lsp")) (c:TEST_GEFAELLESTRECKE) [Mubea Gesamtmodell]^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_mubea.lsp")) (c:TEST_MUBEA) [HundM05 Kreisel-Abschnitt]^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_hundm05.lsp")) (c:TEST_HUNDM05) +[Dematic/Arvato Flowpuffer (Vorlage)]^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_dematic_arvato.lsp")) (c:TEST_DEMATIC_ARVATO) +[ILS 3.0 Showroom (Vorlage)]^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_showroom_ils.lsp")) (c:TEST_SHOWROOM_ILS) [<-KS 2D/3D pruefen (CSV)]^C^C(load (strcat (getenv "DXFMAKRO") "/Lisp/ks_pruefung.lsp")) KS_PRUEFEN ***DOUBLECLICK diff --git a/tests/alltests.json b/tests/alltests.json index dc98cb9..d0caa14 100644 --- a/tests/alltests.json +++ b/tests/alltests.json @@ -10,5 +10,7 @@ { "name": "gefaellestrecke", "save": "dwg", "module": "Gefaellestrecke" }, { "name": "mubea", "save": "dxf", "module": "KreiselInsert" }, { "name": "hundm05", "save": "dxf", "module": "VarioFoerderer" }, - { "name": "hm_recformat", "save": "dxf", "module": "VarioFoerderer", "disabled": true } + { "name": "dematic_arvato", "save": "dxf", "module": "VarioFoerderer", "disabled": true }, + { "name": "hm_recformat", "save": "dxf", "module": "VarioFoerderer", "disabled": true }, + { "name": "showroom_ils", "save": "dxf", "module": "VarioFoerderer", "disabled": true } ] diff --git a/tests/test_dematic_arvato.lsp b/tests/test_dematic_arvato.lsp new file mode 100644 index 0000000..a286aa7 --- /dev/null +++ b/tests/test_dematic_arvato.lsp @@ -0,0 +1,417 @@ +;; ============================================================ +;; test_dematic_arvato.lsp - Nachbau des Abschnitts Flowpuffer/Kreisel der +;; Anlage Dematic/Arvato. +;; +;; Datenquelle: tests/testdata/dematic_arvato.json. Entstehung in zwei +;; Stufen: +;; 1. Vorlage aus der Fremdzeichnung (Scanner, raet vieles): +;; python lib/dxf_scan_components.py \ +;; C:/develop/sps_skel/data/Dematic_arvato_reduced.dxf data \ +;; tests/testdata/dematic_arvato.json --projekt Arvato \ +;; --fenster -2534 -4549 49466 29451 --ohne-hoehen \ +;; --kreisel-hoehe 35860.37 12519.72 2437 \ +;; --kreisel-hoehe 36661.24 24878.8 1826 +;; 2. Vorlage mit diesem Treiber bauen, in BricsCAD von Hand korrigieren +;; und ergaenzen (VF-Ketten, Hoehen, Sensoren aufgeraeumt), als DXF +;; speichern und daraus die Testdaten neu schreiben - AKTUELLER STAND: +;; python lib/dxf_anlage_export.py \ +;; C:/develop/sps_skel/data/Dematic_TST.dxf \ +;; tests/testdata/dematic_arvato.json --projekt Arvato +;; +;; Eintragsarten: +;; "function": "insert" ohne "block" -> Kreisel (hundm05:bau-kreisel -> +;; kreisel-insert-script; x/y = Einfuegepunkt, +;; abstand = Mittenabstand AN8-SP8) +;; "function": "insert" + "block" -> Einzelblock (hundm05:bau-sensor) +;; "typ": "standard"/"etage" + "test_id" +;; -> VarioFoerderer ueber mubea:vario-bauen +;; (mubea-Schema: deltaL, deltaH, richtung, +;; winkel, L_GF1, seite, hz, x/y/z), optional +;; "motorseite" +;; "typ": "linienzug_journal" +;; -> Linienzug-Kette: das aufgezeichnete Eingabe- +;; Journal (SSG_VF_EDIT) wird 1:1 abgespielt +;; (vsp-bau-journal) - keine Spec, weil der +;; Dialog neuer ist als die Spec-Grammatik +;; "modus": 1 -> Gefaellestrecke ueber mubea:gf-bauen +;; (start_mm + deltaL_mm, kein Endpunkt). Mit +;; hoehe_ende_mm (Hoehe des Kreisels am Ende) +;; rechnet dematic_arvato:gf-geometrie Winkel +;; und Staustreckenlaenge (gf-bauen-exakt), +;; sonst gilt winkel_grad. +;; "typ": "gerade" -> waagrechter Foerderer aus der Scanner-Vorlage - +;; kein Bau-Pfad, wird gemeldet und uebersprungen +;; +;; Reihenfolge wie mubea.json (VF vor GF): gf-init-bibliothek setzt die +;; globalen AS/ES-Masse um, siehe mubea:ensure-gf-init. +;; +;; Keine eigene Bau-Logik: hundm05:bau-kreisel/-sensor aus +;; tests/test_hundm05.lsp und mubea:gf-bauen/-vario-bauen aus +;; tests/test_mubea.lsp werden mitgeladen (beide Dateien definieren nur +;; Funktionen), Linienzug-Ketten laufen ueber vsp-bau-journal (vf_spec.lsp). +;; +;; Aufruf in BricsCAD: +;; (load (strcat (getenv "DXFMAKRO") "/tests/test_dematic_arvato.lsp")) +;; TEST_DEMATIC_ARVATO +;; ============================================================ + +(if (not (boundp '*dematic-arvato-records*)) (setq *dematic-arvato-records* nil)) + +;; Module und die Bau-Funktionen aus test_hundm05.lsp beim LADEN dieser Datei +;; holen, nicht erst in c:TEST_DEMATIC_ARVATO: in BricsCAD werden per load +;; definierte Funktionen erst nach Rueckkehr zum Top-Level sichtbar (siehe +;; tests/test_unit.scr). +(ssg-ensure "KreiselInsert") +(ssg-ensure "VarioFoerderer") +(ssg-ensure "SSG_LIB_Commands") +(ssg-ensure "Gefaellestrecke") +(if (null (car (atoms-family 1 '("HUNDM05:BAU-SENSOR")))) + (load (strcat (getenv "DXFMAKRO") "/tests/test_hundm05.lsp"))) +(if (null (car (atoms-family 1 '("MUBEA:VARIO-BAUEN")))) + (load (strcat (getenv "DXFMAKRO") "/tests/test_mubea.lsp"))) + +(defun dematic_arvato:datei () + (strcat (getenv "DXFMAKRO") "/tests/testdata/dematic_arvato.json")) + +;; Record um den gebauten Block ergaenzen (oder als Abbruch markieren). +(defun dematic_arvato:rec-block (rec ent fehlertext / ed) + (if (and ent (= (type ent) 'ENAME)) + (progn + (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")) + (progn + (princ (strcat " FEHLER: " fehlertext)) + (vsp-set (vsp-set rec "status" "abbruch") "fehler_text" fehlertext)))) + +;; Foerderer "gerade": noch kein Bau-Pfad - nur melden. +(defun dematic_arvato:gerade-melden (obj / tid rec) + (setq tid (ssg-val obj "test_id")) + (princ (strcat "\n [GERADE] " (if tid tid "?") + " uebersprungen - waagrechter Foerderer noch nicht unterstuetzt")) + (setq rec (vsp-rec-neu (if tid tid "(ohne id)"))) + (setq rec (vsp-set rec "kind" "gerade")) + (vsp-set (vsp-set rec "status" "uebersprungen") "fehler_text" + "kein Bau-Pfad fuer typ gerade")) + +;; ES-Element bei Einlaufneigung w (Grad, bergab) in Fahrtrichtung hz +;; vermessen: horizontale Laenge entlang hz und Abfall von KS_EIN bis KS_AUS. +;; Wie gf-messe-dz-block (Gefaellestrecke.lsp), liefert aber zusaetzlich die +;; Laenge - beide aendern sich mit der Neigung (90-Grad-ES flach ~436 mm +;; lang, bei 43 Grad nur noch ~292 mm). +;; Rueckgabe: (dx dz), dz positiv = nach unten. +(defun dematic_arvato:es-mass (blockname hz w / rad-h rad-v frame-aus ent-vor ent-nach p) + (setq rad-h (* hz (/ pi 180.0)) + rad-v (* w (/ pi 180.0))) + (setq ent-vor (entlast)) + (setq frame-aus + (insert-block-ks-to-ks blockname + (make-frame-from-dir '(0 0 0) + (list (* (cos rad-h) (cos rad-v)) (* (sin rad-h) (cos rad-v)) + (- (sin rad-v)))))) + (setq ent-nach (entlast)) + (if (and ent-nach (not (eq ent-nach ent-vor))) (entdel ent-nach)) + (setq p (car frame-aus)) + (list (+ (* (car p) (cos rad-h)) (* (cadr p) (sin rad-h))) + (- 0.0 (caddr p)))) + +;; Winkel UND Staustreckenlaenge einer GF (Modus 1) aus Start- und Endhoehe, +;; so dass der ES-Auslauf genau auf Hoehe UND Kette des unteren Kreisels +;; liegt. deltaL reicht von AS-KS_EIN bis ES-KS_AUS (horizontal). +;; +;; H = deltaL - aus-dx - es_dx(w) horizontale Laenge der Schraege +;; tan(w) = (dH - aus-dz - es_dz(w)) / H Schraege = Staustrecke + Separator +;; L_stau = H / cos(w) - 300 +;; +;; es_dx/es_dz werden beim aktuellen Winkel gemessen (dematic_arvato:es-mass) +;; und iteriert; das AS liegt flach, aus-dx/aus-dz sind fest. +;; +;; Warum nicht mubea:gf-bauen / gf-winkel-berechnen: +;; - gf-winkel-berechnen rechnet asin(dH / L), also mit SCHRAEGER Laenge +;; (Modus 3); Modus 1 haelt aber die horizontale Laenge fest. Bei 2-3 Grad +;; (Dematic) gleich, bei den steilen Showroom-GF (1860 mm auf 2405 mm) +;; ~50 statt ~43 Grad - die GF endete weit unter dem Kreisel. +;; - mubea:gf-bauen zieht die FLACH gemessene ES-Laenge ein-dx ab. Bei 43 +;; Grad ist das ES horizontal ~144 mm kuerzer, die GF hoerte so 144 mm +;; vor der Kette des unteren Kreisels auf (Hoehe stimmte). +;; VORZEICHEN: vf-set-as-masse misst aus-dz als KS_AUS.z - KS_EIN.z +;; (negativ) - darum der Betrag (Dematic: sonst HOEHE_BIS 1568 statt 1826). +;; Rueckgabe: (winkel L_stau) +(defun dematic_arvato:gf-geometrie (deltaL hz as-seite es-seite as-winkel es-winkel + z-start z-ende / es-block dH es H winkel vorher + iter dx-as dz-as) + (mubea:ensure-gf-init) + (vf-set-as-masse as-winkel as-seite) + (vf-set-es-masse es-winkel es-seite) + (setq es-block (strcat "ES_Element_" es-winkel "_" es-seite) + dx-as (float aus-dx) + dz-as (abs (float aus-dz)) + dH (- z-start z-ende) + es (list (float ein-dx) (abs (float ein-dz))) + vorher -999.0 + iter 0) + (setq H (- deltaL dx-as (car es)) + winkel (* (atan (- dH dz-as (cadr es)) H) (/ 180.0 pi))) + (while (and (< iter 8) (> (abs (- winkel vorher)) 0.0005)) + (setq vorher winkel + es (dematic_arvato:es-mass es-block hz winkel) + H (- deltaL dx-as (car es)) + winkel (* (atan (- dH dz-as (cadr es)) H) (/ 180.0 pi)) + iter (1+ iter))) + (vf-set-as-masse "90" as-seite) + (vf-set-es-masse "90" es-seite) + (list winkel (- (/ H (cos (* winkel (/ pi 180.0)))) 300.0))) + +;; GF mit vorgegebener Staustreckenlaenge einfuegen (wie mubea:gf-bauen, aber +;; L_stau kommt aus dematic_arvato:gf-geometrie statt aus der flachen +;; ES-Laenge). +(defun dematic_arvato:gf-bauen-exakt (tid start-mm deltaL hz as-seite es-seite + as-winkel es-winkel winkel L_stau / ent) + (mubea:ensure-gf-init) + ;; Element-Masse der Variante wie in mubea:gf-bauen, danach zurueck auf 90 + (vf-set-as-masse as-winkel as-seite) + (vf-set-es-masse es-winkel es-seite) + (princ (strcat "\n [GF] " tid " -> Start=(" (rtos (car start-mm) 2 0) + "," (rtos (cadr start-mm) 2 0) "," (rtos (caddr start-mm) 2 0) + ") hz=" (rtos hz 2 0) " deltaL=" (rtos deltaL 2 0) + " winkel=" (rtos winkel 2 3) " L_stau=" (rtos L_stau 2 1))) + (setq ent (car (gefaellestrecke-einfuegen + L_stau winkel start-mm as-seite es-seite hz + deltaL as-winkel es-winkel "0" "Schoenenberger Geruest"))) + (vf-set-as-masse "90" as-seite) + (vf-set-es-masse "90" es-seite) + ent) + +;; Gefaellestrecke (Modus 1) bauen - Felder wie mubea:build-gf, ohne dessen +;; anzahl/delta-Vervielfaeltigung (jede GF steht einzeln in der Datei). +;; Mit "hoehe_ende_mm" (Hoehe des Kreisels am Ende) wird der Winkel daraus +;; gerechnet, sonst gilt "winkel_grad" (Default 3.0). +(defun dematic_arvato:bau-gf (obj / tid start deltaL hz winkel z-ende geo + as-seite es-seite as-winkel es-winkel rec) + (setq tid (ssg-val obj "test_id") + start (ssg-val obj "start_mm") + deltaL (ssg-val obj "deltaL_mm") + hz (ssg-val obj "hz_grad") + winkel (ssg-val obj "winkel_grad") + z-ende (ssg-val obj "hoehe_ende_mm") + as-seite (cond ((ssg-val obj "as_seite")) ("links")) + es-seite (cond ((ssg-val obj "es_seite")) ("links")) + as-winkel (cond ((ssg-val obj "as_winkel")) ("90")) + es-winkel (cond ((ssg-val obj "es_winkel")) ("90"))) + (setq rec (vsp-rec-neu (if tid tid "(ohne id)"))) + (setq rec (vsp-set rec "kind" "gefaellestrecke")) + (setq rec (vsp-set rec "dimension" (vsp-dim-text))) + (cond + ((not (and (= (type start) 'LIST) (= (length start) 3) + (numberp deltaL) (numberp hz))) + (princ (strcat "\n [GF] " (if tid tid "?") + " - FEHLER: start_mm, deltaL_mm und hz_grad muessen gesetzt sein")) + (vsp-set (vsp-set rec "status" "spec-fehler") "fehler_text" + "start_mm, deltaL_mm oder hz_grad fehlt bzw. ist ungueltig")) + ((and (numberp z-ende) (>= z-ende (caddr start))) + (princ (strcat "\n [GF] " (if tid tid "?") + " - FEHLER: hoehe_ende_mm liegt nicht unter der Starthoehe")) + (vsp-set (vsp-set rec "status" "spec-fehler") "fehler_text" + "hoehe_ende_mm >= start_mm.z - kein Gefaelle")) + (T + ;; Mit Endhoehe: Winkel + Staustreckenlaenge exakt (gf-geometrie), + ;; sonst fester Winkel ueber mubea:gf-bauen. + (setq geo (if (numberp z-ende) + (dematic_arvato:gf-geometrie (float deltaL) (float hz) + as-seite es-seite as-winkel es-winkel + (float (caddr start)) (float z-ende)))) + (setq winkel + (cond + (geo (car geo)) + ((numberp winkel) (float winkel)) + (T 3.0))) + ;; Winkel und Soll-Endhoehe als Meldung (das Record-Schema aus vf_spec + ;; kennt dafuer kein Feld); die Ist-Endhoehe steht in attribs HOEHE_BIS. + (setq rec (vsp-set rec "meldungen" + (list (strcat "winkel_grad " (rtos winkel 2 3) + (if (numberp z-ende) + (strcat " aus Hoehe " (rtos (caddr start) 2 1) + " -> " (rtos z-ende 2 1)) + ""))))) + (setq rec + (dematic_arvato:rec-block rec + (if geo + (dematic_arvato:gf-bauen-exakt tid (mapcar 'float start) (float deltaL) + (float hz) as-seite es-seite as-winkel + es-winkel winkel (cadr geo)) + (mubea:gf-bauen tid (mapcar 'float start) (float deltaL) (float hz) + as-seite es-seite as-winkel es-winkel winkel)) + "gefaellestrecke-einfuegen lieferte keinen GF-Block")) + ;; Ist-Endhoehe gegen den Kreisel am Ende pruefen + (dematic_arvato:gf-endhoehe-pruefen rec z-ende)))) + +;; HOEHE_BIS_mm des gebauten GF-Blocks mit der Soll-Endhoehe vergleichen; +;; mehr als 5 mm daneben -> status "warnung" + Meldung (die GF trifft den +;; unteren Kreisel nicht). +(defun dematic_arvato:gf-endhoehe-pruefen (rec z-ende / ist diff) + (setq ist (cdr (assoc "HOEHE_BIS_mm" (ssg-val rec "attribs")))) + (cond + ((not (and (numberp z-ende) ist (= (ssg-val rec "status") "executed"))) rec) + (T + (setq diff (- (atof ist) z-ende)) + (setq rec (vsp-set rec "meldungen" + (append (ssg-val rec "meldungen") + (list (strcat "HOEHE_BIS " ist " (Soll " + (rtos z-ende 2 0) ", Abweichung " + (rtos diff 2 0) " mm)"))))) + (if (> (abs diff) 5.0) + (progn + (princ (strcat "\n WARNUNG: GF endet bei " ist " statt " + (rtos z-ende 2 0))) + (vsp-set rec "status" "warnung")) + rec)))) + +;; VarioFoerderer "standard"/"etage" - mubea-Schema, Bau ueber +;; mubea:vario-bauen. "motorseite" wirkt wie im Edit-Dialog ueber den +;; Override *vf-motorseite* (vf-block-erstellen liest ihn). +(defun dematic_arvato:bau-vf (obj / tid typ rec ent) + (setq tid (ssg-val obj "test_id") + typ (cond ((ssg-val obj "typ")) ("standard"))) + (setq rec (vsp-rec-neu (if tid tid "(ohne id)"))) + (setq rec (vsp-set rec "kind" "vario")) + (setq rec (vsp-set rec "dimension" (vsp-dim-text))) + (setq *vf-motorseite* (ssg-val obj "motorseite")) + (setq ent + (vl-catch-all-apply 'mubea:vario-bauen + (list tid typ (ssg-val obj "richtung") + (float (ssg-val obj "deltaL")) (float (ssg-val obj "deltaH")) + (ssg-val obj "winkel") (float (cond ((ssg-val obj "L_GF1")) (0.0))) + (ssg-val obj "seite") (ssg-val obj "hz") + (list (float (ssg-val obj "x")) (float (ssg-val obj "y")) + (float (ssg-val obj "z")))))) + (setq *vf-motorseite* nil) + (if (vl-catch-all-error-p ent) + (vsp-set (vsp-set rec "status" "abbruch") "fehler_text" + (vl-catch-all-error-message ent)) + (dematic_arvato:rec-block rec ent + "kein VF-Block (deltaL beim gewuenschten Winkel nicht loesbar?)"))) + +;; Linienzug-Kette: aufgezeichnetes Journal ("ART:wert"-Tokens, wie in der +;; SSG_VF_EDIT-XDATA) 1:1 abspielen. Headless-Rahmen wie vsp-bau-datei; er +;; MUSS auch nach einem Fehler zurueck, sonst bleiben getstring/getpoint der +;; Sitzung ersetzt. +(defun dematic_arvato:bau-journal (obj / tid journal res) + (setq tid (cond ((ssg-val obj "test_id")) ("(ohne id)"))) + (setq journal (mapcar 'vfl-string->entry (ssg-val obj "journal"))) + (vsp-headless-an) + (setq res (vl-catch-all-apply 'vsp-bau-journal + (list tid journal nil + (if journal nil (list "Feld \"journal\" fehlt oder ist leer"))))) + (vsp-headless-aus) + (if (vl-catch-all-error-p res) + (vsp-set (vsp-set (vsp-rec-neu tid) "status" "abbruch") "fehler_text" + (vl-catch-all-error-message res)) + res)) + +(defun dematic_arvato:bau-eintrag (obj / kind res typ) + (setq typ (ssg-val obj "typ")) + (cond + ((ssg-val obj "modus") + (setq kind "gefaellestrecke") + (setq res (vl-catch-all-apply 'dematic_arvato:bau-gf (list obj)))) + ((= typ "linienzug_journal") + (setq kind "linienzug") + (setq res (dematic_arvato:bau-journal obj))) + ((and (member typ '("standard" "etage")) (ssg-val obj "test_id")) + (setq kind "vario") + (setq res (dematic_arvato:bau-vf obj))) + ((and (= (ssg-val obj "function") "insert") (ssg-val obj "block")) + (setq kind "sensor") + (setq res (vl-catch-all-apply 'hundm05:bau-sensor (list obj)))) + ((= (ssg-val obj "function") "insert") + (setq kind "kreisel") + (setq res (vl-catch-all-apply 'hundm05:bau-kreisel (list obj)))) + ((= typ "gerade") + (setq kind "gerade") + (setq res (dematic_arvato:gerade-melden obj))) + (T + (setq kind "unbekannt") + (princ "\n WARNUNG: unbekannter Eintrag uebersprungen") + (setq res (vsp-set (vsp-rec-neu "(unbekannt)") "status" "uebersprungen")))) + (if (vl-catch-all-error-p res) + (progn + (princ (strcat "\n [" (strcase kind) "] Ausnahme: " + (vl-catch-all-error-message res))) + (vsp-set (vsp-set (vsp-set (vsp-rec-neu (cond ((ssg-val obj "id")) + ((ssg-val obj "test_id")) + ((ssg-val obj "block")) + ("?"))) + "kind" kind) "status" "abbruch") + "fehler_text" (vl-catch-all-error-message res))) + res)) + +;; Alle Eintraege einer Nachbau-Datei bauen und die Records zurueckgeben +;; (nil bei Fehler vor dem Bau). Gemeinsamer Lauf fuer jeden Treiber mit +;; diesem Eintragsschema - auch tests/test_showroom_ils.lsp. +;; befehl = Name fuer Meldungen/ssg-start, titel = Kopfzeile, datei = JSON +(defun dematic_arvato:bau-datei (befehl titel datei / daten out records zaehler st) + (princ "\n\n================================================================") + (princ (strcat "\n " befehl " - " titel)) + (princ (strcat "\n " datei)) + (princ "\n================================================================") + (cond + ((null (car (atoms-family 1 '("VSP-BAU-JOURNAL")))) + (princ (strcat "\n[" befehl "] FEHLER: vf_spec.lsp nicht geladen" + " (VarioFoerderer laden).")) + nil) + ((null (findfile datei)) + (princ (strcat "\n[" befehl "] FEHLER: " datei " nicht gefunden!")) + nil) + (T + (setq daten (ssg-json-laden datei)) + (princ (strcat "\n " (itoa (length daten)) " Eintraege geladen")) + (ssg-start befehl '(("OSMODE") ("CECOLOR") ("ATTREQ") ("ATTDIA"))) + (setvar "OSMODE" 0) + (setvar "ATTREQ" 0) + (setvar "ATTDIA" 0) + ;; Vario-Bibliothek (Bogen-/AS-/ES-Masse) wie C:TEST_MUBEA, und die + ;; GF-Elementmasse in jeder Zeichnung neu ermitteln. + (if (vsp-lib-noetig-p) (init-bibliothek)) + (setq *mubea-gf-init-done* nil) + (setq out '()) + (foreach obj daten + (setq out (cons (dematic_arvato:bau-eintrag obj) out))) + (ssg-end) + (setq records (reverse out)) + ;; Zusammenfassung je Status + (setq zaehler '()) + (foreach r records + (setq st (cond ((ssg-val r "status")) ("?"))) + (if (assoc st zaehler) + (setq zaehler (subst (cons st (1+ (cdr (assoc st zaehler)))) + (assoc st zaehler) zaehler)) + (setq zaehler (cons (cons st 1) zaehler)))) + (princ "\n================================================================") + (foreach z (reverse zaehler) + (princ (strcat "\n " (car z) ": " (itoa (cdr z))))) + (princ "\n================================================================") + (command "_.ZOOM" "_E") + records))) + +(defun c:TEST_DEMATIC_ARVATO () + (setq *dematic-arvato-records* + (dematic_arvato:bau-datei "TEST_DEMATIC_ARVATO" "Abschnitt Flowpuffer/Kreisel" + (dematic_arvato:datei))) + (princ)) + +;; Ergebnis-Export (Konvention :export-results fuer test_run_all.lsp) +(defun dematic_arvato:export-results (out-dir / pfad) + (if (null *dematic-arvato-records*) + (princ "\n Keine Dematic/Arvato-Ergebnisse vorhanden.") + (progn + (vl-mkdir out-dir) + (setq pfad (strcat out-dir "/dematic_arvato_results.json")) + (vsp-results-schreiben *dematic-arvato-records* pfad))) + (princ)) + +(princ "\nTEST_DEMATIC_ARVATO geladen. Aufruf: TEST_DEMATIC_ARVATO") +(princ) diff --git a/tests/test_dxf_anlage_export.py b/tests/test_dxf_anlage_export.py new file mode 100644 index 0000000..22fb36f --- /dev/null +++ b/tests/test_dxf_anlage_export.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +test_dxf_anlage_export.py - Gebaute Anlage -> Nachbau-Testdaten +(lib/dxf_anlage_export.py). Laeuft ohne BricsCAD und ohne DXF: die +Wrapper-Daten sind die Werte aus Dematic_TST.dxf, von Hand nachgestellt +(Form wie lese_zeichnung sie liefert). + + 1. Kreisel: abstand exakt aus der Blockdefinition (AN8 -> SP8), nicht aus + dem ganzzahligen ABSTAND-Attribut. + 2. GF auf zwei Kreiselketten: Endhoehe = Hoehe des Kreisels am Ende, + der (falsch) gebaute Winkel wird NICHT uebernommen. + 3. GF ohne Kreisel am Ende: gebauter Winkel bleibt. + 4. VF "standard": Parameter wie vfs-edit-basis-lesen, L_GF1 aus L_GF_m. + 5. VF "linienzug": Journal unveraendert als Token-Liste. + 6. Einzelblock: _2D/_3D-Suffix entfaellt. +""" + +import os +import sys + +import pytest + +sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), + "..", "lib")) +import dxf_anlage_export as dae # noqa: E402 + + +def _insert(name, p, rot=0.0, attribs=None, xdata=None): + return {"name": name, "handle": "1", "p": list(p), "rot": rot, + "attribs": attribs or {}, "xdata": xdata or {}, "ps": 0} + + +KREISEL_OBEN = _insert("KREISEL_2_STANDARD_33044", (35860.37, 12519.72, 2437.0), + 180.0, {"ID": "0002", "NUMMER": "2", "ABSTAND": "33044", + "KREISELART": "STANDARD"}) +KREISEL_UNTEN = _insert("KREISEL_3_STANDARD_33470", (36661.24, 24878.8, 1826.0), + 180.0, {"ID": "0003", "NUMMER": "3", "ABSTAND": "33470", + "KREISELART": "STANDARD"}) +BLOCK_INHALT = { + "KREISEL_2_STANDARD_33044": [{"name": "AN8_3D", "p": [400.0, 0.0, 0.0]}, + {"name": "SP8_3D", "p": [33444.28, 0.0, 0.0]}], + "KREISEL_3_STANDARD_33470": [{"name": "AN8_3D", "p": [400.0, 0.0, 0.0]}, + {"name": "SP8_3D", "p": [33870.18, 0.0, 0.0]}], +} + + +def _geo(): + return [dae.kreisel_geo(dae.kreisel_eintrag(k, BLOCK_INHALT, "T")) + for k in (KREISEL_OBEN, KREISEL_UNTEN)] + + +def test_kreisel_abstand_aus_blockdefinition(): + k = dae.kreisel_eintrag(KREISEL_OBEN, BLOCK_INHALT, "T") + assert k["id"] == "Kreisel_T_02" + assert k["abstand"] == pytest.approx(33044.28, abs=0.01) + assert (k["x"], k["y"], k["z"], k["rotation"]) == (35860.37, 12519.72, 2437.0, 180.0) + ohne_block = dae.kreisel_eintrag(KREISEL_OBEN, {}, "T") + assert ohne_block["abstand"] == 33044.0 + assert k["drehrichtung"] == "UZS" # Attribut fehlt -> Vorgabe + + +def test_kreisel_drehrichtung_aus_attribut(): + guz = _insert("KREISEL_4_STANDARD_3619", (42563.78, 1703.66, 2177.0), 90.0, + {"NUMMER": "4", "DREHRICHTUNG": "GUZ"}) + assert dae.kreisel_eintrag(guz, {}, "T")["drehrichtung"] == "GUZ" + + +def test_gf_zwischen_zwei_kreiseln_bekommt_endhoehe(): + gf = _insert("GF_1", (5833.56, 12919.72, 2437.0), 0.0, + {"ID": "0005", "SEITE_AS": "links", "SEITE_ES": "links", + "HOEHE_BIS_mm": "1568"}, + {"SSG_GF_EDIT": ["90", "3.7929", "90", "90", "2437", "11559.08"]}) + hinweise = [] + e = dae.gf_eintrag(gf, _geo(), "T", hinweise) + assert e["test_id"] == "GF_T_01" + assert e["start_mm"] == [5833.56, 12919.72, 2437.0] + assert e["deltaL_mm"] == 11559.08 and e["hz_grad"] == 90.0 + assert (e["kreisel_start"], e["kreisel_ende"]) == ("Kreisel_T_02", "Kreisel_T_03") + assert e["hoehe_ende_mm"] == 1826.0 + assert "winkel_grad" not in e + assert hinweise == [] + + +def test_gf_ohne_kreisel_behaelt_gebauten_winkel(): + gf = _insert("GF_7", (0.0, 0.0, 1000.0), 0.0, {}, + {"SSG_GF_EDIT": ["0", "3", "90", "30", "1000", "5000"]}) + e = dae.gf_eintrag(gf, _geo(), "T", []) + assert e["winkel_grad"] == 3.0 + assert "hoehe_ende_mm" not in e and "kreisel_ende" not in e + assert e["es_winkel"] == "30" + + +def test_gf_ohne_xdata_wird_gemeldet(): + hinweise = [] + assert dae.gf_eintrag(_insert("GF_9", (0, 0, 0)), [], "T", hinweise) is None + assert "SSG_GF_EDIT" in hinweise[0] + + +def test_vf_standard(): + vf = _insert("VF_2", (3314.07, 24478.8, 1826.0), 0.0, + {"ID": "0082", "DELTA_L_mm": "11559", "DELTA_H_mm": "611", + "ANTRIEBFAHRTRICHTUNG": "Auf", "SEITE_AS": "links", + "MOTORSEITE": "rechts", "VF_WINKEL": "15", + "L_GF_m": "1.907,1.907"}, + {"SSG_VF_EDIT": ["standard", "270"]}) + e = dae.vf_standard_eintrag(vf, "T", "standard") + assert e["test_id"] == "VF_T_02" + assert (e["richtung"], e["deltaL"], e["deltaH"], e["winkel"]) == ("Auf", 11559.0, 611.0, 15) + assert e["L_GF1"] == 1907.0 and e["hz"] == 270.0 + assert (e["seite"], e["motorseite"]) == ("links", "rechts") + assert (e["x"], e["y"], e["z"]) == (3314.07, 24478.8, 1826.0) + + +def test_vf_linienzug_journal_unveraendert(): + roh = ["linienzug", "PT:42161.679193,2448.319538,0^JREAL:2177^JSTR:1^JSTR:9", + "0^JSTEP:Linie-VF^JDL:9370.410886"] + vf = _insert("VF_26", (42161.68, 2448.32, 2177.0), 0.0, {"ID": "0184"}, + {"SSG_VF_EDIT": roh}) + e = dae.vf_journal_eintrag(vf, "T", 1) + assert e["typ"] == "linienzug_journal" and e["test_id"] == "VF_T_LZ_01" + # Chunk-Grenze mitten im Token ("STR:9" + "0") wird korrekt zusammengesetzt + assert e["journal"] == ["PT:42161.679193,2448.319538,0", "REAL:2177", "STR:1", + "STR:90", "STEP:Linie-VF", "DL:9370.410886"] + + +def test_einzelblock_ohne_dimensionssuffix(): + e = dae.einzel_eintrag(_insert("Separator_SP_3D", (3038.72, 12119.73, 2473.0), + 0.0, {"ID": "0043", "ZUORDNUNG": "0002"})) + assert e["block"] == "Separator_SP" + assert e["function"] == "insert" and e["z"] == 2473.0 + assert "ID 0043" in e["comment"] + + +def test_reihenfolge_vf_vor_gf(): + inserts = [ + _insert("GF_1", (5833.56, 12919.72, 2437.0), 0.0, {}, + {"SSG_GF_EDIT": ["90", "3", "90", "90", "2437", "11559.08"]}), + _insert("Scanner_3D", (1, 2, 3)), + _insert("VF_2", (0, 0, 0), 0.0, {}, {"SSG_VF_EDIT": ["standard", "0"]}), + KREISEL_OBEN, KREISEL_UNTEN, + _insert("*U12", (0, 0, 0)), + ] + eintraege, _ = dae.baue_eintraege(inserts, BLOCK_INHALT, "T") + art = ["K" if "abstand" in e else "GF" if "modus" in e else + "VF" if "typ" in e else e["block"] for e in eintraege] + assert art == ["K", "K", "VF", "GF", "Scanner"] diff --git a/tests/test_dxf_scan_gf.py b/tests/test_dxf_scan_gf.py new file mode 100644 index 0000000..3768974 --- /dev/null +++ b/tests/test_dxf_scan_gf.py @@ -0,0 +1,288 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +test_dxf_scan_gf.py - Gefaellestrecken aus Staustrecken-Linien +(gf_eintraege_aus_linien in lib/dxf_scan_components.py). Laeuft ohne +BricsCAD und ohne DXF: die Linien und Ausschleuselemente sind die +gemessenen Werte aus Dematic_arvato_reduced.dxf, von Hand nachgestellt. + + 1. Fallende Linie mit AS am oberen Ende -> GF, AS wird verbraucht. + 2. Der Nachbar-AS der Linie daneben (800 mm Raster, auf der anderen Seite + naeher dran) wird NICHT gegriffen - die Seite aus dem Blocknamen + entscheidet, nicht der kleinste Abstand. + 3. Rueckwaerts gezeichnete GF (tiefes Ende zuerst) -> Start bleibt oben. + 4. AS am UNTEREN Ende -> Steigfoerderer, keine GF, AS bleibt unverbraucht. + 5. Waagrechte Linie -> keine GF. + 6. Anschluss an die Kreisel: Start/Ende werden auf die Ketten geschoben, + deltaL reicht von Kette zu Kette; Hoehen kommen von den Kreiseln. + 7. Separator auf der Achse wird verbraucht, einer daneben nicht. + 8. hoehen_entfernen laesst nur Anwender-Hoehen stehen. +""" + +import os +import sys + +import pytest + +sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), + "..", "lib")) +import dxf_scan_components as dsc # noqa: E402 + +AS_LINKS = "Ausschleuselement links 200000217 Draufsicht" + + +def _as(x, y, z=3191.63, block=AS_LINKS): + return {"familie": "schleuselement", "block": block, + "x": x, "y": y, "z": z, "rot": 270.0} + + +def _linie(a, b, layer="6-SP"): + return {"layer": layer, "a": list(a), "b": list(b)} + + +@pytest.fixture(autouse=True) +def _projekt(monkeypatch): + monkeypatch.setattr(dsc, "PROJEKT", "Test") + + +# Die beiden Kreisel am GF-Feld (Radmitten AN8 -> SP8, r = 400), wie +# baue_eintraege sie als kreisel_geo anlegt. Ketten: oben y = 12919.72, +# unten y = 24478.8. +def _kreisel_oben(z=2437.0): + return {"id": "K_oben", "a": (35460.37, 12519.72), "u": (-1.0, 0.0), + "laenge": 33044.28, "r": 400.0, "z_anwender": z} + + +def _kreisel_unten(z=1826.0): + return {"id": "K_unten", "a": (36261.24, 24878.8), "u": (-1.0, 0.0), + "laenge": 33470.18, "r": 400.0, "z_anwender": z} + + +def test_gf_mit_as_am_oberen_ende(): + linie = _linie((5833.56, 13348.38, 3191.63), (5833.44, 24002.54, 2821.63)) + as_eigen = _as(5367.10, 14505.61) + eintraege, belegt, hinweise = dsc.gf_eintraege_aus_linien([linie], [as_eigen]) + assert len(eintraege) == 1 + gf = eintraege[0] + assert gf["test_id"] == "GF_Test_01" + assert gf["modus"] == 1 + # ohne Kreisel: Linienende bleibt stehen, Hoehe unbekannt -> Platzhalter + assert gf["start_mm"] == [5833.56, 13348.38, 0.0] + assert gf["deltaL_mm"] == pytest.approx(10654.16, abs=0.01) + assert gf["hz_grad"] == 90.0 + assert gf["winkel_grad"] == 3.0 + assert gf["hoehe_unbekannt"] is True + assert (gf["as_seite"], gf["as_winkel"]) == ("links", "90") + assert belegt == {id(as_eigen)} + assert len(hinweise) == 2 and all("kein Kreisel" in h for h in hinweise) + + +def test_anschluss_an_beide_kreisel_mit_hoehen(): + linie = _linie((5833.56, 13348.38, 3191.63), (5833.44, 24002.54, 2821.63)) + eintraege, _, hinweise = dsc.gf_eintraege_aus_linien( + [linie], [_as(5367.10, 14505.61)], [_kreisel_oben(), _kreisel_unten()]) + gf = eintraege[0] + assert gf["start_mm"][1] == pytest.approx(12919.72, abs=0.01) + assert gf["start_mm"][2] == 2437.0 + assert gf["hoehe_ende_mm"] == 1826.0 + assert gf["hoehe_quelle"] == "Anwender" + assert "winkel_grad" not in gf # rechnet der LISP-Treiber + assert gf["deltaL_mm"] == pytest.approx(24478.8 - 12919.72, abs=0.05) + assert (gf["kreisel_start"], gf["kreisel_ende"]) == ("K_oben", "K_unten") + assert hinweise == [] + + +def test_kreisel_ohne_hoehe_gibt_platzhalter(): + linie = _linie((5833.56, 13348.38, 3191.63), (5833.44, 24002.54, 2821.63)) + eintraege, _, _ = dsc.gf_eintraege_aus_linien( + [linie], [], [_kreisel_oben(z=None), _kreisel_unten()]) + assert eintraege[0]["kreisel_start"] == "K_oben" + assert "hoehe_ende_mm" not in eintraege[0] + assert eintraege[0]["winkel_grad"] == 3.0 + + +def test_separator_auf_achse_wird_verbraucht(): + linie = _linie((5833.56, 13348.38, 3191.63), (5833.44, 24002.54, 2821.63)) + sep_achse = {"familie": "sensor", "block": "S-SP", "x": 5834.0, "y": 23810.0} + sep_daneben = {"familie": "sensor", "block": "S-SP", "x": 6734.0, "y": 23810.0} + scanner = {"familie": "sensor", "block": "Scanner", "x": 5834.0, "y": 20000.0} + _, belegt, _ = dsc.gf_eintraege_aus_linien( + [linie], [], [_kreisel_oben(), _kreisel_unten()], + [sep_achse, sep_daneben, scanner]) + assert belegt == {id(sep_achse)} + + +def test_hoehen_entfernen_laesst_anwender_hoehen_stehen(): + eintraege = [ + {"id": "K1", "x": 0, "y": 0, "z": 2437.0, "hoehe_quelle": "Anwender"}, + {"block": "S-SP", "x": 0, "y": 0, "z": 2821.63, "comment": ""}, + {"test_id": "G", "typ": "gerade", "startpunkt": [0, 0, 5.0], + "endpunkt": [1, 0, 5.0]}, + ] + assert dsc.hoehen_entfernen(eintraege) == 2 + assert eintraege[0]["z"] == 2437.0 + assert eintraege[1]["z"] == 0.0 and eintraege[1]["hoehe_unbekannt"] is True + assert eintraege[2]["startpunkt"][2] == 0.0 and eintraege[2]["endpunkt"][2] == 0.0 + + +def test_nachbar_as_auf_falscher_seite_wird_nicht_gegriffen(): + # Linie bei x=5834; ihr AS 467 mm links, der AS der Nachbarlinie + # (x=6734) 433 mm RECHTS - also naeher, aber auf der falschen Seite. + linie = _linie((5833.56, 13348.38, 3191.63), (5833.44, 24002.54, 2821.63)) + as_eigen = _as(5367.10, 14505.61) + as_nachbar = _as(6267.10, 14505.61) + _, belegt, _ = dsc.gf_eintraege_aus_linien([linie], [as_nachbar, as_eigen]) + assert belegt == {id(as_eigen)} + + +def test_rueckwaerts_gezeichnete_gf_startet_oben(): + linie = _linie((5833.44, 24002.54, 2821.63), (5833.56, 13348.38, 3191.63)) + eintraege, _, _ = dsc.gf_eintraege_aus_linien([linie], [_as(5367.10, 14505.61)]) + assert eintraege[0]["start_mm"][:2] == [5833.56, 13348.38] + assert eintraege[0]["hz_grad"] == 90.0 + + +def test_as_am_unteren_ende_ist_steigfoerderer(): + # Gemessen: Linie x=2801 von 2822 hinauf auf 3192, AS am tiefen Ende. + linie = _linie((2801.42, 24043.0, 2821.63), (2801.42, 13389.0, 3191.63)) + as_unten = _as(3267.48, 22892.92, z=2821.63) + eintraege, belegt, hinweise = dsc.gf_eintraege_aus_linien([linie], [as_unten]) + assert eintraege == [] + assert belegt == set() + assert len(hinweise) == 1 and "Steigfoerderer" in hinweise[0] + + +def test_waagrechte_linie_ist_keine_gf(): + linie = _linie((36661.0, 10279.0, 2821.63), (36661.0, 24879.0, 2821.63)) + eintraege, belegt, hinweise = dsc.gf_eintraege_aus_linien([linie], []) + assert eintraege == [] + assert belegt == set() + assert any("waagrechte" in h for h in hinweise) + + +def test_ohne_as_defaults_und_hinweis(): + linie = _linie((0.0, 0.0, 1000.0), (5000.0, 0.0, 800.0)) + eintraege, _, hinweise = dsc.gf_eintraege_aus_linien([linie], []) + assert eintraege[0]["hz_grad"] == 0.0 + assert eintraege[0]["as_seite"] == dsc.DEFAULT_SEITE + assert any("ohne Ausschleuselement" in h for h in hinweise) + + +# --- Showroom_ILS_3_0.dxf: flacher 2D-Plan, Werte dort gemessen ------------- + +def _station(x, y): + return {"x": x, "y": y} + + +def test_kreisel_paare_zwei_kreisel_auf_einer_achse(): + # x=26998: AN8 y=3466 / SP8 y=12499 und AN8 y=13299 / SP8 y=16299. Die + # Luecke SP8(12499)-AN8(13299) = 800 ist das kuerzeste Paar - ohne + # Tausch-Nachbesserung kamen 800 + 12833 statt 9033 + 3000 heraus. + an = [_station(26998.0, 3466.4), _station(26998.0, 13298.9)] + sp = [_station(26998.0, 16298.9), _station(26998.0, 12498.9)] + paare, an_rest, sp_rest = dsc.kreisel_paare_bilden(an, sp) + assert an_rest == [] and sp_rest == [] + abstaende = sorted(round(p[2], 1) for p in paare) + assert abstaende == [3000.0, 9032.5] + + +def test_zerstueckelte_linie_wird_vereint(): + # GF bei x=34006 in vier Stuecken, das Separatorstueck 0,3 mm daneben + stuecke = [ + _linie((34005.93, 13893.90, 0.0), (34005.93, 14193.90, 0.0)), + _linie((34005.93, 15043.90, 0.0), (34005.93, 15593.90, 0.0)), + _linie((34006.24, 14744.31, 0.0), (34006.24, 15044.31, 0.0)), + _linie((34005.93, 14193.90, 0.0), (34005.93, 14743.90, 0.0)), + ] + vereint = dsc.linien_zusammenfassen(stuecke) + assert len(vereint) == 1 + ys = sorted([vereint[0]["a"][1], vereint[0]["b"][1]]) + assert ys == pytest.approx([13893.90, 15593.90], abs=0.01) + + +def test_parallele_nachbarlinie_wird_nicht_vereint(): + linien = [_linie((0.0, 0.0, 0.0), (0.0, 1000.0, 0.0)), + _linie((800.0, 1000.0, 0.0), (800.0, 2000.0, 0.0))] + assert len(dsc.linien_zusammenfassen(linien)) == 2 + + +def _kreisel_d(): + # Schleife oben (Kette unten y=15898.9), Radmitten per --kreisel-achse + return {"id": "K_D", "a": (27797.93, 16298.9), "u": (1.0, 0.0), + "laenge": 8908.0, "r": 400.0, "z_anwender": None} + + +def _kreisel_e(): + # Schleife Mitte (Kette oben y=13493.9) + return {"id": "K_E", "a": (28772.93, 13093.9), "u": (1.0, 0.0), + "laenge": 6533.0, "r": 400.0, "z_anwender": None} + + +def test_flacher_plan_richtung_und_seiten_aus_as_es(): + # Waagrechte Linie; AE-D sitzt 305 mm VOR dem oberen Ende auf der + # Kreisel-Kette, 180 mm links der Fahrtrichtung (Fahrt -y, links = +x). + # EE-R am unteren Ende 212 mm rechts davon. + linie = _linie((30455.93, 13893.9, 0.0), (30455.93, 15593.9, 0.0)) + ae = {"familie": "schleuselement", "block": "AE-D", + "x": 30636.27, "y": 15898.9, "z": 0.0, "rot": 270.0} + ee = {"familie": "schleuselement", "block": "EE-R", + "x": 30243.76, "y": 13493.9, "z": 0.0, "rot": 90.0} + eintraege, belegt, _ = dsc.gf_eintraege_aus_linien( + [linie], [ae], [_kreisel_d(), _kreisel_e()], es_liste=[ee], flach=True) + assert len(eintraege) == 1 + gf = eintraege[0] + assert gf["hz_grad"] == 270.0 + assert gf["start_mm"][:2] == [30455.93, 15898.9] + assert gf["deltaL_mm"] == pytest.approx(2405.0, abs=0.01) + assert (gf["as_seite"], gf["es_seite"]) == ("links", "rechts") + assert (gf["kreisel_start"], gf["kreisel_ende"]) == ("K_D", "K_E") + assert belegt == {id(ae), id(ee)} + + +def test_flacher_plan_ohne_flach_schalter_keine_gf(): + linie = _linie((30455.93, 13893.9, 0.0), (30455.93, 15593.9, 0.0)) + ae = {"familie": "schleuselement", "block": "AE-D", + "x": 30636.27, "y": 15898.9, "z": 0.0, "rot": 270.0} + eintraege, _, _ = dsc.gf_eintraege_aus_linien([linie], [ae]) + assert eintraege == [] + + +def test_flacher_plan_ohne_as_keine_gf(): + # Staulinie am Kreisel B (x=36348) - kein AS an einem Ende + linie = _linie((36347.6, 11313.9, 0.0), (36347.6, 14913.9, 0.0)) + eintraege, _, hinweise = dsc.gf_eintraege_aus_linien([linie], [], flach=True) + assert eintraege == [] + assert any("waagrechte" in h for h in hinweise) + + +def test_kurznamen_ae_ee(): + assert dsc.familie_von("AE-D") == "schleuselement" + assert dsc.familie_von("EE-R") == "schleuselement" + assert dsc.mappe_block("AE-D")[0].startswith("AS_Element_90_") + assert dsc.mappe_block("EE-R")[0].startswith("ES_Element_90_") + + +def test_sensor_bekommt_hoehe_des_kreisels(): + # Kreisel_Showroom_03: Einfuegepunkt (37105.93, 9888.9), rotation 180, + # Ketten y=9488.9/10288.9. Separator auf der Kette, Scanner 280 mm + # innerhalb, ein Teil 1000 mm daneben bleibt unberuehrt. + kreisel = {"id": "K3", "x": 37105.93, "y": 9888.9, "z": 1700.0, + "abstand": 7200.0, "rotation": 180.0, "hoehe_quelle": "Anwender"} + sep = {"function": "insert", "block": "S-LP", "x": 30343.39, "y": 9488.9, + "z": 0.0, "hoehe_unbekannt": True, "comment": ""} + scan = {"function": "insert", "block": "Scanner", "x": 35905.93, + "y": 10008.9, "z": 0.0, "comment": ""} + weit = {"function": "insert", "block": "S-LP", "x": 33000.0, "y": 11300.0, + "z": 0.0, "comment": ""} + assert dsc.sensor_hoehen_von_kreiseln([kreisel, sep, scan, weit]) == 2 + assert sep["z"] == 1700.0 and "hoehe_unbekannt" not in sep + assert scan["z"] == 1700.0 + assert weit["z"] == 0.0 + + +def test_sensor_ohne_anwender_hoehe_bleibt(): + kreisel = {"id": "K", "x": 0.0, "y": 0.0, "z": 0.0, "abstand": 5000.0, + "rotation": 0.0} + sep = {"function": "insert", "block": "S-LP", "x": 1000.0, "y": 400.0, + "z": 0.0, "comment": ""} + assert dsc.sensor_hoehen_von_kreiseln([kreisel, sep]) == 0 diff --git a/tests/test_export_kreisel_umlauf.py b/tests/test_export_kreisel_umlauf.py index 2d712b3..b2bce05 100644 --- a/tests/test_export_kreisel_umlauf.py +++ b/tests/test_export_kreisel_umlauf.py @@ -485,3 +485,56 @@ class TestAsEsImKreiselUmlauf: keys = [p[0] for p in umlauf["0001"]] assert "0057" not in keys, ( "ES-Item weit ausserhalb der Kreiselbox wurde faelschlich erkannt") + + +def _trans(x, y, z=0.0): + """Gegenstueck zu _decode_trans_xy (csv:trans-encode in export.lsp): drei + vorzeichenbehaftete 24-Bit-Fixed-Point-Werte (Faktor 10), je 4 Zeichen.""" + from export_neighbors import _B64_CHARS + text = "" + for v in (x, y, z): + n = int(round(v * 10.0)) & 0xFFFFFF + text += "".join(_B64_CHARS[(n >> s) & 63] for s in (18, 12, 6, 0)) + return text + + +def _strecke(teileid, k1_xy, k2_xy): + return {"teileart": "ILS 2.0 Strecke", "teileid": teileid, + "k1": _trans(*k1_xy), "k2": _trans(*k2_xy)} + + +class TestAsEsUeberEinlaufpunkt: + """Dematic/Arvato VF_5 an Kreisel1 (Review 2026-10-08): die Strecke laeuft + PARALLEL zum Kreisel, der 90-Grad-Einlauf (KS_EIN) liegt darum quer zur + Kette - Marker (Einfuegepunkt) 557 mm neben der Kette, Einlauf nur 52 mm + davor. Kreisel entlang +Y, rechte Kette bei x = +400.""" + + def _kreisel_y(self): + return _kreisel("0001", 0.0, 0.0, 90.0, abstand=3619.0) + + def test_marker_daneben_einlauf_auf_der_kette(self): + items = [self._kreisel_y(), + _separator("0043", 400.0, 1500.0), + _ases("ILS Ausschleuselement", "0071", 957.0, 2283.0, "0009"), + _strecke("0009", (348.0, 2283.0), (348.0, 9000.0))] + umlauf = compute_kreisel_umlauf( + items, tolerance_mm=200.0, durchmesser_mm=DURCHMESSER_MM) + punkte = {p[0]: p for p in umlauf["0001"]} + assert "0071" in punkte, "AS ueber den Einlaufpunkt nicht im Umlauf" + # nach dem Einlauf einsortiert, nicht nach dem Marker + assert len(umlauf["0001"]) == 2 + + def test_ohne_einlaufpunkt_bleibt_es_bei_der_marker_box(self): + items = [self._kreisel_y(), + _ases("ILS Ausschleuselement", "0071", 957.0, 2283.0, "0009")] + umlauf = compute_kreisel_umlauf( + items, tolerance_mm=200.0, durchmesser_mm=DURCHMESSER_MM) + assert "0071" not in [p[0] for p in umlauf["0001"]] + + def test_einlauf_weit_weg_wird_nicht_zugeordnet(self): + items = [self._kreisel_y(), + _ases("ILS Ausschleuselement", "0071", 957.0, 2283.0, "0009"), + _strecke("0009", (5000.0, 2283.0), (9000.0, 2283.0))] + umlauf = compute_kreisel_umlauf( + items, tolerance_mm=200.0, durchmesser_mm=DURCHMESSER_MM) + assert "0071" not in [p[0] for p in umlauf["0001"]] diff --git a/tests/test_hm_recformat.lsp b/tests/test_hm_recformat.lsp index bbd5972..3347780 100644 --- a/tests/test_hm_recformat.lsp +++ b/tests/test_hm_recformat.lsp @@ -10,7 +10,7 @@ ;; roh aus der XDATA einer echten Zeichnung. Damit ist dies der einzige Test, ;; der belegt, dass ein BESTEHENDER VF_n-Block weiter abspielbar bleibt - ;; also dass Doppelklick-Editieren und 2D/3D-Konvertierung an Altbestand -;; funktionieren (Invariante I1 in doc/TODO-plan-vf-interactive.md). Ein +;; funktionieren (Invariante I1 in doc/todo/plan-vf-interactive.md). Ein ;; spec-gebautes Journal kann das nicht zeigen, es kommt aus dem Uebersetzer. ;; ;; Darum in tests/alltests.json auf "disabled": true - er baut dieselbe diff --git a/tests/test_hundm05.lsp b/tests/test_hundm05.lsp index e579570..bf972ca 100644 --- a/tests/test_hundm05.lsp +++ b/tests/test_hundm05.lsp @@ -78,14 +78,15 @@ ;; Rueckgabe: Ergebnis-Record in derselben Form wie die Ketten, damit ;; vsp-results-schreiben beide in EINE Datei schreibt (unterschieden ueber ;; das Feld "kind"). -(defun hundm05:bau-kreisel (obj / id x y z abstand rotation typ pt ent ed rec) +(defun hundm05:bau-kreisel (obj / id x y z abstand rotation typ drehrichtung pt ent ed rec) (setq id (ssg-val obj "id")) (setq x (ssg-val obj "x") y (ssg-val obj "y") z (ssg-val obj "z") abstand (ssg-val obj "abstand") rotation (ssg-val obj "rotation") - typ (ssg-val obj "typ")) + typ (ssg-val obj "typ") + drehrichtung (ssg-val obj "drehrichtung")) (if (numberp x) (setq x (float x))) (if (numberp y) (setq y (float y))) (if (numberp z) (setq z (float z))) @@ -106,9 +107,9 @@ (princ (strcat "\n [KREISEL] " id " -> (" (rtos x 2 0) ", " (rtos y 2 0) ", " (rtos (if z z 0.0) 2 0) ") A=" (rtos abstand 2 0) " R=" (rtos (if rotation rotation 0.0) 2 1) - " " typ)) + " " typ (if drehrichtung (strcat " " drehrichtung) ""))) (setq ent (kreisel-insert-script pt abstand - (if rotation rotation 0.0) typ z)) + (if rotation rotation 0.0) typ z drehrichtung)) (if ent (progn (setq ed (entget ent)) @@ -138,6 +139,10 @@ (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")) + ;; Eintraege aus lib/dxf_scan_components.py haben kein "id" - die + ;; Meldungen unten setzen id per strcat zusammen und brachen dann mit + ;; "bad argument type " ab. + (if (null id) (setq id (cond (blockname) ("(ohne id)")))) (setq x (ssg-val obj "x") y (ssg-val obj "y") z (ssg-val obj "z") diff --git a/tests/test_kreisel.lsp b/tests/test_kreisel.lsp index 531fdb9..4ab63a2 100644 --- a/tests/test_kreisel.lsp +++ b/tests/test_kreisel.lsp @@ -52,8 +52,8 @@ ;; --- Einzelnen Insert-Testfall ausfuehren --- (defun kreisel:run-insert-test (eintrag / id x y z abstand rotation typ - pt blockEnt ed attribs block-name - block-handle insert-point) + drehrichtung pt blockEnt ed attribs + block-name block-handle insert-point) (setq id (ssg-val eintrag "id")) (setq x (ssg-val eintrag "x")) (setq y (ssg-val eintrag "y")) @@ -61,6 +61,7 @@ (setq abstand (ssg-val eintrag "abstand")) (setq rotation (ssg-val eintrag "rotation")) (setq typ (ssg-val eintrag "typ")) + (setq drehrichtung (ssg-val eintrag "drehrichtung")) (if (numberp x) (setq x (float x))) (if (numberp y) (setq y (float y))) @@ -71,9 +72,10 @@ (setq pt (list x y z)) (princ (strcat "\n [INSERT] " id " -> (" (rtos x 2 0) ", " (rtos y 2 0) ", " (rtos z 2 0) ") A=" (rtos abstand 2 0) - " R=" (rtos rotation 2 1) " " typ)) + " R=" (rtos rotation 2 1) " " typ + (if drehrichtung (strcat " " drehrichtung) ""))) - (setq blockEnt (kreisel-insert-script pt abstand rotation typ z)) + (setq blockEnt (kreisel-insert-script pt abstand rotation typ z drehrichtung)) (if blockEnt (progn diff --git a/tests/test_mubea.lsp b/tests/test_mubea.lsp index 3d92a29..982a6b6 100644 --- a/tests/test_mubea.lsp +++ b/tests/test_mubea.lsp @@ -119,19 +119,20 @@ ;; ============================================================ ;; --- Kreisel (wie test_kreisel: kreisel-insert-script) --- -(defun mubea:build-kreisel (e / id x y z abstand rotation typ pt ent) - (setq id (ssg-val e "id") - x (float (ssg-val e "x")) - y (float (ssg-val e "y")) - z (float (ssg-val e "z")) - abstand (float (ssg-val e "abstand")) - rotation (float (ssg-val e "rotation")) - typ (ssg-val e "typ")) +(defun mubea:build-kreisel (e / id x y z abstand rotation typ drehrichtung pt ent) + (setq id (ssg-val e "id") + x (float (ssg-val e "x")) + y (float (ssg-val e "y")) + z (float (ssg-val e "z")) + abstand (float (ssg-val e "abstand")) + rotation (float (ssg-val e "rotation")) + typ (ssg-val e "typ") + drehrichtung (ssg-val e "drehrichtung")) (if (null typ) (setq typ "STANDARD")) (setq pt (list x y z)) (princ (strcat "\n [KREISEL] " id " -> (" (rtos x 2 0) "," (rtos y 2 0) "," (rtos z 2 0) ") A=" (rtos abstand 2 0) " R=" (rtos rotation 2 1))) - (setq ent (kreisel-insert-script pt abstand rotation typ z)) + (setq ent (kreisel-insert-script pt abstand rotation typ z drehrichtung)) (mubea:ent-json id "kreisel" ent) ) @@ -146,8 +147,18 @@ ;; gueltigen Winkel zu nehmen. L_GF1 aus JSON ist der Vorlauf-Zielwert; ;; L_GF2 (Nachlauf) ist der Rest (L_GF_gesamt - L_GF1). (defun mubea:build-vario-one (tid typ richtung deltaL deltaH winkel-soll gf1-ziel seite hz - startpunkt / ergebnis winkel-treffer best-winkel L_GF L_VF - gf1 gf2 hoehe-bis lastEnt ent vf-nummer) + startpunkt) + (mubea:ent-json tid "vario" + (mubea:vario-bauen tid typ richtung deltaL deltaH winkel-soll gf1-ziel seite hz + startpunkt))) + +;; Der eigentliche Bau ohne Ergebnis-Format - auch von +;; tests/test_dematic_arvato.lsp genutzt (dort mit vsp-Records). +;; Rueckgabe: Entity des VF_n-Blocks oder nil (auch, wenn deltaL beim +;; gewuenschten Winkel nicht loesbar ist). +(defun mubea:vario-bauen (tid typ richtung deltaL deltaH winkel-soll gf1-ziel seite hz + startpunkt / ergebnis winkel-treffer best-winkel L_GF L_VF + gf1 gf2 hoehe-bis lastEnt ent vf-nummer) (if (or (null seite) (= seite "")) (setq seite "links")) (if (or (null typ) (= typ "")) (setq typ "standard")) (if (null hz) (setq hz 0.0) (setq hz (float hz))) @@ -207,12 +218,12 @@ ;; Linienzug/Altbestand bleiben unmarkiert. (if (and ent (= (type ent) 'ENAME) (member typ '("standard" "etage"))) (vf-edit-xdata-schreiben ent typ hz)) - (mubea:ent-json tid "vario" ent)) + (if (and ent (= (type ent) 'ENAME)) ent nil)) (progn (princ (strcat "\n FEHLER: deltaL=" (rtos deltaL 2 0) " bei Winkel " (if winkel-soll (rtos winkel-soll 2 0) "auto") " nicht loesbar (kein gueltiges L_GF/L_VF).")) - (mubea:result-json tid "vario" "failed" nil nil nil nil))) + nil)) ) ;; --- Vario(s) aus einem Template-Eintrag bauen --- @@ -290,8 +301,15 @@ ;; sep_x = 300 * cos(v), v = Element-Winkel (winkel_grad, Default 3 Grad) ;; Die Zielhoehe ergibt sich aus L_stau/Winkel (kein Ziel-Hoehen-Parameter). ;; Rueckgabe: Ergebnis-JSON-String. -(defun mubea:build-gf-one (tid start-mm deltaL hz as-seite es-seite as-winkel es-winkel winkel / - rad sep-x test-L_stau ent) +(defun mubea:build-gf-one (tid start-mm deltaL hz as-seite es-seite as-winkel es-winkel winkel) + (mubea:ent-json tid "gefaellestrecke" + (mubea:gf-bauen tid start-mm deltaL hz as-seite es-seite as-winkel es-winkel winkel))) + +;; Der eigentliche Bau ohne Ergebnis-Format - auch von +;; tests/test_dematic_arvato.lsp genutzt (dort mit vsp-Records). +;; Rueckgabe: Entity des GF_n-Blocks oder nil. +(defun mubea:gf-bauen (tid start-mm deltaL hz as-seite es-seite as-winkel es-winkel winkel / + rad sep-x test-L_stau ent) (mubea:ensure-gf-init) ;; Element-Masse fuer die gewaehlte AS/ES-Variante setzen, damit aus-dx/ein-dx ;; zur Variante (90/30 Grad) passen. Danach wieder auf 90 zuruecksetzen. @@ -312,7 +330,7 @@ deltaL as-winkel es-winkel "0" "Schoenenberger Geruest"))) (vf-set-as-masse "90" as-seite) (vf-set-es-masse "90" es-seite) - (mubea:ent-json tid "gefaellestrecke" ent) + ent ) ;; --- Gefaellestrecke(n) aus einem Template-Eintrag bauen --- diff --git a/tests/test_showroom_ils.lsp b/tests/test_showroom_ils.lsp new file mode 100644 index 0000000..28c7ed9 --- /dev/null +++ b/tests/test_showroom_ils.lsp @@ -0,0 +1,77 @@ +;; ============================================================ +;; test_showroom_ils.lsp - Nachbau des ILS-3.0-Showrooms. +;; +;; Datenquelle: tests/testdata/showroom_ils.json. Entstehung in zwei Stufen: +;; 1. Scanner auf der Fremdzeichnung (flacher 2D-Plan, Hoehen vom Anwender): +;; python lib/dxf_scan_components.py \ +;; C:/develop/sps_skel/data/Showroom_ILS_3_0.dxf data \ +;; tests/testdata/showroom_ils.json --projekt Showroom \ +;; --flach --ohne-hoehen \ +;; --kreisel-achse 27797.93 16298.90 36705.93 16298.90 \ +;; --kreisel-achse 35305.93 13093.90 28772.93 13093.90 \ +;; --kreisel-hoehe 26997.93 3066.4 2226.4 \ +;; --kreisel-hoehe 26997.93 12898.9 2226.4 \ +;; --kreisel-hoehe 37105.93 9888.9 1700 \ +;; --kreisel-hoehe 27397.93 16298.9 2226.4 \ +;; --kreisel-hoehe 35705.93 13093.9 1970 +;; 2. VON HAND (geht bei Stufe 1 verloren - danach neu eintragen): +;; Kreisel_Showroom_04 (vertikal, rechts) samt Separator/Scanner bei +;; y=14979 entfernt, dafuer VF_Showroom_01 (VarioFoerderer standard, +;; Auf von Kreisel_Showroom_03 zu Kreisel_Showroom_05) eingetragen. +;; Showroom_BTMT_BEL_01 (Kreisel_Showroom_03) und Showroom_BTMT_ENT_01 +;; (Kreisel_Showroom_01) aus der gebauten Zeichnung uebernommen +;; (results/export_raw.json, EXPORTCSV vom 2026-10-07). +;; +;; Inhalt: 5 Kreisel, 1 VarioFoerderer, 11 Gefaellestrecken (Modus 1), +;; Separatoren/Scanner, BTMT Be- und Entladung. +;; - Ebenen: oben 2226.4 (Kreisel 01/02/05), Mitte 1970 (06), unten 1700 (03) +;; - in der gebauten Zeichnung KREISEL_1/2/4, KREISEL_5, KREISEL_3. +;; - Kreisel_Showroom_05/06 sind Schleifen ohne AN8/SP8 im Plan (an beiden +;; Enden Weichenboegen) - per --kreisel-achse als STANDARD-Kreisel +;; angenaehert (Radmitten = Mittelpunkte der PIN-Kreise). Die AN8-Seite +;; ist NICHT beliebig: der Export legt Bahn L links von AN8->SP8, der +;; Materialfluss setzt den Bahnwechsel ans AN8-Ende. Kreisel_Showroom_06 +;; (UZS) hat AN8 darum im OSTEN, sonst wird Bahn L zur Sackgasse. +;; - Weichen (WB-*/WM-*/WL-*/WR-*) werden bewusst NICHT nachgebaut. +;; - GF: den Winkel +;; und die Staustreckenlaenge rechnet dematic_arvato:gf-geometrie aus +;; hoehe_ende_mm (ES-Masse bei der Neigung gemessen), und +;; dematic_arvato:gf-endhoehe-pruefen meldet "warnung", wenn eine GF +;; den unteren Kreisel um mehr als 5 mm verfehlt. +;; +;; Keine eigene Bau-Logik: Eintragsschema und Lauf wie TEST_DEMATIC_ARVATO +;; (dematic_arvato:bau-datei aus tests/test_dematic_arvato.lsp). +;; +;; Aufruf in BricsCAD: +;; (load (strcat (getenv "DXFMAKRO") "/tests/test_showroom_ils.lsp")) +;; TEST_SHOWROOM_ILS +;; ============================================================ + +(if (not (boundp '*showroom-ils-records*)) (setq *showroom-ils-records* nil)) + +;; Beim LADEN holen (siehe test_dematic_arvato.lsp: per load definierte +;; Funktionen sind erst nach Rueckkehr zum Top-Level sichtbar). +(if (null (car (atoms-family 1 '("DEMATIC_ARVATO:BAU-DATEI")))) + (load (strcat (getenv "DXFMAKRO") "/tests/test_dematic_arvato.lsp"))) + +(defun showroom_ils:datei () + (strcat (getenv "DXFMAKRO") "/tests/testdata/showroom_ils.json")) + +(defun c:TEST_SHOWROOM_ILS () + (setq *showroom-ils-records* + (dematic_arvato:bau-datei "TEST_SHOWROOM_ILS" "ILS 3.0 Showroom" + (showroom_ils:datei))) + (princ)) + +;; Ergebnis-Export (Konvention :export-results fuer test_run_all.lsp) +(defun showroom_ils:export-results (out-dir / pfad) + (if (null *showroom-ils-records*) + (princ "\n Keine Showroom-Ergebnisse vorhanden.") + (progn + (vl-mkdir out-dir) + (setq pfad (strcat out-dir "/showroom_ils_results.json")) + (vsp-results-schreiben *showroom-ils-records* pfad))) + (princ)) + +(princ "\nTEST_SHOWROOM_ILS geladen. Aufruf: TEST_SHOWROOM_ILS") +(princ) diff --git a/tests/test_unit.lsp b/tests/test_unit.lsp index cbabbae..92b812d 100644 --- a/tests/test_unit.lsp +++ b/tests/test_unit.lsp @@ -1601,6 +1601,77 @@ (strcat "harter Fehler: " (vl-catch-all-error-message res)))) ) +;; ------------------------------------------------------------ +;; TESTS: ssg_input / ssg_blocktools / ssg_texttools - reine Funktionen +;; (die interaktiven Befehle SSG_BLOCK_*, SSG_ATTR_ERSETZEN, SSG_TEXT_* +;; brauchen eine Zeichnung und sind hier NICHT abgedeckt) +;; ------------------------------------------------------------ + +(defun tu-tests-werkzeuge ( / _e) + (princ "\n[ssg_input / ssg_blocktools / ssg_texttools]") + (if (not (and (car (atoms-family 1 '("SSG-IN-DEFAULT"))) + (car (atoms-family 1 '("SSG-BT-WERT-NEU"))) + (car (atoms-family 1 '("SSG-TT-LESBARER-WINKEL"))))) + (tu-record nil "werkzeuge/geladen" + "ssg_input/ssg_blocktools/ssg_texttools nicht geladen (ssg_load.lsp)") + (progn + ;; --- ssg_input --- + (tu-eqf "ssg-in-default/vorgabe" 5.0 (ssg-in-default nil 5.0)) + (tu-eqf "ssg-in-default/eingabe" 3.0 (ssg-in-default 3.0 5.0)) + (tu-nil "ssg-in-default/beides-nil" (ssg-in-default nil nil)) + (tu-eq "ssg-in-fmt-real/ganz" "5" (ssg-in-fmt-real 5.0)) + (tu-eq "ssg-in-fmt-real/halb" "2.5" (ssg-in-fmt-real 2.5)) + (tu-eq "ssg-in-fmt-real/null" "0" (ssg-in-fmt-real 0.0)) + (tu-eq "ssg-in-fmt-real/hundert" "100" (ssg-in-fmt-real 100.0)) + (tu-eq "ssg-in-fmt-real/bruch" "0.125" (ssg-in-fmt-real 0.125)) + (tu-eq "ssg-in-fmt-real/negativ" "-1.5" (ssg-in-fmt-real -1.5)) + (tu-eq "ssg-in-fmt-pt/3d" "1,2.5,0" (ssg-in-fmt-pt '(1.0 2.5 0.0))) + (tu-eq "ssg-in-fmt-pt/2d" "1,2,0" (ssg-in-fmt-pt '(1.0 2.0))) + (tu-eq "ssg-in-prompt/mit-vorgabe" "\nHoehe <5>: " (ssg-in-prompt "\nHoehe" "5")) + (tu-eq "ssg-in-prompt/ohne" "\nHoehe: " (ssg-in-prompt "\nHoehe" nil)) + (tu-eq "ssg-in-first-word" "Ja" (ssg-in-first-word "Ja Nein")) + (tu-eq "ssg-in-first-word/ein-wort" "Ja" (ssg-in-first-word "Ja")) + (tu-eq "ssg-in-second-word" "Nein" (ssg-in-second-word "Ja Nein")) + (tu-eq "ssg-in-second-word/ein-wort" "" (ssg-in-second-word "Ja")) + + ;; --- ssg_blocktools --- + (tu-true "ssg-bt-name-passt-p/muster" (ssg-bt-name-passt-p "GR_TR_1" "gr_*")) + (tu-nil "ssg-bt-name-passt-p/kein" (ssg-bt-name-passt-p "GR_TR_1" "XX*")) + (tu-true "ssg-bt-name-passt-p/nil" (ssg-bt-name-passt-p "ABC" nil)) + (tu-true "ssg-bt-name-passt-p/leer" (ssg-bt-name-passt-p "ABC" "")) + (tu-true "ssg-bt-name-passt-p/escape-ja" (ssg-bt-name-passt-p "A.B" "A`.B")) + (tu-nil "ssg-bt-name-passt-p/escape-nein" (ssg-bt-name-passt-p "AxB" "A`.B")) + (tu-eq "ssg-bt-muster-escape" "A`.B`#1" (ssg-bt-muster-escape "A.B#1")) + (tu-eq "ssg-bt-muster-anzeige" "A.B#1" (ssg-bt-muster-anzeige "A`.B`#1")) + (tu-eq "ssg-bt-zaehle-namen" + '(("Alpha" . 1) ("Beta" . 1) ("Zeta" . 3)) + (ssg-bt-zaehle-namen '("Zeta" "Alpha" "Zeta" "Beta" "Zeta"))) + (tu-nil "ssg-bt-zaehle-namen/leer" (ssg-bt-zaehle-namen nil)) + (tu-eq "ssg-bt-wert-neu/teil" "ABC_1" (ssg-bt-wert-neu "ABC-1" "-" "_" T)) + (tu-eq "ssg-bt-wert-neu/mehrfach" "a.b.c" (ssg-bt-wert-neu "a-b-c" "-" "." T)) + (tu-eq "ssg-bt-wert-neu/loeschen" "AC" (ssg-bt-wert-neu "ABC" "B" "" T)) + (tu-nil "ssg-bt-wert-neu/kein-treffer" (ssg-bt-wert-neu "ABC" "X" "y" T)) + (tu-eq "ssg-bt-wert-neu/gesamt-ja" "X" (ssg-bt-wert-neu "ABC" "ABC" "X" nil)) + (tu-nil "ssg-bt-wert-neu/gesamt-nein" (ssg-bt-wert-neu "ABCD" "ABC" "X" nil)) + (tu-nil "ssg-bt-wert-neu/leere-suche" (ssg-bt-wert-neu "ABC" "" "X" T)) + (tu-nil "ssg-bt-wert-neu/gross-klein" (ssg-bt-wert-neu "abc" "ABC" "X" T)) + + ;; --- ssg_texttools --- + (tu-eqf "ssg-tt-lesbarer-winkel/0" 0.0 (ssg-tt-lesbarer-winkel 0.0)) + (tu-eqf "ssg-tt-lesbarer-winkel/45" (/ pi 4.0) (ssg-tt-lesbarer-winkel (/ pi 4.0))) + (tu-eqf "ssg-tt-lesbarer-winkel/90" (/ pi 2.0) (ssg-tt-lesbarer-winkel (/ pi 2.0))) + (tu-eqf "ssg-tt-lesbarer-winkel/135" (* 1.75 pi) (ssg-tt-lesbarer-winkel (* 0.75 pi))) + (tu-eqf "ssg-tt-lesbarer-winkel/180" 0.0 (ssg-tt-lesbarer-winkel pi)) + (tu-eqf "ssg-tt-lesbarer-winkel/270" (/ pi 2.0) (ssg-tt-lesbarer-winkel (* 1.5 pi))) + (tu-eqf "ssg-tt-lesbarer-winkel/-45" (* 1.75 pi) (ssg-tt-lesbarer-winkel (- (/ pi 4.0)))) + (tu-eqf "ssg-tt-lesbarer-winkel/360" 0.0 (ssg-tt-lesbarer-winkel (* 2.0 pi))) + (tu-eqf "ssg-tt-versetzt/x" '(5.0 0.0 0.0) (ssg-tt-versetzt '(0.0 0.0 0.0) 0.0 5.0)) + (tu-eqf "ssg-tt-versetzt/y" '(0.0 2.0 0.0) (ssg-tt-versetzt '(0.0 0.0 0.0) (/ pi 2.0) 2.0)) + (tu-eqf "ssg-tt-versetzt/2d" '(1.0 4.0 0.0) (ssg-tt-versetzt '(1.0 1.0) (/ pi 2.0) 3.0)) + ) + ) +) + (defun c:TEST_UNIT ( / out-dir report) (tu-reset) (princ "\n================================================================") @@ -1610,7 +1681,8 @@ (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-tests-sepscan" "tu-tests-vfl-segs" "tu-tests-vfl-abbruch" + "tu-tests-werkzeuge") (tu-run-block fn)) (princ "\n\n----------------------------------------------------------------") diff --git a/tests/test_vf_geometrie.py b/tests/test_vf_geometrie.py index f62b708..ac8cd37 100644 --- a/tests/test_vf_geometrie.py +++ b/tests/test_vf_geometrie.py @@ -7,7 +7,7 @@ HundM-Ketten. Warum zusaetzlich zu tests/test_hundm05.py: der Ergebnis-Record sagt "executed, kein Prompt, Journal aufgegangen" - er sagt NICHT, ob dieselbe Geometrie herauskam wie vorher. Genau das ist bei der Funktionstrennung in -Stufe 2 (doc/TODO-plan-vf-interactive.md) das Risiko: die Reihenfolge der +Stufe 2 (doc/todo/plan-vf-interactive.md) das Risiko: die Reihenfolge der dL-Subtraktionen und der vfl-acc-*-Aufrufe IST Geometrie, und eine falsche Reihenfolge faellt in der Zeichnung nicht auf - nur im Sivas-Export. diff --git a/tests/test_vf_headless_statisch.py b/tests/test_vf_headless_statisch.py index f098990..60419fe 100644 --- a/tests/test_vf_headless_statisch.py +++ b/tests/test_vf_headless_statisch.py @@ -11,7 +11,7 @@ einem selten erreichten Zweig ein neues (alert ...) oder ein rohes (getreal ...) hinzufuegt. Genau das haelt dieser Test fest: die Fundstellen sind eingecheckt, jede neue muss hier bewusst eingetragen werden. -Hintergrund und Fahrplan: doc/TODO-plan-vf-interactive.md, +Hintergrund und Fahrplan: doc/todo/plan-vf-interactive.md, Mechanik: doc/variofoerderer.md Abschnitt 8b. """ diff --git a/tests/testdata/dematic_arvato.json b/tests/testdata/dematic_arvato.json new file mode 100644 index 0000000..7aade0b --- /dev/null +++ b/tests/testdata/dematic_arvato.json @@ -0,0 +1,774 @@ +[ + { + "id": "Kreisel_Arvato_01", + "function": "insert", + "x": 19204.0, + "y": 1703.66, + "z": 2177.0, + "abstand": 3619.14, + "rotation": 90.0, + "typ": "STANDARD", + "drehrichtung": "GUZ", + "comment": "aus Zeichnung: KREISEL_1_STANDARD_3619 (ID 0056), abstand aus Blockdefinition (AN8 -> SP8)" + }, + { + "id": "Kreisel_Arvato_02", + "function": "insert", + "x": 35860.37, + "y": 12519.72, + "z": 2437.0, + "abstand": 33044.28, + "rotation": 180.0, + "typ": "STANDARD", + "drehrichtung": "UZS", + "comment": "aus Zeichnung: KREISEL_2_STANDARD_33044 (ID 0002), abstand aus Blockdefinition (AN8 -> SP8)" + }, + { + "id": "Kreisel_Arvato_03", + "function": "insert", + "x": 36661.24, + "y": 24878.8, + "z": 1826.0, + "abstand": 33470.18, + "rotation": 180.0, + "typ": "STANDARD", + "drehrichtung": "UZS", + "comment": "aus Zeichnung: KREISEL_3_STANDARD_33470 (ID 0003), abstand aus Blockdefinition (AN8 -> SP8)" + }, + { + "id": "Kreisel_Arvato_04", + "function": "insert", + "x": 42563.78, + "y": 1703.66, + "z": 2177.0, + "abstand": 3619.14, + "rotation": 90.0, + "typ": "STANDARD", + "drehrichtung": "GUZ", + "comment": "aus Zeichnung: KREISEL_4_STANDARD_3619 (ID 0055), abstand aus Blockdefinition (AN8 -> SP8)" + }, + { + "test_id": "VF_Arvato_01", + "typ": "standard", + "richtung": "Auf", + "deltaL": 11559.0, + "deltaH": 611.0, + "winkel": 15, + "L_GF1": 1907.0, + "seite": "links", + "motorseite": "rechts", + "hz": 270.0, + "x": 3314.07, + "y": 24478.8, + "z": 1826.0, + "comment": "aus Zeichnung: VF_1 (ID 0005), Hoehe 0 -> 611; deltaL/deltaH aus den (ganzzahligen) Attributen" + }, + { + "test_id": "VF_Arvato_02", + "typ": "standard", + "richtung": "Auf", + "deltaL": 11559.0, + "deltaH": 611.0, + "winkel": 15, + "L_GF1": 1907.0, + "seite": "links", + "motorseite": "rechts", + "hz": 270.0, + "x": 15285.06, + "y": 24478.8, + "z": 1826.0, + "comment": "aus Zeichnung: VF_2 (ID 0006), Hoehe 0 -> 611; deltaL/deltaH aus den (ganzzahligen) Attributen" + }, + { + "test_id": "VF_Arvato_03", + "typ": "standard", + "richtung": "Auf", + "deltaL": 11559.0, + "deltaH": 611.0, + "winkel": 15, + "L_GF1": 1907.0, + "seite": "links", + "motorseite": "rechts", + "hz": 270.0, + "x": 23843.05, + "y": 24478.8, + "z": 1826.0, + "comment": "aus Zeichnung: VF_3 (ID 0007), Hoehe 0 -> 611; deltaL/deltaH aus den (ganzzahligen) Attributen" + }, + { + "test_id": "VF_Arvato_04", + "typ": "standard", + "richtung": "Auf", + "deltaL": 22559.0, + "deltaH": 0.0, + "winkel": 12, + "L_GF1": 6852.0, + "seite": "rechts", + "motorseite": "rechts", + "hz": 180.0, + "x": 42163.78, + "y": 4017.06, + "z": 2177.0, + "comment": "aus Zeichnung: VF_4 (ID 0008), Hoehe 0 -> 0; deltaL/deltaH aus den (ganzzahligen) Attributen" + }, + { + "test_id": "VF_Arvato_05", + "typ": "standard", + "richtung": "Auf", + "deltaL": 6852.0, + "deltaH": 260.0, + "winkel": 18, + "L_GF1": 767.0, + "seite": "links", + "motorseite": "rechts", + "hz": 90.0, + "x": 19551.49, + "y": 5265.19, + "z": 2177.0, + "comment": "aus Zeichnung: VF_5 (ID 0009), Hoehe 0 -> 260; deltaL/deltaH aus den (ganzzahligen) Attributen" + }, + { + "test_id": "VF_Arvato_LZ_01", + "typ": "linienzug_journal", + "journal": [ + "PT:42161.679193,2448.319538,0", + "REAL:2177", + "STR:1", + "STR:90", + "STR:2", + "STEP:Linie-VF", + "STR:4", + "DL:9370.410886", + "REAL:-180", + "STR:1", + "REAL:2800", + "INT:1", + "STR:2", + "STR:2", + "STR:2", + "STEP:Linie-GF", + "STR:2", + "STR:1", + "STR:3", + "DL:6187.72996", + "STR:1", + "STR:2", + "STR:2", + "REAL:3", + "STEP:Linie-VF", + "STR:3", + "DL:4572.938729", + "STR:2", + "INT:1", + "STR:3", + "STR:1", + "STEP:ES", + "STR:90", + "STR:1", + "DL:2872.479403", + "REAL:2100", + "STR:3", + "STR:1", + "STEP:ES", + "STR:90", + "STR:2", + "DL:2857.418974", + "REAL:2000", + "STR:3", + "STR:1", + "STEP:ES", + "STR:90", + "STR:2", + "DL:2850.93244", + "REAL:2177", + "INT:2", + "STR:2" + ], + "comment": "aus Zeichnung: VF_6 (ID 0010), 52 Eingaben, Hoehe 2177 -> 2174; wird 1:1 abgespielt (vsp-bau-journal)" + }, + { + "test_id": "GF_Arvato_01", + "modus": 1, + "deltaL_mm": 11559.08, + "hz_grad": 90.0, + "start_mm": [5833.56, 12919.72, 2437.0], + "as_seite": "links", + "es_seite": "links", + "as_winkel": "90", + "es_winkel": "90", + "kreisel_start": "Kreisel_Arvato_02", + "kreisel_ende": "Kreisel_Arvato_03", + "hoehe_ende_mm": 1826.0, + "comment": "aus Zeichnung: GF_1 (ID 0011); Endhoehe = Hoehe von Kreisel_Arvato_03 (1826.0), Winkel rechnet der Treiber neu; gebaut war 2.4700 Grad -> HOEHE_BIS 1825" + }, + { + "test_id": "GF_Arvato_02", + "modus": 1, + "deltaL_mm": 11559.08, + "hz_grad": 90.0, + "start_mm": [6733.56, 12919.72, 2437.0], + "as_seite": "links", + "es_seite": "links", + "as_winkel": "90", + "es_winkel": "90", + "kreisel_start": "Kreisel_Arvato_02", + "kreisel_ende": "Kreisel_Arvato_03", + "hoehe_ende_mm": 1826.0, + "comment": "aus Zeichnung: GF_2 (ID 0012); Endhoehe = Hoehe von Kreisel_Arvato_03 (1826.0), Winkel rechnet der Treiber neu; gebaut war 2.4700 Grad -> HOEHE_BIS 1825" + }, + { + "test_id": "GF_Arvato_03", + "modus": 1, + "deltaL_mm": 11559.08, + "hz_grad": 90.0, + "start_mm": [7533.56, 12919.72, 2437.0], + "as_seite": "links", + "es_seite": "links", + "as_winkel": "90", + "es_winkel": "90", + "kreisel_start": "Kreisel_Arvato_02", + "kreisel_ende": "Kreisel_Arvato_03", + "hoehe_ende_mm": 1826.0, + "comment": "aus Zeichnung: GF_3 (ID 0013); Endhoehe = Hoehe von Kreisel_Arvato_03 (1826.0), Winkel rechnet der Treiber neu; gebaut war 2.4700 Grad -> HOEHE_BIS 1825" + }, + { + "test_id": "GF_Arvato_04", + "modus": 1, + "deltaL_mm": 11559.08, + "hz_grad": 90.0, + "start_mm": [8333.56, 12919.72, 2437.0], + "as_seite": "links", + "es_seite": "links", + "as_winkel": "90", + "es_winkel": "90", + "kreisel_start": "Kreisel_Arvato_02", + "kreisel_ende": "Kreisel_Arvato_03", + "hoehe_ende_mm": 1826.0, + "comment": "aus Zeichnung: GF_4 (ID 0014); Endhoehe = Hoehe von Kreisel_Arvato_03 (1826.0), Winkel rechnet der Treiber neu; gebaut war 2.4700 Grad -> HOEHE_BIS 1825" + }, + { + "test_id": "GF_Arvato_05", + "modus": 1, + "deltaL_mm": 11559.08, + "hz_grad": 90.0, + "start_mm": [9383.57, 12919.72, 2437.0], + "as_seite": "links", + "es_seite": "links", + "as_winkel": "90", + "es_winkel": "90", + "kreisel_start": "Kreisel_Arvato_02", + "kreisel_ende": "Kreisel_Arvato_03", + "hoehe_ende_mm": 1826.0, + "comment": "aus Zeichnung: GF_5 (ID 0015); Endhoehe = Hoehe von Kreisel_Arvato_03 (1826.0), Winkel rechnet der Treiber neu; gebaut war 2.4700 Grad -> HOEHE_BIS 1825" + }, + { + "test_id": "GF_Arvato_06", + "modus": 1, + "deltaL_mm": 11559.08, + "hz_grad": 90.0, + "start_mm": [10183.57, 12919.72, 2437.0], + "as_seite": "links", + "es_seite": "links", + "as_winkel": "90", + "es_winkel": "90", + "kreisel_start": "Kreisel_Arvato_02", + "kreisel_ende": "Kreisel_Arvato_03", + "hoehe_ende_mm": 1826.0, + "comment": "aus Zeichnung: GF_6 (ID 0016); Endhoehe = Hoehe von Kreisel_Arvato_03 (1826.0), Winkel rechnet der Treiber neu; gebaut war 2.4700 Grad -> HOEHE_BIS 1825" + }, + { + "test_id": "GF_Arvato_07", + "modus": 1, + "deltaL_mm": 11559.08, + "hz_grad": 90.0, + "start_mm": [10983.57, 12919.72, 2437.0], + "as_seite": "links", + "es_seite": "links", + "as_winkel": "90", + "es_winkel": "90", + "kreisel_start": "Kreisel_Arvato_02", + "kreisel_ende": "Kreisel_Arvato_03", + "hoehe_ende_mm": 1826.0, + "comment": "aus Zeichnung: GF_7 (ID 0017); Endhoehe = Hoehe von Kreisel_Arvato_03 (1826.0), Winkel rechnet der Treiber neu; gebaut war 2.4700 Grad -> HOEHE_BIS 1825" + }, + { + "test_id": "GF_Arvato_08", + "modus": 1, + "deltaL_mm": 11559.08, + "hz_grad": 90.0, + "start_mm": [12323.57, 12919.72, 2437.0], + "as_seite": "links", + "es_seite": "links", + "as_winkel": "90", + "es_winkel": "90", + "kreisel_start": "Kreisel_Arvato_02", + "kreisel_ende": "Kreisel_Arvato_03", + "hoehe_ende_mm": 1826.0, + "comment": "aus Zeichnung: GF_8 (ID 0018); Endhoehe = Hoehe von Kreisel_Arvato_03 (1826.0), Winkel rechnet der Treiber neu; gebaut war 2.4700 Grad -> HOEHE_BIS 1825" + }, + { + "test_id": "GF_Arvato_09", + "modus": 1, + "deltaL_mm": 11559.08, + "hz_grad": 90.0, + "start_mm": [13123.57, 12919.72, 2437.0], + "as_seite": "links", + "es_seite": "links", + "as_winkel": "90", + "es_winkel": "90", + "kreisel_start": "Kreisel_Arvato_02", + "kreisel_ende": "Kreisel_Arvato_03", + "hoehe_ende_mm": 1826.0, + "comment": "aus Zeichnung: GF_9 (ID 0019); Endhoehe = Hoehe von Kreisel_Arvato_03 (1826.0), Winkel rechnet der Treiber neu; gebaut war 2.4700 Grad -> HOEHE_BIS 1825" + }, + { + "test_id": "GF_Arvato_10", + "modus": 1, + "deltaL_mm": 11559.08, + "hz_grad": 90.0, + "start_mm": [13923.57, 12919.72, 2437.0], + "as_seite": "links", + "es_seite": "links", + "as_winkel": "90", + "es_winkel": "90", + "kreisel_start": "Kreisel_Arvato_02", + "kreisel_ende": "Kreisel_Arvato_03", + "hoehe_ende_mm": 1826.0, + "comment": "aus Zeichnung: GF_10 (ID 0020); Endhoehe = Hoehe von Kreisel_Arvato_03 (1826.0), Winkel rechnet der Treiber neu; gebaut war 2.4700 Grad -> HOEHE_BIS 1825" + }, + { + "test_id": "GF_Arvato_11", + "modus": 1, + "deltaL_mm": 11559.08, + "hz_grad": 90.0, + "start_mm": [16733.57, 12919.72, 2437.0], + "as_seite": "links", + "es_seite": "links", + "as_winkel": "90", + "es_winkel": "90", + "kreisel_start": "Kreisel_Arvato_02", + "kreisel_ende": "Kreisel_Arvato_03", + "hoehe_ende_mm": 1826.0, + "comment": "aus Zeichnung: GF_11 (ID 0021); Endhoehe = Hoehe von Kreisel_Arvato_03 (1826.0), Winkel rechnet der Treiber neu; gebaut war 2.4700 Grad -> HOEHE_BIS 1825" + }, + { + "test_id": "GF_Arvato_12", + "modus": 1, + "deltaL_mm": 11559.08, + "hz_grad": 90.0, + "start_mm": [17533.57, 12919.72, 2437.0], + "as_seite": "links", + "es_seite": "links", + "as_winkel": "90", + "es_winkel": "90", + "kreisel_start": "Kreisel_Arvato_02", + "kreisel_ende": "Kreisel_Arvato_03", + "hoehe_ende_mm": 1826.0, + "comment": "aus Zeichnung: GF_12 (ID 0022); Endhoehe = Hoehe von Kreisel_Arvato_03 (1826.0), Winkel rechnet der Treiber neu; gebaut war 2.4700 Grad -> HOEHE_BIS 1825" + }, + { + "test_id": "GF_Arvato_13", + "modus": 1, + "deltaL_mm": 11559.08, + "hz_grad": 90.0, + "start_mm": [18333.57, 12919.72, 2437.0], + "as_seite": "links", + "es_seite": "links", + "as_winkel": "90", + "es_winkel": "90", + "kreisel_start": "Kreisel_Arvato_02", + "kreisel_ende": "Kreisel_Arvato_03", + "hoehe_ende_mm": 1826.0, + "comment": "aus Zeichnung: GF_13 (ID 0023); Endhoehe = Hoehe von Kreisel_Arvato_03 (1826.0), Winkel rechnet der Treiber neu; gebaut war 2.4700 Grad -> HOEHE_BIS 1825" + }, + { + "test_id": "GF_Arvato_14", + "modus": 1, + "deltaL_mm": 11559.08, + "hz_grad": 90.0, + "start_mm": [19381.07, 12919.72, 2437.0], + "as_seite": "links", + "es_seite": "links", + "as_winkel": "90", + "es_winkel": "90", + "kreisel_start": "Kreisel_Arvato_02", + "kreisel_ende": "Kreisel_Arvato_03", + "hoehe_ende_mm": 1826.0, + "comment": "aus Zeichnung: GF_14 (ID 0024); Endhoehe = Hoehe von Kreisel_Arvato_03 (1826.0), Winkel rechnet der Treiber neu; gebaut war 2.4700 Grad -> HOEHE_BIS 1825" + }, + { + "test_id": "GF_Arvato_15", + "modus": 1, + "deltaL_mm": 11559.08, + "hz_grad": 90.0, + "start_mm": [20281.07, 12919.72, 2437.0], + "as_seite": "links", + "es_seite": "links", + "as_winkel": "90", + "es_winkel": "90", + "kreisel_start": "Kreisel_Arvato_02", + "kreisel_ende": "Kreisel_Arvato_03", + "hoehe_ende_mm": 1826.0, + "comment": "aus Zeichnung: GF_15 (ID 0025); Endhoehe = Hoehe von Kreisel_Arvato_03 (1826.0), Winkel rechnet der Treiber neu; gebaut war 2.4700 Grad -> HOEHE_BIS 1825" + }, + { + "test_id": "GF_Arvato_16", + "modus": 1, + "deltaL_mm": 11559.08, + "hz_grad": 90.0, + "start_mm": [21081.07, 12919.72, 2437.0], + "as_seite": "links", + "es_seite": "links", + "as_winkel": "90", + "es_winkel": "90", + "kreisel_start": "Kreisel_Arvato_02", + "kreisel_ende": "Kreisel_Arvato_03", + "hoehe_ende_mm": 1826.0, + "comment": "aus Zeichnung: GF_16 (ID 0026); Endhoehe = Hoehe von Kreisel_Arvato_03 (1826.0), Winkel rechnet der Treiber neu; gebaut war 2.4700 Grad -> HOEHE_BIS 1825" + }, + { + "test_id": "GF_Arvato_17", + "modus": 1, + "deltaL_mm": 11559.08, + "hz_grad": 90.0, + "start_mm": [21881.07, 12919.72, 2437.0], + "as_seite": "links", + "es_seite": "links", + "as_winkel": "90", + "es_winkel": "90", + "kreisel_start": "Kreisel_Arvato_02", + "kreisel_ende": "Kreisel_Arvato_03", + "hoehe_ende_mm": 1826.0, + "comment": "aus Zeichnung: GF_17 (ID 0027); Endhoehe = Hoehe von Kreisel_Arvato_03 (1826.0), Winkel rechnet der Treiber neu; gebaut war 2.4700 Grad -> HOEHE_BIS 1825" + }, + { + "test_id": "GF_Arvato_18", + "modus": 1, + "deltaL_mm": 11559.08, + "hz_grad": 90.0, + "start_mm": [25541.07, 12919.72, 2437.0], + "as_seite": "links", + "es_seite": "links", + "as_winkel": "90", + "es_winkel": "90", + "kreisel_start": "Kreisel_Arvato_02", + "kreisel_ende": "Kreisel_Arvato_03", + "hoehe_ende_mm": 1826.0, + "comment": "aus Zeichnung: GF_18 (ID 0028); Endhoehe = Hoehe von Kreisel_Arvato_03 (1826.0), Winkel rechnet der Treiber neu; gebaut war 2.4700 Grad -> HOEHE_BIS 1825" + }, + { + "test_id": "GF_Arvato_19", + "modus": 1, + "deltaL_mm": 11559.08, + "hz_grad": 90.0, + "start_mm": [26341.07, 12919.72, 2437.0], + "as_seite": "links", + "es_seite": "links", + "as_winkel": "90", + "es_winkel": "90", + "kreisel_start": "Kreisel_Arvato_02", + "kreisel_ende": "Kreisel_Arvato_03", + "hoehe_ende_mm": 1826.0, + "comment": "aus Zeichnung: GF_19 (ID 0029); Endhoehe = Hoehe von Kreisel_Arvato_03 (1826.0), Winkel rechnet der Treiber neu; gebaut war 2.4700 Grad -> HOEHE_BIS 1825" + }, + { + "test_id": "GF_Arvato_20", + "modus": 1, + "deltaL_mm": 11559.08, + "hz_grad": 90.0, + "start_mm": [27841.06, 12919.72, 2437.0], + "as_seite": "links", + "es_seite": "links", + "as_winkel": "90", + "es_winkel": "90", + "kreisel_start": "Kreisel_Arvato_02", + "kreisel_ende": "Kreisel_Arvato_03", + "hoehe_ende_mm": 1826.0, + "comment": "aus Zeichnung: GF_20 (ID 0030); Endhoehe = Hoehe von Kreisel_Arvato_03 (1826.0), Winkel rechnet der Treiber neu; gebaut war 2.4700 Grad -> HOEHE_BIS 1825" + }, + { + "test_id": "GF_Arvato_21", + "modus": 1, + "deltaL_mm": 11559.08, + "hz_grad": 90.0, + "start_mm": [28741.06, 12919.72, 2437.0], + "as_seite": "links", + "es_seite": "links", + "as_winkel": "90", + "es_winkel": "90", + "kreisel_start": "Kreisel_Arvato_02", + "kreisel_ende": "Kreisel_Arvato_03", + "hoehe_ende_mm": 1826.0, + "comment": "aus Zeichnung: GF_21 (ID 0031); Endhoehe = Hoehe von Kreisel_Arvato_03 (1826.0), Winkel rechnet der Treiber neu; gebaut war 2.4700 Grad -> HOEHE_BIS 1825" + }, + { + "test_id": "GF_Arvato_22", + "modus": 1, + "deltaL_mm": 11559.08, + "hz_grad": 90.0, + "start_mm": [29541.06, 12919.72, 2437.0], + "as_seite": "links", + "es_seite": "links", + "as_winkel": "90", + "es_winkel": "90", + "kreisel_start": "Kreisel_Arvato_02", + "kreisel_ende": "Kreisel_Arvato_03", + "hoehe_ende_mm": 1826.0, + "comment": "aus Zeichnung: GF_22 (ID 0032); Endhoehe = Hoehe von Kreisel_Arvato_03 (1826.0), Winkel rechnet der Treiber neu; gebaut war 2.4700 Grad -> HOEHE_BIS 1825" + }, + { + "test_id": "GF_Arvato_23", + "modus": 1, + "deltaL_mm": 11559.08, + "hz_grad": 90.0, + "start_mm": [30341.06, 12919.72, 2437.0], + "as_seite": "links", + "es_seite": "links", + "as_winkel": "90", + "es_winkel": "90", + "kreisel_start": "Kreisel_Arvato_02", + "kreisel_ende": "Kreisel_Arvato_03", + "hoehe_ende_mm": 1826.0, + "comment": "aus Zeichnung: GF_23 (ID 0033); Endhoehe = Hoehe von Kreisel_Arvato_03 (1826.0), Winkel rechnet der Treiber neu; gebaut war 2.4700 Grad -> HOEHE_BIS 1825" + }, + { + "test_id": "GF_Arvato_24", + "modus": 1, + "deltaL_mm": 11559.08, + "hz_grad": 90.0, + "start_mm": [31891.06, 12919.72, 2437.0], + "as_seite": "links", + "es_seite": "links", + "as_winkel": "90", + "es_winkel": "90", + "kreisel_start": "Kreisel_Arvato_02", + "kreisel_ende": "Kreisel_Arvato_03", + "hoehe_ende_mm": 1826.0, + "comment": "aus Zeichnung: GF_24 (ID 0034); Endhoehe = Hoehe von Kreisel_Arvato_03 (1826.0), Winkel rechnet der Treiber neu; gebaut war 2.4700 Grad -> HOEHE_BIS 1825" + }, + { + "test_id": "GF_Arvato_25", + "modus": 1, + "deltaL_mm": 11559.08, + "hz_grad": 90.0, + "start_mm": [32791.06, 12919.72, 2437.0], + "as_seite": "links", + "es_seite": "links", + "as_winkel": "90", + "es_winkel": "90", + "kreisel_start": "Kreisel_Arvato_02", + "kreisel_ende": "Kreisel_Arvato_03", + "hoehe_ende_mm": 1826.0, + "comment": "aus Zeichnung: GF_25 (ID 0035); Endhoehe = Hoehe von Kreisel_Arvato_03 (1826.0), Winkel rechnet der Treiber neu; gebaut war 2.4700 Grad -> HOEHE_BIS 1825" + }, + { + "test_id": "GF_Arvato_26", + "modus": 1, + "deltaL_mm": 11559.08, + "hz_grad": 90.0, + "start_mm": [33591.06, 12919.72, 2437.0], + "as_seite": "links", + "es_seite": "links", + "as_winkel": "90", + "es_winkel": "90", + "kreisel_start": "Kreisel_Arvato_02", + "kreisel_ende": "Kreisel_Arvato_03", + "hoehe_ende_mm": 1826.0, + "comment": "aus Zeichnung: GF_26 (ID 0036); Endhoehe = Hoehe von Kreisel_Arvato_03 (1826.0), Winkel rechnet der Treiber neu; gebaut war 2.4700 Grad -> HOEHE_BIS 1825" + }, + { + "test_id": "GF_Arvato_27", + "modus": 1, + "deltaL_mm": 11559.08, + "hz_grad": 90.0, + "start_mm": [34391.06, 12919.72, 2437.0], + "as_seite": "links", + "es_seite": "links", + "as_winkel": "90", + "es_winkel": "90", + "kreisel_start": "Kreisel_Arvato_02", + "kreisel_ende": "Kreisel_Arvato_03", + "hoehe_ende_mm": 1826.0, + "comment": "aus Zeichnung: GF_27 (ID 0037); Endhoehe = Hoehe von Kreisel_Arvato_03 (1826.0), Winkel rechnet der Treiber neu; gebaut war 2.4700 Grad -> HOEHE_BIS 1825" + }, + { + "function": "insert", + "block": "Separator_SP", + "x": 3038.72, + "y": 12119.73, + "z": 2473.0, + "rotation": 0.0, + "comment": "aus Zeichnung: Separator_SP_3D (ID 0038, ZUORDNUNG 0002, ARTINR 200000077)" + }, + { + "function": "insert", + "block": "Separator_SP", + "x": 3482.38, + "y": 24478.8, + "z": 1826.0, + "rotation": 0.0, + "comment": "aus Zeichnung: Separator_SP_3D (ID 0039, ZUORDNUNG 0003, ARTINR 200000077)" + }, + { + "function": "insert", + "block": "Separator_SP", + "x": 5094.1, + "y": 12919.72, + "z": 2473.0, + "rotation": 180.0, + "comment": "aus Zeichnung: Separator_SP_3D (ID 0040, ZUORDNUNG 0002, ARTINR 200000077)" + }, + { + "function": "insert", + "block": "Separator_SP", + "x": 15412.41, + "y": 24478.8, + "z": 1826.0, + "rotation": 0.0, + "comment": "aus Zeichnung: Separator_SP_3D (ID 0041, ZUORDNUNG 0003, ARTINR 200000077)" + }, + { + "function": "insert", + "block": "Separator_SP", + "x": 15994.1, + "y": 12919.78, + "z": 2473.0, + "rotation": 180.0, + "comment": "aus Zeichnung: Separator_SP_3D (ID 0042, ZUORDNUNG 0002, ARTINR 200000077)" + }, + { + "function": "insert", + "block": "Separator_SP", + "x": 19604.0, + "y": 5042.22, + "z": 2177.0, + "rotation": 270.0, + "comment": "aus Zeichnung: Separator_SP_3D (ID 0043, ZUORDNUNG 0056, ARTINR 200000077)" + }, + { + "function": "insert", + "block": "Separator_SP", + "x": 24002.04, + "y": 24478.8, + "z": 1826.0, + "rotation": 0.0, + "comment": "aus Zeichnung: Separator_SP_3D (ID 0044, ZUORDNUNG 0003, ARTINR 200000077)" + }, + { + "function": "insert", + "block": "Separator_SP", + "x": 24794.1, + "y": 12919.82, + "z": 2473.0, + "rotation": 180.0, + "comment": "aus Zeichnung: Separator_SP_3D (ID 0045, ZUORDNUNG 0002, ARTINR 200000077)" + }, + { + "function": "insert", + "block": "Separator_SP", + "x": 35050.37, + "y": 12919.72, + "z": 2437.0, + "rotation": 180.0, + "comment": "aus Zeichnung: Separator_SP_3D (ID 0046, ZUORDNUNG 0002, ARTINR 200000077)" + }, + { + "function": "insert", + "block": "Separator_SP", + "x": 35182.77, + "y": 25278.8, + "z": 1826.0, + "rotation": 180.0, + "comment": "aus Zeichnung: Separator_SP_3D (ID 0047, ZUORDNUNG 0003, ARTINR 200000077)" + }, + { + "function": "insert", + "block": "Separator_SP", + "x": 42163.99, + "y": 4269.52, + "z": 2177.0, + "rotation": 90.0, + "comment": "aus Zeichnung: Separator_SP_3D (ID 0048, ZUORDNUNG 0055, ARTINR 200000077)" + }, + { + "function": "insert", + "block": "Scanner", + "x": 3632.63, + "y": 24865.38, + "z": 1826.0, + "rotation": 90.0, + "comment": "aus Zeichnung: Scanner_3D (ID 0049, ZUORDNUNG 0003, ARTINR 829534306)" + }, + { + "function": "insert", + "block": "Scanner", + "x": 4943.86, + "y": 13306.31, + "z": 2473.0, + "rotation": 90.0, + "comment": "aus Zeichnung: Scanner_3D (ID 0050, ZUORDNUNG 0011, ARTINR 829534306)" + }, + { + "function": "insert", + "block": "Scanner", + "x": 15562.65, + "y": 24865.38, + "z": 1826.0, + "rotation": 90.0, + "comment": "aus Zeichnung: Scanner_3D (ID 0051, ZUORDNUNG 0003, ARTINR 829534306)" + }, + { + "function": "insert", + "block": "Scanner", + "x": 15843.86, + "y": 13306.36, + "z": 2473.0, + "rotation": 90.0, + "comment": "aus Zeichnung: Scanner_3D (ID 0052, ZUORDNUNG 0002, ARTINR 829534306)" + }, + { + "function": "insert", + "block": "Scanner", + "x": 24152.28, + "y": 24865.38, + "z": 1826.0, + "rotation": 90.0, + "comment": "aus Zeichnung: Scanner_3D (ID 0059, ZUORDNUNG 0003, ARTINR 829534306)" + }, + { + "function": "insert", + "block": "Scanner", + "x": 24643.86, + "y": 13306.4, + "z": 2473.0, + "rotation": 90.0, + "comment": "aus Zeichnung: Scanner_3D (ID 0054, ZUORDNUNG 0028, ARTINR 829534306)" + }, + { + "function": "insert", + "block": "BTMT-Beladung", + "x": 42163.78, + "y": 4978.68, + "z": 2177.0, + "rotation": 270.0, + "comment": "aus Zeichnung: BTMT-Beladung_3D (ID 0057, ARTINR 829662012)" + }, + { + "function": "insert", + "block": "SC_Entladung", + "x": 35609.45, + "y": 25278.8, + "z": 1826.0, + "rotation": 0.0, + "comment": "aus Zeichnung: SC_Entladung_3D (ID 0058, ARTINR 400100156)" + }, + { + "function": "insert", + "block": "Scanner", + "x": 42163.78, + "y": 4224.88, + "z": 2177.0, + "rotation": 180.0, + "comment": "aus Zeichnung: Scanner_3D (ID 0053, ZUORDNUNG 0055, ARTINR 829534306)" + } +] diff --git a/tests/testdata/showroom_ils.json b/tests/testdata/showroom_ils.json new file mode 100644 index 0000000..de0a0bd --- /dev/null +++ b/tests/testdata/showroom_ils.json @@ -0,0 +1,413 @@ +[ + { + "id": "Kreisel_Showroom_01", + "function": "insert", + "x": 26997.93, + "y": 3066.4, + "z": 2226.4, + "abstand": 9032.5, + "rotation": 90.0, + "typ": "STANDARD", + "darstellung": "2D", + "drehrichtung": "GUZ", + "comment": "aus DXF: Radmitte AN8 (Layer 1ANTR-800) (26997.93, 3466.40) -> SP8 (Layer 1SP-800) (26997.93, 12498.90), Mittenabstand 9032.50, rotation 90.00 Grad; x/y = AN8-Mitte minus Radius. ACHTUNG hoehe=11039.9 ist NICHT belegt: AN8/SP8 sind flache 2D-Symbole auf Z=0 und nur 0 3D-Nachbarn in 3000 mm - zu wenige fuer eine belegbare Hoehe. Hoehe 2226.4 vom Anwender.", + "hoehe_quelle": "Anwender" + }, + { + "id": "Kreisel_Showroom_02", + "function": "insert", + "x": 26997.93, + "y": 12898.9, + "z": 2226.4, + "abstand": 3000.0, + "rotation": 90.0, + "typ": "STANDARD", + "darstellung": "2D", + "drehrichtung": "UZS", + "comment": "aus DXF: Radmitte AN8 (Layer 1ANTR-800) (26997.93, 13298.90) -> SP8 (Layer 1SP-800) (26997.93, 16298.90), Mittenabstand 3000.00, rotation 90.00 Grad; x/y = AN8-Mitte minus Radius. ACHTUNG hoehe=11039.9 ist NICHT belegt: AN8/SP8 sind flache 2D-Symbole auf Z=0 und nur 0 3D-Nachbarn in 3000 mm - zu wenige fuer eine belegbare Hoehe. Hoehe 2226.4 vom Anwender.", + "hoehe_quelle": "Anwender" + }, + { + "id": "Kreisel_Showroom_03", + "function": "insert", + "x": 37105.93, + "y": 9888.9, + "z": 1700.0, + "abstand": 7200.0, + "rotation": 180.0, + "typ": "STANDARD", + "darstellung": "2D", + "drehrichtung": "GUZ", + "comment": "aus DXF: Radmitte AN8 (Layer 1ANTR-800) (36705.93, 9888.90) -> SP8 (Layer 1SP-800) (29505.93, 9888.90), Mittenabstand 7200.00, rotation 180.00 Grad; x/y = AN8-Mitte minus Radius. ACHTUNG hoehe=11039.9 ist NICHT belegt: AN8/SP8 sind flache 2D-Symbole auf Z=0 und nur 0 3D-Nachbarn in 3000 mm - zu wenige fuer eine belegbare Hoehe. Hoehe 1700.0 vom Anwender.", + "hoehe_quelle": "Anwender" + }, + { + "id": "Kreisel_Showroom_05", + "function": "insert", + "x": 27397.93, + "y": 16298.9, + "z": 2226.4, + "abstand": 8908.0, + "rotation": 0.0, + "typ": "STANDARD", + "darstellung": "2D", + "drehrichtung": "GUZ", + "comment": "aus DXF: Radmitte (manuell AN8) (Layer --kreisel-achse) (27797.93, 16298.90) -> (manuell SP8) (Layer --kreisel-achse) (36705.93, 16298.90), Mittenabstand 8908.00, rotation 0.00 Grad; x/y = AN8-Mitte minus Radius. ACHTUNG hoehe=0.0 ist NICHT belegt: AN8/SP8 sind flache 2D-Symbole auf Z=0 und nur 0 3D-Nachbarn in 3000 mm - zu wenige fuer eine belegbare Hoehe. Hoehe 2226.4 vom Anwender.", + "hoehe_quelle": "Anwender" + }, + { + "id": "Kreisel_Showroom_06", + "function": "insert", + "x": 35705.93, + "y": 13093.9, + "z": 1970.0, + "abstand": 6533.0, + "rotation": 180.0, + "typ": "STANDARD", + "darstellung": "2D", + "drehrichtung": "UZS", + "comment": "aus DXF: Schleife ohne AN8/SP8, Radmitten der Enden (35305.93, 13093.90) -> (28772.93, 13093.90) aus den PIN-Kreisen (--kreisel-achse), Mittenabstand 6533.00, rotation 180 - AN8-Seite bewusst OSTEN: der Export (export_neighbors.kreisel_half_bboxes) legt Bahn L links von AN8->SP8, und material_flow/tro_flow setzen den Bahnwechsel R->L (UZS) ans AN8-Ende. Mit AN8 im Westen lagen die Zulaeufe von Kreisel_Showroom_05 auf L und die Ablaeufe nach Kreisel_Showroom_03 auf R - Bahn 0005-L wurde zur Sackgasse. Hoehe 1970 vom Anwender.", + "hoehe_quelle": "Anwender" + }, + { + "test_id": "VF_Showroom_01", + "typ": "standard", + "richtung": "Auf", + "deltaL": 5610.0, + "deltaH": 526.4, + "seite": "links", + "hz": 90.0, + "x": 36305.93, + "y": 10288.9, + "z": 1700.0, + "comment": "von Hand: ersetzt Kreisel_Showroom_04 (vertikaler Kreisel AN8 (36706, 10689) -> SP8 (36706, 15499)) samt dessen Separator/Scanner bei y=14979. AS auf der oberen Kette von Kreisel_Showroom_03 (y=10288.9, Hoehe 1700) auf dem linken Strang des alten Kreisels (x=36306), Steigung +Y bis zur unteren Kette von Kreisel_Showroom_05 (y=15898.9, Hoehe 2226.4): deltaL 15898.9 - 10288.9 = 5610, deltaH 2226.4 - 1700 = 526.4. seite noch offen (links als Vorgabe), winkel waehlt der Solver." + }, + { + "test_id": "GF_Showroom_01", + "modus": 1, + "deltaL_mm": 2405.0, + "hz_grad": 270.0, + "start_mm": [30455.93, 15898.9, 2226.4], + "as_seite": "links", + "es_seite": "rechts", + "as_winkel": "90", + "es_winkel": "90", + "kreisel_start": "Kreisel_Showroom_05", + "kreisel_ende": "Kreisel_Showroom_06", + "hoehe_ende_mm": 1970.0, + "hoehe_quelle": "Anwender", + "comment": "aus DXF: LINE auf Layer '6-SP' (30455.93, 15593.90) -> (30455.93, 13893.90), Laenge XY 1700.00; am oberen Ende um 305.0 mm bis zur Kette von Kreisel_Showroom_05 verlaengert; am unteren Ende um 400.0 mm bis zur Kette von Kreisel_Showroom_06 verlaengert; Hoehen vom Anwender (Kreisel Kreisel_Showroom_05 / Kreisel_Showroom_06), den Winkel rechnet der Treiber; as_seite links aus der Lage zur Fahrtrichtung; AS aus Block 'AE-D' bei (30636.27, 15898.90) -> AS_Element_90_links, als Einzel-Insert entfernt (baut die GF selbst) - geraten: Winkel=90; Separator auf der Achse entfernt (die GF baut ihren eigenen): S-SP bei (30456, 13994), S-SP bei (30456, 14807); es_seite rechts aus der Lage zur Fahrtrichtung; ES aus Block 'EE-R' bei (30243.76, 13493.90) -> ES_Element_90_rechts, als Einzel-Insert entfernt (baut die GF selbst) - geraten: Winkel=90." + }, + { + "test_id": "GF_Showroom_02", + "modus": 1, + "deltaL_mm": 2405.0, + "hz_grad": 270.0, + "start_mm": [31255.93, 12693.9, 1970.0], + "as_seite": "rechts", + "es_seite": "links", + "as_winkel": "90", + "es_winkel": "90", + "kreisel_start": "Kreisel_Showroom_06", + "kreisel_ende": "Kreisel_Showroom_03", + "hoehe_ende_mm": 1700.0, + "hoehe_quelle": "Anwender", + "comment": "aus DXF: LINE auf Layer '6-SP' (31255.93, 12388.90) -> (31255.93, 10688.90), Laenge XY 1700.00; am oberen Ende um 305.0 mm bis zur Kette von Kreisel_Showroom_06 verlaengert; am unteren Ende um 400.0 mm bis zur Kette von Kreisel_Showroom_03 verlaengert; Hoehen vom Anwender (Kreisel Kreisel_Showroom_06 / Kreisel_Showroom_03), den Winkel rechnet der Treiber; as_seite rechts aus der Lage zur Fahrtrichtung; AS aus Block 'AE-D' bei (31075.60, 12693.90) -> AS_Element_90_rechts, als Einzel-Insert entfernt (baut die GF selbst) - geraten: Winkel=90; Separator auf der Achse entfernt (die GF baut ihren eigenen): S-SP bei (31256, 10789), S-SP bei (31256, 11602); es_seite links aus der Lage zur Fahrtrichtung; ES aus Block 'EE-R' bei (31468.11, 10288.90) -> ES_Element_90_links, als Einzel-Insert entfernt (baut die GF selbst) - geraten: Winkel=90." + }, + { + "test_id": "GF_Showroom_03", + "modus": 1, + "deltaL_mm": 2405.0, + "hz_grad": 270.0, + "start_mm": [31255.93, 15898.9, 2226.4], + "as_seite": "links", + "es_seite": "rechts", + "as_winkel": "90", + "es_winkel": "90", + "kreisel_start": "Kreisel_Showroom_05", + "kreisel_ende": "Kreisel_Showroom_06", + "hoehe_ende_mm": 1970.0, + "hoehe_quelle": "Anwender", + "comment": "aus DXF: LINE auf Layer '6-SP' (31255.93, 15593.90) -> (31255.93, 13893.90), Laenge XY 1700.00; am oberen Ende um 305.0 mm bis zur Kette von Kreisel_Showroom_05 verlaengert; am unteren Ende um 400.0 mm bis zur Kette von Kreisel_Showroom_06 verlaengert; Hoehen vom Anwender (Kreisel Kreisel_Showroom_05 / Kreisel_Showroom_06), den Winkel rechnet der Treiber; as_seite links aus der Lage zur Fahrtrichtung; AS aus Block 'AE-D' bei (31436.27, 15898.90) -> AS_Element_90_links, als Einzel-Insert entfernt (baut die GF selbst) - geraten: Winkel=90; Separator auf der Achse entfernt (die GF baut ihren eigenen): S-SP bei (31256, 13994), S-SP bei (31256, 14807); es_seite rechts aus der Lage zur Fahrtrichtung; ES aus Block 'EE-R' bei (31043.76, 13493.90) -> ES_Element_90_rechts, als Einzel-Insert entfernt (baut die GF selbst) - geraten: Winkel=90." + }, + { + "test_id": "GF_Showroom_04", + "modus": 1, + "deltaL_mm": 2405.0, + "hz_grad": 270.0, + "start_mm": [32055.93, 12693.9, 1970.0], + "as_seite": "rechts", + "es_seite": "links", + "as_winkel": "90", + "es_winkel": "90", + "kreisel_start": "Kreisel_Showroom_06", + "kreisel_ende": "Kreisel_Showroom_03", + "hoehe_ende_mm": 1700.0, + "hoehe_quelle": "Anwender", + "comment": "aus DXF: LINE auf Layer '6-SP' (32055.93, 12388.90) -> (32055.93, 10688.90), Laenge XY 1700.00; am oberen Ende um 305.0 mm bis zur Kette von Kreisel_Showroom_06 verlaengert; am unteren Ende um 400.0 mm bis zur Kette von Kreisel_Showroom_03 verlaengert; Hoehen vom Anwender (Kreisel Kreisel_Showroom_06 / Kreisel_Showroom_03), den Winkel rechnet der Treiber; as_seite rechts aus der Lage zur Fahrtrichtung; AS aus Block 'AE-D' bei (31875.60, 12693.90) -> AS_Element_90_rechts, als Einzel-Insert entfernt (baut die GF selbst) - geraten: Winkel=90; Separator auf der Achse entfernt (die GF baut ihren eigenen): S-SP bei (32056, 10789), S-SP bei (32056, 11602); es_seite links aus der Lage zur Fahrtrichtung; ES aus Block 'EE-R' bei (32268.11, 10288.90) -> ES_Element_90_links, als Einzel-Insert entfernt (baut die GF selbst) - geraten: Winkel=90." + }, + { + "test_id": "GF_Showroom_05", + "modus": 1, + "deltaL_mm": 2405.0, + "hz_grad": 270.0, + "start_mm": [32055.93, 15898.9, 2226.4], + "as_seite": "links", + "es_seite": "rechts", + "as_winkel": "90", + "es_winkel": "90", + "kreisel_start": "Kreisel_Showroom_05", + "kreisel_ende": "Kreisel_Showroom_06", + "hoehe_ende_mm": 1970.0, + "hoehe_quelle": "Anwender", + "comment": "aus DXF: LINE auf Layer '6-SP' (32055.93, 15593.90) -> (32055.93, 13893.90), Laenge XY 1700.00; am oberen Ende um 305.0 mm bis zur Kette von Kreisel_Showroom_05 verlaengert; am unteren Ende um 400.0 mm bis zur Kette von Kreisel_Showroom_06 verlaengert; Hoehen vom Anwender (Kreisel Kreisel_Showroom_05 / Kreisel_Showroom_06), den Winkel rechnet der Treiber; as_seite links aus der Lage zur Fahrtrichtung; AS aus Block 'AE-D' bei (32236.27, 15898.90) -> AS_Element_90_links, als Einzel-Insert entfernt (baut die GF selbst) - geraten: Winkel=90; Separator auf der Achse entfernt (die GF baut ihren eigenen): S-SP bei (32056, 13994), S-SP bei (32056, 14807); es_seite rechts aus der Lage zur Fahrtrichtung; ES aus Block 'EE-R' bei (31843.76, 13493.90) -> ES_Element_90_rechts, als Einzel-Insert entfernt (baut die GF selbst) - geraten: Winkel=90." + }, + { + "test_id": "GF_Showroom_06", + "modus": 1, + "deltaL_mm": 2405.0, + "hz_grad": 270.0, + "start_mm": [33205.93, 12693.9, 1970.0], + "as_seite": "rechts", + "es_seite": "links", + "as_winkel": "90", + "es_winkel": "90", + "kreisel_start": "Kreisel_Showroom_06", + "kreisel_ende": "Kreisel_Showroom_03", + "hoehe_ende_mm": 1700.0, + "hoehe_quelle": "Anwender", + "comment": "aus DXF: LINE auf Layer '6-SP' (33205.93, 12388.90) -> (33205.93, 10688.90), Laenge XY 1700.00; am oberen Ende um 305.0 mm bis zur Kette von Kreisel_Showroom_06 verlaengert; am unteren Ende um 400.0 mm bis zur Kette von Kreisel_Showroom_03 verlaengert; Hoehen vom Anwender (Kreisel Kreisel_Showroom_06 / Kreisel_Showroom_03), den Winkel rechnet der Treiber; as_seite rechts aus der Lage zur Fahrtrichtung; AS aus Block 'AE-D' bei (33025.60, 12693.90) -> AS_Element_90_rechts, als Einzel-Insert entfernt (baut die GF selbst) - geraten: Winkel=90; Separator auf der Achse entfernt (die GF baut ihren eigenen): S-SP bei (33206, 10789), S-SP bei (33206, 11602); es_seite links aus der Lage zur Fahrtrichtung; ES aus Block 'EE-R' bei (33418.11, 10288.90) -> ES_Element_90_links, als Einzel-Insert entfernt (baut die GF selbst) - geraten: Winkel=90." + }, + { + "test_id": "GF_Showroom_07", + "modus": 1, + "deltaL_mm": 2405.0, + "hz_grad": 270.0, + "start_mm": [33205.93, 15898.9, 2226.4], + "as_seite": "links", + "es_seite": "rechts", + "as_winkel": "90", + "es_winkel": "90", + "kreisel_start": "Kreisel_Showroom_05", + "kreisel_ende": "Kreisel_Showroom_06", + "hoehe_ende_mm": 1970.0, + "hoehe_quelle": "Anwender", + "comment": "aus DXF: LINE auf Layer '6-SP' (33205.93, 15593.90) -> (33205.93, 13893.90), Laenge XY 1700.00; am oberen Ende um 305.0 mm bis zur Kette von Kreisel_Showroom_05 verlaengert; am unteren Ende um 400.0 mm bis zur Kette von Kreisel_Showroom_06 verlaengert; Hoehen vom Anwender (Kreisel Kreisel_Showroom_05 / Kreisel_Showroom_06), den Winkel rechnet der Treiber; as_seite links aus der Lage zur Fahrtrichtung; AS aus Block 'AE-D' bei (33386.27, 15898.90) -> AS_Element_90_links, als Einzel-Insert entfernt (baut die GF selbst) - geraten: Winkel=90; Separator auf der Achse entfernt (die GF baut ihren eigenen): S-SP bei (33206, 13994), S-SP bei (33206, 14807); es_seite rechts aus der Lage zur Fahrtrichtung; ES aus Block 'EE-R' bei (32993.76, 13493.90) -> ES_Element_90_rechts, als Einzel-Insert entfernt (baut die GF selbst) - geraten: Winkel=90." + }, + { + "test_id": "GF_Showroom_08", + "modus": 1, + "deltaL_mm": 2405.0, + "hz_grad": 270.0, + "start_mm": [34005.93, 12693.9, 1970.0], + "as_seite": "rechts", + "es_seite": "links", + "as_winkel": "90", + "es_winkel": "90", + "kreisel_start": "Kreisel_Showroom_06", + "kreisel_ende": "Kreisel_Showroom_03", + "hoehe_ende_mm": 1700.0, + "hoehe_quelle": "Anwender", + "comment": "aus DXF: LINE auf Layer '6-SP' (34005.93, 12388.90) -> (34005.93, 10688.90), Laenge XY 1700.00; am oberen Ende um 305.0 mm bis zur Kette von Kreisel_Showroom_06 verlaengert; am unteren Ende um 400.0 mm bis zur Kette von Kreisel_Showroom_03 verlaengert; Hoehen vom Anwender (Kreisel Kreisel_Showroom_06 / Kreisel_Showroom_03), den Winkel rechnet der Treiber; as_seite rechts aus der Lage zur Fahrtrichtung; AS aus Block 'AE-D' bei (33825.60, 12693.90) -> AS_Element_90_rechts, als Einzel-Insert entfernt (baut die GF selbst) - geraten: Winkel=90; Separator auf der Achse entfernt (die GF baut ihren eigenen): S-SP bei (34006, 10789), S-SP bei (34006, 11602); es_seite links aus der Lage zur Fahrtrichtung; ES aus Block 'EE-R' bei (34218.11, 10288.90) -> ES_Element_90_links, als Einzel-Insert entfernt (baut die GF selbst) - geraten: Winkel=90." + }, + { + "test_id": "GF_Showroom_09", + "modus": 1, + "deltaL_mm": 2405.0, + "hz_grad": 270.0, + "start_mm": [34005.93, 15898.9, 2226.4], + "as_seite": "links", + "es_seite": "rechts", + "as_winkel": "90", + "es_winkel": "90", + "kreisel_start": "Kreisel_Showroom_05", + "kreisel_ende": "Kreisel_Showroom_06", + "hoehe_ende_mm": 1970.0, + "hoehe_quelle": "Anwender", + "comment": "aus DXF: LINE auf Layer '6-SP' (34005.93, 15593.90) -> (34005.93, 13893.90), Laenge XY 1700.00; am oberen Ende um 305.0 mm bis zur Kette von Kreisel_Showroom_05 verlaengert; am unteren Ende um 400.0 mm bis zur Kette von Kreisel_Showroom_06 verlaengert; Hoehen vom Anwender (Kreisel Kreisel_Showroom_05 / Kreisel_Showroom_06), den Winkel rechnet der Treiber; as_seite links aus der Lage zur Fahrtrichtung; AS aus Block 'AE-D' bei (34186.27, 15898.90) -> AS_Element_90_links, als Einzel-Insert entfernt (baut die GF selbst) - geraten: Winkel=90; Separator auf der Achse entfernt (die GF baut ihren eigenen): S-SP bei (34006, 13994), S-SP bei (34006, 14807); es_seite rechts aus der Lage zur Fahrtrichtung; ES aus Block 'EE-R' bei (33793.76, 13493.90) -> ES_Element_90_rechts, als Einzel-Insert entfernt (baut die GF selbst) - geraten: Winkel=90." + }, + { + "test_id": "GF_Showroom_10", + "modus": 1, + "deltaL_mm": 2405.0, + "hz_grad": 270.0, + "start_mm": [34805.93, 12693.9, 1970.0], + "as_seite": "rechts", + "es_seite": "links", + "as_winkel": "90", + "es_winkel": "90", + "kreisel_start": "Kreisel_Showroom_06", + "kreisel_ende": "Kreisel_Showroom_03", + "hoehe_ende_mm": 1700.0, + "hoehe_quelle": "Anwender", + "comment": "aus DXF: LINE auf Layer '6-SP' (34805.93, 12388.90) -> (34805.93, 10688.90), Laenge XY 1700.00; am oberen Ende um 305.0 mm bis zur Kette von Kreisel_Showroom_06 verlaengert; am unteren Ende um 400.0 mm bis zur Kette von Kreisel_Showroom_03 verlaengert; Hoehen vom Anwender (Kreisel Kreisel_Showroom_06 / Kreisel_Showroom_03), den Winkel rechnet der Treiber; as_seite rechts aus der Lage zur Fahrtrichtung; AS aus Block 'AE-D' bei (34625.60, 12693.90) -> AS_Element_90_rechts, als Einzel-Insert entfernt (baut die GF selbst) - geraten: Winkel=90; Separator auf der Achse entfernt (die GF baut ihren eigenen): S-SP bei (34806, 10789), S-SP bei (34806, 11602); es_seite links aus der Lage zur Fahrtrichtung; ES aus Block 'EE-R' bei (35018.11, 10288.90) -> ES_Element_90_links, als Einzel-Insert entfernt (baut die GF selbst) - geraten: Winkel=90." + }, + { + "test_id": "GF_Showroom_11", + "modus": 1, + "deltaL_mm": 2405.0, + "hz_grad": 270.0, + "start_mm": [34805.93, 15898.9, 2226.4], + "as_seite": "links", + "es_seite": "rechts", + "as_winkel": "90", + "es_winkel": "90", + "kreisel_start": "Kreisel_Showroom_05", + "kreisel_ende": "Kreisel_Showroom_06", + "hoehe_ende_mm": 1970.0, + "hoehe_quelle": "Anwender", + "comment": "aus DXF: LINE auf Layer '6-SP' (34805.93, 15593.90) -> (34805.93, 13893.90), Laenge XY 1700.00; am oberen Ende um 305.0 mm bis zur Kette von Kreisel_Showroom_05 verlaengert; am unteren Ende um 400.0 mm bis zur Kette von Kreisel_Showroom_06 verlaengert; Hoehen vom Anwender (Kreisel Kreisel_Showroom_05 / Kreisel_Showroom_06), den Winkel rechnet der Treiber; as_seite links aus der Lage zur Fahrtrichtung; AS aus Block 'AE-D' bei (34986.27, 15898.90) -> AS_Element_90_links, als Einzel-Insert entfernt (baut die GF selbst) - geraten: Winkel=90; Separator auf der Achse entfernt (die GF baut ihren eigenen): S-SP bei (34806, 13994), S-SP bei (34806, 14807); es_seite rechts aus der Lage zur Fahrtrichtung; ES aus Block 'EE-R' bei (34593.76, 13493.90) -> ES_Element_90_rechts, als Einzel-Insert entfernt (baut die GF selbst) - geraten: Winkel=90." + }, + { + "function": "insert", + "block": "S-LP", + "x": 26597.93, + "y": 11973.9, + "z": 2226.4, + "rotation": 270.0, + "darstellung": "2D", + "comment": "aus DXF: Block 'S-LP', Layer '5SEP-LP'; Block 'S-LP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter; 2D-Schema ohne 3D-Zwilling - z ist keine Einbauhoehe (hoehe_unbekannt) [Hoehe entfernt: --ohne-hoehen] Hoehe 2226.4 von Kreisel_Showroom_01 (liegt auf dessen Kette).", + "hoehe_quelle": "Anwender" + }, + { + "function": "insert", + "block": "S-LP", + "x": 26597.93, + "y": 13982.92, + "z": 2226.4, + "rotation": 90.0, + "darstellung": "2D", + "comment": "aus DXF: Block 'S-LP', Layer 'TGW_HB_2OG-AM_5'; Block 'S-LP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter; 2D-Schema ohne 3D-Zwilling - z ist keine Einbauhoehe (hoehe_unbekannt) [Hoehe entfernt: --ohne-hoehen] Hoehe 2226.4 von Kreisel_Showroom_02 (liegt auf dessen Kette).", + "hoehe_quelle": "Anwender" + }, + { + "function": "insert", + "block": "S-LP", + "x": 27397.93, + "y": 5450.07, + "z": 2226.4, + "rotation": 90.0, + "darstellung": "2D", + "comment": "aus DXF: Block 'S-LP', Layer 'TGW_HB_2OG-AM_5'; Block 'S-LP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter; 2D-Schema ohne 3D-Zwilling - z ist keine Einbauhoehe (hoehe_unbekannt) [Hoehe entfernt: --ohne-hoehen] Hoehe 2226.4 von Kreisel_Showroom_01 (liegt auf dessen Kette).", + "hoehe_quelle": "Anwender" + }, + { + "function": "insert", + "block": "S-LP", + "x": 27397.93, + "y": 15773.9, + "z": 2226.4, + "rotation": 270.0, + "darstellung": "2D", + "comment": "aus DXF: Block 'S-LP', Layer '5SEP-LP'; Block 'S-LP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter; 2D-Schema ohne 3D-Zwilling - z ist keine Einbauhoehe (hoehe_unbekannt) [Hoehe entfernt: --ohne-hoehen] Hoehe 2226.4 von Kreisel_Showroom_02 (liegt auf dessen Kette).", + "hoehe_quelle": "Anwender" + }, + { + "function": "insert", + "block": "S-LP", + "x": 29397.93, + "y": 13493.9, + "z": 1970.0, + "rotation": 0.0, + "darstellung": "2D", + "comment": "aus DXF: Block 'S-LP', Layer '5SEP-LP'; Block 'S-LP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter; 2D-Schema ohne 3D-Zwilling - z ist keine Einbauhoehe (hoehe_unbekannt) [Hoehe entfernt: --ohne-hoehen] Hoehe 1970 von Kreisel_Showroom_06 (liegt auf dessen Kette).", + "hoehe_quelle": "Anwender" + }, + { + "function": "insert", + "block": "S-LP", + "x": 30343.39, + "y": 9488.9, + "z": 1700.0, + "rotation": 0.0, + "darstellung": "2D", + "comment": "aus DXF: Block 'S-LP', Layer '5SEP-LP'; Block 'S-LP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter; 2D-Schema ohne 3D-Zwilling - z ist keine Einbauhoehe (hoehe_unbekannt) [Hoehe entfernt: --ohne-hoehen] Hoehe 1700.0 von Kreisel_Showroom_03 (liegt auf dessen Kette).", + "hoehe_quelle": "Anwender" + }, + { + "function": "insert", + "block": "S-LP", + "x": 32083.74, + "y": 9488.9, + "z": 1700.0, + "rotation": 0.0, + "darstellung": "2D", + "comment": "aus DXF: Block 'S-LP', Layer '5SEP-LP'; Block 'S-LP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter; 2D-Schema ohne 3D-Zwilling - z ist keine Einbauhoehe (hoehe_unbekannt) [Hoehe entfernt: --ohne-hoehen] Hoehe 1700.0 von Kreisel_Showroom_03 (liegt auf dessen Kette).", + "hoehe_quelle": "Anwender" + }, + { + "function": "insert", + "block": "S-LP", + "x": 35905.93, + "y": 10288.9, + "z": 1700.0, + "rotation": 180.0, + "darstellung": "2D", + "comment": "aus DXF: Block 'S-LP', Layer '5SEP-LP'; Block 'S-LP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter; 2D-Schema ohne 3D-Zwilling - z ist keine Einbauhoehe (hoehe_unbekannt) [Hoehe entfernt: --ohne-hoehen] Hoehe 1700.0 von Kreisel_Showroom_03 (liegt auf dessen Kette).", + "hoehe_quelle": "Anwender" + }, + { + "function": "insert", + "block": "Scanner", + "x": 29398.73, + "y": 13176.71, + "z": 1970.0, + "rotation": 270.0, + "darstellung": "2D", + "comment": "aus DXF: Block 'SCAN', Layer 'ZZSCANNER', auf Makro-Block 'Scanner' abgebildet; 2D-Schema ohne 3D-Zwilling - z ist keine Einbauhoehe (hoehe_unbekannt) [Hoehe entfernt: --ohne-hoehen] Hoehe 1970 von Kreisel_Showroom_06 (liegt auf dessen Kette).", + "hoehe_quelle": "Anwender" + }, + { + "function": "insert", + "block": "Scanner", + "x": 30343.39, + "y": 9208.9, + "z": 1700.0, + "rotation": 270.0, + "darstellung": "2D", + "comment": "aus DXF: Block 'SCAN', Layer 'ZZSCANNER', auf Makro-Block 'Scanner' abgebildet; 2D-Schema ohne 3D-Zwilling - z ist keine Einbauhoehe (hoehe_unbekannt) [Hoehe entfernt: --ohne-hoehen] Hoehe 1700.0 von Kreisel_Showroom_03 (liegt auf dessen Kette).", + "hoehe_quelle": "Anwender" + }, + { + "function": "insert", + "block": "Scanner", + "x": 32083.74, + "y": 9208.9, + "z": 1700.0, + "rotation": 270.0, + "darstellung": "2D", + "comment": "aus DXF: Block 'SCAN', Layer 'ZZSCANNER', auf Makro-Block 'Scanner' abgebildet; 2D-Schema ohne 3D-Zwilling - z ist keine Einbauhoehe (hoehe_unbekannt) [Hoehe entfernt: --ohne-hoehen] Hoehe 1700.0 von Kreisel_Showroom_03 (liegt auf dessen Kette).", + "hoehe_quelle": "Anwender" + }, + { + "function": "insert", + "block": "Scanner", + "x": 35905.93, + "y": 10008.9, + "z": 1700.0, + "rotation": 270.0, + "darstellung": "2D", + "comment": "aus DXF: Block 'SCAN', Layer 'ZZSCANNER', auf Makro-Block 'Scanner' abgebildet; 2D-Schema ohne 3D-Zwilling - z ist keine Einbauhoehe (hoehe_unbekannt) [Hoehe entfernt: --ohne-hoehen] Hoehe 1700.0 von Kreisel_Showroom_03 (liegt auf dessen Kette).", + "hoehe_quelle": "Anwender" + }, + { + "id": "Showroom_BTMT_BEL_01", + "comment": "BTMT Beladung auf Kreisel_Showroom_03 (Hoehe 1700) - von Hand in der gebauten Showroom-Zeichnung ergaenzt, Einfuegepunkt/Drehung aus results/export_raw.json (EXPORTCSV vom 2026-10-07, Block BTMT-Beladung_3D, ID 0040)", + "function": "insert", + "block": "BTMT-Beladung", + "x": 29877.99, + "y": 9478.98, + "z": 1690.08, + "rotation": 180.0 + }, + { + "id": "Showroom_BTMT_ENT_01", + "comment": "BTMT Entladung auf Kreisel_Showroom_01 (Hoehe 2226.4) - von Hand in der gebauten Showroom-Zeichnung ergaenzt, Einfuegepunkt/Drehung aus results/export_raw.json (EXPORTCSV vom 2026-10-07, Block SC_Entladung_3D, ID 0041)", + "function": "insert", + "block": "SC_Entladung", + "x": 27397.93, + "y": 4555.45, + "z": 2226.4, + "rotation": 270.0 + } +]