280 lines
8.7 KiB
Python
280 lines
8.7 KiB
Python
|
# ##### 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 #####
|
||
|
|
||
|
# <pep8 compliant>
|
||
|
|
||
|
__version__ = '1.3'
|
||
|
|
||
|
bl_info = {
|
||
|
"name": "Blender Asset Tracer",
|
||
|
"author": "Campbell Barton, Sybren A. Stüvel, Loïc Charrière and Clément Ducarteron",
|
||
|
"version": (1, 3, 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",
|
||
|
}
|
||
|
|
||
|
import bpy
|
||
|
from bpy.types import Operator
|
||
|
from bpy_extras.io_utils import ExportHelper
|
||
|
import zipfile
|
||
|
from blender_asset_tracer.pack import zipped
|
||
|
from pathlib import Path, PurePath
|
||
|
|
||
|
import os
|
||
|
import re
|
||
|
import sys
|
||
|
import subprocess
|
||
|
import tempfile
|
||
|
from blender_asset_tracer.trace import deps
|
||
|
|
||
|
|
||
|
class ExportBatPack(Operator, ExportHelper):
|
||
|
bl_idname = "export_bat.pack"
|
||
|
bl_label = "Export to Archive using BAT"
|
||
|
|
||
|
# ExportHelper
|
||
|
filename_ext = ".zip"
|
||
|
|
||
|
@classmethod
|
||
|
def poll(cls, context):
|
||
|
return bpy.data.is_saved
|
||
|
|
||
|
|
||
|
def execute(self, context):
|
||
|
import os
|
||
|
outfname = bpy.path.ensure_ext(self.filepath, ".zip")
|
||
|
scn = bpy.context.scene
|
||
|
|
||
|
print(f'outfname {outfname}')
|
||
|
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'}
|
||
|
|
||
|
|
||
|
class ADM_OT_export_zip(Operator, ExportHelper):
|
||
|
"""Export current blendfile as .ZIP"""
|
||
|
bl_label = "Export File to .ZIP"
|
||
|
bl_idname = "adm.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 !!!',
|
||
|
)
|
||
|
|
||
|
@classmethod
|
||
|
def poll(cls, context):
|
||
|
return bpy.data.is_saved
|
||
|
|
||
|
def execute(self, context):
|
||
|
|
||
|
root_dir = self.root_dir
|
||
|
print('root_dir: ', root_dir)
|
||
|
|
||
|
def open_folder(folderpath):
|
||
|
"""
|
||
|
open the folder at the path given
|
||
|
with cmd relative to user's OS
|
||
|
"""
|
||
|
|
||
|
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('//')
|
||
|
|
||
|
if os.path.isfile(folderpath): # When pointing to a file
|
||
|
select = False
|
||
|
if my_os.startswith('win'):
|
||
|
# Keep same path but add "/select" the file (windows cmd option)
|
||
|
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:
|
||
|
# Use directory of the file
|
||
|
folderpath = os.path.dirname(folderpath)
|
||
|
|
||
|
folderpath = os.path.normpath(folderpath)
|
||
|
fullcmd = cmd.split() + [folderpath]
|
||
|
# print('use opening command :', fullcmd)
|
||
|
subprocess.Popen(fullcmd)
|
||
|
|
||
|
|
||
|
def zip_with_structure(zip, filelist, root=None, compressed=True):
|
||
|
'''
|
||
|
Zip passed filelist into a zip with root path as toplevel tree
|
||
|
If root is not passed, the shortest path in filelist becomes the root
|
||
|
|
||
|
:zip: output fullpath of the created zip
|
||
|
:filelist: list of filepaht as string or Path object (converted anyway)
|
||
|
:root: top level of the created hierarchy (not included), file that are not inside root are discarded
|
||
|
:compressed: Decide if zip is compressed or not
|
||
|
'''
|
||
|
|
||
|
filelist = [Path(f) for f in filelist] # ensure pathlib
|
||
|
if not filelist:
|
||
|
return
|
||
|
if not root:
|
||
|
# autodetect the path thats is closest to root
|
||
|
#root = sorted(filelist, key=lambda f: f.as_posix().count('/'))[0].parent
|
||
|
filelist_abs = [str(fl) for fl in filelist]
|
||
|
root = Path(os.path.commonpath(filelist_abs))
|
||
|
#print('root: ', root)
|
||
|
else:
|
||
|
root = Path(root)
|
||
|
|
||
|
compress_type = zipfile.ZIP_DEFLATED if compressed else zipfile.ZIP_STORED
|
||
|
with zipfile.ZipFile(zip, 'w',compress_type) as zipObj:
|
||
|
for f in filelist:
|
||
|
#print('f: ', f, type(f))
|
||
|
if not f.exists():
|
||
|
print(f'Not exists: {f.name}')
|
||
|
continue
|
||
|
if str(root) not in str(f):
|
||
|
print(f'{f} is out of root {root}')
|
||
|
continue
|
||
|
|
||
|
##
|
||
|
arcname = f.as_posix().replace(root.as_posix(), '').lstrip('/')
|
||
|
print(f'adding: {arcname}')
|
||
|
zipObj.write(f, arcname)
|
||
|
|
||
|
return zip, root
|
||
|
|
||
|
|
||
|
links = []
|
||
|
|
||
|
current_file = Path(bpy.data.filepath)
|
||
|
links.append(str(current_file))
|
||
|
file_link = list(deps(current_file))
|
||
|
for l in file_link:
|
||
|
if Path(l.abspath).exists() == False:
|
||
|
continue
|
||
|
|
||
|
links.append(l.abspath)
|
||
|
if l.is_sequence:
|
||
|
split_path = PurePath(l.abspath).parts
|
||
|
file_dir = os.path.join(*split_path[:-1])
|
||
|
file_name = split_path[-1]
|
||
|
|
||
|
pattern = '[0-9]+\.[a-zA-Z]+$'
|
||
|
file_name = re.sub(pattern, '', file_name)
|
||
|
|
||
|
for im in os.listdir(Path(f"{file_dir}/")):
|
||
|
if im.startswith(file_name) and re.search(pattern, im):
|
||
|
links.append(os.path.join(file_dir, im))
|
||
|
|
||
|
links = list(set(links))
|
||
|
|
||
|
#output_name = current_file.name
|
||
|
output = self.filepath
|
||
|
#print('output: ', output)
|
||
|
|
||
|
root_dir = zip_with_structure(output, links, root_dir)
|
||
|
root_dir = str(root_dir[1])
|
||
|
|
||
|
log_output = Path(tempfile.gettempdir(),'README.txt')
|
||
|
with open(log_output, 'w') as log:
|
||
|
log.write("File is located here:")
|
||
|
log.write(f"\n - /{str(current_file).replace(root_dir,'')}")
|
||
|
|
||
|
with zipfile.ZipFile(output, 'a') as zipObj:
|
||
|
zipObj.write(log_output, log_output.name)
|
||
|
|
||
|
### MEME CHOSE QUE
|
||
|
#zipObj = zipfile.ZipFile(output, 'a')
|
||
|
#zipObj.write(log_output, log_output.name)
|
||
|
#zipObj.close()
|
||
|
|
||
|
open_folder(Path(output).parent)
|
||
|
|
||
|
return {'FINISHED'}
|
||
|
|
||
|
|
||
|
def menu_func(self, context):
|
||
|
layout = self.layout
|
||
|
layout.separator()
|
||
|
layout.operator(ExportBatPack.bl_idname)
|
||
|
filepath = layout.operator(ADM_OT_export_zip.bl_idname)
|
||
|
root_dir_env = os.getenv('ZIP_ROOT')
|
||
|
filepath.root_dir = '' if root_dir_env == None else root_dir_env #os.getenv('PROJECT_STORE')
|
||
|
|
||
|
|
||
|
classes = (
|
||
|
ExportBatPack,
|
||
|
ADM_OT_export_zip,
|
||
|
)
|
||
|
|
||
|
|
||
|
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()
|
||
|
|