import bpy 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) class GP_OT_install_brush_pack(bpy.types.Operator): bl_idname = "gp.import_brush_pack" bl_label = "Download and import texture brush pack" bl_description = "Download and import Grease Pencil brush pack from the web (~3.7 Mo)" bl_options = {"REGISTER", "INTERNAL"} # @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') else: self.report({'WARNING'}, 'Brushes already loaded') 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() return {"FINISHED"} def register(): bpy.utils.register_class(GP_OT_install_brush_pack) def unregister(): bpy.utils.unregister_class(GP_OT_install_brush_pack)