Geometry Nodes: Initial socket visualization for fields.

This implements the update logic for the vizualization of which
sockets pass data or constants directly, and which pass functions.
The socket shapes may still have to be updated. That should be
done separately, because it might be a bit more involved, because
socket shapes are currently linked to keyframe shapes. Currently
the circle and diamond shapes are used with the following meanings:

 - Input Sockets:
    - Circle: Required to be a single value.
    - Diamond: This input supports fields.
 - Output Sockets:
    - Circle: This output is a single value.
    - Diamond: This output may be a field.

Connecting a field to a circle input socket is an error, since a
field cannot be converted to a single value. If the socket shape
is a diamond with a dot in the middle, it means it is currently
a single value, but could be a field.

In addition to socket shapes, the intention is to draw node links
differently based on the field status. However, the exact method for
conveying that isn't decided yet.

Differential Revision: https://developer.blender.org/D12584
This commit is contained in:
Jacques Lucke 2021-09-23 15:21:31 -05:00 committed by Hans Goudey
parent c1b925f7ff
commit 61f3d4eb7c
Notes: blender-bot 2023-02-14 03:00:45 +01:00
Referenced by commit 797064544e, Geometry Nodes: Only show attribute toggle for field inputs
Referenced by issue #91215, Field visualization with socket shapes
43 changed files with 908 additions and 32 deletions

View File

@ -480,7 +480,7 @@ bool ntreeHasType(const struct bNodeTree *ntree, int type);
bool ntreeHasTree(const struct bNodeTree *ntree, const struct bNodeTree *lookup);
void ntreeUpdateTree(struct Main *main, struct bNodeTree *ntree);
void ntreeUpdateAllNew(struct Main *main);
void ntreeUpdateAllUsers(struct Main *main, struct ID *id);
void ntreeUpdateAllUsers(struct Main *main, struct ID *id, int tree_update_flag);
void ntreeGetDependencyList(struct bNodeTree *ntree,
struct bNode ***r_deplist,

View File

@ -345,7 +345,7 @@ static void libblock_remap_data_postprocess_obdata_relink(Main *bmain, Object *o
static void libblock_remap_data_postprocess_nodetree_update(Main *bmain, ID *new_id)
{
/* Update all group nodes using a node group. */
ntreeUpdateAllUsers(bmain, new_id);
ntreeUpdateAllUsers(bmain, new_id, 0);
}
/**

View File

@ -52,9 +52,12 @@
#include "BLI_map.hh"
#include "BLI_math.h"
#include "BLI_path_util.h"
#include "BLI_set.hh"
#include "BLI_stack.hh"
#include "BLI_string.h"
#include "BLI_string_utils.h"
#include "BLI_utildefines.h"
#include "BLI_vector_set.hh"
#include "BLT_translation.h"
@ -80,6 +83,7 @@
#include "NOD_function.h"
#include "NOD_geometry.h"
#include "NOD_node_declaration.hh"
#include "NOD_node_tree_ref.hh"
#include "NOD_shader.h"
#include "NOD_socket.h"
#include "NOD_texture.h"
@ -93,6 +97,20 @@
#define NODE_DEFAULT_MAX_WIDTH 700
using blender::Array;
using blender::MutableSpan;
using blender::Set;
using blender::Span;
using blender::Stack;
using blender::Vector;
using blender::VectorSet;
using blender::nodes::InputSocketFieldType;
using blender::nodes::NodeDeclaration;
using blender::nodes::OutputFieldDependency;
using blender::nodes::OutputSocketFieldType;
using blender::nodes::SocketDeclaration;
using namespace blender::nodes::node_tree_ref_types;
/* Fallback types for undefined tree, nodes, sockets */
static bNodeTreeType NodeTreeTypeUndefined;
bNodeType NodeTypeUndefined;
@ -110,6 +128,10 @@ static void node_socket_interface_free(bNodeTree *UNUSED(ntree),
static void nodeMuteRerouteOutputLinks(struct bNodeTree *ntree,
struct bNode *node,
const bool mute);
static FieldInferencingInterface *node_field_inferencing_interface_copy(
const FieldInferencingInterface &field_inferencing_interface);
static void node_field_inferencing_interface_free(
const FieldInferencingInterface *field_inferencing_interface);
static void ntree_init_data(ID *id)
{
@ -220,6 +242,11 @@ static void ntree_copy_data(Main *UNUSED(bmain), ID *id_dst, const ID *id_src, c
/* node tree will generate its own interface type */
ntree_dst->interface_type = nullptr;
if (ntree_src->field_inferencing_interface) {
ntree_dst->field_inferencing_interface = node_field_inferencing_interface_copy(
*ntree_src->field_inferencing_interface);
}
}
static void ntree_free_data(ID *id)
@ -265,6 +292,8 @@ static void ntree_free_data(ID *id)
MEM_freeN(sock);
}
node_field_inferencing_interface_free(ntree->field_inferencing_interface);
/* free preview hash */
if (ntree->previews) {
BKE_node_instance_hash_free(ntree->previews, (bNodeInstanceValueFP)BKE_node_preview_free);
@ -647,6 +676,8 @@ void ntreeBlendReadData(BlendDataReader *reader, bNodeTree *ntree)
ntree->progress = nullptr;
ntree->execdata = nullptr;
ntree->field_inferencing_interface = nullptr;
BLO_read_data_address(reader, &ntree->adt);
BKE_animdata_blend_read_data(reader, ntree->adt);
@ -4425,7 +4456,518 @@ void ntreeUpdateAllNew(Main *main)
FOREACH_NODETREE_END;
}
void ntreeUpdateAllUsers(Main *main, ID *id)
/**
* Information about how a node interacts with fields.
*/
struct FieldInferencingInterface {
Vector<InputSocketFieldType> inputs;
Vector<OutputFieldDependency> outputs;
friend bool operator==(const FieldInferencingInterface &a, const FieldInferencingInterface &b)
{
return a.inputs == b.inputs && a.outputs == b.outputs;
}
friend bool operator!=(const FieldInferencingInterface &a, const FieldInferencingInterface &b)
{
return !(a == b);
}
};
static FieldInferencingInterface *node_field_inferencing_interface_copy(
const FieldInferencingInterface &field_inferencing_interface)
{
return new FieldInferencingInterface(field_inferencing_interface);
}
static void node_field_inferencing_interface_free(
const FieldInferencingInterface *field_inferencing_interface)
{
delete field_inferencing_interface;
}
namespace blender::bke::node_field_inferencing {
static bool is_field_socket_type(eNodeSocketDatatype type)
{
return ELEM(type, SOCK_FLOAT, SOCK_INT, SOCK_BOOLEAN, SOCK_VECTOR, SOCK_RGBA);
}
static bool is_field_socket_type(const SocketRef &socket)
{
return is_field_socket_type((eNodeSocketDatatype)socket.typeinfo()->type);
}
static bool update_field_inferencing(bNodeTree &btree);
static InputSocketFieldType get_interface_input_field_type(const NodeRef &node,
const InputSocketRef &socket)
{
if (!is_field_socket_type(socket)) {
return InputSocketFieldType::None;
}
if (node.is_reroute_node()) {
return InputSocketFieldType::IsSupported;
}
if (node.is_group_output_node()) {
/* Outputs always support fields when the data type is correct. */
return InputSocketFieldType::IsSupported;
}
const NodeDeclaration *node_decl = node.declaration();
/* Node declarations should be implemented for nodes involved here. */
BLI_assert(node_decl != nullptr);
if (node_decl->is_function_node()) {
/* In a function node, every socket supports fields. */
return InputSocketFieldType::IsSupported;
}
/* Get the field type from the declaration. */
const SocketDeclaration &socket_decl = *node_decl->inputs()[socket.index()];
return socket_decl.input_field_type();
}
static OutputFieldDependency get_interface_output_field_dependency(const NodeRef &node,
const OutputSocketRef &socket)
{
if (!is_field_socket_type(socket)) {
/* Non-field sockets always output data. */
return OutputFieldDependency::ForDataSource();
}
if (node.is_reroute_node()) {
/* The reroute just forwards what is passed in. */
return OutputFieldDependency::ForDependentField();
}
if (node.is_group_input_node()) {
/* Input nodes get special treatment in #determine_group_input_states. */
return OutputFieldDependency::ForDependentField();
}
const NodeDeclaration *node_decl = node.declaration();
/* Node declarations should be implemented for nodes involved here. */
BLI_assert(node_decl != nullptr);
if (node_decl->is_function_node()) {
/* In a generic function node, all outputs depend on all inputs. */
return OutputFieldDependency::ForDependentField();
}
/* Use the socket declaration. */
const SocketDeclaration &socket_decl = *node_decl->outputs()[socket.index()];
return socket_decl.output_field_dependency();
}
/**
* Retrieves information about how the node interacts with fields.
* In the future, this information can be stored in the node declaration. This would allow this
* function to return a reference, making it more efficient.
*/
static FieldInferencingInterface get_node_field_inferencing_interface(const NodeRef &node)
{
/* Node groups already reference all required information, so just return that. */
if (node.is_group_node()) {
bNodeTree *group = (bNodeTree *)node.bnode()->id;
if (group == nullptr) {
return FieldInferencingInterface();
}
if (group->field_inferencing_interface == nullptr) {
/* Update group recursively. */
update_field_inferencing(*group);
}
return *group->field_inferencing_interface;
}
FieldInferencingInterface inferencing_interface;
for (const InputSocketRef *input_socket : node.inputs()) {
inferencing_interface.inputs.append(get_interface_input_field_type(node, *input_socket));
}
for (const OutputSocketRef *output_socket : node.outputs()) {
inferencing_interface.outputs.append(
get_interface_output_field_dependency(node, *output_socket));
}
return inferencing_interface;
}
/**
* This struct contains information for every socket. The values are propagated through the
* network.
*/
struct SocketFieldState {
/* This socket is currently a single value. It could become a field though. */
bool is_single = true;
/* This socket is required to be a single value. It must not be a field. */
bool requires_single = false;
/* This socket starts a new field. */
bool is_field_source = false;
};
static Vector<const InputSocketRef *> gather_input_socket_dependencies(
const OutputFieldDependency &field_dependency, const NodeRef &node)
{
const OutputSocketFieldType type = field_dependency.field_type();
Vector<const InputSocketRef *> input_sockets;
switch (type) {
case OutputSocketFieldType::FieldSource:
case OutputSocketFieldType::None: {
break;
}
case OutputSocketFieldType::DependentField: {
/* This output depends on all inputs. */
input_sockets.extend(node.inputs());
break;
}
case OutputSocketFieldType::PartiallyDependent: {
/* This output depends only on a few inputs. */
for (const int i : field_dependency.linked_input_indices()) {
input_sockets.append(&node.input(i));
}
break;
}
}
return input_sockets;
}
/**
* Check what the group output socket depends on. Potentially traverses the node tree
* to figure out if it is always a field or if it depends on any group inputs.
*/
static OutputFieldDependency find_group_output_dependencies(
const InputSocketRef &group_output_socket,
const Span<SocketFieldState> field_state_by_socket_id)
{
if (!is_field_socket_type(group_output_socket)) {
return OutputFieldDependency::ForDataSource();
}
/* Use a Set here instead of an array indexed by socket id, because we my only need to look at
* very few sockets. */
Set<const InputSocketRef *> handled_sockets;
Stack<const InputSocketRef *> sockets_to_check;
handled_sockets.add(&group_output_socket);
sockets_to_check.push(&group_output_socket);
/* Keeps track of group input indices that are (indirectly) connected to the output. */
Vector<int> linked_input_indices;
while (!sockets_to_check.is_empty()) {
const InputSocketRef *input_socket = sockets_to_check.pop();
for (const OutputSocketRef *origin_socket : input_socket->logically_linked_sockets()) {
const NodeRef &origin_node = origin_socket->node();
const SocketFieldState &origin_state = field_state_by_socket_id[origin_socket->id()];
if (origin_state.is_field_source) {
if (origin_node.is_group_input_node()) {
/* Found a group input that the group output depends on. */
linked_input_indices.append_non_duplicates(origin_socket->index());
}
else {
/* Found a field source that is not the group input. So the output is always a field. */
return OutputFieldDependency::ForFieldSource();
}
}
else if (!origin_state.is_single) {
const FieldInferencingInterface inferencing_interface =
get_node_field_inferencing_interface(origin_node);
const OutputFieldDependency &field_dependency =
inferencing_interface.outputs[origin_socket->index()];
/* Propagate search further to the left. */
for (const InputSocketRef *origin_input_socket :
gather_input_socket_dependencies(field_dependency, origin_node)) {
if (!field_state_by_socket_id[origin_input_socket->id()].is_single) {
if (handled_sockets.add(origin_input_socket)) {
sockets_to_check.push(origin_input_socket);
}
}
}
}
}
}
return OutputFieldDependency::ForPartiallyDependentField(std::move(linked_input_indices));
}
static void propagate_data_requirements_from_right_to_left(
const NodeTreeRef &tree, const MutableSpan<SocketFieldState> field_state_by_socket_id)
{
const Vector<const NodeRef *> sorted_nodes = tree.toposort(
NodeTreeRef::ToposortDirection::RightToLeft);
for (const NodeRef *node : sorted_nodes) {
const FieldInferencingInterface inferencing_interface = get_node_field_inferencing_interface(
*node);
for (const OutputSocketRef *output_socket : node->outputs()) {
SocketFieldState &state = field_state_by_socket_id[output_socket->id()];
const OutputFieldDependency &field_dependency =
inferencing_interface.outputs[output_socket->index()];
if (field_dependency.field_type() == OutputSocketFieldType::FieldSource) {
continue;
}
if (field_dependency.field_type() == OutputSocketFieldType::None) {
state.requires_single = true;
continue;
}
/* The output is required to be a single value when it is connected to any input that does
* not support fields. */
for (const InputSocketRef *target_socket : output_socket->directly_linked_sockets()) {
state.requires_single |= field_state_by_socket_id[target_socket->id()].requires_single;
}
if (state.requires_single) {
bool any_input_is_field_implicitly = false;
const Vector<const InputSocketRef *> connected_inputs = gather_input_socket_dependencies(
field_dependency, *node);
for (const InputSocketRef *input_socket : connected_inputs) {
if (inferencing_interface.inputs[input_socket->index()] ==
InputSocketFieldType::Implicit) {
if (!input_socket->is_logically_linked()) {
any_input_is_field_implicitly = true;
break;
}
}
}
if (any_input_is_field_implicitly) {
/* This output isn't a single value actually. */
state.requires_single = false;
}
else {
/* If the output is required to be a single value, the connected inputs in the same node
* must not be fields as well. */
for (const InputSocketRef *input_socket : connected_inputs) {
field_state_by_socket_id[input_socket->id()].requires_single = true;
}
}
}
}
/* Some inputs do not require fields independent of what the outputs are connected to. */
for (const InputSocketRef *input_socket : node->inputs()) {
SocketFieldState &state = field_state_by_socket_id[input_socket->id()];
if (inferencing_interface.inputs[input_socket->index()] == InputSocketFieldType::None) {
state.requires_single = true;
}
}
}
}
static void determine_group_input_states(
const NodeTreeRef &tree,
FieldInferencingInterface &new_inferencing_interface,
const MutableSpan<SocketFieldState> field_state_by_socket_id)
{
{
/* Non-field inputs never support fields. */
int index;
LISTBASE_FOREACH_INDEX (bNodeSocket *, group_input, &tree.btree()->inputs, index) {
if (!is_field_socket_type((eNodeSocketDatatype)group_input->type)) {
new_inferencing_interface.inputs[index] = InputSocketFieldType::None;
}
}
}
/* Check if group inputs are required to be single values, because they are (indirectly)
* connected to some socket that does not support fields. */
for (const NodeRef *node : tree.nodes_by_type("NodeGroupInput")) {
for (const OutputSocketRef *output_socket : node->outputs().drop_back(1)) {
SocketFieldState &state = field_state_by_socket_id[output_socket->id()];
if (state.requires_single) {
new_inferencing_interface.inputs[output_socket->index()] = InputSocketFieldType::None;
}
}
}
/* If an input does not support fields, this should be reflected in all Group Input nodes. */
for (const NodeRef *node : tree.nodes_by_type("NodeGroupInput")) {
for (const OutputSocketRef *output_socket : node->outputs().drop_back(1)) {
SocketFieldState &state = field_state_by_socket_id[output_socket->id()];
const bool supports_field = new_inferencing_interface.inputs[output_socket->index()] !=
InputSocketFieldType::None;
if (supports_field) {
state.is_single = false;
state.is_field_source = true;
}
else {
state.requires_single = true;
}
}
SocketFieldState &dummy_socket_state = field_state_by_socket_id[node->outputs().last()->id()];
dummy_socket_state.requires_single = true;
}
}
static void propagate_field_status_from_left_to_right(
const NodeTreeRef &tree, const MutableSpan<SocketFieldState> field_state_by_socket_id)
{
Vector<const NodeRef *> sorted_nodes = tree.toposort(
NodeTreeRef::ToposortDirection::LeftToRight);
for (const NodeRef *node : sorted_nodes) {
if (node->is_group_input_node()) {
continue;
}
const FieldInferencingInterface inferencing_interface = get_node_field_inferencing_interface(
*node);
/* Update field state of input sockets, also taking into account linked origin sockets. */
for (const InputSocketRef *input_socket : node->inputs()) {
SocketFieldState &state = field_state_by_socket_id[input_socket->id()];
if (state.requires_single) {
state.is_single = true;
continue;
}
state.is_single = true;
if (input_socket->logically_linked_sockets().is_empty()) {
if (inferencing_interface.inputs[input_socket->index()] ==
InputSocketFieldType::Implicit) {
state.is_single = false;
}
}
else {
for (const OutputSocketRef *origin_socket : input_socket->logically_linked_sockets()) {
if (!field_state_by_socket_id[origin_socket->id()].is_single) {
state.is_single = false;
break;
}
}
}
}
/* Update field state of output sockets, also taking into account input sockets. */
for (const OutputSocketRef *output_socket : node->outputs()) {
SocketFieldState &state = field_state_by_socket_id[output_socket->id()];
const OutputFieldDependency &field_dependency =
inferencing_interface.outputs[output_socket->index()];
switch (field_dependency.field_type()) {
case OutputSocketFieldType::None: {
state.is_single = true;
break;
}
case OutputSocketFieldType::FieldSource: {
state.is_single = false;
state.is_field_source = true;
break;
}
case OutputSocketFieldType::PartiallyDependent:
case OutputSocketFieldType::DependentField: {
for (const InputSocketRef *input_socket :
gather_input_socket_dependencies(field_dependency, *node)) {
if (!field_state_by_socket_id[input_socket->id()].is_single) {
state.is_single = false;
break;
}
}
break;
}
}
}
}
}
static void determine_group_output_states(const NodeTreeRef &tree,
FieldInferencingInterface &new_inferencing_interface,
const Span<SocketFieldState> field_state_by_socket_id)
{
for (const NodeRef *group_output_node : tree.nodes_by_type("NodeGroupOutput")) {
/* Ignore inactive group output nodes. */
if (!(group_output_node->bnode()->flag & NODE_DO_OUTPUT)) {
continue;
}
/* Determine dependencies of all group outputs. */
for (const InputSocketRef *group_output_socket : group_output_node->inputs().drop_back(1)) {
OutputFieldDependency field_dependency = find_group_output_dependencies(
*group_output_socket, field_state_by_socket_id);
new_inferencing_interface.outputs[group_output_socket->index()] = std::move(
field_dependency);
}
break;
}
}
static void update_socket_shapes(const NodeTreeRef &tree,
const Span<SocketFieldState> field_state_by_socket_id)
{
const eNodeSocketDisplayShape requires_data_shape = SOCK_DISPLAY_SHAPE_CIRCLE;
const eNodeSocketDisplayShape data_but_can_be_field_shape = SOCK_DISPLAY_SHAPE_DIAMOND_DOT;
const eNodeSocketDisplayShape is_field_shape = SOCK_DISPLAY_SHAPE_DIAMOND;
for (const InputSocketRef *socket : tree.input_sockets()) {
bNodeSocket *bsocket = socket->bsocket();
const SocketFieldState &state = field_state_by_socket_id[socket->id()];
if (state.requires_single) {
bsocket->display_shape = requires_data_shape;
}
else if (state.is_single) {
bsocket->display_shape = data_but_can_be_field_shape;
}
else {
bsocket->display_shape = is_field_shape;
}
}
for (const OutputSocketRef *socket : tree.output_sockets()) {
bNodeSocket *bsocket = socket->bsocket();
const SocketFieldState &state = field_state_by_socket_id[socket->id()];
if (state.requires_single) {
bsocket->display_shape = requires_data_shape;
}
else if (state.is_single) {
bsocket->display_shape = data_but_can_be_field_shape;
}
else {
bsocket->display_shape = is_field_shape;
}
}
}
static bool update_field_inferencing(bNodeTree &btree)
{
using namespace blender::nodes;
if (btree.type != NTREE_GEOMETRY) {
return false;
}
/* Create new inferencing interface for this node group. */
FieldInferencingInterface *new_inferencing_interface = new FieldInferencingInterface();
new_inferencing_interface->inputs.resize(BLI_listbase_count(&btree.inputs),
InputSocketFieldType::IsSupported);
new_inferencing_interface->outputs.resize(BLI_listbase_count(&btree.outputs),
OutputFieldDependency::ForDataSource());
/* Create #NodeTreeRef to accelerate various queries on the node tree (e.g. linked sockets). */
const NodeTreeRef tree{&btree};
/* Keep track of the state of all sockets. The index into this array is #SocketRef::id(). */
Array<SocketFieldState> field_state_by_socket_id(tree.sockets().size());
propagate_data_requirements_from_right_to_left(tree, field_state_by_socket_id);
determine_group_input_states(tree, *new_inferencing_interface, field_state_by_socket_id);
propagate_field_status_from_left_to_right(tree, field_state_by_socket_id);
determine_group_output_states(tree, *new_inferencing_interface, field_state_by_socket_id);
update_socket_shapes(tree, field_state_by_socket_id);
/* Update the previous group interface. */
const bool group_interface_changed = btree.field_inferencing_interface == nullptr ||
*btree.field_inferencing_interface !=
*new_inferencing_interface;
delete btree.field_inferencing_interface;
btree.field_inferencing_interface = new_inferencing_interface;
return group_interface_changed;
}
} // namespace blender::bke::node_field_inferencing
/**
* \param tree_update_flag: #eNodeTreeUpdate enum.
*/
void ntreeUpdateAllUsers(Main *main, ID *id, const int tree_update_flag)
{
if (id == nullptr) {
return;
@ -4446,7 +4988,8 @@ void ntreeUpdateAllUsers(Main *main, ID *id)
}
if (need_update) {
ntreeUpdateTree(nullptr, ntree);
ntree->update |= tree_update_flag;
ntreeUpdateTree(tree_update_flag ? main : nullptr, ntree);
}
}
FOREACH_NODETREE_END;
@ -4508,8 +5051,18 @@ void ntreeUpdateTree(Main *bmain, bNodeTree *ntree)
ntreeInterfaceTypeUpdate(ntree);
}
int tree_user_update_flag = 0;
if (ntree->update & NTREE_UPDATE) {
/* If the field interface of this node tree has changed, all node trees using
* this group will need to recalculate their interface as well. */
if (blender::bke::node_field_inferencing::update_field_inferencing(*ntree)) {
tree_user_update_flag |= NTREE_UPDATE_FIELD_INFERENCING;
}
}
if (bmain) {
ntreeUpdateAllUsers(bmain, &ntree->id);
ntreeUpdateAllUsers(bmain, &ntree->id, tree_user_update_flag);
}
if (ntree->update & (NTREE_UPDATE_LINKS | NTREE_UPDATE_NODES)) {

View File

@ -4282,6 +4282,13 @@ void node_draw_link(View2D *v2d, SpaceNode *snode, bNodeLink *link)
// th_col3 = -1; /* no shadow */
}
}
/* Links from field to non-field sockets are not allowed. */
if (snode->edittree->type == NTREE_GEOMETRY && !(link->flag & NODE_LINK_DRAGGED)) {
if ((link->fromsock && link->fromsock->display_shape == SOCK_DISPLAY_SHAPE_DIAMOND) &&
(link->tosock && link->tosock->display_shape == SOCK_DISPLAY_SHAPE_CIRCLE)) {
th_col1 = th_col2 = th_col3 = TH_REDALERT;
}
}
node_draw_link_bezier(v2d, snode, link, th_col1, th_col2, th_col3);
}

View File

@ -220,6 +220,7 @@ static LinkData *create_drag_link(Main *bmain, SpaceNode *snode, bNode *node, bN
if (node_connected_to_output(bmain, snode->edittree, node)) {
oplink->flag |= NODE_LINK_TEST;
}
oplink->flag |= NODE_LINK_DRAGGED;
return linkdata;
}
@ -894,6 +895,8 @@ static void node_link_exit(bContext *C, wmOperator *op, bool apply_links)
*/
do_tag_update |= (link->flag & NODE_LINK_TEST) != 0;
link->flag &= ~NODE_LINK_DRAGGED;
if (apply_links && link->tosock && link->fromsock) {
/* before actually adding the link,
* let nodes perform special link insertion handling
@ -1097,6 +1100,7 @@ static bNodeLinkDrag *node_link_init(Main *bmain, SpaceNode *snode, float cursor
*oplink = *link;
oplink->next = oplink->prev = nullptr;
oplink->flag |= NODE_LINK_VALID;
oplink->flag |= NODE_LINK_DRAGGED;
/* The link could be disconnected and in that case we
* wouldn't be able to check whether tag update is
@ -1150,6 +1154,7 @@ static bNodeLinkDrag *node_link_init(Main *bmain, SpaceNode *snode, float cursor
*oplink = *link_to_pick;
oplink->next = oplink->prev = nullptr;
oplink->flag |= NODE_LINK_VALID;
oplink->flag |= NODE_LINK_DRAGGED;
oplink->flag &= ~NODE_LINK_TEST;
if (node_connected_to_output(bmain, snode->edittree, link_to_pick->tonode)) {
oplink->flag |= NODE_LINK_TEST;

View File

@ -445,6 +445,7 @@ typedef struct bNodeLink {
#define NODE_LINK_TEST (1 << 2) /* free test flag, undefined */
#define NODE_LINK_TEMP_HIGHLIGHT (1 << 3) /* Link is highlighted for picking. */
#define NODE_LINK_MUTED (1 << 4) /* Link is muted. */
#define NODE_LINK_DRAGGED (1 << 5) /* Node link is being dragged by the user. */
/* tree->edit_quality/tree->render_quality */
#define NTREE_QUALITY_HIGH 0
@ -459,6 +460,8 @@ typedef struct bNodeLink {
#define NTREE_CHUNKSIZE_512 512
#define NTREE_CHUNKSIZE_1024 1024
struct FieldInferencingInterface;
/* the basis for a Node tree, all links and nodes reside internal here */
/* only re-usable node trees are in the library though,
* materials and textures allocate own tree struct */
@ -481,6 +484,8 @@ typedef struct bNodeTree {
float view_center[2];
ListBase nodes, links;
/** Information about how inputs and outputs of the node group interact with fields. */
struct FieldInferencingInterface *field_inferencing_interface;
/** Set init on fileread. */
int type, init;
@ -575,6 +580,9 @@ typedef enum eNodeTreeUpdate {
NTREE_UPDATE_NODES = (1 << 1), /* nodes or sockets have been added or removed */
NTREE_UPDATE_GROUP_IN = (1 << 4), /* group inputs have changed */
NTREE_UPDATE_GROUP_OUT = (1 << 5), /* group outputs have changed */
/* The field interface has changed. So e.g. an output that was always a field before is not
* anymore. This implies that the field type inferencing has to be done again. */
NTREE_UPDATE_FIELD_INFERENCING = (1 << 6),
/* group has changed (generic flag including all other group flags) */
NTREE_UPDATE_GROUP = (NTREE_UPDATE_GROUP_IN | NTREE_UPDATE_GROUP_OUT),
} eNodeTreeUpdate;

View File

@ -27,6 +27,91 @@ namespace blender::nodes {
class NodeDeclarationBuilder;
enum class InputSocketFieldType {
/** The input is required to be a single value. */
None,
/** The input can be a field. */
IsSupported,
/** The input can be a field and is a field implicitly if nothing is connected. */
Implicit,
};
enum class OutputSocketFieldType {
/** The output is always a single value. */
None,
/** The output is always a field, independent of the inputs. */
FieldSource,
/** If any input is a field, this output will be a field as well. */
DependentField,
/** If any of a subset of inputs is a field, this out will be a field as well.
* The subset is defined by the vector of indices. */
PartiallyDependent,
};
/**
* Contains information about how a node output's field state depends on inputs of the same node.
*/
class OutputFieldDependency {
private:
OutputSocketFieldType type_ = OutputSocketFieldType::None;
Vector<int> linked_input_indices_;
public:
static OutputFieldDependency ForFieldSource()
{
OutputFieldDependency field_dependency;
field_dependency.type_ = OutputSocketFieldType::FieldSource;
return field_dependency;
}
static OutputFieldDependency ForDataSource()
{
OutputFieldDependency field_dependency;
field_dependency.type_ = OutputSocketFieldType::None;
return field_dependency;
}
static OutputFieldDependency ForPartiallyDependentField(Vector<int> indices)
{
OutputFieldDependency field_dependency;
if (indices.is_empty()) {
field_dependency.type_ = OutputSocketFieldType::None;
}
else {
field_dependency.type_ = OutputSocketFieldType::PartiallyDependent;
field_dependency.linked_input_indices_ = std::move(indices);
}
return field_dependency;
}
static OutputFieldDependency ForDependentField()
{
OutputFieldDependency field_dependency;
field_dependency.type_ = OutputSocketFieldType::DependentField;
return field_dependency;
}
OutputSocketFieldType field_type() const
{
return type_;
}
Span<int> linked_input_indices() const
{
return linked_input_indices_;
}
friend bool operator==(const OutputFieldDependency &a, const OutputFieldDependency &b)
{
return a.type_ == b.type_ && a.linked_input_indices_ == b.linked_input_indices_;
}
friend bool operator!=(const OutputFieldDependency &a, const OutputFieldDependency &b)
{
return !(a == b);
}
};
/**
* Describes a single input or output socket. This is subclassed for different socket types.
*/
@ -39,6 +124,9 @@ class SocketDeclaration {
bool is_multi_input_ = false;
bool no_mute_links_ = false;
InputSocketFieldType input_field_type_ = InputSocketFieldType::None;
OutputFieldDependency output_field_dependency_;
friend NodeDeclarationBuilder;
template<typename SocketDecl> friend class SocketDeclarationBuilder;
@ -52,6 +140,9 @@ class SocketDeclaration {
StringRefNull name() const;
StringRefNull identifier() const;
InputSocketFieldType input_field_type() const;
const OutputFieldDependency &output_field_dependency() const;
protected:
void set_common_flags(bNodeSocket &socket) const;
bool matches_common_data(const bNodeSocket &socket) const;
@ -100,6 +191,41 @@ class SocketDeclarationBuilder : public BaseSocketDeclarationBuilder {
decl_->no_mute_links_ = value;
return *(Self *)this;
}
/** The input socket allows passing in a field. */
Self &supports_field()
{
decl_->input_field_type_ = InputSocketFieldType::IsSupported;
return *(Self *)this;
}
/** The input supports a field and is a field by default when nothing is connected. */
Self &implicit_field()
{
decl_->input_field_type_ = InputSocketFieldType::Implicit;
return *(Self *)this;
}
/** The output is always a field, regardless of any inputs. */
Self &field_source()
{
decl_->output_field_dependency_ = OutputFieldDependency::ForFieldSource();
return *(Self *)this;
}
/** The output is a field if any of the inputs is a field. */
Self &dependent_field()
{
decl_->output_field_dependency_ = OutputFieldDependency::ForDependentField();
return *(Self *)this;
}
/** The output is a field if any of the inputs with indices in the given list is a field. */
Self &dependent_field(Vector<int> input_dependencies)
{
decl_->output_field_dependency_ = OutputFieldDependency::ForPartiallyDependentField(
std::move(input_dependencies));
}
};
using SocketDeclarationPtr = std::unique_ptr<SocketDeclaration>;
@ -108,6 +234,7 @@ class NodeDeclaration {
private:
Vector<SocketDeclarationPtr> inputs_;
Vector<SocketDeclarationPtr> outputs_;
bool is_function_node_ = false;
friend NodeDeclarationBuilder;
@ -118,6 +245,11 @@ class NodeDeclaration {
Span<SocketDeclarationPtr> inputs() const;
Span<SocketDeclarationPtr> outputs() const;
bool is_function_node() const
{
return is_function_node_;
}
MEM_CXX_CLASS_ALLOC_FUNCS("NodeDeclaration")
};
@ -129,6 +261,15 @@ class NodeDeclarationBuilder {
public:
NodeDeclarationBuilder(NodeDeclaration &declaration);
/**
* All inputs support fields, and all outputs are fields if any of the inputs is a field.
* Calling field status definitions on each socket is unnecessary.
*/
void is_function_node(bool value = true)
{
declaration_.is_function_node_ = value;
}
template<typename DeclType>
typename DeclType::Builder &add_input(StringRef name, StringRef identifier = "");
template<typename DeclType>
@ -155,6 +296,16 @@ inline StringRefNull SocketDeclaration::identifier() const
return identifier_;
}
inline InputSocketFieldType SocketDeclaration::input_field_type() const
{
return input_field_type_;
}
inline const OutputFieldDependency &SocketDeclaration::output_field_dependency() const
{
return output_field_dependency_;
}
/* --------------------------------------------------------------------
* NodeDeclarationBuilder inline methods.
*/

View File

@ -182,6 +182,7 @@ class NodeRef : NonCopyable, NonMovable {
Span<const InputSocketRef *> inputs() const;
Span<const OutputSocketRef *> outputs() const;
Span<const InternalLinkRef *> internal_links() const;
Span<const SocketRef *> sockets(eNodeSocketInOut in_out) const;
const InputSocketRef &input(int index) const;
const OutputSocketRef &output(int index) const;
@ -189,6 +190,10 @@ class NodeRef : NonCopyable, NonMovable {
const InputSocketRef &input_by_identifier(StringRef identifier) const;
const OutputSocketRef &output_by_identifier(StringRef identifier) const;
bool any_input_is_directly_linked() const;
bool any_output_is_directly_linked() const;
bool any_socket_is_directly_linked(eNodeSocketInOut in_out) const;
bNode *bnode() const;
bNodeTree *btree() const;
@ -196,6 +201,7 @@ class NodeRef : NonCopyable, NonMovable {
StringRefNull idname() const;
StringRefNull name() const;
bNodeType *typeinfo() const;
const NodeDeclaration *declaration() const;
int id() const;
@ -272,6 +278,13 @@ class NodeTreeRef : NonCopyable, NonMovable {
bool has_link_cycles() const;
bool has_undefined_nodes_or_sockets() const;
enum class ToposortDirection {
LeftToRight,
RightToLeft,
};
Vector<const NodeRef *> toposort(ToposortDirection direction) const;
bNodeTree *btree() const;
StringRefNull name() const;
@ -496,6 +509,12 @@ inline Span<const OutputSocketRef *> NodeRef::outputs() const
return outputs_;
}
inline Span<const SocketRef *> NodeRef::sockets(const eNodeSocketInOut in_out) const
{
return in_out == SOCK_IN ? inputs_.as_span().cast<const SocketRef *>() :
outputs_.as_span().cast<const SocketRef *>();
}
inline Span<const InternalLinkRef *> NodeRef::internal_links() const
{
return internal_links_;
@ -553,6 +572,13 @@ inline bNodeType *NodeRef::typeinfo() const
return bnode_->typeinfo;
}
/* Returns a pointer because not all nodes have declarations currently. */
inline const NodeDeclaration *NodeRef::declaration() const
{
nodeDeclarationEnsure(this->tree().btree(), bnode_);
return bnode_->declaration;
}
inline int NodeRef::id() const
{
return id_;

View File

@ -28,6 +28,7 @@ namespace blender::nodes {
static void fn_node_boolean_math_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.add_input<decl::Bool>("Boolean", "Boolean");
b.add_input<decl::Bool>("Boolean", "Boolean_001");
b.add_output<decl::Bool>("Boolean");

View File

@ -30,6 +30,7 @@ namespace blender::nodes {
static void fn_node_float_compare_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.add_input<decl::Float>("A").min(-10000.0f).max(10000.0f);
b.add_input<decl::Float>("B").min(-10000.0f).max(10000.0f);
b.add_input<decl::Float>("Epsilon").default_value(0.001f).min(-10000.0f).max(10000.0f);

View File

@ -29,6 +29,7 @@ namespace blender::nodes {
static void fn_node_float_to_int_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.add_input<decl::Float>("Float");
b.add_output<decl::Int>("Integer");
};

View File

@ -23,6 +23,7 @@ namespace blender::nodes {
static void fn_node_input_string_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.add_output<decl::String>("String");
};

View File

@ -25,6 +25,7 @@ namespace blender::nodes {
static void fn_node_input_vector_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.add_output<decl::Vector>("Vector");
};

View File

@ -22,6 +22,7 @@ namespace blender::nodes {
static void fn_node_random_float_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.add_input<decl::Float>("Min").min(-10000.0f).max(10000.0f);
b.add_input<decl::Float>("Max").default_value(1.0f).min(-10000.0f).max(10000.0f);
b.add_input<decl::Int>("Seed").min(-10000).max(10000);

View File

@ -24,6 +24,7 @@ namespace blender::nodes {
static void fn_node_string_length_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.add_input<decl::String>("String");
b.add_output<decl::Int>("Length");
};

View File

@ -22,6 +22,7 @@ namespace blender::nodes {
static void fn_node_string_substring_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.add_input<decl::String>("String");
b.add_input<decl::Int>("Position");
b.add_input<decl::Int>("Length").min(0);

View File

@ -21,6 +21,7 @@ namespace blender::nodes {
static void fn_node_value_to_string_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.add_input<decl::Float>("Value");
b.add_input<decl::Int>("Decimals").min(0);
b.add_output<decl::String>("String");

View File

@ -26,18 +26,18 @@ namespace blender::nodes {
static void geo_node_attribute_capture_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Geometry>("Geometry");
b.add_input<decl::Vector>("Value");
b.add_input<decl::Float>("Value", "Value_001");
b.add_input<decl::Color>("Value", "Value_002");
b.add_input<decl::Bool>("Value", "Value_003");
b.add_input<decl::Int>("Value", "Value_004");
b.add_input<decl::Vector>("Value").supports_field();
b.add_input<decl::Float>("Value", "Value_001").supports_field();
b.add_input<decl::Color>("Value", "Value_002").supports_field();
b.add_input<decl::Bool>("Value", "Value_003").supports_field();
b.add_input<decl::Int>("Value", "Value_004").supports_field();
b.add_output<decl::Geometry>("Geometry");
b.add_output<decl::Vector>("Attribute");
b.add_output<decl::Float>("Attribute", "Attribute_001");
b.add_output<decl::Color>("Attribute", "Attribute_002");
b.add_output<decl::Bool>("Attribute", "Attribute_003");
b.add_output<decl::Int>("Attribute", "Attribute_004");
b.add_output<decl::Vector>("Attribute").field_source();
b.add_output<decl::Float>("Attribute", "Attribute_001").field_source();
b.add_output<decl::Color>("Attribute", "Attribute_002").field_source();
b.add_output<decl::Bool>("Attribute", "Attribute_003").field_source();
b.add_output<decl::Int>("Attribute", "Attribute_004").field_source();
}
static void geo_node_attribute_capture_layout(uiLayout *layout,

View File

@ -29,8 +29,8 @@ namespace blender::nodes {
static void geo_node_attribute_statistic_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Geometry>("Geometry");
b.add_input<decl::Float>("Attribute").hide_value();
b.add_input<decl::Vector>("Attribute", "Attribute_001").hide_value();
b.add_input<decl::Float>("Attribute").hide_value().supports_field();
b.add_input<decl::Vector>("Attribute", "Attribute_001").hide_value().supports_field();
b.add_output<decl::Float>("Mean");
b.add_output<decl::Float>("Median");

View File

@ -24,7 +24,7 @@ namespace blender::nodes {
static void geo_node_curve_parameter_declare(NodeDeclarationBuilder &b)
{
b.add_output<decl::Float>("Factor");
b.add_output<decl::Float>("Factor").field_source();
}
/**

View File

@ -28,12 +28,12 @@ namespace blender::nodes {
static void geo_node_curve_sample_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Geometry>("Curve");
b.add_input<decl::Float>("Factor").min(0.0f).max(1.0f).subtype(PROP_FACTOR);
b.add_input<decl::Float>("Length").min(0.0f).subtype(PROP_DISTANCE);
b.add_input<decl::Float>("Factor").min(0.0f).max(1.0f).subtype(PROP_FACTOR).supports_field();
b.add_input<decl::Float>("Length").min(0.0f).subtype(PROP_DISTANCE).supports_field();
b.add_output<decl::Vector>("Position");
b.add_output<decl::Vector>("Tangent");
b.add_output<decl::Vector>("Normal");
b.add_output<decl::Vector>("Position").dependent_field();
b.add_output<decl::Vector>("Tangent").dependent_field();
b.add_output<decl::Vector>("Normal").dependent_field();
}
static void geo_node_curve_sample_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr)

View File

@ -20,7 +20,7 @@ namespace blender::nodes {
static void geo_node_input_index_declare(NodeDeclarationBuilder &b)
{
b.add_output<decl::Int>("Index");
b.add_output<decl::Int>("Index").field_source();
}
class IndexFieldInput final : public fn::FieldInput {

View File

@ -28,7 +28,7 @@ namespace blender::nodes {
static void geo_node_input_normal_declare(NodeDeclarationBuilder &b)
{
b.add_output<decl::Vector>("Normal");
b.add_output<decl::Vector>("Normal").field_source();
}
static GVArrayPtr mesh_face_normals(const Mesh &mesh,

View File

@ -20,7 +20,7 @@ namespace blender::nodes {
static void geo_node_input_position_declare(NodeDeclarationBuilder &b)
{
b.add_output<decl::Vector>("Position");
b.add_output<decl::Vector>("Position").field_source();
}
static void geo_node_input_position_exec(GeoNodeExecParams params)

View File

@ -24,7 +24,7 @@ namespace blender::nodes {
static void geo_node_input_tangent_declare(NodeDeclarationBuilder &b)
{
b.add_output<decl::Vector>("Tangent");
b.add_output<decl::Vector>("Tangent").field_source();
}
static void calculate_bezier_tangents(const BezierSpline &spline, MutableSpan<float3> tangents)

View File

@ -30,7 +30,7 @@ static void geo_node_material_assign_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Geometry>("Geometry");
b.add_input<decl::Material>("Material").hide_label();
b.add_input<decl::Bool>("Selection").default_value(true).hide_value();
b.add_input<decl::Bool>("Selection").default_value(true).hide_value().supports_field();
b.add_output<decl::Geometry>("Geometry");
}

View File

@ -31,7 +31,7 @@ namespace blender::nodes {
static void geo_node_material_selection_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Material>("Material").hide_label(true);
b.add_output<decl::Bool>("Selection");
b.add_output<decl::Bool>("Selection").field_source();
}
static void select_mesh_by_material(const Mesh &mesh,

View File

@ -23,8 +23,8 @@ namespace blender::nodes {
static void geo_node_set_position_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Geometry>("Geometry");
b.add_input<decl::Vector>("Position").hide_value();
b.add_input<decl::Bool>("Selection").default_value(true).hide_value();
b.add_input<decl::Vector>("Position").hide_value().implicit_field();
b.add_input<decl::Bool>("Selection").default_value(true).hide_value().supports_field();
b.add_output<decl::Geometry>("Geometry");
}

View File

@ -19,6 +19,7 @@
#include "NOD_node_tree_ref.hh"
#include "BLI_dot_export.hh"
#include "BLI_stack.hh"
namespace blender::nodes {
@ -473,6 +474,108 @@ bool NodeTreeRef::has_undefined_nodes_or_sockets() const
return false;
}
bool NodeRef::any_input_is_directly_linked() const
{
for (const SocketRef *socket : inputs_) {
if (!socket->directly_linked_sockets().is_empty()) {
return true;
}
}
return false;
}
bool NodeRef::any_output_is_directly_linked() const
{
for (const SocketRef *socket : outputs_) {
if (!socket->directly_linked_sockets().is_empty()) {
return true;
}
}
return false;
}
bool NodeRef::any_socket_is_directly_linked(eNodeSocketInOut in_out) const
{
if (in_out == SOCK_IN) {
return this->any_input_is_directly_linked();
}
return this->any_output_is_directly_linked();
}
/**
* Sort nodes topologically from left to right or right to left.
* In the future the result if this could be cached on #NodeTreeRef.
*/
Vector<const NodeRef *> NodeTreeRef::toposort(const ToposortDirection direction) const
{
struct Item {
const NodeRef *node;
/* Index of the next socket that is checked in the depth-first search. */
int socket_index = 0;
/* Link index in the next socket that is checked in the depth-first search. */
int link_index = 0;
};
Vector<const NodeRef *> toposort;
toposort.reserve(nodes_by_id_.size());
Array<bool> node_is_done_by_id(nodes_by_id_.size(), false);
Stack<Item> nodes_to_check;
for (const NodeRef *start_node : nodes_by_id_) {
if (node_is_done_by_id[start_node->id()]) {
/* Ignore nodes that are done already. */
continue;
}
if (start_node->any_socket_is_directly_linked(
direction == ToposortDirection::LeftToRight ? SOCK_OUT : SOCK_IN)) {
/* Ignore non-start nodes. */
continue;
}
/* Do a depth-first search to sort nodes topologically. */
nodes_to_check.push({start_node});
while (!nodes_to_check.is_empty()) {
Item &item = nodes_to_check.peek();
const NodeRef &node = *item.node;
const Span<const SocketRef *> sockets = node.sockets(
direction == ToposortDirection::LeftToRight ? SOCK_IN : SOCK_OUT);
while (true) {
if (item.socket_index == sockets.size()) {
/* All sockets have already been visited. */
break;
}
const SocketRef &socket = *sockets[item.socket_index];
const Span<const SocketRef *> linked_sockets = socket.directly_linked_sockets();
if (item.link_index == linked_sockets.size()) {
/* All linkes connected to this socket have already been visited. */
item.socket_index++;
item.link_index = 0;
continue;
}
const SocketRef &linked_socket = *linked_sockets[item.link_index];
const NodeRef &linked_node = linked_socket.node();
if (node_is_done_by_id[linked_node.id()]) {
/* The linked node has already been visited. */
item.link_index++;
continue;
}
nodes_to_check.push({&linked_node});
break;
}
/* If no other element has been pushed, the current node can be pushed to the sorted list. */
if (&item == &nodes_to_check.peek()) {
node_is_done_by_id[node.id()] = true;
toposort.append(&node);
nodes_to_check.pop();
}
}
}
return toposort;
}
std::string NodeTreeRef::to_dot() const
{
dot::DirectedGraph digraph;

View File

@ -27,6 +27,7 @@ namespace blender::nodes {
static void sh_node_clamp_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.add_input<decl::Float>("Value").min(0.0f).max(1.0f).default_value(1.0f);
b.add_input<decl::Float>("Min").default_value(0.0f).min(-10000.0f).max(10000.0f);
b.add_input<decl::Float>("Max").default_value(1.0f).min(-10000.0f).max(10000.0f);

View File

@ -27,6 +27,7 @@ namespace blender::nodes {
static void sh_node_curve_vec_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.add_input<decl::Float>("Fac").min(0.0f).max(1.0f).default_value(1.0f).subtype(PROP_FACTOR);
b.add_input<decl::Vector>("Vector").min(-1.0f).max(1.0f);
b.add_output<decl::Vector>("Vector");

View File

@ -29,6 +29,7 @@ namespace blender::nodes {
static void sh_node_map_range_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.add_input<decl::Float>("Value").min(-10000.0f).max(10000.0f).default_value(1.0f);
b.add_input<decl::Float>("From Min").min(-10000.0f).max(10000.0f);
b.add_input<decl::Float>("From Max").min(-10000.0f).max(10000.0f).default_value(1.0f);

View File

@ -31,6 +31,7 @@ namespace blender::nodes {
static void sh_node_math_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.add_input<decl::Float>("Value").default_value(0.5f).min(-10000.0f).max(10000.0f);
b.add_input<decl::Float>("Value", "Value_001").default_value(0.5f).min(-10000.0f).max(10000.0f);
b.add_input<decl::Float>("Value", "Value_002").default_value(0.5f).min(-10000.0f).max(10000.0f);

View File

@ -27,6 +27,7 @@ namespace blender::nodes {
static void sh_node_mix_rgb_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.add_input<decl::Float>("Fac").default_value(0.5f).min(0.0f).max(1.0f).subtype(PROP_FACTOR);
b.add_input<decl::Color>("Color1").default_value({0.5f, 0.5f, 0.5f, 1.0f});
b.add_input<decl::Color>("Color2").default_value({0.5f, 0.5f, 0.5f, 1.0f});

View File

@ -27,6 +27,7 @@ namespace blender::nodes {
static void sh_node_seprgb_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.add_input<decl::Color>("Image").default_value({0.8f, 0.8f, 0.8f, 1.0f});
b.add_output<decl::Float>("R");
b.add_output<decl::Float>("G");

View File

@ -27,6 +27,7 @@ namespace blender::nodes {
static void sh_node_sepxyz_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.add_input<decl::Vector>("Vector").min(-10000.0f).max(10000.0f);
b.add_output<decl::Float>("X");
b.add_output<decl::Float>("Y");

View File

@ -23,6 +23,7 @@ namespace blender::nodes {
static void sh_node_tex_musgrave_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.add_input<decl::Vector>("Vector").hide_value();
b.add_input<decl::Float>("W").min(-1000.0f).max(1000.0f);
b.add_input<decl::Float>("Scale").min(-1000.0f).max(1000.0f).default_value(5.0f);

View File

@ -25,7 +25,8 @@ namespace blender::nodes {
static void sh_node_tex_noise_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Vector>("Vector").hide_value();
b.is_function_node();
b.add_input<decl::Vector>("Vector").hide_value().implicit_field();
b.add_input<decl::Float>("W").min(-1000.0f).max(1000.0f);
b.add_input<decl::Float>("Scale").min(-1000.0f).max(1000.0f).default_value(5.0f);
b.add_input<decl::Float>("Detail").min(0.0f).max(16.0f).default_value(2.0f);

View File

@ -23,6 +23,7 @@ namespace blender::nodes {
static void sh_node_tex_voronoi_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.add_input<decl::Vector>("Vector").hide_value();
b.add_input<decl::Float>("W").min(-1000.0f).max(1000.0f);
b.add_input<decl::Float>("Scale").min(-1000.0f).max(1000.0f).default_value(5.0f);

View File

@ -23,6 +23,7 @@ namespace blender::nodes {
static void sh_node_tex_white_noise_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.add_input<decl::Vector>("Vector").min(-10000.0f).max(10000.0f);
b.add_input<decl::Float>("W").min(-10000.0f).max(10000.0f);
b.add_output<decl::Float>("Value");

View File

@ -33,6 +33,7 @@ namespace blender::nodes {
static void sh_node_valtorgb_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.add_input<decl::Float>("Fac").default_value(0.5f).min(0.0f).max(1.0f).subtype(PROP_FACTOR);
b.add_output<decl::Color>("Color");
b.add_output<decl::Float>("Alpha");

View File

@ -29,6 +29,7 @@ namespace blender::nodes {
static void sh_node_vector_math_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.add_input<decl::Vector>("Vector").min(-10000.0f).max(10000.0f);
b.add_input<decl::Vector>("Vector", "Vector_001").min(-10000.0f).max(10000.0f);
b.add_input<decl::Vector>("Vector", "Vector_002").min(-10000.0f).max(10000.0f);

View File

@ -27,6 +27,7 @@ namespace blender::nodes {
static void sh_node_vector_rotate_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.add_input<decl::Vector>("Vector").min(0.0f).max(1.0f).hide_value();
b.add_input<decl::Vector>("Vector");
b.add_input<decl::Vector>("Axis").min(-1.0f).max(1.0f).default_value({0.0f, 0.0f, 1.0f});