forked from 1b-agent/fieldcraft-upstream
engine: add a small heated flow sample
Demonstrate temperature slices and signed face-average wall heat with cool air passing an isothermal bracket. Register the original CPU-rendered gallery thumbnail and document the coarse transient setup. Part of #157.
This commit is contained in:
1 parent
1e5ff08c5f
commit
e9ccf10692
7 files changed
+91
-1
No files matched your search
@@ -30,6 +30,7 @@ is committed and listed here with its exports.
|
|||||||
| 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/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 |
|
| 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 |
|
| 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 |
|
||||||
|
| crates/ui-egui/thumbnails/heated-flow.png | FieldCraft contributors | Original: the heated-flow sample's temperature slice rendered directly at 320 × 213 by `fieldcraft-cli snapshot` with the CPU renderer; geometry is the original upright-bracket recipe | 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 |
|
| 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/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 |
|
| 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 |
|
||||||
|
|||||||
@@ -1971,6 +1971,7 @@ pub const SAMPLE: &[Field] = &[choice(
|
|||||||
("drop-test", "Drop test"),
|
("drop-test", "Drop test"),
|
||||||
("rubber-seal", "Rubber seal"),
|
("rubber-seal", "Rubber seal"),
|
||||||
("flow-past", "Flow past a bracket"),
|
("flow-past", "Flow past a bracket"),
|
||||||
|
("heated-flow", "Heated flow past a bracket"),
|
||||||
("heated-bracket", "Heated bracket"),
|
("heated-bracket", "Heated bracket"),
|
||||||
("bracket-hex", "Hex-meshed bracket"),
|
("bracket-hex", "Hex-meshed bracket"),
|
||||||
("crank-arm", "Bicycle crank arm"),
|
("crank-arm", "Bicycle crank arm"),
|
||||||
|
|||||||
@@ -18,6 +18,8 @@
|
|||||||
use fieldcraft_geometry::Shape;
|
use fieldcraft_geometry::Shape;
|
||||||
use serde_json::{Value, json};
|
use serde_json::{Value, json};
|
||||||
|
|
||||||
|
mod heated_flow;
|
||||||
|
|
||||||
/// A sample the app offers (Help › Samples, the empty viewport, `file.sample`, `--sample NAME`).
|
/// A sample the app offers (Help › Samples, the empty viewport, `file.sample`, `--sample NAME`).
|
||||||
pub struct Sample {
|
pub struct Sample {
|
||||||
pub name: &'static str,
|
pub name: &'static str,
|
||||||
@@ -92,6 +94,14 @@ pub const SAMPLES: &[Sample] = &[
|
|||||||
prepare: None,
|
prepare: None,
|
||||||
script: |_| flow_past(),
|
script: |_| flow_past(),
|
||||||
},
|
},
|
||||||
|
Sample {
|
||||||
|
name: "heated-flow",
|
||||||
|
label: "Heated Flow Past a Bracket",
|
||||||
|
about: "Cool air passing an 80 °C bracket: temperature slices and signed wall heat, a small transient demonstration",
|
||||||
|
design: Some(UPRIGHT_BRACKET),
|
||||||
|
prepare: None,
|
||||||
|
script: heated_flow::script,
|
||||||
|
},
|
||||||
Sample {
|
Sample {
|
||||||
name: "heated-bracket",
|
name: "heated-bracket",
|
||||||
label: "Heated Bracket",
|
label: "Heated Bracket",
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
//! A small transient heated-flow demonstration using the original upright bracket.
|
||||||
|
//! Air at 20 °C passes an 80 °C bracket; no buoyancy or conjugate solid conduction.
|
||||||
|
|
||||||
|
use fieldcraft_geometry::Shape;
|
||||||
|
use serde_json::{Value, json};
|
||||||
|
|
||||||
|
pub(super) fn script(bodies: &[Shape]) -> Value {
|
||||||
|
let walls: Vec<Value> = bodies
|
||||||
|
.iter()
|
||||||
|
.filter(|b| b.name == "Bracket")
|
||||||
|
.flat_map(|b| {
|
||||||
|
b.faces().iter().map(move |f| {
|
||||||
|
json!({
|
||||||
|
"face": {"body": b.name, "face": f.name},
|
||||||
|
"condition": {"type": "temperature", "value": 80.0}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let [density, viscosity] = super::AIR;
|
||||||
|
json!([
|
||||||
|
{"command": "model.units", "params": {"system": "mm-t-s-N-MPa"}},
|
||||||
|
{"command": "model.material.create", "params": {"name": "Air", "density": density,
|
||||||
|
"fluid": {"type": "newtonian", "viscosity": viscosity},
|
||||||
|
"thermal": {"conductivity": 0.026, "specific_heat": 1.005e9}}},
|
||||||
|
{"command": "model.fluid_domain.create", "params": {
|
||||||
|
"name": "WarmAir", "material": "Air", "min": [-30, -20, 0], "max": [100, 60, 70], "spacing": 5,
|
||||||
|
"faces": {"x_min": {"type": "velocity", "velocity": [1, 0, 0]}, "x_max": {"type": "pressure", "pressure": 0},
|
||||||
|
"y_min": {"type": "slip"}, "y_max": {"type": "slip"}, "z_min": {"type": "wall"}, "z_max": {"type": "slip"}},
|
||||||
|
"obstacles": ["Bracket"], "initial_velocity": [1, 0, 0],
|
||||||
|
"thermal": {"initial_temperature": 20, "surfaces": walls,
|
||||||
|
"faces": {"x_min": {"type": "temperature", "value": 20}}}
|
||||||
|
}},
|
||||||
|
{"command": "model.step.create", "params": {"name": "HeatedFlow", "procedure": {
|
||||||
|
"type": "fluid", "domain": "WarmAir", "steps": 180, "frames": 6
|
||||||
|
}}}
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn heated_flow_sample_solves_and_exposes_temperature_and_wall_heat() {
|
||||||
|
let mut s = crate::Session::default();
|
||||||
|
s.execute("file.sample", &json!({"name": "heated-flow"})).unwrap();
|
||||||
|
s.execute("solve.fluid", &json!({"backend": "cpu"})).unwrap();
|
||||||
|
let report = s.execute("results.slice", &json!({"field": "temperature"})).unwrap();
|
||||||
|
let max = report["contour"]["range"]["max"]["value"].as_f64().unwrap();
|
||||||
|
let min = report["contour"]["range"]["min"]["value"].as_f64().unwrap();
|
||||||
|
assert!(max > 20.1 && max <= 80.01 && min >= 19.99, "{report}");
|
||||||
|
let report = s.execute("results.surface", &json!({"field": "wall_heat_flux"})).unwrap();
|
||||||
|
assert_eq!(report["contour"]["unit"], "mW/mm²");
|
||||||
|
assert!(report["wall_heat"]["faces"].as_array().unwrap().iter().any(|f| f["power"].as_f64().unwrap_or(0.0) < 0.0));
|
||||||
|
assert_eq!(report["frames"].as_array().unwrap().len(), 7);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,6 +20,7 @@ const THUMBNAILS: &[(&str, &[u8])] = &[
|
|||||||
("heated-bracket", include_bytes!("../thumbnails/heated-bracket.png")),
|
("heated-bracket", include_bytes!("../thumbnails/heated-bracket.png")),
|
||||||
("bracket-hex", include_bytes!("../thumbnails/bracket-hex.png")),
|
("bracket-hex", include_bytes!("../thumbnails/bracket-hex.png")),
|
||||||
("flow-past", include_bytes!("../thumbnails/flow-past.png")),
|
("flow-past", include_bytes!("../thumbnails/flow-past.png")),
|
||||||
|
("heated-flow", include_bytes!("../thumbnails/heated-flow.png")),
|
||||||
("cantilever", include_bytes!("../thumbnails/cantilever.png")),
|
("cantilever", include_bytes!("../thumbnails/cantilever.png")),
|
||||||
("impact", include_bytes!("../thumbnails/impact.png")),
|
("impact", include_bytes!("../thumbnails/impact.png")),
|
||||||
("parts", include_bytes!("../thumbnails/parts.png")),
|
("parts", include_bytes!("../thumbnails/parts.png")),
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 5.7 KiB |
+20
-1
@@ -6,7 +6,7 @@ press **Solve** (`solve.run`). Each opens fully defined — geometry, mesh, mate
|
|||||||
conditions, loads and steps — in mm, t, s, N and MPa, and opens and solves within half a minute. The geometry is our
|
conditions, loads and steps — in mm, t, s, N and MPa, and opens and solves within half a minute. The geometry is our
|
||||||
own: SolidCraft scripts run when the sample opens (a recipe from [`tests/corpus`](../tests/corpus)
|
own: SolidCraft scripts run when the sample opens (a recipe from [`tests/corpus`](../tests/corpus)
|
||||||
or one written in `crates/engine/src/sample.rs`). Each sample has a test in
|
or one written in `crates/engine/src/sample.rs`). Each sample has a test in
|
||||||
`crates/engine/src/sample.rs` that opens it, checks it clean, solves it and holds its result to
|
`crates/engine/src/sample.rs` or its modules that opens it, checks it clean, solves it and holds its result to
|
||||||
the check given below.
|
the check given below.
|
||||||
|
|
||||||
## Drop test (`drop-test`)
|
## Drop test (`drop-test`)
|
||||||
@@ -200,3 +200,22 @@ SolidCraft geometry with persistent face names, swept all-hex (hex8 of 4 mm, inc
|
|||||||
clamped at `F2:start` and loaded with 1000 N down through a kinematic coupling to `F2:top`.
|
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
|
**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).
|
(beam theory with shear: 0.184 mm).
|
||||||
|
|
||||||
|
## Heated flow past a bracket (`heated-flow`)
|
||||||
|
|
||||||
|
Air at 20 °C enters at 1 mm/s and passes the original upright bracket, whose faces are
|
||||||
|
held at 80 °C. The 130 × 80 × 70 mm domain has 5 mm cells (5,824 cells), a pressure outlet,
|
||||||
|
slip sides and top, and a no-slip floor. There are 180 steps and six saved output intervals,
|
||||||
|
plus the initial frame. Open `file.sample {"name":"heated-flow"}` and solve on CPU or GPU.
|
||||||
|
The slow prescribed flow keeps this coarse lattice away from its small-relaxation-time limit.
|
||||||
|
|
||||||
|
**Look at:** Results → Temperature, or `results.slice {"field":"temperature"}`, for the
|
||||||
|
warming air. `results.surface {"field":"wall_heat_flux"}` shows heat received by each
|
||||||
|
bracket face per area: the hot wall heating the air is negative. In this model's mm–tonne–second
|
||||||
|
units the wall flux is mW/mm². The initial frame has no recorded wall heat and stays uncoloured.
|
||||||
|
|
||||||
|
**Check:** the fluid warms above its inlet temperature while staying below the held wall
|
||||||
|
temperature (with a small numerical tolerance); recorded hot walls give heat to the fluid.
|
||||||
|
This coarse, short transient is a quick display demonstration, not a converged convection
|
||||||
|
benchmark. It has neither buoyancy nor solid heat conduction. The sample is isolated in
|
||||||
|
`crates/engine/src/sample/heated_flow.rs` and its gallery registration.
|
||||||
Reference in new issue
Block a user