How can I get the object under the cursor without selecting it?

How can I get the object under the cursor without selecting it?

143110913
Enthusiast Enthusiast
1,200 Views
6 Replies
Message 1 of 7

How can I get the object under the cursor without selecting it?

143110913
Enthusiast
Enthusiast

I'm working on a custom context and would like to know which object is under my cursor every time it moves. (MPxContext::doPtrMoved)

 

- Since it can be potentially be hundreds of objects, I think any kind operation that checks intersections with MFnMesh are discarded.

- I also can't seem to use MGlobal::selectFromScreen since that makes with my custom drawing blink. (Unless I'm missing something)

Right now I'm using maya.cmds.hitTest but I wonder if there's an equivalent on the API.

 

Thanks!

0 Likes
1,201 Views
6 Replies
Replies (6)
Message 2 of 7

143110913
Enthusiast
Enthusiast

Just adding that cmds.hitTest is very unreliable, it doesn't always recognize a mesh so I can't really use it.

0 Likes
Message 3 of 7

143110913
Enthusiast
Enthusiast

I think I see the error.

 

MEvent.getPosition( x, y ) returns possibly incorrect coordinates.

 

If I use Qt/PySide to read the cursor position it does work properly.

pos = QtGui.QCursor.pos()

widget = QtGui.qApp.widgetAt(pos)

relpos = widget.mapFromGlobal(pos)

 

Is there any offset inside MEvent that I have to take into account or that I'm missing?

0 Likes
Message 4 of 7

143110913
Enthusiast
Enthusiast

I found the solution.

 

On Qt 0 on the Y axis starts at the lower corner, on MEvent at the top.

 

I'll still leave this open for a few days in case there's a better way to do this than hitTest.

 

Thank you!

Message 5 of 7

jmreinhart
Advisor
Advisor

So MGlobal::selectFromScreen actually uses the same calculations as the MFnMesh intersect functions, so I wouldn't rule that out as an option without at least testing it's speed with a lot of objects.

 

If you're ok with posting the part of your code where you encounter flickering we may be able to resolve that too.

 

0 Likes
Message 6 of 7

143110913
Enthusiast
Enthusiast

Hi Jonah,

 

Thank you for your reply,

 

I've prepared a simplified example of my doPtrMoved Fn.

I'd like to get what object is under the mouse and based on that draw some faces.

Calling MGlobal::selectFromScreen will make my custom drawing flicker.

 

MStatus CustomContext::doPtrMoved(MEvent &event, MUIDrawManager &drawMgr, const MFrameContext &context) {

ptArray.clear();
ptArray.append(MPoint(0, 0, 0));
ptArray.append(MPoint(-10, 0, 0));
ptArray.append(MPoint(-10, 0, 10));

MSelectionList incomingList;
MGlobal::getActiveSelectionList(incomingList);
MGlobal::selectFromScreen(0, 0, MGlobal::kReplaceList);
MSelectionList screenMSel;
MGlobal::getActiveSelectionList(screenMSel);
// Restore the active selection
MGlobal::setActiveSelectionList(incomingList, MGlobal::kReplaceList);

drawMgr.setPaintStyle(MUIDrawManager::kStippled);
drawMgr.beginDrawInXray();
drawMgr.mesh(MUIDrawManager::kTriangles, ptArray);
drawMgr.endDrawInXray();

return MStatus();
}

 

Since this is for hundreds of meshes, it is considerable faster to get which mesh is under my mouse with MGlobal::selectFromScreen than instantiating an MFnMesh object with each of those meshes and using MFnMesh::anyIntersection

 

Let me know if I'm missing anything.

 

Thanks.

0 Likes
Message 7 of 7

jmreinhart
Advisor
Advisor

So it seems like the selectFromScreen causes flickering because it causes a refresh of the viewport. I don't know if there is a way to use that function and get around it.

 

I did do some testing of my own using the MfnMesh method and I was getting some good results using some optimization.

 

  • Updating the maxParam whenever you find an intersection on a mesh, so the other meshes we check will only need to search a shorter distance.
  • Get the list of meshes in ToolOnSetup and create MMeshIsectAccelParams for them

I did notice that the M3dView class has a beginSelect and endSelect which seems graphics-based? But I didn't really dig into it.

 

Sorry for the janky test code 

#include <maya/MString.h>
#include <maya/MItSelectionList.h>
#include <maya/MPxContextCommand.h>
#include <maya/MPxContext.h>
#include <maya/MPxSelectionContext.h>
#include <maya/MEvent.h>
#include <maya/M3dView.h>
#include <maya/MObjectArray.h>
#include <maya/MDagPathArray.h>
#include <maya/MIntArray.h>
#include <maya/MGlobal.h>
#include <maya/MFnPlugin.h>
#include <maya/MPointArray.h>
#include <maya/MUIDrawManager.h>
#include <maya/MItDag.h>
#include <maya/MDagPath.h>
#include <maya/MFnDagNode.h>
#include <maya/MFnMesh.h>
#include <maya/M3dView.h>
#include <maya/MFnCamera.h>
#include <vector>

class clickTest : public MPxContext
{
public:
	clickTest() {};
	virtual                 ~clickTest() {};
	void*                   creator() {};

	void    toolOnSetup(MEvent & event);
	MStatus doPress(MEvent &event, MHWRender::MUIDrawManager &drawMgr, const MHWRender::MFrameContext &context) { return MS::kSuccess; }
	MStatus doDrag(MEvent &event, MHWRender::MUIDrawManager &drawMgr, const MHWRender::MFrameContext &context) { return MS::kSuccess; }
	MStatus doRelease(MEvent &event, MHWRender::MUIDrawManager &drawMgr, const MHWRender::MFrameContext &context) { return MS::kSuccess; }


	MStatus doPtrMoved(MEvent &event, MHWRender::MUIDrawManager &drawMgr, const MHWRender::MFrameContext &context);

	MPointArray ptArray;
	MDagPathArray allMeshes;
	std::vector<MMeshIsectAccelParams> accels;
};

// Command to create contexts

class meshReorderContextCmd : public MPxContextCommand
{
public:
	meshReorderContextCmd() {};
	virtual MPxContext* makeObj();
	static void*            creator();

};

MPxContext* meshReorderContextCmd::makeObj()
{
	return new clickTest;
}

void* meshReorderContextCmd::creator()
{
	return new meshReorderContextCmd;
}

bool isVisible(MFnDagNode dagNodee, MStatus status)
{
	return true;
}

MStatus getAllMeshes(MDagPathArray& pathArray)
{
	MStatus status;
	//create an iterator for only the mesh components of the DAG
	MItDag itDag(MItDag::kDepthFirst, MFn::kMesh, &status);

	if (MStatus::kFailure == status) {
		MGlobal::displayError("MItDag::MItDag");
		return MS::kFailure;
	}

	pathArray.clear();
	for (; !itDag.isDone(); itDag.next()) {
		//get the current DAG path
		//
		MDagPath dagPath;
		if (MStatus::kFailure == itDag.getPath(dagPath)) {
			MGlobal::displayError("MDagPath::getPath");
			return MS::kFailure;
		}

		pathArray.append(dagPath);

		//if this node is visible, then process the poly mesh it represents
		// IGNORE FOR NOW
	}
	return MStatus::kSuccess;
}


void  clickTest::toolOnSetup(MEvent & event)
{
	getAllMeshes(allMeshes);
	accels.resize(allMeshes.length());
	for (unsigned i = 0; i < allMeshes.length(); i++)
	{
;		accels[i] = MFnMesh(allMeshes[i]).autoUniformGridParams();
	}
}



MStatus clickTest::doPtrMoved(MEvent &event, MHWRender::MUIDrawManager &drawMgr, const MHWRender::MFrameContext &context)
{
	MStatus status;

	M3dView view = M3dView::active3dView();
	short x_pos;
	short y_pos;
	event.getPosition(x_pos, y_pos);

	ptArray.clear();
	ptArray.append(MPoint(0, 0, 0));
	ptArray.append(MPoint(-10, 0, 0));
	ptArray.append(MPoint(0, 0, -10));

	int method = 1;
	if (method == 0)
	{
		MSelectionList incomingList;
		MGlobal::getActiveSelectionList(incomingList);
		MGlobal::selectFromScreen(x_pos, y_pos, MGlobal::kReplaceList, MGlobal::kSurfaceSelectMethod);
		MSelectionList screenMSel;
		MGlobal::getActiveSelectionList(screenMSel);
		// Restore the active selection
		MGlobal::setActiveSelectionList(incomingList, MGlobal::kReplaceList);
	}
	else if (method == 1)
	{
		// Is having this in tool on setup an option?
		// Sluggish at 1000 spheres

		MDagPath dagPath;
		view.getCamera(dagPath);

		MFnCamera camFn(dagPath);

		MPoint raySource;
		MVector rayDirection;
		view.viewToWorld(x_pos, y_pos, raySource, rayDirection);

		float maxParam = camFn.farClippingPlane();

		MFloatPoint nearestHit;
		MFloatPoint hitPoint;
		float minParam = maxParam;
		float hitRayParam = maxParam;
		
		for (unsigned i = 0; i < allMeshes.length(); i++)
		{
			MFnMesh mFnMesh(allMeshes[i]);
			mFnMesh.closestIntersection(raySource,
				rayDirection,
				NULL,
				NULL,
				true,
				MSpace::kWorld,
				minParam,
				false,
				&accels[i],
				hitPoint,
				&hitRayParam,
				NULL,
				NULL,
				NULL,
				NULL,
				status
			);

			if (status != MS::kSuccess)
			{
				continue;
			}

			if (hitRayParam < minParam)
			{
				minParam = hitRayParam;
				nearestHit = hitPoint;
			}
		}
		ptArray[2] = nearestHit;
	}
	else if (method == 2)
	{
	}

	drawMgr.setPaintStyle(MUIDrawManager::kStippled);
	drawMgr.beginDrawInXray();
	drawMgr.mesh(MUIDrawManager::kTriangles, ptArray);
	drawMgr.endDrawInXray();

	return MStatus();
}

// plugin initialization
MStatus initializePlugin(MObject obj)
{
	MStatus     status;
	MFnPlugin   plugin(obj, PLUGIN_COMPANY, "12.0", "Any");
	status = plugin.registerContextCommand(MString("marqueeToolContext"), meshReorderContextCmd::creator);
	return status;
}

MStatus uninitializePlugin(MObject obj)
{
	MStatus     status;
	MFnPlugin   plugin(obj);
	status = plugin.deregisterContextCommand("marqueeToolContext");
	return status;
}

 

import maya.mel as mel

cmds.file(new = True, f= True)

for i in range(10000):
    x = random.randint(-10, 10)
    y = random.randint(-10, 10)
    z = random.randint(-10, 10)

    s = cmds.polySphere()[0]
    cmds.setAttr(s + '.t', x,y,z)

cmds.loadPlugin('...')

x = cmds.marqueeToolContext()
mel.eval('setToolTo {}'.format(x))

 

 

0 Likes