Advance M8-M11 parity workflows
This commit is contained in:
119
tools/web/check-weight-paint-golden.mjs
Normal file
119
tools/web/check-weight-paint-golden.mjs
Normal file
@@ -0,0 +1,119 @@
|
||||
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 golden = JSON.parse(fs.readFileSync(new URL("tests/golden/M9-12/weight-paint.json", root), "utf8"));
|
||||
const fixture = fs.readFileSync(new URL(`tests/files/web/${golden.fixture}`, root));
|
||||
const wasmBinary = fs.readFileSync(new URL("web/app/src/vendor/blender/web_engine.wasm", root));
|
||||
|
||||
function call(engine, handle, name, payload) {
|
||||
const bytes = new TextEncoder().encode(JSON.stringify(payload));
|
||||
const pointer = engine._malloc(bytes.byteLength);
|
||||
try {
|
||||
engine.HEAPU8.set(bytes, pointer);
|
||||
assert.equal(engine._web_engine_apply_command(handle, pointer, bytes.byteLength), 0,
|
||||
`${name}: ${engine.UTF8ToString(engine._web_engine_last_error_message())}`);
|
||||
}
|
||||
finally {
|
||||
engine._free(pointer);
|
||||
}
|
||||
}
|
||||
|
||||
function open(engine, handle, bytes) {
|
||||
const pointer = engine._malloc(bytes.byteLength);
|
||||
try {
|
||||
engine.HEAPU8.set(bytes, pointer);
|
||||
assert.equal(engine._web_engine_open_blend(handle, pointer, bytes.byteLength), 0,
|
||||
engine.UTF8ToString(engine._web_engine_last_error_message()));
|
||||
}
|
||||
finally {
|
||||
engine._free(pointer);
|
||||
}
|
||||
}
|
||||
|
||||
function snapshot(engine, handle) {
|
||||
const dataOut = engine._malloc(4);
|
||||
const lengthOut = engine._malloc(4);
|
||||
try {
|
||||
assert.equal(engine._web_engine_get_scene_snapshot(handle, dataOut, lengthOut), 0);
|
||||
const pointer = engine.HEAPU32[dataOut >>> 2];
|
||||
const length = engine.HEAPU32[lengthOut >>> 2];
|
||||
return JSON.parse(new TextDecoder().decode(engine.HEAPU8.slice(pointer, pointer + length)));
|
||||
}
|
||||
finally {
|
||||
engine._free(dataOut);
|
||||
engine._free(lengthOut);
|
||||
}
|
||||
}
|
||||
|
||||
function save(engine, handle) {
|
||||
const dataOut = engine._malloc(4);
|
||||
const lengthOut = engine._malloc(4);
|
||||
try {
|
||||
assert.equal(engine._web_engine_save_blend(handle, dataOut, lengthOut), 0);
|
||||
const pointer = engine.HEAPU32[dataOut >>> 2];
|
||||
const length = engine.HEAPU32[lengthOut >>> 2];
|
||||
const bytes = engine.HEAPU8.slice(pointer, pointer + length);
|
||||
engine._web_engine_free_buffer(pointer);
|
||||
return bytes;
|
||||
}
|
||||
finally {
|
||||
engine._free(dataOut);
|
||||
engine._free(lengthOut);
|
||||
}
|
||||
}
|
||||
|
||||
function weightsByVertex(snapshotValue) {
|
||||
const mesh = snapshotValue.meshes.find((item) => item.id === `mesh:${golden.mesh}`);
|
||||
assert.ok(mesh?.skinWeights, "weight golden mesh has no skinWeights");
|
||||
return golden.steps[0].vertices.map((expectedVertex) => {
|
||||
const result = {};
|
||||
for (const [groupIndex, groupName] of mesh.skinWeights.boneNames.entries()) {
|
||||
const offset = expectedVertex.index * 4;
|
||||
const slot = mesh.skinWeights.indices.slice(offset, offset + 4).findIndex((value) => value === groupIndex);
|
||||
result[groupName] = slot < 0 ? 0 : mesh.skinWeights.weights[offset + slot];
|
||||
}
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
function compareStep(actualSnapshot, expected, label) {
|
||||
const actual = weightsByVertex(actualSnapshot);
|
||||
assert.deepEqual(actual.map((weights) => Object.keys(weights).sort()), expected.vertices.map(() => expected.groups.slice().sort()), `${label} group schema`);
|
||||
expected.vertices.forEach((vertex, index) => {
|
||||
for (const group of expected.groups) {
|
||||
const error = Math.abs((actual[index][group] ?? 0) - (vertex.weights[group] ?? 0));
|
||||
assert.ok(error <= golden.tolerance, `${label} vertex=${vertex.index} group=${group} error=${error}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const engine = await factory({ wasmBinary: wasmBinary.slice() });
|
||||
const handle = engine._web_engine_create();
|
||||
assert.ok(handle > 0);
|
||||
try {
|
||||
open(engine, handle, fixture);
|
||||
call(engine, handle, "initial", { type: "setVertexWeights", objectId: `object:${golden.object}`, vertexGroup: "WebPaintGroup", indices: [0, 1], values: [0.75, 0.25] });
|
||||
compareStep(snapshot(engine, handle), golden.steps[0], "initial");
|
||||
call(engine, handle, "normalize", { type: "setVertexWeights", objectId: `object:${golden.object}`, vertexGroup: "WebPaintGroup", indices: [2], values: [0.5], normalize: true });
|
||||
compareStep(snapshot(engine, handle), golden.steps[1], "normalize");
|
||||
call(engine, handle, "limit-normalize", { type: "setVertexWeights", objectId: `object:${golden.object}`, vertexGroup: "WebPaintGroup", indices: [3], values: [0.4], limit: 2, normalize: true });
|
||||
compareStep(snapshot(engine, handle), golden.steps[2], "limit-normalize");
|
||||
call(engine, handle, "mirror", { type: "setVertexWeights", objectId: `object:${golden.object}`, vertexGroup: "WebPaintGroup", indices: [0], values: [0.9], mirror: true, mirrorAxis: 0, mirrorTolerance: 1e-4 });
|
||||
compareStep(snapshot(engine, handle), golden.steps[3], "mirror");
|
||||
const saved = save(engine, handle);
|
||||
const reopened = engine._web_engine_create();
|
||||
try {
|
||||
open(engine, reopened, saved);
|
||||
compareStep(snapshot(engine, reopened), golden.steps[3], "save-reopen");
|
||||
}
|
||||
finally {
|
||||
engine._web_engine_destroy(reopened);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
engine._web_engine_destroy(handle);
|
||||
}
|
||||
|
||||
process.stdout.write(`weight-paint-golden-ok fixture=${golden.fixture} steps=${golden.steps.map((step) => step.name).join(",")} normalize=passed limit=passed mirror=passed save-reopen=passed\n`);
|
||||
Reference in New Issue
Block a user