369 lines
13 KiB
Python
369 lines
13 KiB
Python
# #### BEGIN GPL LICENSE BLOCK #####
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License
|
|
# as published by the Free Software Foundation; either version 2
|
|
# of the License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software Foundation,
|
|
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
#
|
|
# ##### END GPL LICENSE BLOCK #####
|
|
|
|
import os
|
|
|
|
from typing import List
|
|
import threading
|
|
import time
|
|
|
|
import bpy
|
|
from bpy.types import Operator
|
|
from bpy.props import (
|
|
BoolProperty,
|
|
IntProperty,
|
|
StringProperty)
|
|
import bpy.utils.previews
|
|
import bmesh
|
|
|
|
from ..modules.poliigon_core.api_remote_control import ApiJob
|
|
from ..modules.poliigon_core.multilingual import _t
|
|
from ..dialogs.utils_dlg import get_ui_scale, wrapped_label
|
|
from ..constants import POPUP_WIDTH_NARROW, POPUP_WIDTH_LABEL_NARROW
|
|
from ..toolbox import get_context
|
|
from ..toolbox_settings import save_settings
|
|
from ..utils import load_image
|
|
from .. import reporting
|
|
|
|
|
|
class POLIIGON_OT_preview(Operator):
|
|
"""Download and apply a watermarked version of this texture for previewing."""
|
|
|
|
bl_idname = "poliigon.poliigon_preview"
|
|
bl_label = _t("Texture Preview")
|
|
bl_description = _t("Download and apply a watermarked version of this texture for previewing")
|
|
bl_options = {"GRAB_CURSOR", "BLOCKING", "REGISTER", "INTERNAL", "UNDO"}
|
|
|
|
tooltip: StringProperty(options={"HIDDEN"}) # noqa: F821
|
|
asset_id: IntProperty(options={"HIDDEN"}) # noqa: F821
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
"""Runs once per operator call before drawing occurs."""
|
|
super().__init__(*args, **kwargs)
|
|
self.asset_data = None
|
|
|
|
@staticmethod
|
|
def init_context(addon_version: str) -> None:
|
|
"""Called from operators.py to init global addon context."""
|
|
|
|
global cTB
|
|
cTB = get_context(addon_version)
|
|
|
|
@classmethod
|
|
def description(cls, context, properties):
|
|
return properties.tooltip
|
|
|
|
@reporting.handle_operator()
|
|
def execute(self, context):
|
|
self.asset_data = cTB._asset_index.get_asset(self.asset_id)
|
|
asset_name = self.asset_data.asset_name
|
|
|
|
t_start = time.time()
|
|
files = self.download_preview(context)
|
|
t_downloaded = time.time()
|
|
|
|
if len(files) == 0:
|
|
self.report({'ERROR'}, _t("Failed to download preview files"))
|
|
struc_str = f"asset_name: {asset_name}"
|
|
reporting.capture_message(
|
|
"quick_preview_download_failed", struc_str, "error")
|
|
return {'CANCELLED'}
|
|
|
|
# Warn user on viewport change before post process, so that any later
|
|
# warnings will popup and take priority to be visible to user. Blender
|
|
# shows the last "self.report" message only (but all print to console).
|
|
self.report_viewport(context)
|
|
cTB.refresh_ui()
|
|
|
|
res = self.post_process_material(context, files)
|
|
|
|
t_post_processed = time.time()
|
|
total_time = t_post_processed - t_start
|
|
download_time = t_downloaded - t_start
|
|
|
|
debug_str = (f"Preview Total time: {total_time}, "
|
|
f"download: {download_time}")
|
|
cTB.logger.debug(f"POLIIGON_OT_preview {debug_str}")
|
|
|
|
# Set the flag that user has used watermarked preview
|
|
cTB.set_onboarding_wm_preview_done()
|
|
save_settings(cTB)
|
|
|
|
cTB.signal_preview_asset(asset_id=self.asset_id)
|
|
return res
|
|
|
|
def _callback_download_wm_preview_done(self, job: ApiJob) -> None:
|
|
self.ev_download_done.set()
|
|
|
|
def download_preview(self, context) -> List[str]:
|
|
"""Download a preview and return expected files."""
|
|
|
|
asset_name = self.asset_data.asset_name
|
|
asset_type_data = self.asset_data.get_type_data()
|
|
|
|
self._name = f"PREVIEW_{asset_name}"
|
|
|
|
if len(asset_type_data.watermarked_urls):
|
|
bpy.context.window.cursor_set("WAIT")
|
|
|
|
self.ev_download_done = threading.Event()
|
|
|
|
cTB.api_rc.add_job_download_wm_preview(
|
|
self.asset_data,
|
|
renderer="Cycles", # TODO(Andreas)
|
|
callback_done=self._callback_download_wm_preview_done
|
|
)
|
|
self.ev_download_done.wait()
|
|
self.ev_download_done = None
|
|
workflow = asset_type_data.get_workflow("METALNESS")
|
|
tex_maps = asset_type_data.get_maps(workflow=workflow, size="WM")
|
|
files = [_tex_map.get_path() for _tex_map in tex_maps]
|
|
return files
|
|
else:
|
|
return []
|
|
|
|
@staticmethod
|
|
def create_plane(context,
|
|
scale: float = 5.0,
|
|
aspect_ratio: float = 1.0,
|
|
name: str = "Preview Plane",
|
|
do_select: bool = True
|
|
) -> bpy.types.Object:
|
|
"""Creates a new primitive plane object."""
|
|
|
|
mesh = bpy.data.meshes.new(name)
|
|
uv_layer = mesh.uv_layers.new()
|
|
mesh.uv_layers.active = uv_layer
|
|
|
|
obj = bpy.data.objects.new(name, mesh)
|
|
|
|
bpy.context.collection.objects.link(obj)
|
|
|
|
bm = bmesh.new()
|
|
bm.from_mesh(mesh)
|
|
|
|
x = (scale / 2.0) * aspect_ratio
|
|
y = scale / 2.0
|
|
bm.verts.new((x, y, 0))
|
|
bm.verts.new((x, -y, 0))
|
|
bm.verts.new((-x, -y, 0))
|
|
bm.verts.new((-x, y, 0))
|
|
|
|
bm.verts.ensure_lookup_table()
|
|
# Have inverted vertex order for our normal to point upward
|
|
face = bm.faces.new(
|
|
(bm.verts[3], bm.verts[2], bm.verts[1], bm.verts[0]))
|
|
bm.faces.ensure_lookup_table()
|
|
|
|
uv_layer = bm.loops.layers.uv.verify()
|
|
|
|
face.loops[0][uv_layer].uv = (1.0, 1.0)
|
|
face.loops[1][uv_layer].uv = (1.0, -1.0)
|
|
face.loops[2][uv_layer].uv = (-1.0, -1.0)
|
|
face.loops[3][uv_layer].uv = (-1.0, 1.0)
|
|
|
|
bmesh.ops.contextual_create(bm, geom=bm.verts)
|
|
bm.to_mesh(mesh)
|
|
bm.free()
|
|
|
|
obj.location = context.scene.cursor.location
|
|
if do_select:
|
|
obj.select_set(True)
|
|
return obj
|
|
|
|
def post_process_material(self, context, files):
|
|
"""Run after the download of WM textures has completed."""
|
|
|
|
asset_name = self.asset_data.asset_name
|
|
asset_type_data = self.asset_data.get_type_data()
|
|
workflow = asset_type_data.get_workflow("METALNESS")
|
|
|
|
bpy.context.window.cursor_set("DEFAULT")
|
|
|
|
mat = cTB.mat_import.import_material(
|
|
asset_data=self.asset_data,
|
|
do_apply=False,
|
|
workflow=workflow,
|
|
size="WM",
|
|
size_bg=None,
|
|
lod="",
|
|
variant=None,
|
|
name_material=None,
|
|
name_mesh=None,
|
|
ref_objs=None,
|
|
projection="UV",
|
|
use_16bit=True,
|
|
mode_disp="NORMAL",
|
|
translate_x=0.0,
|
|
translate_y=0.0,
|
|
scale=1.0,
|
|
global_rotation=0.0,
|
|
aspect_ratio=1.0,
|
|
displacement=0.0,
|
|
keep_unused_tex_nodes=False,
|
|
reuse_existing=True,
|
|
map_prefs=None # Previews ignore map prefs
|
|
)
|
|
if mat is None:
|
|
self.report({"ERROR"}, _t("Material could not be created."))
|
|
reporting.capture_message(
|
|
"could_not_create_preview_mat", asset_name, "error")
|
|
return {"CANCELLED"}
|
|
|
|
objs_selected = [_obj
|
|
for _obj in context.scene.objects
|
|
if _obj.select_get()]
|
|
if len(objs_selected) == 0:
|
|
img_preview = None
|
|
for _img in bpy.data.images:
|
|
if _img.filepath in files:
|
|
img_preview = _img
|
|
# TODO(Andreas): Added this break as it seemed appropriate.
|
|
# Correct???
|
|
# Consequence is we take AR from first
|
|
# instead of last image in sequence
|
|
break
|
|
|
|
if img_preview is not None:
|
|
aspect_ratio = img_preview.size[0] / img_preview.size[1]
|
|
else:
|
|
aspect_ratio = 1.0
|
|
|
|
self.create_plane(
|
|
context, aspect_ratio=aspect_ratio, do_select=True)
|
|
|
|
result = bpy.ops.poliigon.poliigon_apply(
|
|
"INVOKE_DEFAULT",
|
|
asset_id=self.asset_id,
|
|
name_material=mat.name
|
|
)
|
|
if result == {"CANCELLED"}:
|
|
self.report(
|
|
{"WARNING"}, _t("Could not apply materials to selection"))
|
|
|
|
bpy.context.window.cursor_set("DEFAULT")
|
|
return {"FINISHED"}
|
|
|
|
def report_viewport(self, context):
|
|
"""Send the appropriate report based on the current shading mode."""
|
|
|
|
any_mat_or_render = False
|
|
for vA in context.screen.areas:
|
|
if vA.type != "VIEW_3D":
|
|
continue
|
|
for vSpace in vA.spaces:
|
|
if vSpace.type != "VIEW_3D":
|
|
continue
|
|
if vSpace.shading.type in ["MATERIAL", "RENDERED"]:
|
|
any_mat_or_render = True
|
|
|
|
if not any_mat_or_render:
|
|
msg = _t(
|
|
"Enter material or rendered mode to view applied quick preview"
|
|
)
|
|
self.report({'WARNING'}, msg)
|
|
|
|
|
|
class POLIIGON_OT_popup_first_preview(Operator):
|
|
bl_idname = "poliigon.popup_first_preview"
|
|
bl_label = _t("Texture Preview")
|
|
bl_description = _t("Download and apply a watermarked version of this texture for previewing")
|
|
bl_options = {"INTERNAL"}
|
|
|
|
tooltip: StringProperty(options={"HIDDEN"}) # noqa: F821
|
|
asset_id: IntProperty(options={"HIDDEN"}) # noqa: F821
|
|
force: BoolProperty(options={"HIDDEN"}, default=False) # noqa: F821
|
|
|
|
@staticmethod
|
|
def init_context(addon_version: str) -> None:
|
|
"""Called from operators.py to init global addon context."""
|
|
|
|
global cTB
|
|
cTB = get_context(addon_version)
|
|
|
|
@classmethod
|
|
def description(cls, context, properties):
|
|
return properties.tooltip
|
|
|
|
def _load_images(self) -> None:
|
|
path = os.path.join(cTB.dir_script, "onboarding_watermarked.png")
|
|
self.img_welcome = load_image("POPUP_watermarked", path)
|
|
|
|
def invoke(self, context, event):
|
|
if cTB.settings["popup_preview"] and not self.force:
|
|
return {'FINISHED'}
|
|
|
|
self._load_images()
|
|
save_settings(cTB)
|
|
cTB.signal_popup(popup="ONBOARD_WMPREVIEW")
|
|
return context.window_manager.invoke_props_dialog(
|
|
self, width=POPUP_WIDTH_NARROW)
|
|
|
|
@reporting.handle_draw()
|
|
def draw(self, context):
|
|
label_width = POPUP_WIDTH_LABEL_NARROW * get_ui_scale(cTB)
|
|
# Accounting for the left+right border columns:
|
|
label_width -= 10.0
|
|
|
|
col_content = self.layout.column()
|
|
|
|
col_image = col_content.column()
|
|
col_image.scale_y = 0.5
|
|
col_image.template_icon(
|
|
icon_value=self.img_welcome.preview.icon_id,
|
|
scale=18.0)
|
|
|
|
row_text = col_content.row()
|
|
if bpy.app.version >= (3, 0):
|
|
col_left_gap = row_text.column()
|
|
col_left_gap.alignment = "LEFT"
|
|
col_left_gap.label(text=" ")
|
|
col_text = row_text.column()
|
|
else:
|
|
col_left_gap = row_text.column()
|
|
col_left_gap.alignment = "LEFT"
|
|
# Note, here no label in left column. Otherwise we'd end up with a
|
|
# way too larger border gap.
|
|
col_text = row_text.column()
|
|
col_text.alignment = "CENTER"
|
|
|
|
wrapped_label(
|
|
cTB,
|
|
width=label_width,
|
|
text=_t("Previews are watermarked 1K resolution textures."),
|
|
container=col_text,
|
|
add_padding=True)
|
|
wrapped_label(
|
|
cTB,
|
|
width=label_width,
|
|
text=_t("Subscribe to download high-resolution textures up to 8K "
|
|
"without watermarks."),
|
|
container=col_text,
|
|
add_padding_bottom=True)
|
|
|
|
@reporting.handle_operator(silent=True)
|
|
def execute(self, context):
|
|
cTB.settings["popup_preview"] = 1
|
|
# Set the flag that user has used watermarked preview
|
|
cTB.set_onboarding_wm_preview_done()
|
|
save_settings(cTB)
|
|
cTB.signal_popup(popup="ONBOARD_WMPREVIEW", click="ONBOARD_WMPREVIEW")
|
|
bpy.ops.poliigon.poliigon_preview(asset_id=self.asset_id)
|
|
cTB.refresh_ui()
|
|
return {'FINISHED'}
|