Add sequence pack operator and multi-blend-file CLI support
- Add --sequence CLI flag to pack multiple blend files together with shared dependency deduplication - Add BAT_OT_sequence_pack Blender operator: scan a sequence folder for latest published blend files, review with checkboxes, pack to ZIP - Add studio template system (Autour de Minuit, La Cabane Productions) with configurable folder conventions and task type selection - Add BlenderProgressCallback for cursor progress indicator during pack - Add ZIP write start/finish logging - Deduplicate blend file paths in CLI when positional and --sequence overlap Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -96,7 +96,7 @@ class Packer:
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
bfile: pathlib.Path,
|
||||
bfile: typing.Union[pathlib.Path, typing.List[pathlib.Path]],
|
||||
project: pathlib.Path,
|
||||
target: str,
|
||||
*,
|
||||
@@ -105,7 +105,11 @@ class Packer:
|
||||
relative_only=False,
|
||||
keep_hierarchy=False,
|
||||
) -> None:
|
||||
self.blendfile = bfile
|
||||
if isinstance(bfile, list):
|
||||
self.blendfiles = bfile
|
||||
else:
|
||||
self.blendfiles = [bfile]
|
||||
self.blendfile = self.blendfiles[0] # backward compat
|
||||
self.project = project
|
||||
self.target = target
|
||||
self._target_path = self._make_target_path(target)
|
||||
@@ -136,6 +140,7 @@ class Packer:
|
||||
self.missing_files = set() # type: typing.Set[pathlib.Path]
|
||||
self._new_location_paths = set() # type: typing.Set[pathlib.Path]
|
||||
self._output_path = None # type: typing.Optional[pathlib.PurePath]
|
||||
self._output_paths = [] # type: typing.List[pathlib.PurePath]
|
||||
|
||||
# Filled by execute()
|
||||
self._file_transferer = None # type: typing.Optional[transfer.FileTransferer]
|
||||
@@ -172,6 +177,11 @@ class Packer:
|
||||
assert self._output_path is not None
|
||||
return self._output_path
|
||||
|
||||
@property
|
||||
def output_paths(self) -> typing.List[pathlib.PurePath]:
|
||||
"""The paths of all packed blend files in the target directory."""
|
||||
return list(self._output_paths)
|
||||
|
||||
@property
|
||||
def progress_cb(self) -> progress.Callback:
|
||||
return self._progress_cb
|
||||
@@ -235,46 +245,47 @@ class Packer:
|
||||
in the execute() function.
|
||||
"""
|
||||
|
||||
# The blendfile that we pack is generally not its own dependency, so
|
||||
# we have to explicitly add it to the _packed_paths.
|
||||
bfile_path = bpathlib.make_absolute(self.blendfile)
|
||||
|
||||
# Both paths have to be resolved first, because this also translates
|
||||
# network shares mapped to Windows drive letters back to their UNC
|
||||
# notation. Only resolving one but not the other (which can happen
|
||||
# with the abosolute() call above) can cause errors.
|
||||
if self.keep_hierarchy:
|
||||
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._progress_cb.pack_start()
|
||||
|
||||
act = self._actions[bfile_path]
|
||||
act.path_action = PathAction.KEEP_PATH
|
||||
act.new_path = bfile_pp
|
||||
|
||||
self._check_aborted()
|
||||
self._new_location_paths = set()
|
||||
for usage in trace.deps(self.blendfile, self._progress_cb):
|
||||
self._check_aborted()
|
||||
asset_path = usage.abspath
|
||||
if any(asset_path.match(glob) for glob in self._exclude_globs):
|
||||
log.info("Excluding file: %s", asset_path)
|
||||
continue
|
||||
self._output_paths = []
|
||||
|
||||
if self.relative_only and not usage.asset_path.startswith(b"//"):
|
||||
log.info("Skipping absolute path: %s", usage.asset_path)
|
||||
continue
|
||||
for bf in self.blendfiles:
|
||||
bfile_path = bpathlib.make_absolute(bf)
|
||||
|
||||
if usage.is_sequence:
|
||||
self._visit_sequence(asset_path, usage)
|
||||
# Both paths have to be resolved first, because this also translates
|
||||
# network shares mapped to Windows drive letters back to their UNC
|
||||
# notation. Only resolving one but not the other (which can happen
|
||||
# with the abosolute() call above) can cause errors.
|
||||
if self.keep_hierarchy:
|
||||
bfile_pp = self._target_path / bpathlib.strip_root(bfile_path)
|
||||
else:
|
||||
self._visit_asset(asset_path, usage)
|
||||
bfile_pp = self._target_path / bfile_path.relative_to(
|
||||
bpathlib.make_absolute(self.project)
|
||||
)
|
||||
self._output_paths.append(bfile_pp)
|
||||
|
||||
act = self._actions[bfile_path]
|
||||
act.path_action = PathAction.KEEP_PATH
|
||||
act.new_path = bfile_pp
|
||||
|
||||
self._check_aborted()
|
||||
for usage in trace.deps(bf, self._progress_cb):
|
||||
self._check_aborted()
|
||||
asset_path = usage.abspath
|
||||
if any(asset_path.match(glob) for glob in self._exclude_globs):
|
||||
log.info("Excluding file: %s", asset_path)
|
||||
continue
|
||||
|
||||
if self.relative_only and not usage.asset_path.startswith(b"//"):
|
||||
log.info("Skipping absolute path: %s", usage.asset_path)
|
||||
continue
|
||||
|
||||
if usage.is_sequence:
|
||||
self._visit_sequence(asset_path, usage)
|
||||
else:
|
||||
self._visit_asset(asset_path, usage)
|
||||
|
||||
self._output_path = self._output_paths[0] # backward compat
|
||||
self._find_new_paths()
|
||||
self._group_rewrites()
|
||||
|
||||
@@ -644,11 +655,19 @@ class Packer:
|
||||
log.debug("Writing info to %s", infopath)
|
||||
with infopath.open("wt", encoding="utf8") as infofile:
|
||||
print("This is a Blender Asset Tracer pack.", file=infofile)
|
||||
print("Start by opening the following blend file:", file=infofile)
|
||||
print(
|
||||
" %s" % self._output_path.relative_to(self._target_path).as_posix(),
|
||||
file=infofile,
|
||||
)
|
||||
if len(self._output_paths) > 1:
|
||||
print("This pack contains the following blend files:", file=infofile)
|
||||
for op in self._output_paths:
|
||||
print(
|
||||
" %s" % op.relative_to(self._target_path).as_posix(),
|
||||
file=infofile,
|
||||
)
|
||||
else:
|
||||
print("Start by opening the following blend file:", file=infofile)
|
||||
print(
|
||||
" %s" % self._output_path.relative_to(self._target_path).as_posix(),
|
||||
file=infofile,
|
||||
)
|
||||
|
||||
self._file_transferer.queue_move(infopath, self._target_path / infoname)
|
||||
|
||||
|
||||
@@ -58,6 +58,7 @@ class ZipTransferrer(transfer.FileTransferer):
|
||||
|
||||
zippath = self.zippath.absolute()
|
||||
|
||||
log.info("Writing ZIP file to %s", zippath)
|
||||
with zipfile.ZipFile(str(zippath), "w") as outzip:
|
||||
for src, dst, act in self.iter_queue():
|
||||
assert src.is_absolute(), "expecting only absolute paths, not %r" % src
|
||||
@@ -89,3 +90,4 @@ class ZipTransferrer(transfer.FileTransferer):
|
||||
# be reported there.
|
||||
self.queue.put((src, dst, act))
|
||||
return
|
||||
log.info("Finished writing ZIP file: %s", zippath)
|
||||
|
||||
Reference in New Issue
Block a user