432 lines
15 KiB
Python
432 lines
15 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
|
|
from typing import Optional, Union
|
|
|
|
# mainly update functions and callbacks for ratings properties, here to avoid circular imports.
|
|
import bpy
|
|
from bpy.props import (
|
|
BoolProperty,
|
|
EnumProperty,
|
|
FloatProperty,
|
|
IntProperty,
|
|
StringProperty,
|
|
)
|
|
from bpy.types import Context, PropertyGroup
|
|
|
|
from . import (
|
|
client_lib,
|
|
client_tasks,
|
|
datas,
|
|
global_vars,
|
|
icons,
|
|
reports,
|
|
tasks_queue,
|
|
utils,
|
|
)
|
|
|
|
|
|
bk_logger = logging.getLogger(__name__)
|
|
|
|
|
|
def handle_get_rating_task(task: client_tasks.Task):
|
|
"""Handle incomming get_rating task by saving the results into global_vars."""
|
|
if task.status == "created":
|
|
return
|
|
if task.status == "error":
|
|
return bk_logger.warning(f"{task.task_type} task failed: {task.message}")
|
|
|
|
asset_id = task.data["asset_id"]
|
|
ratings = task.result["results"]
|
|
if len(ratings) == 0:
|
|
store_rating_local(asset_id, "quality", None)
|
|
store_rating_local(asset_id, "working_hours", None)
|
|
return
|
|
|
|
for rating in ratings:
|
|
store_rating_local(asset_id, rating["ratingType"], rating["score"])
|
|
|
|
|
|
def handle_get_ratings_task(task: client_tasks.Task):
|
|
"""Handle incomming get_ratings task. This is a special task used only by validators which fetches the ratings
|
|
in big batch right after the search results come into the Client. This is used only to signal problems in the
|
|
Goroutine which fetches the ratings. The individual ratings are then sent as normal 'get_rating' tasks.
|
|
"""
|
|
if task.status == "error": # only reason this task type exists right now
|
|
return bk_logger.warning(f"{task.task_type} task failed: {task.message}")
|
|
|
|
|
|
def handle_get_bookmarks_task(task: client_tasks.Task):
|
|
"""Handle incomming get_bookmarks task by saving the results into global_vars.
|
|
This is different from standard ratings - the results come from elastic search API
|
|
instead of ratings API.
|
|
"""
|
|
if task.status == "created":
|
|
return
|
|
if task.status == "error":
|
|
bk_logger.warning(f"Could not load bookmarks: {task.message}")
|
|
return
|
|
|
|
for asset in task.result["results"]:
|
|
store_rating_local(asset["id"], "bookmarks", 1)
|
|
bk_logger.info("Bookmarks loaded")
|
|
|
|
|
|
def handle_send_rating_task(task: client_tasks.Task):
|
|
"""Handle send rating task."""
|
|
if task.status == "created":
|
|
return
|
|
if task.status == "error":
|
|
return reports.add_report(
|
|
task.message, type="ERROR", details=task.message_detailed
|
|
)
|
|
if task.status == "finished":
|
|
if utils.profile_is_validator():
|
|
return reports.add_report(task.message, type="VALIDATOR")
|
|
|
|
|
|
def store_rating_local(
|
|
asset_id: str, rating_type: str = "quality", value: Optional[int] = None
|
|
):
|
|
"""Store the rating locally in the global_vars.
|
|
- rating_type can be: "quality", "working_hours", "bookmarks"
|
|
- value set None to create empty rating and prevent add-on from fetching it again next time
|
|
"""
|
|
allowed_rating_types = ["quality", "working_hours", "bookmarks"]
|
|
if rating_type not in allowed_rating_types:
|
|
raise ValueError(f"rating_type must be one of {allowed_rating_types}")
|
|
|
|
rating = global_vars.RATINGS.get(asset_id, datas.AssetRating())
|
|
rating.working_hours_fetched = True
|
|
rating.quality_fetched = True
|
|
setattr(rating, rating_type, value)
|
|
global_vars.RATINGS[asset_id] = rating
|
|
|
|
|
|
def get_rating_local(asset_id: str) -> Optional[datas.AssetRating]:
|
|
"""Get the rating locally from global_vars.RATINGS."""
|
|
return global_vars.RATINGS.get(asset_id)
|
|
|
|
|
|
def ensure_rating(asset_id: str):
|
|
"""Ensure the rating is available. First check if it is available in local cache. If it is not then download it from the server.
|
|
If the rating is present, we need to check if rating.quality_fetched and rating.working_hours_fetched are not False
|
|
because bookmarked assets will have rating created, but for them the quality and wh was not fetched (bookmarked are get from search
|
|
and these data does not contain quality and working_hours - and even bookmarked but that can be deduced from searching for bookmarked).
|
|
"""
|
|
rating = get_rating_local(asset_id)
|
|
if rating is None:
|
|
client_lib.get_rating(asset_id)
|
|
return
|
|
if not rating.quality_fetched or rating.working_hours_fetched:
|
|
client_lib.get_rating(asset_id)
|
|
|
|
|
|
def update_ratings_quality(self, context: Context):
|
|
if not (hasattr(self, "rating_quality")):
|
|
# first option is for rating of assets that are from scene
|
|
asset = self.id_data
|
|
bkit_ratings = asset.bkit_ratings
|
|
asset_id = asset["asset_data"]["id"]
|
|
else:
|
|
# this part is for operator rating:
|
|
bkit_ratings = self
|
|
asset_id = self.asset_id
|
|
|
|
local_rating = get_rating_local(self.asset_id)
|
|
if local_rating is None:
|
|
local_rating = datas.AssetRating(quality=0)
|
|
if local_rating.quality == self.rating_quality:
|
|
return store_rating_local(
|
|
asset_id, rating_type="quality", value=bkit_ratings.rating_quality
|
|
)
|
|
|
|
store_rating_local(
|
|
asset_id, rating_type="quality", value=bkit_ratings.rating_quality
|
|
)
|
|
if self.rating_quality_lock is True:
|
|
return
|
|
|
|
args = (asset_id, "quality", bkit_ratings.rating_quality)
|
|
tasks_queue.add_task((client_lib.send_rating, args), wait=0.5, only_last=True)
|
|
|
|
|
|
def update_ratings_work_hours(self, context: Context):
|
|
if not (hasattr(self, "rating_work_hours")):
|
|
# first option is for rating of assets that are from scene
|
|
asset = self.id_data
|
|
bkit_ratings = asset.bkit_ratings
|
|
asset_id = asset["asset_data"]["id"]
|
|
else:
|
|
# this part is for operator rating:
|
|
bkit_ratings = self
|
|
asset_id = self.asset_id
|
|
|
|
local_rating = get_rating_local(self.asset_id)
|
|
if local_rating is None: # rating was not available online
|
|
local_rating = datas.AssetRating(working_hours=0)
|
|
|
|
if local_rating.working_hours == self.rating_work_hours:
|
|
return store_rating_local(
|
|
asset_id, rating_type="working_hours", value=bkit_ratings.rating_work_hours
|
|
)
|
|
|
|
store_rating_local(
|
|
asset_id, rating_type="working_hours", value=bkit_ratings.rating_work_hours
|
|
)
|
|
if self.rating_work_hours_lock is True:
|
|
return
|
|
|
|
args = (asset_id, "working_hours", bkit_ratings.rating_work_hours)
|
|
tasks_queue.add_task((client_lib.send_rating, args), wait=0.5, only_last=True)
|
|
|
|
|
|
def update_quality_ui(self, context: Context):
|
|
"""Converts the _ui the enum into actual quality number."""
|
|
user_preferences = bpy.context.preferences.addons[__package__].preferences # type: ignore
|
|
api_key = user_preferences.api_key # type: ignore
|
|
# we need to check for matching value not to update twice/call the popup twice.
|
|
if api_key == "" and self.rating_quality != int(self.rating_quality_ui):
|
|
bpy.ops.wm.blenderkit_login( # type: ignore
|
|
"INVOKE_DEFAULT",
|
|
message="Please login/signup to rate assets. Clicking OK takes you to web login.",
|
|
)
|
|
return
|
|
|
|
self.rating_quality = int(self.rating_quality_ui)
|
|
|
|
|
|
def update_ratings_work_hours_ui(self, context: Context):
|
|
user_preferences = bpy.context.preferences.addons[__package__].preferences # type: ignore
|
|
api_key = user_preferences.api_key # type: ignore
|
|
if api_key == "" and self.rating_work_hours != float(self.rating_work_hours_ui):
|
|
bpy.ops.wm.blenderkit_login( # type: ignore
|
|
"INVOKE_DEFAULT",
|
|
message="Please login/signup to rate assets. Clicking OK takes you to web login.",
|
|
)
|
|
return
|
|
self.rating_work_hours = float(self.rating_work_hours_ui)
|
|
|
|
|
|
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, 11):
|
|
if a == 0:
|
|
icon = "REMOVE"
|
|
|
|
elif self.rating_quality < a:
|
|
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}", " ", "", icon, a))
|
|
return items
|
|
|
|
|
|
def wh_enum_callback(self, context):
|
|
"""Regenerates working hours enum."""
|
|
if self.asset_type in ("model", "scene", "printable", "nodegroup"):
|
|
possible_wh_values = [
|
|
0,
|
|
0.5,
|
|
1,
|
|
2,
|
|
3,
|
|
4,
|
|
5,
|
|
6,
|
|
8,
|
|
10,
|
|
15,
|
|
20,
|
|
30,
|
|
50,
|
|
100,
|
|
150,
|
|
200,
|
|
250,
|
|
]
|
|
elif self.asset_type == "hdr":
|
|
possible_wh_values = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
|
else: # for material, brush assets
|
|
possible_wh_values = [0, 0.2, 0.5, 1, 2, 3, 4, 5]
|
|
|
|
work_hours = self.rating_work_hours
|
|
if work_hours < 1:
|
|
work_hours = int(work_hours * 10) / 10
|
|
else:
|
|
work_hours = int(work_hours)
|
|
|
|
if work_hours not in possible_wh_values:
|
|
closest_index = 0
|
|
closest_diff = abs(possible_wh_values[0] - work_hours)
|
|
for i in range(1, len(possible_wh_values)):
|
|
diff = abs(possible_wh_values[i] - work_hours)
|
|
if diff < closest_diff:
|
|
closest_diff = diff
|
|
closest_index = i
|
|
possible_wh_values[closest_index] = work_hours
|
|
|
|
items = []
|
|
items.append(("0", " ", "", "REMOVE", 0))
|
|
pcoll = icons.icon_collections["main"]
|
|
|
|
for w in possible_wh_values:
|
|
if w > 0:
|
|
if w < 1:
|
|
icon_name = f"BK{int(w*10)/10}"
|
|
else:
|
|
icon_name = f"BK{int(w)}"
|
|
if icon_name not in pcoll:
|
|
icon_name = "bar_slider_up"
|
|
icon = pcoll[icon_name]
|
|
# index of the item(last value) is multiplied by 10 to get always integer values that aren't zero
|
|
items.append((f"{w}", " ", "", icon.icon_id, int(w * 10)))
|
|
|
|
return items
|
|
|
|
|
|
class RatingProperties(PropertyGroup):
|
|
message: StringProperty( # type: ignore
|
|
name="message",
|
|
description="message",
|
|
default="Rating asset",
|
|
options={"SKIP_SAVE"},
|
|
)
|
|
|
|
asset_id: StringProperty( # type: ignore
|
|
name="Asset Base Id",
|
|
description="Unique id of the asset (hidden)",
|
|
default="",
|
|
options={"SKIP_SAVE"},
|
|
)
|
|
|
|
asset_name: StringProperty( # type: ignore
|
|
name="Asset Name",
|
|
description="Name of the asset (hidden)",
|
|
default="",
|
|
options={"SKIP_SAVE"},
|
|
)
|
|
|
|
asset_type: StringProperty( # type: ignore
|
|
name="Asset type", description="asset type", default="", options={"SKIP_SAVE"}
|
|
)
|
|
|
|
### QUALITY RATING
|
|
rating_quality_lock: BoolProperty( # type: ignore
|
|
name="Quality Lock",
|
|
description="Quality is locked -> rating is not sent online",
|
|
default=False,
|
|
options={"SKIP_SAVE"},
|
|
)
|
|
|
|
rating_quality: IntProperty( # type: ignore
|
|
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( # type: ignore
|
|
name="Quality",
|
|
items=stars_enum_callback,
|
|
description="Rate the quality of the asset from 1 to 10 stars.\nShortcut: Hover over asset in the asset bar and press 'R' to show rating menu",
|
|
default=0,
|
|
update=update_quality_ui,
|
|
options={"SKIP_SAVE"},
|
|
)
|
|
|
|
### WORK HOURS RATING
|
|
rating_work_hours_lock: BoolProperty( # type: ignore
|
|
name="Work Hours Lock",
|
|
description="Work hours are locked -> rating is not sent online",
|
|
default=False,
|
|
options={"SKIP_SAVE"},
|
|
)
|
|
rating_work_hours: FloatProperty( # type: ignore
|
|
name="Work Hours",
|
|
description="nonUI How many hours did this work take?\nShortcut: Hover over asset in the asset bar and press 'R' to show rating menu.",
|
|
default=0.00,
|
|
min=0.0,
|
|
max=300,
|
|
update=update_ratings_work_hours,
|
|
options={"SKIP_SAVE"},
|
|
)
|
|
rating_work_hours_ui: EnumProperty( # type: ignore
|
|
name="Work Hours",
|
|
description="UI How many hours did this work take?\nShortcut: Hover over asset in the asset bar and press 'R' to show rating menu",
|
|
items=wh_enum_callback,
|
|
default=0,
|
|
update=update_ratings_work_hours_ui,
|
|
options={"SKIP_SAVE"},
|
|
)
|
|
|
|
def prefill_ratings(self) -> None:
|
|
"""Pre-fill the quality and work hours ratings if available.
|
|
Locks the ratings locks so that the update function is not called and ratings are not sent online.
|
|
"""
|
|
if not utils.user_logged_in():
|
|
return
|
|
rating = get_rating_local(self.asset_id)
|
|
if rating is None:
|
|
return
|
|
if rating.quality is None and rating.working_hours is None:
|
|
return
|
|
if self.rating_quality != 0:
|
|
return # return if the rating was already filled
|
|
if self.rating_work_hours != 0:
|
|
return # return if the rating was already filled
|
|
|
|
if rating.quality is not None:
|
|
self.rating_quality_lock = True
|
|
self.rating_quality = int(rating.quality)
|
|
self.rating_quality_lock = False
|
|
|
|
if rating.working_hours is not None:
|
|
wh: Union[float, int]
|
|
if rating.working_hours >= 1:
|
|
wh = int(rating.working_hours)
|
|
else:
|
|
wh = round(rating.working_hours, 1)
|
|
whs = str(wh)
|
|
self.rating_work_hours_lock = True
|
|
self.rating_work_hours = round(rating.working_hours, 2)
|
|
try:
|
|
# when the value is not in the enum, it throws an error
|
|
if whs == "0.0":
|
|
whs = "0"
|
|
self.rating_work_hours_ui = whs
|
|
except Exception as e:
|
|
bk_logger.warning(f"exception setting rating_work_hours_ui: {e}")
|
|
|
|
self.rating_work_hours = round(rating.working_hours, 2)
|
|
self.rating_work_hours_lock = False
|
|
|
|
bpy.context.area.tag_redraw()
|
|
|
|
|
|
# class RatingPropsCollection(PropertyGroup):
|
|
# ratings = CollectionProperty(type = RatingProperties)
|