# ##### 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 paths, ratings, ratings_utils, utils, download, categories, icons, search, resolutions, ui, \ tasks_queue, \ autothumb, upload from bpy.types import ( Panel ) from bpy.props import ( IntProperty, FloatProperty, FloatVectorProperty, StringProperty, EnumProperty, BoolProperty, PointerProperty, ) import bpy import os import random import logging import platform import ctypes bk_logger = logging.getLogger('blenderkit') # this was moved to separate interface: def draw_ratings(layout, context, asset): # layout.operator("wm.url_open", text="Read rating instructions", icon='QUESTION').url = 'https://support.google.com/?hl=en' # the following shouldn't happen at all in an optimal case, # this function should run only when asset was already checked to be existing if asset == None: return; col = layout.column() bkit_ratings = asset.bkit_ratings # layout.template_icon_view(bkit_ratings, property, show_labels=False, scale=6.0, scale_popup=5.0) row = col.row() row.prop(bkit_ratings, 'rating_quality_ui', expand=True, icon_only=True, emboss=False) if bkit_ratings.rating_quality > 0: col.separator() col.prop(bkit_ratings, 'rating_work_hours') # w = context.region.width # layout.label(text='problems') # layout.prop(bkit_ratings, 'rating_problems', text='') # layout.label(text='compliments') # layout.prop(bkit_ratings, 'rating_compliments', text='') # row = layout.row() # op = row.operator("object.blenderkit_rating_upload", text="Send rating", icon='URL') # return op # re-enable layout if included in longer panel def draw_not_logged_in(source, message='Please Login/Signup to use this feature'): title = "You aren't logged in" def draw_message(source, context): layout = source.layout utils.label_multiline(layout, text=message) draw_login_buttons(layout) bpy.context.window_manager.popup_menu(draw_message, title=title, icon='INFO') def draw_upload_common(layout, props, asset_type, context): op = layout.operator("wm.url_open", text=f"Read {asset_type.lower()} upload instructions", icon='QUESTION') if asset_type == 'MODEL': op.url = paths.BLENDERKIT_MODEL_UPLOAD_INSTRUCTIONS_URL if asset_type == 'MATERIAL': op.url = paths.BLENDERKIT_MATERIAL_UPLOAD_INSTRUCTIONS_URL if asset_type == 'BRUSH': op.url = paths.BLENDERKIT_BRUSH_UPLOAD_INSTRUCTIONS_URL if asset_type == 'SCENE': op.url = paths.BLENDERKIT_SCENE_UPLOAD_INSTRUCTIONS_URL if asset_type == 'HDR': op.url = paths.BLENDERKIT_HDR_UPLOAD_INSTRUCTIONS_URL row = layout.row(align=True) if props.upload_state != '': utils.label_multiline(layout, text=props.upload_state, width=context.region.width) if props.uploading: op = layout.operator('object.kill_bg_process', text="", icon='CANCEL') op.process_source = asset_type op.process_type = 'UPLOAD' layout = layout.column() layout.enabled = False # if props.upload_state.find('Error') > -1: # layout.label(text = props.upload_state) if props.asset_base_id == '': optext = 'Upload %s' % asset_type.lower() op = layout.operator("object.blenderkit_upload", text=optext, icon='EXPORT') op.asset_type = asset_type op.reupload = False # make sure everything gets uploaded. op.main_file = True op.metadata = True op.thumbnail = True if props.asset_base_id != '': op = layout.operator("object.blenderkit_upload", text='Reupload asset', icon='EXPORT') op.asset_type = asset_type op.reupload = True op = layout.operator("object.blenderkit_upload", text='Upload as new asset', icon='EXPORT') op.asset_type = asset_type op.reupload = False # layout.label(text = 'asset id, overwrite only for reuploading') layout.label(text='asset has a version online.') # row = layout.row() # row.enabled = False # row.prop(props, 'asset_base_id', icon='FILE_TICK') # row = layout.row() # row.enabled = False # row.prop(props, 'id', icon='FILE_TICK') layout.prop(props, 'category') if props.category != 'NONE' and props.subcategory != 'NONE': layout.prop(props, 'subcategory') if props.subcategory != 'NONE' and props.subcategory1 != 'NONE': layout.prop(props, 'subcategory1') layout.prop(props, 'is_private', expand=True) if props.is_private == 'PUBLIC': layout.prop(props, 'license') layout.prop(props, 'is_free', expand=True) prop_needed(layout, props, 'name', props.name) if props.is_private == 'PUBLIC': prop_needed(layout, props, 'description', props.description) prop_needed(layout, props, 'tags', props.tags) else: layout.prop(props, 'description') layout.prop(props, 'tags') def poll_local_panels(): user_preferences = bpy.context.preferences.addons['blenderkit'].preferences return user_preferences.panel_behaviour == 'BOTH' or user_preferences.panel_behaviour == 'LOCAL' def prop_needed(layout, props, name, value='', is_not_filled=''): row = layout.row() if value == is_not_filled: # row.label(text='', icon = 'ERROR') icon = 'ERROR' row.alert = True row.prop(props, name) # , icon=icon) row.alert = False else: # row.label(text='', icon = 'FILE_TICK') icon = None row.prop(props, name) def draw_panel_hdr_upload(self, context): layout = self.layout ui_props = bpy.context.scene.blenderkitUI # layout.prop_search(ui_props, "hdr_upload_image", bpy.data, "images") layout.prop(ui_props, "hdr_upload_image") hdr = utils.get_active_HDR() if hdr is not None: props = hdr.blenderkit layout = self.layout draw_upload_common(layout, props, 'HDR', context) def draw_panel_hdr_search(self, context): s = context.scene wm = context.window_manager props = wm.blenderkit_HDR layout = self.layout row = layout.row() row.prop(props, "search_keywords", text="", icon='VIEWZOOM') draw_assetbar_show_hide(row, props) layout.prop(props, "own_only") utils.label_multiline(layout, text=props.report) def draw_thumbnail_upload_panel(layout, props): update = False tex = autothumb.get_texture_ui(props.thumbnail, 'upload_preview') if not tex or not tex.image: return box = layout.box() box.template_icon(icon_value=tex.image.preview.icon_id, scale=6.0) def draw_panel_model_upload(self, context): ob = bpy.context.active_object while ob.parent is not None: ob = ob.parent props = ob.blenderkit layout = self.layout draw_upload_common(layout, props, 'MODEL', context) col = layout.column() if props.is_generating_thumbnail: col.enabled = False draw_thumbnail_upload_panel(col, props) prop_needed(col, props, 'thumbnail', props.thumbnail) if bpy.context.scene.render.engine in ('CYCLES', 'BLENDER_EEVEE'): col.operator("object.blenderkit_generate_thumbnail", text='Generate thumbnail', icon='IMAGE') # row = layout.row(align=True) if props.is_generating_thumbnail: row = layout.row(align=True) row.label(text=props.thumbnail_generating_state) op = row.operator('object.kill_bg_process', text="", icon='CANCEL') op.process_source = 'MODEL' op.process_type = 'THUMBNAILER' elif props.thumbnail_generating_state != '': utils.label_multiline(layout, text=props.thumbnail_generating_state) # prop_needed(layout, props, 'style', props.style) # prop_needed(layout, props, 'production_level', props.production_level) layout.prop(props, 'style') layout.prop(props, 'production_level') layout.prop(props, 'condition') layout.prop(props, 'pbr') layout.label(text='design props:') layout.prop(props, 'manufacturer') layout.prop(props, 'designer') layout.prop(props, 'design_collection') layout.prop(props, 'design_variant') layout.prop(props, 'use_design_year') if props.use_design_year: layout.prop(props, 'design_year') row = layout.row() row.prop(props, 'work_hours') layout.prop(props, 'adult') def draw_panel_scene_upload(self, context): s = bpy.context.scene props = s.blenderkit layout = self.layout # if bpy.app.debug_value != -1: # layout.label(text='Scene upload not Implemented') # return draw_upload_common(layout, props, 'SCENE', context) # layout = layout.column() # row = layout.row() # if props.dimensions[0] + props.dimensions[1] == 0 and props.face_count == 0: # icon = 'ERROR' # layout.operator("object.blenderkit_auto_tags", text='Auto fill tags', icon=icon) # else: # layout.operator("object.blenderkit_auto_tags", text='Auto fill tags') col = layout.column() # if props.is_generating_thumbnail: # col.enabled = False draw_thumbnail_upload_panel(col, props) prop_needed(col, props, 'thumbnail', props.has_thumbnail, False) # if bpy.context.scene.render.engine == 'CYCLES': # col.operator("object.blenderkit_generate_thumbnail", text='Generate thumbnail', icon='IMAGE_COL') # row = layout.row(align=True) # if props.is_generating_thumbnail: # row = layout.row(align=True) # row.label(text = props.thumbnail_generating_state) # op = row.operator('object.kill_bg_process', text="", icon='CANCEL') # op.process_source = 'MODEL' # op.process_type = 'THUMBNAILER' # elif props.thumbnail_generating_state != '': # utils.label_multiline(layout, text = props.thumbnail_generating_state) layout.prop(props, 'style') layout.prop(props, 'production_level') layout.prop(props, 'use_design_year') if props.use_design_year: layout.prop(props, 'design_year') layout.prop(props, 'condition') row = layout.row() row.prop(props, 'work_hours') layout.prop(props, 'adult') def draw_assetbar_show_hide(layout, props): s = bpy.context.scene ui_props = s.blenderkitUI if ui_props.assetbar_on: icon = 'HIDE_OFF' ttip = 'Click to Hide Asset Bar' else: icon = 'HIDE_ON' ttip = 'Click to Show Asset Bar' preferences = bpy.context.preferences.addons['blenderkit'].preferences if preferences.experimental_features: op = layout.operator('view3d.blenderkit_asset_bar_widget', text='', icon=icon) op.keep_running = False op.do_search = False op.tooltip = ttip else: op = layout.operator('view3d.blenderkit_asset_bar', text='', icon=icon) op.keep_running = False op.do_search = False op.tooltip = ttip def draw_panel_model_search(self, context): wm = bpy.context.window_manager props = wm.blenderkit_models layout = self.layout row = layout.row() row.prop(props, "search_keywords", text="", icon='VIEWZOOM') draw_assetbar_show_hide(row, props) icon = 'NONE' if props.report == 'You need Full plan to get this item.': icon = 'ERROR' utils.label_multiline(layout, text=props.report, icon=icon) if props.report == 'You need Full plan to get this item.': layout.operator("wm.url_open", text="Get Full plan", icon='URL').url = paths.BLENDERKIT_PLANS # layout.prop(props, "search_style") # layout.prop(props, "own_only") # layout.prop(props, "free_only") # if props.search_style == 'OTHER': # layout.prop(props, "search_style_other") # layout.prop(props, "search_engine") # col = layout.column() # layout.prop(props, 'append_link', expand=True, icon_only=False) # layout.prop(props, 'import_as', expand=True, icon_only=False) # draw_panel_categories(self, context) def draw_panel_scene_search(self, context): wm = bpy.context.window_manager props = wm.blenderkit_scene layout = self.layout # layout.label(text = "common search properties:") row = layout.row() row.prop(props, "search_keywords", text="", icon='VIEWZOOM') draw_assetbar_show_hide(row, props) layout.prop(props, "own_only") utils.label_multiline(layout, text=props.report) # layout.prop(props, "search_style") # if props.search_style == 'OTHER': # layout.prop(props, "search_style_other") # layout.prop(props, "search_engine") layout.separator() # draw_panel_categories(self, context) class VIEW3D_PT_blenderkit_model_properties(Panel): bl_category = "BlenderKit" bl_idname = "VIEW3D_PT_blenderkit_model_properties" bl_space_type = 'VIEW_3D' bl_region_type = 'UI' bl_label = "Selected Model" bl_context = "objectmode" @classmethod def poll(cls, context): p = bpy.context.view_layer.objects.active is not None return p def draw(self, context): # draw asset properties here layout = self.layout o = utils.get_active_model() # o = bpy.context.active_object if o.get('asset_data') is None: utils.label_multiline(layout, text='To upload this asset to BlenderKit, go to the Find and Upload Assets panel.') layout.prop(o, 'name') if o.get('asset_data') is not None: ad = o['asset_data'] layout.label(text=str(ad['name'])) if o.instance_type == 'COLLECTION' and o.instance_collection is not None: layout.operator('object.blenderkit_bring_to_scene', text='Bring to scene') layout.label(text='Asset tools:') draw_asset_context_menu(self.layout, context, ad, from_panel=True) # if 'rig' in ad['tags']: # # layout.label(text = 'can make proxy') # layout.operator('object.blenderkit_make_proxy', text = 'Make Armature proxy') # fast upload, blocked by now # else: # op = layout.operator("object.blenderkit_upload", text='Store as private', icon='EXPORT') # op.asset_type = 'MODEL' # op.fast = True # fun override project, not finished # layout.operator('object.blenderkit_color_corrector') class NODE_PT_blenderkit_material_properties(Panel): bl_category = "BlenderKit" bl_idname = "NODE_PT_blenderkit_material_properties" bl_space_type = 'NODE_EDITOR' bl_region_type = 'UI' bl_label = "Selected Material" bl_context = "objectmode" @classmethod def poll(cls, context): p = bpy.context.view_layer.objects.active is not None and bpy.context.active_object.active_material is not None return p def draw(self, context): # draw asset properties here layout = self.layout m = bpy.context.active_object.active_material # o = bpy.context.active_object if m.get('asset_data') is None and m.blenderkit.id == '': utils.label_multiline(layout, text='To upload this asset to BlenderKit, go to the Find and Upload Assets panel.') layout.prop(m, 'name') if m.get('asset_data') is not None: ad = m['asset_data'] layout.label(text=str(ad['name'])) layout.label(text='Asset tools:') draw_asset_context_menu(self.layout, context, ad, from_panel=True) # if 'rig' in ad['tags']: # # layout.label(text = 'can make proxy') # layout.operator('object.blenderkit_make_proxy', text = 'Make Armature proxy') # fast upload, blocked by now # else: # op = layout.operator("object.blenderkit_upload", text='Store as private', icon='EXPORT') # op.asset_type = 'MODEL' # op.fast = True # fun override project, not finished # layout.operator('object.blenderkit_color_corrector') def draw_rating_asset(self, context, asset): layout = self.layout col = layout.box() # split = layout.split(factor=0.5) # col1 = split.column() # col2 = split.column() # print('%s_search' % asset['asset_data']['assetType']) directory = paths.get_temp_dir('%s_search' % asset['asset_data']['assetType']) tpath = os.path.join(directory, asset['asset_data']['thumbnail_small']) for image in bpy.data.images: if image.filepath == tpath: # split = row.split(factor=1.0, align=False) col.template_icon(icon_value=image.preview.icon_id, scale=6.0) break; # layout.label(text = '', icon_value=image.preview.icon_id, scale = 10) col.label(text=asset.name) draw_ratings(col, context, asset=asset) class VIEW3D_PT_blenderkit_ratings(Panel): bl_category = "BlenderKit" bl_idname = "VIEW3D_PT_blenderkit_ratings" bl_space_type = 'VIEW_3D' bl_region_type = 'UI' bl_label = "Please rate" bl_context = "objectmode" @classmethod def poll(cls, context): # p = bpy.context.view_layer.objects.active is not None return p def draw(self, context): # TODO make a list of assets inside asset appending code, to happen only when assets are added to the scene. # draw asset properties here layout = self.layout assets = ratings.get_assets_for_rating() if len(assets) > 0: utils.label_multiline(layout, text='Please help BlenderKit community by rating these assets:') for a in assets: if a.bkit_ratings.rating_work_hours == 0: draw_rating_asset(self, context, asset=a) def draw_login_progress(layout): layout.label(text='Login through browser') layout.label(text='in progress.') layout.operator("wm.blenderkit_login_cancel", text="Cancel", icon='CANCEL') class VIEW3D_PT_blenderkit_profile(Panel): bl_category = "BlenderKit" bl_idname = "VIEW3D_PT_blenderkit_profile" bl_space_type = 'VIEW_3D' bl_region_type = 'UI' bl_label = "BlenderKit Profile" @classmethod def poll(cls, context): return True def draw(self, context): # draw asset properties here layout = self.layout user_preferences = bpy.context.preferences.addons['blenderkit'].preferences if user_preferences.login_attempt: draw_login_progress(layout) return if user_preferences.api_key != '': me = bpy.context.window_manager.get('bkit profile') if me is not None: me = me['user'] # user name if len(me['firstName']) > 0 or len(me['lastName']) > 0: layout.label(text=f"Me: {me['firstName']} {me['lastName']}") else: layout.label(text=f"Me: {me['email']}") # layout.label(text='Email: %s' % (me['email'])) # plan information if me.get('currentPlanName') is not None: pn = me['currentPlanName'] pcoll = icons.icon_collections["main"] if pn == 'Free': my_icon = pcoll['free'] else: my_icon = pcoll['full'] row = layout.row() row.label(text='My plan:') row.label(text='%s plan' % pn, icon_value=my_icon.icon_id) if pn == 'Free': layout.operator("wm.url_open", text="Change plan", icon='URL').url = paths.get_bkit_url() + paths.BLENDERKIT_PLANS # storage statistics # if me.get('sumAssetFilesSize') is not None: # TODO remove this when production server has these too. # layout.label(text='My public assets: %i MiB' % (me['sumAssetFilesSize'])) # if me.get('sumPrivateAssetFilesSize') is not None: # layout.label(text='My private assets: %i MiB' % (me['sumPrivateAssetFilesSize'])) if me.get('remainingPrivateQuota') is not None: layout.label(text='My free storage: %i MiB' % (me['remainingPrivateQuota'])) layout.operator("wm.url_open", text="See my uploads", icon='URL').url = paths.get_bkit_url() + paths.BLENDERKIT_USER_ASSETS class VIEW3D_PT_blenderkit_login(Panel): bl_category = "BlenderKit" bl_idname = "VIEW3D_PT_blenderkit_login" bl_space_type = 'VIEW_3D' bl_region_type = 'UI' bl_label = "BlenderKit Login" @classmethod def poll(cls, context): return True def draw(self, context): layout = self.layout user_preferences = bpy.context.preferences.addons['blenderkit'].preferences if user_preferences.login_attempt: draw_login_progress(layout) return if user_preferences.enable_oauth: draw_login_buttons(layout) def draw_panel_model_rating(self, context): # o = bpy.context.active_object o = utils.get_active_model() # print('ratings active',o) draw_ratings(self.layout, context, asset=o) # , props) # op.asset_type = 'MODEL' def draw_panel_material_upload(self, context): o = bpy.context.active_object mat = bpy.context.active_object.active_material props = mat.blenderkit layout = self.layout draw_upload_common(layout, props, 'MATERIAL', context) # THUMBNAIL row = layout.column() if props.is_generating_thumbnail: row.enabled = False draw_thumbnail_upload_panel(row, props) prop_needed(row, props, 'thumbnail', props.has_thumbnail, False) if bpy.context.scene.render.engine in ('CYCLES', 'BLENDER_EEVEE'): layout.operator("object.blenderkit_generate_material_thumbnail", text='Render thumbnail with Cycles', icon='EXPORT') if props.is_generating_thumbnail: row = layout.row(align=True) row.label(text=props.thumbnail_generating_state, icon='RENDER_STILL') op = row.operator('object.kill_bg_process', text="", icon='CANCEL') op.process_source = 'MATERIAL' op.process_type = 'THUMBNAILER' elif props.thumbnail_generating_state != '': utils.label_multiline(layout, text=props.thumbnail_generating_state) layout.prop(props, 'style') # if props.style == 'OTHER': # layout.prop(props, 'style_other') # layout.prop(props, 'engine') # if props.engine == 'OTHER': # layout.prop(props, 'engine_other') # layout.prop(props,'shaders')#TODO autofill on upload # row = layout.row() layout.prop(props, 'pbr') layout.prop(props, 'uv') layout.prop(props, 'animated') layout.prop(props, 'texture_size_meters') # tname = "." + bpy.context.active_object.active_material.name + "_thumbnail" # if props.has_thumbnail and bpy.data.textures.get(tname) is not None: # row = layout.row() # # row.scale_y = 1.5 # row.template_preview(bpy.data.textures[tname], preview_id='test') def draw_panel_material_search(self, context): wm = context.window_manager props = wm.blenderkit_mat layout = self.layout row = layout.row() row.prop(props, "search_keywords", text="", icon='VIEWZOOM') draw_assetbar_show_hide(row, props) utils.label_multiline(layout, text=props.report) # layout.prop(props, 'search_style')F # if props.search_style == 'OTHER': # layout.prop(props, 'search_style_other') # layout.prop(props, 'search_engine') # if props.search_engine == 'OTHER': # layout.prop(props, 'search_engine_other') # draw_panel_categories(self, context) def draw_panel_material_ratings(self, context): asset = bpy.context.active_object.active_material draw_ratings(self.layout, context, asset) # , props) # op.asset_type = 'MATERIAL' def draw_panel_brush_upload(self, context): brush = utils.get_active_brush() if brush is not None: props = brush.blenderkit layout = self.layout draw_upload_common(layout, props, 'BRUSH', context) def draw_panel_brush_search(self, context): wm = context.window_manager props = wm.blenderkit_brush layout = self.layout row = layout.row() row.prop(props, "search_keywords", text="", icon='VIEWZOOM') draw_assetbar_show_hide(row, props) layout.prop(props, "own_only") utils.label_multiline(layout, text=props.report) # draw_panel_categories(self, context) def draw_panel_brush_ratings(self, context): # props = utils.get_brush_props(context) brush = utils.get_active_brush() draw_ratings(self.layout, context, asset=brush) # , props) # # op.asset_type = 'BRUSH' def draw_login_buttons(layout, invoke=False): user_preferences = bpy.context.preferences.addons['blenderkit'].preferences if user_preferences.login_attempt: draw_login_progress(layout) else: if invoke: layout.operator_context = 'INVOKE_DEFAULT' else: layout.operator_context = 'EXEC_DEFAULT' if user_preferences.api_key == '': layout.operator("wm.blenderkit_login", text="Login", icon='URL').signup = False layout.operator("wm.blenderkit_login", text="Sign up", icon='URL').signup = True else: layout.operator("wm.blenderkit_login", text="Login as someone else", icon='URL').signup = False layout.operator("wm.blenderkit_logout", text="Logout", icon='URL') class VIEW3D_PT_blenderkit_advanced_model_search(Panel): bl_category = "BlenderKit" bl_idname = "VIEW3D_PT_blenderkit_advanced_model_search" bl_parent_id = "VIEW3D_PT_blenderkit_unified" bl_space_type = 'VIEW_3D' bl_region_type = 'UI' bl_label = "Search filters" bl_options = {'DEFAULT_CLOSED'} @classmethod def poll(cls, context): s = context.scene ui_props = s.blenderkitUI return ui_props.down_up == 'SEARCH' and ui_props.asset_type == 'MODEL' def draw(self, context): wm = bpy.context.window_manager props = wm.blenderkit_models layout = self.layout layout.separator() # layout.label(text = "common searches keywords:") # layout.prop(props, "search_global_keywords", text = "") # layout.prop(props, "search_modifier_keywords") # if props.search_engine == 'OTHER': # layout.prop(props, "search_engine_keyword") layout.prop(props, "own_only") layout.prop(props, "free_only") layout.prop(props, "search_style") # DESIGN YEAR layout.prop(props, "search_design_year", text='Designed in Year') if props.search_design_year: row = layout.row(align=True) row.prop(props, "search_design_year_min", text='Min') row.prop(props, "search_design_year_max", text='Max') # POLYCOUNT layout.prop(props, "search_polycount", text='Poly Count ') if props.search_polycount: row = layout.row(align=True) row.prop(props, "search_polycount_min", text='Min') row.prop(props, "search_polycount_max", text='Max') # TEXTURE RESOLUTION layout.prop(props, "search_texture_resolution", text='Texture Resolutions') if props.search_texture_resolution: row = layout.row(align=True) row.prop(props, "search_texture_resolution_min", text='Min') row.prop(props, "search_texture_resolution_max", text='Max') # FILE SIZE layout.prop(props, "search_file_size", text='File Size (MB)') if props.search_file_size: row = layout.row(align=True) row.prop(props, "search_file_size_min", text='Min') row.prop(props, "search_file_size_max", text='Max') # AGE layout.prop(props, "search_condition", text='Condition') # , text ='condition of object new/old e.t.c.') layout.prop(props, "quality_limit", slider=True) # , text ='condition of object new/old e.t.c.') # layout.prop(props, "search_procedural", expand=True) # ADULT # layout.prop(props, "search_adult") # , text ='condition of object new/old e.t.c.') class VIEW3D_PT_blenderkit_advanced_material_search(Panel): bl_category = "BlenderKit" bl_idname = "VIEW3D_PT_blenderkit_advanced_material_search" bl_parent_id = "VIEW3D_PT_blenderkit_unified" bl_space_type = 'VIEW_3D' bl_region_type = 'UI' bl_label = "Search filters" bl_options = {'DEFAULT_CLOSED'} @classmethod def poll(cls, context): s = context.scene ui_props = s.blenderkitUI return ui_props.down_up == 'SEARCH' and ui_props.asset_type == 'MATERIAL' def draw(self, context): wm = context.window_manager props = wm.blenderkit_mat layout = self.layout layout.separator() layout.prop(props, "own_only") layout.label(text='Texture:') col = layout.column() col.prop(props, "search_procedural", expand=True) if props.search_procedural == 'TEXTURE_BASED': # TEXTURE RESOLUTION layout.prop(props, "search_texture_resolution", text='Texture Resolution') if props.search_texture_resolution: row = layout.row(align=True) row.prop(props, "search_texture_resolution_min", text='Min') row.prop(props, "search_texture_resolution_max", text='Max') # FILE SIZE layout.prop(props, "search_file_size", text='File size (MB)') if props.search_file_size: row = layout.row(align=True) row.prop(props, "search_file_size_min", text='Min') row.prop(props, "search_file_size_max", text='Max') layout.prop(props, "quality_limit", slider=True) class VIEW3D_PT_blenderkit_advanced_HDR_search(Panel): bl_category = "BlenderKit" bl_idname = "VIEW3D_PT_blenderkit_advanced_HDR_search" bl_parent_id = "VIEW3D_PT_blenderkit_unified" bl_space_type = 'VIEW_3D' bl_region_type = 'UI' bl_label = "Search filters" bl_options = {'DEFAULT_CLOSED'} @classmethod def poll(cls, context): s = context.scene ui_props = s.blenderkitUI return ui_props.down_up == 'SEARCH' and ui_props.asset_type == 'HDR' def draw(self, context): wm = context.window_manager props = wm.blenderkit_HDR layout = self.layout layout.separator() layout.prop(props, "own_only") layout.prop(props, "true_hdr") class VIEW3D_PT_blenderkit_categories(Panel): bl_category = "BlenderKit" bl_idname = "VIEW3D_PT_blenderkit_categories" bl_space_type = 'VIEW_3D' bl_region_type = 'UI' bl_label = "Categories" bl_parent_id = "VIEW3D_PT_blenderkit_unified" bl_options = {'DEFAULT_CLOSED'} @classmethod def poll(cls, context): s = context.scene ui_props = s.blenderkitUI mode = True if ui_props.asset_type == 'BRUSH' and not (context.sculpt_object or context.image_paint_object): mode = False return ui_props.down_up == 'SEARCH' and mode def draw(self, context): draw_panel_categories(self, context) def draw_scene_import_settings(self, context): wm = bpy.context.window_manager props = wm.blenderkit_scene layout = self.layout layout.prop(props, 'switch_after_append') # layout.label(text='Import method:') row = layout.row() row.prop(props, 'append_link', expand=True, icon_only=False) class VIEW3D_PT_blenderkit_import_settings(Panel): bl_category = "BlenderKit" bl_idname = "VIEW3D_PT_blenderkit_import_settings" bl_space_type = 'VIEW_3D' bl_region_type = 'UI' bl_label = "Import settings" bl_parent_id = "VIEW3D_PT_blenderkit_unified" bl_options = {'DEFAULT_CLOSED'} @classmethod def poll(cls, context): s = context.scene ui_props = s.blenderkitUI return ui_props.down_up == 'SEARCH' and ui_props.asset_type in ['MATERIAL', 'MODEL', 'SCENE', 'HDR'] def draw(self, context): layout = self.layout s = context.scene wm = bpy.context.window_manager ui_props = s.blenderkitUI if ui_props.asset_type == 'MODEL': # noinspection PyCallByClass props = wm.blenderkit_models layout.prop(props, 'randomize_rotation') if props.randomize_rotation: layout.prop(props, 'randomize_rotation_amount') layout.prop(props, 'perpendicular_snap') # if props.perpendicular_snap: # layout.prop(props,'perpendicular_snap_threshold') layout.label(text='Import method:') row = layout.row() row.prop(props, 'append_method', expand=True, icon_only=False) if ui_props.asset_type == 'MATERIAL': props = wm.blenderkit_mat layout.prop(props, 'automap') layout.label(text='Import method:') row = layout.row() row.prop(props, 'append_method', expand=True, icon_only=False) if ui_props.asset_type == 'SCENE': draw_scene_import_settings(self, context) if ui_props.asset_type == 'HDR': props = wm.blenderkit_HDR if ui_props.asset_type in ['MATERIAL', 'MODEL', 'HDR']: layout.prop(props, 'resolution') # layout.prop(props, 'unpack_files') class VIEW3D_PT_blenderkit_unified(Panel): bl_category = "BlenderKit" bl_idname = "VIEW3D_PT_blenderkit_unified" bl_space_type = 'VIEW_3D' bl_region_type = 'UI' bl_label = "Find and Upload Assets" @classmethod def poll(cls, context): user_preferences = bpy.context.preferences.addons['blenderkit'].preferences return user_preferences.panel_behaviour == 'BOTH' or user_preferences.panel_behaviour == 'UNIFIED' def draw(self, context): s = context.scene ui_props = s.blenderkitUI user_preferences = bpy.context.preferences.addons['blenderkit'].preferences wm = bpy.context.window_manager layout = self.layout # layout.prop_tabs_enum(ui_props, "asset_type", icon_only = True) row = layout.row() # row.scale_x = 1.6 # row.scale_y = 1.6 # row.prop(ui_props, 'down_up', expand=True, icon_only=False) # row.label(text='') # row = row.split().row() # layout.alert = True # layout.alignment = 'CENTER' row = layout.row(align=True) row.scale_x = 1.6 row.scale_y = 1.6 # split = row.split(factor=. expand_icon = 'TRIA_DOWN' if ui_props.asset_type_fold: expand_icon = 'TRIA_RIGHT' row = layout.row() split = row.split(factor=0.15) split.prop(ui_props, 'asset_type_fold', icon=expand_icon, icon_only=True, emboss=False) if ui_props.asset_type_fold: pass # expanded interface with names in column split = split.row() split.scale_x = 8 split.scale_y = 1.6 # split = row # split = layout.row() else: split = split.column() split.prop(ui_props, 'asset_type', expand=True, icon_only=ui_props.asset_type_fold) # row = layout.column(align = False) # layout.prop(ui_props, 'asset_type', expand=False, text='') w = context.region.width if user_preferences.login_attempt: draw_login_progress(layout) return if len(user_preferences.api_key) < 20 and user_preferences.asset_counter > 20: if user_preferences.enable_oauth: draw_login_buttons(layout) else: op = layout.operator("wm.url_open", text="Get your API Key", icon='QUESTION') op.url = paths.BLENDERKIT_SIGNUP_URL layout.label(text='Paste your API Key:') layout.prop(user_preferences, 'api_key', text='') layout.separator() # if bpy.data.filepath == '': # layout.alert = True # utils.label_multiline(layout, text="It's better to save your file first.", width=w) # layout.alert = False # layout.separator() if ui_props.down_up == 'SEARCH': if utils.profile_is_validator(): search_props = utils.get_search_props() layout.prop(search_props, 'search_verification_status') layout.prop(search_props, "unrated_only") if ui_props.asset_type == 'MODEL': # noinspection PyCallByClass draw_panel_model_search(self, context) if ui_props.asset_type == 'SCENE': # noinspection PyCallByClass draw_panel_scene_search(self, context) if ui_props.asset_type == 'HDR': # noinspection PyCallByClass draw_panel_hdr_search(self, context) elif ui_props.asset_type == 'MATERIAL': draw_panel_material_search(self, context) elif ui_props.asset_type == 'BRUSH': if context.sculpt_object or context.image_paint_object: # noinspection PyCallByClass draw_panel_brush_search(self, context) else: utils.label_multiline(layout, text='Switch to paint or sculpt mode.', width=context.region.width) return elif ui_props.down_up == 'UPLOAD': if not ui_props.assetbar_on: text = 'Show asset preview - ;' else: text = 'Hide asset preview - ;' op = layout.operator('view3d.blenderkit_asset_bar', text=text, icon='EXPORT') op.keep_running = False op.do_search = False op.tooltip = 'Show/Hide asset preview' e = s.render.engine if e not in ('CYCLES', 'BLENDER_EEVEE'): rtext = 'Only Cycles and EEVEE render engines are currently supported. ' \ 'Please use Cycles for all assets you upload to BlenderKit.' utils.label_multiline(layout, rtext, icon='ERROR', width=w) return; if ui_props.asset_type == 'MODEL': # utils.label_multiline(layout, "Uploaded models won't be available in b2.79", icon='ERROR') if bpy.context.view_layer.objects.active is not None: draw_panel_model_upload(self, context) else: layout.label(text='selet object to upload') elif ui_props.asset_type == 'SCENE': draw_panel_scene_upload(self, context) elif ui_props.asset_type == 'HDR': draw_panel_hdr_upload(self, context) elif ui_props.asset_type == 'MATERIAL': # utils.label_multiline(layout, "Uploaded materials won't be available in b2.79", icon='ERROR') if bpy.context.view_layer.objects.active is not None and bpy.context.active_object.active_material is not None: draw_panel_material_upload(self, context) else: utils.label_multiline(layout, text='select object with material to upload materials', width=w) elif ui_props.asset_type == 'BRUSH': if context.sculpt_object or context.image_paint_object: draw_panel_brush_upload(self, context) else: layout.label(text='Switch to paint or sculpt mode.') class BlenderKitWelcomeOperator(bpy.types.Operator): """Login online on BlenderKit webpage""" bl_idname = "wm.blenderkit_welcome" bl_label = "Welcome to BlenderKit!" bl_options = {'REGISTER', 'UNDO', 'INTERNAL'} step: IntProperty( name="step", description="Tutorial Step", default=0, options={'SKIP_SAVE'} ) @classmethod def poll(cls, context): return True def draw(self, context): layout = self.layout if self.step == 0: user_preferences = bpy.context.preferences.addons['blenderkit'].preferences # message = "BlenderKit connects from Blender to an online, " \ # "community built shared library of models, " \ # "materials, and brushes. " \ # "Use addon preferences to set up where files will be saved in the Global directory setting." # # utils.label_multiline(layout, text=message, width=300) layout.template_icon(icon_value=self.img.preview.icon_id, scale=18) # utils.label_multiline(layout, text="\n Let's start by searching for some cool materials?", width=300) op = layout.operator("wm.url_open", text='Watch Video Tutorial', icon='QUESTION') op.url = paths.BLENDERKIT_MANUAL else: message = "Operator Tutorial called with invalid step" def execute(self, context): if self.step == 0: # move mouse: # bpy.context.window_manager.windows[0].cursor_warp(1000, 1000) # show n-key sidebar (spaces[index] has to be found for view3d too: # bpy.context.window_manager.windows[0].screen.areas[5].spaces[0].show_region_ui = False print('running search no') ui_props = bpy.context.scene.blenderkitUI # random_searches = [ # ('MATERIAL', 'ice'), # ('MODEL', 'car'), # ('MODEL', 'vase'), # ('MODEL', 'grass'), # ('MODEL', 'plant'), # ('MODEL', 'man'), # ('MATERIAL', 'metal'), # ('MATERIAL', 'wood'), # ('MATERIAL', 'floor'), # ('MATERIAL', 'bricks'), # ] # random_search = random.choice(random_searches) # ui_props.asset_type = random_search[0] ui_props.asset_type = 'MODEL' score_limit = 450 if ui_props.asset_type == 'MATERIAL': props = bpy.context.window_manager.blenderkit_mat elif ui_props.asset_type == 'MODEL': props = bpy.context.window_manager.blenderkit_models score_limit = 1000 props.search_keywords = ''#random_search[1] props.search_keywords += f'+is_free:true+score_gte:{score_limit}+order:-created' # random_search[1] # search.search() return {'FINISHED'} def invoke(self, context, event): wm = bpy.context.window_manager img = utils.get_thumbnail('intro.jpg') utils.img_to_preview(img, copy_original=True) self.img = img w, a, r = utils.get_largest_area(area_type='VIEW_3D') if a is not None: a.spaces.active.show_region_ui = True return wm.invoke_props_dialog(self, width=500) def draw_asset_context_menu(layout, context, asset_data, from_panel=False): ui_props = context.scene.blenderkitUI author_id = str(asset_data['author'].get('id')) wm = bpy.context.window_manager layout.operator_context = 'INVOKE_DEFAULT' if from_panel: op = layout.operator('wm.blenderkit_menu_rating_upload', text='Rate') op.asset_name = asset_data['name'] op.asset_id = asset_data['id'] op.asset_type = asset_data['assetType'] if from_panel and wm.get('bkit authors') is not None and author_id is not None: a = bpy.context.window_manager['bkit authors'].get(author_id) if a is not None: # utils.p('author:', a) op = layout.operator('wm.url_open', text="Open Author's Website") if a.get('aboutMeUrl') is not None: op.url = a['aboutMeUrl'] else: op.url = paths.get_author_gallery_url(a['id']) op = layout.operator('view3d.blenderkit_search', text="Show Assets By Author") op.keywords = '' op.author_id = author_id op = layout.operator('view3d.blenderkit_search', text='Search Similar') op.esc = True op.tooltip = 'Search for similar assets in the library' # build search string from description and tags: op.keywords = asset_data['name'] if asset_data.get('description'): op.keywords += ' ' + asset_data.get('description') + ' ' op.keywords += ' '.join(asset_data.get('tags')) if asset_data.get('canDownload') != 0: if len(bpy.context.selected_objects) > 0 and ui_props.asset_type == 'MODEL': aob = bpy.context.active_object if aob is None: aob = bpy.context.selected_objects[0] op = layout.operator('scene.blenderkit_download', text='Replace Active Models') # this checks if the menu got called from right-click in assetbar(then index is 0 - x) or # from a panel(then replacement happens from the active model) if from_panel: # called from addon panel op.asset_base_id = asset_data['assetBaseId'] else: op.asset_index = ui_props.active_index # op.asset_type = ui_props.asset_type op.model_location = aob.location op.model_rotation = aob.rotation_euler op.target_object = aob.name op.material_target_slot = aob.active_material_index op.replace = True op.replace_resolution = False # resolution replacement operator # if asset_data['downloaded'] == 100: # only show for downloaded/used assets # if ui_props.asset_type in ('MODEL', 'MATERIAL'): # layout.menu(OBJECT_MT_blenderkit_resolution_menu.bl_idname) if ui_props.asset_type in ('MODEL', 'MATERIAL', 'HDR') and \ utils.get_param(asset_data, 'textureResolutionMax') is not None and \ utils.get_param(asset_data, 'textureResolutionMax') > 512: s = bpy.context.scene col = layout.column() col.operator_context = 'INVOKE_DEFAULT' if from_panel: # Called from addon panel if asset_data.get('resolution'): op = col.operator('scene.blenderkit_download', text='Replace asset resolution') op.asset_base_id = asset_data['assetBaseId'] if asset_data['assetType'] == 'MODEL': o = utils.get_active_model() op.model_location = o.location op.model_rotation = o.rotation_euler op.target_object = o.name op.material_target_slot = o.active_material_index elif asset_data['assetType'] == 'MATERIAL': aob = bpy.context.active_object op.model_location = aob.location op.model_rotation = aob.rotation_euler op.target_object = aob.name op.material_target_slot = aob.active_material_index op.replace_resolution = True op.replace = False op.invoke_resolution = True op.max_resolution = asset_data.get('max_resolution', 0) # str(utils.get_param(asset_data, 'textureResolutionMax')) elif asset_data['assetBaseId'] in s['assets used'].keys() and asset_data['assetType'] != 'hdr': # HDRs are excluded from replacement, since they are always replaced. # called from asset bar: op = col.operator('scene.blenderkit_download', text='Replace asset resolution') op.asset_index = ui_props.active_index # op.asset_type = ui_props.asset_type op.replace_resolution = True op.replace = False op.invoke_resolution = True o = utils.get_active_model() if o and o.get('asset_data'): if o['asset_data']['assetBaseId'] == bpy.context.window_manager['search results'][ ui_props.active_index]: op.model_location = o.location op.model_rotation = o.rotation_euler else: op.model_location = (0, 0, 0) op.model_rotation = (0, 0, 0) op.max_resolution = asset_data.get('max_resolution', 0) # str(utils.get_param(asset_data, 'textureResolutionMax')) # print('operator res ', resolution) # op.resolution = resolution wm = bpy.context.window_manager profile = wm.get('bkit profile') if profile is not None: # validation if author_id == str(profile['user']['id']) or utils.profile_is_validator(): layout.label(text='Management tools:') row = layout.row() row.operator_context = 'INVOKE_DEFAULT' op = layout.operator('wm.blenderkit_fast_metadata', text='Edit Metadata') op.asset_id = asset_data['id'] op.asset_type = asset_data['assetType'] if asset_data['assetType'] == 'model': op = layout.operator('object.blenderkit_regenerate_thumbnail', text='Regenerate thumbnail') op.asset_index = ui_props.active_index if asset_data['assetType'] == 'material': op = layout.operator('object.blenderkit_regenerate_material_thumbnail', text='Regenerate thumbnail') op.asset_index = ui_props.active_index # op.asset_id = asset_data['id'] # op.asset_type = asset_data['assetType'] if author_id == str(profile['user']['id']): row = layout.row() row.operator_context = 'INVOKE_DEFAULT' op = row.operator('object.blenderkit_change_status', text='Delete') op.asset_id = asset_data['id'] op.state = 'deleted' if utils.profile_is_validator(): layout.label(text='Dev Tools:') op = layout.operator('object.blenderkit_print_asset_debug', text='Print asset debug') op.asset_id = asset_data['id'] # def draw_asset_resolution_replace(self, context, resolution): # layout = self.layout # ui_props = bpy.context.scene.blenderkitUI # # op = layout.operator('scene.blenderkit_download', text=resolution) # if ui_props.active_index == -3: # # This happens if the command is called from addon panel # o = utils.get_active_model() # op.asset_base_id = o['asset_data']['assetBaseId'] # # else: # op.asset_index = ui_props.active_index # # op.asset_type = ui_props.asset_type # if len(bpy.context.selected_objects) > 0: # and ui_props.asset_type == 'MODEL': # aob = bpy.context.active_object # op.model_location = aob.location # op.model_rotation = aob.rotation_euler # op.target_object = aob.name # op.material_target_slot = aob.active_material_index # op.replace_resolution = True # print('operator res ', resolution) # op.resolution = resolution # class OBJECT_MT_blenderkit_resolution_menu(bpy.types.Menu): # bl_label = "Replace Asset Resolution" # bl_idname = "OBJECT_MT_blenderkit_resolution_menu" # # def draw(self, context): # ui_props = context.scene.blenderkitUI # # # sr = bpy.context.window_manager['search results'] # # # sr = bpy.context.window_manager['search results'] # # asset_data = sr[ui_props.active_index] # # for k in resolutions.resolution_props_to_server.keys(): # draw_asset_resolution_replace(self, context, k) class OBJECT_MT_blenderkit_asset_menu(bpy.types.Menu): bl_label = "Asset options:" bl_idname = "OBJECT_MT_blenderkit_asset_menu" def draw(self, context): ui_props = context.scene.blenderkitUI sr = bpy.context.window_manager['search results'] asset_data = sr[ui_props.active_index] draw_asset_context_menu(self.layout, context, asset_data, from_panel=False) def numeric_to_str(s): if s: if s < 1: s = str(round(s, 1)) else: s = str(round(s)) else: s = '-' return s def push_op_left(layout, strength=3): for a in range(0, strength): layout.label(text='') def label_or_url_or_operator(layout, text='', tooltip='', url='', operator=None, operator_kwargs={}, icon_value=None, icon=None): '''automatically switch between different layout options for linking or tooltips''' layout.emboss = 'NONE' if operator is not None: if icon: op = layout.operator(operator, text=text, icon=icon) elif icon_value: op = layout.operator(operator, text=text, icon_value=icon_value) else: op = layout.operator(operator, text=text) for kwarg in operator_kwargs.keys(): if type(operator_kwargs[kwarg]) == str: quoatation = "'" else: quoatation = "" exec(f"op.{kwarg} = {quoatation}{operator_kwargs[kwarg]}{quoatation}") push_op_left(layout, strength=2) return if url != '': if icon: op = layout.operator('wm.blenderkit_url', text=text, icon=icon) elif icon_value: op = layout.operator('wm.blenderkit_url', text=text, icon_value=icon_value) else: op = layout.operator('wm.blenderkit_url', text=text) op.url = url op.tooltip = tooltip push_op_left(layout, strength=5) return if tooltip != '': if icon: op = layout.operator('wm.blenderkit_tooltip', text=text, icon=icon) elif icon_value: op = layout.operator('wm.blenderkit_tooltip', text=text, icon_value=icon_value) else: op = layout.operator('wm.blenderkit_tooltip', text=text) op.tooltip = tooltip # these are here to move the text to left, since operators can only center text by default push_op_left(layout, strength=3) return if icon: layout.label(text=text, icon=icon) elif icon_value: layout.label(text=text, icon_value=icon_value) else: layout.label(text=text) class AssetPopupCard(bpy.types.Operator, ratings_utils.RatingsProperties): """Generate Cycles thumbnail for model assets""" bl_idname = "wm.blenderkit_asset_popup" bl_label = "BlenderKit asset popup" width = 800 @classmethod def poll(cls, context): return True def draw_menu(self, context, layout): # layout = layout.column() draw_asset_context_menu(layout, context, self.asset_data, from_panel=False) def draw_property(self, layout, left, right, icon=None, icon_value=None, url='', tooltip='', operator=None, operator_kwargs={}): right = str(right) row = layout.row() split = row.split(factor=0.35) split.alignment = 'RIGHT' split.label(text=left) split = split.split() split.alignment = 'LEFT' # split for questionmark: if url != '': split = split.split(factor=0.6) label_or_url_or_operator(split, text=right, tooltip=tooltip, url=url, operator=operator, operator_kwargs=operator_kwargs, icon_value=icon_value, icon=icon) # additional questionmark icon where it's important? if url != '': split = split.split() op = split.operator('wm.blenderkit_url', text='', icon='QUESTION') op.url = url op.tooltip = tooltip def draw_asset_parameter(self, layout, key='', pretext='', do_search=False): parameter = utils.get_param(self.asset_data, key) if parameter == None: return if type(parameter) == int: parameter = f"{parameter:,d}" elif type(parameter) == float: parameter = f"{parameter:,.1f}" if do_search: kwargs = { 'esc': True, 'keywords': f'+{key}:{parameter}', 'tooltip': f'search by {parameter}', } self.draw_property(layout, pretext, parameter, operator='view3d.blenderkit_search', operator_kwargs=kwargs) else: self.draw_property(layout, pretext, parameter) def draw_description(self, layout, width=250): if len(self.asset_data['description']) > 0: box = layout.box() box.scale_y = 0.4 box.label(text='Description') box.separator() link_more = utils.label_multiline(box, self.asset_data['description'], width=width, max_lines=10) if link_more: row = box.row() row.scale_y = 2 op = row.operator('wm.blenderkit_url', text='See full description', icon='URL') op.url = paths.get_asset_gallery_url(self.asset_data['assetBaseId']) op.tooltip = 'Read full description on website' box.separator() def draw_properties(self, layout, width=250): # if type(self.asset_data['parameters']) == list: # mparams = utils.params_to_dict(self.asset_data['parameters']) # else: # mparams = self.asset_data['parameters'] mparams = self.asset_data['dictParameters'] pcoll = icons.icon_collections["main"] box = layout.box() box.scale_y = 0.4 box.label(text='Properties') box.separator() if self.asset_data.get('license') == 'cc_zero': t = 'CC Zero ' icon = pcoll['cc0'] else: t = 'Royalty free' icon = pcoll['royalty_free'] self.draw_property(box, 'License', t, # icon_value=icon.icon_id, url="https://www.blenderkit.com/docs/licenses/", tooltip='All BlenderKit assets are available for commercial use. \n' \ 'Click to read more about BlenderKit licenses on the website' ) if upload.can_edit_asset(asset_data=self.asset_data): icon = pcoll[self.asset_data['verificationStatus']] verification_status_tooltips = { 'uploading': "Your asset got stuck during upload. Probably, your file was too large " "or your connection too slow or interrupting. If you have repeated issues, " "please contact us and let us know, it might be a bug", 'uploaded': "Your asset uploaded successfully. Yay! If it's public, " "it's awaiting validation. If it's private, use it", 'on_hold': "Your asset needs some (usually smaller) fixes, " "so we can make it public for everybody." " Please check your email to see the feedback " "that we send to every creator personally", 'rejected': "The asset has serious quality issues, " \ "and it's probable that it might be good to start " \ "all over again or try with something simpler. " \ "You also get personal feedback into your e-mail, " \ "since we believe that together, we can all learn " \ "to become awesome 3D artists", 'deleted': "You deleted this asset", 'validated': "Your asset passed our validation process, " "and is now available to BlenderKit users" } self.draw_property(box, 'Verification', self.asset_data['verificationStatus'], icon_value=icon.icon_id, url="https://www.blenderkit.com/docs/validation-status/", tooltip=verification_status_tooltips[self.asset_data['verificationStatus']] ) # resolution/s resolution = utils.get_param(self.asset_data, 'textureResolutionMax') if resolution is not None: fs = self.asset_data['files'] ress = f"{int(round(resolution / 1024, 0))}K" self.draw_property(box, 'Resolution', ress, tooltip='Maximal resolution of textures in this asset.\n' \ 'Most texture asset have also lower resolutions generated.\n' \ 'Go to BlenderKit add-on import settings to set default resolution') if fs and len(fs) > 2 and utils.profile_is_validator(): resolutions = '' list.sort(fs, key=lambda f: f['fileType']) for f in fs: if f['fileType'].find('resolution') > -1: resolutions += f['fileType'][11:] + ' ' resolutions = resolutions.replace('_', '.') self.draw_property(box, 'Generated', resolutions) self.draw_asset_parameter(box, key='designer', pretext='Designer', do_search=True) self.draw_asset_parameter(box, key='manufacturer', pretext='Manufacturer', do_search=True) self.draw_asset_parameter(box, key='designCollection', pretext='Collection', do_search=True) self.draw_asset_parameter(box, key='designVariant', pretext='Variant') self.draw_asset_parameter(box, key='designYear', pretext='Design year') self.draw_asset_parameter(box, key='faceCount', pretext='Face count') # self.draw_asset_parameter(box, key='thumbnailScale', pretext='Preview scale') # self.draw_asset_parameter(box, key='purePbr', pretext='Pure PBR') # self.draw_asset_parameter(box, key='productionLevel', pretext='Readiness') # self.draw_asset_parameter(box, key='condition', pretext='Condition') if utils.profile_is_validator(): self.draw_asset_parameter(box, key='materialStyle', pretext='Style') self.draw_asset_parameter(box, key='modelStyle', pretext='Style') if utils.get_param(self.asset_data, 'dimensionX'): t = '%s×%s×%s m' % (utils.fmt_length(mparams['dimensionX']), utils.fmt_length(mparams['dimensionY']), utils.fmt_length(mparams['dimensionZ'])) self.draw_property(box, 'Size', t) if self.asset_data.get('filesSize'): fs = self.asset_data['filesSize'] fsmb = fs // (1024 * 1024) fskb = fs % 1024 if fsmb == 0: self.draw_property(box, 'Original size', f'{fskb} KB') else: self.draw_property(box, 'Original size', f'{fsmb} MB') # Tags section # row = box.row() # letters_on_row = 0 # max_on_row = width / 10 # for tag in self.asset_data['tags']: # if tag in ('manifold', 'uv', 'non-manifold'): # # these are sometimes accidentally stored in the lib # continue # # # row.emboss='NONE' # # we need to split wisely # remaining_row = (max_on_row - letters_on_row) / max_on_row # split_factor = (len(tag) / max_on_row) / remaining_row # row = row.split(factor=split_factor) # letters_on_row += len(tag) # if letters_on_row > max_on_row: # letters_on_row = len(tag) # row = box.row() # remaining_row = (max_on_row - letters_on_row) / max_on_row # split_factor = (len(tag) / max_on_row) / remaining_row # row = row.split(factor=split_factor) # # op = row.operator('wm') # op = row.operator('view3d.blenderkit_search', text=tag) # op.tooltip = f'Search items with tag {tag}' # # build search string from description and tags: # op.keywords = f'+tags:{tag}' # self.draw_property(box, 'Tags', self.asset_data['tags']) #TODO make them clickable! # Free/Full plan or private Access plans_tooltip = 'BlenderKit has 2 plans:\n' \ ' * Free plan - more than 50% of all assets\n' \ ' * Full plan - unlimited access to everything\n' \ 'Click to go to subscriptions page' plans_link = 'https://www.blenderkit.com/plans/pricing/' if self.asset_data['isPrivate']: t = 'Private' self.draw_property(box, 'Access', t, icon='LOCKED') elif self.asset_data['isFree']: t = 'Free plan' icon = pcoll['free'] self.draw_property(box, 'Access', t, icon_value=icon.icon_id, tooltip=plans_tooltip, url=plans_link) else: t = 'Full plan' icon = pcoll['full'] self.draw_property(box, 'Access', t, icon_value=icon.icon_id, tooltip=plans_tooltip, url=plans_link) if utils.profile_is_validator(): date = self.asset_data['created'][:10] date = f"{date[8:10]}. {date[5:7]}. {date[:4]}" self.draw_property(box, 'Created', date) if utils.asset_from_newer_blender_version(self.asset_data): # row = box.row() box.alert = True self.draw_property(box, 'Blender version', self.asset_data['sourceAppVersion'], # icon='ERROR', tooltip='Asset is from a newer Blender version and might work incorrectly in your scene', ) box.alert = False box.separator() def draw_author_area(self, context, layout, width=330): self.draw_author(context, layout, width=width) def draw_author(self, context, layout, width=330): image_split = 0.25 text_width = width authors = bpy.context.window_manager['bkit authors'] a = authors.get(self.asset_data['author']['id']) if a is not None: # or a is '' or (a.get('gravatarHash') is not None and a.get('gravatarImg') is None): row = layout.row() author_box = row.box() author_box.scale_y = 0.6 # get text lines closer to each other author_box.label(text='Author') # just one extra line to give spacing if hasattr(self, 'gimg'): author_left = author_box.split(factor=image_split) author_left.template_icon(icon_value=self.gimg.preview.icon_id, scale=7) text_area = author_left.split() text_width = int(text_width * (1 - image_split)) else: text_area = author_box author_right = text_area.column() row = author_right.row() col = row.column() utils.label_multiline(col, text=a['tooltip'], width=text_width) # check if author didn't fill any data about himself and prompt him if that's the case if utils.user_is_owner(asset_data=self.asset_data) and a.get('aboutMe') is not None and len( a.get('aboutMe', '')) == 0: row = col.row() row.enabled = False row.label(text='Please introduce yourself to the community!') op = col.operator('wm.blenderkit_url', text='Edit your profile') op.url = 'https://www.blenderkit.com/profile' op.tooltip = 'Edit your profile on BlenderKit webpage' button_row = author_box.row() button_row.scale_y = 2.0 if a.get('aboutMeUrl') is not None: url = a['aboutMeUrl'] text = url if len(url) > 25: text = url[:25] + '...' else: url = paths.get_author_gallery_url(a['id']) text = "Open Author's Profile" op = button_row.operator('wm.url_open', text=text) op.url = url op = button_row.operator('view3d.blenderkit_search', text="Find Assets By Author") op.esc = True op.keywords = '' op.author_id = self.asset_data['author']['id'] def draw_thumbnail_box(self, layout, width=250): layout.emboss = 'NORMAL' box_thumbnail = layout.box() box_thumbnail.scale_y = .4 box_thumbnail.template_icon(icon_value=self.img.preview.icon_id, scale=width * .12) # op = row.operator('view3d.asset_drag_drop', text='Drag & Drop from here', depress=True) # From here on, only ratings are drawn, which won't be displayed for private assets from now on. if not self.asset_data['isPrivate']: row = box_thumbnail.row() row.alignment = 'EXPAND' # display_ratings = can_display_ratings(self.asset_data) rc = self.asset_data.get('ratingsCount') show_rating_threshold = 0 show_rating_prompt_threshold = 5 if rc: rcount = min(rc['quality'], rc['workingHours']) else: rcount = 0 if rcount >= show_rating_threshold or upload.can_edit_asset(asset_data=self.asset_data): s = numeric_to_str(self.asset_data['score']) q = numeric_to_str(self.asset_data['ratingsAverage'].get('quality')) c = numeric_to_str(self.asset_data['ratingsAverage'].get('workingHours')) else: s = '-' q = '-' c = '-' pcoll = icons.icon_collections["main"] row.emboss = 'NONE' op = row.operator('wm.blenderkit_tooltip', text=str(s), icon_value=pcoll['trophy'].icon_id) op.tooltip = 'Asset score calculated from user ratings. \n\n' \ 'Score = average quality × median complexity × 10*\n\n *Happiness multiplier' row.label(text=' ') tooltip_extension = f'.\n\nRatings results are shown for assets with more than {show_rating_threshold} ratings' op = row.operator('wm.blenderkit_tooltip', text=str(q), icon='SOLO_ON') op.tooltip = f"Quality, average from {rc['quality']} ratings" \ f"{tooltip_extension if rcount <= show_rating_threshold else ''}" row.label(text=' ') op = row.operator('wm.blenderkit_tooltip', text=str(c), icon_value=pcoll['dumbbell'].icon_id) op.tooltip = f"Complexity, median from {rc['workingHours']} ratings" \ f"{tooltip_extension if rcount <= show_rating_threshold else ''}" if rcount <= show_rating_prompt_threshold: box_thumbnail.alert = True box_thumbnail.label(text=f"") box_thumbnail.label( text=f"This asset has only {rcount} rating{'' if rcount == 1 else 's'}, please rate.") # box_thumbnail.label(text=f"Please rate this asset.") row = box_thumbnail.row() row.alert = False row.scale_y = 3 ui_props = bpy.context.scene.blenderkitUI row.prop(ui_props, 'drag_init_button', icon='MOUSE_LMB_DRAG', text='Click / Drag from here', emboss=True) def draw_menu_desc_author(self, context, layout, width=330): box = layout.column() box.emboss = 'NORMAL' # left - tooltip & params row = box.row() split_factor = 0.7 split_left = row.split(factor=split_factor) col = split_left.column() width_left = int(width * split_factor) self.draw_description(col, width=width_left) self.draw_properties(col, width=width_left) # right - menu split_right = split_left.split() col = split_right.column() self.draw_menu(context, col) # author self.draw_author_area(context, box, width=width) # self.draw_author_area(context, box, width=width) # # col = box.column_flow(columns=2) # self.draw_menu(context, col) # # # # self.draw_description(box, width=int(width)) # self.draw_properties(box, width=int(width)) # define enum flags def draw_titlebar(self, context, layout): top_drag_bar = layout.box() bcats = bpy.context.window_manager['bkit_categories'] cat_path = categories.get_category_path(bcats, self.asset_data['category'])[1:] cat_path_names = categories.get_category_name_path(bcats, self.asset_data['category'])[1:] aname = self.asset_data['displayName'] aname = aname[0].upper() + aname[1:] if 1: name_row = top_drag_bar.row() # name_row = name_row.split(factor=0.5) # name_row = name_row.column() # name_row = name_row.row() for i, c in enumerate(cat_path): cat_name = cat_path_names[i] op = name_row.operator('view3d.blenderkit_asset_bar', text=cat_name + ' >', emboss=True) op.do_search = True op.keep_running = True op.tooltip = f"Browse {cat_name} category" op.category = c # name_row.label(text='>') name_row.label(text=aname) push_op_left(name_row, strength=3) op = name_row.operator('view3d.close_popup_button', text='', icon='CANCEL') def draw(self, context): layout = self.layout # top draggable bar with name of the asset top_row = layout.row() self.draw_titlebar(context, top_row) # left side row = layout.row(align=True) split_ratio = 0.45 split_left = row.split(factor=split_ratio) left_column = split_left.column() self.draw_thumbnail_box(left_column, width=int(self.width * split_ratio)) # self.draw_description(left_column, width = int(self.width*split_ratio)) # right split split_right = split_left.split() self.draw_menu_desc_author(context, split_right, width=int(self.width * (1 - split_ratio))) if not utils.user_is_owner(asset_data=self.asset_data): # Draw ratings, but not for owners of assets - doesn't make sense. ratings_box = layout.box() ratings.draw_ratings_menu(self, context, ratings_box) # else: # ratings_box.label('Here you should find ratings, but you can not rate your own assets ;)') tip_box = layout.box() tip_box.label(text=self.tip) def execute(self, context): wm = context.window_manager ui_props = context.scene.blenderkitUI ui_props.draw_tooltip = False sr = bpy.context.window_manager['search results'] asset_data = sr[ui_props.active_index] self.asset_data = asset_data self.img = ui.get_large_thumbnail_image(asset_data) utils.img_to_preview(self.img, copy_original=True) self.asset_type = asset_data['assetType'] self.asset_id = asset_data['id'] # self.tex = utils.get_hidden_texture(self.img) # self.tex.update_tag() authors = bpy.context.window_manager['bkit authors'] a = authors.get(asset_data['author']['id']) if a is not None and a.get('gravatarImg') is not None: self.gimg = utils.get_hidden_image(a['gravatarImg'], a['gravatarHash']) bl_label = asset_data['name'] self.tip = search.get_random_tip() self.tip = self.tip.replace('\n', '') # pre-fill ratings self.prefill_ratings() return wm.invoke_popup(self, width=self.width) class OBJECT_MT_blenderkit_login_menu(bpy.types.Menu): bl_label = "BlenderKit login/signup:" bl_idname = "OBJECT_MT_blenderkit_login_menu" def draw(self, context): layout = self.layout # utils.label_multiline(layout, text=message) draw_login_buttons(layout) class SetCategoryOperator(bpy.types.Operator): """Visit subcategory""" bl_idname = "view3d.blenderkit_set_category" bl_label = "BlenderKit Set Active Category" bl_options = {'REGISTER', 'UNDO', 'INTERNAL'} category: bpy.props.StringProperty( name="Category", description="set this category active", default="") asset_type: bpy.props.StringProperty( name="Asset Type", description="asset type", default="") @classmethod def poll(cls, context): return True def execute(self, context): acat = bpy.context.window_manager['active_category'][self.asset_type] if self.category == '': acat.remove(acat[-1]) else: acat.append(self.category) # we have to write back to wm. Thought this should happen with original list. bpy.context.window_manager['active_category'][self.asset_type] = acat return {'FINISHED'} class ClosePopupButton(bpy.types.Operator): """Close popup window""" bl_idname = "view3d.close_popup_button" bl_label = "Close popup" bl_options = {'REGISTER', 'INTERNAL'} @classmethod def poll(cls, context): return True def win_close(self): VK_ESCAPE = 0x1B ctypes.windll.user32.keybd_event(VK_ESCAPE) return True def mouse_trick(self, context, x, y): # import time context.area.tag_redraw() w = context.window w.cursor_warp(w.x + 15, w.y + w.height - 15); # time.sleep(.12) w.cursor_warp(x, y); context.area.tag_redraw() def invoke(self, context, event): if platform.system() == 'Windows': self.win_close() else: self.mouse_trick(context, event.mouse_x, event.mouse_y) return {'FINISHED'} class UrlPopupDialog(bpy.types.Operator): """Generate Cycles thumbnail for model assets""" bl_idname = "wm.blenderkit_url_dialog" bl_label = "BlenderKit message:" bl_options = {'REGISTER', 'INTERNAL'} url: bpy.props.StringProperty( name="Url", description="url", default="") link_text: bpy.props.StringProperty( name="Url", description="url", default="Go to website") message: bpy.props.StringProperty( name="Text", description="text", default="") # @classmethod # def poll(cls, context): # return bpy.context.view_layer.objects.active is not None def draw(self, context): layout = self.layout utils.label_multiline(layout, text=self.message, width=300) layout.active_default = True op = layout.operator("wm.url_open", text=self.link_text, icon='QUESTION') if not utils.user_logged_in(): utils.label_multiline(layout, text='Already subscribed? You need to login to access your Full Plan.', width=300) layout.operator_context = 'EXEC_DEFAULT' layout.operator("wm.blenderkit_login", text="Login", icon='URL').signup = False op.url = self.url def execute(self, context): return {'FINISHED'} def invoke(self, context, event): wm = context.window_manager return wm.invoke_props_dialog(self, width=300) class LoginPopupDialog(bpy.types.Operator): """Popup a dialog which enables the user to log in after being logged out automatically.""" bl_idname = "wm.blenderkit_login_dialog" bl_label = "BlenderKit login" bl_options = {'REGISTER', 'INTERNAL'} message: bpy.props.StringProperty( name="Message", description="", default="Your were logged out from BlenderKit. Please login again. ") # @classmethod # def poll(cls, context): # return bpy.context.view_layer.objects.active is not None def draw(self, context): layout = self.layout utils.label_multiline(layout, text=self.message) layout.active_default = True op = layout.operator op = layout.operator("wm.url_open", text=self.link_text, icon='QUESTION') op.url = self.url def execute(self, context): # start_thumbnailer(self, context) return {'FINISHED'} def invoke(self, context, event): wm = context.window_manager return wm.invoke_props_dialog(self) def draw_panel_categories(self, context): s = context.scene ui_props = s.blenderkitUI user_preferences = bpy.context.preferences.addons['blenderkit'].preferences layout = self.layout # row = layout.row() # row.prop(ui_props, 'asset_type', expand=True, icon_only=True) wm = bpy.context.window_manager if wm.get('bkit_categories') == None: return col = layout.column(align=True) if wm.get('active_category') is not None: acat = wm['active_category'][ui_props.asset_type] if len(acat) > 1: # we are in subcategory, so draw the parent button op = col.operator('view3d.blenderkit_set_category', text='...', icon='FILE_PARENT') op.asset_type = ui_props.asset_type op.category = '' cats = categories.get_category(wm['bkit_categories'], cat_path=acat) # draw freebies only in models parent category # if ui_props.asset_type == 'MODEL' and len(acat) == 1: # op = col.operator('view3d.blenderkit_asset_bar', text='freebies') # op.free_only = True for c in cats['children']: if c['assetCount'] > 0 or (utils.profile_is_validator() and user_preferences.categories_fix): row = col.row(align=True) if len(c['children']) > 0 and c['assetCount'] > 15 or ( utils.profile_is_validator() and user_preferences.categories_fix): row = row.split(factor=.8, align=True) # row = split.split() ctext = '%s (%i)' % (c['name'], c['assetCount']) preferences = bpy.context.preferences.addons['blenderkit'].preferences if preferences.experimental_features: op = row.operator('view3d.blenderkit_asset_bar_widget', text=ctext) else: op = row.operator('view3d.blenderkit_asset_bar', text=ctext) op.do_search = True op.keep_running = True op.tooltip = f"Browse {c['name']} category" op.category = c['slug'] if len(c['children']) > 0 and c['assetCount'] > 15 or ( utils.profile_is_validator() and user_preferences.categories_fix): # row = row.split() op = row.operator('view3d.blenderkit_set_category', text='>>') op.asset_type = ui_props.asset_type op.category = c['slug'] # for c1 in c['children']: # if c1['assetCount']>0: # row = col.row() # split = row.split(percentage=.2) # row = split.split() # row = split.split() # ctext = '%s (%i)' % (c1['name'], c1['assetCount']) # op = row.operator('view3d.blenderkit_search', text=ctext) # op.category = c1['slug'] class VIEW3D_PT_blenderkit_downloads(Panel): bl_category = "BlenderKit" bl_idname = "VIEW3D_PT_blenderkit_downloads" bl_space_type = 'VIEW_3D' bl_region_type = 'UI' bl_label = "Downloads" @classmethod def poll(cls, context): return len(download.download_threads) > 0 def draw(self, context): layout = self.layout for i, threaddata in enumerate(download.download_threads): tcom = threaddata[2] asset_data = threaddata[1] row = layout.row() row.label(text=asset_data['name']) row.label(text=str(int(tcom.progress)) + ' %') op = row.operator('scene.blenderkit_download_kill', text='', icon='CANCEL') op.thread_index = i if tcom.passargs.get('retry_counter', 0) > 0: row = layout.row() row.label(text='failed. retrying ... ', icon='ERROR') row.label(text=str(tcom.passargs["retry_counter"])) layout.separator() def header_search_draw(self, context): '''Top bar menu in 3D view''' if not utils.guard_from_crash(): return; preferences = bpy.context.preferences.addons['blenderkit'].preferences if preferences.search_in_header: layout = self.layout s = bpy.context.scene wm = bpy.context.window_manager ui_props = s.blenderkitUI if ui_props.asset_type == 'MODEL': props = wm.blenderkit_models if ui_props.asset_type == 'MATERIAL': props = wm.blenderkit_mat if ui_props.asset_type == 'BRUSH': props = wm.blenderkit_brush if ui_props.asset_type == 'HDR': props = wm.blenderkit_HDR if ui_props.asset_type == 'SCENE': props = wm.blenderkit_scene # the center snap menu is in edit and object mode if tool settings are off. if context.space_data.show_region_tool_header == True or context.mode[:4] not in ('EDIT', 'OBJE'): layout.separator_spacer() layout.prop(ui_props, "asset_type", expand=True, icon_only=True, text='', icon='URL') layout.prop(props, "search_keywords", text="", icon='VIEWZOOM') draw_assetbar_show_hide(layout, props) layout.popover(panel="VIEW3D_PT_blenderkit_categories", text="", icon='OUTLINER') pcoll = icons.icon_collections["main"] if props.use_filters: icon_id = pcoll['filter_active'].icon_id else: icon_id = pcoll['filter'].icon_id if ui_props.asset_type == 'MODEL': layout.popover(panel="VIEW3D_PT_blenderkit_advanced_model_search", text="", icon_value=icon_id) elif ui_props.asset_type == 'MATERIAL': layout.popover(panel="VIEW3D_PT_blenderkit_advanced_material_search", text="", icon_value=icon_id) elif ui_props.asset_type == 'HDR': layout.popover(panel="VIEW3D_PT_blenderkit_advanced_HDR_search", text="", icon_value=icon_id) def ui_message(title, message): def draw_message(self, context): layout = self.layout utils.label_multiline(layout, text=message, width=400) bpy.context.window_manager.popup_menu(draw_message, title=title, icon='INFO') # We can store multiple preview collections here, # however in this example we only store "main" preview_collections = {} classes = ( SetCategoryOperator, VIEW3D_PT_blenderkit_profile, VIEW3D_PT_blenderkit_login, VIEW3D_PT_blenderkit_unified, VIEW3D_PT_blenderkit_advanced_model_search, VIEW3D_PT_blenderkit_advanced_material_search, VIEW3D_PT_blenderkit_advanced_HDR_search, VIEW3D_PT_blenderkit_categories, VIEW3D_PT_blenderkit_import_settings, VIEW3D_PT_blenderkit_model_properties, NODE_PT_blenderkit_material_properties, # VIEW3D_PT_blenderkit_ratings, VIEW3D_PT_blenderkit_downloads, # OBJECT_MT_blenderkit_resolution_menu, OBJECT_MT_blenderkit_asset_menu, OBJECT_MT_blenderkit_login_menu, AssetPopupCard, UrlPopupDialog, ClosePopupButton, BlenderKitWelcomeOperator, ) def register_ui_panels(): for c in classes: bpy.utils.register_class(c) bpy.types.VIEW3D_MT_editor_menus.append(header_search_draw) def unregister_ui_panels(): bpy.types.VIEW3D_MT_editor_menus.remove(header_search_draw) for c in classes: # print('unregister', c) bpy.utils.unregister_class(c)