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:
+79
-13
@@ -6839,7 +6839,16 @@ def _apply_oeffnung_constraint(new_obj, meta, old_obj=None):
|
||||
break
|
||||
|
||||
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:
|
||||
try:
|
||||
rc, t_old = parent_curve.ClosestPoint(
|
||||
@@ -7092,12 +7101,20 @@ def _on_object_replaced_body(sender, e):
|
||||
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
|
||||
veraendert wird. Mapping ueber relative Bogenlaenge: ein Oeffnungs-
|
||||
Punkt bei 30 % der alten Kurve sitzt nachher bei 30 % der neuen.
|
||||
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):
|
||||
return
|
||||
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()
|
||||
except Exception: 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
|
||||
# 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.
|
||||
if str(op_obj.Id) in skip_ids:
|
||||
continue
|
||||
pt_geom = op_obj.Geometry
|
||||
if hasattr(pt_geom, 'Location'):
|
||||
cur_pos = pt_geom.Location
|
||||
elif isinstance(pt_geom, rg.Point3d):
|
||||
cur_pos = pt_geom
|
||||
else:
|
||||
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
|
||||
if hasattr(pt_geom, 'Location'):
|
||||
loc = pt_geom.Location
|
||||
src_pos = (loc.X, loc.Y, loc.Z)
|
||||
elif isinstance(pt_geom, rg.Point3d):
|
||||
src_pos = (pt_geom.X, pt_geom.Y, pt_geom.Z)
|
||||
else:
|
||||
continue
|
||||
# XY-only ClosestPoint — sonst zieht eine non-zero Z-Komponente
|
||||
# (Bruestungs-Hoehe) den Parameter bei kurvigen Wand-Achsen
|
||||
# leicht weg von der „echten" Position.
|
||||
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
|
||||
# Bogenlaenge auf alter Kurve bis t_old → relative Position
|
||||
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
|
||||
except (ValueError, TypeError):
|
||||
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))
|
||||
except Exception as ex:
|
||||
print("[ELEMENTE] migrate one opening:", ex)
|
||||
@@ -8053,6 +8104,7 @@ def _on_command_end(sender, e):
|
||||
try: doc.EndUndoRecord(undo_serial)
|
||||
except Exception: pass
|
||||
sc.sticky["_dossier_undo_serial"] = None
|
||||
sc.sticky["_dossier_migrated_walls"] = None
|
||||
b = sc.sticky.get("elemente_bridge")
|
||||
if b is not None:
|
||||
try: b._send_state()
|
||||
@@ -8111,7 +8163,20 @@ def _on_command_end(sender, e):
|
||||
old_line = rg.LineCurve(
|
||||
rg.Point3d(os[0], os[1], os[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:
|
||||
print("[ELEMENTE] post-cmd migrate:", ex)
|
||||
# Z-Drag detect + Brüstungs-Mitnahme. Constraint setzt
|
||||
@@ -8183,6 +8248,7 @@ def _on_command_end(sender, e):
|
||||
try: doc.EndUndoRecord(undo_serial)
|
||||
except Exception: pass
|
||||
sc.sticky["_dossier_undo_serial"] = None
|
||||
sc.sticky["_dossier_migrated_walls"] = None
|
||||
b = sc.sticky.get("elemente_bridge")
|
||||
if b is not None:
|
||||
try: b._send_state()
|
||||
|
||||
Reference in New Issue
Block a user