1# ##### BEGIN GPL LICENSE BLOCK ##### 2# 3# This program is free software; you can redistribute it and/or 4# modify it under the terms of the GNU General Public License 5# as published by the Free Software Foundation; either version 2 6# of the License, or (at your option) any later version. 7# 8# This program is distributed in the hope that it will be useful, 9# but WITHOUT ANY WARRANTY; without even the implied warranty of 10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11# GNU General Public License for more details. 12# 13# You should have received a copy of the GNU General Public License 14# along with this program; if not, write to the Free Software Foundation, 15# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 16# 17# ##### END GPL LICENSE BLOCK ##### 18 19 20if "bpy" in locals(): 21 from importlib import reload 22 23 paths = reload(paths) 24 utils = reload(utils) 25 bg_blender = reload(bg_blender) 26else: 27 from blenderkit import paths, utils, bg_blender 28 29import tempfile, os, subprocess, json, sys 30 31import bpy 32 33BLENDERKIT_EXPORT_DATA_FILE = "data.json" 34 35 36def check_thumbnail(props, imgpath): 37 img = utils.get_hidden_image(imgpath, 'upload_preview', force_reload=True) 38 if img is not None: # and img.size[0] == img.size[1] and img.size[0] >= 512 and ( 39 # img.file_format == 'JPEG' or img.file_format == 'PNG'): 40 props.has_thumbnail = True 41 props.thumbnail_generating_state = '' 42 return 43 else: 44 props.has_thumbnail = False 45 output = '' 46 if img is None or img.size[0] == 0 or img.filepath.find('thumbnail_notready.jpg') > -1: 47 output += 'No thumbnail or wrong file path\n' 48 else: 49 pass; 50 # this is causing problems on some platforms, don't know why.. 51 # if img.size[0] != img.size[1]: 52 # output += 'image not a square\n' 53 # if img.size[0] < 512: 54 # output += 'image too small, should be at least 512x512\n' 55 # if img.file_format != 'JPEG' or img.file_format != 'PNG': 56 # output += 'image has to be a jpeg or png' 57 props.thumbnail_generating_state = output 58 59 60def update_upload_model_preview(self, context): 61 ob = utils.get_active_model() 62 if ob is not None: 63 props = ob.blenderkit 64 imgpath = props.thumbnail 65 check_thumbnail(props, imgpath) 66 67 68def update_upload_scene_preview(self, context): 69 s = bpy.context.scene 70 props = s.blenderkit 71 imgpath = props.thumbnail 72 check_thumbnail(props, imgpath) 73 74 75def update_upload_material_preview(self, context): 76 if hasattr(bpy.context, 'active_object') \ 77 and bpy.context.view_layer.objects.active is not None \ 78 and bpy.context.active_object.active_material is not None: 79 mat = bpy.context.active_object.active_material 80 props = mat.blenderkit 81 imgpath = props.thumbnail 82 check_thumbnail(props, imgpath) 83 84 85def update_upload_brush_preview(self, context): 86 brush = utils.get_active_brush() 87 if brush is not None: 88 props = brush.blenderkit 89 imgpath = bpy.path.abspath(brush.icon_filepath) 90 check_thumbnail(props, imgpath) 91 92 93def start_thumbnailer(self, context): 94 # Prepare to save the file 95 mainmodel = utils.get_active_model() 96 mainmodel.blenderkit.is_generating_thumbnail = True 97 mainmodel.blenderkit.thumbnail_generating_state = 'starting blender instance' 98 99 binary_path = bpy.app.binary_path 100 script_path = os.path.dirname(os.path.realpath(__file__)) 101 basename, ext = os.path.splitext(bpy.data.filepath) 102 if not basename: 103 basename = os.path.join(basename, "temp") 104 if not ext: 105 ext = ".blend" 106 asset_name = mainmodel.name 107 tempdir = tempfile.mkdtemp() 108 109 file_dir = os.path.dirname(bpy.data.filepath) 110 thumb_path = os.path.join(file_dir, asset_name) 111 rel_thumb_path = os.path.join('//', asset_name) 112 113 i = 0 114 while os.path.isfile(thumb_path + '.jpg'): 115 thumb_path = os.path.join(file_dir, asset_name + '_' + str(i).zfill(4)) 116 rel_thumb_path = os.path.join('//', asset_name + '_' + str(i).zfill(4)) 117 i += 1 118 119 filepath = os.path.join(tempdir, "thumbnailer_blenderkit" + ext) 120 tfpath = paths.get_thumbnailer_filepath() 121 datafile = os.path.join(tempdir, BLENDERKIT_EXPORT_DATA_FILE) 122 try: 123 # save a copy of actual scene but don't interfere with the users models 124 bpy.ops.wm.save_as_mainfile(filepath=filepath, compress=False, copy=True) 125 126 obs = utils.get_hierarchy(mainmodel) 127 obnames = [] 128 for ob in obs: 129 obnames.append(ob.name) 130 with open(datafile, 'w') as s: 131 bkit = mainmodel.blenderkit 132 json.dump({ 133 "type": "model", 134 "models": str(obnames), 135 "thumbnail_angle": bkit.thumbnail_angle, 136 "thumbnail_snap_to": bkit.thumbnail_snap_to, 137 "thumbnail_background_lightness": bkit.thumbnail_background_lightness, 138 "thumbnail_resolution": bkit.thumbnail_resolution, 139 "thumbnail_samples": bkit.thumbnail_samples, 140 "thumbnail_denoising": bkit.thumbnail_denoising, 141 }, s) 142 143 144 145 proc = subprocess.Popen([ 146 binary_path, 147 "--background", 148 "-noaudio", 149 tfpath, 150 "--python", os.path.join(script_path, "autothumb_model_bg.py"), 151 "--", datafile, filepath, thumb_path, tempdir 152 ], bufsize=1, stdout=subprocess.PIPE, stdin=subprocess.PIPE, creationflags=utils.get_process_flags()) 153 154 eval_path_computing = "bpy.data.objects['%s'].blenderkit.is_generating_thumbnail" % mainmodel.name 155 eval_path_state = "bpy.data.objects['%s'].blenderkit.thumbnail_generating_state" % mainmodel.name 156 eval_path = "bpy.data.objects['%s']" % mainmodel.name 157 158 bg_blender.add_bg_process(eval_path_computing=eval_path_computing, eval_path_state=eval_path_state, 159 eval_path=eval_path, process_type='THUMBNAILER', process=proc) 160 161 mainmodel.blenderkit.thumbnail = rel_thumb_path + '.jpg' 162 mainmodel.blenderkit.thumbnail_generating_state = 'Saving .blend file' 163 164 except Exception as e: 165 self.report({'WARNING'}, "Error while exporting file: %s" % str(e)) 166 return {'FINISHED'} 167 168 169def start_material_thumbnailer(self, context): 170 # Prepare to save the file 171 mat = bpy.context.active_object.active_material 172 mat.blenderkit.is_generating_thumbnail = True 173 mat.blenderkit.thumbnail_generating_state = 'starting blender instance' 174 175 binary_path = bpy.app.binary_path 176 script_path = os.path.dirname(os.path.realpath(__file__)) 177 basename, ext = os.path.splitext(bpy.data.filepath) 178 if not basename: 179 basename = os.path.join(basename, "temp") 180 if not ext: 181 ext = ".blend" 182 asset_name = mat.name 183 tempdir = tempfile.mkdtemp() 184 185 file_dir = os.path.dirname(bpy.data.filepath) 186 187 thumb_path = os.path.join(file_dir, asset_name) 188 rel_thumb_path = os.path.join('//', mat.name) 189 i = 0 190 while os.path.isfile(thumb_path + '.png'): 191 thumb_path = os.path.join(file_dir, mat.name + '_' + str(i).zfill(4)) 192 rel_thumb_path = os.path.join('//', mat.name + '_' + str(i).zfill(4)) 193 i += 1 194 195 filepath = os.path.join(tempdir, "material_thumbnailer_cycles" + ext) 196 tfpath = paths.get_material_thumbnailer_filepath() 197 datafile = os.path.join(tempdir, BLENDERKIT_EXPORT_DATA_FILE) 198 try: 199 # save a copy of actual scene but don't interfere with the users models 200 bpy.ops.wm.save_as_mainfile(filepath=filepath, compress=False, copy=True) 201 202 with open(datafile, 'w') as s: 203 bkit = mat.blenderkit 204 json.dump({ 205 "type": "material", 206 "material": mat.name, 207 "thumbnail_type": bkit.thumbnail_generator_type, 208 "thumbnail_scale": bkit.thumbnail_scale, 209 "thumbnail_background": bkit.thumbnail_background, 210 "thumbnail_background_lightness": bkit.thumbnail_background_lightness, 211 "thumbnail_resolution": bkit.thumbnail_resolution, 212 "thumbnail_samples": bkit.thumbnail_samples, 213 "thumbnail_denoising": bkit.thumbnail_denoising, 214 "adaptive_subdivision": bkit.adaptive_subdivision, 215 "texture_size_meters": bkit.texture_size_meters, 216 }, s) 217 218 219 proc = subprocess.Popen([ 220 binary_path, 221 "--background", 222 "-noaudio", 223 tfpath, 224 "--python", os.path.join(script_path, "autothumb_material_bg.py"), 225 "--", datafile, filepath, thumb_path, tempdir 226 ], bufsize=1, stdout=subprocess.PIPE, stdin=subprocess.PIPE, creationflags=utils.get_process_flags()) 227 228 eval_path_computing = "bpy.data.materials['%s'].blenderkit.is_generating_thumbnail" % mat.name 229 eval_path_state = "bpy.data.materials['%s'].blenderkit.thumbnail_generating_state" % mat.name 230 eval_path = "bpy.data.materials['%s']" % mat.name 231 232 bg_blender.add_bg_process(eval_path_computing=eval_path_computing, eval_path_state=eval_path_state, 233 eval_path=eval_path, process_type='THUMBNAILER', process=proc) 234 235 mat.blenderkit.thumbnail = rel_thumb_path + '.png' 236 mat.blenderkit.thumbnail_generating_state = 'Saving .blend file' 237 except Exception as e: 238 self.report({'WARNING'}, "Error while packing file: %s" % str(e)) 239 return {'FINISHED'} 240 241 242class GenerateThumbnailOperator(bpy.types.Operator): 243 """Generate Cycles thumbnail for model assets""" 244 bl_idname = "object.blenderkit_generate_thumbnail" 245 bl_label = "BlenderKit Thumbnail Generator" 246 bl_options = {'REGISTER', 'INTERNAL'} 247 248 @classmethod 249 def poll(cls, context): 250 return bpy.context.view_layer.objects.active is not None 251 252 def draw(self, context): 253 ob = bpy.context.active_object 254 while ob.parent is not None: 255 ob = ob.parent 256 props = ob.blenderkit 257 layout = self.layout 258 layout.label(text='thumbnailer settings') 259 layout.prop(props, 'thumbnail_background_lightness') 260 layout.prop(props, 'thumbnail_angle') 261 layout.prop(props, 'thumbnail_snap_to') 262 layout.prop(props, 'thumbnail_samples') 263 layout.prop(props, 'thumbnail_resolution') 264 layout.prop(props, 'thumbnail_denoising') 265 preferences = bpy.context.preferences.addons['blenderkit'].preferences 266 layout.prop(preferences, "thumbnail_use_gpu") 267 268 def execute(self, context): 269 start_thumbnailer(self, context) 270 return {'FINISHED'} 271 272 def invoke(self, context, event): 273 wm = context.window_manager 274 if bpy.data.filepath == '': 275 title = "Can't render thumbnail" 276 message = "please save your file first" 277 278 def draw_message(self, context): 279 self.layout.label(text = message) 280 281 bpy.context.window_manager.popup_menu(draw_message, title=title, icon='INFO') 282 return {'FINISHED'} 283 284 return wm.invoke_props_dialog(self) 285 286 287class GenerateMaterialThumbnailOperator(bpy.types.Operator): 288 """Tooltip""" 289 bl_idname = "object.blenderkit_material_thumbnail" 290 bl_label = "BlenderKit Material Thumbnail Generator" 291 bl_options = {'REGISTER', 'INTERNAL'} 292 293 @classmethod 294 def poll(cls, context): 295 return bpy.context.view_layer.objects.active is not None 296 297 def check(self, context): 298 return True 299 300 def draw(self, context): 301 layout = self.layout 302 props = bpy.context.active_object.active_material.blenderkit 303 layout.prop(props, 'thumbnail_generator_type') 304 layout.prop(props, 'thumbnail_scale') 305 layout.prop(props, 'thumbnail_background') 306 if props.thumbnail_background: 307 layout.prop(props, 'thumbnail_background_lightness') 308 layout.prop(props, 'thumbnail_resolution') 309 layout.prop(props, 'thumbnail_samples') 310 layout.prop(props, 'thumbnail_denoising') 311 layout.prop(props, 'adaptive_subdivision') 312 preferences = bpy.context.preferences.addons['blenderkit'].preferences 313 layout.prop(preferences, "thumbnail_use_gpu") 314 315 def execute(self, context): 316 start_material_thumbnailer(self, context) 317 318 return {'FINISHED'} 319 320 def invoke(self, context, event): 321 wm = context.window_manager 322 return wm.invoke_props_dialog(self) 323 324 325def register_thumbnailer(): 326 bpy.utils.register_class(GenerateThumbnailOperator) 327 bpy.utils.register_class(GenerateMaterialThumbnailOperator) 328 329 330def unregister_thumbnailer(): 331 bpy.utils.unregister_class(GenerateThumbnailOperator) 332 bpy.utils.unregister_class(GenerateMaterialThumbnailOperator) 333