geometry.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. import os
  2. from .. import constants, logger
  3. from . import base_classes, io, api
  4. FORMAT_VERSION = 3
  5. class Geometry(base_classes.BaseNode):
  6. def __init__(self, node, parent=None):
  7. logger.debug('Geometry().__init__(%s)', node)
  8. #@TODO: maybe better to have `three` constants for
  9. # strings that are specific to `three` properties
  10. geo_type = constants.GEOMETRY.title()
  11. if parent.options.get(constants.GEOMETRY_TYPE):
  12. opt_type = parent.options[constants.GEOMETRY_TYPE]
  13. if opt_type == constants.BUFFER_GEOMETRY:
  14. geo_type = constants.BUFFER_GEOMETRY
  15. elif opt_type != constants.GEOMETRY:
  16. logger.error('Unknown geometry type %s', opt_type)
  17. logger.info('Setting %s to "%s"', node, geo_type)
  18. self._defaults[constants.TYPE] = geo_type
  19. base_classes.BaseNode.__init__(self, node,
  20. parent=parent,
  21. type=geo_type)
  22. @property
  23. def animation_filename(self):
  24. compression = self.options.get(constants.COMPRESSION)
  25. if compression in (None, constants.NONE):
  26. ext = constants.JSON
  27. elif compression == constants.MSGPACK:
  28. ext = constants.PACK
  29. for key in (constants.MORPH_TARGETS, constants.ANIMATION):
  30. try:
  31. self[key]
  32. break
  33. except KeyError:
  34. pass
  35. else:
  36. logger.info('%s has no animation data', self.node)
  37. return
  38. return '%s.%s.%s' % (self.node, key, ext)
  39. @property
  40. def face_count(self):
  41. try:
  42. faces = self[constants.FACES]
  43. except KeyError:
  44. logger.debug('No parsed faces found')
  45. return 0
  46. length = len(faces)
  47. offset = 0
  48. bitset = lambda x,y: x & ( 1 << y )
  49. face_count = 0
  50. masks = (constants.MASK[constants.UVS],
  51. constants.MASK[constants.NORMALS],
  52. constants.MASK[constants.COLORS])
  53. while offset < length:
  54. bit = faces[offset]
  55. offset += 1
  56. face_count += 1
  57. is_quad = bitset(bit, constants.MASK[constants.QUAD])
  58. vector = 4 if is_quad else 3
  59. offset += vector
  60. if bitset(bit, constants.MASK[constants.MATERIALS]):
  61. offset += 1
  62. for mask in masks:
  63. if bitset(bit, mask):
  64. offset += vector
  65. return face_count
  66. @property
  67. def metadata(self):
  68. metadata = {
  69. constants.GENERATOR: constants.THREE,
  70. constants.VERSION: FORMAT_VERSION
  71. }
  72. if self[constants.TYPE] == constants.GEOMETRY.title():
  73. self.__geometry_metadata(metadata)
  74. else:
  75. self.__buffer_geometry_metadata(metadata)
  76. return metadata
  77. def copy(self, scene=True):
  78. logger.debug('Geometry().copy(scene=%s)', scene)
  79. dispatch = {
  80. True: self._scene_format,
  81. False: self._geometry_format
  82. }
  83. data = dispatch[scene]()
  84. try:
  85. data[constants.MATERIALS] = self[constants.MATERIALS].copy()
  86. except KeyError:
  87. logger.debug('No materials to copy')
  88. return data
  89. def copy_textures(self):
  90. logger.debug('Geometry().copy_textures()')
  91. if self.options.get(constants.COPY_TEXTURES):
  92. texture_registration = self.register_textures()
  93. if texture_registration:
  94. logger.info('%s has registered textures', self.node)
  95. io.copy_registered_textures(
  96. os.path.dirname(self.scene.filepath),
  97. texture_registration)
  98. def parse(self):
  99. logger.debug('Geometry().parse()')
  100. if self[constants.TYPE] == constants.GEOMETRY.title():
  101. logger.info('Parsing Geometry format')
  102. self.__parse_geometry()
  103. else:
  104. logger.info('Parsing BufferGeometry format')
  105. self.__parse_buffer_geometry()
  106. def register_textures(self):
  107. logger.debug('Geometry().register_textures()')
  108. return api.mesh.texture_registration(self.node)
  109. def write(self, filepath=None):
  110. logger.debug('Geometry().write(filepath=%s)', filepath)
  111. filepath = filepath or self.scene.filepath
  112. io.dump(filepath, self.copy(scene=False),
  113. options=self.scene.options)
  114. if self.options.get(constants.MAPS):
  115. logger.info('Copying textures for %s', self.node)
  116. self.copy_textures()
  117. def write_animation(self, filepath):
  118. logger.debug('Geometry().write_animation(%s)', filepath)
  119. for key in (constants.MORPH_TARGETS, constants.ANIMATION):
  120. try:
  121. data = self[key]
  122. break
  123. except KeyError:
  124. pass
  125. else:
  126. logger.info('%s has no animation data', self.node)
  127. return
  128. filepath = os.path.join(filepath, self.animation_filename)
  129. if filepath:
  130. logger.info('Dumping animation data to %s', filepath)
  131. io.dump(filepath, data, options=self.scene.options)
  132. return filepath
  133. else:
  134. logger.warning('Could not determine a filepath for '\
  135. 'animation data. Nothing written to disk.')
  136. def _component_data(self):
  137. logger.debug('Geometry()._component_data()')
  138. if self[constants.TYPE] != constants.GEOMETRY.title():
  139. return self[constants.ATTRIBUTES]
  140. components = [constants.VERTICES, constants.FACES,
  141. constants.UVS, constants.COLORS, constants.NORMALS,
  142. constants.BONES, constants.SKIN_WEIGHTS,
  143. constants.SKIN_INDICES, constants.NAME,
  144. constants.INFLUENCES_PER_VERTEX]
  145. data = {}
  146. anim_components = [constants.MORPH_TARGETS, constants.ANIMATION]
  147. if self.options.get(constants.EMBED_ANIMATION):
  148. components.extend(anim_components)
  149. else:
  150. for component in anim_components:
  151. try:
  152. self[component]
  153. except KeyError:
  154. pass
  155. else:
  156. data[component] = os.path.basename(
  157. self.animation_filename)
  158. else:
  159. logger.info('No animation data found for %s', self.node)
  160. for component in components:
  161. try:
  162. data[component] = self[component]
  163. except KeyError:
  164. logger.debug('Component %s not found', component)
  165. pass
  166. return data
  167. def _geometry_format(self):
  168. data = self._component_data()
  169. if self[constants.TYPE] != constants.GEOMETRY.title():
  170. data = {constants.ATTRIBUTES: data}
  171. data[constants.METADATA] = {
  172. constants.TYPE: self[constants.TYPE]
  173. }
  174. data[constants.METADATA].update(self.metadata)
  175. return data
  176. def __buffer_geometry_metadata(self, metadata):
  177. for key, value in self[constants.ATTRIBUTES].items():
  178. size = value[constants.ITEM_SIZE]
  179. array = value[constants.ARRAY]
  180. metadata[key] = len(array)/size
  181. def __geometry_metadata(self, metadata):
  182. skip = (constants.TYPE, constants.FACES, constants.UUID,
  183. constants.ANIMATION, constants.SKIN_INDICES,
  184. constants.SKIN_WEIGHTS, constants.NAME,
  185. constants.INFLUENCES_PER_VERTEX)
  186. vectors = (constants.VERTICES, constants.NORMALS)
  187. for key in self.keys():
  188. if key in vectors:
  189. try:
  190. metadata[key] = int(len(self[key])/3)
  191. except KeyError:
  192. pass
  193. continue
  194. if key in skip: continue
  195. metadata[key] = len(self[key])
  196. faces = self.face_count
  197. if faces > 0:
  198. metadata[constants.FACES] = faces
  199. def _scene_format(self):
  200. data = {
  201. constants.UUID: self[constants.UUID],
  202. constants.TYPE: self[constants.TYPE]
  203. }
  204. component_data = self._component_data()
  205. if self[constants.TYPE] == constants.GEOMETRY.title():
  206. data[constants.DATA] = component_data
  207. data[constants.DATA].update({
  208. constants.METADATA: self.metadata
  209. })
  210. else:
  211. if self.options[constants.EMBED_GEOMETRY]:
  212. data[constants.DATA] = {
  213. constants.ATTRIBUTES: component_data
  214. }
  215. else:
  216. data[constants.ATTRIBUTES] = component_data
  217. data[constants.METADATA] = self.metadata
  218. data[constants.NAME] = self[constants.NAME]
  219. return data
  220. def __parse_buffer_geometry(self):
  221. self[constants.ATTRIBUTES] = {}
  222. options_vertices = self.options.get(constants.VERTICES)
  223. option_normals = self.options.get(constants.NORMALS)
  224. option_uvs = self.options.get(constants.UVS)
  225. dispatch = (
  226. (constants.POSITION, options_vertices,
  227. api.mesh.buffer_position, 3),
  228. (constants.UV, option_uvs, api.mesh.buffer_uv, 2),
  229. (constants.NORMAL, option_normals,
  230. api.mesh.buffer_normal, 3)
  231. )
  232. for key, option, func, size in dispatch:
  233. if not option:
  234. continue
  235. array = func(self.node, self.options)
  236. if not array:
  237. logger.warning('No array could be made for %s', key)
  238. continue
  239. self[constants.ATTRIBUTES][key] = {
  240. constants.ITEM_SIZE: size,
  241. constants.TYPE: constants.FLOAT_32,
  242. constants.ARRAY: array
  243. }
  244. def __parse_geometry(self):
  245. if self.options.get(constants.VERTICES):
  246. logger.info('Parsing %s', constants.VERTICES)
  247. self[constants.VERTICES] = api.mesh.vertices(
  248. self.node, self.options)
  249. if self.options.get(constants.FACES):
  250. logger.info('Parsing %s', constants.FACES)
  251. self[constants.FACES] = api.mesh.faces(
  252. self.node, self.options)
  253. if self.options.get(constants.NORMALS):
  254. logger.info('Parsing %s', constants.NORMALS)
  255. self[constants.NORMALS] = api.mesh.normals(
  256. self.node, self.options)
  257. if self.options.get(constants.COLORS):
  258. logger.info('Parsing %s', constants.COLORS)
  259. self[constants.COLORS] = api.mesh.vertex_colors(
  260. self.node)
  261. if self.options.get(constants.FACE_MATERIALS):
  262. logger.info('Parsing %s', constants.FACE_MATERIALS)
  263. self[constants.MATERIALS] = api.mesh.materials(
  264. self.node, self.options)
  265. if self.options.get(constants.UVS):
  266. logger.info('Parsing %s', constants.UVS)
  267. self[constants.UVS] = api.mesh.uvs(
  268. self.node, self.options)
  269. if self.options.get(constants.ANIMATION):
  270. logger.info('Parsing %s', constants.ANIMATION)
  271. self[constants.ANIMATION] = api.mesh.animation(
  272. self.node, self.options)
  273. #@TODO: considering making bones data implied when
  274. # querying skinning data
  275. bone_map = {}
  276. if self.options.get(constants.BONES):
  277. logger.info('Parsing %s', constants.BONES)
  278. bones, bone_map = api.mesh.bones(self.node)
  279. self[constants.BONES] = bones
  280. if self.options.get(constants.SKINNING):
  281. logger.info('Parsing %s', constants.SKINNING)
  282. influences = self.options.get(
  283. constants.INFLUENCES_PER_VERTEX, 2)
  284. self[constants.INFLUENCES_PER_VERTEX] = influences
  285. self[constants.SKIN_INDICES] = api.mesh.skin_indices(
  286. self.node, bone_map, influences)
  287. self[constants.SKIN_WEIGHTS] = api.mesh.skin_weights(
  288. self.node, bone_map, influences)
  289. if self.options.get(constants.MORPH_TARGETS):
  290. logger.info('Parsing %s', constants.MORPH_TARGETS)
  291. self[constants.MORPH_TARGETS] = api.mesh.morph_targets(
  292. self.node, self.options)