Expresso

  • Dec
    6

    MPO2Stereo

    The following is the code I got from Andres Hernandez [cybereality] for separating a .mpo file into 2 relative .jpeg files (left and right). The key point is to find the second jpeg's header "FFD8FFE1" and this is my first time to touch jpeg's header format. Kinda interesting.

    According to Andres's idea, we all hope everyone could keep sharing any informative stuff to help others' work or research. And eventually, we could benefit from others' great achievement :)

    Cool, no problem man. It would be cool if you could upload the mac version on that same MTBS thread so it can help others. MTBS can host files, there is a small "browse for file" button under the post box, then click "add file", so it is very easy to include files in a thread. I am sure there are other mac users that would appreciate it.

    // MPO2Stereo:
    // Converts Fujifilm MPO format 3D
    // images into JPEG stereo pairs
    //
    // code by: 
    // Andres Hernandez [cybereality]
    
    #include <iostream>
    #include <fstream>
    
    using namespace std;
    
    int fileSize;
    char * stereoData;
    char * rightData;
    
    int main( int argc, char* argv[] ){
    	// title
    	printf("\n*********** MPO2Stereo [cybereality] ***********\n");
    	printf("------------------------------------------------\n");
    
    	// print usage if no arguments
    	if(argc == 1){
    		printf("Converts MPO files to JPEG stereo pairs.\n");
    		printf("Usage: MPO2Stereo.exe File_1.MPO [.. File_N.MPO]\n");
    	}
    	// loop through each argument
    	for( int i = 1; i < argc; i++ ){
    		// get the file name
    		char fileName[255];
    		char fullName[255];
    		// locate end of path, which is the last slash in the string
    		char *ptr = strrchr(argv[i],'/');
    		// make sure its a directory
    		if(ptr != NULL){
    			// copy remainder of string
    			strcpy(fileName,ptr+1);
    			// remember full name
    			strcpy(fullName, fileName);
    			// check if its an MPO file
    			char *MPO = strstr(fileName, ".MPO");
    			char *mpo = strstr(fileName, ".mpo");
    			// strip file extension
    			ptr = strchr(fileName,'.');
    			// if the extension exists, truncate it
    			if(ptr != 0) *ptr = 0;
    			// make sure its an MPO file
    			if(MPO == NULL && mpo == NULL){
    				printf("Cannot Process: \"%s\". Not An MPO File.\n", fullName);
    				// go to next file if any
    				continue;
    			} else {
    				// print file name
    				printf("Processing MPO File: \"%s\"...\n", fullName);
    			}
    		} else {
    			// malformed directory
    			printf("Invalid Directory For File: %s\n", argv[i]);
    			printf("Must Include Full Directory Path To MPO File.\n");
    			// skip
    			continue;
    		}
    
    		// load up the MPO file
    		ifstream mpoFile (argv[i], ios::in|ios::binary|ios::ate);
    		// make sure we can open file
    		if (mpoFile.is_open()) {
    			// read the image data into a buffer
    			fileSize = (int)mpoFile.tellg();
    			stereoData = new char [fileSize];
    			mpoFile.seekg (0, ios::beg);
    			mpoFile.read (stereoData, fileSize);
    			mpoFile.close();
    
    			// start of the next image
    			char startOfImage [8]= { 'F', 'F', 'D', '8', 'F', 'F', 'E', '1' };
    			int imageBreak;
    
    			// find the break point between the image pairs
    			for(int i=0; i < fileSize; i+=4){
    				// get block into a c string
    				char block[255];
    				// extract the characters
    				sprintf(block, "%.2X%.2X%.2X%.2X", 
    				(unsigned int)(unsigned char)stereoData[i], 
    				(unsigned int)(unsigned char)stereoData[i+1], 
    				(unsigned int)(unsigned char)stereoData[i+2], 
    				(unsigned int)(unsigned char)stereoData[i+3]);
    				// check for start of image
    				if(strncmp(block, startOfImage, 8) == 0){
    					// remember where the image starts
    					imageBreak = i;
    					cout << imageBreak << endl;
    				}
    			}
    
    			// write left image to new file
    			char leftName[255];
    			sprintf(leftName, "%s_L.JPG", fileName);
    			ofstream leftFile;
    			leftFile.open (leftName, ios::out | ios::binary);
    			if (leftFile.is_open()) {
    				leftFile.write (stereoData, imageBreak);
    				leftFile.close();
    				printf("Created JPEG File: \"%s\".\n", leftName);
    			} else {
    				printf("Unable To Create JPEG File: \"%s\".\n", leftName);
    			}
    			// write right image to new file
    			char rightName[255];
    			sprintf(rightName, "%s_R.JPG", fileName);
    			ofstream rightFile;
    			rightFile.open (rightName, ios::out | ios::binary);
    			if (rightFile.is_open()) {
    				rightFile.write (stereoData + imageBreak, fileSize-imageBreak);
    				rightFile.close();
    				printf("Created JPEG File: \"%s\".\n", rightName);
    			} else {
    				printf("Unable To Create JPEG File: \"%s\".\n", rightName);
    			}
    			// delete resources
    			delete[] stereoData;
    		} else {
    			printf("Unable To Open MPO File.\n");
    		}
    	}
    	// all done
    	printf("------------------------------------------------\n");
    
    	// return
    	return 0;
    }
  • Feb
    24

    如何讓 Maya 裏的所有 message 都丟到 console 外頭?

    redirect_maya_messages_to_console.png前天 Timothy 和我聊到一個非常有趣的事。

    他因為工作需要,常常要批次(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。
  • Feb
    4

    pymel 讓 optionVar 用起來看起來,更像 dictionary

    Maya 裏頭有個非常 high level 的 mel function: optionVar,有別於其它的 functions 的簡單與立即反應,它的目的在提供一個跟 Maya session 的設定檔的存取。換個說法,你可以在一次執行 Maya 的時候,存一個自訂的變數 IamSoHandsome,然後在下一次執行時取出來用。所以你可以把它想成是 Windows 上頭的 registry、Unix-like 下的 .ooxx 個人設定檔、或是會永續存在下去的 environment variables。

    optionVar 另一個優秀的地方在於,它允許你隨時更改存下來的變數的 data type。所以你可以今天把 IamSoHandsome 宣告成一個只有 0 與 1 的 boolean,然後明天改成 "yes" 與 "no" 的 string,只要直接指定給它一個新的 data type 就做到了。如下:

    optionVar -iv "IamSoHandsome" 1;
    ...
    ...
    optionVar -sv "IamSoHandsome" "yes";
    ...
    ...

    optionVar -sv:

    creates a new variable named using the first string with value given by the second string. If a variable already exists with this name, it is overridden in favour of the new value (even if the type is different).

    pymel 更酷了,它讓整個 optionVar 變成了一個 dictionary-like global object(singleton class),所以你可以非常迅速地做到如下幾件事:

    print optionVar["RecentFilesList"]
    
    myVar = optionVar.get("IamSoHandsome", 1) # default to 1 if it is not in optionVar
    
    optionVar["IamSoHandsome"] = 1
    optionVar["IamSoHandsome"] = "yes"
    optionVar["IamSoHandsome"] = [1, 2, 3, 4] # quickly change its value and data type without any hesitate
    optionVar["IamSoHandsome"] = optionVar["IamSoHandsome"][1:] # [1,2,3,4] => [2,3,4]

    我愈來愈愛用 optionVar 來提供更好的 maya GUI scripts 了 :)

  • Feb
    4

    Maya UI 裏的 Callback

    GUI 是個非常偉大的發明,它讓電腦就這麼快速地滲透進家家戶戶與各大小商辦或公家機關,因為我們有 Mac OS、Windows、X Window System,再加上現下當紅的掌上型界面(iPhone、NDSL、PDS)與從 web browser 裏出生的 web interface,世界就此進入資訊高科技時代,人人都會使用這些電腦設備,因為它們都提供了一套容易使用的 GUI。

    我依稀記得我的第一個有 GUI 的程式,是在新竹中學的軟研社22th招生影片)裏頭,使用 QuickBasic 呼叫底層的 VGA interrupt 來存取 video buffer,在螢幕上畫了兩個眼睛,裏頭的眼珠子會依據滑鼠游標所在的方位,往那對應的方向看過去。這隻程式除了滑鼠移動時所造成的 event 以外,就沒別的了,既沒有 button 也沒有 textField 供輸入什麼的,非常的陽春,但我寫得很愉快,這經驗一直是那麼的深刻。

    時間拉回到進入太極影音以降。在 Maya 裏頭 scripts 提供 GUI programming 的函式,算是進入「MFC 之前,event driven 之後」的時代。所以 Maya 提供的 control(ex, button)大都可以針對一些 event(ex, click)指定一個 callback ,寫這類的程式對於一般修讀過四年資訊課程的學生來說,不是什麼問題。

    遺憾的是,Maya 的 callback 機制很原始,callback 裏頭的程式碼並不會繼承所在的 namespace(or scope)的資訊,而是在 global namespace 下執行的,拿下頭這個例子來看:

    proc dgCallbackCancel() {
        deleteUI "ooxx";
    }
    
    global proc dgShiftAnimationKeysGUI() {
        if(`window -exists "ooxx"`) {
            deleteUI "ooxx";
        }
    
        string $win = `window -title "ooxx by Drake" "ooxx"`;
    
        rowColumnLayout -nc 2 -cal 1 "right" -cal 2 "left" -cw 1 100 -cw 2 200 -w 300;
        text -label "Mode : ";
        string $mode = `radioButtonGrp -nrb 2 -labelArray2 "All" "Selected" -cw2 80 80 -select 1 "mode"`;
        text -label "Frames to shift : ";
        string $shift = `textField "shift"`;
        button -label "Do It" -c "dgCallbackDoIt()";
        button -label "Cancel" -c "dgCallbackCancel()";
    
        showWindow();
    }
    
    dgShiftAnimationKeysGUI();

    兩個 button 的個別 callback functions 裏頭,為了可以順利存取到其它 control,使用了 hard-coded control name 的方式(直接假設 window name 是 "ooxx", 另外兩個 control 分別是 "mode" 與 "shift"),另一種作法是使用全域變數的方式,直接把這些 control 的名稱存到全域變數,然後在 callback functions 裏頭使用他們。

    透過 pymel (0.7.8) 的包裝,加上 python 動態執行時仍擁有 local scope variables 的特性,可以既不用寫死 control name(寫死也沒關係,但已經沒有這個必要了),也不需要使用到全域變數,就可以做同與上頭一樣的事。

    from pymel import *
    
    def dgCallbackCancel(win):
        # just delete the window
        win.delete()
    
    def dgCallbackDoIt(win, shift, mode):
        # do something and then delete the window
        # ...
        modeValue = mode.getSelect()
        shiftValue = shift.getText()
    
        win.delete()
    
    def dgShiftAnimationKeysGUI():
        winName = "ooxx"
        if window(winName, exists=1):
            deleteUI(winName, window=1)
        if windowPref(winName, exists=1):
            windowPref(winName, remove=1)
        win = window(winName, title="ooxx by Drake")
    
        rowColumnLayout(nc=2, cal=[(1, "right"), (2, "left")], cw=[(1, 100), (2, 200)], w=300)
        text("Mode : ")
        mode = radioButtonGrp(nrb=2, labelArray2=["All", "Selected"], cw2=[80, 80])
        text("Frames to shift : ")
        shift = textField()
        button("Do It", align="center", c=lambda *args: dgCallbackDoIt(win, shift, mode))
        button("Cancel", align="center", c=lambda *args: dgCallbackCancel(win))
    
        showWindow(win)
    dgShiftAnimationKeysGUI()

    請仔細看 button 那兩行的最後。pymel(0.7.8)建議使用 lambda function 來包裝,然後依自己的需要把 local variables 給傳進 callback function 裏頭。所以在這個例子裏題頭,我可以把 window 與兩個 control(mode, shift)直接傳給 callback function,而不用去理會任何細節,同時很意外地減少了 programming side-effect。

    pymel(0.7.8)對於 Maya GUI control 的包裝還非常的陽春,雖然每個 UI control 都包成一個 class,但用起來與直接呼叫 mel 無異,並沒有提供多少的抽像與方便的設計。舉個最明顯的例子:當你要對任一個 control 取值時,你得記得是使用 getText()、getSelect()、getLabel()、getValue()、getMaxValue()…多少有點強制要求 programmer 要熟習 mel UI programming 那一套,或是得很勤勞地查閱 api 說明文件或是利用 pydoc 之類的。但我想這一點在未來的發展會獲得改善。不過我也意外注意到,這問頭不管是古老的 MFC 還是 wxWidgets, Qt,也都一樣有這類的問題就是。我總覺得,讓使用者愈少去猜或計要呼叫哪個 member function,就是一個很好的設計 :p

  • Jul
    28

    Maya camera 轉換到 RenderMan 相對應的參數

    如果因為一些原因,你需要自行在 RIB 裏頭產生一段 camera 相關的參數來(像是使用 prman 的 multi-camera 這個 13.5 才有的新功能),那這篇文章就是在提示你,怎麼搞懂 RIB 裏頭,camera 會用到的 transformation, perspective 參數乃至於 screenwindow。

    Transformation

    取出 camera 的 worldInverseMatrix 這個屬性出來(用 mel 或 python 都可),它是一個 matrix。然後因為 RenderMan 是使用左手座標系統,而 Maya 是使用右手座標系統,所以你還得把這個 matrix 的第三行(直行橫列)的值變為相反數,亦即把第三行的值(m[2] m[6] m[10] and m[14])乘上 -1。

    Perspective

    RenderMan's "perspective" camera FOV = rad_to_deg(2*atan((xres/yres)*12.7/cam.focalLength))

    ScreenWindow

    這部分,可以參考我的好同事 Mao 所寫的一篇 blog

    感謝 Dan Maas 的解說 :)

  • Jul
    5

    如何從選取的節點中,過濾出燈光來?

    Screenshot_Maya_retrieve_lights_from_selection

    簡答如下:

    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;
    }