mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-03 18:45:55 +00:00
UI: one continuity control for every continuity choice
ui_kit::continuity_row is the one widget that chooses an order: a dropdown of the orders a construction builds, labelled with the table's words and with a hover line per order naming its code. Patch, Blend Surface, Boundary and the fillet's Corners row (tangent rolling ball or curvature-continuous patch) all use it, and their dialogs keep a Continuity rather than an index into a copied list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -47,7 +47,7 @@ fn edges_at_each_end_make_a_blend_and_come_back_in_an_edit() {
|
||||
d.inputs[0].items = vec![from];
|
||||
d.inputs[1].items = vec![to];
|
||||
let Kind::Surface(Surf::Blend { continuity, bulge, .. }) = &mut d.kind else { panic!("{:?}", d.kind) };
|
||||
*continuity = [2, 1];
|
||||
*continuity = [Continuity::G2, Continuity::G1];
|
||||
*bulge = [0.5, 0.0];
|
||||
ok(&mut h);
|
||||
let name = h.app.session.doc.features.last().unwrap().name.clone();
|
||||
@@ -69,7 +69,7 @@ fn edges_at_each_end_make_a_blend_and_come_back_in_an_edit() {
|
||||
h.step(&json!({"call":"ui.editFeature","params":{"feature":name}})).unwrap();
|
||||
h.frames(8);
|
||||
let d = h.app.dialog.as_mut().unwrap();
|
||||
assert!(matches!(d.kind, Kind::Surface(Surf::Blend { continuity: [2, 1], stitch: false, .. })), "{:?}", d.kind);
|
||||
assert!(matches!(d.kind, Kind::Surface(Surf::Blend { continuity: [Continuity::G2, Continuity::G1], stitch: false, .. })), "{:?}", d.kind);
|
||||
assert_eq!((d.inputs[0].items.len(), d.inputs[1].items.len()), (1, 1), "{:?}", d.inputs);
|
||||
let Kind::Surface(Surf::Blend { stitch, .. }) = &mut d.kind else { panic!() };
|
||||
*stitch = true;
|
||||
|
||||
@@ -70,7 +70,7 @@ fn edges_and_curves_make_a_network_and_come_back_in_an_edit() {
|
||||
let d = h.app.dialog.as_mut().unwrap();
|
||||
d.inputs[0].items = vec![rim.clone(), Sel::SketchCurve { id: top.clone(), sketch: None }];
|
||||
let Kind::Surface(Surf::Boundary { continuity, join }) = &mut d.kind else { panic!("{:?}", d.kind) };
|
||||
*continuity = 2;
|
||||
*continuity = Continuity::G2;
|
||||
*join = true;
|
||||
ok(&mut h);
|
||||
let name = h.app.session.doc.features.last().unwrap().name.clone();
|
||||
@@ -91,7 +91,7 @@ fn edges_and_curves_make_a_network_and_come_back_in_an_edit() {
|
||||
h.step(&json!({"call":"ui.editFeature","params":{"feature":name}})).unwrap();
|
||||
h.frames(8);
|
||||
let d = h.app.dialog.as_ref().unwrap();
|
||||
assert!(matches!(d.kind, Kind::Surface(Surf::Boundary { continuity: 2, join: true })), "{:?}", d.kind);
|
||||
assert!(matches!(d.kind, Kind::Surface(Surf::Boundary { continuity: Continuity::G2, join: true })), "{:?}", d.kind);
|
||||
assert_eq!(d.inputs[0].items.len(), 2, "{:?}", d.inputs[0].items);
|
||||
ok(&mut h);
|
||||
let (u2, v2, op2) = network(&h);
|
||||
@@ -112,7 +112,7 @@ fn curves_that_meet_are_one_chain() {
|
||||
h.app.run("sketch.line", json!({"points":[[10,0],[10,8]],"ids":["d"]})).unwrap();
|
||||
h.app.run("sketch.finish", json!({})).unwrap();
|
||||
let sketch = h.app.session.doc.find_feature("Net").unwrap().id;
|
||||
let k = Surf::Boundary { continuity: 0, join: false };
|
||||
let k = Surf::Boundary { continuity: Continuity::G0, join: false };
|
||||
let (_, mut inputs) = crate::dialogs_surface::start(&h.app, "surface.boundary").unwrap();
|
||||
inputs[0].items = ["a2", "b", "a1"].iter().map(|id| Sel::SketchCurve { id: (*id).into(), sketch: None }).collect();
|
||||
inputs[1].items = ["c", "d"].iter().map(|id| Sel::SketchCurve { id: (*id).into(), sketch: None }).collect();
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
use super::*;
|
||||
use crate::ui_kit;
|
||||
use crate::ui_kit::{choice_row, expr_row, hint_row};
|
||||
use solidcraft_engine::kernel::Continuity;
|
||||
|
||||
pub(super) fn command(kind: &Kind) -> Option<&'static str> {
|
||||
match kind {
|
||||
@@ -107,10 +108,9 @@ const FILLET_TYPES: [(&str, &str); 5] = [
|
||||
("Spline", "spline"),
|
||||
];
|
||||
|
||||
/// What fills a fillet's corners where three or more rounded edges meet (labels and hover texts,
|
||||
/// in the order of the `corner` words `rolling` and `g2`).
|
||||
const FILLET_CORNERS: [(&str, &str); 2] =
|
||||
[("ui.fillet_corner_ball", "ui.fillet_corner_ball_tip"), ("ui.fillet_corner_patch", "ui.fillet_corner_patch_tip")];
|
||||
/// How a fillet's corners where three or more rounded edges meet continue the rounds: tangent (a
|
||||
/// rolling ball, the `corner` default) or curvature continuous (`corner: g2`, a smooth patch).
|
||||
const FILLET_CORNERS: [Continuity; 2] = [Continuity::G1, Continuity::G2];
|
||||
|
||||
/// A fillet's Corners row (`corner`) and, with a smooth patch, its Setback (`setbacks`, one
|
||||
/// distance for every corner). Shown only while the edges have a corner where three or more
|
||||
@@ -131,19 +131,15 @@ fn fillet_corner_rows(
|
||||
let exact = extra.get("fit").and_then(Value::as_str).is_none_or(|fit| fit == "exact");
|
||||
let mut enter = false;
|
||||
ui.add_enabled_ui(rtype == 0 && exact, |ui| {
|
||||
form_row(ui, "ui.corners", |ui| {
|
||||
let mut choice = usize::from(patch);
|
||||
let response = ui_kit::dropdown(ui, "fi_corner", &mut choice, &FILLET_CORNERS.map(|(label, _)| label));
|
||||
if choice != usize::from(patch) {
|
||||
if choice == 1 {
|
||||
extra.insert("corner".into(), json!("g2"));
|
||||
} else {
|
||||
extra.remove("corner");
|
||||
}
|
||||
let mut corner = if patch { Continuity::G2 } else { Continuity::G1 };
|
||||
ui_kit::continuity_row(ui, "ui.corners", "fi_corner", &mut corner, &FILLET_CORNERS);
|
||||
if (corner == Continuity::G2) != patch {
|
||||
if corner == Continuity::G2 {
|
||||
extra.insert("corner".into(), json!(Continuity::G2.as_str()));
|
||||
} else {
|
||||
extra.remove("corner");
|
||||
}
|
||||
response
|
||||
})
|
||||
.on_hover_text(FILLET_CORNERS.map(|(_, tip)| tr(tip)).join("\n"));
|
||||
}
|
||||
if extra.get("corner").and_then(Value::as_str) == Some("g2") {
|
||||
ui.scope(|ui| {
|
||||
let hint = crate::i18n::format("ui.fillet_setback_hint", &[("factor", "1.5")]);
|
||||
@@ -1169,9 +1165,9 @@ mod tests {
|
||||
h.frames(3);
|
||||
}
|
||||
|
||||
/// Corners shows only once three rounded edges meet at a corner; Smooth patch then writes
|
||||
/// Corners shows only once three rounded edges meet at a corner; Curvature (a smooth patch) writes
|
||||
/// `corner: g2`, its Setback (empty for the command's 1.5 × radius) goes to that corner as
|
||||
/// `setbacks`, and Ball writes neither.
|
||||
/// `setbacks`, and Tangent (a rolling ball) writes neither.
|
||||
#[test]
|
||||
fn corners_row_follows_the_selection_and_maps_to_corner_and_setbacks() {
|
||||
let mut h = fillet_harness();
|
||||
@@ -1186,7 +1182,7 @@ mod tests {
|
||||
assert!(scenario::handle_at("dialog:ui.corners").is_some(), "three rounded edges meet at (10, 0, 5)");
|
||||
assert!(scenario::handle_at("dialog:ui.fillet_setback").is_none(), "Setback goes with a smooth patch");
|
||||
assert!(parameters(&h).get("corner").is_none(), "Ball is the default and writes nothing");
|
||||
choose(&mut h, "ui.corners", "ui.fillet_corner_patch");
|
||||
choose(&mut h, "ui.corners", Continuity::G2.label_id());
|
||||
let p = parameters(&h);
|
||||
assert_eq!(p["corner"], json!("g2"));
|
||||
assert!(p.get("setbacks").is_none(), "an empty Setback leaves the command's default: {p}");
|
||||
@@ -1195,12 +1191,12 @@ mod tests {
|
||||
*setback = "3 mm".into();
|
||||
}
|
||||
assert_eq!(parameters(&h)["setbacks"], json!([{"vertex": [10.0, 0.0, 5.0], "distance": "3 mm"}]));
|
||||
choose(&mut h, "ui.corners", "ui.fillet_corner_ball");
|
||||
choose(&mut h, "ui.corners", Continuity::G1.label_id());
|
||||
let p = parameters(&h);
|
||||
assert!(p.get("corner").is_none() && p.get("setbacks").is_none(), "{p}");
|
||||
assert!(scenario::handle_at("dialog:ui.fillet_setback").is_none());
|
||||
// Another type clears the patch; the row stays, disabled, saying why in its hover text.
|
||||
choose(&mut h, "ui.corners", "ui.fillet_corner_patch");
|
||||
choose(&mut h, "ui.corners", Continuity::G2.label_id());
|
||||
choose(&mut h, "Type", "Chord length");
|
||||
let p = parameters(&h);
|
||||
assert!(p.get("corner").is_none() && p.get("setbacks").is_none(), "{p}");
|
||||
@@ -1212,15 +1208,15 @@ mod tests {
|
||||
#[test]
|
||||
fn fillet_labels_use_words_not_continuity_codes() {
|
||||
let coded = |text: &str| ["G0", "G1", "G2", "G3"].iter().any(|code| text.contains(code));
|
||||
for label in FILLET_TYPES.map(|(label, _)| label).into_iter().chain(FILLET_CORNERS.map(|(label, _)| label)) {
|
||||
for label in FILLET_TYPES.map(|(label, _)| label).into_iter().chain(FILLET_CORNERS.map(Continuity::label_id)) {
|
||||
assert!(!coded(crate::i18n::tr_label(label)), "{label}: {}", crate::i18n::tr_label(label));
|
||||
}
|
||||
for tip in FILLET_CORNERS.map(|(_, tip)| tip) {
|
||||
for tip in FILLET_CORNERS.map(Continuity::tip_id) {
|
||||
assert!(coded(crate::i18n::tr(tip)), "the hover text keeps the code for those who know it: {tip}");
|
||||
}
|
||||
let mut h = fillet_harness();
|
||||
set_edges(&mut h, 3);
|
||||
choose(&mut h, "ui.corners", "ui.fillet_corner_patch");
|
||||
choose(&mut h, "ui.corners", Continuity::G2.label_id());
|
||||
for section in ["Variable Radius", "Advanced"] {
|
||||
h.step(&json!({"click":{"handle":format!("dialog:section:{section}")}})).unwrap();
|
||||
}
|
||||
@@ -1228,7 +1224,7 @@ mod tests {
|
||||
assert!(scenario::handle_at("dialog:ui.fillet_setback").is_some() && scenario::handle_at("dialog:Radius Fit").is_some());
|
||||
let tree = h.output.accesskit_update.as_ref().unwrap();
|
||||
let labels: Vec<String> = tree.nodes.iter().flat_map(|(_, node)| [node.label(), node.value()]).flatten().map(str::to_string).collect();
|
||||
assert!(labels.iter().any(|l| l == "Smooth patch"), "{labels:?}");
|
||||
assert!(labels.iter().any(|l| l == "Curvature"), "{labels:?}");
|
||||
let shown: Vec<&String> = labels.iter().filter(|l| coded(l)).collect();
|
||||
assert!(shown.is_empty(), "labels with a continuity code: {shown:?}");
|
||||
}
|
||||
|
||||
@@ -59,24 +59,24 @@ crate::dialog_descriptor::dialog_descriptors! {
|
||||
Loft {
|
||||
rail_fit: usize,
|
||||
},
|
||||
/// `continuity`: an index into [`BLEND_ORDERS`] for each end, [from, to]; `bulge`: how
|
||||
/// `continuity`: each end's, [from, to]; `bulge`: how
|
||||
/// full each end leaves (0 is the plain bridge); `stitch`: sew the blend onto both bodies.
|
||||
Blend {
|
||||
continuity: [usize; 2],
|
||||
continuity: [Continuity; 2],
|
||||
bulge: [f64; 2],
|
||||
stitch: bool,
|
||||
},
|
||||
/// `continuity`: an index into [`BOUNDARY_ORDERS`] (for a loop of sketch curves, how much of
|
||||
/// `continuity`: for a loop of sketch curves, how much of
|
||||
/// the sketch's plane each side repeats; for picked edges, how the surface meets the faces
|
||||
/// beside them); `join`: sew the surface into the body of its edges.
|
||||
Boundary {
|
||||
continuity: usize,
|
||||
continuity: Continuity,
|
||||
join: bool,
|
||||
},
|
||||
/// `continuity`: an index into [`PATCH_ORDERS`] (how a patch on edges meets the faces
|
||||
/// `continuity`: how a patch on edges meets the faces
|
||||
/// beside its loop).
|
||||
Patch {
|
||||
continuity: usize,
|
||||
continuity: Continuity,
|
||||
},
|
||||
Unstitch,
|
||||
ReverseNormal,
|
||||
@@ -138,51 +138,20 @@ impl Surf {
|
||||
}
|
||||
}
|
||||
|
||||
/// A dialog's continuity choices: the orders a construction builds, from the kernel's table
|
||||
/// ([`Construction::offered`]), so an order a construction learns shows up here by itself. The
|
||||
/// dialog keeps an index into them; labels are plain words, each order's code (G0 …) is in the
|
||||
/// hover text.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct Orders(Construction);
|
||||
/// What each dialog's continuity rows offer: the orders its construction builds, from the
|
||||
/// kernel's table, so an order a construction learns shows up by itself.
|
||||
fn patch_orders() -> Vec<Continuity> {
|
||||
Construction::Patch.offered()
|
||||
}
|
||||
|
||||
fn blend_orders() -> Vec<Continuity> {
|
||||
Construction::Bridge.offered()
|
||||
}
|
||||
|
||||
/// How a patch on edges meets the faces beside its loop.
|
||||
const PATCH_ORDERS: Orders = Orders(Construction::Patch);
|
||||
/// How much of the surface at its edges each end of a blend repeats.
|
||||
const BLEND_ORDERS: Orders = Orders(Construction::Bridge);
|
||||
/// A boundary's one row serves its loop patch (a bridge) and its curve network alike: the orders
|
||||
/// a network builds, the fewer of the two.
|
||||
const BOUNDARY_ORDERS: Orders = Orders(Construction::Network);
|
||||
|
||||
impl Orders {
|
||||
fn all(self) -> Vec<Continuity> {
|
||||
self.0.offered()
|
||||
}
|
||||
|
||||
/// The message ids of the labels, in order.
|
||||
fn labels(self) -> Vec<&'static str> {
|
||||
self.all().into_iter().map(Continuity::label_id).collect()
|
||||
}
|
||||
|
||||
/// The hover text: a line for each order.
|
||||
fn tip(self) -> String {
|
||||
self.all().into_iter().map(|g| crate::i18n::tr(g.tip_id())).collect::<Vec<_>>().join("\n")
|
||||
}
|
||||
|
||||
/// The command word of the choice at `index`.
|
||||
fn word(self, index: usize) -> Option<&'static str> {
|
||||
self.all().get(index).map(|g| g.as_str())
|
||||
}
|
||||
|
||||
/// The index of `g` among the choices (`None`: not offered).
|
||||
fn index(self, g: Continuity) -> Option<usize> {
|
||||
self.all().iter().position(|o| *o == g)
|
||||
}
|
||||
|
||||
/// A Continuity row's segments (the segments draw their text as given).
|
||||
fn segments(self, ui: &mut egui::Ui, selected: &mut usize) -> egui::Response {
|
||||
let labels: Vec<&str> = self.labels().into_iter().map(crate::i18n::tr).collect();
|
||||
ui.scope(|ui| ui_kit::segmented(ui, selected, &labels, Default::default())).response
|
||||
}
|
||||
fn boundary_orders() -> Vec<Continuity> {
|
||||
Construction::Network.offered()
|
||||
}
|
||||
|
||||
/// What a boundary surface becomes: a body of its own, or part of the body of its edges.
|
||||
@@ -243,9 +212,9 @@ pub fn start(app: &SolveApp, id: &str) -> Option<(Kind, Vec<SelInput>)> {
|
||||
"surface.revolve" => Surf::Revolve { angle: "360 deg".into(), symmetric: false, axis: None },
|
||||
"surface.sweep" => Surf::Sweep(crate::dialogs_sweep::SweepOpts::default()),
|
||||
"surface.loft" => Surf::Loft { rail_fit: 0 },
|
||||
"surface.blend" => Surf::Blend { continuity: [1, 1], bulge: [0.0, 0.0], stitch: false },
|
||||
"surface.boundary" => Surf::Boundary { continuity: 1, join: false },
|
||||
"surface.patch" => Surf::Patch { continuity: 0 },
|
||||
"surface.blend" => Surf::Blend { continuity: [Continuity::G1; 2], bulge: [0.0, 0.0], stitch: false },
|
||||
"surface.boundary" => Surf::Boundary { continuity: Continuity::G1, join: false },
|
||||
"surface.patch" => Surf::Patch { continuity: Continuity::G0 },
|
||||
"surface.unstitch" => Surf::Unstitch,
|
||||
"surface.reverse_normal" => Surf::ReverseNormal,
|
||||
"surface.ruled" => {
|
||||
@@ -315,23 +284,23 @@ pub fn rows(ui: &mut egui::Ui, k: &mut Surf) -> bool {
|
||||
ui_kit::options(ui, "untrim_options", &mut [("Restore outer boundary", outer), ("In place (surfaces)", in_place)]);
|
||||
}
|
||||
Surf::Patch { continuity } => {
|
||||
form_row(ui, "Continuity", |ui| PATCH_ORDERS.segments(ui, continuity)).on_hover_text(PATCH_ORDERS.tip());
|
||||
ui_kit::continuity_row(ui, "Continuity", "pa_cont", continuity, &patch_orders());
|
||||
}
|
||||
Surf::Blend { continuity, bulge, stitch } => {
|
||||
let [from, to] = continuity;
|
||||
let [from_bulge, to_bulge] = bulge;
|
||||
form_row(ui, "Start continuity", |ui| ui_kit::dropdown(ui, "bl_cont0", from, &BLEND_ORDERS.labels())).on_hover_text(BLEND_ORDERS.tip());
|
||||
ui_kit::continuity_row(ui, "Start continuity", "bl_cont0", from, &blend_orders());
|
||||
form_row(ui, "Start bulge", |ui| {
|
||||
ui_kit::scalar_field(ui, from_bulge, -0.9..=3.0);
|
||||
});
|
||||
form_row(ui, "End continuity", |ui| ui_kit::dropdown(ui, "bl_cont1", to, &BLEND_ORDERS.labels())).on_hover_text(BLEND_ORDERS.tip());
|
||||
ui_kit::continuity_row(ui, "End continuity", "bl_cont1", to, &blend_orders());
|
||||
form_row(ui, "End bulge", |ui| {
|
||||
ui_kit::scalar_field(ui, to_bulge, -0.9..=3.0);
|
||||
});
|
||||
ui_kit::options(ui, "blend_options", &mut [("Stitch to both bodies", stitch)]);
|
||||
}
|
||||
Surf::Boundary { continuity, join } => {
|
||||
form_row(ui, "Continuity", |ui| BOUNDARY_ORDERS.segments(ui, continuity)).on_hover_text(BOUNDARY_ORDERS.tip());
|
||||
ui_kit::continuity_row(ui, "Continuity", "bo_cont", continuity, &boundary_orders());
|
||||
form_row(ui, "Operation", |ui| {
|
||||
let mut op = usize::from(*join);
|
||||
combo(ui, "bo_op", &BOUNDARY_OPS, &mut op);
|
||||
@@ -584,11 +553,10 @@ pub fn commands(app: &SolveApp, k: &Surf, inputs: &[SelInput], extra: &Map<Strin
|
||||
let (body, edges) = body_and_edges(items(inputs, i))?;
|
||||
Ok(json!({"body": body, "edges": edges}))
|
||||
};
|
||||
let word = |i: usize| BLEND_ORDERS.word(i).unwrap_or("g1");
|
||||
let mut p = json!({
|
||||
"from": side(0)?,
|
||||
"to": side(1)?,
|
||||
"continuity": [word(continuity[0]), word(continuity[1])],
|
||||
"continuity": continuity.map(Continuity::as_str),
|
||||
"bulge": bulge,
|
||||
});
|
||||
if *stitch {
|
||||
@@ -598,7 +566,7 @@ pub fn commands(app: &SolveApp, k: &Surf, inputs: &[SelInput], extra: &Map<Strin
|
||||
}
|
||||
Surf::Boundary { continuity, join } => {
|
||||
need(0, "the curves (sketch curves or edges)")?;
|
||||
let word = BOUNDARY_ORDERS.word(*continuity).unwrap_or("g0");
|
||||
let word = continuity.as_str();
|
||||
let across = items(inputs, 1);
|
||||
let picks = items(inputs, 0);
|
||||
let sketch_ids: Vec<String> = picks.curve_ids();
|
||||
@@ -618,7 +586,7 @@ pub fn commands(app: &SolveApp, k: &Surf, inputs: &[SelInput], extra: &Map<Strin
|
||||
Chain::Sketch { sketch, ids } => json!({"sketch": sketch, "curves": ids}),
|
||||
Chain::Edges { body, points } => {
|
||||
let mut v = json!({"body": body, "edges": points.iter().map(|p| pt(*p)).collect::<Vec<_>>()});
|
||||
if *continuity > 0 {
|
||||
if *continuity != Continuity::G0 {
|
||||
v["continuity"] = json!(word);
|
||||
}
|
||||
v
|
||||
@@ -647,8 +615,8 @@ pub fn commands(app: &SolveApp, k: &Surf, inputs: &[SelInput], extra: &Map<Strin
|
||||
let edges: Vec<Value> = items(inputs, 0).edge_points().into_iter().map(pt).collect();
|
||||
let body = items(inputs, 0).iter().find_map(|x| if let Sel::Edge { body, .. } = x { Some(body.clone()) } else { None });
|
||||
let mut p = json!({"edges": edges, "body": body});
|
||||
if let Some(word) = PATCH_ORDERS.word(*continuity).filter(|_| *continuity > 0) {
|
||||
p["continuity"] = json!(word);
|
||||
if *continuity != Continuity::G0 {
|
||||
p["continuity"] = json!(continuity.as_str());
|
||||
}
|
||||
let points: Vec<Value> = items(inputs, 1)
|
||||
.iter()
|
||||
@@ -885,8 +853,7 @@ pub fn for_feature(app: &SolveApp, kind: &FeatureKind, frame: &Mat) -> Option<(K
|
||||
)
|
||||
}
|
||||
FeatureKind::SurfaceBlend { from, to, continuity, bulge, align, seam, stitch } => {
|
||||
let index = |c: &Continuity| BLEND_ORDERS.index(*c).unwrap_or(1);
|
||||
let k = Surf::Blend { continuity: continuity.each_ref().map(index), bulge: *bulge, stitch: *stitch };
|
||||
let k = Surf::Blend { continuity: *continuity, bulge: *bulge, stitch: *stitch };
|
||||
// What the dialog does not show is kept as it was: the leave directions and the seams.
|
||||
let mut extra = Map::new();
|
||||
for (key, points) in [("align", align), ("seam", seam)] {
|
||||
@@ -902,7 +869,7 @@ pub fn for_feature(app: &SolveApp, kind: &FeatureKind, frame: &Mat) -> Option<(K
|
||||
}
|
||||
FeatureKind::SurfaceBoundary { sketch, curves, orders, crown, samples, .. } => {
|
||||
let first = orders.first().copied().unwrap_or(Continuity::G1);
|
||||
let index = BOUNDARY_ORDERS.index(first);
|
||||
let offered = boundary_orders().contains(&first);
|
||||
// What the dialog does not show is kept as it was: a crown, the stations, and orders
|
||||
// that differ from side to side or that the list does not offer (G3).
|
||||
let mut extra = Map::new();
|
||||
@@ -912,10 +879,10 @@ pub fn for_feature(app: &SolveApp, kind: &FeatureKind, frame: &Mat) -> Option<(K
|
||||
if *samples != solidcraft_engine::doc::BOUNDARY_SAMPLES {
|
||||
extra.insert("samples".into(), json!(samples));
|
||||
}
|
||||
if index.is_none() || orders.iter().any(|o| *o != first) {
|
||||
if !offered || orders.iter().any(|o| *o != first) {
|
||||
extra.insert("continuity".into(), json!(orders.iter().map(|o| o.as_str()).collect::<Vec<_>>()));
|
||||
}
|
||||
let k = Surf::Boundary { continuity: index.unwrap_or(1), join: false };
|
||||
let k = Surf::Boundary { continuity: if offered { first } else { Continuity::G1 }, join: false };
|
||||
let mut ins = inputs(&k);
|
||||
if let Some(inp) = ins.first_mut() {
|
||||
inp.items = curves.iter().map(|id| crate::selection::curve_pick(id, sketch.0, s.active_sketch)).collect();
|
||||
@@ -944,8 +911,7 @@ pub fn for_feature(app: &SolveApp, kind: &FeatureKind, frame: &Mat) -> Option<(K
|
||||
.collect()
|
||||
};
|
||||
let g = u.iter().chain(v).find(|c| c.body.is_some()).map_or(Continuity::G0, |c| c.continuity);
|
||||
let index = BOUNDARY_ORDERS.index(g).unwrap_or(0);
|
||||
(Surf::Boundary { continuity: index, join: *operation == solidcraft_engine::doc::Operation::Join }, vec![sels(u), sels(v)])
|
||||
(Surf::Boundary { continuity: g, join: *operation == solidcraft_engine::doc::Operation::Join }, vec![sels(u), sels(v)])
|
||||
}
|
||||
FeatureKind::SurfaceLoft { sections, rails, rail_fit, .. } => {
|
||||
let [rail, rail2] = crate::dialogs_sweep::rail_sels(rails, s.active_sketch);
|
||||
@@ -976,7 +942,6 @@ pub fn for_feature(app: &SolveApp, kind: &FeatureKind, frame: &Mat) -> Option<(K
|
||||
sels.extend((0..ss.profiles.len()).map(|index| Sel::Profile { sketch: sk.0, index }));
|
||||
}
|
||||
}
|
||||
let index = PATCH_ORDERS.index(continuity.all).unwrap_or(0);
|
||||
// What the dialog does not show is kept as it was: the crown, edges with a
|
||||
// continuity of their own, and points that are not on a face any more.
|
||||
let through: Vec<Sel> = points.iter().filter_map(|p| crate::dialogs::face_sel(s, p.0)).collect();
|
||||
@@ -990,7 +955,7 @@ pub fn for_feature(app: &SolveApp, kind: &FeatureKind, frame: &Mat) -> Option<(K
|
||||
if through.len() != points.len() {
|
||||
extra.insert("points".into(), json!(points.iter().map(|p| pt(p.0)).collect::<Vec<_>>()));
|
||||
}
|
||||
let k = Surf::Patch { continuity: index };
|
||||
let k = Surf::Patch { continuity: continuity.all };
|
||||
let mut ins = inputs(&k);
|
||||
for (input, picks) in ins.iter_mut().zip([sels, through]) {
|
||||
input.items = picks;
|
||||
@@ -1132,22 +1097,15 @@ mod tests {
|
||||
Continuity::ALL.iter().any(|g| text.contains(g.code()))
|
||||
}
|
||||
|
||||
/// The Patch, Blend and Boundary rows offer what their construction builds, from G0; their
|
||||
/// labels are words in every complete catalogue (and the RTL ones), never a code, and each
|
||||
/// hover line names its order's code for those who know it.
|
||||
/// The Patch, Blend and Boundary rows offer what their construction builds, from G0; every
|
||||
/// order's label is a word in each complete catalogue (and the RTL ones), never a code, and
|
||||
/// its hover line names the code for those who know it.
|
||||
#[test]
|
||||
fn continuity_rows_are_words_and_keep_the_codes_for_the_hover_text() {
|
||||
for orders in [PATCH_ORDERS, BLEND_ORDERS, BOUNDARY_ORDERS] {
|
||||
let all = orders.all();
|
||||
assert_eq!(all, orders.0.offered());
|
||||
assert_eq!(all.first(), Some(&Continuity::G0), "{orders:?}");
|
||||
for (i, g) in all.iter().enumerate() {
|
||||
assert_eq!(orders.word(i), Some(g.as_str()));
|
||||
assert_eq!(orders.index(*g), Some(i));
|
||||
}
|
||||
assert_eq!(orders.word(all.len()), None);
|
||||
for orders in [patch_orders(), blend_orders(), boundary_orders()] {
|
||||
assert_eq!(orders.first(), Some(&Continuity::G0), "{orders:?}");
|
||||
}
|
||||
assert_eq!(BLEND_ORDERS.all().last(), Some(&Continuity::G3), "a blend builds up to G3 today");
|
||||
assert_eq!(blend_orders().last(), Some(&Continuity::G3), "a blend builds up to G3 today");
|
||||
for lang in ["en", "ar", "he", "ja", "zh", "de", "ko"] {
|
||||
let locale = crate::i18n::Locale::for_language(lang);
|
||||
for g in Continuity::ALL {
|
||||
@@ -1157,5 +1115,6 @@ mod tests {
|
||||
assert!(tip.contains(g.code()), "{lang}: the {g:?} hover text names its code: {tip}");
|
||||
}
|
||||
}
|
||||
assert_eq!(ui_kit::continuity_tip(&blend_orders()).lines().count(), 4);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2215,7 +2215,7 @@ fn patch_dialog_takes_continuity_and_points_and_brings_them_back() {
|
||||
d.inputs[0].items = rim;
|
||||
d.inputs[1].items = vec![Sel::Vertex { body: "Tube".into(), point: Vec3::new(0.0, 0.0, 36.0) }];
|
||||
if let Kind::Surface(crate::dialogs_surface::Surf::Patch { continuity }) = &mut d.kind {
|
||||
*continuity = 1;
|
||||
*continuity = solidcraft_engine::kernel::Continuity::G1;
|
||||
}
|
||||
let (id, p) = apply_commands(&app, &d).unwrap().remove(0);
|
||||
assert_eq!((id.as_str(), &p["continuity"], &p["points"]), ("surface.patch", &json!("g1"), &json!([[0.0, 0.0, 36.0]])), "{p}");
|
||||
@@ -2225,7 +2225,7 @@ fn patch_dialog_takes_continuity_and_points_and_brings_them_back() {
|
||||
// Edit Feature: G1 again, and the point (on no face) kept as it was.
|
||||
let fid = app.session.doc.features.last().unwrap().id;
|
||||
let back = crate::dialogs::for_feature(&app, fid, None).unwrap();
|
||||
assert!(matches!(back.kind, Kind::Surface(crate::dialogs_surface::Surf::Patch { continuity: 1 })));
|
||||
assert!(matches!(back.kind, Kind::Surface(crate::dialogs_surface::Surf::Patch { continuity: solidcraft_engine::kernel::Continuity::G1 })));
|
||||
let (_, p) = apply_commands(&app, &back).unwrap().remove(0);
|
||||
assert_eq!((&p["params"]["continuity"], &p["params"]["points"]), (&json!("g1"), &json!([[0.0, 0.0, 36.0]])), "{p}");
|
||||
}
|
||||
@@ -2244,10 +2244,10 @@ fn patch_dialog_rows_are_translated() {
|
||||
assert!(h.widget(through).is_some(), "{lang}: the points input must show {through:?}");
|
||||
// The continuity in the language too, in words (the code is in the hover text).
|
||||
if let Some(crate::dialogs::Dialog { kind: Kind::Surface(crate::dialogs_surface::Surf::Patch { continuity }), .. }) = h.app.dialog.as_mut() {
|
||||
*continuity = 1;
|
||||
*continuity = solidcraft_engine::kernel::Continuity::G1;
|
||||
}
|
||||
h.frames(2);
|
||||
assert!(h.widget(tangent).is_some(), "{lang}: the Tangent segment must show {tangent:?}");
|
||||
assert!(h.widget(tangent).is_some(), "{lang}: the Tangent choice must show {tangent:?}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
use super::{metrics::*, text::paint_text};
|
||||
use crate::{scenario, theme::Tokens};
|
||||
use egui::{Response, RichText, Ui, vec2};
|
||||
use solidcraft_engine::kernel::Continuity;
|
||||
|
||||
pub(super) const FORM: &str = "sc_dense_form";
|
||||
|
||||
@@ -92,6 +93,25 @@ pub(crate) fn choice_row(ui: &mut Ui, label: &str, id: &str, selected: &mut usiz
|
||||
row(ui, label, |ui| super::dropdown(ui, id, selected, options))
|
||||
}
|
||||
|
||||
/// The one control that chooses a continuity order, wherever one is chosen: a dropdown of
|
||||
/// `allowed` (what the construction builds, `Construction::offered`). Its labels are the orders'
|
||||
/// plain words and its hover text a line per order naming its code, both from the kernel's one
|
||||
/// table of orders; a value not in `allowed` shows the first. Tokens: `row, dropdown`.
|
||||
pub(crate) fn continuity_row(ui: &mut Ui, label: &str, id: &str, value: &mut Continuity, allowed: &[Continuity]) -> Response {
|
||||
let labels: Vec<&str> = allowed.iter().map(|g| g.label_id()).collect();
|
||||
let mut index = allowed.iter().position(|g| g == value).unwrap_or(0);
|
||||
let response = choice_row(ui, label, id, &mut index, &labels);
|
||||
if let Some(g) = allowed.get(index) {
|
||||
*value = *g;
|
||||
}
|
||||
response.on_hover_text(continuity_tip(allowed))
|
||||
}
|
||||
|
||||
/// The hover text of a continuity choice: each order's own line, which names its code.
|
||||
pub(crate) fn continuity_tip(allowed: &[Continuity]) -> String {
|
||||
allowed.iter().map(|g| crate::i18n::tr(g.tip_id())).collect::<Vec<_>>().join("\n")
|
||||
}
|
||||
|
||||
/// Internal form geometry for controls which need several lines (e.g. text areas).
|
||||
pub(super) fn sized_row<R>(ui: &mut Ui, text: &str, height: f32, control: impl FnOnce(&mut Ui) -> R) -> R {
|
||||
let t = Tokens::get();
|
||||
|
||||
Reference in New Issue
Block a user