1 # SPDX-FileCopyrightText: 2013 Campbell Barton
2 # SPDX-FileCopyrightText: 2014 Bastien Montagne
4 # SPDX-License-Identifier: GPL-2.0-or-later
12 from itertools
import zip_longest
13 from functools
import cache
17 if "encode_bin" in locals():
18 importlib
.reload(encode_bin
)
19 if "data_types" in locals():
20 importlib
.reload(data_types
)
21 if "fbx_utils" in locals():
22 importlib
.reload(fbx_utils
)
26 from bpy_extras
import node_shader_utils
27 from bpy
.app
.translations
import pgettext_tip
as tip_
28 from mathutils
import Vector
, Matrix
30 from . import encode_bin
, data_types
, fbx_utils
31 from .fbx_utils
import (
33 FBX_VERSION
, FBX_HEADER_VERSION
, FBX_SCENEINFO_VERSION
, FBX_TEMPLATES_VERSION
,
35 FBX_GEOMETRY_VERSION
, FBX_GEOMETRY_NORMAL_VERSION
, FBX_GEOMETRY_BINORMAL_VERSION
, FBX_GEOMETRY_TANGENT_VERSION
,
36 FBX_GEOMETRY_SMOOTHING_VERSION
, FBX_GEOMETRY_CREASE_VERSION
, FBX_GEOMETRY_VCOLOR_VERSION
, FBX_GEOMETRY_UV_VERSION
,
37 FBX_GEOMETRY_MATERIAL_VERSION
, FBX_GEOMETRY_LAYER_VERSION
,
38 FBX_GEOMETRY_SHAPE_VERSION
, FBX_DEFORMER_SHAPE_VERSION
, FBX_DEFORMER_SHAPECHANNEL_VERSION
,
39 FBX_POSE_BIND_VERSION
, FBX_DEFORMER_SKIN_VERSION
, FBX_DEFORMER_CLUSTER_VERSION
,
40 FBX_MATERIAL_VERSION
, FBX_TEXTURE_VERSION
,
42 FBX_ANIM_PROPSGROUP_NAME
,
44 BLENDER_OTHER_OBJECT_TYPES
, BLENDER_OBJECT_TYPES_MESHLIKE
,
45 FBX_LIGHT_TYPES
, FBX_LIGHT_DECAY_TYPES
,
46 RIGHT_HAND_AXES
, FBX_FRAMERATES
,
47 # Miscellaneous utils.
49 units_blender_to_fbx_factor
, units_convertor
, units_convertor_iter
,
50 matrix4_to_array
, similar_values
, shape_difference_exclude_similar
, astype_view_signedness
, fast_first_axis_unique
,
53 MESH_ATTRIBUTE_CORNER_EDGE
, MESH_ATTRIBUTE_SHARP_EDGE
, MESH_ATTRIBUTE_EDGE_VERTS
, MESH_ATTRIBUTE_CORNER_VERT
,
54 MESH_ATTRIBUTE_SHARP_FACE
, MESH_ATTRIBUTE_POSITION
, MESH_ATTRIBUTE_MATERIAL_INDEX
,
55 # Mesh transform helpers.
56 vcos_transformed
, nors_transformed
,
58 get_fbx_uuid_from_key
,
60 get_blenderID_key
, get_blenderID_name
,
61 get_blender_mesh_shape_key
, get_blender_mesh_shape_channel_key
,
62 get_blender_empty_key
, get_blender_bone_key
,
63 get_blender_bindpose_key
, get_blender_armature_skin_key
, get_blender_bone_cluster_key
,
64 get_blender_anim_id_base
, get_blender_anim_stack_key
, get_blender_anim_layer_key
,
65 get_blender_anim_curve_node_key
, get_blender_anim_curve_key
,
66 get_blender_nodetexture_key
,
69 elem_data_single_char
, elem_data_single_int16
, elem_data_single_int32
, elem_data_single_int64
,
70 elem_data_single_float32
, elem_data_single_float64
,
71 elem_data_single_bytes
, elem_data_single_string
, elem_data_single_string_unicode
,
72 elem_data_single_bool_array
, elem_data_single_int32_array
, elem_data_single_int64_array
,
73 elem_data_single_float32_array
, elem_data_single_float64_array
, elem_data_vec_float64
,
74 # FBX element properties.
75 elem_properties
, elem_props_set
, elem_props_compound
,
76 # FBX element properties handling templates.
77 elem_props_template_init
, elem_props_template_set
, elem_props_template_finalize
,
79 FBXTemplate
, fbx_templates_generate
,
81 AnimationCurveNodeWrapper
,
83 ObjectWrapper
, fbx_name_class
, ensure_object_not_in_edit_mode
,
85 FBXExportSettingsMedia
, FBXExportSettings
, FBXExportData
,
89 convert_sec_to_ktime
= units_convertor("second", "ktime")
90 convert_sec_to_ktime_iter
= units_convertor_iter("second", "ktime")
92 convert_mm_to_inch
= units_convertor("millimeter", "inch")
94 convert_rad_to_deg
= units_convertor("radian", "degree")
95 convert_rad_to_deg_iter
= units_convertor_iter("radian", "degree")
98 # ##### Templates #####
99 # TODO: check all those "default" values, they should match Blender's default as much as possible, I guess?
101 def fbx_template_def_globalsettings(scene
, settings
, override_defaults
=None, nbr_users
=0):
103 if override_defaults
is not None:
104 props
.update(override_defaults
)
105 return FBXTemplate(b
"GlobalSettings", b
"", props
, nbr_users
, [False])
108 def fbx_template_def_model(scene
, settings
, override_defaults
=None, nbr_users
=0):
109 gscale
= settings
.global_scale
111 # Name, Value, Type, Animatable
112 b
"QuaternionInterpolate": (0, "p_enum", False), # 0 = no quat interpolation.
113 b
"RotationOffset": ((0.0, 0.0, 0.0), "p_vector_3d", False),
114 b
"RotationPivot": ((0.0, 0.0, 0.0), "p_vector_3d", False),
115 b
"ScalingOffset": ((0.0, 0.0, 0.0), "p_vector_3d", False),
116 b
"ScalingPivot": ((0.0, 0.0, 0.0), "p_vector_3d", False),
117 b
"TranslationActive": (False, "p_bool", False),
118 b
"TranslationMin": ((0.0, 0.0, 0.0), "p_vector_3d", False),
119 b
"TranslationMax": ((0.0, 0.0, 0.0), "p_vector_3d", False),
120 b
"TranslationMinX": (False, "p_bool", False),
121 b
"TranslationMinY": (False, "p_bool", False),
122 b
"TranslationMinZ": (False, "p_bool", False),
123 b
"TranslationMaxX": (False, "p_bool", False),
124 b
"TranslationMaxY": (False, "p_bool", False),
125 b
"TranslationMaxZ": (False, "p_bool", False),
126 b
"RotationOrder": (0, "p_enum", False), # we always use 'XYZ' order.
127 b
"RotationSpaceForLimitOnly": (False, "p_bool", False),
128 b
"RotationStiffnessX": (0.0, "p_double", False),
129 b
"RotationStiffnessY": (0.0, "p_double", False),
130 b
"RotationStiffnessZ": (0.0, "p_double", False),
131 b
"AxisLen": (10.0, "p_double", False),
132 b
"PreRotation": ((0.0, 0.0, 0.0), "p_vector_3d", False),
133 b
"PostRotation": ((0.0, 0.0, 0.0), "p_vector_3d", False),
134 b
"RotationActive": (False, "p_bool", False),
135 b
"RotationMin": ((0.0, 0.0, 0.0), "p_vector_3d", False),
136 b
"RotationMax": ((0.0, 0.0, 0.0), "p_vector_3d", False),
137 b
"RotationMinX": (False, "p_bool", False),
138 b
"RotationMinY": (False, "p_bool", False),
139 b
"RotationMinZ": (False, "p_bool", False),
140 b
"RotationMaxX": (False, "p_bool", False),
141 b
"RotationMaxY": (False, "p_bool", False),
142 b
"RotationMaxZ": (False, "p_bool", False),
143 b
"InheritType": (0, "p_enum", False), # RrSs
144 b
"ScalingActive": (False, "p_bool", False),
145 b
"ScalingMin": ((0.0, 0.0, 0.0), "p_vector_3d", False),
146 b
"ScalingMax": ((1.0, 1.0, 1.0), "p_vector_3d", False),
147 b
"ScalingMinX": (False, "p_bool", False),
148 b
"ScalingMinY": (False, "p_bool", False),
149 b
"ScalingMinZ": (False, "p_bool", False),
150 b
"ScalingMaxX": (False, "p_bool", False),
151 b
"ScalingMaxY": (False, "p_bool", False),
152 b
"ScalingMaxZ": (False, "p_bool", False),
153 b
"GeometricTranslation": ((0.0, 0.0, 0.0), "p_vector_3d", False),
154 b
"GeometricRotation": ((0.0, 0.0, 0.0), "p_vector_3d", False),
155 b
"GeometricScaling": ((1.0, 1.0, 1.0), "p_vector_3d", False),
156 b
"MinDampRangeX": (0.0, "p_double", False),
157 b
"MinDampRangeY": (0.0, "p_double", False),
158 b
"MinDampRangeZ": (0.0, "p_double", False),
159 b
"MaxDampRangeX": (0.0, "p_double", False),
160 b
"MaxDampRangeY": (0.0, "p_double", False),
161 b
"MaxDampRangeZ": (0.0, "p_double", False),
162 b
"MinDampStrengthX": (0.0, "p_double", False),
163 b
"MinDampStrengthY": (0.0, "p_double", False),
164 b
"MinDampStrengthZ": (0.0, "p_double", False),
165 b
"MaxDampStrengthX": (0.0, "p_double", False),
166 b
"MaxDampStrengthY": (0.0, "p_double", False),
167 b
"MaxDampStrengthZ": (0.0, "p_double", False),
168 b
"PreferedAngleX": (0.0, "p_double", False),
169 b
"PreferedAngleY": (0.0, "p_double", False),
170 b
"PreferedAngleZ": (0.0, "p_double", False),
171 b
"LookAtProperty": (None, "p_object", False),
172 b
"UpVectorProperty": (None, "p_object", False),
173 b
"Show": (True, "p_bool", False),
174 b
"NegativePercentShapeSupport": (True, "p_bool", False),
175 b
"DefaultAttributeIndex": (-1, "p_integer", False),
176 b
"Freeze": (False, "p_bool", False),
177 b
"LODBox": (False, "p_bool", False),
178 b
"Lcl Translation": ((0.0, 0.0, 0.0), "p_lcl_translation", True),
179 b
"Lcl Rotation": ((0.0, 0.0, 0.0), "p_lcl_rotation", True),
180 b
"Lcl Scaling": ((1.0, 1.0, 1.0), "p_lcl_scaling", True),
181 b
"Visibility": (1.0, "p_visibility", True),
182 b
"Visibility Inheritance": (1, "p_visibility_inheritance", False),
184 if override_defaults
is not None:
185 props
.update(override_defaults
)
186 return FBXTemplate(b
"Model", b
"FbxNode", props
, nbr_users
, [False])
189 def fbx_template_def_null(scene
, settings
, override_defaults
=None, nbr_users
=0):
191 b
"Color": ((0.8, 0.8, 0.8), "p_color_rgb", False),
192 b
"Size": (100.0, "p_double", False),
193 b
"Look": (1, "p_enum", False), # Cross (0 is None, i.e. invisible?).
195 if override_defaults
is not None:
196 props
.update(override_defaults
)
197 return FBXTemplate(b
"NodeAttribute", b
"FbxNull", props
, nbr_users
, [False])
200 def fbx_template_def_light(scene
, settings
, override_defaults
=None, nbr_users
=0):
201 gscale
= settings
.global_scale
203 b
"LightType": (0, "p_enum", False), # Point light.
204 b
"CastLight": (True, "p_bool", False),
205 b
"Color": ((1.0, 1.0, 1.0), "p_color", True),
206 b
"Intensity": (100.0, "p_number", True), # Times 100 compared to Blender values...
207 b
"DecayType": (2, "p_enum", False), # Quadratic.
208 b
"DecayStart": (30.0 * gscale
, "p_double", False),
209 b
"CastShadows": (True, "p_bool", False),
210 b
"ShadowColor": ((0.0, 0.0, 0.0), "p_color", True),
211 b
"AreaLightShape": (0, "p_enum", False), # Rectangle.
213 if override_defaults
is not None:
214 props
.update(override_defaults
)
215 return FBXTemplate(b
"NodeAttribute", b
"FbxLight", props
, nbr_users
, [False])
218 def fbx_template_def_camera(scene
, settings
, override_defaults
=None, nbr_users
=0):
221 b
"Color": ((0.8, 0.8, 0.8), "p_color_rgb", False),
222 b
"Position": ((0.0, 0.0, -50.0), "p_vector", True),
223 b
"UpVector": ((0.0, 1.0, 0.0), "p_vector", True),
224 b
"InterestPosition": ((0.0, 0.0, 0.0), "p_vector", True),
225 b
"Roll": (0.0, "p_roll", True),
226 b
"OpticalCenterX": (0.0, "p_opticalcenterx", True),
227 b
"OpticalCenterY": (0.0, "p_opticalcentery", True),
228 b
"BackgroundColor": ((0.63, 0.63, 0.63), "p_color", True),
229 b
"TurnTable": (0.0, "p_number", True),
230 b
"DisplayTurnTableIcon": (False, "p_bool", False),
231 b
"UseMotionBlur": (False, "p_bool", False),
232 b
"UseRealTimeMotionBlur": (True, "p_bool", False),
233 b
"Motion Blur Intensity": (1.0, "p_number", True),
234 b
"AspectRatioMode": (0, "p_enum", False), # WindowSize.
235 b
"AspectWidth": (320.0, "p_double", False),
236 b
"AspectHeight": (200.0, "p_double", False),
237 b
"PixelAspectRatio": (1.0, "p_double", False),
238 b
"FilmOffsetX": (0.0, "p_number", True),
239 b
"FilmOffsetY": (0.0, "p_number", True),
240 b
"FilmWidth": (0.816, "p_double", False),
241 b
"FilmHeight": (0.612, "p_double", False),
242 b
"FilmAspectRatio": (1.3333333333333333, "p_double", False),
243 b
"FilmSqueezeRatio": (1.0, "p_double", False),
244 b
"FilmFormatIndex": (0, "p_enum", False), # Assuming this is ApertureFormat, 0 = custom.
245 b
"PreScale": (1.0, "p_number", True),
246 b
"FilmTranslateX": (0.0, "p_number", True),
247 b
"FilmTranslateY": (0.0, "p_number", True),
248 b
"FilmRollPivotX": (0.0, "p_number", True),
249 b
"FilmRollPivotY": (0.0, "p_number", True),
250 b
"FilmRollValue": (0.0, "p_number", True),
251 b
"FilmRollOrder": (0, "p_enum", False), # 0 = rotate first (default).
252 b
"ApertureMode": (2, "p_enum", False), # 2 = Vertical.
253 b
"GateFit": (0, "p_enum", False), # 0 = no resolution gate fit.
254 b
"FieldOfView": (25.114999771118164, "p_fov", True),
255 b
"FieldOfViewX": (40.0, "p_fov_x", True),
256 b
"FieldOfViewY": (40.0, "p_fov_y", True),
257 b
"FocalLength": (34.89327621672628, "p_number", True),
258 b
"CameraFormat": (0, "p_enum", False), # Custom camera format.
259 b
"UseFrameColor": (False, "p_bool", False),
260 b
"FrameColor": ((0.3, 0.3, 0.3), "p_color_rgb", False),
261 b
"ShowName": (True, "p_bool", False),
262 b
"ShowInfoOnMoving": (True, "p_bool", False),
263 b
"ShowGrid": (True, "p_bool", False),
264 b
"ShowOpticalCenter": (False, "p_bool", False),
265 b
"ShowAzimut": (True, "p_bool", False),
266 b
"ShowTimeCode": (False, "p_bool", False),
267 b
"ShowAudio": (False, "p_bool", False),
268 b
"AudioColor": ((0.0, 1.0, 0.0), "p_vector_3d", False), # Yep, vector3d, not corlorgb… :cry:
269 b
"NearPlane": (10.0, "p_double", False),
270 b
"FarPlane": (4000.0, "p_double", False),
271 b
"AutoComputeClipPanes": (False, "p_bool", False),
272 b
"ViewCameraToLookAt": (True, "p_bool", False),
273 b
"ViewFrustumNearFarPlane": (False, "p_bool", False),
274 b
"ViewFrustumBackPlaneMode": (2, "p_enum", False), # 2 = show back plane if texture added.
275 b
"BackPlaneDistance": (4000.0, "p_number", True),
276 b
"BackPlaneDistanceMode": (1, "p_enum", False), # 1 = relative to camera.
277 b
"ViewFrustumFrontPlaneMode": (2, "p_enum", False), # 2 = show front plane if texture added.
278 b
"FrontPlaneDistance": (10.0, "p_number", True),
279 b
"FrontPlaneDistanceMode": (1, "p_enum", False), # 1 = relative to camera.
280 b
"LockMode": (False, "p_bool", False),
281 b
"LockInterestNavigation": (False, "p_bool", False),
282 # BackPlate... properties **arggggg!**
283 b
"FitImage": (False, "p_bool", False),
284 b
"Crop": (False, "p_bool", False),
285 b
"Center": (True, "p_bool", False),
286 b
"KeepRatio": (True, "p_bool", False),
287 # End of BackPlate...
288 b
"BackgroundAlphaTreshold": (0.5, "p_double", False),
289 b
"ShowBackplate": (True, "p_bool", False),
290 b
"BackPlaneOffsetX": (0.0, "p_number", True),
291 b
"BackPlaneOffsetY": (0.0, "p_number", True),
292 b
"BackPlaneRotation": (0.0, "p_number", True),
293 b
"BackPlaneScaleX": (1.0, "p_number", True),
294 b
"BackPlaneScaleY": (1.0, "p_number", True),
295 b
"Background Texture": (None, "p_object", False),
296 b
"FrontPlateFitImage": (True, "p_bool", False),
297 b
"FrontPlateCrop": (False, "p_bool", False),
298 b
"FrontPlateCenter": (True, "p_bool", False),
299 b
"FrontPlateKeepRatio": (True, "p_bool", False),
300 b
"Foreground Opacity": (1.0, "p_double", False),
301 b
"ShowFrontplate": (True, "p_bool", False),
302 b
"FrontPlaneOffsetX": (0.0, "p_number", True),
303 b
"FrontPlaneOffsetY": (0.0, "p_number", True),
304 b
"FrontPlaneRotation": (0.0, "p_number", True),
305 b
"FrontPlaneScaleX": (1.0, "p_number", True),
306 b
"FrontPlaneScaleY": (1.0, "p_number", True),
307 b
"Foreground Texture": (None, "p_object", False),
308 b
"DisplaySafeArea": (False, "p_bool", False),
309 b
"DisplaySafeAreaOnRender": (False, "p_bool", False),
310 b
"SafeAreaDisplayStyle": (1, "p_enum", False), # 1 = rounded corners.
311 b
"SafeAreaAspectRatio": (1.3333333333333333, "p_double", False),
312 b
"Use2DMagnifierZoom": (False, "p_bool", False),
313 b
"2D Magnifier Zoom": (100.0, "p_number", True),
314 b
"2D Magnifier X": (50.0, "p_number", True),
315 b
"2D Magnifier Y": (50.0, "p_number", True),
316 b
"CameraProjectionType": (0, "p_enum", False), # 0 = perspective, 1 = orthogonal.
317 b
"OrthoZoom": (1.0, "p_double", False),
318 b
"UseRealTimeDOFAndAA": (False, "p_bool", False),
319 b
"UseDepthOfField": (False, "p_bool", False),
320 b
"FocusSource": (0, "p_enum", False), # 0 = camera interest, 1 = distance from camera interest.
321 b
"FocusAngle": (3.5, "p_double", False), # ???
322 b
"FocusDistance": (200.0, "p_double", False),
323 b
"UseAntialiasing": (False, "p_bool", False),
324 b
"AntialiasingIntensity": (0.77777, "p_double", False),
325 b
"AntialiasingMethod": (0, "p_enum", False), # 0 = oversampling, 1 = hardware.
326 b
"UseAccumulationBuffer": (False, "p_bool", False),
327 b
"FrameSamplingCount": (7, "p_integer", False),
328 b
"FrameSamplingType": (1, "p_enum", False), # 0 = uniform, 1 = stochastic.
330 if override_defaults
is not None:
331 props
.update(override_defaults
)
332 return FBXTemplate(b
"NodeAttribute", b
"FbxCamera", props
, nbr_users
, [False])
335 def fbx_template_def_bone(scene
, settings
, override_defaults
=None, nbr_users
=0):
337 if override_defaults
is not None:
338 props
.update(override_defaults
)
339 return FBXTemplate(b
"NodeAttribute", b
"LimbNode", props
, nbr_users
, [False])
342 def fbx_template_def_geometry(scene
, settings
, override_defaults
=None, nbr_users
=0):
344 b
"Color": ((0.8, 0.8, 0.8), "p_color_rgb", False),
345 b
"BBoxMin": ((0.0, 0.0, 0.0), "p_vector_3d", False),
346 b
"BBoxMax": ((0.0, 0.0, 0.0), "p_vector_3d", False),
347 b
"Primary Visibility": (True, "p_bool", False),
348 b
"Casts Shadows": (True, "p_bool", False),
349 b
"Receive Shadows": (True, "p_bool", False),
351 if override_defaults
is not None:
352 props
.update(override_defaults
)
353 return FBXTemplate(b
"Geometry", b
"FbxMesh", props
, nbr_users
, [False])
356 def fbx_template_def_material(scene
, settings
, override_defaults
=None, nbr_users
=0):
359 b
"ShadingModel": ("Phong", "p_string", False),
360 b
"MultiLayer": (False, "p_bool", False),
362 b
"EmissiveColor": ((0.0, 0.0, 0.0), "p_color", True),
363 b
"EmissiveFactor": (1.0, "p_number", True),
364 b
"AmbientColor": ((0.2, 0.2, 0.2), "p_color", True),
365 b
"AmbientFactor": (1.0, "p_number", True),
366 b
"DiffuseColor": ((0.8, 0.8, 0.8), "p_color", True),
367 b
"DiffuseFactor": (1.0, "p_number", True),
368 b
"TransparentColor": ((0.0, 0.0, 0.0), "p_color", True),
369 b
"TransparencyFactor": (0.0, "p_number", True),
370 b
"Opacity": (1.0, "p_number", True),
371 b
"NormalMap": ((0.0, 0.0, 0.0), "p_vector_3d", False),
372 b
"Bump": ((0.0, 0.0, 0.0), "p_vector_3d", False),
373 b
"BumpFactor": (1.0, "p_double", False),
374 b
"DisplacementColor": ((0.0, 0.0, 0.0), "p_color_rgb", False),
375 b
"DisplacementFactor": (1.0, "p_double", False),
376 b
"VectorDisplacementColor": ((0.0, 0.0, 0.0), "p_color_rgb", False),
377 b
"VectorDisplacementFactor": (1.0, "p_double", False),
379 b
"SpecularColor": ((0.2, 0.2, 0.2), "p_color", True),
380 b
"SpecularFactor": (1.0, "p_number", True),
381 # Not sure about the name, importer uses this (but ShininessExponent for tex prop name!)
382 # And in fbx exported by sdk, you have one in template, the other in actual material!!! :/
383 # For now, using both.
384 b
"Shininess": (20.0, "p_number", True),
385 b
"ShininessExponent": (20.0, "p_number", True),
386 b
"ReflectionColor": ((0.0, 0.0, 0.0), "p_color", True),
387 b
"ReflectionFactor": (1.0, "p_number", True),
389 if override_defaults
is not None:
390 props
.update(override_defaults
)
391 return FBXTemplate(b
"Material", b
"FbxSurfacePhong", props
, nbr_users
, [False])
394 def fbx_template_def_texture_file(scene
, settings
, override_defaults
=None, nbr_users
=0):
396 # XXX Not sure about all names!
398 b
"TextureTypeUse": (0, "p_enum", False), # Standard.
399 b
"AlphaSource": (2, "p_enum", False), # Black (i.e. texture's alpha), XXX name guessed!.
400 b
"Texture alpha": (1.0, "p_double", False),
401 b
"PremultiplyAlpha": (True, "p_bool", False),
402 b
"CurrentTextureBlendMode": (1, "p_enum", False), # Additive...
403 b
"CurrentMappingType": (0, "p_enum", False), # UV.
404 b
"UVSet": ("default", "p_string", False), # UVMap name.
405 b
"WrapModeU": (0, "p_enum", False), # Repeat.
406 b
"WrapModeV": (0, "p_enum", False), # Repeat.
407 b
"UVSwap": (False, "p_bool", False),
408 b
"Translation": ((0.0, 0.0, 0.0), "p_vector_3d", False),
409 b
"Rotation": ((0.0, 0.0, 0.0), "p_vector_3d", False),
410 b
"Scaling": ((1.0, 1.0, 1.0), "p_vector_3d", False),
411 b
"TextureRotationPivot": ((0.0, 0.0, 0.0), "p_vector_3d", False),
412 b
"TextureScalingPivot": ((0.0, 0.0, 0.0), "p_vector_3d", False),
413 # Not sure about those two...
414 b
"UseMaterial": (False, "p_bool", False),
415 b
"UseMipMap": (False, "p_bool", False),
417 if override_defaults
is not None:
418 props
.update(override_defaults
)
419 return FBXTemplate(b
"Texture", b
"FbxFileTexture", props
, nbr_users
, [False])
422 def fbx_template_def_video(scene
, settings
, override_defaults
=None, nbr_users
=0):
426 b
"Width": (0, "p_integer", False),
427 b
"Height": (0, "p_integer", False),
428 b
"Path": ("", "p_string_url", False),
429 b
"AccessMode": (0, "p_enum", False), # Disk (0=Disk, 1=Mem, 2=DiskAsync).
431 b
"StartFrame": (0, "p_integer", False),
432 b
"StopFrame": (0, "p_integer", False),
433 b
"Offset": (0, "p_timestamp", False),
434 b
"PlaySpeed": (0.0, "p_double", False),
435 b
"FreeRunning": (False, "p_bool", False),
436 b
"Loop": (False, "p_bool", False),
437 b
"InterlaceMode": (0, "p_enum", False), # None, i.e. progressive.
439 b
"ImageSequence": (False, "p_bool", False),
440 b
"ImageSequenceOffset": (0, "p_integer", False),
441 b
"FrameRate": (0.0, "p_double", False),
442 b
"LastFrame": (0, "p_integer", False),
444 if override_defaults
is not None:
445 props
.update(override_defaults
)
446 return FBXTemplate(b
"Video", b
"FbxVideo", props
, nbr_users
, [False])
449 def fbx_template_def_pose(scene
, settings
, override_defaults
=None, nbr_users
=0):
451 if override_defaults
is not None:
452 props
.update(override_defaults
)
453 return FBXTemplate(b
"Pose", b
"", props
, nbr_users
, [False])
456 def fbx_template_def_deformer(scene
, settings
, override_defaults
=None, nbr_users
=0):
458 if override_defaults
is not None:
459 props
.update(override_defaults
)
460 return FBXTemplate(b
"Deformer", b
"", props
, nbr_users
, [False])
463 def fbx_template_def_animstack(scene
, settings
, override_defaults
=None, nbr_users
=0):
465 b
"Description": ("", "p_string", False),
466 b
"LocalStart": (0, "p_timestamp", False),
467 b
"LocalStop": (0, "p_timestamp", False),
468 b
"ReferenceStart": (0, "p_timestamp", False),
469 b
"ReferenceStop": (0, "p_timestamp", False),
471 if override_defaults
is not None:
472 props
.update(override_defaults
)
473 return FBXTemplate(b
"AnimationStack", b
"FbxAnimStack", props
, nbr_users
, [False])
476 def fbx_template_def_animlayer(scene
, settings
, override_defaults
=None, nbr_users
=0):
478 b
"Weight": (100.0, "p_number", True),
479 b
"Mute": (False, "p_bool", False),
480 b
"Solo": (False, "p_bool", False),
481 b
"Lock": (False, "p_bool", False),
482 b
"Color": ((0.8, 0.8, 0.8), "p_color_rgb", False),
483 b
"BlendMode": (0, "p_enum", False),
484 b
"RotationAccumulationMode": (0, "p_enum", False),
485 b
"ScaleAccumulationMode": (0, "p_enum", False),
486 b
"BlendModeBypass": (0, "p_ulonglong", False),
488 if override_defaults
is not None:
489 props
.update(override_defaults
)
490 return FBXTemplate(b
"AnimationLayer", b
"FbxAnimLayer", props
, nbr_users
, [False])
493 def fbx_template_def_animcurvenode(scene
, settings
, override_defaults
=None, nbr_users
=0):
495 FBX_ANIM_PROPSGROUP_NAME
.encode(): (None, "p_compound", False),
497 if override_defaults
is not None:
498 props
.update(override_defaults
)
499 return FBXTemplate(b
"AnimationCurveNode", b
"FbxAnimCurveNode", props
, nbr_users
, [False])
502 def fbx_template_def_animcurve(scene
, settings
, override_defaults
=None, nbr_users
=0):
504 if override_defaults
is not None:
505 props
.update(override_defaults
)
506 return FBXTemplate(b
"AnimationCurve", b
"", props
, nbr_users
, [False])
509 # ##### Generators for connection elements. #####
511 def elem_connection(elem
, c_type
, uid_src
, uid_dst
, prop_dst
=None):
512 e
= elem_data_single_string(elem
, b
"C", c_type
)
515 if prop_dst
is not None:
516 e
.add_string(prop_dst
)
519 # ##### FBX objects generators. #####
521 def fbx_data_element_custom_properties(props
, bid
):
523 Store custom properties of blender ID bid (any mapping-like object, in fact) into FBX properties props.
530 rna_properties
= {prop
.identifier
for prop
in bid
.bl_rna
.properties
if prop
.is_runtime
}
533 if k
in rna_properties
:
536 list_val
= getattr(v
, "to_list", lambda: None)()
538 if isinstance(v
, str):
539 elem_props_set(props
, "p_string", k
.encode(), v
, custom
=True)
540 elif isinstance(v
, int):
541 elem_props_set(props
, "p_integer", k
.encode(), v
, custom
=True)
542 elif isinstance(v
, float):
543 elem_props_set(props
, "p_double", k
.encode(), v
, custom
=True)
545 if len(list_val
) == 3:
546 elem_props_set(props
, "p_vector", k
.encode(), list_val
, custom
=True)
548 elem_props_set(props
, "p_string", k
.encode(), str(list_val
), custom
=True)
550 elem_props_set(props
, "p_string", k
.encode(), str(v
), custom
=True)
553 def fbx_data_empty_elements(root
, empty
, scene_data
):
555 Write the Empty data block (you can control its FBX datatype with the 'fbx_type' string custom property) or Armature
558 empty_key
= scene_data
.data_empties
[empty
]
560 null
= elem_data_single_int64(root
, b
"NodeAttribute", get_fbx_uuid_from_key(empty_key
))
561 null
.add_string(fbx_name_class(empty
.name
.encode(), b
"NodeAttribute"))
563 if bdata
.type == 'EMPTY':
564 val
= bdata
.get('fbx_type', None)
565 fbx_type
= val
.encode() if val
and isinstance(val
, str) else b
"Null"
568 null
.add_string(fbx_type
)
570 elem_data_single_string(null
, b
"TypeFlags", b
"Null")
572 tmpl
= elem_props_template_init(scene_data
.templates
, b
"Null")
573 props
= elem_properties(null
)
574 elem_props_template_finalize(tmpl
, props
)
576 # Empty/Armature Object custom properties have already been saved with the Model.
577 # Only Armature data custom properties need to be saved here with the NodeAttribute.
578 if bdata
.type == 'ARMATURE':
579 fbx_data_element_custom_properties(props
, bdata
.data
)
582 def fbx_data_light_elements(root
, lamp
, scene_data
):
584 Write the Lamp data block.
586 gscale
= scene_data
.settings
.global_scale
588 light_key
= scene_data
.data_lights
[lamp
]
591 shadow_color
= Vector((0.0, 0.0, 0.0))
592 if lamp
.type not in {'HEMI'}:
594 do_shadow
= lamp
.use_shadow
595 shadow_color
= lamp
.shadow_color
597 light
= elem_data_single_int64(root
, b
"NodeAttribute", get_fbx_uuid_from_key(light_key
))
598 light
.add_string(fbx_name_class(lamp
.name
.encode(), b
"NodeAttribute"))
599 light
.add_string(b
"Light")
601 elem_data_single_int32(light
, b
"GeometryVersion", FBX_GEOMETRY_VERSION
) # Sic...
603 tmpl
= elem_props_template_init(scene_data
.templates
, b
"Light")
604 props
= elem_properties(light
)
605 elem_props_template_set(tmpl
, props
, "p_enum", b
"LightType", FBX_LIGHT_TYPES
[lamp
.type])
606 elem_props_template_set(tmpl
, props
, "p_bool", b
"CastLight", do_light
)
607 elem_props_template_set(tmpl
, props
, "p_color", b
"Color", lamp
.color
)
608 elem_props_template_set(tmpl
, props
, "p_number", b
"Intensity", lamp
.energy
* 100.0)
609 elem_props_template_set(tmpl
, props
, "p_enum", b
"DecayType", FBX_LIGHT_DECAY_TYPES
['INVERSE_SQUARE'])
610 elem_props_template_set(tmpl
, props
, "p_double", b
"DecayStart", 25.0 * gscale
) # 25 is old Blender default
611 elem_props_template_set(tmpl
, props
, "p_bool", b
"CastShadows", do_shadow
)
612 elem_props_template_set(tmpl
, props
, "p_color", b
"ShadowColor", shadow_color
)
613 if lamp
.type in {'SPOT'}:
614 elem_props_template_set(tmpl
, props
, "p_double", b
"OuterAngle", math
.degrees(lamp
.spot_size
))
615 elem_props_template_set(tmpl
, props
, "p_double", b
"InnerAngle",
616 math
.degrees(lamp
.spot_size
* (1.0 - lamp
.spot_blend
)))
617 elem_props_template_finalize(tmpl
, props
)
620 if scene_data
.settings
.use_custom_props
:
621 fbx_data_element_custom_properties(props
, lamp
)
624 def fbx_data_camera_elements(root
, cam_obj
, scene_data
):
626 Write the Camera data blocks.
628 gscale
= scene_data
.settings
.global_scale
632 cam_key
= scene_data
.data_cameras
[cam_obj
]
634 # Real data now, good old camera!
635 # Object transform info.
636 loc
, rot
, scale
, matrix
, matrix_rot
= cam_obj
.fbx_object_tx(scene_data
)
637 up
= matrix_rot
@ Vector((0.0, 1.0, 0.0))
638 to
= matrix_rot
@ Vector((0.0, 0.0, -1.0))
640 # TODO We could export much more...
641 render
= scene_data
.scene
.render
642 width
= render
.resolution_x
643 height
= render
.resolution_y
644 aspect
= width
/ height
645 # Film width & height from mm to inches
646 filmwidth
= convert_mm_to_inch(cam_data
.sensor_width
)
647 filmheight
= convert_mm_to_inch(cam_data
.sensor_height
)
648 filmaspect
= filmwidth
/ filmheight
650 offsetx
= filmwidth
* cam_data
.shift_x
651 offsety
= filmaspect
* filmheight
* cam_data
.shift_y
653 cam
= elem_data_single_int64(root
, b
"NodeAttribute", get_fbx_uuid_from_key(cam_key
))
654 cam
.add_string(fbx_name_class(cam_data
.name
.encode(), b
"NodeAttribute"))
655 cam
.add_string(b
"Camera")
657 tmpl
= elem_props_template_init(scene_data
.templates
, b
"Camera")
658 props
= elem_properties(cam
)
660 elem_props_template_set(tmpl
, props
, "p_vector", b
"Position", loc
)
661 elem_props_template_set(tmpl
, props
, "p_vector", b
"UpVector", up
)
662 elem_props_template_set(tmpl
, props
, "p_vector", b
"InterestPosition", loc
+ to
) # Point, not vector!
663 # Should we use world value?
664 elem_props_template_set(tmpl
, props
, "p_color", b
"BackgroundColor", (0.0, 0.0, 0.0))
665 elem_props_template_set(tmpl
, props
, "p_bool", b
"DisplayTurnTableIcon", True)
667 elem_props_template_set(tmpl
, props
, "p_enum", b
"AspectRatioMode", 2) # FixedResolution
668 elem_props_template_set(tmpl
, props
, "p_double", b
"AspectWidth", float(render
.resolution_x
))
669 elem_props_template_set(tmpl
, props
, "p_double", b
"AspectHeight", float(render
.resolution_y
))
670 elem_props_template_set(tmpl
, props
, "p_double", b
"PixelAspectRatio",
671 float(render
.pixel_aspect_x
/ render
.pixel_aspect_y
))
673 elem_props_template_set(tmpl
, props
, "p_double", b
"FilmWidth", filmwidth
)
674 elem_props_template_set(tmpl
, props
, "p_double", b
"FilmHeight", filmheight
)
675 elem_props_template_set(tmpl
, props
, "p_double", b
"FilmAspectRatio", filmaspect
)
676 elem_props_template_set(tmpl
, props
, "p_double", b
"FilmOffsetX", offsetx
)
677 elem_props_template_set(tmpl
, props
, "p_double", b
"FilmOffsetY", offsety
)
679 elem_props_template_set(tmpl
, props
, "p_enum", b
"ApertureMode", 3) # FocalLength.
680 elem_props_template_set(tmpl
, props
, "p_enum", b
"GateFit", 2) # FitHorizontal.
681 elem_props_template_set(tmpl
, props
, "p_fov", b
"FieldOfView", math
.degrees(cam_data
.angle_x
))
682 elem_props_template_set(tmpl
, props
, "p_fov_x", b
"FieldOfViewX", math
.degrees(cam_data
.angle_x
))
683 elem_props_template_set(tmpl
, props
, "p_fov_y", b
"FieldOfViewY", math
.degrees(cam_data
.angle_y
))
684 # No need to convert to inches here...
685 elem_props_template_set(tmpl
, props
, "p_double", b
"FocalLength", cam_data
.lens
)
686 elem_props_template_set(tmpl
, props
, "p_double", b
"SafeAreaAspectRatio", aspect
)
687 # Depth of field and Focus distance.
688 elem_props_template_set(tmpl
, props
, "p_bool", b
"UseDepthOfField", cam_data
.dof
.use_dof
)
689 elem_props_template_set(tmpl
, props
, "p_double", b
"FocusDistance", cam_data
.dof
.focus_distance
* gscale
)
690 # Default to perspective camera.
691 elem_props_template_set(tmpl
, props
, "p_enum", b
"CameraProjectionType", 1 if cam_data
.type == 'ORTHO' else 0)
692 elem_props_template_set(tmpl
, props
, "p_double", b
"OrthoZoom", cam_data
.ortho_scale
)
694 elem_props_template_set(tmpl
, props
, "p_double", b
"NearPlane", cam_data
.clip_start
* gscale
)
695 elem_props_template_set(tmpl
, props
, "p_double", b
"FarPlane", cam_data
.clip_end
* gscale
)
696 elem_props_template_set(tmpl
, props
, "p_enum", b
"BackPlaneDistanceMode", 1) # RelativeToCamera.
697 elem_props_template_set(tmpl
, props
, "p_double", b
"BackPlaneDistance", cam_data
.clip_end
* gscale
)
699 elem_props_template_finalize(tmpl
, props
)
702 if scene_data
.settings
.use_custom_props
:
703 fbx_data_element_custom_properties(props
, cam_data
)
705 elem_data_single_string(cam
, b
"TypeFlags", b
"Camera")
706 elem_data_single_int32(cam
, b
"GeometryVersion", 124) # Sic...
707 elem_data_vec_float64(cam
, b
"Position", loc
)
708 elem_data_vec_float64(cam
, b
"Up", up
)
709 elem_data_vec_float64(cam
, b
"LookAt", to
)
710 elem_data_single_int32(cam
, b
"ShowInfoOnMoving", 1)
711 elem_data_single_int32(cam
, b
"ShowAudio", 0)
712 elem_data_vec_float64(cam
, b
"AudioColor", (0.0, 1.0, 0.0))
713 elem_data_single_float64(cam
, b
"CameraOrthoZoom", 1.0)
716 def fbx_data_bindpose_element(root
, me_obj
, me
, scene_data
, arm_obj
=None, mat_world_arm
=None, bones
=[]):
718 Helper, since bindpose are used by both meshes shape keys and armature bones...
722 # We assume bind pose for our bones are their "Editmode" pose...
723 # All matrices are expected in global (world) space.
724 bindpose_key
= get_blender_bindpose_key(arm_obj
.bdata
, me
)
725 fbx_pose
= elem_data_single_int64(root
, b
"Pose", get_fbx_uuid_from_key(bindpose_key
))
726 fbx_pose
.add_string(fbx_name_class(me
.name
.encode(), b
"Pose"))
727 fbx_pose
.add_string(b
"BindPose")
729 elem_data_single_string(fbx_pose
, b
"Type", b
"BindPose")
730 elem_data_single_int32(fbx_pose
, b
"Version", FBX_POSE_BIND_VERSION
)
731 elem_data_single_int32(fbx_pose
, b
"NbPoseNodes", 1 + (1 if (arm_obj
!= me_obj
) else 0) + len(bones
))
733 # First node is mesh/object.
734 mat_world_obj
= me_obj
.fbx_object_matrix(scene_data
, global_space
=True)
735 fbx_posenode
= elem_empty(fbx_pose
, b
"PoseNode")
736 elem_data_single_int64(fbx_posenode
, b
"Node", me_obj
.fbx_uuid
)
737 elem_data_single_float64_array(fbx_posenode
, b
"Matrix", matrix4_to_array(mat_world_obj
))
738 # Second node is armature object itself.
739 if arm_obj
!= me_obj
:
740 fbx_posenode
= elem_empty(fbx_pose
, b
"PoseNode")
741 elem_data_single_int64(fbx_posenode
, b
"Node", arm_obj
.fbx_uuid
)
742 elem_data_single_float64_array(fbx_posenode
, b
"Matrix", matrix4_to_array(mat_world_arm
))
743 # And all bones of armature!
746 bomat
= bo_obj
.fbx_object_matrix(scene_data
, rest
=True, global_space
=True)
747 mat_world_bones
[bo_obj
] = bomat
748 fbx_posenode
= elem_empty(fbx_pose
, b
"PoseNode")
749 elem_data_single_int64(fbx_posenode
, b
"Node", bo_obj
.fbx_uuid
)
750 elem_data_single_float64_array(fbx_posenode
, b
"Matrix", matrix4_to_array(bomat
))
752 return mat_world_obj
, mat_world_bones
755 def fbx_data_mesh_shapes_elements(root
, me_obj
, me
, scene_data
, fbx_me_tmpl
, fbx_me_props
):
757 Write shape keys related data.
759 if me
not in scene_data
.data_deformers_shape
:
762 write_normals
= True # scene_data.settings.mesh_smooth_type in {'OFF'}
764 # First, write the geometry data itself (i.e. shapes).
765 _me_key
, shape_key
, shapes
= scene_data
.data_deformers_shape
[me
]
769 vertices
= me
.vertices
770 for shape
, (channel_key
, geom_key
, shape_verts_co
, shape_verts_idx
) in shapes
.items():
771 # Use vgroups as weights, if defined.
772 if shape
.vertex_group
and shape
.vertex_group
in me_obj
.bdata
.vertex_groups
:
773 shape_verts_weights
= np
.zeros(len(shape_verts_idx
), dtype
=np
.float64
)
774 # It's slightly faster to iterate and index the underlying memoryview objects
775 mv_shape_verts_weights
= shape_verts_weights
.data
776 mv_shape_verts_idx
= shape_verts_idx
.data
777 vg_idx
= me_obj
.bdata
.vertex_groups
[shape
.vertex_group
].index
778 for sk_idx
, v_idx
in enumerate(mv_shape_verts_idx
):
779 for vg
in vertices
[v_idx
].groups
:
780 if vg
.group
== vg_idx
:
781 mv_shape_verts_weights
[sk_idx
] = vg
.weight
783 shape_verts_weights
*= 100.0
785 shape_verts_weights
= np
.full(len(shape_verts_idx
), 100.0, dtype
=np
.float64
)
786 channels
.append((channel_key
, shape
, shape_verts_weights
))
788 geom
= elem_data_single_int64(root
, b
"Geometry", get_fbx_uuid_from_key(geom_key
))
789 geom
.add_string(fbx_name_class(shape
.name
.encode(), b
"Geometry"))
790 geom
.add_string(b
"Shape")
792 tmpl
= elem_props_template_init(scene_data
.templates
, b
"Geometry")
793 props
= elem_properties(geom
)
794 elem_props_template_finalize(tmpl
, props
)
796 elem_data_single_int32(geom
, b
"Version", FBX_GEOMETRY_SHAPE_VERSION
)
798 elem_data_single_int32_array(geom
, b
"Indexes", shape_verts_idx
)
799 elem_data_single_float64_array(geom
, b
"Vertices", shape_verts_co
)
801 elem_data_single_float64_array(geom
, b
"Normals", np
.zeros(len(shape_verts_idx
) * 3, dtype
=np
.float64
))
803 # Yiha! BindPose for shapekeys too! Dodecasigh...
804 # XXX Not sure yet whether several bindposes on same mesh are allowed, or not... :/
805 fbx_data_bindpose_element(root
, me_obj
, me
, scene_data
)
807 # ...and now, the deformers stuff.
808 fbx_shape
= elem_data_single_int64(root
, b
"Deformer", get_fbx_uuid_from_key(shape_key
))
809 fbx_shape
.add_string(fbx_name_class(me
.name
.encode(), b
"Deformer"))
810 fbx_shape
.add_string(b
"BlendShape")
812 elem_data_single_int32(fbx_shape
, b
"Version", FBX_DEFORMER_SHAPE_VERSION
)
814 for channel_key
, shape
, shape_verts_weights
in channels
:
815 fbx_channel
= elem_data_single_int64(root
, b
"Deformer", get_fbx_uuid_from_key(channel_key
))
816 fbx_channel
.add_string(fbx_name_class(shape
.name
.encode(), b
"SubDeformer"))
817 fbx_channel
.add_string(b
"BlendShapeChannel")
819 elem_data_single_int32(fbx_channel
, b
"Version", FBX_DEFORMER_SHAPECHANNEL_VERSION
)
820 elem_data_single_float64(fbx_channel
, b
"DeformPercent", shape
.value
* 100.0) # Percents...
821 elem_data_single_float64_array(fbx_channel
, b
"FullWeights", shape_verts_weights
)
823 # *WHY* add this in linked mesh properties too? *cry*
824 # No idea whether it’s percent here too, or more usual factor (assume percentage for now) :/
825 elem_props_template_set(fbx_me_tmpl
, fbx_me_props
, "p_number", shape
.name
.encode(), shape
.value
* 100.0,
829 def fbx_data_mesh_elements(root
, me_obj
, scene_data
, done_meshes
):
831 Write the Mesh (Geometry) data block.
834 def _infinite_gen(val
):
838 me_key
, me
, _free
= scene_data
.data_meshes
[me_obj
]
840 # In case of multiple instances of same mesh, only write it once!
841 if me_key
in done_meshes
:
844 # No gscale/gmat here, all data are supposed to be in object space.
845 smooth_type
= scene_data
.settings
.mesh_smooth_type
846 write_normals
= True # smooth_type in {'OFF'}
848 do_bake_space_transform
= me_obj
.use_bake_space_transform(scene_data
)
850 # Vertices are in object space, but we are post-multiplying all transforms with the inverse of the
851 # global matrix, so we need to apply the global matrix to the vertices to get the correct result.
852 geom_mat_co
= scene_data
.settings
.global_matrix
if do_bake_space_transform
else None
853 # We need to apply the inverse transpose of the global matrix when transforming normals.
854 geom_mat_no
= Matrix(scene_data
.settings
.global_matrix_inv_transposed
) if do_bake_space_transform
else None
855 if geom_mat_no
is not None:
856 # Remove translation & scaling!
857 geom_mat_no
.translation
= Vector()
858 geom_mat_no
.normalize()
860 geom
= elem_data_single_int64(root
, b
"Geometry", get_fbx_uuid_from_key(me_key
))
861 geom
.add_string(fbx_name_class(me
.name
.encode(), b
"Geometry"))
862 geom
.add_string(b
"Mesh")
864 tmpl
= elem_props_template_init(scene_data
.templates
, b
"Geometry")
865 props
= elem_properties(geom
)
868 if scene_data
.settings
.use_custom_props
:
869 fbx_data_element_custom_properties(props
, me
)
871 # Subdivision levels. Take them from the first found subsurf modifier from the
872 # first object that has the mesh. Write crease information if the object has
873 # and subsurf modifier.
875 if scene_data
.settings
.use_subsurf
:
877 for mod
in me_obj
.bdata
.modifiers
:
878 if not (mod
.show_render
or mod
.show_viewport
):
880 if mod
.type == 'SUBSURF' and mod
.subdivision_type
== 'CATMULL_CLARK':
884 elem_data_single_int32(geom
, b
"Smoothness", 2) # Display control mesh and smoothed
885 if last_subsurf
.boundary_smooth
== "PRESERVE_CORNERS":
886 elem_data_single_int32(geom
, b
"BoundaryRule", 1) # CreaseAll
888 elem_data_single_int32(geom
, b
"BoundaryRule", 2) # CreaseEdge
889 elem_data_single_int32(geom
, b
"PreviewDivisionLevels", last_subsurf
.levels
)
890 elem_data_single_int32(geom
, b
"RenderDivisionLevels", last_subsurf
.render_levels
)
892 elem_data_single_int32(geom
, b
"PreserveBorders", 0)
893 elem_data_single_int32(geom
, b
"PreserveHardEdges", 0)
894 elem_data_single_int32(geom
, b
"PropagateEdgeHardness", 0)
896 write_crease
= last_subsurf
.use_creases
898 elem_data_single_int32(geom
, b
"GeometryVersion", FBX_GEOMETRY_VERSION
)
900 attributes
= me
.attributes
903 pos_fbx_dtype
= np
.float64
904 t_pos
= MESH_ATTRIBUTE_POSITION
.to_ndarray(attributes
)
905 elem_data_single_float64_array(geom
, b
"Vertices", vcos_transformed(t_pos
, geom_mat_co
, pos_fbx_dtype
))
910 # We do loose edges as two-vertices faces, if enabled...
912 # Note we have to process Edges in the same time, as they are based on poly's loops...
914 # Total number of loops, including any extra added for loose edges.
915 loop_nbr
= len(me
.loops
)
917 # dtypes matching the C data. Matching the C datatype avoids iteration and casting of every element in foreach_get's
919 bl_loop_index_dtype
= np
.uintc
921 # Start vertex indices of loops (corners). May contain elements for loops added for the export of loose edges.
922 t_lvi
= MESH_ATTRIBUTE_CORNER_VERT
.to_ndarray(attributes
)
924 # Loop start indices of polygons. May contain elements for the polygons added for the export of loose edges.
925 t_ls
= np
.empty(len(me
.polygons
), dtype
=bl_loop_index_dtype
)
927 # Vertex indices of edges (unsorted, unlike Mesh.edge_keys), flattened into an array twice the length of the number
929 t_ev
= MESH_ATTRIBUTE_EDGE_VERTS
.to_ndarray(attributes
)
930 # Each edge has two vertex indices, so it's useful to view the array as 2d where each element on the first axis is a
931 # pair of vertex indices
932 t_ev_pair_view
= t_ev
.view()
933 t_ev_pair_view
.shape
= (-1, 2)
935 # Edge indices of loops (corners). May contain elements for loops added for the export of loose edges.
936 t_lei
= MESH_ATTRIBUTE_CORNER_EDGE
.to_ndarray(attributes
)
938 me
.polygons
.foreach_get("loop_start", t_ls
)
940 # Add "fake" faces for loose edges. Each "fake" face consists of two loops creating a new 2-sided polygon.
941 if scene_data
.settings
.use_mesh_edges
:
942 bl_edge_is_loose_dtype
= bool
943 # Get the mask of edges that are loose
944 loose_mask
= np
.empty(len(me
.edges
), dtype
=bl_edge_is_loose_dtype
)
945 me
.edges
.foreach_get('is_loose', loose_mask
)
947 indices_of_loose_edges
= np
.flatnonzero(loose_mask
)
948 # Since we add two loops per loose edge, repeat the indices so that there's one for each new loop
949 new_loop_edge_indices
= np
.repeat(indices_of_loose_edges
, 2)
951 # Get the loose edge vertex index pairs
952 t_le
= t_ev_pair_view
[loose_mask
]
954 # append will automatically flatten the pairs in t_le
955 t_lvi
= np
.append(t_lvi
, t_le
)
956 t_lei
= np
.append(t_lei
, new_loop_edge_indices
)
957 # Two loops are added per loose edge
958 loop_nbr
+= 2 * len(t_le
)
959 t_ls
= np
.append(t_ls
, np
.arange(len(me
.loops
), loop_nbr
, 2, dtype
=t_ls
.dtype
))
962 del indices_of_loose_edges
963 del new_loop_edge_indices
966 # Note: Edges are represented as a loop here: each edge uses a single index, which refers to the polygon array.
967 # The edge is made by the vertex indexed py this polygon's point and the next one on the same polygon.
968 # Advantage: Only one index per edge.
969 # Drawback: Only polygon's edges can be represented (that's why we have to add fake two-verts polygons
971 # We also have to store a mapping from real edges to their indices in this array, for edge-mapped data
972 # (like e.g. crease).
973 eli_fbx_dtype
= np
.int32
975 # Edge index of each unique edge-key, used to map per-edge data to unique edge-keys (t_pvi).
976 t_pvi_edge_indices
= np
.empty(0, dtype
=t_lei
.dtype
)
978 pvi_fbx_dtype
= np
.int32
979 if t_ls
.size
and t_lvi
.size
:
980 # Get unsorted edge keys by indexing the edge->vertex-indices array by the loop->edge-index array.
981 t_pvi_edge_keys
= t_ev_pair_view
[t_lei
]
983 # Sort each [edge_start_n, edge_end_n] pair to get edge keys. Heapsort seems to be the fastest for this specific
985 t_pvi_edge_keys
.sort(axis
=1, kind
='heapsort')
987 # Note that finding unique edge keys means that if there are multiple edges that share the same vertices (which
988 # shouldn't normally happen), only the first edge found in loops will be exported along with its per-edge data.
989 # To export separate edges that share the same vertices, fast_first_axis_unique can be replaced with np.unique
990 # with t_lei as the first argument, finding unique edges rather than unique edge keys.
992 # Since we want the unique values in their original order, the only part we care about is the indices of the
993 # first occurrence of the unique elements in t_pvi_edge_keys, so we can use our fast uniqueness helper function.
994 t_eli
= fast_first_axis_unique(t_pvi_edge_keys
, return_unique
=False, return_index
=True)
996 # To get the indices of the elements in t_pvi_edge_keys that produce unique values, but in the original order of
997 # t_pvi_edge_keys, t_eli must be sorted.
998 # Due to loops and their edge keys tending to have a partial ordering within meshes, sorting with kind='stable'
999 # with radix sort tends to be faster than the default of kind='quicksort' with introsort.
1000 t_eli
.sort(kind
='stable')
1002 # Edge index of each element in unique t_pvi_edge_keys, used to map per-edge data such as sharp and creases.
1003 t_pvi_edge_indices
= t_lei
[t_eli
]
1005 # We have to ^-1 last index of each loop.
1006 # Ensure t_pvi is the correct number of bits before inverting.
1007 # t_lvi may be used again later, so always create a copy to avoid modifying it in the next step.
1008 t_pvi
= t_lvi
.astype(pvi_fbx_dtype
)
1009 # The index of the end of each loop is one before the index of the start of the next loop.
1010 t_pvi
[t_ls
[1:] - 1] ^
= -1
1011 # The index of the end of the last loop will be the very last index.
1015 # Should be empty, but make sure it's the correct type.
1016 t_pvi
= np
.empty(0, dtype
=pvi_fbx_dtype
)
1017 t_eli
= np
.empty(0, dtype
=eli_fbx_dtype
)
1019 # And finally we can write data!
1020 t_pvi
= astype_view_signedness(t_pvi
, pvi_fbx_dtype
)
1021 t_eli
= astype_view_signedness(t_eli
, eli_fbx_dtype
)
1022 elem_data_single_int32_array(geom
, b
"PolygonVertexIndex", t_pvi
)
1023 elem_data_single_int32_array(geom
, b
"Edges", t_eli
)
1032 if smooth_type
in {'FACE', 'EDGE'}:
1033 ps_fbx_dtype
= np
.int32
1035 if smooth_type
== 'FACE':
1036 # The FBX integer values are usually interpreted as boolean where 0 is False (sharp) and 1 is True
1038 # The values may also be used to represent smoothing group bitflags, but this does not seem well-supported.
1039 t_ps
= MESH_ATTRIBUTE_SHARP_FACE
.get_ndarray(attributes
)
1040 if t_ps
is not None:
1041 # FBX sharp is False, but Blender sharp is True, so invert.
1042 t_ps
= np
.logical_not(t_ps
)
1044 # The mesh has no "sharp_face" attribute, so every face is smooth.
1045 t_ps
= np
.ones(len(me
.polygons
), dtype
=ps_fbx_dtype
)
1049 if t_pvi_edge_indices
.size
:
1050 # Write Edge Smoothing.
1051 # Note edge is sharp also if it's used by more than two faces, or one of its faces is flat.
1052 mesh_poly_nbr
= len(me
.polygons
)
1053 mesh_edge_nbr
= len(me
.edges
)
1054 mesh_loop_nbr
= len(me
.loops
)
1055 # t_ls and t_lei may contain extra polygons or loops added for loose edges that are not present in the
1056 # mesh data, so create views that exclude the extra data added for loose edges.
1057 mesh_t_ls_view
= t_ls
[:mesh_poly_nbr
]
1058 mesh_t_lei_view
= t_lei
[:mesh_loop_nbr
]
1060 # - Get sharp edges from edges used by more than two loops (and therefore more than two faces)
1061 e_more_than_two_faces_mask
= np
.bincount(mesh_t_lei_view
, minlength
=mesh_edge_nbr
) > 2
1063 # - Get sharp edges from the "sharp_edge" attribute. The attribute may not exist, in which case, there
1064 # are no edges marked as sharp.
1065 e_use_sharp_mask
= MESH_ATTRIBUTE_SHARP_EDGE
.get_ndarray(attributes
)
1066 if e_use_sharp_mask
is not None:
1067 # - Combine with edges that are sharp because they're in more than two faces
1068 e_use_sharp_mask
= np
.logical_or(e_use_sharp_mask
, e_more_than_two_faces_mask
, out
=e_use_sharp_mask
)
1070 e_use_sharp_mask
= e_more_than_two_faces_mask
1072 # - Get sharp edges from flat shaded faces
1073 p_flat_mask
= MESH_ATTRIBUTE_SHARP_FACE
.get_ndarray(attributes
)
1074 if p_flat_mask
is not None:
1075 # Convert flat shaded polygons to flat shaded loops by repeating each element by the number of sides
1077 # Polygon sides can be calculated from the element-wise difference of loop starts appended by the
1078 # number of loops. Alternatively, polygon sides can be retrieved directly from the 'loop_total'
1079 # attribute of polygons, but since we already have t_ls, it tends to be quicker to calculate from
1081 polygon_sides
= np
.diff(mesh_t_ls_view
, append
=mesh_loop_nbr
)
1082 p_flat_loop_mask
= np
.repeat(p_flat_mask
, polygon_sides
)
1083 # Convert flat shaded loops to flat shaded (sharp) edge indices.
1084 # Note that if an edge is in multiple loops that are part of flat shaded faces, its edge index will
1085 # end up in sharp_edge_indices_from_polygons multiple times.
1086 sharp_edge_indices_from_polygons
= mesh_t_lei_view
[p_flat_loop_mask
]
1088 # - Combine with edges that are sharp because a polygon they're in has flat shading
1089 e_use_sharp_mask
[sharp_edge_indices_from_polygons
] = True
1090 del sharp_edge_indices_from_polygons
1091 del p_flat_loop_mask
1095 # - Convert sharp edges to sharp edge keys (t_pvi)
1096 ek_use_sharp_mask
= e_use_sharp_mask
[t_pvi_edge_indices
]
1098 # - Sharp edges are indicated in FBX as zero (False), so invert
1099 t_ps
= np
.invert(ek_use_sharp_mask
, out
=ek_use_sharp_mask
)
1100 del ek_use_sharp_mask
1101 del e_use_sharp_mask
1105 t_ps
= np
.empty(0, dtype
=ps_fbx_dtype
)
1106 t_ps
= t_ps
.astype(ps_fbx_dtype
, copy
=False)
1107 lay_smooth
= elem_data_single_int32(geom
, b
"LayerElementSmoothing", 0)
1108 elem_data_single_int32(lay_smooth
, b
"Version", FBX_GEOMETRY_SMOOTHING_VERSION
)
1109 elem_data_single_string(lay_smooth
, b
"Name", b
"")
1110 elem_data_single_string(lay_smooth
, b
"MappingInformationType", _map
)
1111 elem_data_single_string(lay_smooth
, b
"ReferenceInformationType", b
"Direct")
1112 elem_data_single_int32_array(lay_smooth
, b
"Smoothing", t_ps
) # Sight, int32 for bool...
1117 # Edge crease for subdivision
1119 ec_fbx_dtype
= np
.float64
1120 if t_pvi_edge_indices
.size
:
1121 ec_bl_dtype
= np
.single
1122 edge_creases
= me
.edge_creases
1124 t_ec_raw
= np
.empty(len(me
.edges
), dtype
=ec_bl_dtype
)
1125 edge_creases
.data
.foreach_get("value", t_ec_raw
)
1127 # Convert to t_pvi edge-keys.
1128 t_ec_ek_raw
= t_ec_raw
[t_pvi_edge_indices
]
1130 # Blender squares those values before sending them to OpenSubdiv, when other software don't,
1131 # so we need to compensate that to get similar results through FBX...
1132 # Use the precision of the fbx dtype for the calculation since it's usually higher precision.
1133 t_ec_ek_raw
= t_ec_ek_raw
.astype(ec_fbx_dtype
, copy
=False)
1134 t_ec
= np
.square(t_ec_ek_raw
, out
=t_ec_ek_raw
)
1138 # todo: Blender edge creases are optional now, we may be able to avoid writing the array to FBX when
1139 # there are no edge creases.
1140 t_ec
= np
.zeros(t_pvi_edge_indices
.shape
, dtype
=ec_fbx_dtype
)
1142 t_ec
= np
.empty(0, dtype
=ec_fbx_dtype
)
1144 lay_crease
= elem_data_single_int32(geom
, b
"LayerElementEdgeCrease", 0)
1145 elem_data_single_int32(lay_crease
, b
"Version", FBX_GEOMETRY_CREASE_VERSION
)
1146 elem_data_single_string(lay_crease
, b
"Name", b
"")
1147 elem_data_single_string(lay_crease
, b
"MappingInformationType", b
"ByEdge")
1148 elem_data_single_string(lay_crease
, b
"ReferenceInformationType", b
"Direct")
1149 elem_data_single_float64_array(lay_crease
, b
"EdgeCrease", t_ec
)
1152 # And we are done with edges!
1153 del t_pvi_edge_indices
1158 normal_bl_dtype
= np
.single
1159 normal_fbx_dtype
= np
.float64
1160 match me
.normals_domain
:
1162 # All faces are smooth shaded, so we can get normals from the vertices.
1163 normal_source
= me
.vertex_normals
1164 normal_mapping
= b
"ByVertice"
1165 # External software support for b"ByPolygon" normals does not seem to be as widely available as the other
1166 # mappings. See blender/blender#117470.
1168 # # Either all faces or all edges are sharp, so we can get normals from the faces.
1169 # normal_source = me.polygon_normals
1170 # normal_mapping = b"ByPolygon"
1171 case
'CORNER' |
'FACE':
1172 # We have a mix of sharp/smooth edges/faces or custom split normals, so need to get normals from
1174 normal_source
= me
.corner_normals
1175 normal_mapping
= b
"ByPolygonVertex"
1178 raise AssertionError("Unexpected normals domain '%s'" % me
.normals_domain
)
1179 # Each normal has 3 components, so the length is multiplied by 3.
1180 t_normal
= np
.empty(len(normal_source
) * 3, dtype
=normal_bl_dtype
)
1181 normal_source
.foreach_get("vector", t_normal
)
1182 t_normal
= nors_transformed(t_normal
, geom_mat_no
, normal_fbx_dtype
)
1183 normal_idx_fbx_dtype
= np
.int32
1184 lay_nor
= elem_data_single_int32(geom
, b
"LayerElementNormal", 0)
1185 elem_data_single_int32(lay_nor
, b
"Version", FBX_GEOMETRY_NORMAL_VERSION
)
1186 elem_data_single_string(lay_nor
, b
"Name", b
"")
1187 elem_data_single_string(lay_nor
, b
"MappingInformationType", normal_mapping
)
1188 # FBX SDK documentation says that normals should use IndexToDirect.
1189 elem_data_single_string(lay_nor
, b
"ReferenceInformationType", b
"IndexToDirect")
1191 # Tuple of unique sorted normals and then the index in the unique sorted normals of each normal in t_normal.
1192 # Since we don't care about how the normals are sorted, only that they're unique, we can use the fast unique
1194 t_normal
, t_normal_idx
= fast_first_axis_unique(t_normal
.reshape(-1, 3), return_inverse
=True)
1196 # Convert to the type for fbx
1197 t_normal_idx
= astype_view_signedness(t_normal_idx
, normal_idx_fbx_dtype
)
1199 elem_data_single_float64_array(lay_nor
, b
"Normals", t_normal
)
1200 # Normal weights, no idea what it is.
1201 # t_normal_w = np.zeros(len(t_normal), dtype=np.float64)
1202 # elem_data_single_float64_array(lay_nor, b"NormalsW", t_normal_w)
1204 elem_data_single_int32_array(lay_nor
, b
"NormalsIndex", t_normal_idx
)
1211 if scene_data
.settings
.use_tspace
:
1212 tspacenumber
= len(me
.uv_layers
)
1214 # We can only compute tspace on tessellated meshes, need to check that here...
1215 lt_bl_dtype
= np
.uintc
1216 t_lt
= np
.empty(len(me
.polygons
), dtype
=lt_bl_dtype
)
1217 me
.polygons
.foreach_get("loop_total", t_lt
)
1218 if (t_lt
> 4).any():
1220 scene_data
.settings
.report(
1222 tip_("Mesh '%s' has polygons with more than 4 vertices, "
1223 "cannot compute/export tangent space for it") % me
.name
)
1226 num_loops
= len(me
.loops
)
1227 t_ln
= np
.empty(num_loops
* 3, dtype
=normal_bl_dtype
)
1228 # t_lnw = np.zeros(len(me.loops), dtype=np.float64)
1229 uv_names
= [uvlayer
.name
for uvlayer
in me
.uv_layers
]
1230 # Annoying, `me.calc_tangent` errors in case there is no geometry...
1232 for name
in uv_names
:
1233 me
.calc_tangents(uvmap
=name
)
1234 for idx
, uvlayer
in enumerate(me
.uv_layers
):
1236 # Loop bitangents (aka binormals).
1237 # NOTE: this is not supported by importer currently.
1238 me
.loops
.foreach_get("bitangent", t_ln
)
1239 lay_nor
= elem_data_single_int32(geom
, b
"LayerElementBinormal", idx
)
1240 elem_data_single_int32(lay_nor
, b
"Version", FBX_GEOMETRY_BINORMAL_VERSION
)
1241 elem_data_single_string_unicode(lay_nor
, b
"Name", name
)
1242 elem_data_single_string(lay_nor
, b
"MappingInformationType", b
"ByPolygonVertex")
1243 elem_data_single_string(lay_nor
, b
"ReferenceInformationType", b
"Direct")
1244 elem_data_single_float64_array(lay_nor
, b
"Binormals",
1245 nors_transformed(t_ln
, geom_mat_no
, normal_fbx_dtype
))
1246 # Binormal weights, no idea what it is.
1247 # elem_data_single_float64_array(lay_nor, b"BinormalsW", t_lnw)
1250 # NOTE: this is not supported by importer currently.
1251 me
.loops
.foreach_get("tangent", t_ln
)
1252 lay_nor
= elem_data_single_int32(geom
, b
"LayerElementTangent", idx
)
1253 elem_data_single_int32(lay_nor
, b
"Version", FBX_GEOMETRY_TANGENT_VERSION
)
1254 elem_data_single_string_unicode(lay_nor
, b
"Name", name
)
1255 elem_data_single_string(lay_nor
, b
"MappingInformationType", b
"ByPolygonVertex")
1256 elem_data_single_string(lay_nor
, b
"ReferenceInformationType", b
"Direct")
1257 elem_data_single_float64_array(lay_nor
, b
"Tangents",
1258 nors_transformed(t_ln
, geom_mat_no
, normal_fbx_dtype
))
1259 # Tangent weights, no idea what it is.
1260 # elem_data_single_float64_array(lay_nor, b"TangentsW", t_lnw)
1266 # Write VertexColor Layers.
1267 colors_type
= scene_data
.settings
.colors_type
1268 vcolnumber
= 0 if colors_type
== 'NONE' else len(me
.color_attributes
)
1270 color_prop_name
= "color_srgb" if colors_type
== 'SRGB' else "color"
1271 # ByteColorAttribute color also gets returned by the API as single precision float
1272 bl_lc_dtype
= np
.single
1273 fbx_lc_dtype
= np
.float64
1274 fbx_lcidx_dtype
= np
.int32
1276 color_attributes
= me
.color_attributes
1277 if scene_data
.settings
.prioritize_active_color
:
1278 active_color
= me
.color_attributes
.active_color
1279 color_attributes
= sorted(color_attributes
, key
=lambda x
: x
== active_color
, reverse
=True)
1281 for colindex
, collayer
in enumerate(color_attributes
):
1282 is_point
= collayer
.domain
== "POINT"
1283 vcollen
= len(me
.vertices
if is_point
else me
.loops
)
1284 # Each rgba component is flattened in the array
1285 t_lc
= np
.empty(vcollen
* 4, dtype
=bl_lc_dtype
)
1286 collayer
.data
.foreach_get(color_prop_name
, t_lc
)
1287 lay_vcol
= elem_data_single_int32(geom
, b
"LayerElementColor", colindex
)
1288 elem_data_single_int32(lay_vcol
, b
"Version", FBX_GEOMETRY_VCOLOR_VERSION
)
1289 elem_data_single_string_unicode(lay_vcol
, b
"Name", collayer
.name
)
1290 elem_data_single_string(lay_vcol
, b
"MappingInformationType", b
"ByPolygonVertex")
1291 elem_data_single_string(lay_vcol
, b
"ReferenceInformationType", b
"IndexToDirect")
1293 # Use the fast uniqueness helper function since we don't care about sorting.
1294 t_lc
, col_indices
= fast_first_axis_unique(t_lc
.reshape(-1, 4), return_inverse
=True)
1297 # for "point" domain colors, we could directly emit them
1298 # with a "ByVertex" mapping type, but some software does not
1299 # properly understand that. So expand to full "ByPolygonVertex"
1301 # Ignore loops added for loose edges.
1302 col_indices
= col_indices
[t_lvi
[:len(me
.loops
)]]
1304 t_lc
= t_lc
.astype(fbx_lc_dtype
, copy
=False)
1305 col_indices
= astype_view_signedness(col_indices
, fbx_lcidx_dtype
)
1307 elem_data_single_float64_array(lay_vcol
, b
"Colors", t_lc
)
1308 elem_data_single_int32_array(lay_vcol
, b
"ColorIndex", col_indices
)
1314 # Note: LayerElementTexture is deprecated since FBX 2011 - luckily!
1315 # Textures are now only related to materials, in FBX!
1316 uvnumber
= len(me
.uv_layers
)
1318 luv_bl_dtype
= np
.single
1319 luv_fbx_dtype
= np
.float64
1320 lv_idx_fbx_dtype
= np
.int32
1322 t_luv
= np
.empty(len(me
.loops
) * 2, dtype
=luv_bl_dtype
)
1323 # Fast view for sort-based uniqueness of pairs.
1324 t_luv_fast_pair_view
= fast_first_axis_flat(t_luv
.reshape(-1, 2))
1325 # It must be a view of t_luv otherwise it won't update when t_luv is updated.
1326 assert(t_luv_fast_pair_view
.base
is t_luv
)
1328 # Looks like this mapping is also expected to convey UV islands (arg..... :((((( ).
1329 # So we need to generate unique triplets (uv, vertex_idx) here, not only just based on UV values.
1330 # Ignore loops added for loose edges.
1331 t_lvidx
= t_lvi
[:len(me
.loops
)]
1333 # If we were to create a combined array of (uv, vertex_idx) elements, we could find unique triplets by sorting
1334 # that array by first sorting by the vertex_idx column and then sorting by the uv column using a stable sorting
1336 # This is exactly what we'll do, but without creating the combined array, because only the uv elements are
1337 # included in the export and the vertex_idx column is the same for every uv layer.
1339 # Because the vertex_idx column is the same for every uv layer, the vertex_idx column can be sorted in advance.
1340 # argsort gets the indices that sort the array, which are needed to be able to sort the array of uv pairs in the
1341 # same way to create the indices that recreate the full uvs from the unique uvs.
1342 # Loops and vertices tend to naturally have a partial ordering, which makes sorting with kind='stable' (radix
1343 # sort) faster than the default of kind='quicksort' (introsort) in most cases.
1344 perm_vidx
= t_lvidx
.argsort(kind
='stable')
1346 # Mask and uv indices arrays will be modified and re-used by each uv layer.
1347 unique_mask
= np
.empty(len(me
.loops
), dtype
=np
.bool_
)
1348 unique_mask
[:1] = True
1349 uv_indices
= np
.empty(len(me
.loops
), dtype
=lv_idx_fbx_dtype
)
1351 for uvindex
, uvlayer
in enumerate(me
.uv_layers
):
1352 lay_uv
= elem_data_single_int32(geom
, b
"LayerElementUV", uvindex
)
1353 elem_data_single_int32(lay_uv
, b
"Version", FBX_GEOMETRY_UV_VERSION
)
1354 elem_data_single_string_unicode(lay_uv
, b
"Name", uvlayer
.name
)
1355 elem_data_single_string(lay_uv
, b
"MappingInformationType", b
"ByPolygonVertex")
1356 elem_data_single_string(lay_uv
, b
"ReferenceInformationType", b
"IndexToDirect")
1358 uvlayer
.uv
.foreach_get("vector", t_luv
)
1360 # t_luv_fast_pair_view is a view in a dtype that compares elements by individual bytes, but float types have
1361 # separate byte representations of positive and negative zero. For uniqueness, these should be considered
1362 # the same, so replace all -0.0 with 0.0 in advance.
1363 t_luv
[t_luv
== -0.0] = 0.0
1365 # These steps to create unique_uv_pairs are the same as how np.unique would find unique values by sorting a
1366 # structured array where each element is a triplet of (uv, vertex_idx), except uv and vertex_idx are
1367 # separate arrays here and vertex_idx has already been sorted in advance.
1369 # Sort according to the vertex_idx column, using the precalculated indices that sort it.
1370 sorted_t_luv_fast
= t_luv_fast_pair_view
[perm_vidx
]
1372 # Get the indices that would sort the sorted uv pairs. Stable sorting must be used to maintain the sorting
1373 # of the vertex indices.
1374 perm_uv_pairs
= sorted_t_luv_fast
.argsort(kind
='stable')
1375 # Use the indices to sort both the uv pairs and the vertex_idx columns.
1376 perm_combined
= perm_vidx
[perm_uv_pairs
]
1377 sorted_vidx
= t_lvidx
[perm_combined
]
1378 sorted_t_luv_fast
= sorted_t_luv_fast
[perm_uv_pairs
]
1380 # Create a mask where either the uv pair doesn't equal the previous value in the array, or the vertex index
1381 # doesn't equal the previous value, these will be the unique uv-vidx triplets.
1382 # For an imaginary triplet array:
1385 # [(0.4, 0.2), 1] -> Unique because vertex index different from previous
1386 # [(0.4, 0.2), 2] -> Unique because vertex index different from previous
1387 # [(0.7, 0.6), 2] -> Unique because uv different from previous
1390 # Output the result into unique_mask.
1391 np
.logical_or(sorted_t_luv_fast
[1:] != sorted_t_luv_fast
[:-1], sorted_vidx
[1:] != sorted_vidx
[:-1],
1392 out
=unique_mask
[1:])
1394 # Get each uv pair marked as unique by the unique_mask and then view as the original dtype.
1395 unique_uvs
= sorted_t_luv_fast
[unique_mask
].view(luv_bl_dtype
)
1397 # NaN values are considered invalid and indicate a bug somewhere else in Blender or in an addon, we want
1398 # these bugs to be reported instead of hiding them by allowing the export to continue.
1399 if np
.isnan(unique_uvs
).any():
1400 raise RuntimeError("UV layer %s on %r has invalid UVs containing NaN values" % (uvlayer
.name
, me
))
1402 # Convert to the type needed for fbx
1403 unique_uvs
= unique_uvs
.astype(luv_fbx_dtype
, copy
=False)
1405 # Set the indices of pairs in unique_uvs that reconstruct the pairs in t_luv into uv_indices.
1406 # uv_indices will then be the same as an inverse array returned by np.unique with return_inverse=True.
1407 uv_indices
[perm_combined
] = np
.cumsum(unique_mask
, dtype
=uv_indices
.dtype
) - 1
1409 elem_data_single_float64_array(lay_uv
, b
"UV", unique_uvs
)
1410 elem_data_single_int32_array(lay_uv
, b
"UVIndex", uv_indices
)
1412 del sorted_t_luv_fast
1421 del t_luv_fast_pair_view
1425 me_fbxmaterials_idx
= scene_data
.mesh_material_indices
.get(me
)
1426 if me_fbxmaterials_idx
is not None:
1427 # We cannot use me.materials here, as this array is filled with None in case materials are linked to object...
1428 me_blmaterials
= me_obj
.materials
1429 if me_fbxmaterials_idx
and me_blmaterials
:
1430 lay_ma
= elem_data_single_int32(geom
, b
"LayerElementMaterial", 0)
1431 elem_data_single_int32(lay_ma
, b
"Version", FBX_GEOMETRY_MATERIAL_VERSION
)
1432 elem_data_single_string(lay_ma
, b
"Name", b
"")
1433 nbr_mats
= len(me_fbxmaterials_idx
)
1434 multiple_fbx_mats
= nbr_mats
> 1
1435 # If a mesh does not have more than one material its material_index attribute can be ignored.
1436 # If a mesh has multiple materials but all its polygons are assigned to the first material, its
1437 # material_index attribute may not exist.
1438 t_pm
= None if not multiple_fbx_mats
else MESH_ATTRIBUTE_MATERIAL_INDEX
.get_ndarray(attributes
)
1439 if t_pm
is not None:
1440 fbx_pm_dtype
= np
.int32
1442 # We have to validate mat indices, and map them to FBX indices.
1443 # Note a mat might not be in me_fbxmaterials_idx (e.g. node mats are ignored).
1445 # The first valid material will be used for materials out of bounds of me_blmaterials or materials not
1446 # in me_fbxmaterials_idx.
1447 def_me_blmaterial_idx
, def_ma
= next(
1448 (i
, me_fbxmaterials_idx
[m
]) for i
, m
in enumerate(me_blmaterials
) if m
in me_fbxmaterials_idx
)
1450 # Set material indices that are out of bounds to the default material index
1451 mat_idx_limit
= len(me_blmaterials
)
1452 # Material indices shouldn't be negative, but they technically could be. Viewing as unsigned before
1453 # checking for indices that are too large means that a single >= check will pick up both negative
1454 # indices and indices that are too large.
1455 t_pm
[t_pm
.view("u%i" % t_pm
.itemsize
) >= mat_idx_limit
] = def_me_blmaterial_idx
1457 # Map to FBX indices. Materials not in me_fbxmaterials_idx will be set to the default material index.
1458 blmat_fbx_idx
= np
.fromiter((me_fbxmaterials_idx
.get(m
, def_ma
) for m
in me_blmaterials
),
1460 t_pm
= blmat_fbx_idx
[t_pm
]
1462 elem_data_single_string(lay_ma
, b
"MappingInformationType", b
"ByPolygon")
1463 # XXX Logically, should be "Direct" reference type, since we do not have any index array, and have one
1464 # value per polygon...
1465 # But looks like FBX expects it to be IndexToDirect here (maybe because materials are already
1466 # indices??? *sigh*).
1467 elem_data_single_string(lay_ma
, b
"ReferenceInformationType", b
"IndexToDirect")
1468 elem_data_single_int32_array(lay_ma
, b
"Materials", t_pm
)
1470 elem_data_single_string(lay_ma
, b
"MappingInformationType", b
"AllSame")
1471 elem_data_single_string(lay_ma
, b
"ReferenceInformationType", b
"IndexToDirect")
1472 if multiple_fbx_mats
:
1473 # There's no material_index attribute, so every material index is effectively zero.
1474 # In the order of the mesh's materials, get the FBX index of the first material that is exported.
1475 all_same_idx
= next(me_fbxmaterials_idx
[m
] for m
in me_blmaterials
if m
in me_fbxmaterials_idx
)
1477 # There's only one fbx material, so the index will always be zero.
1479 elem_data_single_int32_array(lay_ma
, b
"Materials", [all_same_idx
])
1482 # And the "layer TOC"...
1484 layer
= elem_data_single_int32(geom
, b
"Layer", 0)
1485 elem_data_single_int32(layer
, b
"Version", FBX_GEOMETRY_LAYER_VERSION
)
1487 lay_nor
= elem_empty(layer
, b
"LayerElement")
1488 elem_data_single_string(lay_nor
, b
"Type", b
"LayerElementNormal")
1489 elem_data_single_int32(lay_nor
, b
"TypedIndex", 0)
1491 lay_binor
= elem_empty(layer
, b
"LayerElement")
1492 elem_data_single_string(lay_binor
, b
"Type", b
"LayerElementBinormal")
1493 elem_data_single_int32(lay_binor
, b
"TypedIndex", 0)
1494 lay_tan
= elem_empty(layer
, b
"LayerElement")
1495 elem_data_single_string(lay_tan
, b
"Type", b
"LayerElementTangent")
1496 elem_data_single_int32(lay_tan
, b
"TypedIndex", 0)
1497 if smooth_type
in {'FACE', 'EDGE'}:
1498 lay_smooth
= elem_empty(layer
, b
"LayerElement")
1499 elem_data_single_string(lay_smooth
, b
"Type", b
"LayerElementSmoothing")
1500 elem_data_single_int32(lay_smooth
, b
"TypedIndex", 0)
1502 lay_smooth
= elem_empty(layer
, b
"LayerElement")
1503 elem_data_single_string(lay_smooth
, b
"Type", b
"LayerElementEdgeCrease")
1504 elem_data_single_int32(lay_smooth
, b
"TypedIndex", 0)
1506 lay_vcol
= elem_empty(layer
, b
"LayerElement")
1507 elem_data_single_string(lay_vcol
, b
"Type", b
"LayerElementColor")
1508 elem_data_single_int32(lay_vcol
, b
"TypedIndex", 0)
1510 lay_uv
= elem_empty(layer
, b
"LayerElement")
1511 elem_data_single_string(lay_uv
, b
"Type", b
"LayerElementUV")
1512 elem_data_single_int32(lay_uv
, b
"TypedIndex", 0)
1513 if me_fbxmaterials_idx
is not None:
1514 lay_ma
= elem_empty(layer
, b
"LayerElement")
1515 elem_data_single_string(lay_ma
, b
"Type", b
"LayerElementMaterial")
1516 elem_data_single_int32(lay_ma
, b
"TypedIndex", 0)
1518 # Add other uv and/or vcol layers...
1519 for vcolidx
, uvidx
, tspaceidx
in zip_longest(range(1, vcolnumber
), range(1, uvnumber
), range(1, tspacenumber
),
1521 layer
= elem_data_single_int32(geom
, b
"Layer", max(vcolidx
, uvidx
))
1522 elem_data_single_int32(layer
, b
"Version", FBX_GEOMETRY_LAYER_VERSION
)
1524 lay_vcol
= elem_empty(layer
, b
"LayerElement")
1525 elem_data_single_string(lay_vcol
, b
"Type", b
"LayerElementColor")
1526 elem_data_single_int32(lay_vcol
, b
"TypedIndex", vcolidx
)
1528 lay_uv
= elem_empty(layer
, b
"LayerElement")
1529 elem_data_single_string(lay_uv
, b
"Type", b
"LayerElementUV")
1530 elem_data_single_int32(lay_uv
, b
"TypedIndex", uvidx
)
1532 lay_binor
= elem_empty(layer
, b
"LayerElement")
1533 elem_data_single_string(lay_binor
, b
"Type", b
"LayerElementBinormal")
1534 elem_data_single_int32(lay_binor
, b
"TypedIndex", tspaceidx
)
1535 lay_tan
= elem_empty(layer
, b
"LayerElement")
1536 elem_data_single_string(lay_tan
, b
"Type", b
"LayerElementTangent")
1537 elem_data_single_int32(lay_tan
, b
"TypedIndex", tspaceidx
)
1540 fbx_data_mesh_shapes_elements(root
, me_obj
, me
, scene_data
, tmpl
, props
)
1542 elem_props_template_finalize(tmpl
, props
)
1543 done_meshes
.add(me_key
)
1546 def fbx_data_material_elements(root
, ma
, scene_data
):
1548 Write the Material data block.
1551 ambient_color
= (0.0, 0.0, 0.0)
1552 if scene_data
.data_world
:
1553 ambient_color
= next(iter(scene_data
.data_world
.keys())).color
1555 ma_wrap
= node_shader_utils
.PrincipledBSDFWrapper(ma
, is_readonly
=True)
1556 ma_key
, _objs
= scene_data
.data_materials
[ma
]
1559 fbx_ma
= elem_data_single_int64(root
, b
"Material", get_fbx_uuid_from_key(ma_key
))
1560 fbx_ma
.add_string(fbx_name_class(ma
.name
.encode(), b
"Material"))
1561 fbx_ma
.add_string(b
"")
1563 elem_data_single_int32(fbx_ma
, b
"Version", FBX_MATERIAL_VERSION
)
1564 # those are not yet properties, it seems...
1565 elem_data_single_string(fbx_ma
, b
"ShadingModel", ma_type
)
1566 elem_data_single_int32(fbx_ma
, b
"MultiLayer", 0) # Should be bool...
1568 tmpl
= elem_props_template_init(scene_data
.templates
, b
"Material")
1569 props
= elem_properties(fbx_ma
)
1571 elem_props_template_set(tmpl
, props
, "p_string", b
"ShadingModel", ma_type
.decode())
1572 elem_props_template_set(tmpl
, props
, "p_color", b
"DiffuseColor", ma_wrap
.base_color
)
1573 # Not in Principled BSDF, so assuming always 1
1574 elem_props_template_set(tmpl
, props
, "p_number", b
"DiffuseFactor", 1.0)
1575 # Principled BSDF only has an emissive color, so we assume factor to be always 1.0.
1576 elem_props_template_set(tmpl
, props
, "p_color", b
"EmissiveColor", ma_wrap
.emission_color
)
1577 elem_props_template_set(tmpl
, props
, "p_number", b
"EmissiveFactor", ma_wrap
.emission_strength
)
1578 # Not in Principled BSDF, so assuming always 0
1579 elem_props_template_set(tmpl
, props
, "p_color", b
"AmbientColor", ambient_color
)
1580 elem_props_template_set(tmpl
, props
, "p_number", b
"AmbientFactor", 0.0)
1581 # Sweetness... Looks like we are not the only ones to not know exactly how FBX is supposed to work (see T59850).
1582 # According to one of its developers, Unity uses that formula to extract alpha value:
1584 # alpha = 1 - TransparencyFactor
1585 # if (alpha == 1 or alpha == 0):
1586 # alpha = 1 - TransparentColor.r
1588 # Until further info, let's assume this is correct way to do, hence the following code for TransparentColor.
1589 if ma_wrap
.alpha
< 1.0e-5 or ma_wrap
.alpha
> (1.0 - 1.0e-5):
1590 elem_props_template_set(tmpl
, props
, "p_color", b
"TransparentColor", (1.0 - ma_wrap
.alpha
,) * 3)
1592 elem_props_template_set(tmpl
, props
, "p_color", b
"TransparentColor", ma_wrap
.base_color
)
1593 elem_props_template_set(tmpl
, props
, "p_number", b
"TransparencyFactor", 1.0 - ma_wrap
.alpha
)
1594 elem_props_template_set(tmpl
, props
, "p_number", b
"Opacity", ma_wrap
.alpha
)
1595 elem_props_template_set(tmpl
, props
, "p_vector_3d", b
"NormalMap", (0.0, 0.0, 0.0))
1596 elem_props_template_set(tmpl
, props
, "p_double", b
"BumpFactor", ma_wrap
.normalmap_strength
)
1597 # Not sure about those...
1599 b"Bump": ((0.0, 0.0, 0.0), "p_vector_3d"),
1600 b"DisplacementColor": ((0.0, 0.0, 0.0), "p_color_rgb"),
1601 b"DisplacementFactor": (0.0, "p_double"),
1603 # TODO: use specular tint?
1604 elem_props_template_set(tmpl
, props
, "p_color", b
"SpecularColor", ma_wrap
.base_color
)
1605 elem_props_template_set(tmpl
, props
, "p_number", b
"SpecularFactor", ma_wrap
.specular
/ 2.0)
1606 # See Material template about those two!
1607 # XXX Totally empirical conversion, trying to adapt it
1608 # (from 0.0 - 100.0 FBX shininess range to 1.0 - 0.0 Principled BSDF range)...
1609 shininess
= (1.0 - ma_wrap
.roughness
) * 10
1610 shininess
*= shininess
1611 elem_props_template_set(tmpl
, props
, "p_number", b
"Shininess", shininess
)
1612 elem_props_template_set(tmpl
, props
, "p_number", b
"ShininessExponent", shininess
)
1613 elem_props_template_set(tmpl
, props
, "p_color", b
"ReflectionColor", ma_wrap
.base_color
)
1614 elem_props_template_set(tmpl
, props
, "p_number", b
"ReflectionFactor", ma_wrap
.metallic
)
1616 elem_props_template_finalize(tmpl
, props
)
1618 # Custom properties.
1619 if scene_data
.settings
.use_custom_props
:
1620 fbx_data_element_custom_properties(props
, ma
)
1623 def _gen_vid_path(img
, scene_data
):
1624 msetts
= scene_data
.settings
.media_settings
1625 fname_rel
= bpy_extras
.io_utils
.path_reference(img
.filepath
, msetts
.base_src
, msetts
.base_dst
, msetts
.path_mode
,
1626 msetts
.subdir
, msetts
.copy_set
, img
.library
)
1627 fname_abs
= os
.path
.normpath(os
.path
.abspath(os
.path
.join(msetts
.base_dst
, fname_rel
)))
1628 return fname_abs
, fname_rel
1631 def fbx_data_texture_file_elements(root
, blender_tex_key
, scene_data
):
1633 Write the (file) Texture data block.
1635 # XXX All this is very fuzzy to me currently...
1636 # Textures do not seem to use properties as much as they could.
1637 # For now assuming most logical and simple stuff.
1639 ma
, sock_name
= blender_tex_key
1640 ma_wrap
= node_shader_utils
.PrincipledBSDFWrapper(ma
, is_readonly
=True)
1641 tex_key
, _fbx_prop
= scene_data
.data_textures
[blender_tex_key
]
1642 tex
= getattr(ma_wrap
, sock_name
)
1644 fname_abs
, fname_rel
= _gen_vid_path(img
, scene_data
)
1646 fbx_tex
= elem_data_single_int64(root
, b
"Texture", get_fbx_uuid_from_key(tex_key
))
1647 fbx_tex
.add_string(fbx_name_class(sock_name
.encode(), b
"Texture"))
1648 fbx_tex
.add_string(b
"")
1650 elem_data_single_string(fbx_tex
, b
"Type", b
"TextureVideoClip")
1651 elem_data_single_int32(fbx_tex
, b
"Version", FBX_TEXTURE_VERSION
)
1652 elem_data_single_string(fbx_tex
, b
"TextureName", fbx_name_class(sock_name
.encode(), b
"Texture"))
1653 elem_data_single_string(fbx_tex
, b
"Media", fbx_name_class(img
.name
.encode(), b
"Video"))
1654 elem_data_single_string_unicode(fbx_tex
, b
"FileName", fname_abs
)
1655 elem_data_single_string_unicode(fbx_tex
, b
"RelativeFilename", fname_rel
)
1657 alpha_source
= 0 # None
1658 if img
.alpha_mode
!= 'NONE':
1659 # ~ if tex.texture.use_calculate_alpha:
1660 # ~ alpha_source = 1 # RGBIntensity as alpha.
1662 # ~ alpha_source = 2 # Black, i.e. alpha channel.
1663 alpha_source
= 2 # Black, i.e. alpha channel.
1664 # BlendMode not useful for now, only affects layered textures afaics.
1667 if tex
.texcoords
== 'ORCO': # XXX Others?
1668 if tex
.projection
== 'FLAT':
1669 mapping
= 1 # Planar
1670 elif tex
.projection
== 'CUBE':
1672 elif tex
.projection
== 'TUBE':
1673 mapping
= 3 # Cylindrical
1674 elif tex
.projection
== 'SPHERE':
1675 mapping
= 2 # Spherical
1676 elif tex
.texcoords
== 'UV':
1678 # Yuck, UVs are linked by mere names it seems... :/
1679 # XXX TODO how to get that now???
1680 # uvset = tex.uv_layer
1681 wrap_mode
= 1 # Clamp
1682 if tex
.extension
== 'REPEAT':
1683 wrap_mode
= 0 # Repeat
1685 tmpl
= elem_props_template_init(scene_data
.templates
, b
"TextureFile")
1686 props
= elem_properties(fbx_tex
)
1687 elem_props_template_set(tmpl
, props
, "p_enum", b
"AlphaSource", alpha_source
)
1688 elem_props_template_set(tmpl
, props
, "p_bool", b
"PremultiplyAlpha",
1689 img
.alpha_mode
in {'STRAIGHT'}) # Or is it PREMUL?
1690 elem_props_template_set(tmpl
, props
, "p_enum", b
"CurrentMappingType", mapping
)
1691 if uvset
is not None:
1692 elem_props_template_set(tmpl
, props
, "p_string", b
"UVSet", uvset
)
1693 elem_props_template_set(tmpl
, props
, "p_enum", b
"WrapModeU", wrap_mode
)
1694 elem_props_template_set(tmpl
, props
, "p_enum", b
"WrapModeV", wrap_mode
)
1695 elem_props_template_set(tmpl
, props
, "p_vector_3d", b
"Translation", tex
.translation
)
1696 elem_props_template_set(tmpl
, props
, "p_vector_3d", b
"Rotation", (-r
for r
in tex
.rotation
))
1697 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
))
1698 # UseMaterial should always be ON imho.
1699 elem_props_template_set(tmpl
, props
, "p_bool", b
"UseMaterial", True)
1700 elem_props_template_set(tmpl
, props
, "p_bool", b
"UseMipMap", False)
1701 elem_props_template_finalize(tmpl
, props
)
1703 # No custom properties, since that's not a data-block anymore.
1706 def fbx_data_video_elements(root
, vid
, scene_data
):
1708 Write the actual image data block.
1710 msetts
= scene_data
.settings
.media_settings
1712 vid_key
, _texs
= scene_data
.data_videos
[vid
]
1713 fname_abs
, fname_rel
= _gen_vid_path(vid
, scene_data
)
1715 fbx_vid
= elem_data_single_int64(root
, b
"Video", get_fbx_uuid_from_key(vid_key
))
1716 fbx_vid
.add_string(fbx_name_class(vid
.name
.encode(), b
"Video"))
1717 fbx_vid
.add_string(b
"Clip")
1719 elem_data_single_string(fbx_vid
, b
"Type", b
"Clip")
1722 tmpl
= elem_props_template_init(scene_data
.templates
, b
"Video")
1723 props
= elem_properties(fbx_vid
)
1724 elem_props_template_set(tmpl
, props
, "p_string_url", b
"Path", fname_abs
)
1725 elem_props_template_finalize(tmpl
, props
)
1727 elem_data_single_int32(fbx_vid
, b
"UseMipMap", 0)
1728 elem_data_single_string_unicode(fbx_vid
, b
"Filename", fname_abs
)
1729 elem_data_single_string_unicode(fbx_vid
, b
"RelativeFilename", fname_rel
)
1731 if scene_data
.settings
.media_settings
.embed_textures
:
1732 if vid
.packed_file
is not None:
1733 # We only ever embed a given file once!
1734 if fname_abs
not in msetts
.embedded_set
:
1735 elem_data_single_bytes(fbx_vid
, b
"Content", vid
.packed_file
.data
)
1736 msetts
.embedded_set
.add(fname_abs
)
1738 filepath
= bpy
.path
.abspath(vid
.filepath
)
1739 # We only ever embed a given file once!
1740 if filepath
not in msetts
.embedded_set
:
1742 with
open(filepath
, 'br') as f
:
1743 elem_data_single_bytes(fbx_vid
, b
"Content", f
.read())
1744 except Exception as e
:
1745 print("WARNING: embedding file {} failed ({})".format(filepath
, e
))
1746 elem_data_single_bytes(fbx_vid
, b
"Content", b
"")
1747 msetts
.embedded_set
.add(filepath
)
1748 # Looks like we'd rather not write any 'Content' element in this case (see T44442).
1749 # Sounds suspect, but let's try it!
1751 #~ elem_data_single_bytes(fbx_vid, b"Content", b"")
1753 # Blender currently has no UI for editing custom properties on Images, but the importer will import Image custom
1754 # properties from either a Video Node or a Texture Node, preferring a Video node if one exists. We'll propagate
1755 # these custom properties only to Video Nodes because that is most likely where they were imported from, and Texture
1756 # Nodes are more like Blender's Shader Nodes than Images, which is what we're exporting here.
1757 if scene_data
.settings
.use_custom_props
:
1758 fbx_data_element_custom_properties(props
, vid
)
1762 def fbx_data_armature_elements(root
, arm_obj
, scene_data
):
1765 * Bones "data" (NodeAttribute::LimbNode, contains pretty much nothing!).
1766 * Deformers (i.e. Skin), bind between an armature and a mesh.
1767 ** SubDeformers (i.e. Cluster), one per bone/vgroup pair.
1769 Note armature itself has no data, it is a mere "Null" Model...
1771 mat_world_arm
= arm_obj
.fbx_object_matrix(scene_data
, global_space
=True)
1772 bones
= tuple(bo_obj
for bo_obj
in arm_obj
.bones
if bo_obj
in scene_data
.objects
)
1774 bone_radius_scale
= 33.0
1777 for bo_obj
in bones
:
1779 bo_data_key
= scene_data
.data_bones
[bo_obj
]
1780 fbx_bo
= elem_data_single_int64(root
, b
"NodeAttribute", get_fbx_uuid_from_key(bo_data_key
))
1781 fbx_bo
.add_string(fbx_name_class(bo
.name
.encode(), b
"NodeAttribute"))
1782 fbx_bo
.add_string(b
"LimbNode")
1783 elem_data_single_string(fbx_bo
, b
"TypeFlags", b
"Skeleton")
1785 tmpl
= elem_props_template_init(scene_data
.templates
, b
"Bone")
1786 props
= elem_properties(fbx_bo
)
1787 elem_props_template_set(tmpl
, props
, "p_double", b
"Size", bo
.head_radius
* bone_radius_scale
)
1788 elem_props_template_finalize(tmpl
, props
)
1790 # Custom properties.
1791 if scene_data
.settings
.use_custom_props
:
1792 fbx_data_element_custom_properties(props
, bo
)
1794 # Store Blender bone length - XXX Not much useful actually :/
1795 # (LimbLength can't be used because it is a scale factor 0-1 for the parent-child distance:
1796 # http://docs.autodesk.com/FBX/2014/ENU/FBX-SDK-Documentation/cpp_ref/class_fbx_skeleton.html#a9bbe2a70f4ed82cd162620259e649f0f )
1797 # elem_props_set(props, "p_double", "BlenderBoneLength".encode(), (bo.tail_local - bo.head_local).length, custom=True)
1799 # Skin deformers and BindPoses.
1800 # Note: we might also use Deformers for our "parent to vertex" stuff???
1801 deformer
= scene_data
.data_deformers_skin
.get(arm_obj
, None)
1802 if deformer
is not None:
1803 for me
, (skin_key
, ob_obj
, clusters
) in deformer
.items():
1805 mat_world_obj
, mat_world_bones
= fbx_data_bindpose_element(root
, ob_obj
, me
, scene_data
,
1806 arm_obj
, mat_world_arm
, bones
)
1809 fbx_skin
= elem_data_single_int64(root
, b
"Deformer", get_fbx_uuid_from_key(skin_key
))
1810 fbx_skin
.add_string(fbx_name_class(arm_obj
.name
.encode(), b
"Deformer"))
1811 fbx_skin
.add_string(b
"Skin")
1813 elem_data_single_int32(fbx_skin
, b
"Version", FBX_DEFORMER_SKIN_VERSION
)
1814 elem_data_single_float64(fbx_skin
, b
"Link_DeformAcuracy", 50.0) # Only vague idea what it is...
1816 # Pre-process vertex weights so that the vertices only need to be iterated once.
1818 bo_vg_idx
= {bo_obj
.bdata
.name
: ob
.vertex_groups
[bo_obj
.bdata
.name
].index
1819 for bo_obj
in clusters
.keys() if bo_obj
.bdata
.name
in ob
.vertex_groups
}
1820 valid_idxs
= set(bo_vg_idx
.values())
1821 vgroups
= {vg
.index
: {} for vg
in ob
.vertex_groups
}
1822 for idx
, v
in enumerate(me
.vertices
):
1824 if (w
:= vg
.weight
) and (vg_idx
:= vg
.group
) in valid_idxs
:
1825 vgroups
[vg_idx
][idx
] = w
1827 for bo_obj
, clstr_key
in clusters
.items():
1829 # Find which vertices are affected by this bone/vgroup pair, and matching weights.
1830 # Note we still write a cluster for bones not affecting the mesh, to get 'rest pose' data
1831 # (the TransformBlah matrices).
1832 vg_idx
= bo_vg_idx
.get(bo
.name
, None)
1833 indices
, weights
= ((), ()) if vg_idx
is None or not vgroups
[vg_idx
] else zip(*vgroups
[vg_idx
].items())
1835 # Create the cluster.
1836 fbx_clstr
= elem_data_single_int64(root
, b
"Deformer", get_fbx_uuid_from_key(clstr_key
))
1837 fbx_clstr
.add_string(fbx_name_class(bo
.name
.encode(), b
"SubDeformer"))
1838 fbx_clstr
.add_string(b
"Cluster")
1840 elem_data_single_int32(fbx_clstr
, b
"Version", FBX_DEFORMER_CLUSTER_VERSION
)
1841 # No idea what that user data might be...
1842 fbx_userdata
= elem_data_single_string(fbx_clstr
, b
"UserData", b
"")
1843 fbx_userdata
.add_string(b
"")
1845 elem_data_single_int32_array(fbx_clstr
, b
"Indexes", indices
)
1846 elem_data_single_float64_array(fbx_clstr
, b
"Weights", weights
)
1847 # Transform, TransformLink and TransformAssociateModel matrices...
1848 # They seem to be doublons of BindPose ones??? Have armature (associatemodel) in addition, though.
1849 # WARNING! Even though official FBX API presents Transform in global space,
1850 # **it is stored in bone space in FBX data!** See:
1851 # http://area.autodesk.com/forum/autodesk-fbx/fbx-sdk/why-the-values-return-
1852 # by-fbxcluster-gettransformmatrix-x-not-same-with-the-value-in-ascii-fbx-file/
1853 elem_data_single_float64_array(fbx_clstr
, b
"Transform",
1854 matrix4_to_array(mat_world_bones
[bo_obj
].inverted_safe() @ mat_world_obj
))
1855 elem_data_single_float64_array(fbx_clstr
, b
"TransformLink", matrix4_to_array(mat_world_bones
[bo_obj
]))
1856 elem_data_single_float64_array(fbx_clstr
, b
"TransformAssociateModel", matrix4_to_array(mat_world_arm
))
1859 def fbx_data_leaf_bone_elements(root
, scene_data
):
1860 # Write a dummy leaf bone that is used by applications to show the length of the last bone in a chain
1861 for (node_name
, _par_uuid
, node_uuid
, attr_uuid
, matrix
, hide
, size
) in scene_data
.data_leaf_bones
:
1863 fbx_bo
= elem_data_single_int64(root
, b
"NodeAttribute", attr_uuid
)
1864 fbx_bo
.add_string(fbx_name_class(node_name
.encode(), b
"NodeAttribute"))
1865 fbx_bo
.add_string(b
"LimbNode")
1866 elem_data_single_string(fbx_bo
, b
"TypeFlags", b
"Skeleton")
1868 tmpl
= elem_props_template_init(scene_data
.templates
, b
"Bone")
1869 props
= elem_properties(fbx_bo
)
1870 elem_props_template_set(tmpl
, props
, "p_double", b
"Size", size
)
1871 elem_props_template_finalize(tmpl
, props
)
1874 model
= elem_data_single_int64(root
, b
"Model", node_uuid
)
1875 model
.add_string(fbx_name_class(node_name
.encode(), b
"Model"))
1876 model
.add_string(b
"LimbNode")
1878 elem_data_single_int32(model
, b
"Version", FBX_MODELS_VERSION
)
1880 # Object transform info.
1881 loc
, rot
, scale
= matrix
.decompose()
1882 rot
= rot
.to_euler('XYZ')
1883 rot
= tuple(convert_rad_to_deg_iter(rot
))
1885 tmpl
= elem_props_template_init(scene_data
.templates
, b
"Model")
1886 # For now add only loc/rot/scale...
1887 props
= elem_properties(model
)
1888 # Generated leaf bones are obviously never animated!
1889 elem_props_template_set(tmpl
, props
, "p_lcl_translation", b
"Lcl Translation", loc
)
1890 elem_props_template_set(tmpl
, props
, "p_lcl_rotation", b
"Lcl Rotation", rot
)
1891 elem_props_template_set(tmpl
, props
, "p_lcl_scaling", b
"Lcl Scaling", scale
)
1892 elem_props_template_set(tmpl
, props
, "p_visibility", b
"Visibility", float(not hide
))
1894 # Absolutely no idea what this is, but seems mandatory for validity of the file, and defaults to
1895 # invalid -1 value...
1896 elem_props_template_set(tmpl
, props
, "p_integer", b
"DefaultAttributeIndex", 0)
1898 elem_props_template_set(tmpl
, props
, "p_enum", b
"InheritType", 1) # RSrs
1900 # Those settings would obviously need to be edited in a complete version of the exporter, may depends on
1902 elem_data_single_int32(model
, b
"MultiLayer", 0)
1903 elem_data_single_int32(model
, b
"MultiTake", 0)
1904 # Probably the FbxNode.EShadingMode enum. Full description in fbx_data_object_elements.
1905 elem_data_single_char(model
, b
"Shading", b
"\x01")
1906 elem_data_single_string(model
, b
"Culling", b
"CullingOff")
1908 elem_props_template_finalize(tmpl
, props
)
1911 def fbx_data_object_elements(root
, ob_obj
, scene_data
):
1913 Write the Object (Model) data blocks.
1914 Note this "Model" can also be bone or dupli!
1916 obj_type
= b
"Null" # default, sort of empty...
1918 obj_type
= b
"LimbNode"
1919 elif (ob_obj
.type == 'ARMATURE'):
1920 if scene_data
.settings
.armature_nodetype
== 'ROOT':
1922 elif scene_data
.settings
.armature_nodetype
== 'LIMBNODE':
1923 obj_type
= b
"LimbNode"
1924 else: # Default, preferred option...
1926 elif (ob_obj
.type in BLENDER_OBJECT_TYPES_MESHLIKE
):
1928 elif (ob_obj
.type == 'LIGHT'):
1930 elif (ob_obj
.type == 'CAMERA'):
1931 obj_type
= b
"Camera"
1932 model
= elem_data_single_int64(root
, b
"Model", ob_obj
.fbx_uuid
)
1933 model
.add_string(fbx_name_class(ob_obj
.name
.encode(), b
"Model"))
1934 model
.add_string(obj_type
)
1936 elem_data_single_int32(model
, b
"Version", FBX_MODELS_VERSION
)
1938 # Object transform info.
1939 loc
, rot
, scale
, matrix
, matrix_rot
= ob_obj
.fbx_object_tx(scene_data
)
1940 rot
= tuple(convert_rad_to_deg_iter(rot
))
1942 tmpl
= elem_props_template_init(scene_data
.templates
, b
"Model")
1943 # For now add only loc/rot/scale...
1944 props
= elem_properties(model
)
1945 elem_props_template_set(tmpl
, props
, "p_lcl_translation", b
"Lcl Translation", loc
,
1946 animatable
=True, animated
=((ob_obj
.key
, "Lcl Translation") in scene_data
.animated
))
1947 elem_props_template_set(tmpl
, props
, "p_lcl_rotation", b
"Lcl Rotation", rot
,
1948 animatable
=True, animated
=((ob_obj
.key
, "Lcl Rotation") in scene_data
.animated
))
1949 elem_props_template_set(tmpl
, props
, "p_lcl_scaling", b
"Lcl Scaling", scale
,
1950 animatable
=True, animated
=((ob_obj
.key
, "Lcl Scaling") in scene_data
.animated
))
1951 elem_props_template_set(tmpl
, props
, "p_visibility", b
"Visibility", float(not ob_obj
.hide
))
1953 # Absolutely no idea what this is, but seems mandatory for validity of the file, and defaults to
1954 # invalid -1 value...
1955 elem_props_template_set(tmpl
, props
, "p_integer", b
"DefaultAttributeIndex", 0)
1957 elem_props_template_set(tmpl
, props
, "p_enum", b
"InheritType", 1) # RSrs
1959 # Custom properties.
1960 if scene_data
.settings
.use_custom_props
:
1961 # Here we want customprops from the 'pose' bone, not the 'edit' bone...
1962 bdata
= ob_obj
.bdata_pose_bone
if ob_obj
.is_bone
else ob_obj
.bdata
1963 fbx_data_element_custom_properties(props
, bdata
)
1965 # Those settings would obviously need to be edited in a complete version of the exporter, may depends on
1967 elem_data_single_int32(model
, b
"MultiLayer", 0)
1968 elem_data_single_int32(model
, b
"MultiTake", 0)
1969 # This is probably the FbxNode.EShadingMode enum. Not directly used by the FBX SDK, but the SDK guarantees that the
1970 # value will be passed through from an imported file to an exported one. Common values are 'Y' and 'T'. 'U' and 'W'
1971 # have also been seen in older FBX files. It's not clear which enum member each of these values corresponds to or if
1972 # these values are actually application specific. Blender had been exporting this as a `True` bool for a long time
1973 # seemingly without issue. The '\x01' char is the same value as `True` in raw bytes.
1974 elem_data_single_char(model
, b
"Shading", b
"\x01")
1975 elem_data_single_string(model
, b
"Culling", b
"CullingOff")
1977 if obj_type
== b
"Camera":
1978 # Why, oh why are FBX cameras such a mess???
1979 # And WHY add camera data HERE??? Not even sure this is needed...
1980 render
= scene_data
.scene
.render
1981 width
= render
.resolution_x
* 1.0
1982 height
= render
.resolution_y
* 1.0
1983 elem_props_template_set(tmpl
, props
, "p_enum", b
"ResolutionMode", 0) # Don't know what it means
1984 elem_props_template_set(tmpl
, props
, "p_double", b
"AspectW", width
)
1985 elem_props_template_set(tmpl
, props
, "p_double", b
"AspectH", height
)
1986 elem_props_template_set(tmpl
, props
, "p_bool", b
"ViewFrustum", True)
1987 elem_props_template_set(tmpl
, props
, "p_enum", b
"BackgroundMode", 0) # Don't know what it means
1988 elem_props_template_set(tmpl
, props
, "p_bool", b
"ForegroundTransparent", True)
1990 elem_props_template_finalize(tmpl
, props
)
1993 def fbx_data_animation_elements(root
, scene_data
):
1995 Write animation data.
1997 animations
= scene_data
.animations
2002 for astack_key
, alayers
, alayer_key
, name
, f_start
, f_end
in animations
:
2003 astack
= elem_data_single_int64(root
, b
"AnimationStack", get_fbx_uuid_from_key(astack_key
))
2004 astack
.add_string(fbx_name_class(name
, b
"AnimStack"))
2005 astack
.add_string(b
"")
2007 astack_tmpl
= elem_props_template_init(scene_data
.templates
, b
"AnimationStack")
2008 astack_props
= elem_properties(astack
)
2009 r
= scene_data
.scene
.render
2010 fps
= r
.fps
/ r
.fps_base
2011 start
= int(convert_sec_to_ktime(f_start
/ fps
))
2012 end
= int(convert_sec_to_ktime(f_end
/ fps
))
2013 elem_props_template_set(astack_tmpl
, astack_props
, "p_timestamp", b
"LocalStart", start
)
2014 elem_props_template_set(astack_tmpl
, astack_props
, "p_timestamp", b
"LocalStop", end
)
2015 elem_props_template_set(astack_tmpl
, astack_props
, "p_timestamp", b
"ReferenceStart", start
)
2016 elem_props_template_set(astack_tmpl
, astack_props
, "p_timestamp", b
"ReferenceStop", end
)
2017 elem_props_template_finalize(astack_tmpl
, astack_props
)
2019 # For now, only one layer for all animations.
2020 alayer
= elem_data_single_int64(root
, b
"AnimationLayer", get_fbx_uuid_from_key(alayer_key
))
2021 alayer
.add_string(fbx_name_class(name
, b
"AnimLayer"))
2022 alayer
.add_string(b
"")
2024 for ob_obj
, (alayer_key
, acurvenodes
) in alayers
.items():
2026 # alayer = elem_data_single_int64(root, b"AnimationLayer", get_fbx_uuid_from_key(alayer_key))
2027 # alayer.add_string(fbx_name_class(ob_obj.name.encode(), b"AnimLayer"))
2028 # alayer.add_string(b"")
2030 for fbx_prop
, (acurvenode_key
, acurves
, acurvenode_name
) in acurvenodes
.items():
2031 # Animation curve node.
2032 acurvenode
= elem_data_single_int64(root
, b
"AnimationCurveNode", get_fbx_uuid_from_key(acurvenode_key
))
2033 acurvenode
.add_string(fbx_name_class(acurvenode_name
.encode(), b
"AnimCurveNode"))
2034 acurvenode
.add_string(b
"")
2036 acn_tmpl
= elem_props_template_init(scene_data
.templates
, b
"AnimationCurveNode")
2037 acn_props
= elem_properties(acurvenode
)
2039 for fbx_item
, (acurve_key
, def_value
, (keys
, values
), _acurve_valid
) in acurves
.items():
2040 elem_props_template_set(acn_tmpl
, acn_props
, "p_number", fbx_item
.encode(),
2041 def_value
, animatable
=True)
2043 # Only create Animation curve if needed!
2044 nbr_keys
= len(keys
)
2046 acurve
= elem_data_single_int64(root
, b
"AnimationCurve", get_fbx_uuid_from_key(acurve_key
))
2047 acurve
.add_string(fbx_name_class(b
"", b
"AnimCurve"))
2048 acurve
.add_string(b
"")
2053 1 << 2 |
# interpolation mode, 1 = constant, 2 = linear, 3 = cubic.
2054 1 << 8 |
# tangent mode, 8 = auto, 9 = TCB, 10 = user, 11 = generic break,
2055 1 << 13 |
# tangent mode, 12 = generic clamp, 13 = generic time independent,
2056 1 << 14 |
# tangent mode, 13 + 14 = generic clamp progressive.
2059 # Maybe values controlling TCB & co???
2060 keyattr_datafloat
= (0.0, 0.0, 9.419963346924634e-30, 0.0)
2062 # And now, the *real* data!
2063 elem_data_single_float64(acurve
, b
"Default", def_value
)
2064 elem_data_single_int32(acurve
, b
"KeyVer", FBX_ANIM_KEY_VERSION
)
2065 elem_data_single_int64_array(acurve
, b
"KeyTime", astype_view_signedness(keys
, np
.int64
))
2066 elem_data_single_float32_array(acurve
, b
"KeyValueFloat", values
.astype(np
.float32
, copy
=False))
2067 elem_data_single_int32_array(acurve
, b
"KeyAttrFlags", keyattr_flags
)
2068 elem_data_single_float32_array(acurve
, b
"KeyAttrDataFloat", keyattr_datafloat
)
2069 elem_data_single_int32_array(acurve
, b
"KeyAttrRefCount", (nbr_keys
,))
2071 elem_props_template_finalize(acn_tmpl
, acn_props
)
2074 # ##### Top-level FBX data container. #####
2076 # Mapping Blender -> FBX (principled_socket_name, fbx_name).
2077 PRINCIPLED_TEXTURE_SOCKETS_TO_FBX
= (
2078 # ("diffuse", "diffuse", b"DiffuseFactor"),
2079 ("base_color_texture", b
"DiffuseColor"),
2080 ("alpha_texture", b
"TransparencyFactor"), # Will be inverted in fact, not much we can do really...
2081 # ("base_color_texture", b"TransparentColor"), # Uses diffuse color in Blender!
2082 ("emission_strength_texture", b
"EmissiveFactor"),
2083 ("emission_color_texture", b
"EmissiveColor"),
2084 # ("ambient", "ambient", b"AmbientFactor"),
2085 # ("", "", b"AmbientColor"), # World stuff in Blender, for now ignore...
2086 ("normalmap_texture", b
"NormalMap"),
2087 # Note: unsure about those... :/
2088 # ("", "", b"Bump"),
2089 # ("", "", b"BumpFactor"),
2090 # ("", "", b"DisplacementColor"),
2091 # ("", "", b"DisplacementFactor"),
2092 ("specular_texture", b
"SpecularFactor"),
2093 # ("base_color", b"SpecularColor"), # TODO: use tint?
2094 # See Material template about those two!
2095 ("roughness_texture", b
"Shininess"),
2096 ("roughness_texture", b
"ShininessExponent"),
2097 # ("mirror", "mirror", b"ReflectionColor"),
2098 ("metallic_texture", b
"ReflectionFactor"),
2102 def fbx_skeleton_from_armature(scene
, settings
, arm_obj
, objects
, data_meshes
,
2103 data_bones
, data_deformers_skin
, data_empties
, arm_parents
):
2105 Create skeleton from armature/bones (NodeAttribute/LimbNode and Model/LimbNode), and for each deformed mesh,
2106 create Pose/BindPose(with sub PoseNode) and Deformer/Skin(with Deformer/SubDeformer/Cluster).
2107 Also supports "parent to bone" (simple parent to Model/LimbNode).
2108 arm_parents is a set of tuples (armature, object) for all successful armature bindings.
2110 # We need some data for our armature 'object' too!!!
2111 data_empties
[arm_obj
] = get_blender_empty_key(arm_obj
.bdata
)
2113 arm_data
= arm_obj
.bdata
.data
2115 for bo
in arm_obj
.bones
:
2116 if settings
.use_armature_deform_only
:
2117 if bo
.bdata
.use_deform
:
2120 while bo_par
.is_bone
:
2121 bones
[bo_par
] = True
2122 bo_par
= bo_par
.parent
2123 elif bo
not in bones
: # Do not override if already set in the loop above!
2128 bones
= {bo
: None for bo
, use
in bones
.items() if use
}
2133 data_bones
.update((bo
, get_blender_bone_key(arm_obj
.bdata
, bo
.bdata
)) for bo
in bones
)
2135 for ob_obj
in objects
:
2136 if not ob_obj
.is_deformed_by_armature(arm_obj
):
2139 # Always handled by an Armature modifier...
2141 for mod
in ob_obj
.bdata
.modifiers
:
2142 if mod
.type not in {'ARMATURE'} or not mod
.object:
2144 # We only support vertex groups binding method, not bone envelopes one!
2145 if mod
.object == arm_obj
.bdata
and mod
.use_vertex_groups
:
2152 # Now we have a mesh using this armature.
2153 # Note: bindpose have no relations at all (no connections), so no need for any preprocess for them.
2154 # Create skin & clusters relations (note skins are connected to geometry, *not* model!).
2155 _key
, me
, _free
= data_meshes
[ob_obj
]
2156 clusters
= {bo
: get_blender_bone_cluster_key(arm_obj
.bdata
, me
, bo
.bdata
) for bo
in bones
}
2157 data_deformers_skin
.setdefault(arm_obj
, {})[me
] = (get_blender_armature_skin_key(arm_obj
.bdata
, me
),
2160 # We don't want a regular parent relationship for those in FBX...
2161 arm_parents
.add((arm_obj
, ob_obj
))
2162 # Needed to handle matrices/spaces (since we do not parent them to 'armature' in FBX :/ ).
2163 ob_obj
.parented_to_armature
= True
2165 objects
.update(bones
)
2168 def fbx_generate_leaf_bones(settings
, data_bones
):
2169 # find which bons have no children
2170 child_count
= {bo
: 0 for bo
in data_bones
.keys()}
2171 for bo
in data_bones
.keys():
2172 if bo
.parent
and bo
.parent
.is_bone
:
2173 child_count
[bo
.parent
] += 1
2175 bone_radius_scale
= settings
.global_scale
* 33.0
2177 # generate bone data
2178 leaf_parents
= [bo
for bo
, count
in child_count
.items() if count
== 0]
2180 for parent
in leaf_parents
:
2181 node_name
= parent
.name
+ "_end"
2182 parent_uuid
= parent
.fbx_uuid
2183 parent_key
= parent
.key
2184 node_uuid
= get_fbx_uuid_from_key(parent_key
+ "_end_node")
2185 attr_uuid
= get_fbx_uuid_from_key(parent_key
+ "_end_nodeattr")
2188 size
= parent
.bdata
.head_radius
* bone_radius_scale
2189 bone_length
= (parent
.bdata
.tail_local
- parent
.bdata
.head_local
).length
2190 matrix
= Matrix
.Translation((0, bone_length
, 0))
2191 if settings
.bone_correction_matrix_inv
:
2192 matrix
= settings
.bone_correction_matrix_inv
@ matrix
2193 if settings
.bone_correction_matrix
:
2194 matrix
= matrix
@ settings
.bone_correction_matrix
2195 leaf_bones
.append((node_name
, parent_uuid
, node_uuid
, attr_uuid
, matrix
, hide
, size
))
2200 def fbx_animations_do(scene_data
, ref_id
, f_start
, f_end
, start_zero
, objects
=None, force_keep
=False):
2202 Generate animation data (a single AnimStack) from objects, for a given frame range.
2204 bake_step
= scene_data
.settings
.bake_anim_step
2205 simplify_fac
= scene_data
.settings
.bake_anim_simplify_factor
2206 scene
= scene_data
.scene
2207 depsgraph
= scene_data
.depsgraph
2208 force_keying
= scene_data
.settings
.bake_anim_use_all_bones
2209 force_sek
= scene_data
.settings
.bake_anim_force_startend_keying
2210 gscale
= scene_data
.settings
.global_scale
2212 if objects
is not None:
2213 # Add bones and duplis!
2214 for ob_obj
in tuple(objects
):
2215 if not ob_obj
.is_object
:
2217 if ob_obj
.type == 'ARMATURE':
2218 objects |
= {bo_obj
for bo_obj
in ob_obj
.bones
if bo_obj
in scene_data
.objects
}
2219 for dp_obj
in ob_obj
.dupli_list_gen(depsgraph
):
2220 if dp_obj
in scene_data
.objects
:
2223 objects
= scene_data
.objects
2225 back_currframe
= scene
.frame_current
2229 for ob_obj
in objects
:
2230 if ob_obj
.parented_to_armature
:
2232 ACNW
= AnimationCurveNodeWrapper
2233 loc
, rot
, scale
, _m
, _mr
= ob_obj
.fbx_object_tx(scene_data
)
2234 rot_deg
= tuple(convert_rad_to_deg_iter(rot
))
2235 force_key
= (simplify_fac
== 0.0) or (ob_obj
.is_bone
and force_keying
)
2236 animdata_ob
[ob_obj
] = (ACNW(ob_obj
.key
, 'LCL_TRANSLATION', force_key
, force_sek
, loc
),
2237 ACNW(ob_obj
.key
, 'LCL_ROTATION', force_key
, force_sek
, rot_deg
),
2238 ACNW(ob_obj
.key
, 'LCL_SCALING', force_key
, force_sek
, scale
))
2239 p_rots
[ob_obj
] = rot
2241 force_key
= (simplify_fac
== 0.0)
2242 animdata_shapes
= {}
2244 for me
, (me_key
, _shapes_key
, shapes
) in scene_data
.data_deformers_shape
.items():
2245 # Ignore absolute shape keys for now!
2246 if not me
.shape_keys
.use_relative
:
2248 for shape
, (channel_key
, geom_key
, _shape_verts_co
, _shape_verts_idx
) in shapes
.items():
2249 acnode
= AnimationCurveNodeWrapper(channel_key
, 'SHAPE_KEY', force_key
, force_sek
, (0.0,))
2250 # Sooooo happy to have to twist again like a mad snake... Yes, we need to write those curves twice. :/
2251 acnode
.add_group(me_key
, shape
.name
, shape
.name
, (shape
.name
,))
2252 animdata_shapes
[channel_key
] = (acnode
, me
, shape
)
2254 animdata_cameras
= {}
2255 for cam_obj
, cam_key
in scene_data
.data_cameras
.items():
2256 cam
= cam_obj
.bdata
.data
2257 acnode_lens
= AnimationCurveNodeWrapper(cam_key
, 'CAMERA_FOCAL', force_key
, force_sek
, (cam
.lens
,))
2258 acnode_focus_distance
= AnimationCurveNodeWrapper(cam_key
, 'CAMERA_FOCUS_DISTANCE', force_key
,
2259 force_sek
, (cam
.dof
.focus_distance
,))
2260 animdata_cameras
[cam_key
] = (acnode_lens
, acnode_focus_distance
, cam
)
2262 # Get all parent bdata of animated dupli instances, so that we can quickly identify which instances in
2263 # `depsgraph.object_instances` are animated and need their ObjectWrappers' matrices updated each frame.
2264 dupli_parent_bdata
= {dup
.get_parent().bdata
for dup
in animdata_ob
if dup
.is_dupli
}
2265 has_animated_duplis
= bool(dupli_parent_bdata
)
2267 # Initialize keyframe times array. Each AnimationCurveNodeWrapper will share the same instance.
2268 # `np.arange` excludes the `stop` argument like when using `range`, so we use np.nextafter to get the next
2269 # representable value after f_end and use that as the `stop` argument instead.
2270 currframes
= np
.arange(f_start
, np
.nextafter(f_end
, np
.inf
), step
=bake_step
)
2272 # Convert from Blender time to FBX time.
2273 fps
= scene
.render
.fps
/ scene
.render
.fps_base
2274 real_currframes
= currframes
- f_start
if start_zero
else currframes
2275 real_currframes
= (real_currframes
/ fps
* FBX_KTIME
).astype(np
.int64
)
2277 # Generator that yields the animated values of each frame in order.
2278 def frame_values_gen():
2279 # Precalculate integer frames and subframes.
2280 int_currframes
= currframes
.astype(int)
2281 subframes
= currframes
- int_currframes
2283 # Create simpler iterables that return only the values we care about.
2284 animdata_shapes_only
= [shape
for _anim_shape
, _me
, shape
in animdata_shapes
.values()]
2285 animdata_cameras_only
= [camera
for _anim_camera_lens
, _anim_camera_focus_distance
, camera
2286 in animdata_cameras
.values()]
2287 # Previous frame's rotation for each object in animdata_ob, this will be updated each frame.
2288 animdata_ob_p_rots
= p_rots
.values()
2290 # Iterate through each frame and yield the values for that frame.
2291 # Iterating .data, the memoryview of an array, is faster than iterating the array directly.
2292 for int_currframe
, subframe
in zip(int_currframes
.data
, subframes
.data
):
2293 scene
.frame_set(int_currframe
, subframe
=subframe
)
2295 if has_animated_duplis
:
2296 # Changing the scene's frame invalidates existing dupli instances. To get the updated matrices of duplis
2297 # for this frame, we must get the duplis from the depsgraph again.
2298 for dup
in depsgraph
.object_instances
:
2299 if (parent
:= dup
.parent
) and parent
.original
in dupli_parent_bdata
:
2300 # ObjectWrapper caches its instances. Attempting to create a new instance updates the existing
2301 # ObjectWrapper instance with the current frame's matrix and then returns the existing instance.
2304 for ob_obj
, p_rot
in zip(animdata_ob
, animdata_ob_p_rots
):
2305 # We compute baked loc/rot/scale for all objects (rot being euler-compat with previous value!).
2306 loc
, rot
, scale
, _m
, _mr
= ob_obj
.fbx_object_tx(scene_data
, rot_euler_compat
=p_rot
)
2307 next_p_rots
.append(rot
)
2311 animdata_ob_p_rots
= next_p_rots
2312 for shape
in animdata_shapes_only
:
2314 for camera
in animdata_cameras_only
:
2316 yield camera
.dof
.focus_distance
2318 # Providing `count` to np.fromiter pre-allocates the array, avoiding extra memory allocations while iterating.
2319 num_ob_values
= len(animdata_ob
) * 9 # Location, rotation and scale, each of which have x, y, and z components
2320 num_shape_values
= len(animdata_shapes
) # Only 1 value per shape key
2321 num_camera_values
= len(animdata_cameras
) * 2 # Focal length (`.lens`) and focus distance
2322 num_values_per_frame
= num_ob_values
+ num_shape_values
+ num_camera_values
2323 num_frames
= len(real_currframes
)
2324 all_values_flat
= np
.fromiter(frame_values_gen(), dtype
=float, count
=num_frames
* num_values_per_frame
)
2326 # Restore the scene's current frame.
2327 scene
.frame_set(back_currframe
, subframe
=0.0)
2329 # View such that each column is all values for a single frame and each row is all values for a single curve.
2330 all_values
= all_values_flat
.reshape(num_frames
, num_values_per_frame
).T
2331 # Split into views of the arrays for each curve type.
2332 split_at
= [num_ob_values
, num_shape_values
, num_camera_values
]
2333 # For unequal sized splits, np.split takes indices to split at, which can be acquired through a cumulative sum
2335 # The last value isn't needed, because the last split is assumed to go to the end of the array.
2336 split_at
= np
.cumsum(split_at
[:-1])
2337 all_ob_values
, all_shape_key_values
, all_camera_values
= np
.split(all_values
, split_at
)
2341 # Set location/rotation/scale curves.
2342 # Split into equal sized views of the arrays for each object.
2343 split_into
= len(animdata_ob
)
2344 per_ob_values
= np
.split(all_ob_values
, split_into
) if split_into
> 0 else ()
2345 for anims
, ob_values
in zip(animdata_ob
.values(), per_ob_values
):
2346 # Split again into equal sized views of the location, rotation and scaling arrays.
2347 loc_xyz
, rot_xyz
, sca_xyz
= np
.split(ob_values
, 3)
2348 # In-place convert from Blender rotation to FBX rotation.
2349 np
.rad2deg(rot_xyz
, out
=rot_xyz
)
2351 anim_loc
, anim_rot
, anim_scale
= anims
2352 anim_loc
.set_keyframes(real_currframes
, loc_xyz
)
2353 anim_rot
.set_keyframes(real_currframes
, rot_xyz
)
2354 anim_scale
.set_keyframes(real_currframes
, sca_xyz
)
2355 all_anims
.extend(anims
)
2357 # Set shape key curves.
2358 # There's only one array per shape key, so there's no need to split `all_shape_key_values`.
2359 for (anim_shape
, _me
, _shape
), shape_key_values
in zip(animdata_shapes
.values(), all_shape_key_values
):
2360 # In-place convert from Blender Shape Key Value to FBX Deform Percent.
2361 shape_key_values
*= 100.0
2362 anim_shape
.set_keyframes(real_currframes
, shape_key_values
)
2363 all_anims
.append(anim_shape
)
2365 # Set camera curves.
2366 # Split into equal sized views of the arrays for each camera.
2367 split_into
= len(animdata_cameras
)
2368 per_camera_values
= np
.split(all_camera_values
, split_into
) if split_into
> 0 else ()
2369 zipped
= zip(animdata_cameras
.values(), per_camera_values
)
2370 for (anim_camera_lens
, anim_camera_focus_distance
, _camera
), (lens_values
, focus_distance_values
) in zipped
:
2371 # In-place convert from Blender focus distance to FBX.
2372 focus_distance_values
*= (1000 * gscale
)
2373 anim_camera_lens
.set_keyframes(real_currframes
, lens_values
)
2374 anim_camera_focus_distance
.set_keyframes(real_currframes
, focus_distance_values
)
2375 all_anims
.append(anim_camera_lens
)
2376 all_anims
.append(anim_camera_focus_distance
)
2380 # And now, produce final data (usable by FBX export code)
2381 for anim
in all_anims
:
2382 anim
.simplify(simplify_fac
, bake_step
, force_keep
)
2385 for obj_key
, group_key
, group
, fbx_group
, fbx_gname
in anim
.get_final_data(scene
, ref_id
, force_keep
):
2386 anim_data
= animations
.setdefault(obj_key
, ("dummy_unused_key", {}))
2387 anim_data
[1][fbx_group
] = (group_key
, group
, fbx_gname
)
2389 astack_key
= get_blender_anim_stack_key(scene
, ref_id
)
2390 alayer_key
= get_blender_anim_layer_key(scene
, ref_id
)
2391 name
= (get_blenderID_name(ref_id
) if ref_id
else scene
.name
).encode()
2397 return (astack_key
, animations
, alayer_key
, name
, f_start
, f_end
) if animations
else None
2400 def fbx_animations(scene_data
):
2402 Generate global animation data from objects.
2404 scene
= scene_data
.scene
2410 def add_anim(animations
, animated
, anim
):
2411 nonlocal frame_start
, frame_end
2412 if anim
is not None:
2413 animations
.append(anim
)
2414 f_start
, f_end
= anim
[4:6]
2415 if f_start
< frame_start
:
2416 frame_start
= f_start
2417 if f_end
> frame_end
:
2420 _astack_key
, astack
, _alayer_key
, _name
, _fstart
, _fend
= anim
2421 for elem_key
, (alayer_key
, acurvenodes
) in astack
.items():
2422 for fbx_prop
, (acurvenode_key
, acurves
, acurvenode_name
) in acurvenodes
.items():
2423 animated
.add((elem_key
, fbx_prop
))
2425 # Per-NLA strip animstacks.
2426 if scene_data
.settings
.bake_anim_use_nla_strips
:
2429 for ob_obj
in scene_data
.objects
:
2430 # NLA tracks only for objects, not bones!
2431 if not ob_obj
.is_object
:
2433 ob
= ob_obj
.bdata
# Back to real Blender Object.
2434 if not ob
.animation_data
:
2437 # Some actions are read-only, one cause is being in NLA tweakmode
2438 restore_use_tweak_mode
= ob
.animation_data
.use_tweak_mode
2439 if ob
.animation_data
.is_property_readonly('action'):
2440 ob
.animation_data
.use_tweak_mode
= False
2442 # We have to remove active action from objects, it overwrites strips actions otherwise...
2443 ob_actions
.append((ob
, ob
.animation_data
.action
, restore_use_tweak_mode
))
2444 ob
.animation_data
.action
= None
2445 for track
in ob
.animation_data
.nla_tracks
:
2448 for strip
in track
.strips
:
2451 strips
.append(strip
)
2454 for strip
in strips
:
2456 add_anim(animations
, animated
,
2457 fbx_animations_do(scene_data
, strip
, strip
.frame_start
, strip
.frame_end
, True, force_keep
=True))
2459 scene
.frame_set(scene
.frame_current
, subframe
=0.0)
2461 for strip
in strips
:
2464 for ob
, ob_act
, restore_use_tweak_mode
in ob_actions
:
2465 ob
.animation_data
.action
= ob_act
2466 ob
.animation_data
.use_tweak_mode
= restore_use_tweak_mode
2469 if scene_data
.settings
.bake_anim_use_all_actions
:
2470 def validate_actions(act
, path_resolve
):
2471 for fc
in act
.fcurves
:
2472 data_path
= fc
.data_path
2474 data_path
= data_path
+ "[%d]" % fc
.array_index
2476 path_resolve(data_path
)
2478 return False # Invalid.
2479 return True # Valid.
2481 def restore_object(ob_to
, ob_from
):
2482 # Restore org state of object (ugh :/ ).
2484 'location', 'rotation_quaternion', 'rotation_axis_angle', 'rotation_euler', 'rotation_mode', 'scale',
2485 'delta_location', 'delta_rotation_euler', 'delta_rotation_quaternion', 'delta_scale',
2486 'lock_location', 'lock_rotation', 'lock_rotation_w', 'lock_rotations_4d', 'lock_scale',
2487 'tag', 'track_axis', 'up_axis', 'active_material', 'active_material_index',
2488 'matrix_parent_inverse', 'empty_display_type', 'empty_display_size', 'empty_image_offset', 'pass_index',
2489 'color', 'hide_viewport', 'hide_select', 'hide_render', 'instance_type',
2490 'use_instance_vertices_rotation', 'use_instance_faces_scale', 'instance_faces_scale',
2491 'display_type', 'show_bounds', 'display_bounds_type', 'show_name', 'show_axis', 'show_texture_space',
2492 'show_wire', 'show_all_edges', 'show_transparent', 'show_in_front',
2493 'show_only_shape_key', 'use_shape_key_edit_mode', 'active_shape_key_index',
2496 if not ob_to
.is_property_readonly(p
):
2497 setattr(ob_to
, p
, getattr(ob_from
, p
))
2499 for ob_obj
in scene_data
.objects
:
2500 # Actions only for objects, not bones!
2501 if not ob_obj
.is_object
:
2504 ob
= ob_obj
.bdata
# Back to real Blender Object.
2506 if not ob
.animation_data
:
2507 continue # Do not export animations for objects that are absolutely not animated, see T44386.
2509 if ob
.animation_data
.is_property_readonly('action'):
2510 continue # Cannot re-assign 'active action' to this object (usually related to NLA usage, see T48089).
2512 # We can't play with animdata and actions and get back to org state easily.
2513 # So we have to add a temp copy of the object to the scene, animate it, and remove it... :/
2515 # Great, have to handle bones as well if needed...
2516 pbones_matrices
= [pbo
.matrix_basis
.copy() for pbo
in ob
.pose
.bones
] if ob
.type == 'ARMATURE' else ...
2518 org_act
= ob
.animation_data
.action
2519 path_resolve
= ob
.path_resolve
2521 for act
in bpy
.data
.actions
:
2522 # For now, *all* paths in the action must be valid for the object, to validate the action.
2523 # Unless that action was already assigned to the object!
2524 if act
!= org_act
and not validate_actions(act
, path_resolve
):
2526 ob
.animation_data
.action
= act
2527 frame_start
, frame_end
= act
.frame_range
# sic!
2528 add_anim(animations
, animated
,
2529 fbx_animations_do(scene_data
, (ob
, act
), frame_start
, frame_end
, True,
2530 objects
={ob_obj}
, force_keep
=True))
2532 if pbones_matrices
is not ...:
2533 for pbo
, mat
in zip(ob
.pose
.bones
, pbones_matrices
):
2534 pbo
.matrix_basis
= mat
.copy()
2535 ob
.animation_data
.action
= org_act
2536 restore_object(ob
, ob_copy
)
2537 scene
.frame_set(scene
.frame_current
, subframe
=0.0)
2539 if pbones_matrices
is not ...:
2540 for pbo
, mat
in zip(ob
.pose
.bones
, pbones_matrices
):
2541 pbo
.matrix_basis
= mat
.copy()
2542 ob
.animation_data
.action
= org_act
2544 bpy
.data
.objects
.remove(ob_copy
)
2545 scene
.frame_set(scene
.frame_current
, subframe
=0.0)
2547 # Global (containing everything) animstack, only if not exporting NLA strips and/or all actions.
2548 if not scene_data
.settings
.bake_anim_use_nla_strips
and not scene_data
.settings
.bake_anim_use_all_actions
:
2549 add_anim(animations
, animated
, fbx_animations_do(scene_data
, None, scene
.frame_start
, scene
.frame_end
, False))
2551 # Be sure to update all matrices back to org state!
2552 scene
.frame_set(scene
.frame_current
, subframe
=0.0)
2554 return animations
, animated
, frame_start
, frame_end
2557 def fbx_data_from_scene(scene
, depsgraph
, settings
):
2559 Do some pre-processing over scene's data...
2561 objtypes
= settings
.object_types
2562 dp_objtypes
= objtypes
- {'ARMATURE'} # Armatures are not supported as dupli instances currently...
2566 # ##### Gathering data...
2568 perfmon
.step("FBX export prepare: Wrapping Objects...")
2570 # This is rather simple for now, maybe we could end generating templates with most-used values
2571 # instead of default ones?
2572 objects
= {} # Because we do not have any ordered set...
2573 for ob
in settings
.context_objects
:
2574 if ob
.type not in objtypes
:
2576 ob_obj
= ObjectWrapper(ob
)
2577 objects
[ob_obj
] = None
2579 for dp_obj
in ob_obj
.dupli_list_gen(depsgraph
):
2580 if dp_obj
.type not in dp_objtypes
:
2582 objects
[dp_obj
] = None
2584 perfmon
.step("FBX export prepare: Wrapping Data (lamps, cameras, empties)...")
2586 data_lights
= {ob_obj
.bdata
.data
: get_blenderID_key(ob_obj
.bdata
.data
)
2587 for ob_obj
in objects
if ob_obj
.type == 'LIGHT'}
2588 # Unfortunately, FBX camera data contains object-level data (like position, orientation, etc.)...
2589 data_cameras
= {ob_obj
: get_blenderID_key(ob_obj
.bdata
.data
)
2590 for ob_obj
in objects
if ob_obj
.type == 'CAMERA'}
2591 # Yep! Contains nothing, but needed!
2592 data_empties
= {ob_obj
: get_blender_empty_key(ob_obj
.bdata
)
2593 for ob_obj
in objects
if ob_obj
.type == 'EMPTY'}
2595 perfmon
.step("FBX export prepare: Wrapping Meshes...")
2598 for ob_obj
in objects
:
2599 if ob_obj
.type not in BLENDER_OBJECT_TYPES_MESHLIKE
:
2604 # Do not want to systematically recreate a new mesh for dupliobject instances, kind of break purpose of those.
2606 org_ob_obj
= ObjectWrapper(ob
) # We get the "real" object wrapper from that dupli instance.
2607 if org_ob_obj
in data_meshes
:
2608 data_meshes
[ob_obj
] = data_meshes
[org_ob_obj
]
2611 # There are 4 different cases for what we need to do with the original data of each Object:
2612 # 1) The original data can be used without changes.
2613 # 2) A copy of the original data needs to be made.
2614 # - If an export option modifies the data, e.g. Triangulate Faces is enabled.
2615 # - If the Object has Object-linked materials. This is because our current mapping of materials to FBX requires
2616 # that multiple Objects sharing a single mesh must have the same materials.
2617 # 3) The Object needs to be converted to a mesh.
2618 # - All mesh-like Objects that are not meshes need to be converted to a mesh in order to be exported.
2619 # 4) The Object needs to be evaluated and then converted to a mesh.
2620 # - Whenever use_mesh_modifiers is enabled and either there are modifiers to apply or the Object needs to be
2621 # converted to a mesh.
2622 # If multiple cases apply to an Object, then only the last applicable case is relevant.
2623 do_copy
= any(ms
.link
== 'OBJECT' for ms
in ob
.material_slots
) or settings
.use_triangles
2624 do_convert
= ob
.type in BLENDER_OTHER_OBJECT_TYPES
2625 do_evaluate
= do_convert
and settings
.use_mesh_modifiers
2627 # If the Object is a mesh, and we're applying modifiers, check if there are actually any modifiers to apply.
2628 # If there are then the mesh will need to be evaluated, and we may need to make some temporary changes to the
2629 # modifiers or scene before the mesh is evaluated.
2630 backup_pose_positions
= []
2632 if ob
.type == 'MESH' and settings
.use_mesh_modifiers
:
2633 # No need to create a new mesh in this case, if no modifier is active!
2635 for mod
in ob
.modifiers
:
2636 # For meshes, when armature export is enabled, disable Armature modifiers here!
2637 # XXX Temp hacks here since currently we only have access to a viewport depsgraph...
2639 # NOTE: We put armature to the rest pose instead of disabling it so we still
2640 # have vertex groups in the evaluated mesh.
2641 if mod
.type == 'ARMATURE' and 'ARMATURE' in settings
.object_types
:
2643 if object and object.type == 'ARMATURE':
2644 armature
= object.data
2645 # If armature is already in REST position, there's nothing to back-up
2646 # This cuts down on export time dramatically, if all armatures are already in REST position
2647 # by not triggering dependency graph update
2648 if armature
.pose_position
!= 'REST':
2649 backup_pose_positions
.append((armature
, armature
.pose_position
))
2650 armature
.pose_position
= 'REST'
2651 elif mod
.show_render
or mod
.show_viewport
:
2652 # If exporting with subsurf collect the last Catmull-Clark subsurf modifier
2653 # and disable it. We can use the original data as long as this is the first
2654 # found applicable subsurf modifier.
2655 if settings
.use_subsurf
and mod
.type == 'SUBSURF' and mod
.subdivision_type
== 'CATMULL_CLARK':
2661 if settings
.use_subsurf
and last_subsurf
:
2662 # XXX: When exporting with subsurf information temporarily disable
2663 # the last subsurf modifier.
2664 tmp_mods
.append((last_subsurf
, last_subsurf
.show_render
, last_subsurf
.show_viewport
))
2667 # If modifiers has been altered need to update dependency graph.
2668 if backup_pose_positions
or tmp_mods
:
2670 ob_to_convert
= ob
.evaluated_get(depsgraph
)
2671 # NOTE: The dependency graph might be re-evaluating multiple times, which could
2672 # potentially free the mesh created early on. So we put those meshes to bmain and
2673 # free them afterwards. Not ideal but ensures correct ownership.
2674 tmp_me
= bpy
.data
.meshes
.new_from_object(
2675 ob_to_convert
, preserve_all_data_layers
=True, depsgraph
=depsgraph
)
2677 # Usually the materials of the evaluated object will be the same, but modifiers, such as Geometry Nodes,
2678 # can change the materials.
2679 orig_mats
= tuple(slot
.material
for slot
in ob
.material_slots
)
2680 eval_mats
= tuple(slot
.material
.original
if slot
.material
else None
2681 for slot
in ob_to_convert
.material_slots
)
2682 if orig_mats
!= eval_mats
:
2683 # Override the default behaviour of getting materials from ob_obj.bdata.material_slots.
2684 ob_obj
.override_materials
= eval_mats
2686 tmp_me
= bpy
.data
.meshes
.new_from_object(ob
, preserve_all_data_layers
=True, depsgraph
=depsgraph
)
2688 # bpy.data.meshes.new_from_object removes shape keys (see #104714), so create a copy of the mesh instead.
2689 tmp_me
= ob
.data
.copy()
2694 # Use the original data of this Object.
2695 data_meshes
[ob_obj
] = (get_blenderID_key(ob
.data
), ob
.data
, False)
2697 # Triangulate the mesh if requested
2698 if settings
.use_triangles
:
2701 bm
.from_mesh(tmp_me
)
2702 bmesh
.ops
.triangulate(bm
, faces
=bm
.faces
)
2705 # A temporary mesh was created for this Object, which should be deleted once the export is complete.
2706 data_meshes
[ob_obj
] = (get_blenderID_key(tmp_me
), tmp_me
, True)
2708 # Change armatures back.
2709 for armature
, pose_position
in backup_pose_positions
:
2710 print((armature
, pose_position
))
2711 armature
.pose_position
= pose_position
2712 # Update now, so we don't leave modified state after last object was exported.
2713 # Re-enable temporary disabled modifiers.
2714 for mod
, show_render
, show_viewport
in tmp_mods
:
2715 mod
.show_render
= show_render
2716 mod
.show_viewport
= show_viewport
2717 if backup_pose_positions
or tmp_mods
:
2720 # In case "real" source object of that dupli did not yet still existed in data_meshes, create it now!
2721 if org_ob_obj
is not None:
2722 data_meshes
[org_ob_obj
] = data_meshes
[ob_obj
]
2724 perfmon
.step("FBX export prepare: Wrapping ShapeKeys...")
2727 data_deformers_shape
= {}
2728 geom_mat_co
= settings
.global_matrix
if settings
.bake_space_transform
else None
2729 co_bl_dtype
= np
.single
2730 co_fbx_dtype
= np
.float64
2731 idx_fbx_dtype
= np
.int32
2733 def empty_verts_fallbacks():
2734 """Create fallback arrays for when there are no verts"""
2735 # FBX does not like empty shapes (makes Unity crash e.g.).
2736 # To prevent this, we add a vertex that does nothing, but it keeps the shape key intact
2737 single_vert_co
= np
.zeros((1, 3), dtype
=co_fbx_dtype
)
2738 single_vert_idx
= np
.zeros(1, dtype
=idx_fbx_dtype
)
2739 return single_vert_co
, single_vert_idx
2741 for me_key
, me
, _free
in data_meshes
.values():
2742 if not (me
.shape_keys
and len(me
.shape_keys
.key_blocks
) > 1): # We do not want basis-only relative skeys...
2744 if me
in data_deformers_shape
:
2747 shapes_key
= get_blender_mesh_shape_key(me
)
2749 sk_base
= me
.shape_keys
.key_blocks
[0]
2751 # Get and cache only the cos that we need
2753 def sk_cos(shape_key
):
2754 if shape_key
== sk_base
:
2755 _cos
= MESH_ATTRIBUTE_POSITION
.to_ndarray(me
.attributes
)
2757 _cos
= np
.empty(len(me
.vertices
) * 3, dtype
=co_bl_dtype
)
2758 shape_key
.points
.foreach_get("co", _cos
)
2759 return vcos_transformed(_cos
, geom_mat_co
, co_fbx_dtype
)
2761 for shape
in me
.shape_keys
.key_blocks
[1:]:
2762 # Only write vertices really different from base coordinates!
2763 relative_key
= shape
.relative_key
2764 if shape
== relative_key
:
2765 # Shape is its own relative key, so it does nothing
2766 shape_verts_co
, shape_verts_idx
= empty_verts_fallbacks()
2768 sv_cos
= sk_cos(shape
)
2769 ref_cos
= sk_cos(shape
.relative_key
)
2771 # Exclude cos similar to ref_cos and get the indices of the cos that remain
2772 shape_verts_co
, shape_verts_idx
= shape_difference_exclude_similar(sv_cos
, ref_cos
)
2774 if not shape_verts_co
.size
:
2775 shape_verts_co
, shape_verts_idx
= empty_verts_fallbacks()
2777 # Ensure the indices are of the correct type
2778 shape_verts_idx
= astype_view_signedness(shape_verts_idx
, idx_fbx_dtype
)
2780 channel_key
, geom_key
= get_blender_mesh_shape_channel_key(me
, shape
)
2781 data
= (channel_key
, geom_key
, shape_verts_co
, shape_verts_idx
)
2782 data_deformers_shape
.setdefault(me
, (me_key
, shapes_key
, {}))[2][shape
] = data
2786 perfmon
.step("FBX export prepare: Wrapping Armatures...")
2789 data_deformers_skin
= {}
2792 for ob_obj
in tuple(objects
):
2793 if not (ob_obj
.is_object
and ob_obj
.type in {'ARMATURE'}):
2795 fbx_skeleton_from_armature(scene
, settings
, ob_obj
, objects
, data_meshes
,
2796 data_bones
, data_deformers_skin
, data_empties
, arm_parents
)
2798 # Generate leaf bones
2799 data_leaf_bones
= []
2800 if settings
.add_leaf_bones
:
2801 data_leaf_bones
= fbx_generate_leaf_bones(settings
, data_bones
)
2803 perfmon
.step("FBX export prepare: Wrapping World...")
2805 # Some world settings are embedded in FBX materials...
2807 data_world
= {scene
.world
: get_blenderID_key(scene
.world
)}
2811 perfmon
.step("FBX export prepare: Wrapping Materials...")
2813 # TODO: Check all the material stuff works even when they are linked to Objects
2814 # (we can then have the same mesh used with different materials...).
2815 # *Should* work, as FBX always links its materials to Models (i.e. objects).
2816 # XXX However, material indices would probably break...
2818 for ob_obj
in objects
:
2819 # If obj is not a valid object for materials, wrapper will just return an empty tuple...
2820 for ma
in ob_obj
.materials
:
2822 continue # Empty slots!
2823 # Note theoretically, FBX supports any kind of materials, even GLSL shaders etc.
2824 # However, I doubt anything else than Lambert/Phong is really portable!
2825 # Note we want to keep a 'dummy' empty material even when we can't really support it, see T41396.
2826 ma_data
= data_materials
.setdefault(ma
, (get_blenderID_key(ma
), []))
2827 ma_data
[1].append(ob_obj
)
2829 perfmon
.step("FBX export prepare: Wrapping Textures...")
2831 # Note FBX textures also hold their mapping info.
2832 # TODO: Support layers?
2834 # FbxVideo also used to store static images...
2836 # For now, do not use world textures, don't think they can be linked to anything FBX wise...
2837 for ma
in data_materials
.keys():
2838 # Note: with nodal shaders, we'll could be generating much more textures, but that's kind of unavoidable,
2839 # given that textures actually do not exist anymore in material context in Blender...
2840 ma_wrap
= node_shader_utils
.PrincipledBSDFWrapper(ma
, is_readonly
=True)
2841 for sock_name
, fbx_name
in PRINCIPLED_TEXTURE_SOCKETS_TO_FBX
:
2842 tex
= getattr(ma_wrap
, sock_name
)
2843 if tex
is None or tex
.image
is None:
2845 blender_tex_key
= (ma
, sock_name
)
2846 data_textures
[blender_tex_key
] = (get_blender_nodetexture_key(*blender_tex_key
), fbx_name
)
2849 vid_data
= data_videos
.setdefault(img
, (get_blenderID_key(img
), []))
2850 vid_data
[1].append(blender_tex_key
)
2852 perfmon
.step("FBX export prepare: Wrapping Animations...")
2857 frame_start
= scene
.frame_start
2858 frame_end
= scene
.frame_end
2859 if settings
.bake_anim
:
2860 # From objects & bones only for a start.
2861 # Kind of hack, we need a temp scene_data for object's space handling to bake animations...
2862 tmp_scdata
= FBXExportData(
2864 settings
, scene
, depsgraph
, objects
, None, None, 0.0, 0.0,
2865 data_empties
, data_lights
, data_cameras
, data_meshes
, None,
2866 data_bones
, data_leaf_bones
, data_deformers_skin
, data_deformers_shape
,
2867 data_world
, data_materials
, data_textures
, data_videos
,
2869 animations
, animated
, frame_start
, frame_end
= fbx_animations(tmp_scdata
)
2871 # ##### Creation of templates...
2873 perfmon
.step("FBX export prepare: Generating templates...")
2876 templates
[b
"GlobalSettings"] = fbx_template_def_globalsettings(scene
, settings
, nbr_users
=1)
2879 templates
[b
"Null"] = fbx_template_def_null(scene
, settings
, nbr_users
=len(data_empties
))
2882 templates
[b
"Light"] = fbx_template_def_light(scene
, settings
, nbr_users
=len(data_lights
))
2885 templates
[b
"Camera"] = fbx_template_def_camera(scene
, settings
, nbr_users
=len(data_cameras
))
2888 templates
[b
"Bone"] = fbx_template_def_bone(scene
, settings
, nbr_users
=len(data_bones
))
2891 nbr
= len({me_key
for me_key
, _me
, _free
in data_meshes
.values()})
2892 if data_deformers_shape
:
2893 nbr
+= sum(len(shapes
[2]) for shapes
in data_deformers_shape
.values())
2894 templates
[b
"Geometry"] = fbx_template_def_geometry(scene
, settings
, nbr_users
=nbr
)
2897 templates
[b
"Model"] = fbx_template_def_model(scene
, settings
, nbr_users
=len(objects
))
2900 # Number of Pose|BindPose elements should be the same as number of meshes-parented-to-armatures
2901 templates
[b
"BindPose"] = fbx_template_def_pose(scene
, settings
, nbr_users
=len(arm_parents
))
2903 if data_deformers_skin
or data_deformers_shape
:
2905 if data_deformers_skin
:
2906 nbr
+= len(data_deformers_skin
)
2907 nbr
+= sum(len(clusters
) for def_me
in data_deformers_skin
.values() for a
, b
, clusters
in def_me
.values())
2908 if data_deformers_shape
:
2909 nbr
+= len(data_deformers_shape
)
2910 nbr
+= sum(len(shapes
[2]) for shapes
in data_deformers_shape
.values())
2912 templates
[b
"Deformers"] = fbx_template_def_deformer(scene
, settings
, nbr_users
=nbr
)
2914 # No world support in FBX...
2917 templates[b"World"] = fbx_template_def_world(scene, settings, nbr_users=len(data_world))
2921 templates
[b
"Material"] = fbx_template_def_material(scene
, settings
, nbr_users
=len(data_materials
))
2924 templates
[b
"TextureFile"] = fbx_template_def_texture_file(scene
, settings
, nbr_users
=len(data_textures
))
2927 templates
[b
"Video"] = fbx_template_def_video(scene
, settings
, nbr_users
=len(data_videos
))
2930 nbr_astacks
= len(animations
)
2933 for _astack_key
, astack
, _al
, _n
, _fs
, _fe
in animations
:
2934 for _alayer_key
, alayer
in astack
.values():
2935 for _acnode_key
, acnode
, _acnode_name
in alayer
.values():
2937 for _acurve_key
, _dval
, (keys
, _values
), acurve_valid
in acnode
.values():
2941 templates
[b
"AnimationStack"] = fbx_template_def_animstack(scene
, settings
, nbr_users
=nbr_astacks
)
2942 # Would be nice to have one layer per animated object, but this seems tricky and not that well supported.
2943 # So for now, only one layer per anim stack.
2944 templates
[b
"AnimationLayer"] = fbx_template_def_animlayer(scene
, settings
, nbr_users
=nbr_astacks
)
2945 templates
[b
"AnimationCurveNode"] = fbx_template_def_animcurvenode(scene
, settings
, nbr_users
=nbr_acnodes
)
2946 templates
[b
"AnimationCurve"] = fbx_template_def_animcurve(scene
, settings
, nbr_users
=nbr_acurves
)
2948 templates_users
= sum(tmpl
.nbr_users
for tmpl
in templates
.values())
2950 # ##### Creation of connections...
2952 perfmon
.step("FBX export prepare: Generating Connections...")
2956 # Objects (with classical parenting).
2957 for ob_obj
in objects
:
2958 # Bones are handled later.
2959 if not ob_obj
.is_bone
:
2960 par_obj
= ob_obj
.parent
2961 # Meshes parented to armature are handled separately, yet we want the 'no parent' connection (0).
2962 if par_obj
and ob_obj
.has_valid_parent(objects
) and (par_obj
, ob_obj
) not in arm_parents
:
2963 connections
.append((b
"OO", ob_obj
.fbx_uuid
, par_obj
.fbx_uuid
, None))
2965 connections
.append((b
"OO", ob_obj
.fbx_uuid
, 0, None))
2967 # Armature & Bone chains.
2968 for bo_obj
in data_bones
.keys():
2969 par_obj
= bo_obj
.parent
2970 if par_obj
not in objects
:
2972 connections
.append((b
"OO", bo_obj
.fbx_uuid
, par_obj
.fbx_uuid
, None))
2975 for ob_obj
in objects
:
2977 bo_data_key
= data_bones
[ob_obj
]
2978 connections
.append((b
"OO", get_fbx_uuid_from_key(bo_data_key
), ob_obj
.fbx_uuid
, None))
2980 if ob_obj
.type == 'LIGHT':
2981 light_key
= data_lights
[ob_obj
.bdata
.data
]
2982 connections
.append((b
"OO", get_fbx_uuid_from_key(light_key
), ob_obj
.fbx_uuid
, None))
2983 elif ob_obj
.type == 'CAMERA':
2984 cam_key
= data_cameras
[ob_obj
]
2985 connections
.append((b
"OO", get_fbx_uuid_from_key(cam_key
), ob_obj
.fbx_uuid
, None))
2986 elif ob_obj
.type == 'EMPTY' or ob_obj
.type == 'ARMATURE':
2987 empty_key
= data_empties
[ob_obj
]
2988 connections
.append((b
"OO", get_fbx_uuid_from_key(empty_key
), ob_obj
.fbx_uuid
, None))
2989 elif ob_obj
.type in BLENDER_OBJECT_TYPES_MESHLIKE
:
2990 mesh_key
, _me
, _free
= data_meshes
[ob_obj
]
2991 connections
.append((b
"OO", get_fbx_uuid_from_key(mesh_key
), ob_obj
.fbx_uuid
, None))
2994 for (_node_name
, par_uuid
, node_uuid
, attr_uuid
, _matrix
, _hide
, _size
) in data_leaf_bones
:
2995 connections
.append((b
"OO", node_uuid
, par_uuid
, None))
2996 connections
.append((b
"OO", attr_uuid
, node_uuid
, None))
2998 # 'Shape' deformers (shape keys, only for meshes currently)...
2999 for me_key
, shapes_key
, shapes
in data_deformers_shape
.values():
3001 connections
.append((b
"OO", get_fbx_uuid_from_key(shapes_key
), get_fbx_uuid_from_key(me_key
), None))
3002 for channel_key
, geom_key
, _shape_verts_co
, _shape_verts_idx
in shapes
.values():
3003 # shape channel -> shape
3004 connections
.append((b
"OO", get_fbx_uuid_from_key(channel_key
), get_fbx_uuid_from_key(shapes_key
), None))
3005 # geometry (keys) -> shape channel
3006 connections
.append((b
"OO", get_fbx_uuid_from_key(geom_key
), get_fbx_uuid_from_key(channel_key
), None))
3008 # 'Skin' deformers (armature-to-geometry, only for meshes currently)...
3009 for arm
, deformed_meshes
in data_deformers_skin
.items():
3010 for me
, (skin_key
, ob_obj
, clusters
) in deformed_meshes
.items():
3012 mesh_key
, _me
, _free
= data_meshes
[ob_obj
]
3014 connections
.append((b
"OO", get_fbx_uuid_from_key(skin_key
), get_fbx_uuid_from_key(mesh_key
), None))
3015 for bo_obj
, clstr_key
in clusters
.items():
3017 connections
.append((b
"OO", get_fbx_uuid_from_key(clstr_key
), get_fbx_uuid_from_key(skin_key
), None))
3019 connections
.append((b
"OO", bo_obj
.fbx_uuid
, get_fbx_uuid_from_key(clstr_key
), None))
3022 mesh_material_indices
= {}
3024 for ma
, (ma_key
, ob_objs
) in data_materials
.items():
3025 for ob_obj
in ob_objs
:
3026 connections
.append((b
"OO", get_fbx_uuid_from_key(ma_key
), ob_obj
.fbx_uuid
, None))
3027 # Get index of this material for this object (or dupliobject).
3028 # Material indices for mesh faces are determined by their order in 'ma to ob' connections.
3029 # Only materials for meshes currently...
3030 # Note in case of dupliobjects a same me/ma idx will be generated several times...
3031 # Should not be an issue in practice, and it's needed in case we export duplis but not the original!
3032 if ob_obj
.type not in BLENDER_OBJECT_TYPES_MESHLIKE
:
3034 _mesh_key
, me
, _free
= data_meshes
[ob_obj
]
3035 idx
= _objs_indices
[ob_obj
] = _objs_indices
.get(ob_obj
, -1) + 1
3036 # XXX If a mesh has multiple material slots with the same material, they are combined into one slot.
3037 # Even if duplicate materials were exported without combining them into one slot, keeping duplicate
3038 # materials separated does not appear to be common behaviour of external software when importing FBX.
3039 mesh_material_indices
.setdefault(me
, {})[ma
] = idx
3043 for (ma
, sock_name
), (tex_key
, fbx_prop
) in data_textures
.items():
3044 ma_key
, _ob_objs
= data_materials
[ma
]
3045 # texture -> material properties
3046 connections
.append((b
"OP", get_fbx_uuid_from_key(tex_key
), get_fbx_uuid_from_key(ma_key
), fbx_prop
))
3049 for vid
, (vid_key
, blender_tex_keys
) in data_videos
.items():
3050 for blender_tex_key
in blender_tex_keys
:
3051 tex_key
, _fbx_prop
= data_textures
[blender_tex_key
]
3052 connections
.append((b
"OO", get_fbx_uuid_from_key(vid_key
), get_fbx_uuid_from_key(tex_key
), None))
3055 for astack_key
, astack
, alayer_key
, _name
, _fstart
, _fend
in animations
:
3056 # Animstack itself is linked nowhere!
3057 astack_id
= get_fbx_uuid_from_key(astack_key
)
3058 # For now, only one layer!
3059 alayer_id
= get_fbx_uuid_from_key(alayer_key
)
3060 connections
.append((b
"OO", alayer_id
, astack_id
, None))
3061 for elem_key
, (alayer_key
, acurvenodes
) in astack
.items():
3062 elem_id
= get_fbx_uuid_from_key(elem_key
)
3063 # Animlayer -> animstack.
3064 # alayer_id = get_fbx_uuid_from_key(alayer_key)
3065 # connections.append((b"OO", alayer_id, astack_id, None))
3066 for fbx_prop
, (acurvenode_key
, acurves
, acurvenode_name
) in acurvenodes
.items():
3067 # Animcurvenode -> animalayer.
3068 acurvenode_id
= get_fbx_uuid_from_key(acurvenode_key
)
3069 connections
.append((b
"OO", acurvenode_id
, alayer_id
, None))
3070 # Animcurvenode -> object property.
3071 connections
.append((b
"OP", acurvenode_id
, elem_id
, fbx_prop
.encode()))
3072 for fbx_item
, (acurve_key
, default_value
, (keys
, values
), acurve_valid
) in acurves
.items():
3074 # Animcurve -> Animcurvenode.
3075 connections
.append((b
"OP", get_fbx_uuid_from_key(acurve_key
), acurvenode_id
, fbx_item
.encode()))
3077 perfmon
.level_down()
3079 # ##### And pack all this!
3081 return FBXExportData(
3082 templates
, templates_users
, connections
,
3083 settings
, scene
, depsgraph
, objects
, animations
, animated
, frame_start
, frame_end
,
3084 data_empties
, data_lights
, data_cameras
, data_meshes
, mesh_material_indices
,
3085 data_bones
, data_leaf_bones
, data_deformers_skin
, data_deformers_shape
,
3086 data_world
, data_materials
, data_textures
, data_videos
,
3090 def fbx_scene_data_cleanup(scene_data
):
3092 Some final cleanup...
3094 # Delete temp meshes.
3096 for me_key
, me
, free
in scene_data
.data_meshes
.values():
3097 if free
and me_key
not in done_meshes
:
3098 bpy
.data
.meshes
.remove(me
)
3099 done_meshes
.add(me_key
)
3102 # ##### Top-level FBX elements generators. #####
3104 def fbx_header_elements(root
, scene_data
, time
=None):
3106 Write boiling code of FBX root.
3107 time is expected to be a datetime.datetime object, or None (using now() in this case).
3109 app_vendor
= "Blender Foundation"
3110 app_name
= "Blender (stable FBX IO)"
3111 app_ver
= bpy
.app
.version_string
3113 from . import bl_info
3114 addon_ver
= bl_info
["version"]
3117 # ##### Start of FBXHeaderExtension element.
3118 header_ext
= elem_empty(root
, b
"FBXHeaderExtension")
3120 elem_data_single_int32(header_ext
, b
"FBXHeaderVersion", FBX_HEADER_VERSION
)
3122 elem_data_single_int32(header_ext
, b
"FBXVersion", FBX_VERSION
)
3125 elem_data_single_int32(header_ext
, b
"EncryptionType", 0)
3128 time
= datetime
.datetime
.now()
3129 elem
= elem_empty(header_ext
, b
"CreationTimeStamp")
3130 elem_data_single_int32(elem
, b
"Version", 1000)
3131 elem_data_single_int32(elem
, b
"Year", time
.year
)
3132 elem_data_single_int32(elem
, b
"Month", time
.month
)
3133 elem_data_single_int32(elem
, b
"Day", time
.day
)
3134 elem_data_single_int32(elem
, b
"Hour", time
.hour
)
3135 elem_data_single_int32(elem
, b
"Minute", time
.minute
)
3136 elem_data_single_int32(elem
, b
"Second", time
.second
)
3137 elem_data_single_int32(elem
, b
"Millisecond", time
.microsecond
// 1000)
3139 elem_data_single_string_unicode(header_ext
, b
"Creator", "%s - %s - %d.%d.%d"
3140 % (app_name
, app_ver
, addon_ver
[0], addon_ver
[1], addon_ver
[2]))
3142 # 'SceneInfo' seems mandatory to get a valid FBX file...
3143 # TODO use real values!
3144 # XXX Should we use scene.name.encode() here?
3145 scene_info
= elem_data_single_string(header_ext
, b
"SceneInfo", fbx_name_class(b
"GlobalInfo", b
"SceneInfo"))
3146 scene_info
.add_string(b
"UserData")
3147 elem_data_single_string(scene_info
, b
"Type", b
"UserData")
3148 elem_data_single_int32(scene_info
, b
"Version", FBX_SCENEINFO_VERSION
)
3149 meta_data
= elem_empty(scene_info
, b
"MetaData")
3150 elem_data_single_int32(meta_data
, b
"Version", FBX_SCENEINFO_VERSION
)
3151 elem_data_single_string(meta_data
, b
"Title", b
"")
3152 elem_data_single_string(meta_data
, b
"Subject", b
"")
3153 elem_data_single_string(meta_data
, b
"Author", b
"")
3154 elem_data_single_string(meta_data
, b
"Keywords", b
"")
3155 elem_data_single_string(meta_data
, b
"Revision", b
"")
3156 elem_data_single_string(meta_data
, b
"Comment", b
"")
3158 props
= elem_properties(scene_info
)
3159 elem_props_set(props
, "p_string_url", b
"DocumentUrl", "/foobar.fbx")
3160 elem_props_set(props
, "p_string_url", b
"SrcDocumentUrl", "/foobar.fbx")
3161 original
= elem_props_compound(props
, b
"Original")
3162 original("p_string", b
"ApplicationVendor", app_vendor
)
3163 original("p_string", b
"ApplicationName", app_name
)
3164 original("p_string", b
"ApplicationVersion", app_ver
)
3165 original("p_datetime", b
"DateTime_GMT", "01/01/1970 00:00:00.000")
3166 original("p_string", b
"FileName", "/foobar.fbx")
3167 lastsaved
= elem_props_compound(props
, b
"LastSaved")
3168 lastsaved("p_string", b
"ApplicationVendor", app_vendor
)
3169 lastsaved("p_string", b
"ApplicationName", app_name
)
3170 lastsaved("p_string", b
"ApplicationVersion", app_ver
)
3171 lastsaved("p_datetime", b
"DateTime_GMT", "01/01/1970 00:00:00.000")
3172 original("p_string", b
"ApplicationNativeFile", bpy
.data
.filepath
)
3174 # ##### End of FBXHeaderExtension element.
3176 # FileID is replaced by dummy value currently...
3177 elem_data_single_bytes(root
, b
"FileId", b
"FooBar")
3179 # CreationTime is replaced by dummy value currently, but anyway...
3180 elem_data_single_string_unicode(root
, b
"CreationTime",
3181 "{:04}-{:02}-{:02} {:02}:{:02}:{:02}:{:03}"
3182 "".format(time
.year
, time
.month
, time
.day
, time
.hour
, time
.minute
, time
.second
,
3183 time
.microsecond
* 1000))
3185 elem_data_single_string_unicode(root
, b
"Creator", "%s - %s - %d.%d.%d"
3186 % (app_name
, app_ver
, addon_ver
[0], addon_ver
[1], addon_ver
[2]))
3188 # ##### Start of GlobalSettings element.
3189 global_settings
= elem_empty(root
, b
"GlobalSettings")
3190 scene
= scene_data
.scene
3192 elem_data_single_int32(global_settings
, b
"Version", 1000)
3194 props
= elem_properties(global_settings
)
3195 up_axis
, front_axis
, coord_axis
= RIGHT_HAND_AXES
[scene_data
.settings
.to_axes
]
3196 #~ # DO NOT take into account global scale here! That setting is applied to object transformations during export
3197 #~ # (in other words, this is pure blender-exporter feature, and has nothing to do with FBX data).
3198 #~ if scene_data.settings.apply_unit_scale:
3199 #~ # Unit scaling is applied to objects' scale, so our unit is effectively FBX one (centimeter).
3200 #~ scale_factor_org = 1.0
3201 #~ scale_factor = 1.0 / units_blender_to_fbx_factor(scene)
3203 #~ scale_factor_org = units_blender_to_fbx_factor(scene)
3204 #~ scale_factor = scale_factor_org
3205 scale_factor
= scale_factor_org
= scene_data
.settings
.unit_scale
3206 elem_props_set(props
, "p_integer", b
"UpAxis", up_axis
[0])
3207 elem_props_set(props
, "p_integer", b
"UpAxisSign", up_axis
[1])
3208 elem_props_set(props
, "p_integer", b
"FrontAxis", front_axis
[0])
3209 elem_props_set(props
, "p_integer", b
"FrontAxisSign", front_axis
[1])
3210 elem_props_set(props
, "p_integer", b
"CoordAxis", coord_axis
[0])
3211 elem_props_set(props
, "p_integer", b
"CoordAxisSign", coord_axis
[1])
3212 elem_props_set(props
, "p_integer", b
"OriginalUpAxis", -1)
3213 elem_props_set(props
, "p_integer", b
"OriginalUpAxisSign", 1)
3214 elem_props_set(props
, "p_double", b
"UnitScaleFactor", scale_factor
)
3215 elem_props_set(props
, "p_double", b
"OriginalUnitScaleFactor", scale_factor_org
)
3216 elem_props_set(props
, "p_color_rgb", b
"AmbientColor", (0.0, 0.0, 0.0))
3217 elem_props_set(props
, "p_string", b
"DefaultCamera", "Producer Perspective")
3219 # Global timing data.
3221 _
, fbx_fps_mode
= FBX_FRAMERATES
[0] # Custom framerate.
3222 fbx_fps
= fps
= r
.fps
/ r
.fps_base
3223 for ref_fps
, fps_mode
in FBX_FRAMERATES
:
3224 if similar_values(fps
, ref_fps
):
3226 fbx_fps_mode
= fps_mode
3228 elem_props_set(props
, "p_enum", b
"TimeMode", fbx_fps_mode
)
3229 elem_props_set(props
, "p_timestamp", b
"TimeSpanStart", 0)
3230 elem_props_set(props
, "p_timestamp", b
"TimeSpanStop", FBX_KTIME
)
3231 elem_props_set(props
, "p_double", b
"CustomFrameRate", fbx_fps
)
3233 # ##### End of GlobalSettings element.
3236 def fbx_documents_elements(root
, scene_data
):
3238 Write 'Document' part of FBX root.
3239 Seems like FBX support multiple documents, but until I find examples of such, we'll stick to single doc!
3240 time is expected to be a datetime.datetime object, or None (using now() in this case).
3242 name
= scene_data
.scene
.name
3244 # ##### Start of Documents element.
3245 docs
= elem_empty(root
, b
"Documents")
3247 elem_data_single_int32(docs
, b
"Count", 1)
3249 doc_uid
= get_fbx_uuid_from_key("__FBX_Document__" + name
)
3250 doc
= elem_data_single_int64(docs
, b
"Document", doc_uid
)
3251 doc
.add_string_unicode(name
)
3252 doc
.add_string_unicode(name
)
3254 props
= elem_properties(doc
)
3255 elem_props_set(props
, "p_object", b
"SourceObject")
3256 elem_props_set(props
, "p_string", b
"ActiveAnimStackName", "")
3258 # XXX Some kind of ID? Offset?
3259 # Anyway, as long as we have only one doc, probably not an issue.
3260 elem_data_single_int64(doc
, b
"RootNode", 0)
3263 def fbx_references_elements(root
, scene_data
):
3265 Have no idea what references are in FBX currently... Just writing empty element.
3267 docs
= elem_empty(root
, b
"References")
3270 def fbx_definitions_elements(root
, scene_data
):
3272 Templates definitions. Only used by Objects data afaik (apart from dummy GlobalSettings one).
3274 definitions
= elem_empty(root
, b
"Definitions")
3276 elem_data_single_int32(definitions
, b
"Version", FBX_TEMPLATES_VERSION
)
3277 elem_data_single_int32(definitions
, b
"Count", scene_data
.templates_users
)
3279 fbx_templates_generate(definitions
, scene_data
.templates
)
3282 def fbx_objects_elements(root
, scene_data
):
3284 Data (objects, geometry, material, textures, armatures, etc.).
3288 objects
= elem_empty(root
, b
"Objects")
3290 perfmon
.step("FBX export fetch empties (%d)..." % len(scene_data
.data_empties
))
3292 for empty
in scene_data
.data_empties
:
3293 fbx_data_empty_elements(objects
, empty
, scene_data
)
3295 perfmon
.step("FBX export fetch lamps (%d)..." % len(scene_data
.data_lights
))
3297 for lamp
in scene_data
.data_lights
:
3298 fbx_data_light_elements(objects
, lamp
, scene_data
)
3300 perfmon
.step("FBX export fetch cameras (%d)..." % len(scene_data
.data_cameras
))
3302 for cam
in scene_data
.data_cameras
:
3303 fbx_data_camera_elements(objects
, cam
, scene_data
)
3305 perfmon
.step("FBX export fetch meshes (%d)..."
3306 % len({me_key
for me_key
, _me
, _free
in scene_data
.data_meshes
.values()}))
3309 for me_obj
in scene_data
.data_meshes
:
3310 fbx_data_mesh_elements(objects
, me_obj
, scene_data
, done_meshes
)
3313 perfmon
.step("FBX export fetch objects (%d)..." % len(scene_data
.objects
))
3315 for ob_obj
in scene_data
.objects
:
3318 fbx_data_object_elements(objects
, ob_obj
, scene_data
)
3319 for dp_obj
in ob_obj
.dupli_list_gen(scene_data
.depsgraph
):
3320 if dp_obj
not in scene_data
.objects
:
3322 fbx_data_object_elements(objects
, dp_obj
, scene_data
)
3324 perfmon
.step("FBX export fetch remaining...")
3326 for ob_obj
in scene_data
.objects
:
3327 if not (ob_obj
.is_object
and ob_obj
.type == 'ARMATURE'):
3329 fbx_data_armature_elements(objects
, ob_obj
, scene_data
)
3331 if scene_data
.data_leaf_bones
:
3332 fbx_data_leaf_bone_elements(objects
, scene_data
)
3334 for ma
in scene_data
.data_materials
:
3335 fbx_data_material_elements(objects
, ma
, scene_data
)
3337 for blender_tex_key
in scene_data
.data_textures
:
3338 fbx_data_texture_file_elements(objects
, blender_tex_key
, scene_data
)
3340 for vid
in scene_data
.data_videos
:
3341 fbx_data_video_elements(objects
, vid
, scene_data
)
3343 perfmon
.step("FBX export fetch animations...")
3344 start_time
= time
.process_time()
3346 fbx_data_animation_elements(objects
, scene_data
)
3348 perfmon
.level_down()
3351 def fbx_connections_elements(root
, scene_data
):
3353 Relations between Objects (which material uses which texture, and so on).
3355 connections
= elem_empty(root
, b
"Connections")
3357 for c
in scene_data
.connections
:
3358 elem_connection(connections
, *c
)
3361 def fbx_takes_elements(root
, scene_data
):
3365 # XXX Pretty sure takes are no more needed...
3366 takes
= elem_empty(root
, b
"Takes")
3367 elem_data_single_string(takes
, b
"Current", b
"")
3369 animations
= scene_data
.animations
3370 for astack_key
, animations
, alayer_key
, name
, f_start
, f_end
in animations
:
3371 scene
= scene_data
.scene
3372 fps
= scene
.render
.fps
/ scene
.render
.fps_base
3373 start_ktime
= int(convert_sec_to_ktime(f_start
/ fps
))
3374 end_ktime
= int(convert_sec_to_ktime(f_end
/ fps
))
3376 take
= elem_data_single_string(takes
, b
"Take", name
)
3377 elem_data_single_string(take
, b
"FileName", name
+ b
".tak")
3378 take_loc_time
= elem_data_single_int64(take
, b
"LocalTime", start_ktime
)
3379 take_loc_time
.add_int64(end_ktime
)
3380 take_ref_time
= elem_data_single_int64(take
, b
"ReferenceTime", start_ktime
)
3381 take_ref_time
.add_int64(end_ktime
)
3384 # ##### "Main" functions. #####
3386 # This func can be called with just the filepath
3387 def save_single(operator
, scene
, depsgraph
, filepath
="",
3388 global_matrix
=Matrix(),
3389 apply_unit_scale
=False,
3391 apply_scale_options
='FBX_SCALE_NONE',
3394 context_objects
=None,
3396 use_mesh_modifiers
=True,
3397 use_mesh_modifiers_render
=True,
3398 mesh_smooth_type
='FACE',
3400 use_armature_deform_only
=False,
3402 bake_anim_use_all_bones
=True,
3403 bake_anim_use_nla_strips
=True,
3404 bake_anim_use_all_actions
=True,
3406 bake_anim_simplify_factor
=1.0,
3407 bake_anim_force_startend_keying
=True,
3408 add_leaf_bones
=False,
3409 primary_bone_axis
='Y',
3410 secondary_bone_axis
='X',
3413 use_mesh_edges
=True,
3415 use_triangles
=False,
3416 embed_textures
=False,
3417 use_custom_props
=False,
3418 bake_space_transform
=False,
3419 armature_nodetype
='NULL',
3421 prioritize_active_color
=False,
3425 # Clear cached ObjectWrappers (just in case...).
3426 ObjectWrapper
.cache_clear()
3428 if object_types
is None:
3429 object_types
= {'EMPTY', 'CAMERA', 'LIGHT', 'ARMATURE', 'MESH', 'OTHER'}
3431 if 'OTHER' in object_types
:
3432 object_types |
= BLENDER_OTHER_OBJECT_TYPES
3434 # Default Blender unit is equivalent to meter, while FBX one is centimeter...
3435 unit_scale
= units_blender_to_fbx_factor(scene
) if apply_unit_scale
else 100.0
3436 if apply_scale_options
== 'FBX_SCALE_NONE':
3437 global_matrix
= Matrix
.Scale(unit_scale
* global_scale
, 4) @ global_matrix
3439 elif apply_scale_options
== 'FBX_SCALE_UNITS':
3440 global_matrix
= Matrix
.Scale(global_scale
, 4) @ global_matrix
3441 elif apply_scale_options
== 'FBX_SCALE_CUSTOM':
3442 global_matrix
= Matrix
.Scale(unit_scale
, 4) @ global_matrix
3443 unit_scale
= global_scale
3444 else: # if apply_scale_options == 'FBX_SCALE_ALL':
3445 unit_scale
= global_scale
* unit_scale
3447 global_scale
= global_matrix
.median_scale
3448 global_matrix_inv
= global_matrix
.inverted()
3449 # For transforming mesh normals.
3450 global_matrix_inv_transposed
= global_matrix_inv
.transposed()
3452 # Only embed textures in COPY mode!
3453 if embed_textures
and path_mode
!= 'COPY':
3454 embed_textures
= False
3456 # Calculate bone correction matrix
3457 bone_correction_matrix
= None # Default is None = no change
3458 bone_correction_matrix_inv
= None
3459 if (primary_bone_axis
, secondary_bone_axis
) != ('Y', 'X'):
3460 from bpy_extras
.io_utils
import axis_conversion
3461 bone_correction_matrix
= axis_conversion(from_forward
=secondary_bone_axis
,
3462 from_up
=primary_bone_axis
,
3466 bone_correction_matrix_inv
= bone_correction_matrix
.inverted()
3469 media_settings
= FBXExportSettingsMedia(
3471 os
.path
.dirname(bpy
.data
.filepath
), # base_src
3472 os
.path
.dirname(filepath
), # base_dst
3473 # Local dir where to put images (media), using FBX conventions.
3474 os
.path
.splitext(os
.path
.basename(filepath
))[0] + ".fbm", # subdir
3477 set(), # embedded_set
3480 settings
= FBXExportSettings(
3481 operator
.report
, (axis_up
, axis_forward
), global_matrix
, global_scale
, apply_unit_scale
, unit_scale
,
3482 bake_space_transform
, global_matrix_inv
, global_matrix_inv_transposed
,
3483 context_objects
, object_types
, use_mesh_modifiers
, use_mesh_modifiers_render
,
3484 mesh_smooth_type
, use_subsurf
, use_mesh_edges
, use_tspace
, use_triangles
,
3485 armature_nodetype
, use_armature_deform_only
,
3486 add_leaf_bones
, bone_correction_matrix
, bone_correction_matrix_inv
,
3487 bake_anim
, bake_anim_use_all_bones
, bake_anim_use_nla_strips
, bake_anim_use_all_actions
,
3488 bake_anim_step
, bake_anim_simplify_factor
, bake_anim_force_startend_keying
,
3489 False, media_settings
, use_custom_props
, colors_type
, prioritize_active_color
3492 import bpy_extras
.io_utils
3494 print('\nFBX export starting... %r' % filepath
)
3495 start_time
= time
.process_time()
3497 # Generate some data about exported scene...
3498 scene_data
= fbx_data_from_scene(scene
, depsgraph
, settings
)
3500 # Enable multithreaded array compression in FBXElem and wait until all threads are done before exiting the context
3502 with encode_bin
.FBXElem
.enable_multithreading_cm():
3503 # Writing elements into an FBX hierarchy can now begin.
3504 root
= elem_empty(None, b
"") # Root element has no id, as it is not saved per se!
3506 # Mostly FBXHeaderExtension and GlobalSettings.
3507 fbx_header_elements(root
, scene_data
)
3509 # Documents and References are pretty much void currently.
3510 fbx_documents_elements(root
, scene_data
)
3511 fbx_references_elements(root
, scene_data
)
3513 # Templates definitions.
3514 fbx_definitions_elements(root
, scene_data
)
3517 fbx_objects_elements(root
, scene_data
)
3519 # How data are inter-connected.
3520 fbx_connections_elements(root
, scene_data
)
3523 fbx_takes_elements(root
, scene_data
)
3526 fbx_scene_data_cleanup(scene_data
)
3528 # And we are done, all multithreaded tasks are complete, and we can write the whole thing to file!
3529 encode_bin
.write(filepath
, root
, FBX_VERSION
)
3531 # Clear cached ObjectWrappers!
3532 ObjectWrapper
.cache_clear()
3534 # copy all collected files, if we did not embed them.
3535 if not media_settings
.embed_textures
:
3536 bpy_extras
.io_utils
.path_reference_copy(media_settings
.copy_set
)
3538 print('export finished in %.4f sec.' % (time
.process_time() - start_time
))
3542 # defaults for applications, currently only unity but could add others.
3543 def defaults_unity3d():
3545 # These options seem to produce the same result as the old Ascii exporter in Unity3D:
3547 "axis_forward": '-Z',
3548 "global_matrix": Matrix
.Rotation(-math
.pi
/ 2.0, 4, 'X'),
3549 # Should really be True, but it can cause problems if a model is already in a scene or prefab
3550 # with the old transforms.
3551 "bake_space_transform": False,
3553 "use_selection": False,
3555 "object_types": {'ARMATURE', 'EMPTY', 'MESH', 'OTHER'},
3556 "use_mesh_modifiers": True,
3557 "use_mesh_modifiers_render": True,
3558 "use_mesh_edges": False,
3559 "mesh_smooth_type": 'FACE',
3560 "colors_type": 'SRGB',
3561 "use_subsurf": False,
3562 "use_tspace": False, # XXX Why? Unity is expected to support tspace import...
3563 "use_triangles": False,
3565 "use_armature_deform_only": True,
3567 "use_custom_props": True,
3570 "bake_anim_simplify_factor": 1.0,
3571 "bake_anim_step": 1.0,
3572 "bake_anim_use_nla_strips": True,
3573 "bake_anim_use_all_actions": True,
3574 "add_leaf_bones": False, # Avoid memory/performance cost for something only useful for modelling
3575 "primary_bone_axis": 'Y', # Doesn't really matter for Unity, so leave unchanged
3576 "secondary_bone_axis": 'X',
3578 "path_mode": 'AUTO',
3579 "embed_textures": False,
3580 "batch_mode": 'OFF',
3584 def save(operator
, context
,
3586 use_selection
=False,
3588 use_active_collection
=False,
3591 use_batch_own_dir
=False,
3595 This is a wrapper around save_single, which handles multi-scenes (or collections) cases, when batch-exporting
3596 a whole .blend file.
3601 active_object
= context
.view_layer
.objects
.active
3604 if active_object
and active_object
.mode
!= 'OBJECT' and bpy
.ops
.object.mode_set
.poll():
3605 org_mode
= active_object
.mode
3606 bpy
.ops
.object.mode_set(mode
='OBJECT')
3608 if batch_mode
== 'OFF':
3609 kwargs_mod
= kwargs
.copy()
3611 source_collection
= None
3612 if use_active_collection
:
3613 source_collection
= context
.view_layer
.active_layer_collection
.collection
3615 local_collection
= bpy
.data
.collections
.get((collection
, None))
3616 if local_collection
:
3617 source_collection
= local_collection
3619 operator
.report({'ERROR'}, "Collection '%s' was not found" % collection
)
3620 return {'CANCELLED'}
3622 if source_collection
:
3624 ctx_objects
= tuple(obj
for obj
in source_collection
.all_objects
if obj
.select_get())
3626 ctx_objects
= source_collection
.all_objects
3629 ctx_objects
= context
.selected_objects
3631 ctx_objects
= context
.view_layer
.objects
3633 ctx_objects
= tuple(obj
for obj
in ctx_objects
if obj
.visible_get())
3635 # Ensure no Objects are in Edit mode.
3636 # Copy to a tuple for safety, to avoid the risk of modifying ctx_objects while iterating.
3637 for obj
in tuple(ctx_objects
):
3638 if not ensure_object_not_in_edit_mode(context
, obj
):
3639 operator
.report({'ERROR'}, "%s could not be set out of Edit Mode, so cannot be exported" % obj
.name
)
3640 return {'CANCELLED'}
3642 kwargs_mod
["context_objects"] = ctx_objects
3644 depsgraph
= context
.evaluated_depsgraph_get()
3645 ret
= save_single(operator
, context
.scene
, depsgraph
, filepath
, **kwargs_mod
)
3647 # XXX We need a way to generate a depsgraph for inactive view_layers first...
3648 # XXX Also, what to do in case of batch-exporting scenes, when there is more than one view layer?
3649 # Scenes have no concept of 'active' view layer, that's on window level...
3652 prefix
= os
.path
.basename(fbxpath
)
3654 fbxpath
= os
.path
.dirname(fbxpath
)
3656 if batch_mode
== 'COLLECTION':
3657 data_seq
= tuple((coll
, coll
.name
, 'objects') for coll
in bpy
.data
.collections
if coll
.objects
)
3658 elif batch_mode
in {'SCENE_COLLECTION', 'ACTIVE_SCENE_COLLECTION'}:
3659 scenes
= [context
.scene
] if batch_mode
== 'ACTIVE_SCENE_COLLECTION' else bpy
.data
.scenes
3661 for scene
in scenes
:
3662 if not scene
.objects
:
3664 # Needed to avoid having tens of 'Scene Collection' entries.
3665 todo_collections
= [(scene
.collection
, "_".join((scene
.name
, scene
.collection
.name
)))]
3666 while todo_collections
:
3667 coll
, coll_name
= todo_collections
.pop()
3668 todo_collections
.extend(((c
, c
.name
) for c
in coll
.children
if c
.all_objects
))
3669 data_seq
.append((coll
, coll_name
, 'all_objects'))
3671 data_seq
= tuple((scene
, scene
.name
, 'objects') for scene
in bpy
.data
.scenes
if scene
.objects
)
3673 # Ensure no Objects are in Edit mode.
3674 for data
, data_name
, data_obj_propname
in data_seq
:
3675 # Copy to a tuple for safety, to avoid the risk of modifying the data prop while iterating it.
3676 for obj
in tuple(getattr(data
, data_obj_propname
)):
3677 if not ensure_object_not_in_edit_mode(context
, obj
):
3678 operator
.report({'ERROR'},
3679 "%s in %s could not be set out of Edit Mode, so cannot be exported"
3680 % (obj
.name
, data_name
))
3681 return {'CANCELLED'}
3683 # call this function within a loop with BATCH_ENABLE == False
3685 new_fbxpath
= fbxpath
# own dir option modifies, we need to keep an original
3686 for data
, data_name
, data_obj_propname
in data_seq
: # scene or collection
3687 newname
= "_".join((prefix
, bpy
.path
.clean_name(data_name
))) if prefix
else bpy
.path
.clean_name(data_name
)
3689 if use_batch_own_dir
:
3690 new_fbxpath
= os
.path
.join(fbxpath
, newname
)
3691 # path may already exist... and be a file.
3692 while os
.path
.isfile(new_fbxpath
):
3693 new_fbxpath
= "_".join((new_fbxpath
, "dir"))
3694 if not os
.path
.exists(new_fbxpath
):
3695 os
.makedirs(new_fbxpath
)
3697 filepath
= os
.path
.join(new_fbxpath
, newname
+ '.fbx')
3699 print('\nBatch exporting %s as...\n\t%r' % (data
, filepath
))
3701 if batch_mode
in {'COLLECTION', 'SCENE_COLLECTION', 'ACTIVE_SCENE_COLLECTION'}:
3702 # Collection, so that objects update properly, add a dummy scene.
3703 scene
= bpy
.data
.scenes
.new(name
="FBX_Temp")
3704 src_scenes
= {} # Count how much each 'source' scenes are used.
3705 for obj
in getattr(data
, data_obj_propname
):
3706 for src_sce
in obj
.users_scene
:
3707 src_scenes
[src_sce
] = src_scenes
.setdefault(src_sce
, 0) + 1
3708 scene
.collection
.objects
.link(obj
)
3710 # Find the 'most used' source scene, and use its unit settings. This is somewhat weak, but should work
3711 # fine in most cases, and avoids stupid issues like T41931.
3712 best_src_scene
= None
3713 best_src_scene_users
= -1
3714 for sce
, nbr_users
in src_scenes
.items():
3715 if (nbr_users
) > best_src_scene_users
:
3716 best_src_scene_users
= nbr_users
3717 best_src_scene
= sce
3718 scene
.unit_settings
.system
= best_src_scene
.unit_settings
.system
3719 scene
.unit_settings
.system_rotation
= best_src_scene
.unit_settings
.system_rotation
3720 scene
.unit_settings
.scale_length
= best_src_scene
.unit_settings
.scale_length
3722 # new scene [only one viewlayer to update]
3723 scene
.view_layers
[0].update()
3724 # TODO - BUMMER! Armatures not in the group wont animate the mesh
3728 kwargs_batch
= kwargs
.copy()
3729 kwargs_batch
["context_objects"] = getattr(data
, data_obj_propname
)
3731 save_single(operator
, scene
, scene
.view_layers
[0].depsgraph
, filepath
, **kwargs_batch
)
3733 if batch_mode
in {'COLLECTION', 'SCENE_COLLECTION', 'ACTIVE_SCENE_COLLECTION'}:
3734 # Remove temp collection scene.
3735 bpy
.data
.scenes
.remove(scene
)
3737 if active_object
and org_mode
:
3738 context
.view_layer
.objects
.active
= active_object
3739 if bpy
.ops
.object.mode_set
.poll():
3740 bpy
.ops
.object.mode_set(mode
=org_mode
)