"""FBX to Three.js converter (based on FBX import example from Autodesk FBX SDK)

Converts FBX scene file into Three.js scene format (one scene file and several ascii model files plus textures).

-------------------------
How to use this converter
-------------------------

python convert_fbx_three.py scene.fbx output_folder

------------
Dependencies
------------

Requires Autodesk FBX SDK Python bindings. 

If your platform is not included in "modules", full SDK can be downloaded from here: 
    
    http://usa.autodesk.com/adsk/servlet/pc/index?siteID=123112&id=6837478

---------------------------
How to load generated scene 
---------------------------

<script type="text/javascript" src="ThreeExtras.js"></script>

<script type="text/javascript">

    ...
    
    SCENE_URL = "http://example.com/output_folder/scene.js";
    SceneUtils.loadScene( SCENE_URL, callback_sync, callback_async, callback_progress );

    ...

    /*
    callback_sync     - called after the scene file is loaded (procedural elements like cubes and spheres or materials without textures are now ready)
    callback_progress - called after each asynchronous elements gets loaded (textures and model files)
    callback_async    - called after all asynchronous elements (models and textures) are loaded, now whole scene is ready
    
    Scene is fully created, just needs to be passed to renderer, together with camera. 
    Individual scene elements are also returned for convenient access via hashmap (addressable by their id).
    
    progress = {
        
        total_models:    X,
        total_textures:  Y,
        loaded_models:   A,
        loaded_textures: B
        
    };
    
    result = {
        
        scene: new THREE.Scene(),
        geometries: { ... },
        materials: { ... },
        textures: { ... },
        objects: { ... },
        cameras: { ... },
        lights: { ... },
        fogs: { ... },
        
        currentCamera: THREE.Camera( ... )
    
    };
    
    */
    
    var callback_progress = function( progress, result ) { ... };    
    var callback_sync = function( result ) { ... };
    var callback_async = function( result ) { ... };
    
    };

</script>


--------
Features
--------

- scene + models + materials + textures
- multiple UV layers

-------------------
Current limitations
-------------------

- only very small subset from FBX is currently supported
    - static meshes
    - triangles and quads
    - max two UV layers
    - Lambert and Phong materials (partial: Three doesn't support emissive color)
- no lights yet
- no cameras yet (default one is created instead)

------
Author
------
AlteredQualia http://alteredqualia.com

"""

import os
import sys
import random
import math
import pprint
import shutil
import string
import os.path

# load platform specific binary modules
platform_map = {
"Windows" : 
    { 
    "folder": "win", 
    "versions" : {
        2 : "Python26_x86",
        3 : "Python31_x86"
        }
    },
"Linux" :
    { 
    "folder": "linux", 
    "versions" : {
        2 : "Python26_x86",
        3 : "Python31_x86"
        }
    },
"Darwin"  :
    { 
    "folder": "mac", 
    "versions" : {
        2 : "Python26_x86",
        3 : "Python31_x86"
        }
    }
}

import platform
system = platform.system()
version = sys.version_info[0]
if system in platform_map:
    if version in platform_map[system]["versions"]:
        mod_path = os.path.join(sys.path[0], "modules", platform_map[system]["folder"], platform_map[system]["versions"][version])
        print mod_path
        sys.path.append(mod_path)

# #####################################################
# Configuration
# #####################################################
DEFAULTS = {
"bgcolor" : [0, 0, 0],
"bgalpha" : 1.0,
"camera"  : 
    {
        "name" : "default_camera",
        "type" : "perspective",
        "near" : 1,
        "far"  : 10000,
        "fov"  : 60,
        "aspect": 1.333,
        "position" : [0, 0, 10],
        "target"   : [0, 0, 0]
    }
}

MATERIALS_IN_SCENE = True
DEBUG_FBX_JSON = True

# default colors for debugging (each material gets one distinct color): 
# white, red, green, blue, yellow, cyan, magenta
COLORS = [0xeeeeee, 0xee0000, 0x00ee00, 0x0000ee, 0xeeee00, 0x00eeee, 0xee00ee]


# #####################################################
# Templates - scene
# #####################################################
TEMPLATE_SCENE_ASCII = u"""\
// Converted from: %(fname)s
//  Generated with FBX -> Three.js converter
//  http://github.com/alteredq/three.js/blob/master/utils/exporters/fbx/convert_fbx_three.py

var url_base     = %(url_base)s,
    url_models   = url_base + "models/",
    url_textures = url_base + "textures/";

var scene = {
%(sections)s

"defaults" : 
{
    "bgcolor" : %(bgcolor)s,
    "bgalpha" : %(bgalpha)f,
    "camera"  : %(defcamera)s
}

}

postMessage( scene );

"""

TEMPLATE_SECTION = """
"%s" : 
{
%s
},
"""

TEMPLATE_OBJECT = """\
    %(object_id)s : {
        "geometry"  : %(geometry_id)s,
        "materials" : [ %(material_id)s ],
        "position"  : %(position)s,
        "rotation"  : %(rotation)s,
        "scale"	    : %(scale)s,
        "visible"   : true
    }"""

TEMPLATE_GEOMETRY = """\
    %(geometry_id)s : {
        "type" : "ascii_mesh",
        "url"  : url_models + %(model_file)s
    }"""

TEMPLATE_TEXTURE = """\
    %(texture_id)s : {
        "url": url_textures + %(texture_file)s
    }"""

TEMPLATE_MATERIAL_SCENE = """\
    %(material_id)s : {
        "type": %(type)s,
        "parameters": { %(parameters)s } 
    }"""

TEMPLATE_CAMERA_PERSPECTIVE = """\
    %(camera_id)s : {
        "type"  : "perspective",
        "fov"   : %(fov)f,
        "aspect": %(aspect)f,
        "near"  : %(near)f,
        "far"   : %(far)f,
        "position": %(position)s,
        "target"  : %(target)s
    }"""

TEMPLATE_CAMERA_ORTHO = """\
    %(camera_id)s: {
        "type"  : "ortho",
        "left"  : %(left)f,
        "right" : %(right)f,
        "top"   : %(top)f,
        "bottom": %(bottom)f,
        "near"  : %(near)f,
        "far"   : %(far)f,
        "position": %(position)s,
        "target"  : %(target)s
    }"""

TEMPLATE_VEC3 = '[ %f, %f, %f ]'  
TEMPLATE_VEC2 = '[ %f, %f ]'  
TEMPLATE_STRING = '"%s"'
TEMPLATE_HEX = "0x%06x"

# #####################################################
# Templates - model
# #####################################################
TEMPLATE_MODEL_ASCII = u"""\
// Converted from: %(fname)s
//  vertices: %(nvertex)d
//  faces: %(nface)d 
//  materials: %(nmaterial)d
//
//  Generated with FBX -> Three.js converter
//  http://github.com/alteredq/three.js/blob/master/utils/exporters/fbx/convert_fbx_three.py

var model = {
    'materials': [%(materials)s],

    'normals': [%(normals)s],

    'vertices': [%(vertices)s],

    'uvs': [%(uvs)s],
    'uvs2': [%(uvs2)s],

    'triangles': [%(triangles)s],
    'trianglesUvs': [%(trianglesUvs)s],
    'trianglesNormals': [%(trianglesNormals)s],
    'trianglesNormalsUvs': [%(trianglesNormalsUvs)s],

    'quads': [%(quads)s],
    'quadsUvs': [%(quadsUvs)s],
    'quadsNormals': [%(quadsNormals)s],
    'quadsNormalsUvs': [%(quadsNormalsUvs)s],

    'end': (new Date).getTime()
    }
    
postMessage( model );
"""

TEMPLATE_VERTEX = "%f,%f,%f"

TEMPLATE_UV_TRI = "%f,%f,%f,%f,%f,%f"
TEMPLATE_UV_QUAD = "%f,%f,%f,%f,%f,%f,%f,%f"

TEMPLATE_TRI = "%d,%d,%d,%d"
TEMPLATE_QUAD = "%d,%d,%d,%d,%d"

TEMPLATE_TRI_UV = "%d,%d,%d,%d,%d,%d,%d"
TEMPLATE_QUAD_UV = "%d,%d,%d,%d,%d,%d,%d,%d,%d"

TEMPLATE_TRI_N = "%d,%d,%d,%d,%d,%d,%d"
TEMPLATE_QUAD_N = "%d,%d,%d,%d,%d,%d,%d,%d,%d"

TEMPLATE_TRI_N_UV = "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d"
TEMPLATE_QUAD_N_UV = "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d"

TEMPLATE_N = "%f,%f,%f"
TEMPLATE_UV = "%f,%f"

# #####################################################
# Templates - misc
# #####################################################
TEMPLATE_HTACCESS = """\
<Files *.js>
SetOutputFilter DEFLATE
</Files>
"""

# #####################################################
# Parser - global settings
# #####################################################
def extract_color(color):
    return [color.mRed, color.mGreen, color.mBlue]
    
def extract_vec2(v):
    return [v[0], v[1]]

def extract_vec3(v):
    return [v[0], v[1], v[2]]

def extract_global_settings(settings):
    settings = {
    "ambient_color" : extract_color(settings.GetAmbientColor()),
    "default_camera": settings.GetDefaultCamera().Buffer()
    }
    
    return settings
    
def extract_object_properties(object):
    info = {}

    property = object.GetFirstProperty()
    while property.IsValid():
        name = property.GetName().Buffer()
        
        value = "UNIDENTIFIED"
        
        ptype = property.GetPropertyDataType().GetType()
        if ptype in [eBOOL1, eDOUBLE1, eINTEGER1, eDOUBLE4, eDOUBLE3, eFLOAT1]:
            value = property.Get()            
        elif ptype == eSTRING:
            value = property.Get().Buffer()
        
        info[name] = {
        "label" : property.GetLabel().Buffer(),
        "type"  : property.GetPropertyDataType().GetName(),
        "value" : value
        }
        
        property = object.GetNextProperty(property)
        
    return info
        
def extract_node_generic_info(node):
    info = []
    
    node_info = {
    "name"       : node.GetName(),
    "type"       : node.ClassId.GetFbxFileTypeName(),
    "properties" : extract_object_properties(node)
    }
    
    info.append(node_info)
    
    for i in range(node.GetChildCount()):
        info += extract_node_generic_info(node.GetChild(i))
        
    return info
    
def extract_generic_info(scene):
    info_list = []
    
    root_node = scene.GetRootNode()
    for i in range(root_node.GetChildCount()):
        child = root_node.GetChild(i)
        info_list += extract_node_generic_info(child)
        
    info_dict = {}
    for item in info_list:
        name = item["name"]
        info_dict[name] = item["properties"]
        
    return info_dict
    
# #####################################################
# Parser - hierarchy
# #####################################################
def extract_node_hierarchy(node, depth):
    hierarchy = { "name" : node.GetName(), "depth" : depth, "children" : [] }    

    for i in range(node.GetChildCount()):
        hierarchy["children"].append(extract_node_hierarchy(node.GetChild(i), depth + 1))
    
    return hierarchy

def extract_hierarchy(scene):
    root = scene.GetRootNode()
    
    hierarchy = { "name" : "root", "depth" : 0, "children" : [] }
    
    for i in range(root.GetChildCount()):
        hierarchy["children"].append(extract_node_hierarchy(root.GetChild(i), 1))
        
    return hierarchy
    
# #####################################################
# Parser - mesh - layers
# #####################################################
def extract_layer_groups(layer, poly_count):
    group = []
    
    polygroups = layer.GetPolygonGroups()
    if polygroups and \
       polygroups.GetMappingMode() == KFbxLayerElement.eBY_POLYGON and \
       polygroups.GetReferenceMode() == KFbxLayerElement.eINDEX:
        
        polygroups_index_array = polygroups.GetIndexArray()
        group = []
        for p in range(poly_count):
            group_id = polygroups_index_array.GetAt(p)
            group.append(group_id)
    
    return group

def extract_layer_materials(layer, poly_count, mesh):
    material_index_layer = []
    
    materials = layer.GetMaterials()
    if materials:
        index_array = materials.GetIndexArray()
        index_array_count = index_array.GetCount() 
        for i in range(index_array_count):
            material_index_layer.append(index_array.GetAt(i))
    
    return material_index_layer
    
def extract_layer_uvs(layer, poly_count, mesh):
    uv_values = []
    uv_index_layer = []
    
    uvs = layer.GetUVs()
    if uvs:
        uvs_array = uvs.GetDirectArray()
        uvs_count = uvs_array.GetCount()

        # values
        for i in range(uvs_count):
            uv = extract_vec2(uvs_array.GetAt(i))
            uv_values.append(uv)

        # indices        
        if uvs.GetMappingMode() == KFbxLayerElement.eBY_CONTROL_POINT \
           and uvs.GetReferenceMode() == KFbxLayerElement.eDIRECT:
            
            for p in range(poly_count):
                tmp = []
                polygon_size = mesh.GetPolygonSize(p)
                for v in range(polygon_size):
                    id = mesh.GetPolygonVertex(p, v)
                    tmp.append(id)
                
                uv_index_layer.append(tmp)

        elif uvs.GetMappingMode() == KFbxLayerElement.eBY_CONTROL_POINT \
           and uvs.GetReferenceMode() == KFbxLayerElement.eINDEX_TO_DIRECT:
            
            uvs_index_array = uvs.GetIndexArray()
            for p in range(poly_count):
                tmp = []
                polygon_size = mesh.GetPolygonSize(p)
                for v in range(polygon_size):
                    control_point_index = mesh.GetPolygonVertex(p, v)
                    id = uvs_index_array.GetAt(control_point_index)
                    tmp.append(id)
                    
                uv_index_layer.append(tmp)

        elif uvs.GetMappingMode() == KFbxLayerElement.eBY_POLYGON_VERTEX \
            and ( uvs.GetReferenceMode() == KFbxLayerElement.eDIRECT \
                  or uvs.GetReferenceMode() == KFbxLayerElement.eINDEX_TO_DIRECT ):
            
            for p in range(poly_count):
                tmp = []
                polygon_size = mesh.GetPolygonSize(p)
                for v in range(polygon_size):
                    id = mesh.GetTextureUVIndex(p, v)
                    tmp.append(id)
                    
                uv_index_layer.append(tmp)
    
    return uv_values, uv_index_layer
    
def extract_layer_colors(layer, poly_count, mesh):
    color_values = []
    color_index_layer = []
    
    colors = layer.GetVertexColors()
    if colors:
        colors_array = colors.GetDirectArray()
        colors_count = colors_array.GetCount()

        # values
        tmp = []
        for i in range(colors_count):
            color = extract_color(colors_array.GetAt(i))
            color_values.append(color)
        
        # indices
        if colors.GetMappingMode() == KFbxLayerElement.eBY_CONTROL_POINT \
           and colors.GetReferenceMode() == KFbxLayerElement.eDIRECT:
            
            for p in range(poly_count):
                tmp = []
                polygon_size = mesh.GetPolygonSize(p)
                for v in range(polygon_size):
                    id = mesh.GetPolygonVertex(p, v)
                    tmp.append(id)
                
                color_index_layer.append(tmp)
                
        elif colors.GetMappingMode() == KFbxLayerElement.eBY_CONTROL_POINT \
            and colors.GetReferenceMode() == KFbxLayerElement.eINDEX_TO_DIRECT:

            for p in range(poly_count):
                tmp = []
                polygon_size = mesh.GetPolygonSize(p)
                for v in range(polygon_size):
                    control_point_index = mesh.GetPolygonVertex(p, v)
                    id = colors.GetIndexArray().GetAt(control_point_index)
                    tmp.append(id)
                    
                color_index_layer.append(tmp)
        
        elif colors.GetMappingMode() == KFbxLayerElement.eBY_POLYGON_VERTEX \
            and colors.GetReferenceMode() == KFbxLayerElement.eDIRECT:
                    
            vertex_id = 0
            for p in range(poly_count):
                tmp = []
                polygon_size = mesh.GetPolygonSize(p)
                for v in range(polygon_size):
                    tmp.append(vertex_id)
                    vertex_id += 1
                    
                color_index_layer.append(tmp)

        elif colors.GetMappingMode() == KFbxLayerElement.eBY_POLYGON_VERTEX \
            and colors.GetReferenceMode() == KFbxLayerElement.eINDEX_TO_DIRECT:

            colors_index_array = colors.GetIndexArray()
            vertex_id = 0
            for p in range(poly_count):
                tmp = []
                polygon_size = mesh.GetPolygonSize(p)
                for v in range(polygon_size):
                    id = colors_index_array.GetAt(vertex_id)
                    tmp.append(id)
                    vertex_id += 1
                    
                color_index_layer.append(tmp)
        
    return color_values, color_index_layer
    
# #####################################################
# Parser - mesh - polygons
# #####################################################
def extract_polygons(mesh):
    poly_count = mesh.GetPolygonCount()
    layer_count = mesh.GetLayerCount()
    control_points = mesh.GetControlPoints() 
        
    layers_groups = []
    layers_uvs = []
    layers_colors = []
    
    indices_uv = []
    indices_color = []
    indices_vertex = []
    indices_material = []
    
    # per layer data
    for l in range(layer_count):
        
        layer = mesh.GetLayer(l)
        
        # groups
        group = extract_layer_groups(layer, poly_count)
        if group:
            groups_layers.append(group)
            
        # uvs
        uv_values, uv_index_layer = extract_layer_uvs(layer, poly_count, mesh)
        
        if uv_values:
            layers_uvs.append(uv_values)
        
        if uv_index_layer:
            indices_uv.append(uv_index_layer)
            
        # colors
        color_values, color_index_layer = extract_layer_colors(layer, poly_count, mesh)
        
        if color_values:
            layers_colors.append(color_values)
        
        if color_index_layer:
            indices_color.append(color_index_layer)
            
        # materials
        material_index_layer = extract_layer_materials(layer, poly_count, mesh)
        if material_index_layer:
            indices_material.append(material_index_layer)        
    
    # single layer data
    for p in range(poly_count):
        face_vertex_index = []
        polygon_size = mesh.GetPolygonSize(p)
        for v in range(polygon_size):
            control_point_index = mesh.GetPolygonVertex(p, v)
            face_vertex_index.append(control_point_index)
            
        indices_vertex.append(face_vertex_index)
        
    
    polygons = {}
    conditional_set(polygons, "indices_vertex", indices_vertex)
    conditional_set(polygons, "indices_uv", indices_uv)
    conditional_set(polygons, "indices_color", indices_color)
    conditional_set(polygons, "indices_material", indices_material)
    conditional_set(polygons, "layers_uvs", layers_uvs)
    conditional_set(polygons, "layers_colors", layers_colors)
    conditional_set(polygons, "layers_groups", layers_groups)
    
    return polygons
    
def extract_control_points(mesh):
    control_points_count = mesh.GetControlPointsCount()
    control_points = mesh.GetControlPoints()
    layer_count = mesh.GetLayerCount()
    
    coordinates = []
    layers_normals = []
    
    for i in range(control_points_count):
        coordinates.append( extract_vec3( control_points[i] ) )

    for layer in range(layer_count):
        normals = mesh.GetLayer(layer).GetNormals()
        if normals:
            znormals = []
            for i in range(control_points_count):
                if normals.GetMappingMode() == KFbxLayerElement.eBY_CONTROL_POINT:
                    if normals.GetReferenceMode() == KFbxLayerElement.eDIRECT:
                        znormals.append( extract_vec3( normals.GetDirectArray().GetAt(i) ) )
            
            if znormals:
                layers_normals.append( znormals )

    
    points = {}
    
    conditional_set(points, "coordinates", coordinates)
    conditional_set(points, "normals", layers_normals)    
    
    return points
    
# #####################################################
# Parser - mesh - materials
# #####################################################
def extract_materials(mesh):
    materials_layers = []
    
    material_count = 0
    layer_count = mesh.GetLayerCount()    
    
    node = None
    if mesh:
        node = mesh.GetNode()
        if node:
            material_count = node.GetMaterialCount()
    
    for layer in range(layer_count):
        layer_materials = []
        
        materials = mesh.GetLayer(layer).GetMaterials()
        if materials:
            if materials.GetReferenceMode() == KFbxLayerElement.eINDEX:
                #Materials are in an undefined external table
                continue

            if material_count > 0:

                for i in range(material_count):
                    material = node.GetMaterial(i)

                    zmaterial = {
                    "name" : material.GetName()
                    }

                    # Get the implementation to see if it's a hardware shader.
                    implementation = GetImplementation(material, "ImplementationHLSL")
                    implemenation_type = "HLSL"
                    if not implementation:
                        implementation = GetImplementation(material, "ImplementationCGFX")
                        implemenation_type = "CGFX"
                    if implementation:
                        # Now we have a hardware shader, let's read it
                        zmaterial["hardware_shader_type"] = implemenation_type.Buffer()
                        # Skipped parsing of shaders
                        
                    elif material.GetClassId().Is(KFbxSurfaceLambert.ClassId):
                        ambient = material.GetAmbientColor()
                        diffuse = material.GetDiffuseColor()
                        emissive = material.GetEmissiveColor()
                        
                        zmaterial["ambient"] = [ambient.Get()[0], ambient.Get()[1], ambient.Get()[2]]
                        zmaterial["diffuse"] = [diffuse.Get()[0], diffuse.Get()[1], diffuse.Get()[2]]
                        zmaterial["emissive"] = [emissive.Get()[0], emissive.Get()[1], emissive.Get()[2]]

                        zmaterial["opacity"] = material.GetTransparencyFactor().Get()

                    elif (material.GetClassId().Is(KFbxSurfacePhong.ClassId)):
                        ambient = material.GetAmbientColor()
                        diffuse = material.GetDiffuseColor()
                        emissive = material.GetEmissiveColor()
                        specular = material.GetSpecularColor()
                        
                        zmaterial["ambient"] = [ambient.Get()[0], ambient.Get()[1], ambient.Get()[2]]
                        zmaterial["diffuse"] = [diffuse.Get()[0], diffuse.Get()[1], diffuse.Get()[2]]
                        zmaterial["emissive"] = [emissive.Get()[0], emissive.Get()[1], emissive.Get()[2]]
                        zmaterial["specular"] = [specular.Get()[0], specular.Get()[1], specular.Get()[2]]
                        
                        zmaterial["opacity"] = material.GetTransparencyFactor().Get()
                        
                        zmaterial["shininess"] = material.GetShininess().Get()
                        zmaterial["reflectivity"] = material.GetReflectionFactor().Get()
                        
                    zmaterial["shading_model"] = material.GetShadingModel().Get().Buffer()
                    layer_materials.append(zmaterial)
                    
        if layer_materials:
            materials_layers.append(layer_materials)
    
    return materials_layers
    
# #####################################################
# Parser - mesh - textures
# #####################################################
def extract_texture_info(texture, blend_mode):
    mapping_types = [ "Null", "Planar", "Spherical", "Cylindrical", "Box", "Face", "UV", "Environment" ]
    alpha_sources = [ "None", "RGB Intensity", "Black" ]
    blend_modes   = [ "Translucent", "Add", "Modulate", "Modulate2" ]   
    material_uses = [ "Model Material", "Default Material" ]
    texture_uses  = [ "Standard", "Shadow Map", "Light Map", "Spherical Reflection Map", "Sphere Reflection Map" ]
    planar_mapping_normals = [ "X", "Y", "Z" ]
    
    info = {
    "name"      : texture.GetName(),
    "filename"  : texture.GetFileName(),
    "scale_u"   : texture.GetScaleU(),
    "scale_v"   : texture.GetScaleV(),
    "swap_uv"   : texture.GetSwapUV(),
    "translation_u" : texture.GetTranslationU(),
    "translation_v" : texture.GetTranslationV(),
    "rotation_u"    : texture.GetRotationU(),
    "rotation_v"    : texture.GetRotationV(),
    "rotation_w"    : texture.GetRotationW(),
    "mapping"       : mapping_types[texture.GetMappingType()],
    "alpha_source"  : alpha_sources[texture.GetAlphaSource()],
    "cropping_left"   : texture.GetCroppingLeft(),
    "cropping_top"    : texture.GetCroppingTop(),
    "cropping_right"  : texture.GetCroppingRight(),
    "cropping_bottom" : texture.GetCroppingBottom(),
    "alpha"         : texture.GetDefaultAlpha(),
    "material_use" : material_uses[texture.GetMaterialUse()],
    "texture_use"  : texture_uses[texture.GetTextureUse()]
    }


    if texture.GetMappingType() == KFbxTexture.ePLANAR:
        info["planar_mapping_normal"] = planar_mapping_normals[texture.GetPlanarMappingNormal()]

    if blend_mode >= 0:
        info["blend_mode"] = blend_modes[blend_mode]
        
    return info
    
def extract_texture_info_by_property(property, material_index):
    textures = []
    
    if property.IsValid():
        
        #Here we have to check if it's layeredtextures, or just textures:
        layered_texture_count = property.GetSrcObjectCount(KFbxLayeredTexture.ClassId)
        if layered_texture_count > 0:
            for j in range(layered_texture_count):
                ltexture = {
                "layered_texture" : j,
                "layers" : []
                }
                
                layered_texture = property.GetSrcObject(KFbxLayeredTexture.ClassId, j)
                texture_count = layered_texture.GetSrcObjectCount(KFbxTexture.ClassId)
                for k in range(texture_count):
                    ztexture = lLayeredTexture.GetSrcObject(KFbxTexture.ClassId, k)
                    if ztexture:
                        # NOTE the blend mode is ALWAYS on the LayeredTexture and NOT the one on the texture.
                        # Why is that?  because one texture can be shared on different layered textures and might
                        # have different blend modes.

                        blend_mode = layered_texture.GetTextureBlendMode(k)
                        texture = {
                        "material_index" : material_index,
                        "texture_index"  : k,
                        "property_name"  : property.GetName().Buffer(),
                        "blend_mode"     : blend_mode,
                        "info"           : extract_texture_info(ztexture, blend_mode)
                        }
                        
                        ltexture["layers"].append(texture)
                
                textures.append(ltexture)
        
        # no layered texture simply get on the property
        else:
            
            texture_count = property.GetSrcObjectCount(KFbxTexture.ClassId)
            for j in range(texture_count):
                ztexture = property.GetSrcObject(KFbxTexture.ClassId, j)
                if ztexture:
                    texture = {
                    "material_index" : material_index,
                    "info"           : extract_texture_info(ztexture, -1)
                    }
                    textures.append(texture)

    return textures
            
def extract_textures(mesh):
    textures = {}
    node = mesh.GetNode()
    materials_count = node.GetSrcObjectCount(KFbxSurfaceMaterial.ClassId)
    for material_index in range(materials_count):
        material = node.GetSrcObject(KFbxSurfaceMaterial.ClassId, material_index)
        
        #go through all the possible textures
        if material:
            for texture_index in range(KFbxLayerElement.LAYERELEMENT_TYPE_TEXTURE_COUNT):
                property = material.FindProperty(KFbxLayerElement.TEXTURE_CHANNEL_NAMES[texture_index])
                texture = extract_texture_info_by_property(property, material_index) 
                if texture:
                    textures[property.GetName().Buffer()] = texture
    
    return textures

def extract_material_mapping(mesh):
    return {}
    
def extract_material_connections(mesh):
    return {}
        
def extract_link(mesh):
    return {}
    
def extract_shape(mesh):
    return {}    
    
def extract_mesh(node):
    mesh = node.GetNodeAttribute()

    zmesh = {}
    
    conditional_set(zmesh, "name", node.GetName())
    conditional_set(zmesh, "shape", extract_shape(mesh))
    conditional_set(zmesh, "link", extract_link(mesh))
    conditional_set(zmesh, "control_points", extract_control_points(mesh))
    conditional_set(zmesh, "faces", extract_polygons(mesh))
    conditional_set(zmesh, "textures", extract_textures(mesh))
    conditional_set(zmesh, "materials", extract_materials(mesh))
    conditional_set(zmesh, "material_mapping", extract_material_mapping(mesh))
    conditional_set(zmesh, "material_connections", extract_material_connections(mesh))
    
    return zmesh

# #####################################################
# Parser - nodes (todo)
# #####################################################
def extract_marker(node):
    return {}
    
def extract_nurb(node):
    return {}
    
def extract_patch(node):
    return {}
    
def extract_skeleton(node):
    return {}
    
def extract_camera(node):
    return {}
    
def extract_light(node):
    return {}

# #####################################################
# Parser - nodes (generic)
# #####################################################
def extract_target(node):
    if node.GetTarget():
        return node.GetTarget().GetName()
    return ""

def extract_transform(node):
    translation = node.GetGeometricTranslation(KFbxNode.eSOURCE_SET)
    rotation = node.GetGeometricRotation(KFbxNode.eSOURCE_SET)
    scale = node.GetGeometricScaling(KFbxNode.eSOURCE_SET)
    
    transform = {
    "translation": [ translation[0], translation[1], translation[2] ],
    "rotation"   : [ rotation[0], rotation[1], rotation[2] ],
    "scale"      : [ scale[0], scale[1], scale[2] ],
    }
    
    return transform
    
def extract_transform_propagation(node):
    rotation_order = node.GetRotationOrder(KFbxNode.eSOURCE_SET)
        
    order_map = {
    eEULER_XYZ  : "Euler XYZ",
    eEULER_XZY  : "Euler XZY",
    eEULER_YZX  : "Euler YZX",
    eEULER_YXZ  : "Euler YXZ",
    eEULER_ZXY  : "Euler ZXY",
    eEULER_ZYX  : "Euler ZYX",
    eSPHERIC_XYZ:"Spheric XYZ"
    }
    
    order = "Euler XYZ"
    
    if rotation_order in order_map:
        order = order_map[rotation_order]
        
    # Use the Rotation space only for the limits
    # (keep using eEULER_XYZ for the rest)
    
    if node.GetUseRotationSpaceForLimitOnly(KFbxNode.eSOURCE_SET):
        only_limits = 1
    else:
        only_limits = 0

    inherit_type = node.GetTransformationInheritType()

    inherit_map = {
    eINHERIT_RrSs   : "RrSs",
    eINHERIT_RSrs   : "RSrs",
    eINHERIT_Rrs    : "Rrs"
    }
    
    if inherit_type in inherit_map:
        inheritance = inherit_map[inherit_type]

    transform_propagation = {
    "rotation_order" : order,
    "only_limits"    : only_limits,
    "inheritance"    : inheritance
    }

    return transform_propagation
    
def extract_pivots(node):
    return {}
    
def extract_user_properties(node):
    return {}
    
def extract_node_content(node):
    nodes = []
    
    if node.GetNodeAttribute() == None:
        return "NULL"
        
    else:
        attribute_type = node.GetNodeAttribute().GetAttributeType()        

        ztype = "undefined"
        data = {}
        
        type_map = {
        KFbxNodeAttribute.eMARKER   : ["marker",    extract_marker],
        KFbxNodeAttribute.eSKELETON : ["skeleton",  extract_skeleton],
        KFbxNodeAttribute.eMESH     : ["mesh",      extract_mesh],
        KFbxNodeAttribute.eNURB     : ["nurb",      extract_nurb],
        KFbxNodeAttribute.ePATCH    : ["patch",     extract_patch],
        KFbxNodeAttribute.eCAMERA   : ["camera",    extract_camera],
        KFbxNodeAttribute.eLIGHT    : ["light",     extract_light]
        }
        
        if attribute_type in type_map:
            ztype = type_map[attribute_type][0]
            data = type_map[attribute_type][1](node)
            
        content = { }
        
        conditional_set(content, "type", ztype)
        conditional_set(content, "data", data)
        conditional_set(content, "target", extract_target(node))
        conditional_set(content, "pivots", extract_pivots(node))
        conditional_set(content, "transform", extract_transform(node))
        conditional_set(content, "transform_propagation", extract_transform_propagation(node))
        conditional_set(content, "user_properties", extract_user_properties(node))

    nodes.append(content)
    
    for i in range(node.GetChildCount()):
        nodes += extract_node_content(node.GetChild(i))
        
    return nodes
        
def extract_nodes(scene):    
    nodes = []
    
    root = scene.GetRootNode()
    if root:
        for i in range(root.GetChildCount()):
            nodes += extract_node_content(root.GetChild(i))
    
    return nodes
    
def filter_mesh(item):
    return item["type"] == "mesh"
    
def extract_meshes(scene):
    nodes = extract_nodes(scene)
    meshes = filter(filter_mesh, nodes)
    return meshes
    
# #####################################################
# JSON extractors
# #####################################################
def get_material_texture(material_index, textures, property):
    result = [t for t in textures.get(property, []) if t["material_index"] == material_index]
    return result
    
def collect_textures(data):
    texture_set = set()
    
    for mesh in data["meshes"]:
        for texture_type in mesh["data"]["textures"]:
            for texture in mesh["data"]["textures"][texture_type]:
                texture_file = base_filename(texture["info"]["filename"])
                texture_set.add(texture_file)

    return list(texture_set)
    
# #####################################################
# Generator - model
# #####################################################
def generate_vertex(v):
    return TEMPLATE_VERTEX % (v[0], v[1], v[2])
    
def generate_triangle(f):
    v = f['vertex']
    return TEMPLATE_TRI % (v[0], v[1], v[2], 
                           f['material'])

def generate_triangle_uv(f):
    v = f['vertex']
    uv = f['uv']
    return TEMPLATE_TRI_UV % (v[0], v[1], v[2], 
                              f['material'], 
                              uv[0], uv[1], uv[2])

def generate_triangle_n(f):
    v = f['vertex']
    n = f['normal']
    return TEMPLATE_TRI_N % (v[0], v[1], v[2], 
                             f['material'], 
                             n[0], n[1], n[2])

def generate_triangle_n_uv(f):
    v = f['vertex']
    n = f['normal']
    uv = f['uv']
    return TEMPLATE_TRI_N_UV % (v[0], v[1], v[2], 
                                f['material'], 
                                n[0], n[1], n[2], 
                                uv[0], uv[1], uv[2])

def generate_quad(f):
    vi = f['vertex']
    return TEMPLATE_QUAD % (vi[0], vi[1], vi[2], vi[3], 
                            f['material'])

def generate_quad_uv(f):
    v = f['vertex']
    uv = f['uv']
    return TEMPLATE_QUAD_UV % (v[0], v[1], v[2], v[3], 
                               f['material'], 
                               uv[0], uv[1], uv[2], uv[3])

def generate_quad_n(f):
    v = f['vertex']
    n = f['normal']
    return TEMPLATE_QUAD_N % (v[0], v[1], v[2], v[3], 
                              f['material'],
                              n[0], n[1], n[2], n[3])

def generate_quad_n_uv(f):
    v = f['vertex']
    n = f['normal']
    uv = f['uv']
    return TEMPLATE_QUAD_N_UV % (v[0], v[1], v[2], v[3], 
                                 f['material'],
                                 n[0], n[1], n[2], n[3],
                                 uv[0], uv[1], uv[2], uv[3])

def generate_normal(n):
    return TEMPLATE_N % (n[0], n[1], n[2])

def generate_uv(uv):
    return TEMPLATE_UV % (uv[0], uv[1])
    
# #####################################################
# Generator - scene
# #####################################################
def generate_vec3(vec):
    return TEMPLATE_VEC3 % (vec[0], vec[1], vec[2])

def generate_vec2(vec):
    return TEMPLATE_VEC2 % (vec[0], vec[1])

def generate_hex(number):
    return TEMPLATE_HEX % number
    
def generate_string(s):
    return TEMPLATE_STRING % s
    
def generate_section(label, content):
    return TEMPLATE_SECTION % (label, content)
    
def get_mesh_filename(mesh):
    object_id = mesh["data"]["name"]
    filename = "%s.js" % sanitize(object_id)
    return filename
    
def generate_material_id_list(materials):
    chunks = []
    for layer in materials:
        for material in layer:
            chunks.append(material["name"])
    
    return ",".join(chunks)
    
def generate_objects(data):
    chunks = []
    
    for mesh in data["meshes"]:
        object_id = mesh["data"]["name"]
        geometry_id = "geo_%s" % object_id
        
        material_id = generate_material_id_list(mesh["data"]["materials"])
        
        position = mesh["transform"]["translation"]
        rotation = mesh["transform"]["rotation"]
        scale = mesh["transform"]["scale"]

        # hunt for local transform
        if object_id in data["generic_info"]:
            gi = data["generic_info"][object_id]
            lt = gi.get("Lcl Translation", {})
            local_translation = lt.get("value", [0,0,0])
            position[0] += local_translation[0]
            position[1] += local_translation[1]
            position[2] += local_translation[2]

        object_string = TEMPLATE_OBJECT % {
        "object_id"   : generate_string(object_id),
        "geometry_id" : generate_string(geometry_id),
        "material_id" : generate_string(material_id),
        "position"    : generate_vec3(position),
        "rotation"    : generate_vec3(rotation),
        "scale"       : generate_vec3(scale)
        }
        chunks.append(object_string)
        
    return ",\n\n".join(chunks)
    
def generate_geometries(data):
    chunks = []
    
    for mesh in data["meshes"]:
        geometry_id = "geo_%s" % mesh["data"]["name"]
        model_filename = get_mesh_filename(mesh)
        
        geometry_string = TEMPLATE_GEOMETRY % {
        "geometry_id" : generate_string(geometry_id),
        "model_file"  : generate_string(model_filename)
        }
        chunks.append(geometry_string)
        
    return ",\n\n".join(chunks)
    
def generate_textures_scene(data):
    chunks = []
    texture_set = set()
    
    for mesh in data["meshes"]:
        for texture_type in mesh["data"]["textures"]:
            for texture in mesh["data"]["textures"][texture_type]:
                texture_id = texture["info"]["name"]
                
                if texture_id not in texture_set:
                    texture_set.add(texture_id)
                    texture_file = base_filename(texture["info"]["filename"])
                
                    texture_string = TEMPLATE_TEXTURE % {
                    "texture_id"   : generate_string(texture_id),
                    "texture_file" : generate_string(texture_file)
                    }
                    chunks.append(texture_string)

    return ",\n\n".join(chunks)

def generate_materials_scene(data):
    chunks = []
    
    type_map = {
    "Lambert"   : "MeshLambertMaterial",
    "Phong"     : "MeshPhongMaterial"
    }
    
    for mesh in data["meshes"]:
        for layer in mesh["data"]["materials"]:
            for material_index in range(len(layer)):
                material = layer[material_index]
                material_id = material["name"]
                shading = material["shading_model"]
                material_type = type_map.get(shading, "MeshBasicMaterial")
                
                parameters = "color: %s" % generate_hex(rgb2int(material["diffuse"]))
                
                if shading == "Phong":
                    parameters += ", ambient: %s" % generate_hex(rgb2int(material["ambient"]))
                    parameters += ", specular: %s" % generate_hex(rgb2int(material["specular"]))
                    parameters += ", shininess: %f" % material["shininess"]

                # TODO: proper handling of textures
                colorMap = get_material_texture(material_index, mesh["data"]["textures"], "DiffuseColor")
                lightMap = get_material_texture(material_index, mesh["data"]["textures"], "AmbientColor")
                bumpMap  = get_material_texture(material_index, mesh["data"]["textures"], "Bump")
                
                if map:
                    parameters += ", map: %s" % generate_string(colorMap[0]["info"]["name"])
                if lightMap:
                    parameters += ", lightMap: %s" % generate_string(lightMap[0]["info"]["name"])
                if bumpMap:
                    parameters += ", bumpMap: %s" % generate_string(bumpMap[0]["info"]["name"])
                
                material_string = TEMPLATE_MATERIAL_SCENE % {
                "material_id" : generate_string(material_id),
                "type"        : generate_string(material_type),
                "parameters"  : parameters
                }
                chunks.append(material_string)
        
    return ",\n\n".join(chunks)

# TODO

def generate_cameras(data):
    cameras = data.get("cameras", [])
    if not cameras:
        cameras.append(DEFAULTS["camera"])
        
    chunks = []
    for camera in cameras:
        if camera["type"] == "perspective":
            camera_string = TEMPLATE_CAMERA_PERSPECTIVE % {
            "camera_id" : generate_string(camera["name"]),
            "fov"       : camera["fov"],
            "aspect"    : camera["aspect"],
            "near"      : camera["near"],
            "far"       : camera["far"],
            "position"  : generate_vec3(camera["position"]),
            "target"    : generate_vec3(camera["target"])
            }
        elif camera["type"] == "ortho":
            camera_string = TEMPLATE_CAMERA_ORTHO % {
            "camera_id" : generate_string(camera["name"]),
            "left"      : camera["left"],
            "right"     : camera["right"],
            "top"       : camera["top"],
            "bottom"    : camera["bottom"],
            "near"      : camera["near"],
            "far"       : camera["far"],
            "position"  : generate_vec3(camera["position"]),
            "target"    : generate_vec3(camera["target"])
            }
            
        chunks.append(camera_string)
        
    return ",\n\n".join(chunks)

def generate_lights(data):
    return ""
    
def generate_ascii_scene(data):
    objects = generate_objects(data)
    geometries = generate_geometries(data)
    textures = generate_textures_scene(data)
    materials = generate_materials_scene(data)    
    cameras = generate_cameras(data)
    lights = generate_lights(data)
    
    sections = [
    ["objects",    objects],
    ["geometries", geometries],
    ["textures",   textures],
    ["materials",  materials],
    ["cameras",    cameras],
    ["lights",     lights]
    ]
    
    chunks = []
    for label, content in sections:
        if content:
            chunks.append(generate_section(label, content))

    sections_string = "\n".join(chunks)
    
    default_camera = "default_camera"
    
    parameters = {
    "fname"     : data["source_file"],   
    "url_base"  : generate_string(data["base_folder"]),
    "sections"  : sections_string,
    "bgcolor"   : generate_vec3(DEFAULTS["bgcolor"]),
    "bgalpha"   : DEFAULTS["bgalpha"],
    "defcamera" : generate_string(default_camera)
    }

    text = TEMPLATE_SCENE_ASCII % parameters
    
    return text    

# #####################################################
# Generator - materials 
# #####################################################    
def generate_color(i):
    """Generate hex color corresponding to integer.
    
    Colors should have well defined ordering.
    First N colors are hardcoded, then colors are random 
    (must seed random number  generator with deterministic value 
    before getting colors).
    """
    
    if i < len(COLORS):
        return "0x%06x" % COLORS[i]
    else:
        return "0x%06x" % int(0xffffff * random.random())
        
def value2string(v):
    if type(v)==str and v[0:2] != "0x":
        return '"%s"' % v
    return str(v)

def generate_material_model(material, index):
    m = {
    'DbgName'    :generate_string(material["name"]),
    'DbgIndex'   :index,
    'DbgColor'   :generate_color(index),
    "shading"       :generate_string(material["shading_model"]),
    "opacity"       : material["opacity"]
    }
    
    if material["shading_model"] in ["Lambert", "Phong"]:
        m["colorAmbient"] = material["ambient"]
        m["colorDiffuse"] = material["diffuse"]
        m["col_emissive"] = material["emissive"]
        
    if material["shading_model"] in ["Phong"]:
        m["colorSpecular"] = material["specular"]
        m["shininess"] = material["shininess"]
        
    if not MATERIALS_IN_SCENE:
        conditional_set(m, "mapDiffuse", material.get("mapDiffuse", 0))
        conditional_set(m, "mapLightmap", material.get("mapLightmap", 0))    
        
    mtl_raw = ",\n".join(['\t"%s" : %s' % (n, value2string(v)) for n,v in sorted(m.items())])
    mtl_string = "\t{\n%s\n\t}" % mtl_raw
    
    return mtl_string
    
# #####################################################
# Generator - models 
# #####################################################    
def generate_ascii_model(data):
    materials = data["materials"]

    vertices = data["control_points"]["coordinates"]
    
    normals = []
    if "normals" in data["control_points"]:
        normals = data["control_points"]["normals"][0]
    
    uvs = []
    uvs2 = []
    if "layers_uvs" in data["faces"]:
        n_uvs = len(data["faces"]["layers_uvs"])
        if n_uvs > 0:
            uvs = data["faces"]["layers_uvs"][0]
        if n_uvs > 1:
            uvs2 = data["faces"]["layers_uvs"][1]
        

    triangles = []
    trianglesUvs = []
    trianglesNormals = []
    trianglesNormalsUvs = []

    quads = []
    quadsUvs = []
    quadsNormals = []
    quadsNormalsUvs = []

    indices_vertex = data["faces"]["indices_vertex"]
    for vi in range(len(indices_vertex)):
        
        vertex_index = indices_vertex[vi]
        
        material_index = 0
        if vi < len(data["faces"]["indices_material"][0]):
            material_index = data["faces"]["indices_material"][0][vi]
            
        face = {
        'vertex'   : vertex_index,
        'material' : material_index
        }
    
        if normals:
            face["normal"] = vertex_index
            
        if uvs and "indices_uv" in data["faces"]:
            indices_uv = data["faces"]["indices_uv"]
            face["uv"] = indices_uv[0][vi]
            if len(indices_uv) > 1:
                face["uv2"] = indices_uv[1][vi]
            
        
        if len(vertex_index) == 3:
            if normals:
                if uvs:
                    where = trianglesNormalsUvs
                else:
                    where = trianglesNormals
            else:
                if uvs:
                    where = trianglesUvs
                else:
                    where = triangles
                    
        elif len(vertex_index) == 4:
            if normals:
                if uvs:
                    where = quadsNormalsUvs
                else:
                    where = quadsNormals
            else:
                if uvs:
                    where = quadsUvs
                else:
                    where = quads
            
        where.append(face)
    
    nvertex = len(vertices)
    nface = len(indices_vertex)
    nmaterial = 0
    
    text = TEMPLATE_MODEL_ASCII % {
    "fname"     : source_file,
    "nvertex"   : nvertex,
    "nface"     : nface,
    "nmaterial" : nmaterial,
    
    "materials" : "".join(generate_material_model(m, i) for i, m in enumerate(materials[0])),
    
    "normals"   : ",".join(generate_normal(n) for n in normals),
    "vertices"  : ",".join(generate_vertex(v) for v in vertices),

    "uvs"       : ",".join(generate_uv(u) for u in uvs),
    "uvs2"      : ",".join(generate_uv(u) for u in uvs2),

    "triangles"     : ",".join(generate_triangle(f) for f in triangles),
    "trianglesUvs"  : ",".join(generate_triangle_uv(f) for f in trianglesUvs),
    "trianglesNormals"   : ",".join(generate_triangle_n(f) for f in trianglesNormals),
    "trianglesNormalsUvs": ",".join(generate_triangle_n_uv(f) for f in trianglesNormalsUvs),
        
    "quads"         : ",".join(generate_quad(f) for f in quads),
    "quadsUvs"      : ",".join(generate_quad_uv(f) for f in quadsUvs),
    "quadsNormals"       : ",".join(generate_quad_n(f) for f in quadsNormals),
    "quadsNormalsUvs"    : ",".join(generate_quad_n_uv(f) for f in quadsNormalsUvs)
    }
    
    return text
    
# #####################################################
# Helpers
# #####################################################
def sanitize(text):
    chunks = []
    for ch in text:
        if ch in (string.ascii_letters + string.digits + "_."):
            chunks.append(ch)
        else:
            chunks.append("_")
            
    return "".join(chunks)
    
def base_filename(path):
    return os.path.basename(path)
    
def rgb2int(rgb):
    color = (int(rgb[0]*255) << 16) + (int(rgb[1]*255) << 8) + int(rgb[2]*255);
    return color
    
def conditional_set(where, label, what):
    """Set dictionary property only if it exists"""
    
    if what:
        where[label] = what

def dump_data(data):
    """Generate pretty printed view of data."""
    
    chunks = []
    
    pp = pprint.PrettyPrinter(indent=2, width=160)
    if type(data) == list:
        for d in data:
            chunks.append(pp.pformat(d))
    elif type(data) == dict:
        chunks.append(pp.pformat(data))
        
    return "\n\n".join(chunks)
    
def ensure_folder_exist(foldername):
    """Create folder (with whole path) if it doesn't exist yet."""
    
    if not os.access(foldername, os.R_OK|os.W_OK|os.X_OK):
        os.makedirs(foldername)

def abort(message):
    print message
    sys.exit(1)
    
def write_file(fname, content):
    out = open(fname, "w")
    out.write(content)
    out.close()
    
def copy_files(textures, src_folder, dst_folder):
    for texture in textures:
        src_file = os.path.join(src_folder, texture)
        if os.path.isfile(src_file):
            shutil.copy(src_file, dst_folder)
        else:
            print "WARNING: couldn't find [%s]" % src_file
    
# #####################################################
# Main
# #####################################################
if __name__ == "__main__":
    
    try:
        from FbxCommon import *
        
    except ImportError:
        
        import platform
        
        msg = ""
        
        if platform.system() == 'Windows' or platform.system() == 'Microsoft':
            msg = '"Python26/Lib/site-packages"'
            
        elif platform.system() == 'Linux':
            msg = '"/usr/local/lib/python2.6/site-packages"'
            
        elif platform.system() == 'Darwin':
            msg = '"/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/site-packages"'        
        
        abort('You need to copy the content in compatible subfolder under /lib/python<version> into your python install folder such as %s folder.' % msg)
        
    sdkManager, scene = InitializeSdkObjects()
    
    if len(sys.argv) < 3:
        abort("Usage: convert_fbx_three.py [scene.fbx] [scene_folder]")
    
    source_file = sys.argv[1]
    output_folder = sys.argv[2]
    
    junk, base_folder = os.path.split(os.path.normpath(output_folder))
    
    result = LoadScene(sdkManager, scene, source_file)
        
    if not result:
        abort("An error occurred while loading the scene ...")
    
    random.seed(42) # to get well defined debug color order for materials
    
    ensure_folder_exist(output_folder)
    ensure_folder_exist(output_folder+"/models")
    ensure_folder_exist(output_folder+"/textures")
    
    meshes = extract_meshes(scene)
    generic_info = extract_generic_info(scene)
    
    scene_text = ""
    data = {
    "meshes"      : meshes,
    "generic_info": generic_info,
    "source_file" : source_file,
    "base_folder" : base_folder+"/"
    }
    scene_text += generate_ascii_scene(data)
    
    if DEBUG_FBX_JSON:
        scene_text += "/*" + dump_data(meshes) + "\n\n\n" + dump_data(generic_info) + "*/"
    
    scene_file = os.path.join(output_folder, "scene.js")
    htaccess_file = os.path.join(output_folder, ".htaccess") 
    
    write_file(scene_file, scene_text)
    write_file(htaccess_file, TEMPLATE_HTACCESS)
    
    for mesh in meshes:
        model_text = generate_ascii_model(mesh["data"])
        model_file = os.path.join(output_folder, "models", get_mesh_filename(mesh))
        write_file(model_file, model_text)

    textures_src_folder = os.path.dirname(source_file)
    textures_dst_folder = os.path.join(output_folder, "textures")
    copy_files(collect_textures(data), textures_src_folder, textures_dst_folder)

    # Destroy all objects created by the FBX SDK
    
    sdkManager.Destroy()
