diff --git a/src/ApproxInt/ApproxInt_KnotTools.cxx b/src/ApproxInt/ApproxInt_KnotTools.cxx index f155c87977..0fa2043704 100644 --- a/src/ApproxInt/ApproxInt_KnotTools.cxx +++ b/src/ApproxInt/ApproxInt_KnotTools.cxx @@ -201,7 +201,7 @@ void ApproxInt_KnotTools::ComputeKnotInds(const NCollection_LocalArrayShrunkData(aTS11, aTS12, aBB1); // + aPB1->Indices(nV11, nV12); + // aIt2.Initialize(aLPB2); for (; aIt2.More(); aIt2.Next()) { Bnd_Box aBB2; @@ -347,8 +350,15 @@ void BOPAlgo_PaveFiller::PerformEE() aPB1->Range(aT11, aT12); aPB2->Range(aT21, aT22); // + aPB2->Indices(nV21, nV22); + // + bExpressCompute=((nV11==nV21 && nV12==nV22) || + (nV12==nV21 && nV11==nV22)); + // BOPAlgo_EdgeEdge& anEdgeEdge=aVEdgeEdge.Append1(); - // + // + anEdgeEdge.UseQuickCoincidenceCheck(bExpressCompute); + // anEdgeEdge.SetPaveBlock1(aPB1); anEdgeEdge.SetPaveBlock2(aPB2); // diff --git a/src/BOPAlgo/BOPAlgo_PaveFiller_5.cxx b/src/BOPAlgo/BOPAlgo_PaveFiller_5.cxx index f142aa2b26..f7643cbc45 100644 --- a/src/BOPAlgo/BOPAlgo_PaveFiller_5.cxx +++ b/src/BOPAlgo/BOPAlgo_PaveFiller_5.cxx @@ -147,6 +147,8 @@ void BOPAlgo_PaveFiller::PerformEF() } // Standard_Boolean bJustAdd, bV[2]; + Standard_Boolean bV1, bV2, bExpressCompute; + Standard_Integer nV1, nV2; Standard_Integer nE, nF, aDiscretize, i, aNbCPrts, iX, nV[2]; Standard_Integer aNbEdgeFace, k; Standard_Real aTolE, aTolF, aTS1, aTS2, aT1, aT2, aDeflection; @@ -186,6 +188,9 @@ void BOPAlgo_PaveFiller::PerformEF() BOPDS_FaceInfo& aFI=myDS->ChangeFaceInfo(nF); const BOPDS_IndexedMapOfPaveBlock& aMPBF=aFI.PaveBlocksOn(); // + const BOPCol_MapOfInteger& aMVIn=aFI.VerticesIn(); + const BOPCol_MapOfInteger& aMVOn=aFI.VerticesOn(); + // aTolE=BRep_Tool::Tolerance(aE); aTolF=BRep_Tool::Tolerance(aF); // @@ -210,6 +215,11 @@ void BOPAlgo_PaveFiller::PerformEF() continue; } // + aPBR->Indices(nV1, nV2); + bV1=aMVIn.Contains(nV1) || aMVOn.Contains(nV1); + bV2=aMVIn.Contains(nV2) || aMVOn.Contains(nV2); + bExpressCompute=bV1 && bV2; + // BOPAlgo_EdgeFace& aEdgeFace=aVEdgeFace.Append1(); // aEdgeFace.SetIndices(nE, nF); @@ -221,6 +231,7 @@ void BOPAlgo_PaveFiller::PerformEF() aEdgeFace.SetTolF (aTolF); aEdgeFace.SetDiscretize (aDiscretize); aEdgeFace.SetDeflection (aDeflection); + aEdgeFace.UseQuickCoincidenceCheck(bExpressCompute); // IntTools_Range aSR(aTS1, aTS2); IntTools_Range anewSR=aSR; diff --git a/src/IntTools/IntTools_EdgeEdge.cxx b/src/IntTools/IntTools_EdgeEdge.cxx index b27f96ce3f..6d212df5d4 100644 --- a/src/IntTools/IntTools_EdgeEdge.cxx +++ b/src/IntTools/IntTools_EdgeEdge.cxx @@ -211,6 +211,17 @@ void IntTools_EdgeEdge::Perform() return; } // + if (myQuickCoincidenceCheck) { + if (IsCoincident()) { + Standard_Real aT11, aT12, aT21, aT22; + // + myRange1.Range(aT11, aT12); + myRange2.Range(aT21, aT22); + AddSolution(aT11, aT12, aT21, aT22, TopAbs_EDGE); + return; + } + } + // IntTools_SequenceOfRanges aRanges1, aRanges2; // //3.2. Find ranges containig solutions @@ -221,6 +232,47 @@ void IntTools_EdgeEdge::Perform() MergeSolutions(aRanges1, aRanges2, bSplit2); } +//======================================================================= +//function : IsCoincident +//purpose : +//======================================================================= +Standard_Boolean IntTools_EdgeEdge::IsCoincident() +{ + Standard_Integer i, iCnt, aNbSeg, aNbP2; + Standard_Real dT, aT1, aCoeff, aTresh, aD; + Standard_Real aT11, aT12, aT21, aT22; + GeomAPI_ProjectPointOnCurve aProjPC; + gp_Pnt aP1; + // + aTresh=0.5; + aNbSeg=23; + myRange1.Range(aT11, aT12); + myRange2.Range(aT21, aT22); + // + aProjPC.Init(myGeom2, aT21, aT22); + // + dT=(aT12-aT11)/aNbSeg; + // + iCnt=0; + for(i=0; i <= aNbSeg; ++i) { + aT1 = aT11+i*dT; + myGeom1->D0(aT1, aP1); + // + aProjPC.Perform(aP1); + aNbP2=aProjPC.NbPoints(); + if (!aNbP2) { + continue; + } + // + aD=aProjPC.LowerDistance(); + if(aD < myTol) { + ++iCnt; + } + } + // + aCoeff=(Standard_Real)iCnt/((Standard_Real)aNbSeg+1); + return aCoeff > aTresh; +} //======================================================================= //function : FindSolutions //purpose : diff --git a/src/IntTools/IntTools_EdgeEdge.hxx b/src/IntTools/IntTools_EdgeEdge.hxx index bc8c852f64..a9a432a954 100644 --- a/src/IntTools/IntTools_EdgeEdge.hxx +++ b/src/IntTools/IntTools_EdgeEdge.hxx @@ -106,7 +106,15 @@ public: const IntTools_SequenceOfCommonPrts& CommonParts() const; + //! Sets the flag myQuickCoincidenceCheck + void UseQuickCoincidenceCheck (const Standard_Boolean bFlag) { + myQuickCoincidenceCheck=bFlag; + } + //! Returns the flag myQuickCoincidenceCheck + Standard_Boolean IsCoincidenceCheckedQuickly () { + return myQuickCoincidenceCheck; + } protected: @@ -170,6 +178,8 @@ protected: Standard_EXPORT Standard_Boolean IsIntersection (const Standard_Real aT11, const Standard_Real aT12, const Standard_Real aT21, const Standard_Real aT22); + //! Checks if the edges are coincident really. + Standard_EXPORT Standard_Boolean IsCoincident(); TopoDS_Edge myEdge1; TopoDS_Edge myEdge2; @@ -192,6 +202,14 @@ protected: Standard_Integer myErrorStatus; IntTools_SequenceOfCommonPrts myCommonParts; + //! Allows avoiding use Edge-Edge intersection + //! algorithm (i.e. speeding up the Boolean algorithm) + //! if the edges are coincided really. + //! If it is not evidently set of this flag should + //! be avoided (otherwise, the performance of + //! Boolean algorithm will be slower). + Standard_Boolean myQuickCoincidenceCheck; + private: }; diff --git a/src/IntTools/IntTools_EdgeEdge.lxx b/src/IntTools/IntTools_EdgeEdge.lxx index 2508d533bd..398afc7756 100644 --- a/src/IntTools/IntTools_EdgeEdge.lxx +++ b/src/IntTools/IntTools_EdgeEdge.lxx @@ -33,7 +33,8 @@ inline IntTools_EdgeEdge::IntTools_EdgeEdge() myRange1(0., 0.), myRange2(0., 0.), mySwap(Standard_False), - myErrorStatus(0) + myErrorStatus(0), + myQuickCoincidenceCheck(Standard_False) { } //======================================================================= @@ -57,7 +58,8 @@ inline IntTools_EdgeEdge::IntTools_EdgeEdge(const TopoDS_Edge& theEdge1, myRange1(0., 0.), myRange2(0., 0.), mySwap(Standard_False), - myErrorStatus(0) + myErrorStatus(0), + myQuickCoincidenceCheck(Standard_False) { } //======================================================================= @@ -85,7 +87,8 @@ inline IntTools_EdgeEdge::IntTools_EdgeEdge(const TopoDS_Edge& theEdge1, myRange1(aT11, aT12), myRange2(aT21, aT22), mySwap(Standard_False), - myErrorStatus(0) + myErrorStatus(0), + myQuickCoincidenceCheck(Standard_False) { } //======================================================================= diff --git a/src/IntTools/IntTools_EdgeFace.cxx b/src/IntTools/IntTools_EdgeFace.cxx index 90a397c5f7..3d6749d801 100644 --- a/src/IntTools/IntTools_EdgeFace.cxx +++ b/src/IntTools/IntTools_EdgeFace.cxx @@ -76,12 +76,10 @@ static myTolF=1.e-7; myDiscret=30; myEpsT =1e-12; - myEpsNull=1e-12; myDeflection=0.01; myIsDone=Standard_False; myErrorStatus=1; - myParallel=Standard_False; - myPar1=0.; + myQuickCoincidenceCheck=Standard_False; } //======================================================================= //function : SetContext @@ -188,15 +186,6 @@ void IntTools_EdgeFace::SetEpsilonT(const Standard_Real anEpsT) { myEpsT=anEpsT; } -//======================================================================= -//function : SetEpsilonNull -//purpose : -//======================================================================= -void IntTools_EdgeFace::SetEpsilonNull(const Standard_Real anEpsNull) -{ - myEpsNull=anEpsNull; -} - //======================================================================= //function : SetRange //purpose : @@ -214,8 +203,7 @@ void IntTools_EdgeFace::SetRange(const Standard_Real aFirst, //======================================================================= void IntTools_EdgeFace::SetRange(const IntTools_Range& aRange) { - myRange.SetFirst (aRange.First()); - myRange.SetLast (aRange.Last()); + SetRange(aRange.First(), aRange.Last()); } //======================================================================= //function : IsDone @@ -249,7 +237,84 @@ const IntTools_Range& IntTools_EdgeFace::Range() const { return myRange; } +//======================================================================= +//function : IsCoincident +//purpose : +//======================================================================= +Standard_Boolean IntTools_EdgeFace::IsCoincident() +{ + Standard_Integer i, iCnt; + Standard_Real dT, aT, aD, aT1, aT2, aU, aV; + gp_Pnt aP; + TopAbs_State aState; + gp_Pnt2d aP2d; + // + GeomAPI_ProjectPointOnSurf& aProjector=myContext->ProjPS(myFace); + + const Standard_Integer aNbSeg=23; + const Standard_Real aTresh=0.5; + const Standard_Integer aTreshIdxF = RealToInt((aNbSeg+1)*0.25), + aTreshIdxL = RealToInt((aNbSeg+1)*0.75); + const Handle(Geom_Surface) aSurf = BRep_Tool::Surface(myFace); + + aT1=myRange.First(); + aT2=myRange.Last(); + dT=(aT2-aT1)/aNbSeg; + // + Standard_Boolean isClassified = Standard_False; + iCnt=0; + for(i=0; i <= aNbSeg; ++i) { + aT = aT1+i*dT; + aP=myC.Value(aT); + // + aProjector.Perform(aP); + if (!aProjector.IsDone()) { + continue; + } + // + + aD=aProjector.LowerDistance(); + if (aD>myCriteria) { + continue; + } + // + + ++iCnt; + + //We classify only three points: in the begin, in the + //end and in the middle of the edge. + //However, exact middle point (when i == (aNbSeg + 1)/2) + //can be unprojectable. Therefore, it will not be able to + //be classified. Therefore, points with indexes in + //[aTreshIdxF, aTreshIdxL] range are made available + //for classification. + //isClassified == TRUE if MIDDLE point has been choosen and + //classified correctly. + + if(((0 < i) && (i < aTreshIdxF)) || ((aTreshIdxL < i ) && (i < aNbSeg))) + continue; + + if(isClassified && (i != aNbSeg)) + continue; + + aProjector.LowerDistanceParameters(aU, aV); + aP2d.SetX(aU); + aP2d.SetY(aV); + + IntTools_FClass2d& aClass2d=myContext->FClass2d(myFace); + aState = aClass2d.Perform(aP2d); + + if(aState == TopAbs_OUT) + return Standard_False; + + if(i != 0) + isClassified = Standard_True; + } + // + const Standard_Real aCoeff=(Standard_Real)iCnt/((Standard_Real)aNbSeg+1); + return (aCoeff > aTresh); +} //======================================================================= //function : CheckData //purpose : @@ -270,6 +335,7 @@ void IntTools_EdgeFace::CheckData() void IntTools_EdgeFace::Prepare() { Standard_Integer pri; + Standard_Real aTmin, aTmax; IntTools_CArray1OfReal aPars; // @@ -280,15 +346,15 @@ void IntTools_EdgeFace::Prepare() // // 2.Prepare myCriteria if (aCurveType==GeomAbs_BSplineCurve|| - aCurveType==GeomAbs_BezierCurve) { + aCurveType==GeomAbs_BezierCurve) { myCriteria=1.5*myTolE+myTolF; } else { myCriteria = myTolE + myTolF + Precision::Confusion(); } // 2.a myTmin, myTmax - myTmin=myRange.First(); - myTmax=myRange.Last(); + aTmin=myRange.First(); + aTmax=myRange.Last(); // 2.b myFClass2d myS.Initialize (myFace,Standard_True); myFClass2d.Init(myFace, 1.e-6); @@ -298,7 +364,7 @@ void IntTools_EdgeFace::Prepare() // // // 3.Prepare myPars - pri = IntTools::PrepareArgs(myC, myTmax, myTmin, + pri = IntTools::PrepareArgs(myC, aTmax, aTmin, myDiscret, myDeflection, aPars); if (pri) { myErrorStatus=6; @@ -543,300 +609,6 @@ Standard_Boolean IntTools_EdgeFace::IsEqDistance return !bRetFlag; } // -//======================================================================= -//function : PrepareArgsFuncArrays -//purpose : Obtain -// myFuncArray and myArgsArray for the interval [ta, tb] -//======================================================================= -void IntTools_EdgeFace::PrepareArgsFuncArrays(const Standard_Real ta, - const Standard_Real tb) -{ - IntTools_CArray1OfReal anArgs, aFunc; - Standard_Integer i, aNb, pri; - Standard_Real t, f, f1; - // - // Prepare values of arguments for the interval [ta, tb] - pri=IntTools::PrepareArgs (myC, tb, ta, myDiscret, myDeflection, anArgs); - - if (pri) { - myErrorStatus=8; - return; - } - //... - aNb=anArgs.Length(); - - if (!aNb){ - myErrorStatus=9; - return; - } - // - // Prepare values of functions for the interval [ta, tb] - aFunc.Resize(aNb); - for (i=0; i0.) { - a=x0; r=y; - } - } -} -//======================================================================= -//function : FindGoldRoot -//purpose : [private] -//======================================================================= -Standard_Real IntTools_EdgeFace::FindGoldRoot - (const Standard_Real tA, - const Standard_Real tB, - const Standard_Real coeff) -{ - Standard_Real gs=0.61803399; - Standard_Real a, b, xp, xl, yp, yl; - - a=tA; b=tB; - - xp=a+(b-a)*gs; - xl=b-(b-a)*gs; - yp=coeff*DistanceFunction(xp); - yl=coeff*DistanceFunction(xl); - - - for(;;) { - - if (fabs(b-a) < myEpsT) { - return .5*(b+a); - } - - if (yp < yl) { - a=xl; - xl=xp; - xp=a+(b-a)*gs; - yp=coeff*DistanceFunction(xp); - } - - else { - b=xp; - xp=xl; - yp=yl; - xl=b-(b-a)*gs; - yl=coeff*DistanceFunction(xl); - } - } -} - //======================================================================= //function : MakeType //purpose : @@ -893,183 +665,6 @@ Standard_Integer IntTools_EdgeFace::MakeType } -//======================================================================= -//function : IsIntersection -//purpose : -//======================================================================= -void IntTools_EdgeFace::IsIntersection (const Standard_Real ta, - const Standard_Real tb) -{ - IntTools_CArray1OfReal anArgs, aFunc; - Standard_Integer i, aNb, aCnt=0; - // - Standard_Integer aCntIncreasing=1, aCntDecreasing=1; - Standard_Real t, f, f1; - // - // Prepare values of arguments for the interval [ta, tb] - IntTools::PrepareArgs (myC, tb, ta, myDiscret, myDeflection, anArgs); - aNb=anArgs.Length(); - - aFunc.Resize(aNb); - for (i=0; iaFunc(i-1)) { - aCntIncreasing++; - } - if (aFunc(i) #include #include -#include #include #include class IntTools_Context; @@ -101,11 +100,6 @@ public: //! Initializes algorithm by parameter tolerance Standard_EXPORT void SetEpsilonT (const Standard_Real anEpsT); - - //! Initializes algorithm by distance tolerance - Standard_EXPORT void SetEpsilonNull (const Standard_Real anEpsNull); - - //! Sets boundaries for edge. //! The algorithm processes edge inside these boundaries. Standard_EXPORT void SetRange (const IntTools_Range& aRange); @@ -149,15 +143,20 @@ public: //! Returns boundaries for edge Standard_EXPORT const IntTools_Range& Range() const; - - Standard_EXPORT static Standard_Boolean IsEqDistance (const gp_Pnt& aP, const BRepAdaptor_Surface& aS, const Standard_Real aT, Standard_Real& aD); + //! Sets the flag myQuickCoincidenceCheck + void UseQuickCoincidenceCheck(const Standard_Boolean bFlag) { + myQuickCoincidenceCheck=bFlag; + } + //! Returns the flag myQuickCoincidenceCheck + Standard_Boolean IsCoincidenceCheckedQuickly () { + return myQuickCoincidenceCheck; + } protected: - - + Standard_EXPORT static Standard_Boolean IsEqDistance (const gp_Pnt& aP, const BRepAdaptor_Surface& aS, const Standard_Real aT, Standard_Real& aD); Standard_EXPORT void CheckData(); Standard_EXPORT void Prepare(); @@ -168,28 +167,14 @@ protected: Standard_EXPORT Standard_Real DistanceFunction (const Standard_Real t); - Standard_EXPORT Standard_Real DerivativeFunction (const Standard_Real t); - - Standard_EXPORT void PrepareArgsFuncArrays (const Standard_Real t1, const Standard_Real t2); - - Standard_EXPORT void AddDerivativePoints (const IntTools_CArray1OfReal& t, const IntTools_CArray1OfReal& f); - - Standard_EXPORT Standard_Real FindSimpleRoot (const Standard_Integer IP, const Standard_Real ta, const Standard_Real tb, const Standard_Real fA); - - Standard_EXPORT Standard_Real FindGoldRoot (const Standard_Real ta, const Standard_Real tb, const Standard_Real coeff); - Standard_EXPORT Standard_Integer MakeType (IntTools_CommonPrt& aCP); - - Standard_EXPORT void IsIntersection (const Standard_Real ta, const Standard_Real tb); - - Standard_EXPORT void FindDerivativeRoot (const IntTools_CArray1OfReal& t, const IntTools_CArray1OfReal& f); - - Standard_EXPORT void RemoveIdenticalRoots(); - + Standard_EXPORT Standard_Boolean CheckTouch (const IntTools_CommonPrt& aCP, Standard_Real& aTX); Standard_EXPORT Standard_Boolean CheckTouchVertex (const IntTools_CommonPrt& aCP, Standard_Real& aTX); + //! Checks if the edge is in the face really. + Standard_EXPORT Standard_Boolean IsCoincident(); @@ -203,11 +188,8 @@ private: Standard_Real myTolF; Standard_Integer myDiscret; Standard_Real myEpsT; - Standard_Real myEpsNull; Standard_Real myDeflection; BRepAdaptor_Curve myC; - Standard_Real myTmin; - Standard_Real myTmax; BRepAdaptor_Surface myS; Standard_Real myCriteria; Standard_Boolean myIsDone; @@ -215,15 +197,16 @@ private: Handle(IntTools_Context) myContext; IntTools_SequenceOfRanges myProjectableRanges; IntTools_FClass2d myFClass2d; - IntTools_CArray1OfReal myFuncArray; - IntTools_CArray1OfReal myArgsArray; - IntTools_SequenceOfRoots mySequenceOfRoots; IntTools_SequenceOfCommonPrts mySeqOfCommonPrts; - Standard_Real myPar1; - Standard_Boolean myParallel; IntTools_Range myRange; - + //! Allows avoiding use Edge-Face intersection + //! algorithm (i.e. speeding up the Boolean algorithm) + //! if the edges are coincided really. + //! If it is not evidently set of this flag should + //! be avoided (otherwise, the performance of + //! Boolean algorithm will be slower). + Standard_Boolean myQuickCoincidenceCheck; }; diff --git a/src/IntTools/IntTools_FaceFace.cxx b/src/IntTools/IntTools_FaceFace.cxx index bbd68793f0..e6ead52fca 100644 --- a/src/IntTools/IntTools_FaceFace.cxx +++ b/src/IntTools/IntTools_FaceFace.cxx @@ -888,10 +888,27 @@ void IntTools_FaceFace::MakeCurve(const Standard_Integer Index, } L = aWLine; - // - //if(!myListOfPnts.IsEmpty()) { - // bAvoidLineConstructor = Standard_True; - //} +#ifdef INTTOOLS_FACEFACE_DEBUG + if(!myListOfPnts.IsEmpty()) { + char aBuff[10000]; + const IntSurf_PntOn2S& aPt = myListOfPnts.First(); + Standard_Real u1, v1, u2, v2; + aPt.Parameters(u1, v1, u2, v2); + + Sprintf(aBuff,"bopcurves -2d"); + IntSurf_ListIteratorOfListOfPntOn2S IterLOP1(myListOfPnts); + for(;IterLOP1.More(); IterLOP1.Next()) + { + const IntSurf_PntOn2S& aPt = IterLOP1.Value(); + Standard_Real u1, v1, u2, v2; + aPt.Parameters(u1, v1, u2, v2); + + Sprintf(aBuff, "%s -p %+10.20f %+10.20f %+10.20f %+10.20f", aBuff, u1, v1, u2, v2); + } + + cout << aBuff << endl; + } +#endif Standard_Integer nbp = aWLine->NbPnts(); const IntSurf_PntOn2S& p1 = aWLine->Point(1); diff --git a/tests/bugs/modalg_1/buc60462_1 b/tests/bugs/modalg_1/buc60462_1 index 039cd8e7a2..1938c3f76a 100755 --- a/tests/bugs/modalg_1/buc60462_1 +++ b/tests/bugs/modalg_1/buc60462_1 @@ -1,4 +1,4 @@ -#puts "TODO OCC12345 ALL: Error : The length of result shape is" +#puts "TODO OCC27024 ALL: Error : The length of result shape is" puts "=============" puts "BUC60462" diff --git a/tests/bugs/modalg_1/buc60462_2 b/tests/bugs/modalg_1/buc60462_2 index badc77adc4..3465d6599e 100755 --- a/tests/bugs/modalg_1/buc60462_2 +++ b/tests/bugs/modalg_1/buc60462_2 @@ -1,5 +1,6 @@ -puts "TODO ?OCC26717 ALL: Faulty shapes in variables faulty_1 to faulty_" -puts "TODO OCC26717 ALL: Error : operation bfuse is WRONG because number of SOLID" +#puts "TODO OCC27024 ALL: Faulty shapes in variables faulty_1 to faulty_" +puts "TODO OCC27024 ALL: Tcl Exception: result is not a topological shape!!!" +puts "TODO OCC27024 All:TEST INCOMPLETE" puts "==========" puts "BUC60462" diff --git a/tests/bugs/modalg_6/bug26132 b/tests/bugs/modalg_6/bug26132 new file mode 100755 index 0000000000..5793dc0632 --- /dev/null +++ b/tests/bugs/modalg_6/bug26132 @@ -0,0 +1,31 @@ +puts "============" +puts "OCC26132" +puts "============" +puts "" +###################################################### +# Invalid result of boolean operation +###################################################### + +restore [locate_data_file bug26132_shape.brep] c + +explode c + +bcommon result c_1 c_2 + +set square 947.358 + +set nbshapes_expected " +Number of shapes in shape + VERTEX : 6 + EDGE : 11 + WIRE : 8 + FACE : 8 + SHELL : 2 + SOLID : 2 + COMPSOLID : 0 + COMPOUND : 1 + SHAPE : 38 +" +checknbshapes result -ref ${nbshapes_expected} -t -m "Boolean operations common" + +set 3dviewer 1