Rotation: Snapshot-basierte Migrate für korrekte Bogenlängen-Position
Bug: Bei Rotation um einen externen Punkt liegt der Öffnungs-Punkt nach Rhinos Transform NICHT mehr auf der alten Achse → migrate's ClosestPoint(current_pos) snappte zum nächsten Endpunkt der alten Achse → relative=1 → alle Öffnungen landeten am gleichen Ende der neuen Achse (= „bei Referenzpunkt der Drehung"). Fix: Migrate nutzt jetzt die PRE-TRANSFORM Position aus dem Snapshot (via `old_positions` Parameter). Aufrufer im CommandEnd-Regen-Pfad sammelt die alten Positionen aus `sources_snap` und gibt sie weiter. Migrate setzt opening_point.Z jetzt auch konsistent auf `wall_uk + brüstung` statt nur `brüstung` — vermeidet Brüstung-Drop beim nachfolgenden _apply_oeffnung_constraint. Constraint überspringt XY-Projektion wenn Wand gerade migriert wurde (`_dossier_migrated_walls` sticky-Set) — sonst würde ClosestPoint(pt_old) auf neuer rotierter Achse die Position wieder verschieben. Debug-Logs in _apply_wand_z_drag_constraint + Wand-Regen bleiben drin — haben bei der Eingrenzung des UK_OVER-Bugs geholfen, kosten nichts. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+74
-8
@@ -6839,7 +6839,16 @@ def _apply_oeffnung_constraint(new_obj, meta, old_obj=None):
|
|||||||
break
|
break
|
||||||
|
|
||||||
target_x, target_y = pt_new.X, pt_new.Y
|
target_x, target_y = pt_new.X, pt_new.Y
|
||||||
if parent_curve is not None:
|
# Wenn die Wand gerade migrate'd wurde (Rotation/Reshape/XY-Move) →
|
||||||
|
# XY-Projektion HIER UEBERSPRINGEN. Migrate hat den Punkt schon per
|
||||||
|
# Bogenlaengen-Mapping auf die neue Achse gesetzt. Eine zweite XY-
|
||||||
|
# Projektion mit ClosestPoint(pt_old) auf der NEUEN Achse wuerde die
|
||||||
|
# Position wieder verschieben (Rotation: pt_old liegt nicht mehr auf
|
||||||
|
# der neuen Achse → ClosestPoint+Tangent stimmen nicht zusammen).
|
||||||
|
migrated_walls = sc.sticky.get("_dossier_migrated_walls")
|
||||||
|
skip_xy_projection = (isinstance(migrated_walls, set)
|
||||||
|
and parent_id in migrated_walls)
|
||||||
|
if parent_curve is not None and not skip_xy_projection:
|
||||||
if pt_old is not None:
|
if pt_old is not None:
|
||||||
try:
|
try:
|
||||||
rc, t_old = parent_curve.ClosestPoint(
|
rc, t_old = parent_curve.ClosestPoint(
|
||||||
@@ -7092,12 +7101,20 @@ def _on_object_replaced_body(sender, e):
|
|||||||
print("[ELEMENTE] on_object_replaced:", ex)
|
print("[ELEMENTE] on_object_replaced:", ex)
|
||||||
|
|
||||||
|
|
||||||
def _migrate_openings_to_new_axis(wall_id, old_geom, new_geom):
|
def _migrate_openings_to_new_axis(wall_id, old_geom, new_geom, old_positions=None):
|
||||||
"""Verschiebt alle Oeffnungs-Points einer Wand mit, wenn deren Achse
|
"""Verschiebt alle Oeffnungs-Points einer Wand mit, wenn deren Achse
|
||||||
veraendert wird. Mapping ueber relative Bogenlaenge: ein Oeffnungs-
|
veraendert wird. Mapping ueber relative Bogenlaenge: ein Oeffnungs-
|
||||||
Punkt bei 30 % der alten Kurve sitzt nachher bei 30 % der neuen.
|
Punkt bei 30 % der alten Kurve sitzt nachher bei 30 % der neuen.
|
||||||
So bleiben die Oeffnungen 'sticky' an der Wand bei Verschieben,
|
So bleiben die Oeffnungen 'sticky' an der Wand bei Verschieben,
|
||||||
Drehen, Skalieren oder Reshape der Achse."""
|
Drehen, Skalieren oder Reshape der Achse.
|
||||||
|
|
||||||
|
`old_positions` (optional): {opening_id: (x, y, z)} — Pre-Transform
|
||||||
|
Snapshot der Oeffnungs-Punkte. WICHTIG bei Rotation/Move: nach Rhinos
|
||||||
|
Transform liegen die Punkte schon NICHT MEHR auf der alten Axis →
|
||||||
|
`ClosestPoint(current_pos)` an old_geom snappt zum naechsten Endpunkt
|
||||||
|
statt zur echten Bogenlaengen-Position → alle Oeffnungen landen am
|
||||||
|
selben Ende. Bei Reshape-Operationen ohne Snapshot: Fallback auf
|
||||||
|
aktuelle Geometrie (Punkt liegt dort noch auf alter Axis)."""
|
||||||
if not isinstance(old_geom, rg.Curve) or not isinstance(new_geom, rg.Curve):
|
if not isinstance(old_geom, rg.Curve) or not isinstance(new_geom, rg.Curve):
|
||||||
return
|
return
|
||||||
doc = Rhino.RhinoDoc.ActiveDoc
|
doc = Rhino.RhinoDoc.ActiveDoc
|
||||||
@@ -7107,6 +7124,29 @@ def _migrate_openings_to_new_axis(wall_id, old_geom, new_geom):
|
|||||||
new_len = new_geom.GetLength()
|
new_len = new_geom.GetLength()
|
||||||
except Exception: return
|
except Exception: return
|
||||||
if old_len < 1e-9 or new_len < 1e-9: return
|
if old_len < 1e-9 or new_len < 1e-9: return
|
||||||
|
# Wand-UK aufloesen damit Oeffnungs-Punkte auf UK+Brueestung gesetzt
|
||||||
|
# werden (= visuell auf Unterkante Oeffnung). Sonst landen sie auf
|
||||||
|
# reiner Brueest-Hoehe und der nachfolgende Constraint interpretiert
|
||||||
|
# die Diskrepanz als User-Z-Drag → Brueest dropt.
|
||||||
|
wall_uk = 0.0
|
||||||
|
src = _find_axis(doc, wall_id)
|
||||||
|
if src is not None:
|
||||||
|
wm = _read_meta(src)
|
||||||
|
if wm:
|
||||||
|
try:
|
||||||
|
wall_uk, _ = _resolve_uk_ok(doc, wm["geschoss"],
|
||||||
|
wm["uk_override"],
|
||||||
|
wm["ok_override"])
|
||||||
|
except Exception:
|
||||||
|
wall_uk = 0.0
|
||||||
|
# Migrierten Wand registrieren — der Constraint soll fuer Oeffnungen
|
||||||
|
# dieser Wand die XY-Projektion ueberspringen (migrate hat XY bereits
|
||||||
|
# via Bogenlaengen-Mapping korrekt gesetzt).
|
||||||
|
migrated = sc.sticky.get("_dossier_migrated_walls")
|
||||||
|
if not isinstance(migrated, set):
|
||||||
|
migrated = set()
|
||||||
|
migrated.add(wall_id)
|
||||||
|
sc.sticky["_dossier_migrated_walls"] = migrated
|
||||||
|
|
||||||
# Selected-Snapshot vom Replace-Handler — nicht live IsSelected, weil
|
# Selected-Snapshot vom Replace-Handler — nicht live IsSelected, weil
|
||||||
# op_obj im laufenden Move-Event evtl. schon stale ist.
|
# op_obj im laufenden Move-Event evtl. schon stale ist.
|
||||||
@@ -7127,18 +7167,27 @@ def _migrate_openings_to_new_axis(wall_id, old_geom, new_geom):
|
|||||||
# transform" + ganzer Regen-Undo-Record wird rollbacked.
|
# transform" + ganzer Regen-Undo-Record wird rollbacked.
|
||||||
if str(op_obj.Id) in skip_ids:
|
if str(op_obj.Id) in skip_ids:
|
||||||
continue
|
continue
|
||||||
|
# Pre-Transform Position bevorzugen — die liegt garantiert
|
||||||
|
# auf der alten Axis. Aktuelle (post-transform) Position kann
|
||||||
|
# bei Rotation weit weg liegen → ClosestPoint snappt zum
|
||||||
|
# falschen Endpunkt.
|
||||||
|
src_pos = None
|
||||||
|
if old_positions is not None:
|
||||||
|
src_pos = old_positions.get(op_meta["id"])
|
||||||
|
if src_pos is None:
|
||||||
pt_geom = op_obj.Geometry
|
pt_geom = op_obj.Geometry
|
||||||
if hasattr(pt_geom, 'Location'):
|
if hasattr(pt_geom, 'Location'):
|
||||||
cur_pos = pt_geom.Location
|
loc = pt_geom.Location
|
||||||
|
src_pos = (loc.X, loc.Y, loc.Z)
|
||||||
elif isinstance(pt_geom, rg.Point3d):
|
elif isinstance(pt_geom, rg.Point3d):
|
||||||
cur_pos = pt_geom
|
src_pos = (pt_geom.X, pt_geom.Y, pt_geom.Z)
|
||||||
else:
|
else:
|
||||||
continue
|
continue
|
||||||
# XY-only ClosestPoint — sonst zieht eine non-zero Z-Komponente
|
# XY-only ClosestPoint — sonst zieht eine non-zero Z-Komponente
|
||||||
# (Bruestungs-Hoehe) den Parameter bei kurvigen Wand-Achsen
|
# (Bruestungs-Hoehe) den Parameter bei kurvigen Wand-Achsen
|
||||||
# leicht weg von der „echten" Position.
|
# leicht weg von der „echten" Position.
|
||||||
ok_old, t_old = old_geom.ClosestPoint(
|
ok_old, t_old = old_geom.ClosestPoint(
|
||||||
rg.Point3d(cur_pos.X, cur_pos.Y, 0.0))
|
rg.Point3d(src_pos[0], src_pos[1], 0.0))
|
||||||
if not ok_old: continue
|
if not ok_old: continue
|
||||||
# Bogenlaenge auf alter Kurve bis t_old → relative Position
|
# Bogenlaenge auf alter Kurve bis t_old → relative Position
|
||||||
sub = rg.Interval(old_geom.Domain.Min, t_old)
|
sub = rg.Interval(old_geom.Domain.Min, t_old)
|
||||||
@@ -7174,7 +7223,9 @@ def _migrate_openings_to_new_axis(wall_id, old_geom, new_geom):
|
|||||||
bruest_z = float(bruest) if bruest not in (None, "") else 0.0
|
bruest_z = float(bruest) if bruest not in (None, "") else 0.0
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
bruest_z = 0.0
|
bruest_z = 0.0
|
||||||
new_pos = rg.Point3d(new_pos.X, new_pos.Y, bruest_z)
|
# Welt-Z = Wand-UK + Brueestung (Konvention: Punkt sitzt
|
||||||
|
# visuell auf Unterkante Oeffnung).
|
||||||
|
new_pos = rg.Point3d(new_pos.X, new_pos.Y, wall_uk + bruest_z)
|
||||||
doc.Objects.Replace(op_obj.Id, rg.Point(new_pos))
|
doc.Objects.Replace(op_obj.Id, rg.Point(new_pos))
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
print("[ELEMENTE] migrate one opening:", ex)
|
print("[ELEMENTE] migrate one opening:", ex)
|
||||||
@@ -8053,6 +8104,7 @@ def _on_command_end(sender, e):
|
|||||||
try: doc.EndUndoRecord(undo_serial)
|
try: doc.EndUndoRecord(undo_serial)
|
||||||
except Exception: pass
|
except Exception: pass
|
||||||
sc.sticky["_dossier_undo_serial"] = None
|
sc.sticky["_dossier_undo_serial"] = None
|
||||||
|
sc.sticky["_dossier_migrated_walls"] = None
|
||||||
b = sc.sticky.get("elemente_bridge")
|
b = sc.sticky.get("elemente_bridge")
|
||||||
if b is not None:
|
if b is not None:
|
||||||
try: b._send_state()
|
try: b._send_state()
|
||||||
@@ -8111,7 +8163,20 @@ def _on_command_end(sender, e):
|
|||||||
old_line = rg.LineCurve(
|
old_line = rg.LineCurve(
|
||||||
rg.Point3d(os[0], os[1], os[2]),
|
rg.Point3d(os[0], os[1], os[2]),
|
||||||
rg.Point3d(oe[0], oe[1], oe[2]))
|
rg.Point3d(oe[0], oe[1], oe[2]))
|
||||||
_migrate_openings_to_new_axis(m["id"], old_line, geom)
|
# Pre-Transform Oeffnungs-Positionen aus dem
|
||||||
|
# Snapshot ziehen — Migrate braucht sie um die
|
||||||
|
# Bogenlaengen-Position auf der ALTEN Axis zu
|
||||||
|
# finden (sonst bei Rotation falscher Snap).
|
||||||
|
old_op_positions = {}
|
||||||
|
for snap_id, snap_data in sources_snap.items():
|
||||||
|
if snap_data.get("type") != "oeffnung_point":
|
||||||
|
continue
|
||||||
|
if snap_data.get("oeff_parent") != m["id"]:
|
||||||
|
continue
|
||||||
|
pos = snap_data.get("pos")
|
||||||
|
if pos: old_op_positions[snap_id] = pos
|
||||||
|
_migrate_openings_to_new_axis(
|
||||||
|
m["id"], old_line, geom, old_op_positions)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
print("[ELEMENTE] post-cmd migrate:", ex)
|
print("[ELEMENTE] post-cmd migrate:", ex)
|
||||||
# Z-Drag detect + Brüstungs-Mitnahme. Constraint setzt
|
# Z-Drag detect + Brüstungs-Mitnahme. Constraint setzt
|
||||||
@@ -8183,6 +8248,7 @@ def _on_command_end(sender, e):
|
|||||||
try: doc.EndUndoRecord(undo_serial)
|
try: doc.EndUndoRecord(undo_serial)
|
||||||
except Exception: pass
|
except Exception: pass
|
||||||
sc.sticky["_dossier_undo_serial"] = None
|
sc.sticky["_dossier_undo_serial"] = None
|
||||||
|
sc.sticky["_dossier_migrated_walls"] = None
|
||||||
b = sc.sticky.get("elemente_bridge")
|
b = sc.sticky.get("elemente_bridge")
|
||||||
if b is not None:
|
if b is not None:
|
||||||
try: b._send_state()
|
try: b._send_state()
|
||||||
|
|||||||
Reference in New Issue
Block a user