io_export_babylon.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593
  1. bl_info = {
  2. "name": "Babylon.js",
  3. "author": "David Catuhe",
  4. "version": (1, 0),
  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. class SubMesh:
  25. materialIndex = 0
  26. verticesStart = 0
  27. verticesCount = 0
  28. indexStart = 0
  29. indexCount = 0
  30. class MultiMaterial:
  31. name = ""
  32. materials = []
  33. class Export_babylon(bpy.types.Operator, ExportHelper):
  34. """Export Babylon.js scene (.babylon)"""
  35. bl_idname = "scene.babylon"
  36. bl_label = "Export Babylon.js scene"
  37. filename_ext = ".babylon"
  38. filepath = ""
  39. # global_scale = FloatProperty(name="Scale", min=0.01, max=1000.0, default=1.0)
  40. def execute(self, context):
  41. return Export_babylon.save(self, context, **self.as_keywords(ignore=("check_existing", "filter_glob", "global_scale")))
  42. def mesh_triangulate(mesh):
  43. import bmesh
  44. bm = bmesh.new()
  45. bm.from_mesh(mesh)
  46. bmesh.ops.triangulate(bm, faces=bm.faces)
  47. bm.to_mesh(mesh)
  48. mesh.calc_tessface()
  49. bm.free()
  50. def write_array3(file_handler, name, array):
  51. file_handler.write(",\""+name+"\":[" + "%.4f,%.4f,%.4f"%(array[0],array[1],array[2]) + "]")
  52. def write_color(file_handler, name, color):
  53. file_handler.write(",\""+name+"\":[" + "%.4f,%.4f,%.4f"%(color.r,color.g,color.b) + "]")
  54. def write_vector(file_handler, name, vector):
  55. file_handler.write(",\""+name+"\":[" + "%.4f,%.4f,%.4f"%(vector.x,vector.z,vector.y) + "]")
  56. def write_string(file_handler, name, string, noComma=False):
  57. if noComma == False:
  58. file_handler.write(",")
  59. file_handler.write("\""+name+"\":\"" + string + "\"")
  60. def write_float(file_handler, name, float):
  61. file_handler.write(",\""+name+"\":" + "%.4f"%(float))
  62. def write_int(file_handler, name, int, noComma=False):
  63. if noComma == False:
  64. file_handler.write(",")
  65. file_handler.write("\""+name+"\":" + str(int))
  66. def write_bool(file_handler, name, bool):
  67. if bool:
  68. file_handler.write(",\""+name+"\":" + "true")
  69. else:
  70. file_handler.write(",\""+name+"\":" + "false")
  71. def export_camera(object, scene, file_handler):
  72. invWorld = object.matrix_world.copy()
  73. invWorld.invert()
  74. target = mathutils.Vector((0, 1, 0)) * invWorld
  75. file_handler.write("{")
  76. Export_babylon.write_string(file_handler, "name", object.name, True)
  77. Export_babylon.write_string(file_handler, "id", object.name)
  78. Export_babylon.write_vector(file_handler, "position", object.location)
  79. Export_babylon.write_vector(file_handler, "target", target)
  80. Export_babylon.write_float(file_handler, "fov", object.data.angle)
  81. Export_babylon.write_float(file_handler, "minZ", object.data.clip_start)
  82. Export_babylon.write_float(file_handler, "maxZ", object.data.clip_end)
  83. Export_babylon.write_float(file_handler, "speed", 1.0)
  84. Export_babylon.write_float(file_handler, "inertia", 0.9)
  85. Export_babylon.write_bool(file_handler, "checkCollisions", object.data.checkCollisions)
  86. Export_babylon.write_bool(file_handler, "applyGravity", object.data.applyGravity)
  87. Export_babylon.write_array3(file_handler, "ellipsoid", object.data.ellipsoid)
  88. file_handler.write("}")
  89. def export_light(object, scene, file_handler):
  90. light_type_items = {'POINT': 0, 'SUN': 1, 'SPOT': 2, 'HEMI': 3, 'AREA': 0}
  91. light_type = light_type_items[object.data.type]
  92. file_handler.write("{")
  93. Export_babylon.write_string(file_handler, "name", object.name, True)
  94. Export_babylon.write_string(file_handler, "id", object.name)
  95. Export_babylon.write_float(file_handler, "type", light_type)
  96. if light_type == 0:
  97. Export_babylon.write_vector(file_handler, "data", object.location)
  98. elif light_type == 1:
  99. matrix_world = object.matrix_world.copy()
  100. matrix_world.translation = mathutils.Vector((0, 0, 0))
  101. direction = mathutils.Vector((0, 0, -1)) * matrix_world
  102. Export_babylon.write_vector(file_handler, "data", direction)
  103. elif light_type == 2:
  104. Export_babylon.write_vector(file_handler, "data", object.location)
  105. matrix_world = object.matrix_world.copy()
  106. matrix_world.translation = mathutils.Vector((0, 0, 0))
  107. direction = mathutils.Vector((0, 0, -1)) * matrix_world
  108. Export_babylon.write_vector(file_handler, "direction", direction)
  109. Export_babylon.write_float(file_handler, "angle", object.data.spot_size)
  110. Export_babylon.write_float(file_handler, "exponent", object.data.spot_blend * 2)
  111. else:
  112. matrix_world = object.matrix_world.copy()
  113. matrix_world.translation = mathutils.Vector((0, 0, 0))
  114. direction = mathutils.Vector((0, 0, -1)) * matrix_world
  115. Export_babylon.write_vector(file_handler, "data", -direction)
  116. Export_babylon.write_color(file_handler, "groundColor", mathutils.Color((0, 0, 0)))
  117. Export_babylon.write_float(file_handler, "intensity", object.data.energy)
  118. if object.data.use_diffuse:
  119. Export_babylon.write_color(file_handler, "diffuse", object.data.color)
  120. else:
  121. Export_babylon.write_color(file_handler, "diffuse", mathutils.Color((0, 0, 0)))
  122. if object.data.use_specular:
  123. Export_babylon.write_color(file_handler, "specular", object.data.color)
  124. else:
  125. Export_babylon.write_color(file_handler, "specular", mathutils.Color((0, 0, 0)))
  126. file_handler.write("}")
  127. def export_texture(slot, level, texture, scene, file_handler, filepath):
  128. # Copy image to output
  129. try:
  130. image = texture.texture.image
  131. imageFilepath = os.path.normpath(bpy.path.abspath(image.filepath))
  132. basename = os.path.basename(imageFilepath)
  133. targetdir = os.path.dirname(filepath)
  134. targetpath = os.path.join(targetdir, basename)
  135. if image.packed_file:
  136. image.save_render(targetpath)
  137. else:
  138. sourcepath = bpy.path.abspath(image.filepath)
  139. shutil.copy(sourcepath, targetdir)
  140. except:
  141. pass
  142. # Export
  143. file_handler.write(",\""+slot+"\":{")
  144. Export_babylon.write_string(file_handler, "name", basename, True)
  145. Export_babylon.write_float(file_handler, "level", level)
  146. Export_babylon.write_float(file_handler, "hasAlpha", texture.texture.use_alpha)
  147. coordinatesMode = 0;
  148. if (texture.mapping == "CUBE"):
  149. coordinatesMode = 3;
  150. if (texture.mapping == "SPHERE"):
  151. coordinatesMode = 1;
  152. Export_babylon.write_int(file_handler, "coordinatesMode", coordinatesMode)
  153. Export_babylon.write_float(file_handler, "uOffset", texture.offset.x)
  154. Export_babylon.write_float(file_handler, "vOffset", texture.offset.y)
  155. Export_babylon.write_float(file_handler, "uScale", texture.scale.x)
  156. Export_babylon.write_float(file_handler, "vScale", texture.scale.y)
  157. Export_babylon.write_float(file_handler, "uAng", 0)
  158. Export_babylon.write_float(file_handler, "vAng", 0)
  159. Export_babylon.write_float(file_handler, "wAng", 0)
  160. if (texture.texture.extension == "REPEAT"):
  161. Export_babylon.write_bool(file_handler, "wrapU", True)
  162. Export_babylon.write_bool(file_handler, "wrapV", True)
  163. else:
  164. Export_babylon.write_bool(file_handler, "wrapU", False)
  165. Export_babylon.write_bool(file_handler, "wrapV", False)
  166. Export_babylon.write_int(file_handler, "coordinatesIndex", 0)
  167. file_handler.write("}")
  168. def export_material(material, scene, file_handler, filepath):
  169. file_handler.write("{")
  170. Export_babylon.write_string(file_handler, "name", material.name, True)
  171. Export_babylon.write_string(file_handler, "id", material.name)
  172. Export_babylon.write_color(file_handler, "ambient", material.ambient * material.diffuse_color)
  173. Export_babylon.write_color(file_handler, "diffuse", material.diffuse_intensity * material.diffuse_color)
  174. Export_babylon.write_color(file_handler, "specular", material.specular_intensity * material.specular_color)
  175. Export_babylon.write_float(file_handler, "specularPower", material.specular_hardness)
  176. Export_babylon.write_color(file_handler, "emissive", material.emit * material.diffuse_color)
  177. Export_babylon.write_float(file_handler, "alpha", material.alpha)
  178. Export_babylon.write_bool(file_handler, "backFaceCulling", material.game_settings.use_backface_culling)
  179. # Textures
  180. for mtex in material.texture_slots:
  181. if mtex and mtex.texture and mtex.texture.type == 'IMAGE':
  182. if mtex.texture.image:
  183. if (mtex.use_map_color_diffuse and(mtex.texture_coords != 'REFLECTION')):
  184. # Diffuse
  185. Export_babylon.export_texture("diffuseTexture", mtex.diffuse_color_factor, mtex, scene, file_handler, filepath)
  186. if mtex.use_map_ambient:
  187. # Ambient
  188. Export_babylon.export_texture("ambientTexture", mtex.ambient_factor, mtex, scene, file_handler, filepath)
  189. if mtex.use_map_alpha:
  190. # Opacity
  191. Export_babylon.export_texture("opacityTexture", mtex.alpha_factor, mtex, scene, file_handler, filepath)
  192. if mtex.use_map_color_diffuse and (mtex.texture_coords == 'REFLECTION'):
  193. # Reflection
  194. Export_babylon.export_texture("reflectionTexture", mtex.diffuse_color_factor, mtex, scene, file_handler, filepath)
  195. if mtex.use_map_emit:
  196. # Emissive
  197. Export_babylon.export_texture("emissiveTexture", mtex.emit_factor, mtex, scene, file_handler, filepath)
  198. file_handler.write("}")
  199. def export_multimaterial(multimaterial, scene, file_handler):
  200. file_handler.write("{")
  201. Export_babylon.write_string(file_handler, "name", multimaterial.name, True)
  202. Export_babylon.write_string(file_handler, "id", multimaterial.name)
  203. file_handler.write(",\"materials\":[")
  204. first = True
  205. for materialName in multimaterial.materials:
  206. if first != True:
  207. file_handler.write(",")
  208. file_handler.write("\"" + materialName +"\"")
  209. first = False
  210. file_handler.write("]")
  211. file_handler.write("}")
  212. def export_mesh(object, scene, file_handler, multiMaterials):
  213. # Get mesh
  214. mesh = object.to_mesh(scene, True, "PREVIEW")
  215. # Transform
  216. matrix_world = object.matrix_world.copy()
  217. matrix_world.translation = mathutils.Vector((0, 0, 0))
  218. mesh.transform(matrix_world)
  219. # Triangulate mesh if required
  220. Export_babylon.mesh_triangulate(mesh)
  221. # Getting vertices and indices
  222. vertices=",\"vertices\":["
  223. indices=",\"indices\":["
  224. hasUV = True;
  225. hasUV2 = True;
  226. if len(mesh.tessface_uv_textures) > 0:
  227. UVmap=mesh.tessface_uv_textures[0].data
  228. else:
  229. hasUV = False
  230. if len(mesh.tessface_uv_textures) > 1:
  231. UV2map=mesh.tessface_uv_textures[1].data
  232. else:
  233. hasUV2 = False
  234. alreadySavedVertices = []
  235. vertices_UVs=[]
  236. vertices_UV2s=[]
  237. vertices_indices=[]
  238. subMeshes = []
  239. for v in range(0, len(mesh.vertices)):
  240. alreadySavedVertices.append(False)
  241. vertices_UVs.append([])
  242. vertices_UV2s.append([])
  243. vertices_indices.append([])
  244. materialsCount = max(1, len(object.material_slots))
  245. verticesCount = 0
  246. indicesCount = 0
  247. for materialIndex in range(materialsCount):
  248. subMeshes.append(SubMesh())
  249. subMeshes[materialIndex].materialIndex = materialIndex
  250. subMeshes[materialIndex].verticesStart = verticesCount
  251. subMeshes[materialIndex].indexStart = indicesCount
  252. for face in mesh.tessfaces: # For each face
  253. if face.material_index != materialIndex:
  254. continue
  255. for v in range(3): # For each vertex in face
  256. vertex_index = face.vertices[v]
  257. vertex = mesh.vertices[vertex_index]
  258. position = vertex.co
  259. normal = vertex.normal
  260. if hasUV:
  261. vertex_UV = UVmap[face.index].uv[v]
  262. if hasUV2:
  263. vertex_UV2 = UV2map[face.index].uv[v]
  264. # Check if the current vertex is already saved
  265. alreadySaved = alreadySavedVertices[vertex_index]
  266. index_UV = 0
  267. if alreadySaved:
  268. alreadySaved=False
  269. if hasUV:
  270. for vUV in vertices_UVs[vertex_index]:
  271. if (vUV[0]==vertex_UV[0] and vUV[1]==vertex_UV[1]):
  272. if hasUV2:
  273. vUV2 = vertices_UV2s[vertex_index][index_UV]
  274. if (vUV2[0]==vertex_UV2[0] and vUV2[1]==vertex_UV2[1]):
  275. if vertices_indices[vertex_index][index_UV] >= subMeshes[materialIndex].verticesStart:
  276. alreadySaved=True
  277. break
  278. else:
  279. alreadySaved=True
  280. break
  281. index_UV+=1
  282. else:
  283. for savedIndex in vertices_indices[vertex_index]:
  284. if savedIndex >= subMeshes[materialIndex].verticesStart:
  285. alreadySaved=True
  286. break
  287. index_UV+=1
  288. if (alreadySaved):
  289. # Reuse vertex
  290. index=vertices_indices[vertex_index][index_UV]
  291. else:
  292. # Export new one
  293. index=verticesCount
  294. alreadySavedVertices[vertex_index]=True
  295. if hasUV:
  296. vertices_UVs[vertex_index].append(vertex_UV)
  297. if hasUV2:
  298. vertices_UV2s[vertex_index].append(vertex_UV2)
  299. vertices_indices[vertex_index].append(index)
  300. vertices+="%.4f,%.4f,%.4f,"%(position.x,position.z,position.y)
  301. vertices+="%.4f,%.4f,%.4f,"%(normal.x,normal.z,normal.y)
  302. if hasUV:
  303. vertices+="%.4f,%.4f,"%(vertex_UV[0], vertex_UV[1])
  304. if hasUV2:
  305. vertices+="%.4f,%.4f,"%(vertex_UV2[0], vertex_UV2[1])
  306. verticesCount += 1
  307. indices+="%i,"%(index)
  308. indicesCount += 1
  309. subMeshes[materialIndex].verticesCount = verticesCount - subMeshes[materialIndex].verticesStart
  310. subMeshes[materialIndex].indexCount = indicesCount - subMeshes[materialIndex].indexStart
  311. vertices=vertices.rstrip(',')
  312. indices=indices.rstrip(',')
  313. vertices+="]\n"
  314. indices+="]\n"
  315. # Writing mesh
  316. file_handler.write("{")
  317. Export_babylon.write_string(file_handler, "name", object.name, True)
  318. Export_babylon.write_string(file_handler, "id", object.name)
  319. if object.parent != None:
  320. Export_babylon.write_string(file_handler, "parentId", object.parent.name)
  321. if len(object.material_slots) == 1:
  322. material = object.material_slots[0].material
  323. Export_babylon.write_string(file_handler, "materialId", object.material_slots[0].name)
  324. if material.game_settings.face_orientation != "BILLBOARD":
  325. billboardMode = 0
  326. else:
  327. billboardMode = 7
  328. elif len(object.material_slots) > 1:
  329. multimat = MultiMaterial()
  330. multimat.name = "Multimaterial#" + str(len(multiMaterials))
  331. Export_babylon.write_string(file_handler, "materialId", multimat.name)
  332. for mat in object.material_slots:
  333. multimat.materials.append(mat.name)
  334. multiMaterials.append(multimat)
  335. billboardMode = 0
  336. else:
  337. billboardMode = 0
  338. Export_babylon.write_vector(file_handler, "position", object.location)
  339. Export_babylon.write_vector(file_handler, "rotation", mathutils.Vector((0, 0, 0)))
  340. Export_babylon.write_vector(file_handler, "scaling", mathutils.Vector((1, 1, 1)))
  341. Export_babylon.write_bool(file_handler, "isVisible", object.is_visible(scene))
  342. Export_babylon.write_bool(file_handler, "isEnabled", True)
  343. Export_babylon.write_bool(file_handler, "checkCollisions", object.data.checkCollisions)
  344. Export_babylon.write_int(file_handler, "billboardMode", billboardMode)
  345. if hasUV and hasUV2:
  346. Export_babylon.write_int(file_handler, "uvCount", 2)
  347. elif hasUV:
  348. Export_babylon.write_int(file_handler, "uvCount", 1)
  349. else:
  350. Export_babylon.write_int(file_handler, "uvCount", 0)
  351. file_handler.write(vertices)
  352. file_handler.write(indices)
  353. # Sub meshes
  354. file_handler.write(",\"subMeshes\":[")
  355. first = True
  356. for subMesh in subMeshes:
  357. if first == False:
  358. file_handler.write(",")
  359. file_handler.write("{")
  360. Export_babylon.write_int(file_handler, "materialIndex", subMesh.materialIndex, True)
  361. Export_babylon.write_int(file_handler, "verticesStart", subMesh.verticesStart)
  362. Export_babylon.write_int(file_handler, "verticesCount", subMesh.verticesCount)
  363. Export_babylon.write_int(file_handler, "indexStart", subMesh.indexStart)
  364. Export_babylon.write_int(file_handler, "indexCount", subMesh.indexCount)
  365. file_handler.write("}")
  366. first = False
  367. file_handler.write("]")
  368. # Closing
  369. file_handler.write("}")
  370. def save(operator, context, filepath="",
  371. use_apply_modifiers=False,
  372. use_triangulate=True,
  373. use_compress=False):
  374. # Open file
  375. file_handler = open(filepath, 'w')
  376. if bpy.ops.object.mode_set.poll():
  377. bpy.ops.object.mode_set(mode='OBJECT')
  378. # Writing scene
  379. scene=context.scene
  380. world = scene.world
  381. if world:
  382. world_ambient = world.ambient_color
  383. else:
  384. world_ambient = Color((0.0, 0.0, 0.0))
  385. file_handler.write("{")
  386. file_handler.write("\"autoClear\":true")
  387. Export_babylon.write_color(file_handler, "clearColor", world_ambient)
  388. Export_babylon.write_color(file_handler, "ambientColor", world_ambient)
  389. Export_babylon.write_vector(file_handler, "gravity", scene.gravity)
  390. # Cameras
  391. file_handler.write(",\"cameras\":[")
  392. first = True
  393. for object in [object for object in scene.objects if object.is_visible(scene)]:
  394. if (object.type == 'CAMERA'):
  395. if first != True:
  396. file_handler.write(",")
  397. first = False
  398. data_string = Export_babylon.export_camera(object, scene, file_handler)
  399. file_handler.write("]")
  400. # Active camera
  401. if scene.camera != None:
  402. Export_babylon.write_string(file_handler, "activeCamera", scene.camera.name)
  403. # Lights
  404. file_handler.write(",\"lights\":[")
  405. first = True
  406. for object in [object for object in scene.objects if object.is_visible(scene)]:
  407. if (object.type == 'LAMP'):
  408. if first != True:
  409. file_handler.write(",")
  410. first = False
  411. data_string = Export_babylon.export_light(object, scene, file_handler)
  412. file_handler.write("]")
  413. # Materials
  414. materials = [mat for mat in bpy.data.materials if mat.users >= 1]
  415. file_handler.write(",\"materials\":[")
  416. first = True
  417. for material in materials:
  418. if first != True:
  419. file_handler.write(",")
  420. first = False
  421. data_string = Export_babylon.export_material(material, scene, file_handler, filepath)
  422. file_handler.write("]")
  423. # Meshes
  424. file_handler.write(",\"meshes\":[")
  425. multiMaterials = []
  426. first = True
  427. for object in [object for object in scene.objects]:
  428. if (object.type == 'MESH'):
  429. if first != True:
  430. file_handler.write(",")
  431. first = False
  432. data_string = Export_babylon.export_mesh(object, scene, file_handler, multiMaterials)
  433. file_handler.write("]")
  434. # Multi-materials
  435. file_handler.write(",\"multiMaterials\":[")
  436. first = True
  437. for multimaterial in multiMaterials:
  438. if first != True:
  439. file_handler.write(",")
  440. first = False
  441. data_string = Export_babylon.export_multimaterial(multimaterial, scene, file_handler)
  442. file_handler.write("]")
  443. # Closing
  444. file_handler.write("}")
  445. file_handler.close()
  446. return {'FINISHED'}
  447. # UI
  448. bpy.types.Mesh.checkCollisions = BoolProperty(
  449. name="Check collisions",
  450. default = False)
  451. bpy.types.Camera.checkCollisions = BoolProperty(
  452. name="Check collisions",
  453. default = False)
  454. bpy.types.Camera.applyGravity = BoolProperty(
  455. name="Apply Gravity",
  456. default = False)
  457. bpy.types.Camera.ellipsoid = FloatVectorProperty(
  458. name="Ellipsoid",
  459. default = mathutils.Vector((0.2, 0.9, 0.2)))
  460. class ObjectPanel(bpy.types.Panel):
  461. bl_label = "Babylon.js"
  462. bl_space_type = "PROPERTIES"
  463. bl_region_type = "WINDOW"
  464. bl_context = "data"
  465. def draw(self, context):
  466. ob = context.object
  467. if not ob or not ob.data:
  468. return
  469. layout = self.layout
  470. isMesh = isinstance(ob.data, bpy.types.Mesh)
  471. isCamera = isinstance(ob.data, bpy.types.Camera)
  472. if isMesh:
  473. layout.prop(ob.data, 'checkCollisions')
  474. elif isCamera:
  475. layout.prop(ob.data, 'checkCollisions')
  476. layout.prop(ob.data, 'applyGravity')
  477. layout.prop(ob.data, 'ellipsoid')
  478. ### REGISTER ###
  479. def menu_func(self, context):
  480. self.layout.operator(Export_babylon.bl_idname, text="Babylon.js (.babylon)")
  481. def register():
  482. bpy.utils.register_module(__name__)
  483. bpy.types.INFO_MT_file_export.append(menu_func)
  484. def unregister():
  485. bpy.utils.unregister_module(__name__)
  486. bpy.types.INFO_MT_file_export.remove(menu_func)
  487. if __name__ == "__main__":
  488. register()