File headers: use SPDX license identifiers
[blender-addons.git] / greasepencil_tools / import_brush_pack.py
blob2b960af6c629d4671836f04ce2584d8c797ef93f
1 # SPDX-License-Identifier: GPL-2.0-or-later
3 import bpy
4 import re
5 import ssl
6 import urllib.request
7 import urllib.parse
8 import zipfile
9 from pathlib import Path
11 def unzip(zip_path, extract_dir_path):
12 '''Get a zip path and a directory path to extract to'''
13 with zipfile.ZipFile(zip_path, 'r') as zip_ref:
14 zip_ref.extractall(extract_dir_path)
16 def simple_dl_url(url, dest, fallback_url=None):
17 ## need to import urlib.request or linux module does not found 'request' using urllib directly
18 ## need to create an SSl context or linux fail returning unverified ssl
19 # ssl._create_default_https_context = ssl._create_unverified_context
21 try:
22 urllib.request.urlretrieve(url, dest)
23 except Exception as e:
24 print('Error trying to download\n', e)
25 if fallback_url:
26 print('\nDownload page for manual install:', fallback_url)
27 return e
29 def download_url(url, dest):
30 '''download passed url to dest file (include filename)'''
31 import shutil
32 import time
33 ssl._create_default_https_context = ssl._create_unverified_context
34 start_time = time.time()
36 try:
37 with urllib.request.urlopen(url) as response, open(dest, 'wb') as out_file:
38 shutil.copyfileobj(response, out_file)
39 except Exception as e:
40 print('Error trying to download\n', e)
41 return e
43 print(f"Download time {time.time() - start_time:.2f}s",)
46 def get_brushes(blend_fp):
47 cur_brushes = [b.name for b in bpy.data.brushes]
48 with bpy.data.libraries.load(str(blend_fp), link=False) as (data_from, data_to):
49 # load brushes starting with 'tex' prefix if there are not already there
50 data_to.brushes = [b for b in data_from.brushes if b.startswith('tex_') and not b in cur_brushes]
51 # Add holdout
52 if 'z_holdout' in data_from.brushes and not 'z_holdout' in cur_brushes:
53 data_to.brushes.append('z_holdout')
55 ## force fake user for the brushes
56 for b in data_to.brushes:
57 b.use_fake_user = True
59 return len(data_to.brushes)
61 class GP_OT_install_brush_pack(bpy.types.Operator):
62 bl_idname = "gp.import_brush_pack"
63 bl_label = "Download and import texture brush pack"
64 bl_description = "Download and import Grease Pencil brush pack from the web (~3.7 Mo)"
65 bl_options = {"REGISTER", "INTERNAL"}
67 # @classmethod
68 # def poll(cls, context):
69 # return True
71 def _append_brushes(self, blend_fp):
72 bct = get_brushes(blend_fp)
73 if bct:
74 self.report({'INFO'}, f'{bct} brushes installed')
75 else:
76 self.report({'WARNING'}, 'Brushes already loaded')
78 def _install_from_zip(self):
79 ## get blend file name
80 blendname = None
81 with zipfile.ZipFile(self.brushzip, 'r') as zfd:
82 for f in zfd.namelist():
83 if f.endswith('.blend'):
84 blendname = f
85 break
86 if not blendname:
87 self.report({'ERROR'}, f'blend file not found in zip {self.brushzip}')
88 return
90 unzip(self.brushzip, self.temp)
92 self._append_brushes(Path(self.temp) / blendname)
94 def execute(self, context):
96 import tempfile
97 import json
98 import hashlib
99 import os
101 ## get temp dir
102 temp = tempfile.gettempdir()
103 if not temp:
104 self.report({'ERROR'}, 'no os temporary directory found to download brush pack (using python tempfile.gettempdir())')
105 return {"CANCELLED"}
107 self.temp = Path(temp)
109 ## download link from gitlab
110 # brush pack project https://gitlab.com/pepe-school-land/gp-brush-pack
111 repo_url = r'https://gitlab.com/api/v4/projects/21994857'
112 tree_url = f'{repo_url}/repository/tree'
114 ## need to create an SSl context or linux fail and raise unverified ssl
115 ssl._create_default_https_context = ssl._create_unverified_context
117 try:
118 with urllib.request.urlopen(tree_url) as response:
119 html = response.read()
120 except:
121 ## try loading from tempdir
122 packs = [f for f in os.listdir(self.temp) if 'GP_brush_pack' in f and f.endswith('.blend')]
123 if packs:
124 packs.sort()
125 self._append_brushes(Path(self.temp) / packs[-1])
126 self.report({'WARNING'}, 'Brushes loaded from temp directory (No download)')
127 # print('Could not reach web url : Brushes were loaded from temp directory file (No download)')
128 return {"FINISHED"}
130 self.report({'ERROR'}, f'Check your internet connexion, Impossible to connect to url: {tree_url}')
131 return {"CANCELLED"}
133 if not html:
134 self.report({'ERROR'}, f'No response read from: {tree_url}')
135 return {"CANCELLED"}
137 tree_dic = json.loads(html)
138 zips = [fi for fi in tree_dic if fi['type'] == 'blob' and fi['name'].endswith('.zip')]
140 if not zips:
141 print(f'no zip file found in {tree_url}')
142 return {"CANCELLED"}
144 ## sort by name to get last
145 zips.sort(key=lambda x: x['name'])
146 last_zip = zips[-1]
147 zipname = last_zip['name']
148 id_num = last_zip['id']
151 ## url by filename
152 # filepath_encode = urllib.parse.quote(zipname, safe='')# need safe to convert possible '/'
153 # dl_url = f'{repo_url}/repository/files/{filepath_encode}/raw?ref=master'
155 ## url by blobs
156 dl_url = f"{repo_url}/repository/blobs/{id_num}/raw"
158 self.brushzip = self.temp / zipname
161 ### Load existing files instead of redownloading if exists and up to date (same hash)
162 if self.brushzip.exists():
163 ### Test the hash against online git hash (check for update)
164 BLOCK_SIZE = 524288# 512 Kb buf size
165 file_hash = hashlib.sha1()
166 file_hash.update(("blob %u\0" % os.path.getsize(self.brushzip)).encode('utf-8'))
167 with open(self.brushzip, 'rb') as f:
168 fb = f.read(BLOCK_SIZE)
169 while len(fb) > 0:
170 file_hash.update(fb)
171 fb = f.read(BLOCK_SIZE)
173 if file_hash.hexdigest() == id_num: # same git SHA1
174 ## is up to date, install
175 print(f'{self.brushzip} is up do date, appending brushes')
176 self._install_from_zip()
177 return {"FINISHED"}
179 ## Download, unzip, use blend
180 print(f'Downloading brushpack in {self.brushzip}')
181 ## https://cloud.blender.org/p/gallery/5f235cc297f8815e74ffb90b
183 fallback_url='https://gitlab.com/pepe-school-land/gp-brush-pack/-/blob/master/Official_GP_brush_pack_v01.zip'
184 err = simple_dl_url(dl_url, str(self.brushzip), fallback_url)
185 # err = download_url(dl_url, str(self.brushzip), fallback_url)
187 if err:
188 self.report({'ERROR'}, 'Could not download brush pack. Check your internet connection. (see console for detail)')
189 return {"CANCELLED"}
190 else:
191 print('Done')
192 self._install_from_zip()
193 return {"FINISHED"}
196 def register():
197 bpy.utils.register_class(GP_OT_install_brush_pack)
199 def unregister():
200 bpy.utils.unregister_class(GP_OT_install_brush_pack)