feat: EXTEND and LENGTHEN now support LwPolyline
Both commands detect which terminal segment/vertex is nearest to the
pick point, compute the new endpoint along the segment direction, and
move only that terminal vertex — preserving all other vertices and bulge
values intact. ROADMAP updated: EXTEND and LENGTHEN marked ✅.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
083eb5cbb5
commit
5a682a365a
3 changed files with 192 additions and 10 deletions
|
|
@ -116,9 +116,9 @@ Underlay (PDF/DWF/DGN)
|
|||
| ARRAY Path | ✅ | — |
|
||||
| BREAK (BR) | ✅ | — |
|
||||
| TRIM (TR) | ✅ | — |
|
||||
| EXTEND (EX) | 🔧 | LwPolyline |
|
||||
| EXTEND (EX) | ✅ | — |
|
||||
| OFFSET (O) | ✅ | — |
|
||||
| LENGTHEN (LEN) | 🔧 | LwPolyline |
|
||||
| LENGTHEN (LEN) | ✅ | — |
|
||||
| FILLET (F) | ✅ | — |
|
||||
| CHAMFER (CHA) | ✅ | — |
|
||||
| JOIN (J) | ✅ | — |
|
||||
|
|
@ -302,7 +302,7 @@ Underlay (PDF/DWF/DGN)
|
|||
## Öncelik Sırası (Bir Sonraki Adımlar)
|
||||
|
||||
### Yüksek Öncelik
|
||||
1. **EXTEND / LENGTHEN → LwPolyline desteği**
|
||||
1. **Solid3D tessellation** tamamlama (ACIS → truck pipeline)
|
||||
|
||||
### Orta Öncelik
|
||||
5. **Solid3D tessellation** tamamlama (ACIS → truck pipeline)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
//
|
||||
// The entity is modified at whichever end is closest to the pick point.
|
||||
|
||||
use acadrust::entities::{Arc as ArcEnt, Ellipse as EllipseEnt, Line as LineEnt, Spline as SplineEnt};
|
||||
use acadrust::entities::{Arc as ArcEnt, Ellipse as EllipseEnt, Line as LineEnt, LwPolyline, Spline as SplineEnt};
|
||||
use truck_modeling::base::{BoundedCurve, Cut, ParametricCurve};
|
||||
use crate::modules::home::modify::spline_ops::{bspline_to_spline, spline_nearest_t, spline_to_bspline};
|
||||
use acadrust::types::Vector3;
|
||||
|
|
@ -98,10 +98,11 @@ pub enum LenMode {
|
|||
/// `pick_pt` determines which end to extend/trim (closest end is modified).
|
||||
pub fn lengthen_entity(entity: &EntityType, pick_pt: Vec3, mode: &LenMode) -> Option<EntityType> {
|
||||
match entity {
|
||||
EntityType::Line(l) => lengthen_line(l, pick_pt, mode),
|
||||
EntityType::Arc(a) => lengthen_arc(a, pick_pt, mode),
|
||||
EntityType::Ellipse(e) => lengthen_ellipse(e, pick_pt, mode),
|
||||
EntityType::Spline(s) => lengthen_spline(s, pick_pt, mode),
|
||||
EntityType::Line(l) => lengthen_line(l, pick_pt, mode),
|
||||
EntityType::Arc(a) => lengthen_arc(a, pick_pt, mode),
|
||||
EntityType::Ellipse(e) => lengthen_ellipse(e, pick_pt, mode),
|
||||
EntityType::Spline(s) => lengthen_spline(s, pick_pt, mode),
|
||||
EntityType::LwPolyline(p) => lengthen_lwpoly(p, pick_pt, mode),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
|
@ -266,6 +267,56 @@ fn xz_to_v3(v: Vec3, z: f64) -> Vector3 {
|
|||
Vector3::new(v.x as f64, v.y as f64, z)
|
||||
}
|
||||
|
||||
fn lengthen_lwpoly(poly: &LwPolyline, pick_pt: Vec3, mode: &LenMode) -> Option<EntityType> {
|
||||
let n = poly.vertices.len();
|
||||
if n < 2 { return None; }
|
||||
|
||||
// Determine which end is closer to the pick point (DXF XY: pick_pt.x, pick_pt.z).
|
||||
let px = pick_pt.x as f64;
|
||||
let py = pick_pt.z as f64; // Y-up: world Z = DXF Y
|
||||
|
||||
let first = &poly.vertices[0];
|
||||
let last = &poly.vertices[n - 1];
|
||||
let d_first = (first.location.x - px).hypot(first.location.y - py);
|
||||
let d_last = (last.location.x - px).hypot(last.location.y - py);
|
||||
let at_end = d_last <= d_first;
|
||||
|
||||
// Terminal segment direction and current length.
|
||||
let (sx, sy, ex, ey) = if at_end {
|
||||
(poly.vertices[n - 2].location.x, poly.vertices[n - 2].location.y,
|
||||
last.location.x, last.location.y)
|
||||
} else {
|
||||
(poly.vertices[1].location.x, poly.vertices[1].location.y,
|
||||
first.location.x, first.location.y)
|
||||
};
|
||||
|
||||
let dx = ex - sx;
|
||||
let dy = ey - sy;
|
||||
let current_len = (dx * dx + dy * dy).sqrt();
|
||||
if current_len < 1e-10 { return None; }
|
||||
|
||||
let new_len = apply_mode(current_len, mode)?;
|
||||
if new_len < 1e-10 { return None; }
|
||||
|
||||
let ux = dx / current_len;
|
||||
let uy = dy / current_len;
|
||||
let new_x = sx + ux * new_len;
|
||||
let new_y = sy + uy * new_len;
|
||||
|
||||
let mut new_poly = poly.clone();
|
||||
new_poly.common.handle = Handle::NULL;
|
||||
if at_end {
|
||||
let v = new_poly.vertices.last_mut()?;
|
||||
v.location.x = new_x;
|
||||
v.location.y = new_y;
|
||||
} else {
|
||||
let v = new_poly.vertices.first_mut()?;
|
||||
v.location.x = new_x;
|
||||
v.location.y = new_y;
|
||||
}
|
||||
Some(EntityType::LwPolyline(new_poly))
|
||||
}
|
||||
|
||||
fn lengthen_spline(spl: &SplineEnt, pick_pt: Vec3, mode: &LenMode) -> Option<EntityType> {
|
||||
let bs = spline_to_bspline(spl)?;
|
||||
let (t0, t1) = bs.range_tuple();
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
use std::f64::consts::TAU;
|
||||
|
||||
use acadrust::entities::{Arc as ArcEnt, Ellipse as EllipseEnt, Line as LineEnt, Ray as RayEnt, Spline as SplineEnt, XLine as XLineEnt};
|
||||
use acadrust::entities::{Arc as ArcEnt, Ellipse as EllipseEnt, Line as LineEnt, LwPolyline, Ray as RayEnt, Spline as SplineEnt, XLine as XLineEnt};
|
||||
use acadrust::types::Vector3;
|
||||
use acadrust::{EntityType, Handle};
|
||||
use glam::Vec3;
|
||||
|
|
@ -976,7 +976,116 @@ fn trim_arc(orig: &ArcEnt, ts: &[f64], t_click: f64) -> Vec<EntityType> {
|
|||
.collect()
|
||||
}
|
||||
|
||||
// ── Extend helper ─────────────────────────────────────────────────────────
|
||||
// ── Extend helpers ────────────────────────────────────────────────────────
|
||||
|
||||
/// Extend the first or last segment of an LwPolyline to the nearest boundary.
|
||||
/// Click point (DXF XY) determines which end to extend.
|
||||
fn extend_lwpoly(poly: &LwPolyline, click_x: f64, click_y: f64, geos: &[Geo]) -> Option<EntityType> {
|
||||
let n = poly.vertices.len();
|
||||
if n < 2 { return None; }
|
||||
|
||||
let first = &poly.vertices[0];
|
||||
let second = &poly.vertices[1];
|
||||
let last = &poly.vertices[n - 1];
|
||||
let prev = &poly.vertices[n - 2];
|
||||
|
||||
let d_first = (first.location.x - click_x).hypot(first.location.y - click_y);
|
||||
let d_last = (last.location.x - click_x).hypot(last.location.y - click_y);
|
||||
let extend_end = d_last <= d_first;
|
||||
|
||||
// Extract the terminal segment as a virtual line.
|
||||
let (ax, ay, bx, by) = if extend_end {
|
||||
(prev.location.x, prev.location.y, last.location.x, last.location.y)
|
||||
} else {
|
||||
(second.location.x, second.location.y, first.location.x, first.location.y)
|
||||
};
|
||||
|
||||
let (dx, dy) = (bx - ax, by - ay);
|
||||
let len2 = dx * dx + dy * dy;
|
||||
if len2 < 1e-12 { return None; }
|
||||
|
||||
// t_click on the segment: 0 = ax/ay, 1 = bx/by. We're extending beyond t=1.
|
||||
let target = poly.common.handle;
|
||||
let mut best_t = f64::INFINITY;
|
||||
|
||||
for geo in geos {
|
||||
match geo {
|
||||
Geo::Line { handle, p1, p2 } => {
|
||||
if *handle == target { continue; }
|
||||
let (ex, ey) = (p2[0] - p1[0], p2[1] - p1[1]);
|
||||
if let Some((t, u)) = ll(ax, ay, dx, dy, p1[0], p1[1], ex, ey) {
|
||||
if (-1e-9..=1.0 + 1e-9).contains(&u) && t > 1.0 + 1e-6 && t < best_t {
|
||||
best_t = t;
|
||||
}
|
||||
}
|
||||
}
|
||||
Geo::Arc { handle, cx, cy, r, a0, a1 } => {
|
||||
if *handle == target { continue; }
|
||||
for t in lc(ax, ay, dx, dy, *cx, *cy, *r) {
|
||||
let ix = ax + t * dx;
|
||||
let iy = ay + t * dy;
|
||||
if in_arc((iy - cy).atan2(ix - cx), *a0, *a1) && t > 1.0 + 1e-6 && t < best_t {
|
||||
best_t = t;
|
||||
}
|
||||
}
|
||||
}
|
||||
Geo::Circle { handle, cx, cy, r } => {
|
||||
if *handle == target { continue; }
|
||||
for t in lc(ax, ay, dx, dy, *cx, *cy, *r) {
|
||||
if t > 1.0 + 1e-6 && t < best_t { best_t = t; }
|
||||
}
|
||||
}
|
||||
Geo::Ray { handle, bx: rbx, by: rby, dx: rdx, dy: rdy } => {
|
||||
if *handle == target { continue; }
|
||||
if let Some((t, u)) = ll(ax, ay, dx, dy, *rbx, *rby, *rdx, *rdy) {
|
||||
if u >= -1e-9 && t > 1.0 + 1e-6 && t < best_t { best_t = t; }
|
||||
}
|
||||
}
|
||||
Geo::InfLine { handle, bx: ibx, by: iby, dx: idx, dy: idy } => {
|
||||
if *handle == target { continue; }
|
||||
if let Some((t, _)) = ll(ax, ay, dx, dy, *ibx, *iby, *idx, *idy) {
|
||||
if t > 1.0 + 1e-6 && t < best_t { best_t = t; }
|
||||
}
|
||||
}
|
||||
Geo::Ellipse { handle, cx, cy, a, b, nx, ny, t0, t1 } => {
|
||||
if *handle == target { continue; }
|
||||
for (t, t_ell) in le(ax, ay, dx, dy, *cx, *cy, *a, *b, *nx, *ny) {
|
||||
if in_arc(t_ell, *t0, *t1) && t > 1.0 + 1e-6 && t < best_t {
|
||||
best_t = t;
|
||||
}
|
||||
}
|
||||
}
|
||||
Geo::Spline { handle, segs } => {
|
||||
if *handle == target { continue; }
|
||||
for (p1, p2) in segs {
|
||||
let ex = p2[0] - p1[0]; let ey = p2[1] - p1[1];
|
||||
if let Some((t, u)) = ll(ax, ay, dx, dy, p1[0], p1[1], ex, ey) {
|
||||
if (-1e-9..=1.0+1e-9).contains(&u) && t > 1.0 + 1e-6 && t < best_t {
|
||||
best_t = t;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !best_t.is_finite() { return None; }
|
||||
|
||||
let new_x = ax + best_t * dx;
|
||||
let new_y = ay + best_t * dy;
|
||||
let mut new_poly = poly.clone();
|
||||
new_poly.common.handle = Handle::NULL;
|
||||
if extend_end {
|
||||
let last_v = new_poly.vertices.last_mut()?;
|
||||
last_v.location.x = new_x;
|
||||
last_v.location.y = new_y;
|
||||
} else {
|
||||
let first_v = new_poly.vertices.first_mut()?;
|
||||
first_v.location.x = new_x;
|
||||
first_v.location.y = new_y;
|
||||
}
|
||||
Some(EntityType::LwPolyline(new_poly))
|
||||
}
|
||||
|
||||
/// Extend a Line to the nearest boundary on the extended side.
|
||||
/// t_click < 0.5 → extend start (look for t < 0); t_click ≥ 0.5 → extend end (t > 1).
|
||||
|
|
@ -1268,6 +1377,19 @@ fn entity_pts(e: &EntityType) -> Vec<[f32; 3]> {
|
|||
ellipse_pts(e.center.x, e.center.y, a, b, nx, ny, t0, t1, e.center.z)
|
||||
}
|
||||
EntityType::Spline(s) => spline_pts_wire(s),
|
||||
EntityType::LwPolyline(p) => {
|
||||
let elev = p.elevation as f32;
|
||||
let n = p.vertices.len();
|
||||
let seg_count = if p.is_closed { n } else { n.saturating_sub(1) };
|
||||
let mut pts = Vec::with_capacity(seg_count * 2);
|
||||
for i in 0..seg_count {
|
||||
let v0 = &p.vertices[i];
|
||||
let v1 = &p.vertices[(i + 1) % n];
|
||||
pts.push([v0.location.x as f32, elev, v0.location.y as f32]);
|
||||
pts.push([v1.location.x as f32, elev, v1.location.y as f32]);
|
||||
}
|
||||
pts
|
||||
}
|
||||
// For preview, show a 20-unit section of semi-infinite results
|
||||
EntityType::Ray(r) => {
|
||||
let bx = r.base_point.x;
|
||||
|
|
@ -1724,6 +1846,9 @@ impl CadCommand for ExtendCommand {
|
|||
let _ = span;
|
||||
extend_ellipse(e, t_click, &self.geos)
|
||||
}
|
||||
Some(EntityType::LwPolyline(p)) => {
|
||||
extend_lwpoly(p, pt.x as f64, pt.y as f64, &self.geos)
|
||||
}
|
||||
Some(EntityType::Spline(s)) => {
|
||||
let t_click = spline_nearest_t(s, pt.x as f64, pt.y as f64)
|
||||
.and_then(|t_actual| {
|
||||
|
|
@ -1757,6 +1882,7 @@ impl CadCommand for ExtendCommand {
|
|||
EntityType::Line(l) => l.common.handle = new_handle,
|
||||
EntityType::Ellipse(e) => e.common.handle = new_handle,
|
||||
EntityType::Spline(s) => s.common.handle = new_handle,
|
||||
EntityType::LwPolyline(p) => p.common.handle = new_handle,
|
||||
_ => {}
|
||||
}
|
||||
if let Some(pos) = self
|
||||
|
|
@ -1810,6 +1936,11 @@ impl CadCommand for ExtendCommand {
|
|||
}
|
||||
}
|
||||
}
|
||||
Some(EntityType::LwPolyline(p)) => {
|
||||
if let Some(ext) = extend_lwpoly(p, pt.x as f64, pt.y as f64, &self.geos) {
|
||||
return vec![WireModel::solid("extend_prev".into(), entity_pts(&ext), WireModel::CYAN, false)];
|
||||
}
|
||||
}
|
||||
Some(EntityType::Spline(s)) => {
|
||||
let t_click = spline_nearest_t(s, pt.x as f64, pt.y as f64)
|
||||
.and_then(|t_actual| {
|
||||
|
|
|
|||
Loading…
Reference in a new issue