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]>
This commit is contained in:
selune authored and GitHub committed 2026-10-14 19:24:36 -04:00
1 parent e4ef6cfe29
commit a261aaa841
70 files changed
+5860 -101

No files matched your search

+13
View File
@@ -24,6 +24,14 @@ unless **Replace existing library file** is explicitly selected. Export is a sna
the design definitions, not an automatic merge with entries in an existing catalog.
Editing a copied look/material uses the normal commands and does not edit its source.
The combined render integration uses main's merged #1127 asynchronous picker.
Model edits and tab changes wait for its answer while drawing/resizing continues.
Overwrite consent is captured before opening; export snapshots the design when
`library.export` runs after choosing the destination. Document identity is checked,
and an old library answer cannot replace another Appearance editor. Cancellation
does not read or write a library. The earlier #1114 candidate is no longer a runtime
dependency of the refreshed consumer.
The desktop panel applies appearances. Physical-material import/assignment is available
through `library.apply`; imported definitions also appear in the existing material list.
The browser can export a library through the existing download service. Interactive local
@@ -123,3 +131,8 @@ The copied design remains independent after successful assignment, undo/redo and
reopening. This library has no dependency on the pending render stack and does not change
the document format. User-visible evidence and actual native/wasm results are recorded in the PR;
geometry/robustness checks remain separate and no reference baseline is weakened.
The opaque PBR consumer makes v1 export fail explicitly when any body, component,
face or material-default appearance contains PBR parameters. Neither overwrite consent
nor an existing output file permits a lossy downgrade; use the richer transport
when available. Scalar catalog imports still select the scalar response explicitly.
+1 -1
View File
@@ -73,7 +73,7 @@ is available. Missing or incompatible pictures use the usual thumbnail generatio
The default `cache: false` omits the portable thumbnail too.
The repository's `docs/design-container.md` describes limits and encoding details.
Current designs use format 21, including design properties, named render setups, thin extrudes, sheet metal perforation fields, associative editable sketch patterns, editable associative offset-distance dimensions, the style of sketch text (bold, italic, alignment, spacing and the curve it follows), face anchors on a piece of a face by its own name, sheet metal rims and bend ends, bend edges for Convert to Sheet Metal, the tear corner relief of sheet metal rules, the names of projected edges, physical materials defined in the design, the font of sketch text, sheet metal corner relief shapes, sketch face anchors on cylinder caps, sketch axis
Current designs use format 21, including design properties, named render setups, opaque PBR looks, thin extrudes, sheet metal perforation fields, associative editable sketch patterns, editable associative offset-distance dimensions, the style of sketch text (bold, italic, alignment, spacing and the curve it follows), face anchors on a piece of a face by its own name, sheet metal rims and bend ends, bend edges for Convert to Sheet Metal, the tear corner relief of sheet metal rules, the names of projected edges, physical materials defined in the design, the font of sketch text, sheet metal corner relief shapes, sketch face anchors on cylinder caps, sketch axis
links, persistent
names from exact face history, exact planar shell-face anchors, persistent analytic offset boundary roles and
association profile references, durable sketch face anchors, periodic fit splines and versioned
+9 -1
View File
@@ -71,6 +71,10 @@ MCP server sends its folders this way on every request ([mcp.md](mcp.md)). Witho
Commands never open dialogs when run this way. `solidcraft-cli commands` prints the same registry.
Product images use `render.preflight/start/status/cancel/export` through `engine.execute`.
Keep the returned session-scoped job ID for subsequent calls; status does not block or
render. See [render jobs](render-jobs.md) for capture, cancellation and export semantics.
While an interactive native file picker is open, `ui.inspect` reports `file_picker_busy: true`.
Engine commands, `ui.file` and `ui.home` requests wait for its answer; window resizing,
inspection and screenshots remain available.
@@ -97,7 +101,7 @@ unit regardless of the design, pass an expression with one: `"value": "50 mm"` o
| Method | Params | Notes |
|---|---|---|
| `ui.inspect` | — | UI state, what is hidden (`visibility`: bodies, origin and construction items, sketches, joints (`hidden_joints`, joint ids); set it with the `browser.visibility` command), viewport rect, camera, active tool/dialog, the status line (`status`: text and whether it is an error), hover, renderer, timings, live preview (`active`, `busy`, `error`, `ms`, bodies it stands in for) |
| `ui.inspect` | — | UI state, pending file picker (`file_picker_busy`; compatibility alias `file_dialog_open`, no path), what is hidden (`visibility`: bodies, origin and construction items, sketches, joints (`hidden_joints`, joint ids); set it with the `browser.visibility` command), viewport rect, camera, active tool/dialog, the status line (`status`: text and whether it is an error), hover, renderer, timings, live preview (`active`, `busy`, `error`, `ms`, bodies it stands in for) |
| `ui.set` | any `UiState` field (`tab`, `perspective`, `showGrid`, `dark`, …), and `hiddenBodies`, `hiddenOrigin`, `hiddenSketches`, `shownSketches`, `hiddenJoints` (whole lists; `browser.visibility` changes single items) | `dark: false` switches to the light theme |
| `ui.view` | `view`: `front`, `back`, `top`, `bottom`, `left`, `right`, `iso`, `home`, `fit`; `animate` (bool, default false: snap) | |
| `ui.start` | `command` | like a toolbar click: starts the sketch tool or dialog for the command |
@@ -134,6 +138,10 @@ unit regardless of the design, pass an expression with one: `"value": "50 mm"` o
| `ui.resize` | `width`, `height` | |
| `app.quit` | `force?` (quit without asking about unsaved designs) | `quitting` (the window closes), `asking` (a "save changes?" prompt is open) |
`ui.workspace {workspace?: "design" | "render"}` reads or changes the active workspace.
Entering Render requires finishing the current modelling operation; returning restores
the Design camera. See [the integrated Render workspace](render-workspace.md).
## MCP
`solidcraft-cli mcp --connect 127.0.0.1:PORT` bridges an MCP client (an AI agent) to this
+9 -2
View File
@@ -1,6 +1,6 @@
# Features
SolidCraft's commands by area, generated by `cargo xtask features` from the command registry (`solidcraft-cli commands`). **354 commands.** Planned (registered, not built yet): 6. Every command runs the same way from the toolbar, a script, `solidcraft-cli exec <id> '<json>'`, the control channel and MCP; parameters are in the [command reference](book/src/reference/commands.md) (`cargo xtask book`). Status and plans: [ROADMAP](../ROADMAP.md).
SolidCraft's commands by area, generated by `cargo xtask features` from the command registry (`solidcraft-cli commands`). **363 commands.** Planned (registered, not built yet): 6. Every command runs the same way from the toolbar, a script, `solidcraft-cli exec <id> '<json>'`, the control channel and MCP; parameters are in the [command reference](book/src/reference/commands.md) (`cargo xtask book`). Status and plans: [ROADMAP](../ROADMAP.md).
## Sketch (99)
@@ -517,13 +517,20 @@ SolidCraft's commands by area, generated by `cargo xtask features` from the comm
| Insert Part | `parts.insert` | – |
| Change Part Size | `parts.resize` | – |
## `render.*` (3)
## `render.*` (10)
| Command | Id | Toolbar |
|---|---|---|
| Check Render Request | `render.preflight` | – |
| Render Image | `render.start` | RENDER › IMAGE |
| Render Progress | `render.status` | – |
| Cancel Render | `render.cancel` | – |
| Export Render Image | `render.export` | – |
| Render Backends | `render.backends` | – |
| Render Settings | `render.settings.get` | – |
| Save Render Settings | `render.settings.set` | – |
| Inspect Render Scene | `render.scene` | RENDER › SCENE |
| Apply Render Camera | `render.settings.apply` | – |
## `select.*` (2)
+5
View File
@@ -5,6 +5,11 @@ SolidCraft speaks the [Model Context Protocol](https://modelcontextprotocol.io)
An agent models exactly like a person does: every tool runs engine commands, the same ones the
toolbar, palette, scripts and control channel use, and never opens a dialog.
Persistent headless and bridged sessions expose asynchronous product images through the
ordinary `execute` tool: `render.preflight`, `render.start`, `render.status`,
`render.cancel` and `render.export`. Keep the complete returned job ID; image bytes remain
in the session until explicit PNG export. See [render jobs](render-jobs.md).
## Start it
```sh
+9 -3
View File
@@ -1,5 +1,9 @@
# Render backend capabilities (R1)
This page describes the R1 metadata contract. The installed GPU viewport and CPU
product-job hosts extend it through complete profiles; HDR input preparation adds
no lighting capability. See [the integrated phase boundaries](render-integration.md).
`render.backends` discovers render metadata without opening a window, probing a GPU,
evaluating the model or changing the document, camera, dirty state or undo/redo history.
It also leaves a model made stale by a live sketch gesture stale.
@@ -55,11 +59,13 @@ descriptors, not design-file settings. Executor IDs are scoped to their backend.
Backend/executor IDs are 1–64 ASCII bytes: a lowercase letter followed by lowercase
letters, digits, `.`, `_` or `-`. Both construction and deserialization validate IDs.
The integrated backend currently describes only the existing CPU raster snapshot
Without runtime hosts the integrated backend describes the existing CPU raster snapshot
path. Its `preview`/`image` profile exports PNG; it does not claim HDRI, PBR,
refraction, progressive rendering or a final-quality integrator. The existing GPU
viewport remains separate and is not advertised here until a GPU host can register
and probe its actual capabilities. CPU and GPU are execution paths, not separate
viewport registers separately when the UI installs its actual device/pipelines.
The installed GPU product viewport and CPU product-image host support the subsequent
[opaque PBR consumer](render-opaque-pbr.md); this does not alter the static raster
descriptor or enable HDRI/refraction/final rendering. CPU and GPU are execution paths, not separate
user-selectable engine families.
## Pure request resolution
+97
View File
@@ -0,0 +1,97 @@
# Phased render and material integration
Tracked in #842, with parent render design #620 and physical/visual material roadmap
#922. The combined integration keeps contribution ancestry; it is an alternative
review path, not evidence that the individual PRs have merged. Each result in GitHub
is pinned to its tested source revision. Do not reuse an older green result for a new
merge or describe compiled wasm as verified browser interaction.
## Delivery phases
| Phase | Contribution / deliverable | Acceptance gate |
|---|---|---|
| 1 | R2 #667, R3 #706, R4a #800: document-owned setups, product viewport, bounded CPU PNG jobs | Setup migration/save/reopen/undo, document and job isolation, strict capabilities, cancellation, native evidence and repository gates |
| 2 | Merged HDR input #837 and surface reference #1019 | Bounded codec/BRDF tests; unchanged existing CPU capabilities until actual consumers exist |
| 3 | Opaque product PBR #1046; engineering-card persistence #1060 | Explicit supported optical fields, source/condition preservation, one-step undo, cold reopening, unchanged geometry and independent assigned density |
| 4 | Merged scalar catalogs #863; richer transport #1086; inspector/import #1094 | Exact selected revisions, copied definitions, hostile-input bounds, no lossy downgrade, scoped file access, reviewed import and actual native evidence |
| 5 | Environment consumer #813, then texture/wood mapping #1004/#786 using resource admission #909 | Actual linear/sRGB interpretation and lighting, persistent object-local frames, cancellation/memory bounds, transformed instances, separate native/browser evidence |
| 6 | Family-specific optical/data quality under #922 | Wood L/R/T and moisture/grade conditions; polymer grade/process/temperature/rate; glass thickness/IOR/absorption; metal alloy/temper/conductor response and brushed anisotropy; source/license review before shipping data |
The released document format is 20 (v0.11.0); render setups and opaque PBR looks are saved
under format 21. Saved render setups migrate from older formats with older-reader refusal. The Design
viewport retains upstream display-plan tessellation; Render uses its separate
prepared product meshes. Neither runtime jobs nor device handles are persisted.
See the versioned contracts rather than reserving another format independently.
## Current foundation boundary
The GPU host provides a product **viewport**. The CPU product executor provides
bounded preview **PNG images**, with opt-in opaque PBR and approximate sorted transparency.
The [fixed studio consumer](render-opaque-pbr.md) provides direct lighting only;
it has no HDRI, cast shadows, transmission or indirect bounces. The Radiance
utility prepares linear RGB in the source colour space; it is not lighting. The
numerical opaque surface contract is not proof that a host renders those properties.
Imported resources or saved intent never silently add HDRI/refraction/final/EXR
support. Unsupported requests fail and preserve the last valid image and CAD state.
See [backends](render-backends.md), [settings](render-settings.md),
[workspace](render-workspace.md), [jobs](render-jobs.md),
[HDR input](render-hdr-input.md) and [surface reference](render-surface.md).
Both atomic PNG publication modes check the existing caller file scope. The
control-channel regression rejects outside folders and traversal and permits a
valid destination, including explicit overwrite. This guard and document-adoption
regression now belong to standalone R4a, so its safety does not depend on this
combined integration. Render engine and scene production code match that foundation. The combined UI
additionally consumes the async picker candidate described below; it does not add
a second renderer or file-access service.
## Picker integration before release
The merged upstream #1127 service (`file_picker`) replaces the earlier #1114
candidate in this branch. There is one picker service, provided by main. Its future
returns a selected path or cancellation; the event loop continues drawing/resizing
while model input, tab switching/closing and control engine commands wait.
The compatibility inspection flag `file_dialog_open` mirrors `file_picker_busy`
and exposes no paths. Render and library callbacks capture overwrite consent and
retain explicit job/editor/document identity. An evicted image fails instead of
substituting a newer result. Cancellation writes nothing. Source/host changes that
replace the requesting document refuse the export; a replaced editor cannot receive
an old library response. Controlled future regressions cover these boundaries.
Earlier native responsiveness evidence and tab-routing tests used #1114's candidate
and keep their original pins. They are not proof of the new hold-input policy.
The upstream service's own tests cover held input and resize frames; add native
review evidence before describing the refreshed product workflow as GUI-verified.
## Material, appearance and resource ownership
Physical properties, assigned density, visual appearances and environment lighting
are distinct. A card-only edit does not replace assigned density or visual style;
applying an appearance does not change mass. Missing engineering values stay unknown,
with per-property sources and measurement conditions. Numerical consistency is not
certification or a structural solver. Mechanical axes are not inferred from a texture.
The first scalar catalog remains usable without optical/engineering extensions.
Richer transport must preserve copied PBR/card records and reject lossy downgrade;
reopening a design must not require the source catalog. Keep source revisions,
conflicts and applicability explicit. All inspection/application uses existing engine
commands; programmatic calls never open pickers. Library export edits the reusable
file rather than the CAD design. Applying a definition is one undoable design edit.
Use original synthetic fixtures first. A licence string in imported data is a source
claim, not redistribution permission. Any shipped dataset or texture needs actual
source/license review and attribution. Commercial reference artwork is not copied.
No second renderer, certified material database or simulation solver is implied.
## Verification and collaboration
Run `cargo xtask ci` before each committed arc. Record local/remote results, pinned
upstream, actual platform and failures separately. Do not weaken geometry baselines,
mask warnings or infer whole-app performance from cached capture/cancellation times.
Attach actual app/PNG evidence for visible behavior; label older evidence with its
source revision instead of relabeling it as current. Use scoped original examples.
The private `plan/` documents are unavailable in this checkout; no content is
invented. Public contracts and current instructions govern this work. Propose bounded
interfaces in GitHub where a handoff is missing, invite feedback, and check existing
claims/PRs before implementing. Keep independent contributor branches untouched.
+119
View File
@@ -0,0 +1,119 @@
# 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.
+88
View File
@@ -0,0 +1,88 @@
# Opaque product surfaces
This is the first integrated consumer slice of #1004, under #813/#620. It uses the
existing Render workspace, product geometry, appearance assignment and PNG job host.
There is no second renderer or external material dataset.
## Apply and preserve a response
```json
{"command":"appearance.assign","params":{"bodies":["Specimen"],"color":"#b47846","pbr":{"metallic":1,"roughness":0.3,"dielectric_ior":1.5}}}
```
The Appearance editor has an **Opaque PBR** checkbox and separate Metallic,
Roughness and Dielectric IOR fields. Enabling it explicitly sets opacity to one.
View the response in Render or export a product PNG; Design retains its simple
color preview. Apply/OK, swatch reuse and component/face targeting use the existing
assignment command. A later scalar face override replaces a body's PBR response
on that face; it does not inherit the optical response implicitly.
Parameters are finite and bounded: metallic [0,1], perceptual roughness [0.045,1],
relative dielectric IOR [1,3]. Unknown optical fields and nonnumeric/out-of-range
opacity refuse before changing the design. PBR requires opacity exactly one.
IOR controls the nonmetal fraction's reflection; it does not enable refraction.
Omitting `pbr` or supplying null in an appearance assignment selects the previous
scalar response. Assignment replaces a look; it is not a partial parameter patch.
`material.create/edit` accept the same optional `pbr` block alongside a default
appearance/color. A density-only edit retains the entire existing look. Visual
response changes do not modify density, geometry, placement or engineering axes.
No grade-specific optical or mechanical values are inferred from a name or color.
Format **21** saves `pbr` inside existing body/face/component and material-default
looks. Missing fields retain scalar shading. Format-20 render setups and old looks
upgrade without reinterpretation; older readers refuse newly saved files instead
of dropping their optical response. Undo/redo, copying and cold reopening use the
existing design-owned values, with no external file dependency.
The current scalar catalog v1 cannot represent PBR. Conversion/export refuses a
PBR look or physical-material default instead of silently dropping its parameters.
In-design swatches retain and reuse the response. A richer catalog version remains
a separate integration step.
## Actual consumers and color
The installed integrated `gpu-viewport` and `cpu-product` image hosts advertise the
`pbr` feature. Requests require it when the captured product scene contains PBR.
The static legacy `cpu-raster` descriptor retains its previous capabilities.
No host gains HDRI, refraction, progressive/final rendering or EXR support.
Look colors are decoded from sRGB8 to linear sRGB once in the shared document/scene
adapter. PBR GPU vertices carry linear reflectance and optical parameters. The CPU
rasterizer interpolates world position and normals with perspective correction;
the GPU uses the corresponding fragment interpolation. Both use the original
[opaque BRDF](render-surface.md) and three fixed neutral directional lights in Z-up
world coordinates. They sum BRDF × relative incident radiance × light cosine,
apply per-channel Reinhard preview tone mapping, then encode exactly once.
Linear and sRGB GPU attachments have distinct output transfer paths.
This fixed studio has no cast shadows, environment illumination, indirect bounces,
emission, anisotropy, coating, absorption or transmission. Lighting magnitudes are
relative preview values, not calibrated lux or spectral data. The single-scattering
model can darken rough metals. Transparent legacy looks retain approximate sorted
alpha blending, including when seen in front of an opaque PBR surface; that is not
glass optics. Nondefault exposure/environment controls remain explicitly unsupported.
Scene uploads keep the 128 MiB bound, accounting for the larger PBR vertices.
CPU face-map admission accounts for retained optical descriptors. Existing immutable
capture, ownership, cancellation, stale-result rejection and PNG export policies
remain in effect. Invalid/nonfinite geometry or zero input normals refuse.
## Validation and original example
`examples/render-opaque-studio.json` builds four original spheres and a base,
varying metallic fraction and roughness independently while keeping base color.
Its values are an illustrative optical study, not engineering data for real grades.
The product offscreen test uses the actual installed callback/pipelines on Vulkan,
including both linear and sRGB attachments. For four metal/roughness combinations,
each comparison checks 12,454 interior pixels against CPU PNG output; observed mean
channel error is below 0.2/255, with no pixels beyond the 12/255 outlier threshold.
Silhouettes are excluded because CPU images use 2x supersampling and that test's GPU
target uses 1x. This is a scoped parity result, not browser or universal GPU certification.
Command tests check unchanged geometry/mass, atomic refusal, undo/redo, cold reopen,
physical default/face precedence, retained scalar format-20 looks upgraded to 21 and explicit v1
catalog refusal. A clicked editor regression checks one transaction and reopening
the same selected body. Native media and exact current-head gate results belong
in the PR. wasm compilation and actual browser interaction are separate evidence.
+4 -2
View File
@@ -2,8 +2,10 @@
Named setups save common render intent in the document. They do not start a render,
apply the navigation camera or make a currently unavailable backend available.
The Render workspace, scene extraction, assets, PBR materials and jobs are later slices
of [#620](https://github.com/bherbruck/solidcraft/issues/620).
The integrated workspace and jobs consume these settings through their own capability
checks. [Opaque PBR](render-opaque-pbr.md) is an opt-in appearance response; texture
and environment assets remain follow-ups of [#620](https://github.com/bherbruck/solidcraft/issues/620);
see [the combined integration boundary](render-integration.md).
## Commands
+95
View File
@@ -0,0 +1,95 @@
# Integrated Render workspace (R3)
R4a builds on this viewport contract with a separately advertised CPU product-image
executor and Check/Start/Cancel/Export controls. See [render jobs](render-jobs.md) for
the asynchronous protocol, image limits, captured revisions and PNG export. The legacy
`ui.render` snapshot remains a separate route.
Choose **Render** in the workspace switcher after finishing a modelling operation.
The current integrated GPU renderer shows a separate product scene. Return to Design
restores its camera, projection preference and existing viewport drawing policy.
No second engine is installed or selectable.
## Product scene
`engine::product_scene::extract` consumes authoritative evaluated geometry, reusing
the document's world/occurrence placement and mesh Arcs. Body, component, material,
face and imported appearances retain their existing precedence and opacity. Source
geometry is never transformed a second time or altered to serve a renderer.
The shared L1 `ProductScene`/`ProductMesh` contract contains world-space meshes,
resolved sRGB colours, opacity and face appearance overrides. Face indices are the
existing mesh-to-B-rep mapping, resolved through document appearance references;
they are not new persistent names or triangle-derived face identities.
There are no grid/axis/edge lines, sketches, construction objects, selection or hover
colours, section cuts, analysis shading, active-component fading, canvases or decals
in this initial product policy. Browser visibility still controls which bodies appear.
The existing Design viewport and CPU snapshot routes keep their current policies.
Extraction refuses a stale live-drag model. The explicit model-reading `render.scene {}`
command follows the normal engine evaluation boundary, then returns bounded metadata:
body names, triangle counts, resolved colours/opacity, face-override counts and bounds.
It does not return mesh payloads, change the document or add an undo step. Product
extraction admits at most 16,384 visible bodies.
## Settings and camera
The right panel stages a named setup's background and camera changes. **Save Setup**
records the current existing `Camera` snapshot through `render.settings.set`, with
one undo step; the other R2 fields retain their values. **Reload Saved Settings**
discards staged settings. Draft settings are separate for each open document.
**Restore Saved Camera** runs `render.settings.apply {name}`, preserving target,
distance, yaw, pitch, roll, FOV and up axis as view state. It neither edits the document
nor settles a stale model. The UI honours its up axis in Render; the Design camera
is restored when returning. Drag orbits, middle-drag pans, scrolling zooms, and
**Fit Product** frames visible product bodies without including sketches or selection.
Preview drafts do not make the document dirty until saved. Unknown backend preferences
and unsupported modes remain saved intent: the strict resolver reports the unsupported
viewport request. **Use Integrated Preview** is an explicit draft change, never an
automatic fallback. There is no final-mode or second-engine selector in this slice.
Saved exposure/environment/output fields are not newly applied to shading or export.
## Host capabilities and resources
`render.backends` always retains the built-in `cpu-raster` preview/PNG image executor.
Only an installed UI GPU host adds `gpu-viewport`, advertising **Preview / Viewport**
with no image formats or HDRI/refraction/progressive features. The installed
[opaque PBR consumer](render-opaque-pbr.md) adds the `pbr` feature. The host reports
effective limits from its created device and depth attachment: at most 4096 pixels
per dimension and 4,194,304 pixels, within the 256 MiB pixel scratch admission policy.
The estimate includes the selected MSAA colour/depth cost; it excludes scene geometry,
driver overhead and cached source meshes. It is not a measurement of total GPU memory.
Pure capability metadata lives in the session/host, outside persistence and history,
surviving document replacement, scratch sessions and document-tab switches. No wgpu
types enter the engine or document crates. A host without an installed GPU/depth buffer
reports that product viewport preview is unavailable, rather than silently using the
opaque CPU snapshot path to approximate transparent appearances.
GPU product batches are separate from Design batches while sharing the installed
device and pipelines. Model/visibility/appearance changes update the product scene;
camera movement updates uniforms without rebuilding vertices. Selection and section
state do not change the product buffers. Vertex uploads are bounded to 128 MiB and
checked before allocation; malformed/non-finite triangles and invalid opacity fail.
Reinstalling the host invalidates product uploads. This is not a general device-loss,
render-job or cancellation protocol; those ownership mechanisms belong to R4.
Transparency uses the existing raster far/near alpha-blending approximation, without
refraction or HDR illumination. Opaque PBR uses a separate surface batch in the
same installed renderer. R3 itself added no export job; R4a supplies CPU product
PNG jobs. There is no result gallery or Bevy dependency. Saved setups were added
in format 21, alongside opt-in optical parameters on existing looks.
`ui.render` remains the existing Design CPU snapshot API; `ui.screenshot` captures
the actual integrated Render workspace. Product-image export uses `render.start`
and `render.export`, rather than the Design snapshot API.
## Reproduce
Run the original procedural `examples/render-studio.json` through the engine/CLI and
open its saved design in the app. Select Render, adjust its background or camera and
save a setup. The fixture uses original boxes/cylinders and simple colour/opacity
assignments; no commercial CAD files, artwork or materials are used. Actual Windows
Vulkan/MSAA evidence is attached to [#673](https://github.com/bherbruck/solidcraft/issues/673).