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 <35337584+0xlillith@users.noreply.github.com>
This commit is contained in:
1 parent
e4ef6cfe29
commit
a261aaa841
70 files changed
+5860
-101
No files matched your search
@@ -26,7 +26,7 @@ pub static COMMANDS: &[CommandSpec] = &[
|
||||
CommandSpec::new("appearance.assign", "Appearance", appearance).at("SOLID", "MODIFY").icon("appearance").key("A").params(
|
||||
"bodies?: [names]; faces?: [[x,y,z] on faces] (body?: which body, default the nearest); components?: [names or ids, or occurrence names]; \
|
||||
appearance?: library name (appearance.library) | color: \"#rrggbb\" or [r, g, b] (0–255), opacity? (0–1, default 1); \
|
||||
neither (or color: null): clear, so the face follows its body, the body its component, the component its material",
|
||||
pbr?: {metallic:0..1, roughness:0.045..1, dielectric_ior:1..3} (requires color/appearance and opacity 1); omitted/null uses legacy scalar shading. Neither appearance nor color (or color: null): clear, so the face follows its body, the body its component, the component its material",
|
||||
),
|
||||
CommandSpec::new("appearance.library", "Appearance Library", appearance_library)
|
||||
.noundo()
|
||||
@@ -341,7 +341,7 @@ pub(super) fn color_param(v: &Value) -> Option<[u8; 3]> {
|
||||
fn appearance(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
use solidcraft_doc::appearance::{FaceLook, Look, library};
|
||||
let cmd = "appearance.assign";
|
||||
let look: Option<Look> = match (str_(p, "appearance"), p.get("color")) {
|
||||
let mut look: Option<Look> = match (str_(p, "appearance"), p.get("color")) {
|
||||
// A colour with a name: that colour, called that (a name not in the library is fine).
|
||||
(Some(n), Some(v)) if !v.is_null() => {
|
||||
let c = color_param(v).ok_or_else(|| bad(cmd, "`color` must be \"#rrggbb\" or [r, g, b] with values 0–255"))?;
|
||||
@@ -367,6 +367,11 @@ fn appearance(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
Some(Look::custom(c, o))
|
||||
}
|
||||
};
|
||||
if let Some(l) = &mut look {
|
||||
attach_pbr(cmd, p, l)?;
|
||||
} else if p.get("pbr").is_some_and(|v| !v.is_null()) {
|
||||
return Err(bad(cmd, "`pbr` requires a color or appearance"));
|
||||
}
|
||||
let bodies = string_list(p, "bodies");
|
||||
let st = s.model.state();
|
||||
if bodies.iter().any(|b| st.body(b).is_none()) {
|
||||
@@ -466,7 +471,7 @@ fn appearance(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
"bodies": bodies,
|
||||
"faces": faces.iter().map(|(b, p)| json!({"body": b, "point": p})).collect::<Vec<_>>(),
|
||||
"components": comps,
|
||||
"appearance": look.as_ref().map(|l| json!({"name": l.name, "color": l.hex(), "opacity": l.opacity})),
|
||||
"appearance": look.as_ref().map(look_json),
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -477,7 +482,40 @@ fn dist_to(b: &solidcraft_doc::ModelBody, p: solidcraft_geom::Vec3) -> f64 {
|
||||
}
|
||||
|
||||
pub(super) fn look_json(l: &solidcraft_doc::appearance::Look) -> Value {
|
||||
json!({"name": l.name, "color": l.hex(), "opacity": l.opacity})
|
||||
let mut value = json!({"name": l.name, "color": l.hex(), "opacity": l.opacity});
|
||||
if let Some(pbr) = l.pbr {
|
||||
value["pbr"] = json!(pbr);
|
||||
}
|
||||
value
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "pbr_appearance_tests.rs"]
|
||||
mod pbr_tests;
|
||||
|
||||
pub(super) fn attach_pbr(cmd: &str, params: &Value, look: &mut solidcraft_doc::appearance::Look) -> Result<()> {
|
||||
let Some(value) = params.get("pbr") else { return Ok(()) };
|
||||
if value.is_null() {
|
||||
look.pbr = None;
|
||||
return Ok(());
|
||||
}
|
||||
if let Some(opacity) = params.get("opacity")
|
||||
&& opacity.as_f64().filter(|v| v.is_finite() && *v == 1.0).is_none()
|
||||
{
|
||||
return Err(bad(cmd, "opaque PBR requires numeric opacity 1"));
|
||||
}
|
||||
let object = value
|
||||
.as_object()
|
||||
.filter(|o| o.len() == 3 && o.keys().all(|k| matches!(k.as_str(), "metallic" | "roughness" | "dielectric_ior")))
|
||||
.ok_or_else(|| bad(cmd, "`pbr` requires exactly metallic, roughness and dielectric_ior"))?;
|
||||
let number = |key: &str| object.get(key).and_then(Value::as_f64).ok_or_else(|| bad(cmd, format!("`pbr.{key}` must be a finite number")));
|
||||
let response = solidcraft_doc::pbr_appearance::PbrParameters::new(number("metallic")?, number("roughness")?, number("dielectric_ior")?)
|
||||
.map_err(|e| bad(cmd, e.to_string()))?;
|
||||
if look.opacity != 1.0 {
|
||||
return Err(bad(cmd, "opaque PBR requires opacity 1; refraction/transmission is not supported"));
|
||||
}
|
||||
look.pbr = Some(response);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn appearance_library(_s: &mut Session, _p: &Value) -> Result<Value> {
|
||||
@@ -524,7 +562,13 @@ fn appearance_list(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
}
|
||||
let in_design: Vec<Value> = used
|
||||
.iter()
|
||||
.map(|(l, b, f, c)| json!({"name": l.name, "color": l.hex(), "opacity": l.opacity, "bodies": b, "faces": f, "components": c}))
|
||||
.map(|(l, b, f, c)| {
|
||||
let mut value = look_json(l);
|
||||
value["bodies"] = json!(b);
|
||||
value["faces"] = json!(f);
|
||||
value["components"] = json!(c);
|
||||
value
|
||||
})
|
||||
.collect();
|
||||
let mut out = json!({
|
||||
"in_design": in_design,
|
||||
|
||||
Reference in new issue
Block a user