Normalize unused paths in packed linked blends
This commit is contained in:
parent
89bcad5cdc
commit
8bc8165642
@ -142,6 +142,8 @@ class Packer:
|
|||||||
self._top_level_paths = set() # type: typing.Set[pathlib.Path]
|
self._top_level_paths = set() # type: typing.Set[pathlib.Path]
|
||||||
self._output_path = None # type: typing.Optional[pathlib.PurePath]
|
self._output_path = None # type: typing.Optional[pathlib.PurePath]
|
||||||
self._output_paths = [] # type: typing.List[pathlib.PurePath]
|
self._output_paths = [] # type: typing.List[pathlib.PurePath]
|
||||||
|
self._rewrite_targets = {} # type: typing.Dict[int, pathlib.PurePath]
|
||||||
|
"""Destinations keyed by usage identity for path-only rewrites."""
|
||||||
|
|
||||||
# Filled by execute()
|
# Filled by execute()
|
||||||
self._file_transferer = None # type: typing.Optional[transfer.FileTransferer]
|
self._file_transferer = None # type: typing.Optional[transfer.FileTransferer]
|
||||||
@ -303,6 +305,70 @@ class Packer:
|
|||||||
self._output_path = self._output_paths[0] # backward compat
|
self._output_path = self._output_paths[0] # backward compat
|
||||||
self._find_new_paths()
|
self._find_new_paths()
|
||||||
self._group_rewrites()
|
self._group_rewrites()
|
||||||
|
self._normalise_linked_blend_paths()
|
||||||
|
|
||||||
|
def _normalise_linked_blend_paths(self) -> None:
|
||||||
|
"""Make absolute paths in copied libraries portable.
|
||||||
|
|
||||||
|
Dependency tracing intentionally visits only datablocks used by the
|
||||||
|
top-level blend files. Linked blend files are nevertheless copied in
|
||||||
|
full, so unused datablocks can otherwise retain absolute paths such as
|
||||||
|
``R:/project/asset.vdb``. Normalize those paths to their expected pack
|
||||||
|
locations without adding the referenced files to the pack.
|
||||||
|
"""
|
||||||
|
if self.relative_only:
|
||||||
|
return
|
||||||
|
|
||||||
|
linked_blends = [
|
||||||
|
path
|
||||||
|
for path, action in self._actions.items()
|
||||||
|
if path not in self._top_level_paths
|
||||||
|
and path.suffix.lower() == ".blend"
|
||||||
|
and action.new_path is not None
|
||||||
|
]
|
||||||
|
def usage_key(usage: result.BlockUsage) -> typing.Tuple:
|
||||||
|
field_names: typing.Tuple[bytes, ...]
|
||||||
|
if usage.path_full_field is not None:
|
||||||
|
field_names = (usage.path_full_field.name.name_full,)
|
||||||
|
else:
|
||||||
|
assert usage.path_dir_field is not None
|
||||||
|
assert usage.path_base_field is not None
|
||||||
|
field_names = (
|
||||||
|
usage.path_dir_field.name.name_full,
|
||||||
|
usage.path_base_field.name.name_full,
|
||||||
|
)
|
||||||
|
return usage.block.addr_old, field_names
|
||||||
|
|
||||||
|
for bfile_path in linked_blends:
|
||||||
|
action = self._actions[bfile_path]
|
||||||
|
existing_usages = {usage_key(usage) for usage in action.rewrites}
|
||||||
|
for usage in trace.local_deps(bfile_path):
|
||||||
|
key = usage_key(usage)
|
||||||
|
if key in existing_usages:
|
||||||
|
continue
|
||||||
|
if usage.asset_path.is_blendfile_relative():
|
||||||
|
continue
|
||||||
|
|
||||||
|
asset_path = usage.abspath
|
||||||
|
if self.keep_hierarchy:
|
||||||
|
asset_pp = self._target_path / bpathlib.strip_root(asset_path)
|
||||||
|
elif self._path_in_project(asset_path):
|
||||||
|
asset_pp = self._target_path / asset_path.relative_to(self.project)
|
||||||
|
else:
|
||||||
|
asset_pp = (
|
||||||
|
self._target_path
|
||||||
|
/ "_outside_project"
|
||||||
|
/ bpathlib.strip_root(asset_path)
|
||||||
|
)
|
||||||
|
|
||||||
|
log.info(
|
||||||
|
"Normalizing uncopied path in %s: %s",
|
||||||
|
bfile_path,
|
||||||
|
usage.asset_path,
|
||||||
|
)
|
||||||
|
action.rewrites.append(usage)
|
||||||
|
self._rewrite_targets[id(usage)] = pathlib.Path(asset_pp)
|
||||||
|
existing_usages.add(key)
|
||||||
|
|
||||||
def _visit_sequence(self, asset_path: pathlib.Path, usage: result.BlockUsage):
|
def _visit_sequence(self, asset_path: pathlib.Path, usage: result.BlockUsage):
|
||||||
assert usage.is_sequence
|
assert usage.is_sequence
|
||||||
@ -543,8 +609,10 @@ class Packer:
|
|||||||
for usage in action.rewrites:
|
for usage in action.rewrites:
|
||||||
self._check_aborted()
|
self._check_aborted()
|
||||||
assert isinstance(usage, result.BlockUsage)
|
assert isinstance(usage, result.BlockUsage)
|
||||||
|
asset_pp = self._rewrite_targets.get(id(usage))
|
||||||
|
if asset_pp is None:
|
||||||
asset_pp = self._actions[usage.abspath].new_path
|
asset_pp = self._actions[usage.abspath].new_path
|
||||||
assert isinstance(asset_pp, pathlib.Path)
|
assert isinstance(asset_pp, pathlib.PurePath)
|
||||||
|
|
||||||
log.debug(" - %s is packed at %s", usage.asset_path, asset_pp)
|
log.debug(" - %s is packed at %s", usage.asset_path, asset_pp)
|
||||||
relpath = bpathlib.BlendPath.mkrelative(asset_pp, bfile_pp)
|
relpath = bpathlib.BlendPath.mkrelative(asset_pp, bfile_pp)
|
||||||
|
|||||||
@ -68,6 +68,18 @@ def deps(
|
|||||||
yield block_usage
|
yield block_usage
|
||||||
|
|
||||||
|
|
||||||
|
def local_deps(bfilepath: pathlib.Path) -> typing.Iterator[result.BlockUsage]:
|
||||||
|
"""Report assets from every local block, without following libraries.
|
||||||
|
|
||||||
|
This is used for path-only normalization of linked blend files that are
|
||||||
|
copied whole into a pack. Unlike :func:`deps`, it deliberately inspects
|
||||||
|
unused datablocks too, but does not recursively collect their assets.
|
||||||
|
"""
|
||||||
|
bfile = blendfile.open_cached(bfilepath)
|
||||||
|
for block in asset_holding_blocks(bfile.blocks):
|
||||||
|
yield from blocks2assets.iter_assets(block)
|
||||||
|
|
||||||
|
|
||||||
def asset_holding_blocks(
|
def asset_holding_blocks(
|
||||||
blocks: typing.Iterable[blendfile.BlendFileBlock],
|
blocks: typing.Iterable[blendfile.BlendFileBlock],
|
||||||
) -> typing.Iterator[blendfile.BlendFileBlock]:
|
) -> typing.Iterator[blendfile.BlendFileBlock]:
|
||||||
|
|||||||
@ -10,6 +10,7 @@ from unittest import mock
|
|||||||
|
|
||||||
from blender_asset_tracer import blendfile, pack, bpathlib
|
from blender_asset_tracer import blendfile, pack, bpathlib
|
||||||
from blender_asset_tracer.pack import progress
|
from blender_asset_tracer.pack import progress
|
||||||
|
from blender_asset_tracer.trace import result
|
||||||
from tests.abstract_test import AbstractBlendFileTest
|
from tests.abstract_test import AbstractBlendFileTest
|
||||||
|
|
||||||
|
|
||||||
@ -151,6 +152,49 @@ class PackTest(AbstractPackTest):
|
|||||||
self.assertEqual(b"LILib.002", rw_dbllink[1].block_name)
|
self.assertEqual(b"LILib.002", rw_dbllink[1].block_name)
|
||||||
self.assertEqual(b"//../material_textures.blend", rw_dbllink[1].asset_path)
|
self.assertEqual(b"//../material_textures.blend", rw_dbllink[1].asset_path)
|
||||||
|
|
||||||
|
def test_normalises_unused_path_without_packing_asset(self):
|
||||||
|
infile = self.blendfiles / "doubly_linked.blend"
|
||||||
|
linked_file = self.blendfiles / "linked_cube.blend"
|
||||||
|
uncopied_asset = self.blendfiles / "uncopied.blend"
|
||||||
|
|
||||||
|
linked_bfile = blendfile.open_cached(linked_file)
|
||||||
|
library_block = linked_bfile.code_index[b"LI"][0]
|
||||||
|
_, path_field = library_block.get(b"name", return_field=True)
|
||||||
|
unused_usage = result.BlockUsage(
|
||||||
|
library_block,
|
||||||
|
bpathlib.BlendPath(str(uncopied_asset).encode()),
|
||||||
|
path_full_field=path_field,
|
||||||
|
)
|
||||||
|
|
||||||
|
def local_deps(path):
|
||||||
|
if path == linked_file:
|
||||||
|
return iter([unused_usage])
|
||||||
|
return iter(())
|
||||||
|
|
||||||
|
with mock.patch(
|
||||||
|
"blender_asset_tracer.pack.trace.local_deps", side_effect=local_deps
|
||||||
|
):
|
||||||
|
with pack.Packer(infile, self.blendfiles, self.tpath) as packer:
|
||||||
|
packer.strategise()
|
||||||
|
|
||||||
|
self.assertNotIn(uncopied_asset, packer._actions)
|
||||||
|
self.assertIn(
|
||||||
|
unused_usage, packer._actions[linked_file].rewrites
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
self.tpath / "uncopied.blend",
|
||||||
|
packer._rewrite_targets[id(unused_usage)],
|
||||||
|
)
|
||||||
|
|
||||||
|
packer.execute()
|
||||||
|
|
||||||
|
packed_linked = blendfile.open_cached(
|
||||||
|
self.tpath / "linked_cube.blend", assert_cached=False
|
||||||
|
)
|
||||||
|
packed_library = packed_linked.code_index[b"LI"][0]
|
||||||
|
self.assertEqual(b"//uncopied.blend", packed_library[b"name"])
|
||||||
|
self.assertFalse((self.tpath / "uncopied.blend").exists())
|
||||||
|
|
||||||
def test_strategise_relative_only(self):
|
def test_strategise_relative_only(self):
|
||||||
infile = self.blendfiles / "absolute_path.blend"
|
infile = self.blendfiles / "absolute_path.blend"
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user