# ##### 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. # # Copyright (C) 2014-2018 Blender Foundation # # ##### END GPL LICENSE BLOCK ##### # __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 from bpy.types import Operator from bpy_extras.io_utils import ExportHelper _HAS_BPY = True except ImportError: _HAS_BPY = False if _HAS_BPY: import zipfile import os import re import subprocess import tempfile from pathlib import Path, PurePath from blender_asset_tracer.pack import zipped from blender_asset_tracer import blendfile from . import preferences # 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) class ExportBatPack(Operator, ExportHelper): bl_idname = "export_bat.pack" bl_label = "Export to Archive using BAT" 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 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 = preferences.get_addon_prefs() 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 '' classes = ( ExportBatPack, BAT_OT_export_zip, ) def register(): preferences.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) preferences.unregister() bpy.types.TOPBAR_MT_file_external_data.remove(menu_func) if __name__ == "__main__": register()