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    asset_inspector = reload(asset_inspector)
24    paths = reload(paths)
25    utils = reload(utils)
26    bg_blender = reload(bg_blender)
27    autothumb = reload(autothumb)
28    version_checker = reload(version_checker)
29    search = reload(search)
30    ui_panels = reload(ui_panels)
31    ui = reload(ui)
32    overrides = reload(overrides)
33    colors = reload(colors)
34    rerequests = reload(rerequests)
35else:
36    from blenderkit import asset_inspector, paths, utils, bg_blender, autothumb, version_checker, search, ui_panels, ui, \
37        overrides, colors, rerequests
38
39import tempfile, os, subprocess, json, re
40
41import bpy
42import requests
43import threading
44
45BLENDERKIT_EXPORT_DATA_FILE = "data.json"
46
47from bpy.props import (  # TODO only keep the ones actually used when cleaning
48    EnumProperty,
49    BoolProperty,
50    StringProperty,
51)
52from bpy.types import (
53    Operator,
54    Panel,
55    AddonPreferences,
56    PropertyGroup,
57    UIList
58)
59
60
61def comma2array(text):
62    commasep = text.split(',')
63    ar = []
64    for i, s in enumerate(commasep):
65        s = s.strip()
66        if s != '':
67            ar.append(s)
68    return ar
69
70
71def get_app_version():
72    ver = bpy.app.version
73    return '%i.%i.%i' % (ver[0], ver[1], ver[2])
74
75
76def add_version(data):
77    app_version = get_app_version()
78    addon_version = version_checker.get_addon_version()
79    data["sourceAppName"] = "blender"
80    data["sourceAppVersion"] = app_version
81    data["addonVersion"] = addon_version
82
83
84
85
86def write_to_report(props, text):
87    props.report = props.report + text + '\n'
88
89
90def get_missing_data_model(props):
91    props.report = ''
92    autothumb.update_upload_model_preview(None, None)
93
94    if props.name == '':
95        write_to_report(props, 'Set model name')
96    # if props.tags == '':
97    #     write_to_report(props, 'Write at least 3 tags')
98    if not props.has_thumbnail:
99        write_to_report(props, 'Add thumbnail:')
100
101        props.report += props.thumbnail_generating_state + '\n'
102    if props.engine == 'NONE':
103        write_to_report(props, 'Set at least one rendering/output engine')
104    if not any(props.dimensions):
105        write_to_report(props, 'Run autotags operator or fill in dimensions manually')
106
107
108def get_missing_data_scene(props):
109    props.report = ''
110    autothumb.update_upload_model_preview(None, None)
111
112    if props.name == '':
113        write_to_report(props, 'Set scene name')
114    # if props.tags == '':
115    #     write_to_report(props, 'Write at least 3 tags')
116    if not props.has_thumbnail:
117        write_to_report(props, 'Add thumbnail:')
118
119        props.report += props.thumbnail_generating_state + '\n'
120    if props.engine == 'NONE':
121        write_to_report(props, 'Set at least one rendering/output engine')
122
123
124def get_missing_data_material(props):
125    props.report = ''
126    autothumb.update_upload_material_preview(None, None)
127    if props.name == '':
128        write_to_report(props, 'Set material name')
129    # if props.tags == '':
130    #     write_to_report(props, 'Write at least 3 tags')
131    if not props.has_thumbnail:
132        write_to_report(props, 'Add thumbnail:')
133        props.report += props.thumbnail_generating_state
134    if props.engine == 'NONE':
135        write_to_report(props, 'Set rendering/output engine')
136
137
138def get_missing_data_brush(props):
139    autothumb.update_upload_brush_preview(None, None)
140    props.report = ''
141    if props.name == '':
142        write_to_report(props, 'Set brush name')
143    # if props.tags == '':
144    #     write_to_report(props, 'Write at least 3 tags')
145    if not props.has_thumbnail:
146        write_to_report(props, 'Add thumbnail:')
147        props.report += props.thumbnail_generating_state
148
149
150def sub_to_camel(content):
151    replaced = re.sub(r"_.",
152                      lambda m: m.group(0)[1].upper(), content)
153    return (replaced)
154
155
156def camel_to_sub(content):
157    replaced = re.sub(r"[A-Z]", lambda m: '_' + m.group(0).lower(), content)
158    return replaced
159
160
161def get_upload_data(self, context, asset_type):
162    user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
163    api_key = user_preferences.api_key
164
165    export_data = {
166        "type": asset_type,
167    }
168    upload_params = {}
169    if asset_type == 'MODEL':
170        # Prepare to save the file
171        mainmodel = utils.get_active_model()
172
173        props = mainmodel.blenderkit
174
175        obs = utils.get_hierarchy(mainmodel)
176        obnames = []
177        for ob in obs:
178            obnames.append(ob.name)
179        export_data["models"] = obnames
180        export_data["thumbnail_path"] = bpy.path.abspath(props.thumbnail)
181
182        eval_path_computing = "bpy.data.objects['%s'].blenderkit.uploading" % mainmodel.name
183        eval_path_state = "bpy.data.objects['%s'].blenderkit.upload_state" % mainmodel.name
184        eval_path = "bpy.data.objects['%s']" % mainmodel.name
185
186        engines = [props.engine.lower()]
187        if props.engine1 != 'NONE':
188            engines.append(props.engine1.lower())
189        if props.engine2 != 'NONE':
190            engines.append(props.engine2.lower())
191        if props.engine3 != 'NONE':
192            engines.append(props.engine3.lower())
193        if props.engine == 'OTHER':
194            engines.append(props.engine_other.lower())
195
196        style = props.style.lower()
197        # if style == 'OTHER':
198        #     style = props.style_other.lower()
199
200        pl_dict = {'FINISHED': 'finished', 'TEMPLATE': 'template'}
201
202        upload_data = {
203            "assetType": 'model',
204
205        }
206        upload_params = {
207            "productionLevel": props.production_level.lower(),
208            "model_style": style,
209            "engines": engines,
210            "modifiers": comma2array(props.modifiers),
211            "materials": comma2array(props.materials),
212            "shaders": comma2array(props.shaders),
213            "uv": props.uv,
214            "dimensionX": round(props.dimensions[0], 4),
215            "dimensionY": round(props.dimensions[1], 4),
216            "dimensionZ": round(props.dimensions[2], 4),
217
218            "boundBoxMinX": round(props.bbox_min[0], 4),
219            "boundBoxMinY": round(props.bbox_min[1], 4),
220            "boundBoxMinZ": round(props.bbox_min[2], 4),
221
222            "boundBoxMaxX": round(props.bbox_max[0], 4),
223            "boundBoxMaxY": round(props.bbox_max[1], 4),
224            "boundBoxMaxZ": round(props.bbox_max[2], 4),
225
226            "animated": props.animated,
227            "rig": props.rig,
228            "simulation": props.simulation,
229            "purePbr": props.pbr,
230            "faceCount": props.face_count,
231            "faceCountRender": props.face_count_render,
232            "manifold": props.manifold,
233            "objectCount": props.object_count,
234
235            "procedural": props.is_procedural,
236            "nodeCount": props.node_count,
237            "textureCount": props.texture_count,
238            "megapixels": round(props.total_megapixels/ 1000000),
239            # "scene": props.is_scene,
240        }
241        if props.use_design_year:
242            upload_params["designYear"] = props.design_year
243        if props.condition != 'UNSPECIFIED':
244            upload_params["condition"] = props.condition.lower()
245        if props.pbr:
246            pt = props.pbr_type
247            pt = pt.lower()
248            upload_params["pbrType"] = pt
249
250        if props.texture_resolution_max > 0:
251            upload_params["textureResolutionMax"] = props.texture_resolution_max
252            upload_params["textureResolutionMin"] = props.texture_resolution_min
253        if props.mesh_poly_type != 'OTHER':
254            upload_params["meshPolyType"] = props.mesh_poly_type.lower()  # .replace('_',' ')
255
256        optional_params = ['manufacturer', 'designer', 'design_collection', 'design_variant']
257        for p in optional_params:
258            if eval('props.%s' % p) != '':
259                upload_params[sub_to_camel(p)] = eval('props.%s' % p)
260
261    if asset_type == 'SCENE':
262        # Prepare to save the file
263        s = bpy.context.scene
264
265        props = s.blenderkit
266
267        export_data["scene"] = s.name
268        export_data["thumbnail_path"] = bpy.path.abspath(props.thumbnail)
269
270        eval_path_computing = "bpy.data.scenes['%s'].blenderkit.uploading" % s.name
271        eval_path_state = "bpy.data.scenes['%s'].blenderkit.upload_state" % s.name
272        eval_path = "bpy.data.scenes['%s']" % s.name
273
274        engines = [props.engine.lower()]
275        if props.engine1 != 'NONE':
276            engines.append(props.engine1.lower())
277        if props.engine2 != 'NONE':
278            engines.append(props.engine2.lower())
279        if props.engine3 != 'NONE':
280            engines.append(props.engine3.lower())
281        if props.engine == 'OTHER':
282            engines.append(props.engine_other.lower())
283
284        style = props.style.lower()
285        # if style == 'OTHER':
286        #     style = props.style_other.lower()
287
288        pl_dict = {'FINISHED': 'finished', 'TEMPLATE': 'template'}
289
290        upload_data = {
291            "assetType": 'scene',
292
293        }
294        upload_params = {
295            "productionLevel": props.production_level.lower(),
296            "model_style": style,
297            "engines": engines,
298            "modifiers": comma2array(props.modifiers),
299            "materials": comma2array(props.materials),
300            "shaders": comma2array(props.shaders),
301            "uv": props.uv,
302
303            "animated": props.animated,
304            # "simulation": props.simulation,
305            "purePbr": props.pbr,
306            "faceCount": 1,  # props.face_count,
307            "faceCountRender": 1,  # props.face_count_render,
308            "objectCount": 1,  # props.object_count,
309
310            # "scene": props.is_scene,
311        }
312        if props.use_design_year:
313            upload_params["designYear"] = props.design_year
314        if props.condition != 'UNSPECIFIED':
315            upload_params["condition"] = props.condition.lower()
316        if props.pbr:
317            pt = props.pbr_type
318            pt = pt.lower()
319            upload_params["pbrType"] = pt
320
321        if props.texture_resolution_max > 0:
322            upload_params["textureResolutionMax"] = props.texture_resolution_max
323            upload_params["textureResolutionMin"] = props.texture_resolution_min
324        if props.mesh_poly_type != 'OTHER':
325            upload_params["meshPolyType"] = props.mesh_poly_type.lower()  # .replace('_',' ')
326
327    elif asset_type == 'MATERIAL':
328        mat = bpy.context.active_object.active_material
329        props = mat.blenderkit
330
331        # props.name = mat.name
332
333        export_data["material"] = str(mat.name)
334        export_data["thumbnail_path"] = bpy.path.abspath(props.thumbnail)
335        # mat analytics happen here, since they don't take up any time...
336        asset_inspector.check_material(props, mat)
337
338        eval_path_computing = "bpy.data.materials['%s'].blenderkit.uploading" % mat.name
339        eval_path_state = "bpy.data.materials['%s'].blenderkit.upload_state" % mat.name
340        eval_path = "bpy.data.materials['%s']" % mat.name
341
342        engine = props.engine
343        if engine == 'OTHER':
344            engine = props.engine_other
345        engine = engine.lower()
346        style = props.style.lower()
347        # if style == 'OTHER':
348        #     style = props.style_other.lower()
349
350        upload_data = {
351            "assetType": 'material',
352
353        }
354
355        upload_params = {
356            "material_style": style,
357            "engine": engine,
358            "shaders": comma2array(props.shaders),
359            "uv": props.uv,
360            "animated": props.animated,
361            "purePbr": props.pbr,
362            "textureSizeMeters": props.texture_size_meters,
363            "procedural": props.is_procedural,
364            "nodeCount": props.node_count,
365            "textureCount": props.texture_count,
366            "megapixels": round(props.total_megapixels/ 1000000),
367
368        }
369
370        if props.pbr:
371            upload_params["pbrType"] = props.pbr_type.lower()
372
373        if props.texture_resolution_max > 0:
374            upload_params["textureResolutionMax"] = props.texture_resolution_max
375            upload_params["textureResolutionMin"] = props.texture_resolution_min
376
377    elif asset_type == 'BRUSH':
378        brush = utils.get_active_brush()
379
380        props = brush.blenderkit
381        # props.name = brush.name
382
383        export_data["brush"] = str(brush.name)
384        export_data["thumbnail_path"] = bpy.path.abspath(brush.icon_filepath)
385
386        eval_path_computing = "bpy.data.brushes['%s'].blenderkit.uploading" % brush.name
387        eval_path_state = "bpy.data.brushes['%s'].blenderkit.upload_state" % brush.name
388        eval_path = "bpy.data.brushes['%s']" % brush.name
389
390        # mat analytics happen here, since they don't take up any time...
391
392        brush_type = ''
393        if bpy.context.sculpt_object is not None:
394            brush_type = 'sculpt'
395
396        elif bpy.context.image_paint_object:  # could be just else, but for future p
397            brush_type = 'texture_paint'
398
399        upload_params = {
400            "mode": brush_type,
401        }
402
403        upload_data = {
404            "assetType": 'brush',
405        }
406
407    elif asset_type == 'TEXTURE':
408        style = props.style
409        # if style == 'OTHER':
410        #     style = props.style_other
411
412        upload_data = {
413            "assetType": 'texture',
414
415        }
416        upload_params = {
417            "style": style,
418            "animated": props.animated,
419            "purePbr": props.pbr,
420            "resolution": props.resolution,
421        }
422        if props.pbr:
423            pt = props.pbr_type
424            pt = pt.lower()
425            upload_data["pbrType"] = pt
426
427    add_version(upload_data)
428
429    upload_data["name"] = props.name
430    upload_data["description"] = props.description
431    upload_data["tags"] = comma2array(props.tags)
432    if props.category == '':
433        upload_data["category"] = asset_type.lower()
434    else:
435        upload_data["category"] = props.category
436    if props.subcategory != '':
437        upload_data["category"] = props.subcategory
438    upload_data["license"] = props.license
439    upload_data["isFree"] = props.is_free
440    upload_data["isPrivate"] = props.is_private == 'PRIVATE'
441    upload_data["token"] = user_preferences.api_key
442
443    if props.asset_base_id != '':
444        upload_data['assetBaseId'] = props.asset_base_id
445        upload_data['id'] = props.id
446
447    upload_data['parameters'] = upload_params
448
449    return export_data, upload_data, eval_path_computing, eval_path_state, eval_path, props
450
451
452def verification_status_change_thread(asset_id, state, api_key):
453    upload_data = {
454        "verificationStatus": state
455    }
456    url = paths.get_api_url() + 'assets/' + str(asset_id) + '/'
457    headers = utils.get_headers(api_key)
458    try:
459        r = rerequests.patch(url, json=upload_data, headers=headers, verify=True)  # files = files,
460    except requests.exceptions.RequestException as e:
461        print(e)
462        return {'CANCELLED'}
463    return {'FINISHED'}
464
465
466def get_upload_location(props):
467    scene = bpy.context.scene
468    ui_props = scene.blenderkitUI
469    if ui_props.asset_type == 'MODEL':
470        if bpy.context.view_layer.objects.active is not None:
471            ob = utils.get_active_model()
472            return ob.location
473    if ui_props.asset_type == 'SCENE':
474        return None
475    elif ui_props.asset_type == 'MATERIAL':
476        if bpy.context.view_layer.objects.active is not None and bpy.context.active_object.active_material is not None:
477            return bpy.context.active_object.location
478    elif ui_props.asset_type == 'TEXTURE':
479        return None
480    elif ui_props.asset_type == 'BRUSH':
481        return None
482    return None
483
484
485def check_storage_quota(props):
486    if props.is_private == 'PUBLIC':
487        return True
488
489    profile = bpy.context.window_manager.get('bkit profile')
490    if profile is None or profile.get('remainingPrivateQuota') is None:
491        preferences = bpy.context.preferences.addons['blenderkit'].preferences
492        adata = search.request_profile(preferences.api_key)
493        if adata is None:
494            props.report = 'Please log-in first.'
495            return False
496        search.write_profile(adata)
497        profile = adata
498    quota = profile['user'].get('remainingPrivateQuota')
499    if quota is None or quota > 0:
500        return True
501    props.report = 'Private storage quota exceeded.'
502    return False
503
504
505def auto_fix(asset_type=''):
506    # this applies various procedures to ensure coherency in the database.
507    asset = utils.get_active_asset()
508    props = utils.get_upload_props()
509    if asset_type == 'MATERIAL':
510        overrides.ensure_eevee_transparency(asset)
511        asset.name = props.name
512
513
514def start_upload(self, context, asset_type, reupload, upload_set):
515    '''start upload process, by processing data'''
516
517    # fix the name first
518    utils.name_update()
519
520    props = utils.get_upload_props()
521    storage_quota_ok = check_storage_quota(props)
522    if not storage_quota_ok:
523        self.report({'ERROR_INVALID_INPUT'}, props.report)
524        return {'CANCELLED'}
525
526    location = get_upload_location(props)
527    props.upload_state = 'preparing upload'
528
529    auto_fix(asset_type=asset_type)
530
531    # do this for fixing long tags in some upload cases
532    props.tags = props.tags[:]
533
534    props.name = props.name.strip()
535    # TODO  move this to separate function
536    # check for missing metadata
537    if asset_type == 'MODEL':
538        get_missing_data_model(props)
539    if asset_type == 'SCENE':
540        get_missing_data_scene(props)
541    elif asset_type == 'MATERIAL':
542        get_missing_data_material(props)
543    elif asset_type == 'BRUSH':
544        get_missing_data_brush(props)
545
546    if props.report != '':
547        self.report({'ERROR_INVALID_INPUT'}, props.report)
548        return {'CANCELLED'}
549
550    if not reupload:
551        props.asset_base_id = ''
552        props.id = ''
553    export_data, upload_data, eval_path_computing, eval_path_state, eval_path, props = get_upload_data(self, context,
554                                                                                                       asset_type)
555    # utils.pprint(upload_data)
556    upload_data['parameters'] = utils.dict_to_params(
557        upload_data['parameters'])  # weird array conversion only for upload, not for tooltips.
558
559    binary_path = bpy.app.binary_path
560    script_path = os.path.dirname(os.path.realpath(__file__))
561    basename, ext = os.path.splitext(bpy.data.filepath)
562    # if not basename:
563    #     basename = os.path.join(basename, "temp")
564    if not ext:
565        ext = ".blend"
566    tempdir = tempfile.mkdtemp()
567    source_filepath = os.path.join(tempdir, "export_blenderkit" + ext)
568    clean_file_path = paths.get_clean_filepath()
569    data = {
570        'clean_file_path': clean_file_path,
571        'source_filepath': source_filepath,
572        'temp_dir': tempdir,
573        'export_data': export_data,
574        'upload_data': upload_data,
575        'debug_value': bpy.app.debug_value,
576        'upload_set': upload_set,
577    }
578    datafile = os.path.join(tempdir, BLENDERKIT_EXPORT_DATA_FILE)
579
580    # check if thumbnail exists:
581    if 'THUMBNAIL' in upload_set:
582        if not os.path.exists(export_data["thumbnail_path"]):
583            props.upload_state = 'Thumbnail not found'
584            props.uploading = False
585            return {'CANCELLED'}
586
587    # first upload metadata to server, so it can be saved inside the current file
588    url = paths.get_api_url() + 'assets/'
589
590    headers = utils.get_headers(upload_data['token'])
591
592    # upload_data['license'] = 'ovejajojo'
593    json_metadata = upload_data  # json.dumps(upload_data, ensure_ascii=False).encode('utf8')
594    global reports
595    if props.asset_base_id == '':
596        try:
597            r = rerequests.post(url, json=json_metadata, headers=headers, verify=True, immediate=True)  # files = files,
598            ui.add_report('uploaded metadata')
599            utils.p(r.text)
600        except requests.exceptions.RequestException as e:
601            print(e)
602            props.upload_state = str(e)
603            props.uploading = False
604            return {'CANCELLED'}
605
606    else:
607        url += props.id + '/'
608        try:
609            if 'MAINFILE' in upload_set:
610                json_metadata["verificationStatus"] = "uploading"
611            r = rerequests.put(url, json=json_metadata, headers=headers, verify=True, immediate=True)  # files = files,
612            ui.add_report('uploaded metadata')
613            # parse the request
614            # print('uploaded metadata')
615            # print(r.text)
616        except requests.exceptions.RequestException as e:
617            print(e)
618            props.upload_state = str(e)
619            props.uploading = False
620            return {'CANCELLED'}
621
622    # props.upload_state = 'step 1'
623    if upload_set == ['METADATA']:
624        props.uploading = False
625        props.upload_state = 'upload finished successfully'
626        return {'FINISHED'}
627    try:
628        rj = r.json()
629        utils.pprint(rj)
630        # if r.status_code not in (200, 201):
631        #     if r.status_code == 401:
632        #         ui.add_report(r.detail, 5, colors.RED)
633        #     return {'CANCELLED'}
634        if props.asset_base_id == '':
635            props.asset_base_id = rj['assetBaseId']
636            props.id = rj['id']
637        upload_data['assetBaseId'] = props.asset_base_id
638        upload_data['id'] = props.id
639
640        # bpy.ops.wm.save_mainfile()
641        # bpy.ops.wm.save_as_mainfile(filepath=filepath, compress=False, copy=True)
642
643        props.uploading = True
644        # save a copy of actual scene but don't interfere with the users models
645        bpy.ops.wm.save_as_mainfile(filepath=source_filepath, compress=False, copy=True)
646
647        with open(datafile, 'w') as s:
648            json.dump(data, s)
649
650        proc = subprocess.Popen([
651            binary_path,
652            "--background",
653            "-noaudio",
654            clean_file_path,
655            "--python", os.path.join(script_path, "upload_bg.py"),
656            "--", datafile  # ,filepath, tempdir
657        ], bufsize=5000, stdout=subprocess.PIPE, stdin=subprocess.PIPE)
658
659        bg_blender.add_bg_process(eval_path_computing=eval_path_computing, eval_path_state=eval_path_state,
660                                  eval_path=eval_path, process_type='UPLOAD', process=proc, location=location)
661
662    except Exception as e:
663        props.upload_state = str(e)
664        props.uploading = False
665        print(e)
666        return {'CANCELLED'}
667
668    return {'FINISHED'}
669
670
671asset_types = (
672    ('MODEL', 'Model', 'set of objects'),
673    ('SCENE', 'Scene', 'scene'),
674    ('MATERIAL', 'Material', 'any .blend Material'),
675    ('TEXTURE', 'Texture', 'a texture, or texture set'),
676    ('BRUSH', 'Brush', 'brush, can be any type of blender brush'),
677    ('ADDON', 'Addon', 'addnon'),
678)
679
680
681class UploadOperator(Operator):
682    """Tooltip"""
683    bl_idname = "object.blenderkit_upload"
684    bl_description = "Upload or re-upload asset + thumbnail + metadata"
685
686    bl_label = "BlenderKit asset upload"
687    bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}
688
689    # type of upload - model, material, textures, e.t.c.
690    asset_type: EnumProperty(
691        name="Type",
692        items=asset_types,
693        description="Type of upload",
694        default="MODEL",
695    )
696
697    reupload: BoolProperty(
698        name="reupload",
699        description="reupload but also draw so that it asks what to reupload",
700        default=False,
701        options={'SKIP_SAVE'}
702    )
703
704    metadata: BoolProperty(
705        name="metadata",
706        default=True,
707        options={'SKIP_SAVE'}
708    )
709
710    thumbnail: BoolProperty(
711        name="thumbnail",
712        default=False,
713        options={'SKIP_SAVE'}
714    )
715
716    main_file: BoolProperty(
717        name="main file",
718        default=False,
719        options={'SKIP_SAVE'}
720    )
721
722    @classmethod
723    def poll(cls, context):
724        return bpy.context.view_layer.objects.active is not None
725
726    def execute(self, context):
727        bpy.ops.object.blenderkit_auto_tags()
728        props = utils.get_upload_props()
729
730        # in case of name change, we have to reupload everything, since the name is stored in blender file,
731        # and is used for linking to scene
732        if props.name_changed:
733            # TODO: this needs to be replaced with new double naming scheme (metadata vs blend data)
734            # print('has to reupload whole data, name has changed.')
735            self.main_file = True
736            props.name_changed = False
737
738        upload_set = []
739        if not self.reupload:
740            upload_set = ['METADATA', 'THUMBNAIL', 'MAINFILE']
741        else:
742            if self.metadata:
743                upload_set.append('METADATA')
744            if self.thumbnail:
745                upload_set.append('THUMBNAIL')
746            if self.main_file:
747                upload_set.append('MAINFILE')
748
749        result = start_upload(self, context, self.asset_type, self.reupload, upload_set)
750
751        return result
752
753
754
755    def draw(self, context):
756        props = utils.get_upload_props()
757        layout = self.layout
758
759        if self.reupload:
760            # layout.prop(self, 'metadata')
761            layout.prop(self, 'main_file')
762            layout.prop(self, 'thumbnail')
763
764        if props.asset_base_id != '' and not self.reupload:
765            layout.label(text="Really upload as new? ")
766            layout.label(text="Do this only when you create a new asset from an old one.")
767            layout.label(text="For updates of thumbnail or model use reupload.")
768
769        if props.is_private == 'PUBLIC':
770            utils.label_multiline(layout, text='public assets are validated several hours'
771                                                   ' or days after upload. Remember always to '
772                                                    'test download your asset to a clean file'
773                                                   ' to see if it uploaded correctly.'
774                                      , width=300)
775
776    def invoke(self, context, event):
777        props = utils.get_upload_props()
778
779        if not utils.user_logged_in():
780            ui_panels.draw_not_logged_in(self, message = 'To upload assets you need to login/signup.')
781            return {'CANCELLED'}
782
783        if props.is_private == 'PUBLIC':
784            return context.window_manager.invoke_props_dialog(self)
785        else:
786            return self.execute(context)
787
788
789class AssetVerificationStatusChange(Operator):
790    """Change verification status"""
791    bl_idname = "object.blenderkit_change_status"
792    bl_description = "Change asset ststus"
793    bl_label = "Change verification status"
794    bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}
795
796    # type of upload - model, material, textures, e.t.c.
797    asset_id: StringProperty(
798        name="asset id",
799    )
800
801    state: StringProperty(
802        name="verification_status",
803        default='uploaded'
804    )
805
806    @classmethod
807    def poll(cls, context):
808        return True
809
810    def draw(self, context):
811        layout = self.layout
812        # if self.state == 'deleted':
813        layout.label(text='Really delete asset from BlenderKit online storage?')
814        # layout.prop(self, 'state')
815
816    def execute(self, context):
817        preferences = bpy.context.preferences.addons['blenderkit'].preferences
818
819        if not bpy.context.scene['search results']:
820            return {'CANCELLED'};
821        # update status in search results for validator's clarity
822        sr = bpy.context.scene['search results']
823        sro = bpy.context.scene['search results orig']['results']
824
825        for r in sr:
826            if r['id'] == self.asset_id:
827                r['verificationStatus'] = self.state
828        for r in sro:
829            if r['id'] == self.asset_id:
830                r['verificationStatus'] = self.state
831
832
833        thread = threading.Thread(target=verification_status_change_thread,
834                                  args=(self.asset_id, self.state, preferences.api_key))
835        thread.start()
836        return {'FINISHED'}
837
838    def invoke(self, context, event):
839        # print(self.state)
840        if self.state == 'deleted':
841            wm = context.window_manager
842            return wm.invoke_props_dialog(self)
843        return {'RUNNING_MODAL'}
844
845
846def register_upload():
847    bpy.utils.register_class(UploadOperator)
848    bpy.utils.register_class(AssetVerificationStatusChange)
849
850
851def unregister_upload():
852    bpy.utils.unregister_class(UploadOperator)
853    bpy.utils.unregister_class(AssetVerificationStatusChange)
854