18d6d98e07
- C#-Plugin "DOSSIER" mit 23 nativen Commands (dWall, dDoor, ..., dSection)
- Native Command-Namen + Autocomplete + saubere History
- Idle-Defer + RhinoCode-API → kein _-RunPythonScript-Echo
- Yak-Paket via build.sh, Install in ~/Library/.../packages/8.0/
- Launcher (Tauri):
- dossier_init Tauri-Command + Setup-Tab in Settings
- Yak-Install + StartupCommands-XML + Window-Layout in einem Schritt
- clean-rhino.sh fuer reproduzierbare Resets
- check_dossier_initialized triggert Auto-Open-Setup beim ersten Start
- Wand-Architektur:
- Chain-Logik DEAKTIVIERT → jede Wand baut eigenes Volume (individuell
anwaehlbar, einzeln loeschbar)
- Polyline-Wand: jedes Segment = eigene Wand
- Smart-Split fuer wand_axis/decke/dach/raum/aussparung/traeger
- Auto-Group axis+volume → kein ChooseOne-Dialog, Delete loescht beides
- Stale-Mitre-Fix: Joint-Cache wird vor jedem Wand-Regen invalidiert
- T-Junction-Tolerance auf 1mm (war 1cm, lieferte falsche T-Mitres)
- Wand-Stile:
- Schema in dossier_project_settings.wand_styles (Material + Prio +
Default-Dicke + Referenz, oder Layered mit Schichten)
- dWall-Command Stil-Picker
- ProjectSettingsDialog: Sidebar-Layout (Pill-Selection) +
Wandstile-Tab mit Liste/Editor
- _wand_chain_compat benutzt style_id
- Prio-Dominanz: hoehere Prio gewinnt Eckverbindung, niedrigere wird
T-mitered (siehe _resolve_corner_miter)
- Cmd+G fuer Group (Geschoss-Up auf Alias 'gu')
- Welcome + Cheatsheet borderless mit X/Back-Buttons
- BeginCommand-Hook fuer Gestaltung-Panel-Auto-Open
- panel_base: Python.NET-Enum-Fix fuer Material-Render
437 lines
17 KiB
Python
437 lines
17 KiB
Python
#! 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()
|