fix MigBoneConst not migrating properties/targets of constraints

This commit is contained in:
Nathan
2026-02-19 14:14:06 -07:00
parent 8798c249d7
commit 20f81d0e1e
2 changed files with 628 additions and 18 deletions
@@ -13884,3 +13884,572 @@ _**User**_
--- ---
_**Agent (model default, mode Agent)**_
<think><details><summary>Thought Process</summary>
The user says AnimLayers is still being detected improperly. They're suggesting I grep how the animation layers addon works. The addon might be installed in Blender's addons folder - I should search for it. Common locations would be:
- C:\Program Files\Blender Foundation\Blender 5.0\5.0\scripts\addons
- Or a user addon path
- The terminal mentioned "animation_layers_updater addon" - so there might be a folder like "animation_layers" or "animation_layers_updater"
Let me search the user's system for the animation layers addon so we can see how it stores the "turn on" / layer state. I'll search in common Blender addon paths and also in the workspace.
</details></think>
---
Searching for the Animation Layers addon to see how it stores its state:
---
<tool-use data-tool-type="generic" data-tool-name="glob_file_search">
<details>
<summary>Tool use: **glob_file_search** • Searched codebase "undefined" • **16** results in **1** directory</summary>
Directory: **C:\Program Files\Blender Foundation\Blender 5.0** (16 files)
| File |
|------|
| `../C:\Program Files\Blender Foundation\Blender 5.0\portable\scripts\addons\Animation_Layers\animation_layers_updater\Animation_Layers_updater_status.json` |
| `../C:\Program Files\Blender Foundation\Blender 5.0\portable\scripts\addons\Animation_Layers\multikey.py` |
| `../C:\Program Files\Blender Foundation\Blender 5.0\portable\scripts\addons\Animation_Layers\subscriptions.py` |
| `../C:\Program Files\Blender Foundation\Blender 5.0\portable\scripts\addons\Animation_Layers\anim_layers.py` |
| `../C:\Program Files\Blender Foundation\Blender 5.0\portable\scripts\addons\Animation_Layers\bake_ops.py` |
| `../C:\Program Files\Blender Foundation\Blender 5.0\portable\scripts\addons\Animation_Layers\addon_updater_ops.py` |
| `../C:\Program Files\Blender Foundation\Blender 5.0\portable\scripts\addons\Animation_Layers\addon_updater.py` |
| `../C:\Program Files\Blender Foundation\Blender 5.0\portable\scripts\addons\Animation_Layers\__init__.py` |
| `../C:\Program Files\Blender Foundation\Blender 5.0\portable\scripts\addons\Animation_Layers\animation_layers_updater\backup\animation_layers_updater\Animation_Layers_updater_status.json` |
| `../C:\Program Files\Blender Foundation\Blender 5.0\portable\scripts\addons\Animation_Layers\animation_layers_updater\backup\subscriptions.py` |
| `../C:\Program Files\Blender Foundation\Blender 5.0\portable\scripts\addons\Animation_Layers\animation_layers_updater\backup\__init__.py` |
| `../C:\Program Files\Blender Foundation\Blender 5.0\portable\scripts\addons\Animation_Layers\animation_layers_updater\backup\anim_layers.py` |
| `../C:\Program Files\Blender Foundation\Blender 5.0\portable\scripts\addons\Animation_Layers\animation_layers_updater\backup\bake_ops.py` |
| `../C:\Program Files\Blender Foundation\Blender 5.0\portable\scripts\addons\Animation_Layers\animation_layers_updater\backup\multikey.py` |
| `../C:\Program Files\Blender Foundation\Blender 5.0\portable\scripts\addons\Animation_Layers\animation_layers_updater\backup\addon_updater.py` |
| `../C:\Program Files\Blender Foundation\Blender 5.0\portable\scripts\addons\Animation_Layers\animation_layers_updater\backup\addon_updater_ops.py` |
</details>
</tool-use>
---
<tool-use data-tool-type="unknown" data-tool-name="ripgrep_raw_search">
<details>
<summary>Tool use: **ripgrep_raw_search**</summary>
Parameters:
```json
{
"pattern": "turn_on|als\\.|animation.layer",
"path": "C:\\Program Files\\Blender Foundation\\Blender 5.0",
"glob": "*.py",
"outputMode": "content",
"contextBefore": 0,
"contextAfter": 0,
"caseInsensitive": false
}
```
Additional data:
```json
{
"isPruned": true,
"pattern": "turn_on|als\\.|animation.layer",
"path": "C:\\Program Files\\Blender Foundation\\Blender 5.0",
"outputMode": "content",
"totalFiles": 457,
"totalMatches": 2148,
"topFiles": [
{
"uri": "C:\\Program Files\\Blender Foundation\\Blender 5.0\\portable\\extensions\\.local\\lib\\python3.11\\site-packages\\typing_extensions.py",
"matchCount": 8
},
{
"uri": "C:\\Program Files\\Blender Foundation\\Blender 5.0\\portable\\extensions\\.local\\lib\\python3.11\\site-packages\\regex\\test_regex.py",
"matchCount": 1
},
{
"uri": "C:\\Program Files\\Blender Foundation\\Blender 5.0\\portable\\extensions\\.local\\lib\\python3.11\\site-packages\\regex\\regex.py",
"matchCount": 1
},
{
"uri": "C:\\Program Files\\Blender Foundation\\Blender 5.0\\portable\\extensions\\.local\\lib\\python3.11\\site-packages\\regex\\_regex_core.py",
"matchCount": 3
},
{
"uri": "C:\\Program Files\\Blender Foundation\\Blender 5.0\\portable\\extensions\\.local\\lib\\python3.11\\site-packages\\rdflib\\xsd_datetime.py",
"matchCount": 1
},
{
"uri": "C:\\Program Files\\Blender Foundation\\Blender 5.0\\portable\\extensions\\.local\\lib\\python3.11\\site-packages\\rdflib\\term.py",
"matchCount": 2
},
{
"uri": "C:\\Program Files\\Blender Foundation\\Blender 5.0\\portable\\extensions\\.local\\lib\\python3.11\\site-packages\\rdflib\\plugins\\parsers\\notation3.py",
"matchCount": 1
},
{
"uri": "C:\\Program Files\\Blender Foundation\\Blender 5.0\\portable\\extensions\\.local\\lib\\python3.11\\site-packages\\rdflib\\namespace\\_TIME.py",
"matchCount": 2
},
{
"uri": "C:\\Program Files\\Blender Foundation\\Blender 5.0\\portable\\extensions\\.local\\lib\\python3.11\\site-packages\\rdflib\\namespace\\_SH.py",
"matchCount": 3
},
{
"uri": "C:\\Program Files\\Blender Foundation\\Blender 5.0\\portable\\extensions\\.local\\lib\\python3.11\\site-packages\\rdflib\\namespace\\_SDO.py",
"matchCount": 8
},
{
"uri": "C:\\Program Files\\Blender Foundation\\Blender 5.0\\portable\\extensions\\.local\\lib\\python3.11\\site-packages\\rdflib\\namespace\\_PROV.py",
"matchCount": 1
},
{
"uri": "C:\\Program Files\\Blender Foundation\\Blender 5.0\\portable\\extensions\\.local\\lib\\python3.11\\site-packages\\rdflib\\namespace\\_OWL.py",
"matchCount": 5
},
{
"uri": "C:\\Program Files\\Blender Foundation\\Blender 5.0\\portable\\extensions\\.local\\lib\\python3.11\\site-packages\\dateutil\\rrule.py",
"matchCount": 5
},
{
"uri": "C:\\Program Files\\Blender Foundation\\Blender 5.0\\portable\\extensions\\.local\\lib\\python3.11\\site-packages\\jsonschema\\_keywords.py",
"matchCount": 1
},
{
"uri": "C:\\Program Files\\Blender Foundation\\Blender 5.0\\portable\\extensions\\.local\\lib\\python3.11\\site-packages\\joblib\\test\\test_parallel.py",
"matchCount": 2
},
{
"uri": "C:\\Program Files\\Blender Foundation\\Blender 5.0\\portable\\extensions\\.local\\lib\\python3.11\\site-packages\\joblib\\test\\test_module.py",
"matchCount": 1
},
{
"uri": "C:\\Program Files\\Blender Foundation\\Blender 5.0\\portable\\extensions\\.local\\lib\\python3.11\\site-packages\\joblib\\test\\test_memmapping.py",
"matchCount": 2
},
{
"uri": "C:\\Program Files\\Blender Foundation\\Blender 5.0\\portable\\extensions\\.local\\lib\\python3.11\\site-packages\\joblib\\externals\\loky\\backend\\resource_tracker.py",
"matchCount": 1
},
{
"uri": "C:\\Program Files\\Blender Foundation\\Blender 5.0\\portable\\extensions\\.local\\lib\\python3.11\\site-packages\\joblib\\externals\\loky\\backend\\reduction.py",
"matchCount": 1
},
{
"uri": "C:\\Program Files\\Blender Foundation\\Blender 5.0\\portable\\extensions\\.local\\lib\\python3.11\\site-packages\\joblib\\externals\\loky\\backend\\popen_loky_win32.py",
"matchCount": 1
}
]
}
```
Status: **completed**
</details>
</tool-use>
---
<think><details><summary>Thought Process</summary>
The grep hit a lot of unrelated files. Let me search specifically inside the Animation_Layers addon folder for turn_on, als, nla, layer state, etc.
</details></think>
---
---
<tool-use data-tool-type="unknown" data-tool-name="ripgrep_raw_search">
<details>
<summary>Tool use: **ripgrep_raw_search**</summary>
Parameters:
```json
{
"pattern": "turn_on|als\\.|\\[.als|animation_data|nla_tracks",
"path": "C:\\Program Files\\Blender Foundation\\Blender 5.0\\portable\\scripts\\addons\\Animation_Layers",
"glob": "*.py",
"caseInsensitive": false
}
```
Additional data:
```json
{
"isPruned": true,
"pattern": "turn_on|als\\.|\\[.als|animation_data|nla_tracks",
"path": "C:\\Program Files\\Blender Foundation\\Blender 5.0\\portable\\scripts\\addons\\Animation_Layers",
"outputMode": "content",
"totalFiles": 10,
"totalMatches": 997,
"topFiles": [
{
"uri": "C:\\Program Files\\Blender Foundation\\Blender 5.0\\portable\\scripts\\addons\\Animation_Layers\\subscriptions.py",
"matchCount": 70
},
{
"uri": "C:\\Program Files\\Blender Foundation\\Blender 5.0\\portable\\scripts\\addons\\Animation_Layers\\multikey.py",
"matchCount": 13
},
{
"uri": "C:\\Program Files\\Blender Foundation\\Blender 5.0\\portable\\scripts\\addons\\Animation_Layers\\bake_ops.py",
"matchCount": 105
},
{
"uri": "C:\\Program Files\\Blender Foundation\\Blender 5.0\\portable\\scripts\\addons\\Animation_Layers\\anim_layers.py",
"matchCount": 310
},
{
"uri": "C:\\Program Files\\Blender Foundation\\Blender 5.0\\portable\\scripts\\addons\\Animation_Layers\\__init__.py",
"matchCount": 1
},
{
"uri": "C:\\Program Files\\Blender Foundation\\Blender 5.0\\portable\\scripts\\addons\\Animation_Layers\\animation_layers_updater\\backup\\__init__.py",
"matchCount": 1
},
{
"uri": "C:\\Program Files\\Blender Foundation\\Blender 5.0\\portable\\scripts\\addons\\Animation_Layers\\animation_layers_updater\\backup\\subscriptions.py",
"matchCount": 70
},
{
"uri": "C:\\Program Files\\Blender Foundation\\Blender 5.0\\portable\\scripts\\addons\\Animation_Layers\\animation_layers_updater\\backup\\multikey.py",
"matchCount": 13
},
{
"uri": "C:\\Program Files\\Blender Foundation\\Blender 5.0\\portable\\scripts\\addons\\Animation_Layers\\animation_layers_updater\\backup\\bake_ops.py",
"matchCount": 105
},
{
"uri": "C:\\Program Files\\Blender Foundation\\Blender 5.0\\portable\\scripts\\addons\\Animation_Layers\\animation_layers_updater\\backup\\anim_layers.py",
"matchCount": 309
}
]
}
```
Status: **completed**
</details>
</tool-use>
---
<tool-use data-tool-type="read" data-tool-name="read_file_v2">
<details><summary>Tool use: **read_file_v2** • Read file: C:\Program Files\Blender Foundation\Blender 5.0\portable\scripts\addons\Animation_Layers\anim_layers.py</summary>
</details>
</tool-use>
---
<think><details><summary>Thought Process</summary>
I found it. The addon uses **obj.als.turn_on** - so `als` is an **RNA property** (a PropertyGroup on the object), not an id property. So we're checking `key in orig` (id props) but the addon stores it as **orig.als** which is a PropertyGroup with a **turn_on** attribute. So we need to check `getattr(orig, 'als', None)` and then `getattr(orig.als, 'turn_on', None)` or similar. Let me look at how they register the property.
</details></think>
---
Checking how the addon registers `als` and `Anim_Layers`:
---
<tool-use data-tool-type="unknown" data-tool-name="ripgrep_raw_search">
<details>
<summary>Tool use: **ripgrep_raw_search**</summary>
Parameters:
```json
{
"pattern": "als\\.|Anim_Layers|PropertyGroup|BoolProperty.*turn_on",
"path": "C:\\Program Files\\Blender Foundation\\Blender 5.0\\portable\\scripts\\addons\\Animation_Layers",
"glob": "*.py",
"outputMode": "content",
"contextBefore": 0,
"contextAfter": 0,
"caseInsensitive": false
}
```
Additional data:
```json
{
"isPruned": true,
"pattern": "als\\.|Anim_Layers|PropertyGroup|BoolProperty.*turn_on",
"path": "C:\\Program Files\\Blender Foundation\\Blender 5.0\\portable\\scripts\\addons\\Animation_Layers",
"outputMode": "content",
"totalFiles": 3,
"totalMatches": 699,
"topFiles": [
{
"uri": "C:\\Program Files\\Blender Foundation\\Blender 5.0\\portable\\scripts\\addons\\Animation_Layers\\subscriptions.py",
"matchCount": 54
},
{
"uri": "C:\\Program Files\\Blender Foundation\\Blender 5.0\\portable\\scripts\\addons\\Animation_Layers\\multikey.py",
"matchCount": 2
},
{
"uri": "C:\\Program Files\\Blender Foundation\\Blender 5.0\\portable\\scripts\\addons\\Animation_Layers\\anim_layers.py",
"matchCount": 24
}
]
}
```
Status: **completed**
</details>
</tool-use>
---
<tool-use data-tool-type="read" data-tool-name="read_file_v2">
<details><summary>Tool use: **read_file_v2** • Read file: C:\Program Files\Blender Foundation\Blender 5.0\portable\scripts\addons\Animation_Layers\__init__.py</summary>
</details>
</tool-use>
---
<think><details><summary>Thought Process</summary>
Found it. In __init__.py:
- **AnimLayersSettings** is a PropertyGroup with **turn_on: bpy.props.BoolProperty**
- It's registered on the **Object** type as **als** (need to find where they register it)
So the addon uses **obj.als** (RNA PropertyGroup on Object) with **obj.als.turn_on**. So we must check **getattr(orig, 'als', None)** and then **getattr(orig.als, 'turn_on', None)**. It's not in orig.keys() at all - it's an RNA property. Let me find where they register it on Object.
</details></think>
---
---
<tool-use data-tool-type="unknown" data-tool-name="ripgrep_raw_search">
<details>
<summary>Tool use: **ripgrep_raw_search**</summary>
Parameters:
```json
{
"pattern": "als|Anim_Layers|register_class|Object\\.|bpy\\.types\\.Object",
"path": "C:\\Program Files\\Blender Foundation\\Blender 5.0\\portable\\scripts\\addons\\Animation_Layers\\__init__.py",
"caseInsensitive": false
}
```
Additional data:
```json
{
"isPruned": true,
"pattern": "als|Anim_Layers|register_class|Object\\.|bpy\\.types\\.Object",
"path": "C:\\Program Files\\Blender Foundation\\Blender 5.0\\portable\\scripts\\addons\\Animation_Layers\\__init__.py",
"outputMode": "content",
"totalFiles": 1,
"totalMatches": 39,
"topFiles": [
{
"uri": "C:\\Program Files\\Blender Foundation\\Blender 5.0\\portable\\scripts\\addons\\Animation_Layers\\__init__.py",
"matchCount": 39
}
]
}
```
Status: **completed**
</details>
</tool-use>
---
<think><details><summary>Thought Process</summary>
Perfect. So:
- **bpy.types.Object.als** = PointerProperty(type=AnimLayersSettings)
- So **obj.als** is an RNA property (PropertyGroup) on the **Object**, and **obj.als.turn_on** is the boolean.
So we need to:
1. Detect Animation Layers by checking **getattr(orig, 'als', None)** and then **getattr(orig.als, 'turn_on', None)** - if the addon is loaded, every Object has .als; we consider "has Animation Layers" when orig has NLA tracks (we already have has_nla) OR when we want to report that we migrated the toggle. Actually the message is "Animation Layer attributes migrated" when we find als - so we need has_als to be True when **orig.als exists and we copied turn_on**. So detection should be: has_als = getattr(orig, 'als', None) is not None (and optionally orig.als.turn_on is True or we have nla - but actually we should consider "Animation Layers found" when the Object has the als property group, which it will if the addon is loaded). So simply: has_als = getattr(orig, 'als', None) is not None.
2. Mirror: we need to copy **orig.als.turn_on** to **rep.als.turn_on** (and possibly other writable properties of the als group like layer_index). So in _mirror_als_turn_on we do:
- If getattr(orig, 'als', None) and getattr(rep, 'als', None):
rep.als.turn_on = orig.als.turn_on
And for detection: has_als = getattr(orig, 'als', None) is not None (and maybe rep has als too).
</details></think>
---
Identifying the addon: it registers `bpy.types.Object.als` (RNA PropertyGroup), so the toggle is `obj.als.turn_on`, not an id prop. Updating detection and mirroring to use `orig.als` / `rep.als`.
---
The string to replace was not found in the file.
---
<think><details><summary>Thought Process</summary>
I had a typo in the replace - "except Exception:\n pass" vs "except Exception:\n pass" in the bone loop I wrote "except Exception:\n pass" but with wrong indentation (only 4 spaces before pass in one place). Let me do the replace in two smaller chunks: first _has_als_anywhere, then _mirror_als_turn_on.
</details></think>
---
---
<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** • Edit file: T:\Dynamic-Link-Manager\ops\migrator.py</summary>
</details>
</tool-use>
---
---
<think><details><summary>Thought Process</summary>
Both edits applied. Adding RNA check to _debug_als_lookup so the debug output shows that obj.als exists when the addon is loaded.
</details></think>
---
Adding an RNA check to the debug output so we log when `obj.als` exists:
---
<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>
---
<think><details><summary>Thought Process</summary>
Good. Let me verify there are no linter errors.
</details></think>
---
---
<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>
---
<think><details><summary>Thought Process</summary>
All good. Summarize for the user.
</details></think>
---
Summary of what changed:
**Cause:** The Animation Layers addon uses **RNA**, not id properties: it registers `bpy.types.Object.als` (a `PointerProperty` to `AnimLayersSettings`) and stores the toggle as **`obj.als.turn_on`**. The migrator was only checking id props (`"als.turn_on"` in `orig.keys()`, etc.), so it never saw that.
**Updates in `ops/migrator.py`:**
1. **`_has_als_anywhere(orig)`**
- First checks **`getattr(orig, "als", None) is not None`** (RNA).
- If thats true, it returns True so “Animation Layer attributes migrated” is reported.
- Id-property checks are kept as a fallback.
2. **`_mirror_als_turn_on(orig, rep)`**
- First copies via RNA: if both `orig.als` and `rep.als` exist, sets **`rep.als.turn_on = orig.als.turn_on`**.
- Then runs the existing id-property mirroring as fallback.
3. **`_debug_als_lookup(orig)`**
- Now prints **`orig.als` (RNA)** and **`orig.als.turn_on`** so you can confirm the addons state in the console.
With the Animation Layers addon enabled, any armature object has the `als` RNA property. So when the original has Animation Layers turned on (`orig.als.turn_on`), detection should succeed and the report should say “Animation Layer attributes migrated” after Mig NLA. Reload the addon and run Mig NLA again to confirm.
---
_**User**_
Good.
MigBoneConst is copying the constraints, but it's not copying their properties, specifically the targets. I like to run bpy.ops.pose.copy_pose_con(), but that's context-dependent on pose mode.
---
+59 -18
View File
@@ -356,8 +356,65 @@ def run_mig_cust_props(orig, rep):
print(f"[DLM MigCustProps] Settings[{k!r}] = {v!r}") print(f"[DLM MigCustProps] Settings[{k!r}] = {v!r}")
def _retarget_id(ob, orig, rep, orig_to_rep):
"""Return rep, orig_to_rep[ob], or ob so constraint targets point to replacement when appropriate."""
if ob is None:
return None
if ob == orig:
return rep
return orig_to_rep.get(ob, ob)
def _copy_constraint_props(c, nc, orig, rep, orig_to_rep):
"""Copy all copyable RNA properties from c to nc, retargeting object/armature pointers."""
for rna_prop in c.bl_rna.properties:
if rna_prop.is_readonly or rna_prop.identifier in ("name", "type"):
continue
if not hasattr(nc, rna_prop.identifier):
continue
try:
val = getattr(c, rna_prop.identifier)
except Exception:
continue
rna_type = getattr(rna_prop, "type", None)
if rna_type == "POINTER":
setattr(nc, rna_prop.identifier, _retarget_id(val, orig, rep, orig_to_rep))
elif rna_type == "COLLECTION":
# e.g. ArmatureConstraint.targets: ensure count then copy item props (target, subtarget, weight)
try:
dst_coll = getattr(nc, rna_prop.identifier)
src_coll = getattr(c, rna_prop.identifier)
add_fn = getattr(dst_coll, "add", None) or getattr(dst_coll, "new", None)
for i in range(len(src_coll)):
if i >= len(dst_coll) and add_fn:
add_fn()
for i, src_item in enumerate(src_coll):
if i >= len(dst_coll):
break
dst_item = dst_coll[i]
for p in dst_item.bl_rna.properties:
if p.is_readonly or p.identifier == "name":
continue
if not hasattr(dst_item, p.identifier):
continue
try:
v = getattr(src_item, p.identifier)
if getattr(p, "type", None) == "POINTER":
v = _retarget_id(v, orig, rep, orig_to_rep)
setattr(dst_item, p.identifier, v)
except Exception:
pass
except Exception:
pass
else:
try:
setattr(nc, rna_prop.identifier, val)
except Exception:
pass
def run_mig_bone_const(orig, rep, orig_to_rep): def run_mig_bone_const(orig, rep, orig_to_rep):
"""Bone constraints: remove stale on rep, copy from orig with retarget, trim duplicates.""" """Bone constraints: remove stale on rep, copy from orig with full props (targets, etc.) and retarget, trim duplicates."""
other_originals = [o for o in orig_to_rep if o != orig] other_originals = [o for o in orig_to_rep if o != orig]
for pb in rep.pose.bones: for pb in rep.pose.bones:
to_remove = [c for c in pb.constraints if getattr(c, "target", None) in other_originals] to_remove = [c for c in pb.constraints if getattr(c, "target", None) in other_originals]
@@ -368,25 +425,9 @@ def run_mig_bone_const(orig, rep, orig_to_rep):
continue continue
rbone = rep.pose.bones[pbone.name] rbone = rep.pose.bones[pbone.name]
for c in pbone.constraints: for c in pbone.constraints:
if getattr(c, "target", None) == orig:
continue
nc = rbone.constraints.new(type=c.type) nc = rbone.constraints.new(type=c.type)
nc.name = c.name nc.name = c.name
nc.mute = c.mute _copy_constraint_props(c, nc, orig, rep, orig_to_rep)
nc.influence = c.influence
t = getattr(c, "target", None)
if t is not None and t in orig_to_rep:
nc.target = orig_to_rep[t]
elif getattr(nc, "target", None) is not None and t:
nc.target = t
if hasattr(c, "subtarget") and c.subtarget:
nc.subtarget = c.subtarget
for prop in ("head_tail", "use_bone_object", "invert_x", "invert_y", "invert_z"):
if hasattr(c, prop):
try:
setattr(nc, prop, getattr(c, prop))
except Exception:
pass
for pb in orig.pose.bones: for pb in orig.pose.bones:
if pb.name not in rep.pose.bones: if pb.name not in rep.pose.bones:
continue continue