add manual meshes to migbbody shapekeys

This commit is contained in:
Nathan
2026-03-25 14:48:59 -06:00
parent 7c1969de20
commit b59695f99e
6 changed files with 955 additions and 110 deletions
@@ -4152,4 +4152,793 @@ I'm using the view layer API to properly deselect all objects before setting the
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 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> 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. I'm adjusting the mode switching logic to ensure animlayers are properly activated and OBJECT mode is maintained when needed. I'm removing the restoration of `prev` mode after `_mirror_als_turn_on` and ensuring `bpy.context.view_layer` is properly synced. </details></think>
_**Agent (composer-2-fast)**_
Fixing activation: restoring the previous active object in `finally` immediately returns selection to orig, so AnimLayers won't stay on rep. We should also switch to Object mode before changing the active armature (Pose mode on orig can block switching active). Implementing: force Object mode, deselect all, select `rep`, set active to `rep`, run mirror, and leave `rep` active (don't restore orig).
_**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)**_
_**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)**_
_**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)**_
Fixing a broken `try` block (incomplete structure).
_**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)**_
_**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"
]
}
```
Result:
```json
{
"linterErrorsByFile": [
{
"relativeWorkspacePath": "T:/Dynamic-Link-Manager/ops/migrator.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)**_
<think><details><summary>Thought Process</summary>
**Checking Blender API**
I'm verifying if `select_set` exists on Object in Blender 5 and checking for selection API changes. </details></think>
_**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": "temp_override"
}
```
Additional data:
```json
{
"isPruned": true,
"outputMode": "content",
"path": "T:/Dynamic-Link-Manager",
"pattern": "temp_override",
"topFiles": [
{
"matchCount": 2,
"uri": "ops/fk_rotations.py"
},
{
"matchCount": 2,
"uri": "ops/tweak_tools.py"
}
],
"totalFiles": 2,
"totalMatches": 4
}
```
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/fk_rotations.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>
**Adding view3d helper**
I'm adding a helper function to find the VIEW_3D area and using temp_override for mode_set and select_all operations. </details></think>
_**Agent (composer-2-fast)**_
Using a VIEW_3D `temp_override` so `bpy.ops.object.mode_set` and `select_all` run correctly (they often fail without a 3D View context).
_**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)**_
<think><details><summary>Thought Process</summary>
**Setting active object**
I'm ensuring the active object is set correctly in the view layer to avoid addon issues with `bpy.context.object`. I'm now setting both `vl.objects.active` and syncing `bpy.context` only when view layers match to prevent context mismatches. </details></think>
_**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/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)**_
_**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="unknown" data-tool-name="read_lints">
<details>
<summary>Tool use: **read_lints**</summary>
Parameters:
```json
{
"paths": [
"T:/Dynamic-Link-Manager/ops/migrator.py"
]
}
```
Result:
```json
{
"linterErrorsByFile": [
{
"relativeWorkspacePath": "T:/Dynamic-Link-Manager/ops/migrator.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 was going wrong and what we changed:
### Causes
1. **Selection was restored** The old `finally` set the active object back to **orig**, so AnimLayers never stayed on **rep** and any addon logic tied to the active object ran on the wrong rig.
2. **Active armature in Pose mode** With **orig** in **Pose** mode, only assigning `active = rep` often **does nothing**; you have to go to **Object** mode first, then change selection/active.
3. **Ops need a 3D View** `bpy.ops.object.mode_set` / `select_all` can **no-op** without a **VIEW_3D** `area`, so we now use `context.temp_override(window=..., area=...)` when a 3D View exists.
### Current behavior
- Prefer **Object** mode (via override when possible).
- **Deselect all**, then **`rep.select_set(True)`** and **`view_layer.objects.active = rep`**.
- **Do not** restore the previous active object — **rep** stays active after MigNLA so AnimLayers stays on the replacement.
- Sync **`bpy.context.view_layer.objects.active`** when it matches the same view layer (helps addons that read **`bpy.context`**).
After running MigNLA, **rep** should remain the active, selected armature in Object mode so you dont have to pick it again to turn AnimLayers on.
---
_**User**_
Excellent. Explain to me what migcustprops currently does?
---
_**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)**_
_**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)**_
Heres what **MigCustProps** (`run_mig_cust_props`) does in plain terms:
### Purpose
It copies **Blender ID custom properties** (the things you see in the Object Data Properties / bone panels as **ID Properties**, not regular RNA) from the **original armature** to the **replacement armature**, for the **armature object** and for **matching pose bones by name**.
### What gets copied
- **Armature object** (`orig``rep`): every key in `orig.keys()` except:
- `_RNA_UI`
- `rigify_type`
- `rigify_parameters`
- **Pose bones**: for each bone on `orig` that also exists on `rep`, it copies that bones ID props the same way.
### How nested data is handled
Values can be **leaf** values (numbers, strings, etc.) or **nested groups** (dict-like ID groups).
`_copy_id_prop_recursive` walks groups **recursively** and rebuilds the same structure on the rep side (e.g. nested `Settings` / `Devices`-style trees).
### What it does *not* do
- It does **not** copy normal **RNA** properties (only **ID props** via `.keys()`).
- It does **not** touch bones that arent on both rigs (same bone name on `rep`).
### Debug / logging
In the current code, **`debug = True`**, so it **prints a lot** to the console: keys before/after, bone lists, and `Settings` bone details when `Settings` exists on the rep rig.
So in one line: **MigCustProps = mirror addon/rig custom ID props from orig character to rep character (object + bones), with nested groups supported, and a few Rigify keys excluded.**
---
_**User**_
@c:\Users\Nathan\.cursor\projects\t-Dynamic-Link-Manager\terminals\3.txt looks like it recognizes everything. it's probably good for ARP.
---
_**Agent (composer-2-fast)**_
That lines up with how **MigCustProps** is written: it doesnt care about Rigify vs ARP labels—it only copies **ID properties** from the armature object and from **pose bones that exist on both** rigs under the **same names**.
So for ARP, as long as the replacement rigs bones are named the same as the original (typical after a duplicate / same metarig workflow), those custom props (rig UI, Settings-style groups, etc.) should migrate the same way. The main differences for ARP in your pipeline are things that *are* naming- or structure-specific (automatic pair discovery, tweak/FK bone lists, Remove Original collection logic)—not this step.
If you ever hit a prop that doesnt copy, its usually a bone rename mismatch or an excluded key like `rigify_type` / `rigify_parameters`—we could add ARP-specific excludes later if something shouldnt be mirrored.
---
_**User**_
bpy.ops.dlm.migrator_bone_constraints() is definitely going to need some caution at least. does our current code ignore bone constraints that are on the linked armature in the first place?
---
_**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)**_
<think><details><summary>Thought Process</summary>
**Checking bone constraints**
I'm checking if the code properly handles linked armatures by examining `orig.library` and `pbone.constraints`. I'm testing if reading constraints from `orig.pose.bones` works for linked data and writing to `rep` constraints. </details></think>
_**Agent (composer-2-fast)**_
**No.** `run_mig_bone_const` does **not** skip or special-case bone constraints because the armature is linked.
What it actually does:
1. **Clears** constraints on `rep` whose **target** is another “original” in `orig_to_rep` (only matters if you had multiple originals in that map; the usual pair is just `{orig: rep}`).
2. **For every bone name that exists on both `orig` and `rep`**, it walks **`pbone.constraints` on `orig`** and, for each constraint, does **`rbone.constraints.new(type=c.type)`** on `rep` and copies RNA props via **`_copy_constraint_props`** (with pointer retargeting for `orig``rep`).
There is **no** check for:
- `orig.library` / `orig.override_library`
- `rep.library` / `rep.override_library`
- whether constraints “came from” the linked file vs. were added locally
So if **`orig` is linked**, the addon still **reads** those constraints and **tries to mirror** them onto **`rep`** (assuming `rep` is editable). If **`rep` is still fully linked and not overriden**, adding constraints can **fail** (Blender wont let you edit linked pose data).
So for ARP / library rigs, **caution is warranted**: youre not ignoring linked rig internals; youre **intentionally cloning** whatever is on `orig`s bones onto `rep`, unless Blender blocks writes. If you want “dont copy library-only rig constraints,” that would be a **new** rule (e.g. skip when `orig` is linked, or skip by constraint name/type, or only run when `rep` is local/overridden).
---
_**User**_
Ok, bpy.ops.dlm.migrator_basebody_shapekeys() doesn't work because this isn't a cc/ic rig. Let's just add 2 new fields above the migbbody button that allows the user to select the body of the orig and repchars, deactivated by default, and activated when migbbody fails to find its target.
---
_**Agent (composer-2-fast)**_
<think><details><summary>Thought Process</summary>
**Adding mesh properties**
I'm adding two new PointerProperty fields for manual mesh selection. I'm implementing conditional UI activation for mesh properties based on MigBBody failure detection. I'm adding two optional object pointers for body meshes with UI disabled by default and activated on MigBBody failure. I'm implementing two optional Mesh PointerProperties (`migbbody_orig_body`, `migbbody_rep_body`) with a BoolProperty (`migbbody_use_manual_bodies`) to control their activation. I'm implementing automatic activation of manual body properties when shapekey detection fails during MigBBody execution. </details></think>
_**Agent (composer-2-fast)**_
Inspecting how MigBBody finds base bodies and how the operator invokes it.
_**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="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": "migrator_basebody|run_mig_bbody"
}
```
Additional data:
```json
{
"isPruned": true,
"outputMode": "content",
"path": "T:/Dynamic-Link-Manager",
"pattern": "migrator_basebody|run_mig_bbody",
"topFiles": [
{
"matchCount": 5,
"uri": "ui/operators.py"
},
{
"matchCount": 1,
"uri": "ui\\panels.py"
},
{
"matchCount": 1,
"uri": "ui/panels.py"
},
{
"matchCount": 5,
"uri": "ui\\operators.py"
},
{
"matchCount": 2,
"uri": "ops\\migrator.py"
},
{
"matchCount": 2,
"uri": "ops/migrator.py"
}
],
"totalFiles": 6,
"totalMatches": 16
}
```
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)**_
<think><details><summary>Thought Process</summary>
**Updating migration logic**
I'm adding explicit mesh object parameters for manual override in migration logic.
**Adding mesh pointers**
I'm adding pointer properties for original and replacement body meshes with UI rows disabled by default. I'm implementing pointer properties for original and replacement body meshes with manual enable flag. I'm implementing a BoolProperty `migbbody_manual_active` that enables UI rows when auto migration fails. I'm replacing the bool property with a single flag `migbbody_use_manual_bodies` that user enables to activate pickers and automatically sets True when operator detects failure. </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)**_
<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/panels.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>
**Refactoring loop logic**
I'm extracting the core loop body logic into a new function `_process_mig_bbody_pair` to improve readability. </details></think>
_**Agent (composer-2-fast)**_
Implementing manual body pointers, UI (disabled until auto fails or user enables), and refactoring the mesh-processing block into a reusable helper.
_**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>
**Refactoring loop body**
I'm extracting the inner loop logic into `_process_mig_bbody_mesh` that modifies the mesh object in-place. I'm moving lines 645-748 into `override_hierarchy_create` and will pass `manual_orig_body` and `manual_rep_body` parameters to `run_mig_bbody_shapekeys`. I'm extracting manual override logic from `run_mig_bbody_shapekeys` into a separate function. I'm tracking processed count and found issues when orig_base is missing during loop iterations. I'm simplifying the loop logic to track when no shapes are processed and set auto_found_none flag accordingly. I'm adding an `any_auto` flag to track when auto-processing occurs in the loop.
I'm setting `any_auto` to True when auto-processing occurs in the loop. I'm implementing auto-first logic for mesh migration processing. I'm adding logic to enable UI rows on failure and support manual body selection for mesh migration.
I'm implementing conditional logic for manual body selection in mesh migration. I'm simplifying manual body selection logic to trigger on both pointers being set.
I'm implementing `migbbody_manual_override` bool property to control manual vs auto path logic.
**Implementing override property**
I'm implementing `migbbody_manual_override` property with default False and enabling UI rows based on its value. I'm adding `migbbody_manual_override` BoolProperty with default False and connecting it to UI row visibility.
**Connecting override property**
I'm linking the override property to UI row visibility and setting default values. I'm implementing the migration shapekeys logic with manual override handling.
**Handling override property**
I'm implementing the logic to set `migbbody_manual_override` when auto-processing fails and properties exist. I'm prioritizing auto-processing over manual override, skipping auto only when manual override is active and both fields are filled.
I'm prioritizing manual override path on first run when both bodies are set, skipping auto processing to avoid useless second run. I'm setting `props.migbbody_manual_override = True` only when auto processing fails, not on successful auto runs. </details></think>
_**Agent (composer-2-fast)**_
Refactoring mesh processing into `_process_mig_bbody_mesh`, adding scene properties and UI, and implementing manual-first when override is set with both pointers.
_**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/properties.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>
**Adding body mesh props**
I'm adding new properties to control manual body mesh detection in the migration tool. </details></think>
_**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/properties.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/panels.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>
+4 -4
View File
@@ -226,13 +226,13 @@
"last_updated": "2026-03-25T20:22:34Z" "last_updated": "2026-03-25T20:22:34Z"
}, },
"f09112e7-3ddb-4e4e-b91a-418e38b881bf": { "f09112e7-3ddb-4e4e-b91a-418e38b881bf": {
"user_message_count": 16, "user_message_count": 20,
"agent_message_count": 243, "agent_message_count": 298,
"start_timestamp": "2026-03-13T14:29:12-06:00", "start_timestamp": "2026-03-13T14:29:12-06:00",
"end_timestamp": "2026-03-13T14:29:12-06:00", "end_timestamp": "2026-03-13T14:29:12-06:00",
"markdown_size_bytes": 253227, "markdown_size_bytes": 277098,
"provider": "cursoride", "provider": "cursoride",
"last_updated": "2026-03-25T20:37:28Z" "last_updated": "2026-03-25T20:47:25Z"
}, },
"f1bb25c5-e5d4-4aad-88a3-f562ab877040": { "f1bb25c5-e5d4-4aad-88a3-f562ab877040": {
"user_message_count": 1, "user_message_count": 1,
+121 -104
View File
@@ -629,9 +629,126 @@ def _find_base_body(armature, descendants_iter, rep_base_name=None):
return candidates[0] return candidates[0]
def _process_mig_bbody_mesh(orig_base, ob, context):
"""Library overrides on rep mesh ob; copy shape key values and action from orig_base mesh."""
# Debug: base body mesh state before override handling.
_lib = getattr(ob.data, "library", None)
_ol = getattr(ob.data, "override_library", None)
_sys = getattr(_ol, "is_system_override", None) if _ol else None
print(f"[DLM step6] {ob.name} data: linked={_lib is not None}, override={_ol is not None}, is_system_override={_sys}")
# Library override: use hierarchy create (fully editable) when context available, else single-id override.
if getattr(ob, "library", None):
if context:
try:
ob = ob.override_hierarchy_create(
context.scene, context.view_layer, do_fully_editable=True
)
except Exception:
try:
ob.override_create()
except Exception:
pass
else:
try:
ob.override_create()
except Exception:
pass
if getattr(ob.data, "library", None):
try:
ob.data.override_create(remap_local_usages=True)
# Make override user-editable (same as shift-click in data tab).
ol = getattr(ob.data, "override_library", None)
if ol is not None and getattr(ol, "is_system_override", None) is not None:
try:
ol.is_system_override = False
except Exception as e:
print(f"[DLM step6] {ob.name} set is_system_override=False: {e}")
except Exception as e:
print(f"[DLM step6] {ob.name} ob.data.override_create: {e}")
elif getattr(ob.data, "override_library", None):
ol = ob.data.override_library
if getattr(ol, "is_system_override", False):
try:
ol.is_system_override = False
except Exception as e:
print(f"[DLM step6] {ob.name} set is_system_override=False: {e}")
# Debug: state after override handling.
_ol2 = getattr(ob.data, "override_library", None)
_sys2 = getattr(_ol2, "is_system_override", None) if _ol2 else None
print(f"[DLM step6] {ob.name} after: override={_ol2 is not None}, is_system_override={_sys2} (False=editable)")
if ob.data.shape_keys:
# Ensure we can write shape key values: override the Key block if it is linked.
sk = ob.data.shape_keys
if getattr(sk, "library", None):
try:
sk.override_create(remap_local_usages=True)
except Exception as e:
print(f"[DLM step6] {ob.name} shape_keys.override_create: {e}")
# Copy shape key values from original base body to replacement (by matching key name).
if orig_base and orig_base.data.shape_keys:
rep_blocks = ob.data.shape_keys.key_blocks
orig_blocks = orig_base.data.shape_keys.key_blocks
n_copied = 0
for orig_key in orig_blocks:
rep_key = rep_blocks.get(orig_key.name)
if rep_key is not None:
rep_key.value = orig_key.value
n_copied += 1
print(f"[DLM step6] {ob.name} shapekey values: copied {n_copied}/{len(orig_blocks)} from {orig_base.name}")
else:
if not orig_base:
print(f"[DLM step6] {ob.name} no orig base body found")
elif not orig_base.data.shape_keys:
print(f"[DLM step6] {ob.name} orig base body has no shape_keys")
if not ob.data.shape_keys.animation_data:
ob.data.shape_keys.animation_data_create()
sk_ad = ob.data.shape_keys.animation_data
# Prefer action (and slot) from original base body; fallback to name lookup.
orig_sk_ad = None
if orig_base and orig_base.data.shape_keys:
orig_sk_ad = orig_base.data.shape_keys.animation_data
action = None
if orig_sk_ad and getattr(orig_sk_ad, "action", None):
action = orig_sk_ad.action
if action is None:
body_name = ob.name
action = (
bpy.data.actions.get(body_name + "Action")
or bpy.data.actions.get(ob.data.name + "Action")
or bpy.data.actions.get(body_name + "Action.001")
)
if action:
# Duplicate action so repchar has independent copy
dup_action = _duplicate_action(action, suffix=".rep")
# Copy slot-related props before action so slot is applied (Blender 4.4+).
if orig_sk_ad and hasattr(sk_ad, "last_slot_identifier") and hasattr(orig_sk_ad, "last_slot_identifier") and orig_sk_ad.last_slot_identifier:
sk_ad.last_slot_identifier = orig_sk_ad.last_slot_identifier
sk_ad.action = dup_action
if orig_sk_ad and getattr(orig_sk_ad, "action_slot", None) and getattr(sk_ad, "action_slot", None):
try:
sk_ad.action_slot = orig_sk_ad.action_slot
except Exception:
pass
for prop in ("action_blend_type", "action_extrapolation", "action_influence"):
if orig_sk_ad and hasattr(orig_sk_ad, prop) and hasattr(sk_ad, prop):
try:
setattr(sk_ad, prop, getattr(orig_sk_ad, prop))
except Exception:
pass
def run_mig_bbody_shapekeys(orig, rep, rep_descendants, context=None): def run_mig_bbody_shapekeys(orig, rep, rep_descendants, context=None):
"""Replacement base body: library override (fully editable when context given), copy shapekey values, then shape-key action.""" """Replacement base body: library override (fully editable when context given), copy shapekey values, then shape-key action."""
props = getattr(context.scene, "dynamic_link_manager", None) if context else None
if props and getattr(props, "migbbody_manual_override", False):
mo = getattr(props, "migbbody_orig_body", None)
mr = getattr(props, "migbbody_rep_body", None)
if mo and mr and mo.type == "MESH" and mr.type == "MESH":
_process_mig_bbody_mesh(mo, mr, context)
return
orig_descendants = list(descendants(orig)) orig_descendants = list(descendants(orig))
any_auto = False
for ob in list(rep_descendants): for ob in list(rep_descendants):
if not _base_body_name_match(ob): if not _base_body_name_match(ob):
continue continue
@@ -641,111 +758,11 @@ def run_mig_bbody_shapekeys(orig, rep, rep_descendants, context=None):
break break
else: else:
continue continue
any_auto = True
orig_base = _find_base_body(orig, orig_descendants, rep_base_name=ob.name) orig_base = _find_base_body(orig, orig_descendants, rep_base_name=ob.name)
# Debug: base body mesh state before override handling. _process_mig_bbody_mesh(orig_base, ob, context)
_lib = getattr(ob.data, "library", None) if not any_auto and props is not None:
_ol = getattr(ob.data, "override_library", None) props.migbbody_manual_override = True
_sys = getattr(_ol, "is_system_override", None) if _ol else None
print(f"[DLM step6] {ob.name} data: linked={_lib is not None}, override={_ol is not None}, is_system_override={_sys}")
# Library override: use hierarchy create (fully editable) when context available, else single-id override.
if getattr(ob, "library", None):
if context:
try:
ob = ob.override_hierarchy_create(
context.scene, context.view_layer, do_fully_editable=True
)
except Exception:
try:
ob.override_create()
except Exception:
pass
else:
try:
ob.override_create()
except Exception:
pass
if getattr(ob.data, "library", None):
try:
ob.data.override_create(remap_local_usages=True)
# Make override user-editable (same as shift-click in data tab).
ol = getattr(ob.data, "override_library", None)
if ol is not None and getattr(ol, "is_system_override", None) is not None:
try:
ol.is_system_override = False
except Exception as e:
print(f"[DLM step6] {ob.name} set is_system_override=False: {e}")
except Exception as e:
print(f"[DLM step6] {ob.name} ob.data.override_create: {e}")
elif getattr(ob.data, "override_library", None):
ol = ob.data.override_library
if getattr(ol, "is_system_override", False):
try:
ol.is_system_override = False
except Exception as e:
print(f"[DLM step6] {ob.name} set is_system_override=False: {e}")
# Debug: state after override handling.
_ol2 = getattr(ob.data, "override_library", None)
_sys2 = getattr(_ol2, "is_system_override", None) if _ol2 else None
print(f"[DLM step6] {ob.name} after: override={_ol2 is not None}, is_system_override={_sys2} (False=editable)")
if ob.data.shape_keys:
# Ensure we can write shape key values: override the Key block if it is linked.
sk = ob.data.shape_keys
if getattr(sk, "library", None):
try:
sk.override_create(remap_local_usages=True)
except Exception as e:
print(f"[DLM step6] {ob.name} shape_keys.override_create: {e}")
# Copy shape key values from original base body to replacement (by matching key name).
if orig_base and orig_base.data.shape_keys:
rep_blocks = ob.data.shape_keys.key_blocks
orig_blocks = orig_base.data.shape_keys.key_blocks
n_copied = 0
for orig_key in orig_blocks:
rep_key = rep_blocks.get(orig_key.name)
if rep_key is not None:
rep_key.value = orig_key.value
n_copied += 1
print(f"[DLM step6] {ob.name} shapekey values: copied {n_copied}/{len(orig_blocks)} from {orig_base.name}")
else:
if not orig_base:
print(f"[DLM step6] {ob.name} no orig base body found for armature {orig.name}")
elif not orig_base.data.shape_keys:
print(f"[DLM step6] {ob.name} orig base body has no shape_keys")
if not ob.data.shape_keys.animation_data:
ob.data.shape_keys.animation_data_create()
sk_ad = ob.data.shape_keys.animation_data
# Prefer action (and slot) from original base body; fallback to name lookup.
orig_sk_ad = None
if orig_base and orig_base.data.shape_keys:
orig_sk_ad = orig_base.data.shape_keys.animation_data
action = None
if orig_sk_ad and getattr(orig_sk_ad, "action", None):
action = orig_sk_ad.action
if action is None:
body_name = ob.name
action = (
bpy.data.actions.get(body_name + "Action")
or bpy.data.actions.get(ob.data.name + "Action")
or bpy.data.actions.get(body_name + "Action.001")
)
if action:
# Duplicate action so repchar has independent copy
dup_action = _duplicate_action(action, suffix=".rep")
# Copy slot-related props before action so slot is applied (Blender 4.4+).
if orig_sk_ad and hasattr(sk_ad, "last_slot_identifier") and hasattr(orig_sk_ad, "last_slot_identifier") and orig_sk_ad.last_slot_identifier:
sk_ad.last_slot_identifier = orig_sk_ad.last_slot_identifier
sk_ad.action = dup_action
if orig_sk_ad and getattr(orig_sk_ad, "action_slot", None) and getattr(sk_ad, "action_slot", None):
try:
sk_ad.action_slot = orig_sk_ad.action_slot
except Exception:
pass
for prop in ("action_blend_type", "action_extrapolation", "action_influence"):
if orig_sk_ad and hasattr(orig_sk_ad, prop) and hasattr(sk_ad, prop):
try:
setattr(sk_ad, prop, getattr(orig_sk_ad, prop))
except Exception:
pass
def run_full_migration(context): def run_full_migration(context):
+10 -1
View File
@@ -399,7 +399,16 @@ class DLM_OT_migrator_basebody_shapekeys(Operator):
from ..utils import descendants from ..utils import descendants
rep_descendants = descendants(rep) rep_descendants = descendants(rep)
run_mig_bbody_shapekeys(orig, rep, rep_descendants, context) run_mig_bbody_shapekeys(orig, rep, rep_descendants, context)
self.report({"INFO"}, "Migrate BaseBody shapekeys done.") props = context.scene.dynamic_link_manager
if props.migbbody_manual_override and (
not props.migbbody_orig_body or not props.migbbody_rep_body
):
self.report(
{"WARNING"},
"MigBBody: no CC-style base mesh matched. Pick Original/Replacement body meshes, then run again.",
)
else:
self.report({"INFO"}, "Migrate BaseBody shapekeys done.")
return {"FINISHED"} return {"FINISHED"}
except Exception as e: except Exception as e:
self.report({"ERROR"}, str(e)) self.report({"ERROR"}, str(e))
+8
View File
@@ -98,6 +98,14 @@ class DLM_PT_main_panel(Panel):
# Situational # Situational
situational_box = layout.box() situational_box = layout.box()
situational_box.label(text="Situational Fixes", icon="QUESTION") situational_box.label(text="Situational Fixes", icon="QUESTION")
row = situational_box.row()
row.prop(props, "migbbody_manual_override", text="Manual body meshes")
row = situational_box.row()
row.enabled = props.migbbody_manual_override
row.prop(props, "migbbody_orig_body", text="Original body")
row = situational_box.row()
row.enabled = props.migbbody_manual_override
row.prop(props, "migbbody_rep_body", text="Replacement body")
row = situational_box.row(align=True) row = situational_box.row(align=True)
row.operator("dlm.migrator_basebody_shapekeys", text="MigBBodyShapeKeys", icon="SHAPEKEY_DATA") row.operator("dlm.migrator_basebody_shapekeys", text="MigBBodyShapeKeys", icon="SHAPEKEY_DATA")
row = situational_box.row(align=True) row = situational_box.row(align=True)
+22
View File
@@ -78,6 +78,28 @@ class DynamicLinkManagerProperties(PropertyGroup):
poll=lambda self, obj: obj and obj.type == "ARMATURE", poll=lambda self, obj: obj and obj.type == "ARMATURE",
) )
# MigBBody: manual mesh pair when CC/iClone-style auto-detection fails
migbbody_manual_override: BoolProperty(
name="Manual body meshes",
description=(
"Enable to pick original and replacement body meshes manually. "
"Auto-enables if MigBBody cannot find a base mesh (non-CC rigs)."
),
default=False,
)
migbbody_orig_body: PointerProperty(
name="Original body",
description="Original character body mesh (shape key source)",
type=bpy.types.Object,
poll=lambda self, obj: obj and obj.type == "MESH",
)
migbbody_rep_body: PointerProperty(
name="Replacement body",
description="Replacement character body mesh (shape key target)",
type=bpy.types.Object,
poll=lambda self, obj: obj and obj.type == "MESH",
)
# Tweak tools (collapsible section) # Tweak tools (collapsible section)
tweak_tools_section_expanded: BoolProperty( tweak_tools_section_expanded: BoolProperty(
name="Tweak Tools Expanded", name="Tweak Tools Expanded",