Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--blenderkit/__init__.py210
-rw-r--r--blenderkit/asset_inspector.py16
-rw-r--r--blenderkit/autothumb.py4
-rw-r--r--blenderkit/download.py29
-rw-r--r--blenderkit/icons.py53
-rw-r--r--blenderkit/paths.py1
-rw-r--r--blenderkit/ratings.py17
-rw-r--r--blenderkit/rerequests.py6
-rw-r--r--blenderkit/search.py211
-rw-r--r--blenderkit/thumbnails/flp.pngbin0 -> 540 bytes
-rw-r--r--blenderkit/thumbnails/fp.pngbin0 -> 561 bytes
-rw-r--r--blenderkit/thumbnails/vs_rejected.pngbin0 -> 2114 bytes
-rw-r--r--blenderkit/ui.py105
-rw-r--r--blenderkit/ui_panels.py240
-rw-r--r--blenderkit/upload.py34
-rw-r--r--blenderkit/upload_bg.py6
-rw-r--r--blenderkit/utils.py46
17 files changed, 703 insertions, 275 deletions
diff --git a/blenderkit/__init__.py b/blenderkit/__init__.py
index 5cecc7b9..87358d9b 100644
--- a/blenderkit/__init__.py
+++ b/blenderkit/__init__.py
@@ -19,14 +19,14 @@
bl_info = {
"name": "BlenderKit Asset Library",
"author": "Vilem Duha, Petr Dlouhy",
- "version": (1, 0, 29),
+ "version": (1, 0, 30),
"blender": (2, 82, 0),
"location": "View3D > Properties > BlenderKit",
"description": "Online BlenderKit library (materials, models, brushes and more)",
"warning": "",
"wiki_url": "https://youtu.be/1hVgcQhIAo8"
"Scripts/Add_Mesh/BlenderKit",
- "category": "Add Mesh",
+ "category": "3D View",
}
if "bpy" in locals():
@@ -39,6 +39,7 @@ if "bpy" in locals():
ratings = reload(ratings)
autothumb = reload(autothumb)
ui = reload(ui)
+ icons = reload(icons)
bg_blender = reload(bg_blender)
paths = reload(paths)
utils = reload(utils)
@@ -48,7 +49,8 @@ if "bpy" in locals():
bkit_oauth = reload(bkit_oauth)
tasks_queue = reload(tasks_queue)
else:
- from blenderkit import asset_inspector, search, download, upload, ratings, autothumb, ui, bg_blender, paths, utils, \
+ from blenderkit import asset_inspector, search, download, upload, ratings, autothumb, ui, icons, bg_blender, paths, \
+ utils, \
overrides, ui_panels, categories, bkit_oauth, tasks_queue
import os
@@ -103,6 +105,7 @@ def check_timers_timer():
bpy.app.timers.register(bg_blender.bg_update)
return 5.0
+
licenses = (
('royalty_free', 'Royalty Free', 'royalty free commercial license'),
('cc_zero', 'Creative Commons Zero', 'Creative Commons Zero'),
@@ -191,6 +194,7 @@ thumbnail_resolutions = (
('2048', '2048', ''),
)
+
def get_upload_asset_type(self):
typemapper = {
BlenderKitModelUploadProps: 'model',
@@ -244,16 +248,17 @@ def switch_search_results(self, context):
s['search results orig'] = s.get('bkit brush search orig')
search.load_previews()
+
def asset_type_callback(self, context):
- #s = bpy.context.scene
- #ui_props = s.blenderkitUI
+ # s = bpy.context.scene
+ # ui_props = s.blenderkitUI
if self.down_up == 'SEARCH':
items = (
- ('MODEL', 'Search Models', 'Browse models', 'OBJECT_DATAMODE', 0),
+ ('MODEL', 'Find Models', 'Find models in the BlenderKit online database', 'OBJECT_DATAMODE', 0),
# ('SCENE', 'SCENE', 'Browse scenes', 'SCENE_DATA', 1),
- ('MATERIAL', 'Search Materials', 'Browse materials', 'MATERIAL', 2),
+ ('MATERIAL', 'Find Materials', 'Find models in the BlenderKit online database', 'MATERIAL', 2),
# ('TEXTURE', 'Texture', 'Browse textures', 'TEXTURE', 3),
- ('BRUSH', 'Search Brushes', 'Browse brushes', 'BRUSH_DATA', 3)
+ ('BRUSH', 'Find Brushes', 'Find models in the BlenderKit online database', 'BRUSH_DATA', 3)
)
else:
items = (
@@ -265,6 +270,7 @@ def asset_type_callback(self, context):
)
return items
+
class BlenderKitUIProps(PropertyGroup):
down_up: EnumProperty(
name="Download vs Upload",
@@ -378,6 +384,10 @@ class BlenderKitUIProps(PropertyGroup):
dragging_rating_work_hours: BoolProperty(name="Dragging Rating Work Hours", default=False)
last_rating_time: FloatProperty(name="Last Rating Time", default=0.0)
+def search_procedural_update(self,context):
+ if self.search_procedural in ('PROCEDURAL', 'BOTH'):
+ self.search_texture_resolution = False
+ search.search_update(self, context)
class BlenderKitCommonSearchProps(object):
# STATES
@@ -386,12 +396,90 @@ class BlenderKitCommonSearchProps(object):
default=False)
search_done: BoolProperty(name="Search Completed", description="at least one search did run (internal)",
default=False)
+ own_only: BoolProperty(name="My Assets", description="Search only for your assets",
+ default=False)
+ search_advanced: BoolProperty(name="Advanced Search Options", description="use advanced search properties",
+ default=False, update=search.search_update)
+
search_error: BoolProperty(name="Search Error", description="last search had an error", default=False)
report: StringProperty(
name="Report",
description="errors and messages",
default="")
+ # TEXTURE RESOLUTION
+ search_texture_resolution: BoolProperty(name="Texture Resolution",
+ description="Span of the texture resolutions",
+ default=False,
+ update=search.search_update,
+ )
+ search_texture_resolution_min: IntProperty(name="Min Texture Resolution",
+ description="Minimum texture resolution",
+ default=256,
+ min=0,
+ max=32768,
+ update=search.search_update,
+ )
+
+ search_texture_resolution_max: IntProperty(name="Max Texture Resolution",
+ description="Maximum texture resolution",
+ default=4096,
+ min=0,
+ max=32768,
+ update=search.search_update,
+ )
+
+ # file_size
+ search_file_size: BoolProperty(name="File Size",
+ description="Span of the file sizes",
+ default=False,
+ update=search.search_update,
+ )
+ search_file_size_min: IntProperty(name="Min File Size",
+ description="Minimum file size",
+ default=0,
+ min=0,
+ max=2000,
+ update=search.search_update,
+ )
+
+ search_file_size_max: IntProperty(name="Max File Size",
+ description="Maximum file size",
+ default=500,
+ min=0,
+ max=2000,
+ update=search.search_update,
+ )
+
+ search_procedural: EnumProperty(
+ items=(
+ ('BOTH', 'Both', ''),
+ ('PROCEDURAL', 'Procedural', ''),
+ ('TEXTURE_BASED', 'Texture based', ''),
+
+ ),
+ default='BOTH',
+ description='Search only procedural/texture based assets',
+ update=search_procedural_update
+ )
+
+ search_verification_status: EnumProperty(
+ name="Verification status",
+ description="Search by verification status",
+ items=
+ (
+ ('ALL', 'All', 'All'),
+ ('UPLOADING', 'Uploading', 'Uploading'),
+ ('UPLOADED', 'Uploaded', 'Uploaded'),
+ ('VALIDATED', 'Validated', 'Calidated'),
+ ('ON_HOLD', 'On Hold', 'On Hold'),
+ ('REJECTED', 'Rejected', 'Rejected'),
+ ('DELETED', 'Deleted', 'Deleted'),
+ ),
+ default='ALL',
+ update=search.search_update,
+ )
+
def name_update(self, context):
''' checks for name change, because it decides if whole asset has to be re-uploaded. Name is stored in the blend file
@@ -399,7 +487,6 @@ def name_update(self, context):
utils.name_update()
-
def update_tags(self, context):
props = utils.get_upload_props()
@@ -424,6 +511,7 @@ def update_tags(self, context):
if props.tags != ns:
props.tags = ns
+
def update_free(self, context):
if self.is_free == False:
self.is_free = True
@@ -438,6 +526,7 @@ def update_free(self, context):
bpy.context.window_manager.popup_menu(draw_message, title=title, icon='INFO')
+
class BlenderKitCommonUploadProps(object):
id: StringProperty(
name="Asset Version Id",
@@ -504,12 +593,12 @@ class BlenderKitCommonUploadProps(object):
)
is_procedural: BoolProperty(name="Procedural",
- description="Asset is procedural - has no texture.",
- default=True
- )
+ description="Asset is procedural - has no texture.",
+ default=True
+ )
node_count: IntProperty(name="Node count", description="Total nodes in the asset", default=0)
- texture_count: IntProperty(name="Node count", description="Total nodes in the asset", default=0)
- total_megapixels: IntProperty(name="Node count", description="Total nodes in the asset", default=0)
+ texture_count: IntProperty(name="Texture count", description="Total texture count in asset", default=0)
+ total_megapixels: IntProperty(name="Megapixels", description="Total megapixels of texture", default=0)
# is_private: BoolProperty(name="Asset is Private",
# description="If not marked private, your asset will go into the validation process automatically\n"
@@ -591,22 +680,26 @@ class BlenderKitMaterialSearchProps(PropertyGroup, BlenderKitCommonSearchProps):
items=search_material_styles,
description="Style of material",
default="ANY",
+ update=search.search_update,
)
search_style_other: StringProperty(
name="Style Other",
description="Style not in the list",
default="",
+ update=search.search_update,
)
search_engine: EnumProperty(
name='Engine',
items=engines,
default='NONE',
description='Output engine',
+ update=search.search_update,
)
search_engine_other: StringProperty(
name="Engine",
description="engine not specified by addon",
default="",
+ update=search.search_update,
)
automap: BoolProperty(name="Auto-Map",
description="reset object texture space and also add automatically a cube mapped UV "
@@ -1145,10 +1238,7 @@ class BlenderKitModelSearchProps(PropertyGroup, BlenderKitCommonSearchProps):
)
free_only: BoolProperty(name="Free only", description="Show only free models",
- default=False)
-
- search_advanced: BoolProperty(name="Advanced Search Options", description="use advanced search properties",
- default=False)
+ default=False, update=search.search_update)
# CONDITION
search_condition: EnumProperty(
@@ -1167,52 +1257,45 @@ class BlenderKitModelSearchProps(PropertyGroup, BlenderKitCommonSearchProps):
# DESIGN YEAR
search_design_year: BoolProperty(name="Sesigned in Year",
- description="when the object was approximately designed",
- default=False)
+ description="When the object was approximately designed",
+ default=False,
+ update=search.search_update,
+ )
- search_design_year_min: IntProperty(name="Min Age",
- description="when the object was approximately designed",
- default=1950, min=-100000000, max=1000000000)
+ search_design_year_min: IntProperty(name="Minimum Design Year",
+ description="Minimum design year",
+ default=1950, min=-100000000, max=1000000000,
+ update=search.search_update,
+ )
- search_design_year_max: IntProperty(name="Max Age",
- description="when the object was approximately designed",
+ search_design_year_max: IntProperty(name="Maximum Design Year",
+ description="Maximum design year",
default=2017,
min=0,
- max=10000000)
-
- # TEXTURE RESOLUTION
- search_texture_resolution: BoolProperty(name="Texture Resolution",
- description="Span of the texture resolutions",
- default=False)
-
- search_texture_resolution_min: IntProperty(name="Min Texture Resolution",
- description="when the object was approximately designed",
- default=256,
- min=0,
- max=32768)
-
- search_texture_resolution_max: IntProperty(name="Max Texture Resolution",
- description="when the object was approximately designed",
- default=4096,
- min=0,
- max=32768)
+ max=10000000,
+ update=search.search_update,
+ )
# POLYCOUNT
search_polycount: BoolProperty(name="Use Polycount",
- description="use polycount of object search tag",
- default=False)
+ description="Use polycount of object search tag",
+ default=False,
+ update=search.search_update, )
search_polycount_min: IntProperty(name="Min Polycount",
- description="polycount of the asset minimum",
+ description="Minimum poly count of the asset",
default=0,
min=0,
- max=100000000)
+ max=100000000,
+ update=search.search_update, )
search_polycount_max: IntProperty(name="Max Polycount",
- description="polycount of the asset maximum",
+ description="Maximum poly count of the asset",
default=100000000,
min=0,
- max=100000000)
+ max=100000000,
+ update=search.search_update,
+ )
append_method: EnumProperty(
name="Import Method",
@@ -1324,9 +1407,9 @@ class BlenderKitAddonPreferences(AddonPreferences):
)
api_key_timeout: IntProperty(
- name = 'api key timeout',
- description = 'time where the api key will need to be refreshed',
- default = 0,
+ name='api key timeout',
+ description='time where the api key will need to be refreshed',
+ default=0,
)
api_key_life: IntProperty(
@@ -1353,6 +1436,18 @@ class BlenderKitAddonPreferences(AddonPreferences):
default=False
)
+ tips_on_start: BoolProperty(
+ name="Show tips when starting blender",
+ description="Show tips when starting blender",
+ default=False
+ )
+
+ search_in_header: BoolProperty(
+ name="Show BlenderKit search in 3d view header",
+ description="Show BlenderKit search in 3d view header",
+ default=True
+ )
+
global_dir: StringProperty(
name="Global Files Directory",
description="Global storage for your assets, will use subdirectories for the contents",
@@ -1389,7 +1484,7 @@ class BlenderKitAddonPreferences(AddonPreferences):
thumbnail_use_gpu: BoolProperty(
name="Use GPU for Thumbnails Rendering",
description="By default this is off so you can continue your work without any lag",
- default=True
+ default=False
)
panel_behaviour: EnumProperty(
@@ -1413,10 +1508,8 @@ class BlenderKitAddonPreferences(AddonPreferences):
min=0,
max=20)
-
thumb_size: IntProperty(name="Assetbar thumbnail Size", default=96, min=-1, max=256)
-
asset_counter: IntProperty(name="Usage Counter",
description="Counts usages so it asks for registration only after reaching a limit",
default=0,
@@ -1447,7 +1540,7 @@ class BlenderKitAddonPreferences(AddonPreferences):
layout.operator("wm.blenderkit_logout", text="Logout",
icon='URL')
- #if not self.enable_oauth:
+ # if not self.enable_oauth:
layout.prop(self, "api_key", text='Your API Key')
# layout.label(text='After you paste API Key, categories are downloaded, so blender will freeze for a few seconds.')
layout.prop(self, "global_dir")
@@ -1459,6 +1552,8 @@ class BlenderKitAddonPreferences(AddonPreferences):
# layout.prop(self, "panel_behaviour")
layout.prop(self, "thumb_size")
layout.prop(self, "max_assetbar_rows")
+ layout.prop(self, "tips_on_start")
+ layout.prop(self, "search_in_header")
# registration
@@ -1531,6 +1626,7 @@ def register():
ratings.register_ratings()
autothumb.register_thumbnailer()
ui.register_ui()
+ icons.register_icons()
ui_panels.register_ui_panels()
bg_blender.register()
utils.load_prefs()
@@ -1544,10 +1640,10 @@ def register():
def unregister():
-
bpy.app.timers.unregister(check_timers_timer)
ui.unregister_ui()
+ icons.unregister_icons()
search.unregister_search()
asset_inspector.unregister_asset_inspector()
download.unregister_download()
diff --git a/blenderkit/asset_inspector.py b/blenderkit/asset_inspector.py
index 74f814ca..e6fdc659 100644
--- a/blenderkit/asset_inspector.py
+++ b/blenderkit/asset_inspector.py
@@ -85,8 +85,11 @@ def check_render_engine(props, obs):
mattype = None
materials = []
shaders = []
+ textures = []
props.uv = False
-
+ props.texture_count = 0
+ props.total_megapixels = 0
+ props.node_count = 0
for ob in obs: # TODO , this is duplicated here for other engines, otherwise this should be more clever.
for ms in ob.material_slots:
if ms.material is not None:
@@ -108,6 +111,7 @@ def check_render_engine(props, obs):
checknodes = m.node_tree.nodes[:]
while len(checknodes) > 0:
n = checknodes.pop()
+ props.node_count +=1
if n.type == 'GROUP': # dive deeper here.
checknodes.extend(n.node_tree.nodes)
if len(n.outputs) == 1 and n.outputs[0].type == 'SHADER' and n.type != 'GROUP':
@@ -115,19 +119,21 @@ def check_render_engine(props, obs):
shaders.append(n.type)
if n.type == 'TEX_IMAGE':
mattype = 'image based'
- if n.image is not None:
+ props.is_procedural = False
+ if n.image not in textures:
+ textures.append(n.image)
+ props.texture_count += 1
+ props.total_megapixels += (n.image.size[0] * n.image.size[1])
maxres = max(n.image.size[0], n.image.size[1])
-
props.texture_resolution_max = max(props.texture_resolution_max, maxres)
-
minres = min(n.image.size[0], n.image.size[1])
-
if props.texture_resolution_min == 0:
props.texture_resolution_min = minres
else:
props.texture_resolution_min = min(props.texture_resolution_min, minres)
+
# if mattype == None:
# mattype = 'procedural'
# tags['material type'] = mattype
diff --git a/blenderkit/autothumb.py b/blenderkit/autothumb.py
index 06efd8a0..f26f99dd 100644
--- a/blenderkit/autothumb.py
+++ b/blenderkit/autothumb.py
@@ -262,6 +262,8 @@ class GenerateThumbnailOperator(bpy.types.Operator):
layout.prop(props, 'thumbnail_samples')
layout.prop(props, 'thumbnail_resolution')
layout.prop(props, 'thumbnail_denoising')
+ preferences = bpy.context.preferences.addons['blenderkit'].preferences
+ layout.prop(preferences, "thumbnail_use_gpu")
def execute(self, context):
start_thumbnailer(self, context)
@@ -307,6 +309,8 @@ class GenerateMaterialThumbnailOperator(bpy.types.Operator):
layout.prop(props, 'thumbnail_samples')
layout.prop(props, 'thumbnail_denoising')
layout.prop(props, 'adaptive_subdivision')
+ preferences = bpy.context.preferences.addons['blenderkit'].preferences
+ layout.prop(preferences, "thumbnail_use_gpu")
def execute(self, context):
start_material_thumbnailer(self, context)
diff --git a/blenderkit/download.py b/blenderkit/download.py
index c4a14ecd..3a99f66f 100644
--- a/blenderkit/download.py
+++ b/blenderkit/download.py
@@ -441,6 +441,7 @@ def append_asset(asset_data, **kwargs): # downloaders=[], location=None,
scene['assets rated'][id] = scene['assets rated'].get(id, False)
parent['asset_data'] = asset_data # TODO remove this??? should write to blenderkit Props?
+ bpy.ops.wm.undo_push_context()
# moving reporting to on save.
# report_use_success(asset_data['id'])
@@ -523,6 +524,34 @@ def timer_update(): # TODO might get moved to handle all blenderkit stuff, not
return .5
+def download_file(asset_data):
+ #this is a simple non-threaded way to download files for background resolution genenration tool
+ file_name = paths.get_download_filenames(asset_data)[0] # prefer global dir if possible.
+
+ if check_existing(asset_data):
+ # this sends the thread for processing, where another check should occur, since the file might be corrupted.
+ utils.p('not downloading, already in db')
+ return file_name
+ preferences = bpy.context.preferences.addons['blenderkit'].preferences
+ api_key = preferences.api_key
+
+ with open(file_name, "wb") as f:
+ print("Downloading %s" % file_name)
+ headers = utils.get_headers(api_key)
+
+ response = requests.get(asset_data['url'], stream=True)
+ total_length = response.headers.get('Content-Length')
+
+ if total_length is None: # no content length header
+ f.write(response.content)
+ else:
+ dl = 0
+ for data in response.iter_content(chunk_size=4096):
+ dl += len(data)
+ print(dl)
+ f.write(data)
+ return file_name
+
class Downloader(threading.Thread):
def __init__(self, asset_data, tcom, scene_id, api_key):
super(Downloader, self).__init__()
diff --git a/blenderkit/icons.py b/blenderkit/icons.py
new file mode 100644
index 00000000..3c6cea4b
--- /dev/null
+++ b/blenderkit/icons.py
@@ -0,0 +1,53 @@
+# ##### 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 os
+import bpy
+
+# We can store multiple preview collections here,
+# however in this example we only store "main"
+icon_collections = {}
+
+icons_read = {
+ 'fp.png': 'free',
+ 'flp.png': 'full',
+}
+
+
+def register_icons():
+ # Note that preview collections returned by bpy.utils.previews
+ # are regular py objects - you can use them to store custom data.
+ import bpy.utils.previews
+ pcoll = bpy.utils.previews.new()
+
+ # path to the folder where the icon is
+ # the path is calculated relative to this py file inside the addon folder
+ icons_dir = os.path.join(os.path.dirname(__file__), "thumbnails")
+
+ # load a preview thumbnail of a file and store in the previews collection
+ for ir in icons_read.keys():
+ pcoll.load(icons_read[ir], os.path.join(icons_dir, ir), 'IMAGE')
+
+ icon_collections["main"] = pcoll
+
+
+def unregister_icons():
+ for pcoll in icon_collections.values():
+ bpy.utils.previews.remove(pcoll)
+ icon_collections.clear()
diff --git a/blenderkit/paths.py b/blenderkit/paths.py
index 3b0f22f9..112e2465 100644
--- a/blenderkit/paths.py
+++ b/blenderkit/paths.py
@@ -33,7 +33,6 @@ BLENDERKIT_BRUSH_UPLOAD_INSTRUCTIONS_URL = "https://www.blenderkit.com/docs/uplo
BLENDERKIT_LOGIN_URL = "https://www.blenderkit.com/accounts/login"
BLENDERKIT_OAUTH_LANDING_URL = "/oauth-landing/"
BLENDERKIT_SIGNUP_URL = "https://www.blenderkit.com/accounts/register"
-BLENDERKIT_ADDON_FILE_URL = "https://www.blenderkit.com/get-blenderkit/"
BLENDERKIT_SETTINGS_FILENAME = os.path.join(_presets, "bkit.json")
diff --git a/blenderkit/ratings.py b/blenderkit/ratings.py
index 7684d017..96cbc01f 100644
--- a/blenderkit/ratings.py
+++ b/blenderkit/ratings.py
@@ -80,19 +80,30 @@ def uplaod_review_thread(url, reviews, headers):
# except requests.exceptions.RequestException as e:
# print('reviews upload failed: %s' % str(e))
+def get_rating(asset_id):
+ user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
+ api_key = user_preferences.api_key
+ headers = utils.get_headers(api_key)
+ rl = paths.get_api_url() + 'assets/' + asset['asset_data']['id'] + '/rating/'
+ rtypes = ['quality', 'working_hours']
+ for rt in rtypes:
+ params = {
+ 'rating_type' : rt
+ }
+ r = rerequests.get(r1, params=data, verify=True, headers=headers)
+ print(r.text)
def upload_rating(asset):
user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
api_key = user_preferences.api_key
headers = utils.get_headers(api_key)
- asset_data = asset['asset_data']
-
bkit_ratings = asset.bkit_ratings
# print('rating asset', asset_data['name'], asset_data['asset_base_id'])
url = paths.get_api_url() + 'assets/' + asset['asset_data']['id'] + '/rating/'
ratings = [
+
]
if bkit_ratings.rating_quality > 0.1:
@@ -154,7 +165,7 @@ asset_types = (
class UploadRatingOperator(bpy.types.Operator):
"""Upload rating to the web db"""
bl_idname = "object.blenderkit_rating_upload"
- bl_label = "Upload the Rating"
+ bl_label = "Send Rating"
bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}
# type of upload - model, material, textures, e.t.c.
diff --git a/blenderkit/rerequests.py b/blenderkit/rerequests.py
index 0524c156..eab78fba 100644
--- a/blenderkit/rerequests.py
+++ b/blenderkit/rerequests.py
@@ -55,9 +55,11 @@ def rerequest(method, url, **kwargs):
user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
if user_preferences.api_key != '':
if user_preferences.enable_oauth:
- tasks_queue.add_task((ui.add_report, ('refreshing token.',)))
+ tasks_queue.add_task((ui.add_report, (
+ 'refreshing token. If this fails, please login in BlenderKit Login panel.', 10)))
refresh_url = paths.get_bkit_url()
- auth_token, refresh_token, oauth_response = bkit_oauth.refresh_token(user_preferences.api_key_refresh, refresh_url)
+ auth_token, refresh_token, oauth_response = bkit_oauth.refresh_token(
+ user_preferences.api_key_refresh, refresh_url)
# utils.p(auth_token, refresh_token)
if auth_token is not None:
diff --git a/blenderkit/search.py b/blenderkit/search.py
index 87bf45da..56c22dbb 100644
--- a/blenderkit/search.py
+++ b/blenderkit/search.py
@@ -24,12 +24,13 @@ if "bpy" in locals():
utils = reload(utils)
categories = reload(categories)
ui = reload(ui)
+ colors = reload(colors)
bkit_oauth = reload(bkit_oauth)
version_checker = reload(version_checker)
tasks_queue = reload(tasks_queue)
rerequests = reload(rerequests)
else:
- from blenderkit import paths, utils, categories, ui, bkit_oauth, version_checker, tasks_queue, rerequests
+ from blenderkit import paths, utils, categories, ui, colors, bkit_oauth, version_checker, tasks_queue, rerequests
import blenderkit
from bpy.app.handlers import persistent
@@ -80,6 +81,16 @@ thumb_sml_download_threads = {}
thumb_full_download_threads = {}
reports = ''
+rtips = ['Click or drag model or material in scene to link/append ',
+ "Please rate responsively and plentifully. This helps us distribute rewards to the authors.",
+ "Click on brushes to link them into scene.",
+ "All materials and brushes are free.",
+ "Storage for public assets is unlimited.",
+ "Locked models are available if you subscribe to Full plan.",
+ "Login to upload your own models, materials or brushes.",
+ "Use 'A' key over asset bar to search assets by same author.",
+ "Use 'W' key over asset bar to open Authors webpage.", ]
+
def refresh_token_timer():
''' this timer gets run every time the token needs refresh. It refreshes tokens and also categories.'''
@@ -117,16 +128,37 @@ def fetch_server_data():
first_time = True
+last_clipboard = ''
+
@bpy.app.handlers.persistent
def timer_update(): # TODO might get moved to handle all blenderkit stuff.
- #this makes a first search after opening blender. showing latest assets.
+ # this makes a first search after opening blender. showing latest assets.
global first_time
preferences = bpy.context.preferences.addons['blenderkit'].preferences
if first_time:
first_time = False
if preferences.show_on_start:
search()
+ if preferences.tips_on_start:
+ ui.get_largest_3dview()
+ ui.update_ui_size(ui.active_area, ui.active_region)
+ ui.add_report(text='BlenderKit Tip: ' + random.choice(rtips), timeout=12, color=colors.GREEN)
+
+ # clipboard monitoring to search assets from web
+ global last_clipboard
+ if bpy.context.window_manager.clipboard != last_clipboard:
+ last_clipboard = bpy.context.window_manager.clipboard
+ instr = 'asset_base_id:'
+ if last_clipboard[:len(instr)] == instr:
+ atstr = 'asset_type:'
+ ati = last_clipboard.find(atstr)
+ if ati > -1:
+ at = last_clipboard[ati:]
+
+ search_props = utils.get_search_props()
+ search_props.search_keywords = last_clipboard
+ search()
global search_threads
# don't do anything while dragging - this could switch asset type during drag, and make results list length different,
@@ -134,7 +166,7 @@ def timer_update(): # TODO might get moved to handle all blenderkit stuff.
if len(search_threads) == 0 or bpy.context.scene.blenderkitUI.dragging:
return 1
for thread in search_threads: # TODO this doesn't check all processes when one gets removed,
- # but most of the time only one is running anyway
+ # but most of the time only one is running anyway
if not thread[0].is_alive():
search_threads.remove(thread) #
icons_dir = thread[1]
@@ -220,7 +252,7 @@ def timer_update(): # TODO might get moved to handle all blenderkit stuff.
asset_data['downloaded'] = 0
# parse extra params needed for blender here
- params = params_to_dict(r['parameters'])
+ params = utils.params_to_dict(r['parameters'])
if asset_type == 'model':
if params.get('boundBoxMinX') != None:
@@ -333,11 +365,11 @@ def split_subs(text, threshold=40):
lines = []
while len(text) > threshold:
- #first handle if there's an \n line ending
+ # first handle if there's an \n line ending
i_rn = text.find('\n')
if 1 < i_rn < threshold:
i = i_rn
- text = text.replace('\n','',1)
+ text = text.replace('\n', '', 1)
else:
i = text.rfind(' ', 0, threshold)
i1 = text.rfind(',', 0, threshold)
@@ -403,17 +435,10 @@ def has(mdata, prop):
return False
-def params_to_dict(params):
- params_dict = {}
- for p in params:
- params_dict[p['parameterType']] = p['value']
- return params_dict
-
-
def generate_tooltip(mdata):
col_w = 40
if type(mdata['parameters']) == list:
- mparams = params_to_dict(mdata['parameters'])
+ mparams = utils.params_to_dict(mdata['parameters'])
else:
mparams = mdata['parameters']
t = ''
@@ -479,7 +504,7 @@ def generate_tooltip(mdata):
# t += 'uv: %s\n' % mdata['uv']
# t += '\n'
- # t = writeblockm(t, mdata, key='license', width = col_w)
+ t = writeblockm(t, mdata, key='license', width = col_w)
# generator is for both upload preview and search, this is only after search
# if mdata.get('versionNumber'):
@@ -500,14 +525,7 @@ def generate_tooltip(mdata):
def get_random_tip(mdata):
t = ''
- rtips = ['Click or drag model or material in scene to link/append ',
- "Click on brushes to link them into scene.",
- "All materials are free.",
- "All brushes are free.",
- "Locked models are available if you subscribe to Full plan.",
- "Login to upload your own models, materials or brushes.",
- "Use 'A' key to search assets by same author.",
- "Use 'W' key to open Authors webpage.", ]
+
tip = 'Tip: ' + random.choice(rtips)
t = writeblock(t, tip)
return t
@@ -623,6 +641,7 @@ def fetch_author(a_id, api_key):
utils.p(e)
utils.p('finish fetch')
+
# profile_counter =0
def get_author(r):
@@ -632,7 +651,7 @@ def get_author(r):
if authors == {}:
bpy.context.window_manager['bkit authors'] = authors
a = authors.get(a_id)
- if a is None:# or a is '' or (a.get('gravatarHash') is not None and a.get('gravatarImg') is None):
+ if a is None: # or a is '' or (a.get('gravatarHash') is not None and a.get('gravatarImg') is None):
authors[a_id] = ''
thread = threading.Thread(target=fetch_author, args=(a_id, preferences.api_key), daemon=True)
thread.start()
@@ -683,7 +702,6 @@ def fetch_profile(api_key):
utils.p(e)
-
def get_profile():
preferences = bpy.context.preferences.addons['blenderkit'].preferences
a = bpy.context.window_manager.get('bkit profile')
@@ -691,11 +709,6 @@ def get_profile():
thread.start()
return a
-def profile_is_validator():
- a = bpy.context.window_manager.get('bkit profile')
- if a is not None and a['user'].get('exmenu'):
- return True
- return False
class Searcher(threading.Thread):
query = None
@@ -712,6 +725,45 @@ class Searcher(threading.Thread):
def stopped(self):
return self._stop_event.is_set()
+ def query_to_url(self):
+ query = self.query
+ params = self.params
+ # build a new request
+ url = paths.get_api_url() + 'search/'
+
+ # build request manually
+ # TODO use real queries
+ requeststring = '?query='
+ #
+ if query.get('query') not in ('', None):
+ requeststring += query['query'].lower()
+ for i, q in enumerate(query):
+ if q != 'query':
+ requeststring += '+'
+ requeststring += q + ':' + str(query[q]).lower()
+
+ # result ordering: _score - relevance, score - BlenderKit score
+
+ if query.get('query') is None and query.get('category_subtree') == None:
+ # assumes no keywords and no category, thus an empty search that is triggered on start.
+ # orders by last core file upload
+ requeststring += '+order:-last_upload'
+ elif query.get('author_id') is not None and utils.profile_is_validator():
+
+ requeststring += '+order:-created'
+ else:
+ if query.get('category_subtree') is not None:
+ requeststring += '+order:-score,_score'
+ else:
+ requeststring += '+order:_score'
+
+ requeststring += '&addon_version=%s' % params['addon_version']
+ if params.get('scene_uuid') is not None:
+ requeststring += '&scene_uuid=%s' % params['scene_uuid']
+ # print('params', params)
+ urlquery = url + requeststring
+ return urlquery
+
def run(self):
maxthreads = 50
query = self.query
@@ -733,44 +785,23 @@ class Searcher(threading.Thread):
try:
origdata = json.load(infile)
urlquery = origdata['next']
+ # rparameters = {}
if urlquery == None:
return;
except:
# in case no search results found on drive we don't do next page loading.
params['get_next'] = False
if not params['get_next']:
- # build a new request
url = paths.get_api_url() + 'search/'
- # build request manually
- # TODO use real queries
- requeststring = '?query=' + query['keywords'].lower() + '+'
- #
- for i, q in enumerate(query):
- requeststring += q + ':' + str(query[q]).lower()
- if i < len(query) - 1:
- requeststring += '+'
-
- # result ordering: _score - relevance, score - BlenderKit score
- #first condition assumes no keywords and no category, thus an empty search that is triggered on start.
- if query['keywords'] == '' and query.get('category_subtree') == None:
- requeststring += '+order:-created'
- elif query.get('author_id') is not None and profile_is_validator():
- requeststring += '+order:-created'
- else:
- if query.get('category_subtree') is not None:
- requeststring += '+order:-score,_score'
- else:
- requeststring += '+order:_score'
-
- requeststring += '&addon_version=%s' % params['addon_version']
- if params.get('scene_uuid') is not None:
- requeststring += '&scene_uuid=%s' % params['scene_uuid']
+ urlquery = url
- urlquery = url + requeststring
+ # rparameters = query
+ urlquery = self.query_to_url()
try:
utils.p(urlquery)
- r = rerequests.get(urlquery, headers=headers)
+ r = rerequests.get(urlquery, headers=headers) # , params = rparameters)
+ # print(r.url)
reports = ''
# utils.p(r.text)
except requests.exceptions.RequestException as e:
@@ -798,13 +829,13 @@ class Searcher(threading.Thread):
if p['parameterType'] == 'mode':
mode = p['value']
if query['asset_type'] != 'brush' or (
- query.get('brushType') != None and query['brushType']) == mode:
+ query.get('mode') != None and query['mode']) == mode:
nresults.append(d)
rdata['results'] = nresults
# print('number of results: ', len(rdata.get('results', [])))
if self.stopped():
- utils.p('stopping search : ' + query['keywords'])
+ utils.p('stopping search : ' + str(query))
return
mt('search finished')
@@ -863,7 +894,7 @@ class Searcher(threading.Thread):
# TODO do the killing/ stopping here! remember threads might have finished inbetween!
if self.stopped():
- utils.p('stopping search : ' + query['keywords'])
+ utils.p('stopping search : ' + str(query))
return
# this loop handles downloading of small thumbnails
@@ -887,7 +918,7 @@ class Searcher(threading.Thread):
# utils.p('fetched thumbnail ', i)
i += 1
if self.stopped():
- utils.p('stopping search : ' + query['keywords'])
+ utils.p('stopping search : ' + str(query))
return
idx = 0
while len(thumb_sml_download_threads) > 0:
@@ -899,7 +930,7 @@ class Searcher(threading.Thread):
i += 1
if self.stopped():
- utils.p('stopping search : ' + query['keywords'])
+ utils.p('stopping search : ' + str(query))
return
# start downloading full thumbs in the end
@@ -914,13 +945,35 @@ class Searcher(threading.Thread):
def build_query_common(query, props):
- query_common = {
- "keywords": props.search_keywords
- }
- query.update(query_common)
+ '''add shared parameters to query'''
+ query_common = {}
+ if props.search_keywords != '':
+ query_common["query"] = props.search_keywords
+
+ if props.search_verification_status != 'ALL':
+ query_common['verification_status'] = props.search_verification_status.lower()
+
+ if props.search_advanced:
+ if props.search_texture_resolution:
+ query["textureResolutionMax_gte"] = props.search_texture_resolution_min
+ query["textureResolutionMax_lte"] = props.search_texture_resolution_max
+
+ elif props.search_procedural == 'TEXTURE_BASED':
+ # todo this procedural hack should be replaced with the parameter
+ query["textureResolutionMax_gte"] = 0
+ # query["procedural"] = False
+ if props.search_procedural == "PROCEDURAL":
+ #todo this procedural hack should be replaced with the parameter
+ query["files_size_lte"] = 1024 * 1024
+ # query["procedural"] = True
+ elif props.search_file_size:
+ query_common["files_size_gte"] = props.search_file_size_min * 1024 * 1024
+ query_common["files_size_lte"] = props.search_file_size_max * 1024 * 1024
+
+
+ query.update(query_common)
-# def query_add_range(query, name, rmin, rmax):
def build_query_model():
'''use all search input to request results from server'''
@@ -944,14 +997,11 @@ def build_query_model():
if props.search_condition != 'UNSPECIFIED':
query["condition"] = props.search_condition
if props.search_design_year:
- query["designYearMin"] = props.search_design_year_min
- query["designYearMax"] = props.search_design_year_max
+ query["designYear_gte"] = props.search_design_year_min
+ query["designYear_lte"] = props.search_design_year_max
if props.search_polycount:
- query["polyCountMin"] = props.search_polycount_min
- query["polyCountMax"] = props.search_polycount_max
- if props.search_texture_resolution:
- query["textureResolutionMin"] = props.search_texture_resolution_min
- query["textureResolutionMax"] = props.search_texture_resolution_max
+ query["faceCount_gte"] = props.search_polycount_min
+ query["faceCount_lte"] = props.search_polycount_max
build_query_common(query, props)
@@ -988,6 +1038,7 @@ def build_query_material():
query["style"] = props.search_style
else:
query["style"] = props.search_style_other
+
build_query_common(query, props)
return query
@@ -1024,7 +1075,7 @@ def build_query_brush():
query = {
"asset_type": 'brush',
- "brushType": brush_type
+ "mode": brush_type
}
build_query_common(query, props)
@@ -1063,7 +1114,7 @@ def search(category='', get_next=False, author_id=''):
user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
search_start_time = time.time()
- mt('start')
+ #mt('start')
scene = bpy.context.scene
uiprops = scene.blenderkitUI
@@ -1106,6 +1157,12 @@ def search(category='', get_next=False, author_id=''):
if author_id != '':
query['author_id'] = author_id
+ elif props.own_only:
+ # if user searches for [another] author, 'only my assets' is invalid. that's why in elif.
+ profile = bpy.context.window_manager.get('bkit profile')
+ if profile is not None:
+ query['author_id'] = str(profile['user']['id'])
+
# utils.p('searching')
props.is_searching = True
@@ -1127,7 +1184,7 @@ def search(category='', get_next=False, author_id=''):
def search_update(self, context):
utils.p('search updater')
- #if self.search_keywords != '':
+ # if self.search_keywords != '':
ui_props = bpy.context.scene.blenderkitUI
if ui_props.down_up != 'SEARCH':
ui_props.down_up = 'SEARCH'
diff --git a/blenderkit/thumbnails/flp.png b/blenderkit/thumbnails/flp.png
new file mode 100644
index 00000000..7ac3c3d7
--- /dev/null
+++ b/blenderkit/thumbnails/flp.png
Binary files differ
diff --git a/blenderkit/thumbnails/fp.png b/blenderkit/thumbnails/fp.png
new file mode 100644
index 00000000..4e356ab1
--- /dev/null
+++ b/blenderkit/thumbnails/fp.png
Binary files differ
diff --git a/blenderkit/thumbnails/vs_rejected.png b/blenderkit/thumbnails/vs_rejected.png
new file mode 100644
index 00000000..6ff663cf
--- /dev/null
+++ b/blenderkit/thumbnails/vs_rejected.png
Binary files differ
diff --git a/blenderkit/ui.py b/blenderkit/ui.py
index fc9563a9..3f4d0381 100644
--- a/blenderkit/ui.py
+++ b/blenderkit/ui.py
@@ -50,6 +50,11 @@ import os
handler_2d = None
handler_3d = None
+active_area = None
+active_area = None
+active_window = None
+active_region = None
+
reports = []
mappingdict = {
@@ -67,7 +72,7 @@ verification_icons = {
'uploading': 'vs_uploading.png',
'on_hold': 'vs_on_hold.png',
'validated': None,
- 'rejected': None
+ 'rejected': 'vs_rejected.png'
}
@@ -133,7 +138,8 @@ class Report():
pass;
def draw(self, x, y):
- ui_bgl.draw_text(self.text, x, y + 8, 16, self.draw_color)
+ if bpy.context.area == active_area:
+ ui_bgl.draw_text(self.text, x, y + 8, 16, self.draw_color)
def get_asset_under_mouse(mousex, mousey):
@@ -698,6 +704,7 @@ def draw_callback_2d_upload_preview(self, context):
props = utils.get_upload_props()
if props != None and ui_props.draw_tooltip:
+
if ui_props.asset_type != 'BRUSH':
ui_props.thumbnail_image = props.thumbnail
else:
@@ -889,8 +896,12 @@ def draw_callback_2d_search(self, context):
ui_props.mouse_y - linelength, 2, white)
+
def draw_callback_3d(self, context):
''' Draw snapped bbox while dragging and in the future other blenderkit related stuff. '''
+ if not utils.guard_from_crash():
+ return;
+
ui = context.scene.blenderkitUI
if ui.dragging and ui.asset_type == 'MODEL':
@@ -1173,6 +1184,10 @@ def get_largest_3dview():
for r in a.regions:
if r.type == 'WINDOW':
region = r
+ global active_area, active_window, active_region
+ active_window = maxw
+ active_area = maxa
+ active_region = region
return maxw, maxa, region
@@ -1417,6 +1432,8 @@ class AssetBarOperator(bpy.types.Operator):
ui_props.draw_tooltip = True
ui_props.tooltip = asset_data['tooltip']
+ # bpy.ops.wm.call_menu(name='OBJECT_MT_blenderkit_asset_menu')
+
else:
ui_props.draw_tooltip = False
@@ -1476,7 +1493,7 @@ class AssetBarOperator(bpy.types.Operator):
if not asset_data['can_download']:
message = 'Asset locked. Find out how to unlock Everything and ...'
link_text = 'support all BlenderKit artists.'
- url = paths.get_bkit_url() + '/get-blenderkit/' + asset_data['id']
+ url = paths.get_bkit_url() + '/get-blenderkit/' + asset_data['id'] + '/?from_addon'
bpy.ops.wm.blenderkit_url_dialog('INVOKE_REGION_WIN', url=url, message=message,
link_text=link_text)
return {'RUNNING_MODAL'}
@@ -1696,30 +1713,38 @@ class AssetBarOperator(bpy.types.Operator):
if sr is None:
bpy.context.scene['search results'] = []
- if context.area.type == 'VIEW_3D':
- # the arguments we pass the the callback
- args = (self, context)
- self.window = context.window
- self.area = context.area
- self.scene = bpy.context.scene
- self.has_quad_views = len(bpy.context.area.spaces[0].region_quadviews) > 0
+ if context.area.type != 'VIEW_3D':
+ self.report({'WARNING'}, "View3D not found, cannot run operator")
+ return {'CANCELLED'}
- for r in self.area.regions:
- if r.type == 'WINDOW':
- self.region = r
+ # the arguments we pass the the callback
+ args = (self, context)
- update_ui_size(self.area, self.region)
+ self.window = context.window
+ self.area = context.area
+ self.scene = bpy.context.scene
- self._handle_2d = bpy.types.SpaceView3D.draw_handler_add(draw_callback_2d, args, 'WINDOW', 'POST_PIXEL')
- self._handle_3d = bpy.types.SpaceView3D.draw_handler_add(draw_callback_3d, args, 'WINDOW', 'POST_VIEW')
- context.window_manager.modal_handler_add(self)
- ui_props.assetbar_on = True
- return {'RUNNING_MODAL'}
- else:
- self.report({'WARNING'}, "View3D not found, cannot run operator")
- return {'CANCELLED'}
+ self.has_quad_views = len(bpy.context.area.spaces[0].region_quadviews) > 0
+
+ for r in self.area.regions:
+ if r.type == 'WINDOW':
+ self.region = r
+
+ global active_window, active_area, active_region
+ active_window = self.window
+ active_area = self.area
+ active_region = self.region
+
+ update_ui_size(self.area, self.region)
+
+ self._handle_2d = bpy.types.SpaceView3D.draw_handler_add(draw_callback_2d, args, 'WINDOW', 'POST_PIXEL')
+ self._handle_3d = bpy.types.SpaceView3D.draw_handler_add(draw_callback_3d, args, 'WINDOW', 'POST_VIEW')
+
+ context.window_manager.modal_handler_add(self)
+ ui_props.assetbar_on = True
+ return {'RUNNING_MODAL'}
def execute(self, context):
return {'RUNNING_MODAL'}
@@ -1743,6 +1768,27 @@ class TransferBlenderkitData(bpy.types.Operator):
return {'FINISHED'}
+class UndoWithContext(bpy.types.Operator):
+ """Regenerate cobweb"""
+ bl_idname = "wm.undo_push_context"
+ bl_label = "BlnenderKit undo push"
+ bl_description = "BlenderKit undo push with fixed context"
+ bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}
+
+ # def modal(self, context, event):
+ # return {'RUNNING_MODAL'}
+
+ def execute(self, context):
+ C_dict = bpy.context.copy()
+ C_dict.update(region='WINDOW')
+ if context.area is None or context.area.type != 'VIEW_3D':
+ w, a, r = get_largest_3dview()
+ override = {'window': w, 'screen': w.screen, 'area': a, 'region': r}
+ C_dict.update(override)
+ bpy.ops.ed.undo_push(C_dict, 'INVOKE_REGION_WIN')
+ return {'FINISHED'}
+
+
class RunAssetBarWithContext(bpy.types.Operator):
"""Regenerate cobweb"""
bl_idname = "object.run_assetbar_fix_context"
@@ -1761,18 +1807,28 @@ class RunAssetBarWithContext(bpy.types.Operator):
override = {'window': w, 'screen': w.screen, 'area': a, 'region': r}
C_dict.update(override)
bpy.ops.view3d.blenderkit_asset_bar(C_dict, 'INVOKE_REGION_WIN', keep_running=True, do_search=False)
- return {'RUNNING_MODAL'}
+ return {'FINISHED'}
classess = (
AssetBarOperator,
RunAssetBarWithContext,
- TransferBlenderkitData
+ TransferBlenderkitData,
+ UndoWithContext
)
# store keymap items here to access after registration
addon_keymapitems = []
+#@persistent
+def pre_load(context):
+ ui_props = bpy.context.scene.blenderkitUI
+ ui_props.assetbar_on = False
+ ui_props.turn_off = True
+ preferences = bpy.context.preferences.addons['blenderkit'].preferences
+ preferences.login_attempt = False
+
+
def register_ui():
global handler_2d, handler_3d
@@ -1803,6 +1859,7 @@ def register_ui():
def unregister_ui():
global handler_2d, handler_3d
+ pre_load(bpy.context)
bpy.types.SpaceView3D.draw_handler_remove(handler_2d, 'WINDOW')
bpy.types.SpaceView3D.draw_handler_remove(handler_3d, 'WINDOW')
diff --git a/blenderkit/ui_panels.py b/blenderkit/ui_panels.py
index b981fbcc..8bd9fd58 100644
--- a/blenderkit/ui_panels.py
+++ b/blenderkit/ui_panels.py
@@ -24,8 +24,9 @@ if "bpy" in locals():
utils = importlib.reload(utils)
download = importlib.reload(download)
categories = importlib.reload(categories)
+ icons = importlib.reload(icons)
else:
- from blenderkit import paths, ratings, utils, download, categories
+ from blenderkit import paths, ratings, utils, download, categories, icons
from bpy.types import (
Panel
@@ -43,7 +44,7 @@ def label_multiline(layout, text='', icon='NONE', width=-1):
threshold = int(width / 5.5)
else:
threshold = 35
- maxlines = 6
+ maxlines = 8
li = 0
for l in lines:
while len(l) > threshold:
@@ -53,7 +54,7 @@ def label_multiline(layout, text='', icon='NONE', width=-1):
l1 = l[:i]
layout.label(text=l1, icon=icon)
icon = 'NONE'
- l = l[i:]
+ l = l[i:].lstrip()
li += 1
if li > maxlines:
break;
@@ -75,10 +76,10 @@ def draw_ratings(layout, context):
layout.prop(bkit_ratings, 'rating_work_hours')
w = context.region.width
- layout.label(text='problems')
- layout.prop(bkit_ratings, 'rating_problems', text='')
- layout.label(text='compliments')
- layout.prop(bkit_ratings, 'rating_compliments', text='')
+ # layout.label(text='problems')
+ # layout.prop(bkit_ratings, 'rating_problems', text='')
+ # layout.label(text='compliments')
+ # layout.prop(bkit_ratings, 'rating_compliments', text='')
row = layout.row()
op = row.operator("object.blenderkit_rating_upload", text="Send rating", icon='URL')
@@ -151,7 +152,7 @@ def prop_needed(layout, props, name, value, is_not_filled=''):
# row.label(text='', icon = 'ERROR')
icon = 'ERROR'
row.alert = True
- row.prop(props, name)#, icon=icon)
+ row.prop(props, name) # , icon=icon)
row.alert = False
else:
# row.label(text='', icon = 'FILE_TICK')
@@ -264,6 +265,7 @@ def draw_panel_scene_upload(self, context):
row.prop(props, 'work_hours')
layout.prop(props, 'adult')
+
def draw_assetbar_show_hide(layout, props):
s = bpy.context.scene
ui_props = s.blenderkitUI
@@ -299,7 +301,9 @@ def draw_panel_model_search(self, context):
layout.operator("wm.url_open", text="Get Full plan", icon='URL').url = paths.BLENDERKIT_PLANS
layout.prop(props, "search_style")
+ layout.prop(props, "own_only")
layout.prop(props, "free_only")
+
# if props.search_style == 'OTHER':
# layout.prop(props, "search_style_other")
# layout.prop(props, "search_engine")
@@ -307,7 +311,7 @@ def draw_panel_model_search(self, context):
# layout.prop(props, 'append_link', expand=True, icon_only=False)
# layout.prop(props, 'import_as', expand=True, icon_only=False)
- # layout.prop(props, "search_advanced")
+ layout.prop(props, "search_advanced")
if props.search_advanced:
layout.separator()
@@ -318,34 +322,39 @@ def draw_panel_model_search(self, context):
# layout.prop(props, "search_engine_keyword")
# AGE
- layout.prop(props, "search_condition") # , text ='condition of object new/old e.t.c.')
+ layout.prop(props, "search_condition", text='Condition') # , text ='condition of object new/old e.t.c.')
# DESIGN YEAR
layout.prop(props, "search_design_year", text='designed in ( min - max )')
- row = layout.row(align=True)
- if not props.search_design_year_min:
- row.active = False
- row.prop(props, "search_design_year_min", text='min')
- row.prop(props, "search_design_year_max", text='max')
+ if props.search_design_year:
+ row = layout.row(align=True)
+ row.prop(props, "search_design_year_min", text='min')
+ row.prop(props, "search_design_year_max", text='max')
# POLYCOUNT
- layout.prop(props, "search_polycount", text='polycount in ( min - max )')
- row = layout.row(align=True)
- if not props.search_polycount:
- row.active = False
- row.prop(props, "search_polycount_min", text='min')
- row.prop(props, "search_polycount_max", text='max')
+ layout.prop(props, "search_polycount", text='Poly count in ( min - max )')
+ 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 resolution ( min - max )')
- row = layout.row(align=True)
- if not props.search_texture_resolution:
- row.active = False
- row.prop(props, "search_texture_resolution_min", text='min')
- row.prop(props, "search_texture_resolution_max", text='max')
-
+ 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 ( min - max )')
+ if props.search_file_size:
+ row = layout.row(align=True)
+ row.prop(props, "search_file_size_min", text='min')
+ row.prop(props, "search_file_size_max", text='max')
+
+ # layout.prop(props, "search_procedural", expand=True)
# ADULT
- layout.prop(props, "search_adult") # , text ='condition of object new/old e.t.c.')
+ # layout.prop(props, "search_adult") # , text ='condition of object new/old e.t.c.')
draw_panel_categories(self, context)
@@ -366,7 +375,7 @@ def draw_panel_scene_search(self, context):
row = layout.row()
row.prop(props, "search_keywords", text="", icon='VIEWZOOM')
draw_assetbar_show_hide(row, props)
-
+ layout.prop(props, "own_only")
label_multiline(layout, text=props.report)
# layout.prop(props, "search_style")
@@ -401,6 +410,8 @@ class VIEW3D_PT_blenderkit_model_properties(Panel):
if o.instance_type == 'COLLECTION' and o.instance_collection is not None:
layout.operator('object.blenderkit_bring_to_scene', text='Bring to scene')
+ draw_panel_model_rating(self, context)
+
# if 'rig' in ad['tags']:
# # layout.label(text = 'can make proxy')
# layout.operator('object.blenderkit_make_proxy', text = 'Make Armature proxy')
@@ -424,7 +435,7 @@ class VIEW3D_PT_blenderkit_profile(Panel):
bl_idname = "VIEW3D_PT_blenderkit_profile"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
- bl_label = "Profile"
+ bl_label = "BlenderKit Profile"
@classmethod
def poll(cls, context):
@@ -440,26 +451,59 @@ class VIEW3D_PT_blenderkit_profile(Panel):
draw_login_progress(layout)
return
- if user_preferences.enable_oauth:
- draw_login_buttons(layout)
-
if user_preferences.api_key != '':
me = bpy.context.window_manager.get('bkit profile')
if me is not None:
me = me['user']
- layout.label(text='User: %s %s' % (me['firstName'], me['lastName']))
- layout.label(text='Email: %s' % (me['email']))
- if me.get('sumAssetFilesSize') is not None: # TODO remove this when production server has these too.
- layout.label(text='Public assets: %i MiB' % (me['sumAssetFilesSize']))
- if me.get('sumPrivateAssetFilesSize') is not None:
- layout.label(text='Private assets: %i MiB' % (me['sumPrivateAssetFilesSize']))
+ # user name
+ layout.label(text='Me: %s %s' % (me['firstName'], me['lastName']))
+ # layout.label(text='Email: %s' % (me['email']))
+
+ # plan information
+
+ # pcoll = icons.icon_collections["main"]
+ # my_icon = pcoll['free']
+ # row = layout.row()
+ # row.label(text='My plan:')
+ # row.label(text='Free plan', icon_value=my_icon.icon_id)
+ # layout.operator("wm.url_open", text="Change plan",
+ # icon='URL').url = paths.get_bkit_url() + paths.BLENDERKIT_PLANS
+
+ # storage statistics
+ # if me.get('sumAssetFilesSize') is not None: # TODO remove this when production server has these too.
+ # layout.label(text='My public assets: %i MiB' % (me['sumAssetFilesSize']))
+ # if me.get('sumPrivateAssetFilesSize') is not None:
+ # layout.label(text='My private assets: %i MiB' % (me['sumPrivateAssetFilesSize']))
if me.get('remainingPrivateQuota') is not None:
- layout.label(text='Remaining private storage: %i MiB' % (me['remainingPrivateQuota']))
+ layout.label(text='My free storage: %i MiB' % (me['remainingPrivateQuota']))
layout.operator("wm.url_open", text="See my uploads",
icon='URL').url = paths.get_bkit_url() + paths.BLENDERKIT_USER_ASSETS
+class VIEW3D_PT_blenderkit_login(Panel):
+ bl_category = "BlenderKit"
+ bl_idname = "VIEW3D_PT_blenderkit_login"
+ bl_space_type = 'VIEW_3D'
+ bl_region_type = 'UI'
+ bl_label = "BlenderKit Login"
+
+ @classmethod
+ def poll(cls, context):
+ return True
+
+ def draw(self, context):
+ layout = self.layout
+ user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
+
+ if user_preferences.login_attempt:
+ draw_login_progress(layout)
+ return
+
+ if user_preferences.enable_oauth:
+ draw_login_buttons(layout)
+
+
def draw_panel_model_rating(self, context):
o = bpy.context.active_object
op = draw_ratings(self.layout, context) # , props)
@@ -526,7 +570,7 @@ def draw_panel_material_search(self, context):
row = layout.row()
row.prop(props, "search_keywords", text="", icon='VIEWZOOM')
draw_assetbar_show_hide(row, props)
-
+ layout.prop(props, "own_only")
label_multiline(layout, text=props.report)
# layout.prop(props, 'search_style')
@@ -536,10 +580,33 @@ def draw_panel_material_search(self, context):
# if props.search_engine == 'OTHER':
# layout.prop(props, 'search_engine_other')
- layout.prop(props, 'automap')
+ layout.prop(props, "search_advanced")
+ if props.search_advanced:
+ layout.separator()
+
+ layout.label(text = 'texture types')
+ 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 ( min - max )')
+ 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 ( min - max in 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')
draw_panel_categories(self, context)
+ layout.prop(props, 'automap')
+
def draw_panel_material_ratings(self, context):
op = draw_ratings(self.layout, context) # , props)
@@ -568,7 +635,7 @@ def draw_panel_brush_search(self, context):
row = layout.row()
row.prop(props, "search_keywords", text="", icon='VIEWZOOM')
draw_assetbar_show_hide(row, props)
-
+ layout.prop(props, "own_only")
label_multiline(layout, text=props.report)
draw_panel_categories(self, context)
@@ -605,7 +672,7 @@ class VIEW3D_PT_blenderkit_unified(Panel):
bl_idname = "VIEW3D_PT_blenderkit_unified"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
- bl_label = "BlenderKit"
+ bl_label = "Find and Upload Assets"
@classmethod
def poll(cls, context):
@@ -622,19 +689,19 @@ class VIEW3D_PT_blenderkit_unified(Panel):
# layout.prop_tabs_enum(ui_props, "asset_type", icon_only = True)
row = layout.row()
- #row.scale_x = 1.6
- #row.scale_y = 1.6
+ # row.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)
- #split = row.split(factor=.5)
- #row.prop(ui_props, 'asset_type', expand=True, icon_only=True)
- #row = layout.column(align = False)
- layout.prop(ui_props, 'asset_type', expand=False, text = '')
+ # row = row.split().row()
+ # layout.alert = True
+ # layout.alignment = 'CENTER'
+ # row = layout.row(align = True)
+ # split = row.split(factor=.5)
+ # row.prop(ui_props, 'asset_type', expand=True, icon_only=True)
+ # row = layout.column(align = False)
+ layout.prop(ui_props, 'asset_type', expand=False, text='')
w = context.region.width
if user_preferences.login_attempt:
@@ -651,14 +718,16 @@ class VIEW3D_PT_blenderkit_unified(Panel):
layout.label(text='Paste your API Key:')
layout.prop(user_preferences, 'api_key', text='')
layout.separator()
- if bpy.data.filepath == '':
- layout.alert = True
- label_multiline(layout, text="It's better to save your file first.", width=w)
- layout.alert = False
- layout.separator()
+ # if bpy.data.filepath == '':
+ # layout.alert = True
+ # label_multiline(layout, text="It's better to save your file first.", width=w)
+ # layout.alert = False
+ # layout.separator()
if ui_props.down_up == 'SEARCH':
-
+ if utils.profile_is_validator():
+ search_props = utils.get_search_props()
+ layout.prop(search_props, 'search_verification_status')
if ui_props.asset_type == 'MODEL':
# noinspection PyCallByClass
draw_panel_model_search(self, context)
@@ -744,7 +813,6 @@ class VIEW3D_PT_blenderkit_unified(Panel):
layout.label(text='not yet implemented')
-
class OBJECT_MT_blenderkit_asset_menu(bpy.types.Menu):
bl_label = "Asset options:"
bl_idname = "OBJECT_MT_blenderkit_asset_menu"
@@ -788,9 +856,13 @@ class OBJECT_MT_blenderkit_asset_menu(bpy.types.Menu):
wm = bpy.context.window_manager
profile = wm.get('bkit profile')
if profile is not None:
- # validation by admin
- if profile['user']['exmenu']:
+ # validation
+ if utils.profile_is_validator():
layout.label(text='Validation tools:')
+ if asset_data['verificationStatus'] != 'uploaded':
+ op = layout.operator('object.blenderkit_change_status', text='set Uploaded')
+ op.asset_id = asset_data['id']
+ op.state = 'uploaded'
if asset_data['verificationStatus'] != 'validated':
op = layout.operator('object.blenderkit_change_status', text='Validate')
op.asset_id = asset_data['id']
@@ -881,7 +953,7 @@ class UrlPopupDialog(bpy.types.Operator):
op.url = self.url
def execute(self, context):
- #start_thumbnailer(self, context)
+ # start_thumbnailer(self, context)
return {'FINISHED'}
def invoke(self, context, event):
@@ -976,20 +1048,28 @@ class VIEW3D_PT_blenderkit_downloads(Panel):
def header_search_draw(self, context):
'''Top bar menu in 3d view'''
- layout = self.layout
- s = bpy.context.scene
- ui_props = s.blenderkitUI
- if ui_props.asset_type == 'MODEL':
- props = s.blenderkit_models
- if ui_props.asset_type == 'MATERIAL':
- props = s.blenderkit_mat
- if ui_props.asset_type == 'BRUSH':
- props = s.blenderkit_brush
- layout.separator_spacer()
- layout.prop(ui_props, "asset_type", text='', icon='URL')
- layout.prop(props, "search_keywords", text="", icon='VIEWZOOM')
- draw_assetbar_show_hide(layout, props)
+ if not utils.guard_from_crash():
+ return;
+
+ preferences = bpy.context.preferences.addons['blenderkit'].preferences
+ if preferences.search_in_header:
+ layout = self.layout
+ s = bpy.context.scene
+ ui_props = s.blenderkitUI
+ if ui_props.asset_type == 'MODEL':
+ props = s.blenderkit_models
+ if ui_props.asset_type == 'MATERIAL':
+ props = s.blenderkit_mat
+ if ui_props.asset_type == 'BRUSH':
+ props = s.blenderkit_brush
+
+ # the center snap menu is in edit and object mode if tool settings are off.
+ if context.space_data.show_region_tool_header == True or context.mode[:4] not in ('EDIT', 'OBJE'):
+ layout.separator_spacer()
+ layout.prop(ui_props, "asset_type", text='', icon='URL')
+ layout.prop(props, "search_keywords", text="", icon='VIEWZOOM')
+ draw_assetbar_show_hide(layout, props)
# We can store multiple preview collections here,
@@ -997,11 +1077,11 @@ def header_search_draw(self, context):
preview_collections = {}
classess = (
SetCategoryOperator,
-
+ VIEW3D_PT_blenderkit_profile,
+ VIEW3D_PT_blenderkit_login,
VIEW3D_PT_blenderkit_unified,
VIEW3D_PT_blenderkit_model_properties,
VIEW3D_PT_blenderkit_downloads,
- VIEW3D_PT_blenderkit_profile,
OBJECT_MT_blenderkit_asset_menu,
UrlPopupDialog
)
@@ -1015,4 +1095,6 @@ def register_ui_panels():
def unregister_ui_panels():
for c in classess:
+ print('unregister', c)
bpy.utils.unregister_class(c)
+ bpy.types.VIEW3D_MT_editor_menus.remove(header_search_draw)
diff --git a/blenderkit/upload.py b/blenderkit/upload.py
index b1c4b25c..f2f295e5 100644
--- a/blenderkit/upload.py
+++ b/blenderkit/upload.py
@@ -81,28 +81,6 @@ def add_version(data):
data["addonVersion"] = addon_version
-def params_to_dict(inputs, parameters=None):
- if parameters == None:
- parameters = []
- for k in inputs.keys():
- if type(inputs[k]) == list:
- strlist = ""
- for idx, s in enumerate(inputs[k]):
- strlist += s
- if idx < len(inputs[k]) - 1:
- strlist += ','
-
- value = "%s" % strlist
- elif type(inputs[k]) != bool:
- value = inputs[k]
- else:
- value = str(inputs[k])
- parameters.append(
- {
- "parameterType": k,
- "value": value
- })
- return parameters
def write_to_report(props, text):
@@ -254,6 +232,10 @@ def get_upload_data(self, context, asset_type):
"manifold": props.manifold,
"objectCount": props.object_count,
+ "procedural": props.is_procedural,
+ "nodeCount": props.node_count,
+ "textureCount": props.texture_count,
+ "megapixels": round(props.total_megapixels/ 1000000),
# "scene": props.is_scene,
}
if props.use_design_year:
@@ -381,6 +363,7 @@ def get_upload_data(self, context, asset_type):
"procedural": props.is_procedural,
"nodeCount": props.node_count,
"textureCount": props.texture_count,
+ "megapixels": round(props.total_megapixels/ 1000000),
}
@@ -570,7 +553,7 @@ def start_upload(self, context, asset_type, reupload, upload_set):
export_data, upload_data, eval_path_computing, eval_path_state, eval_path, props = get_upload_data(self, context,
asset_type)
# utils.pprint(upload_data)
- upload_data['parameters'] = params_to_dict(
+ upload_data['parameters'] = utils.dict_to_params(
upload_data['parameters']) # weird array conversion only for upload, not for tooltips.
binary_path = bpy.app.binary_path
@@ -783,7 +766,10 @@ class UploadOperator(Operator):
if props.is_private == 'PUBLIC':
ui_panels.label_multiline(layout, text='public assets are validated several hours'
- ' or days after upload. ', width=300)
+ ' or days after upload. Remember always to '
+ 'test download your asset to a clean file'
+ ' to see if it uploaded correctly.'
+ , width=300)
def invoke(self, context, event):
props = utils.get_upload_props()
diff --git a/blenderkit/upload_bg.py b/blenderkit/upload_bg.py
index b2db44da..236793c0 100644
--- a/blenderkit/upload_bg.py
+++ b/blenderkit/upload_bg.py
@@ -87,8 +87,8 @@ def upload_file(upload_data, f):
upload_create_url = paths.get_api_url() + 'uploads/'
upload = rerequests.post(upload_create_url, json=upload_info, headers=headers, verify=True)
upload = upload.json()
-
- chunk_size = 1024 * 256
+ #
+ chunk_size = 1024 * 1024 * 2
utils.pprint(upload)
# file gets uploaded here:
uploaded = False
@@ -103,8 +103,10 @@ def upload_file(upload_data, f):
if upload_response.status_code == 200:
uploaded = True
else:
+ print(upload_response.text)
bg_blender.progress(f'Upload failed, retry. {a}')
except Exception as e:
+ print(e)
bg_blender.progress('Upload %s failed, retrying' % f['type'])
time.sleep(1)
diff --git a/blenderkit/utils.py b/blenderkit/utils.py
index c1a59cd8..706600ee 100644
--- a/blenderkit/utils.py
+++ b/blenderkit/utils.py
@@ -514,4 +514,48 @@ def name_update():
fname = fname.replace('\'', '')
fname = fname.replace('\"', '')
asset = get_active_asset()
- asset.name = fname \ No newline at end of file
+ asset.name = fname
+
+def params_to_dict(params):
+ params_dict = {}
+ for p in params:
+ params_dict[p['parameterType']] = p['value']
+ return params_dict
+
+def dict_to_params(inputs, parameters=None):
+ if parameters == None:
+ parameters = []
+ for k in inputs.keys():
+ if type(inputs[k]) == list:
+ strlist = ""
+ for idx, s in enumerate(inputs[k]):
+ strlist += s
+ if idx < len(inputs[k]) - 1:
+ strlist += ','
+
+ value = "%s" % strlist
+ elif type(inputs[k]) != bool:
+ value = inputs[k]
+ else:
+ value = str(inputs[k])
+ parameters.append(
+ {
+ "parameterType": k,
+ "value": value
+ })
+ return parameters
+
+
+def profile_is_validator():
+ a = bpy.context.window_manager.get('bkit profile')
+ if a is not None and a['user'].get('exmenu'):
+ return True
+ return False
+
+def guard_from_crash():
+ '''Blender tends to crash when trying to run some functions with the addon going through unregistration process.'''
+ if bpy.context.preferences.addons['blenderkit'] is None:
+ return False;
+ if bpy.context.preferences.addons['blenderkit'].preferences is None:
+ return False;
+ return True \ No newline at end of file