use super::compound::CompoundShape;
use super::edge::Edge;
use super::face::Face;
use super::ffi;
use crate::common::error::Error;
use crate::traits::{Compound, ProfileOrient, SolidStruct, Transform};
use glam::DVec3;
use std::sync::{Mutex, OnceLock};
static LOFT_LOCK: Mutex<()> = Mutex::new(());
fn encode_orient(orient: ProfileOrient) -> (u32, f64, f64, f64, cxx::UniquePtr<cxx::CxxVector<ffi::TopoDS_Edge>>) {
let mut aux_vec = ffi::edge_vec_new();
let (kind, ux, uy, uz) = match orient {
ProfileOrient::Fixed => (0u32, 0.0, 0.0, 0.0),
ProfileOrient::Torsion => (1u32, 0.0, 0.0, 0.0),
ProfileOrient::Up(v) => (2u32, v.x, v.y, v.z),
ProfileOrient::Auxiliary(edges) => {
for e in edges {
ffi::edge_vec_push(aux_vec.pin_mut(), &e.inner);
}
(3u32, 0.0, 0.0, 0.0)
}
};
(kind, ux, uy, uz, aux_vec)
}
#[cfg(feature = "color")]
fn remap_colormap_by_order(old_inner: &ffi::TopoDS_Shape, new_inner: &ffi::TopoDS_Shape, old_colormap: &std::collections::HashMap<u64, crate::common::color::Color>) -> std::collections::HashMap<u64, crate::common::color::Color> {
let mut colormap = std::collections::HashMap::new();
let old_faces = ffi::shape_faces(old_inner);
let new_faces = ffi::shape_faces(new_inner);
for (old_face, new_face) in old_faces.iter().zip(new_faces.iter()) {
if let Some(&color) = old_colormap.get(&ffi::face_tshape_id(old_face)) {
colormap.insert(ffi::face_tshape_id(new_face), color);
}
}
colormap
}
pub struct Solid {
inner: cxx::UniquePtr<ffi::TopoDS_Shape>,
edges: OnceLock<Vec<Edge>>,
faces: OnceLock<Vec<Face>>,
#[cfg(feature = "color")]
colormap: std::collections::HashMap<u64, crate::common::color::Color>,
history: Vec<u64>,
}
impl Solid {
pub(crate) fn new(
inner: cxx::UniquePtr<ffi::TopoDS_Shape>,
#[cfg(feature = "color")] colormap: std::collections::HashMap<u64, crate::common::color::Color>,
history: Vec<u64>,
) -> Self {
debug_assert!(ffi::shape_is_null(&inner) || ffi::shape_is_solid(&inner), "Solid::new called with a non-SOLID shape");
Solid {
inner,
edges: OnceLock::new(),
faces: OnceLock::new(),
#[cfg(feature = "color")]
colormap,
history,
}
}
pub(crate) fn inner(&self) -> &ffi::TopoDS_Shape {
&self.inner
}
#[cfg(feature = "color")]
pub fn colormap(&self) -> &std::collections::HashMap<u64, crate::common::color::Color> {
&self.colormap
}
#[cfg(feature = "color")]
pub fn colormap_mut(&mut self) -> &mut std::collections::HashMap<u64, crate::common::color::Color> {
&mut self.colormap
}
pub fn is_null(&self) -> bool {
ffi::shape_is_null(&self.inner)
}
}
impl SolidStruct for Solid {
type Edge = Edge;
type Face = Face;
fn id(&self) -> u64 {
ffi::shape_tshape_id(&self.inner)
}
fn cube(x: f64, y: f64, z: f64) -> Solid {
let inner = ffi::make_box(0.0, 0.0, 0.0, x, y, z);
Solid::new(
inner,
#[cfg(feature = "color")]
std::collections::HashMap::new(),
Default::default(),
)
}
fn cylinder(r: f64, axis: DVec3, h: f64) -> Solid {
let inner = ffi::make_cylinder(0.0, 0.0, 0.0, axis.x, axis.y, axis.z, r, h);
Solid::new(
inner,
#[cfg(feature = "color")]
std::collections::HashMap::new(),
Default::default(),
)
}
fn sphere(radius: f64) -> Solid {
let inner = ffi::make_sphere(0.0, 0.0, 0.0, radius);
Solid::new(
inner,
#[cfg(feature = "color")]
std::collections::HashMap::new(),
Default::default(),
)
}
fn cone(r1: f64, r2: f64, axis: DVec3, h: f64) -> Solid {
let inner = ffi::make_cone(0.0, 0.0, 0.0, axis.x, axis.y, axis.z, r1, r2, h);
Solid::new(
inner,
#[cfg(feature = "color")]
std::collections::HashMap::new(),
Default::default(),
)
}
fn torus(r1: f64, r2: f64, axis: DVec3) -> Solid {
let inner = ffi::make_torus(0.0, 0.0, 0.0, axis.x, axis.y, axis.z, r1, r2);
Solid::new(
inner,
#[cfg(feature = "color")]
std::collections::HashMap::new(),
Default::default(),
)
}
fn half_space(plane_origin: DVec3, plane_normal: DVec3) -> Solid {
let inner = ffi::make_half_space(plane_origin.x, plane_origin.y, plane_origin.z, plane_normal.x, plane_normal.y, plane_normal.z);
Solid::new(
inner,
#[cfg(feature = "color")]
std::collections::HashMap::new(),
Default::default(),
)
}
fn iter_edge(&self) -> impl Iterator<Item = &Edge> + '_ {
self.edges
.get_or_init(|| {
ffi::shape_edges(&self.inner)
.iter()
.map(|e_ref| {
let owned = ffi::clone_edge_handle(e_ref);
Edge::try_from_ffi(owned, "shape_edges: null".into()).expect("shape_edges: unexpected null (this is a bug)")
})
.collect()
})
.iter()
}
fn iter_face(&self) -> impl Iterator<Item = &Face> + '_ {
self.faces
.get_or_init(|| {
ffi::shape_faces(&self.inner)
.iter()
.map(|f_ref| Face::new(ffi::clone_face_handle(f_ref)))
.collect()
})
.iter()
}
fn iter_history(&self) -> impl Iterator<Item = [u64; 2]> + '_ {
self.history.chunks_exact(2).map(|c| [c[0], c[1]])
}
fn extrude<'a>(profile: impl IntoIterator<Item = &'a Edge>, dir: DVec3) -> Result<Self, Error> {
let mut profile_vec = ffi::edge_vec_new();
for e in profile {
ffi::edge_vec_push(profile_vec.pin_mut(), &e.inner);
}
let shape = ffi::make_extrude(&profile_vec, dir.x, dir.y, dir.z);
if shape.is_null() {
return Err(Error::ExtrudeFailed);
}
Ok(Solid::new(
shape,
#[cfg(feature = "color")]
std::collections::HashMap::new(),
Default::default(),
))
}
fn shell<'a>(&self, thickness: f64, open_faces: impl IntoIterator<Item = &'a Face>) -> Result<Self, Error> {
let mut face_vec = ffi::face_vec_new();
for f in open_faces {
ffi::face_vec_push(face_vec.pin_mut(), &f.inner);
}
let shape = ffi::builder_thick_solid(&self.inner, &face_vec, thickness);
if shape.is_null() {
return Err(Error::ShellFailed);
}
Ok(Solid::new(
shape,
#[cfg(feature = "color")]
std::collections::HashMap::new(),
Default::default(),
))
}
fn fillet_edges<'a>(&self, radius: f64, edges: impl IntoIterator<Item = &'a Edge>) -> Result<Self, Error> {
let mut edge_vec = ffi::edge_vec_new();
for e in edges {
ffi::edge_vec_push(edge_vec.pin_mut(), &e.inner);
}
let shape = ffi::builder_fillet(&self.inner, &edge_vec, radius);
if shape.is_null() {
return Err(Error::FilletFailed);
}
Ok(Solid::new(
shape,
#[cfg(feature = "color")]
std::collections::HashMap::new(),
Default::default(),
))
}
fn chamfer_edges<'a>(&self, distance: f64, edges: impl IntoIterator<Item = &'a Edge>) -> Result<Self, Error> {
let mut edge_vec = ffi::edge_vec_new();
for e in edges {
ffi::edge_vec_push(edge_vec.pin_mut(), &e.inner);
}
let shape = ffi::builder_chamfer(&self.inner, &edge_vec, distance);
if shape.is_null() {
return Err(Error::ChamferFailed);
}
Ok(Solid::new(
shape,
#[cfg(feature = "color")]
std::collections::HashMap::new(),
Default::default(),
))
}
fn sweep<'a, 'b, 'c>(profile: impl IntoIterator<Item = &'a Edge>, spine: impl IntoIterator<Item = &'b Edge>, orient: ProfileOrient<'c>) -> Result<Self, Error> {
let mut profile_vec = ffi::edge_vec_new();
for e in profile {
ffi::edge_vec_push(profile_vec.pin_mut(), &e.inner);
}
let mut spine_vec = ffi::edge_vec_new();
for e in spine {
ffi::edge_vec_push(spine_vec.pin_mut(), &e.inner);
}
let (kind, ux, uy, uz, aux_vec) = encode_orient(orient);
let shape = ffi::make_pipe_shell(&profile_vec, &spine_vec, kind, ux, uy, uz, &aux_vec);
if shape.is_null() {
return Err(Error::SweepFailed);
}
Ok(Solid::new(
shape,
#[cfg(feature = "color")]
std::collections::HashMap::new(),
Default::default(),
))
}
fn loft<'a, S, I>(sections: S) -> Result<Self, Error> where S: IntoIterator<Item = I>, I: IntoIterator<Item = &'a Edge>, Edge: 'a {
let _guard = LOFT_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let mut all_edges = ffi::edge_vec_new();
let mut section_count = 0usize;
for sec in sections {
if section_count > 0 {
ffi::edge_vec_push_null(all_edges.pin_mut());
}
let mut count = 0u32;
for edge in sec {
ffi::edge_vec_push(all_edges.pin_mut(), &edge.inner);
count += 1;
}
if count == 0 {
return Err(Error::LoftFailed(format!(
"loft: section {} is empty (each section must contain ≥1 edge)",
section_count
)));
}
section_count += 1;
}
if section_count < 2 {
return Err(Error::LoftFailed(format!(
"loft: need ≥2 sections, got {} (a single section has no thickness to skin across)",
section_count
)));
}
let shape = ffi::make_loft(&all_edges);
if shape.is_null() {
return Err(Error::LoftFailed(format!(
"loft: OCCT BRepOffsetAPI_ThruSections failed (sections={}). \
Check that each section forms a valid closed wire and sections are not coplanar.",
section_count
)));
}
Ok(Solid::new(
shape,
#[cfg(feature = "color")]
std::collections::HashMap::new(),
Default::default(),
))
}
fn bspline(u: usize, v: usize, u_periodic: bool, point: impl Fn(usize, usize) -> DVec3) -> Result<Self, Error> {
if u < 2 || v < 3 {
return Err(Error::BsplineFailed(format!("grid must be at least 2x3 (u={}, v={})", u, v)));
}
let mut coords = Vec::with_capacity(3 * u * v);
for i in 0..u {
for j in 0..v {
let p = point(i, j);
coords.push(p.x);
coords.push(p.y);
coords.push(p.z);
}
}
let shape = ffi::make_bspline_solid(&coords, u as u32, v as u32, u_periodic);
if shape.is_null() {
return Err(Error::BsplineFailed(format!("OCCT construction failed (u={}, v={}, u_periodic={})", u, v, u_periodic)));
}
Ok(Solid::new(
shape,
#[cfg(feature = "color")]
std::collections::HashMap::new(),
Default::default(),
))
}
fn clean(&self) -> Result<Self, Error> {
let mut history: Vec<u64> = Default::default();
let inner = ffi::builder_clean(&self.inner, &mut history);
if inner.is_null() {
return Err(Error::CleanFailed);
}
#[cfg(feature = "color")]
let colormap = {
let mut m = std::collections::HashMap::new();
for pair in history.chunks_exact(2) {
if let Some(&color) = self.colormap.get(&pair[1]) {
m.entry(pair[0]).or_insert(color);
}
}
m
};
Ok(Solid::new(inner, #[cfg(feature = "color")] colormap, history))
}
fn boolean_union<'a, 'b>(a: impl IntoIterator<Item = &'a Self>, b: impl IntoIterator<Item = &'b Self>) -> Result<Vec<Self>, Error> where Self: 'a + 'b {
Self::boolean_union_impl(a, b)
}
fn boolean_subtract<'a, 'b>(a: impl IntoIterator<Item = &'a Self>, b: impl IntoIterator<Item = &'b Self>) -> Result<Vec<Self>, Error> where Self: 'a + 'b {
Self::boolean_subtract_impl(a, b)
}
fn boolean_intersect<'a, 'b>(a: impl IntoIterator<Item = &'a Self>, b: impl IntoIterator<Item = &'b Self>) -> Result<Vec<Self>, Error> where Self: 'a + 'b {
Self::boolean_intersect_impl(a, b)
}
fn read_step<R: std::io::Read>(reader: &mut R) -> Result<Vec<Self>, Error> {
super::io::read_step(reader)
}
fn read_brep_binary<R: std::io::Read>(reader: &mut R) -> Result<Vec<Self>, Error> {
super::io::read_brep_binary(reader)
}
fn read_brep_text<R: std::io::Read>(reader: &mut R) -> Result<Vec<Self>, Error> {
super::io::read_brep_text(reader)
}
fn write_step<'a, W: std::io::Write>(solids: impl IntoIterator<Item = &'a Self>, writer: &mut W) -> Result<(), Error> where Self: 'a {
super::io::write_step(solids, writer)
}
fn write_brep_binary<'a, W: std::io::Write>(solids: impl IntoIterator<Item = &'a Self>, writer: &mut W) -> Result<(), Error> where Self: 'a {
super::io::write_brep_binary(solids, writer)
}
fn write_brep_text<'a, W: std::io::Write>(solids: impl IntoIterator<Item = &'a Self>, writer: &mut W) -> Result<(), Error> where Self: 'a {
super::io::write_brep_text(solids, writer)
}
fn mesh<'a>(solids: impl IntoIterator<Item = &'a Self>, tolerance: f64) -> Result<crate::common::mesh::Mesh, Error> where Self: 'a {
super::io::mesh(solids, tolerance)
}
}
impl Transform for Solid {
fn translate(self, translation: DVec3) -> Self {
let inner = ffi::transform_translate(&self.inner, translation.x, translation.y, translation.z);
Solid::new(
inner,
#[cfg(feature = "color")]
self.colormap,
self.history,
)
}
fn rotate(self, axis_origin: DVec3, axis_direction: DVec3, angle: f64) -> Self {
let inner = ffi::transform_rotate(&self.inner, axis_origin.x, axis_origin.y, axis_origin.z, axis_direction.x, axis_direction.y, axis_direction.z, angle);
Solid::new(
inner,
#[cfg(feature = "color")]
self.colormap,
self.history,
)
}
fn scale(self, center: DVec3, factor: f64) -> Self {
let inner = ffi::transform_scale(&self.inner, center.x, center.y, center.z, factor);
#[cfg(feature = "color")]
let colormap = remap_colormap_by_order(&self.inner, &inner, &self.colormap);
Solid::new(
inner,
#[cfg(feature = "color")]
colormap,
Default::default(),
)
}
fn mirror(self, plane_origin: DVec3, plane_normal: DVec3) -> Self {
let inner = ffi::transform_mirror(&self.inner, plane_origin.x, plane_origin.y, plane_origin.z, plane_normal.x, plane_normal.y, plane_normal.z);
#[cfg(feature = "color")]
let colormap = remap_colormap_by_order(&self.inner, &inner, &self.colormap);
Solid::new(
inner,
#[cfg(feature = "color")]
colormap,
Default::default(),
)
}
}
impl Compound for Solid {
type Elem = Solid;
fn volume(&self) -> f64 {
ffi::shape_volume(&self.inner)
}
fn area(&self) -> f64 {
ffi::shape_surface_area(&self.inner)
}
fn center(&self) -> DVec3 {
let (mut x, mut y, mut z) = (0.0_f64, 0.0_f64, 0.0_f64);
ffi::shape_center_of_mass(&self.inner, &mut x, &mut y, &mut z);
DVec3::new(x, y, z)
}
fn inertia(&self) -> glam::DMat3 {
let (mut m00, mut m01, mut m02) = (0.0_f64, 0.0_f64, 0.0_f64);
let (mut m10, mut m11, mut m12) = (0.0_f64, 0.0_f64, 0.0_f64);
let (mut m20, mut m21, mut m22) = (0.0_f64, 0.0_f64, 0.0_f64);
ffi::shape_inertia_tensor(&self.inner,
&mut m00, &mut m01, &mut m02,
&mut m10, &mut m11, &mut m12,
&mut m20, &mut m21, &mut m22);
glam::DMat3::from_cols_array(&[
m00, m10, m20,
m01, m11, m21,
m02, m12, m22,
])
}
fn contains(&self, point: DVec3) -> bool {
ffi::shape_contains_point(&self.inner, point.x, point.y, point.z)
}
fn bounding_box(&self) -> [DVec3; 2] {
let (mut xmin, mut ymin, mut zmin) = (0.0_f64, 0.0_f64, 0.0_f64);
let (mut xmax, mut ymax, mut zmax) = (0.0_f64, 0.0_f64, 0.0_f64);
ffi::shape_bounding_box(&self.inner, &mut xmin, &mut ymin, &mut zmin, &mut xmax, &mut ymax, &mut zmax);
[DVec3::new(xmin, ymin, zmin), DVec3::new(xmax, ymax, zmax)]
}
#[cfg(feature = "color")]
fn color(self, color: impl Into<crate::common::color::Color>) -> Self {
let c = color.into();
let colormap = ffi::shape_faces(&self.inner).iter().map(|f| (ffi::face_tshape_id(f), c)).collect();
Self::new(self.inner, colormap, self.history)
}
#[cfg(feature = "color")]
fn color_clear(self) -> Self {
Self::new(self.inner, std::collections::HashMap::new(), self.history)
}
fn union<'a>(&self, tool: impl IntoIterator<Item = &'a Solid>) -> Result<Vec<Solid>, Error> {
<Solid as SolidStruct>::boolean_union([self], tool)
}
fn subtract<'a>(&self, tool: impl IntoIterator<Item = &'a Solid>) -> Result<Vec<Solid>, Error> {
<Solid as SolidStruct>::boolean_subtract([self], tool)
}
fn intersect<'a>(&self, tool: impl IntoIterator<Item = &'a Solid>) -> Result<Vec<Solid>, Error> {
<Solid as SolidStruct>::boolean_intersect([self], tool)
}
fn iter_elem(&self) -> impl Iterator<Item = &Self::Elem> + '_ {
panic!("Cannot iter_elem on Solid, because Solid is not Compound");
#[allow(unreachable_code)] std::iter::empty()
}
fn map_elem(self, _f: impl FnMut(Self::Elem) -> Self::Elem) -> Self {
panic!("Cannot map_elem on Solid, because Solid is not Compound")
}
}
impl Clone for Solid {
fn clone(&self) -> Self {
let inner = ffi::deep_copy(&self.inner);
#[cfg(feature = "color")]
let colormap = remap_colormap_by_order(&self.inner, &inner, &self.colormap);
Solid::new(
inner,
#[cfg(feature = "color")]
colormap,
Default::default(),
)
}
}
#[cfg(feature = "color")]
fn merge_colormaps(history: &[u64], colormap_a: &std::collections::HashMap<u64, crate::common::color::Color>, colormap_b: &std::collections::HashMap<u64, crate::common::color::Color>) -> std::collections::HashMap<u64, crate::common::color::Color> {
let mut result = std::collections::HashMap::new();
for pair in history.chunks_exact(2) {
if let Some(&color) = colormap_a.get(&pair[1]).or_else(|| colormap_b.get(&pair[1])) {
result.insert(pair[0], color);
}
}
result
}
#[cfg_attr(not(feature = "color"), allow(unused_variables))]
fn build_boolean_result(inner: cxx::UniquePtr<ffi::TopoDS_Shape>, history: Vec<u64>, ca: CompoundShape, cb: CompoundShape) -> Result<Vec<Solid>, Error> {
#[cfg(feature = "color")]
let colormap = merge_colormaps(&history, ca.colormap(), cb.colormap());
let compound = CompoundShape::from_raw(
inner,
#[cfg(feature = "color")]
colormap,
history,
);
Ok(compound.decompose())
}
const BOOLEAN_OP_FUSE: u32 = 0;
const BOOLEAN_OP_CUT: u32 = 1;
const BOOLEAN_OP_COMMON: u32 = 2;
impl Solid {
fn boolean_op_impl<'a, 'b>(a: impl IntoIterator<Item = &'a Solid>, b: impl IntoIterator<Item = &'b Solid>, op_kind: u32) -> Result<Vec<Solid>, Error> {
let ca = CompoundShape::new(a);
let cb = CompoundShape::new(b);
let mut history: Vec<u64> = Default::default();
let inner = ffi::builder_boolean(ca.inner(), cb.inner(), op_kind, &mut history);
if inner.is_null() { return Err(Error::BooleanOperationFailed); }
build_boolean_result(inner, history, ca, cb)
}
pub(crate) fn boolean_union_impl<'a, 'b>(a: impl IntoIterator<Item = &'a Solid>, b: impl IntoIterator<Item = &'b Solid>) -> Result<Vec<Solid>, Error> {
Self::boolean_op_impl(a, b, BOOLEAN_OP_FUSE)
}
pub(crate) fn boolean_subtract_impl<'a, 'b>(a: impl IntoIterator<Item = &'a Solid>, b: impl IntoIterator<Item = &'b Solid>) -> Result<Vec<Solid>, Error> {
Self::boolean_op_impl(a, b, BOOLEAN_OP_CUT)
}
pub(crate) fn boolean_intersect_impl<'a, 'b>(a: impl IntoIterator<Item = &'a Solid>, b: impl IntoIterator<Item = &'b Solid>) -> Result<Vec<Solid>, Error> {
Self::boolean_op_impl(a, b, BOOLEAN_OP_COMMON)
}
}