Files
DOSSIER/rhino/startup.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

178 lines
6.9 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)
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))
# 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)
# 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")