diff --git a/utils/exporters/blender/2.57/scripts/addons/io_mesh_threejs/__init__.py b/utils/exporters/blender/2.57/scripts/addons/io_mesh_threejs/__init__.py new file mode 100644 index 00000000..de44df40 --- /dev/null +++ b/utils/exporters/blender/2.57/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", + "version": (1, 0), + "blender": (2, 5, 7), + "api": 35622, + "location": "File > Import-Export > three.js", + "description": "Import-Export THREEjs meshes", + "warning": "", + "tracker_url": "", + "support": 'OFFICIAL', + "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() diff --git a/utils/exporters/blender/2.57/scripts/addons/io_mesh_threejs/export_threejs.py b/utils/exporters/blender/2.57/scripts/addons/io_mesh_threejs/export_threejs.py new file mode 100644 index 00000000..794bd89a --- /dev/null +++ b/utils/exporters/blender/2.57/scripts/addons/io_mesh_threejs/export_threejs.py @@ -0,0 +1,1623 @@ +# ##### 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 + - export scene + - 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.56 Exporter + * https://github.com/alteredq/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 + }""" + +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.56 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 + + if m.active_texture and m.active_texture.type == 'IMAGE' and m.active_texture.image: + fn = bpy.path.abspath(m.active_texture.image.filepath) + fn = os.path.normpath(fn) + fn_strip = os.path.basename(fn) + material['mapDiffuse'] = fn_strip + + 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 img in bpy.data.images: + + texture_id = img.name + texture_file = extract_texture_filename(img) + + texture_string = TEMPLATE_TEXTURE % { + "texture_id" : generate_string(texture_id), + "texture_file" : generate_string(texture_file) + } + chunks.append(texture_string) + + return ",\n\n".join(chunks), 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 + + #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 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 = generate_string("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'} diff --git a/utils/exporters/blender/2.57/scripts/addons/io_mesh_threejs/import_threejs.py b/utils/exporters/blender/2.57/scripts/addons/io_mesh_threejs/import_threejs.py new file mode 100644 index 00000000..79708689 --- /dev/null +++ b/utils/exporters/blender/2.57/scripts/addons/io_mesh_threejs/import_threejs.py @@ -0,0 +1,635 @@ +# ##### 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 import_obj.py +# Contributors: alteredq + + +""" +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()