diff options
author | Vilem Duha <vilem.duha@gmail.com> | 2021-11-22 12:34:42 +0300 |
---|---|---|
committer | Vilem Duha <vilem.duha@gmail.com> | 2021-11-22 12:34:42 +0300 |
commit | 44bb7785ca78177c56de2203de9e03ba7a2513f6 (patch) | |
tree | 1710f7b74a52c7dd5534974334ce43627b6a3771 /blenderkit/upload.py | |
parent | 93d922c0d63d7bbdaf19faa6490ce8c6ccfe3f25 (diff) |
Moving BlenderKit to new repository on GitHub
BlenderKit addon will be here since now:
https://github.com/BlenderKit/blenderkit
And all release notes , docs and information will be on BlenderKit website:
www.blenderkit.com
The move happens since Blender Foundation ended the commercial addon offering for all commercial addons.
This means a bit less comfort for our users, but also many new possibilities!
Diffstat (limited to 'blenderkit/upload.py')
-rw-r--r-- | blenderkit/upload.py | 1387 |
1 files changed, 0 insertions, 1387 deletions
diff --git a/blenderkit/upload.py b/blenderkit/upload.py deleted file mode 100644 index 3d8b705b..00000000 --- a/blenderkit/upload.py +++ /dev/null @@ -1,1387 +0,0 @@ -# ##### BEGIN GPL LICENSE BLOCK ##### -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ##### END GPL LICENSE BLOCK ##### - - -from blenderkit import asset_inspector, paths, utils, bg_blender, autothumb, version_checker, search, ui_panels, ui, \ - overrides, colors, rerequests, categories, upload_bg, tasks_queue, image_utils, asset_bar_op, reports - -import tempfile, os, subprocess, json, re - -import bpy -import requests -import threading -import sys - -BLENDERKIT_EXPORT_DATA_FILE = "data.json" - -from bpy.props import ( # TODO only keep the ones actually used when cleaning - EnumProperty, - BoolProperty, - StringProperty, -) -from bpy.types import ( - Operator, - Panel, - AddonPreferences, - PropertyGroup, - UIList -) - -licenses = ( - ('royalty_free', 'Royalty Free', 'royalty free commercial license'), - ('cc_zero', 'Creative Commons Zero', 'Creative Commons Zero'), -) - - -def comma2array(text): - commasep = text.split(',') - ar = [] - for i, s in enumerate(commasep): - s = s.strip() - if s != '': - ar.append(s) - return ar - - -def get_app_version(): - ver = bpy.app.version - return '%i.%i.%i' % (ver[0], ver[1], ver[2]) - - -def add_version(data): - app_version = get_app_version() - addon_version = version_checker.get_addon_version() - data["sourceAppName"] = "blender" - data["sourceAppVersion"] = app_version - data["addonVersion"] = addon_version - - -def write_to_report(props, text): - props.report = props.report + ' - ' + text + '\n\n' - - -def check_missing_data_model(props): - autothumb.update_upload_model_preview(None, None) - if not props.has_thumbnail: - write_to_report(props, 'Add thumbnail:') - props.report += props.thumbnail_generating_state + '\n' - if props.engine == 'NONE': - write_to_report(props, 'Set at least one rendering/output engine') - - # if not any(props.dimensions): - # write_to_report(props, 'Run autotags operator or fill in dimensions manually') - - -def check_missing_data_scene(props): - autothumb.update_upload_model_preview(None, None) - if not props.has_thumbnail: - write_to_report(props, 'Add thumbnail:') - props.report += props.thumbnail_generating_state + '\n' - if props.engine == 'NONE': - write_to_report(props, 'Set at least one rendering/output engine') - - -def check_missing_data_material(props): - autothumb.update_upload_material_preview(None, None) - if not props.has_thumbnail: - write_to_report(props, 'Add thumbnail:') - props.report += props.thumbnail_generating_state - if props.engine == 'NONE': - write_to_report(props, 'Set rendering/output engine') - - -def check_missing_data_brush(props): - autothumb.update_upload_brush_preview(None, None) - if not props.has_thumbnail: - write_to_report(props, 'Add thumbnail:') - props.report += props.thumbnail_generating_state - - -def check_missing_data(asset_type, props): - ''' - checks if user did everything allright for particular assets and notifies him back if not. - Parameters - ---------- - asset_type - props - - Returns - ------- - - ''' - props.report = '' - - if props.name == '': - write_to_report(props, f'Set {asset_type.lower()} name.\n' - f'It has to be in English and \n' - f'can not be longer than 40 letters.\n') - if len(props.name) > 40: - write_to_report(props, f'The name is too long. maximum is 40 letters') - - if props.is_private == 'PUBLIC': - - if len(props.description) < 20: - write_to_report(props, "The description is too short or empty. \n" - "Please write a description that describes \n " - "your asset as good as possible.\n" - "Description helps to bring your asset up\n in relevant search results. ") - if props.tags == '': - write_to_report(props, 'Write at least 3 tags.\n' - 'Tags help to bring your asset up in relevant search results.') - - if asset_type == 'MODEL': - check_missing_data_model(props) - if asset_type == 'SCENE': - check_missing_data_scene(props) - elif asset_type == 'MATERIAL': - check_missing_data_material(props) - elif asset_type == 'BRUSH': - check_missing_data_brush(props) - - if props.report != '': - props.report = f'Please fix these issues before {props.is_private.lower()} upload:\n\n' + props.report - - -def sub_to_camel(content): - replaced = re.sub(r"_.", - lambda m: m.group(0)[1].upper(), content) - return (replaced) - - -def camel_to_sub(content): - replaced = re.sub(r"[A-Z]", lambda m: '_' + m.group(0).lower(), content) - return replaced - - -def get_upload_data(caller=None, context=None, asset_type=None): - ''' - works though metadata from addom props and prepares it for upload to dicts. - Parameters - ---------- - caller - upload operator or none - context - context - asset_type - asset type in capitals (blender enum) - - Returns - ------- - export_ddta- all extra data that the process needs to upload and communicate with UI from a thread. - - eval_path_computing - string path to UI prop that denots if upload is still running - - eval_path_state - string path to UI prop that delivers messages about upload to ui - - eval_path - path to object holding upload data to be able to access it with various further commands - - models - in case of model upload, list of objects - - thumbnail_path - path to thumbnail file - - upload_data - asset_data generated from the ui properties - - ''' - user_preferences = bpy.context.preferences.addons['blenderkit'].preferences - api_key = user_preferences.api_key - - export_data = { - # "type": asset_type, - } - upload_params = {} - if asset_type == 'MODEL': - # Prepare to save the file - mainmodel = utils.get_active_model() - - props = mainmodel.blenderkit - - obs = utils.get_hierarchy(mainmodel) - obnames = [] - for ob in obs: - obnames.append(ob.name) - export_data["models"] = obnames - export_data["thumbnail_path"] = bpy.path.abspath(props.thumbnail) - - eval_path_computing = "bpy.data.objects['%s'].blenderkit.uploading" % mainmodel.name - eval_path_state = "bpy.data.objects['%s'].blenderkit.upload_state" % mainmodel.name - eval_path = "bpy.data.objects['%s']" % mainmodel.name - - engines = [props.engine.lower()] - if props.engine1 != 'NONE': - engines.append(props.engine1.lower()) - if props.engine2 != 'NONE': - engines.append(props.engine2.lower()) - if props.engine3 != 'NONE': - engines.append(props.engine3.lower()) - if props.engine == 'OTHER': - engines.append(props.engine_other.lower()) - - style = props.style.lower() - # if style == 'OTHER': - # style = props.style_other.lower() - - pl_dict = {'FINISHED': 'finished', 'TEMPLATE': 'template'} - - upload_data = { - "assetType": 'model', - - } - upload_params = { - "productionLevel": props.production_level.lower(), - "model_style": style, - "engines": engines, - "modifiers": comma2array(props.modifiers), - "materials": comma2array(props.materials), - "shaders": comma2array(props.shaders), - "uv": props.uv, - "dimensionX": round(props.dimensions[0], 4), - "dimensionY": round(props.dimensions[1], 4), - "dimensionZ": round(props.dimensions[2], 4), - - "boundBoxMinX": round(props.bbox_min[0], 4), - "boundBoxMinY": round(props.bbox_min[1], 4), - "boundBoxMinZ": round(props.bbox_min[2], 4), - - "boundBoxMaxX": round(props.bbox_max[0], 4), - "boundBoxMaxY": round(props.bbox_max[1], 4), - "boundBoxMaxZ": round(props.bbox_max[2], 4), - - "animated": props.animated, - "rig": props.rig, - "simulation": props.simulation, - "purePbr": props.pbr, - "faceCount": props.face_count, - "faceCountRender": props.face_count_render, - "manifold": props.manifold, - "objectCount": props.object_count, - - "procedural": props.is_procedural, - "nodeCount": props.node_count, - "textureCount": props.texture_count, - "megapixels": round(props.total_megapixels / 1000000), - # "scene": props.is_scene, - } - if props.use_design_year: - upload_params["designYear"] = props.design_year - if props.condition != 'UNSPECIFIED': - upload_params["condition"] = props.condition.lower() - if props.pbr: - pt = props.pbr_type - pt = pt.lower() - upload_params["pbrType"] = pt - - if props.texture_resolution_max > 0: - upload_params["textureResolutionMax"] = props.texture_resolution_max - upload_params["textureResolutionMin"] = props.texture_resolution_min - if props.mesh_poly_type != 'OTHER': - upload_params["meshPolyType"] = props.mesh_poly_type.lower() # .replace('_',' ') - - optional_params = ['manufacturer', 'designer', 'design_collection', 'design_variant'] - for p in optional_params: - if eval('props.%s' % p) != '': - upload_params[sub_to_camel(p)] = eval('props.%s' % p) - - if asset_type == 'SCENE': - # Prepare to save the file - s = bpy.context.scene - - props = s.blenderkit - - export_data["scene"] = s.name - export_data["thumbnail_path"] = bpy.path.abspath(props.thumbnail) - - eval_path_computing = "bpy.data.scenes['%s'].blenderkit.uploading" % s.name - eval_path_state = "bpy.data.scenes['%s'].blenderkit.upload_state" % s.name - eval_path = "bpy.data.scenes['%s']" % s.name - - engines = [props.engine.lower()] - if props.engine1 != 'NONE': - engines.append(props.engine1.lower()) - if props.engine2 != 'NONE': - engines.append(props.engine2.lower()) - if props.engine3 != 'NONE': - engines.append(props.engine3.lower()) - if props.engine == 'OTHER': - engines.append(props.engine_other.lower()) - - style = props.style.lower() - # if style == 'OTHER': - # style = props.style_other.lower() - - pl_dict = {'FINISHED': 'finished', 'TEMPLATE': 'template'} - - upload_data = { - "assetType": 'scene', - - } - upload_params = { - "productionLevel": props.production_level.lower(), - "model_style": style, - "engines": engines, - "modifiers": comma2array(props.modifiers), - "materials": comma2array(props.materials), - "shaders": comma2array(props.shaders), - "uv": props.uv, - - "animated": props.animated, - # "simulation": props.simulation, - "purePbr": props.pbr, - "faceCount": 1, # props.face_count, - "faceCountRender": 1, # props.face_count_render, - "objectCount": 1, # props.object_count, - - # "scene": props.is_scene, - } - if props.use_design_year: - upload_params["designYear"] = props.design_year - if props.condition != 'UNSPECIFIED': - upload_params["condition"] = props.condition.lower() - if props.pbr: - pt = props.pbr_type - pt = pt.lower() - upload_params["pbrType"] = pt - - if props.texture_resolution_max > 0: - upload_params["textureResolutionMax"] = props.texture_resolution_max - upload_params["textureResolutionMin"] = props.texture_resolution_min - if props.mesh_poly_type != 'OTHER': - upload_params["meshPolyType"] = props.mesh_poly_type.lower() # .replace('_',' ') - - elif asset_type == 'MATERIAL': - mat = bpy.context.active_object.active_material - props = mat.blenderkit - - # props.name = mat.name - - export_data["material"] = str(mat.name) - export_data["thumbnail_path"] = bpy.path.abspath(props.thumbnail) - # mat analytics happen here, since they don't take up any time... - asset_inspector.check_material(props, mat) - - eval_path_computing = "bpy.data.materials['%s'].blenderkit.uploading" % mat.name - eval_path_state = "bpy.data.materials['%s'].blenderkit.upload_state" % mat.name - eval_path = "bpy.data.materials['%s']" % mat.name - - engine = props.engine - if engine == 'OTHER': - engine = props.engine_other - engine = engine.lower() - style = props.style.lower() - # if style == 'OTHER': - # style = props.style_other.lower() - - upload_data = { - "assetType": 'material', - - } - - upload_params = { - "material_style": style, - "engine": engine, - "shaders": comma2array(props.shaders), - "uv": props.uv, - "animated": props.animated, - "purePbr": props.pbr, - "textureSizeMeters": props.texture_size_meters, - "procedural": props.is_procedural, - "nodeCount": props.node_count, - "textureCount": props.texture_count, - "megapixels": round(props.total_megapixels / 1000000), - - } - - if props.pbr: - upload_params["pbrType"] = props.pbr_type.lower() - - if props.texture_resolution_max > 0: - upload_params["textureResolutionMax"] = props.texture_resolution_max - upload_params["textureResolutionMin"] = props.texture_resolution_min - - elif asset_type == 'BRUSH': - brush = utils.get_active_brush() - - props = brush.blenderkit - # props.name = brush.name - - export_data["brush"] = str(brush.name) - export_data["thumbnail_path"] = bpy.path.abspath(brush.icon_filepath) - - eval_path_computing = "bpy.data.brushes['%s'].blenderkit.uploading" % brush.name - eval_path_state = "bpy.data.brushes['%s'].blenderkit.upload_state" % brush.name - eval_path = "bpy.data.brushes['%s']" % brush.name - - # mat analytics happen here, since they don't take up any time... - - brush_type = '' - if bpy.context.sculpt_object is not None: - brush_type = 'sculpt' - - elif bpy.context.image_paint_object: # could be just else, but for future p - brush_type = 'texture_paint' - - upload_params = { - "mode": brush_type, - } - - upload_data = { - "assetType": 'brush', - } - - elif asset_type == 'HDR': - ui_props = bpy.context.window_manager.blenderkitUI - - # imagename = ui_props.hdr_upload_image - image = ui_props.hdr_upload_image # bpy.data.images.get(imagename) - if not image: - return None, None - - props = image.blenderkit - - image_utils.analyze_image_is_true_hdr(image) - - # props.name = brush.name - base, ext = os.path.splitext(image.filepath) - thumb_path = base + '.jpg' - export_data["thumbnail_path"] = bpy.path.abspath(thumb_path) - - export_data["hdr"] = str(image.name) - export_data["hdr_filepath"] = str(bpy.path.abspath(image.filepath)) - # export_data["thumbnail_path"] = bpy.path.abspath(brush.icon_filepath) - - eval_path_computing = "bpy.data.images['%s'].blenderkit.uploading" % image.name - eval_path_state = "bpy.data.images['%s'].blenderkit.upload_state" % image.name - eval_path = "bpy.data.images['%s']" % image.name - - # mat analytics happen here, since they don't take up any time... - - upload_params = { - "textureResolutionMax": props.texture_resolution_max, - "trueHDR": props.true_hdr - } - - upload_data = { - "assetType": 'hdr', - } - - elif asset_type == 'TEXTURE': - style = props.style - # if style == 'OTHER': - # style = props.style_other - - upload_data = { - "assetType": 'texture', - - } - upload_params = { - "style": style, - "animated": props.animated, - "purePbr": props.pbr, - "resolution": props.resolution, - } - if props.pbr: - pt = props.pbr_type - pt = pt.lower() - upload_data["pbrType"] = pt - - add_version(upload_data) - - # caller can be upload operator, but also asset bar called from tooltip generator - if caller and caller.properties.main_file == True: - upload_data["name"] = props.name - upload_data["displayName"] = props.name - else: - upload_data["displayName"] = props.name - - upload_data["description"] = props.description - upload_data["tags"] = comma2array(props.tags) - # category is always only one value by a slug, that's why we go down to the lowest level and overwrite. - if props.category == '': - upload_data["category"] = asset_type.lower() - else: - upload_data["category"] = props.category - if props.subcategory != 'NONE': - upload_data["category"] = props.subcategory - if props.subcategory1 != 'NONE': - upload_data["category"] = props.subcategory1 - - upload_data["license"] = props.license - upload_data["isFree"] = props.is_free == 'FREE' - upload_data["isPrivate"] = props.is_private == 'PRIVATE' - upload_data["token"] = user_preferences.api_key - - upload_data['parameters'] = upload_params - - # if props.asset_base_id != '': - export_data['assetBaseId'] = props.asset_base_id - export_data['id'] = props.id - export_data['eval_path_computing'] = eval_path_computing - export_data['eval_path_state'] = eval_path_state - export_data['eval_path'] = eval_path - - return export_data, upload_data - - -def patch_individual_metadata(asset_id, metadata_dict, api_key): - upload_data = metadata_dict - url = paths.get_api_url() + 'assets/' + str(asset_id) + '/' - headers = utils.get_headers(api_key) - try: - r = rerequests.patch(url, json=upload_data, headers=headers, verify=True) # files = files, - except requests.exceptions.RequestException as e: - print(e) - return {'CANCELLED'} - return {'FINISHED'} - - -# class OBJECT_MT_blenderkit_fast_metadata_menu(bpy.types.Menu): -# bl_label = "Fast category change" -# bl_idname = "OBJECT_MT_blenderkit_fast_metadata_menu" -# -# def draw(self, context): -# layout = self.layout -# ui_props = context.window_manager.blenderkitUI -# -# # sr = bpy.context.window_manager['search results'] -# sr = bpy.context.window_manager['search results'] -# asset_data = sr[ui_props.active_index] -# categories = bpy.context.window_manager['bkit_categories'] -# wm = bpy.context.win -# for c in categories: -# if c['name'].lower() == asset_data['assetType']: -# for ch in c['children']: -# op = layout.operator('wm.blenderkit_fast_metadata', text = ch['name']) -# op = layout.operator('wm.blenderkit_fast_metadata', text = ch['name']) - - -def update_free_full(self, context): - if self.asset_type == 'material': - if self.free_full == 'FULL': - self.free_full = 'FREE' - ui_panels.ui_message(title="All BlenderKit materials are free", - message="Any material uploaded to BlenderKit is free." \ - " However, it can still earn money for the author," \ - " based on our fair share system. " \ - "Part of subscription is sent to artists based on usage by paying users.") - - -def can_edit_asset(active_index=-1, asset_data=None): - if active_index < 0 and not asset_data: - return False - profile = bpy.context.window_manager.get('bkit profile') - if profile is None: - return False - if utils.profile_is_validator(): - return True - if not asset_data: - sr = bpy.context.window_manager['search results'] - asset_data = dict(sr[active_index]) - if int(asset_data['author']['id']) == int(profile['user']['id']): - return True - return False - - -class FastMetadata(bpy.types.Operator): - """Edit metadata of the asset""" - bl_idname = "wm.blenderkit_fast_metadata" - bl_label = "Update metadata" - bl_options = {'REGISTER', 'UNDO', 'INTERNAL'} - - asset_id: StringProperty( - name="Asset Base Id", - description="Unique name of the asset (hidden)", - default="" - ) - asset_type: StringProperty( - name="Asset Type", - description="Asset Type", - default="" - ) - name: StringProperty( - name="Name", - description="Main name of the asset", - default="", - ) - description: StringProperty( - name="Description", - description="Description of the asset", - default="") - tags: StringProperty( - name="Tags", - description="List of tags, separated by commas (optional)", - default="", - ) - category: EnumProperty( - name="Category", - description="main category to put into", - items=categories.get_category_enums, - update=categories.update_category_enums - ) - subcategory: EnumProperty( - name="Subcategory", - description="main category to put into", - items=categories.get_subcategory_enums, - update=categories.update_subcategory_enums - ) - subcategory1: EnumProperty( - name="Subcategory", - description="main category to put into", - items=categories.get_subcategory1_enums - ) - license: EnumProperty( - items=licenses, - default='royalty_free', - description='License. Please read our help for choosing the right licenses', - ) - is_private: EnumProperty( - name="Thumbnail Style", - items=( - ('PRIVATE', 'Private', "You asset will be hidden to public. The private assets are limited by a quota."), - ('PUBLIC', 'Public', '"Your asset will go into the validation process automatically') - ), - description="If not marked private, your asset will go into the validation process automatically\n" - "Private assets are limited by quota", - default="PUBLIC", - ) - - free_full: EnumProperty( - name="Free or Full Plan", - items=( - ('FREE', 'Free', "You consent you want to release this asset as free for everyone"), - ('FULL', 'Full', 'Your asset will be in the full plan') - ), - description="Choose whether the asset should be free or in the Full Plan", - default="FULL", - update=update_free_full - ) - - #################### - - @classmethod - def poll(cls, context): - scene = bpy.context.scene - ui_props = bpy.context.window_manager.blenderkitUI - return True - - def draw(self, context): - layout = self.layout - # col = layout.column() - layout.label(text=self.message) - row = layout.row() - - layout.prop(self, 'category') - if self.category != 'NONE' and self.subcategory != 'NONE': - layout.prop(self, 'subcategory') - if self.subcategory != 'NONE' and self.subcategory1 != 'NONE': - enums = categories.get_subcategory1_enums(self, context) - if enums[0][0] != 'NONE': - layout.prop(self, 'subcategory1') - layout.prop(self, 'name') - layout.prop(self, 'description') - layout.prop(self, 'tags') - layout.prop(self, 'is_private', expand=True) - layout.prop(self, 'free_full', expand=True) - if self.is_private == 'PUBLIC': - layout.prop(self, 'license') - - def execute(self, context): - user_preferences = bpy.context.preferences.addons['blenderkit'].preferences - props = bpy.context.window_manager.blenderkitUI - if self.subcategory1 != 'NONE': - category = self.subcategory1 - elif self.subcategory != 'NONE': - category = self.subcategory - else: - category = self.category - utils.update_tags(self, context) - - mdict = { - 'category': category, - 'displayName': self.name, - 'description': self.description, - 'tags': comma2array(self.tags), - 'isPrivate': self.is_private == 'PRIVATE', - 'isFree': self.free_full == 'FREE', - 'license': self.license, - } - - thread = threading.Thread(target=patch_individual_metadata, - args=(self.asset_id, mdict, user_preferences.api_key)) - thread.start() - tasks_queue.add_task((reports.add_report, (f'Uploading metadata for {self.name}. ' - f'Refresh search results to see that changes applied correctly.', 8,))) - - return {'FINISHED'} - - def invoke(self, context, event): - scene = bpy.context.scene - ui_props = bpy.context.window_manager.blenderkitUI - if ui_props.active_index > -1: - sr = bpy.context.window_manager['search results'] - asset_data = dict(sr[ui_props.active_index]) - else: - - active_asset = utils.get_active_asset_by_type(asset_type=self.asset_type) - asset_data = active_asset.get('asset_data') - - if not can_edit_asset(asset_data=asset_data): - return {'CANCELLED'} - self.asset_id = asset_data['id'] - self.asset_type = asset_data['assetType'] - cat_path = categories.get_category_path(bpy.context.window_manager['bkit_categories'], - asset_data['category']) - try: - if len(cat_path) > 1: - self.category = cat_path[1] - if len(cat_path) > 2: - self.subcategory = cat_path[2] - except Exception as e: - print(e) - - self.message = f"Fast edit metadata of {asset_data['name']}" - self.name = asset_data['displayName'] - self.description = asset_data['description'] - self.tags = ','.join(asset_data['tags']) - if asset_data['isPrivate']: - self.is_private = 'PRIVATE' - else: - self.is_private = 'PUBLIC' - - if asset_data['isFree']: - self.free_full = 'FREE' - else: - self.free_full = 'FULL' - self.license = asset_data['license'] - - wm = context.window_manager - - return wm.invoke_props_dialog(self, width=600) - - -def verification_status_change_thread(asset_id, state, api_key): - upload_data = { - "verificationStatus": state - } - url = paths.get_api_url() + 'assets/' + str(asset_id) + '/' - headers = utils.get_headers(api_key) - try: - r = rerequests.patch(url, json=upload_data, headers=headers, verify=True) # files = files, - except requests.exceptions.RequestException as e: - print(e) - return {'CANCELLED'} - return {'FINISHED'} - - -def get_upload_location(props): - ''' - not used by now, gets location of uploaded asset - potentially usefull if we draw a nice upload gizmo in viewport. - Parameters - ---------- - props - - Returns - ------- - - ''' - scene = bpy.context.scene - ui_props = bpy.context.window_manager.blenderkitUI - if ui_props.asset_type == 'MODEL': - if bpy.context.view_layer.objects.active is not None: - ob = utils.get_active_model() - return ob.location - if ui_props.asset_type == 'SCENE': - return None - elif ui_props.asset_type == 'MATERIAL': - if bpy.context.view_layer.objects.active is not None and bpy.context.active_object.active_material is not None: - return bpy.context.active_object.location - elif ui_props.asset_type == 'TEXTURE': - return None - elif ui_props.asset_type == 'BRUSH': - return None - return None - - -def check_storage_quota(props): - if props.is_private == 'PUBLIC': - return True - - profile = bpy.context.window_manager.get('bkit profile') - if profile is None or profile.get('remainingPrivateQuota') is None: - preferences = bpy.context.preferences.addons['blenderkit'].preferences - adata = search.request_profile(preferences.api_key) - if adata is None: - props.report = 'Please log-in first.' - return False - search.write_profile(adata) - profile = adata - quota = profile['user'].get('remainingPrivateQuota') - if quota is None or quota > 0: - return True - props.report = 'Private storage quota exceeded.' - return False - - -def auto_fix(asset_type=''): - # this applies various procedures to ensure coherency in the database. - asset = utils.get_active_asset() - props = utils.get_upload_props() - if asset_type == 'MATERIAL': - overrides.ensure_eevee_transparency(asset) - asset.name = props.name - - -upload_threads = [] - - -class Uploader(threading.Thread): - ''' - Upload thread - - - first uploads metadata - - blender gets started to process the file if .blend is uploaded - - if files need to be uploaded, uploads them - - thumbnail goes first - - files get uploaded - - Returns - ------- - - ''' - - def __init__(self, upload_data=None, export_data=None, upload_set=None): - super(Uploader, self).__init__() - self.upload_data = upload_data - self.export_data = export_data - self.upload_set = upload_set - self._stop_event = threading.Event() - - def stop(self): - self._stop_event.set() - - def stopped(self): - return self._stop_event.is_set() - - def send_message(self, message): - message = str(message).replace("'", "") - - # this adds a UI report but also writes above the upload panel fields. - tasks_queue.add_task((reports.add_report, (message,))) - estring = f"{self.export_data['eval_path_state']} = '{message}'" - tasks_queue.add_task((exec, (estring,))) - - def end_upload(self, message): - estring = self.export_data['eval_path_computing'] + ' = False' - tasks_queue.add_task((exec, (estring,))) - self.send_message(message) - - def run(self): - # utils.pprint(upload_data) - self.upload_data['parameters'] = utils.dict_to_params( - self.upload_data['parameters']) # weird array conversion only for upload, not for tooltips. - - script_path = os.path.dirname(os.path.realpath(__file__)) - - # first upload metadata to server, so it can be saved inside the current file - url = paths.get_api_url() + 'assets/' - - headers = utils.get_headers(self.upload_data['token']) - - # self.upload_data['license'] = 'ovejajojo' - json_metadata = self.upload_data # json.dumps(self.upload_data, ensure_ascii=False).encode('utf8') - - # tasks_queue.add_task((reports.add_report, ('Posting metadata',))) - self.send_message('Posting metadata') - if self.export_data['assetBaseId'] == '': - try: - r = rerequests.post(url, json=json_metadata, headers=headers, verify=True, - immediate=True) # files = files, - - # tasks_queue.add_task((reports.add_report, ('uploaded metadata',))) - utils.p(r.text) - self.send_message('uploaded metadata') - - except requests.exceptions.RequestException as e: - print(e) - self.end_upload(e) - return {'CANCELLED'} - - else: - url += self.export_data['id'] + '/' - try: - if 'MAINFILE' in self.upload_set: - json_metadata["verificationStatus"] = "uploading" - r = rerequests.patch(url, json=json_metadata, headers=headers, verify=True, - immediate=True) # files = files, - self.send_message('uploaded metadata') - - # tasks_queue.add_task((reports.add_report, ('uploaded metadata',))) - # parse the request - # print('uploaded metadata') - print(r.text) - except requests.exceptions.RequestException as e: - print(e) - self.end_upload(e) - return {'CANCELLED'} - - if self.stopped(): - self.end_upload('Upload cancelled by user') - return - # props.upload_state = 'step 1' - if self.upload_set == ['METADATA']: - self.end_upload('Metadata posted successfully') - return {'FINISHED'} - try: - rj = r.json() - # utils.pprint(rj) - # if r.status_code not in (200, 201): - # if r.status_code == 401: - # ###reports.add_report(r.detail, 5, colors.RED) - # return {'CANCELLED'} - # if props.asset_base_id == '': - # props.asset_base_id = rj['assetBaseId'] - # props.id = rj['id'] - if self.export_data['assetBaseId'] == '': - self.export_data['assetBaseId'] = rj['assetBaseId'] - self.export_data['id'] = rj['id'] - # here we need to send asset ID's back into UI to be written in asset data. - estring = f"{self.export_data['eval_path']}.blenderkit.asset_base_id = '{rj['assetBaseId']}'" - tasks_queue.add_task((exec, (estring,))) - estring = f"{self.export_data['eval_path']}.blenderkit.id = '{rj['id']}'" - tasks_queue.add_task((exec, (estring,))) - # after that, the user's file needs to be saved to save the - # estring = f"bpy.ops.wm.save_as_mainfile(filepath={self.export_data['source_filepath']}, compress=False, copy=True)" - # tasks_queue.add_task((exec, (estring,))) - - self.upload_data['assetBaseId'] = self.export_data['assetBaseId'] - self.upload_data['id'] = self.export_data['id'] - - # props.uploading = True - - if 'MAINFILE' in self.upload_set: - if self.upload_data['assetType'] == 'hdr': - fpath = self.export_data['hdr_filepath'] - else: - fpath = os.path.join(self.export_data['temp_dir'], self.upload_data['assetBaseId'] + '.blend') - - clean_file_path = paths.get_clean_filepath() - - data = { - 'export_data': self.export_data, - 'upload_data': self.upload_data, - 'debug_value': self.export_data['debug_value'], - 'upload_set': self.upload_set, - } - datafile = os.path.join(self.export_data['temp_dir'], BLENDERKIT_EXPORT_DATA_FILE) - - with open(datafile, 'w', encoding='utf-8') as s: - json.dump(data, s, ensure_ascii=False, indent=4) - - self.send_message('preparing scene - running blender instance') - - proc = subprocess.run([ - self.export_data['binary_path'], - "--background", - "-noaudio", - clean_file_path, - "--python", os.path.join(script_path, "upload_bg.py"), - "--", datafile - ], bufsize=1, stdout=sys.stdout, stdin=subprocess.PIPE, creationflags=utils.get_process_flags()) - - if self.stopped(): - self.end_upload('Upload stopped by user') - return - - files = [] - if 'THUMBNAIL' in self.upload_set: - files.append({ - "type": "thumbnail", - "index": 0, - "file_path": self.export_data["thumbnail_path"] - }) - if 'MAINFILE' in self.upload_set: - files.append({ - "type": "blend", - "index": 0, - "file_path": fpath - }) - - if not os.path.exists(fpath): - self.send_message("File packing failed, please try manual packing first") - return {'CANCELLED'} - - self.send_message('Uploading files') - - uploaded = upload_bg.upload_files(self.upload_data, files) - - if uploaded: - # mark on server as uploaded - if 'MAINFILE' in self.upload_set: - confirm_data = { - "verificationStatus": "uploaded" - } - - url = paths.get_api_url() + 'assets/' - - headers = utils.get_headers(self.upload_data['token']) - - url += self.upload_data["id"] + '/' - - r = rerequests.patch(url, json=confirm_data, headers=headers, verify=True) # files = files, - - self.end_upload('Upload finished successfully') - else: - self.end_upload('Upload failed') - except Exception as e: - self.end_upload(e) - print(e) - return {'CANCELLED'} - - -def start_upload(self, context, asset_type, reupload, upload_set): - '''start upload process, by processing data, then start a thread that cares about the rest of the upload.''' - - # fix the name first - props = utils.get_upload_props() - - utils.name_update(props) - - storage_quota_ok = check_storage_quota(props) - if not storage_quota_ok: - self.report({'ERROR_INVALID_INPUT'}, props.report) - return {'CANCELLED'} - - location = get_upload_location(props) - props.upload_state = 'preparing upload' - - auto_fix(asset_type=asset_type) - - # do this for fixing long tags in some upload cases - props.tags = props.tags[:] - - # check for missing metadata - check_missing_data(asset_type, props) - # if previous check did find any problems then - if props.report != '': - return {'CANCELLED'} - - if not reupload: - props.asset_base_id = '' - props.id = '' - - export_data, upload_data = get_upload_data(caller=self, context=context, asset_type=asset_type) - - # check if thumbnail exists, generate for HDR: - if 'THUMBNAIL' in upload_set: - if asset_type == 'HDR': - image_utils.generate_hdr_thumbnail() - # get upload data because the image utils function sets true_hdr - export_data, upload_data = get_upload_data(caller=self, context=context, asset_type=asset_type) - - elif not os.path.exists(export_data["thumbnail_path"]): - props.upload_state = 'Thumbnail not found' - props.uploading = False - return {'CANCELLED'} - - if upload_set == {'METADATA'}: - props.upload_state = "Updating metadata. Please don't close Blender until upload finishes" - else: - props.upload_state = "Starting upload. Please don't close Blender until upload finishes" - props.uploading = True - - # save a copy of the file for processing. Only for blend files - basename, ext = os.path.splitext(bpy.data.filepath) - if not ext: - ext = ".blend" - export_data['temp_dir'] = tempfile.mkdtemp() - export_data['source_filepath'] = os.path.join(export_data['temp_dir'], "export_blenderkit" + ext) - if asset_type != 'HDR': - # if this isn't here, blender crashes. - bpy.context.preferences.filepaths.file_preview_type = 'NONE' - - bpy.ops.wm.save_as_mainfile(filepath=export_data['source_filepath'], compress=False, copy=True) - - export_data['binary_path'] = bpy.app.binary_path - export_data['debug_value'] = bpy.app.debug_value - - upload_thread = Uploader(upload_data=upload_data, export_data=export_data, upload_set=upload_set) - - upload_thread.start() - - upload_threads.append(upload_thread) - return {'FINISHED'} - - -asset_types = ( - ('MODEL', 'Model', 'Set of objects'), - ('SCENE', 'Scene', 'Scene'), - ('HDR', 'HDR', 'HDR image'), - ('MATERIAL', 'Material', 'Any .blend Material'), - ('TEXTURE', 'Texture', 'A texture, or texture set'), - ('BRUSH', 'Brush', 'Brush, can be any type of blender brush'), - ('ADDON', 'Addon', 'Addnon'), -) - - -class UploadOperator(Operator): - """Tooltip""" - bl_idname = "object.blenderkit_upload" - bl_description = "Upload or re-upload asset + thumbnail + metadata" - - bl_label = "BlenderKit asset upload" - bl_options = {'REGISTER', 'INTERNAL'} - - # type of upload - model, material, textures, e.t.c. - asset_type: EnumProperty( - name="Type", - items=asset_types, - description="Type of upload", - default="MODEL", - ) - - reupload: BoolProperty( - name="reupload", - description="reupload but also draw so that it asks what to reupload", - default=False, - options={'SKIP_SAVE'} - ) - - metadata: BoolProperty( - name="metadata", - default=True, - options={'SKIP_SAVE'} - ) - - thumbnail: BoolProperty( - name="thumbnail", - default=False, - options={'SKIP_SAVE'} - ) - - main_file: BoolProperty( - name="main file", - default=False, - options={'SKIP_SAVE'} - ) - - @classmethod - def poll(cls, context): - return utils.uploadable_asset_poll() - - def execute(self, context): - bpy.ops.object.blenderkit_auto_tags() - props = utils.get_upload_props() - - # in case of name change, we have to reupload everything, since the name is stored in blender file, - # and is used for linking to scene - # if props.name_changed: - # # TODO: this needs to be replaced with new double naming scheme (metadata vs blend data) - # # print('has to reupload whole data, name has changed.') - # self.main_file = True - # props.name_changed = False - - upload_set = [] - if not self.reupload: - upload_set = ['METADATA', 'THUMBNAIL', 'MAINFILE'] - else: - if self.metadata: - upload_set.append('METADATA') - if self.thumbnail: - upload_set.append('THUMBNAIL') - if self.main_file: - upload_set.append('MAINFILE') - - # this is accessed later in get_upload_data and needs to be written. - # should pass upload_set all the way to it probably - if 'MAINFILE' in upload_set: - self.main_file = True - - result = start_upload(self, context, self.asset_type, self.reupload, upload_set=upload_set, ) - - if props.report != '': - # self.report({'ERROR_INVALID_INPUT'}, props.report) - self.report({'ERROR_INVALID_CONTEXT'}, props.report) - - return result - - def draw(self, context): - props = utils.get_upload_props() - layout = self.layout - - if self.reupload: - # layout.prop(self, 'metadata') - layout.prop(self, 'main_file') - layout.prop(self, 'thumbnail') - - if props.asset_base_id != '' and not self.reupload: - utils.label_multiline(layout, text="Really upload as new?\n" - "Do this only when you create\n" - "a new asset from an old one.\n" - "For updates of thumbnail or model use reupload.\n", - width=400, icon='ERROR') - - - if props.is_private == 'PUBLIC': - if self.asset_type == 'MODEL': - utils.label_multiline(layout, text='You marked the asset as public.\n' - 'This means it will be validated by our team.\n\n' - 'Please test your upload after it finishes:\n' - '- Open a new file\n' - '- Find the asset and download it\n' - '- Check if it snaps correctly to surfaces\n' - '- Check if it has all textures and renders as expected\n' - '- Check if it has correct size in world units (for models)' - , width=400) - elif self.asset_type == 'HDR': - if not props.true_hdr: - utils.label_multiline(layout, text="This image isn't HDR,\n" - "It has a low dynamic range.\n" - "BlenderKit library accepts 360 degree images\n" - "however the default filter setting for search\n" - "is to show only true HDR images\n" - , icon='ERROR', width=400) - - utils.label_multiline(layout, text='You marked the asset as public.\n' - 'This means it will be validated by our team.\n\n' - 'Please test your upload after it finishes:\n' - '- Open a new file\n' - '- Find the asset and download it\n' - '- Check if it works as expected\n' - , width=400) - else: - utils.label_multiline(layout, text='You marked the asset as public.\n' - 'This means it will be validated by our team.\n\n' - 'Please test your upload after it finishes:\n' - '- Open a new file\n' - '- Find the asset and download it\n' - '- Check if it works as expected\n' - , width=400) - - def invoke(self, context, event): - - if not utils.user_logged_in(): - ui_panels.draw_not_logged_in(self, message='To upload assets you need to login/signup.') - return {'CANCELLED'} - - if self.asset_type == 'HDR': - props = utils.get_upload_props() - # getting upload data for images ensures true_hdr check so users can be informed about their handling - # simple 360 photos or renders with LDR are hidden by default.. - export_data, upload_data = get_upload_data(asset_type='HDR') - - # if props.is_private == 'PUBLIC': - return context.window_manager.invoke_props_dialog(self) - # else: - # return self.execute(context) - - -class AssetDebugPrint(Operator): - """Change verification status""" - bl_idname = "object.blenderkit_print_asset_debug" - bl_description = "BlenderKit print asset data for debug purposes" - bl_label = "BlenderKit print asset data" - bl_options = {'REGISTER', 'UNDO', 'INTERNAL'} - - # type of upload - model, material, textures, e.t.c. - asset_id: StringProperty( - name="asset id", - ) - - @classmethod - def poll(cls, context): - return True - - def execute(self, context): - preferences = bpy.context.preferences.addons['blenderkit'].preferences - - if not bpy.context.window_manager['search results']: - print('no search results found') - return {'CANCELLED'}; - # update status in search results for validator's clarity - sr = bpy.context.window_manager['search results'] - - result = None - for r in sr: - if r['id'] == self.asset_id: - result = r.to_dict() - if not result: - ad = bpy.context.active_object.get('asset_data') - if ad: - result = ad.to_dict() - if result: - t = bpy.data.texts.new(result['name']) - t.write(json.dumps(result, indent=4, sort_keys=True)) - print(json.dumps(result, indent=4, sort_keys=True)) - return {'FINISHED'} - - -class AssetVerificationStatusChange(Operator): - """Change verification status""" - bl_idname = "object.blenderkit_change_status" - bl_description = "Change asset status" - bl_label = "Change verification status" - bl_options = {'REGISTER', 'UNDO', 'INTERNAL'} - - # type of upload - model, material, textures, e.t.c. - asset_id: StringProperty( - name="asset id", - ) - - state: StringProperty( - name="verification_status", - default='uploaded' - ) - - @classmethod - def poll(cls, context): - return True - - def draw(self, context): - layout = self.layout - # if self.state == 'deleted': - layout.label(text='Really delete asset from BlenderKit online storage?') - # layout.prop(self, 'state') - - def execute(self, context): - preferences = bpy.context.preferences.addons['blenderkit'].preferences - - if not bpy.context.window_manager['search results']: - return {'CANCELLED'}; - # update status in search results for validator's clarity - sr = bpy.context.window_manager['search results'] - - for r in sr: - if r['id'] == self.asset_id: - r['verificationStatus'] = self.state - - thread = threading.Thread(target=verification_status_change_thread, - args=(self.asset_id, self.state, preferences.api_key)) - thread.start() - if asset_bar_op.asset_bar_operator is not None: - asset_bar_op.asset_bar_operator.update_layout(context, None) - return {'FINISHED'} - - def invoke(self, context, event): - # print(self.state) - if self.state == 'deleted': - wm = context.window_manager - return wm.invoke_props_dialog(self) - return {'RUNNING_MODAL'} - - -def register_upload(): - bpy.utils.register_class(UploadOperator) - bpy.utils.register_class(FastMetadata) - bpy.utils.register_class(AssetDebugPrint) - bpy.utils.register_class(AssetVerificationStatusChange) - - -def unregister_upload(): - bpy.utils.unregister_class(UploadOperator) - bpy.utils.unregister_class(FastMetadata) - bpy.utils.unregister_class(AssetDebugPrint) - bpy.utils.unregister_class(AssetVerificationStatusChange) |