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)
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
#! python 3
|
||||
# -*- coding: utf-8 -*-
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
# Copyright (C) 2026 Karim Gabriele Varano
|
||||
"""
|
||||
oberleiste.py
|
||||
OBERLEISTE-Panel: horizontale Top-Bar mit Architektur-Kontext-Controls.
|
||||
@@ -183,6 +185,363 @@ def _import_display_modes(paths):
|
||||
return count
|
||||
|
||||
|
||||
# Fest-Guid fuer 'Dossier Plan' damit Re-Imports denselben Slot
|
||||
# wiederverwenden statt Duplikate zu erzeugen.
|
||||
_DOSSIER_PLAN_GUID = "d0551e72-7e72-4170-b1a4-d0551e72d055"
|
||||
|
||||
def _apply_dossier_plan_attrs(dmd):
|
||||
"""Wendet die Dossier-Plan-Visual-Settings auf einen DisplayMode an.
|
||||
Wird sowohl beim Erstanlegen als auch bei jedem Reload aufgerufen —
|
||||
so propagieren Attribut-Aenderungen aus dem Code automatisch."""
|
||||
try:
|
||||
from Rhino.Display import DisplayModeDescription
|
||||
except Exception:
|
||||
return
|
||||
attrs = dmd.DisplayAttributes
|
||||
# DEBUG: einmal pro Session alle relevanten Property-Namen loggen damit
|
||||
# wir sehen welche tatsaechlich existieren (Mac vs Win Rhino unterscheidet
|
||||
# sich) — sonst werden Set-Attempts still verschluckt.
|
||||
if not getattr(_apply_dossier_plan_attrs, "_props_logged", False):
|
||||
try:
|
||||
relevant = sorted([n for n in dir(attrs)
|
||||
if not n.startswith("_")
|
||||
and ("idden" in n or "ngent" in n or "ilho" in n
|
||||
or "ire" in n or "soc" in n or "dge" in n
|
||||
or "ech" in n or "hade" in n or "ection" in n)])
|
||||
print("[OBERLEISTE] Plan-Mode Attrs-Inventar:", ", ".join(relevant))
|
||||
# Sub-Objekt-Settings sind in Rhino 8 oft eigene Klassen
|
||||
for sub in ("CurveSettings", "ObjectSettings", "ShadingSettings",
|
||||
"MeshSpecificAttributes"):
|
||||
if hasattr(attrs, sub):
|
||||
obj = getattr(attrs, sub)
|
||||
sub_props = sorted([n for n in dir(obj)
|
||||
if not n.startswith("_")
|
||||
and ("idden" in n or "ngent" in n
|
||||
or "soc" in n or "ire" in n
|
||||
or "dge" in n)])
|
||||
if sub_props:
|
||||
print("[OBERLEISTE] Plan-Mode {}:".format(sub),
|
||||
", ".join(sub_props))
|
||||
except Exception as ex:
|
||||
print("[OBERLEISTE] Plan-Mode inspect:", ex)
|
||||
_apply_dossier_plan_attrs._props_logged = True
|
||||
# Surfaces gefuellt + weiss + kein Shading
|
||||
try: attrs.ShadingEnabled = False
|
||||
except Exception: pass
|
||||
try: import System.Drawing as SD
|
||||
except Exception: SD = None
|
||||
if SD:
|
||||
try:
|
||||
white = SD.Color.FromArgb(255, 255, 255, 255)
|
||||
try: attrs.ShadeSurfaceColor = white
|
||||
except Exception: pass
|
||||
try: attrs.BackgroundColor = white
|
||||
except Exception: pass
|
||||
try: attrs.BackgroundColorTop = white
|
||||
except Exception: pass
|
||||
try: attrs.BackgroundColorBottom = white
|
||||
except Exception: pass
|
||||
except Exception: pass
|
||||
# Wires + Isocurves AUS — sonst Plan-Linien-Noise.
|
||||
# Property-Namen wie sie auf Mac Rhino 8 tatsaechlich existieren
|
||||
# (per Inspektion verifiziert — frueher hatten wir falsche Schreibweisen
|
||||
# die das try/except still verschluckt hat).
|
||||
try: attrs.ShowIsoCurves = False # NICHT ShowIsocurves!
|
||||
except Exception: pass
|
||||
try: attrs.SurfaceIsoThicknessUsed = False
|
||||
except Exception: pass
|
||||
try: attrs.SurfaceIsoColorsUsed = False
|
||||
except Exception: pass
|
||||
try: attrs.ShowTangentEdges = False
|
||||
except Exception: pass
|
||||
try: attrs.ShowTangentSeams = False
|
||||
except Exception: pass
|
||||
try: attrs.ShowSurfaceEdges = True
|
||||
except Exception: pass
|
||||
try: attrs.ShowSurfaceEdge = True # Singular existiert auch
|
||||
except Exception: pass
|
||||
# Mesh-Wires AUS — die liegen auf dem Sub-Objekt MeshSpecificAttributes,
|
||||
# nicht direkt auf attrs. Das sind in Top-Views haeufig die "feinen
|
||||
# Punkte" auf Brep-Wand-Volumen (Rhino mesht intern fuer Display).
|
||||
try:
|
||||
if hasattr(attrs, "MeshSpecificAttributes"):
|
||||
attrs.MeshSpecificAttributes.ShowMeshWires = False
|
||||
attrs.MeshSpecificAttributes.ShowMeshVertices = False
|
||||
except Exception as ex:
|
||||
print("[OBERLEISTE] Plan-Mode MeshSpecificAttributes:", ex)
|
||||
# Section-Styles MUESSEN aktiv sein damit Custom-Section-Styles
|
||||
# (per-Layer oder per-Object) tatsaechlich gerendert werden. Default
|
||||
# ist False — d.h. Section-Hatches werden zugewiesen aber nicht angezeigt.
|
||||
# Diagnose: vorher + nachher loggen weil auf Mac Rhino set+UpdateDisplayMode
|
||||
# diesen Wert manchmal nicht persistiert (wir patchen darum auch direkt
|
||||
# die ini beim Erstanlegen).
|
||||
pre_uss = None
|
||||
try: pre_uss = bool(attrs.UseSectionStyles)
|
||||
except Exception: pass
|
||||
try: attrs.UseSectionStyles = True
|
||||
except Exception as ex:
|
||||
print("[OBERLEISTE] Plan-Mode UseSectionStyles set:", ex)
|
||||
try: post_uss = bool(attrs.UseSectionStyles)
|
||||
except Exception: post_uss = None
|
||||
print("[OBERLEISTE] Plan-Mode UseSectionStyles pre={} post={}".format(
|
||||
pre_uss, post_uss))
|
||||
# Clipping-Edges + Fills sichtbar
|
||||
try: attrs.ShowClippingEdges = True
|
||||
except Exception: pass
|
||||
try: attrs.ShowClippingFills = True
|
||||
except Exception: pass
|
||||
try: attrs.ShowClipIntersectionEdges = True
|
||||
except Exception: pass
|
||||
try: attrs.ShowClipIntersectionSurfaces = True
|
||||
except Exception: pass
|
||||
# Linewidths an — Lineweights-Toggle wirkt
|
||||
try: attrs.ShowLineWidths = True
|
||||
except Exception: pass
|
||||
try:
|
||||
DisplayModeDescription.UpdateDisplayMode(dmd)
|
||||
except Exception as ex:
|
||||
print("[OBERLEISTE] Plan-Mode update:", ex)
|
||||
|
||||
|
||||
_TEMPLATE_INI_PATH = os.path.join(_HERE, "templates", "dossier_plan.ini")
|
||||
|
||||
|
||||
def _ensure_dossier_plan_display_mode():
|
||||
"""Stellt sicher dass der 'Dossier Plan' 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.
|
||||
"""
|
||||
print("[OBERLEISTE] Plan-Mode: check...")
|
||||
try:
|
||||
from Rhino.Display import DisplayModeDescription
|
||||
except Exception as ex:
|
||||
print("[OBERLEISTE] Plan-Mode: DMD nicht verfuegbar:", ex)
|
||||
return False
|
||||
import re # fuer ini-checks unten
|
||||
target_name = "Dossier Plan"
|
||||
try:
|
||||
import System
|
||||
target_guid_obj = System.Guid(_DOSSIER_PLAN_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"))
|
||||
# Schon registriert?
|
||||
try:
|
||||
existing = None
|
||||
for dm in DisplayModeDescription.GetDisplayModes():
|
||||
try:
|
||||
if dm.EnglishName == target_name or dm.LocalName == target_name:
|
||||
existing = dm; break
|
||||
if target_guid_obj is not None and dm.Id == target_guid_obj:
|
||||
existing = dm; break
|
||||
except Exception: pass
|
||||
if existing is not None:
|
||||
# Mode existiert bereits — in Ruhe lassen. User kann manuell
|
||||
# loeschen + reloaden wenn er das Template neu laden will.
|
||||
# Vermeidet delete-loop wenn das Template ini-Werte hat die mein
|
||||
# alter check als "falsch" einstufte.
|
||||
print("[OBERLEISTE] Plan-Mode: existing gefunden, keine Aktion (manuell loeschen fuer Refresh)")
|
||||
return True
|
||||
# Sonst kein existing → vor dem Import alle Orphan-Modes mit unserer
|
||||
# Guid ODER Namen "Dossier Plan" wegputzen (alte kaputte Versionen
|
||||
# ohne Namen sind zuvor manchmal liegen geblieben → Duplikate +
|
||||
# Rhino-Crashes beim Klick).
|
||||
try:
|
||||
cleanup_count = 0
|
||||
for dm in list(DisplayModeDescription.GetDisplayModes()):
|
||||
should_delete = False
|
||||
try:
|
||||
if dm.EnglishName == target_name or dm.LocalName == target_name:
|
||||
should_delete = True
|
||||
elif target_guid_obj is not None and dm.Id == target_guid_obj:
|
||||
should_delete = True
|
||||
except Exception: pass
|
||||
if should_delete:
|
||||
try:
|
||||
DisplayModeDescription.DeleteDisplayMode(dm.Id)
|
||||
cleanup_count += 1
|
||||
except Exception as ex:
|
||||
print("[OBERLEISTE] Plan-Mode cleanup delete fail:", ex)
|
||||
if cleanup_count > 0:
|
||||
print("[OBERLEISTE] Plan-Mode: {} Orphan-Mode(s) entfernt vor Import".format(
|
||||
cleanup_count))
|
||||
except Exception as ex:
|
||||
print("[OBERLEISTE] Plan-Mode cleanup:", ex)
|
||||
except Exception as ex:
|
||||
print("[OBERLEISTE] Plan-Mode list:", ex)
|
||||
return False
|
||||
# ----------------------------------------------------------------
|
||||
# SOURCE: Template-ini bevorzugt (User hat den Mode manuell gebaut +
|
||||
# exportiert) — sonst Fallback auf Technical-Clone.
|
||||
# ----------------------------------------------------------------
|
||||
import tempfile
|
||||
tmp_path = os.path.join(tempfile.gettempdir(), "dossier_plan.ini")
|
||||
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:
|
||||
content = f.read()
|
||||
print("[OBERLEISTE] Plan-Mode: Template geladen ({} bytes)".format(len(content)))
|
||||
except Exception as ex:
|
||||
print("[OBERLEISTE] Plan-Mode Template read:", ex)
|
||||
return False
|
||||
else:
|
||||
# Fallback: Technical exportieren + patchen
|
||||
try:
|
||||
all_modes = list(DisplayModeDescription.GetDisplayModes())
|
||||
except Exception: all_modes = []
|
||||
for prefer in ("Technical", "Pen", "Shaded"):
|
||||
for dm in all_modes:
|
||||
try:
|
||||
if dm.EnglishName == prefer:
|
||||
base = dm; break
|
||||
except Exception: pass
|
||||
if base is not None: break
|
||||
if base is None:
|
||||
print("[OBERLEISTE] Plan-Mode: kein Basis-Modus gefunden")
|
||||
return False
|
||||
try:
|
||||
ok_export = False
|
||||
try:
|
||||
ok_export = bool(DisplayModeDescription.ExportToFile(base, tmp_path))
|
||||
except Exception as ex:
|
||||
print("[OBERLEISTE] Plan-Mode ExportToFile:", ex)
|
||||
if not ok_export:
|
||||
print("[OBERLEISTE] Plan-Mode: ExportToFile failed")
|
||||
return False
|
||||
with open(tmp_path, "r", encoding="utf-8", errors="ignore") as f:
|
||||
content = f.read()
|
||||
except Exception as ex:
|
||||
print("[OBERLEISTE] Plan-Mode fallback read ini:", ex)
|
||||
return False
|
||||
|
||||
try:
|
||||
# Name-Feld ersetzen (verschiedene moegliche Keys)
|
||||
import re
|
||||
try:
|
||||
content = re.sub(
|
||||
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)
|
||||
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})',
|
||||
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.upper(),
|
||||
_DOSSIER_PLAN_GUID.upper())
|
||||
except Exception: pass
|
||||
# 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
|
||||
# Keys beim Re-Import wieder).
|
||||
def _ini_replace(content, key, value):
|
||||
"""Ersetzt erstes Vorkommen von key=... mit key=value (case-insens).
|
||||
Liefert (content, found_bool)."""
|
||||
pat = r'(?im)^(\s*' + re.escape(key) + r'\s*=\s*)(.*)$'
|
||||
new_content, n = re.subn(pat, r'\g<1>' + str(value),
|
||||
content, count=1)
|
||||
return new_content, (n > 0)
|
||||
|
||||
# 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:
|
||||
for key, val in (
|
||||
("ClipSectionUsage", "1"),
|
||||
("TechnicalMask", "15"),
|
||||
("ShowIsocurves", "n"),
|
||||
("ShowTangentEdges", "n"),
|
||||
("ShowTangentSeams", "n"),
|
||||
("ShowMeshWires", "n"),
|
||||
("ShowMeshEdges", "n"),
|
||||
("ShadeSurface", "n"),
|
||||
("ClippingShowXEdges", "y"),
|
||||
("ClippingShowXSurface", "y"),
|
||||
):
|
||||
try:
|
||||
content, found = _ini_replace(content, key, val)
|
||||
if found:
|
||||
print("[OBERLEISTE] Plan-Mode ini {}={}".format(key, val))
|
||||
except Exception as ex:
|
||||
print("[OBERLEISTE] Plan-Mode ini-set {}={} fail: {}".format(
|
||||
key, val, ex))
|
||||
try:
|
||||
with open(tmp_path, "w", encoding="utf-8") as f:
|
||||
f.write(content)
|
||||
except Exception as ex:
|
||||
print("[OBERLEISTE] Plan-Mode write ini:", ex)
|
||||
return False
|
||||
# Import
|
||||
try:
|
||||
ok_import = bool(DisplayModeDescription.ImportFromFile(tmp_path))
|
||||
except Exception as ex:
|
||||
print("[OBERLEISTE] Plan-Mode ImportFromFile:", ex)
|
||||
return False
|
||||
if not ok_import:
|
||||
print("[OBERLEISTE] Plan-Mode: ImportFromFile gab False")
|
||||
return False
|
||||
except Exception as ex:
|
||||
print("[OBERLEISTE] Plan-Mode clone:", ex)
|
||||
return False
|
||||
# Neu importierten Mode holen + tweaken
|
||||
try:
|
||||
dmd = None
|
||||
if target_guid_obj is not None:
|
||||
try: dmd = DisplayModeDescription.GetDisplayMode(target_guid_obj)
|
||||
except Exception: pass
|
||||
if dmd is None:
|
||||
for dm in DisplayModeDescription.GetDisplayModes():
|
||||
try:
|
||||
if dm.EnglishName == target_name or dm.LocalName == target_name:
|
||||
dmd = dm; break
|
||||
except Exception: pass
|
||||
if dmd is None:
|
||||
print("[OBERLEISTE] Plan-Mode: nach Import nicht gefunden")
|
||||
return False
|
||||
# KEIN _apply_dossier_plan_attrs() hier — der wuerde
|
||||
# UpdateDisplayMode() aufrufen und die ini-Werte (ClipSectionUsage,
|
||||
# TechnicalMask) mit den Python-Default-Attrs ueberschreiben.
|
||||
# Die ini hat schon alle richtigen Werte.
|
||||
src = "Template" if template_exists else (base.EnglishName if base else "?")
|
||||
print("[OBERLEISTE] Display-Mode 'Dossier Plan' angelegt (Basis: {})".format(src))
|
||||
# Sanity-Check: liste alle Display-Modes auf damit wir sehen ob unser
|
||||
# Mode wirklich registriert ist (und mit welchem Namen).
|
||||
try:
|
||||
names = []
|
||||
for dm_check in DisplayModeDescription.GetDisplayModes():
|
||||
try:
|
||||
names.append(dm_check.EnglishName)
|
||||
except Exception: pass
|
||||
print("[OBERLEISTE] Plan-Mode: alle registrierten Modes: {}".format(
|
||||
", ".join(names)))
|
||||
except Exception: pass
|
||||
# Cache invalidieren damit das Dropdown ihn sieht
|
||||
try:
|
||||
global _display_modes_cache
|
||||
_display_modes_cache = None
|
||||
except Exception: pass
|
||||
return True
|
||||
except Exception as ex:
|
||||
print("[OBERLEISTE] Plan-Mode tweak:", ex)
|
||||
return False
|
||||
|
||||
|
||||
_THUMB_SIZE = (480, 320) # 3:2 — kompakt fuer Launcher-Cards
|
||||
|
||||
|
||||
@@ -1730,5 +2089,13 @@ def _bridge_factory():
|
||||
return b
|
||||
|
||||
|
||||
# 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)
|
||||
|
||||
|
||||
panel_base.register_and_open("oberleiste", "Oberleiste", PANEL_GUID_STR, _bridge_factory,
|
||||
icon_spec=("menu", "#2f5d54"))
|
||||
|
||||
Reference in New Issue
Block a user