snake5 icon

io_export_ss3dmesh.py

snake5 | PRO | 02/16/16 08:07:59 AM UTC | 0 ⭐ | 840 👁️ | Never ⏰ | []
Python |

32.96 KB

|

None

|

0 👍

/

0 👎

import bpy
from bpy.props import *
from mathutils import *
from pprint import pprint
from copy import copy
from itertools import islice
import math, struct, csv, sys
import os.path
 
 
""" FORMAT
    
    BUFFER: (min size = 4)
    - uint32 size
    - uint8 data[size]
    
    SMALLBUF: (min size = 1)
    - uint8 size
    - uint8 data[size]
    
    PART: (min size = 20)
    - uint8 flags
    - uint8 blendmode
    - uint32 voff
    - uint32 vcount
    - uint32 ioff
    - uint32 icount
    - uint8 texcount
    - smallbuf shader
    - smallbuf textures[texcount]
    
    MESH:
    - magic "SS3DMESH"
    - uint32 flags
    
    - float boundsmin[3]
    - float boundsmax[3]
    
    - buffer vdata
    - buffer idata
    - smallbuf format
    - uint8 numparts
    - part parts[numparts]
    
    minimum size = 12+24+10 = 46
"""
 
 
bl_info = {
    "name": "SS3DMESH Mesh Format (.ssm)",
    "author": "Arvīds Kokins",
    "version": (0, 5, 2),
    "blender": (2, 6, 9),
    "api": 38019,
    "location": "File > Export > SS3DMESH (.ssm)",
    "description": "SS3DMESH Mesh Export (.ssm)",
    "warning": "",
    "wiki_url": "http://cragegames.com",
    "tracker_url": "http://cragegames.com",
    "category": "Import-Export"
}
 
 
def write_smallbuf( f, bytebuf ):
    if len( bytebuf ) > 255:
        raise Exception( "smallbuf too big" )
    if type( bytebuf ) == str:
        bytebuf = bytes( bytebuf, "UTF-8" )
    f.write( struct.pack( "B", len( bytebuf ) ) )
    f.write( bytebuf )
#
 
def write_buffer( f, bytebuf ):
    if len( bytebuf ) > 0xffffffff:
        raise Exception( "buffer too big" )
    if type( bytebuf ) == str:
        bytebuf = bytes( bytebuf, "UTF-8" )
    f.write( struct.pack( "L", len( bytebuf ) ) )
    f.write( bytebuf )
#
 
def serialize_matrix( m ):
    return struct.pack( "16f",
        m[0][0], m[1][0], m[2][0], m[3][0],
        m[0][1], m[1][1], m[2][1], m[3][1],
        m[0][2], m[1][2], m[2][2], m[3][2],
        m[0][3], m[1][3], m[2][3], m[3][3] )
 
def write_part( f, part ):
    if len( part["textures"] ) > 8:
        raise Exception( "too many textures (max. 8 allowed)" )
    f.write( struct.pack( "=BB", part["flags"], part["blendmode"] ) )
    write_smallbuf( f, part["name"] )
    f.write( serialize_matrix( part[ "mtx" ] ) )
    f.write( struct.pack( "=LLLLB", part["voff"], part["vcount"],
        part["ioff"], part["icount"], len( part["textures"] ) ) )
    write_smallbuf( f, bytes( part["shader"], "UTF-8" ) )
    for tex in part["textures"]:
        write_smallbuf( f, tex.replace("\\", "/") )
#
 
def write_mesh( f, meshdata, armdata, boneorder ):
    is_skinned = armdata != None
    bbmin = meshdata["bbmin"]
    bbmax = meshdata["bbmax"]
    vertices = meshdata["vertices"]
    indices = meshdata["indices"]
    format = meshdata["format"]
    parts = meshdata["parts"]
    
    if len( parts ) > 255:
        raise Exception( "too many parts (max. 255 allowed)" )
    
    is_i32 = len( vertices ) > 65535
    
    print( "--- MESH STATS ---" )
    print( "Vertex count: %d" % ( len(vertices) ) )
    print( "Index count: %d" % ( len(indices) ) )
    print( "Format string: " + format )
    print( "Part count: %d" % ( len(parts) ) )
    for part_id, part in enumerate( parts ):
        print( "- part %d: name=%s voff=%d vcount=%d ioff=%d icount=%d flags=%d blendmode=%d texcount=%d shader='%s'" % (
            part_id, part["name"], part["voff"], part["vcount"], part["ioff"], part["icount"],
            part["flags"], part["blendmode"], len( part["textures"] ), part["shader"] ) )
    
    f.write( bytes( "SS3DMESH", "UTF-8" ) )
    # mesh data flags, 0x100 = extended mtl data, 0x200 = part name data
    f.write( struct.pack( "L", 0x100 + 0x200 +
        (1 if is_i32 else 0) * 0x01 + \
        (1 if is_skinned else 0) * 0x80 ) )
    f.write( struct.pack( "6f", bbmin.x, bbmin.y, bbmin.z, bbmax.x, bbmax.y, bbmax.z ) )
    
    vdata = bytes()
    for vertex in vertices:
        vdata += vertex
    write_buffer( f, vdata )
    
    idata = bytes()
    if( is_i32 ):
        for index in indices:
            idata += struct.pack( "L", index )
    else:
        for index in indices:
            idata += struct.pack( "H", index )
    write_buffer( f, idata )
    
    write_smallbuf( f, format )
    f.write( struct.pack( "B", len(parts) ) )
    for part in parts:
        write_part( f, part )
    
    if is_skinned:
        f.write( struct.pack( "B", len(boneorder) ) )
        for bonename in boneorder:
            bone = armdata.bones[ bonename ]
            print( "Bone found: " + bone.name )
            write_smallbuf( f, bone.name )
            pid = 255
            m = bone.matrix_local
            if bone.parent is not None:
                m = bone.parent.matrix_local.inverted() * m
                for bpid, pbone in enumerate(boneorder):
                    if bone.parent.name == pbone:
                        pid = bpid
                        break
            #
            # print(m)
            f.write( struct.pack( "B", pid ) )
            f.write( serialize_matrix( m ) )
        #
    #
    
    return
#
 
def write_anims( f, anims ):
    
    print( "--- ANIMATIONS' STATS ---" )
    print( "Count: %d" % len( anims ) )
    
    f.write( bytes( "SS3DANIM", "UTF-8" ) )
    f.write( struct.pack( "L", len( anims ) ) )
    
    i = 0
    for anim in anims:
        i += 1
        print( "Animation #%d: %s" % ( i, anim["name"] ) )
        
        a_name = anim["name"]
        a_frames = anim["frames"]
        a_tracks = anim["tracks"]
        a_markers = anim["markers"]
        a_speed = anim["speed"]
        
        a_name_bytes = bytes( a_name, "UTF-8" )
        animbuf = struct.pack( "B", len(a_name_bytes) ) + a_name_bytes
        animbuf += struct.pack( "=LfBB", a_frames, a_speed, len( a_tracks ), len( a_markers ) )
        
        for track_name, track_matrices in a_tracks.items():
            track_name_bytes = bytes( track_name, "UTF-8" )
            trackbuf = struct.pack( "B", len(track_name_bytes) ) + track_name_bytes
            
            for fid in range(a_frames):
                mtx = track_matrices[ fid ]
                
                # decompose
                pos = mtx.to_translation()
                rot = mtx.to_quaternion()
                scl = mtx.to_scale()
                
                # write
                trackbuf += struct.pack( "10f",
                    pos.x, pos.y, pos.z,
                    rot.x, rot.y, rot.z, rot.w,
                    scl.x, scl.y, scl.z
                )
            #
            
            animbuf += struct.pack( "L", len(trackbuf) ) + trackbuf
        #
        
        for marker in a_markers:
            animbuf += struct.pack( "=16sL", bytes( marker["name"], "UTF-8" ), marker["frame"] )
        
        write_buffer( f, animbuf )
    #
#
 
def serialize_vec3_array( arr ):
    out = bytes()
    for item in arr:
        out += struct.pack( "fff", item.x, item.y, item.z )
    return out
 
def serialize_quat_array( arr ):
    out = bytes()
    for item in arr:
        out += struct.pack( "ffff", item.x, item.y, item.z, item.w )
    return out
 
def wrap_small_string( strdata ):
    data = bytes( strdata, "UTF-8" )
    return struct.pack( "B", len(data) ) + data
 
def wrap_chunk( name, data ):
    return bytes( name, "UTF-8" ) + struct.pack( "L", len( data ) ) + data
 
def reduce_array( arr ):
    if len(arr) > 1:
        diff = False
        for item in arr:
            if item != arr[0]:
                diff = True
                break
        if diff == False:
            arr = arr[:1]
    return arr
 
def write_anims_anbd( f, anims ):
    
    print( "--- ANIMATIONS' STATS ---" )
    print( "Count: %d" % len( anims ) )
    
    anbd_chunk = bytes()
    
    i = 0
    for anim in anims:
        i += 1
        print( "Animation #%d: %s" % ( i, anim["name"] ) )
        
        a_name = anim["name"]
        a_frames = anim["frames"]
        a_tracks = anim["tracks"]
        a_markers = anim["markers"]
        a_speed = anim["speed"]
        
        anim_chunk = struct.pack( "fH", a_speed, a_frames )
        
        # parse animation
        float_data = bytes()
        track_data = bytes()
        track_count = 0
        
        for track_name, track_matrices in a_tracks.items():
            track_offset = len( float_data ) // 4
            pos_arr = []
            rot_arr = []
            scl_arr = []
            
            # decompose animation matrices into position/rotation/scale
            for fid in range(a_frames):
                mtx = track_matrices[ fid ]
                
                pos = mtx.to_translation()
                rot = mtx.to_quaternion()
                scl = mtx.to_scale()
                
                pos_arr.append( pos )
                rot_arr.append( rot )
                scl_arr.append( scl )
            #
            
            # see if each list is constant and reduce it if so
            pos_arr = reduce_array( pos_arr )
            rot_arr = reduce_array( rot_arr )
            scl_arr = reduce_array( scl_arr )
            
            track_data += wrap_small_string( track_name )
            track_data += struct.pack( "LHHHH", track_offset, len(pos_arr), len(rot_arr), len(scl_arr), 0 )
            track_count += 1
            
            float_data += serialize_vec3_array( pos_arr )
            float_data += serialize_quat_array( rot_arr )
            float_data += serialize_vec3_array( scl_arr )
        #
        
        # floats
        anim_chunk += struct.pack( "L", len( float_data ) // 4 ) + float_data
        
        # tracks
        anim_chunk += struct.pack( "L", track_count ) + track_data
        
        # markers
        anim_chunk += struct.pack( "L", len( a_markers ) )
        for marker in a_markers:
            anim_chunk += struct.pack( "16sH", bytes( marker["name"], "UTF-8" ), marker["frame"] )
        
        anbd_chunk += wrap_chunk( "ANIM", anim_chunk ) + wrap_small_string( a_name )
    #
    
    anbd_chunk = wrap_chunk( "SGRXANBD", anbd_chunk )
    f.write( anbd_chunk )
#
 
def addCached( olist, o, minpos = 0 ):
    try:
        return olist[ minpos: ].index( o )
    except:
        olist.append( o )
        return len( olist ) - 1 - minpos
#
 
def find_in_userdata( obj, key, default = None ):
    for prop in obj.items():
        if type( prop[ 1 ] ) in ( int, str, float, bool ):
            if prop[ 0 ] == key:
                return prop[ 1 ]
    return default
#
 
def parse_materials( geom_node, textures ):
    materials = []
    print( "Parsing materials... ", end="" )
    for mtl in geom_node.data.materials:
        outmtl = { "textures": [], "shader": "default", "flags": 0, "blendmode": 0 }
        for tex in  mtl.texture_slots:
            outmtl["textures"].append( textures[ tex.name ] if tex != None else "" )
        while len(outmtl["textures"]) and outmtl["textures"][-1] == "":
            outmtl["textures"].pop()
        shdr = find_in_userdata( mtl, "shader" )
        if type( shdr ) == str:
            outmtl["shader"] = shdr
        bmode = find_in_userdata( mtl, "blendmode" )
        if type( bmode ) == str:
            if bmode == "none":
                outmtl["blendmode"] = 0
            if bmode == "basic":
                outmtl["blendmode"] = 1
            if bmode == "additive":
                outmtl["blendmode"] = 2
            if bmode == "multiply":
                outmtl["blendmode"] = 3
        if find_in_userdata( mtl, "unlit", False ):
            outmtl["flags"] |= 1
        if find_in_userdata( mtl, "nocull", False ):
            outmtl["flags"] |= 2
        materials.append( outmtl )
    print( "OK!" )
    return materials
 
def parse_geometry( geom_node, textures, opt_boneorder, props ):
    
    if props.apply_modifiers == "NONE":
        MESH = geom_node.to_mesh( bpy.context.scene, False, "PREVIEW" )
    else:
        preview_settings = []
        if props.apply_modifiers == "SKIPARM":
            for mod in geom_node.modifiers:
                preview_settings.append( mod.show_viewport )
                if mod.type == "ARMATURE":
                    mod.show_viewport = False
        #
        MESH = geom_node.to_mesh( bpy.context.scene, True, "PREVIEW" )
        if props.apply_modifiers == "SKIPARM":
            for i, mod in enumerate( geom_node.modifiers ):
                mod.show_viewport = preview_settings[ i ]
        #
    #
        
    opt_vgroups = geom_node.vertex_groups if len(geom_node.vertex_groups) else None
    
    materials = parse_materials( geom_node, textures )
    
    print( "Generating geometry for %s... " % geom_node.name, end="" )
    MESH.calc_normals_split()
    
    # SORT BY MATERIAL
    MID2FACES = {} # material index -> faces
    FACE2MID = {} # face -> material index
    
    for face in MESH.polygons:
        
        if face.material_index not in MID2FACES:
            MID2FACES[ face.material_index ] = []
        MID2FACES[ face.material_index ].append( face )
    
    # GENERATE COMPACT DATA
    Plist = []
    Nlist = []
    Tlists = [ [] for tl in MESH.uv_layers ]
    Clists = [ [] for cl in MESH.vertex_colors ]
    genParts = [] # array of Part ( array of Face ( array of Vertex ( position index, normal index, texcoord indices, color indices ) ) )
    foundMIDs = []
    
    if len( Tlists ) > 2:
        print( "Too many UV layers" )
        Tlists = Tlists[:1]
    if len( Clists ) > 1:
        print( "Too many color layers" )
        Clists = Clists[:1]
    
    for flist_id in MID2FACES:
        genPart = []
        flist = MID2FACES[ flist_id ]
        m_id = 0
        for face in flist:
            m_id = face.material_index
            if flist_id != m_id:
                continue
            
            genFace = []
            
            for vid in range( len( face.vertices ) ):
                v_id = face.vertices[ vid ]
                l_id = face.loop_start + vid
                VTX = MESH.vertices[ v_id ]
                
                pos_id = addCached( Plist, VTX.co )
                if face.use_smooth != False:
                    nrm_id = addCached( Nlist, MESH.loops[ l_id ].normal )
                else:
                    nrm_id = addCached( Nlist, face.normal )
                
                genVertex = [ pos_id, nrm_id ]
                
                for si in range( len( Tlists ) ):
                    txc_id = addCached( Tlists[ si ], MESH.uv_layers[ si ].data[ l_id ].uv )
                    genVertex.append( txc_id )
                for si in range( len( Clists ) ):
                    col_id = addCached( Clists[ si ], MESH.vertex_colors[ si ].data[ l_id ].color )
                    genVertex.append( col_id )
                
                if opt_vgroups != None and opt_boneorder != None:
                    groupweights = []
                    for vg in VTX.groups:
                        for grp in opt_vgroups:
                            if vg.group == grp.index:
                                groupweights.append([ opt_boneorder.index( grp.name ), vg.weight ])
                                break
                            #
                        #
                    #
                    # sort by importance
                    groupweights.sort( key=lambda x: x[1], reverse = True )
                    # trim useless
                    for gwoff, gw in enumerate(groupweights):
                        if gw[1] < 1.0/256.0:
                            del groupweights[ gwoff: ]
                            break
                    # check if more than 4, warn / trim if so
                    if len(groupweights) > 4:
                        print( "Too many weights (%d > 4) for vertex %d at %s" % ( len(groupweights), v_id, VTX.co ) )
                        del groupweights[ 4: ]
                    if len(groupweights) == 0:
                        groupweights.append([ 0, 1.0 ])
                    # renormalize
                    wsum = 0.0
                    for gw in groupweights:
                        wsum += gw[1]
                    wsum /= 255.0
                    for gw in groupweights:
                        gw[1] = round( gw[1] / wsum )
                    # finish quantization to bytes
                    wsum = 0.0
                    for gw in groupweights:
                        wsum += gw[1]
                    gwoff = 0
                    while wsum != 255:
                        sgnadd = -1 if wsum > 255 else 1
                        groupweights[ gwoff ][1] += sgnadd
                        wsum += sgnadd
                    # compress
                    while len(groupweights) < 4:
                        groupweights.append([ 0, 0 ])
                    gw_groups = struct.pack( "4B", groupweights[0][0], groupweights[1][0], groupweights[2][0], groupweights[3][0] )
                    gw_weights = struct.pack( "4B", groupweights[0][1], groupweights[1][1], groupweights[2][1], groupweights[3][1] )
                    
                    genVertex.append( gw_groups )
                    genVertex.append( gw_weights )
                #
                
                genFace.append( genVertex )
            genPart.append( genFace )
        genParts.append( genPart )
        foundMIDs.append( m_id )
    #
    
    # VALIDATION
    if len( Plist ) <= 0:
        raise Exception( "Mesh has no vertices!" )
    #
    
    # CONVERT TO VERTEX BUFFER FORMAT
    vertices = []
    indices = []
    parts = []
    defmtl = { "textures": [], "shader": "default", "flags": 0, "blendmode": 0 }
    
    mtl_num = -1
    for part in genParts:
        mtl_num += 1
        mtl_id = foundMIDs[ mtl_num ]
        vroot = len(vertices)
        outpart = {
            "name": geom_node.name + "#" + str(mtl_num),
            "mtx": geom_node.matrix_world,
            "voff": len(vertices),
            "vcount": 0,
            "ioff": len(indices),
            "icount": 0,
            "flags": materials[ mtl_id ]["flags"] if mtl_id in materials else defmtl["flags"],
            "blendmode": materials[ mtl_id ]["blendmode"] if mtl_id in materials else defmtl["blendmode"],
            "shader": materials[ mtl_id ]["shader"] if mtl_id in materials else defmtl["shader"],
            "textures": materials[ mtl_id ]["textures"] if mtl_id in materials else defmtl["textures"],
        }
        
        for face in part:
            tmpidcs = []
            for vertex in face:
                P = Plist[ vertex[0] ]
                N = Nlist[ vertex[1] ]
                vertexdata = struct.pack( "3f3f", P.x, P.y, P.z, N.x, N.y, N.z )
                vip = 2
                for si in range( len( Tlists ) ):
                    T = Tlists[ si ][ vertex[ vip ] ]
                    vip += 1
                    vertexdata += struct.pack( "2f", T.x, 1 - T.y )
                for si in range( len( Clists ) ):
                    C = Clists[ si ][ vertex[ vip ] ]
                    vip += 1
                    vertexdata += struct.pack( "4B", int(C.r * 255), int(C.g * 255), int(C.b * 255), 255 )
                if opt_vgroups != None and opt_boneorder != None:
                    vertexdata += vertex[ vip ] # indices (groups)
                    vip += 1
                    vertexdata += vertex[ vip ] # weights
                    vip += 1
                tmpidcs.append( addCached( vertices, vertexdata, vroot ) )
            #
            for i in range( 2, len( tmpidcs ) ):
                indices.append( tmpidcs[ 0 ] )
                indices.append( tmpidcs[ i ] )
                indices.append( tmpidcs[ i - 1 ] )
            #
        #
        
        outpart["vcount"] = len(vertices) - outpart["voff"]
        outpart["icount"] = len(indices) - outpart["ioff"]
        parts.append( outpart )
    #
    
    # TANGENT SPACE CALC
    if len( Tlists ) > 0:
        tan1list = [ Vector([0,0,0]) for i in range(len(vertices)) ]
        tan2list = [ Vector([0,0,0]) for i in range(len(vertices)) ]
        hitlist = [ 0 for i in range(len(vertices)) ]
        
        for part in parts:
            voff = part["voff"]
            ioff = part["ioff"]
            for i in range( 0, part["icount"], 3 ):
                i1 = indices[ ioff + i + 0 ] + voff
                i2 = indices[ ioff + i + 1 ] + voff
                i3 = indices[ ioff + i + 2 ] + voff
                
                Pdc1 = struct.unpack( "3f", vertices[ i1 ][ :12 ] )
                Pdc2 = struct.unpack( "3f", vertices[ i2 ][ :12 ] )
                Pdc3 = struct.unpack( "3f", vertices[ i3 ][ :12 ] )
                
                v1 = Vector([ Pdc1[0], Pdc1[1], Pdc1[2] ])
                v2 = Vector([ Pdc2[0], Pdc2[1], Pdc2[2] ])
                v3 = Vector([ Pdc3[0], Pdc3[1], Pdc3[2] ])
                
                Tdc1 = struct.unpack( "2f", vertices[ i1 ][ 24:32 ] )
                Tdc2 = struct.unpack( "2f", vertices[ i2 ][ 24:32 ] )
                Tdc3 = struct.unpack( "2f", vertices[ i3 ][ 24:32 ] )
                
                w1 = Vector([ Tdc1[0], Tdc1[1], 0 ])
                w2 = Vector([ Tdc2[0], Tdc2[1], 0 ])
                w3 = Vector([ Tdc3[0], Tdc3[1], 0 ])
                
                x1 = v2.x - v1.x;
                x2 = v3.x - v1.x;
                y1 = v2.y - v1.y;
                y2 = v3.y - v1.y;
                z1 = v2.z - v1.z;
                z2 = v3.z - v1.z;
                
                s1 = w2.x - w1.x;
                s2 = w3.x - w1.x;
                t1 = w2.y - w1.y;
                t2 = w3.y - w1.y;
                
                ir = s1 * t2 - s2 * t1
                if True: # abs( ir ) > 0.000001:
                    r = abs( ir ) # 1.0 / ir
                    sdir = Vector([(t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r])
                    tdir = Vector([(s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, (s1 * z2 - s2 * z1) * r])
                    
                    tan1list[ i1 ] += sdir
                    tan1list[ i2 ] += sdir
                    tan1list[ i3 ] += sdir
                    
                    tan2list[ i1 ] += tdir
                    tan2list[ i2 ] += tdir
                    tan2list[ i3 ] += tdir
                    
                    hitlist[ i1 ] += 1
                    hitlist[ i2 ] += 1
                    hitlist[ i3 ] += 1
                else:
                    print( "Bad triangle UV map!!! invR: %f | UV0: %f;%f | UV1: %f;%f | UV2: %f;%f" % \
                        (ir, Tdc1[0],Tdc1[1],Tdc2[0],Tdc2[1],Tdc3[0],Tdc3[1]))
                #
            #
        #
        
        for v_id, vertex in enumerate( vertices ):
            Ndc = struct.unpack( "3f", vertex[ 12:24 ] )
            n = Vector([ Ndc[0], Ndc[1], Ndc[2] ])
            t = tan1list[ v_id ]
            t2 = tan2list[ v_id ]
            
            outtan = ( t - n * n.dot( t ) ).normalized()
            if outtan == Vector([0.0,0.0,0.0]):
                outtan = Vector([0.0,0.0,1.0])
                print( "Tangent was detected to be 0,0,0 on vertex %d - changed to 0,0,1" % v_id )
                Tdc = struct.unpack( "2f", vertex[ 24:32 ] )
                print( "HC: %d, Nrm: %s, Tx1: %s, Tg2: %s" % ( hitlist[ v_id ], n, Tdc, t2 ) )
            sign = -1.0 if n.cross( t ).dot( t2 ) < 0.0 else 1.0
            vertices[ v_id ] = vertex[ :24 ] + struct.pack( "4f", outtan.x, outtan.y, outtan.z, sign ) + vertex[ 24: ]
        #
    #
    
    # AABB
    bbmin = Plist[0].copy()
    bbmax = Plist[0].copy()
    for pos in Plist:
        if bbmin.x > pos.x:
            bbmin.x = pos.x
        if bbmin.y > pos.y:
            bbmin.y = pos.y
        if bbmin.z > pos.z:
            bbmin.z = pos.z
        if bbmax.x < pos.x:
            bbmax.x = pos.x
        if bbmax.y < pos.y:
            bbmax.y = pos.y
        if bbmax.z < pos.z:
            bbmax.z = pos.z
    #
    
    # FORMAT STRING
    format = "pf3nf3"
    if len( Tlists ) > 0:
        format += "tf4"
    for si in range( len( Tlists ) ):
        format += "%df2" % ( si )
    for si in range( len( Clists ) ): # only one expected
        format += "cb4"
    if opt_vgroups != None and opt_boneorder != None:
        format += "ib4wb4"
    #
    
    print( "OK!" )
    
    if MESH is not geom_node.data:
        bpy.data.meshes.remove( MESH )
    
    return {
        "bbmin": bbmin, "bbmax": bbmax,
        "vertices": vertices, "indices": indices,
        "format": format, "parts": parts,
    }
#
 
def gen_empty_mesh_data():
    return {
        "bbmin": Vector([ sys.float_info.max, sys.float_info.max, sys.float_info.max ]),
        "bbmax": Vector([ -sys.float_info.max, -sys.float_info.max, -sys.float_info.max ]),
        "vertices": [], "indices": [], "format": "pf3nf3", "parts": [],
    }
#
 
def mesh_data_add( meshdata, ndata ):
    # AABB
    if meshdata["bbmin"].x > ndata["bbmin"].x:
        meshdata["bbmin"].x = ndata["bbmin"].x
    if meshdata["bbmin"].y > ndata["bbmin"].y:
        meshdata["bbmin"].y = ndata["bbmin"].y
    if meshdata["bbmin"].z > ndata["bbmin"].z:
        meshdata["bbmin"].z = ndata["bbmin"].z
    if meshdata["bbmax"].x < ndata["bbmax"].x:
        meshdata["bbmax"].x = ndata["bbmax"].x
    if meshdata["bbmax"].y < ndata["bbmax"].y:
        meshdata["bbmax"].y = ndata["bbmax"].y
    if meshdata["bbmax"].z < ndata["bbmax"].z:
        meshdata["bbmax"].z = ndata["bbmax"].z
    
    # parts
    for part in ndata["parts"]:
        part = copy(part)
        part["voff"] += len(meshdata["vertices"])
        part["ioff"] += len(meshdata["indices"])
        meshdata["parts"].append( part )
    
    # format diff
    format_part_order = [ "pf3", "nf3", "tf4", "0f2", "1f2",
        "3f2", "4f2", "cb4", "ib4", "wb4", "DUMMY",
    ]
    # - split into chunks
    oldfmt_A = meshdata["format"]
    oldfmt_B = ndata["format"]
    fmt_A = [oldfmt_A[i:i+3] for i in range(0, len(oldfmt_A), 3)] + [ "DUMMY" ]
    fmt_B = [oldfmt_B[i:i+3] for i in range(0, len(oldfmt_B), 3)] + [ "DUMMY" ]
    
    # - iterate through
    i = 0
    while i < len(fmt_A) or i < len(fmt_B):
        if fmt_A[ i ] == fmt_B[ i ]:
            i += 1
            continue
        # - formats not equal, need to pad one side
        # - pad the side whose index is higher
        if format_part_order.index( fmt_A[ i ] ) > format_part_order.index( fmt_B[ i ] ):
            fmt_tgt = fmt_A
            fmt_src = fmt_B
            data_tgt = meshdata["vertices"]
        else:
            fmt_src = fmt_A
            fmt_tgt = fmt_B
            data_tgt = ndata["vertices"]
        # - pad the format
        curfmt = fmt_src[ i ]
        fmt_tgt.insert( i, curfmt )
        # - calculate padding offset
        pad_offset = 0
        for f in islice( fmt_src, i ):
            nominal = 4 if f[1] == "f" else 1
            multiplier = int(f[2])
            pad_offset += nominal * multiplier
        # - calculate padding data
        if curfmt == "pf3" or curfmt == "nf3":
            raise Exception( "UNEXPECTED PADDING FORMAT" )
        pad_core = struct.pack( "f", 0.0 ) if curfmt[1] == "f" else struct.pack( "B", 0 )
        pad_mult = int(curfmt[2])
        pad_data = pad_core * pad_mult
        # print( "pad with %d bytes of data by format %s" % ( len(pad_data), curfmt ) )
        # - perform padding on each vertex
        for v in range( len( data_tgt ) ):
            data_tgt[ v ] = data_tgt[ v ][ : pad_offset ] + pad_data + data_tgt[ v ][ pad_offset : ]
        # - move on because formats have been made equal here
        i += 1
    #
    
    # - set new format
    meshdata["format"] = "".join( fmt_A[:-1] )
    
    # validate vertex size
    if len(meshdata["vertices"]) > 0 and len(ndata["vertices"]) > 0:
        if len(meshdata["vertices"][0]) != len(ndata["vertices"][0]):
            raise Exception(
                "Vertex sizes not equal: old=%d new=%d fmtA=%s fmtB=%s newfmtA=%s newfmtB=%s" % (
                    len(meshdata["vertices"][0]),
                    len(ndata["vertices"][0]),
                    oldfmt_A,
                    oldfmt_B,
                    "".join( fmt_A[:-1] ),
                    "".join( fmt_B[:-1] ),
                )
            )
    #
    
    # combine vertex/index data
    meshdata["vertices"] += ndata["vertices"]
    meshdata["indices"] += ndata["indices"]
#
 
def parse_armature( node ):
    for mod in node.modifiers:
        if mod.type == "ARMATURE":
            return mod.object
    return None
#
 
def generate_bone_order( armdata ):
    if armdata == None:
        return None
    bonelist = []
    bonequeue = []
    for bone in armdata.bones:
        if bone.parent == None:
            bonequeue.append( bone )
    #
    while len(bonequeue) != 0:
        bone = bonequeue.pop(0)
        bonelist.append( bone.name )
        for bone in bone.children:
            bonequeue.append( bone )
    #
    return bonelist
#
 
def parse_animations( armobj, boneorder, filepath ):
    animations = []
    if armobj is not None and armobj.animation_data is not None:
        print( "Generating animations... " )
        oldact = armobj.animation_data.action
        for action in bpy.data.actions:
            if action.use_fake_user is False:
                continue # do not export animations that are not pinned (likely to be deleted)
            armobj.animation_data.action = action
            anim_tracks = {}
            for bonename in boneorder:
                anim_tracks[ bonename ] = []
            frame_begin, frame_end = [ int(x) for x in action.frame_range ]
            for frame in range( frame_begin, frame_end + 1 ):
                bpy.context.scene.frame_set( frame )
                for bonename in boneorder:
                    bone = armobj.pose.bones[ bonename ]
                    track = anim_tracks[ bonename ]
                    
                    # input: object space pose/anim matrices (CP, CA, PP, PA)
                    # output: pose space anim matrix (out)
                    # out = inv( inv(PP) * CP ) * ( inv(PA) * CA )
                    mtx_anim = bone.matrix
                    mtx_base = bone.bone.matrix_local
                    if bone.parent is not None:
                        mtx_anim = bone.parent.matrix.inverted() * mtx_anim
                        mtx_base = bone.parent.bone.matrix_local.inverted() * mtx_base
                    mtx = mtx_base.inverted() * mtx_anim
                    
                    track.append( mtx )
                #
            #
            anim_markers = []
            for pmrk in action.pose_markers:
                anim_markers.append({ "name": pmrk.name, "frame": pmrk.frame })
            #
            animspeed = bpy.context.scene.render.fps / bpy.context.scene.render.fps_base
            animlistname = os.path.dirname( filepath ) + "/" + action.name + ".animlist.csv"
            print( "Looking for animation descriptor - " + animlistname )
            if os.path.isfile( animlistname ):
                print( "Found it, decoding action..." )
                try:
                    animlist = csv.reader( open( animlistname, "r" ), delimiter = ",", quotechar = '"' )
                    for anim in animlist:
                        tstart = int(anim[0], 10)
                        tend = int(anim[1], 10)
                        tname = anim[2].strip()
                        anim_sliced_tracks = {}
                        for track_name, track_matrices in anim_tracks.items():
                            anim_sliced_tracks[ track_name ] = track_matrices[ tstart : tend ]
                        animations.append({ "name": tname, "frames": tend - tstart, "tracks": anim_sliced_tracks, "speed": animspeed })
                    #
                except IOError as e:
                    print( "I/O error({0}): {1}".format(e.errno, e.strerror) )
                except ValueError:
                    print( "Could not convert data to an integer." )
                #
            else:
                print( "Did not find it, will append the whole action." )
                animations.append({ "name": action.name, "frames": frame_end - frame_begin + 1, "tracks": anim_tracks, "markers" : anim_markers, "speed": animspeed })
            #
        #
        armobj.animation_data.action = oldact
        print( "\tOK!" )
    #
    return animations
#
 
def parse_textures():
    textures = {}
    print( "Parsing textures... ", end="" )
    for tex in bpy.data.textures:
        texpath = ""
        if hasattr( tex, "image" ) and tex.image != None:
            texpath = tex.image.filepath[ 2: ]
        textures[ tex.name ] = texpath
    print( "OK!" )
    return textures
 
def write_ss3dmesh( ctx, props ):
    filepath = props.filepath
    print( "\n\\\\\n>>> SS3DMESH Exporter v0.5!\n//\n\n" )
    print( "Exporting..." )
    
    textures = parse_textures()
    
    meshdata = gen_empty_mesh_data()
    armobj = None
    armdata = None
    boneorder = []
    for node in ctx.scene.objects:
        if node.type != "MESH":
            continue
        
        if props.export_selected and not node.select:
            continue
        
        cur_armobj = parse_armature( node )
        # do not allow multiple armatures
        if armobj is not None and cur_armobj is not None and armobj.name != cur_armobj.name:
            props.report( {"ERROR"},
                "multiple armatures are not supported (curr=%s, new=%s)" % (
                    armobj.name, cur_armobj.name ) )
            return {'CANCELLED'}
        # do this only for the first time
        if armobj is None and cur_armobj is not None:
            armobj = cur_armobj
            armdata = None if armobj is None else armobj.data
            boneorder = generate_bone_order( armdata )
        cur_meshdata = parse_geometry( node, textures, boneorder, props )
        mesh_data_add( meshdata, cur_meshdata )
    
    if props.export_anim:
        print( "Parsing animations..." )
        animations = parse_animations( armobj, boneorder, filepath )
        print( "OK!" )
    
    print( "Writing mesh... " )
    with open( filepath, 'wb' ) as f:
        write_mesh( f, meshdata, armdata, boneorder )
    
    if props.export_anim:
        if len(animations) == 0:
            props.report( {"WARNING"}, "No animations found!" )
        else:
            print( "Writing animations... " )
            with open( filepath + ".anm", 'wb' ) as f:
                write_anims( f, animations )
            with open( filepath + ".anb", 'wb' ) as f:
                write_anims_anbd( f, animations )
    #
    
    print( "\n\\\\\n>>> Done!\n//\n\n" )
 
    return {'CANCELLED'}
#
 
def write_sgrxanbd( ctx, props ):
    print( "\n\\\\\n>>> SGRXANBD Exporter v0.5!\n//\n\n" )
    print( "Exporting..." )
    
    filepath = props.filepath
    
    print( "Parsing nodes... ", end="" )
    armobj = None
    geom_node = bpy.context.active_object
    if bpy.context.active_object.type == "ARMATURE":
        armobj = bpy.context.active_object
    for node in ctx.scene.objects:
        if node.type == "ARMATURE":
            armobj = node
            break
    #
    if armobj is None:
        props.report( {"ERROR"}, "No armature found!" )
        return {'CANCELLED'}
    else:
        armdata = armobj.data
    print( "OK!" )
    
    boneorder = generate_bone_order( armdata )
    
    print( "Parsing animations..." )
    animations = parse_animations( armobj, boneorder, filepath )
    print( "OK!" )
    
    if len(animations) == 0:
        props.report( {"WARNING"}, "No animations found!" )
    else:
        print( "Writing animations... " )
        with open( filepath, 'wb' ) as f:
            write_anims_anbd( f, animations )
    #
    
    print( "\n\\\\\n>>> Done!\n//\n\n" )
 
    return {'CANCELLED'}
 
# ExportHelper is a helper class, defines filename and
# invoke() function which calls the file selector.
from bpy_extras.io_utils import ExportHelper
from bpy.props import StringProperty, BoolProperty, EnumProperty
 
 
apply_mod_ui_items = [
    ( "NONE", "None", "Don't apply any modifiers" ),
    ( "SKIPARM", "All except armatures", "Apply non-armature modifiers" ),
    ( "ALL", "All", "Apply all modifiers" ),
]
 
 
class ExportSS3DMESH( bpy.types.Operator, ExportHelper ):
    '''SS3DMESH Exporter'''
    bl_idname = "export.ss3dmesh"
    bl_label = "[SGRX] Export .ssm"
    bl_options = {'REGISTER', 'UNDO'}
    
    # ExportHelper mixin class uses this
    filename_ext = ".ssm"
    
    filter_glob = StringProperty(
        default = "*.ssm",
        options = {'HIDDEN'},
    )
    export_anim = BoolProperty(name="Export animation", default=False)
    export_selected = BoolProperty(name="Export selected mesh only", default=True)
    apply_modifiers = EnumProperty(items=apply_mod_ui_items,
        name="Apply modifiers", default="SKIPARM")
    
    def execute( self, ctx ):
        return write_ss3dmesh( ctx, self )
 
class ExportSGRXANBD( bpy.types.Operator, ExportHelper ):
    '''SGRXANBD (anim. bundle) Exporter'''
    bl_idname = "export.sgrxanbd"
    bl_label = "[SGRX] Export .anb"
    bl_options = {'REGISTER', 'UNDO'}
    
    filename_ext = ".anb"
    
    filter_glob = StringProperty(
        default = "*.anb",
        options = {'HIDDEN'},
    )
    
    def execute( self, ctx ):
        return write_sgrxanbd( ctx, self )
 
 
# Only needed if you want to add into a dynamic menu
def menu_func_export( self, ctx ):
    self.layout.operator( ExportSS3DMESH.bl_idname, text="SS3DMESH Exporter" )
    self.layout.operator( ExportSGRXANBD.bl_idname, text="SGRXANBD (anim. bundle) Exporter" )
 
 
 
def makeMaterial(name, uvlayer):
    tex = bpy.data.textures.new(name, type = 'IMAGE')
    
    mat = bpy.data.materials.new(name)
    mat.diffuse_color = (1,1,1)
    mat.diffuse_shader = 'LAMBERT'
    mat.diffuse_intensity = 1.0 
    mat.specular_color = (1,1,1)
    mat.specular_shader = 'COOKTORR'
    mat.specular_intensity = 0.2
    mat.alpha = 1
    mat.ambient = 1
    
    mtex = mat.texture_slots.add()
    mtex.texture = tex
    mtex.texture_coords = 'UV'
    mtex.use_map_color_diffuse = True
    mtex.mapping = 'FLAT'
    mtex.uv_layer = uvlayer
    
    return mat
 
class DialogOperator(bpy.types.Operator):
    bl_idname = "object.generate_material"
    bl_label = "Generate Material"
    
    prop_mtlname = StringProperty(name="Material name", default="newmtl")
    
    def execute(self, context):
        active = bpy.context.active_object.data
        if hasattr(active, "materials"):
            mtlname = self.prop_mtlname
            active.materials.append( makeMaterial( mtlname, active.uv_layers.keys()[0] ) )
            self.report({'INFO'}, "Material created: %s!" % mtlname)
        else:
            self.report({'INFO'}, "Object cannot have materials!")
        #
        return {'FINISHED'}
    
    def invoke(self, context, event):
        return context.window_manager.invoke_props_dialog(self)
 
 
 
class DialogPanel(bpy.types.Panel):
    bl_label = "Texturing Tools"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    
    def draw(self, context):
        self.layout.operator("object.generate_material")
 
 
def register():
    bpy.utils.register_class( ExportSS3DMESH )
    bpy.utils.register_class( ExportSGRXANBD )
    bpy.utils.register_class( DialogOperator )
    bpy.utils.register_class( DialogPanel )
    bpy.types.INFO_MT_file_export.append( menu_func_export )
    # bpy.types.VIEW3D_PT_tools_object.append( generate_material_func )
 
 
def unregister():
    bpy.utils.unregister_class( ExportSS3DMESH )
    bpy.utils.unregister_class( ExportSGRXANBD )
    bpy.utils.unregister_class( DialogOperator )
    bpy.utils.unregister_class( DialogPanel )
    bpy.types.INFO_MT_file_export.remove( menu_func_export )
    # bpy.types.VIEW3D_PT_tools_object.remove( generate_material_func )
 
 
if __name__ == "__main__":
    register()
#

Comments