Snapshot: Transform-Hierarchie + Brüstung-Konvention + Undo-Record
Funktionierender Stand der Move/Rotate-Pipeline mit Eltern-Kind-Cascade und sauberer Brüstung-Semantik: - Pure-Translate hierarchisch: nur Sources mit echtem Delta + ihre Kinder (Öffnungen → Wand) folgen mit. Wand folgt NICHT der Öffnung. - Orphan-Detection: Öffnung ohne mitbewegter Eltern-Wand → Regen-Fallback (sonst bleibt Cutout am alten Ort im Wand-Brep). - Brüstung = relativ zur Wand-UK (Archicad/Revit-Konvention). Bei Wand- Z-Drag wird UK_OVER angepasst, Brüstung bleibt; Öffnungs-Punkt wandert via Snapshot+Delta mit. Keine Doppel-Addition mehr. - Opening-Punkt wird beim Erzeugen direkt auf UK+brüstung platziert (sonst Brüstung-Drop beim ersten Move). - Undo-Record umschliesst Rhinos Move + unseren Regen in einem Cmd+Z- Schritt → keine doppelten Elemente nach Undo. - RedrawEnabled-Suppression event-getriggert (erst beim ersten Replace- Event nach User-Klick) → Rubber-Band + Drag-Vorschau bleiben sichtbar. - _Undo/_Redo: Event-Handler komplett aussetzen → kein Regen-Storm. - Gestaltung-Listener während User-Transform + Regen stumm, danach einmaliger Selection-Refresh. Enthält Debug-Logs in _apply_wand_z_drag_constraint + Wand-Regen für offenen Bug: bei gemeinsamer Z-Verschiebung (Wand+Fenster+Tür) landen Öffnungen manchmal über der Wand — UK_OVER scheint nicht durchzukommen. Logs sollen das eingrenzen. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+271
-53
@@ -3922,6 +3922,8 @@ def _regenerate_element_body(doc, element_id, src_obj, meta, geom, geschoss_name
|
|||||||
if meta["type"] == "wand_axis":
|
if meta["type"] == "wand_axis":
|
||||||
uk, ok = _resolve_uk_ok(doc, meta["geschoss"],
|
uk, ok = _resolve_uk_ok(doc, meta["geschoss"],
|
||||||
meta["uk_override"], meta["ok_override"])
|
meta["uk_override"], meta["ok_override"])
|
||||||
|
print("[ELEMENTE] regen wand {}: uk={:.3f} ok={:.3f} (uk_over='{}' ok_over='{}')".format(
|
||||||
|
element_id, uk, ok, meta.get("uk_override", ""), meta.get("ok_override", "")))
|
||||||
# Wand-Verbindungen: Miter-Linien aus Nachbarwand-Joints (Corner + T).
|
# Wand-Verbindungen: Miter-Linien aus Nachbarwand-Joints (Corner + T).
|
||||||
miter_start = None
|
miter_start = None
|
||||||
miter_end = None
|
miter_end = None
|
||||||
@@ -5580,7 +5582,19 @@ class ElementeBridge(panel_base.BaseBridge):
|
|||||||
oeff_sims_in=simsi_def,
|
oeff_sims_in=simsi_def,
|
||||||
oeff_glas=glas_def,
|
oeff_glas=glas_def,
|
||||||
oeff_referenz=referenz_def)
|
oeff_referenz=referenz_def)
|
||||||
new_id = doc.Objects.AddPoint(on_axis, attrs)
|
# Oeffnungs-Punkt auf UK+Brueestung-Hoehe platzieren (= visuell auf
|
||||||
|
# Unterkante Oeffnung). Constraint vergleicht spaeter pt.Z mit
|
||||||
|
# UK+brueest — wenn der Punkt am axis.Z=0 saesse, wuerde der erste
|
||||||
|
# Move die Brueest auf 0 droppen. Hier UK auflösen (Geschoss-OKFF +
|
||||||
|
# ggf. Override) und Punkt direkt auf richtige Welt-Z setzen.
|
||||||
|
try:
|
||||||
|
wall_uk, _ = _resolve_uk_ok(doc, geschoss,
|
||||||
|
wall_meta.get("uk_override", ""),
|
||||||
|
wall_meta.get("ok_override", ""))
|
||||||
|
except Exception:
|
||||||
|
wall_uk = 0.0
|
||||||
|
pt_at_brueest = rg.Point3d(on_axis.X, on_axis.Y, wall_uk + float(brueest))
|
||||||
|
new_id = doc.Objects.AddPoint(pt_at_brueest, attrs)
|
||||||
if new_id == System.Guid.Empty:
|
if new_id == System.Guid.Empty:
|
||||||
print("[ELEMENTE] AddPoint fehlgeschlagen"); return
|
print("[ELEMENTE] AddPoint fehlgeschlagen"); return
|
||||||
|
|
||||||
@@ -6736,10 +6750,21 @@ def _apply_wand_z_drag_constraint(new_obj, meta):
|
|||||||
meta["uk_override"], meta["ok_override"])
|
meta["uk_override"], meta["ok_override"])
|
||||||
new_uk = uk_cur + delta
|
new_uk = uk_cur + delta
|
||||||
new_ok = ok_cur + delta
|
new_ok = ok_cur + delta
|
||||||
|
print("[ELEMENTE] wand z-drag: uk_cur={:.3f} ok_cur={:.3f} new_uk={:.3f} new_ok={:.3f} (meta uk_over='{}' ok_over='{}')".format(
|
||||||
|
uk_cur, ok_cur, new_uk, new_ok, meta.get("uk_override", ""), meta.get("ok_override", "")))
|
||||||
attrs = new_obj.Attributes.Duplicate()
|
attrs = new_obj.Attributes.Duplicate()
|
||||||
attrs.SetUserString(_KEY_UK_OVER, "{:.6f}".format(new_uk))
|
attrs.SetUserString(_KEY_UK_OVER, "{:.6f}".format(new_uk))
|
||||||
attrs.SetUserString(_KEY_OK_OVER, "{:.6f}".format(new_ok))
|
attrs.SetUserString(_KEY_OK_OVER, "{:.6f}".format(new_ok))
|
||||||
doc.Objects.ModifyAttributes(new_obj.Id, attrs, True)
|
mod_ok = doc.Objects.ModifyAttributes(new_obj.Id, attrs, True)
|
||||||
|
# Verifikation: UK_OVER wirklich in Doc geschrieben?
|
||||||
|
verify = doc.Objects.FindId(new_obj.Id)
|
||||||
|
if verify is not None:
|
||||||
|
actual_uk = verify.Attributes.GetUserString(_KEY_UK_OVER) or "<empty>"
|
||||||
|
actual_ok = verify.Attributes.GetUserString(_KEY_OK_OVER) or "<empty>"
|
||||||
|
print("[ELEMENTE] wand z-drag ModifyAttributes returned={} → stored uk_over='{}' ok_over='{}'".format(
|
||||||
|
mod_ok, actual_uk, actual_ok))
|
||||||
|
else:
|
||||||
|
print("[ELEMENTE] wand z-drag verify: FindId returned None!")
|
||||||
# Curve auf Z=0 fixen. LineCurve: explizit beide Endpunkte (auch bei
|
# Curve auf Z=0 fixen. LineCurve: explizit beide Endpunkte (auch bei
|
||||||
# einzelnem End-Grip-Drag). Andere Curves: ueber Translation (akzeptiert
|
# einzelnem End-Grip-Drag). Andere Curves: ueber Translation (akzeptiert
|
||||||
# leichten Schraeg bei End-Grip-Drag, gleicht sich beim naechsten
|
# leichten Schraeg bei End-Grip-Drag, gleicht sich beim naechsten
|
||||||
@@ -6801,6 +6826,7 @@ def _apply_oeffnung_constraint(new_obj, meta, old_obj=None):
|
|||||||
|
|
||||||
parent_id = meta.get("oeff_parent")
|
parent_id = meta.get("oeff_parent")
|
||||||
parent_curve = None
|
parent_curve = None
|
||||||
|
parent_meta = None
|
||||||
doc = Rhino.RhinoDoc.ActiveDoc
|
doc = Rhino.RhinoDoc.ActiveDoc
|
||||||
if doc is not None and parent_id:
|
if doc is not None and parent_id:
|
||||||
for obj in doc.Objects:
|
for obj in doc.Objects:
|
||||||
@@ -6809,6 +6835,7 @@ def _apply_oeffnung_constraint(new_obj, meta, old_obj=None):
|
|||||||
cg = obj.Geometry
|
cg = obj.Geometry
|
||||||
if isinstance(cg, rg.Curve):
|
if isinstance(cg, rg.Curve):
|
||||||
parent_curve = cg
|
parent_curve = cg
|
||||||
|
parent_meta = m
|
||||||
break
|
break
|
||||||
|
|
||||||
target_x, target_y = pt_new.X, pt_new.Y
|
target_x, target_y = pt_new.X, pt_new.Y
|
||||||
@@ -6850,17 +6877,33 @@ def _apply_oeffnung_constraint(new_obj, meta, old_obj=None):
|
|||||||
cur_bruest_val = float(cur_bruest) if cur_bruest not in (None, "") else 0.9
|
cur_bruest_val = float(cur_bruest) if cur_bruest not in (None, "") else 0.9
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
cur_bruest_val = 0.9
|
cur_bruest_val = 0.9
|
||||||
# Z-Delta = Drag in Z. Der Punkt SITZT visuell auf Bruestung-Hoehe
|
# Z-Delta gegen den ERWARTETEN Welt-Z des Punktes = Wand-UK + Brueest.
|
||||||
# (siehe Geometry-Schreibung unten), daher pt_old.Z ~= alte Bruestung.
|
# Bruestung ist relativ zur Wand-UK gespeichert. Wenn die Wand
|
||||||
delta_z = (pt_new.Z - pt_old.Z) if pt_old is not None else (pt_new.Z - cur_bruest_val)
|
# hochgezogen wurde (UK_OVER += z_delta) und der Wand-Loop den
|
||||||
|
# Oeffnungs-Punkt um z_delta translatet hat, sitzt der Punkt jetzt auf
|
||||||
|
# `new_UK + cur_brueest` = `expected_pt_z`. delta_z = 0 → kein
|
||||||
|
# Bruestungs-Update (gut so, sonst doppelt). Wenn der User nur den
|
||||||
|
# Punkt allein vertikal gezogen hat (Brueestung-Drag), divergiert
|
||||||
|
# pt_new.Z vom expected_pt_z → delta_z entspricht der echten User-
|
||||||
|
# Eingabe → Bruestung wird angepasst.
|
||||||
|
wall_uk = 0.0
|
||||||
|
if parent_meta is not None:
|
||||||
|
try:
|
||||||
|
wall_uk, _ = _resolve_uk_ok(doc, parent_meta["geschoss"],
|
||||||
|
parent_meta["uk_override"],
|
||||||
|
parent_meta["ok_override"])
|
||||||
|
except Exception:
|
||||||
|
wall_uk = 0.0
|
||||||
|
expected_pt_z = wall_uk + cur_bruest_val
|
||||||
|
delta_z = pt_new.Z - expected_pt_z
|
||||||
new_bruest = cur_bruest_val
|
new_bruest = cur_bruest_val
|
||||||
if abs(delta_z) >= 1e-6:
|
if abs(delta_z) >= 1e-6:
|
||||||
new_bruest = max(0.0, cur_bruest_val + delta_z)
|
new_bruest = max(0.0, cur_bruest_val + delta_z)
|
||||||
|
|
||||||
# Punkt visuell auf Bruestungs-Hoehe (= Unterkante Oeffnung), nicht auf 0.
|
# Punkt visuell auf der Unterkante der Oeffnung in Welt-Z platzieren =
|
||||||
# So sieht der User wo die Oeffnung beginnt + Z-Drag-Delta entspricht
|
# Wand-UK + Brueest. So sieht der User wo die Oeffnung beginnt, auch
|
||||||
# direkt der Bruestungsaenderung.
|
# wenn die Wand auf einem hoeheren Geschoss steht.
|
||||||
target_z = new_bruest
|
target_z = wall_uk + new_bruest
|
||||||
geom_changed = not (
|
geom_changed = not (
|
||||||
abs(target_x - pt_new.X) < 1e-9
|
abs(target_x - pt_new.X) < 1e-9
|
||||||
and abs(target_y - pt_new.Y) < 1e-9
|
and abs(target_y - pt_new.Y) < 1e-9
|
||||||
@@ -6901,10 +6944,15 @@ def _on_object_replaced(sender, e):
|
|||||||
"""
|
"""
|
||||||
if sc.sticky.get(_REGEN_BUSY): return
|
if sc.sticky.get(_REGEN_BUSY): return
|
||||||
# Wenn ein User-Transform-Command (_Move/_Rotate/etc.) aktiv ist: GAR
|
# Wenn ein User-Transform-Command (_Move/_Rotate/etc.) aktiv ist: GAR
|
||||||
# NICHTS hier tun. Rhinos Move soll konfliktfrei durchlaufen. Nach
|
# NICHTS hier tun (Rhinos Move soll konfliktfrei durchlaufen). Erstes
|
||||||
# CommandEnd vergleichen wir Snapshot vs. aktuellen State + machen den
|
# Event = User hat geklickt → Redraw ab jetzt suppressen, sonst Mismatch-
|
||||||
# ganzen Update in einem konfliktfreien Batch.
|
# Frame zwischen Rhinos Auto-Redraw und unserem Regen.
|
||||||
if sc.sticky.get(_UT_ACTIVE_KEY): return
|
if sc.sticky.get(_UT_ACTIVE_KEY):
|
||||||
|
_suppress_redraw_until_cmd_end()
|
||||||
|
return
|
||||||
|
# Undo/Redo: Rhino restored den Zustand → wir machen NICHTS, sonst
|
||||||
|
# Regen-Storm fuer jedes restored Object.
|
||||||
|
if sc.sticky.get(_UNDO_ACTIVE_KEY): return
|
||||||
doc = Rhino.RhinoDoc.ActiveDoc
|
doc = Rhino.RhinoDoc.ActiveDoc
|
||||||
# Snapshot der aktuell selektierten IDs — damit Migrate die Objekte
|
# Snapshot der aktuell selektierten IDs — damit Migrate die Objekte
|
||||||
# skippen kann die Rhinos Move/Rotate gerade transformiert (sonst
|
# skippen kann die Rhinos Move/Rotate gerade transformiert (sonst
|
||||||
@@ -7159,6 +7207,14 @@ def _on_object_added(sender, e):
|
|||||||
neue UUID, Volume-Duplikate werden geloescht (Regen baut das neue
|
neue UUID, Volume-Duplikate werden geloescht (Regen baut das neue
|
||||||
Volumen am richtigen Ort)."""
|
Volumen am richtigen Ort)."""
|
||||||
if sc.sticky.get(_REGEN_BUSY): return
|
if sc.sticky.get(_REGEN_BUSY): return
|
||||||
|
# Waehrend Move/Rotate/Mirror/Scale: Rhino feuert intern Delete+Add fuer
|
||||||
|
# jedes transformierte Objekt. CommandEnd uebernimmt die Re-Sync —
|
||||||
|
# diese Events ignorieren, sonst laeuft die Regen-Pipeline trotz
|
||||||
|
# Pure-Translate-Skip.
|
||||||
|
if sc.sticky.get(_UT_ACTIVE_KEY):
|
||||||
|
_suppress_redraw_until_cmd_end()
|
||||||
|
return
|
||||||
|
if sc.sticky.get(_UNDO_ACTIVE_KEY): return
|
||||||
try:
|
try:
|
||||||
new_obj = e.TheObject
|
new_obj = e.TheObject
|
||||||
meta = _read_meta(new_obj)
|
meta = _read_meta(new_obj)
|
||||||
@@ -7254,6 +7310,13 @@ def _on_object_deleted(sender, e):
|
|||||||
wenn die Source mit gleicher ID zurueckkommt (= Transform, kein User-
|
wenn die Source mit gleicher ID zurueckkommt (= Transform, kein User-
|
||||||
Delete).
|
Delete).
|
||||||
"""
|
"""
|
||||||
|
# Waehrend Move/Rotate/Mirror/Scale: CommandEnd-Pfad uebernimmt das
|
||||||
|
# Re-Sync. Sonst queued der Delete-Event ueberfluessige Regen-Calls die
|
||||||
|
# den Pure-Translate-Skip wieder zunichtemachen.
|
||||||
|
if sc.sticky.get(_UT_ACTIVE_KEY):
|
||||||
|
_suppress_redraw_until_cmd_end()
|
||||||
|
return
|
||||||
|
if sc.sticky.get(_UNDO_ACTIVE_KEY): return
|
||||||
try:
|
try:
|
||||||
obj = e.TheObject
|
obj = e.TheObject
|
||||||
meta = _read_meta(obj)
|
meta = _read_meta(obj)
|
||||||
@@ -7688,8 +7751,15 @@ _USER_TRANSFORM_CMDS = frozenset((
|
|||||||
"Drag", "Gumball", "Orient", "Orient3Pt", "RemapCPlane", "Transform",
|
"Drag", "Gumball", "Orient", "Orient3Pt", "RemapCPlane", "Transform",
|
||||||
))
|
))
|
||||||
|
|
||||||
|
# Undo/Redo: Rhino restored Objekte aus dem Undo-Stack → feuert Add/Delete-
|
||||||
|
# Events fuer ALLE betroffenen Objekte. Unsere Handler wuerden fuer jedes
|
||||||
|
# einen Regen queuen → Storm. Wir suppressen die Handler komplett; Undo hat
|
||||||
|
# den Zustand schon konsistent wiederhergestellt, kein Regen noetig.
|
||||||
|
_USER_UNDO_CMDS = frozenset(("Undo", "Redo"))
|
||||||
|
|
||||||
_UT_ACTIVE_KEY = "_dossier_user_transform_active"
|
_UT_ACTIVE_KEY = "_dossier_user_transform_active"
|
||||||
_UT_SNAPSHOT_KEY = "_dossier_pre_transform_snapshot"
|
_UT_SNAPSHOT_KEY = "_dossier_pre_transform_snapshot"
|
||||||
|
_UNDO_ACTIVE_KEY = "_dossier_undo_active"
|
||||||
|
|
||||||
|
|
||||||
def _snapshot_source_positions(doc):
|
def _snapshot_source_positions(doc):
|
||||||
@@ -7707,12 +7777,16 @@ def _snapshot_source_positions(doc):
|
|||||||
t = m.get("type")
|
t = m.get("type")
|
||||||
geom = obj.Geometry
|
geom = obj.Geometry
|
||||||
if t in SOURCE_TYPES:
|
if t in SOURCE_TYPES:
|
||||||
|
parent = m.get("oeff_parent") or ""
|
||||||
if hasattr(geom, "Location"):
|
if hasattr(geom, "Location"):
|
||||||
p = geom.Location
|
p = geom.Location
|
||||||
snap["sources"][m["id"]] = {"type": t, "pos": (p.X, p.Y, p.Z)}
|
snap["sources"][m["id"]] = {"type": t,
|
||||||
|
"oeff_parent": parent,
|
||||||
|
"pos": (p.X, p.Y, p.Z)}
|
||||||
elif isinstance(geom, rg.Curve):
|
elif isinstance(geom, rg.Curve):
|
||||||
s = geom.PointAtStart; e = geom.PointAtEnd
|
s = geom.PointAtStart; e = geom.PointAtEnd
|
||||||
snap["sources"][m["id"]] = {"type": t,
|
snap["sources"][m["id"]] = {"type": t,
|
||||||
|
"oeff_parent": parent,
|
||||||
"start": (s.X, s.Y, s.Z),
|
"start": (s.X, s.Y, s.Z),
|
||||||
"end": (e.X, e.Y, e.Z)}
|
"end": (e.X, e.Y, e.Z)}
|
||||||
elif t in VOLUME_TYPES:
|
elif t in VOLUME_TYPES:
|
||||||
@@ -7728,25 +7802,96 @@ def _snapshot_source_positions(doc):
|
|||||||
return snap
|
return snap
|
||||||
|
|
||||||
|
|
||||||
|
def _suppress_redraw_until_cmd_end():
|
||||||
|
"""Schaltet RedrawEnabled erst auf False sobald das ERSTE Object-Event
|
||||||
|
waehrend eines User-Transform-Commands feuert. Damit bleiben Rubber-
|
||||||
|
Band-Linie und Drag-Vorschau waehrend des Pickings sichtbar (Picking
|
||||||
|
feuert keine Object-Events), aber Rhinos automatischer Post-Move-
|
||||||
|
Redraw (kommt nach dem Klick, direkt nach den Replace-Events) wird
|
||||||
|
unterdrueckt. Wird im selben Command nur einmal aktiv."""
|
||||||
|
if sc.sticky.get("_dossier_cmd_redraw_suppressed"): return
|
||||||
|
doc = Rhino.RhinoDoc.ActiveDoc
|
||||||
|
if doc is None: return
|
||||||
|
try:
|
||||||
|
sc.sticky["_dossier_cmd_redraw_prev"] = bool(doc.Views.RedrawEnabled)
|
||||||
|
doc.Views.RedrawEnabled = False
|
||||||
|
sc.sticky["_dossier_cmd_redraw_suppressed"] = True
|
||||||
|
except Exception as ex:
|
||||||
|
print("[ELEMENTE] suppress redraw:", ex)
|
||||||
|
|
||||||
|
|
||||||
def _on_command_begin(sender, e):
|
def _on_command_begin(sender, e):
|
||||||
try:
|
try:
|
||||||
name = getattr(e, "CommandEnglishName", "") or ""
|
name = getattr(e, "CommandEnglishName", "") or ""
|
||||||
except Exception: name = ""
|
except Exception: name = ""
|
||||||
if name not in _USER_TRANSFORM_CMDS: return
|
|
||||||
doc = Rhino.RhinoDoc.ActiveDoc
|
doc = Rhino.RhinoDoc.ActiveDoc
|
||||||
if doc is None: return
|
if doc is None: return
|
||||||
|
# Undo/Redo: nur Flag setzen, KEIN Snapshot, KEIN Redraw-Suppress —
|
||||||
|
# Rhinos Undo verwaltet RedrawEnabled selbst. Event-Handler ignorieren
|
||||||
|
# waehrend dieser Phase alle Add/Delete/Replace-Events → kein Regen-
|
||||||
|
# Storm.
|
||||||
|
if name in _USER_UNDO_CMDS:
|
||||||
|
sc.sticky[_UNDO_ACTIVE_KEY] = name
|
||||||
|
return
|
||||||
|
if name not in _USER_TRANSFORM_CMDS: return
|
||||||
sc.sticky[_UT_SNAPSHOT_KEY] = _snapshot_source_positions(doc)
|
sc.sticky[_UT_SNAPSHOT_KEY] = _snapshot_source_positions(doc)
|
||||||
sc.sticky[_UT_ACTIVE_KEY] = name
|
sc.sticky[_UT_ACTIVE_KEY] = name
|
||||||
|
# RedrawEnabled bleibt HIER auf True. Wird erst beim ersten Object-Event
|
||||||
|
# (= nach dem Klick) via `_suppress_redraw_until_cmd_end` ausgeschaltet.
|
||||||
|
# Rubber-Band-Linie + Drag-Vorschau bleiben dadurch wahrend Picking
|
||||||
|
# sichtbar.
|
||||||
|
# Undo-Record umschliesst Rhinos Move + unseren Regen in EINEM Undo-
|
||||||
|
# Schritt. Sonst macht jedes Delete/AddBrep eine eigene Undo-Entry und
|
||||||
|
# Cmd+Z bringt nur halbe Wand zurueck → Duplikate.
|
||||||
|
try:
|
||||||
|
serial = doc.BeginUndoRecord("Element-Transform")
|
||||||
|
sc.sticky["_dossier_undo_serial"] = serial
|
||||||
|
except Exception as ex:
|
||||||
|
print("[ELEMENTE] cmd-begin undo record:", ex)
|
||||||
|
sc.sticky["_dossier_undo_serial"] = None
|
||||||
|
|
||||||
|
|
||||||
def _on_command_end(sender, e):
|
def _on_command_end(sender, e):
|
||||||
|
# Undo/Redo abschliessen: nur Flag clearen, kein Regen + ein Selection-
|
||||||
|
# Refresh fuers Gestaltung-Panel (Listener waren waehrend Undo aus).
|
||||||
|
if sc.sticky.get(_UNDO_ACTIVE_KEY):
|
||||||
|
sc.sticky[_UNDO_ACTIVE_KEY] = None
|
||||||
|
gb = sc.sticky.get("gestaltung_bridge")
|
||||||
|
if gb is not None:
|
||||||
|
try: gb._send_selection()
|
||||||
|
except Exception: pass
|
||||||
|
b = sc.sticky.get("elemente_bridge")
|
||||||
|
if b is not None:
|
||||||
|
try: b._send_state()
|
||||||
|
except Exception: pass
|
||||||
|
return
|
||||||
name = sc.sticky.get(_UT_ACTIVE_KEY)
|
name = sc.sticky.get(_UT_ACTIVE_KEY)
|
||||||
if not name: return
|
if not name: return
|
||||||
sc.sticky[_UT_ACTIVE_KEY] = None
|
# _UT_ACTIVE_KEY bleibt gesetzt bis am Ende der Funktion — sonst feuern
|
||||||
|
# gestaltungs Listener auf die Replace-Events die wir hier selber
|
||||||
|
# erzeugen (Pure-Translate translates Volumen via Replace; Regen-Pfad
|
||||||
|
# ersetzt Sub-Volumen). Cleanup im finally-Block am Ende.
|
||||||
snapshot = sc.sticky.get(_UT_SNAPSHOT_KEY) or {}
|
snapshot = sc.sticky.get(_UT_SNAPSHOT_KEY) or {}
|
||||||
sc.sticky[_UT_SNAPSHOT_KEY] = None
|
sc.sticky[_UT_SNAPSHOT_KEY] = None
|
||||||
doc = Rhino.RhinoDoc.ActiveDoc
|
doc = Rhino.RhinoDoc.ActiveDoc
|
||||||
if doc is None: return
|
if doc is None:
|
||||||
|
sc.sticky[_UT_ACTIVE_KEY] = None
|
||||||
|
sc.sticky["_dossier_cmd_redraw_suppressed"] = None
|
||||||
|
sc.sticky["_dossier_cmd_redraw_prev"] = None
|
||||||
|
sc.sticky["_dossier_undo_serial"] = None
|
||||||
|
return
|
||||||
|
|
||||||
|
# RedrawEnabled wurde idR schon beim ersten Object-Event nach dem
|
||||||
|
# User-Klick auf False gesetzt (`_suppress_redraw_until_cmd_end`). Den
|
||||||
|
# gemerkten prev-Wert lesen. Falls kein Event gefeuert hat (z.B. Move
|
||||||
|
# ohne tatsaechliche Aenderung), suppressen wir jetzt selber.
|
||||||
|
if sc.sticky.get("_dossier_cmd_redraw_suppressed"):
|
||||||
|
prev_redraw_enabled = sc.sticky.get("_dossier_cmd_redraw_prev", True)
|
||||||
|
sc.sticky["_dossier_cmd_redraw_suppressed"] = None
|
||||||
|
sc.sticky["_dossier_cmd_redraw_prev"] = None
|
||||||
|
else:
|
||||||
|
prev_redraw_enabled = doc.Views.RedrawEnabled
|
||||||
|
doc.Views.RedrawEnabled = False
|
||||||
|
|
||||||
sources_snap = snapshot.get("sources", {}) if isinstance(snapshot, dict) else {}
|
sources_snap = snapshot.get("sources", {}) if isinstance(snapshot, dict) else {}
|
||||||
volumes_snap = snapshot.get("volumes", {}) if isinstance(snapshot, dict) else {}
|
volumes_snap = snapshot.get("volumes", {}) if isinstance(snapshot, dict) else {}
|
||||||
@@ -7794,10 +7939,29 @@ def _on_command_end(sender, e):
|
|||||||
deltas[m["id"]] = d
|
deltas[m["id"]] = d
|
||||||
except Exception: pass
|
except Exception: pass
|
||||||
|
|
||||||
|
# IDs der tatsaechlich bewegten Sources (Delta != 0). `deltas` enthaelt
|
||||||
|
# ALLE Sources der Szene mit ihrem Snapshot-Delta — unbewegte haben
|
||||||
|
# Delta=(0,0,0). Wir brauchen aber die *bewegten*, um Hierarchie-
|
||||||
|
# Entscheidungen zu treffen (Eltern→Kind-Cascade).
|
||||||
|
moved_ids = {eid for eid, d in deltas.items()
|
||||||
|
if abs(d[0]) > 1e-6 or abs(d[1]) > 1e-6 or abs(d[2]) > 1e-6}
|
||||||
|
|
||||||
|
# Orphan-Oeffnung erkennen: bewegte Oeffnung, deren Eltern-Wand NICHT
|
||||||
|
# mitbewegt wurde. In diesem Fall ist Pure-Translate verboten — die
|
||||||
|
# Wand-Aussparung (Boolean-Difference-Loch im Wand-Brep) bliebe am alten
|
||||||
|
# Ort. Nur ein voller Wand-Regen rebuildet das Loch an der neuen Pos.
|
||||||
|
orphan_opening = False
|
||||||
|
for eid in moved_ids:
|
||||||
|
old = sources_snap.get(eid)
|
||||||
|
if old and old.get("type") == "oeffnung_point":
|
||||||
|
parent = old.get("oeff_parent")
|
||||||
|
if parent and parent not in moved_ids:
|
||||||
|
orphan_opening = True
|
||||||
|
break
|
||||||
|
|
||||||
pure_delta = None
|
pure_delta = None
|
||||||
if not abort_pure:
|
if not abort_pure and not orphan_opening:
|
||||||
moved = [d for d in deltas.values()
|
moved = [d for eid, d in deltas.items() if eid in moved_ids]
|
||||||
if abs(d[0]) > 1e-6 or abs(d[1]) > 1e-6 or abs(d[2]) > 1e-6]
|
|
||||||
if moved:
|
if moved:
|
||||||
# Alle bewegten Sources muessen denselben Vektor haben
|
# Alle bewegten Sources muessen denselben Vektor haben
|
||||||
first = moved[0]
|
first = moved[0]
|
||||||
@@ -7809,24 +7973,45 @@ def _on_command_end(sender, e):
|
|||||||
has_z_drag = abs(first[2]) > 1e-6
|
has_z_drag = abs(first[2]) > 1e-6
|
||||||
if same and not has_z_drag:
|
if same and not has_z_drag:
|
||||||
pure_delta = first
|
pure_delta = first
|
||||||
|
else:
|
||||||
|
print("[ELEMENTE] no pure-translate: same={} has_z={} n_moved={}".format(
|
||||||
|
same, has_z_drag, len(moved)))
|
||||||
|
elif abort_pure:
|
||||||
|
print("[ELEMENTE] no pure-translate: end-grip/rotate/scale detected")
|
||||||
|
elif orphan_opening:
|
||||||
|
print("[ELEMENTE] no pure-translate: opening moved without parent wall (cutout muss regen)")
|
||||||
|
|
||||||
if pure_delta is not None:
|
if pure_delta is not None:
|
||||||
# PURE-TRANSLATE PFAD: nur Geometries translaten die nicht schon vom
|
# PURE-TRANSLATE PFAD: nur Geometries translaten die nicht schon vom
|
||||||
# User-Move transformed wurden. Keine Brep-Regeneration, kein
|
# User-Move transformed wurden. Keine Brep-Regeneration, kein
|
||||||
# Boolean-Diff. → instant feedback.
|
# Boolean-Diff. → instant feedback.
|
||||||
|
print("[ELEMENTE] pure-translate: delta=({:.3f}, {:.3f}, {:.3f})".format(*pure_delta))
|
||||||
vec = rg.Vector3d(*pure_delta)
|
vec = rg.Vector3d(*pure_delta)
|
||||||
|
|
||||||
|
# Eltern→Kind-Cascade: wenn eine Wand bewegt wurde, muessen ihre
|
||||||
|
# Oeffnungen mit. Aber NICHT umgekehrt: wenn nur eine Oeffnung
|
||||||
|
# bewegt wird, bleibt die Wand stehen.
|
||||||
|
def _should_follow(m):
|
||||||
|
eid = m.get("id")
|
||||||
|
if eid in moved_ids: return True
|
||||||
|
# Oeffnung ihrer Eltern-Wand folgen lassen
|
||||||
|
parent = m.get("oeff_parent")
|
||||||
|
if parent and parent in moved_ids: return True
|
||||||
|
return False
|
||||||
|
|
||||||
_was_busy = sc.sticky.get(_REGEN_BUSY, False)
|
_was_busy = sc.sticky.get(_REGEN_BUSY, False)
|
||||||
sc.sticky[_REGEN_BUSY] = True
|
sc.sticky[_REGEN_BUSY] = True
|
||||||
prev_redraw = doc.Views.RedrawEnabled
|
# RedrawEnabled wurde schon in _on_command_begin auf False gesetzt
|
||||||
doc.Views.RedrawEnabled = False
|
|
||||||
try:
|
try:
|
||||||
for obj in list(doc.Objects):
|
for obj in list(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")
|
||||||
# Sources die nicht in deltas waren (= unbewegt) auch translaten
|
if not _should_follow(m): continue
|
||||||
if t in SOURCE_TYPES and m["id"] not in deltas:
|
# Sources die nicht bewegt wurden (= Delta=0) translaten
|
||||||
|
# — nur Oeffnungs-Sources via _should_follow erlaubt.
|
||||||
|
if t in SOURCE_TYPES and m["id"] not in moved_ids:
|
||||||
new_geom = obj.Geometry.Duplicate()
|
new_geom = obj.Geometry.Duplicate()
|
||||||
new_geom.Translate(vec)
|
new_geom.Translate(vec)
|
||||||
doc.Objects.Replace(obj.Id, new_geom)
|
doc.Objects.Replace(obj.Id, new_geom)
|
||||||
@@ -7854,14 +8039,30 @@ def _on_command_end(sender, e):
|
|||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
print("[ELEMENTE] pure-translate:", ex)
|
print("[ELEMENTE] pure-translate:", ex)
|
||||||
finally:
|
finally:
|
||||||
doc.Views.RedrawEnabled = prev_redraw
|
|
||||||
sc.sticky[_REGEN_BUSY] = _was_busy
|
sc.sticky[_REGEN_BUSY] = _was_busy
|
||||||
|
doc.Views.RedrawEnabled = prev_redraw_enabled
|
||||||
try: doc.Views.Redraw()
|
try: doc.Views.Redraw()
|
||||||
except Exception: pass
|
except Exception: pass
|
||||||
|
# Flag erst HIER cleren, nachdem alle Replace-Events durch sind —
|
||||||
|
# sonst feuert gestaltung.on_replace pro Volume.
|
||||||
|
sc.sticky[_UT_ACTIVE_KEY] = None
|
||||||
|
# Undo-Record schliessen — alles seit BeginUndoRecord landet in
|
||||||
|
# einem einzelnen Cmd+Z-Schritt.
|
||||||
|
undo_serial = sc.sticky.get("_dossier_undo_serial")
|
||||||
|
if undo_serial:
|
||||||
|
try: doc.EndUndoRecord(undo_serial)
|
||||||
|
except Exception: pass
|
||||||
|
sc.sticky["_dossier_undo_serial"] = 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()
|
||||||
except Exception: pass
|
except Exception: pass
|
||||||
|
# Gestaltung-Panel einmalig nachziehen — Listener waren waehrend
|
||||||
|
# des User-Transform-Commands suspendiert.
|
||||||
|
gb = sc.sticky.get("gestaltung_bridge")
|
||||||
|
if gb is not None:
|
||||||
|
try: gb._send_selection()
|
||||||
|
except Exception: pass
|
||||||
return
|
return
|
||||||
|
|
||||||
# ─── Regulärer Pfad: Constraints + Migrate + Regen (existing flow) ──────
|
# ─── Regulärer Pfad: Constraints + Migrate + Regen (existing flow) ──────
|
||||||
@@ -7877,11 +8078,10 @@ def _on_command_end(sender, e):
|
|||||||
# Regen ausloesen → mehrere Regens pro Wand. Wir machen am Schluss EINEN
|
# Regen ausloesen → mehrere Regens pro Wand. Wir machen am Schluss EINEN
|
||||||
# Regen pro affected_wall — viel schneller bei mehreren Oeffnungen.
|
# Regen pro affected_wall — viel schneller bei mehreren Oeffnungen.
|
||||||
sc.sticky["_dossier_skip_sync_regen"] = True
|
sc.sticky["_dossier_skip_sync_regen"] = True
|
||||||
# Display-Updates komplett suppressen waehrend der Batch — Rhino zeichnet
|
# RedrawEnabled wurde schon in _on_command_begin auf False gesetzt —
|
||||||
# sonst nach jedem Brep-Add/Replace neu, was bei mehreren Sub-Volumen
|
# damit unterdruecken wir auch Rhinos automatischen Post-Move-Redraw
|
||||||
# sichtbares „Aufbauen" verursacht. Ein einziger Redraw am Ende reicht.
|
# (sonst kurzer Mismatch-Frame: Oeffnung an neuer Pos, Wand-Loch noch
|
||||||
prev_redraw = doc.Views.RedrawEnabled
|
# an alter Pos).
|
||||||
doc.Views.RedrawEnabled = False
|
|
||||||
try:
|
try:
|
||||||
for obj in list(doc.Objects):
|
for obj in list(doc.Objects):
|
||||||
try:
|
try:
|
||||||
@@ -7925,26 +8125,30 @@ def _on_command_end(sender, e):
|
|||||||
except (ValueError, TypeError): z_delta = 0.0
|
except (ValueError, TypeError): z_delta = 0.0
|
||||||
sc.sticky["_elemente_wand_z_delta"] = None
|
sc.sticky["_elemente_wand_z_delta"] = None
|
||||||
if abs(z_delta) >= 1e-6:
|
if abs(z_delta) >= 1e-6:
|
||||||
# Brüstungen aller Öffnungen der Wand um delta mitnehmen
|
# OPTION A: Brueest ist RELATIV zur Wand-UK. Da UK
|
||||||
|
# in `_apply_wand_z_drag_constraint` schon um z_delta
|
||||||
|
# geaendert wurde, folgt die Oeffnung automatisch via
|
||||||
|
# Regen (cutout = new_UK + brueest = old_world_Z +
|
||||||
|
# z_delta). Wir muessen NICHT die brueest-UserString
|
||||||
|
# aktualisieren — sonst gaebe es Doppel-Addition.
|
||||||
|
# Den Oeffnungs-Punkt setzen wir auf Snapshot-Z +
|
||||||
|
# z_delta. So funktioniert es egal ob Rhino die
|
||||||
|
# Oeffnung schon mit-bewegt hat (User-Multi-Select)
|
||||||
|
# oder nicht — das End-Z ist immer das richtige.
|
||||||
for op_obj, op_meta in _find_openings_for_wall(doc, m["id"]):
|
for op_obj, op_meta in _find_openings_for_wall(doc, m["id"]):
|
||||||
cur_b = op_meta.get("oeff_brueest")
|
|
||||||
try:
|
try:
|
||||||
cur_b_val = float(cur_b) if cur_b not in (None, "") else 0.0
|
op_snap = sources_snap.get(op_meta["id"])
|
||||||
except (ValueError, TypeError):
|
if not op_snap: continue
|
||||||
cur_b_val = 0.0
|
op_pos = op_snap.get("pos")
|
||||||
new_b = max(0.0, cur_b_val + z_delta)
|
if op_pos is None: continue
|
||||||
try:
|
|
||||||
attrs = op_obj.Attributes.Duplicate()
|
|
||||||
attrs.SetUserString(_KEY_OEFF_BRUEST,
|
|
||||||
"{:.6f}".format(new_b))
|
|
||||||
doc.Objects.ModifyAttributes(op_obj.Id, attrs, True)
|
|
||||||
pt_geom = op_obj.Geometry
|
pt_geom = op_obj.Geometry
|
||||||
if hasattr(pt_geom, "Location"):
|
if not hasattr(pt_geom, "Location"): continue
|
||||||
pt = pt_geom.Location
|
pt = pt_geom.Location
|
||||||
doc.Objects.Replace(op_obj.Id,
|
target_z = op_pos[2] + z_delta
|
||||||
rg.Point(rg.Point3d(pt.X, pt.Y, new_b)))
|
doc.Objects.Replace(op_obj.Id,
|
||||||
|
rg.Point(rg.Point3d(pt.X, pt.Y, target_z)))
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
print("[ELEMENTE] post-cmd brueest:", ex)
|
print("[ELEMENTE] post-cmd brueest pt-shift:", ex)
|
||||||
affected_walls.add(m["id"])
|
affected_walls.add(m["id"])
|
||||||
elif t == "oeffnung_point":
|
elif t == "oeffnung_point":
|
||||||
op_pos = old.get("pos")
|
op_pos = old.get("pos")
|
||||||
@@ -7962,19 +8166,33 @@ def _on_command_end(sender, e):
|
|||||||
# Sync-Regen aller betroffenen Wände — Move ist sauber abgeschlossen,
|
# Sync-Regen aller betroffenen Wände — Move ist sauber abgeschlossen,
|
||||||
# kein Konflikt mehr moeglich. EIN Regen pro Wand (nicht pro Oeffnung).
|
# kein Konflikt mehr moeglich. EIN Regen pro Wand (nicht pro Oeffnung).
|
||||||
# Display bleibt suppressed bis ALLE Wände durch sind → kein „Aufbauen".
|
# Display bleibt suppressed bis ALLE Wände durch sind → kein „Aufbauen".
|
||||||
try:
|
for wid in affected_walls:
|
||||||
for wid in affected_walls:
|
try: _regenerate_element(doc, wid)
|
||||||
try: _regenerate_element(doc, wid)
|
except Exception as ex:
|
||||||
except Exception as ex:
|
print("[ELEMENTE] post-cmd regen:", ex)
|
||||||
print("[ELEMENTE] post-cmd regen:", ex)
|
doc.Views.RedrawEnabled = prev_redraw_enabled
|
||||||
finally:
|
|
||||||
doc.Views.RedrawEnabled = prev_redraw
|
|
||||||
try: doc.Views.Redraw()
|
try: doc.Views.Redraw()
|
||||||
except Exception: pass
|
except Exception: pass
|
||||||
|
# Flag erst HIER cleren — nach dem Regen-Pfad, der via _regenerate_element
|
||||||
|
# viele Replace-Events erzeugt die wir auch suppressen wollen.
|
||||||
|
sc.sticky[_UT_ACTIVE_KEY] = None
|
||||||
|
# Undo-Record schliessen — alles seit BeginUndoRecord landet in
|
||||||
|
# einem einzelnen Cmd+Z-Schritt.
|
||||||
|
undo_serial = sc.sticky.get("_dossier_undo_serial")
|
||||||
|
if undo_serial:
|
||||||
|
try: doc.EndUndoRecord(undo_serial)
|
||||||
|
except Exception: pass
|
||||||
|
sc.sticky["_dossier_undo_serial"] = 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()
|
||||||
except Exception: pass
|
except Exception: pass
|
||||||
|
# Gestaltung-Panel einmalig nachziehen — Listener waren waehrend
|
||||||
|
# des Commands + des Regens suspendiert.
|
||||||
|
gb = sc.sticky.get("gestaltung_bridge")
|
||||||
|
if gb is not None:
|
||||||
|
try: gb._send_selection()
|
||||||
|
except Exception: pass
|
||||||
|
|
||||||
|
|
||||||
def _install_listeners(bridge):
|
def _install_listeners(bridge):
|
||||||
|
|||||||
+38
-19
@@ -1368,6 +1368,13 @@ def _install_selection_listener(bridge):
|
|||||||
return
|
return
|
||||||
|
|
||||||
def refresh(*args):
|
def refresh(*args):
|
||||||
|
# Waehrend Move/Rotate/Mirror/Scale schweigen — Rhino oszilliert die
|
||||||
|
# Selection pro transformiertem Object mehrfach (deselect→delete→add→
|
||||||
|
# reselect). Bei 7 Objekten sind das ~100 IPC-Sends in den WebView,
|
||||||
|
# was sich als „Regen" anfuehlt. elemente._on_command_end refresht
|
||||||
|
# nach dem Command einmalig.
|
||||||
|
if sc.sticky.get("_dossier_user_transform_active"): return
|
||||||
|
if sc.sticky.get("_dossier_undo_active"): return
|
||||||
b = sc.sticky.get("gestaltung_bridge")
|
b = sc.sticky.get("gestaltung_bridge")
|
||||||
if b is not None:
|
if b is not None:
|
||||||
try: b._send_selection()
|
try: b._send_selection()
|
||||||
@@ -1380,6 +1387,12 @@ def _install_selection_listener(bridge):
|
|||||||
- Hatch hat _FILL_OWNER_KEY (= curve_id) → Curve um den gleichen
|
- Hatch hat _FILL_OWNER_KEY (= curve_id) → Curve um den gleichen
|
||||||
Vektor mit-translaten (User hat Hatch alleine verschoben).
|
Vektor mit-translaten (User hat Hatch alleine verschoben).
|
||||||
"""
|
"""
|
||||||
|
# Waehrend User-Transform-Command: elemente uebernimmt die Geometrie-
|
||||||
|
# Synchronisation. Hatch-Re-Create laeuft hier sowieso ins Leere weil
|
||||||
|
# Rhino bei Move Delete+Add statt Replace feuert.
|
||||||
|
if sc.sticky.get("_dossier_user_transform_active"): return
|
||||||
|
if sc.sticky.get("_dossier_undo_active"): return
|
||||||
|
if sc.sticky.get("_elemente_regen_busy"): return
|
||||||
new_obj = args.NewRhinoObject
|
new_obj = args.NewRhinoObject
|
||||||
if new_obj is None or new_obj.Id in _processing:
|
if new_obj is None or new_obj.Id in _processing:
|
||||||
return
|
return
|
||||||
@@ -1457,11 +1470,13 @@ def _install_selection_listener(bridge):
|
|||||||
"""Wenn eine Curve geloescht wird, ihre gekoppelte Hatch mitloeschen.
|
"""Wenn eine Curve geloescht wird, ihre gekoppelte Hatch mitloeschen.
|
||||||
Wenn umgekehrt eine Hatch direkt geloescht wird, den Verweis auf der
|
Wenn umgekehrt eine Hatch direkt geloescht wird, den Verweis auf der
|
||||||
Curve aufraeumen damit beim naechsten Toggle keine Geister-Referenz steht."""
|
Curve aufraeumen damit beim naechsten Toggle keine Geister-Referenz steht."""
|
||||||
|
# Waehrend User-Transform-Command: Rhino feuert Delete+Add fuer
|
||||||
|
# transformierte Objekte. Curve→Hatch-Cascade hier wuerde die Hatch
|
||||||
|
# killen obwohl sie gleich wieder benoetigt wird.
|
||||||
|
if sc.sticky.get("_dossier_user_transform_active"): return
|
||||||
|
if sc.sticky.get("_dossier_undo_active"): return
|
||||||
|
if sc.sticky.get("_elemente_regen_busy"): return
|
||||||
obj = args.TheObject
|
obj = args.TheObject
|
||||||
try:
|
|
||||||
print("[GESTALTUNG] on_delete fired id={}".format(obj.Id if obj else None))
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
if obj is None or obj.Id in _processing:
|
if obj is None or obj.Id in _processing:
|
||||||
return
|
return
|
||||||
doc = Rhino.RhinoDoc.ActiveDoc
|
doc = Rhino.RhinoDoc.ActiveDoc
|
||||||
@@ -1470,16 +1485,24 @@ def _install_selection_listener(bridge):
|
|||||||
except Exception:
|
except Exception:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Pfad A: geloeschte Curve hatte eine Hatch -> Hatch mitloeschen
|
# Schneller Bail-out: ohne Hatch-UserString interessiert uns das
|
||||||
|
# Event nicht. Vermeidet Print-Spam fuer Wand-Sub-Volumen etc.
|
||||||
try:
|
try:
|
||||||
hatch_id_str = attrs.GetUserString(_FILL_KEY)
|
hatch_id_str = attrs.GetUserString(_FILL_KEY)
|
||||||
except Exception:
|
except Exception:
|
||||||
hatch_id_str = None
|
hatch_id_str = None
|
||||||
# Fallback: Mapping in sc.sticky (UserStrings koennen nach Delete leer sein)
|
try:
|
||||||
if not hatch_id_str:
|
owner_id_str = attrs.GetUserString(_FILL_OWNER_KEY)
|
||||||
|
except Exception:
|
||||||
|
owner_id_str = None
|
||||||
|
if not hatch_id_str and not owner_id_str:
|
||||||
|
# UserStrings koennen nach Delete leer sein → Sticky-Fallback.
|
||||||
hatch_id_str = _lookup_hatch_for_curve(obj.Id)
|
hatch_id_str = _lookup_hatch_for_curve(obj.Id)
|
||||||
if hatch_id_str:
|
if not hatch_id_str:
|
||||||
print("[GESTALTUNG] on_delete: hatch via sticky map gefunden")
|
return
|
||||||
|
print("[GESTALTUNG] on_delete: hatch via sticky map gefunden")
|
||||||
|
|
||||||
|
# Pfad A: geloeschte Curve hatte eine Hatch -> Hatch mitloeschen
|
||||||
if hatch_id_str:
|
if hatch_id_str:
|
||||||
try:
|
try:
|
||||||
hatch_id = System.Guid(hatch_id_str)
|
hatch_id = System.Guid(hatch_id_str)
|
||||||
@@ -1504,10 +1527,6 @@ def _install_selection_listener(bridge):
|
|||||||
return # Curve-Fall fertig
|
return # Curve-Fall fertig
|
||||||
|
|
||||||
# Pfad B: geloeschte Hatch hatte einen Owner-Verweis -> Curve aufraeumen
|
# Pfad B: geloeschte Hatch hatte einen Owner-Verweis -> Curve aufraeumen
|
||||||
try:
|
|
||||||
owner_id_str = attrs.GetUserString(_FILL_OWNER_KEY)
|
|
||||||
except Exception:
|
|
||||||
owner_id_str = None
|
|
||||||
if owner_id_str:
|
if owner_id_str:
|
||||||
try:
|
try:
|
||||||
owner_id = System.Guid(owner_id_str)
|
owner_id = System.Guid(owner_id_str)
|
||||||
@@ -1532,16 +1551,17 @@ def _install_selection_listener(bridge):
|
|||||||
- Wenn das Objekt eben gerade als Teil eines Drag/Move geloescht wurde,
|
- Wenn das Objekt eben gerade als Teil eines Drag/Move geloescht wurde,
|
||||||
stellen wir die Hatch mit den gemerkten Metadaten wieder her.
|
stellen wir die Hatch mit den gemerkten Metadaten wieder her.
|
||||||
- Sonst pruefen wir ob die Ebene ein Auto-Fill konfiguriert hat."""
|
- Sonst pruefen wir ob die Ebene ein Auto-Fill konfiguriert hat."""
|
||||||
|
# Waehrend User-Transform-Command: elemente uebernimmt Geometrie-Sync.
|
||||||
|
# Auto-Fill hier wuerde unnoetige Hatches erzeugen weil das Objekt
|
||||||
|
# bereits eine geerbte Fill-UserString hat (vom Delete+Add im Move).
|
||||||
|
if sc.sticky.get("_dossier_user_transform_active"): return
|
||||||
|
if sc.sticky.get("_dossier_undo_active"): return
|
||||||
|
if sc.sticky.get("_elemente_regen_busy"): return
|
||||||
obj = args.TheObject
|
obj = args.TheObject
|
||||||
if obj is None:
|
if obj is None:
|
||||||
return
|
return
|
||||||
try:
|
|
||||||
geom_kind = type(obj.Geometry).__name__
|
|
||||||
except Exception:
|
|
||||||
geom_kind = "?"
|
|
||||||
if obj.Id in _processing:
|
if obj.Id in _processing:
|
||||||
return
|
return
|
||||||
print("[GESTALTUNG] on_add: id={} type={}".format(obj.Id, geom_kind))
|
|
||||||
doc = Rhino.RhinoDoc.ActiveDoc
|
doc = Rhino.RhinoDoc.ActiveDoc
|
||||||
|
|
||||||
# 1) Drag-Recovery: Hatch-Metadaten wurden gerade in on_delete gespeichert?
|
# 1) Drag-Recovery: Hatch-Metadaten wurden gerade in on_delete gespeichert?
|
||||||
@@ -1566,7 +1586,6 @@ def _install_selection_listener(bridge):
|
|||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
print("[GESTALTUNG] on_add Exception:", ex)
|
print("[GESTALTUNG] on_add Exception:", ex)
|
||||||
return
|
return
|
||||||
print("[GESTALTUNG] on_add ok={}".format(ok))
|
|
||||||
if ok:
|
if ok:
|
||||||
b = sc.sticky.get("gestaltung_bridge")
|
b = sc.sticky.get("gestaltung_bridge")
|
||||||
if b is not None:
|
if b is not None:
|
||||||
|
|||||||
Reference in New Issue
Block a user