Compare commits
2 commits
ab9a798343
...
fa2feee780
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fa2feee780 | ||
|
|
3f0d0f3f33 |
45 changed files with 1106 additions and 440 deletions
|
|
@ -25,20 +25,28 @@ Ein Bauteil ist ein **ganz normales Bibliotheks-Objekt** (`bau_*`, Kategorie
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| Fundament & Boden | Fundament, Boden/Decke (2 × 2 m, 20 cm) |
|
| Fundament & Boden | Fundament, Boden/Decke (2 × 2 m, 20 cm) |
|
||||||
| Wände (2 m breit, 3 m hoch) | Wand, Wand mit Tür (1,2 × 2,2 m), Wand mit Fenster, Wand mit Schießscharte, Brüstung (1,1 m), Eckpfeiler |
|
| Wände (2 m breit, 3 m hoch) | Wand, Wand mit Tür (1,2 × 2,2 m), Wand mit Fenster, Wand mit Schießscharte, Brüstung (1,1 m), Eckpfeiler |
|
||||||
| Dach & Giebel | Giebelwand, Satteldach, Pyramidendach |
|
| Dach & Giebel (klein 2 m) | Giebelwand, Satteldach, Pyramidendach |
|
||||||
|
| Dach & Giebel (mittel 4 m) | Giebelwand 4 m, Satteldach 4×4, Pyramidendach 4×4 (Spitze 2 m) |
|
||||||
|
| Dach & Giebel (groß 6 m) | Giebelwand 6 m, Satteldach 6×6, Pyramidendach 6×6 (Spitze 3 m) |
|
||||||
| Treppe | Treppe 4 × 2 m, 3 m hoch (15 Stufen) |
|
| Treppe | Treppe 4 × 2 m, 3 m hoch (15 Stufen) |
|
||||||
|
|
||||||
Dazu im Tab „Pack-Module“: Container, Hangar-Tore, Betonsperren aus dem
|
Fertige Pack-Objekte (Container, Hangar-Tore, Betonsperren) stehen NICHT im Bauen-Tab,
|
||||||
Military-Desert-Pack (normale Objekte).
|
sondern wie alle Objekte in der Objekt-Bibliothek unten.
|
||||||
|
|
||||||
## Snap (`src/editor/build_snap.gd`)
|
## Snap (`src/editor/build_snap.gd`)
|
||||||
|
|
||||||
* **XZ:** Wände auf die nächste Zellkante (Drehung ergibt sich aus der Kante, `R`
|
* **XZ:** Wände auf die nächste Zellkante — und zwar GANZ in die Zelle, in der die
|
||||||
dreht die Wand um), Böden/Dächer/Fundamente in die Zellmitte, Pfeiler auf die
|
Maus ist (Außenseite bündig mit der Kante, kein Überhang, Ecken schließen ohne
|
||||||
Zellecke, Treppen auf eine 4 × 2-m-Fläche. Zellen sind 2 × 2 m, global ab (0, 0).
|
Lücke). Klick auf eine vorhandene Wand (Seite oder Oberkante) übernimmt deren Lage
|
||||||
|
exakt (ersetzen bzw. stapeln). Böden/Dächer/Fundamente in die Zellmitte, große
|
||||||
|
Dächer (4 m / 6 m) je nach Zellzahl auf Zellecke bzw. Zellmitte, Pfeiler auf die
|
||||||
|
Zellecke, Treppen auf eine 4 × 2-m-Fläche. `R` dreht die Wand um. Zellen sind
|
||||||
|
2 × 2 m, global ab (0, 0).
|
||||||
* **Y:** nie die Klickhöhe, sondern eine **Ebene** — Gelände (höchster Punkt unter
|
* **Y:** nie die Klickhöhe, sondern eine **Ebene** — Gelände (höchster Punkt unter
|
||||||
der Grundfläche) oder Oberkante eines tragenden Teils darunter. Klick auf die
|
der Grundfläche) oder Oberkante eines tragenden Teils darunter. **Wände und
|
||||||
Wandoberkante stapelt das nächste Geschoss.
|
Pfeiler zählen von der Unterkante einer Decke/eines Fundaments**, die Decke selbst
|
||||||
|
liegt auf der Wandoberkante: jedes Geschoss liegt auf Vielfachen von 3 m, mit oder
|
||||||
|
ohne Decke darunter stehen die oberen Wände gleich hoch.
|
||||||
* **Ersetzen:** gleiche Art auf demselben Platz wird ersetzt (Tür in vorhandene
|
* **Ersetzen:** gleiche Art auf demselben Platz wird ersetzt (Tür in vorhandene
|
||||||
Wand). Dasselbe Teil nochmal wird mit Erklärung abgelehnt.
|
Wand). Dasselbe Teil nochmal wird mit Erklärung abgelehnt.
|
||||||
|
|
||||||
|
|
|
||||||
1
library/kollisions/bau_dach_giebel_4.json
Normal file
1
library/kollisions/bau_dach_giebel_4.json
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
{"aabb_max":[2.071,2.141,2.0],"aabb_min":[-2.071,0.0,-2.0],"dreiecke":48,"oid":"bau_dach_giebel_4","quelle_glb":"res://assets/objekte/military_desert/bauteile/bau_dach_giebel_4.glb","vertices":[[-2.0707,0.1414,-2.0],[-0.0707,2.1414,2.0],[-2.0707,0.1414,2.0],[-0.0707,2.1414,2.0],[-2.0707,0.1414,-2.0],[-2.0707,0.1414,2.0],[0.0707,2.0,2.0],[-1.9293,0.0,-2.0],[-1.9293,0.0,2.0],[-1.9293,0.0,-2.0],[0.0707,2.0,2.0],[-1.9293,0.0,2.0],[-2.0707,0.1414,-2.0],[-0.0707,2.1414,-2.0],[-0.0707,2.1414,2.0],[-0.0707,2.1414,-2.0],[-2.0707,0.1414,-2.0],[-0.0707,2.1414,2.0],[0.0707,2.0,-2.0],[-1.9293,0.0,-2.0],[0.0707,2.0,2.0],[-1.9293,0.0,-2.0],[0.0707,2.0,-2.0],[0.0707,2.0,2.0],[-2.0707,0.1414,-2.0],[-1.9293,0.0,2.0],[-1.9293,0.0,-2.0],[-1.9293,0.0,2.0],[-2.0707,0.1414,-2.0],[-1.9293,0.0,-2.0],[-2.0707,0.1414,-2.0],[-2.0707,0.1414,2.0],[-1.9293,0.0,2.0],[-2.0707,0.1414,2.0],[-2.0707,0.1414,-2.0],[-1.9293,0.0,2.0],[-2.0707,0.1414,2.0],[0.0707,2.0,2.0],[-1.9293,0.0,2.0],[0.0707,2.0,2.0],[-2.0707,0.1414,2.0],[-1.9293,0.0,2.0],[-2.0707,0.1414,2.0],[-0.0707,2.1414,2.0],[0.0707,2.0,2.0],[-0.0707,2.1414,2.0],[-2.0707,0.1414,2.0],[0.0707,2.0,2.0],[-0.0707,2.1414,2.0],[0.0707,2.0,-2.0],[0.0707,2.0,2.0],[0.0707,2.0,-2.0],[-0.0707,2.1414,2.0],[0.0707,2.0,2.0],[-0.0707,2.1414,2.0],[-0.0707,2.1414,-2.0],[0.0707,2.0,-2.0],[-0.0707,2.1414,-2.0],[-0.0707,2.1414,2.0],[0.0707,2.0,-2.0],[-0.0707,2.1414,-2.0],[-1.9293,0.0,-2.0],[0.0707,2.0,-2.0],[-1.9293,0.0,-2.0],[-0.0707,2.1414,-2.0],[0.0707,2.0,-2.0],[-0.0707,2.1414,-2.0],[-2.0707,0.1414,-2.0],[-1.9293,0.0,-2.0],[-2.0707,0.1414,-2.0],[-0.0707,2.1414,-2.0],[-1.9293,0.0,-2.0],[0.0707,2.1414,-2.0],[2.0707,0.1414,2.0],[0.0707,2.1414,2.0],[2.0707,0.1414,2.0],[0.0707,2.1414,-2.0],[0.0707,2.1414,2.0],[1.9293,0.0,2.0],[-0.0707,2.0,-2.0],[-0.0707,2.0,2.0],[-0.0707,2.0,-2.0],[1.9293,0.0,2.0],[-0.0707,2.0,2.0],[0.0707,2.1414,-2.0],[2.0707,0.1414,-2.0],[2.0707,0.1414,2.0],[2.0707,0.1414,-2.0],[0.0707,2.1414,-2.0],[2.0707,0.1414,2.0],[1.9293,0.0,-2.0],[-0.0707,2.0,-2.0],[1.9293,0.0,2.0],[-0.0707,2.0,-2.0],[1.9293,0.0,-2.0],[1.9293,0.0,2.0],[0.0707,2.1414,-2.0],[-0.0707,2.0,2.0],[-0.0707,2.0,-2.0],[-0.0707,2.0,2.0],[0.0707,2.1414,-2.0],[-0.0707,2.0,-2.0],[0.0707,2.1414,-2.0],[0.0707,2.1414,2.0],[-0.0707,2.0,2.0],[0.0707,2.1414,2.0],[0.0707,2.1414,-2.0],[-0.0707,2.0,2.0],[0.0707,2.1414,2.0],[1.9293,0.0,2.0],[-0.0707,2.0,2.0],[1.9293,0.0,2.0],[0.0707,2.1414,2.0],[-0.0707,2.0,2.0],[0.0707,2.1414,2.0],[2.0707,0.1414,2.0],[1.9293,0.0,2.0],[2.0707,0.1414,2.0],[0.0707,2.1414,2.0],[1.9293,0.0,2.0],[2.0707,0.1414,2.0],[1.9293,0.0,-2.0],[1.9293,0.0,2.0],[1.9293,0.0,-2.0],[2.0707,0.1414,2.0],[1.9293,0.0,2.0],[2.0707,0.1414,2.0],[2.0707,0.1414,-2.0],[1.9293,0.0,-2.0],[2.0707,0.1414,-2.0],[2.0707,0.1414,2.0],[1.9293,0.0,-2.0],[2.0707,0.1414,-2.0],[-0.0707,2.0,-2.0],[1.9293,0.0,-2.0],[-0.0707,2.0,-2.0],[2.0707,0.1414,-2.0],[1.9293,0.0,-2.0],[2.0707,0.1414,-2.0],[0.0707,2.1414,-2.0],[-0.0707,2.0,-2.0],[0.0707,2.1414,-2.0],[2.0707,0.1414,-2.0],[-0.0707,2.0,-2.0]]}
|
||||||
1
library/kollisions/bau_dach_giebel_6.json
Normal file
1
library/kollisions/bau_dach_giebel_6.json
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
{"aabb_max":[3.071,3.141,3.0],"aabb_min":[-3.071,0.0,-3.0],"dreiecke":48,"oid":"bau_dach_giebel_6","quelle_glb":"res://assets/objekte/military_desert/bauteile/bau_dach_giebel_6.glb","vertices":[[-3.0707,0.1414,-3.0],[-0.0707,3.1414,3.0],[-3.0707,0.1414,3.0],[-0.0707,3.1414,3.0],[-3.0707,0.1414,-3.0],[-3.0707,0.1414,3.0],[0.0707,3.0,3.0],[-2.9293,0.0,-3.0],[-2.9293,0.0,3.0],[-2.9293,0.0,-3.0],[0.0707,3.0,3.0],[-2.9293,0.0,3.0],[-3.0707,0.1414,-3.0],[-0.0707,3.1414,-3.0],[-0.0707,3.1414,3.0],[-0.0707,3.1414,-3.0],[-3.0707,0.1414,-3.0],[-0.0707,3.1414,3.0],[0.0707,3.0,-3.0],[-2.9293,0.0,-3.0],[0.0707,3.0,3.0],[-2.9293,0.0,-3.0],[0.0707,3.0,-3.0],[0.0707,3.0,3.0],[-3.0707,0.1414,-3.0],[-2.9293,0.0,3.0],[-2.9293,0.0,-3.0],[-2.9293,0.0,3.0],[-3.0707,0.1414,-3.0],[-2.9293,0.0,-3.0],[-3.0707,0.1414,-3.0],[-3.0707,0.1414,3.0],[-2.9293,0.0,3.0],[-3.0707,0.1414,3.0],[-3.0707,0.1414,-3.0],[-2.9293,0.0,3.0],[-3.0707,0.1414,3.0],[0.0707,3.0,3.0],[-2.9293,0.0,3.0],[0.0707,3.0,3.0],[-3.0707,0.1414,3.0],[-2.9293,0.0,3.0],[-3.0707,0.1414,3.0],[-0.0707,3.1414,3.0],[0.0707,3.0,3.0],[-0.0707,3.1414,3.0],[-3.0707,0.1414,3.0],[0.0707,3.0,3.0],[-0.0707,3.1414,3.0],[0.0707,3.0,-3.0],[0.0707,3.0,3.0],[0.0707,3.0,-3.0],[-0.0707,3.1414,3.0],[0.0707,3.0,3.0],[-0.0707,3.1414,3.0],[-0.0707,3.1414,-3.0],[0.0707,3.0,-3.0],[-0.0707,3.1414,-3.0],[-0.0707,3.1414,3.0],[0.0707,3.0,-3.0],[-0.0707,3.1414,-3.0],[-2.9293,0.0,-3.0],[0.0707,3.0,-3.0],[-2.9293,0.0,-3.0],[-0.0707,3.1414,-3.0],[0.0707,3.0,-3.0],[-0.0707,3.1414,-3.0],[-3.0707,0.1414,-3.0],[-2.9293,0.0,-3.0],[-3.0707,0.1414,-3.0],[-0.0707,3.1414,-3.0],[-2.9293,0.0,-3.0],[0.0707,3.1414,-3.0],[3.0707,0.1414,3.0],[0.0707,3.1414,3.0],[3.0707,0.1414,3.0],[0.0707,3.1414,-3.0],[0.0707,3.1414,3.0],[2.9293,0.0,3.0],[-0.0707,3.0,-3.0],[-0.0707,3.0,3.0],[-0.0707,3.0,-3.0],[2.9293,0.0,3.0],[-0.0707,3.0,3.0],[0.0707,3.1414,-3.0],[3.0707,0.1414,-3.0],[3.0707,0.1414,3.0],[3.0707,0.1414,-3.0],[0.0707,3.1414,-3.0],[3.0707,0.1414,3.0],[2.9293,0.0,-3.0],[-0.0707,3.0,-3.0],[2.9293,0.0,3.0],[-0.0707,3.0,-3.0],[2.9293,0.0,-3.0],[2.9293,0.0,3.0],[0.0707,3.1414,-3.0],[-0.0707,3.0,3.0],[-0.0707,3.0,-3.0],[-0.0707,3.0,3.0],[0.0707,3.1414,-3.0],[-0.0707,3.0,-3.0],[0.0707,3.1414,-3.0],[0.0707,3.1414,3.0],[-0.0707,3.0,3.0],[0.0707,3.1414,3.0],[0.0707,3.1414,-3.0],[-0.0707,3.0,3.0],[0.0707,3.1414,3.0],[2.9293,0.0,3.0],[-0.0707,3.0,3.0],[2.9293,0.0,3.0],[0.0707,3.1414,3.0],[-0.0707,3.0,3.0],[0.0707,3.1414,3.0],[3.0707,0.1414,3.0],[2.9293,0.0,3.0],[3.0707,0.1414,3.0],[0.0707,3.1414,3.0],[2.9293,0.0,3.0],[3.0707,0.1414,3.0],[2.9293,0.0,-3.0],[2.9293,0.0,3.0],[2.9293,0.0,-3.0],[3.0707,0.1414,3.0],[2.9293,0.0,3.0],[3.0707,0.1414,3.0],[3.0707,0.1414,-3.0],[2.9293,0.0,-3.0],[3.0707,0.1414,-3.0],[3.0707,0.1414,3.0],[2.9293,0.0,-3.0],[3.0707,0.1414,-3.0],[-0.0707,3.0,-3.0],[2.9293,0.0,-3.0],[-0.0707,3.0,-3.0],[3.0707,0.1414,-3.0],[2.9293,0.0,-3.0],[3.0707,0.1414,-3.0],[0.0707,3.1414,-3.0],[-0.0707,3.0,-3.0],[0.0707,3.1414,-3.0],[3.0707,0.1414,-3.0],[-0.0707,3.0,-3.0]]}
|
||||||
1
library/kollisions/bau_dach_pyramide_4.json
Normal file
1
library/kollisions/bau_dach_pyramide_4.json
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
{"aabb_max":[2.071,2.141,2.071],"aabb_min":[-2.071,0.0,-2.071],"dreiecke":64,"oid":"bau_dach_pyramide_4","quelle_glb":"res://assets/objekte/military_desert/bauteile/bau_dach_pyramide_4.glb","vertices":[[-2.0,0.0,-1.9293],[0.0,2.0,0.0707],[2.0,0.0,-1.9293],[0.0,2.0,0.0707],[-2.0,0.0,-1.9293],[2.0,0.0,-1.9293],[0.0,2.1414,-0.0707],[-2.0,0.1414,-2.0707],[2.0,0.1414,-2.0707],[-2.0,0.1414,-2.0707],[0.0,2.1414,-0.0707],[2.0,0.1414,-2.0707],[-2.0,0.0,-1.9293],[2.0,0.1414,-2.0707],[-2.0,0.1414,-2.0707],[2.0,0.1414,-2.0707],[-2.0,0.0,-1.9293],[-2.0,0.1414,-2.0707],[-2.0,0.0,-1.9293],[2.0,0.0,-1.9293],[2.0,0.1414,-2.0707],[2.0,0.0,-1.9293],[-2.0,0.0,-1.9293],[2.0,0.1414,-2.0707],[2.0,0.0,-1.9293],[0.0,2.1414,-0.0707],[2.0,0.1414,-2.0707],[0.0,2.1414,-0.0707],[2.0,0.0,-1.9293],[2.0,0.1414,-2.0707],[2.0,0.0,-1.9293],[0.0,2.0,0.0707],[0.0,2.1414,-0.0707],[0.0,2.0,0.0707],[2.0,0.0,-1.9293],[0.0,2.1414,-0.0707],[0.0,2.0,0.0707],[-2.0,0.1414,-2.0707],[0.0,2.1414,-0.0707],[-2.0,0.1414,-2.0707],[0.0,2.0,0.0707],[0.0,2.1414,-0.0707],[0.0,2.0,0.0707],[-2.0,0.0,-1.9293],[-2.0,0.1414,-2.0707],[-2.0,0.0,-1.9293],[0.0,2.0,0.0707],[-2.0,0.1414,-2.0707],[1.9293,0.0,-2.0],[-0.0707,2.0,0.0],[1.9293,0.0,2.0],[-0.0707,2.0,0.0],[1.9293,0.0,-2.0],[1.9293,0.0,2.0],[0.0707,2.1414,0.0],[2.0707,0.1414,-2.0],[2.0707,0.1414,2.0],[2.0707,0.1414,-2.0],[0.0707,2.1414,0.0],[2.0707,0.1414,2.0],[1.9293,0.0,-2.0],[2.0707,0.1414,2.0],[2.0707,0.1414,-2.0],[2.0707,0.1414,2.0],[1.9293,0.0,-2.0],[2.0707,0.1414,-2.0],[1.9293,0.0,-2.0],[1.9293,0.0,2.0],[2.0707,0.1414,2.0],[1.9293,0.0,2.0],[1.9293,0.0,-2.0],[2.0707,0.1414,2.0],[1.9293,0.0,2.0],[0.0707,2.1414,0.0],[2.0707,0.1414,2.0],[0.0707,2.1414,0.0],[1.9293,0.0,2.0],[2.0707,0.1414,2.0],[1.9293,0.0,2.0],[-0.0707,2.0,0.0],[0.0707,2.1414,0.0],[-0.0707,2.0,0.0],[1.9293,0.0,2.0],[0.0707,2.1414,0.0],[-0.0707,2.0,0.0],[2.0707,0.1414,-2.0],[0.0707,2.1414,0.0],[2.0707,0.1414,-2.0],[-0.0707,2.0,0.0],[0.0707,2.1414,0.0],[-0.0707,2.0,0.0],[1.9293,0.0,-2.0],[2.0707,0.1414,-2.0],[1.9293,0.0,-2.0],[-0.0707,2.0,0.0],[2.0707,0.1414,-2.0],[2.0,0.0,1.9293],[0.0,2.0,-0.0707],[-2.0,0.0,1.9293],[0.0,2.0,-0.0707],[2.0,0.0,1.9293],[-2.0,0.0,1.9293],[0.0,2.1414,0.0707],[2.0,0.1414,2.0707],[-2.0,0.1414,2.0707],[2.0,0.1414,2.0707],[0.0,2.1414,0.0707],[-2.0,0.1414,2.0707],[2.0,0.0,1.9293],[-2.0,0.1414,2.0707],[2.0,0.1414,2.0707],[-2.0,0.1414,2.0707],[2.0,0.0,1.9293],[2.0,0.1414,2.0707],[2.0,0.0,1.9293],[-2.0,0.0,1.9293],[-2.0,0.1414,2.0707],[-2.0,0.0,1.9293],[2.0,0.0,1.9293],[-2.0,0.1414,2.0707],[-2.0,0.0,1.9293],[0.0,2.1414,0.0707],[-2.0,0.1414,2.0707],[0.0,2.1414,0.0707],[-2.0,0.0,1.9293],[-2.0,0.1414,2.0707],[-2.0,0.0,1.9293],[0.0,2.0,-0.0707],[0.0,2.1414,0.0707],[0.0,2.0,-0.0707],[-2.0,0.0,1.9293],[0.0,2.1414,0.0707],[0.0,2.0,-0.0707],[2.0,0.1414,2.0707],[0.0,2.1414,0.0707],[2.0,0.1414,2.0707],[0.0,2.0,-0.0707],[0.0,2.1414,0.0707],[0.0,2.0,-0.0707],[2.0,0.0,1.9293],[2.0,0.1414,2.0707],[2.0,0.0,1.9293],[0.0,2.0,-0.0707],[2.0,0.1414,2.0707],[-1.9293,0.0,2.0],[0.0707,2.0,0.0],[-1.9293,0.0,-2.0],[0.0707,2.0,0.0],[-1.9293,0.0,2.0],[-1.9293,0.0,-2.0],[-0.0707,2.1414,0.0],[-2.0707,0.1414,2.0],[-2.0707,0.1414,-2.0],[-2.0707,0.1414,2.0],[-0.0707,2.1414,0.0],[-2.0707,0.1414,-2.0],[-1.9293,0.0,2.0],[-2.0707,0.1414,-2.0],[-2.0707,0.1414,2.0],[-2.0707,0.1414,-2.0],[-1.9293,0.0,2.0],[-2.0707,0.1414,2.0],[-1.9293,0.0,2.0],[-1.9293,0.0,-2.0],[-2.0707,0.1414,-2.0],[-1.9293,0.0,-2.0],[-1.9293,0.0,2.0],[-2.0707,0.1414,-2.0],[-1.9293,0.0,-2.0],[-0.0707,2.1414,0.0],[-2.0707,0.1414,-2.0],[-0.0707,2.1414,0.0],[-1.9293,0.0,-2.0],[-2.0707,0.1414,-2.0],[-1.9293,0.0,-2.0],[0.0707,2.0,0.0],[-0.0707,2.1414,0.0],[0.0707,2.0,0.0],[-1.9293,0.0,-2.0],[-0.0707,2.1414,0.0],[0.0707,2.0,0.0],[-2.0707,0.1414,2.0],[-0.0707,2.1414,0.0],[-2.0707,0.1414,2.0],[0.0707,2.0,0.0],[-0.0707,2.1414,0.0],[0.0707,2.0,0.0],[-1.9293,0.0,2.0],[-2.0707,0.1414,2.0],[-1.9293,0.0,2.0],[0.0707,2.0,0.0],[-2.0707,0.1414,2.0]]}
|
||||||
1
library/kollisions/bau_dach_pyramide_6.json
Normal file
1
library/kollisions/bau_dach_pyramide_6.json
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
{"aabb_max":[3.071,3.141,3.071],"aabb_min":[-3.071,0.0,-3.071],"dreiecke":64,"oid":"bau_dach_pyramide_6","quelle_glb":"res://assets/objekte/military_desert/bauteile/bau_dach_pyramide_6.glb","vertices":[[-3.0,0.0,-2.9293],[0.0,3.0,0.0707],[3.0,0.0,-2.9293],[0.0,3.0,0.0707],[-3.0,0.0,-2.9293],[3.0,0.0,-2.9293],[0.0,3.1414,-0.0707],[-3.0,0.1414,-3.0707],[3.0,0.1414,-3.0707],[-3.0,0.1414,-3.0707],[0.0,3.1414,-0.0707],[3.0,0.1414,-3.0707],[-3.0,0.0,-2.9293],[3.0,0.1414,-3.0707],[-3.0,0.1414,-3.0707],[3.0,0.1414,-3.0707],[-3.0,0.0,-2.9293],[-3.0,0.1414,-3.0707],[-3.0,0.0,-2.9293],[3.0,0.0,-2.9293],[3.0,0.1414,-3.0707],[3.0,0.0,-2.9293],[-3.0,0.0,-2.9293],[3.0,0.1414,-3.0707],[3.0,0.0,-2.9293],[0.0,3.1414,-0.0707],[3.0,0.1414,-3.0707],[0.0,3.1414,-0.0707],[3.0,0.0,-2.9293],[3.0,0.1414,-3.0707],[3.0,0.0,-2.9293],[0.0,3.0,0.0707],[0.0,3.1414,-0.0707],[0.0,3.0,0.0707],[3.0,0.0,-2.9293],[0.0,3.1414,-0.0707],[0.0,3.0,0.0707],[-3.0,0.1414,-3.0707],[0.0,3.1414,-0.0707],[-3.0,0.1414,-3.0707],[0.0,3.0,0.0707],[0.0,3.1414,-0.0707],[0.0,3.0,0.0707],[-3.0,0.0,-2.9293],[-3.0,0.1414,-3.0707],[-3.0,0.0,-2.9293],[0.0,3.0,0.0707],[-3.0,0.1414,-3.0707],[2.9293,0.0,-3.0],[-0.0707,3.0,0.0],[2.9293,0.0,3.0],[-0.0707,3.0,0.0],[2.9293,0.0,-3.0],[2.9293,0.0,3.0],[0.0707,3.1414,0.0],[3.0707,0.1414,-3.0],[3.0707,0.1414,3.0],[3.0707,0.1414,-3.0],[0.0707,3.1414,0.0],[3.0707,0.1414,3.0],[2.9293,0.0,-3.0],[3.0707,0.1414,3.0],[3.0707,0.1414,-3.0],[3.0707,0.1414,3.0],[2.9293,0.0,-3.0],[3.0707,0.1414,-3.0],[2.9293,0.0,-3.0],[2.9293,0.0,3.0],[3.0707,0.1414,3.0],[2.9293,0.0,3.0],[2.9293,0.0,-3.0],[3.0707,0.1414,3.0],[2.9293,0.0,3.0],[0.0707,3.1414,0.0],[3.0707,0.1414,3.0],[0.0707,3.1414,0.0],[2.9293,0.0,3.0],[3.0707,0.1414,3.0],[2.9293,0.0,3.0],[-0.0707,3.0,0.0],[0.0707,3.1414,0.0],[-0.0707,3.0,0.0],[2.9293,0.0,3.0],[0.0707,3.1414,0.0],[-0.0707,3.0,0.0],[3.0707,0.1414,-3.0],[0.0707,3.1414,0.0],[3.0707,0.1414,-3.0],[-0.0707,3.0,0.0],[0.0707,3.1414,0.0],[-0.0707,3.0,0.0],[2.9293,0.0,-3.0],[3.0707,0.1414,-3.0],[2.9293,0.0,-3.0],[-0.0707,3.0,0.0],[3.0707,0.1414,-3.0],[3.0,0.0,2.9293],[0.0,3.0,-0.0707],[-3.0,0.0,2.9293],[0.0,3.0,-0.0707],[3.0,0.0,2.9293],[-3.0,0.0,2.9293],[0.0,3.1414,0.0707],[3.0,0.1414,3.0707],[-3.0,0.1414,3.0707],[3.0,0.1414,3.0707],[0.0,3.1414,0.0707],[-3.0,0.1414,3.0707],[3.0,0.0,2.9293],[-3.0,0.1414,3.0707],[3.0,0.1414,3.0707],[-3.0,0.1414,3.0707],[3.0,0.0,2.9293],[3.0,0.1414,3.0707],[3.0,0.0,2.9293],[-3.0,0.0,2.9293],[-3.0,0.1414,3.0707],[-3.0,0.0,2.9293],[3.0,0.0,2.9293],[-3.0,0.1414,3.0707],[-3.0,0.0,2.9293],[0.0,3.1414,0.0707],[-3.0,0.1414,3.0707],[0.0,3.1414,0.0707],[-3.0,0.0,2.9293],[-3.0,0.1414,3.0707],[-3.0,0.0,2.9293],[0.0,3.0,-0.0707],[0.0,3.1414,0.0707],[0.0,3.0,-0.0707],[-3.0,0.0,2.9293],[0.0,3.1414,0.0707],[0.0,3.0,-0.0707],[3.0,0.1414,3.0707],[0.0,3.1414,0.0707],[3.0,0.1414,3.0707],[0.0,3.0,-0.0707],[0.0,3.1414,0.0707],[0.0,3.0,-0.0707],[3.0,0.0,2.9293],[3.0,0.1414,3.0707],[3.0,0.0,2.9293],[0.0,3.0,-0.0707],[3.0,0.1414,3.0707],[-2.9293,0.0,3.0],[0.0707,3.0,0.0],[-2.9293,0.0,-3.0],[0.0707,3.0,0.0],[-2.9293,0.0,3.0],[-2.9293,0.0,-3.0],[-0.0707,3.1414,0.0],[-3.0707,0.1414,3.0],[-3.0707,0.1414,-3.0],[-3.0707,0.1414,3.0],[-0.0707,3.1414,0.0],[-3.0707,0.1414,-3.0],[-2.9293,0.0,3.0],[-3.0707,0.1414,-3.0],[-3.0707,0.1414,3.0],[-3.0707,0.1414,-3.0],[-2.9293,0.0,3.0],[-3.0707,0.1414,3.0],[-2.9293,0.0,3.0],[-2.9293,0.0,-3.0],[-3.0707,0.1414,-3.0],[-2.9293,0.0,-3.0],[-2.9293,0.0,3.0],[-3.0707,0.1414,-3.0],[-2.9293,0.0,-3.0],[-0.0707,3.1414,0.0],[-3.0707,0.1414,-3.0],[-0.0707,3.1414,0.0],[-2.9293,0.0,-3.0],[-3.0707,0.1414,-3.0],[-2.9293,0.0,-3.0],[0.0707,3.0,0.0],[-0.0707,3.1414,0.0],[0.0707,3.0,0.0],[-2.9293,0.0,-3.0],[-0.0707,3.1414,0.0],[0.0707,3.0,0.0],[-3.0707,0.1414,3.0],[-0.0707,3.1414,0.0],[-3.0707,0.1414,3.0],[0.0707,3.0,0.0],[-0.0707,3.1414,0.0],[0.0707,3.0,0.0],[-2.9293,0.0,3.0],[-3.0707,0.1414,3.0],[-2.9293,0.0,3.0],[0.0707,3.0,0.0],[-3.0707,0.1414,3.0]]}
|
||||||
1
library/kollisions/bau_giebelwand_4.json
Normal file
1
library/kollisions/bau_giebelwand_4.json
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
{"aabb_max":[2.0,2.0,0.1],"aabb_min":[-2.0,0.0,-0.1],"dreiecke":16,"oid":"bau_giebelwand_4","quelle_glb":"res://assets/objekte/military_desert/bauteile/bau_giebelwand_4.glb","vertices":[[-2.0,0.0,0.1],[0.0,2.0,0.1],[2.0,0.0,0.1],[0.0,2.0,0.1],[-2.0,0.0,0.1],[2.0,0.0,0.1],[2.0,0.0,-0.1],[0.0,2.0,-0.1],[-2.0,0.0,-0.1],[0.0,2.0,-0.1],[2.0,0.0,-0.1],[-2.0,0.0,-0.1],[-2.0,0.0,0.1],[2.0,0.0,-0.1],[-2.0,0.0,-0.1],[2.0,0.0,-0.1],[-2.0,0.0,0.1],[-2.0,0.0,-0.1],[-2.0,0.0,0.1],[2.0,0.0,0.1],[2.0,0.0,-0.1],[2.0,0.0,0.1],[-2.0,0.0,0.1],[2.0,0.0,-0.1],[2.0,0.0,0.1],[0.0,2.0,-0.1],[2.0,0.0,-0.1],[0.0,2.0,-0.1],[2.0,0.0,0.1],[2.0,0.0,-0.1],[2.0,0.0,0.1],[0.0,2.0,0.1],[0.0,2.0,-0.1],[0.0,2.0,0.1],[2.0,0.0,0.1],[0.0,2.0,-0.1],[0.0,2.0,0.1],[-2.0,0.0,-0.1],[0.0,2.0,-0.1],[-2.0,0.0,-0.1],[0.0,2.0,0.1],[0.0,2.0,-0.1],[0.0,2.0,0.1],[-2.0,0.0,0.1],[-2.0,0.0,-0.1],[-2.0,0.0,0.1],[0.0,2.0,0.1],[-2.0,0.0,-0.1]]}
|
||||||
1
library/kollisions/bau_giebelwand_6.json
Normal file
1
library/kollisions/bau_giebelwand_6.json
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
{"aabb_max":[3.0,3.0,0.1],"aabb_min":[-3.0,0.0,-0.1],"dreiecke":16,"oid":"bau_giebelwand_6","quelle_glb":"res://assets/objekte/military_desert/bauteile/bau_giebelwand_6.glb","vertices":[[-3.0,0.0,0.1],[0.0,3.0,0.1],[3.0,0.0,0.1],[0.0,3.0,0.1],[-3.0,0.0,0.1],[3.0,0.0,0.1],[3.0,0.0,-0.1],[0.0,3.0,-0.1],[-3.0,0.0,-0.1],[0.0,3.0,-0.1],[3.0,0.0,-0.1],[-3.0,0.0,-0.1],[-3.0,0.0,0.1],[3.0,0.0,-0.1],[-3.0,0.0,-0.1],[3.0,0.0,-0.1],[-3.0,0.0,0.1],[-3.0,0.0,-0.1],[-3.0,0.0,0.1],[3.0,0.0,0.1],[3.0,0.0,-0.1],[3.0,0.0,0.1],[-3.0,0.0,0.1],[3.0,0.0,-0.1],[3.0,0.0,0.1],[0.0,3.0,-0.1],[3.0,0.0,-0.1],[0.0,3.0,-0.1],[3.0,0.0,0.1],[3.0,0.0,-0.1],[3.0,0.0,0.1],[0.0,3.0,0.1],[0.0,3.0,-0.1],[0.0,3.0,0.1],[3.0,0.0,0.1],[0.0,3.0,-0.1],[0.0,3.0,0.1],[-3.0,0.0,-0.1],[0.0,3.0,-0.1],[-3.0,0.0,-0.1],[0.0,3.0,0.1],[0.0,3.0,-0.1],[0.0,3.0,0.1],[-3.0,0.0,0.1],[-3.0,0.0,-0.1],[-3.0,0.0,0.1],[0.0,3.0,0.1],[-3.0,0.0,-0.1]]}
|
||||||
|
|
@ -3506,6 +3506,36 @@
|
||||||
"kategorie": "bauteile",
|
"kategorie": "bauteile",
|
||||||
"name_de": "Satteldach"
|
"name_de": "Satteldach"
|
||||||
},
|
},
|
||||||
|
"bau_dach_giebel_4": {
|
||||||
|
"poly": 48,
|
||||||
|
"collision": {
|
||||||
|
"quelle": "kollisions/bau_dach_giebel_4.json",
|
||||||
|
"shape": "trimesh",
|
||||||
|
"size": [
|
||||||
|
4.141,
|
||||||
|
2.141,
|
||||||
|
4.0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"glb": "res://assets/objekte/military_desert/bauteile/bau_dach_giebel_4.glb",
|
||||||
|
"kategorie": "bauteile",
|
||||||
|
"name_de": "Satteldach 4×4 m"
|
||||||
|
},
|
||||||
|
"bau_dach_giebel_6": {
|
||||||
|
"poly": 48,
|
||||||
|
"collision": {
|
||||||
|
"quelle": "kollisions/bau_dach_giebel_6.json",
|
||||||
|
"shape": "trimesh",
|
||||||
|
"size": [
|
||||||
|
6.141,
|
||||||
|
3.141,
|
||||||
|
6.0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"glb": "res://assets/objekte/military_desert/bauteile/bau_dach_giebel_6.glb",
|
||||||
|
"kategorie": "bauteile",
|
||||||
|
"name_de": "Satteldach 6×6 m"
|
||||||
|
},
|
||||||
"bau_dach_pyramide": {
|
"bau_dach_pyramide": {
|
||||||
"poly": 64,
|
"poly": 64,
|
||||||
"collision": {
|
"collision": {
|
||||||
|
|
@ -3521,6 +3551,36 @@
|
||||||
"kategorie": "bauteile",
|
"kategorie": "bauteile",
|
||||||
"name_de": "Pyramidendach"
|
"name_de": "Pyramidendach"
|
||||||
},
|
},
|
||||||
|
"bau_dach_pyramide_4": {
|
||||||
|
"poly": 64,
|
||||||
|
"collision": {
|
||||||
|
"quelle": "kollisions/bau_dach_pyramide_4.json",
|
||||||
|
"shape": "trimesh",
|
||||||
|
"size": [
|
||||||
|
4.141,
|
||||||
|
2.141,
|
||||||
|
4.141
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"glb": "res://assets/objekte/military_desert/bauteile/bau_dach_pyramide_4.glb",
|
||||||
|
"kategorie": "bauteile",
|
||||||
|
"name_de": "Pyramidendach 4×4 m"
|
||||||
|
},
|
||||||
|
"bau_dach_pyramide_6": {
|
||||||
|
"poly": 64,
|
||||||
|
"collision": {
|
||||||
|
"quelle": "kollisions/bau_dach_pyramide_6.json",
|
||||||
|
"shape": "trimesh",
|
||||||
|
"size": [
|
||||||
|
6.141,
|
||||||
|
3.141,
|
||||||
|
6.141
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"glb": "res://assets/objekte/military_desert/bauteile/bau_dach_pyramide_6.glb",
|
||||||
|
"kategorie": "bauteile",
|
||||||
|
"name_de": "Pyramidendach 6×6 m"
|
||||||
|
},
|
||||||
"bau_fundament": {
|
"bau_fundament": {
|
||||||
"poly": 24,
|
"poly": 24,
|
||||||
"collision": {
|
"collision": {
|
||||||
|
|
@ -3551,6 +3611,36 @@
|
||||||
"kategorie": "bauteile",
|
"kategorie": "bauteile",
|
||||||
"name_de": "Giebelwand"
|
"name_de": "Giebelwand"
|
||||||
},
|
},
|
||||||
|
"bau_giebelwand_4": {
|
||||||
|
"poly": 16,
|
||||||
|
"collision": {
|
||||||
|
"quelle": "kollisions/bau_giebelwand_4.json",
|
||||||
|
"shape": "trimesh",
|
||||||
|
"size": [
|
||||||
|
4.0,
|
||||||
|
2.0,
|
||||||
|
0.2
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"glb": "res://assets/objekte/military_desert/bauteile/bau_giebelwand_4.glb",
|
||||||
|
"kategorie": "bauteile",
|
||||||
|
"name_de": "Giebelwand 4 m"
|
||||||
|
},
|
||||||
|
"bau_giebelwand_6": {
|
||||||
|
"poly": 16,
|
||||||
|
"collision": {
|
||||||
|
"quelle": "kollisions/bau_giebelwand_6.json",
|
||||||
|
"shape": "trimesh",
|
||||||
|
"size": [
|
||||||
|
6.0,
|
||||||
|
3.0,
|
||||||
|
0.2
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"glb": "res://assets/objekte/military_desert/bauteile/bau_giebelwand_6.glb",
|
||||||
|
"kategorie": "bauteile",
|
||||||
|
"name_de": "Giebelwand 6 m"
|
||||||
|
},
|
||||||
"bau_pfeiler": {
|
"bau_pfeiler": {
|
||||||
"poly": 12,
|
"poly": 12,
|
||||||
"collision": {
|
"collision": {
|
||||||
|
|
|
||||||
BIN
library/thumbs/bau_dach_giebel_4.png
Normal file
BIN
library/thumbs/bau_dach_giebel_4.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
40
library/thumbs/bau_dach_giebel_4.png.import
Normal file
40
library/thumbs/bau_dach_giebel_4.png.import
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://bt6lbs34ymm1a"
|
||||||
|
path="res://.godot/imported/bau_dach_giebel_4.png-168cdf0c90f3e99c7b117e37cec1b44c.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://library/thumbs/bau_dach_giebel_4.png"
|
||||||
|
dest_files=["res://.godot/imported/bau_dach_giebel_4.png-168cdf0c90f3e99c7b117e37cec1b44c.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
BIN
library/thumbs/bau_dach_giebel_6.png
Normal file
BIN
library/thumbs/bau_dach_giebel_6.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
40
library/thumbs/bau_dach_giebel_6.png.import
Normal file
40
library/thumbs/bau_dach_giebel_6.png.import
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://dbvqftv8ptl76"
|
||||||
|
path="res://.godot/imported/bau_dach_giebel_6.png-fa634574bba400175ee1613d1760412b.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://library/thumbs/bau_dach_giebel_6.png"
|
||||||
|
dest_files=["res://.godot/imported/bau_dach_giebel_6.png-fa634574bba400175ee1613d1760412b.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
BIN
library/thumbs/bau_dach_pyramide_4.png
Normal file
BIN
library/thumbs/bau_dach_pyramide_4.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 980 B |
40
library/thumbs/bau_dach_pyramide_4.png.import
Normal file
40
library/thumbs/bau_dach_pyramide_4.png.import
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://y5g22t4cndf0"
|
||||||
|
path="res://.godot/imported/bau_dach_pyramide_4.png-b23be54768ab1d080337a426aa59d63d.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://library/thumbs/bau_dach_pyramide_4.png"
|
||||||
|
dest_files=["res://.godot/imported/bau_dach_pyramide_4.png-b23be54768ab1d080337a426aa59d63d.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
BIN
library/thumbs/bau_dach_pyramide_6.png
Normal file
BIN
library/thumbs/bau_dach_pyramide_6.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 980 B |
40
library/thumbs/bau_dach_pyramide_6.png.import
Normal file
40
library/thumbs/bau_dach_pyramide_6.png.import
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://cao1art7cdwnm"
|
||||||
|
path="res://.godot/imported/bau_dach_pyramide_6.png-76fda4b6ce5077d861a5cf721b328832.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://library/thumbs/bau_dach_pyramide_6.png"
|
||||||
|
dest_files=["res://.godot/imported/bau_dach_pyramide_6.png-76fda4b6ce5077d861a5cf721b328832.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
BIN
library/thumbs/bau_giebelwand_4.png
Normal file
BIN
library/thumbs/bau_giebelwand_4.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 833 B |
40
library/thumbs/bau_giebelwand_4.png.import
Normal file
40
library/thumbs/bau_giebelwand_4.png.import
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://dvf3l17nuahbv"
|
||||||
|
path="res://.godot/imported/bau_giebelwand_4.png-31bd67d6005e218e8ee8915ba9b2723f.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://library/thumbs/bau_giebelwand_4.png"
|
||||||
|
dest_files=["res://.godot/imported/bau_giebelwand_4.png-31bd67d6005e218e8ee8915ba9b2723f.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
BIN
library/thumbs/bau_giebelwand_6.png
Normal file
BIN
library/thumbs/bau_giebelwand_6.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 833 B |
40
library/thumbs/bau_giebelwand_6.png.import
Normal file
40
library/thumbs/bau_giebelwand_6.png.import
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://crpyxcrmio21s"
|
||||||
|
path="res://.godot/imported/bau_giebelwand_6.png-80357dac5fdcf76c9bb8a094c69cceda.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://library/thumbs/bau_giebelwand_6.png"
|
||||||
|
dest_files=["res://.godot/imported/bau_giebelwand_6.png-80357dac5fdcf76c9bb8a094c69cceda.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
|
|
@ -49,11 +49,15 @@ const GRUPPEN := [
|
||||||
{"key": "grund", "label": "Fundament & Boden", "oids": ["bau_fundament", "bau_boden"]},
|
{"key": "grund", "label": "Fundament & Boden", "oids": ["bau_fundament", "bau_boden"]},
|
||||||
{"key": "waende", "label": "Wände", "oids": ["bau_wand", "bau_wand_tuer", "bau_wand_fenster",
|
{"key": "waende", "label": "Wände", "oids": ["bau_wand", "bau_wand_tuer", "bau_wand_fenster",
|
||||||
"bau_wand_scharte", "bau_bruestung", "bau_pfeiler"]},
|
"bau_wand_scharte", "bau_bruestung", "bau_pfeiler"]},
|
||||||
{"key": "dach", "label": "Dach & Giebel", "oids": ["bau_giebelwand", "bau_dach_giebel", "bau_dach_pyramide"]},
|
{"key": "dach", "label": "Dach & Giebel (klein 2 m)", "oids": ["bau_giebelwand", "bau_dach_giebel", "bau_dach_pyramide"]},
|
||||||
|
{"key": "dach_mittel", "label": "Dach & Giebel (mittel 4 m)", "oids": ["bau_giebelwand_4", "bau_dach_giebel_4", "bau_dach_pyramide_4"]},
|
||||||
|
{"key": "dach_gross", "label": "Dach & Giebel (groß 6 m)", "oids": ["bau_giebelwand_6", "bau_dach_giebel_6", "bau_dach_pyramide_6"]},
|
||||||
{"key": "treppe", "label": "Treppe", "oids": ["bau_treppe"]},
|
{"key": "treppe", "label": "Treppe", "oids": ["bau_treppe"]},
|
||||||
]
|
]
|
||||||
|
|
||||||
const QUADRAT := [[-1.0, 0.0, -1.0], [1.0, 0.0, -1.0], [1.0, 0.0, 1.0], [-1.0, 0.0, 1.0]]
|
const QUADRAT := [[-1.0, 0.0, -1.0], [1.0, 0.0, -1.0], [1.0, 0.0, 1.0], [-1.0, 0.0, 1.0]]
|
||||||
|
const QUADRAT_4 := [[-2.0, 0.0, -2.0], [2.0, 0.0, -2.0], [2.0, 0.0, 2.0], [-2.0, 0.0, 2.0]]
|
||||||
|
const QUADRAT_6 := [[-3.0, 0.0, -3.0], [3.0, 0.0, -3.0], [3.0, 0.0, 3.0], [-3.0, 0.0, 3.0]]
|
||||||
|
|
||||||
## Reihenfolge = Anzeige-Reihenfolge (GRUPPEN nutzt dieselben IDs).
|
## Reihenfolge = Anzeige-Reihenfolge (GRUPPEN nutzt dieselben IDs).
|
||||||
## "traeger": darauf darf gestapelt werden (Oberkante zählt als Ebene).
|
## "traeger": darauf darf gestapelt werden (Oberkante zählt als Ebene).
|
||||||
|
|
@ -107,6 +111,34 @@ const KINDS := {
|
||||||
"label": "Pyramidendach", "info": "2×2 m, Spitze in der Mitte", "snap": "zelle", "traeger": false,
|
"label": "Pyramidendach", "info": "2×2 m, Spitze in der Mitte", "snap": "zelle", "traeger": false,
|
||||||
"typ": T_DACH_PYRAMIDE, "punkte": QUADRAT, "rise": 1.0, "farbe": Color(0.40, 0.25, 0.18),
|
"typ": T_DACH_PYRAMIDE, "punkte": QUADRAT, "rise": 1.0, "farbe": Color(0.40, 0.25, 0.18),
|
||||||
},
|
},
|
||||||
|
# Große Dächer (Test-Befund 25.10.: 2×2 m reichte nur für eine Zelle). Immer 45°
|
||||||
|
# (Spitze = halbe Breite); Giebelwand und Dach gleicher Größe passen zusammen.
|
||||||
|
"bau_giebelwand_4": {
|
||||||
|
"label": "Giebelwand 4 m", "info": "Dreieck-Abschluss, 4 m breit, 2 m Spitze — zum Satteldach 4 m",
|
||||||
|
"snap": "kante", "traeger": false,
|
||||||
|
"typ": T_GIEBEL, "punkte": [[0.0, 0.0, 0.0], [4.0, 0.0, 0.0]], "rise": 2.0, "farbe": Color(0.62, 0.53, 0.38),
|
||||||
|
},
|
||||||
|
"bau_dach_giebel_4": {
|
||||||
|
"label": "Satteldach 4×4 m", "info": "4×4 m, First in Richtung der Vorschau (R dreht)", "snap": "zelle", "traeger": false,
|
||||||
|
"typ": T_DACH_GIEBEL, "punkte": QUADRAT_4, "rise": 2.0, "farbe": Color(0.40, 0.25, 0.18),
|
||||||
|
},
|
||||||
|
"bau_dach_pyramide_4": {
|
||||||
|
"label": "Pyramidendach 4×4 m", "info": "4×4 m, Spitze in der Mitte", "snap": "zelle", "traeger": false,
|
||||||
|
"typ": T_DACH_PYRAMIDE, "punkte": QUADRAT_4, "rise": 2.0, "farbe": Color(0.40, 0.25, 0.18),
|
||||||
|
},
|
||||||
|
"bau_giebelwand_6": {
|
||||||
|
"label": "Giebelwand 6 m", "info": "Dreieck-Abschluss, 6 m breit, 3 m Spitze — zum Satteldach 6 m",
|
||||||
|
"snap": "kante", "traeger": false,
|
||||||
|
"typ": T_GIEBEL, "punkte": [[0.0, 0.0, 0.0], [6.0, 0.0, 0.0]], "rise": 3.0, "farbe": Color(0.62, 0.53, 0.38),
|
||||||
|
},
|
||||||
|
"bau_dach_giebel_6": {
|
||||||
|
"label": "Satteldach 6×6 m", "info": "6×6 m, First in Richtung der Vorschau (R dreht)", "snap": "zelle", "traeger": false,
|
||||||
|
"typ": T_DACH_GIEBEL, "punkte": QUADRAT_6, "rise": 3.0, "farbe": Color(0.40, 0.25, 0.18),
|
||||||
|
},
|
||||||
|
"bau_dach_pyramide_6": {
|
||||||
|
"label": "Pyramidendach 6×6 m", "info": "6×6 m, Spitze in der Mitte", "snap": "zelle", "traeger": false,
|
||||||
|
"typ": T_DACH_PYRAMIDE, "punkte": QUADRAT_6, "rise": 3.0, "farbe": Color(0.40, 0.25, 0.18),
|
||||||
|
},
|
||||||
"bau_treppe": {
|
"bau_treppe": {
|
||||||
"label": "Treppe", "info": "4 m lang, 2 m breit, 3 m hoch — führt in ein Geschoss darüber",
|
"label": "Treppe", "info": "4 m lang, 2 m breit, 3 m hoch — führt in ein Geschoss darüber",
|
||||||
"snap": "treppe", "traeger": false,
|
"snap": "treppe", "traeger": false,
|
||||||
|
|
@ -134,6 +166,11 @@ static func snap_art(oid: String) -> String:
|
||||||
return str((KINDS.get(oid, {}) as Dictionary).get("snap", "zelle"))
|
return str((KINDS.get(oid, {}) as Dictionary).get("snap", "zelle"))
|
||||||
|
|
||||||
|
|
||||||
|
## Platte = flaches Zell-Teil, auf dem man steht (Fundament, Boden/Decke).
|
||||||
|
static func ist_platte(oid: String) -> bool:
|
||||||
|
return snap_art(oid) == "zelle" and ist_traeger(oid)
|
||||||
|
|
||||||
|
|
||||||
static func ist_traeger(oid: String) -> bool:
|
static func ist_traeger(oid: String) -> bool:
|
||||||
return bool((KINDS.get(oid, {}) as Dictionary).get("traeger", false))
|
return bool((KINDS.get(oid, {}) as Dictionary).get("traeger", false))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -122,22 +122,22 @@ static func haus_demo(doc: Dictionary, history: Variant, mitte: Vector3) -> int:
|
||||||
["bau_fundament", Vector3(ox + 1, mitte.y, oz - 1), 0.0],
|
["bau_fundament", Vector3(ox + 1, mitte.y, oz - 1), 0.0],
|
||||||
["bau_fundament", Vector3(ox - 1, mitte.y, oz + 1), 0.0],
|
["bau_fundament", Vector3(ox - 1, mitte.y, oz + 1), 0.0],
|
||||||
["bau_fundament", Vector3(ox + 1, mitte.y, oz + 1), 0.0],
|
["bau_fundament", Vector3(ox + 1, mitte.y, oz + 1), 0.0],
|
||||||
["bau_wand", Vector3(ox - 1, mitte.y, oz - 2), 0.0],
|
# Wände liegen GANZ in der Zelle, in der die Maus ist (Außenseite bündig mit
|
||||||
["bau_wand_fenster", Vector3(ox + 1, mitte.y, oz - 2), 0.0],
|
# der Kante) — deshalb zielen die Klicks 10 cm ins Hausinnere.
|
||||||
["bau_wand_tuer", Vector3(ox - 1, mitte.y, oz + 2), 0.0],
|
["bau_wand", Vector3(ox - 1, mitte.y, oz - 1.9), 0.0],
|
||||||
["bau_wand", Vector3(ox + 1, mitte.y, oz + 2), 0.0],
|
["bau_wand_fenster", Vector3(ox + 1, mitte.y, oz - 1.9), 0.0],
|
||||||
["bau_wand", Vector3(ox - 2, mitte.y, oz - 1), 90.0],
|
["bau_wand_tuer", Vector3(ox - 1, mitte.y, oz + 1.9), 0.0],
|
||||||
["bau_wand_scharte", Vector3(ox - 2, mitte.y, oz + 1), 90.0],
|
["bau_wand", Vector3(ox + 1, mitte.y, oz + 1.9), 0.0],
|
||||||
["bau_wand", Vector3(ox + 2, mitte.y, oz - 1), 90.0],
|
["bau_wand", Vector3(ox - 1.9, mitte.y, oz - 1), 90.0],
|
||||||
["bau_wand", Vector3(ox + 2, mitte.y, oz + 1), 90.0],
|
["bau_wand_scharte", Vector3(ox - 1.9, mitte.y, oz + 1), 90.0],
|
||||||
|
["bau_wand", Vector3(ox + 1.9, mitte.y, oz - 1), 90.0],
|
||||||
|
["bau_wand", Vector3(ox + 1.9, mitte.y, oz + 1), 90.0],
|
||||||
]
|
]
|
||||||
for st in schritte:
|
for st in schritte:
|
||||||
_demo_setzen(doc, str(st[0]), st[1] as Vector3, float(st[2]), gid, terrain)
|
_demo_setzen(doc, str(st[0]), st[1] as Vector3, float(st[2]), gid, terrain)
|
||||||
# Decke + Dach auf die Wandoberkante (Klick auf die Oberkante = Ebene Wand oben)
|
# Ein großes Satteldach (4×4 m) auf die Wandoberkante.
|
||||||
var oben := mitte.y + 0.2 + BauKatalog.WAND_H
|
var oben := mitte.y + BauKatalog.WAND_H
|
||||||
for cx in [-1.0, 1.0]:
|
_demo_setzen(doc, "bau_dach_giebel_4", Vector3(ox, oben, oz), 0.0, gid, terrain)
|
||||||
for cz in [-1.0, 1.0]:
|
|
||||||
_demo_setzen(doc, "bau_dach_giebel", Vector3(ox + cx, oben, oz + cz), 0.0, gid, terrain)
|
|
||||||
if history != null:
|
if history != null:
|
||||||
history.commit("Haus gebaut (Demo)", before, history.snapshot(["objects", "group_names"]))
|
history.commit("Haus gebaut (Demo)", before, history.snapshot(["objects", "group_names"]))
|
||||||
return gid
|
return gid
|
||||||
|
|
|
||||||
|
|
@ -43,18 +43,31 @@ static func berechne(oid: String, hit: Vector3, dreh: float, teile: Array,
|
||||||
var q := int(roundf(dreh / 90.0))
|
var q := int(roundf(dreh / 90.0))
|
||||||
var viertel := ((q % 4) + 4) % 4
|
var viertel := ((q % 4) + 4) % 4
|
||||||
var p := Vector2(hit.x, hit.z)
|
var p := Vector2(hit.x, hit.z)
|
||||||
# Treffer auf einem Objekt: Zelle auf der Kameraseite wählen (Klick auf die
|
# Treffer auf einem Objekt: Wände liegen GANZ in einer Zelle — die Quer-Achse
|
||||||
# Wandoberkante liegt genau auf der Zellgrenze).
|
# kommt dann von der Wand (Decke/Dach landet in der Zelle der Wand). Bei anderen
|
||||||
if hit_ist_objekt and art != "kante" and kamera_dir != Vector3.ZERO:
|
# Objekten die Zelle auf der Kameraseite wählen (Klick liegt auf einer Grenze).
|
||||||
var d2 := Vector2(kamera_dir.x, kamera_dir.z)
|
var wand_getroffen := {}
|
||||||
if d2.length() > 0.01:
|
if hit_ist_objekt:
|
||||||
p -= d2.normalized() * 0.2
|
wand_getroffen = _wand_unter_klick(teile, p, hit.y)
|
||||||
|
if hit_ist_objekt and art != "kante":
|
||||||
|
if not wand_getroffen.is_empty():
|
||||||
|
var wp: Vector3 = wand_getroffen["pos"]
|
||||||
|
if absf(sin(deg_to_rad(float(wand_getroffen["rot"])))) > 0.5:
|
||||||
|
p.x = wp.x
|
||||||
|
else:
|
||||||
|
p.y = wp.z
|
||||||
|
elif kamera_dir != Vector3.ZERO:
|
||||||
|
var d2 := Vector2(kamera_dir.x, kamera_dir.z)
|
||||||
|
if d2.length() > 0.01:
|
||||||
|
p -= d2.normalized() * 0.2
|
||||||
var mitte := Vector2.ZERO
|
var mitte := Vector2.ZERO
|
||||||
var rot := 0.0
|
var rot := 0.0
|
||||||
var r := BauKatalog.RASTER_M
|
var r := BauKatalog.RASTER_M
|
||||||
match art:
|
match art:
|
||||||
"zelle":
|
"zelle":
|
||||||
mitte = Vector2(floorf(p.x / r) * r + r * 0.5, floorf(p.y / r) * r + r * 0.5)
|
# Teile über mehrere Zellen (große Dächer): gerade Zellzahl → Mitte auf
|
||||||
|
# einer Zellecke, ungerade → Mitte einer Zelle.
|
||||||
|
mitte = Vector2(_raster_mitte(p.x, gr.x), _raster_mitte(p.y, gr.z))
|
||||||
rot = viertel * 90.0
|
rot = viertel * 90.0
|
||||||
"ecke":
|
"ecke":
|
||||||
mitte = Vector2(roundf(p.x / r) * r, roundf(p.y / r) * r)
|
mitte = Vector2(roundf(p.x / r) * r, roundf(p.y / r) * r)
|
||||||
|
|
@ -65,16 +78,31 @@ static func berechne(oid: String, hit: Vector3, dreh: float, teile: Array,
|
||||||
else:
|
else:
|
||||||
mitte = Vector2(floorf(p.x / r) * r + r * 0.5, roundf(p.y / r) * r)
|
mitte = Vector2(floorf(p.x / r) * r + r * 0.5, roundf(p.y / r) * r)
|
||||||
_: # "kante"
|
_: # "kante"
|
||||||
var fx := p.x / r
|
var vorhanden := wand_getroffen
|
||||||
var fz := p.y / r
|
if not vorhanden.is_empty():
|
||||||
var h_mitte := Vector2((floorf(fx) + 0.5) * r, roundf(fz) * r) # Kante entlang X
|
# Klick auf eine vorhandene Wand (Seite = ersetzen, Oberkante = stapeln):
|
||||||
var v_mitte := Vector2(roundf(fx) * r, (floorf(fz) + 0.5) * r) # Kante entlang Z
|
# GENAU ihre Lage übernehmen, nicht neu aus der Klickstelle raten.
|
||||||
if p.distance_squared_to(h_mitte) <= p.distance_squared_to(v_mitte):
|
var tp: Vector3 = vorhanden["pos"]
|
||||||
mitte = h_mitte
|
mitte = Vector2(tp.x, tp.z)
|
||||||
rot = 0.0
|
rot = 90.0 if absf(sin(deg_to_rad(float(vorhanden["rot"])))) > 0.5 else 0.0
|
||||||
else:
|
else:
|
||||||
mitte = v_mitte
|
# Wand liegt GANZ IN der Zelle, in der der Mauszeiger ist: Außenseite
|
||||||
rot = 90.0
|
# bündig mit der Zellkante (Boden/Fundament), nicht halb in der Luft.
|
||||||
|
# Dadurch schließen sich auch Ecken ohne Lücke (Test-Befund 25.10.).
|
||||||
|
var laenge := gr.x
|
||||||
|
var halbe_dicke := gr.z * 0.5
|
||||||
|
var fx := p.x / r
|
||||||
|
var fz := p.y / r
|
||||||
|
var h_mitte := Vector2(_raster_mitte(p.x, laenge), roundf(fz) * r) # Kante entlang X
|
||||||
|
var v_mitte := Vector2(roundf(fx) * r, _raster_mitte(p.y, laenge)) # Kante entlang Z
|
||||||
|
if p.distance_squared_to(h_mitte) <= p.distance_squared_to(v_mitte):
|
||||||
|
mitte = h_mitte
|
||||||
|
mitte.y += halbe_dicke if p.y >= h_mitte.y else -halbe_dicke
|
||||||
|
rot = 0.0
|
||||||
|
else:
|
||||||
|
mitte = v_mitte
|
||||||
|
mitte.x += halbe_dicke if p.x >= v_mitte.x else -halbe_dicke
|
||||||
|
rot = 90.0
|
||||||
rot += 180.0 * float(viertel % 2) # R dreht die Wand um (Blickseite der Teile)
|
rot += 180.0 * float(viertel % 2) # R dreht die Wand um (Blickseite der Teile)
|
||||||
rot = fposmod(rot, 360.0)
|
rot = fposmod(rot, 360.0)
|
||||||
var quer := absf(sin(deg_to_rad(rot))) > 0.5 # 90°/270°: X- und Z-Ausdehnung getauscht
|
var quer := absf(sin(deg_to_rad(rot))) > 0.5 # 90°/270°: X- und Z-Ausdehnung getauscht
|
||||||
|
|
@ -95,7 +123,15 @@ static func berechne(oid: String, hit: Vector3, dreh: float, teile: Array,
|
||||||
if not tr.intersects(kern):
|
if not tr.intersects(kern):
|
||||||
continue
|
continue
|
||||||
var top: float = (t["pos"] as Vector3).y + BauKatalog.groesse(troid).y
|
var top: float = (t["pos"] as Vector3).y + BauKatalog.groesse(troid).y
|
||||||
if BauKatalog.ist_traeger(troid):
|
if not BauKatalog.ist_traeger(troid):
|
||||||
|
continue
|
||||||
|
if (art == "kante" or art == "ecke") and BauKatalog.ist_platte(troid):
|
||||||
|
# Wände/Pfeiler zählen von der UNTERKANTE einer Platte (Boden/Decke/
|
||||||
|
# Fundament): jedes Geschoss liegt dadurch auf Vielfachen der Wandhöhe —
|
||||||
|
# mit oder ohne Decke darunter sitzen obere Wände gleich hoch
|
||||||
|
# (Test-Befund 25.10.). Die Platte selbst liegt auf der Wandoberkante.
|
||||||
|
ebenen.append((t["pos"] as Vector3).y)
|
||||||
|
else:
|
||||||
ebenen.append(top)
|
ebenen.append(top)
|
||||||
var ebene := _waehle_ebene(ebenen, hit.y)
|
var ebene := _waehle_ebene(ebenen, hit.y)
|
||||||
|
|
||||||
|
|
@ -123,6 +159,39 @@ static func berechne(oid: String, hit: Vector3, dreh: float, teile: Array,
|
||||||
"ebene": ebene, "grund": ""}
|
"ebene": ebene, "grund": ""}
|
||||||
|
|
||||||
|
|
||||||
|
## Mitte eines Teils der Länge `laenge` entlang einer Achse: gerade Zellzahl →
|
||||||
|
## auf der nächsten Zellgrenze, ungerade → Mitte der Zelle.
|
||||||
|
static func _raster_mitte(wert: float, laenge: float) -> float:
|
||||||
|
var r := BauKatalog.RASTER_M
|
||||||
|
var zellen := maxi(1, int(roundf(laenge / r)))
|
||||||
|
if zellen % 2 == 0:
|
||||||
|
return roundf(wert / r) * r
|
||||||
|
return floorf(wert / r) * r + r * 0.5
|
||||||
|
|
||||||
|
|
||||||
|
## Vorhandene Wand, auf die der Klick trifft (Seite oder Oberkante): Rückgabe das
|
||||||
|
## Teil-Wörterbuch oder {}. `p` = Klickpunkt in XZ, `y` = Klickhöhe.
|
||||||
|
static func _wand_unter_klick(teile: Array, p: Vector2, y: float) -> Dictionary:
|
||||||
|
var bester := {}
|
||||||
|
var bester_d := INF
|
||||||
|
for t in teile:
|
||||||
|
var troid := str(t["oid"])
|
||||||
|
if BauKatalog.snap_art(troid) != "kante":
|
||||||
|
continue
|
||||||
|
var tr := teil_rect(t).grow(0.06)
|
||||||
|
if not tr.has_point(p):
|
||||||
|
continue
|
||||||
|
var unten: float = (t["pos"] as Vector3).y
|
||||||
|
var oben := unten + BauKatalog.groesse(troid).y
|
||||||
|
if y < unten - 0.1 or y > oben + 0.1:
|
||||||
|
continue
|
||||||
|
var d := p.distance_squared_to(tr.get_center())
|
||||||
|
if d < bester_d:
|
||||||
|
bester_d = d
|
||||||
|
bester = t
|
||||||
|
return bester
|
||||||
|
|
||||||
|
|
||||||
## Höchste Ebene ≤ hit_y + TOL; gibt es keine (Klick tief unter allen), die tiefste.
|
## Höchste Ebene ≤ hit_y + TOL; gibt es keine (Klick tief unter allen), die tiefste.
|
||||||
static func _waehle_ebene(ebenen: Array, hit_y: float) -> float:
|
static func _waehle_ebene(ebenen: Array, hit_y: float) -> float:
|
||||||
var best := -INF
|
var best := -INF
|
||||||
|
|
|
||||||
127
src/ui/main.gd
127
src/ui/main.gd
|
|
@ -76,7 +76,10 @@ var inspector: Control # Objekt-Inspektor (Karte t_bb0d11c9) — im Insp
|
||||||
# bleiben hier, damit Tests/Tools denselben Vertrag sehen wie vorher)
|
# bleiben hier, damit Tests/Tools denselben Vertrag sehen wie vorher)
|
||||||
var toolbar_mod # src/ui/panels/toolbar.gd
|
var toolbar_mod # src/ui/panels/toolbar.gd
|
||||||
var status_mod # src/ui/panels/status_bar.gd
|
var status_mod # src/ui/panels/status_bar.gd
|
||||||
var selection_mod # src/ui/panels/selection_panel.gd
|
var selection_mod # src/ui/panels/selection_panel.gd (nur Logik, kein Tab mehr)
|
||||||
|
var settings_mod # src/ui/panels/settings_panel.gd (im Einstellungen-Fenster)
|
||||||
|
var settings_dialog: AcceptDialog # Fenster „Einstellungen“ (Knopf in der Kopfleiste)
|
||||||
|
var sym_mod # src/ui/panels/symmetry_panel.gd (im Map-Tab)
|
||||||
var map_mod # src/ui/panels/map_panel.gd
|
var map_mod # src/ui/panels/map_panel.gd
|
||||||
var terrain_mod # src/ui/panels/terrain_panel.gd
|
var terrain_mod # src/ui/panels/terrain_panel.gd
|
||||||
var malen_mod # src/ui/panels/malen_panel.gd (Malen-Tab)
|
var malen_mod # src/ui/panels/malen_panel.gd (Malen-Tab)
|
||||||
|
|
@ -91,13 +94,12 @@ var tool_label: Label # weiter in status_mod
|
||||||
var sym_label: Label # weiter in status_mod
|
var sym_label: Label # weiter in status_mod
|
||||||
var undo_btn: Button # weiter in toolbar_mod
|
var undo_btn: Button # weiter in toolbar_mod
|
||||||
var redo_btn: Button # weiter in toolbar_mod
|
var redo_btn: Button # weiter in toolbar_mod
|
||||||
var grid_btn: Button # weiter in toolbar_mod
|
var grid_btn: CheckButton # weiter in settings_mod (Einstellungen-Fenster)
|
||||||
var contours_btn: Button # weiter in toolbar_mod
|
var contours_btn: CheckButton # weiter in settings_mod
|
||||||
var size_menu: MenuButton # weiter in toolbar_mod
|
var size_menu: OptionButton # weiter in settings_mod
|
||||||
var sym_menu: MenuButton # weiter in toolbar_mod
|
var sym_menu: OptionButton # weiter in settings_mod
|
||||||
var prop_name: LineEdit # weiter in map_mod
|
var prop_name: LineEdit # weiter in map_mod
|
||||||
var sel_label: Label # weiter in selection_mod
|
var gizmo_step_box: SpinBox # weiter in settings_mod
|
||||||
var gizmo_step_box: SpinBox # weiter in selection_mod
|
|
||||||
var library_search: LineEdit # weiter in assets_mod
|
var library_search: LineEdit # weiter in assets_mod
|
||||||
var library_buttons: Dictionary = {} # weiter in assets_mod (Bequemlichkeit)
|
var library_buttons: Dictionary = {} # weiter in assets_mod (Bequemlichkeit)
|
||||||
var _library_group_rows: Array = [] # weiter in assets_mod (Bequemlichkeit)
|
var _library_group_rows: Array = [] # weiter in assets_mod (Bequemlichkeit)
|
||||||
|
|
@ -273,6 +275,7 @@ func _build_ui() -> void:
|
||||||
ui_right_panel = right_tabs
|
ui_right_panel = right_tabs
|
||||||
right_tabs.tab_changed.connect(_on_right_tab_changed)
|
right_tabs.tab_changed.connect(_on_right_tab_changed)
|
||||||
split.add_child(right_tabs)
|
split.add_child(right_tabs)
|
||||||
|
_build_map_sections()
|
||||||
|
|
||||||
# --- Assets unten (Registry-Gruppe "unten", U1 Auftragspunkt 4) ---
|
# --- Assets unten (Registry-Gruppe "unten", U1 Auftragspunkt 4) ---
|
||||||
# Die Objekt-Bibliothek wohnt jetzt UNTER dem Hauptbereich (Unity-
|
# Die Objekt-Bibliothek wohnt jetzt UNTER dem Hauptbereich (Unity-
|
||||||
|
|
@ -337,13 +340,6 @@ func _make_module(script: GDScript, parent: Control) -> Control:
|
||||||
func _pull_toolbar_refs() -> void:
|
func _pull_toolbar_refs() -> void:
|
||||||
undo_btn = toolbar_mod.undo_btn
|
undo_btn = toolbar_mod.undo_btn
|
||||||
redo_btn = toolbar_mod.redo_btn
|
redo_btn = toolbar_mod.redo_btn
|
||||||
grid_btn = toolbar_mod.grid_btn
|
|
||||||
contours_btn = toolbar_mod.contours_btn
|
|
||||||
size_menu = toolbar_mod.size_menu
|
|
||||||
sym_menu = toolbar_mod.sym_menu
|
|
||||||
# Häkchen im UI-Größe-Menü auf die aktuelle Stufe (vor U1 rief die
|
|
||||||
# Top-Bar _sync_size_menu während des Baus — jetzt nach dem Referenz-Zug).
|
|
||||||
_sync_size_menu()
|
|
||||||
|
|
||||||
|
|
||||||
func _pull_status_refs() -> void:
|
func _pull_status_refs() -> void:
|
||||||
|
|
@ -386,8 +382,6 @@ func _panel_group_built(gruppe: String, tabs: TabContainer) -> void:
|
||||||
match child.name:
|
match child.name:
|
||||||
"inspektor":
|
"inspektor":
|
||||||
inspector = child
|
inspector = child
|
||||||
"auswahl":
|
|
||||||
selection_mod = child
|
|
||||||
"map":
|
"map":
|
||||||
map_mod = child
|
map_mod = child
|
||||||
"terrain":
|
"terrain":
|
||||||
|
|
@ -396,12 +390,7 @@ func _panel_group_built(gruppe: String, tabs: TabContainer) -> void:
|
||||||
malen_mod = child
|
malen_mod = child
|
||||||
"bauen":
|
"bauen":
|
||||||
bauen_mod = child
|
bauen_mod = child
|
||||||
"check":
|
|
||||||
check_mod = child
|
|
||||||
sel_label = selection_mod.sel_label
|
|
||||||
gizmo_step_box = selection_mod.gizmo_step_box
|
|
||||||
prop_name = map_mod.prop_name
|
prop_name = map_mod.prop_name
|
||||||
spawn_status = check_mod.spawn_status
|
|
||||||
terrain_info = map_mod.terrain_info
|
terrain_info = map_mod.terrain_info
|
||||||
# Malen-Widgets wohnen im Malen-Tab; das Terrain-Modul hält die
|
# Malen-Widgets wohnen im Malen-Tab; das Terrain-Modul hält die
|
||||||
# alten Namen als Aliase (Tests/Werkzeug-Vertrag).
|
# alten Namen als Aliase (Tests/Werkzeug-Vertrag).
|
||||||
|
|
@ -438,7 +427,73 @@ func _panel_group_built(gruppe: String, tabs: TabContainer) -> void:
|
||||||
library_close_all_btn = assets_mod.library_close_all_btn
|
library_close_all_btn = assets_mod.library_close_all_btn
|
||||||
|
|
||||||
|
|
||||||
|
## Abschnitte im Map-Tab (Registry-Gruppe "map"): Spawn-Zonen, Test-Start-Info,
|
||||||
|
## Symmetrie. Sie hängen UNTER den Map-Eigenschaften im selben Scroll-Bereich.
|
||||||
|
func _build_map_sections() -> void:
|
||||||
|
var host: Control = map_mod.sections_host()
|
||||||
|
for entry in PanelRegistryScript.entries("map"):
|
||||||
|
var script: GDScript = load(str(entry["skript"]))
|
||||||
|
var panel: Control = script.new()
|
||||||
|
panel.name = str(entry["id"])
|
||||||
|
panel.setup(self)
|
||||||
|
if panel.has_method("build"):
|
||||||
|
panel.call("build")
|
||||||
|
host.add_child(HSeparator.new())
|
||||||
|
host.add_child(panel)
|
||||||
|
match str(entry["id"]):
|
||||||
|
"check":
|
||||||
|
check_mod = panel
|
||||||
|
spawn_status = check_mod.spawn_status
|
||||||
|
"symmetrie":
|
||||||
|
sym_mod = panel
|
||||||
|
|
||||||
|
|
||||||
|
## Einstellungen-Fenster (Knopf ⚙ in der Kopfleiste): Inhalt kommt aus der
|
||||||
|
## Registry-Gruppe "dialog". Gitter, Höhenlinien, Symmetrie-Modus, UI-Größe,
|
||||||
|
## Raster-Snap und Gizmo-Schritt wohnen dort (vorher Kopfleiste / Objekt-Werkzeuge).
|
||||||
|
func _build_settings_dialog() -> void:
|
||||||
|
settings_dialog = AcceptDialog.new()
|
||||||
|
settings_dialog.name = "EinstellungenFenster"
|
||||||
|
settings_dialog.title = "Einstellungen"
|
||||||
|
settings_dialog.exclusive = false
|
||||||
|
settings_dialog.ok_button_text = "Schließen"
|
||||||
|
settings_dialog.min_size = UIScale.v2i(520, 420)
|
||||||
|
var scroll := ScrollContainer.new()
|
||||||
|
scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
|
||||||
|
scroll.custom_minimum_size = UIScale.v2(500, 380)
|
||||||
|
settings_dialog.add_child(scroll)
|
||||||
|
for entry in PanelRegistryScript.entries("dialog"):
|
||||||
|
var script: GDScript = load(str(entry["skript"]))
|
||||||
|
var panel: Control = script.new()
|
||||||
|
panel.name = str(entry["id"])
|
||||||
|
panel.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
|
panel.setup(self)
|
||||||
|
if panel.has_method("build"):
|
||||||
|
panel.call("build")
|
||||||
|
scroll.add_child(panel)
|
||||||
|
settings_mod = panel
|
||||||
|
add_child(settings_dialog)
|
||||||
|
grid_btn = settings_mod.grid_btn
|
||||||
|
contours_btn = settings_mod.contours_btn
|
||||||
|
size_menu = settings_mod.size_menu
|
||||||
|
sym_menu = settings_mod.sym_menu
|
||||||
|
gizmo_step_box = settings_mod.gizmo_step_box
|
||||||
|
settings_mod.call("refresh")
|
||||||
|
|
||||||
|
|
||||||
|
func _open_settings() -> void:
|
||||||
|
if settings_dialog == null:
|
||||||
|
return
|
||||||
|
settings_mod.call("refresh")
|
||||||
|
settings_dialog.popup_centered(UIScale.v2i(560, 520))
|
||||||
|
|
||||||
|
|
||||||
func _build_dialogs_and_overlays() -> void:
|
func _build_dialogs_and_overlays() -> void:
|
||||||
|
selection_mod = load("res://src/ui/panels/selection_panel.gd").new()
|
||||||
|
selection_mod.name = "AuswahlLogik"
|
||||||
|
selection_mod.setup(self)
|
||||||
|
add_child(selection_mod)
|
||||||
|
_build_settings_dialog()
|
||||||
# --- Dialoge ---
|
# --- Dialoge ---
|
||||||
new_dialog = preload("res://src/ui/new_map_dialog.gd").new()
|
new_dialog = preload("res://src/ui/new_map_dialog.gd").new()
|
||||||
new_dialog.confirmed.connect(_on_new_map_confirmed)
|
new_dialog.confirmed.connect(_on_new_map_confirmed)
|
||||||
|
|
@ -558,11 +613,26 @@ func _build_dialogs_and_overlays() -> void:
|
||||||
add_child(help_overlay)
|
add_child(help_overlay)
|
||||||
|
|
||||||
|
|
||||||
|
## Ziehen aus der Objekt-Bibliothek: Vorschau folgt der Maus, Loslassen setzt.
|
||||||
|
func _viewport_can_drop(at_position: Vector2, data: Variant) -> bool:
|
||||||
|
if not (data is Dictionary) or not (data as Dictionary).has("svr_oid") or doc.is_empty() or assets_mod == null:
|
||||||
|
return false
|
||||||
|
assets_mod.drag_hover(str(data["svr_oid"]), at_position)
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
func _viewport_drop(at_position: Vector2, data: Variant) -> void:
|
||||||
|
if data is Dictionary and (data as Dictionary).has("svr_oid") and assets_mod != null:
|
||||||
|
assets_mod.drag_drop(str(data["svr_oid"]), at_position)
|
||||||
|
|
||||||
|
|
||||||
func _build_center_viewport() -> SubViewportContainer:
|
func _build_center_viewport() -> SubViewportContainer:
|
||||||
var container := SubViewportContainer.new()
|
var container := SubViewportContainer.new()
|
||||||
container.stretch = true
|
container.stretch = true
|
||||||
container.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
container.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
container.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
container.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||||
|
# Objekt-Kachel aus der Bibliothek in die Szene ziehen (25.10.).
|
||||||
|
container.set_drag_forwarding(Callable(), _viewport_can_drop, _viewport_drop)
|
||||||
|
|
||||||
var viewport := SubViewport.new()
|
var viewport := SubViewport.new()
|
||||||
viewport.own_world_3d = true
|
viewport.own_world_3d = true
|
||||||
|
|
@ -714,10 +784,7 @@ func _on_menu_action(id: int) -> void:
|
||||||
func _sync_size_menu() -> void:
|
func _sync_size_menu() -> void:
|
||||||
if size_menu == null or not is_instance_valid(size_menu):
|
if size_menu == null or not is_instance_valid(size_menu):
|
||||||
return
|
return
|
||||||
var popup := size_menu.get_popup()
|
size_menu.select(UIScale.step_index())
|
||||||
var cur := UIScale.step_index()
|
|
||||||
for i in popup.item_count:
|
|
||||||
popup.set_item_checked(i, i == cur)
|
|
||||||
|
|
||||||
|
|
||||||
## Neue Stufe gewählt: speichern, kompletten UI-Neuaufbau mit dem neuen
|
## Neue Stufe gewählt: speichern, kompletten UI-Neuaufbau mit dem neuen
|
||||||
|
|
@ -732,7 +799,10 @@ func _on_ui_size_selected(index: int) -> void:
|
||||||
_sync_size_menu()
|
_sync_size_menu()
|
||||||
return
|
return
|
||||||
UIScale.set_step_index(index)
|
UIScale.set_step_index(index)
|
||||||
|
var settings_war_offen := settings_dialog != null and settings_dialog.visible
|
||||||
_rebuild_ui_for_scale()
|
_rebuild_ui_for_scale()
|
||||||
|
if settings_war_offen:
|
||||||
|
_open_settings.call_deferred()
|
||||||
_set_status("UI-Größe: %s — sofort wirksam und gespeichert (bleibt auch nach Neustart)." % UIScale.step_label(index))
|
_set_status("UI-Größe: %s — sofort wirksam und gespeichert (bleibt auch nach Neustart)." % UIScale.step_label(index))
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1363,11 +1433,10 @@ func _update_sym_ui() -> void:
|
||||||
if sym_menu == null or sym_label == null:
|
if sym_menu == null or sym_label == null:
|
||||||
return
|
return
|
||||||
var label := "Symmetrie: %s" % SymmetryCore.mode_label(sym_mode)
|
var label := "Symmetrie: %s" % SymmetryCore.mode_label(sym_mode)
|
||||||
sym_menu.text = label
|
|
||||||
sym_label.text = label
|
sym_label.text = label
|
||||||
var popup := sym_menu.get_popup()
|
sym_menu.select(sym_mode)
|
||||||
for i in SymmetryCore.MODE_LABELS.size():
|
if sym_mod != null and sym_mod.has_method("refresh_mode"):
|
||||||
popup.set_item_checked(i, i == sym_mode)
|
sym_mod.call("refresh_mode")
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------- History ----
|
# --------------------------------------------------------------- History ----
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,8 @@ var _recent_box: VBoxContainer # Zuletzt-benutzt-Ansicht (im Scroll)
|
||||||
var _recent_grid: GridContainer
|
var _recent_grid: GridContainer
|
||||||
var _recent_tiles: Dictionary = {} # id → Button (Zuletzt-Ansicht, frisch gebaut)
|
var _recent_tiles: Dictionary = {} # id → Button (Zuletzt-Ansicht, frisch gebaut)
|
||||||
var _recent_ids: Array = [] # Reihenfolge: neueste zuerst
|
var _recent_ids: Array = [] # Reihenfolge: neueste zuerst
|
||||||
|
var _drag_oid := "" # Kachel, die gerade gezogen wird
|
||||||
|
var _drag_started_placement := false # Ziehen hat das Platzieren gestartet
|
||||||
var selected_oid: String = "" # per Klick gewähltes Objekt
|
var selected_oid: String = "" # per Klick gewähltes Objekt
|
||||||
var _id_font_normal: int = 0 # ID-Schriftgröße (skaliert), Normalzustand
|
var _id_font_normal: int = 0 # ID-Schriftgröße (skaliert), Normalzustand
|
||||||
var _empty_hint: Label # Leerzustand (U7): kein Treffer / leerer Bereich
|
var _empty_hint: Label # Leerzustand (U7): kein Treffer / leerer Bereich
|
||||||
|
|
@ -476,6 +478,9 @@ func _build_tile(oid: String) -> Button:
|
||||||
btn.tooltip_text += " · MODELL FEHLT (Platzhalter-Kiste)"
|
btn.tooltip_text += " · MODELL FEHLT (Platzhalter-Kiste)"
|
||||||
btn.pressed.connect(_on_tile_pressed.bind(oid))
|
btn.pressed.connect(_on_tile_pressed.bind(oid))
|
||||||
btn.gui_input.connect(_on_tile_gui_input.bind(oid))
|
btn.gui_input.connect(_on_tile_gui_input.bind(oid))
|
||||||
|
# Ziehen & Loslassen (25.10.): Kachel anklicken, gedrückt halten, in die Szene
|
||||||
|
# ziehen — beim Loslassen wird das Objekt dort gesetzt (Doppelklick geht weiter).
|
||||||
|
btn.set_drag_forwarding(_tile_drag_data.bind(oid, btn), Callable(), Callable())
|
||||||
btn.mouse_entered.connect(_on_tile_hover.bind(oid, true))
|
btn.mouse_entered.connect(_on_tile_hover.bind(oid, true))
|
||||||
btn.mouse_exited.connect(_on_tile_hover.bind(oid, false))
|
btn.mouse_exited.connect(_on_tile_hover.bind(oid, false))
|
||||||
var tex := LibraryBrowser.load_thumb(oid)
|
var tex := LibraryBrowser.load_thumb(oid)
|
||||||
|
|
@ -610,6 +615,65 @@ func _on_tile_gui_input(event: InputEvent, oid: String) -> void:
|
||||||
_activate_tile(oid)
|
_activate_tile(oid)
|
||||||
|
|
||||||
|
|
||||||
|
## Ziehen-Start einer Kachel: Vorschau-Etikett an der Maus, Daten = Objekt-ID.
|
||||||
|
func _tile_drag_data(_at_position: Vector2, oid: String, btn: Button) -> Variant:
|
||||||
|
if main.doc.is_empty():
|
||||||
|
return null
|
||||||
|
_select_tile(oid)
|
||||||
|
_drag_oid = oid
|
||||||
|
var prev := PanelContainer.new()
|
||||||
|
var box := HBoxContainer.new()
|
||||||
|
prev.add_child(box)
|
||||||
|
var tex := LibraryBrowser.load_thumb(oid)
|
||||||
|
if tex != null:
|
||||||
|
var tr := TextureRect.new()
|
||||||
|
tr.texture = tex
|
||||||
|
tr.custom_minimum_size = UIScale.v2(64, 64)
|
||||||
|
tr.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
||||||
|
tr.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
||||||
|
box.add_child(tr)
|
||||||
|
var lbl := Label.new()
|
||||||
|
lbl.text = LibraryBrowser.entry_label(oid, (main.manifest.get("objects", {}) as Dictionary).get(oid, {}))
|
||||||
|
box.add_child(lbl)
|
||||||
|
prev.modulate.a = 0.85
|
||||||
|
if btn.get_viewport().gui_is_dragging():
|
||||||
|
btn.set_drag_preview(prev)
|
||||||
|
else:
|
||||||
|
prev.free()
|
||||||
|
return {"svr_oid": oid}
|
||||||
|
|
||||||
|
|
||||||
|
## Maus mit gezogener Kachel ÜBER der 3D-Ansicht: Platzier-Vorschau folgt der Maus.
|
||||||
|
func drag_hover(oid: String, viewport_pos: Vector2) -> void:
|
||||||
|
if str(main.get("tool_mode")) != "objects":
|
||||||
|
main.call("_set_tool_mode", "objects")
|
||||||
|
if not (main.interaction.placing and main.interaction.place_oid == oid):
|
||||||
|
main.interaction.start_placement(oid)
|
||||||
|
_drag_started_placement = true
|
||||||
|
main.interaction._ghost_update(viewport_pos)
|
||||||
|
|
||||||
|
|
||||||
|
## Loslassen über der 3D-Ansicht: genau wie ein Setz-Klick.
|
||||||
|
func drag_drop(oid: String, viewport_pos: Vector2) -> void:
|
||||||
|
drag_hover(oid, viewport_pos)
|
||||||
|
main.interaction._place_object(viewport_pos, false)
|
||||||
|
if main.interaction.placing and not BauKatalog.ist_bau(oid):
|
||||||
|
main.interaction.cancel_placement() # daneben gezielt: Vorschau nicht hängen lassen
|
||||||
|
_drag_started_placement = false
|
||||||
|
_drag_oid = ""
|
||||||
|
|
||||||
|
|
||||||
|
func _notification(what: int) -> void:
|
||||||
|
if what != NOTIFICATION_DRAG_END or _drag_oid.is_empty():
|
||||||
|
return
|
||||||
|
# Losgelassen ÜBER der Szene → drag_drop hat aufgeräumt. Sonst (abgebrochen,
|
||||||
|
# anderswo losgelassen) die Vorschau wieder wegnehmen.
|
||||||
|
if _drag_started_placement and main != null and main.interaction.placing:
|
||||||
|
main.interaction.cancel_placement()
|
||||||
|
_drag_started_placement = false
|
||||||
|
_drag_oid = ""
|
||||||
|
|
||||||
|
|
||||||
func _select_tile(oid: String) -> void:
|
func _select_tile(oid: String) -> void:
|
||||||
var changed := selected_oid != oid
|
var changed := selected_oid != oid
|
||||||
selected_oid = oid
|
selected_oid = oid
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,11 @@
|
||||||
extends "res://src/ui/panels/editor_panel.gd"
|
extends "res://src/ui/panels/editor_panel.gd"
|
||||||
## Bauen-Tab: Bauteile für eigene Gebäude (Fundament, Wände, Dach, Treppe) plus ein
|
## Bauen-Tab: Bauteile für eigene Gebäude (Fundament, Wände, Dach, Treppe). Fertige
|
||||||
## paar passende Module aus dem Military-Desert-Pack. Ein Klick auf ein Teil startet
|
## Pack-Objekte (Container, Hangar-Tore, Sperren …) gehören in die Objekt-Bibliothek
|
||||||
|
## unten, NICHT hierher. Ein Klick auf ein Teil startet
|
||||||
## das Platzieren (Vorschau rastet an Zelle/Kante/Ebene, siehe build_snap.gd); jedes
|
## das Platzieren (Vorschau rastet an Zelle/Kante/Ebene, siehe build_snap.gd); jedes
|
||||||
## gesetzte Teil ist ein normales Objekt und gehört zum laufenden Gebäude (Gruppe).
|
## gesetzte Teil ist ein normales Objekt und gehört zum laufenden Gebäude (Gruppe).
|
||||||
## Gebäude kopieren/löschen/auswählen läuft über die Knöpfe unten — alles Undo-fähig.
|
## Gebäude kopieren/löschen/auswählen läuft über die Knöpfe unten — alles Undo-fähig.
|
||||||
|
|
||||||
## Pack-Module, die zum Gebäudebau passen (IDs stabil, siehe Bibliothek).
|
|
||||||
const PACK_MODULE := [
|
|
||||||
{"label": "Container", "oids": ["container_001", "container_002", "container_003", "container_004",
|
|
||||||
"container_005", "container_006", "container_007", "container_008"]},
|
|
||||||
{"label": "Hangar-Tore", "oids": ["hangar_door_001", "hangar_door_002", "hangar_door_003"]},
|
|
||||||
{"label": "Betonsperren", "oids": ["barrier_001", "barrier_002", "barrier_003", "barrier_004"]},
|
|
||||||
]
|
|
||||||
|
|
||||||
var bau_buttons: Dictionary = {} # oid → Button
|
var bau_buttons: Dictionary = {} # oid → Button
|
||||||
var info_label: Label
|
var info_label: Label
|
||||||
var _group: ButtonGroup
|
var _group: ButtonGroup
|
||||||
|
|
@ -32,15 +25,7 @@ func build() -> void:
|
||||||
add_child(_muted_label("Wähle ein Teil und klicke in die Karte. Wände rasten an die nächste Zellkante, Böden und Dächer in die Zellmitte (Raster 2 m). Klick auf die Oberkante einer Wand stapelt das nächste Geschoss, Klick auf die Seite ersetzt das Teil (z. B. Wand → Tür). R dreht, Esc beendet."))
|
add_child(_muted_label("Wähle ein Teil und klicke in die Karte. Wände rasten an die nächste Zellkante, Böden und Dächer in die Zellmitte (Raster 2 m). Klick auf die Oberkante einer Wand stapelt das nächste Geschoss, Klick auf die Seite ersetzt das Teil (z. B. Wand → Tür). R dreht, Esc beendet."))
|
||||||
for gruppe in BauKatalog.GRUPPEN:
|
for gruppe in BauKatalog.GRUPPEN:
|
||||||
add_child(_field_label(str(gruppe["label"])))
|
add_child(_field_label(str(gruppe["label"])))
|
||||||
add_child(_knopf_raster(gruppe["oids"] as Array, true))
|
add_child(_knopf_raster(gruppe["oids"] as Array))
|
||||||
var pack := _pack_gruppen()
|
|
||||||
if not pack.is_empty():
|
|
||||||
add_child(_separator())
|
|
||||||
add_child(_header_label("Pack-Module"))
|
|
||||||
add_child(_muted_label("Fertige Teile aus dem Military-Desert-Pack — normale Objekte, werden auf den Boden gesetzt."))
|
|
||||||
for pg in pack:
|
|
||||||
add_child(_field_label(str(pg["label"])))
|
|
||||||
add_child(_knopf_raster(pg["oids"] as Array, false))
|
|
||||||
add_child(_separator())
|
add_child(_separator())
|
||||||
add_child(_header_label("Gebäude"))
|
add_child(_header_label("Gebäude"))
|
||||||
info_label = _muted_label("")
|
info_label = _muted_label("")
|
||||||
|
|
@ -54,22 +39,10 @@ func build() -> void:
|
||||||
add_child(_aktion("🗑 Gebäude löschen", "Löscht alle Teile des Gebäudes (Strg+Z holt es zurück).",
|
add_child(_aktion("🗑 Gebäude löschen", "Löscht alle Teile des Gebäudes (Strg+Z holt es zurück).",
|
||||||
func() -> void: main.call("_bau_gebaeude_loeschen")))
|
func() -> void: main.call("_bau_gebaeude_loeschen")))
|
||||||
refresh_info()
|
refresh_info()
|
||||||
|
wrap_in_scroll()
|
||||||
|
|
||||||
|
|
||||||
func _pack_gruppen() -> Array:
|
func _knopf_raster(oids: Array) -> GridContainer:
|
||||||
var lib: Dictionary = main.manifest.get("objects", {}) if main != null else {}
|
|
||||||
var out: Array = []
|
|
||||||
for pg in PACK_MODULE:
|
|
||||||
var oids: Array = []
|
|
||||||
for oid in (pg["oids"] as Array):
|
|
||||||
if lib.has(oid):
|
|
||||||
oids.append(oid)
|
|
||||||
if not oids.is_empty():
|
|
||||||
out.append({"label": pg["label"], "oids": oids})
|
|
||||||
return out
|
|
||||||
|
|
||||||
|
|
||||||
func _knopf_raster(oids: Array, bau: bool) -> GridContainer:
|
|
||||||
var grid := GridContainer.new()
|
var grid := GridContainer.new()
|
||||||
grid.columns = 2
|
grid.columns = 2
|
||||||
grid.add_theme_constant_override("h_separation", UIScale.px(8))
|
grid.add_theme_constant_override("h_separation", UIScale.px(8))
|
||||||
|
|
@ -82,14 +55,9 @@ func _knopf_raster(oids: Array, bau: bool) -> GridContainer:
|
||||||
btn.custom_minimum_size = UIScale.v2(148, 44)
|
btn.custom_minimum_size = UIScale.v2(148, 44)
|
||||||
btn.clip_text = true
|
btn.clip_text = true
|
||||||
var poly := int((lib.get(oid, {}) as Dictionary).get("poly", 0))
|
var poly := int((lib.get(oid, {}) as Dictionary).get("poly", 0))
|
||||||
if bau:
|
btn.text = BauKatalog.label(oid)
|
||||||
btn.text = BauKatalog.label(oid)
|
btn.tooltip_text = "%s — %s\n%d Dreiecke" % [BauKatalog.label(oid),
|
||||||
btn.tooltip_text = "%s — %s\n%d Dreiecke" % [BauKatalog.label(oid),
|
str((BauKatalog.KINDS[oid] as Dictionary).get("info", "")), poly]
|
||||||
str((BauKatalog.KINDS[oid] as Dictionary).get("info", "")), poly]
|
|
||||||
else:
|
|
||||||
var name_de := str((lib.get(oid, {}) as Dictionary).get("name_de", oid))
|
|
||||||
btn.text = name_de
|
|
||||||
btn.tooltip_text = "%s — %d Dreiecke" % [name_de, poly]
|
|
||||||
btn.pressed.connect(_on_teil_gedrueckt.bind(str(oid), btn))
|
btn.pressed.connect(_on_teil_gedrueckt.bind(str(oid), btn))
|
||||||
bau_buttons[str(oid)] = btn
|
bau_buttons[str(oid)] = btn
|
||||||
grid.add_child(btn)
|
grid.add_child(btn)
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ var spawn_delete_btn: Button
|
||||||
|
|
||||||
|
|
||||||
static func spec() -> Dictionary:
|
static func spec() -> Dictionary:
|
||||||
return {"titel": "Prüfen", "gruppe": "rechts", "order": 50}
|
return {"titel": "Spawn-Zonen", "gruppe": "map", "order": 10}
|
||||||
|
|
||||||
|
|
||||||
func panel_title() -> String:
|
func panel_title() -> String:
|
||||||
|
|
@ -27,6 +27,7 @@ func panel_title() -> String:
|
||||||
## Live-Status IMMER sichtbar + Werkzeug-Button + Bedien-Panel (Team/Radius/
|
## Live-Status IMMER sichtbar + Werkzeug-Button + Bedien-Panel (Team/Radius/
|
||||||
## Auswahl/Löschen). Ein/Aus des Werkzeugs bleibt beim Koordinator (main.gd).
|
## Auswahl/Löschen). Ein/Aus des Werkzeugs bleibt beim Koordinator (main.gd).
|
||||||
func build() -> void:
|
func build() -> void:
|
||||||
|
add_child(_header_label("Spawn-Zonen"))
|
||||||
# --- Spawn-Zonen (Karte t_c4162c5d): Live-Status IMMER sichtbar ---
|
# --- Spawn-Zonen (Karte t_c4162c5d): Live-Status IMMER sichtbar ---
|
||||||
spawn_status = Label.new()
|
spawn_status = Label.new()
|
||||||
spawn_status.text = ""
|
spawn_status.text = ""
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,27 @@ func panel_title() -> String:
|
||||||
return "Panel"
|
return "Panel"
|
||||||
|
|
||||||
|
|
||||||
|
## Lange Tabs (Map, Bauen): den ganzen Inhalt in einen senkrechten Scroll-Bereich
|
||||||
|
## legen, damit die rechte Spalte nicht mit dem Inhalt wächst. Rückgabe = die
|
||||||
|
## innere Box (dort hängen spätere Abschnitte an).
|
||||||
|
func wrap_in_scroll() -> VBoxContainer:
|
||||||
|
var scroll := ScrollContainer.new()
|
||||||
|
scroll.name = "%sScroll" % name
|
||||||
|
scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||||
|
scroll.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
|
scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
|
||||||
|
var inner := VBoxContainer.new()
|
||||||
|
inner.name = "%sInhalt" % name
|
||||||
|
inner.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
|
inner.add_theme_constant_override("separation", UIScale.px(8))
|
||||||
|
for child in get_children():
|
||||||
|
remove_child(child)
|
||||||
|
inner.add_child(child)
|
||||||
|
scroll.add_child(inner)
|
||||||
|
add_child(scroll)
|
||||||
|
return inner
|
||||||
|
|
||||||
|
|
||||||
# --- Farb-Zugriff NUR über das Theme (Karte t_2336c58e/U7) ------------------
|
# --- Farb-Zugriff NUR über das Theme (Karte t_2336c58e/U7) ------------------
|
||||||
## Farben kommen ausschließlich aus theme/editor_theme.tres (Abschnitt
|
## Farben kommen ausschließlich aus theme/editor_theme.tres (Abschnitt
|
||||||
## Editor/colors/…) — keine Farbwerte mehr im Panel-Code. Der Wert liest
|
## Editor/colors/…) — keine Farbwerte mehr im Panel-Code. Der Wert liest
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ var sky_preview: TextureRect # Vorschau der gewählten Skybox (obere Panora
|
||||||
var prop_water: HSlider
|
var prop_water: HSlider
|
||||||
var terrain_info: Label
|
var terrain_info: Label
|
||||||
|
|
||||||
|
var _sections: VBoxContainer # Inhalt des Map-Tabs (im Scroll-Bereich)
|
||||||
var _loading_ui_values := false # verhindert dirty beim Auffüllen der Felder
|
var _loading_ui_values := false # verhindert dirty beim Auffüllen der Felder
|
||||||
|
|
||||||
## Karte t_28fdef03: Slider-Stellung für „Kein Wasser“ (Meta-Vertrag für
|
## Karte t_28fdef03: Slider-Stellung für „Kein Wasser“ (Meta-Vertrag für
|
||||||
|
|
@ -110,6 +111,13 @@ func build() -> void:
|
||||||
prop_weather.item_selected.connect(_on_weather_changed)
|
prop_weather.item_selected.connect(_on_weather_changed)
|
||||||
prop_sky.item_selected.connect(_on_sky_changed)
|
prop_sky.item_selected.connect(_on_sky_changed)
|
||||||
prop_water.value_changed.connect(_on_water_changed)
|
prop_water.value_changed.connect(_on_water_changed)
|
||||||
|
_sections = wrap_in_scroll()
|
||||||
|
|
||||||
|
|
||||||
|
## Hier hängen die Abschnitte der Registry-Gruppe "map" (Spawn-Zonen, Test-Start,
|
||||||
|
## Symmetrie) unter den Map-Eigenschaften.
|
||||||
|
func sections_host() -> Control:
|
||||||
|
return _sections
|
||||||
|
|
||||||
|
|
||||||
func _on_prop_changed(text: String, field: String) -> void:
|
func _on_prop_changed(text: String, field: String) -> void:
|
||||||
|
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
extends "res://src/ui/panels/editor_panel.gd"
|
|
||||||
## Objekt-Werkzeuge (aus main.gd ausgezogen, Karte t_eaccbfed U1 — Logik
|
|
||||||
## unverändert): „Alles auf den Boden setzen" + Raster-Snap-Schalter. Vor U1
|
|
||||||
## wohnten diese beiden Widgets in der linken Spalte unter der Bibliothek;
|
|
||||||
## sie sind als eigener Registry-Bereich (Gruppe "unten") erhalten geblieben,
|
|
||||||
## damit nichts verschwindet.
|
|
||||||
|
|
||||||
var snap_btn: Button
|
|
||||||
var grid_check: CheckButton
|
|
||||||
|
|
||||||
|
|
||||||
static func spec() -> Dictionary:
|
|
||||||
return {"titel": "Objekt-Werkzeuge", "gruppe": "unten", "order": 20}
|
|
||||||
|
|
||||||
|
|
||||||
func panel_title() -> String:
|
|
||||||
return "Objekt-Werkzeuge"
|
|
||||||
|
|
||||||
|
|
||||||
func build() -> void:
|
|
||||||
snap_btn = Button.new()
|
|
||||||
snap_btn.text = "⚡ Alles auf den Boden setzen"
|
|
||||||
snap_btn.custom_minimum_size = UIScale.v2(0, 44)
|
|
||||||
snap_btn.tooltip_text = "Setzt ALLE Objekte auf die Terrain-Höhe — z. B. nach dem Terrain-Formen, wenn etwas in der Luft hängt"
|
|
||||||
snap_btn.pressed.connect(func() -> void: main.call("_on_snap_to_ground"))
|
|
||||||
add_child(snap_btn)
|
|
||||||
|
|
||||||
grid_check = CheckButton.new()
|
|
||||||
grid_check.text = "Raster-Snap (1 m)"
|
|
||||||
grid_check.custom_minimum_size = UIScale.v2(0, 40)
|
|
||||||
grid_check.tooltip_text = "Neue und verschobene Objekte rasten auf ein 1-Meter-Raster ein"
|
|
||||||
grid_check.toggled.connect(func(pressed: bool) -> void: main.call("_on_grid_snap_changed", pressed))
|
|
||||||
add_child(grid_check)
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
uid://cs4xao3sf5y7n
|
|
||||||
|
|
@ -12,7 +12,7 @@ extends RefCounted
|
||||||
## in derselben Datei (gleiche Falle wie früher bei match_hud.gd).
|
## in derselben Datei (gleiche Falle wie früher bei match_hud.gd).
|
||||||
##
|
##
|
||||||
## Struktur je Eintrag: titel (Tab-/Überschrift), gruppe ("rechts" = rechtes
|
## Struktur je Eintrag: titel (Tab-/Überschrift), gruppe ("rechts" = rechtes
|
||||||
## TabControl, "links" = linke Spalte, "unten" = Assets-Bereich, "toolbar" /
|
## TabControl, "links" = linke Spalte, "unten" = Assets-Bereich, "map" = Abschnitte im Map-Tab, "dialog" = Einstellungen-Fenster, "toolbar" /
|
||||||
## "status" = Leisten-Aktionen), order (Sortier-Schlüssel), skript (preload-Pfad
|
## "status" = Leisten-Aktionen), order (Sortier-Schlüssel), skript (preload-Pfad
|
||||||
## des Panel-Moduls). Ein Bereich erscheint GENAU EINMAL im Baum (test_ui_layout
|
## des Panel-Moduls). Ein Bereich erscheint GENAU EINMAL im Baum (test_ui_layout
|
||||||
## prüft das gegen Doppel-Namen).
|
## prüft das gegen Doppel-Namen).
|
||||||
|
|
@ -28,8 +28,6 @@ const PANELS: Array = [
|
||||||
# --- rechts: Tab-Gruppe (Unity-Inspector-Spalte) ---
|
# --- rechts: Tab-Gruppe (Unity-Inspector-Spalte) ---
|
||||||
{"id": "inspektor", "titel": "Inspektor", "gruppe": "rechts", "order": 10,
|
{"id": "inspektor", "titel": "Inspektor", "gruppe": "rechts", "order": 10,
|
||||||
"skript": "res://src/ui/inspector_panel.gd"},
|
"skript": "res://src/ui/inspector_panel.gd"},
|
||||||
{"id": "auswahl", "titel": "Auswahl", "gruppe": "rechts", "order": 20,
|
|
||||||
"skript": "res://src/ui/panels/selection_panel.gd"},
|
|
||||||
{"id": "map", "titel": "Map", "gruppe": "rechts", "order": 30,
|
{"id": "map", "titel": "Map", "gruppe": "rechts", "order": 30,
|
||||||
"skript": "res://src/ui/panels/map_panel.gd"},
|
"skript": "res://src/ui/panels/map_panel.gd"},
|
||||||
{"id": "terrain", "titel": "Terrain", "gruppe": "rechts", "order": 40,
|
{"id": "terrain", "titel": "Terrain", "gruppe": "rechts", "order": 40,
|
||||||
|
|
@ -39,28 +37,26 @@ const PANELS: Array = [
|
||||||
"skript": "res://src/ui/panels/malen_panel.gd"},
|
"skript": "res://src/ui/panels/malen_panel.gd"},
|
||||||
{"id": "bauen", "titel": "Bauen", "gruppe": "rechts", "order": 47,
|
{"id": "bauen", "titel": "Bauen", "gruppe": "rechts", "order": 47,
|
||||||
"skript": "res://src/ui/panels/bauen_panel.gd"},
|
"skript": "res://src/ui/panels/bauen_panel.gd"},
|
||||||
{"id": "check", "titel": "Prüfen", "gruppe": "rechts", "order": 50,
|
# --- map: Abschnitte UNTER den Map-Eigenschaften (Tab „Map“) ---
|
||||||
|
# 25.10. (Test-Befund): Prüfen/Test-Start/Symmetrie sind keine eigenen Tabs
|
||||||
|
# mehr. Spawn-Zonen, Test-Start-Info und Symmetrie wohnen im Map-Tab.
|
||||||
|
{"id": "check", "titel": "Spawn-Zonen", "gruppe": "map", "order": 10,
|
||||||
"skript": "res://src/ui/panels/check_panel.gd"},
|
"skript": "res://src/ui/panels/check_panel.gd"},
|
||||||
{"id": "teststart", "titel": "Test-Start", "gruppe": "rechts", "order": 60,
|
{"id": "teststart", "titel": "Test-Start", "gruppe": "map", "order": 20,
|
||||||
"skript": "res://src/ui/panels/test_start_panel.gd"},
|
"skript": "res://src/ui/panels/test_start_panel.gd"},
|
||||||
# T3 Komfort (Karte t_aaff5dcc): Maus-Empfindlichkeit u. a. — Registry-Weg
|
{"id": "symmetrie", "titel": "Symmetrie", "gruppe": "map", "order": 30,
|
||||||
# statt main.gd (Operator-Vorgabe 20.10.).
|
|
||||||
{"id": "einstellungen", "titel": "Einstellungen", "gruppe": "rechts", "order": 70,
|
|
||||||
"skript": "res://src/ui/panels/settings_panel.gd"},
|
|
||||||
# M5 (Karte t_60014669): Ein-Klick „Karte symmetrisch machen“ + Check mit
|
|
||||||
# Klartext-Bericht — ebenfalls Registry-Weg, main.gd bleibt unangetastet.
|
|
||||||
{"id": "symmetrie", "titel": "Symmetrie", "gruppe": "rechts", "order": 80,
|
|
||||||
"skript": "res://src/ui/panels/symmetry_panel.gd"},
|
"skript": "res://src/ui/panels/symmetry_panel.gd"},
|
||||||
|
# --- dialog: Inhalt des Einstellungen-Fensters (Knopf in der Kopfleiste) ---
|
||||||
|
# T3 Komfort (Karte t_aaff5dcc) + 25.10.: Gitter, Höhenlinien, Symmetrie-
|
||||||
|
# Modus, UI-Größe, Raster-Snap, Gizmo-Schritt, Karte prüfen.
|
||||||
|
{"id": "einstellungen", "titel": "Einstellungen", "gruppe": "dialog", "order": 10,
|
||||||
|
"skript": "res://src/ui/panels/settings_panel.gd"},
|
||||||
# --- links: Vorbereitet für U2 (Szenen-Baum); Bibliothek ist UNTEN ---
|
# --- links: Vorbereitet für U2 (Szenen-Baum); Bibliothek ist UNTEN ---
|
||||||
{"id": "szene", "titel": "Map-Inhalt", "gruppe": "links", "order": 10,
|
{"id": "szene", "titel": "Map-Inhalt", "gruppe": "links", "order": 10,
|
||||||
"skript": "res://src/ui/panels/scene_tree_panel.gd"},
|
"skript": "res://src/ui/panels/scene_tree_panel.gd"},
|
||||||
# --- unten: Objekt-Bibliothek / Assets (Unity-Projekt-Fenster-Platz) ---
|
# --- unten: Objekt-Bibliothek / Assets (Unity-Projekt-Fenster-Platz) ---
|
||||||
{"id": "assets", "titel": "Objekt-Bibliothek", "gruppe": "unten", "order": 10,
|
{"id": "assets", "titel": "Objekt-Bibliothek", "gruppe": "unten", "order": 10,
|
||||||
"skript": "res://src/ui/panels/assets_panel.gd"},
|
"skript": "res://src/ui/panels/assets_panel.gd"},
|
||||||
# Objekt-Werkzeuge (Boden-Setzen/Raster-Snap, vor U1 links) — erhalten
|
|
||||||
# geblieben als eigener Tab unten, damit nichts verschwindet.
|
|
||||||
{"id": "werkzeuge", "titel": "Objekt-Werkzeuge", "gruppe": "unten", "order": 20,
|
|
||||||
"skript": "res://src/ui/panels/object_tools_panel.gd"},
|
|
||||||
]
|
]
|
||||||
|
|
||||||
## Leisten-Aktionen (Toolbar-Buttons/Menüs — KEINE Panels, melden sich ditto
|
## Leisten-Aktionen (Toolbar-Buttons/Menüs — KEINE Panels, melden sich ditto
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,10 @@ extends "res://src/ui/panels/editor_panel.gd"
|
||||||
## "links"), Zustand/Signale wie vorher.
|
## "links"), Zustand/Signale wie vorher.
|
||||||
|
|
||||||
var content_tree: Tree
|
var content_tree: Tree
|
||||||
|
var group_btn: Button
|
||||||
|
var ungroup_btn: Button
|
||||||
var _tree_select_lock := false # verhindert Rückkopplung Auswahl ↔ Baum
|
var _tree_select_lock := false # verhindert Rückkopplung Auswahl ↔ Baum
|
||||||
|
var _tree_select_pending := false # multi_selected gesammelt, Auswertung steht aus
|
||||||
var _obj_index_map: Dictionary = {} # obj_index -> TreeItem (schnelle Zwei-Wege-Kopplung)
|
var _obj_index_map: Dictionary = {} # obj_index -> TreeItem (schnelle Zwei-Wege-Kopplung)
|
||||||
|
|
||||||
# U3 (Karte t_a4bf9b8c): Kontextmenü, Umbenennen, Lösch-Rückfrage, Drag&Drop.
|
# U3 (Karte t_a4bf9b8c): Kontextmenü, Umbenennen, Lösch-Rückfrage, Drag&Drop.
|
||||||
|
|
@ -30,9 +33,35 @@ func build() -> void:
|
||||||
content_tree.allow_reselect = true
|
content_tree.allow_reselect = true
|
||||||
content_tree.select_mode = Tree.SELECT_MULTI # Strg+Klick im Baum = Mehrfachauswahl (t_c46d31c2)
|
content_tree.select_mode = Tree.SELECT_MULTI # Strg+Klick im Baum = Mehrfachauswahl (t_c46d31c2)
|
||||||
content_tree.hide_root = true
|
content_tree.hide_root = true
|
||||||
|
# FIX 25.10. (Test-Befund „Liste anklicken wählt in der Szene nichts aus“):
|
||||||
|
# Im Modus SELECT_MULTI feuert Godot NIE item_selected, sondern nur
|
||||||
|
# multi_selected (pro Zeile). Beide anschließen; das Sammeln übernimmt der
|
||||||
|
# Aufrufer-Merker, damit ein Klick mit mehreren Signalen nur EINMAL wirkt.
|
||||||
content_tree.item_selected.connect(_on_tree_selected)
|
content_tree.item_selected.connect(_on_tree_selected)
|
||||||
|
content_tree.multi_selected.connect(_on_tree_multi_selected)
|
||||||
add_child(content_tree)
|
add_child(content_tree)
|
||||||
|
|
||||||
|
# Gruppen verwalten (25.10.: der Tab „Auswahl“ ist weg): mehrere Objekte in der
|
||||||
|
# Liste (Strg+Klick) oder in der Szene markieren, dann hier gruppieren/lösen.
|
||||||
|
# Rechtsklick auf eine Zeile bietet dieselben Aktionen.
|
||||||
|
var group_row := HBoxContainer.new()
|
||||||
|
group_row.add_theme_constant_override("separation", UIScale.px(8))
|
||||||
|
add_child(group_row)
|
||||||
|
group_btn = Button.new()
|
||||||
|
group_btn.text = "🔗 Gruppieren"
|
||||||
|
group_btn.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
|
group_btn.custom_minimum_size = UIScale.v2(0, 40)
|
||||||
|
group_btn.tooltip_text = "Markierte Objekte zu einer Gruppe verbinden (Strg+G) — sie ziehen und drehen dann gemeinsam. Mehrere markieren: Strg+Klick in der Liste oder Rahmen in der Szene."
|
||||||
|
group_btn.pressed.connect(func() -> void: main.interaction.group_selection())
|
||||||
|
group_row.add_child(group_btn)
|
||||||
|
ungroup_btn = Button.new()
|
||||||
|
ungroup_btn.text = "⛓ Gruppe lösen"
|
||||||
|
ungroup_btn.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
|
ungroup_btn.custom_minimum_size = UIScale.v2(0, 40)
|
||||||
|
ungroup_btn.tooltip_text = "Die Gruppe der Auswahl auflösen (Strg+Umschalt+G) — die Objekte bleiben, nur die Bindung löst sich"
|
||||||
|
ungroup_btn.pressed.connect(func() -> void: main.interaction.ungroup_selection())
|
||||||
|
group_row.add_child(ungroup_btn)
|
||||||
|
|
||||||
# U3 (Karte t_a4bf9b8c): Rechtsklick-Kontextmenü + Drag&Drop für Gruppen.
|
# U3 (Karte t_a4bf9b8c): Rechtsklick-Kontextmenü + Drag&Drop für Gruppen.
|
||||||
content_tree.gui_input.connect(_on_tree_gui_input)
|
content_tree.gui_input.connect(_on_tree_gui_input)
|
||||||
# Tree.set_drag_forwarding: die drei Virtuals (_get_drag_data/_can_drop_data/
|
# Tree.set_drag_forwarding: die drei Virtuals (_get_drag_data/_can_drop_data/
|
||||||
|
|
@ -61,6 +90,20 @@ func build() -> void:
|
||||||
add_child(_separator())
|
add_child(_separator())
|
||||||
|
|
||||||
|
|
||||||
|
## multi_selected kommt je Zeile (auch beim Abwählen): die Auswahl EINMAL pro
|
||||||
|
## Frame auswerten, nicht pro Signal.
|
||||||
|
func _on_tree_multi_selected(_item: TreeItem, _column: int, _selected: bool) -> void:
|
||||||
|
if _tree_select_lock or _tree_select_pending:
|
||||||
|
return
|
||||||
|
_tree_select_pending = true
|
||||||
|
_apply_tree_selection.call_deferred()
|
||||||
|
|
||||||
|
|
||||||
|
func _apply_tree_selection() -> void:
|
||||||
|
_tree_select_pending = false
|
||||||
|
_on_tree_selected()
|
||||||
|
|
||||||
|
|
||||||
## Der Baum ruft zurück, wenn der Nutzer dort ein Objekt auswählt.
|
## Der Baum ruft zurück, wenn der Nutzer dort ein Objekt auswählt.
|
||||||
## SELECT_MULTI (Karte t_c46d31c2): einfacher Klick = Ersatz-Auswahl,
|
## SELECT_MULTI (Karte t_c46d31c2): einfacher Klick = Ersatz-Auswahl,
|
||||||
## Strg+Klick addiert/entfernt nativ — ALLE markierten Objekt-Zeilen spiegeln
|
## Strg+Klick addiert/entfernt nativ — ALLE markierten Objekt-Zeilen spiegeln
|
||||||
|
|
|
||||||
|
|
@ -1,139 +1,21 @@
|
||||||
extends "res://src/ui/panels/editor_panel.gd"
|
extends Node
|
||||||
## Auswahl-Panel (U1, Karte t_eaccbfed — aus main.gd ausgezogen, Logik
|
## Auswahl-Logik (U1, Karte t_eaccbfed — aus main.gd ausgezogen). Seit 25.10.
|
||||||
## UNVERÄNDERT von dort übernommen): Buttons Drehen/Löschen/Duplizieren/
|
## KEIN Tab mehr (Test-Befund): Drehen/Anheben/Duplizieren/Löschen laufen in der
|
||||||
## Spiegeln/Anheben/Absenken + Gruppen-Buttons + Gizmo-Schrittweite.
|
## 3D-Ansicht (Werkzeugleiste, Gizmo, Tasten), Gruppen im Map-Inhalt links, die
|
||||||
|
## Gizmo-Schrittweite im Einstellungen-Fenster. Übrig bleibt die Synchronisation:
|
||||||
|
## Inspektor nachziehen, Gizmo-Schrittweite-Feld folgen, Statuszeile zur frischen
|
||||||
|
## Auswahl (Klartext + Poly-Budget-Warnung).
|
||||||
##
|
##
|
||||||
## Verdrahtung: main.gd ruft sync_selection() an allen Auswahl-Wegen
|
## Verdrahtung: main.gd ruft sync_selection() an allen Auswahl-Wegen
|
||||||
## (vorher: _sync_selection_panel) und selection_status(idx) bei frischer
|
## (vorher: _sync_selection_panel) und selection_status(idx) bei frischer
|
||||||
## Auswahl (vorher: _selection_status). Alle Objekt-Operationen laufen über
|
## Auswahl (vorher: _selection_status). Alle Objekt-Operationen laufen über
|
||||||
## main.interaction — der Vertrag zu interaction.gd bleibt identisch.
|
## main.interaction — der Vertrag zu interaction.gd bleibt identisch.
|
||||||
|
|
||||||
var sel_label: Label
|
var main # src/ui/main.gd (script-erweitert → untypisiert)
|
||||||
var sel_rotate_btn: Button
|
|
||||||
var sel_delete_btn: Button
|
|
||||||
var sel_dup_btn: Button
|
|
||||||
var sel_mirror_x_btn: Button
|
|
||||||
var sel_mirror_z_btn: Button
|
|
||||||
var sel_raise_btn: Button
|
|
||||||
var sel_lower_btn: Button
|
|
||||||
var sel_group_btn: Button
|
|
||||||
var sel_ungroup_btn: Button
|
|
||||||
var gizmo_step_box: SpinBox
|
|
||||||
|
|
||||||
|
|
||||||
static func spec() -> Dictionary:
|
func setup(p_main) -> void:
|
||||||
return {"titel": "Auswahl", "gruppe": "rechts", "order": 20}
|
main = p_main
|
||||||
|
|
||||||
|
|
||||||
func panel_title() -> String:
|
|
||||||
return "Auswahl"
|
|
||||||
|
|
||||||
|
|
||||||
func build() -> void:
|
|
||||||
# --- Auswahl (Anfassen/Greifen) — 1:1 aus der alten _build_right_panel ---
|
|
||||||
sel_label = Label.new()
|
|
||||||
sel_label.text = "Nichts ausgewählt.\nKlicke im Viewport ein Objekt an."
|
|
||||||
sel_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
|
||||||
add_child(sel_label)
|
|
||||||
var sel_btns := HBoxContainer.new()
|
|
||||||
sel_btns.add_theme_constant_override("separation", UIScale.px(8))
|
|
||||||
add_child(sel_btns)
|
|
||||||
sel_rotate_btn = Button.new()
|
|
||||||
sel_rotate_btn.text = "Drehen (R)"
|
|
||||||
sel_rotate_btn.custom_minimum_size = UIScale.v2(140, 40)
|
|
||||||
sel_rotate_btn.tooltip_text = "Ausgewähltes Objekt 15° drehen (auch: Mausrad)"
|
|
||||||
sel_rotate_btn.pressed.connect(func() -> void: main.interaction.rotate_selected(15.0))
|
|
||||||
sel_btns.add_child(sel_rotate_btn)
|
|
||||||
sel_delete_btn = Button.new()
|
|
||||||
sel_delete_btn.text = "Löschen (Entf)"
|
|
||||||
sel_delete_btn.custom_minimum_size = UIScale.v2(140, 40)
|
|
||||||
sel_delete_btn.tooltip_text = "Ausgewähltes Objekt löschen (Strg+Z holt es zurück)"
|
|
||||||
sel_delete_btn.pressed.connect(main.interaction.delete_selected)
|
|
||||||
sel_btns.add_child(sel_delete_btn)
|
|
||||||
|
|
||||||
# Objekt-Ops (Karte t_7567a06f): Duplizieren · Spiegeln · Anheben/Absenken.
|
|
||||||
var dup_row := HBoxContainer.new()
|
|
||||||
dup_row.add_theme_constant_override("separation", UIScale.px(8))
|
|
||||||
add_child(dup_row)
|
|
||||||
sel_dup_btn = _sel_op_button("⧉ Duplizieren (Strg+D)",
|
|
||||||
"Legt eine Kopie daneben — leicht versetzt, direkt angewählt. Strg+D geht auch.",
|
|
||||||
func() -> void: main.interaction.duplicate_selected())
|
|
||||||
dup_row.add_child(sel_dup_btn)
|
|
||||||
|
|
||||||
var mirror_row := HBoxContainer.new()
|
|
||||||
mirror_row.add_theme_constant_override("separation", UIScale.px(8))
|
|
||||||
add_child(mirror_row)
|
|
||||||
sel_mirror_x_btn = _sel_op_button("⇄ Links↔Rechts",
|
|
||||||
"Spiegelt das Objekt an seiner Stelle (links↔rechts, X-Achse) — die Blickrichtung wird mitgespiegelt.",
|
|
||||||
func() -> void: main.interaction.mirror_selected("x"))
|
|
||||||
mirror_row.add_child(sel_mirror_x_btn)
|
|
||||||
sel_mirror_z_btn = _sel_op_button("⇅ Vor↔Zurück",
|
|
||||||
"Spiegelt das Objekt an seiner Stelle (vor↔zurück, Z-Achse) — die Blickrichtung wird mitgespiegelt.",
|
|
||||||
func() -> void: main.interaction.mirror_selected("z"))
|
|
||||||
mirror_row.add_child(sel_mirror_z_btn)
|
|
||||||
|
|
||||||
var lift_row := HBoxContainer.new()
|
|
||||||
lift_row.add_theme_constant_override("separation", UIScale.px(8))
|
|
||||||
add_child(lift_row)
|
|
||||||
sel_raise_btn = _sel_op_button("▲ Anheben (Bild↑)",
|
|
||||||
"Objekt 0,25 m anheben — es schwebt dann, auch wenn der Boden darunter anders ist. Bild↑ geht auch.",
|
|
||||||
func() -> void: main.interaction.lift_selected(ObjectOps.LIFT_STEP_M))
|
|
||||||
lift_row.add_child(sel_raise_btn)
|
|
||||||
sel_lower_btn = _sel_op_button("▼ Absenken (Bild↓)",
|
|
||||||
"Objekt 0,25 m absenken — stoppt am Boden (nichts wird in Hügel begraben). Bild↓ geht auch.",
|
|
||||||
func() -> void: main.interaction.lift_selected(-ObjectOps.LIFT_STEP_M))
|
|
||||||
lift_row.add_child(sel_lower_btn)
|
|
||||||
|
|
||||||
# Gruppen-Buttons (Karte t_f6f29d86): Mehrfachauswahl → Gruppe
|
|
||||||
var group_btns := HBoxContainer.new()
|
|
||||||
group_btns.add_theme_constant_override("separation", UIScale.px(8))
|
|
||||||
add_child(group_btns)
|
|
||||||
sel_group_btn = Button.new()
|
|
||||||
sel_group_btn.text = "🔗 Gruppieren"
|
|
||||||
sel_group_btn.custom_minimum_size = UIScale.v2(150, 40)
|
|
||||||
sel_group_btn.tooltip_text = "Ausgewählte Objekte zu einer Gruppe verbinden (Strg+G) — sie ziehen und drehen dann gemeinsam"
|
|
||||||
sel_group_btn.pressed.connect(func() -> void: main.interaction.group_selection())
|
|
||||||
group_btns.add_child(sel_group_btn)
|
|
||||||
sel_ungroup_btn = Button.new()
|
|
||||||
sel_ungroup_btn.text = "⛓ Gruppe lösen"
|
|
||||||
sel_ungroup_btn.custom_minimum_size = UIScale.v2(150, 40)
|
|
||||||
sel_ungroup_btn.tooltip_text = "Die Gruppe der Auswahl wieder auflösen (Strg+Umschalt+G) — Objekte bleiben, nur die Bindung löst sich"
|
|
||||||
sel_ungroup_btn.pressed.connect(func() -> void: main.interaction.ungroup_selection())
|
|
||||||
group_btns.add_child(sel_ungroup_btn)
|
|
||||||
|
|
||||||
# --- Gizmo-Einstellungen (Karte t_e0120dc2): Schrittweite der Achsen-
|
|
||||||
# Züge. Das Mausrad WÄHREND eines Gizmo-Zugs blättert durch die
|
|
||||||
# Voreinstellungen (fein/normal/grob); dieses Feld setzt sie exakt und
|
|
||||||
# überlebt UI-Rebuilds, weil der Wert in main.gizmo_step_m liegen bleibt.
|
|
||||||
add_child(_header_label("Gizmo-Einstellungen"))
|
|
||||||
var gizmo_row := HBoxContainer.new()
|
|
||||||
gizmo_row.add_theme_constant_override("separation", UIScale.px(8))
|
|
||||||
add_child(gizmo_row)
|
|
||||||
gizmo_row.add_child(_field_label("Schrittweite"))
|
|
||||||
gizmo_step_box = SpinBox.new()
|
|
||||||
gizmo_step_box.min_value = 0.01
|
|
||||||
gizmo_step_box.max_value = 10.0
|
|
||||||
gizmo_step_box.step = 0.01
|
|
||||||
gizmo_step_box.value = main.gizmo_step_m
|
|
||||||
gizmo_step_box.custom_minimum_size = UIScale.v2(110, 30)
|
|
||||||
gizmo_step_box.tooltip_text = "Wie weit bewegt sich das Objekt am Achsen-Gizmo pro Schritt (in Metern). Mausrad WÄHREND des Ziehens wechselt zwischen fein (0.1) / normal (0.5) / grob (2.0)."
|
|
||||||
gizmo_row.add_child(gizmo_step_box)
|
|
||||||
gizmo_step_box.value_changed.connect(func(v: float) -> void:
|
|
||||||
main.gizmo_step_m = v
|
|
||||||
if main.gizmo != null and is_instance_valid(main.gizmo):
|
|
||||||
main.gizmo.set_step_m(v)
|
|
||||||
)
|
|
||||||
add_child(_separator())
|
|
||||||
|
|
||||||
|
|
||||||
## Ein Auswahl-Button mit Klartext-Tipp (aus main.gd übernommen).
|
|
||||||
func _sel_op_button(text: String, tip: String, action: Callable) -> Button:
|
|
||||||
var btn := Button.new()
|
|
||||||
btn.text = text
|
|
||||||
btn.custom_minimum_size = UIScale.v2(140, 40)
|
|
||||||
btn.tooltip_text = tip
|
|
||||||
btn.pressed.connect(action)
|
|
||||||
return btn
|
|
||||||
|
|
||||||
|
|
||||||
## Objekt-Inspektor + Auswahl-Panel synchron halten (vorher
|
## Objekt-Inspektor + Auswahl-Panel synchron halten (vorher
|
||||||
|
|
@ -145,54 +27,9 @@ func sync_selection() -> void:
|
||||||
# Gizmo-Schrittweite-Feld mitziehen (Karte t_e0120dc2): Das Mausrad
|
# Gizmo-Schrittweite-Feld mitziehen (Karte t_e0120dc2): Das Mausrad
|
||||||
# WÄHREND eines Zugs kann sie im Gizmo ändern (cycle_step) — das Panel-
|
# WÄHREND eines Zugs kann sie im Gizmo ändern (cycle_step) — das Panel-
|
||||||
# Feld folgt ohne eigene value_changed-Rückkopplung (set_value_no_signal).
|
# Feld folgt ohne eigene value_changed-Rückkopplung (set_value_no_signal).
|
||||||
if gizmo_step_box != null and is_instance_valid(gizmo_step_box) \
|
var step_box: SpinBox = main.gizmo_step_box
|
||||||
and not gizmo_step_box.has_focus():
|
if step_box != null and is_instance_valid(step_box) and not step_box.has_focus():
|
||||||
gizmo_step_box.set_value_no_signal(main.gizmo_step_m)
|
step_box.set_value_no_signal(main.gizmo_step_m)
|
||||||
var doc: Dictionary = main.doc
|
|
||||||
var objects: Array = doc.get("objects", [])
|
|
||||||
var sel: Array = main.interaction.selected_indices
|
|
||||||
var valid: Array = []
|
|
||||||
for i in sel:
|
|
||||||
if i is int and i >= 0 and i < objects.size():
|
|
||||||
valid.append(i)
|
|
||||||
var has_sel := not valid.is_empty()
|
|
||||||
for btn in [sel_rotate_btn, sel_delete_btn, sel_dup_btn, sel_mirror_x_btn,
|
|
||||||
sel_mirror_z_btn, sel_raise_btn, sel_lower_btn]:
|
|
||||||
btn.disabled = not has_sel
|
|
||||||
var has_group := false
|
|
||||||
for i in valid:
|
|
||||||
if GroupOps.group_id(objects[i]) != GroupOps.UNGROUPED:
|
|
||||||
has_group = true
|
|
||||||
break
|
|
||||||
sel_group_btn.disabled = valid.size() < 2
|
|
||||||
sel_ungroup_btn.disabled = not has_group
|
|
||||||
if not has_sel:
|
|
||||||
sel_label.text = "Nichts ausgewählt.\nKlicke im Viewport ein Objekt an."
|
|
||||||
return
|
|
||||||
if valid.size() == 1:
|
|
||||||
var idx: int = valid[0]
|
|
||||||
var obj: Dictionary = objects[idx]
|
|
||||||
var pos: Array = obj.get("pos", [0.0, 0.0, 0.0])
|
|
||||||
var lift := ObjectOps.lift_offset(doc, idx)
|
|
||||||
var lift_text := "am Boden" if absf(lift) < 0.02 else ("%+.2f m über dem Boden" % lift)
|
|
||||||
var group_line := ""
|
|
||||||
var gid := GroupOps.group_id(obj)
|
|
||||||
if gid != GroupOps.UNGROUPED:
|
|
||||||
group_line = "\nGruppe %d — zieht & dreht GEMEINSAM (Lösen: Button unten)" % (gid + 1)
|
|
||||||
sel_label.text = "%s (Objekt %d von %d)\nPosition: %.0f, %.0f m · Höhe: %s · Richtung: %.0f°%s\nZiehen: verschieben · R oder Mausrad: drehen (15°)\nBild↑/↓: anheben/absenken · Strg+D: duplizieren\nSpiegeln/Löschen: Buttons oben · Strg+Z: zurück" % [
|
|
||||||
main.object_label(str(obj.get("id", ""))), idx + 1, objects.size(),
|
|
||||||
float(pos[0]), float(pos[2]), lift_text, float(obj.get("rot_y_deg", 0.0)), group_line]
|
|
||||||
return
|
|
||||||
# Mehrfachauswahl: Liste aller gewählten Objekte (max. 8 Zeilen, dann „…“).
|
|
||||||
var names := PackedStringArray()
|
|
||||||
for i in valid:
|
|
||||||
if names.size() < 8:
|
|
||||||
names.append(main.object_label(str((objects[i] as Dictionary).get("id", "?"))))
|
|
||||||
var more := ""
|
|
||||||
if valid.size() > 8:
|
|
||||||
more = "\n… und %d weitere" % (valid.size() - 8)
|
|
||||||
sel_label.text = "%d Objekte ausgewählt:\n%s%s\nGemeinsam: ziehen (Gruppe wandert zusammen), drehen um die Mitte, Entf löscht alle." % [
|
|
||||||
valid.size(), " · ".join(names), more]
|
|
||||||
|
|
||||||
|
|
||||||
## Hilfs-Meldung zur frischen Auswahl (Klartext + Budget-Warnung + nächster
|
## Hilfs-Meldung zur frischen Auswahl (Klartext + Budget-Warnung + nächster
|
||||||
|
|
@ -202,9 +39,6 @@ func selection_status(idx: int) -> void:
|
||||||
# 3D-Klick, Rahmen-Ausziehen und Map-Inhalt-Baum.
|
# 3D-Klick, Rahmen-Ausziehen und Map-Inhalt-Baum.
|
||||||
if main.inspector != null:
|
if main.inspector != null:
|
||||||
main.inspector.call("refresh")
|
main.inspector.call("refresh")
|
||||||
# Fix (Vorbestand, beim Inspektor-Einbau gefunden): select_object/
|
|
||||||
# select_many füllten das alte „Auswahl“-Feld NIE — sel_label blieb auf
|
|
||||||
# „Nichts ausgewählt“ stehen, während die Statuszeile das Objekt nannte.
|
|
||||||
# sync_selection ändert bewusst KEINEN Statuszeilen-Text (Stummsync).
|
# sync_selection ändert bewusst KEINEN Statuszeilen-Text (Stummsync).
|
||||||
sync_selection()
|
sync_selection()
|
||||||
var doc: Dictionary = main.doc
|
var doc: Dictionary = main.doc
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
extends "res://src/ui/panels/editor_panel.gd"
|
extends "res://src/ui/panels/editor_panel.gd"
|
||||||
## Einstellungen-Bereich (rechte Spalte — Karte t_aaff5dcc, T3 Komfort).
|
## Einstellungen (Fenster „⚙ Einstellungen“ in der Kopfleiste — Karte
|
||||||
|
## t_aaff5dcc, T3 Komfort; 25.10. Test-Befund: Gitter, Höhenlinien, Symmetrie-
|
||||||
|
## Modus, UI-Größe, Raster-Snap, Gizmo-Schrittweite und „Karte prüfen“ ziehen
|
||||||
|
## aus Kopfleiste/Objekt-Werkzeugen hierher).
|
||||||
##
|
##
|
||||||
## Registry-Weg (Operator-Vorgabe 20.10.): main.gd bleibt unangetastet, das
|
## Registry-Weg (Operator-Vorgabe 20.10.): main.gd bleibt unangetastet, das
|
||||||
## Modul meldet sich nur über src/ui/panels/registry.gd an. Der ZUSTAND lebt
|
## Modul meldet sich nur über src/ui/panels/registry.gd an. Der ZUSTAND lebt
|
||||||
|
|
@ -12,9 +15,19 @@ var sens_box: SpinBox
|
||||||
## Eintrag wie Taste V (PlaytestSettings, persistiert).
|
## Eintrag wie Taste V (PlaytestSettings, persistiert).
|
||||||
var perspektive_box: OptionButton
|
var perspektive_box: OptionButton
|
||||||
|
|
||||||
|
# Ansicht / Platzieren (vorher Kopfleiste und Tab „Objekt-Werkzeuge“). main
|
||||||
|
# hält dieselben Namen als Referenzen (grid_btn, contours_btn, size_menu, …).
|
||||||
|
var grid_btn: CheckButton
|
||||||
|
var contours_btn: CheckButton
|
||||||
|
var size_menu: OptionButton
|
||||||
|
var sym_menu: OptionButton
|
||||||
|
var raster_snap_check: CheckButton
|
||||||
|
var gizmo_step_box: SpinBox
|
||||||
|
var check_btn: Button
|
||||||
|
|
||||||
|
|
||||||
static func spec() -> Dictionary:
|
static func spec() -> Dictionary:
|
||||||
return {"titel": "Einstellungen", "gruppe": "rechts", "order": 70}
|
return {"titel": "Einstellungen", "gruppe": "dialog", "order": 10}
|
||||||
|
|
||||||
|
|
||||||
func panel_title() -> String:
|
func panel_title() -> String:
|
||||||
|
|
@ -22,6 +35,72 @@ func panel_title() -> String:
|
||||||
|
|
||||||
|
|
||||||
func build() -> void:
|
func build() -> void:
|
||||||
|
add_child(_header_label("Ansicht"))
|
||||||
|
grid_btn = CheckButton.new()
|
||||||
|
grid_btn.text = "⊞ Maßstabsgitter (100-m-Raster)"
|
||||||
|
grid_btn.custom_minimum_size = UIScale.v2(0, 36)
|
||||||
|
grid_btn.tooltip_text = "Maßstabsgitter (100-m-Raster) im Viewport ein-/ausblenden. Über sichtbarem Terrain ist es standardmäßig aus."
|
||||||
|
grid_btn.toggled.connect(func(pressed: bool) -> void: main.call("_on_grid_toggled", pressed))
|
||||||
|
add_child(grid_btn)
|
||||||
|
|
||||||
|
contours_btn = CheckButton.new()
|
||||||
|
contours_btn.text = "≈ Höhenlinien fixieren"
|
||||||
|
contours_btn.custom_minimum_size = UIScale.v2(0, 36)
|
||||||
|
contours_btn.tooltip_text = "Höhenlinien (1 m, alle 5 m kräftiger) FIXIERT an/aus. Ohne Fixierung erscheinen sie nur im Terrain-Werkzeug (F1). Taste L schaltet ebenfalls. Bei flachem Terrain (< 2 m) sind sie automatisch aus."
|
||||||
|
contours_btn.toggled.connect(func(pressed: bool) -> void: main.call("_on_contours_toggled", pressed))
|
||||||
|
add_child(contours_btn)
|
||||||
|
|
||||||
|
add_child(_field_label("UI-Größe"))
|
||||||
|
size_menu = OptionButton.new()
|
||||||
|
for i in UIScale.step_count():
|
||||||
|
size_menu.add_item(UIScale.step_label(i), i)
|
||||||
|
size_menu.custom_minimum_size = UIScale.v2(0, 36)
|
||||||
|
size_menu.tooltip_text = "Größe der Oberfläche: Klein (wie bisher), Mittel (1,25-fach), Groß (1,5-fach). Wirkt sofort und bleibt gespeichert."
|
||||||
|
size_menu.item_selected.connect(func(index: int) -> void: main.call("_on_ui_size_selected", index))
|
||||||
|
add_child(size_menu)
|
||||||
|
|
||||||
|
add_child(_separator())
|
||||||
|
add_child(_header_label("Bauen & Platzieren"))
|
||||||
|
raster_snap_check = CheckButton.new()
|
||||||
|
raster_snap_check.text = "Raster-Snap (1 m)"
|
||||||
|
raster_snap_check.custom_minimum_size = UIScale.v2(0, 36)
|
||||||
|
raster_snap_check.tooltip_text = "Neue und verschobene Objekte rasten auf ein 1-Meter-Raster ein (Strg gedrückt halten geht immer)"
|
||||||
|
raster_snap_check.toggled.connect(func(pressed: bool) -> void: main.call("_on_grid_snap_changed", pressed))
|
||||||
|
add_child(raster_snap_check)
|
||||||
|
|
||||||
|
add_child(_field_label("Gizmo-Schrittweite (m)"))
|
||||||
|
gizmo_step_box = SpinBox.new()
|
||||||
|
gizmo_step_box.min_value = 0.01
|
||||||
|
gizmo_step_box.max_value = 10.0
|
||||||
|
gizmo_step_box.step = 0.01
|
||||||
|
gizmo_step_box.value = main.gizmo_step_m
|
||||||
|
gizmo_step_box.custom_minimum_size = UIScale.v2(0, 36)
|
||||||
|
gizmo_step_box.tooltip_text = "Wie weit bewegt sich das Objekt am Achsen-Gizmo pro Schritt (in Metern). Mausrad WÄHREND des Ziehens wechselt zwischen fein (0.1) / normal (0.5) / grob (2.0)."
|
||||||
|
gizmo_step_box.value_changed.connect(func(v: float) -> void:
|
||||||
|
main.gizmo_step_m = v
|
||||||
|
if main.gizmo != null and is_instance_valid(main.gizmo):
|
||||||
|
main.gizmo.set_step_m(v))
|
||||||
|
add_child(gizmo_step_box)
|
||||||
|
|
||||||
|
add_child(_separator())
|
||||||
|
add_child(_header_label("Symmetrie & Prüfen"))
|
||||||
|
add_child(_field_label("Symmetrie-Modus"))
|
||||||
|
sym_menu = OptionButton.new()
|
||||||
|
for i in SymmetryCore.MODE_LABELS.size():
|
||||||
|
sym_menu.add_item("Symmetrie: %s" % SymmetryCore.MODE_LABELS[i], i)
|
||||||
|
sym_menu.custom_minimum_size = UIScale.v2(0, 36)
|
||||||
|
sym_menu.tooltip_text = "Gespiegeltes Bauen: Änderungen werden automatisch auf die andere Kartenseite übertragen. Aus / Punkt (180° um die Kartenmitte) / Achse X / Achse Z. Standard: Punkt."
|
||||||
|
sym_menu.item_selected.connect(func(index: int) -> void: main.call("_on_sym_mode_selected", index))
|
||||||
|
add_child(sym_menu)
|
||||||
|
|
||||||
|
check_btn = Button.new()
|
||||||
|
check_btn.text = "✅ Karte prüfen"
|
||||||
|
check_btn.custom_minimum_size = UIScale.v2(0, 40)
|
||||||
|
check_btn.tooltip_text = "Prüft, ob die Map bereit für den Export ist — sagt VORHER, was fehlt"
|
||||||
|
check_btn.pressed.connect(func() -> void: main.call("_on_check_map"))
|
||||||
|
add_child(check_btn)
|
||||||
|
|
||||||
|
add_child(_separator())
|
||||||
add_child(_header_label("Testlauf-Einstellungen"))
|
add_child(_header_label("Testlauf-Einstellungen"))
|
||||||
|
|
||||||
add_child(_field_label("Maus-Empfindlichkeit (0,1 – 5,0)"))
|
add_child(_field_label("Maus-Empfindlichkeit (0,1 – 5,0)"))
|
||||||
|
|
@ -67,3 +146,15 @@ func _on_perspektive_changed(index: int) -> void:
|
||||||
var body: Variant = pt.get("body")
|
var body: Variant = pt.get("body")
|
||||||
if body != null:
|
if body != null:
|
||||||
body.call("set_perspektive", dritte)
|
body.call("set_perspektive", dritte)
|
||||||
|
|
||||||
|
|
||||||
|
## Schalter aus dem Editor-Zustand nachziehen (beim Öffnen des Fensters).
|
||||||
|
func refresh() -> void:
|
||||||
|
if main == null:
|
||||||
|
return
|
||||||
|
grid_btn.set_pressed_no_signal(bool(main.get("_grid_on")))
|
||||||
|
contours_btn.set_pressed_no_signal(bool(main.get("_contours_on")))
|
||||||
|
raster_snap_check.set_pressed_no_signal(bool(main.get("grid_snap")))
|
||||||
|
gizmo_step_box.set_value_no_signal(float(main.gizmo_step_m))
|
||||||
|
size_menu.select(UIScale.step_index())
|
||||||
|
sym_menu.select(int(main.get("sym_mode")))
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ var _pending_high := false # Quell-Wahl des laufenden Widerspruchs-Dialogs
|
||||||
|
|
||||||
|
|
||||||
static func spec() -> Dictionary:
|
static func spec() -> Dictionary:
|
||||||
return {"titel": "Symmetrie", "gruppe": "rechts", "order": 75}
|
return {"titel": "Symmetrie", "gruppe": "map", "order": 30}
|
||||||
|
|
||||||
|
|
||||||
func panel_title() -> String:
|
func panel_title() -> String:
|
||||||
|
|
@ -108,6 +108,14 @@ func _refresh_half_option() -> void:
|
||||||
half_option.selected = 0
|
half_option.selected = 0
|
||||||
|
|
||||||
|
|
||||||
|
## Vom Einstellungen-Fenster gerufen, wenn der Modus wechselt.
|
||||||
|
func refresh_mode() -> void:
|
||||||
|
if mode_label == null or half_option == null:
|
||||||
|
return
|
||||||
|
_refresh_half_option()
|
||||||
|
_refresh_mode_line()
|
||||||
|
|
||||||
|
|
||||||
func _on_half_selected(_index: int) -> void:
|
func _on_half_selected(_index: int) -> void:
|
||||||
_refresh_mode_line()
|
_refresh_mode_line()
|
||||||
|
|
||||||
|
|
@ -121,7 +129,7 @@ func _refresh_mode_line() -> void:
|
||||||
func _on_apply_pressed() -> void:
|
func _on_apply_pressed() -> void:
|
||||||
var mode := _current_mode()
|
var mode := _current_mode()
|
||||||
if mode == SymmetryCore.Mode.OFF:
|
if mode == SymmetryCore.Mode.OFF:
|
||||||
_say("Der Symmetrie-Modus ist Aus — oben in der Leiste erst Punkt oder eine Achse wählen.")
|
_say("Der Symmetrie-Modus ist Aus — erst in den Einstellungen (⚙) Punkt oder eine Achse wählen.")
|
||||||
return
|
return
|
||||||
if main.doc.is_empty():
|
if main.doc.is_empty():
|
||||||
_say("Keine Karte offen — erst eine Map anlegen oder laden.", true)
|
_say("Keine Karte offen — erst eine Map anlegen oder laden.", true)
|
||||||
|
|
@ -209,7 +217,7 @@ func _apply(quelle_hoch: bool) -> void:
|
||||||
func _on_check_pressed() -> void:
|
func _on_check_pressed() -> void:
|
||||||
var mode := _current_mode()
|
var mode := _current_mode()
|
||||||
if mode == SymmetryCore.Mode.OFF:
|
if mode == SymmetryCore.Mode.OFF:
|
||||||
_say("Der Symmetrie-Modus ist Aus — erst Punkt oder eine Achse wählen, dann prüfen.")
|
_say("Der Symmetrie-Modus ist Aus — erst in den Einstellungen (⚙) Punkt oder eine Achse wählen, dann prüfen.")
|
||||||
return
|
return
|
||||||
_refresh_mode_line()
|
_refresh_mode_line()
|
||||||
var rep := SymmetryOps.check(main.doc, mode)
|
var rep := SymmetryOps.check(main.doc, mode)
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ var info_hint: Label
|
||||||
|
|
||||||
|
|
||||||
static func spec() -> Dictionary:
|
static func spec() -> Dictionary:
|
||||||
return {"titel": "Test-Start", "gruppe": "rechts", "order": 60}
|
return {"titel": "Test-Start", "gruppe": "map", "order": 20}
|
||||||
|
|
||||||
|
|
||||||
func panel_title() -> String:
|
func panel_title() -> String:
|
||||||
|
|
|
||||||
|
|
@ -28,10 +28,7 @@ func panel_title() -> String:
|
||||||
|
|
||||||
var undo_btn: Button
|
var undo_btn: Button
|
||||||
var redo_btn: Button
|
var redo_btn: Button
|
||||||
var grid_btn: Button # „⊞ Gitter“ (Karte t_7ac70014)
|
var settings_btn: Button # „⚙ Einstellungen“ öffnet das Einstellungen-Fenster
|
||||||
var contours_btn: Button # „≈ Höhenlinien“ (Karte t_3ebe4086)
|
|
||||||
var sym_menu: MenuButton
|
|
||||||
var size_menu: MenuButton # „UI-Größe“ (Karte t_297221b5)
|
|
||||||
var title_label: Label
|
var title_label: Label
|
||||||
var file_label: Label
|
var file_label: Label
|
||||||
|
|
||||||
|
|
@ -85,45 +82,6 @@ func build() -> void:
|
||||||
redo_btn.pressed.connect(main.call.bind("_do_redo"))
|
redo_btn.pressed.connect(main.call.bind("_do_redo"))
|
||||||
add_child(redo_btn)
|
add_child(redo_btn)
|
||||||
|
|
||||||
# Maßstabsgitter-Toggle (Karte t_7ac70014): über sichtbarem Terrain ist das
|
|
||||||
# Gitter standardmäßig AUS (das Terrain ist die Bodendarstellung); wer das
|
|
||||||
# 100-m-Raster als Planungshilfe will, schaltet es hier ohne Rebuild zu.
|
|
||||||
grid_btn = Button.new()
|
|
||||||
grid_btn.text = "⊞ Gitter"
|
|
||||||
grid_btn.toggle_mode = true
|
|
||||||
grid_btn.button_pressed = false
|
|
||||||
grid_btn.custom_minimum_size = UIScale.v2(120, 40)
|
|
||||||
grid_btn.tooltip_text = "Maßstabsgitter (100-m-Raster) im Viewport ein-/ausblenden. Über sichtbarem Terrain ist es standardmäßig aus."
|
|
||||||
grid_btn.toggled.connect(func(pressed: bool) -> void: main.call("_on_grid_toggled", pressed))
|
|
||||||
add_child(grid_btn)
|
|
||||||
|
|
||||||
# Höhenlinien-Button (Karte t_3ebe4086, Kopplung t_89c0ec95): drücken =
|
|
||||||
# LINIEN FIXIEREN (immer sichtbar, in jedem Werkzeug). Ohne Fix sind die
|
|
||||||
# Linien NUR im Terrain-Werkzeug (F1) sichtbar; auf flachen Maps (< 2 m
|
|
||||||
# Gesamtrelief) schaltet das Terrain-Mesh zusätzlich selbst auf aus.
|
|
||||||
contours_btn = Button.new()
|
|
||||||
contours_btn.text = "≈ Höhenlinien"
|
|
||||||
contours_btn.toggle_mode = true
|
|
||||||
contours_btn.button_pressed = false
|
|
||||||
contours_btn.custom_minimum_size = UIScale.v2(150, 40)
|
|
||||||
contours_btn.tooltip_text = "Höhenlinien (1 m, alle 5 m kräftiger) FIXIERT an/aus. Ohne Fixierung erscheinen sie nur im Terrain-Werkzeug (F1). Taste L schaltet ebenfalls. Bei flachem Terrain (< 2 m) sind sie automatisch aus."
|
|
||||||
contours_btn.toggled.connect(func(pressed: bool) -> void: main.call("_on_contours_toggled", pressed))
|
|
||||||
add_child(contours_btn)
|
|
||||||
|
|
||||||
# Symmetrie-Schalter (Karte t_f5935023, M1): Aus / Punkt / Achse X / Achse Z.
|
|
||||||
# Standard = Punktspiegelung (Sly 20.10.). M1 setzt nur Modus + Anzeige;
|
|
||||||
# das gespiegelte Bauen selbst kommt mit M2–M4.
|
|
||||||
sym_menu = MenuButton.new()
|
|
||||||
sym_menu.text = "Symmetrie: Punkt"
|
|
||||||
sym_menu.flat = false
|
|
||||||
sym_menu.custom_minimum_size = UIScale.v2(160, 40)
|
|
||||||
sym_menu.tooltip_text = "Gespiegeltes Bauen: Änderungen werden automatisch auf die andere Kartenseite übertragen. Aus / Punkt (180° um die Kartenmitte) / Achse X / Achse Z. Standard: Punkt."
|
|
||||||
var sym_popup := sym_menu.get_popup()
|
|
||||||
for i in SymmetryCore.MODE_LABELS.size():
|
|
||||||
sym_popup.add_check_item(SymmetryCore.MODE_LABELS[i], i)
|
|
||||||
sym_popup.index_pressed.connect(func(index: int) -> void: main.call("_on_sym_mode_selected", index))
|
|
||||||
add_child(sym_menu)
|
|
||||||
|
|
||||||
# Toolbar-Aktionen DATENGETRIEBEN aus der Registry (Operator-Vorgabe
|
# Toolbar-Aktionen DATENGETRIEBEN aus der Registry (Operator-Vorgabe
|
||||||
# 20.10.): Karte prüfen, ▶ Karte testen — künftige Karten melden sich
|
# 20.10.): Karte prüfen, ▶ Karte testen — künftige Karten melden sich
|
||||||
# hier an, ohne main.gd anzufassen. Die Buttons bekommen den Registry-Namen
|
# hier an, ohne main.gd anzufassen. Die Buttons bekommen den Registry-Namen
|
||||||
|
|
@ -147,21 +105,15 @@ func build() -> void:
|
||||||
main.set("test_start_btn", btn)
|
main.set("test_start_btn", btn)
|
||||||
add_child(btn)
|
add_child(btn)
|
||||||
|
|
||||||
# UI-Größe (Karte t_297221b5): Klein/Mittel/Groß — Sly stellt das selbst um,
|
# Einstellungen (25.10.): Gitter, Höhenlinien, Symmetrie-Modus, UI-Größe,
|
||||||
# es wirkt SOFORT (kompletter UI-Neuaufbau mit dem neuen Faktor) und bleibt
|
# Raster-Snap … stehen jetzt in einem eigenen Fenster.
|
||||||
# in user://scopedvr_editor.cfg gespeichert.
|
settings_btn = Button.new()
|
||||||
size_menu = MenuButton.new()
|
settings_btn.text = "⚙ Einstellungen"
|
||||||
size_menu.text = "UI-Größe"
|
settings_btn.name = "einstellungen_knopf"
|
||||||
size_menu.flat = false
|
settings_btn.custom_minimum_size = UIScale.v2(170, 40)
|
||||||
size_menu.custom_minimum_size = UIScale.v2(120, 40)
|
settings_btn.tooltip_text = "Gitter, Höhenlinien, Symmetrie, UI-Größe, Raster-Snap und Testlauf-Einstellungen"
|
||||||
size_menu.tooltip_text = "Größe der Oberfläche: Klein (wie bisher), Mittel (1,25-fach), Groß (1,5-fach). Wirkt sofort und bleibt gespeichert."
|
settings_btn.pressed.connect(func() -> void: main.call("_open_settings"))
|
||||||
var size_popup := size_menu.get_popup()
|
add_child(settings_btn)
|
||||||
for i in UIScale.step_count():
|
|
||||||
size_popup.add_check_item(UIScale.step_label(i), i)
|
|
||||||
# Häkchen-Sync macht der KOORDINATOR nach _pull_toolbar_refs (hier ist
|
|
||||||
# main.size_menu noch null — alter Reihenfolge-Bug, Karte t_eaccbfed).
|
|
||||||
size_popup.index_pressed.connect(func(index: int) -> void: main.call("_on_ui_size_selected", index))
|
|
||||||
add_child(size_menu)
|
|
||||||
|
|
||||||
var help_btn := Button.new()
|
var help_btn := Button.new()
|
||||||
help_btn.text = "❓ Hilfe"
|
help_btn.text = "❓ Hilfe"
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ func _run_tests() -> void:
|
||||||
|
|
||||||
func _test_catalog() -> void:
|
func _test_catalog() -> void:
|
||||||
print("Katalog")
|
print("Katalog")
|
||||||
_check(BauKatalog.KINDS.size() == 12, "12 Bauteile im Katalog")
|
_check(BauKatalog.KINDS.size() == 18, "18 Bauteile im Katalog")
|
||||||
var in_gruppen := {}
|
var in_gruppen := {}
|
||||||
for g in BauKatalog.GRUPPEN:
|
for g in BauKatalog.GRUPPEN:
|
||||||
for oid in (g["oids"] as Array):
|
for oid in (g["oids"] as Array):
|
||||||
|
|
@ -187,11 +187,11 @@ func _test_snap_cells_and_edges() -> void:
|
||||||
s = BuildSnap.berechne("bau_fundament", Vector3(-0.3, 0.0, -2.9), 0.0, kein, _flach(0.0))
|
s = BuildSnap.berechne("bau_fundament", Vector3(-0.3, 0.0, -2.9), 0.0, kein, _flach(0.0))
|
||||||
_check(s["pos"] == Vector3(-1.0, 0.0, -3.0), "Fundament: negative Zelle (−1, −3)")
|
_check(s["pos"] == Vector3(-1.0, 0.0, -3.0), "Fundament: negative Zelle (−1, −3)")
|
||||||
s = BuildSnap.berechne("bau_wand", Vector3(1.0, 0.0, 0.1), 0.0, kein, _flach(0.0))
|
s = BuildSnap.berechne("bau_wand", Vector3(1.0, 0.0, 0.1), 0.0, kein, _flach(0.0))
|
||||||
_check(s["pos"] == Vector3(1.0, 0.0, 0.0) and float(s["rot"]) == 0.0, "Wand: Kante entlang X, Drehung 0")
|
_check(s["pos"] == Vector3(1.0, 0.0, 0.1) and float(s["rot"]) == 0.0, "Wand: Kante entlang X, Drehung 0, 10 cm in die Zelle gerückt (bündig)")
|
||||||
s = BuildSnap.berechne("bau_wand", Vector3(0.1, 0.0, 1.0), 0.0, kein, _flach(0.0))
|
s = BuildSnap.berechne("bau_wand", Vector3(0.1, 0.0, 1.0), 0.0, kein, _flach(0.0))
|
||||||
_check(s["pos"] == Vector3(0.0, 0.0, 1.0) and float(s["rot"]) == 90.0, "Wand: Kante entlang Z, Drehung 90")
|
_check(s["pos"] == Vector3(0.1, 0.0, 1.0) and float(s["rot"]) == 90.0, "Wand: Kante entlang Z, Drehung 90, 10 cm in die Zelle gerückt")
|
||||||
s = BuildSnap.berechne("bau_wand", Vector3(1.0, 0.0, 0.1), 90.0, kein, _flach(0.0))
|
s = BuildSnap.berechne("bau_wand", Vector3(1.0, 0.0, 0.1), 90.0, kein, _flach(0.0))
|
||||||
_check(float(s["rot"]) == 180.0 and s["pos"] == Vector3(1.0, 0.0, 0.0), "R dreht die Wand um (180°), bleibt auf der Kante")
|
_check(float(s["rot"]) == 180.0 and s["pos"] == Vector3(1.0, 0.0, 0.1), "R dreht die Wand um (180°), bleibt an ihrer Stelle")
|
||||||
s = BuildSnap.berechne("bau_wand", Vector3(0.1, 0.0, 1.0), 90.0, kein, _flach(0.0))
|
s = BuildSnap.berechne("bau_wand", Vector3(0.1, 0.0, 1.0), 90.0, kein, _flach(0.0))
|
||||||
_check(float(s["rot"]) == 270.0, "R: Z-Kante 90° → 270°")
|
_check(float(s["rot"]) == 270.0, "R: Z-Kante 90° → 270°")
|
||||||
s = BuildSnap.berechne("bau_pfeiler", Vector3(1.8, 0.0, 2.3), 0.0, kein, _flach(0.0))
|
s = BuildSnap.berechne("bau_pfeiler", Vector3(1.8, 0.0, 2.3), 0.0, kein, _flach(0.0))
|
||||||
|
|
@ -200,17 +200,38 @@ func _test_snap_cells_and_edges() -> void:
|
||||||
_check(s["pos"] == Vector3(4.0, 0.0, 1.0) and float(s["rot"]) == 0.0, "Treppe: 4×2-Fläche entlang X")
|
_check(s["pos"] == Vector3(4.0, 0.0, 1.0) and float(s["rot"]) == 0.0, "Treppe: 4×2-Fläche entlang X")
|
||||||
s = BuildSnap.berechne("bau_treppe", Vector3(1.2, 0.0, 3.3), 90.0, kein, _flach(0.0))
|
s = BuildSnap.berechne("bau_treppe", Vector3(1.2, 0.0, 3.3), 90.0, kein, _flach(0.0))
|
||||||
_check(s["pos"] == Vector3(1.0, 0.0, 4.0) and float(s["rot"]) == 90.0, "Treppe: gedreht 90° → entlang Z")
|
_check(s["pos"] == Vector3(1.0, 0.0, 4.0) and float(s["rot"]) == 90.0, "Treppe: gedreht 90° → entlang Z")
|
||||||
# Alle Fälle: gerasterte Mitten (Wände auf Kanten = ein gerader + ein ungerader Meter)
|
# Alle Fälle: Wandmitte = 10 cm neben einer Kantenmitte (Wand liegt in der Zelle)
|
||||||
for k in range(60):
|
for k in range(60):
|
||||||
var p := Vector3(-9.0 + 0.31 * k, 0.0, 7.0 - 0.27 * k)
|
var p := Vector3(-9.0 + 0.31 * k, 0.0, 7.0 - 0.27 * k)
|
||||||
var w := BuildSnap.berechne("bau_wand", p, 0.0, kein, _flach(0.0))
|
var w := BuildSnap.berechne("bau_wand", p, 0.0, kein, _flach(0.0))
|
||||||
var wp: Vector3 = w["pos"]
|
var wp: Vector3 = w["pos"]
|
||||||
var gerade_x := int(roundf(wp.x)) % 2 == 0
|
var ex := absf(absf(wp.x - roundf(wp.x / 2.0) * 2.0) - 0.1) < 0.001 # X nahe einer geraden Linie
|
||||||
var gerade_z := int(roundf(wp.z)) % 2 == 0
|
var ez := absf(absf(wp.z - roundf(wp.z / 2.0) * 2.0) - 0.1) < 0.001
|
||||||
if not (gerade_x != gerade_z):
|
var ox := absf(fposmod(wp.x, 2.0) - 1.0) < 0.001 # X auf Zellmitte
|
||||||
_check(false, "Wand-Mitte liegt auf Kantenmitte (%s)" % str(wp))
|
var oz := absf(fposmod(wp.z, 2.0) - 1.0) < 0.001
|
||||||
|
if not ((ex and oz) or (ez and ox)):
|
||||||
|
_check(false, "Wand-Mitte liegt in der Zelle an der Kante (%s)" % str(wp))
|
||||||
return
|
return
|
||||||
_check(true, "60 Zufallsklicks: Wandmitte liegt immer auf einer Kantenmitte")
|
# und zwar in der Zelle, in der die Maus war (Außenseite bündig auf der Kante)
|
||||||
|
if (ez and absf(wp.z - p.z) > 1.01) or (ex and absf(wp.x - p.x) > 1.01):
|
||||||
|
_check(false, "Wand liegt auf der Maus-Seite (%s zu %s)" % [str(wp), str(p)])
|
||||||
|
return
|
||||||
|
_check(true, "60 Zufallsklicks: Wand liegt immer in der Zelle der Maus, bündig an einer Kante")
|
||||||
|
# Ecke: zwei Wände in einer Zellecke schließen sich ohne Lücke (äußere Ecke voll)
|
||||||
|
var wa := BuildSnap.berechne("bau_wand", Vector3(1.0, 0.0, 0.3), 0.0, kein, _flach(0.0))
|
||||||
|
var wb := BuildSnap.berechne("bau_wand", Vector3(0.3, 0.0, 1.0), 0.0, kein, _flach(0.0))
|
||||||
|
var ra := BuildSnap.teil_rect({"oid": "bau_wand", "pos": wa["pos"], "rot": wa["rot"]})
|
||||||
|
var rb := BuildSnap.teil_rect({"oid": "bau_wand", "pos": wb["pos"], "rot": wb["rot"]})
|
||||||
|
_check(ra.has_point(Vector2(0.01, 0.01)) and rb.has_point(Vector2(0.01, 0.01)), "Ecke: äußere Ecke (0,0) ist von BEIDEN Wänden gedeckt")
|
||||||
|
_check(absf(ra.position.x) < 0.001 and absf(ra.position.y) < 0.001 and absf(rb.position.x) < 0.001 and absf(rb.position.y) < 0.001,
|
||||||
|
"Ecke: beide Wände beginnen bündig an der Außenkante (0,0)")
|
||||||
|
# Große Teile: Zellzahl bestimmt die Rasterung
|
||||||
|
s = BuildSnap.berechne("bau_dach_giebel_4", Vector3(3.2, 0.0, 3.2), 0.0, kein, _flach(0.0))
|
||||||
|
_check(s["pos"] == Vector3(4.0, 0.0, 4.0), "Satteldach 4×4: Mitte auf Zellecke (4, 4)")
|
||||||
|
s = BuildSnap.berechne("bau_dach_pyramide_6", Vector3(3.2, 0.0, 3.2), 0.0, kein, _flach(0.0))
|
||||||
|
_check(s["pos"] == Vector3(3.0, 0.0, 3.0), "Pyramidendach 6×6: Mitte einer Zelle (3, 3)")
|
||||||
|
s = BuildSnap.berechne("bau_giebelwand_4", Vector3(3.0, 0.0, 0.5), 0.0, kein, _flach(0.0))
|
||||||
|
_check(s["pos"] == Vector3(4.0, 0.0, 0.1), "Giebelwand 4 m: Mitte auf gerader Linie (4), in die Zelle gerückt")
|
||||||
# Gelände: höchste Höhe unter der Grundfläche zählt
|
# Gelände: höchste Höhe unter der Grundfläche zählt
|
||||||
s = BuildSnap.berechne("bau_fundament", Vector3(1.0, 5.0, 1.0), 0.0, kein, _flach(5.0))
|
s = BuildSnap.berechne("bau_fundament", Vector3(1.0, 5.0, 1.0), 0.0, kein, _flach(5.0))
|
||||||
_check(absf(float(s["ebene"]) - 5.0) < 0.001, "Gelände-Höhe wird die Ebene (5,0)")
|
_check(absf(float(s["ebene"]) - 5.0) < 0.001, "Gelände-Höhe wird die Ebene (5,0)")
|
||||||
|
|
@ -224,33 +245,47 @@ func _test_snap_levels() -> void:
|
||||||
print("Snap: Ebenen statt Klickhöhe")
|
print("Snap: Ebenen statt Klickhöhe")
|
||||||
var teile: Array = [
|
var teile: Array = [
|
||||||
_teil(0, "bau_fundament", Vector3(1, 0, 1), 0.0),
|
_teil(0, "bau_fundament", Vector3(1, 0, 1), 0.0),
|
||||||
_teil(1, "bau_wand", Vector3(1, 0.2, 0), 0.0),
|
_teil(1, "bau_wand", Vector3(1, 0, 0.1), 0.0),
|
||||||
_teil(2, "bau_wand", Vector3(2, 0.2, 1), 90.0),
|
_teil(2, "bau_wand", Vector3(1.9, 0, 1), 90.0),
|
||||||
]
|
]
|
||||||
# Klick mitten auf die Seite der Wand (y=1,5): Boden darf NICHT in der Luft landen
|
# Klick mitten auf die Seite der Wand (y=1,5): Boden darf NICHT in der Luft landen
|
||||||
var s := BuildSnap.berechne("bau_boden", Vector3(0.9, 1.5, 0.6), 0.0, teile, _flach(0.0))
|
var s := BuildSnap.berechne("bau_boden", Vector3(0.9, 1.5, 0.6), 0.0, teile, _flach(0.0))
|
||||||
_check(absf(float(s["ebene"]) - 0.2) < 0.001, "Boden nach Klick auf Wandseite liegt auf dem Fundament (0,2), nicht bei 1,5")
|
_check(absf(float(s["ebene"]) - 0.2) < 0.001, "Boden nach Klick auf Wandseite liegt auf dem Fundament (0,2), nicht bei 1,5")
|
||||||
# Klick auf die Oberkante der Wand: nächste Geschoss-Ebene
|
# Klick auf die Oberkante der Wand: nächste Geschoss-Ebene
|
||||||
s = BuildSnap.berechne("bau_boden", Vector3(1.0, 3.2, 0.1), 0.0, teile, _flach(0.0), true, Vector3(0, -0.5, 1))
|
s = BuildSnap.berechne("bau_boden", Vector3(1.0, 3.0, 0.1), 0.0, teile, _flach(0.0), true, Vector3(0, -0.5, 1))
|
||||||
_check(absf(float(s["ebene"]) - 3.2) < 0.001, "Boden nach Klick auf Wandoberkante = Decke auf 3,2")
|
_check(absf(float(s["ebene"]) - 3.0) < 0.001, "Boden nach Klick auf Wandoberkante = Decke auf 3,0")
|
||||||
_check(s["pos"] == Vector3(1.0, 3.2, -1.0), "…und in der Zelle auf der Kameraseite (1, −1)")
|
_check(s["pos"] == Vector3(1.0, 3.0, 1.0), "…und in der Zelle, in der die Wand steht (1, 1) — die Decke liegt auf der Wand")
|
||||||
# Wand auf Wand (Stapeln)
|
s = BuildSnap.berechne("bau_boden", Vector3(1.0, 1.0, 1.0), 0.0, [_teil(9, "bau_pfeiler", Vector3(2, 0, 2), 0.0)], _flach(0.0), true, Vector3(0, -0.5, 1))
|
||||||
s = BuildSnap.berechne("bau_wand", Vector3(1.0, 3.2, 0.0), 0.0, teile, _flach(0.0))
|
_check(s["pos"].z == 1.0, "Treffer auf Nicht-Wand: Zelle der Kameraseite (Pfeiler an der Ecke)")
|
||||||
_check(absf(float(s["ebene"]) - 3.2) < 0.001 and int(s["ersetze"]) == -1, "Wand stapelt sich auf die Oberkante")
|
# Wand auf Wand (Stapeln): genau über der Wand darunter
|
||||||
|
s = BuildSnap.berechne("bau_wand", Vector3(1.0, 3.0, 0.1), 0.0, teile, _flach(0.0), true)
|
||||||
|
_check(absf(float(s["ebene"]) - 3.0) < 0.001 and int(s["ersetze"]) == -1, "Wand stapelt sich auf die Oberkante (3,0)")
|
||||||
|
_check(s["pos"] == Vector3(1.0, 3.0, 0.1), "…und exakt über der Wand darunter (gleiche Lage)")
|
||||||
|
# Test-Befund 25.10.: obere Wände sitzen gleich hoch, egal ob auf der Nachbarwand
|
||||||
|
# eine Decke liegt oder nicht (Decke liegt AUF der Wand, Wände zählen ab Deckenunterkante).
|
||||||
|
var mit_decke: Array = teile.duplicate()
|
||||||
|
mit_decke.append(_teil(3, "bau_boden", Vector3(1, 3.0, -1), 0.0)) # Decke über der Wand vorn (Zelle −1)
|
||||||
|
var ohne := BuildSnap.berechne("bau_wand", Vector3(1.0, 3.0, 0.1), 0.0, mit_decke, _flach(0.0), true)
|
||||||
|
var mit := BuildSnap.berechne("bau_wand", Vector3(1.0, 3.2, -0.1), 0.0, mit_decke, _flach(0.0), true)
|
||||||
|
_check(absf(float(ohne["ebene"]) - 3.0) < 0.001, "Obergeschoss-Wand ohne Decke: Höhe 3,0 (%.2f)" % float(ohne["ebene"]))
|
||||||
|
_check(absf(float(mit["ebene"]) - 3.0) < 0.001, "Obergeschoss-Wand AUF der Decke: ebenfalls 3,0 (%.2f)" % float(mit["ebene"]))
|
||||||
# Ein Dach ist kein Träger: nichts darf auf der Dachspitze landen
|
# Ein Dach ist kein Träger: nichts darf auf der Dachspitze landen
|
||||||
teile.append(_teil(3, "bau_dach_giebel", Vector3(1, 3.2, 1), 0.0))
|
teile.append(_teil(4, "bau_dach_giebel", Vector3(1, 3.0, 1), 0.0))
|
||||||
s = BuildSnap.berechne("bau_boden", Vector3(1.0, 4.2, 1.0), 0.0, teile, _flach(0.0))
|
s = BuildSnap.berechne("bau_boden", Vector3(1.0, 4.0, 1.0), 0.0, teile, _flach(0.0))
|
||||||
_check(float(s["ebene"]) <= 3.2 + 0.001, "Dachspitze (4,2) ist keine Ebene (Ebene %.2f)" % float(s["ebene"]))
|
_check(float(s["ebene"]) <= 3.0 + 0.001, "Dachspitze (4,0) ist keine Ebene (Ebene %.2f)" % float(s["ebene"]))
|
||||||
|
|
||||||
|
|
||||||
func _test_replace_and_stack() -> void:
|
func _test_replace_and_stack() -> void:
|
||||||
print("Ersetzen / doppelt")
|
print("Ersetzen / doppelt")
|
||||||
var teile: Array = [
|
var teile: Array = [
|
||||||
_teil(0, "bau_fundament", Vector3(1, 0, 1), 0.0),
|
_teil(0, "bau_fundament", Vector3(1, 0, 1), 0.0),
|
||||||
_teil(1, "bau_wand", Vector3(1, 0.2, 0), 0.0),
|
_teil(1, "bau_wand", Vector3(1, 0, 0.1), 0.0),
|
||||||
]
|
]
|
||||||
var s := BuildSnap.berechne("bau_wand_tuer", Vector3(1.0, 1.5, 0.1), 0.0, teile, _flach(0.0))
|
var s := BuildSnap.berechne("bau_wand_tuer", Vector3(1.0, 1.5, 0.1), 0.0, teile, _flach(0.0))
|
||||||
_check(bool(s["ok"]) and int(s["ersetze"]) == 1, "Tür in vorhandene Wand → ersetzt Teil 1")
|
_check(bool(s["ok"]) and int(s["ersetze"]) == 1, "Tür in vorhandene Wand → ersetzt Teil 1")
|
||||||
|
# Klick direkt auf die Wand (Objekt-Treffer) übernimmt ihre Lage, auch von der anderen Seite
|
||||||
|
s = BuildSnap.berechne("bau_wand_tuer", Vector3(1.0, 1.5, 0.0), 0.0, teile, _flach(0.0), true, Vector3(0, 0, 1))
|
||||||
|
_check(bool(s["ok"]) and int(s["ersetze"]) == 1, "Tür: Klick auf die Außenseite der Wand ersetzt dieselbe Wand")
|
||||||
s = BuildSnap.berechne("bau_wand", Vector3(1.0, 1.5, 0.1), 0.0, teile, _flach(0.0))
|
s = BuildSnap.berechne("bau_wand", Vector3(1.0, 1.5, 0.1), 0.0, teile, _flach(0.0))
|
||||||
_check(not bool(s["ok"]) and str(s["grund"]).contains("schon"), "gleiche Wand nochmal → abgelehnt mit Erklärung")
|
_check(not bool(s["ok"]) and str(s["grund"]).contains("schon"), "gleiche Wand nochmal → abgelehnt mit Erklärung")
|
||||||
s = BuildSnap.berechne("bau_wand", Vector3(1.0, 1.5, 0.1), 90.0, teile, _flach(0.0))
|
s = BuildSnap.berechne("bau_wand", Vector3(1.0, 1.5, 0.1), 90.0, teile, _flach(0.0))
|
||||||
|
|
@ -283,13 +318,13 @@ func _test_ops_group_undo() -> void:
|
||||||
_check(BauOps.teile_zahl(doc) == 1, "teile_zahl = 1")
|
_check(BauOps.teile_zahl(doc) == 1, "teile_zahl = 1")
|
||||||
# Wand setzen, dann Tür ersetzt dieselbe Stelle (Index bleibt)
|
# Wand setzen, dann Tür ersetzt dieselbe Stelle (Index bleibt)
|
||||||
var teile := BuildSnap.sammle_teile(doc)
|
var teile := BuildSnap.sammle_teile(doc)
|
||||||
var ws := BuildSnap.berechne("bau_wand", Vector3(1, 0.2, 0.05), 0.0, teile, _flach(0.0))
|
var ws := BuildSnap.berechne("bau_wand", Vector3(1, 0.2, 0.15), 0.0, teile, _flach(0.0))
|
||||||
var before2 := hist.snapshot(["objects", "group_names"])
|
var before2 := hist.snapshot(["objects", "group_names"])
|
||||||
var wr := BauOps.setze_teil(doc, "bau_wand", ws, gid, SymmetryCore.Mode.OFF)
|
var wr := BauOps.setze_teil(doc, "bau_wand", ws, gid, SymmetryCore.Mode.OFF)
|
||||||
hist.commit("Wand bauen", before2, hist.snapshot(["objects", "group_names"]))
|
hist.commit("Wand bauen", before2, hist.snapshot(["objects", "group_names"]))
|
||||||
var widx := int(wr["index"])
|
var widx := int(wr["index"])
|
||||||
teile = BuildSnap.sammle_teile(doc)
|
teile = BuildSnap.sammle_teile(doc)
|
||||||
var ts := BuildSnap.berechne("bau_wand_tuer", Vector3(1, 1.5, 0.05), 0.0, teile, _flach(0.0))
|
var ts := BuildSnap.berechne("bau_wand_tuer", Vector3(1, 1.5, 0.15), 0.0, teile, _flach(0.0))
|
||||||
_check(int(ts["ersetze"]) == widx, "Tür-Klick findet die Wand")
|
_check(int(ts["ersetze"]) == widx, "Tür-Klick findet die Wand")
|
||||||
var before3 := hist.snapshot(["objects", "group_names"])
|
var before3 := hist.snapshot(["objects", "group_names"])
|
||||||
var tr := BauOps.setze_teil(doc, "bau_wand_tuer", ts, gid, SymmetryCore.Mode.OFF)
|
var tr := BauOps.setze_teil(doc, "bau_wand_tuer", ts, gid, SymmetryCore.Mode.OFF)
|
||||||
|
|
@ -314,7 +349,7 @@ func _test_ops_group_undo() -> void:
|
||||||
# Haus komplett
|
# Haus komplett
|
||||||
var d2 := _neue_doc()
|
var d2 := _neue_doc()
|
||||||
var gid2 := BauOps.haus_demo(d2, null, Vector3(4.0, 0.0, 4.0))
|
var gid2 := BauOps.haus_demo(d2, null, Vector3(4.0, 0.0, 4.0))
|
||||||
_check(BauOps.teile_zahl(d2) >= 16, "Demo-Haus: %d Teile über die echte Snap-Logik" % BauOps.teile_zahl(d2))
|
_check(BauOps.teile_zahl(d2) >= 13, "Demo-Haus: %d Teile über die echte Snap-Logik" % BauOps.teile_zahl(d2))
|
||||||
_check(BauOps.gebaeude_von(d2, 0).size() == BauOps.teile_zahl(d2), "alle Teile des Hauses in einer Gruppe (%d)" % gid2)
|
_check(BauOps.gebaeude_von(d2, 0).size() == BauOps.teile_zahl(d2), "alle Teile des Hauses in einer Gruppe (%d)" % gid2)
|
||||||
var doppelt := {}
|
var doppelt := {}
|
||||||
var hat_doppelte := false
|
var hat_doppelte := false
|
||||||
|
|
|
||||||
99
tests/test_drag_bibliothek.gd
Normal file
99
tests/test_drag_bibliothek.gd
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
extends SceneTree
|
||||||
|
## test_drag_bibliothek (Test-Befund 25.10.): Objekt-Kachel anklicken, gedrückt
|
||||||
|
## halten, in die Szene ziehen — beim Loslassen wird das Objekt platziert.
|
||||||
|
## Echte Mausereignisse (push_input), kein Direktaufruf.
|
||||||
|
|
||||||
|
var checks := 0
|
||||||
|
var failures := 0
|
||||||
|
|
||||||
|
|
||||||
|
func _check(cond: bool, label: String) -> void:
|
||||||
|
checks += 1
|
||||||
|
if cond:
|
||||||
|
print(" ok: ", label)
|
||||||
|
else:
|
||||||
|
failures += 1
|
||||||
|
print(" FAIL: ", label)
|
||||||
|
|
||||||
|
|
||||||
|
func _mouse(pos: Vector2, pressed: bool, motion := false) -> void:
|
||||||
|
var ev: InputEvent
|
||||||
|
if motion:
|
||||||
|
var mm := InputEventMouseMotion.new()
|
||||||
|
mm.position = pos
|
||||||
|
mm.global_position = pos
|
||||||
|
mm.button_mask = MOUSE_BUTTON_MASK_LEFT
|
||||||
|
ev = mm
|
||||||
|
else:
|
||||||
|
var mb := InputEventMouseButton.new()
|
||||||
|
mb.button_index = MOUSE_BUTTON_LEFT
|
||||||
|
mb.position = pos
|
||||||
|
mb.global_position = pos
|
||||||
|
mb.pressed = pressed
|
||||||
|
ev = mb
|
||||||
|
root.push_input(ev)
|
||||||
|
|
||||||
|
|
||||||
|
func _init() -> void:
|
||||||
|
var main: Control = (load("res://scenes/main.tscn") as PackedScene).instantiate()
|
||||||
|
root.add_child(main)
|
||||||
|
await process_frame
|
||||||
|
await process_frame
|
||||||
|
await process_frame
|
||||||
|
main.sym_mode = SymmetryCore.Mode.OFF # sonst setzt der Standard (Punkt) einen Zwilling dazu
|
||||||
|
var n0: int = (main.doc["objects"] as Array).size()
|
||||||
|
var oid := "barrel_001"
|
||||||
|
var btn: Button = main.library_buttons.get(oid)
|
||||||
|
_check(btn != null, "Kachel für %s vorhanden" % oid)
|
||||||
|
if btn == null:
|
||||||
|
_finish()
|
||||||
|
return
|
||||||
|
# Kachel sichtbar machen: Bibliotheks-Gruppen aufklappen, Scroll an die Kachel.
|
||||||
|
main.assets_mod._on_library_expand_all()
|
||||||
|
await process_frame
|
||||||
|
await process_frame
|
||||||
|
# Headless-Fenster ist 64×64 px: echte Mausereignisse erreichen weder Kachel
|
||||||
|
# noch Szene. Darum die drei Stationen der Ziehen-Kette einzeln und exakt wie
|
||||||
|
# Godot sie aufruft (drag_forwarding → can_drop (je Mausbewegung) → drop).
|
||||||
|
var data: Variant = main.assets_mod.call("_tile_drag_data", Vector2(5, 5), oid, btn)
|
||||||
|
_check(data is Dictionary and str((data as Dictionary).get("svr_oid", "")) == oid, "Ziehen-Start liefert die Objekt-ID")
|
||||||
|
var pos_a := Vector2(300, 150)
|
||||||
|
var pos_b := Vector2(320, 170)
|
||||||
|
_check(main.call("_viewport_can_drop", pos_a, data), "Über der Szene: Ablegen erlaubt")
|
||||||
|
_check(main.interaction.placing and main.interaction.place_oid == oid, "Über der Szene: Platzier-Vorschau läuft")
|
||||||
|
_check(main.call("_viewport_can_drop", pos_b, data), "Mausbewegung: weiter erlaubt")
|
||||||
|
_check(main.interaction.placing, "Vorschau wird nicht neu gestartet")
|
||||||
|
main.call("_viewport_drop", pos_b, data)
|
||||||
|
await process_frame
|
||||||
|
var objs: Array = main.doc["objects"]
|
||||||
|
_check(objs.size() == n0 + 1, "Loslassen setzt genau EIN Objekt (%d → %d)" % [n0, objs.size()])
|
||||||
|
if objs.size() == n0 + 1:
|
||||||
|
_check(str((objs.back() as Dictionary).get("id", "")) == oid, "Das gesetzte Objekt ist %s" % oid)
|
||||||
|
_check(int(main.interaction.selected_index) == n0, "Das neue Objekt ist ausgewählt")
|
||||||
|
_check(not main.interaction.placing, "Danach ist das Platzieren beendet (kein hängender Geist)")
|
||||||
|
_check(main.history.can_undo(), "Undo-Schritt vorhanden")
|
||||||
|
main.history.undo()
|
||||||
|
main.call("_after_structure_change")
|
||||||
|
_check((main.doc["objects"] as Array).size() == n0, "Strg+Z nimmt es zurück")
|
||||||
|
main.call("_clear_edit_state")
|
||||||
|
|
||||||
|
# Abbruch: Ziehen über der Szene, dann außerhalb loslassen (DRAG_END ohne Drop).
|
||||||
|
data = main.assets_mod.call("_tile_drag_data", Vector2(5, 5), oid, btn)
|
||||||
|
main.call("_viewport_can_drop", pos_a, data)
|
||||||
|
_check(main.interaction.placing, "Abbruch-Zug: Vorschau läuft")
|
||||||
|
main.assets_mod.notification(Node.NOTIFICATION_DRAG_END)
|
||||||
|
_check(not main.interaction.placing, "Außerhalb losgelassen: Vorschau weg")
|
||||||
|
_check((main.doc["objects"] as Array).size() == n0, "Außerhalb losgelassen: nichts gesetzt")
|
||||||
|
|
||||||
|
# Fremde Ziehdaten werden abgelehnt.
|
||||||
|
_check(not main.call("_viewport_can_drop", pos_a, "irgendwas"), "Fremde Ziehdaten: kein Ablegen")
|
||||||
|
_finish()
|
||||||
|
|
||||||
|
|
||||||
|
func _finish() -> void:
|
||||||
|
if failures == 0:
|
||||||
|
print("ALL OK: %d checks" % checks)
|
||||||
|
quit(0)
|
||||||
|
else:
|
||||||
|
print("FAILED: %d/%d checks" % [failures, checks])
|
||||||
|
quit(1)
|
||||||
1
tests/test_drag_bibliothek.gd.uid
Normal file
1
tests/test_drag_bibliothek.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://caa44ddv8g124
|
||||||
|
|
@ -113,9 +113,8 @@ func _t1_einzelauswahl() -> void:
|
||||||
_check(str(insp.widget("rot_y").edit.text).contains("0.0"), "T1 rot_y zeigt 0.0")
|
_check(str(insp.widget("rot_y").edit.text).contains("0.0"), "T1 rot_y zeigt 0.0")
|
||||||
# Auswahl-Beweis per Klick im Viewport-Kontext: Panel ist sichtbar.
|
# Auswahl-Beweis per Klick im Viewport-Kontext: Panel ist sichtbar.
|
||||||
_check(insp.get("_grid").visible, "T1 Felder sichtbar bei Auswahl")
|
_check(insp.get("_grid").visible, "T1 Felder sichtbar bei Auswahl")
|
||||||
# Fix Vorbestand: das alte „Auswahl“-Feld füllt sich jetzt auch bei
|
# Das alte „Auswahl“-Feld ist weg (25.10.): der Inspektor ist die einzige Anzeige.
|
||||||
# select_object (Vorher stand dort dauerhaft „Nichts ausgewählt.“).
|
_check(main.get("sel_label") == null, "T1 kein altes Auswahl-Feld mehr (Tab Auswahl entfernt)")
|
||||||
_check(str(main.get("sel_label").text).contains("Fass"), "T1 altes Auswahl-Feld füllt bei 3D-Klick-Weg")
|
|
||||||
|
|
||||||
|
|
||||||
func _t2_tippen() -> void:
|
func _t2_tippen() -> void:
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,7 @@ func _init() -> void:
|
||||||
|
|
||||||
await _t1_baum_fuellt_sich()
|
await _t1_baum_fuellt_sich()
|
||||||
await _t2_klick_setzt_auswahl()
|
await _t2_klick_setzt_auswahl()
|
||||||
|
await _t2b_echter_klick()
|
||||||
await _t3_auswahl_3d_markiert_baum()
|
await _t3_auswahl_3d_markiert_baum()
|
||||||
await _t4_mehrfach_gespiegelt()
|
await _t4_mehrfach_gespiegelt()
|
||||||
await _t5_gruppen_ebene()
|
await _t5_gruppen_ebene()
|
||||||
|
|
@ -118,6 +119,44 @@ func _t2_klick_setzt_auswahl() -> void:
|
||||||
_check(int(sel) == 1, "T2 Klick im Baum setzt Auswahl in 3D (Index 1 — hat %s)" % str(sel))
|
_check(int(sel) == 1, "T2 Klick im Baum setzt Auswahl in 3D (Index 1 — hat %s)" % str(sel))
|
||||||
|
|
||||||
|
|
||||||
|
## FIX 25.10.: Im Modus SELECT_MULTI feuert Godot nie item_selected — der Test
|
||||||
|
## oben simulierte einen Weg, den es in der Praxis nicht gab. Hier ein ECHTER
|
||||||
|
## Mausklick (Eingabe-Ereignis) auf eine Zeile.
|
||||||
|
func _t2b_echter_klick() -> void:
|
||||||
|
var tree: Tree = tree_mod.get("content_tree")
|
||||||
|
main.interaction.clear_selection()
|
||||||
|
root.size = Vector2i(1600, 900)
|
||||||
|
await process_frame
|
||||||
|
await process_frame
|
||||||
|
var item0: TreeItem = null
|
||||||
|
for kat in _find_child(tree.get_root(), "Objekte (3)").get_children():
|
||||||
|
for oi in _obj_items_under(kat):
|
||||||
|
if int(oi.get_meta("obj_index")) == 2:
|
||||||
|
item0 = oi
|
||||||
|
_check(item0 != null, "T2b Zeile für Objekt-Index 2 gefunden")
|
||||||
|
if item0 == null:
|
||||||
|
return
|
||||||
|
tree.scroll_to_item(item0)
|
||||||
|
await process_frame
|
||||||
|
var r := tree.get_item_area_rect(item0, 0)
|
||||||
|
var pos := tree.get_global_position() + r.position + Vector2(r.size.x * 0.5, r.size.y * 0.5)
|
||||||
|
_check(tree.get_global_rect().has_point(pos), "T2b Zeile liegt im sichtbaren Baum-Rechteck")
|
||||||
|
var ev := InputEventMouseButton.new()
|
||||||
|
ev.button_index = MOUSE_BUTTON_LEFT
|
||||||
|
ev.position = pos
|
||||||
|
ev.global_position = pos
|
||||||
|
ev.pressed = true
|
||||||
|
root.push_input(ev)
|
||||||
|
await process_frame
|
||||||
|
var up := ev.duplicate()
|
||||||
|
up.pressed = false
|
||||||
|
root.push_input(up)
|
||||||
|
await process_frame
|
||||||
|
await process_frame
|
||||||
|
_check(int(main.interaction.get("selected_index")) == 2,
|
||||||
|
"T2b ECHTER Klick auf die Listenzeile wählt das Objekt in der Szene (Index 2 — hat %s)" % str(main.interaction.get("selected_index")))
|
||||||
|
|
||||||
|
|
||||||
func _t3_auswahl_3d_markiert_baum() -> void:
|
func _t3_auswahl_3d_markiert_baum() -> void:
|
||||||
var tree: Tree = tree_mod.get("content_tree")
|
var tree: Tree = tree_mod.get("content_tree")
|
||||||
var imap: Dictionary = tree_mod.get("_obj_index_map")
|
var imap: Dictionary = tree_mod.get("_obj_index_map")
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,13 @@ func _check(cond: bool, what: String) -> void:
|
||||||
print(" FAIL: ", what)
|
print(" FAIL: ", what)
|
||||||
|
|
||||||
|
|
||||||
|
func _count_named(node: Node, wanted: String) -> int:
|
||||||
|
var n := 1 if str(node.name) == wanted and not (node is BaseButton) else 0
|
||||||
|
for c in node.get_children():
|
||||||
|
n += _count_named(c, wanted)
|
||||||
|
return n
|
||||||
|
|
||||||
|
|
||||||
func _init() -> void:
|
func _init() -> void:
|
||||||
print("Testgruppe: Layout aus der Registry")
|
print("Testgruppe: Layout aus der Registry")
|
||||||
var scene: PackedScene = load("res://scenes/main.tscn")
|
var scene: PackedScene = load("res://scenes/main.tscn")
|
||||||
|
|
@ -67,6 +74,11 @@ func _init() -> void:
|
||||||
_check(ids.size() == registry_script.PANELS.size(), "Registry: IDs eindeutig (keine Duplikate in der Tabelle)")
|
_check(ids.size() == registry_script.PANELS.size(), "Registry: IDs eindeutig (keine Duplikate in der Tabelle)")
|
||||||
_check(not ids.is_empty(), "Registry: mindestens ein Bereich angemeldet")
|
_check(not ids.is_empty(), "Registry: mindestens ein Bereich angemeldet")
|
||||||
|
|
||||||
|
var embedded_ids: Array = []
|
||||||
|
for entry in registry_script.PANELS:
|
||||||
|
if str(entry["gruppe"]) in ["map", "dialog"]:
|
||||||
|
embedded_ids.append(str(entry["id"]))
|
||||||
|
|
||||||
# --- 2. Jeder Bereich GENAU EINMAL im Baum, als Tab mit Titel --------------
|
# --- 2. Jeder Bereich GENAU EINMAL im Baum, als Tab mit Titel --------------
|
||||||
var tabs_by_name: Dictionary = {} # gruppe → TabContainer
|
var tabs_by_name: Dictionary = {} # gruppe → TabContainer
|
||||||
var stack: Array = [main]
|
var stack: Array = [main]
|
||||||
|
|
@ -91,6 +103,12 @@ func _init() -> void:
|
||||||
_check(tab.get_tab_title(idx) == title,
|
_check(tab.get_tab_title(idx) == title,
|
||||||
"Tab '%s' trägt Registry-Titel '%s'" % [cid, title])
|
"Tab '%s' trägt Registry-Titel '%s'" % [cid, title])
|
||||||
for wanted in ids:
|
for wanted in ids:
|
||||||
|
if wanted in embedded_ids:
|
||||||
|
# Map-Abschnitte und Einstellungen-Fenster sind keine Tabs (25.10.):
|
||||||
|
# GENAU EINMAL im Baum, Registry-Name = Node-Name.
|
||||||
|
var treffer := _count_named(main, wanted)
|
||||||
|
_check(treffer == 1, "Bereich '%s' (eingebettet) GENAU EINMAL im Baum (%d)" % [wanted, treffer])
|
||||||
|
continue
|
||||||
_check(tabs_by_name.get(wanted) != null, "Bereich '%s' aus der Registry im Baum" % wanted)
|
_check(tabs_by_name.get(wanted) != null, "Bereich '%s' aus der Registry im Baum" % wanted)
|
||||||
|
|
||||||
# --- 3. Tab-Gruppen --------------------------------------------------------
|
# --- 3. Tab-Gruppen --------------------------------------------------------
|
||||||
|
|
@ -108,6 +126,7 @@ func _init() -> void:
|
||||||
for entry in registry_script.PANELS:
|
for entry in registry_script.PANELS:
|
||||||
if str(entry["gruppe"]) == "rechts":
|
if str(entry["gruppe"]) == "rechts":
|
||||||
expected_right += 1
|
expected_right += 1
|
||||||
|
_check(expected_right == 5, "rechts: genau 5 Tabs (Inspektor, Map, Terrain, Malen, Bauen)")
|
||||||
_check(right.get_child_count() == expected_right,
|
_check(right.get_child_count() == expected_right,
|
||||||
"rechts: %d Tabs aus der Registry — hat %d" % [expected_right, right.get_child_count()])
|
"rechts: %d Tabs aus der Registry — hat %d" % [expected_right, right.get_child_count()])
|
||||||
_check(right.get_tab_count() == right.get_child_count(),
|
_check(right.get_tab_count() == right.get_child_count(),
|
||||||
|
|
@ -125,7 +144,7 @@ func _init() -> void:
|
||||||
btab = gc
|
btab = gc
|
||||||
_check(btab != null, "unten: TabContainer für Assets gefunden")
|
_check(btab != null, "unten: TabContainer für Assets gefunden")
|
||||||
if btab != null:
|
if btab != null:
|
||||||
_check(btab.get_child_count() == 2, "unten: 2 Tabs (Bibliothek/Werkzeuge) — hat %d" % btab.get_child_count())
|
_check(btab.get_child_count() == 1, "unten: 1 Tab (Objekt-Bibliothek) — hat %d" % btab.get_child_count())
|
||||||
|
|
||||||
# --- 4. Assets-Bereich unten vollständig -----------------------------------
|
# --- 4. Assets-Bereich unten vollständig -----------------------------------
|
||||||
var assets: Control = tabs_by_name.get("assets")
|
var assets: Control = tabs_by_name.get("assets")
|
||||||
|
|
@ -136,8 +155,12 @@ func _init() -> void:
|
||||||
"Bibliothek: „Alle auf/zu“ vorhanden")
|
"Bibliothek: „Alle auf/zu“ vorhanden")
|
||||||
var lib_box: Node = assets.get("library_box")
|
var lib_box: Node = assets.get("library_box")
|
||||||
_check(lib_box != null and lib_box.get_child_count() > 0, "Bibliothek: Kachel-/Gruppen-Liste gefüllt")
|
_check(lib_box != null and lib_box.get_child_count() > 0, "Bibliothek: Kachel-/Gruppen-Liste gefüllt")
|
||||||
var tools: Control = tabs_by_name.get("werkzeuge")
|
_check(tabs_by_name.get("werkzeuge") == null, "Tab „Objekt-Werkzeuge“ ist weg")
|
||||||
_check(tools != null and tools.get("grid_check") != null, "Objekt-Werkzeuge: Raster-Snap erhalten (nichts verschwindet)")
|
var settings: Control = main.find_child("einstellungen", true, false)
|
||||||
|
_check(settings != null and settings.get("raster_snap_check") != null, "Raster-Snap wohnt im Einstellungen-Fenster")
|
||||||
|
_check(settings != null and settings.get("grid_btn") != null and settings.get("contours_btn") != null
|
||||||
|
and settings.get("size_menu") != null and settings.get("sym_menu") != null,
|
||||||
|
"Einstellungen: Gitter, Höhenlinien, UI-Größe, Symmetrie-Modus")
|
||||||
|
|
||||||
# --- 5. Theme als einzige Farbquelle ----------------------------------------
|
# --- 5. Theme als einzige Farbquelle ----------------------------------------
|
||||||
var theme_res: Theme = main.get("theme")
|
var theme_res: Theme = main.get("theme")
|
||||||
|
|
|
||||||
|
|
@ -128,10 +128,9 @@ func _test_real_ui_scaling() -> void:
|
||||||
_check(int(main.theme.default_font_size) == 16, "Klein: Theme-Schrift 16")
|
_check(int(main.theme.default_font_size) == 16, "Klein: Theme-Schrift 16")
|
||||||
_check(main.undo_btn.custom_minimum_size == Vector2(140, 40), "Klein: Undo-Knopf 140×40")
|
_check(main.undo_btn.custom_minimum_size == Vector2(140, 40), "Klein: Undo-Knopf 140×40")
|
||||||
_check(main.redo_btn.custom_minimum_size == Vector2(150, 40), "Klein: Redo-Knopf 150×40")
|
_check(main.redo_btn.custom_minimum_size == Vector2(150, 40), "Klein: Redo-Knopf 150×40")
|
||||||
_check(main.size_menu != null and main.size_menu.text == "UI-Größe", "UI-Größe-Menü in der Top-Bar")
|
_check(main.size_menu != null and main.size_menu is OptionButton, "UI-Größe-Auswahl im Einstellungen-Fenster")
|
||||||
var sp: PopupMenu = main.size_menu.get_popup()
|
_check(main.size_menu.item_count == 3, "Auswahl hat 3 Stufen")
|
||||||
_check(sp.item_count == 3, "Menü hat 3 Einträge")
|
_check(main.size_menu.selected == 0, "Auswahl steht auf aktueller Stufe (Klein)")
|
||||||
_check(sp.is_item_checked(0), "Menü-Häkchen auf aktueller Stufe (Klein)")
|
|
||||||
|
|
||||||
# Rebuild-Pfad (derselbe wie beim Menü-Klick) → Mittel:
|
# Rebuild-Pfad (derselbe wie beim Menü-Klick) → Mittel:
|
||||||
UIScale.set_step_index(1)
|
UIScale.set_step_index(1)
|
||||||
|
|
@ -146,7 +145,7 @@ func _test_real_ui_scaling() -> void:
|
||||||
_check(tile != null and tile.custom_minimum_size.y == 235, "Mittel: Bibliotheks-Kachel 188 → 235")
|
_check(tile != null and tile.custom_minimum_size.y == 235, "Mittel: Bibliotheks-Kachel 188 → 235")
|
||||||
var thumb := _find_thumb_rect(160)
|
var thumb := _find_thumb_rect(160)
|
||||||
_check(thumb != null and thumb.custom_minimum_size.x == 160, "Mittel: Thumbnail 128 → 160")
|
_check(thumb != null and thumb.custom_minimum_size.x == 160, "Mittel: Thumbnail 128 → 160")
|
||||||
_check(main.size_menu.get_popup().is_item_checked(1), "Menü-Häkchen wandert auf Mittel (neues Popup)")
|
_check(main.size_menu.selected == 1, "Auswahl wandert auf Mittel (neues Fenster)")
|
||||||
|
|
||||||
# Groß:
|
# Groß:
|
||||||
main._on_ui_size_selected(2)
|
main._on_ui_size_selected(2)
|
||||||
|
|
@ -161,7 +160,7 @@ func _test_real_ui_scaling() -> void:
|
||||||
main._on_ui_size_selected(0)
|
main._on_ui_size_selected(0)
|
||||||
await process_frame
|
await process_frame
|
||||||
await process_frame
|
await process_frame
|
||||||
_check(main.size_menu.get_popup().is_item_checked(0), "Menü-Häkchen wieder auf Klein")
|
_check(main.size_menu.selected == 0, "Auswahl wieder auf Klein")
|
||||||
_check(int(main.theme.default_font_size) == 16, "Zurück auf Klein: Schrift wieder 16")
|
_check(int(main.theme.default_font_size) == 16, "Zurück auf Klein: Schrift wieder 16")
|
||||||
_check(main.undo_btn.custom_minimum_size == Vector2(140, 40), "Zurück auf Klein: Undo-Knopf 140×40")
|
_check(main.undo_btn.custom_minimum_size == Vector2(140, 40), "Zurück auf Klein: Undo-Knopf 140×40")
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue