undefined

145 views
Skip to first unread message

Christopher Crouzet

unread,
Jun 23, 2014, 11:58:03 AM6/23/14
to python_in...@googlegroups.com
Hi there,


I've initially posted this message onto the Softimage mailing-list but since it's referring to Maya I hope you don't mind me posting it here as well.

Basically I've just published an article about how to monkey patch external libraries in Python while using the Maya Python API as a guinea pig. While I was there, I also came up with a couple of open source libraries on that matter.

You can find the article and all the information over there: From Monkey Patching the Maya Python API to Gorilla and Bananas—I hope you don't mind the bashing on the Maya API :)


And here's a short(er) version:

Monkey patching allows you to modify an existing 3rd-party library by inserting some code of yours. Once the process is done, you can call the extensions you've inserted as if they have always been part of that 3rd-party library.

The article uses Maya as a playground but the technique can definitely be applied to about anything, which is what the library Gorilla is all about. You write your functions, classes, methods, properties, whatever, you tell them the target to patch with the help of a Python decorator, and that's about it. You can find more details in the documentation.

As a proof of concept for this project, I've developed a couple of extensions for the Maya Python API. Say hi to Banana for Maya. This basically shows that extending the API can be as simple as:

    @gorilla.patch(OpenMaya.MFnTransform)
    def whoAmI(self):
        print("My name is %s" % self.name())


Which allows you to fire the method through a call to `OpenMaya.MFnTransform.whoAmI()`.

As for the extensions already in there, there's a shiny documentation for this one too.

Once again the `banana.maya` package is only a proof of concept, which is why it's a bit empty. I've started to implement some methods that could hopefully be useful to everyone (mainly retrieving/iterating through the nodes in the scene) but I'm currently not using Maya anymore and don't have any direct interest in developing those extensions much further. The exception being if there's a need for it and if discussions can be organized to implement the right features.

Note that I'm not saying that monkey patching the Maya Python API v1.0 is the way to go and I acknowledge that there's better alternatives out there for most cases.


That's about it. The code is yours, do what you want with it.

Cheers,
Christopher.

--
Christopher Crouzet
http://christophercrouzet.com

Christopher Crouzet

unread,
Jun 23, 2014, 12:03:59 PM6/23/14
to python_in...@googlegroups.com
I think there has been a glitch with my Gmail—can anyone see a subject other than "untitled" or see this post at all?

Marcus Ottosson

unread,
Jun 23, 2014, 1:06:18 PM6/23/14
to python_in...@googlegroups.com
Hey Christopher,

I'm seeing the post fine on this end (via Gmail) and will look through your code in a bit, looks cool!

Best,
Marcus


--
You received this message because you are subscribed to the Google Groups "Python Programming for Autodesk Maya" group.
To unsubscribe from this group and stop receiving emails from it, send an email to python_inside_m...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/python_inside_maya/CANuKW52Luo-S-OabmBdxeXdfiCZuJTViV2MTju3pQyO8CXdLaQ%40mail.gmail.com.

For more options, visit https://groups.google.com/d/optout.



--
Marcus Ottosson
konstr...@gmail.com

Marcus Ottosson

unread,
Jun 23, 2014, 1:16:10 PM6/23/14
to python_in...@googlegroups.com

For someone who doesn’t generally monkey-patch things, what motivated you to make this library? Could you post a use-case? Looking at your example, how does your library differ from this?

def whoAmI(self):
    print("My name is %s" % [self.name](http://self.name/)())

OpenMaya.MFnTransform.whoAmI = whoAmI

On another note, you’ve got a well executed readthedocs page and your docstrings are very well formatted. It’s quite a joy to read.

--
Marcus Ottosson
konstr...@gmail.com

Christopher Crouzet

unread,
Jun 23, 2014, 2:06:49 PM6/23/14
to python_in...@googlegroups.com
Thanks for letting me know and cheers for the comment—I indeed spent a bit of time to follow the Python standards in the hope that it would be a good enough coding example for the beginners willing to write libraries.


As written in the final note of the article, in Maya 2010 PyMEL wasn't shipped and I was left with either using the MEL/Python commands layer which from a coder standpoing I find to be a real abomination to use, or the Maya Python API.

The good thing with the Maya Python API is that it's not a jungle of nonsensical global functions each spammed with tons of flags that are often incompatible with each other like the MEL/Python commands layer is. At least things the methods from the API are logically organised like with any OO library, so that's a good start. And it follows the actual architecture of the software, which is another good point.

The bad thing is that I couldn't see myself writing 32479083 lines of codes only to retrieve an object, find a child, or whatsoever. Hence why I started to monkey patch it.


As for your code snippet, the library doesn't differ from this exact example atm. There's not hundred of ways of monkey patching after all.

Now, let's look into a different example, patching a class method:

  class MyClass(object):
      
      @classmethod
      def whoAmI(cls):
          print("My name is whatever")
      
      print(whoAmI)
  
  print(MyClass.whoAmI)


Both print statements won't print the same thing. The one nested within the class will return the `classmethod` object while the other will return an object resulting from the Python's descriptor protocol. So basically, you'd have to write your `OpenMaya.MFnTransform.whoAmI = whoAmI` assignment within the class if you want to preserve the class method descriptor in the `OpenMaya.MFnTransform` class, or else you might get a result that you didn't expect.

By using a decorator instead, you know what object you're using as a patch. If you write the decorator on top of `@classmethod`, then the class method gets patched. If you write it below it, then only the underlying function gets patched. More intuitive and visual.

Furthermore, if you now want to patch 50 methods inside `OpenMaya.MFnTransform` for example, then instead of writing 50 times this assignment, you could write those 50 methods within a class and mark the entire class to use as a patch. That's actually pretty much the only magic happening in the library and I'll probably have to turn it into a setting so we can disable this behavior if needed.

  @gorilla.patch(OpenMaya, name='MFnTransform')
  class MyClass(object):
      
      def whoAmI(self):
          print("My name is %s" % self.name())


Now that becomes a real advantage. Each method defined within `MyClass` get individually inserted in the `OpenMaya.MFnTransform` class. If instead you used something like `OpenMaya.MFnTransform = MyClass`, then you would have completely overriden the original `OpenMaya.MFnTransform` class.

And finally, if a name clashing is detected, then the previous method is backuped so you can still call it.

  @gorilla.patch(OpenMaya)
  class MFnTransform(object):
      
      def transformation(self):
          print("do something awesome")
          return gorilla.get_original_attribute(self, 'transformation')()


Well, I've just been repeating the documentation really :)

But in short there's nothing happening in this library that you couldn't do differently. Its goal is to make the process convenient and robust, without you having to worry of eventual corner cases, and to ensure that this will be portable across different versions of Python (Python 2.6+ and Python 3.3+ are currently supported).


I hope this answer your questions.

Cheers,
Christopher.


PS: funny that Gmail treats `self.name` as being an URL :)




For more options, visit https://groups.google.com/d/optout.



--
Christopher Crouzet
http://christophercrouzet.com

Marcus Ottosson

unread,
Jun 23, 2014, 4:58:00 PM6/23/14
to python_in...@googlegroups.com

Well, I’ve just been repeating the documentation really :)

I read through some of the code and the post about this on your blog and you’re right, I should’ve investigated better prior to asking, sorry about that.

One thing about Banana and monkey-patching the Maya API. I’ve seen a similar reaction to maya.cmds from other persons transitioning from Softimage and I wonder, are you using the API for general scripting such as selecting, and iterating over scene items, or reading from the channelBox etc.? I’ve been writing tools for both Maya and Softimage for quite a few years now and have honestly never found any reason to not stick with maya.cmds for common tasks. I’ve seen some use the API for performance concerns, but other than that it seems better suited for plug-ins and hacking than it does for scripting of general Maya operations, mainly due to lengthy code and the obscured naming convention inherited from C++.

If I had to compare scripting in Softimage versus scripting in Maya, I’d have to go with something like:

  • Softimage has a poor scripting interface (mainly due to COM) and a rather well-rounded API/Object Model
  • Maya has a well-rounded scripting interface and a rather poor API (based on what I’ve heard, as I know very little of it myself)

What would be your thoughts on this?

Christopher Crouzet

unread,
Jun 23, 2014, 6:57:30 PM6/23/14
to python_in...@googlegroups.com
I'll reply as objectively as possible to your questions but I sincerely hope that this thread won't turn into a software war or something.


The COM layer allows Softimage's C++ API/object model to be also available within all the scripting languages such as Python, JScript, VBscript, and even Perl if my memory is correct... but not sure if anyone ever used this one to testify that it actually works. This provides a great consistency across all the languages and no, it's far from being poor. I've started writing my first lines of code with that API and I might be a bit biased but I always found it intuitive to use. With a bit more distance, I still believe that it's a modern and well designed API.

Now maybe you're referring to the command layer displayed in the script editor after each operation done in the interface. This one is just a bunch of unordered global functions that beginners can use to create a few scripts by copying/pasting the log. That's not designed to code anything serious.


As for Maya's scripting interface, if you're referring to the commands layer (in MEL or Python), then my point of view would be rather the opposite. I can't think of how representing nodes with strings can be a robust approach (I've had a fun example back then but lost it). And I don't understand in what having 409832 flags defined in a same function can be helpful. It's just confusing and get worse when you sometimes get some functions which features seems to collide such as `getAttr`, `attributeInfo`, and other `attributeQuery`.

I think MEL starts to make sense when we know it's derived from an UNIX shell language that has most probably been created with the intention to describe ASCII Maya scenes. Like Softimage's command layer, I can't think of it being initially created to do some serious development but I guess the community just got along with it since it was either that or C++ back then. When I see some code written in MEL that retrieve some node information by first selecting them, or that hardcore every single name without even thinking that there might be more than one node named 'root' in the scene, I'm thinking that many don't care much about coding practices anyways.

As such, I've never used either MEL or its 1:1 port to Python for anything (but when no other choice) and focused only on working solely with the API even though I haven't been a big fan of it neither. I can assure you that it was indeed a pain and I can understand that most sitck with the commands layer. But since I had to learn one or the other, I rather go for the way which seemed the most robust to me even though it was more verbose and initally required more work on my end. It went better once I implemented a few things through monkey patching.

Working with both would also have been a pain, especially from the commands layer to the API since a conversion from a string to a MFn object would be required every time. The other way around is easier and I actually did something (that must be kept secret), which is to override the `__str__` attribute of the `MFnDagNode` class to return the full path name of an object. This way I could pass a `MFnDagNode` object directly as an argument of a `maya.cmds` function without having to explicitely do the conversion. This obviously didn't work for array of objects though.


But at the end of the day, it all remain just tools. The most important is how they're being used. As such, what precedes is my own way of seeing things leading to why I chose to use the API rather than the commands layer but I totally respect any other alternatives as soon as it's well coded and doesn't break when I use it :)


Cheers,
Christopher.



--
You received this message because you are subscribed to the Google Groups "Python Programming for Autodesk Maya" group.
To unsubscribe from this group and stop receiving emails from it, send an email to python_inside_m...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

Justin Israel

unread,
Jun 23, 2014, 7:52:29 PM6/23/14
to python_in...@googlegroups.com
Monkey-patching is a bit of an evil art. Especially when you are modifying a shared python environment such as Maya. It would have to be done in a way that doesn't result in crazy breakage for other tools that live in the same interpreter and expect one type of return value, but get another (Action At A Distance).


Christopher Crouzet

unread,
Jun 23, 2014, 8:49:52 PM6/23/14
to python_in...@googlegroups.com
Rejecting in block an approach by saying it's evil is evil in itself :)

I agree though, monkey patching can be evil and dangerous. But it's just a tool, and depending on how it's being used, it can also be harmless and useful. I tried to underline the former the best I could within the article to warn against the risks.

As a side note, I took the precaution to prefix all the methods in the `banana.maya` library with 'bnn_'. This hopefully highly minimizes the risk of changing the behavior of an existing method. I could also add an option in the Gorilla library to throw an exception whenever a method already exists instead of saving it under another name.




For more options, visit https://groups.google.com/d/optout.

Christopher Crouzet

unread,
Jun 23, 2014, 9:04:02 PM6/23/14
to python_in...@googlegroups.com
PS: after rereading your post, I kinda realized that I pretty much said the same thing than you only differently, sorry :)

Justin Israel

unread,
Jun 23, 2014, 9:12:02 PM6/23/14
to python_in...@googlegroups.com
lol. yea. I wasn't actually saying your library is evil. I was just commenting about monkey-patching. It is one of those scary things that Python allows you to do. Just thought I would throw in a general comment for fun. 


Marcus Ottosson

unread,
Jun 24, 2014, 2:22:17 AM6/24/14
to python_in...@googlegroups.com

I’ll reply as objectively as possible … And I don’t understand in what having 409832 flags defined in a same function can be helpful .. I can’t think of how representing nodes with strings can be a robust approach … MEL or its 1:1 port to Python

For reference, here are some things you may wish to leave out of an objective discussion, unless you can provide examples.

When I see some code written in MEL that retrieve some node information by first selecting them, or that hardcore every single name without even thinking that there might be more than one node named ‘root’ in the scene, I’m thinking that many don’t care much about coding practices anyways. .. As such, I’ve never used either MEL or its 1:1 port to Python for anything

In general, I think most solutions are born out of frustration from an existing solution, however I also believe that most frustration are born out of either misunderstanding or misuse of an existing solution. When faced with difficulty, there are two ways one could go - either “I’m doing something wrong” or “the developers of my tool has done it wrong”. It’s a slippery one for sure, but as you’ve chosen the latter and also mention that you’ve barely used maya.cmds I’m slightly suspicious.

Could you try and illustrate your frustration with an example perhaps? Maybe I’m missing something.




For more options, visit https://groups.google.com/d/optout.



--
Marcus Ottosson
konstr...@gmail.com

Christopher Crouzet

unread,
Jun 24, 2014, 12:04:28 PM6/24/14
to python_in...@googlegroups.com
Fair enough.

Firstly, I'll restate my saying since it was quite inaccurate.
When I had to switch over to Maya, I was thrown in the fire of the action by having to deliver some critical assets even though I didn't have any previous experience with Maya. As such I started to use the Python commands layer like everyone and I ended knowing it well enough. That's why when that production was over, I swore to not use it anymore unless I really had to.


To put things back into context, I'm a rigger. I'm spending a lot of time dealing with hiearchies and I like writing modular rigs. Modular rigs makes it more than likely to have a same hierarchy of nodes being reused in a same scene. Instead of prefixing the name of each of my nodes with a hundredth of characters worth of the name of the asset and other information to ensure that its name is unique in the scene, I prefer to stay simple and call them 'root', 'spine', 'arm', whatever.

From there, let's consider a first example.

    from maya import cmds
    
    root = cmds.group(name='character', empty=True)
    l_arm = cmds.group(name='l_arm', parent=root, empty=True)
    r_arm = cmds.group(name='r_arm', parent=root, empty=True)
    l_node = cmds.group(name='node', parent=l_arm, empty=True)
    r_node = cmds.group(name='node', parent=r_arm, empty=True)
    l_child = cmds.group(name='child', parent=l_node, empty=True)
    r_child = cmds.group(name='child', parent=r_node, empty=True)


See the problem here? The variable `l_node` is pointing to a node named 'node' but can suddenly become invalid simply because another node has been created in the scene, and even though the node pointed by `l_node` hasn't been modified in any way. Of course this snippet of code reflects a terribly poor coding practice and the issue could be avoided much easily, but the point here is to show a weakness in referring to nodes by their names. As bad as this snippet can be, it is still made of a valid serie of calls and should work in any decent API. It shouldn't require the developer to known how the internals of the API works to workaround the issue.

Ok, let's move on with another example.

    from maya import cmds
    
    def toFullPathName(node):
        return cmds.ls( node, long=True )[0]
    
    root = cmds.group(name='character', empty=True)
    for arm_name in ['l_arm', 'r_arm']:
        arm = toFullPathName(cmds.group(name=arm_name, parent=root, empty=True))
        node = toFullPathName(cmds.group(name='node', parent=arm, empty=True))
        child = toFullPathName(cmds.group(name='child', parent=node, empty=True))


This snippet solve the previous issue already by using a loop structure. Also storing the full path name this time allows to get a false impression of robustness. Note here the lack of consistency within the commands layer: the `group` function doesn't have any flag to retrieve the full path name when many other functions have it (accessible under different names such as `long`, `longName` or `fullPath`, probably depending on the personal preferences of the coder at that time). As a result, you don't know what you'll get as a return value from the `group` command: a name or a partial name? It's like magic and depends on the content of the scene. Something more to take into consideration when coding. As another result, you've got to call quite a ugly workaround with the `ls` function to ensure that you consistently get a full path name, but maybe there's a better way to do so?


Now, as a rigger being, I often have to insert objects within a hierarchy, reparent nodes, and so on. It would for example be common for me to add a parent to the `node` and `child` nodes to set their local transformations to identity as a post-process pass.

Naive approach:

    from maya import cmds
    
    def toFullPathName(node):
        return cmds.ls( node, long=True )[0]
    
    nodes = []
    root = cmds.group(name='character', empty=True)
    for arm_name in ['l_arm', 'r_arm']:
        arm = toFullPathName(cmds.group(name=arm_name, parent=root, empty=True))
        node = toFullPathName(cmds.group(name='node', parent=arm, empty=True))
        child = toFullPathName(cmds.group(name='child', parent=node, empty=True))
        nodes.extend([node, child])
    
    for node in nodes:
        parent = cmds.listRelatives(node, parent=True, fullPath=True)[0]
        buffer = toFullPathName(cmds.group(name='buffer', parent=parent, empty=True))
        cmds.parent(node, buffer)


Without knowing the API and simply by reading the code from this snippet, this should result in an expected result but unfortunately it errors out. The workarounds are quite funny actually. Either you could do your parenting by sorting the list of nodes from the one at the bottom of the hiearchy to the one at the top, or you could store them in a temporary set to make sure you that keep a proper reference to them. In both cases you would still end up with invalid references if you had to use those nodes again later on in your code but if you reassign the variables with their updated full path name...


Those are only 2 basic examples that I can remember despite of not having using Maya in over 1 year. Believe me, I banged my head of frustration a whole lot more than that.


So how do I feel about this command layer? Pretty insecure I have to say. I'm not keen in using a scripting interface that exposes so many inconsistencies and flaws. Yes it's usable and works well if one knows all the corner cases and take care of them. But it's not something that I want to waste energy dealing with only to end up in a state in I would never have the confidence that a certain variable actually still points to the node I'd expect it to. And that sucks.


I'm obviously not teaching you anything that you don't already know here. I'm just highlighting what you accepted to deal with and what I didn't.
Once again, I fully respect any approach and believe they are valid as soon as the code produced is robust. I chose the Maya API because it was simpler for me to write such code, that's all.


Cheers,
Christopher.




For more options, visit https://groups.google.com/d/optout.

Marcus Ottosson

unread,
Jun 24, 2014, 1:28:54 PM6/24/14
to python_in...@googlegroups.com
Hi Christopher,

You've clearly put a lot of thought into this and done your research well, but there is one thing you're missing. Coming from Softimage I can't blame you for assuming an hierarchical approach to objects when it comes to rigging (of which I have done my fair share, in both Maya and Softimage), but Maya, unlike Softimage, isn't hierarchical, it's node-based. The issues you are having with respect to naming is due to treating hierarchies as namespaces and this isn't how things work in Maya, and I doubt you'd find it works in any node-based package and turning to the API isn't as much of a solution to your problems as it is a workaround. What may be of greater gain is to accept a change in thinking with respect to naming conventions.

Sorry to be blunt with you, but I think it's important to point out, for other readers out there, that the naming convention you mention here (pretty and minimalistic though it may be) isn't suitable when every object exists under a single namespace.

Maya does provide the ability to work with namespaces however and though you mention that you aren't into Maya anymore it may be helpful to know that such an approach might be better suited for your naming scheme. Although the simplest solution would be to merely accept lengthier names.
 
Best,
Marcus

For more options, visit https://groups.google.com/d/optout.


--
Marcus Ottosson
konstr...@gmail.com


Christopher Crouzet

unread,
Jun 24, 2014, 2:06:39 PM6/24/14
to python_in...@googlegroups.com
Hey Marcus,

I understand your point and acknowledge that I could have potentially been biased in my approach to rigging and building my hierarchies.

That being said, I might be wrong but I like to think of myself as someone being open-minded and I tried my best to adapt my rigging style to Maya's philosophy instead of imposing my previous experience from Softimage as you might think. I also enjoy reconsidering and criticizing anything I do and any approach/workflow I might use.

But from the way I see things, and after having gone through the Maya API, I don't believe that my approach is in contradiction with Maya's philosophy in any way. I don't believe that there is a such limitation enforced by the software itself and I'm happy to see Maya as being a software flexible enough to not dictacte a single approach. If there was such a constraint, I wouldn't have been able to achieve anything with the API itself anyways. Instead, I could get everything done the way I wanted to. The only thing that breaks this approach is the command layers which is treating nodes as strings.

Once again this makes sense since MEL is derived from a UNIX shell language (said in the official doc) and hence can't deal with actual objects. Obviously we're not coding in a shell, and the limitations of a software shouldn't be reflected by the limitations of a scripted language that is not adapted to the software it's manipulating as it doesn't even understand the notion of objects. Objects being in my opinion quite a core concept in such a complex nodal graph as the one implemented by Maya.


Now I think we've been out of topic for long enough and if you'll excuse me, I'd like to opt out from this discussion as I'm not interested in debating this subject but only replied to your questions by politeness. I'll remain available for discussing the original subject of this thread though.

Kind regards,
Christopher.




For more options, visit https://groups.google.com/d/optout.

Marcus Ottosson

unread,
Jun 24, 2014, 3:24:02 PM6/24/14
to python_in...@googlegroups.com
I'm sorry you feel criticized, Christopher, that wasn't my intent. I don't think our topic is off as it it relates to the merits of your monkey-patching library, it seems to have been built around a misunderstanding rather than solve an actual problem.



For more options, visit https://groups.google.com/d/optout.



--
Marcus Ottosson
konstr...@gmail.com

Christopher Crouzet

unread,
Jun 24, 2014, 4:02:28 PM6/24/14
to python_in...@googlegroups.com
I didn't feel criticized at all! It's been an interesting discussion but I'm simply over this kind of debates. I've had enough of those during my career, most of them ending up being pretty bad experiences—unlike here so far—and I have been happily taking a well deserved break from them for more than 1 year now (I've been travelling since then) :)


I hope you understand.

Cheers,
Christopher.


PS: paradoxally I'd be tempted to publish what I wrote here on my blog to genuinely share my experience and point of view but I'm a bit scared of the outcome of starting new debates, haha.




For more options, visit https://groups.google.com/d/optout.

Marcus Ottosson

unread,
Jun 24, 2014, 6:00:23 PM6/24/14
to python_in...@googlegroups.com
I'm relieved to hear that. Haven't seen you around here before and was worried I might have scared you off; I'm generally quite direct. As you're into blogging, I'd encourage you to do more posting here, there are quite a few bright heads on this list (who are more polite than me and) who I'm sure would be willing to share their perspectives. In other words, welcome (back) to python-inside-maya. :)
To view this discussion on the web visit https://groups.google.com/d/msgid/python_inside_maya/CANuKW53cDJAwLNXAKOaeF4VPXTry%2BZdw_gxSXbLJNC_bhdSRGg%40mail.gmail.com.

For more options, visit https://groups.google.com/d/optout.


--
Marcus Ottosson
konstr...@gmail.com


Christopher Crouzet

unread,
Jun 24, 2014, 10:13:58 PM6/24/14
to python_in...@googlegroups.com
It wasn't the easiest welcoming to obtain but I feel glad to have made it that far, cheers! :)




For more options, visit https://groups.google.com/d/optout.

Christopher Crouzet

unread,
Jun 29, 2014, 11:40:41 AM6/29/14
to python_in...@googlegroups.com
New FAQ section for Gorilla, thanks to you :)

Reply all
Reply to author
Forward
0 new messages