work
save startup blend for animation tab & whatnot
This commit is contained in:
+55
-18
@@ -20,7 +20,7 @@
|
||||
bl_info = {
|
||||
"name": "BlenderKit Online Asset Library",
|
||||
"author": "Vilem Duha, Petr Dlouhy, A. Gajdosik",
|
||||
"version": (3, 18, 0, 251121), # X.Y.Z.yymmdd
|
||||
"version": (3, 18, 1, 251219), # X.Y.Z.yymmdd
|
||||
"blender": (3, 0, 0),
|
||||
"location": "View3D > Properties > BlenderKit",
|
||||
"description": "Boost your workflow with drag&drop assets from the community driven library.",
|
||||
@@ -28,7 +28,7 @@ bl_info = {
|
||||
"tracker_url": "https://github.com/BlenderKit/blenderkit/issues",
|
||||
"category": "3D View",
|
||||
}
|
||||
VERSION = (3, 18, 0, 251121)
|
||||
VERSION = (3, 18, 1, 251219)
|
||||
|
||||
import logging
|
||||
import random
|
||||
@@ -242,7 +242,7 @@ engines = (
|
||||
("CYCLES", "Cycles", "Blender Cycles"),
|
||||
("EEVEE", "Eevee", "Blender eevee renderer"),
|
||||
("EEEVE_NEXT", "Eevee Next", "Blender eevee renderer (new)"),
|
||||
("OCTANE", "Octane", "Octane render enginge"),
|
||||
("OCTANE", "Octane", "Octane render engine"),
|
||||
("ARNOLD", "Arnold", "Arnold render engine"),
|
||||
("V-RAY", "V-Ray", "V-Ray renderer"),
|
||||
("UNREAL", "Unreal", "Unreal engine"),
|
||||
@@ -267,6 +267,12 @@ mesh_poly_types = (
|
||||
)
|
||||
|
||||
|
||||
EXTRA_PATH_OPTIONS = {}
|
||||
|
||||
if bpy.app.version >= (4, 5, 0):
|
||||
EXTRA_PATH_OPTIONS = {"options": {"PATH_SUPPORTS_BLEND_RELATIVE"}}
|
||||
|
||||
|
||||
def udate_down_up(self, context):
|
||||
"""Perform a search if results are empty."""
|
||||
props = bpy.context.window_manager.blenderkitUI
|
||||
@@ -461,12 +467,12 @@ class BlenderKitUIProps(PropertyGroup):
|
||||
search_blender_version: BoolProperty(
|
||||
name="Asset Blender Version",
|
||||
description="Limit the assets by version of Blender (minimum, maximum) in which they were created. "
|
||||
+ "Use maximum version limit to exclude incompatible assets from newer Blender versions than yours. Or set the minumum version to exclude assets created in quite old Blender versions",
|
||||
+ "Use maximum version limit to exclude incompatible assets from newer Blender versions than yours. Or set the minimum version to exclude assets created in quite old Blender versions",
|
||||
)
|
||||
search_blender_version_min: StringProperty(
|
||||
name="Minimal version (including, higher than or equal)",
|
||||
default="0.0",
|
||||
description="Limit the assets by minimum version of Blender in which they were created, including also the specified version and exluding all older versions from the search results. "
|
||||
description="Limit the assets by minimum version of Blender in which they were created, including also the specified version and excluding all older versions from the search results. "
|
||||
+ "Only assets created in HIGHER THAN OR EQUAL (>= min) minimum version will be shown. Use semantic versioning format: X.Y.Z.\n\n"
|
||||
+ "E.g.: exclude all Blender 2 assets by specifying 3, 3.0, or 3.0.0. Assets created in 3.0 or higher will be shown",
|
||||
update=search.search_update,
|
||||
@@ -474,7 +480,7 @@ class BlenderKitUIProps(PropertyGroup):
|
||||
search_blender_version_max: StringProperty(
|
||||
name="Maximum version (excluding, lower than)",
|
||||
default="5.99",
|
||||
description="Limit the assets by maximum version of Blender in which they were created, exluding the specified version and all newer versions from the search results. "
|
||||
description="Limit the assets by maximum version of Blender in which they were created, excluding the specified version and all newer versions from the search results. "
|
||||
+ "Only assets created in LOWER THAN (< max) maximum version will be shown. Use semantic versioning format: X.Y.Z.\n\n"
|
||||
+ "E.g.: exclude all Blender 4 assets by specifying 4, 4.0, or 4.0.0. Assets created in 3.6 and lower will be shown",
|
||||
update=search.search_update,
|
||||
@@ -580,7 +586,7 @@ class BlenderKitUIProps(PropertyGroup):
|
||||
|
||||
rating_ui_width: IntProperty(name="Rating UI Width", default=rating_ui_scale * 600)
|
||||
rating_ui_height: IntProperty(
|
||||
name="Rating UI Heightt", default=rating_ui_scale * 256
|
||||
name="Rating UI Height", default=rating_ui_scale * 256
|
||||
)
|
||||
|
||||
quality_stars_x: IntProperty(name="Rating UI Stars X", default=rating_ui_scale * 90)
|
||||
@@ -1130,6 +1136,7 @@ class BlenderKitMaterialUploadProps(PropertyGroup, BlenderKitCommonUploadProps):
|
||||
subtype="FILE_PATH",
|
||||
default="",
|
||||
update=autothumb.update_upload_material_preview,
|
||||
**EXTRA_PATH_OPTIONS,
|
||||
)
|
||||
|
||||
is_generating_thumbnail: BoolProperty(
|
||||
@@ -1213,13 +1220,14 @@ class BlenderKitBrushUploadProps(PropertyGroup, BlenderKitCommonUploadProps):
|
||||
)
|
||||
|
||||
|
||||
class BlenderKitNodeGroulUploadProps(PropertyGroup, BlenderKitCommonUploadProps):
|
||||
class BlenderKitNodeGroupUploadProps(PropertyGroup, BlenderKitCommonUploadProps):
|
||||
thumbnail: StringProperty(
|
||||
name="Thumbnail",
|
||||
description="Thumbnail path - minimum 1024x1024 square .jpg\n"
|
||||
"And make it beautiful!",
|
||||
subtype="FILE_PATH",
|
||||
default="",
|
||||
**EXTRA_PATH_OPTIONS,
|
||||
# update=autothumb.update_upload_model_preview,
|
||||
)
|
||||
# mode: EnumProperty(
|
||||
@@ -1326,6 +1334,7 @@ class BlenderKitModelUploadProps(PropertyGroup, BlenderKitCommonUploadProps):
|
||||
subtype="FILE_PATH",
|
||||
default="",
|
||||
update=autothumb.update_upload_model_preview,
|
||||
**EXTRA_PATH_OPTIONS,
|
||||
)
|
||||
|
||||
thumbnail_background_lightness: FloatProperty(
|
||||
@@ -1529,6 +1538,7 @@ class BlenderKitModelUploadProps(PropertyGroup, BlenderKitCommonUploadProps):
|
||||
description="Photo of the 3D printed object (JPG or PNG, preferred size is 1024x1024 or higher)",
|
||||
subtype="FILE_PATH",
|
||||
default="",
|
||||
**EXTRA_PATH_OPTIONS,
|
||||
)
|
||||
photo_thumbnail_will_upload_on_website: BoolProperty(
|
||||
name="I will upload photo on website",
|
||||
@@ -1603,6 +1613,7 @@ class BlenderKitSceneUploadProps(PropertyGroup, BlenderKitCommonUploadProps):
|
||||
subtype="FILE_PATH",
|
||||
default="",
|
||||
update=autothumb.update_upload_scene_preview,
|
||||
**EXTRA_PATH_OPTIONS,
|
||||
)
|
||||
|
||||
use_design_year: BoolProperty(
|
||||
@@ -1766,7 +1777,7 @@ class BlenderKitModelSearchProps(PropertyGroup, BlenderKitCommonSearchProps):
|
||||
update=search.search_update,
|
||||
)
|
||||
search_design_year: BoolProperty(
|
||||
name="Sesigned in Year",
|
||||
name="Designed in Year",
|
||||
description="When the object was approximately designed. \n"
|
||||
"Useful for search of historical or future objects",
|
||||
default=False,
|
||||
@@ -1966,7 +1977,7 @@ def fix_subdir(self, context):
|
||||
|
||||
ui_panels.ui_message(
|
||||
title="Fixed to relative path",
|
||||
message="This path should be always realative.\n"
|
||||
message="This path should be always relative.\n"
|
||||
" It's a directory BlenderKit creates where your .blend is \n "
|
||||
"and uses it for storing assets.",
|
||||
)
|
||||
@@ -1992,7 +2003,7 @@ class BlenderKitAddonPreferences(AddonPreferences):
|
||||
|
||||
preferences_lock: BoolProperty(
|
||||
name="Preferences Locked",
|
||||
description="When this is on, preferences will not be saved. Used for programatical changes of preferences",
|
||||
description="When this is on, preferences will not be saved. Used for programmatic changes of preferences",
|
||||
default=False,
|
||||
)
|
||||
|
||||
@@ -2120,6 +2131,14 @@ class BlenderKitAddonPreferences(AddonPreferences):
|
||||
update=utils.save_prefs,
|
||||
)
|
||||
|
||||
# USE OF CLIPBOARD SCAN
|
||||
use_clipboard_scan: BoolProperty(
|
||||
name="Use Clipboard Scan",
|
||||
description="Use the info from BlenderKit website clipboard for visual search",
|
||||
default=True,
|
||||
update=utils.save_prefs,
|
||||
)
|
||||
|
||||
unpack_files: BoolProperty(
|
||||
name="Unpack Files",
|
||||
description="Unpack assets after download \n "
|
||||
@@ -2233,8 +2252,8 @@ class BlenderKitAddonPreferences(AddonPreferences):
|
||||
|
||||
proxy_address: StringProperty(
|
||||
name="Custom proxy address",
|
||||
description="""Set custom HTTP proxy for HTTPS requests of add-on. This setting preceeds any system wide proxy settings. If left empty custom proxy will not be set.
|
||||
|
||||
description="""Set custom HTTP proxy for HTTPS requests of add-on. This setting precedes any system wide proxy settings. If left empty custom proxy will not be set.
|
||||
|
||||
If you use simple HTTP proxy, set in format http://ip:port, or http://username:password@ip:port if your HTTP proxy requires authentication (make sure to escape special characters like #$%:^&*() etc. in username and password). You have to specify the address with http:// prefix.
|
||||
|
||||
HTTPS proxies are not supported! We wait for support in Python 3.11 and in aiohttp module. You can specify the HTTPS proxy with https:// prefix for hacking around and development purposes, but functionality cannot be guaranteed.
|
||||
@@ -2430,12 +2449,21 @@ In this case you should also set path to your system CA bundle containing proxy'
|
||||
default="[]",
|
||||
)
|
||||
|
||||
# EXPERIMENTAL AND DEBUG FEATURES CAN GO BELOW
|
||||
ignore_env_for_thumbnails: BoolProperty(
|
||||
name="Ignore ENVIRONMENT variables for thumbnails",
|
||||
description="If enabled, we will not modify the system environment variables for background thumbnail rendering.",
|
||||
default=False,
|
||||
# do not save prefs here, it's experimental
|
||||
options={"SKIP_SAVE"},
|
||||
)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
if self.api_key.strip() == "":
|
||||
ui_panels.draw_login_buttons(layout)
|
||||
layout.label(
|
||||
text="Sign up to bookmark your favourite assets. Get 200 MiB of private storage in Free Plan."
|
||||
text="Sign up to bookmark your favorite assets. Get 200 MiB of private storage in Free Plan."
|
||||
)
|
||||
else:
|
||||
layout.operator("wm.blenderkit_logout", text="Logout", icon="URL")
|
||||
@@ -2470,8 +2498,9 @@ In this case you should also set path to your system CA bundle containing proxy'
|
||||
gui_settings.prop(self, "show_VIEW3D_MT_blenderkit_model_properties")
|
||||
gui_settings.prop(self, "tips_on_start")
|
||||
gui_settings.prop(self, "announcements_on_start")
|
||||
gui_settings.prop(self, "use_clipboard_scan")
|
||||
|
||||
# NETWORKING SETINGS
|
||||
# NETWORKING SETTINGS
|
||||
network_settings = layout.box()
|
||||
network_settings.alignment = "EXPAND"
|
||||
network_settings.label(text="Networking settings")
|
||||
@@ -2487,6 +2516,14 @@ In this case you should also set path to your system CA bundle containing proxy'
|
||||
# UPDATER SETTINGS
|
||||
addon_updater_ops.update_settings_ui(self, context)
|
||||
|
||||
# EXPERIMENTAL SETTINGS
|
||||
# only if experimental features enabled
|
||||
if self.experimental_features:
|
||||
experimental_settings = layout.box()
|
||||
experimental_settings.alignment = "EXPAND"
|
||||
experimental_settings.label(text="Experimental settings")
|
||||
experimental_settings.prop(self, "ignore_env_for_thumbnails")
|
||||
|
||||
# RUNTIME INFO
|
||||
globdir_op = layout.operator(
|
||||
"wm.blenderkit_open_global_directory",
|
||||
@@ -2535,7 +2572,7 @@ classes = (
|
||||
BlenderKitBrushSearchProps,
|
||||
BlenderKitBrushUploadProps,
|
||||
BlenderKitGeoToolSearchProps,
|
||||
BlenderKitNodeGroulUploadProps,
|
||||
BlenderKitNodeGroupUploadProps,
|
||||
BlenderKitAddonSearchProps,
|
||||
)
|
||||
|
||||
@@ -2598,10 +2635,10 @@ def register():
|
||||
type=BlenderKitGeoToolSearchProps
|
||||
)
|
||||
bpy.types.NodeGroup.blenderkit = PointerProperty( # for uploads, not now...
|
||||
type=BlenderKitNodeGroulUploadProps
|
||||
type=BlenderKitNodeGroupUploadProps
|
||||
)
|
||||
bpy.types.NodeTree.blenderkit = PointerProperty( # for uploads, not now...
|
||||
type=BlenderKitNodeGroulUploadProps
|
||||
type=BlenderKitNodeGroupUploadProps
|
||||
)
|
||||
bpy.types.WindowManager.blenderkit_addon = PointerProperty(
|
||||
type=BlenderKitAddonSearchProps
|
||||
|
||||
+7
-1
@@ -97,7 +97,13 @@ def make_annotations(cls):
|
||||
if bl_props:
|
||||
if "__annotations__" not in cls.__dict__:
|
||||
setattr(cls, "__annotations__", {})
|
||||
annotations = cls.__dict__["__annotations__"]
|
||||
|
||||
try:
|
||||
annotations = cls.__dict__["__annotations__"]
|
||||
except KeyError:
|
||||
# Fedora 43 bug workaround #1823
|
||||
annotations = getattr(cls, "__annotations__")
|
||||
|
||||
for k, v in bl_props.items():
|
||||
annotations[k] = v
|
||||
delattr(cls, k)
|
||||
|
||||
+10
-7
@@ -42,15 +42,19 @@ def find_layer_collection(layer_collection, collection_name):
|
||||
|
||||
def append_brush(file_name, brushname=None, link=False, fake_user=True):
|
||||
"""append a brush"""
|
||||
brushes_before = bpy.data.brushes[:]
|
||||
with bpy.data.libraries.load(file_name, link=link, relative=True) as (
|
||||
data_from,
|
||||
data_to,
|
||||
):
|
||||
for m in data_from.brushes:
|
||||
if m == brushname or brushname is None:
|
||||
if brushname is None or m.strip() == brushname.strip():
|
||||
data_to.brushes = [m]
|
||||
brushname = m
|
||||
brush = bpy.data.brushes[brushname]
|
||||
for b in bpy.data.brushes:
|
||||
if b not in brushes_before:
|
||||
brush = b
|
||||
break
|
||||
brush.use_fake_user = fake_user
|
||||
return brush
|
||||
|
||||
@@ -93,8 +97,7 @@ def append_nodegroup(
|
||||
data_to,
|
||||
):
|
||||
for g in data_from.node_groups:
|
||||
print(g)
|
||||
if g == nodegroupname or nodegroupname is None:
|
||||
if nodegroupname is None or g.strip() == nodegroupname.strip():
|
||||
data_to.node_groups = [g]
|
||||
nodegroupname = g
|
||||
nodegroup = bpy.data.node_groups[nodegroupname]
|
||||
@@ -281,7 +284,7 @@ def append_material(file_name, matname=None, link=False, fake_user=True):
|
||||
):
|
||||
found = False
|
||||
for m in data_from.materials:
|
||||
if m == matname or matname is None:
|
||||
if matname is None or m.strip() == matname.strip():
|
||||
data_to.materials = [m]
|
||||
matname = m
|
||||
found = True
|
||||
@@ -319,7 +322,7 @@ def append_scene(file_name, scenename=None, link=False, fake_user=False):
|
||||
data_to,
|
||||
):
|
||||
for s in data_from.scenes:
|
||||
if s == scenename or scenename is None:
|
||||
if scenename is None or s.strip() == scenename.strip():
|
||||
data_to.scenes = [s]
|
||||
scenename = s
|
||||
scene = bpy.data.scenes[scenename]
|
||||
@@ -448,7 +451,7 @@ def link_collection(
|
||||
data_to,
|
||||
):
|
||||
for col in data_from.collections:
|
||||
if col == kwargs["name"]:
|
||||
if col.strip() == kwargs["name"].strip():
|
||||
data_to.collections = [col]
|
||||
|
||||
rotation = (0, 0, 0)
|
||||
|
||||
+226
-72
@@ -21,7 +21,7 @@ import math
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
from typing import Any, Dict
|
||||
from typing import Any, Dict, Union
|
||||
|
||||
import bpy
|
||||
from bpy.props import BoolProperty, StringProperty
|
||||
@@ -286,6 +286,12 @@ def modal_inside(self, context, event):
|
||||
if self.check_ui_resized(context) or self.check_new_search_results(context):
|
||||
self.update_assetbar_sizes(context)
|
||||
self.update_assetbar_layout(context)
|
||||
# also update tooltip visibility
|
||||
# if there's less results and active button is not visible, hide tooltip
|
||||
# happened only when e.g. running new search from web browser (copying assetbaseid to clipboard)
|
||||
# fixes issue #1766
|
||||
if self.active_index >= len(search.get_search_results()):
|
||||
self.hide_tooltip()
|
||||
self.scroll_update(
|
||||
always=True
|
||||
) # one extra update for scroll for correct redraw, updates all buttons
|
||||
@@ -395,6 +401,17 @@ def get_tooltip_data(asset_data):
|
||||
# Add pricing information
|
||||
price_text = ""
|
||||
price_color = colors.WHITE
|
||||
price_background = (0, 0, 0, 0)
|
||||
|
||||
def format_price(value):
|
||||
if value is None:
|
||||
return ""
|
||||
value_str = str(value).strip()
|
||||
if not value_str:
|
||||
return ""
|
||||
if value_str.startswith("$"):
|
||||
return value_str
|
||||
return f"${value_str}"
|
||||
|
||||
# Check if asset is free or paid (works for all asset types)
|
||||
is_free = asset_data.get("isFree", True)
|
||||
@@ -403,23 +420,38 @@ def get_tooltip_data(asset_data):
|
||||
if asset_data.get("assetType") == "addon":
|
||||
# Get pricing info from extensions cache.
|
||||
# Pricing info is shown only for add-ons.
|
||||
base_price = asset_data.get("basePrice")
|
||||
base_price = format_price(asset_data.get("basePrice"))
|
||||
user_price = format_price(asset_data.get("userPrice"))
|
||||
is_for_sale = asset_data.get("isForSale")
|
||||
|
||||
if is_for_sale and not can_download and base_price:
|
||||
price_text = f"${base_price}"
|
||||
price_color = colors.PURPLE
|
||||
if utils.profile_is_validator():
|
||||
segments = []
|
||||
if user_price:
|
||||
segments.append(f"User {user_price}")
|
||||
if base_price:
|
||||
segments.append(f"Base {base_price}")
|
||||
price_text = " | ".join(segments)
|
||||
price_background = colors.PURPLE_PRICE
|
||||
|
||||
elif is_for_sale and not can_download and user_price and base_price:
|
||||
price_text = f"{user_price} (was {base_price})"
|
||||
price_background = colors.PURPLE_PRICE
|
||||
|
||||
elif is_for_sale and not can_download and base_price:
|
||||
price_text = base_price
|
||||
price_background = colors.PURPLE_PRICE
|
||||
|
||||
elif not is_free and not is_for_sale:
|
||||
price_text = "Full Plan"
|
||||
price_color = colors.PURPLE
|
||||
elif (
|
||||
is_for_sale and can_download
|
||||
): # purchased, but not yet downloaded, so we can't show price
|
||||
price_text = f"Purchased (${base_price})"
|
||||
price_color = colors.PURPLE
|
||||
price_background = colors.ORANGE_FULL
|
||||
|
||||
elif is_for_sale and can_download:
|
||||
price_text = "Purchased"
|
||||
price_background = colors.PURPLE_PRICE
|
||||
|
||||
else:
|
||||
price_text = "Free"
|
||||
price_color = colors.GREEN_FREE
|
||||
price_background = colors.GREEN_PRICE
|
||||
|
||||
tooltip_data = {
|
||||
"aname": aname,
|
||||
@@ -427,12 +459,15 @@ def get_tooltip_data(asset_data):
|
||||
"quality": quality,
|
||||
"price_text": price_text,
|
||||
"price_color": price_color,
|
||||
"price_background": price_background,
|
||||
}
|
||||
asset_data["tooltip_data"] = tooltip_data
|
||||
|
||||
|
||||
def set_thumb_check(
|
||||
element: BL_UI_Button, asset: Dict[str, Any], thumb_type: str = "thumbnail_small"
|
||||
element: Union[BL_UI_Button, BL_UI_Image],
|
||||
asset: Dict[str, Any],
|
||||
thumb_type: str = "thumbnail_small",
|
||||
) -> None:
|
||||
"""Set image in case it is loaded in search results. Checks global_vars.DATA["images available"].
|
||||
- if image download failed, it will be set to 'thumbnail_not_available.jpg'
|
||||
@@ -457,6 +492,8 @@ def set_thumb_check(
|
||||
|
||||
|
||||
class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
|
||||
"""BlenderKit Asset Bar Operator."""
|
||||
|
||||
bl_idname = "view3d.blenderkit_asset_bar_widget"
|
||||
bl_label = "BlenderKit asset bar refresh"
|
||||
bl_description = "BlenderKit asset bar refresh"
|
||||
@@ -508,8 +545,23 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
|
||||
"""Initialize the tooltip panel and its widgets."""
|
||||
self.tooltip_widgets = []
|
||||
self.tooltip_scale = 1.0
|
||||
self.tooltip_height = self.tooltip_size
|
||||
self.tooltip_width = self.tooltip_size
|
||||
|
||||
# Fallbacks in case update_tooltip_size was not called yet
|
||||
self.tooltip_width = getattr(self, "tooltip_width", self.tooltip_size)
|
||||
image_height = getattr(self, "tooltip_image_height", self.tooltip_size)
|
||||
info_height = getattr(
|
||||
self,
|
||||
"tooltip_info_height",
|
||||
max(
|
||||
int(image_height * self.bottom_panel_fraction),
|
||||
self.asset_name_text_size * 3,
|
||||
),
|
||||
)
|
||||
self.tooltip_image_height = image_height
|
||||
self.tooltip_info_height = info_height
|
||||
self.tooltip_height = self.tooltip_image_height + self.tooltip_info_height
|
||||
self.labels_start = self.tooltip_image_height
|
||||
|
||||
# total_size = tooltip# + 2 * self.margin
|
||||
self.tooltip_panel = BL_UI_Drag_Panel(
|
||||
0, 0, self.tooltip_width, self.tooltip_height
|
||||
@@ -520,20 +572,16 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
|
||||
tooltip_image = BL_UI_Image(0, 0, 1, 1)
|
||||
img_path = paths.get_addon_thumbnail_path("thumbnail_notready.jpg")
|
||||
tooltip_image.set_image(img_path)
|
||||
tooltip_image.set_image_size((self.tooltip_width, self.tooltip_height))
|
||||
tooltip_image.set_image_size((self.tooltip_width, self.tooltip_image_height))
|
||||
tooltip_image.set_image_position((0, 0))
|
||||
tooltip_image.set_image_colorspace("")
|
||||
self.tooltip_image = tooltip_image
|
||||
self.tooltip_widgets.append(tooltip_image)
|
||||
|
||||
self.bottom_panel_fraction = 0.15
|
||||
self.labels_start = self.tooltip_height * (1 - self.bottom_panel_fraction)
|
||||
|
||||
dark_panel = BL_UI_Widget(
|
||||
0,
|
||||
self.labels_start,
|
||||
self.tooltip_width,
|
||||
self.tooltip_height * self.bottom_panel_fraction,
|
||||
self.tooltip_info_height,
|
||||
)
|
||||
dark_panel.bg_color = (0.0, 0.0, 0.0, 0.7)
|
||||
self.tooltip_dark_panel = dark_panel
|
||||
@@ -549,8 +597,9 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
|
||||
self.asset_name = name_label
|
||||
self.tooltip_widgets.append(name_label)
|
||||
|
||||
self.gravatar_size = int(
|
||||
self.tooltip_height * self.bottom_panel_fraction - self.tooltip_margin
|
||||
self.gravatar_size = max(
|
||||
int(self.tooltip_info_height - 2 * self.tooltip_margin),
|
||||
self.asset_name_text_size,
|
||||
)
|
||||
|
||||
authors_name = self.new_text(
|
||||
@@ -566,8 +615,8 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
|
||||
self.tooltip_widgets.append(authors_name)
|
||||
|
||||
gravatar_image = BL_UI_Image(
|
||||
self.tooltip_width - self.gravatar_size,
|
||||
self.tooltip_height - self.gravatar_size,
|
||||
self.tooltip_width - self.gravatar_size - self.tooltip_margin,
|
||||
self.tooltip_height - self.gravatar_size - self.tooltip_margin,
|
||||
1,
|
||||
1,
|
||||
)
|
||||
@@ -575,8 +624,8 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
|
||||
gravatar_image.set_image(img_path)
|
||||
gravatar_image.set_image_size(
|
||||
(
|
||||
self.gravatar_size - 1 * self.tooltip_margin,
|
||||
self.gravatar_size - 1 * self.tooltip_margin,
|
||||
self.gravatar_size,
|
||||
self.gravatar_size,
|
||||
)
|
||||
)
|
||||
gravatar_image.set_image_position((0, 0))
|
||||
@@ -617,7 +666,14 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
|
||||
height=self.asset_name_text_size,
|
||||
text_size=self.asset_name_text_size,
|
||||
)
|
||||
price_label.text_color = (1.0, 0.8, 0.2, 1.0) # Golden color for price
|
||||
price_label.background = True
|
||||
price_label.padding = (3, 4)
|
||||
price_label.text_color = (
|
||||
1.0,
|
||||
0.8,
|
||||
0.2,
|
||||
1.0,
|
||||
) # Golden color for price
|
||||
self.tooltip_widgets.append(price_label)
|
||||
self.price_label = price_label
|
||||
|
||||
@@ -728,14 +784,30 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
|
||||
"""Calculate all important sizes for the tooltip"""
|
||||
region = context.region
|
||||
ui_props = bpy.context.window_manager.blenderkitUI
|
||||
ui_scale = bpy.context.preferences.view.ui_scale
|
||||
ui_scale = self.get_ui_scale()
|
||||
|
||||
base_panel_height = self.tooltip_base_size_pixels * (
|
||||
1 + self.bottom_panel_fraction
|
||||
)
|
||||
|
||||
if hasattr(self, "tooltip_panel"):
|
||||
tooltip_y_offset = abs(region.height - self.tooltip_panel.y_screen)
|
||||
tooltip_y_available_height = abs(
|
||||
region.height - self.tooltip_panel.y_screen
|
||||
)
|
||||
# if tooltip is above, we need to reduce it's size if its y is out of region height
|
||||
if self.tooltip_panel.y_screen <= 0:
|
||||
tooltip_y_available_height = (
|
||||
base_panel_height * ui_scale + self.tooltip_panel.y_screen
|
||||
)
|
||||
self.tooltip_panel.set_location(self.tooltip_panel.x, 0)
|
||||
|
||||
else:
|
||||
tooltip_y_offset = abs(region.height - (self.bar_height + self.bar_y))
|
||||
tooltip_y_available_height = abs(
|
||||
region.height - (self.bar_height + self.bar_y)
|
||||
)
|
||||
|
||||
self.tooltip_scale = min(
|
||||
1.0, tooltip_y_offset / (self.tooltip_base_size_pixels * ui_scale)
|
||||
1.0, tooltip_y_available_height / (base_panel_height * ui_scale)
|
||||
)
|
||||
self.asset_name_text_size = int(
|
||||
0.039 * self.tooltip_base_size_pixels * ui_scale * self.tooltip_scale
|
||||
@@ -750,14 +822,33 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
|
||||
|
||||
if ui_props.asset_type == "HDR":
|
||||
self.tooltip_width = self.tooltip_size * 2
|
||||
self.tooltip_height = self.tooltip_size
|
||||
self.tooltip_image_height = self.tooltip_size
|
||||
else:
|
||||
self.tooltip_width = self.tooltip_size
|
||||
self.tooltip_height = self.tooltip_size
|
||||
self.tooltip_image_height = self.tooltip_size
|
||||
|
||||
self.gravatar_size = int(
|
||||
self.tooltip_height * self.bottom_panel_fraction - self.tooltip_margin
|
||||
self.tooltip_info_height = max(
|
||||
int(self.tooltip_image_height * self.bottom_panel_fraction),
|
||||
self.asset_name_text_size * 3,
|
||||
)
|
||||
self.labels_start = self.tooltip_image_height
|
||||
self.tooltip_height = self.tooltip_image_height + self.tooltip_info_height
|
||||
|
||||
self.gravatar_size = max(
|
||||
int(self.tooltip_info_height - 2 * self.tooltip_margin),
|
||||
self.asset_name_text_size,
|
||||
)
|
||||
|
||||
def get_ui_scale(self):
|
||||
"""Get the UI scale"""
|
||||
ui_scale = bpy.context.preferences.view.ui_scale
|
||||
pixel_size = bpy.context.preferences.system.pixel_size
|
||||
if pixel_size > 1:
|
||||
# for a reason unknown,
|
||||
# the pixel size is modified only on mac
|
||||
# where pixel size is 2.0
|
||||
ui_scale = pixel_size
|
||||
return ui_scale
|
||||
|
||||
def update_assetbar_sizes(self, context):
|
||||
"""Calculate all important sizes for the asset bar"""
|
||||
@@ -766,8 +857,7 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
|
||||
|
||||
ui_props = bpy.context.window_manager.blenderkitUI
|
||||
user_preferences = bpy.context.preferences.addons[__package__].preferences
|
||||
ui_scale = bpy.context.preferences.view.ui_scale
|
||||
|
||||
ui_scale = self.get_ui_scale()
|
||||
# assetbar scaling
|
||||
self.button_margin = int(0 * ui_scale)
|
||||
self.assetbar_margin = int(2 * ui_scale)
|
||||
@@ -793,6 +883,10 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
|
||||
self.bar_x = int(
|
||||
tools_width + self.button_margin + ui_props.bar_x_offset * ui_scale
|
||||
)
|
||||
# self.bar_y = region.height - ui_props.bar_y_offset * ui_scale
|
||||
|
||||
self.bar_y = int(ui_props.bar_y_offset * ui_scale)
|
||||
|
||||
self.bar_end = int(ui_width + 180 * ui_scale + self.other_button_size)
|
||||
self.bar_width = int(region.width - self.bar_x - self.bar_end)
|
||||
|
||||
@@ -810,6 +904,16 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
|
||||
if search_results is not None and self.wcount > 0:
|
||||
if user_preferences.assetbar_expanded:
|
||||
max_rows = user_preferences.maximized_assetbar_rows
|
||||
available_height = (
|
||||
region.height
|
||||
- self.bar_y
|
||||
- 2 * self.assetbar_margin
|
||||
- self.other_button_size
|
||||
)
|
||||
max_rows_by_height = math.floor(available_height / self.button_size)
|
||||
max_rows = (
|
||||
min(max_rows, max_rows_by_height) if max_rows_by_height > 0 else 1
|
||||
)
|
||||
else:
|
||||
max_rows = 1
|
||||
self.hcount = min(
|
||||
@@ -821,8 +925,6 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
|
||||
self.hcount = 1
|
||||
|
||||
self.bar_height = (self.button_size) * self.hcount + 2 * self.assetbar_margin
|
||||
# self.bar_y = region.height - ui_props.bar_y_offset * ui_scale
|
||||
self.bar_y = int(ui_props.bar_y_offset * ui_scale)
|
||||
if ui_props.down_up == "UPLOAD":
|
||||
self.reports_y = region.height - self.bar_y - 600
|
||||
ui_props.reports_y = region.height - self.bar_y - 600
|
||||
@@ -886,26 +988,28 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
|
||||
self.tooltip_panel.width = self.tooltip_width
|
||||
self.tooltip_panel.height = self.tooltip_height
|
||||
self.tooltip_image.width = self.tooltip_width
|
||||
self.tooltip_image.height = self.tooltip_height
|
||||
self.tooltip_image.height = self.tooltip_image_height
|
||||
|
||||
self.labels_start = self.tooltip_height * (1 - self.bottom_panel_fraction)
|
||||
self.labels_start = self.tooltip_image_height
|
||||
|
||||
self.tooltip_image.set_image_size((self.tooltip_width, self.tooltip_height))
|
||||
self.tooltip_image.set_image_size(
|
||||
(self.tooltip_width, self.tooltip_image_height)
|
||||
)
|
||||
self.tooltip_image.set_location(0, 0)
|
||||
|
||||
self.gravatar_image.set_location(
|
||||
self.tooltip_width - self.gravatar_size,
|
||||
self.tooltip_height - self.gravatar_size,
|
||||
self.tooltip_width - self.gravatar_size - self.tooltip_margin,
|
||||
self.tooltip_height - self.gravatar_size - self.tooltip_margin,
|
||||
)
|
||||
self.gravatar_image.set_image_size(
|
||||
(
|
||||
self.gravatar_size - 1 * self.tooltip_margin,
|
||||
self.gravatar_size - 1 * self.tooltip_margin,
|
||||
self.gravatar_size,
|
||||
self.gravatar_size,
|
||||
)
|
||||
)
|
||||
|
||||
self.authors_name.set_location(
|
||||
self.tooltip_width - self.gravatar_size - self.tooltip_margin,
|
||||
self.tooltip_width - self.gravatar_size - (self.tooltip_margin * 2),
|
||||
self.tooltip_height - self.author_text_size - self.tooltip_margin,
|
||||
)
|
||||
self.authors_name.text_size = self.author_text_size
|
||||
@@ -922,9 +1026,7 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
|
||||
0,
|
||||
self.labels_start,
|
||||
)
|
||||
self.tooltip_dark_panel.height = (
|
||||
self.tooltip_height * self.bottom_panel_fraction
|
||||
)
|
||||
self.tooltip_dark_panel.height = self.tooltip_info_height
|
||||
self.tooltip_dark_panel.width = self.tooltip_width
|
||||
|
||||
self.quality_label.set_location(
|
||||
@@ -942,6 +1044,15 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
|
||||
(self.asset_name_text_size, self.asset_name_text_size)
|
||||
)
|
||||
|
||||
# right after the asset name
|
||||
self.price_label.set_location(
|
||||
self.tooltip_margin,
|
||||
self.labels_start + (self.tooltip_margin * 3) + self.asset_name.height,
|
||||
)
|
||||
self.price_label.width = self.tooltip_width - 2 * self.tooltip_margin
|
||||
self.price_label.height = self.asset_name_text_size
|
||||
self.price_label.text_size = self.asset_name_text_size
|
||||
|
||||
def update_layout(self, context, event):
|
||||
"""update UI sizes after their recalculation"""
|
||||
self.update_assetbar_layout(context)
|
||||
@@ -1044,6 +1155,7 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
|
||||
self.button_bg_color = (0.2, 0.2, 0.2, 1.0)
|
||||
self.button_hover_color = (0.8, 0.8, 0.8, 1.0)
|
||||
self.button_selected_color = (0.5, 0.5, 0.5, 1.0)
|
||||
self.button_selected_color_dim = (0.3, 0.3, 0.3, 1.0)
|
||||
|
||||
self.buttons = []
|
||||
self.asset_buttons = []
|
||||
@@ -1072,7 +1184,7 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
|
||||
self.other_button_size, # Same height as tab buttons
|
||||
)
|
||||
# dark blue
|
||||
self.tab_area_bg.bg_color = (0.2, 0.25, 0.4, 1.0)
|
||||
self.tab_area_bg.bg_color = colors.TOP_BAR_BLUE
|
||||
|
||||
# Add widgets to panel - add tab background first so it's behind everything
|
||||
self.widgets_panel.append(self.tab_area_bg)
|
||||
@@ -1162,8 +1274,11 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
|
||||
# Add tab navigation elements
|
||||
button_size = self.other_button_size
|
||||
margin = int(button_size * 0.05)
|
||||
space = int(button_size * 0.4)
|
||||
tab_icon_size = int(button_size * 0.7) # Size for the asset type icon
|
||||
tab_width = button_size * 4 # Wider tabs to accommodate icon
|
||||
tab_width = (
|
||||
button_size * 4 + tab_icon_size
|
||||
) # Widen the tabs to accommodate type icon
|
||||
|
||||
# Back/Forward history buttons
|
||||
self.history_back_button = BL_UI_Button(
|
||||
@@ -1199,10 +1314,14 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
|
||||
tabs = global_vars.TABS["tabs"]
|
||||
tab_x_start = margin * 4 + button_size * 3 # Starting x position of first tab
|
||||
|
||||
tabs_end_x = 0
|
||||
|
||||
for i, tab in enumerate(tabs):
|
||||
is_active = i == global_vars.TABS["active_tab"]
|
||||
|
||||
# Calculate positions
|
||||
tab_x = tab_x_start + i * (
|
||||
tab_width + button_size + margin
|
||||
tab_width + button_size + margin + space
|
||||
) # Space for tab and close button
|
||||
|
||||
# Tab button
|
||||
@@ -1212,13 +1331,15 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
|
||||
tab_width, # Width of tab
|
||||
button_size,
|
||||
)
|
||||
tab_button.bg_color = self.button_bg_color
|
||||
if i == global_vars.TABS["active_tab"]:
|
||||
tab_button.bg_color = self.button_selected_color
|
||||
|
||||
tab_button.hover_bg_color = self.button_hover_color
|
||||
tab_button.text = tab["name"]
|
||||
tab_button.text_size = button_size * 0.5
|
||||
tab_button.text_color = self.text_color
|
||||
tab_button.bg_color = self.button_bg_color
|
||||
if is_active:
|
||||
tab_button.bg_color = self.button_selected_color
|
||||
|
||||
tab_button.tab_index = i # Store tab index
|
||||
tab_button.set_mouse_down(self.switch_tab) # Add click handler
|
||||
self.tab_buttons.append(tab_button)
|
||||
@@ -1226,7 +1347,7 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
|
||||
# Set asset type icon as tab button image
|
||||
tab_button.set_image_size((tab_icon_size, tab_icon_size))
|
||||
tab_button.set_image_position(
|
||||
(margin, (button_size - tab_icon_size) / 2)
|
||||
(margin * 2, (button_size - tab_icon_size) / 2)
|
||||
) # Center vertically
|
||||
|
||||
# Only create close button if there's more than one tab
|
||||
@@ -1243,22 +1364,24 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
|
||||
close_tab.text = "×" # Set text after creation
|
||||
close_tab.text_size = button_size * 0.8
|
||||
close_tab.text_color = self.text_color
|
||||
if is_active:
|
||||
close_tab.bg_color = self.button_selected_color_dim
|
||||
|
||||
close_tab.tab_index = i # Store tab index
|
||||
# if there's only one tab, the button closes asset bar instead of closing tab
|
||||
if len(tabs) > 1:
|
||||
close_tab.set_mouse_down(self.remove_tab) # Add click handler
|
||||
else:
|
||||
close_tab.set_mouse_down(self.cancel_press)
|
||||
|
||||
self.close_tab_buttons.append(close_tab)
|
||||
|
||||
tabs_end_x = close_x + button_size
|
||||
|
||||
# New tab button - position after all tabs and close buttons
|
||||
if len(tabs) > 0:
|
||||
last_tab_index = len(tabs) - 1
|
||||
last_tab_x = tab_x_start + last_tab_index * (
|
||||
tab_width + button_size + margin
|
||||
)
|
||||
new_tab_x = (
|
||||
last_tab_x + tab_width + button_size + margin * 2
|
||||
space + tabs_end_x + margin * 2
|
||||
) # After last tab and its close button
|
||||
else:
|
||||
new_tab_x = tab_x_start # If no tabs, start at the beginning
|
||||
@@ -1302,8 +1425,6 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
|
||||
active_tab["history_index"] < len(active_tab["history"]) - 1
|
||||
)
|
||||
|
||||
# self.update_buttons()
|
||||
|
||||
def set_element_images(self):
|
||||
"""set ui elements images, has to be done after init of UI."""
|
||||
# img_fp = paths.get_addon_thumbnail_path("vs_rejected.png")
|
||||
@@ -1345,7 +1466,7 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
|
||||
icon_path = paths.get_addon_thumbnail_path(
|
||||
f"asset_type_{asset_type}.png"
|
||||
)
|
||||
if not os.path.exists(icon_path):
|
||||
if not paths.icon_path_exists(icon_path):
|
||||
icon_path = paths.get_addon_thumbnail_path(
|
||||
"asset_type_model.png"
|
||||
)
|
||||
@@ -1444,7 +1565,7 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
|
||||
"""Initialize the asset bar operator."""
|
||||
self.tooltip_base_size_pixels = 512
|
||||
self.tooltip_scale = 1.0
|
||||
self.bottom_panel_fraction = 0.15
|
||||
self.bottom_panel_fraction = 0.18
|
||||
self.needs_tooltip_update = False
|
||||
self.update_ui_size(bpy.context)
|
||||
|
||||
@@ -1679,9 +1800,13 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
|
||||
price_color = asset_data["tooltip_data"].get(
|
||||
"price_color", (1.0, 0.8, 0.2, 1.0)
|
||||
)
|
||||
price_background = asset_data["tooltip_data"].get(
|
||||
"price_background", (0.2, 0.2, 0.2, 0.0)
|
||||
)
|
||||
self.price_label.text = price_text
|
||||
self.price_label.text_color = price_color
|
||||
self.price_label.visible = bool(price_text)
|
||||
self.price_label.bg_color = price_background
|
||||
|
||||
# preview comments for validators
|
||||
self.update_comments_for_validators(asset_data)
|
||||
@@ -1721,7 +1846,22 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
|
||||
- properties_width
|
||||
),
|
||||
)
|
||||
tooltip_y = int(widget.y_screen + widget.height)
|
||||
|
||||
# Calculate space above and below the button
|
||||
ui_scale = self.get_ui_scale()
|
||||
full_tooltip_height = self.tooltip_panel.height
|
||||
space_above = widget.y_screen
|
||||
space_below = bpy.context.region.height - (widget.y_screen + widget.height)
|
||||
# If space below is insufficient (would make tooltip < 70% size), position above
|
||||
if (
|
||||
space_below < full_tooltip_height
|
||||
and space_below < full_tooltip_height * 0.7
|
||||
and space_below < space_above
|
||||
):
|
||||
tooltip_y = int(widget.y_screen - full_tooltip_height)
|
||||
else:
|
||||
tooltip_y = int(widget.y_screen + widget.height)
|
||||
|
||||
# need to set image here because of context issues.
|
||||
img_path = paths.get_addon_thumbnail_path("star_grey.png")
|
||||
self.quality_star.set_image(img_path)
|
||||
@@ -1730,7 +1870,7 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
|
||||
self.tooltip_panel.set_location(tooltip_x, tooltip_y)
|
||||
self.update_tooltip_size(bpy.context)
|
||||
self.update_tooltip_layout(bpy.context)
|
||||
self.tooltip_panel.set_location(tooltip_x, tooltip_y)
|
||||
self.tooltip_panel.set_location(self.tooltip_panel.x, self.tooltip_panel.y)
|
||||
self.tooltip_panel.layout_widgets()
|
||||
# show bookmark button - always on mouse enter
|
||||
if widget.bookmark_button:
|
||||
@@ -2317,6 +2457,10 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
|
||||
return # Already on this tab and history step
|
||||
# make original tab original background color
|
||||
self.tab_buttons[global_vars.TABS["active_tab"]].bg_color = self.button_bg_color
|
||||
# make also tab close button original background color
|
||||
self.close_tab_buttons[global_vars.TABS["active_tab"]].bg_color = (
|
||||
self.button_bg_color
|
||||
)
|
||||
|
||||
global_vars.TABS["active_tab"] = tab_index
|
||||
global_vars.TABS["tabs"][tab_index]["history_index"] = history_index
|
||||
@@ -2354,14 +2498,24 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
|
||||
|
||||
# Update history button visibility
|
||||
active_tab = global_vars.TABS["tabs"][tab_index]
|
||||
|
||||
self.history_back_button.visible = active_tab["history_index"] > 0
|
||||
self.history_forward_button.visible = (
|
||||
active_tab["history_index"] < len(active_tab["history"]) - 1
|
||||
)
|
||||
|
||||
# make active tab a bit darker
|
||||
if len(self.tab_buttons) > tab_index:
|
||||
self.tab_buttons[tab_index].bg_color = self.button_selected_color
|
||||
# update tab colors
|
||||
for tab_button in self.tab_buttons:
|
||||
c_tab_index = tab_button.tab_index
|
||||
if c_tab_index == tab_index:
|
||||
tab_button.bg_color = self.button_selected_color
|
||||
self.close_tab_buttons[tab_index].bg_color = (
|
||||
self.button_selected_color_dim
|
||||
)
|
||||
|
||||
else:
|
||||
tab_button.bg_color = self.button_bg_color
|
||||
self.close_tab_buttons[c_tab_index].bg_color = self.button_bg_color
|
||||
|
||||
# update filters
|
||||
search.update_filters()
|
||||
|
||||
+12
-2
@@ -1467,8 +1467,18 @@ class AssetDragOperator(bpy.types.Operator):
|
||||
Tuple[None, None, None],
|
||||
]:
|
||||
"""Find the window, region and area under the mouse cursor."""
|
||||
# Iterate windows backwards, so we go from the top-most window to the bottommost window
|
||||
for window in reversed(bpy.context.window_manager.windows):
|
||||
|
||||
wins = bpy.context.window_manager.windows[:]
|
||||
# reverse the list, seemed to work well at least on windows.
|
||||
wins.reverse()
|
||||
context_win = bpy.context.window
|
||||
|
||||
# let's prioritize the context window
|
||||
if context_win is not None:
|
||||
wins.remove(context_win)
|
||||
wins.insert(0, context_win)
|
||||
|
||||
for window in wins:
|
||||
# first let's test if it's in this window, so we know we shall continue
|
||||
window_x = window.x * self.resolution_factor
|
||||
window_y = window.y * self.resolution_factor
|
||||
|
||||
+32
-8
@@ -27,6 +27,8 @@ from . import utils
|
||||
|
||||
RENDER_OBTYPES = ["MESH", "CURVE", "SURFACE", "METABALL", "TEXT"]
|
||||
|
||||
_BLE_5_PLUS = bpy.app.version >= (5, 0, 0)
|
||||
|
||||
|
||||
def check_material(props, mat):
|
||||
e = bpy.context.scene.render.engine
|
||||
@@ -217,17 +219,39 @@ def check_rig(props, obs):
|
||||
props.rig = True
|
||||
|
||||
|
||||
def has_keyframes(obj):
|
||||
"""Checks if object has animation data with keyframes.
|
||||
|
||||
This function only checks for keyframes,
|
||||
may return false negatives for objects animated with constraints, drivers, etc.
|
||||
"""
|
||||
if obj.animation_data is None:
|
||||
return False
|
||||
|
||||
a = obj.animation_data.action
|
||||
if a is None:
|
||||
return False
|
||||
|
||||
# should work from at least Blender4.2+
|
||||
if _BLE_5_PLUS:
|
||||
# combined fcurves ranges
|
||||
# check if start and end frames are different
|
||||
if a.curve_frame_range[0] != a.curve_frame_range[1]:
|
||||
return True
|
||||
else:
|
||||
# older Blender versions
|
||||
for c in a.fcurves:
|
||||
if len(c.keyframe_points) > 1:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def check_anim(props, obs):
|
||||
animated = False
|
||||
for ob in obs:
|
||||
if ob.animation_data is not None:
|
||||
a = ob.animation_data.action
|
||||
if a is not None:
|
||||
for c in a.fcurves:
|
||||
if len(c.keyframe_points) > 1:
|
||||
animated = True
|
||||
|
||||
# c.keyframe_points.remove(c.keyframe_points[0])
|
||||
if has_keyframes(ob):
|
||||
animated = True
|
||||
break
|
||||
if animated:
|
||||
props.animated = True
|
||||
|
||||
|
||||
+19
-2
@@ -209,8 +209,17 @@ def start_model_thumbnailer(
|
||||
blender_user_scripts_dir = (
|
||||
Path(__file__).resolve().parents[2]
|
||||
) # scripts/addons/blenderkit/autothumb.py
|
||||
|
||||
env = {"BLENDER_USER_SCRIPTS": str(blender_user_scripts_dir)}
|
||||
env.update(os.environ)
|
||||
|
||||
# both must be enabled
|
||||
if (
|
||||
user_preferences.experimental_features
|
||||
and user_preferences.ignore_env_for_thumbnails
|
||||
):
|
||||
env = None
|
||||
|
||||
proc = subprocess.Popen(
|
||||
args,
|
||||
stdout=subprocess.PIPE,
|
||||
@@ -218,7 +227,7 @@ def start_model_thumbnailer(
|
||||
creationflags=utils.get_process_flags(),
|
||||
env=env,
|
||||
)
|
||||
bk_logger.info(f"Started Blender executing {SCRIPT_NAME} on file {datafile}")
|
||||
bk_logger.info("Started Blender executing %s on file %s", SCRIPT_NAME, datafile)
|
||||
eval_path_computing = f"bpy.data.objects['{json_args['asset_name']}'].blenderkit.is_generating_thumbnail"
|
||||
eval_path_state = f"bpy.data.objects['{json_args['asset_name']}'].blenderkit.thumbnail_generating_state"
|
||||
eval_path = f"bpy.data.objects['{json_args['asset_name']}']"
|
||||
@@ -284,8 +293,16 @@ def start_material_thumbnailer(
|
||||
blender_user_scripts_dir = (
|
||||
Path(__file__).resolve().parents[2]
|
||||
) # scripts/addons/blenderkit/autothumb.py
|
||||
|
||||
env = {"BLENDER_USER_SCRIPTS": str(blender_user_scripts_dir)}
|
||||
env.update(os.environ)
|
||||
|
||||
if (
|
||||
user_preferences.experimental_features
|
||||
and user_preferences.ignore_env_for_thumbnails
|
||||
):
|
||||
env = None
|
||||
|
||||
proc = subprocess.Popen(
|
||||
args,
|
||||
stdout=subprocess.PIPE,
|
||||
@@ -293,7 +310,7 @@ def start_material_thumbnailer(
|
||||
creationflags=utils.get_process_flags(),
|
||||
env=env,
|
||||
)
|
||||
bk_logger.info(f"Started Blender executing {SCRIPT_NAME} on file {datafile}")
|
||||
bk_logger.info("Started Blender executing %s on file %s", SCRIPT_NAME, datafile)
|
||||
|
||||
eval_path_computing = f"bpy.data.materials['{json_args['asset_name']}'].blenderkit.is_generating_thumbnail"
|
||||
eval_path_state = f"bpy.data.materials['{json_args['asset_name']}'].blenderkit.thumbnail_generating_state"
|
||||
|
||||
+19
-4
@@ -164,11 +164,20 @@ if __name__ == "__main__":
|
||||
ob.data.texspace_size.x = 1 # / tscale
|
||||
ob.data.texspace_size.y = 1 # / tscale
|
||||
ob.data.texspace_size.z = 1 # / tscale
|
||||
if data["adaptive_subdivision"] == True:
|
||||
ob.cycles.use_adaptive_subdivision = True
|
||||
|
||||
# this option was moved in Blender 5.0 from cycles directly to modifier
|
||||
if bpy.app.version >= (5, 0, 0):
|
||||
for mod in ob.modifiers:
|
||||
if mod.type == "SUBSURF":
|
||||
if data["adaptive_subdivision"] == True:
|
||||
mod.use_adaptive_subdivision = True
|
||||
else:
|
||||
mod.use_adaptive_subdivision = False
|
||||
else:
|
||||
ob.cycles.use_adaptive_subdivision = False
|
||||
if data["adaptive_subdivision"] == True:
|
||||
ob.cycles.use_adaptive_subdivision = True
|
||||
else:
|
||||
ob.cycles.use_adaptive_subdivision = False
|
||||
ts = data["texture_size_meters"]
|
||||
if data["thumbnail_type"] in ["BALL", "BALL_COMPLEX", "CLOTH"]:
|
||||
utils.automap(
|
||||
@@ -179,7 +188,13 @@ if __name__ == "__main__":
|
||||
)
|
||||
bpy.context.view_layer.update()
|
||||
|
||||
s.cycles.volume_step_size = tscale * 0.1
|
||||
# this option was removed in Blender 5.0
|
||||
# but we have option to set biased volumes
|
||||
if bpy.app.version >= (5, 0, 0):
|
||||
# usually small speedup with little quality loss
|
||||
s.cycles.volume_biased = True
|
||||
else:
|
||||
s.cycles.volume_step_size = tscale * 0.1
|
||||
|
||||
if thumbnail_use_gpu is True:
|
||||
bpy.context.scene.cycles.device = "GPU"
|
||||
|
||||
+3
@@ -71,6 +71,9 @@ def threadread(tcom: ThreadCom):
|
||||
return # process terminated
|
||||
inline = tcom.proc.stdout.readline()
|
||||
inline = inline.decode("utf-8")
|
||||
# ignore empty lines
|
||||
if inline.strip() == "":
|
||||
continue
|
||||
bk_logger.info(inline.strip())
|
||||
progress = re.findall(r"progress\{(.*?)\}", inline)
|
||||
if len(progress) > 0:
|
||||
|
||||
+2
-4
@@ -139,8 +139,8 @@ def login(signup: bool) -> None:
|
||||
|
||||
|
||||
def generate_pkce_pair() -> tuple[str, str]:
|
||||
"""Generate PKCE pair - a code verifier and code challange.
|
||||
The challange should be sent first to the server, the verifier is used in next steps to verify identity (handles Client).
|
||||
"""Generate PKCE pair - a code verifier and code challenge.
|
||||
The challenge should be sent first to the server, the verifier is used in next steps to verify identity (handles Client).
|
||||
"""
|
||||
rand = random.SystemRandom()
|
||||
code_verifier = "".join(rand.choices(string.ascii_letters + string.digits, k=128))
|
||||
@@ -162,8 +162,6 @@ def write_tokens(auth_token, refresh_token, oauth_response):
|
||||
override_extension_draw.ensure_repository(api_key=auth_token)
|
||||
override_extension_draw.clear_repo_cache()
|
||||
|
||||
#
|
||||
|
||||
|
||||
def ensure_token_refresh() -> bool:
|
||||
"""Check if API token needs refresh, call refresh and return True if so.
|
||||
|
||||
+5
-5
@@ -1,17 +1,17 @@
|
||||
{
|
||||
"last_check": "2026-01-12 10:24:10.400844",
|
||||
"backup_date": "December-1-2025",
|
||||
"last_check": "2026-04-02 12:41:20.491300",
|
||||
"backup_date": "January-12-2026",
|
||||
"update_ready": true,
|
||||
"ignore": false,
|
||||
"just_restored": false,
|
||||
"just_updated": false,
|
||||
"version_text": {
|
||||
"link": "https://github.com/BlenderKit/BlenderKit/releases/download/v3.18.1.251219/blenderkit-v3.18.1.251219.zip",
|
||||
"link": "https://github.com/BlenderKit/BlenderKit/releases/download/v3.19.1.260402/blenderkit-v3.19.1.260402.zip",
|
||||
"version": [
|
||||
3,
|
||||
18,
|
||||
19,
|
||||
1,
|
||||
251219
|
||||
260402
|
||||
]
|
||||
}
|
||||
}
|
||||
+78
-7
@@ -1,5 +1,10 @@
|
||||
import blf
|
||||
import bpy
|
||||
import gpu
|
||||
|
||||
from typing import Tuple, Union
|
||||
|
||||
from gpu_extras.batch import batch_for_shader
|
||||
|
||||
from .bl_ui_widget import BL_UI_Widget
|
||||
|
||||
@@ -17,6 +22,9 @@ class BL_UI_Label(BL_UI_Widget):
|
||||
self.multiline = False
|
||||
self.row_height = 20
|
||||
|
||||
self.padding: Union[Tuple[float, float], float] = 0
|
||||
self.background = False
|
||||
|
||||
@property
|
||||
def text_color(self):
|
||||
return self._text_color
|
||||
@@ -61,6 +69,30 @@ class BL_UI_Label(BL_UI_Widget):
|
||||
blf.size(font_id, self._text_size, 72)
|
||||
else:
|
||||
blf.size(font_id, self._text_size)
|
||||
lines = self._text.split("\n") if self.multiline else [self._text]
|
||||
if not lines:
|
||||
return
|
||||
|
||||
default_line_height = self.row_height if self.multiline else self._text_size
|
||||
line_metrics = []
|
||||
max_line_width = 0.0
|
||||
total_height = 0.0
|
||||
|
||||
for line in lines:
|
||||
width, height = blf.dimensions(font_id, line)
|
||||
if height == 0:
|
||||
height = default_line_height
|
||||
line_height = (
|
||||
self.row_height if self.multiline else max(height, self._text_size)
|
||||
)
|
||||
if line_height == 0:
|
||||
line_height = default_line_height
|
||||
line_metrics.append((line, width, line_height))
|
||||
max_line_width = max(max_line_width, width)
|
||||
total_height += line_height
|
||||
|
||||
if not line_metrics:
|
||||
return
|
||||
|
||||
textpos_y = area_height - self.y_screen - self.height
|
||||
|
||||
@@ -76,16 +108,55 @@ class BL_UI_Label(BL_UI_Widget):
|
||||
if self._valign == "CENTER":
|
||||
y -= height // 2
|
||||
# bottom could be here but there's no reason for it
|
||||
|
||||
first_line_height = line_metrics[0][2]
|
||||
|
||||
if self.background and (max_line_width > 0 or total_height > 0):
|
||||
pad_x, pad_y = self._padding_tuple()
|
||||
text_top = y + first_line_height
|
||||
text_bottom = text_top - total_height
|
||||
left = x - pad_x
|
||||
right = x + max_line_width + pad_x
|
||||
top = text_top + pad_y
|
||||
bottom = text_bottom - pad_y
|
||||
self._draw_background_rect(left, right, bottom, top)
|
||||
|
||||
current_y = y
|
||||
if not self.multiline:
|
||||
blf.position(font_id, x, y, 0)
|
||||
|
||||
blf.position(font_id, x, current_y, 0)
|
||||
blf.color(font_id, r, g, b, a)
|
||||
|
||||
blf.draw(font_id, self._text)
|
||||
else:
|
||||
lines = self._text.split("\n")
|
||||
for line in lines:
|
||||
blf.position(font_id, x, y, 0)
|
||||
for line, _, line_height in line_metrics:
|
||||
blf.position(font_id, x, current_y, 0)
|
||||
blf.color(font_id, r, g, b, a)
|
||||
blf.draw(font_id, line)
|
||||
y -= self.row_height
|
||||
current_y -= line_height
|
||||
|
||||
def _padding_tuple(self) -> Tuple[float, float]:
|
||||
pad = self.padding
|
||||
if isinstance(pad, (list, tuple)):
|
||||
if len(pad) == 0:
|
||||
return (0.0, 0.0)
|
||||
if len(pad) == 1:
|
||||
value = float(pad[0])
|
||||
return (value, value)
|
||||
return (float(pad[0]), float(pad[1]))
|
||||
value = float(pad)
|
||||
return (value, value)
|
||||
|
||||
def _draw_background_rect(self, left, right, bottom, top):
|
||||
vertices = (
|
||||
(left, top),
|
||||
(left, bottom),
|
||||
(right, bottom),
|
||||
(right, top),
|
||||
)
|
||||
indices = ((0, 1, 2), (0, 2, 3))
|
||||
gpu.state.blend_set("ALPHA")
|
||||
self.shader.bind()
|
||||
self.shader.uniform_float("color", self._bg_color)
|
||||
batch = batch_for_shader(
|
||||
self.shader, "TRIS", {"pos": vertices}, indices=indices
|
||||
)
|
||||
batch.draw(self.shader)
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ schema_version = "1.0.0"
|
||||
|
||||
id = "blenderkit"
|
||||
type = "add-on"
|
||||
version = "3.18.0-251121" # X.Y.Z-YYYYMMDD, must have a dash instead of a dot
|
||||
version = "3.18.1-251219" # X.Y.Z-YYYYMMDD, must have a dash instead of a dot
|
||||
|
||||
name = "BlenderKit Online Asset Library"
|
||||
tagline = "Drag & drop of assets from the community driven library"
|
||||
|
||||
+1
-1
@@ -91,7 +91,7 @@ def ensure_minimal_data(data: Optional[dict] = None) -> dict:
|
||||
return data
|
||||
|
||||
|
||||
def ensure_minimal_data_class(data_class):
|
||||
def ensure_minimal_data_class(data_class: datas.SearchData) -> datas.SearchData:
|
||||
"""Ensure that the data send to the BlenderKit-Client contains:
|
||||
- app_id is the process ID of the Blender instance, so BlenderKit-client can return reports to the correct instance.
|
||||
- api_key is the authentication token for the BlenderKit server, so BlenderKit-Client can authenticate the user.
|
||||
|
||||
+26
-7
@@ -19,16 +19,35 @@
|
||||
Module colors defines color palette for BlenderKit UI.
|
||||
"""
|
||||
|
||||
# UI Colors
|
||||
|
||||
TOP_BAR_BLUE = (0.2, 0.25, 0.4, 1.0)
|
||||
"""TOP_BAR_BLUE Color for BlenderKit UI top bar."""
|
||||
|
||||
WHITE = (1, 1, 1, 0.9)
|
||||
|
||||
TEXT = (0.9, 0.9, 0.9, 0.6)
|
||||
GREEN = (0.9, 1, 0.9, 0.6)
|
||||
RED = (1, 0.5, 0.5, 0.8)
|
||||
BLUE = (0.8, 0.8, 1, 0.8)
|
||||
TEXT = (0.9, 0.9, 0.9, 0.9)
|
||||
"""TEXT Color for BlenderKit UI text."""
|
||||
|
||||
PURPLE = (0.8, 0.4, 1.0, 1.0) # Full Plan purple
|
||||
GREEN_FREE = (0.4, 0.8, 0.4, 1.0) # Green for free addons
|
||||
"""Color for validator reports."""
|
||||
TEXT_DIM = (0.8, 0.8, 0.8, 0.9)
|
||||
|
||||
GREEN = (0.9, 1, 0.9, 0.6)
|
||||
"""GREEN Color for validator reports."""
|
||||
|
||||
RED = (1, 0.5, 0.5, 0.8)
|
||||
"""RED Color for validator reports."""
|
||||
|
||||
BLUE = (0.8, 0.8, 1, 0.8)
|
||||
"""BLUE Color for validator reports."""
|
||||
|
||||
GREEN_PRICE = (0.42, 0.49, 0.19, 1.0)
|
||||
"""Emerald Green to be used on "discounted" add-ons."""
|
||||
|
||||
PURPLE_PRICE = (0.59, 0.05, 0.82, 1.0)
|
||||
"""Lavender Purple to be used on "for sale" add-ons."""
|
||||
|
||||
ORANGE_FULL = (0.702, 0.349, 0.208, 1.0)
|
||||
"""Burnt Orange associated with full plan assets and add-ons."""
|
||||
|
||||
GRAY = (0.7, 0.7, 0.7, 0.6)
|
||||
"""Default color for debug reports."""
|
||||
|
||||
+19
-8
@@ -109,8 +109,10 @@ def get_addon_installation_status(asset_data):
|
||||
if not is_enabled:
|
||||
extension_module_name = f"bl_ext.www_blenderkit_com.{extension_id}"
|
||||
is_enabled = extension_module_name in enabled_addons
|
||||
bk_logger.info(
|
||||
f"Checking extension format: {extension_module_name} -> enabled: {is_enabled}"
|
||||
bk_logger.debug(
|
||||
"Checking extension format: %s -> enabled: %s",
|
||||
extension_module_name,
|
||||
is_enabled,
|
||||
)
|
||||
|
||||
# Also try other possible repository name formats
|
||||
@@ -210,10 +212,15 @@ def get_addon_installation_status(asset_data):
|
||||
if "blenderkit" in addon.lower() or addon.endswith(extension_id)
|
||||
]
|
||||
if blenderkit_addons:
|
||||
bk_logger.info(f"Found BlenderKit-related enabled addons: {blenderkit_addons}")
|
||||
bk_logger.debug(
|
||||
"Found BlenderKit-related enabled addons: %s", blenderkit_addons
|
||||
)
|
||||
|
||||
bk_logger.info(
|
||||
f"Addon status check for '{extension_id}': installed={is_installed}, enabled={is_enabled}"
|
||||
bk_logger.debug(
|
||||
"Addon status check for '%s': installed=%s, enabled=%s",
|
||||
extension_id,
|
||||
is_installed,
|
||||
is_enabled,
|
||||
)
|
||||
|
||||
return {
|
||||
@@ -877,8 +884,13 @@ def append_asset(asset_data, **kwargs): # downloaders=[], location=None,
|
||||
if asset_blender_version < (4, 3, 0) and bpy.app.version >= (4, 3, 0):
|
||||
brush.asset_clear()
|
||||
brush.asset_mark()
|
||||
brush.icon_filepath = asset_thumb_path
|
||||
|
||||
if bpy.app.version <= (4, 5, 0):
|
||||
brush.icon_filepath = asset_thumb_path
|
||||
else:
|
||||
# load asset thumbnail into brush if it's not already present
|
||||
if brush.preview is None:
|
||||
with bpy.context.temp_override(id=brush):
|
||||
bpy.ops.ed.lib_id_load_custom_preview(filepath=asset_thumb_path)
|
||||
# set the brush active
|
||||
if bpy.context.view_layer.objects.active.mode == "SCULPT":
|
||||
if bpy.app.version < (4, 3, 0):
|
||||
@@ -897,7 +909,6 @@ def append_asset(asset_data, **kwargs): # downloaders=[], location=None,
|
||||
relative_asset_identifier=f"Brush{os.sep}{brush.name}"
|
||||
)
|
||||
# TODO add grease pencil brushes!
|
||||
|
||||
# bpy.context.tool_settings.image_paint.brush = brush
|
||||
asset_main = brush
|
||||
|
||||
|
||||
+6
-1
@@ -16,7 +16,7 @@
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
from logging import INFO, WARN
|
||||
from logging import DEBUG, INFO, WARN
|
||||
from os import environ
|
||||
from subprocess import Popen
|
||||
from typing import Any, Optional
|
||||
@@ -59,6 +59,11 @@ BKIT_AUTHORS: dict[int, datas.UserProfile] = {}
|
||||
"""All loaded profiles of other users. Current user is also present in stripped down version. Key is the UserProfile.id."""
|
||||
|
||||
LOGGING_LEVEL_BLENDERKIT = INFO
|
||||
|
||||
# read special DEBUG env var to set logging level to DEBUG
|
||||
if environ.get("BLENDERKIT_DEBUG", "0") == "1":
|
||||
LOGGING_LEVEL_BLENDERKIT = DEBUG
|
||||
|
||||
LOGGING_LEVEL_IMPORTED = WARN
|
||||
PREFS = {}
|
||||
|
||||
|
||||
+4
-2
@@ -27,8 +27,10 @@ import bpy
|
||||
icon_collections = {}
|
||||
|
||||
icons_read = {
|
||||
"fp.png": "free",
|
||||
"flp.png": "full",
|
||||
"free_plan.png": "free",
|
||||
"full_plan.png": "full",
|
||||
"promo_sale_symbol.png": "promo_sale_symbol",
|
||||
"sale_purple.png": "for_sale",
|
||||
"trophy.png": "trophy",
|
||||
"dumbbell.png": "dumbbell",
|
||||
"cc0.png": "cc0",
|
||||
|
||||
+13
@@ -24,6 +24,7 @@ import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from functools import lru_cache
|
||||
|
||||
import bpy
|
||||
|
||||
@@ -39,6 +40,9 @@ BLENDERKIT_REPORT_URL = f"{global_vars.SERVER}/usage_report"
|
||||
BLENDERKIT_USER_ASSETS_URL = f"{global_vars.SERVER}/my-assets"
|
||||
BLENDERKIT_MANUAL_URL = "https://youtu.be/0P8ZjfbUjeA"
|
||||
BLENDERKIT_MODEL_UPLOAD_INSTRUCTIONS_URL = f"{global_vars.SERVER}/docs/upload/"
|
||||
BLENDERKIT_PRINTABLE_UPLOAD_INSTRUCTIONS_URL = (
|
||||
f"{global_vars.SERVER}/docs/upload-printables/"
|
||||
)
|
||||
BLENDERKIT_MATERIAL_UPLOAD_INSTRUCTIONS_URL = (
|
||||
f"{global_vars.SERVER}/docs/uploading-material/"
|
||||
)
|
||||
@@ -463,6 +467,8 @@ def get_addon_file(subpath=""):
|
||||
script_path = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
|
||||
# cache this for minor performance boost
|
||||
@lru_cache(maxsize=128)
|
||||
def get_addon_thumbnail_path(name):
|
||||
global script_path
|
||||
# fpath = os.path.join(p, subpath)
|
||||
@@ -474,6 +480,13 @@ def get_addon_thumbnail_path(name):
|
||||
return os.path.join(script_path, subpath)
|
||||
|
||||
|
||||
# cache this for minor performance boost
|
||||
@lru_cache(maxsize=128)
|
||||
def icon_path_exists(path: str) -> bool:
|
||||
"""Cached version of os.path.exists"""
|
||||
return os.path.exists(path)
|
||||
|
||||
|
||||
def get_config_dir_path() -> str:
|
||||
"""Get the path to the config directory in global_dir."""
|
||||
global_dir = bpy.context.preferences.addons[__package__].preferences.global_dir # type: ignore
|
||||
|
||||
+17
-16
@@ -203,7 +203,7 @@ 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 assest"
|
||||
bl_label = "BlenderKit bookmark assets"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
asset_id: StringProperty( # type: ignore[valid-type]
|
||||
@@ -233,28 +233,29 @@ class SetBookmark(bpy.types.Operator):
|
||||
ratings_utils.store_rating_local(
|
||||
self.asset_id, rating_type="bookmarks", value=bookmark_value
|
||||
)
|
||||
client_lib.send_rating(self.asset_id, "bookmarks", bookmark_value)
|
||||
client_lib.send_rating(self.asset_id, "bookmarks", str(bookmark_value))
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
def rating_menu_draw(self, context):
|
||||
layout = self.layout
|
||||
## NOT USED ANYMORE
|
||||
# def rating_menu_draw(self, context):
|
||||
# layout = self.layout
|
||||
|
||||
ui_props = context.window_manager.blenderkitUI
|
||||
sr = search.get_search_results()
|
||||
# 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])
|
||||
# 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"
|
||||
# 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"]
|
||||
# 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).
|
||||
|
||||
+86
-13
@@ -20,6 +20,7 @@ import copy
|
||||
import json
|
||||
import logging
|
||||
import math
|
||||
from functools import lru_cache
|
||||
import os
|
||||
import re
|
||||
import unicodedata
|
||||
@@ -43,6 +44,7 @@ from . import (
|
||||
image_utils,
|
||||
paths,
|
||||
reports,
|
||||
search_price,
|
||||
resolutions,
|
||||
tasks_queue,
|
||||
utils,
|
||||
@@ -53,6 +55,51 @@ bk_logger = logging.getLogger(__name__)
|
||||
search_tasks = {}
|
||||
|
||||
|
||||
def _inject_user_price_data(assets: list[dict]) -> None:
|
||||
"""Augment search results with per-user pricing info when available."""
|
||||
if not assets:
|
||||
bk_logger.debug("User price lookup skipped: empty assets list.")
|
||||
return
|
||||
|
||||
version_uuids: list[str] = [ass["id"] for ass in assets]
|
||||
if not version_uuids:
|
||||
bk_logger.debug("User price lookup skipped: empty version UUIDs list.")
|
||||
return
|
||||
|
||||
try:
|
||||
price_response = search_price.query_user_price(
|
||||
version_uuids=version_uuids,
|
||||
page_size=len(version_uuids),
|
||||
)
|
||||
except Exception as exc:
|
||||
bk_logger.warning("Failed to fetch user prices: %s", exc)
|
||||
return
|
||||
|
||||
if not price_response:
|
||||
bk_logger.debug(
|
||||
"User price lookup skipped: %s",
|
||||
price_response,
|
||||
)
|
||||
return
|
||||
|
||||
price_by_uuid: dict[str, dict] = {}
|
||||
for entry in price_response:
|
||||
version_uuid = entry.get("versionUuid") # maybe assetUuid ?
|
||||
if not version_uuid:
|
||||
continue
|
||||
price_by_uuid[version_uuid] = entry
|
||||
|
||||
if not price_by_uuid:
|
||||
return
|
||||
|
||||
for asset in assets:
|
||||
version_uuid = asset["id"]
|
||||
price_info = price_by_uuid.get(version_uuid)
|
||||
if not price_info:
|
||||
continue
|
||||
asset["userPrice"] = price_info["discountedPrice"]
|
||||
|
||||
|
||||
def update_ad(ad):
|
||||
if not ad.get("assetBaseId"):
|
||||
try:
|
||||
@@ -136,22 +183,23 @@ def check_clipboard():
|
||||
"""
|
||||
global last_clipboard
|
||||
try: # could be problematic on Linux
|
||||
current_clipboard = bpy.context.window_manager.clipboard
|
||||
current_clipboard = str(bpy.context.window_manager.clipboard)
|
||||
except Exception as e:
|
||||
bk_logger.warning(f"Failed to get clipboard: {e}")
|
||||
return
|
||||
|
||||
if current_clipboard == last_clipboard:
|
||||
return
|
||||
last_clipboard = current_clipboard
|
||||
|
||||
asset_type_index = last_clipboard.find("asset_type:")
|
||||
asset_type_index = current_clipboard.find("asset_type:")
|
||||
if asset_type_index == -1:
|
||||
return
|
||||
|
||||
if not last_clipboard.startswith("asset_base_id:"):
|
||||
if not current_clipboard.startswith("asset_base_id:"):
|
||||
return
|
||||
|
||||
last_clipboard = current_clipboard
|
||||
|
||||
asset_type_string = current_clipboard[asset_type_index:].lower()
|
||||
if asset_type_string.find("model") > -1:
|
||||
target_asset_type = "MODEL"
|
||||
@@ -169,6 +217,10 @@ def check_clipboard():
|
||||
target_asset_type = "NODEGROUP"
|
||||
elif asset_type_string.find("addon") > -1 or asset_type_string.find("add-on") > -1:
|
||||
target_asset_type = "ADDON"
|
||||
else:
|
||||
bk_logger.debug("Clipboard does not contain valid asset type.")
|
||||
return
|
||||
|
||||
ui_props = bpy.context.window_manager.blenderkitUI
|
||||
if ui_props.asset_type != target_asset_type:
|
||||
ui_props.asset_type = target_asset_type # switch asset type before placing keywords, so it does not search under wrong asset type
|
||||
@@ -341,7 +393,7 @@ def handle_search_task(task: client_tasks.Task) -> bool:
|
||||
return True
|
||||
|
||||
# don't do anything while dragging - this could switch asset during drag, and make results list length different,
|
||||
# causing a lot of throuble.
|
||||
# causing a lot of trouble.
|
||||
if bpy.context.window_manager.blenderkitUI.dragging: # type: ignore[attr-defined]
|
||||
return False
|
||||
|
||||
@@ -403,6 +455,10 @@ def handle_search_task(task: client_tasks.Task) -> bool:
|
||||
asset for asset in result_field if asset.get("downloaded", 0) > 0
|
||||
]
|
||||
|
||||
# TODO: if ever needed, implement for other future types
|
||||
if result_field:
|
||||
_inject_user_price_data(result_field)
|
||||
|
||||
# Store results in history step
|
||||
history_step["search_results"] = result_field
|
||||
history_step["search_results_orig"] = task.result
|
||||
@@ -710,7 +766,7 @@ def query_to_url(
|
||||
scene_uuid: str = "",
|
||||
page_size: int = 15,
|
||||
) -> str:
|
||||
"""Build a new search request by parsing query dictionaty into appropriate URL.
|
||||
"""Build a new search request by parsing query dictionary into appropriate URL.
|
||||
Also modifies query and adds some stuff in there which is very misleading anti-pattern.
|
||||
TODO: just convert to URL here and move the sorting and adding of params to separate function.
|
||||
https://www.blenderkit.com/api/v1/search/
|
||||
@@ -1012,6 +1068,7 @@ def filter_addon_search_results(search_results, filter_installed_only=False):
|
||||
def add_search_process(
|
||||
query, get_next: bool, page_size: int, next_url: str, history_id: str
|
||||
):
|
||||
"""Initialize search task and add it to the task queue."""
|
||||
global search_tasks
|
||||
addon_version = utils.get_addon_version()
|
||||
blender_version = utils.get_blender_version()
|
||||
@@ -1232,7 +1289,7 @@ def search(get_next=False, query=None, author_id=""):
|
||||
|
||||
|
||||
def clean_filters():
|
||||
"""Cleanup filters in case search needs to be reset, typicaly when asset id is copy pasted."""
|
||||
"""Cleanup filters in case search needs to be reset, typically when asset id is copy pasted."""
|
||||
sprops = utils.get_search_props()
|
||||
ui_props = bpy.context.window_manager.blenderkitUI
|
||||
ui_props.property_unset("own_only")
|
||||
@@ -1551,6 +1608,13 @@ class SearchOperator(Operator):
|
||||
default="Runs search and displays the asset bar at the same time"
|
||||
)
|
||||
|
||||
force_clear: BoolProperty( # type: ignore[valid-type]
|
||||
name="Force clear keywords, before programmatic search",
|
||||
description="Force clear keywords before search",
|
||||
default=True,
|
||||
options={"SKIP_SAVE"},
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def description(cls, context, properties):
|
||||
return properties.tooltip
|
||||
@@ -1564,16 +1628,25 @@ class SearchOperator(Operator):
|
||||
if self.esc:
|
||||
bpy.ops.view3d.close_popup_button("INVOKE_DEFAULT")
|
||||
ui_props = bpy.context.window_manager.blenderkitUI
|
||||
|
||||
search_keywords = str(ui_props.search_keywords)
|
||||
|
||||
if self.keywords != "":
|
||||
search_keywords = self.keywords
|
||||
|
||||
# remove all search keywords if force_clear is set
|
||||
if self.force_clear:
|
||||
# self.force_clear = False # reset the force clear
|
||||
search_keywords = ""
|
||||
|
||||
if self.author_id != "":
|
||||
bk_logger.info(f"Author ID: {self.author_id}")
|
||||
# if there is already an author id in the search keywords, remove it first, the author_id can be any so
|
||||
# use regex to find it
|
||||
ui_props.search_keywords = re.sub(
|
||||
r"\+author_id:\d+", "", ui_props.search_keywords
|
||||
)
|
||||
ui_props.search_keywords += f"+author_id:{self.author_id}"
|
||||
if self.keywords != "":
|
||||
ui_props.search_keywords = self.keywords
|
||||
search_keywords = re.sub(r"\+author_id:\d+", "", search_keywords)
|
||||
search_keywords += f"+author_id:{self.author_id}"
|
||||
|
||||
ui_props.search_keywords = search_keywords
|
||||
|
||||
search(get_next=self.get_next)
|
||||
|
||||
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
from typing import Iterable, List, Optional, Tuple
|
||||
|
||||
from . import client_lib, paths, utils
|
||||
|
||||
|
||||
def _normalize_version_uuid_list(values: Optional[Iterable[str]]) -> List[str]:
|
||||
if values is None:
|
||||
return []
|
||||
|
||||
normalized: List[str] = []
|
||||
for value in values:
|
||||
if not value:
|
||||
continue
|
||||
as_str = str(value)
|
||||
if as_str not in normalized:
|
||||
normalized.append(as_str)
|
||||
return normalized
|
||||
|
||||
|
||||
def query_user_price(
|
||||
version_uuids: list[str] = [],
|
||||
page_size: int = 15,
|
||||
timeout: Tuple[float, float] = (1, 30),
|
||||
) -> dict:
|
||||
"""Return results for price lookup of multiple asset versions.
|
||||
|
||||
The server endpoint now expects a POST body with `version_uuids`, so we keep
|
||||
the helper focused on returning the correct URL alongside the JSON payload
|
||||
that should be sent in the request.
|
||||
"""
|
||||
|
||||
if isinstance(version_uuids, str):
|
||||
version_uuids = [version_uuids]
|
||||
|
||||
version_uuid_list = _normalize_version_uuid_list(version_uuids)
|
||||
if page_size > 0:
|
||||
version_uuid_list = version_uuid_list[:page_size]
|
||||
|
||||
payload: dict = {"version_uuids": version_uuid_list}
|
||||
|
||||
url = f"{paths.BLENDERKIT_API}/cart/request-price-bulk/"
|
||||
|
||||
if not payload["version_uuids"]:
|
||||
raise ValueError("No version UUIDs provided for price lookup.")
|
||||
|
||||
headers = utils.get_simple_headers()
|
||||
headers.setdefault("Content-Type", "application/json")
|
||||
|
||||
response = client_lib.blocking_request(
|
||||
url,
|
||||
"POST",
|
||||
headers,
|
||||
json_data=payload,
|
||||
timeout=timeout,
|
||||
)
|
||||
search_results = response.json()
|
||||
return search_results
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+8
-6
@@ -120,12 +120,14 @@ def handle_failed_reports(exception: Exception) -> float:
|
||||
|
||||
@bpy.app.handlers.persistent
|
||||
def client_communication_timer():
|
||||
"""Recieve all responses from Client and run according followup commands.
|
||||
"""Receive all responses from Client and run according followup commands.
|
||||
This function is the only one responsible for keeping the Client up and running.
|
||||
"""
|
||||
global pending_tasks
|
||||
bk_logger.debug("Getting tasks from Client")
|
||||
search.check_clipboard()
|
||||
bk_logger.log(5, "Getting tasks from Client")
|
||||
user_preferences = bpy.context.preferences.addons[__package__].preferences
|
||||
if user_preferences.use_clipboard_scan:
|
||||
search.check_clipboard()
|
||||
results = list()
|
||||
try:
|
||||
results = client_lib.get_reports(os.getpid())
|
||||
@@ -141,7 +143,7 @@ def client_communication_timer():
|
||||
wm = bpy.context.window_manager
|
||||
wm.blenderkitUI.logo_status = "logo"
|
||||
|
||||
bk_logger.debug("Handling tasks")
|
||||
bk_logger.log(5, "Handling tasks")
|
||||
results_converted_tasks = []
|
||||
|
||||
# convert to task type
|
||||
@@ -166,8 +168,8 @@ def client_communication_timer():
|
||||
for task in results_converted_tasks:
|
||||
handle_task(task)
|
||||
|
||||
bk_logger.debug("Task handling finished")
|
||||
delay = bpy.context.preferences.addons[__package__].preferences.client_polling
|
||||
bk_logger.log(5, "Task handling finished")
|
||||
delay = user_preferences.client_polling
|
||||
if len(download.download_tasks) > 0:
|
||||
return min(0.2, delay)
|
||||
return delay
|
||||
|
||||
-50
@@ -1,50 +0,0 @@
|
||||
.updater_install_popup
|
||||
.updater_check_now
|
||||
.updater_update_now
|
||||
.updater_update_target
|
||||
.updater_install_manually
|
||||
.updater_update_successful
|
||||
.updater_restore_backup
|
||||
.updater_ignore
|
||||
.end_background_check
|
||||
view3d.asset_drag_drop
|
||||
object.blenderkit_auto_tags
|
||||
object.blenderkit_generate_thumbnail
|
||||
object.blenderkit_regenerate_thumbnail
|
||||
object.blenderkit_generate_material_thumbnail
|
||||
object.blenderkit_regenerate_material_thumbnail
|
||||
object.kill_bg_process
|
||||
wm.blenderkit_login
|
||||
wm.blenderkit_logout
|
||||
wm.blenderkit_login_cancel
|
||||
scene.blenderkit_addon_manager
|
||||
scene.blenderkit_addon_choice
|
||||
scene.blenderkit_download_kill
|
||||
scene.blenderkit_download
|
||||
wm.blenderkit_bookmark_asset
|
||||
wm.blenderkit_mark_notification_read
|
||||
wm.blenderkit_mark_notifications_read_all
|
||||
wm.blenderkit_open_notification_target
|
||||
wm.blenderkit_upvote_comment
|
||||
wm.blenderkit_is_private_comment
|
||||
wm.blenderkit_post_comment
|
||||
wm.logo_status
|
||||
wm.show_notifications
|
||||
wm.blenderkit_join_discord
|
||||
wm.blenderkit_welcome
|
||||
wm.blenderkit_open_system_directory
|
||||
wm.blenderkit_asset_popup
|
||||
view3d.blenderkit_set_comment_reply_id
|
||||
view3d.blenderkit_set_category_origin
|
||||
view3d.blenderkit_clear_search_keywords
|
||||
view3d.close_popup_button
|
||||
wm.blenderkit_popup_dialog
|
||||
wm.blenderkit_url_dialog
|
||||
wm.blenderkit_login_dialog
|
||||
wm.blenderkit_nodegroup_drop_dialog
|
||||
object.blenderkit_particles_drop
|
||||
object.blenderkit_data_trasnfer
|
||||
wm.modal_timer_operator
|
||||
view3d.run_assetbar_start_modal
|
||||
view3d.run_assetbar_fix_context
|
||||
wm.blenderkit_fast_metadata
|
||||
+34
-7
@@ -83,7 +83,7 @@ def draw_upload_common(layout, props, asset_type, context):
|
||||
url = "" # paths.BLENDERKIT_NODEGROUP_UPLOAD_INSTRUCTIONS_URL
|
||||
if asset_type == "PRINTABLE":
|
||||
url = (
|
||||
paths.BLENDERKIT_MODEL_UPLOAD_INSTRUCTIONS_URL
|
||||
paths.BLENDERKIT_PRINTABLE_UPLOAD_INSTRUCTIONS_URL
|
||||
) # Reuse model instructions since prints are similar
|
||||
if asset_type == "ADDON":
|
||||
asset_type_text = asset_type
|
||||
@@ -1721,6 +1721,10 @@ class VIEW3D_PT_blenderkit_import_settings(Panel):
|
||||
layout.prop(preferences, "resolution")
|
||||
# layout.prop(props, 'unpack_files')
|
||||
|
||||
# general settings
|
||||
# show toggle for clipboard scan
|
||||
layout.prop(preferences, "use_clipboard_scan")
|
||||
|
||||
|
||||
def deferred_set_name(props, expected_obj_name):
|
||||
"""Deferred timer to set empty name of uploaded asset to active Object's name.
|
||||
@@ -2725,15 +2729,26 @@ class AssetPopupCard(bpy.types.Operator, ratings_utils.RatingProperties):
|
||||
is_free = self.asset_data.get("isFree")
|
||||
|
||||
# Get pricing info from extensions cache
|
||||
user_price = self.asset_data.get("userPrice")
|
||||
base_price = self.asset_data.get("basePrice")
|
||||
is_for_sale = self.asset_data.get("isForSale")
|
||||
|
||||
if self.asset_data["isPrivate"]:
|
||||
text = "Private"
|
||||
self.draw_property(box, "Access", text, icon="LOCKED")
|
||||
elif is_for_sale and not can_download and user_price and base_price:
|
||||
text = f"${user_price} (Not purchased)"
|
||||
icon = pcoll["for_sale"]
|
||||
self.draw_property(
|
||||
box,
|
||||
"Price",
|
||||
text,
|
||||
icon_value=icon.icon_id,
|
||||
tooltip="This addon is for sale but you haven't purchased it yet.\nPrice shown is your price / base price",
|
||||
)
|
||||
elif is_for_sale and not can_download and base_price:
|
||||
text = f"${base_price} (Not purchased)"
|
||||
icon = pcoll["full"]
|
||||
icon = pcoll["for_sale"]
|
||||
self.draw_property(
|
||||
box,
|
||||
"Price",
|
||||
@@ -2742,8 +2757,8 @@ class AssetPopupCard(bpy.types.Operator, ratings_utils.RatingProperties):
|
||||
tooltip="This addon is for sale but you haven't purchased it yet",
|
||||
)
|
||||
elif is_for_sale and can_download and base_price:
|
||||
text = f"${base_price} (Purchased)"
|
||||
icon = pcoll["full"]
|
||||
text = f"Purchased"
|
||||
icon = pcoll["for_sale"]
|
||||
self.draw_property(
|
||||
box,
|
||||
"Price",
|
||||
@@ -2752,7 +2767,7 @@ class AssetPopupCard(bpy.types.Operator, ratings_utils.RatingProperties):
|
||||
tooltip="You have purchased this addon",
|
||||
)
|
||||
elif not is_free and not is_for_sale:
|
||||
text = "Full plan required"
|
||||
text = "Full plan"
|
||||
icon = pcoll["full"]
|
||||
self.draw_property(
|
||||
box,
|
||||
@@ -2991,7 +3006,7 @@ class AssetPopupCard(bpy.types.Operator, ratings_utils.RatingProperties):
|
||||
)
|
||||
op.tooltip = "Search all assets by this author.\nShortcut: Hover over the asset in the asset bar and press 'A'." # type: ignore[attr-defined]
|
||||
op.esc = True # type: ignore[attr-defined]
|
||||
op.keywords = "" # type: ignore[attr-defined]
|
||||
op.keywords = "" # type: ignore[attr-defined] # must not be empty otherwise search will use previous keywords
|
||||
op.author_id = str(author_id) # type: ignore[attr-defined]
|
||||
|
||||
button_row = button_row.row(align=True)
|
||||
@@ -3222,7 +3237,7 @@ class AssetPopupCard(bpy.types.Operator, ratings_utils.RatingProperties):
|
||||
# name_row.label(text='>')
|
||||
|
||||
name_row.label(text=aname)
|
||||
push_op_left(name_row, strength=3)
|
||||
push_op_left(name_row, strength=1)
|
||||
op = name_row.operator("view3d.close_popup_button", text="", icon="CANCEL")
|
||||
|
||||
def draw_comment_response(self, context, layout, comment_id):
|
||||
@@ -3568,6 +3583,18 @@ class SetCategoryOperatorLastInPopupCard(SetCategoryOperatorOrigin):
|
||||
bl_idname = "view3d.blenderkit_set_category_in_popup_card_last"
|
||||
|
||||
|
||||
class ToggleClipboardScan(bpy.types.Operator):
|
||||
"""Toggle whether asset links are set from clipboard when copied."""
|
||||
|
||||
bl_idname = "wm.blenderkit_toggle_clipboard_scan"
|
||||
bl_label = "Toggle Clipboard Scan"
|
||||
|
||||
def execute(self, context):
|
||||
user_preferences = bpy.context.preferences.addons[__package__].preferences
|
||||
user_preferences.use_clipboard_scan = not user_preferences.use_clipboard_scan
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ClearSearchKeywords(bpy.types.Operator):
|
||||
"""Clear search keywords"""
|
||||
|
||||
|
||||
+37
-1
@@ -339,6 +339,25 @@ def get_active_asset_by_type(asset_type="model"):
|
||||
return None
|
||||
|
||||
|
||||
def get_equivalent_datablock(asset_type, name):
|
||||
"""Get the datablock that blocks us from renaming the asset, and rename it to something a bit else."""
|
||||
if asset_type == "MATERIAL":
|
||||
return bpy.data.materials.get(name)
|
||||
elif asset_type == "OBJECT":
|
||||
return bpy.data.objects.get(name)
|
||||
elif asset_type == "SCENE":
|
||||
return bpy.data.scenes.get(name)
|
||||
elif asset_type == "HDR":
|
||||
return bpy.data.images.get(name)
|
||||
elif asset_type == "BRUSH":
|
||||
return bpy.data.brushes.get(name)
|
||||
elif asset_type == "NODEGROUP":
|
||||
return bpy.data.node_groups.get(name)
|
||||
elif asset_type == "ADDON":
|
||||
return bpy.data.addons.get(name)
|
||||
return None
|
||||
|
||||
|
||||
def get_active_asset():
|
||||
scene = bpy.context.scene
|
||||
ui_props = bpy.context.window_manager.blenderkitUI
|
||||
@@ -970,12 +989,17 @@ def get_dimensions(obs):
|
||||
return dim, bbmin, bbmax
|
||||
|
||||
|
||||
def get_headers(api_key: str = "") -> dict[str, str]:
|
||||
def get_simple_headers() -> dict[str, str]:
|
||||
headers = {
|
||||
"accept": "application/json",
|
||||
"Platform-Version": platform.platform(),
|
||||
"addon-version": f"{global_vars.VERSION[0]}.{global_vars.VERSION[1]}.{global_vars.VERSION[2]}.{global_vars.VERSION[3]}",
|
||||
}
|
||||
return headers
|
||||
|
||||
|
||||
def get_headers(api_key: str = "") -> dict[str, str]:
|
||||
headers = get_simple_headers()
|
||||
if api_key != "":
|
||||
headers["Authorization"] = f"Bearer {api_key}"
|
||||
|
||||
@@ -1129,6 +1153,18 @@ def name_update(props, context=None):
|
||||
asset = get_active_asset()
|
||||
if asset.name != fname: # Here we actually rename assets datablocks
|
||||
asset.name = fname # change name of active object to upload Name
|
||||
# we need to set the name back for proper appending later
|
||||
if asset.name != fname and re.search(r"\.\d+$", asset.name) is not None:
|
||||
# - because assets end up with .001, .002, etc. names sometimes.
|
||||
# first, let's get the datablock that blocks us from renaming the asset, and rename it to something a bit else:
|
||||
# we need to ge the equivalent datablock ,
|
||||
# then we can swap those names around.
|
||||
datablock = get_equivalent_datablock(ui_props.asset_type, fname)
|
||||
if datablock is not None:
|
||||
datablock.name = fname + "_temprename"
|
||||
replace_name = asset.name
|
||||
asset.name = fname
|
||||
datablock.name = replace_name
|
||||
|
||||
|
||||
def fmt_dimensions(p):
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"last_check": "2026-03-16 11:19:55.639825",
|
||||
"backup_date": "January-12-2026",
|
||||
"last_check": "2026-04-02 12:41:20.491300",
|
||||
"backup_date": "April-2-2026",
|
||||
"update_ready": false,
|
||||
"ignore": false,
|
||||
"just_restored": false,
|
||||
|
||||
Reference in New Issue
Block a user