Announcements

Between mid-October and November, the content on AREA will be relocated to the Autodesk Community M&E Hub and the Autodesk Community Gallery. Learn more HERE.

Getting compute() to run on transform

Getting compute() to run on transform

Anonymous
Not applicable
684 Views
3 Replies
Message 1 of 4

Getting compute() to run on transform

Anonymous
Not applicable

Hi all.

 

I'm writing a simple collision script and I'm having trouble getting its compute() method to run when I want it to. 

 

The script takes a start point and an input mesh. If the start point is inside the mesh it computes the closest point on that mesh and sets the out point to that value. 

 

This method works fine when I'm not using nodes but I'm having trouble getting it to work in real time. Right now all I'm taking as inputs are a kMesh (the outMesh attribute on a shape node) and a k3Double for the start point. 

 

I'd like to have it so that the compute() method runs when either vertices of the geometry get transformed (say with a skin cluster) or when I move the transform node of that shape. I'm not sure if I'm missing an input attribute or not. I'm using an MpxNode.

 

Here's my rough code so far:

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


class CollisionNode(omMPx.MPxNode):
    # give it a unique id
    id = om.MTypeId(0x20218)

    # declare our input attributes
    inMesh = om.MObject()

    startPoint = om.MObject()

    # declare our output attributes
    outPoint = om.MObject()

    def compute(self, plug, data):
        if plug not in [CollisionNode.outPoint]:
            raise ValueError("kUnknownParameter")
        print "computing!" # only computes on init. Not running whenever the object or its verts are transformed...

        # make our MDataHandles
        inMeshHandle = data.inputValue(CollisionNode.inMesh)
        startPointHandle = data.inputValue(CollisionNode.startPoint)
        outPointHandle = data.outputValue(CollisionNode.outPoint)

        # check the data is the right type. If so, get it as its correct type
        inMeshVal = inMeshHandle.asMeshTransformed()
        startPointVal = startPointHandle.asDouble3()

        # now we can pass that data on to our function
        startPointVector = om.MVector(startPointVal[0], startPointVal[1], startPointVal[2])
        isInside = self.pointInPrimitive(inMeshVal, startPointVector)
        if isInside:
            startPointAsPoint = om.MPoint(startPointVector.x, startPointVector.y, startPointVector.z)
            collisionPoint = self.simpleCollision(inMeshVal, startPointAsPoint)
            outPointHandle.set3Double(collisionPoint[0], collisionPoint[1], collisionPoint[2])
        else:
            outPointHandle.set3Double(startPointVal[0], startPointVal[1], startPointVal[2])

        data.setClean(plug)

    # Fast method to determine if a point is inside a convex polygon primitive
    def pointInPrimitive(self, meshDag, rayOrigin):
        """
        :param meshDag: MDagPath(): the mesh we want to hit
        :param rayOrigin: MVector(): represents the start of our ray
        :return: Boolean: If the point is inside our polygon or not
        """

        # use a MItMeshPolygon iterator. MUCH faster than a for loop
        itPolys = om.MItMeshPolygon(meshDag)

        # for every polygon in our mesh:
        while not itPolys.isDone():
            # See if the vector from our origin point to a point in that poly dot the normal is > 0.
            # If they all are all greater than zero then the point is inside the mesh
            thisPolyNormal = om.MVector()
            itPolys.getNormal(thisPolyNormal, om.MSpace.kWorld)
            pointsOnPoly = om.MPointArray()
            itPolys.getPoints(pointsOnPoly, om.MSpace.kWorld)
            pointVector = om.MVector(pointsOnPoly[0].x, pointsOnPoly[0].y, pointsOnPoly[0].z)

            originToPointVector = pointVector - rayOrigin

            # if the dot product between these two is less then zero then the point is outside of our mesh
            if originToPointVector * thisPolyNormal < 0:
                return False

            itPolys.next()

        # if we made it through the loop without anything returning false then our point is within the mesh
        return True

    def simpleCollision(self, meshDag, p0):
        """
        :param meshDag: MDagPath: Dag path of a polygon primitive
        :param p0: MPoint: The original point that is inside our mesh (detected using bm_pointInPrimitive
        :return: MVector: The new point that is the closest point outside of our mesh
        """

        fnMesh = om.MFnMesh(meshDag)

        # find the closest point on our mesh from the point that's inside the mesh
        closestPoint = om.MPoint()
        fnMesh.getClosestPoint(p0, closestPoint, om.MSpace.kWorld)

        return om.MVector(closestPoint.x, closestPoint.y, closestPoint.z)


def creator():
    # standard creator function for a Maya plugin
    return omMPx.asMPxPtr(CollisionNode())


def initialize():
    # shorten OpenMaya attributes as variables for readability
    tAttr = om.MFnTypedAttribute()
    nAttr = om.MFnNumericAttribute()

    """ Input Attributes """
    # create our input attribute of type kFloat and a default value 0.0
    CollisionNode.inMesh = tAttr.create("inputMesh", "inMesh", om.MFnData.kMesh)
    # set only to storable
    tAttr.setStorable(True)
    # add our attribute to the node
    CollisionNode.addAttribute(CollisionNode.inMesh)

    CollisionNode.startPoint = nAttr.create("startPoint", "sp", om.MFnNumericData.k3Double)
    # input values should be keyable, storable, readable, and writable
    nAttr.setKeyable(True)
    nAttr.setStorable(True)
    nAttr.setReadable(True)
    nAttr.setWritable(True)
    # add our attribute to the node
    CollisionNode.addAttribute(CollisionNode.startPoint)

    """ Output Attributes """
    CollisionNode.outPoint = nAttr.create("outPoint", "op", om.MFnNumericData.k3Double)
    # input values should be keyable, storable, readable, and writable
    nAttr.setKeyable(False)
    nAttr.setStorable(False)
    nAttr.setReadable(True)
    nAttr.setWritable(False)
    # add our attribute to the node
    CollisionNode.addAttribute(CollisionNode.outPoint)

    CollisionNode.attributeAffects(CollisionNode.startPoint, CollisionNode.outPoint)


def initializePlugin(obj):
    plugin = omMPx.MFnPlugin(obj, "Ben Morgan", "1.0", "Any")
    try:
        plugin.registerNode("collisionNode", CollisionNode.id, creator, initialize)
    except:
        raise RuntimeError("Failed to register node")


def uninitializePlugin(obj):
    plugin = omMPx.MFnPlugin(obj)
    try:
        plugin.deregisterNode(CollisionNode.id)
    except:
        raise RuntimeError("Failed to register node")

 

0 Likes
685 Views
3 Replies
Replies (3)
Message 2 of 4

cheng_xi_li
Autodesk Support
Autodesk Support

Hi,

 

For another node's world transform, you could try the method I've used in my blog. For deformers, if you are connecting it to the modified shape's output mesh, it should be fine.

 

If you want your node works as a shape, you probably need to implement MPxSurfaceShape.

 

BTW:asMeshTransformed won't give you a MDagPath. It works because MItMeshPolygon accepts a MObject(mesh) input.

 

Yours,

Li

0 Likes
Message 3 of 4

Anonymous
Not applicable

Hi Li, thanks again.

 

I'm not sure I'm understanding. Your blog talks about using message attributes for certain attributes only accessible via the API but I'm able to connect the outMesh of the shape to affect the output. 

 

Excuse my ignorance, but can a Deformer be used to give the output I'm looking for? (At a surface level) I always assumed deformers would take an input mesh, do something with it, and output a mesh. But you're saying a deformer could take an input mesh, a point, and output a point? 

 

Thanks for helping me over this learning curve,

 

Ben

0 Likes
Message 4 of 4

cheng_xi_li
Autodesk Support
Autodesk Support

Hi,

 

Please look at the callbacks I've used in my blog. It demonstrates how to add a callback when you are connecting two nodes. One of them is a transform callback.

 

I am not saying deformer could take a point or suitable for this job... I mean if you connect a deformer to the original shape, and use that shape's outMesh, you should get deformed mesh. If original shape's outMesh has been dirtied. Your node should be computed on demand.

 

Yours,

Li

0 Likes