The work, first
Two briefs in the studio archive need three dimensions. Both start from a photograph, not from an idea about software.
The Infinite Chai. A barrel room shot on the BMPCC with the IRIX 15, then rebuilt in Blender: the envelope, one barrique modelled properly, the rest instanced. Then the room is extended past its own architecture (a hundred columns deep instead of the thirty the building has) with the light held realistic across the whole distance. Gursky for the extension, Kubrick for the one-point perspective, Sugimoto for the framing.
Vine Geometry. One post, four sagging wires, one vine stock. Geometry Nodes arrays it into a vineyard and a single leaf-density parameter runs the twelve-month cycle: bare wire, budbreak, full canopy, véraison, stripped. Rendered from the same elevated position the 907X shot from, so the frames can be compared directly. Agnes Martin for the grid, the Bechers for the typology.
Both are built for the same wall. Renders hang beside real 907X frames, same size, same frames: 80 × 80 or 100 × 80 in Caisse Américaine, panoramas at 120 × 67 on Alu Dibond brossé, and nothing on the wall says which is which. If a visitor can sort them, the piece has failed at the only thing it is trying to do.
That constraint is the whole reason the technical detail below matters. A render that is merely beautiful fails. It has to be indistinguishable, and indistinguishable is a measurement.
Not another text-to-3D generator
The demos that circulate are missing the middle. You see the prompt and the finished render, never the broken geometry, the missing texture, the camera pointed at a wall, the fifteen attempts. That work still happens. What changes is who does it.
Kimi K3 does not hand back a mesh you cannot edit. Through Kimi Code it connects to an MCP server and uses the tools that server exposes. For Blender, the server most people run is ahujasid/blender-mcp: an add-on opens a socket inside a running Blender, the server talks JSON over TCP to it, and the model drives the session already on screen.
It is worth being precise about how thin that server is, because the write-ups usually are not. There is no dedicated tool for cameras, or lights, or materials. There are four tools.
get_scene_info
Reads the scene back: objects, names, the structure the agent has to keep straight.
get_object_info
One object in detail: transform, mesh data, materials.
get_viewport_screenshot
A PNG of the viewport. This is the tool that makes the loop possible.
execute_blender_code
Arbitrary Python inside Blender. No sandbox. Everything else is built out of this one.
So the honest description is not “an AI that operates Blender”. It is a model that writes Python, runs it inside Blender, photographs the result, and writes the next Python. That is a smaller claim and a more useful one, because Python is the whole of Blender and a screenshot is a real observation.
Why this model
Blender is an awkward room for a language model. It has to hold three-dimensional space, keep twelve hundred object names straight, judge a composition, read a picture of its own work, and remember a decision it made forty steps ago.
K3 is built for that shape of job. It landed in July 2026 as an open-weight model: 2.8 trillion parameters with 104 billion active per token, native vision over text, images and video, and a million-token context window. Moonshot's own name for the workflow is “vision in the loop”: alternating between writing code and looking at live screenshots.
That is the part worth paying for. A model that builds a scene and forgets every decision is an asset generator with extra steps. A model that keeps the scene, looks at the render, names what is wrong and changes only that can be given a note. The practice already runs on notes.
The camera is the constraint
This is where most previs is wasted. If the virtual camera does not see what the real camera sees, the render is a picture of a room and not a shot list. Sensor dimensions and focal length are not approximations; Blender computes perspective and bokeh from them, and both have to be entered by hand.
Hasselblad 907X CFV 100C
- Sensor
- 43.8 × 32.9 mm back
- Glass
- CF 40 mm at f/4
- Frame
- 11656 × 8742
The still camera. Medium format at 40 mm sits slightly wider than a 50 on full frame, and the depth-of-field signature is nothing like it. Blender computes the bokeh correctly only if the sensor and the focal length are both right.
BMPCC 6K Pro
- Sensor
- 23.10 × 12.99 mm, Super 35
- Glass
- IRIX Cine 15 / 45 / 150 mm, the 45 at T1.5
- Frame
- 6144 × 3456
Three saved camera configurations, not one with a slider. The 15 for the aisle, the 45 for coverage, the 150 for the stave and the bung. T-stops read close enough to f/1.2-f/1.4 for a previs.
The slider gets the same treatment. The edelkrone SlideMaster becomes a straight curve with the camera parented to it and the easing shaped in the graph editor, because a real motorised head accelerates and a linear keyframe does not. Three seconds left to right, tested before the tripod comes out of the car.
The blockout
Here is roughly what the first script does. It is not good, and it is not supposed to be. It is a starting point specific enough to criticise, which is the one thing a blank scene never gives you.
import bpy
from math import radians
# ---- Rule 01. Everything lands in one collection. Nothing outside it is touched,
# and this script does not open with a select-all delete.
view_layer = bpy.context.view_layer
target = bpy.data.collections.get('PREVIS_CHAI') or bpy.data.collections.new('PREVIS_CHAI')
if target.name not in bpy.context.scene.collection.children:
bpy.context.scene.collection.children.link(target)
view_layer.active_layer_collection = view_layer.layer_collection.children[target.name]
# ---- Rule 05. Measured, not guessed.
# A barrique bordelaise is 225 L: 95 cm stave, 69 cm at the bilge. (The 1.2 m x 0.9 m
# figure in the knowledge base is a tonneau. Wrong barrel, wrong room.)
STAVE_L, BILGE_D = 0.95, 0.69
TIER_H = 0.78 # barrel plus rack rail
COL_PITCH = 1.05 # centre to centre down the aisle
BANK_X = (-3.4, -2.3, 2.3, 3.4) # two banks either side, 4.6 m aisle between
TIERS, COLUMNS = 3, 100 # 4 x 3 x 100 = 1200 barriques
oak = bpy.data.materials.new('OakBarrique')
oak.use_nodes = True
oak_bsdf = oak.node_tree.nodes['Principled BSDF']
oak_bsdf.inputs['Base Color'].default_value = (0.14, 0.075, 0.038, 1.0)
oak_bsdf.inputs['Roughness'].default_value = 0.62
# One barrel, lying down, axis across the aisle. Every other barrel shares this mesh:
# 1200 linked duplicates keep the viewport interactive. Geometry Nodes is the
# production answer; this is a blockout.
bpy.ops.mesh.primitive_cylinder_add(vertices=48, radius=BILGE_D / 2, depth=STAVE_L)
master = bpy.context.object
master.name = 'Barrique_MASTER'
master.rotation_euler = (0.0, radians(90), 0.0)
master.location = (0.0, -6.0, 0.5) # parked behind the camera, out of frame
master.data.materials.append(oak)
for bank, x in enumerate(BANK_X):
for tier in range(TIERS):
for col in range(COLUMNS):
dup = bpy.data.objects.new(f'Barrique_{bank}_{tier}_{col}', master.data)
dup.rotation_euler = master.rotation_euler
dup.location = (x, 1.0 + col * COL_PITCH, 0.55 + tier * TIER_H)
target.objects.link(dup)
# ---- Chai Cathedral light. One source, 3200 K, no fill. The blacks stay black.
bpy.ops.object.light_add(type='AREA', location=(0.0, COLUMNS * COL_PITCH + 2.0, 4.6))
window = bpy.context.object
window.name = 'Window_3200K'
window.data.shape = 'RECTANGLE'
window.data.size, window.data.size_y = 0.8, 1.2 # the real opening, high on the far wall
window.data.energy = 30.0 # 50 W read as inviting. It should not.
window.data.color = (1.0, 0.71, 0.42) # 3200 K
window.rotation_euler = (radians(-100), 0.0, 0.0) # down the aisle, tipped slightly to the floor
# ---- The dust is what makes the shaft visible. Above 0.03 the air turns to milk.
bpy.ops.mesh.primitive_cube_add(size=1.0, location=(0.0, COLUMNS * COL_PITCH / 2, 3.0))
air = bpy.context.object
air.name = 'Air_VolumeScatter'
air.scale = (5.0, COLUMNS * COL_PITCH / 2, 3.0)
air.display_type = 'WIRE'
fog = bpy.data.materials.new('Air')
fog.use_nodes = True
tree = fog.node_tree
tree.nodes.remove(tree.nodes['Principled BSDF'])
scatter = tree.nodes.new('ShaderNodeVolumeScatter')
scatter.inputs['Density'].default_value = 0.02
scatter.inputs['Anisotropy'].default_value = 0.5 # forward scatter, toward the lens
tree.links.new(scatter.outputs['Volume'], tree.nodes['Material Output'].inputs['Volume'])
air.data.materials.append(fog)
# ---- The camera is the real camera or the previs is decoration.
bpy.ops.object.camera_add(location=(0.0, -1.5, 1.55), rotation=(radians(90), 0.0, 0.0))
cam = bpy.context.object
cam.name = 'Cam_907X_CF40'
cam.data.sensor_fit = 'HORIZONTAL'
cam.data.sensor_width, cam.data.sensor_height = 43.8, 32.9
cam.data.lens = 40.0
cam.data.dof.use_dof = True
cam.data.dof.aperture_fstop = 4.0
cam.data.dof.focus_distance = 26.0
bpy.context.scene.camera = cam
scene = bpy.context.scene
scene.render.engine = 'CYCLES'
scene.cycles.samples = 4096 # a dark room at fewer samples is a noisy dark room
scene.cycles.use_denoising = True
scene.view_settings.view_transform = 'AgX'
scene.view_settings.exposure = 1.8
scene.render.image_settings.file_format = 'OPEN_EXR'
scene.render.image_settings.color_depth = '16'
# Iterate at a quarter of the 907X frame. Render the master once, at full size.
DIVISOR = 4
scene.render.resolution_x = 11656 // DIVISOR
scene.render.resolution_y = 8742 // DIVISOR
print(f'PREVIS_CHAI: {len(BANK_X) * TIERS * COLUMNS} barriques, '
f'aisle {COLUMNS * COL_PITCH:.0f} m, longer than the building it came from.')
Two things in there are house rules and not Blender defaults. It writes into one collection and only that collection. And it does not begin by selecting everything and deleting it, which is how nearly every published example of this workflow begins, including the good ones. An agent that opens by wiping the scene is fine on a demo file and unacceptable on a project.
What the first pass gets wrong
Predictably, and that is the useful part. The failures are known in advance because they are the same failures the light recipe was written to avoid.
The window at 50 watts reads as an invitation; the room has to be mostly dark, so it comes down to 30. Scatter density above 0.03 turns the air to milk and the shaft disappears into it. Barrels on a perfect grid read as a warehouse rather than a cellar, so each instance needs a little rotation and height jitter. And the perspective is wrong by construction: the window sits 4.6 m up the far wall while the camera looks dead level from 1.55 m, so the vanishing point lands three metres under the light. Kubrick's whole trick is that those two are the same point.
Every one of those is a single parameter. That is the argument for the loop: the model takes a viewport screenshot, compares it against the brief, and edits the object that caused the problem. Not a new image: the same scene, one value changed. Then it looks again.
It has no taste
K3 can build the scene. It cannot tell you the shot is good. Ask for “more cathedral” and you get more fog, because fog is the parameter nearest to the word. The instruction is understood and the reason behind it is missed.
So the division of labour is the ordinary one. Direction is human: the shaft lands on the fourth column and not the first, the blacks stay black, the barrels have to look stacked by a cellar hand and not placed by a script, the aisle has to read as longer than a building can be without reading as fake. The model executes and re-renders, as often as that takes.
That is a smaller sentence than one prompt making a film. It is the version that survives contact with a print deadline.
It can also break things faster
Give a model a socket into Blender and it makes unwanted changes at the speed it makes useful ones. “Tidy up the project” is an instruction that can rename twelve hundred objects, purge materials it judged unused, reorganise collections, and overwrite a script.
The vendors say as much. Kimi Code's MCP documentation asks for manual approval on high-risk tools (file writes, command execution) and warns against blanket mcp__* wildcards that approve a server's whole surface at once. blender-mcp's own guidance is that execute_blender_code runs unsandboxed Python at the same trust level as a terminal, and that a big scene should be built in several small calls, not one long one.
So these are the rules the project runs under. They are ours, and they are the boring part that matters.
Rule 01
One collection, no wipes
The agent creates and edits inside PREVIS_CHAI. It does not delete outside it, and never opens a script with a select-all delete. Anything it needs from elsewhere is linked in, read only.
Rule 02
Python is never auto-approved
execute_blender_code is confirmed by hand, every call, however tedious that gets. No wildcard approval on the server. The read tools (scene info, object info, screenshot) run freely.
Rule 03
Scripts live in the repo
Anything worth running twice is committed as a file, not left in a chat window. The chat is where a script is drafted; git is where it exists. Same standard the Capture One recipes are held to.
Rule 04
The .blend is versioned by hand
Numbered incremental saves before each agent session, so any pass can be thrown away whole. A model that can be wrong quickly needs an undo bigger than one step.
Rule 05
Measurements are measured
Sensor dimensions, barrique dimensions, row pitch, ceiling height, window position, latitude, date. The model may not invent any of them, and a stand-in must carry PLACEHOLDER in its name so it cannot be mistaken for a fact.
Rule 06
A previs render is a proof, never a work
Nothing from the previs pipeline is presented as art, and nothing carries a label, marker or annotation baked into pixels. Same rule the print masters have lived under from the start. A render that gets promoted to a work re-renders clean, at full frame.
Where the render goes
Out of Blender as 16-bit EXR, linear, with AgX applied at the view transform only and never baked. Into Capture One, where it takes the same Chai Cathedral recipe the BMPCC low-light frames take: cool tint, saturation down, shadow detail lifted, a light vignette. Then to WhiteWall.
The moving version goes the other way. EXR sequence with cryptomatte into Resolve, cut against BRAW from the real chai, and graded once so the photographed and the rendered material pass through the same node. The drone from the Ableton chai piece sits under it.
The grade is where the seam closes. If the render takes the recipe and the photograph takes the recipe, they match in Capture One, not in Blender, and not by being rendered harder.
What actually shifts
Not automatic 3D art. A model with a socket into Blender does not replace knowing how to light a room, and it will not turn a sentence into a finished sequence.
What it shortens is the distance between a brief and an editable first version. Envelope, instancing, initial lights, a matched camera, a look at the preview, one obvious problem corrected, inside a file that stays a file, with the geometry, the materials and the script all still open.
Image generators hand back finished outputs that are hard to control. This hands back an unfinished production that keeps taking notes. Less magic, and closer to the way the rest of the practice already works.
Read on 30 July 2026: vendor documentation, not summaries
- Kimi K3 technical blog · parameters, attention design, vision in the loop
- moonshotai/Kimi-K3 model card · open weights, context window, modalities
- Kimi Code: Model Context Protocol · stdio, HTTP and SSE transports, tool approval
- ahujasid/blender-mcp · the add-on, the socket, the four tools