2025-07-01

This commit is contained in:
2026-03-17 14:30:01 -06:00
parent f9a22056dd
commit 62b5978595
4579 changed files with 1257472 additions and 0 deletions
@@ -0,0 +1,104 @@
# #### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
from ..modules.poliigon_core.multilingual import _t
from .utils_dlg import (
get_ui_scale,
wrapped_label)
def _build_section_free_user(cTB) -> None:
w_label = cTB.width_draw_ui - 20 * get_ui_scale(cTB)
box_free = cTB.vBase.box()
col = box_free.column()
msg = _t("Access 3,000+ studio quality assets")
wrapped_label(cTB, w_label, msg, col, add_padding=False)
msg = _t("Unused asset balance rolls over each month")
wrapped_label(cTB, w_label, msg, col, add_padding=False, icon="CHECKMARK")
msg = _t("Commercial & personal use license")
wrapped_label(cTB, w_label, msg, col, add_padding=False, icon="CHECKMARK")
msg = _t("Redownload even if your subscription ends")
wrapped_label(cTB, w_label, msg, col, add_padding=False, icon="CHECKMARK")
msg = _t("Cancel or pause at any time in a few clicks")
wrapped_label(cTB, w_label, msg, col, add_padding=False, icon="CHECKMARK")
msg = _t("50% discount for students and teachers")
wrapped_label(cTB, w_label, msg, col, add_padding=False, icon="CHECKMARK")
op = col.operator("poliigon.poliigon_link", text=_t("View Pricing"))
op.mode = "subscribe"
# TODO(Andreas): Figma did not contain any tooltips...
op.tooltip = _t("View Poliigon Pricing Online")
def _build_section_paid_plan(cTB) -> None:
w_label = cTB.width_draw_ui - 20 * get_ui_scale(cTB)
box_free = cTB.vBase.box()
col = box_free.column()
name_plan = cTB.user.plan.plan_name
wrapped_label(cTB, w_label, name_plan, col, add_padding=False)
if not cTB.is_unlimited_user():
credits = cTB.user.plan.plan_credit
msg = _t("Assets per month: {0}").format(credits)
wrapped_label(cTB, w_label, msg, col, add_padding=False)
next_renew = cTB.user.plan.next_subscription_renewal_date
msg = _t("Renewal Date: {0}").format(next_renew)
wrapped_label(cTB, w_label, msg, col, add_padding=False)
is_paused = cTB.is_paused_subscription()
status = _t("Paused") if is_paused else _t("Active")
msg = _t("Status: {0}").format(status)
wrapped_label(cTB, w_label, msg, col, add_padding=False)
op = col.operator("poliigon.poliigon_link", text=_t("View Details"))
op.mode = "credits"
# TODO(Andreas): Figma did not contain any tooltips...
op.tooltip = _t("View Details of Your Plan Online")
def _build_still_loading(cTB) -> None:
box_free = cTB.vBase.box()
col = box_free.column()
w_label = cTB.width_draw_ui - 20 * get_ui_scale(cTB)
wrapped_label(
cTB, w_label, "Fetching user data...", col, add_padding=False)
def build_user(cTB) -> None:
cTB.logger_ui.debug("build_user")
cTB.vBase.label(text=_t("Your Plan"))
if cTB.fetching_user_data:
_build_still_loading(cTB)
return
if cTB.is_free_user() or cTB.user.plan.plan_name is None:
_build_section_free_user(cTB)
else:
_build_section_paid_plan(cTB)
cTB.vBase.separator()
op = cTB.vBase.operator("poliigon.poliigon_user", text=_t("Log Out"))
op.mode = "logout"
op.tooltip = _t("Log Out of Poliigon")
@@ -0,0 +1,47 @@
# #### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
import bpy
from ..modules.poliigon_core.multilingual import _t
def append_poliigon_groups_node_add(self, context) -> None:
"""Appending to add node menu, for Poliigon node groups"""
self.layout.menu('POLIIGON_MT_add_node_groups')
class POLIIGON_MT_add_node_groups(bpy.types.Menu):
"""Menu for the Poliigon Shader node groups"""
bl_space_type = 'NODE_EDITOR'
bl_label = _t("Poliigon Node Groups")
def draw(self, context):
layout = self.layout
col = layout.column(align=True)
if bpy.app.version >= (2, 90):
col.operator("poliigon.add_converter_node",
text=_t("Mosaic")
).node_type = "Mosaic_UV_Mapping"
col.operator("poliigon.add_converter_node",
text=_t("PBR mixer")
).node_type = "Poliigon_Mixer"
col.separator()
@@ -0,0 +1,124 @@
# #### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
from ..modules.poliigon_core.api_remote_control_params import (
CATEGORY_ALL,
KEY_TAB_ONLINE)
from ..modules.poliigon_core.assets import (
AssetType,
ASSET_TYPE_TO_CATEGORY_NAME)
from ..modules.poliigon_core.multilingual import _t
from .utils_dlg import get_ui_scale
# TODO(Andreas): This will be an exciting module in terms of multilingual
# TODO(Andreas): Would like to refactor this module
# @timer
def build_categories(cTB):
cTB.logger_ui.debug("build_categories")
categories_selected = []
categories = []
subcategories = []
if cTB.vAssetType != CATEGORY_ALL:
for _asset_type in cTB.vCategories["poliigon"].keys():
if cTB.vAssetType in [CATEGORY_ALL, _asset_type]:
categories += cTB.vCategories["poliigon"][_asset_type].keys()
categories = sorted(list(set(categories)))
if len(categories) > 0:
category = ""
categories_selected = []
for _idx_sel in range(1, len(cTB.vActiveCat)):
category += "/" + cTB.vActiveCat[_idx_sel]
categories_selected.append(category)
subcategories = [
_cat.split("/")[-1]
for _cat in categories
if _cat.startswith(category) and _cat != category
]
if len(subcategories) > 0:
categories_selected.append("sub")
col_categories = cTB.vBase.column()
width_factor = len(categories_selected) + 1
if cTB.width_draw_ui >= max(width_factor, 2) * 160 * get_ui_scale(cTB):
row_categories = col_categories.row()
else:
row_categories = col_categories
row_sub_cat = row_categories.row(align=True)
type_hdri = ASSET_TYPE_TO_CATEGORY_NAME[AssetType.HDRI]
type_model = ASSET_TYPE_TO_CATEGORY_NAME[AssetType.MODEL]
type_tex = ASSET_TYPE_TO_CATEGORY_NAME[AssetType.TEXTURE]
list_types = [CATEGORY_ALL, type_tex, type_model, type_hdri]
area = cTB.settings["area"]
if cTB.search_free and area == KEY_TAB_ONLINE:
lbl_button_cat = _t("Free")
elif cTB.vAssetType == CATEGORY_ALL:
lbl_button_cat = _t("Select Category")
else:
lbl_button_cat = cTB.vAssetType
op = row_sub_cat.operator(
"poliigon.poliigon_category", text=lbl_button_cat, icon="TRIA_DOWN"
)
op.data = "0@" + "@".join(list_types)
if len(categories_selected) == 0:
col_categories.separator()
return
for _idx_sel, _cat_sel in enumerate(categories_selected):
row_sub_cat = row_categories.row(align=True)
if _idx_sel == 0:
selected_categories = [
_cat.split("/")[-1]
for _cat in categories
if len(_cat.split("/")) == 2
]
elif _cat_sel == "sub":
selected_categories = subcategories
else:
cat_parent = "/".join(_cat_sel.split("/")[:-1])
selected_categories = [
_cat.split("/")[-1]
for _cat in categories
if _cat.startswith(cat_parent) and _cat != cat_parent
]
selected_categories = sorted(list(set(selected_categories)))
lbl_button = _cat_sel.split("/")[-1]
if _cat_sel == "sub":
lbl_button = "All " + cTB.vActiveCat[-1]
selected_categories.insert(0, "All " + cTB.vActiveCat[_idx_sel])
data_op = f"{_idx_sel + 1}@{'@'.join(selected_categories)}"
op = row_sub_cat.operator(
"poliigon.poliigon_category", text=lbl_button, icon="TRIA_DOWN"
)
op.data = data_op
col_categories.separator()
@@ -0,0 +1,282 @@
# #### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
import bpy
from ..modules.poliigon_core.multilingual import _t
from ..modules.poliigon_core.notifications import ActionType
from ..constants import URLS_BLENDER
from .utils_dlg import (
get_ui_scale,
wrapped_label)
# from .. import reporting
def build_mode(url, action, id_notice):
return "notify@{}@{}@{}".format(url, action, id_notice)
def _draw_notification_open_url_single_row(cTB, notice, first_row, icon) -> None:
# Single row with text + button.
# TODO: generalize this for notification message and length,
# and if dismiss is included.
# During SOFT-780 this has been changed for POPUP_MESSAGE in a
# very simplistic way
# (commit: https://github.com/poliigon/poliigon-addon-blender/pull/278/commits/00296ab70288893a023a6705d52eb4505ce36897).
# When addressing this properly,
# make sure to address it for all notification types.
first_row.alert = True
first_row.label(text=notice.title)
first_row.alert = False
op = first_row.operator(
"poliigon.poliigon_link",
icon=icon,
text=notice.label,
)
if notice.tooltip != "":
op.tooltip = notice.tooltip
op.mode = build_mode(
notice.url,
notice.label,
notice.id_notice)
def _draw_notification_open_url_two_rows(
cTB, notice, first_row, main_col, icon) -> None:
# Two rows (or more, if text wrapping).
col = first_row.column(align=True)
col.alert = True
# Empirically found squaring worked best for 1 & 2x displays,
# which accounts for the box+panel padding and the 'x' button.
if notice.allow_dismiss:
padding_width = 32 * get_ui_scale(cTB)
else:
padding_width = 17 * get_ui_scale(cTB)
wrapped_label(
cTB, cTB.width_draw_ui - padding_width, notice.title, col)
col.alert = False
second_row = main_col.row(align=True)
second_row.scale_y = 1.0
op = second_row.operator(
"poliigon.poliigon_link",
icon=icon,
text=notice.label,
)
if notice.tooltip != "":
op.tooltip = notice.tooltip
op.mode = build_mode(
notice.url,
notice.label,
notice.id_notice)
def _draw_notification_open_url(
cTB, notice, first_row, main_col, panel_width, icon) -> None:
# Empirical for width for "Beta addon: [Take survey]" specifically.
single_row_width = 250
if panel_width > single_row_width:
_draw_notification_open_url_single_row(cTB, notice, first_row, icon)
else:
_draw_notification_open_url_two_rows(
cTB, notice, first_row, main_col, icon)
def _draw_notification_update_ready_single_row(cTB, notice, first_row, icon) -> None:
# Single row with text + button.
first_row.alert = True
first_row.label(text=notice.title)
first_row.alert = False
splitrow = first_row.split(factor=0.7, align=True)
splitcol = splitrow.split(align=True)
label = notice.label
if label == "":
label = notice.title
op = splitcol.operator(
"poliigon.poliigon_link",
icon=icon,
text=label,
)
if notice.tooltip != "":
op.tooltip = notice.tooltip
op.mode = build_mode(
notice.download_url, notice.label, notice.id_notice)
splitcol = splitrow.split(align=True)
op = splitcol.operator(
"poliigon.poliigon_link",
text="Logs",
)
# if notice.tooltip is not None:
op.tooltip = _t("See changes in this version")
op.mode = build_mode(
URLS_BLENDER["changelog"], "Logs", notice.id_notice)
def _draw_notification_update_ready_two_rows(
cTB, notice, first_row, main_col, icon) -> None:
# Two rows (or more, if text wrapping).
col = first_row.column(align=True)
col.alert = True
if notice.allow_dismiss:
padding_width = 32 * get_ui_scale(cTB)
else:
padding_width = 17 * get_ui_scale(cTB)
wrapped_label(
cTB, cTB.width_draw_ui - padding_width, notice.title, col)
col.alert = False
label = notice.label
if label == "":
label = notice.title
second_row = main_col.row(align=True)
splitrow = second_row.split(factor=0.7, align=True)
splitcol = splitrow.split(align=True)
op = splitcol.operator(
"poliigon.poliigon_link",
icon=icon,
text=label,
)
if notice.tooltip != "":
op.tooltip = notice.tooltip
op.mode = build_mode(
notice.download_url, notice.label, notice.id_notice)
splitcol = splitrow.split(align=True)
op = splitcol.operator(
"poliigon.poliigon_link",
text="Logs",
)
op.tooltip = _t("See changes in this version")
op.mode = build_mode(
URLS_BLENDER["changelog"], "Logs", notice.id_notice)
def _draw_notification_update_ready(
cTB, notice, first_row, main_col, panel_width, icon) -> None:
# Empirical for width for "Update ready: Download | logs".
single_row_width = 300
if panel_width > single_row_width:
_draw_notification_update_ready_single_row(
cTB, notice, first_row, icon)
else:
_draw_notification_update_ready_two_rows(
cTB, notice, first_row, main_col, icon)
def _draw_notification_popup_message_two_rows(
cTB, notice, first_row, main_col, icon) -> bpy.types.Operator:
# Two rows (or more, if text wrapping).
col = first_row.column(align=True)
col.alert = notice.alert
# Empirically found squaring worked best for 1 & 2x displays,
# which accounts for the box+panel padding and the 'x' button.
if notice.allow_dismiss:
padding_width = 32 * get_ui_scale(cTB)
else:
padding_width = 17 * get_ui_scale(cTB)
wrapped_label(
cTB, cTB.width_draw_ui - padding_width, notice.title, col)
col.alert = False
second_row = main_col.row(align=True)
second_row.scale_y = 1.0
op = second_row.operator(
"poliigon.popup_message",
icon=icon,
text="View",
)
return op
def _draw_notification_popup_message(
cTB, notice, first_row, main_col, panel_width, icon) -> None:
op = _draw_notification_popup_message_two_rows(
cTB, notice, first_row, main_col, icon)
op.message_body = notice.body
op.notice_id = notice.id_notice
if notice.tooltip != "":
op.tooltip = notice.tooltip
if notice.url != "":
op.message_url = notice.url
def _draw_notification_run_operator(cTB, notice, first_row, icon) -> None:
# Single row with only a button.
op = first_row.operator(
"poliigon.notice_operator",
text=notice.title,
icon=icon,
)
op.notice_id = notice.id_notice
op.ops_name = notice.ops_name
op.tooltip = notice.tooltip
# TODO(Andreas): deactivated reporting here, as I needed a third parameter and
# was not able to quickly make handle_draw() work
# @reporting.handle_draw()
def notification_banner(cTB, layout):
"""General purpose notification banner UI draw element."""
notice = cTB.notify.get_top_notice()
if notice is None:
return
box = layout.box()
row = box.row(align=True)
main_col = row.column(align=True)
scale = max(get_ui_scale(cTB), 1)
panel_width = cTB.width_draw_ui / scale
first_row = main_col.row(align=False)
x_row = first_row # x_row is the row to add the x button to, if there.
# Only purpose is to trigger view signal (only once)
cTB.notify.notification_popup(notice, do_signal_view=True)
icon = notice.icon
if icon is None:
icon = "NONE"
if notice.action == ActionType.OPEN_URL:
_draw_notification_open_url(
cTB, notice, first_row, main_col, panel_width, icon)
elif notice.action == ActionType.UPDATE_READY:
_draw_notification_update_ready(
cTB, notice, first_row, main_col, panel_width, icon)
elif notice.action == ActionType.POPUP_MESSAGE:
_draw_notification_popup_message(
cTB, notice, first_row, main_col, panel_width, icon)
elif notice.action == ActionType.RUN_OPERATOR:
_draw_notification_run_operator(cTB, notice, first_row, icon)
else:
main_col.label(text=notice.title)
cTB.logger_ui.error("Invalid notifcation type")
if notice.allow_dismiss:
right_col = x_row.column(align=True)
right_col.operator(
"poliigon.close_notification", icon="X", text="", emboss=False)
layout.separator()
@@ -0,0 +1,139 @@
# #### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
import bpy
from ..modules.poliigon_core.api_remote_control_params import (
KEY_TAB_IMPORTED,
KEY_TAB_MY_ASSETS,
KEY_TAB_ONLINE)
from ..modules.poliigon_core.multilingual import _t
def _draw_unlimited_icon(cTB, *, row: bpy.types.UILayout) -> None:
icon_value = cTB.ui_icons["LOGO_unlimited"].icon_id
op_icon = row.operator(
"poliigon.poliigon_setting", text="", emboss=True, icon_value=icon_value)
op_icon.mode = "show_user"
# TODO(Andreas): Tooltip???
op_icon.tooltip = _t("Switch to your account details")
def _draw_asset_balance(cTB, *, row: bpy.types.UILayout) -> None:
if cTB.is_unlimited_user():
_draw_unlimited_icon(cTB, row=row)
return
# Asset balance
credits = cTB.get_user_credits()
balance_icon = cTB.ui_icons["ICON_asset_balance"].icon_id
if cTB.is_paused_subscription() and credits <= 0:
balance_icon = cTB.ui_icons["ICON_subscription_paused"].icon_id
op_credits = row.operator(
"poliigon.poliigon_setting",
text=str(credits),
icon_value=balance_icon # TODO: use new asset icon
)
op_credits.tooltip = _t(
"Your asset balance shows how many assets you can\n"
"purchase. Free assets and downloading assets you\n"
"already own doesnt affect your balance")
op_credits.mode = "show_user"
def _add_asset_tab(cTB,
row: bpy.types.UILayout,
*,
tab: str,
mode: str,
icon: str = "NONE",
icon_value: int = 0,
tooltip: str = ""
) -> None:
no_user = not cTB.settings["show_user"]
no_settings = not cTB.settings["show_settings"]
no_user_or_settings = no_user and no_settings
col = row.column(align=True)
is_tab_active = cTB.settings["area"] == tab
op = col.operator(
"poliigon.poliigon_setting",
text="",
icon=icon,
icon_value=icon_value,
depress=is_tab_active and no_user_or_settings,
)
op.mode = mode
op.tooltip = tooltip
# @timer
def build_areas(cTB):
cTB.logger_ui.debug("build_areas")
cTB.initial_view_screen()
row = cTB.vBase.row(align=True)
row.scale_x = 1.1
row.scale_y = 1.1
_add_asset_tab(
cTB,
row,
tab=KEY_TAB_ONLINE,
mode="area_poliigon",
icon="HOME",
tooltip=_t("Show Poliigon Assets"))
_add_asset_tab(
cTB,
row,
tab=KEY_TAB_MY_ASSETS,
mode="area_my_assets",
icon_value=cTB.ui_icons["ICON_myassets"].icon_id,
tooltip=_t("Show My Assets"))
_add_asset_tab(
cTB,
row,
tab=KEY_TAB_IMPORTED,
mode="area_imported",
icon="OUTLINER_OB_GROUP_INSTANCE",
tooltip=_t("Show Imported Assets"))
op = row.operator(
"poliigon.poliigon_setting",
text="",
icon_value=cTB.ui_icons["ICON_poliigon"].icon_id,
depress=cTB.settings["show_user"],
)
op.mode = "my_account"
op.tooltip = _t("Show Your Account Details")
row.separator()
row_prefs = row.row(align=True)
row_prefs.alignment = "RIGHT"
_draw_asset_balance(cTB, row=row_prefs)
_ = row_prefs.operator(
"poliigon.open_preferences",
text="",
icon="PREFERENCES",
).set_focus = "all"
cTB.vBase.separator()
@@ -0,0 +1,141 @@
# #### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
from ..modules.poliigon_core.multilingual import _t
from ..modules.poliigon_core.upgrade_content import UpgradeContent
from ..dialogs.utils_dlg import (
get_ui_scale,
wrapped_label)
def _draw_banner(cTB, upgrade_content: UpgradeContent) -> None:
"""Draws the actual banner and its buttons."""
width = cTB.width_draw_ui - 42 * get_ui_scale(cTB)
row = cTB.vBase.row(align=True)
row.scale_x = 1.1
row.scale_y = 1.1
box = row.box()
col = box.column()
text = upgrade_content.banner_primary_text
label = upgrade_content.banner_button_text
key_icon = upgrade_content.icon_path
wrapped_label(
cTB, width=width, text=text, container=col)
row_buttons = col.row(align=True)
if upgrade_content.open_popup:
op = row_buttons.operator(
"poliigon.popup_change_plan",
text=label,
icon_value=cTB.ui_icons[key_icon].icon_id)
op.tooltip = _t("By clicking here, we will change the subscription "
"plan as shown above")
if upgrade_content.allow_dismiss:
op = row_buttons.operator(
"poliigon.popup_change_plan_dismiss",
text="",
icon="PANEL_CLOSE")
else:
op = row_buttons.operator(
"poliigon.poliigon_link",
text=label,
icon_value=cTB.ui_icons[key_icon].icon_id)
op.mode = "subscribe_banner"
def _draw_banner_in_progress(cTB, upgrade_content: UpgradeContent) -> None:
"""Draws an 'upgrade in progress' banner."""
width = cTB.width_draw_ui - 42 * get_ui_scale(cTB)
row = cTB.vBase.row(align=True)
row.scale_x = 1.1
row.scale_y = 1.1
box = row.box()
col = box.column()
primary = upgrade_content.upgrading_primary_text
secondary = upgrade_content.upgrading_secondary_text
text = f"{primary} {secondary}" # three spaces are deliberate
wrapped_label(cTB, width=width, text=text, container=col)
def _draw_banner_finished(cTB, upgrade_content: UpgradeContent) -> None:
"""Draws the final sucess/error banner."""
width = cTB.width_draw_ui - 42 * get_ui_scale(cTB)
row = cTB.vBase.row(align=True)
row.scale_x = 1.1
row.scale_y = 1.1
box = row.box()
col = box.column()
if cTB.msg_plan_upgrade_finished is not None:
text = cTB.msg_plan_upgrade_finished
elif cTB.error_plan_upgrade is not None:
head = upgrade_content.error_popup_title
text = upgrade_content.error_popup_text.format(
cTB.error_plan_upgrade)
text = f"{head}: {text}"
else:
head = upgrade_content.success_popup_title
text = upgrade_content.success_popup_text
text = f"{head}: {text}"
cTB.msg_plan_upgrade_finished = text
wrapped_label(cTB, width=width, text=text, container=col)
row.operator(
"poliigon.banner_finish_dismiss",
text="",
icon="PANEL_CLOSE")
# @timer
def build_upgrade_banner(cTB) -> None:
"""Draws an 'upgrade subscription plan' banner, including a progress
banner and a success/error banner.
"""
cTB.logger_ui.debug("build_upgrade_paths")
if cTB.user is None:
return
if cTB.upgrade_manager is None:
return
if cTB.upgrade_manager.content is None:
return
upgrade_content = cTB.upgrade_manager.content
if cTB.plan_upgrade_finished:
_draw_banner_finished(cTB, upgrade_content)
elif cTB.plan_upgrade_in_progress:
_draw_banner_in_progress(cTB, upgrade_content)
elif cTB.upgrade_manager.check_show_banner():
_draw_banner(cTB, upgrade_content)
else:
return
cTB.vBase.separator()
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,88 @@
# #### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
from ..modules.poliigon_core.multilingual import _t
from .utils_dlg import (
get_ui_scale,
wrapped_label)
# @timer
def build_library(cTB):
cTB.logger_ui.debug("build_library")
factor_space = 1.0 / cTB.width_draw_ui
wrapped_label(
cTB,
cTB.width_draw_ui,
_t("Welcome to the Poliigon Addon!"),
cTB.vBase
)
cTB.vBase.separator()
wrapped_label(
cTB,
cTB.width_draw_ui,
_t("Select where you will store Poliigon assets."),
cTB.vBase
)
cTB.vBase.separator()
box_row = cTB.vBase.box().row()
box_row.separator(factor=factor_space)
col = box_row.column()
box_row.separator(factor=factor_space)
col.label(text=_t("Library Location"))
label_library = cTB.settings["set_library"]
if label_library == "":
label_library = _t("Select Location")
op = col.operator(
"poliigon.poliigon_library",
icon="FILE_FOLDER",
text=label_library,
)
op.mode = "set_library"
op.directory = cTB.settings["set_library"]
op.tooltip = _t("Select Location")
col.separator()
row_confirm = col.row()
row_confirm.scale_y = 1.5
op = row_confirm.operator(
"poliigon.poliigon_setting", text=_t("Confirm"))
op.mode = "set_library"
op.tooltip = _t("Confirm Library location")
col.separator()
wrapped_label(
cTB,
cTB.width_draw_ui - 30 * get_ui_scale(cTB),
_t("You can change this and add more directories in the settings "
"at any time."),
col
)
col.separator()
cTB.vBase.separator()
@@ -0,0 +1,312 @@
# #### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
import bpy
from ..modules.poliigon_core.multilingual import _t
from .utils_dlg import (
get_ui_scale,
wrapped_label)
from ..toolbox import c_Toolbox
ERR_CREDS_FORMAT = _t("Invalid email format/password length.")
# TODO(Andreas): Currently not sure about this error
ERR_LOGIN_TIMEOUT = _t("Login with website timed out, please try again")
def _draw_welcome_or_error(cTB: c_Toolbox, layout: bpy.types.UILayout) -> None:
if cTB.user_invalidated() and not cTB.login_in_progress:
layout.separator()
if cTB.last_login_error == ERR_LOGIN_TIMEOUT:
wrapped_label(
cTB,
cTB.width_draw_ui,
cTB.last_login_error,
layout,
icon="ERROR"
)
else:
wrapped_label(
cTB,
cTB.width_draw_ui,
_t("Warning : You have been logged out as this account was "
"signed in on another device."),
layout,
icon="ERROR"
)
else:
wrapped_label(
cTB,
cTB.width_draw_ui,
_t("Welcome to the Poliigon Addon!"),
layout
)
layout.separator()
def _draw_share_addon_errors(cTB: c_Toolbox,
layout: bpy.types.UILayout,
enabled: bool = True) -> None:
# Show terms of service, optin/out.
row_opt = layout.row()
row_opt.alignment = "LEFT"
row_opt.enabled = enabled
# __spec__.parent since __package__ got deprecated
# Since this module moved into dialogs,
# we need to split off .dialogs
spec_parent = __spec__.parent
spec_parent = spec_parent.split(".")[0]
prefs = bpy.context.preferences.addons.get(spec_parent, None)
row_opt.prop(prefs.preferences, "reporting_opt_in", text="")
twidth = cTB.width_draw_ui - 42 * get_ui_scale(cTB)
wrapped_label(cTB, twidth, _t("Share addon errors / usage"), row_opt)
def _draw_switch_email_login(col: bpy.types.UILayout,
enabled: bool = True) -> None:
row_login_email = col.row()
row_login_email.enabled = enabled
op_login_email = row_login_email.operator("poliigon.poliigon_user",
text=_t("Login via email"),
emboss=False)
op_login_email.mode = "login_switch_to_email"
op_login_email.tooltip = _t("Login via email")
def _draw_browser_login(cTB: c_Toolbox, col: bpy.types.UILayout) -> None:
if cTB.login_in_progress:
_draw_share_addon_errors(cTB, col, enabled=False)
row_buttons = col.row(align=True)
row_buttons.scale_y = 1.25
col1 = row_buttons.column(align=True)
op_login_website = col1.operator("poliigon.poliigon_user",
text=_t("Opening browser..."),
depress=True)
op_login_website.mode = "none"
op_login_website.tooltip = _t("Complete login via opened webpage")
col1.enabled = False
col2 = row_buttons.column(align=True)
op_login_cancel = col2.operator("poliigon.poliigon_user",
text="",
icon="X")
op_login_cancel.mode = "login_cancel"
op_login_cancel.tooltip = _t("Cancel Log In")
col.separator()
_draw_switch_email_login(col, enabled=False)
else:
_draw_share_addon_errors(cTB, col)
row_button = col.row()
row_button.scale_y = 1.25
op_login_website = row_button.operator("poliigon.poliigon_user",
text=_t("Login via Browser"))
op_login_website.mode = "login_with_website"
op_login_website.tooltip = _t("Login via Browser")
col.separator()
_draw_switch_email_login(col)
def _draw_email_login(cTB: c_Toolbox, col: bpy.types.UILayout) -> None:
vProps = bpy.context.window_manager.poliigon_props
col.label(text="Email")
row = col.row(align=True)
row.prop(vProps, "vEmail")
col_x = row.column(align=True)
op = col_x.operator("poliigon.poliigon_setting",
text="",
icon="X")
op.tooltip = _t("Clear Email")
op.mode = "clear_email"
error_credentials = False
has_login_error = cTB.last_login_error is not None
error_login = has_login_error and cTB.last_login_error != ERR_LOGIN_TIMEOUT
if error_login and "@" not in vProps.vEmail:
error_credentials = True
col.separator()
wrapped_label(
cTB,
cTB.width_draw_ui - 40 * get_ui_scale(cTB),
_t("Email format is invalid e.g. john@example.org"),
col,
icon="ERROR")
col.separator()
col.label(text=_t("Password"))
row = col.row(align=True)
if cTB.settings["show_pass"]:
row.prop(vProps, "vPassShow")
vPass = vProps.vPassShow
else:
row.prop(vProps, "vPassHide")
vPass = vProps.vPassHide
col_x = row.column(align=True)
op = col_x.operator("poliigon.poliigon_setting",
text="",
icon="X")
op.tooltip = _t("Clear Password")
op.mode = "clear_pass"
if error_login and len(vPass) < 6:
error_credentials = True
col.separator()
wrapped_label(
cTB,
cTB.width_draw_ui - 40 * get_ui_scale(cTB),
_t("Password should be at least 6 characters."),
col,
icon="ERROR")
col.separator()
_draw_share_addon_errors(cTB, col)
enable_login_button = len(vProps.vEmail) > 0 and len(vPass) > 0
row = col.row()
row.scale_y = 1.25
if cTB.login_in_progress:
op_login = row.operator("poliigon.poliigon_setting",
text=_t("Logging In..."),
depress=enable_login_button)
op_login.mode = "none"
op_login.tooltip = _t("Logging In...")
row.enabled = False
else:
op_login = row.operator("poliigon.poliigon_user",
text=_t("Login via email"))
op_login.mode = "login"
op_login.tooltip = _t("Login via email")
row.enabled = enable_login_button
if cTB.last_login_error == ERR_CREDS_FORMAT:
# Will draw above with more specific messages if condition true, like
# invalid email format or password length.
pass
elif error_login and not error_credentials:
col.separator()
wrapped_label(
cTB,
cTB.width_draw_ui - 40 * get_ui_scale(cTB),
cTB.last_login_error,
col,
icon="ERROR",
)
col.separator()
op_forgot = col.operator("poliigon.poliigon_link",
text=_t("Forgot Password?"),
emboss=False)
op_forgot.mode = "forgot"
op_forgot.tooltip = _t("Reset your Poliigon password")
op_login_website = col.operator("poliigon.poliigon_user",
text=_t("Login via Browser"),
emboss=False)
op_login_website.mode = "login_switch_to_browser"
op_login_website.tooltip = _t("Login via Browser")
def _draw_login(cTB, layout: bpy.types.UILayout) -> None:
spc = 1.0 / cTB.width_draw_ui
box = layout.box()
row = box.row()
row.separator(factor=spc)
col = row.column()
row.separator(factor=spc)
twidth = cTB.width_draw_ui - 42 * get_ui_scale(cTB)
wrapped_label(cTB, twidth, _t("Login"), col)
col.separator()
if cTB.login_mode_browser:
_draw_browser_login(cTB, col)
else:
_draw_email_login(cTB, col)
def _draw_signup(cTB, layout: bpy.types.UILayout) -> None:
wrapped_label(
cTB,
cTB.width_draw_ui,
_t("Don't have an account?"),
layout,
)
op_signup = layout.operator("poliigon.poliigon_link",
text=_t("Sign Up"))
op_signup.mode = "signup"
op_signup.tooltip = _t("Create a Poliigon account")
def _draw_legal(layout: bpy.types.UILayout) -> None:
row = layout.row()
col = row.column(align=True)
op_terms = col.operator("poliigon.poliigon_link",
text=_t("Terms & Conditions"),
emboss=False)
op_terms.tooltip = _t("View the terms and conditions page")
op_terms.mode = "terms"
op_privacy = col.operator("poliigon.poliigon_link",
text=_t("Privacy Policy"),
emboss=False)
op_privacy.tooltip = _t("View the Privacy Policy ")
op_privacy.mode = "privacy"
# @timer
def build_login(cTB):
cTB.logger_ui.debug("build_login")
if cTB.last_login_error is not None:
cTB.login_in_progress = 0
_draw_welcome_or_error(cTB, cTB.vBase)
_draw_login(cTB, cTB.vBase)
cTB.vBase.separator()
_draw_signup(cTB, cTB.vBase)
cTB.vBase.separator()
_draw_legal(cTB.vBase)
@@ -0,0 +1,88 @@
# #### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
from typing import List, Optional
import bpy
from ..modules.poliigon_core.multilingual import _t
from .utils_dlg import (
get_ui_scale,
wrapped_label)
def open_popup(cTB,
title: str = "",
msg: str = "",
buttons: List[str] = [_t("OK")],
commands: List[Optional[str]] = [None],
mode: str = None,
w_limit: int = 0
) -> None:
cTB.logger_ui.debug(f"open_popup mode={mode}, w_limit={w_limit}"
f" title={title}, msg={msg},\n"
f" buttons={buttons},\n"
f" commands={commands}")
def draw(self, context):
layout = self.layout
col = layout.column(align=True)
icon = "INFO"
if mode == "question":
icon = "QUESTION"
elif mode == "error":
icon = "ERROR"
col.label(text=title, icon=icon)
col.separator()
if w_limit == 0:
col.label(text=msg)
else:
wrapped_label(cTB, w_limit * get_ui_scale(cTB), msg, col)
col.separator()
col.separator()
vRow = col.row()
for idx_button in range(len(buttons)):
if commands[idx_button] in [None, "cancel"]:
op = vRow.operator(
"poliigon.poliigon_setting",
text=buttons[idx_button])
op.mode = "none"
elif commands[idx_button] == "credits":
op = vRow.operator(
"poliigon.poliigon_link",
text=_t("Add Credits"),
depress=1)
op.mode = "credits"
elif commands[idx_button] == "open_p4b_url":
op = vRow.operator(
"poliigon.poliigon_link",
text=buttons[idx_button],
depress=1)
op.mode = "p4b"
elif commands[idx_button] == "check_update":
vRow.operator("poliigon.check_update",
text=buttons[idx_button])
bpy.context.window_manager.popover(draw)
@@ -0,0 +1,376 @@
# #### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
import os
import re
import bpy
from ..modules.poliigon_core.api_remote_control_params import (
CATEGORY_ALL,
get_search_key,
KEY_TAB_IMPORTED)
from ..modules.poliigon_core.assets import (
AssetData,
AssetType,
ModelType)
from ..modules.poliigon_core.multilingual import _t
from ..operators.operator_material import set_op_mat_disp_strength
# TODO(SOFT-2421): Deactivated as it seems to have unwanted side effects.
# from .dlg_assets import _draw_button_quick_preview
from .utils_dlg import (
check_convention,
get_model_op_details,
safe_size_apply)
from .. import reporting
def show_quick_menu(
cTB, asset_data: AssetData, hide_detail_view: bool = False) -> None:
"""Generates the quick options menu next to an asset in the UI grid."""
asset_type_data = asset_data.get_type_data()
asset_name = asset_data.asset_name
asset_id = asset_data.asset_id
asset_type = asset_data.asset_type
credits = 0 if asset_data.credits is None else asset_data.credits
is_free = credits == 0
# Configuration
if asset_data.is_purchased:
# If downloading and already purchased.
title = _t("Choose Texture Size")
else:
title = asset_name
in_scene = False
sizes = asset_type_data.get_size_list(local_only=False)
downloaded = asset_type_data.get_size_list(
local_only=True,
addon_convention=cTB._asset_index.addon_convention,
local_convention=asset_data.get_convention(local=True))
key = get_search_key(
tab=KEY_TAB_IMPORTED, search="", category_list=[CATEGORY_ALL])
query_key = cTB._asset_index._query_key_to_tuple(
key, chunk=-1, chunk_size=1000000)
if query_key not in cTB._asset_index.cached_queries:
# The request is probably still be in flight
in_scene = False
elif asset_id in cTB._asset_index.cached_queries[query_key]:
in_scene = True
prefer_blend = cTB.settings["download_prefer_blend"]
link_blend = cTB.link_blend_session
blend_exists = False
fbx_exists = False
if asset_type == AssetType.MODEL:
blend_exists = asset_type_data.has_mesh(
model_type=ModelType.BLEND,
native_only=True,
renderer=None) # None is for legacy cycles models w/o engine name
fbx_exists = asset_type_data.has_mesh(
model_type=ModelType.FBX,
native_only=False,
renderer="")
any_model = blend_exists or fbx_exists
is_linked_blend_import = prefer_blend and link_blend and blend_exists
def _imported_model_extras(
context, layout: bpy.types.UILayout) -> None:
area = cTB.settings["area"]
if area != KEY_TAB_IMPORTED or asset_type != AssetType.MODEL:
return
op = layout.operator(
"poliigon.poliigon_select",
text=_t("Select"),
icon="RESTRICT_SELECT_OFF",
)
op.mode = "model"
op.data = asset_name
op.tooltip = _t("{0}\n(Select all instances)").format(asset_name)
layout.separator()
@reporting.handle_draw()
def draw(self, context):
layout = self.layout
_imported_model_extras(context, layout)
# List the different resolution sizes to provide.
if asset_data.is_purchased or is_free or cTB.is_unlimited_user():
for size in sizes:
if asset_type == AssetType.TEXTURE:
draw_material_sizes(context, size, layout)
elif asset_type == AssetType.MODEL:
draw_model_sizes(context, size, layout)
elif asset_type == AssetType.HDRI:
draw_hdri_sizes(context, size, layout)
else:
label = _t("{0} not implemented yet").format(asset_type)
layout.label(text=label)
# TODO(SOFT-2421): Deactivated as it seems to have unwanted side effects.
# else:
# _draw_button_quick_preview(
# cTB,
# layout_row=layout,
# asset_data=asset_data,
# is_selection=True,
# have_text_label=True
# )
# If else branch is activated, the following separator needs to be
# outside if/else
layout.separator()
op = layout.operator(
"poliigon.open_preferences",
text=_t("Open Import options in Preferences"),
icon="PREFERENCES",
)
op.set_focus = "show_default_prefs"
layout.separator()
# Always show view online and high res previews.
if not hide_detail_view:
# new detail viewer design is unstable on OSX and fails for awhile
# for the remainder of the session. For consistency, we disable it
# outright for all OSX users until a better solution is found.
is_osx = bpy.app.build_platform.lower() == b"darwin"
if bpy.app.version >= (4, 2) and not is_osx:
op_name = "poliigon.detail_view_open"
op_text = _t("View Asset Details")
else:
op_name = "poliigon.view_thumbnail"
op_text = _t("View Large Preview")
op = layout.operator(
op_name,
text=op_text,
icon="OUTLINER_OB_IMAGE",
)
op.asset_id = asset_data.asset_id
op = layout.operator(
"poliigon.poliigon_link",
text=_t("View online"),
icon_value=cTB.ui_icons["ICON_poliigon"].icon_id,
)
op.mode = str(asset_id)
op.tooltip = _t("View on Poliigon.com")
# If already local, support opening the folder location.
if not downloaded:
return
op = layout.operator(
"poliigon.poliigon_folder",
text=_t("Open folder location"),
icon="FILE_FOLDER")
op.asset_id = asset_id
# ... and provide option to sync with asset browser
# TODO(Andreas): Asset Browser integration and AssetIndex
# in_asset_browser = asset_data.get("in_asset_browser", False)
in_asset_browser = asset_data.runtime.is_in_asset_browser()
is_feature_avail = bpy.app.version >= (3, 0)
missing_local_model = asset_type == AssetType.MODEL and not any_model
if not is_feature_avail or missing_local_model:
return
client_starting = cTB.lock_client_start.locked()
layout.separator()
row = layout.row()
op = row.operator(
"poliigon.update_asset_browser",
text=_t("Synchronize with Asset Browser"),
icon="FILE_REFRESH")
op.asset_id = asset_id
row.enabled = not in_asset_browser and not client_starting
def draw_material_sizes(
context, size: str, layout: bpy.types.UILayout) -> None:
"""Draw the menu row for a materials' single resolution size."""
row = layout.row()
imported = f"{asset_name}_{size}" in bpy.data.materials
if asset_data.get_convention() >= 1:
all_expected_maps_for_size = asset_type_data.all_expected_maps_local(
cTB.user.map_preferences, size)
else:
all_expected_maps_for_size = size in downloaded
if imported or all_expected_maps_for_size:
# Action: Load and apply it
if imported:
label = _t("{0} (apply material)").format(size)
tip = _t("Apply {0} Material\n{1}").format(size, asset_name)
elif context.selected_objects:
label = _t("{0} (import + apply)").format(size)
tip = _t("Apply {0} Material\n{1}").format(size, asset_name)
else:
label = _t("{0} (import)").format(size)
tip = _t("Import {0} Material\n{1}").format(size, asset_name)
# If nothing is selected and this size is already importing,
# then there's nothing to do.
if imported and not context.selected_objects:
row.enabled = False
op = row.operator(
"poliigon.poliigon_material",
text=label,
icon="TRACKING_REFINE_BACKWARDS")
# Order is relevant here. vType needs to be set before vSize!
op.asset_id = asset_id
safe_size_apply(cTB, op, size, asset_name)
op.mapping = "UV"
op.scale = 1.0
op.use_16bit = cTB.settings["use_16"]
op.reuse_material = True
op.tooltip = tip
set_op_mat_disp_strength(op, asset_name, op.mode_disp)
else:
# Action: Download
# (for free assets this is purchase + implicit auto-download)
if check_convention(asset_data):
label = _t("{0} (download)").format(size)
else:
label = _t("{size} (Update needed)").format(size)
row.enabled = False
op = row.operator(
"poliigon.poliigon_download",
text=label,
icon="IMPORT")
op.asset_id = asset_id
safe_size_apply(cTB, op, size, asset_name)
if is_free and not asset_data.is_purchased:
op.mode = "purchase"
else:
op.mode = "download"
op.tooltip = _t("Download {0} Material\n{1}").format(
size, asset_name)
def draw_model_sizes(
context, size: str, layout: bpy.types.UILayout) -> None:
"""Draw the menu row for a model's single resolution size."""
row = layout.row()
if size in downloaded and any_model:
# Action: Load and apply it
lod, label, tip = get_model_op_details(
cTB, asset_data, size)
if is_linked_blend_import:
label += _t(" (disable link .blend to import size)")
op = row.operator(
"poliigon.poliigon_model",
text=label,
icon="TRACKING_REFINE_BACKWARDS")
op.asset_id = asset_id
safe_size_apply(cTB, op, size, asset_name)
op.tooltip = tip
op.lod = lod if len(lod) > 0 else "NONE"
row.enabled = not is_linked_blend_import
else:
# Action: Download
if check_convention(asset_data):
label = _t("{0} (download)").format(size)
else:
label = _t("{0} (Update needed)").format(size)
row.enabled = False
op = row.operator(
"poliigon.poliigon_download",
text=label,
icon="IMPORT")
op.asset_id = asset_id
safe_size_apply(cTB, op, size, asset_name)
if is_free and not asset_data.is_purchased:
op.mode = "purchase"
else:
op.mode = "download"
op.tooltip = _t("Download {0} textures\n{1}").format(
size, asset_name)
def draw_hdri_sizes(
context, size: str, layout: bpy.types.UILayout) -> None:
"""Draw the menu row for an HDRI's single resolution size."""
row = layout.row()
size_light = ""
if in_scene:
image_name_light = asset_name + "_Light"
if image_name_light in bpy.data.images.keys():
path_light = bpy.data.images[image_name_light].filepath
filename = os.path.basename(path_light)
match_object = re.search(r"_(\d+K)[_\.]", filename)
size_light = match_object.group(1) if match_object else cTB.settings["hdri"]
if size in downloaded:
# Action: Load and apply it
if size == size_light:
label = _t("{0} (apply HDRI)").format(size)
tip = _t("Apply {0} HDRI\n{1}").format(size, asset_name)
else:
label = _t("{0} (import HDRI)").format(size)
tip = _t("Import {0} HDRI\n{1}").format(size, asset_name)
op = row.operator(
"poliigon.poliigon_hdri",
text=label,
icon="TRACKING_REFINE_BACKWARDS")
op.asset_id = asset_id
safe_size_apply(cTB, op, size, asset_name)
if cTB.settings["hdri_use_jpg_bg"]:
size_bg = cTB.settings["hdrib"]
size_bg = asset_type_data.get_size(
size_bg,
local_only=True,
addon_convention=cTB.addon_convention,
local_convention=asset_data.get_convention(local=True))
op.size_bg = f"{size_bg}_JPG"
else:
op.size_bg = f"{size}_EXR"
op.tooltip = tip
else:
# Action: Download
if check_convention(asset_data):
label = _t("{0} (download)").format(size)
else:
label = _t("{0} (Update needed)").format(size)
row.enabled = False
op = row.operator(
"poliigon.poliigon_download",
text=label,
icon="IMPORT")
op.asset_id = asset_id
safe_size_apply(cTB, op, size, asset_name)
if is_free and not asset_data.is_purchased:
op.mode = "purchase"
else:
op.mode = "download"
op.tooltip = _t("Download {0}\n{1}").format(size, asset_name)
# Generate the popup menu.
bpy.context.window_manager.popup_menu(draw, title=title, icon="QUESTION")
@@ -0,0 +1,220 @@
# #### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
from typing import Optional, Tuple
import bpy
from ..modules.poliigon_core.assets import (
AssetData,
ModelType)
from ..modules.poliigon_core.multilingual import _t
from ..constants import SUPPORTED_CONVENTION
from ..utils import construct_model_name
def check_convention(asset_data: AssetData, local: bool = False) -> bool:
asset_convention = asset_data.get_convention(local=local)
if asset_convention is None:
return False
elif asset_convention > SUPPORTED_CONVENTION:
return False
return True
def get_model_op_details(
cTB, asset_data: AssetData, size: str) -> Tuple[str, str, str]:
"""Get details to use in the ui for a given model and size."""
asset_type_data = asset_data.get_type_data()
asset_name = asset_data.asset_name
default_lod = cTB.settings["lod"]
downloaded = asset_type_data.get_size_list(
local_only=True,
addon_convention=cTB._asset_index.addon_convention,
local_convention=asset_data.get_convention(local=True))
lod = asset_type_data.get_lod(default_lod)
if lod is None:
lod = "NONE"
if not asset_type_data.has_mesh(ModelType.FBX):
lod = "NONE"
coll_name = construct_model_name(asset_name, size, lod)
coll = bpy.data.collections.get(coll_name)
if coll:
in_scene = True
else:
in_scene = False
label = ""
tip = ""
if size in downloaded:
if in_scene:
if lod:
label = _t("{0} {1} (import again)").format(size, lod)
tip = _t("Import {0} {1} again\n{2}").format(
size, lod, asset_name)
else:
label = _t("{0} (import again)").format(size)
tip = _t("Import {0} again\n{1}").format(size, asset_name)
else:
if lod:
label = _t("{0} {1} (import)").format(size, lod)
tip = _t("Import {0} {1}\n{2}").format(
size, lod, asset_name)
else:
label = _t("{0} (import)").format(size)
tip = _t("Import {0}\n{1}").format(size, asset_name)
return lod, label, tip
def safe_size_apply(cTB,
op_ref: bpy.types.OperatorProperties,
size_value: str,
asset_name: str) -> None:
"""Applies a size value to operator draw with a safe fallback.
If we try to apply a size which is not recognized as local, it will fail
and disrupt further drawing. This function mitigates this problem.
"""
try:
op_ref.size = size_value
except TypeError as e:
# Since this is a UI draw issue, there will be multiple of these
# these reports, but we have user-level debouncing for a max number
# per message type.
msg = f"Failed to assign {size_value} size for {asset_name}: {e}"
cTB.logger_ui.error(msg)
# TODO(SOFT-1303): Include in refactor to asset index, disabled
# overreporting for now.
# reporting.capture_message("failed_size_op_set", msg, "error")
def check_dpi(cTB, force: bool = True) -> None:
"""Checks the DPI of the screen to adjust the scale accordingly.
Used to ensure previews remain square and avoid text truncation.
"""
if not force and cTB.ui_scale_checked:
return
prefs = bpy.context.preferences
cTB.settings["win_scale"] = prefs.system.ui_scale
cTB.ui_scale_checked = True
def get_ui_scale(cTB) -> float:
"""Utility for fetching the ui scale, used in draw code."""
check_dpi(cTB)
return cTB.settings["win_scale"]
def _get_line_width(cTB, line: str) -> int:
"""Returns pixel width of a string."""
width_line = 15
for _char in line:
if _char in "ABCDEFGHKLMNOPQRSTUVWXYZmw":
width_line += 9
elif _char in "abcdeghknopqrstuvxyz0123456789":
width_line += 6
elif _char in "IJfijl .":
width_line += 3
width_line *= get_ui_scale(cTB)
return width_line
def wrapped_label(cTB,
width: int,
text: str,
container: bpy.types.UILayout,
icon: Optional[str] = None,
add_padding: bool = False,
add_padding_top: bool = False,
add_padding_bottom: bool = False,
) -> None:
"""Text wrap a label based on indicated width."""
cTB.logger_ui.debug(f"wrapped_label width={width}, text={text}, "
f"icon={icon}, add_padding={add_padding}")
list_words = [_word.replace("!@#", " ") for _word in text.split(" ")]
row = container.row()
parent = row.column(align=True)
parent.scale_y = 0.8 # To make vertical height more natural for text.
if add_padding or add_padding_top:
parent.label(text="")
if icon is not None:
width -= 25 * get_ui_scale(cTB)
line = ""
first = True
for _word in list_words:
width_line = _get_line_width(cTB, line + _word + " ")
if width_line > width:
if first:
if icon is None:
parent.label(text=line)
else:
parent.label(text=line, icon=icon)
first = False
else:
if icon is None:
parent.label(text=line)
else:
parent.label(text=line, icon="BLANK1")
line = _word + " "
else:
line += _word + " "
if line != "":
if icon is None:
parent.label(text=line)
else:
if first:
parent.label(text=line, icon=icon)
else:
parent.label(text=line, icon="BLANK1")
if add_padding or add_padding_bottom:
parent.label(text="")
def separator_p4b(
container: bpy.types.UILayout, *, line: bool = False) -> None:
if line and bpy.app.version >= (4, 2):
container.separator(type='LINE')
else:
container.separator()