Initial commit — Dossier Rhino 8 Plugin
OpenStudio-Suite Architektur-Plugin fuer Rhino 8 (Mac): - Smart-Elemente: Wand, Decke, Dach (Pult/Sattel/Walm/Mansarde), Oeffnungen (Fenster/Tueren mit Rahmen + Sims + Glas + Fluegel), Treppen (gerade · L · Wendel mit Schrittmass-Validierung) - Live-Previews mit Step-Lines + Soll-Range-Clamping - Bidirektionale Selection-Sync zwischen Source-Linie und Volume - Geschoss-/Ebenen-Verwaltung mit OKFF-Persistenz - Layouts mit PDF-Export - Ausschnitte / Massstab / Override-Regeln - Petrol-Gruen Theme (Rapport-konform) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,513 @@
|
||||
# ! python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oberleiste.py
|
||||
OBERLEISTE-Panel: horizontale Top-Bar mit Architektur-Kontext-Controls.
|
||||
Vereint View-Switcher, Display-Mode, Massstab, Print-View und Snap-Toggles.
|
||||
|
||||
Re-used massstab-Modul fuer Skala/PlotWeight-Logik — die Bridge proxiet alle
|
||||
Massstab-bezogenen Nachrichten dorthin.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import Rhino
|
||||
import scriptcontext as sc
|
||||
|
||||
_HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
if _HERE not in sys.path:
|
||||
sys.path.insert(0, _HERE)
|
||||
|
||||
import panel_base
|
||||
import massstab
|
||||
import overrides
|
||||
|
||||
PANEL_GUID_STR = "7e1f6a5b-8e2f-4f3c-d5e6-f70819203b51"
|
||||
OVERRIDES_PANEL_GUID_STR = "8f2a7b6c-9f3a-4f4d-e6f7-08192a3c4d62"
|
||||
|
||||
|
||||
def _run(cmd):
|
||||
"""Hilfsfunktion: Rhino-Befehl ausfuehren, mit Logging."""
|
||||
try:
|
||||
Rhino.RhinoApp.RunScript(cmd, False)
|
||||
except Exception as ex:
|
||||
print("[OBERLEISTE] RunScript-Fehler ({}): {}".format(cmd, ex))
|
||||
|
||||
|
||||
def _get_active_viewport_name():
|
||||
try:
|
||||
v = Rhino.RhinoDoc.ActiveDoc.Views.ActiveView
|
||||
return v.ActiveViewport.Name if v else None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def _list_all_command_names():
|
||||
"""Enumeriert alle registrierten Rhino-Commands (englische Namen).
|
||||
Wird einmalig beim Bridge-Start aufgerufen und gecached."""
|
||||
names = set()
|
||||
# Variante 1: statische API Rhino.Commands.Command.GetCommandNames
|
||||
try:
|
||||
all_names = Rhino.Commands.Command.GetCommandNames(True, True)
|
||||
for n in all_names:
|
||||
if n and isinstance(n, str):
|
||||
names.add(n)
|
||||
except Exception:
|
||||
pass
|
||||
# Variante 2: ueber alle PlugIns iterieren (Fallback)
|
||||
if not names:
|
||||
try:
|
||||
for guid in Rhino.Plugins.PlugIn.GetInstalledPlugIns().Keys:
|
||||
try:
|
||||
pi = Rhino.Plugins.PlugIn.Find(guid)
|
||||
if pi is None: continue
|
||||
cmds = pi.GetCommands() if hasattr(pi, "GetCommands") else []
|
||||
for cmd_guid in cmds:
|
||||
try:
|
||||
n = Rhino.Commands.Command.GetCommandName(cmd_guid)
|
||||
if n: names.add(n)
|
||||
except Exception: pass
|
||||
except Exception: pass
|
||||
except Exception: pass
|
||||
# Variante 3: minimaler Fallback fuer den Fall dass keine API greift
|
||||
if not names:
|
||||
for n in ("Line","Polyline","Rectangle","Circle","Arc","Curve","Text","Hatch",
|
||||
"Move","Copy","Rotate","Scale","Mirror","Offset","Trim","Extend",
|
||||
"Join","Explode","Fillet","Array","Box","ExtrudeCrv","BooleanUnion",
|
||||
"BooleanDifference","BooleanIntersection","Cap","Section","Loft",
|
||||
"Zoom","Pan","Top","Front","Right","Perspective","Undo","Redo",
|
||||
"Group","Ungroup","Hide","Show","Delete","SelAll","SelNone",
|
||||
"Properties","Layer","Snap","Ortho","Planar","Save","SaveAs"):
|
||||
names.add(n)
|
||||
out = sorted(names)
|
||||
print("[OBERLEISTE] {} Rhino-Commands fuer Autocomplete enumeriert".format(len(out)))
|
||||
return out
|
||||
|
||||
|
||||
def _get_command_prompt():
|
||||
"""Liefert den aktuellen Rhino-Command-Prompt oder leeren String.
|
||||
Wird gepollt damit OBERLEISTE den Prompt + Optionen anzeigen kann."""
|
||||
try:
|
||||
p = Rhino.RhinoApp.CommandPrompt
|
||||
return p if p is not None else ""
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
|
||||
def _parse_command_options(prompt):
|
||||
"""Extrahiert Option-Tokens aus einem Rhino-Prompt.
|
||||
Beispiele:
|
||||
"Line: First point ( BothSides Bisector Length Vertical Angle )"
|
||||
"Polyline: Next point of polyline ( Close Helix Mode=Persistent Undo )"
|
||||
Liefert Liste von dicts: [{name, value (optional), token}].
|
||||
"""
|
||||
import re
|
||||
if not prompt: return []
|
||||
# Inhalt der letzten Klammer
|
||||
m = re.search(r"\(([^()]+)\)\s*$", prompt)
|
||||
if not m: return []
|
||||
body = m.group(1).strip()
|
||||
options = []
|
||||
for tok in body.split():
|
||||
tok = tok.strip().rstrip(",;:")
|
||||
if not tok: continue
|
||||
if "=" in tok:
|
||||
name, val = tok.split("=", 1)
|
||||
options.append({"name": name, "value": val, "token": tok})
|
||||
else:
|
||||
options.append({"name": tok, "value": None, "token": tok})
|
||||
return options
|
||||
|
||||
|
||||
def _get_active_display_mode_name():
|
||||
try:
|
||||
v = Rhino.RhinoDoc.ActiveDoc.Views.ActiveView
|
||||
if v is None: return None
|
||||
dm = v.ActiveViewport.DisplayMode
|
||||
return dm.LocalName if dm else None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
_display_modes_cache = None # gecacht — Liste aendert sich pro Rhino-Session selten
|
||||
|
||||
|
||||
def _list_display_modes():
|
||||
"""Alle verfuegbaren Display-Modes (LocalName + Id-String).
|
||||
Gecacht — Liste aendert sich nur wenn User Display-Modes ergaenzt/loescht.
|
||||
Bei Bedarf kann _display_modes_cache von aussen auf None gesetzt werden."""
|
||||
global _display_modes_cache
|
||||
if _display_modes_cache is not None:
|
||||
return _display_modes_cache
|
||||
out = []
|
||||
try:
|
||||
for dm in Rhino.Display.DisplayModeDescription.GetDisplayModes():
|
||||
try:
|
||||
out.append({"name": dm.LocalName, "id": str(dm.Id)})
|
||||
except Exception:
|
||||
continue
|
||||
except Exception as ex:
|
||||
print("[OBERLEISTE] _list_display_modes:", ex)
|
||||
_display_modes_cache = out
|
||||
return out
|
||||
|
||||
|
||||
def _set_display_mode(name):
|
||||
"""Setzt Display-Mode des aktiven Viewports per Name."""
|
||||
try:
|
||||
v = Rhino.RhinoDoc.ActiveDoc.Views.ActiveView
|
||||
if v is None: return False
|
||||
for dm in Rhino.Display.DisplayModeDescription.GetDisplayModes():
|
||||
if dm.LocalName == name or dm.EnglishName == name:
|
||||
v.ActiveViewport.DisplayMode = dm
|
||||
v.Redraw()
|
||||
print("[OBERLEISTE] Display-Mode: {}".format(name))
|
||||
return True
|
||||
except Exception as ex:
|
||||
print("[OBERLEISTE] _set_display_mode:", ex)
|
||||
return False
|
||||
|
||||
|
||||
# --- Snap / Ortho via ModelAidSettings --------------------------------------
|
||||
|
||||
def _get_snap_state():
|
||||
try:
|
||||
s = Rhino.ApplicationSettings.ModelAidSettings
|
||||
return {
|
||||
"ortho": bool(s.Ortho),
|
||||
"gridSnap": bool(s.GridSnap),
|
||||
"osnap": bool(s.UseHorizontalDialog) if False else bool(getattr(s, "Osnap", False)) or False,
|
||||
"planar": bool(getattr(s, "ProjectOsnapsToCPlane", False)),
|
||||
}
|
||||
except Exception:
|
||||
return {"ortho": False, "gridSnap": False, "osnap": False, "planar": False}
|
||||
|
||||
|
||||
def _set_ortho(v):
|
||||
try:
|
||||
Rhino.ApplicationSettings.ModelAidSettings.Ortho = bool(v)
|
||||
except Exception as ex:
|
||||
print("[OBERLEISTE] _set_ortho:", ex)
|
||||
|
||||
|
||||
def _set_grid_snap(v):
|
||||
try:
|
||||
Rhino.ApplicationSettings.ModelAidSettings.GridSnap = bool(v)
|
||||
except Exception as ex:
|
||||
print("[OBERLEISTE] _set_grid_snap:", ex)
|
||||
|
||||
|
||||
def _set_osnap_master(v):
|
||||
"""Master-Toggle fuer Object-Snap (alle aktiven Snaps)."""
|
||||
try:
|
||||
s = Rhino.ApplicationSettings.ModelAidSettings
|
||||
if hasattr(s, "Osnap"):
|
||||
s.Osnap = bool(v)
|
||||
elif hasattr(s, "UsePoints"):
|
||||
# Fallback: einzelne Modi durch
|
||||
pass
|
||||
except Exception as ex:
|
||||
print("[OBERLEISTE] _set_osnap_master:", ex)
|
||||
|
||||
|
||||
# --- Bridge -----------------------------------------------------------------
|
||||
|
||||
class OberleisteBridge(panel_base.BaseBridge):
|
||||
def __init__(self):
|
||||
panel_base.BaseBridge.__init__(self, "oberleiste")
|
||||
self._idle_counter = 0
|
||||
self._last_prompt = ""
|
||||
self._last_state_sig = None # Fingerprint des letzten Push — dedupe
|
||||
self._cached_overrides = None # (enabled, count) — invalidiert bei Toggle/Update
|
||||
# Command-Liste einmalig laden (kann teuer sein -> cachen)
|
||||
try:
|
||||
self._all_commands = _list_all_command_names()
|
||||
except Exception as ex:
|
||||
print("[OBERLEISTE] command-enum:", ex)
|
||||
self._all_commands = []
|
||||
|
||||
def _on_ready(self):
|
||||
# Bootstrap DPI (gemeinsam mit massstab.py)
|
||||
try: massstab._bootstrap_dpi()
|
||||
except Exception: pass
|
||||
# WebView wurde (neu) gemountet — Frontend-State ist leer, also one-shot
|
||||
# Listen (displayModes, allCommands) neu mitsenden. Sonst zeigt das
|
||||
# Display-Dropdown nach einem Re-Mount (z.B. Andocken, Layout-Wechsel)
|
||||
# nur die "—"-Option und wirkt wie ein toter Button.
|
||||
self._dm_sent = False
|
||||
self._commands_sent = False
|
||||
self._send_state(force=True)
|
||||
|
||||
def handle(self, data):
|
||||
if not isinstance(data, dict): return
|
||||
t = data.get("type", "")
|
||||
p = data.get("payload") or {}
|
||||
if not isinstance(p, dict): p = {}
|
||||
|
||||
# --- Lifecycle --------------------------------------------------
|
||||
if t == "READY":
|
||||
self._on_ready()
|
||||
elif t == "REQUEST_STATE":
|
||||
self._send_state(force=True)
|
||||
|
||||
# --- Massstab (delegiert an massstab-Modul) ---------------------
|
||||
elif t == "SET_SCALE":
|
||||
doc, vp = massstab._active_vp()
|
||||
try: ratio = float(p.get("ratio"))
|
||||
except Exception: return
|
||||
if ratio > 0 and massstab._apply_scale(doc, vp, ratio):
|
||||
self._send_state(force=True)
|
||||
elif t == "ZOOM_EXTENTS":
|
||||
doc, vp = massstab._active_vp()
|
||||
massstab._zoom_extents(doc, vp, selected_only=False)
|
||||
self._send_state(force=True)
|
||||
elif t == "ZOOM_SELECTION":
|
||||
doc, vp = massstab._active_vp()
|
||||
massstab._zoom_extents(doc, vp, selected_only=True)
|
||||
self._send_state(force=True)
|
||||
elif t == "SET_LINEWEIGHTS":
|
||||
doc, _ = massstab._active_vp()
|
||||
massstab._set_lineweights_enabled(doc, bool(p.get("enabled")))
|
||||
self._send_state(force=True)
|
||||
elif t == "SET_DPI":
|
||||
doc, _ = massstab._active_vp()
|
||||
massstab._set_dpi(doc, p.get("dpi"), source="manual")
|
||||
self._send_state(force=True)
|
||||
elif t == "DETECT_DPI":
|
||||
massstab._force_redetect_dpi()
|
||||
self._send_state(force=True)
|
||||
|
||||
# --- View-Switcher ----------------------------------------------
|
||||
elif t == "SET_VIEW":
|
||||
v = p.get("view")
|
||||
if v in ("Top", "Front", "Right", "Perspective", "Left", "Back", "Bottom"):
|
||||
_run("_-{} _Enter".format(v))
|
||||
self._send_state(force=True)
|
||||
|
||||
# --- Display-Mode -----------------------------------------------
|
||||
elif t == "SET_DISPLAY_MODE":
|
||||
n = p.get("name")
|
||||
if n:
|
||||
_set_display_mode(n)
|
||||
self._send_state(force=True)
|
||||
|
||||
# --- Snap-Toggles -----------------------------------------------
|
||||
elif t == "TOGGLE_ORTHO":
|
||||
_set_ortho(bool(p.get("enabled")))
|
||||
self._send_state(force=True)
|
||||
elif t == "TOGGLE_GRID_SNAP":
|
||||
_set_grid_snap(bool(p.get("enabled")))
|
||||
self._send_state(force=True)
|
||||
elif t == "TOGGLE_OSNAP":
|
||||
_set_osnap_master(bool(p.get("enabled")))
|
||||
self._send_state(force=True)
|
||||
|
||||
# --- Graphical Overrides ----------------------------------------
|
||||
elif t == "TOGGLE_OVERRIDES":
|
||||
doc = Rhino.RhinoDoc.ActiveDoc
|
||||
overrides.set_enabled(doc, bool(p.get("enabled")))
|
||||
self._cached_overrides = None # Cache invalidieren
|
||||
self._send_state(force=True)
|
||||
elif t == "SET_OVERRIDES_PRESET":
|
||||
doc = Rhino.RhinoDoc.ActiveDoc
|
||||
name = p.get("name") or None
|
||||
overrides.set_active_preset(doc, name)
|
||||
self._cached_overrides = None # Cache invalidieren
|
||||
self._send_state(force=True)
|
||||
# OVERRIDES-Panel mit-informieren: dort haben sich die Rules
|
||||
# geaendert (Preset wurde reingeladen).
|
||||
try:
|
||||
b = sc.sticky.get("overrides_bridge")
|
||||
if b is not None:
|
||||
b._send_state()
|
||||
except Exception as ex:
|
||||
print("[OBERLEISTE] notify overrides:", ex)
|
||||
elif t == "SAVE_OVERRIDES_PRESET":
|
||||
# Quick-Save direkt aus der Topbar: aktuelle Doc-Rules unter
|
||||
# gegebenem Namen ablegen und sofort als activePreset markieren.
|
||||
# Spart dem User den Umweg ueber den grossen OVERRIDES-Editor.
|
||||
doc = Rhino.RhinoDoc.ActiveDoc
|
||||
name = (p.get("name") or "").strip()
|
||||
if not name:
|
||||
pass
|
||||
else:
|
||||
cfg = overrides.load_config(doc)
|
||||
rules = cfg.get("rules") or []
|
||||
if overrides.save_preset(name, rules):
|
||||
overrides.set_active_preset(doc, name)
|
||||
self._cached_overrides = None
|
||||
self._send_state(force=True)
|
||||
try:
|
||||
b = sc.sticky.get("overrides_bridge")
|
||||
if b is not None:
|
||||
b._send_state()
|
||||
except Exception as ex:
|
||||
print("[OBERLEISTE] notify overrides:", ex)
|
||||
elif t == "OPEN_OVERRIDES_PANEL":
|
||||
try:
|
||||
import System
|
||||
import Rhino.UI as RhinoUI
|
||||
RhinoUI.Panels.OpenPanel(System.Guid(OVERRIDES_PANEL_GUID_STR))
|
||||
except Exception as ex:
|
||||
print("[OBERLEISTE] OpenPanel Overrides:", ex)
|
||||
|
||||
# --- Command-Line Integration -----------------------------------
|
||||
elif t == "RUN_COMMAND":
|
||||
cmd = (p.get("cmd") or "").strip()
|
||||
if cmd:
|
||||
# Auto-Praefix mit "_" falls nicht vorhanden, damit auch
|
||||
# lokalisierte Rhino-Installationen die EN-Namen verstehen.
|
||||
if not (cmd.startswith("_") or cmd.startswith("'")):
|
||||
cmd = "_" + cmd
|
||||
try:
|
||||
# WICHTIG: Mac Rhinos Command-Bar sammelt parallel
|
||||
# User-Keystrokes (globaler Keyhook). Wenn unsere React-
|
||||
# Eingabe tippt landet die da auch. ESC clearen sonst
|
||||
# haben wir doppelten Text und braucht 2x Enter.
|
||||
Rhino.RhinoApp.SendKeystrokes("\x1b", False)
|
||||
Rhino.RhinoApp.RunScript(cmd, False)
|
||||
except Exception as ex:
|
||||
print("[OBERLEISTE] RunScript-Fehler:", ex)
|
||||
elif t == "SEND_KEYS":
|
||||
text = p.get("text") or ""
|
||||
append_enter = bool(p.get("enter", True))
|
||||
try:
|
||||
# Ebenfalls Buffer zuerst leeren wenn User parallel mitgetippt hat
|
||||
if text:
|
||||
Rhino.RhinoApp.SendKeystrokes("\x1b", False)
|
||||
Rhino.RhinoApp.SendKeystrokes(text, append_enter)
|
||||
except Exception as ex:
|
||||
print("[OBERLEISTE] SendKeystrokes-Fehler:", ex)
|
||||
elif t == "CANCEL_COMMAND":
|
||||
try:
|
||||
# Doppel-ESC: einmal um Eingabe-Buffer zu clearen, einmal um
|
||||
# aktiven Befehl abzubrechen
|
||||
Rhino.RhinoApp.SendKeystrokes("\x1b", False)
|
||||
Rhino.RhinoApp.SendKeystrokes("\x1b", False)
|
||||
except Exception:
|
||||
pass
|
||||
elif t == "TOGGLE_RHINO_CMD_LINE":
|
||||
# Versucht Rhinos eigene Befehlszeile/History zu togglen.
|
||||
# Mehrere Wege probieren — je nach Version greift einer.
|
||||
for c in (
|
||||
"_-CommandPrompt _Hide _Enter",
|
||||
"_CommandHistory _Toggle _Enter",
|
||||
"_-Toolbar _Hide _Commands _Enter",
|
||||
):
|
||||
try:
|
||||
Rhino.RhinoApp.RunScript(c, False)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _send_state(self, force=False):
|
||||
doc, vp = massstab._active_vp()
|
||||
info = massstab._compute_scale(doc, vp)
|
||||
# Massstab-State (Scale, Print-Toggle, DPI)
|
||||
info["viewMode"] = _get_active_viewport_name()
|
||||
info["displayMode"] = _get_active_display_mode_name()
|
||||
# displayModes-Liste nur einmal initial mitsenden — aendert sich kaum
|
||||
if not getattr(self, "_dm_sent", False):
|
||||
info["displayModes"] = _list_display_modes()
|
||||
self._dm_sent = True
|
||||
# Snap-State
|
||||
info.update(_get_snap_state())
|
||||
# Overrides-State — cached, invalidiert bei TOGGLE_OVERRIDES und
|
||||
# SET_OVERRIDES_PRESET. Bei manuellen Aenderungen via OVERRIDES-Panel
|
||||
# bleibt der Cache stale bis zum naechsten Toggle — pragmatischer
|
||||
# Trade-off, weil die beiden Bridges nicht direkt voneinander wissen.
|
||||
if self._cached_overrides is None:
|
||||
try:
|
||||
cfg = overrides.load_config(doc)
|
||||
presets = [item.get("name") for item in overrides.list_presets() if item.get("name")]
|
||||
self._cached_overrides = (
|
||||
bool(cfg.get("enabled")),
|
||||
len(cfg.get("rules") or []),
|
||||
cfg.get("activePreset"),
|
||||
tuple(presets),
|
||||
)
|
||||
except Exception:
|
||||
self._cached_overrides = (False, 0, None, ())
|
||||
(info["overridesEnabled"],
|
||||
info["overridesCount"],
|
||||
info["overridesActivePreset"],
|
||||
_presets_tuple) = self._cached_overrides
|
||||
info["overridesPresets"] = list(_presets_tuple)
|
||||
# Command-Line State
|
||||
prompt = _get_command_prompt()
|
||||
info["cmdPrompt"] = prompt
|
||||
info["cmdOptions"] = _parse_command_options(prompt)
|
||||
# Command-Autocomplete-Liste — nur einmal initial schicken (gross)
|
||||
if not getattr(self, "_commands_sent", False):
|
||||
info["allCommands"] = self._all_commands
|
||||
self._commands_sent = True
|
||||
force = True # Erste Push immer feuern
|
||||
# Diff-Check: wenn weder Daten noch force, gar nichts schicken
|
||||
# (dedupe Idle-Ticks ohne Aenderung — spart WebView-ExecuteScript Roundtrip)
|
||||
sig = (
|
||||
info.get("scale"),
|
||||
info.get("appliedScale"),
|
||||
info.get("parallel"),
|
||||
info.get("viewMode"),
|
||||
info.get("displayMode"),
|
||||
info.get("ortho"), info.get("gridSnap"), info.get("osnap"),
|
||||
info.get("showLineweights"),
|
||||
info["overridesEnabled"], info["overridesCount"],
|
||||
info.get("overridesActivePreset"),
|
||||
tuple(info.get("overridesPresets") or ()),
|
||||
prompt,
|
||||
)
|
||||
if not force and sig == self._last_state_sig:
|
||||
return
|
||||
self._last_state_sig = sig
|
||||
self.send("STATE", info)
|
||||
|
||||
def tick_idle(self):
|
||||
# Command-Prompt aendert sich oft schnell -> separater Pfad: wenn sich
|
||||
# der Prompt seit letztem Tick geaendert hat, sofort pushen (ungedrosselt).
|
||||
cur_prompt = _get_command_prompt()
|
||||
if cur_prompt != self._last_prompt:
|
||||
self._last_prompt = cur_prompt
|
||||
self._send_state(force=True)
|
||||
self._idle_counter = 0
|
||||
return
|
||||
# Sonst: normaler throttle fuer den restlichen State
|
||||
self._idle_counter += 1
|
||||
if self._idle_counter < massstab._IDLE_THROTTLE:
|
||||
return
|
||||
self._idle_counter = 0
|
||||
self._send_state(force=False)
|
||||
|
||||
|
||||
# --- Listener-Hookup --------------------------------------------------------
|
||||
|
||||
def _install_listeners(bridge):
|
||||
flag = "oberleiste_listeners"
|
||||
sc.sticky["oberleiste_bridge"] = bridge
|
||||
if sc.sticky.get(flag):
|
||||
return
|
||||
|
||||
def on_idle(s, e):
|
||||
b = sc.sticky.get("oberleiste_bridge")
|
||||
if b is not None:
|
||||
try: b.tick_idle()
|
||||
except Exception: pass
|
||||
|
||||
def on_view_change(*args):
|
||||
b = sc.sticky.get("oberleiste_bridge")
|
||||
if b is not None:
|
||||
try: b._send_state(force=True)
|
||||
except Exception: pass
|
||||
|
||||
Rhino.RhinoApp.Idle += on_idle
|
||||
Rhino.RhinoDoc.ActiveDocumentChanged += on_view_change
|
||||
sc.sticky[flag] = True
|
||||
print("[OBERLEISTE] Listener aktiv")
|
||||
|
||||
|
||||
def _bridge_factory():
|
||||
b = OberleisteBridge()
|
||||
_install_listeners(b)
|
||||
return b
|
||||
|
||||
|
||||
panel_base.register_and_open("oberleiste", "OBERLEISTE", PANEL_GUID_STR, _bridge_factory,
|
||||
icon_spec=("O", "#2f5d54"))
|
||||
Reference in New Issue
Block a user