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:
+142
-15
@@ -7693,27 +7693,37 @@ _UT_SNAPSHOT_KEY = "_dossier_pre_transform_snapshot"
|
|||||||
|
|
||||||
|
|
||||||
def _snapshot_source_positions(doc):
|
def _snapshot_source_positions(doc):
|
||||||
"""Schnappschuss aller Source-Geometrien — gerade vor einem User-Transform.
|
"""Schnappschuss vor einem User-Transform: Source-Geometrien + Volume-
|
||||||
Wird in _on_command_end gegen aktuelle Positionen verglichen, um Brüstung-
|
BBox-Centers. Source-Map (key=element_id) füttert Constraint+Migrate.
|
||||||
Mitnahme + Migration zu rechnen ohne mit Rhinos noch laufender Move-
|
Volume-Map (key=obj.Id-string) erlaubt im CommandEnd die Pure-Translate-
|
||||||
Operation zu kollidieren."""
|
Detection — wir checken pro Volume ob es schon vom Rhinos Move
|
||||||
snap = {}
|
transformed wurde, oder noch ge-translaten werden muss."""
|
||||||
|
snap = {"sources": {}, "volumes": {}}
|
||||||
if doc is None: return snap
|
if doc is None: return snap
|
||||||
for obj in doc.Objects:
|
for obj in doc.Objects:
|
||||||
try:
|
try:
|
||||||
m = _read_meta(obj)
|
m = _read_meta(obj)
|
||||||
if not m: continue
|
if not m: continue
|
||||||
t = m.get("type")
|
t = m.get("type")
|
||||||
if t not in SOURCE_TYPES: continue
|
|
||||||
geom = obj.Geometry
|
geom = obj.Geometry
|
||||||
if hasattr(geom, "Location"):
|
if t in SOURCE_TYPES:
|
||||||
p = geom.Location
|
if hasattr(geom, "Location"):
|
||||||
snap[m["id"]] = {"type": t, "pos": (p.X, p.Y, p.Z)}
|
p = geom.Location
|
||||||
elif isinstance(geom, rg.Curve):
|
snap["sources"][m["id"]] = {"type": t, "pos": (p.X, p.Y, p.Z)}
|
||||||
s = geom.PointAtStart; e = geom.PointAtEnd
|
elif isinstance(geom, rg.Curve):
|
||||||
snap[m["id"]] = {"type": t,
|
s = geom.PointAtStart; e = geom.PointAtEnd
|
||||||
"start": (s.X, s.Y, s.Z),
|
snap["sources"][m["id"]] = {"type": t,
|
||||||
"end": (e.X, e.Y, e.Z)}
|
"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
|
except Exception: pass
|
||||||
return snap
|
return snap
|
||||||
|
|
||||||
@@ -7738,6 +7748,123 @@ def _on_command_end(sender, e):
|
|||||||
doc = Rhino.RhinoDoc.ActiveDoc
|
doc = Rhino.RhinoDoc.ActiveDoc
|
||||||
if doc is None: return
|
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
|
# Pseudo-Object Wrapper damit _apply_oeffnung_constraint pt_old.Location
|
||||||
# lesen kann ohne den echten alten RhinoObject zu kennen.
|
# lesen kann ohne den echten alten RhinoObject zu kennen.
|
||||||
class _PseudoOld(object):
|
class _PseudoOld(object):
|
||||||
@@ -7762,7 +7889,7 @@ def _on_command_end(sender, e):
|
|||||||
if not m: continue
|
if not m: continue
|
||||||
t = m.get("type")
|
t = m.get("type")
|
||||||
if t not in SOURCE_TYPES: continue
|
if t not in SOURCE_TYPES: continue
|
||||||
old = snapshot.get(m["id"])
|
old = sources_snap.get(m["id"])
|
||||||
if old is None: continue
|
if old is None: continue
|
||||||
if t == "wand_axis":
|
if t == "wand_axis":
|
||||||
geom = obj.Geometry
|
geom = obj.Geometry
|
||||||
|
|||||||
Reference in New Issue
Block a user