def _walk_point_cache(ctx: ModifierContext, block_name: bytes, bfile: blendfile.BlendFile, pointcache: blendfile.BlendFileBlock, extension: bytes): flag = pointcache[b'flag'] if flag & cdefs.PTCACHE_EXTERNAL: path, field = pointcache.get(b'path', return_field=True) log.info(' external cache at %s', path) bpath = bpathlib.BlendPath(path) yield result.BlockUsage(pointcache, bpath, path_full_field=field, is_sequence=True, block_name=block_name) elif flag & cdefs.PTCACHE_DISK_CACHE: # See ptcache_path() in pointcache.c name, field = pointcache.get(b'name', return_field=True) if not name: # See ptcache_filename() in pointcache.c idname = ctx.owner[b'id', b'name'] name = idname[2:].hex().upper().encode() path = b'//%b%b/%b_*%b' % ( cdefs.PTCACHE_PATH, bfile.filepath.stem.encode(), name, extension) log.info(' disk cache at %s', path) bpath = bpathlib.BlendPath(path) yield result.BlockUsage(pointcache, bpath, path_full_field=field, is_sequence=True, block_name=block_name)
def _expand_group(block: blendfile.BlendFileBlock): log.debug('Collection/group Block: %s (name=%s)', block, block.id_name) objects = block.get_pointer((b'gobject', b'first')) for item in iterators.listbase(objects): yield item.get_pointer(b'ob') # Recurse through child collections. try: children = block.get_pointer((b'children', b'first')) except KeyError: # 'children' was introduced in Blender 2.8 collections pass else: for child in iterators.listbase(children): subcoll = child.get_pointer(b'collection') if subcoll is None: continue if subcoll.dna_type_id == b'ID': # This issue happened while recursing a linked-in 'Hidden' # collection in the Chimes set of the Spring project. Such # collections named 'Hidden' were apparently created while # converting files from Blender 2.79 to 2.80. This error # isn't reproducible with just Blender 2.80. yield subcoll continue log.debug('recursing into child collection %s (name=%r, type=%r)', subcoll, subcoll.id_name, subcoll.dna_type_name) yield from _expand_group(subcoll)
def _expand_particle_settings(block: blendfile.BlendFileBlock): yield from _expand_generic_animdata(block) yield from _expand_generic_mtex(block) block_ren_as = block[b'ren_as'] if block_ren_as == cdefs.PART_DRAW_GR: yield block.get_pointer(b'dup_group') elif block_ren_as == cdefs.PART_DRAW_OB: yield block.get_pointer(b'dup_ob')
def mesh( block: blendfile.BlendFileBlock) -> typing.Iterator[result.BlockUsage]: """Mesh data blocks.""" block_external = block.get_pointer((b'ldata', b'external'), None) if block_external is None: block_external = block.get_pointer((b'fdata', b'external'), None) if block_external is None: return path, field = block_external.get(b'filename', return_field=True) yield result.BlockUsage(block, path, path_full_field=field)
def _expand_curve(block: blendfile.BlendFileBlock): yield from _expand_generic_animdata(block) yield from _expand_generic_material(block) for fieldname in (b'vfont', b'vfontb', b'vfonti', b'vfontbi', b'bevobj', b'taperobj', b'textoncurve'): yield block.get_pointer(fieldname)
def scene( block: blendfile.BlendFileBlock) -> typing.Iterator[result.BlockUsage]: """Scene data blocks.""" # Sequence editor is the only interesting bit. block_ed = block.get_pointer(b'ed') if block_ed is None: return single_asset_types = { cdefs.SEQ_TYPE_MOVIE, cdefs.SEQ_TYPE_SOUND_RAM, cdefs.SEQ_TYPE_SOUND_HD } asset_types = single_asset_types.union({cdefs.SEQ_TYPE_IMAGE}) for seq, seq_type in iterators.sequencer_strips(block_ed): if seq_type not in asset_types: continue seq_strip = seq.get_pointer(b'strip') if seq_strip is None: continue seq_stripdata = seq_strip.get_pointer(b'stripdata') if seq_stripdata is None: continue dirname, dn_field = seq_strip.get(b'dir', return_field=True) basename, bn_field = seq_stripdata.get(b'name', return_field=True) asset_path = bpathlib.BlendPath(dirname) / basename is_sequence = seq_type not in single_asset_types yield result.BlockUsage(seq_strip, asset_path, is_sequence=is_sequence, path_dir_field=dn_field, path_base_field=bn_field)
def vector_font( block: blendfile.BlendFileBlock) -> typing.Iterator[result.BlockUsage]: """Vector Font data blocks.""" path, field = block.get(b'name', return_field=True) if path == b'<builtin>': # builtin font return yield result.BlockUsage(block, path, path_full_field=field)
def image( block: blendfile.BlendFileBlock) -> typing.Iterator[result.BlockUsage]: """Image data blocks.""" # old files miss this image_source = block.get(b'source', default=cdefs.IMA_SRC_FILE) if image_source not in { cdefs.IMA_SRC_FILE, cdefs.IMA_SRC_SEQUENCE, cdefs.IMA_SRC_MOVIE }: return pathname, field = block.get(b'name', return_field=True) is_sequence = image_source == cdefs.IMA_SRC_SEQUENCE yield result.BlockUsage(block, pathname, is_sequence, path_full_field=field)
def modifier_filepath(ctx: ModifierContext, modifier: blendfile.BlendFileBlock, block_name: bytes) \ -> typing.Iterator[result.BlockUsage]: """Just yield the 'filepath' field.""" path, field = modifier.get(b'filepath', return_field=True) yield result.BlockUsage(modifier, path, path_full_field=field, block_name=block_name)
def _expand_generic_mtex(block: blendfile.BlendFileBlock): if not block.dna_type.has_field(b'mtex'): # mtex was removed in Blender 2.8 return for mtex in block.iter_fixed_array_of_pointers(b'mtex'): yield mtex.get_pointer(b'tex') yield mtex.get_pointer(b'object')
def modifier_cloth(ctx: ModifierContext, modifier: blendfile.BlendFileBlock, block_name: bytes) \ -> typing.Iterator[result.BlockUsage]: pointcache = modifier.get_pointer(b'point_cache') if pointcache is None: return yield from _walk_point_cache(ctx, block_name, modifier.bfile, pointcache, cdefs.PTCACHE_EXT)
def movie_clip( block: blendfile.BlendFileBlock) -> typing.Iterator[result.BlockUsage]: """MovieClip data blocks.""" path, field = block.get(b'name', return_field=True) # TODO: The assumption that this is not a sequence may not be true for all modifiers. yield result.BlockUsage(block, path, is_sequence=False, path_full_field=field)
def _expand_material(block: blendfile.BlendFileBlock): yield from _expand_generic_animdata(block) yield from _expand_generic_nodetree_id(block) yield from _expand_generic_mtex(block) try: yield block.get_pointer(b'group') except KeyError: # Groups were removed from Blender 2.8 pass
def _get_texture(prop_name: bytes, dblock: blendfile.BlendFileBlock, block_name: bytes) \ -> typing.Iterator[result.BlockUsage]: """Yield block usages from a texture propery. Assumes dblock[prop_name] is a texture data block. """ if dblock is None: return tx = dblock.get_pointer(prop_name) yield from _get_image(b'ima', tx, block_name)
def modifier_ocean(ctx: ModifierContext, modifier: blendfile.BlendFileBlock, block_name: bytes) \ -> typing.Iterator[result.BlockUsage]: if not modifier[b'cached']: return path, field = modifier.get(b'cachepath', return_field=True) # The path indicates the directory containing the cached files. yield result.BlockUsage(modifier, path, is_sequence=True, path_full_field=field, block_name=block_name)
def _expand_generic_nodetree(block: blendfile.BlendFileBlock): assert block.dna_type.dna_type_id == b'bNodeTree' nodes = block.get_pointer((b'nodes', b'first')) for node in iterators.listbase(nodes): if node[b'type'] == cdefs.CMP_NODE_R_LAYERS: continue yield node # The 'id' property points to whatever is used by the node # (like the image in an image texture node). yield node.get_pointer(b'id')
def _expand_object(block: blendfile.BlendFileBlock): yield from _expand_generic_animdata(block) yield from _expand_generic_material(block) yield block.get_pointer(b'data') if block[b'transflag'] & cdefs.OB_DUPLIGROUP: yield block.get_pointer(b'dup_group') yield block.get_pointer(b'proxy') yield block.get_pointer(b'proxy_group') # 'ob->pose->chanbase[...].custom' block_pose = block.get_pointer(b'pose') if block_pose: assert block_pose.dna_type.dna_type_id == b'bPose' # sdna_index_bPoseChannel = block_pose.file.sdna_index_from_id[b'bPoseChannel'] channels = block_pose.get_pointer((b'chanbase', b'first')) for pose_chan in iterators.listbase(channels): yield pose_chan.get_pointer(b'custom') # Expand the objects 'ParticleSettings' via 'ob->particlesystem[...].part' # sdna_index_ParticleSystem = block.file.sdna_index_from_id.get(b'ParticleSystem') # if sdna_index_ParticleSystem is not None: psystems = block.get_pointer((b'particlesystem', b'first')) for psystem in iterators.listbase(psystems): yield psystem.get_pointer(b'part')
def _expand_scene(block: blendfile.BlendFileBlock): yield from _expand_generic_animdata(block) yield from _expand_generic_nodetree_id(block) yield block.get_pointer(b'camera') yield block.get_pointer(b'world') yield block.get_pointer(b'set', default=None) yield block.get_pointer(b'clip', default=None) # sdna_index_Base = block.file.sdna_index_from_id[b'Base'] # for item in bf_utils.iter_ListBase(block.get_pointer((b'base', b'first'))): # yield item.get_pointer(b'object', sdna_index_refine=sdna_index_Base) bases = block.get_pointer((b'base', b'first')) for base in iterators.listbase(bases): yield base.get_pointer(b'object') # Sequence Editor block_ed = block.get_pointer(b'ed') if not block_ed: return strip_type_to_field = { cdefs.SEQ_TYPE_SCENE: b'scene', cdefs.SEQ_TYPE_MOVIECLIP: b'clip', cdefs.SEQ_TYPE_MASK: b'mask', cdefs.SEQ_TYPE_SOUND_RAM: b'sound', } for strip, strip_type in iterators.sequencer_strips(block_ed): try: field_name = strip_type_to_field[strip_type] except KeyError: continue yield strip.get_pointer(field_name)
def modifier_mesh_sequence_cache( ctx: ModifierContext, modifier: blendfile.BlendFileBlock, block_name: bytes) -> typing.Iterator[result.BlockUsage]: """Yield the Alembic file(s) used by this modifier""" cache_file = modifier.get_pointer(b'cache_file') if cache_file is None: return is_sequence = bool(cache_file[b'is_sequence']) cache_block_name = cache_file.id_name assert cache_block_name is not None path, field = cache_file.get(b'filepath', return_field=True) yield result.BlockUsage(cache_file, path, path_full_field=field, is_sequence=is_sequence, block_name=cache_block_name)
def object_block( block: blendfile.BlendFileBlock) -> typing.Iterator[result.BlockUsage]: """Object data blocks.""" ctx = modifier_walkers.ModifierContext(owner=block) # 'ob->modifiers[...].filepath' mods = block.get_pointer((b'modifiers', b'first')) for mod_idx, block_mod in enumerate( iterators.listbase(mods, next_path=(b'modifier', b'next'))): block_name = b'%s.modifiers[%d]' % (block.id_name, mod_idx) mod_type = block_mod[b'modifier', b'type'] log.debug('Tracing modifier %s, type=%d', block_name.decode(), mod_type) try: mod_handler = modifier_walkers.modifier_handlers[mod_type] except KeyError: continue yield from mod_handler(ctx, block_mod, block_name)
def modifier_smoke_sim(ctx: ModifierContext, modifier: blendfile.BlendFileBlock, block_name: bytes) \ -> typing.Iterator[result.BlockUsage]: my_log = log.getChild('modifier_smoke_sim') domain = modifier.get_pointer(b'domain') if domain is None: my_log.debug('Modifier %r (%r) has no domain', modifier[b'modifier', b'name'], block_name) return pointcache = domain.get_pointer(b'point_cache') if pointcache is None: return format = domain.get(b'cache_file_format') extensions = { cdefs.PTCACHE_FILE_PTCACHE: cdefs.PTCACHE_EXT, cdefs.PTCACHE_FILE_OPENVDB: cdefs.PTCACHE_EXT_VDB } yield from _walk_point_cache(ctx, block_name, modifier.bfile, pointcache, extensions[format])
def modifier_fluid_sim(ctx: ModifierContext, modifier: blendfile.BlendFileBlock, block_name: bytes) \ -> typing.Iterator[result.BlockUsage]: my_log = log.getChild('modifier_fluid_sim') fss = modifier.get_pointer(b'fss') if fss is None: my_log.debug('Modifier %r (%r) has no fss', modifier[b'modifier', b'name'], block_name) return path, field = fss.get(b'surfdataPath', return_field=True) # This may match more than is used by Blender, but at least it shouldn't # miss any files. # The 'fluidsurface' prefix is defined in source/blender/makesdna/DNA_object_fluidsim_types.h bpath = bpathlib.BlendPath(path) yield result.BlockUsage(fss, bpath, path_full_field=field, is_sequence=True, block_name=block_name)
def _expand_generic_animdata(block: blendfile.BlendFileBlock): block_adt = block.get_pointer(b'adt') if block_adt: yield block_adt.get_pointer(b'action')
def _expand_mesh(block: blendfile.BlendFileBlock): yield from _expand_generic_animdata(block) yield from _expand_generic_material(block) yield block.get_pointer(b'texcomesh')
def _expand_texture(block: blendfile.BlendFileBlock): yield from _expand_generic_animdata(block) yield from _expand_generic_nodetree_id(block) yield block.get_pointer(b'ima')
def _expand_generic_material(block: blendfile.BlendFileBlock): array_len = block.get(b'totcol') yield from block.iter_array_of_pointers(b'mat', array_len)
def _expand_generic_nodetree_id(block: blendfile.BlendFileBlock): block_ntree = block.get_pointer(b'nodetree', None) if block_ntree is not None: yield from _expand_generic_nodetree(block_ntree)
def sound( block: blendfile.BlendFileBlock) -> typing.Iterator[result.BlockUsage]: """Sound data blocks.""" path, field = block.get(b'name', return_field=True) yield result.BlockUsage(block, path, path_full_field=field)
def wrapper(block: blendfile.BlendFileBlock, *args, **kwargs): if block.get(b'packedfile', default=False): log.debug('Datablock %r is packed; skipping', block.id_name) return yield from wrapped(block, *args, **kwargs)
def cache_file( block: blendfile.BlendFileBlock) -> typing.Iterator[result.BlockUsage]: """Cache file data blocks.""" path, field = block.get(b'filepath', return_field=True) yield result.BlockUsage(block, path, path_full_field=field)