{"id":4335,"date":"2020-03-06T13:08:38","date_gmt":"2020-03-06T21:08:38","guid":{"rendered":"https:\/\/trikingo.com\/?p=4335"},"modified":"2020-03-06T13:41:55","modified_gmt":"2020-03-06T21:41:55","slug":"custom-aim-constraint-node","status":"publish","type":"post","link":"https:\/\/trikingo.com\/custom-aim-constraint-node\/","title":{"rendered":"Custom Aim Constraint Node"},"content":{"rendered":"\n<h2>Python plugin tutorial<\/h2>\n\n\n\n<p>Finding examples online about OpenMaya and python plugin when you are starting to learn is always difficult! So I decided to put together a simple and basic tutorial for creating a python plugin that will create a custom node.<\/p>\n\n\n\n<p>This plugin will create a node with a simple Aim constraint between a driven object and a driver object. Then we will expand it so the driven object will be guided by 2 driver objects, following each by 50%, and the option for an offset value.<\/p>\n\n\n\n<p>To achieve this, we will be using some matrix and vector calculations, but don\u2019t worry if you are not familiar with them, I will try to explain step by step what\u2019s happening!<\/p>\n\n\n\n<p>Without further delay, here is the python script. Be sure to read the yellow lines for explanation.<\/p>\n\n\n\n<p>To install, copy this file in your \/maya\/plug-ins\/ folder, save it as customAimConstraint.py<br>In a python tab, run this:<br>maya.cmds.loadPlugin(\u201ccustomAimConstraint.py\u201d)<br>maya.cmds.createNode(\u2018customAimConstraint\u2019, n=\u2019MyCustomNode\u2019)<\/p>\n\n\n\n<div class=\"wp-block-image is-style-circle-mask\"><figure class=\"aligncenter size-large\"><img loading=\"lazy\" width=\"240\" height=\"300\" src=\"https:\/\/i0.wp.com\/trikingo.com\/wp-content\/uploads\/2020\/03\/nodeSimple.png?resize=240%2C300&#038;ssl=1\" alt=\"\" class=\"wp-image-4337\" data-recalc-dims=\"1\"\/><\/figure><\/div>\n\n\n\n<div style=\"height: 250px; position:relative; margin-bottom: 50px;\" class=\"wp-block-simple-code-block-ace\"><pre class=\"wp-block-simple-code-block-ace\" style=\"position:absolute;top:0;right:0;bottom:0;left:0\" data-mode=\"python\" data-theme=\"monokai\" data-fontsize=\"14\" data-lines=\"50\" data-showlines=\"true\" data-copy=\"false\">import maya.OpenMayaMPx as OpenMayaMPx\nimport maya.OpenMaya as OpenMaya\n\n\n''' \nTo load the plugin we need to type this in the script editor:\n# maya.cmds.loadPlugin(\"customAimConstraint.py\")\n# And to create our custom node, we use this: \n# maya.cmds.createNode('customAimConstraint')\n'''\nclass CustomAimConstraint(OpenMayaMPx.MPxNode):\n\tkPluginNodeId = OpenMaya.MTypeId(0x00047251)\n\t''' Define the inputs and outputs. We use MOject, and leave them empty for now. Later we will plug the correct information on them.'''\n\t''' The driven object '''\n\tmInputDriven = OpenMaya.MObject()\n\t''' The first driver '''\n\tmInputDriverA = OpenMaya.MObject()\n\t''' The second driver '''\n\tmInputDriverB = OpenMaya.MObject()\n\t''' We will add an offset object, so we can control rotation independently.'''\n\tmRotateOffset = OpenMaya.MObject()\n\t''' The output of the node. This will be connected to the Rotation back into the Driven object.'''\n\trOutput = OpenMaya.MObject()\n\t''' The up vector, this one is important if we want to switch between up vector (x, y, z).'''\n\tupInput = OpenMaya.MObject()\n\n\tdef __init__(self):\n\t\tOpenMayaMPx.MPxNode.__init__(self)\n\n\tdef compute(self, plug, data):\n\t\t'''\n\t\tMaya calls this method when it determines that one of our node's output attributes, or plugs, \n\t\tis out-of-date and needs to be re-computed. \n\t\tIt passes as arguments the plug that needs to be recomputed, and a data block that contains the \n\t\tcurrent values of the input and output \tattributes for the current instance of your node. \n\t\tThis compute() method is expected to re-calculate the value of the requested plug based on the \n\t\tcurrent values of the node's input attributes, and to store the computed output values back into the data block.\n\t\tWe need to assume that our node will be re-evaluated frequently, so we need to make sure its \n\t\tcalculations are as efficient as possible.\n\t\tFor example, during rendering a node may need to be evaluated several times per pixel. \n\t\tIf you do not take care to make its computations as fast as possible, \n\t\tthe dependency graph can easily become a bottleneck.\n\t\t'''\n\t\tif plug != CustomAimConstraint.rOutput:\n\t\t\treturn OpenMaya.MStatus.kUnknownParameter\n\t\t''' \n\t\tNext we need to define a couple of matrices to store our object transform information. \n\t\tWe will be declaring 4 of them, since we want 4 objects.\n\t\tNotice that this is getting the data from the inputs of the node, and using them asMatrix(). \n\t\tThis info comes from, when the object is connected to the node through the Node Editor, \n\t\tor through cmds.connectAttribute command.\n\t\tMatrix for our driven object.\n\t\t'''\n\t\tworldMatrixDriven = data.inputValue(CustomAimConstraint.mInputDriven).asMatrix()\n\t\t''' Matrix for our first driver object.'''\n\t\tworldMatrixDriverA = data.inputValue(CustomAimConstraint.mInputDriverA).asMatrix()\n\t\t''' \n\t\tMatrix for our second driver object. Note that this one is an extra for demonstration purposes, \n\t\tsince the aim will still work 100% with only 1 Driver.\n\t\tAdding a second object will split the Driven object to follow each driver by 50% each.\n\t\t'''\n\t\tworldMatrixDriverB = data.inputValue(CustomAimConstraint.mInputDriverB).asMatrix()\n\t\t''' Matrix for our offset object. This is also not necessary, but for demonstratio purposes we will be adding \n\t\tan extra object to independently affect the rotation of the Driven object.\n\t\t A similar approach can be used if you want to create a \"Maintain Offset\" option. Just store the initial \n\t\t values of the driver.WorldMatrix here.\n\t\t '''\n\t\toffsetMatrix = data.inputValue(CustomAimConstraint.mRotateOffset).asMatrix()\n\t\t''' We get the up input plug from the node.'''\n\t\tupV = data.inputValue(CustomAimConstraint.upInput).asInt()\n\t\t''' \n\t\tAn MTransformationMatrix allows the manipulation of the individual transformation components\n\t\t(eg scale, rotation, shear, etc) of a four by four \n\t\ttransformation matrix. We will store this MTransformationMatrix for each of the objects we have, \n\t\tsince we will be using the Translation later.\n\t\t'''\n\t\twmA = OpenMaya.MTransformationMatrix(worldMatrixDriven)\n\t\twmB = OpenMaya.MTransformationMatrix(worldMatrixDriverA)\n\t\twmP = OpenMaya.MTransformationMatrix(worldMatrixDriverB)\n\t\t''' Next we get the translation component of the translation as a vector in centimeters, again for each of the objects.'''\n\t\ttransDriven = wmA.getTranslation( OpenMaya.MSpace.kTransform )\n\t\ttransDriverA = wmB.getTranslation( OpenMaya.MSpace.kTransform )\n\t\ttransDriverB = wmP.getTranslation( OpenMaya.MSpace.kTransform )\n\t\t''' \n\t\tMVector provides access to Maya's internal vector math library allowing vectors to be handled easily, \n\t\tand in a manner compatible with internal Maya data structures.\n\t\tUsing the translation information we have from the objects, we substract both Driver's translation to our \n\t\tDriven's translation, so we know how much the objects moved in relation to the Driven.\n\t\t'''\n\t\txv = OpenMaya.MVector(transDriven.x-transDriverA.x-transDriverB.x, transDriven.y-transDriverA.y-transDriverB.y, transDriven.z-transDriverA.z-transDriverB.z)\n\t\t''' Normalize the vector's info.'''\n\t\txv.normalize()\n\t\t''' Based on user input, we set the vector accordingly. Can be set on run time changing the attribute directly on the node.'''\n\t\tif upV==0:\n\t\t\tup=[1,0,0]\n\t\telif upV==1:\n\t\t\tup=[0,1,0]\n\t\telif upV==2:\n\t\t\tup=[0,0,1]\n\t\t''' Here we get the vector according to what the user chose. We also need to normalize its data.'''\n\t\tzv = xv ^ OpenMaya.MVector(-up[0], -up[1], -up[2])\n\t\tzv.normalize()\n\t\tyv = xv ^ zv\n\t\tyv.normalize()\n\t\t''' \n\t\tA MMatrix provides access to Maya's internal matrix math library allowing matrices to be handled easily, \n\t\tand in a manner compatible with internal Maya data structures.\n\t\t'''\n\t\tmtx = OpenMaya.MMatrix()\n\t\t''' We create a list with all the values we obtained from our vectors. '''\n\t\tl = [xv.x, xv.y, xv.z, 0, yv.x, yv.y, yv.z, 0, zv.x, zv.y, zv.z, 0, 0, 0, 0, 1]\n\t\t''' We use MScriptUtil to convert the list into a matrix.'''\n\t\tOpenMaya.MScriptUtil.createMatrixFromList(l, mtx)\n\t\t''' Multiply times the maintain offset object's world matrix. If no input provided, matrix is unaffected'''\n\t\tmtx*=offsetMatrix\n\t\t''' Get euler rotations and convert it to radians.'''\n\t\tgetFinalRot = OpenMaya.MTransformationMatrix(mtx).eulerRotation() * 57.2958\n\t\t''' Here we define the plug output of our custom node.'''\n\t\trOutput = data.outputValue(CustomAimConstraint.rOutput)\n\t\t''' \n\t\tConvert our rotations to a MFloatVector, and assing it to the output plug, \n\t\tso that the node will write this information and we will be able to get it and connect it back to \n\t\tthe rotation of our Driven, without having to use composeMatrix node in the Node Editor.\n\t\t'''\n\t\tresultRot = OpenMaya.MFloatVector(getFinalRot[0], getFinalRot[1], getFinalRot[2])\n\t\trOutput.setMFloatVector(resultRot)\n\t\treturn \n\n''' This section initializes the plugin so Maya can read it. Python plugins can be imported from \/maya\/plug-ins\/ folder. '''\ndef creator():\n\treturn OpenMayaMPx.asMPxPtr(CustomAimConstraint())\n\ndef initialize():\n\t''' Here we define some varialbes that can be numeric, strings, or other type of attributes.'''\n\tnAttr = OpenMaya.MFnNumericAttribute()\n\tnMAttr = OpenMaya.MFnMatrixAttribute() \n\t'''\n\tWe name our inputs. This names will be shown in the Node Editor. First value is the long name, second the short name.\n\tIn this case, we want the upVector to be and integer, since we are expecting 0,1 or 2.\n\t'''\n\tCustomAimConstraint.upInput = nAttr.create('upVector', 'upV',OpenMaya.MFnNumericData.kInt, 0)\n\t''' Remember in the compute() method we need to extract the information from this input as follows:\n\t0 = (1,0,0) | 1 = (0,1,0) | 2 = (0,0,1)\n\tSet it to be writable, storable and readable, so the user can modify it.\n\t'''\n\tnAttr.setWritable(True)\n\tnAttr.setStorable(True)\n\tnAttr.setReadable(True)\n\t''' We set the minimum to 0 and maximum to 2.'''\n\tnAttr.setMin(0)\n\tnAttr.setMax(2)\n\tnAttr.setKeyable(True)    \n\t''' Same for our driven object, but this time we expect a kDouble attribute, which will receive the WorldMatrix in the Node Editor'''\n\tCustomAimConstraint.mInputDriven = nMAttr.create('inDriven', 'inD',OpenMaya.MFnMatrixAttribute.kDouble)\n\tnMAttr.setWritable(True)\n\tnMAttr.setStorable(True)\n\tnMAttr.setReadable(True)\n\tnMAttr.setKeyable(True)\n\t''' Our first driver object.'''\n\tCustomAimConstraint.mInputDriverA = nMAttr.create('inDriverA', 'inA',OpenMaya.MFnMatrixAttribute.kDouble)\n\tnMAttr.setWritable(True)\n\tnMAttr.setStorable(True)\n\tnMAttr.setReadable(True)\n\tnMAttr.setKeyable(True)\n\t''' Second driver object.'''\n\tCustomAimConstraint.mInputDriverB = nMAttr.create('inDriverB', 'inB',OpenMaya.MFnMatrixAttribute.kDouble)\n\tnMAttr.setWritable(True)\n\tnMAttr.setStorable(True)\n\tnMAttr.setReadable(True)\n\tnMAttr.setKeyable(True)\n\t''' Offset object.'''\n\tCustomAimConstraint.mRotateOffset = nMAttr.create('rotateOffset', 'ro',OpenMaya.MFnMatrixAttribute.kDouble)\n\tnMAttr.setWritable(True)\n\tnMAttr.setStorable(True)\n\tnMAttr.setReadable(True)\n\tnMAttr.setKeyable(True)\n\t''' \n\tAnd finally our output. Notice that this type is not receiving a third argument, \n\tsince it will be outputing the result of the compute() method.\n\t'''\n\tCustomAimConstraint.rOutput = nAttr.createPoint(\"outputRotate\", \"or\")\n\tnAttr.setWritable(False)\n\tnAttr.setStorable(False)\n\tnAttr.setReadable(True) \n\t''' Once our attributes are defined, we need to add them to the node.'''\n\tCustomAimConstraint.addAttribute(CustomAimConstraint.upInput)\n\tCustomAimConstraint.addAttribute(CustomAimConstraint.rOutput) \n\tCustomAimConstraint.addAttribute(CustomAimConstraint.mInputDriven)\n\tCustomAimConstraint.addAttribute(CustomAimConstraint.mInputDriverA)\n\tCustomAimConstraint.addAttribute(CustomAimConstraint.mInputDriverB)\n\tCustomAimConstraint.addAttribute(CustomAimConstraint.mRotateOffset)\n\t'''\n\t Finally, we need to define how they are going to interact. Since we only have 1 output, all of our inputs are going to affect it.\n\tattributeAffects defines when the compute() method will be called. For example, if we miss to connect upInput to rOutput, the compute()\n\tmethod will not be called if we change the upVector value, failing to recalculate our node.  \n\t'''\n\tCustomAimConstraint.attributeAffects(CustomAimConstraint.upInput, CustomAimConstraint.rOutput)\n\tCustomAimConstraint.attributeAffects(CustomAimConstraint.mInputDriven, CustomAimConstraint.rOutput)\n\tCustomAimConstraint.attributeAffects(CustomAimConstraint.mInputDriverA, CustomAimConstraint.rOutput)\n\tCustomAimConstraint.attributeAffects(CustomAimConstraint.mInputDriverB, CustomAimConstraint.rOutput)\n\tCustomAimConstraint.attributeAffects(CustomAimConstraint.mRotateOffset, CustomAimConstraint.rOutput)\n\n''' Customize plugin info and register it.'''\ndef initializePlugin(obj):\n\tplugin = OpenMayaMPx.MFnPlugin(obj, 'FelipeR', '1.0', 'Any')\n\ttry:\n\t\tplugin.registerNode('customAimConstraint', CustomAimConstraint.kPluginNodeId, creator, initialize)\n\texcept:\n\t\traise RuntimeError, 'Failed to register node'\n\n''' Call if you want to unload plugin'''\ndef uninitializePlugin(obj):\n\tplugin = OpenMayaMPx.MFnPlugin(obj)\n\ttry:\n\t\tplugin.deregisterNode(CustomAimConstraint.kPluginNodeId)\n\texcept:\n\t\traise RuntimeError, 'Failed to register node'<\/pre><\/div>\n","protected":false},"excerpt":{"rendered":"<p>Finding examples online about OpenMaya and python plugin when you are starting to learn is always difficult! So I decided to put together a simple and basic tutorial for creating a python plugin that will create a custom node.<\/p>\n","protected":false},"author":2,"featured_media":4336,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"spay_email":""},"categories":[73],"tags":[79,75,118,119,76],"jetpack_featured_media_url":"https:\/\/i2.wp.com\/trikingo.com\/wp-content\/uploads\/2020\/03\/nodeComplex.png?fit=768%2C763&ssl=1","_links":{"self":[{"href":"https:\/\/trikingo.com\/api\/wp\/v2\/posts\/4335"}],"collection":[{"href":"https:\/\/trikingo.com\/api\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/trikingo.com\/api\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/trikingo.com\/api\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/trikingo.com\/api\/wp\/v2\/comments?post=4335"}],"version-history":[{"count":2,"href":"https:\/\/trikingo.com\/api\/wp\/v2\/posts\/4335\/revisions"}],"predecessor-version":[{"id":4349,"href":"https:\/\/trikingo.com\/api\/wp\/v2\/posts\/4335\/revisions\/4349"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/trikingo.com\/api\/wp\/v2\/media\/4336"}],"wp:attachment":[{"href":"https:\/\/trikingo.com\/api\/wp\/v2\/media?parent=4335"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/trikingo.com\/api\/wp\/v2\/categories?post=4335"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/trikingo.com\/api\/wp\/v2\/tags?post=4335"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}