Cleanup: simplify file name incrementing logic
[blender-addons.git] / blenderkit / autothumb.py
blob2a649427ef3a813063b81acfbf6894c4fcf5cf57
1 # ##### BEGIN GPL LICENSE BLOCK #####
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.
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.
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.
17 # ##### END GPL LICENSE BLOCK #####
20 from blenderkit import paths, utils, bg_blender, ui_panels
22 import tempfile, os, subprocess, json, sys
24 import bpy
26 BLENDERKIT_EXPORT_DATA_FILE = "data.json"
29 def check_thumbnail(props, imgpath):
30 img = utils.get_hidden_image(imgpath, 'upload_preview', force_reload=True)
31 if img is not None: # and img.size[0] == img.size[1] and img.size[0] >= 512 and (
32 # img.file_format == 'JPEG' or img.file_format == 'PNG'):
33 props.has_thumbnail = True
34 props.thumbnail_generating_state = ''
35 return
36 else:
37 props.has_thumbnail = False
38 output = ''
39 if img is None or img.size[0] == 0 or img.filepath.find('thumbnail_notready.jpg') > -1:
40 output += 'No thumbnail or wrong file path\n'
41 else:
42 pass;
43 # this is causing problems on some platforms, don't know why..
44 # if img.size[0] != img.size[1]:
45 # output += 'image not a square\n'
46 # if img.size[0] < 512:
47 # output += 'image too small, should be at least 512x512\n'
48 # if img.file_format != 'JPEG' or img.file_format != 'PNG':
49 # output += 'image has to be a jpeg or png'
50 props.thumbnail_generating_state = output
53 def update_upload_model_preview(self, context):
54 ob = utils.get_active_model()
55 if ob is not None:
56 props = ob.blenderkit
57 imgpath = props.thumbnail
58 check_thumbnail(props, imgpath)
61 def update_upload_scene_preview(self, context):
62 s = bpy.context.scene
63 props = s.blenderkit
64 imgpath = props.thumbnail
65 check_thumbnail(props, imgpath)
68 def update_upload_material_preview(self, context):
69 if hasattr(bpy.context, 'active_object') \
70 and bpy.context.view_layer.objects.active is not None \
71 and bpy.context.active_object.active_material is not None:
72 mat = bpy.context.active_object.active_material
73 props = mat.blenderkit
74 imgpath = props.thumbnail
75 check_thumbnail(props, imgpath)
78 def update_upload_brush_preview(self, context):
79 brush = utils.get_active_brush()
80 if brush is not None:
81 props = brush.blenderkit
82 imgpath = bpy.path.abspath(brush.icon_filepath)
83 check_thumbnail(props, imgpath)
86 def start_thumbnailer(self, context):
87 # Prepare to save the file
88 mainmodel = utils.get_active_model()
89 mainmodel.blenderkit.is_generating_thumbnail = True
90 mainmodel.blenderkit.thumbnail_generating_state = 'starting blender instance'
92 binary_path = bpy.app.binary_path
93 script_path = os.path.dirname(os.path.realpath(__file__))
94 basename, ext = os.path.splitext(bpy.data.filepath)
95 if not basename:
96 basename = os.path.join(basename, "temp")
97 if not ext:
98 ext = ".blend"
99 asset_name = mainmodel.name
100 tempdir = tempfile.mkdtemp()
102 file_dir = os.path.dirname(bpy.data.filepath)
103 thumb_path = os.path.join(file_dir, asset_name)
104 rel_thumb_path = os.path.join('//', asset_name)
106 i = 0
107 while os.path.isfile(thumb_path + '.jpg'):
108 thumb_path = os.path.join(file_dir, asset_name + '_' + str(i).zfill(4))
109 rel_thumb_path = os.path.join('//', asset_name + '_' + str(i).zfill(4))
110 i += 1
112 filepath = os.path.join(tempdir, "thumbnailer_blenderkit" + ext)
113 tfpath = paths.get_thumbnailer_filepath()
114 datafile = os.path.join(tempdir, BLENDERKIT_EXPORT_DATA_FILE)
115 try:
116 # save a copy of actual scene but don't interfere with the users models
117 bpy.ops.wm.save_as_mainfile(filepath=filepath, compress=False, copy=True)
119 obs = utils.get_hierarchy(mainmodel)
120 obnames = []
121 for ob in obs:
122 obnames.append(ob.name)
123 with open(datafile, 'w', encoding = 'utf-8') as s:
124 bkit = mainmodel.blenderkit
125 json.dump({
126 "type": "model",
127 "models": str(obnames),
128 "thumbnail_angle": bkit.thumbnail_angle,
129 "thumbnail_snap_to": bkit.thumbnail_snap_to,
130 "thumbnail_background_lightness": bkit.thumbnail_background_lightness,
131 "thumbnail_resolution": bkit.thumbnail_resolution,
132 "thumbnail_samples": bkit.thumbnail_samples,
133 "thumbnail_denoising": bkit.thumbnail_denoising,
134 }, s, ensure_ascii=False, indent=4)
136 proc = subprocess.Popen([
137 binary_path,
138 "--background",
139 "-noaudio",
140 tfpath,
141 "--python", os.path.join(script_path, "autothumb_model_bg.py"),
142 "--", datafile, filepath, thumb_path, tempdir
143 ], bufsize=1, stdout=subprocess.PIPE, stdin=subprocess.PIPE, creationflags=utils.get_process_flags())
145 eval_path_computing = "bpy.data.objects['%s'].blenderkit.is_generating_thumbnail" % mainmodel.name
146 eval_path_state = "bpy.data.objects['%s'].blenderkit.thumbnail_generating_state" % mainmodel.name
147 eval_path = "bpy.data.objects['%s']" % mainmodel.name
149 bg_blender.add_bg_process(eval_path_computing=eval_path_computing, eval_path_state=eval_path_state,
150 eval_path=eval_path, process_type='THUMBNAILER', process=proc)
152 mainmodel.blenderkit.thumbnail = rel_thumb_path + '.jpg'
153 mainmodel.blenderkit.thumbnail_generating_state = 'Saving .blend file'
155 except Exception as e:
156 self.report({'WARNING'}, "Error while exporting file: %s" % str(e))
157 return {'FINISHED'}
160 def start_material_thumbnailer(self, context, wait=False):
161 # Prepare to save the file
162 mat = bpy.context.active_object.active_material
163 mat.blenderkit.is_generating_thumbnail = True
164 mat.blenderkit.thumbnail_generating_state = 'starting blender instance'
166 binary_path = bpy.app.binary_path
167 script_path = os.path.dirname(os.path.realpath(__file__))
168 basename, ext = os.path.splitext(bpy.data.filepath)
169 if not basename:
170 basename = os.path.join(basename, "temp")
171 if not ext:
172 ext = ".blend"
173 asset_name = mat.name
174 tempdir = tempfile.mkdtemp()
176 file_dir = os.path.dirname(bpy.data.filepath)
178 thumb_path = os.path.join(file_dir, asset_name)
179 rel_thumb_path = os.path.join('//', mat.name)
180 i = 0
181 while os.path.isfile(thumb_path + '.png'):
182 thumb_path = os.path.join(file_dir, mat.name + '_' + str(i).zfill(4))
183 rel_thumb_path = os.path.join('//', mat.name + '_' + str(i).zfill(4))
184 i += 1
186 filepath = os.path.join(tempdir, "material_thumbnailer_cycles" + ext)
187 tfpath = paths.get_material_thumbnailer_filepath()
188 datafile = os.path.join(tempdir, BLENDERKIT_EXPORT_DATA_FILE)
189 try:
190 # save a copy of actual scene but don't interfere with the users models
191 bpy.ops.wm.save_as_mainfile(filepath=filepath, compress=False, copy=True)
193 with open(datafile, 'w', encoding = 'utf-8') as s:
194 bkit = mat.blenderkit
195 json.dump({
196 "type": "material",
197 "material": mat.name,
198 "thumbnail_type": bkit.thumbnail_generator_type,
199 "thumbnail_scale": bkit.thumbnail_scale,
200 "thumbnail_background": bkit.thumbnail_background,
201 "thumbnail_background_lightness": bkit.thumbnail_background_lightness,
202 "thumbnail_resolution": bkit.thumbnail_resolution,
203 "thumbnail_samples": bkit.thumbnail_samples,
204 "thumbnail_denoising": bkit.thumbnail_denoising,
205 "adaptive_subdivision": bkit.adaptive_subdivision,
206 "texture_size_meters": bkit.texture_size_meters,
207 }, s, ensure_ascii=False, indent=4)
209 proc = subprocess.Popen([
210 binary_path,
211 "--background",
212 "-noaudio",
213 tfpath,
214 "--python", os.path.join(script_path, "autothumb_material_bg.py"),
215 "--", datafile, filepath, thumb_path, tempdir
216 ], bufsize=1, stdout=subprocess.PIPE, stdin=subprocess.PIPE, creationflags=utils.get_process_flags())
218 eval_path_computing = "bpy.data.materials['%s'].blenderkit.is_generating_thumbnail" % mat.name
219 eval_path_state = "bpy.data.materials['%s'].blenderkit.thumbnail_generating_state" % mat.name
220 eval_path = "bpy.data.materials['%s']" % mat.name
222 bg_blender.add_bg_process(eval_path_computing=eval_path_computing, eval_path_state=eval_path_state,
223 eval_path=eval_path, process_type='THUMBNAILER', process=proc)
225 mat.blenderkit.thumbnail = rel_thumb_path + '.png'
226 mat.blenderkit.thumbnail_generating_state = 'Saving .blend file'
227 except Exception as e:
228 self.report({'WARNING'}, "Error while packing file: %s" % str(e))
229 return {'FINISHED'}
232 class GenerateThumbnailOperator(bpy.types.Operator):
233 """Generate Cycles thumbnail for model assets"""
234 bl_idname = "object.blenderkit_generate_thumbnail"
235 bl_label = "BlenderKit Thumbnail Generator"
236 bl_options = {'REGISTER', 'INTERNAL'}
238 @classmethod
239 def poll(cls, context):
240 return bpy.context.view_layer.objects.active is not None
242 def draw(self, context):
243 ob = bpy.context.active_object
244 while ob.parent is not None:
245 ob = ob.parent
246 props = ob.blenderkit
247 layout = self.layout
248 layout.label(text='thumbnailer settings')
249 layout.prop(props, 'thumbnail_background_lightness')
250 layout.prop(props, 'thumbnail_angle')
251 layout.prop(props, 'thumbnail_snap_to')
252 layout.prop(props, 'thumbnail_samples')
253 layout.prop(props, 'thumbnail_resolution')
254 layout.prop(props, 'thumbnail_denoising')
255 preferences = bpy.context.preferences.addons['blenderkit'].preferences
256 layout.prop(preferences, "thumbnail_use_gpu")
258 def execute(self, context):
259 start_thumbnailer(self, context)
260 return {'FINISHED'}
262 def invoke(self, context, event):
263 wm = context.window_manager
264 if bpy.data.filepath == '':
265 ui_panels.ui_message(
266 title = "Can't render thumbnail",
267 message = "please save your file first")
269 return {'FINISHED'}
271 return wm.invoke_props_dialog(self)
274 class GenerateMaterialThumbnailOperator(bpy.types.Operator):
275 """Tooltip"""
276 bl_idname = "object.blenderkit_material_thumbnail"
277 bl_label = "BlenderKit Material Thumbnail Generator"
278 bl_options = {'REGISTER', 'INTERNAL'}
280 @classmethod
281 def poll(cls, context):
282 return bpy.context.view_layer.objects.active is not None
284 def check(self, context):
285 return True
287 def draw(self, context):
288 layout = self.layout
289 props = bpy.context.active_object.active_material.blenderkit
290 layout.prop(props, 'thumbnail_generator_type')
291 layout.prop(props, 'thumbnail_scale')
292 layout.prop(props, 'thumbnail_background')
293 if props.thumbnail_background:
294 layout.prop(props, 'thumbnail_background_lightness')
295 layout.prop(props, 'thumbnail_resolution')
296 layout.prop(props, 'thumbnail_samples')
297 layout.prop(props, 'thumbnail_denoising')
298 layout.prop(props, 'adaptive_subdivision')
299 preferences = bpy.context.preferences.addons['blenderkit'].preferences
300 layout.prop(preferences, "thumbnail_use_gpu")
302 def execute(self, context):
303 start_material_thumbnailer(self, context)
305 return {'FINISHED'}
307 def invoke(self, context, event):
308 wm = context.window_manager
309 return wm.invoke_props_dialog(self)
312 def register_thumbnailer():
313 bpy.utils.register_class(GenerateThumbnailOperator)
314 bpy.utils.register_class(GenerateMaterialThumbnailOperator)
317 def unregister_thumbnailer():
318 bpy.utils.unregister_class(GenerateThumbnailOperator)
319 bpy.utils.unregister_class(GenerateMaterialThumbnailOperator)