feat: drag a fillet's radius spline by its handles on the edge (#752)
A spline fillet's radius can now be dragged by handles on the edge, and the spline radius saves under format 17.
This commit is contained in:
1 parent
159f9913c1
commit
5267c96ab2
13 files changed
+414
-31
No files matched your search
@@ -227,7 +227,7 @@ impl FeatureKind {
|
||||
}
|
||||
FeatureKind::Fillet { edges, style, .. } => {
|
||||
edges.iter_mut().for_each(pt);
|
||||
if let crate::FilletStyle::Variable { start, .. } = style {
|
||||
if let crate::FilletStyle::Variable { start, .. } | crate::FilletStyle::Spline { start, .. } = style {
|
||||
pt(start);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -994,6 +994,10 @@ pub enum FilletStyle {
|
||||
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
||||
smooth: bool,
|
||||
},
|
||||
/// The radius along each edge as a clamped B-spline of the position from the end nearest
|
||||
/// `start` (0) to the other (1): `radius` is its first control radius and `radii` the rest,
|
||||
/// its degree what `knots` leave (knots = control radii + degree + 1).
|
||||
Spline { start: Vec3, knots: Vec<f64>, radii: Vec<String> },
|
||||
}
|
||||
|
||||
/// A fillet radius too large for the geometry around its edges: refused (`Exact`, the default),
|
||||
@@ -1019,6 +1023,13 @@ impl FilletStyle {
|
||||
pub fn is_constant(&self) -> bool {
|
||||
*self == FilletStyle::Constant
|
||||
}
|
||||
|
||||
/// The spline of a [`FilletStyle::Spline`] with its control radii `values` (the first is
|
||||
/// the feature's `radius`).
|
||||
pub fn spline(knots: &[f64], values: Vec<f64>) -> solidcraft_kernel::RadiusSpline {
|
||||
let degree = knots.len().saturating_sub(values.len() + 1);
|
||||
solidcraft_kernel::RadiusSpline { degree, knots: knots.to_vec(), radii: values }
|
||||
}
|
||||
}
|
||||
|
||||
/// A face picked by a point on it, on a named body.
|
||||
@@ -1514,9 +1525,13 @@ impl FeatureKind {
|
||||
}
|
||||
FeatureKind::Fillet { radius, style, .. } => {
|
||||
v.push(radius);
|
||||
if let FilletStyle::Variable { radius2, mid, .. } = style {
|
||||
v.push(radius2);
|
||||
v.extend(mid.iter().map(|(_, r)| r.as_str()));
|
||||
match style {
|
||||
FilletStyle::Variable { radius2, mid, .. } => {
|
||||
v.push(radius2);
|
||||
v.extend(mid.iter().map(|(_, r)| r.as_str()));
|
||||
}
|
||||
FilletStyle::Spline { radii, .. } => v.extend(radii.iter().map(String::as_str)),
|
||||
FilletStyle::Constant | FilletStyle::Chord => {}
|
||||
}
|
||||
}
|
||||
FeatureKind::Chamfer { distance, distance2, angle, .. } => {
|
||||
|
||||
+45
-10
@@ -307,6 +307,21 @@ fn tapered_note(tapers: &[Option<kernel::Taper>]) -> Option<String> {
|
||||
Some(format!("the radius tapers to the room on {n} of {} edges, down to {} mm", tapers.len(), show_mm(low)))
|
||||
}
|
||||
|
||||
/// Blend `edges` one at a time with `blend`, each blend's lineage over the one before: the
|
||||
/// result and the lineage of what the last blend started from.
|
||||
fn one_by_one(input: Traced, edges: &[Vec3], blend: impl Fn(&kernel::Body, Vec3) -> kernel::Result<kernel::Body>) -> Result<(kernel::Body, Traced)> {
|
||||
let mut b = input;
|
||||
let mut from = None;
|
||||
for p in edges {
|
||||
let next = Traced::step(blend(&b.body, *p)?, &[&b]);
|
||||
from = Some(std::mem::replace(&mut b, next));
|
||||
}
|
||||
match from {
|
||||
Some(prev) => Ok((b.body, prev)),
|
||||
None => Err(DocError::Invalid("no edges to fillet".into())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Add `note` to a feature's warning.
|
||||
fn append_warning(warning: &mut Option<String>, note: String) {
|
||||
*warning = Some(match warning.take() {
|
||||
@@ -333,6 +348,11 @@ impl ModelState {
|
||||
self.fitted.get(&feature)?.iter().fold(None, |acc, r| Some(acc.map_or((*r, *r), |(lo, hi): (f64, f64)| (lo.min(*r), hi.max(*r)))))
|
||||
}
|
||||
|
||||
/// Each edge fillet `feature` tapered (a point on it) with the radius spline it was given.
|
||||
pub fn tapered(&self, feature: u64) -> Option<&[(Vec3, kernel::Taper)]> {
|
||||
self.tapered.get(&feature).map(Vec::as_slice)
|
||||
}
|
||||
|
||||
/// Each edge fillet `feature` blended (a point on it) with the radius it used, when it
|
||||
/// reduced a radius too large for some of its edges.
|
||||
pub fn fitted_edges(&self, feature: u64) -> Option<Vec<(Vec3, f64)>> {
|
||||
@@ -364,6 +384,9 @@ pub struct ModelState {
|
||||
/// The radius per edge a fillet used when it reduced a radius too large for some of its edges
|
||||
/// (by feature id, see `FilletFit::Reduce`).
|
||||
fitted: BTreeMap<u64, Vec<f64>>,
|
||||
/// The radius spline of each edge a fillet tapered (a point on the edge with it, by feature
|
||||
/// id, see `FilletFit::Taper`).
|
||||
tapered: BTreeMap<u64, Vec<(Vec3, kernel::Taper)>>,
|
||||
}
|
||||
|
||||
impl ModelState {
|
||||
@@ -395,6 +418,16 @@ impl ModelState {
|
||||
r.to_bits().hash(&mut h);
|
||||
}
|
||||
}
|
||||
for (feature, tapers) in &self.tapered {
|
||||
feature.hash(&mut h);
|
||||
for (p, t) in tapers {
|
||||
let s = &t.spline;
|
||||
for x in [p.x, p.y, p.z, t.start.x, t.start.y, t.start.z].iter().chain(&s.knots).chain(&s.radii) {
|
||||
x.to_bits().hash(&mut h);
|
||||
}
|
||||
s.degree.hash(&mut h);
|
||||
}
|
||||
}
|
||||
serde_json::to_string(&self.threads).unwrap_or_default().hash(&mut h);
|
||||
serde_json::to_string(&self.sheets).unwrap_or_default().hash(&mut h);
|
||||
h.finish()
|
||||
@@ -3882,23 +3915,25 @@ fn eval_feature(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st:
|
||||
for (t, e) in mid {
|
||||
pts.push((*t, val(vals, e, Kind::Length)?));
|
||||
}
|
||||
// One edge at a time: each blend's lineage over the one before.
|
||||
let mut b = input;
|
||||
let mut from = None;
|
||||
for p in &edges {
|
||||
let next = Traced::step(kernel::fillet_variable_points(&b.body, *p, *start, &pts, *smooth)?, &[&b]);
|
||||
from = Some(std::mem::replace(&mut b, next));
|
||||
}
|
||||
match from {
|
||||
Some(prev) => (b.body, prev),
|
||||
None => return Err(DocError::Invalid("no edges to fillet".into())),
|
||||
one_by_one(input, &edges, |b, p| kernel::fillet_variable_points(b, p, *start, &pts, *smooth))?
|
||||
}
|
||||
FeatureKind::Fillet { style: crate::FilletStyle::Spline { start, knots, radii }, .. } => {
|
||||
let mut values = vec![r];
|
||||
for e in radii {
|
||||
values.push(val(vals, e, Kind::Length)?);
|
||||
}
|
||||
let law = crate::FilletStyle::spline(knots, values);
|
||||
one_by_one(input, &edges, |b, p| kernel::fillet_variable_spline(b, p, *start, &law))?
|
||||
}
|
||||
FeatureKind::Fillet { fit: crate::FilletFit::Taper, .. } => {
|
||||
let (b, tapers) = kernel::fillet_tapered(&mb.body, &edges, r)?;
|
||||
if let Some(note) = tapered_note(&tapers) {
|
||||
append_warning(warning, note);
|
||||
}
|
||||
let tapered: Vec<(Vec3, kernel::Taper)> = edges.iter().copied().zip(tapers).filter_map(|(p, t)| Some((p, t?))).collect();
|
||||
if !tapered.is_empty() {
|
||||
st.tapered.insert(f.id, tapered);
|
||||
}
|
||||
(b, input)
|
||||
}
|
||||
FeatureKind::Fillet { fit, .. } => {
|
||||
|
||||
@@ -91,6 +91,9 @@
|
||||
//! edited.
|
||||
//! A fillet's `fit` can be `taper` (the radius follows the room along each edge it doesn't
|
||||
//! fit). Fillets with `exact` or `reduce` save as before; 0.8.0 would refuse the unknown fit.
|
||||
//! A fillet's `type` can be `spline` (the radius along each edge a B-spline: `start`, `knots`,
|
||||
//! and the control radii after `radius` in `radii`). Other fillets save as before; 0.8.0 would
|
||||
//! refuse the unknown type.
|
||||
|
||||
use serde_json::Value;
|
||||
|
||||
|
||||
@@ -83,6 +83,11 @@ impl FeatureKind {
|
||||
v.push(("End radius", radius2, L));
|
||||
v.extend(mid.iter_mut().map(|(_, r)| ("Mid radius", r, L)));
|
||||
}
|
||||
crate::FilletStyle::Spline { radii, .. } => {
|
||||
v.push(("Start radius", radius, L));
|
||||
let n = radii.len();
|
||||
v.extend(radii.iter_mut().enumerate().map(|(i, r)| (if i + 1 == n { "End radius" } else { "Control radius" }, r, L)));
|
||||
}
|
||||
},
|
||||
FeatureKind::Chamfer { distance, distance2, angle, .. } => {
|
||||
v.push(("Distance", distance, L));
|
||||
|
||||
Reference in new issue
Block a user