| name | analysis |
| description | Triage and audit a .blend file โ orientation, most-referenced and orphaned datablocks, heaviest meshes/GP, name search, cross-file comparison. The 'where do I start' skill. |
| allowed-tools | ["Bash","Read","Glob","Grep"] |
When the prompt is "what's in this file?", "is anything broken?", "what's making this slow?", or "clean it up", start here. This skill composes the others โ it's mostly a recipe book.
1. Orient
SELECT * FROM welcome;
SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;
SELECT name, frame_start, frame_end, render_engine, camera, world FROM scenes;
SELECT type, COUNT(*) FROM objects GROUP BY type ORDER BY 2 DESC;
2. Find things by name โ grep
SELECT kind, COUNT(*) FROM grep WHERE pattern LIKE 'Probe%' GROUP BY kind;
SELECT name FROM grep WHERE pattern LIKE '%rig%' AND kind='object';
SELECT name FROM grep WHERE pattern='Probe%';
SELECT grep('Mat%', 20, 0);
grep covers every named datablock (objects, meshes, materials, lights, cameras, curves, armatures, images, actions, collections, โฆ). Use it to seed a deeper dive in a domain skill.
3. Reference counts โ what's load-bearing, what's dead
Most datablock tables expose users (the bpy user count). users = 0 โ orphaned (purged on save unless fake-user-flagged).
SELECT name, users FROM materials ORDER BY users DESC LIMIT 15;
SELECT name, users, polygon_count FROM meshes ORDER BY users DESC LIMIT 15;
SELECT 'mesh' AS kind, name FROM meshes WHERE users=0
UNION ALL SELECT 'material', name FROM materials WHERE users=0
UNION ALL SELECT 'image', name FROM images WHERE users=0
UNION ALL SELECT 'action', name FROM actions WHERE users=0
UNION ALL SELECT 'curve', name FROM curves WHERE users=0
UNION ALL SELECT 'armature', name FROM armatures WHERE users=0
UNION ALL SELECT 'light', name FROM lights WHERE users=0
UNION ALL SELECT 'camera', name FROM cameras WHERE users=0
UNION ALL SELECT 'sound', name FROM sounds WHERE users=0
UNION ALL SELECT 'text', name FROM texts WHERE users=0
UNION ALL SELECT 'gp', name FROM grease_pencils WHERE users=0
UNION ALL SELECT 'shape_key', name FROM shape_keys WHERE users=0
UNION ALL SELECT 'font', name FROM fonts WHERE users=0
UNION ALL SELECT 'world', name FROM worlds WHERE users=0
UNION ALL SELECT 'palette', name FROM palettes WHERE users=0
UNION ALL SELECT 'brush', name FROM brushes WHERE users=0
UNION ALL SELECT 'movieclip', name FROM movieclips WHERE users=0
UNION ALL SELECT 'cache', name FROM cache_files WHERE users=0
UNION ALL SELECT 'linestyle', name FROM linestyles WHERE users=0
UNION ALL SELECT 'mask', name FROM masks WHERE users=0
ORDER BY kind, name;
SELECT bpy_eval('[(c, b.name) for c in ("meshes","materials","actions","images","curves","armatures","texts","grease_pencils","shape_keys","fonts","worlds","palettes","linestyles","movieclips","sounds","node_groups") for b in getattr(bpy.data, c) if getattr(b, "use_fake_user", False) and b.users==1]');
SELECT name FROM materials WHERE name NOT IN (SELECT DISTINCT material FROM material_slots WHERE material IS NOT NULL);
SELECT m.name FROM meshes m LEFT JOIN objects o ON o.data=m.name AND o.type='MESH' WHERE o.name IS NULL;
WITH gp_slots AS (
SELECT ms.object, ms.slot_index, ms.material, o.data AS gp_data
FROM material_slots ms JOIN objects o ON o.name=ms.object WHERE o.type='GREASEPENCIL'
)
SELECT object, slot_index, COALESCE(material,'(empty)') AS material FROM gp_slots gs
WHERE (SELECT COUNT(*) FROM gp_strokes st WHERE st.gp=gs.gp_data AND st.material_index=gs.slot_index) = 0
ORDER BY object, slot_index;
Then clean up: SELECT purge_orphans(); removes the users=0 datablocks, and SELECT remove_unused_material_slots(); drops the dead slots (and remaps the geometry's material_index). Both report exactly what they removed and are undoable.
4. Weight / cost โ what makes the file big or slow
SELECT name, vertex_count, edge_count, polygon_count, loop_count FROM meshes ORDER BY polygon_count DESC LIMIT 20;
SELECT SUM(polygon_count) AS total_polys, SUM(vertex_count) AS total_verts FROM meshes;
SELECT gp, SUM(stroke_count) AS strokes FROM gp_frames GROUP BY gp ORDER BY strokes DESC;
SELECT gp, layer, SUM(point_count) AS points FROM gp_strokes GROUP BY gp, layer ORDER BY points DESC LIMIT 20;
SELECT object, name, json_extract(params_json,'$.levels') AS lv, json_extract(params_json,'$.render_levels') AS rlv
FROM modifiers WHERE type='SUBSURF' ORDER BY rlv DESC;
SELECT object, COUNT(*) AS mod_count, GROUP_CONCAT(type) AS types FROM modifiers GROUP BY object ORDER BY mod_count DESC LIMIT 15;
SELECT name, filepath, width, height, file_format, packed FROM images WHERE filepath<>'' ORDER BY width*height DESC LIMIT 15;
SELECT i.name, i.users, EXISTS(SELECT 1 FROM nodes n WHERE n.bl_idname='ShaderNodeTexImage') AS used_somewhere FROM images i;
5. Smell tests / broken-ness
SELECT name, type FROM objects WHERE type IN ('MESH','CURVE','LIGHT','CAMERA','ARMATURE') AND (data IS NULL OR data='');
SELECT owner_type, owner_name, name, type, target FROM constraints
WHERE target IS NOT NULL AND target NOT IN (SELECT name FROM objects);
SELECT action, data_path, array_index, is_valid, is_empty, keyframe_count FROM fcurves WHERE is_valid=0 OR is_empty=1;
SELECT owner_type, owner_id, data_path, expression, is_valid FROM drivers WHERE is_valid=0;
SELECT mesh, COUNT(*) AS loose_edges FROM mesh_edges WHERE is_loose=1 GROUP BY mesh HAVING loose_edges>0;
SELECT bpy_eval('[m.name for m in bpy.data.meshes if m.use_fake_user and m.users==1]');
SELECT op, success, error_type, input FROM session_log ORDER BY ts DESC LIMIT 20;
6. Cross-.blend comparison
If you have two sessions open (e.g. blendersql -s a.blend --http 8174 and blendersql -s b.blend --http 8175), run the same query against each and diff. Or within one session, load() a second file after recording the first's stats:
SELECT * FROM welcome;
SELECT type, COUNT(*) FROM objects GROUP BY type;
SELECT load('/projects/shot_b.blend');
SELECT * FROM welcome;
SELECT type, COUNT(*) FROM objects GROUP BY type;
(load discards the current in-memory state โ save first if you've made edits, e.g. SELECT save('');.)
Routing from here
Once triage points at a problem area, hand off:
- objects / hierarchy / transforms โ
scene
- grease pencil โ
grease_pencil
- mesh geometry โ
mesh
- materials / shaders / node trees โ
materials
- animation / keyframes / drivers โ
animation
- modifiers / constraints โ
modifiers
- video sequencer โ
vse
- images / sounds / curves / lights / cameras / armatures / shape keys / custom props / โฆ โ
assets
- arbitrary edits or operators โ
python
- function signatures โ
functions
Gotchas
users is the bpy user count, not a "is it visible" flag โ a mesh with users=1 linked to one object is normal; users=0 is the orphan signal. use_fake_user (probe via bpy_eval) keeps a 0-real-user block alive.
- Use the cheap GP aggregates (
gp_frames.stroke_count, gp_strokes.point_count) for counts โ don't COUNT(*) FROM gp_points on a real file.
- Constrain
mesh_* tables by mesh โ an unbounded scan of mesh_loops/mesh_uvs will dominate the query.
- "Is anything broken" beyond what's modeled (NLA, libraries/linked data, override hierarchies): probe with
bpy_eval/bpy_exec โ bpy.data.libraries, obj.override_library, etc.
- Cross-file comparison via
load() is destructive to the in-memory session โ save (SELECT save('')) before switching, or use two --http servers.