Snapshot: Wand/Öffnung Multi-Surface-Select + Z-Drag + Brüstungs-Mitnahme
Stable working state after a long iteration session. The plugin now supports:
- Multi-Surface-Select für alle Element-Typen (Türen/Fenster/Treppen/Tragwerk)
- Wand-Z-Drag → unbound mode (UK/OK-Override, Wand vom Geschoss entkoppelt)
- Wand-Z-Drag nimmt verknüpfte Öffnungen mit (Brüstung += delta_z via Idle-Pfad)
- Öffnungs-XY-Drag snapt direktional auf Wand-Tangente
- Öffnungs-Z-Drag passt Brüstung an (Fenster sofort sync, Tür deferred)
- Wand-Delete kaskadiert Öffnungen (deferred via Idle, robust gegen _Rotate/_Move)
- Source-Cascade beim Öffnungs-Delete (deferred analog Wand-Kaskade)
- Listener-Cleanup robust gegen _reset_panels.py Reload (Refs in
_dossier_runtime_event_refs gespeichert, vor Re-Install deregistriert)
- _count_same_id_type filtert IsDeleted (verhindert Source-Duplikat-Bug bei Move)
- Frontend: Brüstungs-Slider für Tür ("Schwelle"), Flügel-Block nur bei Fenster
Plus aus früherer Phase dieser Session:
- Dossier-Launcher Auto-Load via Rhinos StartupCommands-XML
- Default-Pfad zeigt auf gebundeltes startup.py (out-of-the-box für neue User)
- Splash-Window beim Plugin-Load mit native macOS rounded corners
- Diverse Launcher-Verbesserungen (Brüstungs-Default, tauri.conf, capabilities)
Known issue: bei Multi-Select-Move mit vielen Sub-Volumen kann sporadisch
"Unable to transform" auftreten (Rhinos Move-Operation kollidiert mit Wand-
Regen). Tür-spezifischer Defer-Pfad mildert das, Fenster läuft sync.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+91
-3
@@ -1,4 +1,4 @@
|
||||
# ! python3
|
||||
#! python 3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
rhinopanel.py
|
||||
@@ -44,6 +44,34 @@ def _hatch_pattern_names(doc):
|
||||
return out
|
||||
|
||||
|
||||
def _read_launcher_schema():
|
||||
"""Liest das Default-Layer-Schema aus dossier_settings.json (Launcher-Pfad).
|
||||
Liefert eine Liste {code, name, color, lw} oder None wenn nicht gesetzt."""
|
||||
paths = [
|
||||
os.path.expanduser("~/Library/Application Support/"
|
||||
"ch.gabrielevarano.Dossier/dossier_settings.json"),
|
||||
os.path.expanduser("~/Library/Application Support/"
|
||||
"RhinoPanel/dossier_settings.json"),
|
||||
]
|
||||
for p in paths:
|
||||
try:
|
||||
if not os.path.isfile(p): continue
|
||||
with open(p, "rb") as f:
|
||||
data = json.loads(f.read().decode("utf-8"))
|
||||
schema = (data or {}).get("layerSchema")
|
||||
if isinstance(schema, list) and schema:
|
||||
# Sanity: alle vier Pflichtfelder vorhanden
|
||||
clean = [r for r in schema
|
||||
if isinstance(r, dict)
|
||||
and r.get("code") and r.get("name")
|
||||
and r.get("color") is not None
|
||||
and r.get("lw") is not None]
|
||||
if clean: return clean
|
||||
except Exception as ex:
|
||||
print("[EBENEN] launcher-schema lesen ({}):".format(p), ex)
|
||||
return None
|
||||
|
||||
|
||||
class EbenenBridge(panel_base.BaseBridge):
|
||||
def __init__(self):
|
||||
panel_base.BaseBridge.__init__(self, "ebenen")
|
||||
@@ -68,7 +96,15 @@ class EbenenBridge(panel_base.BaseBridge):
|
||||
except Exception as ex:
|
||||
print("[EBENEN] State-Sync:", ex)
|
||||
else:
|
||||
self.send("FIRST_RUN", {"hatchPatterns": _hatch_pattern_names(doc)})
|
||||
payload = {"hatchPatterns": _hatch_pattern_names(doc)}
|
||||
# Falls der User im Launcher eigene Default-Ebenen definiert hat,
|
||||
# mitschicken — React nimmt's statt seiner hardcoded INITIAL_EBENEN.
|
||||
launcher_schema = _read_launcher_schema()
|
||||
if launcher_schema:
|
||||
payload["defaultEbenen"] = launcher_schema
|
||||
print("[EBENEN] FIRST_RUN mit Launcher-Schema ({} Ebenen)".format(
|
||||
len(launcher_schema)))
|
||||
self.send("FIRST_RUN", payload)
|
||||
|
||||
def handle(self, data):
|
||||
if not isinstance(data, dict):
|
||||
@@ -103,6 +139,11 @@ class EbenenBridge(panel_base.BaseBridge):
|
||||
self._move_selection_to_layer(p.get("code", ""))
|
||||
elif t == "SET_VISIBILITY":
|
||||
self._apply_visibility(p)
|
||||
elif t == "SET_CLIPPING":
|
||||
# Toggle ohne Full-Apply — wirkt live auf das aktuell aktive
|
||||
# Geschoss. Erwartet payload {enabled: bool}.
|
||||
enabled = bool(p.get("enabled"))
|
||||
self._toggle_clipping_for_active(enabled)
|
||||
# --- Ebenen-Kombinationen (geteilter Store mit Ausschnitten) -------
|
||||
elif t == "GET_COMBINATION":
|
||||
self._send_combination()
|
||||
@@ -315,6 +356,45 @@ class EbenenBridge(panel_base.BaseBridge):
|
||||
print("[EBENEN] CPlane fehler ({}): {}".format(vp.Name if vp else "?", ex))
|
||||
print("[EBENEN] CPlane Z={} auf {} Top-Style View(s) gesetzt".format(okff, updated))
|
||||
|
||||
def _toggle_clipping_for_active(self, enabled):
|
||||
"""Setzt hasClipping fuer das aktuell aktive Geschoss + persistiert in
|
||||
doc.Strings + triggert plane-update. Wird vom React-Toggle 'Clipping
|
||||
Plane' direkt aufgerufen (ohne Full-Apply)."""
|
||||
doc = Rhino.RhinoDoc.ActiveDoc
|
||||
z_raw = doc.Strings.GetValue("dossier_zeichnungsebenen")
|
||||
active_id = doc.Strings.GetValue("dossier_active_id")
|
||||
if not z_raw or not active_id:
|
||||
print("[CLIP] toggle: kein aktives Geschoss")
|
||||
return
|
||||
try:
|
||||
z_list = json.loads(z_raw)
|
||||
except Exception as ex:
|
||||
print("[CLIP] toggle JSON-decode:", ex); return
|
||||
active_z = None
|
||||
for z in z_list:
|
||||
if z.get("id") == active_id:
|
||||
z["hasClipping"] = bool(enabled)
|
||||
active_z = z
|
||||
break
|
||||
if active_z is None:
|
||||
print("[CLIP] toggle: active_id={} nicht in Liste".format(active_id))
|
||||
return
|
||||
try:
|
||||
doc.Strings.SetString("dossier_zeichnungsebenen",
|
||||
json.dumps(z_list, ensure_ascii=False))
|
||||
except Exception as ex:
|
||||
print("[CLIP] toggle SetString:", ex)
|
||||
self._update_clipping(active_z=active_z)
|
||||
# State an React zurueckspiegeln, damit das Eye-Icon im GeschossManager
|
||||
# synchron bleibt.
|
||||
try:
|
||||
self.send("STATE_SYNC", {
|
||||
"zeichnungsebenen": z_list,
|
||||
"ebenen": json.loads(doc.Strings.GetValue("dossier_ebenen") or "[]"),
|
||||
"hatchPatterns": _hatch_pattern_names(doc),
|
||||
})
|
||||
except Exception: pass
|
||||
|
||||
def _update_clipping(self, active_z=None):
|
||||
"""Clipping-Plane folgt aktivem Geschoss — nur wenn dessen hasClipping=True."""
|
||||
doc = Rhino.RhinoDoc.ActiveDoc
|
||||
@@ -327,6 +407,14 @@ class EbenenBridge(panel_base.BaseBridge):
|
||||
active_z = next((z for z in z_list if z.get("id") == active_id), None)
|
||||
except Exception:
|
||||
active_z = None
|
||||
# Volles Dump des Active-Geschosses fuer Diagnose. Wenn hier
|
||||
# hasClipping fehlt aber im UI gesetzt wurde, hat React's Dialog die
|
||||
# Daten nicht weitergereicht (haeufig: WebView-Cache zeigt alte JS).
|
||||
try:
|
||||
print("[CLIP] active_z keys: {}".format(
|
||||
sorted(active_z.keys()) if active_z else None))
|
||||
print("[CLIP] active_z dump: {}".format(json.dumps(active_z, ensure_ascii=False)))
|
||||
except Exception: pass
|
||||
enabled = bool(active_z and active_z.get("hasClipping"))
|
||||
_set_processing(True)
|
||||
try:
|
||||
@@ -795,4 +883,4 @@ def _install_layer_listener(bridge):
|
||||
|
||||
|
||||
panel_base.register_and_open("ebenen", "EBENEN", PANEL_GUID_STR, _ebenen_bridge_factory,
|
||||
icon_spec=("E", "#3a6fa8"))
|
||||
icon_spec=("layers", "#3a6fa8"))
|
||||
|
||||
Reference in New Issue
Block a user