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/nonmesh_scene.blend"); const fixture = fs.readFileSync(fixturePath); const fixtureSha256 = crypto.createHash("sha256").update(fixture).digest("hex"); test("WBV2 reads non-mesh geometry and attributes from Blender Main without JS simulation", async ({ page }) => { test.setTimeout(120_000); await page.goto("/?engine=manual"); const result = await page.evaluate(async (bytes) => { const { WebEngineClient } = await import("/src/engine-client/WebEngineClient.ts"); const summarize = (snapshot: any) => (snapshot.nonMeshData ?? []).map((data: any) => ({ id: data.id, name: data.name, type: data.type, geometryStatus: data.geometryStatus, pointCount: data.pointCount, splineCount: data.splineCount, controlPointsLength: data.controlPoints?.length ?? 0, splineOffsetsLength: data.splineOffsets?.length ?? 0, pointWeightsLength: data.pointWeights?.length ?? 0, handlePointsLength: data.handlePoints?.length ?? 0, splineTypes: data.splineTypes ?? [], attributes: (data.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))), attributeValueNames: (data.attributeValues ?? []).map((attribute: any) => attribute.name).sort(), textLength: typeof data.text === "string" ? data.text.length : 0, fontCharacterCount: data.fontCharacters?.length ?? 0, fontTextBoxCount: data.fontTextBoxes?.length ?? 0, volumeGridCount: data.volumeGrids?.length ?? 0, })).sort((a: any, b: any) => `${a.type}:${a.name}`.localeCompare(`${b.type}:${b.name}`)); 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 data = summarize(opened.snapshot); if (data.length === 0) throw new Error("Blender Main returned no non-mesh data"); const saved = await client.saveBlend(); client.terminate(); const reopened = await reopenedClient.openBlend(saved.slice(0)); return { browser: "chromium", fixtureSha256: await crypto.subtle.digest("SHA-256", Uint8Array.from(bytes)).then((hash) => Array.from(new Uint8Array(hash), (value) => value.toString(16).padStart(2, "0")).join("")), data, reopenedData: summarize(reopened.snapshot), openedRevision: opened.snapshot.revision, reopenedRevision: reopened.snapshot.revision, }; } finally { client.terminate(); reopenedClient.terminate(); } }, Array.from(fixture)); fs.mkdirSync(path.join(root, "tests/golden/WBV2-P1101-000"), { recursive: true }); fs.writeFileSync(path.join(root, "tests/golden/WBV2-P1101-000/nonmesh-main-reader-production.json"), `${JSON.stringify({ schemaVersion: 1, task: "WBV2-P1101-000", operation: "NONMESH_MAIN_READER_CHROMIUM", browser: "chromium", queueMutation: false, sourceRnaStructs: { "rna-struct:CurvePoint": "nonMeshData[type=CURVES].pointCount + attributes[name=position]", "rna-struct:SplinePoint": "nonMeshData[type=CURVE].pointWeights", "rna-struct:SplinePoints": "nonMeshData[type=CURVE].pointCount", "rna-struct:AttributeGroupCurves": "nonMeshData[type=CURVES].attributes", "rna-struct:AttributeGroupPointCloud": "nonMeshData[type=POINT_CLOUD].attributes", }, ...result, }, null, 2)}\n`); expect(result.browser).toBe("chromium"); expect(result.fixtureSha256).toBe(fixtureSha256); expect(result.openedRevision).toBe(1); expect(result.reopenedRevision).toBe(1); expect(result.data).toEqual(result.reopenedData); expect(result.data.some((data: any) => ["CURVE", "SURFACE"].includes(data.type) && data.pointCount > 0)).toBe(true); expect(result.data.some((data: any) => data.type === "FONT" && data.textLength > 0)).toBe(true); expect(result.data.some((data: any) => data.type === "POINT_CLOUD" && data.attributes.length > 0)).toBe(true); expect(result.data.some((data: any) => data.type === "CURVES" && data.pointCount > 0 && data.attributes.some((attribute: any) => attribute.name === "position"))).toBe(true); expect(result.data.some((data: any) => data.type === "VOLUME")).toBe(true); });