forked from 1b-agent/fieldcraft-upstream
Engine: each command lists what it needs, and execute checks it first
CommandSpec gains `requires: &[Requirement]` (crates/engine/src/requirement.rs): an open
design, geometry, nodes, a mesh, something to analyse, materials and sections for the
elements, sets, the definition an edit or delete acts on, a step, a result. Session::execute
checks them before running, so a command missing one fails at once with no partial work,
naming each missing piece and the toolbar entry that provides it ("Needs a material —
Properties › Material"). Requirements are real prerequisites (the model's own reference
checks and model.check's rules), never a workflow order. engine.commands / list_commands
list them.
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
06b98f01c7
commit
e0d5adba23
20 files changed
+448
-25
No files matched your search
@@ -14,6 +14,7 @@ use serde::de::DeserializeOwned;
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use super::{name, opt, req};
|
||||
use crate::requirement::Requirement;
|
||||
use crate::{CommandKind, CommandSpec, EngineError, Result, Session};
|
||||
|
||||
/// The parameters other than `name`, read as a definition's JSON form.
|
||||
@@ -26,13 +27,14 @@ fn fields<T: DeserializeOwned>(p: &Value, what: &str) -> Result<T> {
|
||||
/// Create, edit and delete commands for one kind of definition.
|
||||
macro_rules! definition_commands {
|
||||
($what:literal, $ty:ty, $id:literal, $label:literal, $doc:literal, $form:expr, $delete_form:expr, $add:ident, $replace:ident, $remove:ident,
|
||||
$CREATE:ident, $EDIT:ident, $DELETE:ident) => {
|
||||
$CREATE:ident, $EDIT:ident, $DELETE:ident, requires: $requires:expr, exists: $exists:ident) => {
|
||||
pub const $CREATE: CommandSpec = CommandSpec {
|
||||
id: concat!("model.", $id, ".create"),
|
||||
label: concat!("New ", $label),
|
||||
params: concat!($doc, " — fails if the name is taken"),
|
||||
form: Some($form),
|
||||
kind: CommandKind::Model,
|
||||
requires: $requires,
|
||||
run: |s: &mut Session, p: &Value| {
|
||||
let n = name(p)?;
|
||||
s.model.$add(n, fields::<$ty>(p, $what)?)?;
|
||||
@@ -45,6 +47,7 @@ macro_rules! definition_commands {
|
||||
params: concat!($doc, " — replaces the existing definition of that name"),
|
||||
form: Some($form),
|
||||
kind: CommandKind::Model,
|
||||
requires: &[Requirement::$exists],
|
||||
run: |s: &mut Session, p: &Value| {
|
||||
let n = name(p)?;
|
||||
s.model.$replace(n, fields::<$ty>(p, $what)?)?;
|
||||
@@ -57,6 +60,7 @@ macro_rules! definition_commands {
|
||||
params: r#"{"name": "…"} — refused while another definition refers to it"#,
|
||||
form: Some($delete_form),
|
||||
kind: CommandKind::Model,
|
||||
requires: &[Requirement::$exists],
|
||||
run: |s: &mut Session, p: &Value| {
|
||||
s.model.$remove(name(p)?)?;
|
||||
Ok(Value::Null)
|
||||
@@ -78,7 +82,9 @@ definition_commands!(
|
||||
remove_set,
|
||||
SET_CREATE,
|
||||
SET_EDIT,
|
||||
SET_DELETE
|
||||
SET_DELETE,
|
||||
requires: &[],
|
||||
exists: Set
|
||||
);
|
||||
|
||||
definition_commands!(
|
||||
@@ -94,7 +100,9 @@ definition_commands!(
|
||||
remove_material,
|
||||
MATERIAL_CREATE,
|
||||
MATERIAL_EDIT,
|
||||
MATERIAL_DELETE
|
||||
MATERIAL_DELETE,
|
||||
requires: &[],
|
||||
exists: Material
|
||||
);
|
||||
|
||||
/// A copy of material `name` with `change` applied, put back through the model's validation.
|
||||
@@ -112,6 +120,7 @@ pub const MATERIAL_PLASTICITY: CommandSpec = CommandSpec {
|
||||
params: r#"{"name": "Steel", "plastic": {"type": "bilinear", "yield_stress": 250, "hardening_modulus": 1000, "hardening": "isotropic|kinematic"} | {"type": "table", "points": [{"strain": 0, "stress": 250}, {"strain": 0.1, "stress": 300}]} | {"type": "johnson_cook", "a": 792, "b": 510, "n": 0.26, "c": 0.014, "m": 1.03, "reference_strain_rate": 1, "room_temperature": 294, "melt_temperature": 1793, "specific_heat": 4.77e8, "taylor_quinney": 0.9} | null} — von Mises plasticity of an isotropic material (null removes it); explicit dynamics uses it, the linear solver the elastic constants alone"#,
|
||||
form: Some(super::forms::MATERIAL_PLASTICITY),
|
||||
kind: CommandKind::Model,
|
||||
requires: &[Requirement::Material],
|
||||
run: |s: &mut Session, p: &Value| {
|
||||
change_material(s, p, |m, p| {
|
||||
m.plastic = req(p, "plastic")?;
|
||||
@@ -126,6 +135,7 @@ pub const MATERIAL_DAMAGE: CommandSpec = CommandSpec {
|
||||
params: r#"{"name": "Steel", "damage": {"type": "johnson_cook", "d1": 0.05, "d2": 3.44, "d3": -2.12, "d4": 0.002, "d5": 0.61} | null, "erosion": {"plastic_strain": 1.0} | null} — damage to failure and element erosion of a plastic material (each key optional; null removes it); a failed element is eroded in explicit dynamics"#,
|
||||
form: Some(super::forms::MATERIAL_DAMAGE),
|
||||
kind: CommandKind::Model,
|
||||
requires: &[Requirement::Material],
|
||||
run: |s: &mut Session, p: &Value| {
|
||||
change_material(s, p, |m, p| {
|
||||
// Absent leaves a field as it is; null removes it.
|
||||
@@ -157,7 +167,9 @@ definition_commands!(
|
||||
remove_section,
|
||||
SECTION_CREATE,
|
||||
SECTION_EDIT,
|
||||
SECTION_DELETE
|
||||
SECTION_DELETE,
|
||||
requires: &[Requirement::Material, Requirement::Set],
|
||||
exists: Section
|
||||
);
|
||||
|
||||
definition_commands!(
|
||||
@@ -173,7 +185,9 @@ definition_commands!(
|
||||
remove_reference_point,
|
||||
REFERENCE_POINT_CREATE,
|
||||
REFERENCE_POINT_EDIT,
|
||||
REFERENCE_POINT_DELETE
|
||||
REFERENCE_POINT_DELETE,
|
||||
requires: &[],
|
||||
exists: ReferencePoint
|
||||
);
|
||||
|
||||
definition_commands!(
|
||||
@@ -189,7 +203,9 @@ definition_commands!(
|
||||
remove_coupling,
|
||||
COUPLING_CREATE,
|
||||
COUPLING_EDIT,
|
||||
COUPLING_DELETE
|
||||
COUPLING_DELETE,
|
||||
requires: &[Requirement::ReferencePoint, Requirement::Set],
|
||||
exists: Coupling
|
||||
);
|
||||
|
||||
definition_commands!(
|
||||
@@ -205,7 +221,9 @@ definition_commands!(
|
||||
remove_mass,
|
||||
MASS_CREATE,
|
||||
MASS_EDIT,
|
||||
MASS_DELETE
|
||||
MASS_DELETE,
|
||||
requires: &[Requirement::Target],
|
||||
exists: Mass
|
||||
);
|
||||
|
||||
definition_commands!(
|
||||
@@ -221,7 +239,9 @@ definition_commands!(
|
||||
remove_spring,
|
||||
SPRING_CREATE,
|
||||
SPRING_EDIT,
|
||||
SPRING_DELETE
|
||||
SPRING_DELETE,
|
||||
requires: &[Requirement::Target],
|
||||
exists: Spring
|
||||
);
|
||||
|
||||
definition_commands!(
|
||||
@@ -237,7 +257,9 @@ definition_commands!(
|
||||
remove_boundary_condition,
|
||||
BOUNDARY_CONDITION_CREATE,
|
||||
BOUNDARY_CONDITION_EDIT,
|
||||
BOUNDARY_CONDITION_DELETE
|
||||
BOUNDARY_CONDITION_DELETE,
|
||||
requires: &[Requirement::Target],
|
||||
exists: BoundaryCondition
|
||||
);
|
||||
|
||||
definition_commands!(
|
||||
@@ -253,7 +275,9 @@ definition_commands!(
|
||||
remove_load,
|
||||
LOAD_CREATE,
|
||||
LOAD_EDIT,
|
||||
LOAD_DELETE
|
||||
LOAD_DELETE,
|
||||
requires: &[],
|
||||
exists: Load
|
||||
);
|
||||
|
||||
definition_commands!(
|
||||
@@ -269,7 +293,9 @@ definition_commands!(
|
||||
remove_fluid_domain,
|
||||
FLUID_DOMAIN_CREATE,
|
||||
FLUID_DOMAIN_EDIT,
|
||||
FLUID_DOMAIN_DELETE
|
||||
FLUID_DOMAIN_DELETE,
|
||||
requires: &[Requirement::Material],
|
||||
exists: FluidDomain
|
||||
);
|
||||
|
||||
pub const FLUID_BOUNDARY: CommandSpec = CommandSpec {
|
||||
@@ -278,6 +304,7 @@ pub const FLUID_BOUNDARY: CommandSpec = CommandSpec {
|
||||
params: r#"{"domain": "Flow", "at": "x_min" (a box face) | {"body": "Body1", "face": "F2:top"} (an obstacle face), "condition": {"type": "velocity", "velocity": [u, v, w], "profile": "uniform|parabolic"} | {"type": "pressure", "pressure": 0} | {"type": "wall"} | {"type": "moving_wall", "velocity": [u, v, w]} | {"type": "slip"} | {"type": "periodic"}} — sets one boundary's condition (obstacle faces take walls and moving walls)"#,
|
||||
form: Some(super::forms::FLUID_BOUNDARY),
|
||||
kind: CommandKind::Model,
|
||||
requires: &[Requirement::FluidDomain],
|
||||
run: fluid_boundary,
|
||||
};
|
||||
|
||||
@@ -295,6 +322,7 @@ pub const STEP_CREATE: CommandSpec = CommandSpec {
|
||||
params: r#"{"name": "Bend", "procedure": {"type": "static"} | {"type": "nonlinear_static", "nonlinear_geometry": true, "initial_increment": 0.1, "min_increment": 1e-5, "max_increment": 1.0 (fractions of the step), "max_increments": 100, "max_iterations": 16 (per increment), "tolerances": {"force": 1e-6, "displacement": 1e-5, "energy": 1e-10}, "line_search": false, "load_path": [[0, 0], [0.5, 1], [1, 0]] (optional: the load factor against the step fraction, piecewise linear from [0, 0] to fraction 1; increments end at its points)} (loads and prescribed displacements are the load factor times their values; it ramps from 0 to 1 without a load path) | {"type": "frequency", "modes": 10, "min_hz": 0, "max_hz": 500} | {"type": "fluid", "domain": "Flow", "end_time": 1.0 | "steps": 1000, "time_step": (default: chosen for Mach 0.087 and τ ≤ 1), "frames": 10, "magic": 0.1875 (the two-relaxation-time Λ)} | {"type": "explicit_dynamic", "end_time": 0.001, "dt_scale": 0.9, "max_increments": 10000000, "output_interval", "history_interval", "history_nodes": "set", "initial_velocities": [{"set", "velocity": [vx, vy, vz]}], "rigid_walls": [{"set", "point", "normal", "friction": {"static": 0.3, "kinetic": 0.2, "decay": 10} (Coulomb; default frictionless)}], "hourglass": {"type": "viscous|stiffness", "coefficient": 0.1}, "bulk_viscosity": {"linear": 0.06, "quadratic": 1.5}, "mass_scaling": {"target_increment": 1e-6, "set": "…" (optional, default every element)} (opt-in: elements below the target increment get the density that reaches it; the added mass is reported)}, "output": ["displacement", "reaction_force", "stress", "strain"] (default by procedure), "boundary_conditions": [names], "loads": [names] (active in this step; nothing carries over between steps), "before": "step" (default: last)}"#,
|
||||
form: Some(super::forms::STEP_CREATE),
|
||||
kind: CommandKind::Model,
|
||||
requires: &[],
|
||||
run: step_create,
|
||||
};
|
||||
|
||||
@@ -304,6 +332,7 @@ pub const STEP_EDIT: CommandSpec = CommandSpec {
|
||||
params: r#"{"name": "Bend", "procedure", "output", "boundary_conditions", "loads"} — as model.step.create; what is left out keeps its value"#,
|
||||
form: Some(super::forms::STEP_EDIT),
|
||||
kind: CommandKind::Model,
|
||||
requires: &[Requirement::Step],
|
||||
run: step_edit,
|
||||
};
|
||||
|
||||
@@ -313,6 +342,7 @@ pub const STEP_DELETE: CommandSpec = CommandSpec {
|
||||
params: r#"{"name": "Bend"}"#,
|
||||
form: Some(super::forms::STEP_DELETE),
|
||||
kind: CommandKind::Model,
|
||||
requires: &[Requirement::Step],
|
||||
run: |s, p| s.model.remove_step(name(p)?).map(|_| Value::Null).map_err(Into::into),
|
||||
};
|
||||
|
||||
@@ -322,6 +352,7 @@ pub const STEP_ACTIVATE: CommandSpec = CommandSpec {
|
||||
params: r#"{"step": "Bend", "boundary_conditions": [names], "loads": [names], "active": true} — switches the listed items on (or off with false) in that step only"#,
|
||||
form: Some(super::forms::STEP_ACTIVATE),
|
||||
kind: CommandKind::Model,
|
||||
requires: &[Requirement::Step],
|
||||
run: step_activate,
|
||||
};
|
||||
|
||||
@@ -331,6 +362,7 @@ pub const ACTIVITY: CommandSpec = CommandSpec {
|
||||
params: "{}",
|
||||
form: Some(&[]),
|
||||
kind: CommandKind::Model,
|
||||
requires: &[],
|
||||
run: |s, _| Ok(activity(&s.model)),
|
||||
};
|
||||
|
||||
|
||||
Reference in new issue
Block a user