forked from 1b-agent/fieldcraft-upstream
Datums: drawn at the model's extent, never framed, the selected one shown
A datum plane is drawn as the model's extent within it (where it cuts the model's box, in the plane's axes) plus a 5 % margin, a datum axis along the model's extent plus the margin, instead of squares 1.2 diagonals wide. Fit, Home, the standard views and headless pictures frame the model without the datums (they stay inside the clipping radius). The datum selected in the browser is drawn highlighted and on top, even when hidden. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
This commit is contained in:
1 parent
b19277ae7a
commit
dddb2d0584
6 files changed
+239
-56
No files matched your search
@@ -883,7 +883,7 @@ impl ModelApp {
|
||||
pub fn scene_key(&self) -> u64 {
|
||||
let mut h = std::hash::DefaultHasher::new();
|
||||
let plot = self.plot.1.as_ref().map(|p| Arc::as_ptr(p) as usize);
|
||||
(self.revision, self.ui.style, self.ui.show(), self.ui.dark, plot).hash(&mut h);
|
||||
(self.revision, self.ui.style, self.ui.show(), self.ui.dark, plot, self.selected_datum()).hash(&mut h);
|
||||
h.finish()
|
||||
}
|
||||
|
||||
@@ -929,19 +929,28 @@ impl ModelApp {
|
||||
};
|
||||
let mut datums = Aabb3::EMPTY;
|
||||
if show.geometry && self.plot.1.is_none() {
|
||||
datums = scene::datums(&mut s, &self.session, &self.display.1.bounds, &t);
|
||||
datums = scene::datums(&mut s, &self.session, &self.display.1.bounds, self.selected_datum(), &t);
|
||||
scene::seeds(&mut s, &self.session.model, &self.pick.names, &self.display.1, &t);
|
||||
}
|
||||
self.scene = (Some(key), s, datums);
|
||||
}
|
||||
// The clipping radius holds the symbols too (framing leaves them out: their size in the
|
||||
// model follows the zoom).
|
||||
// The clipping radius holds the symbols and the datums too (framing leaves them out: the
|
||||
// symbols' size in the model follows the zoom, the datums are drawn at the model).
|
||||
let mut bounds = self.drawn_bounds();
|
||||
bounds.union(self.symbols.2.bounds);
|
||||
bounds.union(self.scene.2);
|
||||
self.scene.1.radius = scene::radius(&bounds, &self.cam);
|
||||
(key, &self.scene.1)
|
||||
}
|
||||
|
||||
/// The datum selected in the browser: drawn highlighted, shown or hidden.
|
||||
fn selected_datum(&self) -> Option<&str> {
|
||||
match &self.selected {
|
||||
Some((browser::Def::Datum, name)) => Some(name.as_str()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The definition selected in the browser, when it has symbols.
|
||||
fn highlighted(&self) -> Option<(Definition, &str)> {
|
||||
let (def, name) = self.selected.as_ref()?;
|
||||
@@ -981,13 +990,13 @@ impl ModelApp {
|
||||
}
|
||||
|
||||
/// What the scene built last draws, as framing sees it: the model, a results plot's
|
||||
/// (deformed) shape, the datums and the reference points.
|
||||
/// (deformed) shape and the reference points. Datums are never framed (#295): they are drawn
|
||||
/// at the model's extent, so framing them would only shrink the part.
|
||||
fn drawn_bounds(&self) -> Aabb3 {
|
||||
let mut bounds = self.display.1.bounds;
|
||||
if let Some(p) = &self.plot.1 {
|
||||
bounds.union(p.display.bounds);
|
||||
}
|
||||
bounds.union(self.scene.2);
|
||||
// Reference points are places in the model, framed with it (their symbols are not).
|
||||
for p in self.session.model.reference_points().values() {
|
||||
let p = Vec3::from(p.xyz);
|
||||
@@ -2038,6 +2047,35 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// Fit and home never frame datums (#295); a hidden datum selected in the browser is drawn,
|
||||
/// highlighted, until the selection moves on.
|
||||
#[test]
|
||||
fn datums_are_not_framed_and_a_selected_one_shows() {
|
||||
let mut app = ModelApp::new(Session::default());
|
||||
let ctx = egui::Context::default();
|
||||
app.run("file.sample", json!({"name": "cantilever"})).unwrap();
|
||||
draw(&mut app, &ctx, 1280.0, 800.0);
|
||||
app.home_view();
|
||||
let home = app.cam;
|
||||
app.run("geometry.datum", json!({"name": "Far", "by": {"type": "offset", "plane": "xy", "offset": 5000}})).unwrap();
|
||||
draw(&mut app, &ctx, 1280.0, 800.0);
|
||||
app.home_view();
|
||||
// What framing sees is the model alone; the view stays as close as before (the overlays
|
||||
// may differ a little after a command).
|
||||
assert_eq!(app.drawn_bounds(), app.display.1.bounds, "a datum far off is not framed");
|
||||
assert!(app.cam.distance < home.distance * 1.2, "{:?} vs {:?}", app.cam, home);
|
||||
app.fit_view();
|
||||
assert!(app.cam.distance < home.distance * 1.2);
|
||||
let highlight = theme::Tokens::get(app.ui.dark).glyph_colors().highlight;
|
||||
let lit = |app: &mut ModelApp| app.scene().1.on_top.iter().filter(|l| l.color == highlight).count();
|
||||
app.run("view.hide", json!({"items": [{"kind": "datum", "name": "Far"}]})).unwrap();
|
||||
assert_eq!(lit(&mut app), 0);
|
||||
app.selected = Some((browser::Def::Datum, "Far".into()));
|
||||
assert!(lit(&mut app) > 0, "the selected datum is drawn though hidden");
|
||||
app.selected = None;
|
||||
assert_eq!(lit(&mut app), 0);
|
||||
}
|
||||
|
||||
/// Opening a sample, then another, frames each in the home view; an edit keeps the camera
|
||||
/// (#233, #223).
|
||||
#[test]
|
||||
|
||||
@@ -233,7 +233,7 @@ mod tests {
|
||||
fn on_datum(app: &mut ModelApp, name: &str) -> egui::Pos2 {
|
||||
let bounds = app.display().bounds;
|
||||
let mut s = fieldcraft_render::Scene::default();
|
||||
fieldcraft_render::glyphs::datums(&app.session.model, &bounds, &|n| n == name, &crate::theme::Tokens::dark().glyph_colors(), &mut s);
|
||||
fieldcraft_render::glyphs::datums(&app.session.model, &bounds, &|n| n == name, None, &crate::theme::Tokens::dark().glyph_colors(), &mut s);
|
||||
let l = s.lines[0];
|
||||
viewport::to_screen(app, (l.a + l.b) * 0.5).unwrap()
|
||||
}
|
||||
|
||||
@@ -82,10 +82,11 @@ pub fn symbols(
|
||||
glyphs.draw(pos, &view, s)
|
||||
}
|
||||
|
||||
/// Draw the datums the session's view shows around `bounds` into `s`; returns the box around them.
|
||||
pub fn datums(s: &mut Scene, session: &fieldcraft_engine::Session, bounds: &Aabb3, t: &Tokens) -> Aabb3 {
|
||||
/// Draw the datums the session's view shows at the model (`bounds`) into `s`, and `selected` (the
|
||||
/// datum selected in the browser), shown or hidden, highlighted; returns the box around them.
|
||||
pub fn datums(s: &mut Scene, session: &fieldcraft_engine::Session, bounds: &Aabb3, selected: Option<&str>, t: &Tokens) -> Aabb3 {
|
||||
let shown = |name: &str| session.shows(fieldcraft_engine::ItemKind::Datum, name);
|
||||
fieldcraft_render::glyphs::datums(&session.model, bounds, &shown, &t.glyph_colors(), s)
|
||||
fieldcraft_render::glyphs::datums(&session.model, bounds, &shown, selected, &t.glyph_colors(), s)
|
||||
}
|
||||
|
||||
/// The scene for `display` drawn in `style`.
|
||||
|
||||
@@ -209,7 +209,7 @@ fn datum_at(app: &mut ModelApp, at: Pos2) -> Option<String> {
|
||||
let mut best: Option<(f32, String)> = None;
|
||||
for name in names {
|
||||
let mut drawn = fieldcraft_render::Scene::default();
|
||||
fieldcraft_render::glyphs::datums(&app.session.model, &bounds, &|n| n == name, &colors, &mut drawn);
|
||||
fieldcraft_render::glyphs::datums(&app.session.model, &bounds, &|n| n == name, None, &colors, &mut drawn);
|
||||
for l in &drawn.lines {
|
||||
let (Some(a), Some(b)) = (to_screen(app, l.a), to_screen(app, l.b)) else { continue };
|
||||
let xy = |p: Pos2| [f64::from(p.x), f64::from(p.y)];
|
||||
|
||||
Reference in new issue
Block a user