diff --git a/src/Select3D/FILES b/src/Select3D/FILES index 24e8443be9..d779a8c322 100755 --- a/src/Select3D/FILES +++ b/src/Select3D/FILES @@ -29,6 +29,8 @@ Select3D_SensitiveSegment.cxx Select3D_SensitiveSegment.hxx Select3D_SensitiveSet.cxx Select3D_SensitiveSet.hxx +Select3D_SensitiveSphere.cxx +Select3D_SensitiveSphere.hxx Select3D_SensitiveTriangle.cxx Select3D_SensitiveTriangle.hxx Select3D_SensitiveTriangulation.cxx diff --git a/src/Select3D/Select3D_SensitiveSphere.cxx b/src/Select3D/Select3D_SensitiveSphere.cxx new file mode 100644 index 0000000000..b289ca3fb3 --- /dev/null +++ b/src/Select3D/Select3D_SensitiveSphere.cxx @@ -0,0 +1,80 @@ +// Created on: 2021-03-04 +// Created by: Maria KRYLOVA +// Copyright (c) 2021 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#include + +IMPLEMENT_STANDARD_RTTIEXT(Select3D_SensitiveSphere, Select3D_SensitiveEntity) + +// ================================================== +// Function: Select3D_SensitiveSphere +// Purpose : +// ================================================== +Select3D_SensitiveSphere::Select3D_SensitiveSphere (const Handle(SelectMgr_EntityOwner)& theOwnerId, + const gp_Pnt& theCenter, + const Standard_Real theRadius) +: Select3D_SensitiveEntity (theOwnerId), + myCenter (theCenter), + myRadius (theRadius) +{ +} + +// ================================================== +// Function: Mathes +// Purpose : +// ================================================== +Standard_Boolean Select3D_SensitiveSphere::Matches (SelectBasics_SelectingVolumeManager& theMgr, + SelectBasics_PickResult& thePickResult) +{ + if (theMgr.GetActiveSelectionType() != SelectMgr_SelectionType_Point) + { + if (!theMgr.IsOverlapAllowed()) + { + Standard_Boolean isInside = Standard_True; + return theMgr.OverlapsSphere (myCenter, myRadius, &isInside) && isInside; + } + else + { + return theMgr.OverlapsSphere (myCenter, myRadius, NULL); + } + } + if (!theMgr.OverlapsSphere (myCenter, myRadius, thePickResult)) + { + return Standard_False; + } + + thePickResult.SetDistToGeomCenter (theMgr.DistToGeometryCenter (myCenter)); + return Standard_True; +} + +// ================================================== +// Function: GetConnected +// Purpose : +// ================================================== +Handle(Select3D_SensitiveEntity) Select3D_SensitiveSphere::GetConnected() +{ + Handle(Select3D_SensitiveEntity) aNewEntity = new Select3D_SensitiveSphere (myOwnerId, myCenter, myRadius); + return aNewEntity; +} + +// ================================================== +// Function: BoundingBox +// Purpose : +// ================================================== +Select3D_BndBox3d Select3D_SensitiveSphere::BoundingBox() +{ + const SelectMgr_Vec3 aMinPnt = SelectMgr_Vec3 (myCenter.X() - myRadius, myCenter.Y() - myRadius, myCenter.Z() - myRadius); + const SelectMgr_Vec3 aMaxPnt = SelectMgr_Vec3 (myCenter.X() + myRadius, myCenter.Y() + myRadius, myCenter.Z() + myRadius); + return Select3D_BndBox3d (aMinPnt, aMaxPnt); +} diff --git a/src/Select3D/Select3D_SensitiveSphere.hxx b/src/Select3D/Select3D_SensitiveSphere.hxx new file mode 100644 index 0000000000..9a976c697a --- /dev/null +++ b/src/Select3D/Select3D_SensitiveSphere.hxx @@ -0,0 +1,63 @@ +// Created on: 2021-03-04 +// Created by: Maria KRYLOVA +// Copyright (c) 2021 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#ifndef _Select3D_SensitiveSphere_HeaderFile +#define _Select3D_SensitiveSphere_HeaderFile + +#include + +//! A framework to define selection by a sensitive sphere. +class Select3D_SensitiveSphere : public Select3D_SensitiveEntity +{ + DEFINE_STANDARD_RTTIEXT(Select3D_SensitiveSphere, Select3D_SensitiveEntity) +public: + + //! Constructs a sensitive sphere object defined by the owner theOwnerId, + //! the center of the sphere and it's radius. + Standard_EXPORT Select3D_SensitiveSphere (const Handle(SelectMgr_EntityOwner)& theOwnerId, + const gp_Pnt& theCenter, + const Standard_Real theRadius); + + //! Returns the radius of the sphere + Standard_Real Radius() const { return myRadius; } + +public: + + //! Checks whether the sphere overlaps current selecting volume + Standard_EXPORT virtual Standard_Boolean Matches (SelectBasics_SelectingVolumeManager& theMgr, + SelectBasics_PickResult& thePickResult) Standard_OVERRIDE; + + //! Returns the copy of this + Standard_EXPORT virtual Handle(Select3D_SensitiveEntity) GetConnected() Standard_OVERRIDE; + + //! Returns bounding box of the sphere. + //! If location transformation is set, it will be applied + Standard_EXPORT virtual Select3D_BndBox3d BoundingBox() Standard_OVERRIDE; + + //! Always returns Standard_False + virtual Standard_Boolean ToBuildBVH() const Standard_OVERRIDE { return Standard_False; } + + //! Returns the amount of points + virtual Standard_Integer NbSubElements() const Standard_OVERRIDE { return 1; } + + //! Returns center of the sphere with transformation applied + virtual gp_Pnt CenterOfGeometry() const Standard_OVERRIDE { return myCenter; }; + +protected: + gp_Pnt myCenter; + Standard_Real myRadius; +}; + +#endif // _Select3D_SensitiveSphere_HeaderFile diff --git a/src/SelectBasics/SelectBasics_SelectingVolumeManager.hxx b/src/SelectBasics/SelectBasics_SelectingVolumeManager.hxx index 68aaaab90c..324d18958e 100644 --- a/src/SelectBasics/SelectBasics_SelectingVolumeManager.hxx +++ b/src/SelectBasics/SelectBasics_SelectingVolumeManager.hxx @@ -85,6 +85,18 @@ public: Standard_Integer theSensType, SelectBasics_PickResult& thePickResult) const = 0; + //! Returns true if selecting volume is overlapped by sphere with center theCenter + //! and radius theRadius + virtual Standard_Boolean OverlapsSphere (const gp_Pnt& theCenter, + const Standard_Real theRadius, + SelectBasics_PickResult& thePickResult) const = 0; + + //! Returns true if selecting volume is overlapped by sphere with center theCenter + //! and radius theRadius + virtual Standard_Boolean OverlapsSphere (const gp_Pnt& theCenter, + const Standard_Real theRadius, + Standard_Boolean* theInside = NULL) const = 0; + public: //! Calculates distance from 3d projection of user-defined selection point diff --git a/src/SelectMgr/SelectMgr_AxisIntersector.cxx b/src/SelectMgr/SelectMgr_AxisIntersector.cxx index c6514cebea..7cc2e9f2a2 100644 --- a/src/SelectMgr/SelectMgr_AxisIntersector.cxx +++ b/src/SelectMgr/SelectMgr_AxisIntersector.cxx @@ -495,6 +495,58 @@ Standard_Boolean SelectMgr_AxisIntersector::OverlapsTriangle (const gp_Pnt& theP && !theClipRange.IsClipped (thePickResult.Depth()); } +//======================================================================= +// function : OverlapsSphere +// purpose : +//======================================================================= +Standard_Boolean SelectMgr_AxisIntersector::OverlapsSphere (const gp_Pnt& theCenter, + const Standard_Real theRadius, + Standard_Boolean* theInside) const +{ + Standard_ASSERT_RAISE (mySelectionType == SelectMgr_SelectionType_Point, + "Error! SelectMgr_AxisIntersector::Overlaps() should be called after selection axis initialization"); + (void )theInside; + Standard_Real aTimeEnter = 0.0, aTimeLeave = 0.0; + if (!RaySphereIntersection (theCenter, theRadius, myAxis.Location(), myAxis.Direction(), aTimeEnter, aTimeLeave)) + { + return Standard_False; + } + if (theInside != NULL) + { + *theInside &= (aTimeEnter >= 0.0); + } + return Standard_True; +} + +//======================================================================= +// function : OverlapsSphere +// purpose : +//======================================================================= +Standard_Boolean SelectMgr_AxisIntersector::OverlapsSphere (const gp_Pnt& theCenter, + const Standard_Real theRadius, + const SelectMgr_ViewClipRange& theClipRange, + SelectBasics_PickResult& thePickResult) const +{ + Standard_ASSERT_RAISE (mySelectionType == SelectMgr_SelectionType_Point, + "Error! SelectMgr_AxisIntersector::Overlaps() should be called after selection axis initialization"); + Standard_Real aTimeEnter = 0.0, aTimeLeave = 0.0; + if (!RaySphereIntersection (theCenter, theRadius, myAxis.Location(), myAxis.Direction(), aTimeEnter, aTimeLeave)) + { + return Standard_False; + } + + Standard_Real aDepth = 0.0; + Bnd_Range aRange (Max (aTimeEnter, 0.0), aTimeLeave); + aRange.GetMin (aDepth); + if (!theClipRange.GetNearestDepth (aRange, aDepth)) + { + return Standard_False; + } + + thePickResult.SetDepth (aDepth); + return Standard_True; +} + //======================================================================= // function : GetNearPnt // purpose : diff --git a/src/SelectMgr/SelectMgr_AxisIntersector.hxx b/src/SelectMgr/SelectMgr_AxisIntersector.hxx index 9b2b5da961..5de38f3646 100644 --- a/src/SelectMgr/SelectMgr_AxisIntersector.hxx +++ b/src/SelectMgr/SelectMgr_AxisIntersector.hxx @@ -98,6 +98,19 @@ public: const SelectMgr_ViewClipRange& theClipRange, SelectBasics_PickResult& thePickResult) const Standard_OVERRIDE; + //! Intersection test between defined axis and given sphere with center theCenter + //! and radius theRadius + Standard_EXPORT virtual Standard_Boolean OverlapsSphere (const gp_Pnt& theCenter, + const Standard_Real theRadius, + Standard_Boolean* theInside = NULL) const Standard_OVERRIDE; + + //! Intersection test between defined axis and given sphere with center theCenter + //! and radius theRadius + Standard_EXPORT virtual Standard_Boolean OverlapsSphere (const gp_Pnt& theCenter, + const Standard_Real theRadius, + const SelectMgr_ViewClipRange& theClipRange, + SelectBasics_PickResult& thePickResult) const Standard_OVERRIDE; + public: //! Measures distance between start axis point and given point theCOG. diff --git a/src/SelectMgr/SelectMgr_BaseFrustum.cxx b/src/SelectMgr/SelectMgr_BaseFrustum.cxx index 51036600a0..f7cca23b14 100644 --- a/src/SelectMgr/SelectMgr_BaseFrustum.cxx +++ b/src/SelectMgr/SelectMgr_BaseFrustum.cxx @@ -99,6 +99,49 @@ void SelectMgr_BaseFrustum::SetBuilder (const Handle(SelectMgr_FrustumBuilder)& } } +//======================================================================= +// function : IsBoundariesIntersectSphere +// purpose : +//======================================================================= +Standard_Boolean SelectMgr_BaseFrustum::IsBoundaryIntersectSphere (const gp_Pnt& theCenter, + const Standard_Real theRadius, + const gp_Dir& thePlaneNormal, + const TColgp_Array1OfPnt& theBoundaries, + Standard_Boolean& theBoundaryInside) const +{ + for (Standard_Integer anIdx = theBoundaries.Lower(); anIdx < theBoundaries.Upper(); ++anIdx) + { + const Standard_Integer aNextIdx = ((anIdx + 1) == theBoundaries.Upper()) ? theBoundaries.Lower() : (anIdx + 1); + const gp_Pnt aPnt1 = theBoundaries.Value (anIdx); + const gp_Pnt aPnt2 = theBoundaries.Value (aNextIdx); + if (aPnt1.Distance (aPnt2) < Precision::Confusion()) + { + continue; + } + + // Projections of the points on the plane + const gp_Pnt aPntProj1 = aPnt1.XYZ() - thePlaneNormal.XYZ() * aPnt1.XYZ().Dot (thePlaneNormal.XYZ()); + const gp_Pnt aPntProj2 = aPnt2.XYZ() - thePlaneNormal.XYZ() * aPnt2.XYZ().Dot (thePlaneNormal.XYZ()); + if (aPntProj1.Distance (theCenter) < theRadius || aPntProj2.Distance (theCenter) < theRadius) // polygon intersects the sphere + { + theBoundaryInside = Standard_True; + return Standard_True; + } + + gp_Dir aRayDir (gp_Vec (aPntProj1, aPntProj2)); + Standard_Real aTimeEnter = 0.0, aTimeLeave = 0.0; + if (RaySphereIntersection (theCenter, theRadius, aPntProj1, aRayDir, aTimeEnter, aTimeLeave)) + { + if ((aTimeEnter > 0 && aTimeEnter < aPntProj1.Distance (aPntProj2)) + || (aTimeLeave > 0 && aTimeLeave < aPntProj1.Distance (aPntProj2))) + { + return Standard_True; // polygon crosses the sphere + } + } + } + return Standard_False; +} + //======================================================================= //function : DumpJson //purpose : diff --git a/src/SelectMgr/SelectMgr_BaseFrustum.hxx b/src/SelectMgr/SelectMgr_BaseFrustum.hxx index 0c9c3b2b35..ea428a1df4 100644 --- a/src/SelectMgr/SelectMgr_BaseFrustum.hxx +++ b/src/SelectMgr/SelectMgr_BaseFrustum.hxx @@ -54,6 +54,13 @@ public: const Standard_Real theWidth, const Standard_Real theHeight) Standard_OVERRIDE; + //! Checks whether the boundary of the current volume selection intersects with a sphere or are there it's boundaries lying inside the sphere + Standard_EXPORT Standard_Boolean IsBoundaryIntersectSphere (const gp_Pnt& theCenter, + const Standard_Real theRadius, + const gp_Dir& thePlaneNormal, + const TColgp_Array1OfPnt& theBoundaries, + Standard_Boolean& theBoundaryInside) const; + //! Dumps the content of me into the stream Standard_EXPORT virtual void DumpJson (Standard_OStream& theOStream, Standard_Integer theDepth = -1) const Standard_OVERRIDE; diff --git a/src/SelectMgr/SelectMgr_BaseIntersector.cxx b/src/SelectMgr/SelectMgr_BaseIntersector.cxx index 53db9740d1..6b7e81cd5d 100644 --- a/src/SelectMgr/SelectMgr_BaseIntersector.cxx +++ b/src/SelectMgr/SelectMgr_BaseIntersector.cxx @@ -122,6 +122,48 @@ const gp_Pnt2d& SelectMgr_BaseIntersector::GetMousePosition() const return aPnt; } +//======================================================================= +// function : RaySphereIntersection +// purpose : +//======================================================================= +Standard_Boolean SelectMgr_BaseIntersector::RaySphereIntersection (const gp_Pnt& theCenter, + const Standard_Real theRadius, + const gp_Pnt& theLoc, + const gp_Dir& theRayDir, + Standard_Real& theTimeEnter, + Standard_Real& theTimeLeave) const +{ + // to find the intersection of the ray (theLoc, theRayDir) and sphere with theCenter(x0, y0, z0) and theRadius(R), you need to solve the equation + // (x' - x0)^2 + (y' - y0)^2 + (z' - z0)^2 = R^2, where P(x',y',z') = theLoc(x,y,z) + theRayDir(vx,vy,vz) * T + // at the end of solving, you receive a square equation with respect to T + // T^2 * (vx^2 + vy^2 + vz^2) + 2 * T * (vx*(x - x0) + vy*(y - y0) + vz*(z - z0)) + ((x-x0)^2 + (y-y0)^2 + (z-z0)^2 -R^2) = 0 (= A*T^2 + K*T + C) + // and find T by discriminant D = K^2 - A*C + const Standard_Real anA = theRayDir.Dot (theRayDir); + const Standard_Real aK = theRayDir.X() * (theLoc.X() - theCenter.X()) + + theRayDir.Y() * (theLoc.Y() - theCenter.Y()) + + theRayDir.Z() * (theLoc.Z() - theCenter.Z()); + const Standard_Real aC = theLoc.Distance (theCenter) * theLoc.Distance (theCenter) - theRadius * theRadius; + const Standard_Real aDiscr = aK * aK - anA * aC; + if (aDiscr < 0) + { + return Standard_False; + } + + const Standard_Real aTime1 = (-aK - Sqrt (aDiscr)) / anA; + const Standard_Real aTime2 = (-aK + Sqrt (aDiscr)) / anA; + if (Abs (aTime1) < Abs (aTime2)) + { + theTimeEnter = aTime1; + theTimeLeave = aTime2; + } + else + { + theTimeEnter = aTime2; + theTimeLeave = aTime1; + } + return Standard_True; +} + //======================================================================= // function : DistToGeometryCenter // purpose : diff --git a/src/SelectMgr/SelectMgr_BaseIntersector.hxx b/src/SelectMgr/SelectMgr_BaseIntersector.hxx index e69f19bd62..9013040497 100644 --- a/src/SelectMgr/SelectMgr_BaseIntersector.hxx +++ b/src/SelectMgr/SelectMgr_BaseIntersector.hxx @@ -168,6 +168,19 @@ public: const SelectMgr_ViewClipRange& theClipRange, SelectBasics_PickResult& thePickResult) const = 0; + //! Returns true if selecting volume is overlapped by sphere with center theCenter + //! and radius theRadius + Standard_EXPORT virtual Standard_Boolean OverlapsSphere (const gp_Pnt& theCenter, + const Standard_Real theRadius, + Standard_Boolean* theInside = NULL) const = 0; + + //! Returns true if selecting volume is overlapped by sphere with center theCenter + //! and radius theRadius + Standard_EXPORT virtual Standard_Boolean OverlapsSphere (const gp_Pnt& theCenter, + const Standard_Real theRadius, + const SelectMgr_ViewClipRange& theClipRange, + SelectBasics_PickResult& thePickResult) const = 0; + public: //! Measures distance between 3d projection of user-picked @@ -184,6 +197,15 @@ public: //! Dumps the content of me into the stream Standard_EXPORT virtual void DumpJson (Standard_OStream& theOStream, Standard_Integer theDepth = -1) const; + //! Checks whether the ray that starts at the point theLoc and directs with the direction theRayDir intersects + //! with the sphere with center at theCenter and radius TheRadius + Standard_EXPORT virtual Standard_Boolean RaySphereIntersection (const gp_Pnt& theCenter, + const Standard_Real theRadius, + const gp_Pnt& theLoc, + const gp_Dir& theRayDir, + Standard_Real& theTimeEnter, + Standard_Real& theTimeLeave) const; + DEFINE_STANDARD_RTTIEXT(SelectMgr_BaseIntersector,Standard_Transient) protected: diff --git a/src/SelectMgr/SelectMgr_Frustum.hxx b/src/SelectMgr/SelectMgr_Frustum.hxx index 8b49b30ed0..5b635b9acb 100644 --- a/src/SelectMgr/SelectMgr_Frustum.hxx +++ b/src/SelectMgr/SelectMgr_Frustum.hxx @@ -88,6 +88,11 @@ protected: const gp_Pnt& thePnt3, gp_Vec& theNormal) const; + //! Intersection test between defined volume and given sphere + Standard_Boolean hasSphereOverlap (const gp_Pnt& thePnt1, + const Standard_Real theRadius, + Standard_Boolean* theInside = NULL) const; + private: //! Checks if AABB and frustum are separated along the given axis diff --git a/src/SelectMgr/SelectMgr_Frustum.lxx b/src/SelectMgr/SelectMgr_Frustum.lxx index c5e38a2d39..4357516dea 100644 --- a/src/SelectMgr/SelectMgr_Frustum.lxx +++ b/src/SelectMgr/SelectMgr_Frustum.lxx @@ -459,10 +459,63 @@ Standard_Boolean SelectMgr_Frustum::hasTriangleOverlap (const gp_Pnt& thePnt1 } } } - return Standard_True; } +// ======================================================================= +// function : hasSphereOverlap +// purpose : +// ======================================================================= +template +Standard_Boolean SelectMgr_Frustum::hasSphereOverlap (const gp_Pnt& thePnt, + const Standard_Real theRadius, + Standard_Boolean* theInside) const +{ + Standard_Boolean isOverlapFull = Standard_True; + const Standard_Integer anIncFactor = (Camera()->IsOrthographic() && N == 4) ? 2 : 1; + for (Standard_Integer aPlaneIdx = 0; aPlaneIdx < N; aPlaneIdx += anIncFactor) + { + const gp_XYZ& aPlane = myPlanes[aPlaneIdx].XYZ(); + const Standard_Real aNormVecLen = Sqrt (aPlane.Dot (aPlane)); + const Standard_Real aCenterProj = aPlane.Dot (thePnt.XYZ()) / aNormVecLen; + const Standard_Real aMaxDist = myMaxVertsProjections[aPlaneIdx] / aNormVecLen; + const Standard_Real aMinDist = myMinVertsProjections[aPlaneIdx] / aNormVecLen; + if (aCenterProj > (aMaxDist + theRadius) + || aCenterProj < (aMinDist - theRadius)) + { + return Standard_False; // fully separated + } + else if (theInside) + { + *theInside &= aCenterProj >= (aMinDist + theRadius) + && aCenterProj <= (aMaxDist - theRadius); + } + isOverlapFull &= aCenterProj >= (aMinDist + theRadius) + && aCenterProj <= (aMaxDist - theRadius); + } + if (theInside || isOverlapFull) + { + return Standard_True; + } + const gp_Vec aVecPlane1 (myVertices[0], myVertices[2]); + const gp_Vec aVecPlane2 (myVertices[0], myVertices[2 * N - 2]); + if (aVecPlane1.IsParallel (aVecPlane2, Precision::Angular())) + { + return Standard_False; + } + const gp_Dir aNorm (aVecPlane1.Crossed (aVecPlane2)); + gp_Pnt aBoundariesCArr[5]; + NCollection_Array1 aBoundaries (aBoundariesCArr[0], 0, N); + for (Standard_Integer anIdx = 0; anIdx < N * 2; anIdx += 2) + { + aBoundaries.SetValue (anIdx / 2, myVertices[anIdx]); + } + // distance from point(x,y,z) to plane(A,B,C,D) d = | Ax + By + Cz + D | / sqrt (A^2 + B^2 + C^2) = aPnt.Dot (Norm) / 1 + const gp_Pnt aCenterProj = thePnt.XYZ() - aNorm.XYZ() * thePnt.XYZ().Dot (aNorm.XYZ()); + Standard_Boolean isBoundaryInside = Standard_False; + return IsBoundaryIntersectSphere (aCenterProj, theRadius, aNorm, aBoundaries, isBoundaryInside); +} + //======================================================================= //function : DumpJson //purpose : diff --git a/src/SelectMgr/SelectMgr_RectangularFrustum.cxx b/src/SelectMgr/SelectMgr_RectangularFrustum.cxx index da122c385c..c47f770bf3 100644 --- a/src/SelectMgr/SelectMgr_RectangularFrustum.cxx +++ b/src/SelectMgr/SelectMgr_RectangularFrustum.cxx @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -752,6 +753,53 @@ const gp_Pnt2d& SelectMgr_RectangularFrustum::GetMousePosition() const return base_type::GetMousePosition(); } +// ======================================================================= +// function : OverlapsSphere +// purpose : +// ======================================================================= +Standard_Boolean SelectMgr_RectangularFrustum::OverlapsSphere (const gp_Pnt& theCenter, + const Standard_Real theRadius, + const SelectMgr_ViewClipRange& theClipRange, + SelectBasics_PickResult& thePickResult) const +{ + Standard_ASSERT_RAISE (mySelectionType == SelectMgr_SelectionType_Point || mySelectionType == SelectMgr_SelectionType_Box, + "Error! SelectMgr_RectangularFrustum::Overlaps() should be called after selection frustum initialization"); + if (!hasSphereOverlap (theCenter, theRadius)) + { + return Standard_False; + } + + Standard_Real aTimeEnter = 0.0, aTimeLeave = 0.0; + if (!RaySphereIntersection (theCenter, theRadius, myNearPickedPnt, myViewRayDir, aTimeEnter, aTimeLeave)) + { + return Standard_False; + } + + thePickResult.SetDepth (aTimeEnter * myScale); + if (theClipRange.IsClipped (thePickResult.Depth())) + { + thePickResult.SetDepth (aTimeLeave * myScale); + } + gp_Pnt aPntOnSphere (myNearPickedPnt.XYZ() + myViewRayDir.XYZ() * thePickResult.Depth()); + gp_Vec aNormal (aPntOnSphere.XYZ() - theCenter.XYZ()); + thePickResult.SetPickedPoint (aPntOnSphere); + thePickResult.SetSurfaceNormal (aNormal); + return !theClipRange.IsClipped (thePickResult.Depth()); +} + +// ======================================================================= +// function : OverlapsSphere +// purpose : +// ======================================================================= +Standard_Boolean SelectMgr_RectangularFrustum::OverlapsSphere (const gp_Pnt& theCenter, + const Standard_Real theRadius, + Standard_Boolean* theInside) const +{ + Standard_ASSERT_RAISE (mySelectionType == SelectMgr_SelectionType_Point || mySelectionType == SelectMgr_SelectionType_Box, + "Error! SelectMgr_RectangularFrustum::Overlaps() should be called after selection frustum initialization"); + return hasSphereOverlap (theCenter, theRadius, theInside); +} + // ======================================================================= // function : DistToGeometryCenter // purpose : Measures distance between 3d projection of user-picked diff --git a/src/SelectMgr/SelectMgr_RectangularFrustum.hxx b/src/SelectMgr/SelectMgr_RectangularFrustum.hxx index 02ace895ae..145f1d072a 100644 --- a/src/SelectMgr/SelectMgr_RectangularFrustum.hxx +++ b/src/SelectMgr/SelectMgr_RectangularFrustum.hxx @@ -133,6 +133,17 @@ public: const SelectMgr_ViewClipRange& theClipRange, SelectBasics_PickResult& thePickResult) const Standard_OVERRIDE; + //! Intersection test between defined volume and given sphere + Standard_EXPORT virtual Standard_Boolean OverlapsSphere (const gp_Pnt& theCenter, + const Standard_Real theRadius, + const SelectMgr_ViewClipRange& theClipRange, + SelectBasics_PickResult& thePickResult) const Standard_OVERRIDE; + + //! Intersection test between defined volume and given sphere + Standard_EXPORT virtual Standard_Boolean OverlapsSphere (const gp_Pnt& theCenter, + const Standard_Real theRadius, + Standard_Boolean* theInside) const Standard_OVERRIDE; + //! Measures distance between 3d projection of user-picked //! screen point and given point theCOG. //! It makes sense only for frustums built on a single point. diff --git a/src/SelectMgr/SelectMgr_SelectingVolumeManager.cxx b/src/SelectMgr/SelectMgr_SelectingVolumeManager.cxx index 6f0e97e117..8ecc30fa92 100644 --- a/src/SelectMgr/SelectMgr_SelectingVolumeManager.cxx +++ b/src/SelectMgr/SelectMgr_SelectingVolumeManager.cxx @@ -388,6 +388,36 @@ Standard_Boolean SelectMgr_SelectingVolumeManager::OverlapsTriangle (const gp_Pn myViewClipRange, thePickResult); } +//======================================================================= +// function : OverlapsSphere +// purpose : +//======================================================================= +Standard_Boolean SelectMgr_SelectingVolumeManager::OverlapsSphere (const gp_Pnt& theCenter, + const Standard_Real theRadius, + SelectBasics_PickResult& thePickResult) const +{ + if (myActiveSelectingVolume.IsNull()) + { + return Standard_False; + } + return myActiveSelectingVolume->OverlapsSphere (theCenter, theRadius, myViewClipRange, thePickResult); +} + +//======================================================================= +// function : OverlapsSphere +// purpose : +//======================================================================= +Standard_Boolean SelectMgr_SelectingVolumeManager::OverlapsSphere (const gp_Pnt& theCenter, + const Standard_Real theRadius, + Standard_Boolean* theInside) const +{ + if (myActiveSelectingVolume.IsNull()) + { + return Standard_False; + } + return myActiveSelectingVolume->OverlapsSphere (theCenter, theRadius, theInside); +} + //======================================================================= // function : DistToGeometryCenter // purpose : Measures distance between 3d projection of user-picked diff --git a/src/SelectMgr/SelectMgr_SelectingVolumeManager.hxx b/src/SelectMgr/SelectMgr_SelectingVolumeManager.hxx index 67f9659009..992a1c834f 100644 --- a/src/SelectMgr/SelectMgr_SelectingVolumeManager.hxx +++ b/src/SelectMgr/SelectMgr_SelectingVolumeManager.hxx @@ -152,6 +152,16 @@ public: Standard_Integer theSensType, SelectBasics_PickResult& thePickResult) const Standard_OVERRIDE; + //! Intersection test between defined volume and given sphere + Standard_EXPORT virtual Standard_Boolean OverlapsSphere (const gp_Pnt& theCenter, + const Standard_Real theRadius, + SelectBasics_PickResult& thePickResult) const Standard_OVERRIDE; + + //! Intersection test between defined volume and given sphere + Standard_EXPORT virtual Standard_Boolean OverlapsSphere (const gp_Pnt& theCenter, + const Standard_Real theRadius, + Standard_Boolean* theInside = NULL) const Standard_OVERRIDE; + //! Measures distance between 3d projection of user-picked //! screen point and given point theCOG Standard_EXPORT virtual Standard_Real DistToGeometryCenter (const gp_Pnt& theCOG) const Standard_OVERRIDE; diff --git a/src/SelectMgr/SelectMgr_TriangularFrustum.cxx b/src/SelectMgr/SelectMgr_TriangularFrustum.cxx index d7c10ee087..6f458b033b 100644 --- a/src/SelectMgr/SelectMgr_TriangularFrustum.cxx +++ b/src/SelectMgr/SelectMgr_TriangularFrustum.cxx @@ -16,6 +16,7 @@ #include #include +#include IMPLEMENT_STANDARD_RTTIEXT(SelectMgr_TriangularFrustum, Standard_Transient) @@ -301,6 +302,33 @@ Standard_Boolean SelectMgr_TriangularFrustum::OverlapsTriangle (const gp_Pnt& th return Standard_True; } +//======================================================================= +// function : OverlapsSphere +// purpose : +//======================================================================= +Standard_Boolean SelectMgr_TriangularFrustum::OverlapsSphere (const gp_Pnt& theCenter, + const Standard_Real theRadius, + Standard_Boolean* theInside) const +{ + (void) theInside; + return hasBoxOverlap (SelectMgr_Vec3 (theCenter.X() - theRadius, theCenter.Y() - theRadius, theCenter.Z() - theRadius), + SelectMgr_Vec3 (theCenter.X() + theRadius, theCenter.Y() + theRadius, theCenter.Z() + theRadius), NULL); +} + +//======================================================================= +// function : OverlapsSphere +// purpose : +//======================================================================= +Standard_Boolean SelectMgr_TriangularFrustum::OverlapsSphere (const gp_Pnt& theCenter, + const Standard_Real theRadius, + const SelectMgr_ViewClipRange& theClipRange, + SelectBasics_PickResult& thePickResult) const +{ + (void )theClipRange; + (void )thePickResult; + return hasSphereOverlap (theCenter, theRadius); +} + // ======================================================================= // function : Clear // purpose : Nullifies the handle for corresponding builder instance to prevent diff --git a/src/SelectMgr/SelectMgr_TriangularFrustum.hxx b/src/SelectMgr/SelectMgr_TriangularFrustum.hxx index 18ae7487e3..0719132ef5 100644 --- a/src/SelectMgr/SelectMgr_TriangularFrustum.hxx +++ b/src/SelectMgr/SelectMgr_TriangularFrustum.hxx @@ -104,6 +104,19 @@ public: //! @name SAT Tests for different objects const SelectMgr_ViewClipRange& theClipRange, SelectBasics_PickResult& thePickResult) const Standard_OVERRIDE; + //! Returns true if selecting volume is overlapped by sphere with center theCenter + //! and radius theRadius + Standard_EXPORT virtual Standard_Boolean OverlapsSphere (const gp_Pnt& theCenter, + const Standard_Real theRadius, + Standard_Boolean* theInside = NULL) const Standard_OVERRIDE; + + //! Returns true if selecting volume is overlapped by sphere with center theCenter + //! and radius theRadius + Standard_EXPORT virtual Standard_Boolean OverlapsSphere (const gp_Pnt& theCenter, + const Standard_Real theRadius, + const SelectMgr_ViewClipRange& theClipRange, + SelectBasics_PickResult& thePickResult) const Standard_OVERRIDE; + public: //! Nullifies the handle to corresponding builder instance to prevent memory leaks diff --git a/src/SelectMgr/SelectMgr_TriangularFrustumSet.cxx b/src/SelectMgr/SelectMgr_TriangularFrustumSet.cxx index db281fe97b..f8ed8857f0 100644 --- a/src/SelectMgr/SelectMgr_TriangularFrustumSet.cxx +++ b/src/SelectMgr/SelectMgr_TriangularFrustumSet.cxx @@ -389,6 +389,115 @@ Standard_Boolean SelectMgr_TriangularFrustumSet::OverlapsTriangle (const gp_Pnt& return Standard_False; } +//======================================================================= +// function : OverlapsSphere +// purpose : +//======================================================================= +Standard_Boolean SelectMgr_TriangularFrustumSet::OverlapsSphere (const gp_Pnt& theCenter, + const Standard_Real theRadius, + Standard_Boolean* /*theInside*/) const +{ + Standard_ASSERT_RAISE (mySelectionType == SelectMgr_SelectionType_Polyline, + "Error! SelectMgr_TriangularFrustumSet::Overlaps() should be called after selection frustum initialization"); + for (SelectMgr_TriangFrustums::Iterator anIter (myFrustums); anIter.More(); anIter.Next()) + { + if (anIter.Value()->OverlapsSphere (theCenter, theRadius, NULL)) + { + // select 3 points of the frustum and build a plane on them + Standard_Real aMaxDist1 = 0.0, aMaxDist2 = 0.0; + Standard_Integer anIdx1 = myBoundaryPoints.Lower(); + Standard_Integer anIdx2 = myBoundaryPoints.Lower(); + Standard_Integer anIdx3 = myBoundaryPoints.Lower(); + for (Standard_Integer anIdx = myBoundaryPoints.Lower(); anIdx < myBoundaryPoints.Size() / 2 + myBoundaryPoints.Lower(); anIdx++) + { + if (myBoundaryPoints[anIdx1].Distance (myBoundaryPoints[anIdx]) < Precision::Confusion()) + { + continue; + } + else if (aMaxDist1 < myBoundaryPoints[anIdx1].Distance (myBoundaryPoints[anIdx])) + { + if (anIdx2 != anIdx3) + { + anIdx3 = anIdx2; + aMaxDist2 = aMaxDist1; + } + anIdx2 = anIdx; + aMaxDist1 = myBoundaryPoints[anIdx1].Distance (myBoundaryPoints[anIdx]); + } + else if (aMaxDist2 < myBoundaryPoints[anIdx2].Distance (myBoundaryPoints[anIdx])) + { + anIdx3 = anIdx; + aMaxDist2 = myBoundaryPoints[anIdx2].Distance (myBoundaryPoints[anIdx]); + } + } + gp_Vec aVecPlane1 (myBoundaryPoints[anIdx1], myBoundaryPoints[anIdx2]); + gp_Vec aVecPlane2 (myBoundaryPoints[anIdx1], myBoundaryPoints[anIdx3]); + + const gp_Dir aNorm (aVecPlane1.Crossed (aVecPlane2)); + + // distance from point(x,y,z) to plane(A,B,C,D) d = | Ax + By + Cz + D | / sqrt (A^2 + B^2 + C^2) = aPnt.Dot (Norm) / 1 + const gp_Pnt aCenterProj = theCenter.XYZ() - aNorm.XYZ() * theCenter.XYZ().Dot (aNorm.XYZ()); + + // If the center of the sphere is inside of the volume projection, then anAngleSum will be equal 2*M_PI + Standard_Real anAngleSum = 0.0; + TColgp_Array1OfPnt aBoundaries (myBoundaryPoints.Lower(), myBoundaryPoints.Size() / 2 + myBoundaryPoints.Lower()); + + for (Standard_Integer anIdx = myBoundaryPoints.Lower(); anIdx < myBoundaryPoints.Size() / 2 + myBoundaryPoints.Lower(); anIdx++) + { + aBoundaries.SetValue (anIdx, myBoundaryPoints[anIdx]); + + gp_Pnt aPnt1 = myBoundaryPoints.Value (anIdx); + gp_Pnt aPnt2 = myBoundaryPoints.Value (anIdx + 1); + + // Projections of the points on the plane + gp_Pnt aPntProj1 = aPnt1.XYZ() - aNorm.XYZ() * aPnt1.XYZ().Dot (aNorm.XYZ()); + gp_Pnt aPntProj2 = aPnt2.XYZ() - aNorm.XYZ() * aPnt2.XYZ().Dot (aNorm.XYZ()); + + gp_Vec aVecAngle1 (aCenterProj, aPntProj1); + gp_Vec aVecAngle2 (aCenterProj, aPntProj2); + anAngleSum += aVecAngle1.Angle (aVecAngle2); + } + Standard_Boolean isCenterInside = Abs (anAngleSum - 2 * M_PI) < Precision::Confusion(); + Standard_Boolean isBoundaryInside = Standard_False; + Standard_Boolean isIntersectSphereBoundaries = IsBoundaryIntersectSphere (aCenterProj, theRadius, aNorm, aBoundaries, isBoundaryInside); + + if (myToAllowOverlap) + { + return isIntersectSphereBoundaries + || isCenterInside; + } + else + { + return !isIntersectSphereBoundaries + && isCenterInside + && !isBoundaryInside; + } + } + } + return Standard_False; +} + +//======================================================================= +// function : OverlapsSphere +// purpose : +//======================================================================= +Standard_Boolean SelectMgr_TriangularFrustumSet::OverlapsSphere (const gp_Pnt& theCenter, + const Standard_Real theRadius, + const SelectMgr_ViewClipRange& theClipRange, + SelectBasics_PickResult& thePickResult) const +{ + Standard_ASSERT_RAISE (mySelectionType == SelectMgr_SelectionType_Polyline, + "Error! SelectMgr_TriangularFrustumSet::Overlaps() should be called after selection frustum initialization"); + for (SelectMgr_TriangFrustums::Iterator anIter (myFrustums); anIter.More(); anIter.Next()) + { + if (anIter.Value()->OverlapsSphere (theCenter, theRadius, theClipRange, thePickResult)) + { + return Standard_True; + } + } + return Standard_False; +} + // ======================================================================= // function : GetPlanes // purpose : diff --git a/src/SelectMgr/SelectMgr_TriangularFrustumSet.hxx b/src/SelectMgr/SelectMgr_TriangularFrustumSet.hxx index 70cfb7a5c8..1618b44d64 100644 --- a/src/SelectMgr/SelectMgr_TriangularFrustumSet.hxx +++ b/src/SelectMgr/SelectMgr_TriangularFrustumSet.hxx @@ -105,6 +105,19 @@ public: //! Calculates the point on a view ray that was detected during the run of selection algo by given depth Standard_EXPORT virtual gp_Pnt DetectedPoint (const Standard_Real theDepth) const Standard_OVERRIDE; + //! Returns true if selecting volume is overlapped by sphere with center theCenter + //! and radius theRadius + Standard_EXPORT virtual Standard_Boolean OverlapsSphere (const gp_Pnt& theCenter, + const Standard_Real theRadius, + Standard_Boolean* theInside = NULL) const Standard_OVERRIDE; + + //! Returns true if selecting volume is overlapped by sphere with center theCenter + //! and radius theRadius + Standard_EXPORT virtual Standard_Boolean OverlapsSphere (const gp_Pnt& theCenter, + const Standard_Real theRadius, + const SelectMgr_ViewClipRange& theClipRange, + SelectBasics_PickResult& thePickResult) const Standard_OVERRIDE; + //! Stores plane equation coefficients (in the following form: //! Ax + By + Cz + D = 0) to the given vector Standard_EXPORT virtual void GetPlanes (NCollection_Vector& thePlaneEquations) const Standard_OVERRIDE; diff --git a/src/StdSelect/StdSelect_BRepSelectionTool.cxx b/src/StdSelect/StdSelect_BRepSelectionTool.cxx index 97793c1464..8d3a79c15d 100644 --- a/src/StdSelect/StdSelect_BRepSelectionTool.cxx +++ b/src/StdSelect/StdSelect_BRepSelectionTool.cxx @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -41,6 +42,7 @@ #include #include #include +#include #include #include #include @@ -572,6 +574,35 @@ Standard_Boolean StdSelect_BRepSelectionTool::GetSensitiveForFace (const TopoDS_ TopLoc_Location aLoc; if (Handle(Poly_Triangulation) aTriangulation = BRep_Tool::Triangulation (theFace, aLoc)) { + TopLoc_Location aLocSurf; + const Handle(Geom_Surface)& aSurf = BRep_Tool::Surface (theFace, aLocSurf); + if (Handle(Geom_SphericalSurface) aGeomSphere = Handle(Geom_SphericalSurface)::DownCast (aSurf)) + { + bool isFullSphere = theFace.NbChildren() == 0; + if (theFace.NbChildren() == 1) + { + const TopoDS_Iterator aWireIter (theFace); + const TopoDS_Wire& aWire = TopoDS::Wire (aWireIter.Value()); + if (aWire.NbChildren() == 4) + { + Standard_Integer aNbSeamEdges = 0, aNbDegenEdges = 0; + for (TopoDS_Iterator anEdgeIter (aWire); anEdgeIter.More(); anEdgeIter.Next()) + { + const TopoDS_Edge& anEdge = TopoDS::Edge (anEdgeIter.Value()); + aNbSeamEdges += BRep_Tool::IsClosed (anEdge, theFace); + aNbDegenEdges += BRep_Tool::Degenerated (anEdge); + } + isFullSphere = aNbSeamEdges == 2 && aNbDegenEdges == 2; + } + } + if (isFullSphere) + { + gp_Sphere aSphere = BRepAdaptor_Surface (theFace).Sphere(); + Handle(Select3D_SensitiveSphere) aSensSphere = new Select3D_SensitiveSphere (theOwner, aSphere.Position().Axis().Location(), aSphere.Radius()); + theSensitiveList.Append (aSensSphere); + return Standard_True; + } + } Handle(Select3D_SensitiveTriangulation) STG = new Select3D_SensitiveTriangulation (theOwner, aTriangulation, aLoc, theInteriorFlag); theSensitiveList.Append (STG); return Standard_True; diff --git a/tests/vselect/clipping/bug30777 b/tests/vselect/clipping/bug30777 index bba0f747ab..7e43a16864 100644 --- a/tests/vselect/clipping/bug30777 +++ b/tests/vselect/clipping/bug30777 @@ -14,6 +14,6 @@ vclipplane p1 -set s -equation 0 1 0 0 set p [vmoveto 200 200] vpoint pp {*}$p vsetcolor pp RED -checkpoint p $p {-0.34896 9.94397 0.27411} 0.0001 +checkpoint p $p {-0.34896 9.99014 0.27411} 0.0001 vdump ${imagedir}/${casename}.png diff --git a/tests/vselect/sphere/check_depth b/tests/vselect/sphere/check_depth new file mode 100644 index 0000000000..6d38a5b922 --- /dev/null +++ b/tests/vselect/sphere/check_depth @@ -0,0 +1,28 @@ +puts "=================================" +puts "0032182: Visualization - add Select3D_SensitiveSphere" +puts "Tests depth value returned by Select3D_SenstiveSphere" +puts "=================================" + +psphere s 1 +vdisplay -dispMode 1 s +vfit +vtop +set top [vmoveto 300 200] +if { ![string match "*Select3D_SensitiveSphere*" [vstate -entities]] } { puts "Error: sphere should be detected" } +checkpoint top_p $top {0.0013453695513163666 -0.0090115971854718303 0.99995849333074871} 0.0001 +vbottom +set bottom [vmoveto 300 200] +if { ![string match "*Select3D_SensitiveSphere*" [vstate -entities]] } { puts "Error: sphere should be detected" } +checkpoint bottom_p $bottom {0.0013453695513163666 0.0090115971854718303 -0.99995849333074871} 0.0001 +vright +set right [vmoveto 300 200] +if { ![string match "*Select3D_SensitiveSphere*" [vstate -entities]] } { puts "Error: sphere should be detected" } +checkpoint right_p $right {0.99995849333074871 0.0013453695513163666 -0.0090115971854718268} 0.0001 +vfront +set front [vmoveto 300 200] +if { ![string match "*Select3D_SensitiveSphere*" [vstate -entities]] } { puts "Error: sphere should be detected" } +checkpoint front_p $front {0.0013453695513163666 -0.99995832904743187 -0.0090115971854718285} 0.01 +vleft +set left [vmoveto 300 200] +if { ![string match "*Select3D_SensitiveSphere*" [vstate -entities]] } { puts "Error: sphere should be detected" } +checkpoint left_p $left {-0.99995817565020639 -0.0013453695513163666 -0.0090115971854718268} 0.0001 diff --git a/tests/vselect/sphere/detecting b/tests/vselect/sphere/detecting new file mode 100644 index 0000000000..a42120b842 --- /dev/null +++ b/tests/vselect/sphere/detecting @@ -0,0 +1,10 @@ +puts "=================================" +puts "0032182: Visualization - add Select3D_SensitiveSphere" +puts "Tests detecting Select3D_SenstiveSphere" +puts "=================================" + +psphere s 1 +vdisplay -dispMode 1 s +vfit +vmoveto 300 200 +if { ![string match "*Select3D_SensitiveSphere*" [vstate -entities]] } { puts "Error: sphere should be detected" } diff --git a/tests/vselect/sphere/polygon_selection b/tests/vselect/sphere/polygon_selection new file mode 100644 index 0000000000..c1cc47fe1c --- /dev/null +++ b/tests/vselect/sphere/polygon_selection @@ -0,0 +1,52 @@ +puts "=================================" +puts "0032182: Visualization - add Select3D_SensitiveSphere" +puts "Tests polygon selection of Select3D_SenstiveSphere" +puts "=================================" + +psphere s1 1 +psphere s2 1 +psphere s3 1 +psphere s4 1 +ttranslate s1 2 2 0 +ttranslate s2 2 -2 0 +ttranslate s3 -2 2 0 +ttranslate s4 -2 -2 0 +vdisplay -dispMode 1 s1 s2 s3 s4 +vfit +vselect 15 200 300 15 585 200 300 385 +if { ![string match "*Selected*" [vstate s1]] || + ![string match "*Selected*" [vstate s2]] || + ![string match "*Selected*" [vstate s3]] || + ![string match "*Selected*" [vstate s4]]} { puts "Error: all spheres should be selected" } +vtop +vselect 170 200 300 80 430 200 300 320 +if { [string match "*Selected*" [vstate s1]] || + [string match "*Selected*" [vstate s2]] || + [string match "*Selected*" [vstate s3]] || + [string match "*Selected*" [vstate s4]]} { puts "Error: all spheres should be unselected" } +vselect 177 78 422 78 422 322 177 322 -allowoverlap 1 +if { ![string match "*Selected*" [vstate s1]] || + ![string match "*Selected*" [vstate s2]] || + ![string match "*Selected*" [vstate s3]] || + ![string match "*Selected*" [vstate s4]]} { puts "Error: all spheres should be selected" } +vright +vselect 181 323 289 196 147 85 59 206 +if { [string match "*Selected*" [vstate s1]] || + ![string match "*Selected*" [vstate s2]] || + [string match "*Selected*" [vstate s3]] || + ![string match "*Selected*" [vstate s4]]} { puts "Error: spheres s1 and s3 should be unselected" } +vselect 131 197 177 156 219 198 179 247 +if { [string match "*Selected*" [vstate s1]] || + [string match "*Selected*" [vstate s2]] || + [string match "*Selected*" [vstate s3]] || + [string match "*Selected*" [vstate s4]]} { puts "Error: all spheres should be unselected" } +vselect 131 197 177 156 219 198 179 247 -allowoverlap 1 +if { [string match "*Selected*" [vstate s1]] || + ![string match "*Selected*" [vstate s2]] || + [string match "*Selected*" [vstate s3]] || + ![string match "*Selected*" [vstate s4]]} { puts "Error: spheres s1 and s3 should be unselected" } +vselect 227 147 282 268 367 151 -allowoverlap 1 +if { [string match "*Selected*" [vstate s1]] || + [string match "*Selected*" [vstate s2]] || + [string match "*Selected*" [vstate s3]] || + [string match "*Selected*" [vstate s4]]} { puts "Error: all spheres should be unselected" }