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 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 _visit_blocks(self, bfile, limit_to): bpath = bfile.filepath.absolute().resolve() root_dir = bpathlib.BlendPath(bpath.parent) # Mapping from library path to data blocks to expand. blocks_per_lib = collections.defaultdict(set) while not self.to_visit.empty(): block = self.to_visit.get() assert isinstance(block, blendfile.BlendFileBlock) if (bpath, block.addr_old) in self.blocks_yielded: continue if block.code == b'ID': # ID blocks represent linked-in assets. Those are the ones that # should be loaded from their own blend file and "expanded" to # the entire set of data blocks required to render them. We # defer the handling of those so that we can work with one # blend file at a time. lib = block.get_pointer(b'lib') lib_bpath = bpathlib.BlendPath(lib[b'name']).absolute(root_dir) blocks_per_lib[lib_bpath].add(block) # The library block itself should also be reported, because it # represents a blend file that is a dependency as well. self.to_visit.put(lib) continue if limit_to: # We're limiting the blocks, so we have to expand them to make # sure we don't miss anything. Otherwise we're yielding the # entire file anyway, and no expansion is necessary. self._queue_dependencies(block) self.blocks_yielded.add((bpath, block.addr_old)) yield block return blocks_per_lib
def abspath(self, relpath: bpathlib.BlendPath) -> bpathlib.BlendPath: """Construct an absolute path from a blendfile-relative path.""" if relpath.is_absolute(): return relpath bfile_dir = self.filepath.absolute().parent root = bpathlib.BlendPath(bfile_dir) abspath = relpath.absolute(root) my_log = self.log.getChild('abspath') my_log.debug('Resolved %s relative to %s to %s', relpath, self.filepath, abspath) return abspath
def test_execute_rewrite(self): infile, _ = self._pack_with_rewrite() extpath = pathlib.PurePosixPath('//_outside_project', *self.blendfiles.parts[1:]) extbpath = bpathlib.BlendPath(extpath) # Those libraries should be properly rewritten. bfile = blendfile.open_cached(self.tpath / infile.name, assert_cached=False) libs = sorted(bfile.code_index[b'LI']) self.assertEqual(b'LILib', libs[0].id_name) self.assertEqual(extbpath / b'linked_cube.blend', libs[0][b'name']) self.assertEqual(b'LILib.002', libs[1].id_name) self.assertEqual(extbpath / b'material_textures.blend', libs[1][b'name'])
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 __init__( self, block: blendfile.BlendFileBlock, asset_path: bpathlib.BlendPath, is_sequence: bool = False, path_full_field: dna.Field = None, path_dir_field: dna.Field = None, path_base_field: dna.Field = None, block_name: bytes = b'', ) -> None: if block_name: self.block_name = block_name else: self.block_name = self.guess_block_name(block) assert isinstance(block, blendfile.BlendFileBlock) assert isinstance(asset_path, (bytes, bpathlib.BlendPath)), \ 'asset_path should be BlendPath, not %r' % type(asset_path) if path_full_field is None: assert isinstance(path_dir_field, dna.Field), \ 'path_dir_field should be dna.Field, not %r' % type(path_dir_field) assert isinstance(path_base_field, dna.Field), \ 'path_base_field should be dna.Field, not %r' % type(path_base_field) else: assert isinstance(path_full_field, dna.Field), \ 'path_full_field should be dna.Field, not %r' % type(path_full_field) if isinstance(asset_path, bytes): asset_path = bpathlib.BlendPath(asset_path) self.block = block self.asset_path = asset_path self.is_sequence = bool(is_sequence) self.path_full_field = path_full_field self.path_dir_field = path_dir_field self.path_base_field = path_base_field # cached by __fspath__() self._abspath = None # type: typing.Optional[pathlib.Path]
def test_rewrite_sequence(self): ppath = self.blendfiles / 'subdir' infile = ppath / 'image_sequence_dir_up.blend' with pack.Packer(infile, ppath, self.tpath) as packer: packer.strategise() packer.execute() bf = blendfile.open_cached(self.tpath / infile.name, assert_cached=False) scene = bf.code_index[b'SC'][0] ed = scene.get_pointer(b'ed') seq = ed.get_pointer((b'seqbase', b'first')) seq_strip = seq.get_pointer(b'strip') imgseq_path = (self.blendfiles / 'imgseq').absolute() as_bytes = str(imgseq_path.relative_to(imgseq_path.anchor)).encode() relpath = bpathlib.BlendPath(b'//_outside_project') / as_bytes # The image sequence base path should be rewritten. self.assertEqual(b'SQ000210.png', seq[b'name']) self.assertEqual(relpath, seq_strip[b'dir'])