Skip to content
Snippets Groups Projects
export_fbx_bin.py 153 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 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 bpy_extras import node_shader_utils
    
    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_CREASE_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.
    
        PerfMon,
        units_blender_to_fbx_factor, 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,
    
        get_blender_nodetexture_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,
    
    # Units converters!
    
    Bastien Montagne's avatar
    Bastien Montagne committed
    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):
    
        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 = {
            # 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"InheritType": (0, "p_enum", False),  # RrSs
            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"DefaultAttributeIndex": (-1, "p_integer", 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 = {
            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 = {
            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):
    
        props = {
            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"PixelAspectRatio": (1.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"ShowTimeCode": (False, "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"BackPlaneDistance": (4000.0, "p_number", True),
            b"BackPlaneDistanceMode": (1, "p_enum", False),  # 1 = relative to camera.
            b"ViewFrustumFrontPlaneMode": (2, "p_enum", False),  # 2 = show front plane if texture added.
            b"FrontPlaneDistance": (10.0, "p_number", True),
            b"FrontPlaneDistanceMode": (1, "p_enum", False),  # 1 = relative to camera.
            b"LockMode": (False, "p_bool", False),
            b"LockInterestNavigation": (False, "p_bool", False),
    
            b"FitImage": (False, "p_bool", False),
            b"Crop": (False, "p_bool", False),
            b"Center": (True, "p_bool", False),
            b"KeepRatio": (True, "p_bool", False),
    
            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"DisplaySafeArea": (False, "p_bool", 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"FocusDistance": (200.0, "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):
    
        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 = {
            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 = {
            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"DisplacementFactor": (1.0, "p_double", 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 = {
            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),
    
            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...
    
            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"PlaySpeed": (0.0, "p_double", 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"FrameRate": (0.0, "p_double", 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):
    
        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):
    
        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 = {
            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 = {
            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 = {
            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):
    
        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.
        """
    
        items = bid.items()
    
        if not items:
            return
    
        rna_properties = {prop.identifier for prop in bid.bl_rna.properties if prop.is_runtime}
    
        for k, v in items:
    
            if k in rna_properties:
    
            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:
                if len(list_val) == 3:
                    elem_props_set(props, "p_vector", k.encode(), list_val, custom=True)
                else:
                    elem_props_set(props, "p_string", k.encode(), str(list_val), custom=True)
            else:
                elem_props_set(props, "p_string", k.encode(), str(v), custom=True)
    
    def fbx_data_empty_elements(root, empty, scene_data):
        """
    
        Write the Empty data block (you can control its FBX datatype with the 'fbx_type' string custom property).
    
        """
        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"))
    
        val = empty.bdata.get('fbx_type', None)
        null.add_string(val.encode() if val and isinstance(val, str) else 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_light_elements(root, lamp, scene_data):
    
        """
        Write the Lamp data block.
        """
        gscale = scene_data.settings.global_scale
    
    
        light_key = scene_data.data_lights[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 = True
            do_shadow = lamp.use_shadow
    
            shadow_color = lamp.shadow_color
    
    
        light = elem_data_single_int64(root, b"NodeAttribute", get_fbx_uuid_from_key(light_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, mat_world_arm=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 + (1 if (arm_obj != me_obj) else 0) + 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))
    
        # Second node is armature object itself.
        if arm_obj != me_obj:
            fbx_posenode = elem_empty(fbx_pose, b"PoseNode")
            elem_data_single_int64(fbx_posenode, b"Node", arm_obj.fbx_uuid)
            elem_data_single_float64_array(fbx_posenode, b"Matrix", matrix4_to_array(mat_world_arm))
    
        # 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
    
    
        write_normals = True  # scene_data.settings.mesh_smooth_type in {'OFF'}
    
    
        # 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)
    
            if write_normals:
                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)
    
        # Subdivision levels. Take them from the first found subsurf modifier from the
        # first object that has the mesh. Write crease information if the object has
        # and subsurf modifier.
        write_crease = False
        if scene_data.settings.use_subsurf:
            last_subsurf = None
            for mod in me_obj.bdata.modifiers:
                if not (mod.show_render or mod.show_viewport):
                    continue
                if mod.type == 'SUBSURF' and mod.subdivision_type == 'CATMULL_CLARK':
                    last_subsurf = mod
    
            if last_subsurf:
                elem_data_single_int32(geom, b"Smoothness", 2) # Display control mesh and smoothed
    
                if last_subsurf.boundary_smooth == "PRESERVE_CORNERS":
                    elem_data_single_int32(geom, b"BoundaryRule", 2) # CreaseAll
                else:
                    elem_data_single_int32(geom, b"BoundaryRule", 1) # CreaseEdge
    
                elem_data_single_int32(geom, b"PreviewDivisionLevels", last_subsurf.levels)
                elem_data_single_int32(geom, b"RenderDivisionLevels", last_subsurf.render_levels)
    
                elem_data_single_int32(geom, b"PreserveBorders", 0)
                elem_data_single_int32(geom, b"PreserveHardEdges", 0)
                elem_data_single_int32(geom, b"PropagateEdgeHardness", 0)
    
    
                write_crease = last_subsurf.use_creases
    
        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
    
            # Sigh, cannot access edge.key through foreach_get... :/
    
            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.
    
                # Note edge is sharp also if it's used by more than two faces, or one of its faces is flat.
    
                t_ps = array.array(data_types.ARRAY_INT32, (0,)) * edges_nbr
    
                sharp_edges = set()
                temp_sharp_edges = {}
                for p in me.polygons:
                    if not p.use_smooth:
                        sharp_edges.update(p.edge_keys)
                        continue
                    for k in p.edge_keys:
                        if temp_sharp_edges.setdefault(k, 0) > 1:
                            sharp_edges.add(k)
                        else:
                            temp_sharp_edges[k] += 1
                del temp_sharp_edges
    
                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 or (e.key in sharp_edges))