forked from 1b-agent/opencadstudio-upstream
fix(fillet/chamfer): polyline arc preview, edge highlight, polyline chamfer
Three polyline issues in the FILLET/CHAMFER commands: - The polyline preview wire ignored bulge, so a filleted corner previewed as a straight line instead of the arc. lwpoly_pts now tessellates bulge segments via BulgeArc (same as the entity render). - Hovering a polyline edge didn't highlight it: the first-pick hover only handled Line/Arc. Both commands now highlight the nearest polyline segment on hover. - CHAMFER didn't work on polylines at all (Line-only). Added a SecondPoly step + chamfer_lwpoly_corner: pick two adjacent segments of one polyline to cut the shared corner back by the two distances with a straight edge. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
d8c60c32d4
commit
ef29d19ae8
1 file changed
+160
-29
@@ -525,7 +525,7 @@ fn lwpoly_shorten_seg(
|
||||
new_poly
|
||||
}
|
||||
|
||||
/// Wire points for a LwPolyline (straight segments only, for preview).
|
||||
/// Wire points for a LwPolyline (honours bulge → arcs, for preview/highlight).
|
||||
fn lwpoly_pts(poly: &LwPolyline) -> Vec<[f32; 3]> {
|
||||
let elev = poly.elevation as f32;
|
||||
let n = poly.vertices.len();
|
||||
@@ -538,12 +538,56 @@ fn lwpoly_pts(poly: &LwPolyline) -> Vec<[f32; 3]> {
|
||||
for i in 0..seg_count {
|
||||
let v0 = &poly.vertices[i];
|
||||
let v1 = &poly.vertices[(i + 1) % n];
|
||||
pts.push([v0.location.x as f32, v0.location.y as f32, elev]);
|
||||
pts.push([v1.location.x as f32, v1.location.y as f32, elev]);
|
||||
lwpoly_seg_pts(
|
||||
[v0.location.x, v0.location.y],
|
||||
[v1.location.x, v1.location.y],
|
||||
v0.bulge,
|
||||
elev,
|
||||
&mut pts,
|
||||
);
|
||||
}
|
||||
pts
|
||||
}
|
||||
|
||||
/// Wire points highlighting the polyline segment nearest to `click`.
|
||||
fn lwpoly_seg_hover_pts(poly: &LwPolyline, click: [f64; 2]) -> Vec<[f32; 3]> {
|
||||
let n = poly.vertices.len();
|
||||
if n < 2 {
|
||||
return vec![];
|
||||
}
|
||||
let seg = lwpoly_nearest_seg(poly, click);
|
||||
let v0 = &poly.vertices[seg];
|
||||
let v1 = &poly.vertices[(seg + 1) % n];
|
||||
let mut pts = Vec::new();
|
||||
lwpoly_seg_pts(
|
||||
[v0.location.x, v0.location.y],
|
||||
[v1.location.x, v1.location.y],
|
||||
v0.bulge,
|
||||
poly.elevation as f32,
|
||||
&mut pts,
|
||||
);
|
||||
pts
|
||||
}
|
||||
|
||||
/// Append the wire points of one polyline segment (straight, or a bulge arc).
|
||||
fn lwpoly_seg_pts(p0: [f64; 2], p1: [f64; 2], bulge: f64, elev: f32, out: &mut Vec<[f32; 3]>) {
|
||||
if let Some(arc) = (bulge.abs() >= 1e-9)
|
||||
.then(|| crate::entities::common::BulgeArc::from_bulge(p0, p1, bulge))
|
||||
.flatten()
|
||||
{
|
||||
const STEPS: usize = 16;
|
||||
for j in 0..STEPS {
|
||||
let a = arc.sample(j as f64 / STEPS as f64);
|
||||
let b = arc.sample((j + 1) as f64 / STEPS as f64);
|
||||
out.push([a[0] as f32, a[1] as f32, elev]);
|
||||
out.push([b[0] as f32, b[1] as f32, elev]);
|
||||
}
|
||||
} else {
|
||||
out.push([p0[0] as f32, p0[1] as f32, elev]);
|
||||
out.push([p1[0] as f32, p1[1] as f32, elev]);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Unified fillet entity type ─────────────────────────────────────────────
|
||||
|
||||
/// A pickable entity for FILLET: Line, Arc, or an LwPolyline segment.
|
||||
@@ -1333,8 +1377,9 @@ impl CadCommand for FilletCommand {
|
||||
.all_entities
|
||||
.iter()
|
||||
.find(|e| e.common().handle == handle)
|
||||
.and_then(|e| {
|
||||
FilletEntity::from_entity(e).map(|fe| entity_pts(&fe.to_entity_type()))
|
||||
.and_then(|e| match e {
|
||||
EntityType::LwPolyline(p) => Some(lwpoly_seg_hover_pts(p, click)),
|
||||
_ => FilletEntity::from_entity(e).map(|fe| entity_pts(&fe.to_entity_type())),
|
||||
});
|
||||
if let Some(pts) = pts {
|
||||
vec![WireModel::solid(
|
||||
@@ -1412,6 +1457,51 @@ impl CadCommand for FilletCommand {
|
||||
// ChamferCommand
|
||||
// ══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
/// Chamfer a corner of a single LwPolyline: the two picks select two adjacent
|
||||
/// segments; the shared corner vertex is cut back by dist1/dist2 and replaced
|
||||
/// with a straight chamfer edge (bulge 0). Returns the rebuilt polyline.
|
||||
fn chamfer_lwpoly_corner(
|
||||
poly: &LwPolyline,
|
||||
click1: [f64; 2],
|
||||
dist1: f64,
|
||||
click2: [f64; 2],
|
||||
dist2: f64,
|
||||
) -> Option<EntityType> {
|
||||
let n = poly.vertices.len();
|
||||
if n < 3 {
|
||||
return None;
|
||||
}
|
||||
let s1 = lwpoly_nearest_seg(poly, click1);
|
||||
let s2 = lwpoly_nearest_seg(poly, click2);
|
||||
if s1 == s2 {
|
||||
return None;
|
||||
}
|
||||
let (low, high) = if s1 < s2 { (s1, s2) } else { (s2, s1) };
|
||||
let consecutive =
|
||||
high == low + 1 || (poly.is_closed && low == 0 && high == n.saturating_sub(1));
|
||||
if !consecutive {
|
||||
return None;
|
||||
}
|
||||
let corner_idx = high % n;
|
||||
let l1 = lwpoly_seg_as_line(poly, low);
|
||||
let l2 = lwpoly_seg_as_line(poly, high % n.max(1));
|
||||
// Map each click + distance to whichever segment it was picked on.
|
||||
let (c1, d1, c2, d2) = if s1 == low {
|
||||
(click1, dist1, click2, dist2)
|
||||
} else {
|
||||
(click2, dist2, click1, dist1)
|
||||
};
|
||||
match compute_chamfer(&l1, c1, d1, &l2, c2, d2)? {
|
||||
(EntityType::Line(tl1), EntityType::Line(tl2), _) => {
|
||||
let t1 = [tl1.end.x, tl1.end.y];
|
||||
let t2 = [tl2.start.x, tl2.start.y];
|
||||
let new_poly = lwpoly_replace_corner(poly, corner_idx, t1, t2, 0.0);
|
||||
Some(EntityType::LwPolyline(new_poly))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
enum ChamferStep {
|
||||
First,
|
||||
WaitingForDist1,
|
||||
@@ -1421,6 +1511,13 @@ enum ChamferStep {
|
||||
l1: LineEnt,
|
||||
click1: [f64; 2],
|
||||
},
|
||||
/// First pick was an LwPolyline segment; waiting for the second segment
|
||||
/// of the same polyline to chamfer the shared corner.
|
||||
SecondPoly {
|
||||
h1: Handle,
|
||||
poly: LwPolyline,
|
||||
click1: [f64; 2],
|
||||
},
|
||||
}
|
||||
|
||||
pub struct ChamferCommand {
|
||||
@@ -1463,6 +1560,10 @@ impl CadCommand for ChamferCommand {
|
||||
"CHAMFER Select second line [D1={:.4} D2={:.4}]:",
|
||||
self.dist1, self.dist2
|
||||
),
|
||||
ChamferStep::SecondPoly { .. } => format!(
|
||||
"CHAMFER Select the adjacent polyline segment [D1={:.4} D2={:.4}]:",
|
||||
self.dist1, self.dist2
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1507,7 +1608,9 @@ impl CadCommand for ChamferCommand {
|
||||
// Invalid — stay and re-prompt
|
||||
Some(CmdResult::NeedPoint)
|
||||
}
|
||||
ChamferStep::First | ChamferStep::Second { .. } => {
|
||||
ChamferStep::First
|
||||
| ChamferStep::Second { .. }
|
||||
| ChamferStep::SecondPoly { .. } => {
|
||||
let t = text.trim();
|
||||
let upper = t.to_uppercase();
|
||||
// "D" alone → enter sub-step to collect distances
|
||||
@@ -1563,26 +1666,40 @@ impl CadCommand for ChamferCommand {
|
||||
return CmdResult::NeedPoint;
|
||||
}
|
||||
ChamferStep::First => {
|
||||
let l1 = self
|
||||
match self
|
||||
.all_entities
|
||||
.iter()
|
||||
.find(|e| e.common().handle == handle)
|
||||
.and_then(|e| {
|
||||
if let EntityType::Line(l) = e {
|
||||
Some(l.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
if let Some(l) = l1 {
|
||||
self.step = ChamferStep::Second {
|
||||
h1: handle,
|
||||
l1: l,
|
||||
click1: click,
|
||||
};
|
||||
CmdResult::NeedPoint
|
||||
} else {
|
||||
CmdResult::NeedPoint
|
||||
{
|
||||
Some(EntityType::Line(l)) => {
|
||||
self.step = ChamferStep::Second {
|
||||
h1: handle,
|
||||
l1: l.clone(),
|
||||
click1: click,
|
||||
};
|
||||
}
|
||||
Some(EntityType::LwPolyline(p)) => {
|
||||
self.step = ChamferStep::SecondPoly {
|
||||
h1: handle,
|
||||
poly: p.clone(),
|
||||
click1: click,
|
||||
};
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
ChamferStep::SecondPoly { h1, poly, click1 } => {
|
||||
let h1 = *h1;
|
||||
let poly = poly.clone();
|
||||
let click1 = *click1;
|
||||
if handle != h1 {
|
||||
// Chamfer corners only within the same polyline.
|
||||
return CmdResult::NeedPoint;
|
||||
}
|
||||
match chamfer_lwpoly_corner(&poly, click1, self.dist1, click, self.dist2) {
|
||||
Some(new_poly) => CmdResult::ReplaceMany(vec![(h1, vec![new_poly])], vec![]),
|
||||
None => CmdResult::NeedPoint,
|
||||
}
|
||||
}
|
||||
ChamferStep::Second { h1, l1, click1 } => {
|
||||
@@ -1633,12 +1750,10 @@ impl CadCommand for ChamferCommand {
|
||||
.all_entities
|
||||
.iter()
|
||||
.find(|e| e.common().handle == handle)
|
||||
.and_then(|e| {
|
||||
if let EntityType::Line(l) = e {
|
||||
Some(line_pts(l))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
.and_then(|e| match e {
|
||||
EntityType::Line(l) => Some(line_pts(l)),
|
||||
EntityType::LwPolyline(p) => Some(lwpoly_seg_hover_pts(p, click)),
|
||||
_ => None,
|
||||
});
|
||||
if let Some(pts) = pts {
|
||||
vec![WireModel::solid(
|
||||
@@ -1693,6 +1808,22 @@ impl CadCommand for ChamferCommand {
|
||||
}
|
||||
vec![]
|
||||
}
|
||||
ChamferStep::SecondPoly { h1, poly, click1 } => {
|
||||
if handle != *h1 {
|
||||
return vec![];
|
||||
}
|
||||
if let Some(new_poly) =
|
||||
chamfer_lwpoly_corner(poly, *click1, self.dist1, click, self.dist2)
|
||||
{
|
||||
return vec![WireModel::solid(
|
||||
"chamfer_poly".into(),
|
||||
entity_pts(&new_poly),
|
||||
WireModel::CYAN,
|
||||
false,
|
||||
)];
|
||||
}
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user