64 lines
3.6 KiB
TypeScript
64 lines
3.6 KiB
TypeScript
import crypto from "node:crypto";
|
|
import fs from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { spawnSync } from "node:child_process";
|
|
import { expect, test } from "@playwright/test";
|
|
|
|
const root = path.resolve(import.meta.dirname, "../../..");
|
|
const source = fs.readFileSync(path.join(root, "tests/files/web/m12_stl_capability_v1/capability-binary.stl"));
|
|
const reportPath = path.join(root, "tests/golden/M12-07F/web-roundtrip-report.json");
|
|
const sha256 = (bytes: Uint8Array | Buffer) => crypto.createHash("sha256").update(bytes).digest("hex");
|
|
|
|
test("M12-07F round-trips Web STL through desktop Blender with material loss report", async ({ page }) => {
|
|
await page.goto("/");
|
|
const result = await page.evaluate(async (bytes) => new Promise<any>((resolve, reject) => {
|
|
const worker = new Worker("/src/workers/stl-roundtrip-test.worker.ts", { type: "module" });
|
|
worker.onmessage = (event: MessageEvent<any>) => { worker.terminate(); resolve({ ...event.data, output: event.data.output ? Array.from(new Uint8Array(event.data.output)) : null }); };
|
|
worker.onerror = (event) => { worker.terminate(); reject(new Error(event.message)); };
|
|
const input = Uint8Array.from(bytes).buffer;
|
|
worker.postMessage({ bytes: input, unitScale: 1, sourceMaterialCount: 2 }, [input]);
|
|
}), Array.from(source));
|
|
expect(result.ok).toBe(true);
|
|
expect(result.lossReport).toEqual({
|
|
schemaVersion: 1,
|
|
operation: "STL_EXPORT_LOSS_REPORT",
|
|
canRoundTrip: true,
|
|
warningCount: 1,
|
|
warnings: [{ code: "STL_MATERIAL_UNSUPPORTED", severity: "warning", message: "STL has no material slots; 2 source material assignments are omitted" }],
|
|
});
|
|
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "m12-07f-stl-"));
|
|
try {
|
|
const outputPath = path.join(temporary, "web-output.stl");
|
|
fs.writeFileSync(outputPath, Buffer.from(result.output));
|
|
const desktopPath = path.join(temporary, "desktop-report.json");
|
|
const blender = process.env.BLENDER_BIN ?? path.join(root, "build_blender_5.2.0/bin/blender");
|
|
const command = spawnSync(blender, ["-b", "--factory-startup", "--python", path.join(root, "tools/web/check-stl-web-roundtrip.py"), "--", outputPath, desktopPath], { cwd: root, encoding: "utf8", maxBuffer: 20 * 1024 * 1024 });
|
|
expect(command.status, `${command.stdout}\n${command.stderr}`).toBe(0);
|
|
const desktop = JSON.parse(JSON.stringify(JSON.parse(fs.readFileSync(desktopPath, "utf8"))));
|
|
const report = {
|
|
schemaVersion: 1,
|
|
task: "M12-07F",
|
|
operation: "WEB_STL_TO_DESKTOP_ROUNDTRIP",
|
|
source: { sha256: sha256(source), bytes: source.byteLength },
|
|
browser: { imported: result.imported, outputSha256: sha256(Buffer.from(result.output)), outputBytes: result.output.length, lossReport: result.lossReport },
|
|
desktop,
|
|
comparison: {
|
|
triangleCountExact: result.imported.triangleCount === desktop.triangleCount,
|
|
normalExact: JSON.stringify(result.imported.normals) === JSON.stringify(desktop.polygonNormals),
|
|
materialLossExplicit: result.lossReport.warnings[0]?.code === "STL_MATERIAL_UNSUPPORTED",
|
|
},
|
|
nextTask: "M12-07G",
|
|
};
|
|
if (process.env.UPDATE_STL_ROUNDTRIP_REPORT === "1") {
|
|
fs.mkdirSync(path.dirname(reportPath), { recursive: true });
|
|
fs.writeFileSync(reportPath, JSON.stringify(report, null, 2) + "\n");
|
|
}
|
|
expect(report).toEqual(JSON.parse(fs.readFileSync(reportPath, "utf8")));
|
|
expect(report.comparison).toEqual({ triangleCountExact: true, normalExact: true, materialLossExplicit: true });
|
|
}
|
|
finally {
|
|
fs.rmSync(temporary, { recursive: true, force: true });
|
|
}
|
|
});
|