forked from 1b-agent/fieldcraft-upstream
ui: draw size controls in the viewport, pick their faces (#435)
Faces and bodies under a size control are tinted, a box drawn as its edges and a sphere as a translucent ball, each labelled with its name and size. The Local Size form takes the selected faces. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
This commit is contained in:
1 parent
b62be1ada6
commit
030799dfc8
4 files changed
+73
-4
No files matched your search
@@ -165,7 +165,7 @@ fn form(app: &mut ModelApp, ui: &mut egui::Ui, t: &Tokens) {
|
|||||||
selection::Target::DatumPoint => "Puts the point on what is selected: a vertex, an edge's midpoint, a face's centre, or the centre of a hole or of several items",
|
selection::Target::DatumPoint => "Puts the point on what is selected: a vertex, an edge's midpoint, a face's centre, or the centre of a hole or of several items",
|
||||||
selection::Target::Bodies => "The selected bodies (Select: Bodies), or the bodies of the selected faces, become the cells",
|
selection::Target::Bodies => "The selected bodies (Select: Bodies), or the bodies of the selected faces, become the cells",
|
||||||
selection::Target::ExtrudeFaces => "Pick the source face and use it, then pick the target surface and use it",
|
selection::Target::ExtrudeFaces => "Pick the source face and use it, then pick the target surface and use it",
|
||||||
selection::Target::Faces => "The selected design faces (Shift-click to pick, Select: Faces) are relaxed instead of a sphere",
|
selection::Target::Faces => "The selected design faces (Shift-click to pick, Select: Faces) become the form's faces, in place of a sphere",
|
||||||
};
|
};
|
||||||
let button = match target {
|
let button = match target {
|
||||||
selection::Target::ExtrudeFaces if selection::extrude_key(¶ms) == "source" => "Use as source",
|
selection::Target::ExtrudeFaces if selection::extrude_key(¶ms) == "source" => "Use as source",
|
||||||
|
|||||||
@@ -1153,6 +1153,7 @@ impl ModelApp {
|
|||||||
datums.union(scene::datum_preview(&mut s, d, &self.display.1.bounds, &t));
|
datums.union(scene::datum_preview(&mut s, d, &self.display.1.bounds, &t));
|
||||||
}
|
}
|
||||||
scene::seeds(&mut s, &self.session.model, &self.pick.names, &self.display.1, &t);
|
scene::seeds(&mut s, &self.session.model, &self.pick.names, &self.display.1, &t);
|
||||||
|
scene::sizes(&mut s, &self.session.model, &self.pick.names, &self.display.1, &t);
|
||||||
}
|
}
|
||||||
self.scene = (Some(key), s, datums);
|
self.scene = (Some(key), s, datums);
|
||||||
}
|
}
|
||||||
@@ -1822,7 +1823,7 @@ impl ModelApp {
|
|||||||
selection::Target::ExtrudeFaces => selection::with_extrude_faces(¶ms, &self.pick.selection)
|
selection::Target::ExtrudeFaces => selection::with_extrude_faces(¶ms, &self.pick.selection)
|
||||||
.ok_or("no face is selected: pick the source face, then the target surface (Select: Faces)")?,
|
.ok_or("no face is selected: pick the source face, then the target surface (Select: Faces)")?,
|
||||||
selection::Target::Faces => {
|
selection::Target::Faces => {
|
||||||
selection::with_faces(¶ms, &self.pick.selection).ok_or("no face is selected: pick the faces to relax (Select: Faces)")?
|
selection::with_faces(¶ms, &self.pick.selection).ok_or("no face is selected: pick the faces (Select: Faces)")?
|
||||||
}
|
}
|
||||||
selection::Target::Bodies => {
|
selection::Target::Bodies => {
|
||||||
let bodies = selection::bodies(&self.pick.selection).ok_or("no geometry is selected: pick cells with Select: Bodies")?;
|
let bodies = selection::bodies(&self.pick.selection).ok_or("no geometry is selected: pick cells with Select: Bodies")?;
|
||||||
|
|||||||
@@ -137,6 +137,57 @@ pub fn seeds(s: &mut Scene, model: &fieldcraft_model::Model, names: &[crate::sel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Draw the model's size controls (mesh.size.set) where they apply: the faces and bodies they
|
||||||
|
/// refine tinted on the drawn geometry, a box as its twelve edges, a sphere as
|
||||||
|
/// [`sphere`] draws one, each labelled with its name and size.
|
||||||
|
pub fn sizes(s: &mut Scene, model: &fieldcraft_model::Model, names: &[crate::selection::BodyNames], display: &DisplayMesh, t: &Tokens) {
|
||||||
|
use fieldcraft_model::{Entity, GeometryRef, SizeRegion};
|
||||||
|
if model.size_controls().is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let color = rgb(t.warm);
|
||||||
|
for (name, c) in model.size_controls() {
|
||||||
|
let text = format!("{name} {}", c.size);
|
||||||
|
let v = |p: [f64; 3]| Vec3 { x: p[0], y: p[1], z: p[2] };
|
||||||
|
match &c.region {
|
||||||
|
SizeRegion::Box { min, max } => {
|
||||||
|
let corner = |k: usize| v([0, 1, 2].map(|a| if (k >> a) & 1 == 1 { max[a] } else { min[a] }));
|
||||||
|
for k in 0..8usize {
|
||||||
|
for a in 0..3 {
|
||||||
|
if (k >> a) & 1 == 0 {
|
||||||
|
s.lines.push(SceneLine { a: corner(k), b: corner(k | (1 << a)), color, width: 1.5 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.labels.push(SceneLabel { p: (corner(0) + corner(7)) * 0.5, text, color });
|
||||||
|
}
|
||||||
|
SizeRegion::Sphere { center, radius } => {
|
||||||
|
sphere(s, v(*center), *radius, color);
|
||||||
|
s.labels.push(SceneLabel { p: v(*center), text, color });
|
||||||
|
}
|
||||||
|
SizeRegion::Geometry(refs) => {
|
||||||
|
let (mut sum, mut n) = (Vec3 { x: 0.0, y: 0.0, z: 0.0 }, 0usize);
|
||||||
|
for tri in &display.surface_tris {
|
||||||
|
let Some(body) = names.get(tri.body as usize) else { continue };
|
||||||
|
let face = body.faces.get(tri.face as usize).and_then(|f| GeometryRef::face(body.name.clone(), f.clone()).ok());
|
||||||
|
let hit = refs.iter().any(|r| match &r.entity {
|
||||||
|
Entity::Body => r.body == body.name,
|
||||||
|
_ => face.as_ref().is_some_and(|f| r.selects(f)),
|
||||||
|
});
|
||||||
|
if hit {
|
||||||
|
s.fills.push(SceneFill { tri: SceneTri { p: tri.p, normal: tri.normal, color }, alpha: 0.3 });
|
||||||
|
sum = sum + (tri.p[0] + tri.p[1] + tri.p[2]) * (1.0 / 3.0);
|
||||||
|
n += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if n > 0 {
|
||||||
|
s.labels.push(SceneLabel { p: sum * (1.0 / n as f64), text, color });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// How many element edges of the drawn mesh lie along the geometry edge `run` (its polyline,
|
/// How many element edges of the drawn mesh lie along the geometry edge `run` (its polyline,
|
||||||
/// `length` long): those whose ends and middle are all on it, counted once per element edge
|
/// `length` long): those whose ends and middle are all on it, counted once per element edge
|
||||||
/// (a quadratic edge is drawn in pieces). 0 when the mesh has none there (not meshed).
|
/// (a quadratic edge is drawn in pieces). 0 when the mesh has none there (not meshed).
|
||||||
@@ -265,4 +316,21 @@ mod tests {
|
|||||||
assert_eq!(s.tris.first().map(|t| t.color), None);
|
assert_eq!(s.tris.first().map(|t| t.color), None);
|
||||||
assert!(radius(&d.bounds, &Camera::default()) > 0.5);
|
assert!(radius(&d.bounds, &Camera::default()) > 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn size_controls_draw_where_they_apply() {
|
||||||
|
use fieldcraft_model::{SizeControl, SizeRegion};
|
||||||
|
let mut m = Model::default();
|
||||||
|
m.set_size_control("Zone", SizeControl { region: SizeRegion::Box { min: [0.0; 3], max: [1.0; 3] }, size: 0.5 }).unwrap();
|
||||||
|
m.set_size_control("Ball", SizeControl { region: SizeRegion::Sphere { center: [0.0; 3], radius: 2.0 }, size: 0.5 }).unwrap();
|
||||||
|
let mut s = Scene::default();
|
||||||
|
sizes(&mut s, &m, &[], &DisplayMesh::default(), &Tokens::dark());
|
||||||
|
assert!(s.lines.len() >= 12 && !s.fills.is_empty(), "the box's edges and the sphere");
|
||||||
|
let mut labels: Vec<&str> = s.labels.iter().map(|l| l.text.as_str()).collect();
|
||||||
|
labels.sort();
|
||||||
|
assert_eq!(labels, ["Ball 0.5", "Zone 0.5"]);
|
||||||
|
let mut empty = Scene::default();
|
||||||
|
sizes(&mut empty, &Model::default(), &[], &DisplayMesh::default(), &Tokens::dark());
|
||||||
|
assert!(empty.lines.is_empty() && empty.labels.is_empty());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -856,7 +856,7 @@ pub enum Target {
|
|||||||
/// `to_surface`: pick the source face, then the target surface.
|
/// `to_surface`: pick the source face, then the target surface.
|
||||||
ExtrudeFaces,
|
ExtrudeFaces,
|
||||||
/// The selected geometry faces as `faces: [{body, face}]`, in place of a sphere or labels
|
/// The selected geometry faces as `faces: [{body, face}]`, in place of a sphere or labels
|
||||||
/// (the faces Relax Nodes relaxes).
|
/// (the faces Relax Nodes relaxes, the faces a local size refines).
|
||||||
Faces,
|
Faces,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -870,7 +870,7 @@ pub fn target(id: &str, params: &Value) -> Option<Target> {
|
|||||||
"geometry.datum.point" | "geometry.datum.point.edit" => Some(Target::DatumPoint),
|
"geometry.datum.point" | "geometry.datum.point.edit" => Some(Target::DatumPoint),
|
||||||
"mesh.cells" => Some(Target::Bodies),
|
"mesh.cells" => Some(Target::Bodies),
|
||||||
"mesh.hex.extrude" => Some(Target::ExtrudeFaces),
|
"mesh.hex.extrude" => Some(Target::ExtrudeFaces),
|
||||||
"mesh.node.relax" => Some(Target::Faces),
|
"mesh.node.relax" | "mesh.size.set" => Some(Target::Faces),
|
||||||
"model.boundary_condition.create" | "model.boundary_condition.edit" => Some(Target::TargetSet),
|
"model.boundary_condition.create" | "model.boundary_condition.edit" => Some(Target::TargetSet),
|
||||||
"model.section.create" | "model.section.edit" | "model.coupling.create" | "model.coupling.edit" | "interaction.rigid_wall" => {
|
"model.section.create" | "model.section.edit" | "model.coupling.create" | "model.coupling.edit" | "interaction.rigid_wall" => {
|
||||||
Some(Target::Set)
|
Some(Target::Set)
|
||||||
|
|||||||
Reference in new issue
Block a user