DOSSIER Multi-Phase: C#-Plugin + Yak + Wandstile + UX-Polish
- 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
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
#! python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# Auto-Wrapper fuer Alias 'aussparung'. Importiert dossier_dispatch + ruft Action.
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
import dossier_dispatch
|
||||
dossier_dispatch.dispatch("aussparung")
|
||||
@@ -0,0 +1,7 @@
|
||||
#! python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# Auto-Wrapper fuer Alias 'dach'. Importiert dossier_dispatch + ruft Action.
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
import dossier_dispatch
|
||||
dossier_dispatch.dispatch("dach")
|
||||
@@ -0,0 +1,7 @@
|
||||
#! python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# Auto-Wrapper fuer Alias 'decke'. Importiert dossier_dispatch + ruft Action.
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
import dossier_dispatch
|
||||
dossier_dispatch.dispatch("decke")
|
||||
@@ -0,0 +1,7 @@
|
||||
#! python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# Alias 'dkeys': oeffnet DOSSIER Shortcuts-Cheatsheet
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||
import welcome
|
||||
welcome.show_cheatsheet()
|
||||
@@ -0,0 +1,8 @@
|
||||
#! python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# Alias 'dwelcome': zeigt DOSSIER Welcome-Screen manuell (force-mode,
|
||||
# ignoriert version-marker + optout)
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||
import welcome
|
||||
welcome._show_welcome_now()
|
||||
@@ -0,0 +1,7 @@
|
||||
#! python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# Auto-Wrapper fuer Alias 'fenster'. Importiert dossier_dispatch + ruft Action.
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
import dossier_dispatch
|
||||
dossier_dispatch.dispatch("fenster")
|
||||
@@ -0,0 +1,436 @@
|
||||
#! 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()
|
||||
@@ -0,0 +1,7 @@
|
||||
#! python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# Auto-Wrapper fuer Alias 'raum'. Importiert dossier_dispatch + ruft Action.
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
import dossier_dispatch
|
||||
dossier_dispatch.dispatch("raum")
|
||||
@@ -0,0 +1,57 @@
|
||||
#! python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# Wrapper fuer dSection: interaktiver Schnitt-Pick (2 Punkte + Blickrichtung).
|
||||
# Defaults kommen aus Project-Settings.defaults; nach erfolgreicher
|
||||
# Erstellung wird der neue Schnitt als aktive Zeichnungs-Ebene gesetzt.
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||
import Rhino
|
||||
import scriptcontext as sc
|
||||
|
||||
doc = Rhino.RhinoDoc.ActiveDoc
|
||||
if doc is None:
|
||||
print("[SECTION] kein aktives Dokument")
|
||||
else:
|
||||
try:
|
||||
import schnitte
|
||||
# Defaults aus Project-Settings; Fallback auf hartkodierte Werte.
|
||||
defaults = {
|
||||
"depthBack": 8.0, "heightMin": -1.0, "heightMax": 12.0,
|
||||
"cutAtLine": True, "namePrefix": "S",
|
||||
}
|
||||
try:
|
||||
import rhinopanel
|
||||
ps = rhinopanel.load_project_settings(doc)
|
||||
d = (ps or {}).get("defaults", {})
|
||||
defaults["depthBack"] = float(d.get("schnittDepthBack", 8.0))
|
||||
defaults["heightMin"] = float(d.get("schnittHeightMin", -1.0))
|
||||
defaults["heightMax"] = float(d.get("schnittHeightMax", 12.0))
|
||||
except Exception as ex:
|
||||
print("[SECTION] defaults from project-settings:", ex)
|
||||
|
||||
sid = schnitte.pick_schnitt_interactive(doc, defaults=defaults)
|
||||
if not sid:
|
||||
print("[SECTION] abgebrochen")
|
||||
else:
|
||||
# Broadcast neue Zeichnungs-Ebene an Panels + auto-aktivieren
|
||||
try:
|
||||
eb = sc.sticky.get("ebenen_bridge")
|
||||
if eb is not None:
|
||||
eb._send_state()
|
||||
except Exception as ex:
|
||||
print("[SECTION] broadcast:", ex)
|
||||
try:
|
||||
import json
|
||||
zraw = doc.Strings.GetValue("dossier_zeichnungsebenen") or "[]"
|
||||
z_list = json.loads(zraw)
|
||||
new_z = next((x for x in z_list
|
||||
if isinstance(x, dict) and x.get("id") == sid), None)
|
||||
if new_z is not None:
|
||||
eb = sc.sticky.get("ebenen_bridge")
|
||||
if eb is not None:
|
||||
eb._set_active_zeichnungsebene(new_z)
|
||||
print("[SECTION] erstellt: {}".format(sid))
|
||||
except Exception as ex:
|
||||
print("[SECTION] auto-activate:", ex)
|
||||
except Exception as ex:
|
||||
print("[SECTION] error:", ex)
|
||||
@@ -0,0 +1,157 @@
|
||||
#! python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# Smart-Join: bei geschlossenen Curves → BooleanUnion (innere Linien weg),
|
||||
# bei offenen Curves → normales _Join (Endpunkt-Verbindung).
|
||||
# Sicherheits-Filter:
|
||||
# A) Group by Layer + Object-Overrides (Color/Linetype/PlotWeight) + Fill —
|
||||
# nur Curves mit IDENTISCHEN visuellen Attributen werden gemerged.
|
||||
# C) Pre-Check Overlap — BooleanUnion liefert genauso viele Outputs wie
|
||||
# Inputs wenn nichts overlapt → dann KEINE Aktion, Curves bleiben.
|
||||
# Kombinierter Effekt: nur visuell zusammengehoerige UND tatsaechlich
|
||||
# ueberlappende Curves werden zu einer Outline vereint.
|
||||
import scriptcontext as sc
|
||||
import Rhino
|
||||
import Rhino.Geometry as rg
|
||||
import Rhino.DocObjects as rdoc
|
||||
|
||||
|
||||
def _attr_key(obj):
|
||||
"""Tuple das definiert ob 2 Curves visuell identisch sind. Layer +
|
||||
Per-Object-Overrides (alles was ByObject nicht ByLayer ist) + Fill-
|
||||
State (Hatch-ID + No-Fill-Flag)."""
|
||||
a = obj.Attributes
|
||||
layer_idx = a.LayerIndex
|
||||
|
||||
# Color: nur Object-Override unterscheidend, ByLayer ist gleich.
|
||||
col_key = ("layer",)
|
||||
try:
|
||||
if a.ColorSource == rdoc.ObjectColorSource.ColorFromObject:
|
||||
col_key = ("obj", a.ObjectColor.ToArgb())
|
||||
except Exception: pass
|
||||
|
||||
# Linetype
|
||||
lt_key = ("layer",)
|
||||
try:
|
||||
if a.LinetypeSource == rdoc.ObjectLinetypeSource.LinetypeFromObject:
|
||||
lt_key = ("obj", a.LinetypeIndex)
|
||||
except Exception: pass
|
||||
|
||||
# PlotWeight
|
||||
pw_key = ("layer",)
|
||||
try:
|
||||
if a.PlotWeightSource == rdoc.ObjectPlotWeightSource.PlotWeightFromObject:
|
||||
pw_key = ("obj", float(a.PlotWeight))
|
||||
except Exception: pass
|
||||
|
||||
# Fill / Hatch via gestaltung-UserStrings
|
||||
fill_hatch = ""
|
||||
fill_source = ""
|
||||
no_fill = ""
|
||||
try:
|
||||
fill_hatch = a.GetUserString("ebenen_fill_hatch_id") or ""
|
||||
fill_source = a.GetUserString("ebenen_fill_source") or ""
|
||||
no_fill = a.GetUserString("ebenen_no_fill") or ""
|
||||
except Exception: pass
|
||||
# Fuer Gruppierung zaehlt: "hatte Fill ja/nein" + Quelle + No-Fill-Flag.
|
||||
fill_key = (bool(fill_hatch), fill_source, no_fill)
|
||||
|
||||
return (layer_idx, col_key, lt_key, pw_key, fill_key)
|
||||
|
||||
|
||||
def _run():
|
||||
doc = Rhino.RhinoDoc.ActiveDoc
|
||||
if doc is None: return
|
||||
sel = list(doc.Objects.GetSelectedObjects(False, False))
|
||||
if not sel:
|
||||
Rhino.RhinoApp.RunScript("_Join", False); return
|
||||
|
||||
# Curves nach Closed/Open trennen
|
||||
closed_objs = []
|
||||
has_non_closed = False
|
||||
for obj in sel:
|
||||
g = obj.Geometry
|
||||
if isinstance(g, rg.Curve) and g.IsClosed:
|
||||
closed_objs.append(obj)
|
||||
else:
|
||||
has_non_closed = True
|
||||
|
||||
# Wenn nicht ALLE closed sind → einfach Standard-Join
|
||||
if has_non_closed or len(closed_objs) < 2:
|
||||
Rhino.RhinoApp.RunScript("_Join", False); return
|
||||
|
||||
# Gruppieren nach (Layer + Attrs + Fill)
|
||||
groups = {} # key → [obj, obj, ...]
|
||||
for obj in closed_objs:
|
||||
try:
|
||||
k = _attr_key(obj)
|
||||
except Exception:
|
||||
k = ("ungroup", id(obj))
|
||||
groups.setdefault(k, []).append(obj)
|
||||
|
||||
# gestaltung fuer Fill-Re-Apply
|
||||
_g = None
|
||||
try:
|
||||
import gestaltung as _gmod; _g = _gmod
|
||||
except Exception as iex:
|
||||
print("[SMART-JOIN] gestaltung import:", iex)
|
||||
|
||||
tol = doc.ModelAbsoluteTolerance
|
||||
ur = doc.BeginUndoRecord("DOSSIER Smart-Join (gruppiert)")
|
||||
n_merged_total = 0
|
||||
n_groups_ops = 0
|
||||
try:
|
||||
for key, objs in groups.items():
|
||||
if len(objs) < 2: continue # einzelne Curve → nichts zu mergen
|
||||
try:
|
||||
curves = [o.Geometry for o in objs]
|
||||
result = rg.Curve.CreateBooleanUnion(curves, tol)
|
||||
except Exception as ex:
|
||||
print("[SMART-JOIN] BooleanUnion in Gruppe fehlgeschlagen:", ex)
|
||||
continue
|
||||
if not result: continue
|
||||
# C) Pre-Check Overlap: wenn result-Anzahl gleich input-Anzahl
|
||||
# ist, gab's keinen tatsaechlichen Overlap → Gruppe nicht
|
||||
# anfassen.
|
||||
if len(result) >= len(objs):
|
||||
continue
|
||||
# Tatsaechlich gemerged → replace
|
||||
attrs_template = objs[0].Attributes.Duplicate()
|
||||
# Fill-Key clearen damit _apply_ebene_fill nicht "schon gefuellt"
|
||||
# zurueckgibt
|
||||
try:
|
||||
attrs_template.SetUserString("ebenen_fill_hatch_id", "")
|
||||
except Exception: pass
|
||||
|
||||
any_had_fill = bool(key[4][0]) # fill_key[0] = had-fill bool
|
||||
|
||||
new_ids = []
|
||||
for crv in result:
|
||||
nid = doc.Objects.AddCurve(crv, attrs_template)
|
||||
if nid: new_ids.append(nid)
|
||||
for o in objs:
|
||||
try: doc.Objects.Delete(o.Id, True)
|
||||
except Exception: pass
|
||||
# Fill nachziehen wenn Inputs welche hatten
|
||||
if any_had_fill and _g is not None:
|
||||
for nid in new_ids:
|
||||
try:
|
||||
nobj = doc.Objects.FindId(nid)
|
||||
if nobj is not None:
|
||||
_g._apply_ebene_fill(doc, nobj)
|
||||
except Exception as fex:
|
||||
print("[SMART-JOIN] fill-apply:", fex)
|
||||
n_merged_total += (len(objs) - len(result))
|
||||
n_groups_ops += 1
|
||||
finally:
|
||||
doc.EndUndoRecord(ur)
|
||||
|
||||
if n_groups_ops == 0:
|
||||
print("[SMART-JOIN] Nichts zu mergen — keine Curves overlappen "
|
||||
"(oder verschiedene Attribute/Layer)")
|
||||
else:
|
||||
doc.Views.Redraw()
|
||||
print("[SMART-JOIN] {} Gruppe(n) bearbeitet, {} Curve(s) zu Union vereint"
|
||||
.format(n_groups_ops, n_merged_total))
|
||||
|
||||
|
||||
_run()
|
||||
@@ -0,0 +1,267 @@
|
||||
#! python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# Smart-Split: User zeichnet eine Splitlinie/Polylinie waehrend des Befehls
|
||||
# (mehrere Klicks, Enter beendet die Eingabe). Alle Curves die die Linie
|
||||
# schneidet werden gesplittet.
|
||||
# - Offene Curves: bei den Schnittpunkten in offene Segmente.
|
||||
# - GESCHLOSSENE Curves: in mehrere CLOSED Sub-Regionen via
|
||||
# Curve.CreateBooleanRegions (funktioniert auch bei multi-segment
|
||||
# Polylinien-Cuttern). Per-Object-Hatch wird auf alle Regionen repliziert.
|
||||
# DOSSIER-Source-Typen (Wand-Achse etc.) bleiben geschuetzt.
|
||||
import scriptcontext as sc
|
||||
import Rhino
|
||||
import Rhino.Input.Custom as ric
|
||||
import Rhino.Geometry as rg
|
||||
import Rhino.DocObjects as rdoc
|
||||
from Rhino.Input import GetResult
|
||||
|
||||
|
||||
# Was Smart-Split NIE anfasst:
|
||||
# - oeffnung_point / stuetze_point: Punkte, nicht teilbar
|
||||
# - schnitt_axis: Schnitt-Linien sollen bleiben, sonst kaputte Schnitte
|
||||
# - treppe_axis: Treppen-State (Lauflinie, Schrittmass-Lock, Wendel-Sweep)
|
||||
# waere bei einem Split inkonsistent
|
||||
# Alles andere (wand/traeger/decke/dach/raum/aussparung) DARF gesplittet werden:
|
||||
# der Add-Listener in elemente.py erkennt die Duplikat-IDs der neuen Stuecke
|
||||
# und vergibt jedem Stueck ein frisches Element-ID + Regen → BIM-Volumen
|
||||
# baut sich pro neuem Stueck neu auf.
|
||||
_PROTECTED_TYPES = {
|
||||
"treppe_axis",
|
||||
"oeffnung_point", "stuetze_point", "schnitt_axis",
|
||||
}
|
||||
|
||||
|
||||
def _capture_hatch_props(doc, src_obj):
|
||||
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),
|
||||
"fill_source": sa.GetUserString("ebenen_fill_source") or "object",
|
||||
}
|
||||
except Exception as ex:
|
||||
print("[SMART-SPLIT] capture-hatch:", ex)
|
||||
return None
|
||||
|
||||
|
||||
def _replicate_hatch(doc, new_obj, hp):
|
||||
if hp is None: return
|
||||
import System
|
||||
try:
|
||||
crv = new_obj.Geometry
|
||||
if not isinstance(crv, rg.Curve) or not crv.IsClosed: return
|
||||
tol = doc.ModelAbsoluteTolerance
|
||||
hatches = rg.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", hp.get("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:
|
||||
ca = new_obj.Attributes.Duplicate()
|
||||
ca.SetUserString("ebenen_fill_hatch_id", str(new_hid))
|
||||
ca.SetUserString("ebenen_fill_source", hp.get("fill_source", "object"))
|
||||
doc.Objects.ModifyAttributes(new_obj, ca, True)
|
||||
except Exception as ex:
|
||||
print("[SMART-SPLIT] hatch-replicate:", ex)
|
||||
|
||||
|
||||
def _collect_polyline_cutter(prompt_first, prompt_more):
|
||||
"""Sammelt n Punkte fuer den Cutter. Enter beendet (min. 2 Punkte).
|
||||
ESC bricht ab. Returnt Polyline oder None."""
|
||||
pts = []
|
||||
while True:
|
||||
gp = ric.GetPoint()
|
||||
if not pts:
|
||||
gp.SetCommandPrompt(prompt_first)
|
||||
else:
|
||||
gp.SetCommandPrompt(prompt_more + " (Enter zum Splitten, ESC = abbrechen)")
|
||||
gp.SetBasePoint(pts[-1], True)
|
||||
gp.DrawLineFromPoint(pts[-1], True)
|
||||
gp.AcceptNothing(True)
|
||||
res = gp.Get()
|
||||
if res == GetResult.Nothing:
|
||||
# Enter gedrueckt
|
||||
if len(pts) >= 2: return rg.Polyline(pts)
|
||||
print("[SMART-SPLIT] Mindestens 2 Punkte noetig"); return None
|
||||
if res != GetResult.Point: return None
|
||||
pts.append(gp.Point())
|
||||
|
||||
|
||||
def _split_closed_with_cutter(closed_crv, cutter_crv, doc):
|
||||
"""Splittet closed curve mit beliebigem cutter (Linie oder Polylinie) in
|
||||
closed Sub-Regionen via Curve.CreateBooleanRegions."""
|
||||
tol = doc.ModelAbsoluteTolerance
|
||||
try:
|
||||
# WorldXY-Plane als Default (DOSSIER ist 2D Plan-Workflow)
|
||||
plane = rg.Plane.WorldXY
|
||||
regions = rg.Curve.CreateBooleanRegions(
|
||||
[closed_crv, cutter_crv], plane, False, tol)
|
||||
if regions is None or regions.RegionCount == 0:
|
||||
return None
|
||||
out = []
|
||||
for i in range(regions.RegionCount):
|
||||
rcurves = list(regions.RegionCurves(i))
|
||||
if not rcurves: continue
|
||||
if len(rcurves) == 1:
|
||||
if rcurves[0].IsClosed:
|
||||
out.append(rcurves[0])
|
||||
else:
|
||||
# einzelne offene curve — sollte nicht passieren bei
|
||||
# Boolean-Regions, aber defensiv
|
||||
joined = rg.Curve.JoinCurves([rcurves[0]], tol)
|
||||
if joined and len(joined) > 0 and joined[0].IsClosed:
|
||||
out.append(joined[0])
|
||||
else:
|
||||
joined = rg.Curve.JoinCurves(rcurves, tol)
|
||||
if joined:
|
||||
for j in joined:
|
||||
if j.IsClosed: out.append(j)
|
||||
return out if out else None
|
||||
except Exception as ex:
|
||||
print("[SMART-SPLIT] closed-split:", ex)
|
||||
return None
|
||||
|
||||
|
||||
def _run():
|
||||
doc = Rhino.RhinoDoc.ActiveDoc
|
||||
if doc is None: return
|
||||
|
||||
# Polylinie als Cutter sammeln
|
||||
poly = _collect_polyline_cutter(
|
||||
"Splitlinie Startpunkt",
|
||||
"Naechster Punkt")
|
||||
if poly is None or poly.Count < 2:
|
||||
return
|
||||
cutter = rg.PolylineCurve(poly)
|
||||
tol = doc.ModelAbsoluteTolerance
|
||||
|
||||
pre_sel = [o for o in doc.Objects.GetSelectedObjects(False, False)
|
||||
if o is not None and not o.IsDeleted]
|
||||
if pre_sel:
|
||||
source = pre_sel
|
||||
mode_label = "selektierte ({})".format(len(pre_sel))
|
||||
else:
|
||||
s = rdoc.ObjectEnumeratorSettings()
|
||||
s.HiddenObjects = False; s.LockedObjects = False
|
||||
source = list(doc.Objects.GetObjectList(s))
|
||||
mode_label = "alle sichtbaren"
|
||||
|
||||
candidates_open = []
|
||||
candidates_closed = []
|
||||
for obj in source:
|
||||
if obj is None or obj.IsDeleted: continue
|
||||
try:
|
||||
t = obj.Attributes.GetUserString("dossier_element_type") or ""
|
||||
if t in _PROTECTED_TYPES: continue
|
||||
except Exception: pass
|
||||
g = obj.Geometry
|
||||
if not isinstance(g, rg.Curve): continue
|
||||
try:
|
||||
ints = rg.Intersect.Intersection.CurveCurve(cutter, g, tol, tol)
|
||||
except Exception:
|
||||
continue
|
||||
if not ints or ints.Count == 0: continue
|
||||
|
||||
if g.IsClosed:
|
||||
candidates_closed.append((obj, g))
|
||||
else:
|
||||
params = []
|
||||
for i in range(ints.Count):
|
||||
ev = ints[i]
|
||||
if ev.IsPoint:
|
||||
params.append(ev.ParameterB)
|
||||
else:
|
||||
params.append(ev.ParameterB); params.append(ev.ParameterB2)
|
||||
if params:
|
||||
params = sorted(set(round(p, 6) for p in params))
|
||||
candidates_open.append((obj, g, params))
|
||||
|
||||
if not candidates_open and not candidates_closed:
|
||||
print("[SMART-SPLIT] Cutter schneidet nichts ({})".format(mode_label))
|
||||
return
|
||||
|
||||
ur = doc.BeginUndoRecord("DOSSIER Smart-Split")
|
||||
n_open = 0; n_closed = 0
|
||||
try:
|
||||
# Closed: Boolean-Regions → CLOSED Sub-Regionen + Fill replicate
|
||||
for obj, crv in candidates_closed:
|
||||
try:
|
||||
regions = _split_closed_with_cutter(crv, cutter, doc)
|
||||
if not regions or len(regions) <= 1: continue
|
||||
hatch_props = _capture_hatch_props(doc, obj)
|
||||
attrs = obj.Attributes.Duplicate()
|
||||
try: attrs.SetUserString("ebenen_fill_hatch_id", "")
|
||||
except Exception: pass
|
||||
new_ids = []
|
||||
for r in regions:
|
||||
nid = doc.Objects.AddCurve(r, attrs)
|
||||
if nid: new_ids.append(nid)
|
||||
doc.Objects.Delete(obj.Id, True)
|
||||
if hatch_props is not None:
|
||||
for nid in new_ids:
|
||||
nobj = doc.Objects.FindId(nid)
|
||||
if nobj is not None:
|
||||
_replicate_hatch(doc, nobj, hatch_props)
|
||||
else:
|
||||
try:
|
||||
import gestaltung as _gmod
|
||||
for nid in new_ids:
|
||||
nobj = doc.Objects.FindId(nid)
|
||||
if nobj is not None:
|
||||
_gmod._apply_ebene_fill(doc, nobj)
|
||||
except Exception: pass
|
||||
n_closed += 1
|
||||
except Exception as ex:
|
||||
print("[SMART-SPLIT] closed-fail:", ex)
|
||||
|
||||
# Open: split bei Params
|
||||
for obj, crv, params in candidates_open:
|
||||
try:
|
||||
pieces = crv.Split(params)
|
||||
if not pieces or len(pieces) <= 1: continue
|
||||
attrs = obj.Attributes.Duplicate()
|
||||
for p in pieces:
|
||||
doc.Objects.AddCurve(p, attrs)
|
||||
doc.Objects.Delete(obj.Id, True)
|
||||
n_open += 1
|
||||
except Exception as ex:
|
||||
print("[SMART-SPLIT] open-fail:", ex)
|
||||
finally:
|
||||
doc.EndUndoRecord(ur)
|
||||
|
||||
doc.Views.Redraw()
|
||||
print("[SMART-SPLIT] {} closed-Regionen + {} offene Curves gesplittet "
|
||||
"({} Cutter-Punkte, {})"
|
||||
.format(n_closed, n_open, poly.Count, mode_label))
|
||||
|
||||
|
||||
_run()
|
||||
@@ -0,0 +1,7 @@
|
||||
#! python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# Auto-Wrapper fuer Alias 'stempel'. Importiert dossier_dispatch + ruft Action.
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
import dossier_dispatch
|
||||
dossier_dispatch.dispatch("stempel")
|
||||
@@ -0,0 +1,7 @@
|
||||
#! python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# Auto-Wrapper fuer Alias 'stuetze'. Importiert dossier_dispatch + ruft Action.
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
import dossier_dispatch
|
||||
dossier_dispatch.dispatch("stuetze")
|
||||
@@ -0,0 +1,7 @@
|
||||
#! python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# Auto-Wrapper fuer Alias 'symbol'. Importiert dossier_dispatch + ruft Action.
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
import dossier_dispatch
|
||||
dossier_dispatch.dispatch("symbol")
|
||||
@@ -0,0 +1,7 @@
|
||||
#! python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# Auto-Wrapper fuer Alias 'traeger'. Importiert dossier_dispatch + ruft Action.
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
import dossier_dispatch
|
||||
dossier_dispatch.dispatch("traeger")
|
||||
@@ -0,0 +1,7 @@
|
||||
#! python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# Auto-Wrapper fuer Alias 'treppe'. Importiert dossier_dispatch + ruft Action.
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
import dossier_dispatch
|
||||
dossier_dispatch.dispatch("treppe")
|
||||
@@ -0,0 +1,7 @@
|
||||
#! python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# Auto-Wrapper fuer Alias 'tuer'. Importiert dossier_dispatch + ruft Action.
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
import dossier_dispatch
|
||||
dossier_dispatch.dispatch("tuer")
|
||||
@@ -0,0 +1,7 @@
|
||||
#! python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# Auto-Wrapper fuer Alias 'wand'. Importiert dossier_dispatch + ruft Action.
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
import dossier_dispatch
|
||||
dossier_dispatch.dispatch("wand")
|
||||
@@ -0,0 +1,97 @@
|
||||
#! python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
# Copyright (C) 2026 Karim Gabriele Varano
|
||||
"""
|
||||
dossier_dispatch.py
|
||||
Universal-Wrapper fuer DOSSIER-Bridge-Commands via Rhino-Alias.
|
||||
|
||||
Aufruf vom Alias:
|
||||
_-RunPythonScript "/.../dossier_dispatch.py" <action>
|
||||
oder via Rhino.Input.RhinoGet — wir lesen den letzten String-Parameter
|
||||
aus der Command-Line.
|
||||
|
||||
Aktionen mappen auf ElementeBridge._cmd_create_* via einer kleinen
|
||||
Dispatch-Tabelle. Bridge-Referenz wird in sc.sticky vom panel_factory
|
||||
abgelegt (siehe elemente.py _bridge_factory).
|
||||
"""
|
||||
import sys
|
||||
import scriptcontext as sc
|
||||
|
||||
|
||||
_ACTIONS = {
|
||||
"wand": ("_cmd_create_wall", ()),
|
||||
"tuer": ("_cmd_create_oeffnung", ("tuer",)),
|
||||
"fenster": ("_cmd_create_oeffnung", ("fenster",)),
|
||||
"decke": ("_cmd_create_decke", ()),
|
||||
"aussparung":("_cmd_create_aussparung",()),
|
||||
"dach": ("_cmd_create_dach", ()),
|
||||
"treppe": ("_cmd_create_treppe", ()),
|
||||
"stuetze": ("_cmd_create_stuetze", ()),
|
||||
"traeger": ("_cmd_create_traeger", ()),
|
||||
"raum": ("_cmd_create_raum", ()),
|
||||
"stempel": ("_cmd_create_stempel", ()),
|
||||
"symbol": ("_cmd_create_symbol", ()),
|
||||
}
|
||||
|
||||
|
||||
_PRETTY = {
|
||||
"wand": "DOSSIER Wand",
|
||||
"tuer": "DOSSIER Tuer",
|
||||
"fenster": "DOSSIER Fenster",
|
||||
"decke": "DOSSIER Decke",
|
||||
"aussparung": "DOSSIER Aussparung",
|
||||
"dach": "DOSSIER Dach",
|
||||
"treppe": "DOSSIER Treppe",
|
||||
"stuetze": "DOSSIER Stuetze",
|
||||
"traeger": "DOSSIER Traeger",
|
||||
"raum": "DOSSIER Raum",
|
||||
"stempel": "DOSSIER Stempel",
|
||||
"symbol": "DOSSIER Symbol",
|
||||
}
|
||||
|
||||
|
||||
def dispatch(action):
|
||||
"""Public entry — von per-action Wrapper-Scripts aufgerufen."""
|
||||
try:
|
||||
import Rhino
|
||||
Rhino.RhinoApp.SetCommandPrompt(_PRETTY.get(action, "DOSSIER " + action.capitalize()))
|
||||
except Exception: pass
|
||||
bridge = sc.sticky.get("dossier_bridge_elemente")
|
||||
if bridge is None:
|
||||
print("[DOSSIER-ALIAS] Elemente-Bridge nicht aktiv (Panel oeffnen)")
|
||||
return
|
||||
spec = _ACTIONS.get(action)
|
||||
if spec is None:
|
||||
print("[DOSSIER-ALIAS] Unbekannte Aktion:", action)
|
||||
return
|
||||
method_name, args = spec
|
||||
method = getattr(bridge, method_name, None)
|
||||
if method is None:
|
||||
print("[DOSSIER-ALIAS] Bridge-Method fehlt:", method_name)
|
||||
return
|
||||
try:
|
||||
method({}, *args)
|
||||
except Exception as ex:
|
||||
print("[DOSSIER-ALIAS]", action, "->", method_name, ":", ex)
|
||||
|
||||
|
||||
# Backwards-Compat (alter Name).
|
||||
_dispatch = dispatch
|
||||
|
||||
|
||||
def _read_action_from_argv():
|
||||
# sys.argv enthaelt bei _-RunPythonScript "path" arg1 arg2 ... die
|
||||
# Args nach dem Skript-Pfad. argv[0] = Skript-Pfad.
|
||||
if len(sys.argv) >= 2:
|
||||
return str(sys.argv[1]).strip().lower()
|
||||
return None
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
a = _read_action_from_argv()
|
||||
if a:
|
||||
_dispatch(a)
|
||||
else:
|
||||
print("[DOSSIER-ALIAS] Keine Aktion uebergeben. Erwartet:",
|
||||
", ".join(sorted(_ACTIONS.keys())))
|
||||
@@ -0,0 +1,72 @@
|
||||
#! python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
# Copyright (C) 2026 Karim Gabriele Varano
|
||||
"""
|
||||
dossier_view_mode.py
|
||||
Setzt Display-Mode (+ optional Standard-Ansicht) im aktiven Viewport.
|
||||
|
||||
Aufruf:
|
||||
_-RunPythonScript "/.../dossier_view_mode.py" <mode>
|
||||
mode: plan | persp3d | material | raytracing
|
||||
"""
|
||||
import sys
|
||||
import Rhino
|
||||
|
||||
|
||||
_MODES = {
|
||||
"plan": {"display": "Dossier Plan", "view": "Top", "label": "DOSSIER Plan-Mode"},
|
||||
"persp3d": {"display": "Dossier 3D", "view": "Perspective","label": "DOSSIER 3D-Mode"},
|
||||
"material": {"display": "Dossier Material", "view": None, "label": "DOSSIER Material-Mode"},
|
||||
"raytracing": {"display": "Dossier Raytracing", "view": None, "label": "DOSSIER Raytracing"},
|
||||
}
|
||||
|
||||
|
||||
def _apply(mode_name):
|
||||
spec = _MODES.get(mode_name)
|
||||
if spec is None:
|
||||
print("[VIEW-MODE] Unbekannt:", mode_name)
|
||||
return
|
||||
try: Rhino.RhinoApp.SetCommandPrompt(spec.get("label", "DOSSIER View"))
|
||||
except Exception: pass
|
||||
doc = Rhino.RhinoDoc.ActiveDoc
|
||||
if doc is None:
|
||||
print("[VIEW-MODE] Kein aktives Doc")
|
||||
return
|
||||
view = doc.Views.ActiveView
|
||||
if view is None:
|
||||
print("[VIEW-MODE] Kein aktiver Viewport")
|
||||
return
|
||||
# Standard-View setzen (Top / Perspective) falls definiert
|
||||
vw_name = spec["view"]
|
||||
if vw_name:
|
||||
try:
|
||||
view.ActiveViewport.SetProjection(
|
||||
Rhino.Display.DefinedViewportProjection.Top
|
||||
if vw_name == "Top"
|
||||
else Rhino.Display.DefinedViewportProjection.Perspective,
|
||||
vw_name, True)
|
||||
except Exception as ex:
|
||||
print("[VIEW-MODE] view-set:", ex)
|
||||
# Display-Mode setzen via Description-Lookup
|
||||
dm_name = spec["display"]
|
||||
try:
|
||||
all_dm = Rhino.Display.DisplayModeDescription.GetDisplayModes()
|
||||
target = None
|
||||
for d in all_dm:
|
||||
if d.EnglishName == dm_name or d.LocalName == dm_name:
|
||||
target = d; break
|
||||
if target is None:
|
||||
print("[VIEW-MODE] Display-Mode nicht gefunden:", dm_name)
|
||||
return
|
||||
view.ActiveViewport.DisplayMode = target
|
||||
view.Redraw()
|
||||
except Exception as ex:
|
||||
print("[VIEW-MODE] display-mode:", ex)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) >= 2:
|
||||
_apply(str(sys.argv[1]).strip().lower())
|
||||
else:
|
||||
print("[VIEW-MODE] Erwartet Mode-Name:", ", ".join(_MODES.keys()))
|
||||
@@ -0,0 +1,469 @@
|
||||
#! python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
# Copyright (C) 2026 Karim Gabriele Varano
|
||||
"""
|
||||
aliases/loader.py
|
||||
Liest shortcuts_default.json + User-Overrides aus dossier_settings.json,
|
||||
merged und wendet via Rhino.ApplicationSettings.CommandAliasList /
|
||||
ShortcutKeySettings an. Wird einmal beim Rhino-Start aus startup.py
|
||||
aufgerufen (idempotent — SetMacro ueberschreibt).
|
||||
|
||||
User-Override-Format in dossier_settings.json:
|
||||
"shortcuts_user": {
|
||||
"<action_id>": "<trigger_string>" // leer = Default
|
||||
}
|
||||
"""
|
||||
import os
|
||||
import json
|
||||
import Rhino
|
||||
|
||||
|
||||
_HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
_quit_xml_pairs = [] # gefuellt in apply_all(), genutzt vom Closing-Hook
|
||||
_DEFAULTS_PATH = os.path.join(_HERE, "shortcuts_default.json")
|
||||
_SETTINGS_PATHS = [
|
||||
os.path.expanduser("~/Library/Application Support/ch.gabrielevarano.Dossier/dossier_settings.json"),
|
||||
os.path.expanduser("~/Library/Application Support/RhinoPanel/dossier_settings.json"), # legacy
|
||||
]
|
||||
|
||||
|
||||
def _read_defaults():
|
||||
try:
|
||||
with open(_DEFAULTS_PATH, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
out = {}
|
||||
for k, v in data.items():
|
||||
if k.startswith("_"): continue
|
||||
if not isinstance(v, dict): continue
|
||||
out[k] = v
|
||||
return out
|
||||
except Exception as ex:
|
||||
print("[ALIAS-LOADER] Defaults lesen:", ex)
|
||||
return {}
|
||||
|
||||
|
||||
def _read_user_overrides():
|
||||
"""Liest 'shortcuts_user' aus dossier_settings.json. Format:
|
||||
{ action_id: trigger_string }. Leerer String / None = Default."""
|
||||
for path in _SETTINGS_PATHS:
|
||||
if not os.path.exists(path): continue
|
||||
try:
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
so = data.get("shortcuts_user")
|
||||
if isinstance(so, dict): return so
|
||||
except Exception as ex:
|
||||
print("[ALIAS-LOADER] Settings lesen:", ex)
|
||||
return {}
|
||||
|
||||
|
||||
def _expand_macro(macro):
|
||||
"""Platzhalter {ALIASDIR} → absoluter Pfad zum aliases/-Ordner."""
|
||||
return macro.replace("{ALIASDIR}", _HERE)
|
||||
|
||||
|
||||
# Sonderzeichen → Rhino-Enum-Namen (Mac XML + ShortcutKey-API)
|
||||
_SPECIAL_KEY_NAMES = {
|
||||
"-": "Minus", "+": "Plus", "=": "Equals",
|
||||
"/": "Slash", "\\": "Backslash",
|
||||
".": "Period", ",": "Comma",
|
||||
";": "Semicolon", "'": "Quote", "`": "Backquote",
|
||||
"[": "OpenBracket", "]": "CloseBracket",
|
||||
}
|
||||
|
||||
|
||||
def _normalize_key_part(key_part):
|
||||
"""Mapped Sonderzeichen wie '-' auf Enum-Namen ('Minus'). Buchstaben/F-Keys
|
||||
bleiben unveraendert (Case-preserved)."""
|
||||
if key_part in _SPECIAL_KEY_NAMES:
|
||||
return _SPECIAL_KEY_NAMES[key_part]
|
||||
return key_part
|
||||
|
||||
|
||||
def _xml_key_from_trigger(trigger):
|
||||
"""'Cmd+Shift+F3' → 'CommandShiftF3' (Mac Rhino XML-Schema).
|
||||
Cmd/Ctrl → 'Command', Shift → 'Shift', Alt/Option → 'Option'.
|
||||
Sonderzeichen ('-', '/', etc.) werden auf Enum-Namen gemapped."""
|
||||
t = trigger.replace(" ", "")
|
||||
parts = t.split("+") if "+" in t[1:] else [t]
|
||||
# Edge-Case: trigger endet auf literal '+' oder '-' → letztes Element ist Key
|
||||
# 'Cmd+-' → ['Cmd', '', '-'] via split. Fix: re-split last token wenn leer
|
||||
parts = [p for p in parts if p != ""]
|
||||
# Sonderfall trigger == 'Cmd+-' → split('+') = ['Cmd', '-'], OK
|
||||
# Sonderfall trigger == 'Cmd++' → split('+') = ['Cmd', '', ''] → key = '+'
|
||||
if "Cmd++" in trigger or "Ctrl++" in trigger or "Shift++" in trigger:
|
||||
parts = trigger.replace(" ", "").rstrip("+").split("+") + ["+"]
|
||||
if not parts: return None
|
||||
key_part = _normalize_key_part(parts[-1])
|
||||
mods = set(p.lower() for p in parts[:-1])
|
||||
has_cmd = ("cmd" in mods) or ("ctrl" in mods) or ("command" in mods)
|
||||
has_shift = "shift" in mods
|
||||
has_alt = ("alt" in mods) or ("option" in mods) or ("opt" in mods)
|
||||
prefix = ""
|
||||
if has_cmd: prefix += "Command"
|
||||
if has_shift: prefix += "Shift"
|
||||
if has_alt: prefix += "Option"
|
||||
return prefix + key_part
|
||||
|
||||
|
||||
def _entry_in_xml(xml_key, expected_macro):
|
||||
"""True wenn <entry key='<xml_key>'>expected_macro</entry> bereits im
|
||||
Mac Rhino settings-XML existiert."""
|
||||
import os
|
||||
import re
|
||||
paths = [
|
||||
os.path.expanduser("~/Library/Application Support/McNeel/Rhinoceros/8.0/settings/settings-Scheme__Default.xml"),
|
||||
]
|
||||
_esc = lambda s: s.replace("&", "&").replace("<", "<").replace(">", ">")
|
||||
pat = re.compile(
|
||||
r'<entry\s+key="' + re.escape(xml_key) + r'"\s*>([^<]*)</entry>')
|
||||
for path in paths:
|
||||
if not os.path.exists(path): continue
|
||||
try:
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
m = pat.search(content)
|
||||
if m and m.group(1) == _esc(expected_macro):
|
||||
return True
|
||||
except Exception: pass
|
||||
return False
|
||||
|
||||
|
||||
def _xml_persist_shortcut(xml_key, macro, verbose=False):
|
||||
"""Schreibt <entry key="<xml_key>"><macro></entry> direkt in Mac Rhino's
|
||||
settings-Scheme__Default.xml unter <child key='ShortcutKeys'>. String-
|
||||
basiert damit die Original-Formatierung 1:1 erhalten bleibt."""
|
||||
import os
|
||||
import re
|
||||
paths = [
|
||||
os.path.expanduser("~/Library/Application Support/McNeel/Rhinoceros/8.0/settings/settings-Scheme__Default.xml"),
|
||||
]
|
||||
n_written = 0
|
||||
_esc = lambda s: s.replace("&", "&").replace("<", "<").replace(">", ">")
|
||||
|
||||
for path in paths:
|
||||
if not os.path.exists(path): continue
|
||||
try:
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
new_entry = '<entry key="{}">{}</entry>'.format(xml_key, _esc(macro))
|
||||
|
||||
# Existing entry? Loeschen (mit umgebendem Whitespace+Newline)
|
||||
# und neu hinzufuegen mit sauberem Format. Vermeidet
|
||||
# kaputt-formatierte Entries.
|
||||
pat = re.compile(
|
||||
r'<entry\s+key="' + re.escape(xml_key) + r'"\s*(/>|>[^<]*</entry>)')
|
||||
m = pat.search(content)
|
||||
if m:
|
||||
# Check Line-Kontext: nur diese Entry auf Zeile + unveraendert?
|
||||
line_start = content.rfind("\n", 0, m.start()) + 1
|
||||
line_end = content.find("\n", m.end())
|
||||
if line_end < 0: line_end = len(content)
|
||||
line_trim = content[line_start:line_end].strip()
|
||||
if line_trim == new_entry:
|
||||
if verbose: print("[ALIAS-LOADER] XML '{}' unchanged".format(xml_key))
|
||||
continue
|
||||
# Sonst: loeschen inkl. preceding-newline+whitespace damit
|
||||
# keine orphan-line uebrig bleibt
|
||||
del_start = m.start()
|
||||
while del_start > 0 and content[del_start-1] in " \t":
|
||||
del_start -= 1
|
||||
if del_start > 0 and content[del_start-1] == "\n":
|
||||
del_start -= 1
|
||||
content = content[:del_start] + content[m.end():]
|
||||
if True:
|
||||
# ShortcutKeys-Section finden
|
||||
sec_start = content.find('<child key="ShortcutKeys">')
|
||||
if sec_start < 0:
|
||||
if verbose: print("[ALIAS-LOADER] ShortcutKeys-section fehlt")
|
||||
continue
|
||||
sec_end = content.find('</child>', sec_start)
|
||||
if sec_end < 0:
|
||||
if verbose: print("[ALIAS-LOADER] ShortcutKeys-close fehlt")
|
||||
continue
|
||||
# Indent vom letzten <entry> in der Section uebernehmen
|
||||
section = content[sec_start:sec_end]
|
||||
ms = list(re.finditer(r'\n([ \t]*)<entry\s', section))
|
||||
entry_indent = ms[-1].group(1) if ms else " "
|
||||
# Indent vor </child> (typisch 6 spaces)
|
||||
close_match = re.search(r'\n([ \t]*)$', content[:sec_end])
|
||||
close_indent = close_match.group(1) if close_match else " "
|
||||
# Section neu zusammensetzen: alles vor </child> bereinigt
|
||||
# + sauberer Insert
|
||||
before = content[:sec_end].rstrip(" \t") + "\n"
|
||||
content = (before + entry_indent + new_entry + "\n"
|
||||
+ close_indent + content[sec_end:])
|
||||
action = "added"
|
||||
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
f.write(content)
|
||||
n_written += 1
|
||||
if verbose: print("[ALIAS-LOADER] XML {} '{}'".format(action, xml_key))
|
||||
except Exception as ex:
|
||||
print("[ALIAS-LOADER] XML-Write {}: {}".format(path, ex))
|
||||
return n_written
|
||||
|
||||
|
||||
def _install_quit_xml_save(pairs):
|
||||
"""Rhino's Closing-Event fired auf Mac NICHT zuverlaessig. Wir
|
||||
installieren MEHRERE Hooks parallel:
|
||||
1. Rhino.RhinoApp.Closing (Mac: meist No-op, Windows: ok)
|
||||
2. Python atexit (laeuft wenn Interpreter terminiert)
|
||||
3. AppDomain.ProcessExit (.NET-Level Hook)
|
||||
4. Idle-Watcher: schreibt XML alle 30s wenn Aenderung erkannt
|
||||
(Fallback fuer Rhino's runtime-flush)
|
||||
Marker-Logging zur Verifikation welcher Hook wirklich feuert."""
|
||||
import os as _os
|
||||
import datetime as _dt
|
||||
_marker = _os.path.expanduser("~/Library/Logs/dossier_quit_hook.log")
|
||||
try:
|
||||
_os.makedirs(_os.path.dirname(_marker), exist_ok=True)
|
||||
except Exception: pass
|
||||
|
||||
def _log(msg):
|
||||
try:
|
||||
with open(_marker, "a") as f:
|
||||
f.write("[{}] {}\n".format(_dt.datetime.now().isoformat(), msg))
|
||||
except Exception: pass
|
||||
|
||||
def _write_all(source):
|
||||
n_ok = 0
|
||||
for xml_key, macro in pairs:
|
||||
if _xml_persist_shortcut(xml_key, macro, verbose=False) > 0:
|
||||
n_ok += 1
|
||||
_log("{} FIRED — {}/{} ok".format(source, n_ok, len(pairs)))
|
||||
return n_ok
|
||||
|
||||
n_hooks = 0
|
||||
try:
|
||||
import Rhino
|
||||
def _on_closing(*_):
|
||||
try: _write_all("RhinoClosing")
|
||||
except Exception as ex: _log("RhinoClosing ERROR: {}".format(ex))
|
||||
Rhino.RhinoApp.Closing += _on_closing
|
||||
n_hooks += 1
|
||||
except Exception as ex:
|
||||
_log("RhinoClosing install err: {}".format(ex))
|
||||
|
||||
try:
|
||||
import atexit
|
||||
def _on_atexit():
|
||||
try: _write_all("atexit")
|
||||
except Exception as ex: _log("atexit ERROR: {}".format(ex))
|
||||
atexit.register(_on_atexit)
|
||||
n_hooks += 1
|
||||
except Exception as ex:
|
||||
_log("atexit install err: {}".format(ex))
|
||||
|
||||
try:
|
||||
import System
|
||||
def _on_process_exit(*_):
|
||||
try: _write_all("ProcessExit")
|
||||
except Exception as ex: _log("ProcessExit ERROR: {}".format(ex))
|
||||
System.AppDomain.CurrentDomain.ProcessExit += _on_process_exit
|
||||
n_hooks += 1
|
||||
except Exception as ex:
|
||||
_log("ProcessExit install err: {}".format(ex))
|
||||
|
||||
# Idle-Watcher: periodisch (alle ~30s) checken ob unsere XML-Entries
|
||||
# noch da sind. Wenn nein → wieder reinschreiben. Ueberlebt Rhino-
|
||||
# Runtime-Flushes auch ohne Close-Event.
|
||||
try:
|
||||
import Rhino
|
||||
import time as _time
|
||||
_state = {"last": 0.0}
|
||||
def _idle_watcher(*_):
|
||||
try:
|
||||
now = _time.time()
|
||||
if now - _state["last"] < 30.0: return
|
||||
_state["last"] = now
|
||||
# Pruefen ob entries fehlen — wenn ja, alle re-schreiben
|
||||
_write_all("IdleWatch")
|
||||
except Exception as ex:
|
||||
_log("IdleWatch ERROR: {}".format(ex))
|
||||
Rhino.RhinoApp.Idle += _idle_watcher
|
||||
n_hooks += 1
|
||||
_log("IdleWatch installed (30s interval)")
|
||||
except Exception as ex:
|
||||
_log("IdleWatch install err: {}".format(ex))
|
||||
|
||||
_log("Hooks INSTALLED ({} of 4) for {} shortcuts".format(n_hooks, len(pairs)))
|
||||
# Initiale Schreibung im ersten Pass auch — falls Rhino sofort flusht
|
||||
_write_all("InitialWrite")
|
||||
return n_hooks > 0
|
||||
|
||||
|
||||
def _resolve_fkey(trigger):
|
||||
"""'F3' / 'Shift+F3' / 'Cmd+F3' / 'Cmd+Alt+F3' → ShortcutKey-Enum-Wert.
|
||||
Enum-Naming-Konvention von Rhino: Ctrl → Shift → Alt → KeyName
|
||||
(z.B. CtrlAltF3, CtrlShiftAltF3). Cmd auf Mac mappt auf Ctrl,
|
||||
Option/Opt auf Alt. Sonderzeichen via _SPECIAL_KEY_NAMES."""
|
||||
SK = Rhino.ApplicationSettings.ShortcutKey
|
||||
t = trigger.replace(" ", "")
|
||||
parts = t.split("+")
|
||||
parts = [p for p in parts if p != ""]
|
||||
if not parts: return None
|
||||
raw_last = parts[-1]
|
||||
if raw_last in _SPECIAL_KEY_NAMES:
|
||||
key_part = _SPECIAL_KEY_NAMES[raw_last]
|
||||
else:
|
||||
key_part = raw_last.upper()
|
||||
mods = set(p.lower() for p in parts[:-1])
|
||||
has_ctrl = ("ctrl" in mods) or ("cmd" in mods) or ("command" in mods)
|
||||
has_shift = "shift" in mods
|
||||
has_alt = ("alt" in mods) or ("option" in mods) or ("opt" in mods)
|
||||
prefix = ""
|
||||
if has_ctrl: prefix += "Ctrl"
|
||||
if has_shift: prefix += "Shift"
|
||||
if has_alt: prefix += "Alt"
|
||||
return getattr(SK, prefix + key_part, None)
|
||||
|
||||
|
||||
def _resolve_cmd_letter(trigger):
|
||||
"""'Cmd+W' / 'Cmd+Shift+W' → ShortcutKey-Enum (Ctrl* auf Rhino-Naming-
|
||||
Konvention; Mac mappt Ctrl auf Cmd intern)."""
|
||||
SK = Rhino.ApplicationSettings.ShortcutKey
|
||||
t = trigger.replace(" ", "")
|
||||
parts = t.split("+")
|
||||
if len(parts) < 2: return None
|
||||
letter = parts[-1].upper()
|
||||
if not (len(letter) == 1 and letter.isalpha()): return None
|
||||
mods = set(p.lower() for p in parts[:-1])
|
||||
has_cmd = ("cmd" in mods) or ("ctrl" in mods)
|
||||
if not has_cmd: return None
|
||||
name = "Ctrl"
|
||||
if "shift" in mods: name += "Shift"
|
||||
if "alt" in mods: name += "Alt"
|
||||
name += letter
|
||||
return getattr(SK, name, None)
|
||||
|
||||
|
||||
def apply_all():
|
||||
"""Liest Defaults + Overrides, wendet alle Aliases + Shortcuts an.
|
||||
Returnt (n_alias, n_fkey, n_cmd, n_skipped)."""
|
||||
global _quit_xml_pairs
|
||||
_quit_xml_pairs = []
|
||||
defaults = _read_defaults()
|
||||
overrides = _read_user_overrides()
|
||||
aliases = Rhino.ApplicationSettings.CommandAliasList
|
||||
skset = Rhino.ApplicationSettings.ShortcutKeySettings
|
||||
n_alias = n_fkey = n_cmd = n_skipped = 0
|
||||
seen_triggers = {} # trigger_normalized -> action_id (Konflikt-Erkennung)
|
||||
|
||||
for action_id, spec in defaults.items():
|
||||
# User-Override hat Vorrang. Leerer String = Default, None/missing = Default.
|
||||
user_trig = overrides.get(action_id)
|
||||
if user_trig is not None and str(user_trig).strip() == "":
|
||||
user_trig = None
|
||||
trigger = user_trig if user_trig else spec.get("trigger", "")
|
||||
if not trigger:
|
||||
n_skipped += 1
|
||||
continue
|
||||
spec_type = spec.get("type", "alias")
|
||||
macro = _expand_macro(spec.get("macro", ""))
|
||||
if not macro:
|
||||
n_skipped += 1; continue
|
||||
|
||||
# Konflikt-Check (gleicher Trigger → letzter gewinnt, Warning)
|
||||
norm = (spec_type, str(trigger).lower())
|
||||
if norm in seen_triggers:
|
||||
print("[ALIAS-LOADER] Konflikt: '{}' fuer {} bereits von {} belegt"
|
||||
.format(trigger, action_id, seen_triggers[norm]))
|
||||
seen_triggers[norm] = action_id
|
||||
|
||||
try:
|
||||
if spec_type == "alias":
|
||||
tname = str(trigger)
|
||||
try:
|
||||
if aliases.IsAlias(tname):
|
||||
aliases.Delete(tname)
|
||||
except Exception: pass
|
||||
added = False
|
||||
try:
|
||||
added = aliases.Add(tname, macro)
|
||||
except Exception as _addex:
|
||||
print("[ALIAS-LOADER] Add({}, ...) Exception: {}"
|
||||
.format(tname, _addex))
|
||||
if not added:
|
||||
try: aliases.SetMacro(tname, macro)
|
||||
except Exception: pass
|
||||
# Verifizieren ob Alias wirklich registriert ist
|
||||
try:
|
||||
is_ok = aliases.IsAlias(tname)
|
||||
if not is_ok:
|
||||
print("[ALIAS-LOADER] WARN: '{}' (action={}) NICHT registriert "
|
||||
"— Rhino lehnt Namen wahrscheinlich ab (z.B. reine Zahl)"
|
||||
.format(tname, action_id))
|
||||
n_skipped += 1
|
||||
continue
|
||||
except Exception: pass
|
||||
n_alias += 1
|
||||
elif spec_type == "fkey":
|
||||
sk = _resolve_fkey(str(trigger))
|
||||
xml_key = _xml_key_from_trigger(str(trigger))
|
||||
api_ok = False
|
||||
if sk is not None:
|
||||
try:
|
||||
skset.SetMacro(sk, macro)
|
||||
got = skset.GetMacro(sk)
|
||||
api_ok = (got == macro)
|
||||
except Exception as _sex:
|
||||
print("[ALIAS-LOADER] SetMacro({}): {}".format(trigger, _sex))
|
||||
if not api_ok and xml_key:
|
||||
# Enum-Wert fehlt → direkt ins XML (mit verbose-Log).
|
||||
# n_xml=0 kann "schon korrekt" ODER "gescheitert" heissen
|
||||
# — wir checken explizit ob Entry im XML existiert.
|
||||
n_xml = _xml_persist_shortcut(xml_key, macro, verbose=True)
|
||||
if n_xml > 0:
|
||||
_quit_xml_pairs.append((xml_key, macro))
|
||||
else:
|
||||
# n_xml == 0 → entweder "unchanged" (= schon korrekt
|
||||
# im XML) oder "missing path/section". Check via
|
||||
# IsAliasInXml damit wir nicht falsch warnen.
|
||||
if _entry_in_xml(xml_key, macro):
|
||||
# Schon korrekt im XML → fuer Quit-Hook merken
|
||||
# damit Rhino-Quit-Save sie nicht ueberschreibt
|
||||
_quit_xml_pairs.append((xml_key, macro))
|
||||
else:
|
||||
print("[ALIAS-LOADER] WARN F-Key {} ({}) konnte weder "
|
||||
"API noch XML gesetzt werden".format(trigger, action_id))
|
||||
n_skipped += 1; continue
|
||||
n_fkey += 1
|
||||
elif spec_type == "cmd":
|
||||
sk = _resolve_cmd_letter(str(trigger))
|
||||
if sk is None:
|
||||
# Fallback: Cmd+Letter API u.U. nicht im Enum → als Alias mit dem
|
||||
# Letter (single-char) registrieren. User tippt dann Letter+Enter.
|
||||
letter_only = str(trigger).split("+")[-1].lower()
|
||||
if len(letter_only) == 1 and letter_only.isalpha():
|
||||
aliases.SetMacro(letter_only, macro)
|
||||
n_alias += 1
|
||||
print("[ALIAS-LOADER] {} ({}): Cmd+Letter nicht im Enum, "
|
||||
"fallback Alias '{}'".format(action_id, trigger, letter_only))
|
||||
else:
|
||||
n_skipped += 1
|
||||
continue
|
||||
skset.SetMacro(sk, macro)
|
||||
n_cmd += 1
|
||||
else:
|
||||
print("[ALIAS-LOADER] Unbekannter Type:", spec_type); n_skipped += 1
|
||||
except Exception as ex:
|
||||
print("[ALIAS-LOADER] Apply", action_id, "->", trigger, ":", ex)
|
||||
n_skipped += 1
|
||||
|
||||
# Quit-Hook installieren falls XML-only Shortcuts gesetzt wurden — diese
|
||||
# ueberlebt sonst Rhino's Auto-Save beim Quit nicht.
|
||||
if _quit_xml_pairs:
|
||||
_install_quit_xml_save(list(_quit_xml_pairs))
|
||||
print("[ALIAS-LOADER] {} XML-only Shortcuts werden bei Quit "
|
||||
"re-persistiert (Closing-Hook installiert)"
|
||||
.format(len(_quit_xml_pairs)))
|
||||
|
||||
return n_alias, n_fkey, n_cmd, n_skipped
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
a, f, c, s = apply_all()
|
||||
print("[ALIAS-LOADER] OK: {} alias, {} fkey, {} cmd, {} skipped"
|
||||
.format(a, f, c, s))
|
||||
@@ -0,0 +1,66 @@
|
||||
{
|
||||
"_meta": {
|
||||
"version": 2,
|
||||
"description": "DOSSIER Default Shortcuts. Schema: F1-F12 = 2D-Werkzeuge (Single-Tastendruck). Shift+F* = Views/Panels. Cmd+F* = BIM-Objekte. F8/F9 bleiben Rhino-Default (Ortho/Snap). 2D-Tools auch als Alias n1-n0 (Fallback fuer typen). 2-Letter-Aliases (st/tg/ra/sy/sp/dh/au) fuer seltenere BIM. User-Overrides leben in dossier_settings.json unter 'shortcuts_user' = {action_id: trigger_string}. Macro-Platzhalter {ALIASDIR} wird zur Laufzeit ersetzt."
|
||||
},
|
||||
|
||||
"wand": { "type": "fkey", "trigger": "Cmd+F1", "label": "DOSSIER Wand erstellen", "macro": "dWall" },
|
||||
"tuer": { "type": "fkey", "trigger": "Cmd+F2", "label": "DOSSIER Tuer erstellen", "macro": "dDoor" },
|
||||
"fenster": { "type": "fkey", "trigger": "Cmd+F3", "label": "DOSSIER Fenster erstellen", "macro": "dWindow" },
|
||||
"decke": { "type": "fkey", "trigger": "Cmd+F4", "label": "DOSSIER Decke erstellen", "macro": "dSlab" },
|
||||
"treppe": { "type": "fkey", "trigger": "Cmd+F5", "label": "DOSSIER Treppe erstellen", "macro": "dStair" },
|
||||
"stuetze": { "type": "fkey", "trigger": "Cmd+F6", "label": "DOSSIER Stuetze erstellen", "macro": "dColumn" },
|
||||
"traeger": { "type": "fkey", "trigger": "Cmd+F7", "label": "DOSSIER Traeger erstellen", "macro": "dBeam" },
|
||||
"raum": { "type": "fkey", "trigger": "Cmd+F10", "label": "DOSSIER Raum erstellen", "macro": "dRoom" },
|
||||
"symbol": { "type": "fkey", "trigger": "Cmd+F11", "label": "DOSSIER Symbol erstellen", "macro": "dSymbol" },
|
||||
"stempel": { "type": "fkey", "trigger": "Cmd+F12", "label": "DOSSIER Stempel erstellen", "macro": "dTag" },
|
||||
"dach": { "type": "alias", "trigger": "dh", "label": "DOSSIER Dach (Alias)", "macro": "dRoof" },
|
||||
"aussparung": { "type": "alias", "trigger": "au", "label": "DOSSIER Aussparung (Alias)", "macro": "dVoid" },
|
||||
|
||||
"text": { "type": "fkey", "trigger": "F1", "label": "Text", "macro": "_Text" },
|
||||
"line": { "type": "fkey", "trigger": "F2", "label": "Linie", "macro": "_Line" },
|
||||
"arc": { "type": "fkey", "trigger": "F3", "label": "Kreisbogen", "macro": "_Arc" },
|
||||
"rectangle": { "type": "fkey", "trigger": "F4", "label": "Rechteck", "macro": "_Rectangle" },
|
||||
"polyline": { "type": "fkey", "trigger": "F5", "label": "Polylinie", "macro": "_Polyline" },
|
||||
"curve": { "type": "fkey", "trigger": "F6", "label": "Spline / Kurve", "macro": "_Curve" },
|
||||
"hatch": { "type": "fkey", "trigger": "F7", "label": "Schraffur", "macro": "_Hatch" },
|
||||
"polygon": { "type": "fkey", "trigger": "F10", "label": "Polygon", "macro": "_Polygon" },
|
||||
"ellipse": { "type": "fkey", "trigger": "F11", "label": "Ellipse", "macro": "_Ellipse" },
|
||||
"circle": { "type": "fkey", "trigger": "F12", "label": "Kreis", "macro": "_Circle" },
|
||||
|
||||
"view_plan": { "type": "fkey", "trigger": "Cmd+K", "label": "Plan-Mode (Top + Dossier Plan)", "macro": "dPlan" },
|
||||
"view_3d": { "type": "fkey", "trigger": "Cmd+L", "label": "3D-Mode (Perspective + Dossier 3D)", "macro": "d3D" },
|
||||
"zoom_ext": { "type": "fkey", "trigger": "Cmd+U", "label": "Zoom Extents", "macro": "_Zoom _All _Extents" },
|
||||
"zoom_sel": { "type": "fkey", "trigger": "Cmd+Shift+U", "label": "Zoom Selected", "macro": "_Zoom _Selected" },
|
||||
"mod_group": { "type": "fkey", "trigger": "Cmd+G", "label": "Gruppieren (Group)", "macro": "_Group" },
|
||||
"geschoss_up": { "type": "alias", "trigger": "gu", "label": "Geschoss hoch (Alias)", "macro": "dLevelUp" },
|
||||
"geschoss_down": { "type": "fkey", "trigger": "Cmd+B", "label": "Geschoss tief", "macro": "dLevelDown" },
|
||||
"view_material": { "type": "alias", "trigger": "ma", "label": "Material-Mode (Alias)", "macro": "dMaterial" },
|
||||
"panel_layer": { "type": "alias", "trigger": "la", "label": "Layer-Panel (Alias)", "macro": "_Layer" },
|
||||
"panel_elemente": { "type": "alias", "trigger": "el", "label": "DOSSIER Elemente-Panel (Alias)", "macro": "-_ShowPanel \"DOSSIER Elemente\"" },
|
||||
|
||||
"mod_mirror": { "type": "fkey", "trigger": "Cmd+I", "label": "Spiegeln (Mirror)", "macro": "_Mirror" },
|
||||
"mod_copy": { "type": "fkey", "trigger": "Cmd+D", "label": "Kopieren (Copy = Duplicate)", "macro": "_Copy" },
|
||||
"mod_rotate": { "type": "fkey", "trigger": "Cmd+R", "label": "Drehen (Rotate)", "macro": "_Rotate" },
|
||||
"mod_trim": { "type": "fkey", "trigger": "Cmd+T", "label": "Trim (Schneiden)", "macro": "_Trim" },
|
||||
"mod_join": { "type": "fkey", "trigger": "Cmd+J", "label": "Verbinden (Smart-Join: Regionen → Union, sonst Join)", "macro": "dJoin" },
|
||||
"mod_explode": { "type": "fkey", "trigger": "Cmd+E", "label": "Trennen (Explode)", "macro": "_Explode" },
|
||||
"mod_fillet": { "type": "fkey", "trigger": "Cmd+Shift+V", "label": "Verrunden (Fillet)", "macro": "_Fillet" },
|
||||
"mod_move": { "type": "fkey", "trigger": "Cmd+M", "label": "Verschieben (Move)", "macro": "_Move" },
|
||||
"mod_offset": { "type": "fkey", "trigger": "Cmd+Shift+P", "label": "Parallele (OffsetCrv)", "macro": "_OffsetCrv" },
|
||||
"mod_split": { "type": "fkey", "trigger": "Cmd+X", "label": "Smart-Split (Splitlinie zeichnen — ueberschreibt Cut)", "macro": "dSplit" },
|
||||
"mod_chamfer": { "type": "fkey", "trigger": "Cmd+Shift+C", "label": "Abfasen (Chamfer)", "macro": "_Chamfer" },
|
||||
"mod_pipette": { "type": "fkey", "trigger": "Cmd+Y", "label": "Pipette (Einstellungen uebernehmen)", "macro": "dPipette" },
|
||||
"cheatsheet": { "type": "fkey", "trigger": "Cmd+-", "label": "DOSSIER Shortcuts-Cheatsheet", "macro": "dKeys" },
|
||||
|
||||
"text_alias": { "type": "alias", "trigger": "n1", "label": "Text (Alias)", "macro": "_Text" },
|
||||
"line_alias": { "type": "alias", "trigger": "n2", "label": "Linie (Alias)", "macro": "_Line" },
|
||||
"arc_alias": { "type": "alias", "trigger": "n3", "label": "Kreisbogen (Alias)", "macro": "_Arc" },
|
||||
"rectangle_alias": { "type": "alias", "trigger": "n4", "label": "Rechteck (Alias)", "macro": "_Rectangle" },
|
||||
"polyline_alias": { "type": "alias", "trigger": "n5", "label": "Polylinie (Alias)", "macro": "_Polyline" },
|
||||
"curve_alias": { "type": "alias", "trigger": "n6", "label": "Kurve (Alias)", "macro": "_Curve" },
|
||||
"hatch_alias": { "type": "alias", "trigger": "n7", "label": "Schraffur (Alias)", "macro": "_Hatch" },
|
||||
"polygon_alias": { "type": "alias", "trigger": "n8", "label": "Polygon (Alias)", "macro": "_Polygon" },
|
||||
"ellipse_alias": { "type": "alias", "trigger": "n9", "label": "Ellipse (Alias)", "macro": "_Ellipse" },
|
||||
"circle_alias": { "type": "alias", "trigger": "n0", "label": "Kreis (Alias)", "macro": "_Circle" }
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
#! python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# Geschoss runter (zum naechsttieferen Eintrag in der Zeichnungsebenen-Liste)
|
||||
import json
|
||||
import scriptcontext as sc
|
||||
import Rhino
|
||||
|
||||
|
||||
def _go(delta):
|
||||
doc = Rhino.RhinoDoc.ActiveDoc
|
||||
if doc is None:
|
||||
print("[GESCHOSS-NAV] kein Doc"); return
|
||||
bridge = sc.sticky.get("ebenen_bridge_ref")
|
||||
if bridge is None:
|
||||
print("[GESCHOSS-NAV] Ebenen-Bridge nicht aktiv (Panel oeffnen)"); return
|
||||
try:
|
||||
zraw = doc.Strings.GetValue("dossier_zeichnungsebenen") or ""
|
||||
zs = json.loads(zraw) if zraw else []
|
||||
if not isinstance(zs, list) or not zs:
|
||||
print("[GESCHOSS-NAV] keine Zeichnungsebenen"); return
|
||||
cur_id = doc.Strings.GetValue("dossier_active_id") or ""
|
||||
idx = -1
|
||||
for i, z in enumerate(zs):
|
||||
if isinstance(z, dict) and z.get("id") == cur_id:
|
||||
idx = i; break
|
||||
if idx < 0:
|
||||
idx = len(zs) # nichts aktiv → starten unten
|
||||
new_idx = max(0, min(len(zs) - 1, idx + delta))
|
||||
if new_idx == idx:
|
||||
print("[GESCHOSS-NAV] schon am {}".format(
|
||||
"untersten" if delta > 0 else "obersten")); return
|
||||
target = zs[new_idx]
|
||||
if not isinstance(target, dict) or not target.get("id"):
|
||||
print("[GESCHOSS-NAV] Zielebene ungueltig"); return
|
||||
print("[GESCHOSS-NAV] wechsle zu '{}'".format(target.get("name") or target["id"]))
|
||||
bridge._set_active_zeichnungsebene(target)
|
||||
except Exception as ex:
|
||||
print("[GESCHOSS-NAV]", ex)
|
||||
|
||||
|
||||
# delta=+1 = nach unten (naechster Eintrag in der Liste)
|
||||
_go(+1)
|
||||
@@ -0,0 +1,43 @@
|
||||
#! python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# Geschoss hoch (zum naechstoberen Eintrag in der Zeichnungsebenen-Liste)
|
||||
import json
|
||||
import scriptcontext as sc
|
||||
import Rhino
|
||||
|
||||
|
||||
def _go(delta):
|
||||
doc = Rhino.RhinoDoc.ActiveDoc
|
||||
if doc is None:
|
||||
print("[GESCHOSS-NAV] kein Doc"); return
|
||||
bridge = sc.sticky.get("ebenen_bridge_ref")
|
||||
if bridge is None:
|
||||
print("[GESCHOSS-NAV] Ebenen-Bridge nicht aktiv (Panel oeffnen)"); return
|
||||
try:
|
||||
zraw = doc.Strings.GetValue("dossier_zeichnungsebenen") or ""
|
||||
zs = json.loads(zraw) if zraw else []
|
||||
if not isinstance(zs, list) or not zs:
|
||||
print("[GESCHOSS-NAV] keine Zeichnungsebenen"); return
|
||||
cur_id = doc.Strings.GetValue("dossier_active_id") or ""
|
||||
idx = -1
|
||||
for i, z in enumerate(zs):
|
||||
if isinstance(z, dict) and z.get("id") == cur_id:
|
||||
idx = i; break
|
||||
if idx < 0:
|
||||
idx = 0 # nichts aktiv → starten oben
|
||||
new_idx = max(0, min(len(zs) - 1, idx + delta))
|
||||
if new_idx == idx:
|
||||
print("[GESCHOSS-NAV] schon am {}".format(
|
||||
"obersten" if delta < 0 else "untersten")); return
|
||||
target = zs[new_idx]
|
||||
if not isinstance(target, dict) or not target.get("id"):
|
||||
print("[GESCHOSS-NAV] Zielebene ungueltig"); return
|
||||
print("[GESCHOSS-NAV] wechsle zu '{}'".format(target.get("name") or target["id"]))
|
||||
bridge._set_active_zeichnungsebene(target)
|
||||
except Exception as ex:
|
||||
print("[GESCHOSS-NAV]", ex)
|
||||
|
||||
|
||||
# delta=-1 = nach oben (vorheriger Eintrag in der Liste, weil Listen
|
||||
# typischerweise oberste Ebene oben sind)
|
||||
_go(-1)
|
||||
@@ -0,0 +1,7 @@
|
||||
#! python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# Auto-Wrapper fuer View-Mode 'material'.
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
import dossier_view_mode
|
||||
dossier_view_mode._apply("material")
|
||||
@@ -0,0 +1,7 @@
|
||||
#! python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# Auto-Wrapper fuer View-Mode 'persp3d'.
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
import dossier_view_mode
|
||||
dossier_view_mode._apply("persp3d")
|
||||
@@ -0,0 +1,7 @@
|
||||
#! python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# Auto-Wrapper fuer View-Mode 'plan'.
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
import dossier_view_mode
|
||||
dossier_view_mode._apply("plan")
|
||||
Reference in New Issue
Block a user