Commit bdf03453 authored by meta-androcto's avatar meta-androcto
Browse files

io_blend_utils: remove: unsupported T63750

parent baa07430
Updating this module
--------------------
This module contains copies of files belonging to
[BAM](https://pypi.python.org/pypi/blender-bam/). Fixes should be
committed to BAM and then copied here, to keep versions in sync.
Bundling BAM with Blender
-------------------------
Blender is bundled with a version of [BAM](https://pypi.python.org/pypi/blender-bam/).
To update this version, first build a new [wheel](http://pythonwheels.com/) file in
BAM itself:
python3 setup.py bdist_wheel
Since we do not want to have binaries in the addons repository, unpack this wheel to Blender
by running:
python3 install_whl.py /path/to/blender-asset-manager/dist/blender_bam-xxx.whl
This script also updates `__init__.py` to update the version number of the extracted
wheel, and removes any pre-existing older versions of the BAM wheels.
The version number and `.whl` extension are maintained in the directory name on purpose.
This way it is clear that it is not a directory to import directly into Blender itself.
Furthermore, I (Sybren) hope that it helps to get changes made in the addons repository
back into the BAM repository.
Running bam-pack from the wheel
-------------------------------
This is the way that Blender runs bam-pack:
PYTHONPATH=./path/to/blender_bam-xxx.whl python3 -m bam.pack
# ***** BEGIN GPL LICENSE BLOCK *****
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# ***** END GPL LICENCE BLOCK *****
bl_info = {
"name": "Blend File Utils",
"author": "Campbell Barton and Sybren A. Stüvel",
"version": (1, 1, 7),
"blender": (2, 76, 0),
"location": "File > External Data > Blend Utils",
"description": "Utility for packing blend files",
"warning": "",
"wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/Import-Export/BlendFile_Utils",
"support": 'OFFICIAL',
"category": "Import-Export",
}
BAM_WHEEL_PATH = 'blender_bam-unpacked.whl'
import logging
import bpy
from bpy.types import Operator
from bpy_extras.io_utils import ExportHelper
from .bl_utils.subprocess_helper import SubprocessHelper
class ExportBlendPack(Operator, ExportHelper, SubprocessHelper):
"""Packs a blend file and all its dependencies into an archive for easy redistribution"""
bl_idname = "export_blend.pack"
bl_label = "Pack Blend to Archive"
log = logging.getLogger('%s.ExportBlendPack' % __name__)
# ExportHelper
filename_ext = ".zip"
# SubprocessHelper
report_interval = 0.25
temp_dir = None
@classmethod
def poll(cls, context):
return bpy.data.is_saved
def process_pre(self):
import tempfile
self.temp_dir = tempfile.TemporaryDirectory()
self.environ = {'PYTHONPATH': pythonpath()}
self.outfname = bpy.path.ensure_ext(self.filepath, ".zip")
self.command = (
bpy.app.binary_path_python,
'-m', 'bam.pack',
# file to pack
"--input", bpy.data.filepath,
# file to write
"--output", self.outfname,
"--temp", self.temp_dir.name,
)
if self.log.isEnabledFor(logging.INFO):
import shlex
cmd_to_log = ' '.join(shlex.quote(s) for s in self.command)
self.log.info('Executing %s', cmd_to_log)
def process_post(self, returncode):
if self.temp_dir is None:
return
try:
self.log.debug('Cleaning up temp dir %s', self.temp_dir)
self.temp_dir.cleanup()
except FileNotFoundError:
# This is expected, the directory was already removed by BAM.
pass
except Exception:
self.log.exception('Unable to clean up temp dir %s', self.temp_dir)
self.log.info('Written to %s', self.outfname)
def menu_func(self, context):
layout = self.layout
layout.separator()
layout.operator(ExportBlendPack.bl_idname)
classes = (
ExportBlendPack,
)
def pythonpath() -> str:
"""Returns the value of a PYTHONPATH environment variable needed to run BAM from its wheel file.
"""
import os
import pathlib
log = logging.getLogger('%s.pythonpath' % __name__)
# Find the wheel to run.
wheelpath = pathlib.Path(__file__).with_name(BAM_WHEEL_PATH)
if not wheelpath.exists():
raise EnvironmentError('Wheel %s does not exist!' % wheelpath)
log.info('Using wheel %s to run BAM-Pack', wheelpath)
# Update the PYTHONPATH to include that wheel.
existing_pypath = os.environ.get('PYTHONPATH', '')
if existing_pypath:
return os.pathsep.join((existing_pypath, str(wheelpath)))
return str(wheelpath)
def register():
for cls in classes:
bpy.utils.register_class(cls)
bpy.types.TOPBAR_MT_file_external_data.append(menu_func)
def unregister():
for cls in classes:
bpy.utils.unregister_class(cls)
bpy.types.TOPBAR_MT_file_external_data.remove(menu_func)
if __name__ == "__main__":
register()
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
"""
Example use:
p = subprocess.Popen(
command,
stdout=subprocess.PIPE,
)
pipe_non_blocking_set(p.stdout.fileno())
try:
data = os.read(p.stdout.fileno(), 1)
except PortableBlockingIOError as ex:
if not pipe_non_blocking_is_error_blocking(ex):
raise ex
"""
__all__ = (
"pipe_non_blocking_set",
"pipe_non_blocking_is_error_blocking",
"PortableBlockingIOError",
)
import os
if os.name == "nt":
# MS-Windows Version
def pipe_non_blocking_set(fd):
# Constant could define globally but avoid polluting the name-space
# thanks to: http://stackoverflow.com/questions/34504970
import msvcrt
from ctypes import windll, byref, wintypes, WinError, POINTER
from ctypes.wintypes import HANDLE, DWORD, BOOL
LPDWORD = POINTER(DWORD)
PIPE_NOWAIT = wintypes.DWORD(0x00000001)
def pipe_no_wait(pipefd):
SetNamedPipeHandleState = windll.kernel32.SetNamedPipeHandleState
SetNamedPipeHandleState.argtypes = [HANDLE, LPDWORD, LPDWORD, LPDWORD]
SetNamedPipeHandleState.restype = BOOL
h = msvcrt.get_osfhandle(pipefd)
res = windll.kernel32.SetNamedPipeHandleState(h, byref(PIPE_NOWAIT), None, None)
if res == 0:
print(WinError())
return False
return True
return pipe_no_wait(fd)
def pipe_non_blocking_is_error_blocking(ex):
if not isinstance(ex, PortableBlockingIOError):
return False
from ctypes import GetLastError
ERROR_NO_DATA = 232
return (GetLastError() == ERROR_NO_DATA)
PortableBlockingIOError = OSError
else:
# Posix Version
def pipe_non_blocking_set(fd):
import fcntl
fl = fcntl.fcntl(fd, fcntl.F_GETFL)
fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
return True
# only for compatibility with 'nt' version.
def pipe_non_blocking_is_error_blocking(ex):
if not isinstance(ex, PortableBlockingIOError):
return False
return True
PortableBlockingIOError = BlockingIOError
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
"""
Defines an operator mix-in to use for non-blocking command line access.
"""
class SubprocessHelper:
"""
Mix-in class for operators to run commands in a non-blocking way.
This uses a modal operator to manage an external process.
Subclass must define:
``command``:
List of arguments to pass to subprocess.Popen
report_interval: Time in seconds between updating reports.
``process_pre()``:
Callback that runs before the process executes.
``process_post(returncode)``:
Callback that runs when the process has ende.
returncode is -1 if the process was terminated.
Subclass may define:
``environment``:
Dict of environment variables exposed to the subprocess.
Contrary to the subprocess.Popen(env=...) parameter, this
dict is and not used to replace the existing environment
entirely, but is just used to update it.
"""
environ = {}
command = ()
@staticmethod
def _non_blocking_readlines(f, chunk=64):
"""
Iterate over lines, yielding b'' when nothings left
or when new data is not yet available.
"""
import os
from .pipe_non_blocking import (
pipe_non_blocking_set,
pipe_non_blocking_is_error_blocking,
PortableBlockingIOError,
)
fd = f.fileno()
pipe_non_blocking_set(fd)
blocks = []
while True:
try:
data = os.read(fd, chunk)
if not data:
# case were reading finishes with no trailing newline
yield b''.join(blocks)
blocks.clear()
except PortableBlockingIOError as ex:
if not pipe_non_blocking_is_error_blocking(ex):
raise ex
yield b''
continue
while True:
n = data.find(b'\n')
if n == -1:
break
yield b''.join(blocks) + data[:n + 1]
data = data[n + 1:]
blocks.clear()
blocks.append(data)
def _report_output(self):
stdout_line_iter, stderr_line_iter = self._buffer_iter
for line_iter, report_type in (
(stdout_line_iter, {'INFO'}),
(stderr_line_iter, {'WARNING'})
):
while True:
line = next(line_iter).rstrip() # rstrip all, to include \r on windows
if not line:
break
self.report(report_type, line.decode(encoding='utf-8', errors='surrogateescape'))
def _wm_enter(self, context):
wm = context.window_manager
window = context.window
self._timer = wm.event_timer_add(self.report_interval, window)
window.cursor_set('WAIT')
def _wm_exit(self, context):
wm = context.window_manager
window = context.window
wm.event_timer_remove(self._timer)
window.cursor_set('DEFAULT')
def process_pre(self):
pass
def process_post(self, returncode):
pass
def modal(self, context, event):
wm = context.window_manager
p = self._process
if event.type == 'ESC':
self.cancel(context)
self.report({'INFO'}, "Operation aborted by user")
return {'CANCELLED'}
elif event.type == 'TIMER':
if p.poll() is not None:
self._report_output()
self._wm_exit(context)
self.process_post(p.returncode)
return {'FINISHED'}
self._report_output()
return {'PASS_THROUGH'}
def execute(self, context):
import subprocess
import os
import copy
self.process_pre()
env = copy.deepcopy(os.environ)
env.update(self.environ)
try:
p = subprocess.Popen(
self.command,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=env,
)
except FileNotFoundError as ex:
# Command not found
self.report({'ERROR'}, str(ex))
return {'CANCELLED'}
self._process = p
self._buffer_iter = (
iter(self._non_blocking_readlines(p.stdout)),
iter(self._non_blocking_readlines(p.stderr)),
)
wm = context.window_manager
wm.modal_handler_add(self)
self._wm_enter(context)
return {'RUNNING_MODAL'}
def cancel(self, context):
self._wm_exit(context)
self._process.kill()
self.process_post(-1)
This diff is collapsed.
This diff is collapsed.
# -*- coding: utf-8 -*-
__version__ = '1.1.8'
if __name__ == '__main__':
from .cli import main
main()
"""Main module for running python -m bam.
Doesn't do much, except for printing general usage information.
"""
print("The 'bam' module cannot be run directly. The following subcommand is available:")
print()
print("python -m bam.pack")
#!/usr/bin/env python3
# ***** BEGIN GPL LICENSE BLOCK *****
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# ***** END GPL LICENCE BLOCK *****
"""
A simply utility to copy blend files and their deps to a new location.
Similar to packing, but don't attempt any path remapping.
"""
from bam.blend import blendfile_path_walker
TIMEIT = False
# ------------------
# Ensure module path
import os
import sys
path = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "modules"))
if path not in sys.path:
sys.path.append(path)
del os, sys, path
# --------
def copy_paths(
paths,
output,
base,
# load every libs dep, not just used deps.
all_deps=False,
# yield reports
report=None,
# Filename filter, allow to exclude files from the pack,
# function takes a string returns True if the files should be included.
filename_filter=None,
):
import os
import shutil
from bam.utils.system import colorize, is_subdir
path_copy_files = set(paths)
# Avoid walking over same libs many times
lib_visit = {}
yield report("Reading %d blend file(s)\n" % len(paths))
for blendfile_src in paths:
yield report(" %s: %r\n" % (colorize("blend", color='blue'), blendfile_src))
for fp, (rootdir, fp_blend_basename) in blendfile_path_walker.FilePath.visit_from_blend(
blendfile_src,
readonly=True,
recursive=True,
recursive_all=all_deps,
lib_visit=lib_visit,
):
f_abs = os.path.normpath(fp.filepath_absolute)
path_copy_files.add(f_abs)
# Source -> Dest Map
path_src_dst_map = {}
for path_src in sorted(path_copy_files):
if filename_filter and not filename_filter(path_src):
yield report(" %s: %r\n" % (colorize("exclude", color='yellow'), path_src))
continue
if not os.path.exists(path_src):
yield report(" %s: %r\n" % (colorize("missing path", color='red'), path_src))
continue
if not is_subdir(path_src, base):
yield report(" %s: %r\n" % (colorize("external path ignored", color='red'), path_src))
continue
path_rel = os.path.relpath(path_src, base)
path_dst = os.path.join(output, path_rel)
path_src_dst_map[path_src] = path_dst