fix(snap): arc segments of a polyline offer their centre and quadrants

Center and Quadrant found nothing on a polyline's bulge segments: the
lightweight and heavy polylines carried no snap points for them, and a
polyline split into several wires kept its points on one piece whose box
the others' never reached. Each arc segment now carries its centre and
quadrants, hovering the segment offers its centre, and a wire's box
covers its snap points.

Fixes #1640

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Opus 5.5 committed 2026-10-07 08:50:05 +03:00
1 parent 29392de267
commit fae64886ee
6 files changed
+150 -28

No files matched your search

+38 -3
View File
@@ -484,9 +484,9 @@ pub fn snap_from(curve: &PlanarCurve) -> CurveSnap {
// there and let their midpoints be derived; everything else names its own
// ends and middle explicitly.
//
// The same distinction is why a polyline's arc-segment centres are not
// emitted: a wire carrying a centre is treated as round elsewhere, which
// a polyline with one bulge in it is not.
// The same distinction is why a polyline's arc-segment centres carry
// their own hint: a wire carrying a `Center` is treated as round
// elsewhere, which a polyline with one bulge in it is not.
let chain = matches!(curve.curve, Curve::Line(_) | Curve::Polyline(_));
let mut out = CurveSnap::default();
let mut push = |world: [f64; 3], hint: SnapHint| {
@@ -499,6 +499,7 @@ pub fn snap_from(curve: &PlanarCurve) -> CurveSnap {
SnapKind::Endpoint => push(world, SnapHint::Endpoint),
SnapKind::Midpoint if !chain => push(world, SnapHint::Midpoint),
SnapKind::Centre if !chain => push(world, SnapHint::Center),
SnapKind::Centre => push(world, SnapHint::ArcCenter),
SnapKind::Quadrant => push(world, SnapHint::Quadrant),
_ => {}
}
@@ -506,6 +507,20 @@ pub fn snap_from(curve: &PlanarCurve) -> CurveSnap {
out
}
/// The centres and quadrants of a polyline's arc segments, for a polyline
/// render that names its vertices itself.
pub fn arc_segment_snaps(entity: &EntityType) -> Vec<(glam::DVec3, SnapHint)> {
entity_curve(entity)
.map(|curve| {
snap_from(&curve)
.snap_pts
.into_iter()
.filter(|(_, hint)| matches!(hint, SnapHint::ArcCenter | SnapHint::Quadrant))
.collect()
})
.unwrap_or_default()
}
/// The magnitude the geometry's own coordinates sit at, for scaling a
/// relative tolerance. Floored at one so geometry near the origin does not
/// end up with a tolerance of nothing.
@@ -806,6 +821,26 @@ mod tests {
assert!(snap.snap_pts.is_empty(), "{:?}", snap.snap_pts);
}
#[test]
fn a_polyline_arc_segment_offers_its_centre_and_quadrants() {
let mut polyline = LwPolylineEnt::default();
polyline.normal = v3(0.0, 0.0, 1.0);
let mut bend = LwVertex::from_coords(0.0, 0.0);
bend.bulge = 0.9999999999999999;
polyline.vertices = vec![LwVertex::from_coords(-20.0, 0.0), bend, LwVertex::from_coords(20.0, 0.0)];
let snaps = arc_segment_snaps(&EntityType::LwPolyline(polyline));
let at = |hint: SnapHint, x: f64, y: f64| {
snaps.iter().any(|(p, h)| {
std::mem::discriminant(h) == std::mem::discriminant(&hint)
&& (p.x - x).abs() < 1e-9
&& (p.y - y).abs() < 1e-9
})
};
assert!(at(SnapHint::ArcCenter, 10.0, 0.0), "{snaps:?}");
assert!(at(SnapHint::Quadrant, 10.0, -10.0), "{snaps:?}");
assert!(!at(SnapHint::Quadrant, 10.0, 10.0), "{snaps:?}");
}
#[test]
fn a_closed_curve_has_no_ends_to_offer() {
let mut circle = CircleEnt::default();
+7 -1
View File
@@ -1226,7 +1226,13 @@ fn apply_transform(pline: &mut LwPolyline, t: &EntityTransform) {
impl RenderConvertible for LwPolyline {
fn to_render(&self, document: &codec::CadDocument) -> Option<RenderEntity> {
Some(to_render(self, document.header.fill_mode))
let mut render = to_render(self, document.header.fill_mode);
// Arc segments offer their centres and quadrants.
if self.vertices.iter().any(|v| v.bulge != 0.0) {
let entity = codec::EntityType::LwPolyline(self.clone());
render.snap_pts.extend(crate::entities::curve::arc_segment_snaps(&entity));
}
Some(render)
}
}
+7 -1
View File
@@ -493,7 +493,13 @@ fn centerline_metadata_2d(
impl RenderConvertible for Polyline2D {
fn to_render(&self, document: &codec::CadDocument) -> Option<RenderEntity> {
Some(tessellate_polyline2d(self, document.header.fill_mode))
let mut render = tessellate_polyline2d(self, document.header.fill_mode);
// Arc segments offer their centres and quadrants.
if self.vertices.iter().any(|v| v.bulge != 0.0) {
let entity = codec::EntityType::Polyline2D(self.clone());
render.snap_pts.extend(crate::entities::curve::arc_segment_snaps(&entity));
}
Some(render)
}
}