Setup symbols: wireframe glyphs per held degree of freedom, a constant size on screen

Boundary condition, load and interaction symbols are wireframes (thin lines,
no shaded cones), so they never hide the mesh or a contour plot:

- a crossed wire triangle per constrained translation, tip on the node,
  coming from outside the part where the set's faces say which side that is;
  a held rotation adds a smaller triangle behind it (the double mark); fixed
  shows all three axes, a symmetry plane only its normal;
- forces as open-headed arrows ending on the node, as long as four support
  units at one place and 2.5 over a set, so loads and supports stay in
  proportion (#78); moments as an axis line and a right-handed curved arrow
  (#79); contact and tie surfaces as small squares on their faces;
- a constant size on screen at any zoom (#80): the symbol unit is the size
  setting (View > Symbol size, `ui.set {symbol_size}`, 14 pt by default)
  times the model length a pixel spans, rounded to a quarter of a doubling;
- spread evenly on a grid a few symbol units across, so their density follows
  the screen, never more than 400 per definition;
- a layer of their own on the desktop (GPU and CPU): zooming rebuilds and
  uploads the symbols only, never the mesh's scene; framing leaves them out;
- colour per kind from the theme's tokens (a new contact colour), and the
  definition selected in the browser drawn in the selection colour, wider;
- depth-tested against the model with the line bias that keeps them on their
  own surface; a load at a reference point or a single node and a reference
  point's cross are drawn over the part (#78).

Headless pictures (MCP screenshot, `ui.render`) frame the camera first and
draw the symbols at the default size for it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
This commit is contained in:
jyvikoandClaude Opus 5.5 authored and Irakli Kourtis committed 2026-10-14 23:14:40 -04:00
1 parent 1fcc7acf84
commit 52c12e2ad7
13 files changed
+1221 -436

No files matched your search

+19 -7
View File
@@ -4,10 +4,10 @@
//! Geometry (the open design, imported surfaces) draws as faces with their boundaries while
//! the model has no element faces; once it is meshed, as its boundaries drawn along the mesh
//! (the element edges on geometry edges), so the mesh is not hidden behind coincident faces and
//! every line lies on the element faces. The analysis setup's symbols ([`symbols`]) go over the model,
//! muted over a results plot.
//! every line lies on the element faces. The analysis setup's symbols ([`symbols`]) are a layer of
//! their own over the model, a constant size on screen, muted over a results plot.
use fieldcraft_render::glyphs::{GlyphShow, Glyphs};
use fieldcraft_render::glyphs::{Definition, Drawn, GlyphShow, GlyphView, Glyphs};
use fieldcraft_render::{Aabb3, Camera, DisplayMesh, Scene, SceneLine, ScenePoint, SceneTri, Vec3};
use serde::{Deserialize, Serialize};
@@ -62,12 +62,24 @@ impl Show {
pub const DEFAULT: Show = Show { geometry: true, mesh: true, nodes: false, loads: true, interactions: true };
}
/// Draw the analysis setup's symbols into `s` at the nodes' positions `pos`, muted over a
/// results plot; returns the box around them.
pub fn symbols(s: &mut Scene, glyphs: &Glyphs, pos: impl Fn(u32) -> Option<Vec3>, show: Show, results: bool, t: &Tokens) -> Aabb3 {
/// Draw the analysis setup's symbols into `s` at the nodes' positions `pos` and symbol unit
/// `unit` (model length, [`fieldcraft_render::glyphs::symbol_unit`]), muted over a results plot,
/// the `highlight`ed definition's in the selection colour.
#[allow(clippy::too_many_arguments)]
pub fn symbols(
s: &mut Scene,
glyphs: &Glyphs,
pos: impl Fn(u32) -> Option<Vec3>,
show: Show,
results: bool,
unit: f64,
highlight: Option<(Definition, &str)>,
t: &Tokens,
) -> Drawn {
let colors = t.glyph_colors();
let colors = if results { colors.over_results(rgb(t.viewport_bottom)) } else { colors };
glyphs.draw(pos, GlyphShow { loads: show.loads, interactions: show.interactions }, &colors, s)
let view = GlyphView { show: GlyphShow { loads: show.loads, interactions: show.interactions }, colors: &colors, unit, highlight };
glyphs.draw(pos, &view, s)
}
/// Draw the model's datum planes and axes around `bounds` into `s`; returns the box around them.