WIP: Single-frame job compiler #104194
@ -0,0 +1,187 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
const JOB_TYPE = {
|
||||||
|
label: "Single-frame Blender Render",
|
||||||
|
settings: [
|
||||||
|
// Settings for artists to determine:
|
||||||
|
{ key: "frame", type: "int32", required: true, eval: "C.scene.frame_current",
|
||||||
|
description: "Frame to render"},
|
||||||
|
{ key: "chunk_size", type: "int32", default: 128, description: "Number of samples to render in one Blender render task",
|
||||||
|
visible: "submission" },
|
||||||
|
{ key: "denoising", type: "bool", required: true, default: false,
|
||||||
|
description: "Toggles OpenImageDenoise" },
|
||||||
|
|
||||||
|
|
||||||
|
// render_output_root + add_path_components determine the value of render_output_path.
|
||||||
|
{ key: "render_output_root", type: "string", subtype: "dir_path", required: true, visible: "submission",
|
||||||
|
description: "Base directory of where render output is stored. Will have some job-specific parts appended to it"},
|
||||||
|
{ key: "add_path_components", type: "int32", required: true, default: 0, propargs: {min: 0, max: 32}, visible: "submission",
|
||||||
|
description: "Number of path components of the current blend file to use in the render output path"},
|
||||||
|
{ key: "render_output_path", type: "string", subtype: "file_path", editable: false,
|
||||||
|
eval: "str(Path(abspath(settings.render_output_root), last_n_dir_parts(settings.add_path_components), jobname, '{timestamp}'))",
|
||||||
|
description: "Final file path of where render output will be saved"},
|
||||||
|
|
||||||
|
// Automatically evaluated settings:
|
||||||
|
{ key: "blendfile", type: "string", required: true, description: "Path of the Blend file to render", visible: "web" },
|
||||||
|
{ key: "samples", type: "string", required: true, eval: "f'0-{C.scene.cycles.samples}'", visible: "web",
|
||||||
|
description: "Total number of samples in the job" },
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
function compileJob(job) {
|
||||||
|
print("Single-frame Render job submitted");
|
||||||
|
print("job: ", job);
|
||||||
|
|
||||||
|
const settings = job.settings;
|
||||||
|
const renderOutput = renderOutputPath(job);
|
||||||
|
|
||||||
|
// Make sure that when the job is investigated later, it shows the
|
||||||
|
// actually-used render output:
|
||||||
|
settings.render_output_path = renderOutput;
|
||||||
|
|
||||||
|
const renderDir = path.dirname(renderOutput);
|
||||||
|
const renderTasks = authorRenderTasks(settings, renderDir, renderOutput);
|
||||||
|
const mergeTask = authorCreateMergeTask(settings, renderOutput);
|
||||||
|
const denoiseTask = authorCreateDenoiseTask(settings, renderOutput);
|
||||||
|
|
||||||
|
for (const rt of renderTasks) {
|
||||||
|
job.addTask(rt);
|
||||||
|
}
|
||||||
|
// All render tasks are a dependency of the merge task
|
||||||
|
for (const rt of renderTasks) {
|
||||||
|
mergeTask.addDependency(rt);
|
||||||
|
}
|
||||||
|
job.addTask(mergeTask);
|
||||||
|
// Add a denoise task if denoising is enabled and
|
||||||
|
// set the merge task as its dependency
|
||||||
|
if (denoiseTask) {
|
||||||
|
denoiseTask.addDependency(mergeTask);
|
||||||
|
job.addTask(denoiseTask);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do field replacement on the render output path.
|
||||||
|
function renderOutputPath(job) {
|
||||||
|
let path = job.settings.render_output_path;
|
||||||
|
if (!path) {
|
||||||
|
throw "no render_output_path setting!";
|
||||||
|
}
|
||||||
|
return path.replace(/{([^}]+)}/g, (match, group0) => {
|
||||||
|
switch (group0) {
|
||||||
|
case "timestamp":
|
||||||
|
return formatTimestampLocal(job.created);
|
||||||
|
default:
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function authorRenderTasks(settings, renderDir, renderOutput) {
|
||||||
|
print("authorRenderTasks(", renderDir, renderOutput, ")");
|
||||||
|
let renderTasks = [];
|
||||||
|
let chunks = frameChunker(settings.samples, settings.chunk_size);
|
||||||
|
for (let chunk of chunks) {
|
||||||
|
const task = author.Task(`render-${chunk}`, "blender");
|
||||||
|
let chunk_size = chunk.split("-", 2)[1] - chunk.split("-", 2)[0]
|
||||||
|
let pythonExpression = `
|
||||||
|
import bpy
|
||||||
|
bpy.context.scene.render.engine = 'CYCLES'
|
||||||
|
bpy.context.scene.render.use_compositing = False
|
||||||
|
bpy.context.scene.cycles.use_denoising = False
|
||||||
|
bpy.context.scene.render.image_settings.file_format = 'OPEN_EXR_MULTILAYER'
|
||||||
|
bpy.context.scene.cycles.samples = ${chunk_size}
|
||||||
|
bpy.context.scene.cycles.sample_offset = ${chunk.split("-", 1)}`;
|
||||||
|
if (settings.denoising) {
|
||||||
|
pythonExpression += `
|
||||||
|
for layer in bpy.context.scene.view_layers:
|
||||||
|
layer['cycles']['denoising_store_passes'] = 1
|
||||||
|
layer.use_pass_vector = True`;
|
||||||
|
}
|
||||||
|
print(pythonExpression);
|
||||||
|
const command = author.Command("blender-render", {
|
||||||
|
exe: "{blender}",
|
||||||
|
exeArgs: "{blenderArgs}",
|
||||||
|
argsBefore: [],
|
||||||
|
blendfile: settings.blendfile,
|
||||||
|
args: [
|
||||||
|
"--python-expr", pythonExpression,
|
||||||
|
"--render-output", path.join(renderDir, path.basename(renderOutput), chunk),
|
||||||
|
"--render-frame", settings.frame
|
||||||
|
]
|
||||||
|
});
|
||||||
|
task.addCommand(command);
|
||||||
|
renderTasks.push(task);
|
||||||
|
}
|
||||||
|
return renderTasks;
|
||||||
|
}
|
||||||
|
|
||||||
|
function authorCreateMergeTask(settings, renderOutput) {
|
||||||
|
const task = author.Task(`merge`, "blender");
|
||||||
|
let pythonExpression = `
|
||||||
|
import os
|
||||||
|
import pathlib
|
||||||
|
import bpy
|
||||||
|
|
||||||
|
basepath = "${renderOutput}/"
|
||||||
|
tmp = basepath + 'merge_tmp/'
|
||||||
|
filenames = [f for f in os.listdir(basepath) if os.path.isfile(basepath + f)]
|
||||||
|
if 'MERGED.exr' in filenames:
|
||||||
|
filenames.remove('MERGED.exr')
|
||||||
|
filenames.sort()
|
||||||
|
if len(filenames) <= 1:
|
||||||
|
print('This job only has one file, merging not required.')
|
||||||
|
print('Renaming ' + basepath + filenames[0] + ' to ' + basepath + 'MERGED.exr')
|
||||||
|
pathlib.Path(basepath + filenames[0]).rename(basepath + 'MERGED.exr')
|
||||||
|
exit()
|
||||||
|
if not os.path.exists(tmp):
|
||||||
|
os.makedirs(tmp)
|
||||||
|
|
||||||
|
index = 0
|
||||||
|
while len(filenames) > 0:
|
||||||
|
if index == 0:
|
||||||
|
print('Merging ' + basepath + filenames[0] + ' and ' + basepath + filenames[1])
|
||||||
|
bpy.ops.cycles.merge_images(input_filepath1=basepath+filenames[0], input_filepath2=basepath + filenames[1], output_filepath=tmp+str(index)+'.exr')
|
||||||
|
del filenames[0]
|
||||||
|
del filenames[0]
|
||||||
|
else:
|
||||||
|
print('Merging ' + tmp + str(index - 1) + '.exr' + ' and ' + basepath + filenames[0])
|
||||||
|
bpy.ops.cycles.merge_images(input_filepath1=tmp + str(index - 1) + '.exr', input_filepath2=basepath + filenames[0], output_filepath=tmp+str(index)+'.exr')
|
||||||
|
del filenames[0]
|
||||||
|
index += 1
|
||||||
|
|
||||||
|
print('Moving ' + tmp + str(index-1) + '.exr' + ' to ' + basepath + 'MERGED.exr')
|
||||||
|
pathlib.Path(tmp + str(index-1)+'.exr').rename(basepath + 'MERGED.exr')`;
|
||||||
|
const command = author.Command("blender-render", {
|
||||||
|
exe: "{blender}",
|
||||||
|
exeArgs: "{blenderArgs}",
|
||||||
|
argsBefore: [],
|
||||||
|
blendfile: settings.blendfile,
|
||||||
|
args: [
|
||||||
|
"--python-expr", pythonExpression
|
||||||
|
]
|
||||||
|
});
|
||||||
|
task.addCommand(command);
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
function authorCreateDenoiseTask(settings, renderOutput) {
|
||||||
|
if (! settings.denoising) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const task = author.Task(`denoise`, "blender");
|
||||||
|
let pythonExpression = `
|
||||||
|
import bpy
|
||||||
|
print("Running the denoiser")
|
||||||
|
bpy.ops.cycles.denoise_animation(input_filepath="${renderOutput}/MERGED.exr", output_filepath="${renderOutput}/DENOISED.exr")`;
|
||||||
|
const command = author.Command("blender-render", {
|
||||||
|
exe: "{blender}",
|
||||||
|
exeArgs: "{blenderArgs}",
|
||||||
|
argsBefore: [],
|
||||||
|
blendfile: settings.blendfile,
|
||||||
|
args: [
|
||||||
|
"--python-expr", pythonExpression
|
||||||
|
]
|
||||||
|
});
|
||||||
|
task.addCommand(command);
|
||||||
|
return task;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user