io_export_babylon.py 46 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107
  1. bl_info = {
  2. "name": "Babylon.js",
  3. "author": "David Catuhe",
  4. "version": (1, 1),
  5. "blender": (2, 67, 0),
  6. "location": "File > Export > Babylon.js (.babylon)",
  7. "description": "Export Babylon.js scenes (.babylon)",
  8. "warning": "",
  9. "wiki_url": "",
  10. "tracker_url": "",
  11. "category": "Import-Export"}
  12. import math
  13. import os
  14. import bpy
  15. import string
  16. import bpy_extras.io_utils
  17. from bpy.props import *
  18. import mathutils, math
  19. import struct
  20. import shutil
  21. from os import remove
  22. from bpy_extras.io_utils import (ExportHelper, axis_conversion)
  23. from bpy.props import (BoolProperty, FloatProperty, StringProperty, EnumProperty, FloatVectorProperty)
  24. from math import radians
  25. from mathutils import *
  26. MAX_VERTICES = 65535
  27. class SubMesh:
  28. materialIndex = 0
  29. verticesStart = 0
  30. verticesCount = 0
  31. indexStart = 0
  32. indexCount = 0
  33. class MultiMaterial:
  34. name = ""
  35. materials = []
  36. class Export_babylon(bpy.types.Operator, ExportHelper):
  37. """Export Babylon.js scene (.babylon)"""
  38. bl_idname = "scene.babylon"
  39. bl_label = "Export Babylon.js scene"
  40. filename_ext = ".babylon"
  41. filepath = ""
  42. # global_scale = FloatProperty(name="Scale", min=0.01, max=1000.0, default=1.0)
  43. def execute(self, context):
  44. return Export_babylon.save(self, context, **self.as_keywords(ignore=("check_existing", "filter_glob", "global_scale")))
  45. def mesh_triangulate(mesh):
  46. try:
  47. import bmesh
  48. bm = bmesh.new()
  49. bm.from_mesh(mesh)
  50. bmesh.ops.triangulate(bm, faces=bm.faces)
  51. bm.to_mesh(mesh)
  52. mesh.calc_tessface()
  53. bm.free()
  54. except:
  55. pass
  56. def write_matrix4(file_handler, name, matrix):
  57. file_handler.write(",\""+name+"\":[")
  58. tempMatrix = matrix.copy()
  59. tempMatrix.transpose()
  60. first = True
  61. for vect in tempMatrix:
  62. if (first != True):
  63. file_handler.write(",")
  64. first = False;
  65. file_handler.write("%.4f,%.4f,%.4f,%.4f"%(vect[0],vect[1], vect[2], vect[3]))
  66. file_handler.write("]")
  67. def write_array3(file_handler, name, array):
  68. file_handler.write(",\""+name+"\":[" + "%.4f,%.4f,%.4f"%(array[0],array[1],array[2]) + "]")
  69. def write_color(file_handler, name, color):
  70. file_handler.write(",\""+name+"\":[" + "%.4f,%.4f,%.4f"%(color.r,color.g,color.b) + "]")
  71. def write_vector(file_handler, name, vector):
  72. file_handler.write(",\""+name+"\":[" + "%.4f,%.4f,%.4f"%(vector.x,vector.z,vector.y) + "]")
  73. def write_quaternion(file_handler, name, quaternion):
  74. file_handler.write(",\""+name+"\":[" + "%.4f,%.4f,%.4f,%.4f"%(quaternion.x,quaternion.z,quaternion.y, -quaternion.w) + "]")
  75. def write_vectorScaled(file_handler, name, vector, mult):
  76. file_handler.write(",\""+name+"\":[" + "%.4f,%.4f,%.4f"%(vector.x * mult, vector.z * mult, vector.y * mult) + "]")
  77. def write_string(file_handler, name, string, noComma=False):
  78. if noComma == False:
  79. file_handler.write(",")
  80. file_handler.write("\""+name+"\":\"" + string + "\"")
  81. def write_float(file_handler, name, float):
  82. file_handler.write(",\""+name+"\":" + "%.4f"%(float))
  83. def write_int(file_handler, name, int, noComma=False):
  84. if noComma == False:
  85. file_handler.write(",")
  86. file_handler.write("\""+name+"\":" + str(int))
  87. def write_bool(file_handler, name, bool, noComma=False):
  88. if noComma == False:
  89. file_handler.write(",")
  90. if bool:
  91. file_handler.write("\""+name+"\":" + "true")
  92. else:
  93. file_handler.write("\""+name+"\":" + "false")
  94. def getDirection(matrix):
  95. return (matrix.to_3x3() * mathutils.Vector((0.0, 0.0, -1.0))).normalized()
  96. def export_camera(object, scene, file_handler):
  97. rotation = mathutils.Vector((-object.rotation_euler[0] + math.pi / 2,
  98. object.rotation_euler[1], -object.rotation_euler[2]))
  99. file_handler.write("{")
  100. Export_babylon.write_string(file_handler, "name", object.name, True)
  101. Export_babylon.write_string(file_handler, "id", object.name)
  102. Export_babylon.write_vector(file_handler, "position", object.location)
  103. Export_babylon.write_vector(file_handler, "rotation", rotation)
  104. Export_babylon.write_float(file_handler, "fov", object.data.angle)
  105. Export_babylon.write_float(file_handler, "minZ", object.data.clip_start)
  106. Export_babylon.write_float(file_handler, "maxZ", object.data.clip_end)
  107. Export_babylon.write_float(file_handler, "speed", 1.0)
  108. Export_babylon.write_float(file_handler, "inertia", 0.9)
  109. Export_babylon.write_bool(file_handler, "checkCollisions", object.data.checkCollisions)
  110. Export_babylon.write_bool(file_handler, "applyGravity", object.data.applyGravity)
  111. Export_babylon.write_array3(file_handler, "ellipsoid", object.data.ellipsoid)
  112. locAnim = False
  113. coma = False
  114. if object.animation_data:
  115. if object.animation_data.action:
  116. file_handler.write(",\"animations\":[")
  117. for fcurve in object.animation_data.action.fcurves:
  118. if fcurve.data_path == "location" and locAnim == False:
  119. Export_babylon.export_animation(object, scene, file_handler, "location", "position", coma, 1)
  120. locAnim = coma = True
  121. file_handler.write("]")
  122. #Set Animations
  123. Export_babylon.write_bool(file_handler, "autoAnimate", True)
  124. Export_babylon.write_int(file_handler, "autoAnimateFrom", 0)
  125. Export_babylon.write_int(file_handler, "autoAnimateTo", bpy.context.scene.frame_end - bpy.context.scene.frame_start + 1)
  126. Export_babylon.write_bool(file_handler, "autoAnimateLoop", True)
  127. file_handler.write("}")
  128. def export_light(object, scene, file_handler):
  129. light_type_items = {'POINT': 0, 'SUN': 1, 'SPOT': 2, 'HEMI': 3, 'AREA': 0}
  130. light_type = light_type_items[object.data.type]
  131. file_handler.write("{")
  132. Export_babylon.write_string(file_handler, "name", object.name, True)
  133. Export_babylon.write_string(file_handler, "id", object.name)
  134. Export_babylon.write_float(file_handler, "type", light_type)
  135. if light_type == 0:
  136. Export_babylon.write_vector(file_handler, "position", object.location)
  137. elif light_type == 1:
  138. direction = Export_babylon.getDirection(object.matrix_world)
  139. Export_babylon.write_vector(file_handler, "position", object.location)
  140. Export_babylon.write_vector(file_handler, "direction", direction)
  141. elif light_type == 2:
  142. Export_babylon.write_vector(file_handler, "position", object.location)
  143. direction = Export_babylon.getDirection(object.matrix_world)
  144. Export_babylon.write_vector(file_handler, "direction", direction)
  145. Export_babylon.write_float(file_handler, "angle", object.data.spot_size)
  146. Export_babylon.write_float(file_handler, "exponent", object.data.spot_blend * 2)
  147. else:
  148. matrix_world = object.matrix_world.copy()
  149. matrix_world.translation = mathutils.Vector((0, 0, 0))
  150. direction = mathutils.Vector((0, 0, -1)) * matrix_world
  151. Export_babylon.write_vector(file_handler, "direction", -direction)
  152. Export_babylon.write_color(file_handler, "groundColor", mathutils.Color((0, 0, 0)))
  153. Export_babylon.write_float(file_handler, "intensity", object.data.energy)
  154. if object.data.use_diffuse:
  155. Export_babylon.write_color(file_handler, "diffuse", object.data.color)
  156. else:
  157. Export_babylon.write_color(file_handler, "diffuse", mathutils.Color((0, 0, 0)))
  158. if object.data.use_specular:
  159. Export_babylon.write_color(file_handler, "specular", object.data.color)
  160. else:
  161. Export_babylon.write_color(file_handler, "specular", mathutils.Color((0, 0, 0)))
  162. file_handler.write("}")
  163. def export_texture(slot, level, texture, scene, file_handler, filepath):
  164. # Copy image to output
  165. try:
  166. image = texture.texture.image
  167. imageFilepath = os.path.normpath(bpy.path.abspath(image.filepath))
  168. basename = os.path.basename(imageFilepath)
  169. targetdir = os.path.dirname(filepath)
  170. targetpath = os.path.join(targetdir, basename)
  171. if image.packed_file:
  172. image.save_render(targetpath)
  173. else:
  174. sourcepath = bpy.path.abspath(image.filepath)
  175. shutil.copy(sourcepath, targetdir)
  176. except:
  177. pass
  178. # Export
  179. file_handler.write(",\""+slot+"\":{")
  180. Export_babylon.write_string(file_handler, "name", basename, True)
  181. Export_babylon.write_float(file_handler, "level", level)
  182. Export_babylon.write_float(file_handler, "hasAlpha", texture.texture.use_alpha)
  183. coordinatesMode = 0;
  184. if (texture.mapping == "CUBE"):
  185. coordinatesMode = 3;
  186. if (texture.mapping == "SPHERE"):
  187. coordinatesMode = 1;
  188. Export_babylon.write_int(file_handler, "coordinatesMode", coordinatesMode)
  189. Export_babylon.write_float(file_handler, "uOffset", texture.offset.x)
  190. Export_babylon.write_float(file_handler, "vOffset", texture.offset.y)
  191. Export_babylon.write_float(file_handler, "uScale", texture.scale.x)
  192. Export_babylon.write_float(file_handler, "vScale", texture.scale.y)
  193. Export_babylon.write_float(file_handler, "uAng", 0)
  194. Export_babylon.write_float(file_handler, "vAng", 0)
  195. Export_babylon.write_float(file_handler, "wAng", 0)
  196. texture_clampMode = 0;
  197. texture_wrapMode = 1;
  198. texture_mirrorMode = 2;
  199. if (texture.texture.extension == "REPEAT"):
  200. if (texture.texture.use_mirror_x):
  201. Export_babylon.write_int(file_handler, "wrapU", texture_mirrorMode)
  202. else:
  203. Export_babylon.write_int(file_handler, "wrapU", texture_wrapMode)
  204. if (texture.texture.use_mirror_y):
  205. Export_babylon.write_int(file_handler, "wrapV", texture_mirrorMode)
  206. else:
  207. Export_babylon.write_int(file_handler, "wrapV", texture_wrapMode)
  208. else:
  209. Export_babylon.write_int(file_handler, "wrapU", texture_clampMode)
  210. Export_babylon.write_int(file_handler, "wrapV", texture_clampMode)
  211. Export_babylon.write_int(file_handler, "coordinatesIndex", 0)
  212. file_handler.write("}")
  213. def export_material(material, scene, file_handler, filepath):
  214. file_handler.write("{")
  215. Export_babylon.write_string(file_handler, "name", material.name, True)
  216. Export_babylon.write_string(file_handler, "id", material.name)
  217. Export_babylon.write_color(file_handler, "ambient", material.ambient * material.diffuse_color)
  218. Export_babylon.write_color(file_handler, "diffuse", material.diffuse_intensity * material.diffuse_color)
  219. Export_babylon.write_color(file_handler, "specular", material.specular_intensity * material.specular_color)
  220. Export_babylon.write_float(file_handler, "specularPower", material.specular_hardness)
  221. Export_babylon.write_color(file_handler, "emissive", material.emit * material.diffuse_color)
  222. Export_babylon.write_float(file_handler, "alpha", material.alpha)
  223. Export_babylon.write_bool(file_handler, "backFaceCulling", material.game_settings.use_backface_culling)
  224. # Textures
  225. for mtex in material.texture_slots:
  226. if mtex and mtex.texture and mtex.texture.type == 'IMAGE':
  227. if mtex.texture.image:
  228. if (mtex.use_map_color_diffuse and(mtex.texture_coords != 'REFLECTION')):
  229. # Diffuse
  230. Export_babylon.export_texture("diffuseTexture", mtex.diffuse_color_factor, mtex, scene, file_handler, filepath)
  231. if mtex.use_map_ambient:
  232. # Ambient
  233. Export_babylon.export_texture("ambientTexture", mtex.ambient_factor, mtex, scene, file_handler, filepath)
  234. if mtex.use_map_alpha:
  235. # Opacity
  236. Export_babylon.export_texture("opacityTexture", mtex.alpha_factor, mtex, scene, file_handler, filepath)
  237. if mtex.use_map_color_diffuse and (mtex.texture_coords == 'REFLECTION'):
  238. # Reflection
  239. Export_babylon.export_texture("reflectionTexture", mtex.diffuse_color_factor, mtex, scene, file_handler, filepath)
  240. if mtex.use_map_emit:
  241. # Emissive
  242. Export_babylon.export_texture("emissiveTexture", mtex.emit_factor, mtex, scene, file_handler, filepath)
  243. if mtex.use_map_normal:
  244. # Bump
  245. Export_babylon.export_texture("bumpTexture", mtex.emit_factor, mtex, scene, file_handler, filepath)
  246. file_handler.write("}")
  247. def export_multimaterial(multimaterial, scene, file_handler):
  248. file_handler.write("{")
  249. Export_babylon.write_string(file_handler, "name", multimaterial.name, True)
  250. Export_babylon.write_string(file_handler, "id", multimaterial.name)
  251. file_handler.write(",\"materials\":[")
  252. first = True
  253. for materialName in multimaterial.materials:
  254. if first != True:
  255. file_handler.write(",")
  256. file_handler.write("\"" + materialName +"\"")
  257. first = False
  258. file_handler.write("]")
  259. file_handler.write("}")
  260. def export_animation(object, scene, file_handler, typeBl, typeBa, coma, mult):
  261. if coma == True:
  262. file_handler.write(",")
  263. file_handler.write("{")
  264. Export_babylon.write_int(file_handler, "dataType", 1, True)
  265. Export_babylon.write_int(file_handler, "framePerSecond", 30)
  266. Export_babylon.write_int(file_handler, "loopBehavior", 1)
  267. Export_babylon.write_string(file_handler, "name", typeBa+" animation")
  268. Export_babylon.write_string(file_handler, "property", typeBa)
  269. file_handler.write(",\"keys\":[")
  270. frames = dict()
  271. for fcurve in object.animation_data.action.fcurves:
  272. if fcurve.data_path == typeBl:
  273. for key in fcurve.keyframe_points:
  274. frame = key.co.x
  275. frames[frame] = 1
  276. #for each frame (next step ==> set for key frames)
  277. i = 0
  278. for Frame in sorted(frames):
  279. if i == 0 and Frame != 0.0:
  280. file_handler.write("{")
  281. Export_babylon.write_int(file_handler, "frame", 0, True)
  282. bpy.context.scene.frame_set(int(Frame + bpy.context.scene.frame_start))
  283. Export_babylon.write_vectorScaled(file_handler, "values", getattr(object,typeBl), mult)
  284. file_handler.write("},")
  285. i = i + 1
  286. file_handler.write("{")
  287. Export_babylon.write_int(file_handler, "frame", Frame, True)
  288. bpy.context.scene.frame_set(int(Frame + bpy.context.scene.frame_start))
  289. Export_babylon.write_vectorScaled(file_handler, "values", getattr(object,typeBl), mult)
  290. file_handler.write("}")
  291. if i != len(frames):
  292. file_handler.write(",")
  293. else:
  294. file_handler.write(",{")
  295. Export_babylon.write_int(file_handler, "frame", bpy.context.scene.frame_end - bpy.context.scene.frame_start + 1, True)
  296. bpy.context.scene.frame_set(int(Frame + bpy.context.scene.frame_start))
  297. Export_babylon.write_vectorScaled(file_handler, "values", getattr(object,typeBl), mult)
  298. file_handler.write("}")
  299. file_handler.write("]}")
  300. def export_mesh(object, scene, file_handler, multiMaterials, startFace, forcedParent, nameID):
  301. # Get mesh
  302. mesh = object.to_mesh(scene, True, "PREVIEW")
  303. # Transform
  304. loc = mathutils.Vector((0, 0, 0))
  305. rot = mathutils.Quaternion((0, 0, 0, -1))
  306. scale = mathutils.Vector((1, 1, 1))
  307. hasSkeleton = False
  308. world = object.matrix_world
  309. if object.parent and object.parent.type == "ARMATURE" and len(object.vertex_groups) > 0:
  310. hasSkeleton = True
  311. else:
  312. if (object.parent):
  313. world = object.parent.matrix_world.inverted() * object.matrix_world
  314. loc, rot, scale = world.decompose()
  315. # Triangulate mesh if required
  316. Export_babylon.mesh_triangulate(mesh)
  317. # Getting vertices and indices
  318. positions=",\"positions\":["
  319. normals=",\"normals\":["
  320. indices=",\"indices\":["
  321. hasUV = True;
  322. hasUV2 = True;
  323. hasVertexColor = True
  324. if len(mesh.tessface_uv_textures) > 0:
  325. UVmap=mesh.tessface_uv_textures[0].data
  326. uvs=",\"uvs\":["
  327. else:
  328. hasUV = False
  329. if len(mesh.tessface_uv_textures) > 1:
  330. UV2map=mesh.tessface_uv_textures[1].data
  331. uvs2=",\"uvs2\":["
  332. else:
  333. hasUV2 = False
  334. if len(mesh.vertex_colors) > 0:
  335. Colormap = mesh.tessface_vertex_colors.active.data
  336. colors=",\"colors\":["
  337. else:
  338. hasVertexColor = False
  339. if hasSkeleton:
  340. skeletonWeight = ",\"matricesWeights\":["
  341. skeletonIndices = ",\"matricesIndices\":["
  342. alreadySavedVertices = []
  343. vertices_UVs=[]
  344. vertices_UV2s=[]
  345. vertices_Colors=[]
  346. vertices_indices=[]
  347. subMeshes = []
  348. offsetFace = 0
  349. for v in range(0, len(mesh.vertices)):
  350. alreadySavedVertices.append(False)
  351. vertices_UVs.append([])
  352. vertices_UV2s.append([])
  353. vertices_Colors.append([])
  354. vertices_indices.append([])
  355. materialsCount = max(1, len(object.material_slots))
  356. verticesCount = 0
  357. indicesCount = 0
  358. for materialIndex in range(materialsCount):
  359. if offsetFace != 0:
  360. break
  361. subMeshes.append(SubMesh())
  362. subMeshes[materialIndex].materialIndex = materialIndex
  363. subMeshes[materialIndex].verticesStart = verticesCount
  364. subMeshes[materialIndex].indexStart = indicesCount
  365. for faceIndex in range(startFace, len(mesh.tessfaces)): # For each face
  366. face = mesh.tessfaces[faceIndex]
  367. if face.material_index != materialIndex:
  368. continue
  369. if verticesCount + 3 > MAX_VERTICES:
  370. offsetFace = faceIndex
  371. break
  372. for v in range(3): # For each vertex in face
  373. vertex_index = face.vertices[v]
  374. vertex = mesh.vertices[vertex_index]
  375. position = vertex.co
  376. normal = vertex.normal
  377. #skeletons
  378. if hasSkeleton:
  379. matricesWeights = []
  380. matricesIndices = 0
  381. matricesWeights.append(0.0)
  382. matricesWeights.append(0.0)
  383. matricesWeights.append(0.0)
  384. matricesWeights.append(0.0)
  385. # Getting influences
  386. i = 0
  387. offset = 0
  388. for group in vertex.groups:
  389. index = group.group
  390. weight = group.weight
  391. for boneIndex, bone in enumerate(object.parent.pose.bones):
  392. if object.vertex_groups[index].name == bone.name:
  393. matricesWeights[i] = weight
  394. matricesIndices += boneIndex << offset
  395. offset = offset + 8
  396. i = i + 1
  397. if (i == 4):
  398. break;
  399. if (i == 4):
  400. break;
  401. # Texture coordinates
  402. if hasUV:
  403. vertex_UV = UVmap[face.index].uv[v]
  404. if hasUV2:
  405. vertex_UV2 = UV2map[face.index].uv[v]
  406. # Vertex color
  407. if hasVertexColor:
  408. if v == 0:
  409. vertex_Color = Colormap[face.index].color1
  410. if v == 1:
  411. vertex_Color = Colormap[face.index].color2
  412. if v == 2:
  413. vertex_Color = Colormap[face.index].color3
  414. # Check if the current vertex is already saved
  415. alreadySaved = alreadySavedVertices[vertex_index] and not hasSkeleton
  416. if alreadySaved:
  417. alreadySaved=False
  418. # UV
  419. index_UV = 0
  420. for savedIndex in vertices_indices[vertex_index]:
  421. if hasUV:
  422. vUV = vertices_UVs[vertex_index][index_UV]
  423. if (vUV[0]!=vertex_UV[0] or vUV[1]!=vertex_UV[1]):
  424. continue
  425. if hasUV2:
  426. vUV2 = vertices_UV2s[vertex_index][index_UV]
  427. if (vUV2[0]!=vertex_UV2[0] or vUV2[1]!=vertex_UV2[1]):
  428. continue
  429. if hasVertexColor:
  430. vColor = vertices_Colors[vertex_index][index_UV]
  431. if (vColor.r!=vertex_Color.r or vColor.g!=vertex_Color.g or vColor.b!=vertex_Color.b):
  432. continue
  433. if vertices_indices[vertex_index][index_UV] >= subMeshes[materialIndex].verticesStart:
  434. alreadySaved=True
  435. break
  436. index_UV+=1
  437. if (alreadySaved):
  438. # Reuse vertex
  439. index=vertices_indices[vertex_index][index_UV]
  440. else:
  441. # Export new one
  442. index=verticesCount
  443. alreadySavedVertices[vertex_index]=True
  444. if hasUV:
  445. vertices_UVs[vertex_index].append(vertex_UV)
  446. uvs+="%.4f,%.4f,"%(vertex_UV[0], vertex_UV[1])
  447. if hasUV2:
  448. vertices_UV2s[vertex_index].append(vertex_UV2)
  449. uvs2+="%.4f,%.4f,"%(vertex_UV2[0], vertex_UV2[1])
  450. if hasVertexColor:
  451. vertices_Colors[vertex_index].append(vertex_Color)
  452. colors+="%.4f,%.4f,%.4f,"%(vertex_Color.r,vertex_Color.g,vertex_Color.b)
  453. if hasSkeleton:
  454. skeletonWeight+="%.4f,%.4f,%.4f,%.4f,"%(matricesWeights[0], matricesWeights[1], matricesWeights[2], matricesWeights[3])
  455. skeletonIndices+="%i,"%(matricesIndices)
  456. vertices_indices[vertex_index].append(index)
  457. positions+="%.4f,%.4f,%.4f,"%(position.x,position.z,position.y)
  458. normals+="%.4f,%.4f,%.4f,"%(normal.x,normal.z,normal.y)
  459. verticesCount += 1
  460. indices+="%i,"%(index)
  461. indicesCount += 1
  462. subMeshes[materialIndex].verticesCount = verticesCount - subMeshes[materialIndex].verticesStart
  463. subMeshes[materialIndex].indexCount = indicesCount - subMeshes[materialIndex].indexStart
  464. positions=positions.rstrip(',')
  465. normals=normals.rstrip(',')
  466. indices=indices.rstrip(',')
  467. positions+="]\n"
  468. normals+="]\n"
  469. indices+="]\n"
  470. if hasUV:
  471. uvs=uvs.rstrip(',')
  472. uvs+="]\n"
  473. if hasUV2:
  474. uvs2=uvs2.rstrip(',')
  475. uvs2+="]\n"
  476. if hasVertexColor:
  477. colors=colors.rstrip(',')
  478. colors+="]\n"
  479. if hasSkeleton:
  480. skeletonWeight=skeletonWeight.rstrip(', ')
  481. skeletonWeight+="]\n"
  482. skeletonIndices= skeletonIndices.rstrip(', ')
  483. skeletonIndices+="]\n"
  484. # Writing mesh
  485. file_handler.write("{")
  486. Export_babylon.write_string(file_handler, "name", object.name + nameID, True)
  487. Export_babylon.write_string(file_handler, "id", object.name + nameID)
  488. if forcedParent is None:
  489. if object.parent != None:
  490. Export_babylon.write_string(file_handler, "parentId", object.parent.name)
  491. else:
  492. Export_babylon.write_string(file_handler, "parentId", forcedParent.name)
  493. if len(object.material_slots) == 1:
  494. material = object.material_slots[0].material
  495. Export_babylon.write_string(file_handler, "materialId", object.material_slots[0].name)
  496. if material.game_settings.face_orientation != "BILLBOARD":
  497. billboardMode = 0
  498. else:
  499. billboardMode = 7
  500. elif len(object.material_slots) > 1:
  501. multimat = MultiMaterial()
  502. multimat.name = "Multimaterial#" + str(len(multiMaterials))
  503. multimat.materials = []
  504. Export_babylon.write_string(file_handler, "materialId", multimat.name)
  505. for mat in object.material_slots:
  506. multimat.materials.append(mat.name)
  507. multiMaterials.append(multimat)
  508. billboardMode = 0
  509. else:
  510. billboardMode = 0
  511. if forcedParent is None:
  512. Export_babylon.write_vector(file_handler, "position", loc)
  513. Export_babylon.write_vectorScaled(file_handler, "rotation", rot.to_euler("XYZ"), -1)
  514. Export_babylon.write_vector(file_handler, "scaling", scale)
  515. else:
  516. Export_babylon.write_vector(file_handler, "position", mathutils.Vector((0, 0, 0)))
  517. Export_babylon.write_vectorScaled(file_handler, "rotation", mathutils.Vector((0, 0, 0)), 1)
  518. Export_babylon.write_vector(file_handler, "scaling", mathutils.Vector((1, 1, 1)))
  519. Export_babylon.write_bool(file_handler, "isVisible", object.is_visible(scene))
  520. Export_babylon.write_bool(file_handler, "isEnabled", True)
  521. Export_babylon.write_bool(file_handler, "useFlatShading", object.data.useFlatShading)
  522. Export_babylon.write_bool(file_handler, "checkCollisions", object.data.checkCollisions)
  523. Export_babylon.write_int(file_handler, "billboardMode", billboardMode)
  524. Export_babylon.write_bool(file_handler, "receiveShadows", object.data.receiveShadows)
  525. # Export Physics
  526. if object.rigid_body != None:
  527. shape_items = {'BOX': 1, 'SPHERE': 2}
  528. shape_type = shape_items[object.rigid_body.collision_shape]
  529. Export_babylon.write_int(file_handler, "physicsImpostor", shape_type)
  530. mass = object.rigid_body.mass
  531. if mass < 0.005:
  532. mass = 0
  533. Export_babylon.write_float(file_handler, "physicsMass", mass)
  534. Export_babylon.write_float(file_handler, "physicsFriction", object.rigid_body.friction)
  535. Export_babylon.write_float(file_handler, "physicsRestitution", object.rigid_body.restitution)
  536. # Geometry
  537. if hasSkeleton:
  538. i = 0
  539. for obj in [object for object in scene.objects if object.is_visible(scene)]:
  540. if (obj.type == 'ARMATURE'):
  541. if (obj.name == object.parent.name):
  542. Export_babylon.write_int(file_handler, "skeletonId", i)
  543. else:
  544. i = i+1
  545. file_handler.write(positions)
  546. file_handler.write(normals)
  547. if hasUV:
  548. file_handler.write(uvs)
  549. if hasUV2:
  550. file_handler.write(uvs2)
  551. if hasVertexColor:
  552. file_handler.write(colors)
  553. if hasSkeleton:
  554. file_handler.write(skeletonWeight)
  555. file_handler.write(skeletonIndices)
  556. file_handler.write(indices)
  557. # Sub meshes
  558. file_handler.write(",\"subMeshes\":[")
  559. first = True
  560. for subMesh in subMeshes:
  561. if first == False:
  562. file_handler.write(",")
  563. file_handler.write("{")
  564. Export_babylon.write_int(file_handler, "materialIndex", subMesh.materialIndex, True)
  565. Export_babylon.write_int(file_handler, "verticesStart", subMesh.verticesStart)
  566. Export_babylon.write_int(file_handler, "verticesCount", subMesh.verticesCount)
  567. Export_babylon.write_int(file_handler, "indexStart", subMesh.indexStart)
  568. Export_babylon.write_int(file_handler, "indexCount", subMesh.indexCount)
  569. file_handler.write("}")
  570. first = False
  571. file_handler.write("]")
  572. #Export Animations
  573. rotAnim = False
  574. locAnim = False
  575. scaAnim = False
  576. coma = False
  577. if object.animation_data:
  578. if object.animation_data.action:
  579. file_handler.write(",\"animations\":[")
  580. for fcurve in object.animation_data.action.fcurves:
  581. if fcurve.data_path == "rotation_euler" and rotAnim == False:
  582. Export_babylon.export_animation(object, scene, file_handler, "rotation_euler", "rotation", coma, -1)
  583. rotAnim = coma = True
  584. elif fcurve.data_path == "location" and locAnim == False:
  585. Export_babylon.export_animation(object, scene, file_handler, "location", "position", coma, 1)
  586. locAnim = coma = True
  587. elif fcurve.data_path == "scale" and scaAnim == False:
  588. Export_babylon.export_animation(object, scene, file_handler, "scale", "scaling", coma, 1)
  589. locAnim = coma = True
  590. file_handler.write("]")
  591. #Set Animations
  592. Export_babylon.write_bool(file_handler, "autoAnimate", True)
  593. Export_babylon.write_int(file_handler, "autoAnimateFrom", 0)
  594. Export_babylon.write_int(file_handler, "autoAnimateTo", bpy.context.scene.frame_end - bpy.context.scene.frame_start + 1)
  595. Export_babylon.write_bool(file_handler, "autoAnimateLoop", True)
  596. # Closing
  597. file_handler.write("}")
  598. return offsetFace
  599. def export_node(object, scene, file_handler):
  600. # Transform
  601. loc = mathutils.Vector((0, 0, 0))
  602. rot = mathutils.Quaternion((0, 0, 0, 1))
  603. scale = mathutils.Vector((1, 1, 1))
  604. world = object.matrix_world
  605. if (object.parent):
  606. world = object.parent.matrix_world.inverted() * object.matrix_world
  607. loc, rot, scale = world.decompose()
  608. # Writing node
  609. file_handler.write("{")
  610. Export_babylon.write_string(file_handler, "name", object.name, True)
  611. Export_babylon.write_string(file_handler, "id", object.name)
  612. if object.parent != None:
  613. Export_babylon.write_string(file_handler, "parentId", object.parent.name)
  614. Export_babylon.write_vector(file_handler, "position", loc)
  615. Export_babylon.write_vectorScaled(file_handler, "rotation", rot.to_euler("XYZ"), -1)
  616. Export_babylon.write_vector(file_handler, "scaling", scale)
  617. Export_babylon.write_bool(file_handler, "isVisible", False)
  618. Export_babylon.write_bool(file_handler, "isEnabled", True)
  619. Export_babylon.write_bool(file_handler, "checkCollisions", False)
  620. Export_babylon.write_int(file_handler, "billboardMode", 0)
  621. Export_babylon.write_bool(file_handler, "receiveShadows", False)
  622. # Closing
  623. file_handler.write("}")
  624. def export_shadowGenerator(lamp, scene, file_handler):
  625. file_handler.write("{")
  626. if lamp.data.shadowMap == 'VAR':
  627. Export_babylon.write_bool(file_handler, "useVarianceShadowMap", True, True)
  628. else:
  629. Export_babylon.write_bool(file_handler, "useVarianceShadowMap", False, True)
  630. Export_babylon.write_int(file_handler, "mapSize", lamp.data.shadowMapSize)
  631. Export_babylon.write_string(file_handler, "lightId", lamp.name)
  632. file_handler.write(",\"renderList\":[")
  633. multiMaterials = []
  634. first = True
  635. for object in [object for object in scene.objects]:
  636. if (object.type == 'MESH' and object.data.castShadows):
  637. if first != True:
  638. file_handler.write(",")
  639. first = False
  640. file_handler.write("\"" + object.name + "\"")
  641. file_handler.write("]")
  642. file_handler.write("}")
  643. def export_bone_matrix(armature, bone, label, file_handler):
  644. SystemMatrix = Matrix.Scale(-1, 4, Vector((0, 0, 1))) * Matrix.Rotation(radians(-90), 4, 'X')
  645. if (bone.parent):
  646. Export_babylon.write_matrix4(file_handler, label, (SystemMatrix * armature.matrix_world * bone.parent.matrix).inverted() * (SystemMatrix * armature.matrix_world * bone.matrix))
  647. else:
  648. Export_babylon.write_matrix4(file_handler, label, SystemMatrix * armature.matrix_world * bone.matrix)
  649. def export_bones(armature, scene, file_handler, id):
  650. file_handler.write("{")
  651. Export_babylon.write_string(file_handler, "name", armature.name, True)
  652. Export_babylon.write_int(file_handler, "id", id)
  653. file_handler.write(",\"bones\":[")
  654. bones = armature.pose.bones
  655. first = True
  656. j = 0
  657. for bone in bones:
  658. if first != True:
  659. file_handler.write(",")
  660. first = False
  661. file_handler.write("{")
  662. Export_babylon.write_string(file_handler, "name", bone.name, True)
  663. Export_babylon.write_int(file_handler, "index", j)
  664. Export_babylon.export_bone_matrix(armature, bone, "matrix", file_handler)
  665. if (bone.parent):
  666. parentId = 0
  667. for parent in bones:
  668. if parent == bone.parent:
  669. break;
  670. parentId += 1
  671. Export_babylon.write_int(file_handler, "parentBoneIndex", parentId)
  672. else:
  673. Export_babylon.write_int(file_handler, "parentBoneIndex", -1)
  674. #animation
  675. if (armature.animation_data.action):
  676. Export_babylon.export_bonesAnimation(armature, bone, scene, file_handler)
  677. j = j + 1
  678. file_handler.write("}")
  679. file_handler.write("]")
  680. file_handler.write("}")
  681. def export_bonesAnimation(armature, bone, scene, file_handler):
  682. file_handler.write(",\"animation\": {")
  683. start_frame = scene.frame_start
  684. end_frame = scene.frame_end
  685. fps = scene.render.fps
  686. Export_babylon.write_int(file_handler, "dataType", 3, True)
  687. Export_babylon.write_int(file_handler, "framePerSecond", fps)
  688. #keys
  689. file_handler.write(",\"keys\":[")
  690. for Frame in range(start_frame, end_frame + 1):
  691. file_handler.write("{")
  692. Export_babylon.write_int(file_handler, "frame", Frame, True)
  693. bpy.context.scene.frame_set(Frame)
  694. Export_babylon.export_bone_matrix(armature, bone, "values", file_handler)
  695. if Frame == end_frame:
  696. file_handler.write("}")
  697. else:
  698. file_handler.write("},")
  699. file_handler.write("]")
  700. Export_babylon.write_int(file_handler, "loopBehavior", 1)
  701. Export_babylon.write_string(file_handler, "name", "anim")
  702. Export_babylon.write_string(file_handler, "property", "_matrix")
  703. file_handler.write("}")
  704. bpy.context.scene.frame_set(start_frame)
  705. def save(operator, context, filepath="",
  706. use_apply_modifiers=False,
  707. use_triangulate=True,
  708. use_compress=False):
  709. # Open file
  710. file_handler = open(filepath, 'w')
  711. if bpy.ops.object.mode_set.poll():
  712. bpy.ops.object.mode_set(mode='OBJECT')
  713. # Writing scene
  714. scene=context.scene
  715. world = scene.world
  716. if world:
  717. world_ambient = world.ambient_color
  718. else:
  719. world_ambient = Color((0.0, 0.0, 0.0))
  720. bpy.ops.screen.animation_cancel()
  721. currentFrame = bpy.context.scene.frame_current
  722. bpy.context.scene.frame_set(0)
  723. file_handler.write("{")
  724. file_handler.write("\"autoClear\":true")
  725. Export_babylon.write_color(file_handler, "clearColor", world_ambient)
  726. Export_babylon.write_color(file_handler, "ambientColor", world_ambient)
  727. Export_babylon.write_vector(file_handler, "gravity", scene.gravity)
  728. if world and world.mist_settings.use_mist:
  729. Export_babylon.write_int(file_handler, "fogMode", 3)
  730. Export_babylon.write_color(file_handler, "fogColor", world.horizon_color)
  731. Export_babylon.write_float(file_handler, "fogStart", world.mist_settings.start)
  732. Export_babylon.write_float(file_handler, "fogEnd", world.mist_settings.depth)
  733. Export_babylon.write_float(file_handler, "fogDensity", 0.1)
  734. # Cameras
  735. file_handler.write(",\"cameras\":[")
  736. first = True
  737. for object in [object for object in scene.objects if object.is_visible(scene)]:
  738. if (object.type == 'CAMERA'):
  739. if first != True:
  740. file_handler.write(",")
  741. first = False
  742. data_string = Export_babylon.export_camera(object, scene, file_handler)
  743. file_handler.write("]")
  744. # Active camera
  745. if scene.camera != None:
  746. Export_babylon.write_string(file_handler, "activeCamera", scene.camera.name)
  747. # Lights
  748. file_handler.write(",\"lights\":[")
  749. first = True
  750. for object in [object for object in scene.objects if object.is_visible(scene)]:
  751. if (object.type == 'LAMP'):
  752. if first != True:
  753. file_handler.write(",")
  754. first = False
  755. data_string = Export_babylon.export_light(object, scene, file_handler)
  756. file_handler.write("]")
  757. # Materials
  758. materials = [mat for mat in bpy.data.materials if mat.users >= 1]
  759. file_handler.write(",\"materials\":[")
  760. first = True
  761. for material in materials:
  762. if first != True:
  763. file_handler.write(",")
  764. first = False
  765. data_string = Export_babylon.export_material(material, scene, file_handler, filepath)
  766. file_handler.write("]")
  767. # Meshes
  768. file_handler.write(",\"meshes\":[")
  769. multiMaterials = []
  770. first = True
  771. for object in [object for object in scene.objects]:
  772. if object.type == 'MESH' or object.type == 'EMPTY':
  773. if first != True:
  774. file_handler.write(",")
  775. first = False
  776. if object.type == 'MESH':
  777. forcedParent = None
  778. nameID = ""
  779. startFace = 0
  780. while True:
  781. startFace = Export_babylon.export_mesh(object, scene, file_handler, multiMaterials, startFace, forcedParent, str(nameID))
  782. if startFace == 0:
  783. break
  784. if forcedParent is None:
  785. nameID = 0
  786. forcedParent = object
  787. nameID = nameID + 1
  788. file_handler.write(",")
  789. else:
  790. data_string = Export_babylon.export_node(object, scene, file_handler)
  791. file_handler.write("]")
  792. # Multi-materials
  793. file_handler.write(",\"multiMaterials\":[")
  794. first = True
  795. for multimaterial in multiMaterials:
  796. if first != True:
  797. file_handler.write(",")
  798. first = False
  799. data_string = Export_babylon.export_multimaterial(multimaterial, scene, file_handler)
  800. file_handler.write("]")
  801. # Shadow generators
  802. file_handler.write(",\"shadowGenerators\":[")
  803. first = True
  804. for object in [object for object in scene.objects if object.is_visible(scene)]:
  805. if (object.type == 'LAMP' and object.data.shadowMap != 'NONE'):
  806. if first != True:
  807. file_handler.write(",")
  808. first = False
  809. data_string = Export_babylon.export_shadowGenerator(object, scene, file_handler)
  810. file_handler.write("]")
  811. # Armatures/Bones
  812. file_handler.write(",\"skeletons\":[")
  813. first = True
  814. i = 0
  815. for object in [object for object in scene.objects if object.is_visible(scene)]:
  816. if (object.type == 'ARMATURE'):
  817. if first != True:
  818. file_handler.write(",")
  819. first = False
  820. data_string = Export_babylon.export_bones(object, scene, file_handler, i)
  821. i = i+1
  822. file_handler.write("]")
  823. # Closing
  824. file_handler.write("}")
  825. file_handler.close()
  826. bpy.context.scene.frame_set(currentFrame)
  827. return {'FINISHED'}
  828. # UI
  829. bpy.types.Mesh.useFlatShading = BoolProperty(
  830. name="Use Flat Shading",
  831. default = False)
  832. bpy.types.Mesh.checkCollisions = BoolProperty(
  833. name="Check Collisions",
  834. default = False)
  835. bpy.types.Mesh.castShadows = BoolProperty(
  836. name="Cast Shadows",
  837. default = False)
  838. bpy.types.Mesh.receiveShadows = BoolProperty(
  839. name="Receive Shadows",
  840. default = False)
  841. bpy.types.Camera.checkCollisions = BoolProperty(
  842. name="Check Collisions",
  843. default = False)
  844. bpy.types.Camera.applyGravity = BoolProperty(
  845. name="Apply Gravity",
  846. default = False)
  847. bpy.types.Camera.ellipsoid = FloatVectorProperty(
  848. name="Ellipsoid",
  849. default = mathutils.Vector((0.2, 0.9, 0.2)))
  850. bpy.types.Lamp.shadowMap = EnumProperty(
  851. name="Shadow Map Type",
  852. items = (('NONE', "None", "No Shadow Maps"), ('STD', "Standard", "Use Standard Shadow Maps"), ('VAR', "Variance", "Use Variance Shadow Maps")),
  853. default = 'NONE')
  854. bpy.types.Lamp.shadowMapSize = IntProperty(
  855. name="Shadow Map Size",
  856. default = 512)
  857. class ObjectPanel(bpy.types.Panel):
  858. bl_label = "Babylon.js"
  859. bl_space_type = "PROPERTIES"
  860. bl_region_type = "WINDOW"
  861. bl_context = "data"
  862. def draw(self, context):
  863. ob = context.object
  864. if not ob or not ob.data:
  865. return
  866. layout = self.layout
  867. isMesh = isinstance(ob.data, bpy.types.Mesh)
  868. isCamera = isinstance(ob.data, bpy.types.Camera)
  869. isLight = isinstance(ob.data, bpy.types.Lamp)
  870. if isMesh:
  871. layout.prop(ob.data, 'useFlatShading')
  872. layout.prop(ob.data, 'checkCollisions')
  873. layout.prop(ob.data, 'castShadows')
  874. layout.prop(ob.data, 'receiveShadows')
  875. elif isCamera:
  876. layout.prop(ob.data, 'checkCollisions')
  877. layout.prop(ob.data, 'applyGravity')
  878. layout.prop(ob.data, 'ellipsoid')
  879. elif isLight:
  880. layout.prop(ob.data, 'shadowMap')
  881. layout.prop(ob.data, 'shadowMapSize')
  882. ### REGISTER ###
  883. def menu_func(self, context):
  884. self.layout.operator(Export_babylon.bl_idname, text="Babylon.js (.babylon)")
  885. def register():
  886. bpy.utils.register_module(__name__)
  887. bpy.types.INFO_MT_file_export.append(menu_func)
  888. def unregister():
  889. bpy.utils.unregister_module(__name__)
  890. bpy.types.INFO_MT_file_export.remove(menu_func)
  891. if __name__ == "__main__":
  892. register()