Skip to content

Commit fefd033

Browse files
TMHSDigitalclaude
andauthored
fix(content): correct 4.5 node-group API, Unreal location scale, export-preset apply, version facts (#309)
- shader-node-group snippet and procedural-materials skill: group.inputs/outputs.new were removed in 4.0; use interface.new_socket on every supported version (#286) - export_preset_unreal: scale object locations with scale so spacing grows 100x (#297) - export presets, engine-export-presets skill, pipeline template, rules: one transform_apply call; override selected_editable_objects, not selected_objects (#298) - version-fact fixes: context-dict override removed in 4.0, Specular rename in 4.0, 5.2 LTS is current stable, Extensions primary since 4.2, vse Strip link and hasattr guidance, dangling import comment, add-on template license/maintainer (#287) Closes #286, closes #297, closes #298, closes #287 Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
1 parent 72a7a92 commit fefd033

15 files changed

Lines changed: 93 additions & 83 deletions

File tree

‎rules/prefer-temp-override-over-context-copy.mdc‎

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
description: Flag uses of `bpy.context.copy()` to override context for an operator call. The copy-and-pass pattern was deprecated in Blender 4.x and the override semantics were removed in 5.x. Use `bpy.context.temp_override(**overrides)` as a context manager instead.
2+
description: Flag uses of `bpy.context.copy()` to override context for an operator call. Passing a context dict to an operator was deprecated in Blender 3.2 and removed in 4.0, so it already fails on the 4.5 LTS fallback. Use `bpy.context.temp_override(**overrides)` as a context manager instead.
33
alwaysApply: false
44
globs:
55
- "**/*.py"
@@ -9,15 +9,14 @@ standards-version: 1.10.0
99
# Prefer `temp_override` over `context.copy()`
1010

1111
`bpy.context.copy()` returns a dict snapshot of the current context. In
12-
Blender 4.x and earlier it was common to mutate that dict and pass it as
12+
Blender 3.x and earlier it was common to mutate that dict and pass it as
1313
the first positional argument to an operator (`bpy.ops.x.y(ctx, ...)`)
1414
to run the operator under a fabricated context. That mechanism was
15-
deprecated in 4.x and the operator override semantics were removed in
16-
5.x. The same code on 5.x either silently ignores the override or fails
17-
with a confusing argument-type error.
15+
deprecated in 3.2 and removed in 4.0. The same code on 4.0 and later,
16+
including 4.5 LTS, fails with a confusing argument-type error.
1817

1918
The supported replacement is `bpy.context.temp_override(**overrides)`,
20-
which works as a context manager and applies cleanly on both 4.5 LTS and
19+
which works as a context manager and applies cleanly on 4.5 LTS and
2120
5.x.
2221

2322
## What this rule flags
@@ -49,23 +48,28 @@ bpy.ops.object.delete({
4948
})
5049
```
5150

52-
Both forms run on 4.x with deprecation warnings. On 5.x they either no-op
53-
the override silently or raise `TypeError: Calling operator "bpy.ops.object.delete"
51+
Both forms ran with deprecation warnings on 3.2 to 3.6. On 4.0 and later
52+
(4.5 LTS, 5.x) they raise `TypeError: Calling operator "bpy.ops.object.delete"
5453
error, expected a string enum, not a dict`.
5554

5655
## Right
5756

5857
```python
5958
import bpy
6059

61-
with bpy.context.temp_override(active_object=my_obj, selected_objects=[my_obj]):
60+
with bpy.context.temp_override(active_object=my_obj, selected_editable_objects=[my_obj]):
6261
bpy.ops.object.delete()
6362
```
6463

6564
`temp_override` accepts the same keyword names as the keys you would
6665
have put in the dict. It restores the previous context on exit, even
67-
on exception. It is supported on 4.2+ and is the only override path
68-
on 5.x.
66+
on exception. It is supported on 3.2+ and is the only override path
67+
on 4.0+.
68+
69+
Override the key the operator actually reads. `transform_apply` and
70+
similar operators act on `selected_editable_objects`, so
71+
`selected_objects=[obj]` alone does not narrow them to one object; use
72+
`selected_editable_objects=[obj]`.
6973

7074
## When you also need a window override
7175

@@ -84,19 +88,18 @@ with bpy.context.temp_override(window=window, area=area, region=region):
8488

8589
## Why it matters
8690

87-
The deprecated path silently drops the override on 5.x in many cases.
88-
Code that "worked" on 4.x produces wrong results on 5.x without an
89-
obvious failure. The user reports "my add-on broke after upgrade" and
90-
the cause is a context override that is now ignored.
91+
The dict-passing path was removed in 4.0, so code that "worked" on 3.x
92+
raises on 4.5 LTS and 5.x. The user reports "my add-on broke after
93+
upgrade" and the cause is a context override that no longer exists.
9194

9295
`temp_override` is the canonical, supported, and forward-compatible
93-
replacement. There is no scenario in 5.x where the dict-passing form is
96+
replacement. There is no scenario on 4.0+ where the dict-passing form is
9497
preferable.
9598

9699
## Related
97100

98101
- Skill `operators`
99102
- Snippet `temp-override-context.py`
100103
- `bpy.types.Context.temp_override`: https://docs.blender.org/api/current/bpy.types.Context.html#bpy.types.Context.temp_override
101-
- Blender 4.0 release notes (deprecation): https://developer.blender.org/docs/release_notes/4.0/python_api/
102-
- Blender 5.0 release notes (removal): https://developer.blender.org/docs/release_notes/5.0/python_api/
104+
- Blender 3.2 release notes (deprecation): https://developer.blender.org/docs/release_notes/3.2/python_api/
105+
- Blender 4.0 release notes (removal): https://developer.blender.org/docs/release_notes/4.0/python_api/

‎rules/validate-imported-mesh-scale.mdc‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ for obj in bpy.context.selected_objects:
6969
sx, sy, sz = obj.scale
7070
if abs(sx - 1.0) > 1e-6 or abs(sy - 1.0) > 1e-6 or abs(sz - 1.0) > 1e-6:
7171
with bpy.context.temp_override(
72-
object=obj, active_object=obj, selected_objects=[obj]
72+
object=obj, active_object=obj, selected_editable_objects=[obj]
7373
):
7474
bpy.ops.object.transform_apply(
7575
location=False, rotation=True, scale=True

‎skills/addon-scaffolding/SKILL.md‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ Use this skill when the user:
1818
## Required inputs
1919

2020
- **Add-on name and short id** (snake_case for the manifest `id`, human-readable `name`)
21-
- **Target Blender versions** (defaults to `blender_version_min = "4.5.0"` so the add-on works on 4.5 LTS and 5.1)
21+
- **Target Blender versions** (defaults to `blender_version_min = "4.5.0"` so the add-on works on 4.5 LTS, 5.1 and 5.2 LTS)
2222
- **Maintainer string** (`Name <email@example.com>` form)
2323
- **License SPDX identifier** (`SPDX:MIT`, `SPDX:GPL-2.0-or-later`, etc.)
2424

@@ -35,12 +35,12 @@ id = "my_addon"
3535
version = "0.1.0"
3636
name = "My Add-on"
3737
tagline = "Short description, max 64 chars, no trailing period"
38-
maintainer = "TMHSDigital <contact@example.com>"
38+
maintainer = "Your Name <you@your-domain.example>"
3939
type = "add-on"
4040

4141
blender_version_min = "4.5.0"
4242

43-
license = ["SPDX:MIT"]
43+
license = ["SPDX:GPL-3.0-or-later"]
4444
copyright = ["2026 TMHSDigital"]
4545
```
4646

@@ -199,7 +199,7 @@ def unregister():
199199

200200
## Compatibility paths
201201

202-
For libraries you want to be installable on both 4.5 LTS and 5.1, also keep `bl_info` for 4.5 fallback even though new submissions to the extensions platform require the manifest. Blender prefers the manifest when both are present.
202+
For libraries you want to be installable on both 4.5 LTS and 5.2 LTS, also keep `bl_info` for 4.5 fallback even though new submissions to the extensions platform require the manifest. Blender prefers the manifest when both are present.
203203

204204
```python
205205
bl_info = {

‎skills/bl-info-migration/SKILL.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ Use this skill when the user:
2020

2121
Yes, with caveats.
2222

23-
- **In 5.1 stable**: `bl_info` is supported as a fallback for legacy add-ons. The Edit > Preferences > Add-ons panel still has "Install legacy Add-on" and recognizes `bl_info` dicts.
23+
- **In 5.2 LTS (current stable; 5.1 is prior stable)**: `bl_info` is supported as a fallback for legacy add-ons. The Edit > Preferences > Add-ons panel still has "Install legacy Add-on" and recognizes `bl_info` dicts.
2424
- **For the Extensions Platform** (extensions.blender.org and the new add-ons UI): you need a `blender_manifest.toml`. `bl_info` is ignored.
2525
- **Dual format** is officially recognized: ship a `blender_manifest.toml` and keep the `bl_info` dict. The platform reads the manifest; legacy installers read `bl_info`. Both code paths work without conditional logic.
2626

@@ -220,9 +220,9 @@ This produces a `.zip` in `dist\` that the user installs via Edit > Preferences
220220

221221
## Version correctness
222222

223-
| Topic | 4.5 LTS | 5.1 stable |
223+
| Topic | 4.5 LTS | 5.1 / 5.2 LTS |
224224
| --- | --- | --- |
225-
| `bl_info` recognized | Yes (primary path) | Yes (legacy fallback only) |
225+
| `bl_info` recognized | Yes (legacy; Extensions is the primary path since 4.2) | Yes (legacy fallback only) |
226226
| `blender_manifest.toml` recognized | Yes (Extensions Platform was added in 4.2) | Yes |
227227
| Synthetic package name | `bl_ext.<repo>.<id>` | Same |
228228
| `extension build` command | Available | Available |

‎skills/drivers-and-app-handlers/SKILL.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ The `exit_pre` handler list is new in Blender 5.1. On 4.5 LTS, fall back to OS-l
269269

270270
## Version correctness
271271

272-
| Topic | 4.5 LTS | 5.1 stable |
272+
| Topic | 4.5 LTS | 5.1 / 5.2 LTS |
273273
| --- | --- | --- |
274274
| `exit_pre` handler | Not available | New in 5.1; use `atexit` fallback for 4.x |
275275
| `save_pre` / `save_post` signature | `(filepath)` — a string | `(filepath)` — a string (unchanged) |

‎skills/engine-export-presets/SKILL.md‎

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,16 @@ Before any preset: meters in the scene (`scale_length == 1.0`), identity object
3232

3333
```python
3434
def apply_selected_mesh_transforms():
35-
for obj in list(bpy.context.selected_objects):
36-
if obj.type != "MESH":
37-
continue
38-
with bpy.context.temp_override(
39-
object=obj, active_object=obj, selected_objects=[obj]
40-
):
41-
bpy.ops.object.transform_apply(
42-
location=False, rotation=True, scale=True
43-
)
35+
# One operator call for the whole selection. transform_apply reads
36+
# selected_editable_objects, so that is the key to override; overriding
37+
# selected_objects alone does not narrow it.
38+
meshes = [o for o in bpy.context.selected_objects if o.type == "MESH"]
39+
if not meshes:
40+
return
41+
with bpy.context.temp_override(
42+
object=meshes[0], active_object=meshes[0], selected_editable_objects=meshes
43+
):
44+
bpy.ops.object.transform_apply(location=False, rotation=True, scale=True)
4445
```
4546

4647
`use_selection=True` on every preset. Draco is opt-in on glTF; do not copy `gltf_draco_export.py` wholesale.

‎skills/procedural-materials-and-shaders/SKILL.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,10 +158,10 @@ When the same subgraph appears across multiple materials, factor it into a `Shad
158158

159159
## Version correctness
160160

161-
| Topic | 4.5 LTS | 5.1 stable | Notes |
161+
| Topic | 4.5 LTS | 5.1 / 5.2 LTS | Notes |
162162
| --- | --- | --- | --- |
163-
| Principled BSDF | `ShaderNodeBsdfPrincipled` | Same | Some inputs renamed in 5.0; `Specular` -> `Specular IOR Level`. Use string lookup with the 5.x name. |
164-
| Node group socket interface | `group.inputs.new` / `group.outputs.new` | `group.interface.new_socket` | Different APIs, see snippet `shader-node-group.py`. |
163+
| Principled BSDF | `ShaderNodeBsdfPrincipled` | Same | Inputs were renamed in 4.0 with the Principled v2 rewrite: `Specular` -> `Specular IOR Level` (see the 4.0 release notes, https://developer.blender.org/docs/release_notes/4.0/). Use string lookup with the 4.0+ name. |
164+
| Node group socket interface | `group.interface.new_socket` | `group.interface.new_socket` | Same API on every supported version. `group.inputs.new` / `outputs.new` were removed in 4.0; see snippet `shader-node-group.py`. |
165165
| 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. |
166166
| Layered Textures | Not present | Not present in 5.1 | Roadmap pushed to 2027. Do not generate code referencing it. |
167167

‎skills/vse-python/SKILL.md‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ else:
3838
strip.color = (0.85, 0.10, 0.22)
3939
```
4040

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`.
41+
Branch on the `bpy.app.version` tuple for known API boundaries (such as the 5.2 COLOR strip `width`/`height` bake), never on `bpy.app.version_string`. Pick the `.strips` vs `.sequences` accessor with `hasattr`, not version: an empty `bpy_prop_collection` is falsy, so `se.strips or se.sequences` silently falls through to the legacy accessor on an empty timeline.
4242

4343
## Accessor: `.strips` vs `.sequences`
4444

@@ -152,6 +152,7 @@ A lone COLOR strip **does** honor `transform.scale_*` on 5.2. The break is media
152152
## References
153153

154154
- `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
155+
- `bpy.types.Strip` (5.x name of `Sequence`): https://docs.blender.org/api/current/bpy.types.Strip.html
156+
- 4.5 LTS `Sequence`: https://docs.blender.org/api/4.5/bpy.types.Sequence.html
156157
- 4.5 LTS `SequenceEditor`: https://docs.blender.org/api/4.5/bpy.types.SequenceEditor.html
157158
- 5.1 `SequenceEditor`: https://docs.blender.org/api/5.1/bpy.types.SequenceEditor.html

‎snippets/action-ensure-channelbag-for-slot.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
# action.fcurves API is still present, so use it directly.
1010
#
1111
# Verified on Blender 4.5.10 LTS and 5.1.1. Import path (5.0+):
12+
# from bpy_extras import anim_utils
1213
#
1314
# Reference:
1415
# https://docs.blender.org/api/current/bpy_extras.anim_utils.html

‎snippets/export_preset_godot.py‎

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,16 @@
1515

1616

1717
def apply_selected_mesh_transforms():
18-
for obj in list(bpy.context.selected_objects):
19-
if obj.type != "MESH":
20-
continue
21-
with bpy.context.temp_override(
22-
object=obj, active_object=obj, selected_objects=[obj]
23-
):
24-
bpy.ops.object.transform_apply(
25-
location=False, rotation=True, scale=True
26-
)
18+
# One operator call for the whole selection. transform_apply reads
19+
# selected_editable_objects, so that is the key to override; overriding
20+
# selected_objects alone does not narrow it.
21+
meshes = [o for o in bpy.context.selected_objects if o.type == "MESH"]
22+
if not meshes:
23+
return
24+
with bpy.context.temp_override(
25+
object=meshes[0], active_object=meshes[0], selected_editable_objects=meshes
26+
):
27+
bpy.ops.object.transform_apply(location=False, rotation=True, scale=True)
2728

2829

2930
def export_preset_godot(filepath, selected_only=True, draco=False):

0 commit comments

Comments
 (0)