Sketch drag feel like Fusion (sketch.drag): the grabbed point goes exactly to the cursor, a line (spline, conic) slides with it, a circle or arc grabbed on its edge changes radius about its centre (an ellipse its minor radius), the centre moves the circle; the grabbed geometry is held while the rest moves as little as it can; fixed and fully constrained geometry stays. Dragging a curve of the sketch being edited now drags it (it used to start a box selection). Tests for per-entity fully-constrained feedback

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
This commit is contained in:
bherbruckandClaude Opus 5.5 committed 2026-10-08 07:50:35 +00:00
1 parent bbd0a19260
commit f6b42f8750
3 files changed
+214 -22

No files matched your search

+20 -15
View File
@@ -48,9 +48,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)>,
/// A sketch point or curve being dragged (its id, the undo depth when the drag began: the
/// whole drag is one undo step, and for a curve where it was last under the cursor).
pub point_drag: Option<(String, usize, Option<Vec2>)>,
}
/// Something under the cursor.
@@ -1176,26 +1176,31 @@ pub fn show(app: &mut SolveApp, ui: &mut egui::Ui) {
{
crate::dim_view::drag_to(app, q);
}
// A point or a curve of the sketch being edited follows a drag that starts on it.
if resp.drag_started_by(egui::PointerButton::Primary)
&& !crate::dim_view::dragging()
&& 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 active = app.session.active_sketch;
match pick(app, &proj, a).into_iter().next() {
Some(Hit::SketchPoint { id, .. }) => app.viewport.point_drag = Some((id, app.session.undo.len(), None)),
Some(Hit::SketchCurve { sketch, id, .. }) if Some(sketch) == active => {
app.viewport.point_drag = on_plane(app, a).map(|q| (id, app.session.undo.len(), Some(q)));
}
_ => {}
}
}
if let Some((id, depth, grab)) = app.viewport.point_drag.clone()
&& resp.dragged_by(egui::PointerButton::Primary)
&& let Some(q) = hover.and_then(|p| on_plane(app, p))
{
let q = ss.plane.to_local(w);
if app.session.execute("sketch.move_point", &json!({"point": id, "to": [q.x, q.y]})).is_ok() {
let from = grab.unwrap_or(q);
if app.session.execute("sketch.drag", &json!({"entity": id, "from": [from.x, from.y], "to": [q.x, q.y]})).is_ok() {
app.session.undo.truncate(depth + 1);
}
if grab.is_some() {
app.viewport.point_drag = Some((id, depth, Some(q)));
}
}
if resp.drag_stopped() {
app.viewport.point_drag = None;