スキニング用 weight/group は頂点(MeshVertex)への追加情報でした。
同じように MeshPolygon (Face) にはマテリアル情報が含まれています。
Table Table の IDが含まれる ------------------------------------------------------------ group Object.vertex_groups MeshVertex.groups[n].group material Mesh.materials MeshPolygon.material_index
Maya では Object Material と Face Material の両方を参照する必要が
ありますが、Blender は Face Material だけで情報が取れます。
その代わり Blender には Material 無しのデータが存在できるようです。
この場合でも MeshPolygon.material_index には有効な値 (0) が
入ってしまうのでこれだけでは判定できません。
事前に Mesh.materials のデータ数を調べておき、Material が無い場合は
適当なデフォルトデータを設定しておく必要があります。
def decode_material( obj ): mesh= obj.data if not mesh.materials: primt( 'Empty' ) else: for material in mesh.materials: print( material.name ) print( material.ambient ) print( material.diffuse_color ) print( material.specular_color ) decode_material( bpy.data.objects['Cube'] )
Texture は Material.texture_slots からたどることができます。
画像かつ外部ファイルと限定すると下記のように取れます。
def decode_material( obj ): mesh= obj.data if not mesh.materials: primt( 'Empty' ) else: for material in mesh.materials: print( material.name ) print( material.ambient ) print( material.diffuse_color ) print( material.specular_color ) for slotid, slot in enumerate( material.texture_slots ): if slot and material.use_textures[ slotid ] and slot.use: if slot.texture.type == 'IMAGE': image= slot.texture.image if image.source == 'FILE': print( 'Texture=', image.filepath ) decode_material( bpy.data.objects['Cube'] )
実際の描画時は Material 単位に行うので、
ポリゴンの描画データは Material 単位に分類しておく必要があります。
最終的には Shader によって必要となるデータが変わるので、
事前に Material やデータ構造を調べて Shader の機能を分類し、
必要となる情報を export する流れとなります。
実際に頂点データを出力してみます。
Primitive は material で分割した Mesh です。
この中に頂点と index を格納していきます。
index 化のために辞書を使っているので、出力時は index 順に
並べ替える必要があります。
Primitive.getVertex() で配列を作り直しています。
MeshPolygon は 4角形以上の可能性があるので decode_shape() で
3角形に変換しています。
まだ Fan なのであまり良い変換ではありません。
法線以外の頂点要素や Material はまだ含まれていません。
# io_export_flatlib_test_a6.py import bpy import math from bpy_extras.io_utils import ExportHelper from bpy.props import BoolProperty from mathutils import Matrix, Vector, Color bl_info= { "name": "Export Test flatlib a6", "author": "Hiroyuki Ogasawara", "version": (1, 0, 0), "blender": (2, 6, 5), "location": "File > Export > flatlib (.a6)", "category": "Import-Export", } class Vertex: def __init__( self, pos ): self.pos= Vector( pos ) self.vbid= 0 def setNormal( self, normal ): self.normal= normal def setVBID( self, id ): self.vbid= id def getVBID( self ): return self.vbid def __hash__( self ): return int( math.frexp( self.pos.dot( self.normal ) )[0] * 0x7fffffff ) def __eq__( self, ver ): return self.pos == ver.pos and self.normal == ver.normal class Primitive: def __init__( self, material_id ): self._vertexBuffer= {} self._indexBuffer= [] self._material_id= material_id def addVertex( self, vertex ): if vertex in self._vertexBuffer: return vertex.getVBID() vertex.setVBID( len(self._vertexBuffer) ) self._vertexBuffer[ vertex ]= vertex return vertex.getVBID() def addIndex( self, index ): self._indexBuffer.append( index ) def getVertexBuffer( self ): mapbuf= [ None ] * len( self._vertexBuffer ) for vertex in self._vertexBuffer: mapbuf[ vertex.getVBID() ]= vertex return mapbuf def getIndexBuffer( self ): return self._indexBuffer class Node: def __init__( self, obj ): self._obj= obj def getName( self ): return self._obj.name def getParent( self ): return self._obj.parent.name if self._obj.parent else None def getMatrix( self ): return self._obj.matrix_local def isMesh( self ): return False class Shape(Node): def __init__( self, obj ): self._obj= obj self._primitive= {} def getMesh( self ): return self._obj.data def createPrimitive( self, material_id ): self._primitive[material_id]= Primitive( material_id ) def getPrimitive( self, material_id ): return self._primitive[material_id] def getPrimitiveList( self ): return self._primitive.values() def isMesh( self ): return True class A6Export: def __init__( self, options ): self.options= options def decode_tree( self, tree, obj ): tree.append( Shape( obj ) if obj.type == 'MESH' else Node( 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_material( self, tree ): for node in tree: mesh= node.getMesh() materiallist= {} if not mesh.materials: node.createPrimitive( 0 ) else: for poly in mesh.polygons: materiallist[ poly.material_index ]= poly.material_index for material_id in materiallist.values(): node.createPrimitive( material_id ) def decode_shape( self, tree ): ver_index= [ None, None, None ] for node in tree: mesh= node.getMesh() for poly in mesh.polygons: prim= node.getPrimitive( poly.material_index ) triangle= 0 for id in range( poly.loop_start, poly.loop_start + poly.loop_total ): vertex= mesh.vertices[ mesh.loops[id].vertex_index ] ver= Vertex( vertex.co ) ver.setNormal( vertex.normal if poly.use_smooth else poly.normal ) ver_index[triangle]= ver triangle+= 1 if triangle >= 3: prim.addIndex( prim.addVertex( ver_index[0] ) ) prim.addIndex( prim.addVertex( ver_index[1] ) ) prim.addIndex( prim.addVertex( ver_index[2] ) ) triangle= 2 ver_index[1]= ver_index[2] def output_geometry( self, fo, tree ): fo.write( "#\nT \"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 output_vertex( self, fo, tree ): fo.write( "#\nT \"Vertex\"\n" ) for nodeid, node in enumerate( tree ): for primid, prim in enumerate( node.getPrimitiveList() ): fo.write( "N \"_vb%03d_%03d\"\n" % (nodeid, primid) ) buffer= prim.getVertexBuffer() stride= 24 fo.write( "I \"Size\" %d %d\n" % (len(buffer), stride) ) fo.write( "M \"v\"\n" ) for vertex in buffer: fo.write( "F %f %f %f\n" % (vertex.pos.x, vertex.pos.y, vertex.pos.z) ) fo.write( "F %f %f %f\n" % (vertex.normal.x, vertex.normal.y, vertex.normal.z) ) def output_index( self, fo, tree ): fo.write( "#\nT \"Index\"\n" ) for nodeid, node in enumerate( tree ): for primid, prim in enumerate( node.getPrimitiveList() ): fo.write( "N \"_ib%03d_%03d\"\n" % (nodeid, primid) ) fo.write( "I \"Size\" %d\n" % len(prim.getIndexBuffer()) ) fo.write( "I \"i\"\n" ) indexlist= prim.getIndexBuffer() for tid in range( 0, len(indexlist), 3 ): fo.write( "I %d %d %d\n" % (indexlist[tid], indexlist[tid+1], indexlist[tid+2] ) ) def output_shape( self, fo, tree ): fo.write( "#\nT \"Command\"\n" ) for nodeid, node in enumerate( tree ): for primid, prim in enumerate( node.getPrimitiveList() ): fo.write( "N \"_pr%03d_%03d\"\n" % (nodeid, primid) ) fo.write( "S \"Geometry\" \"%s\"\n" % node.getName() ) fo.write( "S \"Vertex\" \"_vb%03d_%03d\"\n" % (nodeid, primid) ) fo.write( "S \"Index\" \"_ib%03d_%03d\"\n" % (nodeid, primid) ) fo.write( "S \"Material\" \"unknown\"\n" ) def output_material( self, fo, tree ): pass def export( self, filename ): print( self.options ) tree= self.decode_node() meshlist= [ node for node in tree if node.isMesh()] self.decode_material( meshlist ) self.decode_shape( meshlist ) # fo= open( filename, 'w' ) self.output_material( fo, meshlist ) self.output_geometry( fo, tree ) self.output_vertex( fo, meshlist ) self.output_index( fo, meshlist ) self.output_shape( fo, meshlist ) fo.close() class ExportTestA6( bpy.types.Operator, ExportHelper ): bl_label= 'export flatlib a6' bl_idname= 'export.flatlib_a6' filename_ext= '.a6' flag_selected= BoolProperty( name= "Selected Objects", description= "Export selected objects", default= False ) flag_lefthand= BoolProperty( name= "Left-Hand (DirectX)", description= "Lfef-Hand (DirectX)", default= True ) def execute( self, context ): A6Export( self ).export( self.filepath ) return {'FINISHED'} def menu_func(self, context): self.layout.operator( ExportTestA6.bl_idname, text="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)
今頃気が付きましたが 3ds Max のように Z-up なので、出力時に
Y-up に変換しておいた方がよさそうです。
関連エントリ
・Blender 2.6 の Export plug-in を作る (3) 出力と mathutils
・Blender 2.6 の Export plug-in を作る (2) データアクセス
・Blender 2.6 の Export plug-in を作る (1)