2018-03-15 12:36:05 +01:00
|
|
|
"""BAT🦇 packing interface for Flamenco."""
|
|
|
|
|
|
|
|
import asyncio
|
|
|
|
import logging
|
2018-12-07 12:25:48 +01:00
|
|
|
import pathlib
|
|
|
|
import re
|
2018-03-16 12:15:53 +01:00
|
|
|
import threading
|
2018-03-15 12:36:05 +01:00
|
|
|
import typing
|
|
|
|
|
2018-03-20 16:52:50 +01:00
|
|
|
import bpy
|
2018-03-15 12:36:05 +01:00
|
|
|
from blender_asset_tracer import pack
|
2019-02-28 12:53:29 +01:00
|
|
|
from blender_asset_tracer.pack import progress, transfer, shaman
|
2018-03-15 12:36:05 +01:00
|
|
|
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
2018-03-16 12:15:53 +01:00
|
|
|
_running_packer = None # type: pack.Packer
|
|
|
|
_packer_lock = threading.RLock()
|
2018-03-20 16:52:50 +01:00
|
|
|
|
|
|
|
# For using in other parts of the add-on, so only this file imports BAT.
|
2018-03-16 12:41:09 +01:00
|
|
|
Aborted = pack.Aborted
|
2018-03-20 16:52:50 +01:00
|
|
|
FileTransferError = transfer.FileTransferError
|
2019-02-28 12:53:29 +01:00
|
|
|
ShamanPacker = shaman.ShamanPacker
|
|
|
|
parse_shaman_endpoint = shaman.parse_endpoint
|
2018-03-16 12:15:53 +01:00
|
|
|
|
2018-03-15 12:36:05 +01:00
|
|
|
|
2018-03-15 18:01:04 +01:00
|
|
|
class BatProgress(progress.Callback):
|
|
|
|
"""Report progress of BAT Packing to the UI.
|
|
|
|
|
|
|
|
Uses asyncio.run_coroutine_threadsafe() to ensure the UI is only updated
|
|
|
|
from the main thread. This is required since we run the BAT Pack in a
|
|
|
|
background thread.
|
|
|
|
"""
|
|
|
|
|
2018-03-21 15:45:18 +01:00
|
|
|
def __init__(self) -> None:
|
2018-03-15 18:01:04 +01:00
|
|
|
super().__init__()
|
|
|
|
self.loop = asyncio.get_event_loop()
|
|
|
|
|
2018-03-21 15:45:18 +01:00
|
|
|
def _set_attr(self, attr: str, value):
|
2018-03-16 12:15:53 +01:00
|
|
|
|
2018-03-21 15:45:18 +01:00
|
|
|
async def do_it():
|
|
|
|
setattr(bpy.context.window_manager, attr, value)
|
2018-03-16 12:15:53 +01:00
|
|
|
|
2018-03-21 15:45:18 +01:00
|
|
|
asyncio.run_coroutine_threadsafe(do_it(), loop=self.loop)
|
2018-03-15 18:01:04 +01:00
|
|
|
|
2018-03-21 15:45:18 +01:00
|
|
|
def _txt(self, msg: str):
|
|
|
|
"""Set a text in a thread-safe way."""
|
|
|
|
self._set_attr('flamenco_status_txt', msg)
|
2018-03-16 12:15:53 +01:00
|
|
|
|
2018-03-21 15:45:18 +01:00
|
|
|
def _status(self, status: str):
|
|
|
|
"""Set the flamenco_status property in a thread-safe way."""
|
|
|
|
self._set_attr('flamenco_status', status)
|
2018-03-16 12:15:53 +01:00
|
|
|
|
2018-03-21 15:45:18 +01:00
|
|
|
def _progress(self, progress: int):
|
|
|
|
"""Set the flamenco_progress property in a thread-safe way."""
|
|
|
|
self._set_attr('flamenco_progress', progress)
|
2018-03-16 12:15:53 +01:00
|
|
|
|
2018-03-15 18:01:04 +01:00
|
|
|
def pack_start(self) -> None:
|
2018-03-21 15:45:18 +01:00
|
|
|
self._txt('Starting BAT Pack operation')
|
2018-03-15 18:01:04 +01:00
|
|
|
|
|
|
|
def pack_done(self,
|
|
|
|
output_blendfile: pathlib.Path,
|
|
|
|
missing_files: typing.Set[pathlib.Path]) -> None:
|
|
|
|
if missing_files:
|
2018-03-21 15:45:18 +01:00
|
|
|
self._txt('There were %d missing files' % len(missing_files))
|
2018-03-15 18:01:04 +01:00
|
|
|
else:
|
2018-03-21 15:45:18 +01:00
|
|
|
self._txt('Pack of %s done' % output_blendfile.name)
|
2018-03-15 18:01:04 +01:00
|
|
|
|
2019-02-28 12:53:29 +01:00
|
|
|
def pack_aborted(self, reason: str):
|
|
|
|
self._txt('Aborted: %s' % reason)
|
2018-03-21 15:45:18 +01:00
|
|
|
self._status('ABORTED')
|
2018-03-16 12:15:53 +01:00
|
|
|
|
2018-03-15 18:01:04 +01:00
|
|
|
def trace_blendfile(self, filename: pathlib.Path) -> None:
|
|
|
|
"""Called for every blendfile opened when tracing dependencies."""
|
2018-03-21 15:45:18 +01:00
|
|
|
self._txt('Inspecting %s' % filename.name)
|
2018-03-15 18:01:04 +01:00
|
|
|
|
|
|
|
def trace_asset(self, filename: pathlib.Path) -> None:
|
|
|
|
if filename.stem == '.blend':
|
|
|
|
return
|
2018-03-21 15:45:18 +01:00
|
|
|
self._txt('Found asset %s' % filename.name)
|
2018-03-15 18:01:04 +01:00
|
|
|
|
|
|
|
def rewrite_blendfile(self, orig_filename: pathlib.Path) -> None:
|
2018-03-21 15:45:18 +01:00
|
|
|
self._txt('Rewriting %s' % orig_filename.name)
|
2018-03-15 18:01:04 +01:00
|
|
|
|
|
|
|
def transfer_file(self, src: pathlib.Path, dst: pathlib.Path) -> None:
|
2018-03-21 15:45:18 +01:00
|
|
|
self._txt('Transferring %s' % src.name)
|
2018-03-15 18:01:04 +01:00
|
|
|
|
|
|
|
def transfer_file_skipped(self, src: pathlib.Path, dst: pathlib.Path) -> None:
|
2018-03-21 15:45:18 +01:00
|
|
|
self._txt('Skipped %s' % src.name)
|
2018-03-15 18:01:04 +01:00
|
|
|
|
|
|
|
def transfer_progress(self, total_bytes: int, transferred_bytes: int) -> None:
|
2018-03-21 15:45:18 +01:00
|
|
|
self._progress(round(100 * transferred_bytes / total_bytes))
|
2018-03-15 18:01:04 +01:00
|
|
|
|
|
|
|
def missing_file(self, filename: pathlib.Path) -> None:
|
|
|
|
# TODO(Sybren): report missing files in a nice way
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2018-03-16 12:15:53 +01:00
|
|
|
async def copy(context,
|
|
|
|
base_blendfile: pathlib.Path,
|
|
|
|
project: pathlib.Path,
|
2019-02-28 12:52:51 +01:00
|
|
|
target: str,
|
2018-12-06 15:46:54 +01:00
|
|
|
exclusion_filter: str,
|
|
|
|
*,
|
2019-02-28 12:52:51 +01:00
|
|
|
relative_only: bool,
|
|
|
|
packer_class=pack.Packer,
|
|
|
|
**packer_args) \
|
|
|
|
-> typing.Tuple[pathlib.Path, typing.Set[pathlib.Path]]:
|
2018-03-15 12:36:05 +01:00
|
|
|
"""Use BAT🦇 to copy the given file and dependencies to the target location.
|
|
|
|
|
|
|
|
:raises: FileTransferError if a file couldn't be transferred.
|
|
|
|
:returns: the path of the packed blend file, and a set of missing sources.
|
|
|
|
"""
|
2018-03-16 12:15:53 +01:00
|
|
|
global _running_packer
|
2018-03-15 12:36:05 +01:00
|
|
|
|
|
|
|
loop = asyncio.get_event_loop()
|
2018-03-15 18:01:04 +01:00
|
|
|
wm = bpy.context.window_manager
|
|
|
|
|
2019-02-28 12:52:51 +01:00
|
|
|
packer = packer_class(base_blendfile, project, target,
|
|
|
|
compress=True, relative_only=relative_only, **packer_args)
|
|
|
|
with packer:
|
2018-03-16 12:15:53 +01:00
|
|
|
with _packer_lock:
|
|
|
|
if exclusion_filter:
|
2018-12-07 12:25:48 +01:00
|
|
|
# There was a mistake in an older version of the property tooltip,
|
|
|
|
# showing semicolon-separated instead of space-separated. We now
|
|
|
|
# just handle both.
|
|
|
|
filter_parts = re.split('[ ;]+', exclusion_filter.strip(' ;'))
|
|
|
|
packer.exclude(*filter_parts)
|
2018-03-15 18:01:04 +01:00
|
|
|
|
2018-03-21 15:45:18 +01:00
|
|
|
packer.progress_cb = BatProgress()
|
2018-03-16 12:15:53 +01:00
|
|
|
_running_packer = packer
|
2018-03-15 18:01:04 +01:00
|
|
|
|
2018-03-15 12:36:05 +01:00
|
|
|
log.debug('awaiting strategise')
|
2018-03-15 18:01:04 +01:00
|
|
|
wm.flamenco_status = 'INVESTIGATING'
|
2018-03-15 12:36:05 +01:00
|
|
|
await loop.run_in_executor(None, packer.strategise)
|
2018-03-15 18:01:04 +01:00
|
|
|
|
2018-03-15 12:36:05 +01:00
|
|
|
log.debug('awaiting execute')
|
2018-03-15 18:01:04 +01:00
|
|
|
wm.flamenco_status = 'TRANSFERRING'
|
2018-03-15 12:36:05 +01:00
|
|
|
await loop.run_in_executor(None, packer.execute)
|
2018-03-15 18:01:04 +01:00
|
|
|
|
2018-03-15 12:36:05 +01:00
|
|
|
log.debug('done')
|
2018-03-15 18:01:04 +01:00
|
|
|
wm.flamenco_status = 'DONE'
|
2018-03-15 12:36:05 +01:00
|
|
|
|
2018-03-16 12:15:53 +01:00
|
|
|
with _packer_lock:
|
|
|
|
_running_packer = None
|
|
|
|
|
2018-03-15 12:36:05 +01:00
|
|
|
return packer.output_path, packer.missing_files
|
2018-03-16 12:15:53 +01:00
|
|
|
|
|
|
|
|
|
|
|
def abort() -> None:
|
|
|
|
"""Abort a running copy() call.
|
|
|
|
|
|
|
|
No-op when there is no running copy(). Can be called from any thread.
|
|
|
|
"""
|
|
|
|
|
|
|
|
with _packer_lock:
|
|
|
|
if _running_packer is None:
|
|
|
|
log.debug('No running packer, ignoring call to bat_abort()')
|
|
|
|
return
|
|
|
|
log.info('Aborting running packer')
|
|
|
|
_running_packer.abort()
|