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:
@@ -0,0 +1,142 @@
|
||||
# ! python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
startup.py
|
||||
Laedt DOSSIER-Panels beim Rhino-Start. Liest pro geoeffnetem Dokument eine
|
||||
`dossier.project.json` (neben der `.3dm` abgelegt vom Dossier-Launcher) und
|
||||
aktiviert nur die dort gelisteten Module. Fehlt die Datei → alle bekannten
|
||||
Module laden (Backwards-Compat fuer Setups ohne Launcher).
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import Rhino
|
||||
import scriptcontext as sc
|
||||
|
||||
# DIAGNOSE — welcher Python-Engine laeuft hier wirklich? Einmalig beim Start.
|
||||
print("[STARTUP] Python: {}".format(sys.version))
|
||||
print("[STARTUP] Implementation: {}".format(
|
||||
sys.implementation.name if hasattr(sys, "implementation") else "n/a (IPy2)"))
|
||||
print("[STARTUP] Platform: {}".format(sys.platform))
|
||||
|
||||
_HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
if _HERE not in sys.path:
|
||||
sys.path.insert(0, _HERE)
|
||||
|
||||
# Pfad zur Custom-UI (Toolbars/Sidebar) — wird einmal pro Session geladen
|
||||
_UI_FILE = os.path.join(_HERE, "DOSSIERUI.rhw")
|
||||
|
||||
# Map: Modul-ID (aus dossier.project.json) -> Python-Modulname (Datei in rhino/).
|
||||
# Muss synchron sein mit launcher/modules.json. Wenn neue Module dazukommen,
|
||||
# beide Stellen pflegen.
|
||||
_MODULE_TO_PY = {
|
||||
"ebenen": "rhinopanel",
|
||||
"oberleiste": "oberleiste",
|
||||
"ausschnitte": "ausschnitte",
|
||||
"gestaltung": "gestaltung",
|
||||
"werkzeuge": "werkzeuge",
|
||||
"overrides": "overrides_panel",
|
||||
"dimensionen": "dimensionen",
|
||||
"layouts": "layouts",
|
||||
"elemente": "elemente",
|
||||
}
|
||||
|
||||
_ALL_MODULES = list(_MODULE_TO_PY.keys())
|
||||
|
||||
|
||||
def _read_project_config():
|
||||
"""Liest dossier.project.json aus dem Ordner des aktiven Docs. Rueckgabe:
|
||||
dict oder None. None heisst „keine Config" -> Fallback alle Module."""
|
||||
try:
|
||||
doc = Rhino.RhinoDoc.ActiveDoc
|
||||
if doc is None or not getattr(doc, "Path", None):
|
||||
return None
|
||||
doc_dir = os.path.dirname(doc.Path)
|
||||
if not doc_dir:
|
||||
return None
|
||||
config_path = os.path.join(doc_dir, "dossier.project.json")
|
||||
if not os.path.isfile(config_path):
|
||||
return None
|
||||
with open(config_path, "rb") as f:
|
||||
data = json.loads(f.read().decode("utf-8"))
|
||||
return data if isinstance(data, dict) else None
|
||||
except Exception as ex:
|
||||
print("[STARTUP] Project-Config lesen:", ex)
|
||||
return None
|
||||
|
||||
|
||||
def _migrate_active_doc(*_):
|
||||
"""Migriert Legacy-Keys (traite_*, pause_*) -> dossier_* fuer das aktive Doc."""
|
||||
try:
|
||||
import panel_base
|
||||
panel_base.migrate_to_dossier(Rhino.RhinoDoc.ActiveDoc)
|
||||
except Exception as ex:
|
||||
print("[STARTUP] Migration:", ex)
|
||||
|
||||
|
||||
def _on_doc_opened(sender, e):
|
||||
"""Greift bei jedem geoeffneten Doc nach Rhino-Start. Migration ist
|
||||
idempotent (Flag in doc.Strings)."""
|
||||
try:
|
||||
doc = e.Document if hasattr(e, "Document") else Rhino.RhinoDoc.ActiveDoc
|
||||
import panel_base
|
||||
panel_base.migrate_to_dossier(doc)
|
||||
except Exception as ex:
|
||||
print("[STARTUP] _on_doc_opened:", ex)
|
||||
|
||||
|
||||
def _hint_dossier_ui():
|
||||
"""Mac Rhino 8 kann Window-Layout-Dateien nicht via Skript laden — der
|
||||
Dialog ueber Window-Menue nutzt interne API ohne Command-Echo. Wir
|
||||
geben nur einen Hinweis-Pfad aus, damit der User DOSSIERUI.rhw einmal
|
||||
manuell laden kann. Rhino merkt sich die Anordnung dann persistent."""
|
||||
if not os.path.isfile(_UI_FILE):
|
||||
return
|
||||
print("[STARTUP] DOSSIERUI gefunden: {}".format(_UI_FILE))
|
||||
print("[STARTUP] Einmalig laden: Window -> Window Layout -> Open -> obige Datei")
|
||||
print("[STARTUP] Anordnung bleibt danach ueber Rhino-Restarts erhalten.")
|
||||
|
||||
|
||||
def _load_all(sender, e):
|
||||
"""Wird beim ersten Idle ausgefuehrt — entkoppelt sich danach selbst."""
|
||||
try:
|
||||
Rhino.RhinoApp.Idle -= _load_all
|
||||
except Exception:
|
||||
pass
|
||||
print("[STARTUP] Lade DOSSIER-Panels...")
|
||||
# Migration einmal fuer das beim Start aktive Doc
|
||||
_migrate_active_doc()
|
||||
# Und Listener fuer spaeter geoeffnete Docs registrieren
|
||||
try:
|
||||
Rhino.RhinoDoc.EndOpenDocument += _on_doc_opened
|
||||
except Exception as ex:
|
||||
print("[STARTUP] EndOpenDocument-Hook:", ex)
|
||||
# Projekt-Config bestimmt, welche Module geladen werden. Ohne Config
|
||||
# (kein Launcher benutzt, oder Datei nicht da) laedt der Host alles.
|
||||
config = _read_project_config()
|
||||
if config and isinstance(config.get("modules"), list):
|
||||
enabled_ids = [m for m in config["modules"] if m in _MODULE_TO_PY]
|
||||
unknown = [m for m in config["modules"] if m not in _MODULE_TO_PY]
|
||||
print("[STARTUP] Projekt: '{}'".format(config.get("name") or "?"))
|
||||
print("[STARTUP] Aktivierte Module: {}".format(", ".join(enabled_ids) or "(keine)"))
|
||||
if unknown:
|
||||
print("[STARTUP] Unbekannte Modul-IDs in Config: {}".format(unknown))
|
||||
else:
|
||||
enabled_ids = _ALL_MODULES
|
||||
print("[STARTUP] Keine dossier.project.json — alle Module laden")
|
||||
# massstab.py wird als Library mitgeladen (von oberleiste/ausschnitte/...)
|
||||
# und braucht hier nicht mehr als eigenstaendiges Panel zu erscheinen.
|
||||
for mod_id in enabled_ids:
|
||||
py_name = _MODULE_TO_PY[mod_id]
|
||||
try:
|
||||
__import__(py_name)
|
||||
print("[STARTUP] {} ({}) OK".format(mod_id, py_name))
|
||||
except Exception as ex:
|
||||
print("[STARTUP] {} ({}) FEHLER: {}".format(mod_id, py_name, ex))
|
||||
# DOSSIERUI Window-Layout — Hinweis fuer manuelles Laden
|
||||
_hint_dossier_ui()
|
||||
print("[STARTUP] Fertig")
|
||||
|
||||
|
||||
Rhino.RhinoApp.Idle += _load_all
|
||||
print("[STARTUP] geplant - laedt sobald Rhino idle ist")
|
||||
Reference in New Issue
Block a user