forked from 1b-agent/fieldcraft-upstream
Explicit X2: large deformation, J2 and Johnson-Cook plasticity, damage and erosion (M10)
- model: materials gain solver-neutral plastic (bilinear isotropic/kinematic, hardening table, Johnson-Cook with adiabatic heating), damage (Johnson-Cook D1-D5) and erosion (plastic strain); explicit steps gain nonlinear_geometry (default on) and stress_rate (jaumann | green_naghdi). - fem: rotation (Hughes-Winget, Flanagan-Taylor), plasticity (radial return, capped bracketed Newton for Johnson-Cook, damage; the update takes the mechanical strain increment so thermal strains can be subtracted), closed-form hex8 gradients for deformed configurations. - explicit: updated Lagrangian hex8/tet4, per-element material state, erosion with reported energy, plastic work in the energy account. - solver: warns that the linear solver uses the elastic constants of plastic materials (results hash-identical). - decks: Abaqus *PLASTIC (+ Johnson-Cook keywords), Nastran MATS1/TABLES1, LS-DYNA *MAT_PLASTIC_KINEMATIC / *MAT_PIECEWISE_LINEAR_PLASTICITY; what a format cannot express is reported as Unsupported. - engine: model.material.plasticity, model.material.damage (catalogue ready), explicit element probe, eroded elements in the solve summary. Step commits before the rebase onto the DOF framework: branch explicit-x2-steps. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
This commit is contained in:
1 parent
4c0fcc80ad
commit
50764def18
38 files changed
+2839
-254
No files matched your search
@@ -80,7 +80,7 @@ definition_commands!(
|
||||
Material,
|
||||
"material",
|
||||
"Material",
|
||||
r#"{"name": "Steel", "elastic": {"type": "isotropic", "young": 210000, "poisson": 0.3} | {"type": "engineering_constants", "young": [E1, E2, E3], "poisson": [ν12, ν13, ν23], "shear": [G12, G13, G23]}, "density": 7.85e-9, "fluid": {"type": "newtonian", "viscosity": 1.0e-9} (dynamic viscosity; a fluid needs a density)} — in the model's unit system (model.units), never converted"#,
|
||||
r#"{"name": "Steel", "elastic": {"type": "isotropic", "young": 210000, "poisson": 0.3} | {"type": "engineering_constants", "young": [E1, E2, E3], "poisson": [ν12, ν13, ν23], "shear": [G12, G13, G23]}, "density": 7.85e-9, "fluid": {"type": "newtonian", "viscosity": 1.0e-9} (dynamic viscosity; a fluid needs a density), "plastic": {…} (see model.material.plasticity), "damage": {…}, "erosion": {…} (see model.material.damage)} — in the model's unit system (model.units), never converted"#,
|
||||
add_material,
|
||||
replace_material,
|
||||
remove_material,
|
||||
@@ -89,6 +89,51 @@ definition_commands!(
|
||||
MATERIAL_DELETE
|
||||
);
|
||||
|
||||
/// A copy of material `name` with `change` applied, put back through the model's validation.
|
||||
fn change_material(s: &mut Session, p: &Value, change: impl FnOnce(&mut Material, &Value) -> Result<()>) -> Result<Value> {
|
||||
let n = name(p)?;
|
||||
let mut m = s.model.material(n).cloned().ok_or_else(|| EngineError::Params(format!("material `{n}` does not exist")))?;
|
||||
change(&mut m, p)?;
|
||||
s.model.replace_material(n, m)?;
|
||||
Ok(json!({"name": n}))
|
||||
}
|
||||
|
||||
pub const MATERIAL_PLASTICITY: CommandSpec = CommandSpec {
|
||||
id: "model.material.plasticity",
|
||||
label: "Plasticity",
|
||||
params: r#"{"name": "Steel", "plastic": {"type": "bilinear", "yield_stress": 250, "hardening_modulus": 1000, "hardening": "isotropic|kinematic"} | {"type": "table", "points": [{"strain": 0, "stress": 250}, {"strain": 0.1, "stress": 300}]} | {"type": "johnson_cook", "a": 792, "b": 510, "n": 0.26, "c": 0.014, "m": 1.03, "reference_strain_rate": 1, "room_temperature": 294, "melt_temperature": 1793, "specific_heat": 4.77e8, "taylor_quinney": 0.9} | null} — von Mises plasticity of an isotropic material (null removes it); explicit dynamics uses it, the linear solver the elastic constants alone"#,
|
||||
kind: CommandKind::Model,
|
||||
run: |s: &mut Session, p: &Value| {
|
||||
change_material(s, p, |m, p| {
|
||||
m.plastic = req(p, "plastic")?;
|
||||
Ok(())
|
||||
})
|
||||
},
|
||||
};
|
||||
|
||||
pub const MATERIAL_DAMAGE: CommandSpec = CommandSpec {
|
||||
id: "model.material.damage",
|
||||
label: "Damage",
|
||||
params: r#"{"name": "Steel", "damage": {"type": "johnson_cook", "d1": 0.05, "d2": 3.44, "d3": -2.12, "d4": 0.002, "d5": 0.61} | null, "erosion": {"plastic_strain": 1.0} | null} — damage to failure and element erosion of a plastic material (each key optional; null removes it); a failed element is eroded in explicit dynamics"#,
|
||||
kind: CommandKind::Model,
|
||||
run: |s: &mut Session, p: &Value| {
|
||||
change_material(s, p, |m, p| {
|
||||
// Absent leaves a field as it is; null removes it.
|
||||
let (damage, erosion) = (opt(p, "damage")?, opt(p, "erosion")?);
|
||||
if damage.is_none() && erosion.is_none() {
|
||||
return Err(EngineError::Params("give `damage`, `erosion` or both".into()));
|
||||
}
|
||||
if let Some(d) = damage {
|
||||
m.damage = d;
|
||||
}
|
||||
if let Some(e) = erosion {
|
||||
m.erosion = e;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
},
|
||||
};
|
||||
|
||||
definition_commands!(
|
||||
"section",
|
||||
Section,
|
||||
@@ -423,4 +468,35 @@ pub(crate) mod tests {
|
||||
let e = s.execute("model.set.resolve", &json!({"name": "root"})).unwrap_err().to_string();
|
||||
assert!(e.contains("face left of body Bar is not on the mesh (the mesh carries no geometry tags)"), "{e}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plasticity_and_damage_edit_a_material() {
|
||||
let mut s = Session::default();
|
||||
defined_bar(&mut s);
|
||||
let bilinear = json!({"type": "bilinear", "yield_stress": 250, "hardening_modulus": 1000});
|
||||
s.execute("model.material.plasticity", &json!({"name": "Steel", "plastic": bilinear})).unwrap();
|
||||
assert!(s.model.material("Steel").unwrap().plastic.is_some());
|
||||
// Damage needs plasticity; D4 and D5 need Johnson–Cook: refused, the model unchanged.
|
||||
let before = s.model.clone();
|
||||
let jc_damage = json!({"type": "johnson_cook", "d1": 0.05, "d2": 3.44, "d3": -2.12, "d4": 0.002, "d5": 0.61});
|
||||
assert!(s.execute("model.material.damage", &json!({"name": "Steel", "damage": jc_damage})).is_err());
|
||||
assert_eq!(s.model, before);
|
||||
s.execute(
|
||||
"model.material.damage",
|
||||
&json!({"name": "Steel", "damage": {"type": "johnson_cook", "d1": 0.3, "d2": 0, "d3": 0}, "erosion": {"plastic_strain": 0.5}}),
|
||||
)
|
||||
.unwrap();
|
||||
let m = s.model.material("Steel").unwrap();
|
||||
assert!(m.damage.is_some() && m.erosion.is_some());
|
||||
s.execute("model.material.damage", &json!({"name": "Steel", "erosion": null})).unwrap();
|
||||
assert!(s.model.material("Steel").unwrap().erosion.is_none() && s.model.material("Steel").unwrap().damage.is_some());
|
||||
// Removing plasticity under damage is refused.
|
||||
assert!(s.execute("model.material.plasticity", &json!({"name": "Steel", "plastic": null})).is_err());
|
||||
for bad in
|
||||
[json!({"name": "Steel"}), json!({"name": "Nope", "plastic": null}), json!({"name": "Steel", "plastic": {"type": "table", "points": []}})]
|
||||
{
|
||||
assert!(s.execute("model.material.plasticity", &bad).is_err(), "{bad}");
|
||||
}
|
||||
assert!(s.execute("model.material.damage", &json!({"name": "Steel"})).is_err());
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user