diff --git a/utils/exporters/blender/2.58/scripts/addons/io_mesh_threejs/__init__.py b/utils/exporters/blender/2.58/scripts/addons/io_mesh_threejs/__init__.py index 0b2cd875e1601e0c86268312cb9bddeb58faba6f..c92aaa91778df4f089cc8f69725b6e34f6be63db 100755 --- a/utils/exporters/blender/2.58/scripts/addons/io_mesh_threejs/__init__.py +++ b/utils/exporters/blender/2.58/scripts/addons/io_mesh_threejs/__init__.py @@ -20,13 +20,11 @@ # 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), + "author": "mrdoob, kikko, alteredq, remoe, pxf", + "version": (1, 0, 2), "blender": (2, 5, 7), "api": 35622, "location": "File > Import-Export", @@ -36,6 +34,11 @@ bl_info = { "tracker_url": "https://github.com/mrdoob/three.js/issues", "category": "Import-Export"} +# To support reload properly, try to access a package var, +# if it's there, reload everything + +import bpy + if "bpy" in locals(): import imp if "export_threejs" in locals(): @@ -43,7 +46,6 @@ if "bpy" in locals(): if "import_threejs" in locals(): imp.reload(import_threejs) -import bpy from bpy.props import * from bpy_extras.io_utils import ExportHelper, ImportHelper @@ -175,7 +177,9 @@ def save_settings_export(properties): settings = { "option_export_scene" : properties.option_export_scene, "option_embed_meshes" : properties.option_embed_meshes, - + "option_url_base_html" : properties.option_url_base_html, + "option_copy_textures" : properties.option_copy_textures, + "option_lights" : properties.option_lights, "option_cameras" : properties.option_cameras, @@ -225,6 +229,8 @@ def restore_settings_export(properties): properties.option_export_scene = settings.get("option_export_scene", False) properties.option_embed_meshes = settings.get("option_embed_meshes", True) + properties.option_url_base_html = settings.get("option_url_base_html", False) + properties.option_copy_textures = settings.get("option_copy_textures", False) properties.option_lights = settings.get("option_lights", False) properties.option_cameras = settings.get("option_cameras", False) @@ -263,7 +269,9 @@ class ExportTHREEJS(bpy.types.Operator, ExportHelper): option_export_scene = BoolProperty(name = "Scene", description = "Export scene", default = False) option_embed_meshes = BoolProperty(name = "Embed", description = "Embed meshes", default = True) - + option_copy_textures = BoolProperty(name = "Copy textures", description = "Copy textures", default = False) + option_url_base_html = BoolProperty(name = "HTML as url base", description = "Use HTML as url base ", default = False) + option_lights = BoolProperty(name = "Lights", description = "Export default scene lights", default = False) option_cameras = BoolProperty(name = "Cameras", description = "Export default scene cameras", default = False) @@ -284,6 +292,7 @@ class ExportTHREEJS(bpy.types.Operator, ExportHelper): 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) @@ -341,11 +350,18 @@ class ExportTHREEJS(bpy.types.Operator, ExportHelper): row = layout.row() row.prop(self.properties, "option_export_scene") + + row = layout.row() row.prop(self.properties, "option_lights") row.prop(self.properties, "option_cameras") row = layout.row() row.prop(self.properties, "option_embed_meshes") + row.prop(self.properties, "option_copy_textures") + + row = layout.row() + row.prop(self.properties, "option_url_base_html") + layout.separator() diff --git a/utils/exporters/blender/2.58/scripts/addons/io_mesh_threejs/export_threejs.py b/utils/exporters/blender/2.58/scripts/addons/io_mesh_threejs/export_threejs.py index 24e5b66fa03ddf8a6220849e104e9215b96939e8..153ade43da33f91a25ddc7b170cc796d35806ce4 100644 --- a/utils/exporters/blender/2.58/scripts/addons/io_mesh_threejs/export_threejs.py +++ b/utils/exporters/blender/2.58/scripts/addons/io_mesh_threejs/export_threejs.py @@ -20,13 +20,13 @@ 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 shutil import os import os.path import math @@ -79,7 +79,7 @@ COLORS = [0xeeeeee, 0xee0000, 0x00ee00, 0x0000ee, 0xeeee00, 0x00eeee, 0xee00ee] TEMPLATE_SCENE_ASCII = """\ /* Converted from: %(fname)s * - * File generated with Blender 2.57 Exporter + * File generated with Blender 2.58 Exporter * https://github.com/mrdoob/three.js/tree/master/utils/exporters/blender/ * * objects: %(nobjects)s @@ -91,7 +91,7 @@ TEMPLATE_SCENE_ASCII = """\ var scene = { "type" : "scene", -"urlBaseType" : "relativeToScene", +"urlBaseType" : %(basetype)s, %(sections)s @@ -222,7 +222,7 @@ TEMPLATE_HEX = "0x%06x" TEMPLATE_FILE_ASCII = """\ /* - * File generated with Blender 2.57 Exporter + * File generated with Blender 2.58 Exporter * https://github.com/mrdoob/three.js/tree/master/utils/exporters/blender/ * * vertices: %(nvertex)d @@ -693,6 +693,8 @@ def value2string(v): return '"%s"' % v elif type(v) == bool: return str(v).lower() + elif type(v) == list: + return "[%s]" % (", ".join(value2string(x) for x in v)) return str(v) def generate_materials(mtl, materials, draw_type): @@ -721,7 +723,7 @@ def generate_materials(mtl, materials, draw_type): return ",\n\n".join([m for i,m in sorted(mtl_array)]), len(mtl_array) -def extract_materials(mesh, scene, option_colors): +def extract_materials(mesh, scene, option_colors, option_copy_textures, filepath): world = scene.world materials = {} @@ -749,22 +751,16 @@ def extract_materials(mesh, scene, option_colors): material['transparency'] = m.alpha # not sure about mapping values to Blinn-Phong shader - # Blender uses INT from [1,511] with default 0 + # 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 + textures = guess_material_textures(m) - 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] + handle_texture('diffuse', textures, material, filepath, option_copy_textures) + handle_texture('light', textures, material, filepath, option_copy_textures) + handle_texture('normal', textures, material, filepath, option_copy_textures) material["vertexColors"] = m.THREE_useVertexColors and option_colors @@ -779,7 +775,7 @@ def extract_materials(mesh, scene, option_colors): return materials -def generate_materials_string(mesh, scene, option_colors, draw_type): +def generate_materials_string(mesh, scene, option_colors, draw_type, option_copy_textures, filepath): random.seed(42) # to get well defined color order for debug materials @@ -801,10 +797,45 @@ def generate_materials_string(mesh, scene, option_colors, draw_type): # extract real materials from the mesh - mtl.update(extract_materials(mesh, scene, option_colors)) + mtl.update(extract_materials(mesh, scene, option_colors, option_copy_textures, filepath)) return generate_materials(mtl, materials, draw_type) +def handle_texture(id, textures, material, filepath, option_copy_textures): + + if textures[id]: + texName = 'map%s' % id.capitalize() + repeatName = 'map%sRepeat' % id.capitalize() + wrapName = 'map%sWrap' % id.capitalize() + + slot = textures[id]['slot'] + texture = textures[id]['texture'] + image = texture.image + fname = extract_texture_filename(image) + material[texName] = fname + + if option_copy_textures: + save_image(image, fname, filepath) + + if texture.repeat_x != 1 or texture.repeat_y != 1: + material[repeatName] = [texture.repeat_x, texture.repeat_y] + + if texture.extension == "REPEAT": + wrap_x = "repeat" + wrap_y = "repeat" + + if texture.use_mirror_x: + wrap_x = "mirror" + if texture.use_mirror_y: + wrap_y = "mirror" + + material[wrapName] = [wrap_x, wrap_y] + + if slot.use_map_normal: + if slot.normal_factor != 1.0: + material['mapNormalFactor'] = slot.normal_factor + + # ##################################################### # ASCII model generator # ##################################################### @@ -821,7 +852,9 @@ def generate_ascii_model(mesh, scene, align_model, flipyz, option_scale, - draw_type): + draw_type, + option_copy_textures, + filepath): vertices = mesh.vertices[:] @@ -843,7 +876,7 @@ def generate_ascii_model(mesh, scene, nedges = 0 if option_materials: - materials_string, nmaterial = generate_materials_string(mesh, scene, option_colors, draw_type) + materials_string, nmaterial = generate_materials_string(mesh, scene, option_colors, draw_type, option_copy_textures, filepath) if option_edges: nedges = len(mesh.edges) @@ -873,7 +906,7 @@ def generate_ascii_model(mesh, scene, "ncolor" : ncolor, "nmaterial" : nmaterial, "nedges" : nedges, - + "model" : model_string } @@ -897,7 +930,9 @@ def generate_mesh_string(obj, scene, align_model, flipyz, option_scale, - export_single_model): + export_single_model, + option_copy_textures, + filepath): # collapse modifiers into mesh @@ -937,6 +972,10 @@ def generate_mesh_string(obj, scene, if not active_col_layer: option_colors = False + option_copy_textures_model = False + if export_single_model and option_copy_textures: + option_copy_textures_model = True + text, model_string = generate_ascii_model(mesh, scene, option_vertices, option_vertices_truncate, @@ -949,11 +988,13 @@ def generate_mesh_string(obj, scene, align_model, flipyz, option_scale, - obj.draw_type) + obj.draw_type, + option_copy_textures_model, + filepath) # remove temp mesh bpy.data.meshes.remove(mesh) - + return text, model_string def export_mesh(obj, scene, filepath, @@ -968,7 +1009,8 @@ def export_mesh(obj, scene, filepath, align_model, flipyz, option_scale, - export_single_model): + export_single_model, + option_copy_textures): """Export single mesh""" @@ -984,7 +1026,9 @@ def export_mesh(obj, scene, filepath, align_model, flipyz, option_scale, - export_single_model) + export_single_model, + option_copy_textures, + filepath) write_file(filepath, text) @@ -1085,7 +1129,7 @@ def generate_objects(data): visible = False geometry_string = generate_string(geometry_id) - + object_string = TEMPLATE_OBJECT % { "object_id" : generate_string(object_id), "geometry_id" : geometry_string, @@ -1103,7 +1147,7 @@ def generate_objects(data): "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 @@ -1117,7 +1161,7 @@ def generate_objects(data): 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, @@ -1153,7 +1197,7 @@ def generate_geometries(data): if name not in geo_set: geometry_id = "geo_%s" % name - + if data["embed_meshes"]: embed_id = "emb_%s" % name @@ -1195,10 +1239,25 @@ def generate_textures_scene(data): texture_id = img.name texture_file = extract_texture_filename(img) - + + if data["copy_textures"]: + save_image(img, texture_file, data["filepath"]) + extras = "" + if texture.repeat_x != 1 or texture.repeat_y != 1: - extras = ',\n "repeat": [%f, %f]' % (texture.repeat_x, texture.repeat_y) + extras += ',\n "repeat": [%f, %f]' % (texture.repeat_x, texture.repeat_y) + + if texture.extension == "REPEAT": + wrap_x = "repeat" + wrap_y = "repeat" + + if texture.use_mirror_x: + wrap_x = "mirror" + if texture.use_mirror_y: + wrap_y = "mirror" + + extras += ',\n "wrap": ["%s", "%s"]' % (wrap_x, wrap_y) texture_string = TEMPLATE_TEXTURE % { "texture_id" : generate_string(texture_id), @@ -1215,6 +1274,19 @@ def extract_texture_filename(image): fn_strip = os.path.basename(fn) return fn_strip +def save_image(img, name, fpath): + dst_dir = os.path.dirname(fpath) + dst_path = os.path.join(dst_dir, name) + + ensure_folder_exist(dst_dir) + + if img.packed_file: + img.save_render(dst_path) + + else: + src_path = bpy.path.abspath(img.filepath) + shutil.copy(src_path, dst_dir) + # ##################################################### # Scene exporter - materials # ##################################################### @@ -1248,38 +1320,60 @@ def extract_material_data(m, option_colors): material["specularCoef"] = m.specular_hardness + material["vertexColors"] = m.THREE_useVertexColors and option_colors + material['mapDiffuse'] = "" material['mapLight'] = "" material['mapNormal'] = "" + material['mapNormalFactor'] = 1.0 + + textures = guess_material_textures(m) + + if textures['diffuse']: + material['mapDiffuse'] = textures['diffuse']['texture'].image.name + + if textures['light']: + material['mapLight'] = textures['light']['texture'].image.name + + if textures['normal']: + material['mapNormal'] = textures['normal']['texture'].image.name + if textures['normal']['slot'].use_map_normal: + material['mapNormalFactor'] = textures['normal']['slot'].normal_factor + + material['shading'] = m.THREE_materialType + + return material + +def guess_material_textures(material): + textures = { + 'diffuse' : None, + 'light' : None, + 'normal' : None + } - material["vertexColors"] = m.THREE_useVertexColors and option_colors - # just take first textures of each, for the moment three.js materials can't handle more + # assume diffuse comes before lightmap, normalmap has checked flag - 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 + for i in range(len(material.texture_slots)): + slot = material.texture_slots[i] + if slot: + texture = slot.texture + if slot.use and texture.type == 'IMAGE': - if t.use_normal_map: - material['mapNormal'] = name + if texture.use_normal_map: + textures['normal'] = { "texture": texture, "slot": slot } else: - - if not material['mapDiffuse']: - material['mapDiffuse'] = name + if not textures['diffuse']: + textures['diffuse'] = { "texture": texture, "slot": slot } else: - material['mapLight'] = name + textures['light'] = { "texture": texture, "slot": slot } - if material['mapDiffuse'] and material['mapNormal'] and material['mapLight']: + if textures['diffuse'] and textures['normal'] and textures['light']: break - material['shading'] = m.THREE_materialType - - return material + return textures def generate_material_string(material): type_map = { @@ -1302,6 +1396,7 @@ def generate_material_string(material): colorMap = material['mapDiffuse'] lightMap = material['mapLight'] normalMap = material['mapNormal'] + normalMapFactor = material['mapNormalFactor'] if colorMap: parameters += ', "map": %s' % generate_string(colorMap) @@ -1309,10 +1404,12 @@ def generate_material_string(material): parameters += ', "lightMap": %s' % generate_string(lightMap) if normalMap: parameters += ', "normalMap": %s' % generate_string(normalMap) + if normalMapFactor != 1.0: + parameters += ', "normalMapFactor": %f' % normalMapFactor if material['vertexColors']: parameters += ', "vertexColors": "vertex"' - + material_string = TEMPLATE_MATERIAL_SCENE % { "material_id" : generate_string(material_id), "type" : generate_string(material_type), @@ -1340,14 +1437,13 @@ def generate_materials_scene(data): def generate_cameras(data): if data["use_cameras"]: - cameras = data.get("cameras", []) - - if not cameras: - cameras.append(DEFAULTS["camera"]) + cams = bpy.data.objects + cams = [ob for ob in cams if (ob.type == 'CAMERA' and ob.select)] chunks = [] - for camera in cameras: + if not cams: + camera = DEFAULTS["camera"] if camera["type"] == "perspective": @@ -1377,6 +1473,29 @@ def generate_cameras(data): chunks.append(camera_string) + else: + + for cameraobj in cams: + camera = bpy.data.cameras[cameraobj.name] + + # TODO: + # Support more than perspective camera + # Calculate a target/lookat + # Get correct aspect ratio + if camera.id_data.type == "PERSP": + + camera_string = TEMPLATE_CAMERA_PERSPECTIVE % { + "camera_id" : generate_string(camera.name), + "fov" : (camera.angle / 3.14) * 180.0, + "aspect" : 1.333, + "near" : camera.clip_start, + "far" : camera.clip_end, + "position" : generate_vec3([cameraobj.location[0], -cameraobj.location[1], cameraobj.location[2]]), + "target" : generate_vec3([0, 0, 0]) + } + + chunks.append(camera_string) + return ",\n\n".join(chunks) return "" @@ -1417,7 +1536,7 @@ def generate_lights(data): chunks.append(light_string) return ",\n\n".join(chunks) - + return "" # ##################################################### @@ -1425,20 +1544,20 @@ def generate_lights(data): # ##################################################### 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 # ##################################################### @@ -1455,6 +1574,13 @@ def generate_ascii_scene(data): embeds = generate_embeds(data) + basetype = "relativeTo" + + if data["base_html"]: + basetype += "HTML" + else: + basetype += "Scene" + sections = [ ["objects", objects], ["geometries", geometries], @@ -1474,7 +1600,11 @@ def generate_ascii_scene(data): default_camera = "" if data["use_cameras"]: - default_camera = "default_camera" + cams = [ob for ob in bpy.data.objects if (ob.type == 'CAMERA' and ob.select)] + if not cams: + default_camera = "default_camera" + else: + default_camera = cams[0].name parameters = { "fname" : data["source_file"], @@ -1488,6 +1618,7 @@ def generate_ascii_scene(data): "nobjects" : nobjects, "ngeometries" : ngeometries, "ntextures" : ntextures, + "basetype" : generate_string(basetype), "nmaterials" : nmaterials, "position" : generate_vec3(DEFAULTS["position"]), @@ -1499,22 +1630,24 @@ def generate_ascii_scene(data): return text -def export_scene(scene, filepath, flipyz, option_colors, option_lights, option_cameras, option_embed_meshes, embeds): +def export_scene(scene, filepath, flipyz, option_colors, option_lights, option_cameras, option_embed_meshes, embeds, option_url_base_html, option_copy_textures): 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" : 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, + "base_html" : option_url_base_html, + "copy_textures": option_copy_textures } scene_text += generate_ascii_scene(data) @@ -1539,7 +1672,11 @@ def save(operator, context, filepath = "", option_lights = False, option_cameras = False, option_scale = 1.0, - option_embed_meshes = True): + option_embed_meshes = True, + option_url_base_html = False, + option_copy_textures = False): + + #print("URL TYPE", option_url_base_html) filepath = ensure_extension(filepath, '.js') @@ -1568,9 +1705,9 @@ def save(operator, context, filepath = "", 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, @@ -1580,17 +1717,20 @@ def save(operator, context, filepath = "", option_uv_coords, option_materials, option_colors, - False, + False, # align_model option_flip_yz, option_scale, - False) - + False, # export_single_model + False, # option_copy_textures + filepath) + embeds[name] = model_string else: fname = generate_mesh_filename(name, filepath) - export_mesh(obj, scene, fname, + export_mesh(obj, scene, + fname, option_vertices, option_vertices_truncate, option_faces, @@ -1599,14 +1739,23 @@ def save(operator, context, filepath = "", option_uv_coords, option_materials, option_colors, - False, + False, # align_model option_flip_yz, option_scale, - False) + False, # export_single_model + option_copy_textures) geo_set.add(name) - export_scene(scene, filepath, option_flip_yz, option_colors, option_lights, option_cameras, option_embed_meshes, embeds) + export_scene(scene, filepath, + option_flip_yz, + option_colors, + option_lights, + option_cameras, + option_embed_meshes, + embeds, + option_url_base_html, + option_copy_textures) else: @@ -1626,7 +1775,7 @@ def save(operator, context, filepath = "", align_model, option_flip_yz, option_scale, - True) - + True, # export_single_model + option_copy_textures) return {'FINISHED'}