Merge branch 'master' into temp-xr-actions-D9124

This commit is contained in:
Peter Kim 2021-05-14 21:37:31 +09:00
commit 67ebde367a
31 changed files with 1037 additions and 859 deletions

View File

@ -49,6 +49,7 @@ if "bpy" in locals():
overrides = reload(overrides)
paths = reload(paths)
ratings = reload(ratings)
ratings_utils = reload(ratings_utils)
resolutions = reload(resolutions)
search = reload(search)
tasks_queue = reload(tasks_queue)
@ -84,6 +85,7 @@ else:
from blenderkit import overrides
from blenderkit import paths
from blenderkit import ratings
from blenderkit import ratings_utils
from blenderkit import resolutions
from blenderkit import search
from blenderkit import tasks_queue
@ -678,8 +680,8 @@ class BlenderKitCommonUploadProps(object):
is_free: EnumProperty(
name="Thumbnail Style",
items=(
('FREE', 'Free', "You consent you want to release this asset as free for everyone."),
('FULL', 'Full', "Your asset will be only available for subscribers")
('FULL', 'Full', "Your asset will be only available for subscribers"),
('FREE', 'Free', "You consent you want to release this asset as free for everyone.")
),
description="Assets can be in Free or in Full plan. Also free assets generate credits.",
default="FULL",
@ -731,20 +733,20 @@ class BlenderKitRatingProps(PropertyGroup):
description="quality of the material",
default=0,
min=-1, max=10,
update=ratings.update_ratings_quality)
update=ratings_utils.update_ratings_quality)
# the following enum is only to ease interaction - enums support 'drag over' and enable to draw the stars easily.
rating_quality_ui: EnumProperty(name='rating_quality_ui',
items=ratings.stars_enum_callback,
items=ratings_utils.stars_enum_callback,
description='Rating stars 0 - 10',
default=None,
update=ratings.update_quality_ui,
update=ratings_utils.update_quality_ui,
)
rating_work_hours: FloatProperty(name="Work Hours",
description="How many hours did this work take?",
default=0.00,
min=0.0, max=150, update=ratings.update_ratings_work_hours
min=0.0, max=150, update=ratings_utils.update_ratings_work_hours
)
# rating_complexity: IntProperty(name="Complexity",
@ -849,8 +851,8 @@ class BlenderKitMaterialUploadProps(PropertyGroup, BlenderKitCommonUploadProps):
is_free: EnumProperty(
name="Thumbnail Style",
items=(
('FREE', 'Free', "You consent you want to release this asset as free for everyone."),
('FULL', 'Full', "Your asset will be only available for subscribers.")
('FULL', 'Full', "Your asset will be only available for subscribers."),
('FREE', 'Free', "You consent you want to release this asset as free for everyone.")
),
description="Assets can be in Free or in Full plan. Also free assets generate credits. \n"
"All BlenderKit materials are free.",

View File

@ -225,9 +225,9 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
self.asset_name = name_label
self.tooltip_widgets.append(name_label)
offset_y = 16 + self.margin
label = self.new_text('Left click or drag to append/link. Right click for more options.', self.assetbar_margin*2, labels_start + offset_y,
text_size=14)
self.tooltip_widgets.append(label)
# label = self.new_text('Left click or drag to append/link. Right click for more options.', self.assetbar_margin*2, labels_start + offset_y,
# text_size=14)
# self.tooltip_widgets.append(label)
self.hide_tooltip()
@ -505,6 +505,9 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
# handlers
def enter_button(self, widget):
# context.window.cursor_warp(event.mouse_x, event.mouse_y - 20);
self.show_tooltip()
if self.active_index != widget.search_index:
@ -532,6 +535,8 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
self.tooltip_panel.update(tooltip_x, widget.y_screen + widget.height)
self.tooltip_panel.layout_widgets()
# bpy.ops.wm.blenderkit_asset_popup('INVOKE_DEFAULT')
def exit_button(self, widget):
# this condition checks if there wasn't another button already entered, which can happen with small button gaps

View File

@ -156,7 +156,7 @@ def start_thumbnailer(self=None, json_args=None, props=None, wait=False, add_bg_
eval_path_state = "bpy.data.objects['%s'].blenderkit.thumbnail_generating_state" % json_args['asset_name']
eval_path = "bpy.data.objects['%s']" % json_args['asset_name']
bg_blender.add_bg_process(eval_path_computing=eval_path_computing, eval_path_state=eval_path_state,
bg_blender.add_bg_process(name = f"{json_args['asset_name']} thumbnailer" ,eval_path_computing=eval_path_computing, eval_path_state=eval_path_state,
eval_path=eval_path, process_type='THUMBNAILER', process=proc)
@ -206,7 +206,7 @@ def start_material_thumbnailer(self=None, json_args=None, props=None, wait=False
eval_path_state = "bpy.data.materials['%s'].blenderkit.thumbnail_generating_state" % json_args['asset_name']
eval_path = "bpy.data.materials['%s']" % json_args['asset_name']
bg_blender.add_bg_process(name=json_args['asset_name'], eval_path_computing=eval_path_computing,
bg_blender.add_bg_process(name=f"{json_args['asset_name']} thumbnailer", eval_path_computing=eval_path_computing,
eval_path_state=eval_path_state,
eval_path=eval_path, process_type='THUMBNAILER', process=proc)
if props:
@ -328,7 +328,10 @@ class GenerateThumbnailOperator(bpy.types.Operator):
class ReGenerateThumbnailOperator(bpy.types.Operator):
"""Generate Cycles thumbnail for model assets"""
"""
Generate default thumbnail with Cycles renderer and upload it.
Works also for assets from search results, without being downloaded before.
"""
bl_idname = "object.blenderkit_regenerate_thumbnail"
bl_label = "BlenderKit Thumbnail Re-generate"
bl_options = {'REGISTER', 'INTERNAL'}
@ -371,11 +374,9 @@ class ReGenerateThumbnailOperator(bpy.types.Operator):
return True # bpy.context.view_layer.objects.active is not None
def draw(self, context):
ob = bpy.context.active_object
while ob.parent is not None:
ob = ob.parent
props = self
layout = self.layout
# layout.label('This will re-generate thumbnail and directly upload it to server. You should see your updated thumbnail online depending ')
layout.label(text='thumbnailer settings')
layout.prop(props, 'thumbnail_background_lightness')
layout.prop(props, 'thumbnail_angle')
@ -521,8 +522,8 @@ class GenerateMaterialThumbnailOperator(bpy.types.Operator):
class ReGenerateMaterialThumbnailOperator(bpy.types.Operator):
"""
Generate default thumbnail with Cycles renderer.
Works also for assets from search results, without being downloaded before.
Generate default thumbnail with Cycles renderer and upload it.
Works also for assets from search results, without being downloaded before
"""
bl_idname = "object.blenderkit_regenerate_material_thumbnail"
bl_label = "BlenderKit Material Thumbnail Re-Generator"

View File

@ -20,7 +20,7 @@
from blenderkit import utils, append_link, bg_blender, upload_bg, download
import sys, json, math
import sys, json, math, os
import bpy
from pathlib import Path
@ -48,6 +48,10 @@ if __name__ == "__main__":
data = json.load(s)
# append_material(file_name, matname = None, link = False, fake_user = True)
if data.get('do_download'):
#need to save the file, so that asset doesn't get downloaded into addon directory
temp_blend_path = os.path.join(data['tempdir'], 'temp.blend')
bpy.ops.wm.save_as_mainfile(filepath=temp_blend_path)
asset_data = data['asset_data']
has_url = download.get_download_url(asset_data, download.get_scene_id(), user_preferences.api_key, tcom=None,
resolution='blend')
@ -80,8 +84,6 @@ if __name__ == "__main__":
tscale = data["thumbnail_scale"]
bpy.context.view_layer.objects['scaler'].scale = (tscale, tscale, tscale)
bpy.context.view_layer.update()
print('we have this materialB')
print(mat)
for ob in bpy.context.visible_objects:
if ob.name[:15] == 'MaterialPreview':
@ -99,7 +101,6 @@ if __name__ == "__main__":
if data["thumbnail_type"] in ['BALL', 'BALL_COMPLEX', 'CLOTH']:
utils.automap(ob.name, tex_size = ts / tscale, just_scale = True, bg_exception=True)
bpy.context.view_layer.update()
print('got to C')
s.cycles.volume_step_size = tscale * .1

View File

@ -18,10 +18,9 @@
from blenderkit import utils, append_link, bg_blender, download, upload_bg
from blenderkit import utils, append_link, bg_blender, download, upload_bg, upload
import sys, json, math
from pathlib import Path
import sys, json, math, os
import bpy
import mathutils
@ -78,7 +77,6 @@ def render_thumbnails():
if __name__ == "__main__":
try:
print( 'got to A')
with open(BLENDERKIT_EXPORT_DATA, 'r',encoding='utf-8') as s:
data = json.load(s)
@ -86,8 +84,11 @@ if __name__ == "__main__":
if data.get('do_download'):
bg_blender.progress('Downloading asset')
#need to save the file, so that asset doesn't get downloaded into addon directory
temp_blend_path = os.path.join(data['tempdir'], 'temp.blend')
bpy.ops.wm.save_as_mainfile(filepath = temp_blend_path)
bg_blender.progress('Downloading asset')
asset_data = data['asset_data']
has_url = download.get_download_url(asset_data, download.get_scene_id(), user_preferences.api_key, tcom=None,
resolution='blend')
@ -135,7 +136,6 @@ if __name__ == "__main__":
}
s = bpy.context.scene
s.frame_set(fdict[data['thumbnail_angle']])
print( 'got to C')
snapdict = {
'GROUND': 'Ground',
@ -155,7 +155,6 @@ if __name__ == "__main__":
s.cycles.samples = data['thumbnail_samples']
bpy.context.view_layer.cycles.use_denoising = data['thumbnail_denoising']
bpy.context.view_layer.update()
print( 'got to D')
# import blender's HDR here
# hdr_path = Path('datafiles/studiolights/world/interior.exr')
@ -179,9 +178,10 @@ if __name__ == "__main__":
render_thumbnails()
fpath = data['thumbnail_path'] + '.jpg'
if data.get('upload_after_render') and data.get('asset_data'):
# try to patch for the sake of older assets where thumbnail update doesn't work for the reasont
# that original thumbnail files aren't available.
# upload.patch_individual_metadata(data['asset_data']['id'], {}, user_preferences)
bg_blender.progress('uploading thumbnail')
preferences = bpy.context.preferences.addons['blenderkit'].preferences
print('uploading A')
file = {
"type": "thumbnail",
"index": 0,
@ -189,20 +189,14 @@ if __name__ == "__main__":
}
upload_data = {
"name": data['asset_data']['name'],
"token": preferences.api_key,
"token": user_preferences.api_key,
"id": data['asset_data']['id']
}
print('uploading B')
upload_bg.upload_file(upload_data, file)
print('uploading C')
bg_blender.progress('background autothumbnailer finished successfully')
print( 'got to E')
except:
import traceback

View File

@ -27,9 +27,24 @@ icon_collections = {}
icons_read = {
'fp.png': 'free',
'flp.png': 'full',
'test.jpg': 'test',
'trophy.png': 'trophy',
'dumbbell.png': 'dumbbell',
'cc0.png': 'cc0',
'royalty_free.png': 'royalty_free',
}
verification_icons = {
'vs_ready.png':'ready',
'vs_deleted.png':'deleted' ,
'vs_uploaded.png': 'uploaded',
'vs_uploading.png': 'uploading',
'vs_on_hold.png': 'on_hold',
'vs_validated.png': 'validated',
'vs_rejected.png': 'rejected'
}
icons_read.update(verification_icons)
def register_icons():
# Note that preview collections returned by bpy.utils.previews

View File

@ -16,7 +16,7 @@
#
# ##### END GPL LICENSE BLOCK #####
from blenderkit import paths, utils, rerequests, tasks_queue
from blenderkit import paths, utils, rerequests, tasks_queue, ratings_utils
import bpy
import requests, threading
@ -50,36 +50,6 @@ def pretty_print_POST(req):
))
def upload_rating_thread(url, ratings, headers):
''' Upload rating thread function / disconnected from blender data.'''
bk_logger.debug('upload rating ' + url + str(ratings))
for rating_name, score in ratings:
if (score != -1 and score != 0):
rating_url = url + rating_name + '/'
data = {
"score": score, # todo this kind of mixing is too much. Should have 2 bkit structures, upload, use
}
try:
r = rerequests.put(rating_url, data=data, verify=True, headers=headers)
except requests.exceptions.RequestException as e:
print('ratings upload failed: %s' % str(e))
def send_rating_to_thread_quality(url, ratings, headers):
'''Sens rating into thread rating, main purpose is for tasks_queue.
One function per property to avoid lost data due to stashing.'''
thread = threading.Thread(target=upload_rating_thread, args=(url, ratings, headers))
thread.start()
def send_rating_to_thread_work_hours(url, ratings, headers):
'''Sens rating into thread rating, main purpose is for tasks_queue.
One function per property to avoid lost data due to stashing.'''
thread = threading.Thread(target=upload_rating_thread, args=(url, ratings, headers))
thread.start()
def upload_review_thread(url, reviews, headers):
r = rerequests.put(url, data=reviews, verify=True, headers=headers)
@ -103,43 +73,6 @@ def get_rating(asset_id):
print(r.text)
def update_ratings_quality(self, context):
user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
api_key = user_preferences.api_key
headers = utils.get_headers(api_key)
asset = self.id_data
if asset:
bkit_ratings = asset.bkit_ratings
url = paths.get_api_url() + 'assets/' + asset['asset_data']['id'] + '/rating/'
else:
# this part is for operator rating:
bkit_ratings = self
url = paths.get_api_url() + f'assets/{self.asset_id}/rating/'
if bkit_ratings.rating_quality > 0.1:
ratings = [('quality', bkit_ratings.rating_quality)]
tasks_queue.add_task((send_rating_to_thread_quality, (url, ratings, headers)), wait=2.5, only_last=True)
def update_ratings_work_hours(self, context):
user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
api_key = user_preferences.api_key
headers = utils.get_headers(api_key)
asset = self.id_data
if asset:
bkit_ratings = asset.bkit_ratings
url = paths.get_api_url() + 'assets/' + asset['asset_data']['id'] + '/rating/'
else:
# this part is for operator rating:
bkit_ratings = self
url = paths.get_api_url() + f'assets/{self.asset_id}/rating/'
if bkit_ratings.rating_work_hours > 0.45:
ratings = [('working_hours', round(bkit_ratings.rating_work_hours, 1))]
tasks_queue.add_task((send_rating_to_thread_work_hours, (url, ratings, headers)), wait=2.5, only_last=True)
def upload_rating(asset):
user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
api_key = user_preferences.api_key
@ -155,12 +88,12 @@ def upload_rating(asset):
if bkit_ratings.rating_quality > 0.1:
ratings = (('quality', bkit_ratings.rating_quality),)
tasks_queue.add_task((send_rating_to_thread_quality, (url, ratings, headers)), wait=2.5, only_last=True)
tasks_queue.add_task((ratings_utils.send_rating_to_thread_quality, (url, ratings, headers)), wait=2.5, only_last=True)
if bkit_ratings.rating_work_hours > 0.1:
ratings = (('working_hours', round(bkit_ratings.rating_work_hours, 1)),)
tasks_queue.add_task((send_rating_to_thread_work_hours, (url, ratings, headers)), wait=2.5, only_last=True)
tasks_queue.add_task((ratings_utils.send_rating_to_thread_work_hours, (url, ratings, headers)), wait=2.5, only_last=True)
thread = threading.Thread(target=upload_rating_thread, args=(url, ratings, headers))
thread = threading.Thread(target=ratings_utils.upload_rating_thread, args=(url, ratings, headers))
thread.start()
url = paths.get_api_url() + 'assets/' + asset['asset_data']['id'] + '/review'
@ -242,185 +175,47 @@ class UploadRatingOperator(bpy.types.Operator):
return wm.invoke_props_dialog(self)
def stars_enum_callback(self, context):
'''regenerates the enum property used to display rating stars, so that there are filled/empty stars correctly.'''
items = []
for a in range(0, 10):
if self.rating_quality < a + 1:
icon = 'SOLO_OFF'
else:
icon = 'SOLO_ON'
# has to have something before the number in the value, otherwise fails on registration.
items.append((f'{a + 1}', f'{a + 1}', '', icon, a + 1))
return items
def draw_ratings_menu(self, context, layout):
col = layout.column()
# layout.template_icon_view(bkit_ratings, property, show_labels=False, scale=6.0, scale_popup=5.0)
row = col.row()
row.prop(self, 'rating_quality_ui', expand=True, icon_only=True, emboss=False)
# row.label(text=str(self.rating_quality))
col.separator()
row = layout.row()
row.label(text=f"How many hours did this {self.asset_type} save you?")
if self.asset_type in ('model', 'scene'):
row = layout.row()
if utils.profile_is_validator():
col.prop(self, 'rating_work_hours')
row.prop(self, 'rating_work_hours_ui', expand=True, icon_only=False, emboss=True)
if float(self.rating_work_hours_ui) > 100:
utils.label_multiline(layout,
text=f"\nThat's huge! please be sure to give such rating only to godly {self.asset_type}s.\n",
width=500)
elif float(self.rating_work_hours_ui) > 18:
layout.separator()
utils.label_multiline(layout,
text=f"\nThat's a lot! please be sure to give such rating only to amazing {self.asset_type}s.\n",
width=500)
elif self.asset_type == 'hdr':
row = layout.row()
row.prop(self, 'rating_work_hours_ui_1_10', expand=True, icon_only=False, emboss=True)
else:
row = layout.row()
row.prop(self, 'rating_work_hours_ui_1_5', expand=True, icon_only=False, emboss=True)
def update_quality_ui(self, context):
'''Converts the _ui the enum into actual quality number.'''
user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
if user_preferences.api_key == '':
# ui_panels.draw_not_logged_in(self, message='Please login/signup to rate assets.')
# bpy.ops.wm.call_menu(name='OBJECT_MT_blenderkit_login_menu')
# return
bpy.ops.wm.blenderkit_login('INVOKE_DEFAULT',
message='Please login/signup to rate assets. Clicking OK takes you to web login.')
# self.rating_quality_ui = '0'
self.rating_quality = int(self.rating_quality_ui)
def update_ratings_work_hours_ui(self, context):
user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
if user_preferences.api_key == '':
# ui_panels.draw_not_logged_in(self, message='Please login/signup to rate assets.')
# bpy.ops.wm.call_menu(name='OBJECT_MT_blenderkit_login_menu')
# return
bpy.ops.wm.blenderkit_login('INVOKE_DEFAULT',
message='Please login/signup to rate assets. Clicking OK takes you to web login.')
# self.rating_work_hours_ui = '0'
self.rating_work_hours = float(self.rating_work_hours_ui)
def update_ratings_work_hours_ui_1_5(self, context):
user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
if user_preferences.api_key == '':
# ui_panels.draw_not_logged_in(self, message='Please login/signup to rate assets.')
# bpy.ops.wm.call_menu(name='OBJECT_MT_blenderkit_login_menu')
# return
bpy.ops.wm.blenderkit_login('INVOKE_DEFAULT',
message='Please login/signup to rate assets. Clicking OK takes you to web login.')
# self.rating_work_hours_ui_1_5 = '0'
# print('updating 1-5')
# print(float(self.rating_work_hours_ui_1_5))
self.rating_work_hours = float(self.rating_work_hours_ui_1_5)
def update_ratings_work_hours_ui_1_10(self, context):
user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
if user_preferences.api_key == '':
# ui_panels.draw_not_logged_in(self, message='Please login/signup to rate assets.')
# bpy.ops.wm.call_menu(name='OBJECT_MT_blenderkit_login_menu')
# return
bpy.ops.wm.blenderkit_login('INVOKE_DEFAULT',
message='Please login/signup to rate assets. Clicking OK takes you to web login.')
# self.rating_work_hours_ui_1_5 = '0'
# print('updating 1-5')
# print(float(self.rating_work_hours_ui_1_5))
self.rating_work_hours = float(self.rating_work_hours_ui_1_10)
class FastRateMenu(Operator):
class FastRateMenu(Operator, ratings_utils.RatingsProperties):
"""Rating of the assets , also directly from the asset bar - without need to download assets"""
bl_idname = "wm.blenderkit_menu_rating_upload"
bl_label = "Rate asset"
bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}
message: StringProperty(
name="message",
description="message",
default="Rating asset",
options={'SKIP_SAVE'})
asset_id: StringProperty(
name="Asset Base Id",
description="Unique id of the asset (hidden)",
default="",
options={'SKIP_SAVE'})
asset_name: StringProperty(
name="Asset Name",
description="Name of the asset (hidden)",
default="",
options={'SKIP_SAVE'})
asset_type: StringProperty(
name="Asset type",
description="asset type",
default="",
options={'SKIP_SAVE'})
rating_quality: IntProperty(name="Quality",
description="quality of the material",
default=0,
min=-1, max=10,
# update=update_ratings_quality,
options={'SKIP_SAVE'})
# the following enum is only to ease interaction - enums support 'drag over' and enable to draw the stars easily.
rating_quality_ui: EnumProperty(name='rating_quality_ui',
items=stars_enum_callback,
description='Rating stars 0 - 10',
default=0,
update=update_quality_ui,
options={'SKIP_SAVE'})
rating_work_hours: FloatProperty(name="Work Hours",
description="How many hours did this work take?",
default=0.00,
min=0.0, max=300,
# update=update_ratings_work_hours,
options={'SKIP_SAVE'}
)
high_rating_warning = "This is a high rating, please be sure to give such rating only to amazing assets"
rating_work_hours_ui: EnumProperty(name="Work Hours",
description="How many hours did this work take?",
items=[('0', '0', ''),
('.5', '0.5', ''),
('1', '1', ''),
('2', '2', ''),
('3', '3', ''),
('4', '4', ''),
('5', '5', ''),
('6', '6', ''),
('8', '8', ''),
('10', '10', ''),
('15', '15', ''),
('20', '20', ''),
('30', '30', high_rating_warning),
('50', '50', high_rating_warning),
('100', '100', high_rating_warning),
('150', '150', high_rating_warning),
('200', '200', high_rating_warning),
('250', '250', high_rating_warning),
],
default='0', update=update_ratings_work_hours_ui,
options = {'SKIP_SAVE'}
)
rating_work_hours_ui_1_5: EnumProperty(name="Work Hours",
description="How many hours did this work take?",
items=[('0', '0', ''),
('.2', '0.2', ''),
('.5', '0.5', ''),
('1', '1', ''),
('2', '2', ''),
('3', '3', ''),
('4', '4', ''),
('5', '5', '')
],
default='0',
update=update_ratings_work_hours_ui_1_5,
options = {'SKIP_SAVE'}
)
rating_work_hours_ui_1_10: EnumProperty(name="Work Hours",
description="How many hours did this work take?",
items=[('0', '0', ''),
('1', '1', ''),
('2', '2', ''),
('3', '3', ''),
('4', '4', ''),
('5', '5', ''),
('6', '6', ''),
('7', '7', ''),
('8', '8', ''),
('9', '9', ''),
('10', '10', '')
],
default='0',
update=update_ratings_work_hours_ui_1_10,
options={'SKIP_SAVE'}
)
@classmethod
def poll(cls, context):
@ -430,41 +225,9 @@ class FastRateMenu(Operator):
def draw(self, context):
layout = self.layout
col = layout.column()
# layout.template_icon_view(bkit_ratings, property, show_labels=False, scale=6.0, scale_popup=5.0)
col.label(text=self.message)
row = col.row()
row.prop(self, 'rating_quality_ui', expand=True, icon_only=True, emboss=False)
# row.label(text=str(self.rating_quality))
col.separator()
row = layout.row()
row.label(text=f"How many hours did this {self.asset_type} save you?")
if self.asset_type in ('model', 'scene'):
row = layout.row()
if utils.profile_is_validator():
col.prop(self, 'rating_work_hours')
row.prop(self, 'rating_work_hours_ui', expand=True, icon_only=False, emboss=True)
if float(self.rating_work_hours_ui) > 100:
utils.label_multiline(layout,
text=f"\nThat's huge! please be sure to give such rating only to godly {self.asset_type}s.\n",
width=500)
elif float(self.rating_work_hours_ui) > 18:
layout.separator()
utils.label_multiline(layout,
text=f"\nThat's a lot! please be sure to give such rating only to amazing {self.asset_type}s.\n",
width=500)
elif self.asset_type == 'hdr':
row = layout.row()
row.prop(self, 'rating_work_hours_ui_1_10', expand=True, icon_only=False, emboss=True)
else:
row = layout.row()
row.prop(self, 'rating_work_hours_ui_1_5', expand=True, icon_only=False, emboss=True)
layout.label(text=self.message)
draw_ratings_menu(self, context, layout)
def execute(self, context):
user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
@ -484,11 +247,11 @@ class FastRateMenu(Operator):
if self.rating_quality > 0.1:
rtgs = (('quality', self.rating_quality),)
tasks_queue.add_task((send_rating_to_thread_quality, (url, rtgs, headers)), wait=2.5, only_last=True)
tasks_queue.add_task((ratings_utils.send_rating_to_thread_quality, (url, rtgs, headers)), wait=2.5, only_last=True)
if self.rating_work_hours > 0.45:
rtgs = (('working_hours', round(self.rating_work_hours, 1)),)
tasks_queue.add_task((send_rating_to_thread_work_hours, (url, rtgs, headers)), wait=2.5, only_last=True)
tasks_queue.add_task((ratings_utils.send_rating_to_thread_work_hours, (url, rtgs, headers)), wait=2.5, only_last=True)
return {'FINISHED'}
def invoke(self, context, event):
@ -505,7 +268,7 @@ class FastRateMenu(Operator):
self.message = f"Rate asset {self.asset_name}"
wm = context.window_manager
if self.asset_type in ('model','scene'):
if self.asset_type in ('model', 'scene'):
# spawn a wider one for validators for the enum buttons
return wm.invoke_props_dialog(self, width=500)
else:

282
blenderkit/ratings_utils.py Normal file
View File

@ -0,0 +1,282 @@
# ##### 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 #####
# mainly update functions and callbacks for ratings properties, here to avoid circular imports.
import bpy
from blenderkit import utils, paths, tasks_queue, rerequests
from bpy.props import (
IntProperty,
FloatProperty,
FloatVectorProperty,
StringProperty,
EnumProperty,
BoolProperty,
PointerProperty,
)
import threading
import requests
import logging
bk_logger = logging.getLogger('blenderkit')
def upload_rating_thread(url, ratings, headers):
''' Upload rating thread function / disconnected from blender data.'''
bk_logger.debug('upload rating ' + url + str(ratings))
for rating_name, score in ratings:
if (score != -1 and score != 0):
rating_url = url + rating_name + '/'
data = {
"score": score, # todo this kind of mixing is too much. Should have 2 bkit structures, upload, use
}
try:
r = rerequests.put(rating_url, data=data, verify=True, headers=headers)
except requests.exceptions.RequestException as e:
print('ratings upload failed: %s' % str(e))
def send_rating_to_thread_quality(url, ratings, headers):
'''Sens rating into thread rating, main purpose is for tasks_queue.
One function per property to avoid lost data due to stashing.'''
thread = threading.Thread(target=upload_rating_thread, args=(url, ratings, headers))
thread.start()
def send_rating_to_thread_work_hours(url, ratings, headers):
'''Sens rating into thread rating, main purpose is for tasks_queue.
One function per property to avoid lost data due to stashing.'''
thread = threading.Thread(target=upload_rating_thread, args=(url, ratings, headers))
thread.start()
def update_ratings_quality(self, context):
user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
api_key = user_preferences.api_key
headers = utils.get_headers(api_key)
asset = self.id_data
if asset:
bkit_ratings = asset.bkit_ratings
url = paths.get_api_url() + 'assets/' + asset['asset_data']['id'] + '/rating/'
else:
# this part is for operator rating:
bkit_ratings = self
url = paths.get_api_url() + f'assets/{self.asset_id}/rating/'
if bkit_ratings.rating_quality > 0.1:
ratings = [('quality', bkit_ratings.rating_quality)]
tasks_queue.add_task((send_rating_to_thread_quality, (url, ratings, headers)), wait=2.5, only_last=True)
def update_ratings_work_hours(self, context):
user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
api_key = user_preferences.api_key
headers = utils.get_headers(api_key)
asset = self.id_data
if asset:
bkit_ratings = asset.bkit_ratings
url = paths.get_api_url() + 'assets/' + asset['asset_data']['id'] + '/rating/'
else:
# this part is for operator rating:
bkit_ratings = self
url = paths.get_api_url() + f'assets/{self.asset_id}/rating/'
if bkit_ratings.rating_work_hours > 0.45:
ratings = [('working_hours', round(bkit_ratings.rating_work_hours, 1))]
tasks_queue.add_task((send_rating_to_thread_work_hours, (url, ratings, headers)), wait=2.5, only_last=True)
def update_quality_ui(self, context):
'''Converts the _ui the enum into actual quality number.'''
user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
if user_preferences.api_key == '':
# ui_panels.draw_not_logged_in(self, message='Please login/signup to rate assets.')
# bpy.ops.wm.call_menu(name='OBJECT_MT_blenderkit_login_menu')
# return
bpy.ops.wm.blenderkit_login('INVOKE_DEFAULT',
message='Please login/signup to rate assets. Clicking OK takes you to web login.')
# self.rating_quality_ui = '0'
self.rating_quality = int(self.rating_quality_ui)
def update_ratings_work_hours_ui(self, context):
user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
if user_preferences.api_key == '':
# ui_panels.draw_not_logged_in(self, message='Please login/signup to rate assets.')
# bpy.ops.wm.call_menu(name='OBJECT_MT_blenderkit_login_menu')
# return
bpy.ops.wm.blenderkit_login('INVOKE_DEFAULT',
message='Please login/signup to rate assets. Clicking OK takes you to web login.')
# self.rating_work_hours_ui = '0'
self.rating_work_hours = float(self.rating_work_hours_ui)
def update_ratings_work_hours_ui_1_5(self, context):
user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
if user_preferences.api_key == '':
# ui_panels.draw_not_logged_in(self, message='Please login/signup to rate assets.')
# bpy.ops.wm.call_menu(name='OBJECT_MT_blenderkit_login_menu')
# return
bpy.ops.wm.blenderkit_login('INVOKE_DEFAULT',
message='Please login/signup to rate assets. Clicking OK takes you to web login.')
# self.rating_work_hours_ui_1_5 = '0'
# print('updating 1-5')
# print(float(self.rating_work_hours_ui_1_5))
self.rating_work_hours = float(self.rating_work_hours_ui_1_5)
def update_ratings_work_hours_ui_1_10(self, context):
user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
if user_preferences.api_key == '':
# ui_panels.draw_not_logged_in(self, message='Please login/signup to rate assets.')
# bpy.ops.wm.call_menu(name='OBJECT_MT_blenderkit_login_menu')
# return
bpy.ops.wm.blenderkit_login('INVOKE_DEFAULT',
message='Please login/signup to rate assets. Clicking OK takes you to web login.')
# self.rating_work_hours_ui_1_5 = '0'
# print('updating 1-5')
# print(float(self.rating_work_hours_ui_1_5))
self.rating_work_hours = float(self.rating_work_hours_ui_1_10)
def stars_enum_callback(self, context):
'''regenerates the enum property used to display rating stars, so that there are filled/empty stars correctly.'''
items = []
for a in range(0, 10):
if self.rating_quality < a + 1:
icon = 'SOLO_OFF'
else:
icon = 'SOLO_ON'
# has to have something before the number in the value, otherwise fails on registration.
items.append((f'{a + 1}', f'{a + 1}', '', icon, a + 1))
return items
class RatingsProperties():
message: StringProperty(
name="message",
description="message",
default="Rating asset",
options={'SKIP_SAVE'})
asset_id: StringProperty(
name="Asset Base Id",
description="Unique id of the asset (hidden)",
default="",
options={'SKIP_SAVE'})
asset_name: StringProperty(
name="Asset Name",
description="Name of the asset (hidden)",
default="",
options={'SKIP_SAVE'})
asset_type: StringProperty(
name="Asset type",
description="asset type",
default="",
options={'SKIP_SAVE'})
rating_quality: IntProperty(name="Quality",
description="quality of the material",
default=0,
min=-1, max=10,
update=update_ratings_quality,
options={'SKIP_SAVE'})
# the following enum is only to ease interaction - enums support 'drag over' and enable to draw the stars easily.
rating_quality_ui: EnumProperty(name='rating_quality_ui',
items=stars_enum_callback,
description='Rating stars 0 - 10',
default=0,
update=update_quality_ui,
options={'SKIP_SAVE'})
rating_work_hours: FloatProperty(name="Work Hours",
description="How many hours did this work take?",
default=0.00,
min=0.0, max=300,
update=update_ratings_work_hours,
options={'SKIP_SAVE'}
)
high_rating_warning = "This is a high rating, please be sure to give such rating only to amazing assets"
rating_work_hours_ui: EnumProperty(name="Work Hours",
description="How many hours did this work take?",
items=[('0', '0', ''),
('.5', '0.5', ''),
('1', '1', ''),
('2', '2', ''),
('3', '3', ''),
('4', '4', ''),
('5', '5', ''),
('6', '6', ''),
('8', '8', ''),
('10', '10', ''),
('15', '15', ''),
('20', '20', ''),
('30', '30', high_rating_warning),
('50', '50', high_rating_warning),
('100', '100', high_rating_warning),
('150', '150', high_rating_warning),
('200', '200', high_rating_warning),
('250', '250', high_rating_warning),
],
default='0', update=update_ratings_work_hours_ui,
options={'SKIP_SAVE'}
)
rating_work_hours_ui_1_5: EnumProperty(name="Work Hours",
description="How many hours did this work take?",
items=[('0', '0', ''),
('.2', '0.2', ''),
('.5', '0.5', ''),
('1', '1', ''),
('2', '2', ''),
('3', '3', ''),
('4', '4', ''),
('5', '5', '')
],
default='0',
update=update_ratings_work_hours_ui_1_5,
options={'SKIP_SAVE'}
)
rating_work_hours_ui_1_10: EnumProperty(name="Work Hours",
description="How many hours did this work take?",
items=[('0', '0', ''),
('1', '1', ''),
('2', '2', ''),
('3', '3', ''),
('4', '4', ''),
('5', '5', ''),
('6', '6', ''),
('7', '7', ''),
('8', '8', ''),
('9', '9', ''),
('10', '10', '')
],
default='0',
update=update_ratings_work_hours_ui_1_10,
options={'SKIP_SAVE'}
)

View File

@ -191,7 +191,7 @@ def fetch_server_data():
if api_key != '' and bpy.context.window_manager.get('bkit profile') == None:
get_profile()
if bpy.context.window_manager.get('bkit_categories') is None:
categories.fetch_categories_thread(api_key, force = False)
categories.fetch_categories_thread(api_key, force=False)
first_time = True
@ -238,7 +238,7 @@ def parse_result(r):
# except:
# utils.p('asset with no files-size')
asset_type = r['assetType']
if len(r['files']) > 0:#TODO remove this condition so all assets are parsed.
if len(r['files']) > 0: # TODO remove this condition so all assets are parsed.
get_author(r)
r['available_resolutions'] = []
@ -262,7 +262,6 @@ def parse_result(r):
# small_tname = paths.extract_filename_from_url(f['fileThumbnail'])
# allthumbs.append(tname) # TODO just first thumb is used now.
if f['fileType'] == 'blend':
durl = f['downloadUrl'].split('?')[0]
# fname = paths.extract_filename_from_url(f['filePath'])
@ -270,7 +269,7 @@ def parse_result(r):
if f['fileType'].find('resolution') > -1:
r['available_resolutions'].append(resolutions.resolutions[f['fileType']])
#code for more thumbnails
# code for more thumbnails
# tdict = {}
# for i, t in enumerate(allthumbs):
# tdict['thumbnail_%i'] = t
@ -436,8 +435,8 @@ def timer_update():
load_previews()
ui_props = bpy.context.scene.blenderkitUI
if len(result_field) < ui_props.scrolloffset or not(thread[0].params.get('get_next')):
#jump back
if len(result_field) < ui_props.scrolloffset or not (thread[0].params.get('get_next')):
# jump back
ui_props.scrolloffset = 0
props.is_searching = False
props.search_error = False
@ -572,11 +571,6 @@ def writeblockm(tooltip, mdata, key='', pretext=None, width=40): # for longer t
return tooltip
def fmt_length(prop):
prop = str(round(prop, 2))
return prop
def has(mdata, prop):
if mdata.get(prop) is not None and mdata[prop] is not None and mdata[prop] is not False:
return True
@ -591,178 +585,30 @@ def generate_tooltip(mdata):
else:
mparams = mdata['parameters']
t = ''
t = writeblock(t, mdata['displayName'], width=col_w)
t += '\n'
t = writeblockm(t, mdata, key='description', pretext='', width=col_w)
if mdata['description'] != '':
t += '\n'
bools = (('rig', None), ('animated', None), ('manifold', 'non-manifold'), ('scene', None), ('simulation', None),
('uv', None))
for b in bools:
if mparams.get(b[0]):
mdata['tags'].append(b[0])
elif b[1] != None:
mdata['tags'].append(b[1])
bools_data = ('adult',)
for b in bools_data:
if mdata.get(b) and mdata[b]:
mdata['tags'].append(b)
t = writeblockm(t, mparams, key='designer', pretext='Designer', width=col_w)
t = writeblockm(t, mparams, key='manufacturer', pretext='Manufacturer', width=col_w)
t = writeblockm(t, mparams, key='designCollection', pretext='Design collection', width=col_w)
# t = writeblockm(t, mparams, key='engines', pretext='engine', width = col_w)
# t = writeblockm(t, mparams, key='model_style', pretext='style', width = col_w)
# t = writeblockm(t, mparams, key='material_style', pretext='style', width = col_w)
# t = writeblockm(t, mdata, key='tags', width = col_w)
# t = writeblockm(t, mparams, key='condition', pretext='condition', width = col_w)
# t = writeblockm(t, mparams, key='productionLevel', pretext='production level', width = col_w)
if has(mdata, 'purePbr'):
t = writeblockm(t, mparams, key='pbrType', pretext='Pbr', width=col_w)
t = writeblockm(t, mparams, key='designYear', pretext='Design year', width=col_w)
if has(mparams, 'dimensionX'):
t += 'Size: %s x %s x %sm\n' % (fmt_length(mparams['dimensionX']),
fmt_length(mparams['dimensionY']),
fmt_length(mparams['dimensionZ']))
if has(mparams, 'faceCount') and mdata['assetType'] == 'model':
t += 'Face count: %s\n' % (mparams['faceCount'])
# t += 'face count: %s, render: %s\n' % (mparams['faceCount'], mparams['faceCountRender'])
# write files size - this doesn't reflect true file size, since files size is computed from all asset files, including resolutions.
# if mdata.get('filesSize'):
# fs = utils.files_size_to_text(mdata['filesSize'])
# t += f'files size: {fs}\n'
# t = writeblockm(t, mparams, key='meshPolyType', pretext='mesh type', width = col_w)
# t = writeblockm(t, mparams, key='objectCount', pretext='nubmber of objects', width = col_w)
# t = writeblockm(t, mparams, key='materials', width = col_w)
# t = writeblockm(t, mparams, key='modifiers', width = col_w)
# t = writeblockm(t, mparams, key='shaders', width = col_w)
# if has(mparams, 'textureSizeMeters'):
# t += 'Texture size: %s m\n' % fmt_length(mparams['textureSizeMeters'])
if has(mparams, 'textureResolutionMax') and mparams['textureResolutionMax'] > 0:
if not mparams.get('textureResolutionMin'): # for HDR's
t = writeblockm(t, mparams, key='textureResolutionMax', pretext='Resolution', width=col_w)
elif mparams.get('textureResolutionMin') == mparams['textureResolutionMax']:
t = writeblockm(t, mparams, key='textureResolutionMin', pretext='Texture resolution', width=col_w)
else:
t += 'Tex resolution: %i - %i\n' % (mparams.get('textureResolutionMin'), mparams['textureResolutionMax'])
if has(mparams, 'thumbnailScale'):
t = writeblockm(t, mparams, key='thumbnailScale', pretext='Preview scale', width=col_w)
# t += 'uv: %s\n' % mdata['uv']
t = writeblock(t, mdata['displayName'], width=int(col_w * .6))
# t += '\n'
if mdata.get('license') == 'cc_zero':
t+= 'license: CC Zero\n'
else:
t+= 'license: Royalty free\n'
# t = writeblockm(t, mdata, key='license', width=col_w)
fs = mdata.get('files')
if utils.profile_is_validator():
if fs and len(fs) > 2:
resolutions = 'Resolutions:'
list.sort(fs, key=lambda f: f['fileType'])
for f in fs:
if f['fileType'].find('resolution') > -1:
resolutions += f['fileType'][11:] + ' '
resolutions += '\n'
t += resolutions.replace('_', '.')
if mdata['isFree']:
t += 'Free plan\n'
else:
t += 'Full plan\n'
else:
if fs:
for f in fs:
if f['fileType'].find('resolution') > -1:
t += 'Asset has lower resolutions available\n'
break;
# generator is for both upload preview and search, this is only after search
# if mdata.get('versionNumber'):
# # t = writeblockm(t, mdata, key='versionNumber', pretext='version', width = col_w)
# a_id = mdata['author'].get('id')
# if a_id != None:
# adata = bpy.context.window_manager['bkit authors'].get(str(a_id))
# if adata != None:
# t += generate_author_textblock(adata)
t += '\n'
rc = mdata.get('ratingsCount')
if rc:
t+='\n'
if rc:
rcount = min(rc['quality'], rc['workingHours'])
else:
rcount = 0
show_rating_threshold = 5
if rcount < show_rating_threshold and mdata['assetType'] != 'hdr':
t += f"Only assets with enough ratings \nshow the rating value. Please rate.\n"
if rc['quality'] >= show_rating_threshold:
# t += f"{int(mdata['ratingsAverage']['quality']) * '*'}\n"
t += f"* {round(mdata['ratingsAverage']['quality'],1)}\n"
if rc['workingHours'] >= show_rating_threshold:
t += f"Hours saved: {int(mdata['ratingsAverage']['workingHours'])}\n"
if utils.profile_is_validator():
t += f"Score: {int(mdata['score'])}\n"
t += f"Ratings count {rc['quality']}*/{rc['workingHours']}wh value " \
f"{(mdata['ratingsAverage']['quality'],1)}*/{(mdata['ratingsAverage']['workingHours'],1)}wh\n"
if len(t.split('\n')) < 11:
t += '\n'
t += get_random_tip(mdata)
t += '\n'
# t = writeblockm(t, mdata, key='description', pretext='', width=col_w)
return t
def get_random_tip(mdata):
def get_random_tip():
t = ''
tip = 'Tip: ' + random.choice(rtips)
t = writeblock(t, tip)
return t
# at = mdata['assetType']
# if at == 'brush' or at == 'texture':
# t += 'click to link %s' % mdata['assetType']
# if at == 'model' or at == 'material':
# tips = ['Click or drag in scene to link/append %s' % mdata['assetType'],
# "'A' key to search assets by same author",
# "'W' key to open Authors webpage",
# ]
# tip = 'Tip: ' + random.choice(tips)
# t = writeblock(t, tip)
return t
def generate_author_textblock(adata):
t = '\n\n\n'
t = ''
if adata not in (None, ''):
col_w = 40
col_w = 2000
if len(adata['firstName'] + adata['lastName']) > 0:
t = 'Author:\n'
t += '%s %s\n' % (adata['firstName'], adata['lastName'])
t = '%s %s\n' % (adata['firstName'], adata['lastName'])
t += '\n'
if adata.get('aboutMeUrl') is not None:
t = writeblockm(t, adata, key='aboutMeUrl', pretext='', width=col_w)
t += '\n'
if adata.get('aboutMe') is not None:
t = writeblockm(t, adata, key='aboutMe', pretext='', width=col_w)
t += '\n'
return t
@ -904,7 +750,8 @@ def get_profile():
thread.start()
return a
def query_to_url(query = {}, params = {}):
def query_to_url(query={}, params={}):
# build a new request
url = paths.get_api_url() + 'search/'
@ -948,15 +795,17 @@ def query_to_url(query = {}, params = {}):
urlquery = url + requeststring
return urlquery
def parse_html_formated_error(text):
report = text[text.find('<title>') + 7: text.find('</title>')]
return report
class Searcher(threading.Thread):
query = None
def __init__(self, query, params, orig_result, tempdir = '', headers = None, urlquery = ''):
def __init__(self, query, params, orig_result, tempdir='', headers=None, urlquery=''):
super(Searcher, self).__init__()
self.query = query
self.params = params
@ -979,13 +828,11 @@ class Searcher(threading.Thread):
query = self.query
params = self.params
t = time.time()
mt('search thread started')
# tempdir = paths.get_temp_dir('%s_search' % query['asset_type'])
# json_filepath = os.path.join(tempdir, '%s_searchresult.json' % query['asset_type'])
rdata = {}
rdata['results'] = []
@ -1034,8 +881,6 @@ class Searcher(threading.Thread):
imgpath = os.path.join(self.tempdir, imgname)
thumb_small_filepaths.append(imgpath)
if d["assetType"] == 'hdr':
larege_thumb_url = d['thumbnailMiddleUrlNonsquared']
@ -1047,8 +892,6 @@ class Searcher(threading.Thread):
imgpath = os.path.join(self.tempdir, imgname)
thumb_full_filepaths.append(imgpath)
# for f in d['files']:
# # TODO move validation of published assets to server, too manmy checks here.
# if f['fileType'] == 'thumbnail' and f['fileThumbnail'] != None and f['fileThumbnailLarge'] != None:
@ -1147,10 +990,10 @@ def build_query_common(query, props):
if props.search_keywords != '':
query_common["query"] = props.search_keywords
if props.search_verification_status != 'ALL':
if props.search_verification_status != 'ALL' and utils.profile_is_validator():
query_common['verification_status'] = props.search_verification_status.lower()
if props.unrated_only:
if props.unrated_only and utils.profile_is_validator():
query["quality_count"] = 0
if props.search_file_size:
@ -1323,7 +1166,7 @@ def add_search_process(query, params, orig_result):
if not params['get_next']:
urlquery = query_to_url(query, params)
thread = Searcher(query, params, orig_result, tempdir = tempdir, headers = headers, urlquery = urlquery)
thread = Searcher(query, params, orig_result, tempdir=tempdir, headers=headers, urlquery=urlquery)
thread.start()
search_threads.append([thread, tempdir, query['asset_type'], {}]) # 4th field is for results
@ -1517,17 +1360,20 @@ def search_update(self, context):
search()
# accented_string is of type 'unicode'
def strip_accents(s):
return ''.join(c for c in unicodedata.normalize('NFD', s)
if unicodedata.category(c) != 'Mn')
class SearchOperator(Operator):
"""Tooltip"""
bl_idname = "view3d.blenderkit_search"
bl_label = "BlenderKit asset search"
bl_description = "Search online for assets"
bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}
own: BoolProperty(name="own assets only",
description="Find all own assets",
default=False)
@ -1559,6 +1405,12 @@ class SearchOperator(Operator):
options={'SKIP_SAVE'}
)
tooltip: bpy.props.StringProperty(default='Runs search and displays the asset bar at the same time')
@classmethod
def description(cls, context, properties):
return properties.tooltip
@classmethod
def poll(cls, context):
return True
@ -1571,15 +1423,50 @@ class SearchOperator(Operator):
if self.keywords != '':
sprops.search_keywords = self.keywords
search(category=self.category, get_next=self.get_next, author_id=self.author_id)
# bpy.ops.view3d.blenderkit_asset_bar()
return {'FINISHED'}
class UrlOperator(Operator):
""""""
bl_idname = "wm.blenderkit_url"
bl_label = ""
bl_description = "Search online for assets"
bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}
tooltip: bpy.props.StringProperty(default='Open a web page')
url: bpy.props.StringProperty(default='Runs search and displays the asset bar at the same time')
@classmethod
def description(cls, context, properties):
return properties.tooltip
def execute(self, context):
bpy.ops.wm.url_open(url=self.url)
return {'FINISHED'}
class TooltipLabelOperator(Operator):
""""""
bl_idname = "wm.blenderkit_tooltip"
bl_label = ""
bl_description = "Empty operator to be able to create tooltips on labels in UI"
bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}
tooltip: bpy.props.StringProperty(default='Open a web page')
@classmethod
def description(cls, context, properties):
return properties.tooltip
def execute(self, context):
return {'FINISHED'}
classes = [
SearchOperator
SearchOperator,
UrlOperator,
TooltipLabelOperator
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 540 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -231,11 +231,6 @@ def draw_ratings_bgl():
if rating_possible: # (not rated or ui_props.rating_menu_on):
# print('rating is pssible', asset_data['name'])
bkit_ratings = asset.bkit_ratings
bgcol = bpy.context.preferences.themes[0].user_interface.wcol_tooltip.inner
textcol = (1, 1, 1, 1)
r = bpy.context.region
font_size = int(ui.rating_ui_scale * 20)
if ui.rating_button_on:
# print('should draw button')
@ -261,7 +256,6 @@ def draw_ratings_bgl():
ui.rating_button_width,
ui.rating_button_width,
img, 1)
# ui_bgl.draw_text( 'rate asset %s' % asset_data['name'],r.width - rating_button_width + margin, margin, font_size)
return
@ -278,50 +272,41 @@ def draw_text_block(x=0, y=0, width=40, font_size=10, line_height=15, text='', c
ui_bgl.draw_text(l, x, ytext, font_size, color)
def draw_tooltip(x, y, text='', author='', img=None, gravatar=None):
def draw_tooltip(x, y, name='', author='', quality = '-', img=None, gravatar=None):
region = bpy.context.region
scale = bpy.context.preferences.view.ui_scale
t = time.time()
ttipmargin = 5
textmargin = 10
font_height = int(12 * scale)
line_height = int(15 * scale)
nameline_height = int(23 * scale)
lines = text.split('\n')
alines = author.split('\n')
ncolumns = 2
# nlines = math.ceil((len(lines) - 1) / ncolumns)
nlines = max(len(lines) - 1, len(alines)) # math.ceil((len(lines) - 1) / ncolumns)
texth = line_height * nlines + nameline_height
if not img or max(img.size[0], img.size[1]) == 0:
return;
isizex = int(512 * scale * img.size[0] / max(img.size[0], img.size[1]))
isizey = int(512 * scale * img.size[1] / max(img.size[0], img.size[1]))
estimated_height = 2 * ttipmargin + textmargin + isizey
x += 20
y -= 20
#first get image size scaled
isizex = int(512 * scale * img.size[0] / min(img.size[0], img.size[1]))
isizey = int(512 * scale * img.size[1] / min(img.size[0], img.size[1]))
ttipmargin = 5 * scale
#then do recurrent re-scaling, to know where to fit the tooltip
estimated_height = 2 * ttipmargin + isizey
if estimated_height > y:
scaledown = y / (estimated_height)
scale *= scaledown
# we need to scale these down to have correct size if the tooltip wouldn't fit.
font_height = int(12 * scale)
line_height = int(15 * scale)
nameline_height = int(23 * scale)
lines = text.split('\n')
isizex = int(512 * scale * img.size[0] / min(img.size[0], img.size[1]))
isizey = int(512 * scale * img.size[1] / min(img.size[0], img.size[1]))
texth = line_height * nlines + nameline_height
isizex = int(512 * scale * img.size[0] / max(img.size[0], img.size[1]))
isizey = int(512 * scale * img.size[1] / max(img.size[0], img.size[1]))
ttipmargin = 5 * scale
textmargin = 12 * scale
name_height = int(18 * scale)
if gravatar is not None:
overlay_height_base = 90
else:
overlay_height_base = 70
x += 2 * ttipmargin
y -= 2 * ttipmargin
overlay_height = overlay_height_base * scale
name_height = int(20 * scale)
width = isizex + 2 * ttipmargin
@ -330,116 +315,58 @@ def draw_tooltip(x, y, text='', author='', img=None, gravatar=None):
if r.type == 'UI':
properties_width = r.width
# limit to area borders
x = min(x + width, region.width - properties_width) - width
bgcol = bpy.context.preferences.themes[0].user_interface.wcol_tooltip.inner
bgcol1 = (bgcol[0], bgcol[1], bgcol[2], .6)
# define_colors
background_color = bpy.context.preferences.themes[0].user_interface.wcol_tooltip.inner
background_overlay = (background_color[0], background_color[1], background_color[2], .8)
textcol = bpy.context.preferences.themes[0].user_interface.wcol_tooltip.text
textcol = (textcol[0], textcol[1], textcol[2], 1)
textcol_mild = (textcol[0] * .8, textcol[1] * .8, textcol[2] * .8, 1)
textcol_strong = (textcol[0] * 1.3, textcol[1] * 2.3, textcol[2] * 1.3, 1)
# textcol_strong = (0.4, 1, 0.3, 1)
white = (1, 1, 1, .1)
# background
ui_bgl.draw_rect(x - ttipmargin,
y - 2 * ttipmargin - isizey,
isizex + ttipmargin * 2,
2 * ttipmargin + isizey,
bgcol)
background_color)
# main preview image
ui_bgl.draw_image(x, y - isizey - ttipmargin, isizex, isizey, img, 1)
# text overlay background
ui_bgl.draw_rect(x - ttipmargin,
y - 2 * ttipmargin - isizey,
isizex + ttipmargin * 2,
2 * ttipmargin + texth,
bgcol1)
ttipmargin + overlay_height ,
background_overlay)
#draw name
name_x = x + textmargin
name_y = y - isizey + overlay_height - textmargin - name_height
ui_bgl.draw_text(name, name_x, name_y, name_height, textcol)
# draw gravatar
gsize = 40
author_x_text = x + isizex - textmargin
gravatar_size = overlay_height - 2 * textmargin
gravatar_y = y - isizey - ttipmargin + textmargin
if gravatar is not None:
# ui_bgl.draw_image(x + isizex - gsize - textmargin, y - isizey + texth - gsize - nameline_height - textmargin,
# gsize, gsize, gravatar, 1)
ui_bgl.draw_image(x + isizex / 2 + textmargin, y - isizey + texth - gsize - nameline_height - textmargin,
gsize, gsize, gravatar, 1)
author_x_text -= gravatar_size + textmargin
ui_bgl.draw_image(x + isizex - gravatar_size - textmargin,
gravatar_y, # + textmargin,
gravatar_size, gravatar_size, gravatar, 1)
i = 0
column_lines = -1 # start minus one for the name
xtext = x + textmargin
fsize = name_height
tcol = textcol
#draw author's name
author_text_size = int(name_height * .7)
ui_bgl.draw_text(author, author_x_text, gravatar_y, author_text_size, textcol, ralign=True)
for l in lines:
ytext = y - column_lines * line_height - nameline_height - ttipmargin - textmargin - isizey + texth
if i == 0:
ytext = y - name_height + 5 - isizey + texth - textmargin
elif i == len(lines) - 1:
ytext = y - (nlines - 1) * line_height - nameline_height - ttipmargin * 2 - isizey + texth
tcol = textcol
tsize = font_height
else:
fsize = font_height
# draw quality
quality_text_size = int(name_height * 1)
img = utils.get_thumbnail('star_grey.png')
ui_bgl.draw_image(name_x, gravatar_y, quality_text_size, quality_text_size, img, .6)
ui_bgl.draw_text(str(quality), name_x + quality_text_size + 5, gravatar_y, quality_text_size, textcol)
if l[:4] == 'Tip:' or l[:11] == 'Please rate':
tcol = textcol_strong
fsize = font_height + 1
i += 1
column_lines += 1
ui_bgl.draw_text(l, xtext, ytext, fsize, tcol)
xtext += int(isizex / ncolumns)
column_lines = 1
i = 0
for l in alines:
if gravatar is not None:
if column_lines == 1:
xtext += gsize + textmargin
if column_lines == 4:
xtext -= gsize + textmargin
ytext = y - column_lines * line_height - nameline_height - ttipmargin - textmargin - isizey + texth
if False: # i == 0:
ytext = y - name_height + 5 - isizey + texth - textmargin
elif i == len(lines) - 1:
ytext = y - (nlines - 1) * line_height - nameline_height - ttipmargin * 2 - isizey + texth
tcol = textcol
tsize = font_height
if (i > 0 and alines[i - 1][:7] == 'Author:'):
tcol = textcol_strong
fsize = font_height + 2
else:
fsize = font_height
tcol = textcol
if l[:4] == 'Tip:' or l[:11] == 'Please rate':
tcol = textcol_strong
fsize = font_height + 1
i += 1
column_lines += 1
ui_bgl.draw_text(l, xtext, ytext, fsize, tcol)
# for l in lines:
# if column_lines >= nlines:
# xtext += int(isizex / ncolumns)
# column_lines = 0
# ytext = y - column_lines * line_height - nameline_height - ttipmargin - textmargin - isizey + texth
# if i == 0:
# ytext = y - name_height + 5 - isizey + texth - textmargin
# elif i == len(lines) - 1:
# ytext = y - (nlines - 1) * line_height - nameline_height - ttipmargin * 2 - isizey + texth
# tcol = textcol
# tsize = font_height
# else:
# if l[:4] == 'Tip:':
# tcol = textcol_strong
# fsize = font_height
# i += 1
# column_lines += 1
# ui_bgl.draw_text(l, xtext, ytext, fsize, tcol)
t = time.time()
def draw_tooltip_with_author(asset_data, x, y):
@ -453,104 +380,24 @@ def draw_tooltip_with_author(asset_data, x, y):
if a is not None and a != '':
if a.get('gravatarImg') is not None:
gimg = utils.get_hidden_image(a['gravatarImg'], a['gravatarHash'])
atip = a['tooltip']
# scene = bpy.context.scene
# ui_props = scene.blenderkitUI
draw_tooltip(x, y, text=asset_data['tooltip'], author=atip, img=img,
aname = asset_data['displayName']
if len(aname)>36:
aname = f"{aname[:33]}..."
rc = asset_data.get('ratingsCount')
show_rating_threshold = 0
rcount = 0
quality = '-'
if rc:
rcount = min(rc['quality'], rc['workingHours'])
if rcount>show_rating_threshold:
quality = round(asset_data['ratingsAverage'].get('quality'))
draw_tooltip(x, y, name=aname, author=f"by {a['firstName']} {a['lastName']}", quality= quality, img=img,
gravatar=gimg)
def draw_tooltip_old(x, y, text='', author='', img=None):
region = bpy.context.region
scale = bpy.context.preferences.view.ui_scale
t = time.time()
ttipmargin = 10
font_height = int(12 * scale)
line_height = int(15 * scale)
nameline_height = int(23 * scale)
lines = text.split('\n')
ncolumns = 2
nlines = math.ceil((len(lines) - 1) / ncolumns)
texth = line_height * nlines + nameline_height
isizex = int(512 * scale * img.size[0] / max(img.size[0], img.size[1]))
isizey = int(512 * scale * img.size[1] / max(img.size[0], img.size[1]))
estimated_height = texth + 3 * ttipmargin + isizey
if estimated_height > y:
scaledown = y / (estimated_height)
scale *= scaledown
# we need to scale these down to have correct size if the tooltip wouldn't fit.
font_height = int(12 * scale)
line_height = int(15 * scale)
nameline_height = int(23 * scale)
lines = text.split('\n')
ncolumns = 2
nlines = math.ceil((len(lines) - 1) / ncolumns)
texth = line_height * nlines + nameline_height
isizex = int(512 * scale * img.size[0] / max(img.size[0], img.size[1]))
isizey = int(512 * scale * img.size[1] / max(img.size[0], img.size[1]))
name_height = int(18 * scale)
x += 2 * ttipmargin
y -= 2 * ttipmargin
width = isizex + 2 * ttipmargin
properties_width = 0
for r in bpy.context.area.regions:
if r.type == 'UI':
properties_width = r.width
x = min(x + width, region.width - properties_width) - width
bgcol = bpy.context.preferences.themes[0].user_interface.wcol_tooltip.inner
textcol = bpy.context.preferences.themes[0].user_interface.wcol_tooltip.text
textcol = (textcol[0], textcol[1], textcol[2], 1)
textcol1 = (textcol[0] * .8, textcol[1] * .8, textcol[2] * .8, 1)
white = (1, 1, 1, .1)
ui_bgl.draw_rect(x - ttipmargin,
y - texth - 2 * ttipmargin - isizey,
isizex + ttipmargin * 2,
texth + 3 * ttipmargin + isizey,
bgcol)
i = 0
column_lines = -1 # start minus one for the name
xtext = x
fsize = name_height
tcol = textcol
for l in lines:
if column_lines >= nlines:
xtext += int(isizex / ncolumns)
column_lines = 0
ytext = y - column_lines * line_height - nameline_height - ttipmargin
if i == 0:
ytext = y - name_height + 5
elif i == len(lines) - 1:
ytext = y - (nlines - 1) * line_height - nameline_height - ttipmargin
tcol = textcol
tsize = font_height
else:
if l[:5] == 'tags:':
tcol = textcol1
fsize = font_height
i += 1
column_lines += 1
ui_bgl.draw_text(l, xtext, ytext, fsize, tcol)
t = time.time()
ui_bgl.draw_image(x, y - texth - isizey - ttipmargin, isizex, isizey, img, 1)
def draw_callback_2d(self, context):
if not utils.guard_from_crash():
return
@ -592,14 +439,6 @@ def draw_downloader(x, y, percent=0, img=None, text=''):
# ui_bgl.draw_text(asset_data['filesSize'])
if text:
ui_bgl.draw_text(text, x, y - 15, 12, colors.TEXT)
# asset_data and asset_data.get('filesSize'):
# fs = asset_data['filesSize']
# fsmb = fs // (1024 * 1024)
# fskb = fs % 1024
# if fsmb == 0:
# t += 'files size: %iKB\n' % fskb
# else:
# t += 'files size: %iMB %iKB\n' % (fsmb, fskb)
def draw_progress(x, y, text='', percent=None, color=colors.GREEN):
@ -655,10 +494,10 @@ def draw_callback_2d_progress(self, context):
for process in bg_blender.bg_processes:
tcom = process[1]
n=''
n = ''
if tcom.name is not None:
n = tcom.name +': '
draw_progress(x, y - index * 30, '%s' % n+tcom.lasttext,
n = tcom.name + ': '
draw_progress(x, y - index * 30, '%s' % n + tcom.lasttext,
tcom.progress)
index += 1
global reports
@ -687,7 +526,7 @@ def draw_callback_2d_upload_preview(self, context):
img = utils.get_hidden_image(ui_props.thumbnail_image, 'upload_preview')
draw_tooltip(ui_props.bar_x, ui_props.bar_y, text=ui_props.tooltip, img=img)
draw_tooltip(ui_props.bar_x, ui_props.bar_y, name=ui_props.tooltip, img=img)
def is_upload_old(asset_data):
@ -851,6 +690,8 @@ def draw_asset_bar(self, context):
img = utils.get_thumbnail('locked.png')
ui_bgl.draw_image(x + 2, y + 2, 24, 24, img, 1)
# pcoll = icons.icon_collections["main"]
# v_icon = pcoll['rejected']
v_icon = verification_icons[result.get('verificationStatus', 'validated')]
if v_icon is not None:
img = utils.get_thumbnail(v_icon)
@ -1290,6 +1131,7 @@ class ParticlesDropDialog(bpy.types.Operator):
# wm = context.window_manager
# return wm.invoke_props_dialog(self, width=400)
class AssetBarOperator(bpy.types.Operator):
'''runs search and displays the asset bar at the same time'''
bl_idname = "view3d.blenderkit_asset_bar"
@ -1333,6 +1175,7 @@ class AssetBarOperator(bpy.types.Operator):
ui_props.assetbar_on = False
def modal(self, context, event):
# This is for case of closing the area or changing type:
ui_props = context.scene.blenderkitUI
user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
@ -1421,7 +1264,8 @@ class AssetBarOperator(bpy.types.Operator):
or ui_props.asset_type == 'SCENE' or ui_props.asset_type == 'HDR':
export_data, upload_data = upload.get_upload_data(context=context, asset_type=ui_props.asset_type)
if upload_data:
ui_props.tooltip = search.generate_tooltip(upload_data)
# print(upload_data)
ui_props.tooltip = upload_data['displayName']#search.generate_tooltip(upload_data)
return {'PASS_THROUGH'}
@ -1575,8 +1419,12 @@ class AssetBarOperator(bpy.types.Operator):
my = event.mouse_y - r.y
if event.value == 'PRESS' and mouse_in_asset_bar(mx, my):
# bpy.ops.wm.blenderkit_asset_popup('INVOKE_DEFAULT')
bpy.ops.wm.call_menu(name='OBJECT_MT_blenderkit_asset_menu')
context.window.cursor_warp(event.mouse_x - 400, event.mouse_y - 20);
bpy.ops.wm.blenderkit_asset_popup('INVOKE_DEFAULT')
context.window.cursor_warp(event.mouse_x, event.mouse_y);
# bpy.ops.wm.call_menu(name='OBJECT_MT_blenderkit_asset_menu')
return {'RUNNING_MODAL'}
if event.type == 'LEFTMOUSE':
@ -1768,8 +1616,8 @@ class AssetBarOperator(bpy.types.Operator):
asset_index=asset_search_index,
# replace_resolution=True,
invoke_resolution=True,
max_resolution = asset_data.get('max_resolution', 0)
)
max_resolution=asset_data.get('max_resolution', 0)
)
else:
bpy.ops.scene.blenderkit_download( # asset_type=ui_props.asset_type,
asset_index=asset_search_index,
@ -1877,7 +1725,7 @@ class AssetBarOperator(bpy.types.Operator):
context.window_manager.modal_handler_add(self)
ui_props.assetbar_on = True
#in an exceptional case these were accessed before drag start.
# in an exceptional case these were accessed before drag start.
self.drag_start_x = 0
self.drag_start_y = 0
@ -1960,7 +1808,7 @@ def find_and_activate_instancers(object):
class AssetDragOperator(bpy.types.Operator):
"""Draw a line with the mouse"""
"""Drag & drop assets into scene."""
bl_idname = "view3d.asset_drag_drop"
bl_label = "BlenderKit asset drag drop"
@ -2174,7 +2022,8 @@ class RunAssetBarWithContext(bpy.types.Operator):
do_search=self.do_search)
else:
bpy.ops.view3d.blenderkit_asset_bar(C_dict, 'INVOKE_REGION_WIN', keep_running=self.keep_running, do_search=self.do_search)
bpy.ops.view3d.blenderkit_asset_bar(C_dict, 'INVOKE_REGION_WIN', keep_running=self.keep_running,
do_search=self.do_search)
return {'FINISHED'}

View File

@ -120,10 +120,14 @@ def draw_image(x, y, width, height, image, transparency, crop=(0, 0, 1, 1)):
bgl.glDisable(bgl.GL_TEXTURE_2D)
def draw_text(text, x, y, size, color=(1, 1, 1, 0.5)):
font_id = 0
def draw_text(text, x, y, size, color=(1, 1, 1, 0.5), ralign = False):
font_id = 1
# bgl.glColor4f(*color)
blf.color(font_id, color[0], color[1], color[2], color[3])
blf.position(font_id, x, y, 0)
blf.size(font_id, size, 72)
if ralign:
width,height = blf.dimensions(font_id, text)
x-=width
blf.position(font_id, x, y, 0)
blf.draw(font_id, text)

View File

@ -17,8 +17,9 @@
# ##### END GPL LICENSE BLOCK #####
from blenderkit import paths, ratings, utils, download, categories, icons, search, resolutions, ui, tasks_queue, \
autothumb
from blenderkit import paths, ratings, ratings_utils, utils, download, categories, icons, search, resolutions, ui, \
tasks_queue, \
autothumb, upload
from bpy.types import (
Panel
@ -35,9 +36,7 @@ from bpy.props import (
import bpy
import os
import random
import logging
import blenderkit
bk_logger = logging.getLogger('blenderkit')
@ -207,7 +206,6 @@ def draw_panel_hdr_search(self, context):
utils.label_multiline(layout, text=props.report)
def draw_thumbnail_upload_panel(layout, props):
update = False
tex = autothumb.get_texture_ui(props.thumbnail, '.upload_preview')
@ -216,6 +214,7 @@ def draw_thumbnail_upload_panel(layout, props):
box = layout.box()
box.template_icon(icon_value=tex.image.preview.icon_id, scale=6.0)
def draw_panel_model_upload(self, context):
ob = bpy.context.active_object
while ob.parent is not None:
@ -646,7 +645,8 @@ def draw_panel_material_upload(self, context):
prop_needed(row, props, 'thumbnail', props.has_thumbnail, False)
if bpy.context.scene.render.engine in ('CYCLES', 'BLENDER_EEVEE'):
layout.operator("object.blenderkit_generate_material_thumbnail", text='Render thumbnail with Cycles', icon='EXPORT')
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')
@ -1153,12 +1153,13 @@ def draw_asset_context_menu(layout, context, asset_data, from_panel=False):
layout.operator_context = 'INVOKE_DEFAULT'
op = layout.operator('wm.blenderkit_menu_rating_upload', text='Rate')
op.asset_name = asset_data['name']
op.asset_id = asset_data['id']
op.asset_type = asset_data['assetType']
if from_panel:
op = layout.operator('wm.blenderkit_menu_rating_upload', text='Rate')
op.asset_name = asset_data['name']
op.asset_id = asset_data['id']
op.asset_type = asset_data['assetType']
if wm.get('bkit authors') is not None and author_id is not None:
if from_panel and wm.get('bkit authors') is not None and author_id is not None:
a = bpy.context.window_manager['bkit authors'].get(author_id)
if a is not None:
# utils.p('author:', a)
@ -1172,6 +1173,7 @@ def draw_asset_context_menu(layout, context, asset_data, from_panel=False):
op.author_id = author_id
op = layout.operator('view3d.blenderkit_search', text='Search Similar')
op.tooltip = 'Search for similar assets in the library'
# build search string from description and tags:
op.keywords = asset_data['name']
if asset_data.get('description'):
@ -1244,7 +1246,6 @@ def draw_asset_context_menu(layout, context, asset_data, from_panel=False):
elif asset_data['assetBaseId'] in s['assets used'].keys() and asset_data['assetType'] != 'hdr':
# HDRs are excluded from replacement, since they are always replaced.
# called from asset bar:
print('context menu')
op = col.operator('scene.blenderkit_download', text='Replace asset resolution')
op.asset_index = ui_props.active_index
@ -1263,7 +1264,6 @@ def draw_asset_context_menu(layout, context, asset_data, from_panel=False):
op.model_rotation = (0, 0, 0)
op.max_resolution = asset_data.get('max_resolution',
0) # str(utils.get_param(asset_data, 'textureResolutionMax'))
print('should be drawn!')
# print('operator res ', resolution)
# op.resolution = resolution
@ -1271,33 +1271,13 @@ def draw_asset_context_menu(layout, context, asset_data, from_panel=False):
profile = wm.get('bkit profile')
if profile is not None:
# validation
if utils.profile_is_validator():
layout.label(text='Validation tools:')
layout.operator_context = 'EXEC_DEFAULT'
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']
op.state = 'validated'
if asset_data['verificationStatus'] != 'on_hold':
op = layout.operator('object.blenderkit_change_status', text='Put on Hold')
op.asset_id = asset_data['id']
op.state = 'on_hold'
if asset_data['verificationStatus'] != 'rejected':
op = layout.operator('object.blenderkit_change_status', text='Reject')
op.asset_id = asset_data['id']
op.state = 'rejected'
if author_id == str(profile['user']['id']) or utils.profile_is_validator():
layout.label(text='Management tools:')
row = layout.row()
row.operator_context = 'INVOKE_DEFAULT'
op = layout.operator('wm.blenderkit_fast_metadata', text='Fast Edit Metadata')
op = layout.operator('wm.blenderkit_fast_metadata', text='Edit Metadata')
op.asset_id = asset_data['id']
op.asset_type = asset_data['assetType']
@ -1319,7 +1299,7 @@ def draw_asset_context_menu(layout, context, asset_data, from_panel=False):
op.state = 'deleted'
if utils.profile_is_validator():
layout.label(text='Admin Tools:')
layout.label(text='Dev Tools:')
op = layout.operator('object.blenderkit_print_asset_debug', text='Print asset debug')
op.asset_id = asset_data['id']
@ -1377,97 +1357,431 @@ class OBJECT_MT_blenderkit_asset_menu(bpy.types.Menu):
asset_data = sr[ui_props.active_index]
draw_asset_context_menu(self.layout, context, asset_data, from_panel=False)
# ui_props = context.scene.blenderkitUI
#
# sr = bpy.context.window_manager['search results']
# asset_data = sr[ui_props.active_index]
# layout = self.layout
# row = layout.row()
# split = row.split(factor=0.2)
# col = split.column()
# op = col.operator('view3d.asset_drag_drop')
# op.asset_search_index=ui_props.active_index
#
# draw_asset_context_menu(col, context, asset_data, from_panel=False)
# split = split.split(factor=0.3)
# col1 = split.column()
# box = col1.box()
# utils.label_multiline(box, asset_data['tooltip'])
# col2 = split.column()
#
# pcoll = icons.icon_collections["main"]
# my_icon = pcoll['test']
# row = col2.row()
# row.scale_y = 4
# row.template_icon(icon_value=my_icon.icon_id, scale=2.0)
# # col2.template_icon(icon_value=self.img.preview.icon_id, scale=10.0)
# box2 = col2.box()
#
# box2.label(text='and heere goes the rating')
# box2.label(text='************')
# box2.label(text='dadydadadada')
def numeric_to_str(s):
if s:
s = str(round(s))
else:
s = '-'
return s
class AssetPopupCard(bpy.types.Operator):
def label_or_url(layout, text='', tooltip='', url='', icon_value=None, icon=None):
'''automatically switch between different layout options for linking or tooltips'''
layout.emboss = 'NONE'
if url != '':
if icon:
op = layout.operator('wm.blenderkit_url', text=text, icon=icon)
elif icon_value:
op = layout.operator('wm.blenderkit_url', text=text, icon_value=icon_value)
else:
op = layout.operator('wm.blenderkit_url', text=text)
op.url = url
op.tooltip = tooltip
layout.label(text='')
layout.label(text='')
return
if tooltip != '':
if icon:
op = layout.operator('wm.blenderkit_tooltip', text=text, icon=icon)
elif icon_value:
op = layout.operator('wm.blenderkit_tooltip', text=text, icon_value=icon_value)
else:
op = layout.operator('wm.blenderkit_tooltip', text=text)
op.tooltip = tooltip
layout.label(text='')
layout.label(text='')
return
if icon:
layout.label(text=text, icon=icon)
elif icon_value:
layout.label(text=text, icon_value=icon_value)
else:
layout.label(text=text)
class AssetPopupCard(bpy.types.Operator, ratings_utils.RatingsProperties):
"""Generate Cycles thumbnail for model assets"""
bl_idname = "wm.blenderkit_asset_popup"
bl_label = "BlenderKit asset popup"
# bl_options = {'REGISTER', 'INTERNAL'}
bl_options = {'REGISTER', }
width = 700
@classmethod
def poll(cls, context):
return True
def draw_menu(self, context, layout):
col = layout.column()
draw_asset_context_menu(col, context, self.asset_data, from_panel=False)
def draw_property(self, layout, left, right, icon=None, icon_value=None, url='', tooltip=''):
right = str(right)
row = layout.row()
split = row.split(factor=0.4)
split.alignment = 'RIGHT'
split.label(text=left)
split = split.split()
split.alignment = 'LEFT'
# split for questionmark:
if url != '':
split = split.split(factor=0.7)
label_or_url(split, text=right, tooltip=tooltip, url=url, icon_value=icon_value, icon=icon)
# additional questionmark icon where it's important?
if url != '':
split = split.split()
op = split.operator('wm.blenderkit_url', text='', icon='QUESTION')
op.url = url
op.tooltip = tooltip
def draw_asset_parameter(self, layout, key='', pretext=''):
parameter = utils.get_param(self.asset_data, key)
if parameter == None:
return
self.draw_property(layout, pretext, parameter)
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']
layout = layout.column()
if len(self.asset_data['description']) > 0:
box = layout.box()
box.scale_y = 0.8
box.label(text='Description')
utils.label_multiline(box, self.asset_data['description'], width=width)
pcoll = icons.icon_collections["main"]
box = layout.box()
box.scale_y = 0.8
box.label(text='Properties')
if self.asset_data.get('license') == 'cc_zero':
t = 'CC Zero'
icon = pcoll['cc0']
else:
t = 'Royalty free'
icon = pcoll['royalty_free']
self.draw_property(box,
'License:', t,
# icon_value=icon.icon_id,
url="https://www.blenderkit.com/docs/licenses/",
tooltip='All BlenderKit assets are available for commercial use. \n' \
'Click to read more about BlenderKit licenses on the website'
)
if upload.can_edit_asset(asset_data=self.asset_data):
icon = pcoll[self.asset_data['verificationStatus']]
verification_status_tooltips = {
'uploading': "Your asset got stuck during upload. Probably, your file was too large "
"or your connection too slow or interrupting. If you have repeated issues, "
"please contact us and let us know, it might be a bug",
'uploaded': "Your asset uploaded successfully. Yay! If it's public, "
"it's awaiting validation. If it's private, use it",
'on_hold': "Your asset needs some (usually smaller) fixes, "
"so we can make it public for everybody."
" Please check your email to see the feedback "
"that we send to every creator personally",
'rejected': "The asset has serious quality issues, " \
"and it's probable that it might be good to start " \
"all over again or try with something simpler. " \
"You also get personal feedback into your e-mail, " \
"since we believe that together, we can all learn " \
"to become awesome 3D artists",
'deleted': "You deleted this asset",
'validated': "Your asset passed our validation process, "
"and is now available to BlenderKit users"
}
self.draw_property(box,
'Verification:',
self.asset_data['verificationStatus'],
icon_value=icon.icon_id,
url="https://www.blenderkit.com/docs/validation-status/",
tooltip=verification_status_tooltips[self.asset_data['verificationStatus']]
)
# resolution/s
# fs = self.asset_data['files']
#
# if fs and len(fs) > 2:
# 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, 'Resolutions:', resolutions)
resolution = utils.get_param(self.asset_data, 'textureResolutionMax')
if resolution is not None:
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')
self.draw_asset_parameter(box, key='designer', pretext='Designer')
self.draw_asset_parameter(box, key='manufacturer', pretext='Manufacturer') # TODO make them clickable!
self.draw_asset_parameter(box, key='designCollection', pretext='Collection')
self.draw_asset_parameter(box, key='designVariant', pretext='Variant')
self.draw_asset_parameter(box, key='designYear', pretext='Design year')
self.draw_asset_parameter(box, key='faceCount', pretext='Face count')
# self.draw_asset_parameter(box, key='thumbnailScale', pretext='Preview scale')
# self.draw_asset_parameter(box, key='purePbr', pretext='Pure PBR')
# self.draw_asset_parameter(box, key='productionLevel', pretext='Readiness')
# self.draw_asset_parameter(box, key='condition', pretext='Condition')
self.draw_asset_parameter(box, key='material_style', pretext='Style')
self.draw_asset_parameter(box, key='model_style', pretext='Style')
if utils.get_param(self.asset_data, 'dimensionX'):
t = '%s×%s×%s m' % (utils.fmt_length(mparams['dimensionX']),
utils.fmt_length(mparams['dimensionY']),
utils.fmt_length(mparams['dimensionZ']))
self.draw_property(box, 'Size:', t)
# Tags section
# row = box.row()
# letters_on_row = 0
# max_on_row = width / 10
# for tag in self.asset_data['tags']:
# if tag in ('manifold', 'uv', 'non-manifold'):
# # these are sometimes accidentally stored in the lib
# continue
#
# # row.emboss='NONE'
# # we need to split wisely
# remaining_row = (max_on_row - letters_on_row) / max_on_row
# split_factor = (len(tag) / max_on_row) / remaining_row
# row = row.split(factor=split_factor)
# letters_on_row += len(tag)
# if letters_on_row > max_on_row:
# letters_on_row = len(tag)
# row = box.row()
# remaining_row = (max_on_row - letters_on_row) / max_on_row
# split_factor = (len(tag) / max_on_row) / remaining_row
# row = row.split(factor=split_factor)
#
# op = row.operator('wm')
# op = row.operator('view3d.blenderkit_search', text=tag)
# op.tooltip = f'Search items with tag {tag}'
# # build search string from description and tags:
# op.keywords = f'+tags:{tag}'
# self.draw_property(box, 'Tags', self.asset_data['tags']) #TODO make them clickable!
# Free/Full plan or private Access
plans_tooltip = 'BlenderKit has 2 plans:\n' \
' * Free plan - more than 50% of all assets\n' \
' * Full plan - unlimited access to everything\n' \
'Click to go to subscriptions page'
plans_link = 'https://www.blenderkit.com/plans/pricing/'
if self.asset_data['isPrivate']:
t = 'Private'
self.draw_property(box, 'Access:', t, icon='LOCKED')
elif self.asset_data['isFree']:
t = 'Free plan'
icon = pcoll['free']
self.draw_property(box, 'Access:', t,
icon_value=icon.icon_id,
tooltip=plans_tooltip,
url=plans_link)
else:
t = 'Full plan'
icon = pcoll['full']
self.draw_property(box, 'Access:', t,
icon_value=icon.icon_id,
tooltip=plans_tooltip,
url=plans_link)
def draw_author_area(self, context, layout, width=330):
self.draw_author(context, layout, width=width)
def draw_author(self, context, layout, width=330):
image_split = 0.25
text_width = width
authors = bpy.context.window_manager['bkit authors']
a = authors.get(self.asset_data['author']['id'])
if a is not None: # or a is '' or (a.get('gravatarHash') is not None and a.get('gravatarImg') is None):
row = layout.row()
author_box = row.box()
author_box.scale_y = 0.6 # get text lines closer to each other
author_box.label(text='Author') # just one extra line to give spacing
if hasattr(self, 'gimg'):
author_left = author_box.split(factor=0.25)
author_left.template_icon(icon_value=self.gimg.preview.icon_id, scale=7)
text_area = author_left.split()
text_width = int(text_width * (1 - image_split))
else:
text_area = author_box
author_right = text_area.column()
row = author_right.row()
col = row.column()
utils.label_multiline(col, text=a['tooltip'], width=text_width)
# check if author didn't fill any data about himself and prompt him if that's the case
if upload.user_is_owner(asset_data=self.asset_data) and a.get('aboutMe') is not None and len(
a.get('aboutMe', '')) == 0:
row = col.row()
row.enabled = False
row.label(text='Please introduce yourself to the community!')
op = col.operator('wm.blenderkit_url', text='Edit your profile')
op.url = 'https://www.blenderkit.com/profile'
op.tooltip = 'Edit your profile on BlenderKit webpage'
button_row = author_box.row()
button_row.scale_y = 2.0
if a.get('aboutMeUrl') is not None:
url = a['aboutMeUrl']
text = url
if len(url) > 25:
text = url[:25] + '...'
else:
url = paths.get_author_gallery_url(a['id'])
text = "Open Author's Profile"
op = button_row.operator('wm.url_open', text=text)
op.url = url
op = button_row.operator('view3d.blenderkit_search', text="Find Assets By Author")
op.keywords = ''
op.author_id = self.asset_data['author']['id']
def draw_thumbnail_box(self, layout):
layout.emboss = 'NORMAL'
box_thumbnail = layout.box()
box_thumbnail.scale_y = .4
box_thumbnail.template_icon(icon_value=self.img.preview.icon_id, scale=34.0)
# row = box_thumbnail.row()
# row.scale_y = 3
# op = row.operator('view3d.asset_drag_drop', text='Drag & Drop from here', depress=True)
row = box_thumbnail.row()
row.alignment = 'EXPAND'
# display_ratings = can_display_ratings(self.asset_data)
rc = self.asset_data.get('ratingsCount')
show_rating_threshold = 0
show_rating_prompt_threshold = 5
if rc:
rcount = min(rc['quality'], rc['workingHours'])
else:
rcount = 0
if rcount >= show_rating_threshold or upload.can_edit_asset(asset_data=self.asset_data):
s = numeric_to_str(self.asset_data['score'])
q = numeric_to_str(self.asset_data['ratingsAverage'].get('quality'))
c = numeric_to_str(self.asset_data['ratingsAverage'].get('workingHours'))
else:
s = '-'
q = '-'
c = '-'
pcoll = icons.icon_collections["main"]
row.emboss = 'NONE'
op = row.operator('wm.blenderkit_tooltip', text=str(s), icon_value=pcoll['trophy'].icon_id)
op.tooltip = 'Asset score calculated from averaged user ratings. \n\n' \
'Score = quality × complexity × 10*\n\n *Happiness multiplier'
row.label(text=' ')
tooltip_extension = f'.\n\nRatings results are shown for assets with more than {show_rating_threshold} ratings'
op = row.operator('wm.blenderkit_tooltip', text=str(q), icon='SOLO_ON')
op.tooltip = f"Quality, average from {rc['quality']} ratings" \
f"{tooltip_extension if rcount <= show_rating_threshold else ''}"
row.label(text=' ')
op = row.operator('wm.blenderkit_tooltip', text=str(c), icon_value=pcoll['dumbbell'].icon_id)
op.tooltip = f"Complexity, average from {rc['workingHours']} ratings" \
f"{tooltip_extension if rcount <= show_rating_threshold else ''}"
if rcount <= show_rating_prompt_threshold:
box_thumbnail.alert = True
box_thumbnail.label(text=f"")
box_thumbnail.label(text=f"This asset has only {rcount} rating{'' if rcount == 1 else 's'} , please rate.")
# box_thumbnail.label(text=f"Please rate this asset.")
def draw_menu_desc_author(self, context, layout, width=330):
box = layout.column()
box.emboss = 'NORMAL'
# left - tooltip & params
row = box.row()
split_factor = 0.7
split_left_left = row.split(factor=split_factor)
self.draw_properties(split_left_left, width=int(width * split_factor))
# right - menu
col1 = split_left_left.split()
self.draw_menu(context, col1)
# author
self.draw_author_area(context, box, width=width)
def draw(self, context):
ui_props = context.scene.blenderkitUI
sr = bpy.context.window_manager['search results']
asset_data = sr[ui_props.active_index]
self.asset_data = asset_data
layout = self.layout
row = layout.row()
split = row.split(factor=0.2)
col = split.column()
op = col.operator('view3d.asset_drag_drop')
op.asset_search_index = ui_props.active_index
draw_asset_context_menu(col, context, asset_data, from_panel=False)
split = split.split(factor=0.5)
col1 = split.column()
box = col1.box()
utils.label_multiline(box, asset_data['tooltip'], width=300)
# top draggabe bar with name of the asset
top_row = layout.row()
top_drag_bar = top_row.box()
top_drag_bar.label(text=asset_data['displayName'])
col2 = split.column()
# left side
row = layout.row(align=True)
pcoll = icons.icon_collections["main"]
my_icon = pcoll['test']
col2.template_icon(icon_value=my_icon.icon_id, scale=20.0)
# col2.template_icon(icon_value=self.img.preview.icon_id, scale=10.0)
box2 = col2.box()
split_ratio = 0.5
split_left = row.split(factor=0.5)
self.draw_thumbnail_box(split_left)
# draw_ratings(box2, context, asset_data)
box2.label(text='Ratings')
# print(tp, dir(tp))
# if not hasattr(self, 'first_draw'):# try to redraw because of template preview which needs update
# for region in context.area.regions:
# region.tag_redraw()
# self.first_draw = True
# right split
split_right = split_left.split()
self.draw_menu_desc_author(context, split_right, width=int(self.width * split_ratio))
ratings_box = layout.box()
ratings_box.scale_y = 0.7
ratings_box.label(text='Rate asset quality:')
ratings.draw_ratings_menu(self, context, ratings_box)
tip_box = layout.box()
tip_box.label(text=self.tip)
def execute(self, context):
print('execute')
return {'FINISHED'}
def invoke(self, context, event):
wm = context.window_manager
ui_props = context.scene.blenderkitUI
ui_props.draw_tooltip = False
sr = bpy.context.window_manager['search results']
asset_data = sr[ui_props.active_index]
self.img = ui.get_large_thumbnail_image(asset_data)
self.asset_type = asset_data['assetType']
self.asset_id = asset_data['id']
# self.tex = utils.get_hidden_texture(self.img)
# self.tex.update_tag()
authors = bpy.context.window_manager['bkit authors']
a = authors.get(asset_data['author']['id'])
if a.get('gravatarImg') is not None:
self.gimg = utils.get_hidden_image(a['gravatarImg'], a['gravatarHash'])
bl_label = asset_data['name']
return wm.invoke_props_dialog(self, width=700)
self.tip = search.get_random_tip()
self.tip = self.tip.replace('\n', '')
return wm.invoke_popup(self, width=self.width)
class OBJECT_MT_blenderkit_login_menu(bpy.types.Menu):

View File

@ -567,6 +567,14 @@ def update_free_full(self, context):
" based on our fair share system. " \
"Part of subscription is sent to artists based on usage by paying users.")
def user_is_owner(asset_data=None):
'''Checks if the current logged in user is owner of the asset'''
profile = bpy.context.window_manager.get('bkit profile')
if profile is None:
return False
if int(asset_data['author']['id']) == int(profile['user']['id']):
return True
return False
def can_edit_asset(active_index=-1, asset_data=None):
if active_index < 0 and not asset_data:
@ -580,13 +588,13 @@ def can_edit_asset(active_index=-1, asset_data=None):
sr = bpy.context.window_manager['search results']
asset_data = dict(sr[active_index])
# print(profile, asset_data)
if asset_data['author']['id'] == profile['user']['id']:
if int(asset_data['author']['id']) == int(profile['user']['id']):
return True
return False
class FastMetadata(bpy.types.Operator):
"""Fast change of the category of object directly in asset bar."""
"""Edit metadata of the asset"""
bl_idname = "wm.blenderkit_fast_metadata"
bl_label = "Update metadata"
bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}
@ -731,6 +739,7 @@ class FastMetadata(bpy.types.Operator):
active_asset = utils.get_active_asset_by_type(asset_type = self.asset_type)
asset_data = active_asset.get('asset_data')
print('can edit asset?', can_edit_asset(asset_data=asset_data))
if not can_edit_asset(asset_data=asset_data):
return {'CANCELLED'}
self.asset_id = asset_data['id']
@ -1298,7 +1307,7 @@ class AssetDebugPrint(Operator):
class AssetVerificationStatusChange(Operator):
"""Change verification status"""
bl_idname = "object.blenderkit_change_status"
bl_description = "Change asset ststus"
bl_description = "Change asset status"
bl_label = "Change verification status"
bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}

View File

@ -337,6 +337,12 @@ def get_hidden_texture(name, force_reload=False):
return t
def img_to_preview(img):
img.preview.image_size = (img.size[0], img.size[1])
img.preview.image_pixels_float = img.pixels[:]
# img.preview.icon_size = (img.size[0], img.size[1])
# img.preview.icon_pixels_float = img.pixels[:]
def get_hidden_image(tpath, bdata_name, force_reload=False, colorspace='sRGB'):
if bdata_name[0] == '.':
hidden_name = bdata_name
@ -355,6 +361,7 @@ def get_hidden_image(tpath, bdata_name, force_reload=False, colorspace='sRGB'):
if img is None:
img = bpy.data.images.load(tpath)
img_to_preview(img)
img.name = hidden_name
else:
if img.filepath != tpath:
@ -363,13 +370,16 @@ def get_hidden_image(tpath, bdata_name, force_reload=False, colorspace='sRGB'):
img.filepath = tpath
img.reload()
img_to_preview(img)
image_utils.set_colorspace(img, colorspace)
elif force_reload:
if img.packed_file is not None:
img.unpack(method='USE_ORIGINAL')
img.reload()
img_to_preview(img)
image_utils.set_colorspace(img, colorspace)
return img
@ -690,6 +700,9 @@ def name_update(props):
# Here we actually rename assets datablocks, but don't do that with HDR's and possibly with others
asset.name = fname
def fmt_length(prop):
prop = str(round(prop, 2))
return prop
def get_param(asset_data, parameter_name, default = None):
if not asset_data.get('parameters'):
@ -773,7 +786,12 @@ def profile_is_validator():
def guard_from_crash():
'''Blender tends to crash when trying to run some functions with the addon going through unregistration process.'''
'''
Blender tends to crash when trying to run some functions
with the addon going through unregistration process.
This function is used in these functions (like draw callbacks)
so these don't run during unregistration.
'''
if bpy.context.preferences.addons.get('blenderkit') is None:
return False;
if bpy.context.preferences.addons['blenderkit'].preferences is None:

View File

@ -22,7 +22,7 @@ bl_info = {
"name": "Collection Manager",
"description": "Manage collections and their objects",
"author": "Ryan Inch",
"version": (2, 21, 1),
"version": (2, 21, 3),
"blender": (2, 80, 0),
"location": "View3D - Object Mode (Shortcut - M)",
"warning": '', # used for warning icon and text in addons panel

View File

@ -255,7 +255,7 @@ class QCDSlots():
x += 1
if self.length() > 20:
if self.length() == 20:
break
else:
@ -279,7 +279,7 @@ class QCDSlots():
x += 1
if self.length() > 20:
if self.length() == 20:
break

View File

@ -143,8 +143,13 @@ class CollectionManager(Operator):
right_sec = button_row_1.row()
right_sec.alignment = 'RIGHT'
right_sec.menu("VIEW3D_MT_CM_specials_menu")
right_sec.popover(panel="COLLECTIONMANAGER_PT_display_options",
specials_menu = right_sec.row()
specials_menu.alignment = 'RIGHT'
specials_menu.menu("VIEW3D_MT_CM_specials_menu")
display_options = right_sec.row()
display_options.alignment = 'RIGHT'
display_options.popover(panel="COLLECTIONMANAGER_PT_display_options",
text="", icon='FILTER')
mc_box = layout.box()
@ -449,12 +454,16 @@ class CollectionManager(Operator):
if cm.in_phantom_mode:
view.enabled = False
if prefs.enable_qcd:
renum_sec.enabled = False
undo_sec.enabled = False
specials_menu.enabled = False
c_icon.enabled = False
row_setcol.enabled = False
addcollec_row.enabled = False
button_row_2.enabled = False
def execute(self, context):

View File

@ -61,10 +61,10 @@ class SceneProperties(PropertyGroup):
name="Format",
description="Format type to export to",
items=(
('STL', "STL", ""),
('PLY', "PLY", ""),
('X3D', "X3D", ""),
('OBJ', "OBJ", ""),
('PLY', "PLY", ""),
('STL', "STL", ""),
('X3D', "X3D", ""),
),
default='STL',
)
@ -78,6 +78,13 @@ class SceneProperties(PropertyGroup):
description="Apply scene scale setting on export",
default=False,
)
use_data_layers: BoolProperty(
name="Data Layers",
description=(
"Export normals, UVs, vertex colors and materials for formats that support it "
"significantly increasing filesize"
),
)
export_path: StringProperty(
name="Export Directory",
description="Path to directory where the files are created",

View File

@ -81,6 +81,7 @@ def write_mesh(context, report_cb):
path_mode = 'COPY' if print_3d.use_export_texture else 'AUTO'
export_path = bpy.path.abspath(print_3d.export_path)
obj = layer.objects.active
export_data_layers = print_3d.use_data_layers
# Create name 'export_path/blendname-objname'
# add the filename component
@ -129,9 +130,13 @@ def write_mesh(context, report_cb):
filepath = bpy.path.ensure_ext(filepath, ".ply")
ret = bpy.ops.export_mesh.ply(
filepath=filepath,
use_ascii=False,
use_mesh_modifiers=True,
use_selection=True,
global_scale=global_scale,
use_normals=export_data_layers,
use_uv_coords=export_data_layers,
use_colors=export_data_layers,
)
elif export_format == 'X3D':
addon_ensure("io_scene_x3d")
@ -140,8 +145,9 @@ def write_mesh(context, report_cb):
filepath=filepath,
use_mesh_modifiers=True,
use_selection=True,
path_mode=path_mode,
global_scale=global_scale,
path_mode=path_mode,
use_normals=export_data_layers,
)
elif export_format == 'OBJ':
addon_ensure("io_scene_obj")
@ -150,16 +156,18 @@ def write_mesh(context, report_cb):
filepath=filepath,
use_mesh_modifiers=True,
use_selection=True,
path_mode=path_mode,
global_scale=global_scale,
path_mode=path_mode,
use_normals=export_data_layers,
use_uvs=export_data_layers,
use_materials=export_data_layers,
)
else:
assert 0
# for formats that don't support images
if export_format in {'STL', 'PLY'}:
if path_mode == 'COPY':
image_copy_guess(filepath, context.selected_objects)
if path_mode == 'COPY' and export_format in {'STL', 'PLY'}:
image_copy_guess(filepath, context.selected_objects)
if 'FINISHED' in ret:
if report_cb is not None:

View File

@ -143,10 +143,13 @@ class VIEW3D_PT_print3d_export(View3DPrintPanel, Panel):
print_3d = context.scene.print_3d
layout.prop(print_3d, "export_path", text="")
layout.prop(print_3d, "export_format")
col = layout.column()
col.prop(print_3d, "use_apply_scale")
col.prop(print_3d, "use_export_texture")
sub = col.column()
sub.active = print_3d.export_format != "STL"
sub.prop(print_3d, "use_data_layers")
layout.prop(print_3d, "export_format")
layout.operator("mesh.print3d_export", text="Export", icon='EXPORT')

View File

@ -682,4 +682,4 @@ def set_bone_widget_transform(obj, bone_name, transform_bone, use_size=True, sca
bone.custom_shape_transform = None
bone.use_custom_shape_bone_size = use_size
bone.custom_shape_scale = scale
bone.custom_shape_scale_xyz = (scale, scale, scale)

View File

@ -23,7 +23,7 @@ import math
import inspect
import functools
from mathutils import Matrix
from mathutils import Matrix, Vector, Euler
from .errors import MetarigError
from .collections import ensure_widget_collection
@ -42,7 +42,10 @@ def obj_to_bone(obj, rig, bone_name, bone_transform_name=None):
raise MetarigError("obj_to_bone(): does not work while in edit mode")
bone = rig.pose.bones[bone_name]
scale = bone.custom_shape_scale
loc = bone.custom_shape_translation
rot = bone.custom_shape_rotation_euler
scale = Vector(bone.custom_shape_scale_xyz)
if bone.use_custom_shape_bone_size:
scale *= bone.length
@ -52,8 +55,10 @@ def obj_to_bone(obj, rig, bone_name, bone_transform_name=None):
elif bone.custom_shape_transform:
bone = bone.custom_shape_transform
shape_mat = Matrix.Translation(loc) @ (Euler(rot).to_matrix() @ Matrix.Diagonal(scale)).to_4x4()
obj.rotation_mode = 'XYZ'
obj.matrix_basis = rig.matrix_world @ bone.bone.matrix_local @ Matrix.Scale(scale, 4)
obj.matrix_basis = rig.matrix_world @ bone.bone.matrix_local @ shape_mat
def create_widget(rig, bone_name, bone_transform_name=None, *, widget_name=None, widget_force_new=False):

View File

@ -177,7 +177,9 @@ def pVisScaExec(bone, active, context):
def pDrwExec(bone, active, context):
bone.custom_shape = active.custom_shape
bone.use_custom_shape_bone_size = active.use_custom_shape_bone_size
bone.custom_shape_scale = active.custom_shape_scale
bone.custom_shape_translation = active.custom_shape_translation
bone.custom_shape_rotation_euler = active.custom_shape_rotation_euler
bone.custom_shape_scale_xyz = active.custom_shape_scale_xyz
bone.bone.show_wire = active.bone.show_wire