Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4085f0015f | |||
| cf31dbc5ec | |||
| bb1de717c8 | |||
| 12e6e2643b | |||
| cbbe8be0d8 |
@ -1,4 +1,4 @@
|
|||||||
# Blender Asset Tracer BAT🦇
|
# Blender Asset Tracer BAT🦇 (ADV fork)
|
||||||
|
|
||||||
Script to manage assets with Blender.
|
Script to manage assets with Blender.
|
||||||
|
|
||||||
|
|||||||
@ -21,3 +21,56 @@
|
|||||||
# <pep8 compliant>
|
# <pep8 compliant>
|
||||||
|
|
||||||
__version__ = "1.21"
|
__version__ = "1.21"
|
||||||
|
|
||||||
|
bl_info = {
|
||||||
|
"name": "Blender Asset Tracer",
|
||||||
|
"author": "Campbell Barton, Sybren A. St\u00fcvel, Lo\u00efc Charri\u00e8re, Cl\u00e9ment Ducarteron, Mario Hawat, Joseph Henry",
|
||||||
|
"version": (1, 21, 0),
|
||||||
|
"blender": (2, 80, 0),
|
||||||
|
"location": "File > External Data > BAT",
|
||||||
|
"description": "Utility for packing blend files",
|
||||||
|
"warning": "",
|
||||||
|
"wiki_url": "https://developer.blender.org/project/profile/79/",
|
||||||
|
"category": "Import-Export",
|
||||||
|
}
|
||||||
|
|
||||||
|
# Reset root module name if folder has an unexpected name
|
||||||
|
# (like "blender_asset_tracer-main" from zip-dl)
|
||||||
|
import sys
|
||||||
|
|
||||||
|
if __name__ != "blender_asset_tracer":
|
||||||
|
sys.modules["blender_asset_tracer"] = sys.modules[__name__]
|
||||||
|
|
||||||
|
try:
|
||||||
|
import bpy
|
||||||
|
|
||||||
|
_HAS_BPY = True
|
||||||
|
except ImportError:
|
||||||
|
_HAS_BPY = False
|
||||||
|
|
||||||
|
if _HAS_BPY:
|
||||||
|
from blender_asset_tracer import blendfile
|
||||||
|
from . import preferences, operators
|
||||||
|
|
||||||
|
# Match the CLI's default: skip dangling pointers gracefully instead of crashing.
|
||||||
|
# Production blend files often have references to missing linked libraries.
|
||||||
|
blendfile.set_strict_pointer_mode(False)
|
||||||
|
|
||||||
|
classes = (
|
||||||
|
preferences.BATPreferences,
|
||||||
|
operators.ExportBatPack,
|
||||||
|
operators.BAT_OT_export_zip,
|
||||||
|
)
|
||||||
|
|
||||||
|
def register():
|
||||||
|
for cls in classes:
|
||||||
|
bpy.utils.register_class(cls)
|
||||||
|
bpy.types.TOPBAR_MT_file_external_data.append(operators.menu_func)
|
||||||
|
|
||||||
|
def unregister():
|
||||||
|
bpy.types.TOPBAR_MT_file_external_data.remove(operators.menu_func)
|
||||||
|
for cls in classes:
|
||||||
|
bpy.utils.unregister_class(cls)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
register()
|
||||||
|
|||||||
21
blender_asset_tracer/cli/pack.py
Normal file → Executable file
21
blender_asset_tracer/cli/pack.py
Normal file → Executable file
@ -88,6 +88,15 @@ def add_parser(subparsers):
|
|||||||
help="Only pack assets that are referred to with a relative path (e.g. "
|
help="Only pack assets that are referred to with a relative path (e.g. "
|
||||||
"starting with `//`.",
|
"starting with `//`.",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--keep-hierarchy",
|
||||||
|
default=False,
|
||||||
|
action="store_true",
|
||||||
|
help="Preserve the full filesystem directory hierarchy in the pack. "
|
||||||
|
"All files (including the blend file) are placed at their absolute "
|
||||||
|
"path structure under the target directory. Paths in blend files are "
|
||||||
|
"rewritten to relative paths within this structure.",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def cli_pack(args):
|
def cli_pack(args):
|
||||||
@ -119,6 +128,9 @@ def create_packer(
|
|||||||
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")
|
||||||
|
|
||||||
|
if args.keep_hierarchy:
|
||||||
|
raise ValueError("S3 uploader does not support the --keep-hierarchy option")
|
||||||
|
|
||||||
packer = create_s3packer(bpath, ppath, pathlib.PurePosixPath(target))
|
packer = create_s3packer(bpath, ppath, pathlib.PurePosixPath(target))
|
||||||
|
|
||||||
elif (
|
elif (
|
||||||
@ -137,6 +149,11 @@ def create_packer(
|
|||||||
"Shaman uploader does not support the --relative-only option"
|
"Shaman uploader does not support the --relative-only option"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if args.keep_hierarchy:
|
||||||
|
raise ValueError(
|
||||||
|
"Shaman uploader does not support the --keep-hierarchy option"
|
||||||
|
)
|
||||||
|
|
||||||
packer = create_shamanpacker(bpath, ppath, target)
|
packer = create_shamanpacker(bpath, ppath, target)
|
||||||
|
|
||||||
elif target.lower().endswith(".zip"):
|
elif target.lower().endswith(".zip"):
|
||||||
@ -146,7 +163,8 @@ def create_packer(
|
|||||||
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(
|
packer = zipped.ZipPacker(
|
||||||
bpath, ppath, target, noop=args.noop, relative_only=args.relative_only
|
bpath, ppath, target, noop=args.noop, relative_only=args.relative_only,
|
||||||
|
keep_hierarchy=args.keep_hierarchy,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
packer = pack.Packer(
|
packer = pack.Packer(
|
||||||
@ -156,6 +174,7 @@ def create_packer(
|
|||||||
noop=args.noop,
|
noop=args.noop,
|
||||||
compress=args.compress,
|
compress=args.compress,
|
||||||
relative_only=args.relative_only,
|
relative_only=args.relative_only,
|
||||||
|
keep_hierarchy=args.keep_hierarchy,
|
||||||
)
|
)
|
||||||
|
|
||||||
if args.exclude:
|
if args.exclude:
|
||||||
|
|||||||
163
blender_asset_tracer/operators.py
Normal file
163
blender_asset_tracer/operators.py
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
import zipfile
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
from bpy.types import Operator
|
||||||
|
from bpy_extras.io_utils import ExportHelper
|
||||||
|
|
||||||
|
from blender_asset_tracer.pack import zipped
|
||||||
|
|
||||||
|
|
||||||
|
class ExportBatPack(Operator, ExportHelper):
|
||||||
|
bl_idname = "export_bat.pack"
|
||||||
|
bl_label = "BAT - Zip pack (flat)"
|
||||||
|
filename_ext = ".zip"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return bpy.data.is_saved
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
outfname = bpy.path.ensure_ext(self.filepath, ".zip")
|
||||||
|
self.report({"INFO"}, "Executing ZipPacker ...")
|
||||||
|
|
||||||
|
with zipped.ZipPacker(
|
||||||
|
Path(bpy.data.filepath),
|
||||||
|
Path(bpy.data.filepath).parent,
|
||||||
|
str(self.filepath),
|
||||||
|
) as packer:
|
||||||
|
packer.strategise()
|
||||||
|
packer.execute()
|
||||||
|
self.report({"INFO"}, "Packing successful!")
|
||||||
|
|
||||||
|
with zipfile.ZipFile(str(self.filepath)) as inzip:
|
||||||
|
inzip.testzip()
|
||||||
|
|
||||||
|
self.report({"INFO"}, "Written to %s" % outfname)
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
def open_folder(folderpath):
|
||||||
|
"""Open the folder at the path given with cmd relative to user's OS."""
|
||||||
|
from shutil import which
|
||||||
|
|
||||||
|
my_os = sys.platform
|
||||||
|
if my_os.startswith(("linux", "freebsd")):
|
||||||
|
cmd = "xdg-open"
|
||||||
|
elif my_os.startswith("win"):
|
||||||
|
cmd = "explorer"
|
||||||
|
if not folderpath:
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
cmd = "open"
|
||||||
|
|
||||||
|
if not folderpath:
|
||||||
|
return
|
||||||
|
|
||||||
|
folderpath = str(folderpath)
|
||||||
|
if os.path.isfile(folderpath):
|
||||||
|
select = False
|
||||||
|
if my_os.startswith("win"):
|
||||||
|
cmd = "explorer /select,"
|
||||||
|
select = True
|
||||||
|
elif my_os.startswith(("linux", "freebsd")):
|
||||||
|
if which("nemo"):
|
||||||
|
cmd = "nemo --no-desktop"
|
||||||
|
select = True
|
||||||
|
elif which("nautilus"):
|
||||||
|
cmd = "nautilus --no-desktop"
|
||||||
|
select = True
|
||||||
|
if not select:
|
||||||
|
folderpath = os.path.dirname(folderpath)
|
||||||
|
|
||||||
|
folderpath = os.path.normpath(folderpath)
|
||||||
|
fullcmd = cmd.split() + [folderpath]
|
||||||
|
subprocess.Popen(fullcmd)
|
||||||
|
|
||||||
|
|
||||||
|
class BAT_OT_export_zip(Operator, ExportHelper):
|
||||||
|
"""Export current blendfile with hierarchy preservation"""
|
||||||
|
|
||||||
|
bl_label = "BAT - Zip pack (keep hierarchy)"
|
||||||
|
bl_idname = "bat.export_zip"
|
||||||
|
|
||||||
|
filename_ext = ".zip"
|
||||||
|
|
||||||
|
root_dir: bpy.props.StringProperty(
|
||||||
|
name="Root",
|
||||||
|
description="Top Level Folder of your project."
|
||||||
|
"\nFor now Copy/Paste correct folder by hand if default is incorrect."
|
||||||
|
"\n!!! Everything outside won't be zipped !!!",
|
||||||
|
)
|
||||||
|
|
||||||
|
use_zip: bpy.props.BoolProperty(
|
||||||
|
name="Output as ZIP",
|
||||||
|
description="If enabled, pack into a ZIP archive. If disabled, copy to a directory.",
|
||||||
|
default=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return bpy.data.is_saved
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
from blender_asset_tracer.pack import Packer
|
||||||
|
from blender_asset_tracer.pack.zipped import ZipPacker
|
||||||
|
|
||||||
|
bfile = Path(bpy.data.filepath)
|
||||||
|
project = Path(self.root_dir) if self.root_dir else bfile.parent
|
||||||
|
target = str(self.filepath)
|
||||||
|
|
||||||
|
if self.use_zip:
|
||||||
|
target = bpy.path.ensure_ext(target, ".zip")
|
||||||
|
packer_cls = ZipPacker
|
||||||
|
else:
|
||||||
|
if target.lower().endswith(".zip"):
|
||||||
|
target = target[:-4]
|
||||||
|
packer_cls = Packer
|
||||||
|
|
||||||
|
self.report({"INFO"}, "Packing with hierarchy...")
|
||||||
|
|
||||||
|
with packer_cls(bfile, project, target, keep_hierarchy=True) as packer:
|
||||||
|
packer.strategise()
|
||||||
|
packer.execute()
|
||||||
|
|
||||||
|
if self.use_zip:
|
||||||
|
with zipfile.ZipFile(target) as inzip:
|
||||||
|
inzip.testzip()
|
||||||
|
log_output = Path(tempfile.gettempdir(), "README.txt")
|
||||||
|
with open(log_output, "w") as log:
|
||||||
|
log.write("Packed with BAT (keep-hierarchy mode)")
|
||||||
|
log.write(f"\nBlend file: {bpy.data.filepath}")
|
||||||
|
with zipfile.ZipFile(target, "a") as zipObj:
|
||||||
|
zipObj.write(log_output, log_output.name)
|
||||||
|
|
||||||
|
self.report({"INFO"}, "Written to %s" % target)
|
||||||
|
open_folder(Path(target).parent)
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
def menu_func(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
layout.separator()
|
||||||
|
layout.operator(ExportBatPack.bl_idname)
|
||||||
|
filepath = layout.operator(BAT_OT_export_zip.bl_idname)
|
||||||
|
|
||||||
|
try:
|
||||||
|
prefs = bpy.context.preferences.addons["blender_asset_tracer"].preferences
|
||||||
|
root_dir_env = None
|
||||||
|
if prefs.use_env_root:
|
||||||
|
root_dir_env = os.getenv("ZIP_ROOT")
|
||||||
|
if not root_dir_env:
|
||||||
|
root_dir_env = os.getenv("PROJECT_ROOT")
|
||||||
|
if not root_dir_env:
|
||||||
|
root_dir_env = prefs.root_default
|
||||||
|
filepath.root_dir = "" if root_dir_env is None else root_dir_env
|
||||||
|
except Exception:
|
||||||
|
filepath.root_dir = os.getenv("ZIP_ROOT") or os.getenv("PROJECT_ROOT") or ""
|
||||||
|
|
||||||
|
|
||||||
21
blender_asset_tracer/pack/__init__.py
Normal file → Executable file
21
blender_asset_tracer/pack/__init__.py
Normal file → Executable file
@ -103,6 +103,7 @@ class Packer:
|
|||||||
noop=False,
|
noop=False,
|
||||||
compress=False,
|
compress=False,
|
||||||
relative_only=False,
|
relative_only=False,
|
||||||
|
keep_hierarchy=False,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.blendfile = bfile
|
self.blendfile = bfile
|
||||||
self.project = project
|
self.project = project
|
||||||
@ -111,6 +112,7 @@ class Packer:
|
|||||||
self.noop = noop
|
self.noop = noop
|
||||||
self.compress = compress
|
self.compress = compress
|
||||||
self.relative_only = relative_only
|
self.relative_only = relative_only
|
||||||
|
self.keep_hierarchy = keep_hierarchy
|
||||||
self._aborted = threading.Event()
|
self._aborted = threading.Event()
|
||||||
self._abort_lock = threading.RLock()
|
self._abort_lock = threading.RLock()
|
||||||
self._abort_reason = ""
|
self._abort_reason = ""
|
||||||
@ -241,9 +243,12 @@ class Packer:
|
|||||||
# network shares mapped to Windows drive letters back to their UNC
|
# network shares mapped to Windows drive letters back to their UNC
|
||||||
# notation. Only resolving one but not the other (which can happen
|
# notation. Only resolving one but not the other (which can happen
|
||||||
# with the abosolute() call above) can cause errors.
|
# with the abosolute() call above) can cause errors.
|
||||||
bfile_pp = self._target_path / bfile_path.relative_to(
|
if self.keep_hierarchy:
|
||||||
bpathlib.make_absolute(self.project)
|
bfile_pp = self._target_path / bpathlib.strip_root(bfile_path)
|
||||||
)
|
else:
|
||||||
|
bfile_pp = self._target_path / bfile_path.relative_to(
|
||||||
|
bpathlib.make_absolute(self.project)
|
||||||
|
)
|
||||||
self._output_path = bfile_pp
|
self._output_path = bfile_pp
|
||||||
|
|
||||||
self._progress_cb.pack_start()
|
self._progress_cb.pack_start()
|
||||||
@ -335,7 +340,10 @@ 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_path / asset_path.relative_to(self.project)
|
if self.keep_hierarchy:
|
||||||
|
asset_pp = self._target_path / bpathlib.strip_root(asset_path)
|
||||||
|
else:
|
||||||
|
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):
|
||||||
@ -346,7 +354,10 @@ class Packer:
|
|||||||
assert isinstance(act, AssetAction)
|
assert isinstance(act, AssetAction)
|
||||||
|
|
||||||
relpath = bpathlib.strip_root(path)
|
relpath = bpathlib.strip_root(path)
|
||||||
act.new_path = pathlib.Path(self._target_path, "_outside_project", relpath)
|
if self.keep_hierarchy:
|
||||||
|
act.new_path = pathlib.Path(self._target_path, relpath)
|
||||||
|
else:
|
||||||
|
act.new_path = pathlib.Path(self._target_path, "_outside_project", relpath)
|
||||||
|
|
||||||
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.
|
||||||
|
|||||||
27
blender_asset_tracer/preferences.py
Normal file
27
blender_asset_tracer/preferences.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import bpy
|
||||||
|
from bpy.types import AddonPreferences
|
||||||
|
from bpy.props import BoolProperty, StringProperty
|
||||||
|
|
||||||
|
|
||||||
|
class BATPreferences(AddonPreferences):
|
||||||
|
bl_idname = "blender_asset_tracer"
|
||||||
|
|
||||||
|
use_env_root: BoolProperty(
|
||||||
|
name="Use Environment Variable for Root",
|
||||||
|
description="Read the project root from ZIP_ROOT or PROJECT_ROOT environment variables",
|
||||||
|
default=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
root_default: StringProperty(
|
||||||
|
name="Default Root",
|
||||||
|
description="Fallback project root when the environment variable is not set",
|
||||||
|
default="",
|
||||||
|
subtype="DIR_PATH",
|
||||||
|
)
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
layout.prop(self, "use_env_root")
|
||||||
|
layout.prop(self, "root_default")
|
||||||
|
|
||||||
|
|
||||||
146
tests/test_pack.py
Normal file → Executable file
146
tests/test_pack.py
Normal file → Executable file
@ -694,6 +694,152 @@ class ProgressTest(AbstractPackTest):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class KeepHierarchyPackTest(AbstractPackTest):
|
||||||
|
def hierarchy_path(self, filepath) -> Path:
|
||||||
|
"""Return the keep-hierarchy path for a file: target / strip_root(abs_path)."""
|
||||||
|
return Path(self.tpath, bpathlib.strip_root(filepath))
|
||||||
|
|
||||||
|
def test_strategise_keep_hierarchy_no_rewrite(self):
|
||||||
|
"""When all deps are in-project with relative paths, no rewriting is needed."""
|
||||||
|
infile = self.blendfiles / "doubly_linked.blend"
|
||||||
|
|
||||||
|
packer = pack.Packer(
|
||||||
|
infile, self.blendfiles, self.tpath, keep_hierarchy=True
|
||||||
|
)
|
||||||
|
packer.strategise()
|
||||||
|
|
||||||
|
packed_files = (
|
||||||
|
"doubly_linked.blend",
|
||||||
|
"linked_cube.blend",
|
||||||
|
"basic_file.blend",
|
||||||
|
"material_textures.blend",
|
||||||
|
"textures/Bricks/brick_dotted_04-bump.jpg",
|
||||||
|
"textures/Bricks/brick_dotted_04-color.jpg",
|
||||||
|
)
|
||||||
|
for pf in packed_files:
|
||||||
|
path = self.blendfiles / pf
|
||||||
|
act = packer._actions[path]
|
||||||
|
self.assertEqual(
|
||||||
|
pack.PathAction.KEEP_PATH, act.path_action, "for %s" % pf
|
||||||
|
)
|
||||||
|
# In keep_hierarchy mode, paths use strip_root(abs_path) instead of
|
||||||
|
# relative_to(project).
|
||||||
|
self.assertEqual(
|
||||||
|
self.hierarchy_path(path), act.new_path, "for %s" % pf
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual({}, self.rewrites(packer))
|
||||||
|
self.assertEqual(len(packed_files), len(packer._actions))
|
||||||
|
|
||||||
|
def test_strategise_keep_hierarchy_rewrite(self):
|
||||||
|
"""Deps outside the project go to target/strip_root(path), not _outside_project/."""
|
||||||
|
ppath = self.blendfiles / "subdir"
|
||||||
|
infile = ppath / "doubly_linked_up.blend"
|
||||||
|
|
||||||
|
packer = pack.Packer(infile, ppath, self.tpath, keep_hierarchy=True)
|
||||||
|
packer.strategise()
|
||||||
|
|
||||||
|
# The blendfile itself should be at target / strip_root(abs_path)
|
||||||
|
act = packer._actions[infile]
|
||||||
|
self.assertEqual(pack.PathAction.KEEP_PATH, act.path_action)
|
||||||
|
self.assertEqual(self.hierarchy_path(infile), act.new_path)
|
||||||
|
|
||||||
|
# External files should NOT be under _outside_project/
|
||||||
|
external_files = (
|
||||||
|
"linked_cube.blend",
|
||||||
|
"basic_file.blend",
|
||||||
|
"material_textures.blend",
|
||||||
|
"textures/Bricks/brick_dotted_04-bump.jpg",
|
||||||
|
"textures/Bricks/brick_dotted_04-color.jpg",
|
||||||
|
)
|
||||||
|
for fn in external_files:
|
||||||
|
path = self.blendfiles / fn
|
||||||
|
act = packer._actions[path]
|
||||||
|
self.assertEqual(
|
||||||
|
pack.PathAction.FIND_NEW_LOCATION, act.path_action, "for %s" % fn
|
||||||
|
)
|
||||||
|
# Should be at target / strip_root(abs_path), NOT target/_outside_project/...
|
||||||
|
expected = self.hierarchy_path(path)
|
||||||
|
self.assertEqual(
|
||||||
|
expected,
|
||||||
|
act.new_path,
|
||||||
|
f"\nEXPECT: {expected}\nACTUAL: {act.new_path}\nfor {fn}",
|
||||||
|
)
|
||||||
|
|
||||||
|
# There should be no _outside_project in any new_path
|
||||||
|
for path, action in packer._actions.items():
|
||||||
|
self.assertNotIn(
|
||||||
|
"_outside_project",
|
||||||
|
str(action.new_path),
|
||||||
|
f"_outside_project should not appear in keep_hierarchy mode for {path}",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_execute_keep_hierarchy(self):
|
||||||
|
"""Verify files are copied to correct hierarchy and paths are rewritten."""
|
||||||
|
ppath = self.blendfiles / "subdir"
|
||||||
|
infile = ppath / "doubly_linked_up.blend"
|
||||||
|
|
||||||
|
with pack.Packer(infile, ppath, self.tpath, keep_hierarchy=True) as packer:
|
||||||
|
packer.strategise()
|
||||||
|
packer.execute()
|
||||||
|
|
||||||
|
# The blendfile should be at its hierarchy position
|
||||||
|
packed_blend = self.hierarchy_path(infile)
|
||||||
|
self.assertTrue(packed_blend.exists(), "Blendfile should be in hierarchy")
|
||||||
|
|
||||||
|
# There should be NO _outside_project directory
|
||||||
|
self.assertFalse(
|
||||||
|
(self.tpath / "_outside_project").exists(),
|
||||||
|
"_outside_project should not exist in keep_hierarchy mode",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Dependencies should be at their hierarchy positions
|
||||||
|
for fn in ("linked_cube.blend", "basic_file.blend", "material_textures.blend"):
|
||||||
|
dep_path = self.hierarchy_path(self.blendfiles / fn)
|
||||||
|
self.assertTrue(dep_path.exists(), "%s should be in hierarchy" % fn)
|
||||||
|
|
||||||
|
# Verify paths were rewritten correctly in the packed blend file.
|
||||||
|
# The rewritten paths should be relative from the packed blend to
|
||||||
|
# the packed dependencies.
|
||||||
|
bfile = blendfile.open_cached(packed_blend, assert_cached=False)
|
||||||
|
libs = sorted(bfile.code_index[b"LI"])
|
||||||
|
|
||||||
|
# Since keep_hierarchy preserves relative positions, the relative paths
|
||||||
|
# from subdir/doubly_linked_up.blend to the parent blendfiles should
|
||||||
|
# be the same as the originals (//../linked_cube.blend etc.)
|
||||||
|
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_execute_keep_hierarchy_no_touch_origs(self):
|
||||||
|
"""Original files should not be modified."""
|
||||||
|
ppath = self.blendfiles / "subdir"
|
||||||
|
infile = ppath / "doubly_linked_up.blend"
|
||||||
|
|
||||||
|
with pack.Packer(infile, ppath, self.tpath, keep_hierarchy=True) as packer:
|
||||||
|
packer.strategise()
|
||||||
|
packer.execute()
|
||||||
|
|
||||||
|
# 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 test_keep_hierarchy_output_path(self):
|
||||||
|
"""output_path should use the full hierarchy path."""
|
||||||
|
infile = self.blendfiles / "basic_file.blend"
|
||||||
|
packer = pack.Packer(
|
||||||
|
infile, self.blendfiles, self.tpath, keep_hierarchy=True
|
||||||
|
)
|
||||||
|
packer.strategise()
|
||||||
|
|
||||||
|
self.assertEqual(self.hierarchy_path(infile), packer.output_path)
|
||||||
|
|
||||||
|
|
||||||
class AbortTest(AbstractPackTest):
|
class AbortTest(AbstractPackTest):
|
||||||
def test_abort_strategise(self):
|
def test_abort_strategise(self):
|
||||||
infile = self.blendfiles / "subdir/doubly_linked_up.blend"
|
infile = self.blendfiles / "subdir/doubly_linked_up.blend"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user