forked from 1b-agent/fieldcraft-upstream
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:
1 parent
a09e3c856b
commit
0966dfa2a6
9 files changed
+371
-42
No files matched your search
@@ -75,7 +75,7 @@ pub use sets::Set;
|
||||
pub use steps::{
|
||||
ArcConstraint, ArcLength, BulkViscosity, DisplacementLimit, ExplicitDynamic, Hourglass, HourglassKind, Imperfection, InitialVelocity,
|
||||
MAX_LOAD_PATH_POINTS, MAX_NONLINEAR_INCREMENTS, MAX_NONLINEAR_ITERATIONS, MassScaling, NonlinearStatic, OutputVariable, Procedure, RigidWall,
|
||||
Step, StressRate, Tolerances, Transient,
|
||||
Step, StepInputs, StressRate, Tolerances, Transient,
|
||||
};
|
||||
pub use tie::Tie;
|
||||
pub use units::UnitSystem;
|
||||
|
||||
@@ -135,6 +135,18 @@ impl Model {
|
||||
|
||||
/// The definitions that refer to a set, as `kind \`name\`` strings.
|
||||
pub fn set_users(&self, set: &str) -> Vec<String> {
|
||||
let mut users = self.set_model_users(set);
|
||||
users
|
||||
.extend(self.boundary_conditions.iter().filter(|(_, b)| b.target().set() == Some(set)).map(|(n, _)| format!("boundary condition `{n}`")));
|
||||
users.extend(self.loads.iter().filter(|(_, l)| l.set() == Some(set)).map(|(n, _)| format!("load `{n}`")));
|
||||
users.extend(self.steps.iter().filter(|s| s.procedure.sets().any(|x| x == set)).map(|s| format!("step `{}`", s.name)));
|
||||
users
|
||||
}
|
||||
|
||||
/// The users of a set that act in every step (sections, couplings, contacts, ties, point
|
||||
/// masses and springs): all but boundary conditions, loads and steps, which act only in the
|
||||
/// steps that list them ([`Model::step_inputs`]).
|
||||
fn set_model_users(&self, set: &str) -> Vec<String> {
|
||||
let mut users = Vec::new();
|
||||
users.extend(self.sections.iter().filter(|(_, s)| s.set == set).map(|(n, _)| format!("section `{n}`")));
|
||||
users.extend(self.couplings.iter().filter(|(_, c)| c.set == set).map(|(n, _)| format!("coupling `{n}`")));
|
||||
@@ -142,10 +154,15 @@ impl Model {
|
||||
users.extend(self.tie_users(set));
|
||||
users.extend(self.discrete_users(|t| t.set() == Some(set)));
|
||||
users
|
||||
.extend(self.boundary_conditions.iter().filter(|(_, b)| b.target().set() == Some(set)).map(|(n, _)| format!("boundary condition `{n}`")));
|
||||
users.extend(self.loads.iter().filter(|(_, l)| l.set() == Some(set)).map(|(n, _)| format!("load `{n}`")));
|
||||
users.extend(self.steps.iter().filter(|s| s.procedure.sets().any(|x| x == set)).map(|s| format!("step `{}`", s.name)));
|
||||
users
|
||||
}
|
||||
|
||||
/// Whether solving `step` reads set `set`: a definition acting in every step refers to it,
|
||||
/// or one of the step's own boundary conditions and loads, or its procedure.
|
||||
pub(crate) fn step_uses_set(&self, step: &crate::Step, set: &str) -> bool {
|
||||
!self.set_model_users(set).is_empty()
|
||||
|| step.boundary_conditions.iter().any(|n| self.boundary_conditions.get(n).is_some_and(|b| b.target().set() == Some(set)))
|
||||
|| step.loads.iter().any(|n| self.loads.get(n).is_some_and(|l| l.set() == Some(set)))
|
||||
|| step.procedure.sets().any(|x| x == set)
|
||||
}
|
||||
|
||||
/// Resolve one set against the current mesh. The error names every member that is not on
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
//! before the first step is the unloaded, unconstrained model; a boundary condition active in
|
||||
//! every step can be written at model level by formats that have one.
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -834,7 +834,63 @@ impl Step {
|
||||
}
|
||||
}
|
||||
|
||||
/// What solving one step reads from a [`Model`] ([`Model::step_inputs`]), borrowed: two models
|
||||
/// whose step inputs serialize the same give the step the same result.
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct StepInputs<'a> {
|
||||
nodes: &'a BTreeMap<u32, crate::Node>,
|
||||
elements: &'a BTreeMap<u32, crate::Element>,
|
||||
classification: &'a crate::Classification,
|
||||
units: crate::UnitSystem,
|
||||
sets: BTreeMap<&'a str, &'a crate::Set>,
|
||||
materials: &'a BTreeMap<String, crate::Material>,
|
||||
sections: &'a BTreeMap<String, crate::Section>,
|
||||
reference_points: &'a BTreeMap<String, crate::ReferencePoint>,
|
||||
couplings: &'a BTreeMap<String, crate::Coupling>,
|
||||
contacts: &'a BTreeMap<String, crate::Contact>,
|
||||
ties: &'a BTreeMap<String, crate::Tie>,
|
||||
masses: &'a BTreeMap<String, crate::PointMass>,
|
||||
springs: &'a BTreeMap<String, crate::Spring>,
|
||||
boundary_conditions: BTreeMap<&'a str, &'a crate::BoundaryCondition>,
|
||||
loads: BTreeMap<&'a str, &'a crate::Load>,
|
||||
fluid_domains: &'a BTreeMap<String, crate::FluidDomain>,
|
||||
step: &'a Step,
|
||||
}
|
||||
|
||||
impl Model {
|
||||
/// What solving step `name` reads: the mesh, the materials, sections, interactions and every
|
||||
/// definition that acts in all steps, the step itself and the boundary conditions, loads and
|
||||
/// sets it uses. Other steps and what only they use are left out (steps are solved each on
|
||||
/// its own), and so are the meshing inputs (datums, partitions, seeds: the mesh they made is
|
||||
/// in). `None` when there is no such step.
|
||||
pub fn step_inputs(&self, name: &str) -> Option<StepInputs<'_>> {
|
||||
let step = self.step(name)?;
|
||||
Some(StepInputs {
|
||||
nodes: &self.nodes,
|
||||
elements: &self.elements,
|
||||
classification: &self.classification,
|
||||
units: self.units,
|
||||
sets: self.sets.iter().filter(|(n, _)| self.step_uses_set(step, n)).map(|(n, s)| (n.as_str(), s)).collect(),
|
||||
materials: &self.materials,
|
||||
sections: &self.sections,
|
||||
reference_points: &self.reference_points,
|
||||
couplings: &self.couplings,
|
||||
contacts: &self.contacts,
|
||||
ties: &self.ties,
|
||||
masses: &self.masses,
|
||||
springs: &self.springs,
|
||||
boundary_conditions: self
|
||||
.boundary_conditions
|
||||
.iter()
|
||||
.filter(|(n, _)| step.boundary_conditions.contains(*n))
|
||||
.map(|(n, b)| (n.as_str(), b))
|
||||
.collect(),
|
||||
loads: self.loads.iter().filter(|(n, _)| step.loads.contains(*n)).map(|(n, l)| (n.as_str(), l)).collect(),
|
||||
fluid_domains: &self.fluid_domains,
|
||||
step,
|
||||
})
|
||||
}
|
||||
|
||||
/// The steps in analysis order.
|
||||
pub fn steps(&self) -> &[Step] {
|
||||
&self.steps
|
||||
|
||||
Reference in new issue
Block a user