Nastran bulk data writer (SOL 101/103) and deck.write steps (M3) (#24)

* Nastran bulk data writer: fixed-format entries, exact large-field reals, SOL 101/103 decks

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* Nastran writer tests: golden decks, a re-reader of our output, node order against Nastran's conventions

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* docs: Nastran writer in the roadmap, parity table and deck notes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* Nastran writer: simplify planning (subcases, one property table), stream cards into the deck, write through the VFS

ElementKind::corner_count/is_quadratic and Dofs::bits/from_bits join the model.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* Nastran joins the shared deck formats; deck.write chooses steps for every format

nastran::write_resolved is the FORMATS row; Format::write_steps and with_steps write a subset of
steps (a Nastran run is static or frequency steps); problems go through the shared unsupported().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
authored and GitHub committed 2026-10-13 17:34:26 -04:00
1 parent a71f85eb58
commit f4a0333d28
14 files changed
+2266 -5

No files matched your search

+10
View File
@@ -32,6 +32,16 @@ impl Dofs {
self.0 == 0
}
/// The degrees of freedom as bits: bit `d - 1` for degree of freedom `d`.
pub fn bits(self) -> u8 {
self.0
}
/// The degrees of freedom of bits (bits above the sixth are ignored).
pub fn from_bits(bits: u8) -> Dofs {
Dofs(bits & 0b111_111)
}
/// The degrees of freedom in order.
pub fn list(self) -> Vec<u8> {
(1..=6).filter(|d| self.contains(*d)).collect()
+20
View File
@@ -76,6 +76,22 @@ impl ElementKind {
}
}
/// Corner nodes: the first ones in the node list (the rest are mid-side nodes).
pub fn corner_count(self) -> usize {
match self {
ElementKind::Rod2 | ElementKind::Beam2 => 2,
ElementKind::Tri3 | ElementKind::Tri6 => 3,
ElementKind::Quad4 | ElementKind::Quad8 | ElementKind::Tet4 | ElementKind::Tet10 => 4,
ElementKind::Wedge6 => 6,
ElementKind::Hex8 | ElementKind::Hex20 => 8,
}
}
/// Whether the element has mid-side nodes.
pub fn is_quadratic(self) -> bool {
self.node_count() > self.corner_count()
}
/// Topological dimension: 1 for line elements, 2 for shells, 3 for solids.
pub fn dimension(self) -> u8 {
match self {
@@ -387,6 +403,10 @@ mod tests {
assert_eq!(ElementKind::Tet4.faces().len(), 4);
assert_eq!(ElementKind::Wedge6.faces().len(), 5);
assert_eq!(ElementKind::Hex20.edges().len(), 12);
for kind in ElementKind::ALL {
let mid_sides = kind.edges().iter().filter(|e| e.len() == 3).count();
assert_eq!(kind.corner_count() + mid_sides, kind.node_count(), "{kind:?}");
}
}
#[test]