forked from 1b-agent/fieldcraft-upstream
1D elements I: trusses (rod2, rod3), point masses, rotary inertia, springs and dashpots (#38)
* Model, fem and decks: point masses, rotary inertia, springs, dashpots and rod3 trusses Model: PointMass and Spring definitions (spring | dashpot; to ground or two-point; along a degree of freedom, a vector or the line of the ends) on a set or reference point, resolved per node; ElementKind::Rod3. fem: rod2/rod3 truss kernel on Basis::Line2/Line3 declaring ElementDofs (translations). Decks: Abaqus T3D3, MASS, ROTARYI, SPRING1/2/A, DASHPOT1/2/A; Nastran CONM1, CELAS2, CDAMP2; LS-DYNA ELEMENT_MASS/INERTIA and ELEMENT_DISCRETE; what a format cannot take is reported. Commands model.mass.* and model.spring.*. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm * Implicit solver: trusses, point masses, rotary inertia and springs on the DofLayout Trusses declare their translations like solids; point masses, inertia and springs ask the layout for the kinds they need at their nodes (translations, rotations), and reference points they use get six degrees of freedom. The matrix pattern takes rows for those points; dashpots are ignored with a warning. Existing results are bit-identical (hash-checked). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm * Explicit solver: trusses, point masses, springs and dashpots on the node-major layout Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm * docs: fem.md on trusses and discrete elements in the layout; regenerate features.md Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm * docs: trusses in plane models and corotational explicit trusses 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:
53 files changed
+3626
-110
No files matched your search
@@ -306,8 +306,22 @@ pub static CATALOGUE: &[Capability] = &[
|
||||
soon("interaction.contact.find", "Find Contacts", Interactions, "Contact", "M7", "Detect contact pairs automatically"),
|
||||
soon("interaction.rigid_wall", "Rigid Wall", Interactions, "Contact", "M10", "Rigid walls for explicit dynamics"),
|
||||
soon("interaction.bolt", "Bolt Load", Interactions, "Connectors", "M7", "Bolt pretension"),
|
||||
soon("interaction.spring", "Spring", Interactions, "Connectors", "M7", "Springs and dashpots"),
|
||||
soon("interaction.mass", "Point Mass", Interactions, "Connectors", "M7", "Point masses and inertia"),
|
||||
ready_more(
|
||||
"model.spring.create",
|
||||
"Spring",
|
||||
Interactions,
|
||||
"Connectors",
|
||||
"Spring or dashpot to the ground or between two points, along a direction or their line",
|
||||
&["model.spring.edit", "model.spring.delete"],
|
||||
),
|
||||
ready_more(
|
||||
"model.mass.create",
|
||||
"Point Mass",
|
||||
Interactions,
|
||||
"Connectors",
|
||||
"Point mass and rotary inertia at nodes or a reference point",
|
||||
&["model.mass.edit", "model.mass.delete"],
|
||||
),
|
||||
soon("interaction.connector", "Connector", Interactions, "Connectors", "M7", "Connector elements between points"),
|
||||
soon("interaction.fsi", "FSI Interface", Interactions, "Coupled", "M13", "The fluid–structure interface"),
|
||||
// ---- Solve ----
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
//! Model definition commands: create, edit and delete sets, materials, sections, reference
|
||||
//! points, couplings, boundary conditions, loads, fluid domains and steps, and the step activity
|
||||
//! table.
|
||||
//! points, couplings, point masses, springs and dashpots, boundary conditions, loads, fluid
|
||||
//! domains and steps, and the step activity table.
|
||||
//!
|
||||
//! Each definition's parameters are its JSON form in the model (the same form a `.modelcraft`
|
||||
//! file stores), plus its `name`. `create` fails when the name is taken, `edit` replaces an
|
||||
//! existing definition, `delete` refuses while another definition refers to it.
|
||||
|
||||
use modelcraft_model::{
|
||||
BoundaryCondition, Coupling, FaceCondition, FluidBoundary, FluidDomain, Load, Material, Model, ReferencePoint, Section, Set, Step,
|
||||
BoundaryCondition, Coupling, FaceCondition, FluidBoundary, FluidDomain, Load, Material, Model, PointMass, ReferencePoint, Section, Set, Spring,
|
||||
Step,
|
||||
};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde_json::{Value, json};
|
||||
@@ -176,6 +177,34 @@ definition_commands!(
|
||||
COUPLING_DELETE
|
||||
);
|
||||
|
||||
definition_commands!(
|
||||
"point mass",
|
||||
PointMass,
|
||||
"mass",
|
||||
"Point Mass",
|
||||
r#"{"name": "motor", "target": {"set": "…"} | {"reference_point": "…"}, "mass": 2.5 | [mx, my, mz] (per global direction), "inertia": [I11, I22, I33, I12, I13, I23] (tensor about the node in global axes; needs rotations: a reference point)} — at every node of the target, each with the full value"#,
|
||||
add_mass,
|
||||
replace_mass,
|
||||
remove_mass,
|
||||
MASS_CREATE,
|
||||
MASS_EDIT,
|
||||
MASS_DELETE
|
||||
);
|
||||
|
||||
definition_commands!(
|
||||
"spring",
|
||||
Spring,
|
||||
"spring",
|
||||
"Spring or Dashpot",
|
||||
r#"{"name": "mount", "type": "spring", "stiffness": 1000 | "type": "dashpot", "coefficient": 5, "first": {"set": "…"} | {"reference_point": "…"}, "second": (optional; without it each node of `first` connects to the ground; with it each end is one node), "direction": {"dof": 1..6} (4–6: torsional) | {"vector": [x, y, z]} | "line" (along the two ends, two-point only)} — dashpots act in dynamic steps; static and frequency steps ignore them"#,
|
||||
add_spring,
|
||||
replace_spring,
|
||||
remove_spring,
|
||||
SPRING_CREATE,
|
||||
SPRING_EDIT,
|
||||
SPRING_DELETE
|
||||
);
|
||||
|
||||
definition_commands!(
|
||||
"boundary condition",
|
||||
BoundaryCondition,
|
||||
|
||||
@@ -54,6 +54,12 @@ pub static ALL: &[CommandSpec] = &[
|
||||
define::COUPLING_CREATE,
|
||||
define::COUPLING_EDIT,
|
||||
define::COUPLING_DELETE,
|
||||
define::MASS_CREATE,
|
||||
define::MASS_EDIT,
|
||||
define::MASS_DELETE,
|
||||
define::SPRING_CREATE,
|
||||
define::SPRING_EDIT,
|
||||
define::SPRING_DELETE,
|
||||
define::BOUNDARY_CONDITION_CREATE,
|
||||
define::BOUNDARY_CONDITION_EDIT,
|
||||
define::BOUNDARY_CONDITION_DELETE,
|
||||
|
||||
@@ -950,4 +950,32 @@ pub(crate) mod tests {
|
||||
// Defining steps edited the model, so the impact result is stale.
|
||||
assert_eq!(s.solved_steps().count(), 0);
|
||||
}
|
||||
|
||||
/// A mass on a spring and a dashpot, defined, solved and written through commands; editing
|
||||
/// and deleting them goes through the same checks as every definition.
|
||||
#[test]
|
||||
fn spring_mass_through_commands() {
|
||||
let mut s = Session::default();
|
||||
let script = json!([
|
||||
{"command": "mesh.node.add", "params": {"label": 1, "xyz": [0, 0, 0]}},
|
||||
{"command": "model.set.create", "params": {"name": "n", "nodes": [1]}},
|
||||
{"command": "model.mass.create", "params": {"name": "m", "target": {"set": "n"}, "mass": 4}},
|
||||
{"command": "model.spring.create", "params": {"name": "k", "type": "spring", "first": {"set": "n"}, "direction": {"dof": 1}, "stiffness": 400}},
|
||||
{"command": "model.spring.create", "params": {"name": "c", "type": "dashpot", "first": {"set": "n"}, "direction": {"vector": [1, 0, 0]}, "coefficient": 1}},
|
||||
{"command": "model.boundary_condition.create", "params": {"name": "yz", "type": "displacement", "target": {"set": "n"}, "values": [null, 0, 0, null, null, null]}},
|
||||
{"command": "model.step.create", "params": {"name": "Modes", "procedure": {"type": "frequency", "modes": 1}, "boundary_conditions": ["yz"]}},
|
||||
]);
|
||||
s.run_script(&script).unwrap();
|
||||
let r = s.execute("solve.frequency", &json!({})).unwrap();
|
||||
let f = r["modes"][0]["frequency"].as_f64().unwrap();
|
||||
assert!((f - 10.0 / (2.0 * std::f64::consts::PI)).abs() < 1e-12, "{r}");
|
||||
assert!(r["warnings"].as_array().unwrap().iter().any(|w| w.as_str().unwrap().contains("dashpots `c`")), "{r}");
|
||||
assert!(s.execute("model.mass.edit", &json!({"name": "m", "target": {"set": "n"}, "mass": -1})).is_err());
|
||||
assert!(s.execute("model.set.delete", &json!({"name": "n"})).is_err(), "the set is in use");
|
||||
s.execute("model.mass.edit", &json!({"name": "m", "target": {"set": "n"}, "mass": 1})).unwrap();
|
||||
let r = s.execute("solve.frequency", &json!({})).unwrap();
|
||||
assert!((r["modes"][0]["frequency"].as_f64().unwrap() - 20.0 / (2.0 * std::f64::consts::PI)).abs() < 1e-12);
|
||||
s.execute("model.spring.delete", &json!({"name": "c"})).unwrap();
|
||||
assert!(s.model.springs().len() == 1 && s.model.mass("m").is_some());
|
||||
}
|
||||
}
|
||||
@@ -404,6 +404,13 @@ mod tests {
|
||||
json!({"name": "Bend", "procedure": {"type": "static"}, "loads": "push"}),
|
||||
json!({"step": "Bend", "loads": ["nope"], "active": "yes"}),
|
||||
json!({"step": "Bend", "boundary_conditions": [], "loads": []}),
|
||||
json!({"name": "A", "target": {"set": "end"}, "mass": [1e308, -1, f64::MAX], "inertia": [1, 1, 1, 1e300, 0, 0]}),
|
||||
json!({"name": "A", "target": {"reference_point": "RP"}, "inertia": [1, 2]}),
|
||||
json!({"name": "A", "target": {"set": "end"}, "mass": "heavy"}),
|
||||
json!({"name": "A", "type": "spring", "first": {"set": "end"}, "second": {"set": "end"}, "direction": "line", "stiffness": 1e308}),
|
||||
json!({"name": "A", "type": "spring", "first": {"set": "end"}, "direction": {"dof": 255}, "stiffness": 1}),
|
||||
json!({"name": "A", "type": "dashpot", "first": {"set": "end"}, "direction": {"vector": [1e-320, 0, 0]}, "coefficient": -1}),
|
||||
json!({"name": "A", "type": "dashpot", "first": {"reference_point": "RP"}, "direction": {"dof": 4}, "coefficient": 1e300}),
|
||||
json!({"system": "furlongs"}),
|
||||
// Fluid domains, boundaries and steps.
|
||||
json!({"name": "A", "material": "Steel", "min": [0, 0, 0], "max": [1e308, 1, 1], "spacing": 1e-300}),
|
||||
|
||||
Reference in new issue
Block a user