スキニング用のジオメトリ情報を調べます。
頂点自体に、影響を受けるジオメトリの番号 (vertex group)と重みのセットが
必要個数含まれています。
vertex group の番号が実際に指しているジオメトリの情報は
Object に入っています。
Object ローカルに影響を受けるジオメトリセットがあり、
その index 番号 (group 番号) が頂点に格納されています。
ゲームなどリアルタイム描画時の構造と同じなので、非常にわかりやすく
扱いやすい形式になっているようです。
実際に影響を与えるジオメトリは Armature と呼ばれているようです。
Modifier の扱いとなっており Subdivision Surface 等と同じです。
Object から Modifier を列挙できるため、その中から 'ARMATURE' を
取り出すことができます。
通常の Node と同じだろうと思っていたのですが構造が少々違うようです。
Bone 周りは Blender の機能やデータ構造を知らないといけないので
正直よく理解していません。
なので後回しにします。
●ファイル出力
実際に addon としてデータを出力してみます。
exporter 本体を A6Export() として別に作成し呼び出す形に。
a6 は独自フォーマットの名称で CPU とかは無関係です。
まずは階層構造の解析と matrix の出力のみ。
読みだしたあとの Node を格納するコンテナが Shape です。
●コマンドとして呼び出す
addon として上記 io_export_test_a6.py を読み込むと
File → Export のメニューに組み込まれますが、これをコマンドとして
呼び出すこともできます。
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 も代入も同一です。
上の (1), (2), (3) は全く同じもので値の代入です。
特に Shader Assembler では swizzle と mask の違いを明確にするため
あえて (1) の表記を用いることがあります。
Vector は python のオブジェクトなので、代入は参照であり
swizzle は copy になります。
(4) は dest に src の参照が入ります。
(5) は新 object を生成して copy します。
src.xyz の代わりに src.copy() や src[:] と記述した場合も同様です。
(6) は値代入なので dest と src は別物です。新たな object が作られません。
場合によっては (6) が欲しいのですが immutable なら (4) で十分で
むしろ効率が良くなります。
Vector 同士の比較には注意が必要です。
(7),(8) の結果を見ると (9) が成り立つはずですが False になります。
一致判定は Vector 個々の値を比較しますが、
大小判定には Vecotr の長さが用いられているためです。
つまり (7),(8) は下記の (10),(11) と同じです。
(9) も .length をつければ True になります。
頂点の検索アルゴリズムを作る場合このあたり注意が必要です。
●頂点のコンテナ
頂点をマージして index を作るためにコンテナを作ります。
検索は極めて重要です。
数百万頂点など大きなモデルデータを export した場合に終わらなくなります。
hash はもっと改良できると思いますがとりあえず key として使えました。
比較に関しても最終的にはもっと考慮が必要で、例えば clamp 処理が入ります。
続きます
関連エントリ
・Blender 2.6 の Export plug-in を作る (2) データアクセス
・Blender 2.6 の Export plug-in を作る (1)
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)