io_export_babylon.py 48 KB

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