スキニング用のジオメトリ情報を調べます。
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)