Render R2: persist named render setups without re-evaluating geometry (#667)
render.settings.set saves validated named render setups (camera, output size, exposure, environment) as one undo step, and render.settings.get reads them back without applying a camera. When validated render settings are the only change, the engine keeps the evaluated model instead of rebuilding it. Setups are saved under design format 21; format-20 designs read as having none. Fixes #658; refs #620. Contributed by @0xlillith. Co-authored-by: lillith <35337584+0xlillith@users.noreply.github.com>
This commit is contained in:
1 parent
771a11feab
commit
289b521a65
22 files changed
+641
-16
No files matched your search
@@ -1,15 +1,75 @@
|
||||
//! Render capability discovery. No rendering, host probing or document mutation occurs here.
|
||||
//! Render capability discovery and persistent intent. No rendering or host probing.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde_json::{Value, json};
|
||||
use solidcraft_doc::render_settings::{RenderSettings, RenderSetup};
|
||||
|
||||
use super::CommandSpec;
|
||||
use crate::params::bad;
|
||||
use crate::{Result, Session};
|
||||
|
||||
pub static COMMANDS: &[CommandSpec] = &[CommandSpec::new("render.backends", "Render Backends", backends)
|
||||
pub static COMMANDS: &[CommandSpec] = &[
|
||||
CommandSpec::new("render.backends", "Render Backends", backends)
|
||||
.noundo()
|
||||
.doc_only()
|
||||
.params("No parameters. Returns {schema_version: 1, default_backend: builtin, backends: [{id, label, availability, executors: [{id, availability, profiles, limits, scratch}]}]}. Metadata only; does not start rendering or probe a GPU. See docs/render-backends.md")];
|
||||
.params("No parameters. Returns {schema_version: 1, default_backend: builtin, backends: [{id, label, availability, executors: [{id, availability, profiles, limits, scratch}]}]}. Metadata only; does not start rendering or probe a GPU. See docs/render-backends.md"),
|
||||
CommandSpec::new("render.settings.get", "Render Settings", get_settings).noundo().doc_only()
|
||||
.params("name?: saved setup. Without name returns {setups, defaults}; with name returns {name, setup}. Does not evaluate the model or apply a camera. See docs/render-settings.md"),
|
||||
CommandSpec::new("render.settings.set", "Save Render Settings", set_settings).doc_only()
|
||||
.params("name: setup name, set: object of fields to replace (backend, mode, camera, width, height, format, exposure_ev, environment_rotation, environment_intensity, background). Creates from defaults, validates atomically, one undo step; identical edits are no-ops. See docs/render-settings.md"),
|
||||
];
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct GetSettings {
|
||||
name: Option<String>,
|
||||
}
|
||||
|
||||
fn get_settings(s: &mut Session, params: &Value) -> Result<Value> {
|
||||
let cmd = "render.settings.get";
|
||||
let request: GetSettings = serde_json::from_value(params.clone()).map_err(|e| bad(cmd, e.to_string()))?;
|
||||
match request.name {
|
||||
None => Ok(json!({"setups": s.doc.render.setups, "defaults": RenderSetup::default()})),
|
||||
Some(name) => {
|
||||
RenderSettings::validate_name(&name).map_err(|e| bad(cmd, e.to_string()))?;
|
||||
let setup = s.doc.render.setups.get(&name).ok_or_else(|| bad(cmd, "no saved render setup with that name"))?;
|
||||
Ok(json!({"name": name, "setup": setup}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct SetSettings {
|
||||
name: String,
|
||||
set: Value,
|
||||
}
|
||||
|
||||
fn set_settings(s: &mut Session, params: &Value) -> Result<Value> {
|
||||
let cmd = "render.settings.set";
|
||||
let request: SetSettings = serde_json::from_value(params.clone()).map_err(|e| bad(cmd, e.to_string()))?;
|
||||
RenderSettings::validate_name(&request.name).map_err(|e| bad(cmd, e.to_string()))?;
|
||||
let patch = request.set.as_object().filter(|p| p.len() <= 10).ok_or_else(|| bad(cmd, "`set` must be an object with at most 10 fields"))?;
|
||||
let current = s.doc.render.setups.get(&request.name).cloned().unwrap_or_default();
|
||||
let mut value = serde_json::to_value(¤t).map_err(|e| bad(cmd, e.to_string()))?;
|
||||
let object = value.as_object_mut().ok_or_else(|| bad(cmd, "invalid render setup"))?;
|
||||
for (key, value) in patch {
|
||||
object.insert(key.clone(), value.clone());
|
||||
}
|
||||
let setup: RenderSetup = serde_json::from_value(value).map_err(|e| bad(cmd, e.to_string()))?;
|
||||
setup.validate().map_err(|e| bad(cmd, e.to_string()))?;
|
||||
let reply = json!({"name": request.name, "setup": setup});
|
||||
if s.doc.render.setups.get(&request.name) == Some(&setup) {
|
||||
return Ok(reply);
|
||||
}
|
||||
let mut settings = (*s.doc.render).clone();
|
||||
settings.setups.insert(request.name, setup);
|
||||
settings.validate().map_err(|e| bad(cmd, e.to_string()))?;
|
||||
s.doc_mut().render = Arc::new(settings);
|
||||
Ok(reply)
|
||||
}
|
||||
|
||||
fn backends(_: &mut Session, params: &Value) -> Result<Value> {
|
||||
if !params.as_object().is_some_and(|p| p.is_empty()) {
|
||||
@@ -89,3 +149,6 @@ mod tests {
|
||||
assert_eq!(s.evaluations, 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod settings_tests;
|
||||
Reference in new issue
Block a user