{"id":628,"date":"2019-02-01T19:00:43","date_gmt":"2019-02-02T03:00:43","guid":{"rendered":"http:\/\/www.toadstorm.com\/blog\/?p=628"},"modified":"2019-02-01T17:38:03","modified_gmt":"2019-02-02T01:38:03","slug":"maya-where-everything-is-a-string","status":"publish","type":"post","link":"https:\/\/www.toadstorm.com\/blog\/?p=628","title":{"rendered":"Maya: where everything is a string."},"content":{"rendered":"<p>Here&#8217;s a rare Maya post in 2019! I hate Maya, but sometimes it&#8217;s hard to avoid. <\/p>\n<p>One of the most irritating things about writing scripts for Maya is that the Python commands are totally un-Pythonic&#8230; meaning, there&#8217;s no sense of &#8220;objects&#8221; that have intrinsic properties and functions (or in Python terms, &#8220;attributes&#8221; and &#8220;methods&#8221;). You could have a group node in your scene selected and get a reference to it in Python, but your reference is just a string, so you can&#8217;t do things like access the group node&#8217;s transform matrix as a property or function, and worst of all, if the name of the object changes, your reference is no longer valid! That is, if you say <code>myGroup = cmds.ls(sl=True)[0]<\/code>, and at any point the name of that selected group changes, you&#8217;re SOL. This sucks, especially in situations where you&#8217;re trying to rename a list of a bunch of objects, ESPECIALLY when they&#8217;re all parented to each other, and EVEN MORESO when there&#8217;s objects with duplicate names in that stack. It makes my skin crawl.<\/p>\n<p>PyMEL was developed largely to address this sort of thing, but it has a pretty serious startup time when you first initialize it, and it&#8217;s slow as hell to instantiate objects. If you&#8217;re iterating through a thousand objects and generating PyNodes for each one, you&#8217;re in for a long ride. Granted, it comes with all kinds of other neat toys, but if all you want is a consistent handle for objects regardless of name or hierarchy changes, it&#8217;s just way too much overhead.<\/p>\n<p>Now, you might be saying to yourself, &#8220;what about UUIDs&#8221;? Yes, those are pretty magical, until you have two identical file references in the scene, which happens to be the case <em>all the damned time<\/em>. Those UUIDs stay the same between references, so now you&#8217;re stuck trying to identify objects by UUID and namespace. <\/p>\n<p>Here&#8217;s a proposed lightweight solution: use OpenMaya to create a neat little custom Python object that stores a reference to the Maya node, regardless of the node&#8217;s name. Just get the object by name once, and as long as you keep that object in memory, it doesn&#8217;t matter what the object name changes to; you&#8217;ll always have a pointer to it.<\/p>\n<p>Try this out:<\/p>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\r\nimport maya.OpenMaya as om\r\n\r\nclass MayaNode():\r\n    def __init__(self, node_name):\r\n        self._mobject = om.MObject()\r\n        self._mdagpath = om.MDagPath()\r\n        self._node = om.MFnDependencyNode()\r\n        selection = om.MSelectionList()\r\n        try:\r\n            selection.add(node_name)\r\n            selection.getDependNode(0, self._mobject)\r\n            self._node = om.MFnDependencyNode(self._mobject)\r\n            selection.getDagPath(0, self._mdagpath, om.MObject())\r\n        except:\r\n            pass\r\n\r\n    def name(self, long=False):\r\n        if self._mdagpath.isValid():\r\n            if long:\r\n                return self._mdagpath.fullPathName()\r\n            return self._mdagpath.partialPathName()\r\n        else:\r\n            if not self._mobject.isNull():\r\n                return self._node.name()\r\n            return None\r\n<\/pre>\n<p>There&#8217;s some weird stuff in here to take in, since OpenMaya is a C++ API wrapper and follows those conventions. First, the <code>__init__<\/code> constructor for the object. We create default &#8220;empty&#8221; containers for an <code>MObject<\/code>, an <code>MDagPath<\/code>, an <code>MFnDependencyNode<\/code>, and an <code>MSelectionList<\/code>. The <code>MObject<\/code> is a sort of generic catch-all container for Maya API objects. A lot of functions will expect to write to an <code>MObject<\/code>. In C++, unlike Python, much of the time you&#8217;ll provide arguments to the function as variables to be overwritten; these are commonly marked with a &#8220;&#038;&#8221; in the documentation. Functions like <code>MSelectionList::getDependNode()<\/code> have these &#8220;out&#8221; arguments, and expect you to provide an object of the correct type (in this case, an <code>MObject<\/code>) as an argument to be written to. The <code>MDagPath<\/code> object is a container that describes the DAG (Directed Acyclic Graph) path of an object in Maya; that is, the path in the Outliner, like |group1|polySphere1. We&#8217;ll use this to get the full name of DAG objects later on. Next is the <code>MFnDependencyNode<\/code> object. Any object in Maya that doesn&#8217;t show up in the Outliner is often referred to as a &#8220;Dependency Graph&#8221; or &#8220;DG&#8221; node&#8230; things like shaders, textures, etc. Since we want this MayaNode class to apply to any kind of node in the scene, we&#8217;re creating containers for both types just in case. Finally, the <code>MSelectionList<\/code> object, which is used to hold a list of objects&#8230; we&#8217;re just creating this so that we can use the handy <code>getDependNode()<\/code> and <code>getDagPath()<\/code> functions from that class.<\/p>\n<p>Okay, deep breaths. Now that we&#8217;ve created these objects, we can actually try to turn them into something. First, we tell our <code>MSelectionList<\/code> object to add the node name that we want&#8230; this is the only time we&#8217;ll ever need to refer to this object by it&#8217;s &#8220;MEL-like&#8221; name. Once the list is populated with this one object, we can use its <code>getDependNode()<\/code> function to take the 0th index of our selection list (since there&#8217;s only ever one object in there) and return its <code>MObject<\/code> representation, binding it to self._mobject. This <code>MObject<\/code> isn&#8217;t specific enough, though, so we want to cast it as an <code>MFnDependencyNode<\/code>, which lets us use all the tasty functions associated with that class. We can do this by just using the constructor for <code>MFnDependencyNode<\/code>, passing that <code>MObject<\/code> handle as the argument, and we bind the result to self._node. Finally, we&#8217;re going to try to get an <code>MDagPath<\/code> instance, just in case this node is a DAG node, via the <code>getDagPath()<\/code> function. This is another of those weird functions that writes to an argument, so we pass it our <code>MDagPath<\/code> instance, self._mdagpath. The third argument there is just an empty placeholder; we don&#8217;t need it.<\/p>\n<p>So now we have an <code>MObject<\/code> object, which can give us a convenient &#8220;pointer&#8221; to just about any object in Maya; a <code>MFnDependencyNode<\/code> object, which can get us some basic information about just about any node; and an <code>MDagPath<\/code> object, which we can use to get the DAG path of our node, just in case it&#8217;s a DAG node. Almost there!<\/p>\n<p>Next is the <code>name()<\/code> function. We want this to return the name of the node, and optionally, the &#8220;long&#8221; name of the node (if it&#8217;s actually a DAG object, since only DAG objects have these long hierarchical names). We can tell if the <code>MDagPath<\/code> object we created actually is pointing to a real DAG node by using the <code>MDagPath::isValid()<\/code> function on the instance we stored as a class member earlier (self._mdagpath). If it is in fact a DAG object, we either return the full path or the short path, depending on the value of the long argument to the function. If it&#8217;s not, we do a last check to make sure that the object actually exists (by checking our <code>MObject<\/code> handle using the <code>isValid()<\/code> function), and then return the name of the <code>MFnDependencyNode<\/code> we stored for this object.<\/p>\n<p>Now as long as this MayaObject instance we&#8217;ve created is resident in memory, it doesn&#8217;t matter what we name this object, or what we parent it to, or if there&#8217;s another object elsewhere with the same name&#8230; this MayaObject will <strong>always<\/strong> point to that object and return its current name anytime you call the <code>name()<\/code> function. This is incredibly useful!<\/p>\n<p>That&#8217;s a long explanation for what should be a very simple little node, but there&#8217;s OpenMaya for you. Still, for those of you new to the API, little tools like this can go a long way with removing some of the restrictions and annoyances imposed by MEL (and the Python <code>cmds<\/code> module, since it&#8217;s just a wrapper around MEL). Take a look at the API docs for some of these object types&#8230; there&#8217;s a lot of other little conveniences you could build into this class if you&#8217;re so inclined.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Here&#8217;s a rare Maya post in 2019! I hate Maya, but sometimes it&#8217;s hard to avoid. One of the most irritating things about writing scripts for Maya is that the Python commands are totally un-Pythonic&#8230; meaning, there&#8217;s no sense of &#8220;objects&#8221; that have intrinsic properties and functions (or in Python [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[7,6],"tags":[],"_links":{"self":[{"href":"https:\/\/www.toadstorm.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/628"}],"collection":[{"href":"https:\/\/www.toadstorm.com\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.toadstorm.com\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.toadstorm.com\/blog\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.toadstorm.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=628"}],"version-history":[{"count":2,"href":"https:\/\/www.toadstorm.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/628\/revisions"}],"predecessor-version":[{"id":630,"href":"https:\/\/www.toadstorm.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/628\/revisions\/630"}],"wp:attachment":[{"href":"https:\/\/www.toadstorm.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=628"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.toadstorm.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=628"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.toadstorm.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=628"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}