From: emv Date: Fri, 7 Sep 2018 12:24:49 +0000 (+0300) Subject: 0030145: Modeling Algorithms - Boolean Operations on open solids X-Git-Url: http://git.dev.opencascade.org/gitweb/?a=commitdiff_plain;h=d57d5e4eaee704d9926a28c2123ae7ce90e37f4a;p=occt-copy.git 0030145: Modeling Algorithms - Boolean Operations on open solids Provide possibility to perform Boolean operations on open solids. Implementation of the new method *BOPAlgo_Builder::BuildBOP* performing the construction of the result shape for the given type of Boolean operation. This approach does not rely on the splits of solid to be correct and looks for the faces with necessary state relatively opposite solids to build the result solid. The call to this method is performed from BOP algorithm in case there were open solids in the arguments. Implementation of the draw command *buildbop* performing a call to the method above. Test cases for the issue. --- diff --git a/dox/user_guides/boolean_operations/boolean_operations.md b/dox/user_guides/boolean_operations/boolean_operations.md index eef0ec6083..fa3b33c39f 100644 --- a/dox/user_guides/boolean_operations/boolean_operations.md +++ b/dox/user_guides/boolean_operations/boolean_operations.md @@ -2419,6 +2419,21 @@ bcremoveint res These examples may last forever. To define any new operation, it is just necessary to define, which Cells should be taken and which should be avoided. +@section occt_algorithms_opensolid Boolean operations on open solids + +The Boolean operations on open solids are tricky enough that the standard approach ob Boolean operations for building the result, based on the splits of solids does not work. +It happens because the algorithm for splitting solids (*BOPAlgo_BuilderSolid*) always tries to create the closed loops (shells) and make solids from them. But if the input solid is not closed, what can be expected from its splits? +For performing Boolean Operations on open solids the new approach has been implemented which does not rely on the splits of the solids to be correct, but tries to select the splits of faces, which are necessary for the given type of operation. +The point here is that the type of Boolean operation clearly defines the states for the faces to be taken into result: +- For **COMMON** operation all the faces from the arguments located inside any solid of the opposite group must be taken; +- For **FUSE** operation all the faces from the arguments located outside of all solids of the opposite group must be taken; +- For **CUT** operation all the faces from the Objects located outside of all solids of the Tools and all faces from the Tools located inside any solid of the Objects must be taken; +- For **CUT21** operation all the faces from the Objects located inside any solid of the Tools and all faces from the Tools located outside of all solids of the Objects must be taken. +From the selected faces the result solids are built. Please note, that the result may contain as normal (closed) solids as the open ones. + +Even with this approach, the correct result of Boolean operation on open solids cannot be always guaranteed. +The problem is in classification of the faces relatively such solids which strongly depends on the point on the face chosen for classification. + @section occt_algorithms_10 Algorithm Limitations The chapter describes the problems that are considered as Algorithm limitations. In most cases an Algorithm failure is caused by a combination of various factors, such as self-interfered arguments, inappropriate or ungrounded values of the argument tolerances, adverse mutual position of the arguments, tangency, etc. diff --git a/src/BOPAlgo/BOPAlgo.msg b/src/BOPAlgo/BOPAlgo.msg index fa9a69f63a..15295adadc 100644 --- a/src/BOPAlgo/BOPAlgo.msg +++ b/src/BOPAlgo/BOPAlgo.msg @@ -97,3 +97,6 @@ Warning: Some of the edges passed to the Face Builder algorithm have not been cl .BOPAlgo_AlertUnableToOrientTheShape Warning: Unable to orient the shape correctly + +.BOPAlgo_AlertUnknownShape +Error: Shape is unknown for operation diff --git a/src/BOPAlgo/BOPAlgo_Alerts.hxx b/src/BOPAlgo/BOPAlgo_Alerts.hxx index 894f978ab0..8274528c15 100644 --- a/src/BOPAlgo/BOPAlgo_Alerts.hxx +++ b/src/BOPAlgo/BOPAlgo_Alerts.hxx @@ -111,4 +111,7 @@ DEFINE_ALERT_WITH_SHAPE(BOPAlgo_AlertFaceBuilderUnusedEdges) //! Unable to orient the shape correctly DEFINE_ALERT_WITH_SHAPE(BOPAlgo_AlertUnableToOrientTheShape) +//! Shape is unknown for operation +DEFINE_ALERT_WITH_SHAPE(BOPAlgo_AlertUnknownShape) + #endif // _BOPAlgo_Alerts_HeaderFile diff --git a/src/BOPAlgo/BOPAlgo_BOP.cxx b/src/BOPAlgo/BOPAlgo_BOP.cxx index 8a134caa1c..bffec873e4 100644 --- a/src/BOPAlgo/BOPAlgo_BOP.cxx +++ b/src/BOPAlgo/BOPAlgo_BOP.cxx @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -772,6 +773,32 @@ void BOPAlgo_BOP::BuildRC() //======================================================================= void BOPAlgo_BOP::BuildShape() { + if (myDims[0] == 3 && myDims[1] == 3) + { + // For the Boolean operation on solids we need to check first + // if we are dealing with closed solids, because for open solids + // we cannot expect the BuilderSolid algorithm to produce good + // splits for them and have to try the alternative approach for + // building the result shape. + // This approach is not used by default as it will loose the + // modification history for solids, because the result solid + // will be built from scratch using the splits of faces. + Standard_Boolean hasNotClosedSolids = CheckArgsForOpenSolid(); + if (hasNotClosedSolids) + { + Handle(Message_Report) aReport = new Message_Report(); + BuildBOP(myArguments, myTools, myOperation, aReport); + if (aReport->GetAlerts(Message_Fail).IsEmpty()) + { + // Success. Merge the report into the main report. + myReport->Merge(aReport); + return; + } + } + } + + // Build the result using splits of arguments. + BuildRC(); // if ((myOperation == BOPAlgo_FUSE) && (myDims[0] == 3)) { @@ -1169,6 +1196,157 @@ void BOPAlgo_BOP::BuildSolid() // myShape = aResult; } + +//======================================================================= +//function : CheckArgsForOpenSolid +//purpose : +//======================================================================= +Standard_Boolean BOPAlgo_BOP::CheckArgsForOpenSolid() +{ + // Analyze the report to find if BuilderSolid has generated warnings + // for any of the solids and collect these solids to check if they are open. + TopTools_MapOfShape aFailedSolids; + { + const Message_ListOfAlert& aList = myReport->GetAlerts(Message_Warning); + for (Message_ListOfAlert::Iterator aIt(aList); aIt.More(); aIt.Next()) + { + const Handle(Standard_Type)& aType = aIt.Value()->DynamicType(); + if (aType != STANDARD_TYPE(BOPAlgo_AlertSolidBuilderUnusedFaces)) + continue; + + Handle(TopoDS_AlertWithShape) aShapeAlert = Handle(TopoDS_AlertWithShape)::DownCast(aIt.Value()); + if (!aShapeAlert.IsNull()) + { + const TopoDS_Shape& aWarnShape = aShapeAlert->GetShape(); + if (!aWarnShape.IsNull()) + { + TopExp_Explorer expS(aWarnShape, TopAbs_SOLID); + for (; expS.More(); expS.Next()) + aFailedSolids.Add(expS.Current()); + } + } + } + } + + // Iterate on all solids from the arguments and check if any + // of them are not closed. + // At the same time, collect all internal faces of the input solids + // to check if the splits of open solids did not acquire any new + // internal faces. + + const Standard_Integer aNbS = myDS->NbSourceShapes(); + for (Standard_Integer i = 0; i < aNbS; ++i) + { + const BOPDS_ShapeInfo& aSI = myDS->ShapeInfo(i); + if (aSI.ShapeType() != TopAbs_SOLID) + continue; + + const TopoDS_Shape& aSolid = aSI.Shape(); + + // Check that not INTERNAL faces create closed loops + TopTools_IndexedDataMapOfShapeListOfShape aMEF; + // Collect all splits of internal faces + TopTools_MapOfShape aMFInternal; + + for (TopoDS_Iterator itSh(aSolid); itSh.More(); itSh.Next()) + { + const TopoDS_Shape& aSh = itSh.Value(); + if (aSh.ShapeType() != TopAbs_SHELL) + continue; + + for (TopoDS_Iterator itF(aSh); itF.More(); itF.Next()) + { + const TopoDS_Shape& aF = itF.Value(); + if (aF.Orientation() == TopAbs_INTERNAL) + { + const TopTools_ListOfShape* pLFIm = myImages.Seek(aF); + if (pLFIm) + { + TopTools_ListOfShape::Iterator itLFIm(*pLFIm); + for (; itLFIm.More(); itLFIm.Next()) + aMFInternal.Add(itLFIm.Value()); + } + else + aMFInternal.Add(aF); + } + else + TopExp::MapShapesAndAncestors(aF, TopAbs_EDGE, TopAbs_FACE, aMEF); + } + } + + // Analyze the Edge-Face connection map on free edges + Standard_Boolean isClosed = Standard_True; + const Standard_Integer aNbE = aMEF.Extent(); + for (Standard_Integer j = 1; j <= aNbE && isClosed; ++j) + { + const TopoDS_Edge& aE = TopoDS::Edge(aMEF.FindKey(j)); + if (BRep_Tool::Degenerated(aE)) + // Skip degenerated edges + continue; + + isClosed = (aMEF(j).Extent() > 1); + if (!isClosed) + { + const TopoDS_Face& aF = TopoDS::Face(aMEF(j).First()); + isClosed = BRep_Tool::IsClosed(aE, aF); // Check for seam edges + if (!isClosed) + { + // Check if the edge is not internal in the face + TopExp_Explorer expE(aF, TopAbs_EDGE); + for (; expE.More(); expE.Next()) + { + if (expE.Current().IsSame(aE)) + { + isClosed = (expE.Current().Orientation() == TopAbs_INTERNAL); + break; + } + } + } + } + } + + if (isClosed) + continue; + + // Not closed solid is found + + if (aFailedSolids.Contains(aSolid)) + // Warning has been generated for this solid, return positive result right away. + return Standard_True; + + + // Check the splits not to acquire new INTERNAL faces + const TopTools_ListOfShape *pLSIm = myImages.Seek(aSolid); + if (!pLSIm) + continue; + + TopTools_ListOfShape::Iterator itLSIm(*pLSIm); + for (; itLSIm.More(); itLSIm.Next()) + { + const TopoDS_Shape& aSIm = itLSIm.Value(); + for (TopoDS_Iterator itSh(aSIm); itSh.More(); itSh.Next()) + { + const TopoDS_Shape& aSh = itSh.Value(); + if (aSh.ShapeType() != TopAbs_SHELL) + continue; + + for (TopoDS_Iterator itF(aSh); itF.More(); itF.Next()) + { + const TopoDS_Shape& aF = itF.Value(); + if (aF.Orientation() == TopAbs_INTERNAL) + { + if (!aMFInternal.Contains(aF)) + // New internal face is found + return Standard_True; + } + } + } + } + } + + return Standard_False; +} + //======================================================================= //function : TypeToExplore //purpose : diff --git a/src/BOPAlgo/BOPAlgo_BOP.hxx b/src/BOPAlgo/BOPAlgo_BOP.hxx index 8f18135c09..4598262348 100644 --- a/src/BOPAlgo/BOPAlgo_BOP.hxx +++ b/src/BOPAlgo/BOPAlgo_BOP.hxx @@ -107,6 +107,12 @@ protected: //! all shapes in one of the groups are empty shapes. Standard_EXPORT Standard_Boolean TreatEmptyShape(); + //! Checks if the arguments of Boolean Operation on solids + //! contain any open solids, for which the building of the splits + //! has failed. In case of positive check, run different procedure + //! for building the result shape. + Standard_EXPORT virtual Standard_Boolean CheckArgsForOpenSolid(); + protected: BOPAlgo_Operation myOperation; diff --git a/src/BOPAlgo/BOPAlgo_BOPAlgo_msg.pxx b/src/BOPAlgo/BOPAlgo_BOPAlgo_msg.pxx index f13a73f5c2..e00a643873 100644 --- a/src/BOPAlgo/BOPAlgo_BOPAlgo_msg.pxx +++ b/src/BOPAlgo/BOPAlgo_BOPAlgo_msg.pxx @@ -99,4 +99,7 @@ static const char BOPAlgo_BOPAlgo_msg[] = "Warning: Some of the edges passed to the Face Builder algorithm have not been classified and not used for faces creation\n" "\n" ".BOPAlgo_AlertUnableToOrientTheShape\n" - "Warning: Unable to orient the shape correctly\n"; + "Warning: Unable to orient the shape correctly\n" + "\n" + ".BOPAlgo_AlertUnknownShape\n" + "Error: Shape is unknown for operation\n"; diff --git a/src/BOPAlgo/BOPAlgo_Builder.cxx b/src/BOPAlgo/BOPAlgo_Builder.cxx index c986f6a9f8..57ed3427f1 100644 --- a/src/BOPAlgo/BOPAlgo_Builder.cxx +++ b/src/BOPAlgo/BOPAlgo_Builder.cxx @@ -17,22 +17,24 @@ #include -#include #include +#include +#include +#include +#include +#include #include #include #include #include #include +#include +#include +#include #include -#include - -#include -#include - -#include +#include #include -#include +#include //======================================================================= @@ -50,6 +52,7 @@ BOPAlgo_Builder::BOPAlgo_Builder() myImages(100, myAllocator), myShapesSD(100, myAllocator), myOrigins(100, myAllocator), + myInParts(100, myAllocator), myNonDestructive(Standard_False), myGlue(BOPAlgo_GlueOff), myCheckInverted(Standard_True) @@ -71,6 +74,7 @@ BOPAlgo_Builder::BOPAlgo_Builder myImages(100, myAllocator), myShapesSD(100, myAllocator), myOrigins(100, myAllocator), + myInParts(100, myAllocator), myNonDestructive(Standard_False), myGlue(BOPAlgo_GlueOff), myCheckInverted(Standard_True) @@ -101,6 +105,7 @@ void BOPAlgo_Builder::Clear() myImages.Clear(); myShapesSD.Clear(); myOrigins.Clear(); + myInParts.Clear(); } //======================================================================= //function : AddArgument @@ -376,3 +381,354 @@ void BOPAlgo_Builder::PostTreat() BOPTools_AlgoTools::CorrectTolerances(myShape, aMA, 0.05, myRunParallel); BOPTools_AlgoTools::CorrectShapeTolerances(myShape, aMA, myRunParallel); } + +//======================================================================= +//function : BuildBOP +//purpose : +//======================================================================= +void BOPAlgo_Builder::BuildBOP(const TopTools_ListOfShape& theObjects, + const TopAbs_State theObjState, + const TopTools_ListOfShape& theTools, + const TopAbs_State theToolsState, + Handle(Message_Report) theReport) +{ + if (HasErrors()) + return; + + // Report for the method + Handle(Message_Report) aReport = theReport.IsNull() ? myReport : theReport; + + if (myArguments.IsEmpty() || myShape.IsNull()) + { + aReport->AddAlert(Message_Fail, new BOPAlgo_AlertBuilderFailed()); + return; + } + + // Check the input data + if ((theObjState != TopAbs_IN && theObjState != TopAbs_OUT) || + (theToolsState != TopAbs_IN && theToolsState != TopAbs_OUT)) + { + aReport->AddAlert(Message_Fail, new BOPAlgo_AlertBOPNotSet()); + return; + } + + // Check input shapes + Standard_Boolean hasObjects = !theObjects.IsEmpty(); + Standard_Boolean hasTools = !theTools .IsEmpty(); + if (!hasObjects && !hasTools) + { + aReport->AddAlert(Message_Fail, new BOPAlgo_AlertTooFewArguments()); + return; + } + + // Check that all input solids are from the arguments + for (Standard_Integer i = 0; i < 2; ++i) + { + const TopTools_ListOfShape& aList = !i ? theObjects : theTools; + TopTools_ListOfShape::Iterator itLS(aList); + for (; itLS.More(); itLS.Next()) + { + const TopoDS_Shape& aS = itLS.Value(); + if (aS.ShapeType() != TopAbs_SOLID) + { + TopTools_ListOfShape aLS; + TopTools_MapOfShape aMFence; + BOPAlgo_Tools::TreatCompound(aS, aMFence, aLS); + + TopTools_ListOfShape::Iterator it(aLS); + for (; it.More(); it.Next()) + { + const TopoDS_Shape& aSx = it.Value(); + if (aSx.ShapeType() != TopAbs_SOLID && + aSx.ShapeType() != TopAbs_COMPSOLID) + { + aReport->AddAlert(Message_Fail, new BOPAlgo_AlertUnknownShape(aS)); + return; + } + } + } + + if (myDS->Index(itLS.Value()) < 0) + { + aReport->AddAlert(Message_Fail, new BOPAlgo_AlertUnknownShape(aS)); + return; + } + } + } + + // Classification of the faces relatively solids has been made + // on the stage of Solids splitting. All results are saved into + // myInParts map, which connects the solids with its IN faces from + // other arguments. All faces not contained in the list of IN faces + // will be considered as OUT. + + // Prepare the maps of splits of solids faces with orientations + TopTools_IndexedMapOfOrientedShape aMObjFacesOri, aMToolFacesOri; + // Prepare the maps of splits of solids faces + TopTools_IndexedMapOfShape aMObjFaces, aMToolFaces; + // Copy the list of IN faces of the solids into map + TopTools_MapOfShape anINObjects, anINTools; + + for (Standard_Integer i = 0; i < 2; ++i) + { + const TopTools_ListOfShape& aList = !i ? theObjects : theTools; + TopTools_IndexedMapOfOrientedShape& aMapOri = !i ? aMObjFacesOri : aMToolFacesOri; + TopTools_IndexedMapOfShape& aMap = !i ? aMObjFaces : aMToolFaces; + TopTools_ListOfShape::Iterator itLS(aList); + for (; itLS.More(); itLS.Next()) + { + const TopoDS_Shape& aShape = itLS.Value(); + TopExp_Explorer expS(aShape, TopAbs_SOLID); + for (; expS.More(); expS.Next()) + { + const TopoDS_Shape& aS = expS.Current(); + TopExp_Explorer expF(aS, TopAbs_FACE); + for (; expF.More(); expF.Next()) + { + const TopoDS_Shape& aF = expF.Current(); + const TopTools_ListOfShape* pLFIm = myImages.Seek(aF); + if (pLFIm) + { + TopTools_ListOfShape::Iterator itLFIm(*pLFIm); + for (; itLFIm.More(); itLFIm.Next()) + { + TopoDS_Face aFIm = TopoDS::Face(itLFIm.Value()); + if (BOPTools_AlgoTools::IsSplitToReverse(aFIm, aF, myContext)) + aFIm.Reverse(); + aMapOri.Add(aFIm); + aMap.Add(aFIm); + } + } + else + { + aMapOri.Add(aF); + aMap.Add(aF); + } + } + + // Copy the list of IN faces into a map + const TopTools_ListOfShape* pLFIN = myInParts.Seek(aS); + if (pLFIN) + { + TopTools_MapOfShape& anINMap = !i ? anINObjects : anINTools; + TopTools_ListOfShape::Iterator itLFIn(*pLFIN); + for (; itLFIn.More(); itLFIn.Next()) + anINMap.Add(itLFIn.Value()); + } + } + } + } + + // Now we need to select all faces which will participate in + // building of the resulting solids. The final set of faces + // depends on the given states for the groups. + Standard_Boolean isObjectsIN = (theObjState == TopAbs_IN), + isToolsIN = (theToolsState == TopAbs_IN); + + // Shortcuts + Standard_Boolean bAvoidIN = (!isObjectsIN && !isToolsIN), // avoid all in faces + bAvoidINforBoth = (isObjectsIN != isToolsIN); // avoid faces IN for both groups + + // Choose which SD faces are needed to be taken - equally or differently oriented faces + Standard_Boolean isSameOriNeeded = (theObjState == theToolsState); + // Resulting faces + TopTools_IndexedMapOfOrientedShape aMResFacesOri; + TopTools_MapOfShape aMResFacesFence; + // Fence map + TopTools_MapOfShape aMFence, aMFToAvoid; + // Oriented fence map + TopTools_MapOfOrientedShape aMFenceOri; + + for (Standard_Integer i = 0; i < 2; ++i) + { + const TopTools_IndexedMapOfOrientedShape& aMap = !i ? aMObjFacesOri : aMToolFacesOri; + const TopTools_IndexedMapOfShape& anOppositeMap = !i ? aMToolFaces : aMObjFaces; + const TopTools_MapOfShape& anINMap = !i ? anINObjects : anINTools; + const TopTools_MapOfShape& anOppositeINMap = !i ? anINTools : anINObjects; + const Standard_Boolean bTakeIN = !i ? isObjectsIN : isToolsIN; + + const Standard_Integer aNbF = aMap.Extent(); + for (Standard_Integer j = 1; j <= aNbF; ++j) + { + TopoDS_Shape aFIm = aMap(j); + + Standard_Boolean isIN = anINMap.Contains(aFIm); + Standard_Boolean isINOpposite = anOppositeINMap.Contains(aFIm); + + // Filtering for FUSE - avoid any IN faces + if (bAvoidIN && (isIN || isINOpposite)) + continue; + + // Filtering for CUT - avoid faces IN for both groups + if (bAvoidINforBoth && isIN && isINOpposite) + continue; + + // Treatment of SD faces + if (!aMFence.Add(aFIm)) + { + if (!anOppositeMap.Contains(aFIm)) + { + // The face belongs to only one group + if (bTakeIN != isSameOriNeeded) + aMFToAvoid.Add(aFIm); + } + else + { + // The face belongs to both groups. + // Using its orientation decide if it is needed in the result or not. + Standard_Boolean isSameOri = !aMFenceOri.Add(aFIm); + if (isSameOriNeeded == isSameOri) + { + // Take the shape without classification + if (aMResFacesFence.Add(aFIm)) + aMResFacesOri.Add(aFIm); + } + else + // Remove the face + aMFToAvoid.Add(aFIm); + + continue; + } + } + if (!aMFenceOri.Add(aFIm)) + continue; + + if (bTakeIN == isINOpposite) + { + if (isIN) + { + aMResFacesOri.Add(aFIm); + aMResFacesOri.Add(aFIm.Reversed()); + } + else if (bTakeIN && !isSameOriNeeded) + aMResFacesOri.Add(aFIm.Reversed()); + else + aMResFacesOri.Add(aFIm); + aMResFacesFence.Add(aFIm); + } + } + } + + // Remove the faces which has to be avoided + TopTools_ListOfShape aResFaces; + const Standard_Integer aNbRF = aMResFacesOri.Extent(); + for (Standard_Integer i = 1; i <= aNbRF; ++i) + { + const TopoDS_Shape& aRF = aMResFacesOri(i); + if (!aMFToAvoid.Contains(aRF)) + aResFaces.Append(aRF); + } + + BRep_Builder aBB; + + // Try to build closed solids from the faces + BOPAlgo_BuilderSolid aBS; + aBS.SetShapes(aResFaces); + aBS.SetRunParallel(myRunParallel); + aBS.SetContext(myContext); + aBS.SetFuzzyValue(myFuzzyValue); + aBS.SetProgressIndicator(myProgressIndicator); + aBS.Perform(); + + // Resulting solids + TopTools_ListOfShape aResSolids; + + aMFence.Clear(); + if (!aBS.HasErrors()) + { + // If any, add solids into resulting compound + TopTools_ListIteratorOfListOfShape itA(aBS.Areas()); + for (; itA.More(); itA.Next()) + { + const TopoDS_Shape& aSolid = itA.Value(); + // The solid must contain at least one face + // from either of objects or tools + TopExp_Explorer expF(aSolid, TopAbs_FACE); + for (; expF.More(); expF.Next()) + { + const TopoDS_Shape& aF = expF.Current(); + if (aMObjFacesOri.Contains(aF) || aMToolFacesOri.Contains(aF)) + break; + } + if (expF.More()) + { + aResSolids.Append(aSolid); + TopExp::MapShapes(aSolid, aMFence); + } + } + } + + // Collect unused faces + TopoDS_Compound anUnUsedFaces; + aBB.MakeCompound(anUnUsedFaces); + + TopTools_ListOfShape::Iterator itLF(aResFaces); + for (; itLF.More(); itLF.Next()) + { + if (aMFence.Add(itLF.Value())) + aBB.Add(anUnUsedFaces, itLF.Value()); + } + + // Build blocks from the unused faces + TopTools_ListOfShape aLCB; + BOPTools_AlgoTools::MakeConnexityBlocks(anUnUsedFaces, TopAbs_EDGE, TopAbs_FACE, aLCB); + + // Build solid from each block + TopTools_ListIteratorOfListOfShape itCB(aLCB); + for (; itCB.More(); itCB.Next()) + { + const TopoDS_Shape& aCB = itCB.Value(); + TopoDS_Shell aShell; + aBB.MakeShell(aShell); + // Add faces of the block to the shell + TopExp_Explorer anExpF(aCB, TopAbs_FACE); + for (; anExpF.More(); anExpF.Next()) + aBB.Add(aShell, TopoDS::Face(anExpF.Current())); + + BOPTools_AlgoTools::OrientFacesOnShell(aShell); + // Make solid out of the shell + TopoDS_Solid aSolid; + aBB.MakeSolid(aSolid); + aBB.Add(aSolid, aShell); + // Add new solid to result + aResSolids.Append(aSolid); + } + + if (!bAvoidIN) + { + // Fill solids with internal parts coming with the solids + TopTools_ListOfShape anInParts; + for (Standard_Integer i = 0; i < 2; ++i) + { + const TopTools_ListOfShape& aList = !i ? theObjects : theTools; + TopTools_ListOfShape::Iterator itLS(aList); + for (; itLS.More(); itLS.Next()) + { + TopExp_Explorer expS(itLS.Value(), TopAbs_SOLID); + for (; expS.More(); expS.Next()) + { + const TopoDS_Shape& aS = expS.Current(); // Solid + for (TopoDS_Iterator it(aS); it.More(); it.Next()) + { + const TopoDS_Shape& aSx = it.Value(); + if (aSx.ShapeType() != TopAbs_SHELL) + anInParts.Append(aSx); + } + } + } + } + + BOPAlgo_Tools::FillInternals(aResSolids, anInParts, myImages, myContext); + } + + // Combine solids into compound + TopoDS_Shape aResult; + aBB.MakeCompound(TopoDS::Compound(aResult)); + + TopTools_ListOfShape::Iterator itLS(aResSolids); + for (; itLS.More(); itLS.Next()) + aBB.Add(aResult, itLS.Value()); + + myShape = aResult; + PrepareHistory(); +} diff --git a/src/BOPAlgo/BOPAlgo_Builder.hxx b/src/BOPAlgo/BOPAlgo_Builder.hxx index c9bdc77bae..fb121ec7b0 100644 --- a/src/BOPAlgo/BOPAlgo_Builder.hxx +++ b/src/BOPAlgo/BOPAlgo_Builder.hxx @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -38,6 +39,7 @@ class IntTools_Context; class TopoDS_Shape; class BOPAlgo_PaveFiller; +class TopoDS_Solid; //! //! The class is a General Fuse algorithm - base algorithm for the @@ -174,6 +176,114 @@ public: //! @name Performing the operation Standard_EXPORT virtual void PerformWithFiller (const BOPAlgo_PaveFiller& theFiller); +public: //! @name BOPs on open solids + + //! Builds the result shape according to the given states for the objects + //! and tools. These states can be unambiguously converted into the Boolean operation type. + //! Thus, it performs the Boolean operation on the given groups of shapes. + //! + //! The result is built basing on the result of Builder operation (GF or any other). + //! The only condition for the Builder is that the splits of faces should be created + //! and classified relatively solids. + //! + //! The method uses classification approach for choosing the faces which will + //! participate in building the result shape: + //! - All faces from each group having the given state for the opposite group + //! will be taken into result. + //! + //! Such approach shows better results (in comparison with BOPAlgo_BuilderSolid approach) + //! when working with open solids. However, the result may not be always + //! correct on such data (at least, not as expected) as the correct classification + //! of the faces relatively open solids is not always possible and may vary + //! depending on the chosen classification point on the face. + //! + //! History is not created for the solids in this method. + //! + //! To avoid pollution of the report of Builder algorithm, there is a possibility to pass + //! the different report to collect the alerts of the method only. But, if the new report + //! is not given, the Builder report will be used. + //! So, even if Builder passed without any errors, but some error has been stored into its report + //! in this method, for the following calls the Builder report must be cleared. + //! + //! The method may set the following errors: + //! - BOPAlgo_AlertBuilderFailed - Building operation has not been performed yet or failed; + //! - BOPAlgo_AlertBOPNotSet - invalid BOP type is given (COMMON/FUSE/CUT/CUT21 are supported); + //! - BOPAlgo_AlertTooFewArguments - arguments are not given; + //! - BOPAlgo_AlertUnknownShape - the shape is unknown for the operation. + //! + //! Parameters: + //! @param theObjects - The group of Objects for BOP; + //! @param theObjState - State for objects faces to pass into result; + //! @param theTools - The group of Tools for BOP; + //! @param theObjState - State for tools faces to pass into result; + //! @param theReport - The alternative report to avoid pollution of the main one. + Standard_EXPORT virtual void BuildBOP(const TopTools_ListOfShape& theObjects, + const TopAbs_State theObjState, + const TopTools_ListOfShape& theTools, + const TopAbs_State theToolsState, + Handle(Message_Report) theReport = NULL); + + //! Builds the result of Boolean operation of given type + //! basing on the result of Builder operation (GF or any other). + //! + //! The method converts the given type of operation into the states + //! for the objects and tools required for their face to pass into result + //! and performs the call to the same method, but with states instead + //! of operation type. + //! + //! The conversion looks as follows: + //! - COMMON is built from the faces of objects located IN any of the tools + //! and vice versa. + //! - FUSE is built from the faces OUT of all given shapes; + //! - CUT is built from the faces of the objects OUT of the tools and + //! faces of the tools located IN solids of the objects. + //! + //! @param theObjects - The group of Objects for BOP; + //! @param theTools - The group of Tools for BOP; + //! @param theOperation - The BOP type; + //! @param theReport - The alternative report to avoid pollution of the global one. + void BuildBOP(const TopTools_ListOfShape& theObjects, + const TopTools_ListOfShape& theTools, + const BOPAlgo_Operation theOperation, + Handle(Message_Report) theReport = NULL) + { + TopAbs_State anObjState, aToolsState; + switch (theOperation) + { + case BOPAlgo_COMMON: + { + anObjState = TopAbs_IN; + aToolsState = TopAbs_IN; + break; + } + case BOPAlgo_FUSE: + { + anObjState = TopAbs_OUT; + aToolsState = TopAbs_OUT; + break; + } + case BOPAlgo_CUT: + { + anObjState = TopAbs_OUT; + aToolsState = TopAbs_IN; + break; + } + case BOPAlgo_CUT21: + { + anObjState = TopAbs_IN; + aToolsState = TopAbs_OUT; + break; + } + default: + { + anObjState = TopAbs_UNKNOWN; + aToolsState = TopAbs_UNKNOWN; + break; + } + } + BuildBOP(theObjects, anObjState, theTools, aToolsState, theReport); + } + protected: //! @name History methods //! Prepare information for history support. @@ -315,15 +425,11 @@ protected: //! @name Fill Images of SOLIDS TopTools_ListOfShape& theLIF); //! Finds faces located inside each solid. - Standard_EXPORT virtual void FillIn3DParts (TopTools_DataMapOfShapeListOfShape& theInParts, - TopTools_DataMapOfShapeShape& theDraftSolids, - const Handle(NCollection_BaseAllocator)& theAllocator); + Standard_EXPORT virtual void FillIn3DParts(TopTools_DataMapOfShapeShape& theDraftSolids); //! Builds the splits of the solids using their draft versions //! and faces located inside. - Standard_EXPORT void BuildSplitSolids (TopTools_DataMapOfShapeListOfShape& theInParts, - TopTools_DataMapOfShapeShape& theDraftSolids, - const Handle(NCollection_BaseAllocator)& theAllocator); + Standard_EXPORT void BuildSplitSolids(TopTools_DataMapOfShapeShape& theDraftSolids); //! Classifies the vertices and edges from the arguments relatively //! splits of solids and makes them INTERNAL for solids. @@ -358,6 +464,7 @@ protected: //! @name Fields TopTools_DataMapOfShapeListOfShape myImages; //!< Images - map of Images of the sub-shapes of arguments TopTools_DataMapOfShapeShape myShapesSD; //!< ShapesSD - map of SD Shapes TopTools_DataMapOfShapeListOfShape myOrigins; //!< Origins - map of Origins, back map of Images + TopTools_DataMapOfShapeListOfShape myInParts; //!< InParts - map of own and acquired IN faces of the arguments solids Standard_Boolean myNonDestructive; //!< Safe processing option allows avoiding modification of the input shapes BOPAlgo_GlueEnum myGlue; //!< Gluing option allows speeding up the intersection of the input shapes Standard_Boolean myCheckInverted; //!< Check inverted option allows disabling the check of input solids on inverted status diff --git a/src/BOPAlgo/BOPAlgo_Builder_3.cxx b/src/BOPAlgo/BOPAlgo_Builder_3.cxx index 942b92b2cc..2896c4cf9a 100644 --- a/src/BOPAlgo/BOPAlgo_Builder_3.cxx +++ b/src/BOPAlgo/BOPAlgo_Builder_3.cxx @@ -23,6 +23,7 @@ #include // #include +#include #include #include #include @@ -90,29 +91,21 @@ void BOPAlgo_Builder::FillImagesSolids() if (!bHasSolids) { return; } - // - Handle(NCollection_BaseAllocator) aAlr; - // - aAlr=NCollection_BaseAllocator::CommonBaseAllocator(); - // - TopTools_DataMapOfShapeListOfShape theInParts(100, aAlr); - TopTools_DataMapOfShapeShape theDraftSolids(100, aAlr); - // - FillIn3DParts(theInParts, theDraftSolids, aAlr); - BuildSplitSolids(theInParts, theDraftSolids, aAlr); + + // Draft solids + TopTools_DataMapOfShapeShape aDraftSolids; + // Find all IN faces for all IN faces + FillIn3DParts(aDraftSolids); + // Build split of the solids + BuildSplitSolids(aDraftSolids); + // Fill solids with internal parts FillInternalShapes(); - // - theInParts.Clear(); - theDraftSolids.Clear(); } //======================================================================= //function : FillIn3DParts //purpose : //======================================================================= -void BOPAlgo_Builder::FillIn3DParts - (TopTools_DataMapOfShapeListOfShape& theInParts, - TopTools_DataMapOfShapeShape& theDraftSolids, - const Handle(NCollection_BaseAllocator)& ) +void BOPAlgo_Builder::FillIn3DParts(TopTools_DataMapOfShapeShape& theDraftSolids) { Handle(NCollection_BaseAllocator) anAlloc = new NCollection_IncAllocator; @@ -224,7 +217,7 @@ void BOPAlgo_Builder::FillIn3DParts if (aNbInt || aNbIN) { // Combine the lists - TopTools_ListOfShape *pLIN = theInParts.Bound(aSolid, TopTools_ListOfShape()); + TopTools_ListOfShape *pLIN = myInParts.Bound(aSolid, TopTools_ListOfShape()); TopTools_ListIteratorOfListOfShape aItLS(aLInFaces); for (; aItLS.More(); aItLS.Next()) @@ -360,10 +353,7 @@ typedef BOPTools_CntIsEmpty()) { aSolidsIm(aSolidsIm.Add(aS, TopTools_ListOfShape())).Append(aSD); @@ -464,7 +454,35 @@ void BOPAlgo_Builder::BuildSplitSolids { BOPAlgo_SplitSolid& aBS = aVBS(k); aSolidsIm.Add(aBS.Solid(), aBS.Areas()); - myReport->Merge(aBS.GetReport()); + + // Merge BuilderSolid's report into main report, + // assigning the solid with the warnings/errors which + // have been generated for it. + // Convert all errors of BuilderSolid into warnings for main report. + const Handle(Message_Report)& aBSReport = aBS.GetReport(); + Message_Gravity anAlertTypes[2] = { Message_Warning, Message_Fail }; + for (Standard_Integer iGravity = 0; iGravity < 2; iGravity++) + { + const Message_ListOfAlert& anLAlerts = aBSReport->GetAlerts(anAlertTypes[iGravity]); + for (Message_ListOfAlert::Iterator itA(anLAlerts); itA.More(); itA.Next()) + { + Handle(Message_Alert) anAlert = itA.Value(); + + Handle(TopoDS_AlertWithShape) anAlertWithShape = Handle(TopoDS_AlertWithShape)::DownCast(itA.Value()); + if (!anAlertWithShape.IsNull()) + { + TopoDS_Shape aWarnShape; + BRep_Builder().MakeCompound(TopoDS::Compound(aWarnShape)); + BRep_Builder().Add(aWarnShape, aBS.Solid()); + BRep_Builder().Add(aWarnShape, anAlertWithShape->GetShape()); + + anAlertWithShape->SetShape(aWarnShape); + AddWarning(anAlertWithShape); + } + else + AddWarning(anAlert); + } + } } // // Add new solids to images map diff --git a/src/BOPAlgo/BOPAlgo_MakerVolume.cxx b/src/BOPAlgo/BOPAlgo_MakerVolume.cxx index 21c20486bb..0e7b0b2bce 100644 --- a/src/BOPAlgo/BOPAlgo_MakerVolume.cxx +++ b/src/BOPAlgo/BOPAlgo_MakerVolume.cxx @@ -332,97 +332,40 @@ void BOPAlgo_MakerVolume::FillInternalShapes(const TopTools_ListOfShape& theLSR) if (myAvoidInternalShapes) { return; } - // + UserBreak(); - // - Standard_Integer aNbSI; - TopAbs_ShapeEnum aType; - TopAbs_State aState; - TopoDS_Iterator aItS; - BRep_Builder aBB; + + // Get all non-compound shapes + TopTools_ListOfShape aLSC; + // Fence map TopTools_MapOfShape aMFence; - TopTools_IndexedMapOfShape aMSS; - TopTools_ListOfShape aLVE, aLSC, aLSIn; - TopTools_ListIteratorOfListOfShape aIt, aIt1; - // - // 1. Collect shapes to process: vertices, edges, wires - const TopTools_ListOfShape& anArguments = myDS->Arguments(); - aIt.Initialize(anArguments); - for (; aIt.More(); aIt.Next()) { - const TopoDS_Shape& aS = aIt.Value(); - BOPAlgo_Tools::TreatCompound(aS, aMFence, aLSC); - } - // - aIt.Initialize(aLSC); - for (; aIt.More(); aIt.Next()) { - const TopoDS_Shape& aS = aIt.Value(); - aType = aS.ShapeType(); - if (aType == TopAbs_WIRE) { - aItS.Initialize(aS); - for(; aItS.More(); aItS.Next()) { - const TopoDS_Shape& aE = aItS.Value(); - if (aMFence.Add(aE)) { - aLVE.Append(aE); - } + + TopTools_ListOfShape::Iterator itLA(myDS->Arguments()); + for (; itLA.More(); itLA.Next()) + BOPAlgo_Tools::TreatCompound(itLA.Value(), aMFence, aLSC); + + // Get only edges and vertices from arguments + TopTools_ListOfShape aLVE; + + itLA.Initialize(aLSC); + for (; itLA.More(); itLA.Next()) + { + const TopoDS_Shape& aS = itLA.Value(); + TopAbs_ShapeEnum aType = aS.ShapeType(); + if (aType == TopAbs_WIRE) + { + for (TopoDS_Iterator it(aS); it.More(); it.Next()) + { + const TopoDS_Shape& aSS = it.Value(); + if (aMFence.Add(aSS)) + aLVE.Append(aSS); } } - else if (aType == TopAbs_VERTEX || aType == TopAbs_EDGE) { + else if (aType == TopAbs_VERTEX || aType == TopAbs_EDGE) aLVE.Append(aS); - } - } - // - aIt.Initialize(theLSR); - for (; aIt.More(); aIt.Next()) { - const TopoDS_Shape& aS = aIt.Value(); - TopExp::MapShapes(aS, TopAbs_EDGE, aMSS); - TopExp::MapShapes(aS, TopAbs_VERTEX, aMSS); - } - // - aIt.Initialize(aLVE); - for (; aIt.More(); aIt.Next()) { - const TopoDS_Shape& aS = aIt.Value(); - if (myImages.IsBound(aS)) { - const TopTools_ListOfShape &aLSp = myImages.Find(aS); - aIt1.Initialize(aLSp); - for (; aIt1.More(); aIt1.Next()) { - const TopoDS_Shape& aSp = aIt1.Value(); - if (aMSS.Add(aSp)) { - aLSIn.Append(aSp); - } - } - } - else { - if (aMSS.Add(aS)) { - aLSIn.Append(aS); - } - } - } - // - aNbSI = aLSIn.Extent(); - if (!aNbSI) { - return; - } - // - // 2. Settle internal vertices and edges into solids - aIt.Initialize(theLSR); - for (; aIt.More(); aIt.Next()) { - TopoDS_Solid aSd = *(TopoDS_Solid*)&aIt.Value(); - // - aIt1.Initialize(aLSIn); - for (; aIt1.More(); ) { - TopoDS_Shape aSI = aIt1.Value(); - aSI.Orientation(TopAbs_INTERNAL); - // - aState = BOPTools_AlgoTools::ComputeStateByOnePoint(aSI, aSd, 1.e-11, myContext); - if (aState == TopAbs_IN) { - aBB.Add(aSd, aSI); - aLSIn.Remove(aIt1); - } - else { - aIt1.Next(); - } - } } + + BOPAlgo_Tools::FillInternals(theLSR, aLVE, myImages, myContext); } //======================================================================= diff --git a/src/BOPAlgo/BOPAlgo_Tools.cxx b/src/BOPAlgo/BOPAlgo_Tools.cxx index ae495455f1..586c47535d 100644 --- a/src/BOPAlgo/BOPAlgo_Tools.cxx +++ b/src/BOPAlgo/BOPAlgo_Tools.cxx @@ -1643,3 +1643,69 @@ void BOPAlgo_Tools::ClassifyFaces(const TopTools_ListOfShape& theFaces, theInParts.Add(aS, aLFIn); } } + +//======================================================================= +//function : FillInternals +//purpose : +//======================================================================= +void BOPAlgo_Tools::FillInternals(const TopTools_ListOfShape& theSolids, + const TopTools_ListOfShape& theParts, + const TopTools_DataMapOfShapeListOfShape& theImages, + const Handle(IntTools_Context)& theContext) +{ + if (theSolids.IsEmpty() || theParts.IsEmpty()) + return; + + // Map the solids to avoid classification of the own shapes of the solids + TopTools_IndexedMapOfShape aMSSolids; + TopTools_ListOfShape::Iterator itLS(theSolids); + for (; itLS.More(); itLS.Next()) + { + const TopoDS_Shape& aSolid = itLS.Value(); + TopExp::MapShapes(aSolid, TopAbs_EDGE, aMSSolids); + TopExp::MapShapes(aSolid, TopAbs_VERTEX, aMSSolids); + } + + // Check the parts for possible splits + TopTools_ListOfShape aLParts; + TopTools_ListOfShape::Iterator itLP(theParts); + for (; itLP.More(); itLP.Next()) + { + const TopoDS_Shape& aPart = itLP.Value(); + const TopTools_ListOfShape* pIm = theImages.Seek(aPart); + if (pIm) + { + TopTools_ListOfShape::Iterator itIm(*pIm); + for (; itIm.More(); itIm.Next()) + { + const TopoDS_Shape& aPIm = itIm.Value(); + if (!aMSSolids.Contains(aPIm)) + aLParts.Append(aPIm); + } + } + else if (!aMSSolids.Contains(aPart)) + aLParts.Append(aPart); + } + + itLS.Initialize(theSolids); + for (; itLS.More(); itLS.Next()) + { + TopoDS_Solid aSd = *(TopoDS_Solid*)&itLS.Value(); + + itLP.Initialize(aLParts); + for (; itLP.More();) + { + TopoDS_Shape aPart = itLP.Value(); + TopAbs_State aState = + BOPTools_AlgoTools::ComputeStateByOnePoint(aPart, aSd, Precision::Confusion(), theContext); + if (aState == TopAbs_IN) + { + aPart.Orientation(TopAbs_INTERNAL); + BRep_Builder().Add(aSd, aPart); + aLParts.Remove(itLP); + } + else + itLP.Next(); + } + } +} diff --git a/src/BOPAlgo/BOPAlgo_Tools.hxx b/src/BOPAlgo/BOPAlgo_Tools.hxx index 7d75bfa51d..50492e9b55 100644 --- a/src/BOPAlgo/BOPAlgo_Tools.hxx +++ b/src/BOPAlgo/BOPAlgo_Tools.hxx @@ -194,6 +194,18 @@ public: const TopTools_DataMapOfShapeBox& theShapeBoxMap = TopTools_DataMapOfShapeBox(), const TopTools_DataMapOfShapeListOfShape& theSolidsIF = TopTools_DataMapOfShapeListOfShape()); + //! Classifies the given parts relatively the given solids and + //! fills the solids with the parts classified as INTERNAL. + //! + //! @param theSolids - The solids to put internals to + //! @param theParts - The parts to classify relatively solids + //! @param theImages - Possible images of the parts that has to be classified + //! @param theContext - Cashed geometrical tools to speed-up classifications + Standard_EXPORT static void FillInternals(const TopTools_ListOfShape& theSolids, + const TopTools_ListOfShape& theParts, + const TopTools_DataMapOfShapeListOfShape& theImages, + const Handle(IntTools_Context)& theContext); + }; #endif // _BOPAlgo_Tools_HeaderFile diff --git a/src/BOPTest/BOPTest_PartitionCommands.cxx b/src/BOPTest/BOPTest_PartitionCommands.cxx index 23bd525a94..ebbb605d21 100644 --- a/src/BOPTest/BOPTest_PartitionCommands.cxx +++ b/src/BOPTest/BOPTest_PartitionCommands.cxx @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -29,6 +30,7 @@ #include #include #include +#include #include #include @@ -38,6 +40,7 @@ static Standard_Integer bfillds (Draw_Interpretor&, Standard_Integer, const cha static Standard_Integer bbuild (Draw_Interpretor&, Standard_Integer, const char**); static Standard_Integer bbop (Draw_Interpretor&, Standard_Integer, const char**); static Standard_Integer bsplit (Draw_Interpretor&, Standard_Integer, const char**); +static Standard_Integer buildbop (Draw_Interpretor&, Standard_Integer, const char**); //======================================================================= //function : PartitionCommands @@ -55,6 +58,20 @@ void BOPTest::PartitionCommands(Draw_Interpretor& theCommands) theCommands.Add("bbuild" , "use bbuild r [-t]" , __FILE__, bbuild, g); theCommands.Add("bbop" , "use bbop r op [-t]", __FILE__, bbop, g); theCommands.Add("bsplit" , "use bsplit r [-t]" , __FILE__, bsplit, g); + + theCommands.Add("buildbop", "Builds the result of BOP basing on the GF.\n" + " The command uses classification approach for building the result of BOP\n" + " (thus it operates on solids only and can be used on open solids):\n" + " - FUSE is built from the faces OUT of all arguments\n" + " - COMMON is built from the faces IN any of the object/tools\n" + " - CUT is built from the objects faces OUT of the tools and tools faces IN the objects.\n" + " Please note that history for solids will not be available.\n\n" + " Usage: buildbop result -o s1 [s2 ...] -t s3 [s4 ...] -op operation (common/fuse/cut/tuc)\n" + " Where:\n" + " result - result shape of the operation\n" + " s1 s2 s3 s4 - arguments (solids) of the GF operation\n" + " operation - type of boolean operation", + __FILE__, buildbop, g); } //======================================================================= //function : bfillds @@ -419,3 +436,157 @@ Standard_Integer bsplit(Draw_Interpretor& di, DBRep::Set(a[1], aR); return 0; } + +//======================================================================= +//function : buildbop +//purpose : +//======================================================================= +Standard_Integer buildbop(Draw_Interpretor& di, + Standard_Integer n, + const char** a) +{ + if (n < 3) + { + di.PrintHelp(a[0]); + return 1; + } + + BOPDS_PDS pDS = BOPTest_Objects::PDS(); + if (!pDS) + { + di << "Error: perform intersection of arguments first"; + return 1; + } + + BOPAlgo_Builder *pBuilder = &BOPTest_Objects::Builder(); + if (pBuilder->HasErrors()) + { + di << "Error: there were problems during GF"; + return 0; + } + + if (pBuilder->Arguments().IsEmpty() || + pBuilder->Shape().IsNull()) + { + di << "Error: it seems the GF has not been yet performed"; + return 1; + } + + // Get arguments and operation + TopTools_ListOfShape aLObjects, aLTools; + BOPAlgo_Operation anOp = BOPAlgo_UNKNOWN; + + for (Standard_Integer i = 2; i < n; ++i) + { + if (!strcmp(a[i], "-o") || !strcmp(a[i], "-t")) + { + if (i == (n - 1)) + { + di << "Error: shapes are expected after the key " << a[i]; + return 1; + } + + TopTools_ListOfShape& aList = !strcmp(a[i], "-o") ? aLObjects : aLTools; + Standard_Integer j = i + 1; + for (; j < n; ++j) + { + if (a[j][0] == '-') + { + // reached the following key + i = j - 1; + break; + } + else + { + // Get the shape + TopoDS_Shape aS = DBRep::Get(a[j]); + if (aS.IsNull()) + { + di << "Error: " << a[j] << " is a null shape"; + return 1; + } + if (aS.ShapeType() != TopAbs_SOLID) + { + di << "Error: " << a[j] << " is not a solid"; + return 1; + } + if (pDS->Index(aS) < 0) + { + di << "Error: " << a[j] << " is not an argument of GF"; + return 1; + } + aList.Append(aS); + } + } + // End of arguments is reached + if (j == n) break; + } + else if (!strcmp(a[i], "-op")) + { + if (i == (n - 1)) + { + di << "Error: operation type is expected after the key " << a[i]; + return 1; + } + + ++i; + if (!strcasecmp(a[i], "common")) + anOp = BOPAlgo_COMMON; + else if (!strcasecmp(a[i], "fuse")) + anOp = BOPAlgo_FUSE; + else if (!strcasecmp(a[i], "cut")) + anOp = BOPAlgo_CUT; + else if (!strcasecmp(a[i], "tuc")) + anOp = BOPAlgo_CUT21; + else + { + di << "Error: unknown operation type"; + return 1; + } + } + else + { + di << "Error: " << a[i] << " invalid key"; + return 1; + } + } + + if (anOp == BOPAlgo_UNKNOWN) + { + di << "Error: operation has not been specified"; + return 1; + } + + Standard_Boolean hasObjects = !aLObjects.IsEmpty(); + Standard_Boolean hasTools = !aLTools.IsEmpty(); + if (!hasObjects && !hasTools) + { + di << "Error: no shapes are given"; + return 1; + } + + // Create new report for the operation + Handle(Message_Report) aReport = new Message_Report; + + // Build specific operation + pBuilder->BuildBOP(aLObjects, aLTools, anOp, aReport); + + // Report alerts of the operation + BOPTest::ReportAlerts(aReport); + + if (!aReport->GetAlerts(Message_Fail).IsEmpty()) + { + return 0; + } + + // Set history of Split operation into the session + if (BRepTest_Objects::IsHistoryNeeded()) + BRepTest_Objects::SetHistory(pDS->Arguments(), *pBuilder); + + // Result shape + const TopoDS_Shape& aR = pBuilder->Shape(); + // Draw result shape + DBRep::Set(a[1], aR); + + return 0; +} diff --git a/src/BRepFeat/BRepFeat_Builder.cxx b/src/BRepFeat/BRepFeat_Builder.cxx index f234831e2d..4a6635ea55 100644 --- a/src/BRepFeat/BRepFeat_Builder.cxx +++ b/src/BRepFeat/BRepFeat_Builder.cxx @@ -732,236 +732,24 @@ //function : FillIn3DParts //purpose : //======================================================================= - void BRepFeat_Builder::FillIn3DParts(TopTools_DataMapOfShapeListOfShape& theInParts, - TopTools_DataMapOfShapeShape& theDraftSolids, - const Handle(NCollection_BaseAllocator)& theAllocator) + void BRepFeat_Builder::FillIn3DParts(TopTools_DataMapOfShapeShape& theDraftSolids) { GetReport()->Clear(); - // - Standard_Boolean bIsIN, bHasImage; - Standard_Integer aNbS, i, j, aNbFP, aNbFPx, aNbFIN, aNbLIF, aNbEFP; - TopAbs_ShapeEnum aType; - TopAbs_State aState; - TopoDS_Iterator aIt, aItF; - BRep_Builder aBB; - TopoDS_Solid aSolidSp; - TopoDS_Face aFP; - TopTools_ListIteratorOfListOfShape aItS, aItFP, aItEx; - TopTools_MapIteratorOfMapOfShape aItMS, aItMS1; - // - TopTools_ListOfShape aLIF(theAllocator); - TopTools_MapOfShape aMFDone(100, theAllocator); - TopTools_MapOfShape aMSolids(100, theAllocator); - TopTools_MapOfShape aMFaces(100, theAllocator); - TopTools_MapOfShape aMFIN(100, theAllocator); - TopTools_IndexedMapOfShape aMS(100, theAllocator); - TopTools_IndexedDataMapOfShapeListOfShape aMEF(100, theAllocator); - // - theDraftSolids.Clear(); - // - aNbS=myDS->NbSourceShapes(); - for (i=0; iShapeInfo(i); - const TopoDS_Shape& aS=aSI.Shape(); - // - aType=aSI.ShapeType(); - switch(aType) { - case TopAbs_SOLID: { - aMSolids.Add(aS); - break; - } - // - case TopAbs_FACE: { - // all faces (originals or images) - if (myImages.IsBound(aS)) { - const TopTools_ListOfShape& aLS=myImages.Find(aS); - aItS.Initialize(aLS); - for (; aItS.More(); aItS.Next()) { - const TopoDS_Shape& aFx=aItS.Value(); - if (!myRemoved.Contains(aFx)) { - aMFaces.Add(aFx); - } - } - } - else { - if (!myRemoved.Contains(aS)) { - aMFaces.Add(aS); - } - } - break; - } - // - default: - break; + + BOPAlgo_Builder::FillIn3DParts(theDraftSolids); + + // Clear the IN parts of the solids from the removed faces + TopTools_DataMapOfShapeListOfShape::Iterator itM(myInParts); + for (; itM.More(); itM.Next()) + { + TopTools_ListOfShape& aList = itM.ChangeValue(); + TopTools_ListOfShape::Iterator itL(aList); + for (; itL.More();) + { + if (myRemoved.Contains(itL.Value())) + aList.Remove(itL); + else + itL.Next(); } } - // - aItMS.Initialize(aMSolids); - for (; aItMS.More(); aItMS.Next()) { - const TopoDS_Solid& aSolid=(*(TopoDS_Solid*)(&aItMS.Value())); - // - aMFDone.Clear(); - aMFIN.Clear(); - aMEF.Clear(); - // - aBB.MakeSolid(aSolidSp); - // - // Draft solid and its pure internal faces => aSolidSp, aLIF - aLIF.Clear(); - BuildDraftSolid(aSolid, aSolidSp, aLIF); - aNbLIF=aLIF.Extent(); - // - // 1 all faces/edges from aSolid [ aMS ] - bHasImage=Standard_False; - aMS.Clear(); - aIt.Initialize(aSolid); - for (; aIt.More(); aIt.Next()) { - const TopoDS_Shape& aShell=aIt.Value(); - // - if (myImages.IsBound(aShell)) { - bHasImage=Standard_True; - // - const TopTools_ListOfShape& aLS=myImages.Find(aShell); - aItS.Initialize(aLS); - for (; aItS.More(); aItS.Next()) { - const TopoDS_Shape& aSx=aItS.Value(); - aMS.Add(aSx); - TopExp::MapShapes(aSx, TopAbs_FACE, aMS); - TopExp::MapShapes(aSx, TopAbs_EDGE, aMS); - TopExp::MapShapesAndAncestors(aSx, TopAbs_EDGE, TopAbs_FACE, aMEF); - } - } - else { - //aMS.Add(aShell); - TopExp::MapShapes(aShell, TopAbs_FACE, aMS); - TopExp::MapShapesAndAncestors(aShell, TopAbs_EDGE, TopAbs_FACE, aMEF); - } - } - // - // 2 all faces that are not from aSolid [ aLFP1 ] - TopTools_IndexedDataMapOfShapeListOfShape aMEFP(100, theAllocator); - TopTools_ListOfShape aLFP1(theAllocator); - TopTools_ListOfShape aLFP(theAllocator); - TopTools_ListOfShape aLCBF(theAllocator); - TopTools_ListOfShape aLFIN(theAllocator); - TopTools_ListOfShape aLEx(theAllocator); - // - // for all non-solid faces build EF map [ aMEFP ] - aItMS1.Initialize(aMFaces); - for (; aItMS1.More(); aItMS1.Next()) { - const TopoDS_Shape& aFace=aItMS1.Value(); - if (!aMS.Contains(aFace)) { - TopExp::MapShapesAndAncestors(aFace, TopAbs_EDGE, TopAbs_FACE, aMEFP); - } - } - // - // among all faces from aMEFP select these that have same edges - // with the solid (i.e aMEF). These faces will be treated first - // to prevent the usage of 3D classifier. - // The full list of faces to process is aLFP1. - aNbEFP=aMEFP.Extent(); - for (j=1; j<=aNbEFP; ++j) { - const TopoDS_Shape& aE=aMEFP.FindKey(j); - // - if (aMEF.Contains(aE)) { // !! - const TopTools_ListOfShape& aLF=aMEFP(j); - aItFP.Initialize(aLF); - for (; aItFP.More(); aItFP.Next()) { - const TopoDS_Shape& aF=aItFP.Value(); - if (aMFDone.Add(aF)) { - aLFP1.Append(aF); - } - } - } - else { - aLEx.Append(aE); - } - } - // - aItEx.Initialize(aLEx); - for (; aItEx.More(); aItEx.Next()) { - const TopoDS_Shape& aE=aItEx.Value(); - const TopTools_ListOfShape& aLF=aMEFP.FindFromKey(aE); - aItFP.Initialize(aLF); - for (; aItFP.More(); aItFP.Next()) { - const TopoDS_Shape& aF=aItFP.Value(); - if (aMFDone.Add(aF)) { - //aLFP2.Append(aF); - aLFP1.Append(aF); - } - } - } - // - //========== - // - // 3 Process faces aLFP1 - aMFDone.Clear(); - aNbFP=aLFP1.Extent(); - aItFP.Initialize(aLFP1); - for (; aItFP.More(); aItFP.Next()) { - const TopoDS_Shape& aSP=aItFP.Value(); - if (!aMFDone.Add(aSP)) { - continue; - } - - // - // first face to process - aFP=(*(TopoDS_Face*)(&aSP)); - bIsIN=BOPTools_AlgoTools::IsInternalFace(aFP, aSolidSp, aMEF, 1.e-14, myContext); - aState=(bIsIN) ? TopAbs_IN : TopAbs_OUT; - // - // collect faces to process [ aFP is the first ] - aLFP.Clear(); - aLFP.Append(aFP); - aItS.Initialize(aLFP1); - for (; aItS.More(); aItS.Next()) { - const TopoDS_Shape& aSk=aItS.Value(); - if (!aMFDone.Contains(aSk)) { - aLFP.Append(aSk); - } - } - // - // Connexity Block that spreads from aFP the Bound - // or till the end of the block itself - aLCBF.Clear(); - BOPTools_AlgoTools::MakeConnexityBlock(aLFP, aMS, aLCBF, theAllocator); - // - // fill states for the Connexity Block - aItS.Initialize(aLCBF); - for (; aItS.More(); aItS.Next()) { - const TopoDS_Shape& aSx=aItS.Value(); - aMFDone.Add(aSx); - if (aState==TopAbs_IN) { - aMFIN.Add(aSx); - } - } - // - aNbFPx=aMFDone.Extent(); - if (aNbFPx==aNbFP) { - break; - } - }//for (; aItFP.More(); aItFP.Next()) - // - // faces Inside aSolid - aLFIN.Clear(); - aNbFIN=aMFIN.Extent(); - if (aNbFIN || aNbLIF) { - aItMS1.Initialize(aMFIN); - for (; aItMS1.More(); aItMS1.Next()) { - const TopoDS_Shape& aFIn=aItMS1.Value(); - aLFIN.Append(aFIn); - } - // - aItS.Initialize(aLIF); - for (; aItS.More(); aItS.Next()) { - const TopoDS_Shape& aFIN=aItS.Value(); - aLFIN.Append(aFIN); - } - // - theInParts.Bind(aSolid, aLFIN); - } - if (aNbFIN || bHasImage) { - theDraftSolids.Bind(aSolid, aSolidSp); - } - }// for (; aItMS.More(); aItMS.Next()) { } diff --git a/src/BRepFeat/BRepFeat_Builder.hxx b/src/BRepFeat/BRepFeat_Builder.hxx index 383bff4114..62a5241858 100644 --- a/src/BRepFeat/BRepFeat_Builder.hxx +++ b/src/BRepFeat/BRepFeat_Builder.hxx @@ -117,7 +117,14 @@ protected: Standard_EXPORT virtual void Prepare() Standard_OVERRIDE; //! Function is redefined to avoid the usage of removed faces. - Standard_EXPORT virtual void FillIn3DParts (TopTools_DataMapOfShapeListOfShape& theInParts, TopTools_DataMapOfShapeShape& theDraftSolids, const Handle(NCollection_BaseAllocator)& theAllocator) Standard_OVERRIDE; + Standard_EXPORT virtual void FillIn3DParts (TopTools_DataMapOfShapeShape& theDraftSolids) Standard_OVERRIDE; + + //! Avoid the check for open solids and always use the splits + //! of solids for building the result shape. + virtual Standard_Boolean CheckArgsForOpenSolid() Standard_OVERRIDE + { + return Standard_False; + } TopTools_MapOfShape myShapes; diff --git a/src/TopoDS/TopoDS_AlertWithShape.hxx b/src/TopoDS/TopoDS_AlertWithShape.hxx index 602e5f9583..798f6de001 100644 --- a/src/TopoDS/TopoDS_AlertWithShape.hxx +++ b/src/TopoDS/TopoDS_AlertWithShape.hxx @@ -28,7 +28,10 @@ public: //! Returns contained shape const TopoDS_Shape& GetShape() const { return myShape; } - + + //! Sets the shape + void SetShape(const TopoDS_Shape& theShape) { myShape = theShape; } + //! Returns false. virtual Standard_EXPORT Standard_Boolean SupportsMerge () const Standard_OVERRIDE; diff --git a/tests/boolean/bfuse_complex/O5 b/tests/boolean/bfuse_complex/O5 index 606350d05a..d72799e3b5 100644 --- a/tests/boolean/bfuse_complex/O5 +++ b/tests/boolean/bfuse_complex/O5 @@ -5,5 +5,5 @@ restore [locate_data_file CTO908_topo104-o2.brep] jante bfuse result wheel jante -checkprops result -s 40059.9 +checkprops result -s 28962.2 checkview -display result -2d -otherwise { wheel jante } -s -path ${imagedir}/${test_image}.png \ No newline at end of file diff --git a/tests/boolean/grids.list b/tests/boolean/grids.list index c114c1d8b2..4a295c5475 100644 --- a/tests/boolean/grids.list +++ b/tests/boolean/grids.list @@ -29,4 +29,5 @@ 029 splitter 030 history 031 removefeatures -032 simplify \ No newline at end of file +032 simplify +033 opensolid \ No newline at end of file diff --git a/tests/boolean/opensolid/A1 b/tests/boolean/opensolid/A1 new file mode 100644 index 0000000000..95dda57b87 --- /dev/null +++ b/tests/boolean/opensolid/A1 @@ -0,0 +1,41 @@ +puts "REQUIRED All: Faulty shapes in variables faulty_1 to faulty_2" + +box b1 10 10 10 +box b2 5 0 0 10 10 10 + +shape s1 So +shape s2 So +shape sh1 Sh +shape sh2 Sh + +foreach f [lrange [explode b1 f] 0 4] { add $f sh1 } +foreach f [lrange [explode b2 f] 0 4] { add $f sh2 } + +add sh1 s1 +add sh2 s2 + +bclearobjects +bcleartools +baddobjects s1 +baddtools s2 +bfillds + +bbop r0 0 +bbop r1 1 +bbop r2 2 +bbop r3 3 + +foreach r {r0 r1 r2 r3} { + checkshape $r + if {![regexp "OK" [bopcheck $r]]} { + puts "Error: shape is self-interfered" + } +} + +checknbshapes r1 -vertex 16 -edge 26 -wire 11 -face 11 -shell 1 -solid 1 +checkprops r1 -s 650 + +foreach r {r0 r2 r3} { + checknbshapes $r -vertex 8 -edge 12 -wire 5 -face 5 -shell 1 -solid 1 + checkprops $r -s 350 +} diff --git a/tests/boolean/opensolid/A2 b/tests/boolean/opensolid/A2 new file mode 100644 index 0000000000..18d86f0a3b --- /dev/null +++ b/tests/boolean/opensolid/A2 @@ -0,0 +1,45 @@ +puts "REQUIRED All: Faulty shapes in variables faulty_1 to faulty_2" + +box b1 10 10 10 +box b2 2.5 0 0 5 10 5 + +shape s1 So +shape s2 So +shape sh1 Sh +shape sh2 Sh + +foreach f [lrange [explode b1 f] 0 4] { add $f sh1 } +foreach f [lrange [explode b2 f] 0 4] { add $f sh2 } + +add sh1 s1 +add sh2 s2 + +bclearobjects +bcleartools +baddobjects s1 +baddtools s2 +bfillds + +bbop r0 0 +bbop r1 1 +bbop r2 2 +bbop r3 3 + +foreach r {r0 r1 r2 r3} { + checkshape $r + if {![regexp "OK" [bopcheck $r]]} { + puts "Error: shape is self-interfered" + } +} + +checknbshapes r0 -vertex 8 -edge 12 -wire 5 -face 5 -shell 1 -solid 1 +checkprops r0 -s 200 + +checknbshapes r1 -vertex 16 -edge 24 -wire 9 -face 9 -shell 1 -solid 1 +checkprops r1 -s 500 + +checknbshapes r2 -vertex 16 -edge 24 -wire 8 -face 8 -shell 1 -solid 1 +checkprops r2 -s 500 + +checknbshapes r3 -vertex 0 -edge 0 -wire 0 -face 0 -shell 0 -solid 0 +checkprops r3 -s empty diff --git a/tests/boolean/opensolid/A3 b/tests/boolean/opensolid/A3 new file mode 100644 index 0000000000..db57478dbd --- /dev/null +++ b/tests/boolean/opensolid/A3 @@ -0,0 +1,44 @@ +puts "REQUIRED All: Faulty shapes in variables faulty_1 to faulty_2" + +box b1 -15 0 0 20 5 5 +box b2 5 5 20 + +shape s1 So +shape s2 So +shape sh1 Sh +shape sh2 Sh + +foreach f [lrange [explode b1 f] 1 5] { add $f sh1 } +foreach f [lrange [explode b2 f] 0 4] { add $f sh2 } + +add sh1 s1 +add sh2 s2 + +bclearobjects +bcleartools +baddobjects s1 +baddtools s2 +bfillds + +bbop r0 0 +bbop r1 1 +bbop r2 2 +bbop r3 3 + +foreach r {r0 r1 r2 r3} { + checkshape $r + if {![regexp "OK" [bopcheck $r]]} { + puts "Error: shape is self-interfered" + } +} + +checknbshapes r0 -vertex 8 -edge 12 -wire 6 -face 6 -shell 1 -solid 1 +checkprops r0 -s 150 + +checknbshapes r1 -vertex 16 -edge 28 -wire 12 -face 12 -shell 1 -solid 1 +checkprops r1 -s 700 + +foreach r {r2 r3} { + checknbshapes $r -vertex 8 -edge 12 -wire 5 -face 5 -shell 1 -solid 1 + checkprops $r -s 325 +} diff --git a/tests/boolean/opensolid/A4 b/tests/boolean/opensolid/A4 new file mode 100644 index 0000000000..66721efa43 --- /dev/null +++ b/tests/boolean/opensolid/A4 @@ -0,0 +1,60 @@ +box b1 40 10 10 +box b2 0 0 30 40 10 10 +box b3 10 10 40 +box b4 30 0 0 10 10 40 + +bclearobjects +bcleartools +baddobjects b1 b2 b3 b4 +bfillds +bbuild r + +# fuse of all +buildbop r1 -o b1 b2 b3 b4 -op fuse +buildbop r2 -o b1 b2 b3 -t b4 -op fuse +buildbop r3 -o b1 b2 -t b3 b4 -op fuse +buildbop r4 -o b1 -t b2 b3 b4 -op fuse +buildbop r5 -t b1 b2 b3 b4 -op fuse + +foreach r {r1 r2 r3 r4 r5} { + checkshape $r + checknbshapes $r -vertex 32 -edge 64 -wire 32 -face 32 -shell 1 -solid 1 + checkprops $r -s 4800 -v 12000 +} + +# Cut +buildbop r1 -o b1 b2 -t b3 b4 -op cut +buildbop r2 -o b1 b2 -t b3 b4 -op tuc + +foreach r {r1 r2} { + checkshape $r + checknbshapes $r -vertex 16 -edge 24 -wire 12 -face 12 -shell 2 -solid 2 + checkprops $r -s 2000 -v 4000 +} + +buildbop r1 -o b1 -t b3 b4 -op cut +buildbop r2 -o b2 -t b3 b4 -op cut + +foreach r {r1 r2} { + checkshape $r + checknbshapes $r -vertex 8 -edge 12 -wire 6 -face 6 -shell 1 -solid 1 + checkprops $r -s 1000 -v 2000 +} + +# Common +buildbop r1 -o b1 b2 -t b3 b4 -op common +checkshape r1 +checknbshapes r1 -vertex 32 -edge 48 -wire 24 -face 24 -shell 4 -solid 4 +checkprops r1 -s 2400 -v 4000 + +buildbop r1 -o b1 -t b3 b4 -op common +buildbop r2 -o b2 -t b3 b4 -op common + +checkshape r1 +checkshape r2 + +foreach r {r1 r2} { + checkshape $r + checknbshapes $r -vertex 16 -edge 24 -wire 12 -face 12 -shell 2 -solid 2 + checkprops $r -s 1200 -v 2000 +} diff --git a/tests/boolean/opensolid/A5 b/tests/boolean/opensolid/A5 new file mode 100644 index 0000000000..721baf5a58 --- /dev/null +++ b/tests/boolean/opensolid/A5 @@ -0,0 +1,32 @@ +box b1 10 2 2 +box b2 2 0.5 -3 1 1 8 +box b3 7 0.5 -3 1 1 8 +invert b3 + +bclearobjects +bcleartools +baddobjects b1 +baddtools b2 b3 +bfillds +bbuild r + +buildbop r0 -o b1 -t b2 b3 -op common +buildbop r1 -o b1 -t b2 b3 -op fuse +buildbop r2 -o b1 -t b2 b3 -op cut +buildbop r3 -o b1 -t b2 b3 -op tuc + +checkshape r0 +checkprops r0 -s 110 -v 38 +checknbshapes r0 -vertex 24 -edge 36 -wire 20 -face 16 -shell 2 -solid 2 + +checkshape r1 +checkprops r1 -s 28 -v -6 +checknbshapes r1 -vertex 16 -edge 24 -wire 12 -face 12 -shell 2 -solid 2 + +checkshape r2 +checkprops r2 -s 10 -v 2 +checknbshapes r2 -vertex 8 -edge 12 -wire 6 -face 6 -shell 1 -solid 1 + +checkshape r3 +checkprops r3 -s 164 -v -46 +checknbshapes r3 -vertex 40 -edge 60 -wire 32 -face 28 -shell 3 -solid 3 diff --git a/tests/boolean/opensolid/A6 b/tests/boolean/opensolid/A6 new file mode 100644 index 0000000000..9bd8d89846 --- /dev/null +++ b/tests/boolean/opensolid/A6 @@ -0,0 +1,34 @@ +box b1 10 2 2 +box b2 5 0 0 5 5 5 +box b3 6 0 0 1 1 1 + +bclearobjects +bcleartools +baddobjects b1 +baddtools b2 b3 +bfillds +bbuild r + +buildbop r0 -o b1 -t b2 b3 -op common +buildbop r1 -o b1 -t b2 b3 -op fuse +buildbop r2 -o b1 -t b2 b3 -op cut +buildbop r3 -o b1 -t b2 b3 -op tuc + +foreach r {r0 r1 r2 r3} { + checkshape $r + if {![regexp "OK" [bopcheck $r]]} { + puts "Error: shape is self-interfered" + } +} + +checkprops r0 -s 56 -v 20 +checknbshapes r0 -vertex 16 -edge 25 -wire 12 -face 12 -shell 2 -solid 2 + +checkprops r1 -s 190 -v 145 +checknbshapes r1 -vertex 24 -edge 38 -wire 16 -face 16 -shell 1 -solid 1 + +checkprops r2 -s 48 -v 20 +checknbshapes r2 -vertex 8 -edge 12 -wire 6 -face 6 -shell 1 -solid 1 + +checkprops r3 -s 142 -v 105 +checknbshapes r3 -vertex 12 -edge 18 -wire 8 -face 8 -shell 1 -solid 1 diff --git a/tests/boolean/opensolid/A7 b/tests/boolean/opensolid/A7 new file mode 100644 index 0000000000..860026d762 --- /dev/null +++ b/tests/boolean/opensolid/A7 @@ -0,0 +1,59 @@ +box b1 10 10 10 +box b2 1 1 1 8 8 8 +box b3 -2 3 3 14 4 4 + +bclearobjects +bcleartools +baddobjects b1 b2 b3 +bfillds +bbuild r + +buildbop r0 -o b3 -t b1 b2 -op common +buildbop r1 -o b3 -t b1 b2 -op fuse +buildbop r2 -o b3 -t b1 b2 -op cut +buildbop r3 -o b3 -t b1 b2 -op tuc + +foreach r {r0 r1 r2 r3} { + checkshape $r + if {![regexp "OK" [bopcheck $r]]} { + puts "Error: shape is self-interfered" + } +} + +checkprops r0 -s 256 -v 160 +checknbshapes r0 -vertex 16 -edge 28 -wire 16 -face 16 -shell 3 -solid 3 + +checkprops r1 -s 664 -v 1064 +checknbshapes r1 -vertex 24 -edge 36 -wire 18 -face 16 -shell 1 -solid 1 + +checkprops r2 -s 128 -v 64 +checknbshapes r2 -vertex 16 -edge 24 -wire 12 -face 12 -shell 2 -solid 2 + +checkprops r3 -s 1432 -v 840 +checknbshapes r3 -vertex 32 -edge 52 -wire 28 -face 24 -shell 2 -solid 2 + + + +buildbop r0 -o b2 -t b1 b3 -op common +buildbop r1 -o b2 -t b1 b3 -op fuse +buildbop r2 -o b2 -t b1 b3 -op cut +buildbop r3 -o b2 -t b1 b3 -op tuc + +foreach r {r0 r1 r2 r3} { + checkshape $r + if {![regexp "OK" [bopcheck $r]]} { + puts "Error: shape is self-interfered" + } +} + +checkprops r0 -s 640 -v 512 +checknbshapes r0 -vertex 16 -edge 24 -wire 14 -face 12 -shell 2 -solid 2 + +checkprops r1 -s 664 -v 1064 +checknbshapes r1 -vertex 24 -edge 36 -wire 18 -face 16 -shell 1 -solid 1 + +checkprops r2 -s empty -v empty +checknbshapes r2 -vertex 0 -edge 0 -wire 0 -face 0 -shell 0 -solid 0 + +checkprops r3 -s 1176 -v 552 +checknbshapes r3 -vertex 40 -edge 64 -wire 38 -face 34 -shell 5 -solid 5 diff --git a/tests/boolean/opensolid/A8 b/tests/boolean/opensolid/A8 new file mode 100644 index 0000000000..6d9a1cc18c --- /dev/null +++ b/tests/boolean/opensolid/A8 @@ -0,0 +1,33 @@ +box b1 10 10 10 +box b2 -2 -2 2 7 14 6 +box b3 5 -2 2 7 14 6 +bclearobjects +bcleartools +baddobjects b1 +baddtools b2 b3 +bfillds +bbuild r + +buildbop r0 -o b1 -t b3 b2 -op common +buildbop r1 -o b1 -t b3 b2 -op fuse +buildbop r2 -o b1 -t b3 b2 -op cut +buildbop r3 -o b1 -t b3 b2 -op tuc + +foreach r {r0 r1 r2 r3} { + checkshape $r + if {![regexp "OK" [bopcheck $r]]} { + puts "Error: shape is self-interfered" + } +} + +checkprops r0 -s 560 -v 600 +checknbshapes r0 -vertex 12 -edge 20 -wire 11 -face 11 -shell 2 -solid 2 + +checkprops r1 -s 888 -v 1576 +checknbshapes r1 -vertex 32 -edge 50 -wire 20 -face 20 -shell 1 -solid 1 + +checkprops r2 -s 560 -v 400 +checknbshapes r2 -vertex 20 -edge 30 -wire 14 -face 14 -shell 2 -solid 2 + +checkprops r3 -s 816 -v 576 +checknbshapes r3 -vertex 24 -edge 40 -wire 18 -face 18 -shell 2 -solid 2