- update native web_engine sources and rebuilt wasm artifacts - add task cards, status notes and tooling scripts - add web e2e specs, tutorial UI sources and tutorial assets - sync golden task manifests and audit records Co-Authored-By: Claude Code <noreply@anthropic.com>
93 lines
5.1 KiB
TypeScript
93 lines
5.1 KiB
TypeScript
import crypto from "node:crypto";
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import { expect, test } from "@playwright/test";
|
|
|
|
const root = path.resolve(import.meta.dirname, "../../..");
|
|
const fixturePath = path.join(root, "tests/files/web/attribute_scene.blend");
|
|
const geometryFixturePath = path.join(root, "tests/files/web/generated/WBV2-P2-003-MESH.EDIT_CORE.blend");
|
|
const fixture = fs.readFileSync(fixturePath);
|
|
const geometryFixture = fs.readFileSync(geometryFixturePath);
|
|
const fixtureSha256 = crypto.createHash("sha256").update(fixture).digest("hex");
|
|
|
|
test("WBV2 reads Mesh topology collections from Blender Main without JS simulation", async ({ page }) => {
|
|
test.setTimeout(120_000);
|
|
await page.goto("/?engine=manual");
|
|
const result = await page.evaluate(async (inputs) => {
|
|
const { WebEngineClient } = await import("/src/engine-client/WebEngineClient.ts");
|
|
const summarize = (snapshot: any) => snapshot.meshes.map((mesh: any) => ({
|
|
id: mesh.id,
|
|
name: mesh.name,
|
|
vertexCount: mesh.vertexCount,
|
|
edgeCount: mesh.edgeCount,
|
|
faceCount: mesh.faceCount,
|
|
cornerCount: mesh.cornerCount,
|
|
positionsLength: mesh.positions?.length ?? 0,
|
|
indicesLength: mesh.indices?.length ?? 0,
|
|
edgeVertexIndicesLength: mesh.edgeVertexIndices?.length ?? 0,
|
|
triangleCornerIndicesLength: mesh.triangleCornerIndices?.length ?? 0,
|
|
triangleFaceIndicesLength: mesh.triangleFaceIndices?.length ?? 0,
|
|
attributes: (mesh.attributes ?? []).map((attribute: any) => ({ name: attribute.name, domain: attribute.domain, dataType: attribute.dataType })).sort((a: any, b: any) => JSON.stringify(a).localeCompare(JSON.stringify(b))),
|
|
uvLayers: (mesh.uvLayers ?? []).map((layer: any) => ({ name: layer.name, active: layer.active, activeRender: layer.activeRender })).sort((a: any, b: any) => a.name.localeCompare(b.name)),
|
|
}));
|
|
const roundTrip = async (bytes: number[]) => {
|
|
const client = new WebEngineClient({ timeoutMs: 60_000 });
|
|
const reopenedClient = new WebEngineClient({ timeoutMs: 60_000 });
|
|
try {
|
|
const opened = await client.openBlend(Uint8Array.from(bytes).buffer);
|
|
const meshes = summarize(opened.snapshot);
|
|
if (meshes.length === 0) throw new Error("Blender Main returned no meshes");
|
|
const saved = await client.saveBlend();
|
|
client.terminate();
|
|
const reopened = await reopenedClient.openBlend(saved.slice(0));
|
|
return { meshes, reopenedMeshes: summarize(reopened.snapshot), openedRevision: opened.snapshot.revision, reopenedRevision: reopened.snapshot.revision };
|
|
}
|
|
finally {
|
|
client.terminate();
|
|
reopenedClient.terminate();
|
|
}
|
|
};
|
|
const attribute = await roundTrip(inputs.attributeBytes);
|
|
const geometry = await roundTrip(inputs.geometryBytes);
|
|
return {
|
|
browser: "chromium",
|
|
fixtureSha256: await crypto.subtle.digest("SHA-256", Uint8Array.from(inputs.attributeBytes)).then((hash) => Array.from(new Uint8Array(hash), (value) => value.toString(16).padStart(2, "0")).join("")),
|
|
geometryFixtureSha256: await crypto.subtle.digest("SHA-256", Uint8Array.from(inputs.geometryBytes)).then((hash) => Array.from(new Uint8Array(hash), (value) => value.toString(16).padStart(2, "0")).join("")),
|
|
attribute,
|
|
geometry,
|
|
};
|
|
}, { attributeBytes: Array.from(fixture), geometryBytes: Array.from(geometryFixture) });
|
|
|
|
fs.mkdirSync(path.join(root, "tests/golden/WBV2-P1100-002"), { recursive: true });
|
|
fs.writeFileSync(path.join(root, "tests/golden/WBV2-P1100-002/mesh-main-reader-production.json"), `${JSON.stringify({
|
|
schemaVersion: 1,
|
|
task: "WBV2-P1100-002",
|
|
operation: "MESH_MAIN_READER_CHROMIUM",
|
|
browser: "chromium",
|
|
queueMutation: false,
|
|
sourceRnaStructs: {
|
|
"rna-struct:MeshVertex": "mesh.vertexCount + mesh.attributes[name=position]",
|
|
"rna-struct:MeshEdges": "mesh.edgeCount",
|
|
"rna-struct:MeshLoops": "mesh.cornerCount",
|
|
"rna-struct:MeshPolygons": "mesh.faceCount",
|
|
"rna-struct:MeshUVLoopLayer": "mesh.uvLayers",
|
|
"rna-struct:AttributeGroupMesh": "mesh.attributes",
|
|
},
|
|
...result,
|
|
}, null, 2)}\n`);
|
|
expect(result.browser).toBe("chromium");
|
|
expect(result.fixtureSha256).toBe(fixtureSha256);
|
|
expect(result.geometryFixtureSha256).toBe(crypto.createHash("sha256").update(geometryFixture).digest("hex"));
|
|
expect(result.attribute.openedRevision).toBe(1);
|
|
expect(result.attribute.reopenedRevision).toBe(1);
|
|
expect(result.geometry.openedRevision).toBe(1);
|
|
expect(result.geometry.reopenedRevision).toBe(1);
|
|
expect(result.attribute.meshes).toEqual(result.attribute.reopenedMeshes);
|
|
expect(result.geometry.meshes).toEqual(result.geometry.reopenedMeshes);
|
|
const meshes = [...result.attribute.meshes, ...result.geometry.meshes];
|
|
expect(meshes.every((mesh: any) => mesh.vertexCount > 0 && mesh.edgeCount > 0 && mesh.faceCount > 0 && mesh.cornerCount > 0)).toBe(true);
|
|
expect(meshes.some((mesh: any) => mesh.edgeCount > 0)).toBe(true);
|
|
expect(meshes.some((mesh: any) => mesh.faceCount > 0 && mesh.cornerCount > 0)).toBe(true);
|
|
expect(meshes.some((mesh: any) => mesh.attributes.length > 0)).toBe(true);
|
|
});
|