use super::compound::Compound;
use super::edge::Edge;
use super::face::Face;
use super::ffi;
use super::iterators::{EdgeIterator, FaceIterator};
use crate::common::error::Error;
use crate::traits::{ProfileOrient, SolidExt, SolidStruct, Transform};
use glam::DVec3;
use std::sync::Mutex;
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();
for (old_face, new_face) in FaceIterator::new(ffi::explore_faces(old_inner)).zip(FaceIterator::new(ffi::explore_faces(new_inner))) {
if let Some(&color) = old_colormap.get(&old_face.tshape_id()) {
colormap.insert(new_face.tshape_id(), color);
}
}
colormap
}
pub struct Solid {
inner: cxx::UniquePtr<ffi::TopoDS_Shape>,
#[cfg(feature = "color")]
colormap: std::collections::HashMap<u64, crate::common::color::Color>,
}
impl Solid {
pub(crate) fn new(inner: cxx::UniquePtr<ffi::TopoDS_Shape>, #[cfg(feature = "color")] colormap: std::collections::HashMap<u64, crate::common::color::Color>) -> Self {
debug_assert!(ffi::shape_is_null(&inner) || ffi::shape_is_solid(&inner), "Solid::new called with a non-SOLID shape");
Solid {
inner,
#[cfg(feature = "color")]
colormap,
}
}
pub(crate) fn inner(&self) -> &ffi::TopoDS_Shape {
&self.inner
}
pub fn tshape_id(&self) -> u64 {
ffi::shape_tshape_id(&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)
}
pub fn face_iter(&self) -> FaceIterator {
FaceIterator::new(ffi::explore_faces(&self.inner))
}
pub fn edge_iter(&self) -> EdgeIterator {
EdgeIterator::new(ffi::explore_edges(&self.inner))
}
}
impl SolidStruct for Solid {
type Edge = Edge;
type Face = Face;
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(),
)
}
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(),
)
}
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(),
)
}
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(),
)
}
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(),
)
}
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(),
)
}
fn faces(&self) -> Vec<Face> {
FaceIterator::new(ffi::explore_faces(&self.inner)).collect()
}
fn edges(&self) -> Vec<Edge> {
EdgeIterator::new(ffi::explore_edges(&self.inner)).collect()
}
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(),
))
}
fn sweep_sections<'a, 'b, 'c, S, I>(sections: S, spine: impl IntoIterator<Item = &'b Edge>, orient: ProfileOrient<'c>) -> Result<Self, Error> where S: IntoIterator<Item = I>, I: IntoIterator<Item = &'a Edge>, Edge: 'a {
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::SweepFailed);
}
section_count += 1;
}
if section_count == 0 {
return Err(Error::SweepFailed);
}
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(&all_edges, &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(),
))
}
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(),
))
}
fn boolean_union<'a, 'b>(a: impl IntoIterator<Item = &'a Self>, b: impl IntoIterator<Item = &'b Self>) -> Result<(Vec<Self>, [Vec<u64>; 2]), 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>, [Vec<u64>; 2]), 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>, [Vec<u64>; 2]), Error> where Self: 'a + 'b {
Self::boolean_intersect_impl(a, b)
}
}
impl Transform for Solid {
fn translate(self, translation: DVec3) -> Self {
let inner = ffi::translate_shape(&self.inner, translation.x, translation.y, translation.z);
Solid {
#[cfg(feature = "color")]
colormap: self.colormap,
inner,
}
}
fn rotate(self, axis_origin: DVec3, axis_direction: DVec3, angle: f64) -> Self {
let inner = ffi::rotate_shape(&self.inner, axis_origin.x, axis_origin.y, axis_origin.z, axis_direction.x, axis_direction.y, axis_direction.z, angle);
Solid {
#[cfg(feature = "color")]
colormap: self.colormap,
inner,
}
}
fn scale(self, center: DVec3, factor: f64) -> Self {
let inner = ffi::scale_shape(&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,
)
}
fn mirror(self, plane_origin: DVec3, plane_normal: DVec3) -> Self {
let inner = ffi::mirror_shape(&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,
)
}
}
impl SolidExt for Solid {
type Elem = Solid;
fn clean(&self) -> Result<Self, Error> {
#[cfg(feature = "color")]
{
let r = ffi::clean_shape_full(&self.inner);
if r.is_null() {
return Err(Error::CleanFailed);
}
let inner = ffi::clean_shape_get(&r);
if inner.is_null() {
return Err(Error::CleanFailed);
}
let mapping = ffi::clean_shape_mapping(&r);
let mut colormap = std::collections::HashMap::new();
for pair in mapping.chunks(2) {
let new_id = pair[0];
let old_id = pair[1];
if let Some(&color) = self.colormap.get(&old_id) {
colormap.entry(new_id).or_insert(color);
}
}
return Ok(Solid::new(inner, colormap));
}
#[cfg(not(feature = "color"))]
{
let inner = ffi::clean_shape(&self.inner);
if inner.is_null() {
return Err(Error::CleanFailed);
}
Ok(Solid::new(inner))
}
}
fn volume(&self) -> f64 {
ffi::shape_volume(&self.inner)
}
fn shell_count(&self) -> u32 {
ffi::shape_shell_count(&self.inner)
}
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 = FaceIterator::new(ffi::explore_faces(&self.inner)).map(|f| (f.tshape_id(), c)).collect();
Self::new(self.inner, colormap)
}
#[cfg(feature = "color")]
fn color_clear(self) -> Self {
Self::new(self.inner, std::collections::HashMap::new())
}
fn union_with_metadata<'a>(&self, tool: impl IntoIterator<Item = &'a Solid>) -> Result<(Vec<Solid>, [Vec<u64>; 2]), Error> {
<Solid as SolidStruct>::boolean_union([self], tool)
}
fn subtract_with_metadata<'a>(&self, tool: impl IntoIterator<Item = &'a Solid>) -> Result<(Vec<Solid>, [Vec<u64>; 2]), Error> {
<Solid as SolidStruct>::boolean_subtract([self], tool)
}
fn intersect_with_metadata<'a>(&self, tool: impl IntoIterator<Item = &'a Solid>) -> Result<(Vec<Solid>, [Vec<u64>; 2]), Error> {
<Solid as SolidStruct>::boolean_intersect([self], tool)
}
}
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,
)
}
}
#[cfg(feature = "color")]
fn merge_colormaps(from_a: &[u64], from_b: &[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 from_a.chunks(2) {
if let Some(&color) = colormap_a.get(&pair[1]) {
result.insert(pair[0], color);
}
}
for pair in from_b.chunks(2) {
if let Some(&color) = colormap_b.get(&pair[1]) {
result.insert(pair[0], color);
}
}
result
}
#[cfg_attr(not(feature = "color"), allow(unused_variables))]
fn build_boolean_result(r: cxx::UniquePtr<ffi::BooleanShape>, ca: Compound, cb: Compound) -> Result<(Vec<Solid>, [Vec<u64>; 2]), Error> {
let from_a = ffi::boolean_shape_from_a(&r);
let from_b = ffi::boolean_shape_from_b(&r);
let inner = ffi::boolean_shape_shape(&r);
#[cfg(feature = "color")]
let colormap = merge_colormaps(&from_a, &from_b, ca.colormap(), cb.colormap());
let compound = Compound::from_raw(
inner,
#[cfg(feature = "color")]
colormap,
);
Ok((compound.decompose(), [from_a, from_b]))
}
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>, [Vec<u64>; 2]), Error> {
let ca = Compound::new(a);
let cb = Compound::new(b);
let r = ffi::boolean_op(ca.inner(), cb.inner(), op_kind);
if r.is_null() { return Err(Error::BooleanOperationFailed); }
build_boolean_result(r, 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>, [Vec<u64>; 2]), 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>, [Vec<u64>; 2]), 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>, [Vec<u64>; 2]), Error> {
Self::boolean_op_impl(a, b, BOOLEAN_OP_COMMON)
}
}