json_exporter.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  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 in-lining
  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. self.needPhysics = False
  61. # Scene level sound
  62. if scene.attachedSound != '':
  63. self.sounds.append(Sound(scene.attachedSound, scene.autoPlaySound, scene.loopSound))
  64. # separate loop doing all skeletons, so available in Mesh to make skipping IK bones possible
  65. for object in [object for object in scene.objects]:
  66. scene.frame_set(currentFrame)
  67. if object.type == 'ARMATURE': #skeleton.pose.bones
  68. if object.is_visible(scene):
  69. self.skeletons.append(Skeleton(object, scene, skeletonId, scene.ignoreIKBones))
  70. skeletonId += 1
  71. else:
  72. Logger.warn('The following armature not visible in scene thus ignored: ' + object.name)
  73. # exclude lamps in this pass, so ShadowGenerator constructor can be passed meshesAnNodes
  74. for object in [object for object in scene.objects]:
  75. scene.frame_set(currentFrame)
  76. if object.type == 'CAMERA':
  77. if object.is_visible(scene): # no isInSelectedLayer() required, is_visible() handles this for them
  78. self.cameras.append(Camera(object))
  79. else:
  80. Logger.warn('The following camera not visible in scene thus ignored: ' + object.name)
  81. elif object.type == 'MESH':
  82. forcedParent = None
  83. nameID = ''
  84. nextStartFace = 0
  85. while True and self.isInSelectedLayer(object, scene):
  86. mesh = Mesh(object, scene, nextStartFace, forcedParent, nameID, self)
  87. if mesh.hasUnappliedTransforms and hasattr(mesh, 'skeletonWeights'):
  88. self.fatalError = 'Mesh: ' + mesh.name + ' has un-applied transformations. This will never work for a mesh with an armature. Export cancelled'
  89. Logger.log(self.fatalError)
  90. return
  91. if hasattr(mesh, 'physicsImpostor'): self.needPhysics = True
  92. if hasattr(mesh, 'instances'):
  93. self.meshesAndNodes.append(mesh)
  94. else:
  95. break
  96. if object.data.attachedSound != '':
  97. self.sounds.append(Sound(object.data.attachedSound, object.data.autoPlaySound, object.data.loopSound, object))
  98. nextStartFace = mesh.offsetFace
  99. if nextStartFace == 0:
  100. break
  101. if forcedParent is None:
  102. nameID = 0
  103. forcedParent = object
  104. Logger.warn('The following mesh has exceeded the maximum # of vertex elements & will be broken into multiple Babylon meshes: ' + object.name)
  105. nameID = nameID + 1
  106. elif object.type == 'EMPTY':
  107. self.meshesAndNodes.append(Node(object))
  108. elif object.type != 'LAMP' and object.type != 'ARMATURE':
  109. Logger.warn('The following object (type - ' + object.type + ') is not currently exportable thus ignored: ' + object.name)
  110. # Lamp / shadow Generator pass; meshesAnNodes complete & forceParents included
  111. for object in [object for object in scene.objects]:
  112. if object.type == 'LAMP':
  113. if object.is_visible(scene): # no isInSelectedLayer() required, is_visible() handles this for them
  114. bulb = Light(object, self.meshesAndNodes)
  115. self.lights.append(bulb)
  116. if object.data.shadowMap != 'NONE':
  117. if bulb.light_type == DIRECTIONAL_LIGHT or bulb.light_type == SPOT_LIGHT:
  118. self.shadowGenerators.append(ShadowGenerator(object, self.meshesAndNodes, scene))
  119. else:
  120. Logger.warn('Only directional (sun) and spot types of lamp are valid for shadows thus ignored: ' + object.name)
  121. else:
  122. Logger.warn('The following lamp not visible in scene thus ignored: ' + object.name)
  123. bpy.context.scene.frame_set(currentFrame)
  124. # output file
  125. self.to_scene_file()
  126. except:# catch *all* exceptions
  127. log.log_error_stack()
  128. raise
  129. finally:
  130. log.close()
  131. self.nWarnings = log.nWarnings
  132. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  133. def to_scene_file(self):
  134. Logger.log('========= Writing of scene file started =========', 0)
  135. # Open file
  136. file_handler = open(self.filepathMinusExtension + '.babylon', 'w', encoding='utf8')
  137. file_handler.write('{')
  138. file_handler.write('"producer":{"name":"Blender","version":"' + bpy.app.version_string + '","exporter_version":"' + format_exporter_version() + '","file":"' + JsonExporter.nameSpace + '.babylon"},\n')
  139. self.world.to_scene_file(file_handler, self.needPhysics)
  140. # Materials
  141. file_handler.write(',\n"materials":[')
  142. first = True
  143. for material in self.materials:
  144. if first != True:
  145. file_handler.write(',\n')
  146. first = False
  147. material.to_scene_file(file_handler)
  148. file_handler.write(']')
  149. # Multi-materials
  150. file_handler.write(',\n"multiMaterials":[')
  151. first = True
  152. for multimaterial in self.multiMaterials:
  153. if first != True:
  154. file_handler.write(',')
  155. first = False
  156. multimaterial.to_scene_file(file_handler)
  157. file_handler.write(']')
  158. # Armatures/Bones
  159. file_handler.write(',\n"skeletons":[')
  160. first = True
  161. for skeleton in self.skeletons:
  162. if first != True:
  163. file_handler.write(',')
  164. first = False
  165. skeleton.to_scene_file(file_handler)
  166. file_handler.write(']')
  167. # Meshes
  168. file_handler.write(',\n"meshes":[')
  169. first = True
  170. for mesh in self.meshesAndNodes:
  171. if first != True:
  172. file_handler.write(',')
  173. first = False
  174. mesh.to_scene_file(file_handler)
  175. file_handler.write(']')
  176. # Cameras
  177. file_handler.write(',\n"cameras":[')
  178. first = True
  179. for camera in self.cameras:
  180. if hasattr(camera, 'fatalProblem'): continue
  181. if first != True:
  182. file_handler.write(',')
  183. first = False
  184. camera.update_for_target_attributes(self.meshesAndNodes)
  185. camera.to_scene_file(file_handler)
  186. file_handler.write(']')
  187. # Active camera
  188. if hasattr(self, 'activeCamera'):
  189. write_string(file_handler, 'activeCamera', self.activeCamera)
  190. # Lights
  191. file_handler.write(',\n"lights":[')
  192. first = True
  193. for light in self.lights:
  194. if first != True:
  195. file_handler.write(',')
  196. first = False
  197. light.to_scene_file(file_handler)
  198. file_handler.write(']')
  199. # Shadow generators
  200. file_handler.write(',\n"shadowGenerators":[')
  201. first = True
  202. for shadowGen in self.shadowGenerators:
  203. if first != True:
  204. file_handler.write(',')
  205. first = False
  206. shadowGen.to_scene_file(file_handler)
  207. file_handler.write(']')
  208. # Sounds
  209. if len(self.sounds) > 0:
  210. file_handler.write('\n,"sounds":[')
  211. first = True
  212. for sound in self.sounds:
  213. if first != True:
  214. file_handler.write(',')
  215. first = False
  216. sound.to_scene_file(file_handler)
  217. file_handler.write(']')
  218. # Closing
  219. file_handler.write('\n}')
  220. file_handler.close()
  221. Logger.log('========= Writing of scene file completed =========', 0)
  222. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  223. def getMaterial(self, baseMaterialId):
  224. fullName = JsonExporter.nameSpace + '.' + baseMaterialId
  225. for material in self.materials:
  226. if material.name == fullName:
  227. return material
  228. return None
  229. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  230. def getSourceMeshInstance(self, dataName):
  231. for mesh in self.meshesAndNodes:
  232. # nodes have no 'dataName', cannot be instanced in any case
  233. if hasattr(mesh, 'dataName') and mesh.dataName == dataName:
  234. return mesh
  235. return None
  236. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  237. def isInSelectedLayer(self, obj, scene):
  238. if not scene.export_onlySelectedLayer:
  239. return True
  240. for l in range(0, len(scene.layers)):
  241. if obj.layers[l] and scene.layers[l]:
  242. return True
  243. return False
  244. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  245. def get_skeleton(self, name):
  246. for skeleton in self.skeletons:
  247. if skeleton.name == name:
  248. return skeleton
  249. #really cannot happen, will cause exception in caller
  250. return None