# ! python3 # -*- coding: utf-8 -*- """ layer_builder.py Layer-Struktur: +-- 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 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"]) 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. """ import Rhino.Geometry as rg existing = _find_clipping_plane(doc) is_geschoss = bool(active_z and active_z.get("isGeschoss") and active_z.get("okff") is not None) if (not enabled) or (not is_geschoss): if existing is not None: try: doc.Objects.Delete(existing.Id, True) except Exception: pass return okff = float(active_z.get("okff", 0.0)) sh = float(active_z.get("schnitthoehe", 1.0)) cut_z = okff + sh plane = rg.Plane(rg.Point3d(0.0, 0.0, cut_z), rg.Vector3d.ZAxis) du, dv = 50000.0, 50000.0 if existing is not None: try: new_surf = rg.PlaneSurface(plane, rg.Interval(-du/2.0, du/2.0), rg.Interval(-dv/2.0, dv/2.0)) doc.Objects.Replace(existing.Id, new_surf) except Exception as ex: print("[EBENEN] Clip-Update:", ex) else: vp_ids = [] for view in doc.Views: try: vp_ids.append(view.ActiveViewportID) except Exception: try: vp_ids.append(view.ActiveViewport.Id) except Exception: pass try: new_id = doc.Objects.AddClippingPlane(plane, du, dv, vp_ids) if new_id != System.Guid.Empty: obj = doc.Objects.FindId(new_id) if obj is not None: attrs = obj.Attributes.Duplicate() attrs.SetUserString(_CLIP_KEY, "1") attrs.Mode = Rhino.DocObjects.ObjectMode.Locked doc.Objects.ModifyAttributes(obj, attrs, True) print("[EBENEN] Clipping-Plane bei Z={} erstellt".format(cut_z)) except Exception as ex: print("[EBENEN] Clip-Create:", 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-Mode -> Parent-Zustand 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 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 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()