Skip to content
Back to skills

Blender Controlnet Passes

ASecurity

Set up and render ControlNet conditioning passes from a Blender scene for AI image/video generation - grey clay override, compositor-normalized depth, cryptomatte EXR, optional wireframe overlay for temporal consistency. Wraps scripts/setup_controlnet_passes.py (Blender 5.x APIs, test-slice discipline, trailing-dot File Output naming). Use when preparing depth/edge conditioning inputs, clay renders for video models, or crypto mattes for comp. Triggers: \"controlnet passes\", \"render depth pa...

  • 6 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 3, 2026
ai-agentspythongonodetestingapi

Works with

  • cli
  • api
  • mcp

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned September 20, 2026

npx -y skills add sideshowroberto/vfx-agent-toolkit --skill blender-controlnet-passes --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Blender Controlnet Passes?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Blender Controlnet Passes
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/sideshowroberto-blender-controlnet-passes/badge)](https://www.skillsdirectory.com/skills/sideshowroberto-blender-controlnet-passes)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: blender-controlnet-passes
description: "Set up and render ControlNet conditioning passes from a Blender scene for AI image/video generation - grey clay override, compositor-normalized depth, cryptomatte EXR, optional wireframe overlay for temporal consistency. Wraps scripts/setup_controlnet_passes.py (Blender 5.x APIs, test-slice discipline, trailing-dot File Output naming). Use when preparing depth/edge conditioning inputs, clay renders for video models, or crypto mattes for comp. Triggers: \"controlnet passes\", \"render depth pass\", \"clay render\", \"grey shade render\", \"render passes for AI\", \"control net setup blender\"."
---

# Blender ControlNet Passes

**Version:** 1.1.0 | **Last Updated:** 2026-08-21 | **Blender:** 5.x

One parameterized script replaces the per-project pass-render scripts:
`scripts/setup_controlnet_passes.py` (relative to this skill directory)

Outputs per shot: `<root>/<shot>/grey/` (16-bit PNG clay), `<root>/<shot>/depth/`
(16-bit PNG, near=white/far=black, fixed normalization), `<root>/<shot>/crypto/`
(multilayer EXR: Combined + CryptoObject layers), optional grey MP4, and a
`<shot>_manifest.json`.

## Script vs MCP-interactive setup

- **Use the script** for anything repeatable: full sequences, batch/CLI
  background renders, half-speed re-renders, or when a scene needs the
  standard grey/depth/crypto setup from scratch. Import it via MCP too --
  do not re-derive the compositor graph by hand.
- **Use MCP-interactive** only for scene-specific prep the script cannot
  know about: hiding locator meshes, switching shot collections/cameras,
  inspecting why a render looks wrong. Then call the script's functions.

### CLI (background render)

```
blender -b shot.blend -P setup_controlnet_passes.py -- --shot SHOT_0010 --output-root "X:/renders" --test-slice
blender -b shot.blend -P setup_controlnet_passes.py -- --shot SHOT_0010 --output-root "X:/renders" --render --speed 0.5 --mp4
```

### MCP (execute_blender_code)

```python
import sys
sys.path.append(r"<workspace>/Blender/scripts")
import setup_controlnet_passes as scp
setup = scp.setup_scene(shot_name="SHOT_0010", output_root=r"X:/renders")
scp.render_sequence(setup, test_slice=10)   # verify first
scp.render_sequence(setup)                   # then full range
```

With no action flag the CLI does **setup only** (no rendering). `--test-slice`
renders N frames (default 10) into a sibling `<shot>_TEST` folder; `--render`
does the full range.

## Parameters

| Param (CLI / setup_scene) | Default | Meaning |
|---|---|---|
| `--shot` / `shot_name` | SHOT_0010 | file prefix + shot folder name |
| `--output-root` / `output_root` | `controlnet/` next to .blend | base output dir |
| `--passes` / `passes` | grey,depth,crypto | subset of the three passes |
| `--frame-start` `--frame-end` | scene range | render range |
| `--speed` | 1.0 | 0.5 = half speed via `frame_set(f, subframe=)` sub-frame interpolation (for video-model minimum durations) |
| `--min-duration` | 0 | seconds; tail-hold pad the last frame to reach it |
| `--wireframe` + `--wire-pixel-size` | off / 1.0 px | wireframe overlay on clay |
| `--grey-value` `--roughness` | 0.5 / 0.75 | clay material |
| `--depth-max` / `depth_max` | 100.0 | distance mapped to black (compositor-side) |
| `--crypto-depth` | 6 | cryptomatte depth; EXR layers = ceil(depth/2) |
| `--depth-writer` | auto | file_output (background-safe) or viewer (interactive) |
| `--taa-samples` `--motion-blur` | untouched | optional EEVEE stability overrides |
| `--mp4` / `make_mp4` | off | ffmpeg encode of grey PNGs at SCENE fps |
| `--test-slice [N]` / `test_slice` | off | render first N frames to `<shot>_TEST` |

## GOTCHAS (production-earned; do not relearn these)

1. **Depth clip_end trap.** Never tighten `camera.data.clip_end` to shape the
   depth map. Clip range clips geometry out of ALL passes -- background
   buildings vanished from the grey render in production, twice. Depth range
   is shaped in the compositor only: `Depth -> Math(MIN depth_max) ->
   Math(DIVIDE depth_max) -> Invert`. Fixed mapping, no per-frame Normalize
   (per-frame min/max flickers over an animation).
2. **Trailing-dot file naming.** File Output nodes append the scene frame
   number to `file_name`. `name_0001` + frame 276 becomes `name_00010276`
   (the 960000-range frames bug). Always end `file_name` with `.` --
   `name_0001.` writes `name_0001.exr`. The script does this everywhere.

   **This applies to `file_name` ONLY, and it still holds on 5.1.2** (verified
   headless 2026-08-17: `shot_depth_0001.` round-trips unchanged). Do not
   extend the trailing dot to `file_output_items` names -- those ARE sanitised,
   so an item named `depth.` silently becomes `depth_`. The script names its
   items `Depth` / `Combined` / crypto layer names, which is correct. If you
   ever set an item name, read it back.
3. **Blender 5.x compositor access.** `scene.node_tree` is gone. Use
   `scene.compositing_node_group`; create it with
   `bpy.data.node_groups.new(name, type='CompositorNodeTree')`. There is no
   Composite node; the main output comes from `scene.render.filepath`.
   Details: `references/blender5_api_notes.md`.
4. **File Output items.** The node starts with zero `file_output_items` and
   silently writes nothing. Add items (`socket_type` FLOAT/RGBA/VECTOR),
   then connect by input INDEX, not name.
5. **FPS from the scene.** Duration math and MP4 encode use
   `scene.render.fps / scene.render.fps_base`. Never assume 24.
6. **Test-slice discipline.** Always render ~10 frames and inspect them
   before a full render. A wasted session re-rendered 4 x 216 frames chasing
   settings; change ONE variable per re-render.
7. **Wireframe for temporal consistency.** A mesh-locked wireframe overlay
   acts like a built-in Canny reference and stabilizes video-model output.
   Do NOT use a world-space grid (it slides across animated objects). Some
   models leak wireframe lines into output -- test with the target model.
8. **Depth writer mode.** Viewer-node extraction is the proven interactive
   path but Viewer images may not update in `blender -b`. The script
   auto-selects a File Output PNG node in background mode (`--depth-writer`
   overrides). If background depth PNGs come out empty, verify this first.
   **Fixed 2026-08-21:** this path crashed on every 5.1.x headless run
   (`TypeError: enum "PNG" not found in ('OPEN_EXR_MULTILAYER')`) because a
   fresh File Output node's `format.media_type` is `MULTI_LAYER_IMAGE`. The
   script now sets `media_type = 'IMAGE'` first; in IMAGE mode the item name
   is appended (`shot_depth_0001.Depth.png`) and the script renames to the
   canonical `shot_depth_0001.png` after each frame. Verified headless on
   5.1.2 (16-bit `I;16` PNGs, 1363 distinct values on the test scene).
9. **Verify pixel CONTENT, never just file existence.** A render that wrote
   4 KB of PNG is not a render that shows anything. Two traps caught a
   headless run on 2026-08-16: a clay pass came out a single flat value
   (uniform frame - and a uniform frame passes an "R=G=B within 1 LSB" grey
   check trivially, so the check awards a free point), and the depth pass
   came out uniformly 0.0 because the normalization range was INVENTED
   (30-80 m) for objects actually sitting 2-15 m from camera. Measure the
   real object distances (`(ob.location - cam.location).length`) and derive
   the range from them; then assert the written frame has more than a
   couple of distinct values before calling the pass good.
10. **EEVEE temporal jitter.** TAA sub-pixel jitter shows up as noise in
   tracking curves; `taa_render_samples=1` kills jitter but looks aliased
   and was judged worse in production. Leave scene sampling alone unless
   testing deliberately (`--taa-samples`, `--motion-blur` exist for that).
11. **Long MCP-driven render loops can hang Blender with runaway memory.**
    A 118-frame grey render through the MCP crept from 4.5s to 58s/frame
    and wedged Blender at 28 GB RAM on frame 116 (large arena crowd scene).
    Mitigations: chunk `render_sequence` calls (20-40 frames per call via
    `frame_start`/`frame_end` -- seq numbering is derived from the full
    range so chunks stay consistent only if you pass the SAME full range
    and use `skip_existing=True`), or recover after a crash by re-running
    with `skip_existing=True` (renders only missing frames). Straight
    beauty/pass renders with no subframe/speed tricks are safer via
    Blender's native animation render (`INVOKE_DEFAULT`), which held
    steady for 118-frame 4K renders in the same sessions.
12. **Dark scenes render near-black clay.** The override preserves scene
    lighting; a dark stage/arena scene gives an unreadable conditioning
    frame. Temporarily set the world Background to flat grey
    (color 0.5 / strength 1.0), render, restore. Flat even light is ideal
    for geometry conditioning anyway (no baked-in light direction).
13. **ffmpeg on Windows.** `encode_mp4` calls plain "ffmpeg"; if Blender's
    environment lacks it the script warns and leaves PNGs. A working copy
    often hides at `C:\Program Files\ImageMagick-*\ffmpeg.exe`. Also
    ensure the `grey_mp4` directory exists before an external encode --
    ffmpeg does not create missing output directories.
14. **Multilayer EXRs are multi-part - check ALL parts before calling one
    hollow.** Blender 5.x writes one EXR part per layer. The OpenEXR python
    module's `File.channels()` / `File.header()` show part 0 only, so an
    intact crypto EXR reads as "Combined RGBA and nothing else". On
    2026-08-21 this produced a confident, wrong "5.1.x File Output drops all
    but the first item" diagnosis against 312 production frames (and a
    merge-based rewrite of this script that was reverted). Instrument:
    `python scripts/merge_exr_layers.py --inspect <file.exr>` lists
    every part; the script's `verify_crypto_exr()` does the same on frame
    0001 when the calling python has OpenEXR (Blender's bundled python does
    not - it prints a skip notice, never a false PASS). Rule 8 in action:
    a too-clean "only Combined" result is the instrument, not the file.

## References

- `references/blender5_api_notes.md` -- Blender 5.x API details (compositor
  node group, File Output API, layered Actions, removed nodes).

Files in this skill

  • SKILL.md10.1 KB
  • references/blender5_api_notes.md6.9 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.