mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
255 lines
7.6 KiB
Python
255 lines
7.6 KiB
Python
from pathlib import Path
|
|
|
|
import bpy
|
|
|
|
from openpype.settings import get_project_settings
|
|
from openpype.pipeline import get_current_project_name
|
|
|
|
|
|
def get_default_render_folder(settings):
|
|
"""Get default render folder from blender settings."""
|
|
|
|
return (settings["blender"]
|
|
["RenderSettings"]
|
|
["default_render_image_folder"])
|
|
|
|
|
|
def get_aov_separator(settings):
|
|
"""Get aov separator from blender settings."""
|
|
|
|
aov_sep = (settings["blender"]
|
|
["RenderSettings"]
|
|
["aov_separator"])
|
|
|
|
if aov_sep == "dash":
|
|
return "-"
|
|
elif aov_sep == "underscore":
|
|
return "_"
|
|
elif aov_sep == "dot":
|
|
return "."
|
|
else:
|
|
raise ValueError(f"Invalid aov separator: {aov_sep}")
|
|
|
|
|
|
def get_image_format(settings):
|
|
"""Get image format from blender settings."""
|
|
|
|
return (settings["blender"]
|
|
["RenderSettings"]
|
|
["image_format"])
|
|
|
|
|
|
def get_multilayer(settings):
|
|
"""Get multilayer from blender settings."""
|
|
|
|
return (settings["blender"]
|
|
["RenderSettings"]
|
|
["multilayer_exr"])
|
|
|
|
|
|
def get_render_product(output_path, name, aov_sep):
|
|
"""
|
|
Generate the path to the render product. Blender interprets the `#`
|
|
as the frame number, when it renders.
|
|
|
|
Args:
|
|
file_path (str): The path to the blender scene.
|
|
render_folder (str): The render folder set in settings.
|
|
file_name (str): The name of the blender scene.
|
|
instance (pyblish.api.Instance): The instance to publish.
|
|
ext (str): The image format to render.
|
|
"""
|
|
filepath = output_path / name.lstrip("/")
|
|
render_product = f"{filepath}{aov_sep}beauty.####"
|
|
render_product = render_product.replace("\\", "/")
|
|
|
|
return render_product
|
|
|
|
|
|
def set_render_format(ext, multilayer):
|
|
# Set Blender to save the file with the right extension
|
|
bpy.context.scene.render.use_file_extension = True
|
|
|
|
image_settings = bpy.context.scene.render.image_settings
|
|
|
|
if ext == "exr":
|
|
image_settings.file_format = (
|
|
"OPEN_EXR_MULTILAYER" if multilayer else "OPEN_EXR")
|
|
elif ext == "bmp":
|
|
image_settings.file_format = "BMP"
|
|
elif ext == "rgb":
|
|
image_settings.file_format = "IRIS"
|
|
elif ext == "png":
|
|
image_settings.file_format = "PNG"
|
|
elif ext == "jpeg":
|
|
image_settings.file_format = "JPEG"
|
|
elif ext == "jp2":
|
|
image_settings.file_format = "JPEG2000"
|
|
elif ext == "tga":
|
|
image_settings.file_format = "TARGA"
|
|
elif ext == "tif":
|
|
image_settings.file_format = "TIFF"
|
|
|
|
|
|
def set_render_passes(settings):
|
|
aov_list = (settings["blender"]
|
|
["RenderSettings"]
|
|
["aov_list"])
|
|
|
|
custom_passes = (settings["blender"]
|
|
["RenderSettings"]
|
|
["custom_passes"])
|
|
|
|
vl = bpy.context.view_layer
|
|
|
|
vl.use_pass_combined = "combined" in aov_list
|
|
vl.use_pass_z = "z" in aov_list
|
|
vl.use_pass_mist = "mist" in aov_list
|
|
vl.use_pass_normal = "normal" in aov_list
|
|
vl.use_pass_diffuse_direct = "diffuse_light" in aov_list
|
|
vl.use_pass_diffuse_color = "diffuse_color" in aov_list
|
|
vl.use_pass_glossy_direct = "specular_light" in aov_list
|
|
vl.use_pass_glossy_color = "specular_color" in aov_list
|
|
vl.eevee.use_pass_volume_direct = "volume_light" in aov_list
|
|
vl.use_pass_emit = "emission" in aov_list
|
|
vl.use_pass_environment = "environment" in aov_list
|
|
vl.use_pass_shadow = "shadow" in aov_list
|
|
vl.use_pass_ambient_occlusion = "ao" in aov_list
|
|
|
|
cycles = vl.cycles
|
|
|
|
cycles.denoising_store_passes = "denoising" in aov_list
|
|
cycles.use_pass_volume_direct = "volume_direct" in aov_list
|
|
cycles.use_pass_volume_indirect = "volume_indirect" in aov_list
|
|
|
|
aovs_names = [aov.name for aov in vl.aovs]
|
|
for cp in custom_passes:
|
|
cp_name = cp[0]
|
|
if cp_name not in aovs_names:
|
|
aov = vl.aovs.add()
|
|
aov.name = cp_name
|
|
else:
|
|
aov = vl.aovs[cp_name]
|
|
aov.type = cp[1].get("type", "VALUE")
|
|
|
|
return aov_list, custom_passes
|
|
|
|
|
|
def set_node_tree(output_path, name, aov_sep, ext, multilayer):
|
|
# Set the scene to use the compositor node tree to render
|
|
bpy.context.scene.use_nodes = True
|
|
|
|
tree = bpy.context.scene.node_tree
|
|
|
|
# Get the Render Layers node
|
|
rl_node = None
|
|
for node in tree.nodes:
|
|
if node.bl_idname == "CompositorNodeRLayers":
|
|
rl_node = node
|
|
break
|
|
|
|
# If there's not a Render Layers node, we create it
|
|
if not rl_node:
|
|
rl_node = tree.nodes.new("CompositorNodeRLayers")
|
|
|
|
# Get the enabled output sockets, that are the active passes for the
|
|
# render.
|
|
# We also exclude some layers.
|
|
exclude_sockets = ["Image", "Alpha", "Noisy Image"]
|
|
passes = [
|
|
socket
|
|
for socket in rl_node.outputs
|
|
if socket.enabled and socket.name not in exclude_sockets
|
|
]
|
|
|
|
# Remove all output nodes
|
|
for node in tree.nodes:
|
|
if node.bl_idname == "CompositorNodeOutputFile":
|
|
tree.nodes.remove(node)
|
|
|
|
# Create a new output node
|
|
output = tree.nodes.new("CompositorNodeOutputFile")
|
|
|
|
image_settings = bpy.context.scene.render.image_settings
|
|
output.format.file_format = image_settings.file_format
|
|
|
|
# In case of a multilayer exr, we don't need to use the output node,
|
|
# because the blender render already outputs a multilayer exr.
|
|
if ext == "exr" and multilayer:
|
|
output.layer_slots.clear()
|
|
return []
|
|
|
|
output.file_slots.clear()
|
|
output.base_path = str(output_path)
|
|
|
|
aov_file_products = []
|
|
|
|
# For each active render pass, we add a new socket to the output node
|
|
# and link it
|
|
for render_pass in passes:
|
|
filepath = f"{name}{aov_sep}{render_pass.name}.####"
|
|
|
|
output.file_slots.new(filepath)
|
|
|
|
filename = str(output_path / filepath.lstrip("/"))
|
|
|
|
aov_file_products.append((render_pass.name, filename))
|
|
|
|
node_input = output.inputs[-1]
|
|
|
|
tree.links.new(render_pass, node_input)
|
|
|
|
return aov_file_products
|
|
|
|
|
|
def imprint_render_settings(node, data):
|
|
RENDER_DATA = "render_data"
|
|
if not node.get(RENDER_DATA):
|
|
node[RENDER_DATA] = {}
|
|
for key, value in data.items():
|
|
if value is None:
|
|
continue
|
|
node[RENDER_DATA][key] = value
|
|
|
|
|
|
def prepare_rendering(asset_group):
|
|
name = asset_group.name
|
|
|
|
filepath = Path(bpy.data.filepath)
|
|
assert filepath, "Workfile not saved. Please save the file first."
|
|
|
|
dirpath = filepath.parent
|
|
file_name = Path(filepath.name).stem
|
|
|
|
project = get_current_project_name()
|
|
settings = get_project_settings(project)
|
|
|
|
render_folder = get_default_render_folder(settings)
|
|
aov_sep = get_aov_separator(settings)
|
|
ext = get_image_format(settings)
|
|
multilayer = get_multilayer(settings)
|
|
|
|
set_render_format(ext, multilayer)
|
|
aov_list, custom_passes = set_render_passes(settings)
|
|
|
|
output_path = Path.joinpath(dirpath, render_folder, file_name)
|
|
|
|
render_product = get_render_product(output_path, name, aov_sep)
|
|
aov_file_product = set_node_tree(
|
|
output_path, name, aov_sep, ext, multilayer)
|
|
|
|
bpy.context.scene.render.filepath = render_product
|
|
|
|
render_settings = {
|
|
"render_folder": render_folder,
|
|
"aov_separator": aov_sep,
|
|
"image_format": ext,
|
|
"multilayer_exr": multilayer,
|
|
"aov_list": aov_list,
|
|
"custom_passes": custom_passes,
|
|
"render_product": render_product,
|
|
"aov_file_product": aov_file_product,
|
|
"review": True,
|
|
}
|
|
|
|
imprint_render_settings(asset_group, render_settings)
|