Projection keeps free-form geometry exact: tilted circles project to ellipses and tilted arcs to conics (rational quadratic arcs), other sketches' ellipses, splines and conics in parallel planes map exactly, and smooth edges that are neither lines nor arcs become fit splines instead of line chains
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
This commit is contained in:
1 parent
e4f7ff3997
commit
02de01f87c
4 files changed
+284
-5
No files matched your search
+135
-3
@@ -327,9 +327,9 @@ fn merge_split_circles(geom: Vec<LinkGeom>) -> Vec<LinkGeom> {
|
||||
let near = |p: Vec2, q: Vec2| p.dist(q) <= TOL * (1.0 + p.len().max(q.len()));
|
||||
let mut out = geom.clone();
|
||||
for i in 0..geom.len() {
|
||||
let Some(LinkGeom::Arc { c, a, b }) = geom.get(i).copied() else { continue };
|
||||
let Some(LinkGeom::Arc { c, a, b }) = geom.get(i).cloned() else { continue };
|
||||
for j in i + 1..geom.len() {
|
||||
if let Some(LinkGeom::Arc { c: c2, a: a2, b: b2 }) = geom.get(j).copied()
|
||||
if let Some(LinkGeom::Arc { c: c2, a: a2, b: b2 }) = geom.get(j).cloned()
|
||||
&& near(c, c2)
|
||||
&& near(a, a2)
|
||||
&& near(b, b2)
|
||||
@@ -365,14 +365,136 @@ fn project_sketch_curve(sk: &Sketch, from: &Plane, ci: usize, to: &Plane) -> Vec
|
||||
}
|
||||
_ => Vec::new(),
|
||||
},
|
||||
// Free-form curves in a parallel plane map exactly through their defining points.
|
||||
Some(CurveKind::Ellipse { c, m, r }) if parallel => match (sk.point(*c), sk.point(*m)) {
|
||||
(Some(c), Some(m)) => vec![LinkGeom::Ellipse { c: map(c), major: map(m), minor: *r }],
|
||||
_ => Vec::new(),
|
||||
},
|
||||
Some(CurveKind::Spline { pts, control, degree }) if parallel => {
|
||||
let p: Option<Vec<Vec2>> = pts.iter().map(|q| sk.point(*q).map(map)).collect();
|
||||
p.map(|pts| vec![LinkGeom::Spline { pts, control: *control, degree: *degree }]).unwrap_or_default()
|
||||
}
|
||||
Some(CurveKind::Conic { a, b, apex, rho }) if parallel => match (sk.point(*a), sk.point(*apex), sk.point(*b)) {
|
||||
(Some(a), Some(x), Some(b)) => vec![LinkGeom::Conic { a: map(a), apex: map(x), b: map(b), rho: *rho }],
|
||||
_ => Vec::new(),
|
||||
},
|
||||
Some(_) => {
|
||||
let pts: Vec<Vec3> = sk.segs(ci).iter().flat_map(|s| s.polyline(1e-3)).map(|p| from.to_world(p)).collect();
|
||||
let pts: Vec<Vec3> = sk.polyline(ci).iter().map(|p| from.to_world(*p)).collect();
|
||||
fit_world(to, &pts)
|
||||
}
|
||||
None => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// A circle in space (centre, unit normal, radius) through the polyline's points, and whether
|
||||
/// the polyline closes.
|
||||
fn circle3(pts: &[Vec3]) -> Option<(Vec3, Vec3, f64, bool)> {
|
||||
let n = pts.len();
|
||||
if n < 4 {
|
||||
return None;
|
||||
}
|
||||
let closed = pts.first()?.dist(*pts.last()?) <= 1e-9 * pts.first()?.len().max(1.0);
|
||||
let (a, b, c) = if closed { (*pts.first()?, *pts.get(n / 3)?, *pts.get(2 * n / 3)?) } else { (*pts.first()?, *pts.get(n / 2)?, *pts.last()?) };
|
||||
let (ab, ac) = (b - a, c - a);
|
||||
let nrm = ab.cross(ac);
|
||||
let l2 = nrm.len2();
|
||||
if l2 < 1e-24 {
|
||||
return None;
|
||||
}
|
||||
// Circumcentre of the triangle a, b, c.
|
||||
let centre = a + (nrm.cross(ab) * ac.len2() + ac.cross(nrm) * ab.len2()) / (2.0 * l2);
|
||||
let r = centre.dist(a);
|
||||
let un = nrm.normalized()?;
|
||||
let tol = 1e-7 * r.max(1.0);
|
||||
pts.iter().all(|p| (p.dist(centre) - r).abs() < tol && (*p - centre).dot(un).abs() < tol).then_some((centre, un, r, closed))
|
||||
}
|
||||
|
||||
/// A circle seen at a slant is an ellipse, exactly: a full circle becomes an ellipse; an arc
|
||||
/// becomes conics (rational quadratic Béziers of at most 90 degrees each — parallel projection
|
||||
/// keeps their weights).
|
||||
fn tilted_circle(plane: &Plane, pts: &[Vec3]) -> Option<Vec<LinkGeom>> {
|
||||
let (c, n, r, closed) = circle3(pts)?;
|
||||
let sn = plane.normal();
|
||||
let cosang = n.dot(sn).abs();
|
||||
if cosang > 1.0 - 1e-12 || cosang < 1e-9 {
|
||||
return None;
|
||||
}
|
||||
if closed {
|
||||
let u = n.cross(sn).normalized()?;
|
||||
return Some(vec![LinkGeom::Ellipse { c: plane.to_local(c), major: plane.to_local(c + u * r), minor: r * cosang }]);
|
||||
}
|
||||
// Sweep from the first point along the polyline's direction.
|
||||
let (p0, p1) = (*pts.first()?, *pts.last()?);
|
||||
let e1 = (p0 - c).normalized()?;
|
||||
let e2 = n.cross(e1);
|
||||
let ang = |p: Vec3| (p - c).dot(e2).atan2((p - c).dot(e1));
|
||||
let mut sweep = 0.0;
|
||||
for w in pts.windows(2) {
|
||||
let mut d = ang(w[1]) - ang(w[0]);
|
||||
while d > std::f64::consts::PI {
|
||||
d -= std::f64::consts::TAU;
|
||||
}
|
||||
while d < -std::f64::consts::PI {
|
||||
d += std::f64::consts::TAU;
|
||||
}
|
||||
sweep += d;
|
||||
}
|
||||
let _ = p1;
|
||||
let k = ((sweep.abs() / std::f64::consts::FRAC_PI_2).ceil() as usize).clamp(1, 8);
|
||||
let h = sweep / k as f64;
|
||||
let at = |t: f64| c + (e1 * t.cos() + e2 * t.sin()) * r;
|
||||
let w = (h.abs() / 2.0).cos();
|
||||
let rho = w / (1.0 + w);
|
||||
let mut out = Vec::new();
|
||||
for i in 0..k {
|
||||
let (t0, t1) = (h * i as f64, h * (i + 1) as f64);
|
||||
let apex = c + (e1 * ((t0 + t1) / 2.0).cos() + e2 * ((t0 + t1) / 2.0).sin()) * (r / w);
|
||||
out.push(LinkGeom::Conic { a: plane.to_local(at(t0)), apex: plane.to_local(apex), b: plane.to_local(at(t1)), rho });
|
||||
}
|
||||
Some(out)
|
||||
}
|
||||
|
||||
/// A smooth chain that fitting broke into many short lines is better as one fit spline (a
|
||||
/// projected spline edge or elliptical arc): when a spline through a few of its points stays
|
||||
/// within `tol` of all of them.
|
||||
fn as_spline(pts: &[Vec2], tol: f64) -> Option<LinkGeom> {
|
||||
let n = pts.len();
|
||||
if n < 4 {
|
||||
return None;
|
||||
}
|
||||
// No sharp corners.
|
||||
for w in pts.windows(3) {
|
||||
let (Some(u), Some(v)) = ((w[1] - w[0]).normalized(), (w[2] - w[1]).normalized()) else { continue };
|
||||
if u.dot(v) < 30f64.to_radians().cos() {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
for k in [8usize, 16, 32, 64] {
|
||||
let step = (n - 1) as f64 / k as f64;
|
||||
let mut fit: Vec<Vec2> = (0..=k).map(|i| pts[((i as f64 * step).round() as usize).min(n - 1)]).collect();
|
||||
fit.dedup_by(|a, b| a.dist(*b) < 1e-12);
|
||||
if fit.len() < 2 {
|
||||
return None;
|
||||
}
|
||||
let poly = solvecraft_sketch::spline_polyline(&fit, false, 3);
|
||||
let ok = pts.iter().all(|q| {
|
||||
poly.windows(2)
|
||||
.map(|w| {
|
||||
let (a, b) = (w[0], w[1]);
|
||||
let d = b - a;
|
||||
let t = if d.len2() > 0.0 { ((*q - a).dot(d) / d.len2()).clamp(0.0, 1.0) } else { 0.0 };
|
||||
q.dist(a + d * t)
|
||||
})
|
||||
.fold(f64::INFINITY, f64::min)
|
||||
<= tol
|
||||
});
|
||||
if ok {
|
||||
return Some(LinkGeom::Spline { pts: fit, control: false, degree: 3 });
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// The line where another plane crosses the sketch plane (None when parallel).
|
||||
fn plane_trace(sk: &Plane, other: &Plane) -> Option<LinkGeom> {
|
||||
let m = other.normal();
|
||||
@@ -389,9 +511,19 @@ fn plane_trace(sk: &Plane, other: &Plane) -> Option<LinkGeom> {
|
||||
|
||||
/// Project a world polyline and fit lines and arcs to it.
|
||||
fn fit_world(plane: &Plane, pts: &[Vec3]) -> Vec<LinkGeom> {
|
||||
if let Some(e) = tilted_circle(plane, pts) {
|
||||
return e;
|
||||
}
|
||||
let p2: Vec<Vec2> = pts.iter().map(|p| plane.to_local(*p)).collect();
|
||||
let mut out = Vec::new();
|
||||
fit_chain(&p2, None, &mut out);
|
||||
// Many little lines from one smooth edge: a spline instead.
|
||||
if out.len() > 4 && out.iter().all(|g| matches!(g, LinkGeom::Line(..))) {
|
||||
let ext = p2.iter().fold((f64::INFINITY, f64::NEG_INFINITY), |(lo, hi), p| (lo.min(p.x).min(p.y), hi.max(p.x).max(p.y)));
|
||||
if let Some(sp) = as_spline(&p2, (ext.1 - ext.0).abs().max(1e-9) * 1e-4) {
|
||||
return vec![sp];
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
|
||||
@@ -404,6 +404,10 @@ fn link_polyline(g: &solvecraft_sketch::LinkGeom) -> Vec<Vec2> {
|
||||
(0..=48).map(|i| c + Vec2::from_angle(s0 + sw * i as f64 / 48.0) * r).collect()
|
||||
}
|
||||
LinkGeom::Conic { a, apex, b, rho } => (0..=32).map(|i| solvecraft_sketch::conic_point(a, apex, b, rho, i as f64 / 32.0)).collect(),
|
||||
LinkGeom::Ellipse { c, major, minor } => {
|
||||
(0..=96).map(|i| solvecraft_sketch::ellipse_point(c, major, minor, i as f64 / 96.0 * std::f64::consts::TAU)).collect()
|
||||
}
|
||||
LinkGeom::Spline { ref pts, control, degree } => solvecraft_sketch::spline_polyline(pts, control, degree),
|
||||
LinkGeom::Point(_) => Vec::new(),
|
||||
}
|
||||
}
|
||||
@@ -298,3 +298,55 @@ fn isoparametric_curves_of_a_cylinder() {
|
||||
assert!(zs.iter().cloned().fold(f64::MIN, f64::max) > 19.9 && zs.iter().cloned().fold(f64::MAX, f64::min) < 0.1, "{zs:?}");
|
||||
assert!(w.pts.iter().all(|p| (p.x - 6.0).abs() < 1e-3 && p.y.abs() < 1e-3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tilted_circles_project_to_exact_ellipses_and_splines_stay_splines() {
|
||||
let mut s = Session::default();
|
||||
run(&mut s, "PrimitiveCylinder", json!({"base": [0, 0, 0], "axis": [1, 0, 1], "radius": 5, "height": 20}));
|
||||
run(&mut s, "SketchCreate", json!({"plane": "XY", "offset": -10}));
|
||||
// The base circle (centred at the origin) seen from above.
|
||||
let r = run(&mut s, "ProjectNewCmd", json!({"refs": [{"edge": [0, 5, 0]}]}));
|
||||
// One seam half: exact conics (rational quadratic arcs).
|
||||
let si = inspect(&mut s);
|
||||
let c = si["curves"].as_array().unwrap().iter().find(|c| c["id"] == r["links"][0]["curves"][0]).unwrap().clone();
|
||||
assert_eq!(c["type"], "conic", "{c}");
|
||||
let st = s.model.state();
|
||||
let sk = &st.sketch(s.active_sketch.unwrap()).unwrap().sketch;
|
||||
for ci in sk.link_curves(r["links"][0]["link"].as_str().unwrap()) {
|
||||
for q in sk.polyline(ci) {
|
||||
// On the ellipse x²/(5/√2)² + y²/5² = 1.
|
||||
let e = (q.x / (5.0 * std::f64::consts::FRAC_1_SQRT_2)).powi(2) + (q.y / 5.0).powi(2);
|
||||
assert!((e - 1.0).abs() < 1e-9, "{q:?}");
|
||||
}
|
||||
}
|
||||
// The whole face (both halves of its rim, plus its seam) gives the full ellipse.
|
||||
let r = run(&mut s, "ProjectNewCmd", json!({"refs": [{"face": [0, 0, 0]}]}));
|
||||
let si = inspect(&mut s);
|
||||
let types: Vec<String> = r["links"][0]["curves"]
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|id| si["curves"].as_array().unwrap().iter().find(|c| c["id"] == *id).unwrap()["type"].as_str().unwrap().to_string())
|
||||
.collect();
|
||||
assert!(types.iter().all(|t| t == "conic" || t == "ellipse"), "{types:?}");
|
||||
let r = run(&mut s, "ProjectNewCmd", json!({"refs": [{"edge": [0, -5, 0]}]}));
|
||||
let si = inspect(&mut s);
|
||||
let c = si["curves"].as_array().unwrap().iter().find(|c| c["id"] == r["links"][0]["curves"][0]).unwrap().clone();
|
||||
if c["type"] == "ellipse" {
|
||||
assert!((c["minor_radius"].as_f64().unwrap() - 5.0 * std::f64::consts::FRAC_1_SQRT_2).abs() < 1e-6, "{c}");
|
||||
let major = &c["major_at"];
|
||||
assert!(((major[0].as_f64().unwrap().powi(2) + major[1].as_f64().unwrap().powi(2)).sqrt() - 5.0).abs() < 1e-6, "{c}");
|
||||
}
|
||||
run(&mut s, "SketchStop", json!({}));
|
||||
// A spline of a parallel sketch projects as the same spline.
|
||||
run(&mut s, "SketchCreate", json!({"plane": "XY", "offset": 30}));
|
||||
let sp = run(&mut s, "DrawSpline", json!({"points": [[0, 0], [10, 5], [20, 0]]}))["curves"][0].clone();
|
||||
run(&mut s, "SketchStop", json!({}));
|
||||
let sk1 = s.doc.features.last().unwrap().id;
|
||||
run(&mut s, "SketchCreate", json!({"plane": "XY"}));
|
||||
let r = run(&mut s, "ProjectNewCmd", json!({"refs": [{"sketch": sk1, "curve": sp}]}));
|
||||
let si = inspect(&mut s);
|
||||
let c = si["curves"].as_array().unwrap().iter().find(|c| c["id"] == r["links"][0]["curves"][0]).unwrap().clone();
|
||||
assert_eq!(c["type"], "spline", "{c}");
|
||||
assert_eq!(c["points"].as_array().unwrap().len(), 3);
|
||||
}
|
||||
@@ -110,7 +110,7 @@ pub struct Link {
|
||||
}
|
||||
|
||||
/// Geometry a link produces, in sketch coordinates.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum LinkGeom {
|
||||
Point(Vec2),
|
||||
Line(Vec2, Vec2),
|
||||
@@ -128,6 +128,18 @@ pub enum LinkGeom {
|
||||
b: Vec2,
|
||||
rho: f64,
|
||||
},
|
||||
/// Full ellipse: centre, end of the major axis, minor radius.
|
||||
Ellipse {
|
||||
c: Vec2,
|
||||
major: Vec2,
|
||||
minor: f64,
|
||||
},
|
||||
/// Spline through (`control == false`) or over (`control`) the points.
|
||||
Spline {
|
||||
pts: Vec<Vec2>,
|
||||
control: bool,
|
||||
degree: u8,
|
||||
},
|
||||
}
|
||||
|
||||
impl LinkGeom {
|
||||
@@ -139,6 +151,12 @@ impl LinkGeom {
|
||||
LinkGeom::Circle(c, r) => ok(c) && r.is_finite() && *r > 1e-9 && *r < 1e9,
|
||||
LinkGeom::Arc { c, a, b } => ok(c) && ok(a) && ok(b) && c.dist(*a) > 1e-9 && a.dist(*b) > MERGE_TOL,
|
||||
LinkGeom::Conic { a, apex, b, rho } => ok(a) && ok(apex) && ok(b) && a.dist(*b) > MERGE_TOL && *rho > 1e-6 && *rho < 1.0 - 1e-6,
|
||||
LinkGeom::Ellipse { c, major, minor } => {
|
||||
ok(c) && ok(major) && c.dist(*major) > 1e-9 && minor.is_finite() && *minor > 1e-9 && *minor < 1e9
|
||||
}
|
||||
LinkGeom::Spline { pts, degree, .. } => {
|
||||
(2..=crate::curves::MAX_SPLINE_POINTS).contains(&pts.len()) && pts.iter().all(ok) && (1..=7).contains(degree)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -157,6 +175,10 @@ enum LCurve {
|
||||
Circle(usize, f64),
|
||||
Arc(usize, usize, usize),
|
||||
Conic(usize, usize, usize, f64),
|
||||
Ellipse(usize, usize, f64),
|
||||
/// Start, end, first interior point and interior count (interior points are consecutive
|
||||
/// and never shared), control flag, degree.
|
||||
Spline(usize, usize, usize, usize, bool, u8),
|
||||
}
|
||||
|
||||
fn layout(geom: &[LinkGeom]) -> Result<Layout> {
|
||||
@@ -172,7 +194,7 @@ fn layout(geom: &[LinkGeom]) -> Result<Layout> {
|
||||
pts.len() - 1
|
||||
}
|
||||
for g in geom.iter().filter(|g| g.finite()) {
|
||||
match *g {
|
||||
match g.clone() {
|
||||
LinkGeom::Point(p) => {
|
||||
let i = pt(&mut l.pts, p);
|
||||
if !l.lone.contains(&i) {
|
||||
@@ -203,6 +225,21 @@ fn layout(geom: &[LinkGeom]) -> Result<Layout> {
|
||||
l.curves.push(LCurve::Conic(a, b, x, rho));
|
||||
}
|
||||
}
|
||||
LinkGeom::Ellipse { c, major, minor } => {
|
||||
let (c, m) = (pt(&mut l.pts, c), pt(&mut l.pts, major));
|
||||
if c != m {
|
||||
l.curves.push(LCurve::Ellipse(c, m, minor));
|
||||
}
|
||||
}
|
||||
LinkGeom::Spline { pts, control, degree } => {
|
||||
let (Some(first), Some(last)) = (pts.first(), pts.last()) else { continue };
|
||||
let s0 = pt(&mut l.pts, *first);
|
||||
let first_in = l.pts.len();
|
||||
let inner = pts.get(1..pts.len() - 1).unwrap_or_default();
|
||||
l.pts.extend(inner.iter().copied());
|
||||
let s1 = pt(&mut l.pts, *last);
|
||||
l.curves.push(LCurve::Spline(s0, s1, first_in, inner.len(), control, degree));
|
||||
}
|
||||
}
|
||||
}
|
||||
// Lone points that a curve also uses are not lone.
|
||||
@@ -212,6 +249,8 @@ fn layout(geom: &[LinkGeom]) -> Result<Layout> {
|
||||
LCurve::Circle(c, _) => c == i,
|
||||
LCurve::Arc(c, a, b) => c == i || a == i || b == i,
|
||||
LCurve::Conic(a, b, x, _) => a == i || b == i || x == i,
|
||||
LCurve::Ellipse(c, m, _) => c == i || m == i,
|
||||
LCurve::Spline(a, b, f, n, ..) => a == i || b == i || (i >= f && i < f + n),
|
||||
})
|
||||
};
|
||||
let curves = l.curves.clone();
|
||||
@@ -226,6 +265,8 @@ fn same_shape(a: &LCurve, b: &LCurve) -> bool {
|
||||
| (LCurve::Circle(..), LCurve::Circle(..))
|
||||
| (LCurve::Arc(..), LCurve::Arc(..))
|
||||
| (LCurve::Conic(..), LCurve::Conic(..))
|
||||
| (LCurve::Ellipse(..), LCurve::Ellipse(..))
|
||||
| (LCurve::Spline(..), LCurve::Spline(..))
|
||||
)
|
||||
}
|
||||
|
||||
@@ -248,6 +289,8 @@ fn same_geometry(a: &[(LCurve, Vec<Vec2>)], b: &[(LCurve, Vec<Vec2>)]) -> bool {
|
||||
(LCurve::Circle(_, ra), LCurve::Circle(_, rb)) => all() && close(*ra, *rb),
|
||||
(LCurve::Arc(..), LCurve::Arc(..)) => all(),
|
||||
(LCurve::Conic(.., ra), LCurve::Conic(.., rb)) => all() && close(*ra, *rb),
|
||||
(LCurve::Ellipse(.., ra), LCurve::Ellipse(.., rb)) => all() && close(*ra, *rb),
|
||||
(LCurve::Spline(.., ca, da), LCurve::Spline(.., cb, db)) => ca == cb && da == db && all(),
|
||||
_ => false,
|
||||
}
|
||||
};
|
||||
@@ -274,6 +317,13 @@ fn curve_points(c: &LCurve) -> Vec<usize> {
|
||||
LCurve::Circle(c, _) => vec![c],
|
||||
LCurve::Arc(c, a, b) => vec![c, a, b],
|
||||
LCurve::Conic(a, b, x, _) => vec![a, b, x],
|
||||
LCurve::Ellipse(c, m, _) => vec![c, m],
|
||||
LCurve::Spline(a, b, f, n, ..) => {
|
||||
let mut v = vec![a];
|
||||
v.extend(f..f + n);
|
||||
v.push(b);
|
||||
v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -346,6 +396,15 @@ impl Sketch {
|
||||
LCurve::Circle(c, r) => (CurveKind::Circle { c: point(self, c)?, r }, "c"),
|
||||
LCurve::Arc(c, a, b) => (CurveKind::Arc { c: point(self, c)?, a: point(self, a)?, b: point(self, b)? }, "a"),
|
||||
LCurve::Conic(a, b, x, rho) => (CurveKind::Conic { a: point(self, a)?, b: point(self, b)?, apex: point(self, x)?, rho }, "k"),
|
||||
LCurve::Ellipse(c, m, r) => (CurveKind::Ellipse { c: point(self, c)?, m: point(self, m)?, r }, "e"),
|
||||
LCurve::Spline(a, b, f, n, control, degree) => {
|
||||
let mut pts = vec![point(self, a)?];
|
||||
for i in f..f + n {
|
||||
pts.push(point(self, i)?);
|
||||
}
|
||||
pts.push(point(self, b)?);
|
||||
(CurveKind::Spline { pts, control, degree }, "s")
|
||||
}
|
||||
};
|
||||
let cid = self.fresh(prefix);
|
||||
self.curves.push(Curve { id: cid, kind, construction: false, reversed: false, link: Some(id.to_string()), centerline: false });
|
||||
@@ -376,6 +435,18 @@ impl Sketch {
|
||||
Some(CurveKind::Circle { c, r }) => Some(LCurve::Circle(local(*c)?, *r)),
|
||||
Some(CurveKind::Arc { c, a, b }) => Some(LCurve::Arc(local(*c)?, local(*a)?, local(*b)?)),
|
||||
Some(CurveKind::Conic { a, b, apex, rho }) => Some(LCurve::Conic(local(*a)?, local(*b)?, local(*apex)?, *rho)),
|
||||
Some(CurveKind::Ellipse { c, m, r }) => Some(LCurve::Ellipse(local(*c)?, local(*m)?, *r)),
|
||||
Some(CurveKind::Spline { pts, control, degree }) => {
|
||||
let (a, b) = (local(*pts.first()?)?, local(*pts.last()?)?);
|
||||
let inner: Vec<usize> =
|
||||
pts.get(1..pts.len().saturating_sub(1)).unwrap_or_default().iter().map(|q| local(*q)).collect::<Option<_>>()?;
|
||||
let f = inner.first().copied().unwrap_or(0);
|
||||
// Interior points were made one after another.
|
||||
if inner.iter().enumerate().any(|(k, x)| *x != f + k) {
|
||||
return None;
|
||||
}
|
||||
Some(LCurve::Spline(a, b, f, inner.len(), *control, *degree))
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
@@ -404,6 +475,17 @@ impl Sketch {
|
||||
visit(b);
|
||||
visit(x);
|
||||
}
|
||||
LCurve::Ellipse(c, m, _) => {
|
||||
visit(c);
|
||||
visit(m);
|
||||
}
|
||||
LCurve::Spline(a, b, f, n, ..) => {
|
||||
visit(a);
|
||||
for i in f..f + n {
|
||||
visit(i);
|
||||
}
|
||||
visit(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
for i in &lay.lone {
|
||||
@@ -418,6 +500,8 @@ impl Sketch {
|
||||
LCurve::Circle(c, r) => LCurve::Circle(renum(c), r),
|
||||
LCurve::Arc(c, a, b) => LCurve::Arc(renum(c), renum(a), renum(b)),
|
||||
LCurve::Conic(a, b, x, r) => LCurve::Conic(renum(a), renum(b), renum(x), r),
|
||||
LCurve::Ellipse(c, m, r) => LCurve::Ellipse(renum(c), renum(m), r),
|
||||
LCurve::Spline(a, b, f, n, ct, d) => LCurve::Spline(renum(a), renum(b), renum(f), n, ct, d),
|
||||
})
|
||||
.collect();
|
||||
let same = match &cur {
|
||||
@@ -431,6 +515,10 @@ impl Sketch {
|
||||
(LCurve::Circle(a0, _), LCurve::Circle(b0, _)) => a0 == b0,
|
||||
(LCurve::Arc(a0, a1, a2), LCurve::Arc(b0, b1, b2)) => a0 == b0 && a1 == b1 && a2 == b2,
|
||||
(LCurve::Conic(a0, a1, a2, _), LCurve::Conic(b0, b1, b2, _)) => a0 == b0 && a1 == b1 && a2 == b2,
|
||||
(LCurve::Ellipse(a0, a1, _), LCurve::Ellipse(b0, b1, _)) => a0 == b0 && a1 == b1,
|
||||
(LCurve::Spline(a0, a1, a2, a3, a4, a5), LCurve::Spline(b0, b1, b2, b3, b4, b5)) => {
|
||||
a0 == b0 && a1 == b1 && (a2 == b2 || *a3 == 0) && a3 == b3 && a4 == b4 && a5 == b5
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
})
|
||||
@@ -454,6 +542,9 @@ impl Sketch {
|
||||
if let (LCurve::Conic(.., r), Some(Curve { kind: CurveKind::Conic { rho, .. }, .. })) = (c, self.curves.get_mut(*ci)) {
|
||||
*rho = *r;
|
||||
}
|
||||
if let (LCurve::Ellipse(.., r), Some(Curve { kind: CurveKind::Ellipse { r: slot, .. }, .. })) = (c, self.curves.get_mut(*ci)) {
|
||||
*slot = *r;
|
||||
}
|
||||
}
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user