smooth all objects (so we see their normals)
[blender-addons.git] / io_scene_fbx / export_fbx.py
blobc4628e36ff1d612182e91fdab4f0c91f68f10d84
1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
19 # <pep8 compliant>
21 # Script copyright (C) Campbell Barton
24 import os
25 import time
26 import math # math.pi
28 import bpy
29 from mathutils import Vector, Matrix
31 # I guess FBX uses degrees instead of radians (Arystan).
32 # Call this function just before writing to FBX.
33 # 180 / math.pi == 57.295779513
34 def tuple_rad_to_deg(eul):
35 return eul[0] * 57.295779513, eul[1] * 57.295779513, eul[2] * 57.295779513
37 # Used to add the scene name into the filepath without using odd chars
38 sane_name_mapping_ob = {}
39 sane_name_mapping_ob_unique = set()
40 sane_name_mapping_mat = {}
41 sane_name_mapping_tex = {}
42 sane_name_mapping_take = {}
43 sane_name_mapping_group = {}
45 # Make sure reserved names are not used
46 sane_name_mapping_ob['Scene'] = 'Scene_'
47 sane_name_mapping_ob_unique.add('Scene_')
50 def increment_string(t):
51 name = t
52 num = ''
53 while name and name[-1].isdigit():
54 num = name[-1] + num
55 name = name[:-1]
56 if num:
57 return '%s%d' % (name, int(num) + 1)
58 else:
59 return name + '_0'
62 # todo - Disallow the name 'Scene' - it will bugger things up.
63 def sane_name(data, dct, unique_set=None):
64 #if not data: return None
66 if type(data) == tuple: # materials are paired up with images
67 data, other = data
68 use_other = True
69 else:
70 other = None
71 use_other = False
73 name = data.name if data else None
74 orig_name = name
76 if other:
77 orig_name_other = other.name
78 name = '%s #%s' % (name, orig_name_other)
79 else:
80 orig_name_other = None
82 # dont cache, only ever call once for each data type now,
83 # so as to avoid namespace collision between types - like with objects <-> bones
84 #try: return dct[name]
85 #except: pass
87 if not name:
88 name = 'unnamed' # blank string, ASKING FOR TROUBLE!
89 else:
91 name = bpy.path.clean_name(name) # use our own
93 name_unique = dct.values() if unique_set is None else unique_set
95 while name in name_unique:
96 name = increment_string(name)
98 if use_other: # even if other is None - orig_name_other will be a string or None
99 dct[orig_name, orig_name_other] = name
100 else:
101 dct[orig_name] = name
103 if unique_set is not None:
104 unique_set.add(name)
106 return name
109 def sane_obname(data):
110 return sane_name(data, sane_name_mapping_ob, sane_name_mapping_ob_unique)
113 def sane_matname(data):
114 return sane_name(data, sane_name_mapping_mat)
117 def sane_texname(data):
118 return sane_name(data, sane_name_mapping_tex)
121 def sane_takename(data):
122 return sane_name(data, sane_name_mapping_take)
125 def sane_groupname(data):
126 return sane_name(data, sane_name_mapping_group)
129 def mat4x4str(mat):
130 # blender matrix is row major, fbx is col major so transpose on write
131 return ("%.15f,%.15f,%.15f,%.15f,"
132 "%.15f,%.15f,%.15f,%.15f,"
133 "%.15f,%.15f,%.15f,%.15f,"
134 "%.15f,%.15f,%.15f,%.15f" %
135 tuple([f for v in mat.transposed() for f in v]))
138 def action_bone_names(obj, action):
139 from bpy.types import PoseBone
141 names = set()
142 path_resolve = obj.path_resolve
144 for fcu in action.fcurves:
145 try:
146 prop = path_resolve(fcu.data_path, False)
147 except:
148 prop = None
150 if prop is not None:
151 data = prop.data
152 if isinstance(data, PoseBone):
153 names.add(data.name)
155 return names
158 # ob must be OB_MESH
159 def BPyMesh_meshWeight2List(ob, me):
160 """ Takes a mesh and return its group names and a list of lists, one list per vertex.
161 aligning the each vert list with the group names, each list contains float value for the weight.
162 These 2 lists can be modified and then used with list2MeshWeight to apply the changes.
165 # Clear the vert group.
166 groupNames = [g.name for g in ob.vertex_groups]
167 len_groupNames = len(groupNames)
169 if not len_groupNames:
170 # no verts? return a vert aligned empty list
171 return [[] for i in range(len(me.vertices))], []
172 else:
173 vWeightList = [[0.0] * len_groupNames for i in range(len(me.vertices))]
175 for i, v in enumerate(me.vertices):
176 for g in v.groups:
177 # possible weights are out of range
178 index = g.group
179 if index < len_groupNames:
180 vWeightList[i][index] = g.weight
182 return groupNames, vWeightList
185 def meshNormalizedWeights(ob, me):
186 groupNames, vWeightList = BPyMesh_meshWeight2List(ob, me)
188 if not groupNames:
189 return [], []
191 for i, vWeights in enumerate(vWeightList):
192 tot = 0.0
193 for w in vWeights:
194 tot += w
196 if tot:
197 for j, w in enumerate(vWeights):
198 vWeights[j] = w / tot
200 return groupNames, vWeightList
202 header_comment = \
203 '''; FBX 6.1.0 project file
204 ; Created by Blender FBX Exporter
205 ; for support mail: ideasman42@gmail.com
206 ; ----------------------------------------------------
211 # This func can be called with just the filepath
212 def save_single(operator, scene, filepath="",
213 global_matrix=None,
214 context_objects=None,
215 object_types={'EMPTY', 'CAMERA', 'LAMP', 'ARMATURE', 'MESH'},
216 use_mesh_modifiers=True,
217 mesh_smooth_type='FACE',
218 use_armature_deform_only=False,
219 use_anim=True,
220 use_anim_optimize=True,
221 anim_optimize_precision=6,
222 use_anim_action_all=False,
223 use_metadata=True,
224 path_mode='AUTO',
225 use_mesh_edges=True,
226 use_default_take=True,
229 import bpy_extras.io_utils
231 # Only used for camera and lamp rotations
232 mtx_x90 = Matrix.Rotation(math.pi / 2.0, 3, 'X')
233 # Used for mesh and armature rotations
234 mtx4_z90 = Matrix.Rotation(math.pi / 2.0, 4, 'Z')
236 if global_matrix is None:
237 global_matrix = Matrix()
238 global_scale = 1.0
239 else:
240 global_scale = global_matrix.median_scale
242 # Use this for working out paths relative to the export location
243 base_src = os.path.dirname(bpy.data.filepath)
244 base_dst = os.path.dirname(filepath)
246 # collect images to copy
247 copy_set = set()
249 # ----------------------------------------------
250 # storage classes
251 class my_bone_class(object):
252 __slots__ = ("blenName",
253 "blenBone",
254 "blenMeshes",
255 "restMatrix",
256 "parent",
257 "blenName",
258 "fbxName",
259 "fbxArm",
260 "__pose_bone",
261 "__anim_poselist")
263 def __init__(self, blenBone, fbxArm):
265 # This is so 2 armatures dont have naming conflicts since FBX bones use object namespace
266 self.fbxName = sane_obname(blenBone)
268 self.blenName = blenBone.name
269 self.blenBone = blenBone
270 self.blenMeshes = {} # fbxMeshObName : mesh
271 self.fbxArm = fbxArm
272 self.restMatrix = blenBone.matrix_local
274 # not used yet
275 #~ self.restMatrixInv = self.restMatrix.inverted()
276 #~ self.restMatrixLocal = None # set later, need parent matrix
278 self.parent = None
280 # not public
281 pose = fbxArm.blenObject.pose
282 self.__pose_bone = pose.bones[self.blenName]
284 # store a list if matrices here, (poseMatrix, head, tail)
285 # {frame:posematrix, frame:posematrix, ...}
286 self.__anim_poselist = {}
289 def calcRestMatrixLocal(self):
290 if self.parent:
291 self.restMatrixLocal = self.restMatrix * self.parent.restMatrix.inverted()
292 else:
293 self.restMatrixLocal = self.restMatrix.copy()
295 def setPoseFrame(self, f):
296 # cache pose info here, frame must be set beforehand
298 # Didnt end up needing head or tail, if we do - here it is.
300 self.__anim_poselist[f] = (\
301 self.__pose_bone.poseMatrix.copy(),\
302 self.__pose_bone.head.copy(),\
303 self.__pose_bone.tail.copy() )
306 self.__anim_poselist[f] = self.__pose_bone.matrix.copy()
308 def getPoseBone(self):
309 return self.__pose_bone
311 # get pose from frame.
312 def getPoseMatrix(self, f): # ----------------------------------------------
313 return self.__anim_poselist[f]
315 def getPoseHead(self, f):
316 #return self.__pose_bone.head.copy()
317 return self.__anim_poselist[f][1].copy()
318 def getPoseTail(self, f):
319 #return self.__pose_bone.tail.copy()
320 return self.__anim_poselist[f][2].copy()
322 # end
324 def getAnimParRelMatrix(self, frame):
325 #arm_mat = self.fbxArm.matrixWorld
326 #arm_mat = self.fbxArm.parRelMatrix()
327 if not self.parent:
328 #return mtx4_z90 * (self.getPoseMatrix(frame) * arm_mat) # dont apply arm matrix anymore
329 return self.getPoseMatrix(frame) * mtx4_z90
330 else:
331 #return (mtx4_z90 * ((self.getPoseMatrix(frame) * arm_mat))) * (mtx4_z90 * (self.parent.getPoseMatrix(frame) * arm_mat)).inverted()
332 return (self.parent.getPoseMatrix(frame) * mtx4_z90).inverted() * ((self.getPoseMatrix(frame)) * mtx4_z90)
334 # we need thes because cameras and lights modified rotations
335 def getAnimParRelMatrixRot(self, frame):
336 return self.getAnimParRelMatrix(frame)
338 def flushAnimData(self):
339 self.__anim_poselist.clear()
341 class my_object_generic(object):
342 __slots__ = ("fbxName",
343 "blenObject",
344 "blenData",
345 "origData",
346 "blenTextures",
347 "blenMaterials",
348 "blenMaterialList",
349 "blenAction",
350 "blenActionList",
351 "fbxGroupNames",
352 "fbxParent",
353 "fbxBoneParent",
354 "fbxBones",
355 "fbxArm",
356 "matrixWorld",
357 "__anim_poselist",
360 # Other settings can be applied for each type - mesh, armature etc.
361 def __init__(self, ob, matrixWorld=None):
362 self.fbxName = sane_obname(ob)
363 self.blenObject = ob
364 self.fbxGroupNames = []
365 self.fbxParent = None # set later on IF the parent is in the selection.
366 self.fbxArm = None
367 if matrixWorld:
368 self.matrixWorld = global_matrix * matrixWorld
369 else:
370 self.matrixWorld = global_matrix * ob.matrix_world
372 self.__anim_poselist = {} # we should only access this
374 def parRelMatrix(self):
375 if self.fbxParent:
376 return self.fbxParent.matrixWorld.inverted() * self.matrixWorld
377 else:
378 return self.matrixWorld
380 def setPoseFrame(self, f, fake=False):
381 if fake:
382 self.__anim_poselist[f] = self.matrixWorld * global_matrix.inverted()
383 else:
384 self.__anim_poselist[f] = self.blenObject.matrix_world.copy()
386 def getAnimParRelMatrix(self, frame):
387 if self.fbxParent:
388 #return (self.__anim_poselist[frame] * self.fbxParent.__anim_poselist[frame].inverted() ) * global_matrix
389 return (global_matrix * self.fbxParent.__anim_poselist[frame]).inverted() * (global_matrix * self.__anim_poselist[frame])
390 else:
391 return global_matrix * self.__anim_poselist[frame]
393 def getAnimParRelMatrixRot(self, frame):
394 obj_type = self.blenObject.type
395 if self.fbxParent:
396 matrix_rot = ((global_matrix * self.fbxParent.__anim_poselist[frame]).inverted() * (global_matrix * self.__anim_poselist[frame])).to_3x3()
397 else:
398 matrix_rot = (global_matrix * self.__anim_poselist[frame]).to_3x3()
400 # Lamps need to be rotated
401 if obj_type == 'LAMP':
402 matrix_rot = matrix_rot * mtx_x90
403 elif obj_type == 'CAMERA':
404 y = matrix_rot * Vector((0.0, 1.0, 0.0))
405 matrix_rot = Matrix.Rotation(math.pi / 2.0, 3, y) * matrix_rot
407 return matrix_rot
409 # ----------------------------------------------
411 print('\nFBX export starting... %r' % filepath)
412 start_time = time.clock()
413 try:
414 file = open(filepath, "w", encoding="utf8", newline="\n")
415 except:
416 import traceback
417 traceback.print_exc()
418 operator.report({'ERROR'}, "Couldn't open file %r" % filepath)
419 return {'CANCELLED'}
421 # convenience
422 fw = file.write
424 # scene = context.scene # now passed as an arg instead of context
425 world = scene.world
427 # ---------------------------- Write the header first
428 fw(header_comment)
429 if use_metadata:
430 curtime = time.localtime()[0:6]
431 else:
432 curtime = (0, 0, 0, 0, 0, 0)
435 '''FBXHeaderExtension: {
436 FBXHeaderVersion: 1003
437 FBXVersion: 6100
438 CreationTimeStamp: {
439 Version: 1000
440 Year: %.4i
441 Month: %.2i
442 Day: %.2i
443 Hour: %.2i
444 Minute: %.2i
445 Second: %.2i
446 Millisecond: 0
448 Creator: "FBX SDK/FBX Plugins build 20070228"
449 OtherFlags: {
450 FlagPLE: 0
452 }''' % (curtime))
454 fw('\nCreationTime: "%.4i-%.2i-%.2i %.2i:%.2i:%.2i:000"' % curtime)
455 fw('\nCreator: "Blender version %s"' % bpy.app.version_string)
457 pose_items = [] # list of (fbxName, matrix) to write pose data for, easier to collect along the way
459 # --------------- funcs for exporting
460 def object_tx(ob, loc, matrix, matrix_mod=None):
462 Matrix mod is so armature objects can modify their bone matrices
464 if isinstance(ob, bpy.types.Bone):
466 # we know we have a matrix
467 # matrix = mtx4_z90 * (ob.matrix['ARMATURESPACE'] * matrix_mod)
468 matrix = ob.matrix_local * mtx4_z90 # dont apply armature matrix anymore
470 parent = ob.parent
471 if parent:
472 #par_matrix = mtx4_z90 * (parent.matrix['ARMATURESPACE'] * matrix_mod)
473 par_matrix = parent.matrix_local * mtx4_z90 # dont apply armature matrix anymore
474 matrix = par_matrix.inverted() * matrix
476 loc, rot, scale = matrix.decompose()
477 matrix_rot = rot.to_matrix()
479 loc = tuple(loc)
480 rot = tuple(rot.to_euler()) # quat -> euler
481 scale = tuple(scale)
483 else:
484 # This is bad because we need the parent relative matrix from the fbx parent (if we have one), dont use anymore
485 #if ob and not matrix: matrix = ob.matrix_world * global_matrix
486 if ob and not matrix:
487 raise Exception("error: this should never happen!")
489 matrix_rot = matrix
490 #if matrix:
491 # matrix = matrix_scale * matrix
493 if matrix:
494 loc, rot, scale = matrix.decompose()
495 matrix_rot = rot.to_matrix()
497 # Lamps need to be rotated
498 if ob and ob.type == 'LAMP':
499 matrix_rot = matrix_rot * mtx_x90
500 elif ob and ob.type == 'CAMERA':
501 y = matrix_rot * Vector((0.0, 1.0, 0.0))
502 matrix_rot = Matrix.Rotation(math.pi / 2.0, 3, y) * matrix_rot
503 # else do nothing.
505 loc = tuple(loc)
506 rot = tuple(matrix_rot.to_euler())
507 scale = tuple(scale)
508 else:
509 if not loc:
510 loc = 0.0, 0.0, 0.0
511 scale = 1.0, 1.0, 1.0
512 rot = 0.0, 0.0, 0.0
514 return loc, rot, scale, matrix, matrix_rot
516 def write_object_tx(ob, loc, matrix, matrix_mod=None):
518 We have loc to set the location if non blender objects that have a location
520 matrix_mod is only used for bones at the moment
522 loc, rot, scale, matrix, matrix_rot = object_tx(ob, loc, matrix, matrix_mod)
524 fw('\n\t\t\tProperty: "Lcl Translation", "Lcl Translation", "A+",%.15f,%.15f,%.15f' % loc)
525 fw('\n\t\t\tProperty: "Lcl Rotation", "Lcl Rotation", "A+",%.15f,%.15f,%.15f' % tuple_rad_to_deg(rot))
526 fw('\n\t\t\tProperty: "Lcl Scaling", "Lcl Scaling", "A+",%.15f,%.15f,%.15f' % scale)
527 return loc, rot, scale, matrix, matrix_rot
529 def get_constraints(ob=None):
530 # Set variables to their defaults.
531 constraint_values = {"loc_min": (0.0, 0.0, 0.0),
532 "loc_max": (0.0, 0.0, 0.0),
533 "loc_limit": (0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
534 "rot_min": (0.0, 0.0, 0.0),
535 "rot_max": (0.0, 0.0, 0.0),
536 "rot_limit": (0.0, 0.0, 0.0),
537 "sca_min": (1.0, 1.0, 1.0),
538 "sca_max": (1.0, 1.0, 1.0),
539 "sca_limit": (0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
542 # Iterate through the list of constraints for this object to get the information in a format which is compatible with the FBX format.
543 if ob is not None:
544 for constraint in ob.constraints:
545 if constraint.type == 'LIMIT_LOCATION':
546 constraint_values["loc_min"] = constraint.min_x, constraint.min_y, constraint.min_z
547 constraint_values["loc_max"] = constraint.max_x, constraint.max_y, constraint.max_z
548 constraint_values["loc_limit"] = constraint.use_min_x, constraint.use_min_y, constraint.use_min_z, constraint.use_max_x, constraint.use_max_y, constraint.use_max_z
549 elif constraint.type == 'LIMIT_ROTATION':
550 constraint_values["rot_min"] = math.degrees(constraint.min_x), math.degrees(constraint.min_y), math.degrees(constraint.min_z)
551 constraint_values["rot_max"] = math.degrees(constraint.max_x), math.degrees(constraint.max_y), math.degrees(constraint.max_z)
552 constraint_values["rot_limit"] = constraint.use_limit_x, constraint.use_limit_y, constraint.use_limit_z
553 elif constraint.type == 'LIMIT_SCALE':
554 constraint_values["sca_min"] = constraint.min_x, constraint.min_y, constraint.min_z
555 constraint_values["sca_max"] = constraint.max_x, constraint.max_y, constraint.max_z
556 constraint_values["sca_limit"] = constraint.use_min_x, constraint.use_min_y, constraint.use_min_z, constraint.use_max_x, constraint.use_max_y, constraint.use_max_z
558 # in case bad values are assigned.
559 assert(len(constraint_values) == 9)
561 return constraint_values
563 def write_object_props(ob=None, loc=None, matrix=None, matrix_mod=None, pose_bone=None):
564 # Check if a pose exists for this object and set the constraint soruce accordingly. (Poses only exsit if the object is a bone.)
565 if pose_bone:
566 constraints = get_constraints(pose_bone)
567 else:
568 constraints = get_constraints(ob)
570 # if the type is 0 its an empty otherwise its a mesh
571 # only difference at the moment is one has a color
572 fw('''
573 Properties60: {
574 Property: "QuaternionInterpolate", "bool", "",0
575 Property: "Visibility", "Visibility", "A+",1''')
577 loc, rot, scale, matrix, matrix_rot = write_object_tx(ob, loc, matrix, matrix_mod)
579 # Rotation order, note, for FBX files Iv loaded normal order is 1
580 # setting to zero.
581 # eEULER_XYZ = 0
582 # eEULER_XZY
583 # eEULER_YZX
584 # eEULER_YXZ
585 # eEULER_ZXY
586 # eEULER_ZYX
588 fw('\n\t\t\tProperty: "RotationOffset", "Vector3D", "",0,0,0'
589 '\n\t\t\tProperty: "RotationPivot", "Vector3D", "",0,0,0'
590 '\n\t\t\tProperty: "ScalingOffset", "Vector3D", "",0,0,0'
591 '\n\t\t\tProperty: "ScalingPivot", "Vector3D", "",0,0,0'
592 '\n\t\t\tProperty: "TranslationActive", "bool", "",0'
595 fw('\n\t\t\tProperty: "TranslationMin", "Vector3D", "",%.15g,%.15g,%.15g' % constraints["loc_min"])
596 fw('\n\t\t\tProperty: "TranslationMax", "Vector3D", "",%.15g,%.15g,%.15g' % constraints["loc_max"])
597 fw('\n\t\t\tProperty: "TranslationMinX", "bool", "",%d' % constraints["loc_limit"][0])
598 fw('\n\t\t\tProperty: "TranslationMinY", "bool", "",%d' % constraints["loc_limit"][1])
599 fw('\n\t\t\tProperty: "TranslationMinZ", "bool", "",%d' % constraints["loc_limit"][2])
600 fw('\n\t\t\tProperty: "TranslationMaxX", "bool", "",%d' % constraints["loc_limit"][3])
601 fw('\n\t\t\tProperty: "TranslationMaxY", "bool", "",%d' % constraints["loc_limit"][4])
602 fw('\n\t\t\tProperty: "TranslationMaxZ", "bool", "",%d' % constraints["loc_limit"][5])
604 fw('\n\t\t\tProperty: "RotationOrder", "enum", "",0'
605 '\n\t\t\tProperty: "RotationSpaceForLimitOnly", "bool", "",0'
606 '\n\t\t\tProperty: "AxisLen", "double", "",10'
607 '\n\t\t\tProperty: "PreRotation", "Vector3D", "",0,0,0'
608 '\n\t\t\tProperty: "PostRotation", "Vector3D", "",0,0,0'
609 '\n\t\t\tProperty: "RotationActive", "bool", "",0'
612 fw('\n\t\t\tProperty: "RotationMin", "Vector3D", "",%.15g,%.15g,%.15g' % constraints["rot_min"])
613 fw('\n\t\t\tProperty: "RotationMax", "Vector3D", "",%.15g,%.15g,%.15g' % constraints["rot_max"])
614 fw('\n\t\t\tProperty: "RotationMinX", "bool", "",%d' % constraints["rot_limit"][0])
615 fw('\n\t\t\tProperty: "RotationMinY", "bool", "",%d' % constraints["rot_limit"][1])
616 fw('\n\t\t\tProperty: "RotationMinZ", "bool", "",%d' % constraints["rot_limit"][2])
617 fw('\n\t\t\tProperty: "RotationMaxX", "bool", "",%d' % constraints["rot_limit"][0])
618 fw('\n\t\t\tProperty: "RotationMaxY", "bool", "",%d' % constraints["rot_limit"][1])
619 fw('\n\t\t\tProperty: "RotationMaxZ", "bool", "",%d' % constraints["rot_limit"][2])
621 fw('\n\t\t\tProperty: "RotationStiffnessX", "double", "",0'
622 '\n\t\t\tProperty: "RotationStiffnessY", "double", "",0'
623 '\n\t\t\tProperty: "RotationStiffnessZ", "double", "",0'
624 '\n\t\t\tProperty: "MinDampRangeX", "double", "",0'
625 '\n\t\t\tProperty: "MinDampRangeY", "double", "",0'
626 '\n\t\t\tProperty: "MinDampRangeZ", "double", "",0'
627 '\n\t\t\tProperty: "MaxDampRangeX", "double", "",0'
628 '\n\t\t\tProperty: "MaxDampRangeY", "double", "",0'
629 '\n\t\t\tProperty: "MaxDampRangeZ", "double", "",0'
630 '\n\t\t\tProperty: "MinDampStrengthX", "double", "",0'
631 '\n\t\t\tProperty: "MinDampStrengthY", "double", "",0'
632 '\n\t\t\tProperty: "MinDampStrengthZ", "double", "",0'
633 '\n\t\t\tProperty: "MaxDampStrengthX", "double", "",0'
634 '\n\t\t\tProperty: "MaxDampStrengthY", "double", "",0'
635 '\n\t\t\tProperty: "MaxDampStrengthZ", "double", "",0'
636 '\n\t\t\tProperty: "PreferedAngleX", "double", "",0'
637 '\n\t\t\tProperty: "PreferedAngleY", "double", "",0'
638 '\n\t\t\tProperty: "PreferedAngleZ", "double", "",0'
639 '\n\t\t\tProperty: "InheritType", "enum", "",0'
640 '\n\t\t\tProperty: "ScalingActive", "bool", "",0'
643 fw('\n\t\t\tProperty: "ScalingMin", "Vector3D", "",%.15g,%.15g,%.15g' % constraints["sca_min"])
644 fw('\n\t\t\tProperty: "ScalingMax", "Vector3D", "",%.15g,%.15g,%.15g' % constraints["sca_max"])
645 fw('\n\t\t\tProperty: "ScalingMinX", "bool", "",%d' % constraints["sca_limit"][0])
646 fw('\n\t\t\tProperty: "ScalingMinY", "bool", "",%d' % constraints["sca_limit"][1])
647 fw('\n\t\t\tProperty: "ScalingMinZ", "bool", "",%d' % constraints["sca_limit"][2])
648 fw('\n\t\t\tProperty: "ScalingMaxX", "bool", "",%d' % constraints["sca_limit"][3])
649 fw('\n\t\t\tProperty: "ScalingMaxY", "bool", "",%d' % constraints["sca_limit"][4])
650 fw('\n\t\t\tProperty: "ScalingMaxZ", "bool", "",%d' % constraints["sca_limit"][5])
652 fw('\n\t\t\tProperty: "GeometricTranslation", "Vector3D", "",0,0,0'
653 '\n\t\t\tProperty: "GeometricRotation", "Vector3D", "",0,0,0'
654 '\n\t\t\tProperty: "GeometricScaling", "Vector3D", "",1,1,1'
655 '\n\t\t\tProperty: "LookAtProperty", "object", ""'
656 '\n\t\t\tProperty: "UpVectorProperty", "object", ""'
657 '\n\t\t\tProperty: "Show", "bool", "",1'
658 '\n\t\t\tProperty: "NegativePercentShapeSupport", "bool", "",1'
659 '\n\t\t\tProperty: "DefaultAttributeIndex", "int", "",0'
662 if ob and not isinstance(ob, bpy.types.Bone):
663 # Only mesh objects have color
664 fw('\n\t\t\tProperty: "Color", "Color", "A",0.8,0.8,0.8'
665 '\n\t\t\tProperty: "Size", "double", "",100'
666 '\n\t\t\tProperty: "Look", "enum", "",1'
669 return loc, rot, scale, matrix, matrix_rot
671 # -------------------------------------------- Armatures
672 #def write_bone(bone, name, matrix_mod):
673 def write_bone(my_bone):
674 fw('\n\tModel: "Model::%s", "Limb" {' % my_bone.fbxName)
675 fw('\n\t\tVersion: 232')
677 #~ poseMatrix = write_object_props(my_bone.blenBone, None, None, my_bone.fbxArm.parRelMatrix())[3]
678 poseMatrix = write_object_props(my_bone.blenBone, pose_bone=my_bone.getPoseBone())[3] # dont apply bone matrices anymore
680 # Use the same calculation as in write_sub_deformer_skin to compute the global
681 # transform of the bone for the bind pose.
682 global_matrix_bone = (my_bone.fbxArm.matrixWorld * my_bone.restMatrix) * mtx4_z90
683 pose_items.append((my_bone.fbxName, global_matrix_bone))
685 # fw('\n\t\t\tProperty: "Size", "double", "",%.6f' % ((my_bone.blenData.head['ARMATURESPACE'] - my_bone.blenData.tail['ARMATURESPACE']) * my_bone.fbxArm.parRelMatrix()).length)
686 fw('\n\t\t\tProperty: "Size", "double", "",1')
688 #((my_bone.blenData.head['ARMATURESPACE'] * my_bone.fbxArm.matrixWorld) - (my_bone.blenData.tail['ARMATURESPACE'] * my_bone.fbxArm.parRelMatrix())).length)
691 fw('\n\t\t\tProperty: "LimbLength", "double", "",%.6f' %\
692 ((my_bone.blenBone.head['ARMATURESPACE'] - my_bone.blenBone.tail['ARMATURESPACE']) * my_bone.fbxArm.parRelMatrix()).length)
695 fw('\n\t\t\tProperty: "LimbLength", "double", "",%.6f' %
696 (my_bone.blenBone.head_local - my_bone.blenBone.tail_local).length)
698 #fw('\n\t\t\tProperty: "LimbLength", "double", "",1')
699 fw('\n\t\t\tProperty: "Color", "ColorRGB", "",0.8,0.8,0.8'
700 '\n\t\t\tProperty: "Color", "Color", "A",0.8,0.8,0.8'
701 '\n\t\t}'
702 '\n\t\tMultiLayer: 0'
703 '\n\t\tMultiTake: 1'
704 '\n\t\tShading: Y'
705 '\n\t\tCulling: "CullingOff"'
706 '\n\t\tTypeFlags: "Skeleton"'
707 '\n\t}'
710 def write_camera_switch():
711 fw('''
712 Model: "Model::Camera Switcher", "CameraSwitcher" {
713 Version: 232''')
715 write_object_props()
716 fw('''
717 Property: "Color", "Color", "A",0.8,0.8,0.8
718 Property: "Camera Index", "Integer", "A+",100
720 MultiLayer: 0
721 MultiTake: 1
722 Hidden: "True"
723 Shading: W
724 Culling: "CullingOff"
725 Version: 101
726 Name: "Model::Camera Switcher"
727 CameraId: 0
728 CameraName: 100
729 CameraIndexName:
730 }''')
732 def write_camera_dummy(name, loc, near, far, proj_type, up):
733 fw('\n\tModel: "Model::%s", "Camera" {' % name)
734 fw('\n\t\tVersion: 232')
735 write_object_props(None, loc)
737 fw('\n\t\t\tProperty: "Color", "Color", "A",0.8,0.8,0.8'
738 '\n\t\t\tProperty: "Roll", "Roll", "A+",0'
739 '\n\t\t\tProperty: "FieldOfView", "FieldOfView", "A+",40'
740 '\n\t\t\tProperty: "FieldOfViewX", "FieldOfView", "A+",1'
741 '\n\t\t\tProperty: "FieldOfViewY", "FieldOfView", "A+",1'
742 '\n\t\t\tProperty: "OpticalCenterX", "Real", "A+",0'
743 '\n\t\t\tProperty: "OpticalCenterY", "Real", "A+",0'
744 '\n\t\t\tProperty: "BackgroundColor", "Color", "A+",0.63,0.63,0.63'
745 '\n\t\t\tProperty: "TurnTable", "Real", "A+",0'
746 '\n\t\t\tProperty: "DisplayTurnTableIcon", "bool", "",1'
747 '\n\t\t\tProperty: "Motion Blur Intensity", "Real", "A+",1'
748 '\n\t\t\tProperty: "UseMotionBlur", "bool", "",0'
749 '\n\t\t\tProperty: "UseRealTimeMotionBlur", "bool", "",1'
750 '\n\t\t\tProperty: "ResolutionMode", "enum", "",0'
751 '\n\t\t\tProperty: "ApertureMode", "enum", "",2'
752 '\n\t\t\tProperty: "GateFit", "enum", "",0'
753 '\n\t\t\tProperty: "FocalLength", "Real", "A+",21.3544940948486'
754 '\n\t\t\tProperty: "CameraFormat", "enum", "",0'
755 '\n\t\t\tProperty: "AspectW", "double", "",320'
756 '\n\t\t\tProperty: "AspectH", "double", "",200'
757 '\n\t\t\tProperty: "PixelAspectRatio", "double", "",1'
758 '\n\t\t\tProperty: "UseFrameColor", "bool", "",0'
759 '\n\t\t\tProperty: "FrameColor", "ColorRGB", "",0.3,0.3,0.3'
760 '\n\t\t\tProperty: "ShowName", "bool", "",1'
761 '\n\t\t\tProperty: "ShowGrid", "bool", "",1'
762 '\n\t\t\tProperty: "ShowOpticalCenter", "bool", "",0'
763 '\n\t\t\tProperty: "ShowAzimut", "bool", "",1'
764 '\n\t\t\tProperty: "ShowTimeCode", "bool", "",0'
767 fw('\n\t\t\tProperty: "NearPlane", "double", "",%.6f' % near)
768 fw('\n\t\t\tProperty: "FarPlane", "double", "",%.6f' % far)
770 fw('\n\t\t\tProperty: "FilmWidth", "double", "",0.816'
771 '\n\t\t\tProperty: "FilmHeight", "double", "",0.612'
772 '\n\t\t\tProperty: "FilmAspectRatio", "double", "",1.33333333333333'
773 '\n\t\t\tProperty: "FilmSqueezeRatio", "double", "",1'
774 '\n\t\t\tProperty: "FilmFormatIndex", "enum", "",4'
775 '\n\t\t\tProperty: "ViewFrustum", "bool", "",1'
776 '\n\t\t\tProperty: "ViewFrustumNearFarPlane", "bool", "",0'
777 '\n\t\t\tProperty: "ViewFrustumBackPlaneMode", "enum", "",2'
778 '\n\t\t\tProperty: "BackPlaneDistance", "double", "",100'
779 '\n\t\t\tProperty: "BackPlaneDistanceMode", "enum", "",0'
780 '\n\t\t\tProperty: "ViewCameraToLookAt", "bool", "",1'
781 '\n\t\t\tProperty: "LockMode", "bool", "",0'
782 '\n\t\t\tProperty: "LockInterestNavigation", "bool", "",0'
783 '\n\t\t\tProperty: "FitImage", "bool", "",0'
784 '\n\t\t\tProperty: "Crop", "bool", "",0'
785 '\n\t\t\tProperty: "Center", "bool", "",1'
786 '\n\t\t\tProperty: "KeepRatio", "bool", "",1'
787 '\n\t\t\tProperty: "BackgroundMode", "enum", "",0'
788 '\n\t\t\tProperty: "BackgroundAlphaTreshold", "double", "",0.5'
789 '\n\t\t\tProperty: "ForegroundTransparent", "bool", "",1'
790 '\n\t\t\tProperty: "DisplaySafeArea", "bool", "",0'
791 '\n\t\t\tProperty: "SafeAreaDisplayStyle", "enum", "",1'
792 '\n\t\t\tProperty: "SafeAreaAspectRatio", "double", "",1.33333333333333'
793 '\n\t\t\tProperty: "Use2DMagnifierZoom", "bool", "",0'
794 '\n\t\t\tProperty: "2D Magnifier Zoom", "Real", "A+",100'
795 '\n\t\t\tProperty: "2D Magnifier X", "Real", "A+",50'
796 '\n\t\t\tProperty: "2D Magnifier Y", "Real", "A+",50'
799 fw('\n\t\t\tProperty: "CameraProjectionType", "enum", "",%i' % proj_type)
801 fw('\n\t\t\tProperty: "UseRealTimeDOFAndAA", "bool", "",0'
802 '\n\t\t\tProperty: "UseDepthOfField", "bool", "",0'
803 '\n\t\t\tProperty: "FocusSource", "enum", "",0'
804 '\n\t\t\tProperty: "FocusAngle", "double", "",3.5'
805 '\n\t\t\tProperty: "FocusDistance", "double", "",200'
806 '\n\t\t\tProperty: "UseAntialiasing", "bool", "",0'
807 '\n\t\t\tProperty: "AntialiasingIntensity", "double", "",0.77777'
808 '\n\t\t\tProperty: "UseAccumulationBuffer", "bool", "",0'
809 '\n\t\t\tProperty: "FrameSamplingCount", "int", "",7'
810 '\n\t\t}'
811 '\n\t\tMultiLayer: 0'
812 '\n\t\tMultiTake: 0'
813 '\n\t\tHidden: "True"'
814 '\n\t\tShading: Y'
815 '\n\t\tCulling: "CullingOff"'
816 '\n\t\tTypeFlags: "Camera"'
817 '\n\t\tGeometryVersion: 124'
820 fw('\n\t\tPosition: %.6f,%.6f,%.6f' % loc)
821 fw('\n\t\tUp: %i,%i,%i' % up)
823 fw('\n\t\tLookAt: 0,0,0'
824 '\n\t\tShowInfoOnMoving: 1'
825 '\n\t\tShowAudio: 0'
826 '\n\t\tAudioColor: 0,1,0'
827 '\n\t\tCameraOrthoZoom: 1'
828 '\n\t}'
831 def write_camera_default():
832 # This sucks but to match FBX converter its easier to
833 # write the cameras though they are not needed.
834 write_camera_dummy('Producer Perspective', (0, 71.3, 287.5), 10, 4000, 0, (0, 1, 0))
835 write_camera_dummy('Producer Top', (0, 4000, 0), 1, 30000, 1, (0, 0, -1))
836 write_camera_dummy('Producer Bottom', (0, -4000, 0), 1, 30000, 1, (0, 0, -1))
837 write_camera_dummy('Producer Front', (0, 0, 4000), 1, 30000, 1, (0, 1, 0))
838 write_camera_dummy('Producer Back', (0, 0, -4000), 1, 30000, 1, (0, 1, 0))
839 write_camera_dummy('Producer Right', (4000, 0, 0), 1, 30000, 1, (0, 1, 0))
840 write_camera_dummy('Producer Left', (-4000, 0, 0), 1, 30000, 1, (0, 1, 0))
842 def write_camera(my_cam):
844 Write a blender camera
846 render = scene.render
847 width = render.resolution_x
848 height = render.resolution_y
849 aspect = width / height
851 data = my_cam.blenObject.data
852 # film width & height from mm to inches
853 filmwidth = data.sensor_width * 0.0393700787
854 filmheight = data.sensor_height * 0.0393700787
855 filmaspect = filmwidth / filmheight
856 # film offset
857 offsetx = filmwidth * data.shift_x
858 offsety = filmaspect * filmheight * data.shift_y
860 fw('\n\tModel: "Model::%s", "Camera" {' % my_cam.fbxName)
861 fw('\n\t\tVersion: 232')
862 loc, rot, scale, matrix, matrix_rot = write_object_props(my_cam.blenObject, None, my_cam.parRelMatrix())
864 fw('\n\t\t\tProperty: "Roll", "Roll", "A+",0')
865 fw('\n\t\t\tProperty: "FieldOfView", "FieldOfView", "A+",%.6f' % math.degrees(data.angle_y))
867 fw('\n\t\t\tProperty: "FieldOfViewX", "FieldOfView", "A+",1'
868 '\n\t\t\tProperty: "FieldOfViewY", "FieldOfView", "A+",1'
871 fw('\n\t\t\tProperty: "FocalLength", "Number", "A+",%.6f' % data.lens)
872 fw('\n\t\t\tProperty: "FilmOffsetX", "Number", "A+",%.6f' % offsetx)
873 fw('\n\t\t\tProperty: "FilmOffsetY", "Number", "A+",%.6f' % offsety)
875 fw('\n\t\t\tProperty: "BackgroundColor", "Color", "A+",0,0,0'
876 '\n\t\t\tProperty: "TurnTable", "Real", "A+",0'
877 '\n\t\t\tProperty: "DisplayTurnTableIcon", "bool", "",1'
878 '\n\t\t\tProperty: "Motion Blur Intensity", "Real", "A+",1'
879 '\n\t\t\tProperty: "UseMotionBlur", "bool", "",0'
880 '\n\t\t\tProperty: "UseRealTimeMotionBlur", "bool", "",1'
881 '\n\t\t\tProperty: "ResolutionMode", "enum", "",0'
882 '\n\t\t\tProperty: "ApertureMode", "enum", "",3' # horizontal - Houdini compatible
883 '\n\t\t\tProperty: "GateFit", "enum", "",2'
884 '\n\t\t\tProperty: "CameraFormat", "enum", "",0'
887 fw('\n\t\t\tProperty: "AspectW", "double", "",%i' % width)
888 fw('\n\t\t\tProperty: "AspectH", "double", "",%i' % height)
890 """Camera aspect ratio modes.
891 0 If the ratio mode is eWINDOW_SIZE, both width and height values aren't relevant.
892 1 If the ratio mode is eFIXED_RATIO, the height value is set to 1.0 and the width value is relative to the height value.
893 2 If the ratio mode is eFIXED_RESOLUTION, both width and height values are in pixels.
894 3 If the ratio mode is eFIXED_WIDTH, the width value is in pixels and the height value is relative to the width value.
895 4 If the ratio mode is eFIXED_HEIGHT, the height value is in pixels and the width value is relative to the height value.
897 Definition at line 234 of file kfbxcamera.h. """
899 fw('\n\t\t\tProperty: "PixelAspectRatio", "double", "",1'
900 '\n\t\t\tProperty: "UseFrameColor", "bool", "",0'
901 '\n\t\t\tProperty: "FrameColor", "ColorRGB", "",0.3,0.3,0.3'
902 '\n\t\t\tProperty: "ShowName", "bool", "",1'
903 '\n\t\t\tProperty: "ShowGrid", "bool", "",1'
904 '\n\t\t\tProperty: "ShowOpticalCenter", "bool", "",0'
905 '\n\t\t\tProperty: "ShowAzimut", "bool", "",1'
906 '\n\t\t\tProperty: "ShowTimeCode", "bool", "",0'
909 fw('\n\t\t\tProperty: "NearPlane", "double", "",%.6f' % (data.clip_start * global_scale))
910 fw('\n\t\t\tProperty: "FarPlane", "double", "",%.6f' % (data.clip_end * global_scale))
912 fw('\n\t\t\tProperty: "FilmWidth", "double", "",%.6f' % filmwidth)
913 fw('\n\t\t\tProperty: "FilmHeight", "double", "",%.6f' % filmheight)
914 fw('\n\t\t\tProperty: "FilmAspectRatio", "double", "",%.6f' % filmaspect)
916 fw('\n\t\t\tProperty: "FilmSqueezeRatio", "double", "",1'
917 '\n\t\t\tProperty: "FilmFormatIndex", "enum", "",0'
918 '\n\t\t\tProperty: "ViewFrustum", "bool", "",1'
919 '\n\t\t\tProperty: "ViewFrustumNearFarPlane", "bool", "",0'
920 '\n\t\t\tProperty: "ViewFrustumBackPlaneMode", "enum", "",2'
921 '\n\t\t\tProperty: "BackPlaneDistance", "double", "",100'
922 '\n\t\t\tProperty: "BackPlaneDistanceMode", "enum", "",0'
923 '\n\t\t\tProperty: "ViewCameraToLookAt", "bool", "",1'
924 '\n\t\t\tProperty: "LockMode", "bool", "",0'
925 '\n\t\t\tProperty: "LockInterestNavigation", "bool", "",0'
926 '\n\t\t\tProperty: "FitImage", "bool", "",0'
927 '\n\t\t\tProperty: "Crop", "bool", "",0'
928 '\n\t\t\tProperty: "Center", "bool", "",1'
929 '\n\t\t\tProperty: "KeepRatio", "bool", "",1'
930 '\n\t\t\tProperty: "BackgroundMode", "enum", "",0'
931 '\n\t\t\tProperty: "BackgroundAlphaTreshold", "double", "",0.5'
932 '\n\t\t\tProperty: "ForegroundTransparent", "bool", "",1'
933 '\n\t\t\tProperty: "DisplaySafeArea", "bool", "",0'
934 '\n\t\t\tProperty: "SafeAreaDisplayStyle", "enum", "",1'
937 fw('\n\t\t\tProperty: "SafeAreaAspectRatio", "double", "",%.6f' % aspect)
939 fw('\n\t\t\tProperty: "Use2DMagnifierZoom", "bool", "",0'
940 '\n\t\t\tProperty: "2D Magnifier Zoom", "Real", "A+",100'
941 '\n\t\t\tProperty: "2D Magnifier X", "Real", "A+",50'
942 '\n\t\t\tProperty: "2D Magnifier Y", "Real", "A+",50'
943 '\n\t\t\tProperty: "CameraProjectionType", "enum", "",0'
944 '\n\t\t\tProperty: "UseRealTimeDOFAndAA", "bool", "",0'
945 '\n\t\t\tProperty: "UseDepthOfField", "bool", "",0'
946 '\n\t\t\tProperty: "FocusSource", "enum", "",0'
947 '\n\t\t\tProperty: "FocusAngle", "double", "",3.5'
948 '\n\t\t\tProperty: "FocusDistance", "double", "",200'
949 '\n\t\t\tProperty: "UseAntialiasing", "bool", "",0'
950 '\n\t\t\tProperty: "AntialiasingIntensity", "double", "",0.77777'
951 '\n\t\t\tProperty: "UseAccumulationBuffer", "bool", "",0'
952 '\n\t\t\tProperty: "FrameSamplingCount", "int", "",7'
955 fw('\n\t\t}')
957 fw('\n\t\tMultiLayer: 0'
958 '\n\t\tMultiTake: 0'
959 '\n\t\tShading: Y'
960 '\n\t\tCulling: "CullingOff"'
961 '\n\t\tTypeFlags: "Camera"'
962 '\n\t\tGeometryVersion: 124'
965 fw('\n\t\tPosition: %.6f,%.6f,%.6f' % loc)
966 fw('\n\t\tUp: %.6f,%.6f,%.6f' % (matrix_rot * Vector((0.0, 1.0, 0.0)))[:])
967 fw('\n\t\tLookAt: %.6f,%.6f,%.6f' % (matrix_rot * Vector((0.0, 0.0, -1.0)))[:])
969 #fw('\n\t\tUp: 0,0,0' )
970 #fw('\n\t\tLookAt: 0,0,0' )
972 fw('\n\t\tShowInfoOnMoving: 1')
973 fw('\n\t\tShowAudio: 0')
974 fw('\n\t\tAudioColor: 0,1,0')
975 fw('\n\t\tCameraOrthoZoom: 1')
976 fw('\n\t}')
978 def write_light(my_light):
979 light = my_light.blenObject.data
980 fw('\n\tModel: "Model::%s", "Light" {' % my_light.fbxName)
981 fw('\n\t\tVersion: 232')
983 write_object_props(my_light.blenObject, None, my_light.parRelMatrix())
985 # Why are these values here twice?????? - oh well, follow the holy sdk's output
987 # Blender light types match FBX's, funny coincidence, we just need to
988 # be sure that all unsupported types are made into a point light
989 #ePOINT,
990 #eDIRECTIONAL
991 #eSPOT
992 light_type_items = {'POINT': 0, 'SUN': 1, 'SPOT': 2, 'HEMI': 3, 'AREA': 4}
993 light_type = light_type_items[light.type]
995 if light_type > 2:
996 light_type = 1 # hemi and area lights become directional
998 if light.type == 'HEMI':
999 do_light = not (light.use_diffuse or light.use_specular)
1000 do_shadow = False
1001 else:
1002 do_light = not (light.use_only_shadow or (not light.use_diffuse and not light.use_specular))
1003 do_shadow = (light.shadow_method in {'RAY_SHADOW', 'BUFFER_SHADOW'})
1005 # scale = abs(global_matrix.to_scale()[0]) # scale is always uniform in this case # UNUSED
1007 fw('\n\t\t\tProperty: "LightType", "enum", "",%i' % light_type)
1008 fw('\n\t\t\tProperty: "CastLightOnObject", "bool", "",1')
1009 fw('\n\t\t\tProperty: "DrawVolumetricLight", "bool", "",1')
1010 fw('\n\t\t\tProperty: "DrawGroundProjection", "bool", "",1')
1011 fw('\n\t\t\tProperty: "DrawFrontFacingVolumetricLight", "bool", "",0')
1012 fw('\n\t\t\tProperty: "GoboProperty", "object", ""')
1013 fw('\n\t\t\tProperty: "Color", "Color", "A+",1,1,1')
1014 fw('\n\t\t\tProperty: "Intensity", "Intensity", "A+",%.2f' % (min(light.energy * 100.0, 200.0))) # clamp below 200
1015 if light.type == 'SPOT':
1016 fw('\n\t\t\tProperty: "Cone angle", "Cone angle", "A+",%.2f' % math.degrees(light.spot_size))
1017 fw('\n\t\t\tProperty: "Fog", "Fog", "A+",50')
1018 fw('\n\t\t\tProperty: "Color", "Color", "A",%.2f,%.2f,%.2f' % tuple(light.color))
1020 fw('\n\t\t\tProperty: "Intensity", "Intensity", "A+",%.2f' % (min(light.energy * 100.0, 200.0))) # clamp below 200
1022 fw('\n\t\t\tProperty: "Fog", "Fog", "A+",50')
1023 fw('\n\t\t\tProperty: "LightType", "enum", "",%i' % light_type)
1024 fw('\n\t\t\tProperty: "CastLightOnObject", "bool", "",%i' % do_light)
1025 fw('\n\t\t\tProperty: "DrawGroundProjection", "bool", "",1')
1026 fw('\n\t\t\tProperty: "DrawFrontFacingVolumetricLight", "bool", "",0')
1027 fw('\n\t\t\tProperty: "DrawVolumetricLight", "bool", "",1')
1028 fw('\n\t\t\tProperty: "GoboProperty", "object", ""')
1029 fw('\n\t\t\tProperty: "DecayType", "enum", "",0')
1030 fw('\n\t\t\tProperty: "DecayStart", "double", "",%.2f' % light.distance)
1032 fw('\n\t\t\tProperty: "EnableNearAttenuation", "bool", "",0'
1033 '\n\t\t\tProperty: "NearAttenuationStart", "double", "",0'
1034 '\n\t\t\tProperty: "NearAttenuationEnd", "double", "",0'
1035 '\n\t\t\tProperty: "EnableFarAttenuation", "bool", "",0'
1036 '\n\t\t\tProperty: "FarAttenuationStart", "double", "",0'
1037 '\n\t\t\tProperty: "FarAttenuationEnd", "double", "",0'
1040 fw('\n\t\t\tProperty: "CastShadows", "bool", "",%i' % do_shadow)
1041 fw('\n\t\t\tProperty: "ShadowColor", "ColorRGBA", "",0,0,0,1')
1042 fw('\n\t\t}')
1044 fw('\n\t\tMultiLayer: 0'
1045 '\n\t\tMultiTake: 0'
1046 '\n\t\tShading: Y'
1047 '\n\t\tCulling: "CullingOff"'
1048 '\n\t\tTypeFlags: "Light"'
1049 '\n\t\tGeometryVersion: 124'
1050 '\n\t}'
1053 # matrixOnly is not used at the moment
1054 def write_null(my_null=None, fbxName=None, fbxType="Null", fbxTypeFlags="Null"):
1055 # ob can be null
1056 if not fbxName:
1057 fbxName = my_null.fbxName
1059 fw('\n\tModel: "Model::%s", "%s" {' % (fbxName, fbxType))
1060 fw('\n\t\tVersion: 232')
1062 if my_null:
1063 poseMatrix = write_object_props(my_null.blenObject, None, my_null.parRelMatrix())[3]
1064 else:
1065 poseMatrix = write_object_props()[3]
1067 pose_items.append((fbxName, poseMatrix))
1069 fw('\n\t\t}'
1070 '\n\t\tMultiLayer: 0'
1071 '\n\t\tMultiTake: 1'
1072 '\n\t\tShading: Y'
1073 '\n\t\tCulling: "CullingOff"'
1076 fw('\n\t\tTypeFlags: "%s"' % fbxTypeFlags)
1077 fw('\n\t}')
1079 # Material Settings
1080 if world:
1081 world_amb = world.ambient_color[:]
1082 else:
1083 world_amb = 0.0, 0.0, 0.0 # default value
1085 def write_material(matname, mat):
1086 fw('\n\tMaterial: "Material::%s", "" {' % matname)
1088 # Todo, add more material Properties.
1089 if mat:
1090 mat_cold = tuple(mat.diffuse_color)
1091 mat_cols = tuple(mat.specular_color)
1092 #mat_colm = tuple(mat.mirCol) # we wont use the mirror color
1093 mat_colamb = world_amb
1095 mat_dif = mat.diffuse_intensity
1096 mat_amb = mat.ambient
1097 mat_hard = (float(mat.specular_hardness) - 1.0) / 5.10
1098 mat_spec = mat.specular_intensity / 2.0
1099 mat_alpha = mat.alpha
1100 mat_emit = mat.emit
1101 mat_shadeless = mat.use_shadeless
1102 if mat_shadeless:
1103 mat_shader = 'Lambert'
1104 else:
1105 if mat.diffuse_shader == 'LAMBERT':
1106 mat_shader = 'Lambert'
1107 else:
1108 mat_shader = 'Phong'
1109 else:
1110 mat_cols = mat_cold = 0.8, 0.8, 0.8
1111 mat_colamb = 0.0, 0.0, 0.0
1112 # mat_colm
1113 mat_dif = 1.0
1114 mat_amb = 0.5
1115 mat_hard = 20.0
1116 mat_spec = 0.2
1117 mat_alpha = 1.0
1118 mat_emit = 0.0
1119 mat_shadeless = False
1120 mat_shader = 'Phong'
1122 fw('\n\t\tVersion: 102')
1123 fw('\n\t\tShadingModel: "%s"' % mat_shader.lower())
1124 fw('\n\t\tMultiLayer: 0')
1126 fw('\n\t\tProperties60: {')
1127 fw('\n\t\t\tProperty: "ShadingModel", "KString", "", "%s"' % mat_shader)
1128 fw('\n\t\t\tProperty: "MultiLayer", "bool", "",0')
1129 fw('\n\t\t\tProperty: "EmissiveColor", "ColorRGB", "",%.4f,%.4f,%.4f' % mat_cold) # emit and diffuse color are he same in blender
1130 fw('\n\t\t\tProperty: "EmissiveFactor", "double", "",%.4f' % mat_emit)
1132 fw('\n\t\t\tProperty: "AmbientColor", "ColorRGB", "",%.4f,%.4f,%.4f' % mat_colamb)
1133 fw('\n\t\t\tProperty: "AmbientFactor", "double", "",%.4f' % mat_amb)
1134 fw('\n\t\t\tProperty: "DiffuseColor", "ColorRGB", "",%.4f,%.4f,%.4f' % mat_cold)
1135 fw('\n\t\t\tProperty: "DiffuseFactor", "double", "",%.4f' % mat_dif)
1136 fw('\n\t\t\tProperty: "Bump", "Vector3D", "",0,0,0')
1137 fw('\n\t\t\tProperty: "TransparentColor", "ColorRGB", "",1,1,1')
1138 fw('\n\t\t\tProperty: "TransparencyFactor", "double", "",%.4f' % (1.0 - mat_alpha))
1139 if not mat_shadeless:
1140 fw('\n\t\t\tProperty: "SpecularColor", "ColorRGB", "",%.4f,%.4f,%.4f' % mat_cols)
1141 fw('\n\t\t\tProperty: "SpecularFactor", "double", "",%.4f' % mat_spec)
1142 fw('\n\t\t\tProperty: "ShininessExponent", "double", "",80.0')
1143 fw('\n\t\t\tProperty: "ReflectionColor", "ColorRGB", "",0,0,0')
1144 fw('\n\t\t\tProperty: "ReflectionFactor", "double", "",1')
1145 fw('\n\t\t\tProperty: "Emissive", "ColorRGB", "",0,0,0')
1146 fw('\n\t\t\tProperty: "Ambient", "ColorRGB", "",%.1f,%.1f,%.1f' % mat_colamb)
1147 fw('\n\t\t\tProperty: "Diffuse", "ColorRGB", "",%.1f,%.1f,%.1f' % mat_cold)
1148 if not mat_shadeless:
1149 fw('\n\t\t\tProperty: "Specular", "ColorRGB", "",%.1f,%.1f,%.1f' % mat_cols)
1150 fw('\n\t\t\tProperty: "Shininess", "double", "",%.1f' % mat_hard)
1151 fw('\n\t\t\tProperty: "Opacity", "double", "",%.1f' % mat_alpha)
1152 if not mat_shadeless:
1153 fw('\n\t\t\tProperty: "Reflectivity", "double", "",0')
1155 fw('\n\t\t}')
1156 fw('\n\t}')
1158 # tex is an Image (Arystan)
1159 def write_video(texname, tex):
1160 # Same as texture really!
1161 fw('\n\tVideo: "Video::%s", "Clip" {' % texname)
1163 fw('''
1164 Type: "Clip"
1165 Properties60: {
1166 Property: "FrameRate", "double", "",0
1167 Property: "LastFrame", "int", "",0
1168 Property: "Width", "int", "",0
1169 Property: "Height", "int", "",0''')
1170 if tex:
1171 fname_rel = bpy_extras.io_utils.path_reference(tex.filepath, base_src, base_dst, path_mode, "", copy_set, tex.library)
1172 fname_strip = bpy.path.basename(fname_rel)
1173 else:
1174 fname_strip = fname_rel = ""
1176 fw('\n\t\t\tProperty: "Path", "charptr", "", "%s"' % fname_strip)
1178 fw('''
1179 Property: "StartFrame", "int", "",0
1180 Property: "StopFrame", "int", "",0
1181 Property: "PlaySpeed", "double", "",1
1182 Property: "Offset", "KTime", "",0
1183 Property: "InterlaceMode", "enum", "",0
1184 Property: "FreeRunning", "bool", "",0
1185 Property: "Loop", "bool", "",0
1186 Property: "AccessMode", "enum", "",0
1188 UseMipMap: 0''')
1190 fw('\n\t\tFilename: "%s"' % fname_strip)
1191 fw('\n\t\tRelativeFilename: "%s"' % fname_rel) # make relative
1192 fw('\n\t}')
1194 def write_texture(texname, tex, num):
1195 # if tex is None then this is a dummy tex
1196 fw('\n\tTexture: "Texture::%s", "TextureVideoClip" {' % texname)
1197 fw('\n\t\tType: "TextureVideoClip"')
1198 fw('\n\t\tVersion: 202')
1199 # TODO, rare case _empty_ exists as a name.
1200 fw('\n\t\tTextureName: "Texture::%s"' % texname)
1202 fw('''
1203 Properties60: {
1204 Property: "Translation", "Vector", "A+",0,0,0
1205 Property: "Rotation", "Vector", "A+",0,0,0
1206 Property: "Scaling", "Vector", "A+",1,1,1''')
1207 fw('\n\t\t\tProperty: "Texture alpha", "Number", "A+",%i' % num)
1209 # WrapModeU/V 0==rep, 1==clamp, TODO add support
1210 fw('''
1211 Property: "TextureTypeUse", "enum", "",0
1212 Property: "CurrentTextureBlendMode", "enum", "",1
1213 Property: "UseMaterial", "bool", "",0
1214 Property: "UseMipMap", "bool", "",0
1215 Property: "CurrentMappingType", "enum", "",0
1216 Property: "UVSwap", "bool", "",0''')
1218 fw('\n\t\t\tProperty: "WrapModeU", "enum", "",%i' % tex.use_clamp_x)
1219 fw('\n\t\t\tProperty: "WrapModeV", "enum", "",%i' % tex.use_clamp_y)
1221 fw('''
1222 Property: "TextureRotationPivot", "Vector3D", "",0,0,0
1223 Property: "TextureScalingPivot", "Vector3D", "",0,0,0
1224 Property: "VideoProperty", "object", ""
1225 }''')
1227 fw('\n\t\tMedia: "Video::%s"' % texname)
1229 if tex:
1230 fname_rel = bpy_extras.io_utils.path_reference(tex.filepath, base_src, base_dst, path_mode, "", copy_set, tex.library)
1231 fname_strip = bpy.path.basename(fname_rel)
1232 else:
1233 fname_strip = fname_rel = ""
1235 fw('\n\t\tFileName: "%s"' % fname_strip)
1236 fw('\n\t\tRelativeFilename: "%s"' % fname_rel) # need some make relative command
1238 fw('''
1239 ModelUVTranslation: 0,0
1240 ModelUVScaling: 1,1
1241 Texture_Alpha_Source: "None"
1242 Cropping: 0,0,0,0
1243 }''')
1245 def write_deformer_skin(obname):
1247 Each mesh has its own deformer
1249 fw('\n\tDeformer: "Deformer::Skin %s", "Skin" {' % obname)
1250 fw('''
1251 Version: 100
1252 MultiLayer: 0
1253 Type: "Skin"
1254 Properties60: {
1256 Link_DeformAcuracy: 50
1257 }''')
1259 # in the example was 'Bip01 L Thigh_2'
1260 def write_sub_deformer_skin(my_mesh, my_bone, weights):
1263 Each subdeformer is specific to a mesh, but the bone it links to can be used by many sub-deformers
1264 So the SubDeformer needs the mesh-object name as a prefix to make it unique
1266 Its possible that there is no matching vgroup in this mesh, in that case no verts are in the subdeformer,
1267 a but silly but dosnt really matter
1269 fw('\n\tDeformer: "SubDeformer::Cluster %s %s", "Cluster" {' % (my_mesh.fbxName, my_bone.fbxName))
1271 fw('''
1272 Version: 100
1273 MultiLayer: 0
1274 Type: "Cluster"
1275 Properties60: {
1276 Property: "SrcModel", "object", ""
1277 Property: "SrcModelReference", "object", ""
1279 UserData: "", ""''')
1281 # Support for bone parents
1282 if my_mesh.fbxBoneParent:
1283 if my_mesh.fbxBoneParent == my_bone:
1284 # TODO - this is a bit lazy, we could have a simple write loop
1285 # for this case because all weights are 1.0 but for now this is ok
1286 # Parent Bones arent used all that much anyway.
1287 vgroup_data = [(j, 1.0) for j in range(len(my_mesh.blenData.vertices))]
1288 else:
1289 # This bone is not a parent of this mesh object, no weights
1290 vgroup_data = []
1292 else:
1293 # Normal weight painted mesh
1294 if my_bone.blenName in weights[0]:
1295 # Before we used normalized weight list
1296 group_index = weights[0].index(my_bone.blenName)
1297 vgroup_data = [(j, weight[group_index]) for j, weight in enumerate(weights[1]) if weight[group_index]]
1298 else:
1299 vgroup_data = []
1301 fw('\n\t\tIndexes: ')
1303 i = -1
1304 for vg in vgroup_data:
1305 if i == -1:
1306 fw('%i' % vg[0])
1307 i = 0
1308 else:
1309 if i == 23:
1310 fw('\n\t\t')
1311 i = 0
1312 fw(',%i' % vg[0])
1313 i += 1
1315 fw('\n\t\tWeights: ')
1316 i = -1
1317 for vg in vgroup_data:
1318 if i == -1:
1319 fw('%.8f' % vg[1])
1320 i = 0
1321 else:
1322 if i == 38:
1323 fw('\n\t\t')
1324 i = 0
1325 fw(',%.8f' % vg[1])
1326 i += 1
1328 # Set TransformLink to the global transform of the bone and Transform
1329 # equal to the mesh's transform in bone space.
1330 # http://area.autodesk.com/forum/autodesk-fbx/fbx-sdk/why-the-values-return-by-fbxcluster-gettransformmatrix-x-not-same-with-the-value-in-ascii-fbx-file/
1332 global_bone_matrix = (my_bone.fbxArm.matrixWorld * my_bone.restMatrix) * mtx4_z90
1333 global_mesh_matrix = my_mesh.matrixWorld
1334 transform_matrix = (global_bone_matrix.inverted() * global_mesh_matrix)
1336 global_bone_matrix_string = mat4x4str(global_bone_matrix )
1337 transform_matrix_string = mat4x4str(transform_matrix )
1339 fw('\n\t\tTransform: %s' % transform_matrix_string)
1340 fw('\n\t\tTransformLink: %s' % global_bone_matrix_string)
1341 fw('\n\t}')
1343 def write_mesh(my_mesh):
1345 me = my_mesh.blenData
1347 # if there are non NULL materials on this mesh
1348 do_materials = bool(my_mesh.blenMaterials)
1349 do_textures = bool(my_mesh.blenTextures)
1350 do_uvs = bool(me.tessface_uv_textures)
1351 do_shapekeys = (my_mesh.blenObject.type == 'MESH' and
1352 my_mesh.blenObject.data.shape_keys and
1353 len(my_mesh.blenObject.data.vertices) == len(me.vertices))
1355 fw('\n\tModel: "Model::%s", "Mesh" {' % my_mesh.fbxName)
1356 fw('\n\t\tVersion: 232') # newline is added in write_object_props
1358 # convert into lists once.
1359 me_vertices = me.vertices[:]
1360 me_edges = me.edges[:] if use_mesh_edges else ()
1361 me_faces = me.tessfaces[:]
1363 poseMatrix = write_object_props(my_mesh.blenObject, None, my_mesh.parRelMatrix())[3]
1365 # Calculate the global transform for the mesh in the bind pose the same way we do
1366 # in write_sub_deformer_skin
1367 globalMeshBindPose = my_mesh.matrixWorld * mtx4_z90
1368 pose_items.append((my_mesh.fbxName, globalMeshBindPose))
1370 if do_shapekeys:
1371 for kb in my_mesh.blenObject.data.shape_keys.key_blocks[1:]:
1372 fw('\n\t\t\tProperty: "%s", "Number", "AN",0' % kb.name)
1374 fw('\n\t\t}')
1376 fw('\n\t\tMultiLayer: 0'
1377 '\n\t\tMultiTake: 1'
1378 '\n\t\tShading: Y'
1379 '\n\t\tCulling: "CullingOff"'
1382 # Write the Real Mesh data here
1383 fw('\n\t\tVertices: ')
1384 i = -1
1386 for v in me_vertices:
1387 if i == -1:
1388 fw('%.6f,%.6f,%.6f' % v.co[:])
1389 i = 0
1390 else:
1391 if i == 7:
1392 fw('\n\t\t')
1393 i = 0
1394 fw(',%.6f,%.6f,%.6f' % v.co[:])
1395 i += 1
1397 fw('\n\t\tPolygonVertexIndex: ')
1398 i = -1
1399 for f in me_faces:
1400 fi = f.vertices[:]
1402 # last index XORd w. -1 indicates end of face
1403 if i == -1:
1404 if len(fi) == 3:
1405 fw('%i,%i,%i' % (fi[0], fi[1], fi[2] ^ -1))
1406 else:
1407 fw('%i,%i,%i,%i' % (fi[0], fi[1], fi[2], fi[3] ^ -1))
1408 i = 0
1409 else:
1410 if i == 13:
1411 fw('\n\t\t')
1412 i = 0
1413 if len(fi) == 3:
1414 fw(',%i,%i,%i' % (fi[0], fi[1], fi[2] ^ -1))
1415 else:
1416 fw(',%i,%i,%i,%i' % (fi[0], fi[1], fi[2], fi[3] ^ -1))
1417 i += 1
1419 # write loose edges as faces.
1420 for ed in me_edges:
1421 if ed.is_loose:
1422 ed_val = ed.vertices[:]
1423 ed_val = ed_val[0], ed_val[-1] ^ -1
1425 if i == -1:
1426 fw('%i,%i' % ed_val)
1427 i = 0
1428 else:
1429 if i == 13:
1430 fw('\n\t\t')
1431 i = 0
1432 fw(',%i,%i' % ed_val)
1433 i += 1
1435 fw('\n\t\tEdges: ')
1436 i = -1
1437 for ed in me_edges:
1438 if i == -1:
1439 fw('%i,%i' % (ed.vertices[0], ed.vertices[1]))
1440 i = 0
1441 else:
1442 if i == 13:
1443 fw('\n\t\t')
1444 i = 0
1445 fw(',%i,%i' % (ed.vertices[0], ed.vertices[1]))
1446 i += 1
1448 fw('\n\t\tGeometryVersion: 124')
1450 fw('''
1451 LayerElementNormal: 0 {
1452 Version: 101
1453 Name: ""
1454 MappingInformationType: "ByVertice"
1455 ReferenceInformationType: "Direct"
1456 Normals: ''')
1458 i = -1
1459 for v in me_vertices:
1460 if i == -1:
1461 fw('%.15f,%.15f,%.15f' % v.normal[:])
1462 i = 0
1463 else:
1464 if i == 2:
1465 fw('\n\t\t\t ')
1466 i = 0
1467 fw(',%.15f,%.15f,%.15f' % v.normal[:])
1468 i += 1
1469 fw('\n\t\t}')
1471 # Write Face Smoothing
1472 if mesh_smooth_type == 'FACE':
1473 fw('''
1474 LayerElementSmoothing: 0 {
1475 Version: 102
1476 Name: ""
1477 MappingInformationType: "ByPolygon"
1478 ReferenceInformationType: "Direct"
1479 Smoothing: ''')
1481 i = -1
1482 for f in me_faces:
1483 if i == -1:
1484 fw('%i' % f.use_smooth)
1485 i = 0
1486 else:
1487 if i == 54:
1488 fw('\n\t\t\t ')
1489 i = 0
1490 fw(',%i' % f.use_smooth)
1491 i += 1
1493 fw('\n\t\t}')
1495 elif mesh_smooth_type == 'EDGE':
1496 # Write Edge Smoothing
1497 fw('''
1498 LayerElementSmoothing: 0 {
1499 Version: 101
1500 Name: ""
1501 MappingInformationType: "ByEdge"
1502 ReferenceInformationType: "Direct"
1503 Smoothing: ''')
1505 i = -1
1506 for ed in me_edges:
1507 if i == -1:
1508 fw('%i' % (ed.use_edge_sharp))
1509 i = 0
1510 else:
1511 if i == 54:
1512 fw('\n\t\t\t ')
1513 i = 0
1514 fw(',%i' % ed.use_edge_sharp)
1515 i += 1
1517 fw('\n\t\t}')
1518 elif mesh_smooth_type == 'OFF':
1519 pass
1520 else:
1521 raise Exception("invalid mesh_smooth_type: %r" % mesh_smooth_type)
1523 # Write VertexColor Layers
1524 # note, no programs seem to use this info :/
1525 collayers = []
1526 if len(me.tessface_vertex_colors):
1527 collayers = me.tessface_vertex_colors
1528 for colindex, collayer in enumerate(collayers):
1529 fw('\n\t\tLayerElementColor: %i {' % colindex)
1530 fw('\n\t\t\tVersion: 101')
1531 fw('\n\t\t\tName: "%s"' % collayer.name)
1533 fw('''
1534 MappingInformationType: "ByPolygonVertex"
1535 ReferenceInformationType: "IndexToDirect"
1536 Colors: ''')
1538 i = -1
1539 ii = 0 # Count how many Colors we write
1540 print(len(me_faces), len(collayer.data))
1541 for fi, cf in enumerate(collayer.data):
1542 if len(me_faces[fi].vertices) == 4:
1543 colors = cf.color1[:], cf.color2[:], cf.color3[:], cf.color4[:]
1544 else:
1545 colors = cf.color1[:], cf.color2[:], cf.color3[:]
1547 for col in colors:
1548 if i == -1:
1549 fw('%.4f,%.4f,%.4f,1' % col)
1550 i = 0
1551 else:
1552 if i == 7:
1553 fw('\n\t\t\t\t')
1554 i = 0
1555 fw(',%.4f,%.4f,%.4f,1' % col)
1556 i += 1
1557 ii += 1 # One more Color
1559 fw('\n\t\t\tColorIndex: ')
1560 i = -1
1561 for j in range(ii):
1562 if i == -1:
1563 fw('%i' % j)
1564 i = 0
1565 else:
1566 if i == 55:
1567 fw('\n\t\t\t\t')
1568 i = 0
1569 fw(',%i' % j)
1570 i += 1
1572 fw('\n\t\t}')
1574 # Write UV and texture layers.
1575 uvlayers = []
1576 if do_uvs:
1577 uvlayers = me.tessface_uv_textures
1578 for uvindex, uvlayer in enumerate(me.tessface_uv_textures):
1579 fw('\n\t\tLayerElementUV: %i {' % uvindex)
1580 fw('\n\t\t\tVersion: 101')
1581 fw('\n\t\t\tName: "%s"' % uvlayer.name)
1583 fw('''
1584 MappingInformationType: "ByPolygonVertex"
1585 ReferenceInformationType: "IndexToDirect"
1586 UV: ''')
1588 i = -1
1589 ii = 0 # Count how many UVs we write
1591 for uf in uvlayer.data:
1592 # workaround, since uf.uv iteration is wrong atm
1593 for uv in uf.uv:
1594 if i == -1:
1595 fw('%.6f,%.6f' % uv[:])
1596 i = 0
1597 else:
1598 if i == 7:
1599 fw('\n\t\t\t ')
1600 i = 0
1601 fw(',%.6f,%.6f' % uv[:])
1602 i += 1
1603 ii += 1 # One more UV
1605 fw('\n\t\t\tUVIndex: ')
1606 i = -1
1607 for j in range(ii):
1608 if i == -1:
1609 fw('%i' % j)
1610 i = 0
1611 else:
1612 if i == 55:
1613 fw('\n\t\t\t\t')
1614 i = 0
1615 fw(',%i' % j)
1616 i += 1
1618 fw('\n\t\t}')
1620 if do_textures:
1621 fw('\n\t\tLayerElementTexture: %i {' % uvindex)
1622 fw('\n\t\t\tVersion: 101')
1623 fw('\n\t\t\tName: "%s"' % uvlayer.name)
1625 if len(my_mesh.blenTextures) == 1:
1626 fw('\n\t\t\tMappingInformationType: "AllSame"')
1627 else:
1628 fw('\n\t\t\tMappingInformationType: "ByPolygon"')
1630 fw('\n\t\t\tReferenceInformationType: "IndexToDirect"')
1631 fw('\n\t\t\tBlendMode: "Translucent"')
1632 fw('\n\t\t\tTextureAlpha: 1')
1633 fw('\n\t\t\tTextureId: ')
1635 if len(my_mesh.blenTextures) == 1:
1636 fw('0')
1637 else:
1638 texture_mapping_local = {None: -1}
1640 i = 0 # 1 for dummy
1641 for tex in my_mesh.blenTextures:
1642 if tex: # None is set above
1643 texture_mapping_local[tex] = i
1644 i += 1
1646 i = -1
1647 for f in uvlayer.data:
1648 img_key = f.image
1650 if i == -1:
1651 i = 0
1652 fw('%s' % texture_mapping_local[img_key])
1653 else:
1654 if i == 55:
1655 fw('\n ')
1656 i = 0
1658 fw(',%s' % texture_mapping_local[img_key])
1659 i += 1
1661 else:
1662 fw('''
1663 LayerElementTexture: 0 {
1664 Version: 101
1665 Name: ""
1666 MappingInformationType: "NoMappingInformation"
1667 ReferenceInformationType: "IndexToDirect"
1668 BlendMode: "Translucent"
1669 TextureAlpha: 1
1670 TextureId: ''')
1671 fw('\n\t\t}')
1673 # Done with UV/textures.
1674 if do_materials:
1675 fw('\n\t\tLayerElementMaterial: 0 {')
1676 fw('\n\t\t\tVersion: 101')
1677 fw('\n\t\t\tName: ""')
1679 if len(my_mesh.blenMaterials) == 1:
1680 fw('\n\t\t\tMappingInformationType: "AllSame"')
1681 else:
1682 fw('\n\t\t\tMappingInformationType: "ByPolygon"')
1684 fw('\n\t\t\tReferenceInformationType: "IndexToDirect"')
1685 fw('\n\t\t\tMaterials: ')
1687 if len(my_mesh.blenMaterials) == 1:
1688 fw('0')
1689 else:
1690 # Build a material mapping for this
1691 material_mapping_local = {} # local-mat & tex : global index.
1693 for j, mat_tex_pair in enumerate(my_mesh.blenMaterials):
1694 material_mapping_local[mat_tex_pair] = j
1696 mats = my_mesh.blenMaterialList
1698 if me.tessface_uv_textures.active:
1699 uv_faces = me.tessface_uv_textures.active.data
1700 else:
1701 uv_faces = [None] * len(me_faces)
1703 i = -1
1704 for f, uf in zip(me_faces, uv_faces):
1705 try:
1706 mat = mats[f.material_index]
1707 except:
1708 mat = None
1710 if do_uvs:
1711 tex = uf.image # WARNING - MULTI UV LAYER IMAGES NOT SUPPORTED :/
1712 else:
1713 tex = None
1715 if i == -1:
1716 i = 0
1717 fw('%s' % material_mapping_local[mat, tex]) # None for mat or tex is ok
1718 else:
1719 if i == 55:
1720 fw('\n\t\t\t\t')
1721 i = 0
1723 fw(',%s' % material_mapping_local[mat, tex])
1724 i += 1
1726 fw('\n\t\t}')
1728 fw('''
1729 Layer: 0 {
1730 Version: 100
1731 LayerElement: {
1732 Type: "LayerElementNormal"
1733 TypedIndex: 0
1734 }''')
1736 if do_materials:
1737 fw('''
1738 LayerElement: {
1739 Type: "LayerElementMaterial"
1740 TypedIndex: 0
1741 }''')
1743 # Smoothing info
1744 if mesh_smooth_type != 'OFF':
1745 fw('''
1746 LayerElement: {
1747 Type: "LayerElementSmoothing"
1748 TypedIndex: 0
1749 }''')
1751 # Always write this
1752 if do_textures:
1753 fw('''
1754 LayerElement: {
1755 Type: "LayerElementTexture"
1756 TypedIndex: 0
1757 }''')
1759 if me.tessface_vertex_colors:
1760 fw('''
1761 LayerElement: {
1762 Type: "LayerElementColor"
1763 TypedIndex: 0
1764 }''')
1766 if do_uvs: # same as me.faceUV
1767 fw('''
1768 LayerElement: {
1769 Type: "LayerElementUV"
1770 TypedIndex: 0
1771 }''')
1773 fw('\n\t\t}')
1775 if len(uvlayers) > 1:
1776 for i in range(1, len(uvlayers)):
1778 fw('\n\t\tLayer: %i {' % i)
1779 fw('\n\t\t\tVersion: 100')
1781 fw('''
1782 LayerElement: {
1783 Type: "LayerElementUV"''')
1785 fw('\n\t\t\t\tTypedIndex: %i' % i)
1786 fw('\n\t\t\t}')
1788 if do_textures:
1790 fw('''
1791 LayerElement: {
1792 Type: "LayerElementTexture"''')
1794 fw('\n\t\t\t\tTypedIndex: %i' % i)
1795 fw('\n\t\t\t}')
1797 fw('\n\t\t}')
1799 if len(collayers) > 1:
1800 # Take into account any UV layers
1801 layer_offset = 0
1802 if uvlayers:
1803 layer_offset = len(uvlayers) - 1
1805 for i in range(layer_offset, len(collayers) + layer_offset):
1806 fw('\n\t\tLayer: %i {' % i)
1807 fw('\n\t\t\tVersion: 100')
1809 fw('''
1810 LayerElement: {
1811 Type: "LayerElementColor"''')
1813 fw('\n\t\t\t\tTypedIndex: %i' % i)
1814 fw('\n\t\t\t}')
1815 fw('\n\t\t}')
1817 if do_shapekeys:
1818 key_blocks = my_mesh.blenObject.data.shape_keys.key_blocks[:]
1819 for kb in key_blocks[1:]:
1821 fw('\n\t\tShape: "%s" {' % kb.name)
1822 fw('\n\t\t\tIndexes: ')
1824 basis_verts = key_blocks[0].data
1825 range_verts = []
1826 delta_verts = []
1827 i = -1
1828 for j, kv in enumerate(kb.data):
1829 delta = kv.co - basis_verts[j].co
1830 if delta.length > 0.000001:
1831 if i == -1:
1832 fw('%d' % j)
1833 else:
1834 if i == 7:
1835 fw('\n\t\t\t')
1836 i = 0
1837 fw(',%d' % j)
1838 delta_verts.append(delta[:])
1839 i += 1
1841 fw('\n\t\t\tVertices: ')
1842 i = -1
1843 for dv in delta_verts:
1844 if i == -1:
1845 fw("%.6f,%.6f,%.6f" % dv)
1846 else:
1847 if i == 4:
1848 fw('\n\t\t\t')
1849 i = 0
1850 fw(",%.6f,%.6f,%.6f" % dv)
1851 i += 1
1853 # all zero, why? - campbell
1854 fw('\n\t\t\tNormals: ')
1855 for j in range(len(delta_verts)):
1856 if i == -1:
1857 fw("0,0,0")
1858 else:
1859 if i == 4:
1860 fw('\n\t\t\t')
1861 i = 0
1862 fw(",0,0,0")
1863 i += 1
1864 fw('\n\t\t}')
1866 fw('\n\t}')
1868 def write_group(name):
1869 fw('\n\tGroupSelection: "GroupSelection::%s", "Default" {' % name)
1871 fw('''
1872 Properties60: {
1873 Property: "MultiLayer", "bool", "",0
1874 Property: "Pickable", "bool", "",1
1875 Property: "Transformable", "bool", "",1
1876 Property: "Show", "bool", "",1
1878 MultiLayer: 0
1879 }''')
1881 # add meshes here to clear because they are not used anywhere.
1882 meshes_to_clear = []
1884 ob_meshes = []
1885 ob_lights = []
1886 ob_cameras = []
1887 # in fbx we export bones as children of the mesh
1888 # armatures not a part of a mesh, will be added to ob_arms
1889 ob_bones = []
1890 ob_arms = []
1891 ob_null = [] # emptys
1893 # List of types that have blender objects (not bones)
1894 ob_all_typegroups = [ob_meshes, ob_lights, ob_cameras, ob_arms, ob_null]
1896 groups = [] # blender groups, only add ones that have objects in the selections
1897 materials = {} # (mat, image) keys, should be a set()
1898 textures = {} # should be a set()
1900 tmp_ob_type = None # in case no objects are exported, so as not to raise an error
1902 ## XXX
1904 if 'ARMATURE' in object_types:
1905 # This is needed so applying modifiers dosnt apply the armature deformation, its also needed
1906 # ...so mesh objects return their rest worldspace matrix when bone-parents are exported as weighted meshes.
1907 # set every armature to its rest, backup the original values so we done mess up the scene
1908 ob_arms_orig_rest = [arm.pose_position for arm in bpy.data.armatures]
1910 for arm in bpy.data.armatures:
1911 arm.pose_position = 'REST'
1913 if ob_arms_orig_rest:
1914 for ob_base in bpy.data.objects:
1915 if ob_base.type == 'ARMATURE':
1916 ob_base.update_tag()
1918 # This causes the makeDisplayList command to effect the mesh
1919 scene.frame_set(scene.frame_current)
1921 for ob_base in context_objects:
1923 # ignore dupli children
1924 if ob_base.parent and ob_base.parent.dupli_type in {'VERTS', 'FACES'}:
1925 continue
1927 obs = [(ob_base, ob_base.matrix_world.copy())]
1928 if ob_base.dupli_type != 'NONE':
1929 ob_base.dupli_list_create(scene)
1930 obs = [(dob.object, dob.matrix.copy()) for dob in ob_base.dupli_list]
1932 for ob, mtx in obs:
1933 tmp_ob_type = ob.type
1934 if tmp_ob_type == 'CAMERA':
1935 if 'CAMERA' in object_types:
1936 ob_cameras.append(my_object_generic(ob, mtx))
1937 elif tmp_ob_type == 'LAMP':
1938 if 'LAMP' in object_types:
1939 ob_lights.append(my_object_generic(ob, mtx))
1940 elif tmp_ob_type == 'ARMATURE':
1941 if 'ARMATURE' in object_types:
1942 # TODO - armatures dont work in dupligroups!
1943 if ob not in ob_arms:
1944 ob_arms.append(ob)
1945 # ob_arms.append(ob) # replace later. was "ob_arms.append(sane_obname(ob), ob)"
1946 elif tmp_ob_type == 'EMPTY':
1947 if 'EMPTY' in object_types:
1948 ob_null.append(my_object_generic(ob, mtx))
1949 elif 'MESH' in object_types:
1950 origData = True
1951 if tmp_ob_type != 'MESH':
1952 try:
1953 me = ob.to_mesh(scene, True, 'PREVIEW')
1954 except:
1955 me = None
1957 if me:
1958 meshes_to_clear.append(me)
1959 mats = me.materials
1960 origData = False
1961 else:
1962 # Mesh Type!
1963 if use_mesh_modifiers:
1964 me = ob.to_mesh(scene, True, 'PREVIEW')
1966 # print ob, me, me.getVertGroupNames()
1967 meshes_to_clear.append(me)
1968 origData = False
1969 mats = me.materials
1970 else:
1971 me = ob.data
1972 me.update(calc_tessface=True)
1973 mats = me.materials
1975 # # Support object colors
1976 # tmp_colbits = ob.colbits
1977 # if tmp_colbits:
1978 # tmp_ob_mats = ob.getMaterials(1) # 1 so we get None's too.
1979 # for i in xrange(16):
1980 # if tmp_colbits & (1<<i):
1981 # mats[i] = tmp_ob_mats[i]
1982 # del tmp_ob_mats
1983 # del tmp_colbits
1985 if me:
1986 # # This WILL modify meshes in blender if use_mesh_modifiers is disabled.
1987 # # so strictly this is bad. but only in rare cases would it have negative results
1988 # # say with dupliverts the objects would rotate a bit differently
1989 # if EXP_MESH_HQ_NORMALS:
1990 # BPyMesh.meshCalcNormals(me) # high quality normals nice for realtime engines.
1992 texture_mapping_local = {}
1993 material_mapping_local = {}
1994 if me.tessface_uv_textures:
1995 for uvlayer in me.tessface_uv_textures:
1996 for f, uf in zip(me.tessfaces, uvlayer.data):
1997 tex = uf.image
1998 textures[tex] = texture_mapping_local[tex] = None
2000 try:
2001 mat = mats[f.material_index]
2002 except:
2003 mat = None
2005 materials[mat, tex] = material_mapping_local[mat, tex] = None # should use sets, wait for blender 2.5
2007 else:
2008 for mat in mats:
2009 # 2.44 use mat.lib too for uniqueness
2010 materials[mat, None] = material_mapping_local[mat, None] = None
2011 else:
2012 materials[None, None] = None
2014 if 'ARMATURE' in object_types:
2015 armob = ob.find_armature()
2016 blenParentBoneName = None
2018 # parent bone - special case
2019 if (not armob) and ob.parent and ob.parent.type == 'ARMATURE' and \
2020 ob.parent_type == 'BONE':
2021 armob = ob.parent
2022 blenParentBoneName = ob.parent_bone
2024 if armob and armob not in ob_arms:
2025 ob_arms.append(armob)
2027 # Warning for scaled, mesh objects with armatures
2028 if abs(ob.scale[0] - 1.0) > 0.05 or abs(ob.scale[1] - 1.0) > 0.05 or abs(ob.scale[1] - 1.0) > 0.05:
2029 operator.report({'WARNING'}, "Object '%s' has a scale of (%.3f, %.3f, %.3f), " \
2030 "Armature deformation will not work as expected " \
2031 "(apply Scale to fix)" % ((ob.name,) + tuple(ob.scale)))
2033 else:
2034 blenParentBoneName = armob = None
2036 my_mesh = my_object_generic(ob, mtx)
2037 my_mesh.blenData = me
2038 my_mesh.origData = origData
2039 my_mesh.blenMaterials = list(material_mapping_local.keys())
2040 my_mesh.blenMaterialList = mats
2041 my_mesh.blenTextures = list(texture_mapping_local.keys())
2043 # sort the name so we get predictable output, some items may be NULL
2044 my_mesh.blenMaterials.sort(key=lambda m: (getattr(m[0], "name", ""), getattr(m[1], "name", "")))
2045 my_mesh.blenTextures.sort(key=lambda m: getattr(m, "name", ""))
2047 # if only 1 null texture then empty the list
2048 if len(my_mesh.blenTextures) == 1 and my_mesh.blenTextures[0] is None:
2049 my_mesh.blenTextures = []
2051 my_mesh.fbxArm = armob # replace with my_object_generic armature instance later
2052 my_mesh.fbxBoneParent = blenParentBoneName # replace with my_bone instance later
2054 ob_meshes.append(my_mesh)
2056 # not forgetting to free dupli_list
2057 if ob_base.dupli_list:
2058 ob_base.dupli_list_clear()
2060 if 'ARMATURE' in object_types:
2061 # now we have the meshes, restore the rest arm position
2062 for i, arm in enumerate(bpy.data.armatures):
2063 arm.pose_position = ob_arms_orig_rest[i]
2065 if ob_arms_orig_rest:
2066 for ob_base in bpy.data.objects:
2067 if ob_base.type == 'ARMATURE':
2068 ob_base.update_tag()
2069 # This causes the makeDisplayList command to effect the mesh
2070 scene.frame_set(scene.frame_current)
2072 del tmp_ob_type, context_objects
2074 # now we have collected all armatures, add bones
2075 for i, ob in enumerate(ob_arms):
2077 ob_arms[i] = my_arm = my_object_generic(ob)
2079 my_arm.fbxBones = []
2080 my_arm.blenData = ob.data
2081 if ob.animation_data:
2082 my_arm.blenAction = ob.animation_data.action
2083 else:
2084 my_arm.blenAction = None
2085 my_arm.blenActionList = []
2087 # fbxName, blenderObject, my_bones, blenderActions
2088 #ob_arms[i] = fbxArmObName, ob, arm_my_bones, (ob.action, [])
2090 if use_armature_deform_only:
2091 # tag non deforming bones that have no deforming children
2092 deform_map = dict.fromkeys(my_arm.blenData.bones, False)
2093 for bone in my_arm.blenData.bones:
2094 if bone.use_deform:
2095 deform_map[bone] = True
2096 # tag all parents, even ones that are not deform since their child _is_
2097 for parent in bone.parent_recursive:
2098 deform_map[parent] = True
2100 for bone in my_arm.blenData.bones:
2102 if use_armature_deform_only:
2103 # if this bone doesnt deform, and none of its children deform, skip it!
2104 if not deform_map[bone]:
2105 continue
2107 my_bone = my_bone_class(bone, my_arm)
2108 my_arm.fbxBones.append(my_bone)
2109 ob_bones.append(my_bone)
2111 if use_armature_deform_only:
2112 del deform_map
2114 # add the meshes to the bones and replace the meshes armature with own armature class
2115 #for obname, ob, mtx, me, mats, arm, armname in ob_meshes:
2116 for my_mesh in ob_meshes:
2117 # Replace
2118 # ...this could be sped up with dictionary mapping but its unlikely for
2119 # it ever to be a bottleneck - (would need 100+ meshes using armatures)
2120 if my_mesh.fbxArm:
2121 for my_arm in ob_arms:
2122 if my_arm.blenObject == my_mesh.fbxArm:
2123 my_mesh.fbxArm = my_arm
2124 break
2126 for my_bone in ob_bones:
2128 # The mesh uses this bones armature!
2129 if my_bone.fbxArm == my_mesh.fbxArm:
2130 if my_bone.blenBone.use_deform:
2131 my_bone.blenMeshes[my_mesh.fbxName] = me
2133 # parent bone: replace bone names with our class instances
2134 # my_mesh.fbxBoneParent is None or a blender bone name initialy, replacing if the names match.
2135 if my_mesh.fbxBoneParent == my_bone.blenName:
2136 my_mesh.fbxBoneParent = my_bone
2138 bone_deformer_count = 0 # count how many bones deform a mesh
2139 my_bone_blenParent = None
2140 for my_bone in ob_bones:
2141 my_bone_blenParent = my_bone.blenBone.parent
2142 if my_bone_blenParent:
2143 for my_bone_parent in ob_bones:
2144 # Note 2.45rc2 you can compare bones normally
2145 if my_bone_blenParent.name == my_bone_parent.blenName and my_bone.fbxArm == my_bone_parent.fbxArm:
2146 my_bone.parent = my_bone_parent
2147 break
2149 # Not used at the moment
2150 # my_bone.calcRestMatrixLocal()
2151 bone_deformer_count += len(my_bone.blenMeshes)
2153 del my_bone_blenParent
2155 # Build blenObject -> fbxObject mapping
2156 # this is needed for groups as well as fbxParenting
2157 bpy.data.objects.tag(False)
2159 # using a list of object names for tagging (Arystan)
2161 tmp_obmapping = {}
2162 for ob_generic in ob_all_typegroups:
2163 for ob_base in ob_generic:
2164 ob_base.blenObject.tag = True
2165 tmp_obmapping[ob_base.blenObject] = ob_base
2167 # Build Groups from objects we export
2168 for blenGroup in bpy.data.groups:
2169 fbxGroupName = None
2170 for ob in blenGroup.objects:
2171 if ob.tag:
2172 if fbxGroupName is None:
2173 fbxGroupName = sane_groupname(blenGroup)
2174 groups.append((fbxGroupName, blenGroup))
2176 tmp_obmapping[ob].fbxGroupNames.append(fbxGroupName) # also adds to the objects fbxGroupNames
2178 groups.sort() # not really needed
2180 # Assign parents using this mapping
2181 for ob_generic in ob_all_typegroups:
2182 for my_ob in ob_generic:
2183 parent = my_ob.blenObject.parent
2184 if parent and parent.tag: # does it exist and is it in the mapping
2185 my_ob.fbxParent = tmp_obmapping[parent]
2187 del tmp_obmapping
2188 # Finished finding groups we use
2190 # == WRITE OBJECTS TO THE FILE ==
2191 # == From now on we are building the FBX file from the information collected above (JCB)
2193 materials = [(sane_matname(mat_tex_pair), mat_tex_pair) for mat_tex_pair in materials.keys()]
2194 textures = [(sane_texname(tex), tex) for tex in textures.keys() if tex]
2195 materials.sort(key=lambda m: m[0]) # sort by name
2196 textures.sort(key=lambda m: m[0])
2198 camera_count = 8 if 'CAMERA' in object_types else 0
2200 # sanity checks
2201 try:
2202 assert(not (ob_meshes and ('MESH' not in object_types)))
2203 assert(not (materials and ('MESH' not in object_types)))
2204 assert(not (textures and ('MESH' not in object_types)))
2205 assert(not (textures and ('MESH' not in object_types)))
2207 assert(not (ob_lights and ('LAMP' not in object_types)))
2209 assert(not (ob_cameras and ('CAMERA' not in object_types)))
2210 except AssertionError:
2211 import traceback
2212 traceback.print_exc()
2214 fw('''
2216 ; Object definitions
2217 ;------------------------------------------------------------------
2219 Definitions: {
2220 Version: 100
2221 Count: %i''' % (
2222 1 + camera_count +
2223 len(ob_meshes) +
2224 len(ob_lights) +
2225 len(ob_cameras) +
2226 len(ob_arms) +
2227 len(ob_null) +
2228 len(ob_bones) +
2229 bone_deformer_count +
2230 len(materials) +
2231 (len(textures) * 2))) # add 1 for global settings
2233 del bone_deformer_count
2235 fw('''
2236 ObjectType: "Model" {
2237 Count: %i
2238 }''' % (
2239 camera_count +
2240 len(ob_meshes) +
2241 len(ob_lights) +
2242 len(ob_cameras) +
2243 len(ob_arms) +
2244 len(ob_null) +
2245 len(ob_bones)))
2247 fw('''
2248 ObjectType: "Geometry" {
2249 Count: %i
2250 }''' % len(ob_meshes))
2252 if materials:
2253 fw('''
2254 ObjectType: "Material" {
2255 Count: %i
2256 }''' % len(materials))
2258 if textures:
2259 fw('''
2260 ObjectType: "Texture" {
2261 Count: %i
2262 }''' % len(textures)) # add 1 for an empty tex
2263 fw('''
2264 ObjectType: "Video" {
2265 Count: %i
2266 }''' % len(textures)) # add 1 for an empty tex
2268 tmp = 0
2269 # Add deformer nodes
2270 for my_mesh in ob_meshes:
2271 if my_mesh.fbxArm:
2272 tmp += 1
2274 # Add subdeformers
2275 for my_bone in ob_bones:
2276 tmp += len(my_bone.blenMeshes)
2278 if tmp:
2279 fw('''
2280 ObjectType: "Deformer" {
2281 Count: %i
2282 }''' % tmp)
2283 del tmp
2285 # Bind pose is essential for XNA if the 'MESH' is included,
2286 # but could be removed now?
2287 fw('''
2288 ObjectType: "Pose" {
2289 Count: 1
2290 }''')
2292 if groups:
2293 fw('''
2294 ObjectType: "GroupSelection" {
2295 Count: %i
2296 }''' % len(groups))
2298 fw('''
2299 ObjectType: "GlobalSettings" {
2300 Count: 1
2302 }''')
2304 fw('''
2306 ; Object properties
2307 ;------------------------------------------------------------------
2309 Objects: {''')
2311 if 'CAMERA' in object_types:
2312 # To comply with other FBX FILES
2313 write_camera_switch()
2315 for my_null in ob_null:
2316 write_null(my_null)
2318 # XNA requires the armature to be a Limb (JCB)
2319 # Note, 2.58 and previous wrote these as normal empties and it worked mostly (except for XNA)
2320 for my_arm in ob_arms:
2321 write_null(my_arm, fbxType="Limb", fbxTypeFlags="Skeleton")
2323 for my_cam in ob_cameras:
2324 write_camera(my_cam)
2326 for my_light in ob_lights:
2327 write_light(my_light)
2329 for my_mesh in ob_meshes:
2330 write_mesh(my_mesh)
2332 #for bonename, bone, obname, me, armob in ob_bones:
2333 for my_bone in ob_bones:
2334 write_bone(my_bone)
2336 if 'CAMERA' in object_types:
2337 write_camera_default()
2339 for matname, (mat, tex) in materials:
2340 write_material(matname, mat) # We only need to have a material per image pair, but no need to write any image info into the material (dumb fbx standard)
2342 # each texture uses a video, odd
2343 for texname, tex in textures:
2344 write_video(texname, tex)
2345 i = 0
2346 for texname, tex in textures:
2347 write_texture(texname, tex, i)
2348 i += 1
2350 for groupname, group in groups:
2351 write_group(groupname)
2353 # NOTE - c4d and motionbuilder dont need normalized weights, but deep-exploration 5 does and (max?) do.
2355 # Write armature modifiers
2356 # TODO - add another MODEL? - because of this skin definition.
2357 for my_mesh in ob_meshes:
2358 if my_mesh.fbxArm:
2359 write_deformer_skin(my_mesh.fbxName)
2361 # Get normalized weights for temorary use
2362 if my_mesh.fbxBoneParent:
2363 weights = None
2364 else:
2365 weights = meshNormalizedWeights(my_mesh.blenObject, my_mesh.blenData)
2367 #for bonename, bone, obname, bone_mesh, armob in ob_bones:
2368 for my_bone in ob_bones:
2369 if me in iter(my_bone.blenMeshes.values()):
2370 write_sub_deformer_skin(my_mesh, my_bone, weights)
2372 # Write pose is really weird, only needed when an armature and mesh are used together
2373 # each by themselves do not need pose data. For now only pose meshes and bones
2375 # Bind pose is essential for XNA if the 'MESH' is included (JCB)
2376 fw('''
2377 Pose: "Pose::BIND_POSES", "BindPose" {
2378 Type: "BindPose"
2379 Version: 100
2380 Properties60: {
2382 NbPoseNodes: ''')
2383 fw(str(len(pose_items)))
2385 for fbxName, matrix in pose_items:
2386 fw('\n\t\tPoseNode: {')
2387 fw('\n\t\t\tNode: "Model::%s"' % fbxName)
2388 fw('\n\t\t\tMatrix: %s' % mat4x4str(matrix if matrix else Matrix()))
2389 fw('\n\t\t}')
2391 fw('\n\t}')
2393 # Finish Writing Objects
2394 # Write global settings
2395 fw('''
2396 GlobalSettings: {
2397 Version: 1000
2398 Properties60: {
2399 Property: "UpAxis", "int", "",1
2400 Property: "UpAxisSign", "int", "",1
2401 Property: "FrontAxis", "int", "",2
2402 Property: "FrontAxisSign", "int", "",1
2403 Property: "CoordAxis", "int", "",0
2404 Property: "CoordAxisSign", "int", "",1
2405 Property: "UnitScaleFactor", "double", "",1
2408 ''')
2409 fw('}')
2411 fw('''
2413 ; Object relations
2414 ;------------------------------------------------------------------
2416 Relations: {''')
2418 # Nulls are likely to cause problems for XNA
2420 for my_null in ob_null:
2421 fw('\n\tModel: "Model::%s", "Null" {\n\t}' % my_null.fbxName)
2423 # Armature must be a Limb for XNA
2424 # Note, 2.58 and previous wrote these as normal empties and it worked mostly (except for XNA)
2425 for my_arm in ob_arms:
2426 fw('\n\tModel: "Model::%s", "Limb" {\n\t}' % my_arm.fbxName)
2428 for my_mesh in ob_meshes:
2429 fw('\n\tModel: "Model::%s", "Mesh" {\n\t}' % my_mesh.fbxName)
2431 # TODO - limbs can have the same name for multiple armatures, should prefix.
2432 #for bonename, bone, obname, me, armob in ob_bones:
2433 for my_bone in ob_bones:
2434 fw('\n\tModel: "Model::%s", "Limb" {\n\t}' % my_bone.fbxName)
2436 for my_cam in ob_cameras:
2437 fw('\n\tModel: "Model::%s", "Camera" {\n\t}' % my_cam.fbxName)
2439 for my_light in ob_lights:
2440 fw('\n\tModel: "Model::%s", "Light" {\n\t}' % my_light.fbxName)
2442 fw('''
2443 Model: "Model::Producer Perspective", "Camera" {
2445 Model: "Model::Producer Top", "Camera" {
2447 Model: "Model::Producer Bottom", "Camera" {
2449 Model: "Model::Producer Front", "Camera" {
2451 Model: "Model::Producer Back", "Camera" {
2453 Model: "Model::Producer Right", "Camera" {
2455 Model: "Model::Producer Left", "Camera" {
2457 Model: "Model::Camera Switcher", "CameraSwitcher" {
2458 }''')
2460 for matname, (mat, tex) in materials:
2461 fw('\n\tMaterial: "Material::%s", "" {\n\t}' % matname)
2463 if textures:
2464 for texname, tex in textures:
2465 fw('\n\tTexture: "Texture::%s", "TextureVideoClip" {\n\t}' % texname)
2466 for texname, tex in textures:
2467 fw('\n\tVideo: "Video::%s", "Clip" {\n\t}' % texname)
2469 # deformers - modifiers
2470 for my_mesh in ob_meshes:
2471 if my_mesh.fbxArm:
2472 fw('\n\tDeformer: "Deformer::Skin %s", "Skin" {\n\t}' % my_mesh.fbxName)
2474 #for bonename, bone, obname, me, armob in ob_bones:
2475 for my_bone in ob_bones:
2476 for fbxMeshObName in my_bone.blenMeshes: # .keys() - fbxMeshObName
2477 # is this bone effecting a mesh?
2478 fw('\n\tDeformer: "SubDeformer::Cluster %s %s", "Cluster" {\n\t}' % (fbxMeshObName, my_bone.fbxName))
2480 # This should be at the end
2481 # fw('\n\tPose: "Pose::BIND_POSES", "BindPose" {\n\t}')
2483 for groupname, group in groups:
2484 fw('\n\tGroupSelection: "GroupSelection::%s", "Default" {\n\t}' % groupname)
2486 fw('\n}')
2487 fw('''
2489 ; Object connections
2490 ;------------------------------------------------------------------
2492 Connections: {''')
2494 # NOTE - The FBX SDK does not care about the order but some importers DO!
2495 # for instance, defining the material->mesh connection
2496 # before the mesh->parent crashes cinema4d
2498 for ob_generic in ob_all_typegroups: # all blender 'Object's we support
2499 for my_ob in ob_generic:
2500 # for deformed meshes, don't have any parents or they can get twice transformed.
2501 if my_ob.fbxParent and (not my_ob.fbxArm):
2502 fw('\n\tConnect: "OO", "Model::%s", "Model::%s"' % (my_ob.fbxName, my_ob.fbxParent.fbxName))
2503 else:
2504 fw('\n\tConnect: "OO", "Model::%s", "Model::Scene"' % my_ob.fbxName)
2506 if materials:
2507 for my_mesh in ob_meshes:
2508 # Connect all materials to all objects, not good form but ok for now.
2509 for mat, tex in my_mesh.blenMaterials:
2510 mat_name = mat.name if mat else None
2511 tex_name = tex.name if tex else None
2513 fw('\n\tConnect: "OO", "Material::%s", "Model::%s"' % (sane_name_mapping_mat[mat_name, tex_name], my_mesh.fbxName))
2515 if textures:
2516 for my_mesh in ob_meshes:
2517 if my_mesh.blenTextures:
2518 # fw('\n\tConnect: "OO", "Texture::_empty_", "Model::%s"' % my_mesh.fbxName)
2519 for tex in my_mesh.blenTextures:
2520 if tex:
2521 fw('\n\tConnect: "OO", "Texture::%s", "Model::%s"' % (sane_name_mapping_tex[tex.name], my_mesh.fbxName))
2523 for texname, tex in textures:
2524 fw('\n\tConnect: "OO", "Video::%s", "Texture::%s"' % (texname, texname))
2526 if 'MESH' in object_types:
2527 for my_mesh in ob_meshes:
2528 if my_mesh.fbxArm:
2529 fw('\n\tConnect: "OO", "Deformer::Skin %s", "Model::%s"' % (my_mesh.fbxName, my_mesh.fbxName))
2531 for my_bone in ob_bones:
2532 for fbxMeshObName in my_bone.blenMeshes: # .keys()
2533 fw('\n\tConnect: "OO", "SubDeformer::Cluster %s %s", "Deformer::Skin %s"' % (fbxMeshObName, my_bone.fbxName, fbxMeshObName))
2535 # limbs -> deformers
2536 for my_bone in ob_bones:
2537 for fbxMeshObName in my_bone.blenMeshes: # .keys()
2538 fw('\n\tConnect: "OO", "Model::%s", "SubDeformer::Cluster %s %s"' % (my_bone.fbxName, fbxMeshObName, my_bone.fbxName))
2540 #for bonename, bone, obname, me, armob in ob_bones:
2541 for my_bone in ob_bones:
2542 # Always parent to armature now
2543 if my_bone.parent:
2544 fw('\n\tConnect: "OO", "Model::%s", "Model::%s"' % (my_bone.fbxName, my_bone.parent.fbxName))
2545 else:
2546 # the armature object is written as an empty and all root level bones connect to it
2547 fw('\n\tConnect: "OO", "Model::%s", "Model::%s"' % (my_bone.fbxName, my_bone.fbxArm.fbxName))
2549 # groups
2550 if groups:
2551 for ob_generic in ob_all_typegroups:
2552 for ob_base in ob_generic:
2553 for fbxGroupName in ob_base.fbxGroupNames:
2554 fw('\n\tConnect: "OO", "Model::%s", "GroupSelection::%s"' % (ob_base.fbxName, fbxGroupName))
2556 # I think the following always duplicates the armature connection because it is also in ob_all_typegroups above! (JCB)
2557 # for my_arm in ob_arms:
2558 # fw('\n\tConnect: "OO", "Model::%s", "Model::Scene"' % my_arm.fbxName)
2560 fw('\n}')
2562 # Needed for scene footer as well as animation
2563 render = scene.render
2565 # from the FBX sdk
2566 #define KTIME_ONE_SECOND KTime (K_LONGLONG(46186158000))
2567 def fbx_time(t):
2568 # 0.5 + val is the same as rounding.
2569 return int(0.5 + ((t / fps) * 46186158000))
2571 fps = float(render.fps)
2572 start = scene.frame_start
2573 end = scene.frame_end
2574 if end < start:
2575 start, end = end, start
2577 # comment the following line, otherwise we dont get the pose
2578 # if start==end: use_anim = False
2580 # animations for these object types
2581 ob_anim_lists = ob_bones, ob_meshes, ob_null, ob_cameras, ob_lights, ob_arms
2583 if use_anim and [tmp for tmp in ob_anim_lists if tmp]:
2585 frame_orig = scene.frame_current
2587 if use_anim_optimize:
2588 ANIM_OPTIMIZE_PRECISSION_FLOAT = 0.1 ** anim_optimize_precision
2590 # default action, when no actions are avaioable
2591 tmp_actions = []
2592 blenActionDefault = None
2593 action_lastcompat = None
2595 # instead of tagging
2596 tagged_actions = []
2598 # get the current action first so we can use it if we only export one action (JCB)
2599 for my_arm in ob_arms:
2600 blenActionDefault = my_arm.blenAction
2601 if blenActionDefault:
2602 break
2604 if use_anim_action_all:
2605 tmp_actions = bpy.data.actions[:]
2606 elif not use_default_take:
2607 if blenActionDefault:
2608 # Export the current action (JCB)
2609 tmp_actions.append(blenActionDefault)
2611 if tmp_actions:
2612 # find which actions are compatible with the armatures
2613 tmp_act_count = 0
2614 for my_arm in ob_arms:
2616 arm_bone_names = set([my_bone.blenName for my_bone in my_arm.fbxBones])
2618 for action in tmp_actions:
2620 if arm_bone_names.intersection(action_bone_names(my_arm.blenObject, action)): # at least one channel matches.
2621 my_arm.blenActionList.append(action)
2622 tagged_actions.append(action.name)
2623 tmp_act_count += 1
2625 # in case there are no actions applied to armatures
2626 # for example, when a user deletes the current action.
2627 action_lastcompat = action
2629 if tmp_act_count:
2630 # unlikely to ever happen but if no actions applied to armatures, just use the last compatible armature.
2631 if not blenActionDefault:
2632 blenActionDefault = action_lastcompat
2634 del action_lastcompat
2636 if use_default_take:
2637 tmp_actions.insert(0, None) # None is the default action
2639 fw('''
2640 ;Takes and animation section
2641 ;----------------------------------------------------
2643 Takes: {''')
2645 if blenActionDefault and not use_default_take:
2646 fw('\n\tCurrent: "%s"' % sane_takename(blenActionDefault))
2647 else:
2648 fw('\n\tCurrent: "Default Take"')
2650 for blenAction in tmp_actions:
2651 # we have tagged all actious that are used be selected armatures
2652 if blenAction:
2653 if blenAction.name in tagged_actions:
2654 print('\taction: "%s" exporting...' % blenAction.name)
2655 else:
2656 print('\taction: "%s" has no armature using it, skipping' % blenAction.name)
2657 continue
2659 if blenAction is None:
2660 # Warning, this only accounts for tmp_actions being [None]
2661 take_name = "Default Take"
2662 act_start = start
2663 act_end = end
2664 else:
2665 # use existing name
2666 take_name = sane_name_mapping_take.get(blenAction.name)
2667 if take_name is None:
2668 take_name = sane_takename(blenAction)
2670 act_start, act_end = blenAction.frame_range
2671 act_start = int(act_start)
2672 act_end = int(act_end)
2674 # Set the action active
2675 for my_arm in ob_arms:
2676 if my_arm.blenObject.animation_data and blenAction in my_arm.blenActionList:
2677 my_arm.blenObject.animation_data.action = blenAction
2679 # Use the action name as the take name and the take filename (JCB)
2680 fw('\n\tTake: "%s" {' % take_name)
2681 fw('\n\t\tFileName: "%s.tak"' % take_name.replace(" ", "_"))
2682 fw('\n\t\tLocalTime: %i,%i' % (fbx_time(act_start - 1), fbx_time(act_end - 1))) # ??? - not sure why this is needed
2683 fw('\n\t\tReferenceTime: %i,%i' % (fbx_time(act_start - 1), fbx_time(act_end - 1))) # ??? - not sure why this is needed
2685 fw('''
2687 ;Models animation
2688 ;----------------------------------------------------''')
2690 # set pose data for all bones
2691 # do this here in case the action changes
2693 for my_bone in ob_bones:
2694 my_bone.flushAnimData()
2696 i = act_start
2697 while i <= act_end:
2698 scene.frame_set(i)
2699 for ob_generic in ob_anim_lists:
2700 for my_ob in ob_generic:
2701 #Blender.Window.RedrawAll()
2702 if ob_generic == ob_meshes and my_ob.fbxArm:
2703 # We cant animate armature meshes!
2704 my_ob.setPoseFrame(i, fake=True)
2705 else:
2706 my_ob.setPoseFrame(i)
2708 i += 1
2710 #for bonename, bone, obname, me, armob in ob_bones:
2711 for ob_generic in (ob_bones, ob_meshes, ob_null, ob_cameras, ob_lights, ob_arms):
2713 for my_ob in ob_generic:
2715 if ob_generic == ob_meshes and my_ob.fbxArm:
2716 # do nothing,
2717 pass
2718 else:
2720 fw('\n\t\tModel: "Model::%s" {' % my_ob.fbxName) # ??? - not sure why this is needed
2721 fw('\n\t\t\tVersion: 1.1')
2722 fw('\n\t\t\tChannel: "Transform" {')
2724 context_bone_anim_mats = [(my_ob.getAnimParRelMatrix(frame), my_ob.getAnimParRelMatrixRot(frame)) for frame in range(act_start, act_end + 1)]
2726 # ----------------
2727 # ----------------
2728 for TX_LAYER, TX_CHAN in enumerate('TRS'): # transform, rotate, scale
2730 if TX_CHAN == 'T':
2731 context_bone_anim_vecs = [mtx[0].to_translation() for mtx in context_bone_anim_mats]
2732 elif TX_CHAN == 'S':
2733 context_bone_anim_vecs = [mtx[0].to_scale() for mtx in context_bone_anim_mats]
2734 elif TX_CHAN == 'R':
2735 # Was....
2736 # elif TX_CHAN=='R': context_bone_anim_vecs = [mtx[1].to_euler() for mtx in context_bone_anim_mats]
2738 # ...but we need to use the previous euler for compatible conversion.
2739 context_bone_anim_vecs = []
2740 prev_eul = None
2741 for mtx in context_bone_anim_mats:
2742 if prev_eul:
2743 prev_eul = mtx[1].to_euler('XYZ', prev_eul)
2744 else:
2745 prev_eul = mtx[1].to_euler()
2746 context_bone_anim_vecs.append(tuple_rad_to_deg(prev_eul))
2748 fw('\n\t\t\t\tChannel: "%s" {' % TX_CHAN) # translation
2750 for i in range(3):
2751 # Loop on each axis of the bone
2752 fw('\n\t\t\t\t\tChannel: "%s" {' % ('XYZ'[i])) # translation
2753 fw('\n\t\t\t\t\t\tDefault: %.15f' % context_bone_anim_vecs[0][i])
2754 fw('\n\t\t\t\t\t\tKeyVer: 4005')
2756 if not use_anim_optimize:
2757 # Just write all frames, simple but in-eficient
2758 fw('\n\t\t\t\t\t\tKeyCount: %i' % (1 + act_end - act_start))
2759 fw('\n\t\t\t\t\t\tKey: ')
2760 frame = act_start
2761 while frame <= act_end:
2762 if frame != act_start:
2763 fw(',')
2765 # Curve types are 'C,n' for constant, 'L' for linear
2766 # C,n is for bezier? - linear is best for now so we can do simple keyframe removal
2767 fw('\n\t\t\t\t\t\t\t%i,%.15f,L' % (fbx_time(frame - 1), context_bone_anim_vecs[frame - act_start][i]))
2768 frame += 1
2769 else:
2770 # remove unneeded keys, j is the frame, needed when some frames are removed.
2771 context_bone_anim_keys = [(vec[i], j) for j, vec in enumerate(context_bone_anim_vecs)]
2773 # last frame to fisrt frame, missing 1 frame on either side.
2774 # removeing in a backwards loop is faster
2775 #for j in xrange( (act_end-act_start)-1, 0, -1 ):
2776 # j = (act_end-act_start)-1
2777 j = len(context_bone_anim_keys) - 2
2778 while j > 0 and len(context_bone_anim_keys) > 2:
2779 # print j, len(context_bone_anim_keys)
2780 # Is this key the same as the ones next to it?
2782 # co-linear horizontal...
2783 if abs(context_bone_anim_keys[j][0] - context_bone_anim_keys[j - 1][0]) < ANIM_OPTIMIZE_PRECISSION_FLOAT and \
2784 abs(context_bone_anim_keys[j][0] - context_bone_anim_keys[j + 1][0]) < ANIM_OPTIMIZE_PRECISSION_FLOAT:
2786 del context_bone_anim_keys[j]
2788 else:
2789 frame_range = float(context_bone_anim_keys[j + 1][1] - context_bone_anim_keys[j - 1][1])
2790 frame_range_fac1 = (context_bone_anim_keys[j + 1][1] - context_bone_anim_keys[j][1]) / frame_range
2791 frame_range_fac2 = 1.0 - frame_range_fac1
2793 if abs(((context_bone_anim_keys[j - 1][0] * frame_range_fac1 + context_bone_anim_keys[j + 1][0] * frame_range_fac2)) - context_bone_anim_keys[j][0]) < ANIM_OPTIMIZE_PRECISSION_FLOAT:
2794 del context_bone_anim_keys[j]
2795 else:
2796 j -= 1
2798 # keep the index below the list length
2799 if j > len(context_bone_anim_keys) - 2:
2800 j = len(context_bone_anim_keys) - 2
2802 if len(context_bone_anim_keys) == 2 and context_bone_anim_keys[0][0] == context_bone_anim_keys[1][0]:
2804 # This axis has no moton, its okay to skip KeyCount and Keys in this case
2805 # pass
2807 # better write one, otherwise we loose poses with no animation
2808 fw('\n\t\t\t\t\t\tKeyCount: 1')
2809 fw('\n\t\t\t\t\t\tKey: ')
2810 fw('\n\t\t\t\t\t\t\t%i,%.15f,L' % (fbx_time(start), context_bone_anim_keys[0][0]))
2811 else:
2812 # We only need to write these if there is at least one
2813 fw('\n\t\t\t\t\t\tKeyCount: %i' % len(context_bone_anim_keys))
2814 fw('\n\t\t\t\t\t\tKey: ')
2815 for val, frame in context_bone_anim_keys:
2816 if frame != context_bone_anim_keys[0][1]: # not the first
2817 fw(',')
2818 # frame is already one less then blenders frame
2819 fw('\n\t\t\t\t\t\t\t%i,%.15f,L' % (fbx_time(frame), val))
2821 if i == 0:
2822 fw('\n\t\t\t\t\t\tColor: 1,0,0')
2823 elif i == 1:
2824 fw('\n\t\t\t\t\t\tColor: 0,1,0')
2825 elif i == 2:
2826 fw('\n\t\t\t\t\t\tColor: 0,0,1')
2828 fw('\n\t\t\t\t\t}')
2829 fw('\n\t\t\t\t\tLayerType: %i' % (TX_LAYER + 1))
2830 fw('\n\t\t\t\t}')
2832 # ---------------
2834 fw('\n\t\t\t}')
2835 fw('\n\t\t}')
2837 # end the take
2838 fw('\n\t}')
2840 # end action loop. set original actions
2841 # do this after every loop in case actions effect eachother.
2842 for my_arm in ob_arms:
2843 if my_arm.blenObject.animation_data:
2844 my_arm.blenObject.animation_data.action = my_arm.blenAction
2846 fw('\n}')
2848 scene.frame_set(frame_orig)
2850 else:
2851 # no animation
2852 fw('\n;Takes and animation section')
2853 fw('\n;----------------------------------------------------')
2854 fw('\n')
2855 fw('\nTakes: {')
2856 fw('\n\tCurrent: ""')
2857 fw('\n}')
2859 # write meshes animation
2860 #for obname, ob, mtx, me, mats, arm, armname in ob_meshes:
2862 # Clear mesh data Only when writing with modifiers applied
2863 for me in meshes_to_clear:
2864 bpy.data.meshes.remove(me)
2866 # --------------------------- Footer
2867 if world:
2868 m = world.mist_settings
2869 has_mist = m.use_mist
2870 mist_intense = m.intensity
2871 mist_start = m.start
2872 mist_end = m.depth
2873 # mist_height = m.height # UNUSED
2874 world_hor = world.horizon_color
2875 else:
2876 has_mist = mist_intense = mist_start = mist_end = 0
2877 world_hor = 0, 0, 0
2879 fw('\n;Version 5 settings')
2880 fw('\n;------------------------------------------------------------------')
2881 fw('\n')
2882 fw('\nVersion5: {')
2883 fw('\n\tAmbientRenderSettings: {')
2884 fw('\n\t\tVersion: 101')
2885 fw('\n\t\tAmbientLightColor: %.1f,%.1f,%.1f,0' % tuple(world_amb))
2886 fw('\n\t}')
2887 fw('\n\tFogOptions: {')
2888 fw('\n\t\tFogEnable: %i' % has_mist)
2889 fw('\n\t\tFogMode: 0')
2890 fw('\n\t\tFogDensity: %.3f' % mist_intense)
2891 fw('\n\t\tFogStart: %.3f' % mist_start)
2892 fw('\n\t\tFogEnd: %.3f' % mist_end)
2893 fw('\n\t\tFogColor: %.1f,%.1f,%.1f,1' % tuple(world_hor))
2894 fw('\n\t}')
2895 fw('\n\tSettings: {')
2896 fw('\n\t\tFrameRate: "%i"' % int(fps))
2897 fw('\n\t\tTimeFormat: 1')
2898 fw('\n\t\tSnapOnFrames: 0')
2899 fw('\n\t\tReferenceTimeIndex: -1')
2900 fw('\n\t\tTimeLineStartTime: %i' % fbx_time(start - 1))
2901 fw('\n\t\tTimeLineStopTime: %i' % fbx_time(end - 1))
2902 fw('\n\t}')
2903 fw('\n\tRendererSetting: {')
2904 fw('\n\t\tDefaultCamera: "Producer Perspective"')
2905 fw('\n\t\tDefaultViewingMode: 0')
2906 fw('\n\t}')
2907 fw('\n}')
2908 fw('\n')
2910 # XXX, shouldnt be global!
2911 for mapping in (sane_name_mapping_ob,
2912 sane_name_mapping_ob_unique,
2913 sane_name_mapping_mat,
2914 sane_name_mapping_tex,
2915 sane_name_mapping_take,
2916 sane_name_mapping_group,
2918 mapping.clear()
2919 del mapping
2921 del ob_arms[:]
2922 del ob_bones[:]
2923 del ob_cameras[:]
2924 del ob_lights[:]
2925 del ob_meshes[:]
2926 del ob_null[:]
2928 file.close()
2930 # copy all collected files.
2931 bpy_extras.io_utils.path_reference_copy(copy_set)
2933 print('export finished in %.4f sec.' % (time.clock() - start_time))
2934 return {'FINISHED'}
2937 # defaults for applications, currently only unity but could add others.
2938 def defaults_unity3d():
2939 return dict(global_matrix=Matrix.Rotation(-math.pi / 2.0, 4, 'X'),
2940 use_selection=False,
2941 object_types={'ARMATURE', 'EMPTY', 'MESH'},
2942 use_mesh_modifiers=True,
2943 use_armature_deform_only=True,
2944 use_anim=True,
2945 use_anim_optimize=False,
2946 use_anim_action_all=True,
2947 batch_mode='OFF',
2948 use_default_take=True,
2952 def save(operator, context,
2953 filepath="",
2954 use_selection=False,
2955 batch_mode='OFF',
2956 use_batch_own_dir=False,
2957 **kwargs
2960 if bpy.ops.object.mode_set.poll():
2961 bpy.ops.object.mode_set(mode='OBJECT')
2963 if batch_mode == 'OFF':
2964 kwargs_mod = kwargs.copy()
2965 if use_selection:
2966 kwargs_mod["context_objects"] = context.selected_objects
2967 else:
2968 kwargs_mod["context_objects"] = context.scene.objects
2970 return save_single(operator, context.scene, filepath, **kwargs_mod)
2971 else:
2972 fbxpath = filepath
2974 prefix = os.path.basename(fbxpath)
2975 if prefix:
2976 fbxpath = os.path.dirname(fbxpath)
2978 if not fbxpath.endswith(os.sep):
2979 fbxpath += os.sep
2981 if batch_mode == 'GROUP':
2982 data_seq = bpy.data.groups
2983 else:
2984 data_seq = bpy.data.scenes
2986 # call this function within a loop with BATCH_ENABLE == False
2987 # no scene switching done at the moment.
2988 # orig_sce = context.scene
2990 new_fbxpath = fbxpath # own dir option modifies, we need to keep an original
2991 for data in data_seq: # scene or group
2992 newname = prefix + bpy.path.clean_name(data.name)
2994 if use_batch_own_dir:
2995 new_fbxpath = fbxpath + newname + os.sep
2996 # path may already exist
2997 # TODO - might exist but be a file. unlikely but should probably account for it.
2999 if not os.path.exists(new_fbxpath):
3000 os.makedirs(new_fbxpath)
3002 filepath = new_fbxpath + newname + '.fbx'
3004 print('\nBatch exporting %s as...\n\t%r' % (data, filepath))
3006 # XXX don't know what to do with this, probably do the same? (Arystan)
3007 if batch_mode == 'GROUP': # group
3008 # group, so objects update properly, add a dummy scene.
3009 scene = bpy.data.scenes.new(name="FBX_Temp")
3010 scene.layers = [True] * 20
3011 # bpy.data.scenes.active = scene # XXX, cant switch
3012 for ob_base in data.objects:
3013 scene.objects.link(ob_base)
3015 scene.update()
3016 else:
3017 scene = data
3019 # TODO - BUMMER! Armatures not in the group wont animate the mesh
3021 # else: # scene
3022 # data_seq.active = data
3024 # Call self with modified args
3025 # Dont pass batch options since we already usedt them
3026 kwargs_batch = kwargs.copy()
3028 kwargs_batch["context_objects"] = data.objects
3030 save_single(operator, scene, filepath, **kwargs_batch)
3032 if batch_mode == 'GROUP':
3033 # remove temp group scene
3034 bpy.data.scenes.remove(scene)
3036 # no active scene changing!
3037 # bpy.data.scenes.active = orig_sce
3039 return {'FINISHED'} # so the script wont run after we have batch exported.
3041 # APPLICATION REQUIREMENTS
3042 # Please update the lists for UDK, Unity etc. on the following web page:
3043 # http://wiki.blender.org/index.php/Dev:2.5/Py/Scripts/Import-Export/UnifiedFBX
3045 # NOTE TO Campbell -
3046 # Can any or all of the following notes be removed because some have been here for a long time? (JCB 27 July 2011)
3047 # NOTES (all line numbers correspond to original export_fbx.py (under release/scripts)
3048 # - get rid of bpy.path.clean_name somehow
3049 # + get rid of BPyObject_getObjectArmature, move it in RNA?
3050 # - implement all BPyMesh_* used here with RNA
3051 # - getDerivedObjects is not fully replicated with .dupli* funcs
3052 # - don't know what those colbits are, do we need them? they're said to be deprecated in DNA_object_types.h: 1886-1893
3053 # - no hq normals: 1900-1901