Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 27 additions & 14 deletions skills/drivers-and-app-handlers/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,15 +145,17 @@ The handlers live as lists at `bpy.app.handlers.<event>`. To register, append; t

| Handler | Signature | Fires when |
| --- | --- | --- |
| `save_pre` | `(scene)` (some 4.x), `(scene, filepath)` (5.x) | Before the .blend is written. Use to clean up data you don't want serialized. |
| `save_post` | Same | After the .blend is written. Use for post-save bookkeeping. |
| `load_pre` | `(scene)` | Before a .blend is loaded. The current scene is still the old one. |
| `load_post` | `(scene)` | After a .blend is loaded. Use to validate or migrate add-on data. |
| `save_pre` | `(filepath: str)` | Before the .blend is written. The argument is the file being saved (empty string for the startup file), **not** a Scene. Use to clean up data you don't want serialized. |
| `save_post` | `(filepath: str)` | After the .blend is written. Same single filepath argument. |
| `load_pre` | `(filepath: str)` | Before a .blend is loaded. The argument is the file being loaded. |
| `load_post` | `(filepath: str)` | After a .blend is loaded. Use to validate or migrate add-on data. |
| `depsgraph_update_pre` | `(scene, depsgraph)` | Before a depsgraph evaluation pass. |
| `depsgraph_update_post` | `(scene, depsgraph)` | After a depsgraph evaluation pass. Fires very frequently; must be O(1) or near-O(1). |
| `frame_change_pre` | `(scene, depsgraph)` | Before frame is set. |
| `frame_change_post` | `(scene, depsgraph)` | After frame is set. |
| `exit_pre` (new in 5.1) | `(scene)` | Before Blender shuts down. Use for resource cleanup, telemetry flush, etc. |
| `exit_pre` (new in 5.1) | `(*args)` | Before Blender shuts down. Use for resource cleanup, telemetry flush, etc. The argument is not a Scene; accept `*args`. |

The save/load handlers (`save_pre`, `save_post`, `load_pre`, `load_post`) all receive the **file path as a string** as their single argument, **not** a Scene. (Verified empirically on 4.5.10 LTS and 5.1.1, and against `bpy.app.handlers` docs: "Accepts one argument: the file being saved/loaded.") Only the depsgraph/frame-change handlers receive `(scene, depsgraph)`.

The `exit_pre` handler in 5.1 is particularly useful for add-ons that need to release external resources (sockets, log files, child processes) deterministically before the process terminates.

Expand All @@ -165,10 +167,15 @@ from bpy.app.handlers import persistent


@persistent
def on_save_pre(scene, filepath):
"""Clear temporary cache data before save so it doesn't bloat the .blend."""
if 'my_addon_cache' in scene:
del scene['my_addon_cache']
def on_save_pre(filepath):
"""Clear temporary cache data before save so it doesn't bloat the .blend.

The handler argument is the path being saved (a string), not a Scene, so
reach the scene(s) through bpy.data.
"""
for scene in bpy.data.scenes:
if 'my_addon_cache' in scene:
del scene['my_addon_cache']


def register():
Expand Down Expand Up @@ -201,7 +208,10 @@ from bpy.app.handlers import persistent


@persistent
def increment_save_count(scene, filepath):
def increment_save_count(filepath):
# save_post passes the saved file path (a string) as its only argument.
# Store the running count on the current scene.
scene = bpy.context.scene
counts = scene.get('save_counts', {})
counts[filepath] = counts.get(filepath, 0) + 1
scene['save_counts'] = counts
Expand All @@ -224,8 +234,11 @@ from bpy.app.handlers import persistent


@persistent
def cleanup_on_exit(scene):
"""Release the external log file handle before the process terminates."""
def cleanup_on_exit(*args):
"""Release the external log file handle before the process terminates.

exit_pre's argument is unused here; accept *args to stay signature-proof.
"""
global _log_handle
if _log_handle is not None:
_log_handle.close()
Expand All @@ -250,14 +263,14 @@ The `exit_pre` handler list is new in Blender 5.1. On 4.5 LTS, fall back to OS-l
- **Doing real work inside `depsgraph_update_post`.** This handler fires on every depsgraph evaluation, which is many times per second during playback or interaction. Anything more than O(1) bookkeeping causes user-visible slowdown.
- **Recursively modifying the scene from a depsgraph handler.** The modification triggers another depsgraph evaluation, which calls the handler, which modifies the scene. Infinite loop, often manifesting as a hang.
- **Asymmetric register/unregister.** The handler is appended on register but not removed on unregister. Disabling the add-on leaves the callback in place. After enable/disable cycles, the callback runs N times per event.
- **Assuming the `save_pre` signature.** It changed across the 4.x to 5.x window. Use `(scene, filepath=None)` defensively, or `*args` if you don't need the values.
- **Treating the `save_pre` argument as a Scene.** The save/load handlers receive the **file path string** (empty for the startup file), not a Scene. Name the parameter `filepath` (or take `*args`), and reach scenes via `bpy.context.scene` / `bpy.data.scenes`. A membership test like `'key' in arg0` against the path string is silently wrong, and `del arg0['key']` raises `TypeError`.

## Version correctness

| Topic | 4.5 LTS | 5.1 stable |
| --- | --- | --- |
| `exit_pre` handler | Not available | New in 5.1; use `atexit` fallback for 4.x |
| `save_pre` signature | `(scene)` in 4.0, varies | `(scene, filepath)` in 5.x |
| `save_pre` / `save_post` signature | `(filepath)` — a string | `(filepath)` — a string (unchanged) |
| `driver_namespace` | Available | Same |
| Driver security | Already restrictive | Same |

Expand Down
9 changes: 5 additions & 4 deletions skills/geometry-nodes-python/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,10 +154,10 @@ The argument is the exact RNA `bl_idname` of the node class. A few you'll reach
| Vector Math | `ShaderNodeVectorMath` |
| Mix | `ShaderNodeMix` |
| Noise Texture | `ShaderNodeTexNoise` |
| Mesh to SDF Grid (5.0+) | `GeometryNodeMeshToSDFGrid` |
| Mesh to SDF Grid (4.3+) | `GeometryNodeMeshToSDFGrid` |
| SDF Grid to Mesh / Volume to Mesh | `GeometryNodeVolumeToMesh` |
| Repeat Input / Output | `GeometryNodeRepeatInput`, `GeometryNodeRepeatOutput` |
| For Each Element Input / Output (5.0+) | `GeometryNodeForeachGeometryElementInput`, `GeometryNodeForeachGeometryElementOutput` |
| For Each Element Input / Output (4.3+) | `GeometryNodeForeachGeometryElementInput`, `GeometryNodeForeachGeometryElementOutput` |

To list all available Geometry node types in your Blender version:

Expand Down Expand Up @@ -243,8 +243,9 @@ def has_bundles():
return major >= 5

def has_for_each_element():
major, _minor, _patch = bpy.app.version
return major >= 5
# The For Each Element zone shipped in Blender 4.3, so it is available on
# the whole 4.5 LTS / 5.x supported range. (Bundles, by contrast, are 5.0+.)
return bpy.app.version >= (4, 3, 0)
```

## Common AI mistakes
Expand Down
14 changes: 9 additions & 5 deletions skills/headless-batch-scripting/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,11 +161,15 @@ def main():
parser = argparse.ArgumentParser()
parser.add_argument("--output", required=True)
parser.add_argument("--frames", default="1")
parser.add_argument("--engine", default="CYCLES", choices=["CYCLES", "BLENDER_EEVEE_NEXT"])
parser.add_argument("--engine", default="CYCLES", choices=["CYCLES", "EEVEE"])
args = parser.parse_args(argv)

scene = bpy.context.scene
scene.render.engine = args.engine
if args.engine == "EEVEE":
# EEVEE's engine id is 'BLENDER_EEVEE' on 5.0+, 'BLENDER_EEVEE_NEXT' on 4.2-4.5.
scene.render.engine = 'BLENDER_EEVEE' if bpy.app.version >= (5, 0, 0) else 'BLENDER_EEVEE_NEXT'
else:
scene.render.engine = args.engine
scene.render.image_settings.file_format = 'PNG'

output_dir = os.path.dirname(args.output) or "."
Expand All @@ -185,7 +189,7 @@ if __name__ == "__main__":
Notes:

- `bpy.ops.render.render(write_still=True)` is one of the few `bpy.ops` calls that work in `--background`.
- `BLENDER_EEVEE_NEXT` is the 5.x EEVEE engine identifier. On 4.5 LTS, use `BLENDER_EEVEE`. Detect via `bpy.app.version`.
- EEVEE's engine identifier is `BLENDER_EEVEE` on Blender 5.0+ and `BLENDER_EEVEE_NEXT` on 4.2-4.5 LTS (the id was reclaimed in 5.0 after legacy EEVEE was removed in 4.2). Detect via `bpy.app.version`.

## Detecting Blender version in scripts

Expand All @@ -195,9 +199,9 @@ import bpy
major, minor, _patch = bpy.app.version

if (major, minor) >= (5, 0):
eevee_engine = 'BLENDER_EEVEE_NEXT'
else:
eevee_engine = 'BLENDER_EEVEE'
else:
eevee_engine = 'BLENDER_EEVEE_NEXT'
```

`bpy.app.version` is a `(major, minor, patch)` tuple, always reliable.
Expand Down
2 changes: 1 addition & 1 deletion skills/procedural-materials-and-shaders/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ When the same subgraph appears across multiple materials, factor it into a `Shad
| --- | --- | --- | --- |
| Principled BSDF | `ShaderNodeBsdfPrincipled` | Same | Some inputs renamed in 5.0; `Specular` -> `Specular IOR Level`. Use string lookup with the 5.x name. |
| Node group socket interface | `group.inputs.new` / `group.outputs.new` | `group.interface.new_socket` | Different APIs, see snippet `shader-node-group.py`. |
| EEVEE engine string | `'BLENDER_EEVEE'` | `'BLENDER_EEVEE_NEXT'` | EEVEE Next is the default in 5.x; legacy EEVEE was retired. |
| EEVEE engine string | `'BLENDER_EEVEE_NEXT'` | `'BLENDER_EEVEE'` | Legacy EEVEE was removed in 4.2. EEVEE Next used the id `'BLENDER_EEVEE_NEXT'` on 4.2-4.5, then reclaimed the plain `'BLENDER_EEVEE'` id in 5.0. |
| Layered Textures | Not present | Not present in 5.1 | Roadmap pushed to 2027. Do not generate code referencing it. |

EEVEE Next stabilized in Blender 5.1 with material caching and feature parity improvements; most Principled BSDF graphs render identically across EEVEE Next and Cycles.
Expand Down
Loading
Loading