Treppen 2D-Plansymbol + Pure-Transform fuer hidden Layer
Frontend: - 2D-Plansymbol pro Treppe (Tritte/Lauflinie/Aussenlinie/Bruchsymbol) mit per-Treppe-Toggles in Properties-Panel - 'Obere Stufen gestrichelt'-Toggle splittet Tritte/Aussenlinie an Schnittebene; Lauflinie hat zwei Pfeile bei Bruch - Wand-Polyline-Grips fuer alle Vertices (nicht nur Enden) - PopupMenu unterstuetzt Divider + Checkbox-Items Backend: - Eigener Layer 41_Treppen_2D fuer Plansymbol, Layer-Default schwarz - Aussenlinie-Polygone folgen der Bruch-Diagonale (kein Versatz mehr) - Linetype-Fallback laedt Dashed bei Bedarf nach - Tritten-immer-an (Toggle entfernt), Z auf Geschoss-OKFF - TREPPEN/RAEUME Layer-Migration auf Capital-Case (Treppen/Raeume) - Selection-Partnership: treppe_2d_symbol pairs in axis + volume Pure-Transform fuer Treppen-Move: - treppe_2d_symbol + treppe_volume in VOLUME_TYPES → cascade-Support - Phase 1.5 Volume-only-Detection: wenn Source unbewegt aber Volumes uniform translated → synthetisiere canonical aus Avg-Delta der bewegten Volumes (unbewegte rausgefiltert sonst Verzerrung) - Hidden-inclusive ObjectEnumerator in Snapshot + Apply-Loop damit hidden treppe_axis auf 40_Treppen mit-transformiert wird - Properties-Fallback im _send_state findet hidden Sources via expliziter Iteration → Panel zeigt Treppe auch bei 3D-Layer aus - Dimensionen-Panel skipt on_select/idle waehrend UT_ACTIVE oder Partnership-Cascade → keine Flicker beim Drag mehr
This commit is contained in:
@@ -586,6 +586,10 @@ def _install_listeners(bridge):
|
||||
# tick_idle iteriert alle Doc-Objekte, das ist Overhead bei jedem
|
||||
# Tick zwischen den einzelnen Deletes. CommandEnd refresht.
|
||||
if sc.sticky.get("_dossier_bulk_op_active"): return
|
||||
# Waehrend Gumball/Move/Rotate: nicht pollen. Geometrie ist gerade
|
||||
# in Transit (Live-Replace pro Frame), Werte wuerden mit ~5/s
|
||||
# zwischen Frames flickern. CommandEnd triggert finalen _send_state.
|
||||
if sc.sticky.get("_dossier_user_transform_active"): return
|
||||
b = sc.sticky.get("dimensionen_bridge")
|
||||
if b is not None:
|
||||
try: b.tick_idle()
|
||||
@@ -595,6 +599,15 @@ def _install_listeners(bridge):
|
||||
# Swisstopo-Import feuert tausende Selection-Events → bail.
|
||||
if sc.sticky.get("dossier_swisstopo_busy"): return
|
||||
if sc.sticky.get("_dossier_bulk_op_active"): return
|
||||
# Waehrend elemente.py's Partnership-Cascade (Klick auf Wand/Treppe
|
||||
# → 30+ Partner selektiert in einem Rutsch): NICHT pro Event ein
|
||||
# _send_state feuern. Sonst rauscht das Dimensionen-Panel mit 30+
|
||||
# Re-Renders durch und die Werte/Auswahl-Anzeige flickert wild.
|
||||
# Der Idle-Tick holt die finale Selektion eh ~5/s nach.
|
||||
if sc.sticky.get("_elemente_select_busy"): return
|
||||
# Waehrend User-Transform (Gumball/Move/Rotate): kein Re-Send, sonst
|
||||
# rauscht Replace-Storm durch und der Frontend-State zappelt.
|
||||
if sc.sticky.get("_dossier_user_transform_active"): return
|
||||
b = sc.sticky.get("dimensionen_bridge")
|
||||
if b is not None:
|
||||
try: b._send_state(force=True)
|
||||
|
||||
+1014
-21
File diff suppressed because it is too large
Load Diff
+87
-81
@@ -96,53 +96,44 @@ def _find_axis_for_obj(doc, obj):
|
||||
return None
|
||||
|
||||
|
||||
def _curve_endpoints(curve):
|
||||
"""Liefert (start_pt, end_pt) fuer eine wand_axis. Funktioniert fuer
|
||||
LineCurve, PolylineCurve, NurbsCurve etc — alle Curve-Typen haben
|
||||
PointAtStart/PointAtEnd. Bei degenerierten Curves None."""
|
||||
if curve is None: return None, None
|
||||
def _axis_vertices(geom):
|
||||
"""Liefert die Vertices der wand_axis-Curve als Liste.
|
||||
- PolylineCurve: alle Vertices
|
||||
- LineCurve / sonstige Curve: [Start, End] (zwei-Vertex-Faelle)
|
||||
Returnt [] bei degeneriertem Input."""
|
||||
if geom is None: return []
|
||||
try:
|
||||
return curve.PointAtStart, curve.PointAtEnd
|
||||
if isinstance(geom, rg.PolylineCurve):
|
||||
poly = geom.ToPolyline()
|
||||
if poly is None or poly.Count < 2: return []
|
||||
return list(poly)
|
||||
p_start = geom.PointAtStart
|
||||
p_end = geom.PointAtEnd
|
||||
return [p_start, p_end]
|
||||
except Exception:
|
||||
return None, None
|
||||
return []
|
||||
|
||||
|
||||
def _replace_axis_endpoint(doc, axis_obj, kind, new_pt):
|
||||
"""Tauscht den Start- (kind='start') oder Endpunkt (kind='end') der
|
||||
wand_axis-Curve gegen new_pt. Geht intelligent um mit:
|
||||
- LineCurve: erzeuge neue Line vom fixen Punkt zum neuen Punkt
|
||||
- PolylineCurve: ersetze ersten/letzten Vertex, Rest bleibt
|
||||
- andere Curve-Typen: aktuell nur Line-Fallback (Erst/Letzt-Vertex
|
||||
rekonstruieren)
|
||||
Setzt die neue Geometrie via Objects.Replace — das feuert
|
||||
def _replace_axis_vertex(doc, axis_obj, vertex_idx, new_pt):
|
||||
"""Tauscht den Vertex an Index `vertex_idx` der wand_axis-Curve gegen
|
||||
new_pt. Funktioniert fuer Linien (idx 0/1) und Polylinien (alle idx).
|
||||
Setzt die neue Geometrie via Objects.Replace — feuert
|
||||
ReplaceRhinoObject-Event, was den existierenden Wand-Regen anwirft."""
|
||||
if axis_obj is None or axis_obj.IsDeleted: return False
|
||||
geom = axis_obj.Geometry
|
||||
if geom is None: return False
|
||||
try:
|
||||
# PolylineCurve mit > 2 Vertices: ersten/letzten Vertex ersetzen
|
||||
if isinstance(geom, rg.PolylineCurve):
|
||||
poly = geom.ToPolyline()
|
||||
if poly is None or poly.Count < 2: return False
|
||||
pts = list(poly)
|
||||
if kind == "start":
|
||||
pts[0] = new_pt
|
||||
else:
|
||||
pts[-1] = new_pt
|
||||
new_poly = rg.Polyline(pts)
|
||||
new_curve = rg.PolylineCurve(new_poly)
|
||||
pts = _axis_vertices(geom)
|
||||
if not pts: return False
|
||||
if vertex_idx < 0 or vertex_idx >= len(pts): return False
|
||||
pts[vertex_idx] = new_pt
|
||||
if len(pts) == 2:
|
||||
new_curve = rg.LineCurve(pts[0], pts[1])
|
||||
else:
|
||||
# LineCurve oder unbekannter Typ → reduziere auf Line zwischen
|
||||
# neuem + altem fixen Punkt.
|
||||
p_start, p_end = _curve_endpoints(geom)
|
||||
if p_start is None or p_end is None: return False
|
||||
if kind == "start":
|
||||
new_curve = rg.LineCurve(new_pt, p_end)
|
||||
else:
|
||||
new_curve = rg.LineCurve(p_start, new_pt)
|
||||
new_curve = rg.PolylineCurve(rg.Polyline(pts))
|
||||
return doc.Objects.Replace(axis_obj.Id, new_curve)
|
||||
except Exception as ex:
|
||||
print("[WAND_GRIPS] replace endpoint:", ex)
|
||||
print("[WAND_GRIPS] replace vertex:", ex)
|
||||
return False
|
||||
|
||||
|
||||
@@ -155,15 +146,17 @@ class _EndpointConduit(rd.DisplayConduit):
|
||||
|
||||
def __init__(self):
|
||||
rd.DisplayConduit.__init__(self)
|
||||
self.hot_key = None # (axis_id_str, kind) — fuer Hover
|
||||
self.drag_key = None # (axis_id_str, kind) — waehrend aktivem Drag
|
||||
self.drag_preview = None # rg.Line — Live-Vorschau waehrend GetPoint
|
||||
self.hot_key = None # (axis_id_str, vidx) — fuer Hover
|
||||
self.drag_key = None # (axis_id_str, vidx) — waehrend aktivem Drag
|
||||
self.drag_preview = None # Liste von rg.Line — Live-Vorschau (Linien
|
||||
# zu Nachbar-Vertices waehrend GetPoint)
|
||||
|
||||
def _collect_endpoints(self, doc):
|
||||
"""Liefert Liste von (axis_obj, kind, world_pt) fuer alle selektier-
|
||||
ten Waende. Iteriert die Selektion + dedupliziert Achsen (jede
|
||||
Wand erscheint nur einmal, auch wenn mehrere Volumen mit-selek-
|
||||
tiert sind)."""
|
||||
def _collect_grip_points(self, doc):
|
||||
"""Liefert Liste von (axis_obj, vertex_idx, world_pt) fuer ALLE
|
||||
Vertices aller selektierten Waende — fuer Polyline-Waende ist
|
||||
jeder Knick ein eigener Grip. Iteriert die Selektion + dedupli-
|
||||
ziert Achsen (jede Wand erscheint nur einmal, auch wenn mehrere
|
||||
Volumen mit-selektiert sind)."""
|
||||
out = []
|
||||
seen_axis = set()
|
||||
try:
|
||||
@@ -175,24 +168,21 @@ class _EndpointConduit(rd.DisplayConduit):
|
||||
aid = str(axis.Id)
|
||||
if aid in seen_axis: continue
|
||||
seen_axis.add(aid)
|
||||
p_start, p_end = _curve_endpoints(axis.Geometry)
|
||||
if p_start is not None:
|
||||
out.append((axis, "start", p_start))
|
||||
if p_end is not None:
|
||||
out.append((axis, "end", p_end))
|
||||
for i, pt in enumerate(_axis_vertices(axis.Geometry)):
|
||||
out.append((axis, i, pt))
|
||||
return out
|
||||
|
||||
def DrawForeground(self, e):
|
||||
try:
|
||||
doc = Rhino.RhinoDoc.ActiveDoc
|
||||
if doc is None: return
|
||||
for axis, kind, pt in self._collect_endpoints(doc):
|
||||
for axis, vidx, pt in self._collect_grip_points(doc):
|
||||
aid = str(axis.Id)
|
||||
# Skip den gerade gezogenen Marker — der wird via
|
||||
# drag_preview separat dargestellt.
|
||||
if self.drag_key and self.drag_key == (aid, kind):
|
||||
if self.drag_key and self.drag_key == (aid, vidx):
|
||||
continue
|
||||
is_hot = self.hot_key and self.hot_key == (aid, kind)
|
||||
is_hot = self.hot_key and self.hot_key == (aid, vidx)
|
||||
r = _MARKER_RADIUS_HOVER_PX if is_hot else _MARKER_RADIUS_PX
|
||||
fill = _MARKER_HOVER if is_hot else _MARKER_FILL
|
||||
# DrawPoint mit RoundControlPoint = gefuellter Kreis +
|
||||
@@ -204,11 +194,12 @@ class _EndpointConduit(rd.DisplayConduit):
|
||||
# Fallback fuer aeltere Rhino-Versionen: einfacher
|
||||
# DrawDot mit Label "●"
|
||||
e.Display.DrawDot(pt, "●", fill, _MARKER_BORDER)
|
||||
# Drag-Preview-Linie waehrend GetPoint aktiv ist
|
||||
if self.drag_preview is not None:
|
||||
try:
|
||||
e.Display.DrawLine(self.drag_preview, _MARKER_HOVER, 2)
|
||||
except Exception: pass
|
||||
# Drag-Preview-Linien waehrend GetPoint aktiv ist
|
||||
if self.drag_preview:
|
||||
for line in self.drag_preview:
|
||||
try:
|
||||
e.Display.DrawLine(line, _MARKER_HOVER, 2)
|
||||
except Exception: pass
|
||||
except Exception as ex:
|
||||
print("[WAND_GRIPS] DrawForeground:", ex)
|
||||
|
||||
@@ -226,21 +217,21 @@ class _EndpointMouseHandler(Rhino.UI.MouseCallback):
|
||||
self._busy = False # Re-Entry-Schutz waehrend Drag-Get-Point
|
||||
|
||||
def _hit_test(self, view, screen_pt):
|
||||
"""Liefert (axis, kind, world_pt) wenn screen_pt nahe eines Endpoint-
|
||||
Markers liegt, sonst None. Iteriert die aktuelle Conduit-Liste."""
|
||||
"""Liefert (axis, vertex_idx, world_pt) wenn screen_pt nahe eines
|
||||
Vertex-Markers liegt, sonst None."""
|
||||
doc = Rhino.RhinoDoc.ActiveDoc
|
||||
if doc is None: return None
|
||||
try:
|
||||
vp = view.ActiveViewport
|
||||
except Exception: return None
|
||||
thresh2 = _HIT_RADIUS_PX * _HIT_RADIUS_PX
|
||||
for axis, kind, world_pt in self.conduit._collect_endpoints(doc):
|
||||
for axis, vidx, world_pt in self.conduit._collect_grip_points(doc):
|
||||
try:
|
||||
s = vp.WorldToClient(world_pt)
|
||||
dx = s.X - screen_pt.X
|
||||
dy = s.Y - screen_pt.Y
|
||||
if (dx * dx + dy * dy) <= thresh2:
|
||||
return axis, kind, world_pt
|
||||
return axis, vidx, world_pt
|
||||
except Exception: continue
|
||||
return None
|
||||
|
||||
@@ -274,44 +265,59 @@ class _EndpointMouseHandler(Rhino.UI.MouseCallback):
|
||||
# Default-Klick (Selection) abwuergen — wir uebernehmen
|
||||
try: e.Cancel = True
|
||||
except Exception: pass
|
||||
axis, kind, world_pt = hit
|
||||
self._start_drag(view.Document, axis, kind, world_pt)
|
||||
axis, vidx, world_pt = hit
|
||||
self._start_drag(view.Document, axis, vidx, world_pt)
|
||||
except Exception as ex:
|
||||
print("[WAND_GRIPS] OnMouseDown:", ex)
|
||||
|
||||
def _start_drag(self, doc, axis, kind, anchor_pt):
|
||||
"""Startet eine Rhino-GetPoint-Interaktion um den neuen Endpunkt
|
||||
zu picken. Der ANDERE Endpunkt (Fix-Punkt) wird als BasePoint
|
||||
gesetzt — damit kriegt der User Tracking-Linie, Ortho-Mode etc.
|
||||
wie bei _Move."""
|
||||
def _start_drag(self, doc, axis, vertex_idx, anchor_pt):
|
||||
"""Startet eine Rhino-GetPoint-Interaktion um den Vertex zu
|
||||
verschieben. BasePoint-Strategie:
|
||||
- End-Vertex (idx 0 oder letzter): gegenueberliegender End-Vertex
|
||||
→ User bekommt Tracking-Linie + Wand-Laenge wie bei _Move
|
||||
- Mittel-Vertex (Polyline-Knick): Vertex selbst, plus Live-Preview
|
||||
zu beiden Nachbar-Vertices damit beide Segmente sichtbar mit-
|
||||
schwingen."""
|
||||
if doc is None: return
|
||||
geom = axis.Geometry
|
||||
if geom is None: return
|
||||
p_start, p_end = _curve_endpoints(geom)
|
||||
if p_start is None or p_end is None: return
|
||||
fixed_pt = p_end if kind == "start" else p_start
|
||||
# Conduit-State: drag-Marker hervorheben + Preview-Linie
|
||||
self.conduit.drag_key = (str(axis.Id), kind)
|
||||
self.conduit.drag_preview = rg.Line(fixed_pt, anchor_pt)
|
||||
pts = _axis_vertices(geom)
|
||||
if not pts or vertex_idx < 0 or vertex_idx >= len(pts): return
|
||||
is_first = vertex_idx == 0
|
||||
is_last = vertex_idx == len(pts) - 1
|
||||
prev_pt = pts[vertex_idx - 1] if not is_first else None
|
||||
next_pt = pts[vertex_idx + 1] if not is_last else None
|
||||
if is_first: base_pt = next_pt
|
||||
elif is_last: base_pt = prev_pt
|
||||
else: base_pt = anchor_pt
|
||||
# Conduit-State: drag-Marker hervorheben + Preview-Linien
|
||||
self.conduit.drag_key = (str(axis.Id), vertex_idx)
|
||||
self.conduit.drag_preview = []
|
||||
if prev_pt is not None:
|
||||
self.conduit.drag_preview.append(rg.Line(prev_pt, anchor_pt))
|
||||
if next_pt is not None:
|
||||
self.conduit.drag_preview.append(rg.Line(next_pt, anchor_pt))
|
||||
self._busy = True
|
||||
try:
|
||||
gp = Rhino.Input.Custom.GetPoint()
|
||||
gp.SetCommandPrompt("Wand-Endpunkt: neuer Punkt (Esc=Abbruch)")
|
||||
gp.SetBasePoint(fixed_pt, True)
|
||||
gp.DrawLineFromPoint(fixed_pt, True)
|
||||
# Live-Preview ueber Conduit (zusaetzlich zu Rhinos eigener
|
||||
# Tracking-Linie) — sieht ueblich, hilft beim Verstehen welcher
|
||||
# Endpunkt sich bewegt.
|
||||
gp.SetCommandPrompt("Wand-Vertex: neuer Punkt (Esc=Abbruch)")
|
||||
gp.SetBasePoint(base_pt, True)
|
||||
gp.DrawLineFromPoint(base_pt, True)
|
||||
def _on_mouse_move(sender, args):
|
||||
try:
|
||||
self.conduit.drag_preview = rg.Line(fixed_pt, args.Point)
|
||||
preview = []
|
||||
if prev_pt is not None:
|
||||
preview.append(rg.Line(prev_pt, args.Point))
|
||||
if next_pt is not None:
|
||||
preview.append(rg.Line(next_pt, args.Point))
|
||||
self.conduit.drag_preview = preview
|
||||
except Exception: pass
|
||||
try: gp.MouseMove += _on_mouse_move
|
||||
except Exception: pass
|
||||
res = gp.Get()
|
||||
if res == Rhino.Input.GetResult.Point:
|
||||
new_pt = gp.Point()
|
||||
_replace_axis_endpoint(doc, axis, kind, new_pt)
|
||||
_replace_axis_vertex(doc, axis, vertex_idx, new_pt)
|
||||
except Exception as ex:
|
||||
print("[WAND_GRIPS] _start_drag:", ex)
|
||||
finally:
|
||||
|
||||
Reference in New Issue
Block a user