スキニング用 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)