Jira adesso Group extern https://adesso-group-extern.atlassian.net This file is an XML representation of an issue en-us 1001.0.0-SNAPSHOT 100292 30-06-2026 [GEKO-16] Schutzzaun im Layout schließen/Schutzzaun zwischen zwei Punkten planen https://adesso-group-extern.atlassian.net/browse/GEKO-16 GEKO <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> <h4><a name="Wer%3F%C2%A0"></a><b>Wer?</b> </h4> <p>Anwender des Schutzzaun-Konfigurators im 3D-Layout.</p> <h4><a name="Was%3F%C2%A0"></a><b>Was?</b> </h4> <ul> <li>Interaktives Setzen von Start- und Endpunkt (frei oder gesnappt an PMIs).</li> <li>Dynamische Anzeige von 3 Bemaßungen (Länge, Diagonale, Winkel) während der Mausbewegung.</li> <li>Intelligente Eigenschafts-Vererbung (Typ/Füllung) basierend auf Start-/End-PMIs zur Vermeidung unnötiger UI-Abfragen.</li> <li>Filterung von PMIs: Inkompatible End-PMIs werden nach der Auswahl des Start-PMIs ignoriert und nicht gehighlighted.</li> <li>Mathematische Berechnung von 2D-Länge (XZ-Ebene) und Ausrichtungswinkel.</li> <li>Algorithmus-gestützte Aufteilung der Strecke in Standard- und Sonderelemente.</li> <li>Generierung des Zauns als kaskadierender Verbund (Typ 2 Einbaubedingungen).</li> </ul> <h4><a name="Warum%3F%C2%A0"></a><b>Warum?</b> </h4> <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> <h4><a name="Wozu%3F%C2%A0"></a><b>Wozu?</b> </h4> <p>Ein strukturierter, in den Szenegraphen integrierter Schutzzaun-Zug, mit dem offene Züge geschlossen werden können.</p> GEKO-16 Schutzzaun im Layout schließen/Schutzzaun zwischen zwei Punkten planen Task GEKO-6 Major To Do Unresolved Marcus Hinz Lukas Hörger Wed, 8 Apr 2026 11:43:54 +0200 Thu, 2 Jul 2026 14:28:25 +0200 0 0 Development Rank 0|i5ji9k: T-Shirt Size - Storypoint �� Definition of Ready - Formale Kriterien <h3><a name="%F0%9F%93%98Funktionsbeschreibung%3A"></a><b>�� Funktionsbeschreibung:</b></h3> <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> </ol> </li> </ul> <h4><a name="Algorithmus%3AElementAufteilung%28%7B%7Bberechnungaufteilunglaenge%7D%7D%29"></a>Algorithmus: Element-Aufteilung (<tt>berechnung_aufteilung_laenge</tt>)</h4> <ul> <li><b>1. Grundkonfiguration:</b> <ul> <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> <div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent panelContent"> <pre>FUNCTION calculate_width_distribution(type, has_door, gap_input, total_width, filling_type) INITIALIZE results AS dictionary // --- 1. Base Setup &amp; Door Deduction --- IF type == "Basic" THEN gap = gap_input post_addition = 0 ELSE IF type == "Allround" THEN gap = 11 post_addition = 51 END IF IF has_door == TRUE THEN deduction = 1124 + gap results["door_count"] = 1 ELSE IF type != "0" THEN deduction = 51 results["door_count"] = 0 ELSE deduction = 0 results["door_count"] = 0 END IF remaining_width = total_width - deduction test_width = remaining_width // --- 2. Distribute Standard Elements (Greedy Approach) --- STANDARD_SIZES = [1507, 1034, 776, 518] element_counts = {1507: 0, 1034: 0, 776: 0, 518: 0} FOR EACH size IN STANDARD_SIZES DO required_width = size + post_addition WHILE test_width &gt;= required_width DO element_counts[size] = element_counts[size] + 1 remaining_width = test_width - required_width test_width = remaining_width - gap END WHILE END FOR // --- 3. Dead Zone Correction --- min_special_element = (IF filling_type == 0 THEN 174 ELSE 131) + post_addition IF test_width &gt;= 43 AND test_width &lt;= min_special_element THEN smallest_assigned = FIND_SMALLEST_ASSIGNED_ELEMENT(element_counts) next_smaller = FIND_NEXT_SMALLER_SIZE(STANDARD_SIZES, smallest_assigned) IF smallest_assigned EXISTS THEN element_counts[smallest_assigned] = element_counts[smallest_assigned] - 1 IF next_smaller EXISTS THEN // Downgrade to next smaller standard element element_counts[next_smaller] = element_counts[next_smaller] + 1 difference = smallest_assigned - next_smaller remaining_width = remaining_width + difference test_width = test_width + difference ELSE // Lowest element was 518, remove it completely freed_space = post_addition + smallest_assigned + gap remaining_width = remaining_width + freed_space test_width = test_width + freed_space END IF END IF END IF SAVE element_counts TO results test_width = test_width - post_addition // --- 4. Special Element (Sonderelement) Calculation --- special_element_width = 0 test_width_int = FLOOR(test_width) IF test_width_int &lt; 43 THEN special_element_width = 0 ELSE IF test_width_int &gt;= 518 AND test_width_int &lt; 561 THEN results["518_count"] = results["518_count"] + 1 remaining_width = remaining_width - 518 - gap - post_addition ELSE IF test_width_int &gt;= 776 AND test_width_int &lt; 819 THEN results["776_count"] = results["776_count"] + 1 remaining_width = remaining_width - 776 - gap - post_addition ELSE IF test_width_int &gt;= 131 AND test_width_int &lt; 776 THEN // Calculate based on a grid step of 43 calculated_size = ((test_width_int - 131) / 43) * 43 + 131 IF calculated_size == 131 AND min_special_element != 131 + post_addition THEN special_element_width = 0 // Min size rule prevents a 131 element ELSE special_element_width = calculated_size END IF END IF // --- 5. Final Remainder Output --- results["special_element"] = special_element_width IF special_element_width &gt; 0 THEN remaining_width = remaining_width - special_element_width - gap - post_addition END IF results["final_remainder"] = remaining_width RETURN results END FUNCTION FUNCTION determine_parameters_and_generate(start_point, end_point) // --- 1. Vererbungs-Weiche (Typ und Füllung ermitteln) --- IF start_point.is_pmi == TRUE THEN // Vererbung vom Start-PMI (gilt für PMI+Frei und PMI+PMI) fence_type = start_point.parent.type fence_filling = start_point.parent.filling ELSE IF end_point.is_pmi == TRUE THEN // Rückwärtsvererbung vom End-PMI (gilt für Frei+PMI) fence_type = end_point.parent.type fence_filling = end_point.parent.filling ELSE // Beide Punkte frei (Frei+Frei) -&gt; Modales UI-Overlay ui_selection = SHOW_MODAL_OVERLAY() IF ui_selection == CANCELLED THEN RETURN fence_type = ui_selection.type fence_filling = ui_selection.filling END IF // --- 2. Geometrie berechnen --- total_width = CALCULATE_2D_DISTANCE(start_point, end_point) angle = CALCULATE_2D_ANGLE(start_point, end_point) // --- 3. Aufteilung berechnen (Kern-Algorithmus bleibt unverändert) --- distribution = calculate_width_distribution(fence_type, FALSE, 0, total_width, fence_filling) // --- 4. Kaskadierende Platzierung triggern --- PLACE_FENCE_ELEMENTS(distribution, start_point, angle) END FUNCTION</pre> </div></div> <h4><a name="NichtZiele%28OutofScope%29"></a>Nicht-Ziele (Out of Scope)</h4> <ul> <li><b>Live-Vorschau (Ghosting):</b> Das Rendern einer 3D-Vorschau des Zauns während der Mausbewegung.</li> <li><b>Passstücke (Sonderbau):</b> Generierung freier Sonder-Bauteile außerhalb des 43-mm-Rasters.</li> <li><b>Spezifikation des Overlays:</b> Das UI des modalen Overlays wird separat spezifiziert.</li> </ul> <hr /> <h3><a name="%F0%9F%93%91Akzeptanzkriterien%3A"></a><b>�� Akzeptanzkriterien:</b></h3> <ul> <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;Overlay-Trigger&#93;</span></b>: Startpunkt frei klicken -&gt; Endpunkt frei klicken -&gt;<em>Erwartetes Ergebnis:</em> Modales Overlay zur Parameterauswahl öffnet sich.</li> </ul> <hr /> <h3><a name="%E2%9A%A0%EF%B8%8FEdgeCases%3A%C2%A0"></a><b>⚠️ Edge Cases:</b> </h3> <ul> <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> </ul> �� DoR - Checkliste