#!BPY
"""
Name: 'Ultimate 3D model file format (*.u3d)'
Blender: 248a
Group: 'Export'
Tip: 'Exports models (geometry, materials and animations) to the Ultimate 3D model file format (*.u3d).'
"""
__author__="Christoph Peters";
__url__="http://Ultimate3D.org";
__version__="1.2.0";
__bpydoc__="""Exports geometry, materials and armatures with animations and constraints of models in Blender to the Ultimate 3D model file format.""";
# ----------------------------- LICENSE -----------------------------
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
# Include the needed components of the Blender API
import Blender
from Blender import Scene
from Blender import Group
from Blender import Object
from Blender import Material
from Blender import Mathutils
from Blender import Mesh
from Blender import Armature
from Blender import Ipo
from Blender import Draw
from Blender import Window
# struct is needed to bring data into a binary format
import struct
# The math module is needed for the usual mathematical operations
import math
# This module is useful to copy files
import shutil
# This module is needed to create the directory to which the textures are output
import os
def Choose(Condition,Value1,Value2):
"""This function returns Value1, if the given Condition evaluates true or
Value2 otherwise."""
if(Condition):
return Value1;
else:
return Value2;
class CChunkSizeWriter:
"""This class rembers the offset at which a chunk size should be written
and writes the chunk size in this place on request. It is used to export
fileformat version 2.0."""
def __init__(self,File):
"""This function remembers the current file pointer of the given file
and moves it 4=sizeof(DWORD) bytes forward by writing zeroes. Later
Write() can write a chunk size at this offset."""
self.oChunkSize=File.tell();
self.File=File;
File.write(struct.pack("L",0));
def Write(self):
"""This function writes a DWORD chunk size at the recently marked
position."""
oFilePointer=self.File.tell();
self.File.seek(self.oChunkSize);
self.File.write(struct.pack("L",oFilePointer-self.oChunkSize-4));
self.File.seek(oFilePointer);
oChunkSize=0;
File=None;
class C2DVector:
"""This class represents simple 2D vectors"""
def __init__(self,x,y):
"""Constructs the vector from two components"""
self.x=x;
self.y=y;
def SaveData(self,File):
"""Saves the data of this vector to the given file"""
File.write(struct.pack("ff",self.x,self.y));
x=0.0;
y=0.0;
class C3DVector:
"""This class represents simple 3D vectors"""
def __init__(self,x,y,z):
"""Constructs the vector from three components"""
self.x=x;
self.y=y;
self.z=z;
def SaveData(self,File):
"""Saves the data of this vector to the given file"""
File.write(struct.pack("fff",self.x,self.y,self.z));
def SaveAsCompressedNormal(self,File,UseV2):
"""Saves a normalized copy of this vector as 16-bit or 32-bit
latitude/longitude vector."""
self.Normalize();
Latitude=-math.asin(self.y);
Longitude=math.atan2(self.x,self.z);
if(UseV2):
Latitude*=20860.12008;
Longitude*=10430.06004;
File.write(struct.pack("hh",int(Latitude),int(Longitude)));
else:
Latitude*=20.212678;
Longitude*=40.4253555;
File.write(struct.pack("bb",int(Latitude),int(Longitude)));
def Normalize(self):
"""Normalizes this vector"""
Length=math.sqrt(self.x*self.x+self.y*self.y+self.z*self.z);
if(Length>0.0):
InvLength=1.0/Length;
self.x*=InvLength;
self.y*=InvLength;
self.z*=InvLength;
def Minimize(self,RHS):
"""This function chooses the minimum of this vector and the given other
vector on per-component base."""
self.x=min(self.x,RHS.x);
self.y=min(self.y,RHS.y);
self.z=min(self.z,RHS.z);
def Maximize(self,RHS):
"""This function chooses the maximum of this vector and the given other
vector on per-component base."""
self.x=max(self.x,RHS.x);
self.y=max(self.y,RHS.y);
self.z=max(self.z,RHS.z);
x=0.0;
y=0.0;
z=0.0;
class CMatrix4x4:
"""This class represents a 4x4 transformation matrix."""
def __init__(self,_11,_12,_13,_14,_21,_22,_23,_24,_31,_32,_33,_34,_41,_42,_43,_44):
"""This constructor initializes this matrix with the given entries."""
self._11=_11; self._12=_12; self._13=_13; self._14=_14;
self._21=_21; self._22=_22; self._23=_23; self._24=_24;
self._31=_31; self._32=_32; self._33=_33; self._34=_34;
self._41=_41; self._42=_42; self._43=_43; self._44=_44;
def CreateFromBlenderMatrix(self,BlenderMatrix):
"""Copies the given Blender matrix to this matrix."""
if(BlenderMatrix.colSize<4 or BlenderMatrix.rowSize<4):
print("Failed to copy a Blender matrix object to a 4x4 matrix. The Blender matrix has a size of "+str(BlenderMatrix.rowSize)+"x"+str(BlenderMatrix.colSize)+".");
else:
self._11=BlenderMatrix[0][0]; self._12=BlenderMatrix[0][1]; self._13=BlenderMatrix[0][2]; self._14=BlenderMatrix[0][3];
self._21=BlenderMatrix[1][0]; self._22=BlenderMatrix[1][1]; self._23=BlenderMatrix[1][2]; self._24=BlenderMatrix[1][3];
self._31=BlenderMatrix[2][0]; self._32=BlenderMatrix[2][1]; self._33=BlenderMatrix[2][2]; self._34=BlenderMatrix[2][3];
self._41=BlenderMatrix[3][0]; self._42=BlenderMatrix[3][1]; self._43=BlenderMatrix[3][2]; self._44=BlenderMatrix[3][3];
def ApplyTranslation(self,BlenderVector):
self._41=BlenderVector[0];
self._42=BlenderVector[1];
self._43=BlenderVector[2];
def SaveData(self,File):
"""This function saves this matrix to a given binary file."""
File.write(struct.pack("16f",self._11,self._12,self._13,self._14,self._21,self._22,self._23,self._24,self._31,self._32,self._33,self._34,self._41,self._42,self._43,self._44));
def TransformVector(self,Vector):
"""This function returns a transformed version of the given vector."""
return C3DVector(Vector.x*self._11+Vector.y*self._21+Vector.z*self._31+self._41,Vector.x*self._12+Vector.y*self._22+Vector.z*self._32+self._42,Vector.x*self._13+Vector.y*self._23+Vector.z*self._33+self._43);
def TransformMatrix(self,Matrix):
"""This function transforms the given matrix by this matrix. The
resulting matrix will describe the transformation, that can be
achieved by using first the given matrix and after that this
matrix."""
return CMatrix4x4(
self._11*Matrix._11+self._21*Matrix._12+self._31*Matrix._13+self._41*Matrix._14,
self._12*Matrix._11+self._22*Matrix._12+self._32*Matrix._13+self._42*Matrix._14,
self._13*Matrix._11+self._23*Matrix._12+self._33*Matrix._13+self._43*Matrix._14,
self._14*Matrix._11+self._24*Matrix._12+self._34*Matrix._13+self._44*Matrix._14,
self._11*Matrix._21+self._21*Matrix._22+self._31*Matrix._23+self._41*Matrix._24,
self._12*Matrix._21+self._22*Matrix._22+self._32*Matrix._23+self._42*Matrix._24,
self._13*Matrix._21+self._23*Matrix._22+self._33*Matrix._23+self._43*Matrix._24,
self._14*Matrix._21+self._24*Matrix._22+self._34*Matrix._23+self._44*Matrix._24,
self._11*Matrix._31+self._21*Matrix._32+self._31*Matrix._33+self._41*Matrix._34,
self._12*Matrix._31+self._22*Matrix._32+self._32*Matrix._33+self._42*Matrix._34,
self._13*Matrix._31+self._23*Matrix._32+self._33*Matrix._33+self._43*Matrix._34,
self._14*Matrix._31+self._24*Matrix._32+self._34*Matrix._33+self._44*Matrix._34,
self._11*Matrix._41+self._21*Matrix._42+self._31*Matrix._43+self._41*Matrix._44,
self._12*Matrix._41+self._22*Matrix._42+self._32*Matrix._43+self._42*Matrix._44,
self._13*Matrix._41+self._23*Matrix._42+self._33*Matrix._43+self._43*Matrix._44,
self._14*Matrix._41+self._24*Matrix._42+self._34*Matrix._43+self._44*Matrix._44,
);
def Compute3x3Determinant(self):
"""This function computes the determinant of the 3x3 sub-matrix of
this matrix and returns it."""
return self._11*(self._22*self._33-self._32*self._23)-self._12*(self._21*self._33-self._31*self._23)+self._13*(self._21*self._32-self._31*self._22);
def RotateAndScaleVector(self,Vector):
"""This function returns a rotated and scaled version of the given
vector. Translation is not applied."""
return C3DVector(Vector.x*self._11+Vector.y*self._21+Vector.z*self._31,Vector.x*self._12+Vector.y*self._22+Vector.z*self._32,Vector.x*self._13+Vector.y*self._23+Vector.z*self._33);
def Print(self):
"""This function outputs this matrix to the console."""
print("_11: "+str(self._11)+" _12: "+str(self._12)+" _13: "+str(self._13)+" _14: "+str(self._14));
print("_21: "+str(self._21)+" _22: "+str(self._22)+" _23: "+str(self._23)+" _24: "+str(self._24));
print("_31: "+str(self._31)+" _32: "+str(self._32)+" _33: "+str(self._33)+" _34: "+str(self._34));
print("_41: "+str(self._41)+" _42: "+str(self._42)+" _43: "+str(self._43)+" _44: "+str(self._44));
_11=1.0; _12=0.0; _13=0.0; _14=0.0;
_21=0.0; _22=1.0; _23=0.0; _24=0.0;
_31=0.0; _32=0.0; _33=1.0; _34=0.0;
_41=0.0; _42=0.0; _43=0.0; _44=1.0;
# This global matrix brings a vector from Blender space to Ultimate 3D space
# by switching the y and the z axis. If skinning is used it will be set to
# the identity matrix, because in this case the root bone does the
# transformation.
BlenderToU3DSpace=CMatrix4x4(1.0,0.0,0.0,0.0, 0.0,0.0,1.0,0.0, 0.0,1.0,0.0,0.0, 0.0,0.0,0.0,1.0);
class CQuaternion:
"""This class handles a quaternion describing a rotation."""
def __init__(self,x,y,z,w):
"""This constructor initializes this quaternion with the given entries
and normalizes it. If x, y and z are zero an identity rotation is
created."""
LengthSq=x*x+y*y+z*z+w*w;
if(LengthSq>0.0):
InvLength=1.0/math.sqrt(LengthSq);
self.x=x*InvLength;
self.y=y*InvLength;
self.z=z*InvLength;
self.w=w*InvLength;
else:
self.x=0.0;
self.y=0.0;
self.z=0.0;
self.w=1.0;
def SaveData(self,File):
"""This function saves the data of this quaternion to the given file."""
File.write(struct.pack("4f",self.x,self.y,self.z,self.w));
def AppendRotation(self,RHS):
"""This function multiplies this quaternion by the given quaternion.
Transforming a vector with the resulting quaternion will be
equivalent to transforming it first with this quaternion and after
that with the given quaternion."""
x=self.x;
y=self.y;
z=self.z;
w=self.w;
self.x=RHS.w*x+w*RHS.x+RHS.y*z-RHS.z*y;
self.y=RHS.w*y+w*RHS.y+RHS.z*x-RHS.x*z;
self.z=RHS.w*z+w*RHS.z+RHS.x*y-RHS.y*x;
self.w=RHS.w*w-RHS.x*x-RHS.y*y-RHS.z*z;
def RotateVector(self,VectorX,VectorY,VectorZ):
"""This function returns a version of the given vector, to which the
rotation described by this vector has been applied."""
return C3DVector(
2.0*(VectorX*(0.5-(self.y*self.y+self.z*self.z))+VectorY*(self.x*self.y-self.z*self.w)+VectorZ*(self.x*self.z+self.y*self.w)),
2.0*(VectorX*(self.x*self.y+self.z*self.w)+VectorY*(0.5-(self.x*self.x+self.z*self.z))+VectorZ*(self.y*self.z-self.x*self.w)),
2.0*(VectorX*(self.x*self.z-self.y*self.w)+VectorY*(self.y*self.z+self.x*self.w)+VectorZ*(0.5-(self.x*self.x+self.y*self.y)))
);
# The member variables, describing an identity rotation
x=0.0;
y=0.0;
z=0.0;
w=1.0;
class CTriangleIndices:
"""This class holds three vertex indices, describing a triangle."""
def __init__(self,A,B,C):
"""Constructs the triangle index set from three indices"""
self.A=A;
self.B=B;
self.C=C;
def SaveData(self,File):
"""Saves the data of this triangle index set to the given file"""
File.write(struct.pack("HHH",self.A,self.B,self.C));
A=0;
B=0;
C=0;
class CColor:
""" This class represents an r, g, b, a color (four floats)."""
def __init__(self,r,g,b,a):
self.r=r;
self.g=g;
self.b=b;
self.a=a;
def SaveData(self,File):
File.write(struct.pack("4f",self.r,self.g,self.b,self.a));
r=1.0;
g=1.0;
b=1.0;
a=1.0;
class CHeader:
"""This class collects header information for the *.u3d file"""
def __init__(self,RelevantObjects,MaterialList,nBone,nFrame,UVLayerSet):
"""This constructor collects header data from the given scene."""
# Count the number of meshes
for i in RelevantObjects:
if(i.getType()=="Mesh"):
self.nMesh+=1;
if(nBone):
self.nMesh=1;
# Count the number of frames
self.nFrame=max(1,nFrame);
# Get the number of materials
self.nMaterial=len(MaterialList);
if(self.nMaterial==0):
self.nMaterial=1;
# Get the number of bones
self.nBone=nBone;
# Get the number of UV layers
if(UVLayerSet!=None):
self.nTexCoordSet=len(UVLayerSet);
else:
self.nTexCoordSet=1;
print("The scene contains "+str(nBone)+" bones.");
def SaveData(self,File,UseV2):
"""This function writes collected header data to the given file object"""
if(UseV2):
# Write the chunk header for the file header
File.write("$U3D_FILE_HEADER\0");
# Remember the offset for the chunk size
ChunkSize=CChunkSizeWriter(File);
# Write the file format version
File.write(struct.pack("LLL",2,0,0));
# Write the encryption version and the compression version
File.write(struct.pack("LL",0,0));
# The chunk is complete
ChunkSize.Write();
# Write the chunk header for the model header
File.write("$U3D_MODEL_HEADER\0");
ChunkSize=CChunkSizeWriter(File);
# Write the mesh counts
File.write(struct.pack("LLLL",self.nMesh,self.nMesh,self.nFrame,1));
# Write the material count
File.write(struct.pack("L",self.nMaterial));
# Write the bone count
File.write(struct.pack("L",self.nBone));
# Vertex tweening is not used
File.write(struct.pack("b",0));
# Write the maximal distance of the one and only LoD
File.write(struct.pack("f",3.402823466e+38));
# Write the texture coordinate dimensions
for i in range(max(1,self.nTexCoordSet)):
File.write(struct.pack("L",2));
for i in range(max(1,self.nTexCoordSet),8):
File.write(struct.pack("L",0));
# Write the skin weight count
File.write(struct.pack("L",Choose(self.nBone>0,3,0)));
# Write that there is no shader pack template
File.write(struct.pack("b",0));
# The chunk is complete
ChunkSize.Write();
else:
# Write the null-terminated file format identification string
File.write("U3DModelFile\0");
# Write the file version
File.write(struct.pack("f",1.0));
#Write the mesh count (twice)
File.write(struct.pack("HH",self.nMesh,self.nMesh));
# Write the frame count
File.write(struct.pack("L",self.nFrame));
# Write the LoD count
File.write(struct.pack("H",1));
# Write the material count
File.write(struct.pack("H",self.nMaterial));
# Write the bone count
File.write(struct.pack("H",self.nBone));
# Write the cel-shading line strength and the cel-shading color
File.write(struct.pack("5f",0.0,0.0,0.0,0.0,0.0));
# Write whether this model uses alpha blending
File.write(struct.pack("b",1));
# Write whether this model uses vertex tweening
File.write(struct.pack("b",0));
# These variables save the collected header information
nMesh=0;
nFrame=0;
nMaterial=0;
nBone=0;
nTexCoordSet=0;
class CVertex:
"""This class represents a single vertex. It always contains a position and a
normal. Additionally it may contain skin weights with skinning indices and an
arbitrary number of texture coordinates."""
def __init__(self,VertexPosition=C3DVector(0.0,0.0,0.0),VertexNormal=C3DVector(0.0,0.0,0.0),VertexTextureCoordinate=None):
"""This constructor initializes this vertex with the given position, normal
and optionally a single texture coordinate (may be None)."""
self.Position=VertexPosition;
self.Normal=VertexNormal;
pTexCoord=[];
if(VertexTextureCoordinate!=None):
self.pTexCoord.add(VertexTextureCoordinate);
pSkinWeight=[];
piSkinBone=[];
def Compare(self,RHS):
"""This function returns true, if and only if all data held by the given
vertex matches the data of this vertex exactly."""
Result=(self.Position.x==RHS.Position.x and self.Position.y==RHS.Position.y and self.Position.z==RHS.Position.z);
Result=Result and (self.Normal.x==RHS.Normal.x and self.Normal.y==RHS.Normal.y and self.Normal.z==RHS.Normal.z);
Result=Result and (len(self.pTexCoord)==len(RHS.pTexCoord));
Result=Result and (len(self.pSkinWeight)==len(RHS.pSkinWeight));
Result=Result and (len(self.piSkinBone)==len(RHS.piSkinBone));
if(Result):
for i in range(len(self.pTexCoord)):
Result=Result and (self.pTexCoord[i].x==RHS.pTexCoord[i].x and self.pTexCoord[i].y==RHS.pTexCoord[i].y);
for i in range(len(self.pSkinWeight)):
Result=Result and (self.pSkinWeight[i]==RHS.pSkinWeight[i]);
for i in range(len(self.piSkinBone)):
Result=Result and (self.piSkinBone[i]==RHS.piSkinBone[i]);
return Result;
Position=C3DVector(0.0,0.0,0.0);
Normal=C3DVector(0.0,1.0,0.0);
# Either zero or three skin weights for this vertex
pSkinWeight=[];
# Either zero or four skinning indices for this vertex
piSkinBone=[];
# An arbitrary number of texture coordinates for this vertex (using C2DVector)
pTexCoord=[];
class CBranch:
"""This class represents a branch of an octree. A branch covers a cubic area and
it holds either a list of appended vertices (if it is a leave branch) or a
list of eight child branches."""
def __init__(self,CubeCenterIn,CubeSizeIn,DepthIn=0):
"""This constructor initializes this branch without child branches or
appended vertices. It makes it cover the given axis aligned cubic area."""
# Save the input data
self.CubeCenter=CubeCenterIn;
self.CubeSize=CubeSizeIn;
self.Depth=DepthIn;
# Initialize the branch with no children and no vertices
self.pChildBranch=[];
self.piVertex=[];
def Add(self,iVertex,pVertex):
"""This function adds the vertex with the given index to this branch. It
fails changing nothing, if this branch is not a leaf branch! If the
maximal number of vertices per branch is exceeded through this step, the
branch is split up. For this reason it may need the vertex list."""
if(not self.IsLeaf()):
return False;
# Add the vertex
self.piVertex.append(iVertex);
# If necessary split the branch
if(len(self.piVertex)>16):
self.Split(pVertex);
return True;
def Split(self,pVertex):
"""This function splits this branch, meaning that it converts it from a leaf
branch to a normal branch. Its vertices are moved to the newly created
child branches. For non-leaf branches it does nothing. If the maximal
depth would get exceeded by another split, it does nothing. It needs the
vertex list to look up vertex positions."""
if(not self.IsLeaf() or self.Depth>=7):
return False;
# Create the eight child branches
HalfCubeSize=self.CubeSize*0.5;
P=C3DVector(self.CubeCenter.x+HalfCubeSize,self.CubeCenter.y+HalfCubeSize,self.CubeCenter.z+HalfCubeSize);
N=C3DVector(self.CubeCenter.x-HalfCubeSize,self.CubeCenter.y-HalfCubeSize,self.CubeCenter.z-HalfCubeSize);
self.pChildBranch=[
CBranch(C3DVector(N.x,N.y,N.z),HalfCubeSize,self.Depth+1),
CBranch(C3DVector(N.x,N.y,P.z),HalfCubeSize,self.Depth+1),
CBranch(C3DVector(N.x,P.y,N.z),HalfCubeSize,self.Depth+1),
CBranch(C3DVector(N.x,P.y,P.z),HalfCubeSize,self.Depth+1),
CBranch(C3DVector(P.x,N.y,N.z),HalfCubeSize,self.Depth+1),
CBranch(C3DVector(P.x,N.y,P.z),HalfCubeSize,self.Depth+1),
CBranch(C3DVector(P.x,P.y,N.z),HalfCubeSize,self.Depth+1),
CBranch(C3DVector(P.x,P.y,P.z),HalfCubeSize,self.Depth+1)
];
# Pop the appended vertices from this branch and append them to the right
# child branch
while(len(self.piVertex)>0):
iVertex=self.piVertex.pop();
self.GetChildBranch(pVertex[iVertex].Position).Add(iVertex,pVertex);
return True;
def GetChildBranch(self,Position):
"""This function returns the child branch, which contains the given position.
It does not test, whether the position is in this branch, so the result
may be meaningless. For leaf branches it causes an exception!"""
XPositive=Position.x>self.CubeCenter.x;
YPositive=Position.y>self.CubeCenter.y;
ZPositive=Position.z>self.CubeCenter.z;
return self.pChildBranch[4*XPositive+2*YPositive+1*ZPositive];
def IsLeaf(self):
"""This function returns True, if and only if this branch is a leaf branch."""
return len(self.pChildBranch)==0;
# This C3DVector gives the center of the cubic area covered by this branch
CubeCenter=C3DVector(0.0,0.0,0.0);
# This floating point value gives the edge length of the cubic area covered by
# this branch
CubeSize=0.0;
# This integer saves the depth of this branch. It is needed to avoid endless
# splitting.
Depth=0;
# This is a list of the children of this branch. For leaf branches it is empty
# otherwise it gives exactly eight child branches.
pChildBranch=[];
# This is a list of the indices of the vertices which are appended to this
# branch. It should be empty, if this branch is not a leaf branch.
piVertex=[];
class COctree:
"""This class is used to sort vertices by their position. This makes it
possible to find double vertices more efficiently. It is roughly
an increasement from O(n^2) to O(n*log(n))."""
def __init__(self,CubeCenter,CubeSize):
"""This constructor initializes this octree so that it can cover an axis
aligned cubic area with edge length CubeSize and its center at the given
C3DVector. It associates it with the given vertex list. Vertex indices
always refer to this list."""
# Initialize the root branch to cover the given area
self.Root=CBranch(CubeCenter,CubeSize);
def FindBranch(self,Position):
"""This function finds the leaf branch, which covers the given position and
returns it. It does not verify that the position is in this octree."""
Result=self.Root;
while(not Result.IsLeaf()):
Result=Result.GetChildBranch(Position);
return Result;
# This is the root branch of the octree
Root=None;
class CMesh:
"""This class is used to collect and export mesh data"""
def __init__(self,MeshObject,MaterialList,BoneList,MeshTransformation,iMeshPerFrame,UVLayerSet):
"""This constructor collects mesh data from the given mesh. The given
material and bone list are used for finding indices, the mesh
transformation is applied to the vertex data, double vertices are removed,
the given index is saved and if UVLayerSet is not None all UV layers in
the given set will be saved in the vertex data."""
self.CleanUp();
# Save the mesh index
self.iMeshPerFrame=iMeshPerFrame;
# Get the mesh name
self.Name=MeshObject.name;
# Get the number of texture coordinate sets and a list of UV layers to export
if(MeshObject.vertexUV or MeshObject.faceUV):
self.nTexCoordSet=1;
else:
self.nTexCoordSet=0;
if(UVLayerSet!=None):
self.nTexCoordSet=len(UVLayerSet);
elif(self.nTexCoordSet==1):
UVLayerSet=frozenset([MeshObject.getUVLayerNames()[0]]);
elif(self.nTexCoordSet==0):
UVLayerSet=frozenset([]);
# Compute the complete mesh transformation
if(len(BoneList)==0):
MeshToU3DSpace=BlenderToU3DSpace.TransformMatrix(MeshTransformation);
else:
MeshToU3DSpace=MeshTransformation;
# Determine whether the mesh is partially solid (in this case the
# creation of the vertex list is more complicated)
Solid=False;
for Face in MeshObject.faces:
if(not Face.smooth):
Solid=True;
break;
# There are two different cases when it comes to mapping Blender vertex
# indices to the indices used in the output:
# 1) They are identical (if UseFaceVertices==False)
# 2) The output indices are the unique face vertex indices. Face vertex
# indices can be obtained by checking how many vertices are used by faces
# with smaller index or earlier in the same face. To get to indices of
# unique face vertices you need to map face vertex indices through
# piFaceVertex. Multiple face vertex indices may correspond to the same
# Blender vertex index and a single unique face vertex may refer to
# multiple Blender vertex indices (if UseFaceVertices==True).
# All of this is necessary because different faces may associate different
# texture coordinates and normals with a single Blender vertex.
piFaceVertex=[];
# The bounding box may be needed to remove double vertices efficiently using
# an octree. Initialize vectors to determine it.
MinVec=C3DVector( 1.175494351e-38, 1.175494351e-38, 1.175494351e-38);
MaxVec=C3DVector(-1.175494351e-38,-1.175494351e-38,-1.175494351e-38);
# If the texture coordinates and the normals are saved per vertex the
# process is simple
UseFaceVertices=((MeshObject.vertexUV==False and MeshObject.faceUV) or Solid);
if(not UseFaceVertices):
for v in MeshObject.verts:
# Collect the data of the vertex and add it to the list
NewVertex=CVertex();
NewVertex.Position=C3DVector(v.co.x,v.co.y,v.co.z);
NewVertex.Normal=C3DVector(v.no.x,v.no.y,v.no.z);
NewVertex.pTexCoord=[];
self.pVertex.append(NewVertex);
# Update the bounding box
MinVec.Minimize(NewVertex.Position);
MaxVec.Maximize(NewVertex.Position);
self.nVertex=len(self.pVertex);
# Get the texture coordinates from all relevant UV layers
PreviousUVLayer=MeshObject.activeUVLayer;
for UVLayer in UVLayerSet:
MeshObject.activeUVLayer=UVLayer;
for j in range(self.nVertex):
if(MeshObject.vertexUV):
self.pVertex[j].pTexCoord.append(C2DVector(MeshObject.verts[j].uvco.x,MeshObject.verts[j].uvco.y));
else:
self.pVertex[j].pTexCoord.append(C2DVector(0.0,0.0));
if(PreviousUVLayer!=None):
MeshObject.activeUVLayer=PreviousUVLayer;
# If the texture coordinates or the normals are saved on per triangle
# base get them from there
else:
# This case is a little complicated. One vertex gets created for every
# face vertex. Then the list gets reduced to unique combinations of
# positions, texture coordinates and normals. During this process a
# lookup table for creating the index buffer gets created.
# For all faces iterate through all vertices and create a list of them
pFaceVertex=[];
for i in range(len(MeshObject.faces)):
for j in range(len(MeshObject.faces[i].verts)):
NewVertex=CVertex();
# Get the position
Position=MeshObject.faces[i].verts[j].co;
NewVertex.Position=C3DVector(Position.x,Position.y,Position.z);
# Get the normal
if(MeshObject.faces[i].smooth):
Normal=MeshObject.faces[i].verts[j].no;
else:
Normal=MeshObject.faces[i].no;
NewVertex.Normal=C3DVector(Normal.x,Normal.y,Normal.z);
NewVertex.pTexCoord=[];
pFaceVertex.append(NewVertex);
# Update the bounding box
MinVec.Minimize(NewVertex.Position);
MaxVec.Maximize(NewVertex.Position);
# Get the texture coordinates from all relevant UV layers
PreviousUVLayer=MeshObject.activeUVLayer;
for UVLayer in UVLayerSet:
MeshObject.activeUVLayer=UVLayer;
iFaceVertex=0;
for i in range(len(MeshObject.faces)):
for j in range(len(MeshObject.faces[i].verts)):
# Get the texture coordinate or use a default value
if(MeshObject.vertexUV):
TexCoord=MeshObject.faces[i].verts[j].uvco
elif(MeshObject.faceUV):
TexCoord=MeshObject.faces[i].uv[j];
else:
TexCoord=C2DVector(0.0,0.0);
pFaceVertex[iFaceVertex].pTexCoord.append(C2DVector(TexCoord.x,TexCoord.y));
iFaceVertex+=1;
if(PreviousUVLayer!=None):
MeshObject.activeUVLayer=PreviousUVLayer;
# Now remove double vertices
# Inform the client
print("Removing double vertices for the mesh \""+self.Name+"\".");
# Create an octree, which can contain the entire bounding box. All
# unique vertices are added to it. This way it is possible to
# determine uniqueness of vertices fast.
BBoxSize=C3DVector(MaxVec.x-MinVec.x,MaxVec.y-MinVec.y,MaxVec.z-MinVec.z);
Octree=COctree(C3DVector(MinVec.x+BBoxSize.x*0.5,MinVec.y+BBoxSize.y*0.5,MinVec.z+BBoxSize.z*0.5),max(BBoxSize.x,BBoxSize.y,BBoxSize.z));
# Cycle through all vertices and check whether they equal one of the
# unique vertices found so far. If they do not append them.
nUniqueVertex=0;
for FaceVertex in pFaceVertex:
# Search the vertex in the octree
Found=False;
Branch=Octree.FindBranch(FaceVertex.Position);
for i in Branch.piVertex:
if(FaceVertex.Compare(pFaceVertex[i])):
# Use the existing identical vertex
piFaceVertex.append(i);
Found=True;
break;
if(not Found):
# A new unique vertex has been found. Add it to the list and
# to the octree. The right branch is already known.
pFaceVertex[nUniqueVertex]=FaceVertex;
piFaceVertex.append(nUniqueVertex);
Branch.Add(nUniqueVertex,pFaceVertex);
nUniqueVertex+=1;
print("Removed "+str(len(pFaceVertex)-nUniqueVertex)+" vertices ("+str(100-int((100.0*nUniqueVertex)/len(pFaceVertex)))+"% and "+str(nUniqueVertex)+" remain).");
# Reducing the list has been done in the list. Slice away the
# remaining duplicate vertices and set it up as mesh vertex list.
self.pVertex=pFaceVertex[0:nUniqueVertex];
self.nVertex=nUniqueVertex;
# Coordinates in Blender and U3D differ. Transform the vertex data as needed.
for Vertex in self.pVertex:
Vertex.Position=MeshToU3DSpace.TransformVector(Vertex.Position);
Vertex.Normal=MeshToU3DSpace.RotateAndScaleVector(Vertex.Normal);
for TexCoord in Vertex.pTexCoord:
TexCoord.y=1.0-TexCoord.y;
# Get the triangle count and the triangles with their material indices
iFaceVertex=0;
for Face in MeshObject.faces:
# See the comment of piFaceVertex
piVertex=[];
if(not UseFaceVertices):
for FaceVertex in Face.verts:
# Use Blender indices
piVertex.append(FaceVertex.index);
else:
for i in range(len(Face.verts)):
# Use indices of unique face vertices
piVertex.append(piFaceVertex[iFaceVertex+i]);
# Put the three or four face vertex indices togher to one or two
# triangles (the code is ready for faces with more vertices)
for i in range(1,len(Face.verts)-1):
self.pTriangle.append(CTriangleIndices(piVertex[0],piVertex[i+1],piVertex[i]));
if(len(MeshObject.materials)>0):
if(MeshObject.materials[Face.mat] in MaterialList):
self.piTriangleMaterial.append(MaterialList.index(MeshObject.materials[Face.mat]));
else:
self.piTriangleMaterial.append(0);
else:
self.piTriangleMaterial.append(0);
self.nTriangle+=1;
iFaceVertex+=len(Face.verts);
# Get the skinning weights if necessary
pSkinnedVertex=[];
if(len(BoneList)>0):
self.SkinningUsed=True;
self.iMaxBone=len(BoneList)-1;
# Create a list representing the data of the skin weights for all Blender
# vertices. If face vertices are not used they are written to the output
# vertices immediately.
for i in range(len(MeshObject.verts)):
# Get the influences of this vertex
pInfluence=MeshObject.getVertexInfluences(i);
# Create a vertex. Only the skin weight members are actually used
Weight=None;
if(UseFaceVertices):
pSkinnedVertex.append(CVertex());
Weight=pSkinnedVertex[i];
else:
Weight=self.pVertex[i];
Weight.pSkinWeight=[];
Weight.piSkinBone=[];
# Find the four biggest bone influences
while(len(Weight.pSkinWeight)<4 and len(pInfluence)>0):
# Determine the biggest influence
MaximumInfluence=0.0;
iMaximumInfluence=0;
for j in range(len(pInfluence)):
if(MaximumInfluence0):
# Cycle through all face vertices
iFaceVertex=0;
for i in range(len(MeshObject.faces)):
for j in range(len(MeshObject.faces[i].verts)):
# And assign the matching skinning data
iVertex=piFaceVertex[iFaceVertex];
Destination=self.pVertex[iVertex];
Source=pSkinnedVertex[MeshObject.faces[i].verts[j].index];
Destination.pSkinWeight=Source.pSkinWeight;
Destination.piSkinBone=Source.piSkinBone;
iFaceVertex+=1;
def SaveData(self,File,UseV2):
"""This function writes the collected data to the given file object"""
# Check whether the model can be written
if(self.nVertex>65535 or self.nTriangle>65535):
print("The model can not be written to an Ultimate 3D model file, because one of the meshes has more than 65535 triangles or vertices.");
Draw.PupMenu("Error%t|The model can not be exported because one mesh contains more than 65535 triangles/vertices.");
return False;
if(UseV2):
# Write the chunk header
File.write("$U3D_MESH\0");
ChunkSize=CChunkSizeWriter(File);
# Write the mesh ID values
File.write(struct.pack("LLL",self.iMeshPerFrame,0,0));
else:
# Write the null-terminated chunk identifier
File.write("$U3D_MESH\0");
# Write the null-terminated mesh name
File.write(self.Name+"\0");
if(UseV2==False):
# Write the frame index for the mesh (0)
File.write(struct.pack("H",0));
# Write the lod of this mesh (1.0)
File.write(struct.pack("f",1.0));
# Write the normal scalar
NormalScalar=Choose(self.SkinningUsed,-1.0,1.0);
File.write(struct.pack("f",NormalScalar));
# Write whether this mesh contains valid inverse tangent space matrices
File.write(struct.pack("b",0));
# Write the vertex count
File.write(struct.pack(Choose(UseV2,"L","H"),self.nVertex));
if(UseV2==False):
# Write the texture coordinate dimension array
for i in range(self.nTexCoordSet):
File.write(struct.pack("H",2));
for i in range(self.nTexCoordSet,8):
File.write(struct.pack("H",0));
# Write the number of skin weights per vertex
File.write(struct.pack("H",3*self.SkinningUsed));
# Write the maximum index of the bones that are influencing this mesh
File.write(struct.pack("B",self.iMaxBone));
# Write the vertex and triangle count of shadow optimized geometry
File.write(struct.pack("HH",0,0));
# Write the vertex positions
for Vertex in self.pVertex:
Vertex.Position.SaveData(File);
# Write the vertex normals
if(UseV2):
for Vertex in self.pVertex:
# In version 2.0 the normal scalar is applied to the normals
# in the file after loading. Since the normals are correct now
# they need to be identical after loading. Scale them by the
# normal scalar now (1*1=(-1)*(-1)=1).
ScaledNormal=Vertex.Normal;
ScaledNormal.x*=NormalScalar;
ScaledNormal.y*=NormalScalar;
ScaledNormal.z*=NormalScalar;
ScaledNormal.SaveAsCompressedNormal(File,UseV2);
else:
for Vertex in self.pVertex:
Vertex.Normal.SaveAsCompressedNormal(File,UseV2);
if(UseV2==False):
# Whether a texture coordinate buffer is to be used and whether the
# buffer is owned
File.write(struct.pack("b",0));
File.write(struct.pack("b",1));
# Write the vertex texture coordinates
for i in range(self.nTexCoordSet):
for Vertex in self.pVertex:
Vertex.pTexCoord[i].SaveData(File);
if(UseV2 and self.nTexCoordSet==0):
NullTexCoord=C2DVector(0.0,0.0);
for i in range(self.nVertex):
NullTexCoord.SaveData(File);
# Export skinning data, if necessary
if(self.SkinningUsed):
# Write the skin weights
for Vertex in self.pVertex:
File.write(struct.pack("3f",Vertex.pSkinWeight[0],Vertex.pSkinWeight[1],Vertex.pSkinWeight[2]));
# Write the bone indices for each vertex
for Vertex in self.pVertex:
File.write(struct.pack("4B",Vertex.piSkinBone[0],Vertex.piSkinBone[1],Vertex.piSkinBone[2],Vertex.piSkinBone[3]));
# Write the triangle count
File.write(struct.pack(Choose(UseV2,"L","H"),self.nTriangle));
# Whether the triangle indices are owned by this mesh (true)
File.write(struct.pack("b",1));
# Write the index buffer
for Triangle in self.pTriangle:
Triangle.SaveData(File);
# Write the triangle material indices
for i in range(self.nTriangle):
File.write(struct.pack("H",self.piTriangleMaterial[i]));
if(UseV2):
# Whether there is shadow optimized geometry (false)
File.write(struct.pack("b",0));
# The chunk is completed
ChunkSize.Write();
# The function has succeeded
return True;
def AppendMesh(self,MeshObject):
"""This function appends the given mesh object to this mesh object."""
# Combine the names
self.Name=self.Name+"+"+MeshObject.Name;
# Combine the triangle lists
for i in range(MeshObject.nTriangle):
self.pTriangle.append(CTriangleIndices(MeshObject.pTriangle[i].A+self.nVertex,MeshObject.pTriangle[i].B+self.nVertex,MeshObject.pTriangle[i].C+self.nVertex));
self.piTriangleMaterial.append(MeshObject.piTriangleMaterial[i]);
self.nTriangle+=MeshObject.nTriangle;
# Combine the vertex lists
self.pVertex.extend(MeshObject.pVertex);
self.nVertex+=MeshObject.nVertex;
def CleanUp(self):
"""This function cleans up this mesh object completely."""
self.iMeshPerFrame=0;
self.Name="";
self.nVertex=self.nTriangle=0;
self.nTexCoordSet=0;
self.pVertex=[];
self.pTriangle=[];
self.piTriangleMaterial=[];
self.SkinningUsed=False;
self.iMaxBone=0;
# The index of this mesh
iMeshPerFrame=0;
# The name of this mesh
Name="";
# The number of vertices and triangles
nVertex=0;
nTriangle=0;
# This integer saves the number of texture coordinate sets available in this mesh
nTexCoordSet=0;
# The array of vertices
pVertex=[];
# If this boolean is True the vertices have three skinning weights and four
# skinning indices. Otherwise they have neither weights nor indices.
SkinningUsed=False;
# This integer saves the highest referenced bone index (according to the skinning
# indices)
iMaxBone=0;
# The triangle index array
pTriangle=[];
# The triangle material index array
piTriangleMaterial=[];
def BlenderTexOpToD3D(BlendMode):
"""This is a little utility function, which converts a given Blender texture
operation (resp. blend mode) to the D3DTEXTUREOP DWORD value, which matches it
best."""
if(BlendMode==Blender.Texture.BlendModes["MIX"]):
return 13;
elif(BlendMode==Blender.Texture.BlendModes["MULTIPLY"]):
return 4;
elif(BlendMode==Blender.Texture.BlendModes["ADD"]):
return 7;
elif(BlendMode==Blender.Texture.BlendModes["SUBTRACT"]):
return 10;
elif(BlendMode==Blender.Texture.BlendModes["DIVIDE"]):
return 4;
elif(BlendMode==Blender.Texture.BlendModes["DARKEN"]):
return 4;
elif(BlendMode==Blender.Texture.BlendModes["DIFFERENCE"]):
return 10;
elif(BlendMode==Blender.Texture.BlendModes["LIGHTEN"]):
return 7;
elif(BlendMode==Blender.Texture.BlendModes["SCREEN"]):
return 4;
return 1;
class CMaterial:
"""This class is used to collect data about materials and to write it to
a *.u3d file."""
def __init__(self,MaterialObject,TexturePath,iMaterial,UVLayerSet):
"""This constructor collects data from the given material object."""
self.CleanUp();
# Get the material index and the name
self.iMaterial=iMaterial;
self.Name=MaterialObject.name;
# Get the material colors
self.AmbientColor=CColor(MaterialObject.R*MaterialObject.amb,MaterialObject.G*MaterialObject.amb,MaterialObject.B*MaterialObject.amb,MaterialObject.alpha);
self.DiffuseColor=CColor(MaterialObject.R*MaterialObject.ref,MaterialObject.G*MaterialObject.ref,MaterialObject.B*MaterialObject.ref,MaterialObject.alpha);
self.SpecularColor=CColor(MaterialObject.specR*MaterialObject.spec,MaterialObject.specG*MaterialObject.spec,MaterialObject.specB*MaterialObject.spec,1.0);
self.EmissiveColor=CColor(MaterialObject.R*MaterialObject.emit,MaterialObject.G*MaterialObject.emit,MaterialObject.B*MaterialObject.emit,1.0);
self.SpecularPower=MaterialObject.getHardness();
# Determine the number of used textures and get the list of texture
# files (after unpacking them in case they are packed)
self.nTexture=0;
for iTexture in MaterialObject.enabledTextures:
Texture=MaterialObject.getTextures()[iTexture];
if(Texture==None):
continue;
Image=Texture.tex.getImage();
if(Image==None):
continue;
# Determine the texture coordinate set
iTexCoordSet=0;
if(UVLayerSet):
iUVLayer=0
for UVLayer in UVLayerSet:
if(Texture.uvlayer==UVLayer):
iTexCoordSet=iUVLayer;
iUVLayer+=1;
self.piTexCoordSet.append(iTexCoordSet);
# Get the texture operation
self.pTextureOperation.append(Texture.blendmode);
# Get the file name (without path)
FileName=Image.getFilename();
for i in range(len(FileName)-1,0,-1):
if(FileName[i]=="/" or FileName[i]=="\\"):
FileName=FileName[i+1:];
break;
# Remove slashes from the beginning of the file name with path
FileNameWithPath=Image.getFilename();
while FileNameWithPath[0]=="\\" or FileNameWithPath[0]=="/":
FileNameWithPath=FileNameWithPath[1:];
# Unpack the texture if necessary and remember whether the resulting file
# should be deleted after repacking
Packed=Image.packed;
DeleteTexture=not os.path.exists(FileNameWithPath);
if(Image.packed):
Image.unpack(Blender.UnpackModes["WRITE_LOCAL"]);
# Copy the texture file to the texture path
if(os.path.normcase(os.path.normpath(os.path.abspath(FileNameWithPath)))!=os.path.normcase(os.path.normpath(os.path.abspath(TexturePath+FileName)))):
if(os.path.exists(TexturePath+FileName)):
try:
os.remove(TexturePath+FileName);
except OSError:
print("Failed to remove the recently exported texture file \""+TexturePath+FileName+"\".");
try:
shutil.copyfile(FileNameWithPath,TexturePath+FileName);
except IOError:
print("Failed to copy the texture file \""+FileNameWithPath+"\" to the Textures folder (\""+TexturePath+FileName+"\"). You may be able to fix this by opening the file directly with Blender instead of opening it through File->Open.");
# Add the texture to the list
self.nTexture+=1;
self.pTextureFile.append(FileName);
# Repack the texture in case it has been unpacked
if(Packed and os.path.exists(FileNameWithPath)):
Image.pack();
# Ensure that the file is deleted, or in existance dependent on its
# original state
if(DeleteTexture):
if(os.path.exists(FileNameWithPath)):
try:
os.remove(FileNameWithPath);
except OSError:
print("Failed to remove a temporarily unpacked texture file.");
else:
if(not os.path.exists(FileNameWithPath)):
Image.save();
def SaveData(self,File,UseV2):
"""This function writes the collected data to the given file."""
# Write the null-terminated the chunk header or the material identifier
if(UseV2):
File.write("$U3D_MATERIAL\0");
ChunkSize=CChunkSizeWriter(File);
# Write the material index
File.write(struct.pack("L",self.iMaterial));
else:
File.write("$U3D_MAT\0");
# Write the null-terminated material name
File.write(self.Name+"\0");
# Write default material colors
self.AmbientColor.SaveData(File);
self.DiffuseColor.SaveData(File);
self.SpecularColor.SaveData(File);
self.EmissiveColor.SaveData(File);
# Write the specular power
File.write(struct.pack("f",self.SpecularPower));
# Write the parallax information
if(UseV2):
File.write(struct.pack("ff",0.0,1.0));
# Write the number of textures
if(UseV2==False):
self.nTexture=min(8,self.nTexture);
File.write(struct.pack("H",self.nTexture));
# Write default texture operations
pTextureOperation=[1,1,1,1,1,1,1,1];
if(self.nTexture>0):
pTextureOperation[0]=5;
for i in range(1,self.nTexture):
pTextureOperation[i]=BlenderTexOpToD3D(self.pTextureOperation[i]);
for i in range(0,8):
File.write(struct.pack("L",pTextureOperation[i]));
# Write texture coordinate sets
for i in range(0,self.nTexture):
File.write(struct.pack("L",self.piTexCoordSet[i]));
for i in range(self.nTexture,8):
File.write(struct.pack("L",0));
# Write whether the resources are part of the file (false)
if(UseV2==False):
File.write(struct.pack("b",0));
# Write the texture data
if(UseV2):
for i in range(8):
if(i0):
BoneToParentSpace=BoneObject.matrix["ARMATURESPACE"]*(BlenderBoneList[self.iParent-1].matrix["ARMATURESPACE"].copy().invert());
else:
BoneToParentSpace=BoneObject.matrix["ARMATURESPACE"]*ArmatureObj.getMatrix();
# Create the keys
for Action in pAction:
if(len(Action.getFrameNumbers())==0):
continue;
if(ExportThroughIpo):
self.AddKeysFromActionIpo(Action,BoneToParentSpace);
else:
self.AddKeysFromActionPose(Action,ArmatureObj);
self.nFrame+=max(Action.getFrameNumbers())+1;
# Make sure that there's at least one key of every type to have the
# bone to parent space transformation included
if(len(self.pTranslationKey)==0):
BoneToParentTranslation=BoneToParentSpace.translationPart();
self.pTranslationKey.append(CTranslationKey(0,C3DVector(BoneToParentTranslation[0],BoneToParentTranslation[1],BoneToParentTranslation[2])));
if(len(self.pRotationKey)==0):
BoneToParentRotation=BoneToParentSpace.toQuat();
self.pRotationKey.append(CRotationKey(0,CQuaternion(BoneToParentRotation.x,BoneToParentRotation.y,BoneToParentRotation.z,BoneToParentRotation.w)));
if(len(self.pScalingKey)==0):
BoneToParentScaling=BoneToParentSpace.scalePart();
self.pScalingKey.append(CScalingKey(0,C3DVector(BoneToParentScaling.x,BoneToParentScaling.y,BoneToParentScaling.z)));
def AddKeysFromActionIpo(self,Action,BoneToParentSpace):
"""This function adds key frames from the given action to this bone.
oFrame will be added to the frame values of the key frames."""
# Get the relevant Ipo
try:
AnimationIpo=Action.getChannelIpo(self.Name);
except:
# There's no animation for this bone, return
return;
# Get the transformation curves in the ipo
TranslationXCurve=TranslationYCurve=TranslationZCurve=None;
RotationXCurve=RotationYCurve=RotationZCurve=RotationWCurve=None;
ScalingXCurve=ScalingYCurve=ScalingZCurve=None;
for Curve in AnimationIpo:
if(Curve.name=="PO_LOCX" or Curve.name=="LocX"):
TranslationXCurve=Curve;
elif(Curve.name=="PO_LOCY" or Curve.name=="LocY"):
TranslationYCurve=Curve;
elif(Curve.name=="PO_LOCZ" or Curve.name=="LocZ"):
TranslationZCurve=Curve;
elif(Curve.name=="PO_QUATX" or Curve.name=="QuatX"):
RotationXCurve=Curve;
elif(Curve.name=="PO_QUATY" or Curve.name=="QuatY"):
RotationYCurve=Curve;
elif(Curve.name=="PO_QUATZ" or Curve.name=="QuatZ"):
RotationZCurve=Curve;
elif(Curve.name=="PO_QUATW" or Curve.name=="QuatW"):
RotationWCurve=Curve;
elif(Curve.name=="PO_SIZEX" or Curve.name=="SizeX"):
ScalingXCurve=Curve;
elif(Curve.name=="PO_SIZEY" or Curve.name=="SizeY"):
ScalingYCurve=Curve;
elif(Curve.name=="PO_SIZEZ" or Curve.name=="SizeZ"):
ScalingZCurve=Curve;
# Create the keys (the transformation described by them needs to be
# "multiplied from the right" with the bone to parent space matrix)
SortedKeyFrameList=Action.getFrameNumbers();
SortedKeyFrameList.sort();
BoneToParentScaling=BoneToParentSpace.scalePart();
# If the scaling is not homogenous output a warning
if(BoneToParentScaling.xBoneToParentScaling.y+0.001 or BoneToParentScaling.yBoneToParentScaling.z+0.001):
print("WARNING: The bone "+self.Name+" has an inhomogenous scaling in relation to its parent. Since inhomogenous scaling (scaling that's not the same for all axes) is not supported the scaling has been made homogenous.");
print("The inhomogenous scaling is X="+str(BoneToParentScaling.x)+" Y="+str(BoneToParentScaling.y)+" Z="+str(BoneToParentScaling.z));
BoneToParentScaling.x=BoneToParentScaling.y=BoneToParentScaling.z=(BoneToParentScaling.x+BoneToParentScaling.y+BoneToParentScaling.z)/3.0;
BoneToParentTranslation=BoneToParentSpace.translationPart();
BoneToParentRotation=BoneToParentSpace.toQuat();
BoneToParentRotation=CQuaternion(BoneToParentRotation.x,BoneToParentRotation.y,BoneToParentRotation.z,BoneToParentRotation.w);
for i in SortedKeyFrameList:
KeyTranslation=C3DVector(0.0,0.0,0.0);
KeyScaling=C3DVector(1.0,1.0,1.0);
KeyRotation=CQuaternion(0.0,0.0,0.0,1.0);
if(TranslationXCurve!=None):
KeyTranslation=C3DVector(TranslationXCurve[i],TranslationYCurve[i],TranslationZCurve[i]);
if(RotationXCurve!=None):
KeyRotation=CQuaternion(RotationXCurve[i],RotationYCurve[i],RotationZCurve[i],RotationWCurve[i]);
if(ScalingXCurve!=None):
KeyScaling=C3DVector(ScalingXCurve[i],ScalingYCurve[i],ScalingZCurve[i]);
# Transform the key data
KeyScaling=C3DVector(KeyScaling.x*BoneToParentScaling.x,KeyScaling.y*BoneToParentScaling.y,KeyScaling.z*BoneToParentScaling.z);
RotatedTranslation=BoneToParentRotation.RotateVector(KeyTranslation.x,KeyTranslation.y,KeyTranslation.z);
KeyTranslation=C3DVector(RotatedTranslation.x+BoneToParentTranslation.x,RotatedTranslation.y+BoneToParentTranslation.y,RotatedTranslation.z+BoneToParentTranslation.z);
KeyRotation.AppendRotation(BoneToParentRotation);
# Save the transformed key data
if(TranslationXCurve!=None):
self.pTranslationKey.append(CTranslationKey(i+self.nFrame,KeyTranslation));
if(RotationXCurve!=None):
self.pRotationKey.append(CRotationKey(i+self.nFrame,KeyRotation));
if(ScalingXCurve!=None):
self.pScalingKey.append(CScalingKey(i+self.nFrame,KeyScaling));
def AddKeysFromActionPose(self,Action,ArmatureObj):
"""This function implements an important feature in the only possible
way, which is very ugly. It makes it possible to retrieve
animations, which use constraints like IKs. The keys will be appended
to the array of this instance."""
# Get the pose
Pose=ArmatureObj.getPose();
# Save the current pose and clear the pose bone transformations
pPoseBoneTranslation=[];
pPoseBoneScaling=[];
pPoseBoneRotation=[];
for PoseBone in Pose.bones.values():
pPoseBoneTranslation.append(PoseBone.loc);
pPoseBoneScaling.append(PoseBone.size);
pPoseBoneRotation.append(PoseBone.quat);
PoseBone.loc.x=PoseBone.loc.y=PoseBone.loc.z=0.0;
PoseBone.size.x=PoseBone.size.y=PoseBone.size.z=1.0;
PoseBone.quat.x=PoseBone.quat.y=PoseBone.quat.z=0.0;
PoseBone.quat.w=1.0;
# Save some information that needs to be restored
PreviousAction=ArmatureObj.getAction();
PreviousFrame=Blender.Get("curframe");
# Activate the given action
Action.setActive(ArmatureObj);
# Cycle through all relevant frames
# NOTE: It may be that additional frames are relevant. An alternative,
# but also bad solution would be exporting one key for every frame.
SortedKeyFrameList=Action.getFrameNumbers();
if(0 not in SortedKeyFrameList):
SortedKeyFrameList.append(0);
SortedKeyFrameList.sort();
for Frame in SortedKeyFrameList:
# Set up the right frame for the pose
ArmatureObj.evaluatePose(Frame);
# Retrieve the transformation in relation to the parent
PoseBoneTransformation=Pose.bones[self.Name].poseMatrix;
if(Pose.bones[self.Name].parent!=None):
PoseBoneTransformation*=Pose.bones[self.Name].parent.poseMatrix.copy().invert();
else:
PoseBoneTransformation*=ArmatureObj.getMatrix();
# Create the transformation keys
Rotation=PoseBoneTransformation.toQuat();
Scaling=PoseBoneTransformation.scalePart();
Translation=PoseBoneTransformation.translationPart();
RotationFactor=1;
if(len(self.pRotationKey)>0):
PreviousRotation=self.pRotationKey[len(self.pRotationKey)-1].Rotation;
if(PreviousRotation.x*Rotation.x+PreviousRotation.y*Rotation.y+PreviousRotation.z*Rotation.z+PreviousRotation.w*Rotation.w<0.0):
RotationFactor=-1;
self.pRotationKey.append(CRotationKey(Frame+self.nFrame,CQuaternion(Rotation.x*RotationFactor,Rotation.y*RotationFactor,Rotation.z*RotationFactor,Rotation.w*RotationFactor)));
self.pScalingKey.append(CScalingKey(Frame+self.nFrame,C3DVector(Scaling.x,Scaling.y,Scaling.z)));
self.pTranslationKey.append(CTranslationKey(Frame+self.nFrame,C3DVector(Translation.x,Translation.y,Translation.z)));
# Restore the pose transformation
for PoseBone in Pose.bones.values():
PoseBone.loc=pPoseBoneTranslation.pop(0);
PoseBone.size=pPoseBoneScaling.pop(0);
PoseBone.quat=pPoseBoneRotation.pop(0);
# Restore the previous action and the previous frame
if(PreviousAction!=None):
PreviousAction.setActive(ArmatureObj);
else:
ArmatureObj.action=None;
Blender.Set("curframe",PreviousFrame);
def SaveData(self,File,UseV2):
"""This function saves all data of this bone to the given file."""
# Write the chunk header or the null-terminated bone chunk identifier
ChunkSize=None;
if(UseV2):
File.write("$U3D_BONE\0");
ChunkSize=CChunkSizeWriter(File);
# Write the bone index
if(UseV2 and self.iBone==65535):
File.write(struct.pack("L",0xFFFFFFFF));
else:
File.write(struct.pack("L",self.iBone));
else:
File.write("$U3D_BONE\0");
# Write the null-terminated name of this bone
File.write(self.Name+"\0");
# Write the index of the parent of this bone
File.write(struct.pack(Choose(UseV2,"L","H"),self.iParent));
# Write the bone frame offset (-1.0) and whether this is to effect the
# child frame
File.write(struct.pack("f",-1.0));
File.write(struct.pack("B",0));
# Write the number of meshes that are appended to this bone
File.write(struct.pack(Choose(UseV2,"L","H"),1));
# Write the index of the skinning mesh
File.write(struct.pack(Choose(UseV2,"L","H"),0));
# Write the mesh transformation
self.MeshTransformation.SaveData(File);
# Write the scaling keys
File.write(struct.pack(Choose(UseV2,"L","H"),len(self.pScalingKey)));
for ScalingKey in self.pScalingKey:
ScalingKey.SaveData(File,UseV2);
# Write the translation keys
File.write(struct.pack(Choose(UseV2,"L","H"),len(self.pTranslationKey)));
for TranslationKey in self.pTranslationKey:
TranslationKey.SaveData(File,UseV2);
# Write the rotation keys
File.write(struct.pack(Choose(UseV2,"L","H"),len(self.pRotationKey)));
for RotationKey in self.pRotationKey:
RotationKey.SaveData(File,UseV2);
# The chunk is completed
if(UseV2):
ChunkSize.Write();
def MakeBlenderToU3DSpaceBone(self):
self.CleanUp();
# Set up a bone index...
self.iBone=0;
# ... a descriptive name...
self.Name="BlenderToU3DSpaceBone";
# ...no parent...
self.iParent=65535;
# ...an identity mesh transformation...
self.MeshTransformation=CMatrix4x4(1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0);
# ...and keys that describe the BlenderToU3DSpace matrix.
self.pScalingKey=[CScalingKey(0,C3DVector(1.0,-1.0,1.0))];
self.pRotationKey=[CRotationKey(0,CQuaternion(1.0,0.0,0.0,-1.0))];
self.pTranslationKey=[];
self.nFrame=0;
def CleanUp(self):
"""This function erases all data that's saved within this bone."""
self.iBone=0;
self.Name="";
self.iParent=0;
self.MeshTransformation=CMatrix4x4(1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0);
self.pScalingKey=[];
self.pRotationKey=[];
self.pTranslationKey=[];
self.nFrame=0;
# The index of this bone
iBone=0;
# The name of this bone
Name="";
# The index of the parent bone
iParent=0;
# The transformation that is to be applied to the geometry before the
# final bone transformation
MeshTransformation=CMatrix4x4(1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0);
# The scaling keys of this bone
pScalingKey=[];
# The translation keys of this bone
pRotationKey=[];
# The rotation keys of this bone
pTranslationKey=[];
# The length of the animation of this bone
nFrame=0;
class CActionRange:
"""This class saves the range of a particular class and it can save it to a
*.u3d file."""
def __init__(self,Act,iFirstFrameIn):
"""This init function initializes this action range using data from the given
action and the given first frame."""
self.ActionName=Act.name;
self.iFirstFrame=iFirstFrameIn;
self.iLastFrame=iFirstFrameIn+max(Act.getFrameNumbers())+1;
def SaveData(self,File):
"""This function writes the data of this object to the given file."""
File.write(self.ActionName+"\0");
File.write(struct.pack("LL",self.iFirstFrame,self.iLastFrame));
# The name of the action
ActionName="";
# The first frame of the action
iFirstFrame=0;
# The last frame of the action
iLastFrame=0;
class CSkeleton:
"""This class is used to collect data about all armatures in a given scene
and to write it to a *.u3d file."""
def __init__(self,RelevantObjects,ExportConstraints,ExportActionList,ActionListFileName):
"""This init function initializes this skeleton from the armatures in
the given scene."""
# Create the Blender to U3D space root bone
self.BoneList=[CBone()];
self.BoneList[0].MakeBlenderToU3DSpaceBone();
# Create a list of absolutely all bones in the scene
for Obj in RelevantObjects:
if(Obj.getType()=="Armature"):
# Create a list of all blender bones
iFirstArmatureBone=len(self.BlenderBoneList);
for Bone in Obj.getData().bones.values():
self.BlenderBoneList.append(Bone);
# Collect the needed information from the bones
for i in range(iFirstArmatureBone,len(self.BlenderBoneList)):
self.BoneList.append(CBone());
self.BoneList[i+1].Initialize(self.BlenderBoneList[i],self.BlenderBoneList,Armature.NLA.GetActions().values(),not ExportConstraints,Obj,i+1);
# Output the association between frames and actions and determine the
# number of frames that is needed
self.nFrame=0;
if(ExportActionList):
ActionListFile=open(ActionListFileName,"w");
for Act in Armature.NLA.GetActions().values():
if(len(Act.getFrameNumbers())==0):
continue;
nActionFrame=max(Act.getFrameNumbers())+1;
if(nActionFrame>1):
print("Action \""+Act.name+"\" takes the range from frame "+str(self.nFrame)+" to frame "+str(self.nFrame+nActionFrame-1)+".");
if(ExportActionList):
ActionListFile.write("\""+Act.name+"\" "+str(self.nFrame)+" "+str(self.nFrame+nActionFrame-1)+"\n");
self.ActionList.append(CActionRange(Act,self.nFrame));
self.nFrame+=nActionFrame;
self.nFrame=max(1,self.nFrame);
if(ExportActionList):
ActionListFile.close();
def SaveData(self,File,UseV2):
"""Saves the skeleton to the given file and writes an action range chunk."""
for Bone in self.BoneList:
Bone.SaveData(File,UseV2);
if(UseV2):
File.write("$U3D_ACTION_RANGE\0");
ChunkSize=CChunkSizeWriter(File);
for Act in self.ActionList:
Act.SaveData(File);
ChunkSize.Write();
# A list of all Blender bone objects in the scene
BlenderBoneList=[];
# A list of Ultimate 3D bone objects
BoneList=[];
# The list of actions
ActionList=[];
# The number of frames in the animation of this skeleton
nFrame=1;
def FileSelectorCallback(FileName):
"""This is the callback function for the file selector"""
ExportPlugin.InputCompleted(FileName);
class CInputWindow:
"""This class creates a window in which the user has to enter
information about how he wants the file to be exported."""
def __init__(self):
"""This function creates a user interface to get the input."""
# Display a neat little pop up with the options
FormContent=[];
FormContent.append(("Selection only",self.ExportSelectionOnly,"If checked only selected objects will be exported, otherwise the whole scene will be exported."));
FormContent.append(("Animation",self.ExportArmatures,"Export armatures, skin weights and acions?"));
FormContent.append(("Constraints",self.ExportConstraints,"Cycle through all frames to export constraints?"));
FormContent.append(("Action list",self.ExportActionList,"Export a list of all actions with their corresponding frame values?"));
FormContent.append(("All UV layers",self.ExportAllUVLayers,"Export all UV layers or only the active layers. If enabled all meshes must have the same UV layers with the same names."));
FormContent.append(("Use v2.0 (U3D 3.0)",self.UseV2,"Use format version 2.0 instead of version 1.0? Version 2.0 is new in Ultimate 3D 3.0."));
PopUpReturnValue=Draw.PupBlock("Ultimate 3D export settings",FormContent);
# If OK has been clicked show the file selection dialog
if(PopUpReturnValue):
# Suggest a filename that makes sense
ExportFileSuggestion=Blender.Get("filename");
print(ExportFileSuggestion);
for i in range(len(ExportFileSuggestion)-1,0,-1):
if(ExportFileSuggestion[i]=="."):
ExportFileSuggestion=ExportFileSuggestion[0:i]+".u3d";
break;
if(len(ExportFileSuggestion)==0):
ExportFileSuggestion="Model.u3d";
Window.FileSelector(FileSelectorCallback,"Export *.u3d model",ExportFileSuggestion);
ExportSelectionOnly=Draw.Create(False);
ExportArmatures=Draw.Create(True);
ExportConstraints=Draw.Create(True);
ExportActionList=Draw.Create(False);
ExportAllUVLayers=Draw.Create(False);
UseV2=Draw.Create(False);
class CExportPlugin:
"""This class runs the whole export plugin."""
def __init__(self):
"""As the constructor gets called the plugin starts working."""
self.InputWindow=CInputWindow();
def InputCompleted(self,FileName):
"""When the input window has retrieved all input this function gets
called."""
if(len(FileName)>3):
self.ExportModel(FileName,self.InputWindow.ExportSelectionOnly.val,self.InputWindow.ExportArmatures.val,self.InputWindow.ExportConstraints.val,self.InputWindow.ExportActionList.val,self.InputWindow.ExportAllUVLayers.val,self.InputWindow.UseV2.val);
def ExportModel(self,FileName,ExportSelectionOnly,ExportArmatures,ExportConstraints,ExportActionList,ExportAllUVLayers,UseV2):
"""This function initializes all processes that are necessary to export
the model to a given file."""
print("");
print("*** Exporting to an Ultimate 3D model file.***");
# Prepare some information here:
# Get the relevant scene
SceneObject=Scene.GetCurrent();
# Create a list of all objects (including those, which are in groups)
AllObjects=[];
for Obj in SceneObject.objects:
AllObjects.append(Obj);
for GroupObj in Group.Get():
for Obj in GroupObj.objects:
AllObjects.append(Obj);
# Create a list of the objects, which may need to be exported (if their
# type is right)
RelevantObjects=[];
if(ExportSelectionOnly):
for Obj in AllObjects:
if(Obj.isSelected()):
RelevantObjects.append(Obj);
if(len(RelevantObjects)==0):
print("Error: No object selected for exporting.");
Draw.PupMenu("Error%t|No objects selected for exporting.");
return False;
else:
for Obj in AllObjects:
RelevantObjects.append(Obj);
if(len(RelevantObjects)==0):
print("Error: No object contained in the scene.");
Draw.PupMenu("Error%t|No object contained in the scene.");
# Sort out meshes, which do not contain any geometry and count
# the meshes
nMesh=0;
BlenderMeshList=[];
for Object in RelevantObjects:
if(Object.getType()=="Mesh"):
# Count the number of triangles
nTriangle=0;
for Face in Object.getData(False,True).faces:
nTriangle+=len(Face.verts);
# Remove the mesh if there are zero triangles
if nTriangle==0:
RelevantObjects.remove(Object);
else:
nMesh+=1;
BlenderMeshList.append(Object);
# The model is invalid, if it does not contain any meshes
if(nMesh==0):
print("The model you are trying to export does not contain any meshes. This is invalid. Every model has to contain at least one mesh with at least one triangle. Please add some dummy mesh and try again.");
Draw.PupMenu("Error%t|No geometry found in the model. Please add at least one triangle.");
return False;
# If necessary check whether all meshes have identical UV layers
UVLayerSet=None;
if(ExportAllUVLayers):
UVLayerSet=frozenset(BlenderMeshList[0].getData(False,True).getUVLayerNames());
for MeshObject in BlenderMeshList:
if(frozenset(MeshObject.getData(False,True).getUVLayerNames())!=UVLayerSet):
print("The mesh \""+MeshObject.getData(True)+"\" does not have the same UV layers as the mesh \""+BlenderMeshList[0].getData(True)+"\". Exporting UV layers has been disabled to export the model anyway.");
Draw.PupMenu("Warning%t|UV layers of \""+MeshObject.getData(True)+"\" and \""+BlenderMeshList[0].getData(True)+"\" do not match. Exporting UV layers has been disabled.");
UVLayerSet=None;
ExportAllUVLayers=False;
break;
# If the UV layers can be exported output their order
if(ExportAllUVLayers):
print("All UV layers will be exported. Here is the mapping of UV layers to texture coordinate sets:");
iTexCoordSet=0;
for UVLayer in UVLayerSet:
print(UVLayer+" -> "+str(iTexCoordSet));
iTexCoordSet+=1;
# Create a list of all materials (without double materials)
MaterialList=[];
for Object in RelevantObjects:
if(Object.getType()=="Mesh"):
for Material in Object.getData().materials:
if(Material not in MaterialList):
MaterialList.append(Material);
# Check whether there's an armature in the scene. If so skinning will
# be used
SkinningUsed=False;
for Object in RelevantObjects:
if(Object.getType()=="Armature"):
SkinningUsed=ExportArmatures;
break;
# Create a skeleton object if necessary and get it's bone list
BoneList=[];
if(SkinningUsed):
print("Vertex skinning is being used.");
ActionListFileName="";
if(ExportActionList):
ActionListFileName=FileName[0:-3]+"txt";
Skeleton=CSkeleton(RelevantObjects,ExportConstraints,ExportActionList,ActionListFileName);
BoneList=Skeleton.BoneList;
if(len(BoneList)>=128):
print("Your scene contains more than 126 bones. Since Ultimate 3D and the Ultimate 3D model file format can't handle that many bones in combination with skinning you have to reduce the number of bones. The exporter will terminate now. No file has been created.");
Draw.PupMenu("Error%t|Too many bones found. There mustn't be more than 126.");
return False;
# Try to open the file
File=open(FileName,"wb");
if File.closed:
print("Failed to open the file \""+FileName+"\". Please make sure that you have write access and try again.");
Draw.PupMenu("Error%t|Failed to open the file \""+FileName+"\".");
return False;
print("Opened the file "+FileName+" successfully.");
# Export the header
if SkinningUsed:
HeaderOut=CHeader(RelevantObjects,MaterialList,len(BoneList),Skeleton.nFrame,UVLayerSet);
else:
HeaderOut=CHeader(RelevantObjects,MaterialList,0,1,UVLayerSet);
HeaderOut.SaveData(File,UseV2);
# Export the meshes
if(not SkinningUsed):
# If no skinning is used export each mesh as one mesh chunk
iMesh=0;
for i in BlenderMeshList:
ObjectMatrix=i.getMatrix();
MeshTransformation=CMatrix4x4(ObjectMatrix[0][0],ObjectMatrix[0][1],ObjectMatrix[0][2],ObjectMatrix[0][3],ObjectMatrix[1][0],ObjectMatrix[1][1],ObjectMatrix[1][2],ObjectMatrix[1][3],ObjectMatrix[2][0],ObjectMatrix[2][1],ObjectMatrix[2][2],ObjectMatrix[2][3],ObjectMatrix[3][0],ObjectMatrix[3][1],ObjectMatrix[3][2],ObjectMatrix[3][3]);
MeshOut=CMesh(i.getData(False,True),MaterialList,BoneList,MeshTransformation,iMesh,UVLayerSet);
if(not MeshOut.SaveData(File,UseV2)):
File.close();
return False;
iMesh+=1;
print(str(HeaderOut.nMesh)+" meshes have been exported.");
else:
# If skinning is used combine all meshes into one mesh
MeshList=[];
for i in BlenderMeshList:
ObjectMatrix=i.getMatrix();
MeshTransformation=CMatrix4x4(ObjectMatrix[0][0],ObjectMatrix[0][1],ObjectMatrix[0][2],ObjectMatrix[0][3],ObjectMatrix[1][0],ObjectMatrix[1][1],ObjectMatrix[1][2],ObjectMatrix[1][3],ObjectMatrix[2][0],ObjectMatrix[2][1],ObjectMatrix[2][2],ObjectMatrix[2][3],ObjectMatrix[3][0],ObjectMatrix[3][1],ObjectMatrix[3][2],ObjectMatrix[3][3]);
MeshList.append(CMesh(i.getData(False,True),MaterialList,BoneList,MeshTransformation,0,UVLayerSet));
for i in range(1,len(MeshList)):
MeshList[0].AppendMesh(MeshList[i]);
if(not MeshList[0].SaveData(File,UseV2)):
File.close();
return;
print("All meshes in the scene have been combined and the single resulting mesh has been exported.");
# Export the material list
iMaterial=0;
for BlenderMaterial in MaterialList:
TexturePath=FileName;
for i in range(len(FileName)-1,0,-1):
if(TexturePath[i]=="/" or TexturePath[i]=="\\"):
TexturePath=TexturePath[0:i]+"/Textures/";
break;
if(not os.path.exists(TexturePath)):
os.mkdir(TexturePath);
MaterialOut=CMaterial(BlenderMaterial,TexturePath,iMaterial,UVLayerSet);
MaterialOut.SaveData(File,UseV2);
iMaterial+=1;
if(len(MaterialList)):
print(str(HeaderOut.nMaterial)+" materials have been exported.");
# If there's no material in the material list create a default material
else:
if(UseV2):
# Write the material chunk header
File.write("$U3D_MATERIAL\0");
ChunkSize=CChunkSizeWriter(File);
# Write the material index and the name
File.write(struct.pack("L",0));
File.write("DefaultMaterial\0");
# Write the color values (ambient, diffuse, specular, emissive)
File.write(struct.pack("4f",1.0,1.0,1.0,1.0));
File.write(struct.pack("4f",1.0,1.0,1.0,1.0));
File.write(struct.pack("4f",0.0,0.0,0.0,1.0));
File.write(struct.pack("4f",0.0,0.0,0.0,1.0));
# Write the specular power
File.write(struct.pack("f",0.0));
# Write the material depth and the parallax quality
File.write(struct.pack("ff",0.0,0.0));
# Write the color operations and the texture coordinate sets
File.write(struct.pack("LLLLLLLL",1,1,1,1,1,1,1,1));
File.write(struct.pack("LLLLLLLL",0,0,0,0,0,0,0,0));
# Write eight texture chunks without a texture
for i in range(8):
File.write("$U3D_TEXTURE\0");
ChunkSize2=CChunkSizeWriter(File);
File.write(struct.pack("b",False));
ChunkSize2.Write();
# Write that there is no effect
File.write(struct.pack("b",False));
# The chunk is completed
ChunkSize.Write();
else:
# Material chunk identifier (null-terminated)
File.write("$U3D_MAT\0");
# Material name (null-terminated)
File.write("DefaultMaterial");
File.write(struct.pack("b",0));
# Ambient color
File.write(struct.pack("4f",1.0,1.0,1.0,1.0));
# Diffuse color
File.write(struct.pack("4f",1.0,1.0,1.0,1.0));
# Specular color
File.write(struct.pack("4f",0.0,0.0,0.0,1.0));
# Emissive color
File.write(struct.pack("4f",0.0,0.0,0.0,1.0));
# Specular power
File.write(struct.pack("f",0.0));
# Write the number of textures
File.write(struct.pack("H",0));
# Write the texture operations
File.write(struct.pack("8I",5,5,5,5,5,5,5,5));
# Write the texture coordinate indices
File.write(struct.pack("8I",0,0,0,0,0,0,0,0));
# Write that resources don't get exported
File.write(struct.pack("b",0));
# Write that there's no texture
File.write(struct.pack("8b",0,0,0,0,0,0,0,0));
# Write that there's no effect
File.write(struct.pack("b",0));
print("The blender model didn't contain any materials. A default material has been exported.");
# Export the skeleton
if(SkinningUsed):
Skeleton.SaveData(File,UseV2);
print("The skeleton has been exported successfully.");
# Close the file
File.close();
print("All data has been exported successfully.");
Draw.PupMenu("Success%t|The model has been exported successfully.");
return True;
# Initialize an instance of CExportPlugin to run the plugin
ExportPlugin=CExportPlugin();