Browse Source

Fixed support for Blende 2.58
new file: 2.58/scripts/addons/io_mesh_threejs/__init__.py
new file: 2.58/scripts/addons/io_mesh_threejs/export_threejs.py
new file: 2.58/scripts/addons/io_mesh_threejs/import_threejs.py

Juraj Michalek 14 years ago
parent
commit
237800b9c4

+ 374 - 0
utils/exporters/blender/2.58/scripts/addons/io_mesh_threejs/__init__.py

@@ -0,0 +1,374 @@
+# ##### 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 #####
+
+# ################################################################
+# Init
+# ################################################################
+
+# To support reload properly, try to access a package var,
+# if it's there, reload everything
+
+bl_info = {
+    "name": "three.js format",
+    "author": "mrdoob, kikko, alteredq, remoe",
+    "version": (1, 0),
+    "blender": (2, 5, 7),
+    "api": 35622,
+    "location": "File > Import-Export",
+    "description": "Import-Export three.js meshes",
+    "warning": "",
+    "wiki_url": "https://github.com/mrdoob/three.js/tree/master/utils/exporters/blender",
+    "tracker_url": "https://github.com/mrdoob/three.js/issues",
+    "category": "Import-Export"}
+
+if "bpy" in locals():
+    import imp
+    if "export_threejs" in locals():
+        imp.reload(export_threejs)
+    if "import_threejs" in locals():
+        imp.reload(import_threejs)
+
+import bpy
+from bpy.props import *
+from io_utils import ExportHelper, ImportHelper
+
+# ################################################################
+# Custom properties
+# ################################################################
+
+bpy.types.Object.THREE_castsShadow = bpy.props.BoolProperty()
+bpy.types.Object.THREE_meshCollider = bpy.props.BoolProperty()
+bpy.types.Object.THREE_exportGeometry = bpy.props.BoolProperty(default = True)
+
+THREE_trigger_types = [("None", "None", "None"), ("Small", "Small", "Small"), ("Large", "Large", "Large")]
+bpy.types.Object.THREE_triggerType = EnumProperty(name = "Trigger type", description = "Trigger type", items = THREE_trigger_types, default = "None")
+
+bpy.types.Material.THREE_useVertexColors = bpy.props.BoolProperty()
+
+THREE_material_types = [("Basic", "Basic", "Basic"), ("Phong", "Phong", "Phong"), ("Lambert", "Lambert", "Lambert")]
+bpy.types.Material.THREE_materialType = EnumProperty(name = "Material type", description = "Material type", items = THREE_material_types, default = "Lambert")
+
+class OBJECT_PT_hello( bpy.types.Panel ):
+
+    bl_label = "THREE"
+    bl_space_type = "PROPERTIES"
+    bl_region_type = "WINDOW"
+    bl_context = "object"
+
+    def draw(self, context):
+        layout = self.layout
+        obj = context.object
+
+        row = layout.row()
+        row.label(text="Selected object: " + obj.name )
+
+        row = layout.row()
+        row.prop( obj, "THREE_exportGeometry", text="Export geometry" )
+
+        row = layout.row()
+        row.prop( obj, "THREE_castsShadow", text="Casts shadow" )
+
+        row = layout.row()
+        row.prop( obj, "THREE_meshCollider", text="Mesh collider" )
+
+        row = layout.row()
+        row.prop( obj, "THREE_triggerType", text="Trigger type" )
+
+
+class MATERIAL_PT_hello( bpy.types.Panel ):
+
+    bl_label = "THREE"
+    bl_space_type = "PROPERTIES"
+    bl_region_type = "WINDOW"
+    bl_context = "material"
+
+    def draw(self, context):
+        layout = self.layout
+        mat = context.material
+
+        row = layout.row()
+        row.label(text="Selected material: " + mat.name )
+
+        row = layout.row()
+        row.prop( mat, "THREE_materialType", text="Material type" )
+
+        row = layout.row()
+        row.prop( mat, "THREE_useVertexColors", text="Use vertex colors" )
+
+
+# ################################################################
+# Importer
+# ################################################################
+
+class ImportTHREEJS(bpy.types.Operator, ImportHelper):
+    '''Load a Three.js ASCII JSON model'''
+
+    bl_idname = "import.threejs"
+    bl_label = "Import Three.js"
+
+    filename_ext = ".js"
+    filter_glob = StringProperty(default="*.js", options={'HIDDEN'})
+
+    option_flip_yz = BoolProperty(name="Flip YZ", description="Flip YZ", default=True)
+    recalculate_normals = BoolProperty(name="Recalculate normals", description="Recalculate vertex normals", default=True)
+
+    def execute(self, context):
+        import io_mesh_threejs.import_threejs
+        return io_mesh_threejs.import_threejs.load(self, context, **self.properties)
+
+
+    def draw(self, context):
+        layout = self.layout
+
+        row = layout.row()
+        row.prop(self.properties, "option_flip_yz")
+
+        row = layout.row()
+        row.prop(self.properties, "recalculate_normals")
+
+
+# ################################################################
+# Exporter - settings
+# ################################################################
+
+SETTINGS_FILE_EXPORT = "threejs_settings_export.js"
+
+import os
+import json
+
+def file_exists(filename):
+    """Return true if file exists and accessible for reading.
+
+    Should be safer than just testing for existence due to links and
+    permissions magic on Unix filesystems.
+
+    @rtype: boolean
+    """
+
+    try:
+        f = open(filename, 'r')
+        f.close()
+        return True
+    except IOError:
+        return False
+
+def get_settings_fullpath():
+    return os.path.join(bpy.app.tempdir, SETTINGS_FILE_EXPORT)
+
+def save_settings_export(properties):
+
+    settings = {
+    "option_export_scene" : properties.option_export_scene,
+    "option_embed_meshes" : properties.option_embed_meshes,
+    
+    "option_lights" : properties.option_lights,
+    "option_cameras" : properties.option_cameras,
+
+    "option_flip_yz"      : properties.option_flip_yz,
+
+    "option_materials"       : properties.option_materials,
+    "option_normals"         : properties.option_normals,
+    "option_colors"          : properties.option_colors,
+    "option_uv_coords"       : properties.option_uv_coords,
+    "option_edges"           : properties.option_edges,
+    "option_faces"           : properties.option_faces,
+    "option_vertices"        : properties.option_vertices,
+
+    "option_vertices_truncate" : properties.option_vertices_truncate,
+    "option_scale"        : properties.option_scale,
+
+    "align_model"         : properties.align_model
+    }
+
+    fname = get_settings_fullpath()
+    f = open(fname, "w")
+    json.dump(settings, f)
+
+def restore_settings_export(properties):
+
+    settings = {}
+
+    fname = get_settings_fullpath()
+    if file_exists(fname):
+        f = open(fname, "r")
+        settings = json.load(f)
+
+    properties.option_vertices = settings.get("option_vertices", True)
+    properties.option_vertices_truncate = settings.get("option_vertices_truncate", False)
+    properties.option_faces = settings.get("option_faces", True)
+    properties.option_normals = settings.get("option_normals", True)
+    properties.option_edges = settings.get("option_edges", False)
+
+    properties.option_colors = settings.get("option_colors", True)
+    properties.option_uv_coords = settings.get("option_uv_coords", True)
+    properties.option_materials = settings.get("option_materials", True)
+
+    properties.align_model = settings.get("align_model", "None")
+
+    properties.option_scale = settings.get("option_scale", 1.0)
+    properties.option_flip_yz = settings.get("option_flip_yz", True)
+
+    properties.option_export_scene = settings.get("option_export_scene", False)
+    properties.option_embed_meshes = settings.get("option_embed_meshes", True)
+
+    properties.option_lights = settings.get("option_lights", False)
+    properties.option_cameras = settings.get("option_cameras", False)
+
+# ################################################################
+# Exporter
+# ################################################################
+
+class ExportTHREEJS(bpy.types.Operator, ExportHelper):
+    '''Export selected object / scene for Three.js (ASCII JSON format).'''
+
+    bl_idname = "export.threejs"
+    bl_label = "Export Three.js"
+
+    filename_ext = ".js"
+
+    option_vertices = BoolProperty(name = "Vertices", description = "Export vertices", default = True)
+    option_vertices_deltas = BoolProperty(name = "Deltas", description = "Delta vertices", default = False)
+    option_vertices_truncate = BoolProperty(name = "Truncate", description = "Truncate vertices", default = False)
+
+    option_faces = BoolProperty(name = "Faces", description = "Export faces", default = True)
+    option_faces_deltas = BoolProperty(name = "Deltas", description = "Delta faces", default = False)
+
+    option_normals = BoolProperty(name = "Normals", description = "Export normals", default = True)
+    option_edges = BoolProperty(name = "Edges", description = "Export edges", default = False)
+
+    option_colors = BoolProperty(name = "Colors", description = "Export vertex colors", default = True)
+    option_uv_coords = BoolProperty(name = "UVs", description = "Export texture coordinates", default = True)
+    option_materials = BoolProperty(name = "Materials", description = "Export materials", default = True)
+
+    align_types = [("None","None","None"), ("Center","Center","Center"), ("Bottom","Bottom","Bottom"), ("Top","Top","Top")]
+    align_model = EnumProperty(name = "Align model", description = "Align model", items = align_types, default = "None")
+
+    option_scale = FloatProperty(name = "Scale", description = "Scale vertices", min = 0.01, max = 1000.0, soft_min = 0.01, soft_max = 1000.0, default = 1.0)
+    option_flip_yz = BoolProperty(name = "Flip YZ", description = "Flip YZ", default = True)
+
+    option_export_scene = BoolProperty(name = "Scene", description = "Export scene", default = False)
+    option_embed_meshes = BoolProperty(name = "Embed", description = "Embed meshes", default = True)
+    
+    option_lights = BoolProperty(name = "Lights", description = "Export default scene lights", default = False)
+    option_cameras = BoolProperty(name = "Cameras", description = "Export default scene cameras", default = False)
+
+    def invoke(self, context, event):
+        restore_settings_export(self.properties)
+        return ExportHelper.invoke(self, context, event)
+
+    @classmethod
+    def poll(cls, context):
+        return context.active_object != None
+
+    def execute(self, context):
+        print("Selected: " + context.active_object.name)
+
+        if not self.properties.filepath:
+            raise Exception("filename not set")
+
+        save_settings_export(self.properties)
+
+        filepath = self.filepath
+        import io_mesh_threejs.export_threejs
+        return io_mesh_threejs.export_threejs.save(self, context, **self.properties)
+
+    def draw(self, context):
+        layout = self.layout
+
+        row = layout.row()
+        row.label(text="Geometry:")
+
+        row = layout.row()
+        row.prop(self.properties, "option_vertices")
+        # row = layout.row()
+        # row.enabled = self.properties.option_vertices
+        # row.prop(self.properties, "option_vertices_deltas")
+        row.prop(self.properties, "option_vertices_truncate")
+        layout.separator()
+
+        row = layout.row()
+        row.prop(self.properties, "option_faces")
+        row = layout.row()
+        row.enabled = self.properties.option_faces
+        # row.prop(self.properties, "option_faces_deltas")
+        layout.separator()
+
+        row = layout.row()
+        row.prop(self.properties, "option_normals")
+        layout.separator()
+
+        row = layout.row()
+        row.prop(self.properties, "option_edges")
+        layout.separator()
+
+        row = layout.row()
+        row.label(text="Materials:")
+
+        row = layout.row()
+        row.prop(self.properties, "option_uv_coords")
+        row.prop(self.properties, "option_colors")
+        row = layout.row()
+        row.prop(self.properties, "option_materials")
+        layout.separator()
+
+        row = layout.row()
+        row.label(text="Settings:")
+
+        row = layout.row()
+        row.prop(self.properties, "align_model")
+        row = layout.row()
+        row.prop(self.properties, "option_flip_yz")
+        row.prop(self.properties, "option_scale")
+        layout.separator()
+
+        row = layout.row()
+        row.label(text="Beta:")
+
+        row = layout.row()
+        row.prop(self.properties, "option_export_scene")
+        row.prop(self.properties, "option_lights")
+        row.prop(self.properties, "option_cameras")
+
+        row = layout.row()
+        row.prop(self.properties, "option_embed_meshes")
+        layout.separator()
+
+
+# ################################################################
+# Common
+# ################################################################
+
+def menu_func_export(self, context):
+    default_path = bpy.data.filepath.replace(".blend", ".js")
+    self.layout.operator(ExportTHREEJS.bl_idname, text="Three.js (.js)").filepath = default_path
+
+def menu_func_import(self, context):
+    self.layout.operator(ImportTHREEJS.bl_idname, text="Three.js (.js)")
+
+def register():
+    bpy.utils.register_module(__name__)
+    bpy.types.INFO_MT_file_export.append(menu_func_export)
+    bpy.types.INFO_MT_file_import.append(menu_func_import)
+
+def unregister():
+    bpy.utils.unregister_module(__name__)
+    bpy.types.INFO_MT_file_export.remove(menu_func_export)
+    bpy.types.INFO_MT_file_import.remove(menu_func_import)
+
+if __name__ == "__main__":
+    register()

+ 1632 - 0
utils/exporters/blender/2.58/scripts/addons/io_mesh_threejs/export_threejs.py

@@ -0,0 +1,1632 @@
+# ##### 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 #####
+
+"""
+Blender exporter for Three.js (ASCII JSON format).
+
+TODO
+    - copy used images to folder where exported file goes
+    - binary format
+"""
+
+import bpy
+import mathutils
+
+import os
+import os.path
+import math
+import operator
+import random
+
+# #####################################################
+# Configuration
+# #####################################################
+
+DEFAULTS = {
+"bgcolor" : [0, 0, 0],
+"bgalpha" : 1.0,
+
+"position" : [0, 0, 0],
+"rotation" : [-math.pi/2, 0, 0],
+"scale"    : [1, 1, 1],
+
+"camera"  :
+    {
+        "name" : "default_camera",
+        "type" : "perspective",
+        "near" : 1,
+        "far"  : 10000,
+        "fov"  : 60,
+        "aspect": 1.333,
+        "position" : [0, 0, 10],
+        "target"   : [0, 0, 0]
+    },
+
+"light" :
+ {
+    "name"       : "default_light",
+    "type"       : "directional",
+    "direction"  : [0, 1, 1],
+    "color"      : [1, 1, 1],
+    "intensity"  : 0.8
+ }
+}
+
+# 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 = """\
+/* Converted from: %(fname)s
+ *
+ * File generated with Blender 2.57 Exporter
+ * https://github.com/mrdoob/three.js/tree/master/utils/exporters/blender/
+ *
+ * objects:    %(nobjects)s
+ * geometries: %(ngeometries)s
+ * materials:  %(nmaterials)s
+ * textures:   %(ntextures)s
+ */
+
+var scene = {
+
+"type" : "scene",
+"urlBaseType" : "relativeToScene",
+
+%(sections)s
+
+"transform" :
+{
+    "position"  : %(position)s,
+    "rotation"  : %(rotation)s,
+    "scale"     : %(scale)s
+},
+
+"defaults" :
+{
+    "bgcolor" : %(bgcolor)s,
+    "bgalpha" : %(bgalpha)f,
+    "camera"  : %(defcamera)s
+}
+
+}
+
+postMessage( scene );
+close();
+"""
+
+TEMPLATE_SECTION = """
+"%s" :
+{
+%s
+},
+"""
+
+TEMPLATE_OBJECT = """\
+    %(object_id)s : {
+        "geometry"  : %(geometry_id)s,
+        "groups"    : [ %(group_id)s ],
+        "materials" : [ %(material_id)s ],
+        "position"  : %(position)s,
+        "rotation"  : %(rotation)s,
+        "quaternion": %(quaternion)s,
+        "scale"     : %(scale)s,
+        "visible"       : %(visible)s,
+        "castsShadow"   : %(castsShadow)s,
+        "meshCollider"  : %(meshCollider)s,
+        "trigger"       : %(trigger)s
+    }"""
+
+TEMPLATE_EMPTY = """\
+    %(object_id)s : {
+        "groups"    : [ %(group_id)s ],
+        "position"  : %(position)s,
+        "rotation"  : %(rotation)s,
+        "quaternion": %(quaternion)s,
+        "scale"     : %(scale)s,
+        "trigger"   : %(trigger)s
+    }"""
+
+TEMPLATE_GEOMETRY_LINK = """\
+    %(geometry_id)s : {
+        "type" : "ascii_mesh",
+        "url"  : %(model_file)s
+    }"""
+
+TEMPLATE_GEOMETRY_EMBED = """\
+    %(geometry_id)s : {
+        "type" : "embedded_mesh",
+        "id"  : %(embed_id)s
+    }"""
+
+TEMPLATE_TEXTURE = """\
+    %(texture_id)s : {
+        "url": %(texture_file)s%(extras)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_LIGHT_DIRECTIONAL = """\
+    %(light_id)s: {
+        "type"		 : "directional",
+        "direction"	 : %(direction)s,
+        "color" 	 : %(color)d,
+        "intensity"	 : %(intensity).2f
+    }"""
+
+TEMPLATE_LIGHT_POINT = """\
+    %(light_id)s: {
+        "type"	     : "point",
+        "position"   : %(position)s,
+        "color"      : %(color)d,
+        "intensity"	 : %(intensity).3f
+    }"""
+
+TEMPLATE_VEC4 = '[ %f, %f, %f, %f ]'
+TEMPLATE_VEC3 = '[ %f, %f, %f ]'
+TEMPLATE_VEC2 = '[ %f, %f ]'
+TEMPLATE_STRING = '"%s"'
+TEMPLATE_HEX = "0x%06x"
+
+# #####################################################
+# Templates - model
+# #####################################################
+
+TEMPLATE_FILE_ASCII = """\
+/*
+ * File generated with Blender 2.57 Exporter
+ * https://github.com/mrdoob/three.js/tree/master/utils/exporters/blender/
+ *
+ * vertices: %(nvertex)d
+ * faces: %(nface)d
+ * normals: %(nnormal)d
+ * uvs: %(nuv)d
+ * colors: %(ncolor)d
+ * materials: %(nmaterial)d
+ * edges: %(nedges)d
+ *
+ */
+
+var model = {
+
+%(model)s
+
+};
+
+postMessage( model );
+close();
+"""
+
+TEMPLATE_MODEL_ASCII = """\
+    "version" : 2,
+
+    "scale" : %(scale)f,
+
+    "materials": [%(materials)s],
+
+    "vertices": [%(vertices)s],
+
+    "morphTargets": [],
+
+    "normals": [%(normals)s],
+
+    "colors": [%(colors)s],
+
+    "uvs": [[%(uvs)s]],
+
+    "faces": [%(faces)s],
+
+    "edges" : [%(edges)s]
+"""
+
+TEMPLATE_VERTEX = "%f,%f,%f"
+TEMPLATE_VERTEX_TRUNCATE = "%d,%d,%d"
+
+TEMPLATE_N = "%f,%f,%f"
+TEMPLATE_UV = "%f,%f"
+#TEMPLATE_C = "0x%06x"
+TEMPLATE_C = "%d"
+TEMPLATE_EDGE = "%d,%d"
+
+# #####################################################
+# Utils
+# #####################################################
+
+def veckey3(x,y,z):
+    return round(x, 6), round(y, 6), round(z, 6)
+
+def veckey3d(v):
+    return veckey3(v.x, v.y, v.z)
+
+def veckey2d(v):
+    return round(v[0], 6), round(v[1], 6)
+
+def get_normal_indices(v, normals, mesh):
+    n = []
+    mv = mesh.vertices
+
+    for i in v:
+        normal = mv[i].normal
+        key = veckey3d(normal)
+
+        n.append( normals[key] )
+
+    return n
+
+def get_uv_indices(face_index, uvs, mesh):
+    uv = []
+    uv_layer = mesh.uv_textures.active.data
+    for i in uv_layer[face_index].uv:
+        uv.append( uvs[veckey2d(i)] )
+    return uv
+
+def get_color_indices(face_index, colors, mesh):
+    c = []
+    color_layer = mesh.vertex_colors.active.data
+    face_colors = color_layer[face_index]
+    face_colors = face_colors.color1, face_colors.color2, face_colors.color3, face_colors.color4
+    for i in face_colors:
+        c.append( colors[hexcolor(i)] )
+    return c
+
+def rgb2int(rgb):
+    color = (int(rgb[0]*255) << 16) + (int(rgb[1]*255) << 8) + int(rgb[2]*255);
+    return color
+
+# #####################################################
+# Utils - files
+# #####################################################
+
+def write_file(fname, content):
+    out = open(fname, "w")
+    out.write(content)
+    out.close()
+
+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 ensure_extension(filepath, extension):
+    if not filepath.lower().endswith(extension):
+        filepath += extension
+    return filepath
+
+def generate_mesh_filename(meshname, filepath):
+    normpath = os.path.normpath(filepath)
+    path, ext = os.path.splitext(normpath)
+    return "%s.%s%s" % (path, meshname, ext)
+
+
+# #####################################################
+# Utils - alignment
+# #####################################################
+
+def bbox(vertices):
+    """Compute bounding box of vertex array.
+    """
+
+    if len(vertices)>0:
+        minx = maxx = vertices[0].co.x
+        miny = maxy = vertices[0].co.y
+        minz = maxz = vertices[0].co.z
+
+        for v in vertices[1:]:
+            if v.co.x < minx:
+                minx = v.co.x
+            elif v.co.x > maxx:
+                maxx = v.co.x
+
+            if v.co.y < miny:
+                miny = v.co.y
+            elif v.co.y > maxy:
+                maxy = v.co.y
+
+            if v.co.z < minz:
+                minz = v.co.z
+            elif v.co.z > maxz:
+                maxz = v.co.z
+
+        return { 'x':[minx,maxx], 'y':[miny,maxy], 'z':[minz,maxz] }
+
+    else:
+        return { 'x':[0,0], 'y':[0,0], 'z':[0,0] }
+
+def translate(vertices, t):
+    """Translate array of vertices by vector t.
+    """
+
+    for i in range(len(vertices)):
+        vertices[i].co.x += t[0]
+        vertices[i].co.y += t[1]
+        vertices[i].co.z += t[2]
+
+def center(vertices):
+    """Center model (middle of bounding box).
+    """
+
+    bb = bbox(vertices)
+
+    cx = bb['x'][0] + (bb['x'][1] - bb['x'][0])/2.0
+    cy = bb['y'][0] + (bb['y'][1] - bb['y'][0])/2.0
+    cz = bb['z'][0] + (bb['z'][1] - bb['z'][0])/2.0
+
+    translate(vertices, [-cx,-cy,-cz])
+
+def top(vertices):
+    """Align top of the model with the floor (Y-axis) and center it around X and Z.
+    """
+
+    bb = bbox(vertices)
+
+    cx = bb['x'][0] + (bb['x'][1] - bb['x'][0])/2.0
+    cy = bb['y'][1]
+    cz = bb['z'][0] + (bb['z'][1] - bb['z'][0])/2.0
+
+    translate(vertices, [-cx,-cy,-cz])
+
+def bottom(vertices):
+    """Align bottom of the model with the floor (Y-axis) and center it around X and Z.
+    """
+
+    bb = bbox(vertices)
+
+    cx = bb['x'][0] + (bb['x'][1] - bb['x'][0])/2.0
+    cy = bb['y'][0]
+    cz = bb['z'][0] + (bb['z'][1] - bb['z'][0])/2.0
+
+    translate(vertices, [-cx,-cy,-cz])
+
+# #####################################################
+# Elements rendering
+# #####################################################
+
+def hexcolor(c):
+    return ( int(c[0] * 255) << 16  ) + ( int(c[1] * 255) << 8 ) + int(c[2] * 255)
+
+def generate_vertices(vertices, option_vertices_truncate, option_vertices):
+    if not option_vertices:
+        return ""
+
+    return ",".join(generate_vertex(v, option_vertices_truncate) for v in vertices)
+
+def generate_vertex(v, option_vertices_truncate):
+    if not option_vertices_truncate:
+        return TEMPLATE_VERTEX % (v.co.x, v.co.y, v.co.z)
+    else:
+        return TEMPLATE_VERTEX_TRUNCATE % (v.co.x, v.co.y, v.co.z)
+
+def generate_normal(n):
+    return TEMPLATE_N % (n[0], n[1], n[2])
+
+def generate_vertex_color(c):
+    return TEMPLATE_C % c
+
+def generate_uv(uv):
+    return TEMPLATE_UV % (uv[0], 1.0 - uv[1])
+
+def generate_edge(e):
+    return TEMPLATE_EDGE % (e.vertices[0], e.vertices[1])
+
+# #####################################################
+# Model exporter - faces
+# #####################################################
+
+def setBit(value, position, on):
+    if on:
+        mask = 1 << position
+        return (value | mask)
+    else:
+        mask = ~(1 << position)
+        return (value & mask)
+
+def generate_faces(normals, uvs, colors, mesh, option_normals, option_colors, option_uv_coords, option_materials, flipyz, option_faces):
+    if not option_faces:
+        return ""
+
+    return ",".join(generate_face(f, i, normals, uvs, colors, mesh, option_normals, option_colors, option_uv_coords, option_materials, flipyz) for i, f in enumerate(mesh.faces))
+
+def generate_face(f, faceIndex, normals, uvs, colors, mesh, option_normals, option_colors, option_uv_coords, option_materials, flipyz):
+    isTriangle = ( len(f.vertices) == 3 )
+
+    if isTriangle:
+        nVertices = 3
+    else:
+        nVertices = 4
+
+    hasMaterial = option_materials
+
+    hasFaceUvs = False # not supported in Blender
+    hasFaceVertexUvs = option_uv_coords
+
+    hasFaceNormals = False # don't export any face normals (as they are computed in engine)
+    hasFaceVertexNormals = option_normals
+
+    hasFaceColors = False       # not supported in Blender
+    hasFaceVertexColors = option_colors
+
+    faceType = 0
+    faceType = setBit(faceType, 0, not isTriangle)
+    faceType = setBit(faceType, 1, hasMaterial)
+    faceType = setBit(faceType, 2, hasFaceUvs)
+    faceType = setBit(faceType, 3, hasFaceVertexUvs)
+    faceType = setBit(faceType, 4, hasFaceNormals)
+    faceType = setBit(faceType, 5, hasFaceVertexNormals)
+    faceType = setBit(faceType, 6, hasFaceColors)
+    faceType = setBit(faceType, 7, hasFaceVertexColors)
+
+    faceData = []
+
+    # order is important, must match order in JSONLoader
+
+    # face type
+    # vertex indices
+    # material index
+    # face uvs index
+    # face vertex uvs indices
+    # face color index
+    # face vertex colors indices
+
+    faceData.append(faceType)
+
+    # must clamp in case on polygons bigger than quads
+
+    for i in range(nVertices):
+        index = f.vertices[i]
+        faceData.append(index)
+
+    if hasMaterial:
+        faceData.append( f.material_index )
+
+    if hasFaceVertexUvs:
+        uv = get_uv_indices(faceIndex, uvs, mesh)
+        for i in range(nVertices):
+            index = uv[i]
+            faceData.append(index)
+
+    if hasFaceVertexNormals:
+        n = get_normal_indices(f.vertices, normals, mesh)
+        for i in range(nVertices):
+            index = n[i]
+            faceData.append(index)
+
+    if hasFaceVertexColors:
+        c = get_color_indices(faceIndex, colors, mesh)
+        for i in range(nVertices):
+            index = c[i]
+            faceData.append(index)
+
+    return ",".join( map(str, faceData) )
+
+
+# #####################################################
+# Model exporter - normals
+# #####################################################
+
+def extract_vertex_normals(mesh, option_normals):
+    if not option_normals:
+        return {}, 0
+
+    count = 0
+    normals = {}
+
+    for f in mesh.faces:
+        for v in f.vertices:
+
+            normal = mesh.vertices[v].normal
+            key = veckey3d(normal)
+
+            if key not in normals:
+                normals[key] = count
+                count += 1
+
+    return normals, count
+
+def generate_normals(normals, option_normals):
+    if not option_normals:
+        return ""
+
+    chunks = []
+    for key, index in sorted(normals.items(), key = operator.itemgetter(1)):
+        chunks.append(key)
+
+    return ",".join(generate_normal(n) for n in chunks)
+
+# #####################################################
+# Model exporter - vertex colors
+# #####################################################
+
+def extract_vertex_colors(mesh, option_colors):
+
+    if not option_colors:
+        return {}, 0
+
+    count = 0
+    colors = {}
+
+    color_layer = mesh.vertex_colors.active.data
+
+    for face_index, face in enumerate(mesh.faces):
+
+        face_colors = color_layer[face_index]
+        face_colors = face_colors.color1, face_colors.color2, face_colors.color3, face_colors.color4
+
+        for c in face_colors:
+            key = hexcolor(c)
+            if key not in colors:
+                colors[key] = count
+                count += 1
+
+    return colors, count
+
+def generate_vertex_colors(colors, option_colors):
+    if not option_colors:
+        return ""
+
+    chunks = []
+    for key, index in sorted(colors.items(), key=operator.itemgetter(1)):
+        chunks.append(key)
+
+    return ",".join(generate_vertex_color(c) for c in chunks)
+
+# #####################################################
+# Model exporter - UVs
+# #####################################################
+
+def extract_uvs(mesh, option_uv_coords):
+
+    if not option_uv_coords:
+        return {}, 0
+
+    count = 0
+    uvs = {}
+
+    uv_layer = mesh.uv_textures.active.data
+
+    for face_index, face in enumerate(mesh.faces):
+
+        for uv_index, uv in enumerate(uv_layer[face_index].uv):
+
+            key = veckey2d(uv)
+            if key not in uvs:
+                uvs[key] = count
+                count += 1
+
+    return uvs, count
+
+def generate_uvs(uvs, option_uv_coords):
+    if not option_uv_coords:
+        return ""
+
+    chunks = []
+    for key, index in sorted(uvs.items(), key=operator.itemgetter(1)):
+        chunks.append(key)
+
+    return ",".join(generate_uv(n) for n in chunks)
+
+# #####################################################
+# Model exporter - 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]
+        return COLORS[i]
+    else:
+        #return "0x%06x" % int(0xffffff * random.random())
+        return int(0xffffff * random.random())
+
+def generate_mtl(materials):
+    """Generate dummy materials.
+    """
+
+    mtl = {}
+    for m in materials:
+        index = materials[m]
+        mtl[m] = {
+            "DbgName": m,
+            "DbgIndex": index,
+            "DbgColor": generate_color(index),
+            "vertexColors" : False
+        }
+    return mtl
+
+def value2string(v):
+    if type(v) == str and v[0:2] != "0x":
+        return '"%s"' % v
+    elif type(v) == bool:
+        return str(v).lower()
+    return str(v)
+
+def generate_materials(mtl, materials, draw_type):
+    """Generate JS array of materials objects
+    """
+
+    mtl_array = []
+    for m in mtl:
+        index = materials[m]
+
+        # add debug information
+        #  materials should be sorted according to how
+        #  they appeared in OBJ file (for the first time)
+        #  this index is identifier used in face definitions
+        mtl[m]['DbgName'] = m
+        mtl[m]['DbgIndex'] = index
+        mtl[m]['DbgColor'] = generate_color(index)
+
+        if draw_type in [ "BOUNDS", "WIRE" ]:
+            mtl[m]['wireframe'] = True
+            mtl[m]['DbgColor'] = 0xff0000
+
+        mtl_raw = ",\n".join(['\t"%s" : %s' % (n, value2string(v)) for n,v in sorted(mtl[m].items())])
+        mtl_string = "\t{\n%s\n\t}" % mtl_raw
+        mtl_array.append([index, mtl_string])
+
+    return ",\n\n".join([m for i,m in sorted(mtl_array)]), len(mtl_array)
+
+def extract_materials(mesh, scene, option_colors):
+    world = scene.world
+
+    materials = {}
+    for m in mesh.materials:
+        if m:
+            materials[m.name] = {}
+            material = materials[m.name]
+
+            material['colorDiffuse'] = [m.diffuse_intensity * m.diffuse_color[0],
+                                        m.diffuse_intensity * m.diffuse_color[1],
+                                        m.diffuse_intensity * m.diffuse_color[2]]
+
+            material['colorSpecular'] = [m.specular_intensity * m.specular_color[0],
+                                         m.specular_intensity * m.specular_color[1],
+                                         m.specular_intensity * m.specular_color[2]]
+
+            world_ambient_color = [0, 0, 0]
+            if world:
+                world_ambient_color = world.ambient_color
+
+            material['colorAmbient'] = [m.ambient * world_ambient_color[0],
+                                        m.ambient * world_ambient_color[1],
+                                        m.ambient * world_ambient_color[2]]
+
+            material['transparency'] = m.alpha
+
+            # not sure about mapping values to Blinn-Phong shader
+            # Blender uses INT from [1,511] with default 0
+            # http://www.blender.org/documentation/blender_python_api_2_54_0/bpy.types.Material.html#bpy.types.Material.specular_hardness
+
+            material["specularCoef"] = m.specular_hardness
+
+            texture = m.active_texture
+
+            if texture and texture.type == 'IMAGE' and texture.image:
+                fn = bpy.path.abspath(texture.image.filepath)
+                fn = os.path.normpath(fn)
+                fn_strip = os.path.basename(fn)
+
+                material['mapDiffuse'] = fn_strip
+                
+                if texture.repeat_x != 1 or texture.repeat_y != 1:
+                    material['mapDiffuseRepeat'] = [texture.repeat_x, texture.repeat_y]
+
+            material["vertexColors"] = m.THREE_useVertexColors and option_colors
+
+            # can't really use this reliably to tell apart Phong from Lambert
+            # as Blender defaults to non-zero specular color
+            #if m.specular_intensity > 0.0 and (m.specular_color[0] > 0 or m.specular_color[1] > 0 or m.specular_color[2] > 0):
+            #    material['shading'] = "Phong"
+            #else:
+            #    material['shading'] = "Lambert"
+
+            material['shading'] = m.THREE_materialType
+
+    return materials
+
+def generate_materials_string(mesh, scene, option_colors, draw_type):
+
+    random.seed(42) # to get well defined color order for debug materials
+
+    materials = {}
+    if mesh.materials:
+        for i, m in enumerate(mesh.materials):
+            if m:
+                materials[m.name] = i
+            else:
+                materials["undefined_dummy_%0d" % i] = i
+
+
+    if not materials:
+        materials = { 'default':0 }
+
+    # default dummy materials
+
+    mtl = generate_mtl(materials)
+
+    # extract real materials from the mesh
+
+    mtl.update(extract_materials(mesh, scene, option_colors))
+
+    return generate_materials(mtl, materials, draw_type)
+
+# #####################################################
+# ASCII model generator
+# #####################################################
+
+def generate_ascii_model(mesh, scene,
+                         option_vertices,
+                         option_vertices_truncate,
+                         option_faces,
+                         option_normals,
+                         option_edges,
+                         option_uv_coords,
+                         option_materials,
+                         option_colors,
+                         align_model,
+                         flipyz,
+                         option_scale,
+                         draw_type):
+
+    vertices = mesh.vertices[:]
+
+    if align_model == 1:
+        center(vertices)
+    elif align_model == 2:
+        bottom(vertices)
+    elif align_model == 3:
+        top(vertices)
+
+    normals, nnormal = extract_vertex_normals(mesh, option_normals)
+    colors, ncolor = extract_vertex_colors(mesh, option_colors)
+    uvs, nuv = extract_uvs(mesh, option_uv_coords)
+
+    materials_string = ""
+    nmaterial = 0
+
+    edges_string = ""
+    nedges = 0
+
+    if option_materials:
+        materials_string, nmaterial = generate_materials_string(mesh, scene, option_colors, draw_type)
+
+    if option_edges:
+        nedges = len(mesh.edges)
+        edges_string  = ",".join(generate_edge(e) for e in mesh.edges)
+
+    model_string = TEMPLATE_MODEL_ASCII % {
+    "scale" : option_scale,
+
+    "uvs"       : generate_uvs(uvs, option_uv_coords),
+    "normals"   : generate_normals(normals, option_normals),
+    "colors"    : generate_vertex_colors(colors, option_colors),
+
+    "materials" : materials_string,
+
+    "vertices" : generate_vertices(vertices, option_vertices_truncate, option_vertices),
+
+    "faces"    : generate_faces(normals, uvs, colors, mesh, option_normals, option_colors, option_uv_coords, option_materials, flipyz, option_faces),
+
+    "edges"    : edges_string
+    }
+
+    text = TEMPLATE_FILE_ASCII % {
+    "nvertex"   : len(mesh.vertices),
+    "nface"     : len(mesh.faces),
+    "nuv"       : nuv,
+    "nnormal"   : nnormal,
+    "ncolor"    : ncolor,
+    "nmaterial" : nmaterial,
+    "nedges"    : nedges,
+    
+    "model"     : model_string
+    }
+
+
+    return text, model_string
+
+
+# #####################################################
+# Model exporter - export single mesh
+# #####################################################
+
+def generate_mesh_string(obj, scene,
+                option_vertices,
+                option_vertices_truncate,
+                option_faces,
+                option_normals,
+                option_edges,
+                option_uv_coords,
+                option_materials,
+                option_colors,
+                align_model,
+                flipyz,
+                option_scale,
+                export_single_model):
+
+    # collapse modifiers into mesh
+
+    mesh = obj.to_mesh(scene, True, 'RENDER')
+
+    if not mesh:
+        raise Exception("Error, could not get mesh data from object [%s]" % obj.name)
+
+    # that's what Blender's native export_obj.py does
+    # to flip YZ
+
+    if export_single_model:
+        X_ROT = mathutils.Matrix.Rotation(-math.pi/2, 4, 'X')
+        mesh.transform(X_ROT * obj.matrix_world)
+
+    mesh.calc_normals()
+
+    mesh.transform(mathutils.Matrix.Scale(option_scale, 4))
+
+    faceUV = (len(mesh.uv_textures) > 0)
+    vertexUV = (len(mesh.sticky) > 0)
+    vertexColors = len(mesh.vertex_colors) > 0
+
+    if not vertexColors:
+        option_colors = False
+
+    if (not faceUV) and (not vertexUV):
+        option_uv_coords = False
+
+    if faceUV:
+        active_uv_layer = mesh.uv_textures.active
+        if not active_uv_layer:
+            option_uv_coords = False
+
+    if vertexColors:
+        active_col_layer = mesh.vertex_colors.active
+        if not active_col_layer:
+            option_colors = False
+
+    text, model_string = generate_ascii_model(mesh, scene,
+                                option_vertices,
+                                option_vertices_truncate,
+                                option_faces,
+                                option_normals,
+                                option_edges,
+                                option_uv_coords,
+                                option_materials,
+                                option_colors,
+                                align_model,
+                                flipyz,
+                                option_scale,
+                                obj.draw_type)
+    # remove temp mesh
+
+    bpy.data.meshes.remove(mesh)
+    
+    return text, model_string
+
+def export_mesh(obj, scene, filepath,
+                option_vertices,
+                option_vertices_truncate,
+                option_faces,
+                option_normals,
+                option_edges,
+                option_uv_coords,
+                option_materials,
+                option_colors,
+                align_model,
+                flipyz,
+                option_scale,
+                export_single_model):
+
+    """Export single mesh"""
+
+    text, model_string = generate_mesh_string(obj, scene,
+                option_vertices,
+                option_vertices_truncate,
+                option_faces,
+                option_normals,
+                option_edges,
+                option_uv_coords,
+                option_materials,
+                option_colors,
+                align_model,
+                flipyz,
+                option_scale,
+                export_single_model)
+
+    write_file(filepath, text)
+
+    print("writing", filepath, "done")
+
+
+# #####################################################
+# Scene exporter - render elements
+# #####################################################
+
+def generate_vec4(vec):
+    return TEMPLATE_VEC4 % (vec[0], vec[1], vec[2], vec[3])
+
+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_string_list(src_list):
+    return ", ".join(generate_string(item) for item in src_list)
+
+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 material in materials:
+        chunks.append(material.name)
+
+    return chunks
+
+def generate_group_id_list(obj):
+    chunks = []
+
+    for group in bpy.data.groups:
+        if obj.name in group.objects:
+            chunks.append(group.name)
+
+    return chunks
+
+def generate_bool_property(property):
+    if property:
+        return "true"
+    return "false"
+
+# #####################################################
+# Scene exporter - objects
+# #####################################################
+
+def generate_objects(data):
+    chunks = []
+
+    for obj in data["objects"]:
+
+        if obj.type == "MESH" and obj.THREE_exportGeometry:
+            object_id = obj.name
+
+            if len(obj.modifiers) > 0:
+                geo_name = obj.name
+            else:
+                geo_name = obj.data.name
+
+            geometry_id = "geo_%s" % geo_name
+
+            material_ids = generate_material_id_list(obj.material_slots)
+            group_ids = generate_group_id_list(obj)
+
+            position, quaternion, scale = obj.matrix_world.decompose()
+            rotation = quaternion.to_euler("XYZ")
+
+            material_string = ""
+            if len(material_ids) > 0:
+                material_string = generate_string_list(material_ids)
+
+            group_string = ""
+            if len(group_ids) > 0:
+                group_string = generate_string_list(group_ids)
+
+            castsShadow = obj.THREE_castsShadow
+            meshCollider = obj.THREE_meshCollider
+            triggerType = obj.THREE_triggerType
+
+            visible = True
+            #if obj.draw_type in ["BOUNDS", "WIRE"] and (meshCollider or castsShadow):
+            if meshCollider or castsShadow:
+                visible = False
+
+            geometry_string = generate_string(geometry_id)
+                
+            object_string = TEMPLATE_OBJECT % {
+            "object_id"   : generate_string(object_id),
+            "geometry_id" : geometry_string,
+            "group_id"    : group_string,
+            "material_id" : material_string,
+
+            "position"    : generate_vec3(position),
+            "rotation"    : generate_vec3(rotation),
+            "quaternion"  : generate_vec4(quaternion),
+            "scale"       : generate_vec3(scale),
+
+            "castsShadow"  : generate_bool_property(castsShadow),
+            "meshCollider" : generate_bool_property(meshCollider),
+            "trigger" 	   : generate_string(triggerType),
+            "visible"      : generate_bool_property(visible)
+            }
+            chunks.append(object_string)
+            
+        elif obj.type == "EMPTY" or (obj.type == "MESH" and not obj.THREE_exportGeometry):
+
+            object_id = obj.name
+            group_ids = generate_group_id_list(obj)
+
+            position, quaternion, scale = obj.matrix_world.decompose()
+            rotation = quaternion.to_euler("XYZ")
+
+            group_string = ""
+            if len(group_ids) > 0:
+                group_string = generate_string_list(group_ids)
+
+            triggerType = obj.THREE_triggerType
+                
+            object_string = TEMPLATE_EMPTY % {
+            "object_id"   : generate_string(object_id),
+            "group_id"    : group_string,
+
+            "position"    : generate_vec3(position),
+            "rotation"    : generate_vec3(rotation),
+            "quaternion"  : generate_vec4(quaternion),
+            "scale"       : generate_vec3(scale),
+
+            "trigger" 	   : generate_string(triggerType),
+            }
+            chunks.append(object_string)
+
+    return ",\n\n".join(chunks), len(chunks)
+
+# #####################################################
+# Scene exporter - geometries
+# #####################################################
+
+def generate_geometries(data):
+    chunks = []
+
+    geo_set = set()
+
+    for obj in data["objects"]:
+        if obj.type == "MESH" and obj.THREE_exportGeometry:
+
+            if len(obj.modifiers) > 0:
+                name = obj.name
+            else:
+                name = obj.data.name
+
+            if name not in geo_set:
+
+                geometry_id = "geo_%s" % name
+                
+                if data["embed_meshes"]:
+
+                    embed_id = "emb_%s" % name
+
+                    geometry_string = TEMPLATE_GEOMETRY_EMBED % {
+                    "geometry_id" : generate_string(geometry_id),
+                    "embed_id"  : generate_string(embed_id)
+                    }
+
+                else:
+
+                    model_filename = os.path.basename(generate_mesh_filename(name, data["filepath"]))
+
+                    geometry_string = TEMPLATE_GEOMETRY_LINK % {
+                    "geometry_id" : generate_string(geometry_id),
+                    "model_file"  : generate_string(model_filename)
+                    }
+
+                chunks.append(geometry_string)
+
+                geo_set.add(name)
+
+    return ",\n\n".join(chunks), len(chunks)
+
+# #####################################################
+# Scene exporter - textures
+# #####################################################
+
+def generate_textures_scene(data):
+    chunks = []
+
+    # TODO: extract just textures actually used by some objects in the scene
+
+    for texture in bpy.data.textures:
+
+        if texture.type == 'IMAGE' and texture.image:
+
+            img = texture.image
+
+            texture_id = img.name
+            texture_file = extract_texture_filename(img)
+            
+            extras = ""
+            if texture.repeat_x != 1 or texture.repeat_y != 1:
+                extras = ',\n        "repeat": [%f, %f]' % (texture.repeat_x, texture.repeat_y)
+
+            texture_string = TEMPLATE_TEXTURE % {
+            "texture_id"   : generate_string(texture_id),
+            "texture_file" : generate_string(texture_file),
+            "extras"       : extras
+            }
+            chunks.append(texture_string)
+
+    return ",\n\n".join(chunks), len(chunks)
+
+def extract_texture_filename(image):
+    fn = bpy.path.abspath(image.filepath)
+    fn = os.path.normpath(fn)
+    fn_strip = os.path.basename(fn)
+    return fn_strip
+
+# #####################################################
+# Scene exporter - materials
+# #####################################################
+
+def extract_material_data(m, option_colors):
+    world = bpy.context.scene.world
+
+    material = { 'name': m.name }
+
+    material['colorDiffuse'] = [m.diffuse_intensity * m.diffuse_color[0],
+                                m.diffuse_intensity * m.diffuse_color[1],
+                                m.diffuse_intensity * m.diffuse_color[2]]
+
+    material['colorSpecular'] = [m.specular_intensity * m.specular_color[0],
+                                 m.specular_intensity * m.specular_color[1],
+                                 m.specular_intensity * m.specular_color[2]]
+
+    world_ambient_color = [0, 0, 0]
+    if world:
+        world_ambient_color = world.ambient_color
+
+    material['colorAmbient'] = [m.ambient * world_ambient_color[0],
+                                m.ambient * world_ambient_color[1],
+                                m.ambient * world_ambient_color[2]]
+
+    material['transparency'] = m.alpha
+
+    # not sure about mapping values to Blinn-Phong shader
+    # Blender uses INT from [1,511] with default 0
+    # http://www.blender.org/documentation/blender_python_api_2_54_0/bpy.types.Material.html#bpy.types.Material.specular_hardness
+
+    material["specularCoef"] = m.specular_hardness
+
+    material['mapDiffuse'] = ""
+    material['mapLight'] = ""
+    material['mapNormal'] = ""
+
+    material["vertexColors"] = m.THREE_useVertexColors and option_colors
+    
+    # just take first textures of each, for the moment three.js materials can't handle more
+
+    for i in range(len(m.texture_slots)):
+        ts = m.texture_slots[i]
+        if ts:
+            t = ts.texture
+            if ts.use and t.type == 'IMAGE':
+                name = t.image.name
+
+                if t.use_normal_map:
+                    material['mapNormal'] = name
+
+                else:
+
+                    if not material['mapDiffuse']:
+                        material['mapDiffuse'] = name
+
+                    else:
+                        material['mapLight'] = name
+
+                if material['mapDiffuse'] and material['mapNormal'] and material['mapLight']:
+                    break
+
+    material['shading'] = m.THREE_materialType
+
+    return material
+
+def generate_material_string(material):
+    type_map = {
+    "Lambert"   : "MeshLambertMaterial",
+    "Phong"     : "MeshPhongMaterial"
+    }
+
+    material_id = material["name"]
+    shading = material.get("shading", "Lambert")
+    material_type = type_map.get(shading, "MeshBasicMaterial")
+
+    parameters = '"color": %d' % rgb2int(material["colorDiffuse"])
+    parameters += ', "opacity": %.2g' % material["transparency"]
+
+    if shading == "Phong":
+        parameters += ', "ambient": %d' % rgb2int(material["colorAmbient"])
+        parameters += ', "specular": %d' % rgb2int(material["colorSpecular"])
+        parameters += ', "shininess": %.1g' % material["specularCoef"]
+
+    colorMap = material['mapDiffuse']
+    lightMap = material['mapLight']
+    normalMap = material['mapNormal']
+
+    if colorMap:
+        parameters += ', "map": %s' % generate_string(colorMap)
+    if lightMap:
+        parameters += ', "lightMap": %s' % generate_string(lightMap)
+    if normalMap:
+        parameters += ', "normalMap": %s' % generate_string(normalMap)
+
+    if material['vertexColors']:
+        parameters += ', "vertexColors": "vertex"'
+        
+    material_string = TEMPLATE_MATERIAL_SCENE % {
+    "material_id" : generate_string(material_id),
+    "type"        : generate_string(material_type),
+    "parameters"  : parameters
+    }
+
+    return material_string
+
+def generate_materials_scene(data):
+    chunks = []
+
+    # TODO: extract just materials actually used by some objects in the scene
+
+    for m in bpy.data.materials:
+        material = extract_material_data(m, data["use_colors"])
+        material_string = generate_material_string(material)
+        chunks.append(material_string)
+
+    return ",\n\n".join(chunks), len(chunks)
+
+# #####################################################
+# Scene exporter - cameras
+# #####################################################
+
+def generate_cameras(data):
+    if data["use_cameras"]:
+
+        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)
+
+    return ""
+
+# #####################################################
+# Scene exporter - lights
+# #####################################################
+
+def generate_lights(data):
+
+    if data["use_lights"]:
+
+        lights = data.get("lights", [])
+        if not lights:
+            lights.append(DEFAULTS["light"])
+
+        chunks = []
+        for light in lights:
+
+            if light["type"] == "directional":
+                light_string = TEMPLATE_LIGHT_DIRECTIONAL % {
+                "light_id"      : generate_string(light["name"]),
+                "direction"     : generate_vec3(light["direction"]),
+                #"color"         : generate_hex(rgb2int(light["color"])),
+                "color"         : rgb2int(light["color"]),
+                "intensity"     : light["intensity"]
+                }
+
+            elif light["type"] == "point":
+                light_string = TEMPLATE_LIGHT_POINT % {
+                "light_id"      : generate_string(light["name"]),
+                "position"      : generate_vec3(light["position"]),
+                #"color"         : generate_hex(rgb2int(light["color"])),
+                "color"         : rgb2int(light["color"]),
+                "intensity"     : light["intensity"]
+                }
+
+            chunks.append(light_string)
+
+        return ",\n\n".join(chunks)
+        
+    return ""
+
+# #####################################################
+# Scene exporter - embedded meshes
+# #####################################################
+
+def generate_embeds(data):
+    
+    if data["embed_meshes"]:
+
+        chunks = []
+        
+        for e in data["embeds"]:
+            
+            embed = '"emb_%s": {%s}' % (e, data["embeds"][e])
+            chunks.append(embed)
+            
+        return ",\n\n".join(chunks)
+
+    return ""
+    
+# #####################################################
+# Scene exporter - generate ASCII scene
+# #####################################################
+
+def generate_ascii_scene(data):
+
+    objects, nobjects = generate_objects(data)
+    geometries, ngeometries = generate_geometries(data)
+    textures, ntextures = generate_textures_scene(data)
+    materials, nmaterials = generate_materials_scene(data)
+
+    cameras = generate_cameras(data)
+    lights = generate_lights(data)
+
+    embeds = generate_embeds(data)
+
+    sections = [
+    ["objects",    objects],
+    ["geometries", geometries],
+    ["textures",   textures],
+    ["materials",  materials],
+    ["cameras",    cameras],
+    ["lights",     lights],
+    ["embeds",     embeds]
+    ]
+
+    chunks = []
+    for label, content in sections:
+        if content:
+            chunks.append(generate_section(label, content))
+
+    sections_string = "\n".join(chunks)
+
+    default_camera = ""
+    if data["use_cameras"]:
+        default_camera = "default_camera"
+
+    parameters = {
+    "fname"     : data["source_file"],
+
+    "sections"  : sections_string,
+
+    "bgcolor"   : generate_vec3(DEFAULTS["bgcolor"]),
+    "bgalpha"   : DEFAULTS["bgalpha"],
+    "defcamera" :  generate_string(default_camera),
+
+    "nobjects"      : nobjects,
+    "ngeometries"   : ngeometries,
+    "ntextures"     : ntextures,
+    "nmaterials"    : nmaterials,
+
+    "position"      : generate_vec3(DEFAULTS["position"]),
+    "rotation"      : generate_vec3(DEFAULTS["rotation"]),
+    "scale"         : generate_vec3(DEFAULTS["scale"])
+    }
+
+    text = TEMPLATE_SCENE_ASCII % parameters
+
+    return text
+
+def export_scene(scene, filepath, flipyz, option_colors, option_lights, option_cameras, option_embed_meshes, embeds):
+
+    source_file = os.path.basename(bpy.data.filepath)
+
+    scene_text = ""
+    data = {
+    "scene"       : scene,
+    "objects"     : scene.objects,
+    "embeds"      : embeds,
+    "source_file" : source_file,
+    "filepath"    : filepath,
+    "flipyz"      : flipyz,
+    "use_colors"  : option_colors,
+    "use_lights"  : option_lights, 
+    "use_cameras" : option_cameras,
+    "embed_meshes": option_embed_meshes
+    }
+    scene_text += generate_ascii_scene(data)
+
+    write_file(filepath, scene_text)
+
+# #####################################################
+# Main
+# #####################################################
+
+def save(operator, context, filepath = "",
+         option_flip_yz = True,
+         option_vertices = True,
+         option_vertices_truncate = False,
+         option_faces = True,
+         option_normals = True,
+         option_edges = False,
+         option_uv_coords = True,
+         option_materials = True,
+         option_colors = True,
+         align_model = 0,
+         option_export_scene = False,
+         option_lights = False,
+         option_cameras = False,
+         option_scale = 1.0,
+         option_embed_meshes = True):
+
+    filepath = ensure_extension(filepath, '.js')
+
+    scene = context.scene
+
+    if scene.objects.active:
+        bpy.ops.object.mode_set(mode='OBJECT')
+
+    if option_export_scene:
+
+        geo_set = set()
+        embeds = {}
+
+        for obj in scene.objects:
+            if obj.type == "MESH" and obj.THREE_exportGeometry:
+
+                # create extra copy of geometry with applied modifiers
+                # (if they exist)
+
+                if len(obj.modifiers) > 0:
+                    name = obj.name
+
+                # otherwise can share geometry
+
+                else:
+                    name = obj.data.name
+
+                if name not in geo_set:
+                    
+                    if option_embed_meshes:
+                        
+                        text, model_string = generate_mesh_string(obj, scene,
+                                                        option_vertices,
+                                                        option_vertices_truncate,
+                                                        option_faces,
+                                                        option_normals,
+                                                        option_edges,
+                                                        option_uv_coords,
+                                                        option_materials,
+                                                        option_colors,
+                                                        False,
+                                                        option_flip_yz,
+                                                        option_scale,
+                                                        False)
+                        
+                        embeds[name] = model_string
+
+                    else:
+
+                        fname = generate_mesh_filename(name, filepath)
+                        export_mesh(obj, scene, fname,
+                                    option_vertices,
+                                    option_vertices_truncate,
+                                    option_faces,
+                                    option_normals,
+                                    option_edges,
+                                    option_uv_coords,
+                                    option_materials,
+                                    option_colors,
+                                    False,
+                                    option_flip_yz,
+                                    option_scale,
+                                    False)
+
+                    geo_set.add(name)
+
+        export_scene(scene, filepath, option_flip_yz, option_colors, option_lights, option_cameras, option_embed_meshes, embeds)
+
+    else:
+
+        obj = context.object
+        if not obj:
+            raise Exception("Error, Select 1 active object or select 'export scene'")
+
+        export_mesh(obj, scene, filepath,
+                    option_vertices,
+                    option_vertices_truncate,
+                    option_faces,
+                    option_normals,
+                    option_edges,
+                    option_uv_coords,
+                    option_materials,
+                    option_colors,
+                    align_model,
+                    option_flip_yz,
+                    option_scale,
+                    True)
+
+
+    return {'FINISHED'}

+ 631 - 0
utils/exporters/blender/2.58/scripts/addons/io_mesh_threejs/import_threejs.py

@@ -0,0 +1,631 @@
+# ##### 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 #####
+
+"""
+Blender importer for Three.js (ASCII JSON format).
+
+"""
+
+import os
+import time
+import json
+import bpy
+import mathutils
+from mathutils.geometry import tesselate_polygon
+from io_utils import load_image, unpack_list, unpack_face_list
+
+# #####################################################
+# Generators
+# #####################################################
+def setColor(c, t):
+    c.r = t[0]
+    c.g = t[1]
+    c.b = t[2]
+
+def create_texture(filename, modelpath):
+    name = filename
+    texture = bpy.data.textures.new(name, type='IMAGE')
+    
+    image = load_image(filename, modelpath)
+    has_data = False
+
+    if image:
+        texture.image = image
+        has_data = image.has_data
+    
+    return texture
+    
+def create_materials(data, modelpath):
+    materials = []
+    materials_data = data.get("materials", [])
+    
+    for i, m in enumerate(materials_data):
+
+        name = m.get("DbgName", "material_%d" % i)
+        
+        colorAmbient = m.get("colorAmbient", None)
+        colorDiffuse = m.get("colorDiffuse", None)
+        colorSpecular = m.get("colorSpecular", None)
+        alpha = m.get("transparency", 1.0)
+        specular_hardness = m.get("specularCoef", 0)
+        
+        mapDiffuse = m.get("mapDiffuse", None)
+        mapLightmap = m.get("mapLightmap", None)
+        
+        vertexColorsType = m.get("vertexColors", False)
+        
+        useVertexColors = False
+        if vertexColorsType:
+            useVertexColors = True
+        
+        material = bpy.data.materials.new(name)
+        
+        material.THREE_useVertexColors = useVertexColors
+        
+        if colorDiffuse:
+            setColor(material.diffuse_color, colorDiffuse)
+            material.diffuse_intensity = 1.0
+
+        if colorSpecular:
+            setColor(material.specular_color, colorSpecular)
+            material.specular_intensity = 1.0
+            
+        if alpha < 1.0:
+            material.alpha = alpha
+            material.use_transparency = True
+            
+        if specular_hardness:
+            material.specular_hardness = specular_hardness
+            
+        if mapDiffuse:
+            texture = create_texture(mapDiffuse, modelpath)
+            mtex = material.texture_slots.add()
+            mtex.texture = texture
+            mtex.texture_coords = 'UV'
+            mtex.use = True
+            mtex.use_map_color_diffuse = True
+            
+            material.active_texture = texture
+
+        materials.append(material)
+        
+    return materials
+    
+def create_mesh_object(name, vertices, materials, face_data, flipYZ, recalculate_normals):
+
+    faces         = face_data["faces"]
+    vertexNormals = face_data["vertexNormals"]
+    vertexColors  = face_data["vertexColors"]
+    vertexUVs     = face_data["vertexUVs"]
+    faceMaterials = face_data["materials"]
+    faceColors    = face_data["faceColors"]
+    
+    edges = []
+    
+    # Create a new mesh
+    
+    me = bpy.data.meshes.new(name)
+    me.from_pydata(vertices, edges, faces)
+    
+    # Handle normals
+    
+    if not recalculate_normals:
+        me.update(calc_edges = True)
+    
+    if face_data["hasVertexNormals"]:
+        
+        print("setting vertex normals")
+        
+        for fi in range(len(faces)):
+
+            if vertexNormals[fi]:
+
+                #print("setting face %i with %i vertices" % (fi, len(normals[fi])))
+
+                # if me.update() is called after setting vertex normals
+                # setting face.use_smooth overrides these normals
+                #  - this fixes weird shading artefacts (seems to come from sharing 
+                #    of vertices between faces, didn't find a way how to set vertex normals
+                #    per face use of vertex as opposed to per vertex), 
+                #  - probably this just overrides all custom vertex normals
+                #  - to preserve vertex normals from the original data
+                #    call me.update() before setting them
+                
+                me.faces[fi].use_smooth = True
+                
+                if not recalculate_normals:
+                    for j in range(len(vertexNormals[fi])):
+                        
+                        vertexNormal = vertexNormals[fi][j]
+                    
+                        x = vertexNormal[0]
+                        y = vertexNormal[1]
+                        z = vertexNormal[2]
+                        
+                        if flipYZ:
+                            tmp = y
+                            y = -z
+                            z = tmp
+
+                            # flip normals (this make them look consistent with the original before export)
+
+                            #x = -x
+                            #y = -y
+                            #z = -z
+
+                        vi = me.faces[fi].vertices[j]
+                        
+                        me.vertices[vi].normal.x = x
+                        me.vertices[vi].normal.y = y
+                        me.vertices[vi].normal.z = z
+      
+    if recalculate_normals:
+        me.update(calc_edges = True)
+
+    # Handle colors
+    
+    if face_data["hasVertexColors"]:
+
+        print("setting vertex colors")
+
+        me.vertex_colors.new("vertex_color_layer_0")
+        
+        for fi in range(len(faces)):
+
+            if vertexColors[fi]:
+                
+                face_colors = me.vertex_colors[0].data[fi]
+                face_colors = face_colors.color1, face_colors.color2, face_colors.color3, face_colors.color4
+                
+                for vi in range(len(vertexColors[fi])):
+                    
+                    r = vertexColors[fi][vi][0]
+                    g = vertexColors[fi][vi][1]
+                    b = vertexColors[fi][vi][2]
+                
+                    face_colors[vi].r = r
+                    face_colors[vi].g = g
+                    face_colors[vi].b = b
+                    
+    elif face_data["hasFaceColors"]:
+
+        print("setting vertex colors from face colors")
+
+        me.vertex_colors.new("vertex_color_layer_0")
+        
+        for fi in range(len(faces)):
+
+            if faceColors[fi]:
+
+                r = faceColors[fi][0]
+                g = faceColors[fi][1]
+                b = faceColors[fi][2]
+
+                face_colors = me.vertex_colors[0].data[fi]
+                face_colors = face_colors.color1, face_colors.color2, face_colors.color3, face_colors.color4
+                
+                for vi in range(len(faces[fi])):
+                
+                    face_colors[vi].r = r
+                    face_colors[vi].g = g
+                    face_colors[vi].b = b
+
+    # Handle uvs
+    
+    if face_data["hasVertexUVs"]:
+
+        print("setting vertex uvs")
+        
+        for li, layer in enumerate(vertexUVs):
+
+            me.uv_textures.new("uv_layer_%d" % li)
+
+            for fi in range(len(faces)):
+
+                if layer[fi]:
+                    
+                    uv_face = me.uv_textures[li].data[fi]
+                    face_uvs = uv_face.uv1, uv_face.uv2, uv_face.uv3, uv_face.uv4
+                    
+                    for vi in range(len(layer[fi])):
+                        
+                        u = layer[fi][vi][0]
+                        v = layer[fi][vi][1]
+                    
+                        face_uvs[vi].x = u
+                        face_uvs[vi].y = 1.0 - v
+                        
+                    active_texture = materials[faceMaterials[fi]].active_texture
+                    
+                    if active_texture:
+                        uv_face.use_image = True
+                        uv_face.image = active_texture.image
+
+
+    # Handle materials # 1
+    
+    if face_data["hasMaterials"]:
+        
+
+        print("setting materials (mesh)")
+    
+        for m in materials:
+            
+            me.materials.append(m)
+
+        print("setting materials (faces)")    
+
+        for fi in range(len(faces)):
+            
+            if faceMaterials[fi] >= 0:
+                
+                me.faces[fi].material_index = faceMaterials[fi]
+
+    # Create a new object
+    
+    ob = bpy.data.objects.new(name, me) 
+    ob.data = me                                # link the mesh data to the object
+
+
+    scene = bpy.context.scene                   # get the current scene
+    scene.objects.link(ob)                      # link the object into the scene
+
+    ob.location = scene.cursor_location         # position object at 3d-cursor
+    
+    
+# #####################################################
+# Faces
+# #####################################################
+
+def extract_faces(data):
+    
+    result = {
+    "faces"         : [],
+    "materials"     : [],
+    "faceUVs"       : [],
+    "vertexUVs"     : [],
+    "faceNormals"   : [],
+    "vertexNormals" : [],
+    "faceColors"    : [],
+    "vertexColors"  : [],
+    
+    "hasVertexNormals"  : False,
+    "hasVertexUVs"      : False,
+    "hasVertexColors"   : False,
+    "hasFaceColors"     : False,
+    "hasMaterials"      : False
+    }
+    
+    faces = data.get("faces", [])
+    normals = data.get("normals", [])
+    colors = data.get("colors", [])
+
+    offset = 0
+    zLength = len(faces)
+
+    # disregard empty arrays
+
+    nUvLayers = 0
+
+    for layer in data["uvs"]:
+
+        if len(layer) > 0:
+            nUvLayers += 1
+            result["faceUVs"].append([])
+            result["vertexUVs"].append([])
+
+
+    while ( offset < zLength ):
+
+        type = faces[ offset ]
+        offset += 1
+
+        isQuad          	= isBitSet( type, 0 )
+        hasMaterial         = isBitSet( type, 1 )
+        hasFaceUv           = isBitSet( type, 2 )
+        hasFaceVertexUv     = isBitSet( type, 3 )
+        hasFaceNormal       = isBitSet( type, 4 )
+        hasFaceVertexNormal = isBitSet( type, 5 )
+        hasFaceColor	    = isBitSet( type, 6 )
+        hasFaceVertexColor  = isBitSet( type, 7 )
+
+        #print("type", type, "bits", isQuad, hasMaterial, hasFaceUv, hasFaceVertexUv, hasFaceNormal, hasFaceVertexNormal, hasFaceColor, hasFaceVertexColor)
+
+        result["hasVertexUVs"] = result["hasVertexUVs"] or hasFaceVertexUv
+        result["hasVertexNormals"] = result["hasVertexNormals"] or hasFaceVertexNormal
+        result["hasVertexColors"] = result["hasVertexColors"] or hasFaceVertexColor
+        result["hasFaceColors"] = result["hasFaceColors"] or hasFaceColor
+        result["hasMaterials"] = result["hasMaterials"] or hasMaterial
+        
+        # vertices
+        
+        if isQuad:
+
+            a = faces[ offset ]
+            offset += 1
+            
+            b = faces[ offset ]
+            offset += 1
+            
+            c = faces[ offset ]
+            offset += 1
+            
+            d = faces[ offset ]
+            offset += 1
+            
+            face = [a, b, c, d]
+
+            nVertices = 4
+
+        else:
+
+            a = faces[ offset ]
+            offset += 1
+            
+            b = faces[ offset ]
+            offset += 1
+            
+            c = faces[ offset ]
+            offset += 1
+
+            face = [a, b, c]
+            
+            nVertices = 3
+
+        result["faces"].append(face)
+
+        # material
+        
+        if hasMaterial:
+
+            materialIndex = faces[ offset ]
+            offset += 1
+        
+        else:
+
+            materialIndex = -1
+
+        result["materials"].append(materialIndex)
+
+        # uvs
+
+        for i in range(nUvLayers):
+
+            faceUv = None
+            
+            if hasFaceUv:
+                
+                uvLayer = data["uvs"][ i ]
+
+                uvIndex = faces[ offset ]
+                offset += 1
+
+                u = uvLayer[ uvIndex * 2 ]
+                v = uvLayer[ uvIndex * 2 + 1 ]
+
+                faceUv = [u, v]
+                
+            result["faceUVs"][i].append(faceUv)
+
+
+            if hasFaceVertexUv:
+
+                uvLayer = data["uvs"][ i ]
+
+                vertexUvs = []
+
+                for j in range(nVertices):
+
+                    uvIndex = faces[ offset ]
+                    offset += 1
+
+                    u = uvLayer[ uvIndex * 2 ]
+                    v = uvLayer[ uvIndex * 2 + 1 ]
+
+                    vertexUvs.append([u, v])
+
+            result["vertexUVs"][i].append(vertexUvs)
+
+
+        if hasFaceNormal:
+
+            normalIndex = faces[ offset ] * 3
+            offset += 1
+
+            x = normals[ normalIndex ]
+            y = normals[ normalIndex + 1 ]
+            z = normals[ normalIndex + 2 ]
+
+            faceNormal = [x, y, z]
+            
+        else:
+
+            faceNormal = None
+        
+        result["faceNormals"].append(faceNormal)
+
+
+        if hasFaceVertexNormal:
+
+            vertexNormals = []
+            
+            for j in range(nVertices):
+
+                normalIndex = faces[ offset ] * 3
+                offset += 1
+
+                x = normals[ normalIndex ]
+                y = normals[ normalIndex + 1 ]
+                z = normals[ normalIndex + 2 ]
+                
+                vertexNormals.append( [x, y, z] )
+
+
+        else:
+
+            vertexNormals = None
+        
+        result["vertexNormals"].append(vertexNormals)
+
+
+        if hasFaceColor:
+
+            colorIndex = faces[ offset ]
+            offset += 1
+            
+            faceColor = hexToTuple( colors[ colorIndex ] )
+
+        else:
+            
+            faceColor = None
+
+        result["faceColors"].append(faceColor)
+
+
+        if hasFaceVertexColor:
+
+            vertexColors = []
+
+            for j in range(nVertices):
+
+                colorIndex = faces[ offset ]
+                offset += 1
+
+                color = hexToTuple( colors[ colorIndex ] )
+                vertexColors.append( color )
+
+        else:
+            
+            vertexColors = None
+            
+        result["vertexColors"].append(vertexColors)
+
+
+    return result
+    
+# #####################################################
+# Utils
+# #####################################################
+
+def hexToTuple( hexColor ):
+    r = (( hexColor >> 16 ) & 0xff) / 255.0
+    g = (( hexColor >> 8 ) & 0xff) / 255.0
+    b = ( hexColor & 0xff) / 255.0
+    return (r, g, b)
+    
+def isBitSet(value, position):
+    return value & ( 1 << position )
+    
+def splitArray(data, chunkSize):
+    result = []
+    chunk = []
+    for i in range(len(data)):
+        if i > 0 and i % chunkSize == 0:
+            result.append(chunk)
+            chunk = []
+        chunk.append(data[i])
+    result.append(chunk)
+    return result
+
+
+def extract_json_string(text):
+    marker_begin = "var model ="
+    marker_end = "postMessage"
+    
+    start = text.find(marker_begin) + len(marker_begin)
+    end = text.find(marker_end)
+    end = text.rfind("}", start, end)
+    return text[start:end+1].strip()
+
+def get_name(filepath):
+    return os.path.splitext(os.path.basename(filepath))[0]
+
+def get_path(filepath):
+    return os.path.dirname(filepath)
+
+# #####################################################
+# Parser
+# #####################################################
+
+def load(operator, context, filepath, option_flip_yz = True, recalculate_normals = True):
+    
+    print('\nimporting %r' % filepath)
+
+    time_main = time.time()
+
+    print("\tparsing JSON file...")
+    
+    time_sub = time.time()
+
+    file = open(filepath, 'rU')
+    rawcontent = file.read()
+    file.close()
+
+    json_string = extract_json_string(rawcontent)
+    data = json.loads( json_string )
+    
+    time_new = time.time()
+
+    print('parsing %.4f sec' % (time_new - time_sub))
+    
+    time_sub = time_new
+
+    # flip YZ
+    
+    vertices = splitArray(data["vertices"], 3)
+    
+    if option_flip_yz:
+        vertices[:] = [(v[0], -v[2], v[1]) for v in vertices]        
+
+    # extract faces
+    
+    face_data = extract_faces(data)
+    
+    # deselect all
+
+    bpy.ops.object.select_all(action='DESELECT')
+
+    nfaces = len(face_data["faces"])
+    nvertices = len(vertices)
+    nnormals = len(data.get("normals", [])) / 3
+    ncolors = len(data.get("colors", [])) / 3
+    nuvs = len(data.get("uvs", [])) / 2
+    nmaterials = len(data.get("materials", []))
+    
+    print('\tbuilding geometry...\n\tfaces:%i, vertices:%i, vertex normals: %i, vertex uvs: %i, vertex colors: %i, materials: %i ...' % ( 
+        nfaces, nvertices, nnormals, nuvs, ncolors, nmaterials ))
+
+    # Create materials
+    
+    materials = create_materials(data, get_path(filepath))
+    
+    # Create new obj
+    
+    create_mesh_object(get_name(filepath), vertices, materials, face_data, option_flip_yz, recalculate_normals)
+
+    scene = bpy.context.scene 
+    scene.update()
+
+    time_new = time.time()
+
+    print('finished importing: %r in %.4f sec.' % (filepath, (time_new - time_main)))
+    return {'FINISHED'}
+
+
+if __name__ == "__main__":
+    register()