Mon, 08/19/2024 - 14:43
Hello all,
I'm trying to write a step to csg decomposition algorithm where I iterate over the faces in a solid and split the solid on the surface of that face but I'm not currently have any luck using the boolean operations. The boolean operations only return the original solid without any split. See image below or the desired results generated in SpaceClaim.
I've found a number of posts which suggest using half spaces and boolean operations but I can't seem to get this approach to work:
- https://dev.opencascade.org/content/how-cut-solid-face-two-sub-solids-us...
- https://dev.opencascade.org//content/cut-solid-two-solids-face
The process I'm trying is:
- Load geometry from a step file
- Iterate over solids in structure tree
- Iterate over faces in a given solid
- Create an infinite half space using one side of the face
- Use the BRepAlgoAPI_Cut and BRepAlgoAPI_Common tools to produce solids either side of the faces surface.
- Export generated solids as a step file
The code compiles and runs, but when I load the generated step file into SpaceClaim it only show copies of the original solid without any splits. I've created a shape in the step file attached which should produce a split on any of the faces. The code was compiled using GCC11.3.0 on Rocky Linux 8.6, and using OpenCascade 7.8.0.
Please could someone look over my code and let me know what I'm doing wrong? Or, if there is a better approach to take, I'm all ears.
Thanks in advance for your time.
#include <iostream>
#include <XCAFApp_Application.hxx>
#include <XCAFPrs_DocumentExplorer.hxx>
#include <STEPCAFControl_Reader.hxx>
#include <STEPCAFControl_Writer.hxx>
#include <TDocStd_Document.hxx>
#include <TopExp_Explorer.hxx>
#include <TopoDS.hxx>
#include <TopoDS_Shape.hxx>
#include <TopoDS_Face.hxx>
#include <BRep_Builder.hxx>
#include <BRepPrimAPI_MakeHalfSpace.hxx>
#include <BRepAlgoAPI_Algo.hxx>
#include <BRepAlgoAPI_Cut.hxx>
#include <BRepAlgoAPI_Common.hxx>
#include <BOPAlgo_Splitter.hxx>
#include <TDataStd_Name.hxx>
int main(){
// load step into document
Handle(XCAFApp_Application) hApp = XCAFApp_Application::GetApplication();
Handle(TDocStd_Document) hDoc;
hApp->NewDocument(TCollection_ExtendedString("MDTV-XCAF"), hDoc);
STEPCAFControl_Reader reader;
// read step file and check loaded with error handling
if (reader.ReadFile("star_all_surfaces_cuttable.stp") == IFSelect_RetDone){
std::cout << "[OK] file loaded" << std::endl;
}
else{
std::cout << "[ERROR] file failed to load" << std::endl;
}
// transfer loaded into document
reader.Transfer(hDoc);
// set up some tools to use
BRep_Builder builder;
// set up root compound to store all compounds for output
TopoDS_Compound rootCompound;
builder.MakeCompound(rootCompound);
// iterate over nodes in document and get shapes with solids
for (XCAFPrs_DocumentExplorer aDocExp (hDoc, XCAFPrs_DocumentExplorerFlags_None); aDocExp.More(); aDocExp.Next()){
// access current node in iteration and get its name
const XCAFPrs_DocumentNode& aNode = aDocExp.Current();
TCollection_AsciiString aName (aDocExp.CurrentDepth() , '>');
// get the nodes position in document tree and print
Handle(TDataStd_Name) aNodeName;
if (aNode.RefLabel.FindAttribute (TDataStd_Name::GetID(), aNodeName) || aNode.Label.FindAttribute (TDataStd_Name::GetID(), aNodeName))
{
aName += aNodeName->Get();
}
std::cout << aName << " [id: " << aNode.Id << "]\n";
// get shape of current node
TopoDS_Shape shape = XCAFPrs_DocumentExplorer::FindShapeFromPathId(hDoc, aNode.Id);
// set up compound to hold split shapes
TopoDS_Compound compoundSplit;
builder.MakeCompound(compoundSplit);
TopoDS_Compound compoundCommon;
builder.MakeCompound(compoundCommon);
// check that node is a solid
if (shape.ShapeType() == TopAbs_SOLID){
// iterate over faces in solid
for (TopExp_Explorer exp(shape, TopAbs_FACE); exp.More(); exp.Next()){
// create half space using face
const TopoDS_Face& face = TopoDS::Face(exp.Current());
BRepPrimAPI_MakeHalfSpace halfSpacePos(face, gp_Pnt(100, 100, 100));
// cut half space from original solid
BRepAlgoAPI_Cut cutter(shape, halfSpacePos);
cutter.Build();
// find common between half space and original solid
BRepAlgoAPI_Common common(shape, halfSpacePos);
common.Build();
// access shape generated by cut
TopoDS_Shape shapeGeneratedCut;
shapeGeneratedCut = cutter.Shape();
// access shape generated by common
TopoDS_Shape shapeGeneratedCommon;
shapeGeneratedCommon = common.Shape();
// add cut and common shapes to compound
builder.Add(compoundSplit, shapeGeneratedCut);
builder.Add(compoundCommon, shapeGeneratedCommon);
}
// add all coumpounds to root shape for output
builder.Add(rootCompound, shape);
builder.Add(rootCompound, compoundSplit);
builder.Add(rootCompound, compoundCommon);
}
}
// set up document writer to save output
STEPControl_Writer writer;
writer.Transfer(rootCompound, STEPControl_AsIs);
IFSelect_ReturnStatus writeStatus = writer.Write("output.stp");
if (writeStatus == IFSelect_RetDone){
std::cout << "[OK] step output written" << std::endl;
}
else{
std::cout << "[ERROR] step output not written" << std::endl;
}
return 0;
}
cmake_minimum_required(VERSION 3.16)
# set the project name
project(Decompose)
# add include directories for header files
include_directories(include)
# find open cascade package
find_package(OpenCASCADE COMPONENTS FoundationClasses REQUIRED)
find_package(OpenCASCADE COMPONENTS ModelingAlgorithms REQUIRED)
find_package(OpenCASCADE COMPONENTS ModelingData REQUIRED)
find_package(OpenCASCADE COMPONENTS DataExchange REQUIRED)
include_directories(${OpenCASCADE_INCLUDE_DIR})
link_directories(${OpenCASCADE_LIBRARY_DIR})
MESSAGE("================================")
MESSAGE(${OpenCASCADE_LIBRARY_DIR})
add_executable(Decompose src/decompose.cpp)
target_link_libraries(${PROJECT_NAME} PRIVATE TKBO TKBin TKCAF TKCDF TKMath
TKernel TKGeomAlgo TKTopAlgo TKDESTEP
TKLCAF TKXCAF TKBRep TKPrim)
Thu, 09/05/2024 - 17:22
I've managed to get this working.
I think (not confident) the reason this failed was because I was trying to use a face bound by edges to create the half space, however I think I've managed to make a new face using the surface within the face of the solid and use this to create the half space.
Is the face I create unbound by edges and therefore allows a halfspace on one side of an infinite surface, rather than some kind of bound half space?
If anyone can offer advice on better methods for carrying out this operations, I'm all ears!
Fri, 09/06/2024 - 17:27
The half space itself is valid in both cases. The issue is with the booleans. In order for the the booleans to work with a halfspace, the halfspaces DEFINED geometry has to extend beyond the target of the boolean operation. So what you are doing by creating an infinite copy, is the right approach.