Perf: Pure-Translate-Skip — Wand+Tür+Fenster gemeinsam = instant
Im CommandEnd-Batch wird jetzt zuerst auf pure-translate gecheckt: - Alle bewegten Source-Geometrien haben EXAKT denselben Delta-Vektor - Beide Endpunkte einer Wand-Achse haben gleichen Vektor (= kein End-Grip- Drag, kein Rotate/Scale) - Keine Z-Komponente in den Deltas (= kein Brüstungs-Property-Change) Wenn alle Bedingungen erfüllt: REINE TRANSLATION aller Geometrien um den Delta-Vektor. Sub-Volumen die schon von Rhinos Multi-Select-Move transformed wurden (BBox-Center verschoben) werden geskippt; nur die unbewegten kriegen Translate(vec). KEIN Wand-Regen, KEIN Boolean-Diff → instant. Snapshot um Volume-BBox-Centers erweitert für die Same-Position- Detection. Bei Property-Änderung (Brüstung/Höhe/Breite/Rotate) fällt's automatisch auf den vollen Regen-Pfad zurück. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+136
-9
@@ -7693,27 +7693,37 @@ _UT_SNAPSHOT_KEY = "_dossier_pre_transform_snapshot"
|
||||
|
||||
|
||||
def _snapshot_source_positions(doc):
|
||||
"""Schnappschuss aller Source-Geometrien — gerade vor einem User-Transform.
|
||||
Wird in _on_command_end gegen aktuelle Positionen verglichen, um Brüstung-
|
||||
Mitnahme + Migration zu rechnen ohne mit Rhinos noch laufender Move-
|
||||
Operation zu kollidieren."""
|
||||
snap = {}
|
||||
"""Schnappschuss vor einem User-Transform: Source-Geometrien + Volume-
|
||||
BBox-Centers. Source-Map (key=element_id) füttert Constraint+Migrate.
|
||||
Volume-Map (key=obj.Id-string) erlaubt im CommandEnd die Pure-Translate-
|
||||
Detection — wir checken pro Volume ob es schon vom Rhinos Move
|
||||
transformed wurde, oder noch ge-translaten werden muss."""
|
||||
snap = {"sources": {}, "volumes": {}}
|
||||
if doc is None: return snap
|
||||
for obj in doc.Objects:
|
||||
try:
|
||||
m = _read_meta(obj)
|
||||
if not m: continue
|
||||
t = m.get("type")
|
||||
if t not in SOURCE_TYPES: continue
|
||||
geom = obj.Geometry
|
||||
if t in SOURCE_TYPES:
|
||||
if hasattr(geom, "Location"):
|
||||
p = geom.Location
|
||||
snap[m["id"]] = {"type": t, "pos": (p.X, p.Y, p.Z)}
|
||||
snap["sources"][m["id"]] = {"type": t, "pos": (p.X, p.Y, p.Z)}
|
||||
elif isinstance(geom, rg.Curve):
|
||||
s = geom.PointAtStart; e = geom.PointAtEnd
|
||||
snap[m["id"]] = {"type": t,
|
||||
snap["sources"][m["id"]] = {"type": t,
|
||||
"start": (s.X, s.Y, s.Z),
|
||||
"end": (e.X, e.Y, e.Z)}
|
||||
elif t in VOLUME_TYPES:
|
||||
try:
|
||||
bb = geom.GetBoundingBox(True)
|
||||
if bb.IsValid:
|
||||
c = bb.Center
|
||||
snap["volumes"][str(obj.Id)] = {
|
||||
"element_id": m["id"], "type": t,
|
||||
"center": (c.X, c.Y, c.Z)}
|
||||
except Exception: pass
|
||||
except Exception: pass
|
||||
return snap
|
||||
|
||||
@@ -7738,6 +7748,123 @@ def _on_command_end(sender, e):
|
||||
doc = Rhino.RhinoDoc.ActiveDoc
|
||||
if doc is None: return
|
||||
|
||||
sources_snap = snapshot.get("sources", {}) if isinstance(snapshot, dict) else {}
|
||||
volumes_snap = snapshot.get("volumes", {}) if isinstance(snapshot, dict) else {}
|
||||
|
||||
# ─── Pure-Translate-Detection ───────────────────────────────────────────
|
||||
# Wenn ALLE bewegten Sources um den exakt gleichen Vektor verschoben
|
||||
# wurden, KEIN Z-Drag (= Property-Aenderung), KEIN Rotate/Scale (= End-
|
||||
# Grip-Drag) — dann reicht eine reine Translation aller noch unbewegten
|
||||
# Volumen + Punkte. KEIN Wand-Regen, KEIN Boolean-Diff. Geht instant.
|
||||
def _source_delta(obj, old):
|
||||
geom = obj.Geometry
|
||||
if hasattr(geom, "Location"):
|
||||
op = old.get("pos")
|
||||
if op is None: return None
|
||||
p = geom.Location
|
||||
return (p.X - op[0], p.Y - op[1], p.Z - op[2])
|
||||
if isinstance(geom, rg.Curve):
|
||||
os_pt = old.get("start"); oe_pt = old.get("end")
|
||||
if os_pt is None or oe_pt is None: return None
|
||||
ns = geom.PointAtStart; ne = geom.PointAtEnd
|
||||
ds = (ns.X - os_pt[0], ns.Y - os_pt[1], ns.Z - os_pt[2])
|
||||
de = (ne.X - oe_pt[0], ne.Y - oe_pt[1], ne.Z - oe_pt[2])
|
||||
# Beide Endpunkte muessen den gleichen Vektor haben (= Translate).
|
||||
# Sonst ist's Rotate/Scale/End-Grip-Drag.
|
||||
if (abs(ds[0]-de[0]) > 1e-6 or abs(ds[1]-de[1]) > 1e-6
|
||||
or abs(ds[2]-de[2]) > 1e-6):
|
||||
return None
|
||||
return ds
|
||||
return None
|
||||
|
||||
deltas = {}
|
||||
abort_pure = False
|
||||
for obj in doc.Objects:
|
||||
try:
|
||||
m = _read_meta(obj)
|
||||
if not m: continue
|
||||
if m.get("type") not in SOURCE_TYPES: continue
|
||||
old = sources_snap.get(m["id"])
|
||||
if old is None: continue
|
||||
d = _source_delta(obj, old)
|
||||
if d is None:
|
||||
# End-Grip-Drag o.ae. → kein Pure-Translate
|
||||
abort_pure = True
|
||||
break
|
||||
deltas[m["id"]] = d
|
||||
except Exception: pass
|
||||
|
||||
pure_delta = None
|
||||
if not abort_pure:
|
||||
moved = [d for d in deltas.values()
|
||||
if abs(d[0]) > 1e-6 or abs(d[1]) > 1e-6 or abs(d[2]) > 1e-6]
|
||||
if moved:
|
||||
# Alle bewegten Sources muessen denselben Vektor haben
|
||||
first = moved[0]
|
||||
same = all(abs(d[0]-first[0]) < 1e-6 and
|
||||
abs(d[1]-first[1]) < 1e-6 and
|
||||
abs(d[2]-first[2]) < 1e-6 for d in moved)
|
||||
# Z-Drag erkennen: wenn Wand-Achse Z aenderung hat → Brüstungs-
|
||||
# Mitnahme noetig → kein Pure-Translate.
|
||||
has_z_drag = abs(first[2]) > 1e-6
|
||||
if same and not has_z_drag:
|
||||
pure_delta = first
|
||||
|
||||
if pure_delta is not None:
|
||||
# PURE-TRANSLATE PFAD: nur Geometries translaten die nicht schon vom
|
||||
# User-Move transformed wurden. Keine Brep-Regeneration, kein
|
||||
# Boolean-Diff. → instant feedback.
|
||||
vec = rg.Vector3d(*pure_delta)
|
||||
_was_busy = sc.sticky.get(_REGEN_BUSY, False)
|
||||
sc.sticky[_REGEN_BUSY] = True
|
||||
prev_redraw = doc.Views.RedrawEnabled
|
||||
doc.Views.RedrawEnabled = False
|
||||
try:
|
||||
for obj in list(doc.Objects):
|
||||
try:
|
||||
m = _read_meta(obj)
|
||||
if not m: continue
|
||||
t = m.get("type")
|
||||
# Sources die nicht in deltas waren (= unbewegt) auch translaten
|
||||
if t in SOURCE_TYPES and m["id"] not in deltas:
|
||||
new_geom = obj.Geometry.Duplicate()
|
||||
new_geom.Translate(vec)
|
||||
doc.Objects.Replace(obj.Id, new_geom)
|
||||
continue
|
||||
# Volumes: vergleiche Position vs. Snapshot. Wenn unbewegt,
|
||||
# translaten. Wenn bereits transformed (durch Multi-Select-
|
||||
# Move), skip.
|
||||
if t in VOLUME_TYPES:
|
||||
vol_snap = volumes_snap.get(str(obj.Id))
|
||||
if vol_snap is None: continue
|
||||
try:
|
||||
bb = obj.Geometry.GetBoundingBox(True)
|
||||
if not bb.IsValid: continue
|
||||
c_now = bb.Center
|
||||
c_old = vol_snap["center"]
|
||||
dx = c_now.X - c_old[0]
|
||||
dy = c_now.Y - c_old[1]
|
||||
dz = c_now.Z - c_old[2]
|
||||
if (abs(dx) < 1e-6 and abs(dy) < 1e-6 and abs(dz) < 1e-6):
|
||||
# noch nicht transformed → translaten
|
||||
new_geom = obj.Geometry.Duplicate()
|
||||
new_geom.Translate(vec)
|
||||
doc.Objects.Replace(obj.Id, new_geom)
|
||||
except Exception: pass
|
||||
except Exception as ex:
|
||||
print("[ELEMENTE] pure-translate:", ex)
|
||||
finally:
|
||||
doc.Views.RedrawEnabled = prev_redraw
|
||||
sc.sticky[_REGEN_BUSY] = _was_busy
|
||||
try: doc.Views.Redraw()
|
||||
except Exception: pass
|
||||
b = sc.sticky.get("elemente_bridge")
|
||||
if b is not None:
|
||||
try: b._send_state()
|
||||
except Exception: pass
|
||||
return
|
||||
|
||||
# ─── Regulärer Pfad: Constraints + Migrate + Regen (existing flow) ──────
|
||||
# Pseudo-Object Wrapper damit _apply_oeffnung_constraint pt_old.Location
|
||||
# lesen kann ohne den echten alten RhinoObject zu kennen.
|
||||
class _PseudoOld(object):
|
||||
@@ -7762,7 +7889,7 @@ def _on_command_end(sender, e):
|
||||
if not m: continue
|
||||
t = m.get("type")
|
||||
if t not in SOURCE_TYPES: continue
|
||||
old = snapshot.get(m["id"])
|
||||
old = sources_snap.get(m["id"])
|
||||
if old is None: continue
|
||||
if t == "wand_axis":
|
||||
geom = obj.Geometry
|
||||
|
||||
Reference in New Issue
Block a user