Skip to content
Snippets Groups Projects
export_fbx_bin.py 136 KiB
Newer Older
  • Learn to ignore specific revisions
  • # ##### BEGIN GPL LICENSE BLOCK #####
    #
    #  This program is free software; you can redistribute it and/or
    #  modify it under the terms of the GNU General Public License
    #  as published by the Free Software Foundation; either version 2
    #  of the License, or (at your option) any later version.
    #
    #  This program is distributed in the hope that it will be useful,
    #  but WITHOUT ANY WARRANTY; without even the implied warranty of
    #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    #  GNU General Public License for more details.
    #
    #  You should have received a copy of the GNU General Public License
    #  along with this program; if not, write to the Free Software Foundation,
    #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
    #
    # ##### END GPL LICENSE BLOCK #####
    
    # <pep8 compliant>
    
    # Script copyright (C) Campbell Barton, Bastien Montagne
    
    
    import array
    import datetime
    import math
    import os
    import time
    
    
    from collections import OrderedDict
    
    from itertools import zip_longest, chain
    
    
    if "bpy" in locals():
        import importlib
        if "encode_bin" in locals():
            importlib.reload(encode_bin)
        if "data_types" in locals():
            importlib.reload(data_types)
        if "fbx_utils" in locals():
            importlib.reload(fbx_utils)
    
    
    import bpy
    import bpy_extras
    from mathutils import Vector, Matrix
    
    
    from . import encode_bin, data_types, fbx_utils
    
        # Constants.
        FBX_VERSION, FBX_HEADER_VERSION, FBX_SCENEINFO_VERSION, FBX_TEMPLATES_VERSION,
        FBX_MODELS_VERSION,
        FBX_GEOMETRY_VERSION, FBX_GEOMETRY_NORMAL_VERSION, FBX_GEOMETRY_BINORMAL_VERSION, FBX_GEOMETRY_TANGENT_VERSION,
        FBX_GEOMETRY_SMOOTHING_VERSION, FBX_GEOMETRY_VCOLOR_VERSION, FBX_GEOMETRY_UV_VERSION,
        FBX_GEOMETRY_MATERIAL_VERSION, FBX_GEOMETRY_LAYER_VERSION,
    
        FBX_GEOMETRY_SHAPE_VERSION, FBX_DEFORMER_SHAPE_VERSION, FBX_DEFORMER_SHAPECHANNEL_VERSION,
    
        FBX_POSE_BIND_VERSION, FBX_DEFORMER_SKIN_VERSION, FBX_DEFORMER_CLUSTER_VERSION,
        FBX_MATERIAL_VERSION, FBX_TEXTURE_VERSION,
        FBX_ANIM_KEY_VERSION,
    
        FBX_KTIME,
        BLENDER_OTHER_OBJECT_TYPES, BLENDER_OBJECT_TYPES_MESHLIKE,
        FBX_LIGHT_TYPES, FBX_LIGHT_DECAY_TYPES,
        RIGHT_HAND_AXES, FBX_FRAMERATES,
        # Miscellaneous utils.
    
        units_convertor, units_convertor_iter, matrix4_to_array, similar_values, similar_values_iter,
    
        # Mesh transform helpers.
        vcos_transformed_gen, nors_transformed_gen,
    
        # UUID from key.
        get_fbx_uuid_from_key,
        # Key generators.
        get_blenderID_key, get_blenderID_name,
    
        get_blender_mesh_shape_key, get_blender_mesh_shape_channel_key,
    
        get_blender_empty_key, get_blender_bone_key,
    
        get_blender_bindpose_key, get_blender_armature_skin_key, get_blender_bone_cluster_key,
    
        get_blender_anim_id_base, get_blender_anim_stack_key, get_blender_anim_layer_key,
        get_blender_anim_curve_node_key, get_blender_anim_curve_key,
        # FBX element data.
        elem_empty,
        elem_data_single_bool, elem_data_single_int16, elem_data_single_int32, elem_data_single_int64,
        elem_data_single_float32, elem_data_single_float64,
        elem_data_single_bytes, elem_data_single_string, elem_data_single_string_unicode,
        elem_data_single_bool_array, elem_data_single_int32_array, elem_data_single_int64_array,
    
        elem_data_single_float32_array, elem_data_single_float64_array, elem_data_vec_float64,
    
        # FBX element properties.
        elem_properties, elem_props_set, elem_props_compound,
        # FBX element properties handling templates.
        elem_props_template_init, elem_props_template_set, elem_props_template_finalize,
        # Templates.
        FBXTemplate, fbx_templates_generate,
    
        # Objects.
        ObjectWrapper, fbx_name_class,
        # Top level.
    
        FBXExportSettingsMedia, FBXExportSettings, FBXExportData,
    
    Bastien Montagne's avatar
    Bastien Montagne committed
    # Units convertors!
    convert_sec_to_ktime = units_convertor("second", "ktime")
    convert_sec_to_ktime_iter = units_convertor_iter("second", "ktime")
    
    convert_mm_to_inch = units_convertor("millimeter", "inch")
    
    convert_rad_to_deg = units_convertor("radian", "degree")
    convert_rad_to_deg_iter = units_convertor_iter("radian", "degree")
    
    
    
    # ##### Templates #####
    
    # TODO: check all those "default" values, they should match Blender's default as much as possible, I guess?
    
    def fbx_template_def_globalsettings(scene, settings, override_defaults=None, nbr_users=0):
    
        props = OrderedDict()
    
        if override_defaults is not None:
            props.update(override_defaults)
    
        return FBXTemplate(b"GlobalSettings", b"", props, nbr_users, [False])
    
    
    
    def fbx_template_def_model(scene, settings, override_defaults=None, nbr_users=0):
        gscale = settings.global_scale
    
        props = OrderedDict((
    
            # Name,                   Value, Type, Animatable
            (b"QuaternionInterpolate", (0, "p_enum", False)),  # 0 = no quat interpolation.
    
            (b"RotationOffset", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
            (b"RotationPivot", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
            (b"ScalingOffset", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
            (b"ScalingPivot", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
            (b"TranslationActive", (False, "p_bool", False)),
            (b"TranslationMin", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
            (b"TranslationMax", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
            (b"TranslationMinX", (False, "p_bool", False)),
            (b"TranslationMinY", (False, "p_bool", False)),
            (b"TranslationMinZ", (False, "p_bool", False)),
            (b"TranslationMaxX", (False, "p_bool", False)),
            (b"TranslationMaxY", (False, "p_bool", False)),
            (b"TranslationMaxZ", (False, "p_bool", False)),
            (b"RotationOrder", (0, "p_enum", False)),  # we always use 'XYZ' order.
            (b"RotationSpaceForLimitOnly", (False, "p_bool", False)),
    
            (b"RotationStiffnessX", (0.0, "p_double", False)),
            (b"RotationStiffnessY", (0.0, "p_double", False)),
            (b"RotationStiffnessZ", (0.0, "p_double", False)),
            (b"AxisLen", (10.0, "p_double", False)),
    
            (b"PreRotation", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
            (b"PostRotation", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
            (b"RotationActive", (False, "p_bool", False)),
            (b"RotationMin", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
            (b"RotationMax", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
            (b"RotationMinX", (False, "p_bool", False)),
            (b"RotationMinY", (False, "p_bool", False)),
            (b"RotationMinZ", (False, "p_bool", False)),
            (b"RotationMaxX", (False, "p_bool", False)),
            (b"RotationMaxY", (False, "p_bool", False)),
            (b"RotationMaxZ", (False, "p_bool", False)),
    
            (b"ScalingActive", (False, "p_bool", False)),
    
            (b"ScalingMin", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
            (b"ScalingMax", ((1.0, 1.0, 1.0), "p_vector_3d", False)),
    
            (b"ScalingMinX", (False, "p_bool", False)),
            (b"ScalingMinY", (False, "p_bool", False)),
            (b"ScalingMinZ", (False, "p_bool", False)),
            (b"ScalingMaxX", (False, "p_bool", False)),
            (b"ScalingMaxY", (False, "p_bool", False)),
            (b"ScalingMaxZ", (False, "p_bool", False)),
            (b"GeometricTranslation", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
            (b"GeometricRotation", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
    
            (b"GeometricScaling", ((1.0, 1.0, 1.0), "p_vector_3d", False)),
    
            (b"MinDampRangeX", (0.0, "p_double", False)),
            (b"MinDampRangeY", (0.0, "p_double", False)),
            (b"MinDampRangeZ", (0.0, "p_double", False)),
            (b"MaxDampRangeX", (0.0, "p_double", False)),
            (b"MaxDampRangeY", (0.0, "p_double", False)),
            (b"MaxDampRangeZ", (0.0, "p_double", False)),
            (b"MinDampStrengthX", (0.0, "p_double", False)),
            (b"MinDampStrengthY", (0.0, "p_double", False)),
            (b"MinDampStrengthZ", (0.0, "p_double", False)),
            (b"MaxDampStrengthX", (0.0, "p_double", False)),
            (b"MaxDampStrengthY", (0.0, "p_double", False)),
            (b"MaxDampStrengthZ", (0.0, "p_double", False)),
            (b"PreferedAngleX", (0.0, "p_double", False)),
            (b"PreferedAngleY", (0.0, "p_double", False)),
            (b"PreferedAngleZ", (0.0, "p_double", False)),
    
            (b"LookAtProperty", (None, "p_object", False)),
            (b"UpVectorProperty", (None, "p_object", False)),
            (b"Show", (True, "p_bool", False)),
            (b"NegativePercentShapeSupport", (True, "p_bool", False)),
    
            (b"Freeze", (False, "p_bool", False)),
            (b"LODBox", (False, "p_bool", False)),
            (b"Lcl Translation", ((0.0, 0.0, 0.0), "p_lcl_translation", True)),
            (b"Lcl Rotation", ((0.0, 0.0, 0.0), "p_lcl_rotation", True)),
    
            (b"Lcl Scaling", ((1.0, 1.0, 1.0), "p_lcl_scaling", True)),
    
            (b"Visibility", (1.0, "p_visibility", True)),
    
            (b"Visibility Inheritance", (1, "p_visibility_inheritance", False)),
    
        if override_defaults is not None:
            props.update(override_defaults)
    
        return FBXTemplate(b"Model", b"FbxNode", props, nbr_users, [False])
    
    def fbx_template_def_null(scene, settings, override_defaults=None, nbr_users=0):
        props = OrderedDict((
            (b"Color", ((0.8, 0.8, 0.8), "p_color_rgb", False)),
            (b"Size", (100.0, "p_double", False)),
            (b"Look", (1, "p_enum", False)),  # Cross (0 is None, i.e. invisible?).
        ))
        if override_defaults is not None:
            props.update(override_defaults)
    
        return FBXTemplate(b"NodeAttribute", b"FbxNull", props, nbr_users, [False])
    
    def fbx_template_def_light(scene, settings, override_defaults=None, nbr_users=0):
        gscale = settings.global_scale
    
        props = OrderedDict((
            (b"LightType", (0, "p_enum", False)),  # Point light.
            (b"CastLight", (True, "p_bool", False)),
    
            (b"Color", ((1.0, 1.0, 1.0), "p_color", True)),
    
            (b"Intensity", (100.0, "p_number", True)),  # Times 100 compared to Blender values...
            (b"DecayType", (2, "p_enum", False)),  # Quadratic.
    
            (b"DecayStart", (30.0 * gscale, "p_double", False)),
    
            (b"CastShadows", (True, "p_bool", False)),
    
            (b"ShadowColor", ((0.0, 0.0, 0.0), "p_color", True)),
    
            (b"AreaLightShape", (0, "p_enum", False)),  # Rectangle.
        ))
    
        if override_defaults is not None:
            props.update(override_defaults)
    
        return FBXTemplate(b"NodeAttribute", b"FbxLight", props, nbr_users, [False])
    
    
    
    def fbx_template_def_camera(scene, settings, override_defaults=None, nbr_users=0):
    
        r = scene.render
        props = OrderedDict((
            (b"Color", ((0.8, 0.8, 0.8), "p_color_rgb", False)),
            (b"Position", ((0.0, 0.0, -50.0), "p_vector", True)),
            (b"UpVector", ((0.0, 1.0, 0.0), "p_vector", True)),
            (b"InterestPosition", ((0.0, 0.0, 0.0), "p_vector", True)),
            (b"Roll", (0.0, "p_roll", True)),
            (b"OpticalCenterX", (0.0, "p_opticalcenterx", True)),
            (b"OpticalCenterY", (0.0, "p_opticalcentery", True)),
    
            (b"BackgroundColor", ((0.63, 0.63, 0.63), "p_color", True)),
    
            (b"TurnTable", (0.0, "p_number", True)),
            (b"DisplayTurnTableIcon", (False, "p_bool", False)),
            (b"UseMotionBlur", (False, "p_bool", False)),
            (b"UseRealTimeMotionBlur", (True, "p_bool", False)),
            (b"Motion Blur Intensity", (1.0, "p_number", True)),
    
            (b"AspectRatioMode", (0, "p_enum", False)),  # WindowSize.
    
            (b"AspectWidth", (320.0, "p_double", False)),
            (b"AspectHeight", (200.0, "p_double", False)),
    
            (b"FilmOffsetX", (0.0, "p_number", True)),
            (b"FilmOffsetY", (0.0, "p_number", True)),
    
            (b"FilmWidth", (0.816, "p_double", False)),
            (b"FilmHeight", (0.612, "p_double", False)),
            (b"FilmAspectRatio", (1.3333333333333333, "p_double", False)),
    
            (b"FilmSqueezeRatio", (1.0, "p_double", False)),
            (b"FilmFormatIndex", (0, "p_enum", False)),  # Assuming this is ApertureFormat, 0 = custom.
            (b"PreScale", (1.0, "p_number", True)),
            (b"FilmTranslateX", (0.0, "p_number", True)),
            (b"FilmTranslateY", (0.0, "p_number", True)),
            (b"FilmRollPivotX", (0.0, "p_number", True)),
            (b"FilmRollPivotY", (0.0, "p_number", True)),
            (b"FilmRollValue", (0.0, "p_number", True)),
            (b"FilmRollOrder", (0, "p_enum", False)),  # 0 = rotate first (default).
    
            (b"ApertureMode", (2, "p_enum", False)),  # 2 = Vertical.
    
            (b"GateFit", (0, "p_enum", False)),  # 0 = no resolution gate fit.
    
            (b"FieldOfView", (25.114999771118164, "p_fov", True)),
            (b"FieldOfViewX", (40.0, "p_fov_x", True)),
            (b"FieldOfViewY", (40.0, "p_fov_y", True)),
            (b"FocalLength", (34.89327621672628, "p_number", True)),
    
            (b"CameraFormat", (0, "p_enum", False)),  # Custom camera format.
            (b"UseFrameColor", (False, "p_bool", False)),
            (b"FrameColor", ((0.3, 0.3, 0.3), "p_color_rgb", False)),
            (b"ShowName", (True, "p_bool", False)),
            (b"ShowInfoOnMoving", (True, "p_bool", False)),
            (b"ShowGrid", (True, "p_bool", False)),
            (b"ShowOpticalCenter", (False, "p_bool", False)),
            (b"ShowAzimut", (True, "p_bool", False)),
    
            (b"ShowAudio", (False, "p_bool", False)),
            (b"AudioColor", ((0.0, 1.0, 0.0), "p_vector_3d", False)),  # Yep, vector3d, not corlorgb… :cry:
    
            (b"NearPlane", (10.0, "p_double", False)),
            (b"FarPlane", (4000.0, "p_double", False)),
    
            (b"AutoComputeClipPanes", (False, "p_bool", False)),
            (b"ViewCameraToLookAt", (True, "p_bool", False)),
            (b"ViewFrustumNearFarPlane", (False, "p_bool", False)),
            (b"ViewFrustumBackPlaneMode", (2, "p_enum", False)),  # 2 = show back plane if texture added.
    
            (b"BackPlaneDistanceMode", (1, "p_enum", False)),  # 1 = relative to camera.
            (b"ViewFrustumFrontPlaneMode", (2, "p_enum", False)),  # 2 = show front plane if texture added.
    
            (b"FrontPlaneDistanceMode", (1, "p_enum", False)),  # 1 = relative to camera.
            (b"LockMode", (False, "p_bool", False)),
            (b"LockInterestNavigation", (False, "p_bool", False)),
    
            # BackPlate... properties **arggggg!**
            (b"FitImage", (False, "p_bool", False)),
            (b"Crop", (False, "p_bool", False)),
            (b"Center", (True, "p_bool", False)),
            (b"KeepRatio", (True, "p_bool", False)),
            # End of BackPlate...
    
            (b"BackgroundAlphaTreshold", (0.5, "p_double", False)),
            (b"ShowBackplate", (True, "p_bool", False)),
            (b"BackPlaneOffsetX", (0.0, "p_number", True)),
            (b"BackPlaneOffsetY", (0.0, "p_number", True)),
            (b"BackPlaneRotation", (0.0, "p_number", True)),
            (b"BackPlaneScaleX", (1.0, "p_number", True)),
            (b"BackPlaneScaleY", (1.0, "p_number", True)),
            (b"Background Texture", (None, "p_object", False)),
            (b"FrontPlateFitImage", (True, "p_bool", False)),
            (b"FrontPlateCrop", (False, "p_bool", False)),
            (b"FrontPlateCenter", (True, "p_bool", False)),
            (b"FrontPlateKeepRatio", (True, "p_bool", False)),
            (b"Foreground Opacity", (1.0, "p_double", False)),
            (b"ShowFrontplate", (True, "p_bool", False)),
            (b"FrontPlaneOffsetX", (0.0, "p_number", True)),
            (b"FrontPlaneOffsetY", (0.0, "p_number", True)),
            (b"FrontPlaneRotation", (0.0, "p_number", True)),
            (b"FrontPlaneScaleX", (1.0, "p_number", True)),
            (b"FrontPlaneScaleY", (1.0, "p_number", True)),
            (b"Foreground Texture", (None, "p_object", False)),
    
            (b"DisplaySafeAreaOnRender", (False, "p_bool", False)),
            (b"SafeAreaDisplayStyle", (1, "p_enum", False)),  # 1 = rounded corners.
    
            (b"SafeAreaAspectRatio", (1.3333333333333333, "p_double", False)),
    
            (b"Use2DMagnifierZoom", (False, "p_bool", False)),
            (b"2D Magnifier Zoom", (100.0, "p_number", True)),
            (b"2D Magnifier X", (50.0, "p_number", True)),
            (b"2D Magnifier Y", (50.0, "p_number", True)),
            (b"CameraProjectionType", (0, "p_enum", False)),  # 0 = perspective, 1 = orthogonal.
            (b"OrthoZoom", (1.0, "p_double", False)),
            (b"UseRealTimeDOFAndAA", (False, "p_bool", False)),
            (b"UseDepthOfField", (False, "p_bool", False)),
    
            (b"FocusSource", (0, "p_enum", False)),  # 0 = camera interest, 1 = distance from camera interest.
    
            (b"FocusAngle", (3.5, "p_double", False)),  # ???
    
            (b"UseAntialiasing", (False, "p_bool", False)),
            (b"AntialiasingIntensity", (0.77777, "p_double", False)),
            (b"AntialiasingMethod", (0, "p_enum", False)),  # 0 = oversampling, 1 = hardware.
            (b"UseAccumulationBuffer", (False, "p_bool", False)),
            (b"FrameSamplingCount", (7, "p_integer", False)),
            (b"FrameSamplingType", (1, "p_enum", False)),  # 0 = uniform, 1 = stochastic.
        ))
    
        if override_defaults is not None:
            props.update(override_defaults)
    
        return FBXTemplate(b"NodeAttribute", b"FbxCamera", props, nbr_users, [False])
    
    
    
    def fbx_template_def_bone(scene, settings, override_defaults=None, nbr_users=0):
    
        props = OrderedDict()
    
        if override_defaults is not None:
            props.update(override_defaults)
    
        return FBXTemplate(b"NodeAttribute", b"LimbNode", props, nbr_users, [False])
    
    
    
    def fbx_template_def_geometry(scene, settings, override_defaults=None, nbr_users=0):
    
        props = OrderedDict((
            (b"Color", ((0.8, 0.8, 0.8), "p_color_rgb", False)),
            (b"BBoxMin", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
            (b"BBoxMax", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
            (b"Primary Visibility", (True, "p_bool", False)),
            (b"Casts Shadows", (True, "p_bool", False)),
            (b"Receive Shadows", (True, "p_bool", False)),
    
        if override_defaults is not None:
            props.update(override_defaults)
    
        return FBXTemplate(b"Geometry", b"FbxMesh", props, nbr_users, [False])
    
    
    
    def fbx_template_def_material(scene, settings, override_defaults=None, nbr_users=0):
        # WIP...
    
        props = OrderedDict((
    
            (b"ShadingModel", ("Phong", "p_string", False)),
    
            (b"MultiLayer", (False, "p_bool", False)),
    
            # Lambert-specific.
    
            (b"EmissiveColor", ((0.0, 0.0, 0.0), "p_color", True)),
            (b"EmissiveFactor", (1.0, "p_number", True)),
            (b"AmbientColor", ((0.2, 0.2, 0.2), "p_color", True)),
    
            (b"AmbientFactor", (1.0, "p_number", True)),
    
            (b"DiffuseColor", ((0.8, 0.8, 0.8), "p_color", True)),
    
            (b"DiffuseFactor", (1.0, "p_number", True)),
            (b"TransparentColor", ((0.0, 0.0, 0.0), "p_color", True)),
    
            (b"TransparencyFactor", (0.0, "p_number", True)),
            (b"Opacity", (1.0, "p_number", True)),
            (b"NormalMap", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
            (b"Bump", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
    
            (b"BumpFactor", (1.0, "p_double", False)),
    
            (b"DisplacementColor", ((0.0, 0.0, 0.0), "p_color_rgb", False)),
    
            (b"VectorDisplacementColor", ((0.0, 0.0, 0.0), "p_color_rgb", False)),
    
            (b"VectorDisplacementFactor", (1.0, "p_double", False)),
    
            (b"SpecularColor", ((0.2, 0.2, 0.2), "p_color", True)),
            (b"SpecularFactor", (1.0, "p_number", True)),
    
            # Not sure about the name, importer uses this (but ShininessExponent for tex prop name!)
    
            # And in fbx exported by sdk, you have one in template, the other in actual material!!! :/
            # For now, using both.
    
            (b"Shininess", (20.0, "p_number", True)),
            (b"ShininessExponent", (20.0, "p_number", True)),
            (b"ReflectionColor", ((0.0, 0.0, 0.0), "p_color", True)),
            (b"ReflectionFactor", (1.0, "p_number", True)),
    
        if override_defaults is not None:
            props.update(override_defaults)
    
        return FBXTemplate(b"Material", b"FbxSurfacePhong", props, nbr_users, [False])
    
    
    
    def fbx_template_def_texture_file(scene, settings, override_defaults=None, nbr_users=0):
        # WIP...
        # XXX Not sure about all names!
    
        props = OrderedDict((
            (b"TextureTypeUse", (0, "p_enum", False)),  # Standard.
            (b"AlphaSource", (2, "p_enum", False)),  # Black (i.e. texture's alpha), XXX name guessed!.
    
            (b"Texture alpha", (1.0, "p_double", False)),
    
            (b"PremultiplyAlpha", (True, "p_bool", False)),
            (b"CurrentTextureBlendMode", (1, "p_enum", False)),  # Additive...
            (b"CurrentMappingType", (0, "p_enum", False)),  # UV.
    
            (b"UVSet", ("default", "p_string", False)),  # UVMap name.
    
            (b"WrapModeU", (0, "p_enum", False)),  # Repeat.
            (b"WrapModeV", (0, "p_enum", False)),  # Repeat.
            (b"UVSwap", (False, "p_bool", False)),
            (b"Translation", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
            (b"Rotation", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
            (b"Scaling", ((1.0, 1.0, 1.0), "p_vector_3d", False)),
            (b"TextureRotationPivot", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
            (b"TextureScalingPivot", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
    
            # Not sure about those two...
            (b"UseMaterial", (False, "p_bool", False)),
    
            (b"UseMipMap", (False, "p_bool", False)),
        ))
    
        if override_defaults is not None:
            props.update(override_defaults)
    
        return FBXTemplate(b"Texture", b"FbxFileTexture", props, nbr_users, [False])
    
    
    
    def fbx_template_def_video(scene, settings, override_defaults=None, nbr_users=0):
        # WIP...
    
        props = OrderedDict((
    
            (b"Width", (0, "p_integer", False)),
            (b"Height", (0, "p_integer", False)),
            (b"Path", ("", "p_string_url", False)),
            (b"AccessMode", (0, "p_enum", False)),  # Disk (0=Disk, 1=Mem, 2=DiskAsync).
    
            (b"StartFrame", (0, "p_integer", False)),
            (b"StopFrame", (0, "p_integer", False)),
            (b"Offset", (0, "p_timestamp", False)),
    
            (b"FreeRunning", (False, "p_bool", False)),
            (b"Loop", (False, "p_bool", False)),
            (b"InterlaceMode", (0, "p_enum", False)),  # None, i.e. progressive.
    
            # Image sequences.
    
            (b"ImageSequence", (False, "p_bool", False)),
            (b"ImageSequenceOffset", (0, "p_integer", False)),
    
            (b"LastFrame", (0, "p_integer", False)),
        ))
    
        if override_defaults is not None:
            props.update(override_defaults)
    
        return FBXTemplate(b"Video", b"FbxVideo", props, nbr_users, [False])
    
    def fbx_template_def_pose(scene, settings, override_defaults=None, nbr_users=0):
    
        props = OrderedDict()
    
        if override_defaults is not None:
            props.update(override_defaults)
    
        return FBXTemplate(b"Pose", b"", props, nbr_users, [False])
    
    
    
    def fbx_template_def_deformer(scene, settings, override_defaults=None, nbr_users=0):
    
        props = OrderedDict()
    
        if override_defaults is not None:
            props.update(override_defaults)
    
        return FBXTemplate(b"Deformer", b"", props, nbr_users, [False])
    
    def fbx_template_def_animstack(scene, settings, override_defaults=None, nbr_users=0):
        props = OrderedDict((
            (b"Description", ("", "p_string", False)),
            (b"LocalStart", (0, "p_timestamp", False)),
            (b"LocalStop", (0, "p_timestamp", False)),
            (b"ReferenceStart", (0, "p_timestamp", False)),
            (b"ReferenceStop", (0, "p_timestamp", False)),
        ))
        if override_defaults is not None:
            props.update(override_defaults)
    
        return FBXTemplate(b"AnimationStack", b"FbxAnimStack", props, nbr_users, [False])
    
    
    
    def fbx_template_def_animlayer(scene, settings, override_defaults=None, nbr_users=0):
        props = OrderedDict((
            (b"Weight", (100.0, "p_number", True)),
            (b"Mute", (False, "p_bool", False)),
            (b"Solo", (False, "p_bool", False)),
            (b"Lock", (False, "p_bool", False)),
    
            (b"Color", ((0.8, 0.8, 0.8), "p_color_rgb", False)),
    
            (b"BlendMode", (0, "p_enum", False)),
            (b"RotationAccumulationMode", (0, "p_enum", False)),
            (b"ScaleAccumulationMode", (0, "p_enum", False)),
            (b"BlendModeBypass", (0, "p_ulonglong", False)),
        ))
        if override_defaults is not None:
            props.update(override_defaults)
    
        return FBXTemplate(b"AnimationLayer", b"FbxAnimLayer", props, nbr_users, [False])
    
    
    
    def fbx_template_def_animcurvenode(scene, settings, override_defaults=None, nbr_users=0):
        props = OrderedDict((
    
            (FBX_ANIM_PROPSGROUP_NAME.encode(), (None, "p_compound", False)),
    
        ))
        if override_defaults is not None:
            props.update(override_defaults)
    
        return FBXTemplate(b"AnimationCurveNode", b"FbxAnimCurveNode", props, nbr_users, [False])
    
    
    
    def fbx_template_def_animcurve(scene, settings, override_defaults=None, nbr_users=0):
        props = OrderedDict()
        if override_defaults is not None:
            props.update(override_defaults)
    
        return FBXTemplate(b"AnimationCurve", b"", props, nbr_users, [False])
    
    # ##### Generators for connection elements. #####
    
    def elem_connection(elem, c_type, uid_src, uid_dst, prop_dst=None):
        e = elem_data_single_string(elem, b"C", c_type)
        e.add_int64(uid_src)
        e.add_int64(uid_dst)
        if prop_dst is not None:
            e.add_string(prop_dst)
    
    # ##### FBX objects generators. #####
    
    def fbx_data_element_custom_properties(props, bid):
    
        """
        Store custom properties of blender ID bid (any mapping-like object, in fact) into FBX properties props.
        """
        for k, v in bid.items():
    
            list_val = getattr(v, "to_list", lambda: None)()
    
    
            if isinstance(v, str):
    
                elem_props_set(props, "p_string", k.encode(), v, custom=True)
    
            elif isinstance(v, int):
    
                elem_props_set(props, "p_integer", k.encode(), v, custom=True)
    
            elif isinstance(v, float):
    
                elem_props_set(props, "p_double", k.encode(), v, custom=True)
    
            elif list_val and len(list_val) == 3:
                elem_props_set(props, "p_vector", k.encode(), list_val, custom=True)
    
    def fbx_data_empty_elements(root, empty, scene_data):
        """
        Write the Empty data block.
        """
        empty_key = scene_data.data_empties[empty]
    
    
        null = elem_data_single_int64(root, b"NodeAttribute", get_fbx_uuid_from_key(empty_key))
    
        null.add_string(fbx_name_class(empty.name.encode(), b"NodeAttribute"))
        null.add_string(b"Null")
    
        elem_data_single_string(null, b"TypeFlags", b"Null")
    
    
        tmpl = elem_props_template_init(scene_data.templates, b"Null")
    
    
        # No custom properties, already saved with object (Model).
    
    
    
    def fbx_data_lamp_elements(root, lamp, scene_data):
        """
        Write the Lamp data block.
        """
        gscale = scene_data.settings.global_scale
    
        lamp_key = scene_data.data_lamps[lamp]
        do_light = True
        decay_type = FBX_LIGHT_DECAY_TYPES['CONSTANT']
        do_shadow = False
        shadow_color = Vector((0.0, 0.0, 0.0))
        if lamp.type not in {'HEMI'}:
    
            if lamp.type not in {'SUN', 'AREA'}:
    
                decay_type = FBX_LIGHT_DECAY_TYPES[lamp.falloff_type]
            do_light = (not lamp.use_only_shadow) and (lamp.use_specular or lamp.use_diffuse)
            do_shadow = lamp.shadow_method not in {'NOSHADOW'}
            shadow_color = lamp.shadow_color
    
    
        light = elem_data_single_int64(root, b"NodeAttribute", get_fbx_uuid_from_key(lamp_key))
    
        light.add_string(fbx_name_class(lamp.name.encode(), b"NodeAttribute"))
        light.add_string(b"Light")
    
        elem_data_single_int32(light, b"GeometryVersion", FBX_GEOMETRY_VERSION)  # Sic...
    
    
        tmpl = elem_props_template_init(scene_data.templates, b"Light")
    
        props = elem_properties(light)
        elem_props_template_set(tmpl, props, "p_enum", b"LightType", FBX_LIGHT_TYPES[lamp.type])
        elem_props_template_set(tmpl, props, "p_bool", b"CastLight", do_light)
    
        elem_props_template_set(tmpl, props, "p_color", b"Color", lamp.color)
    
        elem_props_template_set(tmpl, props, "p_number", b"Intensity", lamp.energy * 100.0)
        elem_props_template_set(tmpl, props, "p_enum", b"DecayType", decay_type)
    
        elem_props_template_set(tmpl, props, "p_double", b"DecayStart", lamp.distance * gscale)
    
        elem_props_template_set(tmpl, props, "p_bool", b"CastShadows", do_shadow)
    
        elem_props_template_set(tmpl, props, "p_color", b"ShadowColor", shadow_color)
    
        if lamp.type in {'SPOT'}:
    
            elem_props_template_set(tmpl, props, "p_double", b"OuterAngle", math.degrees(lamp.spot_size))
            elem_props_template_set(tmpl, props, "p_double", b"InnerAngle",
    
                                    math.degrees(lamp.spot_size * (1.0 - lamp.spot_blend)))
    
        if scene_data.settings.use_custom_props:
    
            fbx_data_element_custom_properties(props, lamp)
    
    
    
    def fbx_data_camera_elements(root, cam_obj, scene_data):
        """
        Write the Camera data blocks.
        """
        gscale = scene_data.settings.global_scale
    
    
        cam_key = scene_data.data_cameras[cam_obj]
    
        # Real data now, good old camera!
        # Object transform info.
    
        loc, rot, scale, matrix, matrix_rot = cam_obj.fbx_object_tx(scene_data)
    
        up = matrix_rot * Vector((0.0, 1.0, 0.0))
        to = matrix_rot * Vector((0.0, 0.0, -1.0))
        # Render settings.
        # TODO We could export much more...
        render = scene_data.scene.render
        width = render.resolution_x
        height = render.resolution_y
        aspect = width / height
        # Film width & height from mm to inches
    
    Bastien Montagne's avatar
    Bastien Montagne committed
        filmwidth = convert_mm_to_inch(cam_data.sensor_width)
        filmheight = convert_mm_to_inch(cam_data.sensor_height)
    
        filmaspect = filmwidth / filmheight
        # Film offset
        offsetx = filmwidth * cam_data.shift_x
        offsety = filmaspect * filmheight * cam_data.shift_y
    
    
        cam = elem_data_single_int64(root, b"NodeAttribute", get_fbx_uuid_from_key(cam_key))
    
        cam.add_string(fbx_name_class(cam_data.name.encode(), b"NodeAttribute"))
        cam.add_string(b"Camera")
    
    
        tmpl = elem_props_template_init(scene_data.templates, b"Camera")
    
        props = elem_properties(cam)
    
        elem_props_template_set(tmpl, props, "p_vector", b"Position", loc)
        elem_props_template_set(tmpl, props, "p_vector", b"UpVector", up)
    
        elem_props_template_set(tmpl, props, "p_vector", b"InterestPosition", loc + to)  # Point, not vector!
    
        # Should we use world value?
    
        elem_props_template_set(tmpl, props, "p_color", b"BackgroundColor", (0.0, 0.0, 0.0))
    
        elem_props_template_set(tmpl, props, "p_bool", b"DisplayTurnTableIcon", True)
    
    
        elem_props_template_set(tmpl, props, "p_enum", b"AspectRatioMode", 2)  # FixedResolution
    
        elem_props_template_set(tmpl, props, "p_double", b"AspectWidth", float(render.resolution_x))
        elem_props_template_set(tmpl, props, "p_double", b"AspectHeight", float(render.resolution_y))
        elem_props_template_set(tmpl, props, "p_double", b"PixelAspectRatio",
                                float(render.pixel_aspect_x / render.pixel_aspect_y))
    
        elem_props_template_set(tmpl, props, "p_double", b"FilmWidth", filmwidth)
        elem_props_template_set(tmpl, props, "p_double", b"FilmHeight", filmheight)
        elem_props_template_set(tmpl, props, "p_double", b"FilmAspectRatio", filmaspect)
        elem_props_template_set(tmpl, props, "p_double", b"FilmOffsetX", offsetx)
        elem_props_template_set(tmpl, props, "p_double", b"FilmOffsetY", offsety)
    
    
        elem_props_template_set(tmpl, props, "p_enum", b"ApertureMode", 3)  # FocalLength.
        elem_props_template_set(tmpl, props, "p_enum", b"GateFit", 2)  # FitHorizontal.
        elem_props_template_set(tmpl, props, "p_fov", b"FieldOfView", math.degrees(cam_data.angle_x))
        elem_props_template_set(tmpl, props, "p_fov_x", b"FieldOfViewX", math.degrees(cam_data.angle_x))
        elem_props_template_set(tmpl, props, "p_fov_y", b"FieldOfViewY", math.degrees(cam_data.angle_y))
        # No need to convert to inches here...
    
        elem_props_template_set(tmpl, props, "p_double", b"FocalLength", cam_data.lens)
        elem_props_template_set(tmpl, props, "p_double", b"SafeAreaAspectRatio", aspect)
    
        # Default to perspective camera.
        elem_props_template_set(tmpl, props, "p_enum", b"CameraProjectionType", 1 if cam_data.type == 'ORTHO' else 0)
        elem_props_template_set(tmpl, props, "p_double", b"OrthoZoom", cam_data.ortho_scale)
    
        elem_props_template_set(tmpl, props, "p_double", b"NearPlane", cam_data.clip_start * gscale)
        elem_props_template_set(tmpl, props, "p_double", b"FarPlane", cam_data.clip_end * gscale)
    
        elem_props_template_set(tmpl, props, "p_enum", b"BackPlaneDistanceMode", 1)  # RelativeToCamera.
    
        elem_props_template_set(tmpl, props, "p_double", b"BackPlaneDistance", cam_data.clip_end * gscale)
    
        # Custom properties.
    
        if scene_data.settings.use_custom_props:
    
            fbx_data_element_custom_properties(props, cam_data)
    
    
        elem_data_single_string(cam, b"TypeFlags", b"Camera")
        elem_data_single_int32(cam, b"GeometryVersion", 124)  # Sic...
        elem_data_vec_float64(cam, b"Position", loc)
        elem_data_vec_float64(cam, b"Up", up)
        elem_data_vec_float64(cam, b"LookAt", to)
        elem_data_single_int32(cam, b"ShowInfoOnMoving", 1)
        elem_data_single_int32(cam, b"ShowAudio", 0)
        elem_data_vec_float64(cam, b"AudioColor", (0.0, 1.0, 0.0))
        elem_data_single_float64(cam, b"CameraOrthoZoom", 1.0)
    
    
    
    def fbx_data_bindpose_element(root, me_obj, me, scene_data, arm_obj=None, bones=[]):
        """
        Helper, since bindpose are used by both meshes shape keys and armature bones...
        """
        if arm_obj is None:
            arm_obj = me_obj
        # We assume bind pose for our bones are their "Editmode" pose...
        # All matrices are expected in global (world) space.
        bindpose_key = get_blender_bindpose_key(arm_obj.bdata, me)
        fbx_pose = elem_data_single_int64(root, b"Pose", get_fbx_uuid_from_key(bindpose_key))
        fbx_pose.add_string(fbx_name_class(me.name.encode(), b"Pose"))
        fbx_pose.add_string(b"BindPose")
    
        elem_data_single_string(fbx_pose, b"Type", b"BindPose")
        elem_data_single_int32(fbx_pose, b"Version", FBX_POSE_BIND_VERSION)
        elem_data_single_int32(fbx_pose, b"NbPoseNodes", 1 + len(bones))
    
        # First node is mesh/object.
        mat_world_obj = me_obj.fbx_object_matrix(scene_data, global_space=True)
        fbx_posenode = elem_empty(fbx_pose, b"PoseNode")
        elem_data_single_int64(fbx_posenode, b"Node", me_obj.fbx_uuid)
        elem_data_single_float64_array(fbx_posenode, b"Matrix", matrix4_to_array(mat_world_obj))
        # And all bones of armature!
        mat_world_bones = {}
        for bo_obj in bones:
            bomat = bo_obj.fbx_object_matrix(scene_data, rest=True, global_space=True)
            mat_world_bones[bo_obj] = bomat
            fbx_posenode = elem_empty(fbx_pose, b"PoseNode")
            elem_data_single_int64(fbx_posenode, b"Node", bo_obj.fbx_uuid)
            elem_data_single_float64_array(fbx_posenode, b"Matrix", matrix4_to_array(bomat))
    
        return mat_world_obj, mat_world_bones
    
    
    def fbx_data_mesh_shapes_elements(root, me_obj, me, scene_data, fbx_me_tmpl, fbx_me_props):
        """
        Write shape keys related data.
        """
        if me not in scene_data.data_deformers_shape:
            return
    
        # First, write the geometry data itself (i.e. shapes).
        _me_key, shape_key, shapes = scene_data.data_deformers_shape[me]
    
        channels = []
    
        for shape, (channel_key, geom_key, shape_verts_co, shape_verts_idx) in shapes.items():
            # Use vgroups as weights, if defined.
            if shape.vertex_group and shape.vertex_group in me_obj.bdata.vertex_groups:
                shape_verts_weights = [0.0] * (len(shape_verts_co) // 3)
                vg_idx = me_obj.bdata.vertex_groups[shape.vertex_group].index
                for sk_idx, v_idx in enumerate(shape_verts_idx):
                    for vg in me.vertices[v_idx].groups:
                        if vg.group == vg_idx:
                            shape_verts_weights[sk_idx] = vg.weight * 100.0
            else:
                shape_verts_weights = [100.0] * (len(shape_verts_co) // 3)
            channels.append((channel_key, shape, shape_verts_weights))
    
            geom = elem_data_single_int64(root, b"Geometry", get_fbx_uuid_from_key(geom_key))
            geom.add_string(fbx_name_class(shape.name.encode(), b"Geometry"))
            geom.add_string(b"Shape")
    
            tmpl = elem_props_template_init(scene_data.templates, b"Geometry")
            props = elem_properties(geom)
            elem_props_template_finalize(tmpl, props)
    
            elem_data_single_int32(geom, b"Version", FBX_GEOMETRY_SHAPE_VERSION)
    
            elem_data_single_int32_array(geom, b"Indexes", shape_verts_idx)
            elem_data_single_float64_array(geom, b"Vertices", shape_verts_co)
            elem_data_single_float64_array(geom, b"Normals", [0.0] * len(shape_verts_co))
    
        # Yiha! BindPose for shapekeys too! Dodecasigh...
        # XXX Not sure yet whether several bindposes on same mesh are allowed, or not... :/
        fbx_data_bindpose_element(root, me_obj, me, scene_data)
    
        # ...and now, the deformers stuff.
        fbx_shape = elem_data_single_int64(root, b"Deformer", get_fbx_uuid_from_key(shape_key))
        fbx_shape.add_string(fbx_name_class(me.name.encode(), b"Deformer"))
        fbx_shape.add_string(b"BlendShape")
    
        elem_data_single_int32(fbx_shape, b"Version", FBX_DEFORMER_SHAPE_VERSION)
    
        for channel_key, shape, shape_verts_weights in channels:
            fbx_channel = elem_data_single_int64(root, b"Deformer", get_fbx_uuid_from_key(channel_key))
            fbx_channel.add_string(fbx_name_class(shape.name.encode(), b"SubDeformer"))
            fbx_channel.add_string(b"BlendShapeChannel")
    
            elem_data_single_int32(fbx_channel, b"Version", FBX_DEFORMER_SHAPECHANNEL_VERSION)
            elem_data_single_float64(fbx_channel, b"DeformPercent", shape.value * 100.0)  # Percents...
            elem_data_single_float64_array(fbx_channel, b"FullWeights", shape_verts_weights)
    
            # *WHY* add this in linked mesh properties too? *cry*
            # No idea whether it’s percent here too, or more usual factor (assume percentage for now) :/
            elem_props_template_set(fbx_me_tmpl, fbx_me_props, "p_number", shape.name.encode(), shape.value * 100.0,
                                    animatable=True)
    
    
    
    def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
    
        """
        Write the Mesh (Geometry) data block.
        """
    
        # Ugly helper... :/
        def _infinite_gen(val):
            while 1:
                yield val
    
    
        me_key, me, _free = scene_data.data_meshes[me_obj]
    
        # In case of multiple instances of same mesh, only write it once!
        if me_key in done_meshes:
            return
    
    
        # No gscale/gmat here, all data are supposed to be in object space.
        smooth_type = scene_data.settings.mesh_smooth_type
    
        do_bake_space_transform = me_obj.use_bake_space_transform(scene_data)
    
    
        # Vertices are in object space, but we are post-multiplying all transforms with the inverse of the
        # global matrix, so we need to apply the global matrix to the vertices to get the correct result.
        geom_mat_co = scene_data.settings.global_matrix if do_bake_space_transform else None
        # We need to apply the inverse transpose of the global matrix when transforming normals.
    
        geom_mat_no = Matrix(scene_data.settings.global_matrix_inv_transposed) if do_bake_space_transform else None
        if geom_mat_no is not None:
            # Remove translation & scaling!
            geom_mat_no.translation = Vector()
            geom_mat_no.normalize()
    
        geom = elem_data_single_int64(root, b"Geometry", get_fbx_uuid_from_key(me_key))
    
        geom.add_string(fbx_name_class(me.name.encode(), b"Geometry"))
        geom.add_string(b"Mesh")
    
    
    Bastien Montagne's avatar
    Bastien Montagne committed
        tmpl = elem_props_template_init(scene_data.templates, b"Geometry")
    
        props = elem_properties(geom)
    
        # Custom properties.
    
        if scene_data.settings.use_custom_props:
    
            fbx_data_element_custom_properties(props, me)
    
    
        elem_data_single_int32(geom, b"GeometryVersion", FBX_GEOMETRY_VERSION)
    
        # Vertex cos.
    
        t_co = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * len(me.vertices) * 3
    
        me.vertices.foreach_get("co", t_co)
    
        elem_data_single_float64_array(geom, b"Vertices", chain(*vcos_transformed_gen(t_co, geom_mat_co)))
    
        del t_co
    
        # Polygon indices.
        #
        # We do loose edges as two-vertices faces, if enabled...
        #
    
    Bastien Montagne's avatar
    Bastien Montagne committed
        # Note we have to process Edges in the same time, as they are based on poly's loops...
    
        loop_nbr = len(me.loops)
    
        t_pvi = array.array(data_types.ARRAY_INT32, (0,)) * loop_nbr
    
        t_ls = [None] * len(me.polygons)
    
        me.loops.foreach_get("vertex_index", t_pvi)
        me.polygons.foreach_get("loop_start", t_ls)
    
        # Add "fake" faces for loose edges.
        if scene_data.settings.use_mesh_edges:
            t_le = tuple(e.vertices for e in me.edges if e.is_loose)
            t_pvi.extend(chain(*t_le))
            t_ls.extend(range(loop_nbr, loop_nbr + len(t_le), 2))
            del t_le
    
        # Edges...
        # Note: Edges are represented as a loop here: each edge uses a single index, which refers to the polygon array.
        #       The edge is made by the vertex indexed py this polygon's point and the next one on the same polygon.
        #       Advantage: Only one index per edge.
        #       Drawback: Only polygon's edges can be represented (that's why we have to add fake two-verts polygons
        #                 for loose edges).
        #       We also have to store a mapping from real edges to their indices in this array, for edge-mapped data
        #       (like e.g. crease).
        t_eli = array.array(data_types.ARRAY_INT32)
        edges_map = {}
        edges_nbr = 0
        if t_ls and t_pvi:
            t_ls = set(t_ls)
            todo_edges = [None] * len(me.edges) * 2
            me.edges.foreach_get("vertices", todo_edges)
            todo_edges = set((v1, v2) if v1 < v2 else (v2, v1) for v1, v2 in zip(*(iter(todo_edges),) * 2))
    
            li = 0
            vi = vi_start = t_pvi[0]
            for li_next, vi_next in enumerate(t_pvi[1:] + t_pvi[:1], start=1):
                if li_next in t_ls:  # End of a poly's loop.
                    vi2 = vi_start
                    vi_start = vi_next
                else:
                    vi2 = vi_next
    
                e_key = (vi, vi2) if vi < vi2 else (vi2, vi)
                if e_key in todo_edges:
                    t_eli.append(li)
                    todo_edges.remove(e_key)
                    edges_map[e_key] = edges_nbr
                    edges_nbr += 1
    
                vi = vi_next
                li = li_next
        # End of edges!
    
        # We have to ^-1 last index of each loop.
        for ls in t_ls:
            t_pvi[ls - 1] ^= -1
    
        # And finally we can write data!
        elem_data_single_int32_array(geom, b"PolygonVertexIndex", t_pvi)
        elem_data_single_int32_array(geom, b"Edges", t_eli)
        del t_pvi
        del t_ls
        del t_eli
    
        # And now, layers!
    
        # Smoothing.
        if smooth_type in {'FACE', 'EDGE'}:
            t_ps = None
            _map = b""
            if smooth_type == 'FACE':
    
                t_ps = array.array(data_types.ARRAY_INT32, (0,)) * len(me.polygons)
    
                me.polygons.foreach_get("use_smooth", t_ps)
                _map = b"ByPolygon"
            else:  # EDGE
                # Write Edge Smoothing.
    
                t_ps = array.array(data_types.ARRAY_INT32, (0,)) * edges_nbr
    
                for e in me.edges:
                    if e.key not in edges_map:
                        continue  # Only loose edges, in theory!
                    t_ps[edges_map[e.key]] = not e.use_edge_sharp
                _map = b"ByEdge"
            lay_smooth = elem_data_single_int32(geom, b"LayerElementSmoothing", 0)
            elem_data_single_int32(lay_smooth, b"Version", FBX_GEOMETRY_SMOOTHING_VERSION)
            elem_data_single_string(lay_smooth, b"Name", b"")
            elem_data_single_string(lay_smooth, b"MappingInformationType", _map)
            elem_data_single_string(lay_smooth, b"ReferenceInformationType", b"Direct")
    
    Bastien Montagne's avatar
    Bastien Montagne committed
            elem_data_single_int32_array(lay_smooth, b"Smoothing", t_ps)  # Sight, int32 for bool...
    
            del t_ps
    
        # TODO: Edge crease (LayerElementCrease).
    
        # And we are done with edges!
        del edges_map
    
        # Loop normals.
    
            # NOTE: this is not supported by importer currently.
            # XXX Official docs says normals should use IndexToDirect,
            #     but this does not seem well supported by apps currently...
    
    
            t_ln = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * len(me.loops) * 3
            me.loops.foreach_get("normal", t_ln)
    
            t_ln = nors_transformed_gen(t_ln, geom_mat_no)
    
            if 0:
                t_ln = tuple(t_ln)  # No choice... :/
    
                lay_nor = elem_data_single_int32(geom, b"LayerElementNormal", 0)
                elem_data_single_int32(lay_nor, b"Version", FBX_GEOMETRY_NORMAL_VERSION)
                elem_data_single_string(lay_nor, b"Name", b"")
                elem_data_single_string(lay_nor, b"MappingInformationType", b"ByPolygonVertex")
                elem_data_single_string(lay_nor, b"ReferenceInformationType", b"IndexToDirect")
    
                ln2idx = tuple(set(t_ln))
                elem_data_single_float64_array(lay_nor, b"Normals", chain(*ln2idx))
                # Normal weights, no idea what it is.
                # t_lnw = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * len(ln2idx)
                # elem_data_single_float64_array(lay_nor, b"NormalsW", t_lnw)
    
                ln2idx = {nor: idx for idx, nor in enumerate(ln2idx)}
                elem_data_single_int32_array(lay_nor, b"NormalsIndex", (ln2idx[n] for n in t_ln))
    
                del ln2idx
    
                # del t_lnw
    
            else:
                lay_nor = elem_data_single_int32(geom, b"LayerElementNormal", 0)
                elem_data_single_int32(lay_nor, b"Version", FBX_GEOMETRY_NORMAL_VERSION)
                elem_data_single_string(lay_nor, b"Name", b"")
                elem_data_single_string(lay_nor, b"MappingInformationType", b"ByPolygonVertex")
                elem_data_single_string(lay_nor, b"ReferenceInformationType", b"Direct")
                elem_data_single_float64_array(lay_nor, b"Normals", chain(*t_ln))
                # Normal weights, no idea what it is.
                # t_ln = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * len(me.loops)
                # elem_data_single_float64_array(lay_nor, b"NormalsW", t_ln)
            del t_ln
    
            # tspace
            if scene_data.settings.use_tspace:
                tspacenumber = len(me.uv_layers)
                if tspacenumber:
                    t_ln = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * len(me.loops) * 3
                    # t_lnw = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * len(me.loops)
                    for idx, uvlayer in enumerate(me.uv_layers):
                        name = uvlayer.name
                        me.calc_tangents(name)
                        # Loop bitangents (aka binormals).
                        # NOTE: this is not supported by importer currently.