forked from 1b-agent/opencadstudio-upstream
Aligned 2Lines: place the second line where the reference leaves it
The second line kept its midpoint and turned around it, so it settled at a different offset than the reference, which keeps the line's start along the first line and moves it to the distance the picked point had from it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
133bfddba9
commit
48b8689fc3
4 files changed
+183
-15
No files matched your search
@@ -523,6 +523,68 @@ pub(crate) fn measured_expression(value: f64) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes a constraint point's position: a line's start (0) or end (1), a
|
||||
/// polyline's vertex by index. Other entities and markers are left alone.
|
||||
pub(crate) fn set_resolved_point(
|
||||
entity: &mut acadrust::EntityType,
|
||||
marker: i32,
|
||||
point: Vector3,
|
||||
) -> bool {
|
||||
match entity {
|
||||
acadrust::EntityType::Line(line) => match marker {
|
||||
0 => line.start = point,
|
||||
1 => line.end = point,
|
||||
_ => return false,
|
||||
},
|
||||
acadrust::EntityType::LwPolyline(polyline) if marker >= 0 => {
|
||||
let Some(vertex) = polyline.vertices.get_mut(marker as usize) else {
|
||||
return false;
|
||||
};
|
||||
vertex.location = acadrust::types::Vector2::new(point.x, point.y);
|
||||
}
|
||||
acadrust::EntityType::Polyline2D(polyline) if marker >= 0 => {
|
||||
let Some(vertex) = polyline.vertices.get_mut(marker as usize) else {
|
||||
return false;
|
||||
};
|
||||
vertex.location = point;
|
||||
}
|
||||
_ => return false,
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Where the reference puts the second line of an Aligned 2Lines pick: it
|
||||
/// turns about its start to run with the first line (keeping its heading
|
||||
/// and length) and settles at the distance the first line's picked point
|
||||
/// had from its original line, on the side nearer to where the turn left
|
||||
/// it — the right-hand side of the first line when both are as near.
|
||||
pub(crate) fn two_lines_placement(
|
||||
first_ends: [glam::DVec3; 2],
|
||||
first_pick: glam::DVec3,
|
||||
second_ends: [glam::DVec3; 2],
|
||||
) -> Option<[glam::DVec3; 2]> {
|
||||
let [f0, f1] = first_ends;
|
||||
let [s0, s1] = second_ends;
|
||||
let f_dir = (f1 - f0).try_normalize()?;
|
||||
let normal = glam::DVec3::new(-f_dir.y, f_dir.x, 0.0);
|
||||
let s_dir = (s1 - s0).try_normalize()?;
|
||||
let length = (s1 - s0).length();
|
||||
let pick = f0 + f_dir * (first_pick - f0).dot(f_dir);
|
||||
let offset = pick - s0;
|
||||
let distance = (s_dir.x * offset.y - s_dir.y * offset.x).abs();
|
||||
let heading = if s_dir.dot(f_dir) >= 0.0 { f_dir } else { -f_dir };
|
||||
let turned = (s0 - f0).dot(normal);
|
||||
let target = if turned.abs() <= 1.0e-9 {
|
||||
-distance
|
||||
} else if (turned - distance).abs() <= (turned + distance).abs() {
|
||||
distance
|
||||
} else {
|
||||
-distance
|
||||
};
|
||||
let shift = normal * (target - turned);
|
||||
Some([s0 + shift, s0 + heading * length + shift])
|
||||
}
|
||||
|
||||
/// Moves a dynamic dimension's extension origins to `first`/`second`,
|
||||
/// keeping its dimension line where it was; true when anything moved.
|
||||
fn dynamic_dimension_follow_points(
|
||||
@@ -4173,3 +4235,51 @@ mod tests {
|
||||
assert_eq!(constraints[1].axis_direction, Some(direction.normalize()));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod two_lines_placement_tests {
|
||||
use super::two_lines_placement;
|
||||
use glam::DVec3;
|
||||
|
||||
fn p(x: f64, y: f64) -> DVec3 {
|
||||
DVec3::new(x, y, 0.0)
|
||||
}
|
||||
|
||||
fn close(a: DVec3, b: DVec3) -> bool {
|
||||
(a - b).length() < 1.0e-2
|
||||
}
|
||||
|
||||
// Measured on the reference: first line (200,0)-(300,0) picked at its
|
||||
// midpoint, the second line as listed, and where it ended up.
|
||||
#[test]
|
||||
fn follows_the_reference_measurements() {
|
||||
let first = [p(200.0, 0.0), p(300.0, 0.0)];
|
||||
let pick = p(250.0, 0.0);
|
||||
let cases = [
|
||||
([p(0.0, 0.0), p(100.0, 50.0)], [p(0.0, -111.803), p(111.803, -111.803)]),
|
||||
([p(0.0, 40.0), p(100.0, 90.0)], [p(0.0, 147.58), p(111.803, 147.58)]),
|
||||
([p(100.0, 0.0), p(200.0, 50.0)], [p(100.0, -67.08), p(211.803, -67.08)]),
|
||||
([p(-100.0, 0.0), p(0.0, 50.0)], [p(-100.0, -156.52), p(11.803, -156.52)]),
|
||||
([p(0.0, 0.0), p(200.0, 100.0)], [p(0.0, -111.8), p(223.607, -111.8)]),
|
||||
([p(0.0, 0.0), p(79.0569, 79.0569)], [p(0.0, -176.78), p(111.803, -176.78)]),
|
||||
([p(0.0, 0.0), p(100.0, -50.0)], [p(0.0, -111.8), p(111.803, -111.8)]),
|
||||
([p(100.0, 50.0), p(0.0, 0.0)], [p(100.0, 111.8), p(-11.803, 111.8)]),
|
||||
([p(0.0, 20.0), p(100.0, 70.0)], [p(0.0, 129.69), p(111.803, 129.69)]),
|
||||
];
|
||||
for (second, expected) in cases {
|
||||
let placed = two_lines_placement(first, pick, second).expect("a placement");
|
||||
assert!(close(placed[0], expected[0]) && close(placed[1], expected[1]), "{second:?} -> {placed:?}, expected {expected:?}");
|
||||
}
|
||||
// The first line picked near its end: the distance grows with it.
|
||||
let placed = two_lines_placement(first, p(290.0, 0.0), [p(0.0, 0.0), p(100.0, 50.0)]).unwrap();
|
||||
assert!(close(placed[0], p(0.0, -129.69)));
|
||||
// A slanted first line: the second line's start keeps its place along it.
|
||||
let placed = two_lines_placement(
|
||||
[p(500.0, 300.0), p(600.0, 350.0)],
|
||||
p(550.0, 325.0),
|
||||
[p(400.0, 300.0), p(500.0, 300.0)],
|
||||
)
|
||||
.unwrap();
|
||||
assert!(close(placed[0], p(408.82, 282.36)) && close(placed[1], p(498.26, 327.08)), "{placed:?}");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user