#! python3 # -*- coding: utf-8 -*- # Pipette / Einstellungen-übernehmen: User klickt ein Source-Objekt, dessen # Attribute werden zur aktuellen Default-Einstellung gemacht — der naechste # gezeichnete Curve/Rectangle/etc. erbt sie automatisch. # # Was uebernommen wird: # 1. Layer → wird zum Current Layer # 2. Color (wenn per-Object Override) → wird Current Object-Color # 3. Linetype (per-Object) → Current # 4. PlotWeight (per-Object) → Current # 5. Fuer DOSSIER-Elemente (wand_axis, treppe_axis, etc.) → spezifische # UserStrings (Dicke, Modus, Breite, Stufen etc.) werden in sticky # gespeichert als _last_* → nachste Create-Wand/Treppe etc. nimmt sie. # 6. Bei Hatch-Quelle → wechselt auf den Curve dahinter (Hatch hat selten # direkt Sinn als Pipette-Quelle, eher der gefuellte Rahmen). import scriptcontext as sc import Rhino import Rhino.Input.Custom as ric import Rhino.DocObjects as rdoc from Rhino.Input import GetResult # Welche UserStrings pro DOSSIER-Type als sticky _last_* gespeichert werden, # damit das naechste Create-Cmd sie als Default uebernimmt. _DOSSIER_INHERIT = { "wand_axis": [ ("dossier_wand_dicke", "wand_dicke"), ("dossier_wand_referenz", "wand_referenz"), ("dossier_wand_modus", "wand_modus"), ], "treppe_axis": [ ("dossier_treppe_breite", "treppe_breite"), ("dossier_treppe_n", "treppe_n"), ("dossier_treppe_referenz", "treppe_referenz"), ("dossier_treppe_modus", "treppe_modus"), ("dossier_treppe_lauf_d", "treppe_lauf_d"), ("dossier_treppe_art", "treppe_art"), ], "decke_outline": [ ("dossier_decke_dicke", "decke_dicke"), ("dossier_decke_modus", "decke_modus"), ], "dach_outline": [ ("dossier_dach_dicke", "dach_dicke"), ("dossier_dach_neigung", "dach_neigung"), ], "stuetze_point": [ ("dossier_trag_profil", "stuetze_profil"), ("dossier_trag_b", "stuetze_b"), ("dossier_trag_h", "stuetze_h"), ], "traeger_axis": [ ("dossier_trag_profil", "traeger_profil"), ("dossier_trag_b", "traeger_b"), ("dossier_trag_h", "traeger_h"), ], "oeffnung_point": [ ("dossier_oeff_breite", "oeff_breite"), ("dossier_oeff_hoehe", "oeff_hoehe"), ], } def _save_sticky(key, value): sc.sticky["elemente_last_" + key] = value def _find_curve_behind_hatch(doc, hatch_obj): """Hatches haben in DOSSIER oft eine zugeordnete Source-Curve (gestaltung speichert die Curve-ID auf der Hatch via 'ebenen_fill_owner').""" try: owner = hatch_obj.Attributes.GetUserString("ebenen_fill_owner") or "" if owner: import System cid = System.Guid(owner) cobj = doc.Objects.FindId(cid) if cobj is not None and not cobj.IsDeleted: return cobj except Exception: pass return None def _run(): doc = Rhino.RhinoDoc.ActiveDoc if doc is None: return go = ric.GetObject() go.SetCommandPrompt("Pipette: Quell-Objekt picken (Attribute uebernehmen)") go.GeometryFilter = (rdoc.ObjectType.Curve | rdoc.ObjectType.Brep | rdoc.ObjectType.Hatch | rdoc.ObjectType.PointSet | rdoc.ObjectType.Point | rdoc.ObjectType.Annotation | rdoc.ObjectType.TextDot) go.SubObjectSelect = False if go.Get() != GetResult.Object: print("[PIPETTE] abgebrochen"); return src = go.Object(0).Object() if src is None: return # Wenn Hatch gepickt, switch zur Source-Curve (gefuelltes Rechteck als # Pipette-Quelle ist intuitiver als die Hatch selbst) src_geom_type = type(src.Geometry).__name__ if src_geom_type == "Hatch": cobj = _find_curve_behind_hatch(doc, src) if cobj is not None: src = cobj print("[PIPETTE] Hatch → zugeordnete Curve verwendet") sa = src.Attributes msgs = [] # 1. Layer als Current setzen try: if doc.Layers.CurrentLayerIndex != sa.LayerIndex: doc.Layers.SetCurrentLayerIndex(sa.LayerIndex, True) try: lname = doc.Layers[sa.LayerIndex].FullPath except Exception: lname = "idx=" + str(sa.LayerIndex) msgs.append("Layer={}".format(lname)) except Exception as ex: print("[PIPETTE] Layer-Set:", ex) # 2. Color try: cs = Rhino.ApplicationSettings.AppearanceSettings if sa.ColorSource == rdoc.ObjectColorSource.ColorFromObject: cs.DefaultObjectColorSource = rdoc.ObjectColorSource.ColorFromObject cs.DefaultObjectColor = sa.ObjectColor msgs.append("Color=obj") else: cs.DefaultObjectColorSource = rdoc.ObjectColorSource.ColorFromLayer msgs.append("Color=byLayer") except Exception as ex: print("[PIPETTE] Color-Set:", ex) # 3. Linetype + 4. PlotWeight — komplexer, weil Rhino keine direkten # AppearanceSettings dafuer hat. Wir ueberspringen bewusst, weil der # Layer-Wechsel die meisten Faelle abdeckt (Linetype + PlotWeight # kommen typisch ByLayer). # 5. DOSSIER-spezifische Attrs in sticky uebernehmen try: dtype = sa.GetUserString("dossier_element_type") or "" if dtype in _DOSSIER_INHERIT: inherited = [] for us_key, sticky_key in _DOSSIER_INHERIT[dtype]: v = sa.GetUserString(us_key) if v is None or v == "": continue # Numerische Werte ggf. konvertieren if any(k in sticky_key for k in ("dicke", "breite", "hoehe", "neigung", "lauf_d", "_b", "_h")): try: v = float(v) except Exception: pass elif "n" == sticky_key or sticky_key.endswith("_n"): try: v = int(float(v)) except Exception: pass _save_sticky(sticky_key, v) inherited.append("{}={}".format(sticky_key, v)) if inherited: msgs.append("DOSSIER " + dtype + ": " + ", ".join(inherited)) except Exception as ex: print("[PIPETTE] DOSSIER-Inherit:", ex) if msgs: print("[PIPETTE] Uebernommen: " + " | ".join(msgs)) else: print("[PIPETTE] Keine Aenderung (Source identisch zu Defaults)") # 7. Per-Object Custom-Hatch / Custom-Attrs: speichern als "pending" # + one-shot Listener auf AddRhinoObject — wenn naechster Curve # gezeichnet ist, alle Custom-Attrs auf den uebertragen. _setup_pending_apply(doc, src) # 6. Auto-Chain: passendes Draw-Command starten basierend auf # Source-Typ. So hat der User direkt "die richtige Tool in der Hand". _auto_chain(doc, src) def _capture_source_hatch_props(doc, src_obj): """Wenn Source einen per-Object Custom-Hatch hat, sample dessen Properties (Pattern/Scale/Rotation/Color).""" try: sa = src_obj.Attributes fill_hid = sa.GetUserString("ebenen_fill_hatch_id") or "" if not fill_hid: return None import System hid = System.Guid(fill_hid) hobj = doc.Objects.FindId(hid) if hobj is None or hobj.IsDeleted: return None hg = hobj.Geometry ha = hobj.Attributes if not hasattr(hg, "PatternIndex"): return None return { "pattern_idx": int(hg.PatternIndex), "scale": float(hg.PatternScale), "rotation": float(hg.PatternRotation), "layer_idx": int(ha.LayerIndex), "color_source": int(ha.ColorSource), "color_argb": int(ha.ObjectColor.ToArgb()), "plot_color_source": int(ha.PlotColorSource), "plot_color_argb": int(ha.PlotColor.ToArgb()), "linetype_source": int(ha.LinetypeSource), "linetype_idx": int(ha.LinetypeIndex), } except Exception as ex: print("[PIPETTE] capture-hatch:", ex) return None def _setup_pending_apply(doc, src_obj): """Speichert Source-Custom-Attrs in sticky + registriert one-shot AddRhinoObject-Listener der die Attrs (inkl. Hatch) auf den naechsten neuen Curve uebertraegt. Nach Apply wird Listener wieder entfernt.""" sa = src_obj.Attributes # Custom-User-Strings sammeln (DOSSIER-Element-Typen + andere). Skip # die Fill-Tracking-Keys weil wir den Hatch neu erstellen mit neuer ID. skip_keys = { "ebenen_fill_hatch_id", # zeigt auf alte Source-Hatch-ID "ebenen_fill_owner", } user_strings = {} try: for k in sa.GetUserStringKeys(): if k in skip_keys: continue v = sa.GetUserString(k) if v is not None: user_strings[k] = v except Exception as ex: print("[PIPETTE] user-strings:", ex) # Source-Geometrie Closed-State erfassen — wenn Source closed war, # erzwingen wir nach dem Add auch auf der Kopie ein Close (Polyline # bleibt sonst standardmaessig offen, hatten User-Feedback dazu). src_closed = False try: import Rhino.Geometry as _rg sg = src_obj.Geometry if isinstance(sg, _rg.Curve) and sg.IsClosed: src_closed = True except Exception: pass pending = { "linetype_source": int(sa.LinetypeSource), "linetype_idx": int(sa.LinetypeIndex), "plot_weight_source": int(sa.PlotWeightSource), "plot_weight": float(sa.PlotWeight), "user_strings": user_strings, "hatch_props": _capture_source_hatch_props(doc, src_obj), "src_closed": src_closed, } sc.sticky["dossier_pipette_pending"] = pending # One-shot handler — applied beim naechsten AddRhinoObject + entfernt sich def _on_add(sender, e): try: obj = e.TheObject if obj is None or obj.IsDeleted: return import Rhino.Geometry as rg2 if not isinstance(obj.Geometry, rg2.Curve): return _apply_pending(doc, obj, pending) except Exception as ex: print("[PIPETTE] one-shot apply:", ex) finally: try: Rhino.RhinoDoc.AddRhinoObject -= _on_add except Exception: pass sc.sticky.pop("dossier_pipette_pending", None) try: Rhino.RhinoDoc.AddRhinoObject += _on_add except Exception as ex: print("[PIPETTE] listener-install:", ex) def _force_close_curve(crv): """Schliesst eine offene Polyline durch Anhaengen des Startpunkts. Generische Curves: MakeClosed (nur wenn Endpunkte nahe) oder Join mit Lueckensegment. Returns geschlossene Curve oder None bei Fehler.""" import Rhino.Geometry as rg2 if crv is None or crv.IsClosed: return None try: if isinstance(crv, rg2.PolylineCurve): ok, pl = crv.TryGetPolyline() if ok and pl is not None and pl.Count >= 2: if pl[0].DistanceTo(pl[pl.Count - 1]) > 1e-9: pl.Add(pl[0]) return rg2.PolylineCurve(pl) return None # Generic: erst MakeClosed (closed wenn Endpunkte innerhalb tol) try: if crv.MakeClosed(1e-6): return crv except Exception: pass # Fallback: Lueckensegment einfuegen + joinen line = rg2.LineCurve(crv.PointAtEnd, crv.PointAtStart) joined = rg2.Curve.JoinCurves([crv, line], 1e-6) if joined and len(joined) > 0 and joined[0].IsClosed: return joined[0] except Exception as ex: print("[PIPETTE] force-close:", ex) return None def _apply_pending(doc, new_obj, pending): """Wendet pending state auf das neu erzeugte Objekt an.""" import Rhino.Geometry as rg2 import System # Close-Erzwingen wenn Source geschlossen war — Polyline-Command erzeugt # standardmaessig offene Curves; Pipette soll den Closed-State erhalten. if pending.get("src_closed"): try: crv = new_obj.Geometry if isinstance(crv, rg2.Curve) and not crv.IsClosed: closed = _force_close_curve(crv) if closed is not None: if doc.Objects.Replace(new_obj.Id, closed): ref = doc.Objects.FindId(new_obj.Id) if ref is not None: new_obj = ref print("[PIPETTE] Polyline auto-geschlossen (Source war closed)") except Exception as ex: print("[PIPETTE] close-replace:", ex) # Linetype + PlotWeight overrides try: na = new_obj.Attributes.Duplicate() if pending["linetype_source"] == int(rdoc.ObjectLinetypeSource.LinetypeFromObject): na.LinetypeSource = rdoc.ObjectLinetypeSource.LinetypeFromObject na.LinetypeIndex = pending["linetype_idx"] if pending["plot_weight_source"] == int(rdoc.ObjectPlotWeightSource.PlotWeightFromObject): na.PlotWeightSource = rdoc.ObjectPlotWeightSource.PlotWeightFromObject na.PlotWeight = pending["plot_weight"] # UserStrings 1:1 kopieren for k, v in pending["user_strings"].items(): try: na.SetUserString(k, v) except Exception: pass doc.Objects.ModifyAttributes(new_obj, na, True) except Exception as ex: print("[PIPETTE] apply-attrs:", ex) # Per-Object Custom-Hatch: nachbauen wenn Source einen hatte UND # der neue Curve closed ist hp = pending.get("hatch_props") if hp is None: return try: crv = new_obj.Geometry if not isinstance(crv, rg2.Curve) or not crv.IsClosed: return tol = doc.ModelAbsoluteTolerance hatches = rg2.Hatch.Create(crv, hp["pattern_idx"], hp["rotation"], hp["scale"], tol) if not hatches or len(hatches) == 0: return ha = rdoc.ObjectAttributes() ha.LayerIndex = hp["layer_idx"] ha.ColorSource = rdoc.ObjectColorSource(hp["color_source"]) ha.ObjectColor = System.Drawing.Color.FromArgb(hp["color_argb"]) try: ha.PlotColorSource = rdoc.ObjectPlotColorSource(hp["plot_color_source"]) ha.PlotColor = System.Drawing.Color.FromArgb(hp["plot_color_argb"]) except Exception: pass if hp["linetype_source"] == int(rdoc.ObjectLinetypeSource.LinetypeFromObject): ha.LinetypeSource = rdoc.ObjectLinetypeSource.LinetypeFromObject ha.LinetypeIndex = hp["linetype_idx"] ha.SetUserString("ebenen_fill_source", "object") ha.SetUserString("ebenen_fill_owner", str(new_obj.Id)) new_hid = doc.Objects.AddHatch(hatches[0], ha) if new_hid and new_hid != System.Guid.Empty: # Cross-Link: Curve speichert Hatch-ID ca = new_obj.Attributes.Duplicate() ca.SetUserString("ebenen_fill_hatch_id", str(new_hid)) ca.SetUserString("ebenen_fill_source", "object") doc.Objects.ModifyAttributes(new_obj, ca, True) print("[PIPETTE] Per-Object Hatch uebernommen (Pattern={}, Scale={})" .format(hp["pattern_idx"], hp["scale"])) except Exception as ex: print("[PIPETTE] hatch-replicate:", ex) def _auto_chain(doc, src): """Startet das passende Draw-Command basierend auf Source-Typ.""" sa = src.Attributes dtype = sa.GetUserString("dossier_element_type") or "" geom = src.Geometry geom_type = type(geom).__name__ # DOSSIER-BIM: triggere den Dispatcher _DOSSIER_DRAW = { "wand_axis": "wand", "treppe_axis": "treppe", "decke_outline": "decke", "dach_outline": "dach", "stuetze_point": "stuetze", "traeger_axis": "traeger", "oeffnung_point": None, # braucht parent-Wand-Kontext → skip auto-chain "raum_outline": "raum", } if dtype in _DOSSIER_DRAW: action = _DOSSIER_DRAW[dtype] if action: import os _here = os.path.dirname(os.path.abspath(__file__)) wrapper = os.path.join(_here, action + ".py") if os.path.exists(wrapper): Rhino.RhinoApp.RunScript( '_-RunPythonScript "{}"'.format(wrapper), False) print("[PIPETTE] → starte DOSSIER {}".format(action)) return # Standard-Rhino-Curves: detect Typ → entsprechendes Draw-Cmd cmd = None if geom_type == "LineCurve": cmd = "_Line" elif geom_type == "ArcCurve": # ArcCurve mit voller Sweep = Kreis try: if geom.IsClosed: cmd = "_Circle" else: cmd = "_Arc" except Exception: cmd = "_Arc" elif geom_type == "PolylineCurve": try: ok, pl = geom.TryGetPolyline() if ok and pl is not None and pl.IsClosed and pl.Count == 5: # Geschlossen + 4 Segmente → vermutlich Rectangle cmd = "_Rectangle" else: cmd = "_Polyline" except Exception: cmd = "_Polyline" elif geom_type == "NurbsCurve": cmd = "_Curve" elif geom_type == "TextEntity": cmd = "_Text" if cmd: Rhino.RhinoApp.RunScript(cmd, False) print("[PIPETTE] → starte {}".format(cmd)) _run()