Sfoglia il codice sorgente

Added experimental Blender exporter for the new ascii JSON format.

Models exported by this version should be loaded like this (the same as for slim OBJ converter):

    var loader = new THREE.Loader();
    loader.loadAscii( "path/to/model/Model.js", function( geometry ) { createScene( geometry ) }, "path/to/model" );

Compared to OBJ converter, currently only single mesh is exported (many models are composed of several meshes).

TODO
    - model alignment
    - copy used images to folder where exported file goes
    - export all selected meshes / all meshes in the scene?
    - binary format
alteredq 15 anni fa
parent
commit
c545318b51

+ 78 - 0
utils/exporters/blender/2.54/scripts/op/io_mesh_threejs_slim/__init__.py

@@ -0,0 +1,78 @@
+# ##### 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 #####
+
+# To support reload properly, try to access a package var, if it's there, reload everything
+if "bpy" in locals():
+    import sys
+    reload(sys.modules.get("io_mesh_threejs_slim.export_threejs_slim", sys))
+
+
+import bpy
+from bpy.props import *
+from io_utils import ExportHelper
+
+
+class ExportTHREEJSSlim(bpy.types.Operator, ExportHelper):
+    '''Export selected object for Three.js (ASCII JSON format).'''
+    bl_idname = "export.threejs_slim"
+    bl_label = "Export Three.js Slim"
+    
+    filename_ext = ".js"
+
+    use_modifiers = BoolProperty(name="Apply Modifiers", description="Apply modifiers to the exported mesh", default=True)
+    use_normals = BoolProperty(name="Normals", description="Export normals", default=True)
+    use_uv_coords = BoolProperty(name="UVs", description="Export texture coordinates", default=True)
+
+    @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")
+
+        filepath = self.filepath
+        import io_mesh_threejs_slim.export_threejs_slim
+        return io_mesh_threejs_slim.export_threejs_slim.save(self, context, **self.properties)
+
+    def draw(self, context):
+        layout = self.layout
+
+        row = layout.row()
+        row.prop(self.properties, "use_modifiers")
+        row.prop(self.properties, "use_normals")
+        row = layout.row()
+        row.prop(self.properties, "use_uv_coords")
+        
+
+def menu_func(self, context):
+    default_path = bpy.data.filepath.replace(".blend", ".js")
+    self.layout.operator(ExportTHREEJSSlim.bl_idname, text="Three.js (.js) Slim").filepath = default_path
+
+
+def register():
+    bpy.types.INFO_MT_file_export.append(menu_func)
+
+
+def unregister():
+    bpy.types.INFO_MT_file_export.remove(menu_func)
+
+if __name__ == "__main__":
+    register()

+ 521 - 0
utils/exporters/blender/2.54/scripts/op/io_mesh_threejs_slim/export_threejs_slim.py

@@ -0,0 +1,521 @@
+# ##### 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 #####
+
+# Based on export_obj.py and export_ply.py
+# Contributors: Mr.doob, Kikko, alteredq
+
+"""
+Blender exporter for Three.js (ASCII JSON format).
+
+TODO
+    - model alignment
+    - copy used images to folder where exported file goes
+    - export all selected meshes
+    - binary format
+"""
+
+import bpy
+import mathutils
+
+import os
+import os.path
+import math
+import operator
+import random
+
+# #####################################################
+# Configuration
+# #####################################################
+ALIGN = "center"    # center bottom top none
+SHADING = "smooth"  # smooth flat 
+TYPE = "ascii"      # ascii binary
+
+# 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
+# #####################################################
+TEMPLATE_FILE_ASCII = """\
+//  vertices: %(nvertex)d
+//  faces: %(nface)d 
+//  materials: %(nmaterial)d
+//
+//  Generated with Blender 2.54 slim exporter
+//  https://github.com/alteredq/three.js/tree/master/utils/exporters/blender/
+
+
+var model = {
+    'materials': [%(materials)s],
+
+    'normals': [%(normals)s],
+
+    'vertices': [%(vertices)s],
+
+    'uvs': [%(uvs)s],
+
+    'triangles': [%(triangles)s],
+    'triangles_n': [%(triangles_n)s],
+    'triangles_uv': [%(triangles_uv)s],
+    'triangles_n_uv': [%(triangles_n_uv)s],
+
+    'quads': [%(quads)s],
+    'quads_n': [%(quads_n)s],
+    'quads_uv': [%(quads_uv)s],
+    'quads_n_uv': [%(quads_n_uv)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"
+
+# #####################################################
+# Utils
+# #####################################################
+def veckey3d(v):
+    return round(v.x, 6), round(v.y, 6), round(v.z, 6)
+
+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:
+        n.append( normals[veckey3d(mv[i].normal)] )
+    return n
+
+def get_uv_indices(f, uvs, mesh):
+    uv = []
+    face_index = f[1]
+    uv_layer = mesh.uv_textures.active.data
+    for i in uv_layer[face_index].uv:
+        uv.append( uvs[veckey2d(i)] )
+    return uv
+
+# #####################################################
+# Elements
+# #####################################################
+def generate_vertex(v):
+    return TEMPLATE_VERTEX % (v.co.x, v.co.y, v.co.z)
+
+def generate_normal(n):
+    return TEMPLATE_N % (n[0], n[1], n[2])
+
+def generate_uv(uv):
+    return TEMPLATE_UV % (uv[0], 1.0 - uv[1])
+
+def generate_triangle(f):
+    v = f[0].vertices
+    m = f[0].material_index
+    return TEMPLATE_TRI % (v[0], v[1], v[2], 
+                           m)
+    
+def generate_quad(f):
+    v = f[0].vertices
+    m = f[0].material_index
+    return TEMPLATE_QUAD % (v[0], v[1], v[2], v[3], 
+                            m)
+    
+def generate_triangle_n(f, normals, mesh):    
+    v = f[0].vertices
+    m = f[0].material_index
+    n = get_normal_indices(v, normals, mesh)
+    
+    return TEMPLATE_TRI_N % (v[0], v[1], v[2], 
+                             m, 
+                             n[0], n[1], n[2])
+    
+def generate_quad_n(f, normals, mesh):    
+    v = f[0].vertices
+    m = f[0].material_index
+    n = get_normal_indices(v, normals, mesh)
+    
+    return TEMPLATE_QUAD_N % (v[0], v[1], v[2], v[3],
+                              m, 
+                              n[0], n[1], n[2], n[3])
+
+def generate_triangle_uv(f, uvs, mesh):
+    v = f[0].vertices  
+    m = f[0].material_index
+    uv = get_uv_indices(f, uvs, mesh)
+    
+    return TEMPLATE_TRI_UV % (v[0], v[1], v[2], 
+                              m, 
+                              uv[0], uv[1], uv[2])
+    
+def generate_quad_uv(f, uvs, mesh):
+    v = f[0].vertices  
+    m = f[0].material_index
+    uv = get_uv_indices(f, uvs, mesh)
+    
+    return TEMPLATE_QUAD_UV % (v[0], v[1], v[2], v[3],
+                               m, 
+                               uv[0], uv[1], uv[2], uv[3])
+                              
+def generate_triangle_n_uv(f, normals, uvs, mesh):
+    v = f[0].vertices    
+    m = f[0].material_index
+    n = get_normal_indices(v, normals, mesh)
+    uv = get_uv_indices(f, uvs, mesh)    
+    
+    return TEMPLATE_TRI_N_UV % (v[0], v[1], v[2], 
+                                m, 
+                                n[0], n[1], n[2], 
+                                uv[0], uv[1], uv[2])
+    
+def generate_quad_n_uv(f, normals, uvs, mesh):
+    v = f[0].vertices    
+    m = f[0].material_index
+    n = get_normal_indices(v, normals, mesh)
+    uv = get_uv_indices(f, uvs, mesh)    
+    
+    return TEMPLATE_QUAD_N_UV % (v[0], v[1], v[2], v[3], 
+                                 m, 
+                                 n[0], n[1], n[2], n[3],
+                                 uv[0], uv[1], uv[2], uv[3])
+                                
+# #####################################################
+# Faces
+# #####################################################
+def sort_faces(faces, use_normals, use_uv_coords):
+    data = {
+    'triangles_flat': [],
+    'triangles_flat_uv': [],
+    'triangles_smooth': [],
+    'triangles_smooth_uv': [],
+    
+    'quads_flat': [],
+    'quads_flat_uv': [],
+    'quads_smooth': [],
+    'quads_smooth_uv': []
+    }
+    
+    for i, f in enumerate(faces):
+        
+        if len(f.vertices) == 3:
+            
+            if use_normals and use_uv_coords:
+                data['triangles_smooth_uv'].append([f,i])
+            elif use_normals and not use_uv_coords:
+                data['triangles_smooth'].append([f,i])
+            elif use_uv_coords:
+                data['triangles_flat_uv'].append([f,i])
+            else:
+                data['triangles_flat'].append([f,i])
+        
+        elif len(f.vertices) == 4:
+            
+            if use_normals and use_uv_coords:
+                data['quads_smooth_uv'].append([f,i])
+            elif use_normals and not use_uv_coords:
+                data['quads_smooth'].append([f,i])
+            elif use_uv_coords:
+                data['quads_flat_uv'].append([f,i])
+            else:
+                data['quads_flat'].append([f,i])
+            
+    return data
+    
+# #####################################################
+# Normals
+# #####################################################
+def extract_vertex_normals(mesh, use_normals):
+    if not use_normals:
+        return {}
+        
+    count = 0
+    normals = {}
+    
+    for f in mesh.faces:
+        for v in f.vertices:
+            key = veckey3d(mesh.vertices[v].normal)
+            if key not in normals:
+                normals[key] = count
+                count += 1
+    
+    return normals
+    
+def generate_normals(normals, use_normals):
+    if not use_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)
+    
+# #####################################################
+# UVs
+# #####################################################
+def extract_uvs(mesh, use_uv_coords):
+    if not use_uv_coords:
+        return {}
+        
+    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
+    
+def generate_uvs(uvs, use_uv_coords):
+    if not use_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)
+    
+# #####################################################
+# 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 generate_mtl(materials):
+    """Generate dummy materials.
+    """
+    
+    mtl = {}
+    for m in materials:
+        index = materials[m]
+        mtl[m] = {
+            'a_dbg_name': m,
+            'a_dbg_index': index,
+            'a_dbg_color': generate_color(index)
+        }
+    return mtl
+
+def value2string(v):
+    if type(v)==str and v[0] != "0":
+        return '"%s"' % v
+    return str(v)
+    
+def generate_materials(mtl, materials):
+    """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]['a_dbg_name'] = m
+        mtl[m]['a_dbg_index'] = index
+        mtl[m]['a_dbg_color'] = generate_color(index)
+        
+        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)])
+        
+def extract_materials(mesh, scene):
+    world = scene.world
+
+    materials = {}
+    for m in mesh.materials:
+        materials[m.name] = {}
+        materials[m.name]['col_diffuse'] = [m.diffuse_intensity * m.diffuse_color[0], 
+                                            m.diffuse_intensity * m.diffuse_color[1], 
+                                            m.diffuse_intensity * m.diffuse_color[2]]
+                                            
+        materials[m.name]['col_specular'] = [m.specular_intensity * m.specular_color[0], 
+                                             m.specular_intensity * m.specular_color[1], 
+                                             m.specular_intensity * m.specular_color[2]]
+                                             
+        materials[m.name]['col_ambient'] = [m.ambient * world.ambient_color[0], 
+                                            m.ambient * world.ambient_color[1], 
+                                            m.ambient * world.ambient_color[2]]
+                                            
+        materials[m.name]['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
+        materials[m.name]["specular_coef"] = m.specular_hardness 
+        
+        if m.active_texture:
+            fn = bpy.path.abspath(m.active_texture.image.filepath)
+            fn = os.path.normpath(fn)
+            fn_strip = os.path.basename(fn)
+            materials[m.name]['map_diffuse'] = fn_strip
+            
+    return materials
+        
+def generate_materials_string(mesh, scene):
+
+    random.seed(42) # to get well defined color order for debug materials
+
+    materials = {}
+    for i, m in enumerate(mesh.materials):
+        materials[m.name] = 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))
+    
+    return generate_materials(mtl, materials)
+        
+# #####################################################
+# ASCII exporter
+# #####################################################
+def generate_ascii_model(mesh, scene, use_normals, use_uv_coords):
+    sfaces = sort_faces(mesh.faces, use_normals, use_uv_coords)
+    
+    normals = extract_vertex_normals(mesh, use_normals)
+    uvs = extract_uvs(mesh, use_uv_coords)
+    
+    text = TEMPLATE_FILE_ASCII % {
+    "vertices"      : ",".join(generate_vertex(v) for v in mesh.vertices),
+    
+    "triangles"     : ",".join(generate_triangle(f) for f in sfaces['triangles_flat']),
+    "triangles_n"   : ",".join(generate_triangle_n(f, normals, mesh) for f in sfaces['triangles_smooth']),
+    "triangles_uv"  : ",".join(generate_triangle_uv(f, uvs, mesh) for f in sfaces['triangles_flat_uv']),
+    "triangles_n_uv": ",".join(generate_triangle_n_uv(f, normals, uvs, mesh) for f in sfaces['triangles_smooth_uv']),
+    
+    "quads"         : ",".join(generate_quad(f) for f in sfaces['quads_flat']),
+    "quads_n"       : ",".join(generate_quad_n(f, normals, mesh) for f in sfaces['quads_smooth']),
+    "quads_uv"      : ",".join(generate_quad_uv(f, uvs, mesh) for f in sfaces['quads_flat_uv']),
+    "quads_n_uv"    : ",".join(generate_quad_n_uv(f, normals, uvs, mesh) for f in sfaces['quads_smooth_uv']),
+    
+    "uvs"           : generate_uvs(uvs, use_uv_coords),
+    "normals"       : generate_normals(normals, use_normals),
+    
+    "materials" : generate_materials_string(mesh, scene),
+    
+    "nvertex"   : len(mesh.vertices),
+    "nface"     : len(mesh.faces),
+    "nmaterial" : 0
+    }
+    
+    return text
+
+# #####################################################
+# Main
+# #####################################################
+def save(operator, context, filepath="", use_modifiers=True, use_normals=True, use_uv_coords=True):
+
+    def rvec3d(v):
+        return round(v[0], 6), round(v[1], 6), round(v[2], 6)
+
+    def rvec2d(v):
+        return round(v[0], 6), round(v[1], 6)
+
+    scene = context.scene
+    obj = context.object
+
+    if not filepath.lower().endswith('.js'):
+        filepath += '.js'
+
+    classname = os.path.basename(filepath).split(".")[0]
+
+    if not obj:
+        raise Exception("Error, Select 1 active object")
+
+    if scene.objects.active:
+        bpy.ops.object.mode_set(mode='OBJECT')
+
+    if use_modifiers:
+        mesh = obj.create_mesh(scene, True, 'PREVIEW')
+    else:
+        mesh = obj.data
+
+    if not mesh:
+        raise Exception("Error, could not get mesh data from active object")
+
+    # that's what Blender's native export_obj.py does
+    x_rot = mathutils.Matrix.Rotation(-math.pi/2, 4, 'X')
+    mesh.transform(x_rot * obj.matrix_world)
+    mesh.calc_normals()
+    
+    faceUV = (len(mesh.uv_textures) > 0)
+    vertexUV = (len(mesh.sticky) > 0)
+
+    if (not faceUV) and (not vertexUV):
+        use_uv_coords = False
+
+    if faceUV:
+        active_uv_layer = mesh.uv_textures.active
+        if not active_uv_layer:
+            use_uv_coords = False
+
+    text = generate_ascii_model(mesh, scene, use_normals, use_uv_coords)
+    file = open(filepath, 'w')
+    file.write(text)
+    file.close()
+
+    print("writing", filepath, "done")
+
+    if use_modifiers:
+        bpy.data.meshes.remove(mesh)
+
+    return {'FINISHED'}