スキニング用のジオメトリ情報を調べます。
def decode_shape2( obj ):
mesh= obj.data
for poly in mesh.polygons:
for id in range( poly.loop_start, poly.loop_start + poly.loop_total ):
vertex= mesh.vertices[ mesh.loops[ id ].vertex_index ]
for vgroup in vertex.groups:
print( vgroup.group, vgroup.weight )
shape( bpy.data.objects['Cube'] )
頂点自体に、影響を受けるジオメトリの番号 (vertex group)と重みのセットが
必要個数含まれています。
vertex group の番号が実際に指しているジオメトリの情報は
Object に入っています。
def decode_shape1( obj ):
if len(obj.vertex_groups):
for vgroup in obj.vertex_groups:
print( vgroup.index, ':', vgroup.name )
Object ローカルに影響を受けるジオメトリセットがあり、
その index 番号 (group 番号) が頂点に格納されています。
ゲームなどリアルタイム描画時の構造と同じなので、非常にわかりやすく
扱いやすい形式になっているようです。
実際に影響を与えるジオメトリは Armature と呼ばれているようです。
Modifier の扱いとなっており Subdivision Surface 等と同じです。
Object から Modifier を列挙できるため、その中から ‘ARMATURE’ を
取り出すことができます。
def decode_shape3( obj ):
for modifier in obj.modifiers
if modifier.type == 'ARMATURE':
armature_object= mod.object
通常の Node と同じだろうと思っていたのですが構造が少々違うようです。
Bone 周りは Blender の機能やデータ構造を知らないといけないので
正直よく理解していません。
なので後回しにします。
●ファイル出力
実際に addon としてデータを出力してみます。
# io_export_test_a6.py
import bpy
from bpy_extras.io_utils import ExportHelper
from bpy.props import BoolProperty
from mathutils import Vector, Matrix, Color
bl_info= {
"name": "Export flatlib a6",
"category": "Import-Export",
}
class ExportTestA6( bpy.types.Operator, ExportHelper ):
bl_label= 'export flatlib a6'
bl_idname= 'export.flatlib_a6'
filename_ext= '.a6'
def execute( self, context ):
A6Export( self ).export( self.filepath )
return {'FINISHED'}
def menu_func(self, context):
self.layout.operator( ExportTestA6.bl_idname, text="Export flatlib a6 (.a6)" )
def unregister():
bpy.utils.unregister_module(__name__)
bpy.types.INFO_MT_file_export.remove(menu_func)
def register():
bpy.utils.register_module(__name__)
bpy.types.INFO_MT_file_export.append(menu_func)
exporter 本体を A6Export() として別に作成し呼び出す形に。
a6 は独自フォーマットの名称で CPU とかは無関係です。
class A6Export:
def __init__( self, options ):
self.options= options
def decode_tree( self, tree, obj ):
tree.append( Shape( obj ) )
for child in obj.children:
self.decode_tree( tree, child )
def decode_node( self ):
tree= []
for obj in bpy.data.objects:
if not obj.parent:
if obj.type == 'MESH' or obj.type == 'EMPTY' or obj.type == 'ARMATURE':
self.decode_tree( tree, obj )
return tree
def decode_shape( self, tree ):
pass
def output_geometry( self, fo, tree ):
fo.write( "T \"Geometry\"\n" )
for gid,node in enumerate(tree):
fo.write( "N \"%s\"\n" % node.getName() )
if node.getParent():
fo.write( "S \"Parent\" \"%s\"\n" % node.getParent() )
m= node.getMatrix()
fo.write( "I \"ID\" %d\n" % gid )
fo.write( "F \"Matrix\"\n" )
fo.write( "F %f %f %f %f\n" % (m[0][0],m[1][0],m[2][0],m[3][0]) )
fo.write( "F %f %f %f %f\n" % (m[0][1],m[1][1],m[2][1],m[3][1]) )
fo.write( "F %f %f %f %f\n" % (m[0][2],m[1][2],m[2][2],m[3][2]) )
fo.write( "F %f %f %f %f\n" % (m[0][3],m[1][3],m[2][3],m[3][3]) )
def export( self, filename ):
print( self.options )
tree= self.decode_node()
self.decode_shape( tree )
fo= open( filename, 'w' )
self.output_geometry( fo, tree )
fo.close()
まずは階層構造の解析と matrix の出力のみ。
読みだしたあとの Node を格納するコンテナが Shape です。
class Shape:
def __init__( self, obj ):
self._obj= obj
def getMatrix( self ):
return self._obj.matrix_local
def getName( self ):
return self._obj.name
def getParent( self ):
return self._obj.parent.name if self._obj.parent else None
●コマンドとして呼び出す
addon として上記 io_export_test_a6.py を読み込むと
File → Export のメニューに組み込まれますが、これをコマンドとして
呼び出すこともできます。
>>> bpy.ops.export.flatlib_a6( filepath="output.a6" )
Python Console で実行でき、またダイアログを経由しないので便利です。
● mathutils
頂点情報や Matrix には mathutils が使われています。
今まで出てきたのは Matrix, Vector, Color の 3種類です。
・Math Types & Utilities (mathutils)
Vector は shader のような swizzle が可能で、上のページを見ると
xxxx ~ wwww まですべての組み合わせが定義されていることがわかります。
例えば dest.xyz= src.zyx などができるようです。
Color や Matrix には swizzle がないので color.rg のような記述はできません。
それぞれ配列のようにシーケンス型として扱うこともできます。
hlsl 等の shader の場合 swizzle も代入も同一です。
float3 dest, src; dest.xyz= src.xyz; // -- (1) dest= src; // -- (2) dest.x= src.x; // | dest.y= src.y; // |-- (3) dest.z= src.z; // |
上の (1), (2), (3) は全く同じもので値の代入です。
特に Shader Assembler では swizzle と mask の違いを明確にするため
あえて (1) の表記を用いることがあります。
Vector は python のオブジェクトなので、代入は参照であり
swizzle は copy になります。
from mathutils import Vector src= Vector((1,2,3)) dest= Vector((4,5,6)) dest= src // -- (4) (dest is src) == True dest.xyz= src.xyz // -- (5) (dest is src) == False dest.x= src.x // | dest.y= src.y // |-- (6) (dest is src) == False dest.z= src.z // |
(4) は dest に src の参照が入ります。
(5) は新 object を生成して copy します。
src.xyz の代わりに src.copy() や src[:] と記述した場合も同様です。
(6) は値代入なので dest と src は別物です。新たな object が作られません。
場合によっては (6) が欲しいのですが immutable なら (4) で十分で
むしろ効率が良くなります。
Vector 同士の比較には注意が必要です。
Vector((1,2,3)) >= Vector((3,2,1)) # = True -- (7) Vector((1,2,3)) > Vector((3,2,1)) # = False -- (8) Vector((1,2,3)) == Vector((3,2,1)) # = False -- (9)
(7),(8) の結果を見ると (9) が成り立つはずですが False になります。
一致判定は Vector 個々の値を比較しますが、
大小判定には Vecotr の長さが用いられているためです。
つまり (7),(8) は下記の (10),(11) と同じです。
Vector((1,2,3)).length >= Vector((3,2,1)).length # = True -- (10) Vector((1,2,3)).length > Vector((3,2,1)).length # = False -- (11)
(9) も .length をつければ True になります。
Vector((1,2,3)).length == Vector((3,2,1)).length # = True -- (12)
頂点の検索アルゴリズムを作る場合このあたり注意が必要です。
●頂点のコンテナ
頂点をマージして index を作るためにコンテナを作ります。
検索は極めて重要です。
数百万頂点など大きなモデルデータを export した場合に終わらなくなります。
class Vertex:
def __init__( self, pos, normal ):
self.pos= Vector( pos )
self.normal= Vector( normal )
def __hash__( self ):
return int( frexp( self.pos.dot( self.normal ) )[0] * 0x7fffffff )
def __eq__( self, ver ):
return self.pos == ver.pos and self.normal == ver.normal
hash はもっと改良できると思いますがとりあえず key として使えました。
比較に関しても最終的にはもっと考慮が必要で、例えば clamp 処理が入ります。
>>> a={ Vertex(Vector((1,2,3)),Vector((0,1,1))): 123, Vertex(Vector((3,4,5)),Vector((1,0,1))): 456 }
>>> a[ Vertex(Vector((1,2,3)),Vector((0,1,1))) ]
123
続きます
関連エントリ
・Blender 2.6 の Export plug-in を作る (2) データアクセス
・Blender 2.6 の Export plug-in を作る (1)