begin implementing ARP functionality

This commit is contained in:
Nathan
2026-03-25 14:30:17 -06:00
parent c2b1829fbf
commit 3c0839249f
6 changed files with 1191 additions and 218 deletions
File diff suppressed because one or more lines are too long
+30 -30
View File
@@ -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
View File
@@ -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}")
+2
View File
@@ -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
View File
@@ -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",
+118
View File
@@ -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)