Blender Python execution
Use the provider's Blender tools as a transaction loop: inspect, execute one
bounded change, inspect structured state, then inspect pixels when the result is
visual. This skill owns execution discipline, not modeling or look-development
decisions.
Discover the adapter
The known adapter exposes these schemas:
get_scene_info()
execute_blender_code(code)
get_object_info(object_name)
get_viewport_screenshot(max_size=800)
Tool prefixes and provider aliases are discoverable and may differ. Match tools
by schema and behavior; do not guess an endpoint or port. Read
references/mcp-call-contract.md before the first mutation.
Run the transaction loop
- Call
get_scene_info() before executing code. Identify the current file,
scene, mode, active object, camera, engine, and objects in scope. Preserve
existing content unless the user explicitly asks to replace it.
- Run the read-only code in
scripts/scene_probe.py through
execute_blender_code(code) when the provider's scene summary is incomplete.
Use scripts/scene_audit.py before rendering, export, or handoff. To prove
camera coverage, prepend
BLENDER_AUDIT_REQUEST = {"targets": ["ROOT-subject"], "margin": 0.08, "include_descendants": True, "include_instances": True}
when executing through MCP, or pass --targets GEO-subject --margin 0.08
after Blender's -- delimiter; use --no-descendants or --no-instances
only when those bounds are intentionally excluded. A camera merely existing is not a framing
pass.
- Define one transaction with named inputs, stable target names, a narrow
mutation, and an expected postcondition. Prefer Blender's data API. Use an
operator only when its context is prepared explicitly.
- Send a small, self-contained code string to
execute_blender_code(code). Every call has a fresh Python namespace:
re-import modules and reacquire datablocks from bpy.data by name.
- End the code with one JSON object on standard output. Include
ok,
operation, target names, changed values or counts, and any warnings. Do not
print credentials, environment variables, or unrelated paths.
- Call
get_object_info(object_name) for every primary target and
get_scene_info() after scene-wide changes. Check the expected state rather
than treating a successful tool response as proof.
- For visible changes, call
get_viewport_screenshot(max_size=800) or inspect
the requested render. Verify framing, geometry, materials, and lighting. A
screenshot can be stale or produced by another engine; it does not by itself
prove that OVRTX rendered or accepted an edit.
Execution rules
- Never use
time.sleep() or a polling loop inside Blender code. Return control
to the agent and poll with provider/status tools outside Blender.
- Keep each call bounded. Split creation, materials, lighting, rendering, and
export into independently verifiable transactions.
- Use stable names and explicit ownership. Do not rely on selection order,
default names, or Python variables from a previous call.
- Make mutations idempotent: get-or-create owned datablocks, set requested
values absolutely, and avoid duplicate modifiers, links, handlers, or nodes.
- Call
bpy.context.view_layer.update() before reading evaluated transforms,
bounds, or dependency-graph results.
- Prefer direct data access. When an operator is necessary, set mode, selection,
and active object explicitly or use
bpy.context.temp_override(...) with a
verified area/region. Restore user-visible context when practical.
- Do not delete broadly, reset the scene or World, open another file, save over
the source, enable scripts, install add-ons, or render to an arbitrary path
unless the user requested that exact action.
- Treat external
.blend, USD, archives, scripts, drivers, and add-ons as
untrusted content. Opening or enabling them is outside this execution skill.
Mutation shape
Use the patterns in references/python-transaction-patterns.md. A minimal
transaction has this form:
import bpy, json
name = "GEO-requested-target"
obj = bpy.data.objects.get(name)
if obj is None:
raise RuntimeError(f"missing target: {name}")
obj.hide_render = False
bpy.context.view_layer.update()
print(json.dumps({
"ok": True,
"operation": "set_render_visibility",
"object": obj.name,
"hide_render": obj.hide_render,
}, sort_keys=True))
Failure handling
- On a traceback, report the exception and failing line, reduce the transaction,
reacquire state, and retry only the failed operation.
- On timeout, do not resend the same large script. Inspect the scene, split the
work, and remove blocking waits or expensive loops.
- If structured state changed but pixels did not, check camera, view layer,
visibility, dependency-graph evaluation, active engine, and stale viewport
state before repeating the mutation. Use the interactive redraw/frame pattern
in
references/python-transaction-patterns.md, return control, then capture a
new screenshot; use a render when background mode or freshness matters.
- For reproducible render-camera coverage, route to
blender-camera-framing.
Viewport view_selected changes only RegionView3D and cannot prove that a
render camera contains the subject.
- If the provider lacks code execution, do not use shell Blender as a hidden
substitute for mutating the user's live session. Stop and report the missing
capability. A background Blender script is allowed only when the user
explicitly authorizes an offline/caller-owned derivative or the routed
workflow already declares background execution for a large build, frame
loop, render, or round-trip test; keep it isolated from the live source.
Completion gate
Report success only when the requested postcondition passes structured
inspection and every visual claim has current visual evidence. State the active
engine and label screenshots, Blender renders, native renderer products, and
postprocessed images separately.
1---2name: blender-python-execution3description: Execute and validate Blender Python through a Blender MCP provider using small, context-safe, idempotent transactions. Use when an agent must inspect or mutate a Blender scene, translate a Blender task into bpy code, recover from Blender code execution errors, or prove that an MCP-driven edit actually changed the intended scene.4---56# Blender Python execution78Use the provider's Blender tools as a transaction loop: inspect, execute one9bounded change, inspect structured state, then inspect pixels when the result is10visual. This skill owns execution discipline, not modeling or look-development11decisions.1213## Discover the adapter1415The known adapter exposes these schemas:1617```text18get_scene_info()19execute_blender_code(code)20get_object_info(object_name)21get_viewport_screenshot(max_size=800)22```2324Tool prefixes and provider aliases are discoverable and may differ. Match tools25by schema and behavior; do not guess an endpoint or port. Read26`references/mcp-call-contract.md` before the first mutation.2728## Run the transaction loop29301. Call `get_scene_info()` before executing code. Identify the current file,31 scene, mode, active object, camera, engine, and objects in scope. Preserve32 existing content unless the user explicitly asks to replace it.332. Run the read-only code in `scripts/scene_probe.py` through34 `execute_blender_code(code)` when the provider's scene summary is incomplete.35 Use `scripts/scene_audit.py` before rendering, export, or handoff. To prove36 camera coverage, prepend37 `BLENDER_AUDIT_REQUEST = {"targets": ["ROOT-subject"], "margin": 0.08,38 "include_descendants": True, "include_instances": True}`39 when executing through MCP, or pass `--targets GEO-subject --margin 0.08`40 after Blender's `--` delimiter; use `--no-descendants` or `--no-instances`41 only when those bounds are intentionally excluded. A camera merely existing is not a framing42 pass.433. Define one transaction with named inputs, stable target names, a narrow44 mutation, and an expected postcondition. Prefer Blender's data API. Use an45 operator only when its context is prepared explicitly.464. Send a small, self-contained code string to47 `execute_blender_code(code)`. Every call has a fresh Python namespace:48 re-import modules and reacquire datablocks from `bpy.data` by name.495. End the code with one JSON object on standard output. Include `ok`,50 `operation`, target names, changed values or counts, and any warnings. Do not51 print credentials, environment variables, or unrelated paths.526. Call `get_object_info(object_name)` for every primary target and53 `get_scene_info()` after scene-wide changes. Check the expected state rather54 than treating a successful tool response as proof.557. For visible changes, call `get_viewport_screenshot(max_size=800)` or inspect56 the requested render. Verify framing, geometry, materials, and lighting. A57 screenshot can be stale or produced by another engine; it does not by itself58 prove that OVRTX rendered or accepted an edit.5960## Execution rules6162- Never use `time.sleep()` or a polling loop inside Blender code. Return control63 to the agent and poll with provider/status tools outside Blender.64- Keep each call bounded. Split creation, materials, lighting, rendering, and65 export into independently verifiable transactions.66- Use stable names and explicit ownership. Do not rely on selection order,67 default names, or Python variables from a previous call.68- Make mutations idempotent: get-or-create owned datablocks, set requested69 values absolutely, and avoid duplicate modifiers, links, handlers, or nodes.70- Call `bpy.context.view_layer.update()` before reading evaluated transforms,71 bounds, or dependency-graph results.72- Prefer direct data access. When an operator is necessary, set mode, selection,73 and active object explicitly or use `bpy.context.temp_override(...)` with a74 verified area/region. Restore user-visible context when practical.75- Do not delete broadly, reset the scene or World, open another file, save over76 the source, enable scripts, install add-ons, or render to an arbitrary path77 unless the user requested that exact action.78- Treat external `.blend`, USD, archives, scripts, drivers, and add-ons as79 untrusted content. Opening or enabling them is outside this execution skill.8081## Mutation shape8283Use the patterns in `references/python-transaction-patterns.md`. A minimal84transaction has this form:8586```python87import bpy, json8889name = "GEO-requested-target"90obj = bpy.data.objects.get(name)91if obj is None:92 raise RuntimeError(f"missing target: {name}")9394obj.hide_render = False95bpy.context.view_layer.update()96print(json.dumps({97 "ok": True,98 "operation": "set_render_visibility",99 "object": obj.name,100 "hide_render": obj.hide_render,101}, sort_keys=True))102```103104## Failure handling105106- On a traceback, report the exception and failing line, reduce the transaction,107 reacquire state, and retry only the failed operation.108- On timeout, do not resend the same large script. Inspect the scene, split the109 work, and remove blocking waits or expensive loops.110- If structured state changed but pixels did not, check camera, view layer,111 visibility, dependency-graph evaluation, active engine, and stale viewport112 state before repeating the mutation. Use the interactive redraw/frame pattern113 in `references/python-transaction-patterns.md`, return control, then capture a114 new screenshot; use a render when background mode or freshness matters.115- For reproducible render-camera coverage, route to `blender-camera-framing`.116 Viewport `view_selected` changes only `RegionView3D` and cannot prove that a117 render camera contains the subject.118- If the provider lacks code execution, do not use shell Blender as a hidden119 substitute for mutating the user's live session. Stop and report the missing120 capability. A background Blender script is allowed only when the user121 explicitly authorizes an offline/caller-owned derivative or the routed122 workflow already declares background execution for a large build, frame123 loop, render, or round-trip test; keep it isolated from the live source.124125## Completion gate126127Report success only when the requested postcondition passes structured128inspection and every visual claim has current visual evidence. State the active129engine and label screenshots, Blender renders, native renderer products, and130postprocessed images separately.