UI/Nodes: Improve feedback when adding node fails (e.g. on drag & drop)

This is especially useful when trying to add a node group instance, e.g. via
drag & drop from the Outliner or Asset Browser.
Previously this would just silently fail, with no information why. This is a
source of confusion, e.g. earlier, it took me a moment to realize I was
dragging a node group into itself, which failed of course.
Blender should always try to help the user with useful error messages.

Adds error messages like: "Nesting a node group inside of itself is not
allowed", "Not a compositor node tree", etc.

Adds a disabled hint return argument to node and node tree polling functions.
On error the hint is reported, or could even be shown in advance (e.g. if
checked via an operator poll option).

Differential Revision: https://developer.blender.org/D10422

Reviewed by: Jacques Lucke
This commit is contained in:
Julian Eisel 2021-04-12 18:43:23 +02:00
parent cbd1932619
commit 2bd9f9d976
Notes: blender-bot 2023-02-14 06:45:14 +01:00
Referenced by issue #86610, Blender Crashing when rendering with Opencl GPU on Ubuntu.
21 changed files with 227 additions and 82 deletions

View File

@ -289,10 +289,22 @@ typedef struct bNodeType {
void (*freefunc_api)(struct PointerRNA *ptr);
void (*copyfunc_api)(struct PointerRNA *ptr, const struct bNode *src_node);
/* can this node type be added to a node tree */
bool (*poll)(struct bNodeType *ntype, struct bNodeTree *nodetree);
/* can this node be added to a node tree */
bool (*poll_instance)(struct bNode *node, struct bNodeTree *nodetree);
/**
* Can this node type be added to a node tree?
* \param r_disabled_hint: Optional hint to display in the UI when the poll fails.
* The callback can set this to a static string without having to
* null-check it (or without setting it to null if it's not used).
* The caller must pass a valid `const char **` and null-initialize it
* when it's not just a dummy, that is, if it actually wants to access
* the returned disabled-hint (null-check needed!).
*/
bool (*poll)(struct bNodeType *ntype, struct bNodeTree *nodetree, const char **r_disabled_hint);
/** Can this node be added to a node tree?
* \param r_disabled_hint: See `poll()`.
*/
bool (*poll_instance)(struct bNode *node,
struct bNodeTree *nodetree,
const char **r_disabled_hint);
/* optional handling of link insertion */
void (*insert_link)(struct bNodeTree *ntree, struct bNode *node, struct bNodeLink *link);
@ -804,7 +816,9 @@ void BKE_node_preview_set_pixel(
void nodeLabel(struct bNodeTree *ntree, struct bNode *node, char *label, int maxlen);
const char *nodeSocketLabel(const struct bNodeSocket *sock);
int nodeGroupPoll(struct bNodeTree *nodetree, struct bNodeTree *grouptree);
bool nodeGroupPoll(struct bNodeTree *nodetree,
struct bNodeTree *grouptree,
const char **r_disabled_hint);
/* Init a new node type struct with default values and callbacks */
void node_type_base(struct bNodeType *ntype, int type, const char *name, short nclass, short flag);

View File

@ -2007,7 +2007,8 @@ bNode *nodeAddStaticNode(const struct bContext *C, bNodeTree *ntree, int type)
/* do an extra poll here, because some int types are used
* for multiple node types, this helps find the desired type
*/
if (ntype->type == type && (!ntype->poll || ntype->poll(ntype, ntree))) {
const char *disabled_hint;
if (ntype->type == type && (!ntype->poll || ntype->poll(ntype, ntree, &disabled_hint))) {
idname = ntype->idname;
break;
}
@ -4407,15 +4408,17 @@ static void node_type_base_defaults(bNodeType *ntype)
}
/* allow this node for any tree type */
static bool node_poll_default(bNodeType *UNUSED(ntype), bNodeTree *UNUSED(ntree))
static bool node_poll_default(bNodeType *UNUSED(ntype),
bNodeTree *UNUSED(ntree),
const char **UNUSED(disabled_hint))
{
return true;
}
/* use the basic poll function */
static bool node_poll_instance_default(bNode *node, bNodeTree *ntree)
static bool node_poll_instance_default(bNode *node, bNodeTree *ntree, const char **disabled_hint)
{
return node->typeinfo->poll(node->typeinfo, ntree);
return node->typeinfo->poll(node->typeinfo, ntree, disabled_hint);
}
/* NOLINTNEXTLINE: readability-function-size */
@ -4634,7 +4637,9 @@ void node_type_internal_links(bNodeType *ntype,
/* callbacks for undefined types */
static bool node_undefined_poll(bNodeType *UNUSED(ntype), bNodeTree *UNUSED(nodetree))
static bool node_undefined_poll(bNodeType *UNUSED(ntype),
bNodeTree *UNUSED(nodetree),
const char **UNUSED(r_disabled_hint))
{
/* this type can not be added deliberately, it's just a placeholder */
return false;

View File

@ -339,7 +339,25 @@ static bNodeTree *node_add_group_get_and_poll_group_node_tree(Main *bmain,
if (!node_group) {
return NULL;
}
if ((node_group->type != ntree->type) || !nodeGroupPoll(ntree, node_group)) {
const char *disabled_hint = NULL;
if ((node_group->type != ntree->type) || !nodeGroupPoll(ntree, node_group, &disabled_hint)) {
if (disabled_hint) {
BKE_reportf(op->reports,
RPT_ERROR,
"Can not add node group '%s' to '%s':\n %s",
node_group->id.name + 2,
ntree->id.name + 2,
disabled_hint);
}
else {
BKE_reportf(op->reports,
RPT_ERROR,
"Can not add node group '%s' to '%s'",
node_group->id.name + 2,
ntree->id.name + 2);
}
return NULL;
}

View File

@ -2241,13 +2241,25 @@ static int node_clipboard_paste_exec(bContext *C, wmOperator *op)
/* make sure all clipboard nodes would be valid in the target tree */
bool all_nodes_valid = true;
LISTBASE_FOREACH (bNode *, node, clipboard_nodes_lb) {
if (!node->typeinfo->poll_instance || !node->typeinfo->poll_instance(node, ntree)) {
const char *disabled_hint = NULL;
if (!node->typeinfo->poll_instance ||
!node->typeinfo->poll_instance(node, ntree, &disabled_hint)) {
all_nodes_valid = false;
BKE_reportf(op->reports,
RPT_ERROR,
"Cannot add node %s into node tree %s",
node->name,
ntree->id.name + 2);
if (disabled_hint) {
BKE_reportf(op->reports,
RPT_ERROR,
"Cannot add node %s into node tree %s:\n %s",
node->name,
ntree->id.name + 2,
disabled_hint);
}
else {
BKE_reportf(op->reports,
RPT_ERROR,
"Cannot add node %s into node tree %s",
node->name,
ntree->id.name + 2);
}
}
}
if (!all_nodes_valid) {

View File

@ -679,8 +679,19 @@ static bool node_group_make_test_selected(bNodeTree *ntree,
/* check poll functions for selected nodes */
LISTBASE_FOREACH (bNode *, node, &ntree->nodes) {
if (node_group_make_use_node(node, gnode)) {
if (node->typeinfo->poll_instance && !node->typeinfo->poll_instance(node, ngroup)) {
BKE_reportf(reports, RPT_WARNING, "Can not add node '%s' in a group", node->name);
const char *disabled_hint = NULL;
if (node->typeinfo->poll_instance &&
!node->typeinfo->poll_instance(node, ngroup, &disabled_hint)) {
if (disabled_hint) {
BKE_reportf(reports,
RPT_WARNING,
"Can not add node '%s' in a group:\n %s",
node->name,
disabled_hint);
}
else {
BKE_reportf(reports, RPT_WARNING, "Can not add node '%s' in a group", node->name);
}
ok = false;
break;
}

View File

@ -330,7 +330,9 @@ static void ui_node_link_items(NodeLinkArg *arg,
int i;
for (ngroup = arg->bmain->nodetrees.first; ngroup; ngroup = ngroup->id.next) {
if ((ngroup->type != arg->ntree->type) || !nodeGroupPoll(arg->ntree, ngroup)) {
const char *disabled_hint;
if ((ngroup->type != arg->ntree->type) ||
!nodeGroupPoll(arg->ntree, ngroup, &disabled_hint)) {
continue;
}
@ -343,7 +345,9 @@ static void ui_node_link_items(NodeLinkArg *arg,
i = 0;
for (ngroup = arg->bmain->nodetrees.first; ngroup; ngroup = ngroup->id.next) {
if ((ngroup->type != arg->ntree->type) || !nodeGroupPoll(arg->ntree, ngroup)) {
const char *disabled_hint;
if ((ngroup->type != arg->ntree->type) ||
!nodeGroupPoll(arg->ntree, ngroup, &disabled_hint)) {
continue;
}
@ -481,7 +485,8 @@ static void ui_node_menu_column(NodeLinkArg *arg, int nclass, const char *cname)
BLI_array_declare(sorted_ntypes);
NODE_TYPES_BEGIN (ntype) {
if (!(ntype->poll && ntype->poll(ntype, ntree))) {
const char *disabled_hint;
if (!(ntype->poll && ntype->poll(ntype, ntree, &disabled_hint))) {
continue;
}

View File

@ -1085,13 +1085,25 @@ static bNode *rna_NodeTree_node_new(bNodeTree *ntree,
return NULL;
}
if (ntype->poll && !ntype->poll(ntype, ntree)) {
BKE_reportf(reports,
RPT_ERROR,
"Cannot add node of type %s to node tree '%s'",
type,
ntree->id.name + 2);
return NULL;
const char *disabled_hint = NULL;
if (ntype->poll && !ntype->poll(ntype, ntree, &disabled_hint)) {
if (disabled_hint) {
BKE_reportf(reports,
RPT_ERROR,
"Cannot add node of type %s to node tree '%s'\n %s",
type,
ntree->id.name + 2,
disabled_hint);
return NULL;
}
else {
BKE_reportf(reports,
RPT_ERROR,
"Cannot add node of type %s to node tree '%s'",
type,
ntree->id.name + 2);
return NULL;
}
}
node = nodeAddNode(C, ntree, type);
@ -1531,7 +1543,7 @@ char *rna_Node_ImageUser_path(PointerRNA *ptr)
return NULL;
}
static bool rna_Node_poll(bNodeType *ntype, bNodeTree *ntree)
static bool rna_Node_poll(bNodeType *ntype, bNodeTree *ntree, const char **UNUSED(r_disabled_hint))
{
extern FunctionRNA rna_Node_poll_func;
@ -1556,7 +1568,9 @@ static bool rna_Node_poll(bNodeType *ntype, bNodeTree *ntree)
return visible;
}
static bool rna_Node_poll_instance(bNode *node, bNodeTree *ntree)
static bool rna_Node_poll_instance(bNode *node,
bNodeTree *ntree,
const char **UNUSED(disabled_info))
{
extern FunctionRNA rna_Node_poll_instance_func;
@ -1581,10 +1595,12 @@ static bool rna_Node_poll_instance(bNode *node, bNodeTree *ntree)
return visible;
}
static bool rna_Node_poll_instance_default(bNode *node, bNodeTree *ntree)
static bool rna_Node_poll_instance_default(bNode *node,
bNodeTree *ntree,
const char **disabled_info)
{
/* use the basic poll function */
return rna_Node_poll(node->typeinfo, ntree);
return rna_Node_poll(node->typeinfo, ntree, disabled_info);
}
static void rna_Node_update_reg(bNodeTree *ntree, bNode *node)
@ -3214,18 +3230,20 @@ static PointerRNA rna_NodeInternal_output_template(StructRNA *srna, int index)
static bool rna_NodeInternal_poll(StructRNA *srna, bNodeTree *ntree)
{
bNodeType *ntype = RNA_struct_blender_type_get(srna);
return ntype && (!ntype->poll || ntype->poll(ntype, ntree));
const char *disabled_hint;
return ntype && (!ntype->poll || ntype->poll(ntype, ntree, &disabled_hint));
}
static bool rna_NodeInternal_poll_instance(bNode *node, bNodeTree *ntree)
{
bNodeType *ntype = node->typeinfo;
const char *disabled_hint;
if (ntype->poll_instance) {
return ntype->poll_instance(node, ntree);
return ntype->poll_instance(node, ntree, &disabled_hint);
}
else {
/* fall back to basic poll function */
return !ntype->poll || ntype->poll(ntype, ntree);
return !ntype->poll || ntype->poll(ntype, ntree, &disabled_hint);
}
}
@ -3408,7 +3426,8 @@ static void rna_NodeGroup_node_tree_set(PointerRNA *ptr,
bNode *node = ptr->data;
bNodeTree *ngroup = value.data;
if (nodeGroupPoll(ntree, ngroup)) {
const char *disabled_hint = NULL;
if (nodeGroupPoll(ntree, ngroup, &disabled_hint)) {
if (node->id) {
id_us_min(node->id);
}
@ -3430,7 +3449,8 @@ static bool rna_NodeGroup_node_tree_poll(PointerRNA *ptr, const PointerRNA value
return false;
}
return nodeGroupPoll(ntree, ngroup);
const char *disabled_hint = NULL;
return nodeGroupPoll(ntree, ngroup, &disabled_hint);
}
static StructRNA *rna_NodeGroup_interface_typef(PointerRNA *ptr)

View File

@ -23,9 +23,15 @@
#include "node_composite_util.h"
bool cmp_node_poll_default(bNodeType *UNUSED(ntype), bNodeTree *ntree)
bool cmp_node_poll_default(bNodeType *UNUSED(ntype),
bNodeTree *ntree,
const char **r_disabled_hint)
{
return STREQ(ntree->idname, "CompositorNodeTree");
if (!STREQ(ntree->idname, "CompositorNodeTree")) {
*r_disabled_hint = "Not a compositor node tree";
return false;
}
return true;
}
void cmp_node_update_default(bNodeTree *UNUSED(ntree), bNode *node)

View File

@ -54,7 +54,9 @@ extern "C" {
#define CMP_SCALE_MAX 12000
bool cmp_node_poll_default(struct bNodeType *ntype, struct bNodeTree *ntree);
bool cmp_node_poll_default(struct bNodeType *ntype,
struct bNodeTree *ntree,
const char **r_disabled_info);
void cmp_node_update_default(struct bNodeTree *ntree, struct bNode *node);
void cmp_node_type_base(
struct bNodeType *ntype, int type, const char *name, short nclass, short flag);

View File

@ -263,7 +263,9 @@ static void node_copy_cryptomatte(bNodeTree *UNUSED(dest_ntree),
dest_node->storage = dest_nc;
}
static bool node_poll_cryptomatte(bNodeType *UNUSED(ntype), bNodeTree *ntree)
static bool node_poll_cryptomatte(bNodeType *UNUSED(ntype),
bNodeTree *ntree,
const char **r_disabled_hint)
{
if (STREQ(ntree->idname, "CompositorNodeTree")) {
Scene *scene;
@ -276,8 +278,13 @@ static bool node_poll_cryptomatte(bNodeType *UNUSED(ntype), bNodeTree *ntree)
}
}
if (scene == nullptr) {
*r_disabled_hint =
"The node tree must be the compositing node tree of any scene in the file";
}
return scene != nullptr;
}
*r_disabled_hint = "Not a compositor node tree";
return false;
}

View File

@ -526,24 +526,32 @@ static void node_composit_init_rlayers(const bContext *C, PointerRNA *ptr)
}
}
static bool node_composit_poll_rlayers(bNodeType *UNUSED(ntype), bNodeTree *ntree)
static bool node_composit_poll_rlayers(bNodeType *UNUSED(ntype),
bNodeTree *ntree,
const char **r_disabled_hint)
{
if (STREQ(ntree->idname, "CompositorNodeTree")) {
Scene *scene;
/* XXX ugly: check if ntree is a local scene node tree.
* Render layers node can only be used in local scene->nodetree,
* since it directly links to the scene.
*/
for (scene = G.main->scenes.first; scene; scene = scene->id.next) {
if (scene->nodetree == ntree) {
break;
}
}
return (scene != NULL);
if (!STREQ(ntree->idname, "CompositorNodeTree")) {
*r_disabled_hint = "Not a compositor node tree";
return false;
}
return false;
Scene *scene;
/* XXX ugly: check if ntree is a local scene node tree.
* Render layers node can only be used in local scene->nodetree,
* since it directly links to the scene.
*/
for (scene = G.main->scenes.first; scene; scene = scene->id.next) {
if (scene->nodetree == ntree) {
break;
}
}
if (scene == NULL) {
*r_disabled_hint = "The node tree must be the compositing node tree of any scene in the file";
return false;
}
return true;
}
static void node_composit_free_rlayers(bNode *node)

View File

@ -17,10 +17,16 @@
#include "node_function_util.hh"
#include "node_util.h"
bool fn_node_poll_default(bNodeType *UNUSED(ntype), bNodeTree *ntree)
static bool fn_node_poll_default(bNodeType *UNUSED(ntype),
bNodeTree *ntree,
const char **r_disabled_hint)
{
/* Function nodes are only supported in simulation node trees so far. */
return STREQ(ntree->idname, "GeometryNodeTree");
if (!STREQ(ntree->idname, "GeometryNodeTree")) {
*r_disabled_hint = "Not a geometry node tree";
return false;
}
return true;
}
void fn_node_type_base(bNodeType *ntype, int type, const char *name, short nclass, short flag)

View File

@ -38,4 +38,3 @@
void fn_node_type_base(
struct bNodeType *ntype, int type, const char *name, short nclass, short flag);
bool fn_node_poll_default(struct bNodeType *ntype, struct bNodeTree *ntree);

View File

@ -56,9 +56,15 @@ void update_attribute_input_socket_availabilities(bNode &node,
} // namespace blender::nodes
bool geo_node_poll_default(bNodeType *UNUSED(ntype), bNodeTree *ntree)
bool geo_node_poll_default(bNodeType *UNUSED(ntype),
bNodeTree *ntree,
const char **r_disabled_hint)
{
return STREQ(ntree->idname, "GeometryNodeTree");
if (!STREQ(ntree->idname, "GeometryNodeTree")) {
*r_disabled_hint = "Not a geometry node tree";
return false;
}
return true;
}
void geo_node_type_base(bNodeType *ntype, int type, const char *name, short nclass, short flag)

View File

@ -36,7 +36,9 @@
void geo_node_type_base(
struct bNodeType *ntype, int type, const char *name, short nclass, short flag);
bool geo_node_poll_default(struct bNodeType *ntype, struct bNodeTree *ntree);
bool geo_node_poll_default(struct bNodeType *ntype,
struct bNodeTree *ntree,
const char **r_disabled_hint);
namespace blender::nodes {
void update_attribute_input_socket_availabilities(bNode &node,

View File

@ -79,12 +79,12 @@ void node_group_label(bNodeTree *UNUSED(ntree), bNode *node, char *label, int ma
BLI_strncpy(label, (node->id) ? node->id->name + 2 : IFACE_("Missing Data-Block"), maxlen);
}
bool node_group_poll_instance(bNode *node, bNodeTree *nodetree)
bool node_group_poll_instance(bNode *node, bNodeTree *nodetree, const char **disabled_hint)
{
if (node->typeinfo->poll(node->typeinfo, nodetree)) {
if (node->typeinfo->poll(node->typeinfo, nodetree, disabled_hint)) {
bNodeTree *grouptree = (bNodeTree *)node->id;
if (grouptree) {
return nodeGroupPoll(nodetree, grouptree);
return nodeGroupPoll(nodetree, grouptree, disabled_hint);
}
return true; /* without a linked node tree, group node is always ok */
@ -93,25 +93,27 @@ bool node_group_poll_instance(bNode *node, bNodeTree *nodetree)
return false;
}
int nodeGroupPoll(bNodeTree *nodetree, bNodeTree *grouptree)
bool nodeGroupPoll(bNodeTree *nodetree, bNodeTree *grouptree, const char **r_disabled_hint)
{
bNode *node;
int valid = 1;
bool valid = true;
/* unspecified node group, generally allowed
* (if anything, should be avoided on operator level)
*/
if (grouptree == NULL) {
return 1;
return true;
}
if (nodetree == grouptree) {
return 0;
*r_disabled_hint = "Nesting a node group inside of itself is not allowed";
return false;
}
for (node = grouptree->nodes.first; node; node = node->next) {
if (node->typeinfo->poll_instance && !node->typeinfo->poll_instance(node, nodetree)) {
valid = 0;
if (node->typeinfo->poll_instance &&
!node->typeinfo->poll_instance(node, nodetree, r_disabled_hint)) {
valid = false;
break;
}
}

View File

@ -32,7 +32,9 @@ extern "C" {
struct bNodeTree;
void node_group_label(struct bNodeTree *ntree, struct bNode *node, char *label, int maxlen);
bool node_group_poll_instance(struct bNode *node, struct bNodeTree *nodetree);
bool node_group_poll_instance(struct bNode *node,
struct bNodeTree *nodetree,
const char **r_disabled_hint);
void ntree_update_reroute_nodes(struct bNodeTree *ntree);

View File

@ -27,14 +27,24 @@
#include "node_exec.h"
bool sh_node_poll_default(bNodeType *UNUSED(ntype), bNodeTree *ntree)
bool sh_node_poll_default(bNodeType *UNUSED(ntype), bNodeTree *ntree, const char **r_disabled_hint)
{
return STREQ(ntree->idname, "ShaderNodeTree");
if (!STREQ(ntree->idname, "ShaderNodeTree")) {
*r_disabled_hint = "Not a shader node tree";
return false;
}
return true;
}
static bool sh_fn_poll_default(bNodeType *UNUSED(ntype), bNodeTree *ntree)
static bool sh_fn_poll_default(bNodeType *UNUSED(ntype),
bNodeTree *ntree,
const char **r_disabled_hint)
{
return STREQ(ntree->idname, "ShaderNodeTree") || STREQ(ntree->idname, "GeometryNodeTree");
if (!STREQ(ntree->idname, "ShaderNodeTree") && !STREQ(ntree->idname, "GeometryNodeTree")) {
*r_disabled_hint = "Not a shader or geometry node tree";
return false;
}
return true;
}
void sh_node_type_base(

View File

@ -80,7 +80,9 @@
extern "C" {
#endif
bool sh_node_poll_default(struct bNodeType *ntype, struct bNodeTree *ntree);
bool sh_node_poll_default(struct bNodeType *ntype,
struct bNodeTree *ntree,
const char **r_disabled_hint);
void sh_node_type_base(
struct bNodeType *ntype, int type, const char *name, short nclass, short flag);
void sh_fn_node_type_base(

View File

@ -39,9 +39,15 @@
#include "node_texture_util.h"
bool tex_node_poll_default(bNodeType *UNUSED(ntype), bNodeTree *ntree)
bool tex_node_poll_default(bNodeType *UNUSED(ntype),
bNodeTree *ntree,
const char **r_disabled_hint)
{
return STREQ(ntree->idname, "TextureNodeTree");
if (!STREQ(ntree->idname, "TextureNodeTree")) {
*r_disabled_hint = "Not a texture node tree";
return false;
}
return true;
}
void tex_node_type_base(

View File

@ -106,7 +106,9 @@ typedef struct TexDelegate {
int type;
} TexDelegate;
bool tex_node_poll_default(struct bNodeType *ntype, struct bNodeTree *ntree);
bool tex_node_poll_default(struct bNodeType *ntype,
struct bNodeTree *ntree,
const char **r_disabled_hint);
void tex_node_type_base(
struct bNodeType *ntype, int type, const char *name, short nclass, short flag);