From 95c02bc7284b59a4ec39db209ee9ac6851873f15 Mon Sep 17 00:00:00 2001 From: jyviko <2501648+jyviko@users.noreply.github.com> Date: Tue, 13 Oct 2026 23:39:47 -0400 Subject: [PATCH] model: resolve_set_partial, and the rotating-node rule as Model::rotational_nodes resolve_set_partial resolves the members of a set that are on the mesh, for display: a set with a missing member still shows what it has, where resolve_set refuses it. rotational_nodes is the check's rule for which nodes have rotations (shell and beam nodes, not those of 2D sections), so the viewport's symbols and the check agree. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm --- crates/model/src/check.rs | 19 ++++++++++++------- crates/model/src/sets.rs | 7 +++++++ crates/model/tests/definition.rs | 10 ++++++++++ 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/crates/model/src/check.rs b/crates/model/src/check.rs index 82d9fa1..5121442 100644 --- a/crates/model/src/check.rs +++ b/crates/model/src/check.rs @@ -165,6 +165,17 @@ impl Model { Ok(resolved) } + /// The mesh nodes with rotational degrees of freedom: those of shells (2D elements that are + /// not `planar`: plane stress, plane strain or axisymmetric) and of beams. Reference points + /// rotate too. + pub fn rotational_nodes(&self, planar: impl Fn(u32) -> bool) -> BTreeSet { + self.elements + .iter() + .filter(|(l, e)| (e.kind.dimension() == 2 && !planar(**l)) || e.kind == ElementKind::Beam2) + .flat_map(|(_, e)| e.nodes.iter().copied()) + .collect() + } + fn analyze(&self) -> (Resolved, Vec) { let mut d = Diagnostics::default(); let mut resolved = Resolved::default(); @@ -381,13 +392,7 @@ impl Model { let has_rotations = |t: &Target| match t { Target::ReferencePoint(_) => true, Target::Set(s) => { - let rotational: &BTreeSet = rotational.get_or_init(|| { - self.elements - .iter() - .filter(|(l, e)| (e.kind.dimension() == 2 && !resolved.planar.contains_key(l)) || e.kind == ElementKind::Beam2) - .flat_map(|(_, e)| e.nodes.iter().copied()) - .collect() - }); + let rotational = rotational.get_or_init(|| self.rotational_nodes(|l| resolved.planar.contains_key(&l))); set_of(s).nodes.iter().any(|n| rotational.contains(n)) } }; diff --git a/crates/model/src/sets.rs b/crates/model/src/sets.rs index 2df63c7..87e2cfb 100644 --- a/crates/model/src/sets.rs +++ b/crates/model/src/sets.rs @@ -138,6 +138,13 @@ impl Model { if missing.is_empty() { Ok(resolved) } else { Err(Error::NotFound(format!("set `{name}`: {}", missing.join("; ")))) } } + /// Resolve one set against the current mesh, leaving out the members that are not on it + /// (for display: a set with a missing member still shows what it has). `None` when there is + /// no set called `name`. + pub fn resolve_set_partial(&self, name: &str) -> Option { + self.sets.get(name).map(|set| self.resolve_definition(set).0) + } + /// Resolve a definition, and list the members that are not on the mesh. pub(crate) fn resolve_definition(&self, set: &Set) -> (ResolvedSet, Vec) { let c = &self.classification; diff --git a/crates/model/tests/definition.rs b/crates/model/tests/definition.rs index c4c8c3a..022642b 100644 --- a/crates/model/tests/definition.rs +++ b/crates/model/tests/definition.rs @@ -516,3 +516,13 @@ fn two_dimensional_models_resolve_edges_and_check_their_rules() { assert!(m.add_load("hostile", bad.clone()).is_err(), "{bad:?}"); } } + +#[test] +fn display_resolves_what_is_on_the_mesh() { + let mut m = defined(); + // A set with a node the mesh does not have: the check refuses it, the display shows the rest. + m.add_set("half", Set { nodes: vec![1, 999], ..Set::default() }).unwrap(); + assert!(m.resolve_set("half").is_err()); + assert_eq!(m.resolve_set_partial("half").map(|r| r.nodes), Some(vec![1])); + assert_eq!(m.resolve_set_partial("nope"), None); +}