Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: downloads/ogre/Tools/BlenderExport/ogrepkg/meshexport.py @ 11

Last change on this file since 11 was 6, checked in by anonymous, 17 years ago

=…

File size: 32.1 KB
Line 
1"""Mesh and animation export classes.
2
3   @author Michael Reimpell
4"""
5# Copyright (C) 2005  Michael Reimpell
6#
7# This library is free software; you can redistribute it and/or
8# modify it under the terms of the GNU Lesser General Public
9# License as published by the Free Software Foundation; either
10# version 2.1 of the License, or (at your option) any later version.
11#
12# This library is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15# Lesser General Public License for more details.
16#
17# You should have received a copy of the GNU Lesser General Public
18# License along with this library; if not, write to the Free Software
19# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20
21# epydoc doc format
22__docformat__ = "javadoc en"
23
24import base
25from base import *
26import materialexport
27from materialexport import *
28import armatureexport
29from armatureexport import *
30
31import Blender
32import Blender.Mathutils
33from Blender.Mathutils import *
34import math
35
36# OGRE_VERTEXCOLOUR_BGRA
37#  workaround for Ogre's vertex colour conversion bug.
38#  Set to 0 for RGBA, 1 for BGRA.
39OGRE_OPENGL_VERTEXCOLOUR = 1
40
41matrixOne = [[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]]
42
43class Vertex:
44        """
45        """
46        THRESHOLD = 1e-6
47        def __init__(self, bMesh, bMFace, bIndex, index, parentTransform, armatureExporter=None):
48                """Represents an Ogre vertex.
49                   
50                   @param bIndex Index in the vertex list of the NMFace.
51                   @param index Vertexbuffer position.
52                   @param parentTransform Additional transformation to apply to the vertex.
53                """
54                self.bMesh = bMesh
55                # imples position
56                # vertex in basis shape
57                self.bMVert = bMFace.v[bIndex]
58                bKey = self.bMesh.getKey()
59                if (bKey and len(bKey.blocks)):
60                        # first shape key is rest position
61                        self.bMVert = bKey.blocks[0].data[self.bMVert.index]
62                ## Face properties in Blender
63                self.normal = None
64                self.colourDiffuse = None
65                self.texcoord = None
66                ## bookkeeping
67                # vertexbuffer position in vertexbuffer
68                self.index = index
69                self.parentTransform = parentTransform
70                # implies influences
71                self.armatureExporter = armatureExporter
72                ### populated attributes
73                ## normal
74                if bMFace.smooth:
75                        # key blocks don't have normals
76                        self.normal = self._applyParentTransform(bMFace.v[bIndex].no)
77                else:
78                        # create face normal
79                        # 1 - 2
80                        # | /
81                        # 3
82                        # n = (v_3 - v_1) x (v_2 - v_1)/||n||
83                        if (bKey and len(bKey.blocks)):
84                                # first shape key is rest position
85                                blockData = bKey.blocks[0].data
86                                v1 = self._applyParentTransform(blockData[bMFace.v[0].index].co)
87                                v2 = self._applyParentTransform(blockData[bMFace.v[1].index].co)
88                                v3 = self._applyParentTransform(blockData[bMFace.v[2].index].co)
89                        else:
90                                # self.normal = CrossVecs(bMFace.v[1].co - bMFace.v[0].co, bMFace.v[2].co - bMFace.v[0].co)
91                                v1 = self._applyParentTransform(bMFace.v[0].co)
92                                v2 = self._applyParentTransform(bMFace.v[1].co)
93                                v3 = self._applyParentTransform(bMFace.v[2].co)
94                        self.normal = CrossVecs(v2 - v1, v3 - v1)
95                # self.normal.normalize() does not throw ZeroDivisionError exception
96                normalLength = self.normal.length
97                if (normalLength > Vertex.THRESHOLD):
98                        self.normal = Vector([coordinate/normalLength for coordinate in self.normal])
99                else:
100                        Log.getSingleton().logWarning("Error in normalize! Face of mesh \"%s\" too small." % bMesh.name)
101                        self.normal = Vector([0,0,0])
102                ## colourDiffuse
103                #Mesh#if bMesh.vertexColors:
104                if bMesh.hasVertexColours():
105                        bMCol = bMFace.col[bIndex]
106                        if OGRE_OPENGL_VERTEXCOLOUR:
107                                self.colourDiffuse = (bMCol.b/255.0, bMCol.g/255.0, bMCol.r/255.0, bMCol.a/255.0)
108                        else:
109                                self.colourDiffuse = (bMCol.r/255.0, bMCol.g/255.0, bMCol.b/255.0, bMCol.a/255.0)
110                else:
111                        # Note: hasVertexColours() always returns false when uv coordinates are present.
112                        #   Therefore also check "VCol Paint" and "VCol Light" buttons as well as
113                        #   try if Blender's faces provide vertex colour data.
114                        try:
115                                bMCol = bMFace.col[bIndex]
116                        except:
117                                pass
118                        else:
119                                # vertex colour data available
120                                try:
121                                        bMaterial = self.bMesh.materials[bMFace.mat]
122                                except:
123                                        pass
124                                else:
125                                        # material assigned
126                                        if ((bMaterial.mode & Blender.Material.Modes["VCOL_PAINT"])
127                                                or (bMaterial.mode & Blender.Material.Modes["VCOL_LIGHT"])):
128                                                # vertex colours enabled
129                                                if OGRE_OPENGL_VERTEXCOLOUR:
130                                                        self.colourDiffuse = (bMCol.b/255.0, bMCol.g/255.0, bMCol.r/255.0, bMCol.a/255.0)
131                                                else:
132                                                        self.colourDiffuse = (bMCol.r/255.0, bMCol.g/255.0, bMCol.b/255.0, bMCol.a/255.0)
133                ## texcoord
134                # origin in OGRE is top-left
135                #Mesh#if bMesh.faceUV:
136                if bMesh.hasFaceUV():
137                        self.texcoord = (bMFace.uv[bIndex][0], 1 - bMFace.uv[bIndex][1])
138                #Mesh#elif bMesh.vertexUV:
139                elif bMesh.hasVertexUV():
140                        self.texcoord = (self.bMVert.uvco[0], 1 - self.bMVert.uvco[1])
141                return
142        def __eq__(self, other):
143                """Tests if this vertex is equal to another vertex in the Ogre sense.
144               
145                   Does no take parentTransform into account!
146                   Also, it does not compare the index.
147                """
148                isEqual = 0
149                # compare index, normal, colourDiffuse and texcoord
150                if (self.bMVert.index == other.bMVert.index):
151                        pass
152                elif ((self.normal - other.normal).length > Vertex.THRESHOLD):
153                        pass
154                elif ((self.texcoord and not(other.texcoord)) or 
155                        (not(self.texcoord) and other.texcoord)):
156                        pass
157                elif ((math.fabs(self.texcoord[0] - other.texcoord[0]) > Vertex.THRESHOLD)
158                        or (math.fabs(self.texcoord[1] - other.texcoord[1]) > Vertex.THRESHOLD)):
159                        pass
160                elif ((self.colourDiffuse and not(other.colourDiffuse)) or 
161                        (not(self.colourDiffuse) and other.colourDiffuse)):
162                        pass
163                elif ((math.fabs(self.colourDiffuse[0] - other.colourDiffuse[0]) > Vertex.THRESHOLD)
164                        or (math.fabs(self.colourDiffuse[1] - other.colourDiffuse[1]) > Vertex.THRESHOLD)
165                        or (math.fabs(self.colourDiffuse[2] - other.colourDiffuse[2]) > Vertex.THRESHOLD)
166                        or (math.fabs(self.colourDiffuse[3] - other.colourDiffuse[3]) > Vertex.THRESHOLD)):
167                        pass
168                else:
169                        isEqual = 1
170                return isEqual
171        def hasDiffuseColours(self):
172                available = False
173                if self.colourDiffuse is not None:
174                        available = True
175                return available
176        def nTextureCoords(self):
177                nCoords = 0
178                if self.texcoord is not None:
179                        nCoords += 1
180                return nCoords
181        def writePosition(self, fileObject, indentation=0):
182                fileObject.write(indent(indentation) + "<position x=\"%.6f\" y=\"%.6f\" z=\"%.6f\"/>\n" \
183                        % tuple(self.getPosition()))
184                return
185        def writeNormal(self, fileObject, indentation=0):
186                fileObject.write(indent(indentation) + "<normal x=\"%.6f\" y=\"%.6f\" z=\"%.6f\"/>\n" \
187                        % tuple(self.normal))
188                return
189        def writeColourDiffuse(self, fileObject, indentation=0):
190                if self.colourDiffuse:
191                        fileObject.write(indent(indentation) + "<colour_diffuse value=\"%.6f %.6f %.6f %.6f\"/>\n" \
192                                % self.colourDiffuse)
193                return
194        def writeTexcoord(self, fileObject, indentation=0):
195                if self.texcoord:
196                        fileObject.write(indent(indentation) + "<texcoord u=\"%.6f\" v=\"%.6f\"/>\n"\
197                                % self.texcoord)
198                return
199        def writeVertex(self, fileObject, indentation=0):
200                fileObject.write(indent(indentation) + "<vertex>\n")
201                self.writePosition(fileObject, indentation + 1)
202                self.writeNormal(fileObject, indentation + 1)
203                self.writeColourDiffuse(fileObject, indentation + 1)
204                self.writeTexcoord(fileObject, indentation + 1)
205                fileObject.write(indent(indentation) + "</vertex>\n")
206                return
207        def writeBoneAssignments(self, fileObject, indentation=0):
208                nAssignments = 0
209                weightSum = 0
210                for groupName in self.bMesh.getVertGroupNames():
211                        try:
212                                weight = self.bMesh.getVertsFromGroup(groupName, 1, [self.bMVert.index])[0][1]
213                        except IndexError:
214                                # vertex not in group groupName
215                                pass
216                        else:
217                                if weight > Vertex.THRESHOLD:
218                                        boneIndex = self.armatureExporter.getBoneIndex(groupName)
219                                        if boneIndex is not None:
220                                                # group belongs to an OGRE bone
221                                                fileObject.write(indent(indentation) + \
222                                                        "<vertexboneassignment vertexindex=\"%d\" boneindex=\"%d\" weight=\"%.6f\"/>\n" \
223                                                        % (self.index, boneIndex, weight))
224                                                nAssignments += 1
225                                                weightSum += weight
226                # warnings
227                if (nAssignments == 0):
228                        Log.getSingleton().logWarning("Vertex without bone assignment!")
229                elif (nAssignments  > 4):
230                        Log.getSingleton().logWarning("Vertex with more than 4 bone assignments!")
231                # weightSum > 1.0 seems to be often the case.
232                # TODO: check whether OGRE requires that it is <= 1.0
233                # TODO: check whether Blender takes this as a scale factor, i.e.,
234                #   influence is sum_i bone_i*weight_i, or if weights are normalized to sum_i weight_i == 1
235                #if (weightSum > 1.0):
236                #       Log.getSingleton().logWarning("Vertex with sum of bone assignment weights > 1!")
237                return
238        def getIndex(self):
239                return self.index
240        def getMVert(self):
241                return self.bMVert
242        def getPosition(self):
243                """Returns position vector of the rest position.
244                """
245                return self._applyParentTransform(self.bMVert.co)
246        def getCurrentFramePosition(self, bDeformedNMesh):
247                """Returns position of this vertex in the current frame of the possibly deformed mesh.
248                """
249                return self._applyParentTransform(bDeformedNMesh.verts[self.bMVert.index].co)
250        def getCurrentFrameRelativePosition(self, bDeformedNMesh):
251                """Returns relative position of this vertex in the current frame of the possibly deformed mesh.
252                """
253                return (self.getCurrentFramePosition(bDeformedNMesh) - self.getPosition())
254        def _applyParentTransform(self, vector):
255                """Applies transformation to threedimensional vector.
256                """
257                vec = Vector(vector)
258                vec.resize4D()
259                vec = vec * self.parentTransform
260                vec.resize3D()
261                return vec
262
263class VertexManager:
264        """
265        """
266        def __init__(self, bMesh, parentTransform, armatureExporter=None):
267                self.bMesh = bMesh
268                self.parentTransform = parentTransform
269                # needed for boneassignments
270                self.armatureExporter = armatureExporter
271                # key: index, value: list of vertices with same MVert
272                self.vertexDict = {}
273                # vertices in ascending index order
274                self.vertexList = []
275                return
276        def __iter__(self):
277                return VertexManager.Iterator(self)
278        def getNumberOfVertices(self):
279                """Returns the current number of vertices.
280                """
281                return len(self.vertexList)
282        def getVertex(self, bMFace, bIndex):
283                """Returns possibly shared vertex.
284               
285                   @param bMesh Blender Mesh.
286                   @param bMFace Blender Face.
287                   @param bIndex Index in the vertex list of the MFace.
288                   @return Corresponding vertex.
289                """
290                vertex = Vertex(self.bMesh, bMFace, bIndex, len(self.vertexList), self.parentTransform, self.armatureExporter)
291                if self.vertexDict.has_key(bMFace.v[bIndex].index):
292                        # check Ogre vertices for that Blender vertex
293                        vertexList = self.vertexDict[bMFace.v[bIndex].index]
294                        found = 0
295                        listIndex = 0
296                        while (not(found) and (listIndex < len(vertexList))):
297                                if (vertex == vertexList[listIndex]):
298                                        vertex = vertexList[listIndex]
299                                        found = 1
300                                listIndex = listIndex + 1
301                        if not(found):
302                                # create Ogre vertex for that Blender vertex
303                                self.vertexDict[bMFace.v[bIndex].index].append(vertex)
304                                self.vertexList.append(vertex)
305                else:
306                        # create Ogre vertex for that Blender vertex
307                        self.vertexDict[bMFace.v[bIndex].index] = [vertex]
308                        self.vertexList.append(vertex)
309                return vertex
310        def writeGeometry(self, fileObject, indentation=0):
311                fileObject.write(indent(indentation) + "<geometry vertexcount=\"%d\">\n" % len(self.vertexList))
312                # TODO: replace single vertexbuffer with separate position vertexbuffer for vertex animation
313                fileObject.write(indent(indentation + 1) + "<vertexbuffer positions=\"true\" normals=\"true\"")
314                ## optional attributes
315                # query the first vertex in the buffer
316                if (len(self.vertexList) > 0):
317                        firstVertex = self.vertexList[0]
318                        if firstVertex.hasDiffuseColours():
319                                fileObject.write(" colours_diffuse=\"true\"")
320                        if (firstVertex.nTextureCoords() > 0):
321                                fileObject.write(" texture_coords=\"1\" texture_coord_dimensions_0=\"2\"")
322                fileObject.write(">\n")
323                for vertex in self.vertexList:
324                        vertex.writeVertex(fileObject, indentation + 2)
325                fileObject.write(indent(indentation + 1) + "</vertexbuffer>\n")
326                fileObject.write(indent(indentation) + "</geometry>\n")
327                return
328        def writeBoneAssignments(self, fileObject, indentation=0):
329                if self.armatureExporter:
330                        fileObject.write(indent(indentation) + "<boneassignments>\n")
331                        for vertex in self.vertexList:
332                                vertex.writeBoneAssignments(fileObject, indentation + 1)
333                        fileObject.write(indent(indentation) + "</boneassignments>\n")
334                return
335        class Iterator:
336                """Iterates over vertices in ascending index order.
337                """
338                def __init__(self, vertexManager):
339                        self.vertexManager = vertexManager
340                        self.listIndex = 0
341                        return
342                def next(self):
343                        if self.listIndex >= len(self.vertexManager.vertexList):
344                                raise StopIteration
345                        self.listIndex = self.listIndex + 1
346                        return self.vertexManager.vertexList[self.listIndex - 1]
347
348class Submesh:
349        """Ogre submesh.
350        """
351        def __init__(self, bMesh, material, index, parentTransform, armatureExporter = None):
352                """Constructor.
353               
354                   @param index Index of submesh in submeshes list.
355                """
356                self.bMesh = bMesh
357                self.materialName = material.getName()
358                self.index = index
359                self.parentTransform = parentTransform
360                self.armatureExporter = armatureExporter
361                self.vertexManager = VertexManager(self.bMesh, self.parentTransform, self.armatureExporter)
362                # list of (tuple of vertice indices)
363                self.faces =[]
364                return
365        def getIndex(self):
366                return self.index
367        def addFace(self, bMFace):
368                """Adds a Blender face to the submesh.
369                """
370                # vertex winding:
371                # Blender: clockwise, Ogre: clockwise
372                if (len(bMFace.v) == 3):
373                        v1 = self.vertexManager.getVertex(bMFace, 0)
374                        v2 = self.vertexManager.getVertex(bMFace, 1)
375                        v3 = self.vertexManager.getVertex(bMFace, 2)
376                        self.faces.append((v1.getIndex(), v2.getIndex(), v3.getIndex()))
377                elif (len(bMFace.v) == 4):
378                        v1 = self.vertexManager.getVertex(bMFace, 0)
379                        v2 = self.vertexManager.getVertex(bMFace, 1)
380                        v3 = self.vertexManager.getVertex(bMFace, 2)
381                        v4 = self.vertexManager.getVertex(bMFace, 3)
382                        # Split face on shortest edge
383                        if ((v3.getPosition() - v1.getPosition()).length < (v4.getPosition() - v2.getPosition()).length):
384                                # 1 - 2
385                                # | \ |
386                                # 4 - 3
387                                self.faces.append((v1.getIndex(), v2.getIndex(), v3.getIndex()))
388                                self.faces.append((v1.getIndex(), v3.getIndex(), v4.getIndex()))
389                        else:
390                                # 1 - 2
391                                # | / |
392                                # 4 _ 3
393                                self.faces.append((v1.getIndex(), v2.getIndex(), v4.getIndex()))
394                                self.faces.append((v2.getIndex(), v3.getIndex(), v4.getIndex()))
395                else:
396                        Log.getSingleton().logWarning("Ignored face with %d edges." % len(bMFace.v))
397                return
398        def getVertexManager(self):
399                return self.vertexManager
400        def write(self, fileObject, indentation=0):
401                fileObject.write(indent(indentation) + "<submesh")
402                ## attributes
403                fileObject.write(" material=\"%s\"" % self.materialName)
404                fileObject.write(" usesharedvertices=\"false\"")
405                if (self.vertexManager.getNumberOfVertices() > 65535):
406                        fileObject.write(" use32bitindexes=\"true\"")
407                        Log.getSingleton().logInfo("Switched to 32 bit indices for submesh \"%s\"!" % self.materialName)
408                fileObject.write(">\n")
409                ## elements
410                self._writeFaces(fileObject, indentation + 1)
411                self.vertexManager.writeGeometry(fileObject, indentation + 1)
412                self.vertexManager.writeBoneAssignments(fileObject, indentation + 1)
413                fileObject.write(indent(indentation) + "</submesh>\n")
414                return
415        def _writeFaces(self, fileObject, indentation):
416                fileObject.write(indent(indentation) + "<faces")
417                ## attributes
418                fileObject.write(" count=\"%d\"" % len(self.faces))
419                fileObject.write(">\n")
420                ## elements
421                for face in self.faces:
422                        fileObject.write(indent(indentation + 1) + "<face v1=\"%d\" v2=\"%d\" v3=\"%d\"/>\n" % face)
423                fileObject.write(indent(indentation) + "</faces>\n")
424                return
425
426class SubmeshManager:
427        """
428        """
429        def __init__(self, bMesh, parentTransform, armatureExporter=None):
430                self.bMesh = bMesh
431                self.parentTransform = parentTransform
432                self.armatureExporter = armatureExporter
433                # key: material name, value: Submesh
434                self.submeshDict = {}
435                # submeshes in ascending index order
436                self.submeshList = []
437                return
438        def __iter__(self):
439                return SubmeshManager.Iterator(self)
440        def getSubmesh(self, material):
441                """Returns a Submesh for that material.
442                """
443                submesh = None
444                if self.submeshDict.has_key(material.getName()):
445                        submesh = self.submeshDict[material.getName()]
446                else:
447                        # return new Submesh
448                        index = len(self.submeshList)
449                        submesh = Submesh(self.bMesh, material, index, self.parentTransform, self.armatureExporter)
450                        self.submeshDict[material.getName()] = submesh
451                        self.submeshList.append(submesh)
452                return submesh
453        def write(self, fileObject, indentation=0):
454                if len(self.submeshList):
455                        fileObject.write(indent(indentation) + "<submeshes>\n")
456                        for submesh in self.submeshList:
457                                submesh.write(fileObject, indentation + 1)
458                        fileObject.write(indent(indentation) + "</submeshes>\n")
459                return
460        class Iterator:
461                """Iterates over submeshes in ascending index order.
462                """
463                def __init__(self, submeshManager):
464                        self.submeshManager = submeshManager
465                        self.listIndex = 0
466                        return
467                def next(self):
468                        if self.listIndex >= len(self.submeshManager.submeshList):
469                                raise StopIteration
470                        self.listIndex = self.listIndex + 1
471                        return self.submeshManager.submeshList[self.listIndex - 1]
472
473class Pose:
474        """
475        """
476        THRESHOLD = 1e-7
477        def __init__(self, bKeyBlock, submesh, index, parentTransform):
478                """Constructor.
479               
480                   @param index Index of pose in poses list.
481                """
482                self.bKeyBlock = bKeyBlock
483                self.submesh = submesh
484                self.index = index
485                self.parentTransform = parentTransform
486                # list of pose offset tuples (vertexIndex, deltaX, deltaY, deltaZ)
487                self.poseoffsetList     = []
488                # calculate poseoffsets
489                poseVertexList = self.bKeyBlock.data
490                for vertex in self.submesh.getVertexManager():
491                        offset = self._applyParentTransform(poseVertexList[vertex.getMVert().index]) \
492                                 - vertex.getPosition()
493                        if (offset.length > Pose.THRESHOLD):
494                                self.poseoffsetList.append((vertex.getIndex(), offset.x, offset.y, offset.z))
495                return
496        def getIndex(self):
497                return self.index
498        def getInfluence(self):
499                """Returns influence of this pose in the current frame.
500                """
501                return self.bKeyBlock.curval
502        def getName(self):
503                # unique name = KeyBlock name + submesh index
504                return self.bKeyBlock.name + "-" + str(self.submesh.getIndex())
505        def nPoseoffsets(self):
506                return len(self.poseoffsetList)
507        def write(self, fileObject, indentation=0):
508                if len(self.poseoffsetList):
509                        fileObject.write(indent(indentation) + \
510                                "<pose target=\"submesh\" index=\"%d\" name=\"%s\">\n" \
511                                % (self.submesh.getIndex(), self.getName()))
512                        for poseoffset in self.poseoffsetList:
513                                fileObject.write(indent(indentation + 1) + \
514                                        "<poseoffset index=\"%d\" x=\"%.6f\" y=\"%.6f\" z=\"%.6f\"/>\n" \
515                                        % poseoffset)
516                        fileObject.write(indent(indentation) + "</pose>\n")             
517                return
518        def _applyParentTransform(self, vector):
519                """Applies transformation to threedimensional vector.
520                """
521                vec = Vector(vector)
522                vec.resize4D()
523                vec = vec * self.parentTransform
524                vec.resize3D()
525                return vec
526       
527class PoseManager:
528        """
529        """
530        def __init__(self, bMesh, submeshManager, parentTransform):
531                self.bMesh = bMesh
532                self.submeshManager = submeshManager
533                self.parentTransform = parentTransform
534                # key: submesh, value: poseList
535                self.poseListDict = {}
536                self.poseList = []
537                # create poses
538                # each keyblock creates a pose for every submesh
539                bKey = self.bMesh.getKey()
540                if bKey:
541                        for bKeyBlock in bKey.blocks:
542                                for submesh in self.submeshManager:
543                                        index = len(self.poseList)
544                                        pose = Pose(bKeyBlock, submesh, index, self.parentTransform)
545                                        if (pose.nPoseoffsets() > 0):
546                                                # add nonempty pose to list and dict
547                                                self.poseList.append(pose)
548                                                if self.poseListDict.has_key(submesh):
549                                                        self.poseListDict[submesh].append(pose)
550                                                else:
551                                                        self.poseListDict[submesh] = [pose]
552                return
553        def getPoseList(self, submesh):
554                if self.poseListDict.has_key(submesh):
555                        poseList = self.poseListDict[submesh]
556                else:
557                        poseList = []
558                return poseList
559        def nPoses(self):
560                return len(self.poseList)
561        def write(self, fileObject, indentation=0):
562                if len(self.poseList):
563                        fileObject.write(indent(indentation) + "<poses>\n")
564                        for pose in self.poseList:
565                                pose.write(fileObject, indentation + 1)
566                        fileObject.write(indent(indentation) + "</poses>\n")           
567                return
568
569class MorphAnimationTrack:
570        """
571        """
572        def __init__(self, submesh):
573                """Constructor.
574               
575                   @param submesh Submesh.
576                """
577                self.submesh = submesh
578                # key: time, value: list of position in same order as in the VertexManager.
579                self.keyframeDict = {}
580                return
581        def addKeyframe(self, bDeformedNMesh, time):
582                """Append current frame as keyframe at given time.
583                """
584                positionList = []
585                for vertex in self.submesh.getVertexManager():
586                        positionList.append(vertex.getCurrentFramePosition(bDeformedNMesh))
587                self.keyframeDict[time] = positionList
588                return
589        def write(self, fileObject, indentation):
590                fileObject.write(indent(indentation) + "<track target=\"submesh\" index=\"%d\" type=\"morph\">\n" \
591                        % self.submesh.getIndex())
592                fileObject.write(indent(indentation + 1) + "<keyframes>\n")
593                timeList = self.keyframeDict.keys()
594                timeList.sort()
595                for time in timeList:
596                        fileObject.write(indent(indentation + 2) + "<keyframe time=\"%.6f\">\n" % time)
597                        for position in self.keyframeDict[time]:
598                                fileObject.write(indent(indentation + 3) + "<position x=\"%.6f\" y=\"%.6f\" z=\"%.6f\"/>\n" \
599                                        % tuple(position))
600                        fileObject.write(indent(indentation + 2) + "</keyframe>\n")
601                fileObject.write(indent(indentation + 1) + "</keyframes>\n")
602                fileObject.write(indent(indentation) + "</track>\n")
603                return
604
605class PoseAnimationTrack:
606        """Track with a single pose as keyframes.
607        """
608        THRESHOLD = 1e-6
609        def __init__(self, submesh, poseManager):
610                """Constructor.
611               
612                   @param submesh Submesh.
613                """
614                self.submesh = submesh
615                self.poseManager = poseManager
616                # key: time, value: list of poseref tuples (poseindex, influence).
617                self.keyframeDict = {}
618                return
619        def nKeyframes(self):
620                return len(self.keyframeDict)
621        def addKeyframe(self, time):
622                for pose in self.poseManager.getPoseList(self.submesh):
623                        if (pose.getInfluence() > PoseAnimationTrack.THRESHOLD):
624                                poseref = (pose.getIndex(), pose.getInfluence())
625                                if self.keyframeDict.has_key(time):
626                                        self.keyframeDict[time].append(poseref)
627                                else:
628                                        self.keyframeDict[time] = [poseref]
629                return
630        def write(self, fileObject, indentation):
631                fileObject.write(indent(indentation) + \
632                        "<track target=\"submesh\" index=\"%d\" type=\"pose\">\n" \
633                        % self.submesh.getIndex())
634                fileObject.write(indent(indentation + 1) + "<keyframes>\n")
635                timeList = self.keyframeDict.keys()
636                timeList.sort()
637                for time in timeList:
638                        fileObject.write(indent(indentation + 2) + "<keyframe time=\"%.6f\">\n" % time)
639                        for poseref in self.keyframeDict[time]:
640                                fileObject.write(indent(indentation + 3) + \
641                                        "<poseref poseindex=\"%d\" influence=\"%.6f\"/>\n" \
642                                        % poseref)
643                        fileObject.write(indent(indentation + 2) + "</keyframe>\n")
644                fileObject.write(indent(indentation + 1) + "</keyframes>\n")
645                fileObject.write(indent(indentation) + "</track>\n")
646                return
647
648class VertexAnimation:
649        """Animation base class.
650        """
651        def __init__(self, name, startFrame, endFrame):
652                self.name = name
653                self.startFrame = startFrame
654                self.endFrame = endFrame
655                ## populated on export
656                self.length = None
657                # same order as submeshList of the SubmeshManager
658                self.trackList = None
659                return
660        def getName(self):
661                return self.name
662        def write(self, fileObject, indentation=0):
663                if (len(self.trackList) > 0):
664                        fileObject.write(indent(indentation) + "<animation name=\"%s\" length = \"%.6f\">\n" \
665                                % (self.name, self.length))
666                        fileObject.write(indent(indentation + 1) + "<tracks>\n")
667                        for track in self.trackList:
668                                track.write(fileObject, indentation + 2)
669                        fileObject.write(indent(indentation + 1) + "</tracks>\n")
670                        fileObject.write(indent(indentation) + "</animation>\n")
671                else:
672                        Log.getSingleton().logWarning("Skipped animation \"%s\" as it has no tracks!" \
673                                % self.name)
674                return
675        def _createFrameNumberDict(self):
676                ## frames to times
677                self.length = 0
678                fps = Blender.Scene.GetCurrent().getRenderingContext().framesPerSec()
679                # frameNumberDict: key = export time, value = frame number
680                frameNumberDict = {}
681                if (self.startFrame <= self.endFrame):
682                        minFrame = self.startFrame
683                        maxFrame = self.endFrame
684                else:
685                        minFrame = self.endFrame
686                        maxFrame = self.startFrame
687                for frameNumber in range(int(minFrame), int(maxFrame+1)):
688                        if  (self.startFrame <= self.endFrame):
689                                time = float(frameNumber-self.startFrame)/fps
690                        else:
691                                # backward animation
692                                time = float(self.endFrame-frameNumber)/fps
693                        # update animation duration
694                        if self.length < time:
695                                self.length = time
696                        frameNumberDict[time] = frameNumber
697                return frameNumberDict
698
699class MorphAnimation(VertexAnimation):
700        """Morph animation.
701        """
702        def export(self, bObject, submeshManager):
703                Log.getSingleton().logInfo("Exporting morph animation \"%s\" of mesh \"%s\"" % (self.name, bObject.getData(True)))
704                ## submeshes to tracks
705                self.trackList = []
706                for submesh in submeshManager:
707                        self.trackList.append(MorphAnimationTrack(submesh))
708                ## frames to times
709                frameNumberDict = self._createFrameNumberDict()
710                ## export
711                timeList = frameNumberDict.keys()
712                timeList.sort()
713                for time in timeList:
714                        Blender.Set('curframe', frameNumberDict[time])
715                        bDeformedNMesh = Blender.NMesh.GetRawFromObject(bObject.getName())
716                        for track in self.trackList:
717                                track.addKeyframe(bDeformedNMesh, time)
718                return
719       
720class PoseAnimation(VertexAnimation):
721        """Pose animation.
722        """
723        def export(self, bObject, submeshManager, poseManager):
724                Log.getSingleton().logInfo("Exporting pose animation \"%s\" of mesh \"%s\"" % (self.name, bObject.getData(True)))
725                ## submeshes to tracks
726                self.trackList = []
727                trackList = []
728                for submesh in submeshManager:
729                        trackList.append(PoseAnimationTrack(submesh, poseManager))
730                ## frames to times
731                frameNumberDict = self._createFrameNumberDict()
732                ## export
733                timeList = frameNumberDict.keys()
734                timeList.sort()
735                for time in timeList:
736                        Blender.Set('curframe', frameNumberDict[time])
737                        for track in trackList:
738                                track.addKeyframe(time)
739                for track in trackList:
740                        if (track.nKeyframes() > 0):
741                                self.trackList.append(track)
742                # at least one track?
743                if (len(self.trackList) == 0):
744                        # no pose offsets
745                        Log.getSingleton().logWarning("Pose animation \"%s\" does not differ from restpose." % self.name)
746                return
747
748class VertexAnimationExporter:
749        """
750        """
751        def __init__(self, meshExporter):
752                self.meshExporter = meshExporter
753                self.morphAnimationList = []
754                self.poseAnimationList = []
755                self.poseManager = None
756                return
757        def addMorphAnimation(self, morphAnimation):
758                """Adds a morph animation.
759                """
760                self.morphAnimationList.append(morphAnimation)
761                return
762        def addPoseAnimation(self, poseAnimation):
763                """Adds a pose for pose animation.
764                """
765                self.poseAnimationList.append(poseAnimation)
766                return
767        def hasAnimation(self):
768                return (len(self.morphAnimationList) or len(self.poseAnimationList))
769        def export(self, parentTransform):
770                # generate poses
771                self.poseManager = PoseManager(self.meshExporter.getObject().getData(), self.meshExporter.getSubmeshManager(), parentTransform)
772                if self.hasAnimation():
773                        # sample animations
774                        animationNameList = []
775                        bCurrentFrame = Blender.Get('curframe')
776                        if len(self.poseAnimationList):
777                                # pose animations
778                                if (self.poseManager.nPoses() > 0):
779                                        for poseAnimation in self.poseAnimationList:
780                                                # warn on pose animation name clash
781                                                animationName = poseAnimation.getName()
782                                                if animationName in animationNameList:
783                                                        Log.getSingleton().logWarning("Duplicate animation name \"%s\" for mesh \"%s\"!" \
784                                                                % (animationName, self.meshExporter.getName()))
785                                                animationNameList.append(animationName)
786                                                # export
787                                                poseAnimation.export(self.meshExporter.getObject(), self.meshExporter.getSubmeshManager(), self.poseManager)
788                                else:
789                                        Log.getSingleton().logWarning("Skipped pose animation export as mesh \"%s\"has no shape keys!" \
790                                                % self.meshExporter.getName())
791                                        # clear poseAnimationList to prevent writing
792                                        self.poseAnimationList = []
793                                if len(self.morphAnimationList):
794                                        # morph and pose animation cannot share the same vertex data
795                                        Log.getSingleton().logError("Skipping morph animations of mesh \"%s\": Cannot share vertex data with pose animation!"
796                                                % self.meshExporter.getName())
797                                        self.morphAnimationList = []
798                        elif len(self.morphAnimationList):
799                                # morph animations
800                                for morphAnimation in self.morphAnimationList:
801                                        # warn on morph animation name clash
802                                        animationName = morphAnimation.getName()
803                                        if animationName in animationNameList:
804                                                Log.getSingleton().logWarning("Duplicate animation name \"%s\" for mesh \"%s\"!" \
805                                                        % (animationName, self.meshExporter.getName()))
806                                        animationNameList.append(animationName)
807                                        # export
808                                        morphAnimation.export(self.meshExporter.bObject, self.meshExporter.getSubmeshManager())
809                        Blender.Set('curframe', bCurrentFrame)
810                return
811        def write(self, fileObject, indentation=0):
812                # poses
813                self.poseManager.write(fileObject, indentation)
814                if (len(self.morphAnimationList) or len(self.poseAnimationList)):
815                        fileObject.write(indent(indentation) + "<animations>\n")
816                        if len(self.poseAnimationList):
817                                # pose animations
818                                for poseAnimation in self.poseAnimationList:
819                                        poseAnimation.write(fileObject, indentation + 1)
820                        elif len(self.morphAnimationList):
821                                # morph animations
822                                for morphAnimation in self.morphAnimationList:
823                                        morphAnimation.write(fileObject, indentation + 1)
824                        fileObject.write(indent(indentation) + "</animations>\n")
825                return
826
827class MeshExporter:
828        """Exports a Blender mesh to Ogre.
829       
830           Exports mesh, armature and animations to Ogre XML resp. script files. Materials are
831           exported to a MaterialManager.
832        """
833        def __init__(self, bObject):
834                """
835                """
836                # mesh
837                self.bObject = bObject
838                self.name = self.bObject.getData(True)
839                # vertex animations
840                self.vertexAnimationExporter = VertexAnimationExporter(self)
841                # skeleton
842                self.armatureExporter = None
843                parent = GetArmatureObject(self.bObject)
844                if (parent is not None):
845                        self.armatureExporter = ArmatureExporter(self.bObject, parent)
846                # populated on export
847                self.submeshManager = None
848                return
849        def export(self, dir, materialManager=MaterialManager(), parentTransform=Matrix(*matrixOne), colouredAmbient=False, gameEngineMaterials=False, convertXML=False):
850                # leave editmode
851                editmode = Blender.Window.EditMode()
852                if editmode:
853                        Blender.Window.EditMode(0)
854                Log.getSingleton().logInfo("Exporting mesh \"%s\"" % self.getName())
855                ## export possible armature
856                if self.armatureExporter:
857                        self.armatureExporter.export(dir, parentTransform, convertXML)
858                ## export meshdata
859                self._generateSubmeshes(parentTransform, materialManager, colouredAmbient, gameEngineMaterials)
860                ## export vertex animations
861                self.vertexAnimationExporter.export(parentTransform)
862                ## write files
863                self._write(dir, convertXML)
864                ## cleanup
865                self.submeshManager = None
866                # reenter editmode
867                if editmode:
868                        Blender.Window.EditMode(1)
869                return
870        def getObject(self):
871                return self.bObject
872        def getName(self):
873                return self.name
874        def getVertexAnimationExporter(self):
875                return self.vertexAnimationExporter
876        def getArmatureExporter(self):
877                return self.armatureExporter
878        def getSubmeshManager(self):
879                return self.submeshManager
880        def _generateSubmeshes(self, parentTransform, materialManager, colouredAmbient, gameEngineMaterials):
881                """Generates submeshes of the mesh.
882                """
883                #NMesh# Blender.Mesh.Mesh does not provide access to mesh shape keys, use Blender.NMesh.NMesh
884                bMesh = self.bObject.getData()
885                self.submeshManager = SubmeshManager(bMesh, parentTransform, self.armatureExporter)
886                for bMFace in bMesh.faces:
887                        faceMaterial = materialManager.getMaterial(bMesh, bMFace, colouredAmbient, gameEngineMaterials)
888                        if faceMaterial:
889                                # append face to submesh
890                                self.submeshManager.getSubmesh(faceMaterial).addFace(bMFace)
891                return
892        def _write(self, dir, convertXML):
893                file = self.getName() + ".mesh.xml"
894                Log.getSingleton().logInfo("Writing mesh file \"%s\"" % file)
895                fileObject = open(os.path.join(dir, file), "w")
896                fileObject.write(indent(0)+"<mesh>\n")
897                # submeshes
898                self.submeshManager.write(fileObject, 1)
899                # skeleton
900                if self.armatureExporter:
901                        fileObject.write(indent(1)+"<skeletonlink name=\"%s.skeleton\"/>\n" % self.armatureExporter.getName())
902                # vertex animations
903                self.vertexAnimationExporter.write(fileObject, 1)
904                fileObject.write(indent(0)+"</mesh>\n")
905                fileObject.close()
906                if convertXML:
907                        OgreXMLConverter.getSingleton().convert(os.path.join(dir, file))
908                return
Note: See TracBrowser for help on using the repository browser.