2018年9月24日 星期一

初探Chromium Embedded Framework

初探Chromium Embedded Framework

前言

  Chromium Embedded Framework以下簡稱為"cef",是一個可以在Native App裡使用Chrome的Framework,知名的"Electron"的前端就是"cef",由於是個開源專案,所以來研究一下在此做個紀錄。

內容

  如果上網搜尋很容易會找到開源專案的位置 Chromium Embedded Framework Overview,開啟後會得到Git的URL,如下圖
cef的Git
如果就這麼開開心心的用Git直接Clone的話,就等於在"浪費時間",因為那個Git並不是用來給使用者存取的,而是給"cef"的自動建置流程來存取的!所以下載完後會發現根本不能建置。那麼該如何建置"cef",請參考MasterBuildQuickStart

  在開啟MasterBuildQuickStart後,先看"File Structure",如下圖
cef的File structure
這個"File Structure"有一部分是要手動建構的,不是自動產生的,該網頁還會說明各個平台建置需求與步驟,以下說明為Windows平台。雖說網頁上說要準備40G的空間,但我實際建置64位元Debug版後,發現大概要55G,建立前請確認空間是否充足。

  第一步是建立"automate"與"chromimu_git"資料夾,說明的Root資料夾為"c:\code",請示需求更改Root資料夾。

  第二步是在Root資料夾下建立"depot_tools"資料夾,並下載說明中的"depot_tools.zip",如下圖
下載"depot_tools.zip"
載完後,解壓到"depot_tools"資料夾下即可。

  第三步是執行"update_depot_tools.bat",這個檔案在"depot_tools"資料夾裡,但在執行前請確認Python的版本,限定2.7.x版才可以成功建置,如果是3以後的版本請切換。

  第四步是將"depot_tools"資料夾的位址加到環境變數"path"裡。

  第五步是下載"automate-git.py",如下圖
下載"automate-git.py"
下載後放到"automate"資料夾下。

  第6步是製造"update.bat",到"chromimu_git"的資料夾下新增一個"update.bat",內容如下
set GN_DEFINES=use_jumbo_build=true
set GN_ARGUMENTS=--ide=vs2017 --sln=cef --filters=//cef/*
python ..\automate\automate-git.py --download-dir=c:\code\chromium_git --depot-tools-dir=c:\code\depot_tools --no-distrib --no-build

注意一下紅色的內容,有兩個檔案位置會因為Root資料夾的位置不一樣而改變,請示需要修改。改完後直接執行"update.bat"。這個步驟有點就大概要一個半小時,執行中如果中斷就要重來,請先注意。

  第7步是製造"create.bat",到"(root)\chromimu_git\chromium\src\cef"下,製造"create.bat",內容如下
set GN_DEFINES=use_jumbo_build=true
set GN_ARGUMENTS=--ide=vs2017 --sln=cef --filters=//cef/*
call cef_create_projects.bat

改完後就直接執行。

  第8步就是啟動自動建置,到"(root)\chromimu_git\chromium\src",執行以下命令
ninja -C out\Debug_GN_x64 cef

這種個建置過程非常久,在我的電腦是3個小時,請注意。如果建置完成就可以在"(root)\chromimu_git\chromium\src\out\Debug_GN_x64"下找到"cefclient.exe",執行後就可得到一個cef的視窗。


參考資料

MasterBuildQuickStart

2018年9月17日 星期一

DirectInput的學習心得(2)

DirectInput的學習心得(2)

前言

  續前篇DirectInput的學習心得(1),本次紀錄用DirectInput來偵測手把(Gamepad)。為何不直接使用XInuput呢?為了相容一些較舊的手把,就這麼簡單。如果同時使用DirectInput與XInuput時會有一些要注意的,就是DirectInput可以抓到XInuput的手把,本篇會記錄如何濾除,在此做個紀錄。

內容

  在開始偵測手把時,要先製造出IDirectInput8,如果不知道如何製造,請參照前篇DirectInput的學習心得(1),接著跟上次鍵盤的狀況不太一樣,由於系統鍵盤只會有一個,所以不需要去列舉裝置,但手把可能不只一個,所以透過EnumDevices()來抓出有多少個手把,接著對每個手把做SetCooperativeLevel()與SetDataFormat(),這部分和上次的鍵盤一樣,但這次要多一個步驟,就是透過EnumObjects()來取得類比按鈕的資訊,最後完成後一樣要Acqurie()。
  來看看範例程式,如下
#include "dinput.h"
#include "dinputd.h"
struct SEnumJoystickContext
{
  IDirectInput8* pDirectInput;
  HWND hWnd;
  std::vector<IDirectInputDevice8>& deviceList;
};
BOOL CALLBACK EnumJoystickCallBack(const DIDEVICEINSTANCE* pdidInstance,VOID* pContext)
{
  //Cast context...
  SEnumJoystickContext* pEnumJoystickContext=(SEnumJoystickContext*)pContext;

  //
  LPDIRECTINPUTDEVICE8 pNewDevice=NULL;
  HRESULT hr=pEnumJoystickContext->pDirectInput->CreateDevice(pdidInstance->guidProduct,&pNewDevice,NULL);
  if(hr != DI_OK)
    return DIENUM_STOP;
  //
  pEnumJoystickContext->deviceList.push_back(pNewDevice);
  return DIENUM_CONTINUE;
}
BOOL CALLBACK EnumObjectsCallBack(const DIDEVICEOBJECTINSTANCE* pdidoi,VOID* pContext)
{
  IDirectInput8* pDevice=(IDirectInput8*)pContext;
  if(pdidoi->dwType &DIDFT_AXIS)
  {
    DIPROPRANGE diprg;
    diprg.diph.dwSize=sizeof(DIPROPRANGE);
    diprg.diph.dwHeaderSize=sizeof(DIPROPHEADER);
    diprg.diph.dwHow=DIPH_BYID;
    diprg.diph.dwObj=pdidoi->dwType;
    diprg.lMin=-1000;
    diprg.lMax=1000;
    //
    if(pDevice->SetProperty(DIPROP_RANGE,&diprg.diph)!=DI_OK)
      return DIENUM_STOP;
  }
  return DIENUM_CONTINUE;
}
void DetectedGamepad(IDirectInputDevice8* pDIDeviceGamepad)
{
  DIJOYSTATE2 gamepadState;
  pDIDeviceGamepad->GetDeviceState(sizeof(DIJOYSTATE2), &gamepadState);
  //Get x axis was left
  if(gamepadState.lX < 0)
  {
    //do something...
  }
  //Get button 0 state
  if(gamepadState.rgbButtons[0] &0x80)
  {
   //do something...
  }
}
int WINAPI wWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow )
{
  //Init main window and IDirectInput8
  //...
  HWND mainWin = initMainWindow;
  IDirectInput8* pDirectInput = initDirectInput;
  std::vector<IDirectinputdevice8*> deviceList;
  //
  SEnumJoystickContext enumJoystickContext = { pDirectInput, mainWin, deviceList};
  pDirectInput-&gt;EnumDevices(DI8DEVCLASS_GAMECTRL, EnumJoystickCallBack, &enumJoystickContext, DIEDFL_ATTACHEDONLY) 
  for(int i=0;i<deviceList.size();i++)
    pGamepadDevice->SetCooperativeLevel(mainWin,DISCL_FOREGROUND | DISCL_NONEXCLUSIVE);
    pGamepadDevice->SetDataFormat(&c_dfDIJoystick2);
    //
    pGamepadDevice->EnumObjects(EnumObjectsCallBack, pGamepadDevice, DIDFT_ALL);
    pGamepadDevice->Acquire();
  }
  //Init gamepad end...
}

在製造完IDirectInput8後,會看到一個SEnumJoystickContext的Struct與 EnumDevices(),SEnumJoystickContext是要傳給EnumDevices()的參數,再看看EnumDevices()的參數會發現一個CallBack function名為EnumJoystickCallBack,這個CallBack 會在每次被IDirectInput8列許到裝置後被喚起,看看喚起的EnumJoystickCallBack(),有個引數名為"pContext",會發現在開頭就被轉型為SEnumJoystickContext,也會看到另一個引數"pdidInstance",這次必須透過DIDEVICEINSTANCE來製造出IDirectInputDevice8,這個CallBack 的回傳值有"DIENUM_CONTINUE"與"DIENUM_STOP","DIENUM_CONTINUE"表示持持續列舉,"DIENUM_STOP"則代表中止列舉,在EnumDevices()結束後,所有的IDirectInputDevice8都會在"deviceList",接著就初始化每個裝置。初始化裝置和鍵盤很像,一樣是SetCooperativeLevel()與SetDataFormat(),但這次要多一個步驟,就是EnumObjects(),EnumObjects()一樣需要一個CallBack ,這個CallBack 主要用來初始化類比按鍵,最後一樣要Acquire()。偵測的按鍵的部分可以參考範例的DetectedGamepad(),透過GetDeviceState()可以得到"DIJOYSTATE2",裡面就會放置所有按鍵的值,詳細地的說明可以到DIJOYSTATE2 Structure查詢。
  如果需要與XInuput同時使用,官方的提供的做法是過濾掉XInuput的裝置,官方提供一個檢查是否為XInuput裝置的function,這個function可以用在EnumJoystickCallBack()裡去做濾除的動作,詳細的程式碼可以在XInput and DirectInput裡找到。

參考資料

XInput and DirectInput
DIJOYSTATE2 Structure

相關文章

DirectInput的學習心得(1)

2018年9月10日 星期一

DirectInput的學習心得(1)

DirectInput的使用(1)

前言

  在偵測輸入時可以採用XInput來偵測,可以參考XInput的使用這一篇,但XInput並不支援鍵盤與滑鼠,且手把要是年代較久遠的話也會無法被XInput偵測到。為什麼不用訊息幫浦來偵測輸入呢?理由很簡單,作業系統的訊息幫浦沒辦法調整偵測的速度,像現代電玩時常需求的FPS60偵測速度是達不到的,就我以前實測的經驗,訊息幫浦不但偵測速度不夠,而且偶爾還有掉輸入(明明有輸入卻沒反應),雖然不知是否為偵測速度不夠造成,但用DirectInput偵測鍵盤的輸入,這個狀況就可以得到解決。這次的紀錄只會記錄到鍵盤的偵測與使用,在此做個紀錄。

內容

  使用DirectInput的過程並不會太繁雜,首先使用DirectInputCreate()製造出IDirectInput8,這個介面只需造一次即可,接著用IDirectInput8來製造出IDirectInputDevice8,而依據製造的參數不同可以讓IDirectInputDevice8代表鍵盤、滑鼠與手把,本篇將說明如何將IDirectInputDevice8初始化為鍵盤。
  初始化鍵盤的範例碼如下

#include <dinput>
#include <dinputd>
void DetectedKeyboardKeys(IDirectInputDevice8* pDIDeviceKeyboard)
{
  unsigned char keyState[256];
  if(pDIDeviceKeyboard->etDeviceState(256,keyState) != DI_OK)
    return;
  //if GetDeviceState() is fined,keyState will save all key state from keyboard...
  //Follow detect sample...
  //Is num 0 pressed? 
  if(keyState[DIK_0])
  {
    //do something...
  }
  //Is key a pressed? 
  if(keyState[DIK_A])
  {
    //do something...
  }
  
}
int WINAPI wWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow )
{
  //Init main window
  //...
  HWND mainWin = initMainWindow;
  //Init DiectInput...
  IDirectInput8* pDirectInput = nullptr;
  if(DirectInput8Create(hInstance,DIRECTINPUT_VERSION,IID_IDirectInput8,(void**)&pDirectInput ,NULL) != DI_OK)
    return 1;
  //Init keyboard
  IDirectInputDevice8* pDIDeviceKeyboard = nullptr;
  if(pDirectInput->CreateDevice(GUID_SysKeyboard,&pDIDeviceKeyboard ,NULL) != DI_OK)
  {
    pDirectInput->Release(); 
    return 1;
  }
  //
  if(pDIDeviceKeyboard->SetCooperativeLevel(mainWin,DISCL_FOREGROUND|DISCL_NONEXCLUSIVE) != DI_OK)
  {
    pDIDeviceKeyboard->Release();
    pDirectInput->Release(); 
    return 1;
  }
  if(pDIDeviceKeyboard->SetDataFormat((DIDATAFORMAT*)&amp;c_dfDIKeyboard) != DI_OK)
  {
    pDIDeviceKeyboard->Release();
    pDirectInput->Release(); 
    return 1;
  }
  //
  pDIDeviceKeyboard->Acquire();
  //Init keyboard end
  // 
  return 0;
}

在主程式一如之前所說製造出IDirectInput8,要注意HINSTANCE是必要的參數,再來是透過 IDirectInput8的CreateDevice()來製造出IDirectInputDevice8,並可載參數上看到"GUID_SysKeyboard",這個參數會決定裝置是鍵盤、滑鼠或手把,接著是SetCooperativeLevel(),主要的目的確定要偵測哪個視窗的輸入,SetDataFormat()決定偵測資料的輸出格式,在鍵盤時這是固定的,所以用c_dfDIKeyboard來設定即可,接著Acquire(),這個過程很重要,初始化的最後一個流程,勢必要流程請勿省略,Acquire()如果失敗表示該裝置目前沒連上,以上就是初始化鍵盤的部分。偵測的部分在DetectedKeyboardKeys(),首先準備好一塊Buffer,範例使用的是keyState,256是鍵盤上所有的鍵的大小,透過GetDeviceState()來取得所有按鍵的輸入狀態,取完之後如何取得要偵測的按鍵的狀態呢?可以參考
Keyboard Device Enumeration,用該列舉來做為索引值取得目前按鍵的狀態,如果不是0就代表按下。

參考資料

Capturing DirectX DirectInput Keyboard and Mouse Input
DirectInput
Keyboard Device Enumeration

相關文章

DirectInput的學習心得(2)

2018年9月3日 星期一

從Blender裡萃取模組資料(8)

從Blender裡萃取模組資料(8)

前言

  續前篇從Blender裡萃取模組資料(7),這次說明萃取Skeleton的動畫,在此做個紀錄。

內容

  如果使用前篇的範例來萃取Skeleton的動畫,應該會發現竟然完全沒問題,可以正常執行,但若仔細一看"Data path"的欄位,就會發現它有何不同了,就拿"location"來說,在Skeleton的動畫可能會是"pose.bones["spine"].location",其中的"spine"為該Bone的名稱。這裡有個陷阱!如果你直覺地用字串的split()來parse這個"Data path"的欄位的話會有問題,問題會發生在Bone的名稱是可以包含".",舉例來說如果Bone的名稱是"spine.001"的話,"Data path"就會變成"pose.bones["spine.001"].location",接著split()後就變成"[pose, bones["spine, 001, location]",明顯的,Bone的名稱會被切到,還必須注意Blender的名稱可以很自由,名稱裡可以出現單引號或雙引號,所以需要特製的Parse。

  接著用前篇的範例為基礎,製作出以下範例
import bpy

def ShowActionInfo(act):
  print('animation name',act.name)
  for curve in act.fcurves:
    print('Data path:',curve.data_path,' ',end='')
    print('')
    if curve.data_path == 'location':
        print("Data from transform.location")
    elif curve.data_path == 'rotation_euler':
        print("Data from transform.rotation_euler")
    elif curve.data_path == 'rotation_euler':
        print("Data from transform.rotation_quaternion")
    elif curve.data_path == 'scale':
        print("Data from transform.scale")
    elif curve.data_path[:12] == 'pose.bones["':
        namelastIndex = curve.data_path.rfind('"')
        if namelastIndex >= 0:
            boneName = curve.data_path[12:namelastIndex]
            propertyNameIndex = curve.data_path.rfind('.')
            if propertyNameIndex >= 0:
                propertyName = curve.data_path[ (propertyNameIndex+1):]
                if propertyName == 'location':
                    print('Bone:',boneName,' Property:transform.location')
                elif propertyName == 'rotation_euler':
                    print('Bone:',boneName,' Property:transform.rotation_euler')
                elif propertyName == 'rotation_quaternion':
                    print('Bone:',boneName,' Property:transform.rotation_quaternion')
                elif propertyName == 'scale':
                    print('Bone:',boneName,' Property:transform.scale')
                else:
                    print('Unknown property:',propertyName)
            else:
                print('Parse skeleton property error!')
        else:
            print('Parse skeleton bone name error!')
    else:
        print("Unknown data path!")
    print('Array index:',curve.array_index,' ',end='')
    print('')
    for keyFrame in curve.keyframe_points:
      print('Key time:',keyFrame.co[0],' ',end='')
      print('Key value:',keyFrame.co[1],' ',end='')
      print('')
#
def ShowAnimationInfo(obj):
  if obj.animation_data == None:
    print("No animation data in object!")
    return
  #Check actived action...
  actionCon = 0
  if obj.animation_data.action != None:
    ShowActionInfo(obj.animation_data.action)
    actionCon+=1
  #Check action in NLA tracks...
  for track in obj.animation_data.nla_tracks:
    for strip in track.strips:
      ShowActionInfo(strip.action)
      actionCon +=1
  print("Animation amount:",actionCon )
#
tagObj = bpy.data.objects['metarig']
ShowAnimationInfo(tagObj )

可以看到ShowActionInfo()裡多了非常多的if判斷,目前沒找到比較簡易的寫法,目前的作法以直觀與好懂為原則, 所以程式碼相當長。Bone的名稱如之前所說由於編輯器可以相當自由命名,所以用rfind()來找,property的部分也 是使用rfind()來找也請注意。

參考資料

Blender Documentation Contents

相關文章

從Blender裡萃取模組資料(7)

2018年8月27日 星期一

從Blender裡萃取模組資料(7)

從Blender裡萃取模組資料(7)

前言

  續前篇從Blender裡萃取模組資料(6),這次說明動畫的資料如何萃取,在此做個紀錄。

內容

  在開始前,先看看下圖
Blender的動畫資料
在圖中,我將動畫的狀況分為3種,沒有動畫、只有一個動畫與多個動畫這3種,在"沒有動畫"時,物件底下不會有Animation的node,在"只有一個動畫"時,Animation的node底下會有一個"CubeAction"的node,這個node就是啟用中(Actived)的動畫的資料,再來就是"多個動畫"時會發現底下多了個"NLA Tracks"的node,把整個tree展開,會發現底下會多一個"MultiAnimationCubeAction",這個就是非啟用中的動畫資料,如果有多筆非啟用中的動畫的話,"NLA Tracks"的node下就多有多筆"Action Stash"。接著,來看看動畫的內容是如何存放的,如下圖
Blender的動畫內容資料
可以看到很多個Curve,可以想像每個Curve其實就是一個"float"的資料,所以"Location"的動畫被切成3個Curve,再來會看到右側有一些"菱形",而每個"菱形"就代表一個Keyframe,裡面紀錄的是時間與數值。

  接著就來萃取動畫的資料,範例如下
import bpy

def ShowActionInfo(act):
  print('animation name',act.name)
  for curve in act.fcurves:
    print('Data path:',curve.data_path,' ',end='')
    print('Array index:',curve.array_index,' ',end='')
    print('')
    for keyFrame in curve.keyframe_points:
      print('Key time:',keyFrame.co[0],' ',end='')
      print('Key value:',keyFrame.co[1],' ',end='')
      print('')
#
def ShowAnimationInfo(obj):
  if obj.animation_data == None:
    print("No animation data in object!")
    return
  #Check actived action...
  actionCon = 0
  if obj.animation_data.action != None:
    ShowActionInfo(obj.animation_data.action)
    actionCon+=1
  #Check action in NLA tracks...
  for track in obj.animation_data.nla_tracks:
    for strip in track.strips:
      ShowActionInfo(strip.action)
      actionCon +=1
  print("Animation amount:",actionCon )
#
tagObj = bpy.data.objects['Cube']
ShowAnimationInfo(tagObj )

在ShowAnimationInfo()的部分,只要是在辨識動畫的數量,要注意一點的是啟用中的動畫可能會是None,在編輯器編輯時是可以將它調成空的,ShowActionInfo()的部分,有"Data path"與"Array index"兩個資料,"Data path"可能會是"location"、"rotation_euler"或"scale"...等,"Array index"則是個數值,假設得到的結果為"location"與"1",也就是location[1],進一步說就是location.y,請依此類推,"Key time"舊式下方時間條的數值,"Key value"就是數值。

參考資料

Blender Documentation Contents

相關文章

從Blender裡萃取模組資料(6)
從Blender裡萃取模組資料(8)

2018年8月20日 星期一

從Blender裡萃取模組資料(6)

從Blender裡萃取模組資料(6)

前言

  續前篇從Blender裡萃取模組資料(5),這次來說明SkinedMesh的權重資料如何取得,在此做個紀錄。

內容

  SkinedMesh的權重資料指的是什麼呢?可以參考下圖
SkinedMesh的權重資料
在SkinedMesh裡的每個Vertex都會有個別的權重資料,裡面會記錄那些Bone對該Vertex的引響力,通常小於1並且不會是負數,範例圖可以看到該Vertex有兩個Bone對它有引響力,分別是"Bone"與"Bone.002",而數值分別為"0.906"與"0.235"。

  接著,就來看如何取得權重資料,範例程式如下
import bpy

tagMesh = bpy.data.meshes["Cube"]
#Create bmesh from mesh
tagBMesh = bmesh.new()
tagBMesh.from_mesh(tagMesh)
#
tagBMesh.verts.ensure_lookup_table()
layer_deform = tagBMesh.verts.layers.deform.active

#Start extract
baseIndex = 0
for face in tagBMesh.faces:
  for i in range(len(face.loops) ):
    print("Position:",face.loops[i].vert.co," ",end="")
    print("Normal:",face.normal," ",end="")
    if layer_deform!=None:
      vertData = tagBMesh.verts[face.loops[i].vert.index]
      weightKeyList = vertData[layer_deform].keys()
      for weightKey in weightKeyList:
        print("WeightIndex:",weightKey," WeightValue:",vertData[layer_deform][weightKey]," ",end="")
    
  print("Loop vertex end")
  loopLen = len(face.loops)
  for i in range( len(face.loops) - 2):
    print("index0:",baseIndex," ",end="")
    print("index1:",baseIndex + i + 1," ",end="")
    print("index2:",baseIndex + i + 2," ",end="")
  print("Loop index end")
  
#Free bmesh...
tagBMesh.free()

這個範例相似於從Blender裡萃取模組資料(1)的範例,但這次會去拿權重的資料。在開頭新增了取的"layer_deform",如果這個值是None表示這個Mesh沒有權重資料,取得權重的部分可以看到事先取得Vertex的資料後再取得權重資料,跟之前的"位置"與"Normal"並不一樣,這一點要注意,再來是取得"weightKeyList",在於List的Key來取得權重數值,這個部分有點特別,"weightKey"並不是Bone的名稱,而只是一個數字!這個數字其實是"Armature.bones"的陣列索引 ,如果忘記"Armature.bones"可以參考從Blender裡萃取模組資料(4)。所以如果要得到名稱的話還需要搭配Armature的資料才可以取得。權重數值的部分取出來就會跟編輯器看到的是一樣的不需要再處理。

參考內容

Blender Documentation Contents

相關文章

從Blender裡萃取模組資料(5)
從Blender裡萃取模組資料(7)

2018年8月13日 星期一

從Blender裡萃取模組資料(5)

從Blender裡萃取模組資料(5)

前言

  續前篇從Blender裡萃取模組資料(4),這次會說明Skeleton的transform如何取得,在此做個紀錄。

內容

  在上一篇從Blender裡萃取模組資料(4)中提到了Hierarchy的取得,但Skeleton的Bone還必須包含"Position"與"Rotation"的資訊,這次就坐明這兩種資訊如何取得。
  先來看看只考慮"Position"的部分如何取得,範例如下
import bpy

def ShowBonePosition(bone):
  if bone.parent != None:
    boneWorldPos = bone.head_local
    parentBoneWorldPos = bone.parent.head_local
    print(' Bone pos:',boneWorldPos - parentBoneWorldPos,end='')
  else:
    print(' Bone pos:',bone.head_local,end='')

def ShowBoneInfo(bone,levelCon=0):
  for i in range(levelCon):
    print("  ")
  #
  print('Bone name:',bone.name,end='')
  ShowBonePosition(bone)
  print('')
  #
  for childBone in bone.children:
    ShowBoneInfo(childBone ,levelCon+1)

tagObj = bpy.data.objects["metarig"]
if type(tagObj.data) == bpy.types.Armature:
  armatureData = tagObj.data
  rootBoneList = []
  for bone in armatureData.bones:
    if bone.parent == None:
      rootBoneList.append(bone)
  #
  for rootBone in rootBoneList:
    ShowBoneInfo(rootBone)

搭配前一篇的範例,這次新增"ShowBonePosition()",取得的方法並不困難,Bone的"head_local"代表的是世界座標,所以在有Parent的狀況時,要將它轉成本地座標。

  接著來看看"Position"與"Rotation"如何取得,範例如下
import bpy

def ShowBonePostionAndRotation(bone):
  quat = bone.matrix.to_quaternion()
  if bone.parent != None:
    boneWorldPos = bone.head_local
    parentBoneWorldPos = bone.parent.head_local
    localPos=boneWorldPos - parentBoneWorldPos 
    parentRot = bone.parent.matrix_local.to_3x3().to_quaternion()
    invParentRot = parentRot.inverted()
    print('Bone pos:',invParentQuat.to_matrix() * localPos,end='')
  else:
    print(' Bone pos:',bone.head_local,end='')

  print(' Bone rot:',quat,end='')

def ShowBoneInfo(bone,levelCon=0):
  for i in range(levelCon):
    print("  ")
  #
  print('Bone name:',bone.name,end='')
  ShowBonePostionAndRotation(bone)
  print('')
  #
  for childBone in bone.children:
    ShowBoneInfo(childBone ,levelCon+1)

tagObj = bpy.data.objects["metarig"]
if type(tagObj.data) == bpy.types.Armature:
  armatureData = tagObj.data
  rootBoneList = []
  for bone in armatureData.bones:
    if bone.parent == None:
      rootBoneList.append(bone)
  #
  for rootBone in rootBoneList:
    ShowBoneInfo(rootBone)

這次的做法很不一樣,理由很簡單,如果只是單單需要知道Bone的旋轉,利用Bone的matrix直接取得就是Local的旋轉,不需要再轉換,但"Position"是會被Parent的旋轉引響的,所以要透過Bone的matrix_local(這個是世界旋轉)的"逆旋轉"來反算"Position"。

參考資料

Blender Documentation Contents

相關文章

從Blender裡萃取模組資料(4)
從Blender裡萃取模組資料(6)