import { createMeshRequest, MESH_FIXTURE, MeshResponse } from "./mesh-facade.js"; import { createMeshIoRequest, MESH_IO_FIXTURE, MeshIoResponse } from "./mesh-io-facade.js"; import { createMeshInspectRequest, MESH_INSPECT_FIXTURE, MeshInspectResponse } from "./mesh-inspect-facade.js"; import { createMeshPythonRequest, MESH_PYTHON_FIXTURE, MeshPythonResponse } from "./mesh-python-facade.js"; import { createMeshPersistenceRequest, MESH_PERSISTENCE_FIXTURE, MeshPersistenceResponse } from "./mesh-persistence-facade.js"; interface State { stage: "starting" | "ready" | "success" | "expected-error" | "error" | "reset"; requestId: string | null; response: MeshResponse | MeshIoResponse | MeshInspectResponse | MeshPythonResponse | MeshPersistenceResponse | null; workerMessages: number } declare global { interface Window { __freecadMeshState: State } } const root = document.querySelector("#app"); if (!root) throw new Error("Mesh root is missing"); const actionMode = new URLSearchParams(window.location.search).get("action"); const ioMode = actionMode === "io"; const inspectMode = actionMode === "inspect"; const pythonMode = actionMode === "python"; const saveMode = actionMode === "save"; root.innerHTML = `
FCFreeCAD 1.2 WebMesh
${ioMode ? "IO" : inspectMode || pythonMode ? "R" : saveMode ? "S" : "△"}

${ioMode ? "Mesh import/export" : inspectMode ? "Source inventory" : pythonMode ? "Python API" : saveMode ? "Save and reopen" : "Mesh recompute"}

${ioMode ? "Source-backed STL round-trip" : inspectMode ? "Read-only native registration inventory" : pythonMode ? "Read-only native Python bindings" : saveMode ? "Source-backed FCStd persistence" : "Source-backed triangle fixture"}

Starting Mesh worker
`; const style = document.createElement("style"); style.textContent = `:root{color-scheme:dark;font-family:Inter,system-ui,sans-serif;background:#10161d;color:#e7edf3}*{box-sizing:border-box;letter-spacing:0}body{margin:0;min-width:320px;background:#10161d}header{align-items:center;background:#17212b;border-bottom:1px solid #2c3b48;display:flex;gap:12px;min-height:54px;padding:10px 16px}header b{color:#9ec7d2;margin-left:auto}.mark{align-items:center;background:#3b9db0;border-radius:5px;color:#071219;display:inline-flex;font-weight:800;height:28px;justify-content:center;width:28px}.workspace{display:grid;grid-template-columns:250px minmax(360px,1fr) 280px;min-height:calc(100vh - 54px)}aside{background:#17212b;padding:16px}aside:first-child{border-right:1px solid #2c3b48}aside:last-child{border-left:1px solid #2c3b48}h2{color:#8ea6b2;font-size:11px;letter-spacing:.08em;margin:0 0 14px;text-transform:uppercase}aside p{color:#afc3cc;font-size:13px;margin:10px 0}.canvas{align-items:center;background:#0d1319;display:flex;flex-direction:column;justify-content:center;min-height:520px;position:relative}.scene{align-items:center;background:#254452;border:1px solid #67b4c3;color:#9ee0e8;display:flex;font-size:42px;font-weight:800;height:110px;justify-content:center;transform:rotate(45deg);width:110px}.canvas h1{font-size:21px;margin:34px 0 8px}.canvas p{color:#8ea6b2;font-size:12px}.canvas #status{background:#17212be8;border:1px solid #31434f;bottom:18px;font-size:12px;padding:9px 12px;position:absolute}.canvas #status[data-level=ok]{color:#79d29d}.canvas #status[data-level=error]{color:#ff9e9e}dl{margin:0}dl div{border-bottom:1px solid #2c3b48;display:flex;justify-content:space-between;padding:10px 0}dt{color:#8ea2ae;font-size:12px}dd{font-size:12px;margin:0;text-align:right}.commands{display:grid;gap:8px;grid-template-columns:1fr 1fr 1fr;margin-top:18px}button{background:#1d2a35;border:1px solid #435464;border-radius:6px;color:#e7edf3;cursor:pointer;min-height:36px;padding:7px}button:disabled{opacity:.42}@media(max-width:820px){.workspace{grid-template-columns:1fr}aside:first-child{display:none}aside:last-child{border-left:0;border-top:1px solid #2c3b48}.canvas{min-height:58vh}}`; document.head.append(style); const status = document.querySelector("#status")!; const facets = document.querySelector("#facets")!; const points = document.querySelector("#points")!; const request = document.querySelector("#request")!; const run = document.querySelector("#run")!; const error = document.querySelector("#error")!; const reset = document.querySelector("#reset")!; const worker = new Worker(new URL("./mesh.worker.ts", import.meta.url), { type: "module" }); let sequence = 0; window.__freecadMeshState = { stage: "starting", requestId: null, response: null, workerMessages: 0 }; const setStatus = (text: string, level: "normal" | "ok" | "error" = "normal") => { status.textContent = text; status.dataset.level = level; }; worker.onmessage = (event: MessageEvent) => { const state = window.__freecadMeshState; state.workerMessages += 1; const message = event.data; if (message.kind === "ready") { state.stage = "ready"; run.disabled = false; error.disabled = false; reset.disabled = false; setStatus("Mesh worker ready", "ok"); return; } if (message.kind === "reset") { state.stage = "reset"; state.response = null; state.requestId = message.requestId; setStatus("Mesh state reset", "ok"); return; } state.response = message; state.requestId = message.requestId; request.textContent = message.requestId; if (message.kind === "error") { state.stage = message.error.code === "invalid_fixture" || message.error.code === "revision_mismatch" || message.error.code === "invalid_path" ? "expected-error" : "error"; setStatus(`${message.error.code}: ${message.error.message}`, state.stage === "expected-error" ? "ok" : "error"); return; } state.stage = "success"; if (ioMode && "exportBytes" in message.result) { facets.textContent = String(message.result.exportBytes); points.textContent = String(message.result.importedPointCount); setStatus("Mesh STL imported and exported", "ok"); } else if (inspectMode && "registeredTypeCount" in message.result) { facets.textContent = String(message.result.registeredTypeCount); points.textContent = String(message.result.pythonBindingCount); setStatus("Mesh source inventory inspected", "ok"); } else if (pythonMode && "apiCount" in message.result) { facets.textContent = String(message.result.apiCount); points.textContent = message.result.apiNames.join(", "); setStatus("Mesh Python API completed", "ok"); } else if (saveMode && "roundTrip" in message.result) { facets.textContent = String(message.result.facetCount); points.textContent = String(message.result.pointCount); setStatus("Mesh FCStd saved and reopened", "ok"); } else if ("facetCount" in message.result) { facets.textContent = String(message.result.facetCount); points.textContent = String(message.result.pointCount); setStatus("Mesh recomputed", "ok"); } }; run.onclick = () => worker.postMessage(ioMode ? createMeshIoRequest(`mesh-io-chrome-${++sequence}`) : inspectMode ? createMeshInspectRequest(`mesh-inspect-chrome-${++sequence}`) : pythonMode ? createMeshPythonRequest(`mesh-python-chrome-${++sequence}`) : saveMode ? createMeshPersistenceRequest(`mesh-save-chrome-${++sequence}`) : createMeshRequest(`mesh-chrome-${++sequence}`)); error.onclick = () => worker.postMessage(ioMode ? createMeshIoRequest(`mesh-io-error-${++sequence}`, { ...MESH_IO_FIXTURE, path: "/tmp/MeshUnexpected.stl" as typeof MESH_IO_FIXTURE.path }) : inspectMode ? createMeshInspectRequest(`mesh-inspect-error-${++sequence}`, { ...MESH_INSPECT_FIXTURE, sourceEntry: "src/Mod/Mesh/App/Unexpected.cpp" }) : pythonMode ? createMeshPythonRequest(`mesh-python-error-${++sequence}`, { ...MESH_PYTHON_FIXTURE, sourceEntry: "src/Mod/Mesh/App/Unexpected.cpp" }) : saveMode ? createMeshPersistenceRequest(`mesh-save-error-${++sequence}`, { ...MESH_PERSISTENCE_FIXTURE, path: "/tmp/MeshUnexpected.FCStd" as typeof MESH_PERSISTENCE_FIXTURE.path }) : createMeshRequest(`mesh-error-${++sequence}`, { ...MESH_FIXTURE, vertices: [[0, 0, 0], [1, 0, 0], [0, 0, 1]] })); reset.onclick = () => worker.postMessage({ kind: "reset", requestId: `mesh-reset-${++sequence}` });