From 39850fb6ab5bc859a46cffbfd8c4657282913b92 Mon Sep 17 00:00:00 2001 From: DuBearte <130190089+DuBearte@users.noreply.github.com> Date: Sat, 17 Oct 2026 04:08:03 -0400 Subject: [PATCH] Extrude preview: a New Body has its own colour, apart from Join (#1805) (#1861) Thanks @DuBearte. --- assets/styles/ui/palettes.rs | 1 + crates/ui-egui/src/colour_vision_tests.rs | 1 + crates/ui-egui/src/dialogs_assembly.rs | 2 +- crates/ui-egui/src/dialogs_assembly_tests.rs | 24 +++++++++++++++++--- crates/ui-egui/src/preview.rs | 15 +++++++++--- crates/ui-egui/src/preview_drag_tests.rs | 2 +- crates/ui-egui/src/theme.rs | 2 ++ 7 files changed, 39 insertions(+), 8 deletions(-) diff --git a/assets/styles/ui/palettes.rs b/assets/styles/ui/palettes.rs index 46683450..afe8f013 100644 --- a/assets/styles/ui/palettes.rs +++ b/assets/styles/ui/palettes.rs @@ -48,6 +48,7 @@ impl Tokens { body_edge: Color32::from_rgb(40, 44, 52), manipulator: Color32::from_rgb(40, 110, 220), preview_add: Color32::from_rgb(112, 160, 226), + preview_new: Color32::from_rgb(240, 225, 105), preview_sketch: Color32::from_rgb(32, 190, 96), preview_cut: Color32::from_rgba_unmultiplied_const(232, 84, 44, 110), cube_face: Color32::from_rgb(236, 239, 244), diff --git a/crates/ui-egui/src/colour_vision_tests.rs b/crates/ui-egui/src/colour_vision_tests.rs index 5bba2ea7..63eccee7 100644 --- a/crates/ui-egui/src/colour_vision_tests.rs +++ b/crates/ui-egui/src/colour_vision_tests.rs @@ -292,6 +292,7 @@ fn groups(t: &Tokens) -> Vec { ("body_selected", t.body_selected), ("hover_face_lift", lift(t.body)), ("preview_add", t.preview_add), + ("preview_new", t.preview_new), ], &[], ), diff --git a/crates/ui-egui/src/dialogs_assembly.rs b/crates/ui-egui/src/dialogs_assembly.rs index 4294c0fe..90c7659f 100644 --- a/crates/ui-egui/src/dialogs_assembly.rs +++ b/crates/ui-egui/src/dialogs_assembly.rs @@ -1001,7 +1001,7 @@ pub fn world_preview(s: &Session, cmds: &[(String, Value)], colors: Colors) -> O .map(|(_, r)| r.clone()); let (before, after) = (preview.before, preview.after); // A moved component looks like itself (no new surface), with its old place as the ghost. - let colors = Colors { added: colors.body, ..colors }; + let colors = Colors { added: colors.body, new_body: colors.body, ..colors }; let built = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| crate::preview::build(&before, &after, colors))); Some(match built { Ok(mut b) => { diff --git a/crates/ui-egui/src/dialogs_assembly_tests.rs b/crates/ui-egui/src/dialogs_assembly_tests.rs index 2e36408c..c8dffb0a 100644 --- a/crates/ui-egui/src/dialogs_assembly_tests.rs +++ b/crates/ui-egui/src/dialogs_assembly_tests.rs @@ -74,7 +74,13 @@ fn joint_and_drive_preview_on_the_placed_model() { let mut d = app.dialog.clone().unwrap(); d.pick(&app.session, face_sel(&app.session, Vec3::new(10.0, 10.0, 10.0)).unwrap()); d.pick(&app.session, face_sel(&app.session, Vec3::new(40.0, 10.0, 0.0)).unwrap()); - let colors = crate::preview::Colors { body: [1, 1, 1, 255], added: [2, 2, 2, 255], removed: [3, 3, 3, 100], edge: [0, 0, 0, 255] }; + let colors = crate::preview::Colors { + body: [1, 1, 1, 255], + added: [2, 2, 2, 255], + new_body: [2, 2, 2, 255], + removed: [3, 3, 3, 100], + edge: [0, 0, 0, 255], + }; let cmds = preview_commands(&app, &d).unwrap(); let b = world_preview(&app.session, &cmds, colors).unwrap().unwrap(); assert_eq!(b.replaced, vec!["Body2".to_string()], "B moves"); @@ -461,7 +467,13 @@ fn four_bar() -> SolveApp { #[test] fn drive_says_where_a_loop_stopped_it() { let mut app = four_bar(); - let colors = crate::preview::Colors { body: [1, 1, 1, 255], added: [2, 2, 2, 255], removed: [3, 3, 3, 100], edge: [0, 0, 0, 255] }; + let colors = crate::preview::Colors { + body: [1, 1, 1, 255], + added: [2, 2, 2, 255], + new_body: [2, 2, 2, 255], + removed: [3, 3, 3, 100], + edge: [0, 0, 0, 255], + }; let rocker = app.session.doc.assembly.joints.iter().position(|j| j.name == "Rocker").unwrap(); let mut d = drive_joint(&app, app.session.doc.assembly.joints[rocker].id).unwrap(); let ask = |d: &mut Dialog, v: &str| { @@ -514,7 +526,13 @@ fn drive_says_when_its_joint_is_joined_to_nothing_grounded() { if let Kind::Assembly(Asm::Drive { values, .. }) = &mut d.kind { values[0] = "45 deg".into(); } - let colors = crate::preview::Colors { body: [1, 1, 1, 255], added: [2, 2, 2, 255], removed: [3, 3, 3, 100], edge: [0, 0, 0, 255] }; + let colors = crate::preview::Colors { + body: [1, 1, 1, 255], + added: [2, 2, 2, 255], + new_body: [2, 2, 2, 255], + removed: [3, 3, 3, 100], + edge: [0, 0, 0, 255], + }; let b = world_preview(&app.session, &apply_commands(&app, &d).unwrap(), colors).unwrap().unwrap(); let why = b.stopped.as_ref().and_then(drive_stop).unwrap(); assert!(why.starts_with("Moves nothing") && why.contains("C:1"), "{why}"); diff --git a/crates/ui-egui/src/preview.rs b/crates/ui-egui/src/preview.rs index 04e00bb3..41a2ab13 100644 --- a/crates/ui-egui/src/preview.rs +++ b/crates/ui-egui/src/preview.rs @@ -26,6 +26,8 @@ use crate::theme::Tokens; pub struct Colors { pub body: [u8; 4], pub added: [u8; 4], + /// A body the preview creates (New Body), so it doesn't look like a join. + pub new_body: [u8; 4], pub removed: [u8; 4], pub edge: [u8; 4], } @@ -33,7 +35,7 @@ pub struct Colors { impl Colors { pub fn from_tokens(t: &Tokens) -> Colors { let c = |c: egui::Color32| c.to_srgba_unmultiplied(); - Colors { body: c(t.body), added: c(t.preview_add), removed: c(t.preview_cut), edge: c(t.body_edge) } + Colors { body: c(t.body), added: c(t.preview_add), new_body: c(t.preview_new), removed: c(t.preview_cut), edge: c(t.body_edge) } } } @@ -501,7 +503,11 @@ pub fn build(before: &ModelState, after: &ModelState, c: Colors) -> Built { let cut = old.as_ref().is_some_and(|bm| am.measure().volume < bm.measure().volume - 1e-6); for (t, f) in am.triangles.iter().zip(&am.tri_face) { let new = added.get(*f as usize).copied().unwrap_or(true); - let col = if new { c.added } else { c.body }; + let col = match (new, old.is_some()) { + (false, _) => c.body, + (true, true) => c.added, + (true, false) => c.new_body, + }; for k in t { let i = *k as usize; if let (Some(p), Some(n)) = (am.positions.get(i), am.normals.get(i)) { @@ -717,7 +723,7 @@ mod tests { use serde_json::json; fn colors() -> Colors { - Colors { body: [1, 1, 1, 255], added: [2, 2, 2, 255], removed: [3, 3, 3, 100], edge: [0, 0, 0, 255] } + Colors { body: [1, 1, 1, 255], added: [2, 2, 2, 255], new_body: [4, 4, 4, 255], removed: [3, 3, 3, 100], edge: [0, 0, 0, 255] } } #[test] @@ -1006,6 +1012,9 @@ mod tests { let b = compute(&s, &[("solid.extrude".into(), json!({"distance": "15 mm"}))], colors()).unwrap(); assert!(b.replaced.is_empty()); assert_eq!(count(&b.scene.tris, crate::gpu::TRI_SIZE, colors().body), 0); + // #1805: a new body has its own colour, not a join's. + assert!(count(&b.scene.tris, crate::gpu::TRI_SIZE, colors().new_body) > 0); + assert_eq!(count(&b.scene.tris, crate::gpu::TRI_SIZE, colors().added), 0); assert!(b.scene.ghost.is_empty()); assert!(compute(&s, &[("solid.extrude".into(), json!({"distance": "1 +"}))], colors()).is_err()); assert!(s.model.state().bodies.is_empty()); diff --git a/crates/ui-egui/src/preview_drag_tests.rs b/crates/ui-egui/src/preview_drag_tests.rs index 24f56f7b..bcaa3afa 100644 --- a/crates/ui-egui/src/preview_drag_tests.rs +++ b/crates/ui-egui/src/preview_drag_tests.rs @@ -3,7 +3,7 @@ use super::*; use serde_json::json; fn colors() -> Colors { - Colors { body: [1, 1, 1, 255], added: [2, 2, 2, 255], removed: [3, 3, 3, 100], edge: [0, 0, 0, 255] } + Colors { body: [1, 1, 1, 255], added: [2, 2, 2, 255], new_body: [2, 2, 2, 255], removed: [3, 3, 3, 100], edge: [0, 0, 0, 255] } } fn fixture() -> Session { diff --git a/crates/ui-egui/src/theme.rs b/crates/ui-egui/src/theme.rs index 629b12e0..101c8fc1 100644 --- a/crates/ui-egui/src/theme.rs +++ b/crates/ui-egui/src/theme.rs @@ -208,6 +208,8 @@ pub struct Tokens { pub manipulator: Color32, /// New surface in a feature preview. pub preview_add: Color32, + /// The surface of a body a feature preview creates (New Body), apart from a join (#1805). + pub preview_new: Color32, /// Proposed sketch geometry, distinct from blue selections. pub preview_sketch: Color32, /// Removed material in a feature preview (translucent).