Splitting an solid using and surface of a face in the solid

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:

The process I'm trying is:

  1. Load geometry from a step file
  2. Iterate over solids in structure tree
  3. Iterate over faces in a given solid
  4. Create an infinite half space using one side of the face
  5. Use the BRepAlgoAPI_Cut and BRepAlgoAPI_Common tools to produce solids either side of the faces surface.
  6. 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)
Green Yoshi's picture

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?

Handle(Geom_Surface) surface = BRep_Tool::Surface(face);
Standard_Real realMakeFaceTol = 1e-4;
BRepBuilderAPI_MakeFace *makeFaceFromSurface = new BRepBuilderAPI_MakeFace(surface, realMakeFaceTol);
TopoDS_Face unboundFace = makeFaceFromSurface->Face();

// create unbound surface from face
BRepPrimAPI_MakeHalfSpace *makeHalfSpacePos = new BRepPrimAPI_MakeHalfSpace(unboundFace, gp_Pnt(100, 0, 0));
TopoDS_Solid halfSpacePos = makeHalfSpacePos->Solid();

If anyone can offer advice on better methods for carrying out this operations, I'm all ears!

Thomas Anderson's picture

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.