Files
DOSSIER/rhino/elemente_uebersicht.py
T
karim 13a5e1eb7a AGPL-3.0 Dual-Lizenz + Pill-Stil-UI + Section-Style-Overhaul + Plan-Mode-Template
Lizenz:
- AGPL-3.0 LICENSE-File im Repo-Root (GNU Volltext)
- SPDX-Header + Copyright in allen Source-Files (Python/JSX/JS/Rust)
- license-Feld in package.json + Cargo.toml
- About-App komplett neu: Dual-Lizenz-Block (AGPL + Commercial),
  openbureau-Branding, Version-Pills, made-in-Switzerland-Footer

UI-Restyle (3 Wellen) — alle Dialoge + Satellites + Panel-Sidebars
auf gemeinsamen Pill-Stil aus BarControls (BarToggle/BarButton/BarCombo):
- Welle 1: GeschossDialog/Settings, AusschnittSettings, LayoutDialog
- Welle 2: ConfirmDeleteEbene, Kamera, MasseSettings, Osm, Swisstopo,
  TextEditor, AusschnittLayerDialog, LayerCombinations
- Welle 3: LayoutsApp, MassstabApp, WerkzeugeApp, OverridesApp,
  ZeichnungsebenenApp; Werkzeuge mit ElementeApp-PillGroup-Layout

GeschossDialog Header-Refactor: +Geschoss/+Zeichnung in Toolbar oben,
move-Pfeile-Spalte breiter (kein Overlap mit G-Haken)

Ausschnitte Rows als Pills, kein Outer-Border ums Suchfeld

Section-Style komplett neu (gestaltung.py + GestaltungApp.jsx):
- ObjectSectionAttributesSource.FromObject (richtiger Enum-Name fuer Mac)
- HatchPatternPrintColor + BoundaryPrintColor mit-setzen (Display = Print)
- BoundaryColor nur bei explizitem User-Override, sonst Rhino-Default
- background_color_hex Parameter (BackgroundFillMode=SolidColor)
- Readback aus GetCustomSectionStyle statt direkt aus Attributes
- UI: Schnittkante > Section Style > Solid-Fill mit proper SectionHead
- 'Boundary' (3D Pen) -> 'Background' weil sich's wie Section-Hintergrund verhaelt

Plan-Mode 'Dossier Plan' via Template:
- rhino/templates/dossier_plan.ini wird direkt geladen
- Fallback auf Technical-Clone + ini-Patch wenn Template fehlt
- Auto-Cleanup von Orphan-Modes vor Import (Name- oder Guid-Match)
- ClipSectionUsage=1 + TechnicalMask=15 als bekannte Soll-Werte
- Bei Template-Pfad keine ini-Patches (1:1 wie User exportiert)
- Sanity-Print listet alle registrierten Modes nach Anlegen

Bridge-Unification: 4 Settings-Apps (Ebenen/Project/Geschoss*Dialog)
benutzen jetzt chunkende send() statt eigene bridgeSend ohne Chunk-
Logik -> grosse Payloads (Hatch-Refs etc.) kommen nicht mehr truncated
bei Python an (loeste 'JSON-Fehler char 990'-Regression in Ebenen-
Settings)

Library-Imports robust: 'import library' jetzt Top-Level in elemente.py
+ rhinopanel.py (statt Lazy in Methoden) -> 'No module named library'-
Crashes weg auch wenn sys.path zwischendurch resettet wird

Tools fuer Display-Mode-Maintenance:
- _clean_display_modes.py (loescht alle Custom-Modes, Built-ins bleiben)
- _inspect_plan_mode.py / _inspect_obj_section.py / _inspect_obj_boundary.py
  (Diagnose-Skripte fuer SectionStyle-Property-Reverse-Engineering)
- _reset_rhino_settings.sh (Backup + Nuke der Rhino-Settings als
  letzte Bastion gegen korrupte Display-Modes)
2026-05-26 17:09:18 +02:00

189 lines
6.6 KiB
Python
Raw 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 -*-
# SPDX-License-Identifier: AGPL-3.0-or-later
# Copyright (C) 2026 Karim Gabriele Varano
"""
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)