json_exporter.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. from .animation import *
  2. from .armature import *
  3. from .camera import *
  4. from .exporter_settings_panel import *
  5. from .light_shadow import *
  6. from .logger import *
  7. from .material import *
  8. from .mesh import *
  9. from .package_level import *
  10. from .sound import *
  11. from .world import *
  12. import bpy
  13. from io import open
  14. from os import path, makedirs
  15. #===============================================================================
  16. class JsonExporter:
  17. nameSpace = None # assigned in execute
  18. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  19. def execute(self, context, filepath):
  20. scene = context.scene
  21. self.scene = scene # reference for passing
  22. self.fatalError = None
  23. try:
  24. self.filepathMinusExtension = filepath.rpartition('.')[0]
  25. JsonExporter.nameSpace = getNameSpace(self.filepathMinusExtension)
  26. log = Logger(self.filepathMinusExtension + '.log')
  27. if bpy.ops.object.mode_set.poll():
  28. bpy.ops.object.mode_set(mode = 'OBJECT')
  29. # assign texture location, purely temporary if inlining
  30. self.textureDir = path.dirname(filepath)
  31. if not scene.inlineTextures:
  32. self.textureDir = path.join(self.textureDir, scene.textureDir)
  33. if not path.isdir(self.textureDir):
  34. makedirs(self.textureDir)
  35. Logger.warn("Texture sub-directory did not already exist, created: " + self.textureDir)
  36. Logger.log('========= Conversion from Blender to Babylon.js =========', 0)
  37. Logger.log('Scene settings used:', 1)
  38. Logger.log('selected layers only: ' + format_bool(scene.export_onlySelectedLayer), 2)
  39. Logger.log('flat shading entire scene: ' + format_bool(scene.export_flatshadeScene), 2)
  40. Logger.log('inline textures: ' + format_bool(scene.inlineTextures), 2)
  41. if not scene.inlineTextures:
  42. Logger.log('texture directory: ' + self.textureDir, 2)
  43. self.world = World(scene)
  44. bpy.ops.screen.animation_cancel()
  45. currentFrame = bpy.context.scene.frame_current
  46. # Active camera
  47. if scene.camera != None:
  48. self.activeCamera = scene.camera.name
  49. else:
  50. Logger.warn('No active camera has been assigned, or is not in a currently selected Blender layer')
  51. self.cameras = []
  52. self.lights = []
  53. self.shadowGenerators = []
  54. self.skeletons = []
  55. skeletonId = 0
  56. self.meshesAndNodes = []
  57. self.materials = []
  58. self.multiMaterials = []
  59. self.sounds = []
  60. # Scene level sound
  61. if scene.attachedSound != '':
  62. self.sounds.append(Sound(scene.attachedSound, scene.autoPlaySound, scene.loopSound))
  63. # separate loop doing all skeletons, so available in Mesh to make skipping IK bones possible
  64. for object in [object for object in scene.objects]:
  65. scene.frame_set(currentFrame)
  66. if object.type == 'ARMATURE': #skeleton.pose.bones
  67. if object.is_visible(scene):
  68. self.skeletons.append(Skeleton(object, scene, skeletonId, scene.ignoreIKBones))
  69. skeletonId += 1
  70. else:
  71. Logger.warn('The following armature not visible in scene thus ignored: ' + object.name)
  72. # exclude lamps in this pass, so ShadowGenerator constructor can be passed meshesAnNodes
  73. for object in [object for object in scene.objects]:
  74. scene.frame_set(currentFrame)
  75. if object.type == 'CAMERA':
  76. if object.is_visible(scene): # no isInSelectedLayer() required, is_visible() handles this for them
  77. self.cameras.append(Camera(object))
  78. else:
  79. Logger.warn('The following camera not visible in scene thus ignored: ' + object.name)
  80. elif object.type == 'MESH':
  81. forcedParent = None
  82. nameID = ''
  83. nextStartFace = 0
  84. while True and self.isInSelectedLayer(object, scene):
  85. mesh = Mesh(object, scene, nextStartFace, forcedParent, nameID, self)
  86. if mesh.hasUnappliedTransforms and hasattr(mesh, 'skeletonWeights'):
  87. self.fatalError = 'Mesh: ' + mesh.name + ' has unapplied transformations. This will never work for a mesh with an armature. Export cancelled'
  88. Logger.log(self.fatalError)
  89. return
  90. if hasattr(mesh, 'instances'):
  91. self.meshesAndNodes.append(mesh)
  92. else:
  93. break
  94. if object.data.attachedSound != '':
  95. self.sounds.append(Sound(object.data.attachedSound, object.data.autoPlaySound, object.data.loopSound, object))
  96. nextStartFace = mesh.offsetFace
  97. if nextStartFace == 0:
  98. break
  99. if forcedParent is None:
  100. nameID = 0
  101. forcedParent = object
  102. Logger.warn('The following mesh has exceeded the maximum # of vertex elements & will be broken into multiple Babylon meshes: ' + object.name)
  103. nameID = nameID + 1
  104. elif object.type == 'EMPTY':
  105. self.meshesAndNodes.append(Node(object))
  106. elif object.type != 'LAMP' and object.type != 'ARMATURE':
  107. Logger.warn('The following object (type - ' + object.type + ') is not currently exportable thus ignored: ' + object.name)
  108. # Lamp / shadow Generator pass; meshesAnNodes complete & forceParents included
  109. for object in [object for object in scene.objects]:
  110. if object.type == 'LAMP':
  111. if object.is_visible(scene): # no isInSelectedLayer() required, is_visible() handles this for them
  112. bulb = Light(object, self.meshesAndNodes)
  113. self.lights.append(bulb)
  114. if object.data.shadowMap != 'NONE':
  115. if bulb.light_type == DIRECTIONAL_LIGHT or bulb.light_type == SPOT_LIGHT:
  116. self.shadowGenerators.append(ShadowGenerator(object, self.meshesAndNodes, scene))
  117. else:
  118. Logger.warn('Only directional (sun) and spot types of lamp are valid for shadows thus ignored: ' + object.name)
  119. else:
  120. Logger.warn('The following lamp not visible in scene thus ignored: ' + object.name)
  121. bpy.context.scene.frame_set(currentFrame)
  122. # output file
  123. self.to_scene_file()
  124. except:# catch *all* exceptions
  125. log.log_error_stack()
  126. raise
  127. finally:
  128. log.close()
  129. self.nWarnings = log.nWarnings
  130. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  131. def to_scene_file(self):
  132. Logger.log('========= Writing of scene file started =========', 0)
  133. # Open file
  134. file_handler = open(self.filepathMinusExtension + '.babylon', 'w', encoding='utf8')
  135. file_handler.write('{')
  136. file_handler.write('"producer":{"name":"Blender","version":"' + bpy.app.version_string + '","exporter_version":"' + format_exporter_version() + '","file":"' + JsonExporter.nameSpace + '.babylon"},\n')
  137. self.world.to_scene_file(file_handler)
  138. # Materials
  139. file_handler.write(',\n"materials":[')
  140. first = True
  141. for material in self.materials:
  142. if first != True:
  143. file_handler.write(',\n')
  144. first = False
  145. material.to_scene_file(file_handler)
  146. file_handler.write(']')
  147. # Multi-materials
  148. file_handler.write(',\n"multiMaterials":[')
  149. first = True
  150. for multimaterial in self.multiMaterials:
  151. if first != True:
  152. file_handler.write(',')
  153. first = False
  154. multimaterial.to_scene_file(file_handler)
  155. file_handler.write(']')
  156. # Armatures/Bones
  157. file_handler.write(',\n"skeletons":[')
  158. first = True
  159. for skeleton in self.skeletons:
  160. if first != True:
  161. file_handler.write(',')
  162. first = False
  163. skeleton.to_scene_file(file_handler)
  164. file_handler.write(']')
  165. # Meshes
  166. file_handler.write(',\n"meshes":[')
  167. first = True
  168. for mesh in self.meshesAndNodes:
  169. if first != True:
  170. file_handler.write(',')
  171. first = False
  172. mesh.to_scene_file(file_handler)
  173. file_handler.write(']')
  174. # Cameras
  175. file_handler.write(',\n"cameras":[')
  176. first = True
  177. for camera in self.cameras:
  178. if hasattr(camera, 'fatalProblem'): continue
  179. if first != True:
  180. file_handler.write(',')
  181. first = False
  182. camera.update_for_target_attributes(self.meshesAndNodes)
  183. camera.to_scene_file(file_handler)
  184. file_handler.write(']')
  185. # Active camera
  186. if hasattr(self, 'activeCamera'):
  187. write_string(file_handler, 'activeCamera', self.activeCamera)
  188. # Lights
  189. file_handler.write(',\n"lights":[')
  190. first = True
  191. for light in self.lights:
  192. if first != True:
  193. file_handler.write(',')
  194. first = False
  195. light.to_scene_file(file_handler)
  196. file_handler.write(']')
  197. # Shadow generators
  198. file_handler.write(',\n"shadowGenerators":[')
  199. first = True
  200. for shadowGen in self.shadowGenerators:
  201. if first != True:
  202. file_handler.write(',')
  203. first = False
  204. shadowGen.to_scene_file(file_handler)
  205. file_handler.write(']')
  206. # Sounds
  207. if len(self.sounds) > 0:
  208. file_handler.write('\n,"sounds":[')
  209. first = True
  210. for sound in self.sounds:
  211. if first != True:
  212. file_handler.write(',')
  213. first = False
  214. sound.to_scene_file(file_handler)
  215. file_handler.write(']')
  216. # Closing
  217. file_handler.write('\n}')
  218. file_handler.close()
  219. Logger.log('========= Writing of scene file completed =========', 0)
  220. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  221. def getMaterial(self, baseMaterialId):
  222. fullName = JsonExporter.nameSpace + '.' + baseMaterialId
  223. for material in self.materials:
  224. if material.name == fullName:
  225. return material
  226. return None
  227. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  228. def getSourceMeshInstance(self, dataName):
  229. for mesh in self.meshesAndNodes:
  230. # nodes have no 'dataName', cannot be instanced in any case
  231. if hasattr(mesh, 'dataName') and mesh.dataName == dataName:
  232. return mesh
  233. return None
  234. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  235. def isInSelectedLayer(self, obj, scene):
  236. if not scene.export_onlySelectedLayer:
  237. return True
  238. for l in range(0, len(scene.layers)):
  239. if obj.layers[l] and scene.layers[l]:
  240. return True
  241. return False
  242. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  243. def get_skeleton(self, name):
  244. for skeleton in self.skeletons:
  245. if skeleton.name == name:
  246. return skeleton
  247. #really cannot happen, will cause exception in caller
  248. return None