Symbol-Funktion in Elemente-Panel (Phase S1+S2)

Schema (library.py):
- Item-Format erweitert: files2d + files3d (Backwards-compat zu 'files')
- _build_variant_block + _place_instance + Layer-Routing pro Variante
- import_item akzeptiert at_point + layer2d/layer3d
- _ensure_block_definition mit variant-Suffix (dossier_lib_<id>_2d/_3d)

Backend (elemente.py):
- _layer_path_symbole(geschoss_name, variant) → <geschoss>::40_SYMBOLE::
  SYMBOLE_2D bzw. SYMBOLE_3D
- Default-Ebene 40 SYMBOLE via _find_ebene_sublayer_name
- LIST_LIBRARY-Handler: sendet Library-Manifest als LIBRARY_LIST
- CREATE_SYMBOL-Handler: interactive GetPoint im aktiven Viewport,
  laedt Block-Def + platziert Instanz(en) auf den richtigen Ebenen
- Pair-Items (2D+3D) werden an gleichem Punkt beidseitig platziert →
  Top zeigt 2D-Layer, Persp zeigt 3D-Layer wenn User entsprechend
  Sichtbarkeit setzt

Frontend:
- SymbolPicker Modal-Component: Grid mit Symbol/Object-Cards, Search,
  Type-Filter (Alle/Symbole/Objekte), Doppelklick = Pick
- Symbol-Button in ElementeApp (PillGroup "Library") oeffnet Modal +
  triggert listLibrary() fuer aktuelle Items
- createSymbol(id) → Backend → GetPoint → Place

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-25 03:31:54 +02:00
parent 8f691e37c4
commit 8184f559fc
5 changed files with 394 additions and 56 deletions
+29
View File
@@ -9,7 +9,9 @@ import {
openSwisstopo, openSwisstopoDialog, openOsmDialog,
updateElement, deleteElement, openElementeUebersicht, openElementeProperties,
saveOeffStyle, deleteOeffStyle,
listLibrary, createSymbol,
} from './lib/rhinoBridge'
import SymbolPicker from './components/SymbolPicker'
const labelXs = {
fontSize: 10, fontWeight: 600, color: 'var(--text-muted)',
@@ -327,7 +329,16 @@ function NeuesElementSection({ noGeschoss, activeName, elementsCount }) {
const [treppeMenuOpen, setTreppeMenuOpen] = useState(false)
const [stuetzeMenuOpen, setStuetzeMenuOpen] = useState(false)
const [traegerMenuOpen, setTraegerMenuOpen] = useState(false)
const [symbolPickerOpen, setSymbolPickerOpen] = useState(false)
const [libraryItems, setLibraryItems] = useState([])
const treppeWrapperRef = useRef(null)
// Library-Items kommen via LIBRARY_LIST message vom Backend nach LIST_LIBRARY.
useEffect(() => {
onMessage('LIBRARY_LIST', ({ items }) => {
if (Array.isArray(items)) setLibraryItems(items)
})
}, [])
const dis = noGeschoss
const baseHint = (label) =>
noGeschoss ? 'Erst im Ebenen-Manager ein Geschoss aktivieren'
@@ -477,6 +488,14 @@ function NeuesElementSection({ noGeschoss, activeName, elementsCount }) {
onClick={() => createRaum({})} />
</PillGroup>
<PillGroup label="Library">
<PillButton icon="inventory_2" label="Symbol"
hint={dis ? baseHint('Symbol') :
'Library-Item auswählen · im Viewport Punkt klicken zum Platzieren'}
disabled={dis}
onClick={() => { listLibrary(); setSymbolPickerOpen(true) }} />
</PillGroup>
<PillGroup label="Importer">
<PillButton icon="download" label="Swisstopo"
hint="Vollautomatischer Import via swisstopo STAC-API: Adresse suchen, Radius wählen, Gebäude + Terrain + Luftbild holen"
@@ -488,6 +507,16 @@ function NeuesElementSection({ noGeschoss, activeName, elementsCount }) {
hint="Öffnet map.geo.admin.ch im Browser zur visuellen Inspektion"
onClick={() => openSwisstopo('both')} />
</PillGroup>
{symbolPickerOpen && (
<SymbolPicker
items={libraryItems}
onPick={(id) => {
setSymbolPickerOpen(false)
createSymbol(id)
}}
onClose={() => setSymbolPickerOpen(false)} />
)}
</div>
)
}