import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import { execFileSync } from "node:child_process"; import { fileURLToPath, pathToFileURL } from "node:url"; import { loadWasmProbe } from "./wasm-probe-runtime.mjs"; const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); const capabilityId = "OpenSCAD:openscad.importDxf"; const supportedIds = [capabilityId, "business-format:OpenSCAD:import:.dxf"]; const ids = JSON.parse(process.env.FREECAD_BUSINESS_IDS ?? JSON.stringify(supportedIds)); const cases = []; const timings = { operations: [] }; const results = []; let out, kernel, failure; const files = []; let sequence = 0; // Separate closed contours exercise the native edge-to-face conversion. function dxf(contours) { const layers = [...new Set(contours.map(contour => contour.layer))]; const lines = ["0", "SECTION", "2", "TABLES", "0", "TABLE", "2", "LAYER", "70", String(layers.length)]; for (const layer of layers) lines.push("0", "LAYER", "2", layer, "70", "0", "62", "7", "6", "CONTINUOUS"); lines.push("0", "ENDTAB", "0", "ENDSEC", "0", "SECTION", "2", "ENTITIES"); for (const { layer, x, y = 0, width, height } of contours) { const points = [[x, y], [x + width, y], [x + width, y + height], [x, y + height]]; for (let i = 0; i < points.length; i++) { const a = points[i], b = points[(i + 1) % points.length]; lines.push("0", "LINE", "8", layer, "10", String(a[0]), "20", String(a[1]), "30", "0", "11", String(b[0]), "21", String(b[1]), "31", "0"); } } return [...lines, "0", "ENDSEC", "0", "EOF", ""].join("\n"); } try { if (ids.some(id => supportedIds.includes(id))) { out = fs.mkdtempSync(path.join(os.tmpdir(), "freecad-openscad-business-")); execFileSync(path.join(root, "web/node_modules/.bin/tsc"), [ "--target", "ES2022", "--module", "ESNext", "--moduleResolution", "Bundler", "--strict", "--skipLibCheck", "--outDir", out, path.join(root, "web/src/openscad-format-facade.ts"), ], { cwd: root, stdio: "pipe" }); const facade = await import(pathToFileURL(path.join(out, "openscad-format-facade.js"))); const { FacadeSerialQueue } = await import(pathToFileURL(path.join(out, "facade-envelope.js"))); const started = performance.now(); ({ kernel } = await loadWasmProbe(path.resolve(root, process.env.FREECAD_OPENSCAD_BUSINESS_BUILD ?? "web/public/kernel"), "FreeCADWebOpenSCADRuntimeProbe")); timings.loadSeconds = (performance.now() - started) / 1000; const queue = new FacadeSerialQueue(); const call = async (input, checks, sourceData = source) => { const began = performance.now(); const actual = await facade.dispatchOpenScadImportDxf(queue, facade.createOpenScadImportDxfRequest(`openscad-business-${++sequence}`, input), kernel); timings.operations.push({ path: input.path, documentId: input.documentId, seconds: (performance.now() - began) / 1000 }); const assertions = checks.map(([name, check]) => ({ name, passed: Boolean(check(actual)) })); cases.push({ input: { ...input, source: sourceData }, actual, assertions }); if (assertions.some(item => !item.passed)) { const failedAssertions = assertions.filter(item => !item.passed).map(item => item.name); throw new Error(`DXF business assertion failed: ${JSON.stringify({ actual, failedAssertions, input })}`); } return actual.result; }; const contours = [ { layer: "Single", x: 0, width: 5, height: 3 }, { layer: "Double", x: 20, width: 8, height: 4 }, { layer: "Double", x: 40, width: 6, height: 7 }, ]; const source = dxf(contours); const sourcePath = "/tmp/openscad-business-contours.dxf"; kernel.FS_createDataFile("/tmp", "openscad-business-contours.dxf", new Uint8Array(Buffer.from(source, "utf8")), true, true); files.push(sourcePath); const payload = (documentId, layer) => ({ schema: facade.OPENSCAD_IMPORT_DXF_SCHEMA, module: "OpenSCAD", documentId, path: sourcePath, layer }); const success = (count, areas = count === 1 ? [15] : [32, 42]) => [ ["native import creates the expected number of faces", reply => reply.kind === "result" && reply.result.faceCount === count && reply.result.objectCount === count], ["native objects have distinct names", reply => reply.kind === "result" && new Set(reply.result.objectNames).size === count], ["OCCT areas match the actual input contours", reply => reply.kind === "result" && reply.result.geometryBackend === "OCCT" && Array.isArray(reply.result.faceAreas) && reply.result.faceAreas.length === areas.length && [...reply.result.faceAreas].sort((a, b) => a - b).every((area, index) => Math.abs(area - [...areas].sort((a, b) => a - b)[index]) < 1e-7)], ]; const first = await call(payload("OpenSCADBusinessA", "Single"), [...success(1), ["requested document is created", reply => reply.result?.createdDocument === true && reply.result.documentId === "OpenSCADBusinessA" && reply.result.documentObjectCount === 1]]); await call(payload("OpenSCADBusinessA", "Double"), [...success(2), ["second layer appends distinct faces in the same document", reply => reply.result?.createdDocument === false && reply.result.documentHandle === first.documentHandle && reply.result.documentObjectCount === 3 && reply.result.objectNames.every(name => !first.objectNames.includes(name))]]); await call(payload("OpenSCADBusinessB", "Double"), [...success(2), ["another requested document is isolated", reply => reply.result?.createdDocument === true && reply.result.documentHandle !== first.documentHandle]]); for (const input of [ { ...payload("OpenSCADBusinessA", "Single"), path: "/tmp/openscad-business-missing.dxf" }, payload("OpenSCADBusinessRollback", "AbsentLayer"), ]) { await call(input, [["import rejects unavailable source or layer", reply => reply.kind === "error"]]); } await call(payload("OpenSCADBusinessRollback", "Single"), [...success(1), ["failed new-document import is rolled back before recovery", reply => reply.result?.createdDocument === true]]); await call(payload("OpenSCADBusinessA", "Single"), [...success(1), ["existing document survives failure and accepts another import", reply => reply.result?.createdDocument === false && reply.result.documentHandle === first.documentHandle && reply.result.objectNames.every(name => !first.objectNames.includes(name))]]); await call(payload("OpenSCADAllLayers"), success(3, [15, 32, 42])); const holeSource = dxf([{ layer: "Hole", x: 0, width: 10, height: 10 }, { layer: "Hole", x: 2, y: 2, width: 3, height: 3 }]); const holePath = "/tmp/openscad-business-hole.dxf"; kernel.FS_createDataFile("/tmp", "openscad-business-hole.dxf", new Uint8Array(Buffer.from(holeSource)), true, true); files.push(holePath); await call({ ...payload("OpenSCADHole", "Hole"), path: holePath }, success(1, [91]), holeSource); } } catch (error) { failure = String(error.stack ?? error); if (error.stderr?.length) fs.writeSync(2, error.stderr); } finally { const began = performance.now(); for (const file of files) { try { kernel.FS_unlink(file); } catch (error) { failure ??= String(error); } } if (out) fs.rmSync(out, { recursive: true, force: true }); timings.cleanupSeconds = (performance.now() - began) / 1000; } for (const id of ids) results.push(supportedIds.includes(id) ? { capabilityId: id, status: failure || !cases.length ? "failed" : "passed", cases, ...(failure ? { error: failure } : {}) } : { capabilityId: id, status: "not_implemented", cases: [] }); await new Promise((resolve, reject) => process.stdout.write(JSON.stringify({ schema: "freecad-web-business-cases/v1", timings, results }) + "\n", error => error ? reject(error) : resolve())); process.exit(results.some(result => result.status !== "passed") ? 1 : 0);