forked from 1b-agent/opencadstudio-upstream
fix(pline): Convert to Arc follows the cursor and seats on click (#339)
Three fixes to the polyline arc-segment grip:
- bulge_from_midpoint keyed the sign off which chord side the midpoint
lies on — the opposite convention of BulgeArc::from_bulge, so every
grip-drag frame flipped the arc to the other side and the radius
jumped wildly ("snaps to a restricted range of radii"). It now
derives sign and sweep from the p0 -> mid -> p1 traversal
orientation, verified as the exact inverse of BulgeArc's midpoint.
- The arc segment's mid grip dragged in Translate mode, feeding
per-frame deltas through the recomputed arc midpoint — the drag
deadlocked the moment the cursor crossed the chord. Arc grips now
drag in Absolute mode: the arc re-fits through the cursor itself.
Straight-segment grips keep the Translate stretch.
- Picking Convert to Arc in the grip menu now engages that segment
grip (the Add Leader placement mechanic), so the new arc follows the
mouse immediately and the next click seats it.
Closes #339
Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
1 parent
41f7e2becf
commit
d2be0c9bea
2 files changed
+45
-13
No files matched your search
@@ -981,6 +981,24 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||
self.grip_add_provisional = Some((popup.handle, new_gid));
|
||||
}
|
||||
}
|
||||
// Convert to Arc enters placement: the segment grip goes hot
|
||||
// in Absolute mode, so the arc re-fits through the cursor as
|
||||
// it moves and the next click seats it (#339).
|
||||
if matches!(item.action, GripMenuAction::ConvertToArc) {
|
||||
if let Some(g) = self.tabs[i]
|
||||
.selected_grips
|
||||
.iter()
|
||||
.find(|g| g.id == popup.grip_id)
|
||||
{
|
||||
self.tabs[i].active_grip = Some(GripEdit {
|
||||
handle: popup.handle,
|
||||
grip_id: popup.grip_id,
|
||||
is_translate: false,
|
||||
origin_world: g.world,
|
||||
last_world: g.world,
|
||||
});
|
||||
}
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
|
||||
+27
-13
@@ -23,6 +23,13 @@ fn arc_midpoint(p0: [f64; 2], p1: [f64; 2], bulge: f64) -> [f64; 2] {
|
||||
|
||||
/// Compute the DXF bulge for an arc that passes through p0, mid_pt, and p1.
|
||||
/// Returns None when the three points are collinear (straight segment).
|
||||
///
|
||||
/// The sign and sweep follow the p0 → mid → p1 traversal orientation, which
|
||||
/// keeps it the exact inverse of `BulgeArc::from_bulge` + `sample(0.5)` (a
|
||||
/// CCW traversal = positive bulge). It used to key off which chord side the
|
||||
/// midpoint lies on — the opposite convention — so every grip-drag frame
|
||||
/// flipped the arc to the other side and the radius jumped wildly instead of
|
||||
/// following the cursor (#339).
|
||||
fn bulge_from_midpoint(p0: [f64; 2], p1: [f64; 2], mid: [f64; 2]) -> Option<f64> {
|
||||
// Circumcircle of (p0, mid, p1)
|
||||
let ax = 2.0 * (mid[0] - p0[0]);
|
||||
@@ -39,15 +46,20 @@ fn bulge_from_midpoint(p0: [f64; 2], p1: [f64; 2], mid: [f64; 2]) -> Option<f64>
|
||||
let cy = (ax * cb - bx * ca) / det;
|
||||
let a0 = (p0[1] - cy).atan2(p0[0] - cx);
|
||||
let a1 = (p1[1] - cy).atan2(p1[0] - cx);
|
||||
// Determine arc direction: cross product (p1-p0) × (mid-p0)
|
||||
let cross = (p1[0] - p0[0]) * (mid[1] - p0[1]) - (p1[1] - p0[1]) * (mid[0] - p0[0]);
|
||||
let (sa, mut ea) = if cross > 0.0 { (a0, a1) } else { (a1, a0) };
|
||||
if ea < sa {
|
||||
ea += TAU;
|
||||
// Arc direction = orientation of the p0 → mid → p1 turn.
|
||||
let cross =
|
||||
(mid[0] - p0[0]) * (p1[1] - mid[1]) - (mid[1] - p0[1]) * (p1[0] - mid[0]);
|
||||
if cross == 0.0 {
|
||||
return None;
|
||||
}
|
||||
let span = ea - sa; // central angle in (0, TAU]
|
||||
let bulge = (span / 4.0).tan();
|
||||
Some(if cross >= 0.0 { bulge } else { -bulge })
|
||||
// Central angle measured along that direction, in (0, TAU).
|
||||
let sweep = if cross > 0.0 {
|
||||
(a1 - a0).rem_euclid(TAU)
|
||||
} else {
|
||||
(a0 - a1).rem_euclid(TAU)
|
||||
};
|
||||
let bulge = (sweep / 4.0).tan();
|
||||
Some(if cross > 0.0 { bulge } else { -bulge })
|
||||
}
|
||||
|
||||
/// Tessellate a thick polyline segment list into NaN-separated Lines geometry.
|
||||
@@ -429,11 +441,13 @@ fn grips(pline: &LwPolyline) -> Vec<GripDef> {
|
||||
};
|
||||
let dx = (v1.location.x - v0.location.x) as f32;
|
||||
let dy = (v1.location.y - v0.location.y) as f32;
|
||||
out.push(rectangle_grip(
|
||||
n + i,
|
||||
glam::DVec3::new(mx, my, elev),
|
||||
[dx, dy],
|
||||
));
|
||||
let mut g = rectangle_grip(n + i, glam::DVec3::new(mx, my, elev), [dx, dy]);
|
||||
// An arc's mid grip re-fits the arc THROUGH the cursor, so it must
|
||||
// drag in Absolute mode; only a straight segment's grip translates.
|
||||
// Feeding per-frame Translate deltas through the recomputed arc
|
||||
// midpoint deadlocks the moment the cursor crosses the chord (#339).
|
||||
g.is_midpoint = v0.bulge.abs() < 1e-9;
|
||||
out.push(g);
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user