exclude origchar's hierarchy from retargrelations
This commit is contained in:
@@ -14453,3 +14453,686 @@ MigBoneConst is copying the constraints, but it's not copying their properties,
|
||||
|
||||
---
|
||||
|
||||
_**Agent (model default, mode Agent)**_
|
||||
|
||||
<think><details><summary>Thought Process</summary>
|
||||
|
||||
The user is saying that MigBoneConst copies constraints but not their properties (specifically targets). They'd like to use bpy.ops.pose.copy_pose_con() but that's context-dependent on pose mode.
|
||||
|
||||
So I need to:
|
||||
1. Find where MigBoneConst is implemented and how it currently copies constraints
|
||||
2. Either use the operator in the right context (ensure we're in pose mode, select the right bones, run the op) or manually copy constraint properties including targets
|
||||
|
||||
Let me search for the MigBoneConst / bone constraint migration code.
|
||||
</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": "MigBoneConst|bone.*constraint|copy.*constraint",
|
||||
"path": "T:\\Dynamic-Link-Manager",
|
||||
"caseInsensitive": false
|
||||
}
|
||||
```
|
||||
Additional data:
|
||||
|
||||
```json
|
||||
{
|
||||
"isPruned": true,
|
||||
"pattern": "MigBoneConst|bone.*constraint|copy.*constraint",
|
||||
"path": "T:\\Dynamic-Link-Manager",
|
||||
"outputMode": "content",
|
||||
"totalFiles": 6,
|
||||
"totalMatches": 106,
|
||||
"topFiles": [
|
||||
{
|
||||
"uri": "ops/migrator.py",
|
||||
"matchCount": 3
|
||||
},
|
||||
{
|
||||
"uri": "ui/panels.py",
|
||||
"matchCount": 1
|
||||
},
|
||||
{
|
||||
"uri": ".specstory/history/2026-02-18_23-39Z-character-migrator-workflow-integration.md",
|
||||
"matchCount": 44
|
||||
},
|
||||
{
|
||||
"uri": ".specstory\\history\\2026-02-18_23-39Z-character-migrator-workflow-integration.md",
|
||||
"matchCount": 44
|
||||
},
|
||||
{
|
||||
"uri": "ui/operators.py",
|
||||
"matchCount": 11
|
||||
},
|
||||
{
|
||||
"uri": "ops\\migrator.py",
|
||||
"matchCount": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
Status: **completed**
|
||||
|
||||
|
||||
</details>
|
||||
</tool-use>
|
||||
|
||||
---
|
||||
|
||||
<tool-use data-tool-type="unknown" data-tool-name="semantic_search_full">
|
||||
<details>
|
||||
<summary>Tool use: **semantic_search_full**</summary>
|
||||
|
||||
|
||||
Parameters:
|
||||
|
||||
```json
|
||||
{
|
||||
"query": "Where are bone constraints copied from original to replacement armature?",
|
||||
"includePattern": "T:\\Dynamic-Link-Manager",
|
||||
"topK": 10,
|
||||
"explanation": ""
|
||||
}
|
||||
```
|
||||
Status: **completed**
|
||||
|
||||
|
||||
</details>
|
||||
</tool-use>
|
||||
|
||||
---
|
||||
|
||||
<think><details><summary>Thought Process</summary>
|
||||
|
||||
Found it in ops/migrator.py around lines 370-393. Let me read that section to see exactly how constraints are copied and what properties (like target) are not being copied.
|
||||
</details></think>
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
<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>
|
||||
|
||||
---
|
||||
|
||||
<think><details><summary>Thought Process</summary>
|
||||
|
||||
I see the current logic. It:
|
||||
1. Copies constraint type, name, mute, influence
|
||||
2. For target: only sets nc.target when t is in orig_to_rep (retarget to replacement) or when nc has target and t is truthy it sets nc.target = t (so it does try to copy target when t is not in orig_to_rep - but that might be wrong: if t is another object, we're setting nc.target = t which is correct; but maybe the issue is that many constraint types have different target properties - not just "target" but "targets" (list), or armature vs bone, etc.)
|
||||
3. Copies subtarget (bone name)
|
||||
4. Copies a few known props: head_tail, use_bone_object, invert_x/y/z
|
||||
|
||||
The user says targets are not being copied. So either:
|
||||
- The generic "target" copy isn't working in some cases (e.g. when orig_to_rep doesn't contain the object, or when constraint has multiple targets)
|
||||
- Or we need to copy all RNA properties from the constraint, not just a hardcoded list
|
||||
|
||||
Blender's constraint types vary:
|
||||
- Single target: COPY_LOCATION, TRACK_TO, DAMPED_TRACK, etc. have .target and .subtarget
|
||||
- Some have .targets (list): ACTION, SHRINKWRAP (optional), etc.
|
||||
- ARMATURE has .target (armature object) and .targets (list of bone targets with subtarget and weight)
|
||||
|
||||
The most robust approach would be to copy all copyable RNA properties from the source constraint to the new one. We can iterate over c.bl_rna.properties and for each property that is readable and writable and not a collection that needs special handling, copy it. For PointerProperty (target, armature, etc.) we need to retarget: if the value is in orig_to_rep, use the replacement; if it's orig, we skip (already in the loop) or set to rep. So actually the current code skips copying the constraint when c.target == orig. So we don't add a constraint that points to the original armature. For other objects (e.g. empty, mesh), we just copy the pointer - and we do "elif getattr(nc, "target", None) is not None and t: nc.target = t" which should copy the target. Unless... the issue is that we're not in pose mode and something is read-only? Or the constraint type has multiple targets?
|
||||
|
||||
Let me check: maybe the issue is that we only set nc.target when "t in orig_to_rep" or when "getattr(nc, "target", None) is not None and t". So if the new constraint doesn't have a "target" attribute (unlikely) we wouldn't set it. More likely: for armature constraint, the target might be the armature and we're skipping the whole constraint when t == orig. So we never add constraints that target the same armature. But the user said "it's not copying their properties, specifically the targets" - so they want the target to be copied. So perhaps when the target is the same armature (orig), they want nc.target = rep? So we should add the constraint and set nc.target = rep when t == orig. Let me re-read.
|
||||
|
||||
"MigBoneConst is copying the constraints, but it's not copying their properties, specifically the targets."
|
||||
|
||||
So constraints are being added, but the target field (and possibly subtarget) is not set. That could happen if:
|
||||
1. We skip the constraint when target == orig (we do "if getattr(c, "target", None) == orig: continue") - so we don't add the constraint at all when it targets the same armature. So the user might want to add it and set target to rep.
|
||||
2. Or we do add the constraint but the assignment nc.target = ... fails or isn't done. For example when t is in orig_to_rep we set nc.target = orig_to_rep[t]. When t == orig we skip. When t is something else (e.g. another object), we have "elif getattr(nc, "target", None) is not None and t: nc.target = t". So we only set nc.target when the NEW constraint has a target attribute. All constraint types that have target do have it on the new constraint. So we should set nc.target = t. Unless t is None? So maybe the issue is we're not setting when t is the same armature - we skip the constraint entirely. So the fix could be: don't skip when target == orig; instead add the constraint and set nc.target = rep.
|
||||
|
||||
Let me re-read the code again:
|
||||
- "if getattr(c, "target", None) == orig: continue" - we skip copying this constraint when it targets the original armature. So constraints that point to the same character's armature are not copied. The user wants them copied with target set to rep. So we should remove that continue and instead when we create nc, set nc.target = rep when t == orig.
|
||||
- Also copy all other properties generically so we don't miss any (head_tail, etc. are only a few). We could use RNA property iteration to copy every property that is not a method and is writable.
|
||||
|
||||
Let me also consider using bpy.ops.pose.copy_pose_con(). The user said they'd like to run it but it's context-dependent on pose mode. So we could:
|
||||
1. Switch to pose mode, select the appropriate bones (original then replacement?), run the operator. I'd need to check how copy_pose_con works - it probably copies from active bone to selected bones or something. So we'd set context to the armature, ensure pose mode, set active object to rep (or orig?), select bones... Actually the Blender doc says: "Copy Constraint – Copy the constraint from the active bone to the selected bones." So we need active bone (source) and selected bones (destination). So we could: for each bone, set rep as active object, enter pose mode, select orig armature's bone... no, we need to copy FROM orig TO rep. So active bone should be on orig and selected bones on rep? Or maybe the op copies from active to selected - so we need orig as the object with active bone, and we select the corresponding bone on rep? But then we'd have to have both armatures in the scene and one bone selected on rep. Actually the operator is "Copy Constraint" - it copies constraints. So source = active bone (one bone), destination = selected bones. So if we have orig as active object, active bone = pbone (orig), and we select rbone (rep) - but rbone is on rep, so we need rep as the object. So the selection is on one object. So we can't have active bone on orig and selected on rep because they're different objects. So the operator probably works on a single armature: active bone = source, selected bones = destinations, all on the same armature. So we can't use it to copy from one armature to another. So we have to do the property copy in code.
|
||||
|
||||
I'll do a comprehensive RNA property copy: for each constraint type, iterate over the source constraint's bl_rna.properties; for each property that is not read-only and not a collection (or handle collections like targets), copy the value. For PointerProperty (ID blocks), if the value is orig, set rep; if in orig_to_rep, set orig_to_rep[value]; otherwise set value. For subtarget (bone name string), copy as-is since bone names match. Let me implement that.
|
||||
</details></think>
|
||||
|
||||
---
|
||||
|
||||
|
||||
Checking how Blender exposes constraint properties (including multi-target constraints like ARMATURE):
|
||||
|
||||
|
||||
---
|
||||
|
||||
<tool-use data-tool-type="search" data-tool-name="web_search">
|
||||
<details>
|
||||
<summary>Tool use: **web_search** • Searched web `undefined` • **1** results</summary>
|
||||
|
||||
| Title | URL | Chunk |
|
||||
|-------|-----|-------|
|
||||
| Web search results for query: Blender Python copy constraint properties RNA all properties from one constraint to another | | Links:<br/>1. [Copy Custom Properties from one object to another](https://blender.stackexchange.com/questions/24001/copy-custom-properties-from-one-object-to-another)<br/>2. [How to copy constraints from one bone to another?](https://blender.stackexchange.com/questions/41709/how-to-copy-constraints-from-one-bone-to-another)<br/>3. [Mirroring / Copying Constraints over a whole rig](https://blender.stackexchange.com/questions/70518/mirroring-copying-constraints-over-a-whole-rig)<br/>4. [CopyTransformsConstraint(Constraint) - Blender Python API](https://docs.blender.org/api/current/bpy.types.CopyTransformsConstraint.html)<br/>5. [Copy bone transform question](https://blender.stackexchange.com/questions/177315/copy-bone-transform-question)<br/><br/># Copying Constraint Properties in Blender Python<br/><br/>To copy all properties from one constraint to another in Blender Python, use the `dir()` function to iterate through constraint properties and apply them with `setattr()`. Here's the core approach:[2]<br/><br/>```python<br/>import bpy<br/><br/>source_constraint = source_bone.constraints[0]<br/>target_constraint = target_bone.constraints.new(source_constraint.type)<br/><br/>for prop in dir(source_constraint):<br/> try:<br/> setattr(target_constraint, prop, getattr(source_constraint, prop))<br/> except:<br/> pass<br/>```<br/><br/>This method attempts to copy all properties from the source constraint to the newly created target constraint, catching exceptions for read-only or incompatible properties.[2]<br/><br/>## Full Constraint Copying Example<br/><br/>For a more complete implementation copying constraints between bones:[2]<br/><br/>```python<br/>import bpy<br/><br/>source_bone = bpy.context.object.pose.bones["Bone.L"]<br/>target_bone = bpy.context.object.pose.bones["Bone.R"]<br/><br/>for constraint in source_bone.constraints:<br/> new_constraint = target_bone.constraints.new(constraint.type)<br/> <br/> for prop in dir(constraint):<br/> try:<br/> setattr(new_constraint, prop, getattr(constraint, prop))<br/> except:<br/> pass<br/>```<br/><br/>## Alternative: Using RNA Properties<br/><br/>For more robust copying, you can access the constraint's RNA properties through the Blender API documentation to specifically target known properties rather than using `dir()`.[4] Constraint types like `CopyTransformsConstraint` have specific documented properties such as `target`, `subtarget`, `owner_space`, and `target_space` that can be copied individually.[4]<br/><br/>This approach is cleaner when you know which properties need to be copied for your specific constraint type. |
|
||||
|
||||
</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": "bl_rna\\.properties|constraint\\.target",
|
||||
"path": "T:\\Dynamic-Link-Manager",
|
||||
"caseInsensitive": false
|
||||
}
|
||||
```
|
||||
Additional data:
|
||||
|
||||
```json
|
||||
{
|
||||
"isPruned": true,
|
||||
"pattern": "bl_rna\\.properties|constraint\\.target",
|
||||
"path": "T:\\Dynamic-Link-Manager",
|
||||
"outputMode": "content",
|
||||
"totalFiles": 2,
|
||||
"totalMatches": 16,
|
||||
"topFiles": [
|
||||
{
|
||||
"uri": "ops\\migrator.py",
|
||||
"matchCount": 2
|
||||
},
|
||||
{
|
||||
"uri": ".specstory\\history\\2026-02-18_23-39Z-character-migrator-workflow-integration.md",
|
||||
"matchCount": 14
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
Status: **completed**
|
||||
|
||||
|
||||
</details>
|
||||
</tool-use>
|
||||
|
||||
---
|
||||
|
||||
<think><details><summary>Thought Process</summary>
|
||||
|
||||
So the approach is: iterate over the constraint's RNA properties and copy them, with special handling for pointers (target → retarget orig→rep or via orig_to_rep). I need to:
|
||||
|
||||
1. Not skip constraints when target == orig; instead add them and set nc.target = rep.
|
||||
2. Copy all RNA properties from c to nc, with pointer retargeting: if value is orig → set rep; if value in orig_to_rep → set orig_to_rep[value]; else set value (same for subtarget we keep as-is for bone names).
|
||||
|
||||
Let me look at Blender's constraint property types - some constraints have .targets (collection) like ARMATURE. For those we need to copy each target and retarget the object pointer.
|
||||
</details></think>
|
||||
|
||||
---
|
||||
|
||||
|
||||
Checking constraint RNA property types (pointer vs collection) so we can retarget correctly:
|
||||
|
||||
|
||||
---
|
||||
|
||||
<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="search" data-tool-name="web_search">
|
||||
<details>
|
||||
<summary>Tool use: **web_search** • Searched web `undefined` • **1** results</summary>
|
||||
|
||||
| Title | URL | Chunk |
|
||||
|-------|-----|-------|
|
||||
| Web search results for query: Blender bpy.types.Constraint bl_rna.properties POINTER PROPERTY copy | | Links:<br/>1. [PointerProperty(Property) ¶](https://docs.blender.org/api/current/bpy.types.PointerProperty.html)<br/>2. [Property Definitions (bpy.props) - Blender Python API](https://docs.blender.org/api/current/bpy.props.html)<br/>3. [CopyLocationConstraint(Constraint) — Blender 2.77.0 22a2853](https://docs.blender.org/api/blender_python_api_2_77_release/bpy.types.CopyLocationConstraint.html?highlight=constraints)<br/>4. [How to copy a property definition (including list of possible enum values, tooltip description, etc.)?](https://blender.stackexchange.com/questions/175196/how-to-copy-a-property-definition-including-list-of-possible-enum-values-toolt)<br/>5. [Constraint Operators ¶](https://docs.blender.org/api/current/bpy.ops.constraint.html)<br/><br/># Summary<br/><br/>Based on the search results, here's what's relevant to your query about Blender's `bpy.types.Constraint` and property copying:<br/><br/>## PointerProperty in Blender RNA<br/><br/>`PointerProperty` is an RNA pointer property type used to reference another RNA struct.[1] It inherits from the `Property` class and includes properties like `fixed_type` (which specifies the fixed pointer type if applicable).[1]<br/><br/>## Property Definition and Copying<br/><br/>When working with constraint properties in Blender, you can access property definitions through `bl_rna.properties`. However, directly copying property definitions (including enum values, descriptions, etc.) is not straightforward through the API.[4] <br/><br/>One approach discussed in the community is to programmatically access the RNA property information from existing constraint types and use that to define operator properties, rather than manually duplicating enum values and descriptions.[4] This would involve inspecting the constraint's `bl_rna.properties` to extract metadata like available enum items and tooltips.<br/><br/>## Constraint Types<br/><br/>Specific constraint types like `CopyLocationConstraint` inherit from the base `Constraint` class and have their own properties that can be set via the Python API.[3]<br/><br/>The direct programmatic copying of property definitions from constraints to operators remains a limitation without manual enumeration of the property attributes. |
|
||||
|
||||
</details>
|
||||
</tool-use>
|
||||
|
||||
---
|
||||
|
||||
<think><details><summary>Thought Process</summary>
|
||||
|
||||
I'll implement:
|
||||
1. Remove the "skip when target == orig" - instead copy the constraint and set target to rep when it was orig.
|
||||
2. Add a helper that copies all RNA properties from constraint c to nc, with retargeting for ID pointers (Object, Armature, etc.): if val == orig → rep; if val in orig_to_rep → orig_to_rep[val]; else copy as-is. For collection properties (e.g. ARMATURE constraint's .targets), we need to copy each item and retarget the object in each item.
|
||||
3. Use bl_rna.properties and check property type - skip read-only, handle POINTER (retarget), handle COLLECTION (copy items and retarget pointers inside).
|
||||
|
||||
Let me check the Blender API for how to get property type from rna property.
|
||||
</details></think>
|
||||
|
||||
---
|
||||
|
||||
|
||||
Implementing full constraint property copy with target retargeting: adding a helper and updating `run_mig_bone_const`.
|
||||
|
||||
|
||||
---
|
||||
|
||||
<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>
|
||||
|
||||
---
|
||||
|
||||
<think><details><summary>Thought Process</summary>
|
||||
|
||||
I need to add a function that copies constraint properties with retargeting. In Blender's RNA, we can use:
|
||||
- rna_prop.identifier and rna_prop.type (e.g. 'POINTER', 'STRING', 'COLLECTION', etc.)
|
||||
- For POINTER we need to get the value and if it's an ID (Object, Armature), retarget
|
||||
|
||||
Let me check - in Blender 4/5, the property type is on the Property object. So for each prop in c.bl_rna.properties:
|
||||
- Skip if prop.is_readonly
|
||||
- If prop.type == 'POINTER': get value, retarget (orig->rep, or orig_to_rep), set on nc
|
||||
- If prop.type == 'COLLECTION': e.g. ArmatureConstraint has .targets; we need to clear nc's collection and add items, copying each item's properties and retargeting pointers
|
||||
- Else: setattr(nc, prop.identifier, getattr(c, prop.identifier))
|
||||
|
||||
For COLLECTION like ArmatureConstraint.targets, each element has .target (armature object) and .subtarget (bone name). So we copy length and for each index copy the element's properties with retargeting.
|
||||
|
||||
I'll implement _copy_constraint_props(orig_constraint, new_constraint, orig, rep, orig_to_rep) and call it after creating nc. And remove the "continue" when target==orig; instead we'll copy and set target=rep in the generic loop.
|
||||
</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>
|
||||
|
||||
---
|
||||
|
||||
<think><details><summary>Thought Process</summary>
|
||||
|
||||
Blender's CollectionProperty might not support remove(index) - I need to check. In Blender, collection.remove(item) often takes the item or index. Let me check - for ArmatureConstraintTargets, I think we add with .add() and remove might be .remove(index). Actually the issue is we're copying from orig to rep; the new constraint nc starts with default (e.g. 0 targets). So we don't need to "while len(dst_coll) > len(src_coll): remove". We need to add enough items to match src_coll, then copy each item's properties. So:
|
||||
for i, src_item in enumerate(src_coll):
|
||||
if i >= len(dst_coll):
|
||||
dst_coll.add()
|
||||
dst_item = dst_coll[i]
|
||||
...
|
||||
|
||||
And we don't need to remove extras - the new constraint starts empty. So I'll remove the "while len(dst_coll) > len(src_coll): dst_coll.remove(...)" part because for a freshly created constraint the collection might be default size (e.g. 1 for ARMATURE). Let me check - when you create a new ARMATURE constraint, how many targets does it have? Usually 0 or 1. So we need to add() until we have enough, then set each item. If the collection doesn't support remove, the while loop could error. I'll remove the while loop to be safe; if the new constraint has more default slots than orig, we just overwrite the first len(src_coll) and leave the rest (could be redundant). If it has fewer, we add(). So:
|
||||
for i in range(len(src_coll)):
|
||||
if i >= len(dst_coll):
|
||||
dst_coll.add()
|
||||
...
|
||||
</details></think>
|
||||
|
||||
---
|
||||
|
||||
|
||||
Checking Blender collection API for constraints: add/remove behavior
|
||||
|
||||
|
||||
---
|
||||
|
||||
<tool-use data-tool-type="search" data-tool-name="web_search">
|
||||
<details>
|
||||
<summary>Tool use: **web_search** • Searched web `undefined` • **1** results</summary>
|
||||
|
||||
| Title | URL | Chunk |
|
||||
|-------|-----|-------|
|
||||
| Web search results for query: Blender ArmatureConstraint targets collection add remove Python | | Links:<br/>1. [ArmatureConstraintTargets(bpy_struct) ¶](https://docs.blender.org/api/current/bpy.types.ArmatureConstraintTargets.html)<br/>2. [Adding/Removing a Constraint ](https://docs.blender.org/manual/en/3.6/animation/constraints/interface/adding_removing.html)<br/>3. [Adding/Removing a Constraint - Blender 4.1 Manual](https://docs.blender.org/manual/en/latest/animation/constraints/interface/adding_removing.html)<br/>4. [Armature Operators ¶](https://docs.blender.org/api/current/bpy.ops.armature.html)<br/>5. [Constraint Operators ¶](https://docs.blender.org/api/current/bpy.ops.constraint.html)<br/><br/># Blender ArmatureConstraint Targets Collection - Add/Remove Python<br/><br/>The `ArmatureConstraintTargets` collection in Blender's Python API provides methods to manage target bones for Armature constraints:[1]<br/><br/>## Key Methods<br/><br/>**new()** - Adds a new target to the constraint and returns a `ConstraintTargetBone` object.[1]<br/><br/>**remove(target)** - Deletes a specific target from the constraint by passing the `ConstraintTargetBone` to remove.[1]<br/><br/>**clear()** - Deletes all targets from the constraint at once.[1]<br/><br/>## Usage Example<br/><br/>To work with targets programmatically, you would access the targets collection through an Armature constraint object and call these methods. For instance, you can add a new target with `constraint.targets.new()`, remove a specific target with `constraint.targets.remove(target_bone)`, or clear all targets with `constraint.targets.clear()`. |
|
||||
|
||||
</details>
|
||||
</tool-use>
|
||||
|
||||
---
|
||||
|
||||
<think><details><summary>Thought Process</summary>
|
||||
|
||||
So for ArmatureConstraint, it's .targets with .new(), .remove(target), .clear(). So we can't do dst_coll.remove(len(dst_coll)-1). We'd do dst_coll.remove(dst_coll[-1]) or iterate and remove. So in _copy_constraint_props I should not do "while len(dst_coll) > len(src_coll): dst_coll.remove(...)" because remove takes the item. Let me use: while len(dst_coll) > len(src_coll): dst_coll.remove(dst_coll[len(dst_coll)-1]). Actually the API said "remove(target)" - so pass the item. So dst_coll.remove(dst_coll[-1]). But that might not exist for all constraint types. Safest: only add() when we need more; if dst has more items than src, we could try to remove extras only for collections that support it, or just leave them. I'll remove the "while len(dst_coll) > len(src_coll)" part entirely - so we only add() to match src count and copy; if nc has default items we overwrite them. If the default constraint has 1 empty target and orig has 3, we add() 2 more and fill all 3. Good.
|
||||
</details></think>
|
||||
|
||||
---
|
||||
|
||||
|
||||
Fixing collection handling: don't assume `remove(index)`; use the item-based API or only add as needed.
|
||||
|
||||
|
||||
---
|
||||
|
||||
<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>
|
||||
|
||||
Some constraint collections might use .new() instead of .add() - e.g. ArmatureConstraintTargets. Let me check - the web said "new() - Adds a new target". So it's dst_coll.new() not add(). Let me search.
|
||||
</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": "\\.add\\(|\\.new\\(",
|
||||
"path": "T:\\Dynamic-Link-Manager",
|
||||
"caseInsensitive": false
|
||||
}
|
||||
```
|
||||
Additional data:
|
||||
|
||||
```json
|
||||
{
|
||||
"isPruned": true,
|
||||
"pattern": "\\.add\\(|\\.new\\(",
|
||||
"path": "T:\\Dynamic-Link-Manager",
|
||||
"outputMode": "content",
|
||||
"totalFiles": 10,
|
||||
"totalMatches": 108,
|
||||
"topFiles": [
|
||||
{
|
||||
"uri": "__init__.py",
|
||||
"matchCount": 1
|
||||
},
|
||||
{
|
||||
"uri": "ops/tweak_tools.py",
|
||||
"matchCount": 1
|
||||
},
|
||||
{
|
||||
"uri": ".specstory/history/2025-08-20_22-43Z-creating-a-blender-extension-for-asset-replacement.md",
|
||||
"matchCount": 69
|
||||
},
|
||||
{
|
||||
"uri": "ops\\migrator.py",
|
||||
"matchCount": 6
|
||||
},
|
||||
{
|
||||
"uri": ".specstory\\history\\2026-02-18_23-39Z-character-migrator-workflow-integration.md",
|
||||
"matchCount": 9
|
||||
},
|
||||
{
|
||||
"uri": "ops/migrator.py",
|
||||
"matchCount": 6
|
||||
},
|
||||
{
|
||||
"uri": ".specstory/history/2026-02-18_23-39Z-character-migrator-workflow-integration.md",
|
||||
"matchCount": 9
|
||||
},
|
||||
{
|
||||
"uri": "ops/library.py",
|
||||
"matchCount": 5
|
||||
},
|
||||
{
|
||||
"uri": "utils/__init__.py",
|
||||
"matchCount": 1
|
||||
},
|
||||
{
|
||||
"uri": "ui/operators.py",
|
||||
"matchCount": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
Status: **completed**
|
||||
|
||||
|
||||
</details>
|
||||
</tool-use>
|
||||
|
||||
---
|
||||
|
||||
<tool-use data-tool-type="search" data-tool-name="web_search">
|
||||
<details>
|
||||
<summary>Tool use: **web_search** • Searched web `undefined` • **1** results</summary>
|
||||
|
||||
| Title | URL | Chunk |
|
||||
|-------|-----|-------|
|
||||
| Web search results for query: Blender constraint targets collection .new() or .add() Python | | Links:<br/>1. [ConstraintTarget(bpy_struct) - Blender Python API](https://docs.blender.org/api/current/bpy.types.ConstraintTarget.html)<br/>2. [Collection Operators - Blender Python API](https://docs.blender.org/api/current/bpy.ops.collection.html)<br/>3. [Constraint Operators ¶](https://docs.blender.org/api/current/bpy.ops.constraint.html)<br/>4. [ConstraintTargetBone(bpy_struct) - Blender Python API](https://docs.blender.org/api/current/bpy.types.ConstraintTargetBone.html)<br/>5. [ConstraintTarget(bpy_struct) ¶](https://docs.blender.org/api/blender_python_api_current/bpy.types.ConstraintTarget.html)<br/><br/>Based on the search results, there is no direct information about `.new()` or `.add()` methods for creating constraint targets in Blender's Python API.<br/><br/>The documentation shows that `ConstraintTarget` is a read-only data structure with properties for `target` (the target object) and `subtarget` (bone/vertex group name).[1] However, the API reference does not display methods to directly instantiate new constraint targets.<br/><br/>In Blender's Python API, constraint targets are typically managed through the constraint object itself rather than through a separate collection interface. You would generally access existing targets through a constraint's `targets` property (as referenced in the `PythonConstraint.targets` documentation)[5], but the specific method for adding new targets depends on the constraint type being used.<br/><br/>For general collection operations in Blender, the API does provide `bpy.ops.collection.create()` for creating object collections,[2] but this is different from constraint targets, which are part of constraint objects rather than scene collections.<br/><br/>To add constraint targets programmatically, you would need to consult the documentation for specific constraint types, as the target creation method varies by constraint. |
|
||||
|
||||
</details>
|
||||
</tool-use>
|
||||
|
||||
---
|
||||
|
||||
<think><details><summary>Thought Process</summary>
|
||||
|
||||
The first search said ArmatureConstraintTargets has .new() to add. So I'll try both add and new: getattr(dst_coll, "add", None) or getattr(dst_coll, "new", None). So: add_fn = getattr(dst_coll, "add", None) or getattr(dst_coll, "new", None). Then when we need to add: if add_fn: add_fn(). That way we support both .add() and .new().
|
||||
</details></think>
|
||||
|
||||
---
|
||||
|
||||
|
||||
Using both `.add()` and `.new()` for collections that use `.new()`:
|
||||
|
||||
|
||||
---
|
||||
|
||||
<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>
|
||||
|
||||
Now check - in Blender, does rna_prop have .type? It might be on the Property object. Let me check - the Blender API uses bpy.types.Property and the type could be 'POINTER', 'COLLECTION', etc. I'll leave as is; if it fails we can catch. Let me also ensure we don't copy "owner_space" etc. that might need to stay - actually we want to copy everything so the constraint behaves the same. So copying all is correct.
|
||||
|
||||
One more thing: the base Constraint class has "owner" which is read-only (the pose bone). So we skip readonly. And "type" we're already skipping. Good.
|
||||
|
||||
Read lints.
|
||||
</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. Let me give the user a short summary.
|
||||
</details></think>
|
||||
|
||||
---
|
||||
|
||||
|
||||
Summary of changes:
|
||||
|
||||
**Updates in `ops/migrator.py`**
|
||||
|
||||
1. **`_retarget_id(ob, orig, rep, orig_to_rep)`**
|
||||
- New helper: maps `orig` → `rep`, any key in `orig_to_rep` → its value, and anything else → unchanged. Used for all constraint pointer properties.
|
||||
|
||||
2. **`_copy_constraint_props(c, nc, orig, rep, orig_to_rep)`**
|
||||
- Copies all copyable RNA properties from the source constraint to the new one:
|
||||
- **POINTER**: uses `_retarget_id` so `target` (and any other object/armature pointer) is remapped from `orig` to `rep` or via `orig_to_rep`.
|
||||
- **COLLECTION** (e.g. Armature constraint’s `targets`): ensures the same number of items (using `.add()` or `.new()`), then for each item copies its RNA properties and retargets any POINTER (e.g. armature object).
|
||||
- **Other types**: copied with `setattr`.
|
||||
- Skips read-only and `name`/`type`.
|
||||
|
||||
3. **`run_mig_bone_const`**
|
||||
- No longer skips constraints whose `target == orig`. Every constraint is copied.
|
||||
- After creating the new constraint and setting its name, it only calls `_copy_constraint_props(c, nc, orig, rep, orig_to_rep)`, so **target, subtarget, influence, mute, space switches, and all other RNA props** (including multi-target lists) are copied with correct retargeting.
|
||||
|
||||
So constraint **targets** (and other pointers) are copied and remapped: same-armature → `rep`, others via `orig_to_rep` when given. This is done in code only; `bpy.ops.pose.copy_pose_con()` is not used so there’s no pose-mode/selection dependency.
|
||||
|
||||
---
|
||||
|
||||
_**User**_
|
||||
|
||||
for retargrelations, objects that are within OrigChar's hierarchy (starting from linked collection) should NOT have their relations retargeted to Replacement Char.
|
||||
|
||||
---
|
||||
|
||||
_**Agent (model default, mode Agent)**_
|
||||
|
||||
<think><details><summary>Thought Process</summary>
|
||||
|
||||
The user is saying that for RetargRelations (retarget relations), we should NOT retarget relations for objects that are within the Original Character's hierarchy (starting from the linked collection). So we should only retarget relations for objects that are NOT descendants of the original character - or more precisely, objects that are in the "replacement" side (replacement character's hierarchy) should get retargeted, but objects that are still in the original character's hierarchy (linked collection) should keep pointing to the original.
|
||||
|
||||
Let me re-read. "objects that are within OrigChar's hierarchy (starting from linked collection) should NOT have their relations retargeted to Replacement Char."
|
||||
|
||||
So:
|
||||
- If an object is part of the Original Character's hierarchy (the linked collection), we should NOT change its parent or constraint targets from orig to rep. Those should stay pointing to the original.
|
||||
- We should only retarget relations for objects that are... outside that hierarchy? Or only for objects that belong to the replacement?
|
||||
|
||||
I think the intent is: when you have a linked character (OrigChar) and a replacement (RepChar), the linked collection contains OrigChar and its children. Those objects (OrigChar and everything under it in that hierarchy) should keep their relations as-is (pointing to OrigChar). We should only retarget relations for objects that are "on the replacement side" - i.e. objects that are descendants of RepChar or otherwise part of the replacement character's hierarchy. So the logic would be: only retarget (parent, constraint target, armature modifier) when the object being updated is in the replacement character's hierarchy (rep_descendants or similar). Objects in the original's hierarchy (orig descendants, or in the linked collection) should be left alone.
|
||||
|
||||
Let me look at the current run_retarg_relatives implementation to see how it decides which objects to update.
|
||||
</details></think>
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
<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="unknown" data-tool-name="ripgrep_raw_search">
|
||||
<details>
|
||||
<summary>Tool use: **ripgrep_raw_search**</summary>
|
||||
|
||||
|
||||
Parameters:
|
||||
|
||||
```json
|
||||
{
|
||||
"pattern": "run_retarg|retarg_relatives|rep_descendants",
|
||||
"path": "T:\\Dynamic-Link-Manager\\ops\\migrator.py",
|
||||
"caseInsensitive": false
|
||||
}
|
||||
```
|
||||
Additional data:
|
||||
|
||||
```json
|
||||
{
|
||||
"isPruned": true,
|
||||
"pattern": "run_retarg|retarg_relatives|rep_descendants",
|
||||
"path": "T:\\Dynamic-Link-Manager\\ops\\migrator.py",
|
||||
"outputMode": "content",
|
||||
"totalFiles": 1,
|
||||
"totalMatches": 7,
|
||||
"topFiles": [
|
||||
{
|
||||
"uri": "ops\\migrator.py",
|
||||
"matchCount": 7
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
Status: **completed**
|
||||
|
||||
|
||||
</details>
|
||||
</tool-use>
|
||||
|
||||
---
|
||||
|
||||
<think><details><summary>Thought Process</summary>
|
||||
|
||||
I see. Currently `run_retarg_relatives` builds candidates as:
|
||||
1. rep_descendants (objects that are descendants of rep - parent chain leads to rep)
|
||||
2. objects whose parent is orig
|
||||
3. objects that have a constraint targeting orig
|
||||
|
||||
So it's retargeting both (a) objects in rep's hierarchy and (b) objects that point to orig (parent or constraint). The user says: objects that are within OrigChar's hierarchy (starting from linked collection) should NOT have their relations retargeted. So we should exclude from candidates any object that is in the original character's hierarchy. So:
|
||||
- candidates = rep_descendants only? No - we also add objects whose parent is orig and objects with constraint targeting orig. So we might be retargeting an object that is parented to orig - that object could be in the "original" linked </details></think>
|
||||
|
||||
---
|
||||
|
||||
|
||||
+3
-1
@@ -437,7 +437,8 @@ def run_mig_bone_const(orig, rep, orig_to_rep):
|
||||
|
||||
|
||||
def run_retarg_relatives(orig, rep, rep_descendants, orig_to_rep):
|
||||
"""Retarget relations: parents, constraint targets, Armature modifiers to rep."""
|
||||
"""Retarget relations: parents, constraint targets, Armature modifiers to rep. Skip objects in orig's hierarchy (linked collection)."""
|
||||
orig_hierarchy = {orig} | descendants(orig)
|
||||
candidates = set(rep_descendants)
|
||||
for ob in bpy.data.objects:
|
||||
if getattr(ob.parent, "name", None) == orig.name:
|
||||
@@ -445,6 +446,7 @@ def run_retarg_relatives(orig, rep, rep_descendants, orig_to_rep):
|
||||
for c in getattr(ob, "constraints", []):
|
||||
if getattr(c, "target", None) == orig:
|
||||
candidates.add(ob)
|
||||
candidates -= orig_hierarchy
|
||||
for ob in candidates:
|
||||
if ob.parent == orig:
|
||||
ob.parent = rep
|
||||
|
||||
Reference in New Issue
Block a user