SupComImp-Exp-0.3.0.0
supcom-importer-0.3.0.0.py
#!BPY
#**************************************************************************************************
# Supreme Commander Importer for Blender3D - www.blender3d.org
# Written by dan - www.sup-com.net
#
# History
# 0.1.0.0 06/06/06 - Initial version.
# 0.2.0.0 06/06/10 - Added SCA (Animation) support
# 0.3.0.0 06/07/02 - Alpha release
#
# Todo
# - Material/uv map 2
# - Bone pos/rot for scm & sca. Not perfect to use gentle words.
# - Make sca loading independent of the scm_mesh. (e.g get bone pos/loc from armature instead)
# - GUI for loading
#
#**************************************************************************************************
"""
Name: 'SupCom Model (.scm)...'
Blender: 232
Group: 'Import'
Tooltip: 'Import Supreme Commander Models (*.scm) and Animations (*.sca)'
"""
import Blender
from Blender import NMesh, Scene, Object
import struct
import string
import math
from math import *
import os
from os import path
def pad(size):
val = 32 - (size % 32)
if (val > 31):
return 0
return val
class scm_bone :
name = ""
rest_pose = []
position = []
rotation = []
parent = 0
parent_index = 0
absolute_matrix = []
def __init__(self):
self.name = ""
self.rest_pose = [[0.0] * 4] * 4
self.position = [0.0] * 3
self.rotation = [0.0] * 4
self.absolute_matrix = []
def load(self, file):
bonestruct = '16f3f4f4i'
buffer = file.read(struct.calcsize(bonestruct))
bone = struct.unpack(bonestruct, buffer)
for i in range(4):
self.rest_pose[i] = bone[i*4:i*4+4]
self.rest_pose = Blender.Mathutils.Matrix(self.rest_pose[0], self.rest_pose[1], self.rest_pose[2], self.rest_pose[3])
self.position = bone[16:19]
self.rotation = bone[19:23]
self.parent_index = bone[24]
# Read bone name
#oldpos = file.tell()
#file.seek(bone[..], 0)
#self.name = file.readline()
#file.seek(oldpos, 0)
self.absolute_matrix = Blender.Mathutils.Matrix(self.rest_pose).invert()
return self
def dump(self):
print 'Bone ', self.name
print 'Position ', self.position
print 'Rotation ', self.rotation
print 'Parent Idx ', self.parent_index
if (self.parent != 0):
print 'Parent ', self.parent.name
else:
print 'Parent <NONE>'
print 'Rest Pose Inv.'
for row in range(4):
print ' ', self.rest_pose[row]
class scm_vertex :
position = []
tangent = []
normal = []
binormal = []
uv1 = []
uv2 = []
bone_index = []
def __init__(self):
self.position = [0.0]*3
self.tangent = [0.0]*3
self.normal = [0.0]*3
self.binormal = [0.0]*3
self.uv1 = [0.0]*2
self.uv2 = [0.0]*2
self.bone_index = [0]*4
def load(self, file):
vertstruct = '3f3f3f3f2f2f4B'
vertsize = struct.calcsize(vertstruct)
buffer = file.read(vertsize)
vertex = struct.unpack(vertstruct, buffer)
self.position = vertex[0:3]
self.tangent = vertex[3:6]
self.normal = vertex[6:9]
self.binormal = vertex[9:12]
self.uv1 = vertex[12:14]
self.uv2 = vertex[14:16]
self.bone_index = vertex[16:20]
return self
def dump(self):
print 'position ', self.position
print 'tangent ', self.tangent
print 'normal ', self.normal
print 'binormal ', self.binormal
print 'uv1 ', self.uv1
print 'uv2 ', self.uv2
print 'bones ', self.bone_index
class scm_mesh :
bones = []
vertices = []
faces = []
info = []
filename = ""
def __init__(self):
self.bones = []
self.vertices = []
self.faces = []
self.info = []
self.filename = ""
def load(self, filename):
self.filename = filename
scm = file(filename, 'rb')
# Read header
headerstruct = '4s11L'
buffer = scm.read(struct.calcsize(headerstruct))
header = struct.unpack(headerstruct, buffer)
marker = header[0]
version = header[1]
boneoffset = header[2]
bonecount = header[3]
vertoffset = header[4]
extravertoffset = header[5]
vertcount = header[6]
indexoffset = header[7]
indexcount = header[8]
tricount = indexcount / 3
infooffset = header[9]
infocount = header[10]
totalbonecount = header[11]
if (marker != 'MODL'):
print 'Not a valid scm'
return
if (version != 5):
print 'Unsupported version (%d)' % version
return
# Read bone names
scm.seek(pad(scm.tell()), 1)
length = (boneoffset - 4) - scm.tell()
# This should probably be handeled by the scm_bone reader as it contains the nameoffset. But I'm lazy
# and logic tells me it's written in the same order as the bones.
buffer = scm.read(length)
rawnames = struct.unpack(str(length)+'s',buffer)
bonenames = string.split(rawnames[0],'\0')[:-1]
# Read bones
scm.seek(boneoffset, 0)
for b in range(0, totalbonecount):
bone = scm_bone()
bone.load(scm)
bone.name = bonenames[b]
self.bones.append(bone)
# Set parent (this could probably be done in the other loop since parents are usually written to the file
# before the children. But you never know..
for bone in self.bones:
if (bone.parent_index != -1):
bone.parent = self.bones[bone.parent_index]
else:
bone.parent = 0
# Read vertices
scm.seek(vertoffset, 0)
for b in range(0, vertcount):
vert = scm_vertex()
vert.load(scm)
self.vertices.append(vert)
# Read extra vertex data
# Not implemented in Sup Com 1.0!
# Read indices (triangles)
tristruct = '3h'
trisize = struct.calcsize(tristruct)
scm.seek(indexoffset, 0)
for t in range(tricount):
buffer = scm.read(trisize)
face = struct.unpack(tristruct, buffer)
self.faces.append(face)
# Read info
if (infocount > 0):
scm.seek(infooffset)
buffer = scm.read()
rawinfo = struct.unpack(str(len(buffer))+'s',buffer)
self.info = string.split(rawinfo[0],'\0')[:-1]
scm.close()
return self
def dump(self):
print ''
print 'Filename: ', self.filename
print 'Bones ', len(self.bones)
print 'Verts ', len(self.vertices)
print 'Faces ', len(self.faces)
print ''
print 'INFO: '
for info in self.info:
print ' ', info
class sca_bone:
position = []
rototation = []
absolute_matrix = None
def __init__(self, pos, rot):
self.position = pos
self.rotation = rot
self.absolute_matrix = None
def dump(self):
print 'Position ', self.position
print 'Rotation ', self.rotation
class sca_frame:
keytime = 0.0
keyflags = 0
bones = []
anim = None
def __init__(self, anim):
self.keytime = 0.0
self.keyflags = 0
self.bones = []
self.anim = anim
def load(self, file):
frameheader_fmt = 'fl'
frameheader_size = struct.calcsize(frameheader_fmt)
buffer = file.read(frameheader_size)
(self.keytime, self.keyflags) = struct.unpack(frameheader_fmt, buffer)
posrot_fmt = '3f4f'
posrot_size = struct.calcsize(posrot_fmt)
for b in range (0, self.anim.numbones) :
buffer = file.read(posrot_size)
posrot = struct.unpack(posrot_fmt, buffer)
bone = sca_bone(posrot[0:3], posrot[3:7])
self.bones.append(bone)
def dump(self):
print 'Time ', self.keytime
print 'Flags ', self.keyflags
class sca_anim :
filename = ""
frames = []
bones = []
bonelinks = []
bonenames = []
model = None
numbones = 0
duration = 0.0
def __init__(self, model):
self.filename = ""
self.frames = []
self.bones = []
self.numbones = 0
self.bonelinks = []
self.bonenames = []
self.model = model
self.duration = 0.0
def calcAnimBoneMatrix(self, frame, bone_index):
bone = frame.bones[bone_index];
parent_index = self.bonelinks[bone_index]
relRotationMatrix = Blender.Mathutils.Quaternion(bone.rotation[1],bone.rotation[2],bone.rotation[3],bone.rotation[0]).toMatrix().resize4x4()
relPositionMatrix = Blender.Mathutils.Matrix([1.0, 0.0, 0.0, bone.position[0]],[0.0, 1.0, 0.0, bone.position[1]],[0.0, 0.0, 1.0, bone.position[2]],[0.0, 0.0, 0.0, 1.0])
bone.absolute_matrix = relPositionMatrix * relRotationMatrix
if (parent_index >= 0):
parent = frame.bones[parent_index]
bone.absolute_matrix = parent.absolute_matrix * bone.absolute_matrix
# Call all children
for b in range(len(self.bonelinks)):
if (self.bonelinks[b] == bone_index):
self.calcAnimBoneMatrix(frame, b)
bone.absolute_matrix = bone.absolute_matrix * Blender.Mathutils.Matrix(self.model.bones[b].rest_pose).invert()
def load(self, filename):
self.filename = filename
sca = file(filename, 'rb')
# Read header
headerstruct = '4sllflllll'
buffer = sca.read(struct.calcsize(headerstruct))
header = struct.unpack(headerstruct, buffer)
(magic, \
version, \
numframes, \
self.duration, \
self.numbones, \
namesoffset, \
linksoffset, \
animoffset, \
framesize) = struct.unpack(headerstruct, buffer)
if (magic != 'ANIM'):
print 'Not a valid .sca animation file'
return
if (version != 5):
print 'Unsupported sca version: %d' % version
return
# Read bone names
sca.seek(namesoffset, 0)
length = linksoffset - namesoffset
buffer = sca.read(length)
rawnames = struct.unpack(str(length)+'s',buffer)
self.bonenames = string.split(rawnames[0], '\0')[:-1]
# Read links
links_fmt = str(self.numbones)+'l'
links_size = struct.calcsize(links_fmt)
buffer = sca.read(links_size)
self.bonelinks = struct.unpack(links_fmt, buffer)
posrot_fmt = '3f4f'
posrot_size = struct.calcsize(posrot_fmt)
sca.seek(animoffset)
buffer = sca.read(posrot_size)
root_posrot = struct.unpack(posrot_fmt, buffer)
for f in range (0, numframes) :
frame = sca_frame(self)
frame.load(sca)
self.frames.append(frame)
sca.close()
return self
def dump(self):
print 'SCA: ', self.filename
print 'Duration: %fs' % self.duration
print 'Num loaded frames ', len(self.frames)
print 'Bonelinks'
for link in self.bonelinks:
print ' ', link
print 'Bone names'
for name in self.bonenames:
print ' ', name
#**************************************************************************************************
# Blender Interface
#**************************************************************************************************
def read(filename) :
print "=== LOADING Sup Com Model ==="
print ""
mesh = scm_mesh()
if (mesh.load(filename) == None):
print 'Failed to load %s' %filename
return
modelpath, modelfile = os.path.split(filename)
armature_name = string.rstrip(modelfile, ".scm")
armObj = Object.New('Armature', armature_name)
armData = Blender.Armature.Armature("ARM_" + armature_name)
armData.drawAxes = True
armObj.link(armData)
scene = Blender.Scene.getCurrent()
scene.link(armObj)
armData.makeEditable()
for index in range(len(mesh.bones)):
bone = mesh.bones[index]
#lmat = bone.absolute_matrix
#lmat = Blender.Mathutils.Matrix(lmat[0],lmat[1],lmat[2],lmat[3])
blender_bone = Blender.Armature.Editbone()
#blender_bone.matrix = lmat
#blender_bone.weight = 1.0
armData.bones[bone.name] = blender_bone
for index in range(len(mesh.bones)):
bone = mesh.bones[index]
blender_bone = armData.bones[bone.name]
if (bone.parent != 0) :
blender_bone.parent = armData.bones[bone.parent.name]
#blender_bone.head = blender_bone.parent.tail
#blender_bone.tail = blender_bone.parent.head
bone = mesh.bones[index]
lmat = bone.absolute_matrix
lmat = Blender.Mathutils.Matrix(lmat[0],lmat[1],lmat[2],lmat[3])
blender_bone.matrix = lmat
armData.update()
armObj.makeDisplayList()
blender_mesh = NMesh.New()
for vert in mesh.vertices:
v = NMesh.Vert(vert.position[0], vert.position[1], vert.position[2])
v.uvco[0]= vert.uv1[0]
v.uvco[1]= vert.uv1[1]
blender_mesh.verts.append(v)
for tri in mesh.faces:
face = NMesh.Face()
face.v.append(blender_mesh.verts[tri[0]])
face.v.append(blender_mesh.verts[tri[1]])
face.v.append(blender_mesh.verts[tri[2]])
face.uv.append((blender_mesh.verts[tri[0]].uvco[0], blender_mesh.verts[tri[0]].uvco[1]))
face.uv.append((blender_mesh.verts[tri[1]].uvco[0], blender_mesh.verts[tri[1]].uvco[1]))
face.uv.append((blender_mesh.verts[tri[2]].uvco[0], blender_mesh.verts[tri[2]].uvco[1]))
blender_mesh.faces.append(face)
mesh_obj = NMesh.PutRaw(blender_mesh)
blender_mesh = mesh_obj.getData()
for bone in mesh.bones:
blender_mesh.addVertGroup(bone.name)
for vertex_index in range(len(mesh.vertices)):
vertex = mesh.vertices[vertex_index]
bone_index = vertex.bone_index[0]
blender_mesh.assignVertsToGroup(mesh.bones[bone_index].name, [vertex_index], 1.0, 'replace')
armObj.makeParentDeform([mesh_obj], 0, 0)
armObj.makeDisplayList()
scene.update();
Blender.Window.RedrawAll()
if len(mesh.info):
print "=== INFO ==="
for info in mesh.info:
print "",info
print "=== COMPLETE ==="
Blender.Window.DrawProgressBar(1.0, '')
return mesh
def read_anim(filename, mesh):
print "=== LOADING Sup Com Animation ==="
print ""
anim = sca_anim(mesh)
anim.load(filename)
arm_obj = None
for armobj in Blender.Object.Get():
data = armobj.getData()
if type(data) is Blender.Types.ArmatureType:
arm_obj = armobj
break
if arm_obj == None:
print "couldn't apply animation, no armature in the scene"
return
actionpath, actionname = os.path.split(filename)
action = Blender.Armature.NLA.NewAction(actionname)
action.setActive(arm_obj)
pose = arm_obj.getPose()
for frame_index in range(len(anim.frames)):
Blender.Set("curframe", frame_index+1)
frame = anim.frames[frame_index]
anim.calcAnimBoneMatrix(frame, 0)
for b in range(len(frame.bones)):
pose_bone = pose.bones[anim.bonenames[b]]
if (pose_bone == 0):
print 'Frame %d - Bone \"%s\" not found' % (frame_index, anim.bonenames[b])
continue
anim_bone = frame.bones[b]
model_bone = mesh.bones[b]
mb_quat = Blender.Mathutils.Quaternion([model_bone.rotation[1], model_bone.rotation[2], model_bone.rotation[3], model_bone.rotation[0]])
ab_quat = Blender.Mathutils.Quaternion([anim_bone.rotation[1], anim_bone.rotation[2], anim_bone.rotation[3], anim_bone.rotation[0]])
pose_bone.loc = Blender.Mathutils.Vector(anim_bone.position) - Blender.Mathutils.Vector(model_bone.position)
pose_bone.quat = Blender.Mathutils.DifferenceQuats(ab_quat, mb_quat)
pose.update()
pose_bone.insertKey(arm_obj, frame_index+1, [Blender.Object.Pose.ROT, Blender.Object.Pose.LOC])
pose.update()
Blender.Set("curframe", 1)
scene = Blender.Scene.GetCurrent()
context = scene.getRenderingContext()
context.endFrame(len(anim.frames)+1)
print "=== COMPLETE ==="
#**************************************************************************************************
# Execution stuff..
#**************************************************************************************************
anim_filename = ""
model_filename = ""
# File selector callback from blender
def fs_callback(filename):
global model_filename
model_filename = filename
#read(filename)
Blender.Window.FileSelector(fs_anim_callback, "Import SCA", "*.sca")
def fs_anim_callback(filename):
global anim_filename
global model_filename
anim_filename = filename
#read_anim(filename)
mesh = read(model_filename)
read_anim(anim_filename, mesh)
#Show the file selector
#Blender.Window.FileSelector(fs_callback, "Import SCM", "*.scm")
#Blender.Window.FileSelector(fs_anim_callback, "Import SCA", "*.sca")
#mesh = read("Z:\\dev\\SupComAnimTest\\Sample\\UXA0099_lod0.scm")
#read_anim("Z:\\dev\\SupComAnimTest\\Sample\\UXA0099_Atest.sca", mesh)
#mesh = read("Z:\\dev\\SupComAnimTest\\Sample\\SupComSCM_lod0.scm")
#read_anim("Z:\\dev\\SupComAnimTest\\Sample\\UXA0100_Atest.sca", mesh)
Blender.Window.FileSelector(fs_callback, "Import SCM", "*.scm")
supcom-exporter-0.3.0.0.py
#!BPY
#**************************************************************************************************
# Supreme Commander Exporter for Blender3D - www.blender3d.org
# Written by dan - www.sup-com.net), Brent (www.scmods.net)
#
# History
#
# 0.1.0.0 2006-07-02 Dan Initial version.
#
# 0.2.0.0 2007-03-11 Brent Fixed UV coords, V was inverted.
# Support for exporting quads.
# Fixed a padding issue.
#
# 0.3.0.0 2007-03-18 Dan Code refactoring / Clean up.
# Fixed 'INFO' section size in header.
#
# Todo
# - GUI improvements
# - Support for LOD exporting. Eg. not merging all meshes for an armature into one mech but rather only
# sub-meshes and export the top level meshes to different files.
# - Validation, ensure that
# - Only one root bone
# - Has UV coords
# - Prompt before overwriting files & check that directories exists
# - Tangent/Binormal calculation
# - Second UV set?
# - Set animation time per frame
# - Export LUA script for use in the animation viewer (eg, start anim, set texture etc)..
# - Set root rot/pos for sca
#
#**************************************************************************************************
"""
Name: 'Supreme Commander'
Blender: 242
Group: 'Export'
Tooltip: 'Model / Animation Exporter for Supreme Commander'
"""
VERSION = '0.3.0.0'
import Blender
import struct
from Blender.BGL import *
from Blender.Draw import *
def pad(size):
val = 32 - (size % 32)
if (val < 4):
val = val + 32
return val
def pad_file(file):
padding = struct.pack(str(pad(file.tell()))+'x')
file.write(padding)
class scm_bone :
name = ""
rest_pose = []
position = []
rotation = []
parent_index = 0
used = False
def __init__(self):
self.name = ""
self.rest_pose = [[0.0] * 4] * 4
self.position = [0.0] * 3
self.rotation = [0.0] * 4
self.used = False
def save(self, file):
bonestruct = '16f3f4f4i'
tmp = [0] * 27
tmp[0] = self.rest_pose[0][0]
tmp[1] = self.rest_pose[0][1]
tmp[2] = self.rest_pose[0][2]
tmp[3] = self.rest_pose[0][3]
tmp[4] = self.rest_pose[1][0]
tmp[5] = self.rest_pose[1][1]
tmp[6] = self.rest_pose[1][2]
tmp[7] = self.rest_pose[1][3]
#oops
tmp[9] = self.rest_pose[2][0]
tmp[10] = self.rest_pose[2][1]
tmp[11] = self.rest_pose[2][2]
tmp[12] = self.rest_pose[2][3]
tmp[13] = self.rest_pose[3][0]
tmp[14] = self.rest_pose[3][1]
tmp[15] = self.rest_pose[3][2]
tmp[16] = self.rest_pose[3][3]
tmp[17] = self.position[0]
tmp[18] = self.position[1]
tmp[19] = self.position[2]
tmp[20] = self.rotation[0]
tmp[21] = self.rotation[1]
tmp[22] = self.rotation[2]
#ewups
tmp[8] = self.rotation[3]
tmp[23] = self.name_offset
tmp[24] = self.parent_index
tmp[25] = 0
tmp[26] = 0
bone = struct.pack(bonestruct, \
tmp[0], tmp[1], tmp[2], tmp[3], tmp[4], \
tmp[5], tmp[6], tmp[7], tmp[9], \
tmp[10],tmp[11],tmp[12],tmp[13],tmp[14], \
tmp[15],tmp[16],tmp[17],tmp[18],tmp[19], \
tmp[20],tmp[21],tmp[22],tmp[8], tmp[23], tmp[24], \
tmp[25],tmp[26])
file.write(bone)
class scm_vertex :
position = []
tangent = []
normal = []
binormal = []
uv1 = []
uv2 = []
bone_index = []
def __init__(self):
self.position = [0.0]*3
self.tangent = [0.0]*3
self.normal = [0.0]*3
self.binormal = [0.0]*3
self.uv1 = [0.0]*2
self.uv2 = [0.0]*2
self.bone_index = [0]*4
def save(self, file):
vertstruct = '3f3f3f3f2f2f4B'
vertex = struct.pack(vertstruct, \
self.position[0], self.position[1], self.position[2], \
self.tangent[0], self.tangent[1], self.tangent[2], \
self.normal[0], self.normal[1], self.normal[2], \
self.binormal[0], self.binormal[1], self.binormal[2], \
self.uv1[0], self.uv1[1], \
self.uv2[0], self.uv2[1], \
self.bone_index[0], self.bone_index[1], \
self.bone_index[2], self.bone_index[3])
file.write(vertex)
class scm_mesh :
bones = []
vertices = []
faces = []
info = []
def __init__(self):
self.bones = []
self.vertices = []
self.faces = []
self.info = []
def save(self, filename):
scm = file(filename, 'wb')
headerstruct = '4s11L'
headersize = struct.calcsize(headerstruct)
marker = 'MODL'
version = 5
boneoffset = 0
bonecount = 0
vertoffset = 0
extravertoffset = 0
vertcount = len(self.vertices)
indexoffset = 0
indexcount = len(self.faces) * 3
infooffset = 0
infosize = 0
totalbonecount = len(self.bones)
# Write dummy header
header = struct.pack(headerstruct + '', \
marker, version, boneoffset, bonecount, vertoffset, \
extravertoffset, vertcount, indexoffset, indexcount, \
infooffset, infosize, totalbonecount)
scm.write(header)
pad_file(scm)
# Write bone names
scm.seek(-4, 1)
scm.write('NAME')
for bone in self.bones:
bone.name_offset = scm.tell()
name = bone.name
buffer = struct.pack(str(len(name) + 1)+'s', name)
scm.write(buffer)
pad_file(scm)
# Write bones
scm.seek(-4, 1)
scm.write('SKEL')
boneoffset = scm.tell()
for bone in self.bones:
bone.save(scm)
# if bone.used == True:
bonecount += 1
pad_file(scm)
# Write vertices
scm.seek(-4, 1)
scm.write('VTXL')
vertoffset = scm.tell()
for vertex in self.vertices:
vertex.save(scm)
pad_file(scm)
# Write Faces
scm.seek(-4, 1)
scm.write('TRIS')
indexoffset = scm.tell()
for f in range(len(self.faces)):
face = struct.pack('3h', self.faces[f][0], self.faces[f][1], self.faces[f][2])
scm.write(face)
pad_file(scm)
if len(self.info) > 0:
scm.seek(-4, 1)
scm.write('INFO')
infooffset = scm.tell()
for i in range(len(self.info)):
info = self.info[i]
infolen = len(info) + 1
buffer = struct.pack(str(infolen)+'s', info)
scm.write(buffer)
infosize = scm.tell() - infooffset;
pad_file(scm)
# Now we can update the header
header = struct.pack(headerstruct, \
marker, version, boneoffset, bonecount, vertoffset, \
extravertoffset, vertcount, indexoffset, indexcount, \
infooffset, infosize, totalbonecount)
scm.seek(0, 0)
scm.write(header)
scm.close()
class sca_bone:
position = []
rototation = []
def __init__(self, pos, rot):
self.position = pos
self.rotation = rot
class sca_frame:
keytime = 0.0
keyflags = 0
bones = []
anim = None
def __init__(self, anim):
self.keytime = 0.0
self.keyflags = 0
self.bones = []
self.anim = anim
def save(self, file):
frameheader_fmt = 'fl'
frameheader_size = struct.calcsize(frameheader_fmt)
posrot_fmt = '3f4f'
posrot_size = struct.calcsize(posrot_fmt)
# Frame header
buffer = struct.pack(frameheader_fmt, self.keytime, self.keyflags)
file.write(buffer)
# Bones
for bone in self.bones:
buffer = struct.pack(posrot_fmt, bone.position[0], bone.position[1], bone.position[2], bone.rotation[0], bone.rotation[1], bone.rotation[2], bone.rotation[3])
file.write(buffer)
class sca_anim :
frames = []
bonelinks = []
bonenames = []
duration = 0.0
def __init__(self):
self.frames = []
self.bonelinks = []
self.bonenames = []
self.duration = 0.0
def save(self, filename):
self.filename = filename
sca = file(filename, 'wb')
headerstruct = '4sllflllll'
# Write temp header
magic = 'ANIM'
version = 5
numframes = len(self.frames)
numbones = len(self.bonenames)
namesoffset = 0
linksoffset = 0
animoffset = 0
framesize = 0
header = struct.pack(headerstruct,
magic, version, numframes, self.duration, numbones,
namesoffset, linksoffset, animoffset, framesize)
sca.write(header)
pad_file(sca)
# Write bone names
sca.seek(-4, 1)
sca.write('NAME')
namesoffset = sca.tell()
for bone_name in self.bonenames:
buffer = struct.pack(str(len(bone_name) + 1)+'s', bone_name)
sca.write(buffer)
pad_file(sca)
# Write bone links
sca.seek(-4, 1)
sca.write('LINK')
linksoffset = sca.tell()
for link in self.bonelinks:
buffer = struct.pack('l', link)
sca.write(buffer)
pad_file(sca)
# Write data
sca.seek(-4, 1)
sca.write('DATA')
animoffset = sca.tell()
posrot_fmt = '3f4f'
posrot_size = struct.calcsize(posrot_fmt)
#TODO Root position / rotation
buffer = struct.pack(posrot_fmt, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
sca.write(buffer)
for frame in self.frames:
framesize = sca.tell()
frame.save(sca)
framesize = sca.tell() - framesize
# Update header
sca.seek(0, 0)
header = struct.pack(headerstruct,
magic, version, numframes, self.duration, numbones,
namesoffset, linksoffset, animoffset, framesize)
sca.write(header)
sca.close()
######################################################
# Blender Interface
######################################################
# Helper methods
def apply_transform(verts, matrix):
x, y, z = verts
xloc, yloc, zloc = matrix[3][0], matrix[3][1], matrix[3][2]
return Blender.Mathutils.Vector(
x*matrix[0][0] + y*matrix[1][0] + z*matrix[2][0] + xloc,
x*matrix[0][1] + y*matrix[1][1] + z*matrix[2][1] + yloc,
x*matrix[0][2] + y*matrix[1][2] + z*matrix[2][2] + zloc)
log = []
log_max_lines = 10
def Log(message):
global log, log_max_lines
log.append(message)
print message
if len(log) > log_max_lines:
log.delete(0)
# Armature world matrix
world_matrix = []
BONES = []
# Helper method for itterating through the bone tree
def itterate_bones(mesh, bone, parent = None, scm_parent_index = -1):
global world_matrix
global BONES
if (parent != None and bone.parent.name != parent.name):
return
# Create supcom bone
sc_bone = scm_bone()
sc_bone.name = bone.name
sc_bone.bone_index = len(mesh.bones)
sc_bone.parent_index = scm_parent_index
bone_matrix = Blender.Mathutils.Matrix(bone.matrix['ARMATURESPACE'])
# Calculate the inverse rest pose for the bone
sc_bone.rest_pose = Blender.Mathutils.Matrix(bone_matrix * world_matrix).invert()
if (parent == None):
rel_mat = bone_matrix * world_matrix
else:
rel_mat = bone_matrix * Blender.Mathutils.Matrix(parent.matrix['ARMATURESPACE']).invert()
rotation = rel_mat.toQuat() #.normalize()
# Rotation & Position relative to parent
sc_bone.rotation = [ rotation.w,
rotation.x,
rotation.y,
rotation.z ]
sc_bone.position = [ rel_mat[3][0],
rel_mat[3][1],
rel_mat[3][2] ]
BONES.append(sc_bone)
mesh.bones.append(sc_bone)
# recursive call for all children
if (bone.children != None):
for child in bone.children:
itterate_bones(mesh, child, bone, sc_bone.bone_index)
def make_scm(arm_obj):
global world_matrix
arm = arm_obj.getData()
world_matrix = Blender.Mathutils.Matrix(arm_obj.getMatrix('worldspace'))
# Get all mesh objects for the selected armature
mesh_objs = []
for obj in Blender.Scene.GetCurrent().getChildren():
if obj.parent == arm_obj and obj.getType() == 'Mesh':
blender_mesh = obj.getData(False, True)
mesh_objs.append(obj)
# Create SCM Mesh
supcom_mesh = scm_mesh()
# Traverse the bone tree
for bone in arm.bones.values():
if (bone.parent == None):
itterate_bones(supcom_mesh, bone)
# Keep a counter for the number of vertices added by the previous
# meshes so that we can recalculate the vert indices for faces.
vert_counter = 0
# Process all the meshes
for mesh_obj in mesh_objs:
blender_mesh = mesh_obj.getData(False, True)
mesh_world_matrix = Blender.Mathutils.Matrix(mesh_obj.getMatrix('worldspace'));
for face in blender_mesh.faces:
vert_count = len(supcom_mesh.vertices)
for i in range(len(face.verts)):
vert = face.verts[i]
sc_vertex = scm_vertex()
sc_vertex.position = apply_transform(vert.co, mesh_world_matrix)
sc_vertex.normal = apply_transform(vert.no, mesh_world_matrix)
#TODO, Calculate these.
sc_vertex.tangent = [0.0, 0.0, 0.0]
sc_vertex.binormal = [0.0, 0.0, 0.0]
sc_vertex.uv1 = [face.uv[i][0], 1.0 - face.uv[i][1]]
sc_vertex.uv2 = [face.uv[i][0], 1.0 - face.uv[i][1]]
inf = blender_mesh.getVertexInfluences(vert.index)
if (len(inf)) > 0:
bonename = inf[0][0]
for b in range(len(supcom_mesh.bones)):
bone = supcom_mesh.bones[b]
if bone.name == bonename:
bone.used = True
sc_vertex.bone_index[0] = b
break
supcom_mesh.vertices.append(sc_vertex)
# Triangle
if len(face.verts) == 3:
supcom_mesh.faces.append([vert_count + 0, vert_count + 1, vert_count + 2])
#Quad, split it to two triangles
elif len(face.verts) == 4:
supcom_mesh.faces.append([vert_count + 0, vert_count + 1, vert_count + 3])
supcom_mesh.faces.append([vert_count + 1, vert_count + 2, vert_count + 3])
return supcom_mesh
def make_sca(arm_obj, action):
global BONES
global world_matrix
action.setActive(arm_obj)
scene = Blender.Scene.GetCurrent()
render_context = scene.getRenderingContext()
endframe = render_context.endFrame()
animation = sca_anim()
animation.duration = 1.5
# Add bone names & links
for bone in BONES:
animation.bonenames.append(bone.name)
animation.bonelinks.append(bone.parent_index)
# Add frames
frame_counter = 1
while frame_counter <= endframe:
frame = sca_frame(animation)
render_context.currentFrame(frame_counter)
Blender.Window.RedrawAll()
arm_obj.evaluatePose(frame_counter)
frame_counter += 1
POSED_BONES = {}
for posebone in arm_obj.getPose().bones.values():
POSED_BONES[posebone.name] = posebone.poseMatrix
for bone in BONES:
pose_bone_matrix = POSED_BONES[bone.name]
if (bone.parent_index == -1):
rel_mat = pose_bone_matrix * world_matrix
else:
rel_mat = pose_bone_matrix * Blender.Mathutils.Matrix(POSED_BONES[BONES[bone.parent_index].name]).invert()
rotation = rel_mat.toQuat().normalize()
rot = [ rotation.w, rotation.x, rotation.y, rotation.z ]
pos = [ rel_mat[3][0], rel_mat[3][1], rel_mat[3][2] ]
anim_bone = sca_bone(pos, rot)
frame.bones.append(anim_bone)
animation.frames.append(frame)
return animation
def export(outdir):
global VERSION
# Get Selected object(s)
selected_objects = Blender.Object.GetSelected()
# Look for an armature
arm = None
for obj in selected_objects:
data = obj.getData()
if type(data) is Blender.Types.ArmatureType:
arm = data
arm_obj = obj
break
if arm == None:
Log('No armature found')
return
# SCM
Log('exporting model: ' + arm_obj.name)
mesh = make_scm(arm_obj)
mesh.info.append('Exporter:Blender supcom-exporter.py ' + VERSION)
mesh.save(outdir + arm.name + '.scm')
mesh = None
# SCA
actions = Blender.Armature.NLA.GetActions().iteritems()
for action in actions:
Log('exporting animation: ' + action[0])
animation = make_sca(arm_obj, action[1])
animation.save(outdir + action[0] + '.sca')
Log('')
Log('Done!')
######################################################
# GUI
######################################################
# Events
EVENT_NOEVENT = 1
EVENT_DRAW = 2
EVENT_EXIT = 3
EVENT_CLOSE_LOG = 4
EVENT_EXPORTDIR_TEXT = 5
EVENT_EXPORTDIR = 6
export_directory = Blender.Draw.Create("sds")
show_log = 0
def fileselector_callback(filename):
export_directory.val = filename
def draw():
global EVENT_NOEVENT, EVENT_DRAW, EVENT_EXIT, EVENT_CLOSE_LOG
global export_directory, show_log
global log, log_max_lines
global VERSION
# Titles
glClear(GL_COLOR_BUFFER_BIT)
top = 60 + log_max_lines * 12 + 8
top_x = 304
glColor3f(0.8, 0.8, 1)
glRecti(top_x, top, 4, 4)
glBegin(GL_LINES)
glColor3f(0.8, 0.8, 0.8)
glVertex2d(4, top)
glVertex2d(4, 4)
glVertex2d(4, 4)
glVertex2d(top_x, 4)
glColor3f(0.5, 0.5, 0.5)
glVertex2d(top_x, top)
glVertex2d(4, top)
glVertex2d(top_x, top)
glVertex2d(top_x, top-1)
glEnd()
glColor3f(0, 0, 0)
glRasterPos2d(10, top-16)
Text("Supreme Commander Exporter " + VERSION)
# Show exporting log
if show_log:
for index in range(0, len(log)):
i = (len(log) - 1) - index
glRasterPos2i(10, 40 + i*12)
Text(log[index]) #, 'tiny')
Button("Close", EVENT_EXIT , 10, 10, 80, 18)
# Exporter GUI
else:
Blender.Draw.Button("Browse...", EVENT_EXPORTDIR, 10, 40, 80, 18, "")
export_directory = Blender.Draw.String("", EVENT_EXPORTDIR_TEXT, 100, 40, 200, 18, export_directory.val, 255, "Where to save the exported files")
# Draw and Exit Buttons
Button("Export", EVENT_DRAW , 10, 10, 80, 18)
Button("Exit", EVENT_EXIT , 100, 10, 80, 18)
def event(evt, val):
if (evt == QKEY and not val):
Exit()
def bevent(evt):
global EVENT_NOEVENT,EVENT_DRAW,EVENT_EXIT
global export_directory
global show_log
if (evt == EVENT_EXIT):
show_log = 0
Exit()
elif (evt == EVENT_EXPORTDIR):
Blender.Window.FileSelector (fileselector_callback, "Select output DIR")
Blender.Redraw()
elif (evt== EVENT_DRAW):
if (export_directory.val == ""):
return
show_log = 1
export(export_directory.val)
Blender.Redraw()
Register(draw, event, bevent)