Something went wrong. Try again.
atproto Thingiverse but good
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225const DEFAULT_STEP_URL = '/models/EInk_click_v101.step';const DEFAULT_LIN_DEFLECTION = 0.1;const DEFAULT_ANG_DEFLECTION = 0.1;
let openCascadePromise;
self.postMessage({ kind: 'ready' });
self.onmessage = async (event) => { const msg = event.data || {}; if (msg.command !== 'convert-step') { return; }
const timings = []; const startedAt = performance.now();
try { const stepUrl = msg.stepUrl || DEFAULT_STEP_URL; const linDeflection = Number(msg.linDeflection ?? DEFAULT_LIN_DEFLECTION); const angDeflection = Number(msg.angDeflection ?? DEFAULT_ANG_DEFLECTION);
mark(timings, 'fetch:start'); const stepBytes = new Uint8Array(await (await fetch(stepUrl)).arrayBuffer()); mark(timings, 'fetch:end');
mark(timings, 'init:start'); const oc = await loadOpenCascade(); validateRequiredBindings(oc); mark(timings, 'init:end');
mark(timings, 'read:start'); writeFile(oc, '/model.step', stepBytes); const doc = readStepIntoDocument(oc, '/model.step'); mark(timings, 'read:end');
mark(timings, 'tessellate:start'); const shape = firstFreeShape(oc, doc); if (oc.BRepTools?.Clean && shape) { oc.BRepTools.Clean(shape, false); } if (shape) { new oc.BRepMesh_IncrementalMesh_2(shape, linDeflection, false, angDeflection, false); } mark(timings, 'tessellate:end');
mark(timings, 'export:start'); const glbBytes = exportDocumentToGlb(oc, doc, '/model.glb'); mark(timings, 'export:end');
self.postMessage({ kind: 'converted', timings, totalMs: performance.now() - startedAt, stepBytes: stepBytes.byteLength, glbBytes: glbBytes.byteLength, buffer: glbBytes.buffer, crossOriginIsolated: self.crossOriginIsolated === true, hardwareConcurrency: navigator.hardwareConcurrency || null, }, [glbBytes.buffer]); } catch (error) { self.postMessage({ kind: 'error', timings, totalMs: performance.now() - startedAt, message: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : undefined, crossOriginIsolated: self.crossOriginIsolated === true, hardwareConcurrency: navigator.hardwareConcurrency || null, }); }};
async function loadOpenCascade() { if (!openCascadePromise) { openCascadePromise = (async () => { const openCascadeUrl = new URL('/vendor/opencascade/opencascade.js', self.location.origin).href; const module = await import(openCascadeUrl); const init = module.default || module.initOpenCascade || module.init; if (typeof init !== 'function') { throw new Error('OpenCascade module did not export an init function'); } const options = { locateFile(path) { if (path.endsWith('.wasm')) { return '/vendor/opencascade/opencascade.wasm'; } return `/vendor/opencascade/${path}`; }, }; return init(options); })(); } return openCascadePromise;}
function writeFile(oc, path, bytes) { if (oc.FS.analyzePath(path).exists) { oc.FS.unlink(path); } oc.FS.writeFile(path, bytes);}
function validateRequiredBindings(oc) { const required = [ ['STEPCAFControl_Reader_1', 'STEPCAFControl_Reader'], ['TDocStd_Document'], ['Handle_TDocStd_Document_2'], ['TCollection_ExtendedString_1'], ['XCAFDoc_DocumentTool'], ['XCAFDoc_ShapeTool'], ['TDF_LabelSequence_1'], ['BRepMesh_IncrementalMesh_2', 'BRepMesh_IncrementalMesh'], ['RWGltf_CafWriter'], ['TCollection_AsciiString_2'], ['TColStd_IndexedDataMapOfStringString_1'], ]; const missing = required .filter((alternatives) => !alternatives.some((name) => oc[name] !== undefined)) .map((alternatives) => alternatives.join(' or ')); if (missing.length > 0) { throw new Error(`OpenCascade.js build is missing required STEP→GLB bindings: ${missing.join(', ')}`); }}
function readStepIntoDocument(oc, path) { const reader = constructFirst(oc, ['STEPCAFControl_Reader_1', 'STEPCAFControl_Reader']); const readStatus = reader.ReadFile(path); if (readStatus !== undefined && isFailureStatus(oc, readStatus)) { throw new Error(`STEP read failed with status ${readStatus}`); }
const doc = constructDocument(oc); const handle = construct(oc, 'Handle_TDocStd_Document_2', doc); const progress = optionalConstruct(oc, 'Message_ProgressRange_1'); const transferred = progress ? callFirst(reader, ['Transfer_1', 'Transfer'], [handle, progress]) : callFirst(reader, ['Transfer_1', 'Transfer'], [handle]); if (transferred === false) { throw new Error('STEP transfer into XDE document failed'); } return doc;}
function constructDocument(oc) { const name = construct(oc, 'TCollection_ExtendedString_1'); return construct(oc, 'TDocStd_Document', name);}
function firstFreeShape(oc, doc) { if (!oc.XCAFDoc_DocumentTool?.ShapeTool) { return undefined; } const shapeToolHandle = oc.XCAFDoc_DocumentTool.ShapeTool(doc.Main()); const shapeTool = shapeToolHandle?.get ? shapeToolHandle.get() : shapeToolHandle; const labels = construct(oc, 'TDF_LabelSequence_1'); shapeTool.GetFreeShapes(labels); if (labels.Length && labels.Length() < 1) { return undefined; } const label = labels.First(); return callFirst(oc.XCAFDoc_ShapeTool, ['GetShape_2', 'GetShape'], [label]);}
function exportDocumentToGlb(oc, doc, outputPath) { if (!oc.RWGltf_CafWriter) { throw new Error('RWGltf_CafWriter is missing from this OpenCascade.js build'); } if (oc.FS.analyzePath(outputPath).exists) { oc.FS.unlink(outputPath); } const writer = new oc.RWGltf_CafWriter(construct(oc, 'TCollection_AsciiString_2', outputPath), true); const handle = construct(oc, 'Handle_TDocStd_Document_2', doc); const metadata = construct(oc, 'TColStd_IndexedDataMapOfStringString_1'); const progress = optionalConstruct(oc, 'Message_ProgressRange_1'); const exported = progress ? callFirst(writer, ['Perform_2', 'Perform'], [handle, metadata, progress]) : callFirst(writer, ['Perform_2', 'Perform'], [handle, metadata]); if (exported === false) { throw new Error('RWGltf_CafWriter failed to export GLB'); } return oc.FS.readFile(outputPath, { encoding: 'binary' });}
function construct(oc, name, ...args) { const ctor = oc[name]; if (typeof ctor !== 'function') { throw new Error(`${name} is missing from this OpenCascade.js build`); } return new ctor(...args);}
function optionalConstruct(oc, name, ...args) { return typeof oc[name] === 'function' ? new oc[name](...args) : undefined;}
function constructFirst(oc, names, ...args) { for (const name of names) { if (typeof oc[name] === 'function') { return new oc[name](...args); } } throw new Error(`${names.join(' or ')} is missing from this OpenCascade.js build`);}
function callFirst(target, names, args) { for (const name of names) { if (typeof target?.[name] === 'function') { return target[name](...args); } } throw new Error(`missing method ${names.join(' or ')}`);}
function isFailureStatus(oc, status) { if (typeof oc.IFSelect_ReturnStatus?.IFSelect_RetDone === 'number') { return status !== oc.IFSelect_ReturnStatus.IFSelect_RetDone; } return false;}
function mark(timings, label) { timings.push({ label, ms: performance.now() });}