Skip to content

Commit 57788ec

Browse files
fix: teach the undocumented 5.2 COLOR strip size bake
5.2 bakes readonly width/height at new_effect from scene resolution; 4.5/5.1 have no intrinsic size. That lived only as a vse-cut-list patch. Teach it in vse-python and log that notes-derived deltas are incomplete by default. Signed-off-by: fOuttaMyPaint <TMhospitalitystrategies@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 765e6c3 commit 57788ec

7 files changed

Lines changed: 212 additions & 11 deletions

File tree

‎.cursor-plugin/plugin.json‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@
2525
"skills/procedural-materials-and-shaders/SKILL.md",
2626
"skills/depsgraph-and-evaluated-data/SKILL.md",
2727
"skills/drivers-and-app-handlers/SKILL.md",
28-
"skills/bl-info-migration/SKILL.md"
28+
"skills/bl-info-migration/SKILL.md",
29+
"skills/vse-python/SKILL.md"
2930
],
3031
"rules": [
3132
"rules/prefer-data-over-ops-in-loops.mdc",

‎AGENTS.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ a `.cursor-plugin/plugin.json` manifest so the ecosystem drift checker
2020
classifies it as a `cursor-plugin`. This is content the AI loads when the user
2121
asks Blender questions or works on Blender add-ons in Cursor or Claude Code.
2222

23-
The content base is 12 skills, 6 rules, 2 templates, 17 snippets, and 45
23+
The content base is 13 skills, 6 rules, 2 templates, 17 snippets, and 45
2424
examples (counts are CI-enforced against README.md and the manifest). The full
2525
inventory tables and per-item purposes live in `CLAUDE.md`. Example anatomy
2626
and authoring rules: copy `examples/bmesh-gear/`; the render look is specified
@@ -31,7 +31,7 @@ in `docs/VISUAL-STYLE.md`; the canonical run prompt is
3131

3232
```
3333
Blender-Developer-Tools/
34-
skills/<skill-name>/SKILL.md # 12 skill files
34+
skills/<skill-name>/SKILL.md # 13 skill files
3535
rules/<rule-name>.mdc # 6 rule files
3636
templates/<template-name>/ # 2 starter templates
3737
snippets/<snippet-name>.py # 17 standalone Python snippets

‎CLAUDE.md‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ The **Blender Developer Tools** repository is at **v0.47.0**. It packages skills
1717
## Repository Architecture
1818

1919
```
20-
skills/<skill-name>/SKILL.md - AI workflow definitions, 12 total
20+
skills/<skill-name>/SKILL.md - AI workflow definitions, 13 total
2121
rules/<rule-name>.mdc - Anti-pattern rules, 6 total
2222
templates/<template-name>/ - Starter projects, 2 total
2323
snippets/<snippet-name>.py - Standalone code patterns, 17 total
@@ -28,7 +28,7 @@ docs/gallery/ - Committed generated gallery pages + hero render
2828
VERSION - Source of truth for the repo version
2929
```
3030

31-
## Skills (12)
31+
## Skills (13)
3232

3333
| Skill | Purpose |
3434
| --- | --- |
@@ -44,6 +44,7 @@ VERSION - Source of truth for the repo version
4444
| depsgraph-and-evaluated-data | `evaluated_get` / `to_mesh` / `to_mesh_clear` lifetime contract for exporters and measurement |
4545
| drivers-and-app-handlers | Driver expressions, `driver_namespace`, application handlers including the new 5.1 `exit_pre` |
4646
| bl-info-migration | Three-step migration from legacy `bl_info` to Extensions Platform, dual-format pattern |
47+
| vse-python | VSE timeline from Python: `.strips` vs `.sequences`, `new_effect` kwargs, 5.2 COLOR `width`/`height` bake |
4748

4849
## Rules (6)
4950

@@ -149,7 +150,7 @@ The AI consumes content via:
149150

150151
## Key Conventions
151152

152-
- **Blender versions**: 5.2 LTS primary, 5.1 prior stable, 4.5 LTS fallback. Skills must show both code paths when 4.x and 5.x APIs diverge, and the 5.1-vs-5.2 NodesModifier input split.
153+
- **Blender versions**: 5.2 LTS primary, 5.1 prior stable, 4.5 LTS fallback. Skills must show both code paths when 4.x and 5.x APIs diverge, including the 5.1-vs-5.2 NodesModifier input split and the 5.2 COLOR strip `width`/`height` bake.
153154
- **Properties as annotations**: `my_prop: bpy.props.FloatProperty(...)` (correct), not `my_prop = bpy.props.FloatProperty(...)` (deprecated).
154155
- **bmesh memory**: every `bmesh.new()` must be paired with `bm.free()` in a `try`/`finally`.
155156
- **No `bpy.ops` in tight loops**: use `bpy.data.*` and `bmesh` for bulk work.

‎README.md‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
</p>
1919

2020
<p align="center">
21-
<strong>12 skills</strong> &nbsp;&bull;&nbsp; <strong>6 rules</strong> &nbsp;&bull;&nbsp; <strong>2 templates</strong> &nbsp;&bull;&nbsp; <strong>17 snippets</strong> &nbsp;&bull;&nbsp; <strong>45 examples</strong>
21+
<strong>13 skills</strong> &nbsp;&bull;&nbsp; <strong>6 rules</strong> &nbsp;&bull;&nbsp; <strong>2 templates</strong> &nbsp;&bull;&nbsp; <strong>17 snippets</strong> &nbsp;&bull;&nbsp; <strong>45 examples</strong>
2222
</p>
2323

2424
<p align="center">
@@ -36,13 +36,13 @@
3636

3737
## Overview
3838

39-
This repository ships **12 skills, 6 rules, 2 templates, 17 snippets, and 45 runnable examples** for Blender Python development targeting Blender 5.2 LTS (current stable) with Blender 4.5 LTS fallback support. Blender 5.1 is prior stable.
39+
This repository ships **13 skills, 6 rules, 2 templates, 17 snippets, and 45 runnable examples** for Blender Python development targeting Blender 5.2 LTS (current stable) with Blender 4.5 LTS fallback support. Blender 5.1 is prior stable.
4040

4141
The content is consumed by AI coding agents (Cursor, Claude Code, any MCP-capable client) when working on Blender add-ons, geometry nodes scripts, batch pipelines, or animation tooling. There is no build step. Edit the markdown and Python files directly.
4242

4343
| Layer | Role |
4444
| --- | --- |
45-
| **Skills** | Guided workflows: scaffolding, operators, panels, properties, mesh and bmesh, headless batch, slotted actions, geometry nodes, procedural materials, depsgraph queries, drivers and handlers, `bl_info` migration |
45+
| **Skills** | Guided workflows: scaffolding, operators, panels, properties, mesh and bmesh, headless batch, slotted actions, geometry nodes, procedural materials, depsgraph queries, drivers and handlers, `bl_info` migration, video sequencer |
4646
| **Rules** | Guardrails for the most common AI mistakes: ops-in-loops, bmesh leaks, legacy `bl_info` only, prop assignments, deprecated context-copy override, per-element loops over bulk mesh data |
4747
| **Templates** | A working Extensions Platform add-on starter and a headless batch script starter |
4848
| **Snippets** | 17 small standalone Python files demonstrating canonical patterns |
@@ -887,7 +887,7 @@ Companion to [`color-attribute-wheel`](examples/color-attribute-wheel/) and
887887
## How content is organized
888888

889889
```
890-
skills/<name>/SKILL.md - 12 skill files, YAML frontmatter, one canonical pattern each
890+
skills/<name>/SKILL.md - 13 skill files, YAML frontmatter, one canonical pattern each
891891
rules/<name>.mdc - 6 rule files, anti-pattern + correction
892892
templates/<name>/ - 2 template directories (extension-addon-template, headless-batch-script-template)
893893
snippets/<name>.py - 17 standalone Python snippets, 5 to 50 lines each

‎ROADMAP.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ derives the actual version from conventional-commit types.
1616
| Examples and demos (smoke-gated) | 12 | 6 | 2 | 17 | Shipped (v0.3.0) |
1717
| More examples (turntable, SDF remesh) | 12 | 6 | 2 | 17 | Shipped (v0.4.0) |
1818
| 5.2 LTS targeting, GN modifier inputs | 12 | 6 | 2 | 17 | Shipped |
19+
| VSE COLOR strip intrinsic size (undocumented 5.2) | 13 | 6 | 2 | 17 | Shipped |
1920
| Modal operators, USD, mathutils | — | — | — | — | Upcoming |
2021
| Stable | — | — | — | — | Upcoming |
2122

@@ -100,7 +101,7 @@ Not committed; target list for the next content version. (v0.3.0 shipped the smo
100101
- ~~Image save-format witness~~ **SHIPPED** as `examples/png-exr-alpha/` — float→PNG is RGBA16 and false-unpremultiplies as if associated-alpha (closed-form err 0.98 at RGB 0.02 / a=1/255); OpenEXR preserves float RGBA; byte→PNG is straight RGBA8; `EXR color_mode='RGB'` drops alpha
101102
- ~~Attribute domain witness~~ **SHIPPED** as `examples/attribute-domain-shear/` — POINT vs CORNER semantics on an 8-wedge pinwheel with one shared hub: CORNER authoring exact (hub corners disagree by face, err ≤ 1e-6), naive per-wedge POINT loop shears to the last write at hub + ring verts, measured deviation == palette closed form (0.751031); byte-identical on 4.5.11 and 5.1.2; crisp-vs-smeared dual pinwheel still — companion to `color-attribute-wheel`
102103
- ~~Light-linking collection witness~~ **SHIPPED** as `examples/light-link-studio/` — the API is `obj.light_linking` on the light OBJECT (`ld.light_linking` is an AttributeError on both versions); linked ratio 4.0x at projected-center luminance samples, unlink raises the decoy 244–251% with 0.0% hero drift in the same two-render check; EEVEE Next honors linking too (5.5x on 4.5.11, 5.9x on 5.1.2), Cycles pinned for deterministic samples
103-
- ~~VSE sequences-to-strips witness~~ **SHIPPED** as `examples/vse-cut-list/` — `.sequences` removed on 5.x (4.5 bridges to `.strips`), `new_effect` end kwarg `frame_end=` (4.5) vs `length=` (5.x), `frame_final_*` deprecated in favor of `left_handle`/`right_handle`/`duration`, TRANSFORM effect type removed, GAMMA_CROSS clamps to the source overlap, effect strips consume inputs only when stacked above them, same-scene scene strips render transparent; save/reload round-trip + tiny-render pixel witness
104+
- ~~VSE sequences-to-strips witness~~ **SHIPPED** as `examples/vse-cut-list/` — `.sequences` removed on 5.x (4.5 bridges to `.strips`), `new_effect` end kwarg `frame_end=` (4.5) vs `length=` (5.x), `frame_final_*` deprecated in favor of `left_handle`/`right_handle`/`duration`, TRANSFORM effect type removed, GAMMA_CROSS clamps to the source overlap, effect strips consume inputs only when stacked above them, same-scene scene strips render transparent; save/reload round-trip + tiny-render pixel witness. **5.2 COLOR `width`/`height` bake** (not in the python_api notes) taught in `skills/vse-python/`; the example `--check-pixels` path is the witness (set scene resolution before `new_effect`)
104105
- ~~glTF export round-trip witness~~ **SHIPPED** as `examples/gltf-export-roundtrip/` — `export_yup` bakes `(x,y,z)→(x,z,−y)` into vertex data with no node rotation (probed identical on 4.5.11 and 5.1.2), `export_apply` ships the evaluated mesh (one disk vertex per evaluated loop), TEXCOORD_0 is V-flipped on disk, per-triangle material bindings survive; exporter/importer RNA signatures byte-identical between 4.5.11 and 5.1.2 (guarded against future renames); `Mesh.calc_normals()` removal on 5.x surfaced during authoring
105106
- ~~LOD decimate chain witness~~ **SHIPPED** as `examples/lod-decimate-chain/` — Decimate COLLAPSE evaluated through the depsgraph is non-destructive (obj.data keeps closed-form counts), evaluated tris hit `ratio × base` within 5% (measured 0.0–0.44%), silhouette bbox survives within 1e-3 (measured 7.7e-6); a stacked Decimate halves the effective ratio, an aggressive 0.02 ratio collapses the nose tip — both caught failure modes
106107
- ~~Vertex weight limit witness~~ **SHIPPED** as `examples/vertex-weight-limit/` — the 4-influence engine cap enforced via the data API (`v.groups` + `VertexGroup.remove` + renormalize); unit sums (measured 3e-8), pose preserved (4.9e-3), exact LBS from the mesh's own deform layer (2.7e-7), Root mount pinned

‎docs/technical-audit.md‎

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,47 @@
22

33
_Audit date: 2026-06-20 · Repo @ v0.5.0 · Auditor: Principal Eng review_
44

5+
## Findings the release notes did not list
6+
7+
_Standing log. Notes-derived 5.1→5.2 (and later) deltas are incomplete by default._
8+
9+
The Blender `python_api` release notes are a starting set, not a closed
10+
inventory. A sweep that only copies listed removals will miss behavior
11+
changes that keep the same RNA names. Record those here when CI or
12+
authoring finds them, even when upstream notes omit the break. Do not
13+
treat a clean notes-diff as proof that two series are interchangeable.
14+
15+
### 2026-09-09 — COLOR strip intrinsic size (5.2, not in the notes)
16+
17+
**4.5 LTS / 5.1:** COLOR strips have no intrinsic size. `transform.scale_*`
18+
is a fraction of the output frame. Creating at factory resolution then
19+
rendering a tiny buffer is fine.
20+
21+
**5.2+:** `new_effect(..., type='COLOR')` bakes readonly `width` / `height`
22+
from `scene.render.resolution_*` at creation. Scale and offset are in that
23+
media space. Factory 1920×1080 + later 96×54 render → 0.36-scaled cells
24+
larger than the output; the long-runner covers the frame.
25+
26+
A lone COLOR strip **does** honor `transform.scale_*` on 5.2. The break is
27+
media size vs a later output size, not "transform is ignored."
28+
29+
Set scene resolution **before** building strips.
30+
31+
- Taught: `skills/vse-python/SKILL.md`
32+
- Witness: `examples/vse-cut-list/` `--check-pixels` (on 5.2 asserts
33+
`A.width, A.height == (96, 54)`). No separate example — that script
34+
already fails if the bake drifts.
35+
36+
### Contrast — NodesModifier dict assignment (5.2, was in the notes)
37+
38+
`mod[identifier] = value` raises `TypeError` on 5.2 (`id properties not
39+
supported for this type`). Replacement: `getattr(mod.properties.inputs,
40+
ident).value`. This one **was** listed in the 5.2 python_api notes.
41+
Taught in `skills/geometry-nodes-python/SKILL.md`, witnessed by
42+
`examples/gn-modifier-inputs/`. Keep both classes of finding in this
43+
log: notes-listed removals that still need a taught split, and
44+
behavior changes the notes never mentioned.
45+
546
## 0. Important reframing
647

748
The audit brief assumed a runnable add-on (operators, UI panels, a socket/file-watcher

‎skills/vse-python/SKILL.md‎

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
---
2+
name: vse-python
3+
description: Build Video Sequence Editor timelines from Python. SequenceEditor.strips vs .sequences, new_effect length vs frame_end, and the 5.2 COLOR strip width/height bake from scene resolution.
4+
standards-version: 1.10.0
5+
---
6+
7+
# Video Sequence Editor in Python
8+
9+
## Trigger
10+
11+
Use this skill when the user:
12+
13+
- Builds or inspects a VSE / sequencer timeline from a script
14+
- Mentions `sequence_editor`, `strips`, `sequences`, `new_effect`, COLOR strips, or `StripTransform`
15+
- Hits `AttributeError: 'SequenceEditor' object has no attribute 'sequences'` on 5.x
16+
- Hits `TypeError` on `new_effect` kwargs (`frame_end` vs `length`)
17+
- Renders a sequencer composite whose COLOR cells ignore `transform.scale_*` after a resolution change
18+
19+
## High-level shape
20+
21+
```python
22+
import bpy
23+
24+
scene = bpy.context.scene
25+
se = scene.sequence_editor or scene.sequence_editor_create()
26+
coll = se.strips if hasattr(se, "strips") else se.sequences
27+
28+
if bpy.app.version >= (5, 0, 0):
29+
strip = coll.new_effect(
30+
name="A", type="COLOR", channel=1,
31+
frame_start=1, length=10,
32+
)
33+
else:
34+
strip = coll.new_effect(
35+
name="A", type="COLOR", channel=1,
36+
frame_start=1, frame_end=11,
37+
)
38+
strip.color = (0.85, 0.10, 0.22)
39+
```
40+
41+
Branch on `bpy.app.version`, never on `bpy.app.version_string`. An empty `bpy_prop_collection` is falsy — `se.strips or se.sequences` silently falls through to the legacy accessor on an empty timeline. Always branch on `hasattr`.
42+
43+
## Accessor: `.strips` vs `.sequences`
44+
45+
| | 4.5 LTS | 5.1 / 5.2+ |
46+
| --- | --- | --- |
47+
| Canonical collection | `.strips` (`.sequences` is a bridge to the same strips) | `.strips` only |
48+
| `.sequences` | Present, same contents as `.strips` | `AttributeError` |
49+
50+
```python
51+
def strips_coll(se):
52+
return se.strips if hasattr(se, "strips") else se.sequences
53+
```
54+
55+
Do not write `se.sequences` in new code. On 5.x it is gone; on 4.5 `.strips` already exists.
56+
57+
## `new_effect` end kwarg
58+
59+
`strips.new_effect(...)` ends a strip with **one** accepted end argument. The other raises `TypeError`.
60+
61+
```python
62+
span = (1, 33) # end-exclusive [start, end)
63+
64+
if bpy.app.version >= (5, 0, 0):
65+
strip = coll.new_effect(
66+
name="A", type="COLOR", channel=1,
67+
frame_start=span[0], length=span[1] - span[0],
68+
)
69+
else:
70+
strip = coll.new_effect(
71+
name="A", type="COLOR", channel=1,
72+
frame_start=span[0], frame_end=span[1],
73+
)
74+
```
75+
76+
Timeline bounds: `frame_final_start` / `frame_final_end` / `frame_final_duration` on 4.5. On 5.x those names are deprecated aliases (removal announced for 6.0); canonical accessors are `left_handle` / `right_handle` / `duration`. Scene strips take four args — `new_scene(name, scene, channel, frame_start)` — with no `length` / `frame_end` kwarg on either version.
77+
78+
The `TRANSFORM` effect type is removed on 5.x. Place strips with per-strip `strip.transform` (`StripTransform`) on both versions. Effect inputs are `input_1` / `input_2` (creation kwargs `input1` / `input2`).
79+
80+
## COLOR strip intrinsic size (5.2)
81+
82+
4.5 LTS and 5.1 COLOR strips have **no** intrinsic size. `transform.scale_*` is a fraction of the **output frame**. Creating at factory resolution then rendering a tiny buffer is fine.
83+
84+
5.2 and later bake readonly `width` / `height` from **scene render resolution at `new_effect` time**. Scale and offset are then in that **media** space, not a later output size. Set scene resolution **before** building strips.
85+
86+
This split is **not** in the 5.2 `python_api` release notes. Witnessed by `examples/vse-cut-list/` `--check-pixels`.
87+
88+
```python
89+
width, height = 96, 54
90+
scene.render.resolution_x = width
91+
scene.render.resolution_y = height
92+
93+
se = scene.sequence_editor or scene.sequence_editor_create()
94+
coll = se.strips if hasattr(se, "strips") else se.sequences
95+
96+
if bpy.app.version >= (5, 0, 0):
97+
strip = coll.new_effect(
98+
name="A", type="COLOR", channel=1,
99+
frame_start=1, length=10,
100+
)
101+
else:
102+
strip = coll.new_effect(
103+
name="A", type="COLOR", channel=1,
104+
frame_start=1, frame_end=11,
105+
)
106+
107+
if bpy.app.version >= (5, 2, 0):
108+
# 5.2+: readonly width/height baked at new_effect from scene.render
109+
assert (strip.width, strip.height) == (width, height)
110+
# strip.width = 64 # TypeError — readonly
111+
else:
112+
# 4.5 / 5.1: no intrinsic size; hasattr(strip, "width") is False
113+
pass
114+
115+
strip.transform.scale_x = 0.36
116+
strip.transform.scale_y = 0.36
117+
```
118+
119+
Creating at factory 1920×1080 then dropping the scene to 96×54 for a check render makes a 0.36-scaled COLOR cell larger than the output. On 5.2 the long-runner covers the frame; pixel samples read the wrong strip.
120+
121+
A lone COLOR strip **does** honor `transform.scale_*` on 5.2. The break is media size vs a later output size, not "transform is ignored."
122+
123+
## Common AI mistakes
124+
125+
1. **`sequence_editor.sequences` on 5.x.** `AttributeError`. Use `.strips` (with `hasattr` only as a 4.5 bridge).
126+
127+
2. **Wrong `new_effect` end kwarg.** `frame_end=` on 5.x and `length=` on 4.5 both raise `TypeError`.
128+
129+
3. **Creating COLOR strips, then changing render size** (the 5.2 trap):
130+
131+
```python
132+
a = coll.new_effect(...) # bakes 1920×1080 on 5.2
133+
scene.render.resolution_x = 96 # output changed; media size did not
134+
```
135+
136+
Set `scene.render.resolution_*` **before** `new_effect`.
137+
138+
4. **`se.strips or se.sequences`.** An empty collection is falsy, so this falls through to `.sequences` on a fresh editor.
139+
140+
5. **`TRANSFORM` effect type on 5.x.** Gone. Use `strip.transform`.
141+
142+
6. **Scene strip pointing at its own scene.** Feedback loop; renders transparent. Source a **separate** scene.
143+
144+
7. **Effect strips consume their inputs** only when stacked on a channel **above** those inputs. Below, the inputs keep compositing independently. A `GAMMA_CROSS` asked to outlast its inputs' overlap is silently clamped to the overlap.
145+
146+
## See also
147+
148+
- Example `examples/vse-cut-list/` — accessor rename, `new_effect` kwargs, save/reload, and the 5.2 COLOR size bake (`--check-pixels` asserts `A.width, A.height == render size` on 5.2+).
149+
- Example `examples/vse-gamma-cross/` — `GAMMA_CROSS` blend curve.
150+
- Audit log: `docs/technical-audit.md` § Findings the release notes did not list.
151+
152+
## References
153+
154+
- `bpy.types.SequenceEditor`: https://docs.blender.org/api/current/bpy.types.SequenceEditor.html
155+
- `bpy.types.Sequence`: https://docs.blender.org/api/current/bpy.types.Sequence.html
156+
- 4.5 LTS `SequenceEditor`: https://docs.blender.org/api/4.5/bpy.types.SequenceEditor.html
157+
- 5.1 `SequenceEditor`: https://docs.blender.org/api/5.1/bpy.types.SequenceEditor.html

0 commit comments

Comments
 (0)