Offsets picked faces along their normals into a new surface body: planes and cylinders move exactly, other faces get a fitted B-spline within tolerance, and offsets that would fold over are refused. Adds the SurfaceOffset feature (noted under format 18), the surface.offset engine command and an Offset Surface dialog. Closes #758. Co-authored-by: jyviko <[email protected]>
This commit is contained in:
1 parent
15b5b51bf5
commit
2b70ed7b72
15 files changed
+574
-19
No files matched your search
@@ -237,9 +237,10 @@ impl FeatureKind {
|
||||
}
|
||||
}
|
||||
FeatureKind::Chamfer { edges, .. } => edges.iter_mut().for_each(pt),
|
||||
FeatureKind::Shell { faces, .. } | FeatureKind::OffsetFace { faces, .. } | FeatureKind::DeleteFace { faces, .. } => {
|
||||
faces.iter_mut().for_each(pt)
|
||||
}
|
||||
FeatureKind::Shell { faces, .. }
|
||||
| FeatureKind::OffsetFace { faces, .. }
|
||||
| FeatureKind::DeleteFace { faces, .. }
|
||||
| FeatureKind::SurfaceOffset { faces, .. } => faces.iter_mut().for_each(pt),
|
||||
FeatureKind::Draft { faces, neutral, pull, .. } => {
|
||||
faces.iter_mut().for_each(pt);
|
||||
map_plane_ref(m, neutral);
|
||||
|
||||
@@ -305,6 +305,7 @@ impl FeatureKind {
|
||||
| K::SplitFace { body, .. }
|
||||
| K::OffsetFace { body, .. }
|
||||
| K::DeleteFace { body, .. }
|
||||
| K::SurfaceOffset { body, .. }
|
||||
| K::ReplaceFace { body, .. }
|
||||
| K::SheetFlange { body, .. }
|
||||
| K::SheetHem { body, .. }
|
||||
|
||||
@@ -987,6 +987,19 @@ pub enum FeatureKind {
|
||||
SurfaceLoft {
|
||||
sections: Vec<SheetSection>,
|
||||
},
|
||||
/// The faces at `faces` copied out at a signed `distance` along their normals as a new
|
||||
/// surface body (0 copies them); `chain` takes each pick's whole smooth chain of faces;
|
||||
/// fitted faces within `tolerance`.
|
||||
SurfaceOffset {
|
||||
faces: Vec<Vec3>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
body: Option<String>,
|
||||
distance: String,
|
||||
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
||||
chain: bool,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
tolerance: Option<String>,
|
||||
},
|
||||
/// A planar surface body extended outward by `distance` past the edges through `edges`.
|
||||
SurfaceExtend {
|
||||
body: String,
|
||||
@@ -1331,6 +1344,7 @@ impl FeatureKind {
|
||||
| K::Scale { .. }
|
||||
| K::OffsetFace { .. }
|
||||
| K::DeleteFace { .. }
|
||||
| K::SurfaceOffset { .. }
|
||||
| K::BoundingSolid { .. }
|
||||
| K::ReplaceFace { .. }
|
||||
| K::Align { .. }
|
||||
@@ -1410,6 +1424,7 @@ impl FeatureKind {
|
||||
FeatureKind::Scale { .. } => "ScaleFeature",
|
||||
FeatureKind::OffsetFace { .. } => "OffsetFacesFeature",
|
||||
FeatureKind::DeleteFace { .. } => "DeleteFaceFeature",
|
||||
FeatureKind::SurfaceOffset { .. } => "OffsetSurfaceFeature",
|
||||
FeatureKind::BoundingSolid { .. } => "BoundingSolidFeature",
|
||||
FeatureKind::Pipe { .. } => "PipeFeature",
|
||||
FeatureKind::Emboss { .. } => "EmbossFeature",
|
||||
@@ -1482,6 +1497,7 @@ impl FeatureKind {
|
||||
FeatureKind::Scale { .. } => "Scale",
|
||||
FeatureKind::OffsetFace { .. } => "OffsetFace",
|
||||
FeatureKind::DeleteFace { .. } => "DeleteFace",
|
||||
FeatureKind::SurfaceOffset { .. } => "OffsetSurface",
|
||||
FeatureKind::BoundingSolid { .. } => "BoundingSolid",
|
||||
FeatureKind::Pipe { .. } => "Pipe",
|
||||
FeatureKind::Emboss { .. } => "Emboss",
|
||||
@@ -1572,6 +1588,7 @@ impl FeatureKind {
|
||||
| FeatureKind::Draft { faces, .. }
|
||||
| FeatureKind::OffsetFace { faces, .. }
|
||||
| FeatureKind::DeleteFace { faces, .. }
|
||||
| FeatureKind::SurfaceOffset { faces, .. }
|
||||
| FeatureKind::SplitFace { faces, .. }
|
||||
| FeatureKind::ReplaceFace { faces, .. } => faces.iter_mut().collect(),
|
||||
FeatureKind::Hole { position, points: None, to, .. } => std::iter::once(position).chain(to.iter_mut()).collect(),
|
||||
@@ -1747,6 +1764,12 @@ impl FeatureKind {
|
||||
}
|
||||
FeatureKind::ReplaceFace { target, .. } => plane_exprs(target, &mut v),
|
||||
FeatureKind::Align { .. } | FeatureKind::Remove { .. } | FeatureKind::BoundaryFill { .. } | FeatureKind::DeleteFace { .. } => {}
|
||||
FeatureKind::SurfaceOffset { distance, tolerance, .. } => {
|
||||
v.push(distance);
|
||||
if let Some(t) = tolerance {
|
||||
v.push(t);
|
||||
}
|
||||
}
|
||||
FeatureKind::Stitch { tolerance, .. } => v.push(tolerance),
|
||||
FeatureKind::MeshConvert { tolerance, .. } => v.push(tolerance),
|
||||
FeatureKind::Thicken { thickness, .. } => v.push(thickness),
|
||||
|
||||
@@ -3638,7 +3638,8 @@ fn eval_feature(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st:
|
||||
| FeatureKind::ReverseNormal { .. }
|
||||
| FeatureKind::SurfaceRevolve { .. }
|
||||
| FeatureKind::SurfaceSweep { .. }
|
||||
| FeatureKind::SurfaceLoft { .. } => surface::eval(doc, vals, f, st),
|
||||
| FeatureKind::SurfaceLoft { .. }
|
||||
| FeatureKind::SurfaceOffset { .. } => surface::eval(doc, vals, f, st),
|
||||
FeatureKind::Emboss { .. }
|
||||
| FeatureKind::Rib { .. }
|
||||
| FeatureKind::ReplaceFace { .. }
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! Evaluation of the surface features: Patch, Stitch, Thicken, Trim, Extend, Surface Extrude,
|
||||
//! Revolve, Sweep and Loft, Unstitch and Reverse Normal (kernel `surfaces`).
|
||||
//! Revolve, Sweep and Loft, Offset, Unstitch and Reverse Normal (kernel `surfaces`,
|
||||
//! `surface_offset`).
|
||||
|
||||
use super::*;
|
||||
use solidcraft_geom::Seg2;
|
||||
@@ -211,6 +212,28 @@ pub(super) fn eval(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature,
|
||||
push_sheet(st, f, traced);
|
||||
Ok(())
|
||||
}
|
||||
FeatureKind::SurfaceOffset { faces, body, distance, chain, tolerance } => {
|
||||
let d = val(vals, distance, Kind::Length)?;
|
||||
let tol = match tolerance {
|
||||
Some(t) => val(vals, t, Kind::Length)?,
|
||||
None => 0.01,
|
||||
};
|
||||
let i = body_at(st, body, faces)?;
|
||||
let Some(mb) = st.bodies.get(i).cloned() else { return Err(DocError::Invalid("body".into())) };
|
||||
let picks = if *chain { smooth_chain(&mb, faces) } else { faces.clone() };
|
||||
let mut picked: Vec<usize> = Vec::new();
|
||||
for p in &picks {
|
||||
let fi =
|
||||
crate::appearance::face_index_at(&mb, *p).ok_or_else(|| DocError::Invalid(format!("Offset: no face of {} at {p:?}", mb.name)))?;
|
||||
if !picked.contains(&fi) {
|
||||
picked.push(fi);
|
||||
}
|
||||
}
|
||||
let made = Traced::step(kernel::offset_surface(&mb.body, &picked, d, tol)?, &[&mb.traced()]);
|
||||
let name = new_name(st, f, 0);
|
||||
st.bodies.push(ModelBody::traced_new(name, made, f.id));
|
||||
Ok(())
|
||||
}
|
||||
_ => Err(DocError::Invalid("not a surface feature".into())),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,6 +122,8 @@
|
||||
//! `path_sketch`, `path`) is also first saved in 18.
|
||||
//! A `SurfaceLoft` feature (Surface Loft: `sections`, each a `sketch` with optional `profiles`
|
||||
//! and `curves`) is also first saved in 18.
|
||||
//! A `SurfaceOffset` feature (Offset Surface: `faces` points, optional `body`, `distance`,
|
||||
//! `chain` and `tolerance`) is also first saved in 18.
|
||||
|
||||
use serde_json::Value;
|
||||
|
||||
|
||||
@@ -278,6 +278,12 @@ impl FeatureKind {
|
||||
FeatureKind::SurfaceTrim { plane, .. } => plane_inputs_mut(plane, &mut v),
|
||||
FeatureKind::SurfaceExtend { distance, .. } => v.push(("Distance", distance, L)),
|
||||
FeatureKind::Unstitch { .. } | FeatureKind::ReverseNormal { .. } => {}
|
||||
FeatureKind::SurfaceOffset { distance, tolerance, .. } => {
|
||||
v.push(("Distance", distance, L));
|
||||
if let Some(t) = tolerance {
|
||||
v.push(("Tolerance", t, L));
|
||||
}
|
||||
}
|
||||
FeatureKind::Patch { .. } => {}
|
||||
FeatureKind::Combine { .. }
|
||||
| FeatureKind::SheetBase { .. }
|
||||
|
||||
Reference in new issue
Block a user