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, Bastien Montagne
30 from collections
import OrderedDict
31 from itertools
import zip_longest
, chain
35 if "encode_bin" in locals():
36 importlib
.reload(encode_bin
)
37 if "data_types" in locals():
38 importlib
.reload(data_types
)
39 if "fbx_utils" in locals():
40 importlib
.reload(fbx_utils
)
44 from mathutils
import Vector
, Matrix
46 from . import encode_bin
, data_types
, fbx_utils
47 from .fbx_utils
import (
49 FBX_VERSION
, FBX_HEADER_VERSION
, FBX_SCENEINFO_VERSION
, FBX_TEMPLATES_VERSION
,
51 FBX_GEOMETRY_VERSION
, FBX_GEOMETRY_NORMAL_VERSION
, FBX_GEOMETRY_BINORMAL_VERSION
, FBX_GEOMETRY_TANGENT_VERSION
,
52 FBX_GEOMETRY_SMOOTHING_VERSION
, FBX_GEOMETRY_VCOLOR_VERSION
, FBX_GEOMETRY_UV_VERSION
,
53 FBX_GEOMETRY_MATERIAL_VERSION
, FBX_GEOMETRY_LAYER_VERSION
,
54 FBX_GEOMETRY_SHAPE_VERSION
, FBX_DEFORMER_SHAPE_VERSION
, FBX_DEFORMER_SHAPECHANNEL_VERSION
,
55 FBX_POSE_BIND_VERSION
, FBX_DEFORMER_SKIN_VERSION
, FBX_DEFORMER_CLUSTER_VERSION
,
56 FBX_MATERIAL_VERSION
, FBX_TEXTURE_VERSION
,
58 FBX_ANIM_PROPSGROUP_NAME
,
60 BLENDER_OTHER_OBJECT_TYPES
, BLENDER_OBJECT_TYPES_MESHLIKE
,
61 FBX_LIGHT_TYPES
, FBX_LIGHT_DECAY_TYPES
,
62 RIGHT_HAND_AXES
, FBX_FRAMERATES
,
63 # Miscellaneous utils.
65 units_blender_to_fbx_factor
, units_convertor
, units_convertor_iter
,
66 matrix4_to_array
, similar_values
, similar_values_iter
,
67 # Mesh transform helpers.
68 vcos_transformed_gen
, nors_transformed_gen
,
70 get_fbx_uuid_from_key
,
72 get_blenderID_key
, get_blenderID_name
,
73 get_blender_mesh_shape_key
, get_blender_mesh_shape_channel_key
,
74 get_blender_empty_key
, get_blender_bone_key
,
75 get_blender_bindpose_key
, get_blender_armature_skin_key
, get_blender_bone_cluster_key
,
76 get_blender_anim_id_base
, get_blender_anim_stack_key
, get_blender_anim_layer_key
,
77 get_blender_anim_curve_node_key
, get_blender_anim_curve_key
,
80 elem_data_single_bool
, elem_data_single_int16
, elem_data_single_int32
, elem_data_single_int64
,
81 elem_data_single_float32
, elem_data_single_float64
,
82 elem_data_single_bytes
, elem_data_single_string
, elem_data_single_string_unicode
,
83 elem_data_single_bool_array
, elem_data_single_int32_array
, elem_data_single_int64_array
,
84 elem_data_single_float32_array
, elem_data_single_float64_array
, elem_data_vec_float64
,
85 # FBX element properties.
86 elem_properties
, elem_props_set
, elem_props_compound
,
87 # FBX element properties handling templates.
88 elem_props_template_init
, elem_props_template_set
, elem_props_template_finalize
,
90 FBXTemplate
, fbx_templates_generate
,
92 AnimationCurveNodeWrapper
,
94 ObjectWrapper
, fbx_name_class
,
96 FBXExportSettingsMedia
, FBXExportSettings
, FBXExportData
,
100 convert_sec_to_ktime
= units_convertor("second", "ktime")
101 convert_sec_to_ktime_iter
= units_convertor_iter("second", "ktime")
103 convert_mm_to_inch
= units_convertor("millimeter", "inch")
105 convert_rad_to_deg
= units_convertor("radian", "degree")
106 convert_rad_to_deg_iter
= units_convertor_iter("radian", "degree")
109 # ##### Templates #####
110 # TODO: check all those "default" values, they should match Blender's default as much as possible, I guess?
112 def fbx_template_def_globalsettings(scene
, settings
, override_defaults
=None, nbr_users
=0):
113 props
= OrderedDict()
114 if override_defaults
is not None:
115 props
.update(override_defaults
)
116 return FBXTemplate(b
"GlobalSettings", b
"", props
, nbr_users
, [False])
119 def fbx_template_def_model(scene
, settings
, override_defaults
=None, nbr_users
=0):
120 gscale
= settings
.global_scale
121 props
= OrderedDict((
122 # Name, Value, Type, Animatable
123 (b
"QuaternionInterpolate", (0, "p_enum", False)), # 0 = no quat interpolation.
124 (b
"RotationOffset", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
125 (b
"RotationPivot", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
126 (b
"ScalingOffset", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
127 (b
"ScalingPivot", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
128 (b
"TranslationActive", (False, "p_bool", False)),
129 (b
"TranslationMin", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
130 (b
"TranslationMax", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
131 (b
"TranslationMinX", (False, "p_bool", False)),
132 (b
"TranslationMinY", (False, "p_bool", False)),
133 (b
"TranslationMinZ", (False, "p_bool", False)),
134 (b
"TranslationMaxX", (False, "p_bool", False)),
135 (b
"TranslationMaxY", (False, "p_bool", False)),
136 (b
"TranslationMaxZ", (False, "p_bool", False)),
137 (b
"RotationOrder", (0, "p_enum", False)), # we always use 'XYZ' order.
138 (b
"RotationSpaceForLimitOnly", (False, "p_bool", False)),
139 (b
"RotationStiffnessX", (0.0, "p_double", False)),
140 (b
"RotationStiffnessY", (0.0, "p_double", False)),
141 (b
"RotationStiffnessZ", (0.0, "p_double", False)),
142 (b
"AxisLen", (10.0, "p_double", False)),
143 (b
"PreRotation", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
144 (b
"PostRotation", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
145 (b
"RotationActive", (False, "p_bool", False)),
146 (b
"RotationMin", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
147 (b
"RotationMax", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
148 (b
"RotationMinX", (False, "p_bool", False)),
149 (b
"RotationMinY", (False, "p_bool", False)),
150 (b
"RotationMinZ", (False, "p_bool", False)),
151 (b
"RotationMaxX", (False, "p_bool", False)),
152 (b
"RotationMaxY", (False, "p_bool", False)),
153 (b
"RotationMaxZ", (False, "p_bool", False)),
154 (b
"InheritType", (0, "p_enum", False)), # RrSs
155 (b
"ScalingActive", (False, "p_bool", False)),
156 (b
"ScalingMin", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
157 (b
"ScalingMax", ((1.0, 1.0, 1.0), "p_vector_3d", False)),
158 (b
"ScalingMinX", (False, "p_bool", False)),
159 (b
"ScalingMinY", (False, "p_bool", False)),
160 (b
"ScalingMinZ", (False, "p_bool", False)),
161 (b
"ScalingMaxX", (False, "p_bool", False)),
162 (b
"ScalingMaxY", (False, "p_bool", False)),
163 (b
"ScalingMaxZ", (False, "p_bool", False)),
164 (b
"GeometricTranslation", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
165 (b
"GeometricRotation", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
166 (b
"GeometricScaling", ((1.0, 1.0, 1.0), "p_vector_3d", False)),
167 (b
"MinDampRangeX", (0.0, "p_double", False)),
168 (b
"MinDampRangeY", (0.0, "p_double", False)),
169 (b
"MinDampRangeZ", (0.0, "p_double", False)),
170 (b
"MaxDampRangeX", (0.0, "p_double", False)),
171 (b
"MaxDampRangeY", (0.0, "p_double", False)),
172 (b
"MaxDampRangeZ", (0.0, "p_double", False)),
173 (b
"MinDampStrengthX", (0.0, "p_double", False)),
174 (b
"MinDampStrengthY", (0.0, "p_double", False)),
175 (b
"MinDampStrengthZ", (0.0, "p_double", False)),
176 (b
"MaxDampStrengthX", (0.0, "p_double", False)),
177 (b
"MaxDampStrengthY", (0.0, "p_double", False)),
178 (b
"MaxDampStrengthZ", (0.0, "p_double", False)),
179 (b
"PreferedAngleX", (0.0, "p_double", False)),
180 (b
"PreferedAngleY", (0.0, "p_double", False)),
181 (b
"PreferedAngleZ", (0.0, "p_double", False)),
182 (b
"LookAtProperty", (None, "p_object", False)),
183 (b
"UpVectorProperty", (None, "p_object", False)),
184 (b
"Show", (True, "p_bool", False)),
185 (b
"NegativePercentShapeSupport", (True, "p_bool", False)),
186 (b
"DefaultAttributeIndex", (-1, "p_integer", False)),
187 (b
"Freeze", (False, "p_bool", False)),
188 (b
"LODBox", (False, "p_bool", False)),
189 (b
"Lcl Translation", ((0.0, 0.0, 0.0), "p_lcl_translation", True)),
190 (b
"Lcl Rotation", ((0.0, 0.0, 0.0), "p_lcl_rotation", True)),
191 (b
"Lcl Scaling", ((1.0, 1.0, 1.0), "p_lcl_scaling", True)),
192 (b
"Visibility", (1.0, "p_visibility", True)),
193 (b
"Visibility Inheritance", (1, "p_visibility_inheritance", False)),
195 if override_defaults
is not None:
196 props
.update(override_defaults
)
197 return FBXTemplate(b
"Model", b
"FbxNode", props
, nbr_users
, [False])
200 def fbx_template_def_null(scene
, settings
, override_defaults
=None, nbr_users
=0):
201 props
= OrderedDict((
202 (b
"Color", ((0.8, 0.8, 0.8), "p_color_rgb", False)),
203 (b
"Size", (100.0, "p_double", False)),
204 (b
"Look", (1, "p_enum", False)), # Cross (0 is None, i.e. invisible?).
206 if override_defaults
is not None:
207 props
.update(override_defaults
)
208 return FBXTemplate(b
"NodeAttribute", b
"FbxNull", props
, nbr_users
, [False])
211 def fbx_template_def_light(scene
, settings
, override_defaults
=None, nbr_users
=0):
212 gscale
= settings
.global_scale
213 props
= OrderedDict((
214 (b
"LightType", (0, "p_enum", False)), # Point light.
215 (b
"CastLight", (True, "p_bool", False)),
216 (b
"Color", ((1.0, 1.0, 1.0), "p_color", True)),
217 (b
"Intensity", (100.0, "p_number", True)), # Times 100 compared to Blender values...
218 (b
"DecayType", (2, "p_enum", False)), # Quadratic.
219 (b
"DecayStart", (30.0 * gscale
, "p_double", False)),
220 (b
"CastShadows", (True, "p_bool", False)),
221 (b
"ShadowColor", ((0.0, 0.0, 0.0), "p_color", True)),
222 (b
"AreaLightShape", (0, "p_enum", False)), # Rectangle.
224 if override_defaults
is not None:
225 props
.update(override_defaults
)
226 return FBXTemplate(b
"NodeAttribute", b
"FbxLight", props
, nbr_users
, [False])
229 def fbx_template_def_camera(scene
, settings
, override_defaults
=None, nbr_users
=0):
231 props
= OrderedDict((
232 (b
"Color", ((0.8, 0.8, 0.8), "p_color_rgb", False)),
233 (b
"Position", ((0.0, 0.0, -50.0), "p_vector", True)),
234 (b
"UpVector", ((0.0, 1.0, 0.0), "p_vector", True)),
235 (b
"InterestPosition", ((0.0, 0.0, 0.0), "p_vector", True)),
236 (b
"Roll", (0.0, "p_roll", True)),
237 (b
"OpticalCenterX", (0.0, "p_opticalcenterx", True)),
238 (b
"OpticalCenterY", (0.0, "p_opticalcentery", True)),
239 (b
"BackgroundColor", ((0.63, 0.63, 0.63), "p_color", True)),
240 (b
"TurnTable", (0.0, "p_number", True)),
241 (b
"DisplayTurnTableIcon", (False, "p_bool", False)),
242 (b
"UseMotionBlur", (False, "p_bool", False)),
243 (b
"UseRealTimeMotionBlur", (True, "p_bool", False)),
244 (b
"Motion Blur Intensity", (1.0, "p_number", True)),
245 (b
"AspectRatioMode", (0, "p_enum", False)), # WindowSize.
246 (b
"AspectWidth", (320.0, "p_double", False)),
247 (b
"AspectHeight", (200.0, "p_double", False)),
248 (b
"PixelAspectRatio", (1.0, "p_double", False)),
249 (b
"FilmOffsetX", (0.0, "p_number", True)),
250 (b
"FilmOffsetY", (0.0, "p_number", True)),
251 (b
"FilmWidth", (0.816, "p_double", False)),
252 (b
"FilmHeight", (0.612, "p_double", False)),
253 (b
"FilmAspectRatio", (1.3333333333333333, "p_double", False)),
254 (b
"FilmSqueezeRatio", (1.0, "p_double", False)),
255 (b
"FilmFormatIndex", (0, "p_enum", False)), # Assuming this is ApertureFormat, 0 = custom.
256 (b
"PreScale", (1.0, "p_number", True)),
257 (b
"FilmTranslateX", (0.0, "p_number", True)),
258 (b
"FilmTranslateY", (0.0, "p_number", True)),
259 (b
"FilmRollPivotX", (0.0, "p_number", True)),
260 (b
"FilmRollPivotY", (0.0, "p_number", True)),
261 (b
"FilmRollValue", (0.0, "p_number", True)),
262 (b
"FilmRollOrder", (0, "p_enum", False)), # 0 = rotate first (default).
263 (b
"ApertureMode", (2, "p_enum", False)), # 2 = Vertical.
264 (b
"GateFit", (0, "p_enum", False)), # 0 = no resolution gate fit.
265 (b
"FieldOfView", (25.114999771118164, "p_fov", True)),
266 (b
"FieldOfViewX", (40.0, "p_fov_x", True)),
267 (b
"FieldOfViewY", (40.0, "p_fov_y", True)),
268 (b
"FocalLength", (34.89327621672628, "p_number", True)),
269 (b
"CameraFormat", (0, "p_enum", False)), # Custom camera format.
270 (b
"UseFrameColor", (False, "p_bool", False)),
271 (b
"FrameColor", ((0.3, 0.3, 0.3), "p_color_rgb", False)),
272 (b
"ShowName", (True, "p_bool", False)),
273 (b
"ShowInfoOnMoving", (True, "p_bool", False)),
274 (b
"ShowGrid", (True, "p_bool", False)),
275 (b
"ShowOpticalCenter", (False, "p_bool", False)),
276 (b
"ShowAzimut", (True, "p_bool", False)),
277 (b
"ShowTimeCode", (False, "p_bool", False)),
278 (b
"ShowAudio", (False, "p_bool", False)),
279 (b
"AudioColor", ((0.0, 1.0, 0.0), "p_vector_3d", False)), # Yep, vector3d, not corlorgb… :cry:
280 (b
"NearPlane", (10.0, "p_double", False)),
281 (b
"FarPlane", (4000.0, "p_double", False)),
282 (b
"AutoComputeClipPanes", (False, "p_bool", False)),
283 (b
"ViewCameraToLookAt", (True, "p_bool", False)),
284 (b
"ViewFrustumNearFarPlane", (False, "p_bool", False)),
285 (b
"ViewFrustumBackPlaneMode", (2, "p_enum", False)), # 2 = show back plane if texture added.
286 (b
"BackPlaneDistance", (4000.0, "p_number", True)),
287 (b
"BackPlaneDistanceMode", (1, "p_enum", False)), # 1 = relative to camera.
288 (b
"ViewFrustumFrontPlaneMode", (2, "p_enum", False)), # 2 = show front plane if texture added.
289 (b
"FrontPlaneDistance", (10.0, "p_number", True)),
290 (b
"FrontPlaneDistanceMode", (1, "p_enum", False)), # 1 = relative to camera.
291 (b
"LockMode", (False, "p_bool", False)),
292 (b
"LockInterestNavigation", (False, "p_bool", False)),
293 # BackPlate... properties **arggggg!**
294 (b
"FitImage", (False, "p_bool", False)),
295 (b
"Crop", (False, "p_bool", False)),
296 (b
"Center", (True, "p_bool", False)),
297 (b
"KeepRatio", (True, "p_bool", False)),
298 # End of BackPlate...
299 (b
"BackgroundAlphaTreshold", (0.5, "p_double", False)),
300 (b
"ShowBackplate", (True, "p_bool", False)),
301 (b
"BackPlaneOffsetX", (0.0, "p_number", True)),
302 (b
"BackPlaneOffsetY", (0.0, "p_number", True)),
303 (b
"BackPlaneRotation", (0.0, "p_number", True)),
304 (b
"BackPlaneScaleX", (1.0, "p_number", True)),
305 (b
"BackPlaneScaleY", (1.0, "p_number", True)),
306 (b
"Background Texture", (None, "p_object", False)),
307 (b
"FrontPlateFitImage", (True, "p_bool", False)),
308 (b
"FrontPlateCrop", (False, "p_bool", False)),
309 (b
"FrontPlateCenter", (True, "p_bool", False)),
310 (b
"FrontPlateKeepRatio", (True, "p_bool", False)),
311 (b
"Foreground Opacity", (1.0, "p_double", False)),
312 (b
"ShowFrontplate", (True, "p_bool", False)),
313 (b
"FrontPlaneOffsetX", (0.0, "p_number", True)),
314 (b
"FrontPlaneOffsetY", (0.0, "p_number", True)),
315 (b
"FrontPlaneRotation", (0.0, "p_number", True)),
316 (b
"FrontPlaneScaleX", (1.0, "p_number", True)),
317 (b
"FrontPlaneScaleY", (1.0, "p_number", True)),
318 (b
"Foreground Texture", (None, "p_object", False)),
319 (b
"DisplaySafeArea", (False, "p_bool", False)),
320 (b
"DisplaySafeAreaOnRender", (False, "p_bool", False)),
321 (b
"SafeAreaDisplayStyle", (1, "p_enum", False)), # 1 = rounded corners.
322 (b
"SafeAreaAspectRatio", (1.3333333333333333, "p_double", False)),
323 (b
"Use2DMagnifierZoom", (False, "p_bool", False)),
324 (b
"2D Magnifier Zoom", (100.0, "p_number", True)),
325 (b
"2D Magnifier X", (50.0, "p_number", True)),
326 (b
"2D Magnifier Y", (50.0, "p_number", True)),
327 (b
"CameraProjectionType", (0, "p_enum", False)), # 0 = perspective, 1 = orthogonal.
328 (b
"OrthoZoom", (1.0, "p_double", False)),
329 (b
"UseRealTimeDOFAndAA", (False, "p_bool", False)),
330 (b
"UseDepthOfField", (False, "p_bool", False)),
331 (b
"FocusSource", (0, "p_enum", False)), # 0 = camera interest, 1 = distance from camera interest.
332 (b
"FocusAngle", (3.5, "p_double", False)), # ???
333 (b
"FocusDistance", (200.0, "p_double", False)),
334 (b
"UseAntialiasing", (False, "p_bool", False)),
335 (b
"AntialiasingIntensity", (0.77777, "p_double", False)),
336 (b
"AntialiasingMethod", (0, "p_enum", False)), # 0 = oversampling, 1 = hardware.
337 (b
"UseAccumulationBuffer", (False, "p_bool", False)),
338 (b
"FrameSamplingCount", (7, "p_integer", False)),
339 (b
"FrameSamplingType", (1, "p_enum", False)), # 0 = uniform, 1 = stochastic.
341 if override_defaults
is not None:
342 props
.update(override_defaults
)
343 return FBXTemplate(b
"NodeAttribute", b
"FbxCamera", props
, nbr_users
, [False])
346 def fbx_template_def_bone(scene
, settings
, override_defaults
=None, nbr_users
=0):
347 props
= OrderedDict()
348 if override_defaults
is not None:
349 props
.update(override_defaults
)
350 return FBXTemplate(b
"NodeAttribute", b
"LimbNode", props
, nbr_users
, [False])
353 def fbx_template_def_geometry(scene
, settings
, override_defaults
=None, nbr_users
=0):
354 props
= OrderedDict((
355 (b
"Color", ((0.8, 0.8, 0.8), "p_color_rgb", False)),
356 (b
"BBoxMin", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
357 (b
"BBoxMax", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
358 (b
"Primary Visibility", (True, "p_bool", False)),
359 (b
"Casts Shadows", (True, "p_bool", False)),
360 (b
"Receive Shadows", (True, "p_bool", False)),
362 if override_defaults
is not None:
363 props
.update(override_defaults
)
364 return FBXTemplate(b
"Geometry", b
"FbxMesh", props
, nbr_users
, [False])
367 def fbx_template_def_material(scene
, settings
, override_defaults
=None, nbr_users
=0):
369 props
= OrderedDict((
370 (b
"ShadingModel", ("Phong", "p_string", False)),
371 (b
"MultiLayer", (False, "p_bool", False)),
373 (b
"EmissiveColor", ((0.0, 0.0, 0.0), "p_color", True)),
374 (b
"EmissiveFactor", (1.0, "p_number", True)),
375 (b
"AmbientColor", ((0.2, 0.2, 0.2), "p_color", True)),
376 (b
"AmbientFactor", (1.0, "p_number", True)),
377 (b
"DiffuseColor", ((0.8, 0.8, 0.8), "p_color", True)),
378 (b
"DiffuseFactor", (1.0, "p_number", True)),
379 (b
"TransparentColor", ((0.0, 0.0, 0.0), "p_color", True)),
380 (b
"TransparencyFactor", (0.0, "p_number", True)),
381 (b
"Opacity", (1.0, "p_number", True)),
382 (b
"NormalMap", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
383 (b
"Bump", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
384 (b
"BumpFactor", (1.0, "p_double", False)),
385 (b
"DisplacementColor", ((0.0, 0.0, 0.0), "p_color_rgb", False)),
386 (b
"DisplacementFactor", (1.0, "p_double", False)),
387 (b
"VectorDisplacementColor", ((0.0, 0.0, 0.0), "p_color_rgb", False)),
388 (b
"VectorDisplacementFactor", (1.0, "p_double", False)),
390 (b
"SpecularColor", ((0.2, 0.2, 0.2), "p_color", True)),
391 (b
"SpecularFactor", (1.0, "p_number", True)),
392 # Not sure about the name, importer uses this (but ShininessExponent for tex prop name!)
393 # And in fbx exported by sdk, you have one in template, the other in actual material!!! :/
394 # For now, using both.
395 (b
"Shininess", (20.0, "p_number", True)),
396 (b
"ShininessExponent", (20.0, "p_number", True)),
397 (b
"ReflectionColor", ((0.0, 0.0, 0.0), "p_color", True)),
398 (b
"ReflectionFactor", (1.0, "p_number", True)),
400 if override_defaults
is not None:
401 props
.update(override_defaults
)
402 return FBXTemplate(b
"Material", b
"FbxSurfacePhong", props
, nbr_users
, [False])
405 def fbx_template_def_texture_file(scene
, settings
, override_defaults
=None, nbr_users
=0):
407 # XXX Not sure about all names!
408 props
= OrderedDict((
409 (b
"TextureTypeUse", (0, "p_enum", False)), # Standard.
410 (b
"AlphaSource", (2, "p_enum", False)), # Black (i.e. texture's alpha), XXX name guessed!.
411 (b
"Texture alpha", (1.0, "p_double", False)),
412 (b
"PremultiplyAlpha", (True, "p_bool", False)),
413 (b
"CurrentTextureBlendMode", (1, "p_enum", False)), # Additive...
414 (b
"CurrentMappingType", (0, "p_enum", False)), # UV.
415 (b
"UVSet", ("default", "p_string", False)), # UVMap name.
416 (b
"WrapModeU", (0, "p_enum", False)), # Repeat.
417 (b
"WrapModeV", (0, "p_enum", False)), # Repeat.
418 (b
"UVSwap", (False, "p_bool", False)),
419 (b
"Translation", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
420 (b
"Rotation", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
421 (b
"Scaling", ((1.0, 1.0, 1.0), "p_vector_3d", False)),
422 (b
"TextureRotationPivot", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
423 (b
"TextureScalingPivot", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
424 # Not sure about those two...
425 (b
"UseMaterial", (False, "p_bool", False)),
426 (b
"UseMipMap", (False, "p_bool", False)),
428 if override_defaults
is not None:
429 props
.update(override_defaults
)
430 return FBXTemplate(b
"Texture", b
"FbxFileTexture", props
, nbr_users
, [False])
433 def fbx_template_def_video(scene
, settings
, override_defaults
=None, nbr_users
=0):
435 props
= OrderedDict((
437 (b
"Width", (0, "p_integer", False)),
438 (b
"Height", (0, "p_integer", False)),
439 (b
"Path", ("", "p_string_url", False)),
440 (b
"AccessMode", (0, "p_enum", False)), # Disk (0=Disk, 1=Mem, 2=DiskAsync).
442 (b
"StartFrame", (0, "p_integer", False)),
443 (b
"StopFrame", (0, "p_integer", False)),
444 (b
"Offset", (0, "p_timestamp", False)),
445 (b
"PlaySpeed", (0.0, "p_double", False)),
446 (b
"FreeRunning", (False, "p_bool", False)),
447 (b
"Loop", (False, "p_bool", False)),
448 (b
"InterlaceMode", (0, "p_enum", False)), # None, i.e. progressive.
450 (b
"ImageSequence", (False, "p_bool", False)),
451 (b
"ImageSequenceOffset", (0, "p_integer", False)),
452 (b
"FrameRate", (0.0, "p_double", False)),
453 (b
"LastFrame", (0, "p_integer", False)),
455 if override_defaults
is not None:
456 props
.update(override_defaults
)
457 return FBXTemplate(b
"Video", b
"FbxVideo", props
, nbr_users
, [False])
460 def fbx_template_def_pose(scene
, settings
, override_defaults
=None, nbr_users
=0):
461 props
= OrderedDict()
462 if override_defaults
is not None:
463 props
.update(override_defaults
)
464 return FBXTemplate(b
"Pose", b
"", props
, nbr_users
, [False])
467 def fbx_template_def_deformer(scene
, settings
, override_defaults
=None, nbr_users
=0):
468 props
= OrderedDict()
469 if override_defaults
is not None:
470 props
.update(override_defaults
)
471 return FBXTemplate(b
"Deformer", b
"", props
, nbr_users
, [False])
474 def fbx_template_def_animstack(scene
, settings
, override_defaults
=None, nbr_users
=0):
475 props
= OrderedDict((
476 (b
"Description", ("", "p_string", False)),
477 (b
"LocalStart", (0, "p_timestamp", False)),
478 (b
"LocalStop", (0, "p_timestamp", False)),
479 (b
"ReferenceStart", (0, "p_timestamp", False)),
480 (b
"ReferenceStop", (0, "p_timestamp", False)),
482 if override_defaults
is not None:
483 props
.update(override_defaults
)
484 return FBXTemplate(b
"AnimationStack", b
"FbxAnimStack", props
, nbr_users
, [False])
487 def fbx_template_def_animlayer(scene
, settings
, override_defaults
=None, nbr_users
=0):
488 props
= OrderedDict((
489 (b
"Weight", (100.0, "p_number", True)),
490 (b
"Mute", (False, "p_bool", False)),
491 (b
"Solo", (False, "p_bool", False)),
492 (b
"Lock", (False, "p_bool", False)),
493 (b
"Color", ((0.8, 0.8, 0.8), "p_color_rgb", False)),
494 (b
"BlendMode", (0, "p_enum", False)),
495 (b
"RotationAccumulationMode", (0, "p_enum", False)),
496 (b
"ScaleAccumulationMode", (0, "p_enum", False)),
497 (b
"BlendModeBypass", (0, "p_ulonglong", False)),
499 if override_defaults
is not None:
500 props
.update(override_defaults
)
501 return FBXTemplate(b
"AnimationLayer", b
"FbxAnimLayer", props
, nbr_users
, [False])
504 def fbx_template_def_animcurvenode(scene
, settings
, override_defaults
=None, nbr_users
=0):
505 props
= OrderedDict((
506 (FBX_ANIM_PROPSGROUP_NAME
.encode(), (None, "p_compound", False)),
508 if override_defaults
is not None:
509 props
.update(override_defaults
)
510 return FBXTemplate(b
"AnimationCurveNode", b
"FbxAnimCurveNode", props
, nbr_users
, [False])
513 def fbx_template_def_animcurve(scene
, settings
, override_defaults
=None, nbr_users
=0):
514 props
= OrderedDict()
515 if override_defaults
is not None:
516 props
.update(override_defaults
)
517 return FBXTemplate(b
"AnimationCurve", b
"", props
, nbr_users
, [False])
520 # ##### Generators for connection elements. #####
522 def elem_connection(elem
, c_type
, uid_src
, uid_dst
, prop_dst
=None):
523 e
= elem_data_single_string(elem
, b
"C", c_type
)
526 if prop_dst
is not None:
527 e
.add_string(prop_dst
)
530 # ##### FBX objects generators. #####
532 def fbx_data_element_custom_properties(props
, bid
):
534 Store custom properties of blender ID bid (any mapping-like object, in fact) into FBX properties props.
536 for k
, v
in bid
.items():
537 list_val
= getattr(v
, "to_list", lambda: None)()
539 if isinstance(v
, str):
540 elem_props_set(props
, "p_string", k
.encode(), v
, custom
=True)
541 elif isinstance(v
, int):
542 elem_props_set(props
, "p_integer", k
.encode(), v
, custom
=True)
543 elif isinstance(v
, float):
544 elem_props_set(props
, "p_double", k
.encode(), v
, custom
=True)
546 if len(list_val
) == 3:
547 elem_props_set(props
, "p_vector", k
.encode(), list_val
, custom
=True)
549 elem_props_set(props
, "p_string", k
.encode(), str(list_val
), custom
=True)
551 elem_props_set(props
, "p_string", k
.encode(), str(v
), custom
=True)
554 def fbx_data_empty_elements(root
, empty
, scene_data
):
556 Write the Empty data block (you can control its FBX datatype with the 'fbx_type' string custom property).
558 empty_key
= scene_data
.data_empties
[empty
]
560 null
= elem_data_single_int64(root
, b
"NodeAttribute", get_fbx_uuid_from_key(empty_key
))
561 null
.add_string(fbx_name_class(empty
.name
.encode(), b
"NodeAttribute"))
562 val
= empty
.bdata
.get('fbx_type', None)
563 null
.add_string(val
.encode() if val
and isinstance(val
, str) else b
"Null")
565 elem_data_single_string(null
, b
"TypeFlags", b
"Null")
567 tmpl
= elem_props_template_init(scene_data
.templates
, b
"Null")
568 props
= elem_properties(null
)
569 elem_props_template_finalize(tmpl
, props
)
571 # No custom properties, already saved with object (Model).
574 def fbx_data_lamp_elements(root
, lamp
, scene_data
):
576 Write the Lamp data block.
578 gscale
= scene_data
.settings
.global_scale
580 lamp_key
= scene_data
.data_lamps
[lamp
]
582 decay_type
= FBX_LIGHT_DECAY_TYPES
['CONSTANT']
584 shadow_color
= Vector((0.0, 0.0, 0.0))
585 if lamp
.type not in {'HEMI'}:
586 if lamp
.type not in {'SUN', 'AREA'}:
587 decay_type
= FBX_LIGHT_DECAY_TYPES
[lamp
.falloff_type
]
588 do_light
= (not lamp
.use_only_shadow
) and (lamp
.use_specular
or lamp
.use_diffuse
)
589 do_shadow
= lamp
.shadow_method
not in {'NOSHADOW'}
590 shadow_color
= lamp
.shadow_color
592 light
= elem_data_single_int64(root
, b
"NodeAttribute", get_fbx_uuid_from_key(lamp_key
))
593 light
.add_string(fbx_name_class(lamp
.name
.encode(), b
"NodeAttribute"))
594 light
.add_string(b
"Light")
596 elem_data_single_int32(light
, b
"GeometryVersion", FBX_GEOMETRY_VERSION
) # Sic...
598 tmpl
= elem_props_template_init(scene_data
.templates
, b
"Light")
599 props
= elem_properties(light
)
600 elem_props_template_set(tmpl
, props
, "p_enum", b
"LightType", FBX_LIGHT_TYPES
[lamp
.type])
601 elem_props_template_set(tmpl
, props
, "p_bool", b
"CastLight", do_light
)
602 elem_props_template_set(tmpl
, props
, "p_color", b
"Color", lamp
.color
)
603 elem_props_template_set(tmpl
, props
, "p_number", b
"Intensity", lamp
.energy
* 100.0)
604 elem_props_template_set(tmpl
, props
, "p_enum", b
"DecayType", decay_type
)
605 elem_props_template_set(tmpl
, props
, "p_double", b
"DecayStart", lamp
.distance
* gscale
)
606 elem_props_template_set(tmpl
, props
, "p_bool", b
"CastShadows", do_shadow
)
607 elem_props_template_set(tmpl
, props
, "p_color", b
"ShadowColor", shadow_color
)
608 if lamp
.type in {'SPOT'}:
609 elem_props_template_set(tmpl
, props
, "p_double", b
"OuterAngle", math
.degrees(lamp
.spot_size
))
610 elem_props_template_set(tmpl
, props
, "p_double", b
"InnerAngle",
611 math
.degrees(lamp
.spot_size
* (1.0 - lamp
.spot_blend
)))
612 elem_props_template_finalize(tmpl
, props
)
615 if scene_data
.settings
.use_custom_props
:
616 fbx_data_element_custom_properties(props
, lamp
)
619 def fbx_data_camera_elements(root
, cam_obj
, scene_data
):
621 Write the Camera data blocks.
623 gscale
= scene_data
.settings
.global_scale
627 cam_key
= scene_data
.data_cameras
[cam_obj
]
629 # Real data now, good old camera!
630 # Object transform info.
631 loc
, rot
, scale
, matrix
, matrix_rot
= cam_obj
.fbx_object_tx(scene_data
)
632 up
= matrix_rot
* Vector((0.0, 1.0, 0.0))
633 to
= matrix_rot
* Vector((0.0, 0.0, -1.0))
635 # TODO We could export much more...
636 render
= scene_data
.scene
.render
637 width
= render
.resolution_x
638 height
= render
.resolution_y
639 aspect
= width
/ height
640 # Film width & height from mm to inches
641 filmwidth
= convert_mm_to_inch(cam_data
.sensor_width
)
642 filmheight
= convert_mm_to_inch(cam_data
.sensor_height
)
643 filmaspect
= filmwidth
/ filmheight
645 offsetx
= filmwidth
* cam_data
.shift_x
646 offsety
= filmaspect
* filmheight
* cam_data
.shift_y
648 cam
= elem_data_single_int64(root
, b
"NodeAttribute", get_fbx_uuid_from_key(cam_key
))
649 cam
.add_string(fbx_name_class(cam_data
.name
.encode(), b
"NodeAttribute"))
650 cam
.add_string(b
"Camera")
652 tmpl
= elem_props_template_init(scene_data
.templates
, b
"Camera")
653 props
= elem_properties(cam
)
655 elem_props_template_set(tmpl
, props
, "p_vector", b
"Position", loc
)
656 elem_props_template_set(tmpl
, props
, "p_vector", b
"UpVector", up
)
657 elem_props_template_set(tmpl
, props
, "p_vector", b
"InterestPosition", loc
+ to
) # Point, not vector!
658 # Should we use world value?
659 elem_props_template_set(tmpl
, props
, "p_color", b
"BackgroundColor", (0.0, 0.0, 0.0))
660 elem_props_template_set(tmpl
, props
, "p_bool", b
"DisplayTurnTableIcon", True)
662 elem_props_template_set(tmpl
, props
, "p_enum", b
"AspectRatioMode", 2) # FixedResolution
663 elem_props_template_set(tmpl
, props
, "p_double", b
"AspectWidth", float(render
.resolution_x
))
664 elem_props_template_set(tmpl
, props
, "p_double", b
"AspectHeight", float(render
.resolution_y
))
665 elem_props_template_set(tmpl
, props
, "p_double", b
"PixelAspectRatio",
666 float(render
.pixel_aspect_x
/ render
.pixel_aspect_y
))
668 elem_props_template_set(tmpl
, props
, "p_double", b
"FilmWidth", filmwidth
)
669 elem_props_template_set(tmpl
, props
, "p_double", b
"FilmHeight", filmheight
)
670 elem_props_template_set(tmpl
, props
, "p_double", b
"FilmAspectRatio", filmaspect
)
671 elem_props_template_set(tmpl
, props
, "p_double", b
"FilmOffsetX", offsetx
)
672 elem_props_template_set(tmpl
, props
, "p_double", b
"FilmOffsetY", offsety
)
674 elem_props_template_set(tmpl
, props
, "p_enum", b
"ApertureMode", 3) # FocalLength.
675 elem_props_template_set(tmpl
, props
, "p_enum", b
"GateFit", 2) # FitHorizontal.
676 elem_props_template_set(tmpl
, props
, "p_fov", b
"FieldOfView", math
.degrees(cam_data
.angle_x
))
677 elem_props_template_set(tmpl
, props
, "p_fov_x", b
"FieldOfViewX", math
.degrees(cam_data
.angle_x
))
678 elem_props_template_set(tmpl
, props
, "p_fov_y", b
"FieldOfViewY", math
.degrees(cam_data
.angle_y
))
679 # No need to convert to inches here...
680 elem_props_template_set(tmpl
, props
, "p_double", b
"FocalLength", cam_data
.lens
)
681 elem_props_template_set(tmpl
, props
, "p_double", b
"SafeAreaAspectRatio", aspect
)
682 # Default to perspective camera.
683 elem_props_template_set(tmpl
, props
, "p_enum", b
"CameraProjectionType", 1 if cam_data
.type == 'ORTHO' else 0)
684 elem_props_template_set(tmpl
, props
, "p_double", b
"OrthoZoom", cam_data
.ortho_scale
)
686 elem_props_template_set(tmpl
, props
, "p_double", b
"NearPlane", cam_data
.clip_start
* gscale
)
687 elem_props_template_set(tmpl
, props
, "p_double", b
"FarPlane", cam_data
.clip_end
* gscale
)
688 elem_props_template_set(tmpl
, props
, "p_enum", b
"BackPlaneDistanceMode", 1) # RelativeToCamera.
689 elem_props_template_set(tmpl
, props
, "p_double", b
"BackPlaneDistance", cam_data
.clip_end
* gscale
)
691 elem_props_template_finalize(tmpl
, props
)
694 if scene_data
.settings
.use_custom_props
:
695 fbx_data_element_custom_properties(props
, cam_data
)
697 elem_data_single_string(cam
, b
"TypeFlags", b
"Camera")
698 elem_data_single_int32(cam
, b
"GeometryVersion", 124) # Sic...
699 elem_data_vec_float64(cam
, b
"Position", loc
)
700 elem_data_vec_float64(cam
, b
"Up", up
)
701 elem_data_vec_float64(cam
, b
"LookAt", to
)
702 elem_data_single_int32(cam
, b
"ShowInfoOnMoving", 1)
703 elem_data_single_int32(cam
, b
"ShowAudio", 0)
704 elem_data_vec_float64(cam
, b
"AudioColor", (0.0, 1.0, 0.0))
705 elem_data_single_float64(cam
, b
"CameraOrthoZoom", 1.0)
708 def fbx_data_bindpose_element(root
, me_obj
, me
, scene_data
, arm_obj
=None, mat_world_arm
=None, bones
=[]):
710 Helper, since bindpose are used by both meshes shape keys and armature bones...
714 # We assume bind pose for our bones are their "Editmode" pose...
715 # All matrices are expected in global (world) space.
716 bindpose_key
= get_blender_bindpose_key(arm_obj
.bdata
, me
)
717 fbx_pose
= elem_data_single_int64(root
, b
"Pose", get_fbx_uuid_from_key(bindpose_key
))
718 fbx_pose
.add_string(fbx_name_class(me
.name
.encode(), b
"Pose"))
719 fbx_pose
.add_string(b
"BindPose")
721 elem_data_single_string(fbx_pose
, b
"Type", b
"BindPose")
722 elem_data_single_int32(fbx_pose
, b
"Version", FBX_POSE_BIND_VERSION
)
723 elem_data_single_int32(fbx_pose
, b
"NbPoseNodes", 1 + (1 if (arm_obj
!= me_obj
) else 0) + len(bones
))
725 # First node is mesh/object.
726 mat_world_obj
= me_obj
.fbx_object_matrix(scene_data
, global_space
=True)
727 fbx_posenode
= elem_empty(fbx_pose
, b
"PoseNode")
728 elem_data_single_int64(fbx_posenode
, b
"Node", me_obj
.fbx_uuid
)
729 elem_data_single_float64_array(fbx_posenode
, b
"Matrix", matrix4_to_array(mat_world_obj
))
730 # Second node is armature object itself.
731 if arm_obj
!= me_obj
:
732 fbx_posenode
= elem_empty(fbx_pose
, b
"PoseNode")
733 elem_data_single_int64(fbx_posenode
, b
"Node", arm_obj
.fbx_uuid
)
734 elem_data_single_float64_array(fbx_posenode
, b
"Matrix", matrix4_to_array(mat_world_arm
))
735 # And all bones of armature!
738 bomat
= bo_obj
.fbx_object_matrix(scene_data
, rest
=True, global_space
=True)
739 mat_world_bones
[bo_obj
] = bomat
740 fbx_posenode
= elem_empty(fbx_pose
, b
"PoseNode")
741 elem_data_single_int64(fbx_posenode
, b
"Node", bo_obj
.fbx_uuid
)
742 elem_data_single_float64_array(fbx_posenode
, b
"Matrix", matrix4_to_array(bomat
))
744 return mat_world_obj
, mat_world_bones
747 def fbx_data_mesh_shapes_elements(root
, me_obj
, me
, scene_data
, fbx_me_tmpl
, fbx_me_props
):
749 Write shape keys related data.
751 if me
not in scene_data
.data_deformers_shape
:
754 write_normals
= True # scene_data.settings.mesh_smooth_type in {'OFF'}
756 # First, write the geometry data itself (i.e. shapes).
757 _me_key
, shape_key
, shapes
= scene_data
.data_deformers_shape
[me
]
761 for shape
, (channel_key
, geom_key
, shape_verts_co
, shape_verts_idx
) in shapes
.items():
762 # Use vgroups as weights, if defined.
763 if shape
.vertex_group
and shape
.vertex_group
in me_obj
.bdata
.vertex_groups
:
764 shape_verts_weights
= [0.0] * (len(shape_verts_co
) // 3)
765 vg_idx
= me_obj
.bdata
.vertex_groups
[shape
.vertex_group
].index
766 for sk_idx
, v_idx
in enumerate(shape_verts_idx
):
767 for vg
in me
.vertices
[v_idx
].groups
:
768 if vg
.group
== vg_idx
:
769 shape_verts_weights
[sk_idx
] = vg
.weight
* 100.0
771 shape_verts_weights
= [100.0] * (len(shape_verts_co
) // 3)
772 channels
.append((channel_key
, shape
, shape_verts_weights
))
774 geom
= elem_data_single_int64(root
, b
"Geometry", get_fbx_uuid_from_key(geom_key
))
775 geom
.add_string(fbx_name_class(shape
.name
.encode(), b
"Geometry"))
776 geom
.add_string(b
"Shape")
778 tmpl
= elem_props_template_init(scene_data
.templates
, b
"Geometry")
779 props
= elem_properties(geom
)
780 elem_props_template_finalize(tmpl
, props
)
782 elem_data_single_int32(geom
, b
"Version", FBX_GEOMETRY_SHAPE_VERSION
)
784 elem_data_single_int32_array(geom
, b
"Indexes", shape_verts_idx
)
785 elem_data_single_float64_array(geom
, b
"Vertices", shape_verts_co
)
787 elem_data_single_float64_array(geom
, b
"Normals", [0.0] * len(shape_verts_co
))
789 # Yiha! BindPose for shapekeys too! Dodecasigh...
790 # XXX Not sure yet whether several bindposes on same mesh are allowed, or not... :/
791 fbx_data_bindpose_element(root
, me_obj
, me
, scene_data
)
793 # ...and now, the deformers stuff.
794 fbx_shape
= elem_data_single_int64(root
, b
"Deformer", get_fbx_uuid_from_key(shape_key
))
795 fbx_shape
.add_string(fbx_name_class(me
.name
.encode(), b
"Deformer"))
796 fbx_shape
.add_string(b
"BlendShape")
798 elem_data_single_int32(fbx_shape
, b
"Version", FBX_DEFORMER_SHAPE_VERSION
)
800 for channel_key
, shape
, shape_verts_weights
in channels
:
801 fbx_channel
= elem_data_single_int64(root
, b
"Deformer", get_fbx_uuid_from_key(channel_key
))
802 fbx_channel
.add_string(fbx_name_class(shape
.name
.encode(), b
"SubDeformer"))
803 fbx_channel
.add_string(b
"BlendShapeChannel")
805 elem_data_single_int32(fbx_channel
, b
"Version", FBX_DEFORMER_SHAPECHANNEL_VERSION
)
806 elem_data_single_float64(fbx_channel
, b
"DeformPercent", shape
.value
* 100.0) # Percents...
807 elem_data_single_float64_array(fbx_channel
, b
"FullWeights", shape_verts_weights
)
809 # *WHY* add this in linked mesh properties too? *cry*
810 # No idea whether it’s percent here too, or more usual factor (assume percentage for now) :/
811 elem_props_template_set(fbx_me_tmpl
, fbx_me_props
, "p_number", shape
.name
.encode(), shape
.value
* 100.0,
815 def fbx_data_mesh_elements(root
, me_obj
, scene_data
, done_meshes
):
817 Write the Mesh (Geometry) data block.
820 def _infinite_gen(val
):
824 me_key
, me
, _free
= scene_data
.data_meshes
[me_obj
]
826 # In case of multiple instances of same mesh, only write it once!
827 if me_key
in done_meshes
:
830 # No gscale/gmat here, all data are supposed to be in object space.
831 smooth_type
= scene_data
.settings
.mesh_smooth_type
832 write_normals
= True # smooth_type in {'OFF'}
834 do_bake_space_transform
= me_obj
.use_bake_space_transform(scene_data
)
836 # Vertices are in object space, but we are post-multiplying all transforms with the inverse of the
837 # global matrix, so we need to apply the global matrix to the vertices to get the correct result.
838 geom_mat_co
= scene_data
.settings
.global_matrix
if do_bake_space_transform
else None
839 # We need to apply the inverse transpose of the global matrix when transforming normals.
840 geom_mat_no
= Matrix(scene_data
.settings
.global_matrix_inv_transposed
) if do_bake_space_transform
else None
841 if geom_mat_no
is not None:
842 # Remove translation & scaling!
843 geom_mat_no
.translation
= Vector()
844 geom_mat_no
.normalize()
846 geom
= elem_data_single_int64(root
, b
"Geometry", get_fbx_uuid_from_key(me_key
))
847 geom
.add_string(fbx_name_class(me
.name
.encode(), b
"Geometry"))
848 geom
.add_string(b
"Mesh")
850 tmpl
= elem_props_template_init(scene_data
.templates
, b
"Geometry")
851 props
= elem_properties(geom
)
854 if scene_data
.settings
.use_custom_props
:
855 fbx_data_element_custom_properties(props
, me
)
857 elem_data_single_int32(geom
, b
"GeometryVersion", FBX_GEOMETRY_VERSION
)
860 t_co
= array
.array(data_types
.ARRAY_FLOAT64
, (0.0,)) * len(me
.vertices
) * 3
861 me
.vertices
.foreach_get("co", t_co
)
862 elem_data_single_float64_array(geom
, b
"Vertices", chain(*vcos_transformed_gen(t_co
, geom_mat_co
)))
867 # We do loose edges as two-vertices faces, if enabled...
869 # Note we have to process Edges in the same time, as they are based on poly's loops...
870 loop_nbr
= len(me
.loops
)
871 t_pvi
= array
.array(data_types
.ARRAY_INT32
, (0,)) * loop_nbr
872 t_ls
= [None] * len(me
.polygons
)
874 me
.loops
.foreach_get("vertex_index", t_pvi
)
875 me
.polygons
.foreach_get("loop_start", t_ls
)
877 # Add "fake" faces for loose edges.
878 if scene_data
.settings
.use_mesh_edges
:
879 t_le
= tuple(e
.vertices
for e
in me
.edges
if e
.is_loose
)
880 t_pvi
.extend(chain(*t_le
))
881 t_ls
.extend(range(loop_nbr
, loop_nbr
+ len(t_le
), 2))
885 # Note: Edges are represented as a loop here: each edge uses a single index, which refers to the polygon array.
886 # The edge is made by the vertex indexed py this polygon's point and the next one on the same polygon.
887 # Advantage: Only one index per edge.
888 # Drawback: Only polygon's edges can be represented (that's why we have to add fake two-verts polygons
890 # We also have to store a mapping from real edges to their indices in this array, for edge-mapped data
891 # (like e.g. crease).
892 t_eli
= array
.array(data_types
.ARRAY_INT32
)
897 todo_edges
= [None] * len(me
.edges
) * 2
898 # Sigh, cannot access edge.key through foreach_get... :/
899 me
.edges
.foreach_get("vertices", todo_edges
)
900 todo_edges
= set((v1
, v2
) if v1
< v2
else (v2
, v1
) for v1
, v2
in zip(*(iter(todo_edges
),) * 2))
903 vi
= vi_start
= t_pvi
[0]
904 for li_next
, vi_next
in enumerate(t_pvi
[1:] + t_pvi
[:1], start
=1):
905 if li_next
in t_ls
: # End of a poly's loop.
911 e_key
= (vi
, vi2
) if vi
< vi2
else (vi2
, vi
)
912 if e_key
in todo_edges
:
914 todo_edges
.remove(e_key
)
915 edges_map
[e_key
] = edges_nbr
922 # We have to ^-1 last index of each loop.
926 # And finally we can write data!
927 elem_data_single_int32_array(geom
, b
"PolygonVertexIndex", t_pvi
)
928 elem_data_single_int32_array(geom
, b
"Edges", t_eli
)
936 if smooth_type
in {'FACE', 'EDGE'}:
939 if smooth_type
== 'FACE':
940 t_ps
= array
.array(data_types
.ARRAY_INT32
, (0,)) * len(me
.polygons
)
941 me
.polygons
.foreach_get("use_smooth", t_ps
)
944 # Write Edge Smoothing.
945 # Note edge is sharp also if it's used by more than two faces, or one of its faces is flat.
946 t_ps
= array
.array(data_types
.ARRAY_INT32
, (0,)) * edges_nbr
948 temp_sharp_edges
= {}
949 for p
in me
.polygons
:
951 sharp_edges
.update(p
.edge_keys
)
953 for k
in p
.edge_keys
:
954 if temp_sharp_edges
.setdefault(k
, 0) > 1:
957 temp_sharp_edges
[k
] += 1
960 if e
.key
not in edges_map
:
961 continue # Only loose edges, in theory!
962 t_ps
[edges_map
[e
.key
]] = not (e
.use_edge_sharp
or (e
.key
in sharp_edges
))
964 lay_smooth
= elem_data_single_int32(geom
, b
"LayerElementSmoothing", 0)
965 elem_data_single_int32(lay_smooth
, b
"Version", FBX_GEOMETRY_SMOOTHING_VERSION
)
966 elem_data_single_string(lay_smooth
, b
"Name", b
"")
967 elem_data_single_string(lay_smooth
, b
"MappingInformationType", _map
)
968 elem_data_single_string(lay_smooth
, b
"ReferenceInformationType", b
"Direct")
969 elem_data_single_int32_array(lay_smooth
, b
"Smoothing", t_ps
) # Sight, int32 for bool...
972 # TODO: Edge crease (LayerElementCrease).
974 # And we are done with edges!
980 # NOTE: this is not supported by importer currently.
981 # XXX Official docs says normals should use IndexToDirect,
982 # but this does not seem well supported by apps currently...
983 me
.calc_normals_split()
985 t_ln
= array
.array(data_types
.ARRAY_FLOAT64
, (0.0,)) * len(me
.loops
) * 3
986 me
.loops
.foreach_get("normal", t_ln
)
987 t_ln
= nors_transformed_gen(t_ln
, geom_mat_no
)
989 t_ln
= tuple(t_ln
) # No choice... :/
991 lay_nor
= elem_data_single_int32(geom
, b
"LayerElementNormal", 0)
992 elem_data_single_int32(lay_nor
, b
"Version", FBX_GEOMETRY_NORMAL_VERSION
)
993 elem_data_single_string(lay_nor
, b
"Name", b
"")
994 elem_data_single_string(lay_nor
, b
"MappingInformationType", b
"ByPolygonVertex")
995 elem_data_single_string(lay_nor
, b
"ReferenceInformationType", b
"IndexToDirect")
997 ln2idx
= tuple(set(t_ln
))
998 elem_data_single_float64_array(lay_nor
, b
"Normals", chain(*ln2idx
))
999 # Normal weights, no idea what it is.
1000 # t_lnw = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * len(ln2idx)
1001 # elem_data_single_float64_array(lay_nor, b"NormalsW", t_lnw)
1003 ln2idx
= {nor
: idx
for idx
, nor
in enumerate(ln2idx
)}
1004 elem_data_single_int32_array(lay_nor
, b
"NormalsIndex", (ln2idx
[n
] for n
in t_ln
))
1009 lay_nor
= elem_data_single_int32(geom
, b
"LayerElementNormal", 0)
1010 elem_data_single_int32(lay_nor
, b
"Version", FBX_GEOMETRY_NORMAL_VERSION
)
1011 elem_data_single_string(lay_nor
, b
"Name", b
"")
1012 elem_data_single_string(lay_nor
, b
"MappingInformationType", b
"ByPolygonVertex")
1013 elem_data_single_string(lay_nor
, b
"ReferenceInformationType", b
"Direct")
1014 elem_data_single_float64_array(lay_nor
, b
"Normals", chain(*t_ln
))
1015 # Normal weights, no idea what it is.
1016 # t_ln = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * len(me.loops)
1017 # elem_data_single_float64_array(lay_nor, b"NormalsW", t_ln)
1021 if scene_data
.settings
.use_tspace
:
1022 tspacenumber
= len(me
.uv_layers
)
1024 t_ln
= array
.array(data_types
.ARRAY_FLOAT64
, (0.0,)) * len(me
.loops
) * 3
1025 # t_lnw = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * len(me.loops)
1026 for idx
, uvlayer
in enumerate(me
.uv_layers
):
1028 me
.calc_tangents(name
)
1029 # Loop bitangents (aka binormals).
1030 # NOTE: this is not supported by importer currently.
1031 me
.loops
.foreach_get("bitangent", t_ln
)
1032 lay_nor
= elem_data_single_int32(geom
, b
"LayerElementBinormal", idx
)
1033 elem_data_single_int32(lay_nor
, b
"Version", FBX_GEOMETRY_BINORMAL_VERSION
)
1034 elem_data_single_string_unicode(lay_nor
, b
"Name", name
)
1035 elem_data_single_string(lay_nor
, b
"MappingInformationType", b
"ByPolygonVertex")
1036 elem_data_single_string(lay_nor
, b
"ReferenceInformationType", b
"Direct")
1037 elem_data_single_float64_array(lay_nor
, b
"Binormals",
1038 chain(*nors_transformed_gen(t_ln
, geom_mat_no
)))
1039 # Binormal weights, no idea what it is.
1040 # elem_data_single_float64_array(lay_nor, b"BinormalsW", t_lnw)
1043 # NOTE: this is not supported by importer currently.
1044 me
.loops
.foreach_get("tangent", t_ln
)
1045 lay_nor
= elem_data_single_int32(geom
, b
"LayerElementTangent", idx
)
1046 elem_data_single_int32(lay_nor
, b
"Version", FBX_GEOMETRY_TANGENT_VERSION
)
1047 elem_data_single_string_unicode(lay_nor
, b
"Name", name
)
1048 elem_data_single_string(lay_nor
, b
"MappingInformationType", b
"ByPolygonVertex")
1049 elem_data_single_string(lay_nor
, b
"ReferenceInformationType", b
"Direct")
1050 elem_data_single_float64_array(lay_nor
, b
"Tangents",
1051 chain(*nors_transformed_gen(t_ln
, geom_mat_no
)))
1052 # Tangent weights, no idea what it is.
1053 # elem_data_single_float64_array(lay_nor, b"TangentsW", t_lnw)
1059 me
.free_normals_split()
1061 # Write VertexColor Layers.
1062 vcolnumber
= len(me
.vertex_colors
)
1064 def _coltuples_gen(raw_cols
):
1065 return zip(*(iter(raw_cols
),) * 4)
1067 t_lc
= array
.array(data_types
.ARRAY_FLOAT64
, (0.0,)) * len(me
.loops
) * 4
1068 for colindex
, collayer
in enumerate(me
.vertex_colors
):
1069 collayer
.data
.foreach_get("color", t_lc
)
1070 lay_vcol
= elem_data_single_int32(geom
, b
"LayerElementColor", colindex
)
1071 elem_data_single_int32(lay_vcol
, b
"Version", FBX_GEOMETRY_VCOLOR_VERSION
)
1072 elem_data_single_string_unicode(lay_vcol
, b
"Name", collayer
.name
)
1073 elem_data_single_string(lay_vcol
, b
"MappingInformationType", b
"ByPolygonVertex")
1074 elem_data_single_string(lay_vcol
, b
"ReferenceInformationType", b
"IndexToDirect")
1076 col2idx
= tuple(set(_coltuples_gen(t_lc
)))
1077 elem_data_single_float64_array(lay_vcol
, b
"Colors", chain(*col2idx
)) # Flatten again...
1079 col2idx
= {col
: idx
for idx
, col
in enumerate(col2idx
)}
1080 elem_data_single_int32_array(lay_vcol
, b
"ColorIndex", (col2idx
[c
] for c
in _coltuples_gen(t_lc
)))
1086 # Note: LayerElementTexture is deprecated since FBX 2011 - luckily!
1087 # Textures are now only related to materials, in FBX!
1088 uvnumber
= len(me
.uv_layers
)
1090 def _uvtuples_gen(raw_uvs
):
1091 return zip(*(iter(raw_uvs
),) * 2)
1093 t_luv
= array
.array(data_types
.ARRAY_FLOAT64
, (0.0,)) * len(me
.loops
) * 2
1094 for uvindex
, uvlayer
in enumerate(me
.uv_layers
):
1095 uvlayer
.data
.foreach_get("uv", t_luv
)
1096 lay_uv
= elem_data_single_int32(geom
, b
"LayerElementUV", uvindex
)
1097 elem_data_single_int32(lay_uv
, b
"Version", FBX_GEOMETRY_UV_VERSION
)
1098 elem_data_single_string_unicode(lay_uv
, b
"Name", uvlayer
.name
)
1099 elem_data_single_string(lay_uv
, b
"MappingInformationType", b
"ByPolygonVertex")
1100 elem_data_single_string(lay_uv
, b
"ReferenceInformationType", b
"IndexToDirect")
1102 uv2idx
= tuple(set(_uvtuples_gen(t_luv
)))
1103 elem_data_single_float64_array(lay_uv
, b
"UV", chain(*uv2idx
)) # Flatten again...
1105 uv2idx
= {uv
: idx
for idx
, uv
in enumerate(uv2idx
)}
1106 elem_data_single_int32_array(lay_uv
, b
"UVIndex", (uv2idx
[uv
] for uv
in _uvtuples_gen(t_luv
)))
1112 me_fbxmats_idx
= scene_data
.mesh_mat_indices
.get(me
)
1113 if me_fbxmats_idx
is not None:
1114 me_blmats
= me
.materials
1115 if me_fbxmats_idx
and me_blmats
:
1116 lay_mat
= elem_data_single_int32(geom
, b
"LayerElementMaterial", 0)
1117 elem_data_single_int32(lay_mat
, b
"Version", FBX_GEOMETRY_MATERIAL_VERSION
)
1118 elem_data_single_string(lay_mat
, b
"Name", b
"")
1119 nbr_mats
= len(me_fbxmats_idx
)
1121 t_pm
= array
.array(data_types
.ARRAY_INT32
, (0,)) * len(me
.polygons
)
1122 me
.polygons
.foreach_get("material_index", t_pm
)
1124 # We have to validate mat indices, and map them to FBX indices.
1125 # Note a mat might not be in me_fbxmats_idx (e.g. node mats are ignored).
1126 blmats_to_fbxmats_idxs
= [me_fbxmats_idx
[m
] for m
in me_blmats
if m
in me_fbxmats_idx
]
1127 mat_idx_limit
= len(blmats_to_fbxmats_idxs
)
1128 def_mat
= blmats_to_fbxmats_idxs
[0]
1129 _gen
= (blmats_to_fbxmats_idxs
[m
] if m
< mat_idx_limit
else def_mat
for m
in t_pm
)
1130 t_pm
= array
.array(data_types
.ARRAY_INT32
, _gen
)
1132 elem_data_single_string(lay_mat
, b
"MappingInformationType", b
"ByPolygon")
1133 # XXX Logically, should be "Direct" reference type, since we do not have any index array, and have one
1134 # value per polygon...
1135 # But looks like FBX expects it to be IndexToDirect here (maybe because materials are already
1136 # indices??? *sigh*).
1137 elem_data_single_string(lay_mat
, b
"ReferenceInformationType", b
"IndexToDirect")
1138 elem_data_single_int32_array(lay_mat
, b
"Materials", t_pm
)
1141 elem_data_single_string(lay_mat
, b
"MappingInformationType", b
"AllSame")
1142 elem_data_single_string(lay_mat
, b
"ReferenceInformationType", b
"IndexToDirect")
1143 elem_data_single_int32_array(lay_mat
, b
"Materials", [0])
1145 # And the "layer TOC"...
1147 layer
= elem_data_single_int32(geom
, b
"Layer", 0)
1148 elem_data_single_int32(layer
, b
"Version", FBX_GEOMETRY_LAYER_VERSION
)
1150 lay_nor
= elem_empty(layer
, b
"LayerElement")
1151 elem_data_single_string(lay_nor
, b
"Type", b
"LayerElementNormal")
1152 elem_data_single_int32(lay_nor
, b
"TypedIndex", 0)
1154 lay_binor
= elem_empty(layer
, b
"LayerElement")
1155 elem_data_single_string(lay_binor
, b
"Type", b
"LayerElementBinormal")
1156 elem_data_single_int32(lay_binor
, b
"TypedIndex", 0)
1157 lay_tan
= elem_empty(layer
, b
"LayerElement")
1158 elem_data_single_string(lay_tan
, b
"Type", b
"LayerElementTangent")
1159 elem_data_single_int32(lay_tan
, b
"TypedIndex", 0)
1160 if smooth_type
in {'FACE', 'EDGE'}:
1161 lay_smooth
= elem_empty(layer
, b
"LayerElement")
1162 elem_data_single_string(lay_smooth
, b
"Type", b
"LayerElementSmoothing")
1163 elem_data_single_int32(lay_smooth
, b
"TypedIndex", 0)
1165 lay_vcol
= elem_empty(layer
, b
"LayerElement")
1166 elem_data_single_string(lay_vcol
, b
"Type", b
"LayerElementColor")
1167 elem_data_single_int32(lay_vcol
, b
"TypedIndex", 0)
1169 lay_uv
= elem_empty(layer
, b
"LayerElement")
1170 elem_data_single_string(lay_uv
, b
"Type", b
"LayerElementUV")
1171 elem_data_single_int32(lay_uv
, b
"TypedIndex", 0)
1172 if me_fbxmats_idx
is not None:
1173 lay_mat
= elem_empty(layer
, b
"LayerElement")
1174 elem_data_single_string(lay_mat
, b
"Type", b
"LayerElementMaterial")
1175 elem_data_single_int32(lay_mat
, b
"TypedIndex", 0)
1177 # Add other uv and/or vcol layers...
1178 for vcolidx
, uvidx
, tspaceidx
in zip_longest(range(1, vcolnumber
), range(1, uvnumber
), range(1, tspacenumber
),
1180 layer
= elem_data_single_int32(geom
, b
"Layer", max(vcolidx
, uvidx
))
1181 elem_data_single_int32(layer
, b
"Version", FBX_GEOMETRY_LAYER_VERSION
)
1183 lay_vcol
= elem_empty(layer
, b
"LayerElement")
1184 elem_data_single_string(lay_vcol
, b
"Type", b
"LayerElementColor")
1185 elem_data_single_int32(lay_vcol
, b
"TypedIndex", vcolidx
)
1187 lay_uv
= elem_empty(layer
, b
"LayerElement")
1188 elem_data_single_string(lay_uv
, b
"Type", b
"LayerElementUV")
1189 elem_data_single_int32(lay_uv
, b
"TypedIndex", uvidx
)
1191 lay_binor
= elem_empty(layer
, b
"LayerElement")
1192 elem_data_single_string(lay_binor
, b
"Type", b
"LayerElementBinormal")
1193 elem_data_single_int32(lay_binor
, b
"TypedIndex", tspaceidx
)
1194 lay_tan
= elem_empty(layer
, b
"LayerElement")
1195 elem_data_single_string(lay_tan
, b
"Type", b
"LayerElementTangent")
1196 elem_data_single_int32(lay_tan
, b
"TypedIndex", tspaceidx
)
1199 fbx_data_mesh_shapes_elements(root
, me_obj
, me
, scene_data
, tmpl
, props
)
1201 elem_props_template_finalize(tmpl
, props
)
1202 done_meshes
.add(me_key
)
1205 def check_skip_material(mat
):
1206 """Simple helper to check whether we actually support exporting that material or not"""
1207 return mat
.type not in {'SURFACE'}
1210 def fbx_data_material_elements(root
, mat
, scene_data
):
1212 Write the Material data block.
1214 ambient_color
= (0.0, 0.0, 0.0)
1215 if scene_data
.data_world
:
1216 ambient_color
= next(iter(scene_data
.data_world
.keys())).ambient_color
1218 mat_key
, _objs
= scene_data
.data_materials
[mat
]
1219 skip_mat
= check_skip_material(mat
)
1220 node_mat
= mat
.use_nodes
1223 if not skip_mat
and not node_mat
and mat
.specular_shader
not in {'COOKTORR', 'PHONG', 'BLINN'}:
1224 mat_type
= b
"Lambert"
1226 fbx_mat
= elem_data_single_int64(root
, b
"Material", get_fbx_uuid_from_key(mat_key
))
1227 fbx_mat
.add_string(fbx_name_class(mat
.name
.encode(), b
"Material"))
1228 fbx_mat
.add_string(b
"")
1230 elem_data_single_int32(fbx_mat
, b
"Version", FBX_MATERIAL_VERSION
)
1231 # those are not yet properties, it seems...
1232 elem_data_single_string(fbx_mat
, b
"ShadingModel", mat_type
)
1233 elem_data_single_int32(fbx_mat
, b
"MultiLayer", 0) # Should be bool...
1235 tmpl
= elem_props_template_init(scene_data
.templates
, b
"Material")
1236 props
= elem_properties(fbx_mat
)
1239 elem_props_template_set(tmpl
, props
, "p_string", b
"ShadingModel", mat_type
.decode())
1240 elem_props_template_set(tmpl
, props
, "p_color", b
"DiffuseColor", mat
.diffuse_color
)
1241 elem_props_template_set(tmpl
, props
, "p_number", b
"DiffuseFactor", mat
.diffuse_intensity
)
1243 elem_props_template_set(tmpl
, props
, "p_color", b
"EmissiveColor", mat
.diffuse_color
)
1244 elem_props_template_set(tmpl
, props
, "p_number", b
"EmissiveFactor", mat
.emit
)
1245 elem_props_template_set(tmpl
, props
, "p_color", b
"AmbientColor", ambient_color
)
1246 elem_props_template_set(tmpl
, props
, "p_number", b
"AmbientFactor", mat
.ambient
)
1247 elem_props_template_set(tmpl
, props
, "p_color", b
"TransparentColor",
1248 mat
.diffuse_color
if mat
.use_transparency
else (1.0, 1.0, 1.0))
1249 elem_props_template_set(tmpl
, props
, "p_number", b
"TransparencyFactor",
1250 1.0 - mat
.alpha
if mat
.use_transparency
else 0.0)
1251 elem_props_template_set(tmpl
, props
, "p_number", b
"Opacity", mat
.alpha
if mat
.use_transparency
else 1.0)
1252 elem_props_template_set(tmpl
, props
, "p_vector_3d", b
"NormalMap", (0.0, 0.0, 0.0))
1253 # Not sure about those...
1255 b"Bump": ((0.0, 0.0, 0.0), "p_vector_3d"),
1256 b"BumpFactor": (1.0, "p_double"),
1257 b"DisplacementColor": ((0.0, 0.0, 0.0), "p_color_rgb"),
1258 b"DisplacementFactor": (0.0, "p_double"),
1260 if mat_type
== b
"Phong":
1261 elem_props_template_set(tmpl
, props
, "p_color", b
"SpecularColor", mat
.specular_color
)
1262 elem_props_template_set(tmpl
, props
, "p_number", b
"SpecularFactor", mat
.specular_intensity
/ 2.0)
1263 # See Material template about those two!
1264 elem_props_template_set(tmpl
, props
, "p_number", b
"Shininess", (mat
.specular_hardness
- 1.0) / 5.10)
1265 elem_props_template_set(tmpl
, props
, "p_number", b
"ShininessExponent", (mat
.specular_hardness
- 1.0) / 5.10)
1266 elem_props_template_set(tmpl
, props
, "p_color", b
"ReflectionColor", mat
.mirror_color
)
1267 elem_props_template_set(tmpl
, props
, "p_number", b
"ReflectionFactor",
1268 mat
.raytrace_mirror
.reflect_factor
if mat
.raytrace_mirror
.use
else 0.0)
1270 elem_props_template_finalize(tmpl
, props
)
1272 # Custom properties.
1273 if scene_data
.settings
.use_custom_props
:
1274 fbx_data_element_custom_properties(props
, mat
)
1277 def _gen_vid_path(img
, scene_data
):
1278 msetts
= scene_data
.settings
.media_settings
1279 fname_rel
= bpy_extras
.io_utils
.path_reference(img
.filepath
, msetts
.base_src
, msetts
.base_dst
, msetts
.path_mode
,
1280 msetts
.subdir
, msetts
.copy_set
, img
.library
)
1281 fname_abs
= os
.path
.normpath(os
.path
.abspath(os
.path
.join(msetts
.base_dst
, fname_rel
)))
1282 return fname_abs
, fname_rel
1285 def fbx_data_texture_file_elements(root
, tex
, scene_data
):
1287 Write the (file) Texture data block.
1289 # XXX All this is very fuzzy to me currently...
1290 # Textures do not seem to use properties as much as they could.
1291 # For now assuming most logical and simple stuff.
1293 tex_key
, _mats
= scene_data
.data_textures
[tex
]
1294 img
= tex
.texture
.image
1295 fname_abs
, fname_rel
= _gen_vid_path(img
, scene_data
)
1297 fbx_tex
= elem_data_single_int64(root
, b
"Texture", get_fbx_uuid_from_key(tex_key
))
1298 fbx_tex
.add_string(fbx_name_class(tex
.name
.encode(), b
"Texture"))
1299 fbx_tex
.add_string(b
"")
1301 elem_data_single_string(fbx_tex
, b
"Type", b
"TextureVideoClip")
1302 elem_data_single_int32(fbx_tex
, b
"Version", FBX_TEXTURE_VERSION
)
1303 elem_data_single_string(fbx_tex
, b
"TextureName", fbx_name_class(tex
.name
.encode(), b
"Texture"))
1304 elem_data_single_string(fbx_tex
, b
"Media", fbx_name_class(img
.name
.encode(), b
"Video"))
1305 elem_data_single_string_unicode(fbx_tex
, b
"FileName", fname_abs
)
1306 elem_data_single_string_unicode(fbx_tex
, b
"RelativeFilename", fname_rel
)
1308 alpha_source
= 0 # None
1310 if tex
.texture
.use_calculate_alpha
:
1311 alpha_source
= 1 # RGBIntensity as alpha.
1313 alpha_source
= 2 # Black, i.e. alpha channel.
1314 # BlendMode not useful for now, only affects layered textures afaics.
1317 if tex
.texture_coords
in {'ORCO'}: # XXX Others?
1318 if tex
.mapping
in {'FLAT'}:
1319 mapping
= 1 # Planar
1320 elif tex
.mapping
in {'CUBE'}:
1322 elif tex
.mapping
in {'TUBE'}:
1323 mapping
= 3 # Cylindrical
1324 elif tex
.mapping
in {'SPHERE'}:
1325 mapping
= 2 # Spherical
1326 elif tex
.texture_coords
in {'UV'}:
1328 # Yuck, UVs are linked by mere names it seems... :/
1329 uvset
= tex
.uv_layer
1330 wrap_mode
= 1 # Clamp
1331 if tex
.texture
.extension
in {'REPEAT'}:
1332 wrap_mode
= 0 # Repeat
1334 tmpl
= elem_props_template_init(scene_data
.templates
, b
"TextureFile")
1335 props
= elem_properties(fbx_tex
)
1336 elem_props_template_set(tmpl
, props
, "p_enum", b
"AlphaSource", alpha_source
)
1337 elem_props_template_set(tmpl
, props
, "p_bool", b
"PremultiplyAlpha",
1338 img
.alpha_mode
in {'STRAIGHT'}) # Or is it PREMUL?
1339 elem_props_template_set(tmpl
, props
, "p_enum", b
"CurrentMappingType", mapping
)
1340 if uvset
is not None:
1341 elem_props_template_set(tmpl
, props
, "p_string", b
"UVSet", uvset
)
1342 elem_props_template_set(tmpl
, props
, "p_enum", b
"WrapModeU", wrap_mode
)
1343 elem_props_template_set(tmpl
, props
, "p_enum", b
"WrapModeV", wrap_mode
)
1344 elem_props_template_set(tmpl
, props
, "p_vector_3d", b
"Translation", tex
.offset
)
1345 elem_props_template_set(tmpl
, props
, "p_vector_3d", b
"Scaling", tex
.scale
)
1346 # UseMaterial should always be ON imho.
1347 elem_props_template_set(tmpl
, props
, "p_bool", b
"UseMaterial", True)
1348 elem_props_template_set(tmpl
, props
, "p_bool", b
"UseMipMap", tex
.texture
.use_mipmap
)
1349 elem_props_template_finalize(tmpl
, props
)
1351 # Custom properties.
1352 if scene_data
.settings
.use_custom_props
:
1353 fbx_data_element_custom_properties(props
, tex
.texture
)
1356 def fbx_data_video_elements(root
, vid
, scene_data
):
1358 Write the actual image data block.
1360 msetts
= scene_data
.settings
.media_settings
1362 vid_key
, _texs
= scene_data
.data_videos
[vid
]
1363 fname_abs
, fname_rel
= _gen_vid_path(vid
, scene_data
)
1365 fbx_vid
= elem_data_single_int64(root
, b
"Video", get_fbx_uuid_from_key(vid_key
))
1366 fbx_vid
.add_string(fbx_name_class(vid
.name
.encode(), b
"Video"))
1367 fbx_vid
.add_string(b
"Clip")
1369 elem_data_single_string(fbx_vid
, b
"Type", b
"Clip")
1372 tmpl
= elem_props_template_init(scene_data
.templates
, b
"Video")
1373 props
= elem_properties(fbx_vid
)
1374 elem_props_template_set(tmpl
, props
, "p_string_url", b
"Path", fname_abs
)
1375 elem_props_template_finalize(tmpl
, props
)
1377 elem_data_single_int32(fbx_vid
, b
"UseMipMap", 0)
1378 elem_data_single_string_unicode(fbx_vid
, b
"Filename", fname_abs
)
1379 elem_data_single_string_unicode(fbx_vid
, b
"RelativeFilename", fname_rel
)
1381 if scene_data
.settings
.media_settings
.embed_textures
:
1382 if vid
.packed_file
is not None:
1383 # We only ever embed a given file once!
1384 if fname_abs
not in msetts
.embedded_set
:
1385 elem_data_single_bytes(fbx_vid
, b
"Content", vid
.packed_file
.data
)
1386 msetts
.embedded_set
.add(fname_abs
)
1388 filepath
= bpy
.path
.abspath(vid
.filepath
)
1389 # We only ever embed a given file once!
1390 if filepath
not in msetts
.embedded_set
:
1392 with
open(filepath
, 'br') as f
:
1393 elem_data_single_bytes(fbx_vid
, b
"Content", f
.read())
1394 except Exception as e
:
1395 print("WARNING: embedding file {} failed ({})".format(filepath
, e
))
1396 elem_data_single_bytes(fbx_vid
, b
"Content", b
"")
1397 msetts
.embedded_set
.add(filepath
)
1398 # Looks like we'd rather not write any 'Content' element in this case (see T44442).
1399 # Sounds suspect, but let's try it!
1401 #~ elem_data_single_bytes(fbx_vid, b"Content", b"")
1404 def fbx_data_armature_elements(root
, arm_obj
, scene_data
):
1407 * Bones "data" (NodeAttribute::LimbNode, contains pretty much nothing!).
1408 * Deformers (i.e. Skin), bind between an armature and a mesh.
1409 ** SubDeformers (i.e. Cluster), one per bone/vgroup pair.
1411 Note armature itself has no data, it is a mere "Null" Model...
1413 mat_world_arm
= arm_obj
.fbx_object_matrix(scene_data
, global_space
=True)
1414 bones
= tuple(bo_obj
for bo_obj
in arm_obj
.bones
if bo_obj
in scene_data
.objects
)
1416 bone_radius_scale
= 33.0
1419 for bo_obj
in bones
:
1421 bo_data_key
= scene_data
.data_bones
[bo_obj
]
1422 fbx_bo
= elem_data_single_int64(root
, b
"NodeAttribute", get_fbx_uuid_from_key(bo_data_key
))
1423 fbx_bo
.add_string(fbx_name_class(bo
.name
.encode(), b
"NodeAttribute"))
1424 fbx_bo
.add_string(b
"LimbNode")
1425 elem_data_single_string(fbx_bo
, b
"TypeFlags", b
"Skeleton")
1427 tmpl
= elem_props_template_init(scene_data
.templates
, b
"Bone")
1428 props
= elem_properties(fbx_bo
)
1429 elem_props_template_set(tmpl
, props
, "p_double", b
"Size", bo
.head_radius
* bone_radius_scale
)
1430 elem_props_template_finalize(tmpl
, props
)
1432 # Custom properties.
1433 if scene_data
.settings
.use_custom_props
:
1434 fbx_data_element_custom_properties(props
, bo
)
1436 # Store Blender bone length - XXX Not much useful actually :/
1437 # (LimbLength can't be used because it is a scale factor 0-1 for the parent-child distance:
1438 # http://docs.autodesk.com/FBX/2014/ENU/FBX-SDK-Documentation/cpp_ref/class_fbx_skeleton.html#a9bbe2a70f4ed82cd162620259e649f0f )
1439 # elem_props_set(props, "p_double", "BlenderBoneLength".encode(), (bo.tail_local - bo.head_local).length, custom=True)
1441 # Skin deformers and BindPoses.
1442 # Note: we might also use Deformers for our "parent to vertex" stuff???
1443 deformer
= scene_data
.data_deformers_skin
.get(arm_obj
, None)
1444 if deformer
is not None:
1445 for me
, (skin_key
, ob_obj
, clusters
) in deformer
.items():
1447 mat_world_obj
, mat_world_bones
= fbx_data_bindpose_element(root
, ob_obj
, me
, scene_data
,
1448 arm_obj
, mat_world_arm
, bones
)
1451 fbx_skin
= elem_data_single_int64(root
, b
"Deformer", get_fbx_uuid_from_key(skin_key
))
1452 fbx_skin
.add_string(fbx_name_class(arm_obj
.name
.encode(), b
"Deformer"))
1453 fbx_skin
.add_string(b
"Skin")
1455 elem_data_single_int32(fbx_skin
, b
"Version", FBX_DEFORMER_SKIN_VERSION
)
1456 elem_data_single_float64(fbx_skin
, b
"Link_DeformAcuracy", 50.0) # Only vague idea what it is...
1458 # Pre-process vertex weights (also to check vertices assigned ot more than four bones).
1460 bo_vg_idx
= {bo_obj
.bdata
.name
: ob
.vertex_groups
[bo_obj
.bdata
.name
].index
1461 for bo_obj
in clusters
.keys() if bo_obj
.bdata
.name
in ob
.vertex_groups
}
1462 valid_idxs
= set(bo_vg_idx
.values())
1463 vgroups
= {vg
.index
: OrderedDict() for vg
in ob
.vertex_groups
}
1464 verts_vgroups
= (sorted(((vg
.group
, vg
.weight
) for vg
in v
.groups
if vg
.weight
and vg
.group
in valid_idxs
),
1465 key
=lambda e
: e
[1], reverse
=True)
1466 for v
in me
.vertices
)
1467 for idx
, vgs
in enumerate(verts_vgroups
):
1468 for vg_idx
, w
in vgs
:
1469 vgroups
[vg_idx
][idx
] = w
1471 for bo_obj
, clstr_key
in clusters
.items():
1473 # Find which vertices are affected by this bone/vgroup pair, and matching weights.
1474 # Note we still write a cluster for bones not affecting the mesh, to get 'rest pose' data
1475 # (the TransformBlah matrices).
1476 vg_idx
= bo_vg_idx
.get(bo
.name
, None)
1477 indices
, weights
= ((), ()) if vg_idx
is None or not vgroups
[vg_idx
] else zip(*vgroups
[vg_idx
].items())
1479 # Create the cluster.
1480 fbx_clstr
= elem_data_single_int64(root
, b
"Deformer", get_fbx_uuid_from_key(clstr_key
))
1481 fbx_clstr
.add_string(fbx_name_class(bo
.name
.encode(), b
"SubDeformer"))
1482 fbx_clstr
.add_string(b
"Cluster")
1484 elem_data_single_int32(fbx_clstr
, b
"Version", FBX_DEFORMER_CLUSTER_VERSION
)
1485 # No idea what that user data might be...
1486 fbx_userdata
= elem_data_single_string(fbx_clstr
, b
"UserData", b
"")
1487 fbx_userdata
.add_string(b
"")
1489 elem_data_single_int32_array(fbx_clstr
, b
"Indexes", indices
)
1490 elem_data_single_float64_array(fbx_clstr
, b
"Weights", weights
)
1491 # Transform, TransformLink and TransformAssociateModel matrices...
1492 # They seem to be doublons of BindPose ones??? Have armature (associatemodel) in addition, though.
1493 # WARNING! Even though official FBX API presents Transform in global space,
1494 # **it is stored in bone space in FBX data!** See:
1495 # http://area.autodesk.com/forum/autodesk-fbx/fbx-sdk/why-the-values-return-
1496 # by-fbxcluster-gettransformmatrix-x-not-same-with-the-value-in-ascii-fbx-file/
1497 elem_data_single_float64_array(fbx_clstr
, b
"Transform",
1498 matrix4_to_array(mat_world_bones
[bo_obj
].inverted_safe() * mat_world_obj
))
1499 elem_data_single_float64_array(fbx_clstr
, b
"TransformLink", matrix4_to_array(mat_world_bones
[bo_obj
]))
1500 elem_data_single_float64_array(fbx_clstr
, b
"TransformAssociateModel", matrix4_to_array(mat_world_arm
))
1503 def fbx_data_leaf_bone_elements(root
, scene_data
):
1504 # Write a dummy leaf bone that is used by applications to show the length of the last bone in a chain
1505 for (node_name
, _par_uuid
, node_uuid
, attr_uuid
, matrix
, hide
, size
) in scene_data
.data_leaf_bones
:
1507 fbx_bo
= elem_data_single_int64(root
, b
"NodeAttribute", attr_uuid
)
1508 fbx_bo
.add_string(fbx_name_class(node_name
.encode(), b
"NodeAttribute"))
1509 fbx_bo
.add_string(b
"LimbNode")
1510 elem_data_single_string(fbx_bo
, b
"TypeFlags", b
"Skeleton")
1512 tmpl
= elem_props_template_init(scene_data
.templates
, b
"Bone")
1513 props
= elem_properties(fbx_bo
)
1514 elem_props_template_set(tmpl
, props
, "p_double", b
"Size", size
)
1515 elem_props_template_finalize(tmpl
, props
)
1518 model
= elem_data_single_int64(root
, b
"Model", node_uuid
)
1519 model
.add_string(fbx_name_class(node_name
.encode(), b
"Model"))
1520 model
.add_string(b
"LimbNode")
1522 elem_data_single_int32(model
, b
"Version", FBX_MODELS_VERSION
)
1524 # Object transform info.
1525 loc
, rot
, scale
= matrix
.decompose()
1526 rot
= rot
.to_euler('XYZ')
1527 rot
= tuple(convert_rad_to_deg_iter(rot
))
1529 tmpl
= elem_props_template_init(scene_data
.templates
, b
"Model")
1530 # For now add only loc/rot/scale...
1531 props
= elem_properties(model
)
1532 # Generated leaf bones are obviously never animated!
1533 elem_props_template_set(tmpl
, props
, "p_lcl_translation", b
"Lcl Translation", loc
)
1534 elem_props_template_set(tmpl
, props
, "p_lcl_rotation", b
"Lcl Rotation", rot
)
1535 elem_props_template_set(tmpl
, props
, "p_lcl_scaling", b
"Lcl Scaling", scale
)
1536 elem_props_template_set(tmpl
, props
, "p_visibility", b
"Visibility", float(not hide
))
1538 # Absolutely no idea what this is, but seems mandatory for validity of the file, and defaults to
1539 # invalid -1 value...
1540 elem_props_template_set(tmpl
, props
, "p_integer", b
"DefaultAttributeIndex", 0)
1542 elem_props_template_set(tmpl
, props
, "p_enum", b
"InheritType", 1) # RSrs
1544 # Those settings would obviously need to be edited in a complete version of the exporter, may depends on
1546 elem_data_single_int32(model
, b
"MultiLayer", 0)
1547 elem_data_single_int32(model
, b
"MultiTake", 0)
1548 elem_data_single_bool(model
, b
"Shading", True)
1549 elem_data_single_string(model
, b
"Culling", b
"CullingOff")
1551 elem_props_template_finalize(tmpl
, props
)
1554 def fbx_data_object_elements(root
, ob_obj
, scene_data
):
1556 Write the Object (Model) data blocks.
1557 Note this "Model" can also be bone or dupli!
1559 obj_type
= b
"Null" # default, sort of empty...
1561 obj_type
= b
"LimbNode"
1562 elif (ob_obj
.type == 'ARMATURE'):
1563 if scene_data
.settings
.armature_nodetype
== 'ROOT':
1565 elif scene_data
.settings
.armature_nodetype
== 'LIMBNODE':
1566 obj_type
= b
"LimbNode"
1567 else: # Default, preferred option...
1569 elif (ob_obj
.type in BLENDER_OBJECT_TYPES_MESHLIKE
):
1571 elif (ob_obj
.type == 'LAMP'):
1573 elif (ob_obj
.type == 'CAMERA'):
1574 obj_type
= b
"Camera"
1575 model
= elem_data_single_int64(root
, b
"Model", ob_obj
.fbx_uuid
)
1576 model
.add_string(fbx_name_class(ob_obj
.name
.encode(), b
"Model"))
1577 model
.add_string(obj_type
)
1579 elem_data_single_int32(model
, b
"Version", FBX_MODELS_VERSION
)
1581 # Object transform info.
1582 loc
, rot
, scale
, matrix
, matrix_rot
= ob_obj
.fbx_object_tx(scene_data
)
1583 rot
= tuple(convert_rad_to_deg_iter(rot
))
1585 tmpl
= elem_props_template_init(scene_data
.templates
, b
"Model")
1586 # For now add only loc/rot/scale...
1587 props
= elem_properties(model
)
1588 elem_props_template_set(tmpl
, props
, "p_lcl_translation", b
"Lcl Translation", loc
,
1589 animatable
=True, animated
=((ob_obj
.key
, "Lcl Translation") in scene_data
.animated
))
1590 elem_props_template_set(tmpl
, props
, "p_lcl_rotation", b
"Lcl Rotation", rot
,
1591 animatable
=True, animated
=((ob_obj
.key
, "Lcl Rotation") in scene_data
.animated
))
1592 elem_props_template_set(tmpl
, props
, "p_lcl_scaling", b
"Lcl Scaling", scale
,
1593 animatable
=True, animated
=((ob_obj
.key
, "Lcl Scaling") in scene_data
.animated
))
1594 elem_props_template_set(tmpl
, props
, "p_visibility", b
"Visibility", float(not ob_obj
.hide
))
1596 # Absolutely no idea what this is, but seems mandatory for validity of the file, and defaults to
1597 # invalid -1 value...
1598 elem_props_template_set(tmpl
, props
, "p_integer", b
"DefaultAttributeIndex", 0)
1600 elem_props_template_set(tmpl
, props
, "p_enum", b
"InheritType", 1) # RSrs
1602 # Custom properties.
1603 if scene_data
.settings
.use_custom_props
:
1604 fbx_data_element_custom_properties(props
, ob_obj
.bdata
)
1606 # Those settings would obviously need to be edited in a complete version of the exporter, may depends on
1608 elem_data_single_int32(model
, b
"MultiLayer", 0)
1609 elem_data_single_int32(model
, b
"MultiTake", 0)
1610 elem_data_single_bool(model
, b
"Shading", True)
1611 elem_data_single_string(model
, b
"Culling", b
"CullingOff")
1613 if obj_type
== b
"Camera":
1614 # Why, oh why are FBX cameras such a mess???
1615 # And WHY add camera data HERE??? Not even sure this is needed...
1616 render
= scene_data
.scene
.render
1617 width
= render
.resolution_x
* 1.0
1618 height
= render
.resolution_y
* 1.0
1619 elem_props_template_set(tmpl
, props
, "p_enum", b
"ResolutionMode", 0) # Don't know what it means
1620 elem_props_template_set(tmpl
, props
, "p_double", b
"AspectW", width
)
1621 elem_props_template_set(tmpl
, props
, "p_double", b
"AspectH", height
)
1622 elem_props_template_set(tmpl
, props
, "p_bool", b
"ViewFrustum", True)
1623 elem_props_template_set(tmpl
, props
, "p_enum", b
"BackgroundMode", 0) # Don't know what it means
1624 elem_props_template_set(tmpl
, props
, "p_bool", b
"ForegroundTransparent", True)
1626 elem_props_template_finalize(tmpl
, props
)
1629 def fbx_data_animation_elements(root
, scene_data
):
1631 Write animation data.
1633 animations
= scene_data
.animations
1636 scene
= scene_data
.scene
1638 fps
= scene
.render
.fps
/ scene
.render
.fps_base
1640 def keys_to_ktimes(keys
):
1641 return (int(v
) for v
in convert_sec_to_ktime_iter((f
/ fps
for f
, _v
in keys
)))
1644 for astack_key
, alayers
, alayer_key
, name
, f_start
, f_end
in animations
:
1645 astack
= elem_data_single_int64(root
, b
"AnimationStack", get_fbx_uuid_from_key(astack_key
))
1646 astack
.add_string(fbx_name_class(name
, b
"AnimStack"))
1647 astack
.add_string(b
"")
1649 astack_tmpl
= elem_props_template_init(scene_data
.templates
, b
"AnimationStack")
1650 astack_props
= elem_properties(astack
)
1651 r
= scene_data
.scene
.render
1652 fps
= r
.fps
/ r
.fps_base
1653 start
= int(convert_sec_to_ktime(f_start
/ fps
))
1654 end
= int(convert_sec_to_ktime(f_end
/ fps
))
1655 elem_props_template_set(astack_tmpl
, astack_props
, "p_timestamp", b
"LocalStart", start
)
1656 elem_props_template_set(astack_tmpl
, astack_props
, "p_timestamp", b
"LocalStop", end
)
1657 elem_props_template_set(astack_tmpl
, astack_props
, "p_timestamp", b
"ReferenceStart", start
)
1658 elem_props_template_set(astack_tmpl
, astack_props
, "p_timestamp", b
"ReferenceStop", end
)
1659 elem_props_template_finalize(astack_tmpl
, astack_props
)
1661 # For now, only one layer for all animations.
1662 alayer
= elem_data_single_int64(root
, b
"AnimationLayer", get_fbx_uuid_from_key(alayer_key
))
1663 alayer
.add_string(fbx_name_class(name
, b
"AnimLayer"))
1664 alayer
.add_string(b
"")
1666 for ob_obj
, (alayer_key
, acurvenodes
) in alayers
.items():
1668 # alayer = elem_data_single_int64(root, b"AnimationLayer", get_fbx_uuid_from_key(alayer_key))
1669 # alayer.add_string(fbx_name_class(ob_obj.name.encode(), b"AnimLayer"))
1670 # alayer.add_string(b"")
1672 for fbx_prop
, (acurvenode_key
, acurves
, acurvenode_name
) in acurvenodes
.items():
1673 # Animation curve node.
1674 acurvenode
= elem_data_single_int64(root
, b
"AnimationCurveNode", get_fbx_uuid_from_key(acurvenode_key
))
1675 acurvenode
.add_string(fbx_name_class(acurvenode_name
.encode(), b
"AnimCurveNode"))
1676 acurvenode
.add_string(b
"")
1678 acn_tmpl
= elem_props_template_init(scene_data
.templates
, b
"AnimationCurveNode")
1679 acn_props
= elem_properties(acurvenode
)
1681 for fbx_item
, (acurve_key
, def_value
, keys
, _acurve_valid
) in acurves
.items():
1682 elem_props_template_set(acn_tmpl
, acn_props
, "p_number", fbx_item
.encode(),
1683 def_value
, animatable
=True)
1685 # Only create Animation curve if needed!
1687 acurve
= elem_data_single_int64(root
, b
"AnimationCurve", get_fbx_uuid_from_key(acurve_key
))
1688 acurve
.add_string(fbx_name_class(b
"", b
"AnimCurve"))
1689 acurve
.add_string(b
"")
1692 nbr_keys
= len(keys
)
1695 1 << 2 |
# interpolation mode, 1 = constant, 2 = linear, 3 = cubic.
1696 1 << 8 |
# tangent mode, 8 = auto, 9 = TCB, 10 = user, 11 = generic break,
1697 1 << 13 |
# tangent mode, 12 = generic clamp, 13 = generic time independent,
1698 1 << 14 |
# tangent mode, 13 + 14 = generic clamp progressive.
1701 # Maybe values controlling TCB & co???
1702 keyattr_datafloat
= (0.0, 0.0, 9.419963346924634e-30, 0.0)
1704 # And now, the *real* data!
1705 elem_data_single_float64(acurve
, b
"Default", def_value
)
1706 elem_data_single_int32(acurve
, b
"KeyVer", FBX_ANIM_KEY_VERSION
)
1707 elem_data_single_int64_array(acurve
, b
"KeyTime", keys_to_ktimes(keys
))
1708 elem_data_single_float32_array(acurve
, b
"KeyValueFloat", (v
for _f
, v
in keys
))
1709 elem_data_single_int32_array(acurve
, b
"KeyAttrFlags", keyattr_flags
)
1710 elem_data_single_float32_array(acurve
, b
"KeyAttrDataFloat", keyattr_datafloat
)
1711 elem_data_single_int32_array(acurve
, b
"KeyAttrRefCount", (nbr_keys
,))
1713 elem_props_template_finalize(acn_tmpl
, acn_props
)
1716 # ##### Top-level FBX data container. #####
1718 def fbx_mat_properties_from_texture(tex
):
1720 Returns a set of FBX metarial properties that are affected by the given texture.
1721 Quite obviously, this is a fuzzy and far-from-perfect mapping! Amounts of influence are completely lost, e.g.
1722 Note tex is actually expected to be a texture slot.
1724 # Mapping Blender -> FBX (blend_use_name, blend_fact_name, fbx_name).
1726 # Lambert & Phong...
1727 ("diffuse", "diffuse", b
"DiffuseFactor"),
1728 ("color_diffuse", "diffuse_color", b
"DiffuseColor"),
1729 ("alpha", "alpha", b
"TransparencyFactor"),
1730 ("diffuse", "diffuse", b
"TransparentColor"), # Uses diffuse color in Blender!
1731 ("emit", "emit", b
"EmissiveFactor"),
1732 ("diffuse", "diffuse", b
"EmissiveColor"), # Uses diffuse color in Blender!
1733 ("ambient", "ambient", b
"AmbientFactor"),
1734 # ("", "", b"AmbientColor"), # World stuff in Blender, for now ignore...
1735 ("normal", "normal", b
"NormalMap"),
1736 # Note: unsure about those... :/
1737 # ("", "", b"Bump"),
1738 # ("", "", b"BumpFactor"),
1739 # ("", "", b"DisplacementColor"),
1740 # ("", "", b"DisplacementFactor"),
1742 ("specular", "specular", b
"SpecularFactor"),
1743 ("color_spec", "specular_color", b
"SpecularColor"),
1744 # See Material template about those two!
1745 ("hardness", "hardness", b
"Shininess"),
1746 ("hardness", "hardness", b
"ShininessExponent"),
1747 ("mirror", "mirror", b
"ReflectionColor"),
1748 ("raymir", "raymir", b
"ReflectionFactor"),
1751 tex_fbx_props
= set()
1752 for use_map_name
, name_factor
, fbx_prop_name
in blend_to_fbx
:
1753 # Always export enabled textures, even if they have a null influence...
1754 if getattr(tex
, "use_map_" + use_map_name
):
1755 tex_fbx_props
.add(fbx_prop_name
)
1757 return tex_fbx_props
1760 def fbx_skeleton_from_armature(scene
, settings
, arm_obj
, objects
, data_meshes
,
1761 data_bones
, data_deformers_skin
, data_empties
, arm_parents
):
1763 Create skeleton from armature/bones (NodeAttribute/LimbNode and Model/LimbNode), and for each deformed mesh,
1764 create Pose/BindPose(with sub PoseNode) and Deformer/Skin(with Deformer/SubDeformer/Cluster).
1765 Also supports "parent to bone" (simple parent to Model/LimbNode).
1766 arm_parents is a set of tuples (armature, object) for all successful armature bindings.
1768 # We need some data for our armature 'object' too!!!
1769 data_empties
[arm_obj
] = get_blender_empty_key(arm_obj
.bdata
)
1771 arm_data
= arm_obj
.bdata
.data
1772 bones
= OrderedDict()
1773 for bo
in arm_obj
.bones
:
1774 if settings
.use_armature_deform_only
:
1775 if bo
.bdata
.use_deform
:
1778 while bo_par
.is_bone
:
1779 bones
[bo_par
] = True
1780 bo_par
= bo_par
.parent
1781 elif bo
not in bones
: # Do not override if already set in the loop above!
1786 bones
= OrderedDict((bo
, None) for bo
, use
in bones
.items() if use
)
1791 data_bones
.update((bo
, get_blender_bone_key(arm_obj
.bdata
, bo
.bdata
)) for bo
in bones
)
1793 for ob_obj
in objects
:
1794 if not ob_obj
.is_deformed_by_armature(arm_obj
):
1797 # Always handled by an Armature modifier...
1799 for mod
in ob_obj
.bdata
.modifiers
:
1800 if mod
.type not in {'ARMATURE'} or not mod
.object:
1802 # We only support vertex groups binding method, not bone envelopes one!
1803 if mod
.object in {arm_obj
.bdata
, arm_obj
.bdata
.proxy
} and mod
.use_vertex_groups
:
1810 # Now we have a mesh using this armature.
1811 # Note: bindpose have no relations at all (no connections), so no need for any preprocess for them.
1812 # Create skin & clusters relations (note skins are connected to geometry, *not* model!).
1813 _key
, me
, _free
= data_meshes
[ob_obj
]
1814 clusters
= OrderedDict((bo
, get_blender_bone_cluster_key(arm_obj
.bdata
, me
, bo
.bdata
)) for bo
in bones
)
1815 data_deformers_skin
.setdefault(arm_obj
, OrderedDict())[me
] = (get_blender_armature_skin_key(arm_obj
.bdata
, me
),
1818 # We don't want a regular parent relationship for those in FBX...
1819 arm_parents
.add((arm_obj
, ob_obj
))
1820 # Needed to handle matrices/spaces (since we do not parent them to 'armature' in FBX :/ ).
1821 ob_obj
.parented_to_armature
= True
1823 objects
.update(bones
)
1826 def fbx_generate_leaf_bones(settings
, data_bones
):
1827 # find which bons have no children
1828 child_count
= {bo
: 0 for bo
in data_bones
.keys()}
1829 for bo
in data_bones
.keys():
1830 if bo
.parent
and bo
.parent
.is_bone
:
1831 child_count
[bo
.parent
] += 1
1833 bone_radius_scale
= settings
.global_scale
* 33.0
1835 # generate bone data
1836 leaf_parents
= [bo
for bo
, count
in child_count
.items() if count
== 0]
1838 for parent
in leaf_parents
:
1839 node_name
= parent
.name
+ "_end"
1840 parent_uuid
= parent
.fbx_uuid
1841 parent_key
= parent
.key
1842 node_uuid
= get_fbx_uuid_from_key(parent_key
+ "_end_node")
1843 attr_uuid
= get_fbx_uuid_from_key(parent_key
+ "_end_nodeattr")
1846 size
= parent
.bdata
.head_radius
* bone_radius_scale
1847 bone_length
= (parent
.bdata
.tail_local
- parent
.bdata
.head_local
).length
1848 matrix
= Matrix
.Translation((0, bone_length
, 0))
1849 if settings
.bone_correction_matrix_inv
:
1850 matrix
= settings
.bone_correction_matrix_inv
* matrix
1851 if settings
.bone_correction_matrix
:
1852 matrix
= matrix
* settings
.bone_correction_matrix
1853 leaf_bones
.append((node_name
, parent_uuid
, node_uuid
, attr_uuid
, matrix
, hide
, size
))
1858 def fbx_animations_do(scene_data
, ref_id
, f_start
, f_end
, start_zero
, objects
=None, force_keep
=False):
1860 Generate animation data (a single AnimStack) from objects, for a given frame range.
1862 bake_step
= scene_data
.settings
.bake_anim_step
1863 simplify_fac
= scene_data
.settings
.bake_anim_simplify_factor
1864 scene
= scene_data
.scene
1865 force_keying
= scene_data
.settings
.bake_anim_use_all_bones
1866 force_sek
= scene_data
.settings
.bake_anim_force_startend_keying
1868 if objects
is not None:
1869 # Add bones and duplis!
1870 for ob_obj
in tuple(objects
):
1871 if not ob_obj
.is_object
:
1873 if ob_obj
.type == 'ARMATURE':
1874 objects |
= {bo_obj
for bo_obj
in ob_obj
.bones
if bo_obj
in scene_data
.objects
}
1875 ob_obj
.dupli_list_create(scene
, 'RENDER')
1876 for dp_obj
in ob_obj
.dupli_list
:
1877 if dp_obj
in scene_data
.objects
:
1879 ob_obj
.dupli_list_clear()
1881 objects
= scene_data
.objects
1883 back_currframe
= scene
.frame_current
1884 animdata_ob
= OrderedDict()
1887 for ob_obj
in objects
:
1888 if ob_obj
.parented_to_armature
:
1890 ACNW
= AnimationCurveNodeWrapper
1891 loc
, rot
, scale
, _m
, _mr
= ob_obj
.fbx_object_tx(scene_data
)
1892 rot_deg
= tuple(convert_rad_to_deg_iter(rot
))
1893 force_key
= (simplify_fac
== 0.0) or (ob_obj
.is_bone
and force_keying
)
1894 animdata_ob
[ob_obj
] = (ACNW(ob_obj
.key
, 'LCL_TRANSLATION', force_key
, force_sek
, loc
),
1895 ACNW(ob_obj
.key
, 'LCL_ROTATION', force_key
, force_sek
, rot_deg
),
1896 ACNW(ob_obj
.key
, 'LCL_SCALING', force_key
, force_sek
, scale
))
1897 p_rots
[ob_obj
] = rot
1899 animdata_shapes
= OrderedDict()
1900 force_key
= (simplify_fac
== 0.0)
1901 for me
, (me_key
, _shapes_key
, shapes
) in scene_data
.data_deformers_shape
.items():
1902 # Ignore absolute shape keys for now!
1903 if not me
.shape_keys
.use_relative
:
1905 for shape
, (channel_key
, geom_key
, _shape_verts_co
, _shape_verts_idx
) in shapes
.items():
1906 acnode
= AnimationCurveNodeWrapper(channel_key
, 'SHAPE_KEY', force_key
, force_sek
, (0.0,))
1907 # Sooooo happy to have to twist again like a mad snake... Yes, we need to write those curves twice. :/
1908 acnode
.add_group(me_key
, shape
.name
, shape
.name
, (shape
.name
,))
1909 animdata_shapes
[channel_key
] = (acnode
, me
, shape
)
1912 while currframe
<= f_end
:
1913 real_currframe
= currframe
- f_start
if start_zero
else currframe
1914 scene
.frame_set(int(currframe
), currframe
- int(currframe
))
1916 for ob_obj
in animdata_ob
:
1917 ob_obj
.dupli_list_create(scene
, 'RENDER')
1918 for ob_obj
, (anim_loc
, anim_rot
, anim_scale
) in animdata_ob
.items():
1919 # We compute baked loc/rot/scale for all objects (rot being euler-compat with previous value!).
1920 p_rot
= p_rots
.get(ob_obj
, None)
1921 loc
, rot
, scale
, _m
, _mr
= ob_obj
.fbx_object_tx(scene_data
, rot_euler_compat
=p_rot
)
1922 p_rots
[ob_obj
] = rot
1923 anim_loc
.add_keyframe(real_currframe
, loc
)
1924 anim_rot
.add_keyframe(real_currframe
, tuple(convert_rad_to_deg_iter(rot
)))
1925 anim_scale
.add_keyframe(real_currframe
, scale
)
1926 for ob_obj
in objects
:
1927 ob_obj
.dupli_list_clear()
1928 for anim_shape
, me
, shape
in animdata_shapes
.values():
1929 anim_shape
.add_keyframe(real_currframe
, (shape
.value
* 100.0,))
1930 currframe
+= bake_step
1932 scene
.frame_set(back_currframe
, 0.0)
1934 animations
= OrderedDict()
1936 # And now, produce final data (usable by FBX export code)
1937 # Objects-like loc/rot/scale...
1938 for ob_obj
, anims
in animdata_ob
.items():
1940 anim
.simplify(simplify_fac
, bake_step
, force_keep
)
1943 for obj_key
, group_key
, group
, fbx_group
, fbx_gname
in anim
.get_final_data(scene
, ref_id
, force_keep
):
1944 anim_data
= animations
.get(obj_key
)
1945 if anim_data
is None:
1946 anim_data
= animations
[obj_key
] = ("dummy_unused_key", OrderedDict())
1947 anim_data
[1][fbx_group
] = (group_key
, group
, fbx_gname
)
1949 # And meshes' shape keys.
1950 for channel_key
, (anim_shape
, me
, shape
) in animdata_shapes
.items():
1951 final_keys
= OrderedDict()
1952 anim_shape
.simplify(simplify_fac
, bake_step
, force_keep
)
1955 for elem_key
, group_key
, group
, fbx_group
, fbx_gname
in anim_shape
.get_final_data(scene
, ref_id
, force_keep
):
1956 anim_data
= animations
.get(elem_key
)
1957 if anim_data
is None:
1958 anim_data
= animations
[elem_key
] = ("dummy_unused_key", OrderedDict())
1959 anim_data
[1][fbx_group
] = (group_key
, group
, fbx_gname
)
1961 astack_key
= get_blender_anim_stack_key(scene
, ref_id
)
1962 alayer_key
= get_blender_anim_layer_key(scene
, ref_id
)
1963 name
= (get_blenderID_name(ref_id
) if ref_id
else scene
.name
).encode()
1969 return (astack_key
, animations
, alayer_key
, name
, f_start
, f_end
) if animations
else None
1972 def fbx_animations(scene_data
):
1974 Generate global animation data from objects.
1976 scene
= scene_data
.scene
1982 def add_anim(animations
, animated
, anim
):
1983 nonlocal frame_start
, frame_end
1984 if anim
is not None:
1985 animations
.append(anim
)
1986 f_start
, f_end
= anim
[4:6]
1987 if f_start
< frame_start
:
1988 frame_start
= f_start
1989 if f_end
> frame_end
:
1992 _astack_key
, astack
, _alayer_key
, _name
, _fstart
, _fend
= anim
1993 for elem_key
, (alayer_key
, acurvenodes
) in astack
.items():
1994 for fbx_prop
, (acurvenode_key
, acurves
, acurvenode_name
) in acurvenodes
.items():
1995 animated
.add((elem_key
, fbx_prop
))
1997 # Per-NLA strip animstacks.
1998 if scene_data
.settings
.bake_anim_use_nla_strips
:
2001 for ob_obj
in scene_data
.objects
:
2002 # NLA tracks only for objects, not bones!
2003 if not ob_obj
.is_object
:
2005 ob
= ob_obj
.bdata
# Back to real Blender Object.
2006 if not ob
.animation_data
:
2008 # We have to remove active action from objects, it overwrites strips actions otherwise...
2009 ob_actions
.append((ob
, ob
.animation_data
.action
))
2010 ob
.animation_data
.action
= None
2011 for track
in ob
.animation_data
.nla_tracks
:
2014 for strip
in track
.strips
:
2017 strips
.append(strip
)
2020 for strip
in strips
:
2022 add_anim(animations
, animated
,
2023 fbx_animations_do(scene_data
, strip
, strip
.frame_start
, strip
.frame_end
, True, force_keep
=True))
2025 scene
.frame_set(scene
.frame_current
, 0.0)
2027 for strip
in strips
:
2030 for ob
, ob_act
in ob_actions
:
2031 ob
.animation_data
.action
= ob_act
2034 if scene_data
.settings
.bake_anim_use_all_actions
:
2035 def validate_actions(act
, path_resolve
):
2036 for fc
in act
.fcurves
:
2037 data_path
= fc
.data_path
2039 data_path
= data_path
+ "[%d]" % fc
.array_index
2041 path_resolve(data_path
)
2043 return False # Invalid.
2044 return True # Valid.
2046 def restore_object(ob_to
, ob_from
):
2047 # Restore org state of object (ugh :/ ).
2049 'location', 'rotation_quaternion', 'rotation_axis_angle', 'rotation_euler', 'rotation_mode', 'scale',
2050 'delta_location', 'delta_rotation_euler', 'delta_rotation_quaternion', 'delta_scale',
2051 'lock_location', 'lock_rotation', 'lock_rotation_w', 'lock_rotations_4d', 'lock_scale',
2052 'tag', 'layers', 'select', 'track_axis', 'up_axis', 'active_material', 'active_material_index',
2053 'matrix_parent_inverse', 'empty_draw_type', 'empty_draw_size', 'empty_image_offset', 'pass_index',
2054 'color', 'hide', 'hide_select', 'hide_render', 'use_slow_parent', 'slow_parent_offset',
2055 'use_extra_recalc_object', 'use_extra_recalc_data', 'dupli_type', 'use_dupli_frames_speed',
2056 'use_dupli_vertices_rotation', 'use_dupli_faces_scale', 'dupli_faces_scale', 'dupli_group',
2057 'dupli_frames_start', 'dupli_frames_end', 'dupli_frames_on', 'dupli_frames_off',
2058 'draw_type', 'show_bounds', 'draw_bounds_type', 'show_name', 'show_axis', 'show_texture_space',
2059 'show_wire', 'show_all_edges', 'show_transparent', 'show_x_ray',
2060 'show_only_shape_key', 'use_shape_key_edit_mode', 'active_shape_key_index',
2063 if not ob_to
.is_property_readonly(p
):
2064 setattr(ob_to
, p
, getattr(ob_from
, p
))
2066 for ob_obj
in scene_data
.objects
:
2067 # Actions only for objects, not bones!
2068 if not ob_obj
.is_object
:
2071 ob
= ob_obj
.bdata
# Back to real Blender Object.
2073 if not ob
.animation_data
:
2074 continue # Do not export animations for objects that are absolutely not animated, see T44386.
2076 if ob
.animation_data
.is_property_readonly('action'):
2077 continue # Cannot re-assign 'active action' to this object (usually related to NLA usage, see T48089).
2079 # We can't play with animdata and actions and get back to org state easily.
2080 # So we have to add a temp copy of the object to the scene, animate it, and remove it... :/
2082 # Great, have to handle bones as well if needed...
2083 pbones_matrices
= [pbo
.matrix_basis
.copy() for pbo
in ob
.pose
.bones
] if ob
.type == 'ARMATURE' else ...
2085 org_act
= ob
.animation_data
.action
2086 path_resolve
= ob
.path_resolve
2088 for act
in bpy
.data
.actions
:
2089 # For now, *all* paths in the action must be valid for the object, to validate the action.
2090 # Unless that action was already assigned to the object!
2091 if act
!= org_act
and not validate_actions(act
, path_resolve
):
2093 ob
.animation_data
.action
= act
2094 frame_start
, frame_end
= act
.frame_range
# sic!
2095 add_anim(animations
, animated
,
2096 fbx_animations_do(scene_data
, (ob
, act
), frame_start
, frame_end
, True,
2097 objects
={ob_obj}
, force_keep
=True))
2099 if pbones_matrices
is not ...:
2100 for pbo
, mat
in zip(ob
.pose
.bones
, pbones_matrices
):
2101 pbo
.matrix_basis
= mat
.copy()
2102 ob
.animation_data
.action
= org_act
2103 restore_object(ob
, ob_copy
)
2104 scene
.frame_set(scene
.frame_current
, 0.0)
2106 if pbones_matrices
is not ...:
2107 for pbo
, mat
in zip(ob
.pose
.bones
, pbones_matrices
):
2108 pbo
.matrix_basis
= mat
.copy()
2109 ob
.animation_data
.action
= org_act
2111 bpy
.data
.objects
.remove(ob_copy
)
2112 scene
.frame_set(scene
.frame_current
, 0.0)
2114 # Global (containing everything) animstack, only if not exporting NLA strips and/or all actions.
2115 if not scene_data
.settings
.bake_anim_use_nla_strips
and not scene_data
.settings
.bake_anim_use_all_actions
:
2116 add_anim(animations
, animated
, fbx_animations_do(scene_data
, None, scene
.frame_start
, scene
.frame_end
, False))
2118 # Be sure to update all matrices back to org state!
2119 scene
.frame_set(scene
.frame_current
, 0.0)
2121 return animations
, animated
, frame_start
, frame_end
2124 def fbx_data_from_scene(scene
, settings
):
2126 Do some pre-processing over scene's data...
2128 objtypes
= settings
.object_types
2129 dp_objtypes
= objtypes
- {'ARMATURE'} # Armatures are not supported as dupli instances currently...
2133 # ##### Gathering data...
2135 perfmon
.step("FBX export prepare: Wrapping Objects...")
2137 # This is rather simple for now, maybe we could end generating templates with most-used values
2138 # instead of default ones?
2139 objects
= OrderedDict() # Because we do not have any ordered set...
2140 for ob
in settings
.context_objects
:
2141 if ob
.type not in objtypes
:
2143 ob_obj
= ObjectWrapper(ob
)
2144 objects
[ob_obj
] = None
2146 ob_obj
.dupli_list_create(scene
, 'RENDER')
2147 for dp_obj
in ob_obj
.dupli_list
:
2148 if dp_obj
.type not in dp_objtypes
:
2150 objects
[dp_obj
] = None
2151 ob_obj
.dupli_list_clear()
2153 perfmon
.step("FBX export prepare: Wrapping Data (lamps, cameras, empties)...")
2155 data_lamps
= OrderedDict((ob_obj
.bdata
.data
, get_blenderID_key(ob_obj
.bdata
.data
))
2156 for ob_obj
in objects
if ob_obj
.type == 'LAMP')
2157 # Unfortunately, FBX camera data contains object-level data (like position, orientation, etc.)...
2158 data_cameras
= OrderedDict((ob_obj
, get_blenderID_key(ob_obj
.bdata
.data
))
2159 for ob_obj
in objects
if ob_obj
.type == 'CAMERA')
2160 # Yep! Contains nothing, but needed!
2161 data_empties
= OrderedDict((ob_obj
, get_blender_empty_key(ob_obj
.bdata
))
2162 for ob_obj
in objects
if ob_obj
.type == 'EMPTY')
2164 perfmon
.step("FBX export prepare: Wrapping Meshes...")
2166 data_meshes
= OrderedDict()
2167 for ob_obj
in objects
:
2168 if ob_obj
.type not in BLENDER_OBJECT_TYPES_MESHLIKE
:
2174 # Do not want to systematically recreate a new mesh for dupliobject instances, kind of break purpose of those.
2176 org_ob_obj
= ObjectWrapper(ob
) # We get the "real" object wrapper from that dupli instance.
2177 if org_ob_obj
in data_meshes
:
2178 data_meshes
[ob_obj
] = data_meshes
[org_ob_obj
]
2181 is_ob_material
= any(ms
.link
== 'OBJECT' for ms
in ob
.material_slots
)
2183 if settings
.use_mesh_modifiers
or ob
.type in BLENDER_OTHER_OBJECT_TYPES
or is_ob_material
:
2184 # We cannot use default mesh in that case, or material would not be the right ones...
2185 use_org_data
= not (is_ob_material
or ob
.type in BLENDER_OTHER_OBJECT_TYPES
)
2187 if use_org_data
and ob
.type == 'MESH':
2188 # No need to create a new mesh in this case, if no modifier is active!
2189 for mod
in ob
.modifiers
:
2190 # For meshes, when armature export is enabled, disable Armature modifiers here!
2191 if mod
.type == 'ARMATURE' and 'ARMATURE' in settings
.object_types
:
2192 tmp_mods
.append((mod
, mod
.show_render
))
2193 mod
.show_render
= False
2195 use_org_data
= False
2196 if not use_org_data
:
2197 tmp_me
= ob
.to_mesh(
2199 apply_modifiers
=settings
.use_mesh_modifiers
,
2200 settings
='RENDER' if settings
.use_mesh_modifiers_render
else 'PREVIEW',
2202 data_meshes
[ob_obj
] = (get_blenderID_key(tmp_me
), tmp_me
, True)
2203 # Re-enable temporary disabled modifiers.
2204 for mod
, show_render
in tmp_mods
:
2205 mod
.show_render
= show_render
2207 data_meshes
[ob_obj
] = (get_blenderID_key(ob
.data
), ob
.data
, False)
2209 # In case "real" source object of that dupli did not yet still existed in data_meshes, create it now!
2210 if org_ob_obj
is not None:
2211 data_meshes
[org_ob_obj
] = data_meshes
[ob_obj
]
2213 perfmon
.step("FBX export prepare: Wrapping ShapeKeys...")
2216 data_deformers_shape
= OrderedDict()
2217 geom_mat_co
= settings
.global_matrix
if settings
.bake_space_transform
else None
2218 for me_key
, me
, _free
in data_meshes
.values():
2219 if not (me
.shape_keys
and len(me
.shape_keys
.key_blocks
) > 1): # We do not want basis-only relative skeys...
2221 if me
in data_deformers_shape
:
2224 shapes_key
= get_blender_mesh_shape_key(me
)
2225 # We gather all vcos first, since some skeys may be based on others...
2226 _cos
= array
.array(data_types
.ARRAY_FLOAT64
, (0.0,)) * len(me
.vertices
) * 3
2227 me
.vertices
.foreach_get("co", _cos
)
2228 v_cos
= tuple(vcos_transformed_gen(_cos
, geom_mat_co
))
2230 for shape
in me
.shape_keys
.key_blocks
[1:]:
2231 shape
.data
.foreach_get("co", _cos
)
2232 sk_cos
[shape
] = tuple(vcos_transformed_gen(_cos
, geom_mat_co
))
2233 sk_base
= me
.shape_keys
.key_blocks
[0]
2235 for shape
in me
.shape_keys
.key_blocks
[1:]:
2236 # Only write vertices really different from org coordinates!
2237 # XXX FBX does not like empty shapes (makes Unity crash e.g.), so we have to do this here... :/
2239 shape_verts_idx
= []
2241 sv_cos
= sk_cos
[shape
]
2242 ref_cos
= v_cos
if shape
.relative_key
== sk_base
else sk_cos
[shape
.relative_key
]
2243 for idx
, (sv_co
, ref_co
) in enumerate(zip(sv_cos
, ref_cos
)):
2244 if similar_values_iter(sv_co
, ref_co
):
2245 # Note: Maybe this is a bit too simplistic, should we use real shape base here? Though FBX does not
2246 # have this at all... Anyway, this should cover most common cases imho.
2248 shape_verts_co
.extend(Vector(sv_co
) - Vector(ref_co
))
2249 shape_verts_idx
.append(idx
)
2250 if not shape_verts_co
:
2252 channel_key
, geom_key
= get_blender_mesh_shape_channel_key(me
, shape
)
2253 data
= (channel_key
, geom_key
, shape_verts_co
, shape_verts_idx
)
2254 data_deformers_shape
.setdefault(me
, (me_key
, shapes_key
, OrderedDict()))[2][shape
] = data
2256 perfmon
.step("FBX export prepare: Wrapping Armatures...")
2259 data_deformers_skin
= OrderedDict()
2260 data_bones
= OrderedDict()
2262 for ob_obj
in tuple(objects
):
2263 if not (ob_obj
.is_object
and ob_obj
.type in {'ARMATURE'}):
2265 fbx_skeleton_from_armature(scene
, settings
, ob_obj
, objects
, data_meshes
,
2266 data_bones
, data_deformers_skin
, data_empties
, arm_parents
)
2268 # Generate leaf bones
2269 data_leaf_bones
= []
2270 if settings
.add_leaf_bones
:
2271 data_leaf_bones
= fbx_generate_leaf_bones(settings
, data_bones
)
2273 perfmon
.step("FBX export prepare: Wrapping World...")
2275 # Some world settings are embedded in FBX materials...
2277 data_world
= OrderedDict(((scene
.world
, get_blenderID_key(scene
.world
)),))
2279 data_world
= OrderedDict()
2281 perfmon
.step("FBX export prepare: Wrapping Materials...")
2283 # TODO: Check all the mat stuff works even when mats are linked to Objects
2284 # (we can then have the same mesh used with different materials...).
2285 # *Should* work, as FBX always links its materials to Models (i.e. objects).
2286 # XXX However, material indices would probably break...
2287 data_materials
= OrderedDict()
2288 for ob_obj
in objects
:
2289 # If obj is not a valid object for materials, wrapper will just return an empty tuple...
2290 for mat_s
in ob_obj
.material_slots
:
2291 mat
= mat_s
.material
2293 continue # Empty slots!
2294 # Note theoretically, FBX supports any kind of materials, even GLSL shaders etc.
2295 # However, I doubt anything else than Lambert/Phong is really portable!
2296 # We support any kind of 'surface' shader though, better to have some kind of default Lambert than nothing.
2297 # Note we want to keep a 'dummy' empty mat even when we can't really support it, see T41396.
2298 mat_data
= data_materials
.get(mat
)
2299 if mat_data
is not None:
2300 mat_data
[1].append(ob_obj
)
2302 data_materials
[mat
] = (get_blenderID_key(mat
), [ob_obj
])
2304 perfmon
.step("FBX export prepare: Wrapping Textures...")
2306 # Note FBX textures also hold their mapping info.
2307 # TODO: Support layers?
2308 data_textures
= OrderedDict()
2309 # FbxVideo also used to store static images...
2310 data_videos
= OrderedDict()
2311 # For now, do not use world textures, don't think they can be linked to anything FBX wise...
2312 for mat
in data_materials
.keys():
2313 if check_skip_material(mat
):
2315 for tex
, use_tex
in zip(mat
.texture_slots
, mat
.use_textures
):
2316 if tex
is None or tex
.texture
is None or not use_tex
:
2318 # For now, only consider image textures.
2319 # Note FBX does has support for procedural, but this is not portable at all (opaque blob),
2320 # so not useful for us.
2321 # TODO I think ENVIRONMENT_MAP should be usable in FBX as well, but for now let it aside.
2322 # if tex.texture.type not in {'IMAGE', 'ENVIRONMENT_MAP'}:
2323 if tex
.texture
.type not in {'IMAGE'}:
2325 img
= tex
.texture
.image
2328 # Find out whether we can actually use this texture for this material, in FBX context.
2329 tex_fbx_props
= fbx_mat_properties_from_texture(tex
)
2330 if not tex_fbx_props
:
2332 tex_data
= data_textures
.get(tex
)
2333 if tex_data
is not None:
2334 tex_data
[1][mat
] = tex_fbx_props
2336 data_textures
[tex
] = (get_blenderID_key(tex
), OrderedDict(((mat
, tex_fbx_props
),)))
2337 vid_data
= data_videos
.get(img
)
2338 if vid_data
is not None:
2339 vid_data
[1].append(tex
)
2341 data_videos
[img
] = (get_blenderID_key(img
), [tex
])
2343 perfmon
.step("FBX export prepare: Wrapping Animations...")
2348 frame_start
= scene
.frame_start
2349 frame_end
= scene
.frame_end
2350 if settings
.bake_anim
:
2351 # From objects & bones only for a start.
2352 # Kind of hack, we need a temp scene_data for object's space handling to bake animations...
2353 tmp_scdata
= FBXExportData(
2355 settings
, scene
, objects
, None, None, 0.0, 0.0,
2356 data_empties
, data_lamps
, data_cameras
, data_meshes
, None,
2357 data_bones
, data_leaf_bones
, data_deformers_skin
, data_deformers_shape
,
2358 data_world
, data_materials
, data_textures
, data_videos
,
2360 animations
, animated
, frame_start
, frame_end
= fbx_animations(tmp_scdata
)
2362 # ##### Creation of templates...
2364 perfmon
.step("FBX export prepare: Generating templates...")
2366 templates
= OrderedDict()
2367 templates
[b
"GlobalSettings"] = fbx_template_def_globalsettings(scene
, settings
, nbr_users
=1)
2370 templates
[b
"Null"] = fbx_template_def_null(scene
, settings
, nbr_users
=len(data_empties
))
2373 templates
[b
"Light"] = fbx_template_def_light(scene
, settings
, nbr_users
=len(data_lamps
))
2376 templates
[b
"Camera"] = fbx_template_def_camera(scene
, settings
, nbr_users
=len(data_cameras
))
2379 templates
[b
"Bone"] = fbx_template_def_bone(scene
, settings
, nbr_users
=len(data_bones
))
2382 nbr
= len({me_key
for me_key
, _me
, _free
in data_meshes
.values()})
2383 if data_deformers_shape
:
2384 nbr
+= sum(len(shapes
[2]) for shapes
in data_deformers_shape
.values())
2385 templates
[b
"Geometry"] = fbx_template_def_geometry(scene
, settings
, nbr_users
=nbr
)
2388 templates
[b
"Model"] = fbx_template_def_model(scene
, settings
, nbr_users
=len(objects
))
2391 # Number of Pose|BindPose elements should be the same as number of meshes-parented-to-armatures
2392 templates
[b
"BindPose"] = fbx_template_def_pose(scene
, settings
, nbr_users
=len(arm_parents
))
2394 if data_deformers_skin
or data_deformers_shape
:
2396 if data_deformers_skin
:
2397 nbr
+= len(data_deformers_skin
)
2398 nbr
+= sum(len(clusters
) for def_me
in data_deformers_skin
.values() for a
, b
, clusters
in def_me
.values())
2399 if data_deformers_shape
:
2400 nbr
+= len(data_deformers_shape
)
2401 nbr
+= sum(len(shapes
[2]) for shapes
in data_deformers_shape
.values())
2403 templates
[b
"Deformers"] = fbx_template_def_deformer(scene
, settings
, nbr_users
=nbr
)
2405 # No world support in FBX...
2408 templates[b"World"] = fbx_template_def_world(scene, settings, nbr_users=len(data_world))
2412 templates
[b
"Material"] = fbx_template_def_material(scene
, settings
, nbr_users
=len(data_materials
))
2415 templates
[b
"TextureFile"] = fbx_template_def_texture_file(scene
, settings
, nbr_users
=len(data_textures
))
2418 templates
[b
"Video"] = fbx_template_def_video(scene
, settings
, nbr_users
=len(data_videos
))
2421 nbr_astacks
= len(animations
)
2424 for _astack_key
, astack
, _al
, _n
, _fs
, _fe
in animations
:
2425 for _alayer_key
, alayer
in astack
.values():
2426 for _acnode_key
, acnode
, _acnode_name
in alayer
.values():
2428 for _acurve_key
, _dval
, acurve
, acurve_valid
in acnode
.values():
2432 templates
[b
"AnimationStack"] = fbx_template_def_animstack(scene
, settings
, nbr_users
=nbr_astacks
)
2433 # Would be nice to have one layer per animated object, but this seems tricky and not that well supported.
2434 # So for now, only one layer per anim stack.
2435 templates
[b
"AnimationLayer"] = fbx_template_def_animlayer(scene
, settings
, nbr_users
=nbr_astacks
)
2436 templates
[b
"AnimationCurveNode"] = fbx_template_def_animcurvenode(scene
, settings
, nbr_users
=nbr_acnodes
)
2437 templates
[b
"AnimationCurve"] = fbx_template_def_animcurve(scene
, settings
, nbr_users
=nbr_acurves
)
2439 templates_users
= sum(tmpl
.nbr_users
for tmpl
in templates
.values())
2441 # ##### Creation of connections...
2443 perfmon
.step("FBX export prepare: Generating Connections...")
2447 # Objects (with classical parenting).
2448 for ob_obj
in objects
:
2449 # Bones are handled later.
2450 if not ob_obj
.is_bone
:
2451 par_obj
= ob_obj
.parent
2452 # Meshes parented to armature are handled separately, yet we want the 'no parent' connection (0).
2453 if par_obj
and ob_obj
.has_valid_parent(objects
) and (par_obj
, ob_obj
) not in arm_parents
:
2454 connections
.append((b
"OO", ob_obj
.fbx_uuid
, par_obj
.fbx_uuid
, None))
2456 connections
.append((b
"OO", ob_obj
.fbx_uuid
, 0, None))
2458 # Armature & Bone chains.
2459 for bo_obj
in data_bones
.keys():
2460 par_obj
= bo_obj
.parent
2461 if par_obj
not in objects
:
2463 connections
.append((b
"OO", bo_obj
.fbx_uuid
, par_obj
.fbx_uuid
, None))
2466 for ob_obj
in objects
:
2468 bo_data_key
= data_bones
[ob_obj
]
2469 connections
.append((b
"OO", get_fbx_uuid_from_key(bo_data_key
), ob_obj
.fbx_uuid
, None))
2471 if ob_obj
.type == 'LAMP':
2472 lamp_key
= data_lamps
[ob_obj
.bdata
.data
]
2473 connections
.append((b
"OO", get_fbx_uuid_from_key(lamp_key
), ob_obj
.fbx_uuid
, None))
2474 elif ob_obj
.type == 'CAMERA':
2475 cam_key
= data_cameras
[ob_obj
]
2476 connections
.append((b
"OO", get_fbx_uuid_from_key(cam_key
), ob_obj
.fbx_uuid
, None))
2477 elif ob_obj
.type == 'EMPTY' or ob_obj
.type == 'ARMATURE':
2478 empty_key
= data_empties
[ob_obj
]
2479 connections
.append((b
"OO", get_fbx_uuid_from_key(empty_key
), ob_obj
.fbx_uuid
, None))
2480 elif ob_obj
.type in BLENDER_OBJECT_TYPES_MESHLIKE
:
2481 mesh_key
, _me
, _free
= data_meshes
[ob_obj
]
2482 connections
.append((b
"OO", get_fbx_uuid_from_key(mesh_key
), ob_obj
.fbx_uuid
, None))
2485 for (_node_name
, par_uuid
, node_uuid
, attr_uuid
, _matrix
, _hide
, _size
) in data_leaf_bones
:
2486 connections
.append((b
"OO", node_uuid
, par_uuid
, None))
2487 connections
.append((b
"OO", attr_uuid
, node_uuid
, None))
2489 # 'Shape' deformers (shape keys, only for meshes currently)...
2490 for me_key
, shapes_key
, shapes
in data_deformers_shape
.values():
2492 connections
.append((b
"OO", get_fbx_uuid_from_key(shapes_key
), get_fbx_uuid_from_key(me_key
), None))
2493 for channel_key
, geom_key
, _shape_verts_co
, _shape_verts_idx
in shapes
.values():
2494 # shape channel -> shape
2495 connections
.append((b
"OO", get_fbx_uuid_from_key(channel_key
), get_fbx_uuid_from_key(shapes_key
), None))
2496 # geometry (keys) -> shape channel
2497 connections
.append((b
"OO", get_fbx_uuid_from_key(geom_key
), get_fbx_uuid_from_key(channel_key
), None))
2499 # 'Skin' deformers (armature-to-geometry, only for meshes currently)...
2500 for arm
, deformed_meshes
in data_deformers_skin
.items():
2501 for me
, (skin_key
, ob_obj
, clusters
) in deformed_meshes
.items():
2503 mesh_key
, _me
, _free
= data_meshes
[ob_obj
]
2505 connections
.append((b
"OO", get_fbx_uuid_from_key(skin_key
), get_fbx_uuid_from_key(mesh_key
), None))
2506 for bo_obj
, clstr_key
in clusters
.items():
2508 connections
.append((b
"OO", get_fbx_uuid_from_key(clstr_key
), get_fbx_uuid_from_key(skin_key
), None))
2510 connections
.append((b
"OO", bo_obj
.fbx_uuid
, get_fbx_uuid_from_key(clstr_key
), None))
2513 mesh_mat_indices
= OrderedDict()
2515 for mat
, (mat_key
, ob_objs
) in data_materials
.items():
2516 for ob_obj
in ob_objs
:
2517 connections
.append((b
"OO", get_fbx_uuid_from_key(mat_key
), ob_obj
.fbx_uuid
, None))
2518 # Get index of this mat for this object (or dupliobject).
2519 # Mat indices for mesh faces are determined by their order in 'mat to ob' connections.
2520 # Only mats for meshes currently...
2521 # Note in case of dupliobjects a same me/mat idx will be generated several times...
2522 # Should not be an issue in practice, and it's needed in case we export duplis but not the original!
2523 if ob_obj
.type not in BLENDER_OBJECT_TYPES_MESHLIKE
:
2525 _mesh_key
, me
, _free
= data_meshes
[ob_obj
]
2526 idx
= _objs_indices
[ob_obj
] = _objs_indices
.get(ob_obj
, -1) + 1
2527 mesh_mat_indices
.setdefault(me
, OrderedDict())[mat
] = idx
2531 for tex
, (tex_key
, mats
) in data_textures
.items():
2532 for mat
, fbx_mat_props
in mats
.items():
2533 mat_key
, _ob_objs
= data_materials
[mat
]
2534 for fbx_prop
in fbx_mat_props
:
2535 # texture -> material properties
2536 connections
.append((b
"OP", get_fbx_uuid_from_key(tex_key
), get_fbx_uuid_from_key(mat_key
), fbx_prop
))
2539 for vid
, (vid_key
, texs
) in data_videos
.items():
2541 tex_key
, _texs
= data_textures
[tex
]
2542 connections
.append((b
"OO", get_fbx_uuid_from_key(vid_key
), get_fbx_uuid_from_key(tex_key
), None))
2545 for astack_key
, astack
, alayer_key
, _name
, _fstart
, _fend
in animations
:
2546 # Animstack itself is linked nowhere!
2547 astack_id
= get_fbx_uuid_from_key(astack_key
)
2548 # For now, only one layer!
2549 alayer_id
= get_fbx_uuid_from_key(alayer_key
)
2550 connections
.append((b
"OO", alayer_id
, astack_id
, None))
2551 for elem_key
, (alayer_key
, acurvenodes
) in astack
.items():
2552 elem_id
= get_fbx_uuid_from_key(elem_key
)
2553 # Animlayer -> animstack.
2554 # alayer_id = get_fbx_uuid_from_key(alayer_key)
2555 # connections.append((b"OO", alayer_id, astack_id, None))
2556 for fbx_prop
, (acurvenode_key
, acurves
, acurvenode_name
) in acurvenodes
.items():
2557 # Animcurvenode -> animalayer.
2558 acurvenode_id
= get_fbx_uuid_from_key(acurvenode_key
)
2559 connections
.append((b
"OO", acurvenode_id
, alayer_id
, None))
2560 # Animcurvenode -> object property.
2561 connections
.append((b
"OP", acurvenode_id
, elem_id
, fbx_prop
.encode()))
2562 for fbx_item
, (acurve_key
, default_value
, acurve
, acurve_valid
) in acurves
.items():
2564 # Animcurve -> Animcurvenode.
2565 connections
.append((b
"OP", get_fbx_uuid_from_key(acurve_key
), acurvenode_id
, fbx_item
.encode()))
2567 perfmon
.level_down()
2569 # ##### And pack all this!
2571 return FBXExportData(
2572 templates
, templates_users
, connections
,
2573 settings
, scene
, objects
, animations
, animated
, frame_start
, frame_end
,
2574 data_empties
, data_lamps
, data_cameras
, data_meshes
, mesh_mat_indices
,
2575 data_bones
, data_leaf_bones
, data_deformers_skin
, data_deformers_shape
,
2576 data_world
, data_materials
, data_textures
, data_videos
,
2580 def fbx_scene_data_cleanup(scene_data
):
2582 Some final cleanup...
2584 # Delete temp meshes.
2586 for me_key
, me
, free
in scene_data
.data_meshes
.values():
2587 if free
and me_key
not in done_meshes
:
2588 bpy
.data
.meshes
.remove(me
)
2589 done_meshes
.add(me_key
)
2592 # ##### Top-level FBX elements generators. #####
2594 def fbx_header_elements(root
, scene_data
, time
=None):
2596 Write boiling code of FBX root.
2597 time is expected to be a datetime.datetime object, or None (using now() in this case).
2599 app_vendor
= "Blender Foundation"
2600 app_name
= "Blender (stable FBX IO)"
2601 app_ver
= bpy
.app
.version_string
2605 addon_ver
= addon_utils
.module_bl_info(sys
.modules
[__package__
])['version']
2607 # ##### Start of FBXHeaderExtension element.
2608 header_ext
= elem_empty(root
, b
"FBXHeaderExtension")
2610 elem_data_single_int32(header_ext
, b
"FBXHeaderVersion", FBX_HEADER_VERSION
)
2612 elem_data_single_int32(header_ext
, b
"FBXVersion", FBX_VERSION
)
2615 elem_data_single_int32(header_ext
, b
"EncryptionType", 0)
2618 time
= datetime
.datetime
.now()
2619 elem
= elem_empty(header_ext
, b
"CreationTimeStamp")
2620 elem_data_single_int32(elem
, b
"Version", 1000)
2621 elem_data_single_int32(elem
, b
"Year", time
.year
)
2622 elem_data_single_int32(elem
, b
"Month", time
.month
)
2623 elem_data_single_int32(elem
, b
"Day", time
.day
)
2624 elem_data_single_int32(elem
, b
"Hour", time
.hour
)
2625 elem_data_single_int32(elem
, b
"Minute", time
.minute
)
2626 elem_data_single_int32(elem
, b
"Second", time
.second
)
2627 elem_data_single_int32(elem
, b
"Millisecond", time
.microsecond
// 1000)
2629 elem_data_single_string_unicode(header_ext
, b
"Creator", "%s - %s - %d.%d.%d"
2630 % (app_name
, app_ver
, addon_ver
[0], addon_ver
[1], addon_ver
[2]))
2632 # 'SceneInfo' seems mandatory to get a valid FBX file...
2633 # TODO use real values!
2634 # XXX Should we use scene.name.encode() here?
2635 scene_info
= elem_data_single_string(header_ext
, b
"SceneInfo", fbx_name_class(b
"GlobalInfo", b
"SceneInfo"))
2636 scene_info
.add_string(b
"UserData")
2637 elem_data_single_string(scene_info
, b
"Type", b
"UserData")
2638 elem_data_single_int32(scene_info
, b
"Version", FBX_SCENEINFO_VERSION
)
2639 meta_data
= elem_empty(scene_info
, b
"MetaData")
2640 elem_data_single_int32(meta_data
, b
"Version", FBX_SCENEINFO_VERSION
)
2641 elem_data_single_string(meta_data
, b
"Title", b
"")
2642 elem_data_single_string(meta_data
, b
"Subject", b
"")
2643 elem_data_single_string(meta_data
, b
"Author", b
"")
2644 elem_data_single_string(meta_data
, b
"Keywords", b
"")
2645 elem_data_single_string(meta_data
, b
"Revision", b
"")
2646 elem_data_single_string(meta_data
, b
"Comment", b
"")
2648 props
= elem_properties(scene_info
)
2649 elem_props_set(props
, "p_string_url", b
"DocumentUrl", "/foobar.fbx")
2650 elem_props_set(props
, "p_string_url", b
"SrcDocumentUrl", "/foobar.fbx")
2651 original
= elem_props_compound(props
, b
"Original")
2652 original("p_string", b
"ApplicationVendor", app_vendor
)
2653 original("p_string", b
"ApplicationName", app_name
)
2654 original("p_string", b
"ApplicationVersion", app_ver
)
2655 original("p_datetime", b
"DateTime_GMT", "01/01/1970 00:00:00.000")
2656 original("p_string", b
"FileName", "/foobar.fbx")
2657 lastsaved
= elem_props_compound(props
, b
"LastSaved")
2658 lastsaved("p_string", b
"ApplicationVendor", app_vendor
)
2659 lastsaved("p_string", b
"ApplicationName", app_name
)
2660 lastsaved("p_string", b
"ApplicationVersion", app_ver
)
2661 lastsaved("p_datetime", b
"DateTime_GMT", "01/01/1970 00:00:00.000")
2663 # ##### End of FBXHeaderExtension element.
2665 # FileID is replaced by dummy value currently...
2666 elem_data_single_bytes(root
, b
"FileId", b
"FooBar")
2668 # CreationTime is replaced by dummy value currently, but anyway...
2669 elem_data_single_string_unicode(root
, b
"CreationTime",
2670 "{:04}-{:02}-{:02} {:02}:{:02}:{:02}:{:03}"
2671 "".format(time
.year
, time
.month
, time
.day
, time
.hour
, time
.minute
, time
.second
,
2672 time
.microsecond
* 1000))
2674 elem_data_single_string_unicode(root
, b
"Creator", "%s - %s - %d.%d.%d"
2675 % (app_name
, app_ver
, addon_ver
[0], addon_ver
[1], addon_ver
[2]))
2677 # ##### Start of GlobalSettings element.
2678 global_settings
= elem_empty(root
, b
"GlobalSettings")
2679 scene
= scene_data
.scene
2681 elem_data_single_int32(global_settings
, b
"Version", 1000)
2683 props
= elem_properties(global_settings
)
2684 up_axis
, front_axis
, coord_axis
= RIGHT_HAND_AXES
[scene_data
.settings
.to_axes
]
2685 #~ # DO NOT take into account global scale here! That setting is applied to object transformations during export
2686 #~ # (in other words, this is pure blender-exporter feature, and has nothing to do with FBX data).
2687 #~ if scene_data.settings.apply_unit_scale:
2688 #~ # Unit scaling is applied to objects' scale, so our unit is effectively FBX one (centimeter).
2689 #~ scale_factor_org = 1.0
2690 #~ scale_factor = 1.0 / units_blender_to_fbx_factor(scene)
2692 #~ scale_factor_org = units_blender_to_fbx_factor(scene)
2693 #~ scale_factor = scale_factor_org
2694 scale_factor
= scale_factor_org
= scene_data
.settings
.unit_scale
2695 elem_props_set(props
, "p_integer", b
"UpAxis", up_axis
[0])
2696 elem_props_set(props
, "p_integer", b
"UpAxisSign", up_axis
[1])
2697 elem_props_set(props
, "p_integer", b
"FrontAxis", front_axis
[0])
2698 elem_props_set(props
, "p_integer", b
"FrontAxisSign", front_axis
[1])
2699 elem_props_set(props
, "p_integer", b
"CoordAxis", coord_axis
[0])
2700 elem_props_set(props
, "p_integer", b
"CoordAxisSign", coord_axis
[1])
2701 elem_props_set(props
, "p_integer", b
"OriginalUpAxis", -1)
2702 elem_props_set(props
, "p_integer", b
"OriginalUpAxisSign", 1)
2703 elem_props_set(props
, "p_double", b
"UnitScaleFactor", scale_factor
)
2704 elem_props_set(props
, "p_double", b
"OriginalUnitScaleFactor", scale_factor_org
)
2705 elem_props_set(props
, "p_color_rgb", b
"AmbientColor", (0.0, 0.0, 0.0))
2706 elem_props_set(props
, "p_string", b
"DefaultCamera", "Producer Perspective")
2708 # Global timing data.
2710 _
, fbx_fps_mode
= FBX_FRAMERATES
[0] # Custom framerate.
2711 fbx_fps
= fps
= r
.fps
/ r
.fps_base
2712 for ref_fps
, fps_mode
in FBX_FRAMERATES
:
2713 if similar_values(fps
, ref_fps
):
2715 fbx_fps_mode
= fps_mode
2716 elem_props_set(props
, "p_enum", b
"TimeMode", fbx_fps_mode
)
2717 elem_props_set(props
, "p_timestamp", b
"TimeSpanStart", 0)
2718 elem_props_set(props
, "p_timestamp", b
"TimeSpanStop", FBX_KTIME
)
2719 elem_props_set(props
, "p_double", b
"CustomFrameRate", fbx_fps
)
2721 # ##### End of GlobalSettings element.
2724 def fbx_documents_elements(root
, scene_data
):
2726 Write 'Document' part of FBX root.
2727 Seems like FBX support multiple documents, but until I find examples of such, we'll stick to single doc!
2728 time is expected to be a datetime.datetime object, or None (using now() in this case).
2730 name
= scene_data
.scene
.name
2732 # ##### Start of Documents element.
2733 docs
= elem_empty(root
, b
"Documents")
2735 elem_data_single_int32(docs
, b
"Count", 1)
2737 doc_uid
= get_fbx_uuid_from_key("__FBX_Document__" + name
)
2738 doc
= elem_data_single_int64(docs
, b
"Document", doc_uid
)
2739 doc
.add_string_unicode(name
)
2740 doc
.add_string_unicode(name
)
2742 props
= elem_properties(doc
)
2743 elem_props_set(props
, "p_object", b
"SourceObject")
2744 elem_props_set(props
, "p_string", b
"ActiveAnimStackName", "")
2746 # XXX Some kind of ID? Offset?
2747 # Anyway, as long as we have only one doc, probably not an issue.
2748 elem_data_single_int64(doc
, b
"RootNode", 0)
2751 def fbx_references_elements(root
, scene_data
):
2753 Have no idea what references are in FBX currently... Just writing empty element.
2755 docs
= elem_empty(root
, b
"References")
2758 def fbx_definitions_elements(root
, scene_data
):
2760 Templates definitions. Only used by Objects data afaik (apart from dummy GlobalSettings one).
2762 definitions
= elem_empty(root
, b
"Definitions")
2764 elem_data_single_int32(definitions
, b
"Version", FBX_TEMPLATES_VERSION
)
2765 elem_data_single_int32(definitions
, b
"Count", scene_data
.templates_users
)
2767 fbx_templates_generate(definitions
, scene_data
.templates
)
2770 def fbx_objects_elements(root
, scene_data
):
2772 Data (objects, geometry, material, textures, armatures, etc.).
2776 objects
= elem_empty(root
, b
"Objects")
2778 perfmon
.step("FBX export fetch empties (%d)..." % len(scene_data
.data_empties
))
2780 for empty
in scene_data
.data_empties
:
2781 fbx_data_empty_elements(objects
, empty
, scene_data
)
2783 perfmon
.step("FBX export fetch lamps (%d)..." % len(scene_data
.data_lamps
))
2785 for lamp
in scene_data
.data_lamps
:
2786 fbx_data_lamp_elements(objects
, lamp
, scene_data
)
2788 perfmon
.step("FBX export fetch cameras (%d)..." % len(scene_data
.data_cameras
))
2790 for cam
in scene_data
.data_cameras
:
2791 fbx_data_camera_elements(objects
, cam
, scene_data
)
2793 perfmon
.step("FBX export fetch meshes (%d)..."
2794 % len({me_key
for me_key
, _me
, _free
in scene_data
.data_meshes
.values()}))
2797 for me_obj
in scene_data
.data_meshes
:
2798 fbx_data_mesh_elements(objects
, me_obj
, scene_data
, done_meshes
)
2801 perfmon
.step("FBX export fetch objects (%d)..." % len(scene_data
.objects
))
2803 for ob_obj
in scene_data
.objects
:
2806 fbx_data_object_elements(objects
, ob_obj
, scene_data
)
2807 ob_obj
.dupli_list_create(scene_data
.scene
, 'RENDER')
2808 for dp_obj
in ob_obj
.dupli_list
:
2809 if dp_obj
not in scene_data
.objects
:
2811 fbx_data_object_elements(objects
, dp_obj
, scene_data
)
2812 ob_obj
.dupli_list_clear()
2814 perfmon
.step("FBX export fetch remaining...")
2816 for ob_obj
in scene_data
.objects
:
2817 if not (ob_obj
.is_object
and ob_obj
.type == 'ARMATURE'):
2819 fbx_data_armature_elements(objects
, ob_obj
, scene_data
)
2821 if scene_data
.data_leaf_bones
:
2822 fbx_data_leaf_bone_elements(objects
, scene_data
)
2824 for mat
in scene_data
.data_materials
:
2825 fbx_data_material_elements(objects
, mat
, scene_data
)
2827 for tex
in scene_data
.data_textures
:
2828 fbx_data_texture_file_elements(objects
, tex
, scene_data
)
2830 for vid
in scene_data
.data_videos
:
2831 fbx_data_video_elements(objects
, vid
, scene_data
)
2833 perfmon
.step("FBX export fetch animations...")
2834 start_time
= time
.process_time()
2836 fbx_data_animation_elements(objects
, scene_data
)
2838 perfmon
.level_down()
2841 def fbx_connections_elements(root
, scene_data
):
2843 Relations between Objects (which material uses which texture, and so on).
2845 connections
= elem_empty(root
, b
"Connections")
2847 for c
in scene_data
.connections
:
2848 elem_connection(connections
, *c
)
2851 def fbx_takes_elements(root
, scene_data
):
2855 # XXX Pretty sure takes are no more needed...
2856 takes
= elem_empty(root
, b
"Takes")
2857 elem_data_single_string(takes
, b
"Current", b
"")
2859 animations
= scene_data
.animations
2860 for astack_key
, animations
, alayer_key
, name
, f_start
, f_end
in animations
:
2861 scene
= scene_data
.scene
2862 fps
= scene
.render
.fps
/ scene
.render
.fps_base
2863 start_ktime
= int(convert_sec_to_ktime(f_start
/ fps
))
2864 end_ktime
= int(convert_sec_to_ktime(f_end
/ fps
))
2866 take
= elem_data_single_string(takes
, b
"Take", name
)
2867 elem_data_single_string(take
, b
"FileName", name
+ b
".tak")
2868 take_loc_time
= elem_data_single_int64(take
, b
"LocalTime", start_ktime
)
2869 take_loc_time
.add_int64(end_ktime
)
2870 take_ref_time
= elem_data_single_int64(take
, b
"ReferenceTime", start_ktime
)
2871 take_ref_time
.add_int64(end_ktime
)
2874 # ##### "Main" functions. #####
2876 # This func can be called with just the filepath
2877 def save_single(operator
, scene
, filepath
="",
2878 global_matrix
=Matrix(),
2879 apply_unit_scale
=False,
2881 apply_scale_options
='FBX_SCALE_NONE',
2884 context_objects
=None,
2886 use_mesh_modifiers
=True,
2887 use_mesh_modifiers_render
=True,
2888 mesh_smooth_type
='FACE',
2889 use_armature_deform_only
=False,
2891 bake_anim_use_all_bones
=True,
2892 bake_anim_use_nla_strips
=True,
2893 bake_anim_use_all_actions
=True,
2895 bake_anim_simplify_factor
=1.0,
2896 bake_anim_force_startend_keying
=True,
2897 add_leaf_bones
=False,
2898 primary_bone_axis
='Y',
2899 secondary_bone_axis
='X',
2902 use_mesh_edges
=True,
2904 embed_textures
=False,
2905 use_custom_props
=False,
2906 bake_space_transform
=False,
2907 armature_nodetype
='NULL',
2911 # Clear cached ObjectWrappers (just in case...).
2912 ObjectWrapper
.cache_clear()
2914 if object_types
is None:
2915 object_types
= {'EMPTY', 'CAMERA', 'LAMP', 'ARMATURE', 'MESH', 'OTHER'}
2917 if 'OTHER' in object_types
:
2918 object_types |
= BLENDER_OTHER_OBJECT_TYPES
2920 # Default Blender unit is equivalent to meter, while FBX one is centimeter...
2921 unit_scale
= units_blender_to_fbx_factor(scene
) if apply_unit_scale
else 100.0
2922 if apply_scale_options
== 'FBX_SCALE_NONE':
2923 global_matrix
= Matrix
.Scale(unit_scale
* global_scale
, 4) * global_matrix
2925 elif apply_scale_options
== 'FBX_SCALE_UNITS':
2926 global_matrix
= Matrix
.Scale(global_scale
, 4) * global_matrix
2927 elif apply_scale_options
== 'FBX_SCALE_CUSTOM':
2928 global_matrix
= Matrix
.Scale(unit_scale
, 4) * global_matrix
2929 unit_scale
= global_scale
2930 else: # if apply_scale_options == 'FBX_SCALE_ALL':
2931 unit_scale
= global_scale
* unit_scale
2933 global_scale
= global_matrix
.median_scale
2934 global_matrix_inv
= global_matrix
.inverted()
2935 # For transforming mesh normals.
2936 global_matrix_inv_transposed
= global_matrix_inv
.transposed()
2938 # Only embed textures in COPY mode!
2939 if embed_textures
and path_mode
!= 'COPY':
2940 embed_textures
= False
2942 # Calcuate bone correction matrix
2943 bone_correction_matrix
= None # Default is None = no change
2944 bone_correction_matrix_inv
= None
2945 if (primary_bone_axis
, secondary_bone_axis
) != ('Y', 'X'):
2946 from bpy_extras
.io_utils
import axis_conversion
2947 bone_correction_matrix
= axis_conversion(from_forward
=secondary_bone_axis
,
2948 from_up
=primary_bone_axis
,
2952 bone_correction_matrix_inv
= bone_correction_matrix
.inverted()
2955 media_settings
= FBXExportSettingsMedia(
2957 os
.path
.dirname(bpy
.data
.filepath
), # base_src
2958 os
.path
.dirname(filepath
), # base_dst
2959 # Local dir where to put images (medias), using FBX conventions.
2960 os
.path
.splitext(os
.path
.basename(filepath
))[0] + ".fbm", # subdir
2963 set(), # embedded_set
2966 settings
= FBXExportSettings(
2967 operator
.report
, (axis_up
, axis_forward
), global_matrix
, global_scale
, apply_unit_scale
, unit_scale
,
2968 bake_space_transform
, global_matrix_inv
, global_matrix_inv_transposed
,
2969 context_objects
, object_types
, use_mesh_modifiers
, use_mesh_modifiers_render
,
2970 mesh_smooth_type
, use_mesh_edges
, use_tspace
,
2971 armature_nodetype
, use_armature_deform_only
,
2972 add_leaf_bones
, bone_correction_matrix
, bone_correction_matrix_inv
,
2973 bake_anim
, bake_anim_use_all_bones
, bake_anim_use_nla_strips
, bake_anim_use_all_actions
,
2974 bake_anim_step
, bake_anim_simplify_factor
, bake_anim_force_startend_keying
,
2975 False, media_settings
, use_custom_props
,
2978 import bpy_extras
.io_utils
2980 print('\nFBX export starting... %r' % filepath
)
2981 start_time
= time
.process_time()
2983 # Generate some data about exported scene...
2984 scene_data
= fbx_data_from_scene(scene
, settings
)
2986 root
= elem_empty(None, b
"") # Root element has no id, as it is not saved per se!
2988 # Mostly FBXHeaderExtension and GlobalSettings.
2989 fbx_header_elements(root
, scene_data
)
2991 # Documents and References are pretty much void currently.
2992 fbx_documents_elements(root
, scene_data
)
2993 fbx_references_elements(root
, scene_data
)
2995 # Templates definitions.
2996 fbx_definitions_elements(root
, scene_data
)
2999 fbx_objects_elements(root
, scene_data
)
3001 # How data are inter-connected.
3002 fbx_connections_elements(root
, scene_data
)
3005 fbx_takes_elements(root
, scene_data
)
3008 fbx_scene_data_cleanup(scene_data
)
3010 # And we are down, we can write the whole thing!
3011 encode_bin
.write(filepath
, root
, FBX_VERSION
)
3013 # Clear cached ObjectWrappers!
3014 ObjectWrapper
.cache_clear()
3016 # copy all collected files, if we did not embed them.
3017 if not media_settings
.embed_textures
:
3018 bpy_extras
.io_utils
.path_reference_copy(media_settings
.copy_set
)
3020 print('export finished in %.4f sec.' % (time
.process_time() - start_time
))
3024 # defaults for applications, currently only unity but could add others.
3025 def defaults_unity3d():
3027 # These options seem to produce the same result as the old Ascii exporter in Unity3D:
3028 "version": 'BIN7400',
3030 "axis_forward": '-Z',
3031 "global_matrix": Matrix
.Rotation(-math
.pi
/ 2.0, 4, 'X'),
3032 # Should really be True, but it can cause problems if a model is already in a scene or prefab
3033 # with the old transforms.
3034 "bake_space_transform": False,
3036 "use_selection": False,
3038 "object_types": {'ARMATURE', 'EMPTY', 'MESH', 'OTHER'},
3039 "use_mesh_modifiers": True,
3040 "use_mesh_modifiers_render": True,
3041 "use_mesh_edges": False,
3042 "mesh_smooth_type": 'FACE',
3043 "use_tspace": False, # XXX Why? Unity is expected to support tspace import...
3045 "use_armature_deform_only": True,
3047 "use_custom_props": True,
3050 "bake_anim_simplify_factor": 1.0,
3051 "bake_anim_step": 1.0,
3052 "bake_anim_use_nla_strips": True,
3053 "bake_anim_use_all_actions": True,
3054 "add_leaf_bones": False, # Avoid memory/performance cost for something only useful for modelling
3055 "primary_bone_axis": 'Y', # Doesn't really matter for Unity, so leave unchanged
3056 "secondary_bone_axis": 'X',
3058 "path_mode": 'AUTO',
3059 "embed_textures": False,
3060 "batch_mode": 'OFF',
3064 def save(operator
, context
,
3066 use_selection
=False,
3068 use_batch_own_dir
=False,
3072 This is a wrapper around save_single, which handles multi-scenes (or groups) cases, when batch-exporting a whole
3078 active_object
= context
.scene
.objects
.active
3081 if active_object
and active_object
.mode
!= 'OBJECT' and bpy
.ops
.object.mode_set
.poll():
3082 org_mode
= active_object
.mode
3083 bpy
.ops
.object.mode_set(mode
='OBJECT')
3085 if batch_mode
== 'OFF':
3086 kwargs_mod
= kwargs
.copy()
3088 kwargs_mod
["context_objects"] = context
.selected_objects
3090 kwargs_mod
["context_objects"] = context
.scene
.objects
3092 ret
= save_single(operator
, context
.scene
, filepath
, **kwargs_mod
)
3096 prefix
= os
.path
.basename(fbxpath
)
3098 fbxpath
= os
.path
.dirname(fbxpath
)
3100 if batch_mode
== 'GROUP':
3101 data_seq
= tuple(grp
for grp
in bpy
.data
.groups
if grp
.objects
)
3103 data_seq
= bpy
.data
.scenes
3105 # call this function within a loop with BATCH_ENABLE == False
3106 # no scene switching done at the moment.
3107 # orig_sce = context.scene
3109 new_fbxpath
= fbxpath
# own dir option modifies, we need to keep an original
3110 for data
in data_seq
: # scene or group
3111 newname
= "_".join((prefix
, bpy
.path
.clean_name(data
.name
))) if prefix
else bpy
.path
.clean_name(data
.name
)
3113 if use_batch_own_dir
:
3114 new_fbxpath
= os
.path
.join(fbxpath
, newname
)
3115 # path may already exist
3116 # TODO - might exist but be a file. unlikely but should probably account for it.
3118 if not os
.path
.exists(new_fbxpath
):
3119 os
.makedirs(new_fbxpath
)
3121 filepath
= os
.path
.join(new_fbxpath
, newname
+ '.fbx')
3123 print('\nBatch exporting %s as...\n\t%r' % (data
, filepath
))
3125 if batch_mode
== 'GROUP': # group
3126 # group, so objects update properly, add a dummy scene.
3127 scene
= bpy
.data
.scenes
.new(name
="FBX_Temp")
3128 scene
.layers
= [True] * 20
3129 # bpy.data.scenes.active = scene # XXX, cant switch
3130 src_scenes
= {} # Count how much each 'source' scenes are used.
3131 for ob_base
in data
.objects
:
3132 for src_sce
in ob_base
.users_scene
:
3133 if src_sce
not in src_scenes
:
3134 src_scenes
[src_sce
] = 0
3135 src_scenes
[src_sce
] += 1
3136 scene
.objects
.link(ob_base
)
3138 # Find the 'most used' source scene, and use its unit settings. This is somewhat weak, but should work
3139 # fine in most cases, and avoids stupid issues like T41931.
3140 best_src_scene
= None
3141 best_src_scene_users
= -1
3142 for sce
, nbr_users
in src_scenes
.items():
3143 if (nbr_users
) > best_src_scene_users
:
3144 best_src_scene_users
= nbr_users
3145 best_src_scene
= sce
3146 scene
.unit_settings
.system
= best_src_scene
.unit_settings
.system
3147 scene
.unit_settings
.system_rotation
= best_src_scene
.unit_settings
.system_rotation
3148 scene
.unit_settings
.scale_length
= best_src_scene
.unit_settings
.scale_length
3151 # TODO - BUMMER! Armatures not in the group wont animate the mesh
3155 kwargs_batch
= kwargs
.copy()
3156 kwargs_batch
["context_objects"] = data
.objects
3158 save_single(operator
, scene
, filepath
, **kwargs_batch
)
3160 if batch_mode
== 'GROUP':
3161 # remove temp group scene
3162 bpy
.data
.scenes
.remove(scene
)
3164 # no active scene changing!
3165 # bpy.data.scenes.active = orig_sce
3167 ret
= {'FINISHED'} # so the script wont run after we have batch exported.
3169 if active_object
and org_mode
and bpy
.ops
.object.mode_set
.poll():
3170 bpy
.ops
.object.mode_set(mode
=org_mode
)