Mirror/Copy: Duplikat-IDs in CommandEnd umbenennen statt verkoppelt zu lassen
Rhinos Mirror/Copy/Array kopiert selektierte Objekte mit ihren UserString- Metadaten → Duplikat-IDs im Doc (z.B. zwei `wand_axis` mit gleicher `id=wall_xxx`). Resultat: unser System sieht beide als „dasselbe Element", fasst sie verkoppelt an, Pure-Transform wird konfus, Original wand_volume wandert mit weil bb-snapshot matched. Fix in `_on_command_end`, BEVOR Pure-Transform-Detection laeuft: 1. Snapshot speichert jetzt `obj_ids`-Set aller pre-Command Rhino-Object-Ids. 2. Pass A: alle neuen Sources (obj.Id nicht im Snapshot) deren UserString-id bereits in `sources_snap` existiert → identifiziert als Mirror/Copy- Duplikat, neue UUID generiert (gleicher Prefix wie bei Original-Erzeugung). 3. Pass B: alle neuen Volumes mit id = alter-renamed-Source → bekommen die neue ID + `oeff_parent` wird umgehaengt wenn ihre Eltern-Wand renamed. 4. Pass C: neue oeffnung_points kriegen `oeff_parent` auf renamed Wand umgehaengt. 5. Pass D: alle gesammelten Renames atomar via ModifyAttributes anwenden. Resultat: Mirror-Kopie ist nach CommandEnd ein vollstaendig eigenstaendiges Element mit eigenen IDs + intakter Parent-Cascade. Pure-Transform sieht saubere Snapshot-vs-aktuell-Bilanz (Originale=Identity, Kopien außerhalb des Snapshots → keine Action erforderlich, Rhino hat sie schon geometrisch korrekt platziert). Funktioniert generisch fuer Mirror, Copy, Array — alle dup-id-erzeugenden Operationen. Im Log: `[ELEMENTE] mirror/copy-Duplikate: N Objs neu-ID'd`. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+114
-3
@@ -7818,11 +7818,15 @@ def _snapshot_source_positions(doc):
|
|||||||
BBox-Centers. Source-Map (key=element_id) füttert Constraint+Migrate.
|
BBox-Centers. Source-Map (key=element_id) füttert Constraint+Migrate.
|
||||||
Volume-Map (key=obj.Id-string) erlaubt im CommandEnd die Pure-Translate-
|
Volume-Map (key=obj.Id-string) erlaubt im CommandEnd die Pure-Translate-
|
||||||
Detection — wir checken pro Volume ob es schon vom Rhinos Move
|
Detection — wir checken pro Volume ob es schon vom Rhinos Move
|
||||||
transformed wurde, oder noch ge-translaten werden muss."""
|
transformed wurde, oder noch ge-translaten werden muss.
|
||||||
snap = {"sources": {}, "volumes": {}}
|
obj_ids-Set: alle pre-Command Rhino-Object-IDs. Wird in CommandEnd
|
||||||
|
benutzt um Mirror/Copy-Duplikate zu erkennen (= neue Objs mit IDs die
|
||||||
|
nicht im Snapshot waren)."""
|
||||||
|
snap = {"sources": {}, "volumes": {}, "obj_ids": set()}
|
||||||
if doc is None: return snap
|
if doc is None: return snap
|
||||||
for obj in doc.Objects:
|
for obj in doc.Objects:
|
||||||
try:
|
try:
|
||||||
|
snap["obj_ids"].add(str(obj.Id))
|
||||||
m = _read_meta(obj)
|
m = _read_meta(obj)
|
||||||
if not m: continue
|
if not m: continue
|
||||||
t = m.get("type")
|
t = m.get("type")
|
||||||
@@ -7946,8 +7950,115 @@ def _on_command_end(sender, e):
|
|||||||
|
|
||||||
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 {}
|
||||||
|
old_obj_ids = snapshot.get("obj_ids", set()) if isinstance(snapshot, dict) else set()
|
||||||
|
|
||||||
# ─── Pure-Transform-Detection (Translation + Z-Rotation) ────────────────
|
# ─── Mirror/Copy-Duplikat-Detection ─────────────────────────────────────
|
||||||
|
# Rhinos Mirror/Copy/Array erzeugt KOPIEN selektierter Objekte mit ihren
|
||||||
|
# UserStrings (= Metadata). Resultat: Duplikat-IDs im Doc — z.B. zwei
|
||||||
|
# `wand_axis` mit `id=wall_xxx`. Unser System haelt die fuer „dasselbe
|
||||||
|
# Element", was zu „verkoppelten" Elementen fuehrt und zu kaputten
|
||||||
|
# Pure-Transform-Detections.
|
||||||
|
#
|
||||||
|
# Fix: alle NEUEN Objs (obj.Id nicht im Snapshot) deren UserString-id
|
||||||
|
# bereits im Snapshot existiert → neue UUID. Sub-Volumen und
|
||||||
|
# Oeffnungs-Parent-Refs werden konsistent umgehaengt.
|
||||||
|
_type_to_prefix = {
|
||||||
|
"wand_axis": "wall_",
|
||||||
|
"decke_outline": "decke_",
|
||||||
|
"dach_outline": "dach_",
|
||||||
|
"treppe_axis": "treppe_",
|
||||||
|
"stuetze_point": "trag_",
|
||||||
|
"traeger_axis": "trag_",
|
||||||
|
"raum_outline": "raum_",
|
||||||
|
"decke_aussparung_outline": "aussp_",
|
||||||
|
}
|
||||||
|
# Pass A: identifiziere neue Sources mit dup-IDs, sammle (obj, alte_id, neue_id)
|
||||||
|
dup_source_renames = [] # list of (obj, old_id, new_id, type)
|
||||||
|
for obj in doc.Objects:
|
||||||
|
try:
|
||||||
|
if str(obj.Id) in old_obj_ids: continue # original existed pre-command
|
||||||
|
m = _read_meta(obj)
|
||||||
|
if not m: continue
|
||||||
|
t = m.get("type")
|
||||||
|
if t not in SOURCE_TYPES: continue
|
||||||
|
old_id = m["id"]
|
||||||
|
if old_id not in sources_snap: continue # echtes neues Element
|
||||||
|
if t == "oeffnung_point":
|
||||||
|
prefix = "fenster_" if m.get("oeff_typ") == "fenster" else "tuer_"
|
||||||
|
else:
|
||||||
|
prefix = _type_to_prefix.get(t, "elem_")
|
||||||
|
new_id = prefix + uuid.uuid4().hex[:10]
|
||||||
|
dup_source_renames.append((obj, old_id, new_id, t))
|
||||||
|
except Exception as ex:
|
||||||
|
print("[ELEMENTE] dup detection:", ex)
|
||||||
|
|
||||||
|
# Pass B: neue Volumes mit dup-IDs identifizieren (alte UserString-id ist
|
||||||
|
# eine umbenannte Source). Mapping alte_id → neue_id zum Lookup.
|
||||||
|
elem_id_map = {old_id: new_id for (_, old_id, new_id, _) in dup_source_renames}
|
||||||
|
dup_volume_renames = [] # list of (obj, new_id, oeff_parent_old, oeff_parent_new)
|
||||||
|
for obj in doc.Objects:
|
||||||
|
try:
|
||||||
|
if str(obj.Id) in old_obj_ids: continue
|
||||||
|
m = _read_meta(obj)
|
||||||
|
if not m: continue
|
||||||
|
t = m.get("type")
|
||||||
|
if t not in VOLUME_TYPES: continue
|
||||||
|
old_vol_id = m["id"]
|
||||||
|
new_vol_id = elem_id_map.get(old_vol_id)
|
||||||
|
if not new_vol_id: continue # Volume gehoert nicht zu einem renamed Source
|
||||||
|
# oeff_parent rewire bei oeffnung_volume
|
||||||
|
old_parent = m.get("oeff_parent") or ""
|
||||||
|
new_parent = elem_id_map.get(old_parent, old_parent)
|
||||||
|
dup_volume_renames.append((obj, new_vol_id, old_parent, new_parent))
|
||||||
|
except Exception as ex:
|
||||||
|
print("[ELEMENTE] dup volume detection:", ex)
|
||||||
|
|
||||||
|
# Pass C: oeffnung_point's oeff_parent rewire (nicht-Volume, also Sources)
|
||||||
|
# Wenn eine Wand umbenannt wurde, alle (umbenannten) Oeffnungen die zu ihr
|
||||||
|
# gehoeren auch auf neue Wand-id umhaengen.
|
||||||
|
if elem_id_map:
|
||||||
|
# In dup_source_renames Liste: fuer oeffnung_point-Renames pruefen, ob
|
||||||
|
# ihr oeff_parent in elem_id_map ist → updaten.
|
||||||
|
for i, (obj, old_id, new_id, t) in enumerate(dup_source_renames):
|
||||||
|
if t != "oeffnung_point": continue
|
||||||
|
try:
|
||||||
|
m = _read_meta(obj)
|
||||||
|
if not m: continue
|
||||||
|
old_parent = m.get("oeff_parent") or ""
|
||||||
|
new_parent = elem_id_map.get(old_parent, old_parent)
|
||||||
|
# Tuple aktualisieren (alte vs neue parent-ID, fuer apply unten)
|
||||||
|
dup_source_renames[i] = (obj, old_id, new_id, t, new_parent)
|
||||||
|
except Exception: pass
|
||||||
|
|
||||||
|
# Pass D: alle gesammelten Renames anwenden
|
||||||
|
n_renamed = 0
|
||||||
|
for entry in dup_source_renames:
|
||||||
|
try:
|
||||||
|
if len(entry) == 5:
|
||||||
|
obj, old_id, new_id, t, new_parent = entry
|
||||||
|
else:
|
||||||
|
obj, old_id, new_id, t = entry
|
||||||
|
new_parent = None
|
||||||
|
attrs = obj.Attributes.Duplicate()
|
||||||
|
attrs.SetUserString(_KEY_ID, new_id)
|
||||||
|
if new_parent is not None:
|
||||||
|
attrs.SetUserString(_KEY_OEFF_PARENT, new_parent)
|
||||||
|
doc.Objects.ModifyAttributes(obj.Id, attrs, True)
|
||||||
|
n_renamed += 1
|
||||||
|
except Exception as ex:
|
||||||
|
print("[ELEMENTE] apply source rename:", ex)
|
||||||
|
for obj, new_vol_id, old_parent, new_parent in dup_volume_renames:
|
||||||
|
try:
|
||||||
|
attrs = obj.Attributes.Duplicate()
|
||||||
|
attrs.SetUserString(_KEY_ID, new_vol_id)
|
||||||
|
if old_parent and new_parent and new_parent != old_parent:
|
||||||
|
attrs.SetUserString(_KEY_OEFF_PARENT, new_parent)
|
||||||
|
doc.Objects.ModifyAttributes(obj.Id, attrs, True)
|
||||||
|
n_renamed += 1
|
||||||
|
except Exception as ex:
|
||||||
|
print("[ELEMENTE] apply volume rename:", ex)
|
||||||
|
if n_renamed > 0:
|
||||||
|
print("[ELEMENTE] mirror/copy-Duplikate: {} Objs neu-ID'd".format(n_renamed))
|
||||||
# Wenn ALLE bewegten Sources sich mit dem gleichen Rigid-2D-Transform
|
# Wenn ALLE bewegten Sources sich mit dem gleichen Rigid-2D-Transform
|
||||||
# abbilden lassen (Translation und/oder Rotation um Z-Achse, KEIN Scale,
|
# abbilden lassen (Translation und/oder Rotation um Z-Achse, KEIN Scale,
|
||||||
# KEIN Z-Drag, KEIN End-Grip-Drag, KEIN Mirror), reicht eine Transform-
|
# KEIN Z-Drag, KEIN End-Grip-Drag, KEIN Mirror), reicht eine Transform-
|
||||||
|
|||||||
Reference in New Issue
Block a user