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 #####
21 # Script copyright (C) Campbell Barton
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
):
53 while name
and name
[-1].isdigit():
57 return '%s%d' % (name
, int(num
) + 1)
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
73 name
= data
.name
if data
else None
77 orig_name_other
= other
.name
78 name
= '%s #%s' % (name
, orig_name_other
)
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]
88 name
= 'unnamed' # blank string, ASKING FOR TROUBLE!
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
101 dct
[orig_name
] = name
103 if unique_set
is not None:
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
)
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
142 path_resolve
= obj
.path_resolve
144 for fcu
in action
.fcurves
:
146 prop
= path_resolve(fcu
.data_path
, False)
152 if isinstance(data
, PoseBone
):
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
))], []
173 vWeightList
= [[0.0] * len_groupNames
for i
in range(len(me
.vertices
))]
175 for i
, v
in enumerate(me
.vertices
):
177 # possible weights are out of range
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
)
191 for i
, vWeights
in enumerate(vWeightList
):
197 for j
, w
in enumerate(vWeights
):
198 vWeights
[j
] = w
/ tot
200 return groupNames
, vWeightList
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
="",
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,
220 use_anim_optimize
=True,
221 anim_optimize_precision
=6,
222 use_anim_action_all
=False,
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()
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
249 # ----------------------------------------------
251 class my_bone_class(object):
252 __slots__
= ("blenName",
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
272 self
.restMatrix
= blenBone
.matrix_local
275 #~ self.restMatrixInv = self.restMatrix.inverted()
276 #~ self.restMatrixLocal = None # set later, need parent matrix
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):
291 self.restMatrixLocal = self.restMatrix * self.parent.restMatrix.inverted()
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()
324 def getAnimParRelMatrix(self
, frame
):
325 #arm_mat = self.fbxArm.matrixWorld
326 #arm_mat = self.fbxArm.parRelMatrix()
328 #return mtx4_z90 * (self.getPoseMatrix(frame) * arm_mat) # dont apply arm matrix anymore
329 return self
.getPoseMatrix(frame
) * mtx4_z90
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",
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
)
364 self
.fbxGroupNames
= []
365 self
.fbxParent
= None # set later on IF the parent is in the selection.
368 self
.matrixWorld
= global_matrix
* matrixWorld
370 self
.matrixWorld
= global_matrix
* ob
.matrix_world
372 self
.__anim
_poselist
= {} # we should only access this
374 def parRelMatrix(self
):
376 return self
.fbxParent
.matrixWorld
.inverted() * self
.matrixWorld
378 return self
.matrixWorld
380 def setPoseFrame(self
, f
, fake
=False):
382 self
.__anim
_poselist
[f
] = self
.matrixWorld
* global_matrix
.inverted()
384 self
.__anim
_poselist
[f
] = self
.blenObject
.matrix_world
.copy()
386 def getAnimParRelMatrix(self
, frame
):
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
])
391 return global_matrix
* self
.__anim
_poselist
[frame
]
393 def getAnimParRelMatrixRot(self
, frame
):
394 obj_type
= self
.blenObject
.type
396 matrix_rot
= ((global_matrix
* self
.fbxParent
.__anim
_poselist
[frame
]).inverted() * (global_matrix
* self
.__anim
_poselist
[frame
])).to_3x3()
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
409 # ----------------------------------------------
411 print('\nFBX export starting... %r' % filepath
)
412 start_time
= time
.clock()
414 file = open(filepath
, "w", encoding
="utf8", newline
="\n")
417 traceback
.print_exc()
418 operator
.report({'ERROR'}, "Couldn't open file %r" % filepath
)
424 # scene = context.scene # now passed as an arg instead of context
427 # ---------------------------- Write the header first
430 curtime
= time
.localtime()[0:6]
432 curtime
= (0, 0, 0, 0, 0, 0)
435 '''FBXHeaderExtension: {
436 FBXHeaderVersion: 1003
448 Creator: "FBX SDK/FBX Plugins build 20070228"
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
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()
480 rot
= tuple(rot
.to_euler()) # quat -> euler
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!")
491 # matrix = matrix_scale * 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
506 rot
= tuple(matrix_rot
.to_euler())
511 scale
= 1.0, 1.0, 1.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.
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.)
566 constraints
= get_constraints(pose_bone
)
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
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
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'
702 '\n\t\tMultiLayer: 0'
705 '\n\t\tCulling: "CullingOff"'
706 '\n\t\tTypeFlags: "Skeleton"'
710 def write_camera_switch():
712 Model: "Model::Camera Switcher", "CameraSwitcher" {
717 Property: "Color", "Color", "A",0.8,0.8,0.8
718 Property: "Camera Index", "Integer", "A+",100
724 Culling: "CullingOff"
726 Name: "Model::Camera Switcher"
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'
811 '\n\t\tMultiLayer: 0'
813 '\n\t\tHidden: "True"'
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'
826 '\n\t\tAudioColor: 0,1,0'
827 '\n\t\tCameraOrthoZoom: 1'
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
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'
957 fw('\n\t\tMultiLayer: 0'
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')
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
992 light_type_items
= {'POINT': 0, 'SUN': 1, 'SPOT': 2, 'HEMI': 3, 'AREA': 4}
993 light_type
= light_type_items
[light
.type]
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
)
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')
1044 fw('\n\t\tMultiLayer: 0'
1045 '\n\t\tMultiTake: 0'
1047 '\n\t\tCulling: "CullingOff"'
1048 '\n\t\tTypeFlags: "Light"'
1049 '\n\t\tGeometryVersion: 124'
1053 # matrixOnly is not used at the moment
1054 def write_null(my_null
=None, fbxName
=None, fbxType
="Null", fbxTypeFlags
="Null"):
1057 fbxName
= my_null
.fbxName
1059 fw('\n\tModel: "Model::%s", "%s" {' % (fbxName
, fbxType
))
1060 fw('\n\t\tVersion: 232')
1063 poseMatrix
= write_object_props(my_null
.blenObject
, None, my_null
.parRelMatrix())[3]
1065 poseMatrix
= write_object_props()[3]
1067 pose_items
.append((fbxName
, poseMatrix
))
1070 '\n\t\tMultiLayer: 0'
1071 '\n\t\tMultiTake: 1'
1073 '\n\t\tCulling: "CullingOff"'
1076 fw('\n\t\tTypeFlags: "%s"' % fbxTypeFlags
)
1081 world_amb
= world
.ambient_color
[:]
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.
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
1101 mat_shadeless
= mat
.use_shadeless
1103 mat_shader
= 'Lambert'
1105 if mat
.diffuse_shader
== 'LAMBERT':
1106 mat_shader
= 'Lambert'
1108 mat_shader
= 'Phong'
1110 mat_cols
= mat_cold
= 0.8, 0.8, 0.8
1111 mat_colamb
= 0.0, 0.0, 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')
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
)
1166 Property: "FrameRate", "double", "",0
1167 Property: "LastFrame", "int", "",0
1168 Property: "Width", "int", "",0
1169 Property: "Height", "int", "",0''')
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
)
1174 fname_strip
= fname_rel
= ""
1176 fw('\n\t\t\tProperty: "Path", "charptr", "", "%s"' % fname_strip
)
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
1190 fw('\n\t\tFilename: "%s"' % fname_strip
)
1191 fw('\n\t\tRelativeFilename: "%s"' % fname_rel
) # make relative
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
)
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
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
)
1222 Property: "TextureRotationPivot", "Vector3D", "",0,0,0
1223 Property: "TextureScalingPivot", "Vector3D", "",0,0,0
1224 Property: "VideoProperty", "object", ""
1227 fw('\n\t\tMedia: "Video::%s"' % texname
)
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
)
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
1239 ModelUVTranslation: 0,0
1241 Texture_Alpha_Source: "None"
1245 def write_deformer_skin(obname
):
1247 Each mesh has its own deformer
1249 fw('\n\tDeformer: "Deformer::Skin %s", "Skin" {' % obname
)
1256 Link_DeformAcuracy: 50
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
))
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
))]
1289 # This bone is not a parent of this mesh object, no weights
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
]]
1301 fw('\n\t\tIndexes: ')
1304 for vg
in vgroup_data
:
1315 fw('\n\t\tWeights: ')
1317 for vg
in vgroup_data
:
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
)
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
))
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
)
1376 fw('\n\t\tMultiLayer: 0'
1377 '\n\t\tMultiTake: 1'
1379 '\n\t\tCulling: "CullingOff"'
1382 # Write the Real Mesh data here
1383 fw('\n\t\tVertices: ')
1386 for v
in me_vertices
:
1388 fw('%.6f,%.6f,%.6f' % v
.co
[:])
1394 fw(',%.6f,%.6f,%.6f' % v
.co
[:])
1397 fw('\n\t\tPolygonVertexIndex: ')
1402 # last index XORd w. -1 indicates end of face
1405 fw('%i,%i,%i' % (fi
[0], fi
[1], fi
[2] ^
-1))
1407 fw('%i,%i,%i,%i' % (fi
[0], fi
[1], fi
[2], fi
[3] ^
-1))
1414 fw(',%i,%i,%i' % (fi
[0], fi
[1], fi
[2] ^
-1))
1416 fw(',%i,%i,%i,%i' % (fi
[0], fi
[1], fi
[2], fi
[3] ^
-1))
1419 # write loose edges as faces.
1422 ed_val
= ed
.vertices
[:]
1423 ed_val
= ed_val
[0], ed_val
[-1] ^
-1
1426 fw('%i,%i' % ed_val
)
1432 fw(',%i,%i' % ed_val
)
1439 fw('%i,%i' % (ed
.vertices
[0], ed
.vertices
[1]))
1445 fw(',%i,%i' % (ed
.vertices
[0], ed
.vertices
[1]))
1448 fw('\n\t\tGeometryVersion: 124')
1451 LayerElementNormal: 0 {
1454 MappingInformationType: "ByVertice"
1455 ReferenceInformationType: "Direct"
1459 for v
in me_vertices
:
1461 fw('%.15f,%.15f,%.15f' % v
.normal
[:])
1467 fw(',%.15f,%.15f,%.15f' % v
.normal
[:])
1471 # Write Face Smoothing
1472 if mesh_smooth_type
== 'FACE':
1474 LayerElementSmoothing: 0 {
1477 MappingInformationType: "ByPolygon"
1478 ReferenceInformationType: "Direct"
1484 fw('%i' % f
.use_smooth
)
1490 fw(',%i' % f
.use_smooth
)
1495 elif mesh_smooth_type
== 'EDGE':
1496 # Write Edge Smoothing
1498 LayerElementSmoothing: 0 {
1501 MappingInformationType: "ByEdge"
1502 ReferenceInformationType: "Direct"
1508 fw('%i' % (ed
.use_edge_sharp
))
1514 fw(',%i' % ed
.use_edge_sharp
)
1518 elif mesh_smooth_type
== 'OFF':
1521 raise Exception("invalid mesh_smooth_type: %r" % mesh_smooth_type
)
1523 # Write VertexColor Layers
1524 # note, no programs seem to use this info :/
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
)
1534 MappingInformationType: "ByPolygonVertex"
1535 ReferenceInformationType: "IndexToDirect"
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
[:]
1545 colors
= cf
.color1
[:], cf
.color2
[:], cf
.color3
[:]
1549 fw('%.4f,%.4f,%.4f,1' % col
)
1555 fw(',%.4f,%.4f,%.4f,1' % col
)
1557 ii
+= 1 # One more Color
1559 fw('\n\t\t\tColorIndex: ')
1574 # Write UV and texture layers.
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
)
1584 MappingInformationType: "ByPolygonVertex"
1585 ReferenceInformationType: "IndexToDirect"
1589 ii
= 0 # Count how many UVs we write
1591 for uf
in uvlayer
.data
:
1592 # workaround, since uf.uv iteration is wrong atm
1595 fw('%.6f,%.6f' % uv
[:])
1601 fw(',%.6f,%.6f' % uv
[:])
1603 ii
+= 1 # One more UV
1605 fw('\n\t\t\tUVIndex: ')
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"')
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:
1638 texture_mapping_local
= {None: -1}
1641 for tex
in my_mesh
.blenTextures
:
1642 if tex
: # None is set above
1643 texture_mapping_local
[tex
] = i
1647 for f
in uvlayer
.data
:
1652 fw('%s' % texture_mapping_local
[img_key
])
1658 fw(',%s' % texture_mapping_local
[img_key
])
1663 LayerElementTexture: 0 {
1666 MappingInformationType: "NoMappingInformation"
1667 ReferenceInformationType: "IndexToDirect"
1668 BlendMode: "Translucent"
1673 # Done with UV/textures.
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"')
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:
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
1701 uv_faces
= [None] * len(me_faces
)
1704 for f
, uf
in zip(me_faces
, uv_faces
):
1706 mat
= mats
[f
.material_index
]
1711 tex
= uf
.image
# WARNING - MULTI UV LAYER IMAGES NOT SUPPORTED :/
1717 fw('%s' % material_mapping_local
[mat
, tex
]) # None for mat or tex is ok
1723 fw(',%s' % material_mapping_local
[mat
, tex
])
1732 Type: "LayerElementNormal"
1739 Type: "LayerElementMaterial"
1744 if mesh_smooth_type
!= 'OFF':
1747 Type: "LayerElementSmoothing"
1755 Type: "LayerElementTexture"
1759 if me
.tessface_vertex_colors
:
1762 Type: "LayerElementColor"
1766 if do_uvs
: # same as me.faceUV
1769 Type: "LayerElementUV"
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')
1783 Type: "LayerElementUV"''')
1785 fw('\n\t\t\t\tTypedIndex: %i' % i
)
1792 Type: "LayerElementTexture"''')
1794 fw('\n\t\t\t\tTypedIndex: %i' % i
)
1799 if len(collayers
) > 1:
1800 # Take into account any UV layers
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')
1811 Type: "LayerElementColor"''')
1813 fw('\n\t\t\t\tTypedIndex: %i' % i
)
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
1828 for j
, kv
in enumerate(kb
.data
):
1829 delta
= kv
.co
- basis_verts
[j
].co
1830 if delta
.length
> 0.000001:
1838 delta_verts
.append(delta
[:])
1841 fw('\n\t\t\tVertices: ')
1843 for dv
in delta_verts
:
1845 fw("%.6f,%.6f,%.6f" % dv
)
1850 fw(",%.6f,%.6f,%.6f" % dv
)
1853 # all zero, why? - campbell
1854 fw('\n\t\t\tNormals: ')
1855 for j
in range(len(delta_verts
)):
1868 def write_group(name
):
1869 fw('\n\tGroupSelection: "GroupSelection::%s", "Default" {' % name
)
1873 Property: "MultiLayer", "bool", "",0
1874 Property: "Pickable", "bool", "",1
1875 Property: "Transformable", "bool", "",1
1876 Property: "Show", "bool", "",1
1881 # add meshes here to clear because they are not used anywhere.
1882 meshes_to_clear
= []
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
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
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'}:
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
]
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
:
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
:
1951 if tmp_ob_type
!= 'MESH':
1953 me
= ob
.to_mesh(scene
, True, 'PREVIEW')
1958 meshes_to_clear
.append(me
)
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
)
1972 me
.update(calc_tessface
=True)
1975 # # Support object colors
1976 # tmp_colbits = ob.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]
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
):
1998 textures
[tex
] = texture_mapping_local
[tex
] = None
2001 mat
= mats
[f
.material_index
]
2005 materials
[mat
, tex
] = material_mapping_local
[mat
, tex
] = None # should use sets, wait for blender 2.5
2009 # 2.44 use mat.lib too for uniqueness
2010 materials
[mat
, None] = material_mapping_local
[mat
, None] = None
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':
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
)))
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
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
:
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
]:
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
:
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
:
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)
2121 for my_arm
in ob_arms
:
2122 if my_arm
.blenObject
== my_mesh
.fbxArm
:
2123 my_mesh
.fbxArm
= my_arm
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
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)
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
:
2170 for ob
in blenGroup
.objects
:
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
]
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
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:
2212 traceback
.print_exc()
2216 ; Object definitions
2217 ;------------------------------------------------------------------
2229 bone_deformer_count
+
2231 (len(textures
) * 2))) # add 1 for global settings
2233 del bone_deformer_count
2236 ObjectType: "Model" {
2248 ObjectType: "Geometry" {
2250 }''' % len(ob_meshes
))
2254 ObjectType: "Material" {
2256 }''' % len(materials
))
2260 ObjectType: "Texture" {
2262 }''' % len(textures
)) # add 1 for an empty tex
2264 ObjectType: "Video" {
2266 }''' % len(textures
)) # add 1 for an empty tex
2269 # Add deformer nodes
2270 for my_mesh
in ob_meshes
:
2275 for my_bone
in ob_bones
:
2276 tmp
+= len(my_bone
.blenMeshes
)
2280 ObjectType: "Deformer" {
2285 # Bind pose is essential for XNA if the 'MESH' is included,
2286 # but could be removed now?
2288 ObjectType: "Pose" {
2294 ObjectType: "GroupSelection" {
2299 ObjectType: "GlobalSettings" {
2307 ;------------------------------------------------------------------
2311 if 'CAMERA' in object_types
:
2312 # To comply with other FBX FILES
2313 write_camera_switch()
2315 for my_null
in ob_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
:
2332 #for bonename, bone, obname, me, armob in ob_bones:
2333 for my_bone
in ob_bones
:
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
)
2346 for texname
, tex
in textures
:
2347 write_texture(texname
, tex
, i
)
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
:
2359 write_deformer_skin(my_mesh
.fbxName
)
2361 # Get normalized weights for temorary use
2362 if my_mesh
.fbxBoneParent
:
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)
2377 Pose: "Pose::BIND_POSES", "BindPose" {
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()))
2393 # Finish Writing Objects
2394 # Write global settings
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
2414 ;------------------------------------------------------------------
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
)
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" {
2460 for matname
, (mat
, tex
) in materials
:
2461 fw('\n\tMaterial: "Material::%s", "" {\n\t}' % matname
)
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
:
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
)
2489 ; Object connections
2490 ;------------------------------------------------------------------
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
))
2504 fw('\n\tConnect: "OO", "Model::%s", "Model::Scene"' % my_ob
.fbxName
)
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
))
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
:
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
:
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
2544 fw('\n\tConnect: "OO", "Model::%s", "Model::%s"' % (my_bone
.fbxName
, my_bone
.parent
.fbxName
))
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
))
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)
2562 # Needed for scene footer as well as animation
2563 render
= scene
.render
2566 #define KTIME_ONE_SECOND KTime (K_LONGLONG(46186158000))
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
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
2592 blenActionDefault
= None
2593 action_lastcompat
= None
2595 # instead of tagging
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
:
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
)
2612 # find which actions are compatible with the armatures
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
)
2625 # in case there are no actions applied to armatures
2626 # for example, when a user deletes the current action.
2627 action_lastcompat
= action
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
2640 ;Takes and animation section
2641 ;----------------------------------------------------
2645 if blenActionDefault
and not use_default_take
:
2646 fw('\n\tCurrent: "%s"' % sane_takename(blenActionDefault
))
2648 fw('\n\tCurrent: "Default Take"')
2650 for blenAction
in tmp_actions
:
2651 # we have tagged all actious that are used be selected armatures
2653 if blenAction
.name
in tagged_actions
:
2654 print('\taction: "%s" exporting...' % blenAction
.name
)
2656 print('\taction: "%s" has no armature using it, skipping' % blenAction
.name
)
2659 if blenAction
is None:
2660 # Warning, this only accounts for tmp_actions being [None]
2661 take_name
= "Default Take"
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
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()
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)
2706 my_ob
.setPoseFrame(i
)
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
:
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)]
2728 for TX_LAYER
, TX_CHAN
in enumerate('TRS'): # transform, rotate, scale
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':
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
= []
2741 for mtx
in context_bone_anim_mats
:
2743 prev_eul
= mtx
[1].to_euler('XYZ', prev_eul
)
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
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: ')
2761 while frame
<= act_end
:
2762 if frame
!= act_start
:
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
]))
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
]
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
]
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
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]))
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
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
))
2822 fw('\n\t\t\t\t\t\tColor: 1,0,0')
2824 fw('\n\t\t\t\t\t\tColor: 0,1,0')
2826 fw('\n\t\t\t\t\t\tColor: 0,0,1')
2829 fw('\n\t\t\t\t\tLayerType: %i' % (TX_LAYER
+ 1))
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
2848 scene
.frame_set(frame_orig
)
2852 fw('\n;Takes and animation section')
2853 fw('\n;----------------------------------------------------')
2856 fw('\n\tCurrent: ""')
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
2868 m
= world
.mist_settings
2869 has_mist
= m
.use_mist
2870 mist_intense
= m
.intensity
2871 mist_start
= m
.start
2873 # mist_height = m.height # UNUSED
2874 world_hor
= world
.horizon_color
2876 has_mist
= mist_intense
= mist_start
= mist_end
= 0
2879 fw('\n;Version 5 settings')
2880 fw('\n;------------------------------------------------------------------')
2883 fw('\n\tAmbientRenderSettings: {')
2884 fw('\n\t\tVersion: 101')
2885 fw('\n\t\tAmbientLightColor: %.1f,%.1f,%.1f,0' % tuple(world_amb
))
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
))
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))
2903 fw('\n\tRendererSetting: {')
2904 fw('\n\t\tDefaultCamera: "Producer Perspective"')
2905 fw('\n\t\tDefaultViewingMode: 0')
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
,
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
))
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,
2945 use_anim_optimize
=False,
2946 use_anim_action_all
=True,
2948 use_default_take
=True,
2952 def save(operator
, context
,
2954 use_selection
=False,
2956 use_batch_own_dir
=False,
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()
2966 kwargs_mod
["context_objects"] = context
.selected_objects
2968 kwargs_mod
["context_objects"] = context
.scene
.objects
2970 return save_single(operator
, context
.scene
, filepath
, **kwargs_mod
)
2974 prefix
= os
.path
.basename(fbxpath
)
2976 fbxpath
= os
.path
.dirname(fbxpath
)
2978 if not fbxpath
.endswith(os
.sep
):
2981 if batch_mode
== 'GROUP':
2982 data_seq
= bpy
.data
.groups
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
)
3019 # TODO - BUMMER! Armatures not in the group wont animate the mesh
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