#! python3 # -*- coding: utf-8 -*- # SPDX-License-Identifier: AGPL-3.0-or-later # Copyright (C) 2026 Karim Gabriele Varano """ curve_vertex_dots.py Display-only Vertex-Dots fuer GENERISCHE Curves (Polylinen, Linien, Rectangles, NurbsCurves etc). Zeigt gruene Punkte an allen Vertices selektierter Curves — hilft beim Visuell-Finden von Grip-Positionen wenn die Curve eine Fuellung (Hatch) hat und schwer per Klick auf einen einzelnen Vertex zu treffen ist. Display-only — kein eigener Drag-Handler. User editiert Vertices via Rhino's native _Grips (Punkte sichtbar machen + Standard-Drag) oder direktes Object-Snapping waehrend Drag. Skipt dossier-managed Curves (wand_axis, treppe_axis, schnitt_axis, wand_outline, wand_centerline, raum_polylinie etc) — die haben ihre eigenen Conduits oder duerfen nicht via Vertex editiert werden. """ import Rhino import Rhino.Display as rd import Rhino.Geometry as rg import scriptcontext as sc import System.Drawing as SD # --- Konstanten ------------------------------------------------------------ _MARKER_RADIUS_PX = 6 _MARKER_FILL = SD.Color.FromArgb(200, 95, 168, 150) # accent-gruen _MARKER_BORDER = SD.Color.FromArgb(255, 47, 93, 84) # Dossier-managed Element-Types die NICHT mit generic dots versehen werden # (= haben eigene Conduits oder sind nicht editierbar via Vertex-Click). _SKIP_TYPES = { "wand_axis", "wand_centerline", "wand_outline", "wand_volume", "treppe_axis", "treppe_outline", "treppe_volume", "schnitt_axis", "schnitt_outline", "raum_polylinie", "raum_stempel", "ausschnitt_polylinie", "decke_polylinie", "decke_volume", "dach_polylinie", "dach_volume", } # --- Helpers -------------------------------------------------------------- def _is_dossier_managed(obj): """True wenn obj ein dossier-managed Element ist (= Skip).""" if obj is None or obj.IsDeleted: return True try: t = obj.Attributes.GetUserString("dossier_element_type") or "" return t in _SKIP_TYPES except Exception: return False def _curve_vertices(curve): """Liefert Liste von rg.Point3d fuer alle relevanten Vertices der Curve. Verschiedene Curve-Types haben verschiedene Vertices: - LineCurve: 2 Endpunkte - PolylineCurve: alle Polyline-Punkte (deduplizert wenn closed) - PolyCurve: rekursiv Segmente - NurbsCurve/sonst: Start + End (control points nicht — zu viele)""" pts = [] if curve is None: return pts try: if isinstance(curve, rg.PolylineCurve): ok, pline = curve.TryGetPolyline() if ok and pline is not None: n = pline.Count # Deduplizieren wenn closed (letzter Punkt = erster) last = n try: if (n >= 2 and pline[0].DistanceTo(pline[n - 1]) < 1e-6): last = n - 1 except Exception: pass for i in range(last): pts.append(rg.Point3d(pline[i])) return pts if isinstance(curve, rg.LineCurve): pts.append(curve.PointAtStart) pts.append(curve.PointAtEnd) return pts if isinstance(curve, rg.PolyCurve): for i in range(curve.SegmentCount): seg = curve.SegmentCurve(i) if seg is None: continue # Nur Start jedes Segments (End ist Start des naechsten) pts.append(seg.PointAtStart) # Letztes Segment-End anhaengen try: pts.append(curve.PointAtEnd) except Exception: pass return pts # Generic Curve: nur Start + End try: pts.append(curve.PointAtStart) pts.append(curve.PointAtEnd) except Exception: pass except Exception: pass return pts # --- Conduit ------------------------------------------------------------- class _VertexDotConduit(rd.DisplayConduit): """Zeichnet bei jeder selektierten generischen Curve gruene Punkte an allen Vertices.""" def DrawForeground(self, e): try: doc = Rhino.RhinoDoc.ActiveDoc if doc is None: return try: sel = list(doc.Objects.GetSelectedObjects(False, False)) except Exception: return seen_curve_ids = set() for obj in sel: if _is_dossier_managed(obj): continue try: cid = str(obj.Id) except Exception: continue if cid in seen_curve_ids: continue seen_curve_ids.add(cid) geom = obj.Geometry if not isinstance(geom, rg.Curve): continue for pt in _curve_vertices(geom): try: e.Display.DrawPoint( pt, rd.PointStyle.RoundControlPoint, _MARKER_RADIUS_PX, _MARKER_FILL) except Exception: try: e.Display.DrawDot( pt, "·", _MARKER_FILL, _MARKER_BORDER) except Exception: pass except Exception as ex: print("[CURVE_DOTS] DrawForeground:", ex) # --- Install ------------------------------------------------------------- _STICKY_CONDUIT = "_dossier_curve_vertex_dots_conduit" def install_curve_vertex_dots(): """Idempotent: alten Conduit disable, neuen installieren.""" try: old = sc.sticky.get(_STICKY_CONDUIT) if old is not None: try: old.Enabled = False except Exception: pass conduit = _VertexDotConduit() conduit.Enabled = True sc.sticky[_STICKY_CONDUIT] = conduit print("[CURVE_DOTS] Vertex-Dot-Conduit aktiv") except Exception as ex: print("[CURVE_DOTS] install:", ex)