From a6f1a6dbeb4c37e84abb49575d7f4662279bbf41 Mon Sep 17 00:00:00 2001 From: jyviko <2501648+jyviko@users.noreply.github.com> Date: Thu, 15 Oct 2026 14:38:15 -0400 Subject: [PATCH] ui: Relax around worst element at the top of Mesh Quality; plain preview text (#398) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo --- crates/engine/src/cmd/relax.rs | 6 ++++-- crates/ui-egui/src/lib.rs | 2 +- crates/ui-egui/src/reply.rs | 22 +++++++++++----------- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/crates/engine/src/cmd/relax.rs b/crates/engine/src/cmd/relax.rs index 7eb2631..64e491b 100644 --- a/crates/engine/src/cmd/relax.rs +++ b/crates/engine/src/cmd/relax.rs @@ -374,7 +374,9 @@ pub(crate) fn weights(model: &Model, p: &Value) -> Result<(BTreeMap, O nodes.extend(el.nodes.iter().copied()); } if listed == 0 { - return Err(EngineError::Params("say which nodes to relax: give one of `center` and `radius`, `around_element`, `nodes`, `elements` or `set`".into())); + return Err(EngineError::Params( + "say which nodes to relax: give one of `center` and `radius`, `around_element`, `nodes`, `elements` or `set`".into(), + )); } if let Some(only) = &only { nodes.retain(|n| only.contains(n)); @@ -450,7 +452,7 @@ fn relax_nodes(s: &mut Session, p: &Value) -> Result { s.model.set_node(*n, *x)?; } let summary = format!( - "moved {} node{} (largest {:.3e}); scaled Jacobian min {:.3} → {:.3}{}", + "moved {} node{} (largest {:.3e}); scaled Jacobian min from {:.3} to {:.3}{}", r.moved, if r.moved == 1 { "" } else { "s" }, r.max_displacement, diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index 476c23a..0f24ea0 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -1179,7 +1179,7 @@ impl ModelApp { let p = self.relax_preview()?; let r = &p.relaxed; Some(format!( - "Preview: {} nodes move (largest {:.3}); scaled Jacobian min {:.3} → {:.3}, edge ratio max {:.2} → {:.2}, {} → {} invalid", + "Preview: {} nodes move (largest {:.3}); scaled Jacobian min {:.3} to {:.3}, edge ratio max {:.2} to {:.2}, invalid {} to {}", r.moved, r.max_displacement, r.before.min_quality, diff --git a/crates/ui-egui/src/reply.rs b/crates/ui-egui/src/reply.rs index 070e622..b5bb851 100644 --- a/crates/ui-egui/src/reply.rs +++ b/crates/ui-egui/src/reply.rs @@ -347,7 +347,8 @@ const METRICS: [(&str, &str, bool); 4] = [ const MAX_SELECT: usize = 10_000; /// `mesh.quality`'s reply: the counts, then each metric's range, histogram and worst element (a -/// click selects and frames it), and buttons selecting the invalid and undetermined elements. +/// click selects and frames it), buttons selecting the invalid and undetermined elements, and +/// Relax around worst element (#398). fn quality(ui: &mut egui::Ui, t: &Tokens, v: &Value, acts: &mut Vec) { let n = |k: &str| v.get(k).and_then(Value::as_u64).unwrap_or(0); let labels = |k: &str| -> Vec { @@ -392,6 +393,15 @@ fn quality(ui: &mut egui::Ui, t: &Tokens, v: &Value, acts: &mut Vec) { acts.push(Act::Elements(l)); } } + if let Some(worst) = v.pointer("/metrics/scaled_jacobian/worst").and_then(Value::as_u64).and_then(|w| u32::try_from(w).ok()) { + if ui + .button("Relax around worst element") + .on_hover_text("Open Relax Nodes with its sphere round the element of smallest scaled Jacobian; preview, then OK") + .clicked() + { + acts.push(Act::Relax(worst)); + } + } for (key, label, low_bad) in METRICS { let Some(m) = v.pointer(&format!("/metrics/{key}")).filter(|m| m.is_object()) else { continue }; let f = |k: &str| m.get(k).and_then(Value::as_f64); @@ -419,16 +429,6 @@ fn quality(ui: &mut egui::Ui, t: &Tokens, v: &Value, acts: &mut Vec) { } } } - if let Some(worst) = v.pointer("/metrics/scaled_jacobian/worst").and_then(Value::as_u64).and_then(|w| u32::try_from(w).ok()) { - ui.add_space(6.0); - if ui - .button("Relax around worst element") - .on_hover_text("Open Relax Nodes with its sphere round the element of smallest scaled Jacobian; preview, then OK") - .clicked() - { - acts.push(Act::Relax(worst)); - } - } if let Some(skipped) = v.get("skipped").and_then(Value::as_object).filter(|s| !s.is_empty()) { ui.add_space(6.0); let text: Vec = skipped.iter().map(|(k, n)| format!("{k}: {}", n.as_u64().map_or_else(|| n.to_string(), count))).collect();