ui: the Partition form highlights the bodies it would cut (#347, #371)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
This commit is contained in:
jyvikoandClaude Opus 5.5 authored and Irakli Kourtis committed 2026-10-15 10:17:28 -04:00
1 parent 3a023f3e25
commit 980a50c1d9
2 files changed
+60 -1

No files matched your search

+41 -1
View File
@@ -1092,6 +1092,34 @@ impl ModelApp {
}
}
/// The bodies the open Partition form would cut: those it lists, or every drawn body whose
/// surface lies on both sides of its datum plane. Empty without such a form.
pub fn partition_preview(&self) -> Vec<String> {
let Some(d) = self.dialog.as_ref().filter(|d| d.command == "geometry.partition") else { return Vec::new() };
let Ok(params) = serde_json::from_str::<Value>(&d.text) else { return Vec::new() };
if let Some(listed) = params.get("bodies").and_then(Value::as_array).filter(|l| !l.is_empty()) {
return listed.iter().filter_map(Value::as_str).map(str::to_string).collect();
}
let Some((point, normal)) = params.get("datum").and_then(Value::as_str).and_then(|n| self.session.model.datum(n)).and_then(|d| d.plane())
else {
return Vec::new();
};
let (point, normal) = (Vec3::from(point), Vec3::from(normal));
let display = &self.display.1;
let tol = 1e-6 * display.bounds.diagonal().max(1e-9);
// Per body: whether it has surface points below and above the plane.
let mut sides = vec![(false, false); self.pick.names.len()];
for t in &display.surface_tris {
let Some(side) = sides.get_mut(t.body as usize) else { continue };
for p in t.p {
let h = (p - point).dot(normal);
side.0 |= h < -tol;
side.1 |= h > tol;
}
}
self.pick.names.iter().zip(sides).filter(|(_, s)| s.0 && s.1).map(|(b, _)| b.name.clone()).collect()
}
/// The datum the open datum form defines so far, drawn before OK.
/// Worked out once per form text and model revision (a face centre costs a design query).
fn datum_preview(&self) -> Option<fieldcraft_model::Datum> {
@@ -1336,7 +1364,8 @@ impl ModelApp {
};
let scene = self.scene_key();
let set = self.selected_set();
let selected = hash(&|h| (scene, self.pick.generation, &set).hash(h));
let cut = self.partition_preview();
let selected = hash(&|h| (scene, self.pick.generation, &set, &cut).hash(h));
let key = hash(&|h| (selected, self.pick.hover_generation).hash(h));
if self.pick.overlay.0 == Some(key) {
return (key, self.pick.overlay.1.clone());
@@ -1355,6 +1384,17 @@ impl ModelApp {
s.lines.extend(m.lines);
s.points.extend(m.points);
}
// The bodies the open Partition form would cut (#347).
if !cut.is_empty() {
let items: Vec<selection::Item> = cut
.iter()
.filter_map(|b| fieldcraft_model::GeometryRef::new(b.as_str(), fieldcraft_model::Entity::Body).ok())
.map(selection::Item::Geometry)
.collect();
let m = self.with_picker(|p| p.items_overlay(&items, theme::rgb(t.pick_hover)));
s.tris.extend(m.tris);
s.lines.extend(m.lines);
}
self.pick.selected_overlay = (Some(selected), Arc::new(s));
}
let mut s = (*self.pick.selected_overlay.1).clone();
+19
View File
@@ -261,6 +261,25 @@ mod tests {
(app, dir)
}
/// The Partition form highlights the bodies its plane would cut: a plane through the plate
/// cuts it, one beyond it nothing, and listed bodies are those listed (#347).
#[test]
fn the_partition_form_names_the_bodies_it_would_cut() {
let (mut app, dir) = plate();
app.run("geometry.datum", json!({"name": "Far", "by": {"type": "offset", "plane": "yz", "offset": 1000}})).unwrap();
app.run("geometry.datum", json!({"name": "Cut", "by": {"type": "offset", "plane": "xy", "offset": 1}})).unwrap();
assert!(app.partition_preview().is_empty(), "no form, nothing");
app.open_form("geometry.partition", json!({"name": "Q", "datum": "Cut"}));
let cut = app.partition_preview();
assert_eq!(cut.len(), 2, "both cells of Half: {cut:?}");
app.open_form("geometry.partition", json!({"name": "Q", "datum": "Far"}));
assert!(app.partition_preview().is_empty());
let one = cut.first().cloned().unwrap();
app.open_form("geometry.partition", json!({"name": "Q", "datum": "Cut", "bodies": [one.clone()]}));
assert_eq!(app.partition_preview(), vec![one]);
let _ = std::fs::remove_dir_all(dir);
}
/// A screen point on datum `name`'s drawn lines.
fn on_datum(app: &mut ModelApp, name: &str) -> egui::Pos2 {
let bounds = app.display().bounds;