Convert target path from Path to str & PurePath
The target path is just read as string from the CLI now, to allow more complex targets (such as URLs) that don't directly map to a path. The Packer subclass now handles the conversion from that string to a `pathlib.PurePath`, and specific subclasses & transfer classes can convert those to a `pathlib.Path` to perform actual filesystem operations when necessary.
This commit is contained in:
parent
754edd8711
commit
03fb4da583
@ -41,11 +41,17 @@ class BlendPath(bytes):
|
|||||||
return super().__new__(cls, path.replace(b'\\', b'/'))
|
return super().__new__(cls, path.replace(b'\\', b'/'))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def mkrelative(cls, asset_path: pathlib.Path, bfile_path: pathlib.Path) -> 'BlendPath':
|
def mkrelative(cls, asset_path: pathlib.Path, bfile_path: pathlib.PurePath) -> 'BlendPath':
|
||||||
"""Construct a BlendPath to the asset relative to the blend file."""
|
"""Construct a BlendPath to the asset relative to the blend file.
|
||||||
|
|
||||||
|
Assumes that bfile_path is absolute.
|
||||||
|
"""
|
||||||
from collections import deque
|
from collections import deque
|
||||||
|
|
||||||
bdir_parts = deque(bfile_path.absolute().parent.parts)
|
assert bfile_path.is_absolute(), \
|
||||||
|
'BlendPath().mkrelative(bfile_path=%r) should get absolute bfile_path' % bfile_path
|
||||||
|
|
||||||
|
bdir_parts = deque(bfile_path.parent.parts)
|
||||||
asset_parts = deque(asset_path.absolute().parts)
|
asset_parts = deque(asset_path.absolute().parts)
|
||||||
|
|
||||||
# Remove matching initial parts. What is left in bdir_parts represents
|
# Remove matching initial parts. What is left in bdir_parts represents
|
||||||
|
|||||||
@ -36,7 +36,7 @@ def add_parser(subparsers):
|
|||||||
parser.set_defaults(func=cli_pack)
|
parser.set_defaults(func=cli_pack)
|
||||||
parser.add_argument('blendfile', type=pathlib.Path,
|
parser.add_argument('blendfile', type=pathlib.Path,
|
||||||
help='The Blend file to pack.')
|
help='The Blend file to pack.')
|
||||||
parser.add_argument('target', type=pathlib.Path,
|
parser.add_argument('target', type=str,
|
||||||
help='The target can be a directory, a ZIP file (does not have to exist '
|
help='The target can be a directory, a ZIP file (does not have to exist '
|
||||||
"yet, just use 'something.zip' as target), or a URL of S3 storage "
|
"yet, just use 'something.zip' as target), or a URL of S3 storage "
|
||||||
'(s3://endpoint/path).')
|
'(s3://endpoint/path).')
|
||||||
@ -76,9 +76,8 @@ def cli_pack(args):
|
|||||||
raise SystemExit(1)
|
raise SystemExit(1)
|
||||||
|
|
||||||
|
|
||||||
def create_packer(args, bpath: pathlib.Path, ppath: pathlib.Path,
|
def create_packer(args, bpath: pathlib.Path, ppath: pathlib.Path, target: str) -> pack.Packer:
|
||||||
tpath: pathlib.Path) -> pack.Packer:
|
if target.startswith('s3:/'):
|
||||||
if str(tpath).startswith('s3:/'):
|
|
||||||
if args.noop:
|
if args.noop:
|
||||||
raise ValueError('S3 uploader does not support no-op.')
|
raise ValueError('S3 uploader does not support no-op.')
|
||||||
|
|
||||||
@ -88,18 +87,18 @@ def create_packer(args, bpath: pathlib.Path, ppath: pathlib.Path,
|
|||||||
if args.relative_only:
|
if args.relative_only:
|
||||||
raise ValueError('S3 uploader does not support the --relative-only option')
|
raise ValueError('S3 uploader does not support the --relative-only option')
|
||||||
|
|
||||||
packer = create_s3packer(bpath, ppath, tpath)
|
packer = create_s3packer(bpath, ppath, pathlib.PurePosixPath(target))
|
||||||
elif tpath.suffix.lower() == '.zip':
|
elif target.lower().endswith('.zip'):
|
||||||
from blender_asset_tracer.pack import zipped
|
from blender_asset_tracer.pack import zipped
|
||||||
|
|
||||||
if args.compress:
|
if args.compress:
|
||||||
raise ValueError('ZIP packer does not support on-the-fly compression')
|
raise ValueError('ZIP packer does not support on-the-fly compression')
|
||||||
|
|
||||||
packer = zipped.ZipPacker(bpath, ppath, tpath, noop=args.noop,
|
packer = zipped.ZipPacker(bpath, ppath, target, noop=args.noop,
|
||||||
relative_only=args.relative_only)
|
relative_only=args.relative_only)
|
||||||
else:
|
else:
|
||||||
packer = pack.Packer(bpath, ppath, tpath, noop=args.noop, compress=args.compress,
|
packer = pack.Packer(bpath, ppath, target, noop=args.noop,
|
||||||
relative_only=args.relative_only)
|
compress=args.compress, relative_only=args.relative_only)
|
||||||
|
|
||||||
if args.exclude:
|
if args.exclude:
|
||||||
# args.exclude is a list, due to nargs='*', so we have to split and flatten.
|
# args.exclude is a list, due to nargs='*', so we have to split and flatten.
|
||||||
@ -123,7 +122,7 @@ def create_s3packer(bpath, ppath, tpath) -> pack.Packer:
|
|||||||
return s3.S3Packer(bpath, ppath, tpath, endpoint=endpoint)
|
return s3.S3Packer(bpath, ppath, tpath, endpoint=endpoint)
|
||||||
|
|
||||||
|
|
||||||
def paths_from_cli(args) -> typing.Tuple[pathlib.Path, pathlib.Path, pathlib.Path]:
|
def paths_from_cli(args) -> typing.Tuple[pathlib.Path, pathlib.Path, str]:
|
||||||
"""Return paths to blendfile, project, and pack target.
|
"""Return paths to blendfile, project, and pack target.
|
||||||
|
|
||||||
Calls sys.exit() if anything is wrong.
|
Calls sys.exit() if anything is wrong.
|
||||||
|
|||||||
@ -50,7 +50,7 @@ class AssetAction:
|
|||||||
(if the asset is a blend file) or in another blend file.
|
(if the asset is a blend file) or in another blend file.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.new_path = None # type: typing.Optional[pathlib.Path]
|
self.new_path = None # type: typing.Optional[pathlib.PurePath]
|
||||||
"""Absolute path to the asset in the BAT Pack.
|
"""Absolute path to the asset in the BAT Pack.
|
||||||
|
|
||||||
This path may not exist on the local file system at all, for example
|
This path may not exist on the local file system at all, for example
|
||||||
@ -96,7 +96,7 @@ class Packer:
|
|||||||
def __init__(self,
|
def __init__(self,
|
||||||
bfile: pathlib.Path,
|
bfile: pathlib.Path,
|
||||||
project: pathlib.Path,
|
project: pathlib.Path,
|
||||||
target: pathlib.Path,
|
target: str,
|
||||||
*,
|
*,
|
||||||
noop=False,
|
noop=False,
|
||||||
compress=False,
|
compress=False,
|
||||||
@ -104,6 +104,7 @@ class Packer:
|
|||||||
self.blendfile = bfile
|
self.blendfile = bfile
|
||||||
self.project = project
|
self.project = project
|
||||||
self.target = target
|
self.target = target
|
||||||
|
self._target_path = self._make_target_path(target)
|
||||||
self.noop = noop
|
self.noop = noop
|
||||||
self.compress = compress
|
self.compress = compress
|
||||||
self.relative_only = relative_only
|
self.relative_only = relative_only
|
||||||
@ -128,7 +129,7 @@ class Packer:
|
|||||||
# type: typing.DefaultDict[pathlib.Path, AssetAction]
|
# type: typing.DefaultDict[pathlib.Path, AssetAction]
|
||||||
self.missing_files = set() # type: typing.Set[pathlib.Path]
|
self.missing_files = set() # type: typing.Set[pathlib.Path]
|
||||||
self._new_location_paths = set() # type: typing.Set[pathlib.Path]
|
self._new_location_paths = set() # type: typing.Set[pathlib.Path]
|
||||||
self._output_path = None # type: typing.Optional[pathlib.Path]
|
self._output_path = None # type: typing.Optional[pathlib.PurePath]
|
||||||
|
|
||||||
# Filled by execute()
|
# Filled by execute()
|
||||||
self._file_transferer = None # type: typing.Optional[transfer.FileTransferer]
|
self._file_transferer = None # type: typing.Optional[transfer.FileTransferer]
|
||||||
@ -139,6 +140,15 @@ class Packer:
|
|||||||
self._tmpdir = tempfile.TemporaryDirectory(prefix='bat-', suffix='-batpack')
|
self._tmpdir = tempfile.TemporaryDirectory(prefix='bat-', suffix='-batpack')
|
||||||
self._rewrite_in = pathlib.Path(self._tmpdir.name)
|
self._rewrite_in = pathlib.Path(self._tmpdir.name)
|
||||||
|
|
||||||
|
def _make_target_path(self, target: str) -> pathlib.PurePath:
|
||||||
|
"""Return a Path for the given target.
|
||||||
|
|
||||||
|
This can be the target directory itself, but can also be a non-existent
|
||||||
|
directory if the target doesn't support direct file access. It should
|
||||||
|
only be used to perform path operations, and never for file operations.
|
||||||
|
"""
|
||||||
|
return pathlib.Path(target).absolute()
|
||||||
|
|
||||||
def close(self) -> None:
|
def close(self) -> None:
|
||||||
"""Clean up any temporary files."""
|
"""Clean up any temporary files."""
|
||||||
self._tscb.flush()
|
self._tscb.flush()
|
||||||
@ -151,7 +161,7 @@ class Packer:
|
|||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def output_path(self) -> pathlib.Path:
|
def output_path(self) -> pathlib.PurePath:
|
||||||
"""The path of the packed blend file in the target directory."""
|
"""The path of the packed blend file in the target directory."""
|
||||||
assert self._output_path is not None
|
assert self._output_path is not None
|
||||||
return self._output_path
|
return self._output_path
|
||||||
@ -217,7 +227,7 @@ class Packer:
|
|||||||
# The blendfile that we pack is generally not its own dependency, so
|
# The blendfile that we pack is generally not its own dependency, so
|
||||||
# we have to explicitly add it to the _packed_paths.
|
# we have to explicitly add it to the _packed_paths.
|
||||||
bfile_path = self.blendfile.absolute()
|
bfile_path = self.blendfile.absolute()
|
||||||
bfile_pp = self.target / bfile_path.relative_to(self.project)
|
bfile_pp = self._target_path / bfile_path.relative_to(self.project)
|
||||||
self._output_path = bfile_pp
|
self._output_path = bfile_pp
|
||||||
|
|
||||||
self._progress_cb.pack_start()
|
self._progress_cb.pack_start()
|
||||||
@ -301,7 +311,7 @@ class Packer:
|
|||||||
self._new_location_paths.add(asset_path)
|
self._new_location_paths.add(asset_path)
|
||||||
else:
|
else:
|
||||||
log.debug('%s can keep using %s', bfile_path, usage.asset_path)
|
log.debug('%s can keep using %s', bfile_path, usage.asset_path)
|
||||||
asset_pp = self.target / asset_path.relative_to(self.project)
|
asset_pp = self._target_path / asset_path.relative_to(self.project)
|
||||||
act.new_path = asset_pp
|
act.new_path = asset_pp
|
||||||
|
|
||||||
def _find_new_paths(self):
|
def _find_new_paths(self):
|
||||||
@ -311,7 +321,7 @@ class Packer:
|
|||||||
act = self._actions[path]
|
act = self._actions[path]
|
||||||
assert isinstance(act, AssetAction)
|
assert isinstance(act, AssetAction)
|
||||||
# Like a join, but ignoring the fact that 'path' is absolute.
|
# Like a join, but ignoring the fact that 'path' is absolute.
|
||||||
act.new_path = pathlib.Path(self.target, '_outside_project', *path.parts[1:])
|
act.new_path = pathlib.Path(self._target_path, '_outside_project', *path.parts[1:])
|
||||||
|
|
||||||
def _group_rewrites(self) -> None:
|
def _group_rewrites(self) -> None:
|
||||||
"""For each blend file, collect which fields need rewriting.
|
"""For each blend file, collect which fields need rewriting.
|
||||||
@ -515,7 +525,7 @@ class Packer:
|
|||||||
|
|
||||||
def _send_to_target(self,
|
def _send_to_target(self,
|
||||||
asset_path: pathlib.Path,
|
asset_path: pathlib.Path,
|
||||||
target: pathlib.Path,
|
target: pathlib.PurePath,
|
||||||
may_move=False):
|
may_move=False):
|
||||||
if self.noop:
|
if self.noop:
|
||||||
print('%s -> %s' % (asset_path, target))
|
print('%s -> %s' % (asset_path, target))
|
||||||
@ -542,6 +552,7 @@ class Packer:
|
|||||||
with infopath.open('wt', encoding='utf8') as infofile:
|
with infopath.open('wt', encoding='utf8') as infofile:
|
||||||
print('This is a Blender Asset Tracer pack.', file=infofile)
|
print('This is a Blender Asset Tracer pack.', file=infofile)
|
||||||
print('Start by opening the following blend file:', file=infofile)
|
print('Start by opening the following blend file:', file=infofile)
|
||||||
print(' %s' % self._output_path.relative_to(self.target).as_posix(), file=infofile)
|
print(' %s' % self._output_path.relative_to(self._target_path).as_posix(),
|
||||||
|
file=infofile)
|
||||||
|
|
||||||
self._file_transferer.queue_move(infopath, self.target / infoname)
|
self._file_transferer.queue_move(infopath, self._target_path / infoname)
|
||||||
|
|||||||
@ -59,11 +59,12 @@ class FileCopier(transfer.FileTransferer):
|
|||||||
|
|
||||||
pool = multiprocessing.pool.ThreadPool(processes=self.transfer_threads)
|
pool = multiprocessing.pool.ThreadPool(processes=self.transfer_threads)
|
||||||
|
|
||||||
for src, dst, act in self.iter_queue():
|
for src, pure_dst, act in self.iter_queue():
|
||||||
try:
|
try:
|
||||||
if self._error.is_set() or self._abort.is_set():
|
if self._error.is_set() or self._abort.is_set():
|
||||||
raise AbortTransfer()
|
raise AbortTransfer()
|
||||||
|
|
||||||
|
dst = pathlib.Path(pure_dst)
|
||||||
if self._skip_file(src, dst, act):
|
if self._skip_file(src, dst, act):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|||||||
@ -38,7 +38,7 @@ class Callback(blender_asset_tracer.trace.progress.Callback):
|
|||||||
"""Called when packing starts."""
|
"""Called when packing starts."""
|
||||||
|
|
||||||
def pack_done(self,
|
def pack_done(self,
|
||||||
output_blendfile: pathlib.Path,
|
output_blendfile: pathlib.PurePath,
|
||||||
missing_files: typing.Set[pathlib.Path]) -> None:
|
missing_files: typing.Set[pathlib.Path]) -> None:
|
||||||
"""Called when packing is done."""
|
"""Called when packing is done."""
|
||||||
|
|
||||||
@ -57,10 +57,10 @@ class Callback(blender_asset_tracer.trace.progress.Callback):
|
|||||||
def rewrite_blendfile(self, orig_filename: pathlib.Path) -> None:
|
def rewrite_blendfile(self, orig_filename: pathlib.Path) -> None:
|
||||||
"""Called for every rewritten blendfile."""
|
"""Called for every rewritten blendfile."""
|
||||||
|
|
||||||
def transfer_file(self, src: pathlib.Path, dst: pathlib.Path) -> None:
|
def transfer_file(self, src: pathlib.Path, dst: pathlib.PurePath) -> None:
|
||||||
"""Called when a file transfer starts."""
|
"""Called when a file transfer starts."""
|
||||||
|
|
||||||
def transfer_file_skipped(self, src: pathlib.Path, dst: pathlib.Path) -> None:
|
def transfer_file_skipped(self, src: pathlib.Path, dst: pathlib.PurePath) -> None:
|
||||||
"""Called when a file is skipped because it already exists."""
|
"""Called when a file is skipped because it already exists."""
|
||||||
|
|
||||||
def transfer_progress(self, total_bytes: int, transferred_bytes: int) -> None:
|
def transfer_progress(self, total_bytes: int, transferred_bytes: int) -> None:
|
||||||
@ -105,7 +105,7 @@ class ThreadSafeCallback(Callback):
|
|||||||
self._queue(self.wrapped.pack_start)
|
self._queue(self.wrapped.pack_start)
|
||||||
|
|
||||||
def pack_done(self,
|
def pack_done(self,
|
||||||
output_blendfile: pathlib.Path,
|
output_blendfile: pathlib.PurePath,
|
||||||
missing_files: typing.Set[pathlib.Path]) -> None:
|
missing_files: typing.Set[pathlib.Path]) -> None:
|
||||||
self._queue(self.wrapped.pack_done, output_blendfile, missing_files)
|
self._queue(self.wrapped.pack_done, output_blendfile, missing_files)
|
||||||
|
|
||||||
@ -115,10 +115,10 @@ class ThreadSafeCallback(Callback):
|
|||||||
def trace_asset(self, filename: pathlib.Path) -> None:
|
def trace_asset(self, filename: pathlib.Path) -> None:
|
||||||
self._queue(self.wrapped.trace_asset, filename)
|
self._queue(self.wrapped.trace_asset, filename)
|
||||||
|
|
||||||
def transfer_file(self, src: pathlib.Path, dst: pathlib.Path) -> None:
|
def transfer_file(self, src: pathlib.Path, dst: pathlib.PurePath) -> None:
|
||||||
self._queue(self.wrapped.transfer_file, src, dst)
|
self._queue(self.wrapped.transfer_file, src, dst)
|
||||||
|
|
||||||
def transfer_file_skipped(self, src: pathlib.Path, dst: pathlib.Path) -> None:
|
def transfer_file_skipped(self, src: pathlib.Path, dst: pathlib.PurePath) -> None:
|
||||||
self._queue(self.wrapped.transfer_file_skipped, src, dst)
|
self._queue(self.wrapped.transfer_file_skipped, src, dst)
|
||||||
|
|
||||||
def transfer_progress(self, total_bytes: int, transferred_bytes: int) -> None:
|
def transfer_progress(self, total_bytes: int, transferred_bytes: int) -> None:
|
||||||
|
|||||||
@ -120,7 +120,7 @@ class S3Transferrer(transfer.FileTransferer):
|
|||||||
if files_skipped:
|
if files_skipped:
|
||||||
log.info('Skipped %d files', files_skipped)
|
log.info('Skipped %d files', files_skipped)
|
||||||
|
|
||||||
def upload_file(self, src: pathlib.Path, dst: pathlib.Path) -> bool:
|
def upload_file(self, src: pathlib.Path, dst: pathlib.PurePath) -> bool:
|
||||||
"""Upload a file to an S3 bucket.
|
"""Upload a file to an S3 bucket.
|
||||||
|
|
||||||
The first part of 'dst' is used as the bucket name, the remained as the
|
The first part of 'dst' is used as the bucket name, the remained as the
|
||||||
|
|||||||
@ -44,7 +44,7 @@ class Action(enum.Enum):
|
|||||||
MOVE = 2
|
MOVE = 2
|
||||||
|
|
||||||
|
|
||||||
QueueItem = typing.Tuple[pathlib.Path, pathlib.Path, Action]
|
QueueItem = typing.Tuple[pathlib.Path, pathlib.PurePath, Action]
|
||||||
|
|
||||||
|
|
||||||
class FileTransferer(threading.Thread, metaclass=abc.ABCMeta):
|
class FileTransferer(threading.Thread, metaclass=abc.ABCMeta):
|
||||||
@ -82,7 +82,7 @@ class FileTransferer(threading.Thread, metaclass=abc.ABCMeta):
|
|||||||
def run(self):
|
def run(self):
|
||||||
"""Perform actual file transfer in a thread."""
|
"""Perform actual file transfer in a thread."""
|
||||||
|
|
||||||
def queue_copy(self, src: pathlib.Path, dst: pathlib.Path):
|
def queue_copy(self, src: pathlib.Path, dst: pathlib.PurePath):
|
||||||
"""Queue a copy action from 'src' to 'dst'."""
|
"""Queue a copy action from 'src' to 'dst'."""
|
||||||
assert not self.done.is_set(), 'Queueing not allowed after done_and_join() was called'
|
assert not self.done.is_set(), 'Queueing not allowed after done_and_join() was called'
|
||||||
assert not self._abort.is_set(), 'Queueing not allowed after abort_and_join() was called'
|
assert not self._abort.is_set(), 'Queueing not allowed after abort_and_join() was called'
|
||||||
@ -91,7 +91,7 @@ class FileTransferer(threading.Thread, metaclass=abc.ABCMeta):
|
|||||||
self.queue.put((src, dst, Action.COPY))
|
self.queue.put((src, dst, Action.COPY))
|
||||||
self.total_queued_bytes += src.stat().st_size
|
self.total_queued_bytes += src.stat().st_size
|
||||||
|
|
||||||
def queue_move(self, src: pathlib.Path, dst: pathlib.Path):
|
def queue_move(self, src: pathlib.Path, dst: pathlib.PurePath):
|
||||||
"""Queue a move action from 'src' to 'dst'."""
|
"""Queue a move action from 'src' to 'dst'."""
|
||||||
assert not self.done.is_set(), 'Queueing not allowed after done_and_join() was called'
|
assert not self.done.is_set(), 'Queueing not allowed after done_and_join() was called'
|
||||||
assert not self._abort.is_set(), 'Queueing not allowed after abort_and_join() was called'
|
assert not self._abort.is_set(), 'Queueing not allowed after abort_and_join() was called'
|
||||||
|
|||||||
@ -37,7 +37,8 @@ class ZipPacker(Packer):
|
|||||||
"""Creates a zipped BAT Pack instead of a directory."""
|
"""Creates a zipped BAT Pack instead of a directory."""
|
||||||
|
|
||||||
def _create_file_transferer(self) -> transfer.FileTransferer:
|
def _create_file_transferer(self) -> transfer.FileTransferer:
|
||||||
return ZipTransferrer(self.target.absolute())
|
target_path = pathlib.Path(self._target_path)
|
||||||
|
return ZipTransferrer(target_path.absolute())
|
||||||
|
|
||||||
|
|
||||||
class ZipTransferrer(transfer.FileTransferer):
|
class ZipTransferrer(transfer.FileTransferer):
|
||||||
@ -61,7 +62,7 @@ class ZipTransferrer(transfer.FileTransferer):
|
|||||||
for src, dst, act in self.iter_queue():
|
for src, dst, act in self.iter_queue():
|
||||||
assert src.is_absolute(), 'expecting only absolute paths, not %r' % src
|
assert src.is_absolute(), 'expecting only absolute paths, not %r' % src
|
||||||
|
|
||||||
dst = dst.absolute()
|
dst = pathlib.Path(dst).absolute()
|
||||||
try:
|
try:
|
||||||
relpath = dst.relative_to(zippath)
|
relpath = dst.relative_to(zippath)
|
||||||
|
|
||||||
|
|||||||
@ -87,25 +87,25 @@ class BlendPathTest(unittest.TestCase):
|
|||||||
def test_mkrelative(self):
|
def test_mkrelative(self):
|
||||||
self.assertEqual(b'//asset.png', BlendPath.mkrelative(
|
self.assertEqual(b'//asset.png', BlendPath.mkrelative(
|
||||||
Path('/path/to/asset.png'),
|
Path('/path/to/asset.png'),
|
||||||
Path('/path/to/bfile.blend'),
|
PurePosixPath('/path/to/bfile.blend'),
|
||||||
))
|
))
|
||||||
self.assertEqual(b'//to/asset.png', BlendPath.mkrelative(
|
self.assertEqual(b'//to/asset.png', BlendPath.mkrelative(
|
||||||
Path('/path/to/asset.png'),
|
Path('/path/to/asset.png'),
|
||||||
Path('/path/bfile.blend'),
|
PurePosixPath('/path/bfile.blend'),
|
||||||
))
|
))
|
||||||
self.assertEqual(b'//../of/asset.png', BlendPath.mkrelative(
|
self.assertEqual(b'//../of/asset.png', BlendPath.mkrelative(
|
||||||
Path('/path/of/asset.png'),
|
Path('/path/of/asset.png'),
|
||||||
Path('/path/to/bfile.blend'),
|
PurePosixPath('/path/to/bfile.blend'),
|
||||||
))
|
))
|
||||||
self.assertEqual(b'//../../path/of/asset.png', BlendPath.mkrelative(
|
self.assertEqual(b'//../../path/of/asset.png', BlendPath.mkrelative(
|
||||||
Path('/path/of/asset.png'),
|
Path('/path/of/asset.png'),
|
||||||
Path('/some/weird/bfile.blend'),
|
PurePosixPath('/some/weird/bfile.blend'),
|
||||||
))
|
))
|
||||||
self.assertEqual(b'//very/very/very/very/very/deep/asset.png', BlendPath.mkrelative(
|
self.assertEqual(b'//very/very/very/very/very/deep/asset.png', BlendPath.mkrelative(
|
||||||
Path('/path/to/very/very/very/very/very/deep/asset.png'),
|
Path('/path/to/very/very/very/very/very/deep/asset.png'),
|
||||||
Path('/path/to/bfile.blend'),
|
PurePosixPath('/path/to/bfile.blend'),
|
||||||
))
|
))
|
||||||
self.assertEqual(b'//../../../../../../../../shallow/asset.png', BlendPath.mkrelative(
|
self.assertEqual(b'//../../../../../../../../shallow/asset.png', BlendPath.mkrelative(
|
||||||
Path('/shallow/asset.png'),
|
Path('/shallow/asset.png'),
|
||||||
Path('/path/to/very/very/very/very/very/deep/bfile.blend'),
|
PurePosixPath('/path/to/very/very/very/very/very/deep/bfile.blend'),
|
||||||
))
|
))
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user