import os import sys import subprocess import tempfile import zipfile from pathlib import Path import bpy from bpy.types import Operator from bpy_extras.io_utils import ExportHelper from blender_asset_tracer.pack import zipped class ExportBatPack(Operator, ExportHelper): bl_idname = "export_bat.pack" bl_label = "BAT - Zip pack (flat)" 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 - Zip 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 = bpy.context.preferences.addons["blender_asset_tracer"].preferences 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 ""