From a8f82e033d28934bd5ce230e24f44f817e1fcdb6 Mon Sep 17 00:00:00 2001 From: fOuttaMyPaint Date: Fri, 3 Jul 2026 17:57:09 -0400 Subject: [PATCH] feat: add temp-override-join and gn-instance-grid smoke-gated examples Witness the temp_override context path and generative GN Instance-on-Points topology with closed-form checks on 4.4 and 5.1. Co-authored-by: Cursor --- .cursor-plugin/plugin.json | 2 + .github/workflows/blender-smoke.yml | 18 + README.md | 28 ++ .../gallery/assets/gn-instance-grid-hero.webp | Bin 0 -> 18634 bytes .../assets/temp-override-join-hero.webp | Bin 0 -> 19334 bytes docs/gallery/gn-instance-grid/index.html | 469 ++++++++++++++++++ docs/gallery/index.html | 25 + docs/gallery/temp-override-join/index.html | 460 +++++++++++++++++ examples/gallery.json | 18 + examples/gn-instance-grid/README.md | 27 + examples/gn-instance-grid/gn_instance_grid.py | 227 +++++++++ examples/gn-instance-grid/preview.webp | Bin 0 -> 16138 bytes examples/temp-override-join/README.md | 28 ++ examples/temp-override-join/preview.webp | Bin 0 -> 15730 bytes .../temp-override-join/temp_override_join.py | 218 ++++++++ 15 files changed, 1520 insertions(+) create mode 100644 docs/gallery/assets/gn-instance-grid-hero.webp create mode 100644 docs/gallery/assets/temp-override-join-hero.webp create mode 100644 docs/gallery/gn-instance-grid/index.html create mode 100644 docs/gallery/temp-override-join/index.html create mode 100644 examples/gn-instance-grid/README.md create mode 100644 examples/gn-instance-grid/gn_instance_grid.py create mode 100644 examples/gn-instance-grid/preview.webp create mode 100644 examples/temp-override-join/README.md create mode 100644 examples/temp-override-join/preview.webp create mode 100644 examples/temp-override-join/temp_override_join.py diff --git a/.cursor-plugin/plugin.json b/.cursor-plugin/plugin.json index 6feaef5..08bf226 100644 --- a/.cursor-plugin/plugin.json +++ b/.cursor-plugin/plugin.json @@ -62,9 +62,11 @@ "examples/bmesh-gear", "examples/depsgraph-export", "examples/driver-wave", + "examples/gn-instance-grid", "examples/gn-sdf-remesh", "examples/shader-node-group", "examples/swatch-grid", + "examples/temp-override-join", "examples/turntable", "examples/wave-displace" ] diff --git a/.github/workflows/blender-smoke.yml b/.github/workflows/blender-smoke.yml index daf04a8..a3d0c97 100644 --- a/.github/workflows/blender-smoke.yml +++ b/.github/workflows/blender-smoke.yml @@ -194,3 +194,21 @@ jobs: # Tint values differ. Exits non-zero on failure. xvfb-run -a "$BLENDER" --background \ --python examples/shader-node-group/shader_node_group.py -- + + - name: Shipped example - temp-override join (context override) + run: | + set -euo pipefail + # Frame-independent check only (no render): three cubes joined under + # bpy.context.temp_override; asserts one mesh remains, sources are gone, + # and topology is verts=24 faces=18. Exits non-zero on failure. + xvfb-run -a "$BLENDER" --background \ + --python examples/temp-override-join/temp_override_join.py -- + + - name: Shipped example - GN instance grid (Instance on Points) + run: | + set -euo pipefail + # Frame-independent check only (no render): generative GN tree instances a + # cube on a 3x3 grid and realizes; asserts eval verts=72 faces=54 and Set + # Material carries Lime. Exits non-zero on failure. + xvfb-run -a "$BLENDER" --background \ + --python examples/gn-instance-grid/gn_instance_grid.py -- diff --git a/README.md b/README.md index 67f1f87..0c94789 100644 --- a/README.md +++ b/README.md @@ -161,6 +161,34 @@ One reusable `TintedGloss` group declared via `tree.interface.new_socket`, insta materials with different Tint values. Witnesses the grouping contract: shared datablock (`users == 2`), parameters on the group **node** — two spheres, one group, two colors. + + + + +Temp-override join: an amber L-shaped mesh made of three joined unit cubes on a dark studio floor, viewed into the open corner + + + +### [temp-override-join](examples/temp-override-join/) + +Three unit cubes joined into one L-shaped mesh under `bpy.context.temp_override` — the +supported replacement for the removed `context.copy()` dict-pass form. Asserts one mesh +remains, sources are gone, and topology is verts = 8 × blocks, faces = 6 × blocks. + + + + + +GN instance grid: nine lime-green cubes in a 3x3 grid on a dark studio floor, instanced via Geometry Nodes Instance on Points + + + +### [gn-instance-grid](examples/gn-instance-grid/) + +A generative Geometry Nodes tree — Mesh Grid → Instance on Points → Realize Instances — +attached as a `NODES` modifier with no Group Input. Asserts evaluated topology is +verts = 72, faces = 54, and `Set Material` carries the lime accent. + diff --git a/docs/gallery/assets/gn-instance-grid-hero.webp b/docs/gallery/assets/gn-instance-grid-hero.webp new file mode 100644 index 0000000000000000000000000000000000000000..e0e97fae7a06bbbdd13462526c6a94fe67b8a9c4 GIT binary patch literal 18634 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 F005}7&=>#! literal 0 HcmV?d00001 diff --git a/docs/gallery/gn-instance-grid/index.html b/docs/gallery/gn-instance-grid/index.html new file mode 100644 index 0000000..703c67e --- /dev/null +++ b/docs/gallery/gn-instance-grid/index.html @@ -0,0 +1,469 @@ + + + + + + gn-instance-grid — Examples — Blender Developer Tools + + + + + + + + + + + + + + + + + +

+
+

gn-instance-grid

+

A generative Geometry Nodes tree — Mesh Grid → Instance on Points → Realize Instances — attached as a NODES modifier with no Group Input geometry.

+
+
+ +

Rendered headless by the example itself — click to zoom.

+
witnesses Realized instances produce closed-form topology: eval verts = grid points × cube verts (3 × 3 × 8 = 72), and Set Material carries the accent onto the evaluated mesh.
+
+
blender --background --python examples/gn-instance-grid/gn_instance_grid.py --
+ +
+
+

A runnable example that builds a generative Geometry Nodes tree — Mesh Grid → Instance on Points → Realize Instances → Transform — and attaches it as a NODES modifier, following the geometry-nodes-python skill. The tree has no Group Input: the grid and cube primitives live inside the node group.

+

What it witnesses: instancing is not free geometry until you realize it. The check asserts the closed-form evaluated topology — verts = grid points × cube verts (3 × 3 × 8 = 72), faces = 9 × 6 — and that a Set Material node carries the lime accent onto the evaluated mesh. If Realize Instances is omitted, the evaluated mesh is empty and the counts fail.

+

Run

+
# Cheap correctness check (no render) — the CI check:
+blender --background --python gn_instance_grid.py --
+
+# Also render a still (EEVEE on a GPU host; use --engine cycles on GPU-less hosts):
+blender --background --python gn_instance_grid.py -- --output grid.png
+blender --background --python gn_instance_grid.py -- --output grid.png --engine cycles
+

It exits non-zero on failure (wrong carrier, topology mismatch, or missing material). The blender-smoke workflow runs the check on Blender 4.5 LTS and 5.1.

+
+
+

Source

+
+ examples/gn-instance-grid/gn_instance_grid.py + View on GitHub → +
+
"""Geometry Nodes Instance-on-Points grid — a runnable example.
+
+Witnesses the geometry-nodes-python construction contract for instancing:
+a generative GeometryNodeTree (Mesh Grid → Instance on Points → Realize
+Instances → Transform) attached as a NODES modifier, with no Group Input
+geometry. The check asserts the closed-form evaluated topology —
+verts = grid_points × cube_verts — proving instances were realized, not
+left as empty instance geometry.
+
+By default it runs only the correctness check (no render) — the CI smoke
+check. Pass --output to also render a still:
+
+    blender --background --python gn_instance_grid.py --                 # check only
+    blender --background --python gn_instance_grid.py -- --output g.png  # + render
+"""
+import bpy, bmesh, sys, os, math, argparse
+
+GRID_X = 3
+GRID_Y = 3
+GRID_SIZE = 2.0
+CUBE_SIZE = 0.35
+CUBE_VERTS = 8
+CUBE_FACES = 6
+GRID_POINTS = GRID_X * GRID_Y
+EXPECT_VERTS = GRID_POINTS * CUBE_VERTS
+EXPECT_FACES = GRID_POINTS * CUBE_FACES
+
+
+def build_instance_grid_tree(material=None):
+    tree = bpy.data.node_groups.new("InstanceGrid", 'GeometryNodeTree')
+    # generative: no Group Input — the tree owns the geometry
+    tree.interface.new_socket(
+        name="Geometry", in_out='OUTPUT', socket_type='NodeSocketGeometry',
+    )
+    go = tree.nodes.new('NodeGroupOutput')
+
+    grid = tree.nodes.new('GeometryNodeMeshGrid')
+    grid.inputs["Size X"].default_value = GRID_SIZE
+    grid.inputs["Size Y"].default_value = GRID_SIZE
+    grid.inputs["Vertices X"].default_value = GRID_X
+    grid.inputs["Vertices Y"].default_value = GRID_Y
+
+    cube = tree.nodes.new('GeometryNodeMeshCube')
+    cube.inputs["Size"].default_value = (CUBE_SIZE, CUBE_SIZE, CUBE_SIZE)
+
+    iop = tree.nodes.new('GeometryNodeInstanceOnPoints')
+    realize = tree.nodes.new('GeometryNodeRealizeInstances')
+    xform = tree.nodes.new('GeometryNodeTransform')
+    # cubes are centered on grid points at z=0; lift so they rest on the floor
+    xform.inputs["Translation"].default_value = (0.0, 0.0, CUBE_SIZE / 2)
+
+    tree.links.new(grid.outputs["Mesh"], iop.inputs["Points"])
+    tree.links.new(cube.outputs["Mesh"], iop.inputs["Instance"])
+    tree.links.new(iop.outputs["Instances"], realize.inputs["Geometry"])
+    tree.links.new(realize.outputs["Geometry"], xform.inputs["Geometry"])
+    out_socket = xform.outputs["Geometry"]
+
+    if material is not None:
+        set_mat = tree.nodes.new('GeometryNodeSetMaterial')
+        set_mat.inputs["Material"].default_value = material
+        tree.links.new(out_socket, set_mat.inputs["Geometry"])
+        out_socket = set_mat.outputs["Geometry"]
+
+    tree.links.new(out_socket, go.inputs["Geometry"])
+    return tree
+
+
+def build():
+    bpy.ops.wm.read_factory_settings(use_empty=True)
+    # carrier mesh is unused by the generative tree; one vertex is enough
+    me = bpy.data.meshes.new("Carrier")
+    me.vertices.add(1)
+    obj = bpy.data.objects.new("InstanceGrid", me)
+    bpy.context.collection.objects.link(obj)
+
+    mat = bpy.data.materials.new("Lime")
+    mat.use_nodes = True
+    bsdf = mat.node_tree.nodes["Principled BSDF"]
+    bsdf.inputs["Base Color"].default_value = (0.28, 0.92, 0.08, 1.0)  # lime
+    bsdf.inputs["Roughness"].default_value = 0.24
+
+    tree = build_instance_grid_tree(material=mat)
+    mod = obj.modifiers.new("instance_grid", 'NODES')
+    mod.node_group = tree
+    return obj, mat
+
+
+def check(obj):
+    base = len(obj.data.vertices)
+    if base != 1:
+        print(f"ERROR: carrier should have 1 vertex, got {base}", file=sys.stderr)
+        return 3
+
+    dg = bpy.context.evaluated_depsgraph_get()
+    ev = obj.evaluated_get(dg)
+    em = ev.to_mesh()
+    try:
+        got_v = len(em.vertices)
+        got_f = len(em.polygons)
+        mat_names = [m.name for m in em.materials if m is not None]
+    finally:
+        ev.to_mesh_clear()
+
+    if got_v != EXPECT_VERTS or got_f != EXPECT_FACES:
+        print(f"ERROR: evaluated topology verts={got_v} faces={got_f} != "
+              f"expected verts={EXPECT_VERTS} faces={EXPECT_FACES}",
+              file=sys.stderr)
+        return 4
+
+    if "Lime" not in mat_names:
+        print(f"ERROR: Set Material did not carry Lime onto evaluated mesh "
+              f"(materials={mat_names})", file=sys.stderr)
+        return 5
+
+    print(f"grid={GRID_X}x{GRID_Y} points={GRID_POINTS} "
+          f"eval_verts={got_v} eval_faces={got_f} material=Lime")
+    return 0
+
+
+def eevee_engine_id():
+    return 'BLENDER_EEVEE' if bpy.app.version >= (5, 0, 0) else 'BLENDER_EEVEE_NEXT'
+
+
+def render_still(obj, path, engine):
+    scene = bpy.context.scene
+
+    floor_me = bpy.data.meshes.new("Floor")
+    bm = bmesh.new()
+    try:
+        bmesh.ops.create_grid(bm, x_segments=1, y_segments=1, size=30.0)
+        bm.to_mesh(floor_me)
+    finally:
+        bm.free()
+    fmat = bpy.data.materials.new("Studio")
+    fmat.use_nodes = True
+    fb = fmat.node_tree.nodes["Principled BSDF"]
+    fb.inputs["Base Color"].default_value = (0.055, 0.06, 0.07, 1.0)
+    fb.inputs["Roughness"].default_value = 0.5
+    floor_me.materials.append(fmat)
+    floor = bpy.data.objects.new("Floor", floor_me)
+    scene.collection.objects.link(floor)
+    wall = bpy.data.objects.new("Wall", floor_me.copy())
+    wall.location = (0.0, 9.0, 0.0)
+    wall.rotation_euler = (math.radians(90), 0.0, 0.0)
+    scene.collection.objects.link(wall)
+
+    world = bpy.data.worlds.new("World")
+    world.use_nodes = True
+    world.node_tree.nodes["Background"].inputs["Color"].default_value = (0.008, 0.009, 0.012, 1.0)
+    scene.world = world
+
+    def light(name, loc, energy, size, col, rot):
+        ld = bpy.data.lights.new(name, 'AREA')
+        ld.energy = energy
+        ld.size = size
+        ld.color = col
+        ob = bpy.data.objects.new(name, ld)
+        ob.location = loc
+        ob.rotation_euler = tuple(math.radians(a) for a in rot)
+        scene.collection.objects.link(ob)
+
+    light("Key", (-3.5, -4.5, 5.5), 1400.0, 6.0, (1.0, 0.98, 0.94), (48, 0, -35))
+    light("Fill", (5.0, -3.5, 2.5), 320.0, 8.0, (0.8, 0.87, 1.0), (65, 0, 50))
+    light("Rim", (1.5, 4.5, 2.0), 420.0, 4.0, (1.0, 0.75, 0.45), (-82, 0, 165))
+
+    # tip the grid so depth reads; cubes already rest on the floor via the tree
+    obj.rotation_euler = (0.0, 0.0, math.radians(22))
+
+    aim = bpy.data.objects.new("Aim", None)
+    aim.location = (0.0, 0.0, CUBE_SIZE / 2)
+    scene.collection.objects.link(aim)
+    cam_data = bpy.data.cameras.new("Cam")
+    cam_data.lens = 55.0
+    cam = bpy.data.objects.new("Cam", cam_data)
+    # three-quarter view so the 3x3 grid reads as depth, not a flat array
+    cam.location = (3.2, -3.8, 2.6)
+    scene.collection.objects.link(cam)
+    scene.camera = cam
+    track = cam.constraints.new('TRACK_TO')
+    track.target = aim
+    track.track_axis = 'TRACK_NEGATIVE_Z'
+    track.up_axis = 'UP_Y'
+
+    scene.render.engine = 'CYCLES' if engine == 'cycles' else eevee_engine_id()
+    if engine == 'cycles':
+        scene.cycles.samples = 32
+    else:
+        try:
+            scene.eevee.taa_render_samples = 64
+        except AttributeError:
+            pass
+    scene.render.resolution_x = 1280
+    scene.render.resolution_y = 720
+    scene.render.image_settings.file_format = 'PNG'
+    scene.render.filepath = path
+    bpy.ops.render.render(write_still=True)
+    return os.path.exists(path) and os.path.getsize(path) > 0
+
+
+def main():
+    argv = sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else []
+    p = argparse.ArgumentParser()
+    p.add_argument("--output", default=None, help="optional: render a still PNG here")
+    p.add_argument("--engine", default="eevee", choices=("eevee", "cycles"),
+                   help="render engine for --output (cycles for GPU-less hosts)")
+    args = p.parse_args(argv)
+
+    obj, _mat = build()
+    code = check(obj)
+    if code:
+        return code
+
+    if args.output:
+        if not render_still(obj, os.path.abspath(args.output), args.engine):
+            print("ERROR: render produced no file", file=sys.stderr)
+            return 6
+        print(f"rendered still {args.output}")
+
+    print("gn-instance-grid OK")
+    return 0
+
+
+if __name__ == "__main__":
+    try:
+        sys.exit(main())
+    except Exception as e:
+        import traceback; traceback.print_exc(); print(f"FATAL: {e}", file=sys.stderr); sys.exit(1)
+
+
+
+ +
+
+ generated from examples/gallery.json + CC-BY-NC-ND-4.0 + exit 0 +
+
+ + + diff --git a/docs/gallery/index.html b/docs/gallery/index.html index f171e6c..66cdc57 100644 --- a/docs/gallery/index.html +++ b/docs/gallery/index.html @@ -176,13 +176,16 @@

Examples Gallery

+ + + @@ -276,6 +279,28 @@

shader-node-group

View example +
+ + temp-override-join — Join three unit cubes into one L-shaped mesh under bpy + +
+

temp-override-join

+

Join three unit cubes into one L-shaped mesh under bpy.context.temp_override — the supported replacement for the removed context.copy() dict-pass form.

+

witnesses temp_override actually applies: join consumes the sources, exactly one mesh remains, and topology is verts = 8 × blocks, faces = 6 × blocks.

+ View example +
+
+
+ + gn-instance-grid — A generative Geometry Nodes tree — Mesh Grid → Instance on Points → Realize Instances — attached as a NODES modifier with no Group Input geometry + +
+

gn-instance-grid

+

A generative Geometry Nodes tree — Mesh Grid → Instance on Points → Realize Instances — attached as a NODES modifier with no Group Input geometry.

+

witnesses Realized instances produce closed-form topology: eval verts = grid points × cube verts (3 × 3 × 8 = 72), and Set Material carries the accent onto the evaluated mesh.

+ View example +
+