1 | """Armature and armature 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 | |
---|
24 | import base |
---|
25 | from base import * |
---|
26 | |
---|
27 | import math |
---|
28 | import Blender |
---|
29 | import Blender.Mathutils |
---|
30 | from Blender.Mathutils import * |
---|
31 | |
---|
32 | # TODO: Facade for Blender objects |
---|
33 | def GetArmatureObject(bObject): |
---|
34 | """Returns Blender armature object of this Blender object or <code>None</code>. |
---|
35 | |
---|
36 | Armatures are either parented or listed in the modifier stack of the object to deform. |
---|
37 | """ |
---|
38 | bArmatureObject = None |
---|
39 | if (bObject.getType() == "Mesh"): |
---|
40 | # parented armatures get preferred |
---|
41 | bParentObject = bObject.getParent() |
---|
42 | if (bParentObject and (bParentObject.getType() == "Armature")): |
---|
43 | bArmatureObject = bParentObject |
---|
44 | else: |
---|
45 | # check modifier stack, use last armature modifier |
---|
46 | for modifier in bObject.modifiers: |
---|
47 | if ((modifier.type == Blender.Modifier.Type['ARMATURE']) |
---|
48 | and modifier[Blender.Modifier.Settings.VERTGROUP]): |
---|
49 | bArmatureObject = modifier[Blender.Modifier.Settings.OBJECT] |
---|
50 | return bArmatureObject |
---|
51 | |
---|
52 | class ArmatureAction: |
---|
53 | """Manages a Blender action. |
---|
54 | """ |
---|
55 | def __init__(self, bAction, armatureExporter): |
---|
56 | self.bAction = bAction |
---|
57 | self.armatureExporter = armatureExporter |
---|
58 | return |
---|
59 | def getName(self): |
---|
60 | return self.bAction.getName() |
---|
61 | def hasEffect(self): |
---|
62 | """If true, the action has an effect on at least one bone of the armature. |
---|
63 | """ |
---|
64 | hasEffect = 0 |
---|
65 | channelIpoDict = self.bAction.getAllChannelIpos() |
---|
66 | channelIterator = iter(channelIpoDict) |
---|
67 | try: |
---|
68 | while not(hasEffect): |
---|
69 | channelName = channelIterator.next() |
---|
70 | if ((channelName in self.armatureExporter.boneIndices.keys()) |
---|
71 | and channelIpoDict[channelName].getNcurves()): |
---|
72 | hasEffect = 1 |
---|
73 | except StopIteration: |
---|
74 | pass |
---|
75 | return hasEffect |
---|
76 | |
---|
77 | class ArmatureActionManager: |
---|
78 | def __init__(self, armatureExporter): |
---|
79 | self.armatureExporter = armatureExporter |
---|
80 | self.actionList = [] |
---|
81 | for bAction in Blender.Armature.NLA.GetActions().values(): |
---|
82 | action = ArmatureAction(bAction, self.armatureExporter) |
---|
83 | if action.hasEffect(): |
---|
84 | self.actionList.append(action) |
---|
85 | return |
---|
86 | def __iter__(self): |
---|
87 | return ArmatureActionManager.Iterator(self) |
---|
88 | def getActions(self): |
---|
89 | return self.actionList |
---|
90 | class Iterator: |
---|
91 | """Iterates over ArmatureActions. |
---|
92 | """ |
---|
93 | def __init__(self, armatureActionManager): |
---|
94 | self.armatureActionManager = armatureActionManager |
---|
95 | self.listIndex = 0 |
---|
96 | return |
---|
97 | def next(self): |
---|
98 | if self.listIndex >= len(self.armatureActionManager.actionList): |
---|
99 | raise StopIteration |
---|
100 | self.listIndex = self.listIndex + 1 |
---|
101 | return self.armatureActionManager.actionList[self.listIndex - 1] |
---|
102 | |
---|
103 | class ArmatureAnimation: |
---|
104 | """Resembles Blender's action actuators. |
---|
105 | """ |
---|
106 | def __init__(self, bAction, name, startFrame, endFrame): |
---|
107 | """Constructor |
---|
108 | |
---|
109 | @param bAction Blender action of the animation |
---|
110 | @param name Animation name |
---|
111 | @param startFrame first frame of the animation |
---|
112 | @param endFrame last frame of the animation |
---|
113 | """ |
---|
114 | self.bAction = bAction |
---|
115 | self.name = name |
---|
116 | self.startFrame = startFrame |
---|
117 | self.endFrame = endFrame |
---|
118 | # populated on export |
---|
119 | self.length = None |
---|
120 | self.trackList = None |
---|
121 | return |
---|
122 | def getName(self): |
---|
123 | return self.name |
---|
124 | def export(self, armatureExporter, fps): |
---|
125 | """Export animation to OGRE. |
---|
126 | |
---|
127 | @fps frames per second. Has to be larger than zero. |
---|
128 | """ |
---|
129 | # store current settings |
---|
130 | frameAtExportTime = Blender.Get('curframe') |
---|
131 | actionAtExportTime = armatureExporter.getArmatureObject().getAction() |
---|
132 | |
---|
133 | # total animation time |
---|
134 | startFrame = int(self.startFrame) |
---|
135 | endFrame = int(self.endFrame) |
---|
136 | if (self.endFrame < self.startFrame): |
---|
137 | # TODO backward animation |
---|
138 | startFrame = self.endFrame |
---|
139 | endFrame = self.startFrame |
---|
140 | Log.getSingleton().logError("Start frame after end frame in animation \"%s\"" % self.name) |
---|
141 | self.length = (endFrame - startFrame)/float(fps) |
---|
142 | |
---|
143 | # enable action |
---|
144 | self.bAction.setActive(armatureExporter.getArmatureObject()) |
---|
145 | Blender.Window.Redraw() |
---|
146 | |
---|
147 | ## create track for all OGRE bones |
---|
148 | stack = [] |
---|
149 | for bone in armatureExporter.getRootSkeletonBoneList(): |
---|
150 | stack.append(SkeletonAnimationTrack(armatureExporter, bone)) |
---|
151 | rootTrackList = stack[:] |
---|
152 | self.trackList = stack[:] |
---|
153 | # precondition: stack contains all root tracks |
---|
154 | while (len(stack) > 0): |
---|
155 | parentTrack = stack.pop(0) |
---|
156 | for bone in parentTrack.getSkeletonBone().getChildren(): |
---|
157 | track = SkeletonAnimationTrack(armatureExporter, bone, parentTrack) |
---|
158 | stack.append(track) |
---|
159 | self.trackList.append(track) |
---|
160 | # postcondition: every SkeletonBone has a corresponding track |
---|
161 | |
---|
162 | ## export pose frame by frame |
---|
163 | bArmatureObject = armatureExporter.getArmatureObject() |
---|
164 | meshObjectSpaceTransformation = armatureExporter.getAdditionalRootBoneTransformation() |
---|
165 | for frame in range(startFrame, endFrame + 1): |
---|
166 | # frameTime |
---|
167 | frameTime = (frame - startFrame)/float(fps) |
---|
168 | |
---|
169 | # evaluate pose for current frame |
---|
170 | Blender.Set('curframe', frame) |
---|
171 | Blender.Window.Redraw() |
---|
172 | bArmatureObject.evaluatePose(frame) |
---|
173 | pose = bArmatureObject.getPose() |
---|
174 | |
---|
175 | # tracks on the stack do not have unprocessed parent tracks |
---|
176 | stack = rootTrackList[:] |
---|
177 | # set keyframes |
---|
178 | while (len(stack) > 0): |
---|
179 | track = stack.pop(0) |
---|
180 | track.addKeyframe(pose, frameTime, meshObjectSpaceTransformation) |
---|
181 | stack.extend(track.getChildren()) |
---|
182 | |
---|
183 | # remove unused tracks |
---|
184 | ## TODO |
---|
185 | |
---|
186 | # restore current settings |
---|
187 | # FIXME: does not work with multiple actions |
---|
188 | actionAtExportTime.setActive(armatureExporter.getArmatureObject()) |
---|
189 | Blender.Set('curframe', frameAtExportTime) |
---|
190 | armatureExporter.getArmatureObject().evaluatePose(frameAtExportTime) |
---|
191 | return |
---|
192 | def write(self, f, indentation=0): |
---|
193 | Log.getSingleton().logInfo("Writing skeleton animation \"%s\"." % self.name) |
---|
194 | f.write(indent(indentation) + "<animation name=\"%s\" length=\"%f\">\n" % (self.name, self.length)) |
---|
195 | if (len(self.trackList) > 0): |
---|
196 | f.write(indent(indentation + 1) + "<tracks>\n") |
---|
197 | for track in self.trackList: |
---|
198 | track.write(f, indentation + 2) |
---|
199 | f.write(indent(indentation + 1) + "</tracks>\n") |
---|
200 | f.write(indent(indentation) + "</animation>\n") |
---|
201 | return |
---|
202 | |
---|
203 | class SkeletonBone: |
---|
204 | """Bone of an Ogre sekeleton. |
---|
205 | """ |
---|
206 | def __init__(self, armatureExporter, bBone, parent=None): |
---|
207 | """Constructor. |
---|
208 | |
---|
209 | @param armatureExporter ArmatureExporter, required for bone ids. |
---|
210 | @param bBone Blender bone. |
---|
211 | @param parent Parent SkeletonBone. |
---|
212 | """ |
---|
213 | self.armatureExporter = armatureExporter |
---|
214 | self.bBone = bBone |
---|
215 | self.parent = parent |
---|
216 | self.children = [] |
---|
217 | self.ogreRestMatrix = None |
---|
218 | self.inverseTotalTransformation = None |
---|
219 | # register as child of parent |
---|
220 | if self.parent is not None: |
---|
221 | self.parent.addChild(self) |
---|
222 | return |
---|
223 | def addChild(self, child): |
---|
224 | """Appends a child bone. |
---|
225 | |
---|
226 | This method gets called from the constructor of SkeletonBone. |
---|
227 | |
---|
228 | @param child SkeletonBone. |
---|
229 | """ |
---|
230 | self.children.append(child) |
---|
231 | return |
---|
232 | def getName(self): |
---|
233 | return self.bBone.name |
---|
234 | def getParent(self): |
---|
235 | return self.parent |
---|
236 | def getChildren(self): |
---|
237 | # return new list in order to avoid side effects |
---|
238 | return self.children[:] |
---|
239 | def getBlenderBone(self): |
---|
240 | return self.bBone |
---|
241 | def getOgreRestMatrix(self): |
---|
242 | """Returns a copy of the rest matrix. |
---|
243 | """ |
---|
244 | if self.ogreRestMatrix is None: |
---|
245 | raise AssertionError('OGRE rest matrix not set for this bone!') |
---|
246 | return Blender.Mathutils.Matrix(*self.ogreRestMatrix) |
---|
247 | def getInverseTotalTransformation(self): |
---|
248 | return Blender.Mathutils.Matrix(*self.inverseTotalTransformation) |
---|
249 | def setOgreRestMatrix(self): |
---|
250 | """Sets rest transformation in OGRE. |
---|
251 | |
---|
252 | Note that the parent bone rest matrix is required to be valid! |
---|
253 | """ |
---|
254 | # Warning: Blender uses left multiplication vector*matrix |
---|
255 | # get bone matrix of OGRE parent bone |
---|
256 | inverseParentMatrix = Matrix([1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]) |
---|
257 | if self.parent is not None: |
---|
258 | inverseParentMatrix = self.parent.getInverseTotalTransformation() |
---|
259 | |
---|
260 | # bone matrix relative to armature object |
---|
261 | self.ogreRestMatrix = Blender.Mathutils.Matrix(*self.bBone.matrix['ARMATURESPACE']) |
---|
262 | # relative to mesh object origin |
---|
263 | self.ogreRestMatrix *= self.armatureExporter.getAdditionalRootBoneTransformation() |
---|
264 | # store total transformation |
---|
265 | self.inverseTotalTransformation = Blender.Mathutils.Matrix(*self.ogreRestMatrix) |
---|
266 | self.inverseTotalTransformation.invert() |
---|
267 | # relative to OGRE parent bone origin |
---|
268 | self.ogreRestMatrix *= inverseParentMatrix |
---|
269 | return |
---|
270 | def writeBone(self, f, indentation=0): |
---|
271 | f.write(indent(indentation) + "<bone id=\"%d\" name=\"%s\">\n" |
---|
272 | % (self.armatureExporter.getBoneIndex(self.getName()), self.getName())) |
---|
273 | # get transformation values |
---|
274 | positionTuple = tuple(self.ogreRestMatrix.translationPart()) |
---|
275 | rotationQuaternion = self.ogreRestMatrix.toQuat() |
---|
276 | rotationQuaternion.normalize() |
---|
277 | angle = float(rotationQuaternion.angle)/180*math.pi |
---|
278 | axisTuple = tuple(rotationQuaternion.axis) |
---|
279 | # Blender bones do not have scale in rest position |
---|
280 | #scaleTuple = tuple(self.ogreRestMatrix.scalePart()) |
---|
281 | # write transformation values |
---|
282 | f.write(indent(indentation + 1) + "<position x=\"%.6f\" y=\"%.6f\" z=\"%.6f\"/>\n" % positionTuple) |
---|
283 | f.write(indent(indentation + 1) + "<rotation angle=\"%.6f\">\n" % angle) |
---|
284 | f.write(indent(indentation + 2) + "<axis x=\"%.6f\" y=\"%.6f\" z=\"%.6f\"/>\n" % axisTuple) |
---|
285 | f.write(indent(indentation + 1) + "</rotation>\n") |
---|
286 | f.write(indent(indentation) + "</bone>\n") |
---|
287 | return |
---|
288 | |
---|
289 | class SkeletonAnimationTrack: |
---|
290 | def __init__(self, armatureExporter, skeletonBone, parent=None): |
---|
291 | """Animation track for an OGRE bone. |
---|
292 | |
---|
293 | @param parent parent track. |
---|
294 | """ |
---|
295 | self.armatureExporter = armatureExporter |
---|
296 | self.skeletonBone = skeletonBone |
---|
297 | self.parent = parent |
---|
298 | self.children = [] |
---|
299 | # key: time, value: keyframe matrix. |
---|
300 | self.keyframeDict = {} |
---|
301 | # cache name |
---|
302 | self.name = self.skeletonBone.getName() |
---|
303 | # cache restpose |
---|
304 | self.ogreRestPose = self.skeletonBone.getOgreRestMatrix() |
---|
305 | self.inverseOgreRestPose = self.skeletonBone.getOgreRestMatrix() |
---|
306 | self.inverseOgreRestPose.invert() |
---|
307 | |
---|
308 | # (pose_matrix * additionalRootBoneTransformation)^(-1) of last keyframe |
---|
309 | self.inverseLastKeyframeTotalTransformation = None |
---|
310 | |
---|
311 | # register as child of parent |
---|
312 | if self.parent is not None: |
---|
313 | self.parent.addChild(self) |
---|
314 | return |
---|
315 | def getSkeletonBone(self): |
---|
316 | return self.skeletonBone |
---|
317 | def getChildren(self): |
---|
318 | return self.children |
---|
319 | def addChild(self, child): |
---|
320 | """Appends a child track. |
---|
321 | |
---|
322 | This method gets called from the constructor of SkeletonAnimationTrack. |
---|
323 | |
---|
324 | @param child SkeletonAnimationTrack. |
---|
325 | """ |
---|
326 | self.children.append(child) |
---|
327 | return |
---|
328 | def nKeyframes(self): |
---|
329 | return len(self.keyframeDict) |
---|
330 | def getInverseLastKeyframeTotalTransformation(self): |
---|
331 | """Returns a copy of (pose_matrix * additionalRootBoneTransformation)^(-1) |
---|
332 | of last keyframe. |
---|
333 | |
---|
334 | Called from addKeyframe of child tracks. |
---|
335 | """ |
---|
336 | return Blender.Mathutils.Matrix(*self.inverseLastKeyframeTotalTransformation) |
---|
337 | def addKeyframe(self, pose, time, additionalRootBoneTransformation): |
---|
338 | """Adds current pose as keyframe. |
---|
339 | |
---|
340 | @param additionalRootBoneTransformation transformation from local armature |
---|
341 | object space to the mesh object coordinate system. |
---|
342 | """ |
---|
343 | # Warning: Blender uses left multiplication vector*matrix |
---|
344 | # |
---|
345 | # Blender Bone coordinates in armature object space are |
---|
346 | # (x,y,z,w) * pose_matrix |
---|
347 | # Hence coordinates in meshObject coordinate system are |
---|
348 | # (x,y,z,w) * pose_matrix * additionalRootBoneTransformation. |
---|
349 | # |
---|
350 | poseTransformation = Blender.Mathutils.Matrix(*pose.bones[self.name].poseMatrix) |
---|
351 | poseTransformation *= additionalRootBoneTransformation |
---|
352 | |
---|
353 | self.inverseLastKeyframeTotalTransformation = Blender.Mathutils.Matrix(*poseTransformation) |
---|
354 | self.inverseLastKeyframeTotalTransformation.invert() |
---|
355 | |
---|
356 | # calculate difference to parent bone |
---|
357 | if self.parent is not None: |
---|
358 | poseTransformation *= self.parent.getInverseLastKeyframeTotalTransformation() |
---|
359 | |
---|
360 | self.keyframeDict[time] = Blender.Mathutils.Matrix(*poseTransformation) |
---|
361 | return |
---|
362 | def write(self, f, indentation=0): |
---|
363 | # write optimized keyframes |
---|
364 | if (len(self.keyframeDict) > 0): |
---|
365 | f.write(indent(indentation) + "<track bone=\"%s\">\n" % self.name) |
---|
366 | f.write(indent(indentation + 1) + "<keyframes>\n") |
---|
367 | # write keyframes |
---|
368 | keyframeTimes = self.keyframeDict.keys() |
---|
369 | keyframeTimes.sort() |
---|
370 | for time in keyframeTimes: |
---|
371 | f.write(indent(indentation + 2) + "<keyframe time=\"%f\">\n" % time) |
---|
372 | transformation = self.keyframeDict[time] |
---|
373 | # get transformation values |
---|
374 | # translation relative to parent coordinate system orientation |
---|
375 | # and as difference to rest pose translation |
---|
376 | translation = transformation.translationPart() |
---|
377 | translation -= self.ogreRestPose.translationPart() |
---|
378 | translationTuple = tuple(translation) |
---|
379 | # rotation relative to local coordiante system |
---|
380 | # calculate difference to rest pose |
---|
381 | transformation *= self.inverseOgreRestPose |
---|
382 | rotationQuaternion = transformation.toQuat() |
---|
383 | rotationQuaternion.normalize() |
---|
384 | angle = float(rotationQuaternion.angle)/180*math.pi |
---|
385 | axisTuple = tuple(rotationQuaternion.axis) |
---|
386 | scaleTuple = tuple(transformation.scalePart()) |
---|
387 | # write transformation values |
---|
388 | f.write(indent(indentation + 3) + "<translate x=\"%.6f\" y=\"%.6f\" z=\"%.6f\"/>\n" % translationTuple) |
---|
389 | f.write(indent(indentation + 3) + "<rotate angle=\"%.6f\">\n" % angle) |
---|
390 | f.write(indent(indentation + 4) + "<axis x=\"%.6f\" y=\"%.6f\" z=\"%.6f\"/>\n" % axisTuple) |
---|
391 | f.write(indent(indentation + 3) + "</rotate>\n") |
---|
392 | f.write(indent(indentation + 3) + "<scale x=\"%.6f\" y=\"%.6f\" z=\"%.6f\"/>\n" % scaleTuple) |
---|
393 | f.write(indent(indentation + 2) + "</keyframe>\n") |
---|
394 | f.write(indent(indentation + 1) + "</keyframes>\n") |
---|
395 | f.write(indent(indentation) + "</track>\n") |
---|
396 | return |
---|
397 | def optimizeKeyframes(self): |
---|
398 | """Reduce number of keyframes. |
---|
399 | |
---|
400 | Note that you can't reduce keyframes locally when using |
---|
401 | Catmull-Rom spline interpolation. Doing so would result in |
---|
402 | wrong tangents. |
---|
403 | """ |
---|
404 | return |
---|
405 | |
---|
406 | class ArmatureExporter: |
---|
407 | """Exports Blender armature and its animations. |
---|
408 | |
---|
409 | Only bones with enabled deform button get exported. |
---|
410 | """ |
---|
411 | def __init__(self, bMeshObject, bArmatureObject): |
---|
412 | """Constructor. |
---|
413 | """ |
---|
414 | # Note: getName() and getBoneIndex(boneName) already work prior to export. |
---|
415 | self.bMeshObject = bMeshObject |
---|
416 | self.bArmatureObject = bArmatureObject |
---|
417 | # cache Blender Armature |
---|
418 | self.bArmature = bArmatureObject.getData() |
---|
419 | # name, needed as mesh's skeletonlink name |
---|
420 | # As there may be an additional transformation between bMeshObject and bArmatureObject, |
---|
421 | # it is generally not possible to share the armature between several meshes. |
---|
422 | self.name = self.bMeshObject.getName() + "-" + self.bArmatureObject.getData(True) |
---|
423 | # boneindices, needed for mesh's vertexboneassignments |
---|
424 | # key = boneName, value = boneIndex |
---|
425 | boneNameList = [bone.name for bone in self.bArmature.bones.values() if (Blender.Armature.NO_DEFORM not in bone.options)] |
---|
426 | self.boneIndices = dict(zip(boneNameList, range(len(boneNameList)))) |
---|
427 | # actions |
---|
428 | self.actionManager = ArmatureActionManager(self) |
---|
429 | # animations to export |
---|
430 | self.animationList = [] |
---|
431 | # populated on export |
---|
432 | self.skeletonBoneList = None |
---|
433 | self.rootSkeletonBoneList = None |
---|
434 | self.additionalRootBoneTransformation = None |
---|
435 | return |
---|
436 | def addAnimation(self, animation): |
---|
437 | """Adds animation to export. |
---|
438 | |
---|
439 | @param animation ArmatureAnimation |
---|
440 | """ |
---|
441 | self.animationList.append(animation) |
---|
442 | return |
---|
443 | def export(self, dir, parentTransform, convertXML=False): |
---|
444 | self._convertBoneHierarchy() |
---|
445 | self._convertRestpose() |
---|
446 | self._convertAnimations() |
---|
447 | self.write(dir, convertXML) |
---|
448 | return |
---|
449 | def getName(self): |
---|
450 | return self.name |
---|
451 | def getArmatureObject(self): |
---|
452 | return self.bArmatureObject |
---|
453 | def getMeshObject(self): |
---|
454 | return self.bMeshObject |
---|
455 | def getBoneIndex(self, boneName): |
---|
456 | """Returns bone index for a given bone name. |
---|
457 | |
---|
458 | @param boneName Name of the bone. |
---|
459 | @return Bone index or <code>None</code> if a bone with the given name does not exist. |
---|
460 | """ |
---|
461 | if self.boneIndices.has_key(boneName): |
---|
462 | index = self.boneIndices[boneName] |
---|
463 | else: |
---|
464 | index = None |
---|
465 | return index |
---|
466 | def getActions(self): |
---|
467 | """Returns list of available actions. |
---|
468 | """ |
---|
469 | return self.actionManager.getActions() |
---|
470 | def getAdditionalRootBoneTransformation(self): |
---|
471 | """Retruns transformation from Blender's armature object coordinate system |
---|
472 | to Blender's mesh object coordinate system. |
---|
473 | """ |
---|
474 | return self.additionalRootBoneTransformation |
---|
475 | def getSkeletonBoneList(self): |
---|
476 | """Returns list of all OGRE bones. |
---|
477 | """ |
---|
478 | return self.skeletonBoneList |
---|
479 | def getRootSkeletonBoneList(self): |
---|
480 | """Returns list of all OGRE root bones. |
---|
481 | """ |
---|
482 | return self.rootSkeletonBoneList |
---|
483 | def write(self, dir, convertXML=False): |
---|
484 | Log.getSingleton().logInfo("Exporting armature \"%s\"" % self.getName()) |
---|
485 | filename = Blender.sys.join(dir, self.getName() + ".skeleton.xml") |
---|
486 | f = open(filename, "w") |
---|
487 | f.write(indent(0) + "<skeleton>\n") |
---|
488 | self._writeRestpose(f, 1) |
---|
489 | self._writeBoneHierarchy(f, 1) |
---|
490 | self._writeAnimations(f, 1) |
---|
491 | f.write(indent(0) + "</skeleton>\n") |
---|
492 | f.close() |
---|
493 | if convertXML: |
---|
494 | OgreXMLConverter.getSingleton().convert(filename) |
---|
495 | return |
---|
496 | def _generateActionList(self): |
---|
497 | return |
---|
498 | def _convertBoneHierarchy(self): |
---|
499 | self.skeletonBoneList = [] |
---|
500 | ## find root bones |
---|
501 | self.rootSkeletonBoneList = [] |
---|
502 | for bBone in self.bArmature.bones.values(): |
---|
503 | # export this bone? |
---|
504 | if (Blender.Armature.NO_DEFORM not in bBone.options): |
---|
505 | # find possible parent bone, that gets exported. |
---|
506 | found = False |
---|
507 | current = bBone |
---|
508 | while (not(found) and current.hasParent()): |
---|
509 | current = current.parent |
---|
510 | if (Blender.Armature.NO_DEFORM not in current.options): |
---|
511 | found = True |
---|
512 | if not(found): |
---|
513 | # bone is root bone |
---|
514 | rootBone = SkeletonBone(self, bBone) |
---|
515 | self.rootSkeletonBoneList.append(rootBone) |
---|
516 | self.skeletonBoneList.append(rootBone) |
---|
517 | # postcondition: rootSkeletonBoneList contains root bones |
---|
518 | ## find child bones |
---|
519 | stack = self.rootSkeletonBoneList[:] |
---|
520 | # bones on the stack do not have unprocessed parent bones |
---|
521 | while (len(stack)): |
---|
522 | parent = stack.pop(0) |
---|
523 | # check for child bones |
---|
524 | if parent.getBlenderBone().hasChildren(): |
---|
525 | children = parent.getBlenderBone().children[:] |
---|
526 | while (len(children)): |
---|
527 | bBone = children.pop() |
---|
528 | if Blender.Armature.NO_DEFORM in bBone.options: |
---|
529 | # children of child are possible children |
---|
530 | children.extend(bBone.children) |
---|
531 | else: |
---|
532 | # child found |
---|
533 | child = SkeletonBone(self, bBone, parent) |
---|
534 | stack.append(child) |
---|
535 | self.skeletonBoneList.append(child) |
---|
536 | # postcondition: all bones processed, self.skeletonBoneList contains all bones to export |
---|
537 | return |
---|
538 | def _convertRestpose(self): |
---|
539 | """Convert rest pose of Blender skeleton. |
---|
540 | |
---|
541 | Note that not every Blender bone has a corresponding OGRE bone. |
---|
542 | Root bones need an additional transformation caused by the |
---|
543 | possibliy different object coordinate systems of Blender's |
---|
544 | armature object and Blender's mesh object. |
---|
545 | """ |
---|
546 | # Warning: Blender uses left-multiplication: vector*matrix |
---|
547 | |
---|
548 | # additional transformation caused by the objects |
---|
549 | inverseMeshObjectMatrix = Blender.Mathutils.Matrix(*self.bMeshObject.getMatrix()) |
---|
550 | inverseMeshObjectMatrix.invert() |
---|
551 | armatureObjectMatrix = Blender.Mathutils.Matrix(*self.bArmatureObject.getMatrix()) |
---|
552 | |
---|
553 | # additional transformation for root bones: |
---|
554 | # from armature object space into mesh object space, i.e., |
---|
555 | # (x,y,z,w)*AO*MO^(-1) |
---|
556 | self.additionalRootBoneTransformation = armatureObjectMatrix*inverseMeshObjectMatrix |
---|
557 | |
---|
558 | stack = self.rootSkeletonBoneList[:] |
---|
559 | # precondition: stack contains all root bones, bone hierarchy is fixed |
---|
560 | while (len(stack) > 0): |
---|
561 | bone = stack.pop(0) |
---|
562 | bone.setOgreRestMatrix() |
---|
563 | stack.extend(bone.getChildren()) |
---|
564 | return |
---|
565 | def _restPoseMesh(self): |
---|
566 | """Converts skeleton to mesh. |
---|
567 | """ |
---|
568 | # TODO |
---|
569 | return |
---|
570 | def _writeRestpose(self, f, indentation=0): |
---|
571 | f.write(indent(indentation) + "<bones>\n") |
---|
572 | for bone in self.skeletonBoneList: |
---|
573 | bone.writeBone(f, indentation + 1) |
---|
574 | f.write(indent(indentation) + "</bones>\n") |
---|
575 | return |
---|
576 | def _writeBoneHierarchy(self, f, indentation=0): |
---|
577 | f.write(indent(indentation) + "<bonehierarchy>\n") |
---|
578 | for bone in self.skeletonBoneList: |
---|
579 | if bone.getParent() is not None: |
---|
580 | f.write(indent(indentation + 1) + "<boneparent bone=\"%s\" parent=\"%s\" />\n" |
---|
581 | % (bone.getName(), bone.getParent().getName())) |
---|
582 | f.write(indent(indentation) + "</bonehierarchy>\n") |
---|
583 | return |
---|
584 | def _convertAnimations(self): |
---|
585 | if (len(self.animationList) > 0): |
---|
586 | # store current settings |
---|
587 | frameAtExportTime = Blender.Get('curframe') |
---|
588 | actionAtExportTime = self.bArmatureObject.getAction() |
---|
589 | |
---|
590 | # frames per second |
---|
591 | fps = Blender.Scene.GetCurrent().getRenderingContext().framesPerSec() |
---|
592 | |
---|
593 | animationNameList = [] |
---|
594 | for animation in self.animationList: |
---|
595 | # warn on morph animation name clash |
---|
596 | animationName = animation.getName() |
---|
597 | if animationName in animationNameList: |
---|
598 | Log.getSingleton().logWarning("Duplicate animation name \"%s\" for skeleton \"%s\"!" \ |
---|
599 | % (animationName, self.getName())) |
---|
600 | animationNameList.append(animationName) |
---|
601 | # export |
---|
602 | animation.export(self, fps) |
---|
603 | |
---|
604 | # restore current settings |
---|
605 | Blender.Set('curframe', frameAtExportTime) |
---|
606 | actionAtExportTime.setActive(self.bArmatureObject) |
---|
607 | return |
---|
608 | def _writeAnimations(self, f, indentation=0): |
---|
609 | if (len(self.animationList) > 0): |
---|
610 | f.write(indent(indentation) + "<animations>\n") |
---|
611 | for animation in self.animationList: |
---|
612 | animation.write(f, indentation + 1) |
---|
613 | f.write(indent(indentation) + "</animations>\n") |
---|
614 | return |
---|