scene.py 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. import os
  2. from .. import constants, logger
  3. from . import (
  4. base_classes,
  5. texture,
  6. material,
  7. geometry,
  8. object as object_,
  9. utilities,
  10. io,
  11. api
  12. )
  13. from bpy import context
  14. class Scene(base_classes.BaseScene):
  15. """Class that handles the contruction of a Three scene"""
  16. def __init__(self, filepath, options=None):
  17. logger.debug("Scene().__init__(%s, %s)", filepath, options)
  18. self._defaults = {
  19. constants.METADATA: constants.DEFAULT_METADATA.copy(),
  20. constants.GEOMETRIES: [],
  21. constants.MATERIALS: [],
  22. constants.IMAGES: [],
  23. constants.TEXTURES: [],
  24. constants.ANIMATION: []
  25. }
  26. base_classes.BaseScene.__init__(self, filepath, options or {})
  27. source_file = api.scene_name()
  28. if source_file:
  29. self[constants.METADATA][constants.SOURCE_FILE] = source_file
  30. self.__init_animation()
  31. def __init_animation(self):
  32. self[constants.ANIMATION].append({
  33. constants.NAME: "default",
  34. constants.FPS : context.scene.render.fps,
  35. constants.KEYFRAMES: []
  36. });
  37. pass
  38. @property
  39. def valid_types(self):
  40. """
  41. :return: list of valid node types
  42. """
  43. valid_types = [api.constants.MESH]
  44. if self.options.get(constants.HIERARCHY, False):
  45. valid_types.append(api.constants.EMPTY)
  46. if self.options.get(constants.CAMERAS):
  47. logger.info("Adding cameras to valid object types")
  48. valid_types.append(api.constants.CAMERA)
  49. if self.options.get(constants.LIGHTS):
  50. logger.info("Adding lights to valid object types")
  51. valid_types.append(api.constants.LAMP)
  52. return valid_types
  53. def geometry(self, value):
  54. """Find a geometry node that matches either a name
  55. or uuid value.
  56. :param value: name or uuid
  57. :type value: str
  58. """
  59. logger.debug("Scene().geometry(%s)", value)
  60. return _find_node(value, self[constants.GEOMETRIES])
  61. def image(self, value):
  62. """Find a image node that matches either a name
  63. or uuid value.
  64. :param value: name or uuid
  65. :type value: str
  66. """
  67. logger.debug("Scene().image%s)", value)
  68. return _find_node(value, self[constants.IMAGES])
  69. def material(self, value):
  70. """Find a material node that matches either a name
  71. or uuid value.
  72. :param value: name or uuid
  73. :type value: str
  74. """
  75. logger.debug("Scene().material(%s)", value)
  76. return _find_node(value, self[constants.MATERIALS])
  77. def parse(self):
  78. """Execute the parsing of the scene"""
  79. logger.debug("Scene().parse()")
  80. if self.options.get(constants.MAPS):
  81. self._parse_textures()
  82. if self.options.get(constants.MATERIALS):
  83. self._parse_materials()
  84. self._parse_geometries()
  85. self._parse_objects()
  86. def texture(self, value):
  87. """Find a texture node that matches either a name
  88. or uuid value.
  89. :param value: name or uuid
  90. :type value: str
  91. """
  92. logger.debug("Scene().texture(%s)", value)
  93. return _find_node(value, self[constants.TEXTURES])
  94. def write(self):
  95. """Write the parsed scene to disk."""
  96. logger.debug("Scene().write()")
  97. data = {}
  98. embed_anim = self.options.get(constants.EMBED_ANIMATION, True)
  99. embed = self.options.get(constants.EMBED_GEOMETRY, True)
  100. compression = self.options.get(constants.COMPRESSION)
  101. extension = constants.EXTENSIONS.get(
  102. compression,
  103. constants.EXTENSIONS[constants.JSON])
  104. export_dir = os.path.dirname(self.filepath)
  105. for key, value in self.items():
  106. if key == constants.GEOMETRIES:
  107. geometries = []
  108. for geom in value:
  109. if not embed_anim:
  110. geom.write_animation(export_dir)
  111. geom_data = geom.copy()
  112. if embed:
  113. geometries.append(geom_data)
  114. continue
  115. geo_type = geom_data[constants.TYPE].lower()
  116. if geo_type == constants.GEOMETRY.lower():
  117. geom_data.pop(constants.DATA)
  118. elif geo_type == constants.BUFFER_GEOMETRY.lower():
  119. geom_data.pop(constants.ATTRIBUTES)
  120. geom_data.pop(constants.METADATA)
  121. url = 'geometry.%s%s' % (geom.node, extension)
  122. geometry_file = os.path.join(export_dir, url)
  123. geom.write(filepath=geometry_file)
  124. geom_data[constants.URL] = os.path.basename(url)
  125. geometries.append(geom_data)
  126. data[key] = geometries
  127. elif isinstance(value, list):
  128. data[key] = []
  129. for each in value:
  130. data[key].append(each.copy())
  131. elif isinstance(value, dict):
  132. data[key] = value.copy()
  133. io.dump(self.filepath, data, options=self.options)
  134. if self.options.get(constants.COPY_TEXTURES):
  135. texture_folder = self.options.get(constants.TEXTURE_FOLDER)
  136. for geo in self[constants.GEOMETRIES]:
  137. logger.info("Copying textures from %s", geo.node)
  138. geo.copy_textures(texture_folder)
  139. def _parse_geometries(self):
  140. """Locate all geometry nodes and parse them"""
  141. logger.debug("Scene()._parse_geometries()")
  142. # this is an important step. please refer to the doc string
  143. # on the function for more information
  144. api.object.prep_meshes(self.options)
  145. geometries = []
  146. # now iterate over all the extracted mesh nodes and parse each one
  147. for mesh in api.object.extracted_meshes():
  148. logger.info("Parsing geometry %s", mesh)
  149. geo = geometry.Geometry(mesh, self)
  150. geo.parse()
  151. geometries.append(geo)
  152. logger.info("Added %d geometry nodes", len(geometries))
  153. self[constants.GEOMETRIES] = geometries
  154. def _parse_materials(self):
  155. """Locate all non-orphaned materials and parse them"""
  156. logger.debug("Scene()._parse_materials()")
  157. materials = []
  158. for material_name in api.material.used_materials():
  159. logger.info("Parsing material %s", material_name)
  160. materials.append(material.Material(material_name, parent=self))
  161. logger.info("Added %d material nodes", len(materials))
  162. self[constants.MATERIALS] = materials
  163. def _parse_objects(self):
  164. """Locate all valid objects in the scene and parse them"""
  165. logger.debug("Scene()._parse_objects()")
  166. try:
  167. scene_name = self[constants.METADATA][constants.SOURCE_FILE]
  168. except KeyError:
  169. scene_name = constants.SCENE
  170. self[constants.OBJECT] = object_.Object(None, parent=self)
  171. self[constants.OBJECT][constants.TYPE] = constants.SCENE.title()
  172. self[constants.UUID] = utilities.id_from_name(scene_name)
  173. objects = []
  174. if self.options.get(constants.HIERARCHY, False):
  175. nodes = api.object.assemblies(self.valid_types, self.options)
  176. else:
  177. nodes = api.object.nodes(self.valid_types, self.options)
  178. for node in nodes:
  179. logger.info("Parsing object %s", node)
  180. obj = object_.Object(node, parent=self[constants.OBJECT])
  181. objects.append(obj)
  182. logger.info("Added %d object nodes", len(objects))
  183. self[constants.OBJECT][constants.CHILDREN] = objects
  184. def _parse_textures(self):
  185. """Locate all non-orphaned textures and parse them"""
  186. logger.debug("Scene()._parse_textures()")
  187. textures = []
  188. for texture_name in api.texture.textures():
  189. logger.info("Parsing texture %s", texture_name)
  190. tex_inst = texture.Texture(texture_name, self)
  191. textures.append(tex_inst)
  192. logger.info("Added %d texture nodes", len(textures))
  193. self[constants.TEXTURES] = textures
  194. def _find_node(value, manifest):
  195. """Find a node that matches either a name
  196. or uuid value.
  197. :param value: name or uuid
  198. :param manifest: manifest of nodes to search
  199. :type value: str
  200. :type manifest: list
  201. """
  202. for index in manifest:
  203. uuid = index.get(constants.UUID) == value
  204. name = index.node == value
  205. if uuid or name:
  206. return index
  207. else:
  208. logger.debug("No matching node for %s", value)