WIP: Single-frame job compiler #104194
@ -6,7 +6,7 @@ const JOB_TYPE = {
|
|||||||
// Settings for artists to determine:
|
// Settings for artists to determine:
|
||||||
{ key: "frame", type: "int32", required: true, eval: "C.scene.frame_current",
|
{ key: "frame", type: "int32", required: true, eval: "C.scene.frame_current",
|
||||||
description: "Frame to render"},
|
description: "Frame to render"},
|
||||||
{ key: "chunk_size", type: "int32", default: 128, description: "Number of samples to render in one Blender render task",
|
{ key: "chunk_size", type: "int32", default: 128, propargs: {min: 1}, description: "Number of samples to render in one Blender render task",
|
||||||
visible: "submission" },
|
visible: "submission" },
|
||||||
{ key: "denoising", type: "bool", required: true, default: false,
|
{ key: "denoising", type: "bool", required: true, default: false,
|
||||||
description: "Toggles OpenImageDenoise" },
|
description: "Toggles OpenImageDenoise" },
|
||||||
@ -23,6 +23,10 @@ const JOB_TYPE = {
|
|||||||
|
|
||||||
// Automatically evaluated settings:
|
// Automatically evaluated settings:
|
||||||
{ key: "blendfile", type: "string", required: true, description: "Path of the Blend file to render", visible: "web" },
|
{ key: "blendfile", type: "string", required: true, description: "Path of the Blend file to render", visible: "web" },
|
||||||
|
{ key: "format", type: "string", required: true, eval: "C.scene.render.image_settings.file_format", visible: "web" },
|
||||||
|
{ key: "uses_compositing", type: "bool", required: true, eval: "C.scene.use_nodes and C.scene.render.use_compositing", visible: "web" },
|
||||||
|
{ key: "image_file_extension", type: "string", required: true, eval: "C.scene.render.file_extension", visible: "hidden",
|
||||||
|
description: "File extension used for the final export" },
|
||||||
{ key: "samples", type: "string", required: true, eval: "f'1-{C.scene.cycles.samples}'", visible: "web",
|
{ key: "samples", type: "string", required: true, eval: "f'1-{C.scene.cycles.samples}'", visible: "web",
|
||||||
description: "Total number of samples in the job" },
|
description: "Total number of samples in the job" },
|
||||||
]
|
]
|
||||||
@ -43,6 +47,7 @@ function compileJob(job) {
|
|||||||
const renderTasks = authorRenderTasks(settings, renderDir, renderOutput);
|
const renderTasks = authorRenderTasks(settings, renderDir, renderOutput);
|
||||||
const mergeTask = authorCreateMergeTask(settings, renderOutput);
|
const mergeTask = authorCreateMergeTask(settings, renderOutput);
|
||||||
const denoiseTask = authorCreateDenoiseTask(settings, renderOutput);
|
const denoiseTask = authorCreateDenoiseTask(settings, renderOutput);
|
||||||
|
const compositeTask = authorCreateCompositeTask(settings, renderOutput);
|
||||||
|
|
||||||
for (const rt of renderTasks) {
|
for (const rt of renderTasks) {
|
||||||
job.addTask(rt);
|
job.addTask(rt);
|
||||||
@ -57,7 +62,12 @@ function compileJob(job) {
|
|||||||
if (denoiseTask) {
|
if (denoiseTask) {
|
||||||
denoiseTask.addDependency(mergeTask);
|
denoiseTask.addDependency(mergeTask);
|
||||||
job.addTask(denoiseTask);
|
job.addTask(denoiseTask);
|
||||||
|
compositeTask.addDependency(denoiseTask);
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
compositeTask.addDependency(mergeTask);
|
||||||
|
}
|
||||||
|
job.addTask(compositeTask);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do field replacement on the render output path.
|
// Do field replacement on the render output path.
|
||||||
@ -82,18 +92,18 @@ function authorRenderTasks(settings, renderDir, renderOutput) {
|
|||||||
let chunks = frameChunker(settings.samples, settings.chunk_size);
|
let chunks = frameChunker(settings.samples, settings.chunk_size);
|
||||||
for (let chunk of chunks) {
|
for (let chunk of chunks) {
|
||||||
const task = author.Task(`render-${chunk}`, "blender");
|
const task = author.Task(`render-${chunk}`, "blender");
|
||||||
let chunk_arr = chunk.split("-", 2)
|
let chunk_arr = chunk.split("-", 2);
|
||||||
let chunk_end
|
let chunk_end;
|
||||||
let chunk_start
|
let chunk_start;
|
||||||
if (chunk_arr.length < 2) {
|
if (chunk_arr.length < 2) {
|
||||||
chunk_end = chunk_arr[0]
|
chunk_end = chunk_arr[0];
|
||||||
chunk_start = chunk_arr[0] - 1
|
chunk_start = chunk_arr[0] - 1;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
chunk_end = chunk_arr[1]
|
chunk_end = chunk_arr[1];
|
||||||
chunk_start = chunk_arr[0] - 1
|
chunk_start = chunk_arr[0] - 1;
|
||||||
}
|
}
|
||||||
let chunk_size = chunk_end - chunk_start
|
let chunk_size = chunk_end - chunk_start;
|
||||||
let pythonExpression = `
|
let pythonExpression = `
|
||||||
import bpy
|
import bpy
|
||||||
bpy.context.scene.render.engine = 'CYCLES'
|
bpy.context.scene.render.engine = 'CYCLES'
|
||||||
@ -115,6 +125,7 @@ for layer in bpy.context.scene.view_layers:
|
|||||||
argsBefore: [],
|
argsBefore: [],
|
||||||
blendfile: settings.blendfile,
|
blendfile: settings.blendfile,
|
||||||
args: [
|
args: [
|
||||||
|
"--python-exit-code", 1,
|
||||||
"--python-expr", pythonExpression,
|
"--python-expr", pythonExpression,
|
||||||
"--render-output", path.join(renderDir, path.basename(renderOutput), chunk),
|
"--render-output", path.join(renderDir, path.basename(renderOutput), chunk),
|
||||||
"--render-frame", settings.frame
|
"--render-frame", settings.frame
|
||||||
@ -170,6 +181,7 @@ pathlib.Path(tmp + str(index-1)+'.exr').rename(basepath + 'MERGED.exr')`;
|
|||||||
argsBefore: [],
|
argsBefore: [],
|
||||||
blendfile: settings.blendfile,
|
blendfile: settings.blendfile,
|
||||||
args: [
|
args: [
|
||||||
|
"--python-exit-code", 1,
|
||||||
"--python-expr", pythonExpression
|
"--python-expr", pythonExpression
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
@ -192,6 +204,97 @@ bpy.ops.cycles.denoise_animation(input_filepath="${renderOutput}/MERGED.exr", ou
|
|||||||
argsBefore: [],
|
argsBefore: [],
|
||||||
blendfile: settings.blendfile,
|
blendfile: settings.blendfile,
|
||||||
args: [
|
args: [
|
||||||
|
"--python-exit-code", 1,
|
||||||
|
"--python-expr", pythonExpression
|
||||||
|
]
|
||||||
|
});
|
||||||
|
task.addCommand(command);
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
function authorCreateCompositeTask(settings, renderOutput) {
|
||||||
|
let filename;
|
||||||
|
if (settings.denoising) {
|
||||||
|
filename = "DENOISED.exr";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
filename = "MERGED.exr";
|
||||||
|
}
|
||||||
|
let pythonExpression = `
|
||||||
|
import pathlib
|
||||||
|
import bpy
|
||||||
|
C = bpy.context
|
||||||
|
basepath = "${renderOutput}/"
|
||||||
|
filename = "${filename}"`;
|
||||||
|
if (settings.uses_compositing) {
|
||||||
|
// Do the full composite+export pipeline
|
||||||
|
// uses snippets from
|
||||||
|
// https://github.com/state-of-the-art/BlendNet/blob/master/BlendNet/script-compose.py#L94
|
||||||
|
pythonExpression += `
|
||||||
|
bpy.ops.image.open(filepath=basepath + filename, use_sequence_detection=False)
|
||||||
|
image = bpy.data.images[bpy.path.basename(filename)]
|
||||||
|
image_node = C.scene.node_tree.nodes.new(type='CompositorNodeImage')
|
||||||
|
image_node.image = image
|
||||||
|
nodes_to_remove = []
|
||||||
|
links_to_create = []
|
||||||
|
for node in C.scene.node_tree.nodes:
|
||||||
|
print('DEBUG: Checking node %s' % (node,))
|
||||||
|
if not isinstance(node, bpy.types.CompositorNodeRLayers) or node.scene != C.scene:
|
||||||
|
continue
|
||||||
|
nodes_to_remove.append(node)
|
||||||
|
print('INFO: Reconnecting %s links to render image' % (node,))
|
||||||
|
for link in C.scene.node_tree.links:
|
||||||
|
print('DEBUG: Checking link %s - %s' % (link.from_node, link.to_node))
|
||||||
|
if link.from_node != node:
|
||||||
|
continue
|
||||||
|
print('DEBUG: Found link %s - %s' % (link.from_socket, link.to_socket))
|
||||||
|
link_name = "Combined"
|
||||||
|
for output in image_node.outputs:
|
||||||
|
print('DEBUG: Checking output:', output.name, link_name)
|
||||||
|
if output.name != link_name:
|
||||||
|
continue
|
||||||
|
links_to_create.append((output, link))
|
||||||
|
break
|
||||||
|
|
||||||
|
for output, link in links_to_create:
|
||||||
|
print('INFO: Connecting "%s" output to %s.%s input' % (
|
||||||
|
output, link.to_node, link.to_socket
|
||||||
|
))
|
||||||
|
C.scene.node_tree.links.new(output, link.to_socket)
|
||||||
|
|
||||||
|
print("Removing the nodes could potentially break the pipeline")
|
||||||
|
for node in nodes_to_remove:
|
||||||
|
print('INFO: Removing %s' % (node,))
|
||||||
|
C.scene.node_tree.nodes.remove(node)
|
||||||
|
C.scene.render.filepath = basepath + "FINAL"
|
||||||
|
bpy.ops.render.render(write_still=True)`;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
pythonExpression += `
|
||||||
|
print("Compositing is disabled")`;
|
||||||
|
if (settings.format == "OPEN_EXR_MULTILAYER") {
|
||||||
|
// Only rename
|
||||||
|
pythonExpression += `
|
||||||
|
print('Renaming ' + basepath + filename + ' to ' + basepath + 'FINAL.exr')
|
||||||
|
pathlib.Path(basepath + filename).rename(basepath + 'FINAL.exr')`;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Only export
|
||||||
|
pythonExpression += `
|
||||||
|
bpy.ops.image.open(filepath=basepath + filename, use_sequence_detection=False)
|
||||||
|
image = bpy.data.images[bpy.path.basename(filename)]
|
||||||
|
print("Saving final image to " + basepath + "FINAL" + "${settings.image_file_extension}")
|
||||||
|
image.save_render(basepath + "FINAL" + "${settings.image_file_extension}")`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const task = author.Task(`composite`, "blender");
|
||||||
|
const command = author.Command("blender-render", {
|
||||||
|
exe: "{blender}",
|
||||||
|
exeArgs: "{blenderArgs}",
|
||||||
|
argsBefore: [],
|
||||||
|
blendfile: settings.blendfile,
|
||||||
|
args: [
|
||||||
|
"--python-exit-code", 1,
|
||||||
"--python-expr", pythonExpression
|
"--python-expr", pythonExpression
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user