diff --git a/ATTRIBUTION.md b/ATTRIBUTION.md index bacc57f..7c2429a 100644 --- a/ATTRIBUTION.md +++ b/ATTRIBUTION.md @@ -27,6 +27,8 @@ is committed and listed here with its exports. | docs/media/examples/flow-past.png | FieldCraft contributors | Original: the FieldCraft window captured (`ui.capture`) with the flow-past sample (its bracket; the viewport does not draw flow results yet) (docs/examples.md) | MIT OR Apache-2.0 | | docs/media/examples/cantilever.png | FieldCraft contributors | Original: the FieldCraft window captured (`ui.capture`) with the solved cantilever sample (docs/examples.md) | MIT OR Apache-2.0 | | docs/media/examples/impact.png | FieldCraft contributors | Original: the FieldCraft window captured (`ui.capture`) with the solved impact sample (docs/examples.md) | MIT OR Apache-2.0 | +| docs/media/examples/ibeam.png | FieldCraft contributors | Original: the FieldCraft window captured (`ui.capture`) with the solved ibeam sample (docs/examples.md) | MIT OR Apache-2.0 | +| crates/ui-egui/thumbnails/ibeam.png | FieldCraft contributors | Original: the solved ibeam sample rendered by FieldCraft (`ui.render`, light theme), cropped to the model and scaled to 320 px wide (ImageMagick, 128-colour palette), the ibeam sample's thumbnail on the Start card | MIT OR Apache-2.0 | | docs/media/examples/parts.png | FieldCraft contributors | Original: the FieldCraft window captured (`ui.capture`) with the parts sample (meshes only, nothing to solve) (docs/examples.md) | MIT OR Apache-2.0 | | crates/ui-egui/thumbnails/flow-past.png | FieldCraft contributors | Original: the flow-past sample as docs/media/examples/flow-past.png shows it, rendered by FieldCraft (`ui.render`, light theme), cropped to the model and scaled to 320 px wide (ImageMagick, 128-colour palette), the flow-past sample's thumbnail on the Start card | MIT OR Apache-2.0 | | crates/ui-egui/thumbnails/cantilever.png | FieldCraft contributors | Original: the cantilever sample as docs/media/examples/cantilever.png shows it, rendered by FieldCraft (`ui.render`, light theme), cropped to the model and scaled to 320 px wide (ImageMagick, 128-colour palette), the cantilever sample's thumbnail on the Start card | MIT OR Apache-2.0 | diff --git a/crates/engine/src/cmd/forms.rs b/crates/engine/src/cmd/forms.rs index 1652b21..8e402f4 100644 --- a/crates/engine/src/cmd/forms.rs +++ b/crates/engine/src/cmd/forms.rs @@ -1543,6 +1543,7 @@ pub const SAMPLE: &[Field] = &[choice( ("cantilever", "Cantilever"), ("impact", "Impact"), ("parts", "Parts"), + ("ibeam", "I-beam cantilever"), ("drop-test", "Drop test"), ("rubber-seal", "Rubber seal"), ("flow-past", "Flow past a bracket"), diff --git a/crates/engine/src/sample.rs b/crates/engine/src/sample.rs index d245ee9..142f1e6 100644 --- a/crates/engine/src/sample.rs +++ b/crates/engine/src/sample.rs @@ -11,7 +11,7 @@ //! explicit dynamic step. //! //! The examples gallery (docs/examples.md), each ready to solve with `solve.run`: -//! [`drop_test`], [`rubber_seal`], [`flow_past`], [`heated_bracket`], [`bracket_hex`] and [`crank_arm`]. Their +//! [`ibeam`], [`drop_test`], [`rubber_seal`], [`flow_past`], [`heated_bracket`], [`bracket_hex`] and [`crank_arm`]. Their //! geometry is a SolidCraft script run when the sample opens ([`Sample::design`]): a corpus //! recipe (`tests/corpus`) or [`UPRIGHT_BRACKET`]. @@ -60,6 +60,14 @@ pub const SAMPLES: &[Sample] = &[ prepare: None, script: |_| impact(), }, + Sample { + name: "ibeam", + label: "I-Beam Cantilever", + about: "A 200 mm steel I-beam swept all-hex, clamped at one end with 1 kN on the other through a coupling: static, ready to solve", + design: Some(CANTILEVER_I_BEAM), + prepare: None, + script: |_| ibeam(), + }, Sample { name: "drop-test", label: "Drop Test", @@ -290,6 +298,36 @@ pub fn impact() -> Value { Value::Array(steps) } +/// The 200 mm I-beam lying along x, web upright (`tests/corpus/cantilever-i-beam`): 40 × 40 mm, +/// 3 mm flanges, a 2 mm web. Body `Beam`; its end faces are `F2:start` (x = 0) and `F2:top` +/// (x = 200). +pub const CANTILEVER_I_BEAM: &str = include_str!("../../../tests/corpus/cantilever-i-beam/recipe.json"); + +/// The I-beam's tip load (N). +pub const IBEAM_LOAD: f64 = 1000.0; + +/// [`CANTILEVER_I_BEAM`] swept all-hex (hex8 of 4 mm, incompatible modes) in steel, clamped at +/// x = 0 (set `root`), [`IBEAM_LOAD`] down (−z) on a reference point on the beam's axis at the +/// free end, kinematically coupled to the end face (set `tip`) so the section stays plane. Step +/// `Load` (linear static). The README demo's model (tools/demo-video). +pub fn ibeam() -> Value { + let face = |f: &str| json!([{"body": "Beam", "face": f}]); + json!([ + {"command": "model.units", "params": {"system": "mm-t-s-N-MPa"}}, + {"command": "mesh.hex.sweep", "params": {"body": "Beam", "size": 4}}, + {"command": "model.set.create", "params": {"name": "beam", "geometry": [{"body": "Beam"}]}}, + {"command": "model.material.create", "params": {"name": "Steel", "elastic": {"type": "isotropic", "young": 210000, "poisson": 0.3}, "density": 7.85e-9}}, + {"command": "model.section.create", "params": {"name": "Beam", "set": "beam", "material": "Steel", "kind": {"type": "solid"}, "formulation": {"incompatible_modes": true}}}, + {"command": "model.set.create", "params": {"name": "root", "geometry": face("F2:start")}}, + {"command": "model.boundary_condition.create", "params": {"name": "Clamp", "type": "fixed", "target": {"set": "root"}}}, + {"command": "model.set.create", "params": {"name": "tip", "geometry": face("F2:top")}}, + {"command": "model.reference_point.create", "params": {"name": "End", "xyz": [200, 0, 20]}}, + {"command": "model.coupling.create", "params": {"name": "EndPlate", "reference_point": "End", "set": "tip", "kind": "kinematic"}}, + {"command": "model.load.create", "params": {"name": "Push", "type": "force", "target": {"reference_point": "End"}, "vector": [0, 0, -IBEAM_LOAD]}}, + {"command": "model.step.create", "params": {"name": "Load", "procedure": {"type": "static"}, "boundary_conditions": ["Clamp"], "loads": ["Push"]}}, + ]) +} + /// An L-bracket standing on its foot, Z up, as a SolidCraft script: a 60 × 40 mm foot and a 50 mm /// upright, both 8 mm thick, with an R6 fillet inside the bend (the corpus `filleted-bracket`'s /// profile without its holes, sketched on XZ so it sweeps along y). Body `Bracket`; its faces @@ -646,6 +684,21 @@ mod tests { assert_eq!(f["modes"].as_array().map(Vec::len), Some(6)); } + /// The I-beam opens swept all-hex and ready; solved, its tip deflection is the beam theory's + /// bending plus shear (P L³ / 3 E I + P L / G A_web: 0.149 + 0.035 mm) within 5 %. + #[test] + fn the_ibeam_bends_as_beam_theory() { + let mut s = open("ibeam"); + assert!(s.model.elements().values().all(|e| e.kind == fieldcraft_model::ElementKind::Hex8)); + s.execute("solve.static", &json!({})).unwrap(); + let p = s.execute("solve.probe", &json!({"step": "Load", "at": [[200.0, 0.0, 20.0]]})).unwrap(); + let tip = -p["nodes"][0]["displacement"][2].as_f64().unwrap(); + let (e, g) = (210000.0, 210000.0 / 2.6); + let i = (40.0 * 40.0f64.powi(3) - 38.0 * 34.0f64.powi(3)) / 12.0; + let theory = IBEAM_LOAD * 200.0f64.powi(3) / (3.0 * e * i) + IBEAM_LOAD * 200.0 / (g * 2.0 * 37.0); + assert!((tip - theory).abs() < 0.05 * theory, "{tip} against {theory}"); + } + #[test] fn the_impact_runs_and_keeps_frames_with_stresses() { let mut s = Session::default(); diff --git a/crates/ui-egui/src/samples.rs b/crates/ui-egui/src/samples.rs index 8bc1f8c..0698197 100644 --- a/crates/ui-egui/src/samples.rs +++ b/crates/ui-egui/src/samples.rs @@ -23,6 +23,7 @@ const THUMBNAILS: &[(&str, &[u8])] = &[ ("cantilever", include_bytes!("../thumbnails/cantilever.png")), ("impact", include_bytes!("../thumbnails/impact.png")), ("parts", include_bytes!("../thumbnails/parts.png")), + ("ibeam", include_bytes!("../thumbnails/ibeam.png")), ]; /// The thumbnails' aspect ratio (height over width). diff --git a/crates/ui-egui/thumbnails/ibeam.png b/crates/ui-egui/thumbnails/ibeam.png new file mode 100644 index 0000000..012036d Binary files /dev/null and b/crates/ui-egui/thumbnails/ibeam.png differ diff --git a/docs/examples.md b/docs/examples.md index 99897a1..ae1c967 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -188,3 +188,13 @@ panel. A hex8 block beside a tet4 cube: meshes to look at (element types, nodes, picking), nothing to solve. + +## I-beam cantilever (`ibeam`) + +![Von Mises stress on the swept hex8 I-beam, clamped at x = 0 with 1 kN on its free end](media/examples/ibeam.png) + +The 200 mm steel I-beam of the README video (`tests/corpus/cantilever-i-beam`), opened as +SolidCraft geometry with persistent face names, swept all-hex (hex8 of 4 mm, incompatible modes), +clamped at `F2:start` and loaded with 1000 N down through a kinematic coupling to `F2:top`. +**Look at:** the bending stress in the flanges, highest at the clamp; the tip moves 0.18 mm down +(beam theory with shear: 0.184 mm). diff --git a/docs/media/examples/ibeam.png b/docs/media/examples/ibeam.png new file mode 100644 index 0000000..0adfc21 Binary files /dev/null and b/docs/media/examples/ibeam.png differ