41b6f8ac51
Elemente: - _make_oeffnung_preview baut jetzt einen vollen 3D-Quader (12 Kanten) mit Wand-Dicke statt nur einer 2D-Flaeche. Glas-Diagonalen auf Vorder- und Hinterflaeche, Brueest-Linie (gepunktet) auf der Vorderflaeche, Achs- Marker auf der Wand-Achse, dazu ein 3D-Mass-Label "B x H Br" ueberm Sturz (zentriert, an der Wand "geheftet"). Aktualisiert live bei Option-Aenderungen. Launcher: - Paper-Theme zu reinem Weiss umgestellt (User-Feedback "zu warm"): --bg #ffffff, --dark #f0f0f0, neutrale Greys statt Sand-Tones, Schatten ohne warmen Braun-Stich. Petrol-Radial-Gradient oben raus. - latest.json aus dem neuen Release-Build. SectionStyle (Bug-Hunt — Hatch/Boundary haben nicht gegriffen): - *Source-Properties (HatchColorSource, BoundaryColorSource, BoundaryLinetypeSource) jetzt explizit auf ColorFromObject / LinetypeFromObject — sonst hat Rhino die eigenen Color/Linetype- Werte ignoriert (Default ist ByLayer). - doc.Layers.Modify nach SetCustomSectionStyle, sonst persistiert Mac Rhino den Custom-Style nicht zuverlaessig. - Helper _try_set + _enum_int eliminieren das 8x duplizierte Property-Probe-Pattern. - Property-Inventar wird einmal pro Session gedumpt (verfuegbare SectionStyle-Felder) damit API-Mismatches sichtbar werden. - Per-Layer Apply-Logs zeigen welche Properties via welchem Namen gesetzt wurden — leicht debuggbar im Mismatch-Fall. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
751 lines
27 KiB
Python
751 lines
27 KiB
Python
#! python 3
|
|
# -*- coding: utf-8 -*-
|
|
"""
|
|
layer_builder.py
|
|
Layer-Struktur:
|
|
<Zeichnungsebene-Name>
|
|
+-- 00_RASTER
|
|
+-- 01_VERMESSUNG
|
|
+-- 20_WAENDE
|
|
+-- ...
|
|
Jede Zeichnungsebene erhaelt alle definierten Ebenen als Sublayer.
|
|
"""
|
|
import System
|
|
import System.Drawing as Drawing
|
|
import Rhino
|
|
|
|
GREY = Drawing.Color.FromArgb(150, 150, 150)
|
|
_FROM_LAYER = Rhino.DocObjects.ObjectColorSource.ColorFromLayer
|
|
_FROM_OBJECT = Rhino.DocObjects.ObjectColorSource.ColorFromObject
|
|
_EMPTY_GUID = System.Guid.Empty
|
|
|
|
|
|
def _color(hex_str):
|
|
h = (hex_str or "#888888").lstrip("#")
|
|
if len(h) == 3:
|
|
h = "".join(c * 2 for c in h)
|
|
return Drawing.Color.FromArgb(int(h[0:2], 16), int(h[2:4], 16), int(h[4:6], 16))
|
|
|
|
|
|
def _is_top_level(layer):
|
|
return layer.ParentLayerId == _EMPTY_GUID
|
|
|
|
|
|
def _find_top_by_id(doc, dossier_id):
|
|
for i, layer in enumerate(doc.Layers):
|
|
if not _is_top_level(layer):
|
|
continue
|
|
v = layer.GetUserString("dossier_id")
|
|
if v == dossier_id:
|
|
return i
|
|
return -1
|
|
|
|
|
|
def _find_top_by_name(doc, name):
|
|
for i, layer in enumerate(doc.Layers):
|
|
if _is_top_level(layer) and layer.Name == name:
|
|
return i
|
|
return -1
|
|
|
|
|
|
def _find_sublayer_by_code(doc, parent_id, code):
|
|
prefix = code + "_"
|
|
for i, layer in enumerate(doc.Layers):
|
|
if layer.ParentLayerId == parent_id and layer.Name.startswith(prefix):
|
|
return i
|
|
return -1
|
|
|
|
|
|
def _add_layer(doc, name, parent_id=None, color=None, lw=None):
|
|
layer = Rhino.DocObjects.Layer()
|
|
layer.Name = name
|
|
if parent_id is not None and parent_id != _EMPTY_GUID:
|
|
layer.ParentLayerId = parent_id
|
|
if color is not None:
|
|
layer.Color = color
|
|
if lw is not None:
|
|
layer.PlotWeight = lw
|
|
return doc.Layers.Add(layer)
|
|
|
|
|
|
def _find_hatch_pattern_index(doc, name):
|
|
"""Sucht einen Hatch-Pattern-Index per Name (case-insensitive). -1 wenn nicht da."""
|
|
if not name or name == "None":
|
|
return -1
|
|
target = name.strip().lower()
|
|
try:
|
|
for i in range(doc.HatchPatterns.Count):
|
|
hp = doc.HatchPatterns[i]
|
|
if hp is None or hp.IsDeleted: continue
|
|
if hp.Name and hp.Name.strip().lower() == target:
|
|
return i
|
|
except Exception as ex:
|
|
print("[EBENEN] hatch lookup:", ex)
|
|
return -1
|
|
|
|
|
|
def _find_linetype_index(doc, name):
|
|
"""Sucht einen Linetype-Index per Name. -1 = ByLayer."""
|
|
if not name or name in ("byLayer", "by_layer", "ByLayer"):
|
|
return -1
|
|
target = name.strip().lower()
|
|
try:
|
|
for i in range(doc.Linetypes.Count):
|
|
lt = doc.Linetypes[i]
|
|
if lt is None or lt.IsDeleted: continue
|
|
if lt.Name and lt.Name.strip().lower() == target:
|
|
return i
|
|
except Exception: pass
|
|
return -1
|
|
|
|
|
|
def _try_set(obj, prop_names, value):
|
|
"""Versucht den Wert auf das erste vorhandene Property zu setzen.
|
|
Liefert den Property-Namen bei Erfolg, sonst None."""
|
|
if isinstance(prop_names, str):
|
|
prop_names = (prop_names,)
|
|
for prop in prop_names:
|
|
if hasattr(obj, prop):
|
|
try:
|
|
setattr(obj, prop, value)
|
|
return prop
|
|
except Exception as ex:
|
|
# Property da, aber Wert nicht akzeptiert (z.B. enum-conversion)
|
|
# — anderen Namen probieren statt aufgeben
|
|
continue
|
|
return None
|
|
|
|
|
|
def _enum_int(*candidates):
|
|
"""Liefert den ersten Enum-Wert aus den Kandidaten der existiert.
|
|
candidates = list of (module-path-list, value-name). Bei keiner Match
|
|
liefert None."""
|
|
for path, val_name in candidates:
|
|
try:
|
|
obj = Rhino
|
|
for p in path:
|
|
obj = getattr(obj, p)
|
|
return getattr(obj, val_name)
|
|
except Exception:
|
|
continue
|
|
return None
|
|
|
|
|
|
def _apply_section_style(doc, layer, section_cfg, layer_color):
|
|
"""Setzt einen Custom-SectionStyle auf den Layer aus dem Dossier-section-dict.
|
|
|
|
Nutzt Rhino-8's Python-3-API (Rhino.DocObjects.SectionStyle +
|
|
Layer.SetCustomSectionStyle / RemoveCustomSectionStyle). In IPy 2.7
|
|
sind diese Methoden nicht exponiert — dort no-op.
|
|
|
|
Wichtig: viele Farb-/Linetype-Properties greifen nur wenn der
|
|
zugehoerige "*Source"-Wert auf ColorFromObject / LinetypeFromObject
|
|
steht. Das setzen wir explizit.
|
|
"""
|
|
if not section_cfg or not isinstance(section_cfg, dict):
|
|
return
|
|
has_setter = hasattr(layer, "SetCustomSectionStyle")
|
|
has_remover = hasattr(layer, "RemoveCustomSectionStyle")
|
|
if not has_setter:
|
|
return # IPy-2.7 — keine API
|
|
|
|
try:
|
|
SS = Rhino.DocObjects.SectionStyle
|
|
except Exception as ex:
|
|
print("[EBENEN] SectionStyle-Klasse nicht da:", ex); return
|
|
|
|
pat = (section_cfg.get("hatchPattern") or "None").strip()
|
|
show = bool(section_cfg.get("boundaryShow", True))
|
|
diag = "[SS:{}]".format(layer.Name if layer else "?")
|
|
|
|
# Wenn weder Hatch noch Boundary → Custom-Style entfernen
|
|
if pat == "None" and not show:
|
|
if has_remover:
|
|
try:
|
|
layer.RemoveCustomSectionStyle()
|
|
print(diag, "removed (kein Hatch + kein Boundary)")
|
|
except Exception as ex:
|
|
print(diag, "remove fehlgeschlagen:", ex)
|
|
return
|
|
|
|
style = SS()
|
|
|
|
# Property-Inventar einmal beim ersten Aufruf loggen — hilft bei
|
|
# API-Verschiebungen zwischen Rhino-Versionen sofort den Mismatch zu sehen.
|
|
if not getattr(_apply_section_style, "_props_logged", False):
|
|
props = [n for n in dir(style)
|
|
if not n.startswith("_") and not callable(getattr(style, n, None))]
|
|
print("[SS] verfuegbare Properties:", ", ".join(sorted(props)))
|
|
_apply_section_style._props_logged = True
|
|
|
|
# --- Hatch ---
|
|
if pat and pat != "None":
|
|
hp_idx = _find_hatch_pattern_index(doc, pat)
|
|
if hp_idx >= 0:
|
|
set_to = _try_set(style, ("HatchIndex", "HatchPatternIndex"), hp_idx)
|
|
print(diag, "HatchIndex={} via {}".format(hp_idx, set_to))
|
|
else:
|
|
print(diag, "Hatch '{}' nicht in HatchPatterns gefunden".format(pat))
|
|
|
|
scale_v = float(section_cfg.get("hatchScale") or 1.0)
|
|
_try_set(style, ("HatchScale", "HatchPatternScale"), scale_v)
|
|
|
|
import math
|
|
rot_deg = float(section_cfg.get("hatchRotation") or 0)
|
|
_try_set(style, ("HatchRotation", "HatchAngle"), math.radians(rot_deg))
|
|
|
|
# Hatch-Color: explizit ColorFromObject setzen damit der eigene Wert greift
|
|
hatch_color = section_cfg.get("hatchColor")
|
|
if hatch_color:
|
|
col = _color(hatch_color)
|
|
set_color = _try_set(style, ("HatchColor", "FillColor"), col)
|
|
# Source auf "FromObject" — sonst nutzt Rhino den Layer-Color
|
|
src_from_object = _enum_int(
|
|
(("DocObjects", "ObjectColorSource"), "ColorFromObject"))
|
|
if src_from_object is not None:
|
|
_try_set(style, ("HatchColorSource", "FillColorSource"), src_from_object)
|
|
print(diag, "HatchColor via {}".format(set_color))
|
|
|
|
# Background (viewport=0/transparent vs object=1)
|
|
bg = section_cfg.get("background")
|
|
if bg in ("object", "byObject"):
|
|
# Versuche Enum-Konstanten zu finden
|
|
for prop_names, en_paths in (
|
|
(("BackgroundFillMode", "BackgroundColorUsage", "FillBackground"),
|
|
(("DocObjects", "SectionBackgroundFillMode"), "SolidColor")),
|
|
):
|
|
en_val = _enum_int(en_paths)
|
|
if en_val is not None and _try_set(style, prop_names, en_val):
|
|
break
|
|
# Fallback: bool/int = 1
|
|
else:
|
|
_try_set(style, ("FillBackground",), True)
|
|
|
|
# --- Boundary ---
|
|
set_show = _try_set(style, ("BoundaryVisible", "ShowBoundary"), show)
|
|
print(diag, "BoundaryVisible={} via {}".format(show, set_show))
|
|
|
|
if show:
|
|
# Boundary-Color: setze Color + Source auf FromObject
|
|
bc = section_cfg.get("boundaryColor")
|
|
if bc:
|
|
col = _color(bc)
|
|
set_to = _try_set(style,
|
|
("BoundaryColor", "OutlineColor", "EdgeColor"), col)
|
|
src_from_object = _enum_int(
|
|
(("DocObjects", "ObjectColorSource"), "ColorFromObject"))
|
|
if src_from_object is not None:
|
|
_try_set(style,
|
|
("BoundaryColorSource", "OutlineColorSource",
|
|
"EdgeColorSource"),
|
|
src_from_object)
|
|
print(diag, "BoundaryColor={} via {}".format(bc, set_to))
|
|
|
|
# Width-Scale auf PlotWeight uebertragen (RW8 hat keine WidthScale direkt;
|
|
# alternative Property-Namen probieren)
|
|
ws = float(section_cfg.get("boundaryWidthScale") or 1.0)
|
|
set_to = _try_set(style,
|
|
("BoundaryWidthScale", "EdgeWidthScale", "OutlineWidthScale",
|
|
"PlotWeightScale"), ws)
|
|
if not set_to:
|
|
# Direkte PlotWeight setzen wenn Layer-PlotWeight bekannt
|
|
try:
|
|
base_lw = float(getattr(layer, "PlotWeight", 0.25) or 0.25)
|
|
except Exception:
|
|
base_lw = 0.25
|
|
_try_set(style, ("BoundaryPlotWeight", "PlotWeight"), base_lw * ws)
|
|
|
|
# Linetype: Index + Source auf LinetypeFromObject
|
|
lt = section_cfg.get("boundaryLinetype")
|
|
if lt and lt not in ("byLayer", "ByLayer"):
|
|
lt_idx = _find_linetype_index(doc, lt)
|
|
set_to = _try_set(style,
|
|
("BoundaryLinetypeIndex", "EdgeLinetypeIndex"), lt_idx)
|
|
lt_src = _enum_int(
|
|
(("DocObjects", "ObjectLinetypeSource"), "LinetypeFromObject"))
|
|
if lt_src is not None:
|
|
_try_set(style,
|
|
("BoundaryLinetypeSource", "EdgeLinetypeSource"),
|
|
lt_src)
|
|
print(diag, "BoundaryLinetype={} idx={} via {}".format(lt, lt_idx, set_to))
|
|
|
|
# SectionOpenObjects: bei nicht-geschlossener Geometrie auch schneiden
|
|
soo = bool(section_cfg.get("sectionOpenObjects", True))
|
|
_try_set(style, ("SectionOpenObjects", "ClipOpenObjects",
|
|
"SectionCutsOpenObjects"), soo)
|
|
|
|
# Style auf Layer setzen + explizit Modify damit Mac-Rhino den Layer
|
|
# persistiert (sonst greift's nicht immer)
|
|
try:
|
|
layer.SetCustomSectionStyle(style)
|
|
except Exception as ex:
|
|
print(diag, "SetCustomSectionStyle FAIL:", ex)
|
|
return
|
|
try:
|
|
doc.Layers.Modify(layer, layer.LayerIndex, True)
|
|
except Exception: pass
|
|
print(diag, "OK applied")
|
|
|
|
|
|
def build_layers(doc, zeichnungsebenen, ebenen):
|
|
"""
|
|
Stellt sicher dass fuer jede Zeichnungsebene ein Parent-Layer existiert
|
|
und unter jedem alle Ebenen als Sublayer angelegt/aktualisiert sind.
|
|
"""
|
|
for z in zeichnungsebenen:
|
|
z_id = z["id"]
|
|
z_name = z["name"]
|
|
|
|
# Parent finden oder anlegen
|
|
idx = _find_top_by_id(doc, z_id)
|
|
if idx < 0:
|
|
idx = _find_top_by_name(doc, z_name)
|
|
if idx < 0:
|
|
idx = _add_layer(doc, z_name)
|
|
doc.Layers[idx].SetUserString("dossier_id", z_id)
|
|
else:
|
|
parent = doc.Layers[idx]
|
|
if parent.Name != z_name:
|
|
parent.Name = z_name
|
|
parent.SetUserString("dossier_id", z_id)
|
|
|
|
parent_id = doc.Layers[idx].Id
|
|
|
|
# Sublayer pro Ebene
|
|
for e in ebenen:
|
|
sub_name = "{}_{}".format(e["code"], e["name"])
|
|
col = _color(e.get("color"))
|
|
lw = float(e.get("lw", 0.13))
|
|
sub_idx = _find_sublayer_by_code(doc, parent_id, e["code"])
|
|
if sub_idx < 0:
|
|
sub_idx = _add_layer(doc, sub_name, parent_id, col, lw)
|
|
doc.Layers[sub_idx].SetUserString("dossier_code", e["code"])
|
|
else:
|
|
sub = doc.Layers[sub_idx]
|
|
if sub.Name != sub_name:
|
|
sub.Name = sub_name
|
|
sub.Color = col
|
|
try:
|
|
import massstab as _ms
|
|
_ms.write_plotweight(doc, sub, float(lw))
|
|
except Exception:
|
|
sub.PlotWeight = lw
|
|
sub.SetUserString("dossier_code", e["code"])
|
|
|
|
# Section Style anwenden (Py3-only — IPy 2.7 no-op)
|
|
try:
|
|
_apply_section_style(doc, doc.Layers[sub_idx],
|
|
e.get("section"), e.get("color"))
|
|
except Exception as ex:
|
|
print("[EBENEN] section-style apply ({}): {}".format(sub_name, ex))
|
|
|
|
doc.Views.Redraw()
|
|
print("[EBENEN] {} Zeichnungsebenen x {} Ebenen aktualisiert".format(
|
|
len(zeichnungsebenen), len(ebenen)))
|
|
|
|
|
|
def update_layer_style(doc, code, color_hex=None, lw=None):
|
|
"""Aendert Farbe und/oder Stiftdicke fuer alle Sublayer mit dem gegebenen Code."""
|
|
col = _color(color_hex) if color_hex else None
|
|
try:
|
|
import massstab as _ms
|
|
except Exception:
|
|
_ms = None
|
|
for i, layer in enumerate(doc.Layers):
|
|
if _is_top_level(layer):
|
|
continue
|
|
if layer.Name.startswith(code + "_"):
|
|
if col is not None:
|
|
layer.Color = col
|
|
if lw is not None:
|
|
if _ms is not None:
|
|
_ms.write_plotweight(doc, layer, float(lw))
|
|
else:
|
|
layer.PlotWeight = float(lw)
|
|
doc.Views.Redraw()
|
|
|
|
|
|
def set_ebene_visible(doc, code, visible):
|
|
"""Schaltet alle Sublayer mit Code in/aus Zeichnungsebenen."""
|
|
for i, layer in enumerate(doc.Layers):
|
|
if _is_top_level(layer):
|
|
continue
|
|
if layer.Name.startswith(code + "_"):
|
|
layer.IsVisible = visible
|
|
doc.Views.Redraw()
|
|
|
|
|
|
def set_ebene_locked(doc, code, locked):
|
|
for i, layer in enumerate(doc.Layers):
|
|
if _is_top_level(layer):
|
|
continue
|
|
if layer.Name.startswith(code + "_"):
|
|
layer.IsLocked = locked
|
|
doc.Views.Redraw()
|
|
|
|
|
|
def delete_ebene(doc, code, move_to=None):
|
|
"""
|
|
Loescht alle Sublayer mit dem gegebenen Code in allen Zeichnungsebenen.
|
|
Falls move_to gesetzt: verschiebt vorher alle Objekte zum Sublayer
|
|
mit move_to-Code unter dem selben Parent. Sonst: loescht Objekte mit.
|
|
"""
|
|
if not code:
|
|
return
|
|
|
|
from_prefix = code + "_"
|
|
to_prefix = (move_to + "_") if move_to else None
|
|
|
|
# Top-Level Parents finden
|
|
parents = [layer for layer in doc.Layers if _is_top_level(layer)]
|
|
|
|
moved = 0
|
|
deleted_objs = 0
|
|
deleted_layers = 0
|
|
|
|
for parent in parents:
|
|
from_layer = None
|
|
to_layer = None
|
|
for layer in doc.Layers:
|
|
if layer.ParentLayerId != parent.Id:
|
|
continue
|
|
if layer.Name.startswith(from_prefix):
|
|
from_layer = layer
|
|
elif to_prefix and layer.Name.startswith(to_prefix):
|
|
to_layer = layer
|
|
|
|
if from_layer is None:
|
|
continue
|
|
|
|
from_idx = doc.Layers.FindByFullPath(from_layer.FullPath, -1)
|
|
if from_idx < 0:
|
|
continue
|
|
|
|
objs = list(doc.Objects.FindByLayer(doc.Layers[from_idx]))
|
|
|
|
if move_to and to_layer is not None:
|
|
to_idx = doc.Layers.FindByFullPath(to_layer.FullPath, -1)
|
|
if to_idx >= 0:
|
|
for obj in objs:
|
|
attrs = obj.Attributes.Duplicate()
|
|
attrs.LayerIndex = to_idx
|
|
if doc.Objects.ModifyAttributes(obj, attrs, True):
|
|
moved += 1
|
|
else:
|
|
for obj in objs:
|
|
if doc.Objects.Delete(obj.Id, True):
|
|
deleted_objs += 1
|
|
|
|
# Sublayer loeschen
|
|
try:
|
|
if doc.Layers.Delete(from_idx, True):
|
|
deleted_layers += 1
|
|
except Exception as ex:
|
|
print("[EBENEN] Layer-Delete:", ex)
|
|
|
|
doc.Views.Redraw()
|
|
print("[EBENEN] Ebene {} entfernt: {} Sublayer, {} Objekte verschoben, {} Objekte geloescht".format(
|
|
code, deleted_layers, moved, deleted_objs))
|
|
|
|
|
|
# --- Clipping Plane Management ----------------------------------------------
|
|
|
|
_CLIP_KEY = "dossier_clipping_plane"
|
|
|
|
|
|
def _find_clipping_plane(doc):
|
|
for obj in doc.Objects:
|
|
try:
|
|
if obj.Attributes.GetUserString(_CLIP_KEY) == "1":
|
|
return obj
|
|
except Exception:
|
|
pass
|
|
return None
|
|
|
|
|
|
def update_clipping_plane(doc, active_z, enabled):
|
|
"""
|
|
Erstellt/aktualisiert/entfernt die DOSSIER-Clipping-Plane an OKFF + Schnitthoehe
|
|
des aktiven Geschosses. Plane zeigt nach +Z, schneidet alles oberhalb weg.
|
|
|
|
Diagnostik via [CLIP]-Praefix in den Print-Ausgaben — bei Problemen die
|
|
Rhino-Konsole nach 'CLIP' filtern.
|
|
"""
|
|
import Rhino.Geometry as rg
|
|
z_name = (active_z or {}).get("name") if active_z else None
|
|
print("[CLIP] update: enabled={} active='{}' isGeschoss={} okff={} sh={}".format(
|
|
enabled,
|
|
z_name,
|
|
bool(active_z and active_z.get("isGeschoss")),
|
|
(active_z or {}).get("okff"),
|
|
(active_z or {}).get("schnitthoehe"),
|
|
))
|
|
|
|
existing = _find_clipping_plane(doc)
|
|
print("[CLIP] existing: {}".format(existing.Id if existing else "none"))
|
|
|
|
is_geschoss = bool(active_z and active_z.get("isGeschoss") and active_z.get("okff") is not None)
|
|
|
|
# IMMER vorhandene Plane loeschen — bei Re-Enable wollen wir frische
|
|
# vp_ids (alte koennten leer/falsch sein, dann clippt das Replace zwar
|
|
# die Geometrie aber keinen Viewport).
|
|
if existing is not None:
|
|
try:
|
|
doc.Objects.Delete(existing.Id, True)
|
|
print("[CLIP] alte Plane geloescht")
|
|
except Exception as ex:
|
|
print("[CLIP] Delete fehlgeschlagen:", ex)
|
|
|
|
if (not enabled) or (not is_geschoss):
|
|
print("[CLIP] disabled — fertig (enabled={}, isGeschoss={})".format(enabled, is_geschoss))
|
|
doc.Views.Redraw()
|
|
return
|
|
|
|
# dict.get(k, default) liefert default NUR wenn Key fehlt — bei
|
|
# Key-vorhanden-aber-None gibt's None zurueck. float(None) crasht.
|
|
# Daher explizit None-faangen:
|
|
okff_raw = active_z.get("okff")
|
|
sh_raw = active_z.get("schnitthoehe")
|
|
okff = float(okff_raw) if okff_raw is not None else 0.0
|
|
sh = float(sh_raw) if sh_raw is not None else 1.0
|
|
cut_z = okff + sh
|
|
print("[CLIP] cut_z={} (okff={}, schnitthoehe={})".format(cut_z, okff, sh))
|
|
# Normal nach -Z = sichtbar bleibt UNTERHALB der Plane (Grundriss-Schnitt:
|
|
# man steht auf der Boden-Seite, alles darueber wird weggeschnitten).
|
|
# Mit +Z waere es genau umgekehrt (Decke + Rest oben sichtbar).
|
|
plane = rg.Plane(rg.Point3d(0.0, 0.0, cut_z), rg.Vector3d(0.0, 0.0, -1.0))
|
|
du, dv = 50000.0, 50000.0
|
|
|
|
# Viewport-IDs sammeln — wir wollen ALLE Modell-Viewports clippen,
|
|
# nicht nur den gerade aktiven. Sammeln aus mehreren Quellen +
|
|
# dedupen damit die Plane in Top/Front/Right/Perspective gleichzeitig
|
|
# wirkt.
|
|
vp_ids = []
|
|
seen = set()
|
|
def _add(vpid):
|
|
if vpid is None: return
|
|
try:
|
|
key = str(vpid)
|
|
except Exception: return
|
|
if key in seen or vpid == System.Guid.Empty: return
|
|
seen.add(key); vp_ids.append(vpid)
|
|
|
|
# Methode 1: GetViewList — alle Modell-Views (kein Page-Layout)
|
|
try:
|
|
views = doc.Views.GetViewList(True, False)
|
|
for v in views:
|
|
try: _add(v.ActiveViewport.Id)
|
|
except Exception: pass
|
|
try: _add(v.MainViewport.Id)
|
|
except Exception: pass
|
|
except Exception as ex:
|
|
print("[CLIP] GetViewList Fehler:", ex)
|
|
|
|
# Methode 2: Iteration ueber Views (Fallback falls GetViewList anders)
|
|
try:
|
|
for view in doc.Views:
|
|
try: _add(view.ActiveViewport.Id)
|
|
except Exception: pass
|
|
try: _add(view.MainViewport.Id)
|
|
except Exception: pass
|
|
try: _add(view.ActiveViewportID)
|
|
except Exception: pass
|
|
except Exception as ex:
|
|
print("[CLIP] doc.Views iteration Fehler:", ex)
|
|
|
|
# Namen fuer Debug-Output sammeln
|
|
vp_names = []
|
|
try:
|
|
for view in doc.Views:
|
|
try: vp_names.append(view.ActiveViewport.Name)
|
|
except Exception: pass
|
|
except Exception: pass
|
|
|
|
print("[CLIP] {} Viewport-ID(s) gesammelt: {}".format(
|
|
len(vp_ids), ", ".join(vp_names) or "(keine Namen)"))
|
|
if not vp_ids:
|
|
print("[CLIP] WARNUNG: keine Viewports — Plane wuerde nichts schneiden")
|
|
|
|
try:
|
|
new_id = doc.Objects.AddClippingPlane(plane, du, dv, vp_ids)
|
|
if new_id == System.Guid.Empty:
|
|
print("[CLIP] AddClippingPlane lieferte Empty Guid — Fehler")
|
|
return
|
|
obj = doc.Objects.FindId(new_id)
|
|
if obj is None:
|
|
print("[CLIP] FindId nach Erstellung lieferte None — Object weg")
|
|
return
|
|
attrs = obj.Attributes.Duplicate()
|
|
attrs.SetUserString(_CLIP_KEY, "1")
|
|
# Mode = Normal damit die Plane in Mac Rhino voll sichtbar ist.
|
|
# Locked-Mode rendert auf Mac oft nur ein blasses Edge. Wer den
|
|
# Plane-Boundary nicht selektieren will, kann via Layer locken.
|
|
attrs.Mode = Rhino.DocObjects.ObjectMode.Normal
|
|
doc.Objects.ModifyAttributes(obj, attrs, True)
|
|
print("[CLIP] Plane erstellt: Z={}, ID={}, du/dv={}/{}".format(
|
|
cut_z, new_id, du, dv))
|
|
except Exception as ex:
|
|
print("[CLIP] AddClippingPlane Fehler:", ex)
|
|
doc.Views.Redraw()
|
|
|
|
|
|
def cleanup_default_layers(doc):
|
|
"""Loescht leere Rhino-Default-Layer (Default, Layer 01, ...) — nicht-leere bleiben unberuehrt."""
|
|
import re
|
|
pattern = re.compile(r'^(default|layer\s*0*\d+)$', re.IGNORECASE)
|
|
deleted = []
|
|
for i in range(doc.Layers.Count - 1, -1, -1):
|
|
layer = doc.Layers[i]
|
|
if layer.IsDeleted:
|
|
continue
|
|
if not _is_top_level(layer):
|
|
continue
|
|
if not pattern.match(layer.Name.strip()):
|
|
continue
|
|
try:
|
|
# Name VOR Delete sichern — sonst liefert layer.Name danach None
|
|
nm = layer.Name
|
|
if doc.Layers.Delete(i, True):
|
|
if nm: deleted.append(nm)
|
|
except Exception:
|
|
pass
|
|
if deleted:
|
|
print("[EBENEN] Default-Layer entfernt: {}".format(", ".join(deleted)))
|
|
|
|
|
|
def set_active_sublayer(doc, zeichnungsebene_id, code):
|
|
"""Macht den Sublayer 'code' unter Zeichnungsebene 'zeichnungsebene_id' aktiv."""
|
|
parent_idx = _find_top_by_id(doc, zeichnungsebene_id)
|
|
if parent_idx < 0:
|
|
print("[EBENEN] Parent-Layer fuer Zeichnungsebene {} nicht gefunden".format(zeichnungsebene_id))
|
|
return
|
|
parent_id = doc.Layers[parent_idx].Id
|
|
sub_idx = _find_sublayer_by_code(doc, parent_id, code)
|
|
if sub_idx >= 0:
|
|
doc.Layers.SetCurrentLayerIndex(sub_idx, True)
|
|
else:
|
|
print("[EBENEN] Sublayer mit Code {} unter Parent {} nicht gefunden".format(code, doc.Layers[parent_idx].Name))
|
|
|
|
|
|
def apply_visibility(doc, zeichnungsebenen, ebenen, active_z_id, active_code, z_mode, e_mode):
|
|
"""
|
|
Kombinierte Sichtbarkeit aus Z-Mode (Zeichnungsebenen) und E-Mode (Ebenen).
|
|
Beide Modi: 'all' | 'active' | 'grey' | 'grey_locked'
|
|
"""
|
|
canonical = {e["code"]: _color(e.get("color")) for e in ebenen}
|
|
e_eye_vis = {e["code"]: e.get("visible", True) for e in ebenen}
|
|
e_eye_locked = {e["code"]: e.get("locked", False) for e in ebenen}
|
|
|
|
id_to_top, name_to_top, children_by_parent = {}, {}, {}
|
|
for layer in doc.Layers:
|
|
if _is_top_level(layer):
|
|
uid = layer.GetUserString("dossier_id")
|
|
if uid:
|
|
id_to_top[uid] = layer
|
|
name_to_top[layer.Name] = layer
|
|
else:
|
|
children_by_parent.setdefault(layer.ParentLayerId, []).append(layer)
|
|
|
|
for z in zeichnungsebenen:
|
|
parent = id_to_top.get(z["id"]) or name_to_top.get(z["name"])
|
|
if parent is None:
|
|
continue
|
|
children = children_by_parent.get(parent.Id, [])
|
|
is_active_z = z["id"] == active_z_id
|
|
z_visible_flag = z.get("visible", True)
|
|
z_locked_flag = bool(z.get("locked", False))
|
|
|
|
# Z-Mode -> Parent-Zustand
|
|
# 'all_force' kommt VOR dem visible-Flag-Check: zeigt jede Z auch wenn
|
|
# das User-Eye sie ausgeblendet hatte. 'all' dagegen respektiert das
|
|
# Eye-Flag (= "Ausgewählte" im UI).
|
|
if is_active_z:
|
|
p_vis, p_grey, p_lock = True, False, False
|
|
elif z_mode == "active":
|
|
p_vis, p_grey, p_lock = False, False, False
|
|
elif z_mode == "all_force":
|
|
p_vis, p_grey, p_lock = True, False, False
|
|
elif not z_visible_flag:
|
|
p_vis, p_grey, p_lock = False, False, False
|
|
elif z_mode == "all":
|
|
p_vis, p_grey, p_lock = True, False, False
|
|
elif z_mode == "grey_locked":
|
|
p_vis, p_grey, p_lock = True, True, True
|
|
else: # grey
|
|
p_vis, p_grey, p_lock = True, True, False
|
|
|
|
# Per-Z explizites Sperren ueberlagert (auch fuer die aktive Z) — wer
|
|
# eine Geschoss-Ebene sperrt, will dass Klicks ins Leere gehen.
|
|
if z_locked_flag:
|
|
p_lock = True
|
|
|
|
parent_changed = False
|
|
if parent.IsVisible != p_vis:
|
|
parent.IsVisible = p_vis
|
|
parent_changed = True
|
|
if parent.IsLocked != p_lock:
|
|
parent.IsLocked = p_lock
|
|
parent_changed = True
|
|
if parent_changed:
|
|
try: doc.Layers.Modify(parent, parent.LayerIndex, True)
|
|
except Exception: pass
|
|
|
|
if not p_vis:
|
|
continue # Children erben Parent-Hidden
|
|
|
|
# E-Mode -> Sublayer-Zustand
|
|
for child in children:
|
|
if "_" not in child.Name:
|
|
continue
|
|
code = child.Name.split("_", 1)[0]
|
|
if code not in canonical:
|
|
continue
|
|
is_active_e = (code == active_code)
|
|
eye_v = e_eye_vis.get(code, True)
|
|
eye_l = e_eye_locked.get(code, False)
|
|
|
|
if is_active_e:
|
|
e_vis, e_grey, e_lock = True, False, False
|
|
elif e_mode == "active":
|
|
e_vis, e_grey, e_lock = False, False, False
|
|
elif not eye_v:
|
|
e_vis, e_grey, e_lock = False, False, False
|
|
elif e_mode == "all":
|
|
e_vis, e_grey, e_lock = True, False, False
|
|
elif e_mode == "grey_locked":
|
|
e_vis, e_grey, e_lock = True, True, True
|
|
else: # grey
|
|
e_vis, e_grey, e_lock = True, True, False
|
|
|
|
# Kombination
|
|
child_vis = e_vis
|
|
child_grey = p_grey or e_grey
|
|
child_lock = e_lock or eye_l
|
|
|
|
changed = False
|
|
if child.IsVisible != child_vis:
|
|
child.IsVisible = child_vis
|
|
changed = True
|
|
if child.IsLocked != child_lock:
|
|
child.IsLocked = child_lock
|
|
changed = True
|
|
if child_grey:
|
|
if child.Color != GREY:
|
|
child.Color = GREY
|
|
changed = True
|
|
else:
|
|
canon = canonical.get(code)
|
|
if canon is not None and child.Color != canon:
|
|
child.Color = canon
|
|
changed = True
|
|
# In neueren Rhino-Versionen committed der Property-Setter direkt,
|
|
# in manchen Faellen (besonders auf Mac) wird IsLocked nicht
|
|
# persistiert ohne explizites Modify. Defensiv:
|
|
if changed:
|
|
try:
|
|
doc.Layers.Modify(child, child.LayerIndex, True)
|
|
except Exception:
|
|
pass
|
|
|
|
doc.Views.Redraw()
|