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