[Bf-extensions-cvs] [c3f2438] master: New blend-file addon

Campbell Barton noreply at git.blender.org
Fri Jan 29 08:52:24 CET 2016


Commit: c3f24386bb4ec7c91949a1f024bb1d6464cfd5a2
Author: Campbell Barton
Date:   Thu Jan 28 13:29:04 2016 +1100
Branches: master
https://developer.blender.org/rBAc3f24386bb4ec7c91949a1f024bb1d6464cfd5a2

New blend-file addon

Currently only expose packing functionality for packing
a file and all its deps into a ZIP (shared code with the cloud).
Can run directly or from the command line (without blender) via `blendfile_pack`.

Also adds subprocess_helper module which we may want to re-use elsewhere,
allowing to run external processes that don't lock blender and can be cancelled by pressing Esc.

===================================================================

A	io_blend_utils/__init__.py
A	io_blend_utils/bl_utils/pipe_non_blocking.py
A	io_blend_utils/bl_utils/subprocess_helper.py
A	io_blend_utils/blend/blendfile.py
A	io_blend_utils/blend/blendfile_path_walker.py
A	io_blend_utils/blendfile_pack.py
A	io_blend_utils/utils/system.py

===================================================================

diff --git a/io_blend_utils/__init__.py b/io_blend_utils/__init__.py
new file mode 100644
index 0000000..adbd2d0
--- /dev/null
+++ b/io_blend_utils/__init__.py
@@ -0,0 +1,110 @@
+# ***** 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",
+    "version": (0, 1),
+    "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",
+    }
+
+
+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"
+
+    # 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 os
+        import tempfile
+
+        self.temp_dir = tempfile.TemporaryDirectory()
+
+        filepath_blend = bpy.data.filepath
+
+        self.command = (
+            bpy.app.binary_path_python,
+            os.path.join(os.path.dirname(__file__), "blendfile_pack.py"),
+            # file to pack
+            "--input", filepath_blend,
+            # file to write
+            "--output", bpy.path.ensure_ext(self.filepath, ".zip"),
+            "--temp", self.temp_dir.name,
+            )
+
+    def process_post(self, returncode):
+        if self.temp_dir is not None:
+            try:
+                self.temp_dir.cleanup()
+            except:
+                import traceback
+                traceback.print_exc()
+
+
+def menu_func(self, context):
+    layout = self.layout
+    layout.separator()
+    layout.operator(ExportBlendPack.bl_idname)
+
+
+classes = (
+    ExportBlendPack,
+    )
+
+
+def register():
+    for cls in classes:
+        bpy.utils.register_class(cls)
+
+    bpy.types.INFO_MT_file_external_data.append(menu_func)
+
+
+def unregister():
+    for cls in classes:
+        bpy.utils.unregister_class(cls)
+
+    bpy.types.INFO_MT_file_external_data.remove(menu_func)
+
+
+if __name__ == "__main__":
+    register()
diff --git a/io_blend_utils/bl_utils/pipe_non_blocking.py b/io_blend_utils/bl_utils/pipe_non_blocking.py
new file mode 100644
index 0000000..ead0a73
--- /dev/null
+++ b/io_blend_utils/bl_utils/pipe_non_blocking.py
@@ -0,0 +1,100 @@
+# ##### 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
diff --git a/io_blend_utils/bl_utils/subprocess_helper.py b/io_blend_utils/bl_utils/subprocess_helper.py
new file mode 100644
index 0000000..2d289d3
--- /dev/null
+++ b/io_blend_utils/bl_utils/subprocess_helper.py
@@ -0,0 +1,172 @@
+# ##### 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.
+    """
+
+    @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

@@ Diff output truncated at 10240 characters. @@



More information about the Bf-extensions-cvs mailing list