atproto Thingiverse but good
5.9 kB · 113 lines
Markdown
at main

Renderer STEP conversion evidence #

Environment #

  • Browser route tested: http://127.0.0.1:49177/viewer/body-f-chest-v4
  • Fixture path tested: public/models/EInk_click_v101.step served as /models/EInk_click_v101.step
  • Local tools observed: Node v24.15.0, npm 11.12.1, wasm-bindgen 0.2.121, dx 0.7.9, no emcc in PATH.

Commands run #

just fix
cargo check -p polymodel --target wasm32-unknown-unknown --features web
cargo check --workspace
just check
just build-renderer-worker
node tools/design-detector/detect.mjs --json assets/styling/viewer.css
node tools/renderer-step/prepare-full-npm-control.mjs
node tools/renderer-step/measure-assets.mjs
cargo tree -p polymodel --target wasm32-unknown-unknown --features web --edges normal
dx serve --addr 127.0.0.1 --port 49177 --interactive false --watch false --hot-reload false --open false

cargo check --workspace and just check emitted an unrelated jacquard-identity dead-code warning for invalidate_authority_chain; checks still passed.

Asset sizes measured #

Measured with node tools/renderer-step/measure-assets.mjs after staging the single-threaded custom opencascade.yml Docker build via just build-opencascade-step:

asset raw bytes gzip bytes note
public/vendor/opencascade/opencascade.js 134,503 34,346 single-thread custom JS staged from build output
public/vendor/opencascade/opencascade.wasm 6,128,224 2,719,991 single-thread custom WASM staged from build output
public/vendor/opencascade/opencascade.worker.js missing missing expected for single-thread build
public/step_worker_loader.js 300 230 committed loader
public/step_worker.js 7,449 2,222 committed worker harness

Earlier control measurement of published opencascade.js@1.1.1 was 65.9 MB raw / 13.7 MB gzip WASM and lacked the needed XDE/GLB bindings. The custom build is much smaller and exposes the required conversion path.

Browser observations #

Before clicking Convert STEP fixture, the browser fetched the Dioxus app and renderer worker assets. It did not fetch step_worker_loader.js, step_worker.js, /models/EInk_click_v101.step, or /vendor/opencascade/*.

After clicking Convert STEP fixture, Playwright network output showed lazy fetches:

GET /step_worker_loader.js                         200
GET /step_worker.js                                200
GET /models/EInk_click_v101.step                   200
GET /vendor/opencascade/opencascade.js             200
GET /vendor/opencascade/opencascade.wasm           200

No browser-console error came from the STEP worker. The only console errors observed were Dioxus dev-server websocket messages caused by running with hot reload disabled:

WebSocket connection to 'ws://127.0.0.1:49177/_dioxus?build_id=0' failed: net::ERR_CONNECTION_REFUSED

The page reported crossOriginIsolated=false. The single-threaded custom build does not need COOP/COEP or a pthread worker sidecar.

Conversion result #

The single-threaded custom OpenCascade.js build initialized inside the browser STEP worker, fetched EInk_click_v101.step, converted it to GLB bytes, and posted a converted result back to the Dioxus app.

The app passes the converted GLB object URL into the retained renderer session as MeshFormat::Gltf; the renderer worker loads and renders it successfully. Small electronics/PCB STEP fixtures need camera near-plane and orbit-distance settings that scale with the model bounds.

Validation run after the handoff and camera-fit fixes:

just check

just check passed, including the app check and polymodel-renderer-worker wasm check.

Earlier full-npm-control failures #

The published full npm control initialized inside a browser Worker far enough to validate bindings, fetch the STEP fixture, and load the 65.9 MB OCCT WASM. It could not perform the target STEP→GLB pipeline because the published package lacks required XDE/GLB bindings for this workflow.

Observed worker failures while tightening the harness:

STEP worker blocked after 1325 ms: Message_ProgressRange_1 is missing from this OpenCascade.js build; crossOriginIsolated=false.
STEP worker blocked after 4350 ms: TDF_LabelSequence_1 is missing from this OpenCascade.js build; crossOriginIsolated=false.

The final worker harness fails fast with a required-binding inventory, so future runs report the missing custom-build surface directly rather than one symbol at a time.

Dependency separation #

The app crate does not depend on OCCT or the STEP worker through Cargo. The wasm app dependency-tree check showed only:

polymodel
├── polymodel-api
├── polymodel-renderer-protocol

and no matches for three-d, three-d-asset, stl_io, polymodel-mesh, or opencascade in the app tree. OCCT assets are runtime-loaded by public/step_worker.js only after a STEP source is converted.

What this evidence does and does not decide #

Decided:

  • The worker split is the correct architecture boundary for client-side STEP: CAD conversion stays in a separate STEP worker, and rendering remains in the renderer worker.
  • The published full npm opencascade.js@1.1.1 artifact is not a viable Polymodel STEP→GLB path: it is large and lacks required bindings for the target XDE/GLB workflow.
  • A single-threaded custom OpenCascade.js build can convert STEP fixtures to GLB in a browser Worker without cross-origin isolation.
  • The existing renderer can load converted GLB output through the current blob: object-URL handoff.

Not decided:

  • A multithreaded pthread custom build was not measured.
  • Whether the product path should keep the blob: handoff or add direct transferred GLB bytes to the renderer protocol. Direct bytes would avoid object URL lifetime/fetch semantics, but the current handoff works.