Unstitch and Reverse Normal (#856)
Unstitch takes a body's faces out as surface bodies; Reverse Normal turns a surface body's faces over. Both are saved in format 18. Closes #766.
This commit is contained in:
1 parent
b702f0efde
commit
753b0a2eab
16 files changed
+322
-19
No files matched your search
@@ -112,7 +112,7 @@ pub use step::step_export;
|
||||
pub use step_in::{ImportedBody, StepImport, StepNode, step_import, step_import_shared, step_orientation_errors, step_validate};
|
||||
pub use step_out::{ExportBody, ExportProduct, StepHeader, step_export_bodies, step_export_products};
|
||||
pub use surface_kind::{FaceSurface, face_surface};
|
||||
pub use surfaces::{copy_faces, extend, extrude_sheet, patch_edges, patch_region, stitch, thicken, trim};
|
||||
pub use surfaces::{copy_faces, extend, extrude_sheet, patch_edges, patch_region, reverse_faces, stitch, thicken, trim, unstitch};
|
||||
pub use sweep_path::sweep_path;
|
||||
pub use topo::{CylinderFace, cylinder_face_at};
|
||||
pub use topo::{TopoCounts, merged_topology, seam_flags};
|
||||
|
||||
@@ -186,6 +186,67 @@ pub fn copy_faces(body: &Body, at: &[Vec3]) -> Result<Body> {
|
||||
Ok(rebuilt_body(copied, &[&body.solid], &Rebuild::default()))
|
||||
}
|
||||
|
||||
/// The faces picked by points on them (each once, in the order picked); every face when no
|
||||
/// point is given.
|
||||
fn picked_faces(body: &Body, at: &[Vec3]) -> Result<Vec<usize>> {
|
||||
if at.len() > 10_000 {
|
||||
return Err(KernelError::Invalid("too many faces picked".into()));
|
||||
}
|
||||
if at.is_empty() {
|
||||
return Ok((0..body.face_count()).collect());
|
||||
}
|
||||
let mut chosen = Vec::new();
|
||||
for p in at {
|
||||
let i = nearest_face(body, *p)?;
|
||||
if !chosen.contains(&i) {
|
||||
chosen.push(i);
|
||||
}
|
||||
}
|
||||
Ok(chosen)
|
||||
}
|
||||
|
||||
/// A body's faces taken apart: each picked face (by points on it; every face when none are
|
||||
/// given) its own surface body, and the faces left, if any, one surface body. A solid comes
|
||||
/// apart into surfaces. Histories are over `[body]`: each face is the body's.
|
||||
pub fn unstitch(body: &Body, at: &[Vec3]) -> Result<(Vec<Body>, Option<Body>)> {
|
||||
body.require_brep("unstitch")?;
|
||||
let chosen = picked_faces(body, at)?;
|
||||
let faces: Vec<&mt::Face> = body.solid.face_iter().collect();
|
||||
if faces.len() < 2 {
|
||||
return Err(KernelError::Invalid("the body has one face: nothing to unstitch".into()));
|
||||
}
|
||||
let piece = |picked: Vec<mt::Face>| rebuilt_body(surface_body(picked), &[&body.solid], &Rebuild::default());
|
||||
let pieces: Vec<Body> = chosen.iter().filter_map(|i| faces.get(*i)).map(|f| piece(vec![(*f).clone()])).collect();
|
||||
let rest: Vec<mt::Face> = faces.iter().enumerate().filter(|(i, _)| !chosen.contains(i)).map(|(_, f)| (*f).clone()).collect();
|
||||
Ok((pieces, (!rest.is_empty()).then(|| piece(rest))))
|
||||
}
|
||||
|
||||
/// A surface body with the picked faces (by points on them; every face when none are given)
|
||||
/// turned over: their normals point the other way. A solid is refused (its faces face out).
|
||||
/// The history is over `[body]`: each face is the body's, turned over or not.
|
||||
pub fn reverse_faces(body: &Body, at: &[Vec3]) -> Result<Body> {
|
||||
body.require_brep("reverse normal")?;
|
||||
if !body.is_surface() {
|
||||
return Err(KernelError::Invalid("a solid's faces face out: unstitch it into surfaces first".into()));
|
||||
}
|
||||
let chosen = picked_faces(body, at)?;
|
||||
let mut rec = Rebuild::default();
|
||||
let faces: Vec<mt::Face> = body
|
||||
.solid
|
||||
.face_iter()
|
||||
.enumerate()
|
||||
.map(|(i, f)| {
|
||||
if !chosen.contains(&i) {
|
||||
return f.clone();
|
||||
}
|
||||
let turned = f.inverse();
|
||||
rec.whole(&turned, f);
|
||||
turned
|
||||
})
|
||||
.collect();
|
||||
Ok(rebuilt_body(surface_body(faces), &[&body.solid], &rec))
|
||||
}
|
||||
|
||||
/// Index of the face nearest a point (over its mesh triangles).
|
||||
fn nearest_face(body: &Body, p: Vec3) -> Result<usize> {
|
||||
let m = body.tessellate(body.size() * 2e-3)?;
|
||||
@@ -710,6 +771,42 @@ mod tests {
|
||||
assert!(extrude_sheet(&Plane::XY, &[nan], 0.0, 1.0).is_err());
|
||||
}
|
||||
|
||||
/// A box taken apart into its six faces (each a 10 × 10 surface) and stitched back into
|
||||
/// the same solid; two picked faces come off on their own, the other four stay together;
|
||||
/// a one-face body has nothing to take apart.
|
||||
#[test]
|
||||
fn unstitch_a_box_and_stitch_it_back() {
|
||||
let b = box_solid(Vec3::ZERO, Vec3::new(10.0, 10.0, 10.0)).unwrap();
|
||||
let (pieces, rest) = unstitch(&b, &[]).unwrap();
|
||||
assert!(rest.is_none() && pieces.len() == 6);
|
||||
for p in &pieces {
|
||||
assert!(p.is_surface() && p.face_count() == 1 && rel(measure(p).unwrap().area, 100.0) < 1e-9);
|
||||
}
|
||||
let back = stitch(&pieces.iter().collect::<Vec<_>>(), 1e-6).unwrap();
|
||||
assert!(!back.is_surface() && rel(measure(&back).unwrap().volume, 1000.0) < 1e-9);
|
||||
let (two, rest) = unstitch(&b, &[Vec3::new(5.0, 5.0, 10.0), Vec3::new(5.0, 5.0, 0.0)]).unwrap();
|
||||
let rest = rest.unwrap();
|
||||
assert!(two.len() == 2 && rest.is_surface() && rest.face_count() == 4);
|
||||
assert!(unstitch(&two[0], &[]).is_err());
|
||||
}
|
||||
|
||||
/// A patch thickened along its normal grows up; turned over, the same thickness grows down.
|
||||
/// A solid's faces are not turned over.
|
||||
#[test]
|
||||
fn reverse_faces_turns_the_thicken_side() {
|
||||
let r = Region2 {
|
||||
outer: Loop2::polygon(&[Vec2::new(0.0, 0.0), Vec2::new(10.0, 0.0), Vec2::new(10.0, 10.0), Vec2::new(0.0, 10.0)]),
|
||||
holes: vec![],
|
||||
};
|
||||
let s = patch_region(&Plane::XY, &r).unwrap();
|
||||
let up = measure(&thicken(&s, 2.0, false).unwrap()).unwrap().bbox;
|
||||
let turned = reverse_faces(&s, &[]).unwrap();
|
||||
assert!(turned.is_surface() && turned.face_count() == 1);
|
||||
let down = measure(&thicken(&turned, 2.0, false).unwrap()).unwrap().bbox;
|
||||
assert!((up.min.z - down.min.z - 2.0).abs() < 1e-9 && (up.max.z - down.max.z - 2.0).abs() < 1e-9, "{up:?} vs {down:?}");
|
||||
assert!(reverse_faces(&box_solid(Vec3::ZERO, Vec3::new(1.0, 1.0, 1.0)).unwrap(), &[]).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn patch_and_thicken_a_square() {
|
||||
let r = Region2 {
|
||||
|
||||
Reference in new issue
Block a user