forked from 1b-agent/fieldcraft-upstream
engine: rubber-seal sample, a Mooney-Rivlin gasket ring squeezed 25 %
file.sample rubber-seal: a gasket ring of hybrid hex8 elements in four mapped quarter blocks, squeezed 2.5 mm between plates bonded to it (nonlinear static, Mooney-Rivlin rubber). Its test frees the plates and matches the closed form for incompressible uniaxial compression within 3 %, and bounds the bonded ring's extra stiffness. The plates are prescribed displacements until implicit contact (#161) can press it into a groove. Closes #163 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
This commit is contained in:
1 parent
581646bdac
commit
cf8ed71e4f
5 files changed
+105
-1
No files matched your search
@@ -19,6 +19,7 @@ is committed and listed here with its exports.
|
|||||||
| docs/media/og.svg, docs/media/og.png | FieldCraft contributors | Original social-preview image (1280 × 640): the application icon's cube layer (assets/app-icon/FieldCraft.icon/Assets/02-part.svg) beside the name and tagline set in Inter (OFL, crates/ui-egui/fonts) | MIT OR Apache-2.0 |
|
| docs/media/og.svg, docs/media/og.png | FieldCraft contributors | Original social-preview image (1280 × 640): the application icon's cube layer (assets/app-icon/FieldCraft.icon/Assets/02-part.svg) beside the name and tagline set in Inter (OFL, crates/ui-egui/fonts) | MIT OR Apache-2.0 |
|
||||||
| docs/media/bracket-partition.png, docs/media/bracket-hex.png, docs/media/bracket-stress.png, docs/media/setup-symbols.png | FieldCraft contributors | Original: FieldCraft renders (headless MCP screenshots and a window capture) of corpus parts and the cantilever sample | MIT OR Apache-2.0 |
|
| docs/media/bracket-partition.png, docs/media/bracket-hex.png, docs/media/bracket-stress.png, docs/media/setup-symbols.png | FieldCraft contributors | Original: FieldCraft renders (headless MCP screenshots and a window capture) of corpus parts and the cantilever sample | MIT OR Apache-2.0 |
|
||||||
| docs/media/examples/drop-test.png | FieldCraft contributors | Original: the FieldCraft window captured (`ui.capture`) with the solved drop-test sample (docs/examples.md) | MIT OR Apache-2.0 |
|
| docs/media/examples/drop-test.png | FieldCraft contributors | Original: the FieldCraft window captured (`ui.capture`) with the solved drop-test sample (docs/examples.md) | MIT OR Apache-2.0 |
|
||||||
|
| docs/media/examples/rubber-seal.png | FieldCraft contributors | Original: the FieldCraft window captured (`ui.capture`) with the solved rubber-seal sample (docs/examples.md) | MIT OR Apache-2.0 |
|
||||||
| crates/decks/tests/golden/nastran/*.bdf | FieldCraft contributors | Written by our Nastran writer from our own test models (crates/decks/tests/nastran.rs) | MIT OR Apache-2.0 |
|
| crates/decks/tests/golden/nastran/*.bdf | FieldCraft contributors | Written by our Nastran writer from our own test models (crates/decks/tests/nastran.rs) | MIT OR Apache-2.0 |
|
||||||
| assets/icons/assembly.instance.svg, assets/icons/assembly.instance.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/assembly.instance.json` (`cargo xtask icons`) | MIT OR Apache-2.0 |
|
| assets/icons/assembly.instance.svg, assets/icons/assembly.instance.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/assembly.instance.json` (`cargo xtask icons`) | MIT OR Apache-2.0 |
|
||||||
| assets/icons/deck.read.svg, assets/icons/deck.read.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/deck.read.json` (`cargo xtask icons`) | MIT OR Apache-2.0 |
|
| assets/icons/deck.read.svg, assets/icons/deck.read.vectorcraft | FieldCraft contributors | Original toolbar icon drawn with VectorCraft from `assets/icons/src/deck.read.json` (`cargo xtask icons`) | MIT OR Apache-2.0 |
|
||||||
|
|||||||
@@ -809,7 +809,7 @@ pub const RECOVERY_LIST: &[Field] = &[path("dir", "Folder", false).hint("the rec
|
|||||||
pub const RECOVER: &[Field] = &[text("id", "Autosave").req(), path("dir", "Folder", false).hint("the recovery folder")];
|
pub const RECOVER: &[Field] = &[text("id", "Autosave").req(), path("dir", "Folder", false).hint("the recovery folder")];
|
||||||
pub const RECOVERY_DISCARD: &[Field] = &[text("id", "Autosave"), flag("all", "All"), path("dir", "Folder", false).hint("the recovery folder")];
|
pub const RECOVERY_DISCARD: &[Field] = &[text("id", "Autosave"), flag("all", "All"), path("dir", "Folder", false).hint("the recovery folder")];
|
||||||
pub const SAMPLE: &[Field] =
|
pub const SAMPLE: &[Field] =
|
||||||
&[choice("name", "Sample", &[("cantilever", "Cantilever"), ("impact", "Impact"), ("parts", "Parts"), ("drop-test", "Drop test")]).req().def("\"cantilever\"")];
|
&[choice("name", "Sample", &[("cantilever", "Cantilever"), ("impact", "Impact"), ("parts", "Parts"), ("drop-test", "Drop test"), ("rubber-seal", "Rubber seal")]).req().def("\"cantilever\"")];
|
||||||
pub const COUNT: &[Field] = &[int("count", "Count", 1, 1_000_000).def("1")];
|
pub const COUNT: &[Field] = &[int("count", "Count", 1, 1_000_000).def("1")];
|
||||||
|
|
||||||
pub const DECK: &[Field] = &[
|
pub const DECK: &[Field] = &[
|
||||||
|
|||||||
@@ -61,6 +61,13 @@ pub const SAMPLES: &[Sample] = &[
|
|||||||
design: Some(UPRIGHT_BRACKET),
|
design: Some(UPRIGHT_BRACKET),
|
||||||
script: |_| drop_test(),
|
script: |_| drop_test(),
|
||||||
},
|
},
|
||||||
|
Sample {
|
||||||
|
name: "rubber-seal",
|
||||||
|
label: "Rubber Seal",
|
||||||
|
about: "A rubber gasket ring squeezed 25 % between bonded plates: nonlinear static, Mooney–Rivlin, large strain",
|
||||||
|
design: None,
|
||||||
|
script: |_| rubber_seal(),
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
/// The sample called `name`.
|
/// The sample called `name`.
|
||||||
@@ -288,6 +295,44 @@ pub fn drop_test() -> Value {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The rubber seal: inner and outer radius and height of the ring (mm), and how far it is squeezed.
|
||||||
|
pub const SEAL: [f64; 3] = [20.0, 30.0, 10.0];
|
||||||
|
pub const SEAL_SQUEEZE: f64 = 2.5;
|
||||||
|
|
||||||
|
/// A rubber gasket ring ([`SEAL`]: 20 mm inside radius, 30 mm outside, 10 mm high) of hybrid hex8
|
||||||
|
/// elements, four mapped quarter blocks with arc edges (body `Seal`, block faces `bottom` and
|
||||||
|
/// `top`), squeezed by [`SEAL_SQUEEZE`] (25 %) between plates bonded to it: `bottom` fixed, `top`
|
||||||
|
/// moved down with its in-plane motion held. Generic rubber: E = 6 MPa, ν = 0.49, Mooney–Rivlin
|
||||||
|
/// with C01 = 0.2 MPa (C10 = μ/2 − C01 = 0.81 MPa). Step `Squeeze` (nonlinear static).
|
||||||
|
pub fn rubber_seal() -> Value {
|
||||||
|
let [inner, outer, height] = SEAL;
|
||||||
|
let mut steps = vec![json!({"command": "model.units", "params": {"system": "mm-t-s-N-MPa"}})];
|
||||||
|
for quarter in 0..4u8 {
|
||||||
|
let (a0, a1) = (f64::from(quarter) * std::f64::consts::FRAC_PI_2, f64::from(quarter + 1) * std::f64::consts::FRAC_PI_2);
|
||||||
|
let at = |r: f64, a: f64, z: f64| [r * a.cos(), r * a.sin(), z];
|
||||||
|
let corners =
|
||||||
|
[at(inner, a0, 0.0), at(outer, a0, 0.0), at(outer, a1, 0.0), at(inner, a1, 0.0), at(inner, a0, height), at(outer, a0, height), at(outer, a1, height), at(inner, a1, height)];
|
||||||
|
let arc = |a: u8, b: u8, z: f64| json!({"corners": [a, b], "arc": {"center": [0, 0, z]}});
|
||||||
|
steps.push(json!({"command": "mesh.block", "params": {
|
||||||
|
"body": "Seal", "kind": "hex8", "corners": corners, "divisions": [5, 10, 5],
|
||||||
|
"edges": [arc(1, 4, 0.0), arc(2, 3, 0.0), arc(5, 8, height), arc(6, 7, height)],
|
||||||
|
"faces": {"1": "bottom", "2": "top"}
|
||||||
|
}}));
|
||||||
|
}
|
||||||
|
steps.extend([
|
||||||
|
json!({"command": "model.set.create", "params": {"name": "seal", "geometry": [{"body": "Seal"}]}}),
|
||||||
|
json!({"command": "model.set.create", "params": {"name": "bottom", "geometry": [{"body": "Seal", "face": "bottom"}]}}),
|
||||||
|
json!({"command": "model.set.create", "params": {"name": "top", "geometry": [{"body": "Seal", "face": "top"}]}}),
|
||||||
|
json!({"command": "model.material.create", "params": {"name": "Rubber", "elastic": {"type": "isotropic", "young": 6, "poisson": 0.49}, "density": 1.1e-9,
|
||||||
|
"hyperelastic": {"type": "mooney_rivlin", "c01": 0.2}}}),
|
||||||
|
json!({"command": "model.section.create", "params": {"name": "Seal", "set": "seal", "material": "Rubber", "kind": {"type": "solid"}, "formulation": {"hybrid": true}}}),
|
||||||
|
json!({"command": "model.boundary_condition.create", "params": {"name": "Base", "type": "fixed", "target": {"set": "bottom"}}}),
|
||||||
|
json!({"command": "model.boundary_condition.create", "params": {"name": "Press", "type": "displacement", "target": {"set": "top"}, "values": [0, 0, -SEAL_SQUEEZE, null, null, null]}}),
|
||||||
|
json!({"command": "model.step.create", "params": {"name": "Squeeze", "procedure": {"type": "nonlinear_static", "initial_increment": 0.1}, "boundary_conditions": ["Base", "Press"]}}),
|
||||||
|
]);
|
||||||
|
Value::Array(steps)
|
||||||
|
}
|
||||||
|
|
||||||
/// Six times the signed volume of the tetrahedron `p[0..4]` (positive when p3 lies on the side
|
/// Six times the signed volume of the tetrahedron `p[0..4]` (positive when p3 lies on the side
|
||||||
/// p0 → p1 → p2 turns counter-clockwise around).
|
/// p0 → p1 → p2 turns counter-clockwise around).
|
||||||
fn signed_volume(p: &[[f64; 3]]) -> f64 {
|
fn signed_volume(p: &[[f64; 3]]) -> f64 {
|
||||||
@@ -365,6 +410,44 @@ mod tests {
|
|||||||
s
|
s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The squeeze force of the ring, against the closed form for an incompressible Mooney–Rivlin
|
||||||
|
/// solid in uniaxial compression, P = 2 (λ − λ⁻²)(C10 + C01/λ) per unit of original area: the
|
||||||
|
/// same ring between frictionless plates (each face held only along z, the rigid motions
|
||||||
|
/// held at three nodes on the symmetry planes) compresses evenly and matches it within 3 %
|
||||||
|
/// (ν = 0.49 is nearly incompressible); bonded, the bulge needs more force (shape factor
|
||||||
|
/// ¼ (b − a)/h = 0.5 by the loaded over the free area), within 2.5 times.
|
||||||
|
#[test]
|
||||||
|
fn the_rubber_seal_squeezes_as_mooney_rivlin_rubber() {
|
||||||
|
let [inner, outer, height] = SEAL;
|
||||||
|
let area = std::f64::consts::PI * (outer * outer - inner * inner);
|
||||||
|
let lambda: f64 = 1.0 - SEAL_SQUEEZE / height;
|
||||||
|
let (c01, c10) = (0.2, 6.0 / (2.0 * 1.49) / 2.0 - 0.2);
|
||||||
|
let closed = 2.0 * (lambda - lambda.powi(-2)) * (c10 + c01 / lambda) * area;
|
||||||
|
let squeeze = |s: &mut Session| -> f64 {
|
||||||
|
s.execute("solve.nonlinear", &json!({})).unwrap();
|
||||||
|
let r = s.execute("results.reactions", &json!({"step": "Squeeze", "sets": ["top"]})).unwrap();
|
||||||
|
r["sets"][0]["force"][2].as_f64().unwrap()
|
||||||
|
};
|
||||||
|
let mut s = open("rubber-seal");
|
||||||
|
let bonded = squeeze(&mut s);
|
||||||
|
let node = |s: &Session, xyz: [f64; 3]| -> u32 {
|
||||||
|
*s.model.nodes().iter().find(|(_, n)| (0..3).all(|k| (n.xyz[k] - xyz[k]).abs() < 1e-9)).unwrap().0
|
||||||
|
};
|
||||||
|
let held = [("hold_y", [inner, 0.0, 0.0], [None, Some(0.0), None]), ("hold_x", [0.0, inner, 0.0], [Some(0.0), None, None]), ("hold_y2", [-inner, 0.0, 0.0], [None, Some(0.0), None])];
|
||||||
|
for (name, at, [ux, uy, _]) in held {
|
||||||
|
let n = node(&s, at);
|
||||||
|
s.execute("model.set.create", &json!({"name": name, "nodes": [n]})).unwrap();
|
||||||
|
s.execute("model.boundary_condition.create", &json!({"name": name, "type": "displacement", "target": {"set": name}, "values": [ux, uy, null, null, null, null]})).unwrap();
|
||||||
|
}
|
||||||
|
s.execute("model.boundary_condition.edit", &json!({"name": "Base", "type": "displacement", "target": {"set": "bottom"}, "values": [null, null, 0, null, null, null]})).unwrap();
|
||||||
|
s.execute("model.boundary_condition.edit", &json!({"name": "Press", "type": "displacement", "target": {"set": "top"}, "values": [null, null, -SEAL_SQUEEZE, null, null, null]})).unwrap();
|
||||||
|
s.execute("model.step.edit", &json!({"name": "Squeeze", "boundary_conditions": ["Base", "Press", "hold_y", "hold_x", "hold_y2"]})).unwrap();
|
||||||
|
let free = squeeze(&mut s);
|
||||||
|
assert!((free - closed).abs() < 0.03 * closed.abs(), "frictionless {free} N against {closed} N");
|
||||||
|
let stiffer = bonded / free;
|
||||||
|
assert!(stiffer > 1.05 && stiffer < 2.5, "bonded {bonded} N, frictionless {free} N");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn the_drop_test_opens_ready_to_solve() {
|
fn the_drop_test_opens_ready_to_solve() {
|
||||||
let s = open("drop-test");
|
let s = open("drop-test");
|
||||||
|
|||||||
@@ -26,3 +26,23 @@ turning into vibration with a balance under 0.01 %.
|
|||||||
**Check:** the mass equals ρ V of the bracket (0.5 %); it meets the floor at √(2 g h) =
|
**Check:** the mass equals ρ V of the bracket (0.5 %); it meets the floor at √(2 g h) =
|
||||||
6.26 m/s, with ½ m v² = 1 748 mJ of kinetic energy (1 %); energy is conserved (balance < 1 %);
|
6.26 m/s, with ½ m v² = 1 748 mJ of kinetic energy (1 %); energy is conserved (balance < 1 %);
|
||||||
by 1 ms the floor has taken about half its downward momentum.
|
by 1 ms the floor has taken about half its downward momentum.
|
||||||
|
|
||||||
|
## Rubber seal (`rubber-seal`)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
A rubber gasket ring (20 mm inside radius, 30 mm outside, 10 mm high) squeezed 2.5 mm (25 %)
|
||||||
|
between two plates bonded to it. Generic rubber, E = 6 MPa, ν = 0.49, Mooney–Rivlin with
|
||||||
|
C01 = 0.2 MPa; 1 000 hybrid (u–p) hex8 elements in four mapped quarter blocks. Nonlinear static
|
||||||
|
with large deformation: Newton–Raphson in nine increments, under a second.
|
||||||
|
|
||||||
|
**Look at:** the deformed shape at scale 1 — the free inner and outer walls bulge out, and
|
||||||
|
von Mises peaks (about 7 MPa) where the bulge meets the bonded plates. Results ▸ Increment steps
|
||||||
|
through the converged increments.
|
||||||
|
|
||||||
|
**Check:** the same ring between frictionless plates compresses evenly, and its force matches
|
||||||
|
the closed form for an incompressible Mooney–Rivlin solid in uniaxial compression,
|
||||||
|
P = 2 (λ − λ⁻²)(C10 + C01/λ) per original area: 3 430 N against 3 466 N (within 3 %). Bonded,
|
||||||
|
the bulge makes it stiffer (shape factor 0.5): 6 518 N, held between 1.05 and 2.5 times the
|
||||||
|
frictionless force. The groove and the plates are prescribed displacements today; with implicit
|
||||||
|
contact (#161) the ring will be pressed by contact against a groove.
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 247 KiB |
Reference in new issue
Block a user