[PYTHON] Replace all flags written with short names in maya.cmds with long names

This article is from the December 20th article of Maya-Python Advent Calendar 2016.

Introduction

In maya.cmds, you can write the flags to be passed to the command in two ways: ** short name notation ** and ** long name notation **. For example, with the maya.cmds.optionMenu command, if you write processing with the same meaning in short name notation and long name notation, it will be like this.

#Short name notation
cmds.optionMenu(name, en=True, l="top_node:",
                vis=True, ann=u"Select top node", w=200,
                cc=partial(self.change_topnode, name))

#Long name notation
cmds.optionMenu(name, enable=True, label="top_node:",
                visible=True, annotation=u"Select top node", width=200,
                changeCommand=partial(self.change_topnode, name))

The short name notation makes the code more compact, so it may be better for those who are used to it. However, for beginners of maya.cmds like me, every time a shortname flag appears when inheriting or reviewing code written by someone else, [Autodesk Maya Python Command Reference](http: // help). I end up looking at .autodesk.com/cloudhelp/2016/JPN/Maya-Tech-Docs/CommandsPython/index.html), which is inefficient.

Therefore, this time we will create a script that replaces the short name notation flag with the long name notation at once.

environment

The operation of the program was confirmed in the following environment.

Preparation

You only need an external module called astor, so install it with pip.

pip install astor

Use AST (Abstract Syntax Tree)

AST (Abstract Syntax Tree) is used to parse and replace the parameter names used when calling the maya.cmds function. With the ast module included in the python standard, you can get an AST just by parsing the code you want to parse.

import ast


if __name__ == '__main__':

    example = """def fake(x):
        import maya.cmds as cmds
        cmds.optionMenu(name, q=True, v=True)
        return 0
    """

    tree = ast.parse(example)
    print ast.dump(tree)

When the above program is executed, the following result will be output.

Module(body=[FunctionDef(name='fake', args=arguments(args=[Name(id='x', ctx=Param())], 
vararg=None, kwarg=None, defaults=[]), body=[Import(names=[alias(name='maya.cmds', 
asname='cmds')]), Expr(value=Call(func=Attribute(value=Name(id='cmds', ctx=Load()), 
attr='optionMenu', ctx=Load()), args=[Name(id='name', ctx=Load())], 
keywords=[keyword(arg='q', value=Name(id='True', ctx=Load())), keyword(arg='v', 
value=Name(id='True', ctx=Load()))], starargs=None, kwargs=None)), 
Return(value=Num(n=0))], decorator_list=[])])

It's a bit long and hard to read, but here

It is important that you can get the information about the area. In other words, you can see which command in maya.cmds is being executed with what flag argument.

Execute the help () command using mayapy

Now that we know that AST can be used to determine if the short name notation is used in the code, the next step is to get the correspondence table between the short name notation and the long name notation in each maya.cmds. If you pass the command name to maya.cmds.help (), it will return the short name notation and long name notation as shown below.

import maya.cmds
print(maya.cmds.help("optionMenu"))

-----------------------------------------------------
wrap up: optionMenu [flags] [String]
Flags:
   -e -edit
   -q -query
 -acc -alwaysCallChangeCommand 
 -ann -annotation               String
 -bgc -backgroundColor          Float Float Float
  -cc -changeCommand            Script
...(abridgement)

A dictionary (dict) with a short name notation as a key and a long name notation as a value is dynamically generated using a regular expression.

help_regex.py


method_name_dict = {}

help_messages = cmds.help(method_name)
method_name_dict[method_name] = {}

for line in help_messages.split(os.linesep):
    arg_names = re.search(r"-([a-zA-Z0-9]+) -([a-zA-Z0-9]+)", line)
    if arg_names:
        method_name_dict[method_name][arg_names.group(1)] = arg_names.group(2)

You can create a dict object like this

[('optionMenu', {u'en': u'enable', u'cc': u'changeCommand', u'm': u'manage', u'ann': u'annotation',  ...(abridgement)

However, the maya.cmds.help () command must be run in maya's python runtime environment. (Normally running with python aaa.py does not return anything)

For Mac /Applications/Autodesk/maya2015/Maya.app/Contents/bin/mayapy For Windows C:\Program Files\Autodesk\Maya2015\bin\mayapy.exe

There is mayapy around, so if you run a python program with it, you will get the result of the help () command. /Applications/Autodesk/maya2015/Maya.app/Contents/bin/mayapy aaa.py

You are now ready to replace your short name with your long name.

Replace node with ast.NodeTransformer

Use NodeTransformer to check, replace or delete each element of the AST. For example, if you want to do something to the function call (Call) node like this time, define the visit_Call (self, node) function and write the process.

class MayaPythonShort2LongNameNodeTransformer(ast.NodeTransformer):
    def visit_Call(self, node):
        #I will write the replacement process here

After replacing the flag name with NodeTransformer, finally return to the python code again with astor.to_source ().

print(astor.to_source(tree).decode("unicode-escape"))

Short name-> long name replacement script

The following code is an appropriate summary of the above processing.

maya_cmds_name_converter.py


# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, print_function, unicode_literals
import os
import re
import ast
import astor
import maya.standalone
import maya.cmds as cmds


class MayaPythonShort2LongNameNodeTransformer(ast.NodeTransformer):
    def __init__(self):
        self.method_name_dict = {}

    @staticmethod
    def is_maya_cmds_func(node):
        return hasattr(node, "func") and hasattr(node.func, "value") and hasattr(node.func.value, "id") and \
            node.func.value.id == "cmds"

    def visit_Call(self, node):

        if self.is_maya_cmds_func(node):
            method_name = node.func.attr

            if not self.method_name_dict.get(method_name):
                # short_name and long_Create a corresponding dict for name
                try:
                    help_messages = cmds.help(method_name)
                except RuntimeError:
                    # print(u"help()Is an unsupported command: " + method_name)
                    return node

                self.method_name_dict[method_name] = {}
                for line in help_messages.split(os.linesep):
                    arg_names = re.search(r"-([a-zA-Z0-9]+) -([a-zA-Z0-9]+)", line)
                    if arg_names:
                        self.method_name_dict[method_name][arg_names.group(1)] = arg_names.group(2)

            for keyword in node.keywords:
                if keyword.arg in {"q", "e", "c", "m"}:
                    #Ignore if basic flag
                    continue

                long_name = self.method_name_dict[method_name].get(keyword.arg)
                if long_name:
                    if long_name == "import":
                        #In this case, it becomes strange like python
                        continue

                    # print(keyword.arg + u"To" + long_name + u"Convert to")
                    keyword.arg = long_name

        return node


if __name__ == '__main__':
    maya.standalone.initialize(name='python')

    #Sample code for testing
    example = """def fake(x):
        import maya.cmds as cmds
        cmds.optionMenu(name, en=True, l="top_node:",
                vis=True, ann=u"Select top node", w=200,
                cc=partial(self.change_topnode, name))
        return top_node
    """

    tree = ast.parse(example)
    tree = MayaPythonShort2LongNameNodeTransformer().visit(tree)

    # after convert python code
    print(astor.to_source(tree).decode("unicode-escape"))

    if float(cmds.about(v=True)) >= 2016:
        maya.standalone.uninitialize()

When this program is executed, the command of the short name notation flag introduced at the beginning is replaced with the long name notation and output.

Before replacement


def fake(x):
        import maya.cmds as cmds
        cmds.optionMenu(name, en=True, l="top_node:",
                vis=True, ann=u"Select top node", w=200,
                cc=partial(self.change_topnode, name))
        return top_node

After replacement


def fake(x):
    import maya.cmds as cmds
    cmds.optionMenu(name, enable=True, label='top_node:', visible=True, annotation=u'Select top node', width=200, changeCommand=partial(self.change_topnode, name))
    return top_node

Precautions / restrictions

It seemed to work, but after ast.parse the python code, when I convert it back to the python code with astor.to_source (), I lose a lot of format information.

--u "" "This is a documentation string" "", but all u \ This is a documentation string \ --The line breaks inserted in the middle of the sentence disappear --if not aaa: can be arbitrarily put in parentheses like if (not aaa): --If there is a character string indicating the line feed code of'\ n'in the original code, it will be treated as a line feed at the time of restoration. --This is avoided by using os.linesep

So, after performing short name-> long name replacement, I clean it separately with AutoFormatter / warning deletion / replacement with PyCharm. ..

Summary

If you get used to it, the short name notation may be easier to write and read, but I still can't remember the huge number of flags contained in maya.cmds.

As expected, l is label, v is value, etc., so I think it's a good idea to decide the flag name rule according to the proficiency level of the team involved in the code. If it is a flag that seems to be used infrequently, you can understand the meaning by looking at the long name, but if it is a short name, there is definitely a command reference feed, so be careful.

bsp -> beforeShowPopup npm -> numberOfPopupMenus vcc -> visibleChangeCommand And.

Recommended Posts

Replace all flags written with short names in maya.cmds with long names
Replace all at once with sed
Stress Test with Locust written in Python
Gacha written in python-Rarity confirmed with bonus-
Replace non-ASCII with regular expressions in Python
Replace column names / values with pandas dataframe