如何讓 Maya 裏的所有 message 都丟到 console 外頭?
他因為工作需要,常常要批次(batch)處理一大堆的 Maya scene files,透過一些他自行寫的 scripts 來執行一些例行性的工作。然後他需要把 Maya 產生的任何 message 都以某種型式丟出來,方便他 debug 或是分析。我們兩個人在想,有哪些方法可以做到?(從這點可以看出來,Timothy 是一位非常稱職的 RD/TD,會汲汲於想一些方法來做 mass production,而不是手動一個一個處理。因為即使你手動做得再快,只要量一大,還是有一定的速度上限~)
我們最後釐清了需求:
- Maya 會由 terminal/console 下執行,所有的 message 要能正常輸出,而不是只留在 Maya Script Editor 裏頭。
- message 包括:Maya 本身產生的;Mel 的 print;Python 的 print。
解法:
- 要讓 Python 的 print 得以輸出到 terminal,只要把 sys.stdout 由原來的 Maya Output object 換成 sys.__stdout__ 即可。
- 要讓 Mel 的 print 與 Maya 原生的 message 輸出到 terminal 的話,需要利用到 Maya API 裏頭 CommandMessage 所提供的 CommandOutputCallback。向 Maya 註冊一個 callback 後,每當有任何一個 command 的 output 時,這個 callback 就會被呼叫,然後我們可以來決定怎麼處理這個 output message。
如何從選取的節點中,過濾出燈光來?
簡答如下:
from pymel import *
filter( lambda n: 'light' in n.classification(), ls(sl=True, dag=True, leaf=True))
這邊我使用 pymel 這個 python module 來簡化一些細節。
對照於文章後頭引用的原文(使用 mel 作法,建議先閱讀後,再回過頭來想上頭的這邊兩行程式碼),使用 pymel 不需檢查 getClassification() 的回傳值是不是空字串,透過 python 的 in 這個 operator 就會自動幫你處理掉一些「例外」了,同時,還可能因此少寫一些 if else。寫程式時,如果能少一些判斷式(if ... else ... elif ..., switch case ...),就「程式的易閱讀性」這一點來說,就像是少了一些特例的處理,是個比較好也比較推崇的寫法。像是說,日後在做維護時,不需要一個一個 if else 看下去,一個一個了解哪些情況下,要做什麼檢查之類的,因為對於正在維護或是新增功能的 programmer,他會希望花最少力氣就完成這類工作。同時,心理頭要能很有自信,確信不會因為他寫的程式而造成不必要的 bug 出來。少一些 if else,就少一些這層顧慮,因為你等於是替未來接手的 programmer(這個人常常是你自己)幫了個大忙。
同時,藉由 Functional Programming 裏好用的 list operator: filter ,原本要寫上四五行的 for loop + if else,現在只要一個 filter 就成了,當然,再加上隨寫即可用的 lambda function,寫起程式來,輕鬆得很。這裏的好處,不是只有把原來要寫四五行的程式碼縮短成一行而已,它同時暗指了,讓 programmer 去習慣把一個 for-loop 的想法,一個以流程為主的邏輯想法(flow-oriented logical thinking),轉化為以資料為主的想法(data-oriented thinking),而且是以比較高階的資料結構來思考(一次以一整個 array 或 list 來想)。
在 pymel 的 google groups 上頭,分別有兩位提供了不同的作法,列在內容供參考。
take out the 'sl=True' by using pymel's "selected()" function by Ofer Koren
from pymel import *
filter( lambda n: 'light' in n.classification(), selected(dag=True, leaf=True))
Doing it through the API requires more code but is considerably faster by Dean Edmonds
import maya.OpenMaya as om
sel = om.MSelectionList()
om.MGlobal.getActiveSelectionList(sel)
iter = om.MItSelectionList(sel, om.MFn.kLight)
lights = []
while not iter.isDone():
light = om.MDagPath()
iter.getDagPath(light)
lights += [light]
iter.next()
接下來的文章,是由 Bryan Ewert 所撰寫,原來可以在 http://www.ewertb.com/maya/mel 這個網址找到,但因為一些原因,這個網站已經關站一陣子了。很幸運的是,因為 Bryan 的 Mel Howto 寫得實在太好了,於是你可以透過這兩個網址,看到別人備份的版本:http://ewertb.soundlinker.com/ 或是 Wayback Machine。我把原文貼上來,方便看官你查閱。
Well, logically you would think this would do it:
string $selectedLights[] = `ls -sl -lights`;But -- while this does indeed return the list of selected lights, unfortunately it is only reliable if the user selects the shape node of the light. Often they won't, nor should they be required to. Fortunately, the "ls" command offers a convenient method for retrieving the shape nodes from the current selection:
string $selectedShapes[] = `ls -selection -dag -leaf`;By using this query, you need not concern yourself if the user has selected the transform or the shape for a particular node. From here, you can typically use the "nodeType" command to query if the shape is the type you're looking for:
if ( "mesh" == `nodeType $shape` ) { // Do something with a mesh shape. } if ( "camera" == `nodeType $shape` ) { // Do something with a camera. }However, "light" is not a node type; rather, there are several types of lights: spotLight, directionalLight, etc. A more generalized query is convenient in this case - that provided by the "getClassification" command. This command returns a classification string for the node's type. Be aware that this returns a string array and not just a string.
getClassification spotLight; // Result: light // getClassification volumeLight; // Result: light //The following procedure demonstrates its use for extracting all lights from the current selection.
proc string[] getSelectedLights() { string $selectedLights[]; string $select[] = `ls -sl -dag -leaf`; for ( $shape in $select ) { // Determine if this is a light. // string $class[] = getClassification( `nodeType $shape` ); if ( ( `size $class` ) > 0 && ( "light" == $class[0] ) ) { $selectedLights[ `size $selectedLights` ] = $shape; } } // Result is an array of all lights included in // current selection list. return $selectedLights; }


