PDA

View Full Version : Select objects inside sphere


shcmack
07-23-2010, 06:08 PM
I wasn't sure whether to post this in the general forum or here as this isn't an actual programming question. But, since this have to be done with MEL/Python I thought I'd just post it here.

I want to make a tool/script that enables me to get all the objects which are inside another (specified) object.

So lets say I have a sphere that should be the "selector"-object, the script has to be pretty accurate to distinguish between the objects inside and outside of the sphere because of the roundness of the sphere. I guess it could be easier with a pCube, I don't know :shrug:
Anyway, I've thought about querying the wordspace position of each vtx of the "selector"-object, and then somehow select all of the objects within the worldspace range of the vertexes.

Thing is, I'm not sure how to approach this task. So I was wondering if anyone had any ideas?


EDIT: Or maybe a tool like this already exists?

bendingiscool
07-23-2010, 06:29 PM
Hey,

I've done something like this before for part of a larger script..

Your right that it is easier to use a cube as you would probably have to get into the API stuff if you wanted to use a more precise bounding box set up.

Basically, you query the bbox of your cube and store its values, then query the position of your objects, and if they are more than the bbox's min values and less than the bbox's max values, then select that object.

The xform command will be your friend for this.. ;)

cheers,
Chris

shcmack
07-26-2010, 01:03 AM
Thanks Chris :)

I've gotten it to work the way you explained with a cube, however it doesn't seem that method can work with a sphere (atleast I can't wrap my mind around it).

Does that mean that API is the only way to get this to work? I've had a look at it today for the first time, looks pretty darn complicated :surprised

NateH
07-26-2010, 01:50 AM
To do a true and accurate check for objects inside a sphere, you are going to have to calculate a |distance| between each vertex of each nearby object and the center point of your sphere, and see if it is less than the radius of the sphere.

You will probably want to minimize the number of vertexes you search using an initial bounding box test, and then do a more detailed check using vector length only on nearby verts.

Here's a working script that uses something similar to that. It doesn't use an actual sphere, only a point and a radius. But I'm sure you could adapt it to do so. It uses mostly the API in python.

Note: this script is designed to return ALL nearby vertices inside the selection sphere, whereas you are only looking for whole objects. So you could short circuit this code to continue on to the next object once you have a single matching vertex. (Which should speed up your check by several times for mostly enclosed objects with a large number of verts.

The relevant function is findNearby(point, radius)

import maya.OpenMaya as api
import maya.cmds as cmds

def findNearby(point, tolerance):
'''findNearby
point - Either an MPoint, a tuple (x, y, z), or an object name (Uses that objects pivot)
tolerance - The search radius for finding nearby selection
'''
mode = 'vertex'

#convert an object to an API point (Get it's worldspace position)
if isinstance(point, basestring):
point = pointFromObject(point)
point = Point(point)

meshList = buildMeshList()
selection = api.MSelectionList()

for mesh in meshList:
matrix = api.MMatrix(mesh.inclusiveMatrix())

meshObj = api.MFnMesh(mesh)
meshBB = meshObj.boundingBox()
meshBB.transformUsing(matrix)

pointBB = api.MBoundingBox(
Point(point.x-tolerance, point.y-tolerance, point.z-tolerance),
Point(point.x+tolerance, point.y+tolerance, point.z+tolerance))

#if the mesh is inside the bounding box for the point+tolerance
if meshBB.contains(point) or meshBB.intersects(pointBB):
if mode in ['vertex', 'cv', 'point']:
iter = api.MItGeometry(mesh)
while not iter.isDone():
vert = iter.position(api.MSpace.kWorld)
if pointBB.contains(vert):
if (point-vert).length()<tolerance:
selection.add(mesh, iter.currentItem())
iter.next()
else:
pass
matching = []
selection.getSelectionStrings(matching)
return matching

def buildMeshList():
meshList = []

iter = api.MItDag(api.MItDag.kDepthFirst, api.MFn.kMesh)
while not iter.isDone():
dagPath = api.MDagPath()
iter.getPath(dagPath)
dagPath.extendToShape()
if not dagPath.name.endswith('Orig'):
meshList.append(dagPath)
iter.next()
return meshList

#DEPS#
class Point(api.MPoint, object):
'''Subclass of MPoint, use a nicer str() and repr()'''
def __init__(self, x=0, y=0, z=0):
#allow point to take a single tuple, or 3 floats, or wrap an existing MVector
if isinstance(x, api.MPoint) or isinstance(x, api.MVector):
super(Point, self).__init__(x)
return

if isinstance(x, tuple) or isinstance(x, list):
super(Point, self).__init__(x[0], x[1], x[2])
else:
api.MPoint.__init__(self, x, y, z)

def __str__(self):
return '(%g, %g, %g)'%(self.x, self.y, self.z)
def __repr__(self):
return '<<MPoint (%g, %g, %g)>>'%(self.x, self.y, self.z)

def asTuple(self):
return (self.x, self.y, self.z)

def pointFromObject(object):
'''Get an objects xform, return Point object'''
pos = cmds.xform(object, q=True, ws=True, t=True)
return Point(pos[0], pos[1], pos[2])

And here's the plain python file (http://dl.dropbox.com/u/1633130/Python/findClosest.py) if the forums code block messes up the formatting.

shcmack
07-27-2010, 12:01 AM
Thanks alot :)

I can't get the script to work though :blush:

From the commenting within your script, I thought I could run e.g. findNearby(pSphere1, 1), but then it tells me that name 'pSphere1' is not defined.

NateH
07-27-2010, 02:51 AM
Sounds like you are trying to use pSphere1 as a variable instead of as a string try:
findNearby('pSphere1', 1)
or alternatively give it a position
findNearby([0, 0, 0], 1)

shcmack
07-27-2010, 04:24 AM
Ah, I haven't really used any Python within Maya, just MEL :hmm:

But, eh.. I'm getting # Error: NameError: global name 'DagPath' is not defined # :shrug:

Robert Bateman
07-27-2010, 02:11 PM
To do a true and accurate check for objects inside a sphere, you are going to have to calculate a |distance| between each vertex of each nearby object and the center point of your sphere, and see if it is less than the radius of the sphere.

You will probably want to minimize the number of vertexes you search using an initial bounding box test, and then do a more detailed check using vector length only on nearby verts.

Sphere.position = Vec3(0,0,0);
Sphere.radius = 0.001f;
Box.position = Vec3(0,0,0);
Box.max = Vec3(1,1,1);
Box.min = Vec3(-1,-1,-1);

That royally buggers up your algorithm ;)

The *correct* way to do this is using a Separating Axis Test (SAT). I can't be bothered writing this in mel right now, but basically in psuedo C++ you want something along the lines of:


struct Box
{
Vector3 min;
Vector3 max;
Matrix orientation; // if you want OBB instead of AABB
};
struct Sphere
{
Vector3 position;
float radius;
};

bool testAABB(Box box, Sphere sphere)
{
return (sphere.position.x >= (box.min.x - Sphere.radius)) &&
(sphere.position.x <= (box.max.x + Sphere.radius)) &&
(sphere.position.y >= (box.min.y - Sphere.radius)) &&
(sphere.position.y <= (box.max.y + Sphere.radius)) &&
(sphere.position.z >= (box.min.z - Sphere.radius)) &&
(sphere.position.z <= (box.max.z + Sphere.radius));
}

bool testOBB(Box box, Sphere sphere)
{
// vec from box center to sphere.
Vector3 temp = sphere.position - box.orientation.position;

// transform into boxes co-ordinate frame
Vector3 testVec;
testVec.x = dot( box.orientation.x_axis, temp );
testVec.y = dot( box.orientation.y_axis, temp );
testVec.z = dot( box.orientation.z_axis, temp );

return (testVec.x >= (box.min.x - Sphere.radius)) &&
(testVec.x <= (box.max.x + Sphere.radius)) &&
(testVec.y >= (box.min.y - Sphere.radius)) &&
(testVec.y <= (box.max.y + Sphere.radius)) &&
(testVec.z >= (box.min.z - Sphere.radius)) &&
(testVec.z <= (box.max.z + Sphere.radius));
}

NateH
07-27-2010, 10:24 PM
Ah, I haven't really used any Python within Maya, just MEL :hmm:

But, eh.. I'm getting # Error: NameError: global name 'DagPath' is not defined # :shrug:
Fixed that, I was using my own DagPath class instead of api.MDagPath, Sorry.

And yes, you can break this with any object that has no contained vertices within the sphere, even if the volumes are intersecting. For my needs it works, for yours it might not, depends on the precision you need.

shcmack
07-28-2010, 03:49 AM
Thanks guys! Uhm, guess I should take a stab at Python soon ;) C++ is still greek to me, but I've read a bit on your site Robert, great of you to share all that stuff :bowdown:

To do a true and accurate check for objects inside a sphere, you are going to have to calculate a |distance| between each vertex of each nearby object and the center point of your sphere, and see if it is less than the radius of the sphere.
I didn't really make sense of these words until a guy named Joojaa on CreativeCrash fed it to me with a teaspoon, but yeah, thats brilliant!

Got it working now, if mag(objToEvaluate-centerOfSelectorObj) > radius, select -tgl object :D

CGTalk Moderation
07-28-2010, 03:49 AM
This thread has been automatically closed as it remained inactive for 12 months. If you wish to continue the discussion, please create a new thread in the appropriate forum.