1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
21 # Script copyright (C) Campbell Barton, Bastien Montagne
30 from collections
import OrderedDict
31 from itertools
import zip_longest
, chain
35 if "encode_bin" in locals():
36 importlib
.reload(encode_bin
)
37 if "data_types" in locals():
38 importlib
.reload(data_types
)
39 if "fbx_utils" in locals():
40 importlib
.reload(fbx_utils
)
44 from mathutils
import Vector
, Matrix
46 from . import encode_bin
, data_types
, fbx_utils
47 from .fbx_utils
import (
49 FBX_VERSION
, FBX_HEADER_VERSION
, FBX_SCENEINFO_VERSION
, FBX_TEMPLATES_VERSION
,
51 FBX_GEOMETRY_VERSION
, FBX_GEOMETRY_NORMAL_VERSION
, FBX_GEOMETRY_BINORMAL_VERSION
, FBX_GEOMETRY_TANGENT_VERSION
,
52 FBX_GEOMETRY_SMOOTHING_VERSION
, FBX_GEOMETRY_VCOLOR_VERSION
, FBX_GEOMETRY_UV_VERSION
,
53 FBX_GEOMETRY_MATERIAL_VERSION
, FBX_GEOMETRY_LAYER_VERSION
,
54 FBX_GEOMETRY_SHAPE_VERSION
, FBX_DEFORMER_SHAPE_VERSION
, FBX_DEFORMER_SHAPECHANNEL_VERSION
,
55 FBX_POSE_BIND_VERSION
, FBX_DEFORMER_SKIN_VERSION
, FBX_DEFORMER_CLUSTER_VERSION
,
56 FBX_MATERIAL_VERSION
, FBX_TEXTURE_VERSION
,
58 FBX_ANIM_PROPSGROUP_NAME
,
60 BLENDER_OTHER_OBJECT_TYPES
, BLENDER_OBJECT_TYPES_MESHLIKE
,
61 FBX_LIGHT_TYPES
, FBX_LIGHT_DECAY_TYPES
,
62 RIGHT_HAND_AXES
, FBX_FRAMERATES
,
63 # Miscellaneous utils.
64 units_convertor
, units_convertor_iter
, matrix4_to_array
, similar_values
, similar_values_iter
,
66 get_fbx_uuid_from_key
,
68 get_blenderID_key
, get_blenderID_name
,
69 get_blender_mesh_shape_key
, get_blender_mesh_shape_channel_key
,
70 get_blender_empty_key
, get_blender_bone_key
,
71 get_blender_bindpose_key
, get_blender_armature_skin_key
, get_blender_bone_cluster_key
,
72 get_blender_anim_id_base
, get_blender_anim_stack_key
, get_blender_anim_layer_key
,
73 get_blender_anim_curve_node_key
, get_blender_anim_curve_key
,
76 elem_data_single_bool
, elem_data_single_int16
, elem_data_single_int32
, elem_data_single_int64
,
77 elem_data_single_float32
, elem_data_single_float64
,
78 elem_data_single_bytes
, elem_data_single_string
, elem_data_single_string_unicode
,
79 elem_data_single_bool_array
, elem_data_single_int32_array
, elem_data_single_int64_array
,
80 elem_data_single_float32_array
, elem_data_single_float64_array
, elem_data_vec_float64
,
81 # FBX element properties.
82 elem_properties
, elem_props_set
, elem_props_compound
,
83 # FBX element properties handling templates.
84 elem_props_template_init
, elem_props_template_set
, elem_props_template_finalize
,
86 FBXTemplate
, fbx_templates_generate
,
88 AnimationCurveNodeWrapper
,
90 ObjectWrapper
, fbx_name_class
,
92 FBXExportSettingsMedia
, FBXExportSettings
, FBXExportData
,
96 convert_sec_to_ktime
= units_convertor("second", "ktime")
97 convert_sec_to_ktime_iter
= units_convertor_iter("second", "ktime")
99 convert_mm_to_inch
= units_convertor("millimeter", "inch")
101 convert_rad_to_deg
= units_convertor("radian", "degree")
102 convert_rad_to_deg_iter
= units_convertor_iter("radian", "degree")
105 # ##### Templates #####
106 # TODO: check all those "default" values, they should match Blender's default as much as possible, I guess?
108 def fbx_template_def_globalsettings(scene
, settings
, override_defaults
=None, nbr_users
=0):
109 props
= OrderedDict()
110 if override_defaults
is not None:
111 props
.update(override_defaults
)
112 return FBXTemplate(b
"GlobalSettings", b
"", props
, nbr_users
, [False])
115 def fbx_template_def_model(scene
, settings
, override_defaults
=None, nbr_users
=0):
116 gscale
= settings
.global_scale
117 props
= OrderedDict((
118 # Name, Value, Type, Animatable
119 (b
"QuaternionInterpolate", (0, "p_enum", False)), # 0 = no quat interpolation.
120 (b
"RotationOffset", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
121 (b
"RotationPivot", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
122 (b
"ScalingOffset", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
123 (b
"ScalingPivot", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
124 (b
"TranslationActive", (False, "p_bool", False)),
125 (b
"TranslationMin", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
126 (b
"TranslationMax", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
127 (b
"TranslationMinX", (False, "p_bool", False)),
128 (b
"TranslationMinY", (False, "p_bool", False)),
129 (b
"TranslationMinZ", (False, "p_bool", False)),
130 (b
"TranslationMaxX", (False, "p_bool", False)),
131 (b
"TranslationMaxY", (False, "p_bool", False)),
132 (b
"TranslationMaxZ", (False, "p_bool", False)),
133 (b
"RotationOrder", (0, "p_enum", False)), # we always use 'XYZ' order.
134 (b
"RotationSpaceForLimitOnly", (False, "p_bool", False)),
135 (b
"RotationStiffnessX", (0.0, "p_double", False)),
136 (b
"RotationStiffnessY", (0.0, "p_double", False)),
137 (b
"RotationStiffnessZ", (0.0, "p_double", False)),
138 (b
"AxisLen", (10.0, "p_double", False)),
139 (b
"PreRotation", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
140 (b
"PostRotation", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
141 (b
"RotationActive", (False, "p_bool", False)),
142 (b
"RotationMin", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
143 (b
"RotationMax", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
144 (b
"RotationMinX", (False, "p_bool", False)),
145 (b
"RotationMinY", (False, "p_bool", False)),
146 (b
"RotationMinZ", (False, "p_bool", False)),
147 (b
"RotationMaxX", (False, "p_bool", False)),
148 (b
"RotationMaxY", (False, "p_bool", False)),
149 (b
"RotationMaxZ", (False, "p_bool", False)),
150 (b
"InheritType", (0, "p_enum", False)), # RrSs
151 (b
"ScalingActive", (False, "p_bool", False)),
152 (b
"ScalingMin", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
153 (b
"ScalingMax", ((1.0, 1.0, 1.0), "p_vector_3d", False)),
154 (b
"ScalingMinX", (False, "p_bool", False)),
155 (b
"ScalingMinY", (False, "p_bool", False)),
156 (b
"ScalingMinZ", (False, "p_bool", False)),
157 (b
"ScalingMaxX", (False, "p_bool", False)),
158 (b
"ScalingMaxY", (False, "p_bool", False)),
159 (b
"ScalingMaxZ", (False, "p_bool", False)),
160 (b
"GeometricTranslation", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
161 (b
"GeometricRotation", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
162 (b
"GeometricScaling", ((1.0, 1.0, 1.0), "p_vector_3d", False)),
163 (b
"MinDampRangeX", (0.0, "p_double", False)),
164 (b
"MinDampRangeY", (0.0, "p_double", False)),
165 (b
"MinDampRangeZ", (0.0, "p_double", False)),
166 (b
"MaxDampRangeX", (0.0, "p_double", False)),
167 (b
"MaxDampRangeY", (0.0, "p_double", False)),
168 (b
"MaxDampRangeZ", (0.0, "p_double", False)),
169 (b
"MinDampStrengthX", (0.0, "p_double", False)),
170 (b
"MinDampStrengthY", (0.0, "p_double", False)),
171 (b
"MinDampStrengthZ", (0.0, "p_double", False)),
172 (b
"MaxDampStrengthX", (0.0, "p_double", False)),
173 (b
"MaxDampStrengthY", (0.0, "p_double", False)),
174 (b
"MaxDampStrengthZ", (0.0, "p_double", False)),
175 (b
"PreferedAngleX", (0.0, "p_double", False)),
176 (b
"PreferedAngleY", (0.0, "p_double", False)),
177 (b
"PreferedAngleZ", (0.0, "p_double", False)),
178 (b
"LookAtProperty", (None, "p_object", False)),
179 (b
"UpVectorProperty", (None, "p_object", False)),
180 (b
"Show", (True, "p_bool", False)),
181 (b
"NegativePercentShapeSupport", (True, "p_bool", False)),
182 (b
"DefaultAttributeIndex", (-1, "p_integer", False)),
183 (b
"Freeze", (False, "p_bool", False)),
184 (b
"LODBox", (False, "p_bool", False)),
185 (b
"Lcl Translation", ((0.0, 0.0, 0.0), "p_lcl_translation", True)),
186 (b
"Lcl Rotation", ((0.0, 0.0, 0.0), "p_lcl_rotation", True)),
187 (b
"Lcl Scaling", ((1.0, 1.0, 1.0), "p_lcl_scaling", True)),
188 (b
"Visibility", (1.0, "p_visibility", True)),
189 (b
"Visibility Inheritance", (1, "p_visibility_inheritance", False)),
191 if override_defaults
is not None:
192 props
.update(override_defaults
)
193 return FBXTemplate(b
"Model", b
"FbxNode", props
, nbr_users
, [False])
196 def fbx_template_def_null(scene
, settings
, override_defaults
=None, nbr_users
=0):
197 props
= OrderedDict((
198 (b
"Color", ((0.8, 0.8, 0.8), "p_color_rgb", False)),
199 (b
"Size", (100.0, "p_double", False)),
200 (b
"Look", (1, "p_enum", False)), # Cross (0 is None, i.e. invisible?).
202 if override_defaults
is not None:
203 props
.update(override_defaults
)
204 return FBXTemplate(b
"NodeAttribute", b
"FbxNull", props
, nbr_users
, [False])
207 def fbx_template_def_light(scene
, settings
, override_defaults
=None, nbr_users
=0):
208 gscale
= settings
.global_scale
209 props
= OrderedDict((
210 (b
"LightType", (0, "p_enum", False)), # Point light.
211 (b
"CastLight", (True, "p_bool", False)),
212 (b
"Color", ((1.0, 1.0, 1.0), "p_color", True)),
213 (b
"Intensity", (100.0, "p_number", True)), # Times 100 compared to Blender values...
214 (b
"DecayType", (2, "p_enum", False)), # Quadratic.
215 (b
"DecayStart", (30.0 * gscale
, "p_double", False)),
216 (b
"CastShadows", (True, "p_bool", False)),
217 (b
"ShadowColor", ((0.0, 0.0, 0.0), "p_color", True)),
218 (b
"AreaLightShape", (0, "p_enum", False)), # Rectangle.
220 if override_defaults
is not None:
221 props
.update(override_defaults
)
222 return FBXTemplate(b
"NodeAttribute", b
"FbxLight", props
, nbr_users
, [False])
225 def fbx_template_def_camera(scene
, settings
, override_defaults
=None, nbr_users
=0):
227 props
= OrderedDict((
228 (b
"Color", ((0.8, 0.8, 0.8), "p_color_rgb", False)),
229 (b
"Position", ((0.0, 0.0, -50.0), "p_vector", True)),
230 (b
"UpVector", ((0.0, 1.0, 0.0), "p_vector", True)),
231 (b
"InterestPosition", ((0.0, 0.0, 0.0), "p_vector", True)),
232 (b
"Roll", (0.0, "p_roll", True)),
233 (b
"OpticalCenterX", (0.0, "p_opticalcenterx", True)),
234 (b
"OpticalCenterY", (0.0, "p_opticalcentery", True)),
235 (b
"BackgroundColor", ((0.63, 0.63, 0.63), "p_color", True)),
236 (b
"TurnTable", (0.0, "p_number", True)),
237 (b
"DisplayTurnTableIcon", (False, "p_bool", False)),
238 (b
"UseMotionBlur", (False, "p_bool", False)),
239 (b
"UseRealTimeMotionBlur", (True, "p_bool", False)),
240 (b
"Motion Blur Intensity", (1.0, "p_number", True)),
241 (b
"AspectRatioMode", (0, "p_enum", False)), # WindowSize.
242 (b
"AspectWidth", (320.0, "p_double", False)),
243 (b
"AspectHeight", (200.0, "p_double", False)),
244 (b
"PixelAspectRatio", (1.0, "p_double", False)),
245 (b
"FilmOffsetX", (0.0, "p_number", True)),
246 (b
"FilmOffsetY", (0.0, "p_number", True)),
247 (b
"FilmWidth", (0.816, "p_double", False)),
248 (b
"FilmHeight", (0.612, "p_double", False)),
249 (b
"FilmAspectRatio", (1.3333333333333333, "p_double", False)),
250 (b
"FilmSqueezeRatio", (1.0, "p_double", False)),
251 (b
"FilmFormatIndex", (0, "p_enum", False)), # Assuming this is ApertureFormat, 0 = custom.
252 (b
"PreScale", (1.0, "p_number", True)),
253 (b
"FilmTranslateX", (0.0, "p_number", True)),
254 (b
"FilmTranslateY", (0.0, "p_number", True)),
255 (b
"FilmRollPivotX", (0.0, "p_number", True)),
256 (b
"FilmRollPivotY", (0.0, "p_number", True)),
257 (b
"FilmRollValue", (0.0, "p_number", True)),
258 (b
"FilmRollOrder", (0, "p_enum", False)), # 0 = rotate first (default).
259 (b
"ApertureMode", (2, "p_enum", False)), # 2 = Vertical.
260 (b
"GateFit", (0, "p_enum", False)), # 0 = no resolution gate fit.
261 (b
"FieldOfView", (25.114999771118164, "p_fov", True)),
262 (b
"FieldOfViewX", (40.0, "p_fov_x", True)),
263 (b
"FieldOfViewY", (40.0, "p_fov_y", True)),
264 (b
"FocalLength", (34.89327621672628, "p_number", True)),
265 (b
"CameraFormat", (0, "p_enum", False)), # Custom camera format.
266 (b
"UseFrameColor", (False, "p_bool", False)),
267 (b
"FrameColor", ((0.3, 0.3, 0.3), "p_color_rgb", False)),
268 (b
"ShowName", (True, "p_bool", False)),
269 (b
"ShowInfoOnMoving", (True, "p_bool", False)),
270 (b
"ShowGrid", (True, "p_bool", False)),
271 (b
"ShowOpticalCenter", (False, "p_bool", False)),
272 (b
"ShowAzimut", (True, "p_bool", False)),
273 (b
"ShowTimeCode", (False, "p_bool", False)),
274 (b
"ShowAudio", (False, "p_bool", False)),
275 (b
"AudioColor", ((0.0, 1.0, 0.0), "p_vector_3d", False)), # Yep, vector3d, not corlorgb… :cry:
276 (b
"NearPlane", (10.0, "p_double", False)),
277 (b
"FarPlane", (4000.0, "p_double", False)),
278 (b
"AutoComputeClipPanes", (False, "p_bool", False)),
279 (b
"ViewCameraToLookAt", (True, "p_bool", False)),
280 (b
"ViewFrustumNearFarPlane", (False, "p_bool", False)),
281 (b
"ViewFrustumBackPlaneMode", (2, "p_enum", False)), # 2 = show back plane if texture added.
282 (b
"BackPlaneDistance", (4000.0, "p_number", True)),
283 (b
"BackPlaneDistanceMode", (1, "p_enum", False)), # 1 = relative to camera.
284 (b
"ViewFrustumFrontPlaneMode", (2, "p_enum", False)), # 2 = show front plane if texture added.
285 (b
"FrontPlaneDistance", (10.0, "p_number", True)),
286 (b
"FrontPlaneDistanceMode", (1, "p_enum", False)), # 1 = relative to camera.
287 (b
"LockMode", (False, "p_bool", False)),
288 (b
"LockInterestNavigation", (False, "p_bool", False)),
289 # BackPlate... properties **arggggg!**
290 (b
"FitImage", (False, "p_bool", False)),
291 (b
"Crop", (False, "p_bool", False)),
292 (b
"Center", (True, "p_bool", False)),
293 (b
"KeepRatio", (True, "p_bool", False)),
294 # End of BackPlate...
295 (b
"BackgroundAlphaTreshold", (0.5, "p_double", False)),
296 (b
"ShowBackplate", (True, "p_bool", False)),
297 (b
"BackPlaneOffsetX", (0.0, "p_number", True)),
298 (b
"BackPlaneOffsetY", (0.0, "p_number", True)),
299 (b
"BackPlaneRotation", (0.0, "p_number", True)),
300 (b
"BackPlaneScaleX", (1.0, "p_number", True)),
301 (b
"BackPlaneScaleY", (1.0, "p_number", True)),
302 (b
"Background Texture", (None, "p_object", False)),
303 (b
"FrontPlateFitImage", (True, "p_bool", False)),
304 (b
"FrontPlateCrop", (False, "p_bool", False)),
305 (b
"FrontPlateCenter", (True, "p_bool", False)),
306 (b
"FrontPlateKeepRatio", (True, "p_bool", False)),
307 (b
"Foreground Opacity", (1.0, "p_double", False)),
308 (b
"ShowFrontplate", (True, "p_bool", False)),
309 (b
"FrontPlaneOffsetX", (0.0, "p_number", True)),
310 (b
"FrontPlaneOffsetY", (0.0, "p_number", True)),
311 (b
"FrontPlaneRotation", (0.0, "p_number", True)),
312 (b
"FrontPlaneScaleX", (1.0, "p_number", True)),
313 (b
"FrontPlaneScaleY", (1.0, "p_number", True)),
314 (b
"Foreground Texture", (None, "p_object", False)),
315 (b
"DisplaySafeArea", (False, "p_bool", False)),
316 (b
"DisplaySafeAreaOnRender", (False, "p_bool", False)),
317 (b
"SafeAreaDisplayStyle", (1, "p_enum", False)), # 1 = rounded corners.
318 (b
"SafeAreaAspectRatio", (1.3333333333333333, "p_double", False)),
319 (b
"Use2DMagnifierZoom", (False, "p_bool", False)),
320 (b
"2D Magnifier Zoom", (100.0, "p_number", True)),
321 (b
"2D Magnifier X", (50.0, "p_number", True)),
322 (b
"2D Magnifier Y", (50.0, "p_number", True)),
323 (b
"CameraProjectionType", (0, "p_enum", False)), # 0 = perspective, 1 = orthogonal.
324 (b
"OrthoZoom", (1.0, "p_double", False)),
325 (b
"UseRealTimeDOFAndAA", (False, "p_bool", False)),
326 (b
"UseDepthOfField", (False, "p_bool", False)),
327 (b
"FocusSource", (0, "p_enum", False)), # 0 = camera interest, 1 = distance from camera interest.
328 (b
"FocusAngle", (3.5, "p_double", False)), # ???
329 (b
"FocusDistance", (200.0, "p_double", False)),
330 (b
"UseAntialiasing", (False, "p_bool", False)),
331 (b
"AntialiasingIntensity", (0.77777, "p_double", False)),
332 (b
"AntialiasingMethod", (0, "p_enum", False)), # 0 = oversampling, 1 = hardware.
333 (b
"UseAccumulationBuffer", (False, "p_bool", False)),
334 (b
"FrameSamplingCount", (7, "p_integer", False)),
335 (b
"FrameSamplingType", (1, "p_enum", False)), # 0 = uniform, 1 = stochastic.
337 if override_defaults
is not None:
338 props
.update(override_defaults
)
339 return FBXTemplate(b
"NodeAttribute", b
"FbxCamera", props
, nbr_users
, [False])
342 def fbx_template_def_bone(scene
, settings
, override_defaults
=None, nbr_users
=0):
343 props
= OrderedDict()
344 if override_defaults
is not None:
345 props
.update(override_defaults
)
346 return FBXTemplate(b
"NodeAttribute", b
"LimbNode", props
, nbr_users
, [False])
349 def fbx_template_def_geometry(scene
, settings
, override_defaults
=None, nbr_users
=0):
350 props
= OrderedDict((
351 (b
"Color", ((0.8, 0.8, 0.8), "p_color_rgb", False)),
352 (b
"BBoxMin", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
353 (b
"BBoxMax", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
354 (b
"Primary Visibility", (True, "p_bool", False)),
355 (b
"Casts Shadows", (True, "p_bool", False)),
356 (b
"Receive Shadows", (True, "p_bool", False)),
358 if override_defaults
is not None:
359 props
.update(override_defaults
)
360 return FBXTemplate(b
"Geometry", b
"FbxMesh", props
, nbr_users
, [False])
363 def fbx_template_def_material(scene
, settings
, override_defaults
=None, nbr_users
=0):
365 props
= OrderedDict((
366 (b
"ShadingModel", ("Phong", "p_string", False)),
367 (b
"MultiLayer", (False, "p_bool", False)),
369 (b
"EmissiveColor", ((0.0, 0.0, 0.0), "p_color", True)),
370 (b
"EmissiveFactor", (1.0, "p_number", True)),
371 (b
"AmbientColor", ((0.2, 0.2, 0.2), "p_color", True)),
372 (b
"AmbientFactor", (1.0, "p_number", True)),
373 (b
"DiffuseColor", ((0.8, 0.8, 0.8), "p_color", True)),
374 (b
"DiffuseFactor", (1.0, "p_number", True)),
375 (b
"TransparentColor", ((0.0, 0.0, 0.0), "p_color", True)),
376 (b
"TransparencyFactor", (0.0, "p_number", True)),
377 (b
"Opacity", (1.0, "p_number", True)),
378 (b
"NormalMap", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
379 (b
"Bump", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
380 (b
"BumpFactor", (1.0, "p_double", False)),
381 (b
"DisplacementColor", ((0.0, 0.0, 0.0), "p_color_rgb", False)),
382 (b
"DisplacementFactor", (1.0, "p_double", False)),
383 (b
"VectorDisplacementColor", ((0.0, 0.0, 0.0), "p_color_rgb", False)),
384 (b
"VectorDisplacementFactor", (1.0, "p_double", False)),
386 (b
"SpecularColor", ((0.2, 0.2, 0.2), "p_color", True)),
387 (b
"SpecularFactor", (1.0, "p_number", True)),
388 # Not sure about the name, importer uses this (but ShininessExponent for tex prop name!)
389 # And in fbx exported by sdk, you have one in template, the other in actual material!!! :/
390 # For now, using both.
391 (b
"Shininess", (20.0, "p_number", True)),
392 (b
"ShininessExponent", (20.0, "p_number", True)),
393 (b
"ReflectionColor", ((0.0, 0.0, 0.0), "p_color", True)),
394 (b
"ReflectionFactor", (1.0, "p_number", True)),
396 if override_defaults
is not None:
397 props
.update(override_defaults
)
398 return FBXTemplate(b
"Material", b
"FbxSurfacePhong", props
, nbr_users
, [False])
401 def fbx_template_def_texture_file(scene
, settings
, override_defaults
=None, nbr_users
=0):
403 # XXX Not sure about all names!
404 props
= OrderedDict((
405 (b
"TextureTypeUse", (0, "p_enum", False)), # Standard.
406 (b
"AlphaSource", (2, "p_enum", False)), # Black (i.e. texture's alpha), XXX name guessed!.
407 (b
"Texture alpha", (1.0, "p_double", False)),
408 (b
"PremultiplyAlpha", (True, "p_bool", False)),
409 (b
"CurrentTextureBlendMode", (1, "p_enum", False)), # Additive...
410 (b
"CurrentMappingType", (0, "p_enum", False)), # UV.
411 (b
"UVSet", ("default", "p_string", False)), # UVMap name.
412 (b
"WrapModeU", (0, "p_enum", False)), # Repeat.
413 (b
"WrapModeV", (0, "p_enum", False)), # Repeat.
414 (b
"UVSwap", (False, "p_bool", False)),
415 (b
"Translation", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
416 (b
"Rotation", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
417 (b
"Scaling", ((1.0, 1.0, 1.0), "p_vector_3d", False)),
418 (b
"TextureRotationPivot", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
419 (b
"TextureScalingPivot", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
420 # Not sure about those two...
421 (b
"UseMaterial", (False, "p_bool", False)),
422 (b
"UseMipMap", (False, "p_bool", False)),
424 if override_defaults
is not None:
425 props
.update(override_defaults
)
426 return FBXTemplate(b
"Texture", b
"FbxFileTexture", props
, nbr_users
, [False])
429 def fbx_template_def_video(scene
, settings
, override_defaults
=None, nbr_users
=0):
431 props
= OrderedDict((
433 (b
"Width", (0, "p_integer", False)),
434 (b
"Height", (0, "p_integer", False)),
435 (b
"Path", ("", "p_string_url", False)),
436 (b
"AccessMode", (0, "p_enum", False)), # Disk (0=Disk, 1=Mem, 2=DiskAsync).
438 (b
"StartFrame", (0, "p_integer", False)),
439 (b
"StopFrame", (0, "p_integer", False)),
440 (b
"Offset", (0, "p_timestamp", False)),
441 (b
"PlaySpeed", (0.0, "p_double", False)),
442 (b
"FreeRunning", (False, "p_bool", False)),
443 (b
"Loop", (False, "p_bool", False)),
444 (b
"InterlaceMode", (0, "p_enum", False)), # None, i.e. progressive.
446 (b
"ImageSequence", (False, "p_bool", False)),
447 (b
"ImageSequenceOffset", (0, "p_integer", False)),
448 (b
"FrameRate", (0.0, "p_double", False)),
449 (b
"LastFrame", (0, "p_integer", False)),
451 if override_defaults
is not None:
452 props
.update(override_defaults
)
453 return FBXTemplate(b
"Video", b
"FbxVideo", props
, nbr_users
, [False])
456 def fbx_template_def_pose(scene
, settings
, override_defaults
=None, nbr_users
=0):
457 props
= OrderedDict()
458 if override_defaults
is not None:
459 props
.update(override_defaults
)
460 return FBXTemplate(b
"Pose", b
"", props
, nbr_users
, [False])
463 def fbx_template_def_deformer(scene
, settings
, override_defaults
=None, nbr_users
=0):
464 props
= OrderedDict()
465 if override_defaults
is not None:
466 props
.update(override_defaults
)
467 return FBXTemplate(b
"Deformer", b
"", props
, nbr_users
, [False])
470 def fbx_template_def_animstack(scene
, settings
, override_defaults
=None, nbr_users
=0):
471 props
= OrderedDict((
472 (b
"Description", ("", "p_string", False)),
473 (b
"LocalStart", (0, "p_timestamp", False)),
474 (b
"LocalStop", (0, "p_timestamp", False)),
475 (b
"ReferenceStart", (0, "p_timestamp", False)),
476 (b
"ReferenceStop", (0, "p_timestamp", False)),
478 if override_defaults
is not None:
479 props
.update(override_defaults
)
480 return FBXTemplate(b
"AnimationStack", b
"FbxAnimStack", props
, nbr_users
, [False])
483 def fbx_template_def_animlayer(scene
, settings
, override_defaults
=None, nbr_users
=0):
484 props
= OrderedDict((
485 (b
"Weight", (100.0, "p_number", True)),
486 (b
"Mute", (False, "p_bool", False)),
487 (b
"Solo", (False, "p_bool", False)),
488 (b
"Lock", (False, "p_bool", False)),
489 (b
"Color", ((0.8, 0.8, 0.8), "p_color_rgb", False)),
490 (b
"BlendMode", (0, "p_enum", False)),
491 (b
"RotationAccumulationMode", (0, "p_enum", False)),
492 (b
"ScaleAccumulationMode", (0, "p_enum", False)),
493 (b
"BlendModeBypass", (0, "p_ulonglong", False)),
495 if override_defaults
is not None:
496 props
.update(override_defaults
)
497 return FBXTemplate(b
"AnimationLayer", b
"FbxAnimLayer", props
, nbr_users
, [False])
500 def fbx_template_def_animcurvenode(scene
, settings
, override_defaults
=None, nbr_users
=0):
501 props
= OrderedDict((
502 (FBX_ANIM_PROPSGROUP_NAME
.encode(), (None, "p_compound", False)),
504 if override_defaults
is not None:
505 props
.update(override_defaults
)
506 return FBXTemplate(b
"AnimationCurveNode", b
"FbxAnimCurveNode", props
, nbr_users
, [False])
509 def fbx_template_def_animcurve(scene
, settings
, override_defaults
=None, nbr_users
=0):
510 props
= OrderedDict()
511 if override_defaults
is not None:
512 props
.update(override_defaults
)
513 return FBXTemplate(b
"AnimationCurve", b
"", props
, nbr_users
, [False])
516 # ##### Generators for connection elements. #####
518 def elem_connection(elem
, c_type
, uid_src
, uid_dst
, prop_dst
=None):
519 e
= elem_data_single_string(elem
, b
"C", c_type
)
522 if prop_dst
is not None:
523 e
.add_string(prop_dst
)
526 # ##### FBX objects generators. #####
528 def fbx_data_element_custom_properties(props
, bid
):
530 Store custom properties of blender ID bid (any mapping-like object, in fact) into FBX properties props.
532 for k
, v
in bid
.items():
533 list_val
= getattr(v
, "to_list", lambda: None)()
535 if isinstance(v
, str):
536 elem_props_set(props
, "p_string", k
.encode(), v
, custom
=True)
537 elif isinstance(v
, int):
538 elem_props_set(props
, "p_integer", k
.encode(), v
, custom
=True)
539 elif isinstance(v
, float):
540 elem_props_set(props
, "p_double", k
.encode(), v
, custom
=True)
541 elif list_val
and len(list_val
) == 3:
542 elem_props_set(props
, "p_vector", k
.encode(), list_val
, custom
=True)
545 def fbx_data_empty_elements(root
, empty
, scene_data
):
547 Write the Empty data block.
549 empty_key
= scene_data
.data_empties
[empty
]
551 null
= elem_data_single_int64(root
, b
"NodeAttribute", get_fbx_uuid_from_key(empty_key
))
552 null
.add_string(fbx_name_class(empty
.name
.encode(), b
"NodeAttribute"))
553 null
.add_string(b
"Null")
555 elem_data_single_string(null
, b
"TypeFlags", b
"Null")
557 tmpl
= elem_props_template_init(scene_data
.templates
, b
"Null")
558 props
= elem_properties(null
)
559 elem_props_template_finalize(tmpl
, props
)
561 # No custom properties, already saved with object (Model).
564 def fbx_data_lamp_elements(root
, lamp
, scene_data
):
566 Write the Lamp data block.
568 gscale
= scene_data
.settings
.global_scale
570 lamp_key
= scene_data
.data_lamps
[lamp
]
572 decay_type
= FBX_LIGHT_DECAY_TYPES
['CONSTANT']
574 shadow_color
= Vector((0.0, 0.0, 0.0))
575 if lamp
.type not in {'HEMI'}:
576 if lamp
.type not in {'SUN', 'AREA'}:
577 decay_type
= FBX_LIGHT_DECAY_TYPES
[lamp
.falloff_type
]
578 do_light
= (not lamp
.use_only_shadow
) and (lamp
.use_specular
or lamp
.use_diffuse
)
579 do_shadow
= lamp
.shadow_method
not in {'NOSHADOW'}
580 shadow_color
= lamp
.shadow_color
582 light
= elem_data_single_int64(root
, b
"NodeAttribute", get_fbx_uuid_from_key(lamp_key
))
583 light
.add_string(fbx_name_class(lamp
.name
.encode(), b
"NodeAttribute"))
584 light
.add_string(b
"Light")
586 elem_data_single_int32(light
, b
"GeometryVersion", FBX_GEOMETRY_VERSION
) # Sic...
588 tmpl
= elem_props_template_init(scene_data
.templates
, b
"Light")
589 props
= elem_properties(light
)
590 elem_props_template_set(tmpl
, props
, "p_enum", b
"LightType", FBX_LIGHT_TYPES
[lamp
.type])
591 elem_props_template_set(tmpl
, props
, "p_bool", b
"CastLight", do_light
)
592 elem_props_template_set(tmpl
, props
, "p_color", b
"Color", lamp
.color
)
593 elem_props_template_set(tmpl
, props
, "p_number", b
"Intensity", lamp
.energy
* 100.0)
594 elem_props_template_set(tmpl
, props
, "p_enum", b
"DecayType", decay_type
)
595 elem_props_template_set(tmpl
, props
, "p_double", b
"DecayStart", lamp
.distance
* gscale
)
596 elem_props_template_set(tmpl
, props
, "p_bool", b
"CastShadows", do_shadow
)
597 elem_props_template_set(tmpl
, props
, "p_color", b
"ShadowColor", shadow_color
)
598 if lamp
.type in {'SPOT'}:
599 elem_props_template_set(tmpl
, props
, "p_double", b
"OuterAngle", math
.degrees(lamp
.spot_size
))
600 elem_props_template_set(tmpl
, props
, "p_double", b
"InnerAngle",
601 math
.degrees(lamp
.spot_size
* (1.0 - lamp
.spot_blend
)))
602 elem_props_template_finalize(tmpl
, props
)
605 if scene_data
.settings
.use_custom_props
:
606 fbx_data_element_custom_properties(props
, lamp
)
609 def fbx_data_camera_elements(root
, cam_obj
, scene_data
):
611 Write the Camera data blocks.
613 gscale
= scene_data
.settings
.global_scale
617 cam_key
= scene_data
.data_cameras
[cam_obj
]
619 # Real data now, good old camera!
620 # Object transform info.
621 loc
, rot
, scale
, matrix
, matrix_rot
= cam_obj
.fbx_object_tx(scene_data
)
622 up
= matrix_rot
* Vector((0.0, 1.0, 0.0))
623 to
= matrix_rot
* Vector((0.0, 0.0, -1.0))
625 # TODO We could export much more...
626 render
= scene_data
.scene
.render
627 width
= render
.resolution_x
628 height
= render
.resolution_y
629 aspect
= width
/ height
630 # Film width & height from mm to inches
631 filmwidth
= convert_mm_to_inch(cam_data
.sensor_width
)
632 filmheight
= convert_mm_to_inch(cam_data
.sensor_height
)
633 filmaspect
= filmwidth
/ filmheight
635 offsetx
= filmwidth
* cam_data
.shift_x
636 offsety
= filmaspect
* filmheight
* cam_data
.shift_y
638 cam
= elem_data_single_int64(root
, b
"NodeAttribute", get_fbx_uuid_from_key(cam_key
))
639 cam
.add_string(fbx_name_class(cam_data
.name
.encode(), b
"NodeAttribute"))
640 cam
.add_string(b
"Camera")
642 tmpl
= elem_props_template_init(scene_data
.templates
, b
"Camera")
643 props
= elem_properties(cam
)
645 elem_props_template_set(tmpl
, props
, "p_vector", b
"Position", loc
)
646 elem_props_template_set(tmpl
, props
, "p_vector", b
"UpVector", up
)
647 elem_props_template_set(tmpl
, props
, "p_vector", b
"InterestPosition", loc
+ to
) # Point, not vector!
648 # Should we use world value?
649 elem_props_template_set(tmpl
, props
, "p_color", b
"BackgroundColor", (0.0, 0.0, 0.0))
650 elem_props_template_set(tmpl
, props
, "p_bool", b
"DisplayTurnTableIcon", True)
652 elem_props_template_set(tmpl
, props
, "p_enum", b
"AspectRatioMode", 2) # FixedResolution
653 elem_props_template_set(tmpl
, props
, "p_double", b
"AspectWidth", float(render
.resolution_x
))
654 elem_props_template_set(tmpl
, props
, "p_double", b
"AspectHeight", float(render
.resolution_y
))
655 elem_props_template_set(tmpl
, props
, "p_double", b
"PixelAspectRatio",
656 float(render
.pixel_aspect_x
/ render
.pixel_aspect_y
))
658 elem_props_template_set(tmpl
, props
, "p_double", b
"FilmWidth", filmwidth
)
659 elem_props_template_set(tmpl
, props
, "p_double", b
"FilmHeight", filmheight
)
660 elem_props_template_set(tmpl
, props
, "p_double", b
"FilmAspectRatio", filmaspect
)
661 elem_props_template_set(tmpl
, props
, "p_double", b
"FilmOffsetX", offsetx
)
662 elem_props_template_set(tmpl
, props
, "p_double", b
"FilmOffsetY", offsety
)
664 elem_props_template_set(tmpl
, props
, "p_enum", b
"ApertureMode", 3) # FocalLength.
665 elem_props_template_set(tmpl
, props
, "p_enum", b
"GateFit", 2) # FitHorizontal.
666 elem_props_template_set(tmpl
, props
, "p_fov", b
"FieldOfView", math
.degrees(cam_data
.angle_x
))
667 elem_props_template_set(tmpl
, props
, "p_fov_x", b
"FieldOfViewX", math
.degrees(cam_data
.angle_x
))
668 elem_props_template_set(tmpl
, props
, "p_fov_y", b
"FieldOfViewY", math
.degrees(cam_data
.angle_y
))
669 # No need to convert to inches here...
670 elem_props_template_set(tmpl
, props
, "p_double", b
"FocalLength", cam_data
.lens
)
671 elem_props_template_set(tmpl
, props
, "p_double", b
"SafeAreaAspectRatio", aspect
)
672 # Default to perspective camera.
673 elem_props_template_set(tmpl
, props
, "p_enum", b
"CameraProjectionType", 1 if cam_data
.type == 'ORTHO' else 0)
674 elem_props_template_set(tmpl
, props
, "p_double", b
"OrthoZoom", cam_data
.ortho_scale
)
676 elem_props_template_set(tmpl
, props
, "p_double", b
"NearPlane", cam_data
.clip_start
* gscale
)
677 elem_props_template_set(tmpl
, props
, "p_double", b
"FarPlane", cam_data
.clip_end
* gscale
)
678 elem_props_template_set(tmpl
, props
, "p_enum", b
"BackPlaneDistanceMode", 1) # RelativeToCamera.
679 elem_props_template_set(tmpl
, props
, "p_double", b
"BackPlaneDistance", cam_data
.clip_end
* gscale
)
681 elem_props_template_finalize(tmpl
, props
)
684 if scene_data
.settings
.use_custom_props
:
685 fbx_data_element_custom_properties(props
, cam_data
)
687 elem_data_single_string(cam
, b
"TypeFlags", b
"Camera")
688 elem_data_single_int32(cam
, b
"GeometryVersion", 124) # Sic...
689 elem_data_vec_float64(cam
, b
"Position", loc
)
690 elem_data_vec_float64(cam
, b
"Up", up
)
691 elem_data_vec_float64(cam
, b
"LookAt", to
)
692 elem_data_single_int32(cam
, b
"ShowInfoOnMoving", 1)
693 elem_data_single_int32(cam
, b
"ShowAudio", 0)
694 elem_data_vec_float64(cam
, b
"AudioColor", (0.0, 1.0, 0.0))
695 elem_data_single_float64(cam
, b
"CameraOrthoZoom", 1.0)
698 def fbx_data_bindpose_element(root
, me_obj
, me
, scene_data
, arm_obj
=None, bones
=[]):
700 Helper, since bindpose are used by both meshes shape keys and armature bones...
704 # We assume bind pose for our bones are their "Editmode" pose...
705 # All matrices are expected in global (world) space.
706 bindpose_key
= get_blender_bindpose_key(arm_obj
.bdata
, me
)
707 fbx_pose
= elem_data_single_int64(root
, b
"Pose", get_fbx_uuid_from_key(bindpose_key
))
708 fbx_pose
.add_string(fbx_name_class(me
.name
.encode(), b
"Pose"))
709 fbx_pose
.add_string(b
"BindPose")
711 elem_data_single_string(fbx_pose
, b
"Type", b
"BindPose")
712 elem_data_single_int32(fbx_pose
, b
"Version", FBX_POSE_BIND_VERSION
)
713 elem_data_single_int32(fbx_pose
, b
"NbPoseNodes", 1 + len(bones
))
715 # First node is mesh/object.
716 mat_world_obj
= me_obj
.fbx_object_matrix(scene_data
, global_space
=True)
717 fbx_posenode
= elem_empty(fbx_pose
, b
"PoseNode")
718 elem_data_single_int64(fbx_posenode
, b
"Node", me_obj
.fbx_uuid
)
719 elem_data_single_float64_array(fbx_posenode
, b
"Matrix", matrix4_to_array(mat_world_obj
))
720 # And all bones of armature!
723 bomat
= bo_obj
.fbx_object_matrix(scene_data
, rest
=True, global_space
=True)
724 mat_world_bones
[bo_obj
] = bomat
725 fbx_posenode
= elem_empty(fbx_pose
, b
"PoseNode")
726 elem_data_single_int64(fbx_posenode
, b
"Node", bo_obj
.fbx_uuid
)
727 elem_data_single_float64_array(fbx_posenode
, b
"Matrix", matrix4_to_array(bomat
))
729 return mat_world_obj
, mat_world_bones
732 def fbx_data_mesh_shapes_elements(root
, me_obj
, me
, scene_data
, fbx_me_tmpl
, fbx_me_props
):
734 Write shape keys related data.
736 if me
not in scene_data
.data_deformers_shape
:
739 # First, write the geometry data itself (i.e. shapes).
740 _me_key
, shape_key
, shapes
= scene_data
.data_deformers_shape
[me
]
744 for shape
, (channel_key
, geom_key
, shape_verts_co
, shape_verts_idx
) in shapes
.items():
745 # Use vgroups as weights, if defined.
746 if shape
.vertex_group
and shape
.vertex_group
in me_obj
.bdata
.vertex_groups
:
747 shape_verts_weights
= [0.0] * (len(shape_verts_co
) // 3)
748 vg_idx
= me_obj
.bdata
.vertex_groups
[shape
.vertex_group
].index
749 for sk_idx
, v_idx
in enumerate(shape_verts_idx
):
750 for vg
in me
.vertices
[v_idx
].groups
:
751 if vg
.group
== vg_idx
:
752 shape_verts_weights
[sk_idx
] = vg
.weight
* 100.0
754 shape_verts_weights
= [100.0] * (len(shape_verts_co
) // 3)
755 channels
.append((channel_key
, shape
, shape_verts_weights
))
757 geom
= elem_data_single_int64(root
, b
"Geometry", get_fbx_uuid_from_key(geom_key
))
758 geom
.add_string(fbx_name_class(shape
.name
.encode(), b
"Geometry"))
759 geom
.add_string(b
"Shape")
761 tmpl
= elem_props_template_init(scene_data
.templates
, b
"Geometry")
762 props
= elem_properties(geom
)
763 elem_props_template_finalize(tmpl
, props
)
765 elem_data_single_int32(geom
, b
"Version", FBX_GEOMETRY_SHAPE_VERSION
)
767 elem_data_single_int32_array(geom
, b
"Indexes", shape_verts_idx
)
768 elem_data_single_float64_array(geom
, b
"Vertices", shape_verts_co
)
769 elem_data_single_float64_array(geom
, b
"Normals", [0.0] * len(shape_verts_co
))
771 # Yiha! BindPose for shapekeys too! Dodecasigh...
772 # XXX Not sure yet whether several bindposes on same mesh are allowed, or not... :/
773 fbx_data_bindpose_element(root
, me_obj
, me
, scene_data
)
775 # ...and now, the deformers stuff.
776 fbx_shape
= elem_data_single_int64(root
, b
"Deformer", get_fbx_uuid_from_key(shape_key
))
777 fbx_shape
.add_string(fbx_name_class(me
.name
.encode(), b
"Deformer"))
778 fbx_shape
.add_string(b
"BlendShape")
780 elem_data_single_int32(fbx_shape
, b
"Version", FBX_DEFORMER_SHAPE_VERSION
)
782 for channel_key
, shape
, shape_verts_weights
in channels
:
783 fbx_channel
= elem_data_single_int64(root
, b
"Deformer", get_fbx_uuid_from_key(channel_key
))
784 fbx_channel
.add_string(fbx_name_class(shape
.name
.encode(), b
"SubDeformer"))
785 fbx_channel
.add_string(b
"BlendShapeChannel")
787 elem_data_single_int32(fbx_channel
, b
"Version", FBX_DEFORMER_SHAPECHANNEL_VERSION
)
788 elem_data_single_float64(fbx_channel
, b
"DeformPercent", shape
.value
* 100.0) # Percents...
789 elem_data_single_float64_array(fbx_channel
, b
"FullWeights", shape_verts_weights
)
791 # *WHY* add this in linked mesh properties too? *cry*
792 # No idea whether it’s percent here too, or more usual factor (assume percentage for now) :/
793 elem_props_template_set(fbx_me_tmpl
, fbx_me_props
, "p_number", shape
.name
.encode(), shape
.value
* 100.0,
797 def fbx_data_mesh_elements(root
, me_obj
, scene_data
, done_meshes
):
799 Write the Mesh (Geometry) data block.
802 def _infinite_gen(val
):
806 me_key
, me
, _free
= scene_data
.data_meshes
[me_obj
]
808 # In case of multiple instances of same mesh, only write it once!
809 if me_key
in done_meshes
:
812 # No gscale/gmat here, all data are supposed to be in object space.
813 smooth_type
= scene_data
.settings
.mesh_smooth_type
814 write_normals
= smooth_type
in {'OFF'}
816 do_bake_space_transform
= me_obj
.use_bake_space_transform(scene_data
)
818 # Vertices are in object space, but we are post-multiplying all transforms with the inverse of the
819 # global matrix, so we need to apply the global matrix to the vertices to get the correct result.
820 geom_mat_co
= scene_data
.settings
.global_matrix
if do_bake_space_transform
else None
821 # We need to apply the inverse transpose of the global matrix when transforming normals.
822 geom_mat_no
= Matrix(scene_data
.settings
.global_matrix_inv_transposed
) if do_bake_space_transform
else None
823 if geom_mat_no
is not None:
824 # Remove translation & scaling!
825 geom_mat_no
.translation
= Vector()
826 geom_mat_no
.normalize()
828 geom
= elem_data_single_int64(root
, b
"Geometry", get_fbx_uuid_from_key(me_key
))
829 geom
.add_string(fbx_name_class(me
.name
.encode(), b
"Geometry"))
830 geom
.add_string(b
"Mesh")
832 tmpl
= elem_props_template_init(scene_data
.templates
, b
"Geometry")
833 props
= elem_properties(geom
)
836 if scene_data
.settings
.use_custom_props
:
837 fbx_data_element_custom_properties(props
, me
)
839 elem_data_single_int32(geom
, b
"GeometryVersion", FBX_GEOMETRY_VERSION
)
842 t_co
= array
.array(data_types
.ARRAY_FLOAT64
, (0.0,)) * len(me
.vertices
) * 3
843 me
.vertices
.foreach_get("co", t_co
)
844 if geom_mat_co
is not None:
845 def _vcos_transformed_gen(raw_cos
, m
=None):
846 # Note: we could most likely get much better performances with numpy, but will leave this as TODO for now.
847 return chain(*(m
* Vector(v
) for v
in zip(*(iter(raw_cos
),) * 3)))
848 t_co
= _vcos_transformed_gen(t_co
, geom_mat_co
)
849 elem_data_single_float64_array(geom
, b
"Vertices", t_co
)
854 # We do loose edges as two-vertices faces, if enabled...
856 # Note we have to process Edges in the same time, as they are based on poly's loops...
857 loop_nbr
= len(me
.loops
)
858 t_pvi
= array
.array(data_types
.ARRAY_INT32
, (0,)) * loop_nbr
859 t_ls
= [None] * len(me
.polygons
)
861 me
.loops
.foreach_get("vertex_index", t_pvi
)
862 me
.polygons
.foreach_get("loop_start", t_ls
)
864 # Add "fake" faces for loose edges.
865 if scene_data
.settings
.use_mesh_edges
:
866 t_le
= tuple(e
.vertices
for e
in me
.edges
if e
.is_loose
)
867 t_pvi
.extend(chain(*t_le
))
868 t_ls
.extend(range(loop_nbr
, loop_nbr
+ len(t_le
), 2))
872 # Note: Edges are represented as a loop here: each edge uses a single index, which refers to the polygon array.
873 # The edge is made by the vertex indexed py this polygon's point and the next one on the same polygon.
874 # Advantage: Only one index per edge.
875 # Drawback: Only polygon's edges can be represented (that's why we have to add fake two-verts polygons
877 # We also have to store a mapping from real edges to their indices in this array, for edge-mapped data
878 # (like e.g. crease).
879 t_eli
= array
.array(data_types
.ARRAY_INT32
)
884 todo_edges
= [None] * len(me
.edges
) * 2
885 me
.edges
.foreach_get("vertices", todo_edges
)
886 todo_edges
= set((v1
, v2
) if v1
< v2
else (v2
, v1
) for v1
, v2
in zip(*(iter(todo_edges
),) * 2))
889 vi
= vi_start
= t_pvi
[0]
890 for li_next
, vi_next
in enumerate(t_pvi
[1:] + t_pvi
[:1], start
=1):
891 if li_next
in t_ls
: # End of a poly's loop.
897 e_key
= (vi
, vi2
) if vi
< vi2
else (vi2
, vi
)
898 if e_key
in todo_edges
:
900 todo_edges
.remove(e_key
)
901 edges_map
[e_key
] = edges_nbr
908 # We have to ^-1 last index of each loop.
912 # And finally we can write data!
913 elem_data_single_int32_array(geom
, b
"PolygonVertexIndex", t_pvi
)
914 elem_data_single_int32_array(geom
, b
"Edges", t_eli
)
922 if smooth_type
in {'FACE', 'EDGE'}:
925 if smooth_type
== 'FACE':
926 t_ps
= array
.array(data_types
.ARRAY_INT32
, (0,)) * len(me
.polygons
)
927 me
.polygons
.foreach_get("use_smooth", t_ps
)
930 # Write Edge Smoothing.
931 t_ps
= array
.array(data_types
.ARRAY_INT32
, (0,)) * edges_nbr
933 if e
.key
not in edges_map
:
934 continue # Only loose edges, in theory!
935 t_ps
[edges_map
[e
.key
]] = not e
.use_edge_sharp
937 lay_smooth
= elem_data_single_int32(geom
, b
"LayerElementSmoothing", 0)
938 elem_data_single_int32(lay_smooth
, b
"Version", FBX_GEOMETRY_SMOOTHING_VERSION
)
939 elem_data_single_string(lay_smooth
, b
"Name", b
"")
940 elem_data_single_string(lay_smooth
, b
"MappingInformationType", _map
)
941 elem_data_single_string(lay_smooth
, b
"ReferenceInformationType", b
"Direct")
942 elem_data_single_int32_array(lay_smooth
, b
"Smoothing", t_ps
) # Sight, int32 for bool...
945 # TODO: Edge crease (LayerElementCrease).
947 # And we are done with edges!
953 # NOTE: this is not supported by importer currently.
954 # XXX Official docs says normals should use IndexToDirect,
955 # but this does not seem well supported by apps currently...
956 me
.calc_normals_split()
958 def _nortuples_gen(raw_nors
, m
):
959 # Great, now normals are also expected 4D!
960 # XXX Back to 3D normals for now!
961 # gen = zip(*(iter(raw_nors),) * 3 + (_infinite_gen(1.0),))
962 gen
= zip(*(iter(raw_nors
),) * 3)
963 return gen
if m
is None else (m
* Vector(v
) for v
in gen
)
965 t_ln
= array
.array(data_types
.ARRAY_FLOAT64
, (0.0,)) * len(me
.loops
) * 3
966 me
.loops
.foreach_get("normal", t_ln
)
967 t_ln
= _nortuples_gen(t_ln
, geom_mat_no
)
969 t_ln
= tuple(t_ln
) # No choice... :/
971 lay_nor
= elem_data_single_int32(geom
, b
"LayerElementNormal", 0)
972 elem_data_single_int32(lay_nor
, b
"Version", FBX_GEOMETRY_NORMAL_VERSION
)
973 elem_data_single_string(lay_nor
, b
"Name", b
"")
974 elem_data_single_string(lay_nor
, b
"MappingInformationType", b
"ByPolygonVertex")
975 elem_data_single_string(lay_nor
, b
"ReferenceInformationType", b
"IndexToDirect")
977 ln2idx
= tuple(set(t_ln
))
978 elem_data_single_float64_array(lay_nor
, b
"Normals", chain(*ln2idx
))
979 # Normal weights, no idea what it is.
980 # t_lnw = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * len(ln2idx)
981 # elem_data_single_float64_array(lay_nor, b"NormalsW", t_lnw)
983 ln2idx
= {nor
: idx
for idx
, nor
in enumerate(ln2idx
)}
984 elem_data_single_int32_array(lay_nor
, b
"NormalsIndex", (ln2idx
[n
] for n
in t_ln
))
989 lay_nor
= elem_data_single_int32(geom
, b
"LayerElementNormal", 0)
990 elem_data_single_int32(lay_nor
, b
"Version", FBX_GEOMETRY_NORMAL_VERSION
)
991 elem_data_single_string(lay_nor
, b
"Name", b
"")
992 elem_data_single_string(lay_nor
, b
"MappingInformationType", b
"ByPolygonVertex")
993 elem_data_single_string(lay_nor
, b
"ReferenceInformationType", b
"Direct")
994 elem_data_single_float64_array(lay_nor
, b
"Normals", chain(*t_ln
))
995 # Normal weights, no idea what it is.
996 # t_ln = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * len(me.loops)
997 # elem_data_single_float64_array(lay_nor, b"NormalsW", t_ln)
1001 if scene_data
.settings
.use_tspace
:
1002 tspacenumber
= len(me
.uv_layers
)
1004 t_ln
= array
.array(data_types
.ARRAY_FLOAT64
, (0.0,)) * len(me
.loops
) * 3
1005 # t_lnw = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * len(me.loops)
1006 for idx
, uvlayer
in enumerate(me
.uv_layers
):
1008 me
.calc_tangents(name
)
1009 # Loop bitangents (aka binormals).
1010 # NOTE: this is not supported by importer currently.
1011 me
.loops
.foreach_get("bitangent", t_ln
)
1012 lay_nor
= elem_data_single_int32(geom
, b
"LayerElementBinormal", idx
)
1013 elem_data_single_int32(lay_nor
, b
"Version", FBX_GEOMETRY_BINORMAL_VERSION
)
1014 elem_data_single_string_unicode(lay_nor
, b
"Name", name
)
1015 elem_data_single_string(lay_nor
, b
"MappingInformationType", b
"ByPolygonVertex")
1016 elem_data_single_string(lay_nor
, b
"ReferenceInformationType", b
"Direct")
1017 elem_data_single_float64_array(lay_nor
, b
"Binormals", chain(*_nortuples_gen(t_ln
, geom_mat_no
)))
1018 # Binormal weights, no idea what it is.
1019 # elem_data_single_float64_array(lay_nor, b"BinormalsW", t_lnw)
1022 # NOTE: this is not supported by importer currently.
1023 me
.loops
.foreach_get("tangent", t_ln
)
1024 lay_nor
= elem_data_single_int32(geom
, b
"LayerElementTangent", idx
)
1025 elem_data_single_int32(lay_nor
, b
"Version", FBX_GEOMETRY_TANGENT_VERSION
)
1026 elem_data_single_string_unicode(lay_nor
, b
"Name", name
)
1027 elem_data_single_string(lay_nor
, b
"MappingInformationType", b
"ByPolygonVertex")
1028 elem_data_single_string(lay_nor
, b
"ReferenceInformationType", b
"Direct")
1029 elem_data_single_float64_array(lay_nor
, b
"Tangents", chain(*_nortuples_gen(t_ln
, geom_mat_no
)))
1030 # Tangent weights, no idea what it is.
1031 # elem_data_single_float64_array(lay_nor, b"TangentsW", t_lnw)
1037 me
.free_normals_split()
1040 # Write VertexColor Layers.
1041 vcolnumber
= len(me
.vertex_colors
)
1043 def _coltuples_gen(raw_cols
):
1044 return zip(*(iter(raw_cols
),) * 3 + (_infinite_gen(1.0),)) # We need a fake alpha...
1046 t_lc
= array
.array(data_types
.ARRAY_FLOAT64
, (0.0,)) * len(me
.loops
) * 3
1047 for colindex
, collayer
in enumerate(me
.vertex_colors
):
1048 collayer
.data
.foreach_get("color", t_lc
)
1049 lay_vcol
= elem_data_single_int32(geom
, b
"LayerElementColor", colindex
)
1050 elem_data_single_int32(lay_vcol
, b
"Version", FBX_GEOMETRY_VCOLOR_VERSION
)
1051 elem_data_single_string_unicode(lay_vcol
, b
"Name", collayer
.name
)
1052 elem_data_single_string(lay_vcol
, b
"MappingInformationType", b
"ByPolygonVertex")
1053 elem_data_single_string(lay_vcol
, b
"ReferenceInformationType", b
"IndexToDirect")
1055 col2idx
= tuple(set(_coltuples_gen(t_lc
)))
1056 elem_data_single_float64_array(lay_vcol
, b
"Colors", chain(*col2idx
)) # Flatten again...
1058 col2idx
= {col
: idx
for idx
, col
in enumerate(col2idx
)}
1059 elem_data_single_int32_array(lay_vcol
, b
"ColorIndex", (col2idx
[c
] for c
in _coltuples_gen(t_lc
)))
1065 # Note: LayerElementTexture is deprecated since FBX 2011 - luckily!
1066 # Textures are now only related to materials, in FBX!
1067 uvnumber
= len(me
.uv_layers
)
1069 def _uvtuples_gen(raw_uvs
):
1070 return zip(*(iter(raw_uvs
),) * 2)
1072 t_luv
= array
.array(data_types
.ARRAY_FLOAT64
, (0.0,)) * len(me
.loops
) * 2
1073 for uvindex
, uvlayer
in enumerate(me
.uv_layers
):
1074 uvlayer
.data
.foreach_get("uv", t_luv
)
1075 lay_uv
= elem_data_single_int32(geom
, b
"LayerElementUV", uvindex
)
1076 elem_data_single_int32(lay_uv
, b
"Version", FBX_GEOMETRY_UV_VERSION
)
1077 elem_data_single_string_unicode(lay_uv
, b
"Name", uvlayer
.name
)
1078 elem_data_single_string(lay_uv
, b
"MappingInformationType", b
"ByPolygonVertex")
1079 elem_data_single_string(lay_uv
, b
"ReferenceInformationType", b
"IndexToDirect")
1081 uv2idx
= tuple(set(_uvtuples_gen(t_luv
)))
1082 elem_data_single_float64_array(lay_uv
, b
"UV", chain(*uv2idx
)) # Flatten again...
1084 uv2idx
= {uv
: idx
for idx
, uv
in enumerate(uv2idx
)}
1085 elem_data_single_int32_array(lay_uv
, b
"UVIndex", (uv2idx
[uv
] for uv
in _uvtuples_gen(t_luv
)))
1091 me_fbxmats_idx
= scene_data
.mesh_mat_indices
.get(me
)
1092 if me_fbxmats_idx
is not None:
1093 me_blmats
= me
.materials
1094 if me_fbxmats_idx
and me_blmats
:
1095 lay_mat
= elem_data_single_int32(geom
, b
"LayerElementMaterial", 0)
1096 elem_data_single_int32(lay_mat
, b
"Version", FBX_GEOMETRY_MATERIAL_VERSION
)
1097 elem_data_single_string(lay_mat
, b
"Name", b
"")
1098 nbr_mats
= len(me_fbxmats_idx
)
1100 t_pm
= array
.array(data_types
.ARRAY_INT32
, (0,)) * len(me
.polygons
)
1101 me
.polygons
.foreach_get("material_index", t_pm
)
1103 # We have to validate mat indices, and map them to FBX indices.
1104 # Note a mat might not be in me_fbxmats_idx (e.g. node mats are ignored).
1105 blmats_to_fbxmats_idxs
= [me_fbxmats_idx
[m
] for m
in me_blmats
if m
in me_fbxmats_idx
]
1106 mat_idx_limit
= len(blmats_to_fbxmats_idxs
)
1107 def_mat
= blmats_to_fbxmats_idxs
[0]
1108 _gen
= (blmats_to_fbxmats_idxs
[m
] if m
< mat_idx_limit
else def_mat
for m
in t_pm
)
1109 t_pm
= array
.array(data_types
.ARRAY_INT32
, _gen
)
1111 elem_data_single_string(lay_mat
, b
"MappingInformationType", b
"ByPolygon")
1112 # XXX Logically, should be "Direct" reference type, since we do not have any index array, and have one
1113 # value per polygon...
1114 # But looks like FBX expects it to be IndexToDirect here (maybe because materials are already
1115 # indices??? *sigh*).
1116 elem_data_single_string(lay_mat
, b
"ReferenceInformationType", b
"IndexToDirect")
1117 elem_data_single_int32_array(lay_mat
, b
"Materials", t_pm
)
1120 elem_data_single_string(lay_mat
, b
"MappingInformationType", b
"AllSame")
1121 elem_data_single_string(lay_mat
, b
"ReferenceInformationType", b
"IndexToDirect")
1122 elem_data_single_int32_array(lay_mat
, b
"Materials", [0])
1124 # And the "layer TOC"...
1126 layer
= elem_data_single_int32(geom
, b
"Layer", 0)
1127 elem_data_single_int32(layer
, b
"Version", FBX_GEOMETRY_LAYER_VERSION
)
1128 lay_nor
= elem_empty(layer
, b
"LayerElement")
1129 elem_data_single_string(lay_nor
, b
"Type", b
"LayerElementNormal")
1130 elem_data_single_int32(lay_nor
, b
"TypedIndex", 0)
1132 lay_binor
= elem_empty(layer
, b
"LayerElement")
1133 elem_data_single_string(lay_binor
, b
"Type", b
"LayerElementBinormal")
1134 elem_data_single_int32(lay_binor
, b
"TypedIndex", 0)
1135 lay_tan
= elem_empty(layer
, b
"LayerElement")
1136 elem_data_single_string(lay_tan
, b
"Type", b
"LayerElementTangent")
1137 elem_data_single_int32(lay_tan
, b
"TypedIndex", 0)
1138 if smooth_type
in {'FACE', 'EDGE'}:
1139 lay_smooth
= elem_empty(layer
, b
"LayerElement")
1140 elem_data_single_string(lay_smooth
, b
"Type", b
"LayerElementSmoothing")
1141 elem_data_single_int32(lay_smooth
, b
"TypedIndex", 0)
1143 lay_vcol
= elem_empty(layer
, b
"LayerElement")
1144 elem_data_single_string(lay_vcol
, b
"Type", b
"LayerElementColor")
1145 elem_data_single_int32(lay_vcol
, b
"TypedIndex", 0)
1147 lay_uv
= elem_empty(layer
, b
"LayerElement")
1148 elem_data_single_string(lay_uv
, b
"Type", b
"LayerElementUV")
1149 elem_data_single_int32(lay_uv
, b
"TypedIndex", 0)
1150 if me_fbxmats_idx
is not None:
1151 lay_mat
= elem_empty(layer
, b
"LayerElement")
1152 elem_data_single_string(lay_mat
, b
"Type", b
"LayerElementMaterial")
1153 elem_data_single_int32(lay_mat
, b
"TypedIndex", 0)
1155 # Add other uv and/or vcol layers...
1156 for vcolidx
, uvidx
, tspaceidx
in zip_longest(range(1, vcolnumber
), range(1, uvnumber
), range(1, tspacenumber
),
1158 layer
= elem_data_single_int32(geom
, b
"Layer", max(vcolidx
, uvidx
))
1159 elem_data_single_int32(layer
, b
"Version", FBX_GEOMETRY_LAYER_VERSION
)
1161 lay_vcol
= elem_empty(layer
, b
"LayerElement")
1162 elem_data_single_string(lay_vcol
, b
"Type", b
"LayerElementColor")
1163 elem_data_single_int32(lay_vcol
, b
"TypedIndex", vcolidx
)
1165 lay_uv
= elem_empty(layer
, b
"LayerElement")
1166 elem_data_single_string(lay_uv
, b
"Type", b
"LayerElementUV")
1167 elem_data_single_int32(lay_uv
, b
"TypedIndex", uvidx
)
1169 lay_binor
= elem_empty(layer
, b
"LayerElement")
1170 elem_data_single_string(lay_binor
, b
"Type", b
"LayerElementBinormal")
1171 elem_data_single_int32(lay_binor
, b
"TypedIndex", tspaceidx
)
1172 lay_tan
= elem_empty(layer
, b
"LayerElement")
1173 elem_data_single_string(lay_tan
, b
"Type", b
"LayerElementTangent")
1174 elem_data_single_int32(lay_tan
, b
"TypedIndex", tspaceidx
)
1177 fbx_data_mesh_shapes_elements(root
, me_obj
, me
, scene_data
, tmpl
, props
)
1179 elem_props_template_finalize(tmpl
, props
)
1180 done_meshes
.add(me_key
)
1183 def check_skip_material(mat
):
1184 """Simple helper to check whether we actually support exporting that material or not"""
1185 return mat
.type not in {'SURFACE'} or mat
.use_nodes
1188 def fbx_data_material_elements(root
, mat
, scene_data
):
1190 Write the Material data block.
1192 ambient_color
= (0.0, 0.0, 0.0)
1193 if scene_data
.data_world
:
1194 ambient_color
= next(iter(scene_data
.data_world
.keys())).ambient_color
1196 mat_key
, _objs
= scene_data
.data_materials
[mat
]
1197 skip_mat
= check_skip_material(mat
)
1200 if not skip_mat
and mat
.specular_shader
not in {'COOKTORR', 'PHONG', 'BLINN'}:
1201 mat_type
= b
"Lambert"
1203 fbx_mat
= elem_data_single_int64(root
, b
"Material", get_fbx_uuid_from_key(mat_key
))
1204 fbx_mat
.add_string(fbx_name_class(mat
.name
.encode(), b
"Material"))
1205 fbx_mat
.add_string(b
"")
1207 elem_data_single_int32(fbx_mat
, b
"Version", FBX_MATERIAL_VERSION
)
1208 # those are not yet properties, it seems...
1209 elem_data_single_string(fbx_mat
, b
"ShadingModel", mat_type
)
1210 elem_data_single_int32(fbx_mat
, b
"MultiLayer", 0) # Should be bool...
1212 tmpl
= elem_props_template_init(scene_data
.templates
, b
"Material")
1213 props
= elem_properties(fbx_mat
)
1216 elem_props_template_set(tmpl
, props
, "p_string", b
"ShadingModel", mat_type
.decode())
1217 elem_props_template_set(tmpl
, props
, "p_color", b
"EmissiveColor", mat
.diffuse_color
)
1218 elem_props_template_set(tmpl
, props
, "p_number", b
"EmissiveFactor", mat
.emit
)
1219 elem_props_template_set(tmpl
, props
, "p_color", b
"AmbientColor", ambient_color
)
1220 elem_props_template_set(tmpl
, props
, "p_number", b
"AmbientFactor", mat
.ambient
)
1221 elem_props_template_set(tmpl
, props
, "p_color", b
"DiffuseColor", mat
.diffuse_color
)
1222 elem_props_template_set(tmpl
, props
, "p_number", b
"DiffuseFactor", mat
.diffuse_intensity
)
1223 elem_props_template_set(tmpl
, props
, "p_color", b
"TransparentColor",
1224 mat
.diffuse_color
if mat
.use_transparency
else (1.0, 1.0, 1.0))
1225 elem_props_template_set(tmpl
, props
, "p_number", b
"TransparencyFactor",
1226 1.0 - mat
.alpha
if mat
.use_transparency
else 0.0)
1227 elem_props_template_set(tmpl
, props
, "p_number", b
"Opacity", mat
.alpha
if mat
.use_transparency
else 1.0)
1228 elem_props_template_set(tmpl
, props
, "p_vector_3d", b
"NormalMap", (0.0, 0.0, 0.0))
1229 # Not sure about those...
1231 b"Bump": ((0.0, 0.0, 0.0), "p_vector_3d"),
1232 b"BumpFactor": (1.0, "p_double"),
1233 b"DisplacementColor": ((0.0, 0.0, 0.0), "p_color_rgb"),
1234 b"DisplacementFactor": (0.0, "p_double"),
1236 if mat_type
== b
"Phong":
1237 elem_props_template_set(tmpl
, props
, "p_color", b
"SpecularColor", mat
.specular_color
)
1238 elem_props_template_set(tmpl
, props
, "p_number", b
"SpecularFactor", mat
.specular_intensity
/ 2.0)
1239 # See Material template about those two!
1240 elem_props_template_set(tmpl
, props
, "p_number", b
"Shininess", (mat
.specular_hardness
- 1.0) / 5.10)
1241 elem_props_template_set(tmpl
, props
, "p_number", b
"ShininessExponent", (mat
.specular_hardness
- 1.0) / 5.10)
1242 elem_props_template_set(tmpl
, props
, "p_color", b
"ReflectionColor", mat
.mirror_color
)
1243 elem_props_template_set(tmpl
, props
, "p_number", b
"ReflectionFactor",
1244 mat
.raytrace_mirror
.reflect_factor
if mat
.raytrace_mirror
.use
else 0.0)
1246 elem_props_template_finalize(tmpl
, props
)
1248 # Custom properties.
1249 if scene_data
.settings
.use_custom_props
:
1250 fbx_data_element_custom_properties(props
, mat
)
1253 def _gen_vid_path(img
, scene_data
):
1254 msetts
= scene_data
.settings
.media_settings
1255 fname_rel
= bpy_extras
.io_utils
.path_reference(img
.filepath
, msetts
.base_src
, msetts
.base_dst
, msetts
.path_mode
,
1256 msetts
.subdir
, msetts
.copy_set
, img
.library
)
1257 fname_abs
= os
.path
.normpath(os
.path
.abspath(os
.path
.join(msetts
.base_dst
, fname_rel
)))
1258 return fname_abs
, fname_rel
1261 def fbx_data_texture_file_elements(root
, tex
, scene_data
):
1263 Write the (file) Texture data block.
1265 # XXX All this is very fuzzy to me currently...
1266 # Textures do not seem to use properties as much as they could.
1267 # For now assuming most logical and simple stuff.
1269 tex_key
, _mats
= scene_data
.data_textures
[tex
]
1270 img
= tex
.texture
.image
1271 fname_abs
, fname_rel
= _gen_vid_path(img
, scene_data
)
1273 fbx_tex
= elem_data_single_int64(root
, b
"Texture", get_fbx_uuid_from_key(tex_key
))
1274 fbx_tex
.add_string(fbx_name_class(tex
.name
.encode(), b
"Texture"))
1275 fbx_tex
.add_string(b
"")
1277 elem_data_single_string(fbx_tex
, b
"Type", b
"TextureVideoClip")
1278 elem_data_single_int32(fbx_tex
, b
"Version", FBX_TEXTURE_VERSION
)
1279 elem_data_single_string(fbx_tex
, b
"TextureName", fbx_name_class(tex
.name
.encode(), b
"Texture"))
1280 elem_data_single_string(fbx_tex
, b
"Media", fbx_name_class(img
.name
.encode(), b
"Video"))
1281 elem_data_single_string_unicode(fbx_tex
, b
"FileName", fname_abs
)
1282 elem_data_single_string_unicode(fbx_tex
, b
"RelativeFilename", fname_rel
)
1284 alpha_source
= 0 # None
1286 if tex
.texture
.use_calculate_alpha
:
1287 alpha_source
= 1 # RGBIntensity as alpha.
1289 alpha_source
= 2 # Black, i.e. alpha channel.
1290 # BlendMode not useful for now, only affects layered textures afaics.
1293 if tex
.texture_coords
in {'ORCO'}: # XXX Others?
1294 if tex
.mapping
in {'FLAT'}:
1295 mapping
= 1 # Planar
1296 elif tex
.mapping
in {'CUBE'}:
1298 elif tex
.mapping
in {'TUBE'}:
1299 mapping
= 3 # Cylindrical
1300 elif tex
.mapping
in {'SPHERE'}:
1301 mapping
= 2 # Spherical
1302 elif tex
.texture_coords
in {'UV'}:
1304 # Yuck, UVs are linked by mere names it seems... :/
1305 uvset
= tex
.uv_layer
1306 wrap_mode
= 1 # Clamp
1307 if tex
.texture
.extension
in {'REPEAT'}:
1308 wrap_mode
= 0 # Repeat
1310 tmpl
= elem_props_template_init(scene_data
.templates
, b
"TextureFile")
1311 props
= elem_properties(fbx_tex
)
1312 elem_props_template_set(tmpl
, props
, "p_enum", b
"AlphaSource", alpha_source
)
1313 elem_props_template_set(tmpl
, props
, "p_bool", b
"PremultiplyAlpha",
1314 img
.alpha_mode
in {'STRAIGHT'}) # Or is it PREMUL?
1315 elem_props_template_set(tmpl
, props
, "p_enum", b
"CurrentMappingType", mapping
)
1316 if uvset
is not None:
1317 elem_props_template_set(tmpl
, props
, "p_string", b
"UVSet", uvset
)
1318 elem_props_template_set(tmpl
, props
, "p_enum", b
"WrapModeU", wrap_mode
)
1319 elem_props_template_set(tmpl
, props
, "p_enum", b
"WrapModeV", wrap_mode
)
1320 elem_props_template_set(tmpl
, props
, "p_vector_3d", b
"Translation", tex
.offset
)
1321 elem_props_template_set(tmpl
, props
, "p_vector_3d", b
"Scaling", tex
.scale
)
1322 # UseMaterial should always be ON imho.
1323 elem_props_template_set(tmpl
, props
, "p_bool", b
"UseMaterial", True)
1324 elem_props_template_set(tmpl
, props
, "p_bool", b
"UseMipMap", tex
.texture
.use_mipmap
)
1325 elem_props_template_finalize(tmpl
, props
)
1327 # Custom properties.
1328 if scene_data
.settings
.use_custom_props
:
1329 fbx_data_element_custom_properties(props
, tex
.texture
)
1332 def fbx_data_video_elements(root
, vid
, scene_data
):
1334 Write the actual image data block.
1336 vid_key
, _texs
= scene_data
.data_videos
[vid
]
1337 fname_abs
, fname_rel
= _gen_vid_path(vid
, scene_data
)
1339 fbx_vid
= elem_data_single_int64(root
, b
"Video", get_fbx_uuid_from_key(vid_key
))
1340 fbx_vid
.add_string(fbx_name_class(vid
.name
.encode(), b
"Video"))
1341 fbx_vid
.add_string(b
"Clip")
1343 elem_data_single_string(fbx_vid
, b
"Type", b
"Clip")
1346 tmpl
= elem_props_template_init(scene_data
.templates
, b
"Video")
1347 props
= elem_properties(fbx_vid
)
1348 elem_props_template_set(tmpl
, props
, "p_string_url", b
"Path", fname_abs
)
1349 elem_props_template_finalize(tmpl
, props
)
1351 elem_data_single_int32(fbx_vid
, b
"UseMipMap", 0)
1352 elem_data_single_string_unicode(fbx_vid
, b
"FileName", fname_abs
)
1353 elem_data_single_string_unicode(fbx_vid
, b
"RelativeFilename", fname_rel
)
1355 if scene_data
.settings
.media_settings
.embed_textures
:
1356 if vid
.packed_file
is not None:
1357 elem_data_single_bytes(fbx_vid
, b
"Content", vid
.packed_file
.data
)
1359 filepath
= bpy
.path
.abspath(vid
.filepath
)
1361 with
open(filepath
, 'br') as f
:
1362 elem_data_single_bytes(fbx_vid
, b
"Content", f
.read())
1363 except Exception as e
:
1364 print("WARNING: embedding file {} failed ({})".format(filepath
, e
))
1365 elem_data_single_bytes(fbx_vid
, b
"Content", b
"")
1367 elem_data_single_bytes(fbx_vid
, b
"Content", b
"")
1370 def fbx_data_armature_elements(root
, arm_obj
, scene_data
):
1373 * Bones "data" (NodeAttribute::LimbNode, contains pretty much nothing!).
1374 * Deformers (i.e. Skin), bind between an armature and a mesh.
1375 ** SubDeformers (i.e. Cluster), one per bone/vgroup pair.
1377 Note armature itself has no data, it is a mere "Null" Model...
1379 mat_world_arm
= arm_obj
.fbx_object_matrix(scene_data
, global_space
=True)
1380 bones
= tuple(bo_obj
for bo_obj
in arm_obj
.bones
if bo_obj
in scene_data
.objects
)
1383 for bo_obj
in bones
:
1385 bo_data_key
= scene_data
.data_bones
[bo_obj
]
1386 fbx_bo
= elem_data_single_int64(root
, b
"NodeAttribute", get_fbx_uuid_from_key(bo_data_key
))
1387 fbx_bo
.add_string(fbx_name_class(bo
.name
.encode(), b
"NodeAttribute"))
1388 fbx_bo
.add_string(b
"LimbNode")
1389 elem_data_single_string(fbx_bo
, b
"TypeFlags", b
"Skeleton")
1391 tmpl
= elem_props_template_init(scene_data
.templates
, b
"Bone")
1392 props
= elem_properties(fbx_bo
)
1393 elem_props_template_set(tmpl
, props
, "p_double", b
"Size", (bo
.tail_local
- bo
.head_local
).length
)
1394 elem_props_template_finalize(tmpl
, props
)
1396 # Custom properties.
1397 if scene_data
.settings
.use_custom_props
:
1398 fbx_data_element_custom_properties(props
, bo
)
1400 # Skin deformers and BindPoses.
1401 # Note: we might also use Deformers for our "parent to vertex" stuff???
1402 deformer
= scene_data
.data_deformers_skin
.get(arm_obj
, None)
1403 if deformer
is not None:
1404 for me
, (skin_key
, ob_obj
, clusters
) in deformer
.items():
1407 mat_world_obj
, mat_world_bones
= fbx_data_bindpose_element(root
, ob_obj
, me
, scene_data
, arm_obj
, bones
)
1410 fbx_skin
= elem_data_single_int64(root
, b
"Deformer", get_fbx_uuid_from_key(skin_key
))
1411 fbx_skin
.add_string(fbx_name_class(arm_obj
.name
.encode(), b
"Deformer"))
1412 fbx_skin
.add_string(b
"Skin")
1414 elem_data_single_int32(fbx_skin
, b
"Version", FBX_DEFORMER_SKIN_VERSION
)
1415 elem_data_single_float64(fbx_skin
, b
"Link_DeformAcuracy", 50.0) # Only vague idea what it is...
1417 # Pre-process vertex weights (also to check vertices assigned ot more than four bones).
1419 bo_vg_idx
= {bo_obj
.bdata
.name
: ob
.vertex_groups
[bo_obj
.bdata
.name
].index
1420 for bo_obj
in clusters
.keys() if bo_obj
.bdata
.name
in ob
.vertex_groups
}
1421 valid_idxs
= set(bo_vg_idx
.values())
1422 vgroups
= {vg
.index
: OrderedDict() for vg
in ob
.vertex_groups
}
1423 verts_vgroups
= (sorted(((vg
.group
, vg
.weight
) for vg
in v
.groups
if vg
.weight
and vg
.group
in valid_idxs
),
1424 key
=lambda e
: e
[1], reverse
=True)
1425 for v
in me
.vertices
)
1426 for idx
, vgs
in enumerate(verts_vgroups
):
1427 for vg_idx
, w
in vgs
:
1428 vgroups
[vg_idx
][idx
] = w
1430 for bo_obj
, clstr_key
in clusters
.items():
1432 # Find which vertices are affected by this bone/vgroup pair, and matching weights.
1433 # Note we still write a cluster for bones not affecting the mesh, to get 'rest pose' data
1434 # (the TransformBlah matrices).
1435 vg_idx
= bo_vg_idx
.get(bo
.name
, None)
1436 indices
, weights
= ((), ()) if vg_idx
is None or not vgroups
[vg_idx
] else zip(*vgroups
[vg_idx
].items())
1438 # Create the cluster.
1439 fbx_clstr
= elem_data_single_int64(root
, b
"Deformer", get_fbx_uuid_from_key(clstr_key
))
1440 fbx_clstr
.add_string(fbx_name_class(bo
.name
.encode(), b
"SubDeformer"))
1441 fbx_clstr
.add_string(b
"Cluster")
1443 elem_data_single_int32(fbx_clstr
, b
"Version", FBX_DEFORMER_CLUSTER_VERSION
)
1444 # No idea what that user data might be...
1445 fbx_userdata
= elem_data_single_string(fbx_clstr
, b
"UserData", b
"")
1446 fbx_userdata
.add_string(b
"")
1447 elem_data_single_int32_array(fbx_clstr
, b
"Indexes", indices
)
1448 elem_data_single_float64_array(fbx_clstr
, b
"Weights", weights
)
1449 # Transform, TransformLink and TransformAssociateModel matrices...
1450 # They seem to be doublons of BindPose ones??? Have armature (associatemodel) in addition, though.
1451 # WARNING! Even though official FBX API presents Transform in global space,
1452 # **it is stored in bone space in FBX data!** See:
1453 # http://area.autodesk.com/forum/autodesk-fbx/fbx-sdk/why-the-values-return-
1454 # by-fbxcluster-gettransformmatrix-x-not-same-with-the-value-in-ascii-fbx-file/
1455 elem_data_single_float64_array(fbx_clstr
, b
"Transform",
1456 matrix4_to_array(mat_world_bones
[bo_obj
].inverted() * mat_world_obj
))
1457 elem_data_single_float64_array(fbx_clstr
, b
"TransformLink", matrix4_to_array(mat_world_bones
[bo_obj
]))
1458 elem_data_single_float64_array(fbx_clstr
, b
"TransformAssociateModel", matrix4_to_array(mat_world_arm
))
1461 def fbx_data_object_elements(root
, ob_obj
, scene_data
):
1463 Write the Object (Model) data blocks.
1464 Note this "Model" can also be bone or dupli!
1466 obj_type
= b
"Null" # default, sort of empty...
1468 obj_type
= b
"LimbNode"
1469 elif (ob_obj
.type in BLENDER_OBJECT_TYPES_MESHLIKE
):
1471 elif (ob_obj
.type == 'LAMP'):
1473 elif (ob_obj
.type == 'CAMERA'):
1474 obj_type
= b
"Camera"
1475 model
= elem_data_single_int64(root
, b
"Model", ob_obj
.fbx_uuid
)
1476 model
.add_string(fbx_name_class(ob_obj
.name
.encode(), b
"Model"))
1477 model
.add_string(obj_type
)
1479 elem_data_single_int32(model
, b
"Version", FBX_MODELS_VERSION
)
1481 # Object transform info.
1482 loc
, rot
, scale
, matrix
, matrix_rot
= ob_obj
.fbx_object_tx(scene_data
)
1483 rot
= tuple(convert_rad_to_deg_iter(rot
))
1485 tmpl
= elem_props_template_init(scene_data
.templates
, b
"Model")
1486 # For now add only loc/rot/scale...
1487 props
= elem_properties(model
)
1488 elem_props_template_set(tmpl
, props
, "p_lcl_translation", b
"Lcl Translation", loc
)
1489 elem_props_template_set(tmpl
, props
, "p_lcl_rotation", b
"Lcl Rotation", rot
)
1490 elem_props_template_set(tmpl
, props
, "p_lcl_scaling", b
"Lcl Scaling", scale
)
1491 elem_props_template_set(tmpl
, props
, "p_visibility", b
"Visibility", float(not ob_obj
.hide
))
1493 # Absolutely no idea what this is, but seems mandatory for validity of the file, and defaults to
1494 # invalid -1 value...
1495 elem_props_template_set(tmpl
, props
, "p_integer", b
"DefaultAttributeIndex", 0)
1497 elem_props_template_set(tmpl
, props
, "p_enum", b
"InheritType", 1) # RSrs
1499 # Custom properties.
1500 if scene_data
.settings
.use_custom_props
:
1501 fbx_data_element_custom_properties(props
, ob_obj
.bdata
)
1503 # Those settings would obviously need to be edited in a complete version of the exporter, may depends on
1505 elem_data_single_int32(model
, b
"MultiLayer", 0)
1506 elem_data_single_int32(model
, b
"MultiTake", 0)
1507 elem_data_single_bool(model
, b
"Shading", True)
1508 elem_data_single_string(model
, b
"Culling", b
"CullingOff")
1510 if obj_type
== b
"Camera":
1511 # Why, oh why are FBX cameras such a mess???
1512 # And WHY add camera data HERE??? Not even sure this is needed...
1513 render
= scene_data
.scene
.render
1514 width
= render
.resolution_x
* 1.0
1515 height
= render
.resolution_y
* 1.0
1516 elem_props_template_set(tmpl
, props
, "p_enum", b
"ResolutionMode", 0) # Don't know what it means
1517 elem_props_template_set(tmpl
, props
, "p_double", b
"AspectW", width
)
1518 elem_props_template_set(tmpl
, props
, "p_double", b
"AspectH", height
)
1519 elem_props_template_set(tmpl
, props
, "p_bool", b
"ViewFrustum", True)
1520 elem_props_template_set(tmpl
, props
, "p_enum", b
"BackgroundMode", 0) # Don't know what it means
1521 elem_props_template_set(tmpl
, props
, "p_bool", b
"ForegroundTransparent", True)
1523 elem_props_template_finalize(tmpl
, props
)
1526 def fbx_data_animation_elements(root
, scene_data
):
1528 Write animation data.
1530 animations
= scene_data
.animations
1533 scene
= scene_data
.scene
1535 fps
= scene
.render
.fps
/ scene
.render
.fps_base
1537 def keys_to_ktimes(keys
):
1538 return (int(v
) for v
in convert_sec_to_ktime_iter((f
/ fps
for f
, _v
in keys
)))
1541 for astack_key
, alayers
, alayer_key
, name
, f_start
, f_end
in animations
:
1542 astack
= elem_data_single_int64(root
, b
"AnimationStack", get_fbx_uuid_from_key(astack_key
))
1543 astack
.add_string(fbx_name_class(name
, b
"AnimStack"))
1544 astack
.add_string(b
"")
1546 astack_tmpl
= elem_props_template_init(scene_data
.templates
, b
"AnimationStack")
1547 astack_props
= elem_properties(astack
)
1548 r
= scene_data
.scene
.render
1549 fps
= r
.fps
/ r
.fps_base
1550 start
= int(convert_sec_to_ktime(f_start
/ fps
))
1551 end
= int(convert_sec_to_ktime(f_end
/ fps
))
1552 elem_props_template_set(astack_tmpl
, astack_props
, "p_timestamp", b
"LocalStart", start
)
1553 elem_props_template_set(astack_tmpl
, astack_props
, "p_timestamp", b
"LocalStop", end
)
1554 elem_props_template_set(astack_tmpl
, astack_props
, "p_timestamp", b
"ReferenceStart", start
)
1555 elem_props_template_set(astack_tmpl
, astack_props
, "p_timestamp", b
"ReferenceStop", end
)
1556 elem_props_template_finalize(astack_tmpl
, astack_props
)
1558 # For now, only one layer for all animations.
1559 alayer
= elem_data_single_int64(root
, b
"AnimationLayer", get_fbx_uuid_from_key(alayer_key
))
1560 alayer
.add_string(fbx_name_class(name
, b
"AnimLayer"))
1561 alayer
.add_string(b
"")
1563 for ob_obj
, (alayer_key
, acurvenodes
) in alayers
.items():
1565 # alayer = elem_data_single_int64(root, b"AnimationLayer", get_fbx_uuid_from_key(alayer_key))
1566 # alayer.add_string(fbx_name_class(ob_obj.name.encode(), b"AnimLayer"))
1567 # alayer.add_string(b"")
1569 for fbx_prop
, (acurvenode_key
, acurves
, acurvenode_name
) in acurvenodes
.items():
1570 # Animation curve node.
1571 acurvenode
= elem_data_single_int64(root
, b
"AnimationCurveNode", get_fbx_uuid_from_key(acurvenode_key
))
1572 acurvenode
.add_string(fbx_name_class(acurvenode_name
.encode(), b
"AnimCurveNode"))
1573 acurvenode
.add_string(b
"")
1575 acn_tmpl
= elem_props_template_init(scene_data
.templates
, b
"AnimationCurveNode")
1576 acn_props
= elem_properties(acurvenode
)
1578 for fbx_item
, (acurve_key
, def_value
, keys
, _acurve_valid
) in acurves
.items():
1579 elem_props_template_set(acn_tmpl
, acn_props
, "p_number", fbx_item
.encode(),
1580 def_value
, animatable
=True)
1582 # Only create Animation curve if needed!
1584 acurve
= elem_data_single_int64(root
, b
"AnimationCurve", get_fbx_uuid_from_key(acurve_key
))
1585 acurve
.add_string(fbx_name_class(b
"", b
"AnimCurve"))
1586 acurve
.add_string(b
"")
1589 nbr_keys
= len(keys
)
1592 1 << 2 |
# interpolation mode, 1 = constant, 2 = linear, 3 = cubic.
1593 1 << 8 |
# tangent mode, 8 = auto, 9 = TCB, 10 = user, 11 = generic break,
1594 1 << 13 |
# tangent mode, 12 = generic clamp, 13 = generic time independent,
1595 1 << 14 |
# tangent mode, 13 + 14 = generic clamp progressive.
1598 # Maybe values controlling TCB & co???
1599 keyattr_datafloat
= (0.0, 0.0, 9.419963346924634e-30, 0.0)
1601 # And now, the *real* data!
1602 elem_data_single_float64(acurve
, b
"Default", def_value
)
1603 elem_data_single_int32(acurve
, b
"KeyVer", FBX_ANIM_KEY_VERSION
)
1604 elem_data_single_int64_array(acurve
, b
"KeyTime", keys_to_ktimes(keys
))
1605 elem_data_single_float32_array(acurve
, b
"KeyValueFloat", (v
for _f
, v
in keys
))
1606 elem_data_single_int32_array(acurve
, b
"KeyAttrFlags", keyattr_flags
)
1607 elem_data_single_float32_array(acurve
, b
"KeyAttrDataFloat", keyattr_datafloat
)
1608 elem_data_single_int32_array(acurve
, b
"KeyAttrRefCount", (nbr_keys
,))
1610 elem_props_template_finalize(acn_tmpl
, acn_props
)
1613 # ##### Top-level FBX data container. #####
1615 def fbx_mat_properties_from_texture(tex
):
1617 Returns a set of FBX metarial properties that are affected by the given texture.
1618 Quite obviously, this is a fuzzy and far-from-perfect mapping! Amounts of influence are completely lost, e.g.
1619 Note tex is actually expected to be a texture slot.
1621 # Mapping Blender -> FBX (blend_use_name, blend_fact_name, fbx_name).
1623 # Lambert & Phong...
1624 ("diffuse", "diffuse", b
"DiffuseFactor"),
1625 ("color_diffuse", "diffuse_color", b
"DiffuseColor"),
1626 ("alpha", "alpha", b
"TransparencyFactor"),
1627 ("diffuse", "diffuse", b
"TransparentColor"), # Uses diffuse color in Blender!
1628 ("emit", "emit", b
"EmissiveFactor"),
1629 ("diffuse", "diffuse", b
"EmissiveColor"), # Uses diffuse color in Blender!
1630 ("ambient", "ambient", b
"AmbientFactor"),
1631 # ("", "", b"AmbientColor"), # World stuff in Blender, for now ignore...
1632 ("normal", "normal", b
"NormalMap"),
1633 # Note: unsure about those... :/
1634 # ("", "", b"Bump"),
1635 # ("", "", b"BumpFactor"),
1636 # ("", "", b"DisplacementColor"),
1637 # ("", "", b"DisplacementFactor"),
1639 ("specular", "specular", b
"SpecularFactor"),
1640 ("color_spec", "specular_color", b
"SpecularColor"),
1641 # See Material template about those two!
1642 ("hardness", "hardness", b
"Shininess"),
1643 ("hardness", "hardness", b
"ShininessExponent"),
1644 ("mirror", "mirror", b
"ReflectionColor"),
1645 ("raymir", "raymir", b
"ReflectionFactor"),
1648 tex_fbx_props
= set()
1649 for use_map_name
, name_factor
, fbx_prop_name
in blend_to_fbx
:
1650 # Always export enabled textures, even if they have a null influence...
1651 if getattr(tex
, "use_map_" + use_map_name
):
1652 tex_fbx_props
.add(fbx_prop_name
)
1654 return tex_fbx_props
1657 def fbx_skeleton_from_armature(scene
, settings
, arm_obj
, objects
, data_meshes
,
1658 data_bones
, data_deformers_skin
, arm_parents
):
1660 Create skeleton from armature/bones (NodeAttribute/LimbNode and Model/LimbNode), and for each deformed mesh,
1661 create Pose/BindPose(with sub PoseNode) and Deformer/Skin(with Deformer/SubDeformer/Cluster).
1662 Also supports "parent to bone" (simple parent to Model/LimbNode).
1663 arm_parents is a set of tuples (armature, object) for all successful armature bindings.
1665 arm_data
= arm_obj
.bdata
.data
1666 bones
= OrderedDict()
1667 for bo
in arm_obj
.bones
:
1668 if settings
.use_armature_deform_only
:
1669 if bo
.bdata
.use_deform
:
1672 while bo_par
.is_bone
:
1673 bones
[bo_par
] = True
1674 bo_par
= bo_par
.parent
1675 elif bo
not in bones
: # Do not override if already set in the loop above!
1680 bones
= OrderedDict((bo
, None) for bo
, use
in bones
.items() if use
)
1685 data_bones
.update((bo
, get_blender_bone_key(arm_obj
.bdata
, bo
.bdata
)) for bo
in bones
)
1687 for ob_obj
in objects
:
1688 if not (ob_obj
.is_object
and ob_obj
.type == 'MESH' and ob_obj
.parent
== arm_obj
):
1691 # Always handled by an Armature modifier...
1693 for mod
in ob_obj
.bdata
.modifiers
:
1694 if mod
.type not in {'ARMATURE'}:
1696 # We only support vertex groups binding method, not bone envelopes one!
1697 if mod
.object == arm_obj
.bdata
and mod
.use_vertex_groups
:
1704 # Now we have a mesh using this armature.
1705 # Note: bindpose have no relations at all (no connections), so no need for any preprocess for them.
1706 # Create skin & clusters relations (note skins are connected to geometry, *not* model!).
1707 _key
, me
, _free
= data_meshes
[ob_obj
]
1708 clusters
= OrderedDict((bo
, get_blender_bone_cluster_key(arm_obj
.bdata
, me
, bo
.bdata
)) for bo
in bones
)
1709 data_deformers_skin
.setdefault(arm_obj
, OrderedDict())[me
] = (get_blender_armature_skin_key(arm_obj
.bdata
, me
),
1712 # We don't want a regular parent relationship for those in FBX...
1713 arm_parents
.add((arm_obj
, ob_obj
))
1714 # Needed to handle matrices/spaces (since we do not parent them to 'armature' in FBX :/ ).
1715 ob_obj
.parented_to_armature
= True
1717 objects
.update(bones
)
1720 def fbx_animations_do(scene_data
, ref_id
, f_start
, f_end
, start_zero
, objects
=None, force_keep
=False):
1722 Generate animation data (a single AnimStack) from objects, for a given frame range.
1724 bake_step
= scene_data
.settings
.bake_anim_step
1725 scene
= scene_data
.scene
1726 meshes
= scene_data
.data_meshes
1728 if objects
is not None:
1729 # Add bones and duplis!
1730 for ob_obj
in tuple(objects
):
1731 if not ob_obj
.is_object
:
1733 if ob_obj
.type == 'ARMATURE':
1734 objects |
= {bo_obj
for bo_obj
in ob_obj
.bones
if bo_obj
in scene_data
.objects
}
1735 ob_obj
.dupli_list_create(scene
, 'RENDER')
1736 for dp_obj
in ob_obj
.dupli_list
:
1737 if dp_obj
in scene_data
.objects
:
1739 ob_obj
.dupli_list_clear()
1741 objects
= scene_data
.objects
1743 back_currframe
= scene
.frame_current
1744 animdata_ob
= OrderedDict((ob_obj
, (AnimationCurveNodeWrapper(ob_obj
.key
, 'LCL_TRANSLATION', (0.0, 0.0, 0.0)),
1745 AnimationCurveNodeWrapper(ob_obj
.key
, 'LCL_ROTATION', (0.0, 0.0, 0.0)),
1746 AnimationCurveNodeWrapper(ob_obj
.key
, 'LCL_SCALING', (1.0, 1.0, 1.0))))
1747 for ob_obj
in objects
)
1749 animdata_shapes
= OrderedDict()
1750 for me
, (me_key
, _shapes_key
, shapes
) in scene_data
.data_deformers_shape
.items():
1751 # Ignore absolute shape keys for now!
1752 if not me
.shape_keys
.use_relative
:
1754 for shape
, (channel_key
, geom_key
, _shape_verts_co
, _shape_verts_idx
) in shapes
.items():
1755 acnode
= AnimationCurveNodeWrapper(channel_key
, 'SHAPE_KEY', (0.0,))
1756 # Sooooo happy to have to twist again like a mad snake... Yes, we need to write those curves twice. :/
1757 acnode
.add_group(me_key
, shape
.name
, shape
.name
, (shape
.name
,))
1758 animdata_shapes
[channel_key
] = (acnode
, me
, shape
)
1763 while currframe
< f_end
:
1764 real_currframe
= currframe
- f_start
if start_zero
else currframe
1765 scene
.frame_set(int(currframe
), currframe
- int(currframe
))
1767 for ob_obj
in animdata_ob
:
1768 ob_obj
.dupli_list_create(scene
, 'RENDER')
1769 for ob_obj
, (anim_loc
, anim_rot
, anim_scale
) in animdata_ob
.items():
1770 # We compute baked loc/rot/scale for all objects (rot being euler-compat with previous value!).
1771 p_rot
= p_rots
.get(ob_obj
, None)
1772 loc
, rot
, scale
, _m
, _mr
= ob_obj
.fbx_object_tx(scene_data
, rot_euler_compat
=p_rot
)
1773 p_rots
[ob_obj
] = rot
1774 anim_loc
.add_keyframe(real_currframe
, loc
)
1775 anim_rot
.add_keyframe(real_currframe
, tuple(convert_rad_to_deg_iter(rot
)))
1776 anim_scale
.add_keyframe(real_currframe
, scale
)
1777 for ob_obj
in objects
:
1778 ob_obj
.dupli_list_clear()
1779 for anim_shape
, me
, shape
in animdata_shapes
.values():
1780 anim_shape
.add_keyframe(real_currframe
, (shape
.value
* 100.0,))
1781 currframe
+= bake_step
1783 scene
.frame_set(back_currframe
, 0.0)
1785 animations
= OrderedDict()
1786 simplify_fac
= scene_data
.settings
.bake_anim_simplify_factor
1788 # And now, produce final data (usable by FBX export code)
1789 # Objects-like loc/rot/scale...
1790 for ob_obj
, anims
in animdata_ob
.items():
1792 anim
.simplify(simplify_fac
, bake_step
, force_keep
)
1795 for obj_key
, group_key
, group
, fbx_group
, fbx_gname
in anim
.get_final_data(scene
, ref_id
, force_keep
):
1796 anim_data
= animations
.get(obj_key
)
1797 if anim_data
is None:
1798 anim_data
= animations
[obj_key
] = ("dummy_unused_key", OrderedDict())
1799 anim_data
[1][fbx_group
] = (group_key
, group
, fbx_gname
)
1801 # And meshes' shape keys.
1802 for channel_key
, (anim_shape
, me
, shape
) in animdata_shapes
.items():
1803 final_keys
= OrderedDict()
1804 anim_shape
.simplify(simplify_fac
, bake_step
, force_keep
)
1807 for elem_key
, group_key
, group
, fbx_group
, fbx_gname
in anim_shape
.get_final_data(scene
, ref_id
, force_keep
):
1808 anim_data
= animations
.get(elem_key
)
1809 if anim_data
is None:
1810 anim_data
= animations
[elem_key
] = ("dummy_unused_key", OrderedDict())
1811 anim_data
[1][fbx_group
] = (group_key
, group
, fbx_gname
)
1813 astack_key
= get_blender_anim_stack_key(scene
, ref_id
)
1814 alayer_key
= get_blender_anim_layer_key(scene
, ref_id
)
1815 name
= (get_blenderID_name(ref_id
) if ref_id
else scene
.name
).encode()
1821 return (astack_key
, animations
, alayer_key
, name
, f_start
, f_end
) if animations
else None
1824 def fbx_animations(scene_data
):
1826 Generate global animation data from objects.
1828 scene
= scene_data
.scene
1833 def add_anim(animations
, anim
):
1834 nonlocal frame_start
, frame_end
1835 if anim
is not None:
1836 animations
.append(anim
)
1837 f_start
, f_end
= anim
[4:6]
1838 if f_start
< frame_start
:
1839 frame_start
= f_start
1840 if f_end
> frame_end
:
1843 # Per-NLA strip animstacks.
1844 if scene_data
.settings
.bake_anim_use_nla_strips
:
1846 for ob_obj
in scene_data
.objects
:
1847 # NLA tracks only for objects, not bones!
1848 if not ob_obj
.is_object
:
1850 ob
= ob_obj
.bdata
# Back to real Blender Object.
1851 if not ob
.animation_data
:
1853 for track
in ob
.animation_data
.nla_tracks
:
1856 for strip
in track
.strips
:
1859 strips
.append(strip
)
1862 for strip
in strips
:
1864 add_anim(animations
, fbx_animations_do(scene_data
, strip
, strip
.frame_start
, strip
.frame_end
, True))
1867 for strip
in strips
:
1871 if scene_data
.settings
.bake_anim_use_all_actions
:
1872 def validate_actions(act
, path_resolve
):
1873 for fc
in act
.fcurves
:
1874 data_path
= fc
.data_path
1876 data_path
= data_path
+ "[%d]" % fc
.array_index
1878 path_resolve(data_path
)
1880 return False # Invalid.
1881 return True # Valid.
1883 def restore_object(ob_to
, ob_from
):
1884 # Restore org state of object (ugh :/ ).
1886 'location', 'rotation_quaternion', 'rotation_axis_angle', 'rotation_euler', 'rotation_mode', 'scale',
1887 'delta_location', 'delta_rotation_euler', 'delta_rotation_quaternion', 'delta_scale',
1888 'lock_location', 'lock_rotation', 'lock_rotation_w', 'lock_rotations_4d', 'lock_scale',
1889 'tag', 'layers', 'select', 'track_axis', 'up_axis', 'active_material', 'active_material_index',
1890 'matrix_parent_inverse', 'empty_draw_type', 'empty_draw_size', 'empty_image_offset', 'pass_index',
1891 'color', 'hide', 'hide_select', 'hide_render', 'use_slow_parent', 'slow_parent_offset',
1892 'use_extra_recalc_object', 'use_extra_recalc_data', 'dupli_type', 'use_dupli_frames_speed',
1893 'use_dupli_vertices_rotation', 'use_dupli_faces_scale', 'dupli_faces_scale', 'dupli_group',
1894 'dupli_frames_start', 'dupli_frames_end', 'dupli_frames_on', 'dupli_frames_off',
1895 'draw_type', 'show_bounds', 'draw_bounds_type', 'show_name', 'show_axis', 'show_texture_space',
1896 'show_wire', 'show_all_edges', 'show_transparent', 'show_x_ray',
1897 'show_only_shape_key', 'use_shape_key_edit_mode', 'active_shape_key_index',
1900 setattr(ob_to
, p
, getattr(ob_from
, p
))
1902 for ob_obj
in scene_data
.objects
:
1903 # Actions only for objects, not bones!
1904 if not ob_obj
.is_object
:
1907 ob
= ob_obj
.bdata
# Back to real Blender Object.
1909 # We can't play with animdata and actions and get back to org state easily.
1910 # So we have to add a temp copy of the object to the scene, animate it, and remove it... :/
1912 # Great, have to handle bones as well if needed...
1913 pbones_matrices
= [pbo
.matrix_basis
.copy() for pbo
in ob
.pose
.bones
] if ob
.type == 'ARMATURE' else ...
1915 if ob
.animation_data
:
1916 org_act
= ob
.animation_data
.action
1919 ob
.animation_data_create()
1920 path_resolve
= ob
.path_resolve
1922 for act
in bpy
.data
.actions
:
1923 # For now, *all* paths in the action must be valid for the object, to validate the action.
1924 # Unless that action was already assigned to the object!
1925 if act
!= org_act
and not validate_actions(act
, path_resolve
):
1927 ob
.animation_data
.action
= act
1928 frame_start
, frame_end
= act
.frame_range
# sic!
1929 add_anim(animations
,
1930 fbx_animations_do(scene_data
, (ob
, act
), frame_start
, frame_end
, True, {ob_obj}
, True))
1932 if pbones_matrices
is not ...:
1933 for pbo
, mat
in zip(ob
.pose
.bones
, pbones_matrices
):
1934 pbo
.matrix_basis
= mat
.copy()
1935 ob
.animation_data
.action
= None if org_act
is ... else org_act
1936 restore_object(ob
, ob_copy
)
1938 if pbones_matrices
is not ...:
1939 for pbo
, mat
in zip(ob
.pose
.bones
, pbones_matrices
):
1940 pbo
.matrix_basis
= mat
.copy()
1942 ob
.animation_data_clear()
1944 ob
.animation_data
.action
= org_act
1946 bpy
.data
.objects
.remove(ob_copy
)
1948 # Global (containing everything) animstack.
1949 if not scene_data
.settings
.bake_anim_use_nla_strips
or not animations
:
1950 add_anim(animations
, fbx_animations_do(scene_data
, None, scene
.frame_start
, scene
.frame_end
, False))
1952 # Be sure to update all matrices back to org state!
1953 scene
.frame_set(scene
.frame_current
, 0.0)
1955 return animations
, frame_start
, frame_end
1958 def fbx_data_from_scene(scene
, settings
):
1960 Do some pre-processing over scene's data...
1962 objtypes
= settings
.object_types
1964 # ##### Gathering data...
1966 # This is rather simple for now, maybe we could end generating templates with most-used values
1967 # instead of default ones?
1968 objects
= OrderedDict() # Because we do not have any ordered set...
1969 for ob
in settings
.context_objects
:
1970 if ob
.type not in objtypes
:
1972 ob_obj
= ObjectWrapper(ob
)
1973 objects
[ob_obj
] = None
1975 ob_obj
.dupli_list_create(scene
, 'RENDER')
1976 for dp_obj
in ob_obj
.dupli_list
:
1977 objects
[dp_obj
] = None
1978 ob_obj
.dupli_list_clear()
1980 data_lamps
= OrderedDict((ob_obj
.bdata
.data
, get_blenderID_key(ob_obj
.bdata
.data
))
1981 for ob_obj
in objects
if ob_obj
.type == 'LAMP')
1982 # Unfortunately, FBX camera data contains object-level data (like position, orientation, etc.)...
1983 data_cameras
= OrderedDict((ob_obj
, get_blenderID_key(ob_obj
.bdata
.data
))
1984 for ob_obj
in objects
if ob_obj
.type == 'CAMERA')
1985 # Yep! Contains nothing, but needed!
1986 data_empties
= OrderedDict((ob_obj
, get_blender_empty_key(ob_obj
.bdata
))
1987 for ob_obj
in objects
if ob_obj
.type == 'EMPTY')
1989 data_meshes
= OrderedDict()
1990 for ob_obj
in objects
:
1991 if ob_obj
.type not in BLENDER_OBJECT_TYPES_MESHLIKE
:
1994 if ob
in data_meshes
: # Happens with dupli instances.
1997 if settings
.use_mesh_modifiers
or ob
.type in BLENDER_OTHER_OBJECT_TYPES
:
1998 use_org_data
= False
2000 if ob
.type == 'MESH':
2001 # No need to create a new mesh in this case, if no modifier is active!
2003 for mod
in ob
.modifiers
:
2004 # For meshes, when armature export is enabled, disable Armature modifiers here!
2005 if mod
.type == 'ARMATURE' and 'ARMATURE' in settings
.object_types
:
2006 tmp_mods
.append((mod
, mod
.show_render
))
2007 mod
.show_render
= False
2009 use_org_data
= False
2010 if not use_org_data
:
2011 tmp_me
= ob
.to_mesh(scene
, apply_modifiers
=True, settings
='RENDER')
2012 data_meshes
[ob_obj
] = (get_blenderID_key(tmp_me
), tmp_me
, True)
2013 # Re-enable temporary disabled modifiers.
2014 for mod
, show_render
in tmp_mods
:
2015 mod
.show_render
= show_render
2017 data_meshes
[ob_obj
] = (get_blenderID_key(ob
.data
), ob
.data
, False)
2020 data_deformers_shape
= OrderedDict()
2021 for me_key
, me
, _org
in data_meshes
.values():
2022 if not (me
.shape_keys
and me
.shape_keys
.key_blocks
):
2024 shapes_key
= get_blender_mesh_shape_key(me
)
2025 for shape
in me
.shape_keys
.key_blocks
:
2026 # Only write vertices really different from org coordinates!
2027 # XXX FBX does not like empty shapes (makes Unity crash e.g.), so we have to do this here... :/
2029 shape_verts_idx
= []
2030 for idx
, (sv
, v
) in enumerate(zip(shape
.data
, me
.vertices
)):
2031 if similar_values_iter(sv
.co
, v
.co
):
2032 # Note: Maybe this is a bit too simplistic, should we use real shape base here? Though FBX does not
2033 # have this at all... Anyway, this should cover most common cases imho.
2035 shape_verts_co
.extend(sv
.co
- v
.co
)
2036 shape_verts_idx
.append(idx
)
2037 if not shape_verts_co
:
2039 channel_key
, geom_key
= get_blender_mesh_shape_channel_key(me
, shape
)
2040 data
= (channel_key
, geom_key
, shape_verts_co
, shape_verts_idx
)
2041 data_deformers_shape
.setdefault(me
, (me_key
, shapes_key
, OrderedDict()))[2][shape
] = data
2044 data_deformers_skin
= OrderedDict()
2045 data_bones
= OrderedDict()
2047 for ob_obj
in tuple(objects
):
2048 if not (ob_obj
.is_object
and ob_obj
.type in {'ARMATURE'}):
2050 fbx_skeleton_from_armature(scene
, settings
, ob_obj
, objects
, data_meshes
,
2051 data_bones
, data_deformers_skin
, arm_parents
)
2053 # Some world settings are embedded in FBX materials...
2055 data_world
= OrderedDict(((scene
.world
, get_blenderID_key(scene
.world
)),))
2057 data_world
= OrderedDict()
2059 # TODO: Check all the mat stuff works even when mats are linked to Objects
2060 # (we can then have the same mesh used with different materials...).
2061 # *Should* work, as FBX always links its materials to Models (i.e. objects).
2062 # XXX However, material indices would probably break...
2063 data_materials
= OrderedDict()
2064 for ob_obj
in objects
:
2065 # If obj is not a valid object for materials, wrapper will just return an empty tuple...
2066 for mat_s
in ob_obj
.material_slots
:
2067 mat
= mat_s
.material
2069 continue # Empty slots!
2070 # Note theoretically, FBX supports any kind of materials, even GLSL shaders etc.
2071 # However, I doubt anything else than Lambert/Phong is really portable!
2072 # We support any kind of 'surface' shader though, better to have some kind of default Lambert than nothing.
2073 # Note we want to keep a 'dummy' empty mat even when we can't really support it, see T41396.
2074 mat_data
= data_materials
.get(mat
)
2075 if mat_data
is not None:
2076 mat_data
[1].append(ob_obj
)
2078 data_materials
[mat
] = (get_blenderID_key(mat
), [ob_obj
])
2080 # Note FBX textures also hold their mapping info.
2081 # TODO: Support layers?
2082 data_textures
= OrderedDict()
2083 # FbxVideo also used to store static images...
2084 data_videos
= OrderedDict()
2085 # For now, do not use world textures, don't think they can be linked to anything FBX wise...
2086 for mat
in data_materials
.keys():
2087 if check_skip_material(mat
):
2089 for tex
, use_tex
in zip(mat
.texture_slots
, mat
.use_textures
):
2090 if tex
is None or not use_tex
:
2092 # For now, only consider image textures.
2093 # Note FBX does has support for procedural, but this is not portable at all (opaque blob),
2094 # so not useful for us.
2095 # TODO I think ENVIRONMENT_MAP should be usable in FBX as well, but for now let it aside.
2096 # if tex.texture.type not in {'IMAGE', 'ENVIRONMENT_MAP'}:
2097 if tex
.texture
.type not in {'IMAGE'}:
2099 img
= tex
.texture
.image
2102 # Find out whether we can actually use this texture for this material, in FBX context.
2103 tex_fbx_props
= fbx_mat_properties_from_texture(tex
)
2104 if not tex_fbx_props
:
2106 tex_data
= data_textures
.get(tex
)
2107 if tex_data
is not None:
2108 tex_data
[1][mat
] = tex_fbx_props
2110 data_textures
[tex
] = (get_blenderID_key(tex
), OrderedDict(((mat
, tex_fbx_props
),)))
2111 vid_data
= data_videos
.get(img
)
2112 if vid_data
is not None:
2113 vid_data
[1].append(tex
)
2115 data_videos
[img
] = (get_blenderID_key(img
), [tex
])
2119 frame_start
= scene
.frame_start
2120 frame_end
= scene
.frame_end
2121 if settings
.bake_anim
:
2122 # From objects & bones only for a start.
2123 # Kind of hack, we need a temp scene_data for object's space handling to bake animations...
2124 tmp_scdata
= FBXExportData(
2126 settings
, scene
, objects
, None, 0.0, 0.0,
2127 data_empties
, data_lamps
, data_cameras
, data_meshes
, None,
2128 data_bones
, data_deformers_skin
, data_deformers_shape
,
2129 data_world
, data_materials
, data_textures
, data_videos
,
2131 animations
, frame_start
, frame_end
= fbx_animations(tmp_scdata
)
2133 # ##### Creation of templates...
2135 templates
= OrderedDict()
2136 templates
[b
"GlobalSettings"] = fbx_template_def_globalsettings(scene
, settings
, nbr_users
=1)
2139 templates
[b
"Null"] = fbx_template_def_null(scene
, settings
, nbr_users
=len(data_empties
))
2142 templates
[b
"Light"] = fbx_template_def_light(scene
, settings
, nbr_users
=len(data_lamps
))
2145 templates
[b
"Camera"] = fbx_template_def_camera(scene
, settings
, nbr_users
=len(data_cameras
))
2148 templates
[b
"Bone"] = fbx_template_def_bone(scene
, settings
, nbr_users
=len(data_bones
))
2151 nbr
= len(data_meshes
)
2152 if data_deformers_shape
:
2153 nbr
+= sum(len(shapes
[2]) for shapes
in data_deformers_shape
.values())
2154 templates
[b
"Geometry"] = fbx_template_def_geometry(scene
, settings
, nbr_users
=nbr
)
2157 templates
[b
"Model"] = fbx_template_def_model(scene
, settings
, nbr_users
=len(objects
))
2160 # Number of Pose|BindPose elements should be the same as number of meshes-parented-to-armatures
2161 templates
[b
"BindPose"] = fbx_template_def_pose(scene
, settings
, nbr_users
=len(arm_parents
))
2163 if data_deformers_skin
or data_deformers_shape
:
2165 if data_deformers_skin
:
2166 nbr
+= len(data_deformers_skin
)
2167 nbr
+= sum(len(clusters
) for def_me
in data_deformers_skin
.values() for a
, b
, clusters
in def_me
.values())
2168 if data_deformers_shape
:
2169 nbr
+= len(data_deformers_shape
)
2170 nbr
+= sum(len(shapes
[2]) for shapes
in data_deformers_shape
.values())
2172 templates
[b
"Deformers"] = fbx_template_def_deformer(scene
, settings
, nbr_users
=nbr
)
2174 # No world support in FBX...
2177 templates[b"World"] = fbx_template_def_world(scene, settings, nbr_users=len(data_world))
2181 templates
[b
"Material"] = fbx_template_def_material(scene
, settings
, nbr_users
=len(data_materials
))
2184 templates
[b
"TextureFile"] = fbx_template_def_texture_file(scene
, settings
, nbr_users
=len(data_textures
))
2187 templates
[b
"Video"] = fbx_template_def_video(scene
, settings
, nbr_users
=len(data_videos
))
2190 nbr_astacks
= len(animations
)
2193 for _astack_key
, astack
, _al
, _n
, _fs
, _fe
in animations
:
2194 for _alayer_key
, alayer
in astack
.values():
2195 for _acnode_key
, acnode
, _acnode_name
in alayer
.values():
2197 for _acurve_key
, _dval
, acurve
, acurve_valid
in acnode
.values():
2201 templates
[b
"AnimationStack"] = fbx_template_def_animstack(scene
, settings
, nbr_users
=nbr_astacks
)
2202 # Would be nice to have one layer per animated object, but this seems tricky and not that well supported.
2203 # So for now, only one layer per anim stack.
2204 templates
[b
"AnimationLayer"] = fbx_template_def_animlayer(scene
, settings
, nbr_users
=nbr_astacks
)
2205 templates
[b
"AnimationCurveNode"] = fbx_template_def_animcurvenode(scene
, settings
, nbr_users
=nbr_acnodes
)
2206 templates
[b
"AnimationCurve"] = fbx_template_def_animcurve(scene
, settings
, nbr_users
=nbr_acurves
)
2208 templates_users
= sum(tmpl
.nbr_users
for tmpl
in templates
.values())
2210 # ##### Creation of connections...
2214 # Objects (with classical parenting).
2215 for ob_obj
in objects
:
2216 # Bones are handled later.
2217 if not ob_obj
.is_bone
:
2218 par_obj
= ob_obj
.parent
2219 # Meshes parented to armature are handled separately, yet we want the 'no parent' connection (0).
2220 if par_obj
and ob_obj
.has_valid_parent(objects
) and (par_obj
, ob_obj
) not in arm_parents
:
2221 connections
.append((b
"OO", ob_obj
.fbx_uuid
, par_obj
.fbx_uuid
, None))
2223 connections
.append((b
"OO", ob_obj
.fbx_uuid
, 0, None))
2225 # Armature & Bone chains.
2226 for bo_obj
in data_bones
.keys():
2227 par_obj
= bo_obj
.parent
2228 if par_obj
not in objects
:
2230 connections
.append((b
"OO", bo_obj
.fbx_uuid
, par_obj
.fbx_uuid
, None))
2233 for ob_obj
in objects
:
2235 bo_data_key
= data_bones
[ob_obj
]
2236 connections
.append((b
"OO", get_fbx_uuid_from_key(bo_data_key
), ob_obj
.fbx_uuid
, None))
2238 if ob_obj
.type == 'LAMP':
2239 lamp_key
= data_lamps
[ob_obj
.bdata
.data
]
2240 connections
.append((b
"OO", get_fbx_uuid_from_key(lamp_key
), ob_obj
.fbx_uuid
, None))
2241 elif ob_obj
.type == 'CAMERA':
2242 cam_key
= data_cameras
[ob_obj
]
2243 connections
.append((b
"OO", get_fbx_uuid_from_key(cam_key
), ob_obj
.fbx_uuid
, None))
2244 elif ob_obj
.type == 'EMPTY':
2245 empty_key
= data_empties
[ob_obj
]
2246 connections
.append((b
"OO", get_fbx_uuid_from_key(empty_key
), ob_obj
.fbx_uuid
, None))
2247 elif ob_obj
.type in BLENDER_OBJECT_TYPES_MESHLIKE
:
2248 mesh_key
, _me
, _free
= data_meshes
[ob_obj
]
2249 connections
.append((b
"OO", get_fbx_uuid_from_key(mesh_key
), ob_obj
.fbx_uuid
, None))
2251 # 'Shape' deformers (shape keys, only for meshes currently)...
2252 for me_key
, shapes_key
, shapes
in data_deformers_shape
.values():
2254 connections
.append((b
"OO", get_fbx_uuid_from_key(shapes_key
), get_fbx_uuid_from_key(me_key
), None))
2255 for channel_key
, geom_key
, _shape_verts_co
, _shape_verts_idx
in shapes
.values():
2256 # shape channel -> shape
2257 connections
.append((b
"OO", get_fbx_uuid_from_key(channel_key
), get_fbx_uuid_from_key(shapes_key
), None))
2258 # geometry (keys) -> shape channel
2259 connections
.append((b
"OO", get_fbx_uuid_from_key(geom_key
), get_fbx_uuid_from_key(channel_key
), None))
2261 # 'Skin' deformers (armature-to-geometry, only for meshes currently)...
2262 for arm
, deformed_meshes
in data_deformers_skin
.items():
2263 for me
, (skin_key
, ob_obj
, clusters
) in deformed_meshes
.items():
2265 mesh_key
, _me
, _free
= data_meshes
[ob_obj
]
2267 connections
.append((b
"OO", get_fbx_uuid_from_key(skin_key
), get_fbx_uuid_from_key(mesh_key
), None))
2268 for bo_obj
, clstr_key
in clusters
.items():
2270 connections
.append((b
"OO", get_fbx_uuid_from_key(clstr_key
), get_fbx_uuid_from_key(skin_key
), None))
2272 connections
.append((b
"OO", bo_obj
.fbx_uuid
, get_fbx_uuid_from_key(clstr_key
), None))
2275 mesh_mat_indices
= OrderedDict()
2277 for mat
, (mat_key
, ob_objs
) in data_materials
.items():
2278 for ob_obj
in ob_objs
:
2279 connections
.append((b
"OO", get_fbx_uuid_from_key(mat_key
), ob_obj
.fbx_uuid
, None))
2280 # Get index of this mat for this object (or dupliobject).
2281 # Mat indices for mesh faces are determined by their order in 'mat to ob' connections.
2282 # Only mats for meshes currently...
2283 # Note in case of dupliobjects a same me/mat idx will be generated several times...
2284 # Should not be an issue in practice, and it's needed in case we export duplis but not the original!
2285 if ob_obj
.type not in BLENDER_OBJECT_TYPES_MESHLIKE
:
2287 _mesh_key
, me
, _free
= data_meshes
[ob_obj
]
2288 idx
= _objs_indices
[ob_obj
] = _objs_indices
.get(ob_obj
, -1) + 1
2289 mesh_mat_indices
.setdefault(me
, OrderedDict())[mat
] = idx
2293 for tex
, (tex_key
, mats
) in data_textures
.items():
2294 for mat
, fbx_mat_props
in mats
.items():
2295 mat_key
, _ob_objs
= data_materials
[mat
]
2296 for fbx_prop
in fbx_mat_props
:
2297 # texture -> material properties
2298 connections
.append((b
"OP", get_fbx_uuid_from_key(tex_key
), get_fbx_uuid_from_key(mat_key
), fbx_prop
))
2301 for vid
, (vid_key
, texs
) in data_videos
.items():
2303 tex_key
, _texs
= data_textures
[tex
]
2304 connections
.append((b
"OO", get_fbx_uuid_from_key(vid_key
), get_fbx_uuid_from_key(tex_key
), None))
2307 for astack_key
, astack
, alayer_key
, _name
, _fstart
, _fend
in animations
:
2308 # Animstack itself is linked nowhere!
2309 astack_id
= get_fbx_uuid_from_key(astack_key
)
2310 # For now, only one layer!
2311 alayer_id
= get_fbx_uuid_from_key(alayer_key
)
2312 connections
.append((b
"OO", alayer_id
, astack_id
, None))
2313 for elem_key
, (alayer_key
, acurvenodes
) in astack
.items():
2314 elem_id
= get_fbx_uuid_from_key(elem_key
)
2315 # Animlayer -> animstack.
2316 # alayer_id = get_fbx_uuid_from_key(alayer_key)
2317 # connections.append((b"OO", alayer_id, astack_id, None))
2318 for fbx_prop
, (acurvenode_key
, acurves
, acurvenode_name
) in acurvenodes
.items():
2319 # Animcurvenode -> animalayer.
2320 acurvenode_id
= get_fbx_uuid_from_key(acurvenode_key
)
2321 connections
.append((b
"OO", acurvenode_id
, alayer_id
, None))
2322 # Animcurvenode -> object property.
2323 connections
.append((b
"OP", acurvenode_id
, elem_id
, fbx_prop
.encode()))
2324 for fbx_item
, (acurve_key
, default_value
, acurve
, acurve_valid
) in acurves
.items():
2326 # Animcurve -> Animcurvenode.
2327 connections
.append((b
"OP", get_fbx_uuid_from_key(acurve_key
), acurvenode_id
, fbx_item
.encode()))
2329 # ##### And pack all this!
2331 return FBXExportData(
2332 templates
, templates_users
, connections
,
2333 settings
, scene
, objects
, animations
, frame_start
, frame_end
,
2334 data_empties
, data_lamps
, data_cameras
, data_meshes
, mesh_mat_indices
,
2335 data_bones
, data_deformers_skin
, data_deformers_shape
,
2336 data_world
, data_materials
, data_textures
, data_videos
,
2340 def fbx_scene_data_cleanup(scene_data
):
2342 Some final cleanup...
2344 # Delete temp meshes.
2345 for _key
, me
, free
in scene_data
.data_meshes
.values():
2347 bpy
.data
.meshes
.remove(me
)
2350 # ##### Top-level FBX elements generators. #####
2352 def fbx_header_elements(root
, scene_data
, time
=None):
2354 Write boiling code of FBX root.
2355 time is expected to be a datetime.datetime object, or None (using now() in this case).
2357 app_vendor
= "Blender Foundation"
2358 app_name
= "Blender (stable FBX IO)"
2359 app_ver
= bpy
.app
.version_string
2360 # ##### Start of FBXHeaderExtension element.
2361 header_ext
= elem_empty(root
, b
"FBXHeaderExtension")
2363 elem_data_single_int32(header_ext
, b
"FBXHeaderVersion", FBX_HEADER_VERSION
)
2365 elem_data_single_int32(header_ext
, b
"FBXVersion", FBX_VERSION
)
2368 elem_data_single_int32(header_ext
, b
"EncryptionType", 0)
2371 time
= datetime
.datetime
.now()
2372 elem
= elem_empty(header_ext
, b
"CreationTimeStamp")
2373 elem_data_single_int32(elem
, b
"Version", 1000)
2374 elem_data_single_int32(elem
, b
"Year", time
.year
)
2375 elem_data_single_int32(elem
, b
"Month", time
.month
)
2376 elem_data_single_int32(elem
, b
"Day", time
.day
)
2377 elem_data_single_int32(elem
, b
"Hour", time
.hour
)
2378 elem_data_single_int32(elem
, b
"Minute", time
.minute
)
2379 elem_data_single_int32(elem
, b
"Second", time
.second
)
2380 elem_data_single_int32(elem
, b
"Millisecond", time
.microsecond
// 1000)
2382 elem_data_single_string_unicode(header_ext
, b
"Creator", "%s - %s" % (app_name
, app_ver
))
2384 # 'SceneInfo' seems mandatory to get a valid FBX file...
2385 # TODO use real values!
2386 # XXX Should we use scene.name.encode() here?
2387 scene_info
= elem_data_single_string(header_ext
, b
"SceneInfo", fbx_name_class(b
"GlobalInfo", b
"SceneInfo"))
2388 scene_info
.add_string(b
"UserData")
2389 elem_data_single_string(scene_info
, b
"Type", b
"UserData")
2390 elem_data_single_int32(scene_info
, b
"Version", FBX_SCENEINFO_VERSION
)
2391 meta_data
= elem_empty(scene_info
, b
"MetaData")
2392 elem_data_single_int32(meta_data
, b
"Version", FBX_SCENEINFO_VERSION
)
2393 elem_data_single_string(meta_data
, b
"Title", b
"")
2394 elem_data_single_string(meta_data
, b
"Subject", b
"")
2395 elem_data_single_string(meta_data
, b
"Author", b
"")
2396 elem_data_single_string(meta_data
, b
"Keywords", b
"")
2397 elem_data_single_string(meta_data
, b
"Revision", b
"")
2398 elem_data_single_string(meta_data
, b
"Comment", b
"")
2400 props
= elem_properties(scene_info
)
2401 elem_props_set(props
, "p_string_url", b
"DocumentUrl", "/foobar.fbx")
2402 elem_props_set(props
, "p_string_url", b
"SrcDocumentUrl", "/foobar.fbx")
2403 original
= elem_props_compound(props
, b
"Original")
2404 original("p_string", b
"ApplicationVendor", app_vendor
)
2405 original("p_string", b
"ApplicationName", app_name
)
2406 original("p_string", b
"ApplicationVersion", app_ver
)
2407 original("p_datetime", b
"DateTime_GMT", "01/01/1970 00:00:00.000")
2408 original("p_string", b
"FileName", "/foobar.fbx")
2409 lastsaved
= elem_props_compound(props
, b
"LastSaved")
2410 lastsaved("p_string", b
"ApplicationVendor", app_vendor
)
2411 lastsaved("p_string", b
"ApplicationName", app_name
)
2412 lastsaved("p_string", b
"ApplicationVersion", app_ver
)
2413 lastsaved("p_datetime", b
"DateTime_GMT", "01/01/1970 00:00:00.000")
2415 # ##### End of FBXHeaderExtension element.
2417 # FileID is replaced by dummy value currently...
2418 elem_data_single_bytes(root
, b
"FileId", b
"FooBar")
2420 # CreationTime is replaced by dummy value currently, but anyway...
2421 elem_data_single_string_unicode(root
, b
"CreationTime",
2422 "{:04}-{:02}-{:02} {:02}:{:02}:{:02}:{:03}"
2423 "".format(time
.year
, time
.month
, time
.day
, time
.hour
, time
.minute
, time
.second
,
2424 time
.microsecond
* 1000))
2426 elem_data_single_string_unicode(root
, b
"Creator", "%s - %s" % (app_name
, app_ver
))
2428 # ##### Start of GlobalSettings element.
2429 global_settings
= elem_empty(root
, b
"GlobalSettings")
2430 scene
= scene_data
.scene
2432 elem_data_single_int32(global_settings
, b
"Version", 1000)
2434 props
= elem_properties(global_settings
)
2435 up_axis
, front_axis
, coord_axis
= RIGHT_HAND_AXES
[scene_data
.settings
.to_axes
]
2436 # Currently not sure about that, but looks like default unit of FBX is cm...
2437 scale_factor
= (1.0 if scene
.unit_settings
.system
== 'NONE' else scene
.unit_settings
.scale_length
) * 100
2438 elem_props_set(props
, "p_integer", b
"UpAxis", up_axis
[0])
2439 elem_props_set(props
, "p_integer", b
"UpAxisSign", up_axis
[1])
2440 elem_props_set(props
, "p_integer", b
"FrontAxis", front_axis
[0])
2441 elem_props_set(props
, "p_integer", b
"FrontAxisSign", front_axis
[1])
2442 elem_props_set(props
, "p_integer", b
"CoordAxis", coord_axis
[0])
2443 elem_props_set(props
, "p_integer", b
"CoordAxisSign", coord_axis
[1])
2444 elem_props_set(props
, "p_integer", b
"OriginalUpAxis", -1)
2445 elem_props_set(props
, "p_integer", b
"OriginalUpAxisSign", 1)
2446 elem_props_set(props
, "p_double", b
"UnitScaleFactor", scale_factor
)
2447 elem_props_set(props
, "p_double", b
"OriginalUnitScaleFactor", scale_factor
)
2448 elem_props_set(props
, "p_color_rgb", b
"AmbientColor", (0.0, 0.0, 0.0))
2449 elem_props_set(props
, "p_string", b
"DefaultCamera", "Producer Perspective")
2451 # Global timing data.
2453 _
, fbx_fps_mode
= FBX_FRAMERATES
[0] # Custom framerate.
2454 fbx_fps
= fps
= r
.fps
/ r
.fps_base
2455 for ref_fps
, fps_mode
in FBX_FRAMERATES
:
2456 if similar_values(fps
, ref_fps
):
2458 fbx_fps_mode
= fps_mode
2459 elem_props_set(props
, "p_enum", b
"TimeMode", fbx_fps_mode
)
2460 elem_props_set(props
, "p_timestamp", b
"TimeSpanStart", 0)
2461 elem_props_set(props
, "p_timestamp", b
"TimeSpanStop", FBX_KTIME
)
2462 elem_props_set(props
, "p_double", b
"CustomFrameRate", fbx_fps
)
2464 # ##### End of GlobalSettings element.
2467 def fbx_documents_elements(root
, scene_data
):
2469 Write 'Document' part of FBX root.
2470 Seems like FBX support multiple documents, but until I find examples of such, we'll stick to single doc!
2471 time is expected to be a datetime.datetime object, or None (using now() in this case).
2473 name
= scene_data
.scene
.name
2475 # ##### Start of Documents element.
2476 docs
= elem_empty(root
, b
"Documents")
2478 elem_data_single_int32(docs
, b
"Count", 1)
2480 doc_uid
= get_fbx_uuid_from_key("__FBX_Document__" + name
)
2481 doc
= elem_data_single_int64(docs
, b
"Document", doc_uid
)
2482 doc
.add_string_unicode(name
)
2483 doc
.add_string_unicode(name
)
2485 props
= elem_properties(doc
)
2486 elem_props_set(props
, "p_object", b
"SourceObject")
2487 elem_props_set(props
, "p_string", b
"ActiveAnimStackName", "")
2489 # XXX Some kind of ID? Offset?
2490 # Anyway, as long as we have only one doc, probably not an issue.
2491 elem_data_single_int64(doc
, b
"RootNode", 0)
2494 def fbx_references_elements(root
, scene_data
):
2496 Have no idea what references are in FBX currently... Just writing empty element.
2498 docs
= elem_empty(root
, b
"References")
2501 def fbx_definitions_elements(root
, scene_data
):
2503 Templates definitions. Only used by Objects data afaik (apart from dummy GlobalSettings one).
2505 definitions
= elem_empty(root
, b
"Definitions")
2507 elem_data_single_int32(definitions
, b
"Version", FBX_TEMPLATES_VERSION
)
2508 elem_data_single_int32(definitions
, b
"Count", scene_data
.templates_users
)
2510 fbx_templates_generate(definitions
, scene_data
.templates
)
2513 def fbx_objects_elements(root
, scene_data
):
2515 Data (objects, geometry, material, textures, armatures, etc.
2517 objects
= elem_empty(root
, b
"Objects")
2519 for empty
in scene_data
.data_empties
:
2520 fbx_data_empty_elements(objects
, empty
, scene_data
)
2522 for lamp
in scene_data
.data_lamps
:
2523 fbx_data_lamp_elements(objects
, lamp
, scene_data
)
2525 for cam
in scene_data
.data_cameras
:
2526 fbx_data_camera_elements(objects
, cam
, scene_data
)
2529 for me_obj
in scene_data
.data_meshes
:
2530 fbx_data_mesh_elements(objects
, me_obj
, scene_data
, done_meshes
)
2533 for ob_obj
in scene_data
.objects
:
2536 fbx_data_object_elements(objects
, ob_obj
, scene_data
)
2537 ob_obj
.dupli_list_create(scene_data
.scene
, 'RENDER')
2538 for dp_obj
in ob_obj
.dupli_list
:
2539 if dp_obj
not in scene_data
.objects
:
2541 fbx_data_object_elements(objects
, dp_obj
, scene_data
)
2542 ob_obj
.dupli_list_clear()
2544 for ob_obj
in scene_data
.objects
:
2545 if not (ob_obj
.is_object
and ob_obj
.type == 'ARMATURE'):
2547 fbx_data_armature_elements(objects
, ob_obj
, scene_data
)
2549 for mat
in scene_data
.data_materials
:
2550 fbx_data_material_elements(objects
, mat
, scene_data
)
2552 for tex
in scene_data
.data_textures
:
2553 fbx_data_texture_file_elements(objects
, tex
, scene_data
)
2555 for vid
in scene_data
.data_videos
:
2556 fbx_data_video_elements(objects
, vid
, scene_data
)
2558 fbx_data_animation_elements(objects
, scene_data
)
2561 def fbx_connections_elements(root
, scene_data
):
2563 Relations between Objects (which material uses which texture, and so on).
2565 connections
= elem_empty(root
, b
"Connections")
2567 for c
in scene_data
.connections
:
2568 elem_connection(connections
, *c
)
2571 def fbx_takes_elements(root
, scene_data
):
2575 # XXX Pretty sure takes are no more needed...
2576 takes
= elem_empty(root
, b
"Takes")
2577 elem_data_single_string(takes
, b
"Current", b
"")
2579 animations
= scene_data
.animations
2580 for astack_key
, animations
, alayer_key
, name
, f_start
, f_end
in animations
:
2581 scene
= scene_data
.scene
2582 fps
= scene
.render
.fps
/ scene
.render
.fps_base
2583 start_ktime
= int(convert_sec_to_ktime(f_start
/ fps
))
2584 end_ktime
= int(convert_sec_to_ktime(f_end
/ fps
))
2586 take
= elem_data_single_string(takes
, b
"Take", name
)
2587 elem_data_single_string(take
, b
"FileName", name
+ b
".tak")
2588 take_loc_time
= elem_data_single_int64(take
, b
"LocalTime", start_ktime
)
2589 take_loc_time
.add_int64(end_ktime
)
2590 take_ref_time
= elem_data_single_int64(take
, b
"ReferenceTime", start_ktime
)
2591 take_ref_time
.add_int64(end_ktime
)
2594 # ##### "Main" functions. #####
2596 # This func can be called with just the filepath
2597 def save_single(operator
, scene
, filepath
="",
2598 global_matrix
=Matrix(),
2601 context_objects
=None,
2603 use_mesh_modifiers
=True,
2604 mesh_smooth_type
='FACE',
2605 use_armature_deform_only
=False,
2607 bake_anim_use_nla_strips
=True,
2608 bake_anim_use_all_actions
=True,
2610 bake_anim_simplify_factor
=1.0,
2613 use_mesh_edges
=True,
2615 embed_textures
=False,
2616 use_custom_props
=False,
2617 bake_space_transform
=False,
2621 # Clear cached ObjectWrappers (just in case...).
2622 ObjectWrapper
.cache_clear()
2624 if object_types
is None:
2625 object_types
= {'EMPTY', 'CAMERA', 'LAMP', 'ARMATURE', 'MESH', 'OTHER'}
2627 if 'OTHER' in object_types
:
2628 object_types |
= BLENDER_OTHER_OBJECT_TYPES
2630 global_scale
= global_matrix
.median_scale
2631 global_matrix_inv
= global_matrix
.inverted()
2632 # For transforming mesh normals.
2633 global_matrix_inv_transposed
= global_matrix_inv
.transposed()
2635 # Only embed textures in COPY mode!
2636 if embed_textures
and path_mode
!= 'COPY':
2637 embed_textures
= False
2639 media_settings
= FBXExportSettingsMedia(
2641 os
.path
.dirname(bpy
.data
.filepath
), # base_src
2642 os
.path
.dirname(filepath
), # base_dst
2643 # Local dir where to put images (medias), using FBX conventions.
2644 os
.path
.splitext(os
.path
.basename(filepath
))[0] + ".fbm", # subdir
2649 settings
= FBXExportSettings(
2650 operator
.report
, (axis_up
, axis_forward
), global_matrix
, global_scale
,
2651 bake_space_transform
, global_matrix_inv
, global_matrix_inv_transposed
,
2652 context_objects
, object_types
, use_mesh_modifiers
,
2653 mesh_smooth_type
, use_mesh_edges
, use_tspace
, use_armature_deform_only
,
2654 bake_anim
, bake_anim_use_nla_strips
, bake_anim_use_all_actions
, bake_anim_step
, bake_anim_simplify_factor
,
2655 False, media_settings
, use_custom_props
,
2658 import bpy_extras
.io_utils
2660 print('\nFBX export starting... %r' % filepath
)
2661 start_time
= time
.process_time()
2663 # Generate some data about exported scene...
2664 scene_data
= fbx_data_from_scene(scene
, settings
)
2666 root
= elem_empty(None, b
"") # Root element has no id, as it is not saved per se!
2668 # Mostly FBXHeaderExtension and GlobalSettings.
2669 fbx_header_elements(root
, scene_data
)
2671 # Documents and References are pretty much void currently.
2672 fbx_documents_elements(root
, scene_data
)
2673 fbx_references_elements(root
, scene_data
)
2675 # Templates definitions.
2676 fbx_definitions_elements(root
, scene_data
)
2679 fbx_objects_elements(root
, scene_data
)
2681 # How data are inter-connected.
2682 fbx_connections_elements(root
, scene_data
)
2685 fbx_takes_elements(root
, scene_data
)
2688 fbx_scene_data_cleanup(scene_data
)
2690 # And we are down, we can write the whole thing!
2691 encode_bin
.write(filepath
, root
, FBX_VERSION
)
2693 # Clear cached ObjectWrappers!
2694 ObjectWrapper
.cache_clear()
2696 # copy all collected files, if we did not embed them.
2697 if not media_settings
.embed_textures
:
2698 bpy_extras
.io_utils
.path_reference_copy(media_settings
.copy_set
)
2700 print('export finished in %.4f sec.' % (time
.process_time() - start_time
))
2704 # defaults for applications, currently only unity but could add others.
2705 def defaults_unity3d():
2707 # These options seem to produce the same result as the old Ascii exporter in Unity3D:
2708 "version": 'BIN7400',
2710 "axis_forward": '-Z',
2711 "global_matrix": Matrix
.Rotation(-math
.pi
/ 2.0, 4, 'X'),
2712 # Should really be True, but it can cause problems if a model is already in a scene or prefab
2713 # with the old transforms.
2714 "bake_space_transform": False,
2716 "use_selection": False,
2718 "object_types": {'ARMATURE', 'EMPTY', 'MESH', 'OTHER'},
2719 "use_mesh_modifiers": True,
2720 "use_mesh_edges": False,
2721 "mesh_smooth_type": 'FACE',
2722 "use_tspace": False, # XXX Why? Unity is expected to support tspace import...
2724 "use_armature_deform_only": True,
2726 "use_custom_props": True,
2729 "bake_anim_simplify_factor": 1.0,
2730 "bake_anim_step": 1.0,
2731 "bake_anim_use_nla_strips": True,
2732 "bake_anim_use_all_actions": True,
2734 "path_mode": 'AUTO',
2735 "embed_textures": False,
2736 "batch_mode": 'OFF',
2740 def save(operator
, context
,
2742 use_selection
=False,
2744 use_batch_own_dir
=False,
2748 This is a wrapper around save_single, which handles multi-scenes (or groups) cases, when batch-exporting a whole
2755 if context
.active_object
and context
.active_object
.mode
!= 'OBJECT' and bpy
.ops
.object.mode_set
.poll():
2756 org_mode
= context
.active_object
.mode
2757 bpy
.ops
.object.mode_set(mode
='OBJECT')
2759 if batch_mode
== 'OFF':
2760 kwargs_mod
= kwargs
.copy()
2762 kwargs_mod
["context_objects"] = context
.selected_objects
2764 kwargs_mod
["context_objects"] = context
.scene
.objects
2766 ret
= save_single(operator
, context
.scene
, filepath
, **kwargs_mod
)
2770 prefix
= os
.path
.basename(fbxpath
)
2772 fbxpath
= os
.path
.dirname(fbxpath
)
2774 if batch_mode
== 'GROUP':
2775 data_seq
= bpy
.data
.groups
2777 data_seq
= bpy
.data
.scenes
2779 # call this function within a loop with BATCH_ENABLE == False
2780 # no scene switching done at the moment.
2781 # orig_sce = context.scene
2783 new_fbxpath
= fbxpath
# own dir option modifies, we need to keep an original
2784 for data
in data_seq
: # scene or group
2785 newname
= "_".join((prefix
, bpy
.path
.clean_name(data
.name
)))
2787 if use_batch_own_dir
:
2788 new_fbxpath
= os
.path
.join(fbxpath
, newname
)
2789 # path may already exist
2790 # TODO - might exist but be a file. unlikely but should probably account for it.
2792 if not os
.path
.exists(new_fbxpath
):
2793 os
.makedirs(new_fbxpath
)
2795 filepath
= os
.path
.join(new_fbxpath
, newname
+ '.fbx')
2797 print('\nBatch exporting %s as...\n\t%r' % (data
, filepath
))
2799 if batch_mode
== 'GROUP': # group
2800 # group, so objects update properly, add a dummy scene.
2801 scene
= bpy
.data
.scenes
.new(name
="FBX_Temp")
2802 scene
.layers
= [True] * 20
2803 # bpy.data.scenes.active = scene # XXX, cant switch
2804 for ob_base
in data
.objects
:
2805 scene
.objects
.link(ob_base
)
2808 # TODO - BUMMER! Armatures not in the group wont animate the mesh
2812 kwargs_batch
= kwargs
.copy()
2813 kwargs_batch
["context_objects"] = data
.objects
2815 save_single(operator
, scene
, filepath
, **kwargs_batch
)
2817 if batch_mode
== 'GROUP':
2818 # remove temp group scene
2819 bpy
.data
.scenes
.remove(scene
)
2821 # no active scene changing!
2822 # bpy.data.scenes.active = orig_sce
2824 ret
= {'FINISHED'} # so the script wont run after we have batch exported.
2826 if context
.active_object
and org_mode
and bpy
.ops
.object.mode_set
.poll():
2827 bpy
.ops
.object.mode_set(mode
=org_mode
)