fix(fillet): honor selected trim side

This commit is contained in:
Hakan Seven 2026-08-01 21:37:58 +03:00
commit c17e31c1ed

View file

@ -91,19 +91,31 @@ fn compute_fillet(
click2: [f64; 2], click2: [f64; 2],
radius: f64, radius: f64,
) -> Option<(EntityType, EntityType, Option<EntityType>)> { ) -> Option<(EntityType, EntityType, Option<EntityType>)> {
let (p1, _p2, u1, _len1) = line_geom(l1); let (p1, p2, u1, _len1) = line_geom(l1);
let (p3, _p4, u2, _len2) = line_geom(l2); let (p3, p4, u2, _len2) = line_geom(l2);
// Intersection of infinite lines // 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 // Intersection point
let px = p1[0] + t_p * u1[0]; let px = p1[0] + t_p * u1[0];
let py = p1[1] + t_p * u1[1]; let py = p1[1] + t_p * u1[1];
// Direction from P toward each click (the "keep" side) // Direction from P toward each click (the "keep" side)
let s1 = project_click(click1, [px, py], u1); // positive = along u1 let picked_side = |click: [f64; 2], a: [f64; 2], b: [f64; 2], unit: [f64; 2]| {
let s2 = project_click(click2, [px, py], u2); 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 { let dir1 = if s1 >= 0.0 {
[u1[0], u1[1]] [u1[0], u1[1]]
} else { } else {
@ -128,8 +140,13 @@ fn compute_fillet(
let z = l1.start.z; let z = l1.start.z;
if radius < 1e-9 { if radius < 1e-9 {
// r = 0: just extend/trim both lines to the intersection // r = 0: extend/trim the endpoint opposite each clicked keep-side.
let (new_l1, new_l2) = trim_to_point(l1, t_p, p1, u1, l2, u_p, p3, u2)?; // 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)); 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 // Trim l1 to T1 and l2 to T2
let new_l1 = trim_to_xy(l1, t_p, t1, dir1, p1, u1)?; let new_l1 = trim_to_xy(l1, t1, dir1, u1)?;
let new_l2 = trim_to_xy(l2, u_p, t2, dir2, p3, u2)?; let new_l2 = trim_to_xy(l2, t2, dir2, u2)?;
// Build arc entity // Build arc entity
let mut arc = ArcEnt::new(); 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( fn trim_to_xy(
orig: &LineEnt, orig: &LineEnt,
t_isect: f64,
tangent: [f64; 2], tangent: [f64; 2],
dir: [f64; 2], dir: [f64; 2],
p1: [f64; 2],
unit: [f64; 2], unit: [f64; 2],
) -> Option<LineEnt> { ) -> Option<LineEnt> {
let z = orig.start.z; let z = orig.start.z;
let mut l = orig.clone(); let mut l = orig.clone();
l.common.handle = Handle::NULL; l.common.handle = Handle::NULL;
// t_tangent: parameter of the tangent point along the line from start // dir is positive along unit → keep the portion beyond the tangent in that direction
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 positive: keep from t_tan to +∞ (i.e. set start to tangent point) // 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) // 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 let dot = dir[0] * unit[0] + dir[1] * unit[1]; // +1 or -1
if dot > 0.0 { if dot > 0.0 {
@ -215,48 +222,9 @@ fn trim_to_xy(
// keep from start to tangent → move end to tangent point // keep from start to tangent → move end to tangent point
l.end = Vector3::new(tangent[0], tangent[1], z); l.end = Vector3::new(tangent[0], tangent[1], z);
} }
let _ = (t_isect, len, t_tan); // used implicitly via `dot`
Some(l) 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 ────────────────────────────────────────────── // ── Point-generation helpers ──────────────────────────────────────────────
fn line_pts(l: &LineEnt) -> Vec<[f32; 3]> { fn line_pts(l: &LineEnt) -> Vec<[f32; 3]> {
@ -1148,7 +1116,7 @@ fn compute_chamfer(
let (p1, _, u1, _) = line_geom(l1); let (p1, _, u1, _) = line_geom(l1);
let (p3, _, u2, _) = line_geom(l2); 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 px = p1[0] + t_p * u1[0];
let py = p1[1] + t_p * u1[1]; 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]]; let c2 = [px + dist2 * dir2[0], py + dist2 * dir2[1]];
// Trim l1 to c1 and l2 to c2 // Trim l1 to c1 and l2 to c2
let new_l1 = trim_to_xy(l1, t_p, c1, dir1, p1, u1)?; let new_l1 = trim_to_xy(l1, c1, dir1, u1)?;
let new_l2 = trim_to_xy(l2, u_p, c2, dir2, p3, u2)?; let new_l2 = trim_to_xy(l2, c2, dir2, u2)?;
// Chamfer line // Chamfer line
let mut cline = l1.clone(); let mut cline = l1.clone();