Files
OneCAD/docs/render-jobs.md
selune a261aaa841 Render: integrated workspace, preview image jobs and opaque PBR (#1046)
Lands the Render workspace (R3), bounded preview-image jobs with cancellation and atomic PNG export (R4a), and opaque PBR looks as one change.

- Render workspace: an overlay-free product preview on the installed GPU device. Returning to Design restores its camera. render.scene and render.settings.apply are added.
- Image jobs: render.preflight/start/status/cancel/export run bounded CPU preview jobs. Export captures the job, overwrite choice and document, and checks the caller's file scope before writing.
- Opaque PBR: looks can carry metal fraction, roughness and dielectric IOR. The product viewport and preview PNG use them; geometry and mass are unchanged. The v1 catalog export refuses PBR rather than dropping it.
- Render setups and PBR looks are saved under design format 21. Labels are in en/de/ja/ko/zh/ru.

Supersedes #706 and #800. Refs #620, #673, #756, #1004.

Contributed by @0xlillith.

Co-authored-by: lillith <[email protected]>
2026-10-14 19:24:36 -04:00

7.6 KiB

Product-image jobs (R4a)

R4a adds bounded CPU preview images to the Render workspace. The installed builtin / cpu-product executor supports Preview, Image and PNG. It does not promise refraction, HDRI, exposure, final mode or EXR; those requests fail explicitly. The subsequent opaque PBR consumer adds opt-in direct-lit surfaces through the same host. Legacy scalar looks and the Design snapshot renderer retain their existing shading.

One command path

render.preflight and render.start take setup (a saved name) OR settings (a RenderSetup object; omitted fields use its documented defaults), plus optional transparent_background and replace booleans, both false by default. Without either setup/settings, the current requested camera is used, or the camera is fitted to the product. Saving a setup is independent of starting a job. Unknown fields are errors.

Both commands reject stale geometry without settling a live modelling gesture. Finish the gesture or use the explicit render.scene boundary first. Capture uses R3's authoritative visible meshes, placements and appearances, without selection, edges, axes, grids, sections or analysis overlays. A cold capture can prepare world geometry and meshes synchronously; it is not a promise of bounded CAD evaluation latency. Preflight validates capabilities and estimates admission, but does not reserve a queue slot or the host memory pool. Start checks those resources again.

Start returns a typed job: {session, serial, generation}. Keep all three fields when calling render.status, render.cancel or render.export. IDs are never accepted by another document session, scratch copy, or a newly opened/recovered document. Status does no rendering and never waits for completion. It reports queued, preparing, running, encoding, cancelling, cancelled, completed or failed, with phase-local done/total, structured job errors and completed frame metadata. Progress restarts when a phase changes; it is not a time estimate.

The captured scene_generation is the session revision at start. Later edits, undo, camera changes and tab switches do not mutate the snapshot. An image continues to describe that captured scene; the UI labels a revision mismatch conservatively (view cache invalidation can also advance a revision). Runtime state is outside the document, undo, checkpoints and the design format. Closing/replacing a session cancels its work; the worker cannot publish a cancelled frame. Direct host-side document replacement preserves the installed image service while allocating fresh job ownership; old IDs and completed images never cross into the new document. replace: true cancels retained active and pending jobs before submitting the new request. A cancelled generation cannot publish late. A successful older generation never replaces a newer successful result.

Each document retains at most two live jobs, four recent terminal status records and one last successful image. Cancellation/failure preserves that image. A new successful image releases the previous image; old terminal metadata can still report completion, but it does not promise that its bytes remain exportable. Export requires an explicit retained completed job ID; it never silently exports a different/newer job.

Native and browser execution

Each application installs one CPU host, shared by its documents. Native execution uses one worker and at most two queued inputs across documents; it runs without UI polling. The browser uses the same incremental task, pumped by UI frames, without threads. Preparation, stable transparency sorting, clearing, pixel tests and downsampling check cancellation between small units. A step admits at most 16384 units; PNG encoding additionally yields after four rows. The UI pumps 1024 units per frame. These are work bounds, not hard real-time guarantees: allocation, codec compression and OS scheduling still have platform-dependent latency.

Admission caps output at 4096 per dimension and 4 million pixels; pixel scratch is estimated at 48 bytes per output pixel (2x supersampling). Other limits: 262144 triangles/face overrides/edge polylines, 128 MiB retained source geometry, 384 MiB total estimated memory per job, 512 MiB shared host admission including retained PNGs, and 128 million projected pixel tests. The projected-work limit is checked during bounded preparation and may fail an admitted job. Encoded PNG bytes have an additional 8 * output_pixels + 65536 cap. Allocator and codec internals are estimates, not an operating-system memory quota. Limits fail with errors rather than allocating unbounded buffers. Completed frames keep their memory reservation while bytes are retained.

The CPU path shades in linear light, blends sorted triangle alpha approximately, then encodes 8-bit sRGB with straight alpha and an sRGB PNG chunk. Transparent shared edges use a consistent fill rule. Intersecting transparent surfaces and triangle sorting remain raster approximations. This is not a glass/refraction renderer. Native GPU preview and CPU export can differ in shading and transparency.

PNG uses the already-transitive Rust png crate (MIT OR Apache-2.0), now an explicit render dependency. No external render engine or third-party art is introduced.

Export and automation

render.export takes job, path ending in .png, and overwrite (default false). The destination is checked against the caller's existing file-access scope before reading the retained frame or calling either native writer. Disallowed folders and traversal are refused in both overwrite modes. Native no-clobber export writes/syncs a temporary beside the target and publishes it with an atomic hard link; an existing destination wins even in a race. Filesystems without that operation return an error. Explicit overwrite uses the existing atomic writer without reading or backing up the previous target. Failed writes preserve the completed image and CAD state. Browser export uses the existing virtual file/download service; no filesystem paths or base64 payloads are persisted in the design.

The Render panel provides Check Render Request, Start Render, phase progress, Cancel Render and Export PNG. Replace existing file is an explicit opt-in. A failed or cancelled new render leaves the previous successful export available.

Native PNG export in the combined integration uses the document-bound asynchronous picker from #1114. The requested job ID and overwrite choice are captured before the dialog opens: cancellation writes nothing, closing the originating document drops the answer, and an evicted image fails instead of substituting a newer result. A tab switch does not redirect the export to another document.

Controls and phase messages use stable offline catalog IDs in English, Japanese, Chinese, German and Korean; command IDs and scenario handles remain unchanged.

solidcraft-cli render examples/render-studio.json --out product.png --width 800 --height 600
solidcraft-cli render design.solidcraft --setup Studio --out product.png --transparent

The CLI follows preflight/start/native wait/export, with a default 120-second wait limit (--timeout 1..3600). Timeout requests cancellation. --overwrite is required to replace an existing target. A persistent headless MCP session installs the same host: use the ordinary execute tool with the five commands and returned job ID. Closing the CLI/MCP process ends its session; a one-shot exec render.start is not a durable background service. In a browser, leave the app running so frames can pump work.

Validation evidence and responsiveness measurements are recorded in the R4a issue/PR; passing native tests or a wasm compile does not establish browser visual performance.