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

+32 -1
View File
@@ -10,6 +10,7 @@
#![forbid(unsafe_code)]
pub mod abaqus;
pub mod nastran;
use std::collections::{BTreeMap, BTreeSet};
@@ -52,10 +53,40 @@ impl Format {
let text = (self.writer)(model, &resolved)?;
Ok(Deck { text, warnings: resolved.warnings })
}
/// Write only the named steps (in the model's order), or every step when `steps` is `None`;
/// for runs that hold some steps only, such as a Nastran run (static or frequency steps).
pub fn write_steps(&self, model: &Model, steps: Option<&[String]>) -> Result<Deck> {
match steps {
None => self.write(model),
Some(names) => self.write(&with_steps(model, names)?),
}
}
}
/// The model with only the named steps, in its own order. Definitions the dropped steps used
/// stay (the check warns about the ones no step uses).
pub fn with_steps(model: &Model, names: &[String]) -> Result<Model> {
if names.is_empty() {
return Err(Error::Model(modelcraft_model::Error::Invalid("choose at least one step to write".into())));
}
if let Some(n) = names.iter().find(|n| model.step(n).is_none()) {
let shown: String = n.chars().take(modelcraft_model::MAX_NAME_LEN).collect();
return Err(Error::Model(modelcraft_model::Error::NotFound(format!("step `{shown}`"))));
}
let mut out = model.clone();
let dropped: Vec<String> = model.steps().iter().map(|s| s.name.clone()).filter(|s| !names.contains(s)).collect();
for name in dropped {
out.remove_step(&name)?;
}
Ok(out)
}
/// Every deck format, one row per writer.
pub const FORMATS: &[Format] = &[Format { name: "abaqus", extension: "inp", writer: abaqus::write_resolved }];
pub const FORMATS: &[Format] = &[
Format { name: "abaqus", extension: "inp", writer: abaqus::write_resolved },
Format { name: "nastran", extension: "bdf", writer: nastran::write_resolved },
];
/// The format called `name`.
pub fn format(name: &str) -> Option<&'static Format> {