759 lines
28 KiB
Python
759 lines
28 KiB
Python
"""
|
|
This is a separate library that overrides the extension_draw_item method from Blender extensions list display.
|
|
The original code is in the bl_extension_ui.py file in the Blender source code.
|
|
The override library can be placed in multiple addons, and the override should happen only once.
|
|
The override is done by replacing the original method with the new one, and backing up the original method.
|
|
The original method is then called from the new method, with the same arguments, but with the new code added.
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import time
|
|
import logging
|
|
|
|
from . import icons
|
|
|
|
import bpy
|
|
import bl_pkg.bl_extension_ui as exui
|
|
from bpy.props import IntProperty, StringProperty
|
|
from bpy.types import Operator
|
|
|
|
|
|
EXTENSIONS_API_URL = "https://www.blenderkit.com/api/v1/extensions/"
|
|
|
|
bk_logger = logging.getLogger(__name__)
|
|
|
|
|
|
# --- New Modal Operator ---
|
|
class BK_OT_buy_extension_and_watch(Operator):
|
|
"""Opens URL to buy extension and starts a modal timer to refresh repo periodically."""
|
|
|
|
bl_idname = "bk.buy_extension_and_watch"
|
|
bl_label = "Buy Extension Online and Watch"
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
url: StringProperty(
|
|
name="URL",
|
|
description="Website URL to open",
|
|
)
|
|
repo_index: IntProperty(
|
|
name="Repository Index",
|
|
description="Index of the repository to refresh",
|
|
default=-1,
|
|
)
|
|
|
|
_timer = None
|
|
_last_refresh_time = 0
|
|
_start_time = 0
|
|
_refresh_interval = 60 # seconds
|
|
_max_duration = 300 # seconds (5 minutes timeout)
|
|
|
|
def execute(self, context):
|
|
if not self.url:
|
|
self.report({"ERROR"}, "No URL specified.")
|
|
return {"CANCELLED"}
|
|
if self.repo_index == -1:
|
|
self.report({"ERROR"}, "No repository index specified.")
|
|
return {"CANCELLED"}
|
|
|
|
# Open the URL
|
|
try:
|
|
bpy.ops.wm.url_open(url=self.url)
|
|
bk_logger.info("Opening buy URL: %s.", self.url)
|
|
except Exception as e:
|
|
self.report({"ERROR"}, f"Could not open URL: {e}")
|
|
# Don't cancel, maybe the user still wants the refresh?
|
|
# Decide if you want modal to continue even if URL fails
|
|
|
|
# Add modal handler and timer
|
|
wm = context.window_manager
|
|
self._timer = wm.event_timer_add(
|
|
1.0, window=context.window
|
|
) # Check every second
|
|
wm.modal_handler_add(self)
|
|
self._start_time = time.time()
|
|
self._last_refresh_time = (
|
|
self._start_time
|
|
) # Initialize to avoid immediate refresh
|
|
bk_logger.info(
|
|
"Started watching repository index %s for updates.", self.repo_index
|
|
)
|
|
if context and context.area:
|
|
context.area.tag_redraw() # Update UI to show operator is running if needed
|
|
return {"RUNNING_MODAL"}
|
|
|
|
def modal(self, context, event):
|
|
current_time = time.time()
|
|
|
|
# --- Exit Conditions ---
|
|
# 1. User closed Preferences or changed area
|
|
if context.area is None or context.area.type != "PREFERENCES":
|
|
bk_logger.info("Preferences window closed or changed, stopping watcher.")
|
|
self.cancel(context)
|
|
return {"CANCELLED"}
|
|
|
|
# 2. Timeout
|
|
if current_time - self._start_time > self._max_duration:
|
|
bk_logger.info("Watcher timed out, stopping.")
|
|
self.cancel(context)
|
|
return {"CANCELLED"}
|
|
|
|
# 3. User cancellation
|
|
if event.type in {"RIGHTMOUSE", "ESC"}:
|
|
bk_logger.info("Watcher cancelled by user.")
|
|
self.cancel(context)
|
|
return {"CANCELLED"}
|
|
|
|
# --- Timer Logic ---
|
|
if event.type == "TIMER":
|
|
# Check if refresh interval has passed
|
|
if current_time - self._last_refresh_time >= self._refresh_interval:
|
|
bk_logger.info(
|
|
"Refresh interval reached, attempting sync for repo index %s...",
|
|
self.repo_index,
|
|
)
|
|
try:
|
|
# Check if repo still exists at that index
|
|
if self.repo_index < len(context.preferences.extensions.repos):
|
|
bpy.ops.extensions.repo_sync(repo_index=self.repo_index)
|
|
bk_logger.info(
|
|
"repo_sync called for index %s.", self.repo_index
|
|
)
|
|
else:
|
|
bk_logger.info(
|
|
"Repository index %s no longer valid.", self.repo_index
|
|
)
|
|
# Optionally cancel here if repo is gone
|
|
except:
|
|
# This might fail if another operation is in progress
|
|
bk_logger.exception("extensions.repo_sync failed.")
|
|
finally:
|
|
self._last_refresh_time = (
|
|
current_time # Reset timer regardless of success
|
|
)
|
|
|
|
return {"PASS_THROUGH"} # Pass other events through
|
|
|
|
def cancel(self, context):
|
|
if self._timer:
|
|
wm = context.window_manager
|
|
wm.event_timer_remove(self._timer)
|
|
self._timer = None
|
|
bk_logger.info("Watcher timer removed.")
|
|
if context and context.area:
|
|
context.area.tag_redraw() # Update UI
|
|
|
|
|
|
# --- End New Modal Operator ---
|
|
|
|
|
|
def redraw_preferences_once():
|
|
"""Tag the redraw on the Blender preferences.
|
|
Meant to be registered as a timer, runs just once.
|
|
"""
|
|
for window in bpy.context.window_manager.windows:
|
|
screen = window.screen
|
|
if not screen:
|
|
continue
|
|
for area in screen.areas:
|
|
if area.type != "PREFERENCES":
|
|
continue
|
|
for region in area.regions:
|
|
if region.type in {"UI", "WINDOW"}:
|
|
region.tag_redraw()
|
|
|
|
return None
|
|
|
|
|
|
def extension_draw_item_blenderkit(
|
|
layout,
|
|
*,
|
|
pkg_id, # `str`
|
|
item_local, # `PkgManifest_Normalized | None`
|
|
item_remote, # `PkgManifest_Normalized | None`
|
|
is_enabled, # `bool`
|
|
is_outdated, # `bool`
|
|
show, # `bool`.
|
|
mark, # `bool | None`.
|
|
# General vars.
|
|
repo_index, # `int`
|
|
repo_item, # `RepoItem`
|
|
operation_in_progress, # `bool`
|
|
extensions_warnings, # `dict[str, list[str]]`
|
|
show_developer_ui, # `bool`
|
|
):
|
|
### BlenderKit cache code
|
|
# Ensure cache is up-to-date before drawing
|
|
cache_reloaded = ensure_repo_cache()
|
|
if cache_reloaded:
|
|
# If cache was just reloaded, tag UI for redraw
|
|
# as UILayout doesn't have tag_redraw we call a custom function
|
|
if bpy.app.timers.is_registered(redraw_preferences_once):
|
|
bpy.app.timers.unregister(redraw_preferences_once)
|
|
bpy.app.timers.register(redraw_preferences_once, first_interval=0.01)
|
|
bk_logger.info("Cache reloaded, tagging preferences for redraw.")
|
|
|
|
# check if the cache is already in the window manager
|
|
if "blenderkit_extensions_repo_cache" not in bpy.context.window_manager:
|
|
# Log if cache is missing after trying to ensure it
|
|
bk_logger.info(
|
|
"Extension cache not available in window_manager after ensure_repo_cache call."
|
|
)
|
|
# Optionally draw a minimal representation or return early to avoid errors
|
|
# For now, just return to avoid potential errors accessing bk_ext_cache
|
|
return
|
|
|
|
bk_ext_cache = bpy.context.window_manager["blenderkit_extensions_repo_cache"]
|
|
bk_cache_pkg = bk_ext_cache.get(pkg_id[:32], None)
|
|
### end of BlenderKit cache code
|
|
item = item_local or item_remote
|
|
is_installed = item_local is not None
|
|
has_remote = repo_item.remote_url != ""
|
|
|
|
if item_remote is not None:
|
|
pkg_block = item_remote.block
|
|
else:
|
|
pkg_block = None
|
|
|
|
if is_enabled:
|
|
item_warnings = extensions_warnings.get(
|
|
exui.pkg_repo_module_prefix(repo_item) + pkg_id, []
|
|
)
|
|
else:
|
|
item_warnings = []
|
|
|
|
# Left align so the operator text isn't centered.
|
|
colsub = layout.column()
|
|
row = colsub.row(align=True)
|
|
|
|
if show:
|
|
props = row.operator(
|
|
"extensions.package_show_clear", text="", icon="DOWNARROW_HLT", emboss=False
|
|
)
|
|
else:
|
|
props = row.operator(
|
|
"extensions.package_show_set", text="", icon="RIGHTARROW", emboss=False
|
|
)
|
|
props.pkg_id = pkg_id
|
|
props.repo_index = repo_index
|
|
|
|
if mark is not None:
|
|
if mark:
|
|
props = row.operator(
|
|
"extensions.package_mark_clear",
|
|
text="",
|
|
icon="RADIOBUT_ON",
|
|
emboss=False,
|
|
)
|
|
else:
|
|
props = row.operator(
|
|
"extensions.package_mark_set",
|
|
text="",
|
|
icon="RADIOBUT_OFF",
|
|
emboss=False,
|
|
)
|
|
props.pkg_id = pkg_id
|
|
props.repo_index = repo_index
|
|
|
|
sub = row.row()
|
|
sub.active = is_enabled
|
|
# Without checking `is_enabled` here, there is no way for the user to know if an extension
|
|
# is enabled or not, which is useful to show - when they may be considering removing/updating
|
|
# extensions based on them being used or not.
|
|
if pkg_block or item_warnings:
|
|
sub.label(text=item.name, icon="ERROR", translate=False)
|
|
else:
|
|
sub.label(text=item.name, translate=False)
|
|
|
|
# Add a top-level row so `row_right` can have a grayed out button/label
|
|
# without graying out the menu item since# that is functional.
|
|
row_right_toplevel = row.row(align=True)
|
|
if operation_in_progress:
|
|
row_right_toplevel.enabled = False
|
|
row_right_toplevel.alignment = "RIGHT"
|
|
row_right = row_right_toplevel.row()
|
|
row_right.alignment = "RIGHT"
|
|
|
|
if has_remote and (item_remote is not None):
|
|
if pkg_block is not None:
|
|
row_right.label(text="Blocked ")
|
|
elif is_installed:
|
|
if is_outdated:
|
|
props = row_right.operator("extensions.package_install", text="Update")
|
|
props.repo_index = repo_index
|
|
props.pkg_id = pkg_id
|
|
props.enable_on_install = is_enabled
|
|
else:
|
|
### BlenderKit specific code
|
|
# blenderkit logo icon
|
|
pcoll = icons.icon_collections["main"]
|
|
icon_value = pcoll["logo"].icon_id
|
|
# row.label(text="", icon_value=icon_value)
|
|
# only enable install for those for whom it's available
|
|
if bk_cache_pkg is not None:
|
|
# Free , purchased and subscribed add-ons, probably also private add-ons
|
|
if bk_cache_pkg.get("can_download") is True:
|
|
# if the addon is also for sale, it means the user purchased it and we write "install purchased"
|
|
if bk_cache_pkg.get("is_for_sale") is True:
|
|
props = row_right.operator(
|
|
"extensions.package_install",
|
|
text="Install purchased",
|
|
icon_value=icon_value,
|
|
)
|
|
else:
|
|
props = row_right.operator(
|
|
"extensions.package_install",
|
|
text="Install",
|
|
icon_value=icon_value,
|
|
)
|
|
props.repo_index = repo_index
|
|
props.pkg_id = pkg_id
|
|
|
|
# Full plan addons
|
|
elif not bk_cache_pkg.get("is_free") and not bk_cache_pkg.get(
|
|
"is_for_sale"
|
|
):
|
|
# open website to subscribe
|
|
props = row_right.operator(
|
|
"wm.url_open",
|
|
text="Subscribe to Full Plan",
|
|
icon_value=icon_value,
|
|
)
|
|
props.url = "https://www.blenderkit.com/plans/pricing/"
|
|
|
|
# Paid addons get a buy button and lead to their website link
|
|
else:
|
|
# Use the new modal operator
|
|
props = row_right.operator(
|
|
BK_OT_buy_extension_and_watch.bl_idname, # Use bl_idname
|
|
text=f"Buy online ${bk_cache_pkg.get('base_price')}",
|
|
icon_value=icon_value,
|
|
)
|
|
props.url = bk_cache_pkg.get("website", "") # Pass URL
|
|
props.repo_index = repo_index # Pass repo index
|
|
### end of BlenderKit specific code
|
|
else:
|
|
# Right space for alignment with the button.
|
|
if has_remote and (item_remote is None):
|
|
# There is a local item with no remote
|
|
row_right.label(text="Orphan ")
|
|
|
|
row_right.active = False
|
|
|
|
row_right = row_right_toplevel.row(align=True)
|
|
row_right.alignment = "RIGHT"
|
|
row_right.separator()
|
|
|
|
# NOTE: Keep space between any buttons and this menu to prevent stray clicks accidentally running install.
|
|
# The separator is around together with the align to give some space while keeping the button and the menu
|
|
# still close-by. Used `extension_path` so the menu can access "this" extension.
|
|
row_right.context_string_set(
|
|
"extension_path", "{:s}.{:s}".format(repo_item.module, pkg_id)
|
|
)
|
|
row_right.menu("USERPREF_MT_extensions_item", text="", icon="DOWNARROW_HLT")
|
|
|
|
if show:
|
|
import os
|
|
|
|
from bpy.app.translations import pgettext_iface as iface_
|
|
|
|
col = layout.column()
|
|
|
|
row = col.row()
|
|
row.active = is_enabled
|
|
|
|
# The full tagline may be multiple lines (not yet supported by Blender's UI).
|
|
row.label(text=" {:s}.".format(item.tagline), translate=False)
|
|
|
|
col.separator(type="LINE")
|
|
|
|
col_info = layout.column()
|
|
col_info.active = is_enabled
|
|
split = col_info.split(factor=0.15)
|
|
col_a = split.column()
|
|
col_b = split.column()
|
|
col_a.alignment = "RIGHT"
|
|
|
|
if pkg_block is not None:
|
|
col_a.label(text="Blocked")
|
|
col_b.label(text=pkg_block.reason, translate=False)
|
|
|
|
if item_warnings:
|
|
col_a.label(text="Warning")
|
|
col_b.label(text=item_warnings[0])
|
|
if len(item_warnings) > 1:
|
|
for value in item_warnings[1:]:
|
|
col_a.label(text="")
|
|
col_b.label(text=value)
|
|
# pylint: disable-next=undefined-loop-variable
|
|
|
|
if value := (item_remote or item_local).website:
|
|
col_a.label(text="Website")
|
|
col_b.split(factor=0.5).operator(
|
|
"wm.url_open",
|
|
text=exui.domain_extract_from_url(value),
|
|
icon="URL",
|
|
).url = value
|
|
del value
|
|
|
|
if item.type == "add-on":
|
|
col_a.label(text="Permissions")
|
|
# WARNING: while this is documented to be a dict, old packages may contain a list of strings.
|
|
# As it happens dictionary keys & list values both iterate over string,
|
|
# however we will want to show the dictionary values eventually.
|
|
if value := item.permissions:
|
|
col_b.label(
|
|
text=", ".join([iface_(x).title() for x in value]), translate=False
|
|
)
|
|
else:
|
|
col_b.label(text="No permissions specified")
|
|
del value
|
|
|
|
col_a.label(text="Maintainer")
|
|
col_b.label(text=item.maintainer, translate=False)
|
|
|
|
col_a.label(text="Version")
|
|
if is_outdated:
|
|
col_b.label(
|
|
text=iface_("{:s} ({:s} available)").format(
|
|
item.version, item_remote.version
|
|
),
|
|
translate=False,
|
|
)
|
|
else:
|
|
col_b.label(text=item.version, translate=False)
|
|
|
|
if has_remote and (item_remote is not None):
|
|
col_a.label(text="Size")
|
|
col_b.label(
|
|
text=exui.size_as_fmt_string(item_remote.archive_size), translate=False
|
|
)
|
|
|
|
col_a.label(text="License")
|
|
col_b.label(text=item.license, translate=False)
|
|
|
|
col_a.label(text="Repository")
|
|
col_b.label(text=repo_item.name, translate=False)
|
|
|
|
if is_installed:
|
|
col_a.label(text="Path")
|
|
col_b.label(text=os.path.join(repo_item.directory, pkg_id), translate=False)
|
|
|
|
|
|
def extension_draw_item_override(
|
|
layout,
|
|
*,
|
|
pkg_id, # `str`
|
|
item_local, # `PkgManifest_Normalized | None`
|
|
item_remote, # `PkgManifest_Normalized | None`
|
|
is_enabled, # `bool`
|
|
is_outdated, # `bool`
|
|
show, # `bool`.
|
|
mark, # `bool | None`.
|
|
# General vars.
|
|
repo_index, # `int`
|
|
repo_item, # `RepoItem`
|
|
operation_in_progress, # `bool`
|
|
extensions_warnings, # `dict[str, list[str]]`
|
|
show_developer_ui=False, # `bool`
|
|
):
|
|
# filter by verification state, only for blenderkit repository
|
|
if repo_item.remote_url == EXTENSIONS_API_URL:
|
|
extension_draw_item_blenderkit(
|
|
layout,
|
|
pkg_id=pkg_id,
|
|
item_local=item_local,
|
|
item_remote=item_remote,
|
|
is_enabled=is_enabled,
|
|
is_outdated=is_outdated,
|
|
show=show,
|
|
mark=mark,
|
|
repo_index=repo_index,
|
|
repo_item=repo_item,
|
|
operation_in_progress=operation_in_progress,
|
|
extensions_warnings=extensions_warnings,
|
|
show_developer_ui=show_developer_ui,
|
|
)
|
|
return True
|
|
|
|
# show developer ui only needs to be passed since blender 4.4
|
|
if bpy.app.version >= (4, 4):
|
|
exui.extension_draw_item_original(
|
|
layout,
|
|
pkg_id=pkg_id,
|
|
item_local=item_local,
|
|
item_remote=item_remote,
|
|
is_enabled=is_enabled,
|
|
is_outdated=is_outdated,
|
|
show=show,
|
|
mark=mark,
|
|
repo_index=repo_index,
|
|
repo_item=repo_item,
|
|
operation_in_progress=operation_in_progress,
|
|
extensions_warnings=extensions_warnings,
|
|
show_developer_ui=show_developer_ui,
|
|
)
|
|
else:
|
|
exui.extension_draw_item_original(
|
|
layout,
|
|
pkg_id=pkg_id,
|
|
item_local=item_local,
|
|
item_remote=item_remote,
|
|
is_enabled=is_enabled,
|
|
is_outdated=is_outdated,
|
|
show=show,
|
|
mark=mark,
|
|
repo_index=repo_index,
|
|
repo_item=repo_item,
|
|
operation_in_progress=operation_in_progress,
|
|
extensions_warnings=extensions_warnings,
|
|
)
|
|
|
|
return True
|
|
|
|
|
|
def override_draw_function():
|
|
if hasattr(exui, "extension_draw_item_original"):
|
|
return False
|
|
exui.extension_draw_item_original = exui.extension_draw_item
|
|
exui.extension_draw_item = extension_draw_item_override
|
|
return True
|
|
|
|
|
|
def get_repository_by_url(url: str):
|
|
"""Get the repository by its remote URL, from registered blenderkit Extension repositories."""
|
|
for r in bpy.context.preferences.extensions.repos:
|
|
if r.remote_url == url:
|
|
return r
|
|
return None
|
|
|
|
|
|
def clear_repo_cache():
|
|
"""Clear the repository cache."""
|
|
wm = bpy.context.window_manager
|
|
cache_key = "blenderkit_extensions_repo_cache"
|
|
if cache_key in wm:
|
|
del wm[cache_key]
|
|
|
|
|
|
def ensure_repo_cache():
|
|
r"""
|
|
Reads the .json file blender stores in \extensions\www_blenderkit_com\.blender_ext
|
|
and parses it to a dict from json, we can use it then for drawing purposes and have the extra data BlenderKit api provides.
|
|
Checks the modification time of the cache file and reloads it if necessary.
|
|
"""
|
|
reloaded_flag = False # Track if we actually reloaded
|
|
wm = bpy.context.window_manager
|
|
cache_key = "blenderkit_extensions_repo_cache"
|
|
mtime_key = "blenderkit_extensions_repo_cache_mtime"
|
|
|
|
blenderkit_repository = get_repository_by_url(EXTENSIONS_API_URL)
|
|
if blenderkit_repository is None:
|
|
# If repo doesn't exist, clear cache if it exists in window manager
|
|
if cache_key in wm:
|
|
del wm[cache_key]
|
|
bk_logger.info("Cleared stale extension cache for missing repository.")
|
|
if mtime_key in wm:
|
|
del wm[mtime_key]
|
|
bk_logger.debug("Repository not found, exiting check.")
|
|
return False # No repo, nothing loaded
|
|
|
|
# get the path to the cache file which is in repository directory under /.blender_ext/index.json
|
|
cache_file = os.path.join(
|
|
blenderkit_repository.directory, ".blender_ext", "index.json"
|
|
)
|
|
|
|
current_mtime = None
|
|
try:
|
|
if os.path.exists(cache_file):
|
|
current_mtime = os.path.getmtime(cache_file)
|
|
except OSError as e: # Handle potential race condition or permission issue
|
|
bk_logger.exception("Could not get modification time for %s.", cache_file)
|
|
# Clear cache if we can't verify its freshness? Safer approach.
|
|
if cache_key in wm:
|
|
del wm[cache_key]
|
|
bk_logger.info("Cleared extension cache due to mtime access error.")
|
|
if mtime_key in wm:
|
|
del wm[mtime_key]
|
|
return False # Error, nothing loaded
|
|
|
|
stored_mtime = wm.get(mtime_key, None)
|
|
|
|
# --- Determine if reload is needed ---
|
|
should_reload = False
|
|
if cache_key not in wm:
|
|
if current_mtime is not None: # Only load if file actually exists
|
|
should_reload = True # Cache doesn't exist, need initial load.
|
|
else:
|
|
# Cache doesn't exist and file doesn't exist/accessible. Fall through to check if we need to clear.
|
|
pass
|
|
|
|
elif current_mtime is None:
|
|
# Cache exists in wm, but file is gone/inaccessible. Clear stale cache.
|
|
del wm[cache_key]
|
|
if mtime_key in wm:
|
|
del wm[mtime_key]
|
|
return False # Cleared stale cache, did not load new data
|
|
|
|
elif cache_key not in wm and current_mtime is None:
|
|
# Cache doesn't exist, and file doesn't exist. Nothing to do or load.
|
|
return False
|
|
|
|
elif (
|
|
cache_key in wm and (stored_mtime is None or stored_mtime != current_mtime)
|
|
) or (
|
|
cache_key not in wm and current_mtime is not None
|
|
): # Reload if cache exists and is outdated, OR if cache doesn't exist but file does
|
|
should_reload = True # Cache exists but is outdated or missing mtime.
|
|
|
|
if not should_reload:
|
|
# Cache exists and is up-to-date
|
|
return False # Nothing reloaded
|
|
|
|
# --- (Re)Load cache ---
|
|
try:
|
|
with open(cache_file, "r", encoding="utf-8") as f: # Specify encoding
|
|
data_str = f.read()
|
|
data = json.loads(data_str)
|
|
|
|
# store the data as a dict with keys being the package names (first 32 chars)
|
|
new_cache = {}
|
|
for pkg in data.get(
|
|
"data", []
|
|
): # Handle case where 'data' key might be missing
|
|
if (
|
|
isinstance(pkg, dict) and "id" in pkg
|
|
): # Ensure pkg is a dict and 'id' key exists
|
|
new_cache[pkg["id"][:32]] = pkg
|
|
else:
|
|
bk_logger.info("Skipping invalid package entry in cache: %s.", pkg)
|
|
|
|
wm[cache_key] = new_cache
|
|
wm[mtime_key] = current_mtime # Update mtime only on successful load
|
|
|
|
reloaded_flag = True # Mark that we reloaded successfully
|
|
|
|
except json.JSONDecodeError:
|
|
bk_logger.warning(
|
|
"Error decoding JSON from %s. Cache not loaded/updated.", cache_file
|
|
)
|
|
# Clear potentially corrupt cache? Or leave old one? Clearing is safer.
|
|
if cache_key in wm:
|
|
del wm[cache_key]
|
|
bk_logger.info("Cleared cache due to JSON error.")
|
|
if mtime_key in wm:
|
|
del wm[mtime_key]
|
|
except Exception:
|
|
bk_logger.exception("Error reading or processing cache file %s.", cache_file)
|
|
# Clear potentially corrupt cache?
|
|
if cache_key in wm:
|
|
del wm[cache_key]
|
|
bk_logger.info("Cleared cache due to file processing error.")
|
|
if mtime_key in wm:
|
|
del wm[mtime_key]
|
|
|
|
return reloaded_flag # Return whether cache was actually reloaded
|
|
|
|
|
|
def ensure_repo_order():
|
|
"""Ensure order of repositories in Blender's preferences."""
|
|
# get the blenderkit repository
|
|
blenderkit_repository = get_repository_by_url(EXTENSIONS_API_URL)
|
|
if blenderkit_repository is None:
|
|
return
|
|
|
|
# get all repositories
|
|
all_repos = bpy.context.preferences.extensions.repos
|
|
# get all online repositories except blenderkit
|
|
online_repos = [] # need to convert repos to dicts
|
|
remove_online_repos = []
|
|
for r in all_repos:
|
|
if r.remote_url != EXTENSIONS_API_URL and r.remote_url != "":
|
|
|
|
repo_dict = {
|
|
"name": r.name,
|
|
"module": r.module,
|
|
"use_remote_url": r.use_remote_url,
|
|
"remote_url": r.remote_url,
|
|
"use_sync_on_startup": r.use_sync_on_startup,
|
|
"use_cache": r.use_cache,
|
|
"use_access_token": r.use_access_token,
|
|
"access_token": r.access_token,
|
|
"use_custom_directory": r.use_custom_directory,
|
|
"custom_directory": r.custom_directory,
|
|
"enabled": r.enabled,
|
|
}
|
|
online_repos.append(repo_dict)
|
|
remove_online_repos.append(r)
|
|
|
|
# remove all online repositories except blenderkit
|
|
for r in remove_online_repos:
|
|
all_repos.remove(r)
|
|
|
|
# add all other repositories back
|
|
for r in online_repos:
|
|
# complete list of properties of a repository:
|
|
#'access_token', 'custom_directory', 'directory', 'enabled', 'module', 'name', 'remote_url', 'rna_type', 'source', 'use_access_token', 'use_cache', 'use_custom_directory', 'use_remote_url', 'use_sync_on_startup'
|
|
|
|
new_repo = all_repos.new()
|
|
new_repo.name = r["name"]
|
|
new_repo.module = r["module"]
|
|
new_repo.use_remote_url = r["use_remote_url"]
|
|
new_repo.remote_url = r["remote_url"]
|
|
new_repo.use_sync_on_startup = r["use_sync_on_startup"]
|
|
new_repo.use_cache = r["use_cache"]
|
|
new_repo.use_access_token = r["use_access_token"]
|
|
new_repo.access_token = r["access_token"]
|
|
new_repo.use_custom_directory = r["use_custom_directory"]
|
|
new_repo.custom_directory = r["custom_directory"]
|
|
new_repo.enabled = r["enabled"]
|
|
|
|
|
|
def ensure_repository(api_key: str = ""):
|
|
"""Ensure that the blenderkit extensions repository is correctly added in Blender's preferences.
|
|
If the repository is not present, it is added. If the repository is present, but the API key is not set, it is set.
|
|
"""
|
|
|
|
blenderkit_repository = get_repository_by_url(EXTENSIONS_API_URL)
|
|
|
|
if blenderkit_repository is None:
|
|
|
|
blenderkit_repository = bpy.context.preferences.extensions.repos.new()
|
|
blenderkit_repository.name = "www.blenderkit.com"
|
|
blenderkit_repository.module = "www_blenderkit_com"
|
|
blenderkit_repository.use_remote_url = True
|
|
blenderkit_repository.remote_url = EXTENSIONS_API_URL
|
|
blenderkit_repository.use_sync_on_startup = True
|
|
|
|
if api_key != "":
|
|
blenderkit_repository.use_access_token = True
|
|
blenderkit_repository.access_token = api_key
|
|
else:
|
|
# let's try to import blenderkit preferences and get the api key
|
|
# try:
|
|
user_preferences = bpy.context.preferences.addons[__package__].preferences
|
|
api_key = user_preferences.api_key
|
|
if api_key != "":
|
|
blenderkit_repository.use_access_token = True
|
|
blenderkit_repository.access_token = api_key
|
|
else:
|
|
# clear after logout
|
|
blenderkit_repository.use_access_token = False
|
|
blenderkit_repository.access_token = ""
|
|
# pass
|
|
# ensure_repo_order()
|
|
ensure_repo_cache()
|
|
|
|
|
|
def register():
|
|
|
|
ensure_repository()
|
|
override_draw_function()
|
|
bpy.utils.register_class(BK_OT_buy_extension_and_watch) # Register new operator
|
|
|
|
|
|
def unregister():
|
|
exui.extension_draw_item = exui.extension_draw_item_original
|
|
del exui.extension_draw_item_original
|
|
bpy.utils.unregister_class(BK_OT_buy_extension_and_watch) # Unregister new operator
|