Files
OneCAD/crates/engine/src/cmd/pbr_appearance_tests.rs
T
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 <35337584+0xlillith@users.noreply.github.com>
2026-10-14 19:24:36 -04:00

147 lines
7.8 KiB
Rust

use serde_json::json;
use std::sync::Arc;
use crate::{Session, product_scene};
fn fixture() -> Session {
let mut s = Session::default();
s.execute("solid.box", &json!({"body_name":"Specimen","length":10,"width":20,"height":5})).unwrap();
s
}
#[test]
fn format20_scalar_looks_upgrade_and_format21_setups_keep_their_meaning() {
let mut s = fixture();
s.execute("appearance.assign", &json!({"bodies":["Specimen"],"appearance":"Original legacy look","color":"#b47846","opacity":0.4})).unwrap();
let before = serde_json::to_value(&s.doc.appearances).unwrap();
let mut legacy = serde_json::to_value(&*s.doc).unwrap();
legacy["format"] = json!("solidcraft/20");
legacy.as_object_mut().unwrap().remove("render");
let upgraded = crate::doc::Document::from_json(&legacy.to_string()).unwrap();
assert_eq!(upgraded.format, "solidcraft/21");
assert_eq!(serde_json::to_value(&upgraded.appearances).unwrap(), before);
assert!(upgraded.render.setups.is_empty());
s.execute("render.settings.set", &json!({"name":"Studio","set":{"width":320,"height":240}})).unwrap();
let mut saved: serde_json::Value = serde_json::from_str(&s.doc.to_json()).unwrap();
saved["format"] = json!("solidcraft/21");
let d = crate::doc::Document::from_json(&saved.to_string()).unwrap();
assert_eq!(d.format, crate::doc::format::format_string(crate::doc::format::FORMAT));
assert_eq!(serde_json::to_value(&d.appearances).unwrap(), before);
assert!(d.appearances.bodies["Specimen"].pbr.is_none());
assert_eq!(d.render.setups["Studio"].width, 320);
for version in [22, 23] {
saved["format"] = json!(format!("solidcraft/{version}"));
assert!(crate::doc::Document::from_json(&saved.to_string()).is_err());
}
}
#[test]
fn opaque_response_preserves_geometry_mass_undo_and_cold_reopening() {
let mut s = fixture();
let before = product_scene::extract(&s).unwrap();
let mass = s.execute("component.properties", &json!({})).unwrap();
s.execute(
"appearance.assign",
&json!({"bodies":["Specimen"],"color":"#b47846","appearance":"Original opaque test",
"pbr":{"metallic":1.0,"roughness":0.3,"dielectric_ior":1.5}}),
)
.unwrap();
let after = product_scene::extract(&s).unwrap();
assert!(Arc::ptr_eq(&before.meshes[0].mesh, &after.meshes[0].mesh));
assert_eq!(s.execute("component.properties", &json!({})).unwrap(), mass);
assert!(after.meshes[0].surface.is_some());
assert_eq!(after.required_features(), vec![crate::render::RenderFeature::Pbr]);
let saved = s.doc.to_json();
assert_eq!(serde_json::from_str::<serde_json::Value>(&saved).unwrap()["format"], "solidcraft/21");
let look = &s.doc.appearances.bodies["Specimen"];
assert_eq!(crate::doc::config::appearance_cell(&serde_json::to_string(look).unwrap()).unwrap().as_ref(), Some(look));
let list = s.execute("appearance.list", &json!({})).unwrap();
assert_eq!(list["in_design"][0]["pbr"]["metallic"], 1.0);
s.execute("edit.undo", &json!({})).unwrap();
assert!(product_scene::extract(&s).unwrap().meshes[0].surface.is_none());
s.execute("edit.redo", &json!({})).unwrap();
assert!(product_scene::extract(&s).unwrap().meshes[0].surface.is_some());
let mut reopened = Session::new(crate::doc::Document::from_json(&saved).unwrap());
assert_eq!(product_scene::extract(&reopened).unwrap().meshes[0].surface, after.meshes[0].surface);
assert_eq!(reopened.execute("component.properties", &json!({})).unwrap(), mass);
}
#[test]
fn invalid_optical_requests_refuse_atomically_and_scalar_export_cannot_lose_them() {
let mut s = fixture();
let doc = s.doc.clone();
let history = (s.undo.len(), s.redo.len(), s.revision);
for params in [
json!({"bodies":["Specimen"],"pbr":{"metallic":0,"roughness":0.5,"dielectric_ior":1.5}}),
json!({"bodies":["Specimen"],"color":"#ffffff","pbr":{"metallic":0,"roughness":0,"dielectric_ior":1.5}}),
json!({"bodies":["Specimen"],"color":"#ffffff","opacity":0.5,"pbr":{"metallic":0,"roughness":0.5,"dielectric_ior":1.5}}),
json!({"bodies":["Specimen"],"appearance":"Named","color":"#ffffff","opacity":"invalid","pbr":{"metallic":0,"roughness":0.5,"dielectric_ior":1.5}}),
json!({"bodies":["Specimen"],"appearance":"Named","color":"#ffffff","opacity":2,"pbr":{"metallic":0,"roughness":0.5,"dielectric_ior":1.5}}),
json!({"bodies":["Specimen"],"color":"#ffffff","pbr":{"metallic":0,"roughness":0.5,"dielectric_ior":1.5,"transmission":1}}),
] {
assert!(s.execute("appearance.assign", &params).is_err());
assert!(Arc::ptr_eq(&doc, &s.doc));
assert_eq!((s.undo.len(), s.redo.len(), s.revision), history);
}
s.execute(
"appearance.assign",
&json!({"bodies":["Specimen"],"color":"#ffffff",
"pbr":{"metallic":0,"roughness":0.5,"dielectric_ior":1.5}}),
)
.unwrap();
assert!(solidcraft_io::catalog::Catalog::from_document(&s.doc, "user.fixture", "Original", Default::default()).is_err());
let mut invalid: serde_json::Value = serde_json::from_str(&s.doc.to_json()).unwrap();
invalid["appearances"]["bodies"]["Specimen"]["opacity"] = json!(0.5);
assert!(crate::doc::Document::from_json(&invalid.to_string()).is_err());
invalid["appearances"]["bodies"]["Specimen"]["opacity"] = json!(1.0);
invalid["appearances"]["bodies"]["Specimen"]["pbr"]["roughness"] = json!(0.0);
assert!(crate::doc::Document::from_json(&invalid.to_string()).is_err());
}
#[test]
fn physical_default_look_and_face_override_keep_independent_optical_meaning() {
let mut s = fixture();
s.execute(
"material.create",
&json!({"name":"Original density fixture","density_g_cm3":0.7,"color":"#b47846",
"pbr":{"metallic":0,"roughness":0.4,"dielectric_ior":1.5}}),
)
.unwrap();
s.execute("material.assign", &json!({"bodies":["Specimen"],"material":"Original density fixture"})).unwrap();
let scene = product_scene::extract(&s).unwrap();
assert!(scene.meshes[0].surface.is_some());
s.execute("appearance.assign", &json!({"faces":[[5,10,5]],"body":"Specimen","color":"#0080a0"})).unwrap();
let scene = product_scene::extract(&s).unwrap();
assert!(scene.meshes[0].surface.is_some());
assert!(scene.meshes[0].faces.iter().any(|f| f.color == [0, 128, 160] && f.surface.is_none()));
assert_eq!(s.doc.user_materials[0].density, 0.7);
assert!(solidcraft_io::catalog::Catalog::from_document(&s.doc, "user.fixture", "Original", Default::default()).is_err());
}
#[test]
fn independent_component_copy_keeps_its_opaque_response_after_source_edits_and_reopen() {
let mut s = Session::default();
let part = s.execute("component.create", &json!({"name":"Part"})).unwrap()["component"].as_u64().unwrap();
s.execute("solid.box", &json!({"body_name":"Specimen","length":10,"width":20,"height":5})).unwrap();
s.execute(
"appearance.assign",
&json!({"components":["Part"],"color":"#b47846",
"pbr":{"metallic":1,"roughness":0.3,"dielectric_ior":1.5}}),
)
.unwrap();
s.execute("component.activate", &json!({"component":0})).unwrap();
let copied = s.execute("component.paste_new", &json!({"component":part,"translate":[30,0,0]})).unwrap()["component"].as_u64().unwrap();
let original = s.doc.appearances.components[&part].clone();
assert_eq!(s.doc.appearances.components[&copied], original);
s.execute(
"appearance.assign",
&json!({"components":["Part"],"color":"#b47846",
"pbr":{"metallic":1,"roughness":0.8,"dielectric_ior":1.5}}),
)
.unwrap();
assert_ne!(s.doc.appearances.components[&part], original);
assert_eq!(s.doc.appearances.components[&copied], original);
let reopened = crate::doc::Document::from_json(&s.doc.to_json()).unwrap();
assert_eq!(reopened.appearances.components[&copied], original);
}