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