Files
DOSSIER/rhino/elemente_uebersicht.py
karim 15fb0a6037 Elemente: BIM Project Browser + Properties-Satellite-Window
Zwei neue Satellite-Windows (analog Kamera/Text-Editor):

1) Projekt-Übersicht (elemente_uebersicht.py + ElementeUebersichtApp.jsx)
   - Tree Geschoss → Kind → Element-Instanzen
   - Suche + Kind-Filter-Chips
   - Klick = selektieren in Rhino, Shift+Klick = zoomen
   - Erreichbar via account_tree-Button im Elemente-Panel-Header

2) Properties-Satellite (elemente_properties.py + ElementePropertiesApp.jsx)
   - Eigenes Fenster mit der PropertiesView (gemeinsame Komponente)
   - Live-Updates: elemente._send_state forwarded zu satellite-bridge via sticky
   - Erreichbar via open_in_new-Icon oben rechts in der Properties-Karte
   - Inline-Properties im Panel bleiben — Satellite ist für mehr Platz

Plus ElementeApp-Cleanup:
- ElementList (alle Elemente-Liste) raus — wird jetzt von Projekt-
  Übersicht abgedeckt.
- Properties springen bei Selektion nach oben, NeuesElement bleibt
  voll sichtbar darunter (kein Scrollen mehr).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 01:17:31 +02:00

187 lines
6.5 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#! python 3
# -*- coding: utf-8 -*-
"""
elemente_uebersicht.py
BIM-artiger Project Browser: alle Smart-Elemente in einem Tree
gruppiert nach Geschoss → Kind → Element. Eigene Satellite-Window
(Eto.Form + WebView), liest seine Daten direkt aus dem ActiveDoc
via elemente._read_meta. Klick auf eine Zeile selektiert das Objekt
in Rhino.
"""
import os
import sys
import Rhino
import scriptcontext as sc
_HERE = os.path.dirname(os.path.abspath(__file__))
if _HERE not in sys.path:
sys.path.insert(0, _HERE)
import panel_base
import elemente as _elm
_KIND_MAP = {
"wand_axis": "wand",
"decke_outline": "decke",
"dach_outline": "dach",
"treppe_axis": "treppe",
"stuetze_point": "stuetze",
"traeger_axis": "traeger",
"raum_outline": "raum",
"decke_aussparung_outline": "aussparung",
"oeffnung_point": "oeffnung", # wird zu fenster/tuer aufgeloest
}
def _safe_float(v, default=None):
try: return float(v)
except Exception: return default
def _build_overview(doc):
"""Sammelt alle Smart-Element-Sources, gruppiert nach Geschoss +
Kind. Returns dict mit 'geschosse' (geordnete Liste) + 'items'
(Flat-Liste pro Geschoss/Kind). Frontend baut den Tree."""
if doc is None:
return {"geschosse": [], "items": []}
geschosse = _elm._load_geschosse(doc) or []
items = []
seen = set()
for obj in doc.Objects:
meta = _elm._read_meta(obj)
if meta is None: continue
t = meta.get("type")
if t not in _elm.SOURCE_TYPES: continue
if meta["id"] in seen: continue
seen.add(meta["id"])
kind = _KIND_MAP.get(t, t)
if t == "oeffnung_point":
kind = meta.get("oeff_typ", "fenster")
g = _elm._geschoss_by_id(doc, meta.get("geschoss"))
g_id = (g.get("id") if g else "") or "__keingeschoss__"
g_name = g.get("name") if g else "(kein Geschoss)"
# Kompakte Property-Zusammenfassung pro Element-Typ
info = ""
try:
if kind == "wand":
info = "d {:.2f} m".format(meta.get("dicke", 0) or 0)
elif kind == "decke":
info = "d {:.2f} m".format(meta.get("dicke", 0) or 0)
elif kind == "dach":
info = "d {:.2f} m · {:.0f}°".format(
meta.get("dicke", 0) or 0, meta.get("neigung", 0) or 0)
elif kind in ("fenster", "tuer"):
info = "{:.2f}×{:.2f} m".format(
meta.get("oeff_breite", 0) or 0,
meta.get("oeff_hoehe", 0) or 0)
elif kind == "treppe":
info = "{} St".format(meta.get("treppe_n_stufen", "?"))
elif kind in ("stuetze", "traeger"):
profil = meta.get("trag_profil", "?")
info = "{}".format(profil)
elif kind == "raum":
info = meta.get("raum_name", "") or "Raum"
elif kind == "aussparung":
info = "Aussparung"
except Exception: pass
items.append({
"id": meta["id"],
"objectId": str(obj.Id),
"kind": kind,
"geschossId": g_id,
"geschossName": g_name,
"name": meta.get("raum_name") or "",
"info": info,
"selected": obj.IsSelected(False) > 0,
})
# Geschoss-Liste (geordnet wie in doc.Strings)
out_geschosse = []
for g in geschosse:
if not isinstance(g, dict): continue
out_geschosse.append({
"id": g.get("id") or "",
"name": g.get("name") or "?",
"okff": _safe_float(g.get("okff"), 0.0),
})
# "(kein Geschoss)" anhaengen wenn es Elemente ohne Geschoss gibt
if any(it["geschossId"] == "__keingeschoss__" for it in items):
out_geschosse.append({
"id": "__keingeschoss__", "name": "(kein Geschoss)", "okff": None,
})
return {"geschosse": out_geschosse, "items": items}
class ElementeUebersichtBridge(panel_base.BaseBridge):
def __init__(self):
panel_base.BaseBridge.__init__(self, "elemente_uebersicht")
def _send_state(self):
doc = Rhino.RhinoDoc.ActiveDoc
self.send("STATE", _build_overview(doc))
def _on_ready(self):
self._send_state()
def handle(self, data):
if not isinstance(data, dict): return
t = data.get("type", "")
p = data.get("payload") or {}
if not isinstance(p, dict): p = {}
doc = Rhino.RhinoDoc.ActiveDoc
if t == "READY" or t == "REQUEST_STATE":
self._on_ready()
elif t == "SELECT_ELEMENT":
obj_id_str = p.get("objectId") or ""
try:
import System
guid = System.Guid(obj_id_str)
obj = doc.Objects.FindId(guid)
if obj is not None:
doc.Objects.UnselectAll()
obj.Select(True)
try: doc.Views.Redraw()
except Exception: pass
except Exception as ex:
print("[UEBERSICHT] select:", ex)
self._send_state()
elif t == "ZOOM_TO_ELEMENT":
obj_id_str = p.get("objectId") or ""
try:
import System
guid = System.Guid(obj_id_str)
obj = doc.Objects.FindId(guid)
if obj is not None:
doc.Objects.UnselectAll()
obj.Select(True)
try:
vp = doc.Views.ActiveView.ActiveViewport
bb = obj.Geometry.GetBoundingBox(True)
if bb.IsValid:
bb.Inflate(bb.Diagonal.Length * 0.5,
bb.Diagonal.Length * 0.5,
bb.Diagonal.Length * 0.5)
vp.ZoomBoundingBox(bb)
doc.Views.Redraw()
except Exception as ex:
print("[UEBERSICHT] zoom:", ex)
except Exception as ex:
print("[UEBERSICHT] zoom find:", ex)
def open_as_window():
"""Oeffnet die Element-Uebersicht als Satellite-Window."""
b = ElementeUebersichtBridge()
sc.sticky["elemente_uebersicht_bridge"] = b
panel_base.open_satellite_window(
"elemente_uebersicht",
title="Elemente — Übersicht",
size=(540, 720),
bridge=b)