Files
blender-portable-repo/extensions/user_default/blenderkit/ratings.py
T
2026-03-17 15:25:32 -06:00

399 lines
13 KiB
Python

# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
import logging
import time
import bpy
from bpy.props import StringProperty
from bpy.types import Gizmo, GizmoGroup, Operator
from mathutils import Matrix
from . import (
client_lib,
datas,
global_vars,
icons,
ratings_utils,
search,
ui,
ui_panels,
utils,
)
bk_logger = logging.getLogger(__name__)
def get_assets_for_rating():
"""Get assets from scene that could/should be rated by the user. TODO: this is only a draft"""
assets = []
for ob in bpy.context.scene.objects:
if should_be_rated(ob):
assets.append(ob)
for m in bpy.data.materials:
if m.get("asset_data"):
assets.append(m)
for b in bpy.data.brushes:
if b.get("asset_data"):
assets.append(b)
return assets
asset_types = (
("MODEL", "Model", "set of objects"),
("PRINTABLE", "Printable", "3D printable model"),
("SCENE", "Scene", "scene"),
("HDR", "HDR", "hdr"),
("MATERIAL", "Material", "any .blend Material"),
("TEXTURE", "Texture", "a texture, or texture set"),
("BRUSH", "Brush", "brush, can be any type of blender brush"),
("ADDON", "Addon", "addnon"),
)
def draw_ratings_menu(self, context, layout):
pcoll = icons.icon_collections["main"]
if not utils.user_logged_in():
user_preferences = bpy.context.preferences.addons[__package__].preferences
if user_preferences.login_attempt:
ui_panels.draw_login_progress(layout)
else:
layout.operator_context = "EXEC_DEFAULT"
layout.operator(
"wm.blenderkit_login",
text="Login to Rate and Comment assets",
icon="URL",
).signup = False
return
col = layout.column()
# layout.template_icon_view(bkit_ratings, property, show_labels=False, scale=6.0, scale_popup=5.0)
row = col.row()
if self.asset_data.get("canDownload") is not True:
row.label(text="Asset in Full Plan. Subscribe to rate it.", icon="SOLO_ON")
return
profile_name = ""
profile = global_vars.BKIT_PROFILE
if profile and len(profile.firstName) > 0:
profile_name = " " + profile.firstName
row.label(text="Rate Quality:", icon="SOLO_ON")
# row = col.row()
# row.label(text='Please help the community by rating quality:')
row = col.row()
row.prop(self, "rating_quality_ui", expand=True, icon_only=True, emboss=False)
if self.rating_quality > 0:
row.label(text=f" Thanks{profile_name}!", icon="FUND")
col.separator()
col.separator()
row = col.row()
row.label(text="Rate Complexity:", icon_value=pcoll["dumbbell"].icon_id)
row = col.row()
row.label(text=f"How many hours did this {self.asset_type} save you?")
if utils.profile_is_validator():
row = col.row()
row.prop(self, "rating_work_hours")
row = col.row()
row.prop(self, "rating_work_hours_ui", expand=True, icon_only=False, emboss=True)
if self.rating_work_hours > 100:
utils.label_multiline(
col,
text=f"\nThat's huge! please be sure to give such rating only to godly {self.asset_type}s.\n",
width=300,
)
elif float(self.rating_work_hours) > 18:
col.separator()
utils.label_multiline(
col,
text=f"\nThat's a lot! please be sure to give such rating only to amazing {self.asset_type}s.\n",
width=300,
)
if self.rating_work_hours > 0:
row = col.row()
row.label(text=f"Thanks{profile_name}, you are amazing!", icon="FUND")
class FastRateMenu(Operator, ratings_utils.RatingProperties):
"""Rating of the assets, also directly from the asset bar - without need to download assets"""
bl_idname = "wm.blenderkit_menu_rating_upload"
bl_label = "Ratings"
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
@classmethod
def poll(cls, context):
return True
def draw(self, context):
# when rating gets recieved while the window is already open, we need to prefill.
self.prefill_ratings()
layout = self.layout
layout.label(text=f"Rating of the {self.asset_type}: {self.asset_data['name']}")
draw_ratings_menu(self, context, layout)
layout.template_icon(icon_value=self.img.preview.icon_id, scale=12)
def execute(self, context):
ui_props = bpy.context.window_manager.blenderkitUI
# get asset id
if ui_props.active_index > -1:
sr = search.get_search_results()
self.asset_data = dict(sr[ui_props.active_index])
self.asset_id = self.asset_data["id"]
self.asset_type = self.asset_data["assetType"]
else:
if bpy.context.view_layer.objects.active is not None:
ob = utils.get_active_model()
ad = ob.get("asset_data")
if ad:
self.asset_data = ad
self.asset_id = self.asset_data["id"]
self.asset_type = self.asset_data["assetType"]
self.asset = ob
if self.asset_id == "":
return {"CANCELLED"}
wm = context.window_manager
self.img = ui.get_large_thumbnail_image(self.asset_data)
utils.img_to_preview(self.img, copy_original=True)
ratings_utils.ensure_rating(self.asset_id)
self.prefill_ratings()
# Update last popup activity time to prevent shortcut interference
from . import ui_panels
ui_panels.last_time_dropdown_active = time.time()
if self.asset_type in ("model", "scene"):
# spawn a wider one for validators for the enum buttons
return wm.invoke_popup(self, width=400)
else:
return wm.invoke_popup(self, width=250)
class SetBookmark(bpy.types.Operator):
"""Add or remove bookmarking of the asset.\nShortcut: hover over asset in the asset bar and press 'B'."""
bl_idname = "wm.blenderkit_bookmark_asset"
bl_label = "BlenderKit bookmark assets"
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
asset_id: StringProperty( # type: ignore[valid-type]
name="Asset Base Id",
description="Unique id of the asset (hidden)",
default="",
options={"SKIP_SAVE"},
)
# bookmark: bpy.props.BoolProperty(
# name="bookmark",
# description="Pass current state of bookmark, gets inverted",
# default=True)
@classmethod
def poll(cls, context):
return True
def execute(self, context):
rating = ratings_utils.get_rating_local(self.asset_id)
if rating is None:
rating = datas.AssetRating()
if rating.bookmarks == 1:
bookmark_value = 0
else:
bookmark_value = 1
ratings_utils.store_rating_local(
self.asset_id, rating_type="bookmarks", value=bookmark_value
)
client_lib.send_rating(self.asset_id, "bookmarks", str(bookmark_value))
return {"FINISHED"}
## NOT USED ANYMORE
# def rating_menu_draw(self, context):
# layout = self.layout
# ui_props = context.window_manager.blenderkitUI
# sr = search.get_search_results()
# asset_search_index = ui_props.active_index
# if asset_search_index > -1:
# asset_data = dict(sr["results"][asset_search_index])
# col = layout.column()
# layout.label(text="Admin rating Tools:")
# col.operator_context = "INVOKE_DEFAULT"
# op = col.operator("wm.blenderkit_menu_rating_upload", text="Add Rating")
# op.asset_id = asset_data["id"]
# op.asset_name = asset_data["name"]
# op.asset_type = asset_data["assetType"]
# Coordinates (each one is a triangle).
custom_shape_verts = (
(0.1896940916776657, 0.2608509361743927, 0.0),
(0.2438376545906067, 0.09421423077583313, 0.0),
(0.2979812026023865, 0.2608509361743927, 0.0),
(0.1896940916776657, 0.2608509361743927, 0.0),
(0.052547797560691833, 0.2484826147556305, 0.0),
(0.15623150765895844, 0.1578637957572937, 0.0),
(0.15623150765895844, 0.1578637957572937, 0.0),
(0.12561391294002533, 0.023607879877090454, 0.0),
(0.2438376545906067, 0.09421423077583313, 0.0),
(0.2438376545906067, 0.09421423077583313, 0.0),
(0.36206138134002686, 0.023607879877090454, 0.0),
(0.33144378662109375, 0.1578637957572937, 0.0),
(0.33144378662109375, 0.1578637957572937, 0.0),
(0.4351276159286499, 0.2484826147556305, 0.0),
(0.2979812026023865, 0.2608509361743927, 0.0),
(0.2979812026023865, 0.2608509361743927, 0.0),
(0.2438376396894455, 0.3874630033969879, 0.0),
(0.1896940916776657, 0.2608509361743927, 0.0),
(0.1896940916776657, 0.2608509361743927, 0.0),
(0.15623150765895844, 0.1578637957572937, 0.0),
(0.2438376545906067, 0.09421423077583313, 0.0),
(0.2438376545906067, 0.09421423077583313, 0.0),
(0.33144378662109375, 0.1578637957572937, 0.0),
(0.2979812026023865, 0.2608509361743927, 0.0),
)
class RatingStarWidget(Gizmo):
bl_idname = "VIEW3D_GT_custom_shape_widget"
__slots__ = (
"custom_shape",
"init_mouse_y",
"init_value",
)
def _update_draw_matrix(self):
R = bpy.context.region_data.view_rotation.to_matrix().to_4x4()
loc, _, scale = self.matrix_basis.decompose()
self.matrix_basis = Matrix.Translation(loc) @ R @ Matrix.Diagonal(scale.to_4d())
def draw(self, context):
self._update_draw_matrix()
self.draw_custom_shape(self.custom_shape)
def draw_select(self, context, select_id):
self._update_draw_matrix()
self.draw_custom_shape(self.custom_shape, select_id=select_id)
def setup(self):
if not hasattr(self, "custom_shape"):
self.custom_shape = self.new_custom_shape("TRIS", custom_shape_verts)
def invoke(self, context, event):
return {"RUNNING_MODAL"}
def exit(self, context, cancel):
pass
def modal(self, context, event, tweak):
return {"FINISHED"}
def should_be_rated(ob) -> bool:
ad = ob.get("asset_data")
if ad is None:
return False
rating = ratings_utils.get_rating_local(ad["id"])
if rating is None:
# is None would work too, but would show rating option and then hide it when the assets are already rated
return True
return False
class RatingStarWidgetGroup(GizmoGroup):
bl_idname = "OBJECT_GGT_light_test"
bl_label = "Test Light Widget"
bl_space_type = "VIEW_3D"
bl_region_type = "WINDOW"
bl_options = {"3D", "PERSISTENT"}
@classmethod
def poll(cls, context):
if not utils.profile_is_validator():
return False
if bpy.context.view_layer.objects.active is not None:
ob = utils.get_active_model()
return should_be_rated(ob)
return False
def setup(self, context):
ob = utils.get_active_model()
gz = self.gizmos.new(RatingStarWidget.bl_idname)
props = gz.target_set_operator("wm.blenderkit_menu_rating_upload")
props.asset_id = ob["asset_data"]["assetBaseId"]
gz.color = 0.5, 0.5, 0.0
gz.alpha = 0.5
gz.color_highlight = 1.0, 1.0, 1.0
gz.alpha_highlight = 0.5
gz.scale_basis = 1
gz.use_draw_modal = True
self.energy_gizmo = gz
def refresh(self, context):
ob = utils.get_active_model()
gz = self.energy_gizmo
R = bpy.context.region_data.view_rotation.to_matrix().to_4x4()
loc, _, _ = ob.matrix_world.decompose()
_, _, scale = gz.matrix_basis.decompose()
gz.matrix_basis = Matrix.Translation(loc) @ R @ Matrix.Diagonal(scale.to_4d())
classes = (
FastRateMenu,
SetBookmark,
RatingStarWidget,
RatingStarWidgetGroup,
ratings_utils.RatingProperties,
# ratings_utils.RatingPropsCollection,
)
def register_ratings():
for cls in classes:
bpy.utils.register_class(cls)
def unregister_ratings():
for cls in classes:
bpy.utils.unregister_class(cls)