import assert from "node:assert/strict"; import fs from "node:fs"; import factory from "../../web/app/src/vendor/blender/web_engine.js"; const root = new URL("../../", import.meta.url); const repeat = Math.max(1, Number.parseInt(process.env.DEPSGRAPH_REPEAT ?? "100", 10) || 100); const fixtures = ["empty.blend", "basic_scene.blend", "rigged_shape_scene.blend"]; const deformationGolden = JSON.parse(fs.readFileSync(new URL("tests/golden/W-079/blender-deformation.json", root), "utf8")); function evaluateFixture(engine, bytes) { const handle = engine._web_engine_create(); assert.ok(handle > 0, "WebEngine handle creation failed"); try { const input = engine._malloc(bytes.byteLength); try { engine.HEAPU8.set(bytes, input); assert.equal(engine._web_engine_open_blend(handle, input, bytes.byteLength), 0, "blend open failed"); } finally { engine._free(input); } const dataOut = engine._malloc(4); const lengthOut = engine._malloc(4); try { assert.equal(engine._web_engine_evaluate_depsgraph(handle, dataOut, lengthOut), 0, "depsgraph evaluation failed"); const pointer = engine.HEAPU32[dataOut >>> 2]; const length = engine.HEAPU32[lengthOut >>> 2]; assert.ok(pointer && length, "depsgraph returned an empty report"); return JSON.parse(new TextDecoder().decode(engine.HEAPU8.slice(pointer, pointer + length))); } finally { engine._free(dataOut); engine._free(lengthOut); } } finally { engine._web_engine_destroy(handle); } } for (let iteration = 0; iteration < repeat; iteration++) { for (const fixture of fixtures) { const bytes = fs.readFileSync(new URL(`tests/files/web/${fixture}`, root)); const engine = await factory({ wasmBinary: fs.readFileSync(new URL("web/app/src/vendor/blender/web_engine.wasm", root)), }); const report = evaluateFixture(engine, bytes); assert.equal(report.engine, "BlenderDepsgraph"); assert.equal(report.status, "EVALUATED"); assert.ok(report.objectCount >= report.meshObjectCount); for (const mesh of report.meshes) { assert.equal(mesh.positions.length, mesh.vertexCount * 3); assert.equal(mesh.indices.length, mesh.triangleCount * 3); assert.ok(mesh.indices.every((index) => index >= 0 && index < mesh.vertexCount)); assert.equal(mesh.modifiers.length, mesh.modifierCount); assert.deepEqual(mesh.modifiers.map((modifier) => modifier.index), mesh.modifiers.map((_, index) => index)); assert.ok(mesh.modifiers.every((modifier) => modifier.uuid.startsWith(`modifier:${mesh.objectId}:`))); } if (fixture === "rigged_shape_scene.blend") { const mesh = report.meshes.find((candidate) => candidate.sourceMeshId === `mesh:${deformationGolden.mesh}`); assert.ok(mesh, "rigged mesh missing from depsgraph report"); assert.deepEqual(mesh.modifiers.map((modifier) => modifier.type), ["Armature", "Decimate"]); assert.deepEqual(mesh.modifiers.map((modifier) => modifier.status), ["EVALUATED", "BLOCKED"]); assert.equal(mesh.modifiers[1].showViewport, true); assert.match(mesh.modifiers[1].error, /more than 3 input faces/); assert.equal(mesh.modifiers[1].errorCode, "BLENDER_MODIFIER_ERROR"); assert.match(mesh.modifiers[1].suggestion, /disable/); assert.deepEqual(mesh.modifiers[0].targetObjectIds, ["object:RiggedArmatureObject"]); assert.ok(mesh.modifiers[0].dependsOn.includes("object:RiggedArmatureObject")); assert.deepEqual(mesh.modifiers[1].dependsOn, [mesh.modifiers[0].uuid]); const errors = mesh.positions.map((value, index) => value - deformationGolden.positions[index]); const maxError = Math.max(...errors.map((value) => Math.abs(value))); const rmsError = Math.sqrt(errors.reduce((sum, value) => sum + value * value, 0) / errors.length); assert.ok(maxError <= deformationGolden.tolerance.maxPositionError, `max error ${maxError}`); assert.ok(rmsError <= deformationGolden.tolerance.rmsPositionError, `RMS error ${rmsError}`); } } } process.stdout.write(`depsgraph-ok repeat=${repeat} fixtures=${fixtures.join(",")}\n`);