Sketch drawing: points dropped on a curve stay on it ("on:<curve>:x,y" point arguments add a point-on-curve constraint; On hint while drawing)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
This commit is contained in:
1 parent
a6dc759a02
commit
097217a3f3
3 files changed
+53
-3
No files matched your search
@@ -187,3 +187,16 @@ fn drawing_from_a_line_midpoint_keeps_it_there() {
|
|||||||
let b = sk.resolve_point(&format!("{l}.end")).and_then(|i| sk.point(i)).unwrap();
|
let b = sk.resolve_point(&format!("{l}.end")).and_then(|i| sk.point(i)).unwrap();
|
||||||
assert!(st.dist((a + b) * 0.5) < 1e-7, "{st:?}");
|
assert!(st.dist((a + b) * 0.5) < 1e-7, "{st:?}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn drawing_onto_a_curve_keeps_the_point_on_it() {
|
||||||
|
let mut s = new_sketch();
|
||||||
|
let c = ids(&run(&mut s, "CircleCenterRadius", json!({"center": [0, 0], "radius": 10}))["curves"])[0].clone();
|
||||||
|
let r = run(&mut s, "DrawPolyline", json!({"points": [format!("on:{c}:7.2,7.1"), [30, 30]]}));
|
||||||
|
let n = ids(&r["curves"])[0].clone();
|
||||||
|
run(&mut s, "SketchDimension", json!({"entities": [c], "value": 16}));
|
||||||
|
let sk = sketch(&s);
|
||||||
|
let st = sk.resolve_point(&format!("{n}.start")).and_then(|i| sk.point(i)).unwrap();
|
||||||
|
let cc = sk.center(sk.curve_index(&c).unwrap()).unwrap();
|
||||||
|
assert!((st.dist(cc) - 8.0).abs() < 1e-7, "{st:?} {cc:?}");
|
||||||
|
}
|
||||||
@@ -347,7 +347,16 @@ pub(super) fn snap_points(s: &mut Session, p: &Value, cmd: &str) -> Result<Value
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
let mid = v.as_str().and_then(|x| x.strip_prefix("mid:")).map(str::to_string);
|
let mid = v.as_str().and_then(|x| x.strip_prefix("mid:")).map(str::to_string);
|
||||||
if vertex.is_none() && edge.is_none() && mid.is_none() {
|
// "on:<curve>:x,y": a point on a curve of the sketch near x,y.
|
||||||
|
let on = v.as_str().and_then(|x| x.strip_prefix("on:")).and_then(|t| {
|
||||||
|
let (c, xy) = t.rsplit_once(':')?;
|
||||||
|
let n: Vec<f64> = xy.split(',').filter_map(|x| x.trim().parse().ok()).collect();
|
||||||
|
match n[..] {
|
||||||
|
[x, y] if x.is_finite() && y.is_finite() => Some((c.to_string(), solvecraft_geom::Vec2::new(x, y))),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if vertex.is_none() && edge.is_none() && mid.is_none() && on.is_none() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
if doc.is_none() {
|
if doc.is_none() {
|
||||||
@@ -358,7 +367,16 @@ pub(super) fn snap_points(s: &mut Session, p: &Value, cmd: &str) -> Result<Value
|
|||||||
}
|
}
|
||||||
let Some((d, sk)) = doc.as_mut() else { return Ok(()) };
|
let Some((d, sk)) = doc.as_mut() else { return Ok(()) };
|
||||||
let body = str_(v, "body").unwrap_or("").to_string();
|
let body = str_(v, "body").unwrap_or("").to_string();
|
||||||
let pid = if let Some(line) = mid {
|
let pid = if let Some((curve, near)) = on {
|
||||||
|
let c = sk.curve_index(&curve).ok_or_else(|| bad(cmd, format!("unknown curve `{curve}`")))?;
|
||||||
|
let at = match sk.shape(c) {
|
||||||
|
Some(sh) => sh.project(near),
|
||||||
|
None => near,
|
||||||
|
};
|
||||||
|
let pi = sk.add_point(at, None)?;
|
||||||
|
sk.add_constraint(ConstraintKind::PointOnCurve { p: pi, c }, None)?;
|
||||||
|
sk.points.get(pi).map(|q| q.id.clone()).unwrap_or_default()
|
||||||
|
} else if let Some(line) = mid {
|
||||||
// "mid:<line>": a new point at the line's midpoint, kept there.
|
// "mid:<line>": a new point at the line's midpoint, kept there.
|
||||||
let l = sk.curve_index(&line).ok_or_else(|| bad(cmd, format!("unknown curve `{line}`")))?;
|
let l = sk.curve_index(&line).ok_or_else(|| bad(cmd, format!("unknown curve `{line}`")))?;
|
||||||
let Some(solvecraft_sketch::Shape::Line { a, b }) = sk.shape(l) else { return Err(bad(cmd, format!("`{line}` is not a line"))) };
|
let Some(solvecraft_sketch::Shape::Line { a, b }) = sk.shape(l) else { return Err(bad(cmd, format!("`{line}` is not a line"))) };
|
||||||
|
|||||||
@@ -71,6 +71,18 @@ pub fn refine_snap(
|
|||||||
}
|
}
|
||||||
None => PREV.with(Cell::get),
|
None => PREV.with(Cell::get),
|
||||||
};
|
};
|
||||||
|
// On a curve (coincident with it).
|
||||||
|
if best.is_none() {
|
||||||
|
for (i, c) in sk.curves.iter().enumerate() {
|
||||||
|
let Some(sh) = sk.shape(i) else { continue };
|
||||||
|
let d = sh.dist(lp);
|
||||||
|
if d < tol * 0.8 && best.is_none_or(|x| d < x.0) {
|
||||||
|
let q = sh.project(lp);
|
||||||
|
best = Some((d, "On", q));
|
||||||
|
mid_of = Some(format!("{}:{},{}", c.id, q.x, q.y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if best.is_none()
|
if best.is_none()
|
||||||
&& let Some(prev) = prev
|
&& let Some(prev) = prev
|
||||||
{
|
{
|
||||||
@@ -86,7 +98,14 @@ pub fn refine_snap(
|
|||||||
}
|
}
|
||||||
SNAP.with(|s| *s.borrow_mut() = best.map(|(_, l, p)| (l, p)));
|
SNAP.with(|s| *s.borrow_mut() = best.map(|(_, l, p)| (l, p)));
|
||||||
// A midpoint snap is a point argument the commands understand ("mid:<line>").
|
// A midpoint snap is a point argument the commands understand ("mid:<line>").
|
||||||
best.map(|(_, l, p)| (p, if l == "Mid" { mid_of.map(|m| format!("mid:{m}")) } else { None }))
|
best.map(|(_, l, p)| {
|
||||||
|
let arg = match l {
|
||||||
|
"Mid" => mid_of.map(|m| format!("mid:{m}")),
|
||||||
|
"On" => mid_of.map(|m| format!("on:{m}")),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
(p, arg)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A click on a constraint glyph selects it (true: the click was used).
|
/// A click on a constraint glyph selects it (true: the click was used).
|
||||||
|
|||||||
Reference in new issue
Block a user