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,612 @@
|
||||
# ! python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
dimensionen.py
|
||||
DIMENSIONEN-Panel: Object Info Palette nach Vectorworks-Vorbild.
|
||||
Zeigt Position + Abmessungen der Selektion an und erlaubt direktes Eintippen
|
||||
mit 9-Punkt-Referenz, World/CPlane-Modus und Shape-spezifischen Feldern
|
||||
(Kreis, Linie, Rechteck).
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import math
|
||||
import Rhino
|
||||
import Rhino.Geometry as rg
|
||||
import System
|
||||
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
|
||||
|
||||
PANEL_GUID_STR = "9e3c8c5d-6d4a-4f3e-b3c5-d4e5f6071a2c"
|
||||
|
||||
# Idle-Polling fuer geometrische Aenderungen (Gumball-Move feuert keine
|
||||
# SelectObjects-Events). Tick alle N Idle-Calls — N hoeher = weniger CPU.
|
||||
_IDLE_GEOM_POLL = 8
|
||||
|
||||
|
||||
# --- Geometrie-Helpers ------------------------------------------------------
|
||||
|
||||
def _get_selected_objects(doc):
|
||||
"""Liste aller aktuell selektierten RhinoObjects."""
|
||||
if doc is None: return []
|
||||
try:
|
||||
return list(doc.Objects.GetSelectedObjects(False, False))
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
|
||||
def _get_cplane(doc):
|
||||
"""Aktive Construction Plane oder Plane.WorldXY als Fallback."""
|
||||
try:
|
||||
v = doc.Views.ActiveView
|
||||
if v is not None:
|
||||
cp = v.ActiveViewport.ConstructionPlane()
|
||||
if cp is not None and cp.Plane.IsValid:
|
||||
return cp.Plane
|
||||
except Exception:
|
||||
pass
|
||||
return rg.Plane.WorldXY
|
||||
|
||||
|
||||
def _active_plane(doc, mode):
|
||||
"""Plane fuer die aktuelle Koordinatenangabe — World oder CPlane."""
|
||||
if mode == "cplane":
|
||||
return _get_cplane(doc)
|
||||
return rg.Plane.WorldXY
|
||||
|
||||
|
||||
def _bbox_in_plane(objs, plane):
|
||||
"""BBox aller selektierten Objekte im Koordinatensystem der gegebenen
|
||||
Plane (achsen-aligned zur Plane). Liefert (BoundingBox, plane) oder None."""
|
||||
if not objs:
|
||||
return None
|
||||
# World -> Plane Transform anwenden -> BBox in plane-Koordinaten
|
||||
xform = rg.Transform.PlaneToPlane(plane, rg.Plane.WorldXY)
|
||||
bbox = rg.BoundingBox.Empty
|
||||
for obj in objs:
|
||||
try:
|
||||
geom = obj.Geometry
|
||||
if geom is None: continue
|
||||
bb = geom.GetBoundingBox(xform)
|
||||
if bb.IsValid:
|
||||
bbox.Union(bb)
|
||||
except Exception:
|
||||
pass
|
||||
return bbox if bbox.IsValid else None
|
||||
|
||||
|
||||
def _ref_point_local(bbox, ref):
|
||||
"""Referenzpunkt in plane-lokalen Koordinaten anhand ref-Dict
|
||||
{x: 'min'|'mid'|'max', y: ..., z: ...}."""
|
||||
def axis(amin, amax, code):
|
||||
if code == "min": return amin
|
||||
if code == "max": return amax
|
||||
return (amin + amax) * 0.5
|
||||
return rg.Point3d(
|
||||
axis(bbox.Min.X, bbox.Max.X, ref.get("x", "min")),
|
||||
axis(bbox.Min.Y, bbox.Max.Y, ref.get("y", "min")),
|
||||
axis(bbox.Min.Z, bbox.Max.Z, ref.get("z", "mid")),
|
||||
)
|
||||
|
||||
|
||||
def _ref_point_world(bbox_local, ref, plane):
|
||||
"""Referenzpunkt in Welt-Koordinaten: lokal -> plane.PointAt."""
|
||||
p_local = _ref_point_local(bbox_local, ref)
|
||||
return plane.PointAt(p_local.X, p_local.Y, p_local.Z)
|
||||
|
||||
|
||||
def _round(v, digits=4):
|
||||
try:
|
||||
return round(float(v), digits)
|
||||
except Exception:
|
||||
return v
|
||||
|
||||
|
||||
# --- Shape-Detection --------------------------------------------------------
|
||||
|
||||
def _detect_shape(objs):
|
||||
"""Erkennt spezifische Formen: Kreis, Linie, Rechteck (geschlossene
|
||||
planare Polyline mit 4 perpendikularen Segmenten). Liefert dict oder None.
|
||||
Nur bei genau einem selektierten Curve-Objekt."""
|
||||
if len(objs) != 1:
|
||||
return None
|
||||
obj = objs[0]
|
||||
geom = obj.Geometry
|
||||
if not isinstance(geom, rg.Curve):
|
||||
return None
|
||||
# Kreis?
|
||||
try:
|
||||
ok, circle = geom.TryGetCircle(0.001)
|
||||
if ok and circle.IsValid:
|
||||
return {
|
||||
"type": "circle",
|
||||
"radius": _round(circle.Radius, 4),
|
||||
"center": [_round(circle.Center.X), _round(circle.Center.Y), _round(circle.Center.Z)],
|
||||
}
|
||||
except Exception:
|
||||
pass
|
||||
# Linie?
|
||||
try:
|
||||
if isinstance(geom, rg.LineCurve):
|
||||
line = geom.Line
|
||||
length = line.Length
|
||||
angle_deg = math.degrees(math.atan2(line.Direction.Y, line.Direction.X))
|
||||
return {
|
||||
"type": "line",
|
||||
"length": _round(length, 4),
|
||||
"angle": _round(angle_deg, 3),
|
||||
"start": [_round(line.From.X), _round(line.From.Y), _round(line.From.Z)],
|
||||
"end": [_round(line.To.X), _round(line.To.Y), _round(line.To.Z)],
|
||||
}
|
||||
except Exception:
|
||||
pass
|
||||
# Rechteck als geschlossene Polyline mit 4 perpendikularen Segmenten?
|
||||
try:
|
||||
ok, poly = geom.TryGetPolyline()
|
||||
if ok and poly is not None and poly.Count == 5 and poly[0].DistanceTo(poly[-1]) < 1e-6:
|
||||
pts = [poly[i] for i in range(4)]
|
||||
v0 = pts[1] - pts[0]
|
||||
v1 = pts[2] - pts[1]
|
||||
v2 = pts[3] - pts[2]
|
||||
v3 = pts[0] - pts[3]
|
||||
def _dot(a, b): return a.X * b.X + a.Y * b.Y + a.Z * b.Z
|
||||
# Adjacente Kanten perpendikular?
|
||||
if (abs(_dot(v0, v1)) < 1e-4 and
|
||||
abs(_dot(v1, v2)) < 1e-4 and
|
||||
abs(_dot(v2, v3)) < 1e-4):
|
||||
w = v0.Length
|
||||
h = v1.Length
|
||||
return {
|
||||
"type": "rectangle",
|
||||
"width": _round(w, 4),
|
||||
"height": _round(h, 4),
|
||||
}
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
# --- Transform-Operationen --------------------------------------------------
|
||||
|
||||
def _apply_xform(doc, objs, xform):
|
||||
"""Transform auf alle Objekte anwenden (in-place via ID)."""
|
||||
if not xform.IsValid: return 0
|
||||
n = 0
|
||||
for obj in objs:
|
||||
try:
|
||||
if doc.Objects.Transform(obj.Id, xform, True):
|
||||
n += 1
|
||||
except Exception as ex:
|
||||
print("[DIMENSIONEN] Transform-Fehler:", ex)
|
||||
return n
|
||||
|
||||
|
||||
# --- Undo-Wrapper -----------------------------------------------------------
|
||||
# Ohne BeginUndoRecord/EndUndoRecord wird ein Multi-Objekt-Transform nicht
|
||||
# zuverlaessig als ein einziger Undo-Schritt registriert — Ctrl+Z ueberspringt
|
||||
# dann unsere Aenderung. Wir packen jede User-Aktion in einen benannten Record.
|
||||
|
||||
class _UndoRecord(object):
|
||||
def __init__(self, doc, label):
|
||||
self.doc = doc
|
||||
self.label = label
|
||||
self.serial = 0
|
||||
def __enter__(self):
|
||||
try:
|
||||
self.serial = self.doc.BeginUndoRecord(self.label)
|
||||
except Exception as ex:
|
||||
print("[DIMENSIONEN] BeginUndoRecord:", ex)
|
||||
self.serial = 0
|
||||
return self
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
if self.serial:
|
||||
try: self.doc.EndUndoRecord(self.serial)
|
||||
except Exception as ex:
|
||||
print("[DIMENSIONEN] EndUndoRecord:", ex)
|
||||
return False # exceptions propagieren
|
||||
|
||||
|
||||
def _translate_in_plane(doc, objs, plane, dx, dy, dz):
|
||||
"""Verschiebt um (dx, dy, dz) in plane-lokalen Achsen."""
|
||||
if dx == 0 and dy == 0 and dz == 0: return
|
||||
delta_world = plane.XAxis * dx + plane.YAxis * dy + plane.ZAxis * dz
|
||||
xform = rg.Transform.Translation(delta_world)
|
||||
with _UndoRecord(doc, "Dossier: Position aendern"):
|
||||
_apply_xform(doc, objs, xform)
|
||||
doc.Views.Redraw()
|
||||
|
||||
|
||||
def _scale_around_point(doc, objs, plane, ref_world, sx, sy, sz):
|
||||
"""Skalierung mit eigenen Faktoren pro Achse, zentriert am Referenzpunkt,
|
||||
ausgerichtet an plane."""
|
||||
if sx == 1 and sy == 1 and sz == 1: return
|
||||
if sx <= 0 or sy <= 0 or sz <= 0:
|
||||
print("[DIMENSIONEN] Ungueltige Skalierungsfaktoren:", sx, sy, sz)
|
||||
return
|
||||
p = rg.Plane(plane)
|
||||
p.Origin = ref_world
|
||||
xform = rg.Transform.Scale(p, sx, sy, sz)
|
||||
with _UndoRecord(doc, "Dossier: Abmessung aendern"):
|
||||
_apply_xform(doc, objs, xform)
|
||||
doc.Views.Redraw()
|
||||
|
||||
|
||||
def _rotate_around_axis(doc, objs, ref_world, axis_dir, angle_deg):
|
||||
"""Rotation um axis_dir durch ref_world."""
|
||||
if angle_deg == 0: return
|
||||
xform = rg.Transform.Rotation(math.radians(angle_deg), axis_dir, ref_world)
|
||||
with _UndoRecord(doc, "Dossier: Drehen"):
|
||||
_apply_xform(doc, objs, xform)
|
||||
doc.Views.Redraw()
|
||||
|
||||
|
||||
# --- Shape-Edit-Operationen -------------------------------------------------
|
||||
|
||||
def _set_circle_radius(doc, obj, new_radius, plane, ref_world):
|
||||
"""Skaliert ein Kreis-Curve so, dass es genau new_radius hat — Referenz
|
||||
bleibt fix. Wird ueber globale Scale realisiert, damit das Objekt
|
||||
konsistent mit dem Rest selektierter Objekte transformiert wird."""
|
||||
geom = obj.Geometry
|
||||
ok, circle = geom.TryGetCircle(0.001)
|
||||
if not ok or circle.Radius <= 0:
|
||||
return False
|
||||
factor = float(new_radius) / circle.Radius
|
||||
if factor <= 0: return False
|
||||
p = rg.Plane(plane)
|
||||
p.Origin = ref_world
|
||||
xform = rg.Transform.Scale(p, factor, factor, factor)
|
||||
return bool(doc.Objects.Transform(obj.Id, xform, True))
|
||||
|
||||
|
||||
def _set_line_length(doc, obj, new_length, ref_world):
|
||||
"""Linie so verlaengern/verkuerzen, dass sie new_length hat. Skaliert
|
||||
Linie entlang ihrer Richtung um den Referenzpunkt."""
|
||||
geom = obj.Geometry
|
||||
if not isinstance(geom, rg.LineCurve): return False
|
||||
line = geom.Line
|
||||
cur = line.Length
|
||||
if cur <= 0: return False
|
||||
factor = float(new_length) / cur
|
||||
if factor <= 0: return False
|
||||
# Linie skaliert sich nur entlang ihrer Direction. Scale-1D ueber eine
|
||||
# Plane mit der Linien-Direction als X-Achse waere ideal — vereinfacht:
|
||||
# uniformer Scale, falls Linie achsen-parallel zur lokalen X-Plane
|
||||
# ist das aequivalent zu Length-Scaling.
|
||||
xaxis = rg.Vector3d(line.Direction)
|
||||
xaxis.Unitize()
|
||||
yaxis = rg.Vector3d.CrossProduct(rg.Vector3d.ZAxis, xaxis)
|
||||
if yaxis.Length < 1e-6:
|
||||
yaxis = rg.Vector3d.YAxis
|
||||
yaxis.Unitize()
|
||||
plane = rg.Plane(ref_world, xaxis, yaxis)
|
||||
xform = rg.Transform.Scale(plane, factor, 1.0, 1.0)
|
||||
return bool(doc.Objects.Transform(obj.Id, xform, True))
|
||||
|
||||
|
||||
def _set_rectangle_dims(doc, obj, new_w, new_h, plane, ref_world):
|
||||
"""Skaliert Rechteck-Curve auf (new_w, new_h). Annahme: width = Laenge
|
||||
erster Seite (v0), height = zweiter Seite (v1) in der Polyline-
|
||||
Reihenfolge — entspricht der Reihenfolge aus _detect_shape."""
|
||||
geom = obj.Geometry
|
||||
if not isinstance(geom, rg.Curve): return False
|
||||
ok, poly = geom.TryGetPolyline()
|
||||
if not ok or poly is None or poly.Count != 5: return False
|
||||
pts = [poly[i] for i in range(4)]
|
||||
v0 = pts[1] - pts[0]
|
||||
v1 = pts[2] - pts[1]
|
||||
w_cur = v0.Length
|
||||
h_cur = v1.Length
|
||||
if w_cur <= 0 or h_cur <= 0: return False
|
||||
sw = float(new_w) / w_cur
|
||||
sh = float(new_h) / h_cur
|
||||
if sw <= 0 or sh <= 0: return False
|
||||
# Achsen des Rechtecks als Plane fuer den Scale
|
||||
xaxis = rg.Vector3d(v0); xaxis.Unitize()
|
||||
yaxis = rg.Vector3d(v1); yaxis.Unitize()
|
||||
rect_plane = rg.Plane(ref_world, xaxis, yaxis)
|
||||
xform = rg.Transform.Scale(rect_plane, sw, sh, 1.0)
|
||||
return bool(doc.Objects.Transform(obj.Id, xform, True))
|
||||
|
||||
|
||||
# --- Bridge -----------------------------------------------------------------
|
||||
|
||||
class DimensionenBridge(panel_base.BaseBridge):
|
||||
def __init__(self):
|
||||
panel_base.BaseBridge.__init__(self, "dimensionen")
|
||||
self._ref = {"x": "min", "y": "min", "z": "mid"}
|
||||
self._coord_sys = "world" # "world" | "cplane"
|
||||
self._last_sig = None
|
||||
self._last_ids = ()
|
||||
self._idle_cnt = 0
|
||||
|
||||
def _on_ready(self):
|
||||
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 = {}
|
||||
|
||||
if t == "READY": self._on_ready()
|
||||
elif t == "REQUEST_STATE": self._send_state(force=True)
|
||||
elif t == "SET_REF_POINT": self._set_ref_point(p)
|
||||
elif t == "SET_COORD_SYSTEM": self._set_coord_system(p)
|
||||
elif t == "SET_POSITION": self._set_position(p)
|
||||
elif t == "SET_DIMENSION": self._set_dimension(p)
|
||||
elif t == "SET_ROTATION_Z": self._set_rotation_z(p)
|
||||
elif t == "SET_CIRCLE_RADIUS":self._set_circle_radius(p)
|
||||
elif t == "SET_LINE_LENGTH": self._set_line_length(p)
|
||||
elif t == "SET_RECTANGLE": self._set_rectangle(p)
|
||||
|
||||
# --- State-Snapshot -----------------------------------------------------
|
||||
|
||||
def _compute_state(self):
|
||||
doc = Rhino.RhinoDoc.ActiveDoc
|
||||
if doc is None:
|
||||
return {"selection": {"count": 0, "type": "none", "shape": None},
|
||||
"refPoint": self._ref, "coordSystem": self._coord_sys}
|
||||
objs = _get_selected_objects(doc)
|
||||
plane = _active_plane(doc, self._coord_sys)
|
||||
bbox_local = _bbox_in_plane(objs, plane)
|
||||
|
||||
# Typ der Selektion
|
||||
typ = "none"
|
||||
if len(objs) == 0:
|
||||
typ = "none"
|
||||
elif len(objs) == 1:
|
||||
g = objs[0].Geometry
|
||||
if isinstance(g, rg.Curve): typ = "curve"
|
||||
elif isinstance(g, rg.Brep): typ = "brep"
|
||||
elif isinstance(g, rg.Mesh): typ = "mesh"
|
||||
elif isinstance(g, rg.Extrusion): typ = "extrusion"
|
||||
elif isinstance(g, rg.InstanceReferenceGeometry): typ = "block"
|
||||
elif isinstance(g, rg.Point): typ = "point"
|
||||
elif isinstance(g, rg.TextEntity): typ = "text"
|
||||
else: typ = "other"
|
||||
else:
|
||||
typ = "mixed"
|
||||
|
||||
out = {
|
||||
"selection": {"count": len(objs), "type": typ},
|
||||
"refPoint": self._ref,
|
||||
"coordSystem": self._coord_sys,
|
||||
"planeName": "CPlane" if self._coord_sys == "cplane" else "Welt",
|
||||
}
|
||||
shape = _detect_shape(objs)
|
||||
out["shape"] = shape
|
||||
|
||||
if bbox_local is None:
|
||||
out["position"] = None
|
||||
out["dimensions"] = None
|
||||
return out
|
||||
|
||||
# Position des Referenzpunkts (in plane-lokalen Koordinaten — das ist
|
||||
# das, was der User typischerweise sehen will: World ist Plane=WorldXY,
|
||||
# CPlane ist Plane=ActiveCPlane).
|
||||
ref_local = _ref_point_local(bbox_local, self._ref)
|
||||
out["position"] = {
|
||||
"x": _round(ref_local.X, 4),
|
||||
"y": _round(ref_local.Y, 4),
|
||||
"z": _round(ref_local.Z, 4),
|
||||
}
|
||||
# Abmessungen (Plane-aligned BBox-Spannweite)
|
||||
out["dimensions"] = {
|
||||
"width": _round(bbox_local.Max.X - bbox_local.Min.X, 4),
|
||||
"depth": _round(bbox_local.Max.Y - bbox_local.Min.Y, 4),
|
||||
"height": _round(bbox_local.Max.Z - bbox_local.Min.Z, 4),
|
||||
}
|
||||
return out
|
||||
|
||||
def _send_state(self, force=False):
|
||||
state = self._compute_state()
|
||||
# Signature fuer Diff — komplette JSON-Repr ist ok bei kleinen Dicts
|
||||
sig = (
|
||||
state.get("selection", {}).get("count"),
|
||||
state.get("selection", {}).get("type"),
|
||||
(state.get("shape") or {}).get("type"),
|
||||
state.get("coordSystem"),
|
||||
tuple(sorted((state.get("refPoint") or {}).items())),
|
||||
tuple(sorted((state.get("position") or {}).items())),
|
||||
tuple(sorted((state.get("dimensions") or {}).items())),
|
||||
tuple(sorted((state.get("shape") or {}).items())) if state.get("shape") else None,
|
||||
)
|
||||
if not force and sig == self._last_sig:
|
||||
return
|
||||
self._last_sig = sig
|
||||
self.send("STATE", state)
|
||||
|
||||
def tick_idle(self):
|
||||
# 1) Schnelle Selektions-Erkennung: ID-Liste vergleichen
|
||||
doc = Rhino.RhinoDoc.ActiveDoc
|
||||
if doc is None: return
|
||||
try:
|
||||
objs = _get_selected_objects(doc)
|
||||
ids = tuple(sorted(str(o.Id) for o in objs))
|
||||
if ids != self._last_ids:
|
||||
self._last_ids = ids
|
||||
self._send_state(force=True)
|
||||
self._idle_cnt = 0
|
||||
return
|
||||
except Exception:
|
||||
pass
|
||||
# 2) Geometrie kann sich aendern ohne Selektions-Change (Gumball-Move).
|
||||
# Niedriger-frequenter Geom-Poll.
|
||||
self._idle_cnt += 1
|
||||
if self._idle_cnt >= _IDLE_GEOM_POLL:
|
||||
self._idle_cnt = 0
|
||||
self._send_state(force=False)
|
||||
|
||||
# --- Handler ------------------------------------------------------------
|
||||
|
||||
def _set_ref_point(self, p):
|
||||
ref = self._ref
|
||||
for k in ("x", "y", "z"):
|
||||
v = p.get(k)
|
||||
if v in ("min", "mid", "max"):
|
||||
ref[k] = v
|
||||
self._send_state(force=True)
|
||||
|
||||
def _set_coord_system(self, p):
|
||||
mode = p.get("mode")
|
||||
if mode in ("world", "cplane"):
|
||||
self._coord_sys = mode
|
||||
self._send_state(force=True)
|
||||
|
||||
def _set_position(self, p):
|
||||
"""Verschiebt Selektion so, dass der Referenzpunkt auf den neuen
|
||||
Wert kommt. Nur die angegebene Achse wird geaendert."""
|
||||
doc = Rhino.RhinoDoc.ActiveDoc
|
||||
objs = _get_selected_objects(doc)
|
||||
if not objs: return
|
||||
axis = p.get("axis")
|
||||
try: value = float(p.get("value"))
|
||||
except Exception: return
|
||||
if axis not in ("x", "y", "z"): return
|
||||
plane = _active_plane(doc, self._coord_sys)
|
||||
bbox_local = _bbox_in_plane(objs, plane)
|
||||
if bbox_local is None: return
|
||||
ref_local = _ref_point_local(bbox_local, self._ref)
|
||||
dx = dy = dz = 0.0
|
||||
if axis == "x": dx = value - ref_local.X
|
||||
if axis == "y": dy = value - ref_local.Y
|
||||
if axis == "z": dz = value - ref_local.Z
|
||||
_translate_in_plane(doc, objs, plane, dx, dy, dz)
|
||||
self._send_state(force=True)
|
||||
|
||||
def _set_dimension(self, p):
|
||||
"""Skaliert Selektion in der angegebenen Achse, sodass der angegebene
|
||||
Wert die neue Plane-aligned BBox-Spannweite ist. Referenzpunkt bleibt
|
||||
fix."""
|
||||
doc = Rhino.RhinoDoc.ActiveDoc
|
||||
objs = _get_selected_objects(doc)
|
||||
if not objs: return
|
||||
axis = p.get("axis") # 'width' | 'depth' | 'height'
|
||||
try: value = float(p.get("value"))
|
||||
except Exception: return
|
||||
if axis not in ("width", "depth", "height"): return
|
||||
if value <= 0: return
|
||||
plane = _active_plane(doc, self._coord_sys)
|
||||
bbox_local = _bbox_in_plane(objs, plane)
|
||||
if bbox_local is None: return
|
||||
ref_world = _ref_point_world(bbox_local, self._ref, plane)
|
||||
cur_w = bbox_local.Max.X - bbox_local.Min.X
|
||||
cur_d = bbox_local.Max.Y - bbox_local.Min.Y
|
||||
cur_h = bbox_local.Max.Z - bbox_local.Min.Z
|
||||
sx = sy = sz = 1.0
|
||||
if axis == "width" and cur_w > 0: sx = value / cur_w
|
||||
if axis == "depth" and cur_d > 0: sy = value / cur_d
|
||||
if axis == "height" and cur_h > 0: sz = value / cur_h
|
||||
_scale_around_point(doc, objs, plane, ref_world, sx, sy, sz)
|
||||
self._send_state(force=True)
|
||||
|
||||
def _set_rotation_z(self, p):
|
||||
"""Rotiert Selektion um die Z-Achse der aktiven Plane durch den
|
||||
Referenzpunkt um angle Grad."""
|
||||
doc = Rhino.RhinoDoc.ActiveDoc
|
||||
objs = _get_selected_objects(doc)
|
||||
if not objs: return
|
||||
try: angle = float(p.get("angle"))
|
||||
except Exception: return
|
||||
plane = _active_plane(doc, self._coord_sys)
|
||||
bbox_local = _bbox_in_plane(objs, plane)
|
||||
if bbox_local is None: return
|
||||
ref_world = _ref_point_world(bbox_local, self._ref, plane)
|
||||
_rotate_around_axis(doc, objs, ref_world, plane.ZAxis, angle)
|
||||
self._send_state(force=True)
|
||||
|
||||
def _set_circle_radius(self, p):
|
||||
doc = Rhino.RhinoDoc.ActiveDoc
|
||||
objs = _get_selected_objects(doc)
|
||||
if len(objs) != 1: return
|
||||
try: radius = float(p.get("value"))
|
||||
except Exception: return
|
||||
if radius <= 0: return
|
||||
plane = _active_plane(doc, self._coord_sys)
|
||||
bbox_local = _bbox_in_plane(objs, plane)
|
||||
if bbox_local is None: return
|
||||
ref_world = _ref_point_world(bbox_local, self._ref, plane)
|
||||
with _UndoRecord(doc, "Dossier: Radius aendern"):
|
||||
_set_circle_radius(doc, objs[0], radius, plane, ref_world)
|
||||
doc.Views.Redraw()
|
||||
self._send_state(force=True)
|
||||
|
||||
def _set_line_length(self, p):
|
||||
doc = Rhino.RhinoDoc.ActiveDoc
|
||||
objs = _get_selected_objects(doc)
|
||||
if len(objs) != 1: return
|
||||
try: length = float(p.get("value"))
|
||||
except Exception: return
|
||||
if length <= 0: return
|
||||
plane = _active_plane(doc, self._coord_sys)
|
||||
bbox_local = _bbox_in_plane(objs, plane)
|
||||
if bbox_local is None: return
|
||||
ref_world = _ref_point_world(bbox_local, self._ref, plane)
|
||||
with _UndoRecord(doc, "Dossier: Linienlaenge aendern"):
|
||||
_set_line_length(doc, objs[0], length, ref_world)
|
||||
doc.Views.Redraw()
|
||||
self._send_state(force=True)
|
||||
|
||||
def _set_rectangle(self, p):
|
||||
doc = Rhino.RhinoDoc.ActiveDoc
|
||||
objs = _get_selected_objects(doc)
|
||||
if len(objs) != 1: return
|
||||
try:
|
||||
w = float(p.get("width"))
|
||||
h = float(p.get("height"))
|
||||
except Exception:
|
||||
return
|
||||
if w <= 0 or h <= 0: return
|
||||
plane = _active_plane(doc, self._coord_sys)
|
||||
bbox_local = _bbox_in_plane(objs, plane)
|
||||
if bbox_local is None: return
|
||||
ref_world = _ref_point_world(bbox_local, self._ref, plane)
|
||||
with _UndoRecord(doc, "Dossier: Rechteck-Abmessung"):
|
||||
_set_rectangle_dims(doc, objs[0], w, h, plane, ref_world)
|
||||
doc.Views.Redraw()
|
||||
self._send_state(force=True)
|
||||
|
||||
|
||||
# --- Listener-Installation --------------------------------------------------
|
||||
|
||||
def _install_listeners(bridge):
|
||||
flag = "dimensionen_listeners"
|
||||
sc.sticky["dimensionen_bridge"] = bridge
|
||||
if sc.sticky.get(flag):
|
||||
return
|
||||
|
||||
def on_idle(s, e):
|
||||
b = sc.sticky.get("dimensionen_bridge")
|
||||
if b is not None:
|
||||
try: b.tick_idle()
|
||||
except Exception as ex: print("[DIMENSIONEN] idle:", ex)
|
||||
|
||||
def on_select(s, e):
|
||||
b = sc.sticky.get("dimensionen_bridge")
|
||||
if b is not None:
|
||||
try: b._send_state(force=True)
|
||||
except Exception: pass
|
||||
|
||||
Rhino.RhinoApp.Idle += on_idle
|
||||
try:
|
||||
Rhino.RhinoDoc.SelectObjects += on_select
|
||||
Rhino.RhinoDoc.DeselectObjects += on_select
|
||||
Rhino.RhinoDoc.DeselectAllObjects += on_select
|
||||
except Exception as ex:
|
||||
print("[DIMENSIONEN] select-events:", ex)
|
||||
sc.sticky[flag] = True
|
||||
print("[DIMENSIONEN] Listener aktiv (Idle + SelectObjects)")
|
||||
|
||||
|
||||
def _bridge_factory():
|
||||
b = DimensionenBridge()
|
||||
_install_listeners(b)
|
||||
return b
|
||||
|
||||
|
||||
panel_base.register_and_open("dimensionen", "DIMENSIONEN", PANEL_GUID_STR,
|
||||
_bridge_factory, icon_spec=("D", "#9e7050"))
|
||||
Reference in New Issue
Block a user