Compositor: Redesign File Output node
This patch redesigns the File Output node to provide better UX and UI. This is mainly achieved by allowing the user to create inputs by dragging into an Extend socket and adjust existing inputs using the familiar UI list design available in Blender. Additionally, various UI changes were done: - The Use Node Format option was renamed to Override Node Format for clarity. - Socket types are now fixed and do not change as new links are made, allowing users to specify the exact output type and employ implicit conversion if needed. - The distinction between images and Multi-Layer EXR was made clearer. - Final output paths are drawn in the UI to remove guess work. - The Base Path was split into a Directory and a File Name. - Panels were added to group options, include a panel for the node format, items, and item formats. Pull Request: https://projects.blender.org/blender/blender/pulls/141091
This commit is contained in:
@@ -27,7 +27,7 @@
|
||||
|
||||
/* Blender file format version. */
|
||||
#define BLENDER_FILE_VERSION BLENDER_VERSION
|
||||
#define BLENDER_FILE_SUBVERSION 53
|
||||
#define BLENDER_FILE_SUBVERSION 54
|
||||
|
||||
/* Minimum Blender version that supports reading file written with the current
|
||||
* version. Older Blender versions will test this and cancel loading the file, showing a warning to
|
||||
|
||||
@@ -979,18 +979,6 @@ void node_tree_blend_write(BlendWriter *writer, bNodeTree *ntree)
|
||||
node_blend_write_storage(writer, ntree, node);
|
||||
}
|
||||
|
||||
if (node->type_legacy == CMP_NODE_OUTPUT_FILE) {
|
||||
/* Inputs have their own storage data. */
|
||||
NodeImageMultiFile *nimf = (NodeImageMultiFile *)node->storage;
|
||||
BKE_image_format_blend_write(writer, &nimf->format);
|
||||
|
||||
LISTBASE_FOREACH (bNodeSocket *, sock, &node->inputs) {
|
||||
NodeImageMultiFileSocket *sockdata = static_cast<NodeImageMultiFileSocket *>(
|
||||
sock->storage);
|
||||
BLO_write_struct(writer, NodeImageMultiFileSocket, sockdata);
|
||||
BKE_image_format_blend_write(writer, &sockdata->format);
|
||||
}
|
||||
}
|
||||
if (ELEM(node->type_legacy, CMP_NODE_IMAGE, CMP_NODE_R_LAYERS)) {
|
||||
/* Write extra socket info. */
|
||||
LISTBASE_FOREACH (bNodeSocket *, sock, &node->outputs) {
|
||||
@@ -1608,11 +1596,6 @@ static void node_blend_read_data_storage(BlendDataReader *reader, bNodeTree *ntr
|
||||
iuser->scene = nullptr;
|
||||
break;
|
||||
}
|
||||
case CMP_NODE_OUTPUT_FILE: {
|
||||
NodeImageMultiFile *nimf = static_cast<NodeImageMultiFile *>(node->storage);
|
||||
BKE_image_format_blend_read_data(reader, &nimf->format);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
#include "BKE_anim_visualization.h"
|
||||
#include "BKE_customdata.hh"
|
||||
#include "BKE_image.hh"
|
||||
#include "BKE_image_format.hh"
|
||||
#include "BKE_main.hh" /* for Main */
|
||||
#include "BKE_mesh_legacy_convert.hh"
|
||||
#include "BKE_modifier.hh"
|
||||
@@ -248,13 +249,125 @@ static void do_versions_nodetree_socket_use_flags_2_62(bNodeTree *ntree)
|
||||
}
|
||||
}
|
||||
|
||||
/* find unique path */
|
||||
static bool unique_path_unique_check(ListBase *lb,
|
||||
bNodeSocket *sock,
|
||||
const blender::StringRef name)
|
||||
{
|
||||
LISTBASE_FOREACH (bNodeSocket *, sock_iter, lb) {
|
||||
if (sock_iter != sock) {
|
||||
NodeImageMultiFileSocket *sockdata = (NodeImageMultiFileSocket *)sock_iter->storage;
|
||||
if (sockdata->path == name) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void ntreeCompositOutputFileUniquePath(ListBase *list,
|
||||
bNodeSocket *sock,
|
||||
const char defname[],
|
||||
char delim)
|
||||
{
|
||||
/* See if we are given an empty string */
|
||||
if (ELEM(nullptr, sock, defname)) {
|
||||
return;
|
||||
}
|
||||
NodeImageMultiFileSocket *sockdata = (NodeImageMultiFileSocket *)sock->storage;
|
||||
BLI_uniquename_cb(
|
||||
[&](const blender::StringRef check_name) {
|
||||
return unique_path_unique_check(list, sock, check_name);
|
||||
},
|
||||
defname,
|
||||
delim,
|
||||
sockdata->path,
|
||||
sizeof(sockdata->path));
|
||||
}
|
||||
|
||||
/* find unique EXR layer */
|
||||
static bool unique_layer_unique_check(ListBase *lb,
|
||||
bNodeSocket *sock,
|
||||
const blender::StringRef name)
|
||||
{
|
||||
LISTBASE_FOREACH (bNodeSocket *, sock_iter, lb) {
|
||||
if (sock_iter != sock) {
|
||||
NodeImageMultiFileSocket *sockdata = (NodeImageMultiFileSocket *)sock_iter->storage;
|
||||
if (sockdata->layer == name) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void ntreeCompositOutputFileUniqueLayer(ListBase *list,
|
||||
bNodeSocket *sock,
|
||||
const char defname[],
|
||||
char delim)
|
||||
{
|
||||
/* See if we are given an empty string */
|
||||
if (ELEM(nullptr, sock, defname)) {
|
||||
return;
|
||||
}
|
||||
NodeImageMultiFileSocket *sockdata = (NodeImageMultiFileSocket *)sock->storage;
|
||||
BLI_uniquename_cb(
|
||||
[&](const blender::StringRef check_name) {
|
||||
return unique_layer_unique_check(list, sock, check_name);
|
||||
},
|
||||
defname,
|
||||
delim,
|
||||
sockdata->layer,
|
||||
sizeof(sockdata->layer));
|
||||
}
|
||||
|
||||
static bNodeSocket *ntreeCompositOutputFileAddSocket(bNodeTree *ntree,
|
||||
bNode *node,
|
||||
const char *name,
|
||||
const ImageFormatData *im_format)
|
||||
{
|
||||
NodeCompositorFileOutput *nimf = (NodeCompositorFileOutput *)node->storage;
|
||||
bNodeSocket *sock = blender::bke::node_add_static_socket(
|
||||
*ntree, *node, SOCK_IN, SOCK_RGBA, PROP_NONE, "", name);
|
||||
|
||||
/* create format data for the input socket */
|
||||
NodeImageMultiFileSocket *sockdata = MEM_callocN<NodeImageMultiFileSocket>(__func__);
|
||||
sock->storage = sockdata;
|
||||
|
||||
STRNCPY_UTF8(sockdata->path, name);
|
||||
ntreeCompositOutputFileUniquePath(&node->inputs, sock, name, '_');
|
||||
STRNCPY_UTF8(sockdata->layer, name);
|
||||
ntreeCompositOutputFileUniqueLayer(&node->inputs, sock, name, '_');
|
||||
|
||||
if (im_format) {
|
||||
BKE_image_format_copy(&sockdata->format, im_format);
|
||||
sockdata->format.color_management = R_IMF_COLOR_MANAGEMENT_FOLLOW_SCENE;
|
||||
if (BKE_imtype_is_movie(sockdata->format.imtype)) {
|
||||
sockdata->format.imtype = R_IMF_IMTYPE_OPENEXR;
|
||||
}
|
||||
}
|
||||
else {
|
||||
BKE_image_format_init(&sockdata->format, false);
|
||||
}
|
||||
BKE_image_format_update_color_space_for_type(&sockdata->format);
|
||||
|
||||
/* use node data format by default */
|
||||
sockdata->use_node_format = true;
|
||||
sockdata->save_as_render = true;
|
||||
|
||||
nimf->active_item_index = BLI_findindex(&node->inputs, sock);
|
||||
|
||||
return sock;
|
||||
}
|
||||
|
||||
static void do_versions_nodetree_multi_file_output_format_2_62_1(Scene *sce, bNodeTree *ntree)
|
||||
{
|
||||
LISTBASE_FOREACH (bNode *, node, &ntree->nodes) {
|
||||
if (node->type_legacy == CMP_NODE_OUTPUT_FILE) {
|
||||
/* previous CMP_NODE_OUTPUT_FILE nodes get converted to multi-file outputs */
|
||||
NodeImageFile *old_data = static_cast<NodeImageFile *>(node->storage);
|
||||
NodeImageMultiFile *nimf = MEM_callocN<NodeImageMultiFile>("node image multi file");
|
||||
NodeCompositorFileOutput *nimf = MEM_callocN<NodeCompositorFileOutput>(
|
||||
"node image multi file");
|
||||
bNodeSocket *old_image = static_cast<bNodeSocket *>(BLI_findlink(&node->inputs, 0));
|
||||
bNodeSocket *old_z = static_cast<bNodeSocket *>(BLI_findlink(&node->inputs, 1));
|
||||
|
||||
@@ -276,7 +389,7 @@ static void do_versions_nodetree_multi_file_output_format_2_62_1(Scene *sce, bNo
|
||||
BLI_path_split_dir_file(
|
||||
old_data->name, basepath, sizeof(basepath), filename, sizeof(filename));
|
||||
|
||||
STRNCPY(nimf->base_path, basepath);
|
||||
STRNCPY(nimf->directory, basepath);
|
||||
nimf->format = old_data->im_format;
|
||||
}
|
||||
else {
|
||||
@@ -326,7 +439,7 @@ static void do_versions_nodetree_multi_file_output_format_2_62_1(Scene *sce, bNo
|
||||
}
|
||||
}
|
||||
else if (node->type_legacy == CMP_NODE_OUTPUT_MULTI_FILE__DEPRECATED) {
|
||||
NodeImageMultiFile *nimf = static_cast<NodeImageMultiFile *>(node->storage);
|
||||
NodeCompositorFileOutput *nimf = static_cast<NodeCompositorFileOutput *>(node->storage);
|
||||
|
||||
/* CMP_NODE_OUTPUT_MULTI_FILE has been re-declared as CMP_NODE_OUTPUT_FILE */
|
||||
node->type_legacy = CMP_NODE_OUTPUT_FILE;
|
||||
|
||||
@@ -3713,7 +3713,7 @@ void blo_do_versions_300(FileData *fd, Library * /*lib*/, Main *bmain)
|
||||
}
|
||||
|
||||
if (node->storage) {
|
||||
NodeImageMultiFile *nimf = (NodeImageMultiFile *)node->storage;
|
||||
NodeCompositorFileOutput *nimf = (NodeCompositorFileOutput *)node->storage;
|
||||
version_fix_image_format_copy(bmain, &nimf->format);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,7 +308,7 @@ void blo_do_versions_430(FileData * /*fd*/, Library * /*lib*/, Main *bmain)
|
||||
}
|
||||
|
||||
/* Initialize node format color space if it is not set. */
|
||||
NodeImageMultiFile *storage = static_cast<NodeImageMultiFile *>(node->storage);
|
||||
NodeCompositorFileOutput *storage = static_cast<NodeCompositorFileOutput *>(node->storage);
|
||||
if (storage->format.linear_colorspace_settings.name[0] == '\0') {
|
||||
BKE_image_format_update_color_space_for_type(&storage->format);
|
||||
}
|
||||
|
||||
@@ -3201,8 +3201,8 @@ static void version_escape_curly_braces_in_compositor_file_output_nodes(bNodeTre
|
||||
continue;
|
||||
}
|
||||
|
||||
NodeImageMultiFile *node_data = static_cast<NodeImageMultiFile *>(node->storage);
|
||||
version_escape_curly_braces(node_data->base_path, FILE_MAX);
|
||||
NodeCompositorFileOutput *node_data = static_cast<NodeCompositorFileOutput *>(node->storage);
|
||||
version_escape_curly_braces(node_data->directory, FILE_MAX);
|
||||
|
||||
LISTBASE_FOREACH (bNodeSocket *, sock, &node->inputs) {
|
||||
NodeImageMultiFileSocket *socket_data = static_cast<NodeImageMultiFileSocket *>(
|
||||
|
||||
@@ -1390,6 +1390,59 @@ static void do_version_composite_node_in_scene_tree(bNodeTree &node_tree, bNode
|
||||
version_node_remove(node_tree, node);
|
||||
}
|
||||
|
||||
/* The file output node started using item accessors, so we need to free socket storage and copy
|
||||
* them to the new items members. Additionally, the base path was split into a directory and a file
|
||||
* name, so we need to split it. */
|
||||
static void do_version_file_output_node(bNode &node)
|
||||
{
|
||||
if (node.storage == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
NodeCompositorFileOutput *data = static_cast<NodeCompositorFileOutput *>(node.storage);
|
||||
|
||||
/* The directory previously stored both the directory and the file name. */
|
||||
char directory[FILE_MAX] = "";
|
||||
char file_name[FILE_MAX] = "";
|
||||
BLI_path_split_dir_file(data->directory, directory, FILE_MAX, file_name, FILE_MAX);
|
||||
BLI_strncpy(data->directory, directory, FILE_MAX);
|
||||
data->file_name = BLI_strdup_null(file_name);
|
||||
|
||||
data->items_count = BLI_listbase_count(&node.inputs);
|
||||
data->items = MEM_calloc_arrayN<NodeCompositorFileOutputItem>(data->items_count, __func__);
|
||||
int i = 0;
|
||||
LISTBASE_FOREACH_INDEX (bNodeSocket *, input, &node.inputs, i) {
|
||||
NodeImageMultiFileSocket *old_item_data = static_cast<NodeImageMultiFileSocket *>(
|
||||
input->storage);
|
||||
NodeCompositorFileOutputItem *item_data = &data->items[i];
|
||||
|
||||
item_data->identifier = i;
|
||||
BKE_image_format_copy(&item_data->format, &old_item_data->format);
|
||||
item_data->save_as_render = old_item_data->save_as_render;
|
||||
item_data->override_node_format = !bool(old_item_data->use_node_format);
|
||||
|
||||
item_data->socket_type = input->type;
|
||||
if (item_data->socket_type == SOCK_VECTOR) {
|
||||
item_data->vector_socket_dimensions =
|
||||
input->default_value_typed<bNodeSocketValueVector>()->dimensions;
|
||||
}
|
||||
|
||||
if (data->format.imtype == R_IMF_IMTYPE_MULTILAYER) {
|
||||
item_data->name = BLI_strdup(old_item_data->layer);
|
||||
}
|
||||
else {
|
||||
item_data->name = BLI_strdup(old_item_data->path);
|
||||
}
|
||||
|
||||
const std::string identifier = "Item_" + std::to_string(item_data->identifier);
|
||||
STRNCPY(input->identifier, identifier.c_str());
|
||||
|
||||
BKE_image_format_free(&old_item_data->format);
|
||||
MEM_freeN(old_item_data);
|
||||
input->storage = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/* Updates the media type of the given format to match its imtype. */
|
||||
static void update_format_media_type(ImageFormatData *format)
|
||||
{
|
||||
@@ -1915,7 +1968,7 @@ void blo_do_versions_500(FileData * /*fd*/, Library * /*lib*/, Main *bmain)
|
||||
continue;
|
||||
}
|
||||
|
||||
NodeImageMultiFile *storage = static_cast<NodeImageMultiFile *>(node->storage);
|
||||
NodeCompositorFileOutput *storage = static_cast<NodeCompositorFileOutput *>(node->storage);
|
||||
update_format_media_type(&storage->format);
|
||||
|
||||
LISTBASE_FOREACH (bNodeSocket *, input, &node->inputs) {
|
||||
@@ -2107,6 +2160,20 @@ void blo_do_versions_500(FileData * /*fd*/, Library * /*lib*/, Main *bmain)
|
||||
}
|
||||
}
|
||||
|
||||
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 500, 54)) {
|
||||
FOREACH_NODETREE_BEGIN (bmain, node_tree, id) {
|
||||
if (node_tree->type != NTREE_COMPOSIT) {
|
||||
continue;
|
||||
}
|
||||
LISTBASE_FOREACH (bNode *, node, &node_tree->nodes) {
|
||||
if (node->type_legacy == CMP_NODE_OUTPUT_FILE) {
|
||||
do_version_file_output_node(*node);
|
||||
}
|
||||
}
|
||||
FOREACH_NODETREE_END;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Always bump subversion in BKE_blender_version.h when adding versioning
|
||||
* code here, and wrap it inside a MAIN_VERSION_FILE_ATLEAST check.
|
||||
|
||||
@@ -1137,7 +1137,7 @@ static uiBut *ui_item_with_label(uiLayout *layout,
|
||||
/* We include PROP_NONE here because some plain string properties are used
|
||||
* as parts of paths. For example, the sub-paths in the compositor's File
|
||||
* Output node. */
|
||||
if (ELEM(subtype, PROP_FILEPATH, PROP_DIRPATH, PROP_NONE)) {
|
||||
if (ELEM(subtype, PROP_FILEPATH, PROP_DIRPATH, PROP_FILENAME, PROP_NONE)) {
|
||||
if ((RNA_property_flag(prop) & PROP_PATH_SUPPORTS_TEMPLATES) != 0) {
|
||||
const std::string path = RNA_property_string_get(ptr, prop);
|
||||
if (BKE_path_contains_template_syntax(path)) {
|
||||
|
||||
@@ -1186,7 +1186,7 @@ static std::unique_ptr<uiTooltipData> ui_tooltip_data_from_button_or_extra_icon(
|
||||
/* We include PROP_NONE here because some plain string properties are used
|
||||
* as parts of paths. For example, the sub-paths in the compositor's File
|
||||
* Output node. */
|
||||
if (ELEM(subtype, PROP_FILEPATH, PROP_DIRPATH, PROP_NONE)) {
|
||||
if (ELEM(subtype, PROP_FILEPATH, PROP_DIRPATH, PROP_FILENAME, PROP_NONE)) {
|
||||
/* Template parse errors, for paths that support it. */
|
||||
if ((RNA_property_flag(rnaprop) & PROP_PATH_SUPPORTS_TEMPLATES) != 0) {
|
||||
const std::string path = RNA_property_string_get(&but->rnapoin, rnaprop);
|
||||
|
||||
@@ -995,7 +995,11 @@ void uiTemplateImageSettings(uiLayout *layout,
|
||||
col->use_property_split_set(true);
|
||||
col->use_property_decorate_set(false);
|
||||
|
||||
col->prop(imfptr, "media_type", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
/* The file output node draws the media type itself. */
|
||||
const bool is_file_output = (id && GS(id->name) == ID_NT);
|
||||
if (!is_file_output) {
|
||||
col->prop(imfptr, "media_type", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
}
|
||||
|
||||
/* Multi layer images and video media types only have a single supported format,
|
||||
* so we needn't draw the format enum. */
|
||||
|
||||
@@ -1008,51 +1008,6 @@ static const SocketColorFn std_node_socket_color_funcs[] = {
|
||||
std_node_socket_color_fn<SOCK_CLOSURE>,
|
||||
};
|
||||
|
||||
/* draw function for file output node sockets,
|
||||
* displays only sub-path and format, no value button */
|
||||
static void node_file_output_socket_draw(bContext *C,
|
||||
uiLayout *layout,
|
||||
PointerRNA *ptr,
|
||||
PointerRNA *node_ptr)
|
||||
{
|
||||
bNodeTree *ntree = (bNodeTree *)ptr->owner_id;
|
||||
bNodeSocket *sock = (bNodeSocket *)ptr->data;
|
||||
uiLayout *row;
|
||||
PointerRNA inputptr;
|
||||
|
||||
row = &layout->row(false);
|
||||
|
||||
PointerRNA imfptr = RNA_pointer_get(node_ptr, "format");
|
||||
int imtype = RNA_enum_get(&imfptr, "file_format");
|
||||
|
||||
if (imtype == R_IMF_IMTYPE_MULTILAYER) {
|
||||
NodeImageMultiFileSocket *input = (NodeImageMultiFileSocket *)sock->storage;
|
||||
inputptr = RNA_pointer_create_discrete(&ntree->id, &RNA_NodeOutputFileSlotLayer, input);
|
||||
|
||||
row->label(input->layer, ICON_NONE);
|
||||
}
|
||||
else {
|
||||
NodeImageMultiFileSocket *input = (NodeImageMultiFileSocket *)sock->storage;
|
||||
uiBlock *block;
|
||||
inputptr = RNA_pointer_create_discrete(&ntree->id, &RNA_NodeOutputFileSlotFile, input);
|
||||
|
||||
row->label(input->path, ICON_NONE);
|
||||
|
||||
if (!RNA_boolean_get(&inputptr, "use_node_format")) {
|
||||
imfptr = RNA_pointer_get(&inputptr, "format");
|
||||
}
|
||||
|
||||
const char *imtype_name;
|
||||
PropertyRNA *imtype_prop = RNA_struct_find_property(&imfptr, "file_format");
|
||||
RNA_property_enum_name(
|
||||
C, &imfptr, imtype_prop, RNA_property_enum_get(&imfptr, imtype_prop), &imtype_name);
|
||||
block = row->block();
|
||||
UI_block_emboss_set(block, ui::EmbossType::Pulldown);
|
||||
row->label(imtype_name, ICON_NONE);
|
||||
UI_block_emboss_set(block, ui::EmbossType::None);
|
||||
}
|
||||
}
|
||||
|
||||
static bool socket_needs_attribute_search(bNode &node, bNodeSocket &socket)
|
||||
{
|
||||
const nodes::NodeDeclaration *node_decl = node.declaration();
|
||||
@@ -1151,12 +1106,6 @@ static void std_node_socket_draw(
|
||||
layout->active_set(false);
|
||||
}
|
||||
|
||||
/* XXX not nice, eventually give this node its own socket type ... */
|
||||
if (node->type_legacy == CMP_NODE_OUTPUT_FILE) {
|
||||
node_file_output_socket_draw(C, layout, ptr, node_ptr);
|
||||
return;
|
||||
}
|
||||
|
||||
const bool has_gizmo = tree->runtime->gizmo_propagation ?
|
||||
tree->runtime->gizmo_propagation->gizmo_endpoint_sockets.contains(
|
||||
sock) :
|
||||
|
||||
@@ -2102,194 +2102,6 @@ void NODE_OT_delete_reconnect(wmOperatorType *ot)
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Node File Output Add Socket Operator
|
||||
* \{ */
|
||||
|
||||
static wmOperatorStatus node_output_file_add_socket_exec(bContext *C, wmOperator *op)
|
||||
{
|
||||
Scene *scene = CTX_data_scene(C);
|
||||
SpaceNode *snode = CTX_wm_space_node(C);
|
||||
PointerRNA ptr = CTX_data_pointer_get(C, "node");
|
||||
bNodeTree *ntree = nullptr;
|
||||
bNode *node = nullptr;
|
||||
char file_path[MAX_NAME];
|
||||
|
||||
if (ptr.data) {
|
||||
node = (bNode *)ptr.data;
|
||||
ntree = (bNodeTree *)ptr.owner_id;
|
||||
}
|
||||
else if (snode && snode->edittree) {
|
||||
ntree = snode->edittree;
|
||||
node = bke::node_get_active(*snode->edittree);
|
||||
}
|
||||
|
||||
if (!node || node->type_legacy != CMP_NODE_OUTPUT_FILE) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
RNA_string_get(op->ptr, "file_path", file_path);
|
||||
|
||||
if (file_path[0] != '\0') {
|
||||
ntreeCompositOutputFileAddSocket(ntree, node, file_path, &scene->r.im_format);
|
||||
}
|
||||
else {
|
||||
ntreeCompositOutputFileAddSocket(ntree, node, DATA_("Image"), &scene->r.im_format);
|
||||
}
|
||||
|
||||
BKE_main_ensure_invariants(*CTX_data_main(C), snode->edittree->id);
|
||||
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
void NODE_OT_output_file_add_socket(wmOperatorType *ot)
|
||||
{
|
||||
/* identifiers */
|
||||
ot->name = "Add File Node Socket";
|
||||
ot->description = "Add a new input to a file output node";
|
||||
ot->idname = "NODE_OT_output_file_add_socket";
|
||||
|
||||
/* callbacks */
|
||||
ot->exec = node_output_file_add_socket_exec;
|
||||
ot->poll = composite_node_editable;
|
||||
|
||||
/* flags */
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||
|
||||
RNA_def_string(
|
||||
ot->srna, "file_path", nullptr, MAX_NAME, "File Path", "Subpath of the output file");
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Node Multi File Output Remove Socket Operator
|
||||
* \{ */
|
||||
|
||||
static wmOperatorStatus node_output_file_remove_active_socket_exec(bContext *C,
|
||||
wmOperator * /*op*/)
|
||||
{
|
||||
SpaceNode *snode = CTX_wm_space_node(C);
|
||||
PointerRNA ptr = CTX_data_pointer_get(C, "node");
|
||||
bNodeTree *ntree = nullptr;
|
||||
bNode *node = nullptr;
|
||||
|
||||
if (ptr.data) {
|
||||
node = (bNode *)ptr.data;
|
||||
ntree = (bNodeTree *)ptr.owner_id;
|
||||
}
|
||||
else if (snode && snode->edittree) {
|
||||
ntree = snode->edittree;
|
||||
node = bke::node_get_active(*snode->edittree);
|
||||
}
|
||||
|
||||
if (!node || node->type_legacy != CMP_NODE_OUTPUT_FILE) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
if (!ntreeCompositOutputFileRemoveActiveSocket(ntree, node)) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
BKE_main_ensure_invariants(*CTX_data_main(C), ntree->id);
|
||||
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
void NODE_OT_output_file_remove_active_socket(wmOperatorType *ot)
|
||||
{
|
||||
/* identifiers */
|
||||
ot->name = "Remove File Node Socket";
|
||||
ot->description = "Remove the active input from a file output node";
|
||||
ot->idname = "NODE_OT_output_file_remove_active_socket";
|
||||
|
||||
/* callbacks */
|
||||
ot->exec = node_output_file_remove_active_socket_exec;
|
||||
ot->poll = composite_node_editable;
|
||||
|
||||
/* flags */
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Node Multi File Output Move Socket Node
|
||||
* \{ */
|
||||
|
||||
static wmOperatorStatus node_output_file_move_active_socket_exec(bContext *C, wmOperator *op)
|
||||
{
|
||||
SpaceNode *snode = CTX_wm_space_node(C);
|
||||
PointerRNA ptr = CTX_data_pointer_get(C, "node");
|
||||
bNode *node = nullptr;
|
||||
|
||||
if (ptr.data) {
|
||||
node = (bNode *)ptr.data;
|
||||
}
|
||||
else if (snode && snode->edittree) {
|
||||
node = bke::node_get_active(*snode->edittree);
|
||||
}
|
||||
|
||||
if (!node || node->type_legacy != CMP_NODE_OUTPUT_FILE) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
NodeImageMultiFile *nimf = (NodeImageMultiFile *)node->storage;
|
||||
|
||||
bNodeSocket *sock = (bNodeSocket *)BLI_findlink(&node->inputs, nimf->active_input);
|
||||
if (!sock) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
int direction = RNA_enum_get(op->ptr, "direction");
|
||||
|
||||
if (direction == 1) {
|
||||
bNodeSocket *before = sock->prev;
|
||||
if (!before) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
BLI_remlink(&node->inputs, sock);
|
||||
BLI_insertlinkbefore(&node->inputs, before, sock);
|
||||
nimf->active_input--;
|
||||
}
|
||||
else {
|
||||
bNodeSocket *after = sock->next;
|
||||
if (!after) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
BLI_remlink(&node->inputs, sock);
|
||||
BLI_insertlinkafter(&node->inputs, after, sock);
|
||||
nimf->active_input++;
|
||||
}
|
||||
|
||||
BKE_ntree_update_tag_node_property(snode->edittree, node);
|
||||
BKE_main_ensure_invariants(*CTX_data_main(C), snode->edittree->id);
|
||||
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
void NODE_OT_output_file_move_active_socket(wmOperatorType *ot)
|
||||
{
|
||||
static const EnumPropertyItem direction_items[] = {
|
||||
{1, "UP", 0, "Up", ""}, {2, "DOWN", 0, "Down", ""}, {0, nullptr, 0, nullptr, nullptr}};
|
||||
|
||||
/* identifiers */
|
||||
ot->name = "Move File Node Socket";
|
||||
ot->description = "Move the active input of a file output node up or down the list";
|
||||
ot->idname = "NODE_OT_output_file_move_active_socket";
|
||||
|
||||
/* callbacks */
|
||||
ot->exec = node_output_file_move_active_socket_exec;
|
||||
ot->poll = composite_node_editable;
|
||||
|
||||
/* flags */
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||
|
||||
RNA_def_enum(ot->srna, "direction", direction_items, 2, "Direction", "");
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Node Copy Node Color Operator
|
||||
* \{ */
|
||||
|
||||
@@ -389,10 +389,6 @@ void NODE_OT_activate_viewer(wmOperatorType *ot);
|
||||
void NODE_OT_read_viewlayers(wmOperatorType *ot);
|
||||
void NODE_OT_render_changed(wmOperatorType *ot);
|
||||
|
||||
void NODE_OT_output_file_add_socket(wmOperatorType *ot);
|
||||
void NODE_OT_output_file_remove_active_socket(wmOperatorType *ot);
|
||||
void NODE_OT_output_file_move_active_socket(wmOperatorType *ot);
|
||||
|
||||
/**
|
||||
* \note clipboard_cut is a simple macro of copy + delete.
|
||||
*/
|
||||
|
||||
@@ -92,10 +92,6 @@ void node_operatortypes()
|
||||
WM_operatortype_append(NODE_OT_new_node_tree);
|
||||
WM_operatortype_append(NODE_OT_new_compositing_node_group);
|
||||
|
||||
WM_operatortype_append(NODE_OT_output_file_add_socket);
|
||||
WM_operatortype_append(NODE_OT_output_file_remove_active_socket);
|
||||
WM_operatortype_append(NODE_OT_output_file_move_active_socket);
|
||||
|
||||
WM_operatortype_append(NODE_OT_parent_set);
|
||||
WM_operatortype_append(NODE_OT_join);
|
||||
WM_operatortype_append(NODE_OT_attach);
|
||||
|
||||
@@ -1234,32 +1234,61 @@ typedef struct NodeImageFile {
|
||||
int sfra, efra;
|
||||
} NodeImageFile;
|
||||
|
||||
/**
|
||||
* XXX: first struct fields should match #NodeImageFile to ensure forward compatibility.
|
||||
*/
|
||||
typedef struct NodeImageMultiFile {
|
||||
char base_path[/*FILE_MAX*/ 1024];
|
||||
ImageFormatData format;
|
||||
/** XXX old frame rand values from NodeImageFile for forward compatibility. */
|
||||
int sfra DNA_DEPRECATED, efra DNA_DEPRECATED;
|
||||
/** Selected input in details view list. */
|
||||
int active_input;
|
||||
typedef struct NodeCompositorFileOutputItem {
|
||||
/* The unique identifier of the item used to construct the socket identifier. */
|
||||
int identifier;
|
||||
/* The type of socket for the item, which is limited to the types listed in the
|
||||
* FileOutputItemsAccessor::supports_socket_type. */
|
||||
int16_t socket_type;
|
||||
/* The number of dimensions in the vector socket if the socket type is vector, otherwise, it is
|
||||
* unused, */
|
||||
char vector_socket_dimensions;
|
||||
/* If true and the node is saving individual files, the format an save_as_render members of this
|
||||
* struct will be used, otherwise, the members of the NodeCompositorFileOutput struct will be
|
||||
* used for all items. */
|
||||
char override_node_format;
|
||||
/* Apply the render part of the display transform when saving non-linear images. Unused if
|
||||
* override_node_format is false or the node is saving multi-layer images. */
|
||||
char save_as_render;
|
||||
char _pad[3];
|
||||
} NodeImageMultiFile;
|
||||
char _pad[7];
|
||||
/* The unique name of the item. It is used as the file name when saving individual files and used
|
||||
* as the layer name when saving multi-layer images. */
|
||||
char *name;
|
||||
/* The image format to use when saving individual images and override_node_format is true. */
|
||||
ImageFormatData format;
|
||||
} NodeCompositorFileOutputItem;
|
||||
|
||||
typedef struct NodeCompositorFileOutput {
|
||||
char directory[/*FILE_MAX*/ 1024];
|
||||
/* The base name of the file. Can be nullptr. */
|
||||
char *file_name;
|
||||
/* The image format to use when saving the images. */
|
||||
ImageFormatData format;
|
||||
/* The file output images. They can represent individual images or layers depending on whether
|
||||
* multi-layer images are being saved. */
|
||||
NodeCompositorFileOutputItem *items;
|
||||
/* The number of file output items. */
|
||||
int items_count;
|
||||
/* The currently active file output item. */
|
||||
int active_item_index;
|
||||
/* Apply the render part of the display transform when saving non-linear images. */
|
||||
char save_as_render;
|
||||
char _pad[7];
|
||||
} NodeCompositorFileOutput;
|
||||
|
||||
typedef struct NodeImageMultiFileSocket {
|
||||
/* single layer file output */
|
||||
short use_render_format DNA_DEPRECATED;
|
||||
/** Use overall node image format. */
|
||||
short use_node_format;
|
||||
char save_as_render;
|
||||
short use_node_format DNA_DEPRECATED;
|
||||
char save_as_render DNA_DEPRECATED;
|
||||
char _pad1[3];
|
||||
char path[/*FILE_MAX*/ 1024];
|
||||
ImageFormatData format;
|
||||
char path[/*FILE_MAX*/ 1024] DNA_DEPRECATED;
|
||||
ImageFormatData format DNA_DEPRECATED;
|
||||
|
||||
/* Multi-layer output. */
|
||||
/** Subtract 2 because '.' and channel char are appended. */
|
||||
char layer[/*EXR_TOT_MAXNAME - 2*/ 62];
|
||||
char layer[/*EXR_TOT_MAXNAME - 2*/ 62] DNA_DEPRECATED;
|
||||
char _pad2[2];
|
||||
} NodeImageMultiFileSocket;
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
|
||||
DNA_STRUCT_RENAME(ActionChannelBag, ActionChannelbag)
|
||||
DNA_STRUCT_RENAME(Lamp, Light)
|
||||
DNA_STRUCT_RENAME(NodeImageMultiFile, NodeCompositorFileOutput)
|
||||
DNA_STRUCT_RENAME(SeqConnection, StripConnection)
|
||||
DNA_STRUCT_RENAME(SeqRetimingHandle, SeqRetimingKey)
|
||||
DNA_STRUCT_RENAME(Sequence, Strip)
|
||||
@@ -170,6 +171,8 @@ DNA_STRUCT_RENAME_MEMBER(MovieTrackingTrack, search_max, search_max_legacy)
|
||||
DNA_STRUCT_RENAME_MEMBER(MovieTrackingTrack, search_min, search_min_legacy)
|
||||
DNA_STRUCT_RENAME_MEMBER(NlaStrip, action_slot_name, last_slot_identifier)
|
||||
DNA_STRUCT_RENAME_MEMBER(NodeCryptomatte, num_inputs, inputs_num)
|
||||
DNA_STRUCT_RENAME_MEMBER(NodeCompositorFileOutput, base_path, directory)
|
||||
DNA_STRUCT_RENAME_MEMBER(NodeCompositorFileOutput, active_input, active_item_index)
|
||||
DNA_STRUCT_RENAME_MEMBER(NodeGeometryAttributeCapture, data_type, data_type_legacy)
|
||||
DNA_STRUCT_RENAME_MEMBER(NodesModifierData, simulation_bake_directory, bake_directory)
|
||||
DNA_STRUCT_RENAME_MEMBER(Object, col, color)
|
||||
|
||||
@@ -253,6 +253,7 @@ set(INC
|
||||
../../io/usd
|
||||
../../modifiers
|
||||
../../nodes
|
||||
../../nodes/composite/include
|
||||
../../nodes/function/include
|
||||
../../nodes/geometry/include
|
||||
../../sequencer
|
||||
|
||||
@@ -656,6 +656,7 @@ static const EnumPropertyItem node_cryptomatte_layer_name_items[] = {
|
||||
|
||||
# include "NOD_common.hh"
|
||||
# include "NOD_composite.hh"
|
||||
# include "NOD_compositor_file_output.hh"
|
||||
# include "NOD_fn_format_string.hh"
|
||||
# include "NOD_geo_bake.hh"
|
||||
# include "NOD_geo_bundle.hh"
|
||||
@@ -692,6 +693,7 @@ using blender::nodes::ClosureOutputItemsAccessor;
|
||||
using blender::nodes::CombineBundleItemsAccessor;
|
||||
using blender::nodes::EvaluateClosureInputItemsAccessor;
|
||||
using blender::nodes::EvaluateClosureOutputItemsAccessor;
|
||||
using blender::nodes::FileOutputItemsAccessor;
|
||||
using blender::nodes::ForeachGeometryElementGenerationItemsAccessor;
|
||||
using blender::nodes::ForeachGeometryElementInputItemsAccessor;
|
||||
using blender::nodes::ForeachGeometryElementMainItemsAccessor;
|
||||
@@ -3553,20 +3555,6 @@ static void rna_Image_Node_update_id(Main *bmain, Scene *scene, PointerRNA *ptr)
|
||||
rna_Node_update_relations(bmain, scene, ptr);
|
||||
}
|
||||
|
||||
static void rna_NodeOutputFile_slots_begin(CollectionPropertyIterator *iter, PointerRNA *ptr)
|
||||
{
|
||||
bNode *node = ptr->data_as<bNode>();
|
||||
rna_iterator_listbase_begin(iter, ptr, &node->inputs, nullptr);
|
||||
}
|
||||
|
||||
static PointerRNA rna_NodeOutputFile_slot_file_get(CollectionPropertyIterator *iter)
|
||||
{
|
||||
bNodeSocket *sock = static_cast<bNodeSocket *>(rna_iterator_listbase_get(iter));
|
||||
PointerRNA ptr = RNA_pointer_create_with_parent(
|
||||
iter->parent, &RNA_NodeOutputFileSlotFile, sock->storage);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* White Balance Node.
|
||||
*/
|
||||
@@ -3999,81 +3987,6 @@ static const EnumPropertyItem *rna_NodeGeometryCaptureAttributeItem_data_type_it
|
||||
|
||||
/* ******** Node Socket Types ******** */
|
||||
|
||||
static PointerRNA rna_NodeOutputFile_slot_layer_get(CollectionPropertyIterator *iter)
|
||||
{
|
||||
bNodeSocket *sock = static_cast<bNodeSocket *>(rna_iterator_listbase_get(iter));
|
||||
PointerRNA ptr = RNA_pointer_create_with_parent(
|
||||
iter->parent, &RNA_NodeOutputFileSlotLayer, sock->storage);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
static int rna_NodeOutputFileSocket_find_node(bNodeTree *ntree,
|
||||
NodeImageMultiFileSocket *data,
|
||||
bNode **nodep,
|
||||
bNodeSocket **sockp)
|
||||
{
|
||||
bNode *node;
|
||||
bNodeSocket *sock;
|
||||
|
||||
for (node = static_cast<bNode *>(ntree->nodes.first); node; node = node->next) {
|
||||
for (sock = static_cast<bNodeSocket *>(node->inputs.first); sock; sock = sock->next) {
|
||||
NodeImageMultiFileSocket *sockdata = static_cast<NodeImageMultiFileSocket *>(sock->storage);
|
||||
if (sockdata == data) {
|
||||
*nodep = node;
|
||||
*sockp = sock;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*nodep = nullptr;
|
||||
*sockp = nullptr;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void rna_NodeOutputFileSlotFile_path_set(PointerRNA *ptr, const char *value)
|
||||
{
|
||||
bNodeTree *ntree = reinterpret_cast<bNodeTree *>(ptr->owner_id);
|
||||
NodeImageMultiFileSocket *sockdata = static_cast<NodeImageMultiFileSocket *>(ptr->data);
|
||||
bNode *node;
|
||||
bNodeSocket *sock;
|
||||
|
||||
if (rna_NodeOutputFileSocket_find_node(ntree, sockdata, &node, &sock)) {
|
||||
ntreeCompositOutputFileSetPath(node, sock, value);
|
||||
}
|
||||
}
|
||||
|
||||
static void rna_NodeOutputFileSlotLayer_name_set(PointerRNA *ptr, const char *value)
|
||||
{
|
||||
bNodeTree *ntree = reinterpret_cast<bNodeTree *>(ptr->owner_id);
|
||||
NodeImageMultiFileSocket *sockdata = static_cast<NodeImageMultiFileSocket *>(ptr->data);
|
||||
bNode *node;
|
||||
bNodeSocket *sock;
|
||||
|
||||
if (rna_NodeOutputFileSocket_find_node(ntree, sockdata, &node, &sock)) {
|
||||
ntreeCompositOutputFileSetLayer(node, sock, value);
|
||||
}
|
||||
}
|
||||
|
||||
static bNodeSocket *rna_NodeOutputFile_slots_new(
|
||||
ID *id, bNode *node, bContext *C, ReportList * /*reports*/, const char *name)
|
||||
{
|
||||
bNodeTree *ntree = reinterpret_cast<bNodeTree *>(id);
|
||||
Scene *scene = CTX_data_scene(C);
|
||||
ImageFormatData *im_format = nullptr;
|
||||
bNodeSocket *sock;
|
||||
if (scene) {
|
||||
im_format = &scene->r.im_format;
|
||||
}
|
||||
|
||||
sock = ntreeCompositOutputFileAddSocket(ntree, node, name, im_format);
|
||||
|
||||
BKE_main_ensure_invariants(*CTX_data_main(C), ntree->id);
|
||||
WM_main_add_notifier(NC_NODE | NA_EDITED, ntree);
|
||||
|
||||
return sock;
|
||||
}
|
||||
|
||||
static void rna_FrameNode_label_size_update(Main *bmain, Scene *scene, PointerRNA *ptr)
|
||||
{
|
||||
BLF_cache_clear();
|
||||
@@ -4593,6 +4506,127 @@ static const EnumPropertyItem node_scatter_phase_items[] = {
|
||||
{0, nullptr, 0, nullptr, nullptr},
|
||||
};
|
||||
|
||||
static void rna_def_node_item_array_socket_item_common(
|
||||
StructRNA *srna,
|
||||
const char *accessor,
|
||||
const bool add_socket_type,
|
||||
const bool add_vector_socket_dimensions = false)
|
||||
{
|
||||
static blender::LinearAllocator<> allocator;
|
||||
PropertyRNA *prop;
|
||||
|
||||
char name_set_func[128];
|
||||
SNPRINTF(name_set_func, "rna_Node_ItemArray_item_name_set<%s>", accessor);
|
||||
|
||||
char item_update_func[128];
|
||||
SNPRINTF(item_update_func, "rna_Node_ItemArray_item_update<%s>", accessor);
|
||||
const char *item_update_func_ptr = allocator.copy_string(item_update_func).c_str();
|
||||
|
||||
char socket_type_itemf[128];
|
||||
SNPRINTF(socket_type_itemf, "rna_Node_ItemArray_socket_type_itemf<%s>", accessor);
|
||||
|
||||
char color_get_func[128];
|
||||
SNPRINTF(color_get_func, "rna_Node_ItemArray_item_color_get<%s>", accessor);
|
||||
|
||||
prop = RNA_def_property(srna, "name", PROP_STRING, PROP_NONE);
|
||||
RNA_def_property_string_funcs(
|
||||
prop, nullptr, nullptr, allocator.copy_string(name_set_func).c_str());
|
||||
RNA_def_property_ui_text(prop, "Name", "");
|
||||
RNA_def_struct_name_property(srna, prop);
|
||||
RNA_def_property_update(prop, NC_NODE | NA_EDITED, item_update_func_ptr);
|
||||
|
||||
if (add_socket_type) {
|
||||
prop = RNA_def_property(srna, "socket_type", PROP_ENUM, PROP_NONE);
|
||||
RNA_def_property_enum_items(prop, rna_enum_node_socket_data_type_items);
|
||||
RNA_def_property_enum_funcs(
|
||||
prop, nullptr, nullptr, allocator.copy_string(socket_type_itemf).c_str());
|
||||
RNA_def_property_ui_text(prop, "Socket Type", "");
|
||||
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
|
||||
RNA_def_property_update(prop, NC_NODE | NA_EDITED, item_update_func_ptr);
|
||||
|
||||
if (add_vector_socket_dimensions) {
|
||||
prop = RNA_def_property(srna, "vector_socket_dimensions", PROP_INT, PROP_NONE);
|
||||
RNA_def_property_int_sdna(prop, nullptr, "vector_socket_dimensions");
|
||||
RNA_def_property_range(prop, 2, 4);
|
||||
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
|
||||
RNA_def_property_ui_text(prop, "Dimensions", "Dimensions of the vector socket");
|
||||
RNA_def_property_update(prop, NC_NODE | NA_EDITED, item_update_func_ptr);
|
||||
}
|
||||
}
|
||||
|
||||
prop = RNA_def_property(srna, "color", PROP_FLOAT, PROP_COLOR_GAMMA);
|
||||
RNA_def_property_array(prop, 4);
|
||||
RNA_def_property_float_funcs(
|
||||
prop, allocator.copy_string(color_get_func).c_str(), nullptr, nullptr);
|
||||
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
|
||||
RNA_def_property_ui_text(
|
||||
prop, "Color", "Color of the corresponding socket type in the node editor");
|
||||
}
|
||||
|
||||
static void rna_def_node_item_array_common_functions(StructRNA *srna,
|
||||
const char *item_name,
|
||||
const char *accessor_name)
|
||||
{
|
||||
static blender::LinearAllocator<> allocator;
|
||||
PropertyRNA *parm;
|
||||
FunctionRNA *func;
|
||||
|
||||
char remove_call[128];
|
||||
SNPRINTF(remove_call, "rna_Node_ItemArray_remove<%s>", accessor_name);
|
||||
char clear_call[128];
|
||||
SNPRINTF(clear_call, "rna_Node_ItemArray_clear<%s>", accessor_name);
|
||||
char move_call[128];
|
||||
SNPRINTF(move_call, "rna_Node_ItemArray_move<%s>", accessor_name);
|
||||
|
||||
func = RNA_def_function(srna, "remove", allocator.copy_string(remove_call).c_str());
|
||||
RNA_def_function_ui_description(func, "Remove an item");
|
||||
RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN | FUNC_USE_REPORTS);
|
||||
parm = RNA_def_pointer(func, "item", item_name, "Item", "The item to remove");
|
||||
RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED);
|
||||
|
||||
func = RNA_def_function(srna, "clear", allocator.copy_string(clear_call).c_str());
|
||||
RNA_def_function_ui_description(func, "Remove all items");
|
||||
RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN);
|
||||
|
||||
func = RNA_def_function(srna, "move", allocator.copy_string(move_call).c_str());
|
||||
RNA_def_function_ui_description(func, "Move an item to another position");
|
||||
RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN);
|
||||
parm = RNA_def_int(
|
||||
func, "from_index", -1, 0, INT_MAX, "From Index", "Index of the item to move", 0, 10000);
|
||||
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
|
||||
parm = RNA_def_int(
|
||||
func, "to_index", -1, 0, INT_MAX, "To Index", "Target index for the item", 0, 10000);
|
||||
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
|
||||
}
|
||||
|
||||
static void rna_def_node_item_array_new_with_socket_and_name(StructRNA *srna,
|
||||
const char *item_name,
|
||||
const char *accessor_name)
|
||||
{
|
||||
static blender::LinearAllocator<> allocator;
|
||||
PropertyRNA *parm;
|
||||
FunctionRNA *func;
|
||||
|
||||
char name[128];
|
||||
SNPRINTF(name, "rna_Node_ItemArray_new_with_socket_and_name<%s>", accessor_name);
|
||||
|
||||
func = RNA_def_function(srna, "new", allocator.copy_string(name).c_str());
|
||||
RNA_def_function_ui_description(func, "Add an item at the end");
|
||||
RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN | FUNC_USE_REPORTS);
|
||||
parm = RNA_def_enum(func,
|
||||
"socket_type",
|
||||
rna_enum_node_socket_data_type_items,
|
||||
SOCK_GEOMETRY,
|
||||
"Socket Type",
|
||||
"Socket type of the item");
|
||||
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
|
||||
parm = RNA_def_string(func, "name", nullptr, MAX_NAME, "Name", "");
|
||||
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
|
||||
/* return value */
|
||||
parm = RNA_def_pointer(func, "item", item_name, "Item", "New item");
|
||||
RNA_def_function_return(func, parm);
|
||||
}
|
||||
|
||||
/* -- Common nodes ---------------------------------------------------------- */
|
||||
|
||||
static void def_group_input(BlenderRNA * /*brna*/, StructRNA * /*srna*/) {}
|
||||
@@ -6487,19 +6521,18 @@ static void def_cmp_render_layers(BlenderRNA * /*brna*/, StructRNA *srna)
|
||||
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_view_layer_update");
|
||||
}
|
||||
|
||||
static void rna_def_cmp_output_file_slot_file(BlenderRNA *brna)
|
||||
static void rna_def_cmp_file_output_item(BlenderRNA *brna)
|
||||
{
|
||||
StructRNA *srna;
|
||||
PropertyRNA *prop;
|
||||
StructRNA *srna = RNA_def_struct(brna, "NodeCompositorFileOutputItem", nullptr);
|
||||
RNA_def_struct_ui_text(srna, "File Output Item", "");
|
||||
|
||||
srna = RNA_def_struct(brna, "NodeOutputFileSlotFile", nullptr);
|
||||
RNA_def_struct_sdna(srna, "NodeImageMultiFileSocket");
|
||||
RNA_def_struct_ui_text(
|
||||
srna, "Output File Slot", "Single layer file slot of the file output node");
|
||||
rna_def_node_item_array_socket_item_common(srna, "FileOutputItemsAccessor", true, true);
|
||||
|
||||
prop = RNA_def_property(srna, "use_node_format", PROP_BOOLEAN, PROP_NONE);
|
||||
RNA_def_property_boolean_sdna(prop, nullptr, "use_node_format", 1);
|
||||
RNA_def_property_ui_text(prop, "Node Format", "");
|
||||
PropertyRNA *prop = RNA_def_property(srna, "override_node_format", PROP_BOOLEAN, PROP_NONE);
|
||||
RNA_def_property_boolean_sdna(prop, nullptr, "override_node_format", 1);
|
||||
RNA_def_property_ui_text(prop,
|
||||
"Override Node Format",
|
||||
"Use a different format instead of the node format for this file");
|
||||
RNA_def_property_update(prop, NC_NODE | NA_EDITED, nullptr);
|
||||
|
||||
prop = RNA_def_property(srna, "save_as_render", PROP_BOOLEAN, PROP_NONE);
|
||||
@@ -6510,99 +6543,58 @@ static void rna_def_cmp_output_file_slot_file(BlenderRNA *brna)
|
||||
|
||||
prop = RNA_def_property(srna, "format", PROP_POINTER, PROP_NONE);
|
||||
RNA_def_property_struct_type(prop, "ImageFormatSettings");
|
||||
|
||||
prop = RNA_def_property(srna, "path", PROP_STRING, PROP_NONE);
|
||||
RNA_def_property_string_sdna(prop, nullptr, "path");
|
||||
RNA_def_property_string_funcs(prop, nullptr, nullptr, "rna_NodeOutputFileSlotFile_path_set");
|
||||
RNA_def_struct_name_property(srna, prop);
|
||||
RNA_def_property_ui_text(prop, "Path", "Subpath used for this slot");
|
||||
RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_EDITOR_FILEBROWSER);
|
||||
RNA_def_property_flag(prop, PROP_PATH_OUTPUT | PROP_PATH_SUPPORTS_TEMPLATES);
|
||||
RNA_def_property_path_template_type(prop, PROP_VARIABLES_RENDER_OUTPUT);
|
||||
RNA_def_property_update(prop, NC_NODE | NA_EDITED, nullptr);
|
||||
}
|
||||
static void rna_def_cmp_output_file_slot_layer(BlenderRNA *brna)
|
||||
|
||||
static void rna_def_cmp_file_output_items(BlenderRNA *brna)
|
||||
{
|
||||
StructRNA *srna;
|
||||
PropertyRNA *prop;
|
||||
|
||||
srna = RNA_def_struct(brna, "NodeOutputFileSlotLayer", nullptr);
|
||||
RNA_def_struct_sdna(srna, "NodeImageMultiFileSocket");
|
||||
RNA_def_struct_ui_text(
|
||||
srna, "Output File Layer Slot", "Multilayer slot of the file output node");
|
||||
|
||||
prop = RNA_def_property(srna, "name", PROP_STRING, PROP_NONE);
|
||||
RNA_def_property_string_sdna(prop, nullptr, "layer");
|
||||
RNA_def_property_string_funcs(prop, nullptr, nullptr, "rna_NodeOutputFileSlotLayer_name_set");
|
||||
RNA_def_struct_name_property(srna, prop);
|
||||
RNA_def_property_ui_text(prop, "Name", "OpenEXR layer name used for this slot");
|
||||
RNA_def_property_update(prop, NC_NODE | NA_EDITED, nullptr);
|
||||
}
|
||||
static void rna_def_cmp_output_file_slots_api(BlenderRNA *brna,
|
||||
PropertyRNA *cprop,
|
||||
const char *struct_name)
|
||||
{
|
||||
StructRNA *srna;
|
||||
PropertyRNA *parm;
|
||||
FunctionRNA *func;
|
||||
|
||||
RNA_def_property_srna(cprop, struct_name);
|
||||
srna = RNA_def_struct(brna, struct_name, nullptr);
|
||||
StructRNA *srna = RNA_def_struct(brna, "NodeCompositorFileOutputItems", nullptr);
|
||||
RNA_def_struct_sdna(srna, "bNode");
|
||||
RNA_def_struct_ui_text(srna, "File Output Slots", "Collection of File Output node slots");
|
||||
RNA_def_struct_ui_text(srna, "Items", "Collection of file output items");
|
||||
|
||||
func = RNA_def_function(srna, "new", "rna_NodeOutputFile_slots_new");
|
||||
RNA_def_function_ui_description(func, "Add a file slot to this node");
|
||||
RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_REPORTS | FUNC_USE_CONTEXT);
|
||||
parm = RNA_def_string(func, "name", nullptr, MAX_NAME, "Name", "");
|
||||
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
|
||||
/* return value */
|
||||
parm = RNA_def_pointer(func, "socket", "NodeSocket", "", "New socket");
|
||||
RNA_def_function_return(func, parm);
|
||||
|
||||
/* NOTE: methods below can use the standard node socket API functions,
|
||||
* included here for completeness. */
|
||||
|
||||
func = RNA_def_function(srna, "remove", "rna_Node_socket_remove");
|
||||
RNA_def_function_ui_description(func, "Remove a file slot from this node");
|
||||
RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN | FUNC_USE_REPORTS);
|
||||
parm = RNA_def_pointer(func, "socket", "NodeSocket", "", "The socket to remove");
|
||||
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
|
||||
|
||||
func = RNA_def_function(srna, "clear", "rna_Node_inputs_clear");
|
||||
RNA_def_function_ui_description(func, "Remove all file slots from this node");
|
||||
RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN | FUNC_USE_REPORTS);
|
||||
|
||||
func = RNA_def_function(srna, "move", "rna_Node_inputs_move");
|
||||
RNA_def_function_ui_description(func, "Move a file slot to another position");
|
||||
RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN | FUNC_USE_REPORTS);
|
||||
parm = RNA_def_int(
|
||||
func, "from_index", -1, 0, INT_MAX, "From Index", "Index of the socket to move", 0, 10000);
|
||||
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
|
||||
parm = RNA_def_int(
|
||||
func, "to_index", -1, 0, INT_MAX, "To Index", "Target index for the socket", 0, 10000);
|
||||
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
|
||||
rna_def_node_item_array_new_with_socket_and_name(
|
||||
srna, "NodeCompositorFileOutputItem", "FileOutputItemsAccessor");
|
||||
rna_def_node_item_array_common_functions(
|
||||
srna, "NodeCompositorFileOutputItem", "FileOutputItemsAccessor");
|
||||
}
|
||||
static void def_cmp_output_file(BlenderRNA *brna, StructRNA *srna)
|
||||
|
||||
static void def_cmp_file_output(BlenderRNA *brna, StructRNA *srna)
|
||||
{
|
||||
PropertyRNA *prop;
|
||||
|
||||
rna_def_cmp_output_file_slot_file(brna);
|
||||
rna_def_cmp_output_file_slot_layer(brna);
|
||||
rna_def_cmp_file_output_item(brna);
|
||||
rna_def_cmp_file_output_items(brna);
|
||||
|
||||
RNA_def_struct_sdna_from(srna, "NodeImageMultiFile", "storage");
|
||||
RNA_def_struct_sdna_from(srna, "NodeCompositorFileOutput", "storage");
|
||||
|
||||
prop = RNA_def_property(srna, "base_path", PROP_STRING, PROP_FILEPATH);
|
||||
RNA_def_property_string_sdna(prop, nullptr, "base_path");
|
||||
RNA_def_property_ui_text(prop, "Base Path", "Base output path for the image");
|
||||
prop = RNA_def_property(srna, "file_output_items", PROP_COLLECTION, PROP_NONE);
|
||||
RNA_def_property_collection_sdna(prop, nullptr, "items", "items_count");
|
||||
RNA_def_property_struct_type(prop, "NodeCompositorFileOutputItem");
|
||||
RNA_def_property_ui_text(prop, "Items", "");
|
||||
RNA_def_property_srna(prop, "NodeCompositorFileOutputItems");
|
||||
|
||||
prop = RNA_def_property(srna, "active_item_index", PROP_INT, PROP_UNSIGNED);
|
||||
RNA_def_property_int_sdna(prop, nullptr, "active_item_index");
|
||||
RNA_def_property_ui_text(prop, "Active Item Index", "Index of the active item");
|
||||
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
|
||||
RNA_def_property_flag(prop, PROP_NO_DEG_UPDATE);
|
||||
RNA_def_property_update(prop, NC_NODE | NA_EDITED, nullptr);
|
||||
|
||||
prop = RNA_def_property(srna, "directory", PROP_STRING, PROP_DIRPATH);
|
||||
RNA_def_property_string_sdna(prop, nullptr, "directory");
|
||||
RNA_def_property_ui_text(prop, "Directory", "The directory where the image will be written");
|
||||
RNA_def_property_flag(
|
||||
prop, PROP_PATH_OUTPUT | PROP_PATH_SUPPORTS_BLEND_RELATIVE | PROP_PATH_SUPPORTS_TEMPLATES);
|
||||
RNA_def_property_path_template_type(prop, PROP_VARIABLES_RENDER_OUTPUT);
|
||||
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
|
||||
|
||||
prop = RNA_def_property(srna, "active_input_index", PROP_INT, PROP_NONE);
|
||||
RNA_def_property_int_sdna(prop, nullptr, "active_input");
|
||||
RNA_def_property_ui_text(prop, "Active Input Index", "Active input index in details view list");
|
||||
prop = RNA_def_property(srna, "file_name", PROP_STRING, PROP_FILENAME);
|
||||
RNA_def_property_string_sdna(prop, nullptr, "file_name");
|
||||
RNA_def_property_ui_text(prop,
|
||||
"File Name",
|
||||
"The base name of the file. Other information might be included in the "
|
||||
"final file name depending on the node options");
|
||||
RNA_def_property_flag(prop, PROP_PATH_OUTPUT | PROP_PATH_SUPPORTS_TEMPLATES);
|
||||
RNA_def_property_path_template_type(prop, PROP_VARIABLES_RENDER_OUTPUT);
|
||||
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
|
||||
|
||||
prop = RNA_def_property(srna, "format", PROP_POINTER, PROP_NONE);
|
||||
@@ -6613,38 +6605,6 @@ static void def_cmp_output_file(BlenderRNA *brna, StructRNA *srna)
|
||||
RNA_def_property_ui_text(
|
||||
prop, "Save as Render", "Apply render part of display transform when saving byte image");
|
||||
RNA_def_property_update(prop, NC_NODE | NA_EDITED, nullptr);
|
||||
|
||||
/* XXX using two different collections here for the same basic DNA list!
|
||||
* Details of the output slots depend on whether the node is in Multilayer EXR mode.
|
||||
*/
|
||||
|
||||
prop = RNA_def_property(srna, "file_slots", PROP_COLLECTION, PROP_NONE);
|
||||
RNA_def_property_collection_funcs(prop,
|
||||
"rna_NodeOutputFile_slots_begin",
|
||||
"rna_iterator_listbase_next",
|
||||
"rna_iterator_listbase_end",
|
||||
"rna_NodeOutputFile_slot_file_get",
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr);
|
||||
RNA_def_property_struct_type(prop, "NodeOutputFileSlotFile");
|
||||
RNA_def_property_ui_text(prop, "File Slots", "");
|
||||
rna_def_cmp_output_file_slots_api(brna, prop, "CompositorNodeOutputFileFileSlots");
|
||||
|
||||
prop = RNA_def_property(srna, "layer_slots", PROP_COLLECTION, PROP_NONE);
|
||||
RNA_def_property_collection_funcs(prop,
|
||||
"rna_NodeOutputFile_slots_begin",
|
||||
"rna_iterator_listbase_next",
|
||||
"rna_iterator_listbase_end",
|
||||
"rna_NodeOutputFile_slot_layer_get",
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr);
|
||||
RNA_def_property_struct_type(prop, "NodeOutputFileSlotLayer");
|
||||
RNA_def_property_ui_text(prop, "EXR Layer Slots", "");
|
||||
rna_def_cmp_output_file_slots_api(brna, prop, "CompositorNodeOutputFileLayerSlots");
|
||||
}
|
||||
|
||||
static void def_cmp_dilate_erode(BlenderRNA * /*brna*/, StructRNA *srna)
|
||||
@@ -8051,116 +8011,6 @@ static void def_closure_input(BlenderRNA *brna, StructRNA *srna)
|
||||
def_common_zone_input(brna, srna);
|
||||
}
|
||||
|
||||
static void rna_def_node_item_array_socket_item_common(StructRNA *srna,
|
||||
const char *accessor,
|
||||
const bool add_socket_type)
|
||||
{
|
||||
static blender::LinearAllocator<> allocator;
|
||||
PropertyRNA *prop;
|
||||
|
||||
char name_set_func[128];
|
||||
SNPRINTF(name_set_func, "rna_Node_ItemArray_item_name_set<%s>", accessor);
|
||||
|
||||
char item_update_func[128];
|
||||
SNPRINTF(item_update_func, "rna_Node_ItemArray_item_update<%s>", accessor);
|
||||
const char *item_update_func_ptr = allocator.copy_string(item_update_func).c_str();
|
||||
|
||||
char socket_type_itemf[128];
|
||||
SNPRINTF(socket_type_itemf, "rna_Node_ItemArray_socket_type_itemf<%s>", accessor);
|
||||
|
||||
char color_get_func[128];
|
||||
SNPRINTF(color_get_func, "rna_Node_ItemArray_item_color_get<%s>", accessor);
|
||||
|
||||
prop = RNA_def_property(srna, "name", PROP_STRING, PROP_NONE);
|
||||
RNA_def_property_string_funcs(
|
||||
prop, nullptr, nullptr, allocator.copy_string(name_set_func).c_str());
|
||||
RNA_def_property_ui_text(prop, "Name", "");
|
||||
RNA_def_struct_name_property(srna, prop);
|
||||
RNA_def_property_update(prop, NC_NODE | NA_EDITED, item_update_func_ptr);
|
||||
|
||||
if (add_socket_type) {
|
||||
prop = RNA_def_property(srna, "socket_type", PROP_ENUM, PROP_NONE);
|
||||
RNA_def_property_enum_items(prop, rna_enum_node_socket_data_type_items);
|
||||
RNA_def_property_enum_funcs(
|
||||
prop, nullptr, nullptr, allocator.copy_string(socket_type_itemf).c_str());
|
||||
RNA_def_property_ui_text(prop, "Socket Type", "");
|
||||
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
|
||||
RNA_def_property_update(prop, NC_NODE | NA_EDITED, item_update_func_ptr);
|
||||
}
|
||||
|
||||
prop = RNA_def_property(srna, "color", PROP_FLOAT, PROP_COLOR_GAMMA);
|
||||
RNA_def_property_array(prop, 4);
|
||||
RNA_def_property_float_funcs(
|
||||
prop, allocator.copy_string(color_get_func).c_str(), nullptr, nullptr);
|
||||
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
|
||||
RNA_def_property_ui_text(
|
||||
prop, "Color", "Color of the corresponding socket type in the node editor");
|
||||
}
|
||||
|
||||
static void rna_def_node_item_array_common_functions(StructRNA *srna,
|
||||
const char *item_name,
|
||||
const char *accessor_name)
|
||||
{
|
||||
static blender::LinearAllocator<> allocator;
|
||||
PropertyRNA *parm;
|
||||
FunctionRNA *func;
|
||||
|
||||
char remove_call[128];
|
||||
SNPRINTF(remove_call, "rna_Node_ItemArray_remove<%s>", accessor_name);
|
||||
char clear_call[128];
|
||||
SNPRINTF(clear_call, "rna_Node_ItemArray_clear<%s>", accessor_name);
|
||||
char move_call[128];
|
||||
SNPRINTF(move_call, "rna_Node_ItemArray_move<%s>", accessor_name);
|
||||
|
||||
func = RNA_def_function(srna, "remove", allocator.copy_string(remove_call).c_str());
|
||||
RNA_def_function_ui_description(func, "Remove an item");
|
||||
RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN | FUNC_USE_REPORTS);
|
||||
parm = RNA_def_pointer(func, "item", item_name, "Item", "The item to remove");
|
||||
RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED);
|
||||
|
||||
func = RNA_def_function(srna, "clear", allocator.copy_string(clear_call).c_str());
|
||||
RNA_def_function_ui_description(func, "Remove all items");
|
||||
RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN);
|
||||
|
||||
func = RNA_def_function(srna, "move", allocator.copy_string(move_call).c_str());
|
||||
RNA_def_function_ui_description(func, "Move an item to another position");
|
||||
RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN);
|
||||
parm = RNA_def_int(
|
||||
func, "from_index", -1, 0, INT_MAX, "From Index", "Index of the item to move", 0, 10000);
|
||||
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
|
||||
parm = RNA_def_int(
|
||||
func, "to_index", -1, 0, INT_MAX, "To Index", "Target index for the item", 0, 10000);
|
||||
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
|
||||
}
|
||||
|
||||
static void rna_def_node_item_array_new_with_socket_and_name(StructRNA *srna,
|
||||
const char *item_name,
|
||||
const char *accessor_name)
|
||||
{
|
||||
static blender::LinearAllocator<> allocator;
|
||||
PropertyRNA *parm;
|
||||
FunctionRNA *func;
|
||||
|
||||
char name[128];
|
||||
SNPRINTF(name, "rna_Node_ItemArray_new_with_socket_and_name<%s>", accessor_name);
|
||||
|
||||
func = RNA_def_function(srna, "new", allocator.copy_string(name).c_str());
|
||||
RNA_def_function_ui_description(func, "Add an item at the end");
|
||||
RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN | FUNC_USE_REPORTS);
|
||||
parm = RNA_def_enum(func,
|
||||
"socket_type",
|
||||
rna_enum_node_socket_data_type_items,
|
||||
SOCK_GEOMETRY,
|
||||
"Socket Type",
|
||||
"Socket type of the item");
|
||||
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
|
||||
parm = RNA_def_string(func, "name", nullptr, MAX_NAME, "Name", "");
|
||||
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
|
||||
/* return value */
|
||||
parm = RNA_def_pointer(func, "item", item_name, "Item", "New item");
|
||||
RNA_def_function_return(func, parm);
|
||||
}
|
||||
|
||||
static void rna_def_geo_simulation_state_item(BlenderRNA *brna)
|
||||
{
|
||||
PropertyRNA *prop;
|
||||
@@ -10722,7 +10572,7 @@ static void rna_def_nodes(BlenderRNA *brna)
|
||||
define("CompositorNode", "CompositorNodeMovieDistortion", def_cmp_moviedistortion);
|
||||
define("CompositorNode", "CompositorNodeNormal");
|
||||
define("CompositorNode", "CompositorNodeNormalize");
|
||||
define("CompositorNode", "CompositorNodeOutputFile", def_cmp_output_file);
|
||||
define("CompositorNode", "CompositorNodeOutputFile", def_cmp_file_output);
|
||||
define("CompositorNode", "CompositorNodePixelate");
|
||||
define("CompositorNode", "CompositorNodePlaneTrackDeform", def_cmp_planetrackdeform);
|
||||
define("CompositorNode", "CompositorNodePosterize");
|
||||
|
||||
@@ -733,6 +733,7 @@ static const EnumPropertyItem eevee_resolution_scale_items[] = {
|
||||
|
||||
# include <fmt/format.h>
|
||||
|
||||
# include "BLI_index_range.hh"
|
||||
# include "BLI_string_utils.hh"
|
||||
|
||||
# include "DNA_anim_types.h"
|
||||
@@ -780,6 +781,7 @@ static const EnumPropertyItem eevee_resolution_scale_items[] = {
|
||||
# include "BKE_unit.hh"
|
||||
|
||||
# include "NOD_composite.hh"
|
||||
# include "NOD_compositor_file_output.hh"
|
||||
|
||||
# include "ED_grease_pencil.hh"
|
||||
# include "ED_image.hh"
|
||||
@@ -813,6 +815,7 @@ static const EnumPropertyItem eevee_resolution_scale_items[] = {
|
||||
# include "ANIM_keyingsets.hh"
|
||||
|
||||
using blender::Vector;
|
||||
using blender::nodes::FileOutputItemsAccessor;
|
||||
|
||||
static int rna_ToolSettings_snap_mode_get(PointerRNA *ptr)
|
||||
{
|
||||
@@ -1335,24 +1338,26 @@ static std::optional<std::string> rna_ImageFormatSettings_path(
|
||||
|
||||
for (bNode *node : ntree->all_nodes()) {
|
||||
if (node->type_legacy == CMP_NODE_OUTPUT_FILE) {
|
||||
if (match(&((NodeImageMultiFile *)node->storage)->format)) {
|
||||
NodeCompositorFileOutput &storage = *static_cast<NodeCompositorFileOutput *>(
|
||||
node->storage);
|
||||
if (match(&storage.format)) {
|
||||
char node_name_esc[sizeof(node->name) * 2];
|
||||
BLI_str_escape(node_name_esc, node->name, sizeof(node_name_esc));
|
||||
return fmt::format("nodes[\"{}\"].format", node_name_esc);
|
||||
}
|
||||
else {
|
||||
LISTBASE_FOREACH (bNodeSocket *, socket, &node->inputs) {
|
||||
NodeImageMultiFileSocket *sockdata = static_cast<NodeImageMultiFileSocket *>(
|
||||
socket->storage);
|
||||
if (match(&sockdata->format)) {
|
||||
for (const int i : blender::IndexRange(storage.items_count)) {
|
||||
NodeCompositorFileOutputItem &item = storage.items[i];
|
||||
if (match(&item.format)) {
|
||||
char node_name_esc[sizeof(node->name) * 2];
|
||||
BLI_str_escape(node_name_esc, node->name, sizeof(node_name_esc));
|
||||
|
||||
char socketdata_path_esc[sizeof(sockdata->path) * 2];
|
||||
BLI_str_escape(socketdata_path_esc, sockdata->path, sizeof(socketdata_path_esc));
|
||||
|
||||
return fmt::format(
|
||||
"nodes[\"{}\"].file_slots[\"{}\"].format", node_name_esc, socketdata_path_esc);
|
||||
const std::string identifier = FileOutputItemsAccessor::socket_identifier_for_item(
|
||||
item);
|
||||
const std::string escaped_identifier = BLI_str_escape(identifier.c_str());
|
||||
return fmt::format("nodes[\"{}\"].file_output_items[\"{}\"].format",
|
||||
node_name_esc,
|
||||
escaped_identifier.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,24 +63,6 @@ void ntreeCompositUpdateRLayers(bNodeTree *ntree);
|
||||
|
||||
void ntreeCompositClearTags(bNodeTree *ntree);
|
||||
|
||||
bNodeSocket *ntreeCompositOutputFileAddSocket(bNodeTree *ntree,
|
||||
bNode *node,
|
||||
const char *name,
|
||||
const ImageFormatData *im_format);
|
||||
|
||||
int ntreeCompositOutputFileRemoveActiveSocket(bNodeTree *ntree, bNode *node);
|
||||
void ntreeCompositOutputFileSetPath(bNode *node, bNodeSocket *sock, const char *name);
|
||||
void ntreeCompositOutputFileSetLayer(bNode *node, bNodeSocket *sock, const char *name);
|
||||
/* needed in do_versions */
|
||||
void ntreeCompositOutputFileUniquePath(ListBase *list,
|
||||
bNodeSocket *sock,
|
||||
const char defname[],
|
||||
char delim);
|
||||
void ntreeCompositOutputFileUniqueLayer(ListBase *list,
|
||||
bNodeSocket *sock,
|
||||
const char defname[],
|
||||
char delim);
|
||||
|
||||
void ntreeCompositCryptomatteSyncFromAdd(bNode *node);
|
||||
void ntreeCompositCryptomatteSyncFromRemove(bNode *node);
|
||||
bNodeSocket *ntreeCompositCryptomatteAddSocket(bNodeTree *ntree, bNode *node);
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
* #RepeatItemsAccessor and to implement the same methods.
|
||||
*/
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "BLI_string.h"
|
||||
#include "BLI_string_utils.hh"
|
||||
|
||||
@@ -34,6 +36,7 @@ struct SocketItemsAccessorDefaults {
|
||||
static constexpr bool has_single_identifier_str = true;
|
||||
static constexpr bool has_name_validation = false;
|
||||
static constexpr bool has_custom_initial_name = false;
|
||||
static constexpr bool has_vector_dimensions = false;
|
||||
static constexpr char unique_name_separator = '.';
|
||||
};
|
||||
|
||||
@@ -189,17 +192,30 @@ template<typename Accessor> inline typename Accessor::ItemT &add_item_to_array(b
|
||||
} // namespace detail
|
||||
|
||||
/**
|
||||
* Add a new item at the end with the given socket type and name.
|
||||
* Add a new item at the end with the given socket type and name. The optional dimensions argument
|
||||
* can be provided for types that support multiple possible dimensions like Vector. It is expected
|
||||
* to be in the range [2, 4] and if not provided, 3 should be assumed.
|
||||
*/
|
||||
template<typename Accessor>
|
||||
inline typename Accessor::ItemT *add_item_with_socket_type_and_name(
|
||||
bNodeTree &ntree, bNode &node, const eNodeSocketDatatype socket_type, const char *name)
|
||||
bNodeTree &ntree,
|
||||
bNode &node,
|
||||
const eNodeSocketDatatype socket_type,
|
||||
const char *name,
|
||||
std::optional<int> dimensions = std::nullopt)
|
||||
{
|
||||
using ItemT = typename Accessor::ItemT;
|
||||
BLI_assert(Accessor::supports_socket_type(socket_type, ntree.type));
|
||||
BLI_assert(!(dimensions.has_value() && socket_type != SOCK_VECTOR));
|
||||
BLI_assert(ELEM(dimensions.value_or(3), 2, 3, 4));
|
||||
UNUSED_VARS_NDEBUG(ntree);
|
||||
ItemT &new_item = detail::add_item_to_array<Accessor>(node);
|
||||
Accessor::init_with_socket_type_and_name(node, new_item, socket_type, name);
|
||||
if constexpr (Accessor::has_vector_dimensions) {
|
||||
Accessor::init_with_socket_type_and_name(node, new_item, socket_type, name, dimensions);
|
||||
}
|
||||
else {
|
||||
Accessor::init_with_socket_type_and_name(node, new_item, socket_type, name);
|
||||
}
|
||||
return &new_item;
|
||||
}
|
||||
|
||||
@@ -275,8 +291,12 @@ template<typename Accessor>
|
||||
if constexpr (Accessor::has_custom_initial_name) {
|
||||
name = Accessor::custom_initial_name(storage_node, name);
|
||||
}
|
||||
std::optional<int> dimensions = std::nullopt;
|
||||
if (socket_type == SOCK_VECTOR) {
|
||||
dimensions = src_socket->default_value_typed<bNodeSocketValueVector>()->dimensions;
|
||||
}
|
||||
item = add_item_with_socket_type_and_name<Accessor>(
|
||||
ntree, storage_node, socket_type, name.c_str());
|
||||
ntree, storage_node, socket_type, name.c_str(), dimensions);
|
||||
}
|
||||
else if constexpr (Accessor::has_name && !Accessor::has_type) {
|
||||
item = add_item_with_name<Accessor>(storage_node, src_socket->name);
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
set(INC
|
||||
.
|
||||
..
|
||||
include
|
||||
../intern
|
||||
../../editors/include
|
||||
../../compositor
|
||||
@@ -108,6 +109,7 @@ set(SRC
|
||||
node_composite_tree.cc
|
||||
node_composite_util.cc
|
||||
|
||||
include/NOD_compositor_file_output.hh
|
||||
node_composite_util.hh
|
||||
)
|
||||
|
||||
@@ -115,6 +117,7 @@ set(LIB
|
||||
PRIVATE bf::blenkernel
|
||||
PRIVATE bf::blenlib
|
||||
PRIVATE bf::blentranslation
|
||||
PRIVATE bf::blenloader
|
||||
PRIVATE bf::depsgraph
|
||||
PRIVATE bf::dna
|
||||
PRIVATE bf::functions
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
#include "BLI_math_base.hh"
|
||||
#include "BLI_string.h"
|
||||
#include "BLI_string_ref.hh"
|
||||
#include "BLI_utildefines.h"
|
||||
|
||||
#include "DNA_node_types.h"
|
||||
|
||||
#include "BKE_image_format.hh"
|
||||
|
||||
#include "NOD_socket_items.hh"
|
||||
|
||||
namespace blender::nodes {
|
||||
|
||||
struct FileOutputItemsAccessor : public socket_items::SocketItemsAccessorDefaults {
|
||||
using ItemT = NodeCompositorFileOutputItem;
|
||||
static StructRNA *item_srna;
|
||||
static constexpr StringRefNull node_idname = "CompositorNodeOutputFile";
|
||||
static constexpr bool has_type = true;
|
||||
static constexpr bool has_name = true;
|
||||
static constexpr bool has_name_validation = true;
|
||||
static constexpr bool has_vector_dimensions = true;
|
||||
static constexpr char unique_name_separator = '_';
|
||||
struct operator_idnames {
|
||||
static constexpr StringRefNull add_item = "NODE_OT_file_output_item_add";
|
||||
static constexpr StringRefNull remove_item = "NODE_OT_file_output_item_remove";
|
||||
static constexpr StringRefNull move_item = "NODE_OT_file_output_item_move";
|
||||
};
|
||||
struct ui_idnames {
|
||||
static constexpr StringRefNull list = "DATA_UL_file_output_items";
|
||||
};
|
||||
struct rna_names {
|
||||
static constexpr StringRefNull items = "file_output_items";
|
||||
static constexpr StringRefNull active_index = "active_item_index";
|
||||
};
|
||||
|
||||
static socket_items::SocketItemsRef<NodeCompositorFileOutputItem> get_items_from_node(
|
||||
bNode &node)
|
||||
{
|
||||
auto *storage = static_cast<NodeCompositorFileOutput *>(node.storage);
|
||||
return {&storage->items, &storage->items_count, &storage->active_item_index};
|
||||
}
|
||||
|
||||
static void copy_item(const NodeCompositorFileOutputItem &source,
|
||||
NodeCompositorFileOutputItem &destination)
|
||||
{
|
||||
destination = source;
|
||||
destination.name = BLI_strdup_null(destination.name);
|
||||
BKE_image_format_copy(&destination.format, &source.format);
|
||||
}
|
||||
|
||||
static void destruct_item(NodeCompositorFileOutputItem *item)
|
||||
{
|
||||
MEM_SAFE_FREE(item->name);
|
||||
BKE_image_format_free(&item->format);
|
||||
}
|
||||
|
||||
static void blend_write_item(BlendWriter *writer, const ItemT &item);
|
||||
static void blend_read_data_item(BlendDataReader *reader, ItemT &item);
|
||||
|
||||
static eNodeSocketDatatype get_socket_type(const NodeCompositorFileOutputItem &item)
|
||||
{
|
||||
return eNodeSocketDatatype(item.socket_type);
|
||||
}
|
||||
|
||||
static char **get_name(NodeCompositorFileOutputItem &item)
|
||||
{
|
||||
return &item.name;
|
||||
}
|
||||
|
||||
static bool supports_socket_type(const eNodeSocketDatatype socket_type, const int /*ntree_type*/)
|
||||
{
|
||||
return ELEM(socket_type, SOCK_FLOAT, SOCK_VECTOR, SOCK_RGBA);
|
||||
}
|
||||
|
||||
static int find_available_identifier(const NodeCompositorFileOutputItem *items,
|
||||
const int items_count)
|
||||
{
|
||||
if (items_count == 0) {
|
||||
return 0;
|
||||
}
|
||||
int max_identifier = items[0].identifier;
|
||||
for (int i = 0; i < items_count; i++) {
|
||||
max_identifier = math::max(items[i].identifier, max_identifier);
|
||||
}
|
||||
return max_identifier + 1;
|
||||
}
|
||||
|
||||
static void init_with_socket_type_and_name(bNode &node,
|
||||
NodeCompositorFileOutputItem &item,
|
||||
const eNodeSocketDatatype socket_type,
|
||||
const char *name,
|
||||
std::optional<int> dimensions = std::nullopt)
|
||||
{
|
||||
auto *storage = static_cast<NodeCompositorFileOutput *>(node.storage);
|
||||
item.identifier = FileOutputItemsAccessor::find_available_identifier(storage->items,
|
||||
storage->items_count);
|
||||
|
||||
item.socket_type = socket_type;
|
||||
item.vector_socket_dimensions = dimensions.value_or(3);
|
||||
socket_items::set_item_name_and_make_unique<FileOutputItemsAccessor>(node, item, name);
|
||||
|
||||
item.save_as_render = true;
|
||||
BKE_image_format_init(&item.format, false);
|
||||
BKE_image_format_update_color_space_for_type(&item.format);
|
||||
}
|
||||
|
||||
static std::string validate_name(const StringRef name);
|
||||
|
||||
static std::string socket_identifier_for_item(const NodeCompositorFileOutputItem &item)
|
||||
{
|
||||
return "Item_" + std::to_string(item.identifier);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace blender::nodes
|
||||
@@ -13,13 +13,8 @@
|
||||
#include "BLI_generic_pointer.hh"
|
||||
#include "BLI_index_range.hh"
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLI_math_vector.h"
|
||||
#include "BLI_path_utils.hh"
|
||||
#include "BLI_string.h"
|
||||
#include "BLI_string_utf8.h"
|
||||
#include "BLI_string_utils.hh"
|
||||
#include "BLI_task.hh"
|
||||
#include "BLI_utildefines.h"
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
@@ -28,12 +23,13 @@
|
||||
#include "DNA_node_types.h"
|
||||
#include "DNA_scene_types.h"
|
||||
|
||||
#include "BLO_read_write.hh"
|
||||
|
||||
#include "BKE_context.hh"
|
||||
#include "BKE_cryptomatte.hh"
|
||||
#include "BKE_image.hh"
|
||||
#include "BKE_image_format.hh"
|
||||
#include "BKE_main.hh"
|
||||
#include "BKE_node_tree_update.hh"
|
||||
#include "BKE_scene.hh"
|
||||
|
||||
#include "RNA_access.hh"
|
||||
@@ -52,463 +48,338 @@
|
||||
#include "COM_node_operation.hh"
|
||||
#include "COM_utilities.hh"
|
||||
|
||||
#include "NOD_compositor_file_output.hh"
|
||||
#include "NOD_socket_items_blend.hh"
|
||||
#include "NOD_socket_items_ops.hh"
|
||||
#include "NOD_socket_items_ui.hh"
|
||||
#include "NOD_socket_search_link.hh"
|
||||
|
||||
#include "node_composite_util.hh"
|
||||
|
||||
namespace path_templates = blender::bke::path_templates;
|
||||
|
||||
/* **************** OUTPUT FILE ******************** */
|
||||
|
||||
/* find unique path */
|
||||
static bool unique_path_unique_check(ListBase *lb,
|
||||
bNodeSocket *sock,
|
||||
const blender::StringRef name)
|
||||
{
|
||||
LISTBASE_FOREACH (bNodeSocket *, sock_iter, lb) {
|
||||
if (sock_iter != sock) {
|
||||
NodeImageMultiFileSocket *sockdata = (NodeImageMultiFileSocket *)sock_iter->storage;
|
||||
if (sockdata->path == name) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
void ntreeCompositOutputFileUniquePath(ListBase *list,
|
||||
bNodeSocket *sock,
|
||||
const char defname[],
|
||||
char delim)
|
||||
{
|
||||
/* See if we are given an empty string */
|
||||
if (ELEM(nullptr, sock, defname)) {
|
||||
return;
|
||||
}
|
||||
NodeImageMultiFileSocket *sockdata = (NodeImageMultiFileSocket *)sock->storage;
|
||||
BLI_uniquename_cb(
|
||||
[&](const blender::StringRef check_name) {
|
||||
return unique_path_unique_check(list, sock, check_name);
|
||||
},
|
||||
defname,
|
||||
delim,
|
||||
sockdata->path,
|
||||
sizeof(sockdata->path));
|
||||
}
|
||||
|
||||
/* find unique EXR layer */
|
||||
static bool unique_layer_unique_check(ListBase *lb,
|
||||
bNodeSocket *sock,
|
||||
const blender::StringRef name)
|
||||
{
|
||||
LISTBASE_FOREACH (bNodeSocket *, sock_iter, lb) {
|
||||
if (sock_iter != sock) {
|
||||
NodeImageMultiFileSocket *sockdata = (NodeImageMultiFileSocket *)sock_iter->storage;
|
||||
if (sockdata->layer == name) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
void ntreeCompositOutputFileUniqueLayer(ListBase *list,
|
||||
bNodeSocket *sock,
|
||||
const char defname[],
|
||||
char delim)
|
||||
{
|
||||
/* See if we are given an empty string */
|
||||
if (ELEM(nullptr, sock, defname)) {
|
||||
return;
|
||||
}
|
||||
NodeImageMultiFileSocket *sockdata = (NodeImageMultiFileSocket *)sock->storage;
|
||||
BLI_uniquename_cb(
|
||||
[&](const blender::StringRef check_name) {
|
||||
return unique_layer_unique_check(list, sock, check_name);
|
||||
},
|
||||
defname,
|
||||
delim,
|
||||
sockdata->layer,
|
||||
sizeof(sockdata->layer));
|
||||
}
|
||||
|
||||
bNodeSocket *ntreeCompositOutputFileAddSocket(bNodeTree *ntree,
|
||||
bNode *node,
|
||||
const char *name,
|
||||
const ImageFormatData *im_format)
|
||||
{
|
||||
NodeImageMultiFile *nimf = (NodeImageMultiFile *)node->storage;
|
||||
bNodeSocket *sock = blender::bke::node_add_static_socket(
|
||||
*ntree, *node, SOCK_IN, SOCK_RGBA, PROP_NONE, "", name);
|
||||
|
||||
/* create format data for the input socket */
|
||||
NodeImageMultiFileSocket *sockdata = MEM_callocN<NodeImageMultiFileSocket>(__func__);
|
||||
sock->storage = sockdata;
|
||||
|
||||
STRNCPY_UTF8(sockdata->path, name);
|
||||
ntreeCompositOutputFileUniquePath(&node->inputs, sock, name, '_');
|
||||
STRNCPY_UTF8(sockdata->layer, name);
|
||||
ntreeCompositOutputFileUniqueLayer(&node->inputs, sock, name, '_');
|
||||
|
||||
if (im_format) {
|
||||
BKE_image_format_copy(&sockdata->format, im_format);
|
||||
sockdata->format.color_management = R_IMF_COLOR_MANAGEMENT_FOLLOW_SCENE;
|
||||
if (BKE_imtype_is_movie(sockdata->format.imtype)) {
|
||||
sockdata->format.imtype = R_IMF_IMTYPE_OPENEXR;
|
||||
}
|
||||
}
|
||||
else {
|
||||
BKE_image_format_init(&sockdata->format, false);
|
||||
}
|
||||
BKE_image_format_update_color_space_for_type(&sockdata->format);
|
||||
|
||||
/* use node data format by default */
|
||||
sockdata->use_node_format = true;
|
||||
sockdata->save_as_render = true;
|
||||
|
||||
nimf->active_input = BLI_findindex(&node->inputs, sock);
|
||||
|
||||
return sock;
|
||||
}
|
||||
|
||||
int ntreeCompositOutputFileRemoveActiveSocket(bNodeTree *ntree, bNode *node)
|
||||
{
|
||||
NodeImageMultiFile *nimf = (NodeImageMultiFile *)node->storage;
|
||||
bNodeSocket *sock = (bNodeSocket *)BLI_findlink(&node->inputs, nimf->active_input);
|
||||
int totinputs = BLI_listbase_count(&node->inputs);
|
||||
|
||||
if (!sock) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (nimf->active_input == totinputs - 1) {
|
||||
--nimf->active_input;
|
||||
}
|
||||
|
||||
/* free format data */
|
||||
MEM_freeN(reinterpret_cast<NodeImageMultiFileSocket *>(sock->storage));
|
||||
|
||||
blender::bke::node_remove_socket(*ntree, *node, *sock);
|
||||
return 1;
|
||||
}
|
||||
|
||||
void ntreeCompositOutputFileSetPath(bNode *node, bNodeSocket *sock, const char *name)
|
||||
{
|
||||
NodeImageMultiFileSocket *sockdata = (NodeImageMultiFileSocket *)sock->storage;
|
||||
STRNCPY_UTF8(sockdata->path, name);
|
||||
ntreeCompositOutputFileUniquePath(&node->inputs, sock, name, '_');
|
||||
}
|
||||
|
||||
void ntreeCompositOutputFileSetLayer(bNode *node, bNodeSocket *sock, const char *name)
|
||||
{
|
||||
NodeImageMultiFileSocket *sockdata = (NodeImageMultiFileSocket *)sock->storage;
|
||||
STRNCPY_UTF8(sockdata->layer, name);
|
||||
ntreeCompositOutputFileUniqueLayer(&node->inputs, sock, name, '_');
|
||||
}
|
||||
|
||||
namespace blender::nodes::node_composite_file_output_cc {
|
||||
|
||||
NODE_STORAGE_FUNCS(NodeImageMultiFile)
|
||||
NODE_STORAGE_FUNCS(NodeCompositorFileOutput)
|
||||
|
||||
/* XXX uses initfunc_api callback, regular initfunc does not support context yet */
|
||||
static void init_output_file(const bContext *C, PointerRNA *ptr)
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
Scene *scene = CTX_data_scene(C);
|
||||
bNodeTree *ntree = (bNodeTree *)ptr->owner_id;
|
||||
bNode *node = (bNode *)ptr->data;
|
||||
NodeImageMultiFile *nimf = MEM_callocN<NodeImageMultiFile>(__func__);
|
||||
nimf->save_as_render = true;
|
||||
ImageFormatData *format = nullptr;
|
||||
node->storage = nimf;
|
||||
b.use_custom_socket_order();
|
||||
b.allow_any_socket_order();
|
||||
|
||||
b.add_default_layout();
|
||||
|
||||
const bNodeTree *node_tree = b.tree_or_null();
|
||||
const bNode *node = b.node_or_null();
|
||||
if (!node_tree || !node) {
|
||||
return;
|
||||
}
|
||||
|
||||
const NodeCompositorFileOutput &storage = node_storage(*node);
|
||||
|
||||
/* Inputs for multi-layer files need to be the same size, while they can be different for
|
||||
* individual file outputs. */
|
||||
const bool is_multi_layer = storage.format.imtype == R_IMF_IMTYPE_MULTILAYER;
|
||||
const CompositorInputRealizationMode realization_mode =
|
||||
is_multi_layer ? CompositorInputRealizationMode::OperationDomain :
|
||||
CompositorInputRealizationMode::Transforms;
|
||||
|
||||
for (const int i : IndexRange(storage.items_count)) {
|
||||
const NodeCompositorFileOutputItem &item = storage.items[i];
|
||||
const eNodeSocketDatatype socket_type = eNodeSocketDatatype(item.socket_type);
|
||||
const std::string identifier = FileOutputItemsAccessor::socket_identifier_for_item(item);
|
||||
BaseSocketDeclarationBuilder *declaration = nullptr;
|
||||
if (socket_type == SOCK_VECTOR) {
|
||||
declaration = &b.add_input<decl::Vector>(item.name, identifier)
|
||||
.dimensions(item.vector_socket_dimensions);
|
||||
}
|
||||
else {
|
||||
declaration = &b.add_input(socket_type, item.name, identifier);
|
||||
}
|
||||
declaration->structure_type(StructureType::Dynamic)
|
||||
.compositor_realization_mode(realization_mode)
|
||||
.socket_name_ptr(&node_tree->id, FileOutputItemsAccessor::item_srna, &item, "name");
|
||||
}
|
||||
|
||||
b.add_input<decl::Extend>("", "__extend__");
|
||||
}
|
||||
|
||||
static void node_init(const bContext *C, PointerRNA *node_pointer)
|
||||
{
|
||||
bNode *node = node_pointer->data_as<bNode>();
|
||||
NodeCompositorFileOutput *data = MEM_callocN<NodeCompositorFileOutput>(__func__);
|
||||
node->storage = data;
|
||||
data->save_as_render = true;
|
||||
data->file_name = BLI_strdup("file_name");
|
||||
|
||||
BKE_image_format_init(&data->format, false);
|
||||
BKE_image_format_media_type_set(
|
||||
&data->format, node_pointer->owner_id, MEDIA_TYPE_MULTI_LAYER_IMAGE);
|
||||
BKE_image_format_update_color_space_for_type(&data->format);
|
||||
|
||||
Scene *scene = CTX_data_scene(C);
|
||||
if (scene) {
|
||||
RenderData *rd = &scene->r;
|
||||
|
||||
STRNCPY(nimf->base_path, rd->pic);
|
||||
BKE_image_format_copy(&nimf->format, &rd->im_format);
|
||||
nimf->format.color_management = R_IMF_COLOR_MANAGEMENT_FOLLOW_SCENE;
|
||||
if (BKE_imtype_is_movie(nimf->format.imtype)) {
|
||||
nimf->format.imtype = R_IMF_IMTYPE_OPENEXR;
|
||||
}
|
||||
|
||||
format = &nimf->format;
|
||||
}
|
||||
else {
|
||||
BKE_image_format_init(&nimf->format, false);
|
||||
}
|
||||
BKE_image_format_update_color_space_for_type(&nimf->format);
|
||||
|
||||
/* add one socket by default */
|
||||
ntreeCompositOutputFileAddSocket(ntree, node, DATA_("Image"), format);
|
||||
}
|
||||
|
||||
static void free_output_file(bNode *node)
|
||||
{
|
||||
/* free storage data in sockets */
|
||||
LISTBASE_FOREACH (bNodeSocket *, sock, &node->inputs) {
|
||||
NodeImageMultiFileSocket *sockdata = (NodeImageMultiFileSocket *)sock->storage;
|
||||
BKE_image_format_free(&sockdata->format);
|
||||
MEM_freeN(sockdata);
|
||||
}
|
||||
|
||||
NodeImageMultiFile *nimf = (NodeImageMultiFile *)node->storage;
|
||||
BKE_image_format_free(&nimf->format);
|
||||
MEM_freeN(nimf);
|
||||
}
|
||||
|
||||
static void copy_output_file(bNodeTree * /*dst_ntree*/, bNode *dest_node, const bNode *src_node)
|
||||
{
|
||||
bNodeSocket *src_sock, *dest_sock;
|
||||
|
||||
dest_node->storage = MEM_dupallocN(src_node->storage);
|
||||
NodeImageMultiFile *dest_nimf = (NodeImageMultiFile *)dest_node->storage;
|
||||
NodeImageMultiFile *src_nimf = (NodeImageMultiFile *)src_node->storage;
|
||||
BKE_image_format_copy(&dest_nimf->format, &src_nimf->format);
|
||||
|
||||
/* duplicate storage data in sockets */
|
||||
for (src_sock = (bNodeSocket *)src_node->inputs.first,
|
||||
dest_sock = (bNodeSocket *)dest_node->inputs.first;
|
||||
src_sock && dest_sock;
|
||||
src_sock = src_sock->next, dest_sock = (bNodeSocket *)dest_sock->next)
|
||||
{
|
||||
dest_sock->storage = MEM_dupallocN(src_sock->storage);
|
||||
NodeImageMultiFileSocket *dest_sockdata = (NodeImageMultiFileSocket *)dest_sock->storage;
|
||||
NodeImageMultiFileSocket *src_sockdata = (NodeImageMultiFileSocket *)src_sock->storage;
|
||||
BKE_image_format_copy(&dest_sockdata->format, &src_sockdata->format);
|
||||
const RenderData *render_data = &scene->r;
|
||||
BLI_strncpy(data->directory, render_data->pic, FILE_MAX);
|
||||
}
|
||||
}
|
||||
|
||||
static void update_output_file(bNodeTree *ntree, bNode *node)
|
||||
static void node_free_storage(bNode *node)
|
||||
{
|
||||
/* XXX fix for #36706: remove invalid sockets added with bpy API.
|
||||
* This is not ideal, but prevents crashes from missing storage.
|
||||
* FileOutput node needs a redesign to support this properly.
|
||||
*/
|
||||
LISTBASE_FOREACH_MUTABLE (bNodeSocket *, sock, &node->inputs) {
|
||||
if (sock->storage == nullptr) {
|
||||
blender::bke::node_remove_socket(*ntree, *node, *sock);
|
||||
}
|
||||
}
|
||||
LISTBASE_FOREACH_MUTABLE (bNodeSocket *, sock, &node->outputs) {
|
||||
blender::bke::node_remove_socket(*ntree, *node, *sock);
|
||||
}
|
||||
|
||||
cmp_node_update_default(ntree, node);
|
||||
|
||||
/* automatically update the socket type based on linked input */
|
||||
ntree->ensure_topology_cache();
|
||||
LISTBASE_FOREACH (bNodeSocket *, sock, &node->inputs) {
|
||||
if (sock->is_logically_linked()) {
|
||||
const bNodeSocket *from_socket = sock->logically_linked_sockets()[0];
|
||||
if (sock->type != from_socket->type) {
|
||||
blender::bke::node_modify_socket_type_static(ntree, node, sock, from_socket->type, 0);
|
||||
BKE_ntree_update_tag_socket_property(ntree, sock);
|
||||
}
|
||||
}
|
||||
}
|
||||
socket_items::destruct_array<FileOutputItemsAccessor>(*node);
|
||||
NodeCompositorFileOutput &data = node_storage(*node);
|
||||
BKE_image_format_free(&data.format);
|
||||
MEM_SAFE_FREE(data.file_name);
|
||||
MEM_freeN(&data);
|
||||
}
|
||||
|
||||
static void node_composit_buts_file_output(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
|
||||
static void node_copy_storage(bNodeTree * /*destination_node_tree*/,
|
||||
bNode *destination_node,
|
||||
const bNode *source_node)
|
||||
{
|
||||
layout->prop(ptr, "base_path", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE);
|
||||
const NodeCompositorFileOutput &source_storage = node_storage(*source_node);
|
||||
NodeCompositorFileOutput *destination_storage = MEM_dupallocN<NodeCompositorFileOutput>(
|
||||
__func__, source_storage);
|
||||
destination_storage->file_name = BLI_strdup_null(source_storage.file_name);
|
||||
BKE_image_format_copy(&destination_storage->format, &source_storage.format);
|
||||
destination_node->storage = destination_storage;
|
||||
socket_items::copy_array<FileOutputItemsAccessor>(*source_node, *destination_node);
|
||||
}
|
||||
|
||||
static void node_composit_buts_file_output_ex(uiLayout *layout, bContext *C, PointerRNA *ptr)
|
||||
static bool node_insert_link(bke::NodeInsertLinkParams ¶ms)
|
||||
{
|
||||
Scene *scene = CTX_data_scene(C);
|
||||
PointerRNA imfptr = RNA_pointer_get(ptr, "format");
|
||||
PointerRNA active_input_ptr, op_ptr;
|
||||
uiLayout *row, *col;
|
||||
const bool multilayer = RNA_enum_get(&imfptr, "file_format") == R_IMF_IMTYPE_MULTILAYER;
|
||||
const bool is_multiview = (scene->r.scemode & R_MULTIVIEW) != 0;
|
||||
return socket_items::try_add_item_via_any_extend_socket<FileOutputItemsAccessor>(
|
||||
params.ntree, params.node, params.node, params.link);
|
||||
}
|
||||
|
||||
node_composit_buts_file_output(layout, C, ptr);
|
||||
static void node_operators()
|
||||
{
|
||||
socket_items::ops::make_common_operators<FileOutputItemsAccessor>();
|
||||
}
|
||||
|
||||
{
|
||||
/* Computes the path of the image to be saved based on the given parameters. The given file name
|
||||
* suffix, if not empty, will be added to the file name. If the given view is not empty, its file
|
||||
* suffix will be appended to the name. The frame number, scene, and node are provides for variable
|
||||
* substitution in the path. If there are any errors processing the path, they will be returned. */
|
||||
static Vector<path_templates::Error> compute_image_path(const std::string directory,
|
||||
const std::string file_name,
|
||||
const std::string file_name_suffix,
|
||||
const char *view,
|
||||
const int frame_number,
|
||||
const ImageFormatData &format,
|
||||
const Scene &scene,
|
||||
const bNode &node,
|
||||
char *r_image_path)
|
||||
{
|
||||
char base_path[FILE_MAX] = "";
|
||||
BLI_strncpy(base_path, directory.c_str(), FILE_MAX);
|
||||
const std::string full_file_name = file_name + file_name_suffix;
|
||||
BLI_path_append(base_path, FILE_MAX, full_file_name.c_str());
|
||||
|
||||
path_templates::VariableMap template_variables;
|
||||
BKE_add_template_variables_general(template_variables, &node.owner_tree().id);
|
||||
BKE_add_template_variables_for_render_path(template_variables, scene);
|
||||
BKE_add_template_variables_for_node(template_variables, node);
|
||||
|
||||
return BKE_image_path_from_imformat(r_image_path,
|
||||
base_path,
|
||||
BKE_main_blendfile_path_from_global(),
|
||||
&template_variables,
|
||||
frame_number,
|
||||
&format,
|
||||
scene.r.scemode & R_EXTENSION,
|
||||
true,
|
||||
BKE_scene_multiview_view_suffix_get(&scene.r, view));
|
||||
}
|
||||
|
||||
static void node_layout(uiLayout *layout, bContext * /*context*/, PointerRNA *node_pointer)
|
||||
{
|
||||
layout->prop(node_pointer, "directory", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE);
|
||||
layout->prop(node_pointer, "file_name", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE);
|
||||
}
|
||||
|
||||
static void format_layout(uiLayout *layout,
|
||||
bContext *context,
|
||||
PointerRNA *format_pointer,
|
||||
PointerRNA *node_or_item_pointer)
|
||||
{
|
||||
uiLayout *column = &layout->column(true);
|
||||
column->use_property_split_set(true);
|
||||
column->use_property_decorate_set(false);
|
||||
column->prop(
|
||||
node_or_item_pointer, "save_as_render", UI_ITEM_R_SPLIT_EMPTY_NAME, std::nullopt, ICON_NONE);
|
||||
const bool save_as_render = RNA_boolean_get(node_or_item_pointer, "save_as_render");
|
||||
uiTemplateImageSettings(layout, context, format_pointer, save_as_render);
|
||||
|
||||
if (!save_as_render) {
|
||||
uiLayout *column = &layout->column(true);
|
||||
column->use_property_split_set(true);
|
||||
column->use_property_decorate_set(false);
|
||||
column->prop(ptr, "save_as_render", UI_ITEM_R_SPLIT_EMPTY_NAME, std::nullopt, ICON_NONE);
|
||||
}
|
||||
const bool save_as_render = RNA_boolean_get(ptr, "save_as_render");
|
||||
|
||||
uiTemplateImageSettings(layout, C, &imfptr, save_as_render);
|
||||
|
||||
if (!save_as_render) {
|
||||
uiLayout *col = &layout->column(true);
|
||||
col->use_property_split_set(true);
|
||||
col->use_property_decorate_set(false);
|
||||
|
||||
PointerRNA linear_settings_ptr = RNA_pointer_get(&imfptr, "linear_colorspace_settings");
|
||||
col->prop(&linear_settings_ptr, "name", UI_ITEM_NONE, IFACE_("Color Space"), ICON_NONE);
|
||||
PointerRNA linear_settings_ptr = RNA_pointer_get(format_pointer, "linear_colorspace_settings");
|
||||
column->prop(&linear_settings_ptr, "name", UI_ITEM_NONE, IFACE_("Color Space"), ICON_NONE);
|
||||
}
|
||||
|
||||
/* disable stereo output for multilayer, too much work for something that no one will use */
|
||||
/* if someone asks for that we can implement it */
|
||||
Scene *scene = CTX_data_scene(context);
|
||||
const bool is_multiview = scene->r.scemode & R_MULTIVIEW;
|
||||
if (is_multiview) {
|
||||
uiTemplateImageFormatViews(layout, &imfptr, nullptr);
|
||||
uiTemplateImageFormatViews(layout, format_pointer, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
layout->separator();
|
||||
static void output_path_layout(uiLayout *layout,
|
||||
const std::string directory,
|
||||
const std::string file_name,
|
||||
const std::string file_name_suffix,
|
||||
const char *view,
|
||||
const ImageFormatData &format,
|
||||
const Scene &scene,
|
||||
const bNode &node)
|
||||
{
|
||||
|
||||
uiLayout *header = &layout->row(false);
|
||||
row = &layout->row(false);
|
||||
col = &row->column(true);
|
||||
char image_path[FILE_MAX];
|
||||
const Vector<path_templates::Error> path_errors = compute_image_path(
|
||||
directory, file_name, file_name_suffix, view, scene.r.cfra, format, scene, node, image_path);
|
||||
|
||||
const int active_index = RNA_int_get(ptr, "active_input_index");
|
||||
PropertyRNA *slots_prop = nullptr;
|
||||
/* using different collection properties if multilayer format is enabled */
|
||||
if (multilayer) {
|
||||
header->label(IFACE_("Layers"), ICON_NONE);
|
||||
uiTemplateList(col,
|
||||
C,
|
||||
"UI_UL_list",
|
||||
"file_output_node",
|
||||
ptr,
|
||||
"layer_slots",
|
||||
ptr,
|
||||
"active_input_index",
|
||||
nullptr,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
UI_TEMPLATE_LIST_FLAG_NONE);
|
||||
RNA_property_collection_lookup_int(
|
||||
ptr, RNA_struct_find_property(ptr, "layer_slots"), active_index, &active_input_ptr);
|
||||
slots_prop = RNA_struct_find_property(ptr, "layer_slots");
|
||||
if (path_errors.is_empty()) {
|
||||
layout->label(image_path, ICON_FILE_IMAGE);
|
||||
}
|
||||
else {
|
||||
header->label(IFACE_("File Subpaths"), ICON_NONE);
|
||||
uiTemplateList(col,
|
||||
C,
|
||||
"UI_UL_list",
|
||||
"file_output_node",
|
||||
ptr,
|
||||
"file_slots",
|
||||
ptr,
|
||||
"active_input_index",
|
||||
nullptr,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
UI_TEMPLATE_LIST_FLAG_NONE);
|
||||
RNA_property_collection_lookup_int(
|
||||
ptr, RNA_struct_find_property(ptr, "file_slots"), active_index, &active_input_ptr);
|
||||
slots_prop = RNA_struct_find_property(ptr, "file_slots");
|
||||
}
|
||||
|
||||
col = &row->column(true);
|
||||
|
||||
col->op("NODE_OT_output_file_add_socket",
|
||||
"",
|
||||
ICON_ADD,
|
||||
wm::OpCallContext::ExecDefault,
|
||||
UI_ITEM_NONE);
|
||||
col->op("NODE_OT_output_file_remove_active_socket",
|
||||
"",
|
||||
ICON_REMOVE,
|
||||
wm::OpCallContext::ExecDefault,
|
||||
UI_ITEM_NONE);
|
||||
col->separator();
|
||||
|
||||
/* XXX collection lookup does not return the ID part of the pointer,
|
||||
* setting this manually here */
|
||||
active_input_ptr.owner_id = ptr->owner_id;
|
||||
|
||||
int slots_len = RNA_property_collection_length(ptr, slots_prop);
|
||||
if (slots_len > 0) {
|
||||
wmOperatorType *ot = WM_operatortype_find("NODE_OT_output_file_move_active_socket", false);
|
||||
|
||||
uiLayout *sub = &col->column(true);
|
||||
if (slots_len < 2) {
|
||||
sub->active_set(false);
|
||||
for (const path_templates::Error &error : path_errors) {
|
||||
layout->label(BKE_path_template_error_to_string(error, image_path).c_str(), ICON_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
op_ptr = sub->op(ot, "", ICON_TRIA_UP, wm::OpCallContext::InvokeDefault, UI_ITEM_NONE);
|
||||
RNA_enum_set(&op_ptr, "direction", 1);
|
||||
static void output_paths_layout(uiLayout *layout,
|
||||
bContext *context,
|
||||
const std::string file_name_suffix,
|
||||
const bNode &node,
|
||||
const ImageFormatData &format)
|
||||
{
|
||||
const NodeCompositorFileOutput &storage = node_storage(node);
|
||||
const std::string directory = storage.directory;
|
||||
const std::string file_name = storage.file_name ? storage.file_name : "";
|
||||
const Scene &scene = *CTX_data_scene(context);
|
||||
|
||||
op_ptr = sub->op(ot, "", ICON_TRIA_DOWN, wm::OpCallContext::InvokeDefault, UI_ITEM_NONE);
|
||||
RNA_enum_set(&op_ptr, "direction", 2);
|
||||
if (bool(scene.r.scemode & R_MULTIVIEW) && format.views_format == R_IMF_VIEWS_MULTIVIEW) {
|
||||
LISTBASE_FOREACH (SceneRenderView *, view, &scene.r.views) {
|
||||
if (!BKE_scene_multiview_is_render_view_active(&scene.r, view)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
output_path_layout(
|
||||
layout, directory, file_name, file_name_suffix, view->name, format, scene, node);
|
||||
}
|
||||
}
|
||||
else {
|
||||
output_path_layout(layout, directory, file_name, file_name_suffix, "", format, scene, node);
|
||||
}
|
||||
}
|
||||
|
||||
static void item_layout(uiLayout *layout,
|
||||
bContext *context,
|
||||
PointerRNA *node_pointer,
|
||||
PointerRNA *item_pointer,
|
||||
const bool is_multi_layer)
|
||||
{
|
||||
layout->use_property_split_set(true);
|
||||
layout->use_property_decorate_set(false);
|
||||
layout->prop(item_pointer, "socket_type", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
if (RNA_enum_get(item_pointer, "socket_type") == SOCK_VECTOR) {
|
||||
layout->prop(item_pointer, "vector_socket_dimensions", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
}
|
||||
|
||||
if (active_input_ptr.data) {
|
||||
if (!multilayer) {
|
||||
/* format details for individual files */
|
||||
imfptr = RNA_pointer_get(&active_input_ptr, "format");
|
||||
if (is_multi_layer) {
|
||||
return;
|
||||
}
|
||||
|
||||
col = &layout->column(true);
|
||||
col->prop(&active_input_ptr,
|
||||
"use_node_format",
|
||||
UI_ITEM_R_SPLIT_EMPTY_NAME,
|
||||
std::nullopt,
|
||||
ICON_NONE);
|
||||
layout->prop(
|
||||
item_pointer, "override_node_format", UI_ITEM_R_SPLIT_EMPTY_NAME, std::nullopt, ICON_NONE);
|
||||
const bool override_node_format = RNA_boolean_get(item_pointer, "override_node_format");
|
||||
|
||||
const bool use_node_format = RNA_boolean_get(&active_input_ptr, "use_node_format");
|
||||
PointerRNA node_format_pointer = RNA_pointer_get(node_pointer, "format");
|
||||
PointerRNA item_format_pointer = RNA_pointer_get(item_pointer, "format");
|
||||
PointerRNA *format_pointer = override_node_format ? &item_format_pointer : &node_format_pointer;
|
||||
|
||||
if (!use_node_format) {
|
||||
{
|
||||
uiLayout *column = &layout->column(true);
|
||||
column->use_property_split_set(true);
|
||||
column->use_property_decorate_set(false);
|
||||
column->prop(&active_input_ptr,
|
||||
"save_as_render",
|
||||
UI_ITEM_R_SPLIT_EMPTY_NAME,
|
||||
std::nullopt,
|
||||
ICON_NONE);
|
||||
}
|
||||
if (override_node_format) {
|
||||
if (uiLayout *panel = layout->panel(context, "item_format", false, IFACE_("Item Format"))) {
|
||||
format_layout(panel, context, format_pointer, item_pointer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const bool use_color_management = RNA_boolean_get(&active_input_ptr, "save_as_render");
|
||||
static void node_layout_ex(uiLayout *layout, bContext *context, PointerRNA *node_pointer)
|
||||
{
|
||||
node_layout(layout, context, node_pointer);
|
||||
|
||||
col = &layout->column(false);
|
||||
uiTemplateImageSettings(
|
||||
col, C, &imfptr, use_color_management, "node_settings_color_management");
|
||||
PointerRNA format_pointer = RNA_pointer_get(node_pointer, "format");
|
||||
const bool is_multi_layer = RNA_enum_get(&format_pointer, "file_format") ==
|
||||
R_IMF_IMTYPE_MULTILAYER;
|
||||
layout->prop(&format_pointer, "media_type", UI_ITEM_R_EXPAND, std::nullopt, ICON_NONE);
|
||||
if (uiLayout *panel = layout->panel(context, "node_format", false, IFACE_("Node Format"))) {
|
||||
format_layout(panel, context, &format_pointer, node_pointer);
|
||||
}
|
||||
|
||||
if (!use_color_management) {
|
||||
uiLayout *col = &layout->column(true);
|
||||
col->use_property_split_set(true);
|
||||
col->use_property_decorate_set(false);
|
||||
const char *panel_name = is_multi_layer ? IFACE_("Layers") : IFACE_("Images");
|
||||
if (uiLayout *panel = layout->panel(context, "file_output_items", false, panel_name)) {
|
||||
bNodeTree &tree = *reinterpret_cast<bNodeTree *>(node_pointer->owner_id);
|
||||
bNode &node = *node_pointer->data_as<bNode>();
|
||||
socket_items::ui::draw_items_list_with_operators<FileOutputItemsAccessor>(
|
||||
context, panel, tree, node);
|
||||
socket_items::ui::draw_active_item_props<FileOutputItemsAccessor>(
|
||||
tree, node, [&](PointerRNA *item_pointer) {
|
||||
item_layout(panel, context, node_pointer, item_pointer, is_multi_layer);
|
||||
});
|
||||
}
|
||||
|
||||
PointerRNA linear_settings_ptr = RNA_pointer_get(&imfptr, "linear_colorspace_settings");
|
||||
col->prop(&linear_settings_ptr, "name", UI_ITEM_NONE, IFACE_("Color Space"), ICON_NONE);
|
||||
}
|
||||
if (uiLayout *panel = layout->panel(context, "output_paths", true, IFACE_("Output Paths"))) {
|
||||
const bNode &node = *node_pointer->data_as<bNode>();
|
||||
const ImageFormatData &node_format = *format_pointer.data_as<ImageFormatData>();
|
||||
|
||||
if (is_multiview) {
|
||||
col = &layout->column(false);
|
||||
uiTemplateImageFormatViews(col, &imfptr, nullptr);
|
||||
}
|
||||
if (is_multi_layer) {
|
||||
output_paths_layout(panel, context, "", node, node_format);
|
||||
}
|
||||
else {
|
||||
const NodeCompositorFileOutput &storage = node_storage(node);
|
||||
for (const int i : IndexRange(storage.items_count)) {
|
||||
const NodeCompositorFileOutputItem &item = storage.items[i];
|
||||
const auto &format = item.override_node_format ? item.format : storage.format;
|
||||
output_paths_layout(panel, context, item.name, node, format);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void node_blend_write(const bNodeTree & /*tree*/, const bNode &node, BlendWriter &writer)
|
||||
{
|
||||
const NodeCompositorFileOutput &data = node_storage(node);
|
||||
BLO_write_string(&writer, data.file_name);
|
||||
BKE_image_format_blend_write(&writer, const_cast<ImageFormatData *>(&data.format));
|
||||
socket_items::blend_write<FileOutputItemsAccessor>(&writer, node);
|
||||
}
|
||||
|
||||
static void node_blend_read(bNodeTree & /*tree*/, bNode &node, BlendDataReader &reader)
|
||||
{
|
||||
NodeCompositorFileOutput &data = node_storage(node);
|
||||
BLO_read_string(&reader, &data.file_name);
|
||||
BKE_image_format_blend_read_data(&reader, &data.format);
|
||||
socket_items::blend_read_data<FileOutputItemsAccessor>(&reader, node);
|
||||
}
|
||||
|
||||
using namespace blender::compositor;
|
||||
|
||||
class FileOutputOperation : public NodeOperation {
|
||||
public:
|
||||
FileOutputOperation(Context &context, DNode node) : NodeOperation(context, node)
|
||||
{
|
||||
for (const bNodeSocket *input : node->input_sockets()) {
|
||||
if (!is_socket_available(input)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
InputDescriptor &descriptor = this->get_input_descriptor(input->identifier);
|
||||
/* Inputs for multi-layer files need to be the same size, while they can be different for
|
||||
* individual file outputs. */
|
||||
descriptor.realization_mode = this->is_multi_layer() ?
|
||||
InputRealizationMode::OperationDomain :
|
||||
InputRealizationMode::Transforms;
|
||||
descriptor.skip_type_conversion = true;
|
||||
}
|
||||
}
|
||||
using NodeOperation::NodeOperation;
|
||||
|
||||
void execute() override
|
||||
{
|
||||
if (is_multi_layer()) {
|
||||
execute_multi_layer();
|
||||
if (this->is_multi_layer()) {
|
||||
this->execute_multi_layer();
|
||||
}
|
||||
else {
|
||||
execute_single_layer();
|
||||
this->execute_single_layer();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -518,53 +389,48 @@ class FileOutputOperation : public NodeOperation {
|
||||
|
||||
void execute_single_layer()
|
||||
{
|
||||
for (const bNodeSocket *input : this->node()->input_sockets()) {
|
||||
if (!is_socket_available(input)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const Result &result = get_input(input->identifier);
|
||||
const NodeCompositorFileOutput &storage = node_storage(this->bnode());
|
||||
for (const int i : IndexRange(storage.items_count)) {
|
||||
const NodeCompositorFileOutputItem &item = storage.items[i];
|
||||
const std::string identifier = FileOutputItemsAccessor::socket_identifier_for_item(item);
|
||||
const Result &result = this->get_input(identifier);
|
||||
/* We only write images, not single values. */
|
||||
if (result.is_single_value()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
char base_path[FILE_MAX];
|
||||
const auto &socket = *static_cast<NodeImageMultiFileSocket *>(input->storage);
|
||||
|
||||
if (!get_single_layer_image_base_path(socket.path, base_path)) {
|
||||
/* TODO: propagate this error to the render pipeline and UI. */
|
||||
BKE_report(nullptr,
|
||||
RPT_ERROR,
|
||||
"Invalid path template in File Output node. Skipping writing file.");
|
||||
continue;
|
||||
}
|
||||
|
||||
/* The image saving code expects EXR images to have a different structure than standard
|
||||
* images. In particular, in EXR images, the buffers need to be stored in passes that are, in
|
||||
* turn, stored in a render layer. On the other hand, in non-EXR images, the buffers need to
|
||||
* be stored in views. An exception to this is stereo images, which needs to have the same
|
||||
* structure as non-EXR images. */
|
||||
const auto &format = socket.use_node_format ? node_storage(bnode()).format : socket.format;
|
||||
const bool save_as_render = socket.use_node_format ? node_storage(bnode()).save_as_render :
|
||||
socket.save_as_render;
|
||||
const auto &format = item.override_node_format ? item.format :
|
||||
node_storage(this->bnode()).format;
|
||||
const bool save_as_render = item.override_node_format ?
|
||||
item.save_as_render :
|
||||
node_storage(this->bnode()).save_as_render;
|
||||
const bool is_exr = format.imtype == R_IMF_IMTYPE_OPENEXR;
|
||||
const int views_count = BKE_scene_multiview_num_views_get(&context().get_render_data());
|
||||
const int views_count = BKE_scene_multiview_num_views_get(
|
||||
&this->context().get_render_data());
|
||||
if (is_exr && !(format.views_format == R_IMF_VIEWS_STEREO_3D && views_count == 2)) {
|
||||
execute_single_layer_multi_view_exr(result, format, base_path, socket.layer);
|
||||
this->execute_single_layer_multi_view_exr(result, format, item.name);
|
||||
continue;
|
||||
}
|
||||
|
||||
char image_path[FILE_MAX];
|
||||
get_single_layer_image_path(base_path, format, image_path);
|
||||
Vector<path_templates::Error> path_errors = this->get_image_path(
|
||||
format, item.name, "", image_path);
|
||||
if (!path_errors.is_empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const int2 size = result.domain().size;
|
||||
FileOutput &file_output = context().render_context()->get_file_output(
|
||||
FileOutput &file_output = this->context().render_context()->get_file_output(
|
||||
image_path, format, size, save_as_render);
|
||||
|
||||
add_view_for_result(file_output, result, context().get_view_name().data());
|
||||
this->add_view_for_result(file_output, result, context().get_view_name().data());
|
||||
|
||||
add_meta_data_for_result(file_output, result, socket.layer);
|
||||
this->add_meta_data_for_result(file_output, result, item.name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -574,32 +440,32 @@ class FileOutputOperation : public NodeOperation {
|
||||
|
||||
void execute_single_layer_multi_view_exr(const Result &result,
|
||||
const ImageFormatData &format,
|
||||
const char *base_path,
|
||||
const char *layer_name)
|
||||
{
|
||||
const bool has_views = format.views_format != R_IMF_VIEWS_INDIVIDUAL;
|
||||
|
||||
/* The EXR stores all views in the same file, so we supply an empty view to make sure the file
|
||||
* name does not contain a view suffix. */
|
||||
char image_path[FILE_MAX];
|
||||
const char *path_view = has_views ? "" : context().get_view_name().data();
|
||||
const char *path_view = has_views ? "" : this->context().get_view_name().data();
|
||||
|
||||
if (!get_multi_layer_exr_image_path(base_path, path_view, false, image_path)) {
|
||||
BLI_assert_unreachable();
|
||||
char image_path[FILE_MAX];
|
||||
Vector<path_templates::Error> path_errors = this->get_image_path(
|
||||
format, layer_name, path_view, image_path);
|
||||
if (!path_errors.is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int2 size = result.domain().size;
|
||||
FileOutput &file_output = context().render_context()->get_file_output(
|
||||
FileOutput &file_output = this->context().render_context()->get_file_output(
|
||||
image_path, format, size, true);
|
||||
|
||||
/* The EXR stores all views in the same file, so we add the actual render view. Otherwise, we
|
||||
* add a default unnamed view. */
|
||||
const char *view_name = has_views ? context().get_view_name().data() : "";
|
||||
const char *view_name = has_views ? this->context().get_view_name().data() : "";
|
||||
file_output.add_view(view_name);
|
||||
add_pass_for_result(file_output, result, "", view_name);
|
||||
this->add_pass_for_result(file_output, result, "", view_name);
|
||||
|
||||
add_meta_data_for_result(file_output, result, layer_name);
|
||||
this->add_meta_data_for_result(file_output, result, layer_name);
|
||||
}
|
||||
|
||||
/* -----------------------
|
||||
@@ -614,22 +480,21 @@ class FileOutputOperation : public NodeOperation {
|
||||
return;
|
||||
}
|
||||
|
||||
const bool store_views_in_single_file = is_multi_view_exr();
|
||||
const char *view = context().get_view_name().data();
|
||||
const ImageFormatData format = node_storage(this->bnode()).format;
|
||||
const bool store_views_in_single_file = this->is_multi_view_exr();
|
||||
const char *view = this->context().get_view_name().data();
|
||||
|
||||
/* If we are saving all views in a single multi-layer file, we supply an empty view to make
|
||||
* sure the file name does not contain a view suffix. */
|
||||
char image_path[FILE_MAX];
|
||||
const char *write_view = store_views_in_single_file ? "" : view;
|
||||
if (!get_multi_layer_exr_image_path(get_base_path(), write_view, true, image_path)) {
|
||||
/* TODO: propagate this error to the render pipeline and UI. */
|
||||
BKE_report(
|
||||
nullptr, RPT_ERROR, "Invalid path template in File Output node. Skipping writing file.");
|
||||
Vector<path_templates::Error> path_errors = this->get_image_path(
|
||||
format, "", write_view, image_path);
|
||||
if (!path_errors.is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ImageFormatData format = node_storage(bnode()).format;
|
||||
FileOutput &file_output = context().render_context()->get_file_output(
|
||||
FileOutput &file_output = this->context().render_context()->get_file_output(
|
||||
image_path, format, size, true);
|
||||
|
||||
/* If we are saving views in separate files, we needn't store the view in the channel names, so
|
||||
@@ -637,16 +502,14 @@ class FileOutputOperation : public NodeOperation {
|
||||
const char *pass_view = store_views_in_single_file ? view : "";
|
||||
file_output.add_view(pass_view);
|
||||
|
||||
for (const bNodeSocket *input : this->node()->input_sockets()) {
|
||||
if (!is_socket_available(input)) {
|
||||
continue;
|
||||
}
|
||||
const NodeCompositorFileOutput &storage = node_storage(bnode());
|
||||
for (const int i : IndexRange(storage.items_count)) {
|
||||
const NodeCompositorFileOutputItem &item = storage.items[i];
|
||||
const std::string identifier = FileOutputItemsAccessor::socket_identifier_for_item(item);
|
||||
const Result &input_result = this->get_input(identifier);
|
||||
this->add_pass_for_result(file_output, input_result, item.name, pass_view);
|
||||
|
||||
const Result &input_result = get_input(input->identifier);
|
||||
const char *pass_name = (static_cast<NodeImageMultiFileSocket *>(input->storage))->layer;
|
||||
add_pass_for_result(file_output, input_result, pass_name, pass_view);
|
||||
|
||||
add_meta_data_for_result(file_output, input_result, pass_name);
|
||||
this->add_meta_data_for_result(file_output, input_result, item.name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -669,7 +532,7 @@ class FileOutputOperation : public NodeOperation {
|
||||
buffer = this->inflate_result(result, size);
|
||||
}
|
||||
else {
|
||||
if (context().use_gpu()) {
|
||||
if (this->context().use_gpu()) {
|
||||
GPU_memory_barrier(GPU_BARRIER_TEXTURE_UPDATE);
|
||||
buffer = static_cast<float *>(GPU_texture_read(result, GPU_DATA_FLOAT, 0));
|
||||
}
|
||||
@@ -777,7 +640,7 @@ class FileOutputOperation : public NodeOperation {
|
||||
/* The image buffer in the file output will take ownership of this buffer and freeing it will
|
||||
* be its responsibility. */
|
||||
float *buffer = nullptr;
|
||||
if (context().use_gpu()) {
|
||||
if (this->context().use_gpu()) {
|
||||
GPU_memory_barrier(GPU_BARRIER_TEXTURE_UPDATE);
|
||||
buffer = static_cast<float *>(GPU_texture_read(result, GPU_DATA_FLOAT, 0));
|
||||
}
|
||||
@@ -867,161 +730,59 @@ class FileOutputOperation : public NodeOperation {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the base path of the image to be saved, based on the base path of the
|
||||
* node. The base name is an optional initial name of the image, which will
|
||||
* later be concatenated with other information like the frame number, view,
|
||||
* and extension. If the base name is empty, then the base path represents a
|
||||
* directory, so a trailing slash is ensured.
|
||||
*
|
||||
* Note: this takes care of path template expansion as well.
|
||||
*
|
||||
* If there are any errors processing the path, `bath_base` will be set to an
|
||||
* empty string.
|
||||
*
|
||||
* \return True on success, false if there were any errors processing the
|
||||
* path.
|
||||
*/
|
||||
bool get_single_layer_image_base_path(const char *base_name, char *r_base_path)
|
||||
Vector<path_templates::Error> get_image_path(const ImageFormatData &format,
|
||||
const char *file_name_suffix,
|
||||
const char *view,
|
||||
char *r_image_path)
|
||||
{
|
||||
path_templates::VariableMap template_variables;
|
||||
BKE_add_template_variables_general(template_variables, &this->bnode().owner_tree().id);
|
||||
BKE_add_template_variables_for_render_path(template_variables, context().get_scene());
|
||||
BKE_add_template_variables_for_node(template_variables, this->bnode());
|
||||
const Vector<path_templates::Error> path_errors = compute_image_path(
|
||||
this->get_directory(),
|
||||
this->get_file_name(),
|
||||
file_name_suffix,
|
||||
view,
|
||||
this->context().get_frame_number(),
|
||||
format,
|
||||
this->context().get_scene(),
|
||||
this->bnode(),
|
||||
r_image_path);
|
||||
|
||||
/* Do template expansion on the node's base path. */
|
||||
char node_base_path[FILE_MAX] = "";
|
||||
STRNCPY(node_base_path, get_base_path());
|
||||
{
|
||||
blender::Vector<path_templates::Error> errors = BKE_path_apply_template(
|
||||
node_base_path, FILE_MAX, template_variables);
|
||||
if (!errors.is_empty()) {
|
||||
r_base_path[0] = '\0';
|
||||
return false;
|
||||
}
|
||||
if (!path_errors.is_empty()) {
|
||||
BKE_report(
|
||||
nullptr, RPT_ERROR, "Invalid path template in File Output node. Skipping writing file.");
|
||||
}
|
||||
|
||||
if (base_name[0]) {
|
||||
/* Do template expansion on the socket's sub path ("base name"). */
|
||||
char sub_path[FILE_MAX] = "";
|
||||
STRNCPY(sub_path, base_name);
|
||||
{
|
||||
blender::Vector<path_templates::Error> errors = BKE_path_apply_template(
|
||||
sub_path, FILE_MAX, template_variables);
|
||||
if (!errors.is_empty()) {
|
||||
r_base_path[0] = '\0';
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Combine the base path and sub path. */
|
||||
BLI_path_join(r_base_path, FILE_MAX, node_base_path, sub_path);
|
||||
}
|
||||
else {
|
||||
/* Just use the base path, as a directory. */
|
||||
BLI_strncpy(r_base_path, node_base_path, FILE_MAX);
|
||||
BLI_path_slash_ensure(r_base_path, FILE_MAX);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Get the path of the image to be saved based on the given format. */
|
||||
void get_single_layer_image_path(const char *base_path,
|
||||
const ImageFormatData &format,
|
||||
char *r_image_path)
|
||||
{
|
||||
BKE_image_path_from_imformat(r_image_path,
|
||||
base_path,
|
||||
BKE_main_blendfile_path_from_global(),
|
||||
/* No variables, because path templating is
|
||||
* already done by
|
||||
* `get_single_layer_image_base_path()` before
|
||||
* this is called. */
|
||||
nullptr,
|
||||
context().get_frame_number(),
|
||||
&format,
|
||||
use_file_extension(),
|
||||
true,
|
||||
nullptr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path of the EXR image to be saved. If the given view is not empty,
|
||||
* its corresponding file suffix will be appended to the name.
|
||||
*
|
||||
* If there are any errors processing the path, the resulting path will be
|
||||
* empty.
|
||||
*
|
||||
* \param apply_template: Whether to run templating on the path or not. This is
|
||||
* needed because this function is called from more than one place, some of
|
||||
* which have already applied templating to the path and some of which
|
||||
* haven't. Double-applying templating can give incorrect results.
|
||||
*
|
||||
* \return True on success, false if there were any errors processing the
|
||||
* path.
|
||||
*/
|
||||
bool get_multi_layer_exr_image_path(const char *base_path,
|
||||
const char *view,
|
||||
const bool apply_template,
|
||||
char *r_image_path)
|
||||
{
|
||||
const Scene *scene = &context().get_scene();
|
||||
const RenderData &render_data = context().get_render_data();
|
||||
path_templates::VariableMap template_variables;
|
||||
BKE_add_template_variables_general(template_variables, &this->bnode().owner_tree().id);
|
||||
BKE_add_template_variables_for_render_path(template_variables, *scene);
|
||||
BKE_add_template_variables_for_node(template_variables, this->bnode());
|
||||
|
||||
const char *suffix = BKE_scene_multiview_view_suffix_get(&render_data, view);
|
||||
const char *relbase = BKE_main_blendfile_path_from_global();
|
||||
blender::Vector<path_templates::Error> errors = BKE_image_path_from_imtype(
|
||||
r_image_path,
|
||||
base_path,
|
||||
relbase,
|
||||
apply_template ? &template_variables : nullptr,
|
||||
context().get_frame_number(),
|
||||
R_IMF_IMTYPE_MULTILAYER,
|
||||
use_file_extension(),
|
||||
true,
|
||||
suffix);
|
||||
|
||||
if (!errors.is_empty()) {
|
||||
r_image_path[0] = '\0';
|
||||
}
|
||||
|
||||
return errors.is_empty();
|
||||
return path_errors;
|
||||
}
|
||||
|
||||
bool is_multi_layer()
|
||||
{
|
||||
return node_storage(bnode()).format.imtype == R_IMF_IMTYPE_MULTILAYER;
|
||||
return node_storage(this->bnode()).format.imtype == R_IMF_IMTYPE_MULTILAYER;
|
||||
}
|
||||
|
||||
const char *get_base_path()
|
||||
std::string get_file_name()
|
||||
{
|
||||
return node_storage(bnode()).base_path;
|
||||
const char *file_name = node_storage(this->bnode()).file_name;
|
||||
return file_name ? file_name : "";
|
||||
}
|
||||
|
||||
/* Add the file format extensions to the rendered file name. */
|
||||
bool use_file_extension()
|
||||
std::string get_directory()
|
||||
{
|
||||
return context().get_render_data().scemode & R_EXTENSION;
|
||||
return node_storage(this->bnode()).directory;
|
||||
}
|
||||
|
||||
/* If true, save views in a multi-view EXR file, otherwise, save each view in its own file. */
|
||||
bool is_multi_view_exr()
|
||||
{
|
||||
if (!is_multi_view_scene()) {
|
||||
if (!this->is_multi_view_scene()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return node_storage(bnode()).format.views_format == R_IMF_VIEWS_MULTIVIEW;
|
||||
return node_storage(this->bnode()).format.views_format == R_IMF_VIEWS_MULTIVIEW;
|
||||
}
|
||||
|
||||
bool is_multi_view_scene()
|
||||
{
|
||||
return context().get_render_data().scemode & R_MULTIVIEW;
|
||||
return this->context().get_render_data().scemode & R_MULTIVIEW;
|
||||
}
|
||||
|
||||
Domain compute_domain() override
|
||||
@@ -1043,12 +804,8 @@ static NodeOperation *get_compositor_operation(Context &context, DNode node)
|
||||
return new FileOutputOperation(context, node);
|
||||
}
|
||||
|
||||
} // namespace blender::nodes::node_composite_file_output_cc
|
||||
|
||||
static void register_node_type_cmp_output_file()
|
||||
static void node_register()
|
||||
{
|
||||
namespace file_ns = blender::nodes::node_composite_file_output_cc;
|
||||
|
||||
static blender::bke::bNodeType ntype;
|
||||
|
||||
cmp_node_type_base(&ntype, "CompositorNodeOutputFile", CMP_NODE_OUTPUT_FILE);
|
||||
@@ -1056,14 +813,46 @@ static void register_node_type_cmp_output_file()
|
||||
ntype.ui_description = "Write image file to disk";
|
||||
ntype.enum_name_legacy = "OUTPUT_FILE";
|
||||
ntype.nclass = NODE_CLASS_OUTPUT;
|
||||
ntype.draw_buttons = file_ns::node_composit_buts_file_output;
|
||||
ntype.draw_buttons_ex = file_ns::node_composit_buts_file_output_ex;
|
||||
ntype.initfunc_api = file_ns::init_output_file;
|
||||
ntype.declare = node_declare;
|
||||
ntype.draw_buttons = node_layout;
|
||||
ntype.draw_buttons_ex = node_layout_ex;
|
||||
ntype.insert_link = node_insert_link;
|
||||
ntype.register_operators = node_operators;
|
||||
ntype.initfunc_api = node_init;
|
||||
blender::bke::node_type_storage(
|
||||
ntype, "NodeImageMultiFile", file_ns::free_output_file, file_ns::copy_output_file);
|
||||
ntype.updatefunc = file_ns::update_output_file;
|
||||
ntype.get_compositor_operation = file_ns::get_compositor_operation;
|
||||
ntype, "NodeCompositorFileOutput", node_free_storage, node_copy_storage);
|
||||
ntype.blend_write_storage_content = node_blend_write;
|
||||
ntype.blend_data_read_storage_content = node_blend_read;
|
||||
ntype.get_compositor_operation = get_compositor_operation;
|
||||
|
||||
blender::bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(register_node_type_cmp_output_file)
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_composite_file_output_cc
|
||||
|
||||
namespace blender::nodes {
|
||||
|
||||
StructRNA *FileOutputItemsAccessor::item_srna = &RNA_NodeCompositorFileOutputItem;
|
||||
|
||||
void FileOutputItemsAccessor::blend_write_item(BlendWriter *writer, const ItemT &item)
|
||||
{
|
||||
BLO_write_string(writer, item.name);
|
||||
BKE_image_format_blend_write(writer, const_cast<ImageFormatData *>(&item.format));
|
||||
}
|
||||
|
||||
void FileOutputItemsAccessor::blend_read_data_item(BlendDataReader *reader, ItemT &item)
|
||||
{
|
||||
BLO_read_string(reader, &item.name);
|
||||
BKE_image_format_blend_read_data(reader, &item.format);
|
||||
}
|
||||
|
||||
std::string FileOutputItemsAccessor::validate_name(const StringRef name)
|
||||
{
|
||||
char file_name[FILE_MAX] = "";
|
||||
BLI_strncpy(file_name, name.data(), FILE_MAX);
|
||||
BLI_path_make_safe_filename(file_name);
|
||||
return file_name;
|
||||
}
|
||||
|
||||
} // namespace blender::nodes
|
||||
|
||||
BIN
tests/files/compositor/file_output/exr_passes/Vector0001.exr
(Stored with Git LFS)
BIN
tests/files/compositor/file_output/exr_passes/Vector0001.exr
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/compositor/file_output/exr_png_group_multilayer_passes/Image_0010001.png
(Stored with Git LFS)
BIN
tests/files/compositor/file_output/exr_png_group_multilayer_passes/Image_0010001.png
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/compositor/file_output/exr_png_group_multilayer_passes/Image_0020001.png
(Stored with Git LFS)
BIN
tests/files/compositor/file_output/exr_png_group_multilayer_passes/Image_0020001.png
(Stored with Git LFS)
Binary file not shown.
@@ -197,16 +197,17 @@ class FileOutputTest(unittest.TestCase):
|
||||
self.assertTrue(ok)
|
||||
|
||||
def run_test_script(self, blendfile, curr_out_dir):
|
||||
def set_basepath(node_tree, base_path):
|
||||
def set_directory(node_tree, base_path):
|
||||
for node in node_tree.nodes:
|
||||
if node.type == 'OUTPUT_FILE':
|
||||
node.base_path = f'{curr_out_dir}/'
|
||||
node.directory = f'{curr_out_dir}/'
|
||||
node.file_name = ""
|
||||
elif node.type == 'GROUP' and node.node_tree:
|
||||
set_basepath(node.node_tree, base_path)
|
||||
set_directory(node.node_tree, base_path)
|
||||
|
||||
bpy.ops.wm.open_mainfile(filepath=blendfile)
|
||||
# Set output directory for all existing file output nodes.
|
||||
set_basepath(bpy.data.scenes[0].compositing_node_group, f'{curr_out_dir}/')
|
||||
set_directory(bpy.data.scenes[0].compositing_node_group, f'{curr_out_dir}/')
|
||||
bpy.data.scenes[0].render.compositor_device = f'{self.execution_device}'
|
||||
bpy.ops.render.render()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user