From 268c3f7567fbb1fee925474e15d8cb4820d0bb26 Mon Sep 17 00:00:00 2001 From: fOuttaMyPaint Date: Fri, 3 Jul 2026 18:13:50 -0400 Subject: [PATCH] feat: add shape-key-blend and curve-bevel-arc smoke-gated examples Witness data-level shape-key evaluation vs undeformed mesh.vertices, and beveled Bezier tubes authored on Curve datablocks. Co-authored-by: Cursor --- .cursor-plugin/plugin.json | 2 + .github/workflows/blender-smoke.yml | 18 + README.md | 30 ++ docs/gallery/assets/curve-bevel-arc-hero.webp | Bin 0 -> 12898 bytes docs/gallery/assets/shape-key-blend-hero.webp | Bin 0 -> 8154 bytes docs/gallery/curve-bevel-arc/index.html | 451 +++++++++++++++++ docs/gallery/index.html | 25 + docs/gallery/shape-key-blend/index.html | 455 ++++++++++++++++++ examples/curve-bevel-arc/README.md | 25 + examples/curve-bevel-arc/curve_bevel_arc.py | 209 ++++++++ examples/curve-bevel-arc/preview.webp | Bin 0 -> 11274 bytes examples/gallery.json | 18 + examples/shape-key-blend/README.md | 26 + examples/shape-key-blend/preview.webp | Bin 0 -> 7316 bytes examples/shape-key-blend/shape_key_blend.py | 213 ++++++++ 15 files changed, 1472 insertions(+) create mode 100644 docs/gallery/assets/curve-bevel-arc-hero.webp create mode 100644 docs/gallery/assets/shape-key-blend-hero.webp create mode 100644 docs/gallery/curve-bevel-arc/index.html create mode 100644 docs/gallery/shape-key-blend/index.html create mode 100644 examples/curve-bevel-arc/README.md create mode 100644 examples/curve-bevel-arc/curve_bevel_arc.py create mode 100644 examples/curve-bevel-arc/preview.webp create mode 100644 examples/shape-key-blend/README.md create mode 100644 examples/shape-key-blend/preview.webp create mode 100644 examples/shape-key-blend/shape_key_blend.py diff --git a/.cursor-plugin/plugin.json b/.cursor-plugin/plugin.json index 6baf3ab..579c699 100644 --- a/.cursor-plugin/plugin.json +++ b/.cursor-plugin/plugin.json @@ -60,11 +60,13 @@ ], "examples": [ "examples/bmesh-gear", + "examples/curve-bevel-arc", "examples/depsgraph-export", "examples/driver-wave", "examples/gn-instance-grid", "examples/gn-sdf-remesh", "examples/shader-node-group", + "examples/shape-key-blend", "examples/swatch-grid", "examples/temp-override-join", "examples/turntable", diff --git a/.github/workflows/blender-smoke.yml b/.github/workflows/blender-smoke.yml index a3d0c97..8437253 100644 --- a/.github/workflows/blender-smoke.yml +++ b/.github/workflows/blender-smoke.yml @@ -212,3 +212,21 @@ jobs: # Material carries Lime. Exits non-zero on failure. xvfb-run -a "$BLENDER" --background \ --python examples/gn-instance-grid/gn_instance_grid.py -- + + - name: Shipped example - shape-key blend (data API + evaluated mesh) + run: | + set -euo pipefail + # Frame-independent check only (no render): relative Tall shape key at + # value=0.5; asserts undeformed mesh stays at Basis and evaluated z matches + # basis + value*(key-basis). Exits non-zero on failure. + xvfb-run -a "$BLENDER" --background \ + --python examples/shape-key-blend/shape_key_blend.py -- + + - name: Shipped example - curve bevel arc (Bezier + bevel_depth) + run: | + set -euo pipefail + # Frame-independent check only (no render): beveled Bezier semicircle via + # curve data API; asserts 8 points, bevel_depth=0.12, eval verts=850 + # faces=840, tube rests on floor. Exits non-zero on failure. + xvfb-run -a "$BLENDER" --background \ + --python examples/curve-bevel-arc/curve_bevel_arc.py -- diff --git a/README.md b/README.md index 0c94789..d7cc82c 100644 --- a/README.md +++ b/README.md @@ -189,6 +189,36 @@ A generative Geometry Nodes tree — Mesh Grid → Instance on Points → Realiz attached as a `NODES` modifier with no Group Input. Asserts evaluated topology is verts = 72, faces = 54, and `Set Material` carries the lime accent. + + + + +Shape-key blend: a tall violet rectangular block on a dark studio floor, stretched by a relative Tall shape key at value 0.5 + + + +### [shape-key-blend](examples/shape-key-blend/) + +A relative shape key authored through the data API — `shape_key_add`, per-vertex +`key_blocks` data, and `.value`. Witnesses that shape keys do not rewrite +`mesh.vertices`: undeformed top stays at Basis, evaluated z matches +`basis + value × (key − basis)`. + + + + + +Curve bevel arc: a rose beveled Bezier semicircle tube resting on a dark studio floor + + + +### [curve-bevel-arc](examples/curve-bevel-arc/) + +A beveled Bezier semicircle authored on `bpy.types.Curve` — `splines.new('BEZIER')`, +`bezier_points`, `bevel_depth` — so the curve renders as a tube without a prior mesh +conversion. Asserts eight points, `bevel_depth == 0.12`, and evaluated topology +850 verts / 840 faces. + diff --git a/docs/gallery/assets/curve-bevel-arc-hero.webp b/docs/gallery/assets/curve-bevel-arc-hero.webp new file mode 100644 index 0000000000000000000000000000000000000000..0a65f1b0330b61a81982be73eddf3264171a3d2b GIT binary patch literal 12898 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 MSb3exR*F`D0NauT)c^nh literal 0 HcmV?d00001 diff --git a/docs/gallery/assets/shape-key-blend-hero.webp b/docs/gallery/assets/shape-key-blend-hero.webp new file mode 100644 index 0000000000000000000000000000000000000000..000dc7d3750f9c5f5f05945380009c022606df93 GIT binary patch literal 8154 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 ARR910 literal 0 HcmV?d00001 diff --git a/docs/gallery/curve-bevel-arc/index.html b/docs/gallery/curve-bevel-arc/index.html new file mode 100644 index 0000000..ccfeff0 --- /dev/null +++ b/docs/gallery/curve-bevel-arc/index.html @@ -0,0 +1,451 @@ + + + + + + curve-bevel-arc — Examples — Blender Developer Tools + + + + + + + + + + + + + + + + + + +
+

curve-bevel-arc

+

A beveled Bezier semicircle authored on bpy.types.Curve — splines.new('BEZIER'), bezier_points, bevel_depth — so the curve renders as a tube without a prior mesh conversion.

+
+
+ +

Rendered headless by the example itself — click to zoom.

+
witnesses Curve tubes are curve datablocks: eight Bezier points, bevel_depth == 0.12, and the evaluated mesh has deterministic topology (850 verts, 840 faces) resting on the floor.
+
+
blender --background --python examples/curve-bevel-arc/curve_bevel_arc.py --
+ +
+
+

A runnable example that builds a beveled Bezier semicircle entirely through the curve data API — splines.new('BEZIER'), per-point bezier_points, and bevel_depth — so the curve renders as a tube without a prior mesh conversion.

+

What it witnesses: renderable curve tubes are curve datablocks, not meshes. The check asserts eight Bezier points, bevel_depth == 0.12, and that the depsgraph-evaluated mesh has the deterministic topology (850 verts, 840 faces for these resolution settings) with a Z span that rests on the floor ([≈0, 2 × bevel]).

+

Run

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

It exits non-zero on failure (wrong point count, bevel, topology, or Z span). The blender-smoke workflow runs the check on Blender 4.5 LTS and 5.1.

+
+
+

Source

+
+ examples/curve-bevel-arc/curve_bevel_arc.py + View on GitHub → +
+
"""Beveled Bezier arc via the curve data API — a runnable example.
+
+Witnesses that renderable tubes are authored on `bpy.types.Curve` directly
+(`splines.new('BEZIER')`, `bezier_points`, `bevel_depth`) — not by meshing
+first or calling curve operators. The check asserts the closed-form point
+count, bevel depth, and that the depsgraph-evaluated mesh has the
+deterministic topology and Z span of a tube whose centerline sits at
+`z = bevel_depth` (resting on the floor).
+
+By default it runs only the correctness check (no render) — the CI smoke
+check. Pass --output to also render a still:
+
+    blender --background --python curve_bevel_arc.py --                 # check only
+    blender --background --python curve_bevel_arc.py -- --output c.png  # + render
+"""
+import bpy, bmesh, sys, os, math, argparse
+
+N_POINTS = 8
+RADIUS = 1.5
+BEVEL = 0.12
+BEVEL_RES = 3
+RES_U = 12
+# measured for the parameters above — identical on 4.4 and 5.1
+EXPECT_VERTS = 850
+EXPECT_FACES = 840
+
+
+def build():
+    bpy.ops.wm.read_factory_settings(use_empty=True)
+    curve = bpy.data.curves.new("Arc", 'CURVE')
+    curve.dimensions = '3D'
+    curve.bevel_depth = BEVEL
+    curve.bevel_resolution = BEVEL_RES
+    curve.resolution_u = RES_U
+
+    spline = curve.splines.new('BEZIER')
+    spline.bezier_points.add(N_POINTS - 1)  # one point exists already
+    for i, bp in enumerate(spline.bezier_points):
+        a = i / (N_POINTS - 1) * math.pi  # semicircle in XY
+        # centerline at z=BEVEL so the tube rests on the floor
+        bp.co = (RADIUS * math.cos(a), RADIUS * math.sin(a), BEVEL)
+        bp.handle_left_type = 'AUTO'
+        bp.handle_right_type = 'AUTO'
+
+    obj = bpy.data.objects.new("Arc", curve)
+    bpy.context.collection.objects.link(obj)
+    return obj
+
+
+def check(obj):
+    curve = obj.data
+    if curve.splines[0].type != 'BEZIER':
+        print(f"ERROR: spline type {curve.splines[0].type} != BEZIER", file=sys.stderr)
+        return 3
+    n = len(curve.splines[0].bezier_points)
+    if n != N_POINTS:
+        print(f"ERROR: bezier points {n} != {N_POINTS}", file=sys.stderr)
+        return 4
+    if abs(curve.bevel_depth - BEVEL) > 1e-6:
+        print(f"ERROR: bevel_depth {curve.bevel_depth} != {BEVEL}", file=sys.stderr)
+        return 5
+
+    bpy.context.view_layer.update()
+    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)
+        zs = [v.co.z for v in em.vertices]
+        z_lo, z_hi = min(zs), max(zs)
+    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 6
+
+    # tube diameter ≈ 2 * bevel; centerline at z=BEVEL → span [0, 2*BEVEL]
+    if z_lo < -0.02 or z_lo > 0.03:
+        print(f"ERROR: tube does not rest on floor (z_lo={z_lo:.4f})", file=sys.stderr)
+        return 7
+    if abs(z_hi - 2 * BEVEL) > 0.03:
+        print(f"ERROR: tube height {z_hi:.4f} != 2*bevel={2 * BEVEL:.4f}",
+              file=sys.stderr)
+        return 8
+
+    print(f"points={n} bevel={BEVEL} eval_verts={got_v} eval_faces={got_f} "
+          f"z={z_lo:.3f}..{z_hi:.3f}")
+    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
+    mat = bpy.data.materials.new("Rose")
+    mat.use_nodes = True
+    bsdf = mat.node_tree.nodes["Principled BSDF"]
+    bsdf.inputs["Base Color"].default_value = (0.92, 0.14, 0.42, 1.0)  # rose
+    bsdf.inputs["Roughness"].default_value = 0.28
+    obj.data.materials.append(mat)
+    obj.rotation_euler = (0.0, 0.0, math.radians(-20))
+
+    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
+
+    aim = bpy.data.objects.new("Aim", None)
+    aim.location = (0.0, 0.4, BEVEL)
+    scene.collection.objects.link(aim)
+
+    def light(name, loc, energy, size, col):
+        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
+        scene.collection.objects.link(ob)
+        lc = ob.constraints.new('TRACK_TO')
+        lc.target = aim
+        lc.track_axis = 'TRACK_NEGATIVE_Z'
+        lc.up_axis = 'UP_Y'
+
+    light("Key", (-3.5, -4.5, 5.5), 1400.0, 6.0, (1.0, 0.98, 0.94))
+    light("Fill", (5.0, -3.5, 2.5), 320.0, 8.0, (0.8, 0.87, 1.0))
+    light("Rim", (1.5, 4.5, 2.0), 420.0, 4.0, (1.0, 0.75, 0.45))
+
+    cam_data = bpy.data.cameras.new("Cam")
+    cam_data.lens = 50.0
+    cam = bpy.data.objects.new("Cam", cam_data)
+    cam.location = (2.8, -4.0, 2.4)
+    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 = 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 9
+        print(f"rendered still {args.output}")
+
+    print("curve-bevel-arc 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 66cdc57..dbcf9c6 100644 --- a/docs/gallery/index.html +++ b/docs/gallery/index.html @@ -175,8 +175,10 @@

Examples Gallery

@@ -301,6 +304,28 @@

gn-instance-grid

View example
+
+ + shape-key-blend — A relative shape key authored entirely through the data API — shape_key_add, per-vertex key_blocks data, and + +
+

shape-key-blend

+

A relative shape key authored entirely through the data API — shape_key_add, per-vertex key_blocks data, and .value — read back from the depsgraph-evaluated mesh.

+

witnesses Shape keys do not rewrite mesh.vertices: undeformed top z stays at Basis, and evaluated z matches basis + value × (key − basis).

+ View example +
+
+
+ + curve-bevel-arc — A beveled Bezier semicircle authored on bpy + +
+

curve-bevel-arc

+

A beveled Bezier semicircle authored on bpy.types.Curve — splines.new('BEZIER'), bezier_points, bevel_depth — so the curve renders as a tube without a prior mesh conversion.

+

witnesses Curve tubes are curve datablocks: eight Bezier points, bevel_depth == 0.12, and the evaluated mesh has deterministic topology (850 verts, 840 faces) resting on the floor.

+ View example +
+