add remove original button

This commit is contained in:
Nathan
2026-03-10 10:19:22 -06:00
parent 53927a6880
commit 50d3f05dd9
15 changed files with 2925 additions and 665 deletions
File diff suppressed because one or more lines are too long
@@ -0,0 +1,208 @@
<!-- Generated by SpecStory, Markdown v2.1.0 -->
<!-- cursor Session 986c4b32-627a-4d70-9edb-fcaf30e33ee2 (2026-03-10 16:04Z) -->
# Blender addon file structure exploration (2026-03-10 16:04Z)
_**User**_
I need to understand the file structure of a Blender addon at T:\Dynamic-Link-Manager. Please list all Python files (.py) in this directory and its subdirectories. For each file, show its full path and roughly how many lines it contains.
The shell seems to be returning cached output, so please use the file tools (Glob, Read with limited lines) to explore the directory structure.
---
_**Agent (model accounts/fireworks/models/kimi-k2p5, mode Agent)**_
---
<think><details><summary>Thought Process</summary>
The user wants to understand the file structure of a Blender addon. They want me to:
1. List all Python files (.py) in the directory and subdirectories
2. Show the full path and line count for each file
They mention that shell returns cached output, so I should use Glob and Read tools instead. Let me start by finding all Python files using Glob.</details></think>
---
I'll explore the directory structure using the file tools to find all Python files and their line counts.
---
<tool-use data-tool-type="generic" data-tool-name="glob_file_search">
<details>
<summary>Tool use: **glob_file_search** • Searched codebase "undefined" • **12** results in **1** directory</summary>
Directory: **T:\Dynamic-Link-Manager** (12 files)
| File |
|------|
| `../ui/properties.py` |
| `../ui/__init__.py` |
| `../ui/panels.py` |
| `../ops\migrator.py` |
| `../__init__.py` |
| `../ops/fk_rotations.py` |
| `../utils/__init__.py` |
| `../ops/library.py` |
| `../ui/operators.py` |
| `../ops/__init__.py` |
| `../ops/tweak_tools.py` |
| `../ui/preferences.py` |
</details>
</tool-use>
---
<think><details><summary>Thought Process</summary>
I found 12 Python files. Now I need to read each file to determine the line count. I can use the Read tool with a limit to quickly check the line count for each file. Let me read all 12 files in parallel.</details></think>
---
Now let me read each file to determine their line counts.
---
<tool-use data-tool-type="read" data-tool-name="read_file_v2">
<details><summary>Tool use: **read_file_v2** • Read file: T:\Dynamic-Link-Manager\__init__.py</summary>
</details>
</tool-use>
---
<tool-use data-tool-type="read" data-tool-name="read_file_v2">
<details><summary>Tool use: **read_file_v2** • 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** • Read file: T:\Dynamic-Link-Manager\ui\__init__.py</summary>
</details>
</tool-use>
---
<tool-use data-tool-type="unknown" data-tool-name="read_file_v2">
<details>
<summary>Tool use: **read_file_v2**</summary>
Status: **loading**
</details>
</tool-use>
---
<tool-use data-tool-type="unknown" data-tool-name="read_file_v2">
<details>
<summary>Tool use: **read_file_v2**</summary>
Status: **loading**
</details>
</tool-use>
---
<tool-use data-tool-type="unknown" data-tool-name="read_file_v2">
<details>
<summary>Tool use: **read_file_v2**</summary>
Status: **loading**
</details>
</tool-use>
---
<tool-use data-tool-type="unknown" data-tool-name="read_file_v2">
<details>
<summary>Tool use: **read_file_v2**</summary>
Status: **loading**
</details>
</tool-use>
---
<tool-use data-tool-type="unknown" data-tool-name="read_file_v2">
<details>
<summary>Tool use: **read_file_v2**</summary>
Status: **loading**
</details>
</tool-use>
---
<tool-use data-tool-type="unknown" data-tool-name="read_file_v2">
<details>
<summary>Tool use: **read_file_v2**</summary>
Status: **loading**
</details>
</tool-use>
---
<tool-use data-tool-type="unknown" data-tool-name="read_file_v2">
<details>
<summary>Tool use: **read_file_v2**</summary>
Status: **loading**
</details>
</tool-use>
---
<tool-use data-tool-type="unknown" data-tool-name="read_file_v2">
<details>
<summary>Tool use: **read_file_v2**</summary>
Status: **loading**
</details>
</tool-use>
---
<tool-use data-tool-type="unknown" data-tool-name="read_file_v2">
<details>
<summary>Tool use: **read_file_v2**</summary>
Status: **loading**
</details>
</tool-use>
---
---
---
@@ -0,0 +1,33 @@
<!-- Generated by SpecStory, Markdown v2.1.0 -->
<!-- cursor Session a6a5c4fa-5957-4577-9323-13723784a540 (2026-03-10 16:05Z) -->
# Restoring corrupted Python files in git (2026-03-10 16:05Z)
_**User**_
I need to restore corrupted Python files in a git repository at T:\Dynamic-Link-Manager from git. The files have been corrupted with content from another file.
Please run the following git commands to restore these files:
1. `git -C "T:\Dynamic-Link-Manager" checkout ui/operators.py`
2. `git -C "T:\Dynamic-Link-Manager" checkout ops/migrator.py`
3. `git -C "T:\Dynamic-Link-Manager" checkout __init__.py`
4. `git -C "T:\Dynamic-Link-Manager" checkout ui/__init__.py`
You can run these commands using the Shell tool with `powershell -Command` or directly. Make sure to use the full path T:\Dynamic-Link-Manager for the git commands.
---
_**Agent (model accounts/fireworks/models/kimi-k2p5, mode Agent)**_
---
<think><details><summary>Thought Process</summary>
The user wants to restore corrupted Python files in a git repository using git checkout commands. They want me to run 4 specific git checkout commands to restore files from git.
</details></think>
---
@@ -0,0 +1,78 @@
<!-- Generated by SpecStory, Markdown v2.1.0 -->
<!-- cursor Session be47994a-f179-482a-9af2-4d208a01d324 (2026-03-10 16:07Z) -->
# Listing Python files in directory (2026-03-10 16:07Z)
_**User**_
List all Python files in the directory T:\Dynamic-Link-Manager and its subdirectories. Use the Get-ChildItem PowerShell command to find all .py files recursively. Return the full paths of all Python files found.
---
_**Agent (model accounts/fireworks/models/kimi-k2p5, mode Agent)**_
---
<think><details><summary>Thought Process</summary>
The user wants me to list all Python files in the T:\Dynamic-Link-Manager directory and its subdirectories using PowerShell's Get-ChildItem command. I'll execute this command to find all .py files recursively.</details></think>
---
I'll run the PowerShell command to find all Python files recursively in that directory.
---
<tool-use data-tool-type="shell" data-tool-name="run_terminal_command_v2">
<details><summary>Tool use: **run_terminal_command_v2** • Run command: Get-ChildItem -Path "T:\Dynamic-Link-Manager" -Recurse -Filter "*.py" | Select-Object -ExpandProperty FullName</summary>
```bash
Get-ChildItem -Path "T:\Dynamic-Link-Manager" -Recurse -Filter "*.py" | Select-Object -ExpandProperty FullName
```
```
T:\Dynamic-Link-Manager\__init__.py
T:\Dynamic-Link-Manager\ops\__init__.py
T:\Dynamic-Link-Manager\ops\fk_rotations.py
T:\Dynamic-Link-Manager\ops\library.py
T:\Dynamic-Link-Manager\ops\migrator.py
T:\Dynamic-Link-Manager\ops\tweak_tools.py
T:\Dynamic-Link-Manager\ui\__init__.py
T:\Dynamic-Link-Manager\ui\operators.py
T:\Dynamic-Link-Manager\ui\panels.py
T:\Dynamic-Link-Manager\ui\preferences.py
T:\Dynamic-Link-Manager\ui\properties.py
T:\Dynamic-Link-Manager\utils\__init__.py
```
</details>
</tool-use>
---
<think><details><summary>Thought Process</summary>
The command executed successfully and found 11 Python files in the T:\Dynamic-Link-Manager directory and its subdirectories. I'll present these results to the user in a clear format.</details></think>
---
Found **11 Python files** in `T:\Dynamic-Link-Manager`:
```
T:\Dynamic-Link-Manager\__init__.py
T:\Dynamic-Link-Manager\ops\__init__.py
T:\Dynamic-Link-Manager\ops\fk_rotations.py
T:\Dynamic-Link-Manager\ops\library.py
T:\Dynamic-Link-Manager\ops\migrator.py
T:\Dynamic-Link-Manager\ops\tweak_tools.py
T:\Dynamic-Link-Manager\ui\__init__.py
T:\Dynamic-Link-Manager\ui\operators.py
T:\Dynamic-Link-Manager\ui\panels.py
T:\Dynamic-Link-Manager\ui\preferences.py
T:\Dynamic-Link-Manager\ui\properties.py
T:\Dynamic-Link-Manager\utils\__init__.py
```
---
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,259 @@
<!-- Generated by SpecStory, Markdown v2.1.0 -->
<!-- cursor Session 15ca8ebd-f444-47c7-8df3-efe4c9e8386f (2026-03-10 16:07Z) -->
# Reading content from migrator.py (2026-03-10 16:07Z)
_**User**_
Read the first 100 lines of the file T:\Dynamic-Link-Manager\ops\migrator.py and return the actual content. Use Python to read the file.
---
_**Agent (model accounts/fireworks/models/kimi-k2p5, mode Agent)**_
---
<think><details><summary>Thought Process</summary>
The user wants me to read the first 100 lines of a specific file using Python. Let me use the Shell tool to execute a Python command that reads the file.</details></think>
---
<tool-use data-tool-type="shell" data-tool-name="run_terminal_command_v2">
<details><summary>Tool use: **run_terminal_command_v2** • Run command: python -c "
with open(r'T:\Dynamic-Link-Manager\ops\migrator.py', 'r') as f:
lines = f.readlines()[:100]
print(''.join(lines))
"</summary>
```bash
python -c "
with open(r'T:\Dynamic-Link-Manager\ops\migrator.py', 'r') as f:
lines = f.readlines()[:100]
print(''.join(lines))
"
```
```
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
"""Character migrator: migrate animation, constraints, relations from original to replacement armature."""
import bpy
from ..utils import descendants, collection_containing_armature
def get_pair_manual(context):
"""Return (orig_armature, rep_armature) from scene props, or (None, None)."""
props = getattr(context.scene, "dynamic_link_manager", None)
if not props:
return None, None
orig = getattr(props, "original_character", None)
rep = getattr(props, "replacement_character", None)
if orig and orig.type == "ARMATURE" and rep and rep.type == "ARMATURE":
return orig, rep
return None, None
def get_pair_automatic(context):
"""Discover one pair by convention: Name_Rigify and Name_Rigify.001. Returns (orig, rep) or (None, None)."""
pairs = []
for obj in bpy.data.objects:
if obj.type != "ARMATURE":
continue
name = obj.name
if name.endswith("_Rigify.001"):
base = name[:-len("_Rigify.001")]
orig = bpy.data.objects.get(f"{base}_Rigify")
if orig and orig.type == "ARMATURE" and orig != obj:
pairs.append((orig, obj))
return pairs[0] if pairs else (None, None)
def run_copy_attr(orig, rep):
"""Copy armature object attributes: location, rotation, scale (CopyAttr)."""
rep.location = orig.location.copy()
if orig.rotation_mode == "QUATERNION":
rep.rotation_quaternion = orig.rotation_quaternion.copy()
else:
rep.rotation_euler = orig.rotation_euler.copy()
rep.scale = orig.scale.copy()
def _has_als_anywhere(orig):
"""Return True if orig has Animation Layers (addon uses Object.als RNA PropertyGroup, obj.als.turn_on)."""
# Animation Layers addon: bpy.types.Object.als (RNA), not id props
if getattr(orig, "als", None) is not None:
return True
key = "als.turn_on"
if key in orig:
return True
if getattr(orig, "data", None) and hasattr(orig.data, "keys") and key in orig.data:
return True
try:
als = orig.get("als")
if als is not None and callable(getattr(als, "keys", None)) and "turn_on" in als.keys():
return True
except Exception:
pass
for pb in orig.pose.bones:
if key in pb:
return True
try:
als = pb.get("als")
if als is not None and callable(getattr(als, "keys", None)) and "turn_on" in als.keys():
return True
except Exception:
pass
return False
def _debug_als_lookup(orig):
"""Print full debug for AnimLayers: RNA obj.als and every id_prop on orig."""
key = "als.turn_on"
print("[DLM MigNLA] === AnimLayers debug ===")
als_rna = getattr(orig, "als", None)
print(f"[DLM MigNLA] orig.als (RNA): {als_rna!r}, turn_on={getattr(als_rna, 'turn_on', 'N/A') if als_rna else 'N/A'}")
print(f"[DLM MigNLA] 'als.turn_on' in orig (object): {key in orig}")
if getattr(orig, "data", None):
has_data_keys = hasattr(orig.data, "keys")
print(f"[DLM MigNLA] orig.data has keys(): {has_data_keys}")
if has_data_keys:
print(f"[DLM MigNLA] 'als.turn_on' in orig.data: {key in orig.data}")
print(f"[DLM MigNLA] orig.data keys: {list(orig.data.keys())}")
try:
als = orig.get("als")
has_als = als is not None and callable(getattr(als, "keys", None))
print(f"[DLM MigNLA] orig.get('als') is group: {has_als}")
if has_als:
print(f"[DLM MigNLA] orig['als'] keys: {list(als.keys())}")
print(f"[DLM MigNLA] 'turn_on' in orig['als']: {'turn_on' in als.keys()}")
except Exception as e:
print(f"[DLM MigNLA] orig.get('als') error: {e}")
print(f"[DLM MigNLA] orig (object) all keys: {list(orig.keys())}")
```
</details>
</tool-use>
---
<think><details><summary>Thought Process</summary>
The command executed successfully and returned the first 100 lines of the file. I should present this content to the user clearly.</details></think>
---
Here are the first 100 lines of `T:\Dynamic-Link-Manager\ops\migrator.py`:
```python
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
"""Character migrator: migrate animation, constraints, relations from original to replacement armature."""
import bpy
from ..utils import descendants, collection_containing_armature
def get_pair_manual(context):
"""Return (orig_armature, rep_armature) from scene props, or (None, None)."""
props = getattr(context.scene, "dynamic_link_manager", None)
if not props:
return None, None
orig = getattr(props, "original_character", None)
rep = getattr(props, "replacement_character", None)
if orig and orig.type == "ARMATURE" and rep and rep.type == "ARMATURE":
return orig, rep
return None, None
def get_pair_automatic(context):
"""Discover one pair by convention: Name_Rigify and Name_Rigify.001. Returns (orig, rep) or (None, None)."""
pairs = []
for obj in bpy.data.objects:
if obj.type != "ARMATURE":
continue
name = obj.name
if name.endswith("_Rigify.001"):
base = name[:-len("_Rigify.001")]
orig = bpy.data.objects.get(f"{base}_Rigify")
if orig and orig.type == "ARMATURE" and orig != obj:
pairs.append((orig, obj))
return pairs[0] if pairs else (None, None)
def run_copy_attr(orig, rep):
"""Copy armature object attributes: location, rotation, scale (CopyAttr)."""
rep.location = orig.location.copy()
if orig.rotation_mode == "QUATERNION":
rep.rotation_quaternion = orig.rotation_quaternion.copy()
else:
rep.rotation_euler = orig.rotation_euler.copy()
rep.scale = orig.scale.copy()
def _has_als_anywhere(orig):
"""Return True if orig has Animation Layers (addon uses Object.als RNA PropertyGroup, obj.als.turn_on)."""
# Animation Layers addon: bpy.types.Object.als (RNA), not id props
if getattr(orig, "als", None) is not None:
return True
key = "als.turn_on"
if key in orig:
return True
if getattr(orig, "data", None) and hasattr(orig.data, "keys") and key in orig.data:
return True
try:
als = orig.get("als")
if als is not None and callable(getattr(als, "keys", None)) and "turn_on" in als.keys():
return True
except Exception:
pass
for pb in orig.pose.bones:
if key in pb:
return True
try:
als = pb.get("als")
if als is not None and callable(getattr(als, "keys", None)) and "turn_on" in als.keys():
return True
except Exception:
pass
return False
def _debug_als_lookup(orig):
"""Print full debug for AnimLayers: RNA obj.als and every id_prop on orig."""
key = "als.turn_on"
print("[DLM MigNLA] === AnimLayers debug ===")
als_rna = getattr(orig, "als", None)
print(f"[DLM MigNLA] orig.als (RNA): {als_rna!r}, turn_on={getattr(als_rna, 'turn_on', 'N/A') if als_rna else 'N/A'}")
print(f"[DLM MigNLA] 'als.turn_on' in orig (object): {key in orig}")
if getattr(orig, "data", None):
has_data_keys = hasattr(orig.data, "keys")
print(f"[DLM MigNLA] orig.data has keys(): {has_data_keys}")
if has_data_keys:
print(f"[DLM MigNLA] 'als.turn_on' in orig.data: {key in orig.data}")
print(f"[DLM MigNLA] orig.data keys: {list(orig.data.keys())}")
try:
als = orig.get("als")
has_als = als is not None and callable(getattr(als, "keys", None))
print(f"[DLM MigNLA] orig.get('als') is group: {has_als}")
if has_als:
print(f"[DLM MigNLA] orig['als'] keys: {list(als.keys())}")
print(f"[DLM MigNLA] 'turn_on' in orig['als']: {'turn_on' in als.keys()}")
except Exception as e:
print(f"[DLM MigNLA] orig.get('als') error: {e}")
print(f"[DLM MigNLA] orig (object) all keys: {list(orig.keys())}")
```
---
@@ -0,0 +1,306 @@
<!-- Generated by SpecStory, Markdown v2.1.0 -->
<!-- cursor Session f1bb25c5-e5d4-4aad-88a3-f562ab877040 (2026-03-10 16:08Z) -->
# File content reading in Python (2026-03-10 16:08Z)
_**User**_
Read the full content of the file T:\Dynamic-Link-Manager\ui\panels.py and return it. Use Python to read and print the file.
---
_**Agent (model accounts/fireworks/models/kimi-k2p5, mode Agent)**_
---
<think><details><summary>Thought Process</summary>
The user wants me to read the full content of a file at `T:\Dynamic-Link-Manager\ui\panels.py` using Python to read and print the file.
Let me use the Shell tool to execute a Python command that reads and prints the file content.</details></think>
---
<tool-use data-tool-type="shell" data-tool-name="run_terminal_command_v2">
<details><summary>Tool use: **run_terminal_command_v2** • Run command: python -c "with open('T:\\Dynamic-Link-Manager\\ui\\panels.py', 'r') as f: print(f.read())"</summary>
```bash
python -c "with open('T:\\Dynamic-Link-Manager\\ui\\panels.py', 'r') as f: print(f.read())"
```
```
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
import bpy
from bpy.types import Panel, UIList
from . import properties
class DLM_UL_library_list(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
if self.layout_type in {"DEFAULT", "COMPACT"}:
layout.scale_x = 0.4
layout.label(text=item.name)
layout.scale_x = 0.3
if item.is_missing:
layout.label(text="MISSING", icon="ERROR")
elif item.is_indirect:
layout.label(text="INDIRECT", icon="INFO")
else:
layout.label(text="OK", icon="FILE_BLEND")
layout.scale_x = 0.3
path_text = item.filepath or ""
if path_text.startswith("\\\\"):
parts = path_text.split("\\")
short_path = f"\\\\{parts[2]}\\" if len(parts) &gt;= 3 else "\\\\ (network)"
elif len(path_text) &gt;= 2 and path_text[1] == ":":
short_path = f"{path_text[:2]}\\"
elif path_text.startswith("//"):
short_path = "// (relative)"
else:
short_path = path_text[:15] + "..." if len(path_text) &gt; 15 else path_text
layout.label(text=short_path, icon="FILE_FOLDER")
elif self.layout_type == "GRID":
layout.alignment = "CENTER"
layout.label(text="", icon="FILE_BLEND")
def _get_short_path(filepath):
if not filepath:
return "Unknown"
if filepath.startswith("//"):
return "// (relative)"
if filepath.startswith("\\\\"):
parts = filepath.split("\\")
return f"\\\\{parts[2]}\\" if len(parts) &gt;= 3 else "\\\\ (network)"
if len(filepath) &gt;= 2 and filepath[1] == ":":
return f"{filepath[:2]}\\"
if filepath.startswith("/"):
parts = filepath.split("/")
return f"/{parts[1]}/" if len(parts) &gt;= 2 else "/ (root)"
return "Unknown"
class DLM_PT_main_panel(Panel):
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = "Dynamic Link Manager"
bl_label = "Dynamic Link Manager"
def draw_header(self, context):
layout = self.layout
layout.operator("preferences.addon_show", text="", icon="PREFERENCES").module = __package__.rsplit(".", 1)[0]
def draw(self, context):
layout = self.layout
props = context.scene.dynamic_link_manager
# Path management
row = layout.row()
row.operator("dlm.make_paths_relative", text="Make Paths Relative", icon="FILE_PARENT")
row.operator("dlm.make_paths_absolute", text="Make Paths Absolute", icon="FILE_FOLDER")
# CharMig section (placeholder; full UI in implementation step 5)
box = layout.box()
box.label(text="Character Migrator")
row = box.row()
row.prop(props, "migrator_mode", text="Automatic pair discovery")
row = box.row()
row.prop(props, "original_character", text="Original")
row.operator("dlm.picker_original_character", text="", icon="EYEDROPPER")
row = box.row()
row.prop(props, "replacement_character", text="Replacement")
row.operator("dlm.picker_replacement_character", text="", icon="EYEDROPPER")
row = box.row()
row.operator("dlm.run_character_migration", text="Run migration", icon="ARMATURE_DATA")
row = box.row(align=True)
row.operator("dlm.migrator_copy_attributes", text="CopyAttr", icon="COPY_ID")
row.operator("dlm.migrator_migrate_nla", text="MigNLA", icon="NLA")
row.operator("dlm.migrator_custom_properties", text="MigCustProps", icon="PROPERTIES")
row = box.row(align=True)
row.operator("dlm.migrator_bone_constraints", text="MigBoneConst", icon="CONSTRAINT_BONE")
row.operator("dlm.migrator_retarget_relations", text="RetargRelatives", icon="ORIENTATION_PARENT")
# Situational
situational_box = layout.box()
situational_box.label(text="Situational Fixes", icon="QUESTION")
row = situational_box.row(align=True)
row.operator("dlm.migrator_basebody_shapekeys", text="MigBBodyShapeKeys", icon="SHAPEKEY_DATA")
row = situational_box.row(align=True)
row.operator("dlm.migrator_fk_rotations", text="MigFKRot", icon="BONE_DATA")
row.operator("dlm.migrator_fk_rotations_remove", text="Remove", icon="X")
row.operator("dlm.migrator_fk_rotations_bake", text="Bake", icon="KEYFRAME")
# Tweak Tools: header row (always), main box only when expanded
section_icon = "DISCLOSURE_TRI_DOWN" if props.tweak_tools_section_expanded else "DISCLOSURE_TRI_RIGHT"
row = layout.row(align=True)
row.prop(props, "tweak_tools_section_expanded", text="", icon=section_icon, icon_only=True)
row.label(text="Tweak Tools", icon="CONSTRAINT")
if props.tweak_tools_section_expanded:
tweak_box = layout.box()
row = tweak_box.row(align=True)
row.operator("dlm.tweak_add_arm", text="Add Arm", icon="CONSTRAINT_BONE")
row.operator("dlm.tweak_remove_arm", text="Remove Arm", icon="X")
row.operator("dlm.tweak_bake_arm", text="Bake Arm", icon="KEYFRAME")
row = tweak_box.row(align=True)
row.operator("dlm.tweak_add_leg", text="Add Leg", icon="CONSTRAINT_BONE")
row.operator("dlm.tweak_remove_leg", text="Remove Leg", icon="X")
row.operator("dlm.tweak_bake_leg", text="Bake Leg", icon="KEYFRAME")
row = tweak_box.row(align=True)
row.operator("dlm.tweak_add_both", text="Add Both", icon="CONSTRAINT_BONE")
row.operator("dlm.tweak_remove_both", text="Remove Both", icon="X")
row.operator("dlm.tweak_bake_both", text="Bake Both", icon="KEYFRAME")
row = tweak_box.row()
row.prop(props, "tweak_nla_track_name", text="NLA track")
row = tweak_box.row()
row.prop(props, "tweak_bake_post_clean", text="Post-clean after bake")
# Linked Libraries: header row (always), main box only when expanded
missing_count = sum(1 for lib in props.linked_libraries if lib.is_missing)
section_icon = "DISCLOSURE_TRI_DOWN" if props.linked_libraries_section_expanded else "DISCLOSURE_TRI_RIGHT"
row = layout.row(align=True)
row.prop(props, "linked_libraries_section_expanded", text="", icon=section_icon, icon_only=True)
row.label(text="Linked Libraries Analysis")
if missing_count &gt; 0:
row.label(text=f"({props.linked_assets_count} libs, {missing_count} missing)", icon="ERROR")
else:
row.label(text=f"({props.linked_assets_count} libraries)")
if props.linked_libraries_section_expanded:
box = layout.box()
row = box.row()
row.operator("dlm.scan_linked_assets", text="Scan Linked Assets", icon="FILE_REFRESH")
if props.linked_assets_count &gt; 0:
row = box.row()
row.template_list("DLM_UL_library_list", "", props, "linked_libraries", props, "linked_libraries_index")
row = box.row()
row.operator("dlm.reload_libraries", text="Reload Libraries", icon="FILE_REFRESH")
prefs = context.preferences.addons.get(__package__.rsplit(".", 1)[0])
if prefs and prefs.preferences.search_paths:
for i, path_item in enumerate(prefs.preferences.search_paths):
row = box.row()
row.prop(path_item, "path", text=f"Search path {i+1}")
row.operator("dlm.browse_search_path", text="", icon="FILE_FOLDER").index = i
row.operator("dlm.remove_search_path", text="", icon="REMOVE").index = i
row = box.row()
row.alignment = "RIGHT"
row.operator("dlm.add_search_path", text="Add search path", icon="ADD")
if missing_count &gt; 0:
row = box.row()
row.operator("dlm.find_libraries_in_folders", text="Find libraries in these folders", icon="VIEWZOOM")
if props.linked_libraries and 0 &lt;= props.linked_libraries_index &lt; len(props.linked_libraries):
selected_lib = props.linked_libraries[props.linked_libraries_index]
info_box = box.box()
if selected_lib.is_missing:
info_box.alert = True
info_box.label(text=f"Selected: {selected_lib.name}")
if selected_lib.is_missing:
info_box.label(text="Status: MISSING", icon="ERROR")
elif selected_lib.is_indirect:
info_box.label(text="Status: INDIRECT", icon="INFO")
else:
info_box.label(text="Status: OK", icon="FILE_BLEND")
info_box.label(text=f"Path: {selected_lib.filepath}", icon="FILE_FOLDER")
info_box.operator("dlm.open_linked_file", text="Open Blend", icon="FILE_BLEND").filepath = selected_lib.filepath
op = info_box.operator("dlm.relocate_single_library", text="Relocate Library", icon="FILE_FOLDER")
op.target_filepath = selected_lib.filepath
```
</details>
</tool-use>
---
<think><details><summary>Thought Process</summary>
The command executed successfully and returned the full content of the file. Let me present this to the user clearly.</details></think>
---
Here is the full content of `T:\Dynamic-Link-Manager\ui\panels.py`:
```python
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
import bpy
from bpy.types import Panel, UIList
from . import properties
class DLM_UL_library_list(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
if self.layout_type in {"DEFAULT", "COMPACT"}:
layout.scale_x = 0.4
layout.label(text=item.name)
layout.scale_x = 0.3
if item.is_missing:
layout.label(text="MISSING", icon="ERROR")
elif item.is_indirect:
layout.label(text="INDIRECT", icon="INFO")
else:
layout.label(text="OK", icon="FILE_BLEND")
layout.scale_x = 0.3
path_text = item.filepath or ""
if path_text.startswith("\\\\"):
parts = path_text.split("\\")
short_path = f"\\\\{parts[2]}\\" if len(parts) >= 3 else "\\\\ (network)"
elif len(path_text) >= 2 and path_text[1] == ":":
short_path = f"{path_text[:2]}\\"
elif path_text.startswith("//"):
short_path = "// (relative)"
else:
short_path = path_text[:15] + "..." if len(path_text) > 15 else path_text
layout.label(text=short_path, icon="FILE_FOLDER")
elif self.layout_type == "GRID":
layout.alignment = "CENTER"
layout.label(text="", icon="FILE_BLEND")
def _get_short_path(filepath):
if not filepath:
return "Unknown"
if filepath.startswith("//"):
return "// (relative)"
if filepath.startswith("\\\\"):
parts = filepath.split("\\")
return f"\\\\{parts[2]}\\" if len(parts) >= 3 else "\\\\ (network)"
if len(filepath) >= 2 and filepath[1] == ":":
return f"{filepath[:2]}\\"
if filepath.startswith("/"):
parts = filepath.split("/")
return f"/{parts[1]}/" if len(parts) >= 2 else "/ (root)"
return "Unknown"
class DLM_PT_main_panel(Panel):
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = "Dynamic Link Manager"
bl_label = "Dynamic Link Manager"
def draw_header(self, context):
layout = self.layout
layout.operator("preferences.addon_show", text="", icon="PREFERENCES").module = __package__.rsplit(".", 1)[0]
def draw(self, context):
layout = self.layout
props = context.scene.dynamic_link_manager
# Path management
row = layout.row()
row.operator("dlm.make_paths_relative", text="Make Paths Relative", icon="FILE_PARENT")
row.operator("dlm.make_paths_absolute", text="Make Paths Absolute", icon="FILE_FOLDER")
# CharMig section (placeholder; full UI in implementation step 5)
box = layout.box()
box.label(text="Character Migrator
---
@@ -0,0 +1,135 @@
<!-- Generated by SpecStory, Markdown v2.1.0 -->
<!-- cursor Session 49972a6d-d81d-4647-b6b5-9c92f8599474 (2026-03-10 16:09Z) -->
# Operator line modification in panels.py (2026-03-10 16:09Z)
_**User**_
Update the file T:\Dynamic-Link-Manager\ui\panels.py by adding a new operator line.
Find this line (around line 87):
```
row.operator("dlm.run_character_migration", text="Run migration", icon="ARMATURE_DATA")
```
And replace it with these two lines:
```
row.operator("dlm.migrator_remove_original", text="Remove Original", icon="TRASH")
row.operator("dlm.run_character_migration", text="Run migration", icon="ARMATURE_DATA")
```
Use Python to read the file, make the replacement, and write it back.
---
_**Agent (model accounts/fireworks/models/kimi-k2p5, mode Agent)**_
---
<think><details><summary>Thought Process</summary>
The user wants me to update a specific file by replacing one line with two lines. I need to use Python to read the file, find the line around line 87, and make the replacement.
Let me first read the file to see its contents and understand the structure.</details></think>
---
<tool-use data-tool-type="shell" data-tool-name="run_terminal_command_v2">
<details><summary>Tool use: **run_terminal_command_v2** • Run command: python -c "
with open(r'T:\Dynamic-Link-Manager\ui\panels.py', 'r') as f:
lines = f.readlines()
for i, line in enumerate(lines[85:92], start=86):
print(f'{i}|{line}', end='')
"</summary>
```bash
python -c "
with open(r'T:\Dynamic-Link-Manager\ui\panels.py', 'r') as f:
lines = f.readlines()
for i, line in enumerate(lines[85:92], start=86):
print(f'{i}|{line}', end='')
"
```
```
86| row = box.row()
87| row.operator("dlm.run_character_migration", text="Run migration", icon="ARMATURE_DATA")
88| row = box.row(align=True)
89| row.operator("dlm.migrator_copy_attributes", text="CopyAttr", icon="COPY_ID")
90| row.operator("dlm.migrator_migrate_nla", text="MigNLA", icon="NLA")
91| row.operator("dlm.migrator_custom_properties", text="MigCustProps", icon="PROPERTIES")
92| row = box.row(align=True)
```
</details>
</tool-use>
---
<think><details><summary>Thought Process</summary>
I can see the file content. Line 87 contains the line I need to replace. Now I'll write Python code to:
1. Read the file
2. Replace line 87 with the two new lines
3. Write the file back</details></think>
---
<tool-use data-tool-type="shell" data-tool-name="run_terminal_command_v2">
<details><summary>Tool use: **run_terminal_command_v2** • Run command: python -c "
filepath = r'T:\Dynamic-Link-Manager\ui\panels.py'
with open(filepath, 'r') as f:
lines = f.readlines()
# Line 87 is at index 86
new_lines = [
' row.operator(\"dlm.migrator_remove_original\", text=\"Remove Original\", icon=\"TRASH\")\n',
' row.operator(\"dlm.run_character_migration\", text=\"Run migration\", icon=\"ARMATURE_DATA\")\n'
]
# Replace line at index 86 with the two new lines
lines[86:87] = new_lines
with open(filepath, 'w') as f:
f.writelines(lines)
print('File updated successfully.')
"</summary>
```bash
python -c "
filepath = r'T:\Dynamic-Link-Manager\ui\panels.py'
with open(filepath, 'r') as f:
lines = f.readlines()
# Line 87 is at index 86
new_lines = [
' row.operator(\"dlm.migrator_remove_original\", text=\"Remove Original\", icon=\"TRASH\")\n',
' row.operator(\"dlm.run_character_migration\", text=\"Run migration\", icon=\"ARMATURE_DATA\")\n'
]
# Replace line at index 86 with the two new lines
lines[86:87] = new_lines
with open(filepath, 'w') as f:
f.writelines(lines)
print('File updated successfully.')
"
```
```
File "&lt;string&gt;", line 9
' row.operator(\
^
SyntaxError: unterminated string literal (detected at line 9)
```
</details>
</tool-use>
---
@@ -0,0 +1,86 @@
<!-- Generated by SpecStory, Markdown v2.1.0 -->
<!-- cursor Session b450a89e-cc97-4ef3-a775-fe26c81c7d0b (2026-03-10 16:09Z) -->
# Reading lines with whitespace representation (2026-03-10 16:09Z)
_**User**_
Read lines 82-92 of T:\Dynamic-Link-Manager\ui\panels.py and print them with exact whitespace representation (use repr() on each line).
---
_**Agent (model accounts/fireworks/models/kimi-k2p5, mode Agent)**_
---
<think><details><summary>Thought Process</summary>
The user wants me to read lines 82-92 of a specific file and print them with exact whitespace representation using repr(). I need to use the Shell tool to execute a Python command that will read these specific lines and print them with repr() to show exact whitespace.
Let me execute a Python command to do this.</details></think>
---
<tool-use data-tool-type="shell" data-tool-name="run_terminal_command_v2">
<details><summary>Tool use: **run_terminal_command_v2** • Run command: python -c "
with open(r'T:\Dynamic-Link-Manager\ui\panels.py', 'r') as f:
lines = f.readlines()
for i in range(81, 92): # lines 82-92 (0-indexed: 81-91)
if i < len(lines):
print(f'{i+1}: {repr(lines[i])}')
"</summary>
```bash
python -c "
with open(r'T:\Dynamic-Link-Manager\ui\panels.py', 'r') as f:
lines = f.readlines()
for i in range(81, 92): # lines 82-92 (0-indexed: 81-91)
if i < len(lines):
print(f'{i+1}: {repr(lines[i])}')
"
```
```
82: ' row.operator("dlm.picker_original_character", text="", icon="EYEDROPPER")\n'
83: ' row = box.row()\n'
84: ' row.prop(props, "replacement_character", text="Replacement")\n'
85: ' row.operator("dlm.picker_replacement_character", text="", icon="EYEDROPPER")\n'
86: ' row = box.row()\n'
87: ' row.operator("dlm.run_character_migration", text="Run migration", icon="ARMATURE_DATA")\n'
88: ' row = box.row(align=True)\n'
89: ' row.operator("dlm.migrator_copy_attributes", text="CopyAttr", icon="COPY_ID")\n'
90: ' row.operator("dlm.migrator_migrate_nla", text="MigNLA", icon="NLA")\n'
91: ' row.operator("dlm.migrator_custom_properties", text="MigCustProps", icon="PROPERTIES")\n'
92: ' row = box.row(align=True)\n'
```
</details>
</tool-use>
---
<think><details><summary>Thought Process</summary>
The command executed successfully and returned the lines 82-92 with exact whitespace representation using repr(). I can see each line with its line number and the exact representation including newline characters and any whitespace.</details></think>
---
Here are lines 82-92 of `T:\Dynamic-Link-Manager\ui\panels.py` with exact whitespace representation:
```
82: ' row.operator("dlm.picker_original_character", text="", icon="EYEDROPPER")\n'
83: ' row = box.row()\n'
84: ' row.prop(props, "replacement_character", text="Replacement")\n'
85: ' row.operator("dlm.picker_replacement_character", text="", icon="EYEDROPPER")\n'
86: ' row = box.row()\n'
87: ' row.operator("dlm.run_character_migration", text="Run migration", icon="ARMATURE_DATA")\n'
88: ' row = box.row(align=True)\n'
89: ' row.operator("dlm.migrator_copy_attributes", text="CopyAttr", icon="COPY_ID")\n'
90: ' row.operator("dlm.migrator_migrate_nla", text="MigNLA", icon="NLA")\n'
91: ' row.operator("dlm.migrator_custom_properties", text="MigCustProps", icon="PROPERTIES")\n'
92: ' row = box.row(align=True)\n'
```
---
@@ -0,0 +1,46 @@
<!-- Generated by SpecStory, Markdown v2.1.0 -->
<!-- cursor Session cb944af7-6783-4830-acd6-af18544ff67e (2026-03-10 16:10Z) -->
# Verification of changes in panels.py (2026-03-10 16:10Z)
_**User**_
Read lines 82-95 of T:\Dynamic-Link-Manager\ui\panels.py to verify the changes were applied correctly.
---
_**Agent (model accounts/fireworks/models/kimi-k2p5, mode Agent)**_
---
<think><details><summary>Thought Process</summary>
The user wants me to read specific lines (82-95) from a file at T:\Dynamic-Link-Manager\ui\panels.py to verify changes were applied correctly. I need to use a file reading tool to get this content.
Since I don't see a specific Read tool in my available tools, I'll use the Shell tool to read the file with appropriate commands.</details></think>
---
<tool-use data-tool-type="shell" data-tool-name="run_terminal_command_v2">
<details><summary>Tool use: **run_terminal_command_v2** • Run command: sed -n '82,95p' "T:\Dynamic-Link-Manager\ui\panels.py"</summary>
```bash
sed -n '82,95p' "T:\Dynamic-Link-Manager\ui\panels.py"
```
```
sed: C:\Users\Nathan\AppData\Local\Temp\ps-script-3ec7d68b-574e-4559-a5c7-d770b9bf96d5.ps1:7
Line |
7 | sed -n '82,95p' "T:\Dynamic-Link-Manager\ui\panels.py"
| ~~~
| The term 'sed' is not recognized as a name of a cmdlet, function, script file, or executable program. Check the
| spelling of the name, or if a path was included, verify that the path is correct and try again.
```
</details>
</tool-use>
---
@@ -0,0 +1,129 @@
<!-- Generated by SpecStory, Markdown v2.1.0 -->
<!-- cursor Session 3d77131c-052c-4be7-b2e4-2326abcd6957 (2026-03-10 16:15Z) -->
# File integrity check and code modification (2026-03-10 16:15Z)
_**User**_
The file at t:/Dynamic-Link-Manager/ui/panels.py may be corrupted.
1. First, check if the file is corrupted by reading its first 100 characters. If it contains terminal output (like "head:" or PowerShell errors) instead of Python code, then it's corrupted.
2. If corrupted, restore it from git using: git checkout ui/panels.py
3. Then re-apply this change: Find the line with `row.operator("dlm.run_character_migration", text="Run migration", icon="ARMATURE_DATA")` and add BEFORE it: `row.operator("dlm.migrator_remove_original", text="Remove Original", icon="TRASH")`
Report back what you found and what actions you took.
---
_**Agent (model accounts/fireworks/models/kimi-k2p5, mode Agent)**_
---
<think><details><summary>Thought Process</summary>
The user wants me to:
1. Check if t:/Dynamic-Link-Manager/ui/panels.py is corrupted by reading first 100 characters
2. If corrupted, restore from git
3. Re-apply a specific change
Let me start by reading the first 100 characters of the file.</details></think>
---
<tool-use data-tool-type="shell" data-tool-name="run_terminal_command_v2">
<details><summary>Tool use: **run_terminal_command_v2** • Run command: cd "t:/Dynamic-Link-Manager" && head -c 100 ui/panels.py</summary>
```bash
cd "t:/Dynamic-Link-Manager" && head -c 100 ui/panels.py
```
```
head: C:\Users\Nathan\AppData\Local\Temp\ps-script-91b9ed9c-9402-4b22-8274-cfd7ac52481b.ps1:7
Line |
7 | cd "t:/Dynamic-Link-Manager" &amp;&amp; head -c 100 ui/panels.py
| ~~~~
| The term 'head' is not recognized as a name of a cmdlet, function, script file, or executable program. Check the
| spelling of the name, or if a path was included, verify that the path is correct and try again.
```
</details>
</tool-use>
---
<think><details><summary>Thought Process</summary>
`head` is not available. Let me try using PowerShell's `Get-Content` to read the first 100 characters of the file.</details></think>
---
<tool-use data-tool-type="shell" data-tool-name="run_terminal_command_v2">
<details><summary>Tool use: **run_terminal_command_v2** • Run command: cd "t:/Dynamic-Link-Manager" && powershell -Command "Get-Content ui/panels.py -TotalCount 1 | ForEach-Object { $_.Substring(0, [Math]::Min(100, $_.Length)) }"</summary>
```bash
cd "t:/Dynamic-Link-Manager" && powershell -Command "Get-Content ui/panels.py -TotalCount 1 | ForEach-Object { $_.Substring(0, [Math]::Min(100, $_.Length)) }"
```
```
At line:1 char:89
+ ... otalCount 1 | ForEach-Object { .Substring(0, [Math]::Min(100, .Length ...
+ ~
Missing expression after ','.
At line:1 char:90
+ ... Count 1 | ForEach-Object { .Substring(0, [Math]::Min(100, .Length)) }
+ ~~~~~~~
Unexpected token '.Length' in expression or statement.
At line:1 char:89
+ ... otalCount 1 | ForEach-Object { .Substring(0, [Math]::Min(100, .Length ...
+ ~
Missing closing ')' in expression.
At line:1 char:57
+ Get-Content ui/panels.py -TotalCount 1 | ForEach-Object { .Substring( ...
+ ~
Missing closing '}' in statement block or type definition.
At line:1 char:97
+ ... Count 1 | ForEach-Object { .Substring(0, [Math]::Min(100, .Length)) }
+ ~
Unexpected token ')' in expression or statement.
At line:1 char:98
+ ... Count 1 | ForEach-Object { .Substring(0, [Math]::Min(100, .Length)) }
+ ~
Unexpected token ')' in expression or statement.
At line:1 char:100
+ ... Count 1 | ForEach-Object { .Substring(0, [Math]::Min(100, .Length)) }
+ ~
Unexpected token '}' in expression or statement.
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : MissingExpressionAfterToken
```
</details>
</tool-use>
---
<think><details><summary>Thought Process</summary>
The PowerShell command syntax is causing issues. Let me just read the file using Python which will be more reliable.</details></think>
---
<tool-use data-tool-type="shell" data-tool-name="run_terminal_command_v2">
<details><summary>Tool use: **run_terminal_command_v2** • Run command: cd "t:/Dynamic-Link-Manager" && python -c "with open('ui/panels.py', 'r', encoding='utf-8') as f: print(repr(f.read(100)))"</summary>
```bash
cd "t:/Dynamic-Link-Manager" && python -c "with open('ui/panels.py', 'r', encoding='utf-8') as f: print(repr(f.read(100)))"
```
```
'# This program is free software; you can redistribute it and/or modify\n# it under the terms of the G'
```
</details>
</tool-use>
---
@@ -0,0 +1,80 @@
<!-- Generated by SpecStory, Markdown v2.1.0 -->
<!-- cursor Session 73bda206-5380-40cc-afb8-b5fc426fc0dc (2026-03-10 16:17Z) -->
# Class definition and inclusion verification (2026-03-10 16:17Z)
_**User**_
Check the file T:\Dynamic-Link-Manager\ui\operators.py to verify:
1. The DLM_OT_migrator_remove_original class is defined with correct bl_idname "dlm.migrator_remove_original"
2. The class is included in the OPERATOR_CLASSES list at the bottom of the file
Print the relevant sections of the file to verify.
---
_**Agent (model accounts/fireworks/models/kimi-k2p5, mode Agent)**_
---
<think><details><summary>Thought Process</summary>
The user wants me to check a specific file to verify certain code exists. Let me read the file and look for the specific class and the OPERATOR_CLASSES list.</details></think>
---
<tool-use data-tool-type="shell" data-tool-name="run_terminal_command_v2">
<details><summary>Tool use: **run_terminal_command_v2** • Run command: rg "class DLM_OT_migrator_remove_original|bl_idname.*=.*\"dlm.migrator_remove_original\"|^OPERATOR_CLASSES|DLM_OT_migrator_remove_original" "T:\Dynamic-Link-Manager\ui\operators.py" --line-number --context 3</summary>
```bash
rg "class DLM_OT_migrator_remove_original|bl_idname.*=.*\"dlm.migrator_remove_original\"|^OPERATOR_CLASSES|DLM_OT_migrator_remove_original" "T:\Dynamic-Link-Manager\ui\operators.py" --line-number --context 3
```
```
rg: C:\Users\Nathan\AppData\Local\Temp\ps-script-da8b7975-d4d1-4c9c-b469-9c0979b7c2cf.ps1:7
Line |
7 | rg "class DLM_OT_migrator_remove_original|bl_idname.*=.*\"dlm.migrato .
| ~~
| The term 'rg' is not recognized as a name of a cmdlet, function, script file, or executable program. Check the
| spelling of the name, or if a path was included, verify that the path is correct and try again.
```
</details>
</tool-use>
---
<think><details><summary>Thought Process</summary>
rg is not available. Let me use PowerShell's Select-String instead.</details></think>
---
<tool-use data-tool-type="shell" data-tool-name="run_terminal_command_v2">
<details><summary>Tool use: **run_terminal_command_v2** • Run command: Select-String -Path "T:\Dynamic-Link-Manager\ui\operators.py" -Pattern "class DLM_OT_migrator_remove_original|bl_idname.*dlm\.migrator_remove_original|OPERATOR_CLASSES" -Context 2,2</summary>
```bash
Select-String -Path "T:\Dynamic-Link-Manager\ui\operators.py" -Pattern "class DLM_OT_migrator_remove_original|bl_idname.*dlm\.migrator_remove_original|OPERATOR_CLASSES" -Context 2,2
```
```
ui\operators.py:496:
ui\operators.py:497:
&gt; ui\operators.py:498:class DLM_OT_migrator_remove_original(Operator):
&gt; ui\operators.py:499: bl_idname = "dlm.migrator_remove_original"
ui\operators.py:500: bl_label = "Remove Original"
ui\operators.py:501: bl_description = "Delete the original character armature and its data from the scene"
ui\operators.py:771:
ui\operators.py:772:
&gt; ui\operators.py:773:OPERATOR_CLASSES = [
ui\operators.py:774: DLM_OT_replace_linked_asset,
ui\operators.py:775: DLM_OT_scan_linked_assets,
```
</details>
</tool-use>
---
@@ -0,0 +1,112 @@
<!-- Generated by SpecStory, Markdown v2.1.0 -->
<!-- cursor Session d613f220-0255-4158-8a5d-4f93d50aa47b (2026-03-10 16:17Z) -->
# Operator classes update in operators.py (2026-03-10 16:17Z)
_**User**_
Update T:\Dynamic-Link-Manager\ui\operators.py to add DLM_OT_migrator_remove_original to the OPERATOR_CLASSES list.
The class should be added right before DLM_OT_run_character_migration (since that's where it appears in the UI).
Find the OPERATOR_CLASSES list and change:
```
DLM_OT_relocate_single_library,
DLM_OT_run_character_migration,
```
To:
```
DLM_OT_relocate_single_library,
DLM_OT_migrator_remove_original,
DLM_OT_run_character_migration,
```
Use Python to make this edit.
---
_**Agent (model accounts/fireworks/models/kimi-k2p5, mode Agent)**_
---
<think><details><summary>Thought Process</summary>
The user wants me to update the `ui/operators.py` file to add `DLM_OT_migrator_remove_original` to the `OPERATOR_CLASSES` list, specifically right before `DLM_OT_run_character_migration`.
Let me first read the file to find the OPERATOR_CLASSES list and then make the edit using Python.</details></think>
---
<tool-use data-tool-type="shell" data-tool-name="run_terminal_command_v2">
<details><summary>Tool use: **run_terminal_command_v2** • Run command: cd T:\Dynamic-Link-Manager && python -c "
import re
with open('ui/operators.py', 'r') as f:
content = f.read()
# Find and replace the specific pattern
old = ''' DLM_OT_relocate_single_library,
DLM_OT_run_character_migration,'''
new = ''' DLM_OT_relocate_single_library,
DLM_OT_migrator_remove_original,
DLM_OT_run_character_migration,'''
if old in content:
content = content.replace(old, new)
with open('ui/operators.py', 'w') as f:
f.write(content)
print('Successfully updated OPERATOR_CLASSES')
else:
print('Pattern not found - checking for alternative patterns')
# Show context around OPERATOR_CLASSES
match = re.search(r'OPERATOR_CLASSES.*?(?=\n\n|\Z)', content, re.DOTALL)
if match:
print('Found OPERATOR_CLASSES at:', match.start())
print('Content:', match.group()[:500])
"
</summary>
```bash
cd T:\Dynamic-Link-Manager && python -c "
import re
with open('ui/operators.py', 'r') as f:
content = f.read()
# Find and replace the specific pattern
old = ''' DLM_OT_relocate_single_library,
DLM_OT_run_character_migration,'''
new = ''' DLM_OT_relocate_single_library,
DLM_OT_migrator_remove_original,
DLM_OT_run_character_migration,'''
if old in content:
content = content.replace(old, new)
with open('ui/operators.py', 'w') as f:
f.write(content)
print('Successfully updated OPERATOR_CLASSES')
else:
print('Pattern not found - checking for alternative patterns')
# Show context around OPERATOR_CLASSES
match = re.search(r'OPERATOR_CLASSES.*?(?=\n\n|\Z)', content, re.DOTALL)
if match:
print('Found OPERATOR_CLASSES at:', match.start())
print('Content:', match.group()[:500])
"
```
```
Successfully updated OPERATOR_CLASSES
```
</details>
</tool-use>
---
+29
View File
@@ -495,6 +495,34 @@ MIGRATOR_STEP_OPS = (
)
class DLM_OT_migrator_remove_original(Operator):
bl_idname = "dlm.migrator_remove_original"
bl_label = "Remove Original"
bl_description = "Delete the original character armature and its data from the scene"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
orig, rep = _get_migrator_pair(context)
if not orig:
self.report({"WARNING"}, "No original character selected")
return {"CANCELLED"}
if orig == rep:
self.report({"ERROR"}, "Original and replacement cannot be the same object")
return {"CANCELLED"}
name = orig.name
try:
# Remove from scene
bpy.data.objects.remove(orig, do_unlink=True)
# Clear the property
context.scene.dynamic_link_manager.original_character = None
self.report({"INFO"}, f"Removed original character: {name}")
except Exception as e:
self.report({"ERROR"}, f"Failed to remove original: {e}")
return {"CANCELLED"}
return {"FINISHED"}
class DLM_OT_run_character_migration(Operator):
bl_idname = "dlm.run_character_migration"
bl_label = "Run Character Migration"
@@ -755,6 +783,7 @@ OPERATOR_CLASSES = [
DLM_OT_make_paths_relative,
DLM_OT_make_paths_absolute,
DLM_OT_relocate_single_library,
DLM_OT_migrator_remove_original,
DLM_OT_run_character_migration,
DLM_OT_picker_original_character,
DLM_OT_picker_replacement_character,
+1
View File
@@ -84,6 +84,7 @@ class DLM_PT_main_panel(Panel):
row.prop(props, "replacement_character", text="Replacement")
row.operator("dlm.picker_replacement_character", text="", icon="EYEDROPPER")
row = box.row()
row.operator("dlm.migrator_remove_original", text="Remove Original", icon="TRASH")
row.operator("dlm.run_character_migration", text="Run migration", icon="ARMATURE_DATA")
row = box.row(align=True)
row.operator("dlm.migrator_copy_attributes", text="CopyAttr", icon="COPY_ID")