# blender
Blender MCP-compatible modeling surface for Zero. `execute_blender_code` runs Python/bpy as Zero world content; no external Blender is used. Tool names and argument names mirror Blender MCP.
The Python is interpreted in Luau (`modeling/pythonParser`, `modeling/pythonRuntime`) — a subset, not CPython. The `bpy`/`bmesh`/`mathutils` surface is `modeling/blenderSession`.
## Tools
| Tool | Notes |
|---|---|
| `get_addon_status(user_prompt?)` | Implementation and compatibility version. |
| `bpy_api_lookup(query, user_prompt?)` | Returns the schema from `api.json`. Unknown names return `NotImplementedError` plus related supported names. |
| `execute_blender_code(code, user_prompt?, session?)` | Runs Python, then evaluates modifiers and writes meshes/materials/entities under `modeling/sessions/<session>`. Sessions are isolated; default `default`. |
| `get_scene_info(session?)` | Objects, locations, material count. |
| `get_object_info(object_name, session?)` | Dimensions, mesh counts, evaluated triangles, modifier stack, topology audit. |
| `get_viewport_screenshot(max_size?, session?, view?, perspective?)` | Renders the session's viewport state, which scripts drive through bpy (`bpy.ops.view3d.view_axis/view_all/view_selected/view_orbit/view_persportho`, or `region_3d.view_rotation/view_location/view_distance/view_perspective`). `view` (FRONT…BOTTOM, ISO) and `perspective` (PERSP/ORTHO) override it for one capture. Rendered by Zero, not a Blender viewport. |
## Coordinates
Scripts use Blender coordinates (Z-up, right-handed). Zero entities receive `(x, y, z) -> (x, z, -y)`.
## Supported API
Authoritative list: `api.json` (or `bpy_api_lookup`). Summary:
- **Primitives:** cube, plane, cylinder, cone, uv_sphere, ico_sphere, torus, circle, grid — with `location`, `rotation`, `scale`, `enter_editmode`. New primitives are fully selected, as in Blender.
- **Object ops:** `mode_set` (OBJECT/EDIT), `select_all`, `delete`, `transform_apply`, `shade_smooth/flat`, `modifier_apply`, `modifier_move_up`.
- **Edit ops:** `select_mode`, `select_all`, `extrude_region_move`, `inset` (region and individual, `depth`), `subdivide` (`number_cuts` 1–100), `loopcut_slide`, `bevel` (edges and vertices, segments), `bridge_edge_loops`, `spin`, `remove_doubles`, `object.material_slot_assign`.
- **Transform:** `translate`, `resize`, `rotate`. In EDIT mode the pivot defaults to the selection median; `center_override` sets it explicitly.
- **Data:** `bpy.data.meshes/objects/materials.new`, `objects.remove`, `Mesh.from_pydata`, `mesh.vertices/edges/polygons/materials`, `collection.objects.link`, `view_layer.objects.active`.
- **bmesh:** `new`, `from_edit_mesh`, `update_edit_mesh`, `from_mesh`, `to_mesh`, `verts.new`, `faces.new`, `edges` (`verts`, `link_faces`, `is_boundary`, `is_manifold`, `calc_length`, `select`), `calc_volume`, `ops.recalc_face_normals`.
- **Modifiers:** BEVEL (ANGLE/NONE limit), BOOLEAN (DIFFERENCE/UNION/INTERSECT), ARRAY (with merge), SUBSURF, MIRROR (with merge), SOLIDIFY (offset −1/0/1), SCREW, WELD, DISPLACE (no texture).
- **Curves and text:** `bpy.data.curves.new(name, 'CURVE'|'FONT')`, POLY, BEZIER and NURBS splines, `bevel_depth`, `extrude`, 2D fills with holes, `object.text_add`, `curve.primitive_bezier_curve_add/circle_add`, `object.convert(target='MESH')`. Text uses Zero fonts via `curve.zero_font` (default Arimo).
- **UVs:** `bpy.ops.uv.smart_project`, `mesh.uv_layers` (one layer). UVs are carried into rendered meshes and bundles.
- **Images and shader nodes:** `bpy.data.images.new/load/remove`, `Image.pixels` (incl. `[:]` assignment), `colorspace_settings.name`; node tree with Principled BSDF, Image Texture and Normal Map nodes; texture links to Base Color, Roughness, Metallic, Emission Color and Normal become Zero PBR texture slots (roughness/metallic packed).
- **Zero shaders as nodes:** `nt.nodes.new('ShaderNodeZeroShader')`, set `node.shader` to any Zero shader; its properties become inputs.
- **Knife project, texture DISPLACE, Closest interpolation, float images, multiple UV layers** are implemented (see api.json).
- **Hierarchy:** `Object.parent`, `parent_set`, `parent_clear`, `matrix_world` and friends, `object.join`, `mathutils.Matrix`.
- **Viewport:** `bpy.ops.view3d.*` above, `RegionView3D`, `SpaceView3D.lens`, `context.screen.areas`, `temp_override`.
- **Compare to reference:** `blender.compare_to_reference(reference, session)` scores the session's FRONT elevation against a reference sheet (silhouette IoU, per-band scores, overlay image).
- **Output (preferred):** `bpy.ops.export_scene.zero_bundle(filepath='/zero/source/<folder>/<Name>.bundle')` writes a native Zero bundle — meshes and materials inside it, one Model per material slot, spawnable with the `Asset` component. A material bound with `mat.zero_material = '<material identity>'` keeps its custom Zero shader. Exports are frozen snapshots: an existing path is refused unless `overwrite=True`, and each bundle carries `provenance.json`.
- **Interchange (optional):** `bpy.ops.export_scene.gltf(..., export_format='GLB')` exists for leaving Zero; nothing inside Zero needs it.
- **Materials:** Principled BSDF `Base Color`, `Metallic`, `Roughness`, `Alpha`, `IOR`; multiple slots with per-face `material_index`; `material_slots`, `active_material(_index)`; `zero_material` binds an existing Zero material asset.
## Rigging and animation
Armatures (`bpy.data.armatures`, `object.armature_add`, `edit_bones`, `pose.bones`), bone parenting (`parent_set(type='BONE')`), keyframes (`keyframe_insert` on objects and pose bones), Blender 5 slotted actions and F-Curves, and `scene.frame_set`. Bone matrices and F-Curve evaluation match Blender 5.2.1.
Actions are written into the document's `output.bundle` as animation clips; a placed `.zblend` plays them (SkinnedModel + ClipPlayer). Vertex groups, the ARMATURE modifier and automatic weights deform meshes; constraints (incl. IK), drivers and NLA are implemented and baked into the clips.
## Documents: `.zblend` (live)
`session` names a `.zblend` document, Zero's `.blend`: a bare name maps to `modeling/models/<name>.zblend`, or pass `"<folder>/<Name>"`. The document holds `scene.json` (objects, meshes, curves, modifier stacks, node graphs, images, UV layers, view), `history.json`, `images/`, and `output.bundle/`.
Every `execute_blender_code` updates the document and its `output.bundle` in place (only changed objects are rebuilt). A `.zblend` is scene-instantiable: drag it into a scene or call `asset.resolve(id, "zblend"):instantiate(...)`. Placed copies follow later edits. It has a preview and is indexed for search. `export_scene.zero_bundle` is only for a frozen snapshot; nothing needs an export step to use the model inside Zero.
Session previews live in the `ModelingWorkspace` additive scene layer (`ZeroBlender.package/workspace/ModelingWorkspace.scene`, loaded persistent on first use), so modeling never touches or saves into the scene open in the editor.
Old `modeling/sessions/<name>/` folders migrate automatically on first open.
## Known differences and limits
- Unsupported calls raise `NotImplementedError: …` or `AttributeError: …` naming the missing API. Nothing is silently ignored except `calc_uvs`.
- `subdivide` requires all faces selected; `number_cuts > 1` requires quads.
- `inset` expects planar selections; `extrude_region_move` requires FACE selection.
- BOOLEAN operands must be closed manifold solids.
- ARRAY merge, bevel `limit_method` WEIGHT/VGROUP, and adding primitives in EDIT mode are not implemented.
- SUBSURF: `get_object_info` reports the dimensions of the smoothed surface; Blender with GPU subdivision may report the cage size.
- Python: one number type (so `24.0` prints as `24`); no classes, generators, decorators or `*args`.
## Parity suite
`modeling/references/parity/cases.json` holds scripts with numbers captured from native Blender 5.2.1. Run `execute { file = "/zero/source/modeling/references/parity/run.luau" }`; currently 76/76 pass (subsurf counted as a documented difference).
## Not implemented yet
Scene arrangement is implemented: empties, duplicates and linked duplicates, collections, collection instances, `Object.zero_instance` (place another `.zblend`), `origin_set`, the 3D cursor and snapping.