Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- 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()
- #
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement