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