browser.visibility hides and shows sketches, bodies, construction geometry and origin items (items: ids or names, or a whole folder; visible: bool), so agents can hide helper sketches over MCP. What is hidden moved from the egui UI state into the engine session (view state: not saved, not an undo step); the desktop eye toggles and menus use it, headless renders and fit leave hidden items out, and document.inspect, sketch.inspect and ui.inspect report visibility. ui.set still accepts hiddenBodies, hiddenOrigin, hiddenSketches and shownSketches. Tests: helper sketches hide and show in the engine (hostile input changes nothing) and through MCP only
This commit is contained in:
1 parent
0321778d38
commit
81be4e74b8
16 files changed
+306
-118
No files matched your search
@@ -27,7 +27,7 @@ pub fn map(app: &SolveApp) -> Option<AccessMap> {
|
||||
return Some(m);
|
||||
}
|
||||
let st = app.session.world_state();
|
||||
let meshes: Vec<_> = st.bodies.iter().filter(|b| !app.ui.hidden_bodies.contains(&b.name)).map(|b| b.mesh()).collect();
|
||||
let meshes: Vec<_> = st.bodies.iter().filter(|b| !app.session.visibility.hidden_bodies.contains(&b.name)).map(|b| b.mesh()).collect();
|
||||
// Every new map gets a new version (the GPU uploads it once).
|
||||
let version = VERSION.with(|v| {
|
||||
v.set(v.get() + 1);
|
||||
|
||||
@@ -80,19 +80,19 @@ impl OccMove {
|
||||
|
||||
/// Is a sketch drawn in the viewport?
|
||||
pub fn sketch_visible(app: &SolveApp, id: u64) -> bool {
|
||||
if app.ui.hidden_sketches.contains(&id) {
|
||||
if app.session.visibility.hidden_sketches.contains(&id) {
|
||||
return false;
|
||||
}
|
||||
app.ui.shown_sketches.contains(&id) || (app.ui.show_sketches && !solvecraft_engine::view::sketch_consumed(&app.session, id))
|
||||
app.session.visibility.shown_sketches.contains(&id) || (app.ui.show_sketches && !solvecraft_engine::view::sketch_consumed(&app.session, id))
|
||||
}
|
||||
|
||||
pub fn set_sketch_visible(app: &mut SolveApp, id: u64, on: bool) {
|
||||
app.ui.hidden_sketches.retain(|x| *x != id);
|
||||
app.ui.shown_sketches.retain(|x| *x != id);
|
||||
app.session.visibility.hidden_sketches.retain(|x| *x != id);
|
||||
app.session.visibility.shown_sketches.retain(|x| *x != id);
|
||||
if on {
|
||||
app.ui.shown_sketches.push(id);
|
||||
app.session.visibility.shown_sketches.push(id);
|
||||
} else {
|
||||
app.ui.hidden_sketches.push(id);
|
||||
app.session.visibility.hidden_sketches.push(id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,7 +126,7 @@ pub fn group_items(app: &SolveApp, id: u64) -> Vec<Item> {
|
||||
];
|
||||
if g.folder == "bodies" {
|
||||
let bodies = group_bodies(app, id);
|
||||
let hidden = !bodies.is_empty() && bodies.iter().all(|b| app.ui.hidden_bodies.contains(b));
|
||||
let hidden = !bodies.is_empty() && bodies.iter().all(|b| app.session.visibility.hidden_bodies.contains(b));
|
||||
v.push(Item::sep());
|
||||
v.push(Item::action(if hidden { "ui.show" } else { "ui.hide" }, "Show/Hide", "eye").key("V").with(json!({ "bodies": bodies })));
|
||||
v.push(Item::action("ui.isolate", "Isolate", "").with(json!({ "bodies": bodies })));
|
||||
@@ -158,8 +158,8 @@ pub fn folder_items(app: &SolveApp, component: u64, folder: &str) -> Vec<Item> {
|
||||
|
||||
/// The menu of the Origin folder.
|
||||
pub fn origin_items(app: &SolveApp) -> Vec<Item> {
|
||||
let planes = ["XY", "XZ", "YZ"].iter().all(|p| !app.ui.hidden_origin.iter().any(|h| h == p));
|
||||
let axes = ["X", "Y", "Z"].iter().all(|p| !app.ui.hidden_origin.iter().any(|h| h == p));
|
||||
let planes = ["XY", "XZ", "YZ"].iter().all(|p| !app.session.visibility.hidden_origin.iter().any(|h| h == p));
|
||||
let axes = ["X", "Y", "Z"].iter().all(|p| !app.session.visibility.hidden_origin.iter().any(|h| h == p));
|
||||
vec![
|
||||
Item::action("ui.origin", "Show/Hide", "eye").key("V"),
|
||||
Item::action("ui.originAll", "Show All", "eye"),
|
||||
@@ -520,7 +520,7 @@ fn component_rows(app: &mut SolveApp, ui: &mut egui::Ui, id: u64, depth: usize,
|
||||
let open = is_open(app, &key, true);
|
||||
let active = app.session.active_component == id;
|
||||
let bodies = crate::context_menu::component_bodies(app, id);
|
||||
let visible = bodies.is_empty() || bodies.iter().any(|b| !app.ui.hidden_bodies.contains(b));
|
||||
let visible = bodies.is_empty() || bodies.iter().any(|b| !app.session.visibility.hidden_bodies.contains(b));
|
||||
let grounded = app.session.doc.occurrence_of(id).is_some_and(|o| o.grounded);
|
||||
let faded = app.session.active_component != 0 && !app.session.doc.component_within(id, app.session.active_component) && !active;
|
||||
let name = component_name(app, id);
|
||||
@@ -546,9 +546,12 @@ fn component_rows(app: &mut SolveApp, ui: &mut egui::Ui, id: u64, depth: usize,
|
||||
}
|
||||
if r.eye {
|
||||
if visible {
|
||||
app.ui.hidden_bodies.extend(bodies.iter().filter(|b| !app.ui.hidden_bodies.contains(b)).cloned().collect::<Vec<_>>());
|
||||
app.session
|
||||
.visibility
|
||||
.hidden_bodies
|
||||
.extend(bodies.iter().filter(|b| !app.session.visibility.hidden_bodies.contains(b)).cloned().collect::<Vec<_>>());
|
||||
} else {
|
||||
app.ui.hidden_bodies.retain(|b| !bodies.contains(b));
|
||||
app.session.visibility.hidden_bodies.retain(|b| !bodies.contains(b));
|
||||
}
|
||||
}
|
||||
if r.clicked {
|
||||
@@ -661,7 +664,7 @@ fn origin_rows(app: &mut SolveApp, ui: &mut egui::Ui, depth: usize) {
|
||||
("YZ", "plane", "YZ"),
|
||||
];
|
||||
for (k, icon, label) in items {
|
||||
let visible = !app.ui.hidden_origin.iter().any(|h| h == k);
|
||||
let visible = !app.session.visibility.hidden_origin.iter().any(|h| h == k);
|
||||
let sel = match icon {
|
||||
"plane" => Some(Sel::Plane { name: k.into() }),
|
||||
"axis" => Some(Sel::Axis { name: k.into() }),
|
||||
@@ -675,9 +678,9 @@ fn origin_rows(app: &mut SolveApp, ui: &mut egui::Ui, depth: usize) {
|
||||
);
|
||||
if r.eye {
|
||||
if visible {
|
||||
app.ui.hidden_origin.push(k.into());
|
||||
app.session.visibility.hidden_origin.push(k.into());
|
||||
} else {
|
||||
app.ui.hidden_origin.retain(|h| h != k);
|
||||
app.session.visibility.hidden_origin.retain(|h| h != k);
|
||||
}
|
||||
}
|
||||
if r.clicked
|
||||
@@ -1001,7 +1004,7 @@ fn entries(app: &SolveApp, comp: u64, folder: &str) -> Vec<Entry> {
|
||||
row_key: format!("b:{}", b.name),
|
||||
label: b.name.clone(),
|
||||
icon: "body",
|
||||
visible: !app.ui.hidden_bodies.contains(&b.name),
|
||||
visible: !app.session.visibility.hidden_bodies.contains(&b.name),
|
||||
selected: sel.iter().any(|x| matches!(x, Sel::Body { name } if *name == b.name)),
|
||||
color: None,
|
||||
locked: app.ui.locked_bodies.contains(&b.name),
|
||||
@@ -1040,7 +1043,7 @@ fn entries(app: &SolveApp, comp: u64, folder: &str) -> Vec<Entry> {
|
||||
row_key: format!("p:{}", f.id),
|
||||
label: f.name.clone(),
|
||||
icon: "plane",
|
||||
visible: !app.ui.hidden_origin.contains(&f.name),
|
||||
visible: !app.session.visibility.hidden_origin.contains(&f.name),
|
||||
selected: sel.iter().any(|x| matches!(x, Sel::Plane { name } if *name == f.name)),
|
||||
color: None,
|
||||
locked: false,
|
||||
@@ -1079,9 +1082,9 @@ fn set_visible(app: &mut SolveApp, folder: &str, items: &[&Entry], on: bool) {
|
||||
for e in items {
|
||||
match folder {
|
||||
"bodies" => {
|
||||
app.ui.hidden_bodies.retain(|b| *b != e.key);
|
||||
app.session.visibility.hidden_bodies.retain(|b| *b != e.key);
|
||||
if !on {
|
||||
app.ui.hidden_bodies.push(e.key.clone());
|
||||
app.session.visibility.hidden_bodies.push(e.key.clone());
|
||||
}
|
||||
}
|
||||
"sketches" => {
|
||||
@@ -1090,9 +1093,9 @@ fn set_visible(app: &mut SolveApp, folder: &str, items: &[&Entry], on: bool) {
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
app.ui.hidden_origin.retain(|p| *p != e.label);
|
||||
app.session.visibility.hidden_origin.retain(|p| *p != e.label);
|
||||
if !on {
|
||||
app.ui.hidden_origin.push(e.label.clone());
|
||||
app.session.visibility.hidden_origin.push(e.label.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -358,7 +358,7 @@ fn viewport_items(app: &SolveApp) -> Vec<Item> {
|
||||
if has(|s| matches!(s, Sel::Face { .. })) {
|
||||
let face = sel.iter().find_map(|s| if let Sel::Face { body, .. } = s { Some(body.clone()) } else { None }).unwrap_or_default();
|
||||
let (feature, sketch) = body_feature_and_sketch(app, &face);
|
||||
let hidden = app.ui.hidden_bodies.contains(&face);
|
||||
let hidden = app.session.visibility.hidden_bodies.contains(&face);
|
||||
v.push(cmd(app, "sketch.create", "Create Sketch"));
|
||||
v.push(cmd(app, "solid.extrude", "Extrude"));
|
||||
v.push(cmd(app, "construct.plane.offset", "Offset Plane"));
|
||||
@@ -379,7 +379,7 @@ fn viewport_items(app: &SolveApp) -> Vec<Item> {
|
||||
v.push(cmd(app, "inspect.measure", "Measure"));
|
||||
} else if has(|s| matches!(s, Sel::Edge { .. })) {
|
||||
let body = sel.iter().find_map(|s| if let Sel::Edge { body, .. } = s { Some(body.clone()) } else { None }).unwrap_or_default();
|
||||
let hidden = app.ui.hidden_bodies.contains(&body);
|
||||
let hidden = app.session.visibility.hidden_bodies.contains(&body);
|
||||
v.push(cmd(app, "solid.fillet", "Fillet"));
|
||||
v.push(cmd(app, "solid.chamfer", "Chamfer"));
|
||||
v.push(Item::sep());
|
||||
@@ -403,9 +403,9 @@ fn viewport_items(app: &SolveApp) -> Vec<Item> {
|
||||
v.push(act("ui.fit", "Fit", "fit"));
|
||||
v.push(act("ui.home", "Home View", "home"));
|
||||
v.push(Item::sep());
|
||||
let anything_hidden = !app.ui.hidden_bodies.is_empty() || !app.ui.hidden_sketches.is_empty();
|
||||
let anything_hidden = !app.session.visibility.hidden_bodies.is_empty() || !app.session.visibility.hidden_sketches.is_empty();
|
||||
v.push(act("ui.showAll", "Show All", "eye").on(anything_hidden));
|
||||
v.push(act("ui.showAll", "Unisolate", "").on(!app.ui.hidden_bodies.is_empty()));
|
||||
v.push(act("ui.showAll", "Unisolate", "").on(!app.session.visibility.hidden_bodies.is_empty()));
|
||||
v.push(Item::sep());
|
||||
v.push(cmd(app, "solid.extrude", "Extrude"));
|
||||
v.push(cmd(app, "solid.fillet", "Fillet"));
|
||||
@@ -428,7 +428,7 @@ fn viewport_items(app: &SolveApp) -> Vec<Item> {
|
||||
|
||||
fn body_items(app: &SolveApp, bodies: &[String], browser: bool) -> Vec<Item> {
|
||||
let names = json!(bodies);
|
||||
let all_hidden = bodies.iter().all(|b| app.ui.hidden_bodies.contains(b));
|
||||
let all_hidden = bodies.iter().all(|b| app.session.visibility.hidden_bodies.contains(b));
|
||||
let one = bodies.len() == 1;
|
||||
let locked = bodies.iter().all(|b| app.ui.locked_bodies.contains(b));
|
||||
let items = json!(bodies.iter().map(|b| json!({"type": "body", "name": b})).collect::<Vec<_>>());
|
||||
@@ -506,7 +506,7 @@ fn component_items(app: &SolveApp, id: u64) -> Vec<Item> {
|
||||
let occ = doc.occurrence_of(id);
|
||||
let active = app.session.active_component == id;
|
||||
let bodies = component_bodies(app, id);
|
||||
let hidden = !bodies.is_empty() && bodies.iter().all(|b| app.ui.hidden_bodies.contains(b));
|
||||
let hidden = !bodies.is_empty() && bodies.iter().all(|b| app.session.visibility.hidden_bodies.contains(b));
|
||||
let names = json!(bodies);
|
||||
let mut v = Vec::new();
|
||||
if id != 0 {
|
||||
@@ -666,18 +666,19 @@ pub fn run_item(app: &mut SolveApp, item: &Item, at: Pos2) {
|
||||
"ui.finishSketch" => app.finish_sketch(),
|
||||
"ui.hide" => {
|
||||
for b in bodies {
|
||||
if !app.ui.hidden_bodies.contains(&b) {
|
||||
app.ui.hidden_bodies.push(b);
|
||||
if !app.session.visibility.hidden_bodies.contains(&b) {
|
||||
app.session.visibility.hidden_bodies.push(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
"ui.show" => app.ui.hidden_bodies.retain(|b| !bodies.contains(b)),
|
||||
"ui.show" => app.session.visibility.hidden_bodies.retain(|b| !bodies.contains(b)),
|
||||
"ui.showAll" => {
|
||||
app.ui.hidden_bodies.clear();
|
||||
app.ui.hidden_sketches.clear();
|
||||
app.session.visibility.hidden_bodies.clear();
|
||||
app.session.visibility.hidden_sketches.clear();
|
||||
}
|
||||
"ui.isolate" => {
|
||||
app.ui.hidden_bodies = app.session.world_state().bodies.iter().map(|b| b.name.clone()).filter(|b| !bodies.contains(b)).collect();
|
||||
app.session.visibility.hidden_bodies =
|
||||
app.session.world_state().bodies.iter().map(|b| b.name.clone()).filter(|b| !bodies.contains(b)).collect();
|
||||
}
|
||||
"ui.lock" => {
|
||||
let locked = bodies.iter().all(|b| app.ui.locked_bodies.contains(b));
|
||||
@@ -822,14 +823,14 @@ pub fn run_item(app: &mut SolveApp, item: &Item, at: Pos2) {
|
||||
}
|
||||
"ui.originAll" => {
|
||||
app.ui.show_origin = true;
|
||||
app.ui.hidden_origin.retain(|h| !["O", "X", "Y", "Z", "XY", "XZ", "YZ"].contains(&h.as_str()));
|
||||
app.session.visibility.hidden_origin.retain(|h| !["O", "X", "Y", "Z", "XY", "XZ", "YZ"].contains(&h.as_str()));
|
||||
}
|
||||
"ui.originPart" => {
|
||||
let show = p.get("show").and_then(Value::as_bool).unwrap_or(true);
|
||||
for k in strs(&p, "keys") {
|
||||
app.ui.hidden_origin.retain(|h| *h != k);
|
||||
app.session.visibility.hidden_origin.retain(|h| *h != k);
|
||||
if !show {
|
||||
app.ui.hidden_origin.push(k);
|
||||
app.session.visibility.hidden_origin.push(k);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -925,16 +926,16 @@ pub fn toggle_visibility(app: &mut SolveApp) {
|
||||
})
|
||||
.collect();
|
||||
let canvases = app.tree.picked_canvases.clone();
|
||||
let any_visible = bodies.iter().any(|b| !app.ui.hidden_bodies.contains(b))
|
||||
let any_visible = bodies.iter().any(|b| !app.session.visibility.hidden_bodies.contains(b))
|
||||
|| sketches.iter().any(|s| crate::browser::sketch_visible(app, *s))
|
||||
|| origin.iter().any(|o| !app.ui.hidden_origin.contains(o))
|
||||
|| origin.iter().any(|o| !app.session.visibility.hidden_origin.contains(o))
|
||||
|| app.session.doc.canvases.iter().any(|c| canvases.contains(&c.id) && c.visible);
|
||||
let hide = any_visible;
|
||||
app.ui.hidden_bodies.retain(|b| !bodies.contains(b));
|
||||
app.ui.hidden_origin.retain(|o| !origin.contains(o));
|
||||
app.session.visibility.hidden_bodies.retain(|b| !bodies.contains(b));
|
||||
app.session.visibility.hidden_origin.retain(|o| !origin.contains(o));
|
||||
if hide {
|
||||
app.ui.hidden_bodies.extend(bodies);
|
||||
app.ui.hidden_origin.extend(origin);
|
||||
app.session.visibility.hidden_bodies.extend(bodies);
|
||||
app.session.visibility.hidden_origin.extend(origin);
|
||||
}
|
||||
for s in sketches {
|
||||
crate::browser::set_sketch_visible(app, s, !hide);
|
||||
@@ -1007,7 +1008,7 @@ fn commit_rename(app: &mut SolveApp, r: &Rename) {
|
||||
match &r.what {
|
||||
RenameWhat::Body(old) => {
|
||||
if app.run("body.rename", json!({ "body": old, "name": name })).is_ok() {
|
||||
for list in [&mut app.ui.hidden_bodies, &mut app.ui.locked_bodies] {
|
||||
for list in [&mut app.session.visibility.hidden_bodies, &mut app.ui.locked_bodies] {
|
||||
for b in list.iter_mut().filter(|b| *b == old) {
|
||||
*b = name.clone();
|
||||
}
|
||||
|
||||
@@ -119,6 +119,7 @@ pub fn handle(app: &mut SolveApp, ctx: &egui::Context, req: &ControlRequest) ->
|
||||
"document.inspect" => wrap(app.session.execute("document.inspect", p).map_err(|e| e.to_string())),
|
||||
"ui.inspect" => ok(json!({
|
||||
"ui": serde_json::to_value(&app.ui).unwrap_or_default(),
|
||||
"visibility": app.session.visibility,
|
||||
"viewport": app.viewport.rect.map(|r| json!([r.left(), r.top(), r.width(), r.height()])),
|
||||
"camera": app.cam,
|
||||
"tool": app.tool.as_ref().map(|t| t.cmd),
|
||||
@@ -149,18 +150,30 @@ pub fn handle(app: &mut SolveApp, ctx: &egui::Context, req: &ControlRequest) ->
|
||||
ok(json!({"open": app.home.open, "recent": app.home.recent}))
|
||||
}
|
||||
"ui.set" => {
|
||||
// What is hidden lives in the session (`browser.visibility`); `ui.set` still sets it.
|
||||
const VIS: [(&str, &str); 4] = [
|
||||
("hiddenBodies", "hidden_bodies"),
|
||||
("hiddenOrigin", "hidden_origin"),
|
||||
("hiddenSketches", "hidden_sketches"),
|
||||
("shownSketches", "shown_sketches"),
|
||||
];
|
||||
let mut cur = serde_json::to_value(&app.ui).unwrap_or(json!({}));
|
||||
if let (Some(o), Some(src)) = (cur.as_object_mut(), p.as_object()) {
|
||||
let mut vis = serde_json::to_value(&app.session.visibility).unwrap_or(json!({}));
|
||||
if let (Some(o), Some(vo), Some(src)) = (cur.as_object_mut(), vis.as_object_mut(), p.as_object()) {
|
||||
for (k, v) in src {
|
||||
o.insert(k.clone(), v.clone());
|
||||
match VIS.iter().find(|(camel, _)| camel == k) {
|
||||
Some((_, snake)) => vo.insert(snake.to_string(), v.clone()),
|
||||
None => o.insert(k.clone(), v.clone()),
|
||||
};
|
||||
}
|
||||
}
|
||||
match serde_json::from_value::<crate::UiState>(cur) {
|
||||
Ok(u) => {
|
||||
match (serde_json::from_value::<crate::UiState>(cur), serde_json::from_value::<solvecraft_engine::Visibility>(vis)) {
|
||||
(Ok(u), Ok(v)) => {
|
||||
app.ui = u;
|
||||
app.session.visibility = v;
|
||||
ok(serde_json::to_value(&app.ui).unwrap_or_default())
|
||||
}
|
||||
Err(e) => err(e),
|
||||
(Err(e), _) | (_, Err(e)) => err(e),
|
||||
}
|
||||
}
|
||||
"ui.view" => {
|
||||
|
||||
@@ -27,10 +27,6 @@ pub struct DocSlot {
|
||||
/// The per-design part of the UI state (what is hidden, shown or locked).
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct DocView {
|
||||
hidden_bodies: Vec<String>,
|
||||
hidden_origin: Vec<String>,
|
||||
hidden_sketches: Vec<u64>,
|
||||
shown_sketches: Vec<u64>,
|
||||
hidden_profiles: Vec<u64>,
|
||||
shown_dims: Vec<u64>,
|
||||
locked_bodies: Vec<String>,
|
||||
@@ -63,10 +59,6 @@ impl Documents {
|
||||
fn take_view(app: &mut SolveApp) -> DocView {
|
||||
let u = &mut app.ui;
|
||||
DocView {
|
||||
hidden_bodies: std::mem::take(&mut u.hidden_bodies),
|
||||
hidden_origin: std::mem::take(&mut u.hidden_origin),
|
||||
hidden_sketches: std::mem::take(&mut u.hidden_sketches),
|
||||
shown_sketches: std::mem::take(&mut u.shown_sketches),
|
||||
hidden_profiles: std::mem::take(&mut u.hidden_profiles),
|
||||
shown_dims: std::mem::take(&mut u.shown_dims),
|
||||
locked_bodies: std::mem::take(&mut u.locked_bodies),
|
||||
@@ -75,10 +67,6 @@ fn take_view(app: &mut SolveApp) -> DocView {
|
||||
|
||||
fn put_view(app: &mut SolveApp, v: DocView) {
|
||||
let u = &mut app.ui;
|
||||
u.hidden_bodies = v.hidden_bodies;
|
||||
u.hidden_origin = v.hidden_origin;
|
||||
u.hidden_sketches = v.hidden_sketches;
|
||||
u.shown_sketches = v.shown_sketches;
|
||||
u.hidden_profiles = v.hidden_profiles;
|
||||
u.shown_dims = v.shown_dims;
|
||||
u.locked_bodies = v.locked_bodies;
|
||||
|
||||
@@ -101,9 +101,6 @@ pub struct UiState {
|
||||
pub show_grid: bool,
|
||||
pub show_origin: bool,
|
||||
pub perspective: bool,
|
||||
pub hidden_bodies: Vec<String>,
|
||||
/// Origin items (O, X, Y, Z, XY, XZ, YZ) and construction planes hidden one by one.
|
||||
pub hidden_origin: Vec<String>,
|
||||
pub show_sketches: bool,
|
||||
pub palette_open: bool,
|
||||
/// Dark theme (the default); a preference kept between runs.
|
||||
@@ -118,9 +115,6 @@ pub struct UiState {
|
||||
pub ground_shadow: bool,
|
||||
/// The world's up axis (Z or Y; #3): the camera, view cube, home view and ground follow it.
|
||||
pub up_axis: solvecraft_engine::render::UpAxis,
|
||||
/// Sketches hidden one by one, and finished sketches shown although a feature uses them.
|
||||
pub hidden_sketches: Vec<u64>,
|
||||
pub shown_sketches: Vec<u64>,
|
||||
/// Sketches whose closed profiles are not shaded.
|
||||
pub hidden_profiles: Vec<u64>,
|
||||
/// Finished sketches whose dimensions are shown.
|
||||
@@ -140,8 +134,6 @@ impl Default for UiState {
|
||||
show_grid: true,
|
||||
show_origin: true,
|
||||
perspective: false,
|
||||
hidden_bodies: Vec::new(),
|
||||
hidden_origin: Vec::new(),
|
||||
show_sketches: true,
|
||||
palette_open: false,
|
||||
dark: true,
|
||||
@@ -150,8 +142,6 @@ impl Default for UiState {
|
||||
visual_style: 0,
|
||||
ground_shadow: true,
|
||||
up_axis: Default::default(),
|
||||
hidden_sketches: Vec::new(),
|
||||
shown_sketches: Vec::new(),
|
||||
hidden_profiles: Vec::new(),
|
||||
shown_dims: Vec::new(),
|
||||
locked_bodies: Vec::new(),
|
||||
@@ -594,7 +584,7 @@ impl SolveApp {
|
||||
pub fn origin_visible(&self) -> bool {
|
||||
self.ui.show_origin
|
||||
|| (self.dialog.as_ref().is_some_and(|d| matches!(d.kind, dialogs::Kind::Sketch))
|
||||
&& !self.session.world_state().bodies.iter().any(|b| !self.ui.hidden_bodies.contains(&b.name)))
|
||||
&& !self.session.world_state().bodies.iter().any(|b| !self.session.visibility.hidden_bodies.contains(&b.name)))
|
||||
}
|
||||
|
||||
pub fn set_status(&mut self, s: impl Into<String>, error: bool) {
|
||||
|
||||
@@ -58,7 +58,7 @@ fn face_menu_hides_and_finds_the_body() {
|
||||
context_menu::run_item(&mut app, &find(&items, "Find in Timeline"), pos2(0.0, 0.0));
|
||||
assert!(matches!(app.session.selection.as_slice(), [solvecraft_engine::Sel::Feature { .. }]));
|
||||
context_menu::run_item(&mut app, &find(&items, "Show/Hide"), pos2(0.0, 0.0));
|
||||
assert_eq!(app.ui.hidden_bodies, vec![b]);
|
||||
assert_eq!(app.session.visibility.hidden_bodies, vec![b]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -69,8 +69,8 @@ fn body_menu_isolates_renames_locks_and_deletes() {
|
||||
let t = Target::Body { name: b.clone() };
|
||||
let items = context_menu::items(&app, &t);
|
||||
context_menu::run_item(&mut app, &find(&items, "Isolate"), pos2(0.0, 0.0));
|
||||
assert_eq!(app.ui.hidden_bodies.len(), 1, "the other body is hidden");
|
||||
assert!(!app.ui.hidden_bodies.contains(&b));
|
||||
assert_eq!(app.session.visibility.hidden_bodies.len(), 1, "the other body is hidden");
|
||||
assert!(!app.session.visibility.hidden_bodies.contains(&b));
|
||||
// Rename through the rename box.
|
||||
context_menu::run_item(&mut app, &find(&items, "Rename"), pos2(10.0, 10.0));
|
||||
assert!(context_menu::finish_rename(&mut app, Some("Base"), true));
|
||||
@@ -184,7 +184,7 @@ fn folder_and_group_menus_make_rename_and_drop_groups() {
|
||||
assert!(context_menu::finish_rename(&mut app, Some("Parts"), true));
|
||||
assert_eq!(app.session.doc.browser_groups[0].name, "Parts");
|
||||
act(&mut app, &t, "Show/Hide");
|
||||
assert_eq!(app.ui.hidden_bodies.len(), 2);
|
||||
assert_eq!(app.session.visibility.hidden_bodies.len(), 2);
|
||||
act(&mut app, &t, "Ungroup");
|
||||
assert!(app.session.doc.browser_groups.is_empty());
|
||||
act(&mut app, &folder, "New Group");
|
||||
@@ -351,7 +351,7 @@ fn menus_follow_fusion_order() {
|
||||
let origin = context_menu::items(&app, &Target::Origin);
|
||||
order(&origin, &["Show/Hide", "Show All", "Hide Planes", "Hide Axes"]);
|
||||
context_menu::run_item(&mut app, &find(&origin, "Hide Planes"), pos2(0.0, 0.0));
|
||||
assert!(["XY", "XZ", "YZ"].iter().all(|p| app.ui.hidden_origin.iter().any(|h| h == p)));
|
||||
assert!(["XY", "XZ", "YZ"].iter().all(|p| app.session.visibility.hidden_origin.iter().any(|h| h == p)));
|
||||
app.run("select.clear", json!({})).unwrap();
|
||||
let empty = context_menu::items(&app, &Target::Viewport);
|
||||
order(&empty, &["Pan", "Zoom", "Orbit", "Show All", "Unisolate", "Extrude", "Fillet"]);
|
||||
@@ -367,17 +367,17 @@ fn v_toggles_what_is_selected() {
|
||||
app.run("select.set", json!({"items": [{"type": "body", "name": b}, {"type": "plane", "name": "XY"}, {"type": "feature", "id": sk}]})).unwrap();
|
||||
crate::browser::set_sketch_visible(&mut app, sk, true);
|
||||
context_menu::toggle_visibility(&mut app);
|
||||
assert!(app.ui.hidden_bodies.contains(&b) && app.ui.hidden_origin.iter().any(|h| h == "XY"));
|
||||
assert!(app.session.visibility.hidden_bodies.contains(&b) && app.session.visibility.hidden_origin.iter().any(|h| h == "XY"));
|
||||
assert!(!crate::browser::sketch_visible(&app, sk));
|
||||
context_menu::toggle_visibility(&mut app);
|
||||
assert!(app.ui.hidden_bodies.is_empty() && app.ui.hidden_origin.is_empty());
|
||||
assert!(app.session.visibility.hidden_bodies.is_empty() && app.session.visibility.hidden_origin.is_empty());
|
||||
assert!(crate::browser::sketch_visible(&app, sk));
|
||||
// Components picked in the browser hide their bodies.
|
||||
app.run("component.from_bodies", json!({"bodies": [b.clone()]})).unwrap();
|
||||
app.run("select.clear", json!({})).unwrap();
|
||||
app.tree.picked_components = vec![app.session.doc.components[0].id];
|
||||
context_menu::toggle_visibility(&mut app);
|
||||
assert_eq!(app.ui.hidden_bodies, vec![b]);
|
||||
assert_eq!(app.session.visibility.hidden_bodies, vec![b]);
|
||||
assert_eq!(find(&context_menu::items(&app, &Target::Body { name: body(&app) }), "Show/Hide").shortcut, "V");
|
||||
}
|
||||
|
||||
|
||||
@@ -730,8 +730,7 @@ pub fn check(h: &mut Harness, e: &Value) -> Result<(), String> {
|
||||
}
|
||||
"hidden" => {
|
||||
// Bodies hidden in the view.
|
||||
let h = &ui["ui"]["hidden_bodies"];
|
||||
let h = if h.is_null() { &ui["ui"]["hiddenBodies"] } else { h };
|
||||
let h = &ui["visibility"]["hidden_bodies"];
|
||||
if h != v {
|
||||
return Err(format!("hidden bodies: got {h}, want {v}"));
|
||||
}
|
||||
|
||||
@@ -192,8 +192,9 @@ fn visible_sketches(app: &SolveApp) -> Vec<u64> {
|
||||
.iter()
|
||||
.filter(|s| {
|
||||
app.session.active_sketch == Some(s.feature)
|
||||
|| (!app.ui.hidden_sketches.contains(&s.feature)
|
||||
&& (app.ui.shown_sketches.contains(&s.feature) || (app.ui.show_sketches && !sketch_consumed(&app.session, s.feature))))
|
||||
|| (!app.session.visibility.hidden_sketches.contains(&s.feature)
|
||||
&& (app.session.visibility.shown_sketches.contains(&s.feature)
|
||||
|| (app.ui.show_sketches && !sketch_consumed(&app.session, s.feature))))
|
||||
})
|
||||
.map(|s| s.feature)
|
||||
.collect()
|
||||
@@ -210,9 +211,9 @@ fn scene_key(app: &SolveApp) -> u64 {
|
||||
app.cam.up.hash(&mut h);
|
||||
(app.cam.pitch > 0.0).hash(&mut h);
|
||||
app.ui.show_sketches.hash(&mut h);
|
||||
app.ui.hidden_bodies.hash(&mut h);
|
||||
app.ui.hidden_sketches.hash(&mut h);
|
||||
app.ui.shown_sketches.hash(&mut h);
|
||||
app.session.visibility.hidden_bodies.hash(&mut h);
|
||||
app.session.visibility.hidden_sketches.hash(&mut h);
|
||||
app.session.visibility.shown_sketches.hash(&mut h);
|
||||
app.ui.hidden_profiles.hash(&mut h);
|
||||
app.session.active_component.hash(&mut h);
|
||||
app.preview.replaced.hash(&mut h);
|
||||
@@ -262,7 +263,7 @@ fn build_scene(app: &SolveApp) -> GpuScene {
|
||||
}
|
||||
}
|
||||
for b in &st.bodies {
|
||||
if app.ui.hidden_bodies.contains(&b.name) || app.preview.replaced.contains(&b.name) {
|
||||
if app.session.visibility.hidden_bodies.contains(&b.name) || app.preview.replaced.contains(&b.name) {
|
||||
continue;
|
||||
}
|
||||
// With a component active, bodies outside it are drawn faded.
|
||||
@@ -326,7 +327,7 @@ fn build_scene(app: &SolveApp) -> GpuScene {
|
||||
// Section: the outline of each body where the plane cuts it.
|
||||
if let Some((o, n)) = section_plane(app) {
|
||||
let col = c4(tk.text.gamma_multiply(0.9));
|
||||
for b in st.bodies.iter().filter(|b| !app.ui.hidden_bodies.contains(&b.name)) {
|
||||
for b in st.bodies.iter().filter(|b| !app.session.visibility.hidden_bodies.contains(&b.name)) {
|
||||
for (a, c) in section_segments(&b.mesh(), o, n) {
|
||||
sc.line(a.to_f32(), c.to_f32(), col, 1.8, false);
|
||||
}
|
||||
@@ -367,7 +368,7 @@ fn build_scene(app: &SolveApp) -> GpuScene {
|
||||
fn ground_shadow(sc: &mut GpuScene, app: &SolveApp, st: &solvecraft_engine::doc::ModelState) {
|
||||
// The bodies' box in the camera's Z-up frame (the ground is its XY plane).
|
||||
let mut b = solvecraft_engine::geom::Aabb3::EMPTY;
|
||||
for x in st.bodies.iter().filter(|x| !app.ui.hidden_bodies.contains(&x.name)) {
|
||||
for x in st.bodies.iter().filter(|x| !app.session.visibility.hidden_bodies.contains(&x.name)) {
|
||||
let bb = x.mesh().bounds();
|
||||
if bb.is_empty() {
|
||||
continue;
|
||||
@@ -484,7 +485,7 @@ pub fn origin_planes(app: &SolveApp) -> Vec<(&'static str, Vec3, [Vec3; 4])> {
|
||||
let creating = !app.ui.show_origin && app.dialog.as_ref().is_some_and(|d| matches!(d.kind, crate::dialogs::Kind::Sketch));
|
||||
origin_quads(origin_size(app.cam.half_height()))
|
||||
.into_iter()
|
||||
.filter(|(n, _, _)| creating || !app.ui.hidden_origin.iter().any(|h| h == n))
|
||||
.filter(|(n, _, _)| creating || !app.session.visibility.hidden_origin.iter().any(|h| h == n))
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -492,7 +493,10 @@ pub fn origin_axes(app: &SolveApp) -> Vec<(&'static str, Vec3)> {
|
||||
if !app.origin_visible() {
|
||||
return Vec::new();
|
||||
}
|
||||
[("X", Vec3::X), ("Y", Vec3::Y), ("Z", Vec3::Z)].into_iter().filter(|(n, _)| !app.ui.hidden_origin.iter().any(|h| h == n)).collect()
|
||||
[("X", Vec3::X), ("Y", Vec3::Y), ("Z", Vec3::Z)]
|
||||
.into_iter()
|
||||
.filter(|(n, _)| !app.session.visibility.hidden_origin.iter().any(|h| h == n))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Construction axes shown, as segments: across the model where the axis passes it (a little
|
||||
@@ -503,7 +507,7 @@ pub fn construction_axis_segments(app: &SolveApp) -> Vec<(String, Vec3, Vec3)> {
|
||||
let bb = solvecraft_engine::view::bounds(&app.session);
|
||||
solvecraft_engine::view::construction_axes(&app.session)
|
||||
.into_iter()
|
||||
.filter(|(_, n, _, _)| !app.ui.hidden_origin.contains(n))
|
||||
.filter(|(_, n, _, _)| !app.session.visibility.hidden_origin.contains(n))
|
||||
.map(|(_, name, o, d)| {
|
||||
let (mut lo, mut hi) = (-h, h);
|
||||
if !bb.is_empty() {
|
||||
@@ -527,7 +531,7 @@ pub fn construction_axis_segments(app: &SolveApp) -> Vec<(String, Vec3, Vec3)> {
|
||||
pub fn construction_points(app: &SolveApp) -> Vec<(String, Vec3)> {
|
||||
solvecraft_engine::view::construction_points(&app.session)
|
||||
.into_iter()
|
||||
.filter(|(_, n, _)| !app.ui.hidden_origin.contains(n))
|
||||
.filter(|(_, n, _)| !app.session.visibility.hidden_origin.contains(n))
|
||||
.map(|(_, n, p)| (n, p))
|
||||
.collect()
|
||||
}
|
||||
@@ -537,7 +541,7 @@ pub fn construction_quads(app: &SolveApp) -> Vec<(String, Vec3, [Vec3; 4])> {
|
||||
let h = origin_size(app.cam.half_height()) * 1.2;
|
||||
solvecraft_engine::view::construction_planes(&app.session)
|
||||
.into_iter()
|
||||
.filter(|(_, n, _)| !app.ui.hidden_origin.contains(n))
|
||||
.filter(|(_, n, _)| !app.session.visibility.hidden_origin.contains(n))
|
||||
.map(|(_, name, pl)| {
|
||||
let (o, x, y) = (pl.origin, pl.x * h, pl.y * h);
|
||||
(name, pl.normal(), [o - x - y, o + x - y, o + x + y, o - x + y])
|
||||
@@ -625,7 +629,7 @@ fn pick_cached(app: &mut SolveApp, proj: &Proj, pos: Pos2) -> Vec<Hit> {
|
||||
(std::sync::Arc::as_ptr(&app.session.world_state()) as usize).hash(&mut h);
|
||||
app.session.active_sketch.hash(&mut h);
|
||||
app.dialog.as_ref().and_then(|d| d.active_input()).map(|i| i.accept).hash(&mut h);
|
||||
app.ui.hidden_bodies.hash(&mut h);
|
||||
app.session.visibility.hidden_bodies.hash(&mut h);
|
||||
app.ui.perspective.hash(&mut h);
|
||||
(proj.rect.width().to_bits(), proj.rect.height().to_bits()).hash(&mut h);
|
||||
let key = h.finish();
|
||||
@@ -701,7 +705,7 @@ pub fn pick(app: &SolveApp, proj: &Proj, pos: Pos2) -> Vec<Hit> {
|
||||
// Nearest face along the ray (hides what is behind it).
|
||||
let mut bestf: Option<(f64, Hit)> = None;
|
||||
for b in &st.bodies {
|
||||
if app.ui.hidden_bodies.contains(&b.name) {
|
||||
if app.session.visibility.hidden_bodies.contains(&b.name) {
|
||||
continue;
|
||||
}
|
||||
// Bodies whose box the ray misses (or enters behind the best hit so far) are skipped.
|
||||
@@ -727,7 +731,7 @@ pub fn pick(app: &SolveApp, proj: &Proj, pos: Pos2) -> Vec<Hit> {
|
||||
let mut bestv: Option<(f32, Hit)> = None;
|
||||
let mut beste: Option<(f32, Hit)> = None;
|
||||
for b in &st.bodies {
|
||||
if app.ui.hidden_bodies.contains(&b.name) {
|
||||
if app.session.visibility.hidden_bodies.contains(&b.name) {
|
||||
continue;
|
||||
}
|
||||
// Only bodies whose outline on screen is near the cursor can have an edge under it.
|
||||
@@ -1075,8 +1079,8 @@ fn highlight_key(app: &SolveApp) -> u64 {
|
||||
crate::theme::is_dark().hash(&mut h);
|
||||
app.preview.replaced.hash(&mut h);
|
||||
app.origin_visible().hash(&mut h);
|
||||
app.ui.hidden_origin.hash(&mut h);
|
||||
app.ui.hidden_bodies.hash(&mut h);
|
||||
app.session.visibility.hidden_origin.hash(&mut h);
|
||||
app.session.visibility.hidden_bodies.hash(&mut h);
|
||||
app.cam.half_height().to_bits().hash(&mut h);
|
||||
app.cam.back().x.to_bits().hash(&mut h);
|
||||
h.finish()
|
||||
@@ -1159,7 +1163,7 @@ fn build_highlight(app: &SolveApp) -> GpuScene {
|
||||
}
|
||||
Sel::Body { name } => {
|
||||
if let Some(b) = st.body(name)
|
||||
&& !app.ui.hidden_bodies.contains(name)
|
||||
&& !app.session.visibility.hidden_bodies.contains(name)
|
||||
{
|
||||
let m = b.mesh();
|
||||
for f in 0..b.body.face_count() {
|
||||
@@ -1205,7 +1209,7 @@ fn build_highlight(app: &SolveApp) -> GpuScene {
|
||||
let k = t.hover_face_lift;
|
||||
let lit =
|
||||
|b: &solvecraft_engine::doc::ModelBody| Some(b.feature) == app.viewport.hover_feature || app.viewport.hover_bodies.contains(&b.name);
|
||||
for b in st.bodies.iter().filter(|b| lit(b) && !app.ui.hidden_bodies.contains(&b.name)) {
|
||||
for b in st.bodies.iter().filter(|b| lit(b) && !app.session.visibility.hidden_bodies.contains(&b.name)) {
|
||||
let c = body_rgb(app, &b.name);
|
||||
let m = b.mesh();
|
||||
for f in 0..b.body.face_count() {
|
||||
@@ -1783,7 +1787,7 @@ fn box_select(app: &mut SolveApp, proj: &Proj, bx: BoxSel, add: bool) {
|
||||
} else {
|
||||
let accept = app.dialog.as_ref().and_then(|d| d.active_input()).map(|i| i.accept).unwrap_or(BODIES);
|
||||
for b in &st.bodies {
|
||||
if app.ui.hidden_bodies.contains(&b.name) {
|
||||
if app.session.visibility.hidden_bodies.contains(&b.name) {
|
||||
continue;
|
||||
}
|
||||
let m = b.mesh();
|
||||
@@ -1845,7 +1849,7 @@ fn box_select(app: &mut SolveApp, proj: &Proj, bx: BoxSel, add: bool) {
|
||||
fn points_2d(app: &SolveApp, painter: &egui::Painter, proj: &Proj) {
|
||||
let t = Tokens::get();
|
||||
if app.origin_visible()
|
||||
&& !app.ui.hidden_origin.iter().any(|h| h == "O")
|
||||
&& !app.session.visibility.hidden_origin.iter().any(|h| h == "O")
|
||||
&& let Some(o) = proj.to_screen(Vec3::ZERO)
|
||||
{
|
||||
painter.circle(o, 4.5, t.origin_point, Stroke::new(1.0, Color32::from_gray(110)));
|
||||
@@ -2456,7 +2460,7 @@ mod tests {
|
||||
// It is an axis for dialogs (pattern directions, revolve axes), and hiding it hides it.
|
||||
let (o, dir) = crate::dialogs::axis_of(&app, &Sel::Axis { name: name.clone() }).unwrap();
|
||||
assert!(o.dist(Vec3::new(0.0, 0.0, 20.0)) < 1e-6 && dir.dist(d) < 1e-6);
|
||||
app.ui.hidden_origin.push(name);
|
||||
app.session.visibility.hidden_origin.push(name);
|
||||
assert!(construction_axis_segments(&app).is_empty());
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user