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
10 changes: 6 additions & 4 deletions rules/use-foreach-set-for-bulk-data.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,12 @@ Same 100,000 vertices: under a millisecond.

The signature is `foreach_set(attribute, sequence)` where:

- `attribute` is the name of the per-element attribute as a string
(`"co"`, `"normal"`, `"index"`, `"use_smooth"`, etc.).
- `attribute` is the name of a **writable** per-element attribute as a
string (`"co"`, `"use_smooth"`, `"material_index"`, etc.). Read-only
attributes such as vertex `"normal"` and `"index"` accept `foreach_set`
without error and change nothing (measured on 5.2.1 LTS): vertex normals
are derived, so read them with `foreach_get` (or `mesh.vertex_normals`)
and write custom normals with `mesh.normals_split_custom_set(...)`.
- `sequence` is a flat 1D iterable of `len(elements) * components`
values. For vector attributes (3 floats per vertex), the buffer is
3x as long as the element count.
Expand All @@ -85,8 +89,6 @@ import numpy as np

n = len(mesh.vertices)
coords = np.empty(n * 3, dtype=np.float32)
mesh.vertices.foreach_get("co", coords)

mesh.vertices.foreach_get("co", coords)
xs = coords[0::3]
ys = coords[1::3]
Expand Down
15 changes: 9 additions & 6 deletions skills/drivers-and-app-handlers/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,21 +210,24 @@ from bpy.app.handlers import persistent

@persistent
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.
# save_pre runs before the write, so the new count lands in the file being
# saved (a save_post write would only reach the next save). It receives the
# target path. ID-property keys are limited to 63 characters, so key by the
# file name, never the full path, which raises KeyError when longer.
scene = bpy.context.scene
key = bpy.path.basename(filepath)[:63] or "untitled"
counts = scene.get('save_counts', {})
counts[filepath] = counts.get(filepath, 0) + 1
counts[key] = counts.get(key, 0) + 1
scene['save_counts'] = counts


def register():
bpy.app.handlers.save_post.append(increment_save_count)
bpy.app.handlers.save_pre.append(increment_save_count)


def unregister():
if increment_save_count in bpy.app.handlers.save_post:
bpy.app.handlers.save_post.remove(increment_save_count)
if increment_save_count in bpy.app.handlers.save_pre:
bpy.app.handlers.save_pre.remove(increment_save_count)
```

### Worked example: cleanup on exit (5.1+)
Expand Down
4 changes: 3 additions & 1 deletion skills/engine-export-presets/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ def apply_selected_mesh_transforms():
```python
bpy.ops.export_scene.gltf(
filepath=path,
export_format="GLB",
use_selection=True,
export_yup=True,
export_apply=True,
Expand All @@ -68,6 +69,7 @@ Snippet: [`snippets/export_preset_unity.py`](https://github.com/TMHSDigital/Blen
```python
bpy.ops.export_scene.gltf(
filepath=path,
export_format="GLB",
use_selection=True,
export_yup=False,
export_apply=True,
Expand Down Expand Up @@ -115,7 +117,7 @@ Snippet: [`snippets/export_preset_unreal.py`](https://github.com/TMHSDigital/Ble

Probed on 4.5 LTS, 5.1, and 5.2: `export_yup` on glTF and `axis_forward` / `axis_up` / `global_scale` on FBX are present on all three. No version branch for the axis kwargs. Guard by requiring those names in operator RNA so a future rename fails loudly, as [`examples/gltf-export-roundtrip/`](https://github.com/TMHSDigital/Blender-Developer-Tools/tree/main/examples/gltf-export-roundtrip) does.

`export_format` defaults differ by call site; pass the filepath suffix (`.glb` / `.gltf` / `.fbx`) and let the operator infer, or set `export_format` explicitly on glTF.
Always pass `export_format` on glTF and make the filepath suffix match it. The operator does **not** infer the format from the suffix: `export_scene.gltf(filepath="x.gltf")` with no `export_format` writes a binary `x.glb`, so the requested path never exists (measured on 4.5.11 and 5.2.1). Use `export_format="GLB"` with `.glb`, or `"GLTF_SEPARATE"` with `.gltf`.

## Related

Expand Down
8 changes: 5 additions & 3 deletions skills/geometry-nodes-python/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,10 @@ To list all available Geometry node types in your Blender version:

```python
import bpy
for cls in bpy.types.GeometryNode.__subclasses__():
print(cls.bl_idname)
# bpy.types registers node classes lazily, so GeometryNode.__subclasses__()
# returns [] until something touches them. dir(bpy.types) lists them all.
for name in sorted(n for n in dir(bpy.types) if n.startswith("GeometryNode")):
print(name)
```

## Wiring nodes
Expand Down Expand Up @@ -323,7 +325,7 @@ def has_for_each_element():
tree.nodes.new('GeometryNodeSetPosition') # RIGHT
```

When in doubt, list `bpy.types.GeometryNode.__subclasses__()` and grep.
When in doubt, list `[n for n in dir(bpy.types) if n.startswith("GeometryNode")]` and grep. (`bpy.types.GeometryNode.__subclasses__()` returns an empty list: the classes are created lazily.)

3. **Linking nodes by name when both inputs share a name** (`Value`, `Vector`, `Geometry`) and getting the wrong one. Use indices or the named inputs of the parent node.

Expand Down
1 change: 1 addition & 0 deletions snippets/export_preset_godot.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ def export_preset_godot(filepath, selected_only=True, draco=False):
apply_selected_mesh_transforms()
bpy.ops.export_scene.gltf(
filepath=filepath,
export_format="GLB",
use_selection=selected_only,
export_yup=False,
export_apply=True,
Expand Down
1 change: 1 addition & 0 deletions snippets/export_preset_unity.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ def export_preset_unity(filepath, selected_only=True, draco=False):
apply_selected_mesh_transforms()
bpy.ops.export_scene.gltf(
filepath=filepath,
export_format="GLB",
use_selection=selected_only,
export_yup=True,
export_apply=True,
Expand Down
1 change: 1 addition & 0 deletions snippets/export_preset_unreal.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ def export_preset_unreal_gltf(filepath, selected_only=True, draco=False):
apply_selected_mesh_transforms()
bpy.ops.export_scene.gltf(
filepath=filepath,
export_format="GLB",
use_selection=selected_only,
export_yup=True,
export_apply=True,
Expand Down
1 change: 1 addition & 0 deletions snippets/gltf_draco_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
def export_gltf_draco(filepath, selected_only=True, yup=True):
bpy.ops.export_scene.gltf(
filepath=filepath,
export_format="GLB",
use_selection=selected_only,
export_draco_mesh_compression_enable=True,
export_draco_mesh_compression_level=6,
Expand Down
3 changes: 3 additions & 0 deletions templates/ai-asset-pipeline-template/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ def export_preset(filepath, preset, draco):
if preset == "unity":
bpy.ops.export_scene.gltf(
filepath=filepath,
export_format="GLB",
use_selection=True,
export_yup=True,
export_apply=True,
Expand All @@ -270,6 +271,7 @@ def export_preset(filepath, preset, draco):
if preset == "godot":
bpy.ops.export_scene.gltf(
filepath=filepath,
export_format="GLB",
use_selection=True,
export_yup=False,
export_apply=True,
Expand All @@ -296,6 +298,7 @@ def export_preset(filepath, preset, draco):
apply_selected_mesh_transforms()
bpy.ops.export_scene.gltf(
filepath=filepath,
export_format="GLB",
use_selection=True,
export_yup=True,
export_apply=True,
Expand Down
Loading