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
@@ -7,9 +7,12 @@ use serde_json::json;
|
||||
use solidcraft_engine::io::catalog::Catalog;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use solidcraft_engine::io::catalog::Definition;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct LocalLibrary {
|
||||
/// A delayed picker may update only the editor that requested it.
|
||||
token: Arc<()>,
|
||||
pub search: String,
|
||||
pub path: String,
|
||||
pub catalog: Option<Catalog>,
|
||||
@@ -22,15 +25,55 @@ fn display_order(mut catalog: Catalog) -> Catalog {
|
||||
catalog
|
||||
}
|
||||
|
||||
/// Runs `f` on the open Appearance dialog's state, once a file picker has answered.
|
||||
fn with_state(app: &mut SolveApp, f: impl FnOnce(&mut SolveApp, &mut Ap)) {
|
||||
let Some(mut dialog) = app.dialog.take() else { return };
|
||||
if let crate::dialogs::Kind::Appearance(k) = &mut dialog.kind {
|
||||
f(app, k);
|
||||
}
|
||||
if app.dialog.is_none() {
|
||||
app.dialog = Some(dialog);
|
||||
}
|
||||
fn editor<'a>(app: &'a mut SolveApp, token: &Arc<()>) -> Option<&'a mut Ap> {
|
||||
let crate::dialogs::Kind::Appearance(k) = &mut app.dialog.as_mut()?.kind else { return None };
|
||||
Arc::ptr_eq(&k.local.token, token).then_some(k)
|
||||
}
|
||||
|
||||
fn save_library(app: &mut SolveApp, token: Arc<()>, overwrite: bool) {
|
||||
let origin = app.session.doc.clone();
|
||||
app.pick_save("my-library.solidcraft-library.json", &["json"], move |app, path| {
|
||||
let Some(path) = path else { return };
|
||||
if !Arc::ptr_eq(&origin, &app.session.doc) {
|
||||
app.set_status("The library export document changed while choosing its destination", true);
|
||||
return;
|
||||
}
|
||||
match app.run("library.export", json!({"path":path,"overwrite":overwrite})) {
|
||||
Ok(result) => {
|
||||
let parsed = result.get("catalog").cloned().and_then(|v| serde_json::from_value::<Catalog>(v).ok()).map(display_order);
|
||||
let count = parsed.as_ref().map_or(0, |c| c.entries.len());
|
||||
if let Some(k) = editor(app, &token) {
|
||||
k.local.path = path;
|
||||
k.local.catalog = parsed;
|
||||
k.local.selected = None;
|
||||
k.local.search.clear();
|
||||
k.note = Some(format!("Saved {count} reusable definitions. The design is unchanged."));
|
||||
}
|
||||
}
|
||||
Err(error) => {
|
||||
if let Some(k) = editor(app, &token) {
|
||||
k.note = Some(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn open_library(app: &mut SolveApp, token: Arc<()>) {
|
||||
app.pick_open(move |app, path| {
|
||||
let Some(path) = path else { return };
|
||||
let Some(k) = editor(app, &token) else { return };
|
||||
let mut local = k.local.clone();
|
||||
local.path = path;
|
||||
local.catalog = None;
|
||||
local.selected = None;
|
||||
let note = load(app, &mut local).unwrap_or_else(|error| error);
|
||||
if let Some(k) = editor(app, &token) {
|
||||
k.local = local;
|
||||
k.note = Some(note);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Exports the design's library to `path` and shows it as the local library.
|
||||
@@ -74,17 +117,10 @@ pub fn rows(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut Ap, inputs: &[SelInpu
|
||||
let save = ui.button(crate::i18n::tr_label("Export design library..."));
|
||||
crate::scenario::publish_handle("library:export", save.rect.center());
|
||||
if save.clicked() {
|
||||
let overwrite = k.local.overwrite;
|
||||
if app.services.pick_save.is_some() {
|
||||
// The picker answers in a later frame (#1088); the export runs then.
|
||||
app.pick_save("my-library.solidcraft-library.json", &["json"], move |app, path| {
|
||||
if let Some(path) = path {
|
||||
with_state(app, |app, k| export(app, k, path, overwrite));
|
||||
}
|
||||
});
|
||||
save_library(app, k.local.token.clone(), k.local.overwrite);
|
||||
} else if !k.local.path.is_empty() {
|
||||
let path = k.local.path.clone();
|
||||
export(app, k, path, overwrite);
|
||||
export(app, k, k.local.path.clone(), k.local.overwrite);
|
||||
}
|
||||
}
|
||||
ui.label(
|
||||
@@ -106,17 +142,7 @@ fn import_rows(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut Ap, inputs: &[SelI
|
||||
}
|
||||
ui.horizontal(|ui| {
|
||||
if ui.button(crate::i18n::tr_label("Open library...")).clicked() {
|
||||
// The picker answers in a later frame (#1088); the library loads then.
|
||||
app.pick_open(|app, path| {
|
||||
if let Some(path) = path {
|
||||
with_state(app, |app, k| {
|
||||
k.local.path = path;
|
||||
k.local.catalog = None;
|
||||
k.local.selected = None;
|
||||
k.note = Some(load(app, &mut k.local).unwrap_or_else(|error| error));
|
||||
});
|
||||
}
|
||||
});
|
||||
open_library(app, k.local.token.clone());
|
||||
}
|
||||
let load_button = ui.add_enabled(!k.local.path.is_empty(), egui::Button::new(crate::i18n::tr_label("Load library")));
|
||||
crate::scenario::publish_handle("library:load", load_button.rect.center());
|
||||
@@ -194,6 +220,7 @@ fn import_rows(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut Ap, inputs: &[SelI
|
||||
Ok(_) => {
|
||||
if let Definition::Appearance(look) = entry.definition {
|
||||
k.color = look.color;
|
||||
k.pbr = None;
|
||||
k.opacity = look.opacity;
|
||||
k.look = Some(look.name.clone());
|
||||
k.remove_override = false;
|
||||
|
||||
Reference in new issue
Block a user