4371 lines
156 KiB
Python
4371 lines
156 KiB
Python
# ##### 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 #####
|
||
|
||
import logging
|
||
import os
|
||
import random
|
||
import time
|
||
from webbrowser import open_new_tab
|
||
|
||
import bpy
|
||
from bpy.props import IntProperty, StringProperty, FloatVectorProperty, EnumProperty
|
||
from bpy.types import Context, Menu, Panel, UILayout
|
||
|
||
from . import (
|
||
addon_updater_ops,
|
||
asset_bar_op,
|
||
autothumb,
|
||
categories,
|
||
client_lib,
|
||
comments_utils,
|
||
datas,
|
||
download,
|
||
global_vars,
|
||
icons,
|
||
paths,
|
||
ratings,
|
||
ratings_utils,
|
||
search,
|
||
ui,
|
||
upload,
|
||
utils,
|
||
)
|
||
|
||
|
||
ACCEPTABLE_ENGINES = ("CYCLES", "BLENDER_EEVEE", "BLENDER_EEVEE_NEXT")
|
||
|
||
bk_logger = logging.getLogger(__name__)
|
||
|
||
last_time_overlay_panel_active = 0.0
|
||
|
||
|
||
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):
|
||
asset_type_text = asset_type.lower()
|
||
if asset_type == "MODEL":
|
||
url = paths.BLENDERKIT_MODEL_UPLOAD_INSTRUCTIONS_URL
|
||
if asset_type == "MATERIAL":
|
||
url = paths.BLENDERKIT_MATERIAL_UPLOAD_INSTRUCTIONS_URL
|
||
if asset_type == "BRUSH":
|
||
url = paths.BLENDERKIT_BRUSH_UPLOAD_INSTRUCTIONS_URL
|
||
if asset_type == "SCENE":
|
||
url = paths.BLENDERKIT_SCENE_UPLOAD_INSTRUCTIONS_URL
|
||
if asset_type == "HDR":
|
||
asset_type_text = asset_type
|
||
url = paths.BLENDERKIT_HDR_UPLOAD_INSTRUCTIONS_URL
|
||
if asset_type == "NODEGROUP":
|
||
asset_type_text = asset_type
|
||
url = "" # paths.BLENDERKIT_NODEGROUP_UPLOAD_INSTRUCTIONS_URL
|
||
if asset_type == "PRINTABLE":
|
||
url = (
|
||
paths.BLENDERKIT_PRINTABLE_UPLOAD_INSTRUCTIONS_URL
|
||
) # Reuse model instructions since prints are similar
|
||
if asset_type == "ADDON":
|
||
asset_type_text = asset_type
|
||
url = paths.BLENDERKIT_ADDON_UPLOAD_INSTRUCTIONS_URL
|
||
op = layout.operator(
|
||
"wm.url_open", text=f"Read {asset_type} upload instructions", icon="QUESTION"
|
||
)
|
||
op.url = 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)
|
||
|
||
# PRE-RELEASED WARNING
|
||
if props.is_private == "PUBLIC" and bpy.app.version_cycle != "release":
|
||
layout.row()
|
||
utils.label_multiline(
|
||
layout,
|
||
text="Uploading from Alpha, Beta, or Release Candidate versions of Blender is not recommended. Please use a Stable version.",
|
||
icon="ERROR",
|
||
width=210,
|
||
)
|
||
|
||
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(
|
||
"wm.blenderkit_url", text="Edit Details", icon="GREASEPENCIL"
|
||
)
|
||
op.url = f"{paths.BLENDERKIT_USER_ASSETS_URL}/{props.asset_base_id}/?edit#"
|
||
|
||
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')
|
||
row = layout.row()
|
||
if props.is_private == "PUBLIC" and props.category == "NONE":
|
||
row.alert = True
|
||
row.prop(props, "category")
|
||
if props.category != "NONE" and props.subcategory != "EMPTY":
|
||
row = layout.row()
|
||
if props.is_private == "PUBLIC" and props.subcategory == "NONE":
|
||
row.alert = True
|
||
row.prop(props, "subcategory")
|
||
if props.subcategory != "NONE" and props.subcategory1 != "EMPTY":
|
||
row = layout.row()
|
||
if props.is_private == "PUBLIC" and props.subcategory1 == "NONE":
|
||
row.alert = True
|
||
row.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 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.window_manager.blenderkitUI
|
||
|
||
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
|
||
ui_props = wm.blenderkitUI
|
||
|
||
layout = self.layout
|
||
row = layout.row()
|
||
row.prop(ui_props, "search_keywords", text="", icon="VIEWZOOM")
|
||
draw_assetbar_show_hide(row, props)
|
||
|
||
utils.label_multiline(layout, text=props.report)
|
||
|
||
|
||
def draw_panel_addon_search(self, context):
|
||
wm = context.window_manager
|
||
ui_props = wm.blenderkitUI
|
||
addon_props = wm.blenderkit_addon
|
||
|
||
layout = self.layout
|
||
row = layout.row()
|
||
row.prop(ui_props, "search_keywords", text="", icon="VIEWZOOM")
|
||
draw_assetbar_show_hide(row, addon_props)
|
||
|
||
utils.label_multiline(layout, text=addon_props.report)
|
||
|
||
|
||
def draw_panel_nodegroup_upload(self, context):
|
||
layout = self.layout
|
||
ui_props = bpy.context.window_manager.blenderkitUI
|
||
layout.enabled = True
|
||
|
||
layout.template_ID(ui_props, "nodegroup_upload")
|
||
nodegroup = utils.get_active_nodegroup()
|
||
|
||
if nodegroup is not None:
|
||
props = nodegroup.blenderkit
|
||
|
||
layout = self.layout
|
||
|
||
draw_upload_common(layout, props, "NODEGROUP", context)
|
||
layout.prop(props, "thumbnail")
|
||
|
||
|
||
def draw_panel_nodegroup_search(self, context):
|
||
s = context.scene
|
||
wm = context.window_manager
|
||
props = wm.blenderkit_nodegroup
|
||
ui_props = wm.blenderkitUI
|
||
|
||
layout = self.layout
|
||
row = layout.row()
|
||
row.prop(ui_props, "search_keywords", text="", icon="VIEWZOOM")
|
||
draw_assetbar_show_hide(row, props)
|
||
|
||
utils.label_multiline(layout, text=props.report)
|
||
|
||
|
||
def draw_common_filters(layout, ui_props):
|
||
"""Draw common filter elements shared by multiple asset type panels.
|
||
|
||
Args:
|
||
layout: The UI layout to draw in
|
||
ui_props: The UI properties containing filter settings
|
||
"""
|
||
layout.separator()
|
||
|
||
row = layout.row()
|
||
row.prop(ui_props, "search_bookmarks", text="Bookmarks", icon="BOOKMARKS")
|
||
row.prop(ui_props, "own_only", icon="USER")
|
||
layout.prop(ui_props, "free_only")
|
||
layout.prop(ui_props, "quality_limit", slider=True)
|
||
layout.prop(ui_props, "search_license")
|
||
layout.prop(ui_props, "search_order_by")
|
||
|
||
|
||
def draw_thumbnail_upload_panel(layout, props):
|
||
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):
|
||
"""Draw upload panel for model and printable assets"""
|
||
ob = utils.get_active_model()
|
||
while ob.parent is not None:
|
||
ob = ob.parent
|
||
props = ob.blenderkit
|
||
|
||
layout = self.layout
|
||
asset_type = bpy.context.window_manager.blenderkitUI.asset_type
|
||
|
||
draw_upload_common(layout, props, asset_type, context)
|
||
|
||
# Add the photo thumbnail field only for printable assets
|
||
|
||
if asset_type == "PRINTABLE":
|
||
layout.prop(props, "photo_thumbnail_will_upload_on_website")
|
||
if not props.photo_thumbnail_will_upload_on_website:
|
||
col = layout.column()
|
||
prop_needed(col, props, "photo_thumbnail", props.photo_thumbnail)
|
||
|
||
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 ACCEPTABLE_ENGINES:
|
||
col.operator(
|
||
"object.blenderkit_generate_thumbnail",
|
||
text="Generate thumbnail",
|
||
icon="IMAGE",
|
||
)
|
||
|
||
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 = asset_type
|
||
op.process_type = "THUMBNAILER"
|
||
elif props.thumbnail_generating_state != "":
|
||
utils.label_multiline(layout, text=props.thumbnail_generating_state)
|
||
|
||
# Only show these properties for MODEL type
|
||
if asset_type == "MODEL":
|
||
layout.prop(props, "style")
|
||
layout.prop(props, "production_level")
|
||
layout.prop(props, "condition")
|
||
layout.prop(props, "pbr")
|
||
|
||
design_box = layout.box()
|
||
design_box.alignment = "EXPAND"
|
||
design_box.label(text="Design properties:")
|
||
design_box.prop(props, "manufacturer")
|
||
design_box.prop(props, "designer")
|
||
design_box.prop(props, "design_collection")
|
||
design_box.prop(props, "design_variant")
|
||
design_box.prop(props, "use_design_year")
|
||
if props.use_design_year:
|
||
design_box.prop(props, "design_year")
|
||
|
||
row = layout.row()
|
||
row.prop(props, "work_hours")
|
||
|
||
# CONTENT FLAGS
|
||
content_flag_box = layout.box()
|
||
content_flag_box.alignment = "EXPAND"
|
||
content_flag_box.label(text="Sensitive Content Flags:")
|
||
content_flag_box.prop(props, "sexualized_content")
|
||
|
||
|
||
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")
|
||
|
||
|
||
def draw_assetbar_show_hide(layout, props):
|
||
s = bpy.context.scene
|
||
ui_props = bpy.context.window_manager.blenderkitUI
|
||
|
||
if ui_props.assetbar_on:
|
||
icon = "HIDE_OFF"
|
||
ttip = "Click to Hide Asset Bar.\nShortcut: ;"
|
||
else:
|
||
icon = "HIDE_ON"
|
||
ttip = "Click to Show Asset Bar.\nShortcut: ;"
|
||
|
||
op = layout.operator("view3d.blenderkit_asset_bar_widget", 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
|
||
ui_props = wm.blenderkitUI
|
||
|
||
layout = self.layout
|
||
|
||
row = layout.row()
|
||
row.prop(ui_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_URL
|
||
)
|
||
|
||
|
||
def draw_panel_scene_search(self, context):
|
||
wm = bpy.context.window_manager
|
||
props = wm.blenderkit_scene
|
||
ui_props = wm.blenderkitUI
|
||
layout = self.layout
|
||
row = layout.row()
|
||
row.prop(ui_props, "search_keywords", text="", icon="VIEWZOOM")
|
||
draw_assetbar_show_hide(row, props)
|
||
utils.label_multiline(layout, text=props.report)
|
||
layout.separator()
|
||
|
||
|
||
def draw_model_context_menu(self, context):
|
||
# draw asset properties here
|
||
layout = self.layout
|
||
|
||
o = utils.get_active_model()
|
||
if not o:
|
||
return
|
||
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 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):
|
||
if bpy.context.view_layer.objects.active is None:
|
||
return False
|
||
preferences = bpy.context.preferences.addons[__package__].preferences
|
||
return not preferences.sidebar_panels
|
||
|
||
def draw(self, context):
|
||
draw_model_context_menu(self, context)
|
||
|
||
|
||
class VIEW3D_MT_blenderkit_model_properties(Menu):
|
||
bl_idname = "VIEW3D_MT_blenderkit_model_properties"
|
||
bl_label = "Selected Model"
|
||
|
||
def draw(self, context):
|
||
draw_model_context_menu(self, context)
|
||
|
||
|
||
class NODE_PT_blenderkit_nodegroup_properties(Panel):
|
||
bl_category = "BlenderKit"
|
||
bl_idname = "NODE_PT_blenderkit_nodegroup_properties"
|
||
bl_space_type = "NODE_EDITOR"
|
||
bl_region_type = "UI"
|
||
bl_label = "Selected Geonode tool"
|
||
# bl_context = "editmode"
|
||
|
||
@classmethod
|
||
def poll(cls, context):
|
||
if bpy.context.space_data.tree_type != "GeometryNodeTree":
|
||
return False
|
||
if not bpy.context.space_data.edit_tree:
|
||
return False
|
||
return bpy.context.space_data.edit_tree.is_tool
|
||
|
||
def draw(self, context):
|
||
# draw asset properties here
|
||
layout = self.layout
|
||
|
||
et = bpy.context.space_data.edit_tree
|
||
if et.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(et, "name")
|
||
|
||
if et.get("asset_data") is not None:
|
||
ad = et["asset_data"]
|
||
layout.label(text=str(ad["name"]))
|
||
|
||
layout.label(text="Asset tools:")
|
||
draw_asset_context_menu(self.layout, context, ad, from_panel=True)
|
||
|
||
|
||
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):
|
||
if bpy.context.space_data.tree_type != "ShaderNodeTree":
|
||
return False
|
||
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)
|
||
|
||
# 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, layout, index=0):
|
||
### draws single asset rating.
|
||
# Todo: resolve multiple objects for display, now the props are on respective panel, which isn't great.
|
||
|
||
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" % self.asset_data["assetType"])
|
||
tpath = os.path.join(directory, self.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=self.asset_data["name"])
|
||
ratings.draw_ratings_menu(
|
||
bpy.context.window_manager.blenderkit_ratings[index], context, col
|
||
)
|
||
|
||
|
||
class VIEW3D_PT_blenderkit_ratings(Panel, ratings_utils.RatingProperties):
|
||
bl_category = "BlenderKit"
|
||
bl_idname = "VIEW3D_PT_blenderkit_ratings"
|
||
bl_space_type = "VIEW_3D"
|
||
bl_region_type = "UI"
|
||
bl_label = "Rate assets"
|
||
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:"
|
||
)
|
||
ad = assets[0].get("asset_data")
|
||
reference = bpy.context.window_manager.blenderkit_ratings[0]
|
||
reference.asset_data = ad
|
||
reference.asset_id = self.asset_data["id"]
|
||
reference.asset_type = reference.asset_data["assetType"]
|
||
draw_rating_asset(reference, context, layout, index=0)
|
||
|
||
|
||
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 = ""
|
||
bl_options = {"DEFAULT_CLOSED"}
|
||
|
||
@classmethod
|
||
def poll(cls, context):
|
||
preferences = bpy.context.preferences.addons[__package__].preferences
|
||
return not preferences.sidebar_panels
|
||
|
||
def draw_header(self, context):
|
||
layout = self.layout
|
||
layout.emboss = "NORMAL"
|
||
user_preferences = bpy.context.preferences.addons[__package__].preferences
|
||
if user_preferences.api_key != "":
|
||
layout.label(text="BlenderKit Profile", icon="USER")
|
||
else:
|
||
layout.label(text="BlenderKit Login", icon="USER")
|
||
|
||
def draw(self, context):
|
||
layout = self.layout
|
||
# don't draw when not online
|
||
|
||
if not global_vars.CLIENT_RUNNING:
|
||
layout.label(text="Client not running")
|
||
return
|
||
|
||
user_preferences = bpy.context.preferences.addons[__package__].preferences
|
||
|
||
if user_preferences.login_attempt:
|
||
draw_login_progress(layout)
|
||
return
|
||
|
||
if user_preferences.api_key != "":
|
||
me = global_vars.BKIT_PROFILE
|
||
if me is not None:
|
||
# profile picture is retrieved from author's list, for coherency we store the profile images there.
|
||
authors = global_vars.BKIT_AUTHORS
|
||
me_id = int(me.id)
|
||
if authors.get(me_id) is not None and authors[me_id].gravatarImg:
|
||
profile_img = autothumb.get_texture_ui(
|
||
authors[me_id].gravatarImg, ".blenderkit_profile_picture"
|
||
)
|
||
if profile_img and profile_img.image:
|
||
# draw the profile picture
|
||
box = layout.box()
|
||
box.template_icon(
|
||
icon_value=profile_img.image.preview.icon_id, scale=6.0
|
||
)
|
||
|
||
# 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.currentPlanName is not None:
|
||
pcoll = icons.icon_collections["main"]
|
||
if me.currentPlanName == "Free":
|
||
my_icon = pcoll["free"]
|
||
else:
|
||
my_icon = pcoll["full"]
|
||
|
||
row = layout.row()
|
||
row.label(text="My plan:")
|
||
row.label(
|
||
text=f"{me.currentPlanName} plan", icon_value=my_icon.icon_id
|
||
)
|
||
if me.currentPlanName == "Free":
|
||
layout.operator(
|
||
"wm.url_open", text="Change plan", icon="URL"
|
||
).url = paths.BLENDERKIT_PLANS_URL
|
||
|
||
# STORAGE STATISTICS
|
||
if (
|
||
me.sumPrivateAssetFilesSize != None
|
||
and me.remainingPrivateQuota != None
|
||
):
|
||
plan_storage = (
|
||
me.sumPrivateAssetFilesSize + me.remainingPrivateQuota
|
||
)
|
||
sum_str = utils.files_size_to_text(plan_storage)
|
||
row = layout.row()
|
||
row.label(text=f"Plan storage:")
|
||
row.label(text=sum_str)
|
||
if me.remainingPrivateQuota is not None:
|
||
row = layout.row()
|
||
size_str = utils.files_size_to_text(me.remainingPrivateQuota)
|
||
row.label(text=f"Remaining:")
|
||
row.label(text=size_str)
|
||
|
||
layout.operator("wm.url_open", text="See my uploads", icon="URL").url = (
|
||
paths.BLENDERKIT_USER_ASSETS_URL
|
||
)
|
||
|
||
draw_login_buttons(layout)
|
||
if user_preferences.api_key == "":
|
||
layout.label(text="Log in to bookmark assets.")
|
||
|
||
addon_updater_ops.update_notice_box_ui(self, context)
|
||
|
||
|
||
class MarkNotificationRead(bpy.types.Operator):
|
||
"""Mark notification as read here and also on BlenderKit server"""
|
||
|
||
bl_idname = "wm.blenderkit_mark_notification_read"
|
||
bl_label = "Mark notification as read"
|
||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||
|
||
notification_id: bpy.props.IntProperty( # type: ignore[valid-type]
|
||
name="Id", description="notification id", default=-1
|
||
)
|
||
|
||
@classmethod
|
||
def poll(cls, context):
|
||
return True
|
||
|
||
def execute(self, context):
|
||
notifications = global_vars.DATA["bkit notifications"]
|
||
for n in notifications["results"]:
|
||
if n["id"] == self.notification_id:
|
||
n["unread"] = 0
|
||
comments_utils.check_notifications_read()
|
||
client_lib.mark_notification_read(self.notification_id)
|
||
return {"FINISHED"}
|
||
|
||
|
||
class MarkAllNotificationsRead(bpy.types.Operator):
|
||
"""Mark all notifications as read here and also on BlenderKit server"""
|
||
|
||
bl_idname = "wm.blenderkit_mark_notifications_read_all"
|
||
bl_label = "Mark all notifications as read"
|
||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||
|
||
@classmethod
|
||
def poll(cls, context):
|
||
return True
|
||
|
||
def execute(self, context):
|
||
notifications = global_vars.DATA["bkit notifications"]
|
||
for n in notifications.get("results"):
|
||
if n["unread"] == 1:
|
||
n["unread"] = 0
|
||
client_lib.mark_notification_read(n["id"])
|
||
|
||
comments_utils.check_notifications_read()
|
||
return {"FINISHED"}
|
||
|
||
|
||
class NotificationOpenTarget(bpy.types.Operator):
|
||
"""Open notification target and mark notification as read"""
|
||
|
||
bl_idname = "wm.blenderkit_open_notification_target"
|
||
bl_label = ""
|
||
bl_description = "Open notification target and mark notification as read"
|
||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||
|
||
tooltip: bpy.props.StringProperty(default="Open a web page") # type: ignore[valid-type]
|
||
url: bpy.props.StringProperty( # type: ignore[valid-type]
|
||
default="Runs search and displays the asset bar at the same time"
|
||
)
|
||
notification_id: bpy.props.IntProperty( # type: ignore[valid-type]
|
||
name="Id", description="notification id", default=-1
|
||
)
|
||
|
||
@classmethod
|
||
def description(cls, context, properties):
|
||
return properties.tooltip
|
||
|
||
def execute(self, context):
|
||
bpy.ops.wm.blenderkit_mark_notification_read(
|
||
notification_id=self.notification_id
|
||
)
|
||
bpy.ops.wm.url_open(url=self.url)
|
||
return {"FINISHED"}
|
||
|
||
|
||
class UpvoteComment(bpy.types.Operator):
|
||
"""Up or downvote comment"""
|
||
|
||
bl_idname = "wm.blenderkit_upvote_comment"
|
||
bl_label = "BlenderKit up-downvote comment"
|
||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||
|
||
asset_id: StringProperty( # type: ignore[valid-type]
|
||
name="Asset Base Id",
|
||
description="Unique id of the asset (hidden)",
|
||
default="",
|
||
options={"SKIP_SAVE"},
|
||
)
|
||
|
||
comment_id: bpy.props.IntProperty(name="Id", description="comment id", default=-1) # type: ignore[valid-type]
|
||
|
||
flag: bpy.props.StringProperty( # type: ignore[valid-type]
|
||
name="flag", description="Upvote/downvote comment", default="like"
|
||
)
|
||
|
||
@classmethod
|
||
def poll(cls, context):
|
||
return True
|
||
|
||
def execute(self, context):
|
||
user_preferences = bpy.context.preferences.addons[__package__].preferences
|
||
api_key = user_preferences.api_key
|
||
comments = comments_utils.get_comments_local(self.asset_id)
|
||
profile = global_vars.BKIT_PROFILE
|
||
for comment in comments:
|
||
if comment["id"] != self.comment_id:
|
||
continue
|
||
comment["flags"].append({"flag": self.flag, "user": "", "id": profile.id})
|
||
for flag in comment["flags"]:
|
||
if flag["id"] == profile.id and flag["flag"] != self.flag:
|
||
comment["flags"].remove(flag)
|
||
break
|
||
client_lib.feedback_comment(self.asset_id, self.comment_id, api_key, self.flag)
|
||
return {"FINISHED"}
|
||
|
||
|
||
class SetPrivateComment(bpy.types.Operator):
|
||
"""Set comment private or public"""
|
||
|
||
bl_idname = "wm.blenderkit_is_private_comment"
|
||
bl_label = "BlenderKit set comment or thread private or public"
|
||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||
|
||
asset_id: StringProperty( # type: ignore[valid-type]
|
||
name="Asset Base Id",
|
||
description="Unique id of the asset (hidden)",
|
||
default="",
|
||
options={"SKIP_SAVE"},
|
||
)
|
||
|
||
comment_id: bpy.props.IntProperty(name="Id", description="comment id", default=-1) # type: ignore[valid-type]
|
||
|
||
is_private: bpy.props.BoolProperty( # type: ignore[valid-type]
|
||
name="Is private",
|
||
description="set comment/thread private or public",
|
||
default=False,
|
||
)
|
||
|
||
@classmethod
|
||
def poll(cls, context):
|
||
return True
|
||
|
||
def execute(self, context):
|
||
user_preferences = bpy.context.preferences.addons[__package__].preferences
|
||
api_key = user_preferences.api_key
|
||
comments = comments_utils.get_comments_local(self.asset_id)
|
||
if comments is not None:
|
||
for comment in comments:
|
||
if comment["id"] == self.comment_id:
|
||
comment["isPrivate"] = self.is_private
|
||
client_lib.mark_comment_private(
|
||
self.asset_id, self.comment_id, api_key, self.is_private
|
||
)
|
||
return {"FINISHED"}
|
||
|
||
|
||
# class DeleteComment(bpy.types.Operator):
|
||
# """Delete comment on BlenderKit server"""
|
||
# bl_idname = "wm.blenderkit_delete_comment"
|
||
# bl_label = "BlenderKit delete comment"
|
||
# bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}
|
||
#
|
||
# asset_id: StringProperty(
|
||
# name="Asset Base Id",
|
||
# description="Unique id of the asset (hidden)",
|
||
# default="",
|
||
# options={'SKIP_SAVE'})
|
||
#
|
||
# comment_id: bpy.props.IntProperty(
|
||
# name="Id",
|
||
# description="comment id",
|
||
# default=-1)
|
||
#
|
||
# # flag: bpy.props.StringProperty(
|
||
# # name="flag",
|
||
# # description="Like/dislike comment",
|
||
# # default="like")
|
||
#
|
||
# @classmethod
|
||
# def poll(cls, context):
|
||
# return True
|
||
#
|
||
# def execute(self, context):
|
||
# user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
|
||
# api_key = user_preferences.api_key
|
||
# comments_utils.send_comment_delete_to_thread(asset_id=self.asset_id, comment_id=self.comment_id,# flag=self.flag,
|
||
# api_key=api_key)
|
||
# return {'FINISHED'}
|
||
|
||
|
||
class PostComment(bpy.types.Operator):
|
||
"""Post a comment to BlenderKit server"""
|
||
|
||
bl_idname = "wm.blenderkit_post_comment"
|
||
bl_label = "BlenderKit post a new comment"
|
||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||
|
||
asset_id: StringProperty( # type: ignore[valid-type]
|
||
name="Asset Base Id",
|
||
description="Unique id of the asset (hidden)",
|
||
default="",
|
||
options={"SKIP_SAVE"},
|
||
)
|
||
|
||
comment_id: bpy.props.IntProperty( # type: ignore[valid-type]
|
||
name="Reply to Id", description="reply to comment id", default=0
|
||
)
|
||
|
||
@classmethod
|
||
def poll(cls, context):
|
||
return True
|
||
|
||
def execute(self, context):
|
||
user_preferences = bpy.context.preferences.addons[__package__].preferences
|
||
ui_props = bpy.context.window_manager.blenderkitUI
|
||
api_key = user_preferences.api_key
|
||
|
||
# Store comment locally first for immediate display
|
||
# need to fill in everything to satisfy the drawing of the comment
|
||
comment_data = {
|
||
"comment": ui_props.new_comment,
|
||
"created": "just now",
|
||
"author": global_vars.BKIT_PROFILE,
|
||
"id": -1, # Temporary ID until server response
|
||
"level": self.comment_id != 0 and 1 or 0, # If replying, set level 1
|
||
"parentComment": self.comment_id if self.comment_id != 0 else None,
|
||
"replies": [],
|
||
"feedback": {"flags": [], "score": 0},
|
||
"isPrivate": False,
|
||
"canChangeIsPrivate": True,
|
||
"userModerator": False,
|
||
"isDeleted": False,
|
||
"flags": [], # For storing like/dislike flags
|
||
"asset": self.asset_id, # Reference to the asset being commented on
|
||
"canEdit": True, # User can edit their own new comments
|
||
"submitDate": "just now", # Server-generated timestamp
|
||
"userName": global_vars.BKIT_PROFILE.firstName
|
||
+ " "
|
||
+ global_vars.BKIT_PROFILE.lastName,
|
||
}
|
||
comments = comments_utils.get_comments_local(self.asset_id) or []
|
||
comments.append(comment_data)
|
||
comments_utils.store_comments_local(self.asset_id, comments)
|
||
|
||
# Send to server
|
||
client_lib.create_comment(
|
||
self.asset_id, ui_props.new_comment, api_key, self.comment_id
|
||
)
|
||
ui_props.new_comment = ""
|
||
return {"FINISHED"}
|
||
|
||
|
||
def draw_notification(self, notification, width=600):
|
||
layout = self.layout
|
||
box = layout.box()
|
||
actor = notification.get("actor", {}).get("string", "")
|
||
verb = notification.get("verb", "")
|
||
target = notification.get("target", {})
|
||
if target is None:
|
||
target = {}
|
||
target_string = target.get("string", "")
|
||
notification_string = notification.get("string", "")
|
||
firstline = f"{actor} {verb} {target_string}"
|
||
# firstline = f"{notification_string}"
|
||
box1 = box.box()
|
||
# row = box1.row()
|
||
|
||
split_last = 0.7
|
||
if notification["description"]:
|
||
split_last = 0
|
||
|
||
rows = utils.label_multiline(
|
||
box1, text=firstline, width=width, split_last=split_last
|
||
)
|
||
|
||
if notification["description"]:
|
||
rows = utils.label_multiline(
|
||
box, text=notification["description"], width=width, split_last=0.7
|
||
)
|
||
|
||
if notification["target"]:
|
||
# row = layout.row()
|
||
# split = row.split(factor=.8)
|
||
# split.label(text='')
|
||
# split = split.split()
|
||
# split = rows[-1].split(factor=0.8)
|
||
# split = split.split()
|
||
# split.alignment = 'RIGHT'
|
||
# row = split.row(align = True)
|
||
row = rows[-1]
|
||
row = row.row(align=False)
|
||
|
||
# row = row.split(factor = 0.7)
|
||
|
||
op = row.operator(
|
||
"wm.blenderkit_open_notification_target", text="Open page", icon="HIDE_OFF"
|
||
)
|
||
op.tooltip = "Open the browser on the asset page to comment"
|
||
op.url = global_vars.SERVER + notification["target"]["url"]
|
||
op.notification_id = notification["id"]
|
||
# split =
|
||
op = row.operator(
|
||
"wm.blenderkit_mark_notification_read", text="", icon="CANCEL"
|
||
)
|
||
op.notification_id = notification["id"]
|
||
|
||
|
||
def draw_notifications(self, context, width=600):
|
||
layout = self.layout
|
||
notifications = global_vars.DATA.get("bkit notifications")
|
||
if notifications is not None and notifications.get("count") > 0:
|
||
row = layout.row()
|
||
# row.alert = True
|
||
split = row.split(factor=0.7)
|
||
split.label(text="")
|
||
split = split.split()
|
||
split.operator(
|
||
"wm.blenderkit_mark_notifications_read_all",
|
||
text="Mark All Read",
|
||
icon="CANCEL",
|
||
)
|
||
for notification in notifications["results"]:
|
||
if notification["unread"] == 1:
|
||
draw_notification(self, notification, width=width)
|
||
|
||
|
||
class LogoStatus(bpy.types.Operator):
|
||
"""BlenderKit status"""
|
||
|
||
bl_idname = "wm.logo_status"
|
||
bl_label = "BLENDERKIT STATUS"
|
||
bl_options = {"REGISTER", "UNDO"}
|
||
|
||
logo: StringProperty(name="logo", default="logo_offline") # type: ignore[valid-type]
|
||
|
||
|
||
class ShowNotifications(bpy.types.Operator):
|
||
"""Show notifications"""
|
||
|
||
bl_idname = "wm.show_notifications"
|
||
bl_label = "Show BlenderKit notifications"
|
||
bl_options = {"REGISTER", "UNDO"}
|
||
|
||
notification_id: bpy.props.IntProperty( # type: ignore[valid-type]
|
||
name="Id", description="notification id", default=-1
|
||
)
|
||
|
||
@classmethod
|
||
def poll(cls, context):
|
||
return True
|
||
|
||
def draw(self, context):
|
||
draw_notifications(self, context, width=600)
|
||
|
||
def execute(self, context):
|
||
wm = bpy.context.window_manager
|
||
return wm.invoke_popup(self, width=600)
|
||
|
||
|
||
class VIEW3D_PT_blenderkit_notifications(Panel):
|
||
bl_category = "BlenderKit"
|
||
bl_idname = "VIEW3D_PT_blenderkit_notifications"
|
||
bl_space_type = "VIEW_3D"
|
||
bl_region_type = "UI"
|
||
bl_label = "BlenderKit Notifications"
|
||
|
||
@classmethod
|
||
def poll(cls, context):
|
||
notifications = global_vars.DATA.get("bkit notifications")
|
||
if notifications is not None and len(notifications["results"]) > 0:
|
||
return True
|
||
return False
|
||
|
||
def draw(self, context):
|
||
draw_notifications(self, context)
|
||
|
||
|
||
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"
|
||
bl_options = {"DEFAULT_CLOSED"}
|
||
|
||
@classmethod
|
||
def poll(cls, context):
|
||
return True
|
||
|
||
def draw(self, context):
|
||
layout = self.layout
|
||
# don't draw when not online
|
||
if not global_vars.CLIENT_RUNNING:
|
||
layout.label(text="Client not running")
|
||
return
|
||
user_preferences = bpy.context.preferences.addons[__package__].preferences
|
||
if user_preferences.login_attempt:
|
||
draw_login_progress(layout)
|
||
return
|
||
|
||
draw_login_buttons(layout)
|
||
|
||
|
||
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 ACCEPTABLE_ENGINES:
|
||
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
|
||
ui_props = wm.blenderkitUI
|
||
|
||
layout = self.layout
|
||
row = layout.row()
|
||
row.prop(ui_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_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
|
||
ui_props = wm.blenderkitUI
|
||
|
||
layout = self.layout
|
||
row = layout.row()
|
||
row.prop(ui_props, "search_keywords", text="", icon="VIEWZOOM")
|
||
draw_assetbar_show_hide(row, props)
|
||
|
||
if not context.sculpt_object and not context.image_paint_object:
|
||
utils.label_multiline(
|
||
layout,
|
||
text="Switch to paint or sculpt mode.",
|
||
width=context.region.width,
|
||
)
|
||
|
||
utils.label_multiline(layout, text=props.report)
|
||
|
||
|
||
def draw_login_buttons(layout, invoke=False):
|
||
user_preferences = bpy.context.preferences.addons[__package__].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 not utils.user_logged_in():
|
||
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 OpenBlenderKitDiscord(bpy.types.Operator):
|
||
"""Connect with our team, creators, and fellow users to discuss assets and the add-on"""
|
||
|
||
bl_idname = "wm.blenderkit_join_discord"
|
||
bl_label = "Open Discord"
|
||
bl_options = {"REGISTER", "UNDO"}
|
||
|
||
@classmethod
|
||
def poll(cls, context):
|
||
return True
|
||
|
||
def execute(self, context):
|
||
open_new_tab(global_vars.DISCORD_INVITE_URL)
|
||
return {"FINISHED"}
|
||
|
||
|
||
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):
|
||
ui_props = bpy.context.window_manager.blenderkitUI
|
||
if not global_vars.CLIENT_RUNNING:
|
||
return False
|
||
return ui_props.down_up == "SEARCH" and ui_props.asset_type in (
|
||
"MODEL",
|
||
"PRINTABLE",
|
||
)
|
||
|
||
def draw_layout(self, layout):
|
||
wm = bpy.context.window_manager
|
||
props = wm.blenderkit_models
|
||
ui_props = wm.blenderkitUI
|
||
preferences = bpy.context.preferences.addons[__package__].preferences
|
||
layout.separator()
|
||
|
||
row = layout.row()
|
||
row.prop(ui_props, "search_bookmarks", text="Bookmarks", icon="BOOKMARKS")
|
||
row.prop(ui_props, "own_only", icon="USER")
|
||
row = layout.row()
|
||
layout.prop(ui_props, "free_only")
|
||
|
||
if ui_props.asset_type == "MODEL":
|
||
layout.prop(props, "search_style")
|
||
layout.prop(props, "search_geometry_nodes", text="Geometry Nodes")
|
||
|
||
# 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")
|
||
|
||
if ui_props.asset_type == "MODEL":
|
||
# 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")
|
||
layout.prop(props, "search_animated", text="Animated")
|
||
layout.prop(ui_props, "quality_limit", slider=True)
|
||
|
||
# LICENSE
|
||
layout.prop(ui_props, "search_license")
|
||
|
||
if ui_props.asset_type == "MODEL":
|
||
# LIMIT BLENDER VERSION
|
||
layout.prop(
|
||
ui_props, "search_blender_version", text="Asset's Blender Version"
|
||
)
|
||
if ui_props.search_blender_version:
|
||
row = layout.row(align=True)
|
||
row.prop(ui_props, "search_blender_version_min", text="Min")
|
||
row.prop(ui_props, "search_blender_version_max", text="Max")
|
||
|
||
# NSFW filter
|
||
layout.prop(preferences, "nsfw_filter")
|
||
|
||
# ORDER
|
||
layout.prop(ui_props, "search_order_by")
|
||
|
||
def draw(self, context):
|
||
self.draw_layout(self.layout)
|
||
|
||
|
||
def draw_panel_printable_upload(self, context):
|
||
"""Draw upload panel for printable assets"""
|
||
layout = self.layout
|
||
props = utils.get_upload_props()
|
||
draw_upload_common(layout, props, "PRINTABLE", context)
|
||
|
||
|
||
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):
|
||
ui_props = bpy.context.window_manager.blenderkitUI
|
||
if not global_vars.CLIENT_RUNNING:
|
||
return False
|
||
return ui_props.down_up == "SEARCH" and ui_props.asset_type == "MATERIAL"
|
||
|
||
def draw_layout(self, layout):
|
||
wm = bpy.context.window_manager
|
||
props = wm.blenderkit_mat
|
||
ui_props = wm.blenderkitUI
|
||
|
||
layout.separator()
|
||
|
||
row = layout.row()
|
||
row.prop(ui_props, "search_bookmarks", text="Bookmarks", icon="BOOKMARKS")
|
||
row.prop(ui_props, "own_only", icon="USER")
|
||
|
||
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(ui_props, "quality_limit", slider=True)
|
||
layout.prop(ui_props, "search_license")
|
||
|
||
# ORDER
|
||
layout.prop(ui_props, "search_order_by")
|
||
|
||
def draw(self, context):
|
||
self.draw_layout(self.layout)
|
||
|
||
|
||
class VIEW3D_PT_blenderkit_advanced_scene_search(Panel):
|
||
bl_category = "BlenderKit"
|
||
bl_idname = "VIEW3D_PT_blenderkit_advanced_scene_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 = bpy.context.window_manager.blenderkitUI
|
||
return ui_props.down_up == "SEARCH" and ui_props.asset_type == "SCENE"
|
||
|
||
def draw_layout(self, layout):
|
||
ui_props = bpy.context.window_manager.blenderkitUI
|
||
draw_common_filters(layout, ui_props)
|
||
|
||
def draw(self, context):
|
||
self.draw_layout(self.layout)
|
||
|
||
|
||
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):
|
||
ui_props = bpy.context.window_manager.blenderkitUI
|
||
if not global_vars.CLIENT_RUNNING:
|
||
return False
|
||
return ui_props.down_up == "SEARCH" and ui_props.asset_type == "HDR"
|
||
|
||
def draw(self, context):
|
||
wm = context.window_manager
|
||
props = wm.blenderkit_HDR
|
||
ui_props = wm.blenderkitUI
|
||
|
||
layout = self.layout
|
||
draw_common_filters(layout, ui_props)
|
||
|
||
layout.prop(props, "true_hdr")
|
||
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")
|
||
|
||
|
||
class VIEW3D_PT_blenderkit_advanced_brush_search(Panel):
|
||
bl_category = "BlenderKit"
|
||
bl_idname = "VIEW3D_PT_blenderkit_advanced_brush_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):
|
||
ui_props = bpy.context.window_manager.blenderkitUI
|
||
return ui_props.down_up == "SEARCH" and ui_props.asset_type == "BRUSH"
|
||
|
||
def draw_layout(self, layout):
|
||
ui_props = bpy.context.window_manager.blenderkitUI
|
||
draw_common_filters(layout, ui_props)
|
||
|
||
def draw(self, context):
|
||
self.draw_layout(self.layout)
|
||
|
||
|
||
class VIEW3D_PT_blenderkit_advanced_nodegroup_search(Panel):
|
||
bl_category = "BlenderKit"
|
||
bl_idname = "VIEW3D_PT_blenderkit_advanced_nodegroup_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):
|
||
ui_props = bpy.context.window_manager.blenderkitUI
|
||
if not global_vars.CLIENT_RUNNING:
|
||
return False
|
||
return ui_props.down_up == "SEARCH" and ui_props.asset_type == "NODEGROUP"
|
||
|
||
def draw(self, context):
|
||
ui_props = bpy.context.window_manager.blenderkitUI
|
||
draw_common_filters(self.layout, ui_props)
|
||
|
||
|
||
class VIEW3D_PT_blenderkit_advanced_addon_search(Panel):
|
||
bl_category = "BlenderKit"
|
||
bl_idname = "VIEW3D_PT_blenderkit_advanced_addon_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):
|
||
ui_props = bpy.context.window_manager.blenderkitUI
|
||
if not global_vars.CLIENT_RUNNING:
|
||
return False
|
||
return ui_props.down_up == "SEARCH" and ui_props.asset_type == "ADDON"
|
||
|
||
def draw(self, context):
|
||
ui_props = bpy.context.window_manager.blenderkitUI
|
||
draw_common_filters(self.layout, ui_props)
|
||
layout = self.layout
|
||
addon_props = bpy.context.window_manager.blenderkit_addon
|
||
# Add installed filter for addons
|
||
row = layout.row()
|
||
row.prop(addon_props, "search_installed", text="Installed Only")
|
||
|
||
|
||
class VIEW3D_PT_blenderkit_advanced_printable_search(Panel):
|
||
bl_category = "BlenderKit"
|
||
bl_idname = "VIEW3D_PT_blenderkit_advanced_printable_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):
|
||
ui_props = bpy.context.window_manager.blenderkitUI
|
||
if not global_vars.CLIENT_RUNNING:
|
||
return False
|
||
return ui_props.down_up == "SEARCH" and ui_props.asset_type == "PRINTABLE"
|
||
|
||
def draw_layout(self, layout):
|
||
ui_props = bpy.context.window_manager.blenderkitUI
|
||
draw_common_filters(layout, ui_props)
|
||
|
||
def draw(self, context):
|
||
self.draw_layout(self.layout)
|
||
|
||
|
||
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):
|
||
ui_props = bpy.context.window_manager.blenderkitUI
|
||
if not global_vars.CLIENT_RUNNING:
|
||
return False
|
||
return ui_props.down_up == "SEARCH"
|
||
|
||
def draw(self, context):
|
||
# measure time since last dropdown activation/ mouse hover e.t.c.
|
||
# this is then used in asset_bar_op.py to cancel asset drag drop if the time is too small and thus means double clicking.
|
||
global last_time_overlay_panel_active
|
||
last_time_overlay_panel_active = time.time()
|
||
draw_panel_categories(self.layout, 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):
|
||
ui_props = bpy.context.window_manager.blenderkitUI
|
||
if not global_vars.CLIENT_RUNNING:
|
||
return False
|
||
if ui_props.asset_type not in ["MATERIAL", "MODEL", "SCENE", "HDR"]:
|
||
return False
|
||
return ui_props.down_up == "SEARCH"
|
||
|
||
def draw(self, context):
|
||
layout = self.layout
|
||
|
||
s = context.scene
|
||
wm = bpy.context.window_manager
|
||
ui_props = bpy.context.window_manager.blenderkitUI
|
||
preferences = bpy.context.preferences.addons[__package__].preferences
|
||
|
||
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, "import_method", expand=True, icon_only=False)
|
||
|
||
if ui_props.asset_type == "MATERIAL":
|
||
props = wm.blenderkit_mat
|
||
layout.prop(preferences, "material_import_automap")
|
||
layout.label(text="Import method:")
|
||
row = layout.row()
|
||
|
||
row.prop(props, "import_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(preferences, "unpack_files")
|
||
layout.prop(preferences, "resolution")
|
||
# layout.prop(props, 'unpack_files')
|
||
|
||
# general settings
|
||
# show toggle for clipboard scan
|
||
layout.prop(preferences, "use_clipboard_scan")
|
||
|
||
|
||
def deferred_set_name(props, expected_obj_name):
|
||
"""Deferred timer to set empty name of uploaded asset to active Object's name.
|
||
We check if the names of active_now object and expected object are the same, because active object could have changed.
|
||
This is one-shot timer = return None.
|
||
"""
|
||
active_now = utils.get_active_asset()
|
||
if props.name != "":
|
||
return None
|
||
if not active_now:
|
||
return None
|
||
if active_now.name != expected_obj_name:
|
||
return None # active object is different from the one on which we have called the timer
|
||
props.name_old = (
|
||
expected_obj_name # prevents utils.name_update() from running twice
|
||
)
|
||
props.name = expected_obj_name # this ultimately triggers utils.name_update()
|
||
return None
|
||
|
||
|
||
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_options = {
|
||
"HEADER_LAYOUT_EXPAND",
|
||
}
|
||
bl_label = ""
|
||
|
||
@classmethod
|
||
def poll(cls, context):
|
||
user_preferences = bpy.context.preferences.addons[__package__].preferences
|
||
return not user_preferences.sidebar_panels
|
||
|
||
def draw_header(self, context):
|
||
layout = self.layout
|
||
ui_props = bpy.context.window_manager.blenderkitUI
|
||
pcoll = icons.icon_collections["main"]
|
||
layout.label(
|
||
text="Find and Upload Assets",
|
||
icon_value=pcoll[ui_props.logo_status].icon_id,
|
||
)
|
||
|
||
def draw(self, context):
|
||
ui_props = bpy.context.window_manager.blenderkitUI
|
||
user_preferences = bpy.context.preferences.addons[__package__].preferences
|
||
|
||
layout = self.layout
|
||
# layout.prop_tabs_enum(ui_props, "asset_type", icon_only = True)
|
||
if not global_vars.CLIENT_RUNNING:
|
||
layout.label(text="Client not running")
|
||
return
|
||
|
||
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:
|
||
# 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='')
|
||
|
||
if user_preferences.login_attempt:
|
||
draw_login_progress(layout)
|
||
return
|
||
|
||
if (
|
||
len(user_preferences.api_key) < 20
|
||
and user_preferences.download_counter > 20
|
||
):
|
||
draw_login_buttons(layout)
|
||
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":
|
||
self.draw_search(context, layout, ui_props)
|
||
|
||
if ui_props.down_up == "UPLOAD":
|
||
self.draw_upload(context, layout, ui_props)
|
||
|
||
def draw_search(self, context, layout, ui_props):
|
||
if utils.profile_is_validator():
|
||
search_props = utils.get_search_props()
|
||
layout.prop(search_props, "search_verification_status")
|
||
layout.prop(search_props, "unrated_quality_only")
|
||
layout.prop(search_props, "unrated_wh_only")
|
||
|
||
if ui_props.asset_type == "MODEL" or ui_props.asset_type == "PRINTABLE":
|
||
return draw_panel_model_search(self, context)
|
||
|
||
if ui_props.asset_type == "SCENE":
|
||
return draw_panel_scene_search(self, context)
|
||
|
||
if ui_props.asset_type == "HDR":
|
||
return draw_panel_hdr_search(self, context)
|
||
|
||
if ui_props.asset_type == "MATERIAL":
|
||
return draw_panel_material_search(self, context)
|
||
|
||
if ui_props.asset_type == "BRUSH":
|
||
return draw_panel_brush_search(self, context)
|
||
|
||
if ui_props.asset_type == "NODEGROUP":
|
||
return draw_panel_nodegroup_search(self, context)
|
||
|
||
if ui_props.asset_type == "ADDON":
|
||
return draw_panel_addon_search(self, context)
|
||
|
||
def draw_upload(self, context, layout, ui_props):
|
||
obj = utils.get_active_asset()
|
||
props = getattr(obj, "blenderkit", None)
|
||
if props and not props.name:
|
||
bpy.app.timers.register(
|
||
lambda p=props, n=obj.name: deferred_set_name(p, n), first_interval=0.0
|
||
)
|
||
|
||
if ui_props.asset_type == "MODEL" or ui_props.asset_type == "PRINTABLE":
|
||
if bpy.context.view_layer.objects.active is not None:
|
||
return draw_panel_model_upload(self, context)
|
||
layout.label(text="select object to upload")
|
||
return
|
||
|
||
if ui_props.asset_type == "SCENE":
|
||
return draw_panel_scene_upload(self, context)
|
||
|
||
if ui_props.asset_type == "HDR":
|
||
return draw_panel_hdr_upload(self, context)
|
||
|
||
if ui_props.asset_type == "MATERIAL":
|
||
if (bpy.context.view_layer.objects.active is not None) and (
|
||
bpy.context.active_object.active_material is not None
|
||
):
|
||
return draw_panel_material_upload(self, context)
|
||
|
||
utils.label_multiline(
|
||
layout,
|
||
text="select object with material to upload materials",
|
||
width=context.region.width,
|
||
)
|
||
return
|
||
|
||
if ui_props.asset_type == "BRUSH":
|
||
if context.sculpt_object or context.image_paint_object:
|
||
return draw_panel_brush_upload(self, context)
|
||
layout.label(text="Switch to paint or sculpt mode.")
|
||
return
|
||
|
||
if ui_props.asset_type == "NODEGROUP":
|
||
return draw_panel_nodegroup_upload(self, context)
|
||
|
||
if ui_props.asset_type == "ADDON":
|
||
layout.label(text="Add-on uploads are managed through")
|
||
layout.label(text="the BlenderKit website.")
|
||
op = layout.operator(
|
||
"wm.url_open", text="Go to BlenderKit Website", icon="URL"
|
||
)
|
||
op.url = paths.BLENDERKIT_ADDON_UPLOAD_INSTRUCTIONS_URL
|
||
return
|
||
|
||
|
||
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( # type: ignore[valid-type]
|
||
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[__package__].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_URL
|
||
|
||
else:
|
||
message = "Operator Tutorial called with invalid step"
|
||
|
||
def execute(self, context):
|
||
if self.step == 0:
|
||
ui_props = bpy.context.window_manager.blenderkitUI
|
||
|
||
ui_props.asset_type = "MODEL"
|
||
|
||
search.search(
|
||
query={
|
||
"asset_type": "model",
|
||
"query": f"+is_free:true+score_gte:1000+order:-created",
|
||
}
|
||
)
|
||
return {"FINISHED"}
|
||
|
||
def invoke(self, context, event):
|
||
user_preferences = bpy.context.preferences.addons[__package__].preferences
|
||
if user_preferences.welcome_operator_counter > 10:
|
||
return {"FINISHED"}
|
||
user_preferences.welcome_operator_counter += 1
|
||
|
||
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:
|
||
# Show regions in which the addon has UI
|
||
a.spaces.active.show_region_ui = True
|
||
a.spaces.active.show_region_tool_header = True
|
||
|
||
return wm.invoke_props_dialog(self, width=500)
|
||
|
||
|
||
class OpenSystemDirectory(bpy.types.Operator):
|
||
"""Open directory in default system file explorer"""
|
||
|
||
bl_idname = "wm.blenderkit_open_system_directory"
|
||
bl_label = "Open system directory"
|
||
bl_options = {"REGISTER", "UNDO"}
|
||
directory: StringProperty(name="directory", default="") # type: ignore[valid-type]
|
||
|
||
@classmethod
|
||
def poll(cls, context):
|
||
return True
|
||
|
||
def execute(self, context):
|
||
if not os.path.exists(self.directory):
|
||
self.report({"ERROR"}, "Directory not found.")
|
||
return {"CANCELLED"}
|
||
paths.open_path_in_file_browser(self.directory)
|
||
return {"FINISHED"}
|
||
|
||
|
||
class OpenAssetDirectory(OpenSystemDirectory):
|
||
"""Open directory containing the asset data"""
|
||
|
||
bl_idname = "wm.blenderkit_open_asset_directory"
|
||
bl_label = "Open asset directory"
|
||
|
||
def execute(self, context):
|
||
if not os.path.exists(self.directory):
|
||
self.report({"ERROR"}, "Directory not found. Asset not downloaded yet.")
|
||
return {"CANCELLED"}
|
||
paths.open_path_in_file_browser(self.directory)
|
||
return {"FINISHED"}
|
||
|
||
|
||
class OpenAddonDirectory(OpenSystemDirectory):
|
||
"""Open the directory in which the BlenderKit add-on is installed. Move one level up and delete it to hard-uninstall the add-on"""
|
||
|
||
bl_idname = "wm.blenderkit_open_addon_directory"
|
||
bl_label = "Open global directory"
|
||
|
||
|
||
class OpenGlobalDirectory(OpenSystemDirectory):
|
||
"""Open the BlenderKit's Global directory. This is the directory where BlenderKit stores downloaded assets. It also contains Client binary and log files"""
|
||
|
||
bl_idname = "wm.blenderkit_open_global_directory"
|
||
bl_label = "Open global directory"
|
||
|
||
|
||
class OpenClientLog(OpenSystemDirectory):
|
||
"""Open Log file of currently running Client. Client logs errors and other message in here. Inspect to see what is wrong with Client. Copy the contents when you make a bug report"""
|
||
|
||
bl_idname = "wm.blenderkit_open_client_log"
|
||
bl_label = "Open Client log"
|
||
|
||
|
||
class OpenTempDirectory(OpenSystemDirectory):
|
||
"""Open BlenderKit's temporary directory. This is the directory where thumbnails and other temporary files are stored"""
|
||
|
||
bl_idname = "wm.blenderkit_open_temp_directory"
|
||
bl_label = "Open temp directory"
|
||
|
||
|
||
def draw_asset_context_menu(
|
||
layout, context: Context, asset_data: dict, from_panel: bool = False
|
||
):
|
||
ui_props = context.window_manager.blenderkitUI # type: ignore
|
||
author_id = int(asset_data["author"].get("id"))
|
||
layout.operator_context = "INVOKE_DEFAULT"
|
||
|
||
if utils.user_logged_in():
|
||
rating = ratings_utils.get_rating_local(asset_data["id"])
|
||
if rating is None:
|
||
rating = datas.AssetRating()
|
||
if rating.bookmarks == 1:
|
||
text = "Delete Bookmark"
|
||
icon = "bookmark_full"
|
||
else:
|
||
text = "Bookmark"
|
||
icon = "bookmark_empty"
|
||
|
||
pcoll = icons.icon_collections["main"]
|
||
op = layout.operator(
|
||
"wm.blenderkit_bookmark_asset", text=text, icon_value=pcoll[icon].icon_id
|
||
)
|
||
op.asset_id = asset_data["id"]
|
||
|
||
if from_panel:
|
||
op = layout.operator(
|
||
"wm.blenderkit_menu_rating_upload", text="Add Rating", icon="SOLO_ON"
|
||
)
|
||
op.asset_name = asset_data["name"]
|
||
op.asset_id = asset_data["id"]
|
||
op.asset_type = asset_data["assetType"]
|
||
|
||
if from_panel and global_vars.BKIT_AUTHORS is not None and author_id is not None:
|
||
author = global_vars.BKIT_AUTHORS.get(author_id)
|
||
if author is not None:
|
||
# utils.p('author:', a)
|
||
op = layout.operator("wm.url_open", text="Open Author's Website")
|
||
if author.aboutMeUrl:
|
||
op.url = author.aboutMeUrl
|
||
else:
|
||
op.url = paths.get_author_gallery_url(author.id)
|
||
op = layout.operator(
|
||
"view3d.blenderkit_search", text="Show Assets By Author"
|
||
)
|
||
op.keywords = ""
|
||
op.author_id = str(author_id)
|
||
|
||
op = layout.operator("view3d.blenderkit_search", text="Search Similar")
|
||
op.esc = True
|
||
op.tooltip = "Search for similar assets in the library.\nShortcut: hover over asset in asset bar and press 'S'."
|
||
op.keywords = search.get_search_similar_keywords(asset_data)
|
||
|
||
op = layout.operator("wm.url_open", text="See online", icon="URL")
|
||
if (
|
||
utils.user_is_owner(asset_data)
|
||
and asset_data["verificationStatus"] != "validated"
|
||
):
|
||
op.url = (
|
||
f'{paths.BLENDERKIT_USER_ASSETS_URL}/{asset_data["assetBaseId"]}/?preview#'
|
||
)
|
||
else:
|
||
op.url = paths.get_asset_gallery_url(asset_data["id"])
|
||
# TODO this is where validator should be able to go and see non-validated the assets in gallery,
|
||
# by now there's nowhere to go.
|
||
|
||
# if asset_data["downloaded"] == 100:
|
||
# enable opening the directory on drive
|
||
dir_paths = paths.get_asset_directories(asset_data)
|
||
if len(dir_paths) > 0 and os.path.exists(dir_paths[-1]):
|
||
op = layout.operator(
|
||
"wm.blenderkit_open_asset_directory",
|
||
text="Open Directory",
|
||
icon="FILE_FOLDER",
|
||
)
|
||
op.directory = dir_paths[-1]
|
||
|
||
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"
|
||
)
|
||
op.tooltip = "Replace all selected models with this one"
|
||
|
||
# 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 # type: ignore
|
||
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")
|
||
or asset_data.get("available_resolutions") is not None
|
||
):
|
||
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()
|
||
if o is not None:
|
||
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 # type: ignore
|
||
op.material_target_slot = aob.active_material_index
|
||
op.replace_resolution = True
|
||
op.replace = False
|
||
|
||
op.invoke_resolution = True
|
||
op.use_resolution_operator = 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() # type: ignore
|
||
and asset_data["assetType"] != "hdr"
|
||
and (
|
||
asset_data.get("resolution")
|
||
or asset_data.get("available_resolutions") is not None
|
||
)
|
||
):
|
||
# 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
|
||
op.use_resolution_operator = True
|
||
o = utils.get_active_model()
|
||
if o and o.get("asset_data"):
|
||
if (
|
||
o["asset_data"]["assetBaseId"]
|
||
== search.get_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
|
||
|
||
profile = global_vars.BKIT_PROFILE
|
||
if profile is None:
|
||
return
|
||
|
||
# validation
|
||
if (
|
||
author_id == profile.id or utils.profile_is_validator()
|
||
): # was not working due to wrong types
|
||
layout.label(text="Management tools:")
|
||
|
||
row = layout.row()
|
||
row.operator_context = "INVOKE_DEFAULT"
|
||
op = layout.operator(
|
||
"wm.blenderkit_fast_metadata", text="Edit Metadata", icon="GREASEPENCIL"
|
||
)
|
||
op.asset_id = asset_data["id"]
|
||
op.asset_type = asset_data["assetType"]
|
||
|
||
if author_id == str(profile.id):
|
||
row.operator_context = "EXEC_DEFAULT"
|
||
op = layout.operator(
|
||
"wm.blenderkit_url",
|
||
text="Edit Metadata (browser)",
|
||
icon="GREASEPENCIL",
|
||
)
|
||
op.url = (
|
||
f'{paths.BLENDERKIT_USER_ASSETS_URL}/{asset_data["assetBaseId"]}/?edit#'
|
||
)
|
||
|
||
row.operator_context = "INVOKE_DEFAULT"
|
||
|
||
if asset_data["assetType"] == "model":
|
||
op = layout.operator(
|
||
"object.blenderkit_regenerate_thumbnail",
|
||
text="Regenerate thumbnail",
|
||
)
|
||
op.asset_index = ui_props.active_index
|
||
elif 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 == profile.id: # was not working because of wrong types
|
||
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"
|
||
op.original_state = asset_data["verificationStatus"]
|
||
|
||
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.window_manager.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.window_manager.blenderkitUI
|
||
#
|
||
# # sr = global_vars.DATA['search results']
|
||
#
|
||
# # sr = global_vars.DATA['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.window_manager.blenderkitUI
|
||
sr = search.get_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=None,
|
||
icon_value=None,
|
||
icon=None,
|
||
emboss=False,
|
||
):
|
||
"""automatically switch between different layout options for linking or tooltips"""
|
||
layout.emboss = "NORMAL" if emboss else "NONE"
|
||
if operator_kwargs is None:
|
||
operator_kwargs = {}
|
||
|
||
if operator is not None:
|
||
if icon:
|
||
op = layout.operator(operator, text=text, icon=icon, emboss=emboss)
|
||
elif icon_value:
|
||
op = layout.operator(
|
||
operator, text=text, icon_value=icon_value, emboss=emboss
|
||
)
|
||
else:
|
||
op = layout.operator(operator, text=text, emboss=emboss)
|
||
for kwarg in operator_kwargs.keys():
|
||
setattr(op, kwarg, operator_kwargs[kwarg])
|
||
push_op_left(layout, strength=2)
|
||
|
||
return
|
||
if url != "":
|
||
if icon:
|
||
op = layout.operator(
|
||
"wm.blenderkit_url", text=text, icon=icon, emboss=emboss
|
||
)
|
||
elif icon_value:
|
||
op = layout.operator(
|
||
"wm.blenderkit_url", text=text, icon_value=icon_value, emboss=emboss
|
||
)
|
||
else:
|
||
op = layout.operator("wm.blenderkit_url", text=text, emboss=emboss)
|
||
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, emboss=emboss
|
||
)
|
||
elif icon_value:
|
||
op = layout.operator(
|
||
"wm.blenderkit_tooltip", text=text, icon_value=icon_value, emboss=emboss
|
||
)
|
||
else:
|
||
op = layout.operator("wm.blenderkit_tooltip", text=text, emboss=emboss)
|
||
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.RatingProperties):
|
||
"""
|
||
This is the popup card that appears when you click on an asset in the asset bar.
|
||
It shows the asset details and allows you to download the asset.
|
||
"""
|
||
|
||
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=None,
|
||
emboss=False,
|
||
):
|
||
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 != "" and not emboss:
|
||
split = split.split(factor=0.9)
|
||
split.alignment = "LEFT"
|
||
if operator_kwargs is None:
|
||
operator_kwargs = {}
|
||
label_or_url_or_operator(
|
||
split,
|
||
text=right,
|
||
tooltip=tooltip,
|
||
url=url,
|
||
operator=operator,
|
||
operator_kwargs=operator_kwargs,
|
||
icon_value=icon_value,
|
||
icon=icon,
|
||
emboss=emboss,
|
||
)
|
||
# additional 'question mark' icon where it's important?
|
||
# Embossed elements are visibly clickable, so we don't need the 'question mark' icon
|
||
if url != "" and not emboss:
|
||
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, decimal=True
|
||
):
|
||
parameter = utils.get_param(self.asset_data, key)
|
||
if parameter == None:
|
||
return
|
||
if type(parameter) == int:
|
||
if decimal:
|
||
parameter = f"{parameter:,d}"
|
||
else:
|
||
parameter = f"{parameter}"
|
||
elif type(parameter) == float:
|
||
parameter = f"{parameter:,.1f}"
|
||
if do_search:
|
||
kwargs = {
|
||
"esc": True,
|
||
"keywords": f"+{key}:{parameter}",
|
||
"tooltip": f"search by {parameter}",
|
||
}
|
||
# search gets auto emboss
|
||
self.draw_property(
|
||
layout,
|
||
pretext,
|
||
parameter,
|
||
operator="view3d.blenderkit_search",
|
||
operator_kwargs=kwargs,
|
||
emboss=True,
|
||
)
|
||
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.6
|
||
box.label(text="Properties")
|
||
box.separator()
|
||
|
||
if self.asset_data.get("license") == "cc_zero":
|
||
text = "CC Zero "
|
||
icon = pcoll["cc0"]
|
||
else:
|
||
text = "Royalty free"
|
||
icon = pcoll["royalty_free"]
|
||
self.draw_property(
|
||
box,
|
||
"License",
|
||
text,
|
||
# icon_value=icon.icon_id,
|
||
url=f"{global_vars.SERVER}/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 validator comments under your asset 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=f"{global_vars.SERVER}/docs/validation-status/",
|
||
tooltip=verification_status_tooltips[
|
||
self.asset_data["verificationStatus"]
|
||
],
|
||
)
|
||
# resolution/s
|
||
resolution = utils.get_param(self.asset_data, "textureResolutionMax")
|
||
available_res = self.asset_data.get("available_resolutions")
|
||
fs = self.asset_data["files"]
|
||
|
||
if resolution is not None or len(available_res) > 0:
|
||
if resolution is None:
|
||
# this should get removed once all assets that have texture have proper resolution parameter fixed
|
||
# by now part of assets that have texture don't have texture resolution marked
|
||
ress = f"{int(round(available_res[-1] / 1024, 0))}K"
|
||
else:
|
||
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",
|
||
)
|
||
# this would normally show only when theres's texture resolution parameter.
|
||
# but this parameter wasn't always uploaded correctly, that's why we need to check also for others
|
||
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 res", 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", decimal=False
|
||
)
|
||
|
||
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"):
|
||
text = utils.fmt_dimensions(mparams)
|
||
self.draw_property(box, "Size", text)
|
||
if self.asset_data.get("filesSize"):
|
||
fs = self.asset_data["filesSize"] * 1024
|
||
# multiply because the number is reduced when search is done to avoind C intiger limit with large files
|
||
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 - with special handling for addons
|
||
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"
|
||
)
|
||
|
||
# Special pricing display for addons
|
||
if self.asset_data.get("assetType") == "addon":
|
||
|
||
can_download = self.asset_data.get("canDownload")
|
||
is_free = self.asset_data.get("isFree")
|
||
|
||
# Get pricing info from extensions cache
|
||
user_price = self.asset_data.get("userPrice")
|
||
base_price = self.asset_data.get("basePrice")
|
||
is_for_sale = self.asset_data.get("isForSale")
|
||
|
||
if self.asset_data["isPrivate"]:
|
||
text = "Private"
|
||
self.draw_property(box, "Access", text, icon="LOCKED")
|
||
elif is_for_sale and not can_download and user_price and base_price:
|
||
text = f"${user_price} (Not purchased)"
|
||
icon = pcoll["for_sale"]
|
||
self.draw_property(
|
||
box,
|
||
"Price",
|
||
text,
|
||
icon_value=icon.icon_id,
|
||
tooltip="This addon is for sale but you haven't purchased it yet.\nPrice shown is your price / base price",
|
||
)
|
||
elif is_for_sale and not can_download and base_price:
|
||
text = f"${base_price} (Not purchased)"
|
||
icon = pcoll["for_sale"]
|
||
self.draw_property(
|
||
box,
|
||
"Price",
|
||
text,
|
||
icon_value=icon.icon_id,
|
||
tooltip="This addon is for sale but you haven't purchased it yet",
|
||
)
|
||
elif is_for_sale and can_download and base_price:
|
||
text = f"Purchased"
|
||
icon = pcoll["for_sale"]
|
||
self.draw_property(
|
||
box,
|
||
"Price",
|
||
text,
|
||
icon_value=icon.icon_id,
|
||
tooltip="You have purchased this addon",
|
||
)
|
||
elif not is_free and not is_for_sale:
|
||
text = "Full plan"
|
||
icon = pcoll["full"]
|
||
self.draw_property(
|
||
box,
|
||
"Access",
|
||
text,
|
||
icon_value=icon.icon_id,
|
||
tooltip=plans_tooltip,
|
||
url=paths.BLENDERKIT_PLANS_URL,
|
||
)
|
||
else:
|
||
text = "Free"
|
||
icon = pcoll["free"]
|
||
self.draw_property(
|
||
box,
|
||
"Access",
|
||
text,
|
||
icon_value=icon.icon_id,
|
||
tooltip="This addon is free to use",
|
||
)
|
||
|
||
# Display Blender version requirements for addons
|
||
dict_params = self.asset_data.get("dictParameters", {})
|
||
min_version = dict_params.get("blenderVersionMin")
|
||
max_version = dict_params.get("blenderVersionMax")
|
||
if min_version:
|
||
min_version_tuple = tuple(map(int, min_version.split(".")))
|
||
if max_version:
|
||
max_version_tuple = tuple(map(int, max_version.split(".")))
|
||
|
||
if min_version or max_version:
|
||
version_text = ""
|
||
if min_version and max_version:
|
||
version_text = f"{min_version} - {max_version}"
|
||
elif min_version:
|
||
version_text = f"{min_version}+"
|
||
elif max_version:
|
||
version_text = f"≤ {max_version}"
|
||
|
||
# Check if current Blender version is compatible
|
||
current_version = (
|
||
f"{bpy.app.version[0]}.{bpy.app.version[1]}.{bpy.app.version[2]}"
|
||
)
|
||
is_compatible = True
|
||
|
||
if min_version:
|
||
if bpy.app.version < min_version_tuple:
|
||
is_compatible = False
|
||
|
||
if max_version and is_compatible:
|
||
if bpy.app.version > max_version_tuple:
|
||
is_compatible = False
|
||
|
||
# Display version requirement with appropriate warning
|
||
if not is_compatible:
|
||
box.alert = True
|
||
self.draw_property(
|
||
box,
|
||
"Blender versions",
|
||
f"{version_text} (Incompatible!)",
|
||
icon="ERROR",
|
||
tooltip=f"This addon requires Blender {version_text}, but you're using {current_version}",
|
||
)
|
||
box.alert = False
|
||
else:
|
||
self.draw_property(
|
||
box,
|
||
"Blender versions",
|
||
version_text,
|
||
icon="CHECKMARK",
|
||
tooltip=f"This addon is compatible with your Blender version ({current_version})",
|
||
)
|
||
else:
|
||
# Regular asset access display
|
||
if self.asset_data["isPrivate"]:
|
||
text = "Private"
|
||
self.draw_property(box, "Access", text, icon="LOCKED")
|
||
elif self.asset_data["isFree"]:
|
||
text = "Free plan"
|
||
icon = pcoll["free"]
|
||
self.draw_property(
|
||
box,
|
||
"Access",
|
||
text,
|
||
icon_value=icon.icon_id,
|
||
tooltip=plans_tooltip,
|
||
url=paths.BLENDERKIT_PLANS_URL,
|
||
)
|
||
else:
|
||
text = "Full plan"
|
||
icon = pcoll["full"]
|
||
self.draw_property(
|
||
box,
|
||
"Access",
|
||
text,
|
||
icon_value=icon.icon_id,
|
||
tooltip=plans_tooltip,
|
||
url=paths.BLENDERKIT_PLANS_URL,
|
||
)
|
||
|
||
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)
|
||
self.draw_property(
|
||
box,
|
||
"Sexualized:",
|
||
self.asset_data.get("dictParameters", {}).get("sexualizedContent"),
|
||
)
|
||
|
||
from_newer, difference = utils.asset_from_newer_blender_version(self.asset_data)
|
||
if from_newer:
|
||
if difference == "major":
|
||
warning = (
|
||
f"{self.asset_data['sourceAppVersion']} - newer major version!"
|
||
)
|
||
elif difference == "minor":
|
||
warning = (
|
||
f"{self.asset_data['sourceAppVersion']} - newer minor version!"
|
||
)
|
||
else:
|
||
warning = (
|
||
f"{self.asset_data['sourceAppVersion']} - slightly newer version."
|
||
)
|
||
box.alert = True
|
||
self.draw_property(
|
||
box,
|
||
"Blender version",
|
||
warning,
|
||
icon="ERROR",
|
||
)
|
||
box.alert = False
|
||
else:
|
||
self.draw_property(
|
||
box,
|
||
"Blender version",
|
||
self.asset_data["sourceAppVersion"],
|
||
# icon='ERROR',
|
||
# tooltip='The version this asset was created in.',
|
||
)
|
||
|
||
# Add TwinBru specific parameters for material assets
|
||
# only if they have 'twinbruReference' in the 'dictParameters'
|
||
if self.asset_data.get("dictParameters").get("twinbruReference"):
|
||
box.separator()
|
||
box.label(text="TwinBru physical material categories")
|
||
|
||
self.draw_asset_parameter(
|
||
box,
|
||
key="twinBruCatEndUse",
|
||
pretext="End Use",
|
||
do_search=True,
|
||
)
|
||
self.draw_asset_parameter(
|
||
box,
|
||
key="twinBruColourType",
|
||
pretext="Colour Type",
|
||
do_search=True,
|
||
)
|
||
self.draw_asset_parameter(
|
||
box,
|
||
key="twinBruCharacteristics",
|
||
pretext="Characteristics",
|
||
do_search=True,
|
||
)
|
||
self.draw_asset_parameter(
|
||
box,
|
||
key="twinBruDesignType",
|
||
pretext="Design Type",
|
||
do_search=True,
|
||
)
|
||
|
||
# Product Link for assets that have it.
|
||
if self.asset_data.get("dictParameters").get("productLink"):
|
||
self.draw_property(
|
||
box,
|
||
"Product Link",
|
||
"View on manufacturer's website",
|
||
url=self.asset_data["dictParameters"]["productLink"],
|
||
icon="URL",
|
||
emboss=True,
|
||
)
|
||
|
||
box.separator()
|
||
|
||
def draw_author_area(self, context, layout, width=330):
|
||
self.draw_author(context, layout, width=width)
|
||
|
||
def draw_author(
|
||
self, context: bpy.types.Context, layout: bpy.types.UILayout, width: int = 330
|
||
):
|
||
image_split = 0.25
|
||
text_width = width
|
||
authors = global_vars.BKIT_AUTHORS
|
||
author_id = int(self.asset_data["author"]["id"])
|
||
author = authors.get(author_id)
|
||
if author is None:
|
||
return
|
||
|
||
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)
|
||
self.gimg.gl_touch()
|
||
|
||
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=author.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 not author.aboutMe:
|
||
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 = f"{global_vars.SERVER}/profile" # type: ignore[attr-defined]
|
||
op.tooltip = "Edit your profile on BlenderKit webpage" # type: ignore[attr-defined]
|
||
|
||
pcoll = icons.icon_collections["main"]
|
||
|
||
button_row = author_box.row()
|
||
button_row.scale_y = 2.0
|
||
|
||
# AUTHOR's ASSETS SEARCH
|
||
op = button_row.operator(
|
||
"view3d.blenderkit_search", text="Find Assets By Author", icon="VIEWZOOM"
|
||
)
|
||
op.tooltip = "Search all assets by this author.\nShortcut: Hover over the asset in the asset bar and press 'A'." # type: ignore[attr-defined]
|
||
op.esc = True # type: ignore[attr-defined]
|
||
op.keywords = "" # type: ignore[attr-defined] # must not be empty otherwise search will use previous keywords
|
||
op.author_id = str(author_id) # type: ignore[attr-defined]
|
||
|
||
button_row = button_row.row(align=True)
|
||
|
||
# AUTHOR's BLENDERKIT PROFILE
|
||
url = paths.get_author_gallery_url(author.id)
|
||
tooltip = "Go to author's profile on BlenderKit web.\nShortcut: Hover over asset in the asset bar and press 'P'."
|
||
icon_value = pcoll["logo"].icon_id
|
||
op = button_row.operator("wm.blenderkit_url", text="", icon_value=icon_value)
|
||
op.url = url # type: ignore[attr-defined]
|
||
op.tooltip = tooltip # type: ignore[attr-defined]
|
||
|
||
# ABOUT ME WEBPAGE
|
||
text = None
|
||
if author.aboutMeUrl:
|
||
text = utils.remove_url_protocol(author.aboutMeUrl)
|
||
text = utils.shorten_text(text, 45)
|
||
op = button_row.operator("wm.blenderkit_url", text="", icon="URL")
|
||
op.url = author.aboutMeUrl # type: ignore[attr-defined]
|
||
op.tooltip = f"Go to author's personal Webpage: {author.aboutMeUrl}\nShortcut: Hover over asset in the asset bar and press 'W'." # type: ignore[attr-defined]
|
||
|
||
# SOCIAL NETWORKS
|
||
social_networks = author.socialNetworks
|
||
for social_network in social_networks:
|
||
url = social_network.url
|
||
if url is None or text is None:
|
||
continue
|
||
|
||
icon_name = f"logo_{social_network.name.lower()}"
|
||
if icon_name in pcoll:
|
||
op = button_row.operator(
|
||
"wm.blenderkit_url", text="", icon_value=pcoll[icon_name].icon_id
|
||
)
|
||
else:
|
||
bk_logger.warning(
|
||
f"Social network icon {icon_name} not found in icon collection"
|
||
)
|
||
op = button_row.operator("wm.blenderkit_url", text="", icon="URL")
|
||
|
||
op.url = url # type: ignore[attr-defined]
|
||
op.tooltip = f"Go to {social_network.name} profile" # type: ignore[attr-defined]
|
||
|
||
def draw_thumbnail_box(self, layout, width=250):
|
||
layout.emboss = "NORMAL"
|
||
|
||
box_thumbnail = layout.box()
|
||
|
||
box_thumbnail.scale_y = 0.4
|
||
box_thumbnail.template_icon(
|
||
icon_value=self.img.preview.icon_id, scale=width * 0.12
|
||
)
|
||
self.img.gl_touch()
|
||
|
||
# Display photo thumbnail for printable objects
|
||
if (
|
||
self.asset_data.get("assetType") == "printable"
|
||
and hasattr(self, "full_photo_thumbnail")
|
||
and self.full_photo_thumbnail
|
||
):
|
||
box_thumbnail.scale_y = 0.4
|
||
box_thumbnail.template_icon(
|
||
icon_value=self.full_photo_thumbnail.preview.icon_id,
|
||
scale=width * 0.12,
|
||
)
|
||
self.full_photo_thumbnail.gl_touch()
|
||
|
||
# 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.
|
||
|
||
rc = self.asset_data.get("ratingsCount")
|
||
|
||
if (
|
||
not self.asset_data["isPrivate"]
|
||
and rc.get("quality") is not None
|
||
and rc.get("workingHours") is not None
|
||
):
|
||
row = box_thumbnail.row()
|
||
row.alignment = "EXPAND"
|
||
|
||
# display_ratings = can_display_ratings(self.asset_data)
|
||
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["ratingsMedian"].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']} rating{'' if rc['quality'] == 1 else 's'}"
|
||
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']} rating{'' if rc['workingHours'] == 1 else 's'}"
|
||
f"{tooltip_extension if rcount <= show_rating_threshold else ''}"
|
||
)
|
||
|
||
if (
|
||
rcount <= show_rating_prompt_threshold
|
||
and self.rating_quality == 0
|
||
and self.rating_work_hours == 0
|
||
):
|
||
# if the asset has less than 5 ratings, and the user hasn't rated it yet, prompt them to do so
|
||
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.window_manager.blenderkitUI
|
||
if self.asset_data.get("canDownload", True):
|
||
row.prop(
|
||
ui_props,
|
||
"drag_init_button",
|
||
icon="MOUSE_LMB_DRAG",
|
||
text="Click / Drag from here",
|
||
emboss=True,
|
||
)
|
||
else:
|
||
op = layout.operator(
|
||
"wm.blenderkit_url", text="Unlock this asset", icon="UNLOCKED"
|
||
)
|
||
op.url = f'{global_vars.SERVER}/get-blenderkit/{self.asset_data["id"]}/?from_addon=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 = global_vars.DATA["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:]
|
||
|
||
name_row = top_drag_bar.row()
|
||
|
||
pcoll = icons.icon_collections["main"]
|
||
|
||
name_row.label(text="", icon_value=pcoll["logo"].icon_id)
|
||
|
||
for i, c in enumerate(cat_path):
|
||
cat_name = cat_path_names[i]
|
||
ui_props = bpy.context.window_manager.blenderkitUI
|
||
if i < len(cat_path) - 1:
|
||
bl_id = "view3d.blenderkit_set_category_in_popup_card"
|
||
else:
|
||
bl_id = "view3d.blenderkit_set_category_in_popup_card_last"
|
||
op = name_row.operator(bl_id, text=cat_name + " >", emboss=True)
|
||
op.asset_type = ui_props.asset_type
|
||
# this gets filled not to change anything in browsing categories
|
||
op.category_browse = global_vars.DATA["active_category_browse"][
|
||
ui_props.asset_type
|
||
][-1]
|
||
# but enables to direclty browse the category clicked.
|
||
op.category_search = c
|
||
# name_row.label(text='>')
|
||
|
||
name_row.label(text=aname)
|
||
push_op_left(name_row, strength=1)
|
||
op = name_row.operator("view3d.close_popup_button", text="", icon="CANCEL")
|
||
|
||
def draw_comment_response(self, context, layout, comment_id):
|
||
if not utils.user_logged_in():
|
||
return
|
||
pcoll = icons.icon_collections["main"]
|
||
|
||
layout.separator()
|
||
|
||
row = layout.row()
|
||
ui_props = bpy.context.window_manager.blenderkitUI
|
||
split = row.split(factor=0.8, align=True)
|
||
split.active = True
|
||
split.prop(ui_props, "new_comment", text="", icon="GREASEPENCIL")
|
||
split = split.split()
|
||
op = split.operator(
|
||
"wm.blenderkit_post_comment",
|
||
text="post comment",
|
||
icon_value=pcoll["post_comment"].icon_id,
|
||
)
|
||
op.asset_id = self.asset_data["assetBaseId"]
|
||
op.comment_id = comment_id
|
||
|
||
layout.separator()
|
||
|
||
def draw_comment(
|
||
self, context: Context, layout: UILayout, comment: dict, width: int = 330
|
||
):
|
||
row = layout.row()
|
||
if comment["level"] > 0:
|
||
split = row.split(factor=0.05 * comment["level"])
|
||
split.label(text="")
|
||
row = split.split()
|
||
box = row.box()
|
||
box.emboss = "NORMAL"
|
||
row = box.row()
|
||
factor = 0.8
|
||
if comment["canChangeIsPrivate"]:
|
||
factor = 0.7
|
||
split = row.split(factor=factor)
|
||
is_moderator = comment["userModerator"]
|
||
if is_moderator:
|
||
role_text = f" - moderator"
|
||
else:
|
||
role_text = ""
|
||
row = split.row()
|
||
row.enabled = False
|
||
row.label(text=f"{comment['submitDate']} - {comment['userName']}{role_text}")
|
||
|
||
if comment["canChangeIsPrivate"]:
|
||
if comment["isPrivate"]:
|
||
ptext = "Private"
|
||
val = False
|
||
else:
|
||
ptext = "Public"
|
||
val = True
|
||
split = split.split()
|
||
split = split.split(factor=0.333)
|
||
split.enabled = True
|
||
op = split.operator(
|
||
"wm.blenderkit_is_private_comment", text=ptext
|
||
) # , icon='TRIA_DOWN')
|
||
op.asset_id = self.asset_data["assetBaseId"] # type: ignore
|
||
op.comment_id = comment["id"] # type: ignore
|
||
op.is_private = val # type: ignore
|
||
|
||
removal = False
|
||
likes = 0
|
||
dislikes = 0
|
||
user_liked = False
|
||
user_disliked = False
|
||
profile = global_vars.BKIT_PROFILE
|
||
|
||
for l in comment["flags"]:
|
||
if l["flag"] == "like":
|
||
likes += 1
|
||
if profile is not None:
|
||
if l["id"] == profile.id:
|
||
user_liked = True
|
||
if l["flag"] == "dislike":
|
||
dislikes += 1
|
||
if profile is not None:
|
||
if l["id"] == profile.id:
|
||
user_disliked = True
|
||
|
||
if l["flag"] == "removal":
|
||
removal = True
|
||
|
||
# row = box.row()
|
||
split = split.split()
|
||
split_like = split.split(factor=0.5)
|
||
sub_like = split_like.row()
|
||
sub_like.enabled = utils.user_logged_in() and not user_liked
|
||
# split1.emboss = 'NONE'
|
||
op = sub_like.operator(
|
||
"wm.blenderkit_upvote_comment", text=str(likes), icon="TRIA_UP"
|
||
)
|
||
op.asset_id = self.asset_data["assetBaseId"] # type: ignore
|
||
op.comment_id = comment["id"] # type: ignore
|
||
op.flag = "like" # type: ignore
|
||
|
||
split_dislike = split_like.split()
|
||
split_dislike = split_dislike.row()
|
||
split_dislike.enabled = utils.user_logged_in() and not user_disliked
|
||
op = split_dislike.operator(
|
||
"wm.blenderkit_upvote_comment", text=str(dislikes), icon="TRIA_DOWN"
|
||
)
|
||
op.asset_id = self.asset_data["assetBaseId"] # type: ignore
|
||
op.comment_id = comment["id"] # type: ignore
|
||
op.flag = "dislike" # type: ignore
|
||
|
||
if removal:
|
||
row.alert = True
|
||
row.label(text="", icon="ERROR")
|
||
rows = utils.label_multiline(
|
||
box,
|
||
text=comment["comment"],
|
||
width=width * (1 - 0.05 * comment["level"]),
|
||
use_urls=True,
|
||
)
|
||
|
||
if utils.profile_is_validator():
|
||
row = box.row()
|
||
split = row.split(factor=0.95)
|
||
split.label(text="")
|
||
split = split.split()
|
||
row.alert = False
|
||
op = row.operator("wm.url_open", text="", icon="GREASEPENCIL")
|
||
op.url = f'{global_vars.SERVER}/bksecretadmin/django_comments_xtd/xtdcomment/{comment["id"]}/change/' # type: ignore
|
||
# row.alert = True
|
||
# op = row.operator("wm.url_open", text="", icon='CANCEL')
|
||
# op.url = f'{global_vars.SERVER}/bksecretadmin/django_comments_xtd/xtdcomment/{comment["id"]}/delete/'
|
||
|
||
if utils.user_logged_in():
|
||
# row = rows[-1]
|
||
row = layout.row()
|
||
split = row.split(factor=0.8)
|
||
split.label(text="")
|
||
split = split.split()
|
||
op = split.operator(
|
||
"view3d.blenderkit_set_comment_reply_id",
|
||
text="Reply",
|
||
icon="GREASEPENCIL",
|
||
)
|
||
op.comment_id = comment["id"] # type: ignore
|
||
|
||
# box.label(text=str(comment['flags']))
|
||
|
||
def draw(self, context):
|
||
global last_time_overlay_panel_active
|
||
last_time_overlay_panel_active = time.time()
|
||
|
||
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))
|
||
|
||
if (
|
||
not utils.user_is_owner(asset_data=self.asset_data)
|
||
and self.asset_data.get("assetType") != "addon"
|
||
):
|
||
# Draw ratings, but not for owners of assets - doesn't make sense.
|
||
# also addons are now disabled until we figure out how to handle them.
|
||
ratings_box = left_column.box()
|
||
self.prefill_ratings()
|
||
ratings.draw_ratings_menu(self, context, ratings_box)
|
||
# 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))
|
||
)
|
||
|
||
# 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)
|
||
# comments
|
||
ui_props = bpy.context.window_manager.blenderkitUI
|
||
if ui_props.reply_id == 0:
|
||
self.draw_comment_response(context, layout, 0)
|
||
comments = global_vars.DATA.get("asset comments", {})
|
||
self.comments = comments.get(self.asset_data["assetBaseId"], [])
|
||
if self.comments is not None:
|
||
for comment in self.comments:
|
||
self.draw_comment(context, layout, comment, width=self.width)
|
||
if ui_props.reply_id == comment["id"]:
|
||
self.draw_comment_response(context, layout, comment["id"])
|
||
|
||
def execute(self, context):
|
||
wm = context.window_manager
|
||
ui_props = context.window_manager.blenderkitUI
|
||
ui_props.draw_tooltip = False
|
||
ui_props.reply_id = 0
|
||
|
||
history_step = search.get_active_history_step()
|
||
sr = history_step.get("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)
|
||
|
||
if asset_data["assetType"] == "printable":
|
||
self.full_photo_thumbnail = ui.get_full_photo_thumbnail(asset_data)
|
||
if self.full_photo_thumbnail:
|
||
utils.img_to_preview(self.full_photo_thumbnail, 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()
|
||
|
||
author_id = int(asset_data["author"]["id"])
|
||
author = global_vars.BKIT_AUTHORS.get(author_id)
|
||
if author and author.gravatarImg and author.gravatarHash:
|
||
self.gimg = utils.get_hidden_image(author.gravatarImg, author.gravatarHash)
|
||
|
||
self.tip = f"Tip: {random.choice(global_vars.TIPS)[0]}"
|
||
|
||
if utils.user_logged_in():
|
||
ratings_utils.ensure_rating(self.asset_id)
|
||
# pre-fill ratings
|
||
self.prefill_ratings()
|
||
|
||
user_preferences = bpy.context.preferences.addons[__package__].preferences
|
||
if (
|
||
user_preferences.asset_popup_counter
|
||
< user_preferences.asset_popup_counter_max
|
||
):
|
||
user_preferences.asset_popup_counter += 1
|
||
# get comments
|
||
api_key = user_preferences.api_key
|
||
comments = comments_utils.get_comments_local(asset_data["assetBaseId"])
|
||
# if comments is None:
|
||
client_lib.get_comments(asset_data["assetBaseId"], api_key)
|
||
|
||
# TODO: SHOULD BE DONE ONCE COMMENTS TASK IS RETURNED - HOW TO INVOKE REFRESH FROM HANDLE_GET_COMMENTS_TASK
|
||
comments = global_vars.DATA.get("asset comments", {})
|
||
self.comments = comments.get(asset_data["assetBaseId"], [])
|
||
|
||
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 SetCommentReplyId(bpy.types.Operator):
|
||
"""Set comment reply ID, setting to which comment it is replied to and where the input box should be shown."""
|
||
|
||
bl_idname = "view3d.blenderkit_set_comment_reply_id"
|
||
bl_label = "BlenderKit Set Comment reply ID"
|
||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||
|
||
comment_id: bpy.props.IntProperty( # type: ignore[valid-type]
|
||
name="Category", description="set this category active", default=0
|
||
)
|
||
|
||
@classmethod
|
||
def poll(cls, context):
|
||
return True
|
||
|
||
def execute(self, context):
|
||
ui_props = bpy.context.window_manager.blenderkitUI
|
||
ui_props.reply_id = self.comment_id
|
||
# print(f'changed reply id to {self.comment_id}')
|
||
return {"FINISHED"}
|
||
|
||
|
||
class SetCategoryOperatorOrigin(bpy.types.Operator):
|
||
bl_idname = "view3d.blenderkit_set_category_origin"
|
||
bl_label = "BlenderKit Set Active Category"
|
||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||
|
||
category_browse: bpy.props.StringProperty( # type: ignore[valid-type]
|
||
name="Category browse",
|
||
description="set this category active for browsing",
|
||
default="",
|
||
)
|
||
category_search: bpy.props.StringProperty( # type: ignore[valid-type]
|
||
name="Category search",
|
||
description="set this category active for search",
|
||
default="",
|
||
)
|
||
|
||
asset_type: bpy.props.StringProperty( # type: ignore[valid-type]
|
||
name="Asset Type", description="asset type", default="MODEL"
|
||
)
|
||
|
||
@classmethod
|
||
def poll(cls, context):
|
||
return True
|
||
|
||
def execute(self, context):
|
||
acat = global_vars.DATA["active_category_browse"][self.asset_type]
|
||
if self.category_browse == "":
|
||
acat.remove(acat[-1])
|
||
elif self.category_browse == acat[-1]:
|
||
# don't change category if it is the same
|
||
pass
|
||
else:
|
||
acat.append(self.category_browse)
|
||
search_props = utils.get_search_props()
|
||
search_props.search_category = self.category_search
|
||
# we have to write back to wm. Thought this should happen with original list.
|
||
global_vars.DATA["active_category_browse"][self.asset_type] = acat
|
||
return {"FINISHED"}
|
||
|
||
|
||
# TODO: Handle here SelectSubcategory/SelectCategory
|
||
# and VisitSubcategory/VisitCategory and VisitUpperCategory/VisitUpperSubcategory
|
||
class SetCategoryOperator(SetCategoryOperatorOrigin):
|
||
"""Visit subcategory"""
|
||
|
||
bl_idname = "view3d.blenderkit_set_category"
|
||
|
||
|
||
class SetCategoryOperatorInPopupCard(SetCategoryOperatorOrigin):
|
||
"""Subcategory of the asset. Click to search this subcategory."""
|
||
|
||
bl_idname = "view3d.blenderkit_set_category_in_popup_card"
|
||
|
||
|
||
class SetCategoryOperatorLastInPopupCard(SetCategoryOperatorOrigin):
|
||
"""Subcategory of the asset. Click to search this subcategory. Shortcut: Hover over asset in the asset bar and press 'C'."""
|
||
|
||
bl_idname = "view3d.blenderkit_set_category_in_popup_card_last"
|
||
|
||
|
||
class ToggleClipboardScan(bpy.types.Operator):
|
||
"""Toggle whether asset links are set from clipboard when copied."""
|
||
|
||
bl_idname = "wm.blenderkit_toggle_clipboard_scan"
|
||
bl_label = "Toggle Clipboard Scan"
|
||
|
||
def execute(self, context):
|
||
user_preferences = bpy.context.preferences.addons[__package__].preferences
|
||
user_preferences.use_clipboard_scan = not user_preferences.use_clipboard_scan
|
||
return {"FINISHED"}
|
||
|
||
|
||
class ClearSearchKeywords(bpy.types.Operator):
|
||
"""Clear search keywords"""
|
||
|
||
bl_idname = "view3d.blenderkit_clear_search_keywords"
|
||
bl_label = "Clear search keywords"
|
||
|
||
def execute(self, context):
|
||
ui_props = bpy.context.window_manager.blenderkitUI
|
||
ui_props.search_keywords = ""
|
||
return {"FINISHED"}
|
||
|
||
|
||
class ClosePopupButton(bpy.types.Operator):
|
||
"""Close the popup window. It can also be closed by pressing Esc or clicking outside it."""
|
||
|
||
bl_idname = "view3d.close_popup_button"
|
||
bl_label = "Close Popup"
|
||
bl_options = {"REGISTER", "INTERNAL"}
|
||
|
||
def invoke(self, context, event):
|
||
"""Force the (containing, parent) popup to close.
|
||
This was done by emulating Esc or hacking mouse, but stopped working in B5.
|
||
But can be effectively done by just tweaking screen: https://blender.stackexchange.com/a/329900
|
||
"""
|
||
bpy.context.window.screen = bpy.context.window.screen
|
||
return {"FINISHED"}
|
||
|
||
|
||
class PopupDialog(bpy.types.Operator):
|
||
"""Small popup dialog to inform user."""
|
||
|
||
bl_idname = "wm.blenderkit_popup_dialog"
|
||
bl_label = "BlenderKit message:"
|
||
bl_options = {"REGISTER", "INTERNAL"}
|
||
|
||
message: bpy.props.StringProperty(default="") # type: ignore[valid-type]
|
||
width: bpy.props.IntProperty(default=300) # type: ignore[valid-type]
|
||
|
||
def draw(self, context):
|
||
layout = self.layout
|
||
row = layout.row()
|
||
row.label(text=self.message)
|
||
row.operator("view3d.close_popup_button", text="", icon="CANCEL")
|
||
layout.active_default = True
|
||
|
||
def execute(self, context):
|
||
wm = bpy.context.window_manager
|
||
return wm.invoke_popup(self, width=self.width)
|
||
|
||
|
||
class UrlPopupDialog(bpy.types.Operator):
|
||
"""Show a popup asking the user to subscribe or log in to access the locked asset"""
|
||
|
||
bl_idname = "wm.blenderkit_url_dialog"
|
||
bl_label = "BlenderKit message:"
|
||
bl_options = {"REGISTER", "INTERNAL"}
|
||
|
||
url: bpy.props.StringProperty(name="Url", description="url", default="") # type: ignore[valid-type]
|
||
|
||
link_text: bpy.props.StringProperty( # type: ignore[valid-type]
|
||
name="Url", description="url", default="Go to website"
|
||
)
|
||
|
||
message: bpy.props.StringProperty(name="Text", description="text", default="") # type: ignore[valid-type]
|
||
|
||
width: bpy.props.IntProperty(name="width", description="width", default=300) # type: ignore[valid-type]
|
||
|
||
def draw(self, context):
|
||
layout = self.layout
|
||
row = layout.row()
|
||
utils.label_multiline(layout, text=self.message, width=300)
|
||
row.operator("view3d.close_popup_button", text="", icon="CANCEL")
|
||
|
||
layout.active_default = True
|
||
op = layout.operator("wm.url_open", text=self.link_text, icon="QUESTION")
|
||
if not utils.user_logged_in():
|
||
if self.message.find("purchased") != -1:
|
||
text = "purchased"
|
||
else:
|
||
text = "subscribed"
|
||
utils.label_multiline(
|
||
layout,
|
||
text=f"Already {text}? Log in to access your account.",
|
||
width=300,
|
||
)
|
||
|
||
layout.operator_context = "EXEC_DEFAULT"
|
||
layout.operator(
|
||
"wm.blenderkit_login", text="Welcome Home", icon="URL"
|
||
).signup = False
|
||
op.url = self.url
|
||
|
||
def execute(self, context):
|
||
wm = bpy.context.window_manager
|
||
return wm.invoke_popup(self, width=self.width)
|
||
|
||
|
||
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( # type: ignore[valid-type]
|
||
name="Message",
|
||
description="",
|
||
default="Your were logged out from . Please login again. ",
|
||
)
|
||
|
||
link_text: bpy.props.StringProperty( # type: ignore[valid-type]
|
||
name="Url", description="url", default="Login to BlenderKit"
|
||
)
|
||
|
||
# @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
|
||
layout.operator_context = "EXEC_DEFAULT"
|
||
layout.operator(
|
||
"wm.blenderkit_login", text=self.link_text, icon="URL"
|
||
).signup = False
|
||
|
||
def execute(self, context):
|
||
return {"FINISHED"}
|
||
|
||
def invoke(self, context, event):
|
||
wm = context.window_manager
|
||
return wm.invoke_props_dialog(self, width=300)
|
||
|
||
|
||
def draw_panel_categories(layout, context):
|
||
ui_props = bpy.context.window_manager.blenderkitUI
|
||
user_preferences = bpy.context.preferences.addons[__package__].preferences
|
||
|
||
search_props = utils.get_search_props()
|
||
acat_search = search_props.search_category
|
||
# row = layout.row()
|
||
# row.prop(ui_props, 'asset_type', expand=True, icon_only=True)
|
||
if global_vars.DATA.get("bkit_categories") == None:
|
||
return
|
||
col = layout.column(align=True)
|
||
if global_vars.DATA.get("active_category_browse") is not None:
|
||
acat = global_vars.DATA["active_category_browse"][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_browse = ""
|
||
op.category_search = acat[-2]
|
||
elif acat_search not in ("", ui_props.asset_type.lower()):
|
||
# we are in subcategory, so draw the parent button
|
||
op = col.operator(
|
||
"view3d.blenderkit_set_category",
|
||
text="[All]",
|
||
icon="FILE_PARENT",
|
||
)
|
||
op.asset_type = ui_props.asset_type
|
||
op.category_browse = acat[-1]
|
||
op.category_search = acat[-1]
|
||
|
||
cats = categories.get_category(global_vars.DATA["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_widget', 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=0.8, align=True)
|
||
# row = split.split()
|
||
ctext = "%s (%i)" % (c["name"], c["assetCount"])
|
||
|
||
emboss = acat_search == c["slug"]
|
||
op = row.operator(
|
||
"view3d.blenderkit_set_category", text=ctext, depress=emboss
|
||
)
|
||
op.asset_type = ui_props.asset_type
|
||
op.category_browse = acat[-1]
|
||
op.category_search = 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_browse = c["slug"]
|
||
op.category_search = 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_tasks) > 0
|
||
|
||
def draw(self, context):
|
||
layout = self.layout
|
||
for key, data in download.download_tasks.items():
|
||
row = layout.row()
|
||
row.label(text=data["asset_data"]["name"])
|
||
row.label(text=str(int(data["progress"])) + " %")
|
||
op = row.operator("scene.blenderkit_download_kill", text="", icon="CANCEL")
|
||
op.task_id = key
|
||
if data.get("retry_counter", 0) > 0:
|
||
row = layout.row()
|
||
row.label(text="failed. retrying ... ", icon="ERROR")
|
||
row.label(text=str(data["retry_counter"]))
|
||
|
||
layout.separator()
|
||
|
||
|
||
def update_header_menu_fold(self, context):
|
||
preferences = bpy.context.preferences.addons[__package__].preferences
|
||
if preferences.header_menu_fold and asset_bar_op.asset_bar_operator is not None:
|
||
bpy.ops.view3d.run_assetbar_fix_context(keep_running=False, do_search=False)
|
||
elif not preferences.header_menu_fold and asset_bar_op.asset_bar_operator is None:
|
||
bpy.ops.view3d.run_assetbar_fix_context(keep_running=True, do_search=False)
|
||
|
||
|
||
def header_search_draw(self, context):
|
||
"""Top bar menu in 3D view"""
|
||
if not utils.guard_from_crash():
|
||
return
|
||
|
||
preferences = bpy.context.preferences.addons[__package__].preferences
|
||
if not preferences.search_in_header:
|
||
return
|
||
if context.mode not in ("PAINT_TEXTURE", "OBJECT", "SCULPT", "POSE"):
|
||
return
|
||
# hide search bar if overlays are hidden
|
||
# this was nice, but was then reported as a bug by some users, who didn't understand this behaviour.
|
||
# users tend to work also with overlays hidden, so this was not a good idea.
|
||
# if context.area.spaces[0].overlay.show_overlays == False:
|
||
# return
|
||
|
||
layout = self.layout
|
||
wm = bpy.context.window_manager
|
||
ui_props = bpy.context.window_manager.blenderkitUI
|
||
|
||
props_dict = {
|
||
"MODEL": wm.blenderkit_models,
|
||
"PRINTABLE": wm.blenderkit_models, # PRINTABLE assets use same props as MODEL
|
||
"MATERIAL": wm.blenderkit_mat,
|
||
"BRUSH": wm.blenderkit_brush,
|
||
"HDR": wm.blenderkit_HDR,
|
||
"SCENE": wm.blenderkit_scene,
|
||
"NODEGROUP": wm.blenderkit_nodegroup,
|
||
"ADDON": wm.blenderkit_addon,
|
||
}
|
||
props = props_dict[ui_props.asset_type]
|
||
pcoll = icons.icon_collections["main"]
|
||
icons_dict = {
|
||
"MODEL": "OBJECT_DATAMODE",
|
||
"PRINTABLE": pcoll[
|
||
"asset_type_printable"
|
||
].icon_id, # Using our custom printable icon
|
||
"MATERIAL": "MATERIAL",
|
||
"BRUSH": "BRUSH_DATA",
|
||
"HDR": "WORLD",
|
||
"SCENE": "SCENE_DATA",
|
||
"NODEGROUP": "NODETREE",
|
||
"ADDON": "PLUGIN",
|
||
}
|
||
|
||
asset_type_icon = icons_dict[ui_props.asset_type]
|
||
# pcoll = icons.icon_collections["main"] # Removing this line since we moved it up
|
||
|
||
# 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()
|
||
row = layout.row(align=True)
|
||
row.scale_x = 0.9
|
||
|
||
if preferences.header_menu_fold:
|
||
row.prop(
|
||
preferences, "header_menu_fold", text="", icon="RIGHTARROW", emboss=False
|
||
)
|
||
row.prop(
|
||
preferences,
|
||
"header_menu_fold",
|
||
text="",
|
||
icon_value=pcoll[ui_props.logo_status].icon_id,
|
||
emboss=False,
|
||
)
|
||
return
|
||
else:
|
||
row.prop(
|
||
preferences, "header_menu_fold", text="", icon="DOWNARROW_HLT", emboss=False
|
||
)
|
||
|
||
# draw logo as part of the folding UI, it is better clickable.
|
||
row.prop(
|
||
preferences,
|
||
"header_menu_fold",
|
||
text="",
|
||
icon_value=pcoll[ui_props.logo_status].icon_id,
|
||
emboss=False,
|
||
)
|
||
# row.label(text="", icon_value=pcoll[ui_props.logo_status].icon_id)
|
||
|
||
layout = layout.row(align=True)
|
||
# layout.separator()
|
||
if not global_vars.CLIENT_RUNNING:
|
||
layout.label(text="Waiting for Client")
|
||
return
|
||
|
||
layout.prop(
|
||
ui_props,
|
||
"asset_type",
|
||
expand=True,
|
||
icon_only=True,
|
||
text="",
|
||
icon="NONE" if isinstance(asset_type_icon, int) else asset_type_icon,
|
||
icon_value=asset_type_icon if isinstance(asset_type_icon, int) else 0,
|
||
)
|
||
|
||
row = layout.row()
|
||
if (context.region.width) > 700:
|
||
row.ui_units_x = 5 + int(context.region.width / 200)
|
||
search_field_width = bpy.context.preferences.addons[
|
||
__package__
|
||
].preferences.search_field_width
|
||
|
||
has_search_keywords = ui_props.search_keywords != ""
|
||
|
||
if search_field_width > 0:
|
||
row.ui_units_x = search_field_width - has_search_keywords * 0.5
|
||
|
||
# print(row.ui_units_x)
|
||
|
||
row.prop(ui_props, "search_keywords", text="", icon="VIEWZOOM")
|
||
|
||
# if there are search keywords, draw an x icon to clear the search keywords
|
||
if has_search_keywords:
|
||
layout.operator("view3d.blenderkit_clear_search_keywords", text="", icon="X")
|
||
|
||
draw_assetbar_show_hide(layout, props)
|
||
layout.prop(ui_props, "search_bookmarks", text="", icon="BOOKMARKS")
|
||
if (
|
||
props.search_category == ui_props.asset_type.lower()
|
||
or props.search_category == ""
|
||
):
|
||
icon_id = pcoll["categories"].icon_id
|
||
else:
|
||
icon_id = pcoll["categories_active"].icon_id
|
||
|
||
layout.popover(
|
||
panel="VIEW3D_PT_blenderkit_categories",
|
||
text="",
|
||
icon_value=icon_id,
|
||
)
|
||
|
||
# FILTER ICON: filters are default or modified
|
||
if props.use_filters:
|
||
icon_id = pcoll["filter_active"].icon_id
|
||
else:
|
||
icon_id = pcoll["filter"].icon_id
|
||
|
||
if context.mode == "EDIT_MESH":
|
||
# geo node tools right now.
|
||
pass
|
||
|
||
elif 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 == "SCENE":
|
||
layout.popover(
|
||
panel="VIEW3D_PT_blenderkit_advanced_scene_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,
|
||
)
|
||
elif ui_props.asset_type == "BRUSH":
|
||
layout.popover(
|
||
panel="VIEW3D_PT_blenderkit_advanced_brush_search",
|
||
text="",
|
||
icon_value=icon_id,
|
||
)
|
||
elif ui_props.asset_type == "NODEGROUP":
|
||
layout.popover(
|
||
panel="VIEW3D_PT_blenderkit_advanced_nodegroup_search",
|
||
text="",
|
||
icon_value=icon_id,
|
||
)
|
||
elif ui_props.asset_type == "ADDON":
|
||
layout.popover(
|
||
panel="VIEW3D_PT_blenderkit_advanced_addon_search",
|
||
text="",
|
||
icon_value=icon_id,
|
||
)
|
||
elif ui_props.asset_type == "PRINTABLE":
|
||
layout.popover(
|
||
panel="VIEW3D_PT_blenderkit_advanced_printable_search",
|
||
text="",
|
||
icon_value=icon_id,
|
||
)
|
||
|
||
# NSFW filter shield badge - only for models right now
|
||
if preferences.nsfw_filter and ui_props.asset_type == "MODEL":
|
||
layout.prop(
|
||
preferences,
|
||
"nsfw_filter",
|
||
text="",
|
||
icon_value=pcoll["nsfw"].icon_id,
|
||
emboss=False,
|
||
)
|
||
|
||
# elif ui_props.asset_type in ('BRUSH', 'SCENE'):
|
||
# # this is just a placeholder so that the UI doesn't get out of alignment
|
||
# row = layout.column()
|
||
# row.enabled = False
|
||
# row.ui_units_x = 1.5
|
||
# row.label(text='', icon_value=icon_id)
|
||
|
||
notifications = global_vars.DATA.get("bkit notifications")
|
||
if notifications is not None and notifications.get("count", 0) > 0:
|
||
layout.operator(
|
||
"wm.show_notifications", text="", icon_value=pcoll["bell"].icon_id
|
||
)
|
||
# layout.popover(panel="VIEW3D_PT_blenderkit_notifications", text="", icon_value=pcoll['bell'].icon_id)
|
||
|
||
if utils.profile_is_validator():
|
||
search_props = utils.get_search_props()
|
||
layout.prop(search_props, "search_verification_status", text="")
|
||
|
||
|
||
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")
|
||
|
||
|
||
class NodegroupDropDialog(bpy.types.Operator):
|
||
"""Dialog for choosing how to add a nodegroup when dropped on an object or in node editor"""
|
||
|
||
bl_idname = "wm.blenderkit_nodegroup_drop_dialog"
|
||
bl_label = "Add Nodegroup"
|
||
bl_options = {"REGISTER", "INTERNAL"}
|
||
|
||
# Store the parameters needed for the download
|
||
asset_search_index: bpy.props.IntProperty(default=-1) # type: ignore[valid-type]
|
||
target_object_name: bpy.props.StringProperty(default="") # type: ignore[valid-type]
|
||
snapped_location: bpy.props.FloatVectorProperty(size=3) # type: ignore[valid-type]
|
||
snapped_rotation: bpy.props.FloatVectorProperty(size=3) # type: ignore[valid-type]
|
||
|
||
# Node editor positioning (when dropped in node editor)
|
||
node_x: bpy.props.FloatProperty(default=0.0) # type: ignore[valid-type]
|
||
node_y: bpy.props.FloatProperty(default=0.0) # type: ignore[valid-type]
|
||
|
||
# Option for how to add the nodegroup
|
||
add_mode: bpy.props.EnumProperty( # type: ignore[valid-type]
|
||
name="Add Mode",
|
||
description="How to add the nodegroup",
|
||
items=[
|
||
(
|
||
"MODIFIER",
|
||
"As Modifier",
|
||
"Add the nodegroup as a new modifier on the object",
|
||
),
|
||
("NODE", "As Node", "Add the nodegroup as a node in an existing node tree"),
|
||
],
|
||
default="MODIFIER",
|
||
)
|
||
|
||
# Option for overwriting existing geometry nodes modifier
|
||
overwrite_modifier: bpy.props.BoolProperty( # type: ignore[valid-type]
|
||
name="Overwrite Last Geometry Nodes Modifier",
|
||
description="Replace the last geometry nodes modifier instead of creating a new one (recommended to avoid recursion)",
|
||
default=True,
|
||
)
|
||
|
||
def get_existing_geometry_modifiers(self, target_obj):
|
||
"""Get list of existing geometry nodes modifiers on target object"""
|
||
if not target_obj:
|
||
return []
|
||
return [mod for mod in target_obj.modifiers if mod.type == "NODES"]
|
||
|
||
def draw(self, context):
|
||
layout = self.layout
|
||
|
||
# Get asset data for display
|
||
sr = search.get_search_results()
|
||
if self.asset_search_index >= 0 and self.asset_search_index < len(sr):
|
||
asset_data = sr[self.asset_search_index]
|
||
|
||
col = layout.column(align=True)
|
||
col.label(text=f"Adding nodegroup: {asset_data['displayName']}")
|
||
|
||
# Get target object and check for existing geometry nodes modifiers
|
||
target_obj = None
|
||
existing_geo_modifiers = []
|
||
if self.target_object_name:
|
||
target_obj = bpy.data.objects.get(self.target_object_name)
|
||
existing_geo_modifiers = self.get_existing_geometry_modifiers(
|
||
target_obj
|
||
)
|
||
col.label(text=f"To object: {self.target_object_name}")
|
||
else:
|
||
col.label(text="A new target object will be created")
|
||
|
||
col.separator()
|
||
|
||
col.prop(self, "add_mode", expand=True)
|
||
|
||
# Show overwrite option only for MODIFIER mode when there are existing geometry nodes modifiers
|
||
if self.add_mode == "MODIFIER" and existing_geo_modifiers:
|
||
col.separator()
|
||
|
||
# Show info about existing modifiers
|
||
if len(existing_geo_modifiers) == 1:
|
||
col.label(text=f"Found 1 geometry nodes modifier:", icon="INFO")
|
||
else:
|
||
col.label(
|
||
text=f"Found {len(existing_geo_modifiers)} geometry nodes modifiers:",
|
||
icon="INFO",
|
||
)
|
||
|
||
# Show the last modifier name
|
||
last_modifier = existing_geo_modifiers[-1]
|
||
col.label(text=f" • {last_modifier.name} (will be affected)")
|
||
|
||
col.separator()
|
||
col.prop(self, "overwrite_modifier")
|
||
|
||
col.separator()
|
||
|
||
# Add description based on selected mode
|
||
if self.add_mode == "MODIFIER":
|
||
if self.target_object_name:
|
||
if existing_geo_modifiers and self.overwrite_modifier:
|
||
col.label(text="The last geometry nodes modifier will be")
|
||
col.label(text="replaced with the new nodegroup.")
|
||
col.label(
|
||
text="(Recommended to avoid recursion)", icon="CHECKMARK"
|
||
)
|
||
else:
|
||
col.label(text="The nodegroup will be added as a new")
|
||
col.label(text="geometry nodes modifier on the object.")
|
||
if existing_geo_modifiers:
|
||
col.label(text="⚠ May cause recursion issues", icon="ERROR")
|
||
else:
|
||
col.label(text="A new cube will be created and the")
|
||
col.label(text="nodegroup added as a modifier.")
|
||
else:
|
||
if self.target_object_name:
|
||
col.label(text="The nodegroup will be added as a node")
|
||
col.label(text="in the geometry nodes editor.")
|
||
else:
|
||
col.label(text="A new cube will be created and the")
|
||
col.label(text="nodegroup added as a node.")
|
||
# Show node position if we have it
|
||
if self.node_x != 0.0 or self.node_y != 0.0:
|
||
col.label(
|
||
text=f"Position: ({self.node_x:.1f}, {self.node_y:.1f})",
|
||
icon="NODE",
|
||
)
|
||
|
||
def execute(self, context):
|
||
# Download the nodegroup with the specified mode
|
||
target_object = ""
|
||
if self.target_object_name:
|
||
target_object = self.target_object_name
|
||
|
||
# Handle modifier overwrite if requested
|
||
if self.add_mode == "MODIFIER" and self.overwrite_modifier and target_object:
|
||
|
||
target_obj = bpy.data.objects.get(target_object)
|
||
if target_obj:
|
||
existing_geo_modifiers = self.get_existing_geometry_modifiers(
|
||
target_obj
|
||
)
|
||
if existing_geo_modifiers:
|
||
# Remove the last geometry nodes modifier
|
||
last_modifier = existing_geo_modifiers[-1]
|
||
bk_logger.info(
|
||
f"Removed geometry nodes modifier '{last_modifier.name}' before adding new nodegroup"
|
||
)
|
||
target_obj.modifiers.remove(last_modifier)
|
||
|
||
# When adding as a node, use node positioning; when adding as modifier, use 3D positioning
|
||
if self.add_mode == "NODE":
|
||
bpy.ops.scene.blenderkit_download(
|
||
"EXEC_DEFAULT",
|
||
asset_index=self.asset_search_index,
|
||
node_x=self.node_x,
|
||
node_y=self.node_y,
|
||
target_object=target_object,
|
||
nodegroup_mode=self.add_mode,
|
||
model_location=self.snapped_location,
|
||
model_rotation=self.snapped_rotation,
|
||
)
|
||
else: # MODIFIER mode
|
||
bpy.ops.scene.blenderkit_download(
|
||
"EXEC_DEFAULT",
|
||
asset_index=self.asset_search_index,
|
||
model_location=self.snapped_location,
|
||
model_rotation=self.snapped_rotation,
|
||
target_object=target_object,
|
||
nodegroup_mode=self.add_mode,
|
||
)
|
||
return {"FINISHED"}
|
||
|
||
def invoke(self, context, event):
|
||
return context.window_manager.invoke_props_dialog(self, width=400)
|
||
|
||
|
||
classes = (
|
||
SetCategoryOperatorOrigin,
|
||
SetCategoryOperator,
|
||
SetCategoryOperatorInPopupCard,
|
||
SetCategoryOperatorLastInPopupCard,
|
||
ClearSearchKeywords,
|
||
SetCommentReplyId,
|
||
VIEW3D_PT_blenderkit_profile,
|
||
# VIEW3D_PT_blenderkit_login,
|
||
# VIEW3D_PT_blenderkit_notifications,
|
||
VIEW3D_PT_blenderkit_unified,
|
||
VIEW3D_PT_blenderkit_advanced_model_search,
|
||
VIEW3D_PT_blenderkit_advanced_material_search,
|
||
VIEW3D_PT_blenderkit_advanced_scene_search,
|
||
VIEW3D_PT_blenderkit_advanced_HDR_search,
|
||
VIEW3D_PT_blenderkit_advanced_brush_search,
|
||
VIEW3D_PT_blenderkit_advanced_nodegroup_search,
|
||
VIEW3D_PT_blenderkit_advanced_addon_search,
|
||
VIEW3D_PT_blenderkit_advanced_printable_search,
|
||
VIEW3D_PT_blenderkit_categories,
|
||
VIEW3D_PT_blenderkit_import_settings,
|
||
VIEW3D_PT_blenderkit_model_properties,
|
||
VIEW3D_MT_blenderkit_model_properties,
|
||
NODE_PT_blenderkit_material_properties,
|
||
OpenBlenderKitDiscord,
|
||
OpenSystemDirectory,
|
||
OpenAssetDirectory,
|
||
OpenAddonDirectory,
|
||
OpenGlobalDirectory,
|
||
OpenClientLog,
|
||
OpenTempDirectory,
|
||
# VIEW3D_PT_blenderkit_ratings,
|
||
VIEW3D_PT_blenderkit_downloads,
|
||
# OBJECT_MT_blenderkit_resolution_menu,
|
||
OBJECT_MT_blenderkit_asset_menu,
|
||
OBJECT_MT_blenderkit_login_menu,
|
||
AssetPopupCard,
|
||
PopupDialog,
|
||
UrlPopupDialog,
|
||
ClosePopupButton,
|
||
BlenderKitWelcomeOperator,
|
||
MarkNotificationRead,
|
||
UpvoteComment,
|
||
SetPrivateComment,
|
||
PostComment,
|
||
# DeleteComment,
|
||
ShowNotifications,
|
||
LogoStatus,
|
||
NotificationOpenTarget,
|
||
MarkAllNotificationsRead,
|
||
LoginPopupDialog,
|
||
NodegroupDropDialog,
|
||
)
|
||
|
||
|
||
def header_search_draw_tools(self, context):
|
||
if not bpy.context.area.spaces.active.show_region_tool_header:
|
||
return
|
||
if bpy.context.mode in ("SCULPT", "PAINT_TEXTURE"):
|
||
return
|
||
header_search_draw(self, context)
|
||
|
||
|
||
def header_search_draw_others(self, context):
|
||
if (
|
||
not bpy.context.area.spaces.active.show_region_tool_header
|
||
or bpy.context.mode in ("SCULPT", "PAINT_TEXTURE")
|
||
):
|
||
header_search_draw(self, context)
|
||
|
||
|
||
def header_draw(self, context):
|
||
layout = self.layout
|
||
|
||
self.draw_tool_settings(context)
|
||
|
||
layout.separator_spacer()
|
||
header_search_draw_tools(self, context)
|
||
layout.separator_spacer()
|
||
|
||
self.draw_mode_settings(context)
|
||
|
||
|
||
def object_context_draw(self, context):
|
||
preferences = bpy.context.preferences.addons[__package__].preferences
|
||
layout = self.layout
|
||
pcoll = icons.icon_collections["main"]
|
||
if not preferences.show_VIEW3D_MT_blenderkit_model_properties:
|
||
return
|
||
layout.menu(
|
||
VIEW3D_MT_blenderkit_model_properties.bl_idname,
|
||
icon_value=pcoll["logo"].icon_id,
|
||
)
|
||
|
||
|
||
def register_ui_panels():
|
||
for c in classes:
|
||
bpy.utils.register_class(c)
|
||
|
||
bpy.types.VIEW3D_HT_tool_header.draw = header_draw
|
||
# bpy.types.VIEW3D_HT_tool_header.append(header_search_draw)
|
||
bpy.types.VIEW3D_MT_editor_menus.append(header_search_draw_others)
|
||
bpy.types.VIEW3D_MT_object_context_menu.append(object_context_draw)
|
||
# bpy.types.VIEW3D_PT_tools_active.prepend(header_search_draw_new)
|
||
|
||
|
||
def unregister_ui_panels():
|
||
# bpy.types.VIEW3D_HT_tool_header.remove(header_search_draw)
|
||
bpy.types.VIEW3D_MT_editor_menus.remove(header_search_draw_others)
|
||
bpy.types.VIEW3D_MT_object_context_menu.remove(object_context_draw)
|
||
# bpy.types.VIEW3D_PT_tools_active.remove(header_search_draw_new)
|
||
for c in classes:
|
||
# print('unregister', c)
|
||
bpy.utils.unregister_class(c)
|