forked from 1b-agent/fieldcraft-upstream
model: rename a definition and every reference to it (model.rename, #290)
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
587cbc3b01
commit
a0ae8b2111
7 files changed
+458
-2
No files matched your search
@@ -583,7 +583,8 @@ pub static CATALOGUE: &[Capability] = &[
|
||||
soon("results.read", "Read Results", Results, "Files", "M9", "Read results in public formats"),
|
||||
soon("results.convergence", "Convergence", Results, "Files", "M9", "Mesh convergence studies"),
|
||||
// ---- Tools ----
|
||||
ready("model.info", "Model Info", Tools, "Model", "Node and element counts and the model summary"),
|
||||
// Renaming starts from a definition's item menu (Rename…); the search finds it here too.
|
||||
ready_more("model.info", "Model Info", Tools, "Model", "Node and element counts and the model summary", &["model.rename"]),
|
||||
ready("file.open", "Open Model", Tools, "File", "Open a .fieldcraft model"),
|
||||
ready("file.sample", "Samples", Tools, "File", "Open a sample model, ready to solve"),
|
||||
ready("file.save", "Save", Tools, "File", "Save the model to its .fieldcraft file"),
|
||||
|
||||
@@ -13,6 +13,9 @@ use fieldcraft_model::{
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use fieldcraft_io::{Item, ItemKind};
|
||||
use fieldcraft_model::rename::Kind as RenameKind;
|
||||
|
||||
use super::{name, opt, req};
|
||||
use crate::requirement::Requirement;
|
||||
use crate::{CommandKind, CommandSpec, EngineError, Result, Session};
|
||||
@@ -356,6 +359,60 @@ pub const STEP_ACTIVATE: CommandSpec = CommandSpec {
|
||||
run: step_activate,
|
||||
};
|
||||
|
||||
pub const RENAME: CommandSpec = CommandSpec {
|
||||
id: "model.rename",
|
||||
label: "Rename",
|
||||
params: r#"{"kind": "set" | "material" | "section" | "step" | "boundary_condition" | "load" | "reference_point" | "coupling" | "contact" | "tie" | "point_mass" | "spring" | "fluid_domain" | "datum", "from": "end", "to": "Tip"} — renames the definition and every reference to it (sets in sections, couplings, contacts, ties, discrete elements, boundary conditions, loads and explicit steps; materials in sections and fluid domains; reference points; boundary conditions and loads in the steps they are active in; steps in imperfections; fluid domains in fluid steps; datum planes in partitions). A name already taken is refused; the viewport's hidden items follow the new name"#,
|
||||
form: Some(super::forms::RENAME),
|
||||
kind: CommandKind::Model,
|
||||
requires: &[],
|
||||
run: rename,
|
||||
};
|
||||
|
||||
/// `model.rename`'s kinds, as the command takes them.
|
||||
pub const RENAME_KINDS: &[(&str, RenameKind)] = &[
|
||||
("set", RenameKind::Set),
|
||||
("material", RenameKind::Material),
|
||||
("section", RenameKind::Section),
|
||||
("step", RenameKind::Step),
|
||||
("boundary_condition", RenameKind::BoundaryCondition),
|
||||
("load", RenameKind::Load),
|
||||
("reference_point", RenameKind::ReferencePoint),
|
||||
("coupling", RenameKind::Coupling),
|
||||
("contact", RenameKind::Contact),
|
||||
("tie", RenameKind::Tie),
|
||||
("point_mass", RenameKind::PointMass),
|
||||
("spring", RenameKind::Spring),
|
||||
("fluid_domain", RenameKind::FluidDomain),
|
||||
("datum", RenameKind::Datum),
|
||||
];
|
||||
|
||||
fn rename(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let kind: &str = req(p, "kind")?;
|
||||
let from: &str = req(p, "from")?;
|
||||
let to: &str = req(p, "to")?;
|
||||
let Some((_, k)) = RENAME_KINDS.iter().find(|(id, _)| *id == kind) else {
|
||||
let kinds: Vec<&str> = RENAME_KINDS.iter().map(|(id, _)| *id).collect();
|
||||
return Err(EngineError::Params(format!("`kind` is one of {}", kinds.join(", "))));
|
||||
};
|
||||
s.model.rename(*k, from, to)?;
|
||||
// What the viewport hides follows the new name.
|
||||
let item = match k {
|
||||
RenameKind::Datum => Some(ItemKind::Datum),
|
||||
RenameKind::BoundaryCondition => Some(ItemKind::BoundaryCondition),
|
||||
RenameKind::Load => Some(ItemKind::Load),
|
||||
RenameKind::ReferencePoint => Some(ItemKind::ReferencePoint),
|
||||
RenameKind::Coupling => Some(ItemKind::Coupling),
|
||||
_ => None,
|
||||
};
|
||||
if let Some(kind) = item
|
||||
&& s.view.hidden.remove(&Item { kind, name: from.to_string() })
|
||||
{
|
||||
s.view.hidden.insert(Item { kind, name: to.to_string() });
|
||||
}
|
||||
Ok(json!({"kind": kind, "from": from, "name": to}))
|
||||
}
|
||||
|
||||
pub const ACTIVITY: CommandSpec = CommandSpec {
|
||||
id: "model.activity",
|
||||
label: "Step Table",
|
||||
@@ -473,6 +530,27 @@ pub(crate) mod tests {
|
||||
s.run_script(&script).unwrap();
|
||||
}
|
||||
|
||||
/// A rename rewrites the references, checks clean, is undone in one step, follows the hidden
|
||||
/// state and refuses a name in use (#290).
|
||||
#[test]
|
||||
fn rename_keeps_the_model_whole_and_undoes() {
|
||||
let mut s = Session::default();
|
||||
defined_bar(&mut s);
|
||||
s.execute("view.hide", &json!({"items": [{"kind": "load", "name": "push"}]})).unwrap();
|
||||
s.execute("model.rename", &json!({"kind": "set", "from": "end", "to": "Tip"})).unwrap();
|
||||
s.execute("model.rename", &json!({"kind": "load", "from": "push", "to": "Push"})).unwrap();
|
||||
s.execute("model.rename", &json!({"kind": "step", "from": "Bend", "to": "Load"})).unwrap();
|
||||
assert!(s.model.sets().get("end").is_none() && !s.model.set_users("Tip").is_empty());
|
||||
assert!(s.model.step("Load").is_some_and(|st| st.loads.contains("Push")));
|
||||
assert!(s.view.is_hidden(fieldcraft_io::ItemKind::Load, "Push") && !s.view.is_hidden(fieldcraft_io::ItemKind::Load, "push"));
|
||||
assert_eq!(s.execute("model.check", &json!({})).unwrap()["ready"], true);
|
||||
let e = s.execute("model.rename", &json!({"kind": "set", "from": "Tip", "to": "root"})).unwrap_err().to_string();
|
||||
assert!(e.contains("already exists"), "{e}");
|
||||
assert!(s.execute("model.rename", &json!({"kind": "partition", "from": "a", "to": "b"})).is_err());
|
||||
s.execute("edit.undo", &json!({})).unwrap();
|
||||
assert!(s.model.step("Bend").is_some() && s.model.step("Load").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_script_defines_a_model_that_checks_clean() {
|
||||
let mut s = Session::default();
|
||||
|
||||
@@ -834,6 +834,33 @@ pub const STEP_ACTIVATE: &[Field] = &[
|
||||
flag("active", "Active").help("On: they act in the step. Off: they are switched off from it on.").def("true"),
|
||||
];
|
||||
|
||||
pub const RENAME: &[Field] = &[
|
||||
choice(
|
||||
"kind",
|
||||
"Kind",
|
||||
&[
|
||||
("set", "Set"),
|
||||
("material", "Material"),
|
||||
("section", "Section"),
|
||||
("step", "Step"),
|
||||
("boundary_condition", "Boundary condition"),
|
||||
("load", "Load"),
|
||||
("reference_point", "Reference point"),
|
||||
("coupling", "Coupling"),
|
||||
("contact", "Contact"),
|
||||
("tie", "Tie"),
|
||||
("point_mass", "Point mass"),
|
||||
("spring", "Spring"),
|
||||
("fluid_domain", "Fluid domain"),
|
||||
("datum", "Datum"),
|
||||
],
|
||||
)
|
||||
.help("What kind of definition to rename.")
|
||||
.req(),
|
||||
text("from", "Name").help("Its current name.").req(),
|
||||
text("to", "New name").help("Every definition that refers to it is changed to the new name.").req(),
|
||||
];
|
||||
|
||||
pub const UNITS: &[Field] = &[choice(
|
||||
"system",
|
||||
"Unit system",
|
||||
|
||||
@@ -131,6 +131,7 @@ pub static ALL: &[CommandSpec] = &[
|
||||
define::STEP_EDIT,
|
||||
define::STEP_DELETE,
|
||||
define::STEP_ACTIVATE,
|
||||
define::RENAME,
|
||||
define::ACTIVITY,
|
||||
file::OPEN,
|
||||
file::SAVE,
|
||||
|
||||
Reference in new issue
Block a user