ui: jobs with progress, refusals listed, datum preview, form filters and defaults

- A solve or a mesher runs as a job with one progress mechanism: the overlay
  shows the step, stage, count and elapsed time with a bar when bounded, OK
  waits while it runs, and the status sums it up (elements, smallest angle,
  time) (#322, #357).
- Over the control channel such a command is a job too: answered when it
  ends however long it runs, or at once with wait: false, then job.status,
  job.wait, job.cancel; ui.inspect gives job; MCP waits for it (#367).
- A command that cannot run yet lists what it misses in the context panel,
  each with a button to the command that provides it (#323).
- A cancelled or refused solve stays in its workspace (#324, test).
- A mesher form starts from the parameters of the mesh it would replace (#343).
- Seed Edges and edge/vertex/face datum points switch the selection filter
  while open and restore it (#341).
- Datum forms draw the datum live, the Partition form shows its datum, and
  new datums and partitions get the next free name (#347).

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-15 09:14:34 -04:00
1 parent 3eaca26246
commit 4ee855bb27
11 files changed
+447 -28

No files matched your search

+20
View File
@@ -820,6 +820,23 @@ pub fn target(id: &str, params: &Value) -> Option<Target> {
}
}
/// The filter command `id`'s form picks with, when its target is one kind of entity: edges for
/// edge seeds, and the vertex, edge or face a datum point is defined on.
pub fn form_filter(id: &str, params: &Value) -> Option<Filter> {
if target(id, params) == Some(Target::Edges) {
return Some(Filter::Edges);
}
match id {
"geometry.datum.point" | "geometry.datum.point.edit" => match params.pointer("/by/type").and_then(Value::as_str) {
Some("vertex") => Some(Filter::Vertices),
Some("edge") => Some(Filter::Edges),
Some("face_centre") => Some(Filter::Faces),
_ => None,
},
_ => None,
}
}
/// `params` with the selection's members put in (replacing any members it had).
pub fn with_members(params: &Value, selection: &Selection) -> Value {
let mut out = params.as_object().cloned().unwrap_or_default();
@@ -1160,6 +1177,9 @@ mod tests {
assert_eq!(target("model.section.edit", &json!({})), Some(Target::Set));
assert_eq!(target("model.coupling.create", &json!({})), Some(Target::Set));
assert_eq!(target("mesh.node.add", &json!({})), None);
assert_eq!(form_filter("mesh.seed.edge", &json!({})), Some(Filter::Edges));
assert_eq!(form_filter("geometry.datum.point", &json!({"by": {"type": "vertex"}})), Some(Filter::Vertices));
assert_eq!(form_filter("model.load.create", &json!({})), None);
assert_eq!(with_set(&json!({"name": "fix"}), Target::TargetSet, "fix-set"), json!({"name": "fix", "target": {"set": "fix-set"}}));
assert_eq!(with_set(&json!({"name": "web"}), Target::Set, "web-set"), json!({"name": "web", "set": "web-set"}));
let mut m = Model::default();