diff --git a/crates/opencascade-sys/include/wrapper.hxx b/crates/opencascade-sys/include/wrapper.hxx index c4d34d50..9b8655cd 100644 --- a/crates/opencascade-sys/include/wrapper.hxx +++ b/crates/opencascade-sys/include/wrapper.hxx @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -523,3 +524,19 @@ cast_section_to_builderalgo(std::unique_ptr section) { return section; } // namespace BRepAlgoAPI + +// Bnd_Box +inline std::unique_ptr Bnd_Box_ctor() { return std::unique_ptr(new Bnd_Box()); } +inline std::unique_ptr Bnd_Box_CornerMin(const Bnd_Box &box) { + auto p = box.CornerMin(); + return std::unique_ptr(new gp_Pnt(p)); +} +inline std::unique_ptr Bnd_Box_CornerMax(const Bnd_Box &box) { + auto p = box.CornerMax(); + return std::unique_ptr(new gp_Pnt(p)); +} + +// BRepBndLib +inline void BRepBndLib_Add(const TopoDS_Shape &shape, Bnd_Box &box, const Standard_Boolean useTriangulation) { + BRepBndLib::Add(shape, box, useTriangulation); +} diff --git a/crates/opencascade-sys/src/lib.rs b/crates/opencascade-sys/src/lib.rs index da62fada..ab141157 100644 --- a/crates/opencascade-sys/src/lib.rs +++ b/crates/opencascade-sys/src/lib.rs @@ -1375,6 +1375,34 @@ pub mod ffi { shared: bool, wires: Pin<&mut HandleTopTools_HSequenceOfShape>, ); + + // BndBox + // Describes a bounding box in 3D space. + type Bnd_Box; + + #[cxx_name = "construct_unique"] + pub fn Bnd_Box_ctor() -> UniquePtr; + pub fn IsVoid(self: &Bnd_Box) -> bool; + pub fn Get( + self: &Bnd_Box, + xMin: &mut f64, + yMin: &mut f64, + zMin: &mut f64, + xMax: &mut f64, + yMax: &mut f64, + zMax: &mut f64, + ); + pub fn Bnd_Box_CornerMin(b: &Bnd_Box) -> UniquePtr; + pub fn Bnd_Box_CornerMax(b: &Bnd_Box) -> UniquePtr; + pub fn GetGap(self: &Bnd_Box) -> f64; + pub fn Set(self: Pin<&mut Bnd_Box>, p: &gp_Pnt); + pub fn SetGap(self: Pin<&mut Bnd_Box>, gap: f64); + + // BRepBndLib + // Bounding boxes for curves and surfaces. + type BRepBndLib; + + pub fn BRepBndLib_Add(shape: &TopoDS_Shape, bb: Pin<&mut Bnd_Box>, use_triangulation: bool); } } diff --git a/crates/opencascade/src/bounding_box.rs b/crates/opencascade/src/bounding_box.rs new file mode 100644 index 00000000..c1a82fc0 --- /dev/null +++ b/crates/opencascade/src/bounding_box.rs @@ -0,0 +1,85 @@ +use cxx::UniquePtr; +use glam::DVec3; +use opencascade_sys::ffi; + +use crate::primitives::Shape; + +/// A wrapper around the `Bnd_Box` API of OCC. Note that a `Bnd_Box` has a `Gap` +/// property, which is a small tolerance value added to all dimensions. This +/// means that the point values of a `BoundingBox` will often be slightly larger +/// or smaller than expected of the geometry of known shapes. +pub struct BoundingBox { + pub(crate) inner: UniquePtr, +} +impl BoundingBox { + /// Create a new void box. A void box in OCC is defined as a box that contains no points. + pub fn void() -> BoundingBox { + Self { inner: ffi::Bnd_Box_ctor() } + } + + pub fn is_void(&self) -> bool { + self.inner.IsVoid() + } + + pub fn get_gap(&self) -> f64 { + self.inner.GetGap() + } + + pub fn min(&self) -> DVec3 { + let p = ffi::Bnd_Box_CornerMin(self.inner.as_ref().unwrap()); + glam::dvec3(p.X(), p.Y(), p.Z()) + } + + pub fn max(&self) -> DVec3 { + let p = ffi::Bnd_Box_CornerMax(self.inner.as_ref().unwrap()); + glam::dvec3(p.X(), p.Y(), p.Z()) + } + + /// Get a vector corresponding to the `gap` of this box in all dimensions. + pub fn gap_vec(&self) -> DVec3 { + glam::DVec3::ONE * self.get_gap() + } +} + +/// Compute the axis-aligned bounding box of `shape` using the `BRepBndLib` +/// package. +pub fn aabb(shape: &Shape) -> BoundingBox { + let mut bb = BoundingBox::void(); + ffi::BRepBndLib_Add( + shape.inner.as_ref().expect("Input shape ref was null"), + bb.inner.pin_mut(), + true, + ); + bb +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn create_bounding_box() { + let bb = BoundingBox::void(); + assert!(bb.is_void()); + } + + #[test] + fn get_bounding_box_of_sphere() { + let s = Shape::sphere(1.0).build(); + + let bb = aabb(&s); + + assert_eq!(bb.min(), glam::dvec3(-1.0, -1.0, -1.0) - bb.gap_vec()); + assert_eq!(bb.max(), glam::dvec3(1.0, 1.0, 1.0) + bb.gap_vec()); + } + + #[test] + fn get_bounding_box_of_sphere_transformed() { + let s = Shape::sphere(1.0).at(glam::dvec3(1.0, 2.0, 3.0)).build(); + + let bb = aabb(&s); + let gap = bb.gap_vec(); + assert_eq!(bb.min(), glam::dvec3(0.0, 1.0, 2.0) - gap); + assert_eq!(bb.max(), glam::dvec3(2.0, 3.0, 4.0) + gap); + } +} diff --git a/crates/opencascade/src/lib.rs b/crates/opencascade/src/lib.rs index c3801119..af659c38 100644 --- a/crates/opencascade/src/lib.rs +++ b/crates/opencascade/src/lib.rs @@ -1,6 +1,7 @@ use thiserror::Error; pub mod angle; +pub mod bounding_box; pub mod kicad; pub mod mesh; pub mod primitives; diff --git a/examples/src/bounding_box.rs b/examples/src/bounding_box.rs new file mode 100644 index 00000000..83b057c0 --- /dev/null +++ b/examples/src/bounding_box.rs @@ -0,0 +1,35 @@ +use opencascade::{ + bounding_box, + primitives::{Compound, IntoShape, Shape}, + workplane::Workplane, +}; + +pub fn shape() -> Shape { + let mut shapes = vec![]; + + // Create some non-orthogonal shapes. + for i in 0..=1 { + // NOTE: Try changing the range values and the position vector + let v = (2i32.pow(i)) as f64; + let s = Shape::sphere(1.0).at(glam::dvec3(v, v, v * 2.0)).build(); + shapes.push(s); + } + + // Add a circle to show that edges work, too. + shapes.push(Workplane::xy().circle(0.0, 0.0, 2.0).into_shape()); + + // Combine them to create a bounding box. + let c = Compound::from_shapes(shapes); + + // Create the bounding box. + let bb = bounding_box::aabb(&Shape::from(&c)); + + // Create a box geometry for rendering. + let bb_shape = Shape::box_from_corners(bb.min(), bb.max()); + + let all_shapes = + [vec![c.into_shape()], bb_shape.edges().map(|e| e.into_shape()).collect::>()]; + + // Combine the bounded and bounding geometry. + Compound::from_shapes(all_shapes.iter().flatten()).into_shape() +} diff --git a/examples/src/lib.rs b/examples/src/lib.rs index e82ad700..489651a8 100644 --- a/examples/src/lib.rs +++ b/examples/src/lib.rs @@ -2,6 +2,7 @@ use clap::ValueEnum; use opencascade::primitives::Shape; pub mod airfoil; +pub mod bounding_box; pub mod box_shape; pub mod cable_bracket; pub mod chamfer; @@ -26,6 +27,7 @@ pub mod zbox_case; #[derive(Debug, Copy, Clone, PartialEq, ValueEnum)] pub enum Example { Airfoil, + BoundingBox, CableBracket, BoxShape, Chamfer, @@ -52,6 +54,7 @@ impl Example { pub fn shape(self) -> Shape { match self { Example::Airfoil => airfoil::shape(), + Example::BoundingBox => bounding_box::shape(), Example::CableBracket => cable_bracket::shape(), Example::BoxShape => box_shape::shape(), Example::Chamfer => chamfer::shape(),