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)