Skip to content

fix: polish the four newest examples #39

fix: polish the four newest examples

fix: polish the four newest examples #39

Workflow file for this run

name: Blender Smoke Test
# Executes the snippets' and skills' headline examples inside REAL Blender, headless,
# on the current stable (5.1.x) and the active LTS (4.5.x), and fails on any error or
# empty-output assertion. py_compile (in validate.yml) cannot catch API-level regressions
# like the EEVEE-id inversion, the slotted-actions boundary, the driver TypeError, or the
# dead SDF link -- this gate runs the code so those surface in CI, not in users' files.
on:
workflow_dispatch: {}
schedule:
- cron: "0 7 * * 1" # weekly, Monday 07:00 UTC
pull_request:
branches: [main]
permissions:
contents: read
concurrency:
group: blender-smoke-${{ github.ref }}
cancel-in-progress: true
jobs:
smoke:
name: Blender ${{ matrix.series }} smoke
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
include:
- series: "5.1" # current stable
- series: "4.5" # active LTS
steps:
- uses: actions/checkout@v7
- name: Install Blender runtime libraries
run: |
set -euo pipefail
sudo apt-get update
# Blender needs these shared libs even in --background (GPU/GL module init);
# xvfb provides a virtual display so GL/EGL init does not abort the process.
sudo apt-get install -y --no-install-recommends \
xvfb libgl1 libegl1 libxrender1 libxxf86vm1 libxfixes3 libxi6 \
libxkbcommon0 libsm6 libice6
- name: Resolve and download Blender ${{ matrix.series }}
run: |
set -euo pipefail
series="${{ matrix.series }}"
base="https://download.blender.org/release/Blender${series}/"
echo "Listing $base"
# pick the highest point release for this series (linux x64 portable)
file=$(curl -fsSL "$base" \
| grep -oE "blender-${series}\.[0-9]+-linux-x64\.tar\.xz" \
| sort -V | uniq | tail -1)
if [ -z "$file" ]; then
echo "::error::Could not resolve a linux-x64 build for Blender ${series} at $base"
exit 1
fi
url="${base}${file}"
echo "Downloading $url"
mkdir -p "$RUNNER_TEMP/bl"
curl -fSL --retry 3 -o "$RUNNER_TEMP/bl.tar.xz" "$url"
tar -xf "$RUNNER_TEMP/bl.tar.xz" -C "$RUNNER_TEMP/bl"
bl=$(find "$RUNNER_TEMP/bl" -maxdepth 2 -type f -name blender | head -1)
if [ -z "$bl" ]; then
echo "::error::blender binary not found after extraction"
exit 1
fi
echo "BLENDER=$bl" >> "$GITHUB_ENV"
- name: Print Blender version
run: |
set -euo pipefail
"$BLENDER" --version | head -1
# series guard: confirm we actually got the matrix series
"$BLENDER" --version | head -1 | grep -q "Blender ${{ matrix.series }}\." \
|| { echo "::error::version does not match series ${{ matrix.series }}"; exit 1; }
- name: Run in-Blender smoke driver
run: |
set -euo pipefail
mkdir -p "$RUNNER_TEMP/out"
xvfb-run -a "$BLENDER" --background --python tests/smoke/run_smoke.py -- "$RUNNER_TEMP/out"
- name: Build template input scene
run: |
set -euo pipefail
xvfb-run -a "$BLENDER" --background --python tests/smoke/make_input.py -- "$RUNNER_TEMP/out/input.blend"
- name: Headless glTF template runs (exit 0, .glb produced)
run: |
set -euo pipefail
xvfb-run -a "$BLENDER" --background "$RUNNER_TEMP/out/input.blend" \
--python tests/smoke/tmpl_gltf.py -- \
--output "$RUNNER_TEMP/out/out.glb" --apply-modifier SUBSURF
test -s "$RUNNER_TEMP/out/out.glb" || { echo "::error::glTF output missing/empty"; exit 1; }
head -c4 "$RUNNER_TEMP/out/out.glb" | grep -q "glTF" || { echo "::error::not a glTF binary"; exit 1; }
- name: Headless template no-mesh path returns exit 2
run: |
set +e
xvfb-run -a "$BLENDER" --background "$RUNNER_TEMP/out/empty.blend" \
--python tests/smoke/tmpl_gltf.py -- --output "$RUNNER_TEMP/out/none.glb"
code=$?
set -e
[ "$code" -eq 2 ] || { echo "::error::expected exit 2 for no-mesh input, got $code"; exit 1; }
echo "no-mesh exit code = $code (correct)"
- name: Headless render template runs (exit 0, PNG produced)
run: |
set -euo pipefail
# Cycles (CPU) so this is reliable on GPU-less runners; the EEVEE-id regression
# itself is gated in run_smoke.py via engine assignment.
xvfb-run -a "$BLENDER" --background "$RUNNER_TEMP/out/input.blend" \
--python tests/smoke/tmpl_render.py -- \
--output "$RUNNER_TEMP/out/render.png" --engine CYCLES
test -s "$RUNNER_TEMP/out/render.png" || { echo "::error::render PNG missing/empty"; exit 1; }
- name: Shipped example - swatch grid renders and self-verifies
run: |
set -euo pipefail
# Run the SHIPPED example file (not a copy). It asserts non-black AND
# distinct-region-count == material-count internally and exits non-zero on
# failure. Cycles (CPU) for pixels on the GPU-less runner; the example still
# asserts the version-correct EEVEE engine id before rendering. Small/low-sample
# for speed -- region detection holds at this size.
xvfb-run -a "$BLENDER" --background \
--python examples/swatch-grid/swatch_grid.py -- \
--output "$RUNNER_TEMP/out/swatch.png" --engine cycles --samples 8 --width 640
test -s "$RUNNER_TEMP/out/swatch.png" || { echo "::error::swatch grid output missing/empty"; exit 1; }
- name: Shipped example - turntable correctness (slotted actions)
run: |
set -euo pipefail
# Frame-independent check only (no render): asserts the rotation keys drive
# playback via the cross-version channelbag path; exits non-zero on failure.
xvfb-run -a "$BLENDER" --background \
--python examples/turntable/turntable.py --
- name: Shipped example - GN SDF remesh correctness (GridToMesh)
run: |
set -euo pipefail
# Frame-independent check only (no render): asserts the SDF->GridToMesh remesh
# yields geometry (eval vcount > 0 and != base); exits non-zero on failure.
xvfb-run -a "$BLENDER" --background \
--python examples/gn-sdf-remesh/gn_sdf_remesh.py --
- name: Shipped example - depsgraph-evaluated export
run: |
set -euo pipefail
# Frame-independent check only (no render): asserts wm.obj_export ships the
# depsgraph-evaluated (modifier-applied) geometry -- exported vcount == evaluated
# > base. Exits non-zero on failure.
xvfb-run -a "$BLENDER" --background \
--python examples/depsgraph-export/depsgraph_export.py --
- name: Shipped example - wave displace (foreach bulk IO)
run: |
set -euo pipefail
# Frame-independent check only (no render): 9409 verts displaced via one
# foreach_get + one foreach_set; asserts count unchanged, Z span matches the
# amplitude, and a probe vertex matches the closed-form wave. Exits non-zero
# on failure.
xvfb-run -a "$BLENDER" --background \
--python examples/wave-displace/wave_displace.py --
- name: Shipped example - driver wave (driver_namespace + depsgraph)
run: |
set -euo pipefail
# Frame-independent check only (no render): sixteen SCRIPTED drivers call a
# driver_namespace function; asserts the driven values on the evaluated copy
# AND the flushed-back original both match the closed form. Exits non-zero
# on failure.
xvfb-run -a "$BLENDER" --background \
--python examples/driver-wave/driver_wave.py --
- name: Shipped example - bmesh gear (ownership + watertight topology)
run: |
set -euo pipefail
# Frame-independent check only (no render): parametric gear via bmesh with
# bm.free() in try/finally; asserts closed-form vert/edge/face counts and
# that every edge borders exactly two faces. Exits non-zero on failure.
xvfb-run -a "$BLENDER" --background \
--python examples/bmesh-gear/bmesh_gear.py --
- name: Shipped example - shader node group (interface sockets + sharing)
run: |
set -euo pipefail
# Frame-independent check only (no render): TintedGloss group declared via
# tree.interface.new_socket, instanced by two materials; asserts the sockets
# exist, the group datablock is shared (users == 2), and the per-instance
# 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-step staircase joined under
# bpy.context.temp_override; asserts one mesh remains, sources are gone,
# topology verts=24 faces=18, and local Z spans all steps. 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, Set
# Material carries Lime, and corner center matches the grid. 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 key lifts+flares
# at value=0.5; asserts undeformed mesh stays at Basis and every evaluated
# vert 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.15, fill caps, eval
# verts=1044 faces=1028, tube rests on floor. Exits non-zero on failure.
xvfb-run -a "$BLENDER" --background \
--python examples/curve-bevel-arc/curve_bevel_arc.py --