import os from .. import constants, logger from . import base_classes, io, api FORMAT_VERSION = 3 class Geometry(base_classes.BaseNode): def __init__(self, node, parent=None): logger.debug('Geometry().__init__(%s)', node) #@TODO: maybe better to have `three` constants for # strings that are specific to `three` properties geo_type = constants.GEOMETRY.title() if parent.options.get(constants.GEOMETRY_TYPE): opt_type = parent.options[constants.GEOMETRY_TYPE] if opt_type == constants.BUFFER_GEOMETRY: geo_type = constants.BUFFER_GEOMETRY elif opt_type != constants.GEOMETRY: logger.error('Unknown geometry type %s', opt_type) logger.info('Setting %s to "%s"', node, geo_type) self._defaults[constants.TYPE] = geo_type base_classes.BaseNode.__init__(self, node, parent=parent, type=geo_type) @property def animation_filename(self): compression = self.options.get(constants.COMPRESSION) if compression in (None, constants.NONE): ext = constants.JSON elif compression == constants.MSGPACK: ext = constants.PACK for key in (constants.MORPH_TARGETS, constants.ANIMATION): try: self[key] break except KeyError: pass else: logger.info('%s has no animation data', self.node) return return '%s.%s.%s' % (self.node, key, ext) @property def face_count(self): try: faces = self[constants.FACES] except KeyError: logger.debug('No parsed faces found') return 0 length = len(faces) offset = 0 bitset = lambda x,y: x & ( 1 << y ) face_count = 0 masks = (constants.MASK[constants.UVS], constants.MASK[constants.NORMALS], constants.MASK[constants.COLORS]) while offset < length: bit = faces[offset] offset += 1 face_count += 1 is_quad = bitset(bit, constants.MASK[constants.QUAD]) vector = 4 if is_quad else 3 offset += vector if bitset(bit, constants.MASK[constants.MATERIALS]): offset += 1 for mask in masks: if bitset(bit, mask): offset += vector return face_count @property def metadata(self): metadata = { constants.GENERATOR: constants.THREE, constants.VERSION: FORMAT_VERSION } if self[constants.TYPE] == constants.GEOMETRY.title(): self.__geometry_metadata(metadata) else: self.__buffer_geometry_metadata(metadata) return metadata def copy(self, scene=True): logger.debug('Geometry().copy(scene=%s)', scene) dispatch = { True: self._scene_format, False: self._geometry_format } data = dispatch[scene]() try: data[constants.MATERIALS] = self[constants.MATERIALS].copy() except KeyError: logger.debug('No materials to copy') return data def copy_textures(self): logger.debug('Geometry().copy_textures()') if self.options.get(constants.COPY_TEXTURES): texture_registration = self.register_textures() if texture_registration: logger.info('%s has registered textures', self.node) io.copy_registered_textures( os.path.dirname(self.scene.filepath), texture_registration) def parse(self): logger.debug('Geometry().parse()') if self[constants.TYPE] == constants.GEOMETRY.title(): logger.info('Parsing Geometry format') self.__parse_geometry() else: logger.info('Parsing BufferGeometry format') self.__parse_buffer_geometry() def register_textures(self): logger.debug('Geometry().register_textures()') return api.mesh.texture_registration(self.node) def write(self, filepath=None): logger.debug('Geometry().write(filepath=%s)', filepath) filepath = filepath or self.scene.filepath io.dump(filepath, self.copy(scene=False), options=self.scene.options) if self.options.get(constants.MAPS): logger.info('Copying textures for %s', self.node) self.copy_textures() def write_animation(self, filepath): logger.debug('Geometry().write_animation(%s)', filepath) for key in (constants.MORPH_TARGETS, constants.ANIMATION): try: data = self[key] break except KeyError: pass else: logger.info('%s has no animation data', self.node) return filepath = os.path.join(filepath, self.animation_filename) if filepath: logger.info('Dumping animation data to %s', filepath) io.dump(filepath, data, options=self.scene.options) return filepath else: logger.warning('Could not determine a filepath for '\ 'animation data. Nothing written to disk.') def _component_data(self): logger.debug('Geometry()._component_data()') if self[constants.TYPE] != constants.GEOMETRY.title(): return self[constants.ATTRIBUTES] components = [constants.VERTICES, constants.FACES, constants.UVS, constants.COLORS, constants.NORMALS, constants.BONES, constants.SKIN_WEIGHTS, constants.SKIN_INDICES, constants.NAME, constants.INFLUENCES_PER_VERTEX] data = {} anim_components = [constants.MORPH_TARGETS, constants.ANIMATION] if self.options.get(constants.EMBED_ANIMATION): components.extend(anim_components) else: for component in anim_components: try: self[component] except KeyError: pass else: data[component] = os.path.basename( self.animation_filename) else: logger.info('No animation data found for %s', self.node) for component in components: try: data[component] = self[component] except KeyError: logger.debug('Component %s not found', component) pass return data def _geometry_format(self): data = self._component_data() if self[constants.TYPE] != constants.GEOMETRY.title(): data = {constants.ATTRIBUTES: data} data[constants.METADATA] = { constants.TYPE: self[constants.TYPE] } data[constants.METADATA].update(self.metadata) return data def __buffer_geometry_metadata(self, metadata): for key, value in self[constants.ATTRIBUTES].items(): size = value[constants.ITEM_SIZE] array = value[constants.ARRAY] metadata[key] = len(array)/size def __geometry_metadata(self, metadata): skip = (constants.TYPE, constants.FACES, constants.UUID, constants.ANIMATION, constants.SKIN_INDICES, constants.SKIN_WEIGHTS, constants.NAME, constants.INFLUENCES_PER_VERTEX) vectors = (constants.VERTICES, constants.NORMALS) for key in self.keys(): if key in vectors: try: metadata[key] = int(len(self[key])/3) except KeyError: pass continue if key in skip: continue metadata[key] = len(self[key]) faces = self.face_count if faces > 0: metadata[constants.FACES] = faces def _scene_format(self): data = { constants.UUID: self[constants.UUID], constants.TYPE: self[constants.TYPE] } component_data = self._component_data() if self[constants.TYPE] == constants.GEOMETRY.title(): data[constants.DATA] = component_data data[constants.DATA].update({ constants.METADATA: self.metadata }) else: if self.options[constants.EMBED_GEOMETRY]: data[constants.DATA] = { constants.ATTRIBUTES: component_data } else: data[constants.ATTRIBUTES] = component_data data[constants.METADATA] = self.metadata data[constants.NAME] = self[constants.NAME] return data def __parse_buffer_geometry(self): self[constants.ATTRIBUTES] = {} options_vertices = self.options.get(constants.VERTICES) option_normals = self.options.get(constants.NORMALS) option_uvs = self.options.get(constants.UVS) dispatch = ( (constants.POSITION, options_vertices, api.mesh.buffer_position, 3), (constants.UV, option_uvs, api.mesh.buffer_uv, 2), (constants.NORMAL, option_normals, api.mesh.buffer_normal, 3) ) for key, option, func, size in dispatch: if not option: continue array = func(self.node, self.options) if not array: logger.warning('No array could be made for %s', key) continue self[constants.ATTRIBUTES][key] = { constants.ITEM_SIZE: size, constants.TYPE: constants.FLOAT_32, constants.ARRAY: array } def __parse_geometry(self): if self.options.get(constants.VERTICES): logger.info('Parsing %s', constants.VERTICES) self[constants.VERTICES] = api.mesh.vertices( self.node, self.options) if self.options.get(constants.FACES): logger.info('Parsing %s', constants.FACES) self[constants.FACES] = api.mesh.faces( self.node, self.options) if self.options.get(constants.NORMALS): logger.info('Parsing %s', constants.NORMALS) self[constants.NORMALS] = api.mesh.normals( self.node, self.options) if self.options.get(constants.COLORS): logger.info('Parsing %s', constants.COLORS) self[constants.COLORS] = api.mesh.vertex_colors( self.node) if self.options.get(constants.FACE_MATERIALS): logger.info('Parsing %s', constants.FACE_MATERIALS) self[constants.MATERIALS] = api.mesh.materials( self.node, self.options) if self.options.get(constants.UVS): logger.info('Parsing %s', constants.UVS) self[constants.UVS] = api.mesh.uvs( self.node, self.options) if self.options.get(constants.ANIMATION): logger.info('Parsing %s', constants.ANIMATION) self[constants.ANIMATION] = api.mesh.animation( self.node, self.options) #@TODO: considering making bones data implied when # querying skinning data bone_map = {} if self.options.get(constants.BONES): logger.info('Parsing %s', constants.BONES) bones, bone_map = api.mesh.bones(self.node) self[constants.BONES] = bones if self.options.get(constants.SKINNING): logger.info('Parsing %s', constants.SKINNING) influences = self.options.get( constants.INFLUENCES_PER_VERTEX, 2) self[constants.INFLUENCES_PER_VERTEX] = influences self[constants.SKIN_INDICES] = api.mesh.skin_indices( self.node, bone_map, influences) self[constants.SKIN_WEIGHTS] = api.mesh.skin_weights( self.node, bone_map, influences) if self.options.get(constants.MORPH_TARGETS): logger.info('Parsing %s', constants.MORPH_TARGETS) self[constants.MORPH_TARGETS] = api.mesh.morph_targets( self.node, self.options)