414 lines
14 KiB
Python
414 lines
14 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 #####
|
|
|
|
from typing import List
|
|
|
|
from bpy.types import Operator
|
|
from bpy.props import (
|
|
BoolProperty,
|
|
EnumProperty,
|
|
FloatProperty,
|
|
IntProperty,
|
|
StringProperty)
|
|
import bpy.utils.previews
|
|
|
|
from ..modules.poliigon_core.multilingual import _t
|
|
from ..material_import_utils import find_identical_material
|
|
from .utils_operator import fill_size_drop_down
|
|
from ..toolbox import get_context
|
|
from ..utils import (
|
|
is_cycles,
|
|
is_eevee_next)
|
|
from .. import reporting
|
|
|
|
|
|
UV_OPTIONS = [("UV", "UV", "UV"),
|
|
("MOSAIC", "Mosaic", "Poliigon Mosaic"),
|
|
("FLAT", "Flat", "Flat"),
|
|
("BOX", "Box", "Box"),
|
|
("SPHERE", "Sphere", "Sphere"),
|
|
("TUBE", "Tube", "Tube")]
|
|
|
|
|
|
def set_op_mat_disp_strength(ops, asset_name: str, mode_disp: str) -> None:
|
|
if asset_name.startswith("Poliigon_"):
|
|
ops.displacement = 0.2
|
|
elif mode_disp == "MICRO":
|
|
ops.displacement = 0.05
|
|
else:
|
|
ops.displacement = 0.0
|
|
|
|
|
|
class POLIIGON_OT_material(Operator):
|
|
bl_idname = "poliigon.poliigon_material"
|
|
bl_label = _t("Poliigon Material Import")
|
|
bl_description = _t("Create Material")
|
|
bl_options = {"GRAB_CURSOR", "BLOCKING", "REGISTER", "INTERNAL", "UNDO"}
|
|
|
|
def _get_dispopts(self, context):
|
|
options = [
|
|
("NORMAL", "Normal Only", "Use the Normal Map for surface details")
|
|
]
|
|
|
|
if is_cycles() or is_eevee_next():
|
|
# Only cycles and 4.2's eevee next support displacement
|
|
options.append(
|
|
("BUMP",
|
|
"Bump Only",
|
|
("Use the displacement map for surface details without "
|
|
"displacement"))
|
|
)
|
|
options.append(
|
|
("DISP",
|
|
"Displacement and Bump",
|
|
("Use the displacement map for surface details and physical "
|
|
"displacement"))
|
|
)
|
|
|
|
if is_cycles():
|
|
# While Eevee next in blender 4.2 could have the above options,
|
|
# below is still only relevnat in cycles.
|
|
options.append(
|
|
("MICRO",
|
|
"Adaptive Displacement Only",
|
|
("Use the displacement map for physical displacement with "
|
|
"adaptive geometry subdivisions\n"
|
|
"Note: This has a high render performance cost!"))
|
|
)
|
|
return options
|
|
|
|
def _fill_size_drop_down(self, context):
|
|
return fill_size_drop_down(cTB, self.asset_id)
|
|
|
|
def _update_displacement_options(self, context):
|
|
# We can not access self.asset_data, here!
|
|
asset_data = cTB._asset_index.get_asset(self.asset_id)
|
|
if asset_data.local_convention == 1:
|
|
self.displacement = 0.2
|
|
else:
|
|
self.displacement = 0.05
|
|
|
|
tooltip: StringProperty(options={"HIDDEN"}) # noqa: F821
|
|
asset_id: IntProperty(options={"HIDDEN"}) # noqa: F821
|
|
size: EnumProperty(
|
|
name=_t("Texture"), # noqa: F821
|
|
items=_fill_size_drop_down,
|
|
description=_t("Change size of assigned textures.") # noqa: F722
|
|
)
|
|
mapping: EnumProperty(
|
|
name=_t("Mapping"), # noqa: F821
|
|
items=UV_OPTIONS,
|
|
default="UV" # noqa: F821
|
|
)
|
|
scale: FloatProperty(
|
|
name=_t("Scale"), # noqa: F821
|
|
default=1.0
|
|
)
|
|
if bpy.app.version >= (3, 0):
|
|
mode_disp: EnumProperty(name="Displacement", # noqa: F821
|
|
items=_get_dispopts,
|
|
default=0, # noqa: F821
|
|
update=_update_displacement_options)
|
|
else:
|
|
mode_disp: EnumProperty(name="Displacement", # noqa: F821
|
|
items=_get_dispopts,
|
|
update=_update_displacement_options)
|
|
|
|
displacement: FloatProperty(
|
|
name=_t("Displacement Strength"), # noqa: F722
|
|
default=0.0
|
|
)
|
|
use_16bit: BoolProperty(
|
|
name=_t("16-Bit Textures (if any)"), # noqa: F722
|
|
default=False
|
|
)
|
|
reuse_material: BoolProperty(
|
|
name=_t("Reuse Material"), # noqa: F722
|
|
default=True
|
|
)
|
|
keep_unused_tex_nodes: BoolProperty(
|
|
name=_t("Keep Additional Texture Nodes"), # noqa: F722
|
|
default=True
|
|
)
|
|
# TODO(Andreas): Can do_rename be removed, seems not in use at all?
|
|
do_rename: BoolProperty(options={"HIDDEN"}) # noqa: F821
|
|
do_apply: BoolProperty(options={"HIDDEN"}, default=True) # noqa: F821
|
|
ignore_map_prefs: BoolProperty(options={"HIDDEN"}, default=False) # noqa: F821
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
"""Runs once per operator call before drawing occurs."""
|
|
super().__init__(*args, **kwargs)
|
|
|
|
# Note: During property update handlers (like e.g.
|
|
# _update_displacement_options()) we can not rely on on these
|
|
# members to be defined!
|
|
self.asset_data = None
|
|
self.exec_count = 0
|
|
|
|
@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 draw(self, context):
|
|
if self.asset_data is None:
|
|
self.asset_data = cTB._asset_index.get_asset(self.asset_id)
|
|
|
|
is_backplate = self.asset_data.is_backplate()
|
|
|
|
col = self.layout.column()
|
|
col.prop(self, "size")
|
|
row = col.row()
|
|
row.prop(self, "mapping")
|
|
row.enabled = not is_backplate
|
|
if is_cycles() or is_eevee_next():
|
|
row = col.row()
|
|
row.prop(self, "mode_disp")
|
|
row.enabled = not is_backplate
|
|
if self.mode_disp != "NORMAL" and (is_cycles() or is_eevee_next()):
|
|
row = col.row()
|
|
row.prop(self, "displacement")
|
|
row.enabled = not is_backplate
|
|
row = col.row()
|
|
row.prop(self, "scale") # TODO(Andreas): implement for backplate?
|
|
row.enabled = not is_backplate
|
|
row = col.row()
|
|
row.prop(self, "use_16bit")
|
|
row.enabled = not is_backplate
|
|
col.prop(self, "reuse_material")
|
|
row = col.row()
|
|
row.prop(self, "keep_unused_tex_nodes")
|
|
row.enabled = not self.reuse_material
|
|
|
|
def evaluate_displacement_method_property(self) -> None:
|
|
"""Tries to set dislacement method from prefs, if not set already."""
|
|
|
|
if self.properties.is_property_set("mode_disp"):
|
|
return
|
|
|
|
try:
|
|
self.mode_disp = cTB.prefs.mode_disp
|
|
except TypeError:
|
|
# Could be Eevee active while assigning a displacement value
|
|
self.mode_disp = "NORMAL"
|
|
except AttributeError as e:
|
|
reporting.capture_exception(e)
|
|
self.mode_disp = "NORMAL"
|
|
|
|
def determine_objects_needing_subdiv(
|
|
self, context) -> List[bpy.types.Object]:
|
|
"""Returns a list of selected objects, which still need a Subdivision
|
|
modifier for microdisplacement.
|
|
"""
|
|
|
|
if not is_cycles() or self.mode_disp != "MICRO":
|
|
return []
|
|
|
|
objs_selected = [_obj for _obj in context.selected_objects]
|
|
objs_add_subdiv = [_obj
|
|
for _obj in objs_selected
|
|
if "Subdivision" not in _obj.modifiers]
|
|
if bpy.app.version < (5, 0):
|
|
# Blender 5.0 no longer has feature sets, adaptive is included
|
|
bpy.context.scene.cycles.feature_set = "EXPERIMENTAL"
|
|
return objs_add_subdiv
|
|
|
|
def add_subdiv_to_objects(self, obj_list: List[bpy.types.Object]) -> None:
|
|
"""Adds a Subdivision modifier to all objects in list."""
|
|
|
|
for _obj in obj_list:
|
|
if bpy.app.version < (5, 0):
|
|
# In blender 5.0, there's no special settings for this.
|
|
_obj.cycles.use_adaptive_subdivision = True
|
|
modifier = _obj.modifiers.new("Subdivision", "SUBSURF")
|
|
if modifier is None:
|
|
# TODO(Andreas): Would we want to report failure to create modifier?
|
|
continue
|
|
modifier.subdivision_type = "SIMPLE"
|
|
modifier.levels = 0 # Don't do subdiv in viewport
|
|
|
|
def check_material_reuse(self) -> bool:
|
|
"""Optionally re-uses an already imported identical material."""
|
|
|
|
if not self.reuse_material:
|
|
return False
|
|
|
|
identical_mat = find_identical_material(
|
|
self.asset_data,
|
|
self.size,
|
|
self.mapping,
|
|
self.scale,
|
|
self.displacement,
|
|
self.use_16bit,
|
|
self.mode_disp
|
|
)
|
|
|
|
if identical_mat is None:
|
|
return False
|
|
|
|
# Prevent duplicate materials from being created unintentionally
|
|
# but we probably want to provide an option for that at some point.
|
|
cTB.logger.debug("POLIIGON_OT_material Applying existing material: "
|
|
f"{identical_mat.name}")
|
|
self.report({"WARNING"}, _t("Applying existing material"))
|
|
|
|
result = bpy.ops.poliigon.poliigon_apply(
|
|
"INVOKE_DEFAULT",
|
|
asset_id=self.asset_id,
|
|
name_material=identical_mat.name
|
|
)
|
|
if result == {"CANCELLED"}:
|
|
self.report(
|
|
{"WARNING"}, _t("Could not apply materials to selection"))
|
|
else:
|
|
self.signal_import()
|
|
return True
|
|
|
|
def signal_import(self) -> None:
|
|
if self.exec_count == 0:
|
|
cTB.signal_import_asset(asset_id=self.asset_id)
|
|
cTB.set_first_local_asset()
|
|
self.exec_count += 1
|
|
|
|
@reporting.handle_operator()
|
|
def execute(self, context):
|
|
self.asset_data = cTB._asset_index.get_asset(self.asset_id)
|
|
|
|
# TODO(Andreas): Can do_rename be removed, seems not in use at all?
|
|
if self.do_rename:
|
|
mat = bpy.data.materials[cTB.vActiveMat]
|
|
cTB.vActiveMat = bpy.context.scene.vEditMatName
|
|
mat.name = cTB.vActiveMat
|
|
return {"FINISHED"}
|
|
|
|
asset_type_data = self.asset_data.get_type_data()
|
|
asset_name = self.asset_data.asset_name
|
|
asset_type = self.asset_data.asset_type
|
|
|
|
workflow = asset_type_data.get_workflow("METALNESS")
|
|
self.size = asset_type_data.get_size(
|
|
self.size,
|
|
local_only=True,
|
|
addon_convention=cTB.addon_convention,
|
|
local_convention=self.asset_data.local_convention
|
|
)
|
|
|
|
self.evaluate_displacement_method_property()
|
|
|
|
objs_add_disp = self.determine_objects_needing_subdiv(context)
|
|
|
|
# TODO(Andreas): Not sure what this was supposed to be good for:
|
|
# msg_error_local = None
|
|
# msg_error_my_assets = None
|
|
# if msg_error_local is not None:
|
|
# self.report({"ERROR"}, msg_error_local)
|
|
# return {'CANCELLED'}
|
|
# if msg_error_my_assets is not None:
|
|
# cTB.print_debug_(
|
|
# 0, "apply_mat_size_not_local", asset_name, self.size)
|
|
# self.report({"ERROR"}, msg_error_my_assets)
|
|
# reporting.capture_message("apply_mat_size_not_local", asset_name)
|
|
# return {"CANCELLED"}
|
|
|
|
cTB.logger.debug(f"POLIIGON_OT_material Size: {self.size}")
|
|
|
|
did_reuse = self.check_material_reuse()
|
|
if did_reuse:
|
|
self.signal_import()
|
|
return {"FINISHED"}
|
|
|
|
tex_maps = asset_type_data.get_maps(
|
|
workflow=workflow,
|
|
size=self.size,
|
|
prefer_16_bit=self.use_16bit,
|
|
variant=None)
|
|
if len(tex_maps) == 0:
|
|
self.report({"WARNING"}, _t("No Textures found."))
|
|
reporting.capture_message("apply_mat_tex_not_found", asset_name)
|
|
return {"CANCELLED"}
|
|
|
|
if self.ignore_map_prefs:
|
|
map_prefs = None
|
|
else:
|
|
map_prefs = cTB.user.map_preferences
|
|
|
|
mat = cTB.mat_import.import_material(
|
|
asset_data=self.asset_data,
|
|
do_apply=False,
|
|
workflow="METALNESS",
|
|
size=self.size,
|
|
lod="",
|
|
projection=self.mapping,
|
|
use_16bit=self.use_16bit,
|
|
mode_disp=self.mode_disp,
|
|
translate_x=0.0,
|
|
translate_y=0.0,
|
|
scale=self.scale,
|
|
global_rotation=0.0,
|
|
aspect_ratio=1.0,
|
|
displacement=self.displacement,
|
|
keep_unused_tex_nodes=self.keep_unused_tex_nodes,
|
|
reuse_existing=False, # we already checked for reusable mats before
|
|
map_prefs=map_prefs
|
|
)
|
|
|
|
if mat is None:
|
|
reporting.capture_message(
|
|
"could_not_create_mat", asset_name, "error")
|
|
self.report({"ERROR"}, _t("Material could not be created."))
|
|
return {"CANCELLED"}
|
|
|
|
self.add_subdiv_to_objects(objs_add_disp)
|
|
|
|
cTB.f_GetSceneAssets()
|
|
|
|
# TODO(Andreas): check what it is used for
|
|
# TODO(Andreas): Even more strange, op.poliigon_active does the
|
|
# same again...
|
|
# TODO(Andreas): Even more more strange, op.poliigon_apply then does
|
|
# this again and also calls op.poliigon_active, again?
|
|
cTB.vActiveType = asset_type.name # self.vType
|
|
cTB.active_asset_id = self.asset_id
|
|
cTB.vActiveMat = mat.name
|
|
|
|
bpy.ops.poliigon.poliigon_active(
|
|
mode="mat", asset_type=cTB.vActiveType, data=cTB.vActiveMat
|
|
)
|
|
|
|
self.signal_import()
|
|
|
|
if not self.do_apply:
|
|
return {"FINISHED"}
|
|
|
|
rtn = bpy.ops.poliigon.poliigon_apply(
|
|
"INVOKE_DEFAULT",
|
|
asset_id=self.asset_id,
|
|
name_material=mat.name
|
|
)
|
|
if rtn == {"CANCELLED"}:
|
|
self.report(
|
|
{"WARNING"}, _t("Could not apply materials to selection"))
|
|
|
|
return {"FINISHED"}
|