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)

2018年8月6日 星期一

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

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

前言

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

內容

  記得在從Blender裡萃取模組資料(1)時取得"Mesh"的做法嗎?透過"bpy.data.meshes"取得Mesh的資料,但這個方法有個前提是必須知道"Mesh"的名稱,如果要從所屬的物件來取得是如何取?答案是透過"Object.data"這個Property來取得,透過檢查型別或是用"Object.type"來檢查也可以,為什麼要說明這件事呢?因為這次的Skeleton的資料也是用一樣的方式來取得,在Blender的編輯器裡,看到的"Object.data"如下圖
Skeleton的"Object.data"
取得範例程式碼如下
import bpy

tagObj = bpy.data.objects["metarig"]

#check data by type
print('Check object.data use type()')
if type(tagObj.data) == bpy.types.Armature:
  print('Object has armature data')
elif type(tagObj.data) == bpy.types.Mesh:
  print('Object has mesh data')

print('Check object.data use object.type')
if tagObj.type == 'ARMATURE':
  print('Object has armature data')
elif tagObj.type == 'Mesh':
  print('Object has mesh data')

Mesh的資料取得後可以到從Blender裡萃取模組資料(1)裡參考資料如何萃取,這裡就不再贅述。另外,在Blender中,稱Skeleton為"Armature",要找關鍵字請用"Armature"。

  接著,進入正題"Skeleton的Hierarchy如何萃取",取得的方法是透過"Armature.bones"來取得每一個"Bone",而每一個"Bone"皆有parent與children的資訊,所以只要抓Root bone(沒有parent的Bone),再從Root bone的children來萃取整個Hierarchy即可,範例的程式碼如下
import bpy

def ShowBoneInfo(bone,levelCon=0):
  for i in range(levelCon):
    print("  ")
  #
  print('Bone name:',bone.name)
  #
  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"裡有"name"這個Property,可以取得Bone的名稱。"ShowBoneInfo()"會遞迴出整個Hierarchy,"levelCon"可以知道目前的階層數。

參考資料

Blender Documentation Contents

相關文章

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

2018年7月30日 星期一

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

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

前言

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

內容
  在Blender設定Hierarchy有3種常用的方法,一個是直接設定物件的Relations的Parent,如下圖
設定Relations的Parent
這個方法我認為是最好的方式,因為這個方法支援當Parent是Bone的狀況,第二種是"Set Parent To",用法很直覺,選取Child物件後再押住"Shift"選取Parent物件,然後按下Ctrl+p,選取需要的Parent選項即可,這方法很直覺而好用,還可以用來綁骨架,但這個方法有個缺陷,就是無法將物件的Parent設定到Bone,操作狀況如下圖
使用"Set Parent To"
接著是第三種方法,使用"Child Of Constraint",操作狀況如下圖
使用"Child Of Constraint"
這個方法雖然也可以達成Hierarchy效果,但有些相當棘手的問題,問題一,在操作狀況圖片可以看到物件的Hierarchy並沒有真正的改變,這會造成匯出模組時有些物件(Child Of Constraint的Parent物件)必須先匯出,問題二,這個方法並不會防止循環Hierarchy或是2個以上的Parent,如果先用第一或第二種方法設定Hierarchy為A物件的Child為B物件,B物件的Child為C物件,接著利用
"Child Of Constraint"將C物件的Parent設成A物件,造成循環Hierarchy且C物件具有2個Parent,Blender並不會阻止這件事的發生,所以這第三種方法就不打算支援了。
  接著就來看看由第一與第二種設定Hierarchy的資料如何萃取,萃取的範例如下
tagObj = bpy.data.objects["Cube"]
#
for obj in tagObj.children:
  print("child name:",obj.name)
#
if obj.parent != Nome:
  if obj.parent_type=='OBJECT':
    print("parent name:",obj.parent.name)
  elif obj.parent_type=='ARMATURE':
    print("parent name:",obj.parent.name)
  elif obj.parent_type=='Bone':  
    print("parent name:",obj.parent.name," bone name:",parent_bone)

只要存取物件的"Children"就可以得到該物件的Child物件,然後可以透過"Parent"來得到Parent物件,唯一要記住的特例是當物件的Parent是Bone的狀況,實際狀況會到萃取骨架資料時再詳細說明。

參考資料

Blender Documentation Contents

相關文章

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

2018年7月23日 星期一

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

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

前言

  續前篇從Blender裡萃取模組資料(1),這次會說明物件Transform的資料如何萃取,在此做個紀錄。

內容

  萃取Transform的Position與Scale相當容易,直接從object的Property取得即可,但Rotation的部分就不能直接取得了,相看下圖
Blender的旋轉模式
可以在編輯器看到旋轉模式的選項,在不同的旋轉模式要取不同的Property才能正確取到值,如下說明:
1.  Quaternion(WXYZ)模式下,取roation_quaternion
2.  XYZ Euler模式下,取roation_euler
3.  XZY Euler模式下,取roation_euler
4.  YXZ Euler模式下,取roation_euler
5.  YZX Euler模式下,取roation_euler
6.  ZXY Euler模式下,取roation_euler
7.  ZYX Euler模式下,取roation_euler
8.  Axis Angle模式下,取roation_axis_angle

旋轉模式在編輯器的預設值是"XYZ Euler",以我的需求來說,我會希望它是"Quaternion(WXYZ)",所以該怎麼轉換呢?只要將現在的旋轉模式轉到你想要的模式後,再去取對應的Property即可,整個Transform的資料萃取範例如下

import bpy

tagObj = bpy.data.objects["Cube"]
#
print('Object postion:',tagObj.location)

if obj.rotation_mode == 'QUATERNION':
  print('Object ratation(quaternion):',tagObj .rotation_quaternion)
else:
  #save old ratation mode for recover it.
  oldRotationMode = obj.rotation_mode
  #
  obj.rotation_mode = 'QUATERNION'
  print('Object ratation(quaternion):',tagObj .rotation_quaternion)
  #Recover ratation mode
  obj.rotation_mode = oldRotationMode 

#
print('Object scale:',tagObj.scale)

在取得非"Quaternion"時要把舊的模式記住,這只是為了恢復原本的操作模式,如果不介意旋轉模式被更改的話可以直接設成想要模式後取Property即可。

參考資料

Blender Documentation Contents

相關文章

從Blender裡萃取模組資料(1)
從Blender裡萃取模組資料(3)

2018年7月16日 星期一

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

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

前言

  從Blender裡匯出模型的完整範例很難搜尋到,都只能搜尋到片段的資料會出範例,所以這裡做個紀錄,由於目前並未研究透徹,只會萃取"位址"、"Normal"與"Index"的部分。

內容

  在之前的那一篇初探Blender的script裡有一個輸出的範例,那個範例輸出的資料恐怕是有問題的,如果用該範例輸出一個Cube的話,會得到36個vertex與36個index,這個和一般在Direcct3D與OpenGL的範例不一樣,Direcct3D與OpenGL的範例是24個vertex與36個index,其中的vertex是有Face的概念的,而不是將全部的Triangle一個一個輸出。
  這裡說一下輸出格式的問題,由於每個引擎有自己的格式,所以範例的用法並會包含輸出的格式,而是著重在從Blender裡將資料萃取出來,至於輸出格式的部分,相信並不困難,這裡就不記錄了。
  接著,就來看萃取的範例
import bpy
import bmesh

tagMesh = bpy.data.meshes["Cube"]
#Create bmesh from mesh
tagBMesh = bmesh.new()
tagBMesh.from_mesh(tagMesh)

#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="")
  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()

範例會去取"Cube"這個Mesh,如果取其它的Mesh的話,更換名稱即可。接著,會去從每個face裡取出loop的資料,這裡如果以"Cube"來說的話,loop就代表了"Cube"的面,如果用len(face.loops)取得的數值會是4,再依據每個loop的索引取得vertex的位址。Normal的部分可以直接從face拿到。接著會依據loop來輸出Index,每個面要輸出的Index應該有6個(兩個Triangle),輸出的過程如下圖
Index的輸出順序
至於BaseIndex是作為目前已第幾個Index為基準輸出Index,這樣就可以輸出6個Index了。
  輸出資料的時候要注意一些事,像是vertex的數值如果要取值時要經過round(),具體的範例如下
tagX = round(vert.co.x,3)
tagY = round(vert.co.y,3)
tagZ = round(vert.co.z,3)

取完後,才會是float的數值,原本的數值是double的。還有要注意Blender的坐標系是"右手坐標系",Index的順序是"順時針",如果需要轉換的話請自行轉換。

參考資料

Blender Documentation Contents

相關文章

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