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 <[email protected]>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
This commit is contained in:
jyvikoandClaude Opus 5.5 authored and Irakli Kourtis committed 2026-10-14 00:34:33 -04:00
1 parent 73f35bd7f7
commit 95c02bc728
3 files changed
+29 -7

No files matched your search

+12 -7
View File
@@ -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<u32> {
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<Diagnostic>) {
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<u32> = 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))
}
};
+7
View File
@@ -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<ResolvedSet> {
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<String>) {
let c = &self.classification;
+10
View File
@@ -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);
}