Skinify: fix shape generation
[blender-addons.git] / io_scene_fbx / export_fbx_bin.py
blobc5cd93a7cc7517ba4d5f49e916cfe30bb4d9c33e
1 # SPDX-License-Identifier: GPL-2.0-or-later
3 # Script copyright (C) Campbell Barton, Bastien Montagne
6 import array
7 import datetime
8 import math
9 import os
10 import time
12 from itertools import zip_longest, chain
14 if "bpy" in locals():
15 import importlib
16 if "encode_bin" in locals():
17 importlib.reload(encode_bin)
18 if "data_types" in locals():
19 importlib.reload(data_types)
20 if "fbx_utils" in locals():
21 importlib.reload(fbx_utils)
23 import bpy
24 import bpy_extras
25 from bpy_extras import node_shader_utils
26 from bpy.app.translations import pgettext_tip as tip_
27 from mathutils import Vector, Matrix
29 from . import encode_bin, data_types, fbx_utils
30 from .fbx_utils import (
31 # Constants.
32 FBX_VERSION, FBX_HEADER_VERSION, FBX_SCENEINFO_VERSION, FBX_TEMPLATES_VERSION,
33 FBX_MODELS_VERSION,
34 FBX_GEOMETRY_VERSION, FBX_GEOMETRY_NORMAL_VERSION, FBX_GEOMETRY_BINORMAL_VERSION, FBX_GEOMETRY_TANGENT_VERSION,
35 FBX_GEOMETRY_SMOOTHING_VERSION, FBX_GEOMETRY_CREASE_VERSION, FBX_GEOMETRY_VCOLOR_VERSION, FBX_GEOMETRY_UV_VERSION,
36 FBX_GEOMETRY_MATERIAL_VERSION, FBX_GEOMETRY_LAYER_VERSION,
37 FBX_GEOMETRY_SHAPE_VERSION, FBX_DEFORMER_SHAPE_VERSION, FBX_DEFORMER_SHAPECHANNEL_VERSION,
38 FBX_POSE_BIND_VERSION, FBX_DEFORMER_SKIN_VERSION, FBX_DEFORMER_CLUSTER_VERSION,
39 FBX_MATERIAL_VERSION, FBX_TEXTURE_VERSION,
40 FBX_ANIM_KEY_VERSION,
41 FBX_ANIM_PROPSGROUP_NAME,
42 FBX_KTIME,
43 BLENDER_OTHER_OBJECT_TYPES, BLENDER_OBJECT_TYPES_MESHLIKE,
44 FBX_LIGHT_TYPES, FBX_LIGHT_DECAY_TYPES,
45 RIGHT_HAND_AXES, FBX_FRAMERATES,
46 # Miscellaneous utils.
47 PerfMon,
48 units_blender_to_fbx_factor, units_convertor, units_convertor_iter,
49 matrix4_to_array, similar_values, similar_values_iter,
50 # Mesh transform helpers.
51 vcos_transformed_gen, nors_transformed_gen,
52 # UUID from key.
53 get_fbx_uuid_from_key,
54 # Key generators.
55 get_blenderID_key, get_blenderID_name,
56 get_blender_mesh_shape_key, get_blender_mesh_shape_channel_key,
57 get_blender_empty_key, get_blender_bone_key,
58 get_blender_bindpose_key, get_blender_armature_skin_key, get_blender_bone_cluster_key,
59 get_blender_anim_id_base, get_blender_anim_stack_key, get_blender_anim_layer_key,
60 get_blender_anim_curve_node_key, get_blender_anim_curve_key,
61 get_blender_nodetexture_key,
62 # FBX element data.
63 elem_empty,
64 elem_data_single_bool, elem_data_single_int16, elem_data_single_int32, elem_data_single_int64,
65 elem_data_single_float32, elem_data_single_float64,
66 elem_data_single_bytes, elem_data_single_string, elem_data_single_string_unicode,
67 elem_data_single_bool_array, elem_data_single_int32_array, elem_data_single_int64_array,
68 elem_data_single_float32_array, elem_data_single_float64_array, elem_data_vec_float64,
69 # FBX element properties.
70 elem_properties, elem_props_set, elem_props_compound,
71 # FBX element properties handling templates.
72 elem_props_template_init, elem_props_template_set, elem_props_template_finalize,
73 # Templates.
74 FBXTemplate, fbx_templates_generate,
75 # Animation.
76 AnimationCurveNodeWrapper,
77 # Objects.
78 ObjectWrapper, fbx_name_class,
79 # Top level.
80 FBXExportSettingsMedia, FBXExportSettings, FBXExportData,
83 # Units converters!
84 convert_sec_to_ktime = units_convertor("second", "ktime")
85 convert_sec_to_ktime_iter = units_convertor_iter("second", "ktime")
87 convert_mm_to_inch = units_convertor("millimeter", "inch")
89 convert_rad_to_deg = units_convertor("radian", "degree")
90 convert_rad_to_deg_iter = units_convertor_iter("radian", "degree")
93 # ##### Templates #####
94 # TODO: check all those "default" values, they should match Blender's default as much as possible, I guess?
96 def fbx_template_def_globalsettings(scene, settings, override_defaults=None, nbr_users=0):
97 props = {}
98 if override_defaults is not None:
99 props.update(override_defaults)
100 return FBXTemplate(b"GlobalSettings", b"", props, nbr_users, [False])
103 def fbx_template_def_model(scene, settings, override_defaults=None, nbr_users=0):
104 gscale = settings.global_scale
105 props = {
106 # Name, Value, Type, Animatable
107 b"QuaternionInterpolate": (0, "p_enum", False), # 0 = no quat interpolation.
108 b"RotationOffset": ((0.0, 0.0, 0.0), "p_vector_3d", False),
109 b"RotationPivot": ((0.0, 0.0, 0.0), "p_vector_3d", False),
110 b"ScalingOffset": ((0.0, 0.0, 0.0), "p_vector_3d", False),
111 b"ScalingPivot": ((0.0, 0.0, 0.0), "p_vector_3d", False),
112 b"TranslationActive": (False, "p_bool", False),
113 b"TranslationMin": ((0.0, 0.0, 0.0), "p_vector_3d", False),
114 b"TranslationMax": ((0.0, 0.0, 0.0), "p_vector_3d", False),
115 b"TranslationMinX": (False, "p_bool", False),
116 b"TranslationMinY": (False, "p_bool", False),
117 b"TranslationMinZ": (False, "p_bool", False),
118 b"TranslationMaxX": (False, "p_bool", False),
119 b"TranslationMaxY": (False, "p_bool", False),
120 b"TranslationMaxZ": (False, "p_bool", False),
121 b"RotationOrder": (0, "p_enum", False), # we always use 'XYZ' order.
122 b"RotationSpaceForLimitOnly": (False, "p_bool", False),
123 b"RotationStiffnessX": (0.0, "p_double", False),
124 b"RotationStiffnessY": (0.0, "p_double", False),
125 b"RotationStiffnessZ": (0.0, "p_double", False),
126 b"AxisLen": (10.0, "p_double", False),
127 b"PreRotation": ((0.0, 0.0, 0.0), "p_vector_3d", False),
128 b"PostRotation": ((0.0, 0.0, 0.0), "p_vector_3d", False),
129 b"RotationActive": (False, "p_bool", False),
130 b"RotationMin": ((0.0, 0.0, 0.0), "p_vector_3d", False),
131 b"RotationMax": ((0.0, 0.0, 0.0), "p_vector_3d", False),
132 b"RotationMinX": (False, "p_bool", False),
133 b"RotationMinY": (False, "p_bool", False),
134 b"RotationMinZ": (False, "p_bool", False),
135 b"RotationMaxX": (False, "p_bool", False),
136 b"RotationMaxY": (False, "p_bool", False),
137 b"RotationMaxZ": (False, "p_bool", False),
138 b"InheritType": (0, "p_enum", False), # RrSs
139 b"ScalingActive": (False, "p_bool", False),
140 b"ScalingMin": ((0.0, 0.0, 0.0), "p_vector_3d", False),
141 b"ScalingMax": ((1.0, 1.0, 1.0), "p_vector_3d", False),
142 b"ScalingMinX": (False, "p_bool", False),
143 b"ScalingMinY": (False, "p_bool", False),
144 b"ScalingMinZ": (False, "p_bool", False),
145 b"ScalingMaxX": (False, "p_bool", False),
146 b"ScalingMaxY": (False, "p_bool", False),
147 b"ScalingMaxZ": (False, "p_bool", False),
148 b"GeometricTranslation": ((0.0, 0.0, 0.0), "p_vector_3d", False),
149 b"GeometricRotation": ((0.0, 0.0, 0.0), "p_vector_3d", False),
150 b"GeometricScaling": ((1.0, 1.0, 1.0), "p_vector_3d", False),
151 b"MinDampRangeX": (0.0, "p_double", False),
152 b"MinDampRangeY": (0.0, "p_double", False),
153 b"MinDampRangeZ": (0.0, "p_double", False),
154 b"MaxDampRangeX": (0.0, "p_double", False),
155 b"MaxDampRangeY": (0.0, "p_double", False),
156 b"MaxDampRangeZ": (0.0, "p_double", False),
157 b"MinDampStrengthX": (0.0, "p_double", False),
158 b"MinDampStrengthY": (0.0, "p_double", False),
159 b"MinDampStrengthZ": (0.0, "p_double", False),
160 b"MaxDampStrengthX": (0.0, "p_double", False),
161 b"MaxDampStrengthY": (0.0, "p_double", False),
162 b"MaxDampStrengthZ": (0.0, "p_double", False),
163 b"PreferedAngleX": (0.0, "p_double", False),
164 b"PreferedAngleY": (0.0, "p_double", False),
165 b"PreferedAngleZ": (0.0, "p_double", False),
166 b"LookAtProperty": (None, "p_object", False),
167 b"UpVectorProperty": (None, "p_object", False),
168 b"Show": (True, "p_bool", False),
169 b"NegativePercentShapeSupport": (True, "p_bool", False),
170 b"DefaultAttributeIndex": (-1, "p_integer", False),
171 b"Freeze": (False, "p_bool", False),
172 b"LODBox": (False, "p_bool", False),
173 b"Lcl Translation": ((0.0, 0.0, 0.0), "p_lcl_translation", True),
174 b"Lcl Rotation": ((0.0, 0.0, 0.0), "p_lcl_rotation", True),
175 b"Lcl Scaling": ((1.0, 1.0, 1.0), "p_lcl_scaling", True),
176 b"Visibility": (1.0, "p_visibility", True),
177 b"Visibility Inheritance": (1, "p_visibility_inheritance", False),
179 if override_defaults is not None:
180 props.update(override_defaults)
181 return FBXTemplate(b"Model", b"FbxNode", props, nbr_users, [False])
184 def fbx_template_def_null(scene, settings, override_defaults=None, nbr_users=0):
185 props = {
186 b"Color": ((0.8, 0.8, 0.8), "p_color_rgb", False),
187 b"Size": (100.0, "p_double", False),
188 b"Look": (1, "p_enum", False), # Cross (0 is None, i.e. invisible?).
190 if override_defaults is not None:
191 props.update(override_defaults)
192 return FBXTemplate(b"NodeAttribute", b"FbxNull", props, nbr_users, [False])
195 def fbx_template_def_light(scene, settings, override_defaults=None, nbr_users=0):
196 gscale = settings.global_scale
197 props = {
198 b"LightType": (0, "p_enum", False), # Point light.
199 b"CastLight": (True, "p_bool", False),
200 b"Color": ((1.0, 1.0, 1.0), "p_color", True),
201 b"Intensity": (100.0, "p_number", True), # Times 100 compared to Blender values...
202 b"DecayType": (2, "p_enum", False), # Quadratic.
203 b"DecayStart": (30.0 * gscale, "p_double", False),
204 b"CastShadows": (True, "p_bool", False),
205 b"ShadowColor": ((0.0, 0.0, 0.0), "p_color", True),
206 b"AreaLightShape": (0, "p_enum", False), # Rectangle.
208 if override_defaults is not None:
209 props.update(override_defaults)
210 return FBXTemplate(b"NodeAttribute", b"FbxLight", props, nbr_users, [False])
213 def fbx_template_def_camera(scene, settings, override_defaults=None, nbr_users=0):
214 r = scene.render
215 props = {
216 b"Color": ((0.8, 0.8, 0.8), "p_color_rgb", False),
217 b"Position": ((0.0, 0.0, -50.0), "p_vector", True),
218 b"UpVector": ((0.0, 1.0, 0.0), "p_vector", True),
219 b"InterestPosition": ((0.0, 0.0, 0.0), "p_vector", True),
220 b"Roll": (0.0, "p_roll", True),
221 b"OpticalCenterX": (0.0, "p_opticalcenterx", True),
222 b"OpticalCenterY": (0.0, "p_opticalcentery", True),
223 b"BackgroundColor": ((0.63, 0.63, 0.63), "p_color", True),
224 b"TurnTable": (0.0, "p_number", True),
225 b"DisplayTurnTableIcon": (False, "p_bool", False),
226 b"UseMotionBlur": (False, "p_bool", False),
227 b"UseRealTimeMotionBlur": (True, "p_bool", False),
228 b"Motion Blur Intensity": (1.0, "p_number", True),
229 b"AspectRatioMode": (0, "p_enum", False), # WindowSize.
230 b"AspectWidth": (320.0, "p_double", False),
231 b"AspectHeight": (200.0, "p_double", False),
232 b"PixelAspectRatio": (1.0, "p_double", False),
233 b"FilmOffsetX": (0.0, "p_number", True),
234 b"FilmOffsetY": (0.0, "p_number", True),
235 b"FilmWidth": (0.816, "p_double", False),
236 b"FilmHeight": (0.612, "p_double", False),
237 b"FilmAspectRatio": (1.3333333333333333, "p_double", False),
238 b"FilmSqueezeRatio": (1.0, "p_double", False),
239 b"FilmFormatIndex": (0, "p_enum", False), # Assuming this is ApertureFormat, 0 = custom.
240 b"PreScale": (1.0, "p_number", True),
241 b"FilmTranslateX": (0.0, "p_number", True),
242 b"FilmTranslateY": (0.0, "p_number", True),
243 b"FilmRollPivotX": (0.0, "p_number", True),
244 b"FilmRollPivotY": (0.0, "p_number", True),
245 b"FilmRollValue": (0.0, "p_number", True),
246 b"FilmRollOrder": (0, "p_enum", False), # 0 = rotate first (default).
247 b"ApertureMode": (2, "p_enum", False), # 2 = Vertical.
248 b"GateFit": (0, "p_enum", False), # 0 = no resolution gate fit.
249 b"FieldOfView": (25.114999771118164, "p_fov", True),
250 b"FieldOfViewX": (40.0, "p_fov_x", True),
251 b"FieldOfViewY": (40.0, "p_fov_y", True),
252 b"FocalLength": (34.89327621672628, "p_number", True),
253 b"CameraFormat": (0, "p_enum", False), # Custom camera format.
254 b"UseFrameColor": (False, "p_bool", False),
255 b"FrameColor": ((0.3, 0.3, 0.3), "p_color_rgb", False),
256 b"ShowName": (True, "p_bool", False),
257 b"ShowInfoOnMoving": (True, "p_bool", False),
258 b"ShowGrid": (True, "p_bool", False),
259 b"ShowOpticalCenter": (False, "p_bool", False),
260 b"ShowAzimut": (True, "p_bool", False),
261 b"ShowTimeCode": (False, "p_bool", False),
262 b"ShowAudio": (False, "p_bool", False),
263 b"AudioColor": ((0.0, 1.0, 0.0), "p_vector_3d", False), # Yep, vector3d, not corlorgb… :cry:
264 b"NearPlane": (10.0, "p_double", False),
265 b"FarPlane": (4000.0, "p_double", False),
266 b"AutoComputeClipPanes": (False, "p_bool", False),
267 b"ViewCameraToLookAt": (True, "p_bool", False),
268 b"ViewFrustumNearFarPlane": (False, "p_bool", False),
269 b"ViewFrustumBackPlaneMode": (2, "p_enum", False), # 2 = show back plane if texture added.
270 b"BackPlaneDistance": (4000.0, "p_number", True),
271 b"BackPlaneDistanceMode": (1, "p_enum", False), # 1 = relative to camera.
272 b"ViewFrustumFrontPlaneMode": (2, "p_enum", False), # 2 = show front plane if texture added.
273 b"FrontPlaneDistance": (10.0, "p_number", True),
274 b"FrontPlaneDistanceMode": (1, "p_enum", False), # 1 = relative to camera.
275 b"LockMode": (False, "p_bool", False),
276 b"LockInterestNavigation": (False, "p_bool", False),
277 # BackPlate... properties **arggggg!**
278 b"FitImage": (False, "p_bool", False),
279 b"Crop": (False, "p_bool", False),
280 b"Center": (True, "p_bool", False),
281 b"KeepRatio": (True, "p_bool", False),
282 # End of BackPlate...
283 b"BackgroundAlphaTreshold": (0.5, "p_double", False),
284 b"ShowBackplate": (True, "p_bool", False),
285 b"BackPlaneOffsetX": (0.0, "p_number", True),
286 b"BackPlaneOffsetY": (0.0, "p_number", True),
287 b"BackPlaneRotation": (0.0, "p_number", True),
288 b"BackPlaneScaleX": (1.0, "p_number", True),
289 b"BackPlaneScaleY": (1.0, "p_number", True),
290 b"Background Texture": (None, "p_object", False),
291 b"FrontPlateFitImage": (True, "p_bool", False),
292 b"FrontPlateCrop": (False, "p_bool", False),
293 b"FrontPlateCenter": (True, "p_bool", False),
294 b"FrontPlateKeepRatio": (True, "p_bool", False),
295 b"Foreground Opacity": (1.0, "p_double", False),
296 b"ShowFrontplate": (True, "p_bool", False),
297 b"FrontPlaneOffsetX": (0.0, "p_number", True),
298 b"FrontPlaneOffsetY": (0.0, "p_number", True),
299 b"FrontPlaneRotation": (0.0, "p_number", True),
300 b"FrontPlaneScaleX": (1.0, "p_number", True),
301 b"FrontPlaneScaleY": (1.0, "p_number", True),
302 b"Foreground Texture": (None, "p_object", False),
303 b"DisplaySafeArea": (False, "p_bool", False),
304 b"DisplaySafeAreaOnRender": (False, "p_bool", False),
305 b"SafeAreaDisplayStyle": (1, "p_enum", False), # 1 = rounded corners.
306 b"SafeAreaAspectRatio": (1.3333333333333333, "p_double", False),
307 b"Use2DMagnifierZoom": (False, "p_bool", False),
308 b"2D Magnifier Zoom": (100.0, "p_number", True),
309 b"2D Magnifier X": (50.0, "p_number", True),
310 b"2D Magnifier Y": (50.0, "p_number", True),
311 b"CameraProjectionType": (0, "p_enum", False), # 0 = perspective, 1 = orthogonal.
312 b"OrthoZoom": (1.0, "p_double", False),
313 b"UseRealTimeDOFAndAA": (False, "p_bool", False),
314 b"UseDepthOfField": (False, "p_bool", False),
315 b"FocusSource": (0, "p_enum", False), # 0 = camera interest, 1 = distance from camera interest.
316 b"FocusAngle": (3.5, "p_double", False), # ???
317 b"FocusDistance": (200.0, "p_double", False),
318 b"UseAntialiasing": (False, "p_bool", False),
319 b"AntialiasingIntensity": (0.77777, "p_double", False),
320 b"AntialiasingMethod": (0, "p_enum", False), # 0 = oversampling, 1 = hardware.
321 b"UseAccumulationBuffer": (False, "p_bool", False),
322 b"FrameSamplingCount": (7, "p_integer", False),
323 b"FrameSamplingType": (1, "p_enum", False), # 0 = uniform, 1 = stochastic.
325 if override_defaults is not None:
326 props.update(override_defaults)
327 return FBXTemplate(b"NodeAttribute", b"FbxCamera", props, nbr_users, [False])
330 def fbx_template_def_bone(scene, settings, override_defaults=None, nbr_users=0):
331 props = {}
332 if override_defaults is not None:
333 props.update(override_defaults)
334 return FBXTemplate(b"NodeAttribute", b"LimbNode", props, nbr_users, [False])
337 def fbx_template_def_geometry(scene, settings, override_defaults=None, nbr_users=0):
338 props = {
339 b"Color": ((0.8, 0.8, 0.8), "p_color_rgb", False),
340 b"BBoxMin": ((0.0, 0.0, 0.0), "p_vector_3d", False),
341 b"BBoxMax": ((0.0, 0.0, 0.0), "p_vector_3d", False),
342 b"Primary Visibility": (True, "p_bool", False),
343 b"Casts Shadows": (True, "p_bool", False),
344 b"Receive Shadows": (True, "p_bool", False),
346 if override_defaults is not None:
347 props.update(override_defaults)
348 return FBXTemplate(b"Geometry", b"FbxMesh", props, nbr_users, [False])
351 def fbx_template_def_material(scene, settings, override_defaults=None, nbr_users=0):
352 # WIP...
353 props = {
354 b"ShadingModel": ("Phong", "p_string", False),
355 b"MultiLayer": (False, "p_bool", False),
356 # Lambert-specific.
357 b"EmissiveColor": ((0.0, 0.0, 0.0), "p_color", True),
358 b"EmissiveFactor": (1.0, "p_number", True),
359 b"AmbientColor": ((0.2, 0.2, 0.2), "p_color", True),
360 b"AmbientFactor": (1.0, "p_number", True),
361 b"DiffuseColor": ((0.8, 0.8, 0.8), "p_color", True),
362 b"DiffuseFactor": (1.0, "p_number", True),
363 b"TransparentColor": ((0.0, 0.0, 0.0), "p_color", True),
364 b"TransparencyFactor": (0.0, "p_number", True),
365 b"Opacity": (1.0, "p_number", True),
366 b"NormalMap": ((0.0, 0.0, 0.0), "p_vector_3d", False),
367 b"Bump": ((0.0, 0.0, 0.0), "p_vector_3d", False),
368 b"BumpFactor": (1.0, "p_double", False),
369 b"DisplacementColor": ((0.0, 0.0, 0.0), "p_color_rgb", False),
370 b"DisplacementFactor": (1.0, "p_double", False),
371 b"VectorDisplacementColor": ((0.0, 0.0, 0.0), "p_color_rgb", False),
372 b"VectorDisplacementFactor": (1.0, "p_double", False),
373 # Phong-specific.
374 b"SpecularColor": ((0.2, 0.2, 0.2), "p_color", True),
375 b"SpecularFactor": (1.0, "p_number", True),
376 # Not sure about the name, importer uses this (but ShininessExponent for tex prop name!)
377 # And in fbx exported by sdk, you have one in template, the other in actual material!!! :/
378 # For now, using both.
379 b"Shininess": (20.0, "p_number", True),
380 b"ShininessExponent": (20.0, "p_number", True),
381 b"ReflectionColor": ((0.0, 0.0, 0.0), "p_color", True),
382 b"ReflectionFactor": (1.0, "p_number", True),
384 if override_defaults is not None:
385 props.update(override_defaults)
386 return FBXTemplate(b"Material", b"FbxSurfacePhong", props, nbr_users, [False])
389 def fbx_template_def_texture_file(scene, settings, override_defaults=None, nbr_users=0):
390 # WIP...
391 # XXX Not sure about all names!
392 props = {
393 b"TextureTypeUse": (0, "p_enum", False), # Standard.
394 b"AlphaSource": (2, "p_enum", False), # Black (i.e. texture's alpha), XXX name guessed!.
395 b"Texture alpha": (1.0, "p_double", False),
396 b"PremultiplyAlpha": (True, "p_bool", False),
397 b"CurrentTextureBlendMode": (1, "p_enum", False), # Additive...
398 b"CurrentMappingType": (0, "p_enum", False), # UV.
399 b"UVSet": ("default", "p_string", False), # UVMap name.
400 b"WrapModeU": (0, "p_enum", False), # Repeat.
401 b"WrapModeV": (0, "p_enum", False), # Repeat.
402 b"UVSwap": (False, "p_bool", False),
403 b"Translation": ((0.0, 0.0, 0.0), "p_vector_3d", False),
404 b"Rotation": ((0.0, 0.0, 0.0), "p_vector_3d", False),
405 b"Scaling": ((1.0, 1.0, 1.0), "p_vector_3d", False),
406 b"TextureRotationPivot": ((0.0, 0.0, 0.0), "p_vector_3d", False),
407 b"TextureScalingPivot": ((0.0, 0.0, 0.0), "p_vector_3d", False),
408 # Not sure about those two...
409 b"UseMaterial": (False, "p_bool", False),
410 b"UseMipMap": (False, "p_bool", False),
412 if override_defaults is not None:
413 props.update(override_defaults)
414 return FBXTemplate(b"Texture", b"FbxFileTexture", props, nbr_users, [False])
417 def fbx_template_def_video(scene, settings, override_defaults=None, nbr_users=0):
418 # WIP...
419 props = {
420 # All pictures.
421 b"Width": (0, "p_integer", False),
422 b"Height": (0, "p_integer", False),
423 b"Path": ("", "p_string_url", False),
424 b"AccessMode": (0, "p_enum", False), # Disk (0=Disk, 1=Mem, 2=DiskAsync).
425 # All videos.
426 b"StartFrame": (0, "p_integer", False),
427 b"StopFrame": (0, "p_integer", False),
428 b"Offset": (0, "p_timestamp", False),
429 b"PlaySpeed": (0.0, "p_double", False),
430 b"FreeRunning": (False, "p_bool", False),
431 b"Loop": (False, "p_bool", False),
432 b"InterlaceMode": (0, "p_enum", False), # None, i.e. progressive.
433 # Image sequences.
434 b"ImageSequence": (False, "p_bool", False),
435 b"ImageSequenceOffset": (0, "p_integer", False),
436 b"FrameRate": (0.0, "p_double", False),
437 b"LastFrame": (0, "p_integer", False),
439 if override_defaults is not None:
440 props.update(override_defaults)
441 return FBXTemplate(b"Video", b"FbxVideo", props, nbr_users, [False])
444 def fbx_template_def_pose(scene, settings, override_defaults=None, nbr_users=0):
445 props = {}
446 if override_defaults is not None:
447 props.update(override_defaults)
448 return FBXTemplate(b"Pose", b"", props, nbr_users, [False])
451 def fbx_template_def_deformer(scene, settings, override_defaults=None, nbr_users=0):
452 props = {}
453 if override_defaults is not None:
454 props.update(override_defaults)
455 return FBXTemplate(b"Deformer", b"", props, nbr_users, [False])
458 def fbx_template_def_animstack(scene, settings, override_defaults=None, nbr_users=0):
459 props = {
460 b"Description": ("", "p_string", False),
461 b"LocalStart": (0, "p_timestamp", False),
462 b"LocalStop": (0, "p_timestamp", False),
463 b"ReferenceStart": (0, "p_timestamp", False),
464 b"ReferenceStop": (0, "p_timestamp", False),
466 if override_defaults is not None:
467 props.update(override_defaults)
468 return FBXTemplate(b"AnimationStack", b"FbxAnimStack", props, nbr_users, [False])
471 def fbx_template_def_animlayer(scene, settings, override_defaults=None, nbr_users=0):
472 props = {
473 b"Weight": (100.0, "p_number", True),
474 b"Mute": (False, "p_bool", False),
475 b"Solo": (False, "p_bool", False),
476 b"Lock": (False, "p_bool", False),
477 b"Color": ((0.8, 0.8, 0.8), "p_color_rgb", False),
478 b"BlendMode": (0, "p_enum", False),
479 b"RotationAccumulationMode": (0, "p_enum", False),
480 b"ScaleAccumulationMode": (0, "p_enum", False),
481 b"BlendModeBypass": (0, "p_ulonglong", False),
483 if override_defaults is not None:
484 props.update(override_defaults)
485 return FBXTemplate(b"AnimationLayer", b"FbxAnimLayer", props, nbr_users, [False])
488 def fbx_template_def_animcurvenode(scene, settings, override_defaults=None, nbr_users=0):
489 props = {
490 FBX_ANIM_PROPSGROUP_NAME.encode(): (None, "p_compound", False),
492 if override_defaults is not None:
493 props.update(override_defaults)
494 return FBXTemplate(b"AnimationCurveNode", b"FbxAnimCurveNode", props, nbr_users, [False])
497 def fbx_template_def_animcurve(scene, settings, override_defaults=None, nbr_users=0):
498 props = {}
499 if override_defaults is not None:
500 props.update(override_defaults)
501 return FBXTemplate(b"AnimationCurve", b"", props, nbr_users, [False])
504 # ##### Generators for connection elements. #####
506 def elem_connection(elem, c_type, uid_src, uid_dst, prop_dst=None):
507 e = elem_data_single_string(elem, b"C", c_type)
508 e.add_int64(uid_src)
509 e.add_int64(uid_dst)
510 if prop_dst is not None:
511 e.add_string(prop_dst)
514 # ##### FBX objects generators. #####
516 def fbx_data_element_custom_properties(props, bid):
518 Store custom properties of blender ID bid (any mapping-like object, in fact) into FBX properties props.
520 items = bid.items()
522 if not items:
523 return
525 rna_properties = {prop.identifier for prop in bid.bl_rna.properties if prop.is_runtime}
527 for k, v in items:
528 if k in rna_properties:
529 continue
531 list_val = getattr(v, "to_list", lambda: None)()
533 if isinstance(v, str):
534 elem_props_set(props, "p_string", k.encode(), v, custom=True)
535 elif isinstance(v, int):
536 elem_props_set(props, "p_integer", k.encode(), v, custom=True)
537 elif isinstance(v, float):
538 elem_props_set(props, "p_double", k.encode(), v, custom=True)
539 elif list_val:
540 if len(list_val) == 3:
541 elem_props_set(props, "p_vector", k.encode(), list_val, custom=True)
542 else:
543 elem_props_set(props, "p_string", k.encode(), str(list_val), custom=True)
544 else:
545 elem_props_set(props, "p_string", k.encode(), str(v), custom=True)
548 def fbx_data_empty_elements(root, empty, scene_data):
550 Write the Empty data block (you can control its FBX datatype with the 'fbx_type' string custom property).
552 empty_key = scene_data.data_empties[empty]
554 null = elem_data_single_int64(root, b"NodeAttribute", get_fbx_uuid_from_key(empty_key))
555 null.add_string(fbx_name_class(empty.name.encode(), b"NodeAttribute"))
556 val = empty.bdata.get('fbx_type', None)
557 null.add_string(val.encode() if val and isinstance(val, str) else b"Null")
559 elem_data_single_string(null, b"TypeFlags", b"Null")
561 tmpl = elem_props_template_init(scene_data.templates, b"Null")
562 props = elem_properties(null)
563 elem_props_template_finalize(tmpl, props)
565 # No custom properties, already saved with object (Model).
568 def fbx_data_light_elements(root, lamp, scene_data):
570 Write the Lamp data block.
572 gscale = scene_data.settings.global_scale
574 light_key = scene_data.data_lights[lamp]
575 do_light = True
576 decay_type = FBX_LIGHT_DECAY_TYPES['CONSTANT']
577 do_shadow = False
578 shadow_color = Vector((0.0, 0.0, 0.0))
579 if lamp.type not in {'HEMI'}:
580 if lamp.type not in {'SUN', 'AREA'}:
581 decay_type = FBX_LIGHT_DECAY_TYPES[lamp.falloff_type]
582 do_light = True
583 do_shadow = lamp.use_shadow
584 shadow_color = lamp.shadow_color
586 light = elem_data_single_int64(root, b"NodeAttribute", get_fbx_uuid_from_key(light_key))
587 light.add_string(fbx_name_class(lamp.name.encode(), b"NodeAttribute"))
588 light.add_string(b"Light")
590 elem_data_single_int32(light, b"GeometryVersion", FBX_GEOMETRY_VERSION) # Sic...
592 tmpl = elem_props_template_init(scene_data.templates, b"Light")
593 props = elem_properties(light)
594 elem_props_template_set(tmpl, props, "p_enum", b"LightType", FBX_LIGHT_TYPES[lamp.type])
595 elem_props_template_set(tmpl, props, "p_bool", b"CastLight", do_light)
596 elem_props_template_set(tmpl, props, "p_color", b"Color", lamp.color)
597 elem_props_template_set(tmpl, props, "p_number", b"Intensity", lamp.energy * 100.0)
598 elem_props_template_set(tmpl, props, "p_enum", b"DecayType", decay_type)
599 elem_props_template_set(tmpl, props, "p_double", b"DecayStart", lamp.distance * gscale)
600 elem_props_template_set(tmpl, props, "p_bool", b"CastShadows", do_shadow)
601 elem_props_template_set(tmpl, props, "p_color", b"ShadowColor", shadow_color)
602 if lamp.type in {'SPOT'}:
603 elem_props_template_set(tmpl, props, "p_double", b"OuterAngle", math.degrees(lamp.spot_size))
604 elem_props_template_set(tmpl, props, "p_double", b"InnerAngle",
605 math.degrees(lamp.spot_size * (1.0 - lamp.spot_blend)))
606 elem_props_template_finalize(tmpl, props)
608 # Custom properties.
609 if scene_data.settings.use_custom_props:
610 fbx_data_element_custom_properties(props, lamp)
613 def fbx_data_camera_elements(root, cam_obj, scene_data):
615 Write the Camera data blocks.
617 gscale = scene_data.settings.global_scale
619 cam = cam_obj.bdata
620 cam_data = cam.data
621 cam_key = scene_data.data_cameras[cam_obj]
623 # Real data now, good old camera!
624 # Object transform info.
625 loc, rot, scale, matrix, matrix_rot = cam_obj.fbx_object_tx(scene_data)
626 up = matrix_rot @ Vector((0.0, 1.0, 0.0))
627 to = matrix_rot @ Vector((0.0, 0.0, -1.0))
628 # Render settings.
629 # TODO We could export much more...
630 render = scene_data.scene.render
631 width = render.resolution_x
632 height = render.resolution_y
633 aspect = width / height
634 # Film width & height from mm to inches
635 filmwidth = convert_mm_to_inch(cam_data.sensor_width)
636 filmheight = convert_mm_to_inch(cam_data.sensor_height)
637 filmaspect = filmwidth / filmheight
638 # Film offset
639 offsetx = filmwidth * cam_data.shift_x
640 offsety = filmaspect * filmheight * cam_data.shift_y
642 cam = elem_data_single_int64(root, b"NodeAttribute", get_fbx_uuid_from_key(cam_key))
643 cam.add_string(fbx_name_class(cam_data.name.encode(), b"NodeAttribute"))
644 cam.add_string(b"Camera")
646 tmpl = elem_props_template_init(scene_data.templates, b"Camera")
647 props = elem_properties(cam)
649 elem_props_template_set(tmpl, props, "p_vector", b"Position", loc)
650 elem_props_template_set(tmpl, props, "p_vector", b"UpVector", up)
651 elem_props_template_set(tmpl, props, "p_vector", b"InterestPosition", loc + to) # Point, not vector!
652 # Should we use world value?
653 elem_props_template_set(tmpl, props, "p_color", b"BackgroundColor", (0.0, 0.0, 0.0))
654 elem_props_template_set(tmpl, props, "p_bool", b"DisplayTurnTableIcon", True)
656 elem_props_template_set(tmpl, props, "p_enum", b"AspectRatioMode", 2) # FixedResolution
657 elem_props_template_set(tmpl, props, "p_double", b"AspectWidth", float(render.resolution_x))
658 elem_props_template_set(tmpl, props, "p_double", b"AspectHeight", float(render.resolution_y))
659 elem_props_template_set(tmpl, props, "p_double", b"PixelAspectRatio",
660 float(render.pixel_aspect_x / render.pixel_aspect_y))
662 elem_props_template_set(tmpl, props, "p_double", b"FilmWidth", filmwidth)
663 elem_props_template_set(tmpl, props, "p_double", b"FilmHeight", filmheight)
664 elem_props_template_set(tmpl, props, "p_double", b"FilmAspectRatio", filmaspect)
665 elem_props_template_set(tmpl, props, "p_double", b"FilmOffsetX", offsetx)
666 elem_props_template_set(tmpl, props, "p_double", b"FilmOffsetY", offsety)
668 elem_props_template_set(tmpl, props, "p_enum", b"ApertureMode", 3) # FocalLength.
669 elem_props_template_set(tmpl, props, "p_enum", b"GateFit", 2) # FitHorizontal.
670 elem_props_template_set(tmpl, props, "p_fov", b"FieldOfView", math.degrees(cam_data.angle_x))
671 elem_props_template_set(tmpl, props, "p_fov_x", b"FieldOfViewX", math.degrees(cam_data.angle_x))
672 elem_props_template_set(tmpl, props, "p_fov_y", b"FieldOfViewY", math.degrees(cam_data.angle_y))
673 # No need to convert to inches here...
674 elem_props_template_set(tmpl, props, "p_double", b"FocalLength", cam_data.lens)
675 elem_props_template_set(tmpl, props, "p_double", b"SafeAreaAspectRatio", aspect)
676 # Depth of field and Focus distance.
677 elem_props_template_set(tmpl, props, "p_bool", b"UseDepthOfField", cam_data.dof.use_dof)
678 elem_props_template_set(tmpl, props, "p_double", b"FocusDistance", cam_data.dof.focus_distance * 1000 * gscale)
679 # Default to perspective camera.
680 elem_props_template_set(tmpl, props, "p_enum", b"CameraProjectionType", 1 if cam_data.type == 'ORTHO' else 0)
681 elem_props_template_set(tmpl, props, "p_double", b"OrthoZoom", cam_data.ortho_scale)
683 elem_props_template_set(tmpl, props, "p_double", b"NearPlane", cam_data.clip_start * gscale)
684 elem_props_template_set(tmpl, props, "p_double", b"FarPlane", cam_data.clip_end * gscale)
685 elem_props_template_set(tmpl, props, "p_enum", b"BackPlaneDistanceMode", 1) # RelativeToCamera.
686 elem_props_template_set(tmpl, props, "p_double", b"BackPlaneDistance", cam_data.clip_end * gscale)
688 elem_props_template_finalize(tmpl, props)
690 # Custom properties.
691 if scene_data.settings.use_custom_props:
692 fbx_data_element_custom_properties(props, cam_data)
694 elem_data_single_string(cam, b"TypeFlags", b"Camera")
695 elem_data_single_int32(cam, b"GeometryVersion", 124) # Sic...
696 elem_data_vec_float64(cam, b"Position", loc)
697 elem_data_vec_float64(cam, b"Up", up)
698 elem_data_vec_float64(cam, b"LookAt", to)
699 elem_data_single_int32(cam, b"ShowInfoOnMoving", 1)
700 elem_data_single_int32(cam, b"ShowAudio", 0)
701 elem_data_vec_float64(cam, b"AudioColor", (0.0, 1.0, 0.0))
702 elem_data_single_float64(cam, b"CameraOrthoZoom", 1.0)
705 def fbx_data_bindpose_element(root, me_obj, me, scene_data, arm_obj=None, mat_world_arm=None, bones=[]):
707 Helper, since bindpose are used by both meshes shape keys and armature bones...
709 if arm_obj is None:
710 arm_obj = me_obj
711 # We assume bind pose for our bones are their "Editmode" pose...
712 # All matrices are expected in global (world) space.
713 bindpose_key = get_blender_bindpose_key(arm_obj.bdata, me)
714 fbx_pose = elem_data_single_int64(root, b"Pose", get_fbx_uuid_from_key(bindpose_key))
715 fbx_pose.add_string(fbx_name_class(me.name.encode(), b"Pose"))
716 fbx_pose.add_string(b"BindPose")
718 elem_data_single_string(fbx_pose, b"Type", b"BindPose")
719 elem_data_single_int32(fbx_pose, b"Version", FBX_POSE_BIND_VERSION)
720 elem_data_single_int32(fbx_pose, b"NbPoseNodes", 1 + (1 if (arm_obj != me_obj) else 0) + len(bones))
722 # First node is mesh/object.
723 mat_world_obj = me_obj.fbx_object_matrix(scene_data, global_space=True)
724 fbx_posenode = elem_empty(fbx_pose, b"PoseNode")
725 elem_data_single_int64(fbx_posenode, b"Node", me_obj.fbx_uuid)
726 elem_data_single_float64_array(fbx_posenode, b"Matrix", matrix4_to_array(mat_world_obj))
727 # Second node is armature object itself.
728 if arm_obj != me_obj:
729 fbx_posenode = elem_empty(fbx_pose, b"PoseNode")
730 elem_data_single_int64(fbx_posenode, b"Node", arm_obj.fbx_uuid)
731 elem_data_single_float64_array(fbx_posenode, b"Matrix", matrix4_to_array(mat_world_arm))
732 # And all bones of armature!
733 mat_world_bones = {}
734 for bo_obj in bones:
735 bomat = bo_obj.fbx_object_matrix(scene_data, rest=True, global_space=True)
736 mat_world_bones[bo_obj] = bomat
737 fbx_posenode = elem_empty(fbx_pose, b"PoseNode")
738 elem_data_single_int64(fbx_posenode, b"Node", bo_obj.fbx_uuid)
739 elem_data_single_float64_array(fbx_posenode, b"Matrix", matrix4_to_array(bomat))
741 return mat_world_obj, mat_world_bones
744 def fbx_data_mesh_shapes_elements(root, me_obj, me, scene_data, fbx_me_tmpl, fbx_me_props):
746 Write shape keys related data.
748 if me not in scene_data.data_deformers_shape:
749 return
751 write_normals = True # scene_data.settings.mesh_smooth_type in {'OFF'}
753 # First, write the geometry data itself (i.e. shapes).
754 _me_key, shape_key, shapes = scene_data.data_deformers_shape[me]
756 channels = []
758 vertices = me.vertices
759 for shape, (channel_key, geom_key, shape_verts_co, shape_verts_idx) in shapes.items():
760 # Use vgroups as weights, if defined.
761 if shape.vertex_group and shape.vertex_group in me_obj.bdata.vertex_groups:
762 shape_verts_weights = array.array(data_types.ARRAY_FLOAT64, [0.0]) * (len(shape_verts_co) // 3)
763 vg_idx = me_obj.bdata.vertex_groups[shape.vertex_group].index
764 for sk_idx, v_idx in enumerate(shape_verts_idx):
765 for vg in vertices[v_idx].groups:
766 if vg.group == vg_idx:
767 shape_verts_weights[sk_idx] = vg.weight * 100.0
768 break
769 else:
770 shape_verts_weights = array.array(data_types.ARRAY_FLOAT64, [100.0]) * (len(shape_verts_co) // 3)
771 channels.append((channel_key, shape, shape_verts_weights))
773 geom = elem_data_single_int64(root, b"Geometry", get_fbx_uuid_from_key(geom_key))
774 geom.add_string(fbx_name_class(shape.name.encode(), b"Geometry"))
775 geom.add_string(b"Shape")
777 tmpl = elem_props_template_init(scene_data.templates, b"Geometry")
778 props = elem_properties(geom)
779 elem_props_template_finalize(tmpl, props)
781 elem_data_single_int32(geom, b"Version", FBX_GEOMETRY_SHAPE_VERSION)
783 elem_data_single_int32_array(geom, b"Indexes", shape_verts_idx)
784 elem_data_single_float64_array(geom, b"Vertices", shape_verts_co)
785 if write_normals:
786 elem_data_single_float64_array(geom, b"Normals",
787 array.array(data_types.ARRAY_FLOAT64, [0.0]) * len(shape_verts_co))
789 # Yiha! BindPose for shapekeys too! Dodecasigh...
790 # XXX Not sure yet whether several bindposes on same mesh are allowed, or not... :/
791 fbx_data_bindpose_element(root, me_obj, me, scene_data)
793 # ...and now, the deformers stuff.
794 fbx_shape = elem_data_single_int64(root, b"Deformer", get_fbx_uuid_from_key(shape_key))
795 fbx_shape.add_string(fbx_name_class(me.name.encode(), b"Deformer"))
796 fbx_shape.add_string(b"BlendShape")
798 elem_data_single_int32(fbx_shape, b"Version", FBX_DEFORMER_SHAPE_VERSION)
800 for channel_key, shape, shape_verts_weights in channels:
801 fbx_channel = elem_data_single_int64(root, b"Deformer", get_fbx_uuid_from_key(channel_key))
802 fbx_channel.add_string(fbx_name_class(shape.name.encode(), b"SubDeformer"))
803 fbx_channel.add_string(b"BlendShapeChannel")
805 elem_data_single_int32(fbx_channel, b"Version", FBX_DEFORMER_SHAPECHANNEL_VERSION)
806 elem_data_single_float64(fbx_channel, b"DeformPercent", shape.value * 100.0) # Percents...
807 elem_data_single_float64_array(fbx_channel, b"FullWeights", shape_verts_weights)
809 # *WHY* add this in linked mesh properties too? *cry*
810 # No idea whether it’s percent here too, or more usual factor (assume percentage for now) :/
811 elem_props_template_set(fbx_me_tmpl, fbx_me_props, "p_number", shape.name.encode(), shape.value * 100.0,
812 animatable=True)
815 def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
817 Write the Mesh (Geometry) data block.
819 # Ugly helper... :/
820 def _infinite_gen(val):
821 while 1:
822 yield val
824 me_key, me, _free = scene_data.data_meshes[me_obj]
826 # In case of multiple instances of same mesh, only write it once!
827 if me_key in done_meshes:
828 return
830 # No gscale/gmat here, all data are supposed to be in object space.
831 smooth_type = scene_data.settings.mesh_smooth_type
832 write_normals = True # smooth_type in {'OFF'}
834 do_bake_space_transform = me_obj.use_bake_space_transform(scene_data)
836 # Vertices are in object space, but we are post-multiplying all transforms with the inverse of the
837 # global matrix, so we need to apply the global matrix to the vertices to get the correct result.
838 geom_mat_co = scene_data.settings.global_matrix if do_bake_space_transform else None
839 # We need to apply the inverse transpose of the global matrix when transforming normals.
840 geom_mat_no = Matrix(scene_data.settings.global_matrix_inv_transposed) if do_bake_space_transform else None
841 if geom_mat_no is not None:
842 # Remove translation & scaling!
843 geom_mat_no.translation = Vector()
844 geom_mat_no.normalize()
846 geom = elem_data_single_int64(root, b"Geometry", get_fbx_uuid_from_key(me_key))
847 geom.add_string(fbx_name_class(me.name.encode(), b"Geometry"))
848 geom.add_string(b"Mesh")
850 tmpl = elem_props_template_init(scene_data.templates, b"Geometry")
851 props = elem_properties(geom)
853 # Custom properties.
854 if scene_data.settings.use_custom_props:
855 fbx_data_element_custom_properties(props, me)
857 # Subdivision levels. Take them from the first found subsurf modifier from the
858 # first object that has the mesh. Write crease information if the object has
859 # and subsurf modifier.
860 write_crease = False
861 if scene_data.settings.use_subsurf:
862 last_subsurf = None
863 for mod in me_obj.bdata.modifiers:
864 if not (mod.show_render or mod.show_viewport):
865 continue
866 if mod.type == 'SUBSURF' and mod.subdivision_type == 'CATMULL_CLARK':
867 last_subsurf = mod
869 if last_subsurf:
870 elem_data_single_int32(geom, b"Smoothness", 2) # Display control mesh and smoothed
871 if last_subsurf.boundary_smooth == "PRESERVE_CORNERS":
872 elem_data_single_int32(geom, b"BoundaryRule", 1) # CreaseAll
873 else:
874 elem_data_single_int32(geom, b"BoundaryRule", 2) # CreaseEdge
875 elem_data_single_int32(geom, b"PreviewDivisionLevels", last_subsurf.levels)
876 elem_data_single_int32(geom, b"RenderDivisionLevels", last_subsurf.render_levels)
878 elem_data_single_int32(geom, b"PreserveBorders", 0)
879 elem_data_single_int32(geom, b"PreserveHardEdges", 0)
880 elem_data_single_int32(geom, b"PropagateEdgeHardness", 0)
882 write_crease = last_subsurf.use_creases
884 elem_data_single_int32(geom, b"GeometryVersion", FBX_GEOMETRY_VERSION)
886 # Vertex cos.
887 t_co = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * len(me.vertices) * 3
888 me.vertices.foreach_get("co", t_co)
889 elem_data_single_float64_array(geom, b"Vertices", chain(*vcos_transformed_gen(t_co, geom_mat_co)))
890 del t_co
892 # Polygon indices.
894 # We do loose edges as two-vertices faces, if enabled...
896 # Note we have to process Edges in the same time, as they are based on poly's loops...
897 loop_nbr = len(me.loops)
898 t_pvi = array.array(data_types.ARRAY_INT32, (0,)) * loop_nbr
899 t_ls = [None] * len(me.polygons)
901 me.loops.foreach_get("vertex_index", t_pvi)
902 me.polygons.foreach_get("loop_start", t_ls)
904 # Add "fake" faces for loose edges.
905 if scene_data.settings.use_mesh_edges:
906 t_le = tuple(e.vertices for e in me.edges if e.is_loose)
907 t_pvi.extend(chain(*t_le))
908 t_ls.extend(range(loop_nbr, loop_nbr + len(t_le) * 2, 2))
909 del t_le
911 # Edges...
912 # Note: Edges are represented as a loop here: each edge uses a single index, which refers to the polygon array.
913 # The edge is made by the vertex indexed py this polygon's point and the next one on the same polygon.
914 # Advantage: Only one index per edge.
915 # Drawback: Only polygon's edges can be represented (that's why we have to add fake two-verts polygons
916 # for loose edges).
917 # We also have to store a mapping from real edges to their indices in this array, for edge-mapped data
918 # (like e.g. crease).
919 t_eli = array.array(data_types.ARRAY_INT32)
920 edges_map = {}
921 edges_nbr = 0
922 if t_ls and t_pvi:
923 # t_ls is loop start indices of polygons, but we want to use it to indicate the end loop of each polygon.
924 # The loop end index of a polygon is the loop start index of the next polygon minus one, so the first element of
925 # t_ls will be ignored, and we need to add an extra element at the end to signify the end of the last polygon.
926 # If we were to add another polygon to the mesh, its loop start index would be the next loop index.
927 t_ls = set(t_ls[1:])
928 t_ls.add(loop_nbr)
929 todo_edges = [None] * len(me.edges) * 2
930 # Sigh, cannot access edge.key through foreach_get... :/
931 me.edges.foreach_get("vertices", todo_edges)
932 todo_edges = set((v1, v2) if v1 < v2 else (v2, v1) for v1, v2 in zip(*(iter(todo_edges),) * 2))
934 li = 0
935 vi = vi_start = t_pvi[0]
936 for li_next, vi_next in enumerate(t_pvi[1:] + t_pvi[:1], start=1):
937 if li_next in t_ls: # End of a poly's loop.
938 vi2 = vi_start
939 vi_start = vi_next
940 else:
941 vi2 = vi_next
943 e_key = (vi, vi2) if vi < vi2 else (vi2, vi)
944 if e_key in todo_edges:
945 t_eli.append(li)
946 todo_edges.remove(e_key)
947 edges_map[e_key] = edges_nbr
948 edges_nbr += 1
950 vi = vi_next
951 li = li_next
952 # End of edges!
954 # We have to ^-1 last index of each loop.
955 for ls in t_ls:
956 t_pvi[ls - 1] ^= -1
958 # And finally we can write data!
959 elem_data_single_int32_array(geom, b"PolygonVertexIndex", t_pvi)
960 elem_data_single_int32_array(geom, b"Edges", t_eli)
961 del t_pvi
962 del t_ls
963 del t_eli
965 # And now, layers!
967 # Smoothing.
968 if smooth_type in {'FACE', 'EDGE'}:
969 t_ps = None
970 _map = b""
971 if smooth_type == 'FACE':
972 t_ps = array.array(data_types.ARRAY_INT32, (0,)) * len(me.polygons)
973 me.polygons.foreach_get("use_smooth", t_ps)
974 _map = b"ByPolygon"
975 else: # EDGE
976 # Write Edge Smoothing.
977 # Note edge is sharp also if it's used by more than two faces, or one of its faces is flat.
978 t_ps = array.array(data_types.ARRAY_INT32, (0,)) * edges_nbr
979 sharp_edges = set()
980 temp_sharp_edges = {}
981 for p in me.polygons:
982 if not p.use_smooth:
983 sharp_edges.update(p.edge_keys)
984 continue
985 for k in p.edge_keys:
986 if temp_sharp_edges.setdefault(k, 0) > 1:
987 sharp_edges.add(k)
988 else:
989 temp_sharp_edges[k] += 1
990 del temp_sharp_edges
991 for e in me.edges:
992 if e.key not in edges_map:
993 continue # Only loose edges, in theory!
994 t_ps[edges_map[e.key]] = not (e.use_edge_sharp or (e.key in sharp_edges))
995 _map = b"ByEdge"
996 lay_smooth = elem_data_single_int32(geom, b"LayerElementSmoothing", 0)
997 elem_data_single_int32(lay_smooth, b"Version", FBX_GEOMETRY_SMOOTHING_VERSION)
998 elem_data_single_string(lay_smooth, b"Name", b"")
999 elem_data_single_string(lay_smooth, b"MappingInformationType", _map)
1000 elem_data_single_string(lay_smooth, b"ReferenceInformationType", b"Direct")
1001 elem_data_single_int32_array(lay_smooth, b"Smoothing", t_ps) # Sight, int32 for bool...
1002 del t_ps
1004 # Edge crease for subdivision
1005 if write_crease:
1006 t_ec = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * edges_nbr
1007 for e in me.edges:
1008 if e.key not in edges_map:
1009 continue # Only loose edges, in theory!
1010 # Blender squares those values before sending them to OpenSubdiv, when other software don't,
1011 # so we need to compensate that to get similar results through FBX...
1012 t_ec[edges_map[e.key]] = e.crease * e.crease
1014 lay_crease = elem_data_single_int32(geom, b"LayerElementEdgeCrease", 0)
1015 elem_data_single_int32(lay_crease, b"Version", FBX_GEOMETRY_CREASE_VERSION)
1016 elem_data_single_string(lay_crease, b"Name", b"")
1017 elem_data_single_string(lay_crease, b"MappingInformationType", b"ByEdge")
1018 elem_data_single_string(lay_crease, b"ReferenceInformationType", b"Direct")
1019 elem_data_single_float64_array(lay_crease, b"EdgeCrease", t_ec)
1020 del t_ec
1022 # And we are done with edges!
1023 del edges_map
1025 # Loop normals.
1026 tspacenumber = 0
1027 if write_normals:
1028 # NOTE: this is not supported by importer currently.
1029 # XXX Official docs says normals should use IndexToDirect,
1030 # but this does not seem well supported by apps currently...
1031 me.calc_normals_split()
1033 t_ln = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * len(me.loops) * 3
1034 me.loops.foreach_get("normal", t_ln)
1035 t_ln = nors_transformed_gen(t_ln, geom_mat_no)
1036 if 0:
1037 t_ln = tuple(t_ln) # No choice... :/
1039 lay_nor = elem_data_single_int32(geom, b"LayerElementNormal", 0)
1040 elem_data_single_int32(lay_nor, b"Version", FBX_GEOMETRY_NORMAL_VERSION)
1041 elem_data_single_string(lay_nor, b"Name", b"")
1042 elem_data_single_string(lay_nor, b"MappingInformationType", b"ByPolygonVertex")
1043 elem_data_single_string(lay_nor, b"ReferenceInformationType", b"IndexToDirect")
1045 ln2idx = tuple(set(t_ln))
1046 elem_data_single_float64_array(lay_nor, b"Normals", chain(*ln2idx))
1047 # Normal weights, no idea what it is.
1048 # t_lnw = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * len(ln2idx)
1049 # elem_data_single_float64_array(lay_nor, b"NormalsW", t_lnw)
1051 ln2idx = {nor: idx for idx, nor in enumerate(ln2idx)}
1052 elem_data_single_int32_array(lay_nor, b"NormalsIndex", (ln2idx[n] for n in t_ln))
1054 del ln2idx
1055 # del t_lnw
1056 else:
1057 lay_nor = elem_data_single_int32(geom, b"LayerElementNormal", 0)
1058 elem_data_single_int32(lay_nor, b"Version", FBX_GEOMETRY_NORMAL_VERSION)
1059 elem_data_single_string(lay_nor, b"Name", b"")
1060 elem_data_single_string(lay_nor, b"MappingInformationType", b"ByPolygonVertex")
1061 elem_data_single_string(lay_nor, b"ReferenceInformationType", b"Direct")
1062 elem_data_single_float64_array(lay_nor, b"Normals", chain(*t_ln))
1063 # Normal weights, no idea what it is.
1064 # t_ln = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * len(me.loops)
1065 # elem_data_single_float64_array(lay_nor, b"NormalsW", t_ln)
1066 del t_ln
1068 # tspace
1069 if scene_data.settings.use_tspace:
1070 tspacenumber = len(me.uv_layers)
1071 if tspacenumber:
1072 # We can only compute tspace on tessellated meshes, need to check that here...
1073 t_lt = [None] * len(me.polygons)
1074 me.polygons.foreach_get("loop_total", t_lt)
1075 if any((lt > 4 for lt in t_lt)):
1076 del t_lt
1077 scene_data.settings.report(
1078 {'WARNING'},
1079 tip_("Mesh '%s' has polygons with more than 4 vertices, "
1080 "cannot compute/export tangent space for it") % me.name)
1081 else:
1082 del t_lt
1083 num_loops = len(me.loops)
1084 t_ln = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * num_loops * 3
1085 # t_lnw = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * len(me.loops)
1086 uv_names = [uvlayer.name for uvlayer in me.uv_layers]
1087 # Annoying, `me.calc_tangent` errors in case there is no geometry...
1088 if num_loops > 0:
1089 for name in uv_names:
1090 me.calc_tangents(uvmap=name)
1091 for idx, uvlayer in enumerate(me.uv_layers):
1092 name = uvlayer.name
1093 # Loop bitangents (aka binormals).
1094 # NOTE: this is not supported by importer currently.
1095 me.loops.foreach_get("bitangent", t_ln)
1096 lay_nor = elem_data_single_int32(geom, b"LayerElementBinormal", idx)
1097 elem_data_single_int32(lay_nor, b"Version", FBX_GEOMETRY_BINORMAL_VERSION)
1098 elem_data_single_string_unicode(lay_nor, b"Name", name)
1099 elem_data_single_string(lay_nor, b"MappingInformationType", b"ByPolygonVertex")
1100 elem_data_single_string(lay_nor, b"ReferenceInformationType", b"Direct")
1101 elem_data_single_float64_array(lay_nor, b"Binormals",
1102 chain(*nors_transformed_gen(t_ln, geom_mat_no)))
1103 # Binormal weights, no idea what it is.
1104 # elem_data_single_float64_array(lay_nor, b"BinormalsW", t_lnw)
1106 # Loop tangents.
1107 # NOTE: this is not supported by importer currently.
1108 me.loops.foreach_get("tangent", t_ln)
1109 lay_nor = elem_data_single_int32(geom, b"LayerElementTangent", idx)
1110 elem_data_single_int32(lay_nor, b"Version", FBX_GEOMETRY_TANGENT_VERSION)
1111 elem_data_single_string_unicode(lay_nor, b"Name", name)
1112 elem_data_single_string(lay_nor, b"MappingInformationType", b"ByPolygonVertex")
1113 elem_data_single_string(lay_nor, b"ReferenceInformationType", b"Direct")
1114 elem_data_single_float64_array(lay_nor, b"Tangents",
1115 chain(*nors_transformed_gen(t_ln, geom_mat_no)))
1116 # Tangent weights, no idea what it is.
1117 # elem_data_single_float64_array(lay_nor, b"TangentsW", t_lnw)
1119 del t_ln
1120 # del t_lnw
1121 me.free_tangents()
1123 me.free_normals_split()
1125 # Write VertexColor Layers.
1126 colors_type = scene_data.settings.colors_type
1127 vcolnumber = 0 if colors_type == 'NONE' else len(me.color_attributes)
1128 if vcolnumber:
1129 def _coltuples_gen(raw_cols):
1130 return zip(*(iter(raw_cols),) * 4)
1132 color_prop_name = "color_srgb" if colors_type == 'SRGB' else "color"
1134 for colindex, collayer in enumerate(me.color_attributes):
1135 is_point = collayer.domain == "POINT"
1136 vcollen = len(me.vertices if is_point else me.loops)
1137 t_lc = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * vcollen * 4
1138 collayer.data.foreach_get(color_prop_name, t_lc)
1140 lay_vcol = elem_data_single_int32(geom, b"LayerElementColor", colindex)
1141 elem_data_single_int32(lay_vcol, b"Version", FBX_GEOMETRY_VCOLOR_VERSION)
1142 elem_data_single_string_unicode(lay_vcol, b"Name", collayer.name)
1143 elem_data_single_string(lay_vcol, b"MappingInformationType", b"ByPolygonVertex")
1144 elem_data_single_string(lay_vcol, b"ReferenceInformationType", b"IndexToDirect")
1146 col2idx = tuple(set(_coltuples_gen(t_lc)))
1147 elem_data_single_float64_array(lay_vcol, b"Colors", chain(*col2idx)) # Flatten again...
1149 col2idx = {col: idx for idx, col in enumerate(col2idx)}
1150 col_indices = list(col2idx[c] for c in _coltuples_gen(t_lc))
1151 if is_point:
1152 # for "point" domain colors, we could directly emit them
1153 # with a "ByVertex" mapping type, but some software does not
1154 # properly understand that. So expand to full "ByPolygonVertex"
1155 # index map.
1156 col_indices = list((col_indices[c.vertex_index] for c in me.loops))
1157 elem_data_single_int32_array(lay_vcol, b"ColorIndex", col_indices)
1158 del col2idx
1159 del t_lc
1160 del _coltuples_gen
1162 # Write UV layers.
1163 # Note: LayerElementTexture is deprecated since FBX 2011 - luckily!
1164 # Textures are now only related to materials, in FBX!
1165 uvnumber = len(me.uv_layers)
1166 if uvnumber:
1167 # Looks like this mapping is also expected to convey UV islands (arg..... :((((( ).
1168 # So we need to generate unique triplets (uv, vertex_idx) here, not only just based on UV values.
1169 def _uvtuples_gen(raw_uvs, raw_lvidxs):
1170 return zip(zip(*(iter(raw_uvs),) * 2), raw_lvidxs)
1172 t_luv = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * len(me.loops) * 2
1173 t_lvidx = array.array(data_types.ARRAY_INT32, (0,)) * len(me.loops)
1174 me.loops.foreach_get("vertex_index", t_lvidx)
1175 for uvindex, uvlayer in enumerate(me.uv_layers):
1176 uvlayer.data.foreach_get("uv", t_luv)
1177 lay_uv = elem_data_single_int32(geom, b"LayerElementUV", uvindex)
1178 elem_data_single_int32(lay_uv, b"Version", FBX_GEOMETRY_UV_VERSION)
1179 elem_data_single_string_unicode(lay_uv, b"Name", uvlayer.name)
1180 elem_data_single_string(lay_uv, b"MappingInformationType", b"ByPolygonVertex")
1181 elem_data_single_string(lay_uv, b"ReferenceInformationType", b"IndexToDirect")
1183 uv_ids = tuple(set(_uvtuples_gen(t_luv, t_lvidx)))
1184 elem_data_single_float64_array(lay_uv, b"UV", chain(*(uv for uv, vidx in uv_ids))) # Flatten again...
1186 uv2idx = {uv_id: idx for idx, uv_id in enumerate(uv_ids)}
1187 elem_data_single_int32_array(lay_uv, b"UVIndex", (uv2idx[uv_id] for uv_id in _uvtuples_gen(t_luv, t_lvidx)))
1188 del uv2idx
1189 del uv_ids
1190 del t_luv
1191 del t_lvidx
1192 del _uvtuples_gen
1194 # Face's materials.
1195 me_fbxmaterials_idx = scene_data.mesh_material_indices.get(me)
1196 if me_fbxmaterials_idx is not None:
1197 # We cannot use me.materials here, as this array is filled with None in case materials are linked to object...
1198 me_blmaterials = [mat_slot.material for mat_slot in me_obj.material_slots]
1199 if me_fbxmaterials_idx and me_blmaterials:
1200 lay_ma = elem_data_single_int32(geom, b"LayerElementMaterial", 0)
1201 elem_data_single_int32(lay_ma, b"Version", FBX_GEOMETRY_MATERIAL_VERSION)
1202 elem_data_single_string(lay_ma, b"Name", b"")
1203 nbr_mats = len(me_fbxmaterials_idx)
1204 if nbr_mats > 1:
1205 t_pm = array.array(data_types.ARRAY_INT32, (0,)) * len(me.polygons)
1206 me.polygons.foreach_get("material_index", t_pm)
1208 # We have to validate mat indices, and map them to FBX indices.
1209 # Note a mat might not be in me_fbxmats_idx (e.g. node mats are ignored).
1210 def_ma = next(me_fbxmaterials_idx[m] for m in me_blmaterials if m in me_fbxmaterials_idx)
1211 blmaterials_to_fbxmaterials_idxs = [me_fbxmaterials_idx.get(m, def_ma) for m in me_blmaterials]
1212 ma_idx_limit = len(blmaterials_to_fbxmaterials_idxs)
1213 _gen = (blmaterials_to_fbxmaterials_idxs[m] if m < ma_idx_limit else def_ma for m in t_pm)
1214 t_pm = array.array(data_types.ARRAY_INT32, _gen)
1216 elem_data_single_string(lay_ma, b"MappingInformationType", b"ByPolygon")
1217 # XXX Logically, should be "Direct" reference type, since we do not have any index array, and have one
1218 # value per polygon...
1219 # But looks like FBX expects it to be IndexToDirect here (maybe because materials are already
1220 # indices??? *sigh*).
1221 elem_data_single_string(lay_ma, b"ReferenceInformationType", b"IndexToDirect")
1222 elem_data_single_int32_array(lay_ma, b"Materials", t_pm)
1223 del t_pm
1224 else:
1225 elem_data_single_string(lay_ma, b"MappingInformationType", b"AllSame")
1226 elem_data_single_string(lay_ma, b"ReferenceInformationType", b"IndexToDirect")
1227 elem_data_single_int32_array(lay_ma, b"Materials", [0])
1229 # And the "layer TOC"...
1231 layer = elem_data_single_int32(geom, b"Layer", 0)
1232 elem_data_single_int32(layer, b"Version", FBX_GEOMETRY_LAYER_VERSION)
1233 if write_normals:
1234 lay_nor = elem_empty(layer, b"LayerElement")
1235 elem_data_single_string(lay_nor, b"Type", b"LayerElementNormal")
1236 elem_data_single_int32(lay_nor, b"TypedIndex", 0)
1237 if tspacenumber:
1238 lay_binor = elem_empty(layer, b"LayerElement")
1239 elem_data_single_string(lay_binor, b"Type", b"LayerElementBinormal")
1240 elem_data_single_int32(lay_binor, b"TypedIndex", 0)
1241 lay_tan = elem_empty(layer, b"LayerElement")
1242 elem_data_single_string(lay_tan, b"Type", b"LayerElementTangent")
1243 elem_data_single_int32(lay_tan, b"TypedIndex", 0)
1244 if smooth_type in {'FACE', 'EDGE'}:
1245 lay_smooth = elem_empty(layer, b"LayerElement")
1246 elem_data_single_string(lay_smooth, b"Type", b"LayerElementSmoothing")
1247 elem_data_single_int32(lay_smooth, b"TypedIndex", 0)
1248 if write_crease:
1249 lay_smooth = elem_empty(layer, b"LayerElement")
1250 elem_data_single_string(lay_smooth, b"Type", b"LayerElementEdgeCrease")
1251 elem_data_single_int32(lay_smooth, b"TypedIndex", 0)
1252 if vcolnumber:
1253 lay_vcol = elem_empty(layer, b"LayerElement")
1254 elem_data_single_string(lay_vcol, b"Type", b"LayerElementColor")
1255 elem_data_single_int32(lay_vcol, b"TypedIndex", 0)
1256 if uvnumber:
1257 lay_uv = elem_empty(layer, b"LayerElement")
1258 elem_data_single_string(lay_uv, b"Type", b"LayerElementUV")
1259 elem_data_single_int32(lay_uv, b"TypedIndex", 0)
1260 if me_fbxmaterials_idx is not None:
1261 lay_ma = elem_empty(layer, b"LayerElement")
1262 elem_data_single_string(lay_ma, b"Type", b"LayerElementMaterial")
1263 elem_data_single_int32(lay_ma, b"TypedIndex", 0)
1265 # Add other uv and/or vcol layers...
1266 for vcolidx, uvidx, tspaceidx in zip_longest(range(1, vcolnumber), range(1, uvnumber), range(1, tspacenumber),
1267 fillvalue=0):
1268 layer = elem_data_single_int32(geom, b"Layer", max(vcolidx, uvidx))
1269 elem_data_single_int32(layer, b"Version", FBX_GEOMETRY_LAYER_VERSION)
1270 if vcolidx:
1271 lay_vcol = elem_empty(layer, b"LayerElement")
1272 elem_data_single_string(lay_vcol, b"Type", b"LayerElementColor")
1273 elem_data_single_int32(lay_vcol, b"TypedIndex", vcolidx)
1274 if uvidx:
1275 lay_uv = elem_empty(layer, b"LayerElement")
1276 elem_data_single_string(lay_uv, b"Type", b"LayerElementUV")
1277 elem_data_single_int32(lay_uv, b"TypedIndex", uvidx)
1278 if tspaceidx:
1279 lay_binor = elem_empty(layer, b"LayerElement")
1280 elem_data_single_string(lay_binor, b"Type", b"LayerElementBinormal")
1281 elem_data_single_int32(lay_binor, b"TypedIndex", tspaceidx)
1282 lay_tan = elem_empty(layer, b"LayerElement")
1283 elem_data_single_string(lay_tan, b"Type", b"LayerElementTangent")
1284 elem_data_single_int32(lay_tan, b"TypedIndex", tspaceidx)
1286 # Shape keys...
1287 fbx_data_mesh_shapes_elements(root, me_obj, me, scene_data, tmpl, props)
1289 elem_props_template_finalize(tmpl, props)
1290 done_meshes.add(me_key)
1293 def fbx_data_material_elements(root, ma, scene_data):
1295 Write the Material data block.
1298 ambient_color = (0.0, 0.0, 0.0)
1299 if scene_data.data_world:
1300 ambient_color = next(iter(scene_data.data_world.keys())).color
1302 ma_wrap = node_shader_utils.PrincipledBSDFWrapper(ma, is_readonly=True)
1303 ma_key, _objs = scene_data.data_materials[ma]
1304 ma_type = b"Phong"
1306 fbx_ma = elem_data_single_int64(root, b"Material", get_fbx_uuid_from_key(ma_key))
1307 fbx_ma.add_string(fbx_name_class(ma.name.encode(), b"Material"))
1308 fbx_ma.add_string(b"")
1310 elem_data_single_int32(fbx_ma, b"Version", FBX_MATERIAL_VERSION)
1311 # those are not yet properties, it seems...
1312 elem_data_single_string(fbx_ma, b"ShadingModel", ma_type)
1313 elem_data_single_int32(fbx_ma, b"MultiLayer", 0) # Should be bool...
1315 tmpl = elem_props_template_init(scene_data.templates, b"Material")
1316 props = elem_properties(fbx_ma)
1318 elem_props_template_set(tmpl, props, "p_string", b"ShadingModel", ma_type.decode())
1319 elem_props_template_set(tmpl, props, "p_color", b"DiffuseColor", ma_wrap.base_color)
1320 # Not in Principled BSDF, so assuming always 1
1321 elem_props_template_set(tmpl, props, "p_number", b"DiffuseFactor", 1.0)
1322 # Principled BSDF only has an emissive color, so we assume factor to be always 1.0.
1323 elem_props_template_set(tmpl, props, "p_color", b"EmissiveColor", ma_wrap.emission_color)
1324 elem_props_template_set(tmpl, props, "p_number", b"EmissiveFactor", ma_wrap.emission_strength)
1325 # Not in Principled BSDF, so assuming always 0
1326 elem_props_template_set(tmpl, props, "p_color", b"AmbientColor", ambient_color)
1327 elem_props_template_set(tmpl, props, "p_number", b"AmbientFactor", 0.0)
1328 # Sweetness... Looks like we are not the only ones to not know exactly how FBX is supposed to work (see T59850).
1329 # According to one of its developers, Unity uses that formula to extract alpha value:
1331 # alpha = 1 - TransparencyFactor
1332 # if (alpha == 1 or alpha == 0):
1333 # alpha = 1 - TransparentColor.r
1335 # Until further info, let's assume this is correct way to do, hence the following code for TransparentColor.
1336 if ma_wrap.alpha < 1.0e-5 or ma_wrap.alpha > (1.0 - 1.0e-5):
1337 elem_props_template_set(tmpl, props, "p_color", b"TransparentColor", (1.0 - ma_wrap.alpha,) * 3)
1338 else:
1339 elem_props_template_set(tmpl, props, "p_color", b"TransparentColor", ma_wrap.base_color)
1340 elem_props_template_set(tmpl, props, "p_number", b"TransparencyFactor", 1.0 - ma_wrap.alpha)
1341 elem_props_template_set(tmpl, props, "p_number", b"Opacity", ma_wrap.alpha)
1342 elem_props_template_set(tmpl, props, "p_vector_3d", b"NormalMap", (0.0, 0.0, 0.0))
1343 elem_props_template_set(tmpl, props, "p_double", b"BumpFactor", ma_wrap.normalmap_strength)
1344 # Not sure about those...
1346 b"Bump": ((0.0, 0.0, 0.0), "p_vector_3d"),
1347 b"DisplacementColor": ((0.0, 0.0, 0.0), "p_color_rgb"),
1348 b"DisplacementFactor": (0.0, "p_double"),
1350 # TODO: use specular tint?
1351 elem_props_template_set(tmpl, props, "p_color", b"SpecularColor", ma_wrap.base_color)
1352 elem_props_template_set(tmpl, props, "p_number", b"SpecularFactor", ma_wrap.specular / 2.0)
1353 # See Material template about those two!
1354 # XXX Totally empirical conversion, trying to adapt it
1355 # (from 0.0 - 100.0 FBX shininess range to 1.0 - 0.0 Principled BSDF range)...
1356 shininess = (1.0 - ma_wrap.roughness) * 10
1357 shininess *= shininess
1358 elem_props_template_set(tmpl, props, "p_number", b"Shininess", shininess)
1359 elem_props_template_set(tmpl, props, "p_number", b"ShininessExponent", shininess)
1360 elem_props_template_set(tmpl, props, "p_color", b"ReflectionColor", ma_wrap.base_color)
1361 elem_props_template_set(tmpl, props, "p_number", b"ReflectionFactor", ma_wrap.metallic)
1363 elem_props_template_finalize(tmpl, props)
1365 # Custom properties.
1366 if scene_data.settings.use_custom_props:
1367 fbx_data_element_custom_properties(props, ma)
1370 def _gen_vid_path(img, scene_data):
1371 msetts = scene_data.settings.media_settings
1372 fname_rel = bpy_extras.io_utils.path_reference(img.filepath, msetts.base_src, msetts.base_dst, msetts.path_mode,
1373 msetts.subdir, msetts.copy_set, img.library)
1374 fname_abs = os.path.normpath(os.path.abspath(os.path.join(msetts.base_dst, fname_rel)))
1375 return fname_abs, fname_rel
1378 def fbx_data_texture_file_elements(root, blender_tex_key, scene_data):
1380 Write the (file) Texture data block.
1382 # XXX All this is very fuzzy to me currently...
1383 # Textures do not seem to use properties as much as they could.
1384 # For now assuming most logical and simple stuff.
1386 ma, sock_name = blender_tex_key
1387 ma_wrap = node_shader_utils.PrincipledBSDFWrapper(ma, is_readonly=True)
1388 tex_key, _fbx_prop = scene_data.data_textures[blender_tex_key]
1389 tex = getattr(ma_wrap, sock_name)
1390 img = tex.image
1391 fname_abs, fname_rel = _gen_vid_path(img, scene_data)
1393 fbx_tex = elem_data_single_int64(root, b"Texture", get_fbx_uuid_from_key(tex_key))
1394 fbx_tex.add_string(fbx_name_class(sock_name.encode(), b"Texture"))
1395 fbx_tex.add_string(b"")
1397 elem_data_single_string(fbx_tex, b"Type", b"TextureVideoClip")
1398 elem_data_single_int32(fbx_tex, b"Version", FBX_TEXTURE_VERSION)
1399 elem_data_single_string(fbx_tex, b"TextureName", fbx_name_class(sock_name.encode(), b"Texture"))
1400 elem_data_single_string(fbx_tex, b"Media", fbx_name_class(img.name.encode(), b"Video"))
1401 elem_data_single_string_unicode(fbx_tex, b"FileName", fname_abs)
1402 elem_data_single_string_unicode(fbx_tex, b"RelativeFilename", fname_rel)
1404 alpha_source = 0 # None
1405 if img.alpha_mode != 'NONE':
1406 # ~ if tex.texture.use_calculate_alpha:
1407 # ~ alpha_source = 1 # RGBIntensity as alpha.
1408 # ~ else:
1409 # ~ alpha_source = 2 # Black, i.e. alpha channel.
1410 alpha_source = 2 # Black, i.e. alpha channel.
1411 # BlendMode not useful for now, only affects layered textures afaics.
1412 mapping = 0 # UV.
1413 uvset = None
1414 if tex.texcoords == 'ORCO': # XXX Others?
1415 if tex.projection == 'FLAT':
1416 mapping = 1 # Planar
1417 elif tex.projection == 'CUBE':
1418 mapping = 4 # Box
1419 elif tex.projection == 'TUBE':
1420 mapping = 3 # Cylindrical
1421 elif tex.projection == 'SPHERE':
1422 mapping = 2 # Spherical
1423 elif tex.texcoords == 'UV':
1424 mapping = 0 # UV
1425 # Yuck, UVs are linked by mere names it seems... :/
1426 # XXX TODO how to get that now???
1427 # uvset = tex.uv_layer
1428 wrap_mode = 1 # Clamp
1429 if tex.extension == 'REPEAT':
1430 wrap_mode = 0 # Repeat
1432 tmpl = elem_props_template_init(scene_data.templates, b"TextureFile")
1433 props = elem_properties(fbx_tex)
1434 elem_props_template_set(tmpl, props, "p_enum", b"AlphaSource", alpha_source)
1435 elem_props_template_set(tmpl, props, "p_bool", b"PremultiplyAlpha",
1436 img.alpha_mode in {'STRAIGHT'}) # Or is it PREMUL?
1437 elem_props_template_set(tmpl, props, "p_enum", b"CurrentMappingType", mapping)
1438 if uvset is not None:
1439 elem_props_template_set(tmpl, props, "p_string", b"UVSet", uvset)
1440 elem_props_template_set(tmpl, props, "p_enum", b"WrapModeU", wrap_mode)
1441 elem_props_template_set(tmpl, props, "p_enum", b"WrapModeV", wrap_mode)
1442 elem_props_template_set(tmpl, props, "p_vector_3d", b"Translation", tex.translation)
1443 elem_props_template_set(tmpl, props, "p_vector_3d", b"Rotation", (-r for r in tex.rotation))
1444 elem_props_template_set(tmpl, props, "p_vector_3d", b"Scaling", (((1.0 / s) if s != 0.0 else 1.0) for s in tex.scale))
1445 # UseMaterial should always be ON imho.
1446 elem_props_template_set(tmpl, props, "p_bool", b"UseMaterial", True)
1447 elem_props_template_set(tmpl, props, "p_bool", b"UseMipMap", False)
1448 elem_props_template_finalize(tmpl, props)
1450 # No custom properties, since that's not a data-block anymore.
1453 def fbx_data_video_elements(root, vid, scene_data):
1455 Write the actual image data block.
1457 msetts = scene_data.settings.media_settings
1459 vid_key, _texs = scene_data.data_videos[vid]
1460 fname_abs, fname_rel = _gen_vid_path(vid, scene_data)
1462 fbx_vid = elem_data_single_int64(root, b"Video", get_fbx_uuid_from_key(vid_key))
1463 fbx_vid.add_string(fbx_name_class(vid.name.encode(), b"Video"))
1464 fbx_vid.add_string(b"Clip")
1466 elem_data_single_string(fbx_vid, b"Type", b"Clip")
1467 # XXX No Version???
1469 tmpl = elem_props_template_init(scene_data.templates, b"Video")
1470 props = elem_properties(fbx_vid)
1471 elem_props_template_set(tmpl, props, "p_string_url", b"Path", fname_abs)
1472 elem_props_template_finalize(tmpl, props)
1474 elem_data_single_int32(fbx_vid, b"UseMipMap", 0)
1475 elem_data_single_string_unicode(fbx_vid, b"Filename", fname_abs)
1476 elem_data_single_string_unicode(fbx_vid, b"RelativeFilename", fname_rel)
1478 if scene_data.settings.media_settings.embed_textures:
1479 if vid.packed_file is not None:
1480 # We only ever embed a given file once!
1481 if fname_abs not in msetts.embedded_set:
1482 elem_data_single_bytes(fbx_vid, b"Content", vid.packed_file.data)
1483 msetts.embedded_set.add(fname_abs)
1484 else:
1485 filepath = bpy.path.abspath(vid.filepath)
1486 # We only ever embed a given file once!
1487 if filepath not in msetts.embedded_set:
1488 try:
1489 with open(filepath, 'br') as f:
1490 elem_data_single_bytes(fbx_vid, b"Content", f.read())
1491 except Exception as e:
1492 print("WARNING: embedding file {} failed ({})".format(filepath, e))
1493 elem_data_single_bytes(fbx_vid, b"Content", b"")
1494 msetts.embedded_set.add(filepath)
1495 # Looks like we'd rather not write any 'Content' element in this case (see T44442).
1496 # Sounds suspect, but let's try it!
1497 #~ else:
1498 #~ elem_data_single_bytes(fbx_vid, b"Content", b"")
1501 def fbx_data_armature_elements(root, arm_obj, scene_data):
1503 Write:
1504 * Bones "data" (NodeAttribute::LimbNode, contains pretty much nothing!).
1505 * Deformers (i.e. Skin), bind between an armature and a mesh.
1506 ** SubDeformers (i.e. Cluster), one per bone/vgroup pair.
1507 * BindPose.
1508 Note armature itself has no data, it is a mere "Null" Model...
1510 mat_world_arm = arm_obj.fbx_object_matrix(scene_data, global_space=True)
1511 bones = tuple(bo_obj for bo_obj in arm_obj.bones if bo_obj in scene_data.objects)
1513 bone_radius_scale = 33.0
1515 # Bones "data".
1516 for bo_obj in bones:
1517 bo = bo_obj.bdata
1518 bo_data_key = scene_data.data_bones[bo_obj]
1519 fbx_bo = elem_data_single_int64(root, b"NodeAttribute", get_fbx_uuid_from_key(bo_data_key))
1520 fbx_bo.add_string(fbx_name_class(bo.name.encode(), b"NodeAttribute"))
1521 fbx_bo.add_string(b"LimbNode")
1522 elem_data_single_string(fbx_bo, b"TypeFlags", b"Skeleton")
1524 tmpl = elem_props_template_init(scene_data.templates, b"Bone")
1525 props = elem_properties(fbx_bo)
1526 elem_props_template_set(tmpl, props, "p_double", b"Size", bo.head_radius * bone_radius_scale)
1527 elem_props_template_finalize(tmpl, props)
1529 # Custom properties.
1530 if scene_data.settings.use_custom_props:
1531 fbx_data_element_custom_properties(props, bo)
1533 # Store Blender bone length - XXX Not much useful actually :/
1534 # (LimbLength can't be used because it is a scale factor 0-1 for the parent-child distance:
1535 # http://docs.autodesk.com/FBX/2014/ENU/FBX-SDK-Documentation/cpp_ref/class_fbx_skeleton.html#a9bbe2a70f4ed82cd162620259e649f0f )
1536 # elem_props_set(props, "p_double", "BlenderBoneLength".encode(), (bo.tail_local - bo.head_local).length, custom=True)
1538 # Skin deformers and BindPoses.
1539 # Note: we might also use Deformers for our "parent to vertex" stuff???
1540 deformer = scene_data.data_deformers_skin.get(arm_obj, None)
1541 if deformer is not None:
1542 for me, (skin_key, ob_obj, clusters) in deformer.items():
1543 # BindPose.
1544 mat_world_obj, mat_world_bones = fbx_data_bindpose_element(root, ob_obj, me, scene_data,
1545 arm_obj, mat_world_arm, bones)
1547 # Deformer.
1548 fbx_skin = elem_data_single_int64(root, b"Deformer", get_fbx_uuid_from_key(skin_key))
1549 fbx_skin.add_string(fbx_name_class(arm_obj.name.encode(), b"Deformer"))
1550 fbx_skin.add_string(b"Skin")
1552 elem_data_single_int32(fbx_skin, b"Version", FBX_DEFORMER_SKIN_VERSION)
1553 elem_data_single_float64(fbx_skin, b"Link_DeformAcuracy", 50.0) # Only vague idea what it is...
1555 # Pre-process vertex weights (also to check vertices assigned to more than four bones).
1556 ob = ob_obj.bdata
1557 bo_vg_idx = {bo_obj.bdata.name: ob.vertex_groups[bo_obj.bdata.name].index
1558 for bo_obj in clusters.keys() if bo_obj.bdata.name in ob.vertex_groups}
1559 valid_idxs = set(bo_vg_idx.values())
1560 vgroups = {vg.index: {} for vg in ob.vertex_groups}
1561 verts_vgroups = (sorted(((vg.group, vg.weight) for vg in v.groups if vg.weight and vg.group in valid_idxs),
1562 key=lambda e: e[1], reverse=True)
1563 for v in me.vertices)
1564 for idx, vgs in enumerate(verts_vgroups):
1565 for vg_idx, w in vgs:
1566 vgroups[vg_idx][idx] = w
1568 for bo_obj, clstr_key in clusters.items():
1569 bo = bo_obj.bdata
1570 # Find which vertices are affected by this bone/vgroup pair, and matching weights.
1571 # Note we still write a cluster for bones not affecting the mesh, to get 'rest pose' data
1572 # (the TransformBlah matrices).
1573 vg_idx = bo_vg_idx.get(bo.name, None)
1574 indices, weights = ((), ()) if vg_idx is None or not vgroups[vg_idx] else zip(*vgroups[vg_idx].items())
1576 # Create the cluster.
1577 fbx_clstr = elem_data_single_int64(root, b"Deformer", get_fbx_uuid_from_key(clstr_key))
1578 fbx_clstr.add_string(fbx_name_class(bo.name.encode(), b"SubDeformer"))
1579 fbx_clstr.add_string(b"Cluster")
1581 elem_data_single_int32(fbx_clstr, b"Version", FBX_DEFORMER_CLUSTER_VERSION)
1582 # No idea what that user data might be...
1583 fbx_userdata = elem_data_single_string(fbx_clstr, b"UserData", b"")
1584 fbx_userdata.add_string(b"")
1585 if indices:
1586 elem_data_single_int32_array(fbx_clstr, b"Indexes", indices)
1587 elem_data_single_float64_array(fbx_clstr, b"Weights", weights)
1588 # Transform, TransformLink and TransformAssociateModel matrices...
1589 # They seem to be doublons of BindPose ones??? Have armature (associatemodel) in addition, though.
1590 # WARNING! Even though official FBX API presents Transform in global space,
1591 # **it is stored in bone space in FBX data!** See:
1592 # http://area.autodesk.com/forum/autodesk-fbx/fbx-sdk/why-the-values-return-
1593 # by-fbxcluster-gettransformmatrix-x-not-same-with-the-value-in-ascii-fbx-file/
1594 elem_data_single_float64_array(fbx_clstr, b"Transform",
1595 matrix4_to_array(mat_world_bones[bo_obj].inverted_safe() @ mat_world_obj))
1596 elem_data_single_float64_array(fbx_clstr, b"TransformLink", matrix4_to_array(mat_world_bones[bo_obj]))
1597 elem_data_single_float64_array(fbx_clstr, b"TransformAssociateModel", matrix4_to_array(mat_world_arm))
1600 def fbx_data_leaf_bone_elements(root, scene_data):
1601 # Write a dummy leaf bone that is used by applications to show the length of the last bone in a chain
1602 for (node_name, _par_uuid, node_uuid, attr_uuid, matrix, hide, size) in scene_data.data_leaf_bones:
1603 # Bone 'data'...
1604 fbx_bo = elem_data_single_int64(root, b"NodeAttribute", attr_uuid)
1605 fbx_bo.add_string(fbx_name_class(node_name.encode(), b"NodeAttribute"))
1606 fbx_bo.add_string(b"LimbNode")
1607 elem_data_single_string(fbx_bo, b"TypeFlags", b"Skeleton")
1609 tmpl = elem_props_template_init(scene_data.templates, b"Bone")
1610 props = elem_properties(fbx_bo)
1611 elem_props_template_set(tmpl, props, "p_double", b"Size", size)
1612 elem_props_template_finalize(tmpl, props)
1614 # And bone object.
1615 model = elem_data_single_int64(root, b"Model", node_uuid)
1616 model.add_string(fbx_name_class(node_name.encode(), b"Model"))
1617 model.add_string(b"LimbNode")
1619 elem_data_single_int32(model, b"Version", FBX_MODELS_VERSION)
1621 # Object transform info.
1622 loc, rot, scale = matrix.decompose()
1623 rot = rot.to_euler('XYZ')
1624 rot = tuple(convert_rad_to_deg_iter(rot))
1626 tmpl = elem_props_template_init(scene_data.templates, b"Model")
1627 # For now add only loc/rot/scale...
1628 props = elem_properties(model)
1629 # Generated leaf bones are obviously never animated!
1630 elem_props_template_set(tmpl, props, "p_lcl_translation", b"Lcl Translation", loc)
1631 elem_props_template_set(tmpl, props, "p_lcl_rotation", b"Lcl Rotation", rot)
1632 elem_props_template_set(tmpl, props, "p_lcl_scaling", b"Lcl Scaling", scale)
1633 elem_props_template_set(tmpl, props, "p_visibility", b"Visibility", float(not hide))
1635 # Absolutely no idea what this is, but seems mandatory for validity of the file, and defaults to
1636 # invalid -1 value...
1637 elem_props_template_set(tmpl, props, "p_integer", b"DefaultAttributeIndex", 0)
1639 elem_props_template_set(tmpl, props, "p_enum", b"InheritType", 1) # RSrs
1641 # Those settings would obviously need to be edited in a complete version of the exporter, may depends on
1642 # object type, etc.
1643 elem_data_single_int32(model, b"MultiLayer", 0)
1644 elem_data_single_int32(model, b"MultiTake", 0)
1645 elem_data_single_bool(model, b"Shading", True)
1646 elem_data_single_string(model, b"Culling", b"CullingOff")
1648 elem_props_template_finalize(tmpl, props)
1651 def fbx_data_object_elements(root, ob_obj, scene_data):
1653 Write the Object (Model) data blocks.
1654 Note this "Model" can also be bone or dupli!
1656 obj_type = b"Null" # default, sort of empty...
1657 if ob_obj.is_bone:
1658 obj_type = b"LimbNode"
1659 elif (ob_obj.type == 'ARMATURE'):
1660 if scene_data.settings.armature_nodetype == 'ROOT':
1661 obj_type = b"Root"
1662 elif scene_data.settings.armature_nodetype == 'LIMBNODE':
1663 obj_type = b"LimbNode"
1664 else: # Default, preferred option...
1665 obj_type = b"Null"
1666 elif (ob_obj.type in BLENDER_OBJECT_TYPES_MESHLIKE):
1667 obj_type = b"Mesh"
1668 elif (ob_obj.type == 'LIGHT'):
1669 obj_type = b"Light"
1670 elif (ob_obj.type == 'CAMERA'):
1671 obj_type = b"Camera"
1672 model = elem_data_single_int64(root, b"Model", ob_obj.fbx_uuid)
1673 model.add_string(fbx_name_class(ob_obj.name.encode(), b"Model"))
1674 model.add_string(obj_type)
1676 elem_data_single_int32(model, b"Version", FBX_MODELS_VERSION)
1678 # Object transform info.
1679 loc, rot, scale, matrix, matrix_rot = ob_obj.fbx_object_tx(scene_data)
1680 rot = tuple(convert_rad_to_deg_iter(rot))
1682 tmpl = elem_props_template_init(scene_data.templates, b"Model")
1683 # For now add only loc/rot/scale...
1684 props = elem_properties(model)
1685 elem_props_template_set(tmpl, props, "p_lcl_translation", b"Lcl Translation", loc,
1686 animatable=True, animated=((ob_obj.key, "Lcl Translation") in scene_data.animated))
1687 elem_props_template_set(tmpl, props, "p_lcl_rotation", b"Lcl Rotation", rot,
1688 animatable=True, animated=((ob_obj.key, "Lcl Rotation") in scene_data.animated))
1689 elem_props_template_set(tmpl, props, "p_lcl_scaling", b"Lcl Scaling", scale,
1690 animatable=True, animated=((ob_obj.key, "Lcl Scaling") in scene_data.animated))
1691 elem_props_template_set(tmpl, props, "p_visibility", b"Visibility", float(not ob_obj.hide))
1693 # Absolutely no idea what this is, but seems mandatory for validity of the file, and defaults to
1694 # invalid -1 value...
1695 elem_props_template_set(tmpl, props, "p_integer", b"DefaultAttributeIndex", 0)
1697 elem_props_template_set(tmpl, props, "p_enum", b"InheritType", 1) # RSrs
1699 # Custom properties.
1700 if scene_data.settings.use_custom_props:
1701 # Here we want customprops from the 'pose' bone, not the 'edit' bone...
1702 bdata = ob_obj.bdata_pose_bone if ob_obj.is_bone else ob_obj.bdata
1703 fbx_data_element_custom_properties(props, bdata)
1705 # Those settings would obviously need to be edited in a complete version of the exporter, may depends on
1706 # object type, etc.
1707 elem_data_single_int32(model, b"MultiLayer", 0)
1708 elem_data_single_int32(model, b"MultiTake", 0)
1709 elem_data_single_bool(model, b"Shading", True)
1710 elem_data_single_string(model, b"Culling", b"CullingOff")
1712 if obj_type == b"Camera":
1713 # Why, oh why are FBX cameras such a mess???
1714 # And WHY add camera data HERE??? Not even sure this is needed...
1715 render = scene_data.scene.render
1716 width = render.resolution_x * 1.0
1717 height = render.resolution_y * 1.0
1718 elem_props_template_set(tmpl, props, "p_enum", b"ResolutionMode", 0) # Don't know what it means
1719 elem_props_template_set(tmpl, props, "p_double", b"AspectW", width)
1720 elem_props_template_set(tmpl, props, "p_double", b"AspectH", height)
1721 elem_props_template_set(tmpl, props, "p_bool", b"ViewFrustum", True)
1722 elem_props_template_set(tmpl, props, "p_enum", b"BackgroundMode", 0) # Don't know what it means
1723 elem_props_template_set(tmpl, props, "p_bool", b"ForegroundTransparent", True)
1725 elem_props_template_finalize(tmpl, props)
1728 def fbx_data_animation_elements(root, scene_data):
1730 Write animation data.
1732 animations = scene_data.animations
1733 if not animations:
1734 return
1735 scene = scene_data.scene
1737 fps = scene.render.fps / scene.render.fps_base
1739 def keys_to_ktimes(keys):
1740 return (int(v) for v in convert_sec_to_ktime_iter((f / fps for f, _v in keys)))
1742 # Animation stacks.
1743 for astack_key, alayers, alayer_key, name, f_start, f_end in animations:
1744 astack = elem_data_single_int64(root, b"AnimationStack", get_fbx_uuid_from_key(astack_key))
1745 astack.add_string(fbx_name_class(name, b"AnimStack"))
1746 astack.add_string(b"")
1748 astack_tmpl = elem_props_template_init(scene_data.templates, b"AnimationStack")
1749 astack_props = elem_properties(astack)
1750 r = scene_data.scene.render
1751 fps = r.fps / r.fps_base
1752 start = int(convert_sec_to_ktime(f_start / fps))
1753 end = int(convert_sec_to_ktime(f_end / fps))
1754 elem_props_template_set(astack_tmpl, astack_props, "p_timestamp", b"LocalStart", start)
1755 elem_props_template_set(astack_tmpl, astack_props, "p_timestamp", b"LocalStop", end)
1756 elem_props_template_set(astack_tmpl, astack_props, "p_timestamp", b"ReferenceStart", start)
1757 elem_props_template_set(astack_tmpl, astack_props, "p_timestamp", b"ReferenceStop", end)
1758 elem_props_template_finalize(astack_tmpl, astack_props)
1760 # For now, only one layer for all animations.
1761 alayer = elem_data_single_int64(root, b"AnimationLayer", get_fbx_uuid_from_key(alayer_key))
1762 alayer.add_string(fbx_name_class(name, b"AnimLayer"))
1763 alayer.add_string(b"")
1765 for ob_obj, (alayer_key, acurvenodes) in alayers.items():
1766 # Animation layer.
1767 # alayer = elem_data_single_int64(root, b"AnimationLayer", get_fbx_uuid_from_key(alayer_key))
1768 # alayer.add_string(fbx_name_class(ob_obj.name.encode(), b"AnimLayer"))
1769 # alayer.add_string(b"")
1771 for fbx_prop, (acurvenode_key, acurves, acurvenode_name) in acurvenodes.items():
1772 # Animation curve node.
1773 acurvenode = elem_data_single_int64(root, b"AnimationCurveNode", get_fbx_uuid_from_key(acurvenode_key))
1774 acurvenode.add_string(fbx_name_class(acurvenode_name.encode(), b"AnimCurveNode"))
1775 acurvenode.add_string(b"")
1777 acn_tmpl = elem_props_template_init(scene_data.templates, b"AnimationCurveNode")
1778 acn_props = elem_properties(acurvenode)
1780 for fbx_item, (acurve_key, def_value, keys, _acurve_valid) in acurves.items():
1781 elem_props_template_set(acn_tmpl, acn_props, "p_number", fbx_item.encode(),
1782 def_value, animatable=True)
1784 # Only create Animation curve if needed!
1785 if keys:
1786 acurve = elem_data_single_int64(root, b"AnimationCurve", get_fbx_uuid_from_key(acurve_key))
1787 acurve.add_string(fbx_name_class(b"", b"AnimCurve"))
1788 acurve.add_string(b"")
1790 # key attributes...
1791 nbr_keys = len(keys)
1792 # flags...
1793 keyattr_flags = (
1794 1 << 2 | # interpolation mode, 1 = constant, 2 = linear, 3 = cubic.
1795 1 << 8 | # tangent mode, 8 = auto, 9 = TCB, 10 = user, 11 = generic break,
1796 1 << 13 | # tangent mode, 12 = generic clamp, 13 = generic time independent,
1797 1 << 14 | # tangent mode, 13 + 14 = generic clamp progressive.
1800 # Maybe values controlling TCB & co???
1801 keyattr_datafloat = (0.0, 0.0, 9.419963346924634e-30, 0.0)
1803 # And now, the *real* data!
1804 elem_data_single_float64(acurve, b"Default", def_value)
1805 elem_data_single_int32(acurve, b"KeyVer", FBX_ANIM_KEY_VERSION)
1806 elem_data_single_int64_array(acurve, b"KeyTime", keys_to_ktimes(keys))
1807 elem_data_single_float32_array(acurve, b"KeyValueFloat", (v for _f, v in keys))
1808 elem_data_single_int32_array(acurve, b"KeyAttrFlags", keyattr_flags)
1809 elem_data_single_float32_array(acurve, b"KeyAttrDataFloat", keyattr_datafloat)
1810 elem_data_single_int32_array(acurve, b"KeyAttrRefCount", (nbr_keys,))
1812 elem_props_template_finalize(acn_tmpl, acn_props)
1815 # ##### Top-level FBX data container. #####
1817 # Mapping Blender -> FBX (principled_socket_name, fbx_name).
1818 PRINCIPLED_TEXTURE_SOCKETS_TO_FBX = (
1819 # ("diffuse", "diffuse", b"DiffuseFactor"),
1820 ("base_color_texture", b"DiffuseColor"),
1821 ("alpha_texture", b"TransparencyFactor"), # Will be inverted in fact, not much we can do really...
1822 # ("base_color_texture", b"TransparentColor"), # Uses diffuse color in Blender!
1823 ("emission_strength_texture", b"EmissiveFactor"),
1824 ("emission_color_texture", b"EmissiveColor"),
1825 # ("ambient", "ambient", b"AmbientFactor"),
1826 # ("", "", b"AmbientColor"), # World stuff in Blender, for now ignore...
1827 ("normalmap_texture", b"NormalMap"),
1828 # Note: unsure about those... :/
1829 # ("", "", b"Bump"),
1830 # ("", "", b"BumpFactor"),
1831 # ("", "", b"DisplacementColor"),
1832 # ("", "", b"DisplacementFactor"),
1833 ("specular_texture", b"SpecularFactor"),
1834 # ("base_color", b"SpecularColor"), # TODO: use tint?
1835 # See Material template about those two!
1836 ("roughness_texture", b"Shininess"),
1837 ("roughness_texture", b"ShininessExponent"),
1838 # ("mirror", "mirror", b"ReflectionColor"),
1839 ("metallic_texture", b"ReflectionFactor"),
1843 def fbx_skeleton_from_armature(scene, settings, arm_obj, objects, data_meshes,
1844 data_bones, data_deformers_skin, data_empties, arm_parents):
1846 Create skeleton from armature/bones (NodeAttribute/LimbNode and Model/LimbNode), and for each deformed mesh,
1847 create Pose/BindPose(with sub PoseNode) and Deformer/Skin(with Deformer/SubDeformer/Cluster).
1848 Also supports "parent to bone" (simple parent to Model/LimbNode).
1849 arm_parents is a set of tuples (armature, object) for all successful armature bindings.
1851 # We need some data for our armature 'object' too!!!
1852 data_empties[arm_obj] = get_blender_empty_key(arm_obj.bdata)
1854 arm_data = arm_obj.bdata.data
1855 bones = {}
1856 for bo in arm_obj.bones:
1857 if settings.use_armature_deform_only:
1858 if bo.bdata.use_deform:
1859 bones[bo] = True
1860 bo_par = bo.parent
1861 while bo_par.is_bone:
1862 bones[bo_par] = True
1863 bo_par = bo_par.parent
1864 elif bo not in bones: # Do not override if already set in the loop above!
1865 bones[bo] = False
1866 else:
1867 bones[bo] = True
1869 bones = {bo: None for bo, use in bones.items() if use}
1871 if not bones:
1872 return
1874 data_bones.update((bo, get_blender_bone_key(arm_obj.bdata, bo.bdata)) for bo in bones)
1876 for ob_obj in objects:
1877 if not ob_obj.is_deformed_by_armature(arm_obj):
1878 continue
1880 # Always handled by an Armature modifier...
1881 found = False
1882 for mod in ob_obj.bdata.modifiers:
1883 if mod.type not in {'ARMATURE'} or not mod.object:
1884 continue
1885 # We only support vertex groups binding method, not bone envelopes one!
1886 if mod.object == arm_obj.bdata and mod.use_vertex_groups:
1887 found = True
1888 break
1890 if not found:
1891 continue
1893 # Now we have a mesh using this armature.
1894 # Note: bindpose have no relations at all (no connections), so no need for any preprocess for them.
1895 # Create skin & clusters relations (note skins are connected to geometry, *not* model!).
1896 _key, me, _free = data_meshes[ob_obj]
1897 clusters = {bo: get_blender_bone_cluster_key(arm_obj.bdata, me, bo.bdata) for bo in bones}
1898 data_deformers_skin.setdefault(arm_obj, {})[me] = (get_blender_armature_skin_key(arm_obj.bdata, me),
1899 ob_obj, clusters)
1901 # We don't want a regular parent relationship for those in FBX...
1902 arm_parents.add((arm_obj, ob_obj))
1903 # Needed to handle matrices/spaces (since we do not parent them to 'armature' in FBX :/ ).
1904 ob_obj.parented_to_armature = True
1906 objects.update(bones)
1909 def fbx_generate_leaf_bones(settings, data_bones):
1910 # find which bons have no children
1911 child_count = {bo: 0 for bo in data_bones.keys()}
1912 for bo in data_bones.keys():
1913 if bo.parent and bo.parent.is_bone:
1914 child_count[bo.parent] += 1
1916 bone_radius_scale = settings.global_scale * 33.0
1918 # generate bone data
1919 leaf_parents = [bo for bo, count in child_count.items() if count == 0]
1920 leaf_bones = []
1921 for parent in leaf_parents:
1922 node_name = parent.name + "_end"
1923 parent_uuid = parent.fbx_uuid
1924 parent_key = parent.key
1925 node_uuid = get_fbx_uuid_from_key(parent_key + "_end_node")
1926 attr_uuid = get_fbx_uuid_from_key(parent_key + "_end_nodeattr")
1928 hide = parent.hide
1929 size = parent.bdata.head_radius * bone_radius_scale
1930 bone_length = (parent.bdata.tail_local - parent.bdata.head_local).length
1931 matrix = Matrix.Translation((0, bone_length, 0))
1932 if settings.bone_correction_matrix_inv:
1933 matrix = settings.bone_correction_matrix_inv @ matrix
1934 if settings.bone_correction_matrix:
1935 matrix = matrix @ settings.bone_correction_matrix
1936 leaf_bones.append((node_name, parent_uuid, node_uuid, attr_uuid, matrix, hide, size))
1938 return leaf_bones
1941 def fbx_animations_do(scene_data, ref_id, f_start, f_end, start_zero, objects=None, force_keep=False):
1943 Generate animation data (a single AnimStack) from objects, for a given frame range.
1945 bake_step = scene_data.settings.bake_anim_step
1946 simplify_fac = scene_data.settings.bake_anim_simplify_factor
1947 scene = scene_data.scene
1948 depsgraph = scene_data.depsgraph
1949 force_keying = scene_data.settings.bake_anim_use_all_bones
1950 force_sek = scene_data.settings.bake_anim_force_startend_keying
1951 gscale = scene_data.settings.global_scale
1953 if objects is not None:
1954 # Add bones and duplis!
1955 for ob_obj in tuple(objects):
1956 if not ob_obj.is_object:
1957 continue
1958 if ob_obj.type == 'ARMATURE':
1959 objects |= {bo_obj for bo_obj in ob_obj.bones if bo_obj in scene_data.objects}
1960 for dp_obj in ob_obj.dupli_list_gen(depsgraph):
1961 if dp_obj in scene_data.objects:
1962 objects.add(dp_obj)
1963 else:
1964 objects = scene_data.objects
1966 back_currframe = scene.frame_current
1967 animdata_ob = {}
1968 p_rots = {}
1970 for ob_obj in objects:
1971 if ob_obj.parented_to_armature:
1972 continue
1973 ACNW = AnimationCurveNodeWrapper
1974 loc, rot, scale, _m, _mr = ob_obj.fbx_object_tx(scene_data)
1975 rot_deg = tuple(convert_rad_to_deg_iter(rot))
1976 force_key = (simplify_fac == 0.0) or (ob_obj.is_bone and force_keying)
1977 animdata_ob[ob_obj] = (ACNW(ob_obj.key, 'LCL_TRANSLATION', force_key, force_sek, loc),
1978 ACNW(ob_obj.key, 'LCL_ROTATION', force_key, force_sek, rot_deg),
1979 ACNW(ob_obj.key, 'LCL_SCALING', force_key, force_sek, scale))
1980 p_rots[ob_obj] = rot
1982 force_key = (simplify_fac == 0.0)
1983 animdata_shapes = {}
1985 for me, (me_key, _shapes_key, shapes) in scene_data.data_deformers_shape.items():
1986 # Ignore absolute shape keys for now!
1987 if not me.shape_keys.use_relative:
1988 continue
1989 for shape, (channel_key, geom_key, _shape_verts_co, _shape_verts_idx) in shapes.items():
1990 acnode = AnimationCurveNodeWrapper(channel_key, 'SHAPE_KEY', force_key, force_sek, (0.0,))
1991 # Sooooo happy to have to twist again like a mad snake... Yes, we need to write those curves twice. :/
1992 acnode.add_group(me_key, shape.name, shape.name, (shape.name,))
1993 animdata_shapes[channel_key] = (acnode, me, shape)
1995 animdata_cameras = {}
1996 for cam_obj, cam_key in scene_data.data_cameras.items():
1997 cam = cam_obj.bdata.data
1998 acnode_lens = AnimationCurveNodeWrapper(cam_key, 'CAMERA_FOCAL', force_key, force_sek, (cam.lens,))
1999 acnode_focus_distance = AnimationCurveNodeWrapper(cam_key, 'CAMERA_FOCUS_DISTANCE', force_key,
2000 force_sek, (cam.dof.focus_distance,))
2001 animdata_cameras[cam_key] = (acnode_lens, acnode_focus_distance, cam)
2003 currframe = f_start
2004 while currframe <= f_end:
2005 real_currframe = currframe - f_start if start_zero else currframe
2006 scene.frame_set(int(currframe), subframe=currframe - int(currframe))
2008 for dp_obj in ob_obj.dupli_list_gen(depsgraph):
2009 pass # Merely updating dupli matrix of ObjectWrapper...
2010 for ob_obj, (anim_loc, anim_rot, anim_scale) in animdata_ob.items():
2011 # We compute baked loc/rot/scale for all objects (rot being euler-compat with previous value!).
2012 p_rot = p_rots.get(ob_obj, None)
2013 loc, rot, scale, _m, _mr = ob_obj.fbx_object_tx(scene_data, rot_euler_compat=p_rot)
2014 p_rots[ob_obj] = rot
2015 anim_loc.add_keyframe(real_currframe, loc)
2016 anim_rot.add_keyframe(real_currframe, tuple(convert_rad_to_deg_iter(rot)))
2017 anim_scale.add_keyframe(real_currframe, scale)
2018 for anim_shape, me, shape in animdata_shapes.values():
2019 anim_shape.add_keyframe(real_currframe, (shape.value * 100.0,))
2020 for anim_camera_lens, anim_camera_focus_distance, camera in animdata_cameras.values():
2021 anim_camera_lens.add_keyframe(real_currframe, (camera.lens,))
2022 anim_camera_focus_distance.add_keyframe(real_currframe, (camera.dof.focus_distance * 1000 * gscale,))
2023 currframe += bake_step
2025 scene.frame_set(back_currframe, subframe=0.0)
2027 animations = {}
2029 # And now, produce final data (usable by FBX export code)
2030 # Objects-like loc/rot/scale...
2031 for ob_obj, anims in animdata_ob.items():
2032 for anim in anims:
2033 anim.simplify(simplify_fac, bake_step, force_keep)
2034 if not anim:
2035 continue
2036 for obj_key, group_key, group, fbx_group, fbx_gname in anim.get_final_data(scene, ref_id, force_keep):
2037 anim_data = animations.setdefault(obj_key, ("dummy_unused_key", {}))
2038 anim_data[1][fbx_group] = (group_key, group, fbx_gname)
2040 # And meshes' shape keys.
2041 for channel_key, (anim_shape, me, shape) in animdata_shapes.items():
2042 final_keys = {}
2043 anim_shape.simplify(simplify_fac, bake_step, force_keep)
2044 if not anim_shape:
2045 continue
2046 for elem_key, group_key, group, fbx_group, fbx_gname in anim_shape.get_final_data(scene, ref_id, force_keep):
2047 anim_data = animations.setdefault(elem_key, ("dummy_unused_key", {}))
2048 anim_data[1][fbx_group] = (group_key, group, fbx_gname)
2050 # And cameras' lens and focus distance keys.
2051 for cam_key, (anim_camera_lens, anim_camera_focus_distance, camera) in animdata_cameras.items():
2052 final_keys = {}
2053 anim_camera_lens.simplify(simplify_fac, bake_step, force_keep)
2054 anim_camera_focus_distance.simplify(simplify_fac, bake_step, force_keep)
2055 if anim_camera_lens:
2056 for elem_key, group_key, group, fbx_group, fbx_gname in \
2057 anim_camera_lens.get_final_data(scene, ref_id, force_keep):
2058 anim_data = animations.setdefault(elem_key, ("dummy_unused_key", {}))
2059 anim_data[1][fbx_group] = (group_key, group, fbx_gname)
2060 if anim_camera_focus_distance:
2061 for elem_key, group_key, group, fbx_group, fbx_gname in \
2062 anim_camera_focus_distance.get_final_data(scene, ref_id, force_keep):
2063 anim_data = animations.setdefault(elem_key, ("dummy_unused_key", {}))
2064 anim_data[1][fbx_group] = (group_key, group, fbx_gname)
2066 astack_key = get_blender_anim_stack_key(scene, ref_id)
2067 alayer_key = get_blender_anim_layer_key(scene, ref_id)
2068 name = (get_blenderID_name(ref_id) if ref_id else scene.name).encode()
2070 if start_zero:
2071 f_end -= f_start
2072 f_start = 0.0
2074 return (astack_key, animations, alayer_key, name, f_start, f_end) if animations else None
2077 def fbx_animations(scene_data):
2079 Generate global animation data from objects.
2081 scene = scene_data.scene
2082 animations = []
2083 animated = set()
2084 frame_start = 1e100
2085 frame_end = -1e100
2087 def add_anim(animations, animated, anim):
2088 nonlocal frame_start, frame_end
2089 if anim is not None:
2090 animations.append(anim)
2091 f_start, f_end = anim[4:6]
2092 if f_start < frame_start:
2093 frame_start = f_start
2094 if f_end > frame_end:
2095 frame_end = f_end
2097 _astack_key, astack, _alayer_key, _name, _fstart, _fend = anim
2098 for elem_key, (alayer_key, acurvenodes) in astack.items():
2099 for fbx_prop, (acurvenode_key, acurves, acurvenode_name) in acurvenodes.items():
2100 animated.add((elem_key, fbx_prop))
2102 # Per-NLA strip animstacks.
2103 if scene_data.settings.bake_anim_use_nla_strips:
2104 strips = []
2105 ob_actions = []
2106 for ob_obj in scene_data.objects:
2107 # NLA tracks only for objects, not bones!
2108 if not ob_obj.is_object:
2109 continue
2110 ob = ob_obj.bdata # Back to real Blender Object.
2111 if not ob.animation_data:
2112 continue
2114 # Some actions are read-only, one cause is being in NLA tweakmode
2115 restore_use_tweak_mode = ob.animation_data.use_tweak_mode
2116 if ob.animation_data.is_property_readonly('action'):
2117 ob.animation_data.use_tweak_mode = False
2119 # We have to remove active action from objects, it overwrites strips actions otherwise...
2120 ob_actions.append((ob, ob.animation_data.action, restore_use_tweak_mode))
2121 ob.animation_data.action = None
2122 for track in ob.animation_data.nla_tracks:
2123 if track.mute:
2124 continue
2125 for strip in track.strips:
2126 if strip.mute:
2127 continue
2128 strips.append(strip)
2129 strip.mute = True
2131 for strip in strips:
2132 strip.mute = False
2133 add_anim(animations, animated,
2134 fbx_animations_do(scene_data, strip, strip.frame_start, strip.frame_end, True, force_keep=True))
2135 strip.mute = True
2136 scene.frame_set(scene.frame_current, subframe=0.0)
2138 for strip in strips:
2139 strip.mute = False
2141 for ob, ob_act, restore_use_tweak_mode in ob_actions:
2142 ob.animation_data.action = ob_act
2143 ob.animation_data.use_tweak_mode = restore_use_tweak_mode
2145 # All actions.
2146 if scene_data.settings.bake_anim_use_all_actions:
2147 def validate_actions(act, path_resolve):
2148 for fc in act.fcurves:
2149 data_path = fc.data_path
2150 if fc.array_index:
2151 data_path = data_path + "[%d]" % fc.array_index
2152 try:
2153 path_resolve(data_path)
2154 except ValueError:
2155 return False # Invalid.
2156 return True # Valid.
2158 def restore_object(ob_to, ob_from):
2159 # Restore org state of object (ugh :/ ).
2160 props = (
2161 'location', 'rotation_quaternion', 'rotation_axis_angle', 'rotation_euler', 'rotation_mode', 'scale',
2162 'delta_location', 'delta_rotation_euler', 'delta_rotation_quaternion', 'delta_scale',
2163 'lock_location', 'lock_rotation', 'lock_rotation_w', 'lock_rotations_4d', 'lock_scale',
2164 'tag', 'track_axis', 'up_axis', 'active_material', 'active_material_index',
2165 'matrix_parent_inverse', 'empty_display_type', 'empty_display_size', 'empty_image_offset', 'pass_index',
2166 'color', 'hide_viewport', 'hide_select', 'hide_render', 'instance_type',
2167 'use_instance_vertices_rotation', 'use_instance_faces_scale', 'instance_faces_scale',
2168 'display_type', 'show_bounds', 'display_bounds_type', 'show_name', 'show_axis', 'show_texture_space',
2169 'show_wire', 'show_all_edges', 'show_transparent', 'show_in_front',
2170 'show_only_shape_key', 'use_shape_key_edit_mode', 'active_shape_key_index',
2172 for p in props:
2173 if not ob_to.is_property_readonly(p):
2174 setattr(ob_to, p, getattr(ob_from, p))
2176 for ob_obj in scene_data.objects:
2177 # Actions only for objects, not bones!
2178 if not ob_obj.is_object:
2179 continue
2181 ob = ob_obj.bdata # Back to real Blender Object.
2183 if not ob.animation_data:
2184 continue # Do not export animations for objects that are absolutely not animated, see T44386.
2186 if ob.animation_data.is_property_readonly('action'):
2187 continue # Cannot re-assign 'active action' to this object (usually related to NLA usage, see T48089).
2189 # We can't play with animdata and actions and get back to org state easily.
2190 # So we have to add a temp copy of the object to the scene, animate it, and remove it... :/
2191 ob_copy = ob.copy()
2192 # Great, have to handle bones as well if needed...
2193 pbones_matrices = [pbo.matrix_basis.copy() for pbo in ob.pose.bones] if ob.type == 'ARMATURE' else ...
2195 org_act = ob.animation_data.action
2196 path_resolve = ob.path_resolve
2198 for act in bpy.data.actions:
2199 # For now, *all* paths in the action must be valid for the object, to validate the action.
2200 # Unless that action was already assigned to the object!
2201 if act != org_act and not validate_actions(act, path_resolve):
2202 continue
2203 ob.animation_data.action = act
2204 frame_start, frame_end = act.frame_range # sic!
2205 add_anim(animations, animated,
2206 fbx_animations_do(scene_data, (ob, act), frame_start, frame_end, True,
2207 objects={ob_obj}, force_keep=True))
2208 # Ugly! :/
2209 if pbones_matrices is not ...:
2210 for pbo, mat in zip(ob.pose.bones, pbones_matrices):
2211 pbo.matrix_basis = mat.copy()
2212 ob.animation_data.action = org_act
2213 restore_object(ob, ob_copy)
2214 scene.frame_set(scene.frame_current, subframe=0.0)
2216 if pbones_matrices is not ...:
2217 for pbo, mat in zip(ob.pose.bones, pbones_matrices):
2218 pbo.matrix_basis = mat.copy()
2219 ob.animation_data.action = org_act
2221 bpy.data.objects.remove(ob_copy)
2222 scene.frame_set(scene.frame_current, subframe=0.0)
2224 # Global (containing everything) animstack, only if not exporting NLA strips and/or all actions.
2225 if not scene_data.settings.bake_anim_use_nla_strips and not scene_data.settings.bake_anim_use_all_actions:
2226 add_anim(animations, animated, fbx_animations_do(scene_data, None, scene.frame_start, scene.frame_end, False))
2228 # Be sure to update all matrices back to org state!
2229 scene.frame_set(scene.frame_current, subframe=0.0)
2231 return animations, animated, frame_start, frame_end
2234 def fbx_data_from_scene(scene, depsgraph, settings):
2236 Do some pre-processing over scene's data...
2238 objtypes = settings.object_types
2239 dp_objtypes = objtypes - {'ARMATURE'} # Armatures are not supported as dupli instances currently...
2240 perfmon = PerfMon()
2241 perfmon.level_up()
2243 # ##### Gathering data...
2245 perfmon.step("FBX export prepare: Wrapping Objects...")
2247 # This is rather simple for now, maybe we could end generating templates with most-used values
2248 # instead of default ones?
2249 objects = {} # Because we do not have any ordered set...
2250 for ob in settings.context_objects:
2251 if ob.type not in objtypes:
2252 continue
2253 ob_obj = ObjectWrapper(ob)
2254 objects[ob_obj] = None
2255 # Duplis...
2256 for dp_obj in ob_obj.dupli_list_gen(depsgraph):
2257 if dp_obj.type not in dp_objtypes:
2258 continue
2259 objects[dp_obj] = None
2261 perfmon.step("FBX export prepare: Wrapping Data (lamps, cameras, empties)...")
2263 data_lights = {ob_obj.bdata.data: get_blenderID_key(ob_obj.bdata.data)
2264 for ob_obj in objects if ob_obj.type == 'LIGHT'}
2265 # Unfortunately, FBX camera data contains object-level data (like position, orientation, etc.)...
2266 data_cameras = {ob_obj: get_blenderID_key(ob_obj.bdata.data)
2267 for ob_obj in objects if ob_obj.type == 'CAMERA'}
2268 # Yep! Contains nothing, but needed!
2269 data_empties = {ob_obj: get_blender_empty_key(ob_obj.bdata)
2270 for ob_obj in objects if ob_obj.type == 'EMPTY'}
2272 perfmon.step("FBX export prepare: Wrapping Meshes...")
2274 data_meshes = {}
2275 for ob_obj in objects:
2276 if ob_obj.type not in BLENDER_OBJECT_TYPES_MESHLIKE:
2277 continue
2278 ob = ob_obj.bdata
2279 use_org_data = True
2280 org_ob_obj = None
2282 # Do not want to systematically recreate a new mesh for dupliobject instances, kind of break purpose of those.
2283 if ob_obj.is_dupli:
2284 org_ob_obj = ObjectWrapper(ob) # We get the "real" object wrapper from that dupli instance.
2285 if org_ob_obj in data_meshes:
2286 data_meshes[ob_obj] = data_meshes[org_ob_obj]
2287 continue
2289 is_ob_material = any(ms.link == 'OBJECT' for ms in ob.material_slots)
2291 if settings.use_mesh_modifiers or settings.use_triangles or ob.type in BLENDER_OTHER_OBJECT_TYPES or is_ob_material:
2292 # We cannot use default mesh in that case, or material would not be the right ones...
2293 use_org_data = not (is_ob_material or ob.type in BLENDER_OTHER_OBJECT_TYPES)
2294 backup_pose_positions = []
2295 tmp_mods = []
2296 if use_org_data and ob.type == 'MESH':
2297 if settings.use_triangles:
2298 use_org_data = False
2299 # No need to create a new mesh in this case, if no modifier is active!
2300 last_subsurf = None
2301 for mod in ob.modifiers:
2302 # For meshes, when armature export is enabled, disable Armature modifiers here!
2303 # XXX Temp hacks here since currently we only have access to a viewport depsgraph...
2305 # NOTE: We put armature to the rest pose instead of disabling it so we still
2306 # have vertex groups in the evaluated mesh.
2307 if mod.type == 'ARMATURE' and 'ARMATURE' in settings.object_types:
2308 object = mod.object
2309 if object and object.type == 'ARMATURE':
2310 armature = object.data
2311 # If armature is already in REST position, there's nothing to back-up
2312 # This cuts down on export time dramatically, if all armatures are already in REST position
2313 # by not triggering dependency graph update
2314 if armature.pose_position != 'REST':
2315 backup_pose_positions.append((armature, armature.pose_position))
2316 armature.pose_position = 'REST'
2317 elif mod.show_render or mod.show_viewport:
2318 # If exporting with subsurf collect the last Catmull-Clark subsurf modifier
2319 # and disable it. We can use the original data as long as this is the first
2320 # found applicable subsurf modifier.
2321 if settings.use_subsurf and mod.type == 'SUBSURF' and mod.subdivision_type == 'CATMULL_CLARK':
2322 if last_subsurf:
2323 use_org_data = False
2324 last_subsurf = mod
2325 else:
2326 use_org_data = False
2327 if settings.use_subsurf and last_subsurf:
2328 # XXX: When exporting with subsurf information temporarily disable
2329 # the last subsurf modifier.
2330 tmp_mods.append((last_subsurf, last_subsurf.show_render, last_subsurf.show_viewport))
2331 last_subsurf.show_render = False
2332 last_subsurf.show_viewport = False
2333 if not use_org_data:
2334 # If modifiers has been altered need to update dependency graph.
2335 if backup_pose_positions or tmp_mods:
2336 depsgraph.update()
2337 ob_to_convert = ob.evaluated_get(depsgraph) if settings.use_mesh_modifiers else ob
2338 # NOTE: The dependency graph might be re-evaluating multiple times, which could
2339 # potentially free the mesh created early on. So we put those meshes to bmain and
2340 # free them afterwards. Not ideal but ensures correct ownerwhip.
2341 tmp_me = bpy.data.meshes.new_from_object(
2342 ob_to_convert, preserve_all_data_layers=True, depsgraph=depsgraph)
2343 # Triangulate the mesh if requested
2344 if settings.use_triangles:
2345 import bmesh
2346 bm = bmesh.new()
2347 bm.from_mesh(tmp_me)
2348 bmesh.ops.triangulate(bm, faces=bm.faces)
2349 bm.to_mesh(tmp_me)
2350 bm.free()
2351 data_meshes[ob_obj] = (get_blenderID_key(tmp_me), tmp_me, True)
2352 # Change armatures back.
2353 for armature, pose_position in backup_pose_positions:
2354 print((armature, pose_position))
2355 armature.pose_position = pose_position
2356 # Update now, so we don't leave modified state after last object was exported.
2357 # Re-enable temporary disabled modifiers.
2358 for mod, show_render, show_viewport in tmp_mods:
2359 mod.show_render = show_render
2360 mod.show_viewport = show_viewport
2361 if backup_pose_positions or tmp_mods:
2362 depsgraph.update()
2363 if use_org_data:
2364 data_meshes[ob_obj] = (get_blenderID_key(ob.data), ob.data, False)
2366 # In case "real" source object of that dupli did not yet still existed in data_meshes, create it now!
2367 if org_ob_obj is not None:
2368 data_meshes[org_ob_obj] = data_meshes[ob_obj]
2370 perfmon.step("FBX export prepare: Wrapping ShapeKeys...")
2372 # ShapeKeys.
2373 data_deformers_shape = {}
2374 geom_mat_co = settings.global_matrix if settings.bake_space_transform else None
2375 for me_key, me, _free in data_meshes.values():
2376 if not (me.shape_keys and len(me.shape_keys.key_blocks) > 1): # We do not want basis-only relative skeys...
2377 continue
2378 if me in data_deformers_shape:
2379 continue
2381 shapes_key = get_blender_mesh_shape_key(me)
2382 # We gather all vcos first, since some skeys may be based on others...
2383 _cos = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * len(me.vertices) * 3
2384 me.vertices.foreach_get("co", _cos)
2385 v_cos = tuple(vcos_transformed_gen(_cos, geom_mat_co))
2386 sk_cos = {}
2387 for shape in me.shape_keys.key_blocks[1:]:
2388 shape.data.foreach_get("co", _cos)
2389 sk_cos[shape] = tuple(vcos_transformed_gen(_cos, geom_mat_co))
2390 sk_base = me.shape_keys.key_blocks[0]
2392 for shape in me.shape_keys.key_blocks[1:]:
2393 # Only write vertices really different from org coordinates!
2394 shape_verts_co = []
2395 shape_verts_idx = []
2397 sv_cos = sk_cos[shape]
2398 ref_cos = v_cos if shape.relative_key == sk_base else sk_cos[shape.relative_key]
2399 for idx, (sv_co, ref_co) in enumerate(zip(sv_cos, ref_cos)):
2400 if similar_values_iter(sv_co, ref_co):
2401 # Note: Maybe this is a bit too simplistic, should we use real shape base here? Though FBX does not
2402 # have this at all... Anyway, this should cover most common cases imho.
2403 continue
2404 shape_verts_co.extend(Vector(sv_co) - Vector(ref_co))
2405 shape_verts_idx.append(idx)
2407 # FBX does not like empty shapes (makes Unity crash e.g.).
2408 # To prevent this, we add a vertex that does nothing, but it keeps the shape key intact
2409 if not shape_verts_co:
2410 shape_verts_co.extend((0, 0, 0))
2411 shape_verts_idx.append(0)
2413 channel_key, geom_key = get_blender_mesh_shape_channel_key(me, shape)
2414 data = (channel_key, geom_key, shape_verts_co, shape_verts_idx)
2415 data_deformers_shape.setdefault(me, (me_key, shapes_key, {}))[2][shape] = data
2417 perfmon.step("FBX export prepare: Wrapping Armatures...")
2419 # Armatures!
2420 data_deformers_skin = {}
2421 data_bones = {}
2422 arm_parents = set()
2423 for ob_obj in tuple(objects):
2424 if not (ob_obj.is_object and ob_obj.type in {'ARMATURE'}):
2425 continue
2426 fbx_skeleton_from_armature(scene, settings, ob_obj, objects, data_meshes,
2427 data_bones, data_deformers_skin, data_empties, arm_parents)
2429 # Generate leaf bones
2430 data_leaf_bones = []
2431 if settings.add_leaf_bones:
2432 data_leaf_bones = fbx_generate_leaf_bones(settings, data_bones)
2434 perfmon.step("FBX export prepare: Wrapping World...")
2436 # Some world settings are embedded in FBX materials...
2437 if scene.world:
2438 data_world = {scene.world: get_blenderID_key(scene.world)}
2439 else:
2440 data_world = {}
2442 perfmon.step("FBX export prepare: Wrapping Materials...")
2444 # TODO: Check all the material stuff works even when they are linked to Objects
2445 # (we can then have the same mesh used with different materials...).
2446 # *Should* work, as FBX always links its materials to Models (i.e. objects).
2447 # XXX However, material indices would probably break...
2448 data_materials = {}
2449 for ob_obj in objects:
2450 # If obj is not a valid object for materials, wrapper will just return an empty tuple...
2451 for ma_s in ob_obj.material_slots:
2452 ma = ma_s.material
2453 if ma is None:
2454 continue # Empty slots!
2455 # Note theoretically, FBX supports any kind of materials, even GLSL shaders etc.
2456 # However, I doubt anything else than Lambert/Phong is really portable!
2457 # Note we want to keep a 'dummy' empty material even when we can't really support it, see T41396.
2458 ma_data = data_materials.setdefault(ma, (get_blenderID_key(ma), []))
2459 ma_data[1].append(ob_obj)
2461 perfmon.step("FBX export prepare: Wrapping Textures...")
2463 # Note FBX textures also hold their mapping info.
2464 # TODO: Support layers?
2465 data_textures = {}
2466 # FbxVideo also used to store static images...
2467 data_videos = {}
2468 # For now, do not use world textures, don't think they can be linked to anything FBX wise...
2469 for ma in data_materials.keys():
2470 # Note: with nodal shaders, we'll could be generating much more textures, but that's kind of unavoidable,
2471 # given that textures actually do not exist anymore in material context in Blender...
2472 ma_wrap = node_shader_utils.PrincipledBSDFWrapper(ma, is_readonly=True)
2473 for sock_name, fbx_name in PRINCIPLED_TEXTURE_SOCKETS_TO_FBX:
2474 tex = getattr(ma_wrap, sock_name)
2475 if tex is None or tex.image is None:
2476 continue
2477 blender_tex_key = (ma, sock_name)
2478 data_textures[blender_tex_key] = (get_blender_nodetexture_key(*blender_tex_key), fbx_name)
2480 img = tex.image
2481 vid_data = data_videos.setdefault(img, (get_blenderID_key(img), []))
2482 vid_data[1].append(blender_tex_key)
2484 perfmon.step("FBX export prepare: Wrapping Animations...")
2486 # Animation...
2487 animations = ()
2488 animated = set()
2489 frame_start = scene.frame_start
2490 frame_end = scene.frame_end
2491 if settings.bake_anim:
2492 # From objects & bones only for a start.
2493 # Kind of hack, we need a temp scene_data for object's space handling to bake animations...
2494 tmp_scdata = FBXExportData(
2495 None, None, None,
2496 settings, scene, depsgraph, objects, None, None, 0.0, 0.0,
2497 data_empties, data_lights, data_cameras, data_meshes, None,
2498 data_bones, data_leaf_bones, data_deformers_skin, data_deformers_shape,
2499 data_world, data_materials, data_textures, data_videos,
2501 animations, animated, frame_start, frame_end = fbx_animations(tmp_scdata)
2503 # ##### Creation of templates...
2505 perfmon.step("FBX export prepare: Generating templates...")
2507 templates = {}
2508 templates[b"GlobalSettings"] = fbx_template_def_globalsettings(scene, settings, nbr_users=1)
2510 if data_empties:
2511 templates[b"Null"] = fbx_template_def_null(scene, settings, nbr_users=len(data_empties))
2513 if data_lights:
2514 templates[b"Light"] = fbx_template_def_light(scene, settings, nbr_users=len(data_lights))
2516 if data_cameras:
2517 templates[b"Camera"] = fbx_template_def_camera(scene, settings, nbr_users=len(data_cameras))
2519 if data_bones:
2520 templates[b"Bone"] = fbx_template_def_bone(scene, settings, nbr_users=len(data_bones))
2522 if data_meshes:
2523 nbr = len({me_key for me_key, _me, _free in data_meshes.values()})
2524 if data_deformers_shape:
2525 nbr += sum(len(shapes[2]) for shapes in data_deformers_shape.values())
2526 templates[b"Geometry"] = fbx_template_def_geometry(scene, settings, nbr_users=nbr)
2528 if objects:
2529 templates[b"Model"] = fbx_template_def_model(scene, settings, nbr_users=len(objects))
2531 if arm_parents:
2532 # Number of Pose|BindPose elements should be the same as number of meshes-parented-to-armatures
2533 templates[b"BindPose"] = fbx_template_def_pose(scene, settings, nbr_users=len(arm_parents))
2535 if data_deformers_skin or data_deformers_shape:
2536 nbr = 0
2537 if data_deformers_skin:
2538 nbr += len(data_deformers_skin)
2539 nbr += sum(len(clusters) for def_me in data_deformers_skin.values() for a, b, clusters in def_me.values())
2540 if data_deformers_shape:
2541 nbr += len(data_deformers_shape)
2542 nbr += sum(len(shapes[2]) for shapes in data_deformers_shape.values())
2543 assert(nbr != 0)
2544 templates[b"Deformers"] = fbx_template_def_deformer(scene, settings, nbr_users=nbr)
2546 # No world support in FBX...
2548 if data_world:
2549 templates[b"World"] = fbx_template_def_world(scene, settings, nbr_users=len(data_world))
2552 if data_materials:
2553 templates[b"Material"] = fbx_template_def_material(scene, settings, nbr_users=len(data_materials))
2555 if data_textures:
2556 templates[b"TextureFile"] = fbx_template_def_texture_file(scene, settings, nbr_users=len(data_textures))
2558 if data_videos:
2559 templates[b"Video"] = fbx_template_def_video(scene, settings, nbr_users=len(data_videos))
2561 if animations:
2562 nbr_astacks = len(animations)
2563 nbr_acnodes = 0
2564 nbr_acurves = 0
2565 for _astack_key, astack, _al, _n, _fs, _fe in animations:
2566 for _alayer_key, alayer in astack.values():
2567 for _acnode_key, acnode, _acnode_name in alayer.values():
2568 nbr_acnodes += 1
2569 for _acurve_key, _dval, acurve, acurve_valid in acnode.values():
2570 if acurve:
2571 nbr_acurves += 1
2573 templates[b"AnimationStack"] = fbx_template_def_animstack(scene, settings, nbr_users=nbr_astacks)
2574 # Would be nice to have one layer per animated object, but this seems tricky and not that well supported.
2575 # So for now, only one layer per anim stack.
2576 templates[b"AnimationLayer"] = fbx_template_def_animlayer(scene, settings, nbr_users=nbr_astacks)
2577 templates[b"AnimationCurveNode"] = fbx_template_def_animcurvenode(scene, settings, nbr_users=nbr_acnodes)
2578 templates[b"AnimationCurve"] = fbx_template_def_animcurve(scene, settings, nbr_users=nbr_acurves)
2580 templates_users = sum(tmpl.nbr_users for tmpl in templates.values())
2582 # ##### Creation of connections...
2584 perfmon.step("FBX export prepare: Generating Connections...")
2586 connections = []
2588 # Objects (with classical parenting).
2589 for ob_obj in objects:
2590 # Bones are handled later.
2591 if not ob_obj.is_bone:
2592 par_obj = ob_obj.parent
2593 # Meshes parented to armature are handled separately, yet we want the 'no parent' connection (0).
2594 if par_obj and ob_obj.has_valid_parent(objects) and (par_obj, ob_obj) not in arm_parents:
2595 connections.append((b"OO", ob_obj.fbx_uuid, par_obj.fbx_uuid, None))
2596 else:
2597 connections.append((b"OO", ob_obj.fbx_uuid, 0, None))
2599 # Armature & Bone chains.
2600 for bo_obj in data_bones.keys():
2601 par_obj = bo_obj.parent
2602 if par_obj not in objects:
2603 continue
2604 connections.append((b"OO", bo_obj.fbx_uuid, par_obj.fbx_uuid, None))
2606 # Object data.
2607 for ob_obj in objects:
2608 if ob_obj.is_bone:
2609 bo_data_key = data_bones[ob_obj]
2610 connections.append((b"OO", get_fbx_uuid_from_key(bo_data_key), ob_obj.fbx_uuid, None))
2611 else:
2612 if ob_obj.type == 'LIGHT':
2613 light_key = data_lights[ob_obj.bdata.data]
2614 connections.append((b"OO", get_fbx_uuid_from_key(light_key), ob_obj.fbx_uuid, None))
2615 elif ob_obj.type == 'CAMERA':
2616 cam_key = data_cameras[ob_obj]
2617 connections.append((b"OO", get_fbx_uuid_from_key(cam_key), ob_obj.fbx_uuid, None))
2618 elif ob_obj.type == 'EMPTY' or ob_obj.type == 'ARMATURE':
2619 empty_key = data_empties[ob_obj]
2620 connections.append((b"OO", get_fbx_uuid_from_key(empty_key), ob_obj.fbx_uuid, None))
2621 elif ob_obj.type in BLENDER_OBJECT_TYPES_MESHLIKE:
2622 mesh_key, _me, _free = data_meshes[ob_obj]
2623 connections.append((b"OO", get_fbx_uuid_from_key(mesh_key), ob_obj.fbx_uuid, None))
2625 # Leaf Bones
2626 for (_node_name, par_uuid, node_uuid, attr_uuid, _matrix, _hide, _size) in data_leaf_bones:
2627 connections.append((b"OO", node_uuid, par_uuid, None))
2628 connections.append((b"OO", attr_uuid, node_uuid, None))
2630 # 'Shape' deformers (shape keys, only for meshes currently)...
2631 for me_key, shapes_key, shapes in data_deformers_shape.values():
2632 # shape -> geometry
2633 connections.append((b"OO", get_fbx_uuid_from_key(shapes_key), get_fbx_uuid_from_key(me_key), None))
2634 for channel_key, geom_key, _shape_verts_co, _shape_verts_idx in shapes.values():
2635 # shape channel -> shape
2636 connections.append((b"OO", get_fbx_uuid_from_key(channel_key), get_fbx_uuid_from_key(shapes_key), None))
2637 # geometry (keys) -> shape channel
2638 connections.append((b"OO", get_fbx_uuid_from_key(geom_key), get_fbx_uuid_from_key(channel_key), None))
2640 # 'Skin' deformers (armature-to-geometry, only for meshes currently)...
2641 for arm, deformed_meshes in data_deformers_skin.items():
2642 for me, (skin_key, ob_obj, clusters) in deformed_meshes.items():
2643 # skin -> geometry
2644 mesh_key, _me, _free = data_meshes[ob_obj]
2645 assert(me == _me)
2646 connections.append((b"OO", get_fbx_uuid_from_key(skin_key), get_fbx_uuid_from_key(mesh_key), None))
2647 for bo_obj, clstr_key in clusters.items():
2648 # cluster -> skin
2649 connections.append((b"OO", get_fbx_uuid_from_key(clstr_key), get_fbx_uuid_from_key(skin_key), None))
2650 # bone -> cluster
2651 connections.append((b"OO", bo_obj.fbx_uuid, get_fbx_uuid_from_key(clstr_key), None))
2653 # Materials
2654 mesh_material_indices = {}
2655 _objs_indices = {}
2656 for ma, (ma_key, ob_objs) in data_materials.items():
2657 for ob_obj in ob_objs:
2658 connections.append((b"OO", get_fbx_uuid_from_key(ma_key), ob_obj.fbx_uuid, None))
2659 # Get index of this material for this object (or dupliobject).
2660 # Material indices for mesh faces are determined by their order in 'ma to ob' connections.
2661 # Only materials for meshes currently...
2662 # Note in case of dupliobjects a same me/ma idx will be generated several times...
2663 # Should not be an issue in practice, and it's needed in case we export duplis but not the original!
2664 if ob_obj.type not in BLENDER_OBJECT_TYPES_MESHLIKE:
2665 continue
2666 _mesh_key, me, _free = data_meshes[ob_obj]
2667 material_indices = mesh_material_indices.setdefault(me, {})
2668 if ma in material_indices:
2669 # Material has already been found for this mesh.
2670 # XXX If a mesh has multiple material slots with the same material, they are combined into one slot.
2671 continue
2672 idx = _objs_indices[ob_obj] = _objs_indices.get(ob_obj, -1) + 1
2673 material_indices[ma] = idx
2674 del _objs_indices
2676 # Textures
2677 for (ma, sock_name), (tex_key, fbx_prop) in data_textures.items():
2678 ma_key, _ob_objs = data_materials[ma]
2679 # texture -> material properties
2680 connections.append((b"OP", get_fbx_uuid_from_key(tex_key), get_fbx_uuid_from_key(ma_key), fbx_prop))
2682 # Images
2683 for vid, (vid_key, blender_tex_keys) in data_videos.items():
2684 for blender_tex_key in blender_tex_keys:
2685 tex_key, _fbx_prop = data_textures[blender_tex_key]
2686 connections.append((b"OO", get_fbx_uuid_from_key(vid_key), get_fbx_uuid_from_key(tex_key), None))
2688 # Animations
2689 for astack_key, astack, alayer_key, _name, _fstart, _fend in animations:
2690 # Animstack itself is linked nowhere!
2691 astack_id = get_fbx_uuid_from_key(astack_key)
2692 # For now, only one layer!
2693 alayer_id = get_fbx_uuid_from_key(alayer_key)
2694 connections.append((b"OO", alayer_id, astack_id, None))
2695 for elem_key, (alayer_key, acurvenodes) in astack.items():
2696 elem_id = get_fbx_uuid_from_key(elem_key)
2697 # Animlayer -> animstack.
2698 # alayer_id = get_fbx_uuid_from_key(alayer_key)
2699 # connections.append((b"OO", alayer_id, astack_id, None))
2700 for fbx_prop, (acurvenode_key, acurves, acurvenode_name) in acurvenodes.items():
2701 # Animcurvenode -> animalayer.
2702 acurvenode_id = get_fbx_uuid_from_key(acurvenode_key)
2703 connections.append((b"OO", acurvenode_id, alayer_id, None))
2704 # Animcurvenode -> object property.
2705 connections.append((b"OP", acurvenode_id, elem_id, fbx_prop.encode()))
2706 for fbx_item, (acurve_key, default_value, acurve, acurve_valid) in acurves.items():
2707 if acurve:
2708 # Animcurve -> Animcurvenode.
2709 connections.append((b"OP", get_fbx_uuid_from_key(acurve_key), acurvenode_id, fbx_item.encode()))
2711 perfmon.level_down()
2713 # ##### And pack all this!
2715 return FBXExportData(
2716 templates, templates_users, connections,
2717 settings, scene, depsgraph, objects, animations, animated, frame_start, frame_end,
2718 data_empties, data_lights, data_cameras, data_meshes, mesh_material_indices,
2719 data_bones, data_leaf_bones, data_deformers_skin, data_deformers_shape,
2720 data_world, data_materials, data_textures, data_videos,
2724 def fbx_scene_data_cleanup(scene_data):
2726 Some final cleanup...
2728 # Delete temp meshes.
2729 done_meshes = set()
2730 for me_key, me, free in scene_data.data_meshes.values():
2731 if free and me_key not in done_meshes:
2732 bpy.data.meshes.remove(me)
2733 done_meshes.add(me_key)
2736 # ##### Top-level FBX elements generators. #####
2738 def fbx_header_elements(root, scene_data, time=None):
2740 Write boiling code of FBX root.
2741 time is expected to be a datetime.datetime object, or None (using now() in this case).
2743 app_vendor = "Blender Foundation"
2744 app_name = "Blender (stable FBX IO)"
2745 app_ver = bpy.app.version_string
2747 import addon_utils
2748 import sys
2749 addon_ver = addon_utils.module_bl_info(sys.modules[__package__])['version']
2751 # ##### Start of FBXHeaderExtension element.
2752 header_ext = elem_empty(root, b"FBXHeaderExtension")
2754 elem_data_single_int32(header_ext, b"FBXHeaderVersion", FBX_HEADER_VERSION)
2756 elem_data_single_int32(header_ext, b"FBXVersion", FBX_VERSION)
2758 # No encryption!
2759 elem_data_single_int32(header_ext, b"EncryptionType", 0)
2761 if time is None:
2762 time = datetime.datetime.now()
2763 elem = elem_empty(header_ext, b"CreationTimeStamp")
2764 elem_data_single_int32(elem, b"Version", 1000)
2765 elem_data_single_int32(elem, b"Year", time.year)
2766 elem_data_single_int32(elem, b"Month", time.month)
2767 elem_data_single_int32(elem, b"Day", time.day)
2768 elem_data_single_int32(elem, b"Hour", time.hour)
2769 elem_data_single_int32(elem, b"Minute", time.minute)
2770 elem_data_single_int32(elem, b"Second", time.second)
2771 elem_data_single_int32(elem, b"Millisecond", time.microsecond // 1000)
2773 elem_data_single_string_unicode(header_ext, b"Creator", "%s - %s - %d.%d.%d"
2774 % (app_name, app_ver, addon_ver[0], addon_ver[1], addon_ver[2]))
2776 # 'SceneInfo' seems mandatory to get a valid FBX file...
2777 # TODO use real values!
2778 # XXX Should we use scene.name.encode() here?
2779 scene_info = elem_data_single_string(header_ext, b"SceneInfo", fbx_name_class(b"GlobalInfo", b"SceneInfo"))
2780 scene_info.add_string(b"UserData")
2781 elem_data_single_string(scene_info, b"Type", b"UserData")
2782 elem_data_single_int32(scene_info, b"Version", FBX_SCENEINFO_VERSION)
2783 meta_data = elem_empty(scene_info, b"MetaData")
2784 elem_data_single_int32(meta_data, b"Version", FBX_SCENEINFO_VERSION)
2785 elem_data_single_string(meta_data, b"Title", b"")
2786 elem_data_single_string(meta_data, b"Subject", b"")
2787 elem_data_single_string(meta_data, b"Author", b"")
2788 elem_data_single_string(meta_data, b"Keywords", b"")
2789 elem_data_single_string(meta_data, b"Revision", b"")
2790 elem_data_single_string(meta_data, b"Comment", b"")
2792 props = elem_properties(scene_info)
2793 elem_props_set(props, "p_string_url", b"DocumentUrl", "/foobar.fbx")
2794 elem_props_set(props, "p_string_url", b"SrcDocumentUrl", "/foobar.fbx")
2795 original = elem_props_compound(props, b"Original")
2796 original("p_string", b"ApplicationVendor", app_vendor)
2797 original("p_string", b"ApplicationName", app_name)
2798 original("p_string", b"ApplicationVersion", app_ver)
2799 original("p_datetime", b"DateTime_GMT", "01/01/1970 00:00:00.000")
2800 original("p_string", b"FileName", "/foobar.fbx")
2801 lastsaved = elem_props_compound(props, b"LastSaved")
2802 lastsaved("p_string", b"ApplicationVendor", app_vendor)
2803 lastsaved("p_string", b"ApplicationName", app_name)
2804 lastsaved("p_string", b"ApplicationVersion", app_ver)
2805 lastsaved("p_datetime", b"DateTime_GMT", "01/01/1970 00:00:00.000")
2806 original("p_string", b"ApplicationNativeFile", bpy.data.filepath)
2808 # ##### End of FBXHeaderExtension element.
2810 # FileID is replaced by dummy value currently...
2811 elem_data_single_bytes(root, b"FileId", b"FooBar")
2813 # CreationTime is replaced by dummy value currently, but anyway...
2814 elem_data_single_string_unicode(root, b"CreationTime",
2815 "{:04}-{:02}-{:02} {:02}:{:02}:{:02}:{:03}"
2816 "".format(time.year, time.month, time.day, time.hour, time.minute, time.second,
2817 time.microsecond * 1000))
2819 elem_data_single_string_unicode(root, b"Creator", "%s - %s - %d.%d.%d"
2820 % (app_name, app_ver, addon_ver[0], addon_ver[1], addon_ver[2]))
2822 # ##### Start of GlobalSettings element.
2823 global_settings = elem_empty(root, b"GlobalSettings")
2824 scene = scene_data.scene
2826 elem_data_single_int32(global_settings, b"Version", 1000)
2828 props = elem_properties(global_settings)
2829 up_axis, front_axis, coord_axis = RIGHT_HAND_AXES[scene_data.settings.to_axes]
2830 #~ # DO NOT take into account global scale here! That setting is applied to object transformations during export
2831 #~ # (in other words, this is pure blender-exporter feature, and has nothing to do with FBX data).
2832 #~ if scene_data.settings.apply_unit_scale:
2833 #~ # Unit scaling is applied to objects' scale, so our unit is effectively FBX one (centimeter).
2834 #~ scale_factor_org = 1.0
2835 #~ scale_factor = 1.0 / units_blender_to_fbx_factor(scene)
2836 #~ else:
2837 #~ scale_factor_org = units_blender_to_fbx_factor(scene)
2838 #~ scale_factor = scale_factor_org
2839 scale_factor = scale_factor_org = scene_data.settings.unit_scale
2840 elem_props_set(props, "p_integer", b"UpAxis", up_axis[0])
2841 elem_props_set(props, "p_integer", b"UpAxisSign", up_axis[1])
2842 elem_props_set(props, "p_integer", b"FrontAxis", front_axis[0])
2843 elem_props_set(props, "p_integer", b"FrontAxisSign", front_axis[1])
2844 elem_props_set(props, "p_integer", b"CoordAxis", coord_axis[0])
2845 elem_props_set(props, "p_integer", b"CoordAxisSign", coord_axis[1])
2846 elem_props_set(props, "p_integer", b"OriginalUpAxis", -1)
2847 elem_props_set(props, "p_integer", b"OriginalUpAxisSign", 1)
2848 elem_props_set(props, "p_double", b"UnitScaleFactor", scale_factor)
2849 elem_props_set(props, "p_double", b"OriginalUnitScaleFactor", scale_factor_org)
2850 elem_props_set(props, "p_color_rgb", b"AmbientColor", (0.0, 0.0, 0.0))
2851 elem_props_set(props, "p_string", b"DefaultCamera", "Producer Perspective")
2853 # Global timing data.
2854 r = scene.render
2855 _, fbx_fps_mode = FBX_FRAMERATES[0] # Custom framerate.
2856 fbx_fps = fps = r.fps / r.fps_base
2857 for ref_fps, fps_mode in FBX_FRAMERATES:
2858 if similar_values(fps, ref_fps):
2859 fbx_fps = ref_fps
2860 fbx_fps_mode = fps_mode
2861 break
2862 elem_props_set(props, "p_enum", b"TimeMode", fbx_fps_mode)
2863 elem_props_set(props, "p_timestamp", b"TimeSpanStart", 0)
2864 elem_props_set(props, "p_timestamp", b"TimeSpanStop", FBX_KTIME)
2865 elem_props_set(props, "p_double", b"CustomFrameRate", fbx_fps)
2867 # ##### End of GlobalSettings element.
2870 def fbx_documents_elements(root, scene_data):
2872 Write 'Document' part of FBX root.
2873 Seems like FBX support multiple documents, but until I find examples of such, we'll stick to single doc!
2874 time is expected to be a datetime.datetime object, or None (using now() in this case).
2876 name = scene_data.scene.name
2878 # ##### Start of Documents element.
2879 docs = elem_empty(root, b"Documents")
2881 elem_data_single_int32(docs, b"Count", 1)
2883 doc_uid = get_fbx_uuid_from_key("__FBX_Document__" + name)
2884 doc = elem_data_single_int64(docs, b"Document", doc_uid)
2885 doc.add_string_unicode(name)
2886 doc.add_string_unicode(name)
2888 props = elem_properties(doc)
2889 elem_props_set(props, "p_object", b"SourceObject")
2890 elem_props_set(props, "p_string", b"ActiveAnimStackName", "")
2892 # XXX Some kind of ID? Offset?
2893 # Anyway, as long as we have only one doc, probably not an issue.
2894 elem_data_single_int64(doc, b"RootNode", 0)
2897 def fbx_references_elements(root, scene_data):
2899 Have no idea what references are in FBX currently... Just writing empty element.
2901 docs = elem_empty(root, b"References")
2904 def fbx_definitions_elements(root, scene_data):
2906 Templates definitions. Only used by Objects data afaik (apart from dummy GlobalSettings one).
2908 definitions = elem_empty(root, b"Definitions")
2910 elem_data_single_int32(definitions, b"Version", FBX_TEMPLATES_VERSION)
2911 elem_data_single_int32(definitions, b"Count", scene_data.templates_users)
2913 fbx_templates_generate(definitions, scene_data.templates)
2916 def fbx_objects_elements(root, scene_data):
2918 Data (objects, geometry, material, textures, armatures, etc.).
2920 perfmon = PerfMon()
2921 perfmon.level_up()
2922 objects = elem_empty(root, b"Objects")
2924 perfmon.step("FBX export fetch empties (%d)..." % len(scene_data.data_empties))
2926 for empty in scene_data.data_empties:
2927 fbx_data_empty_elements(objects, empty, scene_data)
2929 perfmon.step("FBX export fetch lamps (%d)..." % len(scene_data.data_lights))
2931 for lamp in scene_data.data_lights:
2932 fbx_data_light_elements(objects, lamp, scene_data)
2934 perfmon.step("FBX export fetch cameras (%d)..." % len(scene_data.data_cameras))
2936 for cam in scene_data.data_cameras:
2937 fbx_data_camera_elements(objects, cam, scene_data)
2939 perfmon.step("FBX export fetch meshes (%d)..."
2940 % len({me_key for me_key, _me, _free in scene_data.data_meshes.values()}))
2942 done_meshes = set()
2943 for me_obj in scene_data.data_meshes:
2944 fbx_data_mesh_elements(objects, me_obj, scene_data, done_meshes)
2945 del done_meshes
2947 perfmon.step("FBX export fetch objects (%d)..." % len(scene_data.objects))
2949 for ob_obj in scene_data.objects:
2950 if ob_obj.is_dupli:
2951 continue
2952 fbx_data_object_elements(objects, ob_obj, scene_data)
2953 for dp_obj in ob_obj.dupli_list_gen(scene_data.depsgraph):
2954 if dp_obj not in scene_data.objects:
2955 continue
2956 fbx_data_object_elements(objects, dp_obj, scene_data)
2958 perfmon.step("FBX export fetch remaining...")
2960 for ob_obj in scene_data.objects:
2961 if not (ob_obj.is_object and ob_obj.type == 'ARMATURE'):
2962 continue
2963 fbx_data_armature_elements(objects, ob_obj, scene_data)
2965 if scene_data.data_leaf_bones:
2966 fbx_data_leaf_bone_elements(objects, scene_data)
2968 for ma in scene_data.data_materials:
2969 fbx_data_material_elements(objects, ma, scene_data)
2971 for blender_tex_key in scene_data.data_textures:
2972 fbx_data_texture_file_elements(objects, blender_tex_key, scene_data)
2974 for vid in scene_data.data_videos:
2975 fbx_data_video_elements(objects, vid, scene_data)
2977 perfmon.step("FBX export fetch animations...")
2978 start_time = time.process_time()
2980 fbx_data_animation_elements(objects, scene_data)
2982 perfmon.level_down()
2985 def fbx_connections_elements(root, scene_data):
2987 Relations between Objects (which material uses which texture, and so on).
2989 connections = elem_empty(root, b"Connections")
2991 for c in scene_data.connections:
2992 elem_connection(connections, *c)
2995 def fbx_takes_elements(root, scene_data):
2997 Animations.
2999 # XXX Pretty sure takes are no more needed...
3000 takes = elem_empty(root, b"Takes")
3001 elem_data_single_string(takes, b"Current", b"")
3003 animations = scene_data.animations
3004 for astack_key, animations, alayer_key, name, f_start, f_end in animations:
3005 scene = scene_data.scene
3006 fps = scene.render.fps / scene.render.fps_base
3007 start_ktime = int(convert_sec_to_ktime(f_start / fps))
3008 end_ktime = int(convert_sec_to_ktime(f_end / fps))
3010 take = elem_data_single_string(takes, b"Take", name)
3011 elem_data_single_string(take, b"FileName", name + b".tak")
3012 take_loc_time = elem_data_single_int64(take, b"LocalTime", start_ktime)
3013 take_loc_time.add_int64(end_ktime)
3014 take_ref_time = elem_data_single_int64(take, b"ReferenceTime", start_ktime)
3015 take_ref_time.add_int64(end_ktime)
3018 # ##### "Main" functions. #####
3020 # This func can be called with just the filepath
3021 def save_single(operator, scene, depsgraph, filepath="",
3022 global_matrix=Matrix(),
3023 apply_unit_scale=False,
3024 global_scale=1.0,
3025 apply_scale_options='FBX_SCALE_NONE',
3026 axis_up="Z",
3027 axis_forward="Y",
3028 context_objects=None,
3029 object_types=None,
3030 use_mesh_modifiers=True,
3031 use_mesh_modifiers_render=True,
3032 mesh_smooth_type='FACE',
3033 use_subsurf=False,
3034 use_armature_deform_only=False,
3035 bake_anim=True,
3036 bake_anim_use_all_bones=True,
3037 bake_anim_use_nla_strips=True,
3038 bake_anim_use_all_actions=True,
3039 bake_anim_step=1.0,
3040 bake_anim_simplify_factor=1.0,
3041 bake_anim_force_startend_keying=True,
3042 add_leaf_bones=False,
3043 primary_bone_axis='Y',
3044 secondary_bone_axis='X',
3045 use_metadata=True,
3046 path_mode='AUTO',
3047 use_mesh_edges=True,
3048 use_tspace=True,
3049 use_triangles=False,
3050 embed_textures=False,
3051 use_custom_props=False,
3052 bake_space_transform=False,
3053 armature_nodetype='NULL',
3054 colors_type='SRGB',
3055 **kwargs
3058 # Clear cached ObjectWrappers (just in case...).
3059 ObjectWrapper.cache_clear()
3061 if object_types is None:
3062 object_types = {'EMPTY', 'CAMERA', 'LIGHT', 'ARMATURE', 'MESH', 'OTHER'}
3064 if 'OTHER' in object_types:
3065 object_types |= BLENDER_OTHER_OBJECT_TYPES
3067 # Default Blender unit is equivalent to meter, while FBX one is centimeter...
3068 unit_scale = units_blender_to_fbx_factor(scene) if apply_unit_scale else 100.0
3069 if apply_scale_options == 'FBX_SCALE_NONE':
3070 global_matrix = Matrix.Scale(unit_scale * global_scale, 4) @ global_matrix
3071 unit_scale = 1.0
3072 elif apply_scale_options == 'FBX_SCALE_UNITS':
3073 global_matrix = Matrix.Scale(global_scale, 4) @ global_matrix
3074 elif apply_scale_options == 'FBX_SCALE_CUSTOM':
3075 global_matrix = Matrix.Scale(unit_scale, 4) @ global_matrix
3076 unit_scale = global_scale
3077 else: # if apply_scale_options == 'FBX_SCALE_ALL':
3078 unit_scale = global_scale * unit_scale
3080 global_scale = global_matrix.median_scale
3081 global_matrix_inv = global_matrix.inverted()
3082 # For transforming mesh normals.
3083 global_matrix_inv_transposed = global_matrix_inv.transposed()
3085 # Only embed textures in COPY mode!
3086 if embed_textures and path_mode != 'COPY':
3087 embed_textures = False
3089 # Calculate bone correction matrix
3090 bone_correction_matrix = None # Default is None = no change
3091 bone_correction_matrix_inv = None
3092 if (primary_bone_axis, secondary_bone_axis) != ('Y', 'X'):
3093 from bpy_extras.io_utils import axis_conversion
3094 bone_correction_matrix = axis_conversion(from_forward=secondary_bone_axis,
3095 from_up=primary_bone_axis,
3096 to_forward='X',
3097 to_up='Y',
3098 ).to_4x4()
3099 bone_correction_matrix_inv = bone_correction_matrix.inverted()
3102 media_settings = FBXExportSettingsMedia(
3103 path_mode,
3104 os.path.dirname(bpy.data.filepath), # base_src
3105 os.path.dirname(filepath), # base_dst
3106 # Local dir where to put images (media), using FBX conventions.
3107 os.path.splitext(os.path.basename(filepath))[0] + ".fbm", # subdir
3108 embed_textures,
3109 set(), # copy_set
3110 set(), # embedded_set
3113 settings = FBXExportSettings(
3114 operator.report, (axis_up, axis_forward), global_matrix, global_scale, apply_unit_scale, unit_scale,
3115 bake_space_transform, global_matrix_inv, global_matrix_inv_transposed,
3116 context_objects, object_types, use_mesh_modifiers, use_mesh_modifiers_render,
3117 mesh_smooth_type, use_subsurf, use_mesh_edges, use_tspace, use_triangles,
3118 armature_nodetype, use_armature_deform_only,
3119 add_leaf_bones, bone_correction_matrix, bone_correction_matrix_inv,
3120 bake_anim, bake_anim_use_all_bones, bake_anim_use_nla_strips, bake_anim_use_all_actions,
3121 bake_anim_step, bake_anim_simplify_factor, bake_anim_force_startend_keying,
3122 False, media_settings, use_custom_props, colors_type,
3125 import bpy_extras.io_utils
3127 print('\nFBX export starting... %r' % filepath)
3128 start_time = time.process_time()
3130 # Generate some data about exported scene...
3131 scene_data = fbx_data_from_scene(scene, depsgraph, settings)
3133 root = elem_empty(None, b"") # Root element has no id, as it is not saved per se!
3135 # Mostly FBXHeaderExtension and GlobalSettings.
3136 fbx_header_elements(root, scene_data)
3138 # Documents and References are pretty much void currently.
3139 fbx_documents_elements(root, scene_data)
3140 fbx_references_elements(root, scene_data)
3142 # Templates definitions.
3143 fbx_definitions_elements(root, scene_data)
3145 # Actual data.
3146 fbx_objects_elements(root, scene_data)
3148 # How data are inter-connected.
3149 fbx_connections_elements(root, scene_data)
3151 # Animation.
3152 fbx_takes_elements(root, scene_data)
3154 # Cleanup!
3155 fbx_scene_data_cleanup(scene_data)
3157 # And we are down, we can write the whole thing!
3158 encode_bin.write(filepath, root, FBX_VERSION)
3160 # Clear cached ObjectWrappers!
3161 ObjectWrapper.cache_clear()
3163 # copy all collected files, if we did not embed them.
3164 if not media_settings.embed_textures:
3165 bpy_extras.io_utils.path_reference_copy(media_settings.copy_set)
3167 print('export finished in %.4f sec.' % (time.process_time() - start_time))
3168 return {'FINISHED'}
3171 # defaults for applications, currently only unity but could add others.
3172 def defaults_unity3d():
3173 return {
3174 # These options seem to produce the same result as the old Ascii exporter in Unity3D:
3175 "axis_up": 'Y',
3176 "axis_forward": '-Z',
3177 "global_matrix": Matrix.Rotation(-math.pi / 2.0, 4, 'X'),
3178 # Should really be True, but it can cause problems if a model is already in a scene or prefab
3179 # with the old transforms.
3180 "bake_space_transform": False,
3182 "use_selection": False,
3184 "object_types": {'ARMATURE', 'EMPTY', 'MESH', 'OTHER'},
3185 "use_mesh_modifiers": True,
3186 "use_mesh_modifiers_render": True,
3187 "use_mesh_edges": False,
3188 "mesh_smooth_type": 'FACE',
3189 "colors_type": 'SRGB',
3190 "use_subsurf": False,
3191 "use_tspace": False, # XXX Why? Unity is expected to support tspace import...
3192 "use_triangles": False,
3194 "use_armature_deform_only": True,
3196 "use_custom_props": True,
3198 "bake_anim": True,
3199 "bake_anim_simplify_factor": 1.0,
3200 "bake_anim_step": 1.0,
3201 "bake_anim_use_nla_strips": True,
3202 "bake_anim_use_all_actions": True,
3203 "add_leaf_bones": False, # Avoid memory/performance cost for something only useful for modelling
3204 "primary_bone_axis": 'Y', # Doesn't really matter for Unity, so leave unchanged
3205 "secondary_bone_axis": 'X',
3207 "path_mode": 'AUTO',
3208 "embed_textures": False,
3209 "batch_mode": 'OFF',
3213 def save(operator, context,
3214 filepath="",
3215 use_selection=False,
3216 use_visible=False,
3217 use_active_collection=False,
3218 batch_mode='OFF',
3219 use_batch_own_dir=False,
3220 **kwargs
3223 This is a wrapper around save_single, which handles multi-scenes (or collections) cases, when batch-exporting
3224 a whole .blend file.
3227 ret = {'FINISHED'}
3229 active_object = context.view_layer.objects.active
3231 org_mode = None
3232 if active_object and active_object.mode != 'OBJECT' and bpy.ops.object.mode_set.poll():
3233 org_mode = active_object.mode
3234 bpy.ops.object.mode_set(mode='OBJECT')
3236 if batch_mode == 'OFF':
3237 kwargs_mod = kwargs.copy()
3238 if use_active_collection:
3239 if use_selection:
3240 ctx_objects = tuple(obj
3241 for obj in context.view_layer.active_layer_collection.collection.all_objects
3242 if obj.select_get())
3243 else:
3244 ctx_objects = context.view_layer.active_layer_collection.collection.all_objects
3245 else:
3246 if use_selection:
3247 ctx_objects = context.selected_objects
3248 else:
3249 ctx_objects = context.view_layer.objects
3250 if use_visible:
3251 ctx_objects = tuple(obj for obj in ctx_objects if obj.visible_get())
3252 kwargs_mod["context_objects"] = ctx_objects
3254 depsgraph = context.evaluated_depsgraph_get()
3255 ret = save_single(operator, context.scene, depsgraph, filepath, **kwargs_mod)
3256 else:
3257 # XXX We need a way to generate a depsgraph for inactive view_layers first...
3258 # XXX Also, what to do in case of batch-exporting scenes, when there is more than one view layer?
3259 # Scenes have no concept of 'active' view layer, that's on window level...
3260 fbxpath = filepath
3262 prefix = os.path.basename(fbxpath)
3263 if prefix:
3264 fbxpath = os.path.dirname(fbxpath)
3266 if batch_mode == 'COLLECTION':
3267 data_seq = tuple((coll, coll.name, 'objects') for coll in bpy.data.collections if coll.objects)
3268 elif batch_mode in {'SCENE_COLLECTION', 'ACTIVE_SCENE_COLLECTION'}:
3269 scenes = [context.scene] if batch_mode == 'ACTIVE_SCENE_COLLECTION' else bpy.data.scenes
3270 data_seq = []
3271 for scene in scenes:
3272 if not scene.objects:
3273 continue
3274 # Needed to avoid having tens of 'Scene Collection' entries.
3275 todo_collections = [(scene.collection, "_".join((scene.name, scene.collection.name)))]
3276 while todo_collections:
3277 coll, coll_name = todo_collections.pop()
3278 todo_collections.extend(((c, c.name) for c in coll.children if c.all_objects))
3279 data_seq.append((coll, coll_name, 'all_objects'))
3280 else:
3281 data_seq = tuple((scene, scene.name, 'objects') for scene in bpy.data.scenes if scene.objects)
3283 # call this function within a loop with BATCH_ENABLE == False
3285 new_fbxpath = fbxpath # own dir option modifies, we need to keep an original
3286 for data, data_name, data_obj_propname in data_seq: # scene or collection
3287 newname = "_".join((prefix, bpy.path.clean_name(data_name))) if prefix else bpy.path.clean_name(data_name)
3289 if use_batch_own_dir:
3290 new_fbxpath = os.path.join(fbxpath, newname)
3291 # path may already exist... and be a file.
3292 while os.path.isfile(new_fbxpath):
3293 new_fbxpath = "_".join((new_fbxpath, "dir"))
3294 if not os.path.exists(new_fbxpath):
3295 os.makedirs(new_fbxpath)
3297 filepath = os.path.join(new_fbxpath, newname + '.fbx')
3299 print('\nBatch exporting %s as...\n\t%r' % (data, filepath))
3301 if batch_mode in {'COLLECTION', 'SCENE_COLLECTION', 'ACTIVE_SCENE_COLLECTION'}:
3302 # Collection, so that objects update properly, add a dummy scene.
3303 scene = bpy.data.scenes.new(name="FBX_Temp")
3304 src_scenes = {} # Count how much each 'source' scenes are used.
3305 for obj in getattr(data, data_obj_propname):
3306 for src_sce in obj.users_scene:
3307 src_scenes[src_sce] = src_scenes.setdefault(src_sce, 0) + 1
3308 scene.collection.objects.link(obj)
3310 # Find the 'most used' source scene, and use its unit settings. This is somewhat weak, but should work
3311 # fine in most cases, and avoids stupid issues like T41931.
3312 best_src_scene = None
3313 best_src_scene_users = -1
3314 for sce, nbr_users in src_scenes.items():
3315 if (nbr_users) > best_src_scene_users:
3316 best_src_scene_users = nbr_users
3317 best_src_scene = sce
3318 scene.unit_settings.system = best_src_scene.unit_settings.system
3319 scene.unit_settings.system_rotation = best_src_scene.unit_settings.system_rotation
3320 scene.unit_settings.scale_length = best_src_scene.unit_settings.scale_length
3322 # new scene [only one viewlayer to update]
3323 scene.view_layers[0].update()
3324 # TODO - BUMMER! Armatures not in the group wont animate the mesh
3325 else:
3326 scene = data
3328 kwargs_batch = kwargs.copy()
3329 kwargs_batch["context_objects"] = getattr(data, data_obj_propname)
3331 save_single(operator, scene, scene.view_layers[0].depsgraph, filepath, **kwargs_batch)
3333 if batch_mode in {'COLLECTION', 'SCENE_COLLECTION', 'ACTIVE_SCENE_COLLECTION'}:
3334 # Remove temp collection scene.
3335 bpy.data.scenes.remove(scene)
3337 if active_object and org_mode:
3338 context.view_layer.objects.active = active_object
3339 if bpy.ops.object.mode_set.poll():
3340 bpy.ops.object.mode_set(mode=org_mode)
3342 return ret