Really Solid MEL, no C++, trying to write Python Plugin - totally stuck

269 views
Skip to first unread message

jasonosipa

unread,
Mar 27, 2008, 1:18:34 PM3/27/08
to python_inside_maya
Hi there!

I have a very good MEL foundation, but no C++ experience and all the
documentation for API stuff seems to be geared C++ > Python instead of
MEL > Python, leaving folks like me confused.

I think my specific goal is to get at the closestIntersection portion
of MFnMesh. I really just want to feed that its appropriate args: a
ray source, a ray direction, a mesh to collide with, etc, and get back
the point in space the intersection happens (IF it happens).

Now, no matter what I try, I can't seem to get that command to work,
and frankly, don't understand the documentation. From examples, and
looking things up, and asking, I think I need to:

(forgive potentially hilarious errors in syntax and/or judgment )

make an MObject?
somehow tie/connect/glue my collider mesh to that or pass it to
MFnMesh?
pass all that to closestIntersection with args?

The problem is, I don't know how to interpret the docs to make any of
this work...

If someone could even post what a valid call to
MFnMesh.closestIntersection looks like, I might be able to work
backwards? Thanks.

Dean Edmonds

unread,
Mar 28, 2008, 1:39:33 PM3/28/08
to python_in...@googlegroups.com
On Thu, Mar 27, 2008 at 10:18 AM, jasonosipa <jason...@gmail.com> wrote:
>
> I think my specific goal is to get at the closestIntersection portion
> of MFnMesh. I really just want to feed that its appropriate args: a
> ray source, a ray direction, a mesh to collide with, etc, and get back
> the point in space the intersection happens (IF it happens).
[...]
> make an MObject?

Many Maya methods can return an MObject. To get the MObject or
MDagPath a node whose name you
know, you can use an MSelectionList:

import maya.OpenMaya as om
selList = om.MSelection list;

nodeObject = om.MObject();
selList.getDependNode(0, nodeObject)

> If someone could even post what a valid call to
> MFnMesh.closestIntersection looks like, I might be able to work
> backwards? Thanks.

Here's a function that takes the name of mesh node, a start point and
a direction
for a ray and returns the interestion point, if found, otherwise None.

import maya.OpenMaya as om

def findMeshIntersection(meshName, raySource, rayDir):
# Create an empty selection list.
selectionList = om.MSelectionList()

# Put the mesh's name on the selection list.
selectionList.add(meshName)

# Create an empty MDagPath object.
meshPath = om.MDagPath()

# Get the first item on the selection list (which will be our mesh)
# as an MDagPath.
selectionList.getDagPath(0, meshPath)

# Create an MFnMesh functionset to operate on the node pointed to by
# the dag path.
meshFn = om.MFnMesh(meshPath)

# Convert the 'raySource' parameter into an MFloatPoint.
raySource = om.MFloatPoint(raySource[0], raySource[1], raySource[2])

# Convert the 'rayDir' parameter into an MVector.`
rayDirection = om.MFloatVector(rayDir[0], rayDir[1], rayDir[2])

# Create an empty MFloatPoint to receive the hit point from the call.
hitPoint = om.MFloatPoint()

# Set up a variable for each remaining parameter in the
# MFnMesh::closestIntersection call. We could have supplied these as
# literal values in the call, but this makes the example more readable.
sortIds = False
maxDist = 10.0
bothDirections = False
noFaceIds = None
noTriangleIds = None
noAccelerator = None
noHitParam = None
noHitFace = None
noHitTriangle = None
noHitBary1 = None
noHitBary2 = None

# Get the closest intersection.
gotHit = meshFn.closestIntersection(
raySource, rayDirection,
noFaceIds, noTriangleIds,
sortIds, om.MSpace.kWorld, maxDist, bothDirections,
noAccelerator,
hitPoint,
noHitParam, noHitFace, noHitTriangle, noHitBary1, noHitBary2
)

# Return the intersection as a Pthon list.
if gotHit:
return [hitPoint.x, hitPoint.y, hitPoint.z]
else:
return None

--
-deane

jasonosipa

unread,
Mar 28, 2008, 2:41:17 PM3/28/08
to python_inside_maya
Wow! Thank you! This is very very generous and useful, and so
wonderfully commented! Another super-friend of mine also got me a
working version, here it is:

import maya.cmds as cm
import maya.OpenMaya as om

def nameToNode( name ):
selectionList = om.MSelectionList()
selectionList.add( name )
node = om.MObject()
selectionList.getDependNode( 0, node )
return node

def nameToDag( name ):
selectionList = om.MSelectionList()
selectionList.add( name )
node = om.MDagPath()
selectionList.getDagPath( 0, node )
return node

dag = nameToDag("pSphereShape1" )

meshFn = om.MFnMesh()
meshFn.setObject( dag )

raySource = om.MPoint(0,0,0)
rayDirection = om.MVector(1,0,0)

pointsValue = om.MPointArray()
polygonIdsValue = om.MIntArray()
spaceValue = 4
toleranceValue = om.kMFnMeshPointTolerance

ret = om.MFnMesh.intersect(meshFn, raySource, rayDirection.normal(),
pointsValue, toleranceValue, spaceValue, polygonIdsValue )

print pointsValue[0].x
print pointsValue[0].y
print pointsValue[0].z


Us MEL-folk have a hope at getting this mystical python doodad... I
can smell it... Thank you!

On Mar 28, 10:39 am, "Dean Edmonds" <dean.edmo...@gmail.com> wrote:

jasonosipa

unread,
Mar 29, 2008, 9:33:09 AM3/29/08
to python_inside_maya
So here is my plugin - mostly working at a basic level. I am having
trouble understanding how to pass an object name to be used in the
calculation. I am trying here with a message attribute, but I am
messing something up along the way, in how I work with it inside the
compute function. If you replace polyShape in the line
"selectionList.add ( polyShape )" with the name of a poly object, it
will work... Thoughts?


import sys
import maya.OpenMaya as om
import maya.OpenMayaMPx as omMPx

nodeName = "joRay"
nodeId = om.MTypeId(0x919191)

class joRayNode(omMPx.MPxNode):

INPUTSH = om.MObject()
INPUTRS = om.MObject()
INPUTRD = om.MObject()
OUTPOS = om.MObject()

def __init__ ( self ) :
omMPx.MPxNode.__init__(self)

def compute ( self, plug, dataBlock ) :

if ( plug == joRayNode.OUTPOS ) :

polyShape = dataBlock.inputValue ( joRayNode.INPUTSH )
rs = dataBlock.inputValue ( joRayNode.INPUTRS )
rd = dataBlock.inputValue ( joRayNode.INPUTRD )
outputHandle = dataBlock.outputValue ( joRayNode.OUTPOS )

shDataHandle = polyShape.asString()
rsDataHandle = rs.asFloat3()
rdDataHandle = rd.asFloat3()

selectionList = om.MSelectionList()
selectionList.add ( polyShape )
dag = om.MDagPath ()
selectionList.getDagPath ( 0, dag )

meshFn = om.MFnMesh()
meshFn.setObject ( dag )

raySource = om.MPoint ( rsDataHandle[0], rsDataHandle[1],
rsDataHandle[2] )
rayDirection = om.MVector ( rdDataHandle[0], rdDataHandle[1],
rdDataHandle[2] )

polygonIdsValue = om.MIntArray()
toleranceValue = om.kMFnMeshPointTolerance
pointsValue = om.MPointArray()
spaceValue = 4

hit = om.MFnMesh.intersect(meshFn, raySource, rayDirection,
pointsValue, toleranceValue, spaceValue, polygonIdsValue )

if hit:
result = ( pointsValue[0].x, pointsValue[0].y, pointsValue[0].z )

else:
result = ( raySource.x, raySource.y, raySource.z )

outputHandle.set3Float ( result[0], result[1], result[2] )
dataBlock.setClean ( plug )


def nodeCreator () :
return omMPx.asMPxPtr ( joRayNode() )

def nodeInit () :

msgAttr = om.MFnMessageAttribute()
joRayNode.INPUTSH = msgAttr.create("collider","c")
msgAttr.setStorable(1)

numAttr = om.MFnNumericAttribute()
joRayNode.INPUTRS = numAttr.create ( "raySource", "rs",
om.MFnNumericData.k3Float, 0.0 )
numAttr.setStorable(1)

numAttr = om.MFnNumericAttribute()
joRayNode.INPUTRD = numAttr.create ( "rayDirection", "rd",
om.MFnNumericData.k3Float, 0.0 )
numAttr.setStorable(1)

numAttr = om.MFnNumericAttribute()
joRayNode.OUTPOS = numAttr.create ( "outPosition", "op",
om.MFnNumericData.k3Float, 0.0 )
numAttr.setStorable(1)
numAttr.setWritable(1)

joRayNode.addAttribute ( joRayNode.INPUTSH )
joRayNode.addAttribute ( joRayNode.INPUTRS )
joRayNode.addAttribute ( joRayNode.INPUTRD )
joRayNode.addAttribute ( joRayNode.OUTPOS )

joRayNode.attributeAffects ( joRayNode.INPUTSH,
joRayNode.OUTPOS )
joRayNode.attributeAffects ( joRayNode.INPUTRS,
joRayNode.OUTPOS )
joRayNode.attributeAffects ( joRayNode.INPUTRD,
joRayNode.OUTPOS )

def initializePlugin(mobject):
mplugin = omMPx.MFnPlugin ( mobject )
try:
mplugin.registerNode ( nodeName, nodeId, nodeCreator,
nodeInit )
except:
sys.stderr.write ( "Error loading" )
raise

def uninitializePlugin ( mobject ):
mplugin = omMPx.MFnPlugin ( mobject )
try:
mplugin.deregisterNode ( nodeId )
except:
sys.stderr.write ( "Error removing" )
raise

Chad Vernon

unread,
Mar 29, 2008, 1:39:44 PM3/29/08
to python_in...@googlegroups.com
With nodes, you usually only want to get data that is connected to that node.  You shouldn't really look outside the node (e.g. your "selectionList.add ( polyShape )" line). In this case you should make an attribute that accepts meshes and read the mesh from that attribute instead of looking outside the node for the mesh.

# Create the attribute
tAttr = om.MFnTypedAttribute()
joRayNode.mesh = tAttr.create( 'colliderMesh', 'colliderMesh', om.MFnData.kMesh )
joRayNode.addAttribute( joRayNode.mesh )
joRayNode.attributeAffects( joRayNode.mesh, joRayNode.OUTPOS )


# To get the data in the compute method
fnMesh = om.MFnMesh( dataBlock.inputValue(joRayNode.mesh).asMesh() )


You'll need to connect the worldMesh to this plug to this attribute to get the correct world space calculation.

Chad
--
www.chadvernon.com

jasonosipa

unread,
Mar 29, 2008, 2:08:38 PM3/29/08
to python_inside_maya
Thank you! That got me where I needed to go:



import sys
import maya.OpenMaya as om
import maya.OpenMayaMPx as omMPx

nodeName = "joRay"
nodeId = om.MTypeId(0x919191)

class joRayNode(omMPx.MPxNode):

MESH = om.MObject()
INPUTRS = om.MObject()
INPUTRD = om.MObject()
OUTPOS = om.MObject()

def __init__ ( self ) :
omMPx.MPxNode.__init__(self)

def compute ( self, plug, dataBlock ) :

if ( plug == joRayNode.OUTPOS ) :

meshFn =
om.MFnMesh( dataBlock.inputValue(joRayNode.MESH).asMesh() )
rs = dataBlock.inputValue ( joRayNode.INPUTRS )
rd = dataBlock.inputValue ( joRayNode.INPUTRD )
outputHandle = dataBlock.outputValue ( joRayNode.OUTPOS )

rsDataHandle = rs.asFloat3()
rdDataHandle = rd.asFloat3()

raySource = om.MPoint ( rsDataHandle[0], rsDataHandle[1],
rsDataHandle[2] )
rayDirection = om.MVector ( rdDataHandle[0], rdDataHandle[1],
rdDataHandle[2] )

polygonIdsValue = om.MIntArray()
toleranceValue = om.kMFnMeshPointTolerance
pointsValue = om.MPointArray()
spaceValue = 4

hit = om.MFnMesh.intersect(meshFn, raySource, rayDirection,
pointsValue, toleranceValue, spaceValue, polygonIdsValue )

if hit:
result = ( pointsValue[0].x, pointsValue[0].y, pointsValue[0].z )

else:
result = ( raySource.x, raySource.y, raySource.z )

outputHandle.set3Float ( result[0], result[1], result[2] )
dataBlock.setClean ( plug )


def nodeCreator () :
return omMPx.asMPxPtr ( joRayNode() )

def nodeInit () :

tAttr = om.MFnTypedAttribute()
joRayNode.MESH = tAttr.create ( "colliderMesh", "c",
om.MFnData.kMesh )
tAttr.setStorable(1)

numAttr = om.MFnNumericAttribute()
joRayNode.INPUTRS = numAttr.create ( "raySource", "rs",
om.MFnNumericData.k3Float, 0.0 )
numAttr.setStorable(1)

numAttr = om.MFnNumericAttribute()
joRayNode.INPUTRD = numAttr.create ( "rayDirection", "rd",
om.MFnNumericData.k3Float, 0.0 )
numAttr.setStorable(1)

numAttr = om.MFnNumericAttribute()
joRayNode.OUTPOS = numAttr.create ( "outPosition", "op",
om.MFnNumericData.k3Float, 0.0 )
numAttr.setStorable(1)
numAttr.setWritable(1)

joRayNode.addAttribute ( joRayNode.MESH )
joRayNode.addAttribute ( joRayNode.INPUTRS )
joRayNode.addAttribute ( joRayNode.INPUTRD )
joRayNode.addAttribute ( joRayNode.OUTPOS )

joRayNode.attributeAffects ( joRayNode.MESH, joRayNode.OUTPOS )
joRayNode.attributeAffects ( joRayNode.INPUTRS, joRayNode.OUTPOS )
joRayNode.attributeAffects ( joRayNode.INPUTRD, joRayNode.OUTPOS )

def initializePlugin(mobject):
mplugin = omMPx.MFnPlugin ( mobject )
try:
mplugin.registerNode ( nodeName, nodeId, nodeCreator, nodeInit )
except:
sys.stderr.write ( "Error loading" )
raise

def uninitializePlugin ( mobject ):
mplugin = omMPx.MFnPlugin ( mobject )
try:
mplugin.deregisterNode ( nodeId )
except:
sys.stderr.write ( "Error removing" )
raise




now I am going to try (in on particular order):

1) make the attributes proper compound attributes, with x,y,z
attributes
2) have more info coming out (did I even get an intersection?, what is
the closest face/vert/uv to the intersection?)
3) make it work properly for NO intersection, right now, it always
forces a hit to the surface, I want the output to stay at the
raySource, if it doesn't collide

* some of these may mean moving to .closestIntersection
from .intersection

jasonosipa

unread,
Mar 29, 2008, 3:50:04 PM3/29/08
to python_inside_maya
At this point, I'm basically logging anything I learn here, so, for
folks who don't really know whats up like me, and the API docs aren't
as clear to you as oyu might hope - here's info on how I'm working
towards my 3 gaols I just laid out. To get the attributes to have x,y
and z components under a main one, I had to take the lines like this:

joRayNode.INPUTRS = numAttr.create ( "raySource",
"rs",om.MFnNumericData.k3Float, 0.0 )

and change them to look like this:

joRayNode.INPUTRS = numAttr.createPoint ( "raySource", "rs" )


I kept trying to find a way to change/get at the .k3Float part of the
line (looking at MFnNumericData docs), when in fact, the real thing to
do is change the CREATE portion of the command (looking at
MFnNumericAttribute docs). Create makes an attr and type, but you can
tell it to do more than that. Create seems to give you a generic
attr, or group of attrs, but createPoint treats it as if it expects
x,y, and z data. It looks like in there is how you would set things
to be color extensions (r,g,b) too, with .createColor. Neato.

And I think I am starting to get what Object Oriented Programming
means in MEL-guy-terms: is it effectively just like saying "You RENAME
any command you might want to use, and then use that renamed command
to do the work"? Thats what it is starting to feel like. I know that
in reality, it is in an instance and not a replaced renamed version,
and that "command" might be the wrong word to use, but to the
uninitiated, it might seem like you are giving your commands/functions
a new name. This gets confusing (at least to begin with ) because
numAttr.createPoint and hat.createPoint would actually do the exact
same thing, and coming from MEL, that makes you feel like you are
missing something, and wouldn't know where to look for documentation,
since the core doodad (numAttr or hat) is of a flexible name! To look
to previous lines and find numAttr, you get "numAttr =
om.MFnNumericAttribute()" and that is to realize how to look up info
on what you can do with numAttr - its just exactly what you can do
with MFnNumericAttribute. Again, to MEL-head, I just want to type
om.MFnNumbericAttribute.createPoint, but I'm seeing that's not how it
works, I need to do that little dance to some name like numAttr to
then go "numAttr.createPoint". This will take some getting used to!

Anyways, with (potentially flawed) idea in mind, I've been feeling the
fog lift a little but more...

Jason Osipa

unread,
Mar 29, 2008, 5:16:36 PM3/29/08
to python_in...@googlegroups.com
In Dean's example here, hitPoint was set up to catch output values.  Despite my best efforts, I can't figure out how to do the same for hitFace.  I looked up INT, SHORT, all things I could figure out, and nothing I could understand really seems to do the same for ints...

       hitPoint = om.MFloatPoint()


       gotHit = meshFn.closestIntersection(
               raySource, rayDirection,
               faceIds, triangleIds,

               sortIds, om.MSpace.kWorld, maxDist, bothDirections,
               noAccelerator,
               hitPoint,
               hitParam, hitFace, hitTriangle, hitBary1, hitBary2
       )

Dean Edmonds

unread,
Mar 29, 2008, 11:36:03 PM3/29/08
to python_in...@googlegroups.com
On Sat, Mar 29, 2008 at 2:16 PM, Jason Osipa <jason...@gmail.com> wrote:
> In Dean's example here, hitPoint was set up to catch output values. Despite
> my best efforts, I can't figure out how to do the same for hitFace. I
> looked up INT, SHORT, all things I could figure out, and nothing I could
> understand really seems to do the same for ints...

Yes, I very deliberately avoided retrieving any values whose parameters
were pointers or references to simple types like 'int' because that's more
complex.

If you pass a python object to a function the function can modify attributes
of that object and the new values of those attributes will be available to the
caller. So we can pass down an MPoint object, let the closestIntersection
method change its x, y and z attributes and then have those new attribute
values available to the caller when the method returns.

With simple types like integers and floats, that approach won't work
because there are no attributes whose values the called function can
modify. So there's no way for closestIntersection to pass the hitFace
back to us because it is a simple integer.

To work around this the Maya API provides the MScriptUtil class.

Since the 'hitFace' parameter is a pointer to an int we first use
MScriptUtil to create a pointer to an int:

hitFacePtr = om.MScriptUtil().asIntPtr()

We pass that pointer to the closestIntersection method:

gotHit = meshFn.closestIntersection(
raySource, rayDirection,
faceIds, triangleIds,
sortIds, om.MSpace.kWorld, maxDist, bothDirections,
noAccelerator,
hitPoint,

hitParam, hitFacePtr, hitTriangle, hitBary1, hitBary2
)

Finally, we use MScriptUtil again to retrieve the integer value from
the pointer:

hitFace = om.MScriptUtil(hitFacePtr).asInt()

--
-deane

Ofer Koren

unread,
Mar 30, 2008, 6:39:27 AM3/30/08
to python_in...@googlegroups.com
And I think I am starting to get what Object Oriented Programming
means in MEL-guy-terms: is it effectively just like saying "You RENAME
any command you might want to use, and then use that renamed command
to do the work"?  Thats what it is starting to feel like. 

OOP is very much summerized with this syntax:
    object.member

This dot syntax separates the object on the left side, from its 'member' on the right side. The dot is like the posessive apostrophe in English (the object's member).
In the Maya API a member is usually a function ("method" in OOP terminology). The difference between these object-functions and the functions (commands) that exist in MEL, is that they operate on the object to which they belong, either by using its data to perform an action, or modify its data.
So instead of (for example):
    getScale(object)
you would say:
   object.getScale()

Why say it that way? because 'getScale' is explicitly and firmly tied to the object on which it operates. There is no 'getScale' function that 'lives' by itself - only the one tied to the objects of the appropriate type.
The command itself is never 'renamed', just the object through which it is invoked. It's like:
Jason.walk()   # 'walk' is a method, a member of the "Jason" object
Ofer.eat()      
# 'eat' is a method, a member of the "Ofer" object
a = Ofer.height     # 'height' is an attribute,  a member of the "Ofer" object...
b = Chad.height   
# 'height' is an attribute,  a member of the "Chad" object...
...

Hopefully I haven't confused anyone....



--


- Ofer
www.mrbroken.com

jasonosipa

unread,
Apr 1, 2008, 4:01:14 PM4/1/08
to python_inside_maya
Here are the results of everyone's help!

http://highend3d.com/maya/downloads/plugins/polygon/oeRay-py-5167.html

Thank you!


On Mar 30, 3:39 am, "Ofer Koren" <kor...@gmail.com> wrote:
> And I think I am starting to get what Object Oriented Programming
> means in MEL-guy-terms: is it effectively just like saying "You RENAME
> any command you might want to use, and then use that renamed command
> to do the work"? Thats what it is starting to feel like.
>
> OOP is very much summerized with this syntax:
> object.member
>
> This dot syntax separates the object on the left side, from its 'member' on
> the right side. The dot is like the posessive apostrophe in English (the
> object's member).
> In the Maya API a member is usually a function ("method" in OOP
> terminology). The difference between these object-functions and the
> functions (commands) that exist in MEL, is that they* operate on the object
> to which they belong*, either by using its data to perform an action, or
> modify its data.
> So instead of (for example):
> getScale(object)
> you would say:
> object.getScale()
>
> Why say it that way? because 'getScale' is explicitly and firmly tied to the
> object on which it operates. There is no 'getScale' function that 'lives' by
> itself - only the one tied to the objects of the appropriate type.
> The command itself is never 'renamed', just the object through which it is
> invoked. It's like:
> Jason.walk() # 'walk' is a method, a member of the "Jason" object
> Ofer.eat() # 'eat' is a method, a member of the "Ofer" object
> a = Ofer.height # 'height' is an attribute, a member of the "Ofer"
> object...
> b = Chad.height # 'height' is an attribute, a member of the "Chad"
> object...
> ...
>
> Hopefully I haven't confused anyone....
>
> On Sat, Mar 29, 2008 at 8:36 PM, Dean Edmonds <dean.edmo...@gmail.com>
> wrote:
>
>
>
>
>
> > On Sat, Mar 29, 2008 at 2:16 PM, Jason Osipa <jason.os...@gmail.com>
Reply all
Reply to author
Forward
0 new messages