Display-Modes: 3D-Template + Auto-Assign + Material/Raytracing-Slots

- 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)
This commit is contained in:
2026-05-26 18:26:49 +02:00
parent 13a5e1eb7a
commit 02a00a9b4a
5 changed files with 497 additions and 59 deletions
+79 -35
View File
@@ -185,9 +185,23 @@ def _import_display_modes(paths):
return count
# Fest-Guid fuer 'Dossier Plan' damit Re-Imports denselben Slot
# Fest-Guids fuer die Dossier-Display-Modes damit Re-Imports denselben Slot
# wiederverwenden statt Duplikate zu erzeugen.
_DOSSIER_PLAN_GUID = "d0551e72-7e72-4170-b1a4-d0551e72d055"
_DOSSIER_PLAN_GUID = "d0551e72-7e72-4170-b1a4-d0551e72d055"
_DOSSIER_3D_GUID = "d0551e72-7e72-4170-b1a4-3d3d3d3d3d3d"
_DOSSIER_MATERIAL_GUID = "d0551e72-7e72-4170-b1a4-555555555555"
_DOSSIER_RAYTRACING_GUID = "d0551e72-7e72-4170-b1a4-666666666666"
# Registrierte Dossier-Display-Modes: (name, guid, template_basename, default_pipeline_fallback)
# Material + Raytracing haben (noch) kein Template — werden vom Fallback-Base
# (Rendered / Raytraced) geklont. User kann sie in Rhino anpassen und spaeter
# als Template exportieren.
_DOSSIER_DISPLAY_MODES = (
("Dossier Plan", _DOSSIER_PLAN_GUID, "dossier_plan.ini", "Technical"),
("Dossier 3D", _DOSSIER_3D_GUID, "dossier_3d.ini", "Shaded"),
("Dossier Material", _DOSSIER_MATERIAL_GUID, "dossier_material.ini", "Rendered"),
("Dossier Raytracing", _DOSSIER_RAYTRACING_GUID, "dossier_raytracing.ini", "Raytraced"),
)
def _apply_dossier_plan_attrs(dmd):
"""Wendet die Dossier-Plan-Visual-Settings auf einen DisplayMode an.
@@ -303,38 +317,54 @@ def _apply_dossier_plan_attrs(dmd):
print("[OBERLEISTE] Plan-Mode update:", ex)
_TEMPLATE_INI_PATH = os.path.join(_HERE, "templates", "dossier_plan.ini")
_TEMPLATES_DIR = os.path.join(_HERE, "templates")
def _ensure_dossier_plan_display_mode():
"""Stellt sicher dass der 'Dossier Plan' Display-Mode existiert.
def _ensure_dossier_display_mode(target_name, target_guid, template_basename,
fallback_base_name):
"""Stellt sicher dass ein Dossier-Display-Mode existiert.
Strategie: wenn eine Template-ini im Repo existiert
(rhino/templates/dossier_plan.ini), laden wir die. Sonst Fallback auf
Clone-Technical + ini-Patch. Template ist die bevorzugte Methode weil
sich Mac-Rhino-Display-Mode-Properties via Python-API unzuverlaessig
setzen lassen — der User baut den Mode einmal manuell perfekt zusammen
und exportiert ihn dort hin.
(rhino/templates/<template_basename>), laden wir die. Sonst Fallback auf
Clone-eines-Built-in-Mode + ini-Patch. Template ist die bevorzugte
Methode weil sich Mac-Rhino-Display-Mode-Properties via Python-API
unzuverlaessig setzen lassen.
Args:
target_name: 'Dossier Plan' / 'Dossier 3D' / ...
target_guid: Fix-Guid die wir fuer Re-Imports wiederverwenden
template_basename: 'dossier_plan.ini' / 'dossier_3d.ini'
fallback_base_name: 'Technical' / 'Shaded' — Mode zum Klonen
"""
print("[OBERLEISTE] Plan-Mode: check...")
print("[OBERLEISTE] {}: check...".format(target_name))
try:
from Rhino.Display import DisplayModeDescription
except Exception as ex:
print("[OBERLEISTE] Plan-Mode: DMD nicht verfuegbar:", ex)
print("[OBERLEISTE] {}: DMD nicht verfuegbar: {}".format(target_name, ex))
return False
import re # fuer ini-checks unten
target_name = "Dossier Plan"
import re
template_ini_path = os.path.join(_TEMPLATES_DIR, template_basename)
try:
import System
target_guid_obj = System.Guid(_DOSSIER_PLAN_GUID)
target_guid_obj = System.Guid(target_guid)
except Exception:
target_guid_obj = None
# Template-Datei vorhanden? Wenn ja, Hash davon als "version key"
# benutzen — wir nur neu importieren wenn sich die Template-Datei
# geaendert hat.
template_exists = os.path.isfile(_TEMPLATE_INI_PATH)
print("[OBERLEISTE] Plan-Mode template: {}".format(
"found at " + _TEMPLATE_INI_PATH if template_exists else "missing → fallback"))
template_exists = os.path.isfile(template_ini_path)
print("[OBERLEISTE] {} template: {}".format(target_name,
"found at " + template_ini_path if template_exists else "missing"))
# Fallback-Clone nur fuer "Dossier Plan" erlauben. Bei anderen Modes
# ohne Template skippen — sonst klonen wir z.B. den Raytraced-Mode mit
# Cycles-PipelineId, was Rhinos Display-Mode-State auf Mac korrumpieren
# und ALLE Modes nach Restart verschwinden lassen kann. User soll den
# Mode in Rhino bauen + per "Save As" -> templates/<basename> exportieren.
if not template_exists and target_name != "Dossier Plan":
print("[OBERLEISTE] {}: kein Template → skip (in Rhino bauen, "
"Display-Mode -> Save As -> {})".format(
target_name, template_ini_path))
return False
# Schon registriert?
try:
existing = None
@@ -385,23 +415,24 @@ def _ensure_dossier_plan_display_mode():
# exportiert) — sonst Fallback auf Technical-Clone.
# ----------------------------------------------------------------
import tempfile
tmp_path = os.path.join(tempfile.gettempdir(), "dossier_plan.ini")
tmp_path = os.path.join(tempfile.gettempdir(), template_basename)
base = None
if template_exists:
# Template-ini lesen + ueberschreiben den tmp_path
try:
with open(_TEMPLATE_INI_PATH, "r", encoding="utf-8", errors="ignore") as f:
with open(template_ini_path, "r", encoding="utf-8", errors="ignore") as f:
content = f.read()
print("[OBERLEISTE] Plan-Mode: Template geladen ({} bytes)".format(len(content)))
print("[OBERLEISTE] {}: Template geladen ({} bytes)".format(
target_name, len(content)))
except Exception as ex:
print("[OBERLEISTE] Plan-Mode Template read:", ex)
print("[OBERLEISTE] {} Template read: {}".format(target_name, ex))
return False
else:
# Fallback: Technical exportieren + patchen
# Fallback: einen Base-Mode exportieren + patchen
try:
all_modes = list(DisplayModeDescription.GetDisplayModes())
except Exception: all_modes = []
for prefer in ("Technical", "Pen", "Shaded"):
for prefer in (fallback_base_name, "Technical", "Pen", "Shaded"):
for dm in all_modes:
try:
if dm.EnglishName == prefer:
@@ -434,18 +465,24 @@ def _ensure_dossier_plan_display_mode():
r'(?i)^(\s*Name\s*=\s*)(.*)$',
r'\1' + target_name, content, count=1, flags=re.MULTILINE)
except Exception: pass
# Guid (im ini meist als [<guid>] Section-Header oder als "id="-Feld)
# Mode-Guid PRAEZISE per Section-Header finden — nicht irgendeine
# Guid im ini erwischen (PipelineId etc. sind eigene Guids die wir
# NICHT anfassen duerfen, sonst geht die Pipeline-Referenz kaputt
# und Rhino verliert den Mode bzw. crasht beim Klick).
try:
# Bestehende Guid aus dem File extrahieren + ueberall ersetzen
old_guid_match = re.search(
r'([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})',
r'\[DisplayMode\\([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})',
content)
if old_guid_match:
old_guid = old_guid_match.group(1)
content = content.replace(old_guid, _DOSSIER_PLAN_GUID)
content = content.replace(old_guid, target_guid)
content = content.replace(old_guid.upper(),
_DOSSIER_PLAN_GUID.upper())
except Exception: pass
target_guid.upper())
else:
print("[OBERLEISTE] {}: kein DisplayMode-Section-Header "
"gefunden — Mode-Guid nicht ersetzt".format(target_name))
except Exception as ex:
print("[OBERLEISTE] {} Guid-Replace: {}".format(target_name, ex))
# Plan-Mode-Settings in der ini patchen — Rhino-DisplayMode-ini hat
# nested Sections wie [DisplayMode\<guid>\Objects\Surfaces]. Wir
# patchen nur EXISTIERENDE Keys (Rhino's Parser stripped unbekannte
@@ -460,8 +497,12 @@ def _ensure_dossier_plan_display_mode():
# Bei Template-Pfad: NUR Name+Guid normalisieren, KEINE inhaltlichen
# Patches (User hat das Template bewusst so konfiguriert).
# Bei Fallback-Pfad (Technical-Clone): die bekannten Settings forcen.
if not template_exists:
# Bei Fallback-Pfad fuer Dossier Plan (Technical-Clone): die Plan-
# spezifischen Settings forcen. Andere Modes (Material/Raytracing)
# uebernehmen den Fallback-Base 1:1 — sonst wuerden Plan-Settings
# wie TechnicalMask in eine Rendered/Raytraced-ini gequetscht und
# alles zerschiessen.
if not template_exists and target_name == "Dossier Plan":
for key, val in (
("ClipSectionUsage", "1"),
("TechnicalMask", "15"),
@@ -2092,9 +2133,12 @@ def _bridge_factory():
# Custom Display-Mode 'Dossier Plan' beim Modul-Load registrieren — laeuft
# bei jedem startup.py oder _reset_panels.py, unabhaengig davon ob das
# Panel jemals geoeffnet wird. Funktion ist idempotent.
try: _ensure_dossier_plan_display_mode()
except Exception as ex:
print("[OBERLEISTE] ensure_dossier_plan_display_mode:", ex)
# Alle Dossier-Display-Modes registrieren (Plan, 3D, ...)
for _name, _guid, _tmpl, _fallback in _DOSSIER_DISPLAY_MODES:
try:
_ensure_dossier_display_mode(_name, _guid, _tmpl, _fallback)
except Exception as _ex:
print("[OBERLEISTE] ensure {}: {}".format(_name, _ex))
panel_base.register_and_open("oberleiste", "Oberleiste", PANEL_GUID_STR, _bridge_factory,