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); });