02a00a9b4a
- Neues Template rhino/templates/dossier_3d.ini fuer perspektivische Views - Registry-Loop in oberleiste.py generalisiert (Plan + 3D + Material + Raytracing) — Material/Raytracing skippen wenn kein Template vorhanden, um Cycles-Pipeline-Clone-Crash zu vermeiden - Guid-Replace praezisiert: nur Section-Header-Guid, nie PipelineId - Plan-spezifische ini-Patches auf target_name=="Dossier Plan" gegated - Auto-Assign in startup.py: Parallel-Viewports -> Plan, Perspective -> 3D, einmal-pro-Doc via doc.Strings-Flag (User-Overrides bleiben) - schnitte.activate_schnitt setzt Dossier Plan explizit (Hatches auch in Schnittperspektive sichtbar) - GestaltungApp Section-Block neu strukturiert: PenBlock fuer 3D als 'Fill' innerhalb der Section, Solid-Fill-Toggle entfernt (war Duplikat)
249 lines
9.4 KiB
Python
249 lines
9.4 KiB
Python
#! python 3
|
|
# -*- coding: utf-8 -*-
|
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
# Copyright (C) 2026 Karim Gabriele Varano
|
|
"""
|
|
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)
|
|
|
|
|
|
_DOC_FLAG_VIEW_MODES = "dossier_view_modes_initialized"
|
|
|
|
|
|
def _assign_default_display_modes(doc):
|
|
"""Setzt einmalig pro Doc die Display-Modes auf die Dossier-Defaults:
|
|
- Parallel-Projektionen (Top/Front/Right/Schnitt-parallel) -> 'Dossier Plan'
|
|
- Perspektive (Perspective/Schnittperspektive) -> 'Dossier 3D'
|
|
Persistiert einen Flag in doc.Strings → laeuft nur EINMAL pro Doc.
|
|
User-Overrides (manuelles Wechseln) bleiben damit erhalten.
|
|
"""
|
|
if doc is None: return
|
|
try:
|
|
if doc.Strings.GetValue(_DOC_FLAG_VIEW_MODES) == "1":
|
|
return # schon initialisiert
|
|
except Exception: pass
|
|
|
|
try:
|
|
from Rhino.Display import DisplayModeDescription
|
|
except Exception as ex:
|
|
print("[STARTUP] view-modes: DMD nicht verfuegbar:", ex); return
|
|
|
|
# Mode-Lookup per Name
|
|
mode_plan = mode_3d = None
|
|
try:
|
|
for dm in DisplayModeDescription.GetDisplayModes():
|
|
try:
|
|
n = dm.EnglishName
|
|
if n == "Dossier Plan": mode_plan = dm
|
|
elif n == "Dossier 3D": mode_3d = dm
|
|
except Exception: pass
|
|
except Exception as ex:
|
|
print("[STARTUP] view-modes: Mode-List:", ex); return
|
|
|
|
if mode_plan is None and mode_3d is None:
|
|
print("[STARTUP] view-modes: keine Dossier-Modes registriert — skip")
|
|
return
|
|
|
|
n_set = 0
|
|
try:
|
|
for view in doc.Views:
|
|
try:
|
|
vp = view.ActiveViewport
|
|
if vp is None: continue
|
|
is_par = bool(vp.IsParallelProjection)
|
|
target = mode_plan if is_par else mode_3d
|
|
if target is None: continue
|
|
try:
|
|
vp.DisplayMode = target
|
|
n_set += 1
|
|
except Exception as ex:
|
|
print("[STARTUP] view-modes set ({}): {}".format(
|
|
vp.Name, ex))
|
|
except Exception: pass
|
|
try:
|
|
doc.Views.Redraw()
|
|
except Exception: pass
|
|
except Exception as ex:
|
|
print("[STARTUP] view-modes iterate:", ex)
|
|
|
|
try:
|
|
doc.Strings.SetString(_DOC_FLAG_VIEW_MODES, "1")
|
|
except Exception: pass
|
|
print("[STARTUP] view-modes: {} Viewport(s) gesetzt".format(n_set))
|
|
|
|
|
|
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)
|
|
_assign_default_display_modes(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))
|
|
# Text-Editor Doppelklick-Hook fuer DOSSIER-Texte
|
|
try:
|
|
import text_editor
|
|
text_editor._ensure_double_click_hook()
|
|
except Exception as ex:
|
|
print("[STARTUP] text_editor hook:", ex)
|
|
# Display-Modes auf Default fuer aktives Doc setzen (einmalig)
|
|
try:
|
|
_assign_default_display_modes(Rhino.RhinoDoc.ActiveDoc)
|
|
except Exception as ex:
|
|
print("[STARTUP] view-modes assign:", ex)
|
|
# DOSSIERUI Window-Layout — Hinweis fuer manuelles Laden
|
|
_hint_dossier_ui()
|
|
# Startup-Timing-Summary 3 Sekunden spaeter (nachdem alle async Idle-
|
|
# Loads + WebView-Renders durch sind). Manueller Aufruf:
|
|
# _RunPythonScript -c "import panel_base; panel_base.print_startup_summary()"
|
|
def _summary():
|
|
try:
|
|
import panel_base
|
|
panel_base.print_startup_summary()
|
|
except Exception as ex:
|
|
print("[STARTUP] summary:", ex)
|
|
import threading
|
|
threading.Timer(3.0, _summary).start()
|
|
# Marker fuer den Launcher-Splash mit Verzoegerung: erst nachdem Rhino die
|
|
# Panels visuell platziert hat (~2s nach Modul-Imports). Pfad ist projekt-
|
|
# stabil (gleich wie dossier_settings.json), damit Launcher ohne
|
|
# Konfiguration weiss wohin er pollt.
|
|
def _write_marker():
|
|
try:
|
|
marker_dir = os.path.expanduser(
|
|
"~/Library/Application Support/ch.gabrielevarano.Dossier"
|
|
)
|
|
if os.path.isdir(marker_dir):
|
|
with open(os.path.join(marker_dir, "plugin_loaded.flag"), "w") as f:
|
|
f.write("ok")
|
|
except Exception as ex:
|
|
print("[STARTUP] marker schreiben:", ex)
|
|
import threading
|
|
threading.Timer(2.0, _write_marker).start()
|
|
print("[STARTUP] Fertig")
|
|
|
|
|
|
Rhino.RhinoApp.Idle += _load_all
|
|
print("[STARTUP] geplant - laedt sobald Rhino idle ist")
|