Files
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

120 lines
7.6 KiB
Markdown

# 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](render-opaque-pbr.md) 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.
```sh
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.