begin implementing ARP functionality
This commit is contained in:
File diff suppressed because one or more lines are too long
+30
-30
@@ -7,7 +7,7 @@
|
||||
"end_timestamp": "2026-02-18T16:39:18-07:00",
|
||||
"markdown_size_bytes": 789326,
|
||||
"provider": "cursoride",
|
||||
"last_updated": "2026-03-25T19:45:41Z"
|
||||
"last_updated": "2026-03-25T20:22:34Z"
|
||||
},
|
||||
"14305e53-b999-4885-8511-924b3957eb04": {
|
||||
"user_message_count": 1,
|
||||
@@ -16,7 +16,7 @@
|
||||
"end_timestamp": "2026-03-13T14:21:38-06:00",
|
||||
"markdown_size_bytes": 659,
|
||||
"provider": "cursoride",
|
||||
"last_updated": "2026-03-25T19:47:32Z"
|
||||
"last_updated": "2026-03-25T20:24:24Z"
|
||||
},
|
||||
"15ca8ebd-f444-47c7-8df3-efe4c9e8386f": {
|
||||
"user_message_count": 1,
|
||||
@@ -25,7 +25,7 @@
|
||||
"end_timestamp": "2026-03-10T10:07:27-06:00",
|
||||
"markdown_size_bytes": 9952,
|
||||
"provider": "cursoride",
|
||||
"last_updated": "2026-03-25T19:45:41Z"
|
||||
"last_updated": "2026-03-25T20:22:34Z"
|
||||
},
|
||||
"315fde62-50dd-4c50-9d07-4089c4952f9d": {
|
||||
"user_message_count": 1,
|
||||
@@ -34,7 +34,7 @@
|
||||
"end_timestamp": "2026-03-13T14:28:15-06:00",
|
||||
"markdown_size_bytes": 1169,
|
||||
"provider": "cursoride",
|
||||
"last_updated": "2026-03-25T19:47:32Z"
|
||||
"last_updated": "2026-03-25T20:24:24Z"
|
||||
},
|
||||
"3178f110-1a60-43b6-9d92-e189b2342c51": {
|
||||
"user_message_count": 1,
|
||||
@@ -43,7 +43,7 @@
|
||||
"end_timestamp": "2026-03-13T14:27:44-06:00",
|
||||
"markdown_size_bytes": 880,
|
||||
"provider": "cursoride",
|
||||
"last_updated": "2026-03-25T19:47:32Z"
|
||||
"last_updated": "2026-03-25T20:24:24Z"
|
||||
},
|
||||
"3d77131c-052c-4be7-b2e4-2326abcd6957": {
|
||||
"user_message_count": 1,
|
||||
@@ -52,7 +52,7 @@
|
||||
"end_timestamp": "2026-03-10T10:15:27-06:00",
|
||||
"markdown_size_bytes": 8570,
|
||||
"provider": "cursoride",
|
||||
"last_updated": "2026-03-25T19:45:41Z"
|
||||
"last_updated": "2026-03-25T20:22:34Z"
|
||||
},
|
||||
"3f042eb5-f457-4e06-9b56-ee14ac7afd90": {
|
||||
"user_message_count": 1,
|
||||
@@ -61,7 +61,7 @@
|
||||
"end_timestamp": "2026-02-19T11:53:38-07:00",
|
||||
"markdown_size_bytes": 36111,
|
||||
"provider": "cursoride",
|
||||
"last_updated": "2026-03-25T19:45:41Z"
|
||||
"last_updated": "2026-03-25T20:22:34Z"
|
||||
},
|
||||
"49972a6d-d81d-4647-b6b5-9c92f8599474": {
|
||||
"user_message_count": 1,
|
||||
@@ -70,7 +70,7 @@
|
||||
"end_timestamp": "2026-03-10T10:09:58-06:00",
|
||||
"markdown_size_bytes": 12876,
|
||||
"provider": "cursoride",
|
||||
"last_updated": "2026-03-25T19:45:41Z"
|
||||
"last_updated": "2026-03-25T20:22:34Z"
|
||||
},
|
||||
"4ce1e38a-1e85-411b-93ee-a378eef56146": {
|
||||
"user_message_count": 91,
|
||||
@@ -79,7 +79,7 @@
|
||||
"end_timestamp": "2025-08-20T16:43:14-06:00",
|
||||
"markdown_size_bytes": 784540,
|
||||
"provider": "cursoride",
|
||||
"last_updated": "2026-03-25T19:45:41Z"
|
||||
"last_updated": "2026-03-25T20:22:34Z"
|
||||
},
|
||||
"4f67a3b1-1537-4680-9763-d0c401c58d7e": {
|
||||
"user_message_count": 1,
|
||||
@@ -88,7 +88,7 @@
|
||||
"end_timestamp": "2026-03-10T10:19:09-06:00",
|
||||
"markdown_size_bytes": 71838,
|
||||
"provider": "cursoride",
|
||||
"last_updated": "2026-03-25T19:45:41Z"
|
||||
"last_updated": "2026-03-25T20:22:34Z"
|
||||
},
|
||||
"577872ce-9067-4be5-bdb0-89d48d9424ca": {
|
||||
"user_message_count": 1,
|
||||
@@ -97,7 +97,7 @@
|
||||
"end_timestamp": "2026-03-10T10:25:51-06:00",
|
||||
"markdown_size_bytes": 75382,
|
||||
"provider": "cursoride",
|
||||
"last_updated": "2026-03-25T19:45:41Z"
|
||||
"last_updated": "2026-03-25T20:22:34Z"
|
||||
},
|
||||
"73bda206-5380-40cc-afb8-b5fc426fc0dc": {
|
||||
"user_message_count": 1,
|
||||
@@ -106,7 +106,7 @@
|
||||
"end_timestamp": "2026-03-10T10:17:09-06:00",
|
||||
"markdown_size_bytes": 11226,
|
||||
"provider": "cursoride",
|
||||
"last_updated": "2026-03-25T19:45:41Z"
|
||||
"last_updated": "2026-03-25T20:22:34Z"
|
||||
},
|
||||
"788ea0b5-9ff6-440d-993d-a05234d95c5a": {
|
||||
"user_message_count": 1,
|
||||
@@ -115,7 +115,7 @@
|
||||
"end_timestamp": "2026-03-10T10:22:17-06:00",
|
||||
"markdown_size_bytes": 69439,
|
||||
"provider": "cursoride",
|
||||
"last_updated": "2026-03-25T19:45:41Z"
|
||||
"last_updated": "2026-03-25T20:22:34Z"
|
||||
},
|
||||
"7ac38efd-6dd0-42da-9a01-f7d5062046e9": {
|
||||
"user_message_count": 1,
|
||||
@@ -124,7 +124,7 @@
|
||||
"end_timestamp": "2026-03-10T10:23:09-06:00",
|
||||
"markdown_size_bytes": 61512,
|
||||
"provider": "cursoride",
|
||||
"last_updated": "2026-03-25T19:45:41Z"
|
||||
"last_updated": "2026-03-25T20:22:34Z"
|
||||
},
|
||||
"7d4946a5-6831-4e4c-90f7-8e9a96fa62d5": {
|
||||
"user_message_count": 1,
|
||||
@@ -133,7 +133,7 @@
|
||||
"end_timestamp": "2026-03-10T11:03:21-06:00",
|
||||
"markdown_size_bytes": 107701,
|
||||
"provider": "cursoride",
|
||||
"last_updated": "2026-03-25T19:45:41Z"
|
||||
"last_updated": "2026-03-25T20:22:34Z"
|
||||
},
|
||||
"986c4b32-627a-4d70-9edb-fcaf30e33ee2": {
|
||||
"user_message_count": 1,
|
||||
@@ -142,7 +142,7 @@
|
||||
"end_timestamp": "2026-03-10T10:04:31-06:00",
|
||||
"markdown_size_bytes": 9282,
|
||||
"provider": "cursoride",
|
||||
"last_updated": "2026-03-25T19:45:41Z"
|
||||
"last_updated": "2026-03-25T20:22:34Z"
|
||||
},
|
||||
"9d47ad7e-a8c1-4b72-b7cb-d27d2232bf50": {
|
||||
"user_message_count": 1,
|
||||
@@ -151,7 +151,7 @@
|
||||
"end_timestamp": "2026-03-10T11:24:38-06:00",
|
||||
"markdown_size_bytes": 13504,
|
||||
"provider": "cursoride",
|
||||
"last_updated": "2026-03-25T19:45:41Z"
|
||||
"last_updated": "2026-03-25T20:22:34Z"
|
||||
},
|
||||
"9dca74c5-e8b6-4191-ae49-949e8c742e4d": {
|
||||
"user_message_count": 1,
|
||||
@@ -160,7 +160,7 @@
|
||||
"end_timestamp": "2026-03-10T10:07:46-06:00",
|
||||
"markdown_size_bytes": 58095,
|
||||
"provider": "cursoride",
|
||||
"last_updated": "2026-03-25T19:45:41Z"
|
||||
"last_updated": "2026-03-25T20:22:34Z"
|
||||
},
|
||||
"a6a5c4fa-5957-4577-9323-13723784a540": {
|
||||
"user_message_count": 1,
|
||||
@@ -169,7 +169,7 @@
|
||||
"end_timestamp": "2026-03-10T10:05:03-06:00",
|
||||
"markdown_size_bytes": 1116,
|
||||
"provider": "cursoride",
|
||||
"last_updated": "2026-03-25T19:45:41Z"
|
||||
"last_updated": "2026-03-25T20:22:34Z"
|
||||
},
|
||||
"b450a89e-cc97-4ef3-a775-fe26c81c7d0b": {
|
||||
"user_message_count": 1,
|
||||
@@ -178,7 +178,7 @@
|
||||
"end_timestamp": "2026-03-10T10:09:41-06:00",
|
||||
"markdown_size_bytes": 3758,
|
||||
"provider": "cursoride",
|
||||
"last_updated": "2026-03-25T19:45:41Z"
|
||||
"last_updated": "2026-03-25T20:22:34Z"
|
||||
},
|
||||
"be47994a-f179-482a-9af2-4d208a01d324": {
|
||||
"user_message_count": 1,
|
||||
@@ -187,7 +187,7 @@
|
||||
"end_timestamp": "2026-03-10T10:07:14-06:00",
|
||||
"markdown_size_bytes": 2904,
|
||||
"provider": "cursoride",
|
||||
"last_updated": "2026-03-25T19:45:41Z"
|
||||
"last_updated": "2026-03-25T20:22:34Z"
|
||||
},
|
||||
"cb944af7-6783-4830-acd6-af18544ff67e": {
|
||||
"user_message_count": 1,
|
||||
@@ -196,7 +196,7 @@
|
||||
"end_timestamp": "2026-03-10T10:10:35-06:00",
|
||||
"markdown_size_bytes": 5188,
|
||||
"provider": "cursoride",
|
||||
"last_updated": "2026-03-25T19:45:41Z"
|
||||
"last_updated": "2026-03-25T20:22:34Z"
|
||||
},
|
||||
"d613f220-0255-4158-8a5d-4f93d50aa47b": {
|
||||
"user_message_count": 1,
|
||||
@@ -205,7 +205,7 @@
|
||||
"end_timestamp": "2026-03-10T10:17:43-06:00",
|
||||
"markdown_size_bytes": 3856,
|
||||
"provider": "cursoride",
|
||||
"last_updated": "2026-03-25T19:45:41Z"
|
||||
"last_updated": "2026-03-25T20:22:34Z"
|
||||
},
|
||||
"daaa74ba-72d7-4686-a95b-ae6379d17ee9": {
|
||||
"user_message_count": 1,
|
||||
@@ -214,7 +214,7 @@
|
||||
"end_timestamp": "2026-03-10T10:25:21-06:00",
|
||||
"markdown_size_bytes": 3302,
|
||||
"provider": "cursoride",
|
||||
"last_updated": "2026-03-25T19:45:41Z"
|
||||
"last_updated": "2026-03-25T20:22:34Z"
|
||||
},
|
||||
"dd526f99-b20d-401d-abac-ea6770d3a424": {
|
||||
"user_message_count": 1,
|
||||
@@ -223,16 +223,16 @@
|
||||
"end_timestamp": "2026-03-10T11:25:10-06:00",
|
||||
"markdown_size_bytes": 24412,
|
||||
"provider": "cursoride",
|
||||
"last_updated": "2026-03-25T19:45:41Z"
|
||||
"last_updated": "2026-03-25T20:22:34Z"
|
||||
},
|
||||
"f09112e7-3ddb-4e4e-b91a-418e38b881bf": {
|
||||
"user_message_count": 12,
|
||||
"agent_message_count": 162,
|
||||
"user_message_count": 14,
|
||||
"agent_message_count": 199,
|
||||
"start_timestamp": "2026-03-13T14:29:12-06:00",
|
||||
"end_timestamp": "2026-03-13T14:29:12-06:00",
|
||||
"markdown_size_bytes": 199500,
|
||||
"markdown_size_bytes": 230631,
|
||||
"provider": "cursoride",
|
||||
"last_updated": "2026-03-25T19:45:41Z"
|
||||
"last_updated": "2026-03-25T20:28:01Z"
|
||||
},
|
||||
"f1bb25c5-e5d4-4aad-88a3-f562ab877040": {
|
||||
"user_message_count": 1,
|
||||
@@ -241,7 +241,7 @@
|
||||
"end_timestamp": "2026-03-10T10:08:58-06:00",
|
||||
"markdown_size_bytes": 21001,
|
||||
"provider": "cursoride",
|
||||
"last_updated": "2026-03-25T19:45:41Z"
|
||||
"last_updated": "2026-03-25T20:22:34Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+10
-5
@@ -9,7 +9,7 @@ from bpy.types import Operator
|
||||
from bpy.props import StringProperty, BoolProperty
|
||||
from bpy.props import StringProperty, IntProperty
|
||||
|
||||
from ..utils import collection_containing_armature
|
||||
from ..utils.remove_original import resolve_collection_for_remove_original
|
||||
|
||||
ADDON_NAME = __package__.rsplit(".", 1)[0] if "." in __package__ else __package__
|
||||
|
||||
@@ -531,9 +531,10 @@ class DLM_OT_migrator_remove_original(Operator):
|
||||
if removed_actions:
|
||||
self.report({"INFO"}, f"Removed {len(removed_actions)} action(s) from original")
|
||||
|
||||
props = context.scene.dynamic_link_manager
|
||||
rig_family = getattr(props, "migrator_rig_family", "RIGIFY")
|
||||
try:
|
||||
# Try to find and delete the collection containing the original character
|
||||
coll = collection_containing_armature(orig)
|
||||
coll = resolve_collection_for_remove_original(orig, rig_family, context.scene)
|
||||
if coll:
|
||||
coll_name = coll.name # Store name BEFORE removal (RNA invalidates after remove)
|
||||
context.scene.dynamic_link_manager.original_character = None
|
||||
@@ -541,10 +542,14 @@ class DLM_OT_migrator_remove_original(Operator):
|
||||
bpy.data.collections.remove(coll)
|
||||
self.report({"INFO"}, f"Removed collection: {coll_name}")
|
||||
except Exception as remove_err:
|
||||
# Collection may have already been removed by another process
|
||||
self.report({"WARNING"}, f"Collection {coll_name} removal issue: {remove_err}")
|
||||
try:
|
||||
bpy.data.objects.remove(orig, do_unlink=True)
|
||||
self.report({"INFO"}, f"Removed original object: {name}")
|
||||
except Exception as e2:
|
||||
self.report({"ERROR"}, f"Could not remove original after collection failure: {e2}")
|
||||
return {"CANCELLED"}
|
||||
else:
|
||||
# Fallback: just delete the armature object
|
||||
bpy.data.objects.remove(orig, do_unlink=True)
|
||||
context.scene.dynamic_link_manager.original_character = None
|
||||
self.report({"INFO"}, f"Removed original character: {name}")
|
||||
|
||||
@@ -76,6 +76,8 @@ class DLM_PT_main_panel(Panel):
|
||||
box = layout.box()
|
||||
box.label(text="Character Migrator")
|
||||
row = box.row()
|
||||
row.prop(props, "migrator_rig_family", expand=True)
|
||||
row = box.row()
|
||||
row.prop(props, "migrator_mode", text="Automatic pair discovery")
|
||||
row = box.row()
|
||||
row.prop(props, "original_character", text="Original")
|
||||
|
||||
+19
-1
@@ -5,7 +5,14 @@
|
||||
|
||||
import bpy
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import IntProperty, StringProperty, BoolProperty, CollectionProperty, PointerProperty
|
||||
from bpy.props import (
|
||||
IntProperty,
|
||||
StringProperty,
|
||||
BoolProperty,
|
||||
CollectionProperty,
|
||||
PointerProperty,
|
||||
EnumProperty,
|
||||
)
|
||||
|
||||
|
||||
class SearchPathItem(PropertyGroup):
|
||||
@@ -41,6 +48,17 @@ class DynamicLinkManagerProperties(PropertyGroup):
|
||||
)
|
||||
selected_asset_path: StringProperty(name="Selected Asset Path", default="")
|
||||
|
||||
# Character migrator: rig family (future ARP-specific migration; Remove Original uses it for collection resolution)
|
||||
migrator_rig_family: EnumProperty(
|
||||
name="Rig family",
|
||||
description="Rigify vs Auto-Rig Pro: affects Remove Original collection detection and future bone heuristics",
|
||||
items=(
|
||||
("RIGIFY", "Rigify", "Rigify rig naming and defaults"),
|
||||
("ARP", "ARP", "Auto-Rig Pro rig naming and collection behavior"),
|
||||
),
|
||||
default="RIGIFY",
|
||||
)
|
||||
|
||||
# Character migrator (manual mode)
|
||||
migrator_mode: BoolProperty(
|
||||
name="Automatic",
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
# 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
|
||||
if coll in master.children:
|
||||
return master
|
||||
for p in bpy.data.collections:
|
||||
if coll 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 _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):
|
||||
"""
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
return _pick_remove_target_from_chain(orig, chain, rig_family)
|
||||
Reference in New Issue
Block a user