The user says the action slot is still not migrating in MigNLA when there's no NLA but there is an active action. They want debug added and pointed to the Action operators docs.
From the Action operators page, I see:
- `bpy.ops.action.push_down()` - "Push action down on to the NLA stack as a new strip"
- `bpy.ops.action.unlink()` - "Unlink this action from the active action slot (and/or exit Tweak Mode)"
So "action slot" in the UI might be referring to something that requires an operator to properly assign. Let me add debug to the no-NLA path so we can see:
1. What orig.animation_data has (action, action_slot, action_slot_handle, last_slot_identifier, action_blend_type, etc.)
2. What we're setting on rep
3. Whether action_slot assignment fails or is read-only
I'll add print statements (debug) in the migrator's no-NLA branch, and also consider if we need to use an operator like `bpy.ops.action.push_down` or something else. Actually re-reading the user message - they want to "migrate" the slot, i.e. copy the original's slot to the replacement. So the issue might be:
1. action_slot might be read-only when we assign action?
2. We might need to set last_slot_identifier or action_slot_handle instead?
3. Or we need to use an operator to "assign" the action in a way that sets the slot?
Let me add debug first so we can see what's on orig and what gets set on rep, and whether any assignment fails.
</details></think>
---
Adding debug in the MigNLA no-NLA path to log the original's and replacement's animation_data (action, action_slot, action_slot_handle, last_slot_identifier) and any assignment errors.
4. If we try to set action_slot and it fails, catch and print
I'll also check the API - maybe we need to set `last_slot_identifier` when assigning an action so the slot gets resolved? Or use the operator that "assigns" the action to the slot. Looking at the docs again, AnimData has:
- last_slot_identifier: "The identifier of the most recently assigned action slot..."
So when you assign an action, Blender might use last_slot_identifier to find which slot to use. So we might need to copy last_slot_identifier from orig to rep before or after setting the action.
1. _slot_debug() to print action and slot-related props for orig (before), rep (before), and rep (after).
2. Setting last_slot_identifier on rep before or after action (I did it after action; the API said "its identifier is used to find the right slot when assigning an Action" - so it might need to be set before assigning the action so that when we set rad.action, Blender knows which slot. Let me try setting last_slot_identifier *before* rad.action so the slot is selected first.
</details></think>
---
Setting `last_slot_identifier` before assigning the action so slot resolution runs when the action is set.
All good. Let me give a short summary to the user.
</details></think>
---
Summary of changes:
**1. Debug logging in the no-NLA path**
- **Orig (before):**`action` name and, when present: `action_slot`, `action_slot_handle`, `last_slot_identifier`, `action_blend_type`, `action_extrapolation`, `action_influence`.
- **Rep (before):** same set of properties.
- **Rep (after):** same set after we copy action and slot-related data.
- Logs when `last_slot_identifier` is set and when each of `action_blend_type`, `action_extrapolation`, `action_influence` is set.
- If assigning `rad.action_slot = ad.action_slot` raises, the exception is caught and printed.
**2. Slot handling order**
- `last_slot_identifier` is copied from orig to rep **before** setting `rad.action`, so when the action is assigned Blender can resolve the correct slot (see [AnimData](https://docs.blender.org/api/current/bpy.types.AnimData.html): *“its identifier is used to find the right slot when assigning an Action”*).
After reloading the add-on and running MigNLA (with no NLA, only an active action), check the Blender terminal or system console for lines starting with `[DLM MigNLA]`. From that you can see:
- Whether the original has an `action_slot` / `last_slot_identifier`,
- What gets set on the replacement,
- Any error when setting `action_slot`.
If the slot still doesn’t migrate, the next step is to try using [bpy.ops.action](https://docs.blender.org/api/current/bpy.ops.action.html) (e.g. a temp context override with the replacement armature selected and `action.push_down()` or the operator that assigns the action to the slot) so the UI’s “assign to slot” path runs. The new debug output will show whether the problem is missing/original data or a read-only or failing assignment.
---
_**User**_
Add a gitea workflow for this project in a way that mirrors my AMZNCharTools extension that we modeled the structure after.
# Blendfile local datablocks analysis (2026-02-19 18:53Z)
_**User**_
Use blender mcp to view my scene. What is taking up space in the blendfile? Only count local datablocks and local override data, linked libraries are not stored in the blendfile itself.
The user wants me to use the Blender MCP to view their scene and find out what's taking up space in the blend file. They specifically want to count only local datablocks and local override data - not linked libraries since those are stored elsewhere.
Let me first check what MCP tools are available from the user-blender server.
The user said "Use blender mcp" - the MCP servers were listed as user-blender and cursor-ide-browser. The instructions said "Browse the MCP tool descriptors in the file system" at C:\Users\Nathan\.cursor\projects\t-Dynamic-Link-Manager/mcps/<server>/tools/. Let me try with the path that was in the system message - maybe the folder is there but empty or has a different structure. Let me list what's in mcps.
<summary>Tool use: **web_search** • Searched web `undefined` • **1** results</summary>
| Title | URL | Chunk |
|-------|-----|-------|
| Web search results for query: Blender MCP server tools list datablocks scene | | Links:<br/>1. [Blender MCP Server: Headless Script Management](https://mcpmarket.com/server/blender)<br/>2. [Blender Script Manager – README \| MCP Marketplace](https://ubos.tech/mcp/blender-mcp-server/)<br/>3. [poly-mcp/Blender-MCP-Server](https://github.com/llm-use/Blender-MCP-Server)<br/>4. [Blender MCP Server - poly-mcp/blender-mcp-server](https://playbooks.com/mcp/llm-use-blender)<br/>5. [cwahlfeldt/blender-mcp](https://github.com/cwahlfeldt/blender-mcp)<br/><br/># Blender MCP Server Tools Overview<br/><br/>There are two main Blender MCP server implementations with different capability levels:<br/><br/>## Basic Script Manager (cwahlfeldt/blender-mcp)[2][5]<br/><br/>**Tools:**<br/>- `add_script(name, content)` - Add new scripts<br/>- `edit_script(name, content)` - Edit existing scripts<br/>- `execute_script(name, blend_file=None)` - Execute scripts in headless Blender<br/>- `remove_script(name)` - Remove scripts<br/><br/>**Resources:**<br/>- `scripts://list` - List available scripts<br/>- `script://\{name\}` - Access specific script content<br/>- `result://\{name\}` - Get execution results<br/><br/>## Advanced Complete Server (poly-mcp/Blender-MCP-Server)[3][4]<br/><br/>**50+ Tools across multiple categories:**<br/>- Object creation, manipulation, and transformation<br/>- Material and shader system management<br/>- Animation and keyframe control<br/>- Camera and lighting setup<br/>- Modifiers and constraints<br/>- Physics simulations (rigid body, cloth, fluid)<br/>- Geometry nodes and procedural generation<br/>- File import/export operations<br/>- Scene optimization and batch operations<br/><br/>**Additional Features:**[3][4]<br/>- Thread-safe execution queue for concurrent operations<br/>- Auto-dependency installation<br/>- Real-time monitoring and statistics<br/>- Production-ready error handling and logging<br/>- Simple UI panel in Blender's N-panel for server control<br/><br/>Both servers support working with datablocks and scene data through Blender's Python API (`bpy.data` objects).[2][5] |
The MCP tool descriptors were said to be in the file system at C:\Users\Nathan\.cursor\projects\t-Dynamic-Link-Manager/mcps/<server>/tools/. The server name is "user-blender". So the path would be C:\Users\Nathan\.cursor\projects\t-Dynamic-Link-Manager\mcps\user-blender. The glob returned 0 files - maybe the path uses forward slashes or the folder doesn't have the tool JSON files. Let me try to list resources and call a generic tool to see what's available. I'll use list_dir on the workspace to see the mcps folder.
</details></think>
---
Checking the MCP server structure and available tools:
The user-blender MCP has tools like import_generated_asset, generate_hunyuan3d_model, etc. - these seem to be asset-generation focused. I need to find if there's a tool to run Python in Blender or get scene/datablock info. Let me list all user-blender tools and check the server metadata.
There's `execute_blender_code.json`, `get_scene_info.json`, and `get_object_info.json`. Let me read those to see if I can get datablock counts and size info. get_scene_info might give scene overview; execute_blender_code would let me run custom Python to count local datablocks and override data.
</details></think>
---
Reading the schema for the tools that can inspect the scene and run code:
I'll use get_scene_info first to get an overview, then execute_blender_code to run Python that:
1. Iterates bpy.data and counts only local datablocks (where .library is None)
2. Counts local override data (overrides that are stored in this file)
3. Optionally reports approximate size or counts by type
In Blender:
- A datablock is "local" if it's not from a library: `id_data.library is None`
- Override data: `bpy.data.libraries` contains linked libraries; overrides are stored in the current file. I need to count override properties - in Blender 4.0+ we have `bpy.types.ID.override_create` and overrides live in the file. Actually "local override data" might mean the override hierarchy / override properties stored in the blend file for linked data. So:
- Local datablocks: all id_data where .library is None
- Local override data: could mean the override system's storage. In Blender, when you have a linked object and you override properties, that override info is stored locally. So we need datablocks that are in the override hierarchy (they might be virtual/local overrides).
Let me first get scene info to see what we're working with, then run code to count:
1. Per-type counts of datablocks where block.library is None (or getattr(block, 'library', None) is None for types that don't have library)
2. Override: in Blender, overrides are typically ID types that have .override_library set. So we could count override data as IDs that have override_library and are stored in this file (their override is local). Actually the user said "local override data" - meaning the override data that is stored in the blend file. So we want:
- All IDs that are local (library is None)
- Among those, some might be "override" type (they override a linked datablock) - the override hierarchy and property overrides are stored locally
I'll write Python that:
1. For each bpy.data collection (objects, meshes, materials, etc.), count items where .library is None
2. For override: count IDs that have override_library (they represent local override data)
Let me get scene info first, then run the counting code.
</details></think>
---
Fetching scene info, then running Blender Python to count only local datablocks and local override data.
Scene has 3104 objects and 145 materials. Now I need to run execute_blender_code to count local vs linked datablocks and get override counts. I'll write Python that:
1. Iterates all bpy.data.* collections
2. For each datablock, check if it's local (library is None)
3. Count overrides (id_data.override_library is not None means it's an override - but overrides can be on linked data too; the override *data* is stored locally). So: local datablocks = library is None. For "local override data" - in Blender, when you override a linked ID, the override is stored in the current file. So we want IDs that have override_library - those are the ones that have local override data (the override hierarchy). Let me count both.
</details></think>
---
Running Python in Blender to count local datablocks and local override data:
"parameters":"{\"user_prompt\":\"Count local datablocks and override data in blend file\",\"code\":\"import bpy\\nimport collections\\n\\n# Count local datablocks only (library is None)\\nlocal_counts = collections.defaultdict(int)\\noverride_count = 0\\noverride_by_type = collections.defaultdict(int)\\n\\nfor attr in dir(bpy.data):\\n if attr.startswith('_'):\\n continue\\n coll = getattr(bpy.data, attr, None)\\n if not hasattr(coll, 'values'):\\n continue\\n try:\\n for block in coll:\\n if getattr(block, 'library', None) is not None:\\n continue # skip linked\\n local_counts[attr] += 1\\n if getattr(block, 'override_library', None) and block.override_library:\\n override_count += 1\\n override_by_type[attr] += 1\\n except (TypeError, AttributeError):\\n pass\\n\\nlines = ['=== Local datablocks (library is None) ===']\\nfor k in sorted(local_counts.keys()):\\n lines.append(f'{k}: {local_counts[k]}')\\nlines.append('')\\nlines.append(f'Total local datablocks: {sum(local_counts.values())}')\\nlines.append('')\\nlines.append('=== Local override data (IDs with override_library) ===')\\nfor k in sorted(override_by_type.keys()):\\n lines.append(f'{k}: {override_by_type[k]}')\\nlines.append(f'Total with overrides: {override_count}')\\nprint('\\\\n'.join(lines))\"}",
"serverName":"blender"
}
]
}
```
Additional data:
```json
{
"status":"success"
}
```
Result:
```json
{
"result":"{\"content\":[{\"type\":\"text\",\"text\":\"Code executed successfully: === Local datablocks (library is None) ===\\nactions: 4\\narmatures: 3\\nbrushes: 44\\ncameras: 1\\ncollections: 33\\nfonts: 1\\nimages: 9\\nlibraries: 6\\nlights: 108\\nlinestyles: 1\\nmaterials: 21\\nmeshes: 19\\nnode_groups: 8\\nobjects: 3092\\nscenes: 1\\nscreens: 18\\nshape_keys: 6\\nwindow_managers: 1\\nworkspaces: 12\\nworlds: 1\\n\\nTotal local datablocks: 3389\\n\\n=== Local override data (IDs with override_library) ===\\narmatures: 3\\ncollections: 23\\nlights: 104\\nmaterials: 1\\nmeshes: 7\\nnode_groups: 1\\nobjects: 3072\\nTotal with overrides: 3211\\n\"}]}"
- 3211 of those (or overlapping) have override_library - meaning they're overriding linked data, so the override data is stored locally
The biggest contributors to local storage:
- objects: 3092 local (and 3072 have overrides)
- lights: 108 local (104 with overrides)
- brushes: 44
- collections: 33 (23 with overrides)
- materials: 21
- meshes: 19
- screens: 18
- workspaces: 12
- images: 9
- node_groups: 8
- etc.
Let me also get a sense of what might be taking the most *space* - e.g. mesh data (verts/faces), image size, etc. I'll run another snippet to summarize size-heavy items.
</details></think>
---
Gathering what's using the most space (mesh geometry, images, etc.):
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.