Maya Scripting - MEL V.S. Python

Feb 8 2009

animationpython

day fourty: the endless fight by petite corneille @ flickr

Maya scripting 現在有了 MEL 與 Python 可供選擇,對於大部分的 Maya 使用者來說,這是件微不足道的事,但對於天天使用與撰寫 Maya scripting 的人來說(ex, TD?!)(或保守一點的說,很有機會被要求寫點程式的人),這重要極了。

個人淺見,Python 與之後由 Luma Pictures 的 Chad Dombrova 所推出的 pymel (0.7.8),是 Autodesk 買下 Maya 後,在 8.5 所做的 features 裏頭,最最重要的一點了。好吧,我是以 RD/TD/SD 人員的角度來看事的,請先暫時原諒我這有點狹隘的說詞,然後接著繼續看文章。

Maya 8.5 發表以前,在 Maya 上頭工作的技術人員,除了和大家一樣要去了解與使用 Maya 以外(說真的,Maya 的學習曲線有點長,進入門檻有點兒高,以致於很多資訊系所的電腦圖學實驗室,都不容易利用這個工具來做點什麼,實在很可惜),可能還需要去了解一下 Maya ascii file format, Maya nodes, Hierarchy in a Maya scene file, Mel, Maya C++ API, 技術名詞, …好不容易似懂非懂一些東西後,就是寫些程式來試試看。

舉些個例子。如果你想寫個可以自動幫你架好骨架的 joints 與一些 IK/FK 的設定的東西,你可以利用 Mel 來做到;如果你希望有一個特別的材質球,它會在你的模型有互相穿刺時,用紅色這種非常鮮豔且帶警告意味的顏色來提醒人,你可以用 C++ API 寫個 plugin 來提供一個特製化的 material node;如果你想用透過一個放在 Maya shelf 上頭的按鈕,讓 lighter 可以很快的下算圖指令的話,那你可以使用 Mel 外加 RMS/MtoR 提供的指令來做到;如果你想利用 locator 來實作 instance(一些 Maya instance 無法做到的情況下),同時要讓這個 locator 秀出原 reference model 的各種動態的話,你得用 Maya API 外加對於 Maya node 的了解(當然,你也可以使用 billboard(sprite) 做到);…

事情就是這麼複雜,以致於要訓練出一位可以依靠的 3D/CG TD,實在很花時間。如果很不幸地又沒辦法好好留住他,或是他個人不夠積極與拼命學習的話,可能浪費了兩三年,然後拍拍屁股走人,什麼都沒有留下來!!

這邊我們也許應該換個觀點,先來想想:「為什麼 Maya 把事情搞得這麼麻煩?」

理由很簡單:因為 Maya 很遜,它不管是使用界面或是裏頭使用到的技術,都是老玩意兒。不過它挺開放的,允許任何人以它為基礎,新增或修改使用 Maya 的方式,也因此需要技術人員的導入。這時就可以引用一下 Teddy Yang 的看法:

Animation Studio := Experienced Artists + Great Coordinators + Smart Engineers

而這也是我會認真思考 Mel 與 Python 之間關係的點,因為,既然我們無可避免地要一再地在 Maya 上頭開發東西,那與其各自發展,然後沒有效率地寫一堆可能重覆且到處都有潛在 bug 的程式,何不集中火力改善開發的環境,然後集眾人智慧,使用比較好的語言與寫程式的方法,留下值得一再反覆使用的程式下來?(這句話說得容易,執行起來得花不少精力才行)

OK, 我們來想想,Mel 與 Python(或更進一步的 pymel),有什麼好聊的。基於幾個理由,我個人覺得,所有的程式都應該使用 pymel 來開發,直接捨棄掉繼續使用 Mel 的做法,並且可以的話,把過去使用 Mel 開發的程式也一併一一轉過去。

List, Dictionary and Set

Python 帶來的第一個(也可能是最大的)貢獻,就是 list, dictionary, set 這三個高階且基本的資料結構。引用 Wikipedia 上頭的註釋:

A data structure in computer science is a way of storing data in a computer so that it can be used efficiently. It is an organization of mathematical and logical concepts of data. Often a carefully chosen data structure will allow the most efficient algorithm to be used. The choice of the data structure often begins from the choice of an abstract data type. A well-designed data structure allows a variety of critical operations to be performed, using as few resources, both execution time and memory space, as possible. Data structures are implemented by a programming language as data types and the references and operations they provide.

Quote: Data structure - Wikipedia.

我想說的是,任何一段程式,除了那種非常像 hello world! 的以外,絕大部分都會有一定的程序,更甚者會有一些或簡單或複雜高深的演算法,而不論它簡單或複雜,一定和資料結構有某種層度上的掛勾。Mel 提供的資料結構可以說基本到不能再基本,高階一點的像是 n 維陣列(n dimensional array)、集合(set)、關聯式結構(hash/map/dictionary)、樹(binary tree)…都沒有。沒有這些資料結構,你還是可以寫程式,只要你有很多時間可以寫的話。Python 原生就提供了 list, dictionary 與 set,光是這三個就靠你用的了,而且使用他們還可以延伸出更多的來。

下頭是一段我們用來自動得知使用到的 slim appearances 裏頭有哪些 AOV channel 的作法,使用 Mel 做到:

    for($ch in $channellist)
    {
        if(`gmatch $ch "*AmbientSC*"`) $outputlist[size($outputlist)]="amb: color Ambient, float a\n";
        else if(`gmatch $ch "*ReflectionSC*"`) $outputlist[size($outputlist)]="refl: color Reflection, float a\n";
        else if(`gmatch $ch "*RefractionSC*"`) $outputlist[size($outputlist)]="refr: color Refraction, float a\n";
        else if(`gmatch $ch "*IncandescenceSC*"`) $outputlist[size($outputlist)]="inc: color Incandescence, float a\n";
        else if(`gmatch $ch "*DiffuseSC*"`) $outputlist[size($outputlist)]="diff: color Diffuse, float a\n";
        else if(`gmatch $ch "*OrenNayarSC*"`) $outputlist[size($outputlist)]="diff: color Diffuse, float a\n";
        else if(`gmatch $ch "*SpecularSC*"`) $outputlist[size($outputlist)]="spec: color Specular, float a\n";
        else if(`gmatch $ch "*BlinnSpecularSC*"`) $outputlist[size($outputlist)]="spec: color Specular, float a\n";
        else if(`gmatch $ch "*CookTorranceSpecularSC*"`) $outputlist[size($outputlist)]="spec: color Specular, float a\n";
        else if(`gmatch $ch "*AnisotropicSC*"`) $outputlist[size($outputlist)]="spec: color Specular, float a\n";
        else if(`gmatch $ch "*RimSC*"`) $outputlist[size($outputlist)]="rim: color Rim, float a\n";
        else if(`gmatch $ch "*SSSSC*"`) $outputlist[size($outputlist)]="sss: color Subsurface, float a\n";
        else if(`gmatch $ch "*BackScatteringSC*"`) $outputlist[size($outputlist)]="bksct: color Backscattering , float a\n";
        else if(`gmatch $ch "*ThinTranslucenceSC*"`) $outputlist[size($outputlist)]="tlus: color Translucence, float a\n";
        else print("Export AOV====missing:"+$ch+"\n");
    }
    $outputlist = stringArrayRemoveDuplicates($outputlist);

下頭是 python (pymel) 寫法:

    switch = {
        "AmbientSC" : ("amb", "color Ambient, float a"),
        "AnisotropicSC" : ("spec", "color Specular, float a"),
        "BackScatteringSC" : ("bksct", "color Backscattering, float a"),
        "BlinnSpecularSC" : [("spec", "color Specular, float a"), ("refl", "color Reflection, float a")],
        "CookTorranceSpecularSC" : [("spec", "color Specular, float a"), ("refl", "color Reflection, float a")],
        "DiffuseSC" : ("diff", "color Diffuse, float a"),
        "IncandescenceSC" : ("inc", "color Incandescence, float a"),
        "OrenNayarSC" : ("diff", "color Diffuse, float a"),
        "ReflectionSC" : ("refl", "color Reflection, float a"),
        "RefractionSC" : ("refr", "color Refraction, float a"),
        "RimSC" : ("rim", "color Rim, float a"),
        "SpecularSC" : [("spec", "color Specular, float a"), ("refl", "color Reflection, float a")],
        "SSSSC" : ("sss", "color Subsurface, float a"),
        "SubsurfaceScatteringSC" : ("sss", "color Subsurface, float a"),
        "ThinTranslucenceSC" : ("tlus", "color Translucence, float a")
    }

    # TODO: right now, we just focus on Delux! how about others? ensemble driven? Drake
    plt = slim.PySlim(plt)
    deluxList = plt.GetAppearances(template="*,DeluxSM#*")

    AOV = {}
    for func in funcList:
        tplt = func.GetTemplateName()
        try:
            if isinstance(switch[tplt], tuple):
                secondary, channel = switch[tplt]
                AOV[secondary] = channel
            elif isinstance(switch[tplt], list):
                for secondary, channel in switch[tplt]:
                    AOV[secondary] = channel
        except KeyError, e:     # other components not in default switch. 
            pass

Internal/External modules

另一個顯而易見的好處是,Python 有眾多廣大的 module 可供使用,對於原本就熟悉 python 的人來說,Autodesk 無疑是開啟了一個非常之大的門來,讓 Maya 從此進入“眾人各顯身手的時代“,讓原來擁有最多 plugin 的 3DS Max 不再專美於前。

  • pickle/cPickle:做到 persistent object save/load (Serialization in Java)
  • CTypes:直接呼叫採用 c/c++/… 所編譯出來的 .dll, .so
  • PIL:媲美 imagemagick 的 Python Imaging LIbrary(可惜還不支援 OpenEXR)
  • ElementTree:處理 XML 的好幫手
  • CGAL-Python:把最優秀的 CGAL 直接拿來用,這點對於想直接在 Maya 裏頭寫 CG algorithm 的人,是最最直接的利器。於是像一些非常基本的 convex hull, geodesic distance, boolean operation, polygon partitioning, envelopes, triangulation, alpha shapes, voronoi diagram, … 太多了。
  • SciPy & NumPy:numerical mathematical algorithms.
  • matplotlib:plotting!

Maya plugin without C++

亦即是使用 Python 來開發 Maya plugin,而不再讓 C++ 專美於前。這有什麼好處呢? plugin 中,除了一些(20%)的程式碼會很在乎效能以外,其餘的都可以一併使用 python 來撰寫,於是你可以整合 python scripting 與 python api 兩者之間的技術與知識,同時省去了 C/C++ 複雜的 compiling/making 過程,而同一套 python plugin 不需經過 compiling 就可以跨平台則是連帶的好處。

這方面我還沒有什麼經驗,所以無法給與非常明確的介紹,得依靠其它人了 :)

Object Oriented (pymel)

MEL:

string $sel[] = `ls -sl`;
string $shapes[] = `listRelatives -s $sel[0]`;
string $conn[] = `listConnections -s 1 -d 0 $shapes[0]`;
setAttr ( $conn[0] + ".radius") 3;

PyMEL:

selected()[0].getShape().inputs()[0].radius.set(3)

Python 是個 OO 的語言,但是真正讓它在 Maya 裏頭有 OO 味道的,則非 pymel 莫屬了。pymel 除了提供 OO 的封裝以外(ex, Transform class, Camera class, …),還提供了很多簡化程式撰寫的功能,放上 pymel 官方的示範性程式碼:

from pymel import *   # safe to import into main namespace
for x in ls( type='transform'):
    print x.longName()   # object oriented design

    # make and break some connections
    x.sx >> x.sy  # connection operator    
    x.sx >> x.sz    
    x.sx <> x.sy  # disconnection operator
    x.sx.disconnect() # smarter methods (no args = disconnect all)

    # add and set a string array attribute
    # with the history of this transform's shape
    x.newAt.set( x.getShape().history(), force=1 )

    # get and set some attributes
    x.rotate =  [1,1,1]    
    trans = x.attr('translate').get()
    trans *= x.scale.get()        # vector math
    x.attr('translate') = trans     # ability to pass list/vector args
    mel.myMelScript( x.type(), trans) # automatic handling of mel commands

Integration into Pipeline

如果只是把 Python 拿來做為 Maya script writing 的好用語言版的話,就太大材小用了,使用 Python 另一個不得忽視的好處是「整合」。原本使用 Mel 撰寫的程式碼,要與 pipeline 的任何工具整合時,常常只能透過 sytem call 來做到,與外部程式的溝通,只有最最原的 commandPort (pipe or TCP/IP),Mel 本身並沒有提供任何的支援。

Python 有很多 module 則可以非常方便「整合」:赫赫有名的 Twisted 就是個專供 Python 使用的 Networking Engine;搭配 mysql/SQLAlchemy 與資料庫連線,與外部程式共享一套函式庫,於是當你修正或更新函式庫時,雙方同時受惠;與民生需求的整合(訂便當系統, 請假系統, 會議通知(groupware), …)

以太極為例,我們使用 Python 開發了使用者端的 production system,供所有參與專案的人,以非常方便而簡易的方式了解專案的面貌與進展,同時做到某種程度的 scheduling,另一方面,也做了某種程度的 concurrent version control。開發了專供 alfred 與 slim 使用的模組出來,於是任何需要送到 renderfarm 的 task,就可以很快速且無誤地寫出一個 job script submitter 來。

GUI

PyQt, PyGTK, wxPython 等都是非常優秀的 python GUI binding。我們在太極目前只使用了 wxPython 來開發與整合,非常省力地做到了跨平台(可惜 Mac 因為人力不足,沒空安裝好 macports 好讓我可以在上頭開發測試),不打算使用 pyGTK(既不夠 naive 也不夠 OO 的語法),還沒開始接觸 Autodesk 開始推的 PyQt(或是個謠傳? 不確定就是了)。

事實上,我正在考慮是不是需要訂出一個完整的架構,做出類似 Python commandPort 與 GUI 的骨架,然後同時提供一些常用的指令,之後,所以我們在 Maya 裏頭開發的程式,一旦需要使用到 GUI,就直接以我們訂的架構來寫,直接捨棄掉 MEL GUI 的東西,提供更多更 user friendly (and user behaviour smart) UI 來。這只是個還在我腦子裏的想法,而且也可以藉此把不同於原 3D 軟體(Maya, 3DS Max, XSI Softimage)的操作流程帶進來(ex, Blender 的工作方式),也許真的是太過實驗性了,還不具體,就繼續放在腦子裏一兩年好了~

comments powered by Disqus