Skip to content
Snippets Groups Projects
import_brush_pack.py 7.16 KiB
Newer Older
  • Learn to ignore specific revisions
  • Pullusb's avatar
    Pullusb committed
    import bpy
    
    Pullusb's avatar
    Pullusb committed
    import re
    import ssl
    import urllib.request
    import urllib.parse
    import zipfile
    from pathlib import Path
    
    def unzip(zip_path, extract_dir_path):
        '''Get a zip path and a directory path to extract to'''
        with zipfile.ZipFile(zip_path, 'r') as zip_ref:
            zip_ref.extractall(extract_dir_path)
    
    def simple_dl_url(url, dest, fallback_url=None):
        ## need to import urlib.request or linux module does not found 'request' using urllib directly
        ## need to create an SSl context or linux fail returning unverified ssl
        # ssl._create_default_https_context = ssl._create_unverified_context
    
        try:
            urllib.request.urlretrieve(url, dest)
        except Exception as e:
            print('Error trying to download\n', e)
            if fallback_url:
                print('\nDownload page for manual install:', fallback_url)
            return e
    
    def download_url(url, dest):
        '''download passed url to dest file (include filename)'''
        import shutil
        import time
        ssl._create_default_https_context = ssl._create_unverified_context
        start_time = time.time()
        
        try:
            with urllib.request.urlopen(url) as response, open(dest, 'wb') as out_file:
                shutil.copyfileobj(response, out_file)
        except Exception as e:
            print('Error trying to download\n', e)
            return e
    
        print(f"Download time {time.time() - start_time:.2f}s",)
    
    
    def get_brushes(blend_fp):
        cur_brushes = [b.name for b in bpy.data.brushes]
        with bpy.data.libraries.load(str(blend_fp), link=False) as (data_from, data_to):
            # load brushes starting with 'tex' prefix if there are not already there
            data_to.brushes = [b for b in data_from.brushes if b.startswith('tex_') and not b in cur_brushes]
            # Add holdout
            if 'z_holdout' in data_from.brushes and not 'z_holdout' in cur_brushes:
                data_to.brushes.append('z_holdout')
    
        ## force fake user for the brushes
        for b in data_to.brushes:
            b.use_fake_user = True
        
        return len(data_to.brushes)
    
    Pullusb's avatar
    Pullusb committed
    
    class GP_OT_install_brush_pack(bpy.types.Operator):
        bl_idname = "gp.import_brush_pack"
    
    Pullusb's avatar
    Pullusb committed
        bl_label = "Download and import texture brush pack"
        bl_description = "Download and import Grease Pencil brush pack from the web (~3.7 Mo)"
    
    Pullusb's avatar
    Pullusb committed
        bl_options = {"REGISTER", "INTERNAL"}
    
    
    Pullusb's avatar
    Pullusb committed
        # @classmethod
        # def poll(cls, context):
        #     return True
    
        def _append_brushes(self, blend_fp):
            bct = get_brushes(blend_fp)
            if bct:
                self.report({'INFO'}, f'{bct} brushes installed')
    
    Pullusb's avatar
    Pullusb committed
            else:
                self.report({'WARNING'}, 'Brushes already loaded')
    
    Pullusb's avatar
    Pullusb committed
    
        def _install_from_zip(self):
            ## get blend file name
            blendname = None
            with zipfile.ZipFile(self.brushzip, 'r') as zfd:
                for f in zfd.namelist():
                    if f.endswith('.blend'):
                        blendname = f
                        break
            if not blendname:
                self.report({'ERROR'}, f'blend file not found in zip {self.brushzip}')
                return
    
            unzip(self.brushzip, self.temp)
    
            self._append_brushes(Path(self.temp) / blendname)
    
        def execute(self, context):
    
            import tempfile
            import json
            import hashlib
            import os
    
            ## get temp dir
            temp = tempfile.gettempdir()
            if not temp:
                self.report({'ERROR'}, 'no os temporary directory found to download brush pack (using python tempfile.gettempdir())')
                return {"CANCELLED"}
            
            self.temp = Path(temp)
    
            ## download link from gitlab
            # brush pack project https://gitlab.com/pepe-school-land/gp-brush-pack 
            repo_url = r'https://gitlab.com/api/v4/projects/21994857' 
            tree_url = f'{repo_url}/repository/tree'
    
            ## need to create an SSl context or linux fail and raise unverified ssl
            ssl._create_default_https_context = ssl._create_unverified_context
    
            try:
                with urllib.request.urlopen(tree_url) as response:
                    html = response.read()
            except:
                ## try loading from tempdir
                packs = [f for f in os.listdir(self.temp) if 'GP_brush_pack' in f and f.endswith('.blend')]
                if packs:
                    packs.sort()
                    self._append_brushes(Path(self.temp) / packs[-1])
                    self.report({'WARNING'}, 'Brushes loaded from temp directory (No download)')
                    # print('Could not reach web url : Brushes were loaded from temp directory file (No download)')
                    return {"FINISHED"}
    
                self.report({'ERROR'}, f'Check your internet connexion, Impossible to connect to url: {tree_url}')
                return {"CANCELLED"}
            
            if not html:
                self.report({'ERROR'}, f'No response read from: {tree_url}')
                return {"CANCELLED"}
    
            tree_dic = json.loads(html)
            zips = [fi for fi in tree_dic if fi['type'] == 'blob' and fi['name'].endswith('.zip')]
    
            if not zips:
                print(f'no zip file found in {tree_url}')
                return {"CANCELLED"}
    
            ## sort by name to get last
            zips.sort(key=lambda x: x['name'])
            last_zip = zips[-1]
            zipname = last_zip['name']
            id_num = last_zip['id']
    
    
            ## url by filename
            # filepath_encode = urllib.parse.quote(zipname, safe='')# need safe to convert possible '/'
            # dl_url = f'{repo_url}/repository/files/{filepath_encode}/raw?ref=master'
    
            ## url by blobs
            dl_url = f"{repo_url}/repository/blobs/{id_num}/raw"
    
            self.brushzip = self.temp / zipname
            
    
            ### Load existing files instead of redownloading if exists and up to date (same hash)
            if self.brushzip.exists():
                ### Test the hash against online git hash (check for update)
                BLOCK_SIZE = 524288# 512 Kb buf size
                file_hash = hashlib.sha1()
                file_hash.update(("blob %u\0" % os.path.getsize(self.brushzip)).encode('utf-8'))
                with open(self.brushzip, 'rb') as f:
                    fb = f.read(BLOCK_SIZE)
                    while len(fb) > 0:
                        file_hash.update(fb)
                        fb = f.read(BLOCK_SIZE)
                
                if file_hash.hexdigest() == id_num: # same git SHA1
                    ## is up to date, install 
                    print(f'{self.brushzip} is up do date, appending brushes')
                    self._install_from_zip()
                    return {"FINISHED"}
            
            ## Download, unzip, use blend
            print(f'Downloading brushpack in {self.brushzip}')
            ## https://cloud.blender.org/p/gallery/5f235cc297f8815e74ffb90b
    
            fallback_url='https://gitlab.com/pepe-school-land/gp-brush-pack/-/blob/master/Official_GP_brush_pack_v01.zip'
            err = simple_dl_url(dl_url, str(self.brushzip), fallback_url)
            # err = download_url(dl_url, str(self.brushzip), fallback_url) 
    
            if err:
                self.report({'ERROR'}, 'Could not download brush pack. Check your internet connection. (see console for detail)')
                return {"CANCELLED"}
            else:
                print('Done')
            self._install_from_zip()
    
    Pullusb's avatar
    Pullusb committed
            return {"FINISHED"}
    
    
    def register():
        bpy.utils.register_class(GP_OT_install_brush_pack)
    
    def unregister():
        bpy.utils.unregister_class(GP_OT_install_brush_pack)