engine: interaction.tie, tie.edit and tie.delete

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
This commit is contained in:
jyvikoandClaude Opus 5.5 authored and Irakli Kourtis committed 2026-10-14 15:28:02 -04:00
1 parent f9981d305e
commit 7e9366f726
6 files changed
+104 -5

No files matched your search

+75 -2
View File
@@ -1,5 +1,5 @@
//! `interaction.contact.*`: contact definitions (pairs of surfaces, general contact) and the
//! search for surfaces that touch.
//! search for surfaces that touch; `interaction.tie*`: ties between non-matching meshes.
//!
//! A contact's surfaces are sets of element faces ([`fieldcraft_model::contact`]). The commands
//! take the friction, penalty scale and initial-penetration choice of the model's form; `edit`
@@ -14,7 +14,7 @@
use std::collections::BTreeMap;
use fieldcraft_fem::contact::{Grid, bounds, closest_point};
use fieldcraft_model::{Contact, ContactSurfaces, ElementFace, Friction, InitialPenetration, Model, Set};
use fieldcraft_model::{Contact, ContactSurfaces, ElementFace, Friction, InitialPenetration, Model, Set, Tie};
use serde_json::{Value, json};
use super::{name, opt};
@@ -102,6 +102,53 @@ pub const DELETE: CommandSpec = CommandSpec {
},
};
/// A tie from the parameters.
fn tie(p: &Value) -> Result<Tie> {
Ok(Tie {
main: text(p, "main")?.to_string(),
secondary: text(p, "secondary")?.to_string(),
position_tolerance: opt::<f64>(p, "position_tolerance")?,
})
}
pub const TIE: CommandSpec = CommandSpec {
id: "interaction.tie",
label: "Tie",
params: r#"{"name": "glue", "main": "plate" (a set of element faces), "secondary": "rib" (another), "position_tolerance": 0.1 (optional: how far a secondary node may lie from the main surface and still be tied; default half the main element's thickness)} — bonds the secondary surface to the main one across non-matching meshes: each secondary node follows the main surface under it (dual mortar constraints in the implicit analyses; a constant stress crosses the tie exactly); fails if the name is taken"#,
form: Some(super::forms::TIE),
kind: CommandKind::Model,
run: |s: &mut Session, p: &Value| {
let n = name(p)?;
s.model.add_tie(n, tie(p)?)?;
Ok(json!({"name": n}))
},
};
pub const TIE_EDIT: CommandSpec = CommandSpec {
id: "interaction.tie.edit",
label: "Edit Tie",
params: r#"{"name": "glue", "main": "plate", "secondary": "rib", "position_tolerance": 0.1} — replaces the tie of that name (see interaction.tie)"#,
form: Some(super::forms::TIE_EDIT),
kind: CommandKind::Model,
run: |s: &mut Session, p: &Value| {
let n = name(p)?;
s.model.replace_tie(n, tie(p)?)?;
Ok(json!({"name": n}))
},
};
pub const TIE_DELETE: CommandSpec = CommandSpec {
id: "interaction.tie.delete",
label: "Delete Tie",
params: r#"{"name": "glue"}"#,
form: Some(super::forms::TIE_DELETE),
kind: CommandKind::Model,
run: |s: &mut Session, p: &Value| {
s.model.remove_tie(name(p)?)?;
Ok(Value::Null)
},
};
pub const FIND: CommandSpec = CommandSpec {
id: "interaction.contact.find",
label: "Find Contacts",
@@ -334,6 +381,32 @@ mod tests {
assert!(s.model.contacts().is_empty());
}
#[test]
fn ties_are_created_edited_and_deleted() {
let mut s = Session::default();
stacked(&mut s);
s.execute("model.set.create", &json!({"name": "a", "faces": [{"element": 5, "face": 2}]})).unwrap();
s.execute("model.set.create", &json!({"name": "b", "faces": [{"element": 9, "face": 1}]})).unwrap();
s.execute("interaction.tie", &json!({"name": "glue", "main": "a", "secondary": "b"})).unwrap();
assert_eq!(s.model.tie("glue"), Some(&Tie::new("a", "b")));
for bad in [
json!({"name": "glue", "main": "a", "secondary": "b"}),
json!({"name": "x", "main": "a", "secondary": "a"}),
json!({"name": "x", "main": "a", "secondary": "nope"}),
json!({"name": "x", "main": "a", "secondary": "b", "position_tolerance": -1}),
json!({"name": "x", "main": "a", "secondary": "b", "position_tolerance": "far"}),
json!({"name": "x", "main": 3, "secondary": "b"}),
] {
assert!(s.execute("interaction.tie", &bad).is_err(), "{bad}");
}
s.execute("interaction.tie.edit", &json!({"name": "glue", "main": "b", "secondary": "a", "position_tolerance": 0.01})).unwrap();
assert_eq!(s.model.tie("glue").and_then(|t| t.position_tolerance), Some(0.01));
assert!(s.execute("interaction.tie.edit", &json!({"name": "nope", "main": "a", "secondary": "b"})).is_err());
assert!(s.execute("model.set.delete", &json!({"name": "a"})).unwrap_err().to_string().contains("tie `glue`"));
s.execute("interaction.tie.delete", &json!({"name": "glue"})).unwrap();
assert!(s.model.ties().is_empty() && s.execute("interaction.tie.delete", &json!({"name": "glue"})).is_err());
}
#[test]
fn find_reports_and_creates_the_touching_parts() {
let mut s = Session::default();
+13
View File
@@ -31,6 +31,7 @@ pub const BOUNDARY_CONDITION_DELETE: &[Field] = existing!("Boundary condition",
pub const LOAD_DELETE: &[Field] = existing!("Load", C::Load);
pub const FLUID_DOMAIN_DELETE: &[Field] = existing!("Fluid domain", C::FluidDomain);
pub const CONTACT_DELETE: &[Field] = existing!("Contact", C::Contact);
pub const TIE_DELETE: &[Field] = existing!("Tie", C::Tie);
pub const STEP_DELETE: &[Field] = existing!("Step", C::Step);
pub const SET_RESOLVE: &[Field] = existing!("Set", C::Set);
@@ -614,6 +615,18 @@ pub const CONTACT_EDIT: &[Field] = &[
PENALTY,
PENETRATION,
];
pub const TIE: &[Field] = &[
NAME,
reference("main", "Main surface", C::Set).req(),
reference("secondary", "Secondary surface", C::Set).req(),
positive("position_tolerance", "Position tolerance", D::Length).hint("half the main element's thickness"),
];
pub const TIE_EDIT: &[Field] = &[
reference("name", "Tie", C::Tie).req(),
reference("main", "Main surface", C::Set).req(),
reference("secondary", "Secondary surface", C::Set).req(),
positive("position_tolerance", "Position tolerance", D::Length).hint("half the main element's thickness"),
];
pub const CONTACT_FIND: &[Field] = &[
positive("tolerance", "Tolerance", D::Length).hint("a tenth of the mean face size"),
flag("create", "Create pairs").def("false"),
+3
View File
@@ -74,6 +74,9 @@ pub static ALL: &[CommandSpec] = &[
contact::EDIT,
contact::DELETE,
contact::FIND,
contact::TIE,
contact::TIE_EDIT,
contact::TIE_DELETE,
define::MASS_CREATE,
define::MASS_EDIT,
define::MASS_DELETE,