blender-addons/blenderkit/ui_panels.py
Vilem Duha 29db4b5feb BlenderKit: fix category display
was actually showing slugs, now shows names with links
fix long descriptions issue - now has a 'more' button to read the rest online
(label_multiline now has  a max_lines parameter and returns True if max lenght was reached)
fix avatars to match server
2021-07-26 08:08:58 +02:00

2203 lines
83 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ##### 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
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
props = s.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):
s = context.scene
props = s.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):
s = context.scene
props = s.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.scene
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):
s = context.scene
props = s.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):
s = context.scene
props = s.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, "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):
s = context.scene
props = s.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')
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):
s = context.scene
props = s.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
ui_props = s.blenderkitUI
if ui_props.asset_type == 'MODEL':
# noinspection PyCallByClass
props = s.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 = s.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 = s.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]
bpy.context.scene.blenderkit_mat.search_keywords = ''#random_search[1]
bpy.context.scene.blenderkit_mat.search_keywords = '+is_free:true+score_gte:1000+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.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:
s = str(round(s))
else:
s = '-'
return s
def push_op_left(layout, strength =5):
for a in range(0, strength):
layout.label(text='')
def label_or_url(layout, text='', tooltip='', url='', icon_value=None, icon=None):
'''automatically switch between different layout options for linking or tooltips'''
layout.emboss = 'NONE'
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)
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)
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=''):
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(split, text=right, tooltip=tooltip, url=url, 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=''):
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}"
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']
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')
self.draw_asset_parameter(box, key='manufacturer', pretext='Manufacturer') # TODO make them clickable!
self.draw_asset_parameter(box, key='designCollection', pretext='Collection')
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')
self.draw_asset_parameter(box, key='material_style', pretext='Style')
self.draw_asset_parameter(box, key='model_style', 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)
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.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)
# row = box_thumbnail.row()
# row.scale_y = 3
# op = row.operator('view3d.asset_drag_drop', text='Drag & Drop from here', depress=True)
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 averaged user ratings. \n\n' \
'Score = quality × 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, average 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.")
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))
def draw(self, context):
ui_props = context.scene.blenderkitUI
sr = bpy.context.window_manager['search results']
asset_data = sr[ui_props.active_index]
self.asset_data = asset_data
layout = self.layout
# top draggabe bar with name of the asset
top_row = layout.row()
top_drag_bar = top_row.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 = 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)
# for i,c in enumerate(cat_path_names):
# cat_path_names[i] = c.capitalize()
# cat_path_names_string = ' > '.join(cat_path_names)
# # box.label(text=cat_path)
#
#
#
#
# # name_row.label(text=' ')
# top_drag_bar.label(text=f'{cat_path_names_string} > {aname}')
# 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=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.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.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 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):
# start_thumbnailer(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
ui_props = s.blenderkitUI
if ui_props.asset_type == 'MODEL':
props = s.blenderkit_models
if ui_props.asset_type == 'MATERIAL':
props = s.blenderkit_mat
if ui_props.asset_type == 'BRUSH':
props = s.blenderkit_brush
if ui_props.asset_type == 'HDR':
props = s.blenderkit_HDR
if ui_props.asset_type == 'SCENE':
props = s.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)
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_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,
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)