692e200ffe
save startup blend for animation tab & whatnot
141 lines
4.8 KiB
Python
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)
|