forked from 1b-agent/fieldcraft-upstream
mcp: the probe tool takes at, points whose nearest nodes are probed (#105)
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
5aace9b5b3
commit
92b6225a72
3 files changed
+20
-3
No files matched your search
@@ -224,6 +224,22 @@ fn screenshot_takes_a_camera_orientation() {
|
|||||||
assert_eq!(again, turned);
|
assert_eq!(again, turned);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `probe {at}` reads a structural result at the nodes nearest to points (#105); hostile points
|
||||||
|
/// are refused without a panic.
|
||||||
|
#[test]
|
||||||
|
fn probe_takes_points_at() {
|
||||||
|
let mut s = server();
|
||||||
|
ok(&mut s, "execute", json!({"command": "file.sample", "params": {"name": "cantilever"}}));
|
||||||
|
ok(&mut s, "solve", json!({"analysis": "static", "step": "Load"}));
|
||||||
|
let r = ok(&mut s, "probe", json!({"step": "Load", "at": [[0, 0, 0]]}));
|
||||||
|
let node = &r["nodes"][0];
|
||||||
|
assert!(node["xyz"].is_array() && node["distance"].is_number(), "{r}");
|
||||||
|
for at in [json!([[1e308, "x"]]), json!(vec![json!([0, 0, 0]); 20_000])] {
|
||||||
|
let (text, err) = call(&mut s, "probe", json!({"step": "Load", "at": at}));
|
||||||
|
assert!(err && text.len() < 100_000, "{text}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn a_batch_stops_at_the_first_error() {
|
fn a_batch_stops_at_the_first_error() {
|
||||||
let mut s = server();
|
let mut s = server();
|
||||||
|
|||||||
@@ -251,7 +251,7 @@ pub fn tool_definitions() -> Vec<Value> {
|
|||||||
tool(
|
tool(
|
||||||
"probe",
|
"probe",
|
||||||
"Probe results",
|
"Probe results",
|
||||||
"Stored results at nodes (solve.probe): displacement, reaction, stress and von Mises (static), the shape of \
|
"Stored results at nodes, or at the nodes nearest to points `at` (solve.probe): displacement, reaction, stress and von Mises (static), the shape of \
|
||||||
mode `mode` (frequency), displacement and velocity in field frame `frame` (explicit) and, with `elements`, \
|
mode `mode` (frequency), displacement and velocity in field frame `frame` (explicit) and, with `elements`, \
|
||||||
explicit element values; for a fluid step, the flow at `points` and the force histories of `forces`.",
|
explicit element values; for a fluid step, the flow at `points` and the force histories of `forces`.",
|
||||||
obj(
|
obj(
|
||||||
@@ -259,6 +259,7 @@ pub fn tool_definitions() -> Vec<Value> {
|
|||||||
"step": string("Solved step"),
|
"step": string("Solved step"),
|
||||||
"nodes": {"type": "array", "items": {"type": "integer"}, "description": "Node labels"},
|
"nodes": {"type": "array", "items": {"type": "integer"}, "description": "Node labels"},
|
||||||
"elements": {"type": "array", "items": {"type": "integer"}, "description": "Element labels (explicit)"},
|
"elements": {"type": "array", "items": {"type": "integer"}, "description": "Element labels (explicit)"},
|
||||||
|
"at": {"type": "array", "items": {"type": "array"}, "description": "Points [x, y, z]; the nearest nodes are probed (structural steps)"},
|
||||||
"points": {"type": "array", "items": {"type": "array"}, "description": "Points [x, y, z] (fluid)"},
|
"points": {"type": "array", "items": {"type": "array"}, "description": "Points [x, y, z] (fluid)"},
|
||||||
"forces": {"type": "array", "items": {"type": "string"}, "description": "Walls or faces whose force histories to return (fluid)"},
|
"forces": {"type": "array", "items": {"type": "string"}, "description": "Walls or faces whose force histories to return (fluid)"},
|
||||||
"mode": integer("Mode, from 1 (frequency)"),
|
"mode": integer("Mode, from 1 (frequency)"),
|
||||||
@@ -497,7 +498,7 @@ fn dispatch(b: &mut dyn Backend, name: &str, a: &Map<String, Value>) -> Result<V
|
|||||||
b.execute(command, params)
|
b.execute(command, params)
|
||||||
}
|
}
|
||||||
"show_results" => show_results(b, a),
|
"show_results" => show_results(b, a),
|
||||||
"probe" => b.execute("solve.probe", pick(a, &["step", "nodes", "elements", "points", "forces", "mode", "frame"])),
|
"probe" => b.execute("solve.probe", pick(a, &["step", "nodes", "at", "elements", "points", "forces", "mode", "frame"])),
|
||||||
"reactions" => b.execute("results.reactions", pick(a, &["step", "sets", "about"])),
|
"reactions" => b.execute("results.reactions", pick(a, &["step", "sets", "about"])),
|
||||||
"select" => {
|
"select" => {
|
||||||
let p = pick(a, &["filter", "box_mode", "mode", "clear", "geometry", "nodes", "elements", "faces", "at", "box"]);
|
let p = pick(a, &["filter", "box_mode", "mode", "clear", "geometry", "nodes", "elements", "faces", "at", "box"]);
|
||||||
|
|||||||
+1
-1
@@ -102,7 +102,7 @@ software on the computer.
|
|||||||
| `mesh` | `method`, `params?` | Runs the mesher (`tet`: `mesh.tet.generate`; `hex`, `hex_sweep`, `hex_mapped`, `hex_blocks`: the structured hex meshers; `cells`: `mesh.cells`, a partitioned part cell by cell; `mirror`: `mesh.mirror`, a meshed half reflected across a plane; `block`: `mesh.block`; `planar`: `mesh.planar`) and replies with its result, `model.info` and `mesh.quality`. |
|
| `mesh` | `method`, `params?` | Runs the mesher (`tet`: `mesh.tet.generate`; `hex`, `hex_sweep`, `hex_mapped`, `hex_blocks`: the structured hex meshers; `cells`: `mesh.cells`, a partitioned part cell by cell; `mirror`: `mesh.mirror`, a meshed half reflected across a plane; `block`: `mesh.block`; `planar`: `mesh.planar`) and replies with its result, `model.info` and `mesh.quality`. |
|
||||||
| `solve` | `analysis` (static, frequency, explicit, fluid; heat as it lands), `step?`, `options?` | `solve.<analysis>` with the step and the solver's caps and settings. |
|
| `solve` | `analysis` (static, frequency, explicit, fluid; heat as it lands), `step?`, `options?` | `solve.<analysis>` with the step and the solver's caps and settings. |
|
||||||
| `show_results` | `step?`, `field?`, `bands?`, `deformed?`, `scale?`, `mode?`, `frame?`, `time?` | The `results.*` commands its arguments ask for (`deformed: false` draws the undeformed mesh; `scale` is a factor, 0 for the undeformed shape so a sequence from 0 swings the deformation out, or `"auto"`); the reply has the contour range (smallest and largest value and their nodes: `field: "displacement"` gives the largest displacement), modes or frames. |
|
| `show_results` | `step?`, `field?`, `bands?`, `deformed?`, `scale?`, `mode?`, `frame?`, `time?` | The `results.*` commands its arguments ask for (`deformed: false` draws the undeformed mesh; `scale` is a factor, 0 for the undeformed shape so a sequence from 0 swings the deformation out, or `"auto"`); the reply has the contour range (smallest and largest value and their nodes: `field: "displacement"` gives the largest displacement), modes or frames. |
|
||||||
| `probe` | `step?`, `nodes?`, `elements?`, `points?`, `forces?`, `mode?`, `frame?` | `solve.probe`: stored results at nodes, elements (explicit) or points (fluid). |
|
| `probe` | `step?`, `nodes?`, `at?`, `elements?`, `points?`, `forces?`, `mode?`, `frame?` | `solve.probe`: stored results at nodes, at the nodes nearest to points `at` (structural steps), elements (explicit) or points (fluid). |
|
||||||
| `reactions` | `step?`, `sets?`, `about?` | `results.reactions`: reaction force and moment per node set and in total. |
|
| `reactions` | `step?`, `sets?`, `about?` | `results.reactions`: reaction force and moment per node set and in total. |
|
||||||
| `screenshot` | `view?`, `camera?`, `source?` (window, model), `width?`, `height?`, `path?` | A PNG image content block and what it shows. Headless: a CPU render framing the model, the geometry, the analysis setup's symbols (boundary conditions, loads, reference points, couplings; `sampled_symbols` lists definitions drawn with a sample) and the results display (deformed shape, contour bands). Bridged: the whole app window drawn offscreen (`ui.capture`, the default) or a model render with the app's camera (`ui.render`). `view` turns the camera to a standard view first; `camera` `{yaw, pitch, distance?}` to any orientation, in degrees (yaw 0 looks along +Y, −45 is the iso view's; pitch is the elevation, clamped to ±90) and model units (`ui.camera`; headless, the picture is framed unless `distance` is given). Give one of the two; the orientation is kept for the next picture, so a sequence of `camera` angles orbits smoothly. |
|
| `screenshot` | `view?`, `camera?`, `source?` (window, model), `width?`, `height?`, `path?` | A PNG image content block and what it shows. Headless: a CPU render framing the model, the geometry, the analysis setup's symbols (boundary conditions, loads, reference points, couplings; `sampled_symbols` lists definitions drawn with a sample) and the results display (deformed shape, contour bands). Bridged: the whole app window drawn offscreen (`ui.capture`, the default) or a model render with the app's camera (`ui.render`). `view` turns the camera to a standard view first; `camera` `{yaw, pitch, distance?}` to any orientation, in degrees (yaw 0 looks along +Y, −45 is the iso view's; pitch is the elevation, clamped to ±90) and model units (`ui.camera`; headless, the picture is framed unless `distance` is given). Give one of the two; the orientation is kept for the next picture, so a sequence of `camera` angles orbits smoothly. |
|
||||||
| `select` | `filter?`, `box_mode?`, `mode?`, `clear?`, `geometry?`, `nodes?`, `elements?`, `faces?`, `at?`, `box?` | Bridged only: changes the app's viewport selection (`ui.select`; control-protocol.md, Selection) and returns it. Headless it is an error: selection belongs to the window. |
|
| `select` | `filter?`, `box_mode?`, `mode?`, `clear?`, `geometry?`, `nodes?`, `elements?`, `faces?`, `at?`, `box?` | Bridged only: changes the app's viewport selection (`ui.select`; control-protocol.md, Selection) and returns it. Headless it is an error: selection belongs to the window. |
|
||||||
|
|||||||
Reference in new issue
Block a user