Rename the project to FieldCraft

Crates fieldcraft-*, binaries fieldcraft and fieldcraft-cli, the .fieldcraft file (format tag
fieldcraft/1, manifest fieldcraft.json), FIELDCRAFT_* environment variables, the MCP server
name, egui widget ids (fc_*), the app icon document FieldCraft.icon and its generated family,
docs and licences. No compatibility shim: no files of the earlier name exist.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
This commit is contained in:
jyvikoandClaude Opus 5.5 authored and Irakli Kourtis committed 2026-10-13 23:51:40 -04:00
1 parent 9bc4e1b182
commit d5bd74570f
268 files changed
+1666 -1666

No files matched your search

+3 -3
View File
@@ -1,6 +1,6 @@
[package]
name = "modelcraft-decks"
description = "ModelCraft's solver decks: Abaqus input, Nastran bulk data and LS-DYNA keyword writers (and later readers)"
name = "fieldcraft-decks"
description = "FieldCraft's solver decks: Abaqus input, Nastran bulk data and LS-DYNA keyword writers (and later readers)"
version.workspace = true
edition.workspace = true
license.workspace = true
@@ -9,7 +9,7 @@ license.workspace = true
workspace = true
[dependencies]
modelcraft-model = { workspace = true }
fieldcraft-model = { workspace = true }
thiserror = { workspace = true }
[dev-dependencies]
+10 -10
View File
@@ -6,7 +6,7 @@
//! # Layout
//!
//! The deck is *flat*: nodes, elements, sets, materials, sections and couplings sit at model level,
//! with no parts, assembly or instances. ModelCraft has one mesh with model-wide labels and no
//! with no parts, assembly or instances. FieldCraft has one mesh with model-wide labels and no
//! instance transforms, so a part/assembly wrapper would only prefix every set name with an
//! instance name (`Part-1-1.root`) and bring the 80-character limit on the joined name closer. When
//! the model gains several parts or instances the writer switches to the part/assembly form.
@@ -53,7 +53,7 @@
//!
//! # Element types
//!
//! | ModelCraft | Solid section | Shell section | Beam | Rod |
//! | FieldCraft | Solid section | Shell section | Beam | Rod |
//! |---|---|---|---|---|
//! | tet4 | C3D4 (H) | | | |
//! | tet10 | C3D10 (H) | | | |
@@ -76,7 +76,7 @@
//! differs by direction and a spring along a skew fixed vector are reported (they need a local
//! orientation, not written yet).
//!
//! | ModelCraft | Plane stress | Plane strain | Axisymmetric |
//! | FieldCraft | Plane stress | Plane strain | Axisymmetric |
//! |---|---|---|---|
//! | tri3 | CPS3 | CPE3 | CAX3 |
//! | tri6 | CPS6 | CPE6 | CAX6 |
@@ -115,7 +115,7 @@
use std::collections::{BTreeMap, BTreeSet};
use std::fmt::Write as _;
use modelcraft_model::{
use fieldcraft_model::{
BeamProfile, BoundaryCondition, CouplingKind, Elastic, Element, ElementKind, Hardening, Integration, Load, Model, OutputVariable, Plastic,
Procedure, Resolved, Section, SectionKind, Step, Symmetry, Target,
};
@@ -330,10 +330,10 @@ fn discrete_groups<'r>(model: &Model, r: &'r Resolved) -> Result<Vec<DiscreteGro
let mut next = model.elements().keys().next_back().map_or(1, |l| l.saturating_add(1));
let mut label = || -> Result<u32> {
let l = next;
if l > modelcraft_model::MAX_LABEL {
if l > fieldcraft_model::MAX_LABEL {
return Err(Error::Limit(format!(
"no element label left below {} for point masses, springs and dashpots",
modelcraft_model::MAX_LABEL as u64 + 1
fieldcraft_model::MAX_LABEL as u64 + 1
)));
}
next += 1;
@@ -371,7 +371,7 @@ fn discrete_groups<'r>(model: &Model, r: &'r Resolved) -> Result<Vec<DiscreteGro
}
for s in &r.springs {
let what = s.kind();
let line = matches!(s.direction, modelcraft_model::ResolvedDirection::Line(_));
let line = matches!(s.direction, fieldcraft_model::ResolvedDirection::Line(_));
let (element_type, dofs, nodes) = match (s.second, crate::axis_dof(s.direction), line) {
(Some(b), _, true) => (if s.dashpot { "DASHPOTA" } else { "SPRINGA" }, String::new(), vec![s.first, b]),
(Some(b), Some(d), false) => (if s.dashpot { "DASHPOT2" } else { "SPRING2" }, format!("{d}, {d}"), vec![s.first, b]),
@@ -556,7 +556,7 @@ impl<'a> StepLoads<'a> {
/// `everything` is every element and the section sets that hold them (gravity on the whole
/// model).
fn new(model: &'a Model, r: &'a Resolved, step: &'a Step, everything: &'a (Vec<&'a str>, Vec<u32>)) -> StepLoads<'a> {
let axisymmetric = r.extent() == Some(modelcraft_model::Extent::Revolved);
let axisymmetric = r.extent() == Some(fieldcraft_model::Extent::Revolved);
let mut s = StepLoads { concentrated: crate::concentrated_loads(model, r, &step.loads), axisymmetric, ..StepLoads::default() };
let (mut gravity, mut body) = (Vec::new(), Vec::new());
for name in &step.loads {
@@ -637,8 +637,8 @@ impl Deck {
None => "units not specified".to_string(),
};
self.line("*HEADING");
self.line(&format!("ModelCraft model, {units}"));
self.comment("Written by ModelCraft. The numbers are in one consistent unit system and are not converted.");
self.line(&format!("FieldCraft model, {units}"));
self.comment("Written by FieldCraft. The numbers are in one consistent unit system and are not converted.");
}
fn nodes(&mut self, model: &Model) -> Result<()> {
+9 -9
View File
@@ -1,7 +1,7 @@
//! Solver decks: the solver-neutral model written as Abaqus input, Nastran bulk data and LS-DYNA
//! keyword files. Each format is its own module (`abaqus`, `nastran`, `lsdyna`), written from the
//! vendor's public reference manual in our own words; examples and expected files are our own.
//! Writers read [`modelcraft_model::Model`] and its `resolve()` result, never another format.
//! Writers read [`fieldcraft_model::Model`] and its `resolve()` result, never another format.
//!
//! Writers produce text; saving it is the caller's business (the engine's `deck.write`), so the
//! crate stays free of the file system. [`FORMATS`] lists every writer by the name `deck.write`
@@ -15,8 +15,8 @@ pub mod nastran;
use std::collections::{BTreeMap, BTreeSet};
use modelcraft_model::nodal::edge_pressure;
use modelcraft_model::{Diagnostic, Load, Model, Resolved, ResolvedDirection};
use fieldcraft_model::nodal::edge_pressure;
use fieldcraft_model::{Diagnostic, Load, Model, Resolved, ResolvedDirection};
#[derive(Debug, Clone, PartialEq, thiserror::Error)]
pub enum Error {
@@ -25,7 +25,7 @@ pub enum Error {
Unsupported { format: &'static str, what: String },
/// The model is not ready to write (its check failed).
#[error(transparent)]
Model(#[from] modelcraft_model::Error),
Model(#[from] fieldcraft_model::Error),
#[error("limit reached: {0}")]
Limit(String),
}
@@ -70,11 +70,11 @@ impl Format {
/// 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())));
return Err(Error::Model(fieldcraft_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 shown: String = n.chars().take(fieldcraft_model::MAX_NAME_LEN).collect();
return Err(Error::Model(fieldcraft_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();
@@ -84,7 +84,7 @@ pub fn with_steps(model: &Model, names: &[String]) -> Result<Model> {
Ok(out)
}
/// A fluid step: solved by ModelCraft's lattice Boltzmann solver, never written to a deck.
/// A fluid step: solved by FieldCraft's lattice Boltzmann solver, never written to a deck.
pub(crate) fn fluid_step(format: &'static str, step: &str) -> Error {
Error::Unsupported { format, what: format!("fluid step `{step}` (solve it with solve.fluid; write the structural steps only)") }
}
@@ -155,7 +155,7 @@ pub fn concentrated_loads<'a>(model: &Model, r: &Resolved, active: impl IntoIter
/// The consistent nodal forces (degrees of freedom 1 and 2) of the active pressures on a 2D
/// model's element edges, for formats that write them as concentrated loads: the model's edge
/// integration ([`modelcraft_model::nodal::edge_pressure`]), a positive pressure pushing
/// integration ([`fieldcraft_model::nodal::edge_pressure`]), a positive pressure pushing
/// into the element, summed per node and direction; zero sums are left out.
pub fn edge_pressure_loads<'a>(model: &Model, r: &Resolved, active: impl IntoIterator<Item = &'a str>) -> Result<BTreeMap<(u32, u8), f64>> {
let mut out: BTreeMap<(u32, u8), f64> = BTreeMap::new();
+1 -1
View File
@@ -17,7 +17,7 @@
//!
//! Every mapping is checked by the sign of the element Jacobian in the tests.
use modelcraft_model::{ElementKind, Formulation, Integration, SectionKind};
use fieldcraft_model::{ElementKind, Formulation, Integration, SectionKind};
/// How an element is written: its keyword and its node list in LS-DYNA order.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+5 -5
View File
@@ -61,7 +61,7 @@ mod elements;
use std::collections::{BTreeMap, BTreeSet};
use modelcraft_model::{
use fieldcraft_model::{
BoundaryCondition, CouplingKind, Diagnostic, Dofs, Elastic, Element, ElementKind, Hardening, Load, Material, Model, OutputVariable, Plastic,
Procedure, Resolved, ResolvedDirection, Section, SectionKind, Step, Target,
};
@@ -102,7 +102,7 @@ const MAX_SHELL_POINTS: u32 = 10;
/// A resolved model that breaks its own invariants (unreachable after `resolve`).
fn broken(what: String) -> Error {
Error::Model(modelcraft_model::Error::Invalid(format!("inconsistent resolved model: {what}")))
Error::Model(fieldcraft_model::Error::Invalid(format!("inconsistent resolved model: {what}")))
}
/// One `*PART`: a section's elements of one kind.
@@ -159,7 +159,7 @@ fn write_with(model: &Model, r: &Resolved) -> Result<Written> {
let cases = if steps.len() > 1 { steps.len() } else { 0 };
d.line("*KEYWORD long=y");
d.comment("Written by ModelCraft: implicit linear analysis, long format (20-column fields).");
d.comment("Written by FieldCraft: implicit linear analysis, long format (20-column fields).");
d.comment(&format!("Units: {} (consistent; nothing is converted).", model.units().label()));
if cases > 0 {
d.comment(&format!("{cases} steps are written as *CASE analyses: run LS-DYNA with CASE on its command line."));
@@ -676,7 +676,7 @@ fn discrete(d: &mut Deck, model: &Model, r: &Resolved, plan: &Plan) -> Result<()
let limit = |what: &str| Error::Limit(format!("no {what} id left for point masses, springs and dashpots"));
let mut eid = model.elements().keys().next_back().copied().unwrap_or(0);
let mut next_eid = || -> Result<u32> {
eid = eid.checked_add(1).filter(|e| *e <= modelcraft_model::MAX_LABEL).ok_or_else(|| limit("element"))?;
eid = eid.checked_add(1).filter(|e| *e <= fieldcraft_model::MAX_LABEL).ok_or_else(|| limit("element"))?;
Ok(eid)
};
d.comment("Point masses");
@@ -754,7 +754,7 @@ fn discrete(d: &mut Deck, model: &Model, r: &Resolved, plan: &Plan) -> Result<()
while taken.contains(&node) {
node = node.checked_add(1).ok_or_else(|| limit("node"))?;
}
if node > modelcraft_model::MAX_LABEL {
if node > fieldcraft_model::MAX_LABEL {
return Err(limit("node"));
}
ground.push((node, model.label_xyz(s.first).ok_or_else(|| broken(format!("node {}", s.first)))?));
+5 -5
View File
@@ -52,7 +52,7 @@ mod card;
use std::collections::{BTreeMap, BTreeSet};
use std::fmt::Write as _;
use modelcraft_model::{
use fieldcraft_model::{
BeamProfile, Coupling, CouplingKind, Dofs, Elastic, ElementKind, Formulation, Hardening, Integration, Load, MAX_LABEL, Model, OutputVariable,
Plastic, Procedure, Resolved, SectionKind, Step,
};
@@ -688,9 +688,9 @@ impl<'m> Deck<'m> {
fn emit(&self) -> Result<String> {
let mut out = String::new();
out.push_str("$ Nastran bulk data written by ModelCraft\n");
out.push_str("$ Nastran bulk data written by FieldCraft\n");
let _ = writeln!(out, "$ units: {}", self.model.units().label());
let _ = writeln!(out, "SOL {}\nCEND\nTITLE = ModelCraft model\nECHO = NONE", self.sol);
let _ = writeln!(out, "SOL {}\nCEND\nTITLE = FieldCraft model\nECHO = NONE", self.sol);
for (i, sub) in self.subcases.iter().enumerate() {
let _ = writeln!(out, "SUBCASE {}", i + 1);
let title: String = format!(" SUBTITLE = {}", sub.step.name).chars().take(72).collect();
@@ -945,7 +945,7 @@ impl<'m> Deck<'m> {
}
/// A `PLOAD4` for one element face, positive pushing into the element.
fn pressure(&self, sid: u32, f: modelcraft_model::ElementFace, magnitude: f64) -> Option<Card> {
fn pressure(&self, sid: u32, f: fieldcraft_model::ElementFace, magnitude: f64) -> Option<Card> {
let el = self.model.element(f.element)?;
let card = Card::new("PLOAD4").int(sid).int(f.element);
if el.kind.dimension() == 2 {
@@ -1010,7 +1010,7 @@ fn plasticity(out: &mut String, name: &str, mid: u32, young: f64, plastic: &Plas
write_named(out, &format!("stress-strain curve of {name}"), tid, Card::new("TABLES1").int(tid).end_line().reals(curve).text("ENDT"))
}
(Plastic::Table { .. }, None) | (Plastic::JohnsonCook { .. }, _) => {
Err(Error::Model(modelcraft_model::Error::Invalid(format!("material `{name}`: its plasticity was not planned for MATS1"))))
Err(Error::Model(fieldcraft_model::Error::Invalid(format!("material `{name}`: its plasticity was not planned for MATS1"))))
}
}
}
+12 -12
View File
@@ -5,8 +5,8 @@
use std::collections::{BTreeMap, BTreeSet};
use std::path::PathBuf;
use modelcraft_decks::{Error, abaqus};
use modelcraft_model::{
use fieldcraft_decks::{Error, abaqus};
use fieldcraft_model::{
BeamProfile, BoundaryCondition, Coupling, CouplingKind, Distribution, Dofs, Elastic, ElementFace, ElementKind, Formulation, Integration, Load,
Material, Model, OutputVariable, Procedure, ReferencePoint, Section, SectionKind, Set, Step, Symmetry, Target, UnitSystem,
};
@@ -185,12 +185,12 @@ fn read(deck: &str) -> Parsed {
fn golden(name: &str, deck: &str) {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/abaqus").join(format!("{name}.inp"));
if std::env::var_os("MODELCRAFT_BLESS").is_some() {
if std::env::var_os("FIELDCRAFT_BLESS").is_some() {
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
std::fs::write(&path, deck).unwrap();
}
let expected = std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("{}: {e} (run with MODELCRAFT_BLESS=1 to write it)", path.display()));
assert!(expected == deck, "{name}.inp differs from the written deck (MODELCRAFT_BLESS=1 rewrites it):\n{deck}");
let expected = std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("{}: {e} (run with FIELDCRAFT_BLESS=1 to write it)", path.display()));
assert!(expected == deck, "{name}.inp differs from the written deck (FIELDCRAFT_BLESS=1 rewrites it):\n{deck}");
}
fn assert_lines_fit(deck: &str) {
@@ -513,16 +513,16 @@ fn numbers_read_back_exactly() {
#[test]
fn largest_labels_longest_numbers_and_names_fit_a_line() {
let mut m = Model::default();
let base = modelcraft_model::MAX_LABEL - 30;
let base = fieldcraft_model::MAX_LABEL - 30;
let ugly = -1.234_567_890_123_456_7e-300;
let mut xyz = one_element(ElementKind::Hex20).nodes().values().map(|n| n.xyz).collect::<Vec<_>>();
xyz[0] = [ugly, ugly, ugly];
for (i, p) in xyz.iter().enumerate() {
m.set_node(base + i as u32, *p).unwrap();
}
m.set_element(modelcraft_model::MAX_LABEL, ElementKind::Hex20, (0..20).map(|i| base + i).collect()).unwrap();
let long = format!("A{}", "x".repeat(modelcraft_model::MAX_NAME_LEN - 1));
m.add_set(&long, Set { elements: vec![modelcraft_model::MAX_LABEL], nodes: (0..20).map(|i| base + i).collect(), ..Set::default() }).unwrap();
m.set_element(fieldcraft_model::MAX_LABEL, ElementKind::Hex20, (0..20).map(|i| base + i).collect()).unwrap();
let long = format!("A{}", "x".repeat(fieldcraft_model::MAX_NAME_LEN - 1));
m.add_set(&long, Set { elements: vec![fieldcraft_model::MAX_LABEL], nodes: (0..20).map(|i| base + i).collect(), ..Set::default() }).unwrap();
m.add_material(
&long,
Material { elastic: Some(Elastic::Isotropic { young: ugly.abs(), poisson: 0.3 }), density: Some(1.0), ..Material::default() },
@@ -537,7 +537,7 @@ fn largest_labels_longest_numbers_and_names_fit_a_line() {
let deck = abaqus::write(&m).unwrap();
assert_lines_fit(&deck);
let p = read(&deck);
assert_eq!(p.elements[&modelcraft_model::MAX_LABEL].1.len(), 20, "a hex20 continues onto a second line");
assert_eq!(p.elements[&fieldcraft_model::MAX_LABEL].1.len(), 20, "a hex20 continues onto a second line");
assert_eq!(p.nodes[&base], [ugly; 3]);
}
@@ -704,7 +704,7 @@ fn a_model_that_is_not_ready_is_refused() {
}
/// The cantilever with its steel given each plasticity in turn.
fn plastic_cantilever(p: modelcraft_model::Plastic) -> Model {
fn plastic_cantilever(p: fieldcraft_model::Plastic) -> Model {
let mut m = cantilever();
m.replace_material("Steel", Material { plastic: Some(p), ..steel() }).unwrap();
m
@@ -714,7 +714,7 @@ fn plastic_cantilever(p: modelcraft_model::Plastic) -> Model {
/// erosion, failure models of Abaqus/Explicit, are reported.
#[test]
fn plasticity_is_written_and_failure_reported() {
use modelcraft_model::{Damage, Erosion, Hardening, HardeningPoint, Plastic};
use fieldcraft_model::{Damage, Erosion, Hardening, HardeningPoint, Plastic};
let section = |deck: &str| deck.split("*MATERIAL, NAME=Steel").nth(1).and_then(|s| s.split("*SOLID SECTION").next()).unwrap().to_string();
let deck =
abaqus::write(&plastic_cantilever(Plastic::Bilinear { yield_stress: 250.0, hardening_modulus: 1000.0, hardening: Hardening::Isotropic }))
+2 -2
View File
@@ -1,6 +1,6 @@
*HEADING
ModelCraft model, units mm-t-s-N-MPa
** Written by ModelCraft. The numbers are in one consistent unit system and are not converted.
FieldCraft model, units mm-t-s-N-MPa
** Written by FieldCraft. The numbers are in one consistent unit system and are not converted.
** Nodes
*NODE
1, 0.0, 0.0, 0.0
+2 -2
View File
@@ -1,6 +1,6 @@
*HEADING
ModelCraft model, units mm-t-s-N-MPa
** Written by ModelCraft. The numbers are in one consistent unit system and are not converted.
FieldCraft model, units mm-t-s-N-MPa
** Written by FieldCraft. The numbers are in one consistent unit system and are not converted.
** Nodes
*NODE
1, 0.0, 0.0, 0.0
+2 -2
View File
@@ -1,6 +1,6 @@
*HEADING
ModelCraft model, units mm-t-s-N-MPa
** Written by ModelCraft. The numbers are in one consistent unit system and are not converted.
FieldCraft model, units mm-t-s-N-MPa
** Written by FieldCraft. The numbers are in one consistent unit system and are not converted.
** Nodes
*NODE
1, 0.0, 0.0, 0.0
+2 -2
View File
@@ -1,8 +1,8 @@
//! Trusses (rod3), point masses, rotary inertia, springs and dashpots in every deck format:
//! what each writes, and what each reports as unsupported.
use modelcraft_decks::{Error, abaqus, lsdyna, nastran};
use modelcraft_model::{
use fieldcraft_decks::{Error, abaqus, lsdyna, nastran};
use fieldcraft_model::{
BoundaryCondition, Direction, Elastic, ElementKind, Formulation, Mass, Material, Model, PointMass, Procedure, ReferencePoint, Section,
SectionKind, Set, Spring, Step, Target, UnitSystem,
};
@@ -1,8 +1,8 @@
$ Nastran bulk data written by ModelCraft
$ Nastran bulk data written by FieldCraft
$ units: mm-t-s-N-MPa
SOL 101
CEND
TITLE = ModelCraft model
TITLE = FieldCraft model
ECHO = NONE
SUBCASE 1
SUBTITLE = Bend
+2 -2
View File
@@ -1,8 +1,8 @@
$ Nastran bulk data written by ModelCraft
$ Nastran bulk data written by FieldCraft
$ units: unspecified
SOL 103
CEND
TITLE = ModelCraft model
TITLE = FieldCraft model
ECHO = NONE
SUBCASE 1
SUBTITLE = Modes
+2 -2
View File
@@ -1,8 +1,8 @@
$ Nastran bulk data written by ModelCraft
$ Nastran bulk data written by FieldCraft
$ units: mm-t-s-N-MPa
SOL 101
CEND
TITLE = ModelCraft model
TITLE = FieldCraft model
ECHO = NONE
SUBCASE 1
SUBTITLE = Sag
+7 -7
View File
@@ -1,13 +1,13 @@
//! LS-DYNA deck writer: golden files of our own small models, node-order mappings checked by the
//! sign of the element Jacobian, a re-read of our own output, and what the format cannot take.
//!
//! Golden files live in `tests/lsdyna/`; `MODELCRAFT_BLESS=1 cargo test -p modelcraft-decks`
//! Golden files live in `tests/lsdyna/`; `FIELDCRAFT_BLESS=1 cargo test -p fieldcraft-decks`
//! rewrites them after an intended change (review the diff).
use std::collections::{BTreeMap, BTreeSet};
use modelcraft_decks::{Error, lsdyna};
use modelcraft_model::*;
use fieldcraft_decks::{Error, lsdyna};
use fieldcraft_model::*;
// ---------------------------------------------------------------------------------------------
// Our own models.
@@ -200,11 +200,11 @@ fn golden(name: &str, model: &Model) -> lsdyna::Written {
let again = lsdyna::write(model).unwrap();
assert_eq!(written, again, "deterministic");
let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/lsdyna").join(format!("{name}.k"));
if std::env::var_os("MODELCRAFT_BLESS").is_some() {
if std::env::var_os("FIELDCRAFT_BLESS").is_some() {
std::fs::write(&path, &written.text).unwrap();
}
let expected = std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("{}: {e} (run with MODELCRAFT_BLESS=1)", path.display()));
assert!(written.text == expected, "{name}.k differs from the golden file (MODELCRAFT_BLESS=1 rewrites it)");
let expected = std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("{}: {e} (run with FIELDCRAFT_BLESS=1)", path.display()));
assert!(written.text == expected, "{name}.k differs from the golden file (FIELDCRAFT_BLESS=1 rewrites it)");
assert!(written.text.is_ascii());
for line in written.text.lines() {
assert!(line.len() <= 200 && line == line.trim_end(), "{line:?}");
@@ -688,7 +688,7 @@ fn hostile_numbers_stay_within_their_fields() {
fn explicit_steps_are_refused_by_every_format() {
let mut m = cantilever();
m.add_step(step("Impact", Procedure::ExplicitDynamic(ExplicitDynamic::new(1e-3)), &["clamp"], &[]), None).unwrap();
for f in modelcraft_decks::FORMATS {
for f in fieldcraft_decks::FORMATS {
let e = f.write(&m).unwrap_err();
assert!(matches!(e, Error::Unsupported { .. }), "{}: {e}", f.name);
assert!(e.to_string().contains("step `Impact`: explicit dynamic"), "{}: {e}", f.name);
+1 -1
View File
@@ -1,5 +1,5 @@
*KEYWORD long=y
$ Written by ModelCraft: implicit linear analysis, long format (20-column fields).
$ Written by FieldCraft: implicit linear analysis, long format (20-column fields).
$ Units: mm-t-s-N-MPa (consistent; nothing is converted).
*NODE
$ nid x y z
+1 -1
View File
@@ -1,5 +1,5 @@
*KEYWORD long=y
$ Written by ModelCraft: implicit linear analysis, long format (20-column fields).
$ Written by FieldCraft: implicit linear analysis, long format (20-column fields).
$ Units: mm-t-s-N-MPa (consistent; nothing is converted).
$ 2 steps are written as *CASE analyses: run LS-DYNA with CASE on its command line.
*NODE
+1 -1
View File
@@ -1,5 +1,5 @@
*KEYWORD long=y
$ Written by ModelCraft: implicit linear analysis, long format (20-column fields).
$ Written by FieldCraft: implicit linear analysis, long format (20-column fields).
$ Units: mm-t-s-N-MPa (consistent; nothing is converted).
*NODE
$ nid x y z
+1 -1
View File
@@ -1,5 +1,5 @@
*KEYWORD long=y
$ Written by ModelCraft: implicit linear analysis, long format (20-column fields).
$ Written by FieldCraft: implicit linear analysis, long format (20-column fields).
$ Units: m-kg-s-N-Pa (consistent; nothing is converted).
*NODE
$ nid x y z
+9 -9
View File
@@ -2,13 +2,13 @@
//! output (grids, elements, constraint sets, case control), node order checked against Nastran's
//! own element conventions by Jacobian sign and mid-side positions, and what is unsupported.
//!
//! Golden files are rewritten with `MODELCRAFT_BLESS=1 cargo test -p modelcraft-decks`.
//! Golden files are rewritten with `FIELDCRAFT_BLESS=1 cargo test -p fieldcraft-decks`.
use std::collections::BTreeMap;
use modelcraft_decks::nastran;
use modelcraft_decks::{Error, with_steps};
use modelcraft_model::{
use fieldcraft_decks::nastran;
use fieldcraft_decks::{Error, with_steps};
use fieldcraft_model::{
BeamProfile, BoundaryCondition, Coupling, CouplingKind, Distribution, Dofs, Elastic, ElementFace, ElementKind, Formulation, Integration, Load,
Material, Model, Procedure, ReferencePoint, Section, SectionKind, Set, Step, Target, UnitSystem,
};
@@ -266,12 +266,12 @@ fn modes() -> Model {
fn golden(name: &str, deck: &str) {
let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/golden/nastran").join(format!("{name}.bdf"));
if std::env::var_os("MODELCRAFT_BLESS").is_some() {
if std::env::var_os("FIELDCRAFT_BLESS").is_some() {
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
std::fs::write(&path, deck).unwrap();
}
let want = std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("{}: {e} (bless with MODELCRAFT_BLESS=1)", path.display()));
assert_eq!(deck, want, "{name}: the deck changed (bless with MODELCRAFT_BLESS=1 after checking it)");
let want = std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("{}: {e} (bless with FIELDCRAFT_BLESS=1)", path.display()));
assert_eq!(deck, want, "{name}: the deck changed (bless with FIELDCRAFT_BLESS=1 after checking it)");
}
fn unsupported(m: &Model) -> String {
@@ -640,7 +640,7 @@ fn steps_are_chosen_and_mixed_solutions_are_refused() {
// A modes step holds the clamp but carries no loads.
assert!(r.cards("FORCE").next().is_none());
assert_eq!(read(&nastran::write(&with_steps(&m, &only(&["Bend"])).unwrap()).unwrap()).sol, 101);
let nastran = modelcraft_decks::format("nastran").unwrap();
let nastran = fieldcraft_decks::format("nastran").unwrap();
assert_eq!(nastran.write_steps(&m, Some(&only(&["Bend"]))).unwrap().text, nastran::write(&with_steps(&m, &only(&["Bend"])).unwrap()).unwrap());
for bad in [only(&[]), only(&["Nope"]), only(&[&"x".repeat(100_000)]), only(&["", "\u{0}"])] {
@@ -740,7 +740,7 @@ fn conflicting_values_and_ids_at_the_top_of_the_range() {
/// damage and erosion are reported.
#[test]
fn plasticity_is_written_as_mats1() {
use modelcraft_model::{Erosion, Hardening, HardeningPoint, Plastic};
use fieldcraft_model::{Erosion, Hardening, HardeningPoint, Plastic};
let with = |p: Plastic| {
let mut m = cantilever();
m.replace_material("Steel", Material { plastic: Some(p), ..steel() }).unwrap();
+2 -2
View File
@@ -1,8 +1,8 @@
//! 2D models (plane stress, plane strain, axisymmetric) in every deck format: element names,
//! section data, edge surfaces and pressures, centrifugal loads, and what a format cannot write.
use modelcraft_decks::{Error, abaqus, edge_pressure_loads, lsdyna, nastran};
use modelcraft_model::{
use fieldcraft_decks::{Error, abaqus, edge_pressure_loads, lsdyna, nastran};
use fieldcraft_model::{
BoundaryCondition, Elastic, ElementEdge, ElementKind, Formulation, Integration, Load, Material, Model, Procedure, Section, SectionKind, Set,
Step, Target, UnitSystem,
};