engine: result units, per-step staleness, step symbols, true-scale nonlinear results

- Quantity names the unit of every number the results show from the model's
  unit system (mm, MPa, N, N·mm, mJ, mW, °C or K); Field::title puts it in the
  legend title, and calls a mode shape normalised (#321).
- A solved step's result stays current through edits that leave what it reads
  alone (Model::step_inputs: other steps and what only they use), and comes
  back current on undo (#318).
- Session::step_glyphs: the symbols of what a solved step used, for drawing
  over its results; no load arrows on a frequency step's modes (#327).
- Nonlinear static steps open deformed at true scale like explicit ones, and
  their automatic scale is 1 (auto_scale, shared by the viewport and the
  headless pictures) (#326).
- results.reactions with an empty sets list sums the step's boundary-condition
  sets, as its form says (#328).

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-15 09:36:54 -04:00
1 parent a09e3c856b
commit 0966dfa2a6
9 files changed
+371 -42

No files matched your search

+25 -1
View File
@@ -247,6 +247,9 @@ pub struct Session {
/// The latest result of each solved step, by step name, with the revision it was solved at;
/// [`Session::result`] hands out only those that describe the current state.
results: BTreeMap<String, (u64, Arc<StepResult>)>,
/// What each solved step read ([`results::inputs_fingerprint`]): an edit that leaves it the
/// same keeps the step's result current.
result_inputs: BTreeMap<String, u64>,
/// What the results display shows (`results.*`); `None` shows the model.
pub results_view: Option<results::ResultsView>,
/// The default work caps of commands; a file replacing the session keeps them.
@@ -331,6 +334,9 @@ impl Session {
cmd::view::forget_missing(self);
}
}
if self.revision != revision {
self.refresh_results();
}
drop(scope);
crash::command_done(self, journaled, self.revision != revision || self.opened != opened, id, params, &result);
result
@@ -396,7 +402,25 @@ impl Session {
}
Some(_) => {}
}
self.results.insert(result.step().to_string(), (self.revision, Arc::new(result)));
let step = result.step().to_string();
match results::inputs_fingerprint(&self.model, &step) {
Some(f) => self.result_inputs.insert(step.clone(), f),
None => self.result_inputs.remove(&step),
};
self.results.insert(step, (self.revision, Arc::new(result)));
}
/// After the state changed (an edit, undo or redo): a result whose step reads what it read
/// when it was solved describes the new state too, so it stays (or is again) current; the
/// others go stale.
fn refresh_results(&mut self) {
let revision = self.revision;
for (step, (solved, _)) in self.results.iter_mut().filter(|(_, (r, _))| *r != revision) {
let read = self.result_inputs.get(step).copied();
if read.is_some() && read == results::inputs_fingerprint(&self.model, step) {
*solved = revision;
}
}
}
/// The path to use for `access` to the file at `path`, checked against [`Session::file_access`].