Attempted Unreal Tournament 2004 PSK exporter

I’ve decided to write an export script for PSK files based on the specifications on UDN for Blender. Because the specs were like “this struct then that struct” I had to use C++ to transcribe it into binary. So there are two scripts involved, one Python script, which dumps the data into an ASCII format (works, included only for completeness) and one that makes a PSK (and later a PSA as well) out of the file. Well, should make, that is, since the only thing the resulting file does is crash UnrealED (General Protection Fault in MeshSkelLOD or something). Since PSK is binary I cannot get any feedback on what I’m doing wrong except that something crashes UED. I had to guess on a few instances (like in what order the members of a struct need to be written into a file, I assumed in order of declaration).

http://mitglied.lycos.de/KDR_11k/files/code/export_ask.py.txt
http://mitglied.lycos.de/KDR_11k/files/code/psk_compiler.cpp.txt
http://mitglied.lycos.de/KDR_11k/files/code/bla.ask.txt
All files are txt for better hotlinking (they’re ASCII so you can view them with that extension as well). The CPP requires the struct definitions you can get from UDN (where they describe the binary file formats). The ASK (ASCII PSK…) is the model I tried to import.
Can anyone figure out what I did wrong?

well, this could be useful

because, if upaint can open it…

I would have implemeted each struct as a python class with a write function or soemthing

I’ll have to look at it then

edit:
woo, format specs:
http://udn.epicgames.com/Technical/BinaryFormatSpecifications

oooh, and a friend of mine (python god, well, from my pov) pointed out the struct module

http://www.python.org/doc/2.2.3/lib/module-struct.html

kind of an fprintf, for python. so, the classes could be implemented (same names an in the header files) such that they could easily be output (or input) via python

so, essentially you would open the file “wb” and write the strings created by these classes…

I only have Linux based systems but I did recently get UT 2004. Too bad they didn’t port their editing stuff to linux. So I would be interested in this script. So if you need anybody to test your stuff out on Linux just let me know!

Course since they didn’t port their editor I didn’t even bother to read up on how to make mods…guess it’s time to hit some fan sites…

(sorry for the late reply, was on vacation)

zero d: I used those specifications. I guess I misinterpreted some stuff.

CraigS: They ported ucc AFAIK, which is all you need for coding. It acts as the compiler. I don’t think it can be used for compiling PSKs, though.

The format posted at UDN is not 100% correct. I got a test file, Male.psk, from UDN and also a test file from that Atari forums. When I checked the files with a hex editor, the data didn’t match the C++ headers in UnrealAnimDataStructs.h (http://udn.epicgames.com/Two/BinaryFormatSpecifications). Specifically, VVertex wedges array (a wedge consists of a UV pair with an index into the 3d points array.)
struct VVertex
{
_WORD PointIndex; // Index into the 3d point table.
FLOAT U,V; // Texture U, V coordinates. – starts at byte 5
BYTE MatIndex; // At runtime, this one will be implied by the face that’s pointing to us.
BYTE Reserved; // Top secret.
};
Actually has this format:
bytes 1-2 = word, reference to point index
bytes 3-4 = unknown, values are \x00\x00 or \xD4\x77 or \xD5\x77; file 2 values are \x00\x00 or \x12\x00
bytes 5-8 = float, U coordinate
bytes 9-12 = float, V coordinate
bytes 13-14 = integer, Material Index
bytes 15-16 = unknown, values are \xD5\x77 or \xD6\x77 or \x00\x00; file 2 values are \x75\x66 or \xD4\x77

The specs say that everything has to be padded to 4 bytes, which would explain the “trash bytes”. If you are correct, though, the RESERVED value is missing…

Does anyone know if GCC 3 understands the “#pragma pack(push,4)” command? Because that could be the problem…

it should have the same output regardless

unless you overide it it should be the default to do that [at least on 32 bit systems]

as for the “garbage values” I consider them odd, I guess I’ll get to play with this if I want answers

I imagine they used the c (fopen…) way of file writing

it makes it much easier to write whole structs and classes…

it could also explain the of the garbage values where there shouldn’t be anything [after the null in a string, in padding bytes, unused fields]

so by float they mean a single precision?

I’m at the moment playing with creating a psk of a cube, from there I will work on a python conversion of UnrealAnimDataStructs.h, then the porting of my cube creating script, then an export [or I can leave that up to you]

don’t expect much quickly though…

well, I was able to make a cube unrealed could open

upaint didn’t think much of it [but it didn’t crash]

also of note there is a function to view a .psk file I wrote… it was helpful for debugging, and I will probably use it to test python…

meh.cpp

#include <iostream>
#include <stdio.h>
#include <string.h>

#define _WORD unsigned short
#define DWORD unsigned int
#define INT int
#define BYTE unsigned char
#define FLOAT float

#define ANSICHAR char

#include "UnrealAnimDataStructs.h"

// I USE THE C METHOD OF WRITING FILES!!!

using namespace std;

void makepskcube();
void readpsk(char * filename);
void sizeofall();
	
	/* quoth : http://udn.epicgames.com/Two/BinaryFormatSpecifications
	
This contains the mesh, bone influence indices and weights for each vertex, the 
bone names, bone hierarchy, and skeletal default pose.
	PSK format, Version 1.0
*General Header (all headers are of type VChunkHeader)
  * When the VChunkHeader.Typeflag version number equals decimal '1999801' or 
    lower it denotes this version 1.0 layout of the PSK file.
  * Header specifying number of points ( in ChunkHeader.Datacount )
*VPoint points array 
*Header specifying amount of wedges.
*VVertex wedges array (a wedge consists of a UV pair with an index into the 3d 
  points array.) 
*Header specifying amount of faces.
*VTriangle faces array.
*Header specifying amount of materials.
*VMaterial Materials array.
*Header specifying amount of bones.
*VBone bones array.
*Header specifying amount of bone influences.
*VRawBoneInfluence array of Influences.

	*/
	
int main ( ) {
	//sizeofall();

	makepskcube();

	readpsk("testpsk.psk");
	//readpsk("KarmaTube.PSK");

	return 0;
}

void makepskcube() {	
	FILE * apsk = fopen("testpsk.psk", "wb" );

 	if (!apsk) {
 		cout << "couldn't open file" << endl;
		exit(1);
	} else {
		int i;
		// an attempt to write a cube
		
		VChunkHeader mainheader;
			strcpy(mainheader.ChunkID,"dinner_yum");
			mainheader.TypeFlag = 1999801; // should indicate file format version
			mainheader.DataCount = 0;
			mainheader.DataSize = 0;

		cout << "1 " << fwrite(&mainheader, sizeof(VChunkHeader), 1, apsk) << endl;

		// header for number of verts
		VChunkHeader header;
			strcpy(header.ChunkID,"dinner_yum");
			header.TypeFlag = 1999801; // should indicate file format version
			header.DataCount = 8;  // number of verts
			header.DataSize = sizeof(VPoint); // just a guess

		cout << "1 " << fwrite(&header, sizeof(VChunkHeader), 1, apsk) << endl;
		
		struct VPoint verts[8];
		
			verts[0].Point.X = -10.0; verts[0].Point.Y = -10.0; verts[0].Point.Z = -10.0;
			verts[1].Point.X =  10.0; verts[1].Point.Y = -10.0; verts[1].Point.Z = -10.0;
			verts[2].Point.X =  10.0; verts[2].Point.Y = -10.0; verts[2].Point.Z =  10.0;
			verts[3].Point.X = -10.0; verts[3].Point.Y = -10.0; verts[3].Point.Z =  10.0;
			verts[4].Point.X = -10.0; verts[4].Point.Y =  10.0; verts[4].Point.Z = -10.0;
			verts[5].Point.X =  10.0; verts[5].Point.Y =  10.0; verts[5].Point.Z = -10.0;
			verts[6].Point.X =  10.0; verts[6].Point.Y =  10.0; verts[6].Point.Z =  10.0;
			verts[7].Point.X = -10.0; verts[7].Point.Y =  10.0; verts[7].Point.Z =  10.0;
		
		cout << "8 " << fwrite(verts, sizeof(VPoint), 8, apsk) << endl;
		
		// a "wedge" associates a uv coordinate with a vertex
  		VChunkHeader wedgeheader;
			strcpy(wedgeheader.ChunkID,"wedges_moo");
			wedgeheader.TypeFlag = 1999801;
			wedgeheader.DataCount = 24;
			wedgeheader.DataSize = sizeof(VVertex);
			
		cout << "1 " << fwrite(&wedgeheader, sizeof(VChunkHeader), 1, apsk) << endl;
			
		struct VVertex wedges[24];
		
		for (i = 0; i < 6; i++) { // 24 / 4 = 6
			// I am creating the uvs of one quad at a time
			for (int j = 0; j < 4; j++) {
				wedges[4*i+j].MatIndex = 0;
				wedges[4*i+j].Reserved = 0;
				wedges[4*i+j].PointIndex = (4*i+j)%8;
			}
			// these aren't ideal... yet
			
			// now set the UV coordinates
			wedges[4*i].U = 0.25*(i%3);
			wedges[4*i].V = 0.25*(i/3);
			
			wedges[4*i+1].U = 0.25*(i%3)+0.2;
			wedges[4*i+1].V = 0.25*(i/3);

			wedges[4*i+2].U = 0.25*(i%3)+0.2;
			wedges[4*i+2].V = 0.25*(i/3)+0.2;
			
			wedges[4*i+3].U = 0.25*(i%3);
			wedges[4*i+3].V = 0.25*(i/3)+0.2;
		}

		cout << "24 " << fwrite(wedges, sizeof(VVertex), 24, apsk) << endl;

		VChunkHeader faceheader;
			strcpy(faceheader.ChunkID,"w00t_faces");
			faceheader.TypeFlag = 1999801;
			faceheader.DataCount = 12; // all triangles, quads aren't allowed
			faceheader.DataSize = sizeof(VTriangle);
		
		cout << "1 " << fwrite(&faceheader, sizeof(VChunkHeader), 1, apsk) << endl;
		
		struct VTriangle tris[12];
		
		for (i = 0; i<12; i++) {
  			tris[i].MatIndex = 0;
     		tris[i].AuxMatIndex = 0;
       		tris[i].SmoothingGroups = 0x00000001;
   		}
		
		// it will be a pain if it turns out I got the vertex order backwards
		// these go counter clockwise

		// bottom, top
		tris[0].WedgeIndex[0] = 0; tris[0].WedgeIndex[1] = 1; tris[0].WedgeIndex[2] = 2;
		tris[1].WedgeIndex[0] = 2; tris[1].WedgeIndex[1] = 3; tris[1].WedgeIndex[2] = 0;
		
		tris[10].WedgeIndex[0]= 4; tris[10].WedgeIndex[1]= 7; tris[10].WedgeIndex[2]= 6;
		tris[11].WedgeIndex[0]= 6; tris[11].WedgeIndex[1]= 5; tris[11].WedgeIndex[2]= 4;

		// left front right back
		tris[2].WedgeIndex[0] = 9; tris[2].WedgeIndex[1] = 13; tris[2].WedgeIndex[2] = 14;// + 8
		tris[3].WedgeIndex[0] = 14; tris[3].WedgeIndex[1] = 10; tris[3].WedgeIndex[2] = 9;
		
  		tris[4].WedgeIndex[0] = 18; tris[4].WedgeIndex[1] = 22; tris[4].WedgeIndex[2] = 23;// +16
		tris[5].WedgeIndex[0] = 23; tris[5].WedgeIndex[1] = 19; tris[5].WedgeIndex[2] = 18;
		
		tris[6].WedgeIndex[0] = 11; tris[6].WedgeIndex[1] = 15; tris[6].WedgeIndex[2] = 12;// + 8
		tris[7].WedgeIndex[0] = 12; tris[7].WedgeIndex[1] = 8; tris[7].WedgeIndex[2] = 11;
		
		tris[8].WedgeIndex[0] = 16; tris[8].WedgeIndex[1] = 20; tris[8].WedgeIndex[2] = 21;// + 16
		tris[9].WedgeIndex[0] = 21; tris[9].WedgeIndex[1] = 17; tris[9].WedgeIndex[2] = 16;
		
		// hack [temporary hopefully] to make vertex order clockwise
		for (i =0; i<12; i++) {
			int temp = tris[i].WedgeIndex[2];
			tris[i].WedgeIndex[2] = tris[i].WedgeIndex[0];
			tris[i].WedgeIndex[0] = temp;
		}
		

		cout << "12 " << fwrite(tris, sizeof(VTriangle), 12, apsk) << endl;
		
		VChunkHeader matheader;
			strcpy(matheader.ChunkID,"materialz");
			matheader.TypeFlag = 1999801;
			matheader.DataCount = 1;
			matheader.DataSize = sizeof(VMaterial);
			
		cout << "1 " << fwrite(&matheader, sizeof(VChunkHeader), 1, apsk) << endl;
		
		struct VMaterial materials[1];
		
		strcpy(materials[0].MaterialName,"MATERIAL_name_meh\0");
		materials[0].TextureIndex = 0;
		materials[0].PolyFlags = 0;
		materials[0].AuxMaterial = 0;
		materials[0].AuxFlags = 0;
		materials[0].LodBias = 0;
		materials[0].LodStyle = 0;
		
		cout << "1 " << fwrite(materials, sizeof(VMaterial), 1, apsk) << endl;
		
		VChunkHeader boneheader;
			strcpy(boneheader.ChunkID, "bones_yay");
			boneheader.TypeFlag = 1999801;
			boneheader.DataCount = 1;
			boneheader.DataSize = sizeof(VBone);
			
		cout << "1 " << fwrite(&boneheader, sizeof(VChunkHeader), 1, apsk) << endl;
		
		struct VBone bones[1];
		
		strcpy(bones[0].Name, "HEAD_BONE");
		bones[0].Flags = 0;
		bones[0].NumChildren = 0;
		bones[0].ParentIndex = 0; // this is the root bone
		bones[0].BonePos.Position.X = 0;
		bones[0].BonePos.Position.Y = 0;
		bones[0].BonePos.Position.Z = 0;
		// what is the y axis as a quaternoin?
		bones[0].BonePos.Orientation.X = 0;
		bones[0].BonePos.Orientation.Y = 1;
		bones[0].BonePos.Orientation.Z = 0;
		bones[0].BonePos.Orientation.W = 0;
		bones[0].BonePos.Length = 1;
		bones[0].BonePos.XSize = 1;
		bones[0].BonePos.YSize = 1;
		bones[0].BonePos.ZSize = 1;
		
		cout << "1 " << fwrite(bones, sizeof(VBone), 1, apsk) << endl;
		
		VChunkHeader boneinfluencesheader;
			strcpy(boneinfluencesheader.ChunkID, "influences");
			boneinfluencesheader.TypeFlag = 1999801;
			boneinfluencesheader.DataCount = 8;
			boneinfluencesheader.DataSize = sizeof(VRawBoneInfluence);
			
		cout << "1 " << fwrite(&boneinfluencesheader, sizeof(VChunkHeader), 1, apsk) << endl;
			
		struct VRawBoneInfluence boneinfluences[8];
		
		for (i = 0; i<8; i++) {
			boneinfluences[i].BoneIndex = 0;
			boneinfluences[i].PointIndex = i;
			boneinfluences[i].Weight = 1.0;
		}
		
		cout << "8 " << fwrite(boneinfluences, sizeof(VRawBoneInfluence), 8, apsk) << endl;
		

		fclose(apsk);
	}	
}

void readpsk(char * filename) {
/* VChunkHeader
VPoint
VVertex
VTriangle
VMaterial
VBone
VRawBoneInfluence */
	int count = 1, i;
	FILE * apsk = fopen(filename, "rb" );
	
	if (!apsk) {
		cout << "couldn't open file" << endl;
		return;
	}
	
	VChunkHeader header;
	
	if ( fread(&header, sizeof(VChunkHeader), 1, apsk) < 1)
		goto end_file;
	
	cout << "Main Header" << 
	"
	ChunkID  	" << header.ChunkID <<
	"
	TypeFlag 	" << header.TypeFlag <<
	"
	DataCount	" << header.DataCount <<
	"
	DataSize 	" << header.DataSize << 
	endl;
	
	if ( fread(&header, sizeof(VChunkHeader), 1, apsk) < 1)
		goto end_file;
	
	cout << "Points Header" << 
	"
	ChunkID  	" << header.ChunkID <<
	"
	TypeFlag 	" << header.TypeFlag <<
	"
	DataCount	" << header.DataCount <<
	"
	DataSize 	" << header.DataSize << 
	endl;
	
	count = header.DataCount;
	
	for (i = 0; i < count; i++) {
		VPoint point;
		if ( fread(&point, sizeof(VPoint), 1, apsk) < 1)
			goto end_file;
		cout << "VPoint " << i <<
		"
	Point.X	" << point.Point.X << 
		"
	Point.Y	" << point.Point.Y <<
  		"
	Point.Z	" << point.Point.Z <<
    	endl;
    }

	if ( fread(&header, sizeof(VChunkHeader), 1, apsk) < 1)
		goto end_file;
	
	cout << "Verticies [wedges] header" << 
	"
	ChunkID  	" << header.ChunkID <<
	"
	TypeFlag 	" << header.TypeFlag <<
	"
	DataCount	" << header.DataCount <<
	"
	DataSize 	" << header.DataSize << 
	endl;
	
	count = header.DataCount;
   	
   	for (i = 0; i < count; i++) {
   		VVertex wedge;
		if ( fread(&wedge, sizeof(VVertex), 1, apsk) < 1)
			goto end_file;
		cout << "VVertex " << i <<
		"
	PointIndex	" << wedge.PointIndex <<
		"
	U         	" << wedge.U <<
		"
	V         	" << wedge.V <<
		"
	MatIndex  	" << int(wedge.MatIndex) <<
		"
	Reserved  	" << int(wedge.Reserved) <<
		endl;
	}

	if ( fread(&header, sizeof(VChunkHeader), 1, apsk) < 1)
		goto end_file;
	
	cout << "Triangles header" << 
	"
	ChunkID  	" << header.ChunkID <<
	"
	TypeFlag 	" << header.TypeFlag <<
	"
	DataCount	" << header.DataCount <<
	"
	DataSize 	" << header.DataSize << 
	endl;
	
	count = header.DataCount;

   	for (i = 0; i < count; i++) {
   		VTriangle tri;
		if ( fread(&tri, sizeof(VTriangle), 1, apsk) < 1)
			goto end_file;
		cout << "VTriangle " << i <<
		"
	WedgeIndex[0]  	" << tri.WedgeIndex[0] << 
		"
	WedgeIndex[1]  	" << tri.WedgeIndex[1] <<
		"
	WedgeIndex[2]  	" << tri.WedgeIndex[2] <<
		"
	MatIndex       	" << int(tri.MatIndex) <<
		"
	AuxMatIndex    	" << int(tri.AuxMatIndex) <<
		"
	SmoothingGroups	" << tri.SmoothingGroups <<
		endl;
   	}
   	
	if ( fread(&header, sizeof(VChunkHeader), 1, apsk) < 1)
		goto end_file;
	
	cout << "Materials header" << 
	"
	ChunkID  	" << header.ChunkID <<
	"
	TypeFlag 	" << header.TypeFlag <<
	"
	DataCount	" << header.DataCount <<
	"
	DataSize 	" << header.DataSize << 
	endl;
	
	count = header.DataCount;
	
   	for (i = 0; i < count; i++) {
   		VMaterial mat;
		if ( fread(&mat, sizeof(VMaterial), 1, apsk) < 1)
			goto end_file;
		cout << "Material " << i << 
		"
	MaterialName	" << mat.MaterialName <<
		"
	TextureIndex	" << mat.TextureIndex <<
		"
	PolyFlags   	" << mat.PolyFlags <<
		"
	AuxMaterial 	" << mat.AuxMaterial <<
		"
	AuxFlags    	" << mat.AuxFlags <<
		"
	LodBias     	" << mat.LodBias <<
		"
	LodStyle    	" << mat.LodStyle <<
		endl;
	}
	
	if ( fread(&header, sizeof(VChunkHeader), 1, apsk) < 1)
		goto end_file;
	
	cout << "bones header" << 
	"
	ChunkID  	" << header.ChunkID <<
	"
	TypeFlag 	" << header.TypeFlag <<
	"
	DataCount	" << header.DataCount <<
	"
	DataSize 	" << header.DataSize << 
	endl;
	
	count = header.DataCount;
	
	for (i = 0; i < count; i++) {
   		VBone bone;
		if ( fread(&bone, sizeof(VBone), 1, apsk) < 1)
			goto end_file;
		cout << "Bone " << i << 
		"
	Name                 	" << bone.Name <<
		"
	Flags                	" << bone.Flags <<
		"
	NumChildren          	" << bone.NumChildren <<
		"
	ParentIndex          	" << bone.ParentIndex <<
		"
	BonePos.Orientation.X	" << bone.BonePos.Orientation.X <<
		"
	BonePos.Orientation.Y	" << bone.BonePos.Orientation.Y <<
		"
	BonePos.Orientation.Z	" << bone.BonePos.Orientation.Z <<
		"
	BonePos.Orientation.W	" << bone.BonePos.Orientation.W <<
		"
	BonePos.Position.X   	" << bone.BonePos.Position.X <<
  		"
	BonePos.Position.Y   	" << bone.BonePos.Position.Y <<
   		"
	BonePos.Position.Z   	" << bone.BonePos.Position.Z <<
   		"
	BonePos.Length       	" << bone.BonePos.Length <<
   		"
	BonePos.XSize        	" << bone.BonePos.XSize <<
   		"
	BonePos.YSize        	" << bone.BonePos.YSize <<
   		"
	BonePos.ZSize        	" << bone.BonePos.ZSize <<
		endl;
	}
	
	if ( fread(&header, sizeof(VChunkHeader), 1, apsk) < 1)
		goto end_file;
	
	cout << "bone influences header" << 
	"
	ChunkID  	" << header.ChunkID <<
	"
	TypeFlag 	" << header.TypeFlag <<
	"
	DataCount	" << header.DataCount <<
	"
	DataSize 	" << header.DataSize << 
	endl;
	
	count = header.DataCount;
	
	for (i = 0; i < count; i++) {
   		VRawBoneInfluence bi;
		if ( fread(&bi, sizeof(VRawBoneInfluence), 1, apsk) < 1)
			goto end_file;
		cout << "Bone influence " << i << 
		"
	Weight    	" << bi.Weight <<
		"
	PointIndex	" << bi.PointIndex <<
		"
	BoneIndex 	" << bi.BoneIndex <<
		endl;
	}
	
	if (!feof(apsk) ) {
		cout << "some data remains in file unread" << endl;
	}

end_file:
	cout << "###EOF###?";
	
}

void sizeofall() {
	cout <<
 		"_WORD	2 " << sizeof(_WORD) << "
" <<
 		"DWORD	4 " << sizeof(DWORD) << "
" <<
 		"INT	4 " << sizeof(INT) << "
" <<
 		"BYTE	1 " << sizeof(BYTE) << "
" <<
 		"FLOAT	4 " << sizeof(FLOAT) << "
" <<
 		"ANSICHAR	1 " << sizeof(ANSICHAR) << "
" <<
 		
 		flush;
	cout << endl;
	
	cout << 
		"VChunkHeader      " << sizeof(VChunkHeader) << "
" <<
		"VPoint            " << sizeof(VPoint) << "
" <<
		"FVector           " << sizeof(FVector) << "
" <<
		"VVertex           " << sizeof(VVertex) << "
" <<
		"VTriangle         " << sizeof(VTriangle) << "
" <<
		"VMaterial         " << sizeof(VMaterial) << "
" <<
		"VBone             " << sizeof(VBone) << "
" <<
		"VJointPos         " << sizeof(VJointPos) << "
" <<
		"VRawBoneInfluence " << sizeof(VRawBoneInfluence) << "
" <<
		
		endl;
}

the uv coordinates are odd

and it appears upaint uses left handed coordinates [y axis up] where unrealED uses right handed [z axis up]

I don’t know unrealEd very well, so I can’t say I can do everything, but getting pretty far [even tonight or tomorrow] seems possible

I missed the part that all values are padded to 4 bytes. That clears things up. But why would the padded bytes be set to \xD4\x77, etc. instead of \x00\x00? But to my point…
I modified a Python script to output meshes to ASE format for UEd3. I’d like to modify it further to export PSK format. ASE format is ascii, while PSK is binary. Python only writes out text, but includes a built in type “Struct” to convert Python values to a hex string equivalent to the C binary value. I hope to have the script in a week or two.
Here is the ASE script (there is a better script posted by Doc Holiday )
ASE_Export_for_UEd3.py

# ASE Export Script  (for importing into UnrealEd3)
#  Plus Smoothing Group Workaround
# - Modified script to function with Blender 2.28 API
# - Modified script to use smoothing groups workaround script
# - Modified script to export submaterials
# - Added button interface
# This script will export an ASE format file for each 
# selected Mesh object in a scene.
# Each object will be written to it's own ASE file.
#**Please read the comments about UnrealEd below.
#
# Original script by:
# v1.0 22/4/02 Copyright (C) 2002 Stephen Doughty <[email protected]>
# v1.0 is a rewritten version that relies very heavily on the original script
# written by Andrew Chadwick <[email protected]> (http://www.piffle.org/).
#
# Run the script in the Blender script window using Alt-P
# This script will export any selected meshes into individual ase files,
# to directory "C:\ase". 
# The OBJECT transformation SIZE and LOCATION are now
#  incorporated into the vertex data
# This script is supplied 'as is', NO warranty of any nature is implied.
#
#Notes about use for UnrealEd3 (provided with Unreal 2)
# UnrealEd expects imported meshes to be wrapped, so UV texture coordinates
# are required.  The UV texture image information is imported as sub-textures
# of the first material, so this script will only export 1 material with a
# sub-material for each image used for UV wrapping.  Each image is imported
# as a section into UEd.
# If no UV mapping is specified, default values will be exported (and it's
# pretty ugly).
# 4 vertex faces get converted to 3 vertext faces.  For best results convert
# all 4 vertex faces before UV wrapping and exporting.
#
#Notes about Smoothing Group Workaround
# Blender does not implement the use of smoothing groups, but uses the
# AutoSmooth function instead.  But the ASE format uses smoothing groups
# assigned to each face.  This script includes a workaround to set and export
# smoothing groups. It does not implement the use or display of smoothing
# groups in Blender.  It just keeps a list of the smoothing group for each
# face that can be used for exporting or face selection.
#
# The workaround creates a text object, named <ObjectName>SmthGrps,
# containing a list of the smoothing group that each face is assigned to. It
# will allow you to add faces to a smoothing group or select all faces
# assigned to a particular smoothing group. Each line in the text object
# corresponds to a mesh face index. The value on each line is the smoothing
# group that face is assigned to.  Initially all faces default to smoothing
# group = 0.  When you save your Blender scene, the smoothing group text
# object will be saved with it.

import sys
sys.path.append('C:\\Progra~1\\Python~1\\Lib')
import Blender
import string
import os
from Blender import NMesh, Text, BGL, Draw

def Addheader():
	file.write("*3DSMAX_ASCIIEXPORT 200
")
	file.write("*COMMENT \"Exported from Blender by ExportASEII.py\"
")
	file.write("*SCENE {
")
	file.write("%s*SCENE_FILENAME \"%s\"
" % (tab, filename))
	file.write("%s*SCENE_FIRSTFRAME 0
" % (tab)) #Dummy Value
	file.write("%s*SCENE_LASTFRAME 1
" % (tab)) #Dummy Value
	file.write("%s*SCENE_FRAMESPEED 30
" % (tab)) #Dummy Value
	file.write("%s*SCENE_TICKSPERFRAME 160
" % (tab)) #Dummy Value
	file.write("%s*SCENE_BACKGROUND_STATIC 0.0000 0.0000 0.0000
" % (tab)) #Dummy Value
	file.write("%s*SCENE_AMBIENT_STATIC 0.0431 0.0431 0.0431
" % (tab)) #Dummy Value
	file.write("}
")

def Addmaterials():
	global eachmesh
	#add the object/nmesh material data here
	#It seems that UEd3 expects all meshes to be UV wrapped
	#Scan the object for textures assigned to the faces and
	#create a sub-material for each texture found.
	imglist = []
	for y in range(len(eachmesh.faces)):
		tmpname = str(eachmesh.faces[y].image)
		name2 = tmpname[8:-2]
		##Need to test this
		if imglist.count(name2) == 0:
			imglist.append(name2)
	if imglist[0] == '':
		imglist[0] = 'IMAGEMAP_PLACEHOLDER'

	file.write("*MATERIAL_LIST {
")
	file.write("%s*MATERIAL_COUNT 1
" % (tab))
	file.write("%s*MATERIAL 0 {
" % (tab))
	file.write("%s*MATERIAL_NAME \"Material #1\"
" % (tab * 2))
	file.write("%s*MATERIAL_CLASS \"Multi/Sub-Object\"
" % (tab * 2))
	file.write("%s*MATERIAL_AMBIENT 0.50000	0.50000	0.50000
" % (tab * 2))
	file.write("%s*MATERIAL_DIFFUSE 0.50000	0.50000	0.50000
" % (tab * 2))
	file.write("%s*MATERIAL_SPECULAR 0.50000	0.50000	0.50000
" % (tab * 2))
	file.write("%s*MATERIAL_SHINE 25.0000
" % (tab * 2))
	file.write("%s*MATERIAL_SHINESTRENGTH 0.80000
" % (tab * 2))
	file.write("%s*MATERIAL_TRANSPARENCY 0.00000
" % (tab * 2))
	file.write("%s*MATERIAL_WIRESIZE 1.0000
" % (tab * 2))
	file.write("%s*NUMSUBMTLS %d
" % (tab * 2, len(imglist)))
	if eachmesh.materials:
		for x in range(len(imglist)):
			eachmat = Blender.Material.Get(eachmesh.materials[0].name)
			file.write("%s*SUBMATERIAL %d {
" % (tab * 2, x))
			submatnm = "Submaterial #"
			submatnm.append(str(x))
			file.write("%s*MATERIAL_NAME \"%s\"
" % ((tab * 3),submatnm))
			file.write("%s*MATERIAL_CLASS \"Standard\"
" % (tab * 3))
			file.write("%s*MATERIAL_AMBIENT %f %f %f
" % ((tab * 3),(eachmat.R * eachmat.amb),(eachmat.G * eachmat.amb),(eachmat.B * eachmat.amb)))
			file.write("%s*MATERIAL_DIFFUSE %f %f %f
" % ((tab * 3),eachmat.R, eachmat.G, eachmat.B))
			file.write("%s*MATERIAL_SPECULAR %f %f %f
" % ((tab * 3),eachmat.specCol[0] * eachmat.spec, eachmat.specCol[1] * eachmat.spec, eachmat.specCol[2] * eachmat.spec))
			file.write("%s*MATERIAL_SHINE %f
" % ((tab * 3),(eachmat.hard/2)))
			file.write("%s*MATERIAL_SHINESTRENGTH %f
" % ((tab * 3),eachmat.ref))
			file.write("%s*MATERIAL_TRANSPARENCY %f
" % ((tab * 3),1 - eachmat.alpha))
			file.write("%s*MATERIAL_WIRESIZE 1.0000
" % (tab * 3)) #Dummy Value
			file.write("%s*MATERIAL_SHADING Blinn
" % (tab * 3)) #Dummy Value
			file.write("%s*MATERIAL_XP_FALLOFF In
" % (tab * 3)) #Dummy Value
			file.write("%s*MATERIAL_SELFILLUM %f
" % ((tab * 3), eachmat.emit))
			file.write("%s*MATERIAL_FALLOFF In
" % (tab * 3)) #Dummy Value
			file.write("%s*MATERIAL_XP_TYPE Filter
" % (tab * 3)) #Dummy Value
			file.write("%s*MAP_DIFFUSE {
" % (tab * 3)) 
			file.write("%s*MAP_NAME \"Map #%d\"
" % ((tab * 4), x))
			file.write("%s*MAP_CLASS \"Bitmap\"
" % (tab * 4))
			file.write("%s*MAP_SUBNO 1
" % (tab * 4)) #Dummy Value
			file.write("%s*MAP_AMOUNT 1.0000
" % (tab * 4)) #Dummy Value
			file.write("%s*BITMAP \"%s\"
" % ((tab * 4),imglist[x]))
			file.write("%s*MAP_TYPE Screen
" % (tab * 4)) #Dummy Value
			file.write("%s*UVW_U_OFFSET 0.0000
" % (tab * 4)) #Dummy Value
			file.write("%s*UVW_V_OFFSET 0.0000
" % (tab * 4)) #Dummy Value
			file.write("%s*UVW_U_TILING 1.0000
" % (tab * 4)) #Dummy Value
			file.write("%s*UVW_V_TILING 1.0000
" % (tab * 4)) #Dummy Value
			file.write("%s*UVW_ANGLE 0.0000
" % (tab * 4)) #Dummy Value
			file.write("%s*UVW_BLUR 1.0000
" % (tab * 4)) #Dummy Value
			file.write("%s*UVW_BLUR_OFFSET 0.0000
" % (tab * 4)) #Dummy Value
			file.write("%s*UVW_NOISE_AMT 1.0000
" % (tab * 4)) #Dummy Value
			file.write("%s*UVW_NOISE_SIZE 1.0000
" % (tab * 4)) #Dummy Value
			file.write("%s*UVW_NOISE_LEVEL 1
" % (tab * 4)) #Dummy Value
			file.write("%s*UVW_NOISE_PHASE 0.0000
" % (tab * 4)) #Dummy Value
			file.write("%s*BITMAP_FILTER Pyramidal
" % (tab * 4)) #Dummy Value
			file.write("%s}
" % (tab * 3))
			file.write("%s}
" % (tab * 2))
	else:
		for x in range(len(imglist)):
			#Put Dummy Values except for image map here!
			file.write("%s*SUBMATERIAL %d {
" % (tab * 2, x))
			submatnm = "Submaterial #"
			submatnm.append(str(x))
			file.write("%s*MATERIAL_NAME \"%s\"
" % ((tab * 3),submatnm))
			file.write("%s*MATERIAL_CLASS \"Standard\"
" % (tab * 3))
			file.write("%s*MATERIAL_AMBIENT 0.5000	0.5000	0.5000
" % (tab * 3))
			file.write("%s*MATERIAL_DIFFUSE 0.8500, 0.8500, 0.8500
" % (tab * 3))
			file.write("%s*MATERIAL_SPECULAR 0.5000, 0.5000, 0.5000
" % (tab * 3))
			file.write("%s*MATERIAL_SHINE 15
" % (tab * 3))
			file.write("%s*MATERIAL_SHINESTRENGTH 0.1
" % (tab * 3))
			file.write("%s*MATERIAL_TRANSPARENCY 0.0000
" % (tab * 3))
			file.write("%s*MATERIAL_WIRESIZE 1.0000
" % (tab * 3)) #Dummy Value
			file.write("%s*MATERIAL_SHADING Blinn
" % (tab * 3)) #Dummy Value
			file.write("%s*MATERIAL_XP_FALLOFF In
" % (tab * 3)) #Dummy Value
			file.write("%s*MATERIAL_SELFILLUM 0.0000
" % (tab * 3))
			file.write("%s*MATERIAL_FALLOFF In
" % (tab * 3)) #Dummy Value
			file.write("%s*MATERIAL_XP_TYPE Filter
" % (tab * 3)) #Dummy Value
			file.write("%s*MAP_DIFFUSE {
" % (tab * 3))
			file.write("%s*MAP_NAME \"Map #1\"
" % ((tab * 4)))
			file.write("%s*MAP_CLASS \"Bitmap\"
" % (tab * 4))
			file.write("%s*MAP_SUBNO 1
" % (tab * 4)) #Dummy Value
			file.write("%s*MAP_AMOUNT 1.0000
" % (tab * 4)) #Dummy Value
			file.write("%s*BITMAP \"%s\"
" % ((tab * 4),imglist[0]))
			file.write("%s*MAP_TYPE Screen
" % (tab * 4)) #Dummy Value
			file.write("%s*UVW_U_OFFSET 0.0000
" % (tab * 4)) #Dummy Value
			file.write("%s*UVW_V_OFFSET 0.0000
" % (tab * 4)) #Dummy Value
			file.write("%s*UVW_U_TILING 1.0000
" % (tab * 4)) #Dummy Value
			file.write("%s*UVW_V_TILING 1.0000
" % (tab * 4)) #Dummy Value
			file.write("%s*UVW_ANGLE 0.0000
" % (tab * 4)) #Dummy Value
			file.write("%s*UVW_BLUR 1.0000
" % (tab * 3)) #Dummy Value
			file.write("%s*UVW_BLUR_OFFSET 0.0000
" % (tab * 3)) #Dummy Value
			file.write("%s*UVW_NOUSE_AMT 1.0000
" % (tab * 3)) #Dummy Value
			file.write("%s*UVW_NOISE_SIZE 1.0000
" % (tab * 3)) #Dummy Value
			file.write("%s*UVW_NOISE_LEVEL 1
" % (tab * 3)) #Dummy Value
			file.write("%s*UVW_NOISE_PHASE 0.0000
" % (tab * 3)) #Dummy Value
			file.write("%s*BITMAP_FILTER Pyramidal
" % (tab * 3)) #Dummy Value
			file.write("%s}
" % (tab * 2))
			file.write("%s}
" % (tab))
		#End of IF/ELSE material section
	file.write("}
")       


def Addgeobheader():
	global eachmesh
	file.write("*GEOMOBJECT {
")
	file.write("%s*NODE_NAME \"%s\"
" % (tab, eachmesh.name ))
	file.write("%s*NODE_TM {
" % (tab))
	file.write("%s*NODE_NAME \"%s\"
" % ((tab * 2), eachmesh.name ))
	file.write("%s*INHERIT_POS 0 0 0
" % (tab * 2))
	file.write("%s*INHERIT_ROT 0 0 0
" % (tab * 2))
	file.write("%s*INHERIT_SCL 0 0 0
" % (tab * 2))
	file.write("%s*TM_ROW0 1.0000 0.0000 0.0000
" % (tab * 2))
	file.write("%s*TM_ROW1 0.0000 1.0000 0.0000
" % (tab * 2))
	file.write("%s*TM_ROW2 0.0000 0.0000 1.0000
" % (tab * 2))
	file.write("%s*TM_ROW3 0.0000 0.0000 0.0000
" % (tab * 2))
	file.write("%s*TM_POS 0.0000 0.0000 0.0000
" % (tab * 2))
	file.write("%s*TM_ROTAXIS 0.0000 0.0000 0.0000
" % (tab * 2))
	file.write("%s*TM_ROTANGLE 0.0000
" % (tab * 2))
	file.write("%s*TM_SCALE 1.0000 1.0000 1.0000
" % (tab * 2))
	file.write("%s*TM_SCALEAXIS 0.0000 0.0000 0.0000
" % (tab * 2))
	file.write("%s*TM_SCALEAXISANG 0.0000
" % (tab * 2))
	file.write("%s}
" % (tab))

def Addvertexdata():
	global eachmesh
	file.write("%s*MESH {
" % (tab))
	file.write("%s*TIMEVALUE 0
" % (tab * 2))
	file.write("%s*MESH_NUMVERTEX %d
" % ((tab * 2),len(eachmesh.verts)))
	file.write("%s*MESH_NUMFACES %d
" % ((tab * 2),tris))
	file.write("%s*MESH_VERTEX_LIST {
" % (tab * 2))
	mv = eachmesh.verts
	for x in range(len(mv)):
		coords = eachmesh.verts[x].co
		coords[0] = (coords[0] * each.SizeX) + each.LocX
		coords[1] = (coords[1] * each.SizeY) + each.LocY
		coords[2] = (coords[2] * each.SizeZ) + each.LocZ
		file.write("%s*MESH_VERTEX %d %f %f %f
" % ((tab * 3), x, coords[0], coords[1], coords[2]))
	file.write("%s}
" % ((tab * 2)))

def Addfacelist():
	global eachmesh, sglist
	file.write("%s*MESH_FACE_LIST{
" % (tab * 2))
	mf = eachmesh.faces
	fn = 0
	for y in range(len(mf)):
		if len(mf[y].v) == 3:
			vA = mf[y].v[0].index
			vB = mf[y].v[1].index
			vC = mf[y].v[2].index
			fMS = sglist[y] 
			fMtlId = mf[y].mat
			file.write("%s*MESH_FACE %d: A: %d B: %d C: %d AB: 1 BC: 1 CA: 1 *MESH_SMOOTHING %s *MESH_MTLID %d
" % ((tab * 3), fn, vA, vB, vC, fMS, fMtlId ))
			fn = fn +1
		if len(mf[y].v) == 4:
			vA = mf[y].v[0].index
			vB = mf[y].v[1].index
			vC = mf[y].v[2].index
			vD = mf[y].v[3].index
			fMS = sglist[y]
			fMtlId = mf[y].mat
			#face one
			file.write("%s*MESH_FACE %d: A: %d B: %d C: %d AB: 1 BC: 1 CA: 1 *MESH_SMOOTHING %s *MESH_MTLID %d
" % ((tab * 3), fn, vA, vB, vC, fMS, fMtlId ))
			fn = fn +1
			#face two
			file.write("%s*MESH_FACE %d: A: %d B: %d C: %d AB: 1 BC: 1 CA: 1 *MESH_SMOOTHING %s *MESH_MTLID %d
" % ((tab * 3), fn, vC, vD, vA, fMS, fMtlId ))
			fn = fn +1
	file.write("%s}
" % ((tab * 2)))

def AddUVdata():
	global eachmesh
	#check for texture vertex data
	file.write("%s*MESH_NUMTVERTEX %d
" % ((tab * 2), tris*3))
	file.write("%s*MESH_TVERTLIST {
" % (tab * 2))
	i = 0
	for x in range(len(eachmesh.faces)):
		if len(eachmesh.faces[x].uv) == 3:
			U0 = eachmesh.faces[x].uv[0][0]
			V0 = eachmesh.faces[x].uv[0][1]
			U1 = eachmesh.faces[x].uv[1][0]
			V1 = eachmesh.faces[x].uv[1][1]
			U2 = eachmesh.faces[x].uv[2][0]
			V2 = eachmesh.faces[x].uv[2][1]
			file.write("%s*MESH_TVERT %d %f %f 0.0000
" % ((tab * 3),i,U0,V0))
			i += 1
			file.write("%s*MESH_TVERT %d %f %f 0.0000
" % ((tab * 3),i,U1,V1))
			i += 1
			file.write("%s*MESH_TVERT %d %f %f 0.0000
" % ((tab * 3),i,U2,V2))
			i += 1
		if len(eachmesh.faces[x].uv) == 4:
			U0 = eachmesh.faces[x].uv[0][0]
			V0 = eachmesh.faces[x].uv[0][1]
			U1 = eachmesh.faces[x].uv[1][0]
			V1 = eachmesh.faces[x].uv[1][1]
			U2 = eachmesh.faces[x].uv[2][0]
			V2 = eachmesh.faces[x].uv[2][1]
			U3 = eachmesh.faces[x].uv[3][0]
			V3 = eachmesh.faces[x].uv[3][1]
			file.write("%s*MESH_TVERT %d %f %f 0.0000
" % ((tab * 3),i, U0, V0))
			i += 1
			file.write("%s*MESH_TVERT %d %f %f 0.0000
" % ((tab * 3),i, U1, V1))
			i += 1
			file.write("%s*MESH_TVERT %d %f %f 0.0000
" % ((tab * 3),i, U2, V2))
			i += 1
			file.write("%s*MESH_TVERT %d %f %f 0.0000
" % ((tab * 3),i, U2, V2))
			i += 1
			file.write("%s*MESH_TVERT %d %f %f 0.0000
" % ((tab * 3),i, U3, V3))
			i += 1
			file.write("%s*MESH_TVERT %d %f %f 0.0000
" % ((tab * 3),i, U0, V0))
			i += 1
		if len(eachmesh.faces[x].uv) == 0:
			if len(eachmesh.faces[x].v) == 3:
				file.write("%s*MESH_TVERT %d 0.0000 1.0000 0.0000
" % ((tab * 3),i))
				i += 1
				file.write("%s*MESH_TVERT %d 1.0000 1.0000 0.0000
" % ((tab * 3),i))
				i += 1
				file.write("%s*MESH_TVERT %d 0.0000 0.0000 0.0000
" % ((tab * 3),i))
				i += 1
			else:   #assume 4 vertices
				file.write("%s*MESH_TVERT %d 0.0000 1.0000 0.0000
" % ((tab * 3),i))
				i += 1
				file.write("%s*MESH_TVERT %d 1.0000 1.0000 0.0000
" % ((tab * 3),i))
				i += 1
				file.write("%s*MESH_TVERT %d 0.0000 0.0000 0.0000
" % ((tab * 3),i))
				i += 1
				file.write("%s*MESH_TVERT %d 0.0000 1.0000 0.0000
" % ((tab * 3),i))
				i += 1
				file.write("%s*MESH_TVERT %d 1.0000 1.0000 0.0000
" % ((tab * 3),i))
				i += 1
				file.write("%s*MESH_TVERT %d 0.0000 0.0000 0.0000
" % ((tab * 3),i))
				i += 1
	file.write("%s}
" % ((tab * 2)))
	file.write("%s*MESH_NUMTFACES %d
" % ((tab * 2), tris))
	file.write("%s*MESH_TFACELIST {
" % (tab * 2))
	vno = 0
	fn = 0
	for x in range(len(eachmesh.faces)):
		#changed eachmesh.faces[x].uv to eachmesh.faces[x].v so that faces with no
		#uv texture data will be output anyway
		if len(eachmesh.faces[x].v) == 3:
			file.write("%s*MESH_TFACE %d %d %d %d
" % ((tab * 3),fn, vno, vno+1, vno+2))
			vno += 3
			fn += 1
		if len(eachmesh.faces[x].v) == 4:
			file.write("%s*MESH_TFACE %d %d %d %d
" % ((tab * 3),fn, vno, vno+1, vno+2))
			vno += 3
			fn += 1
			file.write("%s*MESH_TFACE %d %d %d %d
" % ((tab * 3),fn, vno, vno+1, vno+2))
			vno += 3
			fn += 1
	file.write("%s}
" % ((tab * 2)))

def Addvertexcol():
	global eachmesh
	cno = 0
	mf = eachmesh.faces
	file.write("%s*MESH_NUMCVERTEX %d
" % ((tab * 2), (tris * 3)))
	file.write("%s*MESH_CVERTLIST {
" % (tab * 2))
	for x in range(len(mf)):
		if len(mf[x].v) == 3:
			c0r = float(mf[x].col[0].r)/255
			c0g = float(mf[x].col[0].g)/255
			c0b = float(mf[x].col[0].b)/255
			c1r = float(mf[x].col[1].r)/255
			c1g = float(mf[x].col[1].g)/255
			c1b = float(mf[x].col[1].b)/255
			c2r = float(mf[x].col[2].r)/255
			c2g = float(mf[x].col[2].g)/255
			c2b = float(mf[x].col[2].b)/255
			file.write("%s*MESH_VERTCOL %d %f %f %f
" % ((tab * 3),cno,c0r,c0g,c0b))
			cno += 1
			file.write("%s*MESH_VERTCOL %d %f %f %f
" % ((tab * 3),cno,c1r,c1g,c1b))
			cno += 1
			file.write("%s*MESH_VERTCOL %d %f %f %f
" % ((tab * 3),cno,c2r,c2g,c2b))
			cno += 1
		if len(mf[x].v) == 4:
			c0r = float(mf[x].col[0].r)/255
			c0g = float(mf[x].col[0].g)/255
			c0b = float(mf[x].col[0].b)/255
			c1r = float(mf[x].col[1].r)/255
			c1g = float(mf[x].col[1].g)/255
			c1b = float(mf[x].col[1].b)/255
			c2r = float(mf[x].col[2].r)/255
			c2g = float(mf[x].col[2].g)/255
			c2b = float(mf[x].col[2].b)/255
			c3r = float(mf[x].col[3].r)/255
			c3g = float(mf[x].col[3].g)/255
			c3b = float(mf[x].col[3].b)/255
			file.write("%s*MESH_VERTCOL %d %f %f %f
" % ((tab * 3),cno,c0r,c0g,c0b))
			cno += 1
			file.write("%s*MESH_VERTCOL %d %f %f %f
" % ((tab * 3),cno,c1r,c1g,c1b))
			cno += 1
			file.write("%s*MESH_VERTCOL %d %f %f %f
" % ((tab * 3),cno,c2r,c2g,c2b))
			cno += 1
			file.write("%s*MESH_VERTCOL %d %f %f %f
" % ((tab * 3),cno,c2r,c2g,c2b))
			cno += 1
			file.write("%s*MESH_VERTCOL %d %f %f %f
" % ((tab * 3),cno,c3r,c3g,c3b))
			cno += 1
			file.write("%s*MESH_VERTCOL %d %f %f %f
" % ((tab * 3),cno,c0r,c0g,c0b))
			cno += 1
			
	file.write("%s}
" % ((tab * 2)))

	fn = 0
	vno = 0
	file.write("%s*MESH_NUMCVFACES %d
" % ((tab * 2), tris))
	file.write("%s*MESH_CFACELIST {
" % (tab * 2))
	for x in range(len(eachmesh.faces)):
		if len(eachmesh.faces[x].v) == 3:
			file.write("%s*MESH_CFACE %d %d %d %d
" % ((tab * 3),fn, vno, vno+1, vno+2))
			vno += 3
			fn += 1
		if len(eachmesh.faces[x].v) == 4:
			file.write("%s*MESH_CFACE %d %d %d %d
" % ((tab * 3),fn, vno, vno+1, vno+2))
			vno += 3
			fn += 1
			file.write("%s*MESH_CFACE %d %d %d %d
" % ((tab * 3),fn, vno, vno+1, vno+2))
			vno += 3
			fn += 1
	file.write("%s}
" % ((tab * 2)))

def Addnormals():
	global eachmesh
	file.write("%s*MESH_NORMALS{
" % (tab * 2))
	mf = eachmesh.faces
	mv = eachmesh.verts
	fn = 0
	for y in range(len(mf)):
		if len(eachmesh.faces[y].v) == 3:
			vA = mf[y].v[0].index
			RGBA = mv[vA].no
			vB = mf[y].v[1].index
			RGBB = mv[vB].no
			vC = mf[y].v[2].index
			RGBC = mv[vC].no
			file.write("%s*MESH_FACENORMAL %d 0.0000 0.0000 0.0000
" % ((tab * 3), fn)) #Dummy value
			file.write("%s*MESH_VERTEXNORMAL %d %f %f %f
" % ((tab * 4), vA, RGBA[0], RGBA[1], RGBA[2]))
			file.write("%s*MESH_VERTEXNORMAL %d %f %f %f
" % ((tab * 4), vB, RGBB[0], RGBB[1], RGBB[2]))
			file.write("%s*MESH_VERTEXNORMAL %d %f %f %f
" % ((tab * 4), vC, RGBC[0], RGBC[1], RGBC[2]))
			fn += 1
		if len(eachmesh.faces[y].v) == 4:
			vA = mf[y].v[0].index
			RGBA = mv[vA].no
			vB = mf[y].v[1].index
			RGBB = mv[vB].no
			vC = mf[y].v[2].index
			RGBC = mv[vC].no
			vD = mf[y].v[3].index
			RGBD = mv[vD].no
			file.write("%s*MESH_FACENORMAL %d 0.0000 0.0000 0.0000
" % ((tab * 3), fn)) #Dummy value
			file.write("%s*MESH_VERTEXNORMAL %d %f %f %f
" % ((tab * 4), vA, RGBA[0], RGBA[1], RGBA[2]))
			file.write("%s*MESH_VERTEXNORMAL %d %f %f %f
" % ((tab * 4), vB, RGBB[0], RGBB[1], RGBB[2]))
			file.write("%s*MESH_VERTEXNORMAL %d %f %f %f
" % ((tab * 4), vC, RGBC[0], RGBC[1], RGBC[2]))
			fn += 1
			file.write("%s*MESH_FACENORMAL %d 0.0000 0.0000 0.0000
" % ((tab * 3), fn)) #Dummy value
			file.write("%s*MESH_VERTEXNORMAL %d %f %f %f
" % ((tab * 4), vC, RGBC[0], RGBC[1], RGBC[2]))
			file.write("%s*MESH_VERTEXNORMAL %d %f %f %f
" % ((tab * 4), vD, RGBD[0], RGBD[1], RGBD[2]))
			file.write("%s*MESH_VERTEXNORMAL %d %f %f %f
" % ((tab * 4), vA, RGBA[0], RGBA[1], RGBA[2]))
			fn += 1
	file.write("%s}
" % ((tab * 2)))
	file.write("%s}
" % (tab))


def Addfooter():
	file.write("%s*PROP_MOTIONBLUR 0
" % (tab))
	file.write("%s*PROP_CASTSHADOW 1
" % (tab))
	file.write("%s*PROP_RECVSHADOW 1
" % (tab))
	file.write("%s*MATERIAL_REF 0
" % (tab))
	file.write("}
")



def DoExport():
	global eachmesh, sgtxt, sglist
#Set up export directory
	if os.path.exists("C:\\ase"):
		print "DIR: C:\\ase does exist"
	else:
		os.mkdir("C:\\ase")
		print "Ok I've made DIR: C:\\ase"
	
	os.chdir("C:\\ase")
	
	#Get the object data
	allobjects = Blender.Object.GetSelected()
	
	for each in allobjects:
		print "   %s" % str(type(each.data))
		if type(each.data) == Blender.Types.NMeshType:
			eachmesh = NMesh.GetRaw(each.data.name)
			tempfilename = str(eachmesh.name)
			filename = string.replace(tempfilename,".","_") + ".ase"
			print "Exporting %s" % filename
			file = open(filename, "w")
####SmoothingGroupCODE Start
			sgtxtname = each.name + "SmthGrps"
			txtobjs = Text.Get()
			txtnames = []
			sglist = []
			for y in txtobjs:
				txtnames.append(y.getName())
			if txtnames.count(sgtxtname) == 0:
				#create a SGlist
				sglist = sglist + ['0']*len(eachmesh.faces)
			else:
				#load an existing SGlist
				sgtxt = Text.Get(sgtxtname)
				sglist = sgtxt.asLines()
				#verify that the list has the same number of items as faces
				if len(sglist) < len(eachmesh.faces):
					print("ERROR! Total faces %d not equal to nbr faces in smthgrp list %d
" % (len(eachmesh.faces),len(sglist)))
					print("Verify the smoothing group list ",sgtxtname)
					sglist = sglist + ['0']*(len(eachmesh.faces) - len(sglist))
####SmoothingGroupCODE END
			#Calculate no. of tris (quad = 2x tris)
			tris = 0
			for y in range(len(eachmesh.faces)):
				if len(eachmesh.faces[y].v) == 3:tris = tris +1
				if len(eachmesh.faces[y].v) == 4:tris = tris +2      
			Addheader()
			Addmaterials()
			Addgeobheader()
			Addvertexdata()
			Addfacelist()
			AddUVdata()
			# New test - I hope it works!!
			if eachmesh.faces[0].col:
				print "Has VCs"
				Addvertexcol()
			Addnormals()
			Addfooter()
			file.close
		#end if block 
	#end for block
	sys.stdout.flush()


def GetSmGrpList():
	global eachmesh, sgtxt, sglist
	allobjs = Blender.Object.GetSelected()
	for each in allobjs:
		if type(each.data) == Blender.Types.NMeshType:
		#get smthgrp text object name
			eachmesh = Blender.NMesh.GetRaw(each.data.name)
			sgtxtname = eachmesh.name + "SmthGrps"
			txtobjs = Text.Get()
			txtnames = []
			for y in txtobjs:
				txtnames.append(y.getName())
		sglist = []
		if txtnames.count(sgtxtname) == 0:
			#create a SGlist
			sgtxt = Text.New(sgtxtname)
			sglist = sglist + ['0']*len(eachmesh.faces)
		else:
			#load an existing SGlist
			sgtxt = Text.Get(sgtxtname)
			sglist = sgtxt.asLines()
			#verify that the list has the same number of items as faces
			if len(sglist) != len(eachmesh.faces):
				print("Warning. Total faces %d not equal to nbr faces in smthgrp list %d
" % (len(eachmesh.faces),len(sglist)))
				print("  smthgrp list length fixed")
				if len(sglist) < len(eachmesh.faces):
					sglist = sglist + ['0']*(len(eachmesh.faces) - len(sglist))
				else:
					shrtnList = len(eachmesh.faces)
					sglist = sglist[0:shrtnList]


def SelectSmthGrp():
	global SmthGrpNm, eachmesh, sgtxt
	for y in range(len(eachmesh.faces)):
		if sglist[y] == str(SmthGrpNm):
			eachmesh.faces[y].flag = eachmesh.faces[y].flag | Blender.NMesh.FaceFlags.SELECT
		else:
			eachmesh.faces[y].flag = (eachmesh.faces[y].flag &  ~Blender.NMesh.FaceFlags.SELECT)
	eachmesh.update()

def AssignSmthGrp():
	global SmthGrpNm, eachmesh, sgtxt, sglist
	#loop through the list of faces,
	for y in range(len(eachmesh.faces)):
	#if selected then set the smoothing group in the list
		if eachmesh.faces[y].flag & Blender.NMesh.FaceFlags.SELECT:
			sglist[y] = str(SmthGrpNm)
	#Clear the text object and write the smoothing group list to it
	sgtxt.clear()
	for y in range(len(sglist)):
		sgtxt.write(sglist[y] + "
")


def gui():
	global eachmesh, SmthGrpNm, SmthGrpBtn

	Blender.BGL.glClearColor(0.6, 0.6, 0.6, 0.0)
	Blender.BGL.glClear(Blender.BGL.GL_COLOR_BUFFER_BIT)
	BGL.glRasterPos2i(35, 150)
	Draw.Text("ASE Export Script for Unreal Ed 3")
	SmthGrpBtn = Blender.Draw.Number("Smoothing Group Number", 2, 20, 120, 220, 20, SmthGrpNm, 0, 31)
	Blender.Draw.Button("Select Faces in Smoothing Group", 4, 20, 95, 220, 20)
	Blender.Draw.Button("Assign Selected Faces to SmthGrp", 5, 20, 70, 220, 20)
	Blender.Draw.Button("Export to ASE", 1, 70, 45, 120, 20)
	Blender.Draw.Button("Exit (or 'Esc')", 3, 70, 20, 120, 20)


def event(evt, mode):
	if evt == Blender.Draw.ESCKEY: Blender.Draw.Exit()

def bevent(evt):
	global SmthGrpNm, SmthGrpBtn
	if (evt == 1):
		DoExport()
	elif (evt == 2):
		SmthGrpNm = SmthGrpBtn.val
	elif (evt == 3):
		Blender.Draw.Exit()
	elif (evt == 4):
		allobjs = Blender.Object.GetSelected()
		for each in allobjs:
			if type(each.data) == Blender.Types.NMeshType:
				eachmesh = Blender.NMesh.GetRaw(each.data.name)
				GetSmGrpList()
				SelectSmthGrp()
	elif (evt == 5):
		allobjs = Blender.Object.GetSelected()
		for each in allobjs:
			if type(each.data) == Blender.Types.NMeshType:
				eachmesh = Blender.NMesh.GetRaw(each.data.name)
				GetSmGrpList()
				AssignSmthGrp()
	else:
		Blender.Draw.Register(gui,event,bevent)


#Default values
tab = "	"
SmthGrpNm = 0
#Register GUI
Blender.Draw.Register(gui,event,bevent)


well, I guess I should post my progress for the day as well then

UnrealAnimDataStructs.py

"""
/*======================================================================

    Animation file data structures 
    Copyright 1997-2003 Epic Games, Inc. All Rights Reserved.

========================================================================*/

ported to python, may 2004
[copyright permission issues aside]

type        expected size [alone]
_WORD           2
DWORD           4
INT             4
BYTE            1
FLOAT           4
ANSICHAR        1

"""

import struct

def sizeof(item):
	if hasattr(item, "fmt"):
		return struct.calcsize(item.fmt)
	else:
		return 0

# allows a format like 30c to take a single string
# no spaces before the c are allowed however
def mypack(fmt, *args):
	print "mypack(\"", fmt, "\", *", args, ")"
	indx = 0
	elem = -1
	newargs = (fmt,) # a single element tuple, to become full argument list
	while indx < len(fmt):
		#print fmt[indx]
		if not fmt[indx].isalpha():
			indx += 1
		else:
			elem += 1
			#print elem
			if fmt[indx] == "c" and indx > 0 and fmt[indx-1].isdigit() and type(args[elem]) == str:
				# find the leftmost numeric character starting at indx-1
				indx2 = indx -1
				while indx2 > 0 and fmt[indx2-1].isdigit(): indx2 -= 1
				numchars = int(fmt[indx2:indx])
				# turn args[elem] into a tuple of length numchars of strings
				strtuple = ()
				for char in args[elem]: strtuple += (char,)
				# shorten if necescary by truncation
				strtuple = strtuple[:numchars]
				# lengthen if necescary
				extendby = numchars - len(strtuple)
				if extendby: strtuple += extendby * ("\0",)
				newargs += strtuple
			else:
				newargs += (args[elem],)
			indx += 1
	#print newargs
	return struct.pack(*newargs)

# same addition as mypack: allows a number followed by a c to be converted
# into a string . the string may end up padded to field length
def myunpack(fmt, *args_old):
	result = struct.unpack(fmt, *args_old)
	# this is actually much easier to implement that mypack
	print "myunpack(\"", fmt, "\", *", args_old, ")"
	indx = 0
	elem = -1
	newresult = () # empty tuple, to be filled with result sort of
	while indx < len(fmt):
		#print fmt[indx]
		if not fmt[indx].isalpha():
			indx += 1
		else:
			elem += 1
			#print elem
			if fmt[indx] == "c" and indx > 0 and fmt[indx-1].isdigit() and type(result[elem]) == str:
				# find the leftmost numeric character starting at indx-1
				indx2 = indx -1
				while indx2 > 0 and fmt[indx2-1].isdigit(): indx2 -= 1
				numchars = int(fmt[indx2:indx])
				resultstr = ""
				i = 0
				while i < numchars:
					resultstr += result[elem+i]
					i+= 1
				newresult += (resultstr,)
				elem += 1 - numchars
			else:
				newresult += (result[elem],)
			indx += 1
	return newresult

# Stub to outline FQuat, which we won't fully define here...
class FQuat:
	"""
	FLOAT X,Y,Z,W;
	"""
	fmt = "ffff"
	def __init__(self):
		self.X = 0.0
		self.Y = 0.0
		self.Z = 0.0
		self.W = 0.0
	def set(self, values):
		(self.X, self.Y, self.Z, self.W) = values
	def get(self):
		return (self.X, self.Y, self.Z, self.W)
	def toString(self):
		return mypack(self.fmt, *self.get() )
	def fromString(self, str):
		self.set(myunpack(self.fmt, str))

# 
class FVector:
	"""
	FLOAT X,Y,Z;
	"""
	fmt = "fff"
	def __init__(self):
		self.X = 0.0
		self.Y = 0.0
		self.Z = 0.0
	def set(self, values):
		(self.X, self.Y, self.Z) = values
	def get(self):
		return (self.X, self.Y, self.Z)
	def toString(self):
		return mypack(self.fmt, *self.get() )
	def fromString(self, str):
		self.set(myunpack(self.fmt, str))



# A bone: an orientation, and a position, all relative to their parent.
class VJointPos:
	"""
	FQuat       Orientation;  //
	FVector        Position;     //  
	
	FLOAT       Length;       //  For collision testing / debugging drawing.  (unused)
	FLOAT       XSize;
	FLOAT       YSize;
	FLOAT       ZSize;
	"""
	fmt = FQuat.fmt + FVector.fmt + "ffff"
	def __init__(self):
		self.Orientation = FQuat()
		self.Position = FVector()
		self.Length = 0.0
		self.XSize = 0.0
		self.YSize = 0.0
		self.ZSize = 0.0
	def set(self, values):
		lenquat = len(self.Orientation.get())
		len2 = lenquat + len(self.Orientation.get())-1
		self.Orientation.set(values[:lenquat])
		self.Position.set(values[lenquat:len2])
		(self.Length, self.XSize, self.YSize, self.ZSize) = values[len2:]
	def get(self):
		return self.Orientation.get() + self.Position.get() + \
			(self.Length, self.XSize, self.YSize, self.ZSize)
	def toString(self):
		return mypack(self.fmt, *self.get() )
	def fromString(self, str):
		self.set(myunpack(self.fmt, str))

# Binary animation info format - used to organize raw animation keys into FAnimSeqs on rebuild
# Similar to MotionChunkDigestInfo..
class AnimInfoBinary:
	"""
	ANSICHAR Name[64];     // Animation's name
	ANSICHAR Group[64];    // Animation's group name    
	
	INT TotalBones;           // TotalBones * NumRawFrames is number of animation keys to digest.
	
	INT RootInclude;          // 0 none 1 included     (unused)
	INT KeyCompressionStyle;  // Reserved: variants in tradeoffs for compression.
	INT KeyQuotum;            // Max key quotum for compression    
	FLOAT KeyReduction;       // desired 
	FLOAT TrackTime;          // explicit - can be overridden by the animation rate
	FLOAT AnimRate;           // frames per second.
	INT StartBone;            // - Reserved: for partial animations (unused)
	INT FirstRawFrame;        //
	INT NumRawFrames;         // NumRawFrames and AnimRate dictate tracktime...
	"""
	fmt = "64c 64c i iii fff iii"
	def __init__(self):
		self.Name = ""
		self.Group = ""
		self.TotalBones = 0
		self.RootInclude = 0
		self.KeyCompressionStyle = 0
		self.KeyQuotum = 0
		self.KeyReduction = 0.0
		self.TrackTime = 0.0
		self.AnimRate = 0.0
		self.StartBone = 0
		self.FirstRawFrame = 0
		self.NumRawFrames = 0
	def set(self, values):
		(self.Name, self.Group, self.TotalBones, self.RootInclude, \
			self.KeyCompressionStyle, self.KeyQuotum, self.KeyReduction, \
			self.TrackTime, self.AnimRate, self.StartBone, self.FirstRawFrame, \
			self.NumRawFrames) = values
	def get(self):
		return (self.Name, self.Group, self.TotalBones, self.RootInclude, \
			self.KeyCompressionStyle, self.KeyQuotum, self.KeyReduction, \
			self.TrackTime, self.AnimRate, self.StartBone, self.FirstRawFrame, \
			self.NumRawFrames)
	def toString(self):
		return mypack(self.fmt, *self.get() )
	def fromString(self, str):
		self.set(myunpack(self.fmt, str))

# File header structure. 
class VChunkHeader:
	"""
	ANSICHAR    ChunkID[20];  // String ID of up to 19 chars (usually zero-terminated)
	INT            TypeFlag;     // Flags/reserved
	   INT         DataSize;     // Size per struct following;
	INT         DataCount;    // Number of structs/
	"""
	fmt = "20c iii"
	def __init__(self):
		self.ChunkID = ""
		self.typeflag = 1999801 # what the default probably should be
		self.DataSize = 0
		self.DataCount = 0
	def set(self, values):
		(self.ChunkID, self.typeflag, self.DataSize, self.DataCount) = values
	def get(self):
		return (self.ChunkID, self.typeflag, self.DataSize, self.DataCount)
	def toString(self):
		return mypack(self.fmt, *self.get() )
	def fromString(self, str):
		self.set(myunpack(self.fmt, str))


# Raw data material.
class VMaterial:
	"""
	ANSICHAR    MaterialName[64];
	INT            TextureIndex;  // Texture index ('multiskin index')
	DWORD        PolyFlags;     // ALL poly's with THIS material will have this flag.
	INT            AuxMaterial;   // Reserved: index into another material, eg. detailtexture/shininess/whatever.
	DWORD        AuxFlags;      // Reserved: auxiliary flags 
	INT            LodBias;       // Material-specific lod bias (unused)
	INT            LodStyle;      // Material-specific lod style (unused)
	"""
	fmt = "64c iIiIii"
	def __init__(self):
		self.MaterialName = ""
		self.TextureIndex = 0
		self.PolyFlags = 0
		self.AuxMaterial = 0
		self.AuxFlags = 0
		self.LodBias = 0
		self.LodStyle = 0
	def set(self, values):
		(self.MaterialName, self.TextureIndex, self.PolyFlags, self.AuxMaterial, self.AuxFlags, self.LodBias, self.LodStyle) = values
	def get(self):
		return (self.MaterialName, self.TextureIndex, self.PolyFlags, self.AuxMaterial, self.AuxFlags, self.LodBias, self.LodStyle)
	def toString(self):
		return mypack(self.fmt, *self.get() )
	def fromString(self, str):
		self.set(myunpack(self.fmt, str))

# Raw data bone.
class VBone:
	"""
	ANSICHAR    Name[64];     //
	DWORD        Flags;        // Reserved.
	INT         NumChildren;  // Children  (not used.)
	INT         ParentIndex;  // 0/NULL if this is the root bone.  
	VJointPos   BonePos;      // Reference position.
	"""
	fmt = "64c Iii" + VJointPos.fmt
	def __init__(self):
		self.Name = ""
		self.Flags = 0
		self.NumChildren = 0
		self.ParentIndex = 0
		self.BonePos = VJointPos()
	def set(self, values):
		(self.Name, self.Flags, self.NumChildren, self.ParentIndex) = values[:4]
		self.BonePos.set(values[4:])
	def get(self):
		return (self.Name, self.Flags, self.NumChildren, self.ParentIndex)+self.BonePos.get()
	def toString(self):
		return mypack(self.fmt, *self.get() )
	def fromString(self, str):
		self.set(myunpack(self.fmt, str))


# Binary bone format to deal with raw animations as generated by various exporters.
class FNamedBoneBinary:
	"""
	ANSICHAR   Name[64];    // Bone's name
	DWORD      Flags;        // reserved
	INT        NumChildren; //
	INT           ParentIndex;    // 0/NULL if this is the root bone.  
	VJointPos  BonePos;        //
	"""
	fmt = "64c Iii" + VJointPos.fmt
	def __init__(self):
		self.Name = ""
		self.Flags = 0
		self.NumChildren = 0
		self.ParentIndex = 0
		self.BonePos = VJointPos()
	def set(self, values):
		(self.Name, self.Flags, self.NumChildren, self.ParentIndex) = values[:4]
		self.BonePos.set(values[4:])
	def get(self):
		return (self.Name, self.Flags, self.NumChildren, self.ParentIndex) + self.BonePos.get()
	def toString(self):
		return mypack(self.fmt, *self.get() )
	def fromString(self, str):
		self.set(myunpack(self.fmt, str))


# Raw data bone influence.
class VRawBoneInfluence: # Just weight, vertex, and Bone, sorted later.
	"""
	FLOAT Weight;
	INT   PointIndex;
	INT   BoneIndex;
	"""
	fmt = "f i i"
	def __init__(self):
		self.Weight = 0.0
		self.PointIndex = 0
		self.BoneIndex = 0
	def set(self, values):
		(self.Weight, self.PointIndex, self.BoneIndex) = values
	def get(self):
		return (self.Weight, self.PointIndex, self.BoneIndex)
	def toString(self):
		return mypack(self.fmt, *self.get() )
	def fromString(self, str):
		self.set(myunpack(self.fmt, str))


# An animation key.
class VQuatAnimKey:
	"""
	FVector        Position;           // Relative to parent.
	FQuat       Orientation;        // Relative to parent.
	FLOAT       Time;                // The duration until the next key (end key wraps to first...)
	"""
	fmt = ""+FVector.fmt + FQuat.fmt +"f"
	def __init__(self):
		self.Position = FVector()
		self.Orientation = FQuat()
		self.Time = 0.0
	def set(self, values):
		lenPos = len(self.Position.get())
		len2 = lenPos + len(self.Orientation.get())
		self.Position.set(values[:lenPos])
		self.Orientation.set(values[lenPos:len2])
		(self.Time,) = values[len2:]
	def get(self):
		return self.Position.get() + self.Orientation.get() + (self.Time,)
	def toString(self):
		return mypack(self.fmt, *self.get() )
	def fromString(self, str):
		self.set(myunpack(self.fmt, str))


# Vertex with texturing info, akin to Hoppe's 'Wedge' concept - import only.
class VVertex:
	"""
	_WORD    PointIndex;     // Index into the 3d point table.
	FLOAT   U,V;         // Texture U, V coordinates.
	BYTE    MatIndex;    // At runtime, this one will be implied by the face that's pointing to us.
	BYTE    Reserved;    // Top secret.
	"""
	fmt = "hxx f f cx cx"
	def __init__(self):
		self.PointIndex = 0
		self.U = 0.0
		self.V = 0.0
		self.MatIndex = '\0'
		self.Reserved = '\0'
	def set(self, values):
		(self.PointIndex, self.U, self.V, self.MatIndex, self.Reserved) = values
	def get(self):
		return (self.PointIndex, self.U, self.V, self.MatIndex, self.Reserved)
	def toString(self):
		return mypack(self.fmt, *self.get() )
	def fromString(self, str):
		self.set(myunpack(self.fmt, str))

a = VVertex()
b = VVertex()
b.set(a.get())


# Points: regular FVectors 
class VPoint:
	"""
	FVector            Point;
	"""
	fmt = FVector.fmt
	def __init__(self):
		self.Point = FVector()
	def set(self, values):
		self.Point.set(values)
	def get(self):
		return self.Point.get()
	def toString(self):
		return mypack(self.fmt, *self.get() )
	def fromString(self, str):
		self.set(myunpack(self.fmt, str))

# Textured triangle.
class VTriangle:
	"""
	_WORD   WedgeIndex[3];     // Point to three vertices in the vertex list.
	BYTE    MatIndex;         // Materials can be anything.
	BYTE    AuxMatIndex;     // Second material (unused).
	DWORD   SmoothingGroups; // 32-bit flag for smoothing groups.
	"""
	fmt = "H cx cx I"
	def __init__(self):
		self.WedgeIndex = [0,0,0]
		self.MatIndex = 0
		self.AuxMatIndex = 0
		self.SmoothingGroups = 0
	def set(self, values):
		(self.WedgeIndex[0],self.WedgeIndex[1],self.WedgeIndex[2], \
			self.MatIndex, self.AuxMatIndex, self.SmoothingGroups) = values
	def get(self):
		return (self.WedgeIndex[0],self.WedgeIndex[1],self.WedgeIndex[2], \
			self.MatIndex, self.AuxMatIndex, self.SmoothingGroups)
	def toString(self):
		return mypack(self.fmt, *self.get() )
	def fromString(self, str):
		self.set(myunpack(self.fmt, str))

def test():
	""" certify the size of all classes """
	print "type        should be  is"
	print "VChunkHeader      32 ", sizeof(VChunkHeader)
	print "VPoint            12 ", sizeof(VPoint)
	print "FQuat             16 ", sizeof(FQuat)
	print "FVector           12 ", sizeof(FVector)
	print "VVertex           16 ", sizeof(VVertex)
	print "VTriangle         12 ", sizeof(VTriangle)
	print "VMaterial         88 ", sizeof(VMaterial)
	print "VBone             120", sizeof(VBone)
	print "VJointPos         44 ", sizeof(VJointPos)
	print "VRawBoneInfluence 12 ", sizeof(VRawBoneInfluence)
	print ""
	print "AnimInfoBinary    168", sizeof(AnimInfoBinary)
	print "FNamedBoneBinary  120", sizeof(FNamedBoneBinary)
	print "VQuatAnimKey      32 ", sizeof(VQuatAnimKey)

if __name__ == "__main__":
	test()

so, I haven’t fully tested this yet. The struct sizes are right though. I am currently porting my cube writing function to python, then I will work on a mesh export [perhaps I’ll leave the armature stuff to someone else?]

if this reply starts page 2 I will edit it to include my updated UnrealAnimStructs.py and cubeExport.py (which I have verified to produce output the same in all expected respects [I changed a string literal in it, and it saves as a different filename])

… or not, is phpBB configured for 15 posts per page? I think this is post 11 or 12 and I don’t want to spam too much to move to the next page

"""
UnrealAnimDataStructs.py


/*======================================================================

    Animation file data structures 
    Copyright 1997-2003 Epic Games, Inc. All Rights Reserved.

========================================================================*/

ported to python, may 2004
[copyright permission issues aside]

type        expected size [alone]
_WORD           2
DWORD           4
INT             4
BYTE            1
FLOAT           4
ANSICHAR        1

"""

import struct

def sizeof(item):
	if hasattr(item, "fmt"):
		return struct.calcsize(item.fmt)
	else:
		return 0

# allows a format like 30c to take a single string
# no spaces before the c are allowed however
# doesn't allow non-characters to use formats preceeded by numbers
def mypack(fmt, *args):
	print "mypack(\"", fmt, "\", *", args, ")"
	indx = 0
	elem = -1
	newargs = (fmt,) # a single element tuple, to become full argument list
	while indx < len(fmt):
		#print fmt[indx]
		if fmt[indx] == "x" or not fmt[indx].isalpha():
			indx += 1
		else:
			elem += 1
			#print elem
			if fmt[indx] == "c" and indx > 0 and fmt[indx-1].isdigit() and type(args[elem]) == str:
				# find the leftmost numeric character starting at indx-1
				indx2 = indx -1
				while indx2 > 0 and fmt[indx2-1].isdigit(): indx2 -= 1
				numchars = int(fmt[indx2:indx])
				# turn args[elem] into a tuple of length numchars of strings
				strtuple = ()
				for char in args[elem]: strtuple += (char,)
				# shorten if necescary by truncation
				strtuple = strtuple[:numchars]
				# lengthen if necescary
				extendby = numchars - len(strtuple)
				if extendby: strtuple += extendby * ("\0",)
				newargs += strtuple
			else:
				newargs += (args[elem],)
			indx += 1
	#print newargs
	return struct.pack(*newargs)

# same addition as mypack: allows a number followed by a c to be converted
# into a string . the string may end up padded to field length
def myunpack(fmt, *args_old):
	result = struct.unpack(fmt, *args_old)
	# this is actually much easier to implement that mypack
	print "myunpack(\"", fmt, "\", *", args_old, ")"
	indx = 0
	elem = -1
	newresult = () # empty tuple, to be filled with result sort of
	while indx < len(fmt):
		#print fmt[indx]
		if not fmt[indx].isalpha():
			indx += 1
		else:
			elem += 1
			#print elem
			if fmt[indx] == "c" and indx > 0 and fmt[indx-1].isdigit() and type(result[elem]) == str:
				# find the leftmost numeric character starting at indx-1
				indx2 = indx -1
				while indx2 > 0 and fmt[indx2-1].isdigit(): indx2 -= 1
				numchars = int(fmt[indx2:indx])
				resultstr = ""
				i = 0
				while i < numchars:
					resultstr += result[elem+i]
					i+= 1
				newresult += (resultstr,)
				elem += 1 - numchars
			else:
				newresult += (result[elem],)
			indx += 1
	return newresult

# Stub to outline FQuat, which we won't fully define here...
class FQuat:
	"""
	FLOAT X,Y,Z,W;
	"""
	fmt = "ffff"
	def __init__(self):
		self.X = 0.0
		self.Y = 0.0
		self.Z = 0.0
		self.W = 0.0
	def set(self, values):
		(self.X, self.Y, self.Z, self.W) = values
	def get(self):
		return (self.X, self.Y, self.Z, self.W)
	def toString(self):
		return mypack(self.fmt, *self.get() )
	def fromString(self, str):
		self.set(myunpack(self.fmt, str))

# 
class FVector:
	"""
	FLOAT X,Y,Z;
	"""
	fmt = "fff"
	def __init__(self):
		self.X = 0.0
		self.Y = 0.0
		self.Z = 0.0
	def set(self, values):
		(self.X, self.Y, self.Z) = values
	def get(self):
		return (self.X, self.Y, self.Z)
	def toString(self):
		return mypack(self.fmt, *self.get() )
	def fromString(self, str):
		self.set(myunpack(self.fmt, str))



# A bone: an orientation, and a position, all relative to their parent.
class VJointPos:
	"""
	FQuat       Orientation;  //
	FVector        Position;     //  
	
	FLOAT       Length;       //  For collision testing / debugging drawing.  (unused)
	FLOAT       XSize;
	FLOAT       YSize;
	FLOAT       ZSize;
	"""
	fmt = FQuat.fmt + FVector.fmt + "ffff"
	def __init__(self):
		self.Orientation = FQuat()
		self.Position = FVector()
		self.Length = 0.0
		self.XSize = 0.0
		self.YSize = 0.0
		self.ZSize = 0.0
	def set(self, values):
		lenquat = len(self.Orientation.get())
		len2 = lenquat + len(self.Orientation.get())-1
		self.Orientation.set(values[:lenquat])
		self.Position.set(values[lenquat:len2])
		(self.Length, self.XSize, self.YSize, self.ZSize) = values[len2:]
	def get(self):
		return self.Orientation.get() + self.Position.get() + \
			(self.Length, self.XSize, self.YSize, self.ZSize)
	def toString(self):
		return mypack(self.fmt, *self.get() )
	def fromString(self, str):
		self.set(myunpack(self.fmt, str))

# Binary animation info format - used to organize raw animation keys into FAnimSeqs on rebuild
# Similar to MotionChunkDigestInfo..
class AnimInfoBinary:
	"""
	ANSICHAR Name[64];     // Animation's name
	ANSICHAR Group[64];    // Animation's group name    
	
	INT TotalBones;           // TotalBones * NumRawFrames is number of animation keys to digest.
	
	INT RootInclude;          // 0 none 1 included     (unused)
	INT KeyCompressionStyle;  // Reserved: variants in tradeoffs for compression.
	INT KeyQuotum;            // Max key quotum for compression    
	FLOAT KeyReduction;       // desired 
	FLOAT TrackTime;          // explicit - can be overridden by the animation rate
	FLOAT AnimRate;           // frames per second.
	INT StartBone;            // - Reserved: for partial animations (unused)
	INT FirstRawFrame;        //
	INT NumRawFrames;         // NumRawFrames and AnimRate dictate tracktime...
	"""
	fmt = "64c 64c i iii fff iii"
	def __init__(self):
		self.Name = ""
		self.Group = ""
		self.TotalBones = 0
		self.RootInclude = 0
		self.KeyCompressionStyle = 0
		self.KeyQuotum = 0
		self.KeyReduction = 0.0
		self.TrackTime = 0.0
		self.AnimRate = 0.0
		self.StartBone = 0
		self.FirstRawFrame = 0
		self.NumRawFrames = 0
	def set(self, values):
		(self.Name, self.Group, self.TotalBones, self.RootInclude, \
			self.KeyCompressionStyle, self.KeyQuotum, self.KeyReduction, \
			self.TrackTime, self.AnimRate, self.StartBone, self.FirstRawFrame, \
			self.NumRawFrames) = values
	def get(self):
		return (self.Name, self.Group, self.TotalBones, self.RootInclude, \
			self.KeyCompressionStyle, self.KeyQuotum, self.KeyReduction, \
			self.TrackTime, self.AnimRate, self.StartBone, self.FirstRawFrame, \
			self.NumRawFrames)
	def toString(self):
		return mypack(self.fmt, *self.get() )
	def fromString(self, str):
		self.set(myunpack(self.fmt, str))

# File header structure. 
class VChunkHeader:
	"""
	ANSICHAR    ChunkID[20];  // String ID of up to 19 chars (usually zero-terminated)
	INT            TypeFlag;     // Flags/reserved
	   INT         DataSize;     // Size per struct following;
	INT         DataCount;    // Number of structs/
	"""
	fmt = "20c iii"
	def __init__(self):
		self.ChunkID = ""
		self.typeflag = 1999801 # what the default probably should be
		self.DataSize = 0
		self.DataCount = 0
	def set(self, values):
		(self.ChunkID, self.typeflag, self.DataSize, self.DataCount) = values
	def get(self):
		return (self.ChunkID, self.typeflag, self.DataSize, self.DataCount)
	def toString(self):
		return mypack(self.fmt, *self.get() )
	def fromString(self, str):
		self.set(myunpack(self.fmt, str))


# Raw data material.
class VMaterial:
	"""
	ANSICHAR    MaterialName[64];
	INT            TextureIndex;  // Texture index ('multiskin index')
	DWORD        PolyFlags;     // ALL poly's with THIS material will have this flag.
	INT            AuxMaterial;   // Reserved: index into another material, eg. detailtexture/shininess/whatever.
	DWORD        AuxFlags;      // Reserved: auxiliary flags 
	INT            LodBias;       // Material-specific lod bias (unused)
	INT            LodStyle;      // Material-specific lod style (unused)
	"""
	fmt = "64c iIiIii"
	def __init__(self):
		self.MaterialName = ""
		self.TextureIndex = 0
		self.PolyFlags = 0
		self.AuxMaterial = 0
		self.AuxFlags = 0
		self.LodBias = 0
		self.LodStyle = 0
	def set(self, values):
		(self.MaterialName, self.TextureIndex, self.PolyFlags, self.AuxMaterial, self.AuxFlags, self.LodBias, self.LodStyle) = values
	def get(self):
		return (self.MaterialName, self.TextureIndex, self.PolyFlags, self.AuxMaterial, self.AuxFlags, self.LodBias, self.LodStyle)
	def toString(self):
		return mypack(self.fmt, *self.get() )
	def fromString(self, str):
		self.set(myunpack(self.fmt, str))

# Raw data bone.
class VBone:
	"""
	ANSICHAR    Name[64];     //
	DWORD        Flags;        // Reserved.
	INT         NumChildren;  // Children  (not used.)
	INT         ParentIndex;  // 0/NULL if this is the root bone.  
	VJointPos   BonePos;      // Reference position.
	"""
	fmt = "64c Iii" + VJointPos.fmt
	def __init__(self):
		self.Name = ""
		self.Flags = 0
		self.NumChildren = 0
		self.ParentIndex = 0
		self.BonePos = VJointPos()
	def set(self, values):
		(self.Name, self.Flags, self.NumChildren, self.ParentIndex) = values[:4]
		self.BonePos.set(values[4:])
	def get(self):
		return (self.Name, self.Flags, self.NumChildren, self.ParentIndex)+self.BonePos.get()
	def toString(self):
		return mypack(self.fmt, *self.get() )
	def fromString(self, str):
		self.set(myunpack(self.fmt, str))


# Binary bone format to deal with raw animations as generated by various exporters.
class FNamedBoneBinary:
	"""
	ANSICHAR   Name[64];    // Bone's name
	DWORD      Flags;        // reserved
	INT        NumChildren; //
	INT           ParentIndex;    // 0/NULL if this is the root bone.  
	VJointPos  BonePos;        //
	"""
	fmt = "64c Iii" + VJointPos.fmt
	def __init__(self):
		self.Name = ""
		self.Flags = 0
		self.NumChildren = 0
		self.ParentIndex = 0
		self.BonePos = VJointPos()
	def set(self, values):
		(self.Name, self.Flags, self.NumChildren, self.ParentIndex) = values[:4]
		self.BonePos.set(values[4:])
	def get(self):
		return (self.Name, self.Flags, self.NumChildren, self.ParentIndex) + self.BonePos.get()
	def toString(self):
		return mypack(self.fmt, *self.get() )
	def fromString(self, str):
		self.set(myunpack(self.fmt, str))


# Raw data bone influence.
class VRawBoneInfluence: # Just weight, vertex, and Bone, sorted later.
	"""
	FLOAT Weight;
	INT   PointIndex;
	INT   BoneIndex;
	"""
	fmt = "f i i"
	def __init__(self):
		self.Weight = 0.0
		self.PointIndex = 0
		self.BoneIndex = 0
	def set(self, values):
		(self.Weight, self.PointIndex, self.BoneIndex) = values
	def get(self):
		return (self.Weight, self.PointIndex, self.BoneIndex)
	def toString(self):
		return mypack(self.fmt, *self.get() )
	def fromString(self, str):
		self.set(myunpack(self.fmt, str))


# An animation key.
class VQuatAnimKey:
	"""
	FVector        Position;           // Relative to parent.
	FQuat       Orientation;        // Relative to parent.
	FLOAT       Time;                // The duration until the next key (end key wraps to first...)
	"""
	fmt = ""+FVector.fmt + FQuat.fmt +"f"
	def __init__(self):
		self.Position = FVector()
		self.Orientation = FQuat()
		self.Time = 0.0
	def set(self, values):
		lenPos = len(self.Position.get())
		len2 = lenPos + len(self.Orientation.get())
		self.Position.set(values[:lenPos])
		self.Orientation.set(values[lenPos:len2])
		(self.Time,) = values[len2:]
	def get(self):
		return self.Position.get() + self.Orientation.get() + (self.Time,)
	def toString(self):
		return mypack(self.fmt, *self.get() )
	def fromString(self, str):
		self.set(myunpack(self.fmt, str))


# Vertex with texturing info, akin to Hoppe's 'Wedge' concept - import only.
class VVertex:
	"""
	_WORD    PointIndex;     // Index into the 3d point table.
	FLOAT   U,V;         // Texture U, V coordinates.
	BYTE    MatIndex;    // At runtime, this one will be implied by the face that's pointing to us.
	BYTE    Reserved;    // Top secret.
	"""
	fmt = "hxx f f cx cx"
	def __init__(self):
		self.PointIndex = 0
		self.U = 0.0
		self.V = 0.0
		self.MatIndex = '\0'
		self.Reserved = '\0'
	def set(self, values):
		(self.PointIndex, self.U, self.V, self.MatIndex, self.Reserved) = values
	def get(self):
		return (self.PointIndex, self.U, self.V, self.MatIndex, self.Reserved)
	def toString(self):
		return mypack(self.fmt, *self.get() )
	def fromString(self, str):
		self.set(myunpack(self.fmt, str))

# Points: regular FVectors 
class VPoint:
	"""
	FVector            Point;
	"""
	fmt = FVector.fmt
	def __init__(self):
		self.Point = FVector()
	def set(self, values):
		self.Point.set(values)
	def get(self):
		return self.Point.get()
	def toString(self):
		return mypack(self.fmt, *self.get() )
	def fromString(self, str):
		self.set(myunpack(self.fmt, str))

# Textured triangle.
class VTriangle:
	"""
	_WORD   WedgeIndex[3];     // Point to three vertices in the vertex list.
	BYTE    MatIndex;         // Materials can be anything.
	BYTE    AuxMatIndex;     // Second material (unused).
	DWORD   SmoothingGroups; // 32-bit flag for smoothing groups.
	"""
	fmt = "h h h c c I"
	def __init__(self):
		self.WedgeIndex = [0,0,0]
		self.MatIndex = '\0'
		self.AuxMatIndex = '\0'
		self.SmoothingGroups = 0
	def set(self, values):
		(self.WedgeIndex[0],self.WedgeIndex[1],self.WedgeIndex[2], \
			self.MatIndex, self.AuxMatIndex, self.SmoothingGroups) = values
	def get(self):
		return (self.WedgeIndex[0],self.WedgeIndex[1],self.WedgeIndex[2], \
			self.MatIndex, self.AuxMatIndex, self.SmoothingGroups)
	def toString(self):
		return mypack(self.fmt, *self.get() )
	def fromString(self, str):
		self.set(myunpack(self.fmt, str))

def test():
	""" certify the size of all classes """
	print "type        should be  is"
	print "VChunkHeader      32 ", sizeof(VChunkHeader)
	print "VPoint            12 ", sizeof(VPoint)
	print "FQuat             16 ", sizeof(FQuat)
	print "FVector           12 ", sizeof(FVector)
	print "VVertex           16 ", sizeof(VVertex)
	print "VTriangle         12 ", sizeof(VTriangle)
	print "VMaterial         88 ", sizeof(VMaterial)
	print "VBone             120", sizeof(VBone)
	print "VJointPos         44 ", sizeof(VJointPos)
	print "VRawBoneInfluence 12 ", sizeof(VRawBoneInfluence)
	print ""
	print "AnimInfoBinary    168", sizeof(AnimInfoBinary)
	print "FNamedBoneBinary  120", sizeof(FNamedBoneBinary)
	print "VQuatAnimKey      32 ", sizeof(VQuatAnimKey)

if __name__ == "__main__":
	test()
"""
cube_export.py
a test of .psk exporting

.psk is the model with rigging format for unreal tournament 2003/2004

a .psa file will hold the animation
"""

from UnrealAnimDataStructs import *

def pskcube(filename):
	apsk = open(filename, "wb" );
	if not apsk:
		print "couldn't open file"
	else:
		i = 0
		# an attempt to write a cube
		
		mainheader = VChunkHeader()
		mainheader.ChunkID="main_name"
		mainheader.TypeFlag = 1999801 	# should indicate file format version
		mainheader.DataCount = 0
		mainheader.DataSize = 0
		
		apsk.write(mainheader.toString())
		
		# header for number of verts
		header = VChunkHeader()
		header.ChunkID="dinner_yum"
		header.TypeFlag = 1999801 # should indicate file format version
		header.DataCount = 8  # number of verts
		header.DataSize = sizeof(VPoint) 
		
		apsk.write(header.toString())
		
		verts = [VPoint(), VPoint(), VPoint(), VPoint(), VPoint(), VPoint(), VPoint(), VPoint()]
		
		verts[0].Point.X = -10.0; verts[0].Point.Y = -10.0; verts[0].Point.Z = -10.0;
		verts[1].Point.X =  10.0; verts[1].Point.Y = -10.0; verts[1].Point.Z = -10.0;
		verts[2].Point.X =  10.0; verts[2].Point.Y = -10.0; verts[2].Point.Z =  10.0;
		verts[3].Point.X = -10.0; verts[3].Point.Y = -10.0; verts[3].Point.Z =  10.0;
		verts[4].Point.X = -10.0; verts[4].Point.Y =  10.0; verts[4].Point.Z = -10.0;
		verts[5].Point.X =  10.0; verts[5].Point.Y =  10.0; verts[5].Point.Z = -10.0;
		verts[6].Point.X =  10.0; verts[6].Point.Y =  10.0; verts[6].Point.Z =  10.0;
		verts[7].Point.X = -10.0; verts[7].Point.Y =  10.0; verts[7].Point.Z =  10.0;
		
		for vert in verts: apsk.write(vert.toString())
		
		# a "wedge" associates a uv coordinate with a vertex
		wedgeheader = VChunkHeader()
		wedgeheader.ChunkID="wedges_moo"
		wedgeheader.TypeFlag = 1999801
		wedgeheader.DataCount = 24
		wedgeheader.DataSize = sizeof(VVertex)
		
		apsk.write(wedgeheader.toString())
		
		wedges = range(24)
		# probably a more standard way to do this:
		for itm in wedges: wedges[itm] = VVertex()
		
		i = 0
		while (i < 6): # 24 / 4 = 6
			# I am creating the uvs of one quad at a time
			j = 0
			while (j < 4):
				# defaults are okay too
				#wedges[4*i+j].MatIndex = '\0';
				#wedges[4*i+j].Reserved = '\0';	
				wedges[4*i+j].PointIndex = (4*i+j)%8;
				j += 1
			
			# these aren't ideal... yet
			# they are sheared for some faces
			
			# now set the UV coordinates
			wedges[4*i].U = 0.25*(i%3);
			wedges[4*i].V = 0.25*(i/3);
			
			wedges[4*i+1].U = 0.25*(i%3)+0.2;
			wedges[4*i+1].V = 0.25*(i/3);
			
			wedges[4*i+2].U = 0.25*(i%3)+0.2;
			wedges[4*i+2].V = 0.25*(i/3)+0.2;
			
			wedges[4*i+3].U = 0.25*(i%3);
			wedges[4*i+3].V = 0.25*(i/3)+0.2;
			
			i += 1
		
		for a_wedge in wedges: apsk.write(a_wedge.toString())
		
		faceheader = VChunkHeader()
		faceheader.ChunkID="w00t_faces"
		faceheader.TypeFlag = 1999801
		faceheader.DataCount = 12 # all triangles, quads aren't allowed
		faceheader.DataSize = sizeof(VTriangle)
		
		apsk.write(faceheader.toString())
		
		tris = [VTriangle(), VTriangle(), VTriangle(), VTriangle(), VTriangle(), VTriangle(), \
			VTriangle(), VTriangle(), VTriangle(), VTriangle(), VTriangle(), VTriangle()]
		
		i = 0
		while i<12:
			# defaults are okay
			#tris[i].MatIndex = '\0'
			#tris[i].AuxMatIndex = '\0'
			tris[i].SmoothingGroups = 0x00000001
			i += 1
		
		# it will be a pain if it turns out I got the vertex order backwards
		# these go counter clockwise
		
		# bottom, top
		tris[0].WedgeIndex[0] = 0; tris[0].WedgeIndex[1] = 1; tris[0].WedgeIndex[2] = 2;
		tris[1].WedgeIndex[0] = 2; tris[1].WedgeIndex[1] = 3; tris[1].WedgeIndex[2] = 0;
		
		tris[10].WedgeIndex[0]= 4; tris[10].WedgeIndex[1]= 7; tris[10].WedgeIndex[2]= 6;
		tris[11].WedgeIndex[0]= 6; tris[11].WedgeIndex[1]= 5; tris[11].WedgeIndex[2]= 4;
		
		# left front right back
		tris[2].WedgeIndex[0] = 9; tris[2].WedgeIndex[1] = 13; tris[2].WedgeIndex[2] = 14;
		tris[3].WedgeIndex[0] = 14; tris[3].WedgeIndex[1] = 10; tris[3].WedgeIndex[2] = 9;
		
		tris[4].WedgeIndex[0] = 18; tris[4].WedgeIndex[1] = 22; tris[4].WedgeIndex[2] = 23;
		tris[5].WedgeIndex[0] = 23; tris[5].WedgeIndex[1] = 19; tris[5].WedgeIndex[2] = 18;
		
		tris[6].WedgeIndex[0] = 11; tris[6].WedgeIndex[1] = 15; tris[6].WedgeIndex[2] = 12;
		tris[7].WedgeIndex[0] = 12; tris[7].WedgeIndex[1] = 8; tris[7].WedgeIndex[2] = 11;
		
		tris[8].WedgeIndex[0] = 16; tris[8].WedgeIndex[1] = 20; tris[8].WedgeIndex[2] = 21;
		tris[9].WedgeIndex[0] = 21; tris[9].WedgeIndex[1] = 17; tris[9].WedgeIndex[2] = 16;
		
		# hack [temporary hopefully] to make vertex order clockwise
		i = 0
		while i<12:
			temp = tris[i].WedgeIndex[2]
			tris[i].WedgeIndex[2] = tris[i].WedgeIndex[0]
			tris[i].WedgeIndex[0] = temp
			i += 1
		
		# should return: 12, on success
		for atri in tris: apsk.write(atri.toString())
		
		matHeader = VChunkHeader()
		matHeader.ChunkID = "materialz"
		matHeader.TypeFlag = 1999801
		matHeader.DataCount = 1
		matHeader.DataSize = sizeof(VMaterial)
		
		apsk.write(matHeader.toString())
		
		materials = [VMaterial()]
		
		materials[0].MaterialName="MATERIAL_name_meh"
		materials[0].TextureIndex = 0;
		materials[0].PolyFlags = 0;
		materials[0].AuxMaterial = 0;
		materials[0].AuxFlags = 0;
		materials[0].LodBias = 0;
		materials[0].LodStyle = 0;
		
		for mat in materials: apsk.write(mat.toString())
		
		boneheader = VChunkHeader();
		boneheader.ChunkID = "bones_yay"
		boneheader.TypeFlag = 1999801;
		boneheader.DataCount = 1;
		boneheader.DataSize = sizeof(VBone);
		
		apsk.write(boneheader.toString())
		
		bones = [VBone()]
		
		bones[0].Name="HEAD_BONE"
		bones[0].Flags = 0
		bones[0].NumChildren = 0
		bones[0].ParentIndex = 0 # this is the root bone
		bones[0].BonePos.Position.X = 0
		bones[0].BonePos.Position.Y = 0
		bones[0].BonePos.Position.Z = 0
		# what is the y axis as a quaternoin?
		bones[0].BonePos.Orientation.X = 0
		bones[0].BonePos.Orientation.Y = 1
		bones[0].BonePos.Orientation.Z = 0
		bones[0].BonePos.Orientation.W = 0
		bones[0].BonePos.Length = 1
		bones[0].BonePos.XSize = 1
		bones[0].BonePos.YSize = 1
		bones[0].BonePos.ZSize = 1
		
		for bone in bones: apsk.write(bone.toString())
		
		boneinfluencesheader = VChunkHeader()
		boneinfluencesheader.ChunkID = "influences"
		boneinfluencesheader.TypeFlag = 1999801
		boneinfluencesheader.DataCount = 8
		boneinfluencesheader.DataSize = sizeof(VRawBoneInfluence)
		
		apsk.write(boneinfluencesheader.toString())
		
		boneinfluences = \
			[ VRawBoneInfluence(), VRawBoneInfluence(), VRawBoneInfluence(), VRawBoneInfluence(),
			VRawBoneInfluence(), VRawBoneInfluence(), VRawBoneInfluence(), VRawBoneInfluence()]
		
		i = 0
		while i<8:
			boneinfluences[i].BoneIndex = 0;
			boneinfluences[i].PointIndex = i;
			boneinfluences[i].Weight = 1.0;
			i += 1
		
		for boneinfluence in boneinfluences: apsk.write(boneinfluence.toString())
		
		
		apsk.close()

pskcube("E:\\Blender\\python\\unrealexport\\cube_python.psk")

I need to figure out a couple things as to why I can’t import this into upaint, and then I will do a blender export.

okay, enough coding for today [I count days by when I sleep, it is 1am, but tommrrow in that respect hasn’t arrived]

http://home.earthlink.net/~nwinters99/temp/suzane_unrealEd.png

the code has absolutely NO ui or implementation of looking at an actual armature

and it doesn’t try to produce the optimal result [that will wait]

"""
psk_export.py
a test of .psk exporting

.psk is the model with rigging format for unreal tournament 2003/2004

a .psa file will hold the animation
"""

# name shortened for limitation of blender text name
##from UnrealAnimDataStructs import *
from UnrealAnimDataStructs import *
import Blender

def pskexport(filename, mesh_obj, rig_obj):
	apsk = open(filename, "wb" );
	if not apsk:
		print "couldn't open file"
	else:
		mesh = mesh_obj.getData()
		##rig = rig_obj.getData()
		i = 0
		# an attempt to write a cube
		
		mainheader = VChunkHeader()
		mainheader.ChunkID=mesh_obj.getName()
		mainheader.TypeFlag = 1999801 	# should indicate file format version
		mainheader.DataCount = 0
		mainheader.DataSize = 0
		
		apsk.write(mainheader.toString())
		
		# header for number of verts
		header = VChunkHeader()
		header.ChunkID="VERTICIES000"
		header.TypeFlag = 1999801 # should indicate file format version
		header.DataCount = len(mesh.verts)  # number of verts
		header.DataSize = sizeof(VPoint) 
		
		apsk.write(header.toString())
		
		for vert_obj in mesh.verts:
			vert = vert_obj.co
			psk_vert = VPoint()
			psk_vert.Point.X = 100.0*vert[0]
			psk_vert.Point.Y = 100.0*vert[1]
			psk_vert.Point.Z = 100.0*vert[2]
			apsk.write(psk_vert.toString())
		
		# a "wedge" associates a uv coordinate with a vertex
		# ideally I would have only one wedge for a vertex where multilple faces using
		# it have the same uv coordinate [which appears to be the intention of having 
		# wedges].  However, I can be very lazy by not doing that.  
		# problem: this will likely result in a sub-optimal set of tristrips
		# (or whatever) from unrealEd or whatever does the conversion to their format
		wedgeheader = VChunkHeader()
		wedgeheader.ChunkID="WEDGES000"
		wedgeheader.TypeFlag = 1999801
		wedgeheader.DataCount = None # to be determined later
		wedgeheader.DataSize = sizeof(VVertex)
		
		faceheader = VChunkHeader()
		faceheader.ChunkID="TRIANGLES000"
		faceheader.TypeFlag = 1999801
		faceheader.DataCount = None # determined later
		faceheader.DataSize = sizeof(VTriangle)

		# wedges and faces are related enough that I can't write one before 
		# determining the other
		
		wedges = []
		faces = []
		for nm_face in mesh.faces:
			# for wedges set at least: PointIndex, U, V
			# new wedge: VVertex()
			# for faces set at least: WedgeIndex[0-2], (SmoothingGroups?)
			# new face: VTriangle()
			faces_wedges = []
			for i in range(len(nm_face.v)):
				# modify to look for wedge with same u, v, and PointIndex 
				# in future
				faces_wedges.append(VVertex())
				faces_wedges[i].PointIndex = mesh.verts.index(nm_face.v[i])
				faces_wedges[i].U = nm_face.uv[i][0]
				faces_wedges[i].V = nm_face.uv[i][1]
			wedge_r0 = len(wedges)
			wedges.extend(faces_wedges)
			if len(nm_face.v) == 4:
				new_face = VTriangle()
				new_face.WedgeIndex = [wedge_r0+2,wedge_r0+1,wedge_r0+0]
				new_face2 = VTriangle()
				new_face2.WedgeIndex = [wedge_r0+0,wedge_r0+3,wedge_r0+2]
				faces.append(new_face)
				faces.append(new_face2)
			elif len(nm_face.v) == 3:
				new_face = VTriangle()
				new_face.WedgeIndex = [wedge_r0+2,wedge_r0+1,wedge_r0+0]
				faces.append(new_face)
			else:
				raise unexpectedContitionError()

		wedgeheader.DataCount = len(wedges)
		faceheader.DataCount = len(faces)
		
		apsk.write(wedgeheader.toString())
		for a_wedge in wedges: apsk.write(a_wedge.toString())
		apsk.write(faceheader.toString())
		for aface in faces: apsk.write(aface.toString())
		
		matHeader = VChunkHeader()
		matHeader.ChunkID = "material000"
		matHeader.TypeFlag = 1999801
		matHeader.DataCount = 1
		matHeader.DataSize = sizeof(VMaterial)
		
		apsk.write(matHeader.toString())
		
		materials = [VMaterial()]
		
		materials[0].MaterialName="MATERIALS000"
		materials[0].TextureIndex = 0
		materials[0].PolyFlags = 0
		materials[0].AuxMaterial = 2295624 # that is how KarmaTube had it, I think it is ignored
		materials[0].AuxFlags = 0
		materials[0].LodBias = 0
		materials[0].LodStyle = 0
		
		for mat in materials: apsk.write(mat.toString())
		
		## for the moment, bones are not implemented, so I just create a bone
		
		boneheader = VChunkHeader();
		boneheader.ChunkID = "BONES000"
		boneheader.TypeFlag = 1999801;
		boneheader.DataCount = 1;
		boneheader.DataSize = sizeof(VBone);
		
		apsk.write(boneheader.toString())
		
		bones = [VBone()]
		
		bones[0].Name="HEADBONE000"
		bones[0].Flags = 0
		bones[0].NumChildren = 0
		bones[0].ParentIndex = 0 # this is the root bone
		bones[0].BonePos.Position.X = 0
		bones[0].BonePos.Position.Y = 0
		bones[0].BonePos.Position.Z = 0
		# what is the y axis as a quaternoin?
		bones[0].BonePos.Orientation.X = -0.5
		bones[0].BonePos.Orientation.Y = 0.5
		bones[0].BonePos.Orientation.Z = 0.5
		bones[0].BonePos.Orientation.W = 0.5
		bones[0].BonePos.Length = 10
		bones[0].BonePos.XSize = 0
		bones[0].BonePos.YSize = 1
		bones[0].BonePos.ZSize = 0
		
		for bone in bones: apsk.write(bone.toString())
		
		# weights are simply full for every VPoint()
		
		boneinfluencesheader = VChunkHeader()
		boneinfluencesheader.ChunkID = "influences"
		boneinfluencesheader.TypeFlag = 1999801
		boneinfluencesheader.DataCount = len(mesh.verts)
		boneinfluencesheader.DataSize = sizeof(VRawBoneInfluence)
		
		apsk.write(boneinfluencesheader.toString())
		
		i = 0
		while i<len(mesh.verts):
			boneinfluence = VRawBoneInfluence()
			boneinfluence.BoneIndex = 0;
			boneinfluence.PointIndex = i;
			boneinfluence.Weight = 1.0;
			apsk.write(boneinfluence.toString())
			i += 1
		
		
		apsk.close()
		print "SUCCESS!!

"


got = 0
mesh = None
rig = None
for obj in Blender.Object.GetSelected():
	if got == 1 and obj.getType() == "Armature": # I think
		rig = obj
		got = 2
		break #I'm done
	if got == 0 and obj.getType() == "Mesh":
		mesh = obj # I think that is the call
		##got = 1
		got = 2; break # debug until I implement rigging

if got == 2:
	pskexport("E:\\Blender\\python\\unrealexport\\psk_object.psk", mesh, rig)
else:
	print "You need to select both an armature and a mesh"

oh, and I still haven’t figured out the problem with upaint

a clarification
I don’t intend on continuing on this script beyond what I have so far. That doesn’t mean I will not, just that it cannot be expected. You are free to use what I have written to whatever extent I can make that possible
[I don’t own the copyright on UnrealAnimDataStructs.h for example, and you don’t have to credit me if you modify or extend my incomplete psk export]

well, I’m more content now

http://home.earthlink.net/~nwinters99/temp/upaint.png

still no armature support or anything

"""
psk_export.py
a test of .psk exporting

.psk is the model with rigging format for unreal tournament 2003/2004

a .psa file will hold the animation
"""

# name shortened for limitation of blender text name
##from UnrealAnimDataStructs import *
from UnrealAnimDataSt import *
import Blender

def pskexport(filename, mesh_obj, rig_obj):
	apsk = open(filename, "wb" );
	if not apsk:
		print "couldn't open file"
	else:
		mesh = mesh_obj.getData()
		##rig = rig_obj.getData()
		i = 0
		# an attempt to write a cube
		
		mainheader = VChunkHeader()
		mainheader.ChunkID="ACTRHEAD" ##mesh_obj.getName()
		mainheader.TypeFlag = 1999801 	# should indicate file format version
		mainheader.DataCount = 0
		mainheader.DataSize = 0
		
		apsk.write(mainheader.toString())
		
		# header for number of verts
		header = VChunkHeader()
		header.ChunkID="PNTS0000"
		header.TypeFlag = 1999801 # should indicate file format version
		header.DataCount = len(mesh.verts)  # number of verts
		header.DataSize = sizeof(VPoint) 
		
		apsk.write(header.toString())
		
		for vert_obj in mesh.verts:
			vert = vert_obj.co
			psk_vert = VPoint()
			psk_vert.Point.X = 100.0*vert[0]
			psk_vert.Point.Y = 100.0*vert[1]
			psk_vert.Point.Z = 100.0*vert[2]
			apsk.write(psk_vert.toString())
		
		# a "wedge" associates a uv coordinate with a vertex
		# ideally I would have only one wedge for a vertex where multilple faces using
		# it have the same uv coordinate [which appears to be the intention of having 
		# wedges].  However, I can be very lazy by not doing that.  
		# problem: this will likely result in a sub-optimal set of tristrips
		# (or whatever) from unrealEd or whatever does the conversion to their format
		wedgeheader = VChunkHeader()
		wedgeheader.ChunkID="VTXW0000"
		wedgeheader.TypeFlag = 1999801
		wedgeheader.DataCount = None # to be determined later
		wedgeheader.DataSize = sizeof(VVertex)
		
		faceheader = VChunkHeader()
		faceheader.ChunkID="FACE0000"
		faceheader.TypeFlag = 1999801
		faceheader.DataCount = None # determined later
		faceheader.DataSize = sizeof(VTriangle)

		# wedges and faces are related enough that I can't write one before 
		# determining the other
		
		wedges = []
		faces = []
		for nm_face in mesh.faces:
			# for wedges set at least: PointIndex, U, V
			# new wedge: VVertex()
			# for faces set at least: WedgeIndex[0-2], (SmoothingGroups?)
			# new face: VTriangle()
			faces_wedges = []
			for i in range(len(nm_face.v)):
				# modify to look for wedge with same u, v, and PointIndex 
				# in future
				faces_wedges.append(VVertex())
				faces_wedges[i].PointIndex = mesh.verts.index(nm_face.v[i])
				faces_wedges[i].U = nm_face.uv[i][0]
				faces_wedges[i].V = 1.0 - nm_face.uv[i][1]
			wedge_r0 = len(wedges)
			wedges.extend(faces_wedges)
			if len(nm_face.v) == 4:
				new_face = VTriangle()
				new_face.WedgeIndex = [wedge_r0+2,wedge_r0+1,wedge_r0+0]
				new_face2 = VTriangle()
				new_face2.WedgeIndex = [wedge_r0+0,wedge_r0+3,wedge_r0+2]
				faces.append(new_face)
				faces.append(new_face2)
			elif len(nm_face.v) == 3:
				new_face = VTriangle()
				new_face.WedgeIndex = [wedge_r0+2,wedge_r0+1,wedge_r0+0]
				faces.append(new_face)
			else:
				raise unexpectedContitionError()

		wedgeheader.DataCount = len(wedges)
		faceheader.DataCount = len(faces)
		
		apsk.write(wedgeheader.toString())
		for a_wedge in wedges: apsk.write(a_wedge.toString())
		apsk.write(faceheader.toString())
		for aface in faces: apsk.write(aface.toString())
		
		matHeader = VChunkHeader()
		matHeader.ChunkID = "MATT0000"
		matHeader.TypeFlag = 1999801
		matHeader.DataCount = 1
		matHeader.DataSize = sizeof(VMaterial)
		
		apsk.write(matHeader.toString())
		
		materials = [VMaterial()]
		
		materials[0].MaterialName="lambert2SG"
		materials[0].TextureIndex = 0
		materials[0].PolyFlags = 0
		materials[0].AuxMaterial = 2295624 # that is how KarmaTube had it, I think it is ignored
		materials[0].AuxFlags = 0
		materials[0].LodBias = 0
		materials[0].LodStyle = 0
		
		for mat in materials: apsk.write(mat.toString())
		
		## for the moment, bones are not implemented, so I just create 2 bones
		
		boneheader = VChunkHeader();
		boneheader.ChunkID = "REFSKELT"
		boneheader.TypeFlag = 1999801
		boneheader.DataCount = 2
		boneheader.DataSize = sizeof(VBone)
		
		apsk.write(boneheader.toString())
		
		bones = [VBone(), VBone()]
		
		bones[0].Name="joint1"
		bones[0].Flags = 0
		bones[0].NumChildren = 1
		bones[0].ParentIndex = 0 # this is the root bone
		bones[0].BonePos.Position.X = 0
		bones[0].BonePos.Position.Y = 0
		bones[0].BonePos.Position.Z = 0
		# what is the y axis as a quaternoin?
		bones[0].BonePos.Orientation.X = -0.5
		bones[0].BonePos.Orientation.Y = 0.5
		bones[0].BonePos.Orientation.Z = 0.5
		bones[0].BonePos.Orientation.W = 0.5
		bones[0].BonePos.Length = 10
		bones[0].BonePos.XSize = 0
		bones[0].BonePos.YSize = 1
		bones[0].BonePos.ZSize = 0
		
		bones[1].Name="joint2"
		bones[1].Flags = 0
		bones[1].NumChildren = 0
		bones[1].ParentIndex = 0
		bones[1].BonePos.Position.X = 0
		bones[1].BonePos.Position.Y = 10
		bones[1].BonePos.Position.Z = 10
		# what is the y axis as a quaternoin?
		bones[1].BonePos.Orientation.X = 0
		bones[1].BonePos.Orientation.Y = 1
		bones[1].BonePos.Orientation.Z = 0
		bones[1].BonePos.Orientation.W = 0
		bones[1].BonePos.Length = 10
		bones[1].BonePos.XSize = 1
		bones[1].BonePos.YSize = 1
		bones[1].BonePos.ZSize = 1
		
		for bone in bones: apsk.write(bone.toString())
		
		# weights are simply full for every VPoint()
		
		boneinfluencesheader = VChunkHeader()
		boneinfluencesheader.ChunkID = "influences"
		boneinfluencesheader.TypeFlag = 1999801
		boneinfluencesheader.DataCount = len(mesh.verts)
		boneinfluencesheader.DataSize = sizeof(VRawBoneInfluence)
		
		apsk.write(boneinfluencesheader.toString())
		
		i = 0
		while i<len(mesh.verts):
			boneinfluence = VRawBoneInfluence()
			boneinfluence.BoneIndex = i/(len(mesh.verts)/2+1)
			boneinfluence.PointIndex = i
			boneinfluence.Weight = 1.0
			apsk.write(boneinfluence.toString())
			i += 1
		
		
		apsk.close()
		print "SUCCESS!!

"


got = 0
mesh = None
rig = None
for obj in Blender.Object.GetSelected():
	if got == 1 and obj.getType() == "Armature": # I think
		rig = obj
		got = 2
		break #I'm done
	if got == 0 and obj.getType() == "Mesh":
		mesh = obj # I think that is the call
		##got = 1
		got = 2; break # debug until I implement rigging

if got == 2:
	pskexport("E:\\Blender\\python\\unrealexport\\psk_object.psk", mesh, rig)
else:
	print "You need to select both an armature and a mesh"


I’ve run into a problem trying to export the armature. Blender API methods Armature.Bone.GetQuat() and Armature.Bone.GetLoc() do not return the Quaternion and Position of a bone in the reference pose. They are used in conjunction with IPOs for animation. When I called those methods to dump the data for some test armatures, all the bones had a quaternion = [1.000,0.000,0.000,0.000] and a location = [0.000,0.000,0.000]. We’ll need to convert head, tail and roll to a quaternion. Plus all the bones had head = [0.0,0.0,0.0] so I assume that each bone is relative to the parent bone (where parent tail location = bone head location) …(huhhuh huhhuh, he said bonehead)

If anyone has formulas for converting between “head,tail and roll” and quaternion, please post.
Thanks!

Nevermind. I found all the answers with a forum search.

I just wanted to quickly say thanks to everyone who’s working on this. I’m going to be contributing to a game project for the “make something unreal” contest (in the context of a game design course), and this could just make that possible! it’s either that, or applying for funding to get a copy of Max… bleh and fat chence we’ll get it anyway.

----EDIT----

hmmm. I need to make up some animations for parts of vehicles… no armatures involved, just a mechanical door opening. do you think that this script could work at this point?

or would it be better to script those parts in unrealed?

i recommend using the static mesh exporter for now, and get the uv and other information done properly, because currenlty the script i am updating has some problems :/… i have got it working properly to begin with, but i have noticed some blender indescrepensies that worry me… details are on the post on blender.org.

edit: here are the 2 links to help…

http://www.blender.org/modules.php?op=modload&name=phpBB2&file=viewtopic&t=4469

http://www.blender.org/modules.php?op=modload&name=phpBB2&file=viewtopic&t=4448

… and also, take a look at the bone.getloc() function it seems to be a little off, as shown in this script…


#!BPY

""" Registration info for Blender menus:
Name: 'bone nfo print...'
Blender: 234
Group: 'Export'
Tip: 'print the bone info from blender to blender's console.'
"""
import Blender
from Blender import *
from Blender.Armature import *
from Blender.Armature.Bone import *
# import Blender
# from Blender import Armature



def fs_callback(filename):
    space = 0
    xtotal = 0
    tex = []
    armatures = Armature.Get()
    for a in armatures:
       print "Armature ", a
       print "- The root bones of %s: %s" % (a.name, a.getBones())
    print "
 exporting ..."
#   self.writeHeader()
    for name in Object.GetSelected():
           obj = name.getData()
           arm = name.getData()
           if type(obj) == Types.NMeshType :
               print "has mesh"
           if type(arm) == Types.ArmatureType :
               Blender.Set('curframe',1)
               bonelist = arm.getBones()
               bonetotal = len(arm.getBones())
               arm_name  = arm.name
               print "the armature name is...", arm_name, "
"
              # print "the bones in the model are...", bonelist, "
"
               print "the armature has", bonetotal, "bones.
"
               print "printing bone info.
"
               for bones in range(bonetotal):
                   bonename = bonelist[bones].getName()
                   bone_loca = bonelist[bones].getLoc()
                   bone_locb = bonelist[bones].getQuat()
                   bone_head_loc = bonelist[bones].getHead()
                   bone_tail_loc = bonelist[bones].getTail()
                   bone_parent = bonelist[bones].getParent()
                   bone_size = bonelist[bones].getSize()
                   print "the name of this bone is...", bonename, "
"
                   print "this bone has a parent of...", bone_parent, "
"
                   print "the bone has a location of...", bone_loca, "
"
                   print "here is the 2nd attempt to get the bone loaction via quat method...", bone_locb, "
"
                   print "the bone has a head location of...", bone_head_loc, "
"
                   print "the bone has a tail location of...", bone_tail_loc, "
"
                   print "the bone has a size of...",bone_size, "
"

Blender.Window.FileSelector(fs_callback, "Export to log")

for some reason the bone location always returns 0 0 and 0, and the head and tail locations return other things, but are not 0.