LS-DYNA keyword writer (implicit static and eigenvalue, M3) (#25)

* decks: LS-DYNA keyword writer (long format, implicit static and eigenvalue)

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

* decks: LS-DYNA golden files, Jacobian-sign node-order tests, re-reader and refusals

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

* docs: LS-DYNA writer status and deck notes

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

* decks: simplify the LS-DYNA writer (plan decides once, cards written in place, shared test tables)

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

* decks: LS-DYNA joins FORMATS (deck.write lsdyna), refusals reported together, shared concentrated loads

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

* decks: LS-DYNA on main's shared deck API (steps parameter, model helpers)

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:39:45 -04:00
1 parent f4a0333d28
commit 81fa8d7220
13 files changed
+2353 -3

No files matched your search

+27 -1
View File
@@ -10,7 +10,7 @@ use crate::{CommandKind, CommandSpec, EngineError, Result, Session};
pub const WRITE: CommandSpec = CommandSpec {
id: "deck.write",
label: "Write Solver Deck",
params: r#"{"format": "abaqus|nastran", "path": "part.inp", "steps": ["Load"]} — writes the model as a solver deck (formats: modelcraft_decks::FORMATS); `steps` (optional) writes only those steps, as a Nastran run needs (static or frequency steps, not both); fails naming every error of the model check and everything the format cannot express"#,
params: r#"{"format": "abaqus|nastran|lsdyna", "path": "part.inp", "steps": ["Load"]} — writes the model as a solver deck (formats: modelcraft_decks::FORMATS); `steps` (optional) writes only those steps, as a Nastran run needs (static or frequency steps, not both); LS-DYNA writes several steps as *CASE analyses (run it with CASE); fails naming every error of the model check and everything the format cannot express"#,
kind: CommandKind::Session,
run: write,
};
@@ -77,4 +77,30 @@ mod tests {
}
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn writes_lsdyna_keyword_decks() {
let dir = std::env::temp_dir().join(format!("modelcraft-deck-lsdyna-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let path = dir.join("bar.k");
let mut s = Session::default();
crate::cmd::define::tests::defined_bar(&mut s);
let out = s.execute("deck.write", &json!({"format": "lsdyna", "path": path.to_string_lossy()})).unwrap();
let written = std::fs::read_to_string(&path).unwrap();
assert_eq!(written, modelcraft_decks::lsdyna::write(&s.model).unwrap().text);
assert_eq!(out["format"], "lsdyna");
assert!(written.starts_with("*KEYWORD long=y\n") && written.contains("*CASE_BEGIN_2"), "two steps, two cases");
// One chosen step: a single analysis, no cases.
s.execute("deck.write", &json!({"format": "lsdyna", "path": path.to_string_lossy(), "steps": ["Modes"]})).unwrap();
let one = std::fs::read_to_string(&path).unwrap();
assert!(one.contains("*CONTROL_IMPLICIT_EIGENVALUE") && !one.contains("*CASE_BEGIN") && !one.contains("*CONTROL_IMPLICIT_SOLUTION"), "{one}");
// What LS-DYNA cannot take is named and nothing is written.
s.execute("model.load.create", &json!({"name": "heat", "type": "body_force", "set": "all", "vector": [0, 0, 1]})).unwrap();
s.execute("model.step.activate", &json!({"step": "Bend", "loads": ["heat"]})).unwrap();
let other = dir.join("heat.k");
let e = s.execute("deck.write", &json!({"format": "lsdyna", "path": other.to_string_lossy()})).unwrap_err().to_string();
assert!(e.contains("not supported by LS-DYNA") && e.contains("load `heat`"), "{e}");
assert!(!other.exists());
let _ = std::fs::remove_dir_all(&dir);
}
}