3578 lines
150 KiB
Python
3578 lines
150 KiB
Python
# Copyright (C) 2021 Victor Soupday
|
|
# This file is part of CC/iC Blender Tools <https://github.com/soupday/cc_blender_tools>
|
|
#
|
|
# CC/iC Blender Tools 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 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# CC/iC Blender Tools 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 CC/iC Blender Tools. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
import bpy
|
|
import os
|
|
from mathutils import Vector
|
|
from . import normal, colorspace, imageutils, wrinkle, nodeutils, materials, utils, params, vars
|
|
from .exporter import get_export_objects
|
|
from . utils import B500
|
|
|
|
BAKE_SAMPLES = 4
|
|
BAKE_INDEX = 1001
|
|
BUMP_BAKE_MULTIPLIER = 2.0
|
|
NODE_CURSOR = Vector((0, 0))
|
|
|
|
def init_bake(id = 1001):
|
|
global BAKE_INDEX
|
|
BAKE_INDEX = id
|
|
|
|
|
|
def set_cycles_samples(context, samples, adaptive_samples = -1, denoising = False, time_limit = 0, use_gpu = False):
|
|
if not context:
|
|
context = bpy.context
|
|
|
|
context.scene.cycles.samples = samples
|
|
if utils.B300():
|
|
context.scene.cycles.device = 'GPU' if use_gpu else 'CPU'
|
|
context.scene.cycles.preview_samples = samples
|
|
context.scene.cycles.use_adaptive_sampling = adaptive_samples >= 0
|
|
context.scene.cycles.adaptive_threshold = adaptive_samples
|
|
context.scene.cycles.use_preview_adaptive_sampling = adaptive_samples >= 0
|
|
context.scene.cycles.preview_adaptive_threshold = adaptive_samples
|
|
context.scene.cycles.use_denoising = denoising
|
|
context.scene.cycles.use_preview_denoising = denoising
|
|
context.scene.cycles.use_auto_tile = False
|
|
context.scene.cycles.time_limit = time_limit
|
|
|
|
|
|
def prep_bake(context, mat: bpy.types.Material=None, samples=BAKE_SAMPLES, image_format="PNG", make_surface=True):
|
|
bake_state = {}
|
|
if not context:
|
|
context = bpy.context
|
|
|
|
# cycles settings
|
|
bake_state["samples"] = context.scene.cycles.samples
|
|
if utils.B500():
|
|
bake_state["use_bake_multires"] = context.scene.render.bake.use_multires
|
|
else:
|
|
bake_state["use_bake_multires"] = context.scene.render.use_bake_multires
|
|
# Blender 3.0
|
|
if utils.B300():
|
|
bake_state["preview_samples"] = context.scene.cycles.preview_samples
|
|
bake_state["use_adaptive_sampling"] = context.scene.cycles.use_adaptive_sampling
|
|
bake_state["use_preview_adaptive_sampling"] = context.scene.cycles.use_preview_adaptive_sampling
|
|
bake_state["use_denoising"] = context.scene.cycles.use_denoising
|
|
bake_state["use_preview_denoising"] = context.scene.cycles.use_preview_denoising
|
|
bake_state["use_auto_tile"] = context.scene.cycles.use_auto_tile
|
|
# render settings
|
|
bake_state["file_format"] = context.scene.render.image_settings.file_format
|
|
bake_state["color_depth"] = context.scene.render.image_settings.color_depth
|
|
bake_state["color_mode"] = context.scene.render.image_settings.color_mode
|
|
bake_state["use_selected_to_active"] = context.scene.render.bake.use_selected_to_active
|
|
bake_state["use_pass_direct"] = context.scene.render.bake.use_pass_direct
|
|
bake_state["use_pass_indirect"] = context.scene.render.bake.use_pass_indirect
|
|
bake_state["margin"] = context.scene.render.bake.margin
|
|
bake_state["use_clear"] = context.scene.render.bake.use_clear
|
|
bake_state["image_format"] = context.scene.render.image_settings.file_format
|
|
# Blender 2.92
|
|
if utils.B292():
|
|
bake_state["target"] = context.scene.render.bake.target
|
|
# color management
|
|
bake_state["view_transform"] = context.scene.view_settings.view_transform
|
|
bake_state["look"] = context.scene.view_settings.look
|
|
bake_state["gamma"] = context.scene.view_settings.gamma
|
|
bake_state["exposure"] = context.scene.view_settings.exposure
|
|
bake_state["colorspace"] = context.scene.sequencer_colorspace_settings.name
|
|
|
|
context.scene.cycles.samples = samples
|
|
context.scene.render.image_settings.file_format = image_format
|
|
if utils.B500():
|
|
context.scene.render.bake.use_multires = False
|
|
else:
|
|
context.scene.render.use_bake_multires = False
|
|
context.scene.render.bake.use_selected_to_active = False
|
|
context.scene.render.bake.use_pass_direct = False
|
|
context.scene.render.bake.use_pass_indirect = False
|
|
context.scene.render.bake.margin = 16
|
|
context.scene.render.bake.use_clear = True
|
|
# color management settings affect the baked output so set them to standard/raw defaults:
|
|
context.scene.view_settings.view_transform = 'Standard'
|
|
context.scene.view_settings.look = 'None'
|
|
context.scene.view_settings.gamma = 1
|
|
context.scene.view_settings.exposure = 0
|
|
colorspace.set_sequencer_color_space("Raw")
|
|
|
|
# Blender 3.0
|
|
if utils.B300():
|
|
context.scene.cycles.preview_samples = samples
|
|
context.scene.cycles.use_adaptive_sampling = False
|
|
context.scene.cycles.use_preview_adaptive_sampling = False
|
|
context.scene.cycles.use_denoising = False
|
|
context.scene.cycles.use_preview_denoising = False
|
|
context.scene.cycles.use_auto_tile = False
|
|
|
|
# Blender 2.92
|
|
if utils.B292():
|
|
context.scene.render.bake.target = 'IMAGE_TEXTURES'
|
|
|
|
# go into wireframe mode (so Blender doesn't update or recompile the material shaders while
|
|
# we manipulate them for baking, and also so Blender doesn't fire up the cycles viewport...):
|
|
shading: bpy.types.View3DShading = utils.get_view_3d_shading(context)
|
|
if shading:
|
|
bake_state["shading"] = shading.type
|
|
shading.type = 'WIREFRAME'
|
|
else:
|
|
bake_state["shading"] = 'MATERIAL'
|
|
shading.type = 'WIREFRAME'
|
|
# set cycles rendering mode for baking
|
|
bake_state["engine"] = context.scene.render.engine
|
|
context.scene.render.engine = 'CYCLES'
|
|
bake_state["cycles_bake_type"] = context.scene.cycles.bake_type
|
|
if utils.B500():
|
|
bake_state["render_bake_type"] = context.scene.render.bake.type
|
|
else:
|
|
bake_state["render_bake_type"] = context.scene.render.bake_type
|
|
context.scene.cycles.bake_type = "COMBINED"
|
|
|
|
|
|
if make_surface:
|
|
|
|
# deselect everything
|
|
bpy.ops.object.select_all(action='DESELECT')
|
|
# create the baking plane, a single quad baking surface for an even sampling across the entire texture
|
|
bpy.ops.mesh.primitive_plane_add(size=2, enter_editmode=False, align='WORLD', location=(0, 0, 0))
|
|
bake_surface = utils.get_active_object()
|
|
bake_state["bake_surface"] = bake_surface
|
|
set_bake_material(bake_state, mat)
|
|
# replicate any material node UV layers in bake surface
|
|
if mat and mat.node_tree and mat.node_tree.nodes:
|
|
for node in mat.node_tree.nodes:
|
|
if node.type == "UVMAP":
|
|
uv_name = node.uv_map
|
|
mesh: bpy.types.Mesh = bake_surface.data
|
|
if uv_name not in mesh.uv_layers:
|
|
mesh.uv_layers.new(name=uv_name)
|
|
|
|
return bake_state
|
|
|
|
|
|
def set_bake_material(bake_state, mat):
|
|
if mat and bake_state and "bake_surface" in bake_state:
|
|
bake_surface = bake_state["bake_surface"]
|
|
if bake_surface:
|
|
# attach the material to bake to the baking surface plane
|
|
# (the baking plane also ensures that only one material is baked onto only one target image)
|
|
if len(bake_surface.data.materials) == 0:
|
|
bake_surface.data.materials.append(mat)
|
|
else:
|
|
bake_surface.data.materials[0] = mat
|
|
bake_state["bake_material"] = mat
|
|
return True
|
|
return False
|
|
|
|
|
|
def post_bake(context, state):
|
|
if not context:
|
|
context = bpy.context
|
|
|
|
# cycles settings
|
|
context.scene.cycles.samples = state["samples"]
|
|
# Blender 3.0
|
|
if utils.B300():
|
|
context.scene.cycles.preview_samples = state["preview_samples"]
|
|
context.scene.cycles.use_adaptive_sampling = state["use_adaptive_sampling"]
|
|
context.scene.cycles.use_preview_adaptive_sampling = state["use_preview_adaptive_sampling"]
|
|
context.scene.cycles.use_denoising = state["use_denoising"]
|
|
context.scene.cycles.use_preview_denoising = state["use_preview_denoising"]
|
|
context.scene.cycles.use_auto_tile = state["use_auto_tile"]
|
|
# render settings
|
|
context.scene.render.image_settings.file_format = state["file_format"]
|
|
context.scene.render.image_settings.color_depth = state["color_depth"]
|
|
context.scene.render.image_settings.color_mode = state["color_mode"]
|
|
if utils.B500():
|
|
context.scene.render.bake.use_multires = state["use_bake_multires"]
|
|
else:
|
|
context.scene.render.use_bake_multires = state["use_bake_multires"]
|
|
context.scene.render.bake.use_selected_to_active = state["use_selected_to_active"]
|
|
context.scene.render.bake.use_pass_direct = state["use_pass_direct"]
|
|
context.scene.render.bake.use_pass_indirect = state["use_pass_indirect"]
|
|
context.scene.render.bake.margin = state["margin"]
|
|
context.scene.render.bake.use_clear = state["use_clear"]
|
|
context.scene.render.image_settings.file_format = state["image_format"]
|
|
# Blender 2.92
|
|
if utils.B292():
|
|
context.scene.render.bake.target = state["target"]
|
|
# color management
|
|
context.scene.view_settings.view_transform = state["view_transform"]
|
|
context.scene.view_settings.look = state["look"]
|
|
context.scene.view_settings.gamma = state["gamma"]
|
|
context.scene.view_settings.exposure = state["exposure"]
|
|
context.scene.sequencer_colorspace_settings.name = state["colorspace"]
|
|
# render engine
|
|
context.scene.render.engine = state["engine"]
|
|
# viewport shading
|
|
shading = utils.get_view_3d_shading(context)
|
|
if shading:
|
|
shading.type = state["shading"]
|
|
|
|
# bake type
|
|
context.scene.cycles.bake_type = state["cycles_bake_type"]
|
|
if utils.B500():
|
|
context.scene.render.bake.type = state["render_bake_type"]
|
|
else:
|
|
context.scene.render.bake_type = state["render_bake_type"]
|
|
|
|
# remove the bake surface
|
|
if "bake_surface" in state:
|
|
bpy.data.objects.remove(state["bake_surface"])
|
|
|
|
|
|
def get_existing_bake_image(mat, channel_id, width, height,
|
|
shader_node, socket,
|
|
bake_dir, name_prefix="",
|
|
force_srgb=False,
|
|
channel_pack=False,
|
|
exact_name=False):
|
|
return None
|
|
|
|
|
|
def get_export_bake_image_name(mat, channel_id, name_prefix="", exact_name=False, underscores=True):
|
|
global BAKE_INDEX
|
|
sep = " "
|
|
if underscores:
|
|
sep = "_"
|
|
prefix_sep = ""
|
|
if name_prefix:
|
|
prefix_sep = sep
|
|
# determine image name and color space
|
|
if exact_name:
|
|
image_name = name_prefix + prefix_sep + mat.name + sep + channel_id
|
|
else:
|
|
image_name = "EXPORT_BAKE" + sep + name_prefix + prefix_sep + mat.name + sep + channel_id + sep + str(BAKE_INDEX)
|
|
BAKE_INDEX += 1
|
|
return image_name
|
|
|
|
|
|
def get_bake_image(mat, channel_id, width, height, shader_node, socket, bake_dir,
|
|
name_prefix="", force_srgb=False, channel_pack=False,
|
|
exact_name=False, underscores=True, unique_name=False, image_format="PNG"):
|
|
"""Makes an image and image file to bake the shader socket to and returns the image and image name
|
|
"""
|
|
|
|
image_name = get_export_bake_image_name(mat, channel_id, name_prefix, exact_name, underscores)
|
|
socket_name = nodeutils.safe_socket_name(socket)
|
|
|
|
is_data = True
|
|
alpha = False
|
|
rgb_sockets = [ "Diffuse", "Diffuse Map", "Base Color", "Emission", "Emission Color", "Subsurface Color" ]
|
|
rgb_channels = [ "Diffuse", "Emission", "BaseMap", "Base Color", "Glow" ]
|
|
if socket_name in rgb_sockets:
|
|
is_data = False
|
|
if channel_id in rgb_channels:
|
|
is_data = False
|
|
if force_srgb:
|
|
is_data = False
|
|
if channel_pack:
|
|
alpha = True
|
|
|
|
# make (and save) the target image
|
|
image, exists = get_image_target(image_name, width, height, bake_dir, is_data, alpha,
|
|
channel_packed=channel_pack, format=image_format)
|
|
|
|
# make sure we don't reuse an image as the target, that is also in the nodes we are baking from...
|
|
i = 0
|
|
base_name = image_name
|
|
if shader_node and socket:
|
|
while nodeutils.is_image_node_connected_to_socket(shader_node, socket, image, []):
|
|
i += 1
|
|
old_name = image_name
|
|
image_name = base_name + "_" + str(i)
|
|
utils.log_info(f"Image: {old_name} in use, trying: {image_name}")
|
|
image, exists = get_image_target(image_name, width, height, bake_dir, is_data, alpha,
|
|
channel_packed=channel_pack, format=image_format)
|
|
|
|
return image, image_name, exists
|
|
|
|
|
|
def bake_node_socket_input(context, node, socket, mat, channel_id, bake_dir, name_prefix="",
|
|
override_size=0, size_override_node=None, size_override_socket=None,
|
|
exact_name=False, underscores=True, unique_name=False,
|
|
no_prep=False, image_format="PNG"):
|
|
"""Bakes the input to the supplied node and socket to an appropriate image.\n
|
|
Image size is determined by the sizes of the connected image nodes (or overriden).\n
|
|
Image name and path is determined by the texture channel id and material name and name prefix.\n
|
|
An alternative size override node and socket can be supplied to determine the size.
|
|
(e.g. matching image sizes with another texture channel)\n
|
|
Returns the image baked."""
|
|
|
|
# determine the size of the image to bake onto
|
|
size_node = node
|
|
size_socket = socket
|
|
if size_override_node:
|
|
size_node = size_override_node
|
|
if size_override_socket:
|
|
size_socket = size_override_socket
|
|
width, height = get_connected_texture_size(size_node, override_size, size_socket)
|
|
|
|
# get the node and output socket to bake from
|
|
source_node, source_socket = nodeutils.get_node_and_socket_connected_to_input(node, socket)
|
|
|
|
# bake the source node output onto the target image and re-save it
|
|
image, image_name, exists = get_bake_image(mat, channel_id, width, height, node, socket, bake_dir,
|
|
name_prefix=name_prefix, exact_name=exact_name, unique_name=unique_name,
|
|
underscores=underscores, image_format=image_format)
|
|
image_node = cycles_bake_color_output(context, mat, source_node, source_socket, image, image_name,
|
|
no_prep=no_prep, image_format=image_format)
|
|
|
|
# remove the image node
|
|
if image_node:
|
|
nodes = mat.node_tree.nodes
|
|
nodes.remove(image_node)
|
|
|
|
return image
|
|
|
|
|
|
def bake_node_socket_output(context, node, socket, mat, channel_id, bake_dir, name_prefix = "",
|
|
override_size = 0, size_override_node=None, size_override_socket=None,
|
|
exact_name=False, underscores=True, unique_name=False,
|
|
no_prep=False, image_format="PNG"):
|
|
"""Bakes the output of the supplied node and socket to an appropriate image.\n
|
|
Image size is determined by the sizes of the connected image nodes (or overriden).\n
|
|
Image name and path is determined by the texture channel id, material name, bake dir and name prefix.\n
|
|
An alternative size override node and socket can be supplied to determine the size.
|
|
(e.g. matching image sizes with another texture channel)\n
|
|
Returns the image baked."""
|
|
|
|
# determine the size of the image to bake onto
|
|
size_node = node
|
|
size_socket = socket
|
|
if size_override_node:
|
|
size_node = size_override_node
|
|
if size_override_socket:
|
|
size_socket = size_override_socket
|
|
width, height = get_connected_texture_size(size_node, override_size, size_socket)
|
|
|
|
# bake the source node output onto the target image and re-save it
|
|
image, image_name, exists = get_bake_image(mat, channel_id, width, height, node, socket, bake_dir,
|
|
name_prefix=name_prefix, exact_name=exact_name, unique_name=unique_name,
|
|
underscores=underscores, image_format=image_format)
|
|
image_node = cycles_bake_color_output(context, mat, node, socket, image, image_name,
|
|
no_prep=no_prep, image_format=image_format)
|
|
|
|
# remove the image node
|
|
if image_node:
|
|
nodes = mat.node_tree.nodes
|
|
nodes.remove(image_node)
|
|
|
|
return image
|
|
|
|
|
|
def bake_rl_bump_and_normal(context, shader_node, bsdf_node, mat, channel_id, bake_dir,
|
|
normal_socket_name="Normal Map", bump_socket_name="Bump Map",
|
|
normal_strength_socket_name="Normal Strength",
|
|
bump_distance_socket_name="Bump Strength",
|
|
name_prefix="", override_size=0, no_prep=False, image_format="PNG"):
|
|
"""Bakes the normal map and bump map inputs to the supplied RL master shader node, to a combined
|
|
normal map image which takes the normal and bump strengths into account.\n
|
|
If supplied socket names are empty they will not be included in the bake.\n
|
|
Image size is determined by the sizes of the connected image nodes (or overriden).\n
|
|
Image name and path is determined by the texture channel id, material name, bake dir and name prefix.\n
|
|
An alternative size override node and socket can be supplied to determine the size.
|
|
(e.g. matching image sizes with another texture channel)\n
|
|
Returns the normal image baked."""
|
|
|
|
# determine the size of the image to bake onto
|
|
width, height = get_connected_texture_size(shader_node, override_size, normal_socket_name, bump_socket_name)
|
|
|
|
nodes = mat.node_tree.nodes
|
|
links = mat.node_tree.links
|
|
|
|
# store original links to BSDF normal socket
|
|
bsdf_normal_node, bsdf_normal_socket = nodeutils.get_node_and_socket_connected_to_input(bsdf_node, "Normal")
|
|
#
|
|
normal_source_node = None
|
|
normal_source_socket = None
|
|
bump_source_node = None
|
|
bump_source_socket = None
|
|
bump_distance = 0.01
|
|
normal_strength = 1.0
|
|
bump_map_node = None
|
|
normal_map_node = None
|
|
if normal_socket_name:
|
|
if normal_strength_socket_name:
|
|
normal_strength = nodeutils.get_node_input_value(shader_node, normal_strength_socket_name, 1.0)
|
|
normal_source_node, normal_source_socket = nodeutils.get_node_and_socket_connected_to_input(shader_node, normal_socket_name)
|
|
normal_map_node = nodeutils.make_normal_map_node(nodes, normal_strength)
|
|
nodeutils.link_nodes(links, normal_source_node, normal_source_socket, normal_map_node, "Color")
|
|
nodeutils.link_nodes(links, normal_map_node, "Normal", bsdf_node, "Normal")
|
|
if bump_socket_name:
|
|
if bump_distance_socket_name:
|
|
bump_distance = nodeutils.get_node_input_value(shader_node, bump_distance_socket_name, 0.01) * BUMP_BAKE_MULTIPLIER
|
|
bump_source_node, bump_source_socket = nodeutils.get_node_and_socket_connected_to_input(shader_node, bump_socket_name)
|
|
# the bump map bakes to a normal map quite a bit weaker than it looks on the bump node, so increase it's strength here
|
|
bump_map_node = nodeutils.make_bump_node(nodes, 1, bump_distance)
|
|
nodeutils.link_nodes(links, bump_source_node, bump_source_socket, bump_map_node, "Height")
|
|
if normal_map_node:
|
|
nodeutils.link_nodes(links, normal_map_node, "Normal", bump_map_node, "Normal")
|
|
nodeutils.link_nodes(links, bump_map_node, "Normal", bsdf_node, "Normal")
|
|
|
|
# bake the source node output onto the target image and re-save it
|
|
image, image_name, exists = get_bake_image(mat, channel_id, width, height,
|
|
shader_node, normal_socket_name, bake_dir,
|
|
name_prefix=name_prefix, image_format=image_format)
|
|
image_node = cycles_bake_normal_output(context, mat, bsdf_node, image, image_name,
|
|
no_prep=no_prep, image_format=image_format)
|
|
|
|
# remove the bake nodes and restore the normal links to the bsdf
|
|
if bump_map_node:
|
|
nodes.remove(bump_map_node)
|
|
if normal_map_node:
|
|
nodes.remove(normal_map_node)
|
|
if image_node:
|
|
nodes.remove(image_node)
|
|
nodeutils.link_nodes(links, bsdf_normal_node, bsdf_normal_socket, bsdf_node, "Normal")
|
|
|
|
return image
|
|
|
|
|
|
def bake_bsdf_normal(context, bsdf_node, mat, channel_id, bake_dir,
|
|
name_prefix = "", override_size = 0,
|
|
no_prep = False, image_format="PNG"):
|
|
"""Bakes the normal output of the supplied BSDF shader node, to a normal map image.\n
|
|
Image size is determined by the sizes of the connected image nodes (or overriden).\n
|
|
Image name and path is determined by the texture channel id, material name, bake dir and name prefix.\n
|
|
Returns the normal image baked."""
|
|
|
|
# determine the size of the image to bake onto
|
|
width, height = get_connected_texture_size(bsdf_node, override_size, "Normal")
|
|
|
|
# get the node and output socket to bake from
|
|
nodes = mat.node_tree.nodes
|
|
|
|
# double the bump distance for baking
|
|
bump_distance = 0.01
|
|
normal_input_node, normal_input_socket = nodeutils.get_node_and_socket_connected_to_input(bsdf_node, "Normal")
|
|
if normal_input_node and normal_input_node.type == "BUMP":
|
|
bump_distance = nodeutils.get_node_input_value(normal_input_node, "Distance", bump_distance)
|
|
nodeutils.set_node_input_value(normal_input_node, "Distance", bump_distance * BUMP_BAKE_MULTIPLIER)
|
|
|
|
# bake the source node output onto the target image and re-save it
|
|
image, image_name, exists = get_bake_image(mat, channel_id, width, height, bsdf_node, "Normal", bake_dir,
|
|
name_prefix=name_prefix, image_format=image_format)
|
|
image_node = cycles_bake_normal_output(context, mat, bsdf_node, image, image_name,
|
|
no_prep=no_prep, image_format=image_format)
|
|
|
|
if normal_input_node and normal_input_node.type == "BUMP":
|
|
nodeutils.set_node_input_value(normal_input_node, "Distance", bump_distance)
|
|
|
|
if image_node:
|
|
nodes.remove(image_node)
|
|
|
|
return image
|
|
|
|
|
|
def pack_value_image(value, mat, channel_id, bake_dir,
|
|
name_prefix="", size=64, image_format="PNG"):
|
|
"""Generates a 64 x 64 texture of a single value. Linear or sRGB depending on channel id.\n
|
|
Image name and path is determined by the texture channel id, material name, bake dir and name prefix."""
|
|
|
|
width = size
|
|
height = size
|
|
image, image_name, exists = get_bake_image(mat, channel_id, width, height, None, "", bake_dir,
|
|
name_prefix=name_prefix, image_format=image_format)
|
|
|
|
image_pixels = list(image.pixels)
|
|
|
|
l = len(image_pixels)
|
|
for i in range(0, l, 4):
|
|
image_pixels[i + 0] = value
|
|
image_pixels[i + 1] = value
|
|
image_pixels[i + 2] = value
|
|
image_pixels[i + 3] = 1
|
|
|
|
# replace-in-place all the pixels from the list:
|
|
image.pixels[:] = image_pixels
|
|
image.update()
|
|
image.save()
|
|
return image
|
|
|
|
|
|
|
|
def pack_RGBA(mat, channel_id, pack_mode, bake_dir,
|
|
image_r=None, image_g=None, image_b=None, image_a=None,
|
|
value_r=0, value_g=0, value_b=0, value_a=0,
|
|
name_prefix="", min_size=64, srgb=False, max_size=0,
|
|
reuse_existing=False, image_format="PNG"):
|
|
"""pack_mode = RGB_A, R_G_B_A"""
|
|
|
|
width = min_size
|
|
height = min_size
|
|
|
|
# get the largest dimensions
|
|
img : bpy.types.Image
|
|
for img in [ image_r, image_g, image_b, image_a ]:
|
|
if img:
|
|
if img.size[0] > width:
|
|
width = img.size[0]
|
|
if img.size[1] > height:
|
|
height = img.size[1]
|
|
|
|
width, height = imageutils.get_max_sized_width_height(width, height, max_size)
|
|
|
|
utils.log_info(f"Packing {mat.name} for {channel_id} ({width}x{height})")
|
|
|
|
# get the bake image
|
|
image, image_name, exists = get_bake_image(mat, channel_id, width, height, None, "", bake_dir,
|
|
name_prefix=name_prefix, force_srgb=srgb,
|
|
channel_pack=True, exact_name=True,
|
|
image_format=image_format)
|
|
|
|
# if we are reusing an existing image, return this now
|
|
if image and reuse_existing and exists:
|
|
utils.log_info(f"Resuing existing texture pack {image.name}, not baking.")
|
|
return image
|
|
|
|
remove_after = []
|
|
|
|
# if all images are not the same size, use resized copies
|
|
if image_r and (image_r.size[0] != width or image_r.size[1] != height):
|
|
image_r = image_r.copy()
|
|
utils.log_info(f"Resizing {image_r.name} for {channel_id}")
|
|
image_r.scale(width, height)
|
|
remove_after.append(image_r)
|
|
|
|
if image_g and (image_g.size[0] != width or image_g.size[1] != height):
|
|
image_g = image_g.copy()
|
|
utils.log_info(f"Resizing {image_g.name} for {channel_id}")
|
|
image_g.scale(width, height)
|
|
remove_after.append(image_g)
|
|
|
|
if image_b and (image_b.size[0] != width or image_b.size[1] != height):
|
|
image_b = image_b.copy()
|
|
utils.log_info(f"Resizing {image_b.name} for {channel_id}")
|
|
image_b.scale(width, height)
|
|
remove_after.append(image_b)
|
|
|
|
if image_a and (image_a.size[0] != width or image_a.size[1] != height):
|
|
image_a = image_a.copy()
|
|
utils.log_info(f"Resizing {image_a.name} for {channel_id}")
|
|
image_a.scale(width, height)
|
|
remove_after.append(image_a)
|
|
|
|
r_data = None
|
|
g_data = None
|
|
b_data = None
|
|
a_data = None
|
|
|
|
if image_r:
|
|
r_data = image_r.pixels[:]
|
|
if image_g:
|
|
g_data = image_g.pixels[:]
|
|
if image_b:
|
|
b_data = image_b.pixels[:]
|
|
if image_a:
|
|
a_data = image_a.pixels[:]
|
|
|
|
image_pixels = list(image.pixels)
|
|
|
|
l = len(image_pixels)
|
|
|
|
if pack_mode == "RGB_A":
|
|
for i in range(0, l, 4):
|
|
if r_data:
|
|
image_pixels[i] = r_data[i]
|
|
image_pixels[i + 1] = r_data[i + 1]
|
|
image_pixels[i + 2] = r_data[i + 2]
|
|
else:
|
|
image_pixels[i] = value_r
|
|
image_pixels[i + 1] = value_g
|
|
image_pixels[i + 2] = value_b
|
|
if a_data:
|
|
image_pixels[i + 3] = a_data[i]
|
|
else:
|
|
image_pixels[i + 2] = value_a
|
|
|
|
elif pack_mode == "R_G_B_A":
|
|
for i in range(0, l, 4):
|
|
if r_data:
|
|
image_pixels[i] = r_data[i]
|
|
else:
|
|
image_pixels[i] = value_r
|
|
if g_data:
|
|
image_pixels[i + 1] = g_data[i]
|
|
else:
|
|
image_pixels[i + 1] = value_g
|
|
if b_data:
|
|
image_pixels[i + 2] = b_data[i]
|
|
else:
|
|
image_pixels[i + 2] = value_b
|
|
if a_data:
|
|
image_pixels[i + 3] = a_data[i]
|
|
else:
|
|
image_pixels[i + 3] = value_a
|
|
|
|
image.pixels[:] = image_pixels
|
|
image.update()
|
|
image.save()
|
|
|
|
for img in remove_after:
|
|
bpy.data.images.remove(img)
|
|
|
|
return image
|
|
|
|
|
|
def get_compositor_tree(context) -> bpy.types.NodeGroup:
|
|
if B500():
|
|
return context.scene.compositing_node_group
|
|
else:
|
|
return context.scene.node_tree
|
|
|
|
|
|
def compositing_setup(context):
|
|
if B500():
|
|
old_tree = context.scene.compositing_node_group
|
|
tree = bpy.data.node_groups.new("Compositor Bake", "CompositorNodeTree")
|
|
context.scene.compositing_node_group = tree
|
|
nodes = tree.nodes
|
|
links = tree.links
|
|
store = (tree, old_tree)
|
|
else:
|
|
context.scene.use_nodes = True
|
|
tree = context.scene.node_tree
|
|
nodes = tree.nodes
|
|
links = tree.links
|
|
# store nodes state
|
|
store = {}
|
|
for node in nodes:
|
|
store[node] = node.mute
|
|
node.mute = True
|
|
return nodes, links, store, tree
|
|
|
|
|
|
def compositing_cleanup(context, store):
|
|
if B500():
|
|
context.scene.compositing_node_group = store[1]
|
|
bpy.data.node_groups.remove(store[0])
|
|
else:
|
|
nodes = context.scene.node_tree.nodes
|
|
# restore nodes and clean up
|
|
for node in store:
|
|
node.mute = store[node]
|
|
clean_up = []
|
|
for node in nodes:
|
|
if node not in store.keys():
|
|
clean_up.append(node)
|
|
for node in clean_up:
|
|
nodes.remove(node)
|
|
|
|
|
|
def compositor_pack_RGBA(mat, channel_id, pack_mode, bake_dir,
|
|
image_r: bpy.types.Image=None, image_g=None, image_b=None, image_a=None,
|
|
value_r=0, value_g=0, value_b=0, value_a=0,
|
|
name_prefix="", min_size=64, srgb=False, max_size=0,
|
|
reuse_existing=False, image_format="PNG"):
|
|
"""pack_mode = RGB_A, R_G_B_A"""
|
|
|
|
context = bpy.context
|
|
width = min_size
|
|
height = min_size
|
|
color_space = "sRGB" if srgb else "Non-Color"
|
|
color_depth = '16'
|
|
|
|
# get the largest dimensions
|
|
img : bpy.types.Image
|
|
for img in [ image_r, image_g, image_b, image_a ]:
|
|
if img:
|
|
if img.size[0] > width:
|
|
width = img.size[0]
|
|
if img.size[1] > height:
|
|
height = img.size[1]
|
|
|
|
width, height = imageutils.get_max_sized_width_height(width, height, max_size)
|
|
|
|
utils.log_info(f"Packing {mat.name} for {channel_id} ({width}x{height})")
|
|
|
|
# get the bake image
|
|
image_name = get_export_bake_image_name(mat, channel_id, name_prefix=name_prefix, exact_name=True)
|
|
# exr is the only format that retains correct channel packing and color space
|
|
image_path = os.path.join(bake_dir, image_name+".exr")
|
|
|
|
# if we are reusing an existing image, return this now
|
|
if os.path.exists(image_path):
|
|
utils.log_info(f"Resuing existing texture pack {image_name}, not baking.")
|
|
return imageutils.load_image(image_path, color_space)
|
|
|
|
nodes, links, store, node_tree = compositing_setup(context)
|
|
|
|
CNCC_node = nodeutils.make_shader_node(nodes, "CompositorNodeCombineColor")
|
|
CNCGO_node = None
|
|
if B500():
|
|
CNCGO_node = nodeutils.make_shader_node(nodes, "NodeGroupOutput")
|
|
node_tree.interface.new_socket(name="Image", in_out="OUTPUT", socket_type="NodeSocketColor")
|
|
node_tree.interface.new_socket(name="Alpha", in_out="OUTPUT", socket_type="NodeSocketFloat")
|
|
else:
|
|
CNCGO_node = nodeutils.make_shader_node(nodes, "CompositorNodeComposite")
|
|
CNCGO_node.use_alpha = True
|
|
nodeutils.set_node_input_value(CNCC_node, "Red", value_r)
|
|
nodeutils.set_node_input_value(CNCC_node, "Green", value_g)
|
|
nodeutils.set_node_input_value(CNCC_node, "Blue", value_b)
|
|
nodeutils.set_node_input_value(CNCC_node, "Alpha", value_a)
|
|
nodeutils.link_nodes(links, CNCC_node, "Image", CNCGO_node, "Image")
|
|
|
|
if pack_mode == "RGB_A":
|
|
|
|
if image_r:
|
|
if image_r.depth > 32: color_depth = '32'
|
|
CNI_R_node = nodeutils.make_shader_node(nodes, "CompositorNodeImage")
|
|
CNI_R_node.image = image_r
|
|
if image_r.size[0] != width or image_r.size[1] != height:
|
|
CNS_R_node = nodeutils.make_shader_node(nodes, "CompositorNodeScale")
|
|
if B500():
|
|
nodeutils.set_node_input_value(CNS_R_node, "Type", "Absolute")
|
|
else:
|
|
CNS_R_node.space = "ABSOLUTE"
|
|
nodeutils.set_node_input_value(CNS_R_node, "X", width)
|
|
nodeutils.set_node_input_value(CNS_R_node, "Y", height)
|
|
nodeutils.link_nodes(links, CNI_R_node, "Image", CNS_R_node, "Image")
|
|
nodeutils.link_nodes(links, CNS_R_node, "Image", CNCGO_node, "Image")
|
|
else:
|
|
nodeutils.link_nodes(links, CNI_R_node, "Image", CNCGO_node, "Image")
|
|
|
|
if image_a:
|
|
if image_a.depth > 32: color_depth = '32'
|
|
CNI_A_node = nodeutils.make_shader_node(nodes, "CompositorNodeImage")
|
|
CNI_A_node.image = image_a
|
|
if image_a.size[0] != width or image_a.size[1] != height:
|
|
CNS_A_node = nodeutils.make_shader_node(nodes, "CompositorNodeScale")
|
|
if B500():
|
|
nodeutils.set_node_input_value(CNS_A_node, "Type", "Absolute")
|
|
else:
|
|
CNS_A_node.space = "ABSOLUTE"
|
|
nodeutils.set_node_input_value(CNS_A_node, "X", width)
|
|
nodeutils.set_node_input_value(CNS_A_node, "Y", height)
|
|
nodeutils.link_nodes(links, CNI_A_node, "Image", CNS_A_node, "Image")
|
|
#nodeutils.link_nodes(links, CNS_A_node, "Image", CNC_node, "Alpha")
|
|
else:
|
|
nodeutils.link_nodes(links, CNI_A_node, "Image", CNCGO_node, "Alpha")
|
|
|
|
else:
|
|
|
|
if image_r:
|
|
if image_r.depth > 32: color_depth = '32'
|
|
CNI_R_node = nodeutils.make_shader_node(nodes, "CompositorNodeImage")
|
|
CNI_R_node.image = image_r
|
|
if image_r.size[0] != width or image_r.size[1] != height:
|
|
CNS_R_node = nodeutils.make_shader_node(nodes, "CompositorNodeScale")
|
|
if B500():
|
|
nodeutils.set_node_input_value(CNS_R_node, "Type", "Absolute")
|
|
else:
|
|
CNS_R_node.space = "ABSOLUTE"
|
|
nodeutils.set_node_input_value(CNS_R_node, "X", width)
|
|
nodeutils.set_node_input_value(CNS_R_node, "Y", height)
|
|
nodeutils.link_nodes(links, CNI_R_node, "Image", CNS_R_node, "Image")
|
|
nodeutils.link_nodes(links, CNS_R_node, "Image", CNCC_node, "Red")
|
|
else:
|
|
nodeutils.link_nodes(links, CNI_R_node, "Image", CNCC_node, "Red")
|
|
|
|
if image_g:
|
|
if image_g.depth > 32: color_depth = '32'
|
|
CNI_G_node = nodeutils.make_shader_node(nodes, "CompositorNodeImage")
|
|
CNI_G_node.image = image_g
|
|
if image_g.size[0] != width or image_g.size[1] != height:
|
|
CNS_G_node = nodeutils.make_shader_node(nodes, "CompositorNodeScale")
|
|
if B500():
|
|
nodeutils.set_node_input_value(CNS_G_node, "Type", "Absolute")
|
|
else:
|
|
CNS_G_node.space = "ABSOLUTE"
|
|
nodeutils.set_node_input_value(CNS_G_node, "X", width)
|
|
nodeutils.set_node_input_value(CNS_G_node, "Y", height)
|
|
nodeutils.link_nodes(links, CNI_G_node, "Image", CNS_G_node, "Image")
|
|
nodeutils.link_nodes(links, CNS_G_node, "Image", CNCC_node, "Green")
|
|
else:
|
|
nodeutils.link_nodes(links, CNI_G_node, "Image", CNCC_node, "Green")
|
|
|
|
if image_b:
|
|
if image_b.depth > 32: color_depth = '32'
|
|
CNI_B_node = nodeutils.make_shader_node(nodes, "CompositorNodeImage")
|
|
CNI_B_node.image = image_b
|
|
if image_b.size[0] != width or image_b.size[1] != height:
|
|
CNS_B_node = nodeutils.make_shader_node(nodes, "CompositorNodeScale")
|
|
if B500():
|
|
nodeutils.set_node_input_value(CNS_B_node, "Type", "Absolute")
|
|
else:
|
|
CNS_B_node.space = "ABSOLUTE"
|
|
nodeutils.set_node_input_value(CNS_B_node, "X", width)
|
|
nodeutils.set_node_input_value(CNS_B_node, "Y", height)
|
|
nodeutils.link_nodes(links, CNI_B_node, "Image", CNS_B_node, "Image")
|
|
nodeutils.link_nodes(links, CNS_B_node, "Image", CNCC_node, "Blue")
|
|
else:
|
|
nodeutils.link_nodes(links, CNI_B_node, "Image", CNCC_node, "Blue")
|
|
|
|
if image_a:
|
|
if image_a.depth > 32: color_depth = '32'
|
|
CNI_A_node = nodeutils.make_shader_node(nodes, "CompositorNodeImage")
|
|
CNI_A_node.image = image_a
|
|
if image_a.size[0] != width or image_a.size[1] != height:
|
|
CNS_A_node = nodeutils.make_shader_node(nodes, "CompositorNodeScale")
|
|
if B500():
|
|
nodeutils.set_node_input_value(CNS_A_node, "Type", "Absolute")
|
|
else:
|
|
CNS_A_node.space = "ABSOLUTE"
|
|
nodeutils.set_node_input_value(CNS_A_node, "X", width)
|
|
nodeutils.set_node_input_value(CNS_A_node, "Y", height)
|
|
nodeutils.link_nodes(links, CNI_A_node, "Image", CNS_A_node, "Image")
|
|
nodeutils.link_nodes(links, CNS_A_node, "Image", CNCC_node, "Alpha")
|
|
#nodeutils.link_nodes(links, CNS_A_node, "Image", CNC_node, "Alpha")
|
|
else:
|
|
nodeutils.link_nodes(links, CNI_A_node, "Image", CNCC_node, "Alpha")
|
|
nodeutils.link_nodes(links, CNI_A_node, "Image", CNCGO_node, "Alpha")
|
|
|
|
X = context.scene.render.resolution_x
|
|
Y = context.scene.render.resolution_y
|
|
FP = context.scene.render.filepath
|
|
FF = context.scene.render.image_settings.file_format
|
|
CD = context.scene.render.image_settings.color_depth
|
|
CM = context.scene.render.image_settings.color_mode
|
|
VT = context.scene.view_settings.view_transform
|
|
LK = context.scene.view_settings.look
|
|
GA = context.scene.view_settings.gamma
|
|
EX = context.scene.view_settings.exposure
|
|
context.scene.render.resolution_x = width
|
|
context.scene.render.resolution_y = height
|
|
context.scene.render.image_settings.file_format = 'OPEN_EXR'
|
|
context.scene.render.image_settings.color_depth = color_depth
|
|
context.scene.render.image_settings.color_mode = 'RGBA'
|
|
context.scene.render.image_settings.exr_codec = 'ZIP'
|
|
context.scene.render.image_settings.color_management = 'OVERRIDE'
|
|
context.scene.render.image_settings.linear_colorspace_settings.name = color_space
|
|
|
|
context.scene.render.filepath = image_path
|
|
context.scene.view_settings.view_transform = 'Standard'
|
|
context.scene.view_settings.look = 'None'
|
|
context.scene.view_settings.gamma = 1
|
|
context.scene.view_settings.exposure = 0
|
|
|
|
for node in nodes:
|
|
node.select = False
|
|
CNCGO_node.select = True
|
|
node_tree.nodes.active = CNCGO_node
|
|
|
|
bpy.ops.render.render(write_still=True)
|
|
|
|
context.scene.render.resolution_x = X
|
|
context.scene.render.resolution_y = Y
|
|
context.scene.render.filepath = FP
|
|
context.scene.render.image_settings.file_format = FF
|
|
context.scene.render.image_settings.color_depth = CD
|
|
context.scene.render.image_settings.color_mode = CM
|
|
context.scene.view_settings.view_transform = VT
|
|
context.scene.view_settings.look = LK
|
|
context.scene.view_settings.gamma = GA
|
|
context.scene.view_settings.exposure = EX
|
|
|
|
compositing_cleanup(context, store)
|
|
|
|
image: bpy.types.Image = imageutils.load_image(image_path, color_space)
|
|
return image
|
|
|
|
|
|
def cycles_bake_color_output(context, mat, source_node, source_socket,
|
|
image: bpy.types.Image, image_name,
|
|
no_prep=False, image_format="PNG"):
|
|
"""Runs a cycles bake of the supplied source node and socket output onto the supplied image.\n
|
|
Returns a new image node with the image."""
|
|
|
|
if not (utils.material_exists(mat) and source_node and source_socket and image):
|
|
return None
|
|
|
|
nodes = mat.node_tree.nodes
|
|
links = mat.node_tree.links
|
|
|
|
output_node = nodeutils.find_node_by_type(nodes, "OUTPUT_MATERIAL")
|
|
output_source, output_source_socket = nodeutils.get_node_and_socket_connected_to_input(output_node, "Surface")
|
|
|
|
image_node = nodeutils.make_image_node(nodes, image, "bake")
|
|
image_node.name = image_name
|
|
|
|
utils.log_info(f"Baking: {image_name} / {source_node.name} / {nodeutils.safe_socket_name(source_socket)}")
|
|
|
|
if not no_prep:
|
|
bake_state = prep_bake(context, mat=mat, image_format=image_format, make_surface=True)
|
|
|
|
nodeutils.link_nodes(links, source_node, source_socket, output_node, "Surface")
|
|
image_node.select = True
|
|
nodes.active = image_node
|
|
|
|
bpy.ops.object.bake(type='COMBINED')
|
|
|
|
context.scene.render.image_settings.color_depth = '8'
|
|
context.scene.render.image_settings.color_mode = 'RGB' if image.depth == 24 else 'RGBA'
|
|
|
|
image.save_render(filepath = bpy.path.abspath(image.filepath), scene = context.scene)
|
|
image.reload()
|
|
|
|
if output_source:
|
|
nodeutils.link_nodes(links, output_source, output_source_socket, output_node, "Surface")
|
|
|
|
if not no_prep:
|
|
post_bake(context, bake_state)
|
|
|
|
return image_node
|
|
|
|
|
|
def cycles_bake_normal_output(context, mat, bsdf_node, image, image_name,
|
|
no_prep=False, image_format="PNG"):
|
|
"""Runs a cycles bake of the normal output of the supplied BSDF shader node to the supplied image.
|
|
Returns a new image node with the image."""
|
|
|
|
nodes = mat.node_tree.nodes
|
|
links = mat.node_tree.links
|
|
|
|
output_node = nodeutils.find_node_by_type(nodes, "OUTPUT_MATERIAL")
|
|
|
|
image_node = nodeutils.make_image_node(nodes, image, "bake")
|
|
image_node.name = image_name
|
|
|
|
utils.log_info("Baking normal: " + image_name)
|
|
|
|
if not no_prep:
|
|
bake_state = prep_bake(context, mat=mat, image_format=image_format, make_surface=True)
|
|
|
|
nodeutils.link_nodes(links, bsdf_node, "BSDF", output_node, "Surface")
|
|
image_node.select = True
|
|
nodes.active = image_node
|
|
|
|
bpy.ops.object.bake(type='NORMAL')
|
|
|
|
context.scene.render.image_settings.color_depth = '8'
|
|
context.scene.render.image_settings.color_mode = 'RGB' if image.depth == 24 else 'RGBA'
|
|
image.save_render(filepath = bpy.path.abspath(image.filepath), scene = context.scene)
|
|
image.reload()
|
|
|
|
if not no_prep:
|
|
post_bake(context, bake_state)
|
|
|
|
return image_node
|
|
|
|
|
|
def get_largest_texture_to_node(node, done):
|
|
"""Recursively traverses the input sockets to the supplied node and
|
|
returns the largest found texture dimensions, or [0, 0] if nothing found."""
|
|
|
|
largest_width = 0
|
|
largest_height = 0
|
|
for socket in node.inputs:
|
|
if socket.is_linked:
|
|
width, height = get_largest_texture_to_socket(node, socket, done)
|
|
if width > largest_width:
|
|
largest_width = width
|
|
if height > largest_height:
|
|
largest_height = height
|
|
return largest_width, largest_height
|
|
|
|
|
|
def get_largest_texture_to_socket(node, socket, done):
|
|
"""Recursively traverses the nodes connected to the supplied node's input socket and
|
|
returns the largest found texture dimensions, or [0, 0] if nothing found."""
|
|
|
|
connected_node = nodeutils.get_node_connected_to_input(node, socket)
|
|
if not connected_node or connected_node in done:
|
|
return 0, 0
|
|
|
|
done.append(connected_node)
|
|
|
|
if connected_node.type == "TEX_IMAGE":
|
|
if connected_node.image:
|
|
return connected_node.image.size[0], connected_node.image.size[1]
|
|
else:
|
|
return 0, 0
|
|
else:
|
|
return get_largest_texture_to_node(connected_node, done)
|
|
|
|
|
|
def get_connected_texture_size(node, override_size, *sockets):
|
|
"""Recursively searches through all connected image nodes to the supplied node's input socket(s)
|
|
and returns the largest image dimensions found.\n
|
|
If no connected image nodes found then returns the preferences minimum export texture size.\n
|
|
Returned width and height can be overridden with override_size."""
|
|
|
|
prefs = vars.prefs()
|
|
width = 0
|
|
height = 0
|
|
if override_size > 0:
|
|
return override_size, override_size
|
|
for socket in sockets:
|
|
if socket:
|
|
w, h = get_largest_texture_to_socket(node, socket, [])
|
|
if w > width:
|
|
width = w
|
|
if h > height:
|
|
height = h
|
|
if width == 0:
|
|
width = int(prefs.export_texture_size)
|
|
if height == 0:
|
|
height = int(prefs.export_texture_size)
|
|
return width, height
|
|
|
|
|
|
def get_image_target(image_name, width, height, image_dir,
|
|
is_data = True, has_alpha = False, force_new = False, channel_packed = False,
|
|
format="PNG"):
|
|
"""Returns (image, exists)"""
|
|
depth = 24
|
|
if has_alpha:
|
|
depth = 32
|
|
color_space = "sRGB"
|
|
if is_data:
|
|
color_space = "Non-Color"
|
|
|
|
img : bpy.types.Image
|
|
|
|
# find an existing image in the same folder, with the same name to reuse:
|
|
if not force_new:
|
|
for img in bpy.data.images:
|
|
# it is expected that the image name is a vary particular prefixed and suffixed name based on the
|
|
# material and texture channel and bake index, thus any duplication of this should mean the same intended image.
|
|
if img and utils.is_name_or_duplication(img.name, image_name):
|
|
|
|
img_folder, img_file = os.path.split(bpy.path.abspath(img.filepath))
|
|
same_folder = False
|
|
try:
|
|
if os.path.samefile(image_dir, img_folder):
|
|
same_folder = True
|
|
except:
|
|
same_folder = False
|
|
|
|
if same_folder:
|
|
try:
|
|
if img.file_format == format and img.depth == depth and img.size[0] == width and img.size[1] == height:
|
|
utils.log_info(f"Reusing image: {image_name} - {color_space} - {img.file_format}")
|
|
colorspace.set_image_color_space(img, color_space)
|
|
if len(img.pixels) > 0:
|
|
return img, True
|
|
except:
|
|
utils.log_info("Bad image: " + img.name)
|
|
if img:
|
|
utils.log_info(f"Wrong format: {img.name} / format: {img.file_format} == {format} ? depth: {str(depth)} == {str(img.depth)} ?")
|
|
bpy.data.images.remove(img)
|
|
|
|
# or if the image file exists (but not in blender yet)
|
|
file = imageutils.find_file_by_name(image_dir, image_name)
|
|
if file:
|
|
img = imageutils.load_image(file, color_space, reuse_existing = False)
|
|
try:
|
|
if img.file_format == format and img.depth == depth and img.size[0] == width and img.size[1] == height:
|
|
colorspace.set_image_color_space(img, color_space)
|
|
utils.log_info(f"Reusing found image file: {img.filepath} - {img.colorspace_settings.name} - {img.format}")
|
|
if len(img.pixels) > 0:
|
|
return img, True
|
|
except:
|
|
utils.log_info("Bad found image file: " + img.name)
|
|
if img:
|
|
utils.log_info(f"Wrong format: {img.name} / format: {img.file_format} == {format} ? depth: {str(depth)} == {str(img.depth)} ?")
|
|
bpy.data.images.remove(img)
|
|
|
|
# or just make a new one:
|
|
utils.log_info(f"Creating new image: {image_name} size: {str(width)} format: {format}")
|
|
img = imageutils.make_new_image(image_name, width, height, format, image_dir, is_data, has_alpha, channel_packed)
|
|
colorspace.set_image_color_space(img, color_space)
|
|
return img, False
|
|
|
|
|
|
def get_bake_dir(chr_cache):
|
|
bake_path = os.path.join(chr_cache.get_import_dir(), "textures", chr_cache.get_character_id(), "Blender_Baked")
|
|
return bake_path
|
|
|
|
|
|
def combine_normal(context, chr_cache, mat_cache):
|
|
"""Combines the normal and bump maps by baking and connecting a new normal map."""
|
|
|
|
init_bake(5001)
|
|
|
|
mat = mat_cache.material
|
|
nodes = mat.node_tree.nodes
|
|
links = mat.node_tree.links
|
|
mat_name = utils.strip_name(mat.name)
|
|
shader = params.get_shader_name(mat_cache)
|
|
bsdf_node, shader_node, mix_node = nodeutils.get_shader_nodes(mat, shader)
|
|
bake_path = get_bake_dir(chr_cache)
|
|
|
|
selection = context.selected_objects.copy()
|
|
active = utils.get_active_object()
|
|
|
|
if mat_cache.material_type == "DEFAULT" or mat_cache.material_type == "SSS":
|
|
|
|
nodeutils.clear_cursor()
|
|
|
|
normal_node, normal_socket = nodeutils.get_node_and_socket_connected_to_input(shader_node, "Normal Map")
|
|
bump_node, bump_socket = nodeutils.get_node_and_socket_connected_to_input(shader_node, "Bump Map")
|
|
|
|
if normal_node and bump_node:
|
|
|
|
normal_image = bake_rl_bump_and_normal(context, shader_node, bsdf_node, mat, "Normal", bake_path)
|
|
normal_image_name = utils.unique_name("(NORMAL)")
|
|
normal_image_node = nodeutils.make_image_node(nodes, normal_image, normal_image_name)
|
|
nodeutils.link_nodes(links, normal_image_node, "Color", shader_node, "Normal Map")
|
|
nodeutils.unlink_node_output(links, shader_node, "Bump Map")
|
|
mat_cache.parameters.default_normal_strength = 1.0
|
|
nodeutils.set_node_input_value(shader_node, "Normal Strength", 1.0)
|
|
|
|
elif bump_node:
|
|
|
|
normal_image = bake_rl_bump_and_normal(context, shader_node, bsdf_node, mat, "Normal", bake_path,
|
|
normal_socket_name = "", bump_socket_name = "")
|
|
normal_image_name = utils.unique_name("(NORMAL)")
|
|
normal_image_node = nodeutils.make_image_node(nodes, normal_image, normal_image_name)
|
|
nodeutils.link_nodes(links, normal_image_node, "Color", shader_node, "Normal Map")
|
|
nodeutils.unlink_node_output(links, shader_node, "Bump Map")
|
|
mat_cache.parameters.default_normal_strength = 1.0
|
|
nodeutils.set_node_input_value(shader_node, "Normal Strength", 1.0)
|
|
|
|
elif normal_node and normal_node.type != "TEX_IMAGE":
|
|
|
|
normal_image = bake_rl_bump_and_normal(context, shader_node, bsdf_node, mat, "Normal", bake_path,
|
|
bump_socket_name = "", bump_distance_socket_name = "")
|
|
normal_image_name = utils.unique_name("(NORMAL)")
|
|
normal_image_node = nodeutils.make_image_node(nodes, normal_image, normal_image_name)
|
|
nodeutils.link_nodes(links, normal_image_node, "Color", shader_node, "Normal Map")
|
|
mat_cache.parameters.default_normal_strength = 1.0
|
|
nodeutils.set_node_input_value(shader_node, "Normal Strength", 1.0)
|
|
|
|
utils.try_select_objects(selection, True)
|
|
if active:
|
|
utils.set_active_object(active)
|
|
|
|
|
|
def bake_flow_to_normal(context, chr_cache, mat_cache):
|
|
"""Convert's a hair shader's flow map into an approximate normal map."""
|
|
|
|
init_bake(4001)
|
|
|
|
mat = mat_cache.material
|
|
nodes = mat.node_tree.nodes
|
|
links = mat.node_tree.links
|
|
mat_name = utils.strip_name(mat.name)
|
|
shader = params.get_shader_name(mat_cache)
|
|
bsdf_node, shader_node, mix_node = nodeutils.get_shader_nodes(mat, shader)
|
|
bake_dir = mat_cache.get_tex_dir(chr_cache)
|
|
|
|
if mat_cache.material_type == "HAIR":
|
|
|
|
nodeutils.clear_cursor()
|
|
|
|
mode_selection = utils.store_mode_selection_state()
|
|
|
|
# get the flow map
|
|
flow_node = nodeutils.get_node_connected_to_input(shader_node, "Flow Map")
|
|
flow_image: bpy.types.Image = flow_node.image
|
|
|
|
width = flow_image.size[0]
|
|
height = flow_image.size[1]
|
|
|
|
normal_node = None
|
|
normal_image = None
|
|
|
|
# try to re-use normal map
|
|
if nodeutils.has_connected_input(shader_node, "Normal Map"):
|
|
normal_node = nodeutils.get_node_connected_to_input(shader_node, "Normal Map")
|
|
|
|
if normal_node and normal_node.image:
|
|
normal_image: bpy.types.Image = normal_node.image
|
|
|
|
try:
|
|
|
|
utils.log_info("Found existing normal image: " + normal_image.name + ": " + normal_image.filepath)
|
|
if normal_image.size[0] != width or normal_image.size[1] != height:
|
|
utils.log_info("Resizing normal image: " + str(width) + " x " + str(height))
|
|
normal_image.scale(width, height)
|
|
|
|
except:
|
|
|
|
utils.log_info("Removing bad normal image: " + normal_image.name)
|
|
bpy.data.images.remove(normal_image)
|
|
|
|
# if no existing normal image, create one and link it to the shader
|
|
if not normal_image:
|
|
utils.log_info("Creating new normal image.")
|
|
normal_image = imageutils.make_new_image(mat_name + "_Normal", width, height, "PNG", bake_dir, True, False, False)
|
|
if not normal_node:
|
|
utils.log_info("Creating new normal image node.")
|
|
normal_node = nodeutils.make_image_node(nodes, normal_image, "Generated Normal Map")
|
|
nodeutils.link_nodes(links, normal_node, "Color", shader_node, "Normal Map")
|
|
|
|
# convert the flow map to a normal map
|
|
tangent = mat_cache.parameters.hair_tangent_vector
|
|
flip_y = mat_cache.parameters.hair_tangent_flip_green > 0
|
|
utils.log_info("Converting Flow Map to Normal Map...")
|
|
convert_flow_to_normal(flow_image, normal_image, tangent, flip_y)
|
|
|
|
utils.restore_mode_selection_state(mode_selection)
|
|
|
|
|
|
def convert_flow_to_normal(flow_image: bpy.types.Image, normal_image: bpy.types.Image, tangent, flip_y):
|
|
|
|
# fetching a copy of the normal pixels as a list gives us the fastest write speed:
|
|
normal_pixels = list(normal_image.pixels)
|
|
# fetching the flow pixels as a tuple with slice notation gives the fastest read speed:
|
|
flow_pixels = flow_image.pixels[:]
|
|
|
|
#tangent_vector = Vector(tangent)
|
|
|
|
if flip_y:
|
|
flip = -1
|
|
else:
|
|
flip = 1
|
|
|
|
l = len(flow_pixels)
|
|
for i in range(0, l, 4):
|
|
|
|
# rgb -> flow_vector
|
|
flow_vector = Vector((flow_pixels[i + 0] * 2 - 1,
|
|
(flow_pixels[i + 1] * 2 - 1) * flip,
|
|
flow_pixels[i + 2] * 2 - 1))
|
|
tangent_vector = Vector((-flow_vector.y, flow_vector.x, 0))
|
|
|
|
# calculate normal vector
|
|
normal_vector = flow_vector.cross(tangent_vector)
|
|
normal_vector.x *= 0.35
|
|
normal_vector.y *= 0.35
|
|
normal_vector.normalize()
|
|
|
|
# normal_vector -> rgb
|
|
normal_pixels[i + 0] = (normal_vector[0] + 1) / 2
|
|
normal_pixels[i + 1] = (normal_vector[1] + 1) / 2
|
|
normal_pixels[i + 2] = (normal_vector[2] + 1) / 2
|
|
normal_pixels[i + 3] = 1
|
|
|
|
# replace-in-place all the pixels from the list:
|
|
normal_image.pixels[:] = normal_pixels
|
|
normal_image.update()
|
|
normal_image.save()
|
|
|
|
|
|
|
|
|
|
def pack_rgb_a(mat, bake_dir, channel_id, shader_node, pack_node_id,
|
|
rgb_id, a_id, rgb_socket, a_socket, rgb_default, a_default,
|
|
srgb=False, max_size=0, reuse_existing=False, image_format="PNG"):
|
|
"""Pack 2 textures into the RGB and A channels of a single texture."""
|
|
|
|
global NODE_CURSOR
|
|
|
|
nodes = mat.node_tree.nodes
|
|
links = mat.node_tree.links
|
|
|
|
rgb_node = None
|
|
a_node = None
|
|
pack_node = nodeutils.find_node_by_type_and_keywords(nodes, "TEX_IMAGE", f"({pack_node_id})")
|
|
|
|
if rgb_id:
|
|
rgb_node = nodeutils.find_node_by_type_and_keywords(nodes, "TEX_IMAGE", f"({rgb_id})")
|
|
if a_id:
|
|
a_node = nodeutils.find_node_by_type_and_keywords(nodes, "TEX_IMAGE", f"({a_id})")
|
|
|
|
if rgb_node and a_node:
|
|
|
|
rgb_tiling_node = nodeutils.get_node_connected_to_input(rgb_node, "Vector")
|
|
a_tiling_node = nodeutils.get_node_connected_to_input(a_node, "Vector")
|
|
|
|
if not pack_node:
|
|
if rgb_node and a_node:
|
|
image = compositor_pack_RGBA(mat, channel_id, "RGB_A", bake_dir,
|
|
image_r=rgb_node.image, image_a=a_node.image,
|
|
value_r=rgb_default, value_g=rgb_default,
|
|
value_b=rgb_default, value_a=a_default,
|
|
srgb=srgb, max_size=max_size, reuse_existing=reuse_existing,
|
|
image_format=image_format)
|
|
pack_node = nodeutils.make_image_node(nodes, image, f"({pack_node_id})")
|
|
|
|
n = nodeutils.closest_to(shader_node, Vector((-1, -0.25)), rgb_node, a_node)
|
|
pack_node.location = n.location
|
|
if rgb_node:
|
|
nodeutils.link_nodes(links, pack_node, "Color", shader_node, rgb_socket)
|
|
rgb_node.location = NODE_CURSOR
|
|
NODE_CURSOR += Vector((0,-300))
|
|
if a_node:
|
|
nodeutils.link_nodes(links, pack_node, "Alpha", shader_node, a_socket)
|
|
a_node.location = NODE_CURSOR
|
|
NODE_CURSOR += Vector((0,-300))
|
|
|
|
tiling_node = None
|
|
if rgb_tiling_node and a_tiling_node:
|
|
nodes.remove(a_tiling_node)
|
|
tiling_node = rgb_tiling_node
|
|
nodeutils.unlink_node_output(links, tiling_node, "Vector")
|
|
elif rgb_tiling_node:
|
|
tiling_node = rgb_tiling_node
|
|
else:
|
|
tiling_node = a_tiling_node
|
|
|
|
if tiling_node:
|
|
tiling_node.location = pack_node.location + Vector((-300,0))
|
|
nodeutils.link_nodes(links, tiling_node, "Vector", pack_node, "Vector")
|
|
|
|
|
|
def pack_r_g_b_a(mat, bake_dir, channel_id, shader_node, pack_node_id,
|
|
r_id, g_id, b_id, a_id,
|
|
r_socket, g_socket, b_socket, a_socket,
|
|
r_default, g_default, b_default, a_default,
|
|
srgb=False, max_size=0, reuse_existing=False,
|
|
image_format="PNG"):
|
|
"""Pack 4 textures into the RGBA channels of a single texture."""
|
|
|
|
global NODE_CURSOR
|
|
nodes = mat.node_tree.nodes
|
|
links = mat.node_tree.links
|
|
|
|
r_node = None
|
|
g_node = None
|
|
b_node = None
|
|
a_node = None
|
|
r_image = None
|
|
g_image = None
|
|
b_image = None
|
|
a_image = None
|
|
pack_node = nodeutils.find_node_by_type_and_keywords(nodes, "TEX_IMAGE", f"({pack_node_id})")
|
|
sep_node = nodeutils.find_node_by_keywords(nodes, pack_node_id + "_SPLIT")
|
|
|
|
if r_id:
|
|
r_node = nodeutils.find_node_by_type_and_keywords(nodes, "TEX_IMAGE", f"({r_id})")
|
|
if r_node:
|
|
r_image = r_node.image
|
|
if g_id:
|
|
g_node = nodeutils.find_node_by_type_and_keywords(nodes, "TEX_IMAGE", f"({g_id})")
|
|
if g_node:
|
|
g_image = g_node.image
|
|
if b_id:
|
|
b_node = nodeutils.find_node_by_type_and_keywords(nodes, "TEX_IMAGE", f"({b_id})")
|
|
if b_node:
|
|
b_image = b_node.image
|
|
if a_id:
|
|
a_node = nodeutils.find_node_by_type_and_keywords(nodes, "TEX_IMAGE", f"({a_id})")
|
|
if a_node:
|
|
a_image = a_node.image
|
|
|
|
if utils.count_maps(r_node, g_node, b_node, a_node) > 1:
|
|
|
|
if not pack_node:
|
|
image = compositor_pack_RGBA(mat, channel_id, "R_G_B_A", bake_dir,
|
|
image_r=r_image, image_g=g_image,
|
|
image_b=b_image, image_a=a_image,
|
|
value_r=r_default, value_g=g_default,
|
|
value_b=b_default, value_a=a_default,
|
|
srgb=srgb, max_size=max_size, reuse_existing=reuse_existing,
|
|
image_format=image_format)
|
|
pack_node = nodeutils.make_image_node(nodes, image, f"({pack_node_id})")
|
|
|
|
if not sep_node:
|
|
sep_node = nodeutils.make_separate_rgb_node(nodes, "Pack Split", f"({pack_node_id}_SPLIT)")
|
|
|
|
n = nodeutils.closest_to(shader_node, Vector((-1, -0.25)), r_node, g_node, b_node, a_node)
|
|
pack_node.location = n.location
|
|
sep_node.location = pack_node.location + Vector((275, 69))
|
|
nodeutils.link_nodes(links, pack_node, "Color", sep_node, "Image")
|
|
nodeutils.link_nodes(links, pack_node, "Color", sep_node, "Color")
|
|
if r_node:
|
|
nodeutils.link_nodes(links, sep_node, "R", shader_node, r_socket)
|
|
nodeutils.link_nodes(links, sep_node, "Red", shader_node, r_socket)
|
|
r_node.location = NODE_CURSOR
|
|
NODE_CURSOR += Vector((0,-300))
|
|
if g_node:
|
|
nodeutils.link_nodes(links, sep_node, "G", shader_node, g_socket)
|
|
nodeutils.link_nodes(links, sep_node, "Green", shader_node, g_socket)
|
|
g_node.location = NODE_CURSOR
|
|
NODE_CURSOR += Vector((0,-300))
|
|
if b_node:
|
|
nodeutils.link_nodes(links, sep_node, "B", shader_node, b_socket)
|
|
nodeutils.link_nodes(links, sep_node, "Blue", shader_node, b_socket)
|
|
b_node.location = NODE_CURSOR
|
|
NODE_CURSOR += Vector((0,-300))
|
|
if a_node:
|
|
nodeutils.link_nodes(links, pack_node, "Alpha", shader_node, a_socket)
|
|
a_node.location = NODE_CURSOR
|
|
NODE_CURSOR += Vector((0,-300))
|
|
|
|
|
|
def unlink_texture_nodes(mat, *tex_ids):
|
|
global NODE_CURSOR
|
|
nodes = mat.node_tree.nodes
|
|
links = mat.node_tree.links
|
|
|
|
for tex_id in tex_ids:
|
|
tex_node = nodeutils.find_node_by_type_and_keywords(nodes, "TEX_IMAGE", f"({tex_id})")
|
|
if tex_node:
|
|
nodeutils.unlink_node_output(links, tex_node, "Color")
|
|
nodeutils.unlink_node_output(links, tex_node, "Alpha")
|
|
tex_node.location = NODE_CURSOR
|
|
NODE_CURSOR += Vector((0,-300))
|
|
|
|
|
|
def pack_skin_shader(chr_cache, mat_cache, shader_node, limit_textures = False):
|
|
prefs = vars.prefs()
|
|
|
|
mat = mat_cache.material
|
|
wrinkle_node = wrinkle.get_wrinkle_shader_node(mat)
|
|
bake_dir = mat_cache.get_tex_dir(chr_cache)
|
|
reuse = chr_cache.build_count > 0 and prefs.build_reuse_baked_channel_packs
|
|
|
|
if prefs.build_limit_textures:
|
|
unlink_texture_nodes(mat, "BLEND2", "NORMALBLEND", "CFULCMASK", "ENNASK")
|
|
|
|
pack_max_tex_size = 8192
|
|
|
|
if wrinkle_node:
|
|
|
|
if prefs.build_pack_wrinkle_diffuse_roughness:
|
|
|
|
# this can free up 1 more texture, but takes longer.
|
|
pack_rgb_a(mat, bake_dir, vars.PACK_DIFFUSEROUGHNESS_NAME, wrinkle_node, vars.PACK_DIFFUSEROUGHNESS_ID,
|
|
"DIFFUSE", "ROUGHNESS",
|
|
"Diffuse Map", "Roughness Map", 1.0, 0.5, srgb = True,
|
|
reuse_existing = reuse,
|
|
max_size=pack_max_tex_size)
|
|
|
|
pack_rgb_a(mat, bake_dir, vars.PACK_DIFFUSEROUGHNESSBLEND1_NAME, wrinkle_node, vars.PACK_DIFFUSEROUGHNESSBLEND1_ID,
|
|
"WRINKLEDIFFUSE1", "WRINKLEROUGHNESS1",
|
|
"Diffuse Blend Map 1", "Roughness Blend Map 1", 1.0, 0.5, srgb = True,
|
|
reuse_existing = reuse,
|
|
max_size=pack_max_tex_size)
|
|
|
|
pack_rgb_a(mat, bake_dir, vars.PACK_DIFFUSEROUGHNESSBLEND2_NAME, wrinkle_node, vars.PACK_DIFFUSEROUGHNESSBLEND2_ID,
|
|
"WRINKLEDIFFUSE2", "WRINKLEROUGHNESS2",
|
|
"Diffuse Blend Map 2", "Roughness Blend Map 2", 1.0, 0.5, srgb = True,
|
|
reuse_existing = reuse,
|
|
max_size=pack_max_tex_size)
|
|
|
|
pack_rgb_a(mat, bake_dir, vars.PACK_DIFFUSEROUGHNESSBLEND3_NAME, wrinkle_node, vars.PACK_DIFFUSEROUGHNESSBLEND3_ID,
|
|
"WRINKLEDIFFUSE3", "WRINKLEROUGHNESS3",
|
|
"Diffuse Blend Map 3", "Roughness Blend Map 3", 1.0, 0.5, srgb = True,
|
|
reuse_existing = reuse,
|
|
max_size=pack_max_tex_size)
|
|
|
|
else:
|
|
|
|
# otherwise pack the 4 roughness channels into a single RGBA texture
|
|
pack_r_g_b_a(mat, bake_dir, vars.PACK_WRINKLEROUGHNESS_NAME, wrinkle_node, vars.PACK_WRINKLEROUGHNESS_ID,
|
|
"WRINKLEROUGHNESS1", "WRINKLEROUGHNESS2", "WRINKLEROUGHNESS3", "ROUGHNESS",
|
|
"Roughness Blend Map 1", "Roughness Blend Map 2", "Roughness Blend Map 3", "Roughness Map",
|
|
0.5, 0.5, 0.5, 0.5,
|
|
reuse_existing = reuse,
|
|
max_size=pack_max_tex_size)
|
|
|
|
pack_r_g_b_a(mat, bake_dir, vars.PACK_WRINKLEDISPLACEMENT_NAME, wrinkle_node, vars.PACK_WRINKLEDISPLACEMENT_ID,
|
|
"WRINKLEDISPLACEMENT1", "WRINKLEDISPLACEMENT2", "WRINKLEDISPLACEMENT3", "DISPLACE",
|
|
"Displacement Map 1", "Displacement Map 2", "Displacement Map 3", "Displacement Map",
|
|
0.5, 0.5, 0.5, 0.5,
|
|
reuse_existing = reuse,
|
|
max_size=pack_max_tex_size)
|
|
|
|
|
|
pack_r_g_b_a(mat, bake_dir, vars.PACK_WRINKLEFLOW_NAME, wrinkle_node, vars.PACK_WRINKLEFLOW_ID,
|
|
"WRINKLEFLOW1", "WRINKLEFLOW2", "WRINKLEFLOW3", "",
|
|
"Flow Map 1", "Flow Map 2", "Flow Map 3", "",
|
|
1.0, 1.0, 1.0, 1.0,
|
|
reuse_existing = reuse,
|
|
max_size=pack_max_tex_size)
|
|
|
|
# pack SSS and Transmission
|
|
pack_rgb_a(mat, bake_dir, vars.PACK_SSTM_NAME, shader_node, vars.PACK_SSTM_ID,
|
|
"SSS", "TRANSMISSION",
|
|
"Subsurface Map", "Transmission Map", 1.0, 0.0,
|
|
max_size = 1024,
|
|
reuse_existing = reuse)
|
|
|
|
# pack Metallic, Specular Mask, Micro Normal Mask and AO
|
|
pack_r_g_b_a(mat, bake_dir, vars.PACK_MSMNAO_NAME, shader_node, vars.PACK_MSMNAO_ID,
|
|
"METALLIC", "SPECMASK", "MICRONMASK", "AO",
|
|
"Metallic Map", "Specular Mask", "Micro Normal Mask", "AO Map",
|
|
0.0, 1.0, 1.0, 1.0,
|
|
reuse_existing = reuse,
|
|
max_size=pack_max_tex_size)
|
|
|
|
if prefs.build_skin_shader_dual_spec:
|
|
# pack SSS and Transmission
|
|
pack_rgb_a(mat, bake_dir, vars.PACK_MICRODETAIL_NAME, shader_node, vars.PACK_MICRODETAIL_ID,
|
|
"MICRONORMAL", "SKINSPECDETAIL",
|
|
"Micro Normal Map", "Specular Detail Mask", 1.0, 1.0,
|
|
reuse_existing = reuse,
|
|
max_size=pack_max_tex_size)
|
|
|
|
|
|
def pack_default_shader(chr_cache, mat_cache, shader_node):
|
|
prefs = vars.prefs()
|
|
|
|
mat = mat_cache.material
|
|
bake_dir = mat_cache.get_tex_dir(chr_cache)
|
|
reuse = chr_cache.build_count > 0 and prefs.build_reuse_baked_channel_packs
|
|
|
|
pack_max_tex_size = int(prefs.pack_max_tex_size)
|
|
|
|
# pack diffuse + alpha
|
|
pack_rgb_a(mat, bake_dir, vars.PACK_DIFFUSEALPHA_NAME, shader_node, vars.PACK_DIFFUSEALPHA_ID,
|
|
"DIFFUSE", "ALPHA",
|
|
"Diffuse Map", "Alpha Map",
|
|
1.0, 1.0, srgb = True,
|
|
reuse_existing = reuse,
|
|
max_size=pack_max_tex_size)
|
|
|
|
# pack Metallic, Specular Mask, Micro Normal Mask and AO
|
|
pack_r_g_b_a(mat, bake_dir, vars.PACK_MRSO_NAME, shader_node, vars.PACK_MRSO_ID,
|
|
"METALLIC", "ROUGHNESS", "SPECULAR", "AO",
|
|
"Metallic Map", "Roughness Map", "Specular Map", "AO Map",
|
|
0.0, 0.5, 1.0, 1.0,
|
|
reuse_existing = reuse,
|
|
max_size=pack_max_tex_size)
|
|
|
|
|
|
def pack_sss_shader(chr_cache, mat_cache, shader_node):
|
|
prefs = vars.prefs()
|
|
|
|
mat = mat_cache.material
|
|
bake_dir = mat_cache.get_tex_dir(chr_cache)
|
|
reuse = chr_cache.build_count > 0 and prefs.build_reuse_baked_channel_packs
|
|
|
|
pack_max_tex_size = int(prefs.pack_max_tex_size)
|
|
|
|
# pack diffuse + alpha
|
|
pack_rgb_a(mat, bake_dir, vars.PACK_DIFFUSEALPHA_NAME, shader_node, vars.PACK_DIFFUSEALPHA_ID,
|
|
"DIFFUSE", "ALPHA",
|
|
"Diffuse Map", "Alpha Map",
|
|
1.0, 1.0, srgb = True,
|
|
reuse_existing = reuse,
|
|
max_size=pack_max_tex_size)
|
|
|
|
# pack Metallic, Specular Mask, Micro Normal Mask and AO
|
|
pack_r_g_b_a(mat, bake_dir, vars.PACK_MRSO_NAME, shader_node, vars.PACK_MRSO_ID,
|
|
"METALLIC", "ROUGHNESS", "SPECULAR", "AO",
|
|
"Metallic Map", "Roughness Map", "Specular Map", "AO Map",
|
|
0.0, 0.5, 1.0, 1.0,
|
|
reuse_existing = reuse,
|
|
max_size=pack_max_tex_size)
|
|
|
|
# pack SSS, Transmission, Micro Normal Mask
|
|
pack_r_g_b_a(mat, bake_dir, vars.PACK_SSTMMNM_NAME, shader_node, vars.PACK_SSTMMNM_ID,
|
|
"SSS", "TRANSMISSION", "MICRONMASK", "",
|
|
"Subsurface Map", "Transmission Map", "Micro Normal Mask", "",
|
|
0.0, 1.0, 1.0, 1.0,
|
|
reuse_existing = reuse,
|
|
max_size=pack_max_tex_size)
|
|
|
|
|
|
def pack_hair_shader(chr_cache, mat_cache, shader_node):
|
|
prefs = vars.prefs()
|
|
|
|
mat = mat_cache.material
|
|
bake_dir = mat_cache.get_tex_dir(chr_cache)
|
|
reuse = chr_cache.build_count > 0 and prefs.build_reuse_baked_channel_packs
|
|
|
|
pack_max_tex_size = int(prefs.pack_max_tex_size)
|
|
|
|
# pack diffuse + alpha
|
|
pack_rgb_a(mat, bake_dir, vars.PACK_DIFFUSEALPHA_NAME, shader_node, vars.PACK_DIFFUSEALPHA_ID,
|
|
"DIFFUSE", "ALPHA",
|
|
"Diffuse Map", "Alpha Map",
|
|
1.0, 1.0, srgb = True,
|
|
reuse_existing = reuse,
|
|
max_size=pack_max_tex_size)
|
|
|
|
# pack Metallic, Roughness, Specular and AO
|
|
pack_r_g_b_a(mat, bake_dir, vars.PACK_MRSO_NAME, shader_node, vars.PACK_MRSO_ID,
|
|
"METALLIC", "ROUGHNESS", "SPECULAR", "AO",
|
|
"Metallic Map", "Roughness Map", "Specular Map", "AO Map",
|
|
0.0, 0.5, 1.0, 1.0,
|
|
reuse_existing = reuse,
|
|
max_size=pack_max_tex_size)
|
|
|
|
# pack Root map, ID map
|
|
pack_rgb_a(mat, bake_dir, vars.PACK_ROOTID_NAME, shader_node, vars.PACK_ROOTID_ID,
|
|
"HAIRROOT", "HAIRID",
|
|
"Root Map", "ID Map",
|
|
0.5, 0.5,
|
|
reuse_existing = reuse,
|
|
max_size=pack_max_tex_size)
|
|
|
|
|
|
def pack_shader_channels(chr_cache, mat_cache):
|
|
global NODE_CURSOR
|
|
prefs = vars.prefs()
|
|
|
|
init_bake(5001)
|
|
|
|
nodeutils.clear_cursor()
|
|
NODE_CURSOR = Vector((-4500, 400))
|
|
|
|
mode_selection = utils.store_mode_selection_state()
|
|
|
|
shader = params.get_shader_name(mat_cache)
|
|
mat = mat_cache.material
|
|
bsdf_node, shader_node, mix_node = nodeutils.get_shader_nodes(mat, shader)
|
|
if not shader_node:
|
|
# fall back to default shader
|
|
shader = "rl_pbr_shader"
|
|
bsdf_node, shader_node, mix_node = nodeutils.get_shader_nodes(mat, shader)
|
|
|
|
if shader_node:
|
|
|
|
if shader == "rl_head_shader":
|
|
if prefs.build_limit_textures:
|
|
pack_skin_shader(chr_cache, mat_cache, shader_node, limit_textures = True)
|
|
else:
|
|
pack_skin_shader(chr_cache, mat_cache, shader_node)
|
|
|
|
elif shader == "rl_skin_shader":
|
|
pack_skin_shader(chr_cache, mat_cache, shader_node)
|
|
|
|
elif shader == "rl_hair_shader":
|
|
pack_hair_shader(chr_cache, mat_cache, shader_node)
|
|
|
|
elif shader == "rl_sss_shader":
|
|
pack_sss_shader(chr_cache, mat_cache, shader_node)
|
|
|
|
elif shader == "rl_tearline_shader" or shader == "rl_eye_occlusion_shader":
|
|
# no textures to pack for these.
|
|
pass
|
|
|
|
else:
|
|
# everything else can be packed as default shader
|
|
pack_default_shader(chr_cache, mat_cache, shader_node)
|
|
|
|
|
|
utils.restore_mode_selection_state(mode_selection)
|
|
|
|
|
|
class CC3BakeOperator(bpy.types.Operator):
|
|
"""Bake Operator"""
|
|
bl_idname = "cc3.bake"
|
|
bl_label = "Bake Operator"
|
|
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
|
|
|
param: bpy.props.StringProperty(
|
|
name = "param",
|
|
default = ""
|
|
)
|
|
|
|
def execute(self, context):
|
|
props = vars.props()
|
|
|
|
if self.param == "BAKE_FLOW_NORMAL":
|
|
mat = utils.get_context_material(context)
|
|
chr_cache = props.get_context_character_cache(context)
|
|
mat_cache = chr_cache.get_material_cache(mat)
|
|
bake_flow_to_normal(context, chr_cache, mat_cache)
|
|
|
|
if self.param == "BAKE_BUMP_NORMAL":
|
|
mat = utils.get_context_material(context)
|
|
chr_cache = props.get_context_character_cache(context)
|
|
mat_cache = chr_cache.get_material_cache(mat)
|
|
combine_normal(context, chr_cache, mat_cache)
|
|
|
|
if self.param == "BUILD_DISPLACEMENT":
|
|
mat = utils.get_context_material(context)
|
|
chr_cache = props.get_context_character_cache(context)
|
|
mat_cache = chr_cache.get_material_cache(mat)
|
|
normal.build_displacement_system(chr_cache, mat_cache)
|
|
|
|
return {"FINISHED"}
|
|
|
|
@classmethod
|
|
def description(cls, context, properties):
|
|
|
|
if properties.param == "BAKE_FLOW_NORMAL":
|
|
return "Generates a normal map from the flow map and connects it"
|
|
if properties.param == "BAKE_BUMP_NORMAL":
|
|
return "Combines the Bump and Normal maps into a single normal map"
|
|
return ""
|
|
|
|
|
|
|
|
|
|
|
|
#####################################################
|
|
# BAKE TOOL OPERATORS
|
|
|
|
|
|
def get_export_bake_image_format():
|
|
props = vars.bake_props()
|
|
format = props.target_format
|
|
ext = ".jpg"
|
|
|
|
if format == "PNG":
|
|
ext = ".png"
|
|
elif format == "JPEG":
|
|
ext = ".jpg"
|
|
|
|
return format, ext
|
|
|
|
|
|
def get_bake_path():
|
|
props = vars.bake_props()
|
|
|
|
base_dir = os.path.join(bpy.path.abspath("//"))
|
|
bake_path = props.bake_path
|
|
try:
|
|
if os.path.isabs(bake_path):
|
|
path = bake_path
|
|
else:
|
|
path = os.path.join(base_dir, bake_path)
|
|
except:
|
|
path = os.path.join(base_dir, "Bake")
|
|
|
|
return path
|
|
|
|
|
|
def copy_image_target(image_node, name, width, height, data = True, alpha = False):
|
|
props = vars.bake_props()
|
|
|
|
# return None if it's a bad image source
|
|
if image_node is None or image_node.image is None:
|
|
return None
|
|
if image_node.image.size[0] == 0 or image_node.image.size[1] == 0:
|
|
return None
|
|
|
|
format, ext = get_export_bake_image_format()
|
|
depth = 24
|
|
if alpha:
|
|
format = "PNG"
|
|
ext = ".png"
|
|
depth = 32
|
|
|
|
path = get_bake_path()
|
|
|
|
# find an old image with the same name to reuse:
|
|
for img in bpy.data.images:
|
|
if img.name.startswith(name) and img.name.endswith(name):
|
|
|
|
img_path, img_file = os.path.split(img.filepath)
|
|
same_path = False
|
|
try:
|
|
if os.path.samefile(path, img_path):
|
|
same_path = True
|
|
except:
|
|
same_path = False
|
|
|
|
if same_path:
|
|
utils.log_info("Removing existing copy: " + img.name)
|
|
bpy.data.images.remove(img)
|
|
|
|
utils.log_info("Copying existing image: " + image_node.image.name)
|
|
img = image_node.image.copy()
|
|
img.name = name
|
|
if img.size[0] != width or img.size[1] != height:
|
|
utils.log_info(f"Resizing image: {width} x {height})")
|
|
img.scale(width, height)
|
|
if img.file_format != format:
|
|
if utils.B300():
|
|
utils.log_info("Changing image format: " + format)
|
|
img.file_format = format
|
|
else:
|
|
utils.log_info("Not changing image format of copy in Blender <= 2.93 (causes crash): " + format)
|
|
|
|
dir = os.path.join(bpy.path.abspath("//"), path)
|
|
os.makedirs(dir, exist_ok=True)
|
|
img.filepath_raw = os.path.join(dir, name + ext)
|
|
img.save()
|
|
|
|
return img
|
|
|
|
|
|
def copy_target(source_mat, mat, source_node, map_suffix, data):
|
|
image_name = get_target_bake_image_name(mat, map_suffix)
|
|
width, height = nodeutils.get_tex_image_size(source_node)
|
|
width, height = apply_override_size(source_mat, map_suffix, width, height)
|
|
utils.log_info("Copying direct image source: " + source_node.name)
|
|
image = copy_image_target(source_node, image_name, width, height, data)
|
|
return image
|
|
|
|
|
|
def get_target_bake_image_name(mat, map_suffix):
|
|
target_suffix = get_target_map_suffix(map_suffix)
|
|
mat_name = utils.strip_name(mat.name)
|
|
image_name = f"{mat_name}_{target_suffix}"
|
|
return image_name
|
|
|
|
|
|
def get_bake_image_node_name(mat, global_suffix):
|
|
image_node_name = f"({global_suffix})"
|
|
return image_node_name
|
|
|
|
|
|
def export_bake_shader_normal(context, source_mat, mat):
|
|
"""Bake bsdf normal output for export bake operator.
|
|
Uses bake props target format for image file format."""
|
|
props = vars.bake_props()
|
|
|
|
nodes = mat.node_tree.nodes
|
|
suffix = "Normal"
|
|
target_suffix = get_target_map_suffix(suffix)
|
|
bsdf_node = nodeutils.get_bsdf_node(mat)
|
|
path = get_bake_path()
|
|
|
|
#target_size = get_target_map_size(source_mat, suffix)
|
|
#source_size = detect_bake_size_from_suffix(source_mat, suffix)
|
|
#if props.scale_maps and target_size < source_size:
|
|
# size = source_size
|
|
#else:
|
|
# size = target_size
|
|
|
|
image = bake_bsdf_normal(context, bsdf_node, mat, target_suffix, path,
|
|
no_prep=True, image_format=props.target_format)
|
|
|
|
image_node = None
|
|
if image:
|
|
image_node_name = get_bake_image_node_name(mat, suffix)
|
|
image_node = nodeutils.make_image_node(nodes, image, image_node_name)
|
|
|
|
#if props.scale_maps and target_size < source_size:
|
|
# image.scale(target_size, target_size)
|
|
|
|
return image_node
|
|
|
|
|
|
def export_bake_socket_input(context, source_mat, source_mat_cache, mat,
|
|
to_node, to_socket, suffix,
|
|
data=True):
|
|
"""Bake node socket input for export bake operator.
|
|
Uses bake props target format for image file format."""
|
|
from_node = nodeutils.get_node_connected_to_input(to_node, to_socket)
|
|
from_socket = nodeutils.get_socket_connected_to_input(to_node, to_socket)
|
|
return export_bake_socket_output(context, source_mat, source_mat_cache, mat,
|
|
from_node, from_socket, suffix,
|
|
data=data)
|
|
|
|
|
|
def export_bake_socket_output(context, source_mat, source_mat_cache, mat,
|
|
from_node, from_socket, suffix,
|
|
data=True):
|
|
"""Bake node socket output for export bake operator.
|
|
Uses bake props target format for image file format."""
|
|
props = vars.bake_props()
|
|
|
|
if from_node:
|
|
image = None
|
|
|
|
# TODO use vars.TEX_SIZE_OVERRIDE to limit some bake sizes
|
|
if from_node.type == "TEX_IMAGE" and nodeutils.safe_socket_name(from_socket) == "Color":
|
|
image = copy_target(source_mat, mat, from_node, suffix, data)
|
|
else:
|
|
path = get_bake_path()
|
|
image = bake_node_socket_output(context, from_node, from_socket, mat, suffix, path,
|
|
exact_name=True, underscores=True,
|
|
no_prep=True, image_format=props.target_format)
|
|
|
|
if image:
|
|
image_node_name = get_bake_image_node_name(mat, suffix)
|
|
image_node = nodeutils.make_image_node(mat.node_tree.nodes, image, image_node_name)
|
|
return image_node
|
|
|
|
return None
|
|
|
|
|
|
def set_loc(node, loc):
|
|
if node:
|
|
node.location = loc
|
|
|
|
|
|
def prep_diffuse(mat, shader_name, shader_node, separate, ao_strength):
|
|
props = vars.bake_props()
|
|
nodes = mat.node_tree.nodes
|
|
links = mat.node_tree.links
|
|
# turn of anisotropic highlights in specular blend
|
|
if shader_name == "rl_hair_shader":
|
|
nodeutils.set_node_input_value(shader_node, "Specular Blend", 0)
|
|
nodeutils.set_node_input_value(shader_node, "Anisotropic Strength", 0)
|
|
# turn off depth for cornea parallax
|
|
parallax_tiling_node = nodeutils.find_node_by_keywords(nodes, vars.NODE_PREFIX, "(tiling_rl_cornea_shader_DIFFUSE_mapping)")
|
|
if parallax_tiling_node:
|
|
nodeutils.set_node_input_value(parallax_tiling_node, "Depth", 0.0)
|
|
# for baking separate diffuse and AO, set the amount of AO to bake into the diffuse map
|
|
if separate and shader_node:
|
|
nodeutils.set_node_input_value(shader_node, "AO Strength", props.ao_in_diffuse * ao_strength)
|
|
|
|
|
|
def prep_ao(mat, shader_node):
|
|
props = vars.bake_props()
|
|
ao_strength = 1.0
|
|
if shader_node:
|
|
# fetch the intended ao strength
|
|
ao_strength = nodeutils.get_node_input_value(shader_node, "AO Strength", 1.0)
|
|
# max out the ao strength for baking
|
|
nodeutils.set_node_input_value(shader_node, "AO Strength", 1.0)
|
|
return ao_strength
|
|
|
|
|
|
def disable_ao(mat, shader_node):
|
|
if shader_node:
|
|
nodeutils.set_node_input_value(shader_node, "AO Strength", 0.0)
|
|
|
|
|
|
def prep_sss(shader_node, bsdf_node : bpy.types.Node):
|
|
props = vars.bake_props()
|
|
sss_radius = Vector((0.01, 0.01, 0.01))
|
|
sss_radius = nodeutils.get_node_input_value(bsdf_node, "Subsurface Radius", sss_radius)
|
|
return sss_radius
|
|
|
|
|
|
def prep_alpha(mat, shader_node, shader_name):
|
|
"""Only some shaders should bake alpha,
|
|
those with alpha map inputs and
|
|
those with procedurally generated alpha"""
|
|
|
|
props = vars.bake_props()
|
|
nodes = mat.node_tree.nodes
|
|
links = mat.node_tree.links
|
|
|
|
if shader_name in ["rl_cornea_refractive_shader",
|
|
"rl_eye_occlusion_shader",
|
|
"rl_tearline_shader",
|
|
"rl_tearline_cycles_shader",
|
|
"rl_eye_occlusion_cycles_mix_shader",
|
|
"rl_tearline_cycles_mix_shader"]:
|
|
return True
|
|
|
|
alpha_map_socket = nodeutils.input_socket(shader_node, "Alpha Map")
|
|
if alpha_map_socket:
|
|
return alpha_map_socket.is_linked
|
|
|
|
alpha_socket = nodeutils.output_socket(shader_node, "Alpha")
|
|
if alpha_socket:
|
|
return True
|
|
|
|
opacity_socket = nodeutils.output_socket(shader_node, "Opacity")
|
|
if opacity_socket:
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
def prep_specular(mat, shader_name, shader_node):
|
|
props = vars.bake_props()
|
|
nodes = mat.node_tree.nodes
|
|
links = mat.node_tree.links
|
|
# turn of anisotropic highlights in specular blend
|
|
if shader_name == "rl_hair_shader":
|
|
nodeutils.set_node_input_value(shader_node, "Specular Blend", 0)
|
|
nodeutils.set_node_input_value(shader_node, "Anisotropic Strength", 0)
|
|
|
|
|
|
def can_bake_from_shader_node(shader_node, bsdf_node, bsdf_socket):
|
|
props = vars.bake_props()
|
|
|
|
if shader_node is None:
|
|
return False
|
|
if nodeutils.is_mixer_connected(bsdf_node, bsdf_socket):
|
|
return not props.bake_mixers
|
|
return nodeutils.get_node_connected_to_input(bsdf_node, bsdf_socket) == shader_node
|
|
|
|
|
|
def test_unmodified_socket(shader_node, socket_name, default_value, linked_socket=None):
|
|
if shader_node:
|
|
if socket_name in shader_node.inputs:
|
|
socket = nodeutils.safe_node_input_socket(shader_node, socket_name)
|
|
if linked_socket: # if there is no corresponding linked socket, consider it not modified
|
|
linked_socket = nodeutils.safe_node_input_socket(shader_node, linked_socket)
|
|
if not linked_socket.is_linked:
|
|
return True
|
|
socket_value = socket.default_value
|
|
if type(default_value) is list or type(default_value) is tuple or type(default_value) is bpy.types.bpy_prop_array:
|
|
for i in range(0, len(default_value)):
|
|
if abs(default_value[i] - socket_value[i]) > 0.001:
|
|
return False
|
|
else:
|
|
if abs(default_value - socket_value) > 0.001:
|
|
return False
|
|
return True
|
|
|
|
|
|
def does_shader_modify(shader_node, shader_name, socket_type):
|
|
"""Test if the shader node input socket is modified by the shader node group. e.g. Roughness remap range altered."""
|
|
|
|
# TODO test for and include rebuilding without wrinkle maps before baking.
|
|
# default return as modified, only need to check for shaders that do not modify the inputs
|
|
modifies = True
|
|
|
|
if socket_type == "Diffuse":
|
|
|
|
if shader_name == "rl_sss_shader":
|
|
if (test_unmodified_socket(shader_node, "Diffuse Color", (1,1,1)) and
|
|
test_unmodified_socket(shader_node, "Hue", 0.5) and
|
|
test_unmodified_socket(shader_node, "Saturation", 1.0) and
|
|
test_unmodified_socket(shader_node, "Brightness", 1.0) and
|
|
test_unmodified_socket(shader_node, "Blend Multiply Strength", 0.0, "Blend Multiply")):
|
|
modifies = False
|
|
|
|
elif shader_name in ["rl_skin_shader", "rl_pbr_shader"]:
|
|
if (test_unmodified_socket(shader_node, "Diffuse Color", (1,1,1)) and
|
|
test_unmodified_socket(shader_node, "Diffuse Hue", 0.5) and
|
|
test_unmodified_socket(shader_node, "Diffuse Saturation", 1.0) and
|
|
test_unmodified_socket(shader_node, "Diffuse Brightness", 1.0)):
|
|
modifies = False
|
|
|
|
if socket_type == "AO":
|
|
# nothing fancy is done with the AO maps, they blend multiply into the diffuse with a strength fac,
|
|
# so consider them all unmodified.
|
|
modifies = False
|
|
|
|
if socket_type == "SSS":
|
|
# all the sss effects modify the input through masks, or procedurarally generate it
|
|
# except pbr which doesnt use SSS
|
|
if shader_name == "rl_pbr_shader":
|
|
modifies = False
|
|
|
|
if socket_type == "Metallic":
|
|
# nothing modifies metallic
|
|
modifies = False
|
|
|
|
if socket_type == "Specular":
|
|
if shader_name in ["rl_skin_shader", "rl_pbr_shader", "rl_sss_shader"]:
|
|
if (test_unmodified_socket(shader_node, "Specular Strength", 1.0) and
|
|
test_unmodified_socket(shader_node, "Specular Scale", 1.0)):
|
|
modifies = False
|
|
|
|
if socket_type == "Roughness":
|
|
if shader_name in ["rl_pbr_shader"]:
|
|
if (test_unmodified_socket(shader_node, "Roughness Power", 1) and
|
|
test_unmodified_socket(shader_node, "Roughness Min", 0) and
|
|
test_unmodified_socket(shader_node, "Roughness Max", 1)):
|
|
modifies = False
|
|
if shader_name in ["rl_hair_shader", "rl_hair_cycles_shader"]:
|
|
if (test_unmodified_socket(shader_node, "Roughness Strength", 1, "Roughness Map")):
|
|
modifies = False
|
|
|
|
if socket_type == "Emission":
|
|
if (test_unmodified_socket(shader_node, "Emission Strength", 1.0, "Emission Map") and
|
|
test_unmodified_socket(shader_node, "Emissive Color", (1,1,1), "Emission Map")):
|
|
modifies = False
|
|
|
|
if socket_type == "Transmission":
|
|
# only the rl_cornea_refractive_shader generates a transmission map
|
|
pass
|
|
|
|
if socket_type == "Bump":
|
|
pass
|
|
|
|
if socket_type == "Normal":
|
|
modifies = False
|
|
if shader_name == "rl_head_shader":
|
|
if not test_unmodified_socket(shader_node, "Normal Blend Strength", 0.0, "Normal Blend Map"):
|
|
modifies = True
|
|
|
|
if socket_type == "Alpha":
|
|
if shader_name in ["rl_pbr_shader", "rl_sss_shader"]:
|
|
if (test_unmodified_socket(shader_node, "Alpha Strength", 1.0, "Alpha Map") and
|
|
test_unmodified_socket(shader_node, "Opacity", 1.0, "Alpha Map")):
|
|
modifies = False
|
|
if shader_name in ["rl_hair_shader", "rl_hair_cycles_shader"]:
|
|
if (test_unmodified_socket(shader_node, "Alpha Strength", 1.0, "Alpha Map") and
|
|
test_unmodified_socket(shader_node, "Alpha Power", 1.0, "Alpha Map") and
|
|
test_unmodified_socket(shader_node, "Opacity", 1.0, "Alpha Map")):
|
|
modifies = False
|
|
|
|
#if not modifies:
|
|
# utils.log_info(f"{shader_name} {socket_type} NOT MODIFIED!")
|
|
|
|
return modifies
|
|
|
|
|
|
def bake_export_material(context, mat, source_mat, source_mat_cache):
|
|
props = vars.bake_props()
|
|
|
|
if not (utils.material_exists(mat) and
|
|
utils.material_exists(source_mat) and
|
|
source_mat_cache):
|
|
utils.log_error("Invalid material or material cache!")
|
|
return
|
|
|
|
nodes = mat.node_tree.nodes
|
|
links = mat.node_tree.links
|
|
shader_name = params.get_shader_name(source_mat_cache)
|
|
bsdf_node, shader_node, mix_node = nodeutils.get_shader_nodes(mat, shader_name)
|
|
bake_maps = vars.get_bake_target_maps(props.target_mode)
|
|
bake_path = get_bake_path()
|
|
|
|
if not bsdf_node or not shader_node:
|
|
utils.log_info(f"Material: {mat.name} has no RL shader")
|
|
return
|
|
|
|
utils.log_info(f"Baking export material {props.target_mode} / {mat.name} / {shader_name}")
|
|
utils.log_info("")
|
|
|
|
# Texture Map Baking
|
|
# Note: As mat is a copy of the source material, which is repurposed into the target material
|
|
# it's OK to change the shader inputs without restoring them
|
|
|
|
# Diffuse Maps & AO
|
|
diffuse_bake_node = None
|
|
ao_bake_node = None
|
|
ao_strength = nodeutils.get_node_input_value(shader_node, "AO Strength", 1.0)
|
|
bsdf_socket = nodeutils.input_socket(bsdf_node, "Base Color")
|
|
shader_socket = nodeutils.output_socket(shader_node, "Base Color")
|
|
if nodeutils.has_connected_input(bsdf_node, bsdf_socket):
|
|
if can_bake_from_shader_node(shader_node, bsdf_node, bsdf_socket):
|
|
if "AO" in bake_maps:
|
|
ao_strength = prep_ao(mat, shader_node)
|
|
ao_node, ao_socket = nodeutils.get_node_and_socket_connected_to_input(shader_node, "AO Map")
|
|
if ao_node:
|
|
utils.log_info("AO from Shader Node Input")
|
|
ao_bake_node = export_bake_socket_output(context, source_mat, source_mat_cache,
|
|
mat, ao_node, ao_socket, "AO")
|
|
|
|
# disable any further AO contribution before diffuse baking
|
|
disable_ao(mat, shader_node)
|
|
|
|
# eye shaders use 1/8 the AO strength
|
|
if shader_name in ["rl_cornea_shader", "rl_eye_shader"]:
|
|
ao_strength /= 8
|
|
# use unmodified iris brightness
|
|
nodeutils.set_node_input_value(shader_node, "Iris Brightness", source_mat_cache.parameters.eye_iris_brightness)
|
|
|
|
if "Diffuse" in bake_maps:
|
|
# if there is a "Diffuse" output node, bake that, otherwise bake the "Base Color" output node.
|
|
prep_diffuse(mat, shader_name, shader_node, "AO" in bake_maps, ao_strength)
|
|
is_unmodified = not does_shader_modify(shader_node, shader_name, "Diffuse")
|
|
diffuse_node, diffuse_socket = nodeutils.get_node_and_socket_connected_to_input(shader_node, "Diffuse Map")
|
|
if "Transmission" in shader_node.outputs and props.target_mode == "BLENDER":
|
|
utils.log_info("Diffuse from Shader Node Output (Base Color) (includes transmission)")
|
|
diffuse_bake_node = export_bake_socket_output(context, source_mat, source_mat_cache, mat, shader_node, "Base Color", "Diffuse", False)
|
|
elif diffuse_node and is_unmodified:
|
|
utils.log_info("Diffuse from Shader Node Input")
|
|
diffuse_bake_node = export_bake_socket_output(context, source_mat, source_mat_cache, mat, diffuse_node, diffuse_socket, "Diffuse", False)
|
|
elif "Diffuse" in shader_node.outputs:
|
|
utils.log_info("Diffuse from Shader Node (Diffuse) Output")
|
|
diffuse_bake_node = export_bake_socket_output(context, source_mat, source_mat_cache, mat, shader_node, "Diffuse", "Diffuse", False)
|
|
else:
|
|
utils.log_info("Diffuse from Shader Node (Base Color) Output")
|
|
diffuse_bake_node = export_bake_socket_output(context, source_mat, source_mat_cache, mat, shader_node, "Base Color", "Diffuse", False)
|
|
|
|
elif bsdf_node:
|
|
utils.log_info("Diffuse from BSDF Node")
|
|
# bake BSDF base color input
|
|
diffuse_bake_node = export_bake_socket_input(context, source_mat, source_mat_cache, mat,
|
|
bsdf_node, bsdf_socket, "Diffuse",
|
|
data=False)
|
|
|
|
# Subsurface Scattering Maps
|
|
sss_bake_node = None
|
|
sss_radius = 1.0
|
|
bsdf_socket = nodeutils.input_socket(bsdf_node, "Subsurface")
|
|
shader_socket = nodeutils.output_socket(shader_node, "Subsurface")
|
|
if nodeutils.has_connected_input(bsdf_node, bsdf_socket):
|
|
if shader_socket and "Subsurface" in bake_maps:
|
|
sss_radius = prep_sss(mat, shader_node)
|
|
if can_bake_from_shader_node(shader_node, bsdf_node, bsdf_socket):
|
|
is_unmodified = not does_shader_modify(shader_node, shader_name, "Subsurface")
|
|
sss_node, sss_socket = nodeutils.get_node_and_socket_connected_to_input(shader_node, "Subsurface")
|
|
if sss_node and is_unmodified:
|
|
utils.log_info("Subsurface from Shader Node Input")
|
|
sss_bake_node = export_bake_socket_output(context, source_mat, source_mat_cache, mat, sss_node, sss_socket, "Subsurface")
|
|
else:
|
|
utils.log_info("Subsurface from Shader Node Output")
|
|
sss_bake_node = export_bake_socket_output(context, source_mat, source_mat_cache, mat, shader_node, "Subsurface", "Subsurface")
|
|
elif bsdf_node:
|
|
utils.log_info("Subsurface from BSDF Node Input")
|
|
sss_bake_node = export_bake_socket_input(context, source_mat, source_mat_cache, mat, bsdf_node, bsdf_socket, "Subsurface")
|
|
|
|
# Thickness Maps (Subsurface transmission)
|
|
# the transmission map texture is not used, but it is added to the material nodes
|
|
thickness_bake_node = None
|
|
if "Thickness" in bake_maps:
|
|
utils.log_info("Processing Thickness/Transmission")
|
|
thickness_node = nodeutils.find_shader_texture(nodes, "TRANSMISSION")
|
|
if thickness_node:
|
|
utils.log_info("Thickness from Transmission Texture")
|
|
thickness_bake_node = export_bake_socket_output(context, source_mat, source_mat_cache, mat, thickness_node, "Color", "Thickness")
|
|
|
|
# Metallic Maps
|
|
metallic_bake_node = None
|
|
bsdf_socket = nodeutils.input_socket(bsdf_node, "Metallic")
|
|
if nodeutils.has_connected_input(bsdf_node, bsdf_socket):
|
|
if "Metallic" in bake_maps:
|
|
is_unmodified = not does_shader_modify(shader_node, shader_name, "Metallic")
|
|
metallic_node, metallic_socket = nodeutils.get_node_and_socket_connected_to_input(shader_node, "Metallic Map")
|
|
utils.log_info(f"Processing Metallic {metallic_node} / {is_unmodified}")
|
|
if can_bake_from_shader_node(shader_node, bsdf_node, bsdf_socket):
|
|
if metallic_node and is_unmodified:
|
|
utils.log_info("Metallic from Shader Node Input")
|
|
metallic_bake_node = export_bake_socket_output(context, source_mat, source_mat_cache,
|
|
mat, metallic_node, metallic_socket, "Metallic")
|
|
else:
|
|
utils.log_info("Metallic from Shader Node Output")
|
|
metallic_bake_node = export_bake_socket_output(context, source_mat, source_mat_cache,
|
|
mat, shader_node, "Metallic", "Metallic")
|
|
elif bsdf_node:
|
|
utils.log_info("Metallic from BSDF Node Input")
|
|
metallic_bake_node = export_bake_socket_input(context, source_mat, source_mat_cache, mat, bsdf_node, bsdf_socket, "Metallic")
|
|
|
|
# Specular Maps
|
|
specular_bake_node = None
|
|
bsdf_socket = nodeutils.input_socket(bsdf_node, "Specular")
|
|
if nodeutils.has_connected_input(bsdf_node, bsdf_socket):
|
|
if "Specular" in bake_maps:
|
|
prep_specular(mat, shader_name, shader_node)
|
|
is_unmodified = not does_shader_modify(shader_node, shader_name, "Specular")
|
|
specular_node, specular_socket = nodeutils.get_node_and_socket_connected_to_input(shader_node, "Specular Map")
|
|
utils.log_info("Processing Specular")
|
|
if can_bake_from_shader_node(shader_node, bsdf_node, bsdf_socket):
|
|
if specular_node and is_unmodified:
|
|
utils.log_info("Specular from Shader Node Input")
|
|
specular_bake_node = export_bake_socket_output(context, source_mat, source_mat_cache, mat, specular_node, specular_socket, "Specular")
|
|
else:
|
|
utils.log_info("Specular from Shader Node Output")
|
|
specular_bake_node = export_bake_socket_output(context, source_mat, source_mat_cache, mat, shader_node, "Specular", "Specular")
|
|
elif bsdf_node:
|
|
utils.log_info("Sepcular from BSDF Node Input")
|
|
specular_bake_node = export_bake_socket_input(context, source_mat, source_mat_cache, mat, bsdf_node, bsdf_socket, "Specular")
|
|
specular_scale = 1.0
|
|
if shader_node:
|
|
specular_scale = nodeutils.get_node_input_value(shader_node, "Specular Scale", specular_scale)
|
|
specular_scale = nodeutils.get_node_input_value(shader_node, "Front Specular", specular_scale)
|
|
nodeutils.set_node_input_value(bsdf_node, bsdf_socket, 0.5 * specular_scale)
|
|
|
|
# Roughness Maps
|
|
roughnesss_bake_node = None
|
|
bsdf_socket = nodeutils.input_socket(bsdf_node, "Roughness")
|
|
if nodeutils.has_connected_input(bsdf_node, bsdf_socket):
|
|
if "Roughness" in bake_maps:
|
|
is_unmodified = not does_shader_modify(shader_node, shader_name, "Roughness")
|
|
roughness_node, roughness_socket = nodeutils.get_node_and_socket_connected_to_input(shader_node, "Roughness Map")
|
|
utils.log_info("Processing Roughness")
|
|
if can_bake_from_shader_node(shader_node, bsdf_node, bsdf_socket):
|
|
if roughness_node and is_unmodified:
|
|
utils.log_info("Roughness from Shader Node Input")
|
|
roughnesss_bake_node = export_bake_socket_output(context, source_mat, source_mat_cache, mat, roughness_node, roughness_socket, "Roughness")
|
|
else:
|
|
utils.log_info("Roughness from Shader Node Output")
|
|
roughnesss_bake_node = export_bake_socket_output(context, source_mat, source_mat_cache, mat, shader_node, "Roughness", "Roughness")
|
|
elif bsdf_node:
|
|
utils.log_info("Roughness from BSDF Node Input")
|
|
roughnesss_bake_node = export_bake_socket_input(context, source_mat, source_mat_cache, mat, bsdf_node, bsdf_socket, "Roughness")
|
|
|
|
# Emission Maps
|
|
# copy emission maps directly...
|
|
emission_bake_node = None
|
|
emission_strength = nodeutils.get_node_input_value(shader_node, "Emission Strength", 0.0)
|
|
bsdf_socket = nodeutils.input_socket(bsdf_node, "Emission")
|
|
if nodeutils.has_connected_input(bsdf_node, bsdf_socket):
|
|
if "Emission" in bake_maps:
|
|
is_unmodified = not does_shader_modify(shader_node, shader_name, "Emission")
|
|
emission_node, emission_socket = nodeutils.get_node_and_socket_connected_to_input(shader_node, "Emission Map")
|
|
utils.log_info("Processing Emission")
|
|
if emission_node:
|
|
if can_bake_from_shader_node(shader_node, bsdf_node, bsdf_socket):
|
|
if emission_node and is_unmodified:
|
|
utils.log_info("Emission from Shader Node Input")
|
|
emission_bake_node = export_bake_socket_output(context, source_mat, source_mat_cache, mat, emission_node, emission_socket, "Emission")
|
|
else:
|
|
utils.log_info("Emission from Shader Node Output")
|
|
emission_bake_node = export_bake_socket_output(context, source_mat, source_mat_cache, mat, shader_node, "Emission", "Emission")
|
|
elif bsdf_node:
|
|
utils.log_info("Emission from BSDF Node Input")
|
|
emission_bake_node = export_bake_socket_input(context, source_mat, source_mat_cache, mat, bsdf_node, bsdf_socket, "Emission")
|
|
|
|
# Alpha Maps
|
|
alpha_bake_node = None
|
|
bsdf_socket = nodeutils.input_socket(bsdf_node, "Alpha")
|
|
# Opacity output is for baking only
|
|
if nodeutils.has_connected_input(bsdf_node, bsdf_socket) or (shader_node and "Opacity" in shader_node.outputs):
|
|
if "Alpha" in bake_maps:
|
|
if shader_node and ("Opacity" in shader_node.outputs or "Alpha" in shader_node.outputs):
|
|
if prep_alpha(mat, shader_node, shader_name):
|
|
if (can_bake_from_shader_node(shader_node, bsdf_node, bsdf_socket)
|
|
or (shader_node and "Opacity" in shader_node.outputs)):
|
|
is_unmodified = not does_shader_modify(shader_node, shader_name, "Alpha")
|
|
alpha_node, alpha_socket = nodeutils.get_node_and_socket_connected_to_input(shader_node, "Alpha Map")
|
|
for alpha_socket_name in ["Opacity", "Alpha"]:
|
|
if alpha_socket_name in shader_node.outputs:
|
|
if alpha_node and is_unmodified:
|
|
utils.log_info("Alpha from Shader Node Input")
|
|
alpha_bake_node = export_bake_socket_output(context, source_mat, source_mat_cache, mat, alpha_node, alpha_socket, "Alpha")
|
|
else:
|
|
utils.log_info("Alpha from Shader Node Output")
|
|
alpha_bake_node = export_bake_socket_output(context, source_mat, source_mat_cache, mat, shader_node, alpha_socket_name, "Alpha")
|
|
if utils.B430():
|
|
materials.set_material_alpha(mat, "DITHERED", shadows=False)
|
|
else:
|
|
materials.set_material_alpha(mat, "BLEND", shadows=False)
|
|
break
|
|
elif bsdf_node:
|
|
utils.log_info("Alpha from BSDF Node Input")
|
|
alpha_bake_node = export_bake_socket_input(context, source_mat, source_mat_cache, mat, bsdf_node, bsdf_socket, "Alpha")
|
|
|
|
# Transmission Maps (Refractive Transparency)
|
|
transmission_bake_node = None
|
|
bsdf_socket = nodeutils.input_socket(bsdf_node, "Transmission")
|
|
thickness = 0.0
|
|
if shader_name in ["rl_cornea_shader", "rl_cornea_refractive_shader"]:
|
|
iris_depth = nodeutils.get_node_input_value(shader_node, "Iris Depth", 0.0)
|
|
thickness = 0.4 * iris_depth + 0.2
|
|
if nodeutils.has_connected_input(bsdf_node, bsdf_socket):
|
|
if "Transmission" in bake_maps:
|
|
if can_bake_from_shader_node(shader_node, bsdf_node, bsdf_socket):
|
|
utils.log_info("Transmission from Shader Node Output")
|
|
transmission_bake_node = export_bake_socket_output(context, source_mat, source_mat_cache, mat, shader_node, "Transmission", "Transmission")
|
|
elif bsdf_node:
|
|
utils.log_info("Transmission from BSDF Node Input")
|
|
transmission_bake_node = export_bake_socket_input(context, source_mat, source_mat_cache, mat, bsdf_node, bsdf_socket, "Transmission")
|
|
|
|
# Bump Maps
|
|
# if shader group node has a "Bump Map" input, then copy the bump map texture directly
|
|
bump_bake_node = None
|
|
bump_distance = 0.01
|
|
bsdf_socket = nodeutils.input_socket(bsdf_node, "Normal")
|
|
if nodeutils.has_connected_input(bsdf_node, bsdf_socket):
|
|
if "Bump" in bake_maps and props.allow_bump_maps:
|
|
if can_bake_from_shader_node(shader_node, bsdf_node, bsdf_socket):
|
|
bump_node, bump_socket = nodeutils.get_node_and_socket_connected_to_input(shader_node, ["Bump Map"])
|
|
bump_distance = nodeutils.get_node_input_value(shader_node, "Bump Strength", 0.01)
|
|
# note: there is not shader node bump output, so only bake the input
|
|
if bump_node:
|
|
utils.log_info("Bump from Shader Node Input")
|
|
bump_bake_node = export_bake_socket_output(context, source_mat, source_mat_cache, mat, bump_node, bump_socket, "Bump")
|
|
elif bsdf_node:
|
|
input_node = nodeutils.get_node_connected_to_input(bsdf_node, bsdf_socket)
|
|
if input_node.type == "BUMP":
|
|
height_node = nodeutils.get_node_connected_to_input(input_node, "Height")
|
|
if height_node:
|
|
utils.log_info("Bump from BSDF:Bump Node Input")
|
|
bump_bake_node = export_bake_socket_input(context, source_mat, source_mat_cache, mat, input_node, "Height", "Bump")
|
|
bump_distance = nodeutils.get_node_input_value(input_node, "Distance", 1.0)
|
|
|
|
# Normal Maps
|
|
# if the shader group node has a "Blend Normal" color output, bake that,
|
|
# otherwise copy the normal map texture directly
|
|
# DO NOT BAKE the normal vector output.
|
|
normal_bake_node = None
|
|
normal_strength = 1.0
|
|
bump_to_normal = False
|
|
bsdf_socket = nodeutils.input_socket(bsdf_node, "Normal")
|
|
if nodeutils.has_connected_input(bsdf_node, bsdf_socket):
|
|
if "Normal" in bake_maps:
|
|
if "Bump" not in bake_maps or not props.allow_bump_maps:
|
|
bump_to_normal = True
|
|
if can_bake_from_shader_node(shader_node, bsdf_node, bsdf_socket):
|
|
normal_strength = nodeutils.get_node_input_value(shader_node, "Normal Strength", 1.0)
|
|
if "Blend Normal" in shader_node.outputs:
|
|
utils.log_info("Normal from Shader Node Output")
|
|
normal_strength = 1.0
|
|
normal_bake_node = export_bake_socket_output(context, source_mat, source_mat_cache, mat, shader_node, "Blend Normal", "Normal")
|
|
else:
|
|
normal_node, normal_socket = nodeutils.get_node_and_socket_connected_to_input(shader_node, "Normal Map")
|
|
if normal_node:
|
|
utils.log_info("Normal from Shader Node Input")
|
|
normal_bake_node = export_bake_socket_output(context, source_mat, source_mat_cache, mat, normal_node, normal_socket, "Normal")
|
|
else:
|
|
utils.log_info("Normal from BSDF Node Output")
|
|
normal_bake_node = export_bake_shader_normal(context, source_mat, mat)
|
|
elif bsdf_node:
|
|
input_node = nodeutils.get_node_connected_to_input(bsdf_node, bsdf_socket)
|
|
if input_node:
|
|
if input_node.type == "NORMAL_MAP":
|
|
utils.log_info("Normal from BSDF:Normal Node Input")
|
|
# just a normal mapper, bake the entire normal input
|
|
normal_bake_node = export_bake_shader_normal(context, source_mat, mat)
|
|
normal_strength = nodeutils.get_node_input_value(input_node, "Strength", 1.0)
|
|
elif input_node.type == "BUMP":
|
|
utils.log_info("Normal from BSDF:Bump Node Input")
|
|
# bump node mappers can have heightmap and normal inputs
|
|
normal_node = nodeutils.get_node_connected_to_input(input_node, "Normal")
|
|
height_node = nodeutils.get_node_connected_to_input(input_node, "Height")
|
|
if bump_to_normal:
|
|
# bake everything into the normal
|
|
normal_bake_node = export_bake_shader_normal(context, source_mat, mat)
|
|
normal_strength = 1.0
|
|
else:
|
|
# bake the normal separately
|
|
if normal_node:
|
|
normal_bake_node = export_bake_socket_input(context, source_mat, source_mat_cache, mat, input_node, "Normal", "Normal")
|
|
normal_strength = nodeutils.get_node_input_value(normal_node, "Strength", 1.0)
|
|
else:
|
|
utils.log_info("Normal from BSDF Node Output")
|
|
# something is plugged into the normals, but can't tell what, so just bake the shader normals
|
|
normal_bake_node = export_bake_shader_normal(context, source_mat, mat)
|
|
|
|
# Micro Normals
|
|
# always copy the micro normal map texture directly
|
|
micro_normal_bake_node = None
|
|
micro_normal_strength = 1
|
|
micro_normal_tiling = 20
|
|
micro_normal_scale = Vector((1, 1, 1))
|
|
if nodeutils.has_connected_input(bsdf_node, bsdf_socket):
|
|
if "MicroNormal" in bake_maps:
|
|
micro_normal_node = nodeutils.find_shader_texture(nodes, "MICRONORMAL")
|
|
if micro_normal_node:
|
|
tiling_node = nodeutils.get_node_connected_to_input(micro_normal_node, "Vector")
|
|
if tiling_node:
|
|
if "Tiling" in tiling_node.inputs:
|
|
micro_normal_scale = nodeutils.get_node_input_value(tiling_node, "Tiling", micro_normal_scale)
|
|
micro_normal_tiling = micro_normal_scale[0]
|
|
micro_normal_scale = Vector((micro_normal_tiling, micro_normal_tiling, 1))
|
|
elif "Scale" in tiling_node.inputs:
|
|
micro_normal_scale = nodeutils.get_node_input_value(tiling_node, "Scale", micro_normal_scale)
|
|
micro_normal_tiling = micro_normal_scale[0]
|
|
micro_normal_scale = Vector((micro_normal_tiling, micro_normal_tiling, 1))
|
|
utils.log_info(f"Tiling: {micro_normal_scale}")
|
|
# disconnect any tiling/mapping nodes before baking the micro normal...
|
|
nodeutils.unlink_node_input(links, micro_normal_node, "Vector")
|
|
utils.log_info("Micro-Normal from Texture")
|
|
micro_normal_bake_node = export_bake_socket_output(context, source_mat, source_mat_cache, mat, micro_normal_node, "Color", "MicroNormal")
|
|
|
|
# Micro Normal Mask
|
|
# if the shader group node has a "Normal Mask" float output, bake that,
|
|
# otherwise copy the micro normal mask directly
|
|
micro_normal_mask_bake_node = None
|
|
micro_normal_stength = 1.0
|
|
if nodeutils.has_connected_input(bsdf_node, bsdf_socket):
|
|
if "MicroNormalMask" in bake_maps:
|
|
if shader_node:
|
|
if "Normal Mask" in shader_node.outputs:
|
|
utils.log_info("Micro-Normal Mask from Shader Node Output")
|
|
micro_normal_mask_bake_node = export_bake_socket_output(context, source_mat, source_mat_cache, mat, shader_node, "Normal Mask", "MicroNormalMask")
|
|
micro_normal_strength = 1.0
|
|
else:
|
|
micro_normal_mask_node = nodeutils.find_shader_texture(nodes, "MICRONMASK")
|
|
micro_normal_strength = nodeutils.get_node_input_value(shader_node, "Micro Normal Strength", 1.0)
|
|
if micro_normal_mask_node:
|
|
utils.log_info("Micro-Normal Mask from Texture")
|
|
micro_normal_mask_bake_node = export_bake_socket_output(context, source_mat, source_mat_cache, mat, micro_normal_mask_node, "Color", "MicroNormalMask")
|
|
|
|
# Post processing
|
|
#
|
|
utils.log_info("Post Processing Textures...")
|
|
utils.log_info("")
|
|
|
|
if props.target_mode == "BLENDER":
|
|
pass
|
|
|
|
elif props.target_mode == "GODOT":
|
|
pass
|
|
|
|
elif props.target_mode == "SKETCHFAB":
|
|
pass
|
|
|
|
elif props.target_mode == "GLTF":
|
|
if props.pack_gltf:
|
|
# BaseMap: RGB: diffuse, A: alpha
|
|
combine_diffuse_tex(nodes, source_mat, source_mat_cache, mat,
|
|
diffuse_bake_node, alpha_bake_node)
|
|
# GLTF pack: R: Ao, G: Roughness, B: Metallic
|
|
combine_gltf(nodes, source_mat, source_mat_cache, mat,
|
|
ao_bake_node, roughnesss_bake_node, metallic_bake_node)
|
|
|
|
elif props.target_mode == "UNITY_URP":
|
|
# BaseMap: RGB: diffuse, A: alpha
|
|
combine_diffuse_tex(nodes, source_mat, source_mat_cache, mat,
|
|
diffuse_bake_node, alpha_bake_node)
|
|
|
|
# MetallicAlpha: RGB: Metallic, A: Smoothness = f(Rougness)
|
|
make_metallic_smoothness_tex(nodes, source_mat, source_mat_cache, mat,
|
|
metallic_bake_node, roughnesss_bake_node)
|
|
|
|
elif props.target_mode == "UNITY_HDRP":
|
|
# BaseMap: RGB: diffuse, A: alpha
|
|
combine_diffuse_tex(nodes, source_mat, source_mat_cache, mat,
|
|
diffuse_bake_node, alpha_bake_node)
|
|
|
|
# Mask: R: Metallic, G: AO, B: Micro-Normal Mask, A: Smoothness = f(Roughness)
|
|
combine_hdrp_mask_tex(nodes, source_mat, source_mat_cache, mat,
|
|
metallic_bake_node, ao_bake_node, micro_normal_mask_bake_node, roughnesss_bake_node)
|
|
|
|
# Detail: R: 0.5, G: Micro-Normal.R, B: 0.5, A: Micro-Normal.G
|
|
combine_hdrp_detail_tex(nodes, source_mat, source_mat_cache, mat,
|
|
micro_normal_bake_node)
|
|
|
|
# invert the thickness map
|
|
process_hdrp_subsurfaces_tex(sss_bake_node, thickness_bake_node)
|
|
|
|
# reconnect the materials
|
|
|
|
utils.log_info("Reconnecting baked material:")
|
|
utils.log_info("")
|
|
|
|
reconnect_material(mat, source_mat_cache,
|
|
ao_strength, sss_radius, bump_distance,
|
|
normal_strength, micro_normal_strength, micro_normal_scale,
|
|
emission_strength, thickness)
|
|
|
|
|
|
def fetch_pack_image_data(width, height, *nodes, no_rescale = False):
|
|
|
|
utils.log_info(f"Using packed map size: {width} x {height}")
|
|
|
|
data = []
|
|
|
|
for node in nodes:
|
|
pixels = None
|
|
if node and node.image:
|
|
image : bpy.types.Image = node.image
|
|
utils.log_info(f"Using tex image: {image.name} - {image.size[0]} x {image.size[1]} - {image.colorspace_settings.name}")
|
|
if image.size[0] != width or image.size[1] != height:
|
|
utils.log_info(f" - Scaling tex image: {width} x {height}")
|
|
scaled_image = image.copy()
|
|
scaled_image.scale(width, height)
|
|
pixels = list(scaled_image.pixels)
|
|
else:
|
|
pixels = list(image.pixels)
|
|
data.append(pixels)
|
|
|
|
return data
|
|
|
|
|
|
def combine_diffuse_tex(nodes, source_mat, source_mat_cache, mat,
|
|
diffuse_node, alpha_node, image_format="PNG"):
|
|
|
|
diffuse_data = None
|
|
alpha_data = None
|
|
bsdf_node = nodeutils.get_bsdf_node(mat)
|
|
base_color_socket = nodeutils.input_socket(bsdf_node, "Base Color")
|
|
diffuse_value = nodeutils.get_node_input_color(bsdf_node, base_color_socket, (1,1,1,1))
|
|
|
|
map_suffix = "BaseMap"
|
|
path = get_bake_path()
|
|
width, height = nodeutils.get_largest_image_size(diffuse_node, alpha_node)
|
|
width, height = apply_override_size(mat, map_suffix, width, height)
|
|
diffuse_data, alpha_data = fetch_pack_image_data(width, height, diffuse_node, alpha_node)
|
|
|
|
if diffuse_data is None and alpha_data is None:
|
|
return
|
|
|
|
utils.log_info("Combining diffuse with alpha...")
|
|
|
|
image_name = get_target_bake_image_name(mat, map_suffix)
|
|
image_node_name = get_bake_image_node_name(mat, map_suffix)
|
|
image, exists = get_image_target(image_name, width, height, path, is_data=False, has_alpha=True,
|
|
channel_packed=True, format=image_format)
|
|
image_node = nodeutils.make_image_node(nodes, image, image_node_name)
|
|
image_node.select = True
|
|
nodes.active = image_node
|
|
# writeable list copy for fastest write speed
|
|
image_data = list(image.pixels)
|
|
l = len(image_data)
|
|
|
|
for i in range(0, l, 4):
|
|
if diffuse_data:
|
|
image_data[i+0] = diffuse_data[i+0]
|
|
image_data[i+1] = diffuse_data[i+1]
|
|
image_data[i+2] = diffuse_data[i+2]
|
|
else:
|
|
image_data[i+0] = diffuse_value[0]
|
|
image_data[i+1] = diffuse_value[1]
|
|
image_data[i+2] = diffuse_value[2]
|
|
|
|
if alpha_data:
|
|
image_data[i+3] = alpha_data[i]
|
|
else:
|
|
image_data[i+3] = 1
|
|
|
|
# replace in-place in one go.
|
|
image.pixels[:] = image_data
|
|
image.update()
|
|
image.save()
|
|
|
|
|
|
def combine_hdrp_mask_tex(nodes, source_mat, source_mat_cache, mat,
|
|
metallic_node, ao_node, mask_node, roughness_node,
|
|
image_format="PNG"):
|
|
props = vars.bake_props()
|
|
|
|
metallic_data = None
|
|
ao_data = None
|
|
mask_data = None
|
|
roughness_data = None
|
|
|
|
map_suffix = "Mask"
|
|
path = get_bake_path()
|
|
width, height = nodeutils.get_largest_image_size(metallic_node, ao_node, mask_node, roughness_node)
|
|
width, height = apply_override_size(mat, map_suffix, width, height)
|
|
metallic_data, ao_data, mask_data, roughness_data = fetch_pack_image_data(width, height, metallic_node, ao_node, mask_node, roughness_node)
|
|
|
|
if metallic_data is None and ao_data is None and mask_data is None and roughness_data is None:
|
|
return
|
|
|
|
utils.log_info("Combining Unity HDRP Mask Texture...")
|
|
|
|
bsdf_node = nodeutils.get_bsdf_node(mat)
|
|
metallic_socket = nodeutils.input_socket(bsdf_node, "Metallic")
|
|
roughness_socket = nodeutils.input_socket(bsdf_node, "Roughness")
|
|
metallic_value = nodeutils.get_node_input_value(bsdf_node, metallic_socket, 0.0)
|
|
roughness_value = nodeutils.get_node_input_value(bsdf_node, roughness_socket, 0.0)
|
|
ao_value = 1
|
|
mask_value = 1
|
|
|
|
image_name = get_target_bake_image_name(mat, map_suffix)
|
|
image_node_name = get_bake_image_node_name(mat, map_suffix)
|
|
image, exists = get_image_target(image_name, width, height, path,
|
|
is_data=True, has_alpa=True, channel_packed=True,
|
|
format=image_format)
|
|
image_node = nodeutils.make_image_node(nodes, image, image_node_name)
|
|
image_node.select = True
|
|
nodes.active = image_node
|
|
image_data = list(image.pixels)
|
|
l = len(image_data)
|
|
|
|
# Mask: R: Metallic, G: AO, B: Micro-Normal Mask, A: Smoothness = 0.5 + 0.5*(1-Roughess)^2
|
|
for i in range(0, l, 4):
|
|
|
|
# Red
|
|
if metallic_data:
|
|
image_data[i] = metallic_data[i]
|
|
else:
|
|
image_data[i] = metallic_value
|
|
|
|
# Green
|
|
if ao_data:
|
|
image_data[i+1] = ao_data[i]
|
|
else:
|
|
image_data[i+1] = ao_value
|
|
|
|
# Blue
|
|
if mask_data:
|
|
image_data[i+2] = mask_data[i]
|
|
else:
|
|
image_data[i+2] = mask_value
|
|
|
|
# Alpha
|
|
if roughness_data:
|
|
roughness = roughness_data[i]
|
|
else:
|
|
roughness = roughness_value
|
|
|
|
if props.smoothness_mapping == "SIR":
|
|
smoothness = pow(1 - roughness, 2)
|
|
elif props.smoothness_mapping == "IRS":
|
|
smoothness = 1 - pow(roughness, 2)
|
|
elif props.smoothness_mapping == "IRSR":
|
|
smoothness = 1 - pow(roughness, 0.5)
|
|
elif props.smoothness_mapping == "SRIR":
|
|
smoothness = pow(1 - roughness, 0.5)
|
|
elif props.smoothness_mapping == "SRIRS":
|
|
smoothness = pow(1 - pow(roughness, 2), 0.5)
|
|
else: # IR
|
|
smoothness = 1 - roughness
|
|
|
|
image_data[i+3] = smoothness
|
|
|
|
image.pixels[:] = image_data
|
|
image.update()
|
|
image.save()
|
|
|
|
|
|
def combine_hdrp_detail_tex(nodes, source_mat, source_mat_cache, mat,
|
|
detail_normal_node, image_format="PNG"):
|
|
|
|
detail_data = None
|
|
|
|
map_suffix = "Detail"
|
|
path = get_bake_path()
|
|
width, height = nodeutils.get_largest_image_size(detail_normal_node)
|
|
width, height = apply_override_size(mat, map_suffix, width, height)
|
|
detail_data = fetch_pack_image_data(width, height, detail_normal_node)
|
|
|
|
if not detail_data:
|
|
return
|
|
|
|
utils.log_info("Combining Unity HDRP Detail Texture...")
|
|
|
|
image_name = get_target_bake_image_name(mat, map_suffix)
|
|
image_node_name = get_bake_image_node_name(mat, map_suffix)
|
|
image, exists = get_image_target(image_name, width, height, path,
|
|
is_data=True, has_alpha=True, channel_packed=True,
|
|
format=image_format)
|
|
image_node = nodeutils.make_image_node(nodes, image, image_node_name)
|
|
image_node.select = True
|
|
nodes.active = image_node
|
|
image_data = list(image.pixels)
|
|
l = len(image_data)
|
|
|
|
# Detail: R: 0.5, G: Micro-Normal.R, B: 0.5, A: Micro-Normal.G
|
|
for i in range(0, l, 4):
|
|
|
|
image_data[i+0] = 0.5
|
|
image_data[i+2] = 0.5
|
|
|
|
if detail_data:
|
|
image_data[i+1] = detail_data[i+0]
|
|
image_data[i+3] = detail_data[i+1]
|
|
else:
|
|
image_data[i+1] = 0.5
|
|
image_data[i+3] = 0.5
|
|
|
|
image.pixels[:] = image_data
|
|
image.update()
|
|
image.save()
|
|
|
|
|
|
def process_hdrp_subsurfaces_tex(sss_node, trans_node):
|
|
|
|
if trans_node and trans_node.image:
|
|
image = trans_node.image
|
|
trans_data = list(image.pixels)
|
|
l = len(trans_data)
|
|
for i in range(0, l, 4):
|
|
trans_data[i+0] = 1.0 - trans_data[i+0]
|
|
trans_data[i+1] = 1.0 - trans_data[i+1]
|
|
trans_data[i+2] = 1.0 - trans_data[i+2]
|
|
|
|
image.pixels[:] = trans_data
|
|
image.update()
|
|
image.save()
|
|
|
|
|
|
def make_metallic_smoothness_tex(nodes, source_mat, source_mat_cache, mat,
|
|
metallic_node, roughness_node,
|
|
image_format="PNG"):
|
|
props = vars.bake_props()
|
|
|
|
metallic_data = None
|
|
roughness_data = None
|
|
|
|
map_suffix = "MetallicAlpha"
|
|
path = get_bake_path()
|
|
width, height = nodeutils.get_largest_image_size(metallic_node, roughness_node)
|
|
width, height = apply_override_size(mat, map_suffix, width, height)
|
|
metallic_data, roughness_data = fetch_pack_image_data(width, height, metallic_node, roughness_node)
|
|
|
|
if metallic_data is None and roughness_data is None:
|
|
return
|
|
|
|
utils.log_info("Create Unity URP/3D Metallic Alpha Texture from Metallic and Roughness...")
|
|
|
|
bsdf_node = nodeutils.get_bsdf_node(mat)
|
|
metallic_socket = nodeutils.input_socket(bsdf_node, "Metallic")
|
|
roughness_socket = nodeutils.input_socket(bsdf_node, "Roughness")
|
|
metallic_value = nodeutils.get_node_input_value(bsdf_node, metallic_socket, 0)
|
|
roughness_value = nodeutils.get_node_input_value(bsdf_node, roughness_socket, 0.5)
|
|
|
|
image_name = get_target_bake_image_name(mat, map_suffix)
|
|
image_node_name = get_bake_image_node_name(mat, map_suffix)
|
|
image, exists = get_image_target(image_name, width, height, path,
|
|
is_data=True, has_alpha=True, channel_packed=True,
|
|
format=image_format)
|
|
image_node = nodeutils.make_image_node(nodes, image, image_node_name)
|
|
image_node.select = True
|
|
nodes.active = image_node
|
|
image_data = list(image.pixels)
|
|
l = len(image_data)
|
|
|
|
# Mask: R: Metallic, G: AO, B: Micro-Normal Mask, A: Smoothness = 0.5 + 0.5*(1-Roughess)^2
|
|
for i in range(0, l, 4):
|
|
|
|
if roughness_data:
|
|
roughness = roughness_data[i]
|
|
else:
|
|
roughness = roughness_value
|
|
|
|
if metallic_data:
|
|
metallic = metallic_data[i]
|
|
else:
|
|
metallic = metallic_value
|
|
|
|
if props.smoothness_mapping == "SIR":
|
|
smoothness = pow(1 - roughness, 2)
|
|
elif props.smoothness_mapping == "IRS":
|
|
smoothness = 1 - pow(roughness, 2)
|
|
elif props.smoothness_mapping == "IRSR":
|
|
smoothness = 1 - pow(roughness, 0.5)
|
|
elif props.smoothness_mapping == "SRIR":
|
|
smoothness = pow(1 - roughness, 0.5)
|
|
elif props.smoothness_mapping == "SRIRS":
|
|
smoothness = pow(1 - pow(roughness, 2), 0.5)
|
|
else: # IR
|
|
smoothness = 1 - roughness
|
|
|
|
image_data[i+0] = metallic
|
|
image_data[i+1] = metallic
|
|
image_data[i+2] = metallic
|
|
image_data[i+3] = smoothness
|
|
|
|
image.pixels[:] = image_data
|
|
image.update()
|
|
image.save()
|
|
|
|
|
|
def combine_gltf(nodes, source_mat, source_mat_cache, mat,
|
|
ao_node, roughness_node, metallic_node,
|
|
image_format="PNG"):
|
|
|
|
props = vars.bake_props()
|
|
|
|
metallic_data = None
|
|
ao_data = None
|
|
roughness_data = None
|
|
|
|
map_suffix = "GLTF"
|
|
path = get_bake_path()
|
|
width, height = nodeutils.get_largest_image_size(ao_node, roughness_node, metallic_node)
|
|
width, height = apply_override_size(mat, map_suffix, width, height)
|
|
ao_data, roughness_data, metallic_data = fetch_pack_image_data(width, height, ao_node, roughness_node, metallic_node)
|
|
|
|
if ao_data is None and roughness_data is None and metallic_data is None:
|
|
return
|
|
|
|
utils.log_info("Combining GLTF texture pack...")
|
|
|
|
bsdf_node = nodeutils.get_bsdf_node(mat)
|
|
metallic_socket = nodeutils.input_socket(bsdf_node, "Metallic")
|
|
roughness_socket = nodeutils.input_socket(bsdf_node, "Roughness")
|
|
metallic_value = nodeutils.get_node_input_value(bsdf_node, metallic_socket, 0.0)
|
|
roughness_value = nodeutils.get_node_input_value(bsdf_node, roughness_socket, 0.0)
|
|
ao_value = 1
|
|
|
|
image_name = get_target_bake_image_name(mat, map_suffix)
|
|
image_node_name = get_bake_image_node_name(mat, map_suffix)
|
|
image, exists = get_image_target(image_name, width, height, path,
|
|
is_data=True, has_alpha=False, channel_packed=False,
|
|
format=image_format)
|
|
image_node = nodeutils.make_image_node(nodes, image, image_node_name)
|
|
image_node.select = True
|
|
nodes.active = image_node
|
|
image_data = list(image.pixels)
|
|
l = len(image_data)
|
|
|
|
# GLTF: R: AO, G: Roughness, B: Metallic
|
|
for i in range(0, l, 4):
|
|
|
|
# Red
|
|
if ao_data:
|
|
image_data[i+0] = ao_data[i]
|
|
else:
|
|
image_data[i+0] = ao_value
|
|
|
|
# Green
|
|
if roughness_data:
|
|
image_data[i+1] = roughness_data[i]
|
|
else:
|
|
image_data[i+1] = roughness_value
|
|
|
|
# Blue
|
|
if metallic_data:
|
|
image_data[i+2] = metallic_data[i]
|
|
else:
|
|
image_data[i+2] = metallic_value
|
|
|
|
image.pixels[:] = image_data
|
|
image.update()
|
|
image.save()
|
|
|
|
|
|
def find_baked_image_nodes(nodes, tex_nodes, global_suffix):
|
|
node = nodeutils.find_shader_texture(nodes, global_suffix)
|
|
tex_nodes[global_suffix] = node
|
|
|
|
|
|
def reconnect_material(mat, mat_cache, ao_strength, sss_radius, bump_distance, normal_strength,
|
|
micro_normal_strength, micro_normal_scale, emission_strength, thickness):
|
|
props = vars.bake_props()
|
|
|
|
nodes = mat.node_tree.nodes
|
|
links = mat.node_tree.links
|
|
shader_name = params.get_shader_name(mat_cache)
|
|
bsdf_node, shader_node, mix_node = nodeutils.get_shader_nodes(mat, shader_name)
|
|
output_node = nodeutils.find_node_by_type(nodes, "OUTPUT_MATERIAL")
|
|
|
|
nodeutils.link_nodes(links, bsdf_node, "BSDF", output_node, "Surface")
|
|
|
|
tex_nodes = {}
|
|
find_baked_image_nodes(nodes, tex_nodes, "Diffuse")
|
|
find_baked_image_nodes(nodes, tex_nodes, "BaseMap")
|
|
find_baked_image_nodes(nodes, tex_nodes, "AO")
|
|
find_baked_image_nodes(nodes, tex_nodes, "Subsurface")
|
|
find_baked_image_nodes(nodes, tex_nodes, "Thickness")
|
|
find_baked_image_nodes(nodes, tex_nodes, "Metallic")
|
|
find_baked_image_nodes(nodes, tex_nodes, "Specular")
|
|
find_baked_image_nodes(nodes, tex_nodes, "Roughness")
|
|
find_baked_image_nodes(nodes, tex_nodes, "Emission")
|
|
find_baked_image_nodes(nodes, tex_nodes, "Alpha")
|
|
find_baked_image_nodes(nodes, tex_nodes, "Transmission")
|
|
find_baked_image_nodes(nodes, tex_nodes, "Normal")
|
|
find_baked_image_nodes(nodes, tex_nodes, "Bump")
|
|
find_baked_image_nodes(nodes, tex_nodes, "MicroNormal")
|
|
find_baked_image_nodes(nodes, tex_nodes, "MicroNormalMask")
|
|
find_baked_image_nodes(nodes, tex_nodes, "GLTF")
|
|
find_baked_image_nodes(nodes, tex_nodes, "Mask")
|
|
find_baked_image_nodes(nodes, tex_nodes, "Detail")
|
|
|
|
mix_node = None
|
|
bump_map_node = None
|
|
normal_map_node = None
|
|
gltf_settings_node = None
|
|
split_node = None
|
|
micro_mix_node = None
|
|
micro_mapping_node = None
|
|
micro_texcoord_node = None
|
|
micro_mask_mult_node = None
|
|
|
|
base_color_socket = nodeutils.input_socket(bsdf_node, "Base Color")
|
|
sss_socket = nodeutils.input_socket(bsdf_node, "Subsurface")
|
|
metallic_socket = nodeutils.input_socket(bsdf_node, "Metallic")
|
|
specular_socket = nodeutils.input_socket(bsdf_node, "Specular")
|
|
roughness_socket = nodeutils.input_socket(bsdf_node, "Roughness")
|
|
emission_socket = nodeutils.input_socket(bsdf_node, "Emission")
|
|
transmission_socket = nodeutils.input_socket(bsdf_node, "Transmission")
|
|
alpha_socket = nodeutils.input_socket(bsdf_node, "Alpha")
|
|
normal_socket = nodeutils.input_socket(bsdf_node, "Normal")
|
|
|
|
if shader_node:
|
|
nodes.remove(shader_node)
|
|
|
|
for node in nodes:
|
|
if node != bsdf_node and node != output_node:
|
|
if node not in tex_nodes.values():
|
|
nodes.remove(node)
|
|
|
|
if props.target_mode == "GLTF" and props.pack_gltf:
|
|
if tex_nodes["Diffuse"]:
|
|
nodes.remove(tex_nodes["Diffuse"])
|
|
tex_nodes["Diffuse"] = None
|
|
if tex_nodes["AO"]:
|
|
nodes.remove(tex_nodes["AO"])
|
|
tex_nodes["AO"] = None
|
|
if tex_nodes["Roughness"]:
|
|
nodes.remove(tex_nodes["Roughness"])
|
|
tex_nodes["Roughness"] = None
|
|
if tex_nodes["Metallic"]:
|
|
nodes.remove(tex_nodes["Metallic"])
|
|
tex_nodes["Metallic"] = None
|
|
if tex_nodes["BaseMap"]:
|
|
nodeutils.link_nodes(links, tex_nodes["BaseMap"], "Color", bsdf_node, base_color_socket)
|
|
if tex_nodes["Alpha"]:
|
|
nodeutils.link_nodes(links, tex_nodes["BaseMap"], "Alpha", bsdf_node, alpha_socket)
|
|
nodes.remove(tex_nodes["Alpha"])
|
|
tex_nodes["Alpha"] = None
|
|
if tex_nodes["GLTF"]:
|
|
if not gltf_settings_node:
|
|
gltf_settings_node = nodeutils.make_gltf_settings_node(nodes)
|
|
if utils.B330():
|
|
split_node = nodeutils.make_shader_node(nodes, "ShaderNodeSeparateColor")
|
|
split_node.mode = "RGB"
|
|
nodeutils.link_nodes(links, tex_nodes["GLTF"], "Color", split_node, "Color")
|
|
nodeutils.link_nodes(links, split_node, "Red", gltf_settings_node, "Occlusion")
|
|
nodeutils.link_nodes(links, split_node, "Green", bsdf_node, roughness_socket)
|
|
nodeutils.link_nodes(links, split_node, "Blue", bsdf_node, metallic_socket)
|
|
else:
|
|
split_node = nodeutils.make_shader_node(nodes, "ShaderNodeSeparateRGB")
|
|
nodeutils.link_nodes(links, tex_nodes["GLTF"], "Color", split_node, "Image")
|
|
nodeutils.link_nodes(links, split_node, "R", gltf_settings_node, "Occlusion")
|
|
nodeutils.link_nodes(links, split_node, "G", bsdf_node, roughness_socket)
|
|
nodeutils.link_nodes(links, split_node, "B", bsdf_node, metallic_socket)
|
|
|
|
if tex_nodes["Diffuse"] or tex_nodes["AO"]:
|
|
|
|
if props.target_mode == "GLTF": # unpacked GLTF diffuse & AO
|
|
nodeutils.link_nodes(links, tex_nodes["Diffuse"], "Color", bsdf_node, base_color_socket)
|
|
if not gltf_settings_node:
|
|
gltf_settings_node = nodeutils.make_gltf_settings_node(nodes)
|
|
nodeutils.link_nodes(links, tex_nodes["AO"], "Color", gltf_settings_node, "Occlusion")
|
|
else:
|
|
mix_node = nodeutils.make_mixrgb_node(nodes, "MULTIPLY")
|
|
nodeutils.set_node_input_value(mix_node, "Fac", ao_strength)
|
|
nodeutils.link_nodes(links, tex_nodes["Diffuse"], "Color", mix_node, "Color1")
|
|
nodeutils.link_nodes(links, tex_nodes["AO"], "Color", mix_node, "Color2")
|
|
nodeutils.link_nodes(links, mix_node, "Color", bsdf_node, base_color_socket)
|
|
|
|
elif tex_nodes["Diffuse"]:
|
|
nodeutils.link_nodes(links, tex_nodes["Diffuse"], "Color", bsdf_node, base_color_socket)
|
|
|
|
if tex_nodes["Subsurface"]:
|
|
nodeutils.link_nodes(links, tex_nodes["Subsurface"], "Color", bsdf_node, sss_socket)
|
|
nodeutils.set_node_input_value(bsdf_node, "Subsurface Radius", sss_radius)
|
|
if mix_node:
|
|
nodeutils.link_nodes(links, mix_node, "Color", bsdf_node, "Subsurface Color")
|
|
else:
|
|
nodeutils.link_nodes(links, tex_nodes["Diffuse"], "Color", bsdf_node, "Subsurface Color")
|
|
|
|
if tex_nodes["Metallic"]:
|
|
nodeutils.link_nodes(links, tex_nodes["Metallic"], "Color", bsdf_node, metallic_socket)
|
|
if tex_nodes["Specular"]:
|
|
nodeutils.link_nodes(links, tex_nodes["Specular"], "Color", bsdf_node, specular_socket)
|
|
if tex_nodes["Roughness"]:
|
|
nodeutils.link_nodes(links, tex_nodes["Roughness"], "Color", bsdf_node, roughness_socket)
|
|
if tex_nodes["Emission"]:
|
|
nodeutils.link_nodes(links, tex_nodes["Emission"], "Color", bsdf_node, emission_socket)
|
|
|
|
thickness_value_node = None
|
|
if tex_nodes["Transmission"]:
|
|
nodeutils.link_nodes(links, tex_nodes["Transmission"], "Color", bsdf_node, transmission_socket)
|
|
if utils.B420() and thickness > 0.0:
|
|
thickness_value_node = nodeutils.make_value_node(nodes, "Thickness", "thickness", thickness)
|
|
nodeutils.link_nodes(links, thickness_value_node, "Value", output_node, "Thickness")
|
|
materials.set_material_alpha(mat, "DITHERED")
|
|
elif tex_nodes["Alpha"]:
|
|
nodeutils.link_nodes(links, tex_nodes["Alpha"], "Color", bsdf_node, alpha_socket)
|
|
|
|
if tex_nodes["Normal"]:
|
|
normal_map_node = nodeutils.make_shader_node(nodes, "ShaderNodeNormalMap")
|
|
nodeutils.link_nodes(links, tex_nodes["Normal"], "Color", normal_map_node, "Color")
|
|
nodeutils.link_nodes(links, normal_map_node, "Normal", bsdf_node, normal_socket)
|
|
nodeutils.set_node_input_value(normal_map_node, "Strength", normal_strength)
|
|
elif tex_nodes["Normal"]:
|
|
bump_map_node = nodeutils.make_shader_node(nodes, "ShaderNodeBump")
|
|
nodeutils.link_nodes(links, tex_nodes["Normal"], "Color", bump_map_node, "Height")
|
|
nodeutils.link_nodes(links, bump_map_node, "Normal", bsdf_node, normal_socket)
|
|
nodeutils.set_node_input_value(bump_map_node, "Distance", bump_distance)
|
|
|
|
if tex_nodes["MicroNormal"]:
|
|
if normal_map_node is None:
|
|
normal_map_node = nodeutils.make_shader_node(nodes, "ShaderNodeNormalMap")
|
|
nodeutils.link_nodes(links, normal_map_node, "Normal", bsdf_node, normal_socket)
|
|
micro_mix_node = nodeutils.make_mixrgb_node(nodes, "OVERLAY")
|
|
micro_mapping_node = nodeutils.make_shader_node(nodes, "ShaderNodeMapping")
|
|
micro_texcoord_node = nodeutils.make_shader_node(nodes, "ShaderNodeTexCoord")
|
|
nodeutils.set_node_input_value(micro_mix_node, "Fac", micro_normal_strength)
|
|
nodeutils.link_nodes(links, micro_texcoord_node, "UV", micro_mapping_node, "Vector")
|
|
nodeutils.link_nodes(links, micro_mapping_node, "Vector", tex_nodes["MicroNormal"], "Vector")
|
|
nodeutils.set_node_input_value(micro_mapping_node, "Scale", micro_normal_scale)
|
|
if tex_nodes["MicroNormalMask"]:
|
|
micro_mask_mult_node = nodeutils.make_math_node(nodes, "MULTIPLY", 1, micro_normal_strength)
|
|
nodeutils.link_nodes(links, tex_nodes["MicroNormalMask"], "Color", micro_mask_mult_node, 0)
|
|
nodeutils.link_nodes(links, micro_mask_mult_node, "Value", micro_mix_node, "Fac")
|
|
if tex_nodes["Normal"]:
|
|
nodeutils.link_nodes(links, tex_nodes["Normal"], "Color", micro_mix_node, "Color1")
|
|
nodeutils.link_nodes(links, tex_nodes["MicroNormal"], "Color", micro_mix_node, "Color2")
|
|
nodeutils.link_nodes(links, micro_mix_node, "Color", normal_map_node, "Color")
|
|
|
|
if utils.B400():
|
|
nodeutils.set_node_input_value(bsdf_node, "Emission Strength", emission_strength)
|
|
|
|
set_loc(bsdf_node, (200, 400))
|
|
set_loc(output_node, (600, 400))
|
|
|
|
set_loc(tex_nodes["Diffuse"], (-600, 600))
|
|
set_loc(tex_nodes["AO"], (-900, 600))
|
|
set_loc(mix_node, (-300, 600))
|
|
|
|
set_loc(thickness_value_node, (250, 520))
|
|
|
|
if props.target_mode == "GLTF" and props.pack_gltf:
|
|
set_loc(tex_nodes["BaseMap"], (-600, 600))
|
|
set_loc(tex_nodes["GLTF"], (-900, 0))
|
|
set_loc(split_node, (-600, 0))
|
|
set_loc(gltf_settings_node, (200, 700))
|
|
else:
|
|
set_loc(gltf_settings_node, (-600, 700))
|
|
set_loc(tex_nodes["BaseMap"], (-1800, 600))
|
|
set_loc(tex_nodes["Mask"], (-1800, 300))
|
|
set_loc(tex_nodes["Detail"], (-1800, 0))
|
|
|
|
set_loc(tex_nodes["Subsurface"], (-600, 300))
|
|
set_loc(tex_nodes["Thickness"], (-900, 300))
|
|
|
|
set_loc(tex_nodes["Metallic"], (-1200, 0))
|
|
set_loc(tex_nodes["Specular"], (-900, 0))
|
|
set_loc(tex_nodes["Roughness"], (-600, 0))
|
|
|
|
set_loc(tex_nodes["Transmission"], (-1200, -300))
|
|
set_loc(tex_nodes["Emission"], (-900, -300))
|
|
set_loc(tex_nodes["Alpha"], (-600, -300))
|
|
|
|
set_loc(tex_nodes["Normal"], (-900, -600))
|
|
set_loc(normal_map_node, (-300, -600))
|
|
set_loc(tex_nodes["Bump"], (-900, -600))
|
|
set_loc(bump_map_node, (-300, -600))
|
|
set_loc(tex_nodes["MicroNormalMask"], (-1200, -600))
|
|
set_loc(tex_nodes["MicroNormal"], (-1500, -600))
|
|
|
|
set_loc(micro_mix_node, (-470,-690))
|
|
set_loc(micro_mapping_node, (-1710,-600))
|
|
set_loc(micro_texcoord_node, (-1890,-600))
|
|
set_loc(micro_mask_mult_node, (-640,-600))
|
|
|
|
|
|
def bake_character(context, chr_cache):
|
|
props = vars.bake_props()
|
|
prefs = vars.prefs()
|
|
|
|
utils.log_info("")
|
|
utils.log_info("Baking Selected Objects:")
|
|
utils.log_info("")
|
|
|
|
if prefs.bake_objects_mode == "SELECTED":
|
|
selected_objects = [ o for o in context.selected_objects if utils.object_exists_is_mesh(o) ]
|
|
else:
|
|
selected_objects = None
|
|
objects = get_export_objects(chr_cache, only_objects=selected_objects)
|
|
|
|
bake_state = prep_bake(context, samples=props.bake_samples,
|
|
image_format=props.target_format,
|
|
make_surface=True)
|
|
|
|
if prefs.bake_use_gpu:
|
|
set_cycles_samples(context, samples=props.bake_samples, adaptive_samples=0.01, use_gpu=True, denoising=False)
|
|
|
|
chr_cache.baked_target_mode = "NONE"
|
|
|
|
materials_done = []
|
|
obj : bpy.types.Object
|
|
for obj in objects:
|
|
if obj.type == "MESH":
|
|
bake_character_object(context, chr_cache, obj, bake_state, materials_done)
|
|
materials_done.clear()
|
|
|
|
chr_cache.baked_target_mode = props.target_mode
|
|
|
|
post_bake(context, bake_state)
|
|
|
|
|
|
def next_uid():
|
|
props = vars.bake_props()
|
|
uid = props.auto_increment
|
|
props.auto_increment += 1
|
|
return uid
|
|
|
|
|
|
def get_target_map(suffix):
|
|
props = vars.bake_props()
|
|
|
|
bake_maps = vars.get_bake_target_maps(props.target_mode)
|
|
|
|
if suffix in bake_maps:
|
|
return bake_maps[suffix]
|
|
|
|
utils.log_error("No matching target map for suffix: " + suffix)
|
|
return None
|
|
|
|
|
|
def get_target_map_suffix(suffix):
|
|
target_map = get_target_map(suffix)
|
|
if target_map:
|
|
return target_map[0]
|
|
|
|
utils.log_error("No matching target map suffix: " + suffix)
|
|
return "None"
|
|
|
|
|
|
def get_int_prop_by_name(props, prop_name):
|
|
scope = locals()
|
|
prop_val = eval("props." + prop_name)
|
|
return int(prop_val)
|
|
|
|
|
|
def get_max_texture_size(mat, mat_cache, tex_list, input_list):
|
|
"""
|
|
Attempts to get the largest texture size from the given named texure nodes (CC3 materials),
|
|
then falls back to finding the largest texture connected to the given list of inputs to the BSDF shader
|
|
"""
|
|
|
|
if mat is None or mat.node_tree is None or mat.node_tree.nodes is None:
|
|
return vars.NO_SIZE
|
|
|
|
max_size = 0
|
|
|
|
if mat_cache is not None and tex_list is not None:
|
|
for t in tex_list:
|
|
tex_node = nodeutils.find_shader_texture(mat.node_tree.nodes, t)
|
|
if tex_node is not None:
|
|
size = nodeutils.get_tex_image_size(tex_node)
|
|
utils.log_info("Found CC3 texture: " + t + " size: " + str(size))
|
|
if size > max_size:
|
|
max_size = size
|
|
|
|
elif input_list is not None and max_size == 0:
|
|
bsdf_node = nodeutils.get_bsdf_node(mat)
|
|
max_size = 0
|
|
size = get_connected_texture_size(bsdf_node, 0, input_list)
|
|
utils.log_info("Detected largest texture size: " + str(size))
|
|
max_size = size
|
|
|
|
# here we can override the result based on the mat_cache.material_type and the looked for tex_list
|
|
if mat_cache is not None:
|
|
if mat_cache.material_type in vars.TEX_SIZE_OVERRIDE:
|
|
mat_overrides = vars.TEX_SIZE_OVERRIDE[mat_cache.material_type]
|
|
for t in tex_list:
|
|
if t in mat_overrides:
|
|
utils.log_info("Overriding size for material: " + mat.name + " texture: " + t + " size: " + str(mat_overrides[t]))
|
|
size = mat_overrides[t]
|
|
if size > max_size:
|
|
max_size = size
|
|
|
|
if max_size == 0:
|
|
max_size = vars.NO_SIZE
|
|
|
|
utils.log_info("Max texture size: " + str(max_size))
|
|
|
|
return max_size
|
|
|
|
|
|
# suffix as defined in: vars.py *_MAPS
|
|
def detect_bake_size_from_suffix(mat, mat_cache, suffix):
|
|
props = vars.bake_props()
|
|
|
|
target_map = get_target_map(suffix)
|
|
if target_map:
|
|
target_size = target_map[1]
|
|
if target_size in vars.TEX_SIZE_DETECT:
|
|
tex_size_detect = vars.TEX_SIZE_DETECT[target_size]
|
|
tex_list = tex_size_detect[0]
|
|
input_list = tex_size_detect[1]
|
|
return get_max_texture_size(mat, mat_cache, tex_list, input_list)
|
|
|
|
# otherwise just return the default of 1024
|
|
return vars.DEFAULT_SIZE
|
|
|
|
|
|
def apply_override_size(mat, global_suffix, width, height):
|
|
|
|
# get either the global default props or the material specific props if they exist...
|
|
props = vars.bake_props()
|
|
p = get_material_bake_settings(mat)
|
|
if p is None:
|
|
p = props
|
|
|
|
max_size = int(props.max_size)
|
|
|
|
# if overriding with custom max sizes:
|
|
if props.custom_sizes:
|
|
bake_maps = vars.get_bake_target_maps(props.target_mode)
|
|
if global_suffix in bake_maps:
|
|
max_size = get_int_prop_by_name(p, bake_maps[global_suffix][1])
|
|
utils.log_info(f"{global_suffix} map {props.target_mode} maximum map size: {width} x {height}")
|
|
|
|
if width > max_size:
|
|
width = max_size
|
|
if height > max_size:
|
|
height = max_size
|
|
|
|
return width, height
|
|
|
|
|
|
def get_bake_target_material_name(name, uid):
|
|
props = vars.bake_props()
|
|
|
|
# Sketchfab recommends no spaces or symbols in the texture names...
|
|
if props.target_mode == "SKETCHFAB":
|
|
text = name.replace("_", "").replace("-", "").replace(".", "")
|
|
text += "B" + str(uid)
|
|
else:
|
|
text = name + "_B" + str(uid)
|
|
return text
|
|
|
|
|
|
def bake_character_object(context, chr_cache, obj, bake_state, materials_done):
|
|
props = vars.bake_props()
|
|
|
|
if not utils.object_exists_is_mesh(obj):
|
|
utils.log_warn(f"Object doesn't exist!")
|
|
return
|
|
|
|
for slot in obj.material_slots:
|
|
source_mat = slot.material
|
|
if source_mat is None: continue
|
|
bake_cache = get_export_bake_cache(source_mat)
|
|
|
|
# in case we haven't reverted to the source materials get the real source_mat:
|
|
if (bake_cache and
|
|
bake_cache.source_material is not None and
|
|
bake_cache.source_material != source_mat):
|
|
utils.log_info("Using cached source material!")
|
|
source_mat = bake_cache.source_material
|
|
|
|
if not utils.material_exists(source_mat):
|
|
utils.log_warn(f"No Material in slot!")
|
|
continue
|
|
|
|
source_mat_cache = chr_cache.get_material_cache(source_mat)
|
|
if not source_mat_cache:
|
|
utils.log_warn(f"Material has no character data: {source_mat.name}. Skipping!")
|
|
continue
|
|
|
|
# if there is no BSDF node, don't process.
|
|
bsdf_node = None
|
|
if source_mat and source_mat.node_tree:
|
|
bsdf_node = nodeutils.get_bsdf_node(source_mat)
|
|
if bsdf_node is None:
|
|
utils.log_warn(f"Material has no BSDF node: {source_mat.name}. Skipping!")
|
|
continue
|
|
|
|
# only process each material once:
|
|
if source_mat not in materials_done:
|
|
materials_done.append(source_mat)
|
|
|
|
old_mat = None
|
|
if bake_cache is None:
|
|
uid = next_uid()
|
|
else:
|
|
uid = bake_cache.uid
|
|
if bake_cache.baked_material:
|
|
old_mat = bake_cache.baked_material
|
|
|
|
bake_mat_name = get_bake_target_material_name(source_mat.name, uid)
|
|
|
|
# copy the source material
|
|
bake_mat = source_mat.copy()
|
|
bake_mat.name = "TEMP_" + bake_mat_name
|
|
|
|
# try to find any old baked material by name
|
|
if old_mat is None:
|
|
for m in bpy.data.materials:
|
|
if m.name == bake_mat_name:
|
|
old_mat = m
|
|
|
|
# replace all of the old baked materials with the new copy:
|
|
if old_mat:
|
|
for o in context.scene.objects:
|
|
if o != obj and o.type == "MESH" and o.data.materials:
|
|
for s in o.material_slots:
|
|
if s.material and s.material == old_mat:
|
|
s.material = bake_mat
|
|
# remove the old material once all copies of it have been replaced...
|
|
bpy.data.materials.remove(old_mat)
|
|
|
|
# give the new copy the correct name
|
|
bake_mat.name = bake_mat_name
|
|
|
|
# add/update the bake cache
|
|
add_material_bake_cache(uid, source_mat, bake_mat)
|
|
|
|
# attach the bake material to the bake surface plane
|
|
set_bake_material(bake_state, bake_mat)
|
|
|
|
try:
|
|
bake_export_material(context, bake_mat, source_mat, source_mat_cache)
|
|
slot.material = bake_mat
|
|
except Exception as e:
|
|
utils.log_error("Bake Character Object: Something went horribly wrong!", e)
|
|
|
|
else:
|
|
# if the material has already been baked elsewhere, replace the material here
|
|
if bake_cache and slot.material != bake_cache.baked_material:
|
|
slot.material = bake_cache.baked_material
|
|
|
|
|
|
def get_export_bake_cache(mat):
|
|
props = vars.bake_props()
|
|
for bc in props.bake_cache:
|
|
if bc.source_material == mat or bc.baked_material == mat:
|
|
return bc
|
|
return None
|
|
|
|
|
|
def add_material_bake_cache(uid, source_mat, bake_mat):
|
|
props = vars.bake_props()
|
|
bc = get_export_bake_cache(source_mat)
|
|
if bc is None:
|
|
bc = props.bake_cache.add()
|
|
bc.uid = uid
|
|
bc.source_material = source_mat
|
|
bc.baked_material = bake_mat
|
|
return bc
|
|
|
|
|
|
def remove_material_bake_cache(mat):
|
|
props = vars.bake_props()
|
|
bc = get_export_bake_cache(mat)
|
|
if bc:
|
|
utils.remove_from_collection(props.bake_cache, bc)
|
|
|
|
|
|
def get_material_bake_settings(mat):
|
|
props = vars.bake_props()
|
|
if mat:
|
|
for ms in props.material_settings:
|
|
if ms.material == mat:
|
|
return ms
|
|
return None
|
|
|
|
|
|
def add_material_bake_settings(mat):
|
|
props = vars.bake_props()
|
|
ms = get_material_bake_settings(mat)
|
|
if ms is None:
|
|
ms = props.material_settings.add()
|
|
ms.material = mat
|
|
ms.diffuse_size = props.diffuse_size
|
|
ms.ao_size = props.ao_size
|
|
ms.sss_size = props.sss_size
|
|
ms.thickness_size = props.thickness_size
|
|
ms.transmission_size = props.transmission_size
|
|
ms.metallic_size = props.metallic_size
|
|
ms.specular_size = props.specular_size
|
|
ms.roughness_size = props.roughness_size
|
|
ms.emissive_size = props.emissive_size
|
|
ms.alpha_size = props.alpha_size
|
|
ms.normal_size = props.normal_size
|
|
ms.bump_size = props.bump_size
|
|
ms.mask_size = props.mask_size
|
|
ms.detail_size = props.detail_size
|
|
return ms
|
|
|
|
|
|
def remove_material_bake_settings(mat):
|
|
props = vars.bake_props()
|
|
for ms in props.material_settings:
|
|
if ms.material == mat:
|
|
utils.remove_from_collection(props.material_settings, ms)
|
|
|
|
|
|
def revert_baked_materials(chr_cache):
|
|
|
|
objects = get_export_objects(chr_cache)
|
|
|
|
for obj in objects:
|
|
if obj.type == "MESH":
|
|
for i in range(0, len(obj.data.materials)):
|
|
mat = obj.data.materials[i]
|
|
bc = get_export_bake_cache(mat)
|
|
if bc and bc.baked_material == mat:
|
|
obj.data.materials[i] = bc.source_material
|
|
|
|
|
|
def restore_baked_materials(chr_cache):
|
|
|
|
objects = get_export_objects(chr_cache)
|
|
|
|
for obj in objects:
|
|
if obj.type == "MESH":
|
|
for i in range(0, len(obj.data.materials)):
|
|
mat = obj.data.materials[i]
|
|
bc = get_export_bake_cache(mat)
|
|
if bc and bc.source_material == mat:
|
|
obj.data.materials[i] = bc.baked_material
|
|
|
|
|
|
class CCICBaker(bpy.types.Operator):
|
|
"""Bake CC/iC Character For Export"""
|
|
bl_idname = "ccic.baker"
|
|
bl_label = "Baker"
|
|
bl_options = {"REGISTER"}
|
|
|
|
param: bpy.props.StringProperty(
|
|
name = "param",
|
|
default = "",
|
|
options={"HIDDEN"}
|
|
)
|
|
|
|
def execute(self, context):
|
|
props = vars.props()
|
|
prefs = vars.prefs()
|
|
|
|
mode_selection = utils.store_mode_selection_state()
|
|
|
|
chr_cache = props.get_context_character_cache(context)
|
|
|
|
if not chr_cache:
|
|
self.report({"ERROR"}, "No current character!")
|
|
return {"FINISHED"}
|
|
|
|
utils.start_timer()
|
|
|
|
if self.param == "BAKE":
|
|
bake_character(context, chr_cache)
|
|
utils.restore_mode_selection_state(mode_selection)
|
|
|
|
utils.log_timer("Baking Completed!", "m")
|
|
|
|
return {"FINISHED"}
|
|
|
|
@classmethod
|
|
def description(cls, context, properties):
|
|
|
|
if properties.param == "BAKE":
|
|
return "Bake Textures..."
|
|
return ""
|
|
|
|
|
|
class CCICBakeSettings(bpy.types.Operator):
|
|
"""Bake Settings"""
|
|
bl_idname = "ccic.bakesettings"
|
|
bl_label = "Bake Settings"
|
|
bl_options = {"REGISTER"}
|
|
|
|
param: bpy.props.StringProperty(
|
|
name = "param",
|
|
default = "",
|
|
options={"HIDDEN"}
|
|
)
|
|
|
|
def execute(self, context):
|
|
props = vars.props()
|
|
prefs = vars.prefs()
|
|
|
|
obj = context.object
|
|
mat = utils.get_context_material(context)
|
|
mat_settings = get_material_bake_settings(mat)
|
|
chr_cache = props.get_context_character_cache(context)
|
|
|
|
if obj and obj.type == "MESH" and mat:
|
|
|
|
if self.param == "ADD":
|
|
add_material_bake_settings(mat)
|
|
|
|
if self.param == "REMOVE":
|
|
remove_material_bake_settings(mat)
|
|
|
|
if self.param == "SOURCE":
|
|
revert_baked_materials(chr_cache)
|
|
|
|
if self.param == "BAKED":
|
|
restore_baked_materials(chr_cache)
|
|
|
|
return {"FINISHED"}
|
|
|
|
@classmethod
|
|
def description(cls, context, properties):
|
|
|
|
if properties.param == "ADD":
|
|
return "Add custom bake settings for this material."
|
|
if properties.param == "REMOVE":
|
|
return "Remove custom bake settings for this material."
|
|
if properties.param == "SOURCE":
|
|
return "Revert to the source materials."
|
|
if properties.param == "BAKED":
|
|
return "Restore the baked materials."
|
|
if properties.param == "DEFAULTS":
|
|
return "Attempt to assign default settings to each material in the selected objects."
|
|
return ""
|
|
|
|
|
|
JPEGIFY_FORMATS = [
|
|
"BMP",
|
|
"PNG",
|
|
"TARGA",
|
|
"TARGA_RAW",
|
|
"TIFF",
|
|
]
|
|
|
|
|
|
class CCICJpegify(bpy.types.Operator):
|
|
"""Jpegifyer"""
|
|
bl_idname = "ccic.jpegify"
|
|
bl_label = "Jpegify"
|
|
bl_options = {"REGISTER"}
|
|
|
|
def execute(self, context):
|
|
props = vars.bake_props()
|
|
|
|
bake_path = get_bake_path()
|
|
os.makedirs(bake_path, exist_ok=True)
|
|
context.scene.render.image_settings.quality = props.jpeg_quality
|
|
|
|
for img in bpy.data.images:
|
|
|
|
try:
|
|
if img and img.size[0] > 0 and img.size[1] > 0:
|
|
if img.file_format in JPEGIFY_FORMATS:
|
|
img.file_format = "JPEG"
|
|
dir, file = os.path.split(img.filepath)
|
|
root, ext = os.path.splitext(file)
|
|
new_path = os.path.join(bake_path, root + ".jpg")
|
|
img.filepath_raw = new_path
|
|
img.save()
|
|
else:
|
|
if not os.path.normcase(os.path.realpath(bake_path)) in os.path.normcase(os.path.realpath(img.filepath)):
|
|
dir, file = os.path.split(img.filepath)
|
|
new_path = os.path.join(bake_path, file)
|
|
img.filepath_raw = new_path
|
|
img.save()
|
|
img.reload()
|
|
except:
|
|
utils.log_error("ERROR")
|
|
|
|
return {"FINISHED"}
|
|
|
|
@classmethod
|
|
def description(cls, context, properties):
|
|
return "Makes all suitable texture maps jpegs and puts them all in the bake folder."
|
|
|