<description><p>Dieses Ticket implementiert die interaktive Planung eines Schutzzauns zwischen zwei Punkten im 3D-Layout. Die Funktion ermöglicht das Ziehen einer Strecke, berechnet die Geometrie und generiert den Zaun als kaskadierende Baugruppe. Neu ist die intelligente Vererbungslogik: Werden PMIs als Start- oder Endpunkt genutzt, erbt der Zaun Typ und Füllung automatisch; andernfalls werden diese über ein modales Overlay abgefragt.</p>
<h3><a name="%F0%9F%9F%A6DefinitionofReady%C2%A0"></a><b>�� Definition of Ready</b> </h3>
<p>Effiziente Planung von Schutzzäunen über größere Distanzen, ohne dass der Nutzer jedes Element einzeln platzieren muss und unter Vermeidung von fehlerhaften Typen-Mischungen.</p>
<p>Ein strukturierter, in den Szenegraphen integrierter Schutzzaun-Zug, mit dem offene Züge geschlossen werden können.</p></description>
<environment></environment>
<keyid="139286">GEKO-16</key>
<summary>Schutzzaun im Layout schließen/Schutzzaun zwischen zwei Punkten planen</summary>
<p><b>Input:</b> Klick-Events im 3D-Viewport und ggf. Auswahl-Events im modalen Overlay.</p>
<ul>
<li><b>Verhalten / Ablauf:</b>
<ol>
<li><b>Startpunkt (Klick 1):</b> Nutzer setzt den ersten Punkt (Typ 1 Weltkoordinaten oder Typ 2 PMI).</li>
<li><b>Zeichnen &amp; Filtern:</b> Unmittelbar <b>nach dem 1. Klick</b> (sofern dieser an einem PMI erfolgte) filtert die Engine den Szenegraphen. Anhand des Typs des Start-PMIs (z. B. Basic) werden alle inkompatiblen PMIs (z. B. Allround) für den aktuellen Zeichen-Modus als ungültig markiert. Während der Nutzer nun die Gummiband-Linie zum Endpunkt zieht, werden diese inkompatiblen PMIs konsequent nicht gehighlighted und können nicht angeklickt werden.</li>
<li><b>Endpunkt (Klick 2):</b> Nutzer setzt den zweiten Punkt.</li>
<li><b>Vererbungslogik &amp; Konfiguration:</b> Die Engine entscheidet anhand folgender Logik-Matrix, woher die Parameter (Typ/Füllung) stammen:
<ul>
<li><b>Start: Frei | Ende: Frei</b>→ Keine Vererbung. Das modale Auswahl-Overlay öffnet sich. Standard-Elemente werden nach Bestätigung generiert.</li>
<li><b>Start: PMI | Ende: Frei</b>→ Vererbung der Properties vom Start-PMI. Das Overlay erscheint <b>nicht</b>.</li>
<li><b>Start: Frei | Ende: PMI</b>→ Vererbung der Properties vom End-PMI. Das Overlay erscheint <b>nicht</b>.</li>
<li><b>Start: PMI | Ende: PMI</b>→ Vererbung der Properties vom Start-PMI. (Typengleichheit ist durch das Filtern in Schritt 2 bereits sichergestellt). Das Overlay erscheint <b>nicht</b>.</li>
</ul>
</li>
<li><b>Geometrie-Berechnung:</b> Y-Offset und Rotationen werden ignoriert. Absolute Strecke über Pythagoras ({{L = √(Δx² + Δz²)}}), Winkel über <tt>atan2(Δz, Δx)</tt> (auf XZ-Ebene projiziert).</li>
<li><b>Element-Aufteilung:</b> Die Logik <tt>berechnung_aufteilung_laenge</tt> ermittelt die Rasterelemente <em>(siehe Algorithmus-Abschnitt unten)</em>.</li>
<li><b>Generierung:</b> Elemente werden kaskadierend via PMI (Typ 2) eingebaut.</li>
<li><b>Validierung:</b> Trigger der lokalen Validierungsengine.</li>
<li><b>Typ "Basic":</b> Der Elementabstand wird als Parameter übergeben; es gibt keinen Steherzusatz (0 mm).</li>
<li><b>Typ "Allround":</b> Fester Elementabstand von <b>11 mm</b>, Steherzusatz von <b>51 mm</b> pro Element.</li>
<li><b>Türen:</b> Ist eine Tür in der Konfiguration enthalten, wird pauschal ein Maß von <b>1124 mm</b> (zzgl. Elementabstand) von der Gesamtbreite abgezogen.</li>
</ul>
</li>
<li><b>2. Standardelemente (Greedy):</b> Der Raum wird systematisch mit den größten passenden Standardelementen gefüllt (Priorität: 1507, 1034, 776, 518 mm).</li>
<li><b>3. Dead Zone Correction (Todeszone):</b> Bleibt eine kritische Restbreite (zwischen 43 mm und Mindestbreite der Füllung) übrig, wird das kleinste bereits verplante Element downgradet (z. B. 776 mm -&gt; 518 mm) oder gelöscht, um Platz für ein produzierbares Sonderelement zu schaffen.</li>
<li><b>4. Sonderelement (Rasterung):</b> Aus der Restbreite wird im <b>43-mm-Raster</b> das finale Passstück ermittelt. Restlücken &lt; 43 mm werden als Toleranz ignoriert.</li>
</ul>
<p><b>Pseudocode der Aufteilungslogik:</b></p>
<li>Die Vererbungsmatrix wird strikt eingehalten: Das Konfigurations-Overlay öffnet sich <em>ausschließlich</em>, wenn sowohl Start- als auch Endpunkt frei im Raum (Typ 1) liegen.</li>
<li>Ist der Startpunkt ein PMI, werden inkompatible PMIs im Viewer beim Hovern ignoriert und nicht als Snapping-Ziel angeboten.</li>
<li>Startet der Zaun frei und endet an einem PMI, übernimmt der Zaun die Parameter des Ziel-Elements fehlerfrei (Rückwärtsvererbung).</li>
<li>Geometrie wird flach auf der XZ-Ebene berechnet; Höhenunterschiede zwischen den Punkten führen nicht zu schiefen Zäunen.</li>
<li>Kaskadierendes Verhalten: Bei Verschiebung des Eltern-Bauteils bewegt sich der gesamte Zaun-Verbund mit.</li>
</ul>
<hr />
<h3><a name="%F0%9F%A7%AATestCases%3A"></a><b>�� Test Cases:</b></h3>
<ul>
<li><b><span class="error">&#91;Rückwärtsvererbung&#93;</span></b>: Startpunkt ins Freie klicken -&gt; Endpunkt an PMI eines Basic-Zauns mit Polycarbonat-Füllung snappen -&gt; <em>Erwartetes Ergebnis:</em> Kein Overlay öffnet sich. Es wird sofort ein Basic-Zaun mit Polycarbonat generiert, der an das Ziel snappt.</li>
<li><b><span class="error">&#91;PMI Typen-Filter&#93;</span></b>: Startpunkt an Allround-PMI klicken -&gt; Maus über ein Basic-PMI bewegen -&gt; <em>Erwartetes Ergebnis:</em> Das Basic-PMI wird nicht gehighlighted und kann nicht als Endpunkt ausgewählt werden.</li>
<li><b><span class="error">&#91;Abbruch während Zeichnen&#93;</span></b>: <tt>ESC</tt> oder ein Klick außerhalb des Canvas bricht den Modus nach Klick 1 restlos ab.</li>
<li><b><span class="error">&#91;Distanz zu kurz&#93;</span></b>: Ist die Gesamtdistanz kleiner als das schmalste verfügbare Rasterelement, führt der Abschluss im Overlay zu einem No-Op (keine Generierung).</li>
<li><b><span class="error">&#91;Eltern-Löschung&#93;</span></b>: Wird das Bauteil gelöscht, an dessen PMI der Zaun beginnt, verliert das erste Zaunelement seine Typ 2 Bedingung, fällt auf Typ 1 zurück und der Zaun bleibt stehen.</li>
- [cpq Verbau 90° am Steher](cpq-verbau-90grad-steher.md) — Engine kann's, cpq-GroupProcessor (translation-only) plättet die Rotation beim Replay; Punktdaten tragen die Orientierung schon
- [cpq Verbau 90° am Steher](cpq-verbau-90grad-steher.md) — Rotation ist im GroupProcessor-Replay angekommen (Edge.rot/computeLayout); fachliche 90°-Klärung läuft noch
- [cpq Configurator Modul-Registry](cpq-configurator-modul-registry.md) — GEKO-76/16: Registry in configurator/modules, Hook nach replay(), Heilung=Ableitung, Overrides=Events
- [Suche nur in Projektordnern](feedback-suche-nur-projektordner.md) — find/grep nie über ~ oder den ganzen Rechner, nur im konkreten erlaubten Verzeichnis
Konzept-Entscheidung (2026-07-03) für die cpq/geko-Abgrenzung der Schutzzaun-Tickets GEKO-76 (Steher-Logik) und GEKO-16 (Zaun zwischen zwei Punkten):
- **Registry-Ort:** `packages/frontend/src/configurator/` (neues Verzeichnis `modules/`), kein neues Package. cpq bleibt domänen-agnostisch; geko liefert Module (Steher, Zaun-Zug) von außen.
- **Injection:** Konstruktor des `DefaultCommandProcessor`; `Fire3D.vue` nimmt bereits `props.processor` entgegen — die geko-App baut den Processor mit ihren Modulen und reicht ihn durch.
- **Zentraler Hook:** nach `groups.replay(...)` in `DefaultCommandProcessor.replay()` — einziger Engpass für Live, Undo/Redo und Placement; Module können Live/Replay nicht auseinanderlaufen lassen.
- **Semantik:** Selbstheilung (Bauform A–G, Höhe) ist reine Ableitung aus der Topologie (`occupiedPmis()`, `Edge.rot`) und landet nie im EventStore; manuelle Detailmenü-Overrides sind echte, undo-bare Events (Modul-eigene Event-Typen).
- **Mit aufzuräumen:** hartkodierte `.name-CS_EIN_*`-Selektoren im `DefaultCommandProcessor` → Placement-Konfiguration des Moduls; Löschen ist im Processor noch unbehandelt → Kaskaden-Hook von Anfang an als Modul-Vertrag entwerfen.
Ziel: an den Stehern im 90°-Winkel anbauen können, genau wie der jetzige Links/Rechts-Verbau.
Ziel: an den Stehern im 90°-Winkel anbauen können, genau wie der Links/Rechts-Verbau.
**Wer ist der Blocker: wir (cpq), nicht fire-visual.**
**Stand 2026-07-03 (verifiziert im Code):** Der frühere Blocker — translation-only-Replay im cpq-`GroupProcessor` — ist behoben. `packages/frontend/src/configurator/model/GroupProcessor.ts`:
-`Edge` trägt `rot?: Quat` (relative Rotation pro Dock-Kante).
-`computeLayout` komponiert Rotationen über den ungerichteten Kantengraphen (Quaternionen, vorwärts `R_to = R_from ∘ edge.rot`, rückwärts invers); mit Identity-Rotationen reduziert es sich exakt auf den alten translation-only-Solver.
-`dockRotation` leitet `Edge.rot` aus den PMI-Frame-Orientierungen ab (`pmiRotation` liest `properties.rotation` als Euler ZYX Grad → Quat).
- Placement nutzt die `CS_EIN_VORNE`/`CS_EIN_HINTEN`-Selektoren (hart im `DefaultCommandProcessor` — Kandidat für Modul-Konfiguration).
- **fire-visual kann es.** Das Live-Snapping im PlacementMode setzt die Subject-Matrix als `bestMatrix · dragPointTransform⁻¹` — die Rotation des Drag-Punkts ist drin, die Engine richtet also den vollen Frame aus und dreht das Teil beim Snap schon korrekt um 90°. (Vorbehalt: aus der Placement-Matrix-Notiz abgeleitet, nicht aus frischem Quell-Read; bei Bedarf `PlacementMode`-Quelle prüfen, ob DropPoints den Rotations-Frame durchreichen.)
- **cpq verliert es beim Replay.** Nach dem Snap übernehmen wir das Engine-Ergebnis nicht, sondern merken nur „Punkt A trifft Punkt B" (`group-merge` mit zwei PMI-IDs) und rechnen die Anordnung im `GroupProcessor` selbst neu — rein translatorisch. Beim nächsten `setScene` wird die 90°-Drehung auf achsparallel zurückgeplättet.
Die fachliche Klärung der 90°-Frage (GEKO-Seite) läuft laut Marcus noch separat.
**Warum schwer — das Dock-Modell ist translation-only, einachsig, gemeinsame Orientierung. Links/Rechts geht nur, weil nie rotiert wird.** Zu ändernde Schichten:
1.`Edge` = `{from,to,fromPmi,toPmi}` kennt keine Rotation → relative Rotation pro Kante nötig.
2.`computePositions` dockt mit `pos(to)=pos(from)+pmi(from)−pmi(to)` (nur Position) → Rotation entlang der Kanten komponieren.
3.`pmiLocal` liest nur `position.value`. **Die Orientierung steckt schon in den Daten** (`properties.rotation`), wird aber ignoriert — und Format passt nicht: 3 Euler-Grad (z. B. `CS_EIN_90_0_0` → `[0,-0,180]`), deklariert als „quaternion", während `transformMath``[w,x,y,z]` erwartet.
4. Placement-Mode hartverdrahtet auf `.name-CS_LINKS`↔`.name-CS_RECHTS`; Steher-Anschlüsse leben in der `CS_EIN_*`-Familie → neue Drag-/Drop-Selektoren + Open-End-Logik (`occupiedPmis`).
5.`buildDetachEvent`/`areConnected` nehmen einen 1-achsigen Lauf an (dominante Achse, Links→Rechts-Leseordnung) → 90°-Anbau macht den Verbau zum 2D-Baum, Annahmen brechen.
**Datenlage:** kein plain `CS_VORNE`/`CS_HINTEN` (0 Teile); `CS_LINKS`/`CS_RECHTS` nur auf 12 Teilen; Vorne/Hinten nur in `CS_EIN_*` (rotationscodiert: `CS_EIN_VORNE`, `CS_EIN_HINTEN`, `CS_EIN_STEHER_VORNE/HINTEN`, `CS_EIN_90_0_0` …).
**Machbar:** Erweiterung unseres Modells (Rotation konsumieren + entlang Kanten propagieren + Format-Fix), keine Nachgenerierung der Teile — die nötige Orientierung liefern die Punktdaten bereits.
Such-Befehle (find, grep, ls …) nur innerhalb der erlaubten Arbeitsverzeichnisse des jeweiligen Projekts ausführen — nicht über `/Users/marcus.hinz`, nicht über breite Pfad-Listen quer durch `~/projects`, nicht über den ganzen Rechner.
**Why:** Marcus erlaubt Zugriff gezielt pro Projektordner; breite Sweeps über Home verletzen diese Abgrenzung, auch wenn sie technisch funktionieren.
**How to apply:** Vor einem Such-Befehl den konkretesten bekannten Pfad wählen (z. B. das genannte Repo oder `~/claude-projects/robotunits`). Liegt das Ziel dort nicht, melden statt den Suchraum eigenmächtig auszuweiten. Gilt zusätzlich zu [[feedback-keine-intent-unterstellung]]s Grundregel „nur suchen, wenn angewiesen".
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.