FBX Export: Fix shapekeys w/ object materials
[blender-addons.git] / io_scene_fbx / export_fbx_bin.py
blobd161844f5b04e84ebd819bc12ddc80752aaa5a18
1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
19 # <pep8 compliant>
21 # Script copyright (C) Campbell Barton, Bastien Montagne
24 import array
25 import datetime
26 import math
27 import os
28 import time
30 from collections import OrderedDict
31 from itertools import zip_longest, chain
33 if "bpy" in locals():
34 import importlib
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)
42 import bpy
43 import bpy_extras
44 from mathutils import Vector, Matrix
46 from . import encode_bin, data_types, fbx_utils
47 from .fbx_utils import (
48 # Constants.
49 FBX_VERSION, FBX_HEADER_VERSION, FBX_SCENEINFO_VERSION, FBX_TEMPLATES_VERSION,
50 FBX_MODELS_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,
57 FBX_ANIM_KEY_VERSION,
58 FBX_ANIM_PROPSGROUP_NAME,
59 FBX_KTIME,
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.
64 PerfMon,
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,
69 # UUID from key.
70 get_fbx_uuid_from_key,
71 # Key generators.
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,
78 # FBX element data.
79 elem_empty,
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,
89 # Templates.
90 FBXTemplate, fbx_templates_generate,
91 # Animation.
92 AnimationCurveNodeWrapper,
93 # Objects.
94 ObjectWrapper, fbx_name_class,
95 # Top level.
96 FBXExportSettingsMedia, FBXExportSettings, FBXExportData,
99 # Units convertors!
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):
230 r = scene.render
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):
368 # WIP...
369 props = OrderedDict((
370 (b"ShadingModel", ("Phong", "p_string", False)),
371 (b"MultiLayer", (False, "p_bool", False)),
372 # Lambert-specific.
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)),
389 # Phong-specific.
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):
406 # WIP...
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):
434 # WIP...
435 props = OrderedDict((
436 # All pictures.
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).
441 # All videos.
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.
449 # Image sequences.
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)
524 e.add_int64(uid_src)
525 e.add_int64(uid_dst)
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)
545 elif list_val:
546 if len(list_val) == 3:
547 elem_props_set(props, "p_vector", k.encode(), list_val, custom=True)
548 else:
549 elem_props_set(props, "p_string", k.encode(), str(list_val), custom=True)
550 else:
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]
581 do_light = True
582 decay_type = FBX_LIGHT_DECAY_TYPES['CONSTANT']
583 do_shadow = False
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)
614 # Custom properties.
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
625 cam = cam_obj.bdata
626 cam_data = cam.data
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))
634 # Render settings.
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
644 # Film offset
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)
693 # Custom properties.
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...
712 if arm_obj is None:
713 arm_obj = me_obj
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!
736 mat_world_bones = {}
737 for bo_obj in bones:
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:
752 return
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]
759 channels = []
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
770 else:
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)
786 if write_normals:
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,
812 animatable=True)
815 def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
817 Write the Mesh (Geometry) data block.
819 # Ugly helper... :/
820 def _infinite_gen(val):
821 while 1:
822 yield 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:
828 return
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)
853 # Custom properties.
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)
859 # Vertex cos.
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)))
863 del t_co
865 # Polygon indices.
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))
882 del t_le
884 # Edges...
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
889 # for loose edges).
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)
893 edges_map = {}
894 edges_nbr = 0
895 if t_ls and t_pvi:
896 t_ls = set(t_ls)
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))
902 li = 0
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.
906 vi2 = vi_start
907 vi_start = vi_next
908 else:
909 vi2 = vi_next
911 e_key = (vi, vi2) if vi < vi2 else (vi2, vi)
912 if e_key in todo_edges:
913 t_eli.append(li)
914 todo_edges.remove(e_key)
915 edges_map[e_key] = edges_nbr
916 edges_nbr += 1
918 vi = vi_next
919 li = li_next
920 # End of edges!
922 # We have to ^-1 last index of each loop.
923 for ls in t_ls:
924 t_pvi[ls - 1] ^= -1
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)
929 del t_pvi
930 del t_ls
931 del t_eli
933 # And now, layers!
935 # Smoothing.
936 if smooth_type in {'FACE', 'EDGE'}:
937 t_ps = None
938 _map = b""
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)
942 _map = b"ByPolygon"
943 else: # EDGE
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
947 sharp_edges = set()
948 temp_sharp_edges = {}
949 for p in me.polygons:
950 if not p.use_smooth:
951 sharp_edges.update(p.edge_keys)
952 continue
953 for k in p.edge_keys:
954 if temp_sharp_edges.setdefault(k, 0) > 1:
955 sharp_edges.add(k)
956 else:
957 temp_sharp_edges[k] += 1
958 del temp_sharp_edges
959 for e in me.edges:
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))
963 _map = b"ByEdge"
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...
970 del t_ps
972 # TODO: Edge crease (LayerElementCrease).
974 # And we are done with edges!
975 del edges_map
977 # Loop normals.
978 tspacenumber = 0
979 if write_normals:
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)
988 if 0:
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))
1006 del ln2idx
1007 # del t_lnw
1008 else:
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)
1018 del t_ln
1020 # tspace
1021 if scene_data.settings.use_tspace:
1022 tspacenumber = len(me.uv_layers)
1023 if tspacenumber:
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):
1027 name = uvlayer.name
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)
1042 # Loop tangents.
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)
1055 del t_ln
1056 # del t_lnw
1057 me.free_tangents()
1059 me.free_normals_split()
1061 # Write VertexColor Layers.
1062 vcolnumber = len(me.vertex_colors)
1063 if vcolnumber:
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)))
1081 del col2idx
1082 del t_lc
1083 del _coltuples_gen
1085 # Write UV layers.
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)
1089 if uvnumber:
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)))
1107 del uv2idx
1108 del t_luv
1109 del _uvtuples_gen
1111 # Face's materials.
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)
1120 if nbr_mats > 1:
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)
1139 del t_pm
1140 else:
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)
1149 if write_normals:
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)
1153 if tspacenumber:
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)
1164 if vcolnumber:
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)
1168 if uvnumber:
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),
1179 fillvalue=0):
1180 layer = elem_data_single_int32(geom, b"Layer", max(vcolidx, uvidx))
1181 elem_data_single_int32(layer, b"Version", FBX_GEOMETRY_LAYER_VERSION)
1182 if vcolidx:
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)
1186 if uvidx:
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)
1190 if tspaceidx:
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)
1198 # Shape keys...
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
1221 mat_type = b"Phong"
1222 # Approximation...
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)
1238 if not skip_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)
1242 if not node_mat:
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
1309 if img.use_alpha:
1310 if tex.texture.use_calculate_alpha:
1311 alpha_source = 1 # RGBIntensity as alpha.
1312 else:
1313 alpha_source = 2 # Black, i.e. alpha channel.
1314 # BlendMode not useful for now, only affects layered textures afaics.
1315 mapping = 0 # UV.
1316 uvset = None
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'}:
1321 mapping = 4 # Box
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'}:
1327 mapping = 0 # 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")
1370 # XXX No Version???
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)
1387 else:
1388 filepath = bpy.path.abspath(vid.filepath)
1389 # We only ever embed a given file once!
1390 if filepath not in msetts.embedded_set:
1391 try:
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!
1400 #~ else:
1401 #~ elem_data_single_bytes(fbx_vid, b"Content", b"")
1404 def fbx_data_armature_elements(root, arm_obj, scene_data):
1406 Write:
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.
1410 * BindPose.
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
1418 # Bones "data".
1419 for bo_obj in bones:
1420 bo = bo_obj.bdata
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():
1446 # BindPose.
1447 mat_world_obj, mat_world_bones = fbx_data_bindpose_element(root, ob_obj, me, scene_data,
1448 arm_obj, mat_world_arm, bones)
1450 # Deformer.
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).
1459 ob = ob_obj.bdata
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():
1472 bo = bo_obj.bdata
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"")
1488 if indices:
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:
1506 # Bone 'data'...
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)
1517 # And bone object.
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
1545 # object type, etc.
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...
1560 if ob_obj.is_bone:
1561 obj_type = b"LimbNode"
1562 elif (ob_obj.type == 'ARMATURE'):
1563 if scene_data.settings.armature_nodetype == 'ROOT':
1564 obj_type = b"Root"
1565 elif scene_data.settings.armature_nodetype == 'LIMBNODE':
1566 obj_type = b"LimbNode"
1567 else: # Default, preferred option...
1568 obj_type = b"Null"
1569 elif (ob_obj.type in BLENDER_OBJECT_TYPES_MESHLIKE):
1570 obj_type = b"Mesh"
1571 elif (ob_obj.type == 'LAMP'):
1572 obj_type = b"Light"
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
1607 # object type, etc.
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
1634 if not animations:
1635 return
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)))
1643 # Animation stacks.
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():
1667 # Animation layer.
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!
1686 if keys:
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"")
1691 # key attributes...
1692 nbr_keys = len(keys)
1693 # flags...
1694 keyattr_flags = (
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).
1725 blend_to_fbx = (
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"),
1741 # Phong only.
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:
1776 bones[bo] = True
1777 bo_par = bo.parent
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!
1782 bones[bo] = False
1783 else:
1784 bones[bo] = True
1786 bones = OrderedDict((bo, None) for bo, use in bones.items() if use)
1788 if not bones:
1789 return
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):
1795 continue
1797 # Always handled by an Armature modifier...
1798 found = False
1799 for mod in ob_obj.bdata.modifiers:
1800 if mod.type not in {'ARMATURE'} or not mod.object:
1801 continue
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:
1804 found = True
1805 break
1807 if not found:
1808 continue
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),
1816 ob_obj, clusters)
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]
1837 leaf_bones = []
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")
1845 hide = parent.hide
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))
1855 return leaf_bones
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:
1872 continue
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:
1878 objects.add(dp_obj)
1879 ob_obj.dupli_list_clear()
1880 else:
1881 objects = scene_data.objects
1883 back_currframe = scene.frame_current
1884 animdata_ob = OrderedDict()
1885 p_rots = {}
1887 for ob_obj in objects:
1888 if ob_obj.parented_to_armature:
1889 continue
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:
1904 continue
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)
1911 currframe = f_start
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():
1939 for anim in anims:
1940 anim.simplify(simplify_fac, bake_step, force_keep)
1941 if not anim:
1942 continue
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)
1953 if not anim_shape:
1954 continue
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()
1965 if start_zero:
1966 f_end -= f_start
1967 f_start = 0.0
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
1977 animations = []
1978 animated = set()
1979 frame_start = 1e100
1980 frame_end = -1e100
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:
1990 frame_end = f_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:
1999 strips = []
2000 ob_actions = []
2001 for ob_obj in scene_data.objects:
2002 # NLA tracks only for objects, not bones!
2003 if not ob_obj.is_object:
2004 continue
2005 ob = ob_obj.bdata # Back to real Blender Object.
2006 if not ob.animation_data:
2007 continue
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:
2012 if track.mute:
2013 continue
2014 for strip in track.strips:
2015 if strip.mute:
2016 continue
2017 strips.append(strip)
2018 strip.mute = True
2020 for strip in strips:
2021 strip.mute = False
2022 add_anim(animations, animated,
2023 fbx_animations_do(scene_data, strip, strip.frame_start, strip.frame_end, True, force_keep=True))
2024 strip.mute = True
2025 scene.frame_set(scene.frame_current, 0.0)
2027 for strip in strips:
2028 strip.mute = False
2030 for ob, ob_act in ob_actions:
2031 ob.animation_data.action = ob_act
2033 # All actions.
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
2038 if fc.array_index:
2039 data_path = data_path + "[%d]" % fc.array_index
2040 try:
2041 path_resolve(data_path)
2042 except ValueError:
2043 return False # Invalid.
2044 return True # Valid.
2046 def restore_object(ob_to, ob_from):
2047 # Restore org state of object (ugh :/ ).
2048 props = (
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',
2062 for p in props:
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:
2069 continue
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... :/
2081 ob_copy = ob.copy()
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):
2092 continue
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))
2098 # Ugly! :/
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...
2130 perfmon = PerfMon()
2131 perfmon.level_up()
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:
2142 continue
2143 ob_obj = ObjectWrapper(ob)
2144 objects[ob_obj] = None
2145 # Duplis...
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:
2149 continue
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:
2169 continue
2170 ob = ob_obj.bdata
2171 use_org_data = True
2172 org_ob_obj = None
2174 # Do not want to systematically recreate a new mesh for dupliobject instances, kind of break purpose of those.
2175 if ob_obj.is_dupli:
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]
2179 continue
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)
2186 tmp_mods = []
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
2194 if mod.show_render:
2195 use_org_data = False
2196 if not use_org_data:
2197 tmp_me = ob.to_mesh(
2198 scene,
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
2206 if use_org_data:
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...")
2215 # 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...
2220 continue
2221 if me in data_deformers_shape:
2222 continue
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))
2229 sk_cos = {}
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... :/
2238 shape_verts_co = []
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.
2247 continue
2248 shape_verts_co.extend(Vector(sv_co) - Vector(ref_co))
2249 shape_verts_idx.append(idx)
2250 if not shape_verts_co:
2251 continue
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...")
2258 # Armatures!
2259 data_deformers_skin = OrderedDict()
2260 data_bones = OrderedDict()
2261 arm_parents = set()
2262 for ob_obj in tuple(objects):
2263 if not (ob_obj.is_object and ob_obj.type in {'ARMATURE'}):
2264 continue
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...
2276 if scene.world:
2277 data_world = OrderedDict(((scene.world, get_blenderID_key(scene.world)),))
2278 else:
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
2292 if mat is None:
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)
2301 else:
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):
2314 continue
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:
2317 continue
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'}:
2324 continue
2325 img = tex.texture.image
2326 if img is None:
2327 continue
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:
2331 continue
2332 tex_data = data_textures.get(tex)
2333 if tex_data is not None:
2334 tex_data[1][mat] = tex_fbx_props
2335 else:
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)
2340 else:
2341 data_videos[img] = (get_blenderID_key(img), [tex])
2343 perfmon.step("FBX export prepare: Wrapping Animations...")
2345 # Animation...
2346 animations = ()
2347 animated = set()
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(
2354 None, None, None,
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)
2369 if data_empties:
2370 templates[b"Null"] = fbx_template_def_null(scene, settings, nbr_users=len(data_empties))
2372 if data_lamps:
2373 templates[b"Light"] = fbx_template_def_light(scene, settings, nbr_users=len(data_lamps))
2375 if data_cameras:
2376 templates[b"Camera"] = fbx_template_def_camera(scene, settings, nbr_users=len(data_cameras))
2378 if data_bones:
2379 templates[b"Bone"] = fbx_template_def_bone(scene, settings, nbr_users=len(data_bones))
2381 if data_meshes:
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)
2387 if objects:
2388 templates[b"Model"] = fbx_template_def_model(scene, settings, nbr_users=len(objects))
2390 if arm_parents:
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:
2395 nbr = 0
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())
2402 assert(nbr != 0)
2403 templates[b"Deformers"] = fbx_template_def_deformer(scene, settings, nbr_users=nbr)
2405 # No world support in FBX...
2407 if data_world:
2408 templates[b"World"] = fbx_template_def_world(scene, settings, nbr_users=len(data_world))
2411 if data_materials:
2412 templates[b"Material"] = fbx_template_def_material(scene, settings, nbr_users=len(data_materials))
2414 if data_textures:
2415 templates[b"TextureFile"] = fbx_template_def_texture_file(scene, settings, nbr_users=len(data_textures))
2417 if data_videos:
2418 templates[b"Video"] = fbx_template_def_video(scene, settings, nbr_users=len(data_videos))
2420 if animations:
2421 nbr_astacks = len(animations)
2422 nbr_acnodes = 0
2423 nbr_acurves = 0
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():
2427 nbr_acnodes += 1
2428 for _acurve_key, _dval, acurve, acurve_valid in acnode.values():
2429 if acurve:
2430 nbr_acurves += 1
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...")
2445 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))
2455 else:
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:
2462 continue
2463 connections.append((b"OO", bo_obj.fbx_uuid, par_obj.fbx_uuid, None))
2465 # Object data.
2466 for ob_obj in objects:
2467 if ob_obj.is_bone:
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))
2470 else:
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))
2484 # Leaf Bones
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():
2491 # shape -> geometry
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():
2502 # skin -> geometry
2503 mesh_key, _me, _free = data_meshes[ob_obj]
2504 assert(me == _me)
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():
2507 # cluster -> skin
2508 connections.append((b"OO", get_fbx_uuid_from_key(clstr_key), get_fbx_uuid_from_key(skin_key), None))
2509 # bone -> cluster
2510 connections.append((b"OO", bo_obj.fbx_uuid, get_fbx_uuid_from_key(clstr_key), None))
2512 # Materials
2513 mesh_mat_indices = OrderedDict()
2514 _objs_indices = {}
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:
2524 continue
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
2528 del _objs_indices
2530 # Textures
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))
2538 # Images
2539 for vid, (vid_key, texs) in data_videos.items():
2540 for tex in texs:
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))
2544 # Animations
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():
2563 if acurve:
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.
2585 done_meshes = set()
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
2603 import addon_utils
2604 import sys
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)
2614 # No encryption!
2615 elem_data_single_int32(header_ext, b"EncryptionType", 0)
2617 if time is None:
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)
2691 #~ else:
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.
2709 r = scene.render
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):
2714 fbx_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.).
2774 perfmon = PerfMon()
2775 perfmon.level_up()
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()}))
2796 done_meshes = set()
2797 for me_obj in scene_data.data_meshes:
2798 fbx_data_mesh_elements(objects, me_obj, scene_data, done_meshes)
2799 del done_meshes
2801 perfmon.step("FBX export fetch objects (%d)..." % len(scene_data.objects))
2803 for ob_obj in scene_data.objects:
2804 if ob_obj.is_dupli:
2805 continue
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:
2810 continue
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'):
2818 continue
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):
2853 Animations.
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,
2880 global_scale=1.0,
2881 apply_scale_options='FBX_SCALE_NONE',
2882 axis_up="Z",
2883 axis_forward="Y",
2884 context_objects=None,
2885 object_types=None,
2886 use_mesh_modifiers=True,
2887 use_mesh_modifiers_render=True,
2888 mesh_smooth_type='FACE',
2889 use_armature_deform_only=False,
2890 bake_anim=True,
2891 bake_anim_use_all_bones=True,
2892 bake_anim_use_nla_strips=True,
2893 bake_anim_use_all_actions=True,
2894 bake_anim_step=1.0,
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',
2900 use_metadata=True,
2901 path_mode='AUTO',
2902 use_mesh_edges=True,
2903 use_tspace=True,
2904 embed_textures=False,
2905 use_custom_props=False,
2906 bake_space_transform=False,
2907 armature_nodetype='NULL',
2908 **kwargs
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
2924 unit_scale = 1.0
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,
2949 to_forward='X',
2950 to_up='Y',
2951 ).to_4x4()
2952 bone_correction_matrix_inv = bone_correction_matrix.inverted()
2955 media_settings = FBXExportSettingsMedia(
2956 path_mode,
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
2961 embed_textures,
2962 set(), # copy_set
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)
2998 # Actual data.
2999 fbx_objects_elements(root, scene_data)
3001 # How data are inter-connected.
3002 fbx_connections_elements(root, scene_data)
3004 # Animation.
3005 fbx_takes_elements(root, scene_data)
3007 # Cleanup!
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))
3021 return {'FINISHED'}
3024 # defaults for applications, currently only unity but could add others.
3025 def defaults_unity3d():
3026 return {
3027 # These options seem to produce the same result as the old Ascii exporter in Unity3D:
3028 "version": 'BIN7400',
3029 "axis_up": 'Y',
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,
3049 "bake_anim": 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,
3065 filepath="",
3066 use_selection=False,
3067 batch_mode='OFF',
3068 use_batch_own_dir=False,
3069 **kwargs
3072 This is a wrapper around save_single, which handles multi-scenes (or groups) cases, when batch-exporting a whole
3073 .blend file.
3076 ret = None
3078 active_object = context.scene.objects.active
3080 org_mode = None
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()
3087 if use_selection:
3088 kwargs_mod["context_objects"] = context.selected_objects
3089 else:
3090 kwargs_mod["context_objects"] = context.scene.objects
3092 ret = save_single(operator, context.scene, filepath, **kwargs_mod)
3093 else:
3094 fbxpath = filepath
3096 prefix = os.path.basename(fbxpath)
3097 if prefix:
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)
3102 else:
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
3150 scene.update()
3151 # TODO - BUMMER! Armatures not in the group wont animate the mesh
3152 else:
3153 scene = data
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)
3172 return ret