About blender
blender is a skill for controlling a running Blender session through Blender Bridge and Python. It is intended for creators automating scene construction, animation, and rendering, with scene inspection and visual or geometry checks built into the workflow.
Key features
- 01Run Python inside Blender
- 02Inspect scene objects and settings
- 03Build re-runnable scene scripts
- 04Create viewport snapshots and renders
- 05Verify geometry with metrics
Use cases
- 01Script models, materials, and cameras
- 02Create keyframed or procedural animations
- 03Inspect geometry and iterate on a scene
- 04Render images and export scene files
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ jablonkai/ skills --skill blenderAfter installing, type/blenderin Claude Code to confirm it loaded.
Bundled files
References and scripts installed along with SKILL.md.
references/animation-rigging.mdapi-reference.mdgeometry-nodes.mdgotchas.mdgrease-pencil.mdio-formats.mdmodeling.mdrendering.mdshading.mdsimulation.mdvse.mdscripts/blender-bridge.pyblender-send.shexample-animated-logo.pyexample-product-shot.pyDetails
From the project READMEBlender Control
Blender (/Applications/Blender.app) is scriptable in Python against bpy, bmesh and
mathutils. Drive it through the Blender Bridge — a script running inside a running
Blender GUI that executes whatever build script is POSTed to 127.0.0.1:8736. Because it
runs in the live session on the main thread, bpy.data writes are legal, the viewport
updates as you build, and viewport screenshots work. Everything documented here was executed
and verified on Blender 5.2.2 LTS (bundled Python 3.13) — the current stable release;
5.2 is the LTS line, so this holds for every 5.2.x. Check version in the ping reply: on
a newer release, trust the live session over these notes and probe before assuming.
- scripts/blender-bridge.py — the bridge to run inside Blender.
- scripts/blender-send.sh — send a
.pyfile (or-c 'inline') and print its captured output;--pingchecks it's up,--statedumps the scene as JSON. - scripts/example-product-shot.py — model → material → three-point light → camera → measure → render.
- scripts/example-animated-logo.py — text → keyframes → geometry-nodes scatter → frame sequence → video.
Read references/gotchas.md before writing anything. Blender's
API changed substantially in 5.x and most of what a model has memorised — action.fcurves,
scene.node_tree, mod["Socket_2"], BLENDER_EEVEE_NEXT, use_nodes = True — is now wrong
or deprecated.
The control loop
-
User starts the bridge (one-time). This cannot be done remotely — if
bash scripts/blender-send.sh --pinggets no answer, ask the user to do one of:- copy scripts/blender-bridge.py to
~/Library/Application Support/Blender/5.2/scripts/startup/blender_bridge.py(the folder is the runningmajor.minorversion) and restart Blender — it then starts automatically on every launch (recommended), or - open the Scripting workspace, paste the file into the text editor, press Run Script (⌥P) — lasts for that session, or
- install it as an add-on via Preferences ▸ Add-ons ▸ Install.
A successful ping returns
{"ok": true, "bridge": "blender", "bridge_version": "1.2.0", "version": "5.2.2 LTS", ...}. An olderbridge_versionmeans the user's installed copy is stale — ask them to re-copy the file (older copies crashmetrics()on cameras and empties). - copy scripts/blender-bridge.py to
-
Look before you build:
bash scripts/blender-send.sh --statereturns objects and types, collections, materials, node groups, frame range, resolution, engine and whether the file has unsaved changes — cheaper than writing a script to ask. -
Write a build script to the scratchpad, starting from the cheatsheet below and the right reference file.
-
Send it:
OUT=/path/to/outdir bash scripts/blender-send.sh /path/build.py. The bridge runs it in the live session and returns the script's captured stdout; on error it returns the traceback and the sender exits non-zero.BLENDER_SEND_TIMEOUT=900(seconds) for heavy renders and bakes. The script runs inside Blender, so your shell's environment does not reach it — parameterise a send with--arg quality=final --arg samples=128, read back asARGS.get("quality", "preview")(values are strings). -
Feedback: returned stdout first;
metrics(...)for structured geometry checks;snapshot(...)for a viewport PNG to Read;render(...)for the real thing. -
Iterate: inspect, fix, re-send. Scripts must be re-runnable — build inside
stage("name")so a re-send replaces its own output instead of stacking duplicates.
Reference routing
| Task | Read |
|---|---|
| Anything, before you start | gotchas.md |
| Data-blocks, transforms, collections, depsgraph, scenes | api-reference.md |
| bmesh, modifiers, curves, text, UVs, booleans | modeling.md |
| Materials, shader nodes, textures, world/HDRI, lights, cameras | shading.md |
| Procedural geometry, scattering, instancing, fields, zones | geometry-nodes.md |
| Keyframes, F-curves, drivers, NLA, armatures, IK, shape keys | animation-rigging.md |
| Rigid body, cloth, soft body, particles, hair, fluid, bakes | simulation.md |
| EEVEE/Cycles settings, passes, compositor, output, video | rendering.md |
| 2D / toon linework, Line Art | grease-pencil.md |
| Editing clips, titles, transitions, final cut | vse.md |
| glTF, FBX, USD, Alembic, OBJ, STL, .blend append/link | io-formats.md |
Injected namespace
Pre-imported: bpy, bmesh, mathutils, Vector, Matrix, Euler, Quaternion,
math, os, json, plus OUT (from $OUT, also os.environ["OUT"]), ARGS (the
--arg KEY=VALUE strings as a dict) and these helpers:
| Helper | Does |
|---|---|
stage(name) | get-or-recreate a named collection, make it active — makes re-sends idempotent (also frees the meshes, materials and node groups its old objects leave orphaned) |
sync() | view_layer.update(); required before reading matrix_world |
evaluated(ob) | the depsgraph-evaluated object (modifier / geometry-nodes result) |
frame(n) | frame_set(n) + depsgraph update |
metrics(objs, path=) | dict + JSON: counts, verts/tris, world bbox, materials, frame range |
snapshot(path, view=, shading=, fit=) | viewport PNG (fast visual check) |
render(path, engine=, samples=, ...) | real EEVEE/Cycles still; restores every setting it touches |
frame_view(objs, view=) | aim the viewport (ISO/FRONT/TOP/CAMERA/…) |
world_bounds(objs) | world-space (min, max) |
fcurves(ob) / fcurve(ob, path, i) / channelbag(ob) | slotted-action F-curve access |
gn_input(mod, name, value=, attribute=) | read/set a geometry-nodes modifier input by name |
ui_override(area) | context override for the few bpy.ops that need an editor |
Cheatsheet
Units are metres and radians. Prefer the data API; bpy.ops is for mode changes,
smart_project, rigidbody.*, nla.bake, and importers/exporters.
Build geometry (re-runnable)
coll = stage("hero_v1") # owns its own output; safe to re-send
me = bpy.data.meshes.new("Body")
bm = bmesh.new()
bmesh.ops.create_cube(bm, size=2.0)
bmesh.ops.bevel(bm, geom=list(bm.edges), offset=0.06, segments=3, affect="EDGES")
bm.to_mesh(me); bm.free()
ob = bpy.data.objects.new("Body", me)
coll.objects.link(ob)
ob.location = (0, 0, 1)
sync() # before ANY matrix_world read
Material
m = bpy.data.materials.new("Shell")
b = m.node_tree.nodes["Principled BSDF"]
b.inputs["Base Color"].default_value = (0.75, 0.2, 0.15, 1.0)
b.inputs["Roughness"].default_value = 0.35
b.inputs["Metallic"].default_value = 0.0 # no "Specular"/"Emission" inputs since 4.0
ob.data.materials.append(m)
Camera aimed at the subject
cd = bpy.data.cameras.new("Cam"); cd.lens = 50
cam = bpy.data.objects.new("Cam", cd); coll.objects.link(cam)
cam.location = (6, -6, 4)
cam.rotation_euler = (Vector((0, 0, 1)) - cam.location).to_track_quat("-Z", "Y").to_euler()
bpy.context.scene.camera = cam
Feedback
m = metrics([ob], path=OUT + "/metrics.json")
print("METRICS", json.dumps(m)) # comes straight back through the sender
print(snapshot(OUT + "/check.png", view="ISO"))
print(render(OUT + "/hero.png", engine="BLENDER_EEVEE", samples=64,
width=1280, height=720))
Keyframes
for f, z in ((1, 0.0), (24, 3.0), (48, 0.0)):
ob.location.z = z
ob.keyframe_insert("location", index=2, frame=f)
for kp in fcurves(ob)[0].keyframe_points: # NOT action.fcurves — that is gone
kp.interpolation = "BEZIER"
kp.easing = "EASE_IN_OUT"
Export
bpy.context.view_layer.objects.active = ob; ob.select_set(True)
bpy.ops.export_scene.gltf(filepath=OUT + "/hero.glb", export_format="GLB",
use_selection=True)
bpy.ops.wm.obj_export(filepath=OUT + "/hero.obj", export_selected_objects=True)
bpy.ops.wm.save_as_mainfile(filepath=OUT + "/hero.blend", copy=True)
Verification
- Returned stdout is the immediate signal —
print(...)comes straight back. metrics(...)is the structured check (object/vert/tri counts, world bbox, materials). Write it to$OUT/metrics.jsonand Read it to confirm geometry without eyeballing.snapshot(...)is the fast visual check — a viewport OpenGL PNG, no full render.shading="RENDERED"previews materials and lights.render(...)for the deliverable. It raises a diagnosticRuntimeErrorif Blender reported success but wrote nothing. It restores every setting it touched afterwards — so when the user also wants a.blendthat re-renders the same shot (F12), set resolution, engine and samples on the scene yourself before saving.- Hand-off: a
.glb/.blendin$OUTopens in whatever the user already has.
Security
Installing this skill means running a code-execution server on the user's machine. Say so before asking them to start the bridge.
- The bridge binds
127.0.0.1:8736(BLENDER_BRIDGE_PORT) and executes any Python POSTed to/runinside the live session — the user's privileges, the user's open file. Requests carry no authentication: every local process, and every other user on a shared machine, can drive Blender through it. - Web pages cannot. Requests carrying an
Originheader or a cross-siteSec-Fetch-Siteare rejected with 403, so a page in the user's browser can't reach the bridge. That check is the only gate — there is no token. - The install choice decides how long the port stays open. Scripting workspace ▸ Run
Script lasts until Blender quits. The
scripts/startup/install is recommended above for convenience, but it makes every Blender launch listen, whether or not an agent is driving it — offer the trade-off rather than assuming it. - To stop the bridge, quit Blender. There is no remote shutdown; the port is released with the process.
Safety
- The open file may hold unsaved work. Never call
bpy.ops.wm.read_homefile()orbpy.data.batch_removewithout asking.stage()is the non-destructive default; it isolates geometry but not scene-level settings. - Renders, bakes and sims block the main thread — the UI freezes until they finish. Iterate small, and warn the user before anything long.
- Save into
$OUT, not over the user's.blend;save_as_mainfile(..., copy=True)leaves the live session pointed at their own file.