├── include └── occutils │ ├── Shapes.hxx │ ├── Curves.hxx │ ├── Pipe.hxx │ ├── STEPExport.hxx │ ├── Plane.hxx │ ├── PrintOCC.hxx │ ├── Compound.hxx │ ├── Fillet.hxx │ ├── Equality.hxx │ ├── Line.hxx │ ├── ExtendedSTEP.hxx │ ├── Direction.hxx │ ├── Exceptions.hxx │ ├── Point.hxx │ ├── Edge.hxx │ ├── Shape.hxx │ ├── Curve.hxx │ ├── Face.hxx │ ├── Wire.hxx │ ├── IO.hxx │ ├── ListUtils.hxx │ ├── Primitive.hxx │ ├── Axis.hxx │ ├── Boolean.hxx │ ├── Surface.hxx │ └── ShapeComponents.hxx ├── .gitignore ├── src ├── Pipe.cxx ├── PrintOCC.cxx ├── Plane.cxx ├── Compound.cxx ├── Equality.cxx ├── STEPExport.cxx ├── Line.cxx ├── Fillet.cxx ├── Direction.cxx ├── Face.cxx ├── Edge.cxx ├── Axis.cxx ├── Shape.cxx ├── Curve.cxx ├── IO.cxx ├── Point.cxx ├── Primitive.cxx ├── ExtendedSTEP.cxx ├── Boolean.cxx ├── Wire.cxx ├── Surface.cxx └── ShapeComponents.cxx ├── tests └── TestLine.cpp ├── CMakeLists.txt ├── README.md └── LICENSE /include/occutils/Shapes.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | // Dummy header in case someone tries to include Shapes.hxx instead of Shape.hxx 3 | #include -------------------------------------------------------------------------------- /include/occutils/Curves.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | // Dummy header to prevent errors if one imports Curves.hxx instead of Curve.hxx 3 | #include 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | CMakeCache.txt 2 | CMakeFiles 3 | CMakeScripts 4 | Testing 5 | Makefile 6 | cmake_install.cmake 7 | install_manifest.txt 8 | compile_commands.json 9 | CTestTestfile.cmake 10 | occutils.cmake 11 | *.so 12 | test_occutils 13 | build 14 | .vscode -------------------------------------------------------------------------------- /include/occutils/Pipe.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | namespace OCCUtils { 6 | namespace Pipe { 7 | TopoDS_Shape FromSplineAndProfile(const TopoDS_Wire& wire, const TopoDS_Shape& profile); 8 | } 9 | } -------------------------------------------------------------------------------- /src/Pipe.cxx: -------------------------------------------------------------------------------- 1 | #include "occutils/Pipe.hxx" 2 | #include 3 | 4 | TopoDS_Shape OCCUtils::Pipe::FromSplineAndProfile(const TopoDS_Wire& wire, const TopoDS_Shape& profile) { 5 | BRepOffsetAPI_MakePipe makePipe(wire, profile); 6 | makePipe.Build(); 7 | return makePipe.Shape(); 8 | } -------------------------------------------------------------------------------- /include/occutils/STEPExport.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | namespace OCCUtils { 6 | namespace STEP { 7 | /** 8 | * Export a shape to a file 9 | * Units: M, MM, KM, INCH, FT, MI, MIL, UM, CM, UIN 10 | */ 11 | void ExportSTEP( 12 | const TopoDS_Shape& shape, 13 | const std::string& filename, 14 | const std::string& unit = "MM"); 15 | } 16 | } -------------------------------------------------------------------------------- /include/occutils/Plane.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | namespace OCCUtils { 6 | namespace Plane { 7 | /** 8 | * Construct an infinite plane from three points. 9 | * The plane's cartesion coordinate system is: 10 | * - (U,V)(0,0) = pO 11 | * - X axis = gp_Vec(pO, pX) 12 | * - Y axis = gp_Vec(pO, pY) 13 | * Precondition: No pair of points must coincide. 14 | * @throws OCCConstructionFailedException() if the parameters are invalid 15 | */ 16 | gp_Pln FromPoints(const gp_Pnt& pO, const gp_Pnt& pX, const gp_Pnt& pY); 17 | } 18 | } -------------------------------------------------------------------------------- /src/PrintOCC.cxx: -------------------------------------------------------------------------------- 1 | #include "occutils/PrintOCC.hxx" 2 | 3 | std::ostream &operator<<(std::ostream &os, const gp_Pnt2d &xy) { return os << "gp_Pnt2d " << xy.XY(); } 4 | std::ostream &operator<<(std::ostream &os, const gp_Pnt &xy) { return os << "gp_Pnt " << xy.XYZ(); } 5 | std::ostream &operator<<(std::ostream &os, const gp_Dir2d &xy) { return os << "gp_Dir2d " << xy.XY(); } 6 | std::ostream &operator<<(std::ostream &os, const gp_Dir &xy) { return os << "gp_Dir " << xy.XYZ(); } 7 | 8 | 9 | std::ostream &operator<<(std::ostream &os, const gp_XY &xy) { return (os << '[' << xy.X() << ", " << xy.Y() << ']'); } 10 | 11 | std::ostream &operator<<(std::ostream &os, const gp_XYZ &xyz) { return (os << '[' << xyz.X() << ", " << xyz.Y() << ", " << xyz.Z() << ']'); } 12 | 13 | std::ostream &operator<<(std::ostream &os, const gp_Vec &vec) { 14 | return os << "gp_Vec " << vec.XYZ(); 15 | } 16 | -------------------------------------------------------------------------------- /include/occutils/PrintOCC.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /** 3 | * Include this header to provide operator<< instances 4 | * for common OpenCASCADE types so that you can print them 5 | * using cout, cerr etc. 6 | */ 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | std::ostream &operator<<(std::ostream &os, const gp_Pnt2d &xy); 17 | std::ostream &operator<<(std::ostream &os, const gp_Pnt &xy); 18 | std::ostream &operator<<(std::ostream &os, const gp_Dir2d &xy); 19 | std::ostream &operator<<(std::ostream &os, const gp_Dir &xy); 20 | std::ostream &operator<<(std::ostream &os, const gp_XY &xy); 21 | std::ostream &operator<<(std::ostream &os, const gp_XYZ &xyz); 22 | std::ostream &operator<<(std::ostream &os, const gp_Vec &vec); 23 | -------------------------------------------------------------------------------- /include/occutils/Compound.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /** 3 | * Boolean operation utilities 4 | */ 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace OCCUtils { 15 | namespace Compound { 16 | 17 | /** 18 | * Create a compound from multiple shapes 19 | */ 20 | TopoDS_Compound From(const TopTools_ListOfShape& shapes); 21 | 22 | /** 23 | * Create a compound from multiple shapes 24 | */ 25 | TopoDS_Compound From(const std::vector& shapes); 26 | TopoDS_Compound From(const std::vector& shapes); 27 | TopoDS_Compound From(const std::vector& shapes); 28 | } 29 | } -------------------------------------------------------------------------------- /src/Plane.cxx: -------------------------------------------------------------------------------- 1 | #include "occutils/Plane.hxx" 2 | #include "occutils/Exceptions.hxx" 3 | #include "occutils/Equality.hxx" 4 | 5 | gp_Pln OCCUtils::Plane::FromPoints(const gp_Pnt& pO, const gp_Pnt& pX, const gp_Pnt& pY) { 6 | if(pO == pX) { 7 | throw OCCConstructionFailedException("Plane construction failed: pO coincides with pX"); 8 | } else if(pO == pY) { 9 | throw OCCConstructionFailedException("Plane construction failed: pO coincides with pY"); 10 | } else if(pX == pY) { 11 | throw OCCConstructionFailedException("Plane construction failed: pX coincides with pY"); 12 | } 13 | gp_Dir xAx(gp_Vec(pO, pX)); 14 | gp_Dir yAx(gp_Vec(pO, pY)); 15 | if(!xAx.IsNormal(yAx, Precision::Angular())) { 16 | throw OCCConstructionFailedException("Plane construction failed: Axes are not normal to each other"); 17 | } 18 | // Ax3 takes normal axis 19 | gp_Dir normal = xAx.Crossed(yAx); 20 | return gp_Pln(gp_Ax3(pO, normal, xAx)); 21 | } -------------------------------------------------------------------------------- /include/occutils/Fillet.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | namespace OCCUtils { 7 | namespace Fillet { 8 | /** 9 | * Fillet all edges of the given shape using a single radius 10 | * and return the filleted shape. 11 | * 12 | * Usually the shape is a solid. 13 | */ 14 | TopoDS_Shape FilletAll(const TopoDS_Shape& shape, double radius=1.0); 15 | 16 | 17 | /** 18 | * Fillet all edges of the given shape using a 19 | * user-defined function to define the radius separately 20 | * for each edge. 21 | * 22 | * radiusByEdge takes the edge and should return either: 23 | * - A radius 24 | * - or NaN if this edge should NOT be filleted 25 | * 26 | * Usually the shape is a solid. 27 | */ 28 | TopoDS_Shape FilletAdaptiveRadius(const TopoDS_Shape& shape, std::function radiusByEdge); 29 | } 30 | } -------------------------------------------------------------------------------- /include/occutils/Equality.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /** 3 | * Include this file for equality & inequality operators 4 | * for different datatypes where this makes sense. 5 | * 6 | * All equalities are computed with Precision::Confusion() 7 | * as tolerance. 8 | */ 9 | #include 10 | #include 11 | #include 12 | 13 | bool operator==(const gp_Pnt &a, const gp_Pnt &b); 14 | bool operator!=(const gp_Pnt &a, const gp_Pnt &b); 15 | 16 | bool operator==(const gp_XYZ &a, const gp_XYZ &b); 17 | bool operator!=(const gp_XYZ &a, const gp_XYZ &b); 18 | 19 | bool operator==(const gp_Pnt2d &a, const gp_Pnt2d &b); 20 | bool operator!=(const gp_Pnt2d &a, const gp_Pnt2d &b); 21 | 22 | bool operator==(const gp_XY &a, const gp_XY &b); 23 | bool operator!=(const gp_XY &a, const gp_XY &b); 24 | 25 | /** 26 | * Checks vector equality with: 27 | * - Precision::Confusion() linear & 28 | * - Precision::Angular() angular 29 | * tolerances using gpVec::IsEqual(). 30 | */ 31 | bool operator==(const gp_Vec &a, const gp_Vec &b); 32 | bool operator!=(const gp_Vec &a, const gp_Vec &b); -------------------------------------------------------------------------------- /src/Compound.cxx: -------------------------------------------------------------------------------- 1 | #include "occutils/Compound.hxx" 2 | #include "occutils/ListUtils.hxx" 3 | #include "occutils/Shape.hxx" 4 | #include 5 | 6 | using namespace OCCUtils; 7 | 8 | 9 | template 10 | TopoDS_Compound _ToCompound(const T& shapes) { 11 | BRep_Builder builder; 12 | TopoDS_Compound compound; 13 | builder.MakeCompound (compound); 14 | for (const auto& shape : shapes) { 15 | if (!shape.IsNull()) { 16 | builder.Add (compound, shape); 17 | } 18 | } 19 | return compound; 20 | } 21 | 22 | TopoDS_Compound OCCUtils::Compound::From(const TopTools_ListOfShape& shapes) { 23 | return _ToCompound(shapes); 24 | } 25 | 26 | TopoDS_Compound OCCUtils::Compound::From(const std::vector& shapes) { 27 | return _ToCompound(shapes); 28 | } 29 | 30 | TopoDS_Compound OCCUtils::Compound::From(const std::vector& shapes) { 31 | return _ToCompound(shapes); 32 | } 33 | 34 | TopoDS_Compound OCCUtils::Compound::From(const std::vector& shapes) { 35 | return _ToCompound(shapes); 36 | } 37 | -------------------------------------------------------------------------------- /src/Equality.cxx: -------------------------------------------------------------------------------- 1 | #include "occutils/Equality.hxx" 2 | #include 3 | 4 | bool operator==(const gp_Pnt &a, const gp_Pnt &b) { 5 | return a.IsEqual(b, Precision::Confusion()); 6 | } 7 | 8 | bool operator!=(const gp_Pnt &a, const gp_Pnt &b) { 9 | return !(a == b); 10 | } 11 | 12 | bool operator==(const gp_XYZ &a, const gp_XYZ &b){ 13 | return a.IsEqual(b, Precision::Confusion()); 14 | } 15 | 16 | bool operator!=(const gp_XYZ &a, const gp_XYZ &b){ 17 | return !(a == b); 18 | } 19 | 20 | bool operator==(const gp_Pnt2d &a, const gp_Pnt2d &b) { 21 | return a.IsEqual(b, Precision::Confusion()); 22 | } 23 | 24 | bool operator!=(const gp_Pnt2d &a, const gp_Pnt2d &b) { 25 | return !(a == b); 26 | } 27 | 28 | bool operator==(const gp_XY &a, const gp_XY &b) { 29 | return a.IsEqual(b, Precision::Confusion()); 30 | 31 | } 32 | 33 | bool operator!=(const gp_XY &a, const gp_XY &b) { 34 | return !(a == b); 35 | } 36 | 37 | bool operator==(const gp_Vec &a, const gp_Vec &b){ 38 | return a.IsEqual(b, Precision::Confusion(), Precision::Angular()); 39 | } 40 | 41 | bool operator!=(const gp_Vec &a, const gp_Vec &b){ 42 | return !(a == b); 43 | } 44 | -------------------------------------------------------------------------------- /include/occutils/Line.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /** 3 | * Utilities for analyzing lines 4 | */ 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace OCCUtils { 11 | namespace Line { 12 | 13 | /** 14 | * @return true only if lin1 is normal to lin2 within angularTolerance 15 | */ 16 | bool IsNormal(const gp_Lin &lin1, const gp_Lin &lin2, double angularTolerance=1e-6); 17 | 18 | 19 | /** 20 | * @return true if lin1 is parallel to lin2 to within angularTolerance 21 | */ 22 | bool IsParallel(const gp_Lin &lin1, const gp_Lin &lin2, double angularTolerance=1e-6); 23 | 24 | /** 25 | * @return true if lin1 is parallel to lin2 to within angularTolerance 26 | */ 27 | bool IsParallel(const gp_Lin2d &lin1, const gp_Lin2d &lin2, double angularTolerance=1e-6); 28 | 29 | /** 30 | * Comute the 2D intersection between two lines. 31 | * @returns nullopt if there is no intersection or the Algorithm fails, the 2D point else 32 | */ 33 | std::optional Intersection(const gp_Lin2d &lin1, const gp_Lin2d &lin2); 34 | 35 | } 36 | } -------------------------------------------------------------------------------- /src/STEPExport.cxx: -------------------------------------------------------------------------------- 1 | #include "occutils/STEPExport.hxx" 2 | #include 3 | #include 4 | #include 5 | 6 | using namespace std; 7 | 8 | void OCCUtils::STEP::ExportSTEP(const TopoDS_Shape& shape, const string& filename, const string& unit) { 9 | if (shape.IsNull () == true) { 10 | throw new invalid_argument("Can't export null shape to STEP"); 11 | } 12 | 13 | STEPControl_Writer writer; 14 | Interface_Static::SetCVal ("xstep.cascade.unit", unit.c_str()); 15 | Interface_Static::SetCVal ("write.step.unit", unit.c_str ()); 16 | Interface_Static::SetIVal ("write.step.nonmanifold", 1); 17 | // "Transfer" = convert 18 | IFSelect_ReturnStatus transferStatus = writer.Transfer(shape, STEPControl_AsIs); 19 | 20 | if (transferStatus != IFSelect_RetDone) { 21 | throw std::logic_error ("Error while transferring shape to STEP"); 22 | } 23 | // Write transferred structure to STEP file 24 | IFSelect_ReturnStatus writeStatus = writer.Write(filename.c_str()); 25 | 26 | // Return previous locale 27 | if (writeStatus != IFSelect_RetDone) 28 | { 29 | throw std::logic_error ("Error while writing transferred shape to STEP file"); 30 | } 31 | } -------------------------------------------------------------------------------- /src/Line.cxx: -------------------------------------------------------------------------------- 1 | #include "occutils/Line.hxx" 2 | #include 3 | #include 4 | #include 5 | 6 | bool OCCUtils::Line::IsNormal(const gp_Lin &lin1, const gp_Lin &lin2, double angularTolerance) { 7 | // lin.Position() returns a gp_Ax1 that can be used to compute the normalness 8 | return lin1.Position().IsNormal(lin2.Position(), angularTolerance); 9 | } 10 | 11 | bool OCCUtils::Line::IsParallel(const gp_Lin &lin1, const gp_Lin &lin2, double angularTolerance) { 12 | return lin1.Position().IsParallel(lin2.Position(), angularTolerance); 13 | } 14 | 15 | bool OCCUtils::Line::IsParallel(const gp_Lin2d &lin1, const gp_Lin2d &lin2, double angularTolerance) { 16 | return lin1.Position().IsParallel(lin2.Position(), angularTolerance); 17 | } 18 | 19 | std::optional OCCUtils::Line::Intersection(const gp_Lin2d &lin1, const gp_Lin2d &lin2) { 20 | auto intersector = IntAna2d_AnaIntersection(lin1, lin2); 21 | if(!intersector.IsDone()) { // Algorithm failure, returned as no intersection 22 | return std::nullopt; 23 | } 24 | if (intersector.NbPoints() == 0 || intersector.NbPoints() > 1) { 25 | return std::nullopt; 26 | } 27 | return intersector.Point(1).Value(); 28 | } -------------------------------------------------------------------------------- /src/Fillet.cxx: -------------------------------------------------------------------------------- 1 | #include "occutils/Fillet.hxx" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | TopoDS_Shape OCCUtils::Fillet::FilletAll(const TopoDS_Shape& shape, double radius) { 10 | 11 | BRepFilletAPI_MakeFillet filletMaker(shape); 12 | // Iterate edges 13 | TopTools_IndexedMapOfShape edges; 14 | TopExp::MapShapes (shape, TopAbs_EDGE, edges); 15 | 16 | for (size_t i = 1; i <= edges.Extent(); i++) { 17 | const TopoDS_Edge& edge = TopoDS::Edge(edges(i)); 18 | filletMaker.Add(radius, edge); 19 | } 20 | filletMaker.Build(); 21 | return filletMaker.Shape(); 22 | } 23 | 24 | TopoDS_Shape OCCUtils::Fillet::FilletAdaptiveRadius(const TopoDS_Shape& shape, std::function radiusByEdge) { 25 | 26 | BRepFilletAPI_MakeFillet filletMaker(shape); 27 | // Iterate edges 28 | TopTools_IndexedMapOfShape edges; 29 | TopExp::MapShapes (shape, TopAbs_EDGE, edges); 30 | 31 | for (size_t i = 1; i <= edges.Extent(); i++) { 32 | const TopoDS_Edge& edge = TopoDS::Edge(edges(i)); 33 | double radius = radiusByEdge(edge); 34 | if(!std::isnan(radius)) { // NaN => dont fillet this edge! 35 | filletMaker.Add(radius, edge); 36 | } 37 | } 38 | filletMaker.Build(); 39 | return filletMaker.Shape(); 40 | } 41 | -------------------------------------------------------------------------------- /include/occutils/ExtendedSTEP.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace OCCUtils { 9 | namespace STEP { 10 | /** 11 | * Hidden internal state for the extended STEP exporter. 12 | */ 13 | struct _ExtendedSTEPExporterInternals; 14 | /** 15 | * Extended STEP Exporter that allows you to use: 16 | * - Colors 17 | * - Materials 18 | * - etc 19 | */ 20 | class ExtendedSTEPExporter { 21 | public: 22 | ExtendedSTEPExporter(); 23 | ~ExtendedSTEPExporter(); 24 | /** 25 | * Add a shape without any special attributes 26 | * @return The internal ID for this shape. Can be used to refer to it later. 27 | * Usually you can ignore this. 28 | */ 29 | size_t AddShape(TopoDS_Shape& shape); 30 | size_t AddShapeWithColor(TopoDS_Shape& shape, const Quantity_Color& color, XCAFDoc_ColorType colorType = XCAFDoc_ColorGen ); 31 | /** 32 | * Write the data from this STEP exporter to 33 | * the given STEP file 34 | */ 35 | void Write(const std::string& filename); 36 | private: 37 | _ExtendedSTEPExporterInternals* internals; 38 | 39 | }; 40 | } 41 | } -------------------------------------------------------------------------------- /include/occutils/Direction.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | gp_Vec operator*(const gp_Dir &a, double factor); 6 | 7 | namespace OCCUtils { 8 | namespace Direction { 9 | /** 10 | * Get the global X axis direction 11 | */ 12 | gp_Dir X(); 13 | /** 14 | * Get the global Y axis direction 15 | */ 16 | gp_Dir Y(); 17 | /** 18 | * Get the global Z axis direction 19 | */ 20 | gp_Dir Z(); 21 | /** 22 | * Get the global -X axis direction 23 | */ 24 | gp_Dir MinusX(); 25 | /** 26 | * Get the global -Y axis direction 27 | */ 28 | gp_Dir MinusY(); 29 | /** 30 | * Get the global -Z axis direction 31 | */ 32 | gp_Dir MinusZ(); 33 | 34 | 35 | /** 36 | * Get the global 2D X axis direction 37 | */ 38 | gp_Dir2d X2d(); 39 | /** 40 | * Get the global 2D -X axis direction 41 | */ 42 | gp_Dir2d MinusX2d(); 43 | /** 44 | * Get the global 2D Y axis direction 45 | */ 46 | gp_Dir2d Y2d(); 47 | /** 48 | * Get the global 2D -Y axis direction 49 | */ 50 | gp_Dir2d MinusY2d(); 51 | 52 | /** 53 | * Get the direction that is orthogonal to both a and b. 54 | */ 55 | gp_Dir Orthogonal(const gp_Dir& a, const gp_Dir& b); 56 | } 57 | } -------------------------------------------------------------------------------- /src/Direction.cxx: -------------------------------------------------------------------------------- 1 | #include "occutils/Direction.hxx" 2 | #include 3 | 4 | gp_Vec operator*(const gp_Dir &a, double factor) { 5 | return gp_Vec(a.X() * factor, a.Y() * factor, a.Z() * factor); 6 | } 7 | 8 | gp_Dir OCCUtils::Direction::X() { 9 | return gp_Dir(gp_XYZ(1.0, 0.0, 0.0)); 10 | } 11 | 12 | gp_Dir OCCUtils::Direction::Y() { 13 | return gp_Dir(gp_XYZ(0.0, 1.0, 0.0)); 14 | } 15 | 16 | gp_Dir OCCUtils::Direction::Z() { 17 | return gp_Dir(gp_XYZ(0.0, 0.0, 1.0)); 18 | } 19 | 20 | gp_Dir OCCUtils::Direction::MinusX() { 21 | return gp_Dir(gp_XYZ(-1.0, 0.0, 0.0)); 22 | } 23 | 24 | gp_Dir OCCUtils::Direction::MinusY() { 25 | return gp_Dir(gp_XYZ(0.0, -1.0, 0.0)); 26 | } 27 | 28 | gp_Dir OCCUtils::Direction::MinusZ() { 29 | return gp_Dir(gp_XYZ(0.0, 0.0, -1.0)); 30 | } 31 | 32 | /** 33 | * Get the global 2D X axis direction 34 | */ 35 | gp_Dir2d OCCUtils::Direction::X2d() { 36 | return gp_Dir2d(gp_XY(1.0, 0.0)); 37 | } 38 | /** 39 | * Get the global 2D -X axis direction 40 | */ 41 | gp_Dir2d OCCUtils::Direction::MinusX2d() { 42 | return gp_Dir2d(gp_XY(-1.0, 0.0)); 43 | } 44 | /** 45 | * Get the global 2D Y axis direction 46 | */ 47 | gp_Dir2d OCCUtils::Direction::Y2d() { 48 | return gp_Dir2d(gp_XY(0.0, 1.0)); 49 | } 50 | /** 51 | * Get the global 2D -Y axis direction 52 | */ 53 | gp_Dir2d OCCUtils::Direction::MinusY2d() { 54 | return gp_Dir2d(gp_XY(0.0, -1.0)); 55 | } 56 | 57 | gp_Dir OCCUtils::Direction::Orthogonal(const gp_Dir& a, const gp_Dir& b) { 58 | return a.Crossed(b); 59 | } -------------------------------------------------------------------------------- /src/Face.cxx: -------------------------------------------------------------------------------- 1 | #include "occutils/Face.hxx" 2 | #include "occutils/Wire.hxx" 3 | #include "occutils/Surface.hxx" 4 | 5 | #include 6 | 7 | using namespace OCCUtils; 8 | 9 | TopoDS_Face OCCUtils::Face::FromWire(const TopoDS_Wire& wire) { 10 | if (wire.IsNull()) { 11 | return TopoDS_Face(); 12 | } 13 | BRepBuilderAPI_MakeFace faceMaker(wire); 14 | return faceMaker.IsDone() ? faceMaker.Face() : TopoDS_Face(); 15 | } 16 | 17 | TopoDS_Face OCCUtils::Face::FromEdges(const std::initializer_list& edges) { 18 | return FromWire(Wire::FromEdges(edges)); 19 | } 20 | 21 | TopoDS_Face OCCUtils::Face::FromEdges(const std::vector& edges) { 22 | return FromWire(Wire::FromEdges(edges)); 23 | } 24 | 25 | TopoDS_Face OCCUtils::Face::FromEdge(const TopoDS_Edge& edge) { 26 | return Face::FromWire(Wire::FromEdges({edge})); 27 | } 28 | 29 | 30 | std::optional OCCUtils::Face::Normal(const TopoDS_Face& face, double u, double v, double precision) { 31 | auto surface = Surface::FromFace(face); 32 | if(surface.Surface().IsNull()) { 33 | return std::nullopt; 34 | } 35 | return Surface::Normal(surface, u, v, precision); 36 | } 37 | 38 | std::optional OCCUtils::Face::NormalDirection(const TopoDS_Face& face, double u, double v, double precision) { 39 | auto surface = Surface::FromFace(face); 40 | if(surface.Surface().IsNull()) { 41 | return std::nullopt; 42 | } 43 | return Surface::NormalDirection(surface, u, v, precision); 44 | } 45 | 46 | TopoDS_Face OCCUtils::Face::FromPoints(const std::vector& points) { 47 | return FromWire(Wire::FromPoints(points, true)); 48 | } -------------------------------------------------------------------------------- /src/Edge.cxx: -------------------------------------------------------------------------------- 1 | #include "occutils/Edge.hxx" 2 | #include "occutils/Axis.hxx" 3 | #include "occutils/Point.hxx" 4 | #include "occutils/Equality.hxx" 5 | #include "occutils/Curve.hxx" 6 | #include 7 | #include 8 | #include 9 | 10 | TopoDS_Edge OCCUtils::Edge::FromPoints (const gp_Pnt& p1, const gp_Pnt& p2) { 11 | // Are the two points coincident? 12 | // If so, return an "empty" edge 13 | if (p1 == p2) { 14 | return TopoDS_Edge (); 15 | } 16 | // Not the same => Create a linear edge 17 | return BRepBuilderAPI_MakeEdge(p1, p2).Edge(); 18 | } 19 | 20 | TopoDS_Edge OCCUtils::Edge::FullCircle(double radius) { 21 | return FullCircle(Ax2::OZ(), radius); 22 | } 23 | 24 | TopoDS_Edge OCCUtils::Edge::FullCircle(const gp_Dir& direction, double radius) { 25 | return FullCircle(gp_Ax2(Point::Origin(), direction), radius); 26 | } 27 | 28 | TopoDS_Edge OCCUtils::Edge::FullCircle(const gp_Ax1& axis, double radius) { 29 | return FullCircle(Ax2::FromAx1(axis), radius); 30 | } 31 | 32 | TopoDS_Edge OCCUtils::Edge::FullCircle(const gp_Pnt& center, const gp_Dir& direction, double radius) { 33 | return FullCircle(gp_Ax1(center, direction), radius); 34 | } 35 | 36 | TopoDS_Edge OCCUtils::Edge::FullCircle(const gp_Ax2& axis, double radius) { 37 | return BRepBuilderAPI_MakeEdge(gp_Circ(axis, radius)).Edge(); 38 | } 39 | 40 | double OCCUtils::Edge::Length(const TopoDS_Edge& edge) { 41 | return Curve::Length(Curve::FromEdge(edge)); 42 | } 43 | 44 | TopoDS_Edge OCCUtils::Edge::CircleSegment( 45 | const gp_Ax2& axis, double radius, 46 | const gp_Pnt& p1, const gp_Pnt& p2) { 47 | gp_Circ circ(axis, radius); 48 | return BRepBuilderAPI_MakeEdge(circ, p1, p2).Edge(); 49 | } -------------------------------------------------------------------------------- /include/occutils/Exceptions.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | /** 4 | * All OCCUtils exceptions derive from this 5 | */ 6 | struct OCCUtilsBaseException : std::invalid_argument { 7 | OCCUtilsBaseException(const char *msg) : invalid_argument(msg) {} 8 | OCCUtilsBaseException(const std::string &msg) : invalid_argument(msg) {} 9 | }; 10 | 11 | /** 12 | * Baseclass for exceptions that directly relate to the 13 | * BRep topology. 14 | */ 15 | struct OCCConstructionFailedException : OCCUtilsBaseException { 16 | OCCConstructionFailedException(const char *msg) : OCCUtilsBaseException(msg) {} 17 | OCCConstructionFailedException(const std::string &msg) : OCCUtilsBaseException(msg) {} 18 | }; 19 | 20 | /** 21 | * Exception occuring related to IO of data 22 | */ 23 | struct OCCIOException : OCCUtilsBaseException { 24 | OCCIOException(const char *msg) : OCCUtilsBaseException(msg) {} 25 | OCCIOException(const std::string &msg) : OCCUtilsBaseException(msg) {} 26 | }; 27 | 28 | /** 29 | * Baseclass for exceptions that directly relate to the 30 | * BRep topology. 31 | */ 32 | struct OCCTopologyException : OCCUtilsBaseException { 33 | OCCTopologyException(const char *msg) : OCCUtilsBaseException(msg) {} 34 | OCCTopologyException(const std::string &msg) : OCCUtilsBaseException(msg) {} 35 | }; 36 | 37 | /** 38 | * Thrown if there are either too many or too few 39 | * sub-topologies in a topology, 40 | * e.g. too many or too few edges in a face. 41 | * "Too many" or "Too few" is relative to what the calling code expects. 42 | */ 43 | struct OCCTopologyCountMismatchException : OCCTopologyException { 44 | OCCTopologyCountMismatchException(const char *msg) : OCCTopologyException(msg) {} 45 | OCCTopologyCountMismatchException(const std::string &msg) : OCCTopologyException(msg) {} 46 | }; 47 | -------------------------------------------------------------------------------- /include/occutils/Point.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | /** 7 | * Add the coordinates of two points. 8 | * Performs coordinate-wise addition. 9 | */ 10 | gp_Pnt operator+(const gp_Pnt &a, const gp_Pnt &b); 11 | gp_Pnt operator+(const gp_Pnt &a, const gp_Vec &b); 12 | gp_Pnt operator+(const gp_Pnt &a, const gp_XYZ &b); 13 | 14 | /** 15 | * Subtract the coordinates of two points. 16 | * Performs coordinate-wise subtraction. 17 | */ 18 | gp_Pnt operator-(const gp_Pnt &a, const gp_Pnt &b); 19 | gp_Pnt operator-(const gp_Pnt &a, const gp_Vec &b); 20 | gp_Pnt operator-(const gp_Pnt &a, const gp_XYZ &b); 21 | 22 | namespace OCCUtils { 23 | namespace Point { 24 | /** 25 | * Return the global coordinate system's origin: 26 | * (0,0,0) 27 | */ 28 | gp_Pnt Origin(); 29 | 30 | /** 31 | * Generate a 3D point from a 2D point 32 | * by setting the Z coordinate to 0.0 33 | */ 34 | gp_Pnt From2d(const gp_Pnt2d& pnt); 35 | gp_Pnt From2d(double x, double y); 36 | 37 | /** 38 | * Get the point of mean X/Y/Z between the given points 39 | */ 40 | gp_Pnt Midpoint(const std::initializer_list& points); 41 | gp_Pnt Midpoint(const std::vector& points); 42 | 43 | /** 44 | * Get the distance between pnt and axis, 45 | * strictly defined as the distance between pnt and the orthogonal 46 | * projection of pnt onto axis. 47 | */ 48 | double Distance(const gp_Pnt& pnt, const gp_Ax1& axis); 49 | 50 | /** 51 | * Orthogonally project pnt onto ax 52 | */ 53 | gp_Pnt OrthogonalProjectOnto(const gp_Pnt &pnt, const gp_Ax1 &ax); 54 | 55 | /** 56 | * Orthogonally project pnt onto ax 57 | */ 58 | gp_Pnt2d OrthogonalProjectOnto(const gp_Pnt2d &pnt, const gp_Ax2d &ax); 59 | } 60 | } -------------------------------------------------------------------------------- /include/occutils/Edge.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace OCCUtils { 9 | namespace Edge { 10 | /** 11 | * Create a linear edge between two points 12 | */ 13 | TopoDS_Edge FromPoints (const gp_Pnt& p1, const gp_Pnt& p2); 14 | /** 15 | * Create a full circle edge at origin, 16 | * with the circle's normal pointing at the Z direction. 17 | */ 18 | TopoDS_Edge FullCircle(double radius=1.0); 19 | /** 20 | * Create a full circle edge at origin, 21 | * with the circle's normal pointing in the given direction. 22 | */ 23 | TopoDS_Edge FullCircle(const gp_Dir& direction, double radius=1.0); 24 | /** 25 | * Create a full circle edge at axis.Location(), 26 | * with the circle's normal pointing to axis.Direction(). 27 | */ 28 | TopoDS_Edge FullCircle(const gp_Ax1& axis, double radius=1.0); 29 | /** 30 | * Create a full circle edge at center 31 | * with the circle's normal pointing to direction. 32 | */ 33 | TopoDS_Edge FullCircle(const gp_Pnt& center, const gp_Dir& direction, double radius=1.0); 34 | /** 35 | * Create a full circle edge at axis.Location(), 36 | * with the circle's normal pointing to axis.Direction(). 37 | */ 38 | TopoDS_Edge FullCircle(const gp_Ax2& axis, double radius=1.0); 39 | 40 | /** 41 | * Like FullCircle(), but creates a circle only from 42 | * p1 to p2. 43 | * Precondition: p1 and p2 must lie on the circle. 44 | */ 45 | TopoDS_Edge CircleSegment(const gp_Ax2& axis, double radius, 46 | const gp_Pnt& p1, const gp_Pnt& p2); 47 | 48 | /** 49 | * Get the length of the given edge 50 | */ 51 | double Length(const TopoDS_Edge& edge); 52 | } 53 | } -------------------------------------------------------------------------------- /src/Axis.cxx: -------------------------------------------------------------------------------- 1 | #include "occutils/Axis.hxx" 2 | #include "occutils/Axis.hxx" 3 | #include "occutils/Direction.hxx" 4 | #include "occutils/Point.hxx" 5 | 6 | #include 7 | 8 | using namespace OCCUtils; 9 | 10 | gp_Ax1 OCCUtils::Ax1::OX() { 11 | return gp_Ax1(Point::Origin(), Direction::X()); 12 | } 13 | 14 | gp_Ax1 OCCUtils::Ax1::OY() { 15 | return gp_Ax1(Point::Origin(), Direction::Y()); 16 | } 17 | 18 | gp_Ax1 OCCUtils::Ax1::OZ() { 19 | return gp_Ax1(Point::Origin(), Direction::Z()); 20 | } 21 | 22 | gp_Ax1 OCCUtils::Ax1::OMinusX() { 23 | return gp_Ax1(Point::Origin(), Direction::MinusX()); 24 | } 25 | 26 | gp_Ax1 OCCUtils::Ax1::OMinusY() { 27 | return gp_Ax1(Point::Origin(), Direction::MinusY()); 28 | } 29 | 30 | gp_Ax1 OCCUtils::Ax1::OMinusZ() { 31 | return gp_Ax1(Point::Origin(), Direction::MinusZ()); 32 | } 33 | 34 | gp_Ax2 OCCUtils::Ax2::FromAx1(const gp_Ax1& axis) { 35 | return gp_Ax2(axis.Location(), axis.Direction()); 36 | } 37 | 38 | gp_Ax2 OCCUtils::Ax2::OX() { 39 | return gp_Ax2(Point::Origin(), Direction::X()); 40 | } 41 | 42 | gp_Ax2 OCCUtils::Ax2::OY() { 43 | return gp_Ax2(Point::Origin(), Direction::Y()); 44 | } 45 | 46 | gp_Ax2 OCCUtils::Ax2::OZ() { 47 | return gp_Ax2(Point::Origin(), Direction::Z()); 48 | } 49 | 50 | gp_Ax2 OCCUtils::Ax2::OMinusX() { 51 | return gp_Ax2(Point::Origin(), Direction::MinusX()); 52 | } 53 | 54 | gp_Ax2 OCCUtils::Ax2::OMinusY() { 55 | return gp_Ax2(Point::Origin(), Direction::MinusY()); 56 | } 57 | 58 | gp_Ax2 OCCUtils::Ax2::OMinusZ() { 59 | return gp_Ax2(Point::Origin(), Direction::MinusZ()); 60 | } 61 | 62 | bool OCCUtils::Axis::Contains(const gp_Ax1& axis, const gp_Pnt& pnt, double tolerance) { 63 | return gp_Lin(axis).Contains(pnt, tolerance); 64 | } 65 | 66 | double OCCUtils::Axis::Distance(const gp_Ax1& axis, const gp_Pnt& pnt) { 67 | return gp_Lin(axis).Distance(pnt); 68 | } 69 | 70 | gp_Ax1 operator+(const gp_Ax1& axis, const gp_Vec& vec) { 71 | return gp_Ax1(axis.Location() + vec, axis.Direction()); 72 | } 73 | 74 | gp_Ax1 operator+(const gp_Ax1& axis, const gp_XYZ& vec) { 75 | return gp_Ax1(axis.Location() + vec, axis.Direction()); 76 | } -------------------------------------------------------------------------------- /tests/TestLine.cpp: -------------------------------------------------------------------------------- 1 | #define BOOST_TEST_MAIN 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | 9 | BOOST_AUTO_TEST_CASE( LineParallel2d ) 10 | { 11 | // A line should be parallel to itself 12 | gp_Lin2d linX(gp_Pnt2d(0, 0), OCCUtils::Direction::X2d()); 13 | BOOST_CHECK(OCCUtils::Line::IsParallel(linX, linX)); 14 | 15 | // A line should be parallel to its reverse line 16 | gp_Lin2d linMinusX = linX.Reversed(); 17 | BOOST_CHECK(OCCUtils::Line::IsParallel(linX, linMinusX)); 18 | BOOST_CHECK(OCCUtils::Line::IsParallel(linMinusX, linX)); 19 | 20 | // Two perpendicular lines should not be parallel 21 | gp_Lin2d linY(gp_Pnt2d(0, 0), OCCUtils::Direction::Y2d()); 22 | BOOST_CHECK(!OCCUtils::Line::IsParallel(linX, linY)); 23 | BOOST_CHECK(!OCCUtils::Line::IsParallel(linY, linX)); 24 | BOOST_CHECK(!OCCUtils::Line::IsParallel(linMinusX, linY)); 25 | BOOST_CHECK(!OCCUtils::Line::IsParallel(linY, linMinusX)); 26 | } 27 | 28 | BOOST_AUTO_TEST_CASE( LineIntersection2D ) 29 | { 30 | // Intersect between a line and itself has infinite points 31 | gp_Lin2d lin1(gp_Pnt2d(0, 0), OCCUtils::Direction::X2d()); 32 | auto result = OCCUtils::Line::Intersection(lin1, lin1); 33 | BOOST_CHECK(!result.has_value()); 34 | 35 | // Two intersecting lines should have an intersection point 36 | gp_Lin2d lin2(gp_Pnt2d(0, -1), OCCUtils::Direction::Y2d()); 37 | result = OCCUtils::Line::Intersection(lin1, lin2); 38 | BOOST_CHECK(result.has_value()); 39 | BOOST_CHECK_EQUAL(result.value(), gp_Pnt2d(0, 0)); 40 | 41 | // Two more intersecting lines that do not intersect @ origin 42 | gp_Lin2d lin3(gp_Pnt2d(0, 1), OCCUtils::Direction::X2d()); 43 | result = OCCUtils::Line::Intersection(lin2, lin3); 44 | BOOST_CHECK(result.has_value()); 45 | BOOST_CHECK_EQUAL(result.value(), gp_Pnt2d(0, 1)); 46 | 47 | // ... same as last test but argument order inverted (same result) 48 | result = OCCUtils::Line::Intersection(lin3, lin2); 49 | BOOST_CHECK(result.has_value()); 50 | BOOST_CHECK_EQUAL(result.value(), gp_Pnt2d(0, 1)); 51 | } -------------------------------------------------------------------------------- /include/occutils/Shape.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /** 3 | * Utilities for dissecting shapes into their components. 4 | */ 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace OCCUtils { 14 | namespace Shape { 15 | /** 16 | * @return true if the given shape is a TopoDS_Solid 17 | */ 18 | bool IsSolid(const TopoDS_Shape& shape); 19 | /** 20 | * @return true if the given shape is a TopoDS_Face 21 | */ 22 | bool IsFace(const TopoDS_Shape& shape); 23 | /** 24 | * @return true if the given shape is a TopoDS_Edge 25 | */ 26 | bool IsEdge(const TopoDS_Shape& shape); 27 | /** 28 | * @return true if the given shape is a TopoDS_Wire 29 | */ 30 | bool IsWire(const TopoDS_Shape& shape); 31 | /** 32 | * @return true if the given shape is a TopoDS_Vertex 33 | */ 34 | bool IsVertex(const TopoDS_Shape& shape); 35 | 36 | /** 37 | * Compute the volume of the given shape. 38 | */ 39 | double Volume(const TopoDS_Shape& shape); 40 | 41 | /** 42 | * Compute the bounding box of the given shape, 43 | * represented by two corner coordinate vectors. 44 | * 45 | * The first returned vector contains xmin, ymin & zmin 46 | * The second returned vector contains xmax, ymax & zmax 47 | */ 48 | std::pair BoundingBox(const TopoDS_Shape& shape); 49 | 50 | /** 51 | * Compute the shape of the bounding box for the given shape 52 | */ 53 | gp_XYZ BoundingBoxSize(const TopoDS_Shape& shape); 54 | 55 | /** 56 | * Compute the volume of the bounding box of the given shape. 57 | */ 58 | double BoundingBoxVolume(const TopoDS_Shape& shape); 59 | } 60 | 61 | 62 | namespace Shapes { 63 | /** 64 | * Convert a solid vector to a shape vector 65 | */ 66 | std::vector FromSolids(const std::vector& solids); 67 | 68 | /** 69 | * Convert a face vector to a shape vector 70 | */ 71 | std::vector FromFaces(const std::vector& faces); 72 | } 73 | } -------------------------------------------------------------------------------- /include/occutils/Curve.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace OCCUtils { 9 | namespace Curve { 10 | /** 11 | * Get a curve from the given edge. 12 | * If it fails, returnValue.Curve().IsNull() == true 13 | */ 14 | GeomAdaptor_Curve FromEdge(const TopoDS_Edge& edge); 15 | 16 | /** 17 | * Convert a trimmed curve to a GeomAdapter_Curve 18 | */ 19 | GeomAdaptor_Curve FromTrimmedCurve(const Geom_TrimmedCurve &curve); 20 | 21 | /** 22 | * Length of a curve (by adaptor) 23 | */ 24 | double Length(const GeomAdaptor_Curve& curve); 25 | /** 26 | * Length of a curve (by curve handle). 27 | * Note that many Geom_Curve subclasses describe infinite 28 | * curves and Geom_Curve does not have the correct trimming 29 | * parameters by itself. Use GeomAdaptor_Curve or 30 | * Geom_TrimmedCurve instead. 31 | */ 32 | double Length(const Handle(Geom_Curve)& curve); 33 | /** 34 | * Length of a curve (by curve handle) 35 | */ 36 | double Length(const Geom_TrimmedCurve &curve); 37 | 38 | bool IsLine(const GeomAdaptor_Curve& curve); 39 | bool IsCircle(const GeomAdaptor_Curve& curve); 40 | bool IsEllipse(const GeomAdaptor_Curve& curve); 41 | bool IsHyperbola(const GeomAdaptor_Curve& curve); 42 | bool IsParabola(const GeomAdaptor_Curve& curve); 43 | bool IsBezier(const GeomAdaptor_Curve& curve); 44 | bool IsBSpline(const GeomAdaptor_Curve& curve); 45 | bool IsOffsetCurve(const GeomAdaptor_Curve& curve); 46 | bool IsOther(const GeomAdaptor_Curve& curve); 47 | } 48 | namespace Curves { 49 | /** 50 | * Filter a list of curves: Get only curves that match the given type 51 | */ 52 | std::vector Only(const std::vector& curves, 53 | GeomAbs_CurveType type); 54 | 55 | /** 56 | * Filter a list of curves: Get only curves for which the given predicate returns true 57 | */ 58 | std::vector Filter(const std::vector& curves, 59 | std::function predicate); 60 | } 61 | } -------------------------------------------------------------------------------- /src/Shape.cxx: -------------------------------------------------------------------------------- 1 | #include "occutils/Shape.hxx" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | bool OCCUtils::Shape::IsSolid(const TopoDS_Shape &shape) { return shape.ShapeType() == TopAbs_SOLID; } 10 | bool OCCUtils::Shape::IsFace(const TopoDS_Shape &shape) { return shape.ShapeType() == TopAbs_FACE; } 11 | bool OCCUtils::Shape::IsEdge(const TopoDS_Shape &shape) { return shape.ShapeType() == TopAbs_EDGE; } 12 | bool OCCUtils::Shape::IsWire(const TopoDS_Shape &shape) { return shape.ShapeType() == TopAbs_WIRE; } 13 | bool OCCUtils::Shape::IsVertex(const TopoDS_Shape &shape) { return shape.ShapeType() == TopAbs_VERTEX; } 14 | 15 | double OCCUtils::Shape::Volume(const TopoDS_Shape& shape) { 16 | GProp_GProps gprops; 17 | BRepGProp::VolumeProperties(shape, gprops); 18 | return gprops.Mass(); 19 | } 20 | 21 | /** 22 | * Internal converter function 23 | */ 24 | template 25 | std::vector _ToShapes(const std::vector& elems) { 26 | // Create return vector 27 | std::vector ret; 28 | ret.reserve(elems.size()); 29 | // Do the copying 30 | std::copy(elems.begin(), elems.end(), std::back_inserter(ret)); 31 | return ret; 32 | } 33 | 34 | std::vector OCCUtils::Shapes::FromSolids(const std::vector& solids) { 35 | return _ToShapes(solids); 36 | } 37 | 38 | std::vector OCCUtils::Shapes::FromFaces(const std::vector& faces) { 39 | return _ToShapes(faces); 40 | } 41 | 42 | 43 | std::pair OCCUtils::Shape::BoundingBox(const TopoDS_Shape& shape) { 44 | 45 | Standard_Real xmin, xmax, ymin, ymax, zmin, zmax; 46 | Bnd_Box box; 47 | BRepBndLib::Add(shape, box); 48 | box.Get(xmin, ymin, zmin, xmax, ymax, zmax); 49 | 50 | return std::make_pair( 51 | gp_Vec(xmin, ymin, zmin), 52 | gp_Vec(xmax, ymax, zmax) 53 | ); 54 | } 55 | 56 | gp_XYZ OCCUtils::Shape::BoundingBoxSize(const TopoDS_Shape& shape) { 57 | Standard_Real xmin, xmax, ymin, ymax, zmin, zmax; 58 | Bnd_Box box; 59 | BRepBndLib::Add(shape, box); 60 | box.Get(xmin, ymin, zmin, xmax, ymax, zmax); 61 | 62 | return gp_XYZ(abs(xmax - xmin), abs(ymax - ymin), abs(zmax - zmin)); 63 | } 64 | 65 | double OCCUtils::Shape::BoundingBoxVolume(const TopoDS_Shape& shape) { 66 | gp_XYZ bbox = BoundingBoxSize(shape); 67 | return bbox.X() * bbox.Y() * bbox.Z(); 68 | } -------------------------------------------------------------------------------- /include/occutils/Face.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace OCCUtils { 10 | namespace Face { 11 | /** 12 | * Construct a face from the given outer wire. 13 | * If wire.IsNull(), then result.IsNull() as well. 14 | */ 15 | TopoDS_Face FromWire(const TopoDS_Wire& wire); 16 | /** 17 | * Construct an outer wire from the given edge 18 | * and construct a face from the outer wire. 19 | * If wire.IsNull(), then result.IsNull() as well. 20 | */ 21 | TopoDS_Face FromEdge(const TopoDS_Edge& edge); 22 | /** 23 | * Construct an outer wire from the given edges 24 | * and construct a face from the outer wire. 25 | * If wire.IsNull(), then result.IsNull() as well. 26 | */ 27 | TopoDS_Face FromEdges(const std::initializer_list& edges); 28 | /** 29 | * Construct an outer wire from the given edges 30 | * and construct a face from the outer wire. 31 | */ 32 | TopoDS_Face FromEdges(const std::vector& edges); 33 | /** 34 | * Construct an outer wire from points, linearly connecting each point to the next. 35 | * The last point is automatically connected to the first point, unless they are equal. 36 | * Equivalent to calling Face::FromWire(Wire::FromPoints(points, true)) 37 | */ 38 | TopoDS_Face FromPoints(const std::vector& points); 39 | 40 | /** 41 | * Compute the normal of a surface at the given U/V coordinates. 42 | * @param surf The surface 43 | * @param u The U coordinate 44 | * @param v The V coordinate 45 | * @param precision Affects computation speed. 46 | * @returns The gp_Ax1 of the point on the surface described by U/V coords and the normal direction, or nothing if the face does not have any surface 47 | */ 48 | std::optional Normal(const TopoDS_Face& face, double u = 0.0, double v = 0.0, double precision=1e-6); 49 | 50 | /** 51 | * Compute the normal direction of a surface at the given U/V coordinates. 52 | * @param surf The surface 53 | * @param u The U coordinate 54 | * @param v The V coordinate 55 | * @param precision Affects computation speed. 56 | */ 57 | std::optional NormalDirection(const TopoDS_Face& face, double u = 0.0, double v = 0.0, double precision=1e-6); 58 | 59 | } 60 | } -------------------------------------------------------------------------------- /include/occutils/Wire.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace OCCUtils { 11 | namespace Wire { 12 | /** 13 | * Create a wire from one or multiple edges. 14 | * Conveniece function to be used like this: 15 | * Wire::FromEdges 16 | */ 17 | TopoDS_Wire FromEdges(const std::initializer_list& edges); 18 | TopoDS_Wire FromEdges(const std::vector& edges); 19 | 20 | /** 21 | * Make a TopoDS_Wire by linearly connecting all of the given points in order, 22 | * @param close If set to true, connect the last point to the first point. 23 | */ 24 | TopoDS_Wire FromPoints(const std::vector& edges, bool close=false); 25 | 26 | /** 27 | * Build a wire incrementally, 28 | * uses relative coordinates. 29 | * 30 | * This is a convenience wrapper to 31 | * programmatically build wires. 32 | */ 33 | class IncrementalBuilder { 34 | public: 35 | IncrementalBuilder(const gp_Pnt& pnt); 36 | 37 | /** 38 | * Add a line segment 39 | */ 40 | void Line(double dx, double dy, double dz); 41 | 42 | /** 43 | * ALPHA - API MAY CHANGE! 44 | * Create a 90° arc from the current position. 45 | */ 46 | void Arc90( 47 | double dx, double dy, double dz, 48 | double centerDx, double centerDy, double centerDz, 49 | const gp_Dir& normal = Direction::Z()); 50 | 51 | /** 52 | * Get the current direction vector, 53 | * i.e. the end direction of the resulting edge. 54 | */ 55 | std::optional Direction(); 56 | /** 57 | * Get the current position 58 | */ 59 | gp_Pnt Location(); 60 | 61 | /** 62 | * Get the resulting wire. 63 | */ 64 | TopoDS_Wire Wire(); 65 | 66 | /** 67 | * Create a pipe from the wire using the given profile. 68 | */ 69 | TopoDS_Shape Pipe(const TopoDS_Face& profile); 70 | /** 71 | * Create a pipe from the wire using a circular profile 72 | * of the given radius. 73 | */ 74 | TopoDS_Shape PipeWithCircularProfile(double radius); 75 | 76 | // Current location 77 | gp_Pnt current; 78 | // Current direction 79 | std::optional currentDirection; 80 | std::vector edges; 81 | }; 82 | } 83 | } -------------------------------------------------------------------------------- /include/occutils/IO.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | namespace OCCUtils { 8 | namespace IO { 9 | /** 10 | * Read the given file and extract a shape from it. 11 | * STEP/IGES filetype is determined automatically by filename extension - see STEPorIGESReader(). 12 | * 13 | * Returns: 14 | * - A null shape if empty 15 | * - If there is a single shape, that shape 16 | * - If there are multiple shapes, a TopoDS_Compound of that shape. 17 | * 18 | * This function basically works like this: 19 | * - IO::Reader::STEPorIGESReader(filename) 20 | * - IO::Reader::ReadFile(reader, filename) 21 | * - IO::Reader::ReadOneShape(reader) 22 | */ 23 | TopoDS_Shape Read(const std::string& filename); 24 | 25 | namespace Reader { 26 | /** 27 | * Get the appropriate XSControl_Reader instance for the given filename. 28 | * - If filename ends with ".stp" or ".step" (case-insensitive), it will use STEPControl_Reader() 29 | * - If filename ends with ".iges" or ".igs" (case-insensitive), it will use IGESControl_Reader() 30 | * 31 | * NOTE: This does NOT actually read the file, just initialize an empty reader 32 | * 33 | * @throws OCCIOException in case of error (could not determine filename) 34 | */ 35 | std::shared_ptr STEPorIGESReader(const std::string& filename); 36 | /** 37 | * Like STEPorIGESReader(), but ignores the filename and always uses a STEPControl_Reader reader. 38 | */ 39 | std::shared_ptr STEPReader(); 40 | /** 41 | * Like STEPorIGESReader(), but ignores the filename and always uses a IGESControl_Reader reader. 42 | */ 43 | std::shared_ptr IGESReader(); 44 | 45 | /** 46 | * Make the given XSControl_Reader read the given file. 47 | * @throws OCCIOException in case of read error 48 | */ 49 | void ReadFile(const std::shared_ptr& reader, const std::string& filename); 50 | 51 | /** 52 | * Read a single shape from the given reader: 53 | * - A null shape if empty 54 | * - If there is a single shape, that shape 55 | * - If there are multiple shapes, a TopoDS_Compound of that shape. 56 | * 57 | * Preconditions: 58 | * - Initialize correct reader type (STEP or IGES) 59 | * - Call ReadFile() to read the input 60 | */ 61 | TopoDS_Shape ReadOneShape(const std::shared_ptr& reader); 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /src/Curve.cxx: -------------------------------------------------------------------------------- 1 | #include "occutils/Curve.hxx" 2 | #include 3 | #include 4 | 5 | GeomAdaptor_Curve OCCUtils::Curve::FromEdge(const TopoDS_Edge& edge) { 6 | Standard_Real umin, umax; 7 | // Get unbounded curve plus separate bounding parameters 8 | auto rawCurve = BRep_Tool::Curve(edge, umin, umax); 9 | return GeomAdaptor_Curve(rawCurve, umin, umax); 10 | } 11 | 12 | GeomAdaptor_Curve OCCUtils::Curve::FromTrimmedCurve(const Geom_TrimmedCurve &curve) { 13 | return GeomAdaptor_Curve( 14 | curve.BasisCurve(), curve.FirstParameter(), curve.LastParameter() 15 | ); 16 | } 17 | 18 | double OCCUtils::Curve::Length(const GeomAdaptor_Curve& curve) { 19 | return GCPnts_AbscissaPoint::Length(curve); 20 | } 21 | 22 | double OCCUtils::Curve::Length(const Handle(Geom_Curve)& curve) { 23 | return Length(GeomAdaptor_Curve(curve)); 24 | } 25 | 26 | double OCCUtils::Curve::Length(const Geom_TrimmedCurve &curve) { 27 | return Length(Curve::FromTrimmedCurve(curve)); 28 | } 29 | 30 | 31 | bool OCCUtils::Curve::IsLine(const GeomAdaptor_Curve& curve) { 32 | return curve.GetType() == GeomAbs_Line; 33 | } 34 | 35 | bool OCCUtils::Curve::IsCircle(const GeomAdaptor_Curve& curve) { 36 | return curve.GetType() == GeomAbs_Circle; 37 | } 38 | 39 | bool OCCUtils::Curve::IsEllipse(const GeomAdaptor_Curve& curve) { 40 | return curve.GetType() == GeomAbs_Ellipse; 41 | } 42 | 43 | bool OCCUtils::Curve::IsHyperbola(const GeomAdaptor_Curve& curve) { 44 | return curve.GetType() == GeomAbs_Hyperbola; 45 | } 46 | 47 | bool OCCUtils::Curve::IsParabola(const GeomAdaptor_Curve& curve) { 48 | return curve.GetType() == GeomAbs_Parabola; 49 | } 50 | 51 | bool OCCUtils::Curve::IsBezier(const GeomAdaptor_Curve& curve) { 52 | return curve.GetType() == GeomAbs_BezierCurve; 53 | } 54 | 55 | bool OCCUtils::Curve::IsBSpline(const GeomAdaptor_Curve& curve) { 56 | return curve.GetType() == GeomAbs_BSplineCurve; 57 | } 58 | 59 | bool OCCUtils::Curve::IsOffsetCurve(const GeomAdaptor_Curve& curve) { 60 | return curve.GetType() == GeomAbs_OffsetCurve; 61 | } 62 | 63 | bool OCCUtils::Curve::IsOther(const GeomAdaptor_Curve& curve) { 64 | return curve.GetType() == GeomAbs_OtherCurve; 65 | } 66 | 67 | 68 | std::vector OCCUtils::Curves::Only(const std::vector& curves, 69 | GeomAbs_CurveType type) { 70 | return Filter(curves, [=](const GeomAdaptor_Curve& curve) { 71 | return curve.GetType () == type; 72 | }); 73 | } 74 | 75 | /** 76 | * Filter a list of curves: Get only curves for which the given predicate returns true 77 | */ 78 | std::vector OCCUtils::Curves::Filter(const std::vector& curves, 79 | std::function predicate) { 80 | std::vector ret; 81 | ret.reserve(curves.size()); 82 | for(const auto& curve: curves) { 83 | if(predicate(curve)) { 84 | ret.push_back(curve); 85 | } 86 | } 87 | return ret; 88 | } -------------------------------------------------------------------------------- /src/IO.cxx: -------------------------------------------------------------------------------- 1 | #include "occutils/IO.hxx" 2 | #include "occutils/Exceptions.hxx" 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | using namespace std; 9 | using namespace boost::algorithm; 10 | 11 | std::shared_ptr OCCUtils::IO::Reader::STEPorIGESReader(const std::string& filename) { 12 | shared_ptr reader; 13 | // Automatically determine filename 14 | string lowercaseFilename = to_lower_copy(filename); 15 | if (ends_with(lowercaseFilename, ".step") || ends_with(lowercaseFilename, ".stp")) { 16 | reader = shared_ptr(dynamic_cast(new STEPControl_Reader())); 17 | } else if (ends_with(lowercaseFilename, ".iges") || ends_with(lowercaseFilename, ".igs")) { 18 | reader = shared_ptr(dynamic_cast(new IGESControl_Reader())); 19 | } else { 20 | throw OCCIOException("Unknown file extension (.stp/.step or .igs/.iges expected): " + filename); 21 | } 22 | return reader; 23 | } 24 | 25 | std::shared_ptr OCCUtils::IO::Reader::STEPReader() { 26 | return shared_ptr(dynamic_cast(new STEPControl_Reader())); 27 | } 28 | 29 | std::shared_ptr OCCUtils::IO::Reader::IGESReader() { 30 | return shared_ptr(dynamic_cast(new IGESControl_Reader())); 31 | } 32 | 33 | /** 34 | * Convert a IFSelect_ReturnStatus code to string 35 | */ 36 | static std::string _IFSelectReturnStatusToString(IFSelect_ReturnStatus code) { 37 | switch (code) { 38 | case IFSelect_RetVoid: return "Void"; 39 | case IFSelect_RetDone: return "Done"; 40 | case IFSelect_RetError: return "Error"; 41 | case IFSelect_RetFail: return "Fail"; 42 | case IFSelect_RetStop: return "Stop"; 43 | default: return "Unknown"; 44 | } 45 | } 46 | 47 | void OCCUtils::IO::Reader::ReadFile(const std::shared_ptr& reader, const std::string& filename) { 48 | auto readStat = reader->ReadFile(filename.c_str()); 49 | if (readStat != IFSelect_ReturnStatus::IFSelect_RetDone) { 50 | throw OCCIOException("Failed to read file, error code: " + _IFSelectReturnStatusToString(readStat)); 51 | } 52 | } 53 | 54 | TopoDS_Shape OCCUtils::IO::Reader::ReadOneShape(const std::shared_ptr& reader) { 55 | // Check if there is anything to convert 56 | auto numroots = reader->NbRootsForTransfer(); 57 | if (numroots < 1) { 58 | throw OCCIOException("Failed to read file: No roots to transfer are present"); 59 | } 60 | // Convert STEP to shape 61 | if (reader->TransferRoots() < 1) { 62 | throw OCCIOException("Failed to read file: Failed to transfer any roots"); 63 | } 64 | return reader->OneShape(); 65 | } 66 | 67 | TopoDS_Shape OCCUtils::IO::Read(const std::string& filename) { 68 | auto reader = IO::Reader::STEPorIGESReader(filename); 69 | IO::Reader::ReadFile(reader, filename); 70 | return IO::Reader::ReadOneShape(reader); 71 | } -------------------------------------------------------------------------------- /src/Point.cxx: -------------------------------------------------------------------------------- 1 | #include "occutils/Point.hxx" 2 | #include "occutils/Axis.hxx" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | gp_Pnt OCCUtils::Point::Origin() { 10 | return gp_Pnt(); 11 | } 12 | 13 | gp_Pnt operator+(const gp_Pnt &a, const gp_Pnt &b){ 14 | return gp_Pnt(a.X() + b.X(), a.Y() + b.Y(), a.Z() + b.Z()); 15 | } 16 | 17 | gp_Pnt operator+(const gp_Pnt &a, const gp_Vec &b){ 18 | return gp_Pnt(a.X() + b.X(), a.Y() + b.Y(), a.Z() + b.Z()); 19 | } 20 | 21 | gp_Pnt operator+(const gp_Pnt &a, const gp_XYZ &b){ 22 | return gp_Pnt(a.X() + b.X(), a.Y() + b.Y(), a.Z() + b.Z()); 23 | } 24 | 25 | gp_Pnt operator-(const gp_Pnt &a, const gp_Pnt &b){ 26 | return gp_Pnt(a.X() - b.X(), a.Y() - b.Y(), a.Z() - b.Z()); 27 | } 28 | 29 | gp_Pnt operator-(const gp_Pnt &a, const gp_Vec &b){ 30 | return gp_Pnt(a.X() - b.X(), a.Y() - b.Y(), a.Z() - b.Z()); 31 | } 32 | 33 | gp_Pnt operator-(const gp_Pnt &a, const gp_XYZ &b){ 34 | return gp_Pnt(a.X() - b.X(), a.Y() - b.Y(), a.Z() - b.Z()); 35 | } 36 | 37 | 38 | gp_Pnt OCCUtils::Point::Midpoint(const std::initializer_list& points) { 39 | double x = 0.0, y = 0.0, z = 0.0; 40 | for (const gp_Pnt &pnt : points) { 41 | x += pnt.X(); 42 | y += pnt.Y(); 43 | z += pnt.Z(); 44 | } 45 | size_t size = points.size(); 46 | return gp_Pnt(x / size, y / size, z / size); 47 | 48 | } 49 | 50 | gp_Pnt OCCUtils::Point::Midpoint(const std::vector& points) { 51 | double x = 0.0, y = 0.0, z = 0.0; 52 | for (const gp_Pnt &pnt : points) { 53 | x += pnt.X(); 54 | y += pnt.Y(); 55 | z += pnt.Z(); 56 | } 57 | size_t size = points.size(); 58 | return gp_Pnt(x / size, y / size, z / size); 59 | } 60 | 61 | double OCCUtils::Point::Distance(const gp_Pnt& pnt, const gp_Ax1& axis) { 62 | return Axis::Distance(axis, pnt); 63 | } 64 | 65 | gp_Pnt OCCUtils::Point::OrthogonalProjectOnto(const gp_Pnt &pnt, const gp_Ax1 &ax) { 66 | Handle(Geom_Line) hax = new Geom_Line(ax); 67 | auto projector = GeomAPI_ProjectPointOnCurve(pnt, hax); 68 | projector.Perform(pnt); 69 | if (projector.NbPoints() == 0) { 70 | // TODO use more appropriate exception 71 | throw std::out_of_range("Projection of point onto curve failed"); 72 | } 73 | return projector.NearestPoint(); 74 | } 75 | 76 | gp_Pnt2d OCCUtils::Point::OrthogonalProjectOnto(const gp_Pnt2d &pnt, const gp_Ax2d &ax) { 77 | Handle(Geom2d_Line) hax = new Geom2d_Line(ax); 78 | auto projector = Geom2dAPI_ProjectPointOnCurve(pnt, hax); 79 | if (projector.NbPoints() == 0) { 80 | // TODO use more appropriate exception 81 | throw std::out_of_range("Projection of point onto axis failed"); 82 | } 83 | return projector.NearestPoint(); 84 | } 85 | 86 | 87 | gp_Pnt OCCUtils::Point::From2d(const gp_Pnt2d& pnt) { 88 | return gp_Pnt(pnt.X(), pnt.Y(), 0.0); 89 | } 90 | 91 | gp_Pnt OCCUtils::Point::From2d(double x, double y) { 92 | return gp_Pnt(x, y, 0.0); 93 | } -------------------------------------------------------------------------------- /include/occutils/ListUtils.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include // std::pair<> 4 | #include 5 | #include 6 | #include 7 | 8 | namespace OCCUtils { 9 | namespace ListUtils { 10 | 11 | /** 12 | * Split a NCollection_List into 13 | * a head list and a tail list. 14 | */ 15 | template 16 | std::pair, NCollection_List> SplitIntoHeadAndTail( 17 | const NCollection_List& arg, 18 | size_t headSize 19 | ) { 20 | auto ret = std::make_pair(NCollection_List(), NCollection_List()); 21 | // Iterate arg 22 | for(const T& value : arg) { 23 | if(headSize > 0) { 24 | ret.first.Append(value); 25 | headSize--; 26 | } else { 27 | ret.second.Append(value); 28 | } 29 | } 30 | return ret; 31 | } 32 | 33 | /** 34 | * Convert any STL or STL-like container of type T 35 | * to a NCollection_List. 36 | */ 37 | template typename Container, typename T, typename Allocator> 38 | NCollection_List ToOCCList(const Container& args) { 39 | NCollection_List ret; 40 | for(const T& arg : args) { 41 | ret.Append(arg); 42 | } 43 | return ret; 44 | } 45 | 46 | /** 47 | * Convert any STL or STL-like container of type T 48 | * to a NCollection_List. 49 | */ 50 | template 51 | NCollection_List ToOCCList(const std::initializer_list& args) { 52 | NCollection_List ret; 53 | for(const T& arg : args) { 54 | ret.Append(arg); 55 | } 56 | return ret; 57 | } 58 | 59 | /** 60 | * Convert any simple-style template container to an OCC list. 61 | * Also: Convenience to convert an initializer list, 62 | * e.g. {arg1, arg2, arg3} to an OCC list 63 | */ 64 | template typename Container, typename T> 65 | NCollection_List ToOCCList(const Container& args) { 66 | NCollection_List ret; 67 | for(const T& arg : args) { 68 | ret.Append(arg); 69 | } 70 | return ret; 71 | } 72 | 73 | 74 | template 75 | std::vector ToSTLVector(const NCollection_List& args) { 76 | std::vector ret; 77 | ret.reserve(args.size()); 78 | for(const T& arg : args) { 79 | ret.push_back(arg); 80 | } 81 | return ret; 82 | } 83 | 84 | template 85 | std::list ToSTLList(const NCollection_List& args) { 86 | std::list ret; 87 | for(const T& arg : args) { 88 | ret.push_back(arg); 89 | } 90 | return ret; 91 | } 92 | } 93 | } -------------------------------------------------------------------------------- /src/Primitive.cxx: -------------------------------------------------------------------------------- 1 | #include "occutils/Primitive.hxx" 2 | #include "occutils/Direction.hxx" 3 | #include "occutils/Point.hxx" 4 | #include 5 | #include 6 | #include 7 | 8 | using namespace OCCUtils; 9 | 10 | TopoDS_Solid OCCUtils::Primitive::MakeBox( 11 | double xSize, double ySize, double zSize, 12 | int center, 13 | gp_Pnt origin) { 14 | // Compute offsets based on centering 15 | if(center & CenterX) { 16 | origin.SetX(origin.X() - xSize / 2.0); 17 | } 18 | if(center & CenterY) { 19 | origin.SetY(origin.Y() - ySize / 2.0); 20 | } 21 | if(center & CenterZ) { 22 | origin.SetZ(origin.Z() - zSize / 2.0); 23 | } 24 | // Build primitive 25 | BRepPrimAPI_MakeBox box(origin, xSize, ySize, zSize); 26 | box.Build(); 27 | return box.Solid(); 28 | 29 | } 30 | 31 | 32 | TopoDS_Solid OCCUtils::Primitive::MakeBox(const gp_Pnt& a, const gp_Pnt& b) { 33 | BRepPrimAPI_MakeBox box(a, b.X() - a.X(), b.Y() - a.Y(), b.Z() - a.Z()); 34 | box.Build(); 35 | return box.Solid(); 36 | } 37 | 38 | TopoDS_Solid OCCUtils::Primitive::MakeBox(const gp_Vec& a, const gp_Vec& b) { 39 | return MakeBox(gp_Pnt(a.X(), a.Y(), a.Z()), gp_Pnt(b.X(), b.Y(), b.Z())); 40 | } 41 | 42 | TopoDS_Solid OCCUtils::Primitive::MakeBox(const std::pair& ab) { 43 | return MakeBox(ab.first, ab.second); 44 | } 45 | 46 | TopoDS_Solid OCCUtils::Primitive::MakeBox(const std::pair& ab) { 47 | return MakeBox(ab.first, ab.second); 48 | } 49 | 50 | TopoDS_Solid OCCUtils::Primitive::MakeCylinder( 51 | double diameter, double length, 52 | Orientation orientation, 53 | int center, 54 | gp_Pnt origin) { 55 | // Compute offsets based on centering 56 | if(center & CenterL) { 57 | if(orientation == Orientation::X) { 58 | origin.SetX(origin.X() - diameter / 2.0); 59 | } else if(orientation == Orientation::Y) { 60 | origin.SetY(origin.Y() - diameter / 2.0); 61 | } else if(orientation == Orientation::Z) { 62 | origin.SetZ(origin.Z() - diameter / 2.0); 63 | } 64 | } 65 | // Which axis 66 | gp_Dir axis = (orientation == Orientation::X ? Direction::X() : (orientation == Orientation::Y ? Direction::Y() : Direction::Z())); 67 | // Build primitive 68 | gp_Ax2 ax(origin, axis); 69 | BRepPrimAPI_MakeCylinder cyl(ax, diameter / 2.0, length); 70 | cyl.Build(); 71 | return cyl.Solid(); 72 | } 73 | 74 | TopoDS_Solid OCCUtils::Primitive::MakeCube( 75 | double size, int center, gp_Pnt origin) { 76 | return MakeBox(size, size, size, center, origin); 77 | } 78 | 79 | 80 | TopoDS_Solid OCCUtils::Primitive::MakeCone( 81 | gp_Ax1 axis, 82 | double diameter1, 83 | double diameter2, 84 | double length, 85 | bool centerLength 86 | ) { 87 | BRepPrimAPI_MakeCone builder( 88 | gp_Ax2( 89 | centerLength ? 90 | axis.Location() - axis.Direction() * (length / 2) 91 | : axis.Location(), 92 | axis.Direction() 93 | ), 94 | diameter1, diameter2, length); 95 | return builder.Solid(); 96 | } -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | 3 | project(liboccutils) 4 | 5 | # Typical OpenCASCADE install paths 6 | include_directories (/usr/include/opencascade /usr/include/occt /usr/include/freetype2) 7 | 8 | add_library(occutils SHARED 9 | src/Surface.cxx 10 | src/ShapeComponents.cxx 11 | src/Shape.cxx 12 | src/Plane.cxx 13 | src/Pipe.cxx 14 | src/IO.cxx 15 | src/Curve.cxx 16 | src/STEPExport.cxx 17 | src/Primitive.cxx 18 | src/Direction.cxx 19 | src/Boolean.cxx 20 | src/ExtendedSTEP.cxx 21 | src/PrintOCC.cxx 22 | src/Fillet.cxx 23 | src/Wire.cxx 24 | src/Edge.cxx 25 | src/Face.cxx 26 | src/Equality.cxx 27 | src/Axis.cxx 28 | src/Point.cxx 29 | src/Line.cxx 30 | src/Compound.cxx 31 | ) 32 | 33 | #target_include_directories(occutils 34 | # PUBLIC 35 | # $ 36 | # $ 37 | #) 38 | 39 | set_target_properties(occutils 40 | PROPERTIES 41 | INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_CURRENT_LIST_DIR}/include" 42 | INTERFACE_LINK_LIBRARIES "occutils" 43 | ) 44 | 45 | target_include_directories(occutils PUBLIC "${CMAKE_CURRENT_LIST_DIR}/include") 46 | target_compile_options(occutils PRIVATE -Werror) 47 | target_compile_features(occutils PRIVATE cxx_std_17) 48 | 49 | install(TARGETS occutils 50 | LIBRARY DESTINATION lib 51 | PUBLIC_HEADER DESTINATION include) 52 | install(DIRECTORY 53 | "${CMAKE_CURRENT_LIST_DIR}/include/" 54 | DESTINATION include) 55 | 56 | # 57 | # Compile test suite 58 | # 59 | find_package(Boost COMPONENTS system filesystem unit_test_framework REQUIRED) 60 | 61 | add_executable(test_occutils 62 | tests/TestLine.cpp 63 | ) 64 | target_include_directories(test_occutils PUBLIC "${CMAKE_CURRENT_LIST_DIR}/include") 65 | target_compile_options(occutils PRIVATE -Werror) 66 | target_compile_features(test_occutils PRIVATE cxx_std_17) 67 | 68 | target_link_libraries(test_occutils 69 | occutils 70 | ${Boost_FILESYSTEM_LIBRARY} 71 | ${Boost_SYSTEM_LIBRARY} 72 | ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY} 73 | TKBO 74 | TKBRep 75 | TKOffset 76 | TKFeat 77 | TKFillet 78 | TKXDESTEP 79 | TKG2d 80 | TKG3d 81 | TKGeomAlgo 82 | TKGeomBase 83 | TKHLR 84 | TKIGES 85 | TKOffset 86 | TKPrim 87 | TKShHealing 88 | TKSTEP 89 | TKSTEP209 90 | TKSTEPAttr 91 | TKSTEPBase 92 | TKXSBase 93 | TKSTL 94 | TKTopAlgo 95 | TKV3d 96 | ) 97 | add_test(test_occutils test_occutils) 98 | # make "make test" run the test program 99 | add_custom_target(test 100 | DEPENDS test_occutils 101 | COMMAND ./test_occutils 102 | ) 103 | 104 | #export ( 105 | # TARGETS 106 | # occutils 107 | # FILE 108 | # occutils.cmake 109 | #) -------------------------------------------------------------------------------- /include/occutils/Primitive.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /** 3 | * Create boundary representation (BRep) primitives 4 | */ 5 | #include 6 | #include 7 | #include 8 | 9 | namespace OCCUtils { 10 | namespace Primitive { 11 | /** 12 | * Configure how a primitive is centered in the coordinate system 13 | */ 14 | enum PositionCentering : uint8_t { 15 | DoNotCenter = 0, 16 | CenterX = 1, 17 | CenterY = 2, 18 | CenterZ = 4, 19 | CenterL = 8 20 | }; 21 | 22 | /** 23 | * Configure in which axis a primitive is oriented. 24 | * Defines the direction of the primitive's main axis. 25 | */ 26 | enum class Orientation : uint8_t { 27 | X = 1, 28 | Y = 2, 29 | Z = 4 30 | }; 31 | 32 | /** 33 | * Make a box that can be centered on all axes individually. 34 | * @param xSize The dimension in the X dimension 35 | * @param ySize The dimension in the Y dimension 36 | * @param zSize The dimension in the Z dimension 37 | * @param center A bitwise-OR (|) combination of CenterX, CenterY & CenterZ. 38 | * A set flag causes the box to be center in that dimension on the origin 39 | * @param origin The point where to create the box. Default is (0,0,0) 40 | */ 41 | TopoDS_Solid MakeBox( 42 | double xSize, double ySize, double zSize, 43 | int center=0, 44 | gp_Pnt origin=gp_Pnt()); 45 | 46 | 47 | /** 48 | * Make a box that stretches from point a to point b. 49 | */ 50 | TopoDS_Solid MakeBox(const gp_Pnt& a, const gp_Pnt& b); 51 | TopoDS_Solid MakeBox(const gp_Vec& a, const gp_Vec& b); 52 | TopoDS_Solid MakeBox(const std::pair& ab); 53 | TopoDS_Solid MakeBox(const std::pair& ab); 54 | 55 | /** 56 | * Make a cube that can be centered on all axes individually. 57 | * @param size The dimension in the X/Y/Z dimensions 58 | * @param center A bitwise-OR (|) combination of CenterX, CenterY & CenterZ. 59 | * A set flag causes the box to be center in that dimension on the origin 60 | * @param origin The point where to create the box. Default is (0,0,0) 61 | */ 62 | TopoDS_Solid MakeCube( 63 | double size, 64 | int center=0, 65 | gp_Pnt origin=gp_Pnt()); 66 | 67 | /** 68 | * Make a cone. 69 | * @param diameter1 The diameter of the cone at the origin point 70 | * @param diameter2 The diameter of the cone opposite to the origin point 71 | * @param length The total length of the cone 72 | * @param axis Defines both the origin point and the axis of the cone 73 | * @param centerLength Whether to center the cone on its length 74 | */ 75 | TopoDS_Solid MakeCone( 76 | gp_Ax1 axis, 77 | double diameter1, 78 | double diameter2, 79 | double length, 80 | bool centerLength=false 81 | ); 82 | 83 | /** 84 | * Make a cylinder that can be centered 85 | * @param orientation How the cylinder's main axis is oriented 86 | * @param center A bitwise-OR (|) combination of 87 | * CenterL and CenterD or 0. 88 | */ 89 | TopoDS_Solid MakeCylinder( 90 | double diameter, double length, 91 | Orientation orientation = Orientation::Z, 92 | int center=0, 93 | gp_Pnt origin=gp_Pnt()); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/ExtendedSTEP.cxx: -------------------------------------------------------------------------------- 1 | #include "occutils/ExtendedSTEP.hxx" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | using namespace std; 18 | 19 | namespace OCCUtils { 20 | namespace STEP { 21 | /** 22 | * Internal state 23 | */ 24 | struct _ExtendedSTEPExporterInternals { 25 | _ExtendedSTEPExporterInternals() : application(XCAFApp_Application::GetApplication()) 26 | { 27 | // Create document 28 | application->NewDocument("MDTV-XCAF", document); 29 | // Get tools 30 | shapeTool = XCAFDoc_DocumentTool::ShapeTool(document->Main()); 31 | colorTool = XCAFDoc_DocumentTool::ColorTool(document->Main()); 32 | materialTool = XCAFDoc_DocumentTool::MaterialTool(document->Main()); 33 | layerTool = XCAFDoc_DocumentTool::LayerTool(document->Main()); 34 | dimTolTool = XCAFDoc_DocumentTool::DimTolTool(document->Main()); 35 | // Reserve some space in the labels to prevent frequent reallocations 36 | shapeLabels.reserve(25); 37 | } 38 | // Base attributes 39 | Handle(TDocStd_Document) document; 40 | Handle(TDocStd_Application) application; 41 | // Tools 42 | Handle(XCAFDoc_ShapeTool) shapeTool; 43 | Handle(XCAFDoc_ColorTool) colorTool; 44 | Handle(XCAFDoc_MaterialTool) materialTool; 45 | Handle(XCAFDoc_LayerTool) layerTool; 46 | Handle(XCAFDoc_DimTolTool) dimTolTool; 47 | // Internal storage for labels 48 | std::vector shapeLabels; 49 | }; 50 | 51 | ExtendedSTEPExporter::ExtendedSTEPExporter() : internals(new _ExtendedSTEPExporterInternals()) { 52 | 53 | } 54 | 55 | ExtendedSTEPExporter::~ExtendedSTEPExporter() { 56 | delete internals; 57 | } 58 | 59 | size_t ExtendedSTEPExporter::AddShape(TopoDS_Shape& shape) { 60 | TDF_Label shapeLabel = internals->shapeTool->NewShape(); 61 | internals->shapeTool->SetShape(shapeLabel, shape); 62 | // Add to internal labels 63 | size_t idx = internals->shapeLabels.size(); 64 | internals->shapeLabels.push_back(shapeLabel); 65 | return idx; 66 | } 67 | 68 | size_t ExtendedSTEPExporter::AddShapeWithColor(TopoDS_Shape& shape, const Quantity_Color& color, XCAFDoc_ColorType colorType) { 69 | TDF_Label shapeLabel = internals->shapeTool->NewShape(); 70 | internals->shapeTool->SetShape(shapeLabel, shape); 71 | internals->colorTool->SetColor(shape, color, colorType); 72 | // Add to internal labels 73 | size_t idx = internals->shapeLabels.size(); 74 | internals->shapeLabels.push_back(shapeLabel); 75 | return idx; 76 | } 77 | 78 | void ExtendedSTEPExporter::Write(const std::string& filename) { 79 | STEPCAFControl_Writer writer; 80 | writer.SetMaterialMode(true); 81 | writer.SetDimTolMode(true); 82 | writer.SetLayerMode(true); 83 | writer.SetPropsMode(true); 84 | writer.SetColorMode(true); 85 | writer.Perform(internals->document, filename.c_str()); 86 | } 87 | } 88 | } -------------------------------------------------------------------------------- /include/occutils/Axis.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | gp_Ax1 operator+(const gp_Ax1& axis, const gp_Vec& vec); 7 | 8 | /** 9 | * Shifts the origin point of the axis by vec. 10 | * The direction is unchanged 11 | */ 12 | gp_Ax1 operator+(const gp_Ax1& axis, const gp_XYZ& vec); 13 | 14 | namespace OCCUtils { 15 | namespace Axis { 16 | /** 17 | * Check if the given axis contains the given point, 18 | * i.e. if the distance between the point and the axis is <= tolerance 19 | */ 20 | bool Contains(const gp_Ax1& axis, const gp_Pnt& pnt, double tolerance = Precision::Confusion()); 21 | 22 | /** 23 | * Get the distance between axis and pnt, 24 | * strictly defined as the distance between pnt and the orthogonal 25 | * projection of pnt onto axis. 26 | */ 27 | double Distance(const gp_Ax1& axis, const gp_Pnt& pnt); 28 | } 29 | namespace Ax1 30 | { 31 | /** 32 | * Get the Ax1 with: 33 | * - Location = origin point 34 | * - Direction = X axis 35 | */ 36 | gp_Ax1 OX(); 37 | /** 38 | * Get the Ax1 with: 39 | * - Location = origin point 40 | * - Direction = Y axis 41 | */ 42 | gp_Ax1 OY(); 43 | /** 44 | * Get the Ax1 with: 45 | * - Location = origin point 46 | * - Direction = Z axis 47 | */ 48 | gp_Ax1 OZ(); 49 | /** 50 | * Get the Ax1 with: 51 | * - Location = origin point 52 | * - Direction = negative X axis 53 | */ 54 | gp_Ax1 OMinusX(); 55 | /** 56 | * Get the Ax1 with: 57 | * - Location = origin point 58 | * - Direction = negative Y axis 59 | */ 60 | gp_Ax1 OMinusY(); 61 | /** 62 | * Get the Ax1 with: 63 | * - Location = origin point 64 | * - Direction = negative Z axis 65 | */ 66 | gp_Ax1 OMinusZ(); 67 | } 68 | namespace Ax2 { 69 | /** 70 | * Initialize a gp_Ax2 from a gp_Ax1. 71 | * Note that the main direction is strictly defined, 72 | * but the "X" direction isn't. However for many use-cases 73 | * this is fine. 74 | */ 75 | gp_Ax2 FromAx1(const gp_Ax1& axis); 76 | /** 77 | * Get the Ax1 with: 78 | * - Location = origin point 79 | * - Main axis = X axis 80 | * - Normal: Automatically defined 81 | */ 82 | gp_Ax2 OX(); 83 | /** 84 | * Get the Ax1 with: 85 | * - Location = origin point 86 | * - Main axis = Y axis 87 | * - Normal: Automatically defined 88 | */ 89 | gp_Ax2 OY(); 90 | /** 91 | * Get the Ax1 with: 92 | * - Location = origin point 93 | * - Main axis = Z axis 94 | * - Normal: Automatically defined 95 | */ 96 | gp_Ax2 OZ(); 97 | /** 98 | * Get the Ax1 with: 99 | * - Location = origin point 100 | * - Main axis = negative X axis 101 | * - Normal: Automatically defined 102 | */ 103 | gp_Ax2 OMinusX(); 104 | /** 105 | * Get the Ax1 with: 106 | * - Location = origin point 107 | * - Main axis = negative Y axis 108 | * - Normal: Automatically defined 109 | */ 110 | gp_Ax2 OMinusY(); 111 | /** 112 | * Get the Ax1 with: 113 | * - Location = origin point 114 | * - Main axis = negative Z axis 115 | * - Normal: Automatically defined 116 | */ 117 | gp_Ax2 OMinusZ(); 118 | } 119 | } -------------------------------------------------------------------------------- /src/Boolean.cxx: -------------------------------------------------------------------------------- 1 | #include "occutils/Boolean.hxx" 2 | #include "occutils/ListUtils.hxx" 3 | #include "occutils/Shape.hxx" 4 | #include 5 | #include 6 | 7 | using namespace OCCUtils; 8 | 9 | TopoDS_Shape OCCUtils::Boolean::Fuse(const TopTools_ListOfShape& arguments, const TopTools_ListOfShape& tools) { 10 | if(arguments.Size() + tools.Size() == 1) { 11 | // Return that shape! 12 | if(arguments.Size() == 1) { 13 | return arguments.First(); 14 | } else if(tools.Size() == 1) { 15 | return tools.First(); 16 | } else { 17 | // Will never happen, just in case of hard issues to provide a hard return path 18 | return TopoDS_Shape(); 19 | } 20 | } else if(arguments.Size() + tools.Size() == 0) { 21 | // No shape => return no shape 22 | return TopoDS_Shape(); 23 | } else if(arguments.Size() == 0) { 24 | throw std::invalid_argument("Fuse arguments must have at least one shape!"); 25 | } else if(tools.Size() == 0) { 26 | throw std::invalid_argument("Fuse tools must have at least one shape!"); 27 | } 28 | // Configure fuse 29 | BRepAlgoAPI_Fuse fuse; 30 | fuse.SetArguments(arguments); 31 | fuse.SetTools(tools); 32 | // Run fuse 33 | fuse.Build(); 34 | return fuse.Shape(); // Raises NotDone if not done. 35 | } 36 | 37 | TopoDS_Shape OCCUtils::Boolean::Fuse(const TopTools_ListOfShape& shapes) { 38 | // We need "tools" and "arguments". 39 | // For fuse, the exact split does not matter, 40 | // but each must be size >= 1! 41 | auto toolsAndArgs = ListUtils::SplitIntoHeadAndTail(shapes, 1); 42 | return Fuse(toolsAndArgs.second, toolsAndArgs.first); 43 | } 44 | 45 | TopoDS_Shape OCCUtils::Boolean::Fuse(const std::initializer_list& shapes) { 46 | return Fuse(OCCUtils::ListUtils::ToOCCList(shapes)); 47 | } 48 | 49 | TopoDS_Shape OCCUtils::Boolean::Cut(const TopTools_ListOfShape& positive, const TopTools_ListOfShape& negative) { 50 | if(positive.Size() == 0) { 51 | throw std::invalid_argument("Cut positive must have at least one shape!"); 52 | } 53 | if(negative.Size() == 0) { 54 | // Just fuse positive 55 | return Fuse(positive); 56 | } 57 | // Configure fuse 58 | BRepAlgoAPI_Cut cut; 59 | cut.SetArguments(positive); 60 | cut.SetTools(negative); 61 | // Run cut 62 | cut.Build(); 63 | return cut.Shape(); // Raises NotDone if not done. 64 | } 65 | 66 | TopoDS_Shape OCCUtils::Boolean::Cut(const TopoDS_Shape& positive, const TopoDS_Shape& negative) { 67 | return Cut( 68 | OCCUtils::ListUtils::ToOCCList({positive}), 69 | OCCUtils::ListUtils::ToOCCList({negative}) 70 | ); 71 | } 72 | 73 | 74 | TopoDS_Shape OCCUtils::Boolean::Cut(const TopoDS_Shape& positive, const TopTools_ListOfShape& negative) { 75 | return Cut( 76 | OCCUtils::ListUtils::ToOCCList({positive}), 77 | negative 78 | ); 79 | } 80 | 81 | 82 | TopoDS_Shape OCCUtils::Boolean::Cut(const TopoDS_Shape& positive, const std::initializer_list& negative) { 83 | return Cut( 84 | OCCUtils::ListUtils::ToOCCList({positive}), 85 | OCCUtils::ListUtils::ToOCCList(negative) 86 | ); 87 | } 88 | 89 | TopoDS_Shape OCCUtils::Boolean::Cut(const std::vector& positive, const std::vector& negative) { 90 | return Cut(Shapes::FromSolids(positive), Shapes::FromSolids(negative)); 91 | } 92 | 93 | TopoDS_Shape OCCUtils::Boolean::Cut(const TopoDS_Solid& positive, const std::vector& negative) { 94 | return Cut({positive}, Shapes::FromSolids(negative)); 95 | } 96 | 97 | TopoDS_Shape OCCUtils::Boolean::Cut(const std::vector& positive, const std::vector& negative) { 98 | return Cut(Shapes::FromFaces(positive), Shapes::FromFaces(negative)); 99 | } 100 | 101 | TopoDS_Shape OCCUtils::Boolean::Cut(const TopoDS_Face& positive, const std::vector& negative) { 102 | return Cut({positive}, Shapes::FromFaces(negative)); 103 | } 104 | 105 | 106 | TopoDS_Shape OCCUtils::Boolean::Fuse(const std::vector& shapes) { 107 | return Fuse(Shapes::FromSolids(shapes)); 108 | } 109 | 110 | TopoDS_Shape OCCUtils::Boolean::Fuse(const std::initializer_list& shapes) { 111 | return Fuse(Shapes::FromSolids(shapes)); 112 | } 113 | 114 | TopoDS_Shape OCCUtils::Boolean::Fuse(const std::vector& shapes) { 115 | return Fuse(Shapes::FromFaces(shapes)); 116 | } 117 | 118 | TopoDS_Shape OCCUtils::Boolean::Fuse(const std::initializer_list& shapes) { 119 | return Fuse(Shapes::FromFaces(shapes)); 120 | } 121 | -------------------------------------------------------------------------------- /src/Wire.cxx: -------------------------------------------------------------------------------- 1 | #include "occutils/Wire.hxx" 2 | #include "occutils/Edge.hxx" 3 | #include "occutils/Pipe.hxx" 4 | #include "occutils/Face.hxx" 5 | #include "occutils/Point.hxx" 6 | #include "occutils/Equality.hxx" 7 | #include 8 | #include 9 | #include 10 | 11 | using namespace OCCUtils; 12 | 13 | TopoDS_Wire OCCUtils::Wire::FromEdges(const std::initializer_list& edges) { 14 | BRepLib_MakeWire wireMaker; 15 | for(const auto& edge : edges) { 16 | if(edge.IsNull()) { 17 | continue; 18 | } 19 | wireMaker.Add(edge); 20 | } 21 | return wireMaker.IsDone() ? wireMaker.Wire() : TopoDS_Wire(); 22 | } 23 | 24 | TopoDS_Wire OCCUtils::Wire::FromEdges(const std::vector& edges) { 25 | BRepLib_MakeWire wireMaker; 26 | for(const auto& edge : edges) { 27 | if(edge.IsNull()) { 28 | continue; 29 | } 30 | wireMaker.Add(edge); 31 | } 32 | return wireMaker.IsDone() ? wireMaker.Wire() : TopoDS_Wire(); 33 | } 34 | 35 | OCCUtils::Wire::IncrementalBuilder::IncrementalBuilder(const gp_Pnt& pnt) 36 | : current(pnt), currentDirection(std::nullopt), edges() { 37 | edges.reserve(25); // Prevent frequent reallocations at the expense of some memory 38 | } 39 | 40 | /** 41 | * Add a line segment 42 | */ 43 | void OCCUtils::Wire::IncrementalBuilder::Line(double dx, double dy, double dz) { 44 | gp_Pnt p1(current); // Copy current point 45 | // Increment coordinates 46 | current = current + gp_Pnt(dx, dy, dz); 47 | // Create edges 48 | currentDirection = gp_Vec(p1, current); 49 | edges.emplace_back(Edge::FromPoints(p1, current)); 50 | } 51 | 52 | 53 | void OCCUtils::Wire::IncrementalBuilder::Arc90( 54 | double dx, double dy, double dz, 55 | double centerDx, double centerDy, double centerDz, 56 | const gp_Dir& normal) { 57 | gp_Pnt p2 = current + gp_Pnt(dx, dy, dz); 58 | gp_Pnt center = current + gp_Pnt(centerDx, centerDy, centerDz); 59 | gp_Dir resultingDirection(gp_Vec(current, center)); 60 | double radius = current.Distance(center); 61 | double radiusAlt = p2.Distance(center); 62 | if(abs(radius - radiusAlt) >= Precision::Confusion()) { 63 | throw std::invalid_argument("dx/dy/dz does not match centerD...!"); 64 | } 65 | // Current algorithm: Compute both options, 66 | // one is 90° and one is 270°, select the shorter one. 67 | auto option1 = Edge::CircleSegment(gp_Ax2(center, normal), radius, current, p2); 68 | auto option2 = Edge::CircleSegment(gp_Ax2(center, normal), radius, p2, current); 69 | double length1 = Edge::Length(option1); 70 | double length2 = Edge::Length(option2); 71 | edges.emplace_back(length1 < length2 ? option1 : option2); 72 | current = p2; 73 | currentDirection = resultingDirection; 74 | } 75 | 76 | TopoDS_Wire OCCUtils::Wire::IncrementalBuilder::Wire() { 77 | return Wire::FromEdges(edges); 78 | } 79 | 80 | gp_Pnt OCCUtils::Wire::IncrementalBuilder::Location() { 81 | return gp_Pnt(current); // make copy to avoid modification 82 | } 83 | 84 | /** 85 | * Create a pipe from the wire using the given profile. 86 | */ 87 | TopoDS_Shape OCCUtils::Wire::IncrementalBuilder::Pipe(const TopoDS_Face& profile) { 88 | return Pipe::FromSplineAndProfile(this->Wire(), profile); 89 | } 90 | 91 | 92 | TopoDS_Shape OCCUtils::Wire::IncrementalBuilder::PipeWithCircularProfile(double radius) { 93 | auto profile = Face::FromEdge(Edge::FullCircle(gp_Ax2(current, currentDirection.value_or(Direction::Z())), radius)); 94 | return Pipe(profile); 95 | } 96 | 97 | std::optional OCCUtils::Wire::IncrementalBuilder::Direction() { 98 | // Make copy of direction to prevent modification! 99 | if(currentDirection.has_value()) { 100 | return std::make_optional(gp_Dir(currentDirection.value())); 101 | } else { 102 | return std::nullopt; 103 | } 104 | } 105 | 106 | TopoDS_Wire OCCUtils::Wire::FromPoints(const std::vector& points, bool close) { 107 | if (points.size() < 2) { 108 | return TopoDS_Wire (); 109 | } 110 | // Build directly without making a vector of edges 111 | // This is likely slightly more efficient 112 | BRepLib_MakeWire makeWire; 113 | for (size_t i = 0; i < points.size() - 1; i++) 114 | { 115 | const auto& p1 = points[i]; 116 | const auto& p2 = points[i + 1]; 117 | // Ignore duplicate points 118 | if (p1 == p2) { 119 | continue; 120 | } 121 | makeWire.Add(Edge::FromPoints(p1, p2)); 122 | } 123 | // Close curve if enabled 124 | if(close) { 125 | const auto& p0 = points[0]; 126 | const auto& plast = points[points.size() - 1]; 127 | if(p0 != plast) { 128 | makeWire.Add(Edge::FromPoints(p0, plast)); 129 | } 130 | } 131 | return makeWire.Wire (); 132 | } 133 | -------------------------------------------------------------------------------- /include/occutils/Boolean.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /** 3 | * Boolean operation utilities 4 | */ 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace OCCUtils { 14 | namespace Boolean { 15 | 16 | /** 17 | * Fuse two or more shapes in a OCC-style container. 18 | * Returns a shape with IsNull() == true if there is no shape 19 | */ 20 | TopoDS_Shape Fuse(const TopTools_ListOfShape& shapes); 21 | 22 | /** 23 | * Fuse with two lists of arguments. 24 | * For Fuse() this is equivalent to joining the lists. 25 | * Returns a shape with IsNull() == true if there is no shape 26 | */ 27 | TopoDS_Shape Fuse(const TopTools_ListOfShape& arguments, const TopTools_ListOfShape& tools); 28 | 29 | /** 30 | * Fuse two or more shapes in an STL-like container 31 | * Raises std::invalid_argument if there is only ONE shape 32 | */ 33 | template typename Container, typename Allocator> 34 | TopoDS_Shape Fuse(const Container& shapes) { 35 | return Fuse(OCCUtils::ListUtils::ToOCCList(shapes)); 36 | } 37 | 38 | /** 39 | * Fuse two or more shapes in a OCC-style container 40 | * Raises std::invalid_argument if there is only ONE shape 41 | */ 42 | template typename Container> 43 | TopoDS_Shape Fuse(const Container& shapes) { 44 | return Fuse(OCCUtils::ListUtils::ToOCCList(shapes)); 45 | } 46 | 47 | /** 48 | * Fuse two or more shapes in a OCC-style container. 49 | * Convenience call function: 50 | * Fuse({shape1, shape2}) 51 | * Fuse({shape1, shape2, shape3}) 52 | * Raises std::invalid_argument if there is only ONE shape 53 | */ 54 | TopoDS_Shape Fuse(const std::initializer_list& shapes); 55 | TopoDS_Shape Fuse(const std::vector& shapes); 56 | TopoDS_Shape Fuse(const std::initializer_list& shapes); 57 | TopoDS_Shape Fuse(const std::vector& shapes); 58 | TopoDS_Shape Fuse(const std::initializer_list& shapes); 59 | 60 | 61 | /** 62 | * Boolean subtraction with two lists of arguments. 63 | * negative is subtracted from positive, i.e. tools is negative, arguments is positive 64 | */ 65 | TopoDS_Shape Cut(const TopTools_ListOfShape& positive, const TopTools_ListOfShape& negative); 66 | 67 | /** 68 | * Boolean subtraction with two lists of arguments. 69 | * negative is subtracted from positive, i.e. tools is negative, arguments is positive 70 | */ 71 | TopoDS_Shape Cut(const TopoDS_Shape& positive, const TopoDS_Shape& negative); 72 | 73 | /** 74 | * Boolean subtraction with two lists of arguments. 75 | * negative is subtracted from positive, i.e. tools is negative, arguments is positive 76 | */ 77 | TopoDS_Shape Cut(const TopoDS_Shape& positive, const TopTools_ListOfShape& negative); 78 | 79 | 80 | /** 81 | * Boolean subtraction with two lists of arguments. 82 | * negative is subtracted from positive, i.e. tools is negative, arguments is positive 83 | */ 84 | TopoDS_Shape Cut(const TopoDS_Shape& positive, const std::initializer_list& negative); 85 | 86 | /** 87 | * Boolean subtraction with two lists of arguments. 88 | * negative is subtracted from positive, i.e. tools is negative, arguments is positive 89 | */ 90 | template typename Container, typename Allocator> 91 | TopoDS_Shape Cut(const TopoDS_Shape& positive, const Container& negative) { 92 | return Cut(OCCUtils::ListUtils::ToOCCList({positive}), OCCUtils::ListUtils::ToOCCList(negative)); 93 | } 94 | 95 | /** 96 | * Boolean subtraction with two lists of arguments. 97 | * tools is subtracted from argument, i.e. tools is negative, arguments is positive 98 | */ 99 | template typename Container, typename Allocator1, typename Allocator2> 100 | TopoDS_Shape Cut(const Container& positive, const Container& negative) { 101 | return Cut(OCCUtils::ListUtils::ToOCCList(positive), OCCUtils::ListUtils::ToOCCList(negative)); 102 | } 103 | 104 | /** 105 | * Boolean subtraction with two lists of TopoDS_Solids. 106 | * This is a common usecase, hence we provide a utility function. 107 | * tools is subtracted from argument, i.e. tools is negative, arguments is positive 108 | */ 109 | TopoDS_Shape Cut(const std::vector& positive, const std::vector& negative); 110 | 111 | /** 112 | * Boolean subtraction with two lists of TopoDS_Solids. 113 | * This is a common usecase, hence we provide a utility function. 114 | * tools is subtracted from argument, i.e. tools is negative, arguments is positive 115 | */ 116 | TopoDS_Shape Cut(const TopoDS_Solid& positive, const std::vector& negative); 117 | 118 | /** 119 | * Boolean subtraction with two lists of TopoDS_Faces. 120 | * This is a common usecase, hence we provide a utility function. 121 | * tools is subtracted from argument, i.e. tools is negative, arguments is positive 122 | */ 123 | TopoDS_Shape Cut(const std::vector& positive, const std::vector& negative); 124 | 125 | /** 126 | * Boolean subtraction with two lists of TopoDS_Faces. 127 | * This is a common usecase, hence we provide a utility function. 128 | * tools is subtracted from argument, i.e. tools is negative, arguments is positive 129 | */ 130 | TopoDS_Shape Cut(const TopoDS_Face& positive, const std::vector& negative); 131 | } 132 | } -------------------------------------------------------------------------------- /include/occutils/Surface.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /** 3 | * Utilities for analyzing surfaces 4 | */ 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace OCCUtils { 17 | namespace Surface { 18 | /** 19 | * Get the center of mass of a surface. 20 | * This isn't neccessarily on the surface itself. 21 | */ 22 | gp_Pnt CenterOfMass(const TopoDS_Shape& face); 23 | /** 24 | * Get the total surface area of a face, solid etc. 25 | */ 26 | double Area(const TopoDS_Shape& face); 27 | /** 28 | * Get the 3D surface from a given face 29 | * If no surface can be found, returnValue.Surface().IsNull() == true 30 | */ 31 | GeomAdaptor_Surface FromFace(const TopoDS_Face& face); 32 | /** 33 | * Get both the area and the center of mass of a surface. 34 | * This is more efficient than computing them individually. 35 | */ 36 | std::pair AreaAndCenterOfMass(const TopoDS_Shape& face); 37 | 38 | /** 39 | * Compute the normal of a surface at the given U/V coordinates. 40 | * @param surf The surface 41 | * @param u The U coordinate 42 | * @param v The V coordinate 43 | * @param precision Affects computation speed. 44 | * @returns The gp_Ax1 of the point on the surface described by U/V coords and the direction 45 | */ 46 | gp_Ax1 Normal(const GeomAdaptor_Surface& surf, double u = 0.0, double v = 0.0, double precision=1e-6); 47 | gp_Ax1 Normal(const GeomAdaptor_Surface& surf, const gp_Pnt2d& uv, double precision=1e-6); 48 | gp_Ax1 Normal(const GeomAdaptor_Surface& surf, const gp_XY& uv, double precision=1e-6); 49 | 50 | /** 51 | * Compute the normal direction of a surface at the given U/V coordinates. 52 | * @param surf The surface 53 | * @param u The U coordinate 54 | * @param v The V coordinate 55 | * @param precision Affects computation speed. 56 | */ 57 | gp_Dir NormalDirection(const GeomAdaptor_Surface& surf, double u = 0.0, double v = 0.0, double precision=1e-6); 58 | 59 | 60 | bool IsPlane(const GeomAdaptor_Surface& surf); 61 | bool IsCylinder(const GeomAdaptor_Surface& surf); 62 | bool IsCone(const GeomAdaptor_Surface& surf); 63 | bool IsSphere(const GeomAdaptor_Surface& surf); 64 | bool IsTorus(const GeomAdaptor_Surface& surf); 65 | bool IsBezierSurface(const GeomAdaptor_Surface& surf); 66 | bool IsBSplineSurface(const GeomAdaptor_Surface& surf); 67 | bool IsSurfaceOfRevolution(const GeomAdaptor_Surface& surf); 68 | bool IsSurfaceOfExtrusion(const GeomAdaptor_Surface& surf); 69 | bool IsOffsetSurface(const GeomAdaptor_Surface& surf); 70 | bool IsOtherSurface(const GeomAdaptor_Surface& surf); 71 | 72 | /** 73 | * Sample the point on the given surface at the given U/V coordinates. 74 | * The point represents the 0th derivative. 75 | */ 76 | gp_Pnt PointAt(const GeomAdaptor_Surface& surf, double u = 0.0, double v = 0.0); 77 | /** 78 | * Sample the point on the given surface at the given U/V coordinates. 79 | * The X coord of the gp_Pnt2d is interpreted as U whereas the Y coord of the gp_Pnt2d 80 | * is interpreted as V. 81 | * The point represents the 0th derivative. 82 | */ 83 | gp_Pnt PointAt(const GeomAdaptor_Surface& surf, const gp_Pnt2d& uv); 84 | /** 85 | * Sample the point on the given surface at the given U/V coordinates. 86 | * The X coord of the gp_XY is interpreted as U whereas the Y coord of the gp_XY 87 | * is interpreted as V. 88 | * The point represents the 0th derivative. 89 | */ 90 | gp_Pnt PointAt(const GeomAdaptor_Surface& surf, const gp_XY& uv); 91 | 92 | /** 93 | * Given a surface, creates a NxM uniformly spaces U/V grid between Umin/Umax and Vmin/Vmax. 94 | * Umin/Umax and Vmin/Umax are included and represent the first/last sample point. 95 | * The returned gp_XY coordinates shall be interpreted as U/V coordinates! 96 | */ 97 | std::vector UniformUVSampleLocations( 98 | const GeomAdaptor_Surface& surf, size_t uSamples=10, size_t vSamples=10); 99 | 100 | /** 101 | * Like UniformUVSampleLocations(), but computes sample points within the limits, 102 | * not including the U/V limits. 103 | * Equivalent to calling UniformUVSampleLocations() with 2 more samples in each direction 104 | */ 105 | std::vector UniformUVSampleLocationsWithin( 106 | const GeomAdaptor_Surface& surf, size_t uSamples=10, size_t vSamples=10); 107 | 108 | 109 | //TODO These are not implemented yet! 110 | /** 111 | * Sample the slope on the given surface at the given U/V coordinates. 112 | * The slope represents the 1st derivative. 113 | */ 114 | //gp_Pnt SlopeAt(const GeomAdaptor_Surface& surf, double u = 0.0, double v = 0.0); 115 | /** 116 | * Sample the curvature on the given surface at the given U/V coordinates. 117 | * The curvature represents the 2nd derivative. 118 | */ 119 | //gp_Pnt CurvatureAt(const GeomAdaptor_Surface& surf, double u = 0.0, double v = 0.0); 120 | } 121 | namespace Surfaces { 122 | struct SurfaceInfo { 123 | TopoDS_Face face; 124 | GeomAdaptor_Surface surface; 125 | }; 126 | /** 127 | * Get all faces & their surfaces that are within shape. 128 | * Uses ShapeComponents::AllFacesWithin(shape) internally. 129 | * Does not return surfaces that have no surface 130 | */ 131 | std::vector FromShape(const TopoDS_Shape& shape); 132 | 133 | /** 134 | * Filter surfaces by type 135 | */ 136 | std::vector Only(const std::vector& surfaces, 137 | GeomAbs_SurfaceType type); 138 | 139 | /** 140 | * Filter surfaces by a custom filter function 141 | */ 142 | std::vector Filter(const std::vector& surfaces, 143 | const std::function& filt); 144 | 145 | 146 | struct SurfaceTypeStats { 147 | void Add(GeomAbs_SurfaceType typ, size_t cnt = 1); 148 | size_t Count(GeomAbs_SurfaceType typ); 149 | // Get a human-readable summary of the stats 150 | std::string Summary(); 151 | // Counts how many time 152 | std::map count; 153 | }; 154 | 155 | SurfaceTypeStats Statistics(const std::vector& surfaces); 156 | 157 | } 158 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OCCUtils 2 | [OpenCASCADE](https://opencascade.com) utility library - algorithms and convenience functions. 3 | 4 | ## Design goals 5 | 6 | OCCUtils aims to be 7 | 8 | * **Simple to use**: Most tasks should be accomplishable in *one line of code*. 9 | * **Aid rapid development**: No need to write tons of utility functions ; no need to wait for long compile-times 10 | * **Modular**: Pull in only what you need, or just copy the underlying sourcecode. 11 | * **Clear**: What you write should be what you mean: `Edge::FromPoints()` instead of `BRepBuilderAPI_MakeEdge` three-liners. 12 | * **High-Level**: Common tasks in 3D engineering should be accomplishable without diving into low level OpenCASCADE code 13 | * **Modern**: Uses features from C++17, because those make your code more readable. 14 | * **Liberally licensed**: OCCUtils is licensed under Apache License v2.0, allowing you to copy & modify the sourcecode for use in your commercial projects for free. Keep in mind that OpenCASCADE is still LGPL licensed. 15 | 16 | Note that OCCUtils is very young and although it is used in multiple production projects, it might not have all the functionality you want or need, and might have some bugs. 17 | 18 | If you are missing functionality, feel free to submit an issue or pull request. 19 | 20 | ### Prerequisites 21 | 22 | First install OpenCASCADE 7.x. My preferred method on Ubuntu is to use the [FreeCAD daily PPA](https://launchpad.net/~freecad-maintainers/+archive/ubuntu/freecad-daily): 23 | 24 | ```sh 25 | sudo add-apt-repository ppa:freecad-maintainers/freecad-daily 26 | sudo apt-get update 27 | sudo apt install libocct\* 28 | ``` 29 | 30 | Also you need to install a recent version of CMake & GCC. Since we use C++17 features, a recent version of both G++ and CMake is required: 31 | ```sh 32 | sudo apt install cmake build-essential 33 | ``` 34 | 35 | On Ubuntu 18.04+ you don't need to do anything special to compile. 36 | 37 | ### How to build 38 | 39 | There are two preferred methods of building and installing OCCUtils 40 | 41 | ##### System-wide install 42 | 43 | ```sh 44 | git clone https://github.com/ulikoehler/OCCUtils.git 45 | cmake . 46 | make 47 | sudo make install 48 | ``` 49 | 50 | Then you can use e.g. 51 | ```cpp 52 | #include 53 | 54 | using namespace OCCUtils; 55 | 56 | // ... 57 | auto surfOpt = SurfaceUtils::SurfaceFromFace(face); 58 | // ... 59 | ``` 60 | 61 | and link with `-loccutils`. 62 | 63 | ##### git-submodule based installation 64 | 65 | This method involves adding the repository and building it as a subproject of your CMake-based main project. I recommend doing this especially for more complex projects. However you need some knowledge of CMake to get it working and debug related issues. 66 | 67 | In your project root directory: 68 | ```sh 69 | git submodule init 70 | git submodule add https://github.com/ulikoehler/OCCUtils.git OCCUtils 71 | ``` 72 | 73 | Then add this CMake code to your `CMakeLists.txt`: 74 | ```cmake 75 | add_subdirectory(OCCUtils) 76 | ``` 77 | 78 | and 79 | 80 | ```cmake 81 | add_dependencies( my_target occutils ) 82 | target_link_libraries( my_target occutils ) 83 | ``` 84 | replacing `my_target` with the name of your build target (i.e. the first argument you give to `add_executable()`). The `occutils` CMake script will take care of the rest. 85 | 86 | ## How to use 87 | 88 | On my [blog](https://techoverflow.net) I provide examples of specific usecases for OpenCASCADE, including the following full examples: 89 | * [OCCUtils full example: Make box and export it to STEP](https://techoverflow.net/2022/11/25/occutils-full-example-make-box-and-export-it-to-step/) 90 | 91 | ... and examples of how to use the specific OCCUtils functions: 92 | 93 | * [How to compute surface area of TopoDS_Face in OpenCASCADE](https://techoverflow.net/2019/06/13/how-to-compute-surface-area-of-topods_face-in-opencascade/) 94 | * [How to create a Cylinder TopoDS_Solid in OpenCASCADE](https://techoverflow.net/2019/06/13/how-to-create-a-cylinder-topods_solid-in-opencascade/) 95 | * [How to create a Box TopoDS_Solid in OpenCASCADE](https://techoverflow.net/2019/06/13/how-to-create-a-box-topods_solid-in-opencascade/) 96 | * [How to create a Cube TopoDS_Solid in OpenCASCADE](https://techoverflow.net/2019/06/13/how-to-create-a-cube-topods_solid-in-opencascade/) 97 | * [How to create TopTools_ListOfShape of two or more shapes in OpenCASCADE](https://techoverflow.net/2019/06/13/how-to-create-toptools_listofshape-of-two-or-more-shapes-in-opencascade/) 98 | * [How to iterate all edges in TopoDS_Face using OpenCASCADE](https://techoverflow.net/2019/06/13/how-to-iterate-all-edges-in-topods_face-using-opencascade/) 99 | * [How to export STEP file in OpenCASCADE](https://techoverflow.net/2019/06/13/how-to-export-step-file-in-opencascade/) 100 | * [How to fuse TopoDS_Shapes in OpenCASCADE (boolean AND)](https://techoverflow.net/2019/06/14/how-to-fuse-topods_shapes-in-opencascade-boolean-and/) 101 | * [How to cut shapes using OCCUtils for OpenCascade (boolean difference)](https://techoverflow.net/2022/11/25/how-to-cut-shapes-using-occutils-for-opencascade-boolean-difference/) 102 | * [How to export colored STEP files in OpenCASCADE](https://techoverflow.net/2019/06/14/how-to-export-colored-step-files-in-opencascade/) 103 | * [Overview of all standard colors available in OpenCASCADE](https://techoverflow.net/2019/06/14/overview-of-all-standard-colors-available-in-opencascade/) 104 | * [How to check if two gp_Pnt coincide in OpenCASCADE](https://techoverflow.net/2019/06/15/how-to-check-if-two-gp_pnt-coincide-in-opencascade/) 105 | * [How to create TopoDS_Edge from to gp_Pnt in OpenCASCADE](https://techoverflow.net/2019/06/15/how-to-create-topods_edge-from-to-gp_pnt-in-opencascade/) 106 | * [How to create TopoDS_Wire from TopoDS_Edge(s) in OpenCASCADE](https://techoverflow.net/2019/06/14/how-to-create-topods_wire-from-topods_edges-in-opencascade/) 107 | * [How to convert Geom_TrimmedCurve to GeomAdaptor_Curve in OpenCASCADE](https://techoverflow.net/2019/06/16/how-to-convert-geom_trimmedcurve-to-geomadaptor_curve-in-opencascade/) 108 | * [How to compute surface normal in OpenCASCADE](https://techoverflow.net/2019/06/26/how-to-compute-surface-normal-in-opencascade/) 109 | * [How to compute volume of TopoDS_Shape / TopoDS_Solid in OpenCASCADE](https://techoverflow.net/2019/06/29/how-to-compute-volume-of-topods_shape-topods_solid-in-opencascade/) 110 | * [How to get midpoint/center between points in OpenCASCADE](https://techoverflow.net/2019/06/30/how-to-get-midpoint-center-between-points-in-opencascade/) 111 | * [How to get gp_Dir orthogonal to two gp_Dirs in OpenCASCADE](https://techoverflow.net/2019/06/30/how-to-get-gp_dir-orthogonal-to-two-gp_dirs-in-opencascade/) 112 | * [How to check if gp_Ax1 contains gp_Pnt in OpenCASCADE](https://techoverflow.net/2019/06/30/how-to-check-if-gp_ax1-contains-gp_pnt-in-opencascade/) 113 | * [Computing distance between gp_Pnt and gp_Ax1 in OpenCASCADE](https://techoverflow.net/2019/06/30/computing-distance-between-gp_pnt-and-gp_ax1-in-opencascade/) 114 | * [Converting vector of TopoDS_Face to vector of TopoDS_Shape in OpenCASCADE](https://techoverflow.net/2019/07/05/converting-vector-of-topods_face-to-vector-of-topods_shape-in-opencascade/) 115 | * [Converting vector of TopoDS_Solid to vector of TopoDS_Shape in OpenCASCADE](https://techoverflow.net/2019/07/05/converting-vector-of-topods_solid-to-vector-of-topods_shape-in-opencascade/) 116 | * [How to make TopoDS_Face from gp_Pnts](https://techoverflow.net/2019/07/05/how-to-make-topods_face-from-gp_pnts/) 117 | -------------------------------------------------------------------------------- /include/occutils/ShapeComponents.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /** 3 | * Utilities for dissecting shapes into their components. 4 | */ 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace OCCUtils { 15 | namespace ShapeComponents { 16 | 17 | /** 18 | * Iterate over all components of shape and count how many 19 | * sub-shapes of type [type] it contains. 20 | * 21 | * NOTE: shape itself will NOT count, even if it is of type [type] 22 | */ 23 | size_t CountX(const TopoDS_Shape& shape, TopAbs_ShapeEnum type); 24 | size_t CountX(const std::vector& shapes, TopAbs_ShapeEnum type); 25 | size_t CountSolids(const TopoDS_Shape& shape, TopAbs_ShapeEnum type); 26 | 27 | /** 28 | * Get all solids in a given shape 29 | * (not including the shape itself, if it is a solid) 30 | */ 31 | std::vector AllSolidsWithin(const TopoDS_Shape& shape); 32 | std::vector AllSolidsWithin(const std::vector& shapes); 33 | /** 34 | * Get all faces in a given shape 35 | * (not including the shape itself, if it is a face) 36 | */ 37 | std::vector AllFacesWithin(const TopoDS_Shape& shape); 38 | std::vector AllFacesWithin(const std::vector& shapes); 39 | /** 40 | * Get all edges in a given shape 41 | * (not including the shape itself, if it is an edge) 42 | */ 43 | std::vector AllEdgesWithin(const TopoDS_Shape& shape); 44 | std::vector AllEdgesWithin(const std::vector& shapes); 45 | std::vector AllEdgesWithin(const std::vector& wires); 46 | /** 47 | * Get all wires in a given shape 48 | * (not including the shape itself, if it is a wire) 49 | */ 50 | std::vector AllWiresWithin(const TopoDS_Shape& shape); 51 | std::vector AllWiresWithin(const std::vector& shapes); 52 | /** 53 | * Get all vertices in a given shape 54 | * (not including the shape itself, if it is a vertex) 55 | */ 56 | std::vector AllVerticesWithin(const TopoDS_Shape& shape); 57 | std::vector AllVerticesWithin(const std::vector& shapes); 58 | /** 59 | * Get all vertex coordinates in a given shape. 60 | * Like AllVerticesWithin() but converts the TopoDS_Vertex instances 61 | * to gp_Pnts 62 | * (not including the shape itself, if it is a vertex) 63 | */ 64 | std::vector AllVertexCoordinatesWithin(const TopoDS_Shape& shape); 65 | std::vector AllVertexCoordinatesWithin(const std::vector& shapes); 66 | 67 | /** 68 | * If [shape] is a solid, return shape. 69 | * Else, if there is a single solid within [shape], 70 | * returns that solid. Else, returns no value. 71 | * 72 | * If there are multiple solids within shape: 73 | * - if [firstOfMultipleOK] == true => get first of these solids 74 | * - if [firstOfMultipleOK] == false => return std::nullopt 75 | */ 76 | std::optional TryGetSingleSolid (const TopoDS_Shape& shape, bool firstOfMultipleOK=true); 77 | /** 78 | * If [shape] is a face, return shape. 79 | * Else, if there is a single face within [shape], 80 | * returns that face. Else, returns no value. 81 | * 82 | * If there are multiple faces within shape: 83 | * - if [firstOfMultipleOK] == true => get first of these faces 84 | * - if [firstOfMultipleOK] == false => return std::nullopt 85 | */ 86 | std::optional TryGetSingleFace (const TopoDS_Shape& shape, bool firstOfMultipleOK=true); 87 | /** 88 | * If [shape] is a edge, return shape. 89 | * Else, if there is a single edge within [shape], 90 | * returns that edge. Else, returns no value. 91 | * 92 | * If there are multiple edges within shape: 93 | * - if [firstOfMultipleOK] == true => get first of these edges 94 | * - if [firstOfMultipleOK] == false => return std::nullopt 95 | */ 96 | std::optional TryGetSingleEdge (const TopoDS_Shape& shape, bool firstOfMultipleOK=true); 97 | /** 98 | * If [shape] is a edge, return shape. 99 | * Else, if there is a single edge within [shape], 100 | * returns that edge. Else, returns no value. 101 | * 102 | * If there are multiple wires within shape: 103 | * - if [firstOfMultipleOK] == true => get first of these wires 104 | * - if [firstOfMultipleOK] == false => return std::nullopt 105 | */ 106 | std::optional TryGetSingleWire (const TopoDS_Shape& shape, bool firstOfMultipleOK=true); 107 | /** 108 | * If [shape] is a vertex, return shape. 109 | * Else, if there is a single vertex within [shape], 110 | * returns that vertex. Else, returns no value. 111 | * 112 | * If there are multiple vertices within shape: 113 | * - if [firstOfMultipleOK] == true => get first of these vertices 114 | * - if [firstOfMultipleOK] == false => return std::nullopt 115 | */ 116 | std::optional TryGetSingleVertex (const TopoDS_Shape& shape, bool firstOfMultipleOK=true); 117 | 118 | /** 119 | * If [shape] is a solid, return shape. 120 | * Else, expects there to be a single solid within [shape]. 121 | * If there are zero solids within [shape], throws OCCTopologyCountMismatchException(). 122 | * If there are 2+ solids within [shape]: 123 | * - if [firstOfMultipleOK] == true => get first shape 124 | * - if [firstOfMultipleOK] == false => throws OCCTopologyCountMismatchException(). 125 | */ 126 | TopoDS_Solid GetSingleSolid (const TopoDS_Shape& shape, bool firstOfMultipleOK=true); 127 | /** 128 | * If [shape] is a face, return shape. 129 | * Else, expects there to be a single face within [shape]. 130 | * If there are zero faces within [shape], throws OCCTopologyCountMismatchException(). 131 | * If there are 2+ faces within [shape]: 132 | * - if [firstOfMultipleOK] == true => get first shape 133 | * - if [firstOfMultipleOK] == false => throws OCCTopologyCountMismatchException(). 134 | */ 135 | TopoDS_Face GetSingleFace (const TopoDS_Shape& shape, bool firstOfMultipleOK=true); 136 | /** 137 | * If [shape] is an edge, return shape. 138 | * Else, expects there to be a single edge within [shape]. 139 | * If there are zero edges within [shape], throws OCCTopologyCountMismatchException(). 140 | * If there are 2+ edges within [shape]: 141 | * - if [firstOfMultipleOK] == true => get first shape 142 | * - if [firstOfMultipleOK] == false => throws OCCTopologyCountMismatchException(). 143 | */ 144 | TopoDS_Edge GetSingleEdge (const TopoDS_Shape& shape, bool firstOfMultipleOK=true); 145 | /** 146 | * If [shape] is a wire, return shape. 147 | * Else, expects there to be a single wire within [shape]. 148 | * If there are zero wires within [shape], throws OCCTopologyCountMismatchException(). 149 | * If there are 2+ wires within [shape]: 150 | * - if [firstOfMultipleOK] == true => get first shape 151 | * - if [firstOfMultipleOK] == false => throws OCCTopologyCountMismatchException(). 152 | */ 153 | TopoDS_Wire GetSingleWire (const TopoDS_Shape& shape, bool firstOfMultipleOK=true); 154 | /** 155 | * If [shape] is an vertex, return shape. 156 | * Else, expects there to be a single vertex within [shape]. 157 | * If there are zero vertices within [shape], throws OCCTopologyCountMismatchException(). 158 | * If there are 2+ vertices within [shape]: 159 | * - if [firstOfMultipleOK] == true => get first shape 160 | * - if [firstOfMultipleOK] == false => throws OCCTopologyCountMismatchException(). 161 | */ 162 | TopoDS_Vertex GetSingleVertex (const TopoDS_Shape& shape, bool firstOfMultipleOK=true); 163 | } 164 | } -------------------------------------------------------------------------------- /src/Surface.cxx: -------------------------------------------------------------------------------- 1 | #include "occutils/Surface.hxx" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "occutils/ShapeComponents.hxx" 9 | 10 | using namespace std; 11 | 12 | double OCCUtils::Surface::Area(const TopoDS_Shape& face) { 13 | GProp_GProps gprops; 14 | BRepGProp::SurfaceProperties(face, gprops); 15 | return gprops.Mass(); 16 | } 17 | 18 | GeomAdaptor_Surface OCCUtils::Surface::FromFace(const TopoDS_Face& face) { 19 | BRepLib_FindSurface bfs(face); 20 | if (!bfs.Found()) { 21 | return GeomAdaptor_Surface(); 22 | } 23 | return bfs.Surface(); 24 | } 25 | 26 | gp_Pnt OCCUtils::Surface::CenterOfMass(const TopoDS_Shape& face) { 27 | GProp_GProps gprops; 28 | BRepGProp::SurfaceProperties(face, gprops); 29 | return gprops.CentreOfMass(); 30 | } 31 | 32 | std::pair OCCUtils::Surface::AreaAndCenterOfMass(const TopoDS_Shape& face) { 33 | GProp_GProps gprops; 34 | BRepGProp::SurfaceProperties(face, gprops); 35 | return std::make_pair(gprops.Mass(), gprops.CentreOfMass()); 36 | } 37 | 38 | 39 | std::vector OCCUtils::Surfaces::FromShape(const TopoDS_Shape& shape) { 40 | auto faces = ShapeComponents::AllFacesWithin(shape); 41 | // Create return vector 42 | std::vector ret; 43 | ret.reserve(faces.size()); 44 | for(const auto& face : faces) { 45 | SurfaceInfo info; 46 | info.face = face; 47 | info.surface = Surface::FromFace(face); 48 | if(!info.surface.Surface().IsNull()) { // If we have found the surface 49 | ret.push_back(info); 50 | } 51 | } 52 | return ret; 53 | } 54 | 55 | 56 | bool OCCUtils::Surface::IsPlane(const GeomAdaptor_Surface& surf) { 57 | return surf.GetType() == GeomAbs_Plane; 58 | } 59 | 60 | bool OCCUtils::Surface::IsCylinder(const GeomAdaptor_Surface& surf) { 61 | return surf.GetType() == GeomAbs_Cylinder; 62 | } 63 | 64 | bool OCCUtils::Surface::IsCone(const GeomAdaptor_Surface& surf) { 65 | return surf.GetType() == GeomAbs_Cone; 66 | } 67 | 68 | bool OCCUtils::Surface::IsSphere(const GeomAdaptor_Surface& surf) { 69 | return surf.GetType() == GeomAbs_Sphere; 70 | } 71 | 72 | bool OCCUtils::Surface::IsTorus(const GeomAdaptor_Surface& surf) { 73 | return surf.GetType() == GeomAbs_Torus; 74 | } 75 | 76 | bool OCCUtils::Surface::IsBezierSurface(const GeomAdaptor_Surface& surf) { 77 | return surf.GetType() == GeomAbs_BezierSurface; 78 | } 79 | 80 | bool OCCUtils::Surface::IsBSplineSurface(const GeomAdaptor_Surface& surf) { 81 | return surf.GetType() == GeomAbs_BSplineSurface; 82 | } 83 | 84 | bool OCCUtils::Surface::IsSurfaceOfRevolution(const GeomAdaptor_Surface& surf) { 85 | return surf.GetType() == GeomAbs_SurfaceOfRevolution; 86 | } 87 | 88 | bool OCCUtils::Surface::IsSurfaceOfExtrusion(const GeomAdaptor_Surface& surf) { 89 | return surf.GetType() == GeomAbs_SurfaceOfExtrusion; 90 | } 91 | 92 | bool OCCUtils::Surface::IsOffsetSurface(const GeomAdaptor_Surface& surf) { 93 | return surf.GetType() == GeomAbs_OffsetSurface; 94 | } 95 | 96 | bool OCCUtils::Surface::IsOtherSurface(const GeomAdaptor_Surface& surf) { 97 | return surf.GetType() == GeomAbs_OtherSurface; 98 | } 99 | 100 | 101 | std::vector OCCUtils::Surfaces::Only(const std::vector& surfaces, 102 | GeomAbs_SurfaceType type) { 103 | return Filter(surfaces, [&](const GeomAdaptor_Surface& surf) { 104 | return surf.GetType() == type; 105 | }); 106 | }; 107 | 108 | /** 109 | * Filter surfaces by a custom filter function 110 | */ 111 | std::vector OCCUtils::Surfaces::Filter(const std::vector& surfaces, 112 | const std::function& filt) { 113 | // Create output vector 114 | std::vector ret; 115 | ret.reserve(surfaces.size()); 116 | // Run algorithm 117 | std::copy_if(surfaces.begin(), surfaces.end(), std::back_inserter(ret), [&](const SurfaceInfo& surf) { 118 | return filt(surf.surface); 119 | }); 120 | return ret; 121 | } 122 | 123 | void OCCUtils::Surfaces::SurfaceTypeStats::Add(GeomAbs_SurfaceType typ, size_t cnt) { 124 | if(count.count(typ) == 0) { 125 | count[typ] = cnt; 126 | } else { 127 | count[typ] = count[typ] + cnt; 128 | } 129 | } 130 | 131 | size_t OCCUtils::Surfaces::SurfaceTypeStats::Count(GeomAbs_SurfaceType typ) { 132 | if(count.count(typ) == 0) { 133 | return 0; 134 | } else { 135 | return count[typ]; 136 | } 137 | } 138 | 139 | std::string OCCUtils::Surfaces::SurfaceTypeStats::Summary() { 140 | ostringstream ss; 141 | ss << "{\n"; 142 | if (Count(GeomAbs_Plane)) { 143 | ss << "\tGeomAbs_Plane = " << Count(GeomAbs_Plane) << '\n'; 144 | } 145 | if (Count(GeomAbs_Cylinder)) { 146 | ss << "\tGeomAbs_Cylinder = " << Count(GeomAbs_Cylinder) << '\n'; 147 | } 148 | if (Count(GeomAbs_Cone)) { 149 | ss << "\tGeomAbs_Cone = " << Count(GeomAbs_Cone) << '\n'; 150 | } 151 | if (Count(GeomAbs_Sphere)) { 152 | ss << "\tGeomAbs_Sphere = " << Count(GeomAbs_Sphere) << '\n'; 153 | } 154 | if (Count(GeomAbs_Torus)) { 155 | ss << "\tGeomAbs_Torus = " << Count(GeomAbs_Torus) << '\n'; 156 | } 157 | if (Count(GeomAbs_BezierSurface)) { 158 | ss << "\tGeomAbs_BezierSurface = " << Count(GeomAbs_BezierSurface) << '\n'; 159 | } 160 | if (Count(GeomAbs_BSplineSurface)) { 161 | ss << "\tGeomAbs_BSplineSurface = " << Count(GeomAbs_BSplineSurface) << '\n'; 162 | } 163 | if (Count(GeomAbs_SurfaceOfRevolution)) { 164 | ss << "\tGeomAbs_SurfaceOfRevolution = " << Count(GeomAbs_SurfaceOfRevolution) << '\n'; 165 | } 166 | if (Count(GeomAbs_SurfaceOfExtrusion)) { 167 | ss << "\tGeomAbs_SurfaceOfExtrusion = " << Count(GeomAbs_SurfaceOfExtrusion) << '\n'; 168 | } 169 | if (Count(GeomAbs_OffsetSurface)) { 170 | ss << "\tGeomAbs_OffsetSurface = " << Count(GeomAbs_OffsetSurface) << '\n'; 171 | } 172 | if (Count(GeomAbs_OtherSurface)) { 173 | ss << "\tGeomAbs_OtherSurface = " << Count(GeomAbs_OtherSurface) << '\n'; 174 | } 175 | ss << "}"; 176 | return ss.str(); 177 | } 178 | 179 | OCCUtils::Surfaces::SurfaceTypeStats OCCUtils::Surfaces::Statistics(const std::vector& surfaces) { 180 | SurfaceTypeStats stats; 181 | for(const auto& info : surfaces) { 182 | stats.Add(info.surface.GetType()); 183 | } 184 | return stats; 185 | } 186 | 187 | gp_Ax1 OCCUtils::Surface::Normal(const GeomAdaptor_Surface& surf, const gp_Pnt2d& uv, double precision) { 188 | return Normal(surf, uv.X(), uv.Y(), precision); 189 | } 190 | 191 | gp_Ax1 OCCUtils::Surface::Normal(const GeomAdaptor_Surface& surf, const gp_XY& uv, double precision) { 192 | return Normal(surf, uv.X(), uv.Y(), precision); 193 | } 194 | 195 | gp_Ax1 OCCUtils::Surface::Normal(const GeomAdaptor_Surface& surf, double u, double v, double precision) { 196 | GeomLProp_SLProps props(surf.Surface(), u, v, 1 /* max 1 derivation */, precision); 197 | return gp_Ax1(props.Value(), props.Normal()); 198 | } 199 | 200 | gp_Dir OCCUtils::Surface::NormalDirection(const GeomAdaptor_Surface& surf, double u, double v, double precision) { 201 | GeomLProp_SLProps props(surf.Surface(), u, v, 1 /* max 1 derivation */, precision); 202 | return props.Normal(); 203 | } 204 | 205 | /** 206 | * Sample the point on the given surface at the given U/V coordinates. 207 | * The point represents the 0th derivative. 208 | */ 209 | gp_Pnt OCCUtils::Surface::PointAt(const GeomAdaptor_Surface& surf, double u, double v) { 210 | return surf.Value(u, v); 211 | } 212 | 213 | gp_Pnt OCCUtils::Surface::PointAt(const GeomAdaptor_Surface& surf, const gp_Pnt2d& uv) { 214 | return surf.Value(uv.X(), uv.Y()); 215 | } 216 | 217 | gp_Pnt OCCUtils::Surface::PointAt(const GeomAdaptor_Surface& surf, const gp_XY& uv) { 218 | return surf.Value(uv.X(), uv.Y()); 219 | } 220 | 221 | vector OCCUtils::Surface::UniformUVSampleLocations(const GeomAdaptor_Surface& surf, size_t uSamples, size_t vSamples) { 222 | double u0 = surf.FirstUParameter(); 223 | double v0 = surf.FirstVParameter(); 224 | 225 | double uInterval = (surf.LastUParameter() - u0) / (uSamples - 1); // -1: include both end points 226 | double vInterval = (surf.LastVParameter() - v0) / (vSamples - 1); // -1: include both end points 227 | 228 | vector ret; 229 | ret.reserve(uSamples * vSamples); 230 | for (size_t u = 0; u < uSamples; u++) { 231 | for (size_t v = 0; v < vSamples; v++) { 232 | ret.emplace_back(u0 + uInterval * u, v0 + vInterval * v); 233 | } 234 | } 235 | return ret; 236 | } 237 | 238 | vector OCCUtils::Surface::UniformUVSampleLocationsWithin(const GeomAdaptor_Surface& surf, size_t uSamples, size_t vSamples) { 239 | double u0 = surf.FirstUParameter(); 240 | double v0 = surf.FirstVParameter(); 241 | 242 | double uInterval = (surf.LastUParameter() - u0) / (uSamples + 1); 243 | double vInterval = (surf.LastVParameter() - v0) / (vSamples + 1); 244 | 245 | vector ret; 246 | ret.reserve(uSamples * vSamples); 247 | for (size_t u = 1; u < uSamples; u++) { 248 | for (size_t v = 1; v < vSamples; v++) { 249 | ret.emplace_back(u0 + uInterval * u, v0 + vInterval * v); 250 | } 251 | } 252 | return ret; 253 | } 254 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/ShapeComponents.cxx: -------------------------------------------------------------------------------- 1 | #include "occutils/ShapeComponents.hxx" 2 | #include "occutils/Exceptions.hxx" 3 | #include 4 | #include 5 | #include 6 | 7 | std::vector OCCUtils::ShapeComponents::AllSolidsWithin(const TopoDS_Shape& shape) { 8 | std::vector solids; 9 | for (TopExp_Explorer solidExplorer(shape, TopAbs_SOLID); solidExplorer.More(); solidExplorer.Next()) { 10 | const auto &face = TopoDS::Solid(solidExplorer.Current()); 11 | if (face.IsNull()) { 12 | continue; 13 | } 14 | solids.push_back(face); 15 | } 16 | return solids; 17 | } 18 | 19 | std::vector OCCUtils::ShapeComponents::AllSolidsWithin(const std::vector& shapes) { 20 | std::vector solids; 21 | for(const auto& shape : shapes) { 22 | for (TopExp_Explorer solidExplorer(shape, TopAbs_SOLID); solidExplorer.More(); solidExplorer.Next()) { 23 | const auto &face = TopoDS::Solid(solidExplorer.Current()); 24 | if (face.IsNull()) { 25 | continue; 26 | } 27 | solids.push_back(face); 28 | } 29 | } 30 | return solids; 31 | } 32 | 33 | std::vector OCCUtils::ShapeComponents::AllFacesWithin(const TopoDS_Shape& shape) { 34 | std::vector faces; 35 | for (TopExp_Explorer faceExplorer(shape, TopAbs_FACE); faceExplorer.More(); faceExplorer.Next()) { 36 | const auto &face = TopoDS::Face(faceExplorer.Current()); 37 | if (face.IsNull()) { 38 | continue; 39 | } 40 | faces.push_back(face); 41 | } 42 | return faces; 43 | } 44 | 45 | std::vector OCCUtils::ShapeComponents::AllFacesWithin(const std::vector& shapes) { 46 | std::vector faces; 47 | for(const auto& shape : shapes) { 48 | for (TopExp_Explorer faceExplorer(shape, TopAbs_FACE); faceExplorer.More(); faceExplorer.Next()) { 49 | const auto &face = TopoDS::Face(faceExplorer.Current()); 50 | if (face.IsNull()) { 51 | continue; 52 | } 53 | faces.push_back(face); 54 | } 55 | } 56 | return faces; 57 | } 58 | 59 | std::vector OCCUtils::ShapeComponents::AllEdgesWithin(const TopoDS_Shape& shape) { 60 | std::vector edges; 61 | for (TopExp_Explorer edgeExplorer(shape, TopAbs_EDGE); edgeExplorer.More(); edgeExplorer.Next()) { 62 | const auto &edge = TopoDS::Edge(edgeExplorer.Current()); 63 | if (edge.IsNull()) { 64 | continue; 65 | } 66 | edges.push_back(edge); 67 | } 68 | return edges; 69 | } 70 | 71 | std::vector OCCUtils::ShapeComponents::AllEdgesWithin(const std::vector& shapes) { 72 | 73 | std::vector edges; 74 | for(const auto& shape : shapes) { 75 | for (TopExp_Explorer edgeExplorer(shape, TopAbs_EDGE); edgeExplorer.More(); edgeExplorer.Next()) { 76 | const auto &edge = TopoDS::Edge(edgeExplorer.Current()); 77 | if (edge.IsNull()) { 78 | continue; 79 | } 80 | edges.push_back(edge); 81 | } 82 | } 83 | return edges; 84 | } 85 | 86 | std::vector OCCUtils::ShapeComponents::AllEdgesWithin(const std::vector& shapes) { 87 | std::vector edges; 88 | for(const auto& shape : shapes) { 89 | for (TopExp_Explorer edgeExplorer(shape, TopAbs_EDGE); edgeExplorer.More(); edgeExplorer.Next()) { 90 | const auto &edge = TopoDS::Edge(edgeExplorer.Current()); 91 | if (edge.IsNull()) { 92 | continue; 93 | } 94 | edges.push_back(edge); 95 | } 96 | } 97 | return edges; 98 | } 99 | 100 | std::vector OCCUtils::ShapeComponents::AllWiresWithin(const TopoDS_Shape& shape) { 101 | std::vector wires; 102 | for (TopExp_Explorer wireExplorer(shape, TopAbs_WIRE); wireExplorer.More(); wireExplorer.Next()) { 103 | const auto &wire = TopoDS::Wire(wireExplorer.Current()); 104 | if (wire.IsNull()) { 105 | continue; 106 | } 107 | wires.push_back(wire); 108 | } 109 | return wires; 110 | } 111 | 112 | std::vector OCCUtils::ShapeComponents::AllWiresWithin(const std::vector& shapes) { 113 | std::vector wires; 114 | for(const auto& shape : shapes) { 115 | for (TopExp_Explorer wireExplorer(shape, TopAbs_WIRE); wireExplorer.More(); wireExplorer.Next()) { 116 | const auto &wire = TopoDS::Wire(wireExplorer.Current()); 117 | if (wire.IsNull()) { 118 | continue; 119 | } 120 | wires.push_back(wire); 121 | } 122 | } 123 | return wires; 124 | } 125 | 126 | std::vector OCCUtils::ShapeComponents::AllVerticesWithin(const TopoDS_Shape& shape) { 127 | std::vector wires; 128 | for (TopExp_Explorer vertexExplorer(shape, TopAbs_VERTEX); vertexExplorer.More(); vertexExplorer.Next()) { 129 | const auto &vertex = TopoDS::Vertex(vertexExplorer.Current()); 130 | if (vertex.IsNull()) { 131 | continue; 132 | } 133 | wires.push_back(vertex); 134 | } 135 | return wires; 136 | } 137 | 138 | std::vector OCCUtils::ShapeComponents::AllVerticesWithin(const std::vector& shapes) { 139 | std::vector wires; 140 | for(const auto& shape : shapes) { 141 | for (TopExp_Explorer vertexExplorer(shape, TopAbs_VERTEX); vertexExplorer.More(); vertexExplorer.Next()) { 142 | const auto &vertex = TopoDS::Vertex(vertexExplorer.Current()); 143 | if (vertex.IsNull()) { 144 | continue; 145 | } 146 | wires.push_back(vertex); 147 | } 148 | } 149 | return wires; 150 | 151 | } 152 | 153 | std::vector OCCUtils::ShapeComponents::AllVertexCoordinatesWithin(const TopoDS_Shape& shape) { 154 | std::vector vertices; 155 | for (TopExp_Explorer vertexExplorer(shape, TopAbs_VERTEX); vertexExplorer.More(); vertexExplorer.Next()) { 156 | const auto &vertex = TopoDS::Vertex(vertexExplorer.Current()); 157 | if (vertex.IsNull()) { 158 | continue; 159 | } 160 | vertices.push_back(BRep_Tool::Pnt(vertex)); 161 | } 162 | return vertices; 163 | } 164 | 165 | std::vector OCCUtils::ShapeComponents::AllVertexCoordinatesWithin(const std::vector& shapes) { 166 | std::vector vertices; 167 | for(const auto& shape : shapes) { 168 | for (TopExp_Explorer vertexExplorer(shape, TopAbs_VERTEX); vertexExplorer.More(); vertexExplorer.Next()) { 169 | const auto &vertex = TopoDS::Vertex(vertexExplorer.Current()); 170 | if (vertex.IsNull()) { 171 | continue; 172 | } 173 | vertices.push_back(BRep_Tool::Pnt(vertex)); 174 | } 175 | } 176 | return vertices; 177 | 178 | } 179 | 180 | std::optional OCCUtils::ShapeComponents::TryGetSingleSolid (const TopoDS_Shape& shape, bool firstOfMultipleOK) { 181 | // Is shape itself a solid? 182 | if(shape.ShapeType() == TopAbs_SOLID) { 183 | return TopoDS::Solid(shape); 184 | } 185 | // Else, expect there to be ONE sub-solid 186 | auto solids = ShapeComponents::AllSolidsWithin(shape); 187 | if(solids.empty()) { 188 | return std::nullopt; 189 | } 190 | if(solids.size() > 1 && !firstOfMultipleOK) { 191 | return std::nullopt; 192 | } 193 | return solids[0]; 194 | } 195 | 196 | std::optional OCCUtils::ShapeComponents::TryGetSingleFace (const TopoDS_Shape& shape, bool firstOfMultipleOK) { 197 | // Is shape itself a solid? 198 | if(shape.ShapeType() == TopAbs_FACE) { 199 | return TopoDS::Face(shape); 200 | } 201 | // Else, expect there to be ONE sub-face 202 | auto faces = ShapeComponents::AllFacesWithin(shape); 203 | if(faces.empty()) { 204 | return std::nullopt; 205 | } 206 | if(faces.size() > 1 && !firstOfMultipleOK) { 207 | return std::nullopt; 208 | } 209 | return faces[0]; 210 | } 211 | 212 | std::optional OCCUtils::ShapeComponents::TryGetSingleEdge (const TopoDS_Shape& shape, bool firstOfMultipleOK) { 213 | // Is shape itself an edge? 214 | if(shape.ShapeType() == TopAbs_EDGE) { 215 | return TopoDS::Edge(shape); 216 | } 217 | // Else, expect there to be ONE sub-edge 218 | auto edges = ShapeComponents::AllEdgesWithin(shape); 219 | if(edges.empty()) { 220 | return std::nullopt; 221 | } 222 | if(edges.size() > 1 && !firstOfMultipleOK) { 223 | return std::nullopt; 224 | } 225 | return edges[0]; 226 | } 227 | 228 | std::optional OCCUtils::ShapeComponents::TryGetSingleWire (const TopoDS_Shape& shape, bool firstOfMultipleOK) { 229 | // Is shape itself a solid? 230 | if(shape.ShapeType() == TopAbs_WIRE) { 231 | return TopoDS::Wire(shape); 232 | } 233 | // Else, expect there to be ONE sub-wire 234 | auto wires = ShapeComponents::AllWiresWithin(shape); 235 | if(wires.empty()) { 236 | return std::nullopt; 237 | } 238 | if(wires.size() > 1 && !firstOfMultipleOK) { 239 | return std::nullopt; 240 | } 241 | return wires[0]; 242 | } 243 | 244 | std::optional OCCUtils::ShapeComponents::TryGetSingleVertex (const TopoDS_Shape& shape, bool firstOfMultipleOK) { 245 | // Is shape itself a solid? 246 | if(shape.ShapeType() == TopAbs_VERTEX) { 247 | return TopoDS::Vertex(shape); 248 | } 249 | // Else, expect there to be ONE sub-vertex 250 | auto vertices = ShapeComponents::AllVerticesWithin(shape); 251 | if(vertices.empty()) { 252 | return std::nullopt; 253 | } 254 | if(vertices.size() > 1 && !firstOfMultipleOK) { 255 | return std::nullopt; 256 | } 257 | return vertices[0]; 258 | } 259 | 260 | TopoDS_Solid OCCUtils::ShapeComponents::GetSingleSolid (const TopoDS_Shape& shape, bool firstOfMultipleOK) { 261 | auto opt = TryGetSingleSolid(shape, firstOfMultipleOK); 262 | if(!opt.has_value()) { 263 | throw new OCCTopologyCountMismatchException("Shape is not a solid and does not contain a single solid"); 264 | } 265 | return opt.value(); 266 | } 267 | 268 | TopoDS_Face OCCUtils::ShapeComponents::GetSingleFace (const TopoDS_Shape& shape, bool firstOfMultipleOK) { 269 | auto opt = TryGetSingleFace(shape, firstOfMultipleOK); 270 | if(!opt.has_value()) { 271 | throw new OCCTopologyCountMismatchException("Shape is not a face and does not contain a single face"); 272 | } 273 | return opt.value(); 274 | } 275 | 276 | TopoDS_Edge OCCUtils::ShapeComponents::GetSingleEdge (const TopoDS_Shape& shape, bool firstOfMultipleOK) { 277 | auto opt = TryGetSingleEdge(shape, firstOfMultipleOK); 278 | if(!opt.has_value()) { 279 | throw new OCCTopologyCountMismatchException("Shape is not a edge and does not contain a single edge"); 280 | } 281 | return opt.value(); 282 | } 283 | 284 | TopoDS_Wire OCCUtils::ShapeComponents::GetSingleWire (const TopoDS_Shape& shape, bool firstOfMultipleOK) { 285 | auto opt = TryGetSingleWire(shape, firstOfMultipleOK); 286 | if(!opt.has_value()) { 287 | throw new OCCTopologyCountMismatchException("Shape is not a wire and does not contain a single wire"); 288 | } 289 | return opt.value(); 290 | } 291 | 292 | TopoDS_Vertex OCCUtils::ShapeComponents::GetSingleVertex (const TopoDS_Shape& shape, bool firstOfMultipleOK) { 293 | auto opt = TryGetSingleVertex(shape, firstOfMultipleOK); 294 | if(!opt.has_value()) { 295 | throw new OCCTopologyCountMismatchException("Shape is not a vertex and does not contain a single vertex"); 296 | } 297 | return opt.value(); 298 | } 299 | 300 | size_t OCCUtils::ShapeComponents::CountX(const TopoDS_Shape& shape, TopAbs_ShapeEnum type) { 301 | size_t cnt = 0; 302 | for (TopExp_Explorer xExplorer(shape, type); xExplorer.More(); xExplorer.Next()) { 303 | cnt += xExplorer.Current().ShapeType() == type ? 1 : 0; 304 | } 305 | return cnt; 306 | } 307 | 308 | size_t OCCUtils::ShapeComponents::CountX(const std::vector& shapes, TopAbs_ShapeEnum type) { 309 | size_t cnt = 0; 310 | for(const auto& shape: shapes) { 311 | cnt += CountX(shape, type); 312 | } 313 | return cnt; 314 | } --------------------------------------------------------------------------------