Exact free-form profiles: geom Seg2 gains cubic Béziers and conics (rational quadratics: exact length, area, split, tangents); sketch ellipses, conics, text, fit splines and control splines up to degree 3 enter profiles as exact segments wherever a loop uses a whole segment, and the kernel builds them as B-spline / NURBS edges (an extruded ellipse is three faces with the exact volume)
Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
This commit is contained in:
1 parent
8b4ec13d3b
commit
31030ed008
13 files changed
+447
-31
No files matched your search
+11
-11
@@ -41,6 +41,9 @@ pub(crate) fn wire(plane: &Plane, lp: &Loop2) -> Result<mt::Wire> {
|
||||
builder::circle_arc(a, b, p3(plane.to_world(s.mid())))
|
||||
}
|
||||
}
|
||||
Seg2::Cubic { .. } | Seg2::Conic { .. } => {
|
||||
crate::freeform::edge(plane, s, a, b).ok_or_else(|| KernelError::Invalid("profile curve".into()))?
|
||||
}
|
||||
};
|
||||
edges.push(e);
|
||||
}
|
||||
@@ -66,6 +69,8 @@ fn region_ok(r: &Region2) -> Result<()> {
|
||||
Seg2::Arc { center, radius, start, sweep } => {
|
||||
center.is_finite() && radius.is_finite() && radius > 1e-9 && start.is_finite() && sweep.is_finite()
|
||||
}
|
||||
Seg2::Cubic { p0, p1, p2, p3 } => p0.is_finite() && p1.is_finite() && p2.is_finite() && p3.is_finite(),
|
||||
Seg2::Conic { a, apex, b, w } => a.is_finite() && apex.is_finite() && b.is_finite() && w.is_finite() && w > 1e-9,
|
||||
};
|
||||
if !ok {
|
||||
return Err(KernelError::Invalid("non-finite profile".into()));
|
||||
@@ -234,7 +239,7 @@ fn offset_loop(lp: &Loop2, d: f64) -> Result<Loop2> {
|
||||
Err(KernelError::Invalid("the taper closes the profile".into()))
|
||||
}
|
||||
}
|
||||
Seg2::Line { .. } => Err(KernelError::Failed("not supported yet: tapered profiles mixing lines and arcs".into())),
|
||||
_ => Err(KernelError::Failed("not supported yet: tapered profiles mixing lines and arcs".into())),
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
return Ok(Loop2 { segs });
|
||||
@@ -377,6 +382,10 @@ fn revolve_touching_axis(plane: &Plane, lp: &Loop2, axis_origin: Vec2, d: Vec2)
|
||||
edges.push(match *s {
|
||||
Seg2::Line { .. } => builder::line(a, b),
|
||||
Seg2::Arc { .. } => builder::circle_arc(a, b, p3(plane.to_world(s.mid()))),
|
||||
Seg2::Cubic { .. } | Seg2::Conic { .. } => match crate::freeform::edge(plane, s, a, b) {
|
||||
Some(e) => e,
|
||||
None => builder::line(a, b),
|
||||
},
|
||||
});
|
||||
}
|
||||
let mut w: mt::Wire = edges.into();
|
||||
@@ -459,16 +468,7 @@ fn resample(lp: &Loop2, n: usize, angles: &[f64]) -> Loop2 {
|
||||
while segs.len() < n {
|
||||
let Some((k, _)) = segs.iter().enumerate().max_by(|a, b| a.1.length().total_cmp(&b.1.length())) else { break };
|
||||
let s = segs.remove(k);
|
||||
let (p, q) = match s {
|
||||
Seg2::Line { a, b } => {
|
||||
let m = a.lerp(b, 0.5);
|
||||
(Seg2::Line { a, b: m }, Seg2::Line { a: m, b })
|
||||
}
|
||||
Seg2::Arc { center, radius, start, sweep } => (
|
||||
Seg2::Arc { center, radius, start, sweep: sweep / 2.0 },
|
||||
Seg2::Arc { center, radius, start: start + sweep / 2.0, sweep: sweep / 2.0 },
|
||||
),
|
||||
};
|
||||
let (p, q) = s.split_half();
|
||||
segs.insert(k, q);
|
||||
segs.insert(k, p);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
//! Free-form profile edges: cubic Béziers as B-spline curves and conics as rational quadratic
|
||||
//! NURBS curves (exact ellipses, parabolas and hyperbolas from sketches).
|
||||
|
||||
use solvecraft_geom::{Plane, Seg2};
|
||||
use truck_modeling as mt;
|
||||
|
||||
use crate::body::p3;
|
||||
|
||||
/// The edge for a free-form segment between two existing vertices (None for lines and arcs).
|
||||
pub(crate) fn edge(plane: &Plane, s: &Seg2, a: &mt::Vertex, b: &mt::Vertex) -> Option<mt::Edge> {
|
||||
match *s {
|
||||
Seg2::Cubic { p1, p2, .. } => Some(mt::builder::bezier(a, b, vec![p3(plane.to_world(p1)), p3(plane.to_world(p2))])),
|
||||
Seg2::Conic { apex, w, .. } => {
|
||||
let (pa, pb, px) = (a.point(), b.point(), p3(plane.to_world(apex)));
|
||||
let ctrl = vec![
|
||||
mt::Vector4::new(pa.x, pa.y, pa.z, 1.0),
|
||||
mt::Vector4::new(px.x * w, px.y * w, px.z * w, w),
|
||||
mt::Vector4::new(pb.x, pb.y, pb.z, 1.0),
|
||||
];
|
||||
let curve = mt::NurbsCurve::new(mt::BSplineCurve::new(mt::KnotVec::bezier_knot(2), ctrl));
|
||||
Some(mt::Edge::new(a, b, mt::Curve::NurbsCurve(curve)))
|
||||
}
|
||||
Seg2::Line { .. } | Seg2::Arc { .. } => None,
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ mod blend;
|
||||
mod body;
|
||||
mod build;
|
||||
mod coplanar;
|
||||
mod freeform;
|
||||
mod heal;
|
||||
mod helix;
|
||||
mod loopblend;
|
||||
|
||||
Reference in new issue
Block a user