fix(fillet): honor selected trim side

This commit is contained in:
Hakan Seven committed 2026-08-01 21:37:58 +03:00
1 parent 456ed24d71
commit c17e31c1ed
1 file changed
+31 -63
+31 -63
View File
@@ -91,19 +91,31 @@ fn compute_fillet(
click2: [f64; 2],
radius: f64,
) -> Option<(EntityType, EntityType, Option<EntityType>)> {
let (p1, _p2, u1, _len1) = line_geom(l1);
let (p3, _p4, u2, _len2) = line_geom(l2);
let (p1, p2, u1, _len1) = line_geom(l1);
let (p3, p4, u2, _len2) = line_geom(l2);
// Intersection of infinite lines
let (t_p, u_p) = ll(p1[0], p1[1], u1[0], u1[1], p3[0], p3[1], u2[0], u2[1])?;
let (t_p, _u_p) = ll(p1[0], p1[1], u1[0], u1[1], p3[0], p3[1], u2[0], u2[1])?;
// Intersection point
let px = p1[0] + t_p * u1[0];
let py = p1[1] + t_p * u1[1];
// Direction from P toward each click (the "keep" side)
let s1 = project_click(click1, [px, py], u1); // positive = along u1
let s2 = project_click(click2, [px, py], u2);
let picked_side = |click: [f64; 2], a: [f64; 2], b: [f64; 2], unit: [f64; 2]| {
let picked = project_click(click, [px, py], unit);
if picked.abs() > 1e-9 {
picked
} else {
// Clicking exactly on the intersection is common when the two
// entities already meet. Fall back to the existing segment's
// midpoint so entity start/end storage order cannot flip the side
// FILLET keeps (#616).
project_click([(a[0] + b[0]) * 0.5, (a[1] + b[1]) * 0.5], [px, py], unit)
}
};
let s1 = picked_side(click1, p1, p2, u1); // positive = along u1
let s2 = picked_side(click2, p3, p4, u2);
let dir1 = if s1 >= 0.0 {
[u1[0], u1[1]]
} else {
@@ -128,8 +140,13 @@ fn compute_fillet(
let z = l1.start.z;
if radius < 1e-9 {
// r = 0: just extend/trim both lines to the intersection
let (new_l1, new_l2) = trim_to_point(l1, t_p, p1, u1, l2, u_p, p3, u2)?;
// r = 0: extend/trim the endpoint opposite each clicked keep-side.
// The old path chose by the sign of t_p/u_p, so reversing an entity's
// stored direction changed the result and an intersection at its start
// could collapse the whole line (#616).
let intersection = [px, py];
let new_l1 = trim_to_xy(l1, intersection, dir1, u1)?;
let new_l2 = trim_to_xy(l2, intersection, dir2, u2)?;
return Some((EntityType::Line(new_l1), EntityType::Line(new_l2), None));
}
@@ -163,8 +180,8 @@ fn compute_fillet(
};
// Trim l1 to T1 and l2 to T2
let new_l1 = trim_to_xy(l1, t_p, t1, dir1, p1, u1)?;
let new_l2 = trim_to_xy(l2, u_p, t2, dir2, p3, u2)?;
let new_l1 = trim_to_xy(l1, t1, dir1, u1)?;
let new_l2 = trim_to_xy(l2, t2, dir2, u2)?;
// Build arc entity
let mut arc = ArcEnt::new();
@@ -182,30 +199,20 @@ fn compute_fillet(
))
}
/// Trim a line's parameter to an intersection t on the same side as dir (keep side).
/// Move the endpoint opposite the selected keep-side to the tangent point.
fn trim_to_xy(
orig: &LineEnt,
t_isect: f64,
tangent: [f64; 2],
dir: [f64; 2],
p1: [f64; 2],
unit: [f64; 2],
) -> Option<LineEnt> {
let z = orig.start.z;
let mut l = orig.clone();
l.common.handle = Handle::NULL;
// t_tangent: parameter of the tangent point along the line from start
let t_tan = (tangent[0] - p1[0]) * unit[0] + (tangent[1] - p1[1]) * unit[1];
// dir is positive along unit → we keep the portion BEYOND t_tan in that direction
// dir is positive along unit → keep the portion beyond the tangent in that direction
// dir positive: keep from t_tan to +∞ (i.e. set start to tangent point)
// dir negative: keep from -∞ to t_tan (i.e. set end to tangent point)
let len = {
let dx = orig.end.x - orig.start.x;
let dy = orig.end.y - orig.start.y;
(dx * dx + dy * dy).sqrt().max(1e-12)
};
let dot = dir[0] * unit[0] + dir[1] * unit[1]; // +1 or -1
if dot > 0.0 {
@@ -215,48 +222,9 @@ fn trim_to_xy(
// keep from start to tangent → move end to tangent point
l.end = Vector3::new(tangent[0], tangent[1], z);
}
let _ = (t_isect, len, t_tan); // used implicitly via `dot`
Some(l)
}
/// Trim both lines exactly to their intersection point (r=0 case).
fn trim_to_point(
l1: &LineEnt,
t_p: f64,
p1: [f64; 2],
u1: [f64; 2],
l2: &LineEnt,
u_p: f64,
_p3: [f64; 2],
_u2: [f64; 2],
) -> Option<(LineEnt, LineEnt)> {
let px = p1[0] + t_p * u1[0];
let py = p1[1] + t_p * u1[1];
let z1 = l1.start.z;
let z2 = l2.start.z;
// For l1: if t_p is past the midpoint, keep start…P; else keep P…end
// We use the same "which end is P closer to" logic
let mut ll1 = l1.clone();
ll1.common.handle = Handle::NULL;
let mut ll2 = l2.clone();
ll2.common.handle = Handle::NULL;
if t_p >= 0.0 {
ll1.end = Vector3::new(px, py, z1);
} else {
ll1.start = Vector3::new(px, py, z1);
}
if u_p >= 0.0 {
ll2.end = Vector3::new(px, py, z2);
} else {
ll2.start = Vector3::new(px, py, z2);
}
Some((ll1, ll2))
}
// ── Point-generation helpers ──────────────────────────────────────────────
fn line_pts(l: &LineEnt) -> Vec<[f32; 3]> {
@@ -1148,7 +1116,7 @@ fn compute_chamfer(
let (p1, _, u1, _) = line_geom(l1);
let (p3, _, u2, _) = line_geom(l2);
let (t_p, u_p) = ll(p1[0], p1[1], u1[0], u1[1], p3[0], p3[1], u2[0], u2[1])?;
let (t_p, _u_p) = ll(p1[0], p1[1], u1[0], u1[1], p3[0], p3[1], u2[0], u2[1])?;
let px = p1[0] + t_p * u1[0];
let py = p1[1] + t_p * u1[1];
@@ -1172,8 +1140,8 @@ fn compute_chamfer(
let c2 = [px + dist2 * dir2[0], py + dist2 * dir2[1]];
// Trim l1 to c1 and l2 to c2
let new_l1 = trim_to_xy(l1, t_p, c1, dir1, p1, u1)?;
let new_l2 = trim_to_xy(l2, u_p, c2, dir2, p3, u2)?;
let new_l1 = trim_to_xy(l1, c1, dir1, u1)?;
let new_l2 = trim_to_xy(l2, c2, dir2, u2)?;
// Chamfer line
let mut cline = l1.clone();