diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 96913c5f..18432331 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -39,4 +39,4 @@ jobs: - name: Build run: cargo build --verbose --workspace --all-features - name: Run tests - run: cargo test --verbose --workspace --all-features + run: cargo test --verbose --workspace --all-features -- --include-ignored diff --git a/crates/occara/cpp/geom.cpp b/crates/occara/cpp/geom.cpp index 0c9e6562..702821fa 100644 --- a/crates/occara/cpp/geom.cpp +++ b/crates/occara/cpp/geom.cpp @@ -54,6 +54,13 @@ Direction Direction::create(Standard_Real x, Standard_Real y, Standard_Real z) { Direction Direction::clone() const { return *this; } +void Direction::get_components(Standard_Real &x, Standard_Real &y, + Standard_Real &z) const { + x = direction.X(); + y = direction.Y(); + z = direction.Z(); +} + // Direction2D Direction2D Direction2D::create(Standard_Real x, Standard_Real y) { @@ -62,6 +69,11 @@ Direction2D Direction2D::create(Standard_Real x, Standard_Real y) { Direction2D Direction2D::clone() const { return *this; } +void Direction2D::get_components(Standard_Real &x, Standard_Real &y) const { + x = direction.X(); + y = direction.Y(); +} + // Axis Axis Axis::create(const Point &origin, const Direction &direction) { @@ -70,6 +82,10 @@ Axis Axis::create(const Point &origin, const Direction &direction) { Axis Axis::clone() const { return *this; } +Point Axis::location() const { return Point{axis.Location()}; } + +Direction Axis::direction() const { return Direction{axis.Direction()}; } + // Axis2D Axis2D Axis2D::create(const Point2D &origin, const Direction2D &direction) { @@ -78,6 +94,10 @@ Axis2D Axis2D::create(const Point2D &origin, const Direction2D &direction) { Axis2D Axis2D::clone() const { return *this; } +Point2D Axis2D::location() const { return Point2D{axis.Location()}; } + +Direction2D Axis2D::direction() const { return Direction2D{axis.Direction()}; } + // PlaneAxis PlaneAxis PlaneAxis::create(const Point &origin, const Direction &direction) { @@ -86,6 +106,10 @@ PlaneAxis PlaneAxis::create(const Point &origin, const Direction &direction) { PlaneAxis PlaneAxis::clone() const { return *this; } +Point PlaneAxis::location() const { return Point{axis.Location()}; } + +Direction PlaneAxis::direction() const { return Direction{axis.Direction()}; } + // SpaceAxis SpaceAxis SpaceAxis::create(const Point &origin, const Direction &direction) { @@ -94,6 +118,10 @@ SpaceAxis SpaceAxis::create(const Point &origin, const Direction &direction) { SpaceAxis SpaceAxis::clone() const { return *this; } +Point SpaceAxis::location() const { return Point{axis.Location()}; } + +Direction SpaceAxis::direction() const { return Direction{axis.Direction()}; } + // TrimmedCurve TrimmedCurve TrimmedCurve::arc_of_circle(const Point &p1, const Point &p2, diff --git a/crates/occara/cpp/shape.cpp b/crates/occara/cpp/shape.cpp index 8c6ea89a..a77b4d16 100644 --- a/crates/occara/cpp/shape.cpp +++ b/crates/occara/cpp/shape.cpp @@ -1,7 +1,11 @@ #include "shape.hpp" +#include "BRepAlgoAPI_Common.hxx" +#include "BRepAlgoAPI_Cut.hxx" #include "BRepAlgoAPI_Fuse.hxx" #include "BRepPrimAPI_MakeCylinder.hxx" +#include #include +#include namespace occara::shape { @@ -64,6 +68,14 @@ Shape Shape::fuse(const Shape &other) const { return Shape{BRepAlgoAPI_Fuse(shape, other.shape).Shape()}; } +Shape Shape::subtract(const Shape &other) const { + return Shape{BRepAlgoAPI_Cut(shape, other.shape).Shape()}; +} + +Shape Shape::intersect(const Shape &other) const { + return Shape{BRepAlgoAPI_Common(shape, other.shape).Shape()}; +} + Shape Shape::cylinder(const occara::geom::PlaneAxis &axis, Standard_Real radius, Standard_Real height) { BRepPrimAPI_MakeCylinder cylinder(axis.axis, radius, height); @@ -86,7 +98,6 @@ Mesh Shape::mesh() const { // Perform meshing BRepMesh_IncrementalMesh mesher(shape, meshParams); - std::cout << "Mesh called\n"; // Collect vertices and indices std::vector vertices; @@ -95,13 +106,11 @@ Mesh Shape::mesh() const { TopExp_Explorer faceExplorer(shape, TopAbs_FACE); for (; faceExplorer.More(); faceExplorer.Next()) { TopoDS_Face face = TopoDS::Face(faceExplorer.Current()); - std::cerr << "Face\n"; TopLoc_Location loc; Handle(Poly_Triangulation) triangulation = BRep_Tool::Triangulation(face, loc); if (triangulation.IsNull()) { - std::cerr << "Triangulation is null for face\n"; continue; } @@ -127,6 +136,20 @@ Mesh Shape::mesh() const { }; } +ShapeType Shape::shape_type() const { + return static_cast(shape.ShapeType()); +} + +Standard_Boolean Shape::is_null() const { return shape.IsNull(); } + +Standard_Boolean Shape::is_closed() const { return shape.Closed(); } + +Standard_Real Shape::mass() const { + GProp_GProps props; + BRepGProp::VolumeProperties(shape, props); + return props.Mass(); +} + // Edge Edge Edge::from_curve(const occara::geom::TrimmedCurve &curve) { diff --git a/crates/occara/include/geom.hpp b/crates/occara/include/geom.hpp index 95ddd3ed..f599c5b1 100644 --- a/crates/occara/include/geom.hpp +++ b/crates/occara/include/geom.hpp @@ -70,6 +70,9 @@ struct Direction { static Direction create(Standard_Real x, Standard_Real y, Standard_Real z); Direction clone() const; + + void get_components(Standard_Real &x, Standard_Real &y, + Standard_Real &z) const; }; struct Direction2D { @@ -77,6 +80,8 @@ struct Direction2D { static Direction2D create(Standard_Real x, Standard_Real y); Direction2D clone() const; + + void get_components(Standard_Real &x, Standard_Real &y) const; }; struct Axis { @@ -84,6 +89,9 @@ struct Axis { static Axis create(const Point &origin, const Direction &direction); Axis clone() const; + + Point location() const; + Direction direction() const; }; struct Axis2D { @@ -91,6 +99,9 @@ struct Axis2D { static Axis2D create(const Point2D &origin, const Direction2D &direction); Axis2D clone() const; + + Point2D location() const; + Direction2D direction() const; }; struct PlaneAxis { @@ -98,6 +109,9 @@ struct PlaneAxis { static PlaneAxis create(const Point &origin, const Direction &direction); PlaneAxis clone() const; + + Point location() const; + Direction direction() const; }; struct SpaceAxis { @@ -105,6 +119,9 @@ struct SpaceAxis { static SpaceAxis create(const Point &origin, const Direction &direction); SpaceAxis clone() const; + + Point location() const; + Direction direction() const; }; struct TrimmedCurve { diff --git a/crates/occara/include/shape.hpp b/crates/occara/include/shape.hpp index 4ea6bc63..89391ef1 100644 --- a/crates/occara/include/shape.hpp +++ b/crates/occara/include/shape.hpp @@ -69,6 +69,19 @@ struct ShellBuilder { Shape build(); }; +// This is equal to TopAbs_ShapeEnum +enum class ShapeType { + Compound, + CompoundSolid, + Solid, + Shell, + Face, + Wire, + Edge, + Vertex, + Shape +}; + struct Shape { TopoDS_Shape shape; @@ -76,9 +89,15 @@ struct Shape { FilletBuilder fillet() const; Shape fuse(const Shape &other) const; + Shape subtract(const Shape &other) const; + Shape intersect(const Shape &other) const; static Shape cylinder(const occara::geom::PlaneAxis &axis, Standard_Real radius, Standard_Real height); Mesh mesh() const; + ShapeType shape_type() const; + Standard_Boolean is_null() const; + Standard_Boolean is_closed() const; + Standard_Real mass() const; }; struct Edge { diff --git a/crates/occara/src/geom.rs b/crates/occara/src/geom.rs index a5d561cc..1f50f5f0 100644 --- a/crates/occara/src/geom.rs +++ b/crates/occara/src/geom.rs @@ -1,6 +1,6 @@ use crate::ffi::occara::geom as ffi_geom; use autocxx::prelude::*; -use std::pin::Pin; +use std::{fmt, pin::Pin}; pub struct Point(pub(crate) Pin>); @@ -55,6 +55,32 @@ impl Clone for Point { } } +impl fmt::Display for Point { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let (x, y, z) = self.get_coordinates(); + write!(f, "Vertex({x:.6}, {y:.6}, {z:.6})") + } +} + +impl fmt::Debug for Point { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let (x, y, z) = self.get_coordinates(); + f.debug_struct("Vertex") + .field("x", &x) + .field("y", &y) + .field("z", &z) + .finish() + } +} + +// SAFETY: Safe because the underlying C++ type contains no thread-local state +// and all internal data is properly encapsulated. +unsafe impl Send for Point {} + +// SAFETY: Safe because this type provides no shared mutable access, and the underlying +// C++ type is designed for thread-safe read operations. +unsafe impl Sync for Point {} + pub struct Direction(pub(crate) Pin>); impl Direction { @@ -77,6 +103,15 @@ impl Direction { pub fn z() -> Self { Self::new(0.0, 0.0, 1.0) } + // TODO: maybe rename x/y/z to new_x/new_y/new_z and add x() -> f64 + + #[must_use] + pub fn get_components(&self) -> (f64, f64, f64) { + let (mut x, mut y, mut z) = (0.0, 0.0, 0.0); + self.0 + .get_components(Pin::new(&mut x), Pin::new(&mut y), Pin::new(&mut z)); + (x, y, z) + } } impl Clone for Direction { @@ -85,6 +120,32 @@ impl Clone for Direction { } } +impl fmt::Display for Direction { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let (x, y, z) = self.get_components(); + write!(f, "Vertex({x:.6}, {y:.6}, {z:.6})") + } +} + +impl fmt::Debug for Direction { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let (x, y, z) = self.get_components(); + f.debug_struct("Direction") + .field("x", &x) + .field("y", &y) + .field("z", &z) + .finish() + } +} + +// SAFETY: Safe because the underlying C++ type contains no thread-local state +// and all internal data is properly encapsulated. +unsafe impl Send for Direction {} + +// SAFETY: Safe because this type provides no shared mutable access, and the underlying +// C++ type is designed for thread-safe read operations. +unsafe impl Sync for Direction {} + pub struct Axis(pub(crate) Pin>); impl Axis { @@ -92,6 +153,16 @@ impl Axis { pub fn new(location: &Point, direction: &Direction) -> Self { Self(ffi_geom::Axis::create(&location.0, &direction.0).within_box()) } + + #[must_use] + pub fn location(&self) -> Point { + Point(self.0.location().within_box()) + } + + #[must_use] + pub fn direction(&self) -> Direction { + Direction(self.0.direction().within_box()) + } } impl Clone for Axis { @@ -100,6 +171,35 @@ impl Clone for Axis { } } +impl fmt::Display for Axis { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let loc = self.location().get_coordinates(); + let dir = self.direction().get_components(); + write!( + f, + "Axis(loc: ({:.2}, {:.2}, {:.2}), dir: ({:.2}, {:.2}, {:.2}))", + loc.0, loc.1, loc.2, dir.0, dir.1, dir.2, + ) + } +} + +impl fmt::Debug for Axis { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Axis") + .field("location", &self.location()) + .field("direction", &self.direction()) + .finish() + } +} + +// SAFETY: Safe because the underlying C++ type contains no thread-local state +// and all internal data is properly encapsulated. +unsafe impl Send for Axis {} + +// SAFETY: Safe because this type provides no shared mutable access, and the underlying +// C++ type is designed for thread-safe read operations. +unsafe impl Sync for Axis {} + pub struct Point2D(pub(crate) Pin>); impl Point2D { @@ -142,6 +242,31 @@ impl Clone for Point2D { } } +impl fmt::Display for Point2D { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let (x, y) = self.get_coordinates(); + write!(f, "Point2D({x:.6}, {y:.6})") + } +} + +impl fmt::Debug for Point2D { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let (x, y) = self.get_coordinates(); + f.debug_struct("Point2D") + .field("x", &x) + .field("y", &y) + .finish() + } +} + +// SAFETY: Safe because the underlying C++ type contains no thread-local state +// and all internal data is properly encapsulated. +unsafe impl Send for Point2D {} + +// SAFETY: Safe because this type provides no shared mutable access, and the underlying +// C++ type is designed for thread-safe read operations. +unsafe impl Sync for Point2D {} + pub struct Direction2D(pub(crate) Pin>); impl Direction2D { @@ -159,6 +284,13 @@ impl Direction2D { pub fn y() -> Self { Self::new(0.0, 1.0) } + + #[must_use] + pub fn get_components(&self) -> (f64, f64) { + let (mut x, mut y) = (0.0, 0.0); + self.0.get_components(Pin::new(&mut x), Pin::new(&mut y)); + (x, y) + } } impl Clone for Direction2D { @@ -167,6 +299,31 @@ impl Clone for Direction2D { } } +impl fmt::Display for Direction2D { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let (x, y) = self.get_components(); + write!(f, "Direction2D({x:.6}, {y:.6})") + } +} + +impl fmt::Debug for Direction2D { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let (x, y) = self.get_components(); + f.debug_struct("Direction2D") + .field("x", &x) + .field("y", &y) + .finish() + } +} + +// SAFETY: Safe because the underlying C++ type contains no thread-local state +// and all internal data is properly encapsulated. +unsafe impl Send for Direction2D {} + +// SAFETY: Safe because this type provides no shared mutable access, and the underlying +// C++ type is designed for thread-safe read operations. +unsafe impl Sync for Direction2D {} + pub struct Axis2D(pub(crate) Pin>); impl Axis2D { @@ -174,6 +331,16 @@ impl Axis2D { pub fn new(location: &Point2D, direction: &Direction2D) -> Self { Self(ffi_geom::Axis2D::create(&location.0, &direction.0).within_box()) } + + #[must_use] + pub fn location(&self) -> Point2D { + Point2D(self.0.location().within_box()) + } + + #[must_use] + pub fn direction(&self) -> Direction2D { + Direction2D(self.0.direction().within_box()) + } } impl Clone for Axis2D { @@ -182,6 +349,35 @@ impl Clone for Axis2D { } } +impl fmt::Display for Axis2D { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let loc = self.location().get_coordinates(); + let dir = self.direction().get_components(); + write!( + f, + "Axis2D(loc: ({:.2}, {:.2}), dir: ({:.2}, {:.2}))", + loc.0, loc.1, dir.0, dir.1, + ) + } +} + +impl fmt::Debug for Axis2D { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Axis2D") + .field("location", &self.location()) + .field("direction", &self.direction()) + .finish() + } +} + +// SAFETY: Safe because the underlying C++ type contains no thread-local state +// and all internal data is properly encapsulated. +unsafe impl Send for Axis2D {} + +// SAFETY: Safe because this type provides no shared mutable access, and the underlying +// C++ type is designed for thread-safe read operations. +unsafe impl Sync for Axis2D {} + pub struct PlaneAxis(pub(crate) Pin>); impl PlaneAxis { @@ -189,6 +385,16 @@ impl PlaneAxis { pub fn new(location: &Point, direction: &Direction) -> Self { Self(ffi_geom::PlaneAxis::create(&location.0, &direction.0).within_box()) } + + #[must_use] + pub fn location(&self) -> Point { + Point(self.0.location().within_box()) + } + + #[must_use] + pub fn direction(&self) -> Direction { + Direction(self.0.direction().within_box()) + } } impl Clone for PlaneAxis { @@ -197,6 +403,35 @@ impl Clone for PlaneAxis { } } +impl fmt::Display for PlaneAxis { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let loc = self.location().get_coordinates(); + let dir = self.direction().get_components(); + write!( + f, + "PlaneAxis(loc: ({:.2}, {:.2}, {:.2}), dir: ({:.2}, {:.2}, {:.2}))", + loc.0, loc.1, loc.2, dir.0, dir.1, dir.2, + ) + } +} + +impl fmt::Debug for PlaneAxis { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("PlaneAxis") + .field("location", &self.location()) + .field("direction", &self.direction()) + .finish() + } +} + +// SAFETY: Safe because the underlying C++ type contains no thread-local state +// and all internal data is properly encapsulated. +unsafe impl Send for PlaneAxis {} + +// SAFETY: Safe because this type provides no shared mutable access, and the underlying +// C++ type is designed for thread-safe read operations. +unsafe impl Sync for PlaneAxis {} + pub struct SpaceAxis(pub(crate) Pin>); impl SpaceAxis { @@ -204,6 +439,16 @@ impl SpaceAxis { pub fn new(location: &Point, direction: &Direction) -> Self { Self(ffi_geom::SpaceAxis::create(&location.0, &direction.0).within_box()) } + + #[must_use] + pub fn location(&self) -> Point { + Point(self.0.location().within_box()) + } + + #[must_use] + pub fn direction(&self) -> Direction { + Direction(self.0.direction().within_box()) + } } impl Clone for SpaceAxis { @@ -212,6 +457,35 @@ impl Clone for SpaceAxis { } } +impl fmt::Display for SpaceAxis { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let loc = self.location().get_coordinates(); + let dir = self.direction().get_components(); + write!( + f, + "SpaceAxis(loc: ({:.2}, {:.2}, {:.2}), dir: ({:.2}, {:.2}, {:.2}))", + loc.0, loc.1, loc.2, dir.0, dir.1, dir.2, + ) + } +} + +impl fmt::Debug for SpaceAxis { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("SpaceAxis") + .field("location", &self.location()) + .field("direction", &self.direction()) + .finish() + } +} + +// SAFETY: Safe because the underlying C++ type contains no thread-local state +// and all internal data is properly encapsulated. +unsafe impl Send for SpaceAxis {} + +// SAFETY: Safe because this type provides no shared mutable access, and the underlying +// C++ type is designed for thread-safe read operations. +unsafe impl Sync for SpaceAxis {} + pub struct TrimmedCurve(pub(crate) Pin>); impl TrimmedCurve { @@ -247,6 +521,21 @@ impl Clone for TrimmedCurve2D { } } +impl fmt::Debug for TrimmedCurve2D { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // TODO: be more informative + f.debug_struct("TrimmedCurve2D").finish() + } +} + +// SAFETY: Safe because the underlying C++ type contains no thread-local state +// and all internal data is properly encapsulated. +unsafe impl Send for TrimmedCurve2D {} + +// SAFETY: Safe because this type provides no shared mutable access, and the underlying +// C++ type is designed for thread-safe read operations. +unsafe impl Sync for TrimmedCurve2D {} + pub struct Curve2D(pub(crate) Pin>); impl Curve2D { @@ -269,6 +558,21 @@ impl Clone for Curve2D { } } +impl fmt::Debug for Curve2D { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // TODO: be more informative + f.debug_struct("Curve2D").finish() + } +} + +// SAFETY: Safe because the underlying C++ type contains no thread-local state +// and all internal data is properly encapsulated. +unsafe impl Send for Curve2D {} + +// SAFETY: Safe because this type provides no shared mutable access, and the underlying +// C++ type is designed for thread-safe read operations. +unsafe impl Sync for Curve2D {} + pub struct Ellipse2D(pub(crate) Pin>); impl Ellipse2D { @@ -295,6 +599,21 @@ impl Clone for Ellipse2D { } } +impl fmt::Debug for Ellipse2D { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // TODO: be more informative + f.debug_struct("Ellipse2D").finish() + } +} + +// SAFETY: Safe because the underlying C++ type contains no thread-local state +// and all internal data is properly encapsulated. +unsafe impl Send for Ellipse2D {} + +// SAFETY: Safe because this type provides no shared mutable access, and the underlying +// C++ type is designed for thread-safe read operations. +unsafe impl Sync for Ellipse2D {} + pub struct Plane(pub(crate) Pin>); impl Plane { @@ -311,6 +630,21 @@ impl Clone for Plane { } } +impl fmt::Debug for Plane { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // TODO: be more informative + f.debug_struct("Plane").finish() + } +} + +// SAFETY: Safe because the underlying C++ type contains no thread-local state +// and all internal data is properly encapsulated. +unsafe impl Send for Plane {} + +// SAFETY: Safe because this type provides no shared mutable access, and the underlying +// C++ type is designed for thread-safe read operations. +unsafe impl Sync for Plane {} + pub struct Surface(pub(crate) Pin>); impl From<&CylindricalSurface> for Surface { @@ -337,6 +671,21 @@ impl Clone for Surface { } } +impl fmt::Debug for Surface { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // TODO: be more informative + f.debug_struct("Surface").finish() + } +} + +// SAFETY: Safe because the underlying C++ type contains no thread-local state +// and all internal data is properly encapsulated. +unsafe impl Send for Surface {} + +// SAFETY: Safe because this type provides no shared mutable access, and the underlying +// C++ type is designed for thread-safe read operations. +unsafe impl Sync for Surface {} + pub trait Transformable { #[must_use] fn transform(&self, transformation: &Transformation) -> Self; @@ -364,6 +713,21 @@ impl Clone for Transformation { } } +impl fmt::Debug for Transformation { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // TODO: be more informative + f.debug_struct("Transformation").finish() + } +} + +// SAFETY: Safe because the underlying C++ type contains no thread-local state +// and all internal data is properly encapsulated. +unsafe impl Send for Transformation {} + +// SAFETY: Safe because this type provides no shared mutable access, and the underlying +// C++ type is designed for thread-safe read operations. +unsafe impl Sync for Transformation {} + pub struct Vector(pub(crate) Pin>); impl Vector { @@ -379,6 +743,21 @@ impl Clone for Vector { } } +impl fmt::Debug for Vector { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // TODO: be more informative + f.debug_struct("Vector").finish() + } +} + +// SAFETY: Safe because the underlying C++ type contains no thread-local state +// and all internal data is properly encapsulated. +unsafe impl Send for Vector {} + +// SAFETY: Safe because this type provides no shared mutable access, and the underlying +// C++ type is designed for thread-safe read operations. +unsafe impl Sync for Vector {} + pub struct CylindricalSurface(pub(crate) Pin>); impl CylindricalSurface { @@ -393,3 +772,18 @@ impl Clone for CylindricalSurface { Self(self.0.clone().within_box()) } } + +impl fmt::Debug for CylindricalSurface { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // TODO: be more informative + f.debug_struct("CylindricalSurface").finish() + } +} + +// SAFETY: Safe because the underlying C++ type contains no thread-local state +// and all internal data is properly encapsulated. +unsafe impl Send for CylindricalSurface {} + +// SAFETY: Safe because this type provides no shared mutable access, and the underlying +// C++ type is designed for thread-safe read operations. +unsafe impl Sync for CylindricalSurface {} diff --git a/crates/occara/src/lib.rs b/crates/occara/src/lib.rs index 2d53ba61..dc1531b9 100644 --- a/crates/occara/src/lib.rs +++ b/crates/occara/src/lib.rs @@ -2,6 +2,9 @@ #![warn(clippy::pedantic)] #![allow(clippy::module_name_repetitions)] #![allow(clippy::cognitive_complexity)] +// We can not implement Send on the underlying autocxx managed raw pointer, therefore this +// lint is not applicable here. +#![allow(clippy::non_send_fields_in_send_ty)] mod ffi; diff --git a/crates/occara/src/shape.rs b/crates/occara/src/shape.rs index f11a0cbc..5033bcac 100644 --- a/crates/occara/src/shape.rs +++ b/crates/occara/src/shape.rs @@ -1,6 +1,7 @@ use super::ffi::occara::shape as ffi_shape; use crate::geom; use autocxx::prelude::*; +use std::fmt; use std::io::Write; use std::{fs::File, path::Path, pin::Pin}; @@ -44,8 +45,53 @@ impl Clone for Vertex { } } +impl fmt::Display for Vertex { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let (x, y, z) = self.get_coordinates(); + write!(f, "Vertex({x:.6}, {y:.6}, {z:.6})") + } +} + +impl fmt::Debug for Vertex { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let (x, y, z) = self.get_coordinates(); + f.debug_struct("Vertex") + .field("x", &x) + .field("y", &y) + .field("z", &z) + .finish() + } +} + +// SAFETY: Safe because the underlying C++ type contains no thread-local state +// and all internal data is properly encapsulated. +unsafe impl Send for Vertex {} + +// SAFETY: Safe because this type provides no shared mutable access, and the underlying +// C++ type is designed for thread-safe read operations. +unsafe impl Sync for Vertex {} + pub struct Shape(pub(crate) Pin>); +pub use crate::shape::ffi_shape::ShapeType; + +impl ShapeType { + #[must_use] + pub const fn to_str(&self) -> &'static str { + match self { + Self::Compound => "Compound", + Self::CompoundSolid => "CompoundSolid", + Self::Solid => "Solid", + Self::Shell => "Shell", + Self::Face => "Face", + Self::Wire => "Wire", + Self::Edge => "Edge", + Self::Vertex => "Vertex", + Self::Shape => "Shape", + } + } +} + impl Shape { #[must_use] pub fn fillet(&self) -> FilletBuilder { @@ -67,6 +113,16 @@ impl Shape { Self(self.0.fuse(&other.0).within_box()) } + #[must_use] + pub fn subtract(&self, other: &Self) -> Self { + Self(self.0.subtract(&other.0).within_box()) + } + + #[must_use] + pub fn intersect(&self, other: &Self) -> Self { + Self(self.0.intersect(&other.0).within_box()) + } + #[must_use] pub fn shell(&self) -> ShellBuilder { ShellBuilder(ffi_shape::ShellBuilder::create(&self.0).within_box()) @@ -81,6 +137,26 @@ impl Shape { pub fn mesh(&self) -> Mesh { Mesh(ffi_shape::Shape::mesh(&self.0).within_box()) } + + #[must_use] + pub fn shape_type(&self) -> ShapeType { + ffi_shape::Shape::shape_type(&self.0) + } + + #[must_use] + pub fn is_null(&self) -> bool { + ffi_shape::Shape::is_null(&self.0) + } + + #[must_use] + pub fn is_closed(&self) -> bool { + ffi_shape::Shape::is_closed(&self.0) + } + + #[must_use] + pub fn mass(&self) -> f64 { + ffi_shape::Shape::mass(&self.0) + } } impl Clone for Shape { @@ -89,6 +165,30 @@ impl Clone for Shape { } } +impl fmt::Display for Shape { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Shape({})", self.shape_type().to_str()) + } +} + +impl fmt::Debug for Shape { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Shape") + .field("type", &self.shape_type().to_str()) + .field("is_null", &self.is_null()) + .field("is_closed", &self.is_closed()) + .finish() + } +} + +// SAFETY: Safe because the underlying C++ type contains no thread-local state +// and all internal data is properly encapsulated. +unsafe impl Send for Shape {} + +// SAFETY: Safe because this type provides no shared mutable access, and the underlying +// C++ type is designed for thread-safe read operations. +unsafe impl Sync for Shape {} + pub struct Mesh(pub(crate) Pin>); impl Mesh { @@ -149,6 +249,23 @@ impl Mesh { } } +impl fmt::Debug for Mesh { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Mesh") + .field("vertices", &self.0.as_ref().vertices_size()) + .field("indices", &self.0.as_ref().indices_size()) + .finish() + } +} + +// SAFETY: Safe because the underlying C++ type contains no thread-local state +// and all internal data is properly encapsulated. +unsafe impl Send for Mesh {} + +// SAFETY: Safe because this type provides no shared mutable access, and the underlying +// C++ type is designed for thread-safe read operations. +unsafe impl Sync for Mesh {} + pub struct EdgeIterator(pub(crate) Pin>); impl Iterator for EdgeIterator { @@ -170,6 +287,21 @@ impl Clone for EdgeIterator { } } +impl fmt::Debug for EdgeIterator { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // TODO: be more informative + f.debug_struct("EdgeIterator").finish() + } +} + +// SAFETY: Safe because the underlying C++ type contains no thread-local state +// and all internal data is properly encapsulated. +unsafe impl Send for EdgeIterator {} + +// SAFETY: Safe because this type provides no shared mutable access, and the underlying +// C++ type is designed for thread-safe read operations. +unsafe impl Sync for EdgeIterator {} + pub struct FaceIterator(pub(crate) Pin>); impl Iterator for FaceIterator { @@ -191,6 +323,21 @@ impl Clone for FaceIterator { } } +impl fmt::Debug for FaceIterator { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // TODO: be more informative + f.debug_struct("FaceIterator").finish() + } +} + +// SAFETY: Safe because the underlying C++ type contains no thread-local state +// and all internal data is properly encapsulated. +unsafe impl Send for FaceIterator {} + +// SAFETY: Safe because this type provides no shared mutable access, and the underlying +// C++ type is designed for thread-safe read operations. +unsafe impl Sync for FaceIterator {} + pub struct FilletBuilder(pub(crate) Pin>); impl FilletBuilder { @@ -209,6 +356,21 @@ impl Clone for FilletBuilder { } } +impl fmt::Debug for FilletBuilder { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // TODO: be more informative + f.debug_struct("FilletBuilder").finish() + } +} + +// SAFETY: Safe because the underlying C++ type contains no thread-local state +// and all internal data is properly encapsulated. +unsafe impl Send for FilletBuilder {} + +// SAFETY: Safe because this type provides no shared mutable access, and the underlying +// C++ type is designed for thread-safe read operations. +unsafe impl Sync for FilletBuilder {} + pub struct ShellBuilder(pub(crate) Pin>); impl ShellBuilder { @@ -240,6 +402,21 @@ impl Clone for ShellBuilder { } } +impl fmt::Debug for ShellBuilder { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // TODO: be more informative + f.debug_struct("ShellBuilder").finish() + } +} + +// SAFETY: Safe because the underlying C++ type contains no thread-local state +// and all internal data is properly encapsulated. +unsafe impl Send for ShellBuilder {} + +// SAFETY: Safe because this type provides no shared mutable access, and the underlying +// C++ type is designed for thread-safe read operations. +unsafe impl Sync for ShellBuilder {} + pub struct Edge(pub(crate) Pin>); impl Edge { @@ -271,6 +448,21 @@ impl From for Edge { } } +impl fmt::Debug for Edge { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // TODO: be more informative + f.debug_struct("Edge").finish() + } +} + +// SAFETY: Safe because the underlying C++ type contains no thread-local state +// and all internal data is properly encapsulated. +unsafe impl Send for Edge {} + +// SAFETY: Safe because this type provides no shared mutable access, and the underlying +// C++ type is designed for thread-safe read operations. +unsafe impl Sync for Edge {} + pub struct Face(pub(crate) Pin>); impl Face { @@ -291,6 +483,21 @@ impl Clone for Face { } } +impl fmt::Debug for Face { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // TODO: be more informative + f.debug_struct("Face").finish() + } +} + +// SAFETY: Safe because the underlying C++ type contains no thread-local state +// and all internal data is properly encapsulated. +unsafe impl Send for Face {} + +// SAFETY: Safe because this type provides no shared mutable access, and the underlying +// C++ type is designed for thread-safe read operations. +unsafe impl Sync for Face {} + pub struct Wire(pub(crate) Pin>); impl Wire { @@ -323,6 +530,21 @@ impl Clone for Wire { } } +impl fmt::Debug for Wire { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // TODO: be more informative + f.debug_struct("Wire").finish() + } +} + +// SAFETY: Safe because the underlying C++ type contains no thread-local state +// and all internal data is properly encapsulated. +unsafe impl Send for Wire {} + +// SAFETY: Safe because this type provides no shared mutable access, and the underlying +// C++ type is designed for thread-safe read operations. +unsafe impl Sync for Wire {} + impl geom::Transformable for Wire { fn transform(&self, transformation: &geom::Transformation) -> Self { let transformed_wire = self.0.transform(&transformation.0).within_box(); @@ -378,6 +600,21 @@ impl Clone for Loft { } } +impl fmt::Debug for Loft { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // TODO: be more informative + f.debug_struct("Loft").finish() + } +} + +// SAFETY: Safe because the underlying C++ type contains no thread-local state +// and all internal data is properly encapsulated. +unsafe impl Send for Loft {} + +// SAFETY: Safe because this type provides no shared mutable access, and the underlying +// C++ type is designed for thread-safe read operations. +unsafe impl Sync for Loft {} + pub struct Compound(pub(crate) Pin>); impl Default for Compound { @@ -401,3 +638,18 @@ impl Compound { Shape(self.0.as_mut().build().within_box()) } } + +impl fmt::Debug for Compound { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // TODO: be more informative + f.debug_struct("Loft").finish() + } +} + +// SAFETY: Safe because the underlying C++ type contains no thread-local state +// and all internal data is properly encapsulated. +unsafe impl Send for Compound {} + +// SAFETY: Safe because this type provides no shared mutable access, and the underlying +// C++ type is designed for thread-safe read operations. +unsafe impl Sync for Compound {} diff --git a/crates/occara/tests/make_bottle.rs b/crates/occara/tests/make_bottle.rs index 0954ef0d..4471f952 100644 --- a/crates/occara/tests/make_bottle.rs +++ b/crates/occara/tests/make_bottle.rs @@ -5,6 +5,8 @@ use occara::geom::{ use occara::shape::{Compound, Edge, Loft, Shape, Wire}; use ordered_float::OrderedFloat; use std::f64::consts::PI; +use std::sync::Arc; +use std::thread; fn make_bottle_rust(width: f64, height: f64, thickness: f64) -> Shape { // Define first half of the profile @@ -119,15 +121,29 @@ fn make_bottle_rust(width: f64, height: f64, thickness: f64) -> Shape { } #[test] +#[ignore] // Don't run this test by default, since its quite slow fn test_make_bottle() { use occara::internal::make_bottle_cpp; - let width = 50.0; - let height = 70.0; - let thickness = 30.0; + const WIDTH: f64 = 50.0; + const HEIGHT: f64 = 70.0; + const THICKNESS: f64 = 30.0; + const EPSILON: f64 = 1e-6; - let _bottle_rust = make_bottle_rust(width, height, thickness); - let _result_cpp = make_bottle_cpp(width, height, thickness); + let bottle_rust = thread::spawn(move || make_bottle_rust(WIDTH, HEIGHT, THICKNESS)); + let bottle_cpp = thread::spawn(move || make_bottle_cpp(WIDTH, HEIGHT, THICKNESS)); - // TODO: Compare the two shapes + let bottle_rust = bottle_rust.join().unwrap(); + let bottle_cpp = bottle_cpp.join().unwrap(); + + let bottle_rust = Arc::new(bottle_rust); + let bottle_rust2 = bottle_rust.clone(); + let bottle_cpp = Arc::new(bottle_cpp); + let bottle_cpp2 = bottle_cpp.clone(); + + let error_rust_cpp = thread::spawn(move || bottle_rust.subtract(&bottle_cpp).mass()); + let error_cpp_rust = thread::spawn(move || bottle_cpp2.subtract(&bottle_rust2).mass()); + + assert!(error_rust_cpp.join().unwrap() < EPSILON); + assert!(error_cpp_rust.join().unwrap() < EPSILON); } diff --git a/crates/occara/tests/thread_safety.rs b/crates/occara/tests/thread_safety.rs new file mode 100644 index 00000000..1a4e3019 --- /dev/null +++ b/crates/occara/tests/thread_safety.rs @@ -0,0 +1,37 @@ +use std::{sync::Arc, thread}; + +use occara::{ + geom::{Direction, Point}, + shape::Shape, +}; + +#[test] +fn test_thread_safety() { + // This is a basic test for verifying that this library is probably thread safe. + // Since all wrapper types function roughly in the same way, we will only test `Mesh` here. + // This test passes while using valgrind. + // It may still be possible that (form a type system) independent objects cause a race condition. + let plane = Point::new(0.0, 0.0, 0.0).plane_axis_with(&Direction::z()); + let mesh = Shape::cylinder(&plane, 1.0, 1.0).mesh(); + + let mesh = Arc::new(mesh); + let handles: Vec<_> = (0..200) + .map(|_| { + let m = mesh.clone(); + thread::spawn(move || { + let _ = m.vertices(); + let _ = m.indices(); + }) + }) + .collect(); + for h in handles { + h.join().unwrap(); + } + + thread::spawn(|| { + // This tests the Send trait + std::mem::drop(mesh); + }) + .join() + .unwrap(); +}