Guest User

Untitled

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