Model renders (screenshot with source model, ui.render) show the section the window shows: the CPU renderer drew the cut face on the far walls at their own depth in a plain grey hatch, so the inside of the body, its edges and the grid showed through it and the cut barely read as a section, it had no outline, perspective views cut along a curve, and an open Section Analysis dialog's preview (or a sketch's Slice) was ignored. The cut face now writes the section plane's depth where it is the visible surface (the CPU counterpart of the viewport's stencil cap), takes the viewport's cap tint and hatch, the cut outline is drawn (section_segments moved from the UI to engine::view and shared), world positions and lines are clipped perspective-correctly, and ui.render in the app uses the viewport's section plane
This commit is contained in:
1 parent
22672585c9
commit
21e0e99c86
7 files changed
+217
-46
No files matched your search
@@ -15,7 +15,8 @@
|
||||
//! - `ui.click {x, y, button?, shift?}`, `ui.move {x, y}`, `ui.scroll {x, y, delta}`: real
|
||||
//! pointer input in screen points; `ui.key {key, cmd?, shift?}`, `ui.text {text}`
|
||||
//! - `ui.screenshot {path?}`: PNG of the window; `ui.render {path, width?, height?}`: headless
|
||||
//! CPU render of the model with the current camera (no window needed)
|
||||
//! CPU render of the model with the current camera and the section the viewport shows (no
|
||||
//! window needed)
|
||||
//! - `ui.menu {x?, y?, target?, close?}`: open a context menu at a point (the viewport's marking
|
||||
//! menu, or a browser menu with `target: {type: body|sketch|component, …}`); without a point,
|
||||
//! the open menu and its items. `ui.menuPick {item}` runs an item by id or label;
|
||||
@@ -512,7 +513,9 @@ pub fn handle(app: &mut SolveApp, ctx: &egui::Context, req: &ControlRequest) ->
|
||||
"ui.render" => {
|
||||
let w = f("width").unwrap_or(1280.0).clamp(16.0, 8192.0) as usize;
|
||||
let h = f("height").unwrap_or(800.0).clamp(16.0, 8192.0) as usize;
|
||||
let scene = solvecraft_engine::view::scene(&app.session, &app.cam);
|
||||
// Cut by the section the viewport shows (an open Section Analysis dialog's preview,
|
||||
// a sketch's Slice), not only the committed one.
|
||||
let scene = solvecraft_engine::view::scene_with_section(&app.session, &app.cam, crate::viewport::section_plane(app));
|
||||
let Some(png) = render_png(&scene, &app.cam, w, h) else { return err("render failed") };
|
||||
match s("path") {
|
||||
Some(path) => match std::fs::write(path, &png) {
|
||||
@@ -552,3 +555,45 @@ pub fn save_screenshot(image: &egui::ColorImage, path: Option<&str>) -> Value {
|
||||
Err(e) => json!({"ok": false, "error": e.to_string()}),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use serde_json::json;
|
||||
use solvecraft_engine::{Sel, Session};
|
||||
|
||||
use super::*;
|
||||
use crate::Services;
|
||||
|
||||
fn render(app: &mut SolveApp, path: &std::path::Path) -> Vec<u8> {
|
||||
let (req, _rx) = ControlRequest::new("ui.render", json!({"path": path.to_string_lossy(), "width": 200, "height": 140}));
|
||||
match handle(app, &egui::Context::default(), &req) {
|
||||
Outcome::Done(v) => assert_eq!(v["ok"], true, "{v}"),
|
||||
Outcome::Screenshot { .. } => panic!("ui.render took a screenshot"),
|
||||
}
|
||||
std::fs::read(path).unwrap()
|
||||
}
|
||||
|
||||
/// A model render shows the section the window shows: the open Section Analysis dialog's
|
||||
/// preview too, not only a committed `inspect.section`.
|
||||
#[test]
|
||||
fn model_render_shows_the_section_dialog_preview() {
|
||||
let mut s = Session::default();
|
||||
s.execute("solid.box", &json!({"length": 40, "width": 30, "height": 20})).unwrap();
|
||||
let mut app = SolveApp::new(s, Services::default());
|
||||
let dir = std::env::temp_dir().join(format!("solvecraft-render-section-{}", std::process::id()));
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let whole = render(&mut app, &dir.join("whole.png"));
|
||||
app.session.selection = vec![Sel::Plane { name: "XZ".into() }];
|
||||
app.start("inspect.section");
|
||||
let Some(crate::dialogs::Kind::Section { offset, .. }) = app.dialog.as_mut().map(|d| &mut d.kind) else { panic!("no section dialog") };
|
||||
*offset = "15 mm".into();
|
||||
assert!(app.session.section.is_none() && crate::viewport::section_plane(&app).is_some());
|
||||
let preview = render(&mut app, &dir.join("preview.png"));
|
||||
assert!(whole != preview, "the model render ignores the section the window shows");
|
||||
// Committed, it renders the same.
|
||||
app.run("inspect.section", json!({"plane": "XZ", "offset": 15})).unwrap();
|
||||
app.dialog = None;
|
||||
assert!(render(&mut app, &dir.join("committed.png")) == preview, "the committed section renders differently");
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user