MigNLA: now makes repchar active before activating animlayers to avoid requirement for manual activation

This commit is contained in:
Nathan
2026-03-25 14:39:03 -06:00
parent 3c0839249f
commit 7c1969de20
4 changed files with 940 additions and 10 deletions
@@ -3286,3 +3286,870 @@ _**Agent (composer-2-fast)**_
<summary>Tool use: **edit_file_v2**</summary>
</details></tool-use>
_**Agent (composer-2-fast)**_
<tool-use data-tool-type="write" data-tool-name="edit_file_v2">
<details><summary>Tool use: **edit_file_v2** • Edit file: T:/Dynamic-Link-Manager/utils/remove_original.py</summary>
</details>
</tool-use>
<tool-use data-tool-type="write" data-tool-name="edit_file_v2"><details>
<summary>Tool use: **edit_file_v2**</summary>
</details></tool-use>
_**Agent (composer-2-fast)**_
_**Agent (composer-2-fast)**_
<tool-use data-tool-type="read" data-tool-name="read_file_v2">
<details><summary>Tool use: **read_file_v2** • Read file: T:/Dynamic-Link-Manager/ui/operators.py</summary>
</details>
</tool-use>
<tool-use data-tool-type="read" data-tool-name="read_file_v2"><details>
<summary>Tool use: **read_file_v2**</summary>
</details></tool-use>
_**Agent (composer-2-fast)**_
<tool-use data-tool-type="read" data-tool-name="read_file_v2">
<details><summary>Tool use: **read_file_v2** • Read file: T:/Dynamic-Link-Manager/ui/operators.py</summary>
</details>
</tool-use>
<tool-use data-tool-type="read" data-tool-name="read_file_v2"><details>
<summary>Tool use: **read_file_v2**</summary>
</details></tool-use>
_**Agent (composer-2-fast)**_
_**Agent (composer-2-fast)**_
<tool-use data-tool-type="unknown" data-tool-name="ripgrep_raw_search">
<details>
<summary>Tool use: **ripgrep_raw_search**</summary>
Parameters:
```json
{
"caseInsensitive": false,
"path": "T:/Dynamic-Link-Manager/ui/operators.py",
"pattern": "collection_containing_armature"
}
```
Additional data:
```json
{
"isPruned": true,
"outputMode": "content",
"path": "T:/Dynamic-Link-Manager/ui/operators.py",
"pattern": "collection_containing_armature",
"topFiles": [
{
"matchCount": 2,
"uri": "ui/operators.py"
}
],
"totalFiles": 1,
"totalMatches": 2
}
```
Status: **completed**
</details>
</tool-use>
<tool-use data-tool-type="unknown" data-tool-name="ripgrep_raw_search"><details>
<summary>Tool use: **ripgrep_raw_search**</summary>
</details></tool-use>
_**Agent (composer-2-fast)**_
_**Agent (composer-2-fast)**_
<tool-use data-tool-type="write" data-tool-name="edit_file_v2">
<details><summary>Tool use: **edit_file_v2** • Edit file: T:/Dynamic-Link-Manager/ui/operators.py</summary>
</details>
</tool-use>
<tool-use data-tool-type="write" data-tool-name="edit_file_v2"><details>
<summary>Tool use: **edit_file_v2**</summary>
</details></tool-use>
_**Agent (composer-2-fast)**_
<tool-use data-tool-type="write" data-tool-name="edit_file_v2">
<details><summary>Tool use: **edit_file_v2** • Edit file: T:/Dynamic-Link-Manager/ui/operators.py</summary>
</details>
</tool-use>
<tool-use data-tool-type="write" data-tool-name="edit_file_v2"><details>
<summary>Tool use: **edit_file_v2**</summary>
</details></tool-use>
_**Agent (composer-2-fast)**_
_**Agent (composer-2-fast)**_
<tool-use data-tool-type="unknown" data-tool-name="read_lints">
<details>
<summary>Tool use: **read_lints**</summary>
Parameters:
```json
{
"paths": [
"T:/Dynamic-Link-Manager/ui/properties.py",
"T:/Dynamic-Link-Manager/ui/panels.py",
"T:/Dynamic-Link-Manager/ui/operators.py",
"T:/Dynamic-Link-Manager/utils/remove_original.py"
]
}
```
Result:
```json
{
"linterErrorsByFile": [
{
"relativeWorkspacePath": "T:/Dynamic-Link-Manager/ui/properties.py"
},
{
"relativeWorkspacePath": "T:/Dynamic-Link-Manager/ui/panels.py"
},
{
"relativeWorkspacePath": "T:/Dynamic-Link-Manager/ui/operators.py"
},
{
"relativeWorkspacePath": "T:/Dynamic-Link-Manager/utils/remove_original.py"
}
]
}
```
Status: **completed**
</details>
</tool-use>
<tool-use data-tool-type="unknown" data-tool-name="read_lints"><details>
<summary>Tool use: **read_lints**</summary>
</details></tool-use>
_**Agent (composer-2-fast)**_
_**Agent (composer-2-fast)**_
<tool-use data-tool-type="read" data-tool-name="read_file_v2">
<details><summary>Tool use: **read_file_v2** • Read file: T:/Dynamic-Link-Manager/__init__.py</summary>
</details>
</tool-use>
<tool-use data-tool-type="read" data-tool-name="read_file_v2"><details>
<summary>Tool use: **read_file_v2**</summary>
</details></tool-use>
_**Agent (composer-2-fast)**_
_**Agent (composer-2-fast)**_
<tool-use data-tool-type="unknown" data-tool-name="todo_write">
<details>
<summary>Tool use: **todo_write**</summary>
Parameters:
```json
{
"merge": true
}
```
Result:
```json
{
"finalTodos": [
{
"content": "Add migrator_rig_family EnumProperty and draw at top of CharMig box in panels.py",
"id": "enum-prop-ui",
"status": "completed"
},
{
"content": "Implement resolve_collection_for_remove_original(orig, rig_family) with linked/override hierarchy walk + safe fallback",
"id": "collection-resolver",
"status": "completed"
},
{
"content": "Refactor DLM_OT_migrator_remove_original to use resolver; keep action purge/rename logic",
"id": "wire-remove-original",
"status": "completed"
}
],
"initialTodos": [
{
"content": "Add migrator_rig_family EnumProperty and draw at top of CharMig box in panels.py",
"id": "enum-prop-ui",
"status": "completed"
},
{
"content": "Implement resolve_collection_for_remove_original(orig, rig_family) with linked/override hierarchy walk + safe fallback",
"id": "collection-resolver",
"status": "in_progress"
},
{
"content": "Refactor DLM_OT_migrator_remove_original to use resolver; keep action purge/rename logic",
"id": "wire-remove-original",
"status": "pending"
}
],
"success": true,
"wasMerge": true
}
```
Status: **completed**
</details>
</tool-use>
<tool-use data-tool-type="unknown" data-tool-name="todo_write"><details>
<summary>Tool use: **todo_write**</summary>
</details></tool-use>
_**Agent (composer-2-fast)**_
Heres what was implemented:
### 1. Rig family selector ([`ui/properties.py`](ui/properties.py), [`ui/panels.py`](ui/panels.py))
- Added `migrator_rig_family` **`EnumProperty`**: **Rigify** | **ARP** (default Rigify).
- First row under “Character Migrator” uses **`expand=True`** for the two buttons.
### 2. Collection resolver ([`utils/remove_original.py`](utils/remove_original.py))
- **`resolve_collection_for_remove_original(orig, rig_family, scene)`**:
- Picks the **deepest** `users_collection` entry (most nested).
- Walks **up** to `scene.collection` via **`_parent_collection`**.
- Chooses the remove target:
- **Linked armature:** outermost collection in that chain whose **`library.filepath`** matches **`orig.library`**.
- **Override:** outermost collection with **`override_library`** set when **`orig.override_library`** is set.
- **Rigify:** optional name match (`_Rigify`, base name) on the way **down** from the top of the chain.
- **ARP:** no `_Rigify` stripping; hierarchy + defaults rule.
- Default: **`chain[-1]`** (top of the branch under the scene).
### 3. Remove Original ([`ui/operators.py`](ui/operators.py))
- Imports **`resolve_collection_for_remove_original`** (replaces **`collection_containing_armature`** for this operator).
- Reads **`migrator_rig_family`** from scene props.
- Tries **`bpy.data.collections.remove()`**; on failure, **warns** and falls back to **`bpy.data.objects.remove(orig, do_unlink=True)`**.
- If the resolver returns **None**, removes the object only (unchanged idea).
- **Action purge and `.rep` rename** on the replacement are unchanged.
`collection_containing_armature` in [`utils/__init__.py`](utils/__init__.py) is unchanged for **MigBBody** and other callers.
---
_**User**_
So far, CopyAttr and MigNLA need no rig type-specific behavior; current behavior is sufficient for both.
MigNLA needs to do one special thing: It needs to select the RepChar before activating animlayers.
---
_**Agent (composer-2-fast)**_
_**Agent (composer-2-fast)**_
<tool-use data-tool-type="unknown" data-tool-name="ripgrep_raw_search">
<details>
<summary>Tool use: **ripgrep_raw_search**</summary>
Parameters:
```json
{
"caseInsensitive": false,
"path": "T:/Dynamic-Link-Manager/ops/migrator.py",
"pattern": "als|AnimLayer|run_mig_nla|mirror_als"
}
```
Additional data:
```json
{
"isPruned": true,
"outputMode": "content",
"path": "T:/Dynamic-Link-Manager/ops/migrator.py",
"pattern": "als|AnimLayer|run_mig_nla|mirror_als",
"topFiles": [
{
"matchCount": 71,
"uri": "ops/migrator.py"
}
],
"totalFiles": 1,
"totalMatches": 71
}
```
Status: **completed**
</details>
</tool-use>
<tool-use data-tool-type="unknown" data-tool-name="ripgrep_raw_search"><details>
<summary>Tool use: **ripgrep_raw_search**</summary>
</details></tool-use>
_**Agent (composer-2-fast)**_
<tool-use data-tool-type="read" data-tool-name="read_file_v2">
<details><summary>Tool use: **read_file_v2** • Read file: T:/Dynamic-Link-Manager/ops/migrator.py</summary>
</details>
</tool-use>
<tool-use data-tool-type="read" data-tool-name="read_file_v2"><details>
<summary>Tool use: **read_file_v2**</summary>
</details></tool-use>
_**Agent (composer-2-fast)**_
_**Agent (composer-2-fast)**_
<tool-use data-tool-type="unknown" data-tool-name="ripgrep_raw_search">
<details>
<summary>Tool use: **ripgrep_raw_search**</summary>
Parameters:
```json
{
"caseInsensitive": false,
"path": "T:/Dynamic-Link-Manager",
"pattern": "run_mig_nla"
}
```
Additional data:
```json
{
"isPruned": true,
"outputMode": "content",
"path": "T:/Dynamic-Link-Manager",
"pattern": "run_mig_nla",
"topFiles": [
{
"matchCount": 4,
"uri": ".specstory/history/2026-03-10_16-22-17Z-dlm_ot_migrator_remove_original-class-implementation.md"
},
{
"matchCount": 2,
"uri": ".specstory/history/2026-03-10_16-25Z-collection-removal-error-handling.md"
},
{
"matchCount": 2,
"uri": ".specstory/history/2026-03-10_16-07-46Z-operators.py-file-content.md"
},
{
"matchCount": 2,
"uri": ".specstory/history/2026-03-10_16-07Z-operators-py-file-content.md"
},
{
"matchCount": 2,
"uri": ".specstory/history/2026-03-10_16-19Z-removal-of-run-migration-command.md"
},
{
"matchCount": 2,
"uri": ".specstory/history/2026-03-10_16-19-09Z-removal-of-run-migration-command.md"
},
{
"matchCount": 2,
"uri": ".specstory/history/2026-03-10_17-03-21Z-operator-update-for-dlm_ot_migrator_remove_original.md"
},
{
"matchCount": 4,
"uri": ".specstory/history/2026-03-10_16-22Z-dlm-ot-migrator-remove-original-class-implementation.md"
},
{
"matchCount": 2,
"uri": "ui/operators.py"
},
{
"matchCount": 2,
"uri": ".specstory/history/2026-03-10_16-23Z-dlm-ot-migrator-remove-original-class-update.md"
},
{
"matchCount": 2,
"uri": ".specstory/history/2026-03-10_17-03Z-operator-update-for-dlm-ot-migrator-remove-original.md"
},
{
"matchCount": 2,
"uri": "ops/migrator.py"
},
{
"matchCount": 2,
"uri": ".specstory/history/2026-03-10_16-25-51Z-collection-removal-error-handling.md"
},
{
"matchCount": 2,
"uri": ".specstory/history/2026-03-10_16-23-09Z-dlm_ot_migrator_remove_original-class-update.md"
},
{
"matchCount": 2,
"uri": ".specstory/history/2026-02-18_23-39-18Z-character-migrator-workflow-integration.md"
},
{
"matchCount": 3,
"uri": ".specstory/history/2026-02-18_23-39Z-character-migrator-workflow-integration.md"
},
{
"matchCount": 2,
"uri": "ui\\operators.py"
}
],
"totalFiles": 17,
"totalMatches": 39
}
```
Status: **completed**
</details>
</tool-use>
<tool-use data-tool-type="unknown" data-tool-name="ripgrep_raw_search"><details>
<summary>Tool use: **ripgrep_raw_search**</summary>
</details></tool-use>
_**Agent (composer-2-fast)**_
_**Agent (composer-2-fast)**_
<tool-use data-tool-type="read" data-tool-name="read_file_v2">
<details><summary>Tool use: **read_file_v2** • Read file: T:/Dynamic-Link-Manager/ops/migrator.py</summary>
</details>
</tool-use>
<tool-use data-tool-type="read" data-tool-name="read_file_v2"><details>
<summary>Tool use: **read_file_v2**</summary>
</details></tool-use>
_**Agent (composer-2-fast)**_
<tool-use data-tool-type="read" data-tool-name="read_file_v2">
<details><summary>Tool use: **read_file_v2** • Read file: T:/Dynamic-Link-Manager/ui/operators.py</summary>
</details>
</tool-use>
<tool-use data-tool-type="read" data-tool-name="read_file_v2"><details>
<summary>Tool use: **read_file_v2**</summary>
</details></tool-use>
_**Agent (composer-2-fast)**_
<think><details><summary>Thought Process</summary>
**Updating migration function**
I'm adding `context=None` parameter to `run_mig_nla()` and creating a helper `_activate_rep_for_animlayers()` to manage active object state. </details></think>
_**Agent (composer-2-fast)**_
_**Agent (composer-2-fast)**_
<tool-use data-tool-type="read" data-tool-name="read_file_v2">
<details><summary>Tool use: **read_file_v2** • Read file: T:/Dynamic-Link-Manager/ops/migrator.py</summary>
</details>
</tool-use>
<tool-use data-tool-type="read" data-tool-name="read_file_v2"><details>
<summary>Tool use: **read_file_v2**</summary>
</details></tool-use>
_**Agent (composer-2-fast)**_
Selecting the replacement armature as active before `_mirror_als_turn_on` so the Animation Layers addon receives the correct active object. Adding a context manager and passing `context` through `run_mig_nla`.
_**Agent (composer-2-fast)**_
<tool-use data-tool-type="write" data-tool-name="edit_file_v2">
<details><summary>Tool use: **edit_file_v2** • Edit file: T:/Dynamic-Link-Manager/ops/migrator.py</summary>
</details>
</tool-use>
<tool-use data-tool-type="write" data-tool-name="edit_file_v2"><details>
<summary>Tool use: **edit_file_v2**</summary>
</details></tool-use>
_**Agent (composer-2-fast)**_
<tool-use data-tool-type="write" data-tool-name="edit_file_v2">
<details><summary>Tool use: **edit_file_v2** • Edit file: T:/Dynamic-Link-Manager/ops/migrator.py</summary>
</details>
</tool-use>
<tool-use data-tool-type="write" data-tool-name="edit_file_v2"><details>
<summary>Tool use: **edit_file_v2**</summary>
</details></tool-use>
_**Agent (composer-2-fast)**_
<tool-use data-tool-type="write" data-tool-name="edit_file_v2">
<details><summary>Tool use: **edit_file_v2** • Edit file: T:/Dynamic-Link-Manager/ops/migrator.py</summary>
</details>
</tool-use>
<tool-use data-tool-type="write" data-tool-name="edit_file_v2"><details>
<summary>Tool use: **edit_file_v2**</summary>
</details></tool-use>
_**Agent (composer-2-fast)**_
<tool-use data-tool-type="write" data-tool-name="edit_file_v2">
<details><summary>Tool use: **edit_file_v2** • Edit file: T:/Dynamic-Link-Manager/ops/migrator.py</summary>
</details>
</tool-use>
<tool-use data-tool-type="write" data-tool-name="edit_file_v2"><details>
<summary>Tool use: **edit_file_v2**</summary>
</details></tool-use>
_**Agent (composer-2-fast)**_
<tool-use data-tool-type="write" data-tool-name="edit_file_v2">
<details><summary>Tool use: **edit_file_v2** • Edit file: T:/Dynamic-Link-Manager/ops/migrator.py</summary>
</details>
</tool-use>
<tool-use data-tool-type="write" data-tool-name="edit_file_v2"><details>
<summary>Tool use: **edit_file_v2**</summary>
</details></tool-use>
_**Agent (composer-2-fast)**_
<tool-use data-tool-type="write" data-tool-name="edit_file_v2">
<details><summary>Tool use: **edit_file_v2** • Edit file: T:/Dynamic-Link-Manager/ui/operators.py</summary>
</details>
</tool-use>
<tool-use data-tool-type="write" data-tool-name="edit_file_v2"><details>
<summary>Tool use: **edit_file_v2**</summary>
</details></tool-use>
_**Agent (composer-2-fast)**_
_**Agent (composer-2-fast)**_
<tool-use data-tool-type="unknown" data-tool-name="ripgrep_raw_search">
<details>
<summary>Tool use: **ripgrep_raw_search**</summary>
Parameters:
```json
{
"caseInsensitive": false,
"glob": "*.py",
"path": "T:/Dynamic-Link-Manager",
"pattern": "run_mig_nla\\("
}
```
Additional data:
```json
{
"isPruned": true,
"outputMode": "content",
"path": "T:/Dynamic-Link-Manager",
"pattern": "run_mig_nla\\(",
"topFiles": [
{
"matchCount": 1,
"uri": "ui/operators.py"
},
{
"matchCount": 2,
"uri": "ops/migrator.py"
},
{
"matchCount": 1,
"uri": "ui\\operators.py"
},
{
"matchCount": 2,
"uri": "ops\\migrator.py"
}
],
"totalFiles": 4,
"totalMatches": 6
}
```
Status: **completed**
</details>
</tool-use>
<tool-use data-tool-type="unknown" data-tool-name="ripgrep_raw_search"><details>
<summary>Tool use: **ripgrep_raw_search**</summary>
</details></tool-use>
_**Agent (composer-2-fast)**_
<tool-use data-tool-type="unknown" data-tool-name="read_lints">
<details>
<summary>Tool use: **read_lints**</summary>
Parameters:
```json
{
"paths": [
"T:/Dynamic-Link-Manager/ops/migrator.py",
"T:/Dynamic-Link-Manager/ui/operators.py"
]
}
```
Result:
```json
{
"linterErrorsByFile": [
{
"relativeWorkspacePath": "T:/Dynamic-Link-Manager/ops/migrator.py"
},
{
"relativeWorkspacePath": "T:/Dynamic-Link-Manager/ui/operators.py"
}
]
}
```
Status: **completed**
</details>
</tool-use>
<tool-use data-tool-type="unknown" data-tool-name="read_lints"><details>
<summary>Tool use: **read_lints**</summary>
</details></tool-use>
_**Agent (composer-2-fast)**_
Heres what changed:
### `run_mig_nla` + Animation Layers active object
- Added **`_rep_active_for_animlayers(context, rep)`** (`contextlib.contextmanager`): sets **`context.view_layer.objects.active = rep`** before AnimLayers handling, then restores the previous active object if it still exists.
- **`run_mig_nla(orig, rep, report=None, context=None)`** wraps both **`_mirror_als_turn_on(orig, rep)`** calls (noNLA path and NLA path) in that context manager.
- **`run_full_migration`** now calls **`run_mig_nla(orig, rep, context=context)`**.
- **`DLM_OT_migrator_migrate_nla`** passes **`context=context`** into **`run_mig_nla`**.
If **`context` is omitted** (e.g. scripts), behavior is unchanged and AnimLayers mirroring runs without forcing selection.
---
_**User**_
@c:\Users\Nathan\.cursor\projects\t-Dynamic-Link-Manager\terminals\3.txt doesn't work; if I select origchar, repchar is not selected first. the reason I'm trying to do this is because if repchar is not active when mignla is ran, animlayers will not be activated on repchar and the user must select repchar and activate animlayers again.
---
_**Agent (composer-2-fast)**_
<think><details><summary>Thought Process</summary>
**Checking terminal output**
I'm reading the terminal file to identify any errors causing the repchar activation issue. </details></think>
_**Agent (composer-2-fast)**_
_**Agent (composer-2-fast)**_
<tool-use data-tool-type="read" data-tool-name="read_file_v2">
<details><summary>Tool use: **read_file_v2** • Read file: c:\Users\Nathan\.cursor\projects\t-Dynamic-Link-Manager\terminals\3.txt</summary>
</details>
</tool-use>
<tool-use data-tool-type="read" data-tool-name="read_file_v2"><details>
<summary>Tool use: **read_file_v2**</summary>
</details></tool-use>
_**Agent (composer-2-fast)**_
<tool-use data-tool-type="read" data-tool-name="read_file_v2">
<details><summary>Tool use: **read_file_v2** • Read file: T:/Dynamic-Link-Manager/ops/migrator.py</summary>
</details>
</tool-use>
<tool-use data-tool-type="read" data-tool-name="read_file_v2"><details>
<summary>Tool use: **read_file_v2**</summary>
</details></tool-use>
_**Agent (composer-2-fast)**_
<tool-use data-tool-type="read" data-tool-name="read_file_v2">
<details><summary>Tool use: **read_file_v2** • Read file: T:/Dynamic-Link-Manager/ops/migrator.py</summary>
</details>
</tool-use>
<tool-use data-tool-type="read" data-tool-name="read_file_v2"><details>
<summary>Tool use: **read_file_v2**</summary>
</details></tool-use>
_**Agent (composer-2-fast)**_
<think><details><summary>Thought Process</summary>
**Investigating restore issue**
The issue may be that we're restoring the original active object too early in the `finally` block, before animation layers can process it. The addon likely needs the replacement object to remain active during AnimLayers processing, not the original object. I'll leave `rep` as the active object after MigNLA completes to keep AnimLayers UI active on the replacement object. I'll set rep as active before `_mirror_als_turn_on` and avoid restoring previous active object.
**Testing object activation**
I'm testing if `vl.objects.active = rep` works when rep is hidden or obscured. I need to ensure `rep` is in the current view layer and not hidden or excluded from selection. I'm checking if `rep` needs to be selected explicitly with `Object.select_set(True)` to become active. Setting `view_layer.objects.active = obj` requires `obj.select_set(True)` and object to be in view layer.
**Setting object selection**
I'm using the view layer API to properly deselect all objects before setting the active object. I'm switching to object mode before selecting the replacement object to ensure compatibility across modes. I'm setting the replacement object as active before entering object mode to ensure proper selection.
**Handling mode switching**
I need to switch modes before setting the replacement object as active to avoid conflicts with armature parenting. I need to ensure proper mode switching before setting the replacement object as active to avoid armature parenting conflicts. I'm implementing robust activation logic that ensures proper mode switching before setting the replacement object as active. I'm refining the object selection logic to properly set the active object in Blender. I'm optimizing object selection by using `bpy.ops.object.select_all` instead of looping through objects. I'm switching to OBJECT mode from POSE mode and deselecting all objects before selecting the replacement object.
I'm updating the `_rep_active_for_animlayers` function to enter OBJECT mode, select the replacement object, and skip restoring the active object afterward. I'm implementing the selection restoration logic with try/except for `select_all` and using `context.temp_override` for proper window context. </details></think>
+4 -4
View File
@@ -226,13 +226,13 @@
"last_updated": "2026-03-25T20:22:34Z"
},
"f09112e7-3ddb-4e4e-b91a-418e38b881bf": {
"user_message_count": 14,
"agent_message_count": 199,
"user_message_count": 16,
"agent_message_count": 243,
"start_timestamp": "2026-03-13T14:29:12-06:00",
"end_timestamp": "2026-03-13T14:29:12-06:00",
"markdown_size_bytes": 230631,
"markdown_size_bytes": 253227,
"provider": "cursoride",
"last_updated": "2026-03-25T20:28:01Z"
"last_updated": "2026-03-25T20:37:28Z"
},
"f1bb25c5-e5d4-4aad-88a3-f562ab877040": {
"user_message_count": 1,
+68 -5
View File
@@ -5,11 +5,71 @@
"""Character migrator: migrate animation, constraints, relations from original to replacement armature."""
from contextlib import contextmanager
import bpy
from ..utils import descendants, collection_containing_armature
def _first_view3d_area(context):
win = getattr(context, "window", None)
if not win or not getattr(win, "screen", None):
return None, None
for area in win.screen.areas:
if area.type == "VIEW_3D":
return win, area
return None, None
@contextmanager
def _rep_active_for_animlayers(context, rep):
"""Make rep the only selected active object in Object mode so Animation Layers (als.turn_on) applies to rep.
Does not restore the previous selection: Anim Layers and the UI expect rep to stay active after MigNLA.
"""
if context is None or rep is None:
yield
return
vl = context.view_layer
win, area = _first_view3d_area(context)
# Ops need a VIEW_3D context; selection/active still use view_layer.
if win and area:
with context.temp_override(window=win, area=area):
if context.mode != "OBJECT":
try:
bpy.ops.object.mode_set(mode="OBJECT")
except Exception:
pass
try:
bpy.ops.object.select_all(action="DESELECT")
except Exception:
pass
else:
if context.mode != "OBJECT":
try:
bpy.ops.object.mode_set(mode="OBJECT")
except Exception:
pass
try:
bpy.ops.object.select_all(action="DESELECT")
except Exception:
pass
for ob in vl.objects:
try:
ob.select_set(False)
except Exception:
pass
try:
rep.select_set(True)
except Exception:
pass
vl.objects.active = rep
if bpy.context.view_layer == vl:
bpy.context.view_layer.objects.active = rep
yield
def get_pair_manual(context):
"""Return (orig_armature, rep_armature) from scene props, or (None, None)."""
props = getattr(context.scene, "dynamic_link_manager", None)
@@ -202,9 +262,10 @@ def _duplicate_action(src_action, suffix=".rep"):
return new_action
def run_mig_nla(orig, rep, report=None):
def run_mig_nla(orig, rep, report=None, context=None):
"""Migrate NLA: copy tracks and strips to replacement; or mirror action slot when no NLA (MigNLA).
Actions are duplicated so repchar has independent copies."""
Actions are duplicated so repchar has independent copies.
Pass context so Animation Layers mirroring runs with rep as active object."""
if not orig.animation_data:
return
ad = orig.animation_data
@@ -249,7 +310,8 @@ def run_mig_nla(orig, rep, report=None):
setattr(rad, prop, getattr(ad, prop))
print(f"[DLM MigNLA] set rep {prop}={getattr(ad, prop)!r}")
_slot_debug("Rep (after)", rad)
_mirror_als_turn_on(orig, rep)
with _rep_active_for_animlayers(context, rep):
_mirror_als_turn_on(orig, rep)
if report:
report({"INFO"}, "No NLA detected, active action (duplicated) and slot copied to Replacement Armature.")
return
@@ -296,7 +358,8 @@ def run_mig_nla(orig, rep, report=None):
new_strip.use_animated_time_cyclic = strip.use_animated_time_cyclic
new_strip.use_sync_length = strip.use_sync_length
prev_track = new_track
_mirror_als_turn_on(orig, rep)
with _rep_active_for_animlayers(context, rep):
_mirror_als_turn_on(orig, rep)
if report:
_debug_als_lookup(orig)
has_als = _has_als_anywhere(orig)
@@ -703,7 +766,7 @@ def run_full_migration(context):
try:
run_copy_attr(orig, rep)
run_mig_nla(orig, rep)
run_mig_nla(orig, rep, context=context)
run_mig_cust_props(orig, rep)
run_mig_bone_const(orig, rep, orig_to_rep)
run_retarg_relatives(orig, rep, rep_descendants, orig_to_rep)
+1 -1
View File
@@ -305,7 +305,7 @@ class DLM_OT_migrator_migrate_nla(Operator):
return {"CANCELLED"}
try:
from ..ops.migrator import run_mig_nla
run_mig_nla(orig, rep, report=self.report)
run_mig_nla(orig, rep, report=self.report, context=context)
return {"FINISHED"}
except Exception as e:
self.report({"ERROR"}, str(e))