def test_compression(self): blendname = 'subdir/doubly_linked_up.blend' imgfile = self.blendfiles / blendname packer = pack.Packer(imgfile, self.blendfiles, self.tpath, compress=True) packer.strategise() packer.execute() dest = self.tpath / blendname self.assertTrue(dest.exists()) self.assertTrue(blendfile.open_cached(dest).is_compressed) for bpath in self.tpath.rglob('*.blend'): if bpath == dest: # Only test files that were bundled as dependency; the main # file was tested above already. continue self.assertTrue( blendfile.open_cached(bpath).is_compressed, 'Expected %s to be compressed' % bpath) break else: self.fail('Expected to have Blend files in the BAT pack.') for imgpath in self.tpath.rglob('*.jpg'): with imgpath.open('rb') as imgfile: magic = imgfile.read(3) self.assertEqual(b'\xFF\xD8\xFF', magic, 'Expected %s to NOT be compressed' % imgpath) break else: self.fail('Expected to have JPEG files in the BAT pack.')
def test_compressed(self): infile = self.blendfiles / 'linked_cube_compressed.blend' bf1 = blendfile.open_cached(infile) bf2 = blendfile.open_cached(infile) # The file should only be opened & parsed once. self.assertIs(bf1, bf2) self.assertIs(bf1, blendfile._cached_bfiles[infile])
def test_open_cached(self): infile = self.blendfiles / 'basic_file.blend' bf1 = blendfile.open_cached(infile) bf2 = blendfile.open_cached(infile) # The file should only be opened & parsed once. self.assertIs(bf1, bf2) self.assertIs(bf1, blendfile._cached_bfiles[infile])
def test_close_one_file(self): path1 = self.blendfiles / 'linked_cube_compressed.blend' path2 = self.blendfiles / 'basic_file.blend' bf1 = blendfile.open_cached(path1) bf2 = blendfile.open_cached(path2) self.assertIs(bf1, blendfile._cached_bfiles[path1]) # Closing a file should remove it from the cache. bf1.close() self.assertTrue(bf1.fileobj.closed) self.assertEqual({path2: bf2}, blendfile._cached_bfiles)
def deps(bfilepath: pathlib.Path, progress_cb: typing.Optional[progress.Callback] = None) \ -> typing.Iterator[result.BlockUsage]: """Open the blend file and report its dependencies. :param bfilepath: File to open. :param progress_cb: Progress callback object. """ log.info('opening: %s', bfilepath) bfile = blendfile.open_cached(bfilepath) bi = file2blocks.BlockIterator() if progress_cb: bi.progress_cb = progress_cb # Remember which block usages we've reported already, without keeping the # blocks themselves in memory. seen_hashes = set() # type: typing.Set[int] for block in asset_holding_blocks(bi.iter_blocks(bfile)): for block_usage in blocks2assets.iter_assets(block): usage_hash = hash(block_usage) if usage_hash in seen_hashes: continue seen_hashes.add(usage_hash) yield block_usage
def _open_and_rebind_test(self, infile: pathlib.Path, other: pathlib.Path): self.assertFalse(other.exists()) bf = blendfile.open_cached(infile) self.assertEqual(str(bf.raw_filepath), bf.fileobj.name) before_filepath = bf.filepath before_raw_fp = bf.raw_filepath before_blocks = bf.blocks before_compressed = bf.is_compressed bf.copy_and_rebind(other, mode='rb+') self.assertTrue(other.exists()) self.assertEqual(before_compressed, bf.is_compressed) if bf.is_compressed: self.assertNotEqual(bf.filepath, bf.raw_filepath) else: self.assertEqual(bf.filepath, bf.raw_filepath) self.assertNotEqual(before_filepath, bf.filepath) self.assertNotEqual(before_raw_fp, bf.raw_filepath) self.assertEqual(other, bf.filepath) self.assertIs(before_blocks, bf.blocks) self.assertNotIn(infile, blendfile._cached_bfiles) self.assertIs(bf, blendfile._cached_bfiles[other]) self.assertEqual(str(bf.raw_filepath), bf.fileobj.name)
def test_closed(self): infile = self.blendfiles / 'linked_cube_compressed.blend' bf = blendfile.open_cached(infile) self.assertIs(bf, blendfile._cached_bfiles[infile]) blendfile.close_all_cached() self.assertTrue(bf.fileobj.closed) self.assertEqual({}, blendfile._cached_bfiles)
def test_execute_rewrite_no_touch_origs(self): infile, _ = self._pack_with_rewrite() # The original file shouldn't be touched. bfile = blendfile.open_cached(infile, assert_cached=False) libs = sorted(bfile.code_index[b'LI']) self.assertEqual(b'LILib', libs[0].id_name) self.assertEqual(b'//../linked_cube.blend', libs[0][b'name']) self.assertEqual(b'LILib.002', libs[1].id_name) self.assertEqual(b'//../material_textures.blend', libs[1][b'name'])
def _visit_linked_blocks(self, blocks_per_lib): # We've gone through all the blocks in this file, now open the libraries # and iterate over the blocks referred there. for lib_bpath, idblocks in blocks_per_lib.items(): lib_path = pathlib.Path(lib_bpath.to_path()) try: lib_path = lib_path.resolve() except FileNotFoundError: log.warning('Library %s does not exist', lib_path) continue log.debug('Expanding %d blocks in %s', len(idblocks), lib_path) libfile = blendfile.open_cached(lib_path) yield from self.iter_blocks(libfile, idblocks)
def _visit_linked_blocks(self, blocks_per_lib): # We've gone through all the blocks in this file, now open the libraries # and iterate over the blocks referred there. for lib_bpath, idblocks in blocks_per_lib.items(): lib_path = bpathlib.make_absolute(lib_bpath.to_path()) assert lib_path.exists() if not lib_path.exists(): log.warning('Library %s does not exist', lib_path) continue log.debug('Expanding %d blocks in %s', len(idblocks), lib_path) libfile = blendfile.open_cached(lib_path) yield from self.iter_blocks(libfile, idblocks)
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 test_noop(self): ppath = self.blendfiles / 'subdir' infile = ppath / 'doubly_linked_up.blend' packer = pack.Packer(infile, ppath, self.tpath, noop=True) packer.strategise() packer.execute() self.assertEqual([], list(self.tpath.iterdir())) # The original file shouldn't be touched. bfile = blendfile.open_cached(infile) libs = sorted(bfile.code_index[b'LI']) self.assertEqual(b'LILib', libs[0].id_name) self.assertEqual(b'//../linked_cube.blend', libs[0][b'name']) self.assertEqual(b'LILib.002', libs[1].id_name) self.assertEqual(b'//../material_textures.blend', libs[1][b'name'])
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'])
def _rewrite_paths(self) -> None: """Rewrite paths to the new location of the assets. Writes the rewritten blend files to a temporary location. """ for bfile_path, action in self._actions.items(): if not action.rewrites: continue self._check_aborted() assert isinstance(bfile_path, pathlib.Path) # bfile_pp is the final path of this blend file in the BAT pack. # It is used to determine relative paths to other blend files. # It is *not* used for any disk I/O, since the file may not even # exist on the local filesystem. bfile_pp = action.new_path assert bfile_pp is not None # Use tempfile to create a unique name in our temporary directoy. # The file should be deleted when self.close() is called, and not # when the bfile_tp object is GC'd. bfile_tmp = tempfile.NamedTemporaryFile(dir=str(self._rewrite_in), prefix='bat-', suffix='-' + bfile_path.name, delete=False) bfile_tp = pathlib.Path(bfile_tmp.name) action.read_from = bfile_tp log.info('Rewriting %s to %s', bfile_path, bfile_tp) # The original blend file will have been cached, so we can use it # to avoid re-parsing all data blocks in the to-be-rewritten file. bfile = blendfile.open_cached(bfile_path, assert_cached=True) bfile.copy_and_rebind(bfile_tp, mode='rb+') for usage in action.rewrites: self._check_aborted() assert isinstance(usage, result.BlockUsage) asset_pp = self._actions[usage.abspath].new_path assert isinstance(asset_pp, pathlib.Path) log.debug(' - %s is packed at %s', usage.asset_path, asset_pp) relpath = bpathlib.BlendPath.mkrelative(asset_pp, bfile_pp) if relpath == usage.asset_path: log.info(' - %s remained at %s', usage.asset_path, relpath) continue log.info(' - %s moved to %s', usage.asset_path, relpath) # Find the same block in the newly copied file. block = bfile.dereference_pointer(usage.block.addr_old) if usage.path_full_field is None: dir_field = usage.path_dir_field assert dir_field is not None log.debug(' - updating field %s of block %s', dir_field.name.name_only, block) reldir = bpathlib.BlendPath.mkrelative( asset_pp.parent, bfile_pp) written = block.set(dir_field.name.name_only, reldir) log.debug(' - written %d bytes', written) # BIG FAT ASSUMPTION that the filename (e.g. basename # without path) does not change. This makes things much # easier, as in the sequence editor the directory and # filename fields are in different blocks. See the # blocks2assets.scene() function for the implementation. else: log.debug(' - updating field %s of block %s', usage.path_full_field.name.name_only, block) written = block.set(usage.path_full_field.name.name_only, relpath) log.debug(' - written %d bytes', written) # Make sure we close the file, otherwise changes may not be # flushed before it gets copied. if bfile.is_modified: self._progress_cb.rewrite_blendfile(bfile_path) bfile.close()
## list items in blend using blender asset tracer from pathlib import Path from blender_asset_tracer import blendfile ''' Brush = BR Object = OB WindowManager = WM WindowSpace = WS Screen = SR Collection = GR NodeTree = NT Palette = PL Image = IM Action = AC Library = LI Material = MA Text = TX Mesh = ME ''' fp = Path('path/to/file') print(f'Items in {str(fp)}:\n') with blendfile.open_cached(fp) as bf: for item in bf.find_blocks_from_code(b'OB'): print(item.id_name.decode()[2:]) print('\nDone')