Blender 2.6 の Export plug-in を作る (4) マテリアルと頂点出力

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