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
+85
View File
@@ -929,6 +929,20 @@ def _layer_path_raum(doc, geschoss_name):
return "{}::{}".format(geschoss_name, sub)
def _layer_path_symbole(doc, geschoss_name, variant):
"""Symbol-Layer (Library-Items). variant = '2d' oder '3d'.
Pfad: <geschoss>::40_SYMBOLE::SYMBOLE_2D bzw. SYMBOLE_3D.
User kann die Top-Level-Ebene 40 frei rein-/raus-schalten die
Sub-Layer 2D/3D regeln View-spezifische Sichtbarkeit (Top zeigt 2D,
Persp zeigt 3D)."""
sub = _find_ebene_sublayer_name(doc, ["symbol"],
"40", "SYMBOLE",
default_color="#5fa896", default_lw=0.13)
if variant.lower() == "3d":
return "{}::{}::SYMBOLE_3D".format(geschoss_name, sub)
return "{}::{}::SYMBOLE_2D".format(geschoss_name, sub)
def _ensure_layer(doc, path):
"""Stellt sicher, dass ein Layer-Pfad existiert. Liefert Layer-Index."""
idx = doc.Layers.FindByFullPath(path, -1)
@@ -6030,6 +6044,8 @@ class ElementeBridge(panel_base.BaseBridge):
elif t == "CREATE_TRAEGER": self._cmd_create_traeger(p)
elif t == "CREATE_RAUM": self._cmd_create_raum(p)
elif t == "EXPORT_RAEUME": self._cmd_export_raeume(p)
elif t == "LIST_LIBRARY": self._cmd_list_library(p)
elif t == "CREATE_SYMBOL": self._cmd_create_symbol(p)
elif t == "OPEN_SWISSTOPO": self._cmd_open_swisstopo(p)
elif t == "IMPORT_SWISSTOPO": self._cmd_import_swisstopo(p)
elif t == "OPEN_SWISSTOPO_DIALOG": self._cmd_open_swisstopo_dialog(p)
@@ -8071,6 +8087,75 @@ class ElementeBridge(panel_base.BaseBridge):
# unter dem aktiven Geschoss.
# ------------------------------------------------------------------
# ------------------------------------------------------------------
# Library-Symbol/Object — listet + platziert Library-Items im Doc
# ------------------------------------------------------------------
def _cmd_list_library(self, p):
"""Liefert Library-Manifest ans Frontend (fuer den Symbol-Picker
im Elemente-Panel). Antwort: LIBRARY_LIST message."""
try:
import library
manifest = library.load_manifest()
self.send("LIBRARY_LIST", {
"items": manifest.get("items", []),
"name": manifest.get("name", "Dossier-Library"),
})
except Exception as ex:
print("[ELEMENTE] list library:", ex)
self.send("LIBRARY_LIST", {"items": [], "name": ""})
def _cmd_create_symbol(self, p):
"""Platziert ein Library-Item (symbol/object) im Doc. Interactive
GetPoint im aktiven Viewport User klickt Position.
Payload: { id: libraryId }. Layer-Routing auf 40_SYMBOLE::2D/3D
unter aktivem Geschoss."""
lib_id = (p.get("id") or "").strip()
if not lib_id:
print("[ELEMENTE] CREATE_SYMBOL ohne id")
return
doc = Rhino.RhinoDoc.ActiveDoc
if doc is None: return
import library
item = library.find_item(lib_id)
if item is None:
print("[ELEMENTE] CREATE_SYMBOL: item not found", lib_id)
return
if item.get("type") not in ("symbol", "object"):
print("[ELEMENTE] CREATE_SYMBOL: ungueltiger Typ", item.get("type"))
return
# Interactive point pick
try:
import Rhino.Input.Custom as ric
from Rhino.Input import GetResult
except Exception as ex:
print("[ELEMENTE] Imports:", ex); return
try:
gp = ric.GetPoint()
gp.SetCommandPrompt("Symbol platzieren — Punkt waehlen")
res = gp.Get()
if res != GetResult.Point: return
pt = gp.Point()
except Exception as ex:
print("[ELEMENTE] GetPoint:", ex); return
# Layer-Routing
geschoss_id = _active_geschoss_id(doc)
g = _geschoss_by_id(doc, geschoss_id) if geschoss_id else None
geschoss_name = (g.get("name") if g else "EG") or "EG"
layer2d = _ensure_layer(doc, _layer_path_symbole(doc, geschoss_name, "2d"))
layer3d = _ensure_layer(doc, _layer_path_symbole(doc, geschoss_name, "3d"))
# Import + place
undo_serial = doc.BeginUndoRecord(
"Symbol einfuegen: " + (item.get("name") or ""))
try:
ok, msg = library.import_item(doc, lib_id, at_point=pt,
layer2d=layer2d, layer3d=layer3d)
print("[ELEMENTE] CREATE_SYMBOL '{}': ok={} ({})".format(
item.get("name"), ok, msg))
finally:
try: doc.EndUndoRecord(undo_serial)
except Exception: pass
def _cmd_open_swisstopo(self, p):
"""Oeffnet map.geo.admin.ch im Default-Browser mit den swisstopo-
Layern voreingestellt. Param `mode`: 'buildings' | 'terrain' | 'both'.