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
9 changes: 9 additions & 0 deletions .github/workflows/blender-smoke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,12 @@ jobs:
# 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 --
26 changes: 26 additions & 0 deletions examples/depsgraph-export/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Depsgraph-Evaluated Export

A runnable example that proves **modifiers actually ship in exports** and demonstrates the
[`depsgraph-and-evaluated-data`](../../skills/depsgraph-and-evaluated-data/SKILL.md) lifetime
contract. It builds a cube with a SUBSURF modifier, measures the evaluated mesh via
`evaluated_get().to_mesh()` (paired with `to_mesh_clear()`), exports through `wm.obj_export`,
and asserts the exported vertex count equals the **evaluated** (modifier-applied) count and is
strictly greater than the base mesh.

**What it witnesses:** the `evaluated_get` → `to_mesh` → `to_mesh_clear` contract, and that
`wm.obj_export` writes the depsgraph-evaluated geometry (so modifiers are baked into the
export) rather than the unmodified base mesh.

## Run

```bash
# Cheap correctness check (writes an OBJ to a temp path, asserts the counts) — the CI check:
blender --background --python depsgraph_export.py --

# Write the exported OBJ to a specific path:
blender --background --python depsgraph_export.py -- --output remeshed.obj
```

It exits non-zero on failure (modifier not applied, or exported count ≠ evaluated count). The
`blender-smoke` workflow runs this check on Blender 4.5 LTS and 5.1: base 8 → evaluated/exported
98 vertices with a 2-level SUBSURF.
54 changes: 54 additions & 0 deletions examples/depsgraph-export/depsgraph_export.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"""Candidate B: depsgraph-evaluated export (SCRATCH).

Witnesses the depsgraph lifetime contract AND that modifiers actually ship in exports. Builds
a cube with a SUBSURF modifier, measures the evaluated mesh via evaluated_get().to_mesh()
(paired with to_mesh_clear()), exports through wm.obj_export, and asserts the exported vertex
count equals the EVALUATED (modifier-applied) count and is strictly greater than the base.
"""
import bpy, bmesh, sys, os, argparse

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: write the exported OBJ here (else a temp path)")
args = p.parse_args(argv)

bpy.ops.wm.read_factory_settings(use_empty=True)
me = bpy.data.meshes.new("Cube"); bm = bmesh.new(); bmesh.ops.create_cube(bm, size=2.0); bm.to_mesh(me); bm.free()
obj = bpy.data.objects.new("Cube", me); bpy.context.collection.objects.link(obj)
obj.modifiers.new("ss", 'SUBSURF').levels = 2
base = len(obj.data.vertices)

# depsgraph lifetime contract: evaluate, read, then release with to_mesh_clear
dg = bpy.context.evaluated_depsgraph_get()
ev = obj.evaluated_get(dg)
em = ev.to_mesh()
eval_vcount = len(em.vertices)
ev.to_mesh_clear() # must be paired; releases the temporary mesh

import tempfile
out = args.output or os.path.join(tempfile.gettempdir(), "depsgraph_export.obj")
os.makedirs(os.path.dirname(os.path.abspath(out)) or ".", exist_ok=True)
# obj_export writes the evaluated (modifier-applied) geometry by default
bpy.ops.wm.obj_export(filepath=out, export_selected_objects=False)
if not (os.path.exists(out) and os.path.getsize(out) > 0):
print("ERROR: no OBJ written", file=sys.stderr); return 4
exported = 0
with open(out) as f:
for line in f:
if line.startswith("v "): exported += 1

print(f"base_vcount={base} eval_vcount={eval_vcount} exported_vcount={exported}")
if not (eval_vcount > base):
print("ERROR: evaluated mesh did not apply the modifier", file=sys.stderr); return 3
if exported != eval_vcount:
print(f"ERROR: export ({exported}) != evaluated ({eval_vcount}); modifier did not ship",
file=sys.stderr); return 5
print("depsgraph-export 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)
Loading