Skip to content

Instantly share code, notes, and snippets.

@kontan
Created January 8, 2013 15:31
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save kontan/4484676 to your computer and use it in GitHub Desktop.
Save kontan/4484676 to your computer and use it in GitHub Desktop.
Blender JSON exporter that supported multi action
# ##### 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
# ################################################################
bl_info = {
"name": "three.js format",
"author": "mrdoob, kikko, alteredq, remoe, pxf, n3tfr34k",
"version": (1, 4, 0),
"blender": (2, 6, 4),
"api": 35622,
"location": "File > Import-Export",
"description": "Import-Export three.js meshes",
"warning": "",
"wiki_url": "https://github.com/mrdoob/three.js/tree/master/utils/exporters/blender",
"tracker_url": "https://github.com/mrdoob/three.js/issues",
"category": "Import-Export"}
# 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():
imp.reload(export_threejs)
if "import_threejs" in locals():
imp.reload(import_threejs)
from bpy.props import *
from bpy_extras.io_utils import ExportHelper, ImportHelper
# ################################################################
# Custom properties
# ################################################################
bpy.types.Object.THREE_castShadow = bpy.props.BoolProperty()
bpy.types.Object.THREE_receiveShadow = bpy.props.BoolProperty()
bpy.types.Object.THREE_doubleSided = bpy.props.BoolProperty()
bpy.types.Object.THREE_exportGeometry = bpy.props.BoolProperty(default = True)
bpy.types.Material.THREE_useVertexColors = bpy.props.BoolProperty()
bpy.types.Material.THREE_depthWrite = bpy.props.BoolProperty(default = True)
bpy.types.Material.THREE_depthTest = bpy.props.BoolProperty(default = True)
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")
THREE_blending_types = [("NoBlending", "NoBlending", "NoBlending"), ("NormalBlending", "NormalBlending", "NormalBlending"),
("AdditiveBlending", "AdditiveBlending", "AdditiveBlending"), ("SubtractiveBlending", "SubtractiveBlending", "SubtractiveBlending"),
("MultiplyBlending", "MultiplyBlending", "MultiplyBlending"), ("AdditiveAlphaBlending", "AdditiveAlphaBlending", "AdditiveAlphaBlending")]
bpy.types.Material.THREE_blendingType = EnumProperty(name = "Blending type", description = "Blending type", items = THREE_blending_types, default = "NormalBlending")
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_castShadow", text="Casts shadow" )
row = layout.row()
row.prop( obj, "THREE_receiveShadow", text="Receives shadow" )
row = layout.row()
row.prop( obj, "THREE_doubleSided", text="Double sided" )
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_blendingType", text="Blending type" )
row = layout.row()
row.prop( mat, "THREE_useVertexColors", text="Use vertex colors" )
row = layout.row()
row.prop( mat, "THREE_depthWrite", text="Enable depth writing" )
row = layout.row()
row.prop( mat, "THREE_depthTest", text="Enable depth testing" )
# ################################################################
# 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)
option_worker = BoolProperty(name="Worker", description="Old format using workers", default=False)
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")
row = layout.row()
row.prop(self.properties, "option_worker")
# ################################################################
# 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_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,
"option_animation_morph" : properties.option_animation_morph,
"option_animation_skeletal" : properties.option_animation_skeletal,
"option_frame_step" : properties.option_frame_step,
"option_all_meshes" : properties.option_all_meshes,
"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_faces" : properties.option_faces,
"option_vertices" : properties.option_vertices,
"option_skinning" : properties.option_skinning,
"option_bones" : properties.option_bones,
"option_vertices_truncate" : properties.option_vertices_truncate,
"option_scale" : properties.option_scale,
"align_model" : properties.align_model,
"option_all_animation" : properties.option_all_animations
}
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_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.option_skinning = settings.get("option_skinning", True)
properties.option_bones = settings.get("option_bones", 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_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)
properties.option_animation_morph = settings.get("option_animation_morph", False)
properties.option_animation_skeletal = settings.get("option_animation_skeletal", False)
properties.option_frame_step = settings.get("option_frame_step", 1)
properties.option_all_meshes = settings.get("option_all_meshes", True)
properties.option_all_animations = settings.get("option_all_animations", True)
# ################################################################
# 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_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)
option_skinning = BoolProperty(name = "Skinning", description = "Export skin data", default = True)
option_bones = BoolProperty(name = "Bones", description = "Export bones", 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 meshes", 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)
option_animation_morph = BoolProperty(name = "Morph animation", description = "Export animation (morphs)", default = False)
option_animation_skeletal = BoolProperty(name = "Skeletal animation", description = "Export animation (skeletal)", default = False)
option_frame_step = IntProperty(name = "Frame step", description = "Animation frame step", min = 1, max = 1000, soft_min = 1, soft_max = 1000, default = 1)
option_all_meshes = BoolProperty(name = "All meshes", description = "All meshes (merged)", default = True)
option_all_animations = BoolProperty(name = "All animations", description = "All animations", default = True)
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_bones")
row.prop(self.properties, "option_skinning")
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="--------- Experimental ---------")
layout.separator()
row = layout.row()
row.label(text="Scene:")
row = layout.row()
row.prop(self.properties, "option_export_scene")
row.prop(self.properties, "option_embed_meshes")
row = layout.row()
row.prop(self.properties, "option_lights")
row.prop(self.properties, "option_cameras")
layout.separator()
row = layout.row()
row.label(text="Animation:")
row = layout.row()
row.prop(self.properties, "option_animation_morph")
row = layout.row()
row.prop(self.properties, "option_animation_skeletal")
row = layout.row()
row.prop(self.properties, "option_frame_step")
row = layout.row()
row.prop(self.properties, "option_all_animations")
layout.separator()
row = layout.row()
row.label(text="Settings:")
row = layout.row()
row.prop(self.properties, "option_all_meshes")
row = layout.row()
row.prop(self.properties, "option_copy_textures")
row = layout.row()
row.prop(self.properties, "option_url_base_html")
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()
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
"""
Blender exporter for Three.js (ASCII JSON format).
TODO
- binary format
"""
import bpy
import mathutils
import shutil
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" : "PerspectiveCamera",
"near" : 1,
"far" : 10000,
"fov" : 60,
"aspect": 1.333,
"position" : [0, 0, 10],
"target" : [0, 0, 0]
},
"light" :
{
"name" : "default_light",
"type" : "DirectionalLight",
"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]
# skinning
MAX_INFLUENCES = 2
# #####################################################
# Templates - scene
# #####################################################
TEMPLATE_SCENE_ASCII = """\
{
"metadata" :
{
"formatVersion" : 3.2,
"type" : "scene",
"sourceFile" : "%(fname)s",
"generatedBy" : "Blender 2.64 Exporter",
"objects" : %(nobjects)s,
"geometries" : %(ngeometries)s,
"materials" : %(nmaterials)s,
"textures" : %(ntextures)s
},
"urlBaseType" : %(basetype)s,
%(sections)s
"transform" :
{
"position" : %(position)s,
"rotation" : %(rotation)s,
"scale" : %(scale)s
},
"defaults" :
{
"bgcolor" : %(bgcolor)s,
"bgalpha" : %(bgalpha)f,
"camera" : %(defcamera)s
}
}
"""
TEMPLATE_SECTION = """
"%s" :
{
%s
},
"""
TEMPLATE_OBJECT = """\
%(object_id)s : {
"geometry" : %(geometry_id)s,
"groups" : [ %(group_id)s ],
"material" : %(material_id)s,
"position" : %(position)s,
"rotation" : %(rotation)s,
"quaternion": %(quaternion)s,
"scale" : %(scale)s,
"visible" : %(visible)s,
"castShadow" : %(castShadow)s,
"receiveShadow" : %(receiveShadow)s,
"doubleSided" : %(doubleSided)s
}"""
TEMPLATE_EMPTY = """\
%(object_id)s : {
"groups" : [ %(group_id)s ],
"position" : %(position)s,
"rotation" : %(rotation)s,
"quaternion": %(quaternion)s,
"scale" : %(scale)s
}"""
TEMPLATE_GEOMETRY_LINK = """\
%(geometry_id)s : {
"type" : "ascii",
"url" : %(model_file)s
}"""
TEMPLATE_GEOMETRY_EMBED = """\
%(geometry_id)s : {
"type" : "embedded",
"id" : %(embed_id)s
}"""
TEMPLATE_TEXTURE = """\
%(texture_id)s : {
"url": %(texture_file)s%(extras)s
}"""
TEMPLATE_MATERIAL_SCENE = """\
%(material_id)s : {
"type": %(type)s,
"parameters": { %(parameters)s }
}"""
TEMPLATE_CAMERA_PERSPECTIVE = """\
%(camera_id)s : {
"type" : "PerspectiveCamera",
"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" : "OrthographicCamera",
"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" : "DirectionalLight",
"direction" : %(direction)s,
"color" : %(color)d,
"intensity" : %(intensity).2f
}"""
TEMPLATE_LIGHT_POINT = """\
%(light_id)s : {
"type" : "PointLight",
"position" : %(position)s,
"color" : %(color)d,
"intensity" : %(intensity).3f
}"""
TEMPLATE_VEC4 = '[ %g, %g, %g, %g ]'
TEMPLATE_VEC3 = '[ %g, %g, %g ]'
TEMPLATE_VEC2 = '[ %g, %g ]'
TEMPLATE_STRING = '"%s"'
TEMPLATE_HEX = "0x%06x"
# #####################################################
# Templates - model
# #####################################################
TEMPLATE_FILE_ASCII = """\
{
"metadata" :
{
"formatVersion" : 3.1,
"generatedBy" : "Blender 2.64 Exporter",
"vertices" : %(nvertex)d,
"faces" : %(nface)d,
"normals" : %(nnormal)d,
"colors" : %(ncolor)d,
"uvs" : [%(nuvs)s],
"materials" : %(nmaterial)d,
"morphTargets" : %(nmorphTarget)d,
"bones" : %(nbone)d
},
%(model)s
}
"""
TEMPLATE_MODEL_ASCII = """\
"scale" : %(scale)f,
"materials" : [%(materials)s],
"vertices" : [%(vertices)s],
"morphTargets" : [%(morphTargets)s],
"normals" : [%(normals)s],
"colors" : [%(colors)s],
"uvs" : [%(uvs)s],
"faces" : [%(faces)s],
"bones" : [%(bones)s],
"skinIndices" : [%(indices)s],
"skinWeights" : [%(weights)s],
"animation" : {%(animation)s},
"animations" : [%(animations)s]
"""
TEMPLATE_VERTEX = "%g,%g,%g"
TEMPLATE_VERTEX_TRUNCATE = "%d,%d,%d"
TEMPLATE_N = "%g,%g,%g"
TEMPLATE_UV = "%g,%g"
TEMPLATE_C = "%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_faces(obj):
if hasattr(obj, "tessfaces"):
return obj.tessfaces
else:
return obj.faces
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, layer_index):
uv = []
uv_layer = mesh.tessface_uv_textures[layer_index].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.tessface_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])
return [-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])
return [-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])
return [-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], uv[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, uv_layers, colors, meshes, option_normals, option_colors, option_uv_coords, option_materials, option_faces):
if not option_faces:
return "", 0
vertex_offset = 0
material_offset = 0
chunks = []
for mesh, object in meshes:
vertexUV = len(mesh.uv_textures) > 0
vertexColors = len(mesh.vertex_colors) > 0
mesh_colors = option_colors and vertexColors
mesh_uvs = option_uv_coords and vertexUV
if vertexUV:
active_uv_layer = mesh.uv_textures.active
if not active_uv_layer:
mesh_extract_uvs = False
if vertexColors:
active_col_layer = mesh.vertex_colors.active
if not active_col_layer:
mesh_extract_colors = False
for i, f in enumerate(get_faces(mesh)):
face = generate_face(f, i, normals, uv_layers, colors, mesh, option_normals, mesh_colors, mesh_uvs, option_materials, vertex_offset, material_offset)
chunks.append(face)
vertex_offset += len(mesh.vertices)
material_count = len(mesh.materials)
if material_count == 0:
material_count = 1
material_offset += material_count
return ",".join(chunks), len(chunks)
def generate_face(f, faceIndex, normals, uv_layers, colors, mesh, option_normals, option_colors, option_uv_coords, option_materials, vertex_offset, material_offset):
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] + vertex_offset
faceData.append(index)
if hasMaterial:
index = f.material_index + material_offset
faceData.append( index )
if hasFaceVertexUvs:
for layer_index, uvs in enumerate(uv_layers):
uv = get_uv_indices(faceIndex, uvs, mesh, layer_index)
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, normals, count):
for f in get_faces(mesh):
for v in f.vertices:
normal = mesh.vertices[v].normal
key = veckey3d(normal)
if key not in normals:
normals[key] = count
count += 1
return 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, colors, count):
color_layer = mesh.tessface_vertex_colors.active.data
for face_index, face in enumerate(get_faces(mesh)):
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 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, uv_layers, counts):
for index, layer in enumerate(mesh.tessface_uv_textures):
if len(uv_layers) <= index:
uvs = {}
count = 0
uv_layers.append(uvs)
counts.append(count)
else:
uvs = uv_layers[index]
count = counts[index]
uv_layer = layer.data
for face_index, face in enumerate(get_faces(mesh)):
for uv_index, uv in enumerate(uv_layer[face_index].uv):
key = veckey2d(uv)
if key not in uvs:
uvs[key] = count
count += 1
counts[index] = count
return counts
def generate_uvs(uv_layers, option_uv_coords):
if not option_uv_coords:
return "[]"
layers = []
for uvs in uv_layers:
chunks = []
for key, index in sorted(uvs.items(), key=operator.itemgetter(1)):
chunks.append(key)
layer = ",".join(generate_uv(n) for n in chunks)
layers.append(layer)
return ",".join("[%s]" % n for n in layers)
# ##############################################################################
# Model exporter - bones
# (only the first armature will exported)
# ##############################################################################
def generate_bones(option_bones, flipyz):
if not option_bones or len(bpy.data.armatures) == 0:
return "", 0
hierarchy = []
armature = bpy.data.armatures[0]
TEMPLATE_BONE = '{"parent":%d,"name":"%s","pos":[%g,%g,%g],"rotq":[0,0,0,1]}'
for bone in armature.bones:
if bone.parent == None:
if flipyz:
joint = TEMPLATE_BONE % (-1, bone.name, bone.head.x, bone.head.z, -bone.head.y)
hierarchy.append(joint)
else:
joint = TEMPLATE_BONE % (-1, bone.name, bone.head.x, bone.head.y, bone.head.z)
hierarchy.append(joint)
else:
index = i = 0
for parent in armature.bones:
if parent.name == bone.parent.name:
index = i
i += 1
position = bone.head_local - bone.parent.head_local
if flipyz:
joint = TEMPLATE_BONE % (index, bone.name, position.x, position.z, -position.y)
hierarchy.append(joint)
else:
joint = TEMPLATE_BONE % (index, bone.name, position.x, position.y, position.z)
hierarchy.append(joint)
bones_string = ",".join(hierarchy)
return bones_string, len(armature.bones)
# ##############################################################################
# Model exporter - skin indices and weights
# ##############################################################################
def generate_indices_and_weights(meshes, option_skinning):
if not option_skinning or len(bpy.data.armatures) == 0:
return "", ""
indices = []
weights = []
armature = bpy.data.armatures[0]
for mesh, object in meshes:
print("generate_indices_and_weights name=" + mesh.name)
weightCount = 0;
i = 0
mesh_index = -1
# find the original object
for obj in bpy.data.objects:
if obj.name == mesh.name or obj == object:
mesh_index = i
i += 1
if mesh_index == -1:
print("generate_indices: couldn't find object for mesh", mesh.name)
continue
object = bpy.data.objects[mesh_index]
for vertex in mesh.vertices:
# sort bones by influence
bone_array = []
for group in vertex.groups:
index = group.group
weight = group.weight
bone_array.append( (index, weight) )
bone_array.sort(key = operator.itemgetter(1), reverse=True)
# select first N bones
for i in range(MAX_INFLUENCES):
if i < len(bone_array):
bone_proxy = bone_array[i]
index = bone_proxy[0]
weight = bone_proxy[1]
for j, bone in enumerate(armature.bones):
if object.vertex_groups[index].name == bone.name:
indices.append('%d' % j)
weights.append('%g' % weight)
weightCount += 1;
break
else:
indices.append('0')
weights.append('0')
print("vertex.length=", len(mesh.vertices));
print("weight.length=", weightCount);
weightCount = 0;
indices_string = ",".join(indices)
weights_string = ",".join(weights)
print("*** indices.length=", len(indices));
print("*** weights.length=", len(weights));
return indices_string, weights_string
# ##############################################################################
# Model exporter - skeletal animation
# (only the first action will exported)
# ##############################################################################
def generate_animation(option_animation_skeletal, option_frame_step, flipyz, action_index):
if not option_animation_skeletal or len(bpy.data.actions) == 0 or len(bpy.data.armatures) == 0:
return ""
# TODO: Add scaling influences
action = bpy.data.actions[action_index]
armature = bpy.data.armatures[0]
parents = []
parent_index = -1
fps = bpy.data.scenes[0].render.fps
end_frame = action.frame_range[1]
start_frame = action.frame_range[0]
frame_length = end_frame - start_frame
TEMPLATE_KEYFRAME_FULL = '{"time":%g,"pos":[%g,%g,%g],"rot":[%g,%g,%g,%g],"scl":[1,1,1]}'
TEMPLATE_KEYFRAME = '{"time":%g,"pos":[%g,%g,%g],"rot":[%g,%g,%g,%g]}'
TEMPLATE_KEYFRAME_POS = '{"time":%g,"pos":[%g,%g,%g]}'
TEMPLATE_KEYFRAME_ROT = '{"time":%g,"rot":[%g,%g,%g,%g]}'
for hierarchy in armature.bones:
keys = []
for frame in range(int(start_frame), int(end_frame / option_frame_step) + 1):
pos, pchange = position(hierarchy, frame * option_frame_step, action)
rot, rchange = rotation(hierarchy, frame * option_frame_step, action)
if flipyz:
px, py, pz = pos.x, pos.z, -pos.y
rx, ry, rz, rw = rot.x, rot.z, -rot.y, rot.w
else:
px, py, pz = pos.x, pos.y, pos.z
rx, ry, rz, rw = rot.x, rot.y, rot.z, rot.w
# START-FRAME: needs pos, rot and scl attributes (required frame)
if frame == int(start_frame):
time = (frame * option_frame_step - start_frame) / fps
keyframe = TEMPLATE_KEYFRAME_FULL % (time, px, py, pz, rx, ry, rz, rw)
keys.append(keyframe)
# END-FRAME: needs pos, rot and scl attributes with animation length (required frame)
elif frame == int(end_frame / option_frame_step):
time = frame_length / fps
keyframe = TEMPLATE_KEYFRAME_FULL % (time, px, py, pz, rx, ry, rz, rw)
keys.append(keyframe)
# MIDDLE-FRAME: needs only one of the attributes, can be an empty frame (optional frame)
elif pchange == True or rchange == True:
time = (frame * option_frame_step - start_frame) / fps
if pchange == True and rchange == True:
keyframe = TEMPLATE_KEYFRAME % (time, px, py, pz, rx, ry, rz, rw)
elif pchange == True:
keyframe = TEMPLATE_KEYFRAME_POS % (time, px, py, pz)
elif rchange == True:
keyframe = TEMPLATE_KEYFRAME_ROT % (time, rx, ry, rz, rw)
keys.append(keyframe)
keys_string = ",".join(keys)
parent = '{"parent":%d,"keys":[%s]}' % (parent_index, keys_string)
parent_index += 1
parents.append(parent)
hierarchy_string = ",".join(parents)
animation_string = '"name":"%s","fps":%d,"length":%g,"hierarchy":[%s]' % (action.name, fps, (frame_length / fps), hierarchy_string)
return animation_string
def generate_all_animations(option_animation_skeletal, option_frame_step, flipyz, option_all_animations):
all_animations_string = ""
if option_all_animations:
for index in range(0, len(bpy.data.actions)):
if index != 0 :
all_animations_string += ", \n"
all_animations_string += "{" + generate_animation(option_animation_skeletal, option_frame_step, flipyz, index) + "}"
return all_animations_string
def handle_position_channel(channel, frame, position):
change = False
if channel.array_index in [0, 1, 2]:
for keyframe in channel.keyframe_points:
if keyframe.co[0] == frame:
change = True
value = channel.evaluate(frame)
if channel.array_index == 0:
position.x = value
if channel.array_index == 1:
position.y = value
if channel.array_index == 2:
position.z = value
return change
def position(bone, frame, action):
position = mathutils.Vector((0,0,0))
change = False
ngroups = len(action.groups)
if ngroups > 0:
index = 0
for i in range(ngroups):
if action.groups[i].name == bone.name:
index = i
for channel in action.groups[index].channels:
if "location" in channel.data_path:
hasChanged = handle_position_channel(channel, frame, position)
change = change or hasChanged
else:
bone_label = '"%s"' % bone.name
for channel in action.fcurves:
data_path = channel.data_path
if bone_label in data_path and "location" in data_path:
hasChanged = handle_position_channel(channel, frame, position)
change = change or hasChanged
position = position * bone.matrix_local.inverted()
if bone.parent == None:
position.x += bone.head.x
position.y += bone.head.y
position.z += bone.head.z
else:
parent = bone.parent
parentInvertedLocalMatrix = parent.matrix_local.inverted()
parentHeadTailDiff = parent.tail_local - parent.head_local
position.x += (bone.head * parentInvertedLocalMatrix).x + parentHeadTailDiff.x
position.y += (bone.head * parentInvertedLocalMatrix).y + parentHeadTailDiff.y
position.z += (bone.head * parentInvertedLocalMatrix).z + parentHeadTailDiff.z
return position, change
def handle_rotation_channel(channel, frame, rotation):
change = False
if channel.array_index in [0, 1, 2, 3]:
for keyframe in channel.keyframe_points:
if keyframe.co[0] == frame:
change = True
value = channel.evaluate(frame)
if channel.array_index == 1:
rotation.x = value
elif channel.array_index == 2:
rotation.y = value
elif channel.array_index == 3:
rotation.z = value
elif channel.array_index == 0:
rotation.w = value
return change
def rotation(bone, frame, action):
# TODO: calculate rotation also from rotation_euler channels
rotation = mathutils.Vector((0,0,0,1))
change = False
ngroups = len(action.groups)
# animation grouped by bones
if ngroups > 0:
index = 0
for i in range(ngroups):
if action.groups[i].name == bone.name:
index = i
for channel in action.groups[index].channels:
if "quaternion" in channel.data_path:
hasChanged = handle_rotation_channel(channel, frame, rotation)
change = change or hasChanged
# animation in raw fcurves
else:
bone_label = '"%s"' % bone.name
for channel in action.fcurves:
data_path = channel.data_path
if bone_label in data_path and "quaternion" in data_path:
hasChanged = handle_rotation_channel(channel, frame, rotation)
change = change or hasChanged
rot3 = rotation.to_3d()
rotation.xyz = rot3 * bone.matrix_local.inverted()
return rotation, change
# #####################################################
# 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()
elif type(v) == list:
return "[%s]" % (", ".join(value2string(x) for x in v))
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\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, option_copy_textures, filepath):
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]]
material['colorAmbient'] = [m.ambient * material['colorDiffuse'][0],
m.ambient * material['colorDiffuse'][1],
m.ambient * material['colorDiffuse'][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
textures = guess_material_textures(m)
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)
handle_texture('specular', textures, material, filepath, option_copy_textures)
handle_texture('bump', textures, material, filepath, option_copy_textures)
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"
if textures['normal']:
material['shading'] = "Phong"
else:
material['shading'] = m.THREE_materialType
material['blending'] = m.THREE_blendingType
material['depthWrite'] = m.THREE_depthWrite
material['depthTest'] = m.THREE_depthTest
material['transparent'] = m.use_transparency
return materials
def generate_materials_string(mesh, scene, option_colors, draw_type, option_copy_textures, filepath, offset):
random.seed(42) # to get well defined color order for debug materials
materials = {}
if mesh.materials:
for i, m in enumerate(mesh.materials):
mat_id = i + offset
if m:
materials[m.name] = mat_id
else:
materials["undefined_dummy_%0d" % mat_id] = mat_id
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, 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:
if id == "bump":
material['mapBumpScale'] = slot.normal_factor
else:
material['mapNormalFactor'] = slot.normal_factor
# #####################################################
# ASCII model generator
# #####################################################
def generate_ascii_model(meshes, morphs,
scene,
option_vertices,
option_vertices_truncate,
option_faces,
option_normals,
option_uv_coords,
option_materials,
option_colors,
option_bones,
option_skinning,
align_model,
flipyz,
option_scale,
option_copy_textures,
filepath,
option_animation_morph,
option_animation_skeletal,
option_all_animations,
option_frame_step):
vertices = []
vertex_offset = 0
vertex_offsets = []
nnormal = 0
normals = {}
ncolor = 0
colors = {}
nuvs = []
uv_layers = []
nmaterial = 0
materials = []
for mesh, object in meshes:
vertexUV = len(mesh.uv_textures) > 0
vertexColors = len(mesh.vertex_colors) > 0
mesh_extract_colors = option_colors and vertexColors
mesh_extract_uvs = option_uv_coords and vertexUV
if vertexUV:
active_uv_layer = mesh.uv_textures.active
if not active_uv_layer:
mesh_extract_uvs = False
if vertexColors:
active_col_layer = mesh.vertex_colors.active
if not active_col_layer:
mesh_extract_colors = False
vertex_offsets.append(vertex_offset)
vertex_offset += len(vertices)
vertices.extend(mesh.vertices[:])
if option_normals:
nnormal = extract_vertex_normals(mesh, normals, nnormal)
if mesh_extract_colors:
ncolor = extract_vertex_colors(mesh, colors, ncolor)
if mesh_extract_uvs:
nuvs = extract_uvs(mesh, uv_layers, nuvs)
if option_materials:
mesh_materials, nmaterial = generate_materials_string(mesh, scene, mesh_extract_colors, object.draw_type, option_copy_textures, filepath, nmaterial)
materials.append(mesh_materials)
morphTargets_string = ""
nmorphTarget = 0
if option_animation_morph:
chunks = []
for i, morphVertices in enumerate(morphs):
morphTarget = '{ "name": "%s_%06d", "vertices": [%s] }' % ("animation", i, morphVertices)
chunks.append(morphTarget)
morphTargets_string = ",\n\t".join(chunks)
nmorphTarget = len(morphs)
if align_model == 1:
center(vertices)
elif align_model == 2:
bottom(vertices)
elif align_model == 3:
top(vertices)
faces_string, nfaces = generate_faces(normals, uv_layers, colors, meshes, option_normals, option_colors, option_uv_coords, option_materials, option_faces)
bones_string, nbone = generate_bones(option_bones, flipyz)
indices_string, weights_string = generate_indices_and_weights(meshes, option_skinning)
materials_string = ",\n\n".join(materials)
model_string = TEMPLATE_MODEL_ASCII % {
"scale" : option_scale,
"uvs" : generate_uvs(uv_layers, 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" : faces_string,
"morphTargets" : morphTargets_string,
"bones" : bones_string,
"indices" : indices_string,
"weights" : weights_string,
"animation" : generate_animation(option_animation_skeletal, option_frame_step, flipyz, 0),
"animations" : generate_all_animations(option_animation_skeletal, option_frame_step, flipyz, option_all_animations)
}
text = TEMPLATE_FILE_ASCII % {
"nvertex" : len(vertices),
"nface" : nfaces,
"nuvs" : ",".join("%d" % n for n in nuvs),
"nnormal" : nnormal,
"ncolor" : ncolor,
"nmaterial" : nmaterial,
"nmorphTarget": nmorphTarget,
"nbone" : nbone,
"model" : model_string
}
return text, model_string
# #####################################################
# Model exporter - export single mesh
# #####################################################
def extract_meshes(objects, scene, export_single_model, option_scale, flipyz):
meshes = []
for object in objects:
if object.type == "MESH" and object.THREE_exportGeometry:
# collapse modifiers into mesh
mesh = object.to_mesh(scene, True, 'RENDER')
if not mesh:
raise Exception("Error, could not get mesh data from object [%s]" % object.name)
# preserve original name
mesh.name = object.name
if export_single_model:
if flipyz:
# that's what Blender's native export_obj.py does to flip YZ
X_ROT = mathutils.Matrix.Rotation(-math.pi/2, 4, 'X')
mesh.transform(X_ROT * object.matrix_world)
else:
mesh.transform(object.matrix_world)
mesh.calc_normals()
mesh.calc_tessface()
mesh.transform(mathutils.Matrix.Scale(option_scale, 4))
meshes.append([mesh, object])
return meshes
def generate_mesh_string(objects, scene,
option_vertices,
option_vertices_truncate,
option_faces,
option_normals,
option_uv_coords,
option_materials,
option_colors,
option_bones,
option_skinning,
align_model,
flipyz,
option_scale,
export_single_model,
option_copy_textures,
filepath,
option_animation_morph,
option_animation_skeletal,
option_all_animations,
option_frame_step):
meshes = extract_meshes(objects, scene, export_single_model, option_scale, flipyz)
morphs = []
if option_animation_morph:
original_frame = scene.frame_current # save animation state
scene_frames = range(scene.frame_start, scene.frame_end + 1, option_frame_step)
for index, frame in enumerate(scene_frames):
scene.frame_set(frame, 0.0)
anim_meshes = extract_meshes(objects, scene, export_single_model, option_scale, flipyz)
frame_vertices = []
for mesh, object in anim_meshes:
frame_vertices.extend(mesh.vertices[:])
if index == 0:
if align_model == 1:
offset = center(frame_vertices)
elif align_model == 2:
offset = bottom(frame_vertices)
elif align_model == 3:
offset = top(frame_vertices)
else:
offset = False
else:
if offset:
translate(frame_vertices, offset)
morphVertices = generate_vertices(frame_vertices, option_vertices_truncate, option_vertices)
morphs.append(morphVertices)
# remove temp meshes
for mesh, object in anim_meshes:
bpy.data.meshes.remove(mesh)
scene.frame_set(original_frame, 0.0) # restore animation state
text, model_string = generate_ascii_model(meshes, morphs,
scene,
option_vertices,
option_vertices_truncate,
option_faces,
option_normals,
option_uv_coords,
option_materials,
option_colors,
option_bones,
option_skinning,
align_model,
flipyz,
option_scale,
option_copy_textures,
filepath,
option_animation_morph,
option_animation_skeletal,
option_all_animations,
option_frame_step)
# remove temp meshes
for mesh, object in meshes:
bpy.data.meshes.remove(mesh)
return text, model_string
def export_mesh(objects,
scene, filepath,
option_vertices,
option_vertices_truncate,
option_faces,
option_normals,
option_uv_coords,
option_materials,
option_colors,
option_bones,
option_skinning,
align_model,
flipyz,
option_scale,
export_single_model,
option_copy_textures,
option_animation_morph,
option_animation_skeletal,
option_all_animations,
option_frame_step):
"""Export single mesh"""
text, model_string = generate_mesh_string(objects,
scene,
option_vertices,
option_vertices_truncate,
option_faces,
option_normals,
option_uv_coords,
option_materials,
option_colors,
option_bones,
option_skinning,
align_model,
flipyz,
option_scale,
export_single_model,
option_copy_textures,
filepath,
option_animation_morph,
option_animation_skeletal,
option_all_animations,
option_frame_step)
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")
# use empty material string for multi-material objects
# this will trigger use of MeshFaceMaterial in SceneLoader
material_string = ""
if len(material_ids) == 1:
material_string = generate_string_list(material_ids)
group_string = ""
if len(group_ids) > 0:
group_string = generate_string_list(group_ids)
castShadow = obj.THREE_castShadow
receiveShadow = obj.THREE_receiveShadow
doubleSided = obj.THREE_doubleSided
visible = True
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),
"castShadow" : generate_bool_property(castShadow),
"receiveShadow" : generate_bool_property(receiveShadow),
"doubleSided" : generate_bool_property(doubleSided),
"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)
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)
}
chunks.append(object_string)
return ",\n\n".join(chunks), len(chunks)
# #####################################################
# Scene exporter - geometries
# #####################################################
def generate_geometries(data):
chunks = []
geo_set = set()
for obj in data["objects"]:
if obj.type == "MESH" and obj.THREE_exportGeometry:
if len(obj.modifiers) > 0:
name = obj.name
else:
name = obj.data.name
if name not in geo_set:
geometry_id = "geo_%s" % name
if data["embed_meshes"]:
embed_id = "emb_%s" % name
geometry_string = TEMPLATE_GEOMETRY_EMBED % {
"geometry_id" : generate_string(geometry_id),
"embed_id" : generate_string(embed_id)
}
else:
model_filename = os.path.basename(generate_mesh_filename(name, data["filepath"]))
geometry_string = TEMPLATE_GEOMETRY_LINK % {
"geometry_id" : generate_string(geometry_id),
"model_file" : generate_string(model_filename)
}
chunks.append(geometry_string)
geo_set.add(name)
return ",\n\n".join(chunks), len(chunks)
# #####################################################
# Scene exporter - textures
# #####################################################
def generate_textures_scene(data):
chunks = []
# TODO: extract just textures actually used by some objects in the scene
for texture in bpy.data.textures:
if texture.type == 'IMAGE' and texture.image:
img = texture.image
texture_id = img.name
texture_file = extract_texture_filename(img)
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": [%g, %g]' % (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),
"texture_file" : generate_string(texture_file),
"extras" : extras
}
chunks.append(texture_string)
return ",\n\n".join(chunks), len(chunks)
def extract_texture_filename(image):
fn = bpy.path.abspath(image.filepath)
fn = os.path.normpath(fn)
fn_strip = os.path.basename(fn)
return fn_strip
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
# #####################################################
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]]
material['colorAmbient'] = [m.ambient * material['colorDiffuse'][0],
m.ambient * material['colorDiffuse'][1],
m.ambient * material['colorDiffuse'][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["vertexColors"] = m.THREE_useVertexColors and option_colors
material['mapDiffuse'] = ""
material['mapLight'] = ""
material['mapSpecular'] = ""
material['mapNormal'] = ""
material['mapBump'] = ""
material['mapNormalFactor'] = 1.0
material['mapBumpScale'] = 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['specular']:
material['mapSpecular'] = textures['specular']['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
if textures['bump']:
material['mapBump'] = textures['bump']['texture'].image.name
if textures['normal']['slot'].use_map_normal:
material['mapBumpScale'] = textures['normal']['slot'].normal_factor
material['shading'] = m.THREE_materialType
material['blending'] = m.THREE_blendingType
material['depthWrite'] = m.THREE_depthWrite
material['depthTest'] = m.THREE_depthTest
material['transparent'] = m.use_transparency
return material
def guess_material_textures(material):
textures = {
'diffuse' : None,
'light' : None,
'normal' : None,
'specular': None,
'bump' : None
}
# 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(material.texture_slots)):
slot = material.texture_slots[i]
if slot:
texture = slot.texture
if slot.use and texture and texture.type == 'IMAGE':
# normal map in Blender UI: textures => image sampling => normal map
if texture.use_normal_map:
textures['normal'] = { "texture": texture, "slot": slot }
# bump map in Blender UI: textures => influence => geometry => normal
elif slot.use_map_normal:
textures['bump'] = { "texture": texture, "slot": slot }
elif slot.use_map_specular or slot.use_map_hardness:
textures['specular'] = { "texture": texture, "slot": slot }
else:
if not textures['diffuse'] and not slot.blend_type == 'MULTIPLY':
textures['diffuse'] = { "texture": texture, "slot": slot }
else:
textures['light'] = { "texture": texture, "slot": slot }
if textures['diffuse'] and textures['normal'] and textures['light'] and textures['specular'] and textures['bump']:
break
return textures
def generate_material_string(material):
material_id = material["name"]
# default to Lambert
shading = material.get("shading", "Lambert")
# normal and bump mapped materials must use Phong
# to get all required parameters for normal shader
if material['mapNormal'] or material['mapBump']:
shading = "Phong"
type_map = {
"Lambert" : "MeshLambertMaterial",
"Phong" : "MeshPhongMaterial"
}
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']
specularMap = material['mapSpecular']
normalMap = material['mapNormal']
bumpMap = material['mapBump']
normalMapFactor = material['mapNormalFactor']
bumpMapScale = material['mapBumpScale']
if colorMap:
parameters += ', "map": %s' % generate_string(colorMap)
if lightMap:
parameters += ', "lightMap": %s' % generate_string(lightMap)
if specularMap:
parameters += ', "specularMap": %s' % generate_string(specularMap)
if normalMap:
parameters += ', "normalMap": %s' % generate_string(normalMap)
if bumpMap:
parameters += ', "bumpMap": %s' % generate_string(bumpMap)
if normalMapFactor != 1.0:
parameters += ', "normalMapFactor": %g' % normalMapFactor
if bumpMapScale != 1.0:
parameters += ', "bumpMapScale": %g' % bumpMapScale
if material['vertexColors']:
parameters += ', "vertexColors": "vertex"'
if material['transparent']:
parameters += ', "transparent": true'
parameters += ', "blending": "%s"' % material['blending']
if not material['depthWrite']:
parameters += ', "depthWrite": false'
if not material['depthTest']:
parameters += ', "depthTest": false'
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):
chunks = []
if data["use_cameras"]:
cams = bpy.data.objects
cams = [ob for ob in cams if (ob.type == 'CAMERA' and ob.select)]
if not cams:
camera = DEFAULTS["camera"]
if camera["type"] == "PerspectiveCamera":
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"] == "OrthographicCamera":
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)
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), len(chunks)
# #####################################################
# Scene exporter - lights
# #####################################################
def generate_lights(data):
chunks = []
if data["use_lights"]:
lights = data.get("lights", [])
if not lights:
lights.append(DEFAULTS["light"])
for light in lights:
if light["type"] == "DirectionalLight":
light_string = TEMPLATE_LIGHT_DIRECTIONAL % {
"light_id" : generate_string(light["name"]),
"direction" : generate_vec3(light["direction"]),
"color" : rgb2int(light["color"]),
"intensity" : light["intensity"]
}
elif light["type"] == "PointLight":
light_string = TEMPLATE_LIGHT_POINT % {
"light_id" : generate_string(light["name"]),
"position" : generate_vec3(light["position"]),
"color" : rgb2int(light["color"]),
"intensity" : light["intensity"]
}
chunks.append(light_string)
return ",\n\n".join(chunks), len(chunks)
# #####################################################
# 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)
lights, nlights = generate_lights(data)
cameras, ncameras = generate_cameras(data)
embeds = generate_embeds(data)
if nlights > 0:
if nobjects > 0:
objects = objects + ",\n\n" + lights
else:
objects = lights
nobjects += nlights
if ncameras > 0:
if nobjects > 0:
objects = objects + ",\n\n" + cameras
else:
objects = cameras
nobjects += ncameras
basetype = "relativeTo"
if data["base_html"]:
basetype += "HTML"
else:
basetype += "Scene"
sections = [
["objects", objects],
["geometries", geometries],
["textures", textures],
["materials", materials],
["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"]:
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"],
"sections" : sections_string,
"bgcolor" : generate_vec3(DEFAULTS["bgcolor"]),
"bgalpha" : DEFAULTS["bgalpha"],
"defcamera" : generate_string(default_camera),
"nobjects" : nobjects,
"ngeometries" : ngeometries,
"ntextures" : ntextures,
"basetype" : generate_string(basetype),
"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, option_url_base_html, option_copy_textures):
source_file = os.path.basename(bpy.data.filepath)
# objects are contained in scene and linked groups
objects = []
# get scene objects
sceneobjects = scene.objects
for obj in sceneobjects:
objects.append(obj)
# get linked group objcts
for group in bpy.data.groups:
for object in group.objects:
objects.append(object)
scene_text = ""
data = {
"scene" : scene,
"objects" : 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)
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_uv_coords = True,
option_materials = True,
option_colors = True,
option_bones = True,
option_skinning = True,
align_model = 0,
option_export_scene = False,
option_lights = False,
option_cameras = False,
option_scale = 1.0,
option_embed_meshes = True,
option_url_base_html = False,
option_copy_textures = False,
option_animation_morph = False,
option_animation_skeletal = False,
option_all_animations = False,
option_frame_step = 1,
option_all_meshes = True):
#print("URL TYPE", option_url_base_html)
filepath = ensure_extension(filepath, '.js')
scene = context.scene
if scene.objects.active:
bpy.ops.object.mode_set(mode='OBJECT')
if option_all_meshes:
sceneobjects = scene.objects
else:
sceneobjects = context.selected_objects
# objects are contained in scene and linked groups
objects = []
# get scene objects
for obj in sceneobjects:
objects.append(obj)
# get objects in linked groups
for group in bpy.data.groups:
for object in group.objects:
objects.append(object)
if option_export_scene:
geo_set = set()
embeds = {}
for object in objects:
if object.type == "MESH" and object.THREE_exportGeometry:
# create extra copy of geometry with applied modifiers
# (if they exist)
if len(object.modifiers) > 0:
name = object.name
# otherwise can share geometry
else:
name = object.data.name
if name not in geo_set:
if option_embed_meshes:
text, model_string = generate_mesh_string([object], scene,
option_vertices,
option_vertices_truncate,
option_faces,
option_normals,
option_uv_coords,
option_materials,
option_colors,
option_bones,
option_skinning,
False, # align_model
option_flip_yz,
option_scale,
False, # export_single_model
False, # option_copy_textures
filepath,
option_animation_morph,
option_animation_skeletal,
option_all_animations,
option_frame_step)
embeds[name] = model_string
else:
fname = generate_mesh_filename(name, filepath)
export_mesh([object], scene,
fname,
option_vertices,
option_vertices_truncate,
option_faces,
option_normals,
option_uv_coords,
option_materials,
option_colors,
option_bones,
option_skinning,
False, # align_model
option_flip_yz,
option_scale,
False, # export_single_model
option_copy_textures,
option_animation_morph,
option_animation_skeletal,
option_all_animations,
option_frame_step)
geo_set.add(name)
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:
export_mesh(objects, scene, filepath,
option_vertices,
option_vertices_truncate,
option_faces,
option_normals,
option_uv_coords,
option_materials,
option_colors,
option_bones,
option_skinning,
align_model,
option_flip_yz,
option_scale,
True, # export_single_model
option_copy_textures,
option_animation_morph,
option_animation_skeletal,
option_all_animations,
option_frame_step)
return {'FINISHED'}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment