Sketch: drag points of the active sketch (the solver keeps the constraints, one undo step per drag); closed profiles shaded

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
This commit is contained in:
bherbruckandClaude Opus 5.5 committed 2026-10-07 23:16:59 +00:00
1 parent 0f8e4a464b
commit b511ac6124
2 files changed
+43 -1

No files matched your search

+4
View File
@@ -83,6 +83,8 @@ pub struct Tokens {
/// Sketch points: fully constrained, free.
pub sketch_point: Color32,
pub sketch_point_free: Color32,
/// Closed sketch profiles (translucent fill).
pub profile_fill: Color32,
/// Constraint glyph boxes.
pub glyph_bg: Color32,
pub glyph_edge: Color32,
@@ -165,6 +167,7 @@ impl Tokens {
sketch_fixed: Color32::from_rgb(20, 20, 20),
sketch_point: Color32::BLACK,
sketch_point_free: Color32::from_rgb(30, 90, 200),
profile_fill: Color32::from_rgba_unmultiplied_const(255, 196, 120, 70),
glyph_bg: Color32::WHITE,
glyph_edge: Color32::from_rgb(90, 150, 90),
glyph_text: Color32::from_rgb(40, 110, 40),
@@ -215,6 +218,7 @@ impl Tokens {
t.sketch_fixed = Color32::from_rgb(232, 234, 238);
t.sketch_point = Color32::from_rgb(232, 234, 238);
t.sketch_point_free = Color32::from_rgb(92, 150, 255);
t.profile_fill = Color32::from_rgba_unmultiplied_const(120, 150, 200, 40);
t.glyph_bg = Color32::from_rgb(44, 50, 60);
t.glyph_edge = Color32::from_rgb(96, 170, 110);
t.glyph_text = Color32::from_rgb(150, 220, 160);
+39 -1
View File
@@ -42,6 +42,9 @@ pub struct ViewportState {
pub build_ms: f64,
/// The right-click menu, open at this screen position.
pub context_menu: Option<Pos2>,
/// A sketch point being dragged (its id, and the undo depth when the drag began: the whole
/// drag is one undo step).
pub point_drag: Option<(String, usize)>,
}
/// Something under the cursor.
@@ -214,6 +217,15 @@ fn build_scene(app: &SolveApp) -> GpuScene {
for sid in visible_sketches(app) {
let Some(ss) = st.sketch(sid) else { continue };
let active = s.active_sketch == Some(sid);
// Closed profiles are shaded (lifted off a face they may lie on).
let lift = ss.plane.normal() * 0.01;
for p in &ss.profiles {
for tri in p.region.triangulate(0.05) {
for q in tri {
sc.trans_tri((ss.plane.to_world(q) + lift).to_f32(), [0.0; 3], c4(tk.profile_fill));
}
}
}
for (pts, col, cons) in sketch_lines(&ss.sketch, &ss.plane, active, &ss.report.curve_determined) {
for w in pts.windows(2) {
sc.line(w[0].to_f32(), w[1].to_f32(), sketch_color(&tk, col), if cons { 1.2 } else { 2.0 }, active);
@@ -881,8 +893,34 @@ pub fn show(app: &mut SolveApp, ui: &mut egui::Ui) {
}
}
}
// Dragging a sketch point of the active sketch moves it; the solver keeps the constraints.
if app.tool.is_none() && app.dialog.is_none() && app.viewport.nav.is_none() && app.session.active_sketch.is_some() {
if resp.drag_started_by(egui::PointerButton::Primary)
&& let Some(a) = ui.input(|i| i.pointer.press_origin())
&& let Some(Hit::SketchPoint { id, .. }) = pick(app, &proj, a).into_iter().next()
{
app.viewport.point_drag = Some((id, app.session.undo.len()));
}
if let Some((id, depth)) = app.viewport.point_drag.clone()
&& resp.dragged_by(egui::PointerButton::Primary)
&& let Some(p) = hover
&& let Some(ss) = app.session.active_sketch.and_then(|sid| app.session.model.state().sketch(sid).cloned())
&& let Some(w) = {
let (o, d) = proj.ray(p);
ss.plane.intersect_ray(o, d)
}
{
let q = ss.plane.to_local(w);
if app.session.execute("sketch.move_point", &json!({"point": id, "to": [q.x, q.y]})).is_ok() {
app.session.undo.truncate(depth + 1);
}
}
if resp.drag_stopped() {
app.viewport.point_drag = None;
}
}
// Box selection: a primary drag on the model when no navigation mode is on.
if app.tool.is_none() && app.viewport.nav.is_none() {
if app.tool.is_none() && app.viewport.nav.is_none() && app.viewport.point_drag.is_none() {
let origin = ui.input(|i| i.pointer.press_origin());
if resp.dragged_by(egui::PointerButton::Primary)
&& let (Some(a), Some(b)) = (origin, hover)