Files
Raincloud 692e200ffe work
save startup blend for animation tab & whatnot
2026-04-08 12:10:18 -06:00

141 lines
4.8 KiB
Python

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
"""Resolve which collection to remove when deleting the original character armature."""
import bpy
def _parent_collection(scene, coll):
"""Return the parent of coll in the scene tree, or None if not found."""
if coll is None:
return None
master = scene.collection
# Blender 5+: children.__contains__ expects collection name strings, not Collection objects.
if coll.name in master.children:
return master
for p in bpy.data.collections:
if coll.name in p.children:
return p
return None
def _depth_from_scene_root(scene, coll):
"""Depth: number of steps walking up from coll until scene.collection (deeper = larger)."""
d = 0
cur = coll
while cur is not None and cur != scene.collection:
d += 1
cur = _parent_collection(scene, cur)
return d
def _walk_up_chain(scene, coll):
"""Return [inner, ..., top] where top is a direct child of scene.collection (or highest ancestor)."""
chain = []
cur = coll
while cur is not None and cur != scene.collection:
chain.append(cur)
cur = _parent_collection(scene, cur)
return chain
def _collection_contains_object_recursive(coll, ob):
"""True if ob is in coll.objects or in any descendant collection (recursive)."""
if coll is None or ob is None:
return False
for o in coll.objects:
if o == ob:
return True
for child in coll.children:
if _collection_contains_object_recursive(child, ob):
return True
return False
def _deepest_users_collection(scene, armature):
"""Among armature.users_collection, pick the most nested (max depth) as the inner anchor."""
colls = list(getattr(armature, "users_collection", []) or [])
if not colls:
return None
best = colls[0]
best_d = _depth_from_scene_root(scene, best)
for c in colls[1:]:
d = _depth_from_scene_root(scene, c)
if d > best_d:
best_d = d
best = c
return best
def _library_ptr(lib):
"""Stable id for comparing library datablocks."""
return lib.filepath if lib else None
def _pick_remove_target_from_chain(orig, chain, rig_family):
"""
From walk-up chain [inner..top], choose one collection to remove.
Prefer the topmost under scene (chain[-1]) so nested linked setups remove the whole instance.
If orig is linked, prefer the outermost chain entry whose library matches orig.library.
"""
if not chain:
return None
orig_lib = _library_ptr(getattr(orig, "library", None))
# Linked armature: outermost in chain from same library file (reversed = top -> inner)
if orig_lib:
for c in reversed(chain):
if _library_ptr(getattr(c, "library", None)) == orig_lib:
return c
# Local override: prefer outermost collection that participates in override hierarchy
if getattr(orig, "override_library", None):
for c in reversed(chain):
if getattr(c, "override_library", None) is not None:
return c
# Rigify: optional name hint — prefer collection whose name matches base armature name
if rig_family == "RIGIFY":
name = orig.name
base = name.replace("_Rigify", "").replace(".001", "").rstrip("0123456789.")
for c in reversed(chain):
if c.name == base or base in c.name or c.name in name:
return c
# Default: top of chain (direct child of scene.collection subtree root)
return chain[-1]
def resolve_collection_for_remove_original(orig, rig_family, scene, rep=None):
"""
Return a collection to remove for Remove Original, or None to fall back to object-only removal.
Walks up from the deepest users_collection so nested linked rigs remove the outer instance,
not an inner linked child collection.
If rep is the replacement armature, never remove a collection whose subtree contains rep
(avoids deleting both characters when they share a parent collection).
rig_family: 'RIGIFY' | 'ARP' (ARP skips Rigify name heuristics in _pick_remove_target_from_chain).
"""
if not orig or orig.type != "ARMATURE" or orig.name not in bpy.data.objects:
return None
inner = _deepest_users_collection(scene, orig)
if inner is None:
return None
chain = _walk_up_chain(scene, inner)
if not chain:
return None
if rep is not None and rep.name in bpy.data.objects:
chain = [c for c in chain if not _collection_contains_object_recursive(c, rep)]
if not chain:
return None
return _pick_remove_target_from_chain(orig, chain, rig_family)