mirror of
https://github.com/storytold/cadcraft.git
synced 2026-10-28 12:56:47 -06:00
Angular constraint between two lines holds the angle it draws, whichever way the lines run (part of #1134) (#1140)
This commit is contained in:
@@ -40,6 +40,11 @@ pub(crate) enum Form {
|
||||
C1,
|
||||
/// Arc sweep (angular on an arc).
|
||||
ArcSweep,
|
||||
/// Two lines measured along the rays from their intersection to each line's far end, the
|
||||
/// angle the dimension draws; `rev` marks a line whose far end is its start point.
|
||||
Rays {
|
||||
rev: [bool; 2],
|
||||
},
|
||||
/// Fix of a derived point (residual to an anchor).
|
||||
Anchor(Vec2),
|
||||
/// Fix handled by locking parameters (no rows).
|
||||
@@ -92,6 +97,10 @@ fn dir_n(a: Vec2, b: Vec2) -> Vec2 {
|
||||
if l > 1e-12 { d * (1.0 / l) } else { d }
|
||||
}
|
||||
|
||||
fn ray(a: Vec2, b: Vec2, rev: bool) -> Vec2 {
|
||||
if rev { dir_n(b, a) } else { dir_n(a, b) }
|
||||
}
|
||||
|
||||
fn sgn(v: f64) -> f64 {
|
||||
if v < 0.0 { -1.0 } else { 1.0 }
|
||||
}
|
||||
@@ -284,10 +293,15 @@ impl Built {
|
||||
}
|
||||
K::Angular => {
|
||||
b.rows = 1;
|
||||
if n == 2 && ln(0).is_some() && ln(1).is_some() {
|
||||
b.form = Form::LL;
|
||||
let (Some((a1, b1)), Some((a2, b2))) = (ln(0), ln(1)) else { return need("two lines") };
|
||||
let (d1, d2) = (dir_n(a1, b1), dir_n(a2, b2));
|
||||
if let (2, Some((a1, b1)), Some((a2, b2))) = (n, ln(0), ln(1)) {
|
||||
// Start-to-end directions would measure the supplement when a line is drawn
|
||||
// towards the vertex, so use the rays the dimension draws.
|
||||
let rev = match cadcraft_geom::line_line_infinite(a1, b1, a2, b2) {
|
||||
Some((_, t, u)) => [t > 0.5, u > 0.5],
|
||||
None => [false, false],
|
||||
};
|
||||
b.form = Form::Rays { rev };
|
||||
let (d1, d2) = (ray(a1, b1, rev[0]), ray(a2, b2, rev[1]));
|
||||
b.sign = sgn(d1.cross(d2).atan2(d1.dot(d2)));
|
||||
} else if n == 1 && m.arc_sweep(x, r_at(c, 0).ok_or_else(|| "Angular needs references".to_string())?).is_some() {
|
||||
b.form = Form::ArcSweep;
|
||||
@@ -297,7 +311,7 @@ impl Built {
|
||||
b.target = target.to_radians();
|
||||
// Two lines measure a signed angle in [-180, 180] and the sign is re-read from the geometry, so a larger
|
||||
// target would flip the sign once solved and never be satisfied; only an arc sweep can reach a full turn.
|
||||
let max = if b.form == Form::LL { std::f64::consts::PI } else { std::f64::consts::TAU };
|
||||
let max = if matches!(b.form, Form::Rays { .. }) { std::f64::consts::PI } else { std::f64::consts::TAU };
|
||||
if !b.target.is_finite() || b.target.abs() > max {
|
||||
return Err("angle out of range".into());
|
||||
}
|
||||
@@ -461,9 +475,9 @@ impl Built {
|
||||
(K::Angular, Form::ArcSweep) => {
|
||||
out.push(m.arc_sweep(x, r(0)?)? - self.target);
|
||||
}
|
||||
(K::Angular, _) => {
|
||||
(K::Angular, Form::Rays { rev }) => {
|
||||
let ((a1, b1), (a2, b2)) = (ln(0)?, ln(1)?);
|
||||
let (d1, d2) = (dir_n(a1, b1), dir_n(a2, b2));
|
||||
let (d1, d2) = (ray(a1, b1, rev[0]), ray(a2, b2, rev[1]));
|
||||
out.push(wrap(d1.cross(d2).atan2(d1.dot(d2)) - self.sign * self.target));
|
||||
}
|
||||
(K::Radius, _) => out.push(ci(0)?.1 - self.target),
|
||||
|
||||
@@ -231,3 +231,32 @@ fn delconstraint_rejects_referenced_dimension() {
|
||||
s.execute("delconstraint", &json!({ "ids": ids })).unwrap();
|
||||
assert!(s.doc().unwrap().constraints.is_empty());
|
||||
}
|
||||
|
||||
/// The angle between the two rays from the vertex to the far end of each line: the angle
|
||||
/// DCANGULAR draws.
|
||||
fn drawn_angle(s: &Session, a: &str, b: &str) -> f64 {
|
||||
let ((a0, a1), (b0, b1)) = (ends(s, a), ends(s, b));
|
||||
let (o, _, _) = cadcraft_geom::line_line_infinite(a0, a1, b0, b1).unwrap();
|
||||
let far = |p: Vec2, q: Vec2| if p.dist(o) > q.dist(o) { p } else { q };
|
||||
let (u, v) = (far(a0, a1) - o, far(b0, b1) - o);
|
||||
u.cross(v).atan2(u.dot(v)).abs().to_degrees()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dcangular_constrains_the_drawn_angle_whichever_way_the_lines_run() {
|
||||
// #1134: the second line drawn towards the shared vertex got the supplement of the value.
|
||||
for b_pts in [[[2.0, 10.0], [0.0, 0.0]], [[0.0, 0.0], [2.0, 10.0]]] {
|
||||
let mut s = Session::new();
|
||||
s.viewport_px = (1000.0, 1000.0);
|
||||
let a = line(&mut s, [0.0, 0.0], [10.0, 0.0]);
|
||||
let b = line(&mut s, b_pts[0], b_pts[1]);
|
||||
s.cmdline("dcangular").unwrap();
|
||||
s.input(Input::Point(Vec2::new(3.0, 0.0))).unwrap();
|
||||
s.input(Input::Point(Vec2::new(0.6, 3.0))).unwrap();
|
||||
assert!(s.prompt_text().contains("78.69"), "{b_pts:?}: {}", s.prompt_text());
|
||||
s.cmdline("100").unwrap();
|
||||
assert!(s.running.is_none());
|
||||
let got = drawn_angle(&s, &a, &b);
|
||||
assert!((got - 100.0).abs() < 1e-6, "{b_pts:?}: drawn angle {got}");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user