Geometry Nodes: Add a toggle to use attributes as input values

This adds a toggle to node group inputs exposed in the modifier to use
an attribute instead of a single value. When the toggle is pressed, the
button switches to a text button to choose an attribute name. Attribute
search isn't implemented here yet.

One confusing thing is that some values can't be driven by attributes
at all, like the size of a primitive node. In that case, we should have
a node warning, but that will be separate since it's more general.
We can also have an option to turn off this toggle in node group
input settings.

The two new properties for each input are stored with the same name
as the value, but with `"_use_attribute"` and `"_attribute_name"``
suffixes. The properties are not added for socket types that don't
support attribute input, like object sockets.

Differential Revision: https://developer.blender.org/D12504
This commit is contained in:
Hans Goudey 2021-09-16 20:49:10 -05:00
parent 4fa0bbb5ac
commit 8e21d528ca
Notes: blender-bot 2023-02-14 08:28:46 +01:00
Referenced by commit b37d36a60f, Fix: Add versioning for geometry nodes attribute input toggle
Referenced by issue #91158, Support choosing input attribute names in modifier
5 changed files with 155 additions and 8 deletions

View File

@ -1321,7 +1321,9 @@ const GVArray *AttributeFieldInput::get_varray_for_context(const fn::FieldContex
const AttributeDomain domain = geometry_context->domain();
const CustomDataType data_type = cpp_type_to_custom_data_type(*type_);
GVArrayPtr attribute = component.attribute_try_get_for_read(name_, domain, data_type);
return scope.add(std::move(attribute));
if (attribute) {
return scope.add(std::move(attribute));
}
}
return nullptr;
}

View File

@ -191,6 +191,7 @@ void OBJECT_OT_skin_radii_equalize(struct wmOperatorType *ot);
void OBJECT_OT_skin_armature_create(struct wmOperatorType *ot);
void OBJECT_OT_laplaciandeform_bind(struct wmOperatorType *ot);
void OBJECT_OT_surfacedeform_bind(struct wmOperatorType *ot);
void OBJECT_OT_geometry_nodes_input_attribute_toggle(struct wmOperatorType *ot);
/* object_gpencil_modifiers.c */
void OBJECT_OT_gpencil_modifier_add(struct wmOperatorType *ot);

View File

@ -3242,3 +3242,54 @@ void OBJECT_OT_surfacedeform_bind(wmOperatorType *ot)
}
/** \} */
/* ------------------------------------------------------------------- */
/** \name Toggle Value or Attribute Operator
*
* \note This operator basically only exists to provide a better tooltip for the toggle button,
* since it is stored as an IDProperty. It also stops the button from being highlighted when
* "use_attribute" is on, which isn't expected.
* \{ */
static int geometry_nodes_input_attribute_toggle_exec(bContext *C, wmOperator *op)
{
Object *ob = ED_object_active_context(C);
char modifier_name[MAX_NAME];
RNA_string_get(op->ptr, "modifier_name", modifier_name);
NodesModifierData *nmd = (NodesModifierData *)BKE_modifiers_findby_name(ob, modifier_name);
if (nmd == NULL) {
return OPERATOR_CANCELLED;
}
char prop_path[MAX_NAME];
RNA_string_get(op->ptr, "prop_path", prop_path);
PointerRNA mod_ptr;
RNA_pointer_create(&ob->id, &RNA_Modifier, nmd, &mod_ptr);
const int old_value = RNA_int_get(&mod_ptr, prop_path);
const int new_value = !old_value;
RNA_int_set(&mod_ptr, prop_path, new_value);
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, ob);
return OPERATOR_FINISHED;
}
void OBJECT_OT_geometry_nodes_input_attribute_toggle(wmOperatorType *ot)
{
ot->name = "Input Attribute Toggle";
ot->description =
"Switch between an attribute and a single value to define the data for every element";
ot->idname = "OBJECT_OT_geometry_nodes_input_attribute_toggle";
ot->exec = geometry_nodes_input_attribute_toggle_exec;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL;
RNA_def_string(ot->srna, "prop_path", NULL, 0, "Prop Path", "");
RNA_def_string(ot->srna, "modifier_name", NULL, MAX_NAME, "Modifier Name", "");
}
/** \} */

View File

@ -145,6 +145,7 @@ void ED_operatortypes_object(void)
WM_operatortype_append(OBJECT_OT_skin_loose_mark_clear);
WM_operatortype_append(OBJECT_OT_skin_radii_equalize);
WM_operatortype_append(OBJECT_OT_skin_armature_create);
WM_operatortype_append(OBJECT_OT_geometry_nodes_input_attribute_toggle);
/* grease pencil modifiers */
WM_operatortype_append(OBJECT_OT_gpencil_modifier_add);

View File

@ -68,6 +68,8 @@
#include "UI_interface.h"
#include "UI_resources.h"
#include "WM_types.h"
#include "RNA_access.h"
#include "RNA_enum_types.h"
@ -291,6 +293,17 @@ static bool logging_enabled(const ModifierEvalContext *ctx)
return true;
}
static const std::string use_attribute_suffix = "_use_attribute";
static const std::string attribute_name_suffix = "_attribute_name";
/**
* \return Whether using an attribute to input values of this type is supported.
*/
static bool socket_type_has_attribute_toggle(const bNodeSocket &socket)
{
return ELEM(socket.type, SOCK_FLOAT, SOCK_VECTOR, SOCK_BOOLEAN, SOCK_RGBA, SOCK_INT);
}
static IDProperty *id_property_create_from_socket(const bNodeSocket &socket)
{
switch (socket.type) {
@ -546,6 +559,32 @@ void MOD_nodes_update_interface(Object *object, NodesModifierData *nmd)
new_prop->ui_data = ui_data;
}
}
if (socket_type_has_attribute_toggle(*socket)) {
const std::string use_attribute_id = socket->identifier + use_attribute_suffix;
const std::string attribute_name_id = socket->identifier + attribute_name_suffix;
IDPropertyTemplate idprop = {0};
IDProperty *use_attribute_prop = IDP_New(IDP_INT, &idprop, use_attribute_id.c_str());
IDP_AddToGroup(nmd->settings.properties, use_attribute_prop);
IDProperty *attribute_prop = IDP_New(IDP_STRING, &idprop, attribute_name_id.c_str());
IDP_AddToGroup(nmd->settings.properties, attribute_prop);
if (old_properties != nullptr) {
IDProperty *old_prop_use_attribute = IDP_GetPropertyFromGroup(old_properties,
use_attribute_id.c_str());
if (old_prop_use_attribute != nullptr) {
IDP_CopyPropertyContent(use_attribute_prop, old_prop_use_attribute);
}
IDProperty *old_attribute_name_prop = IDP_GetPropertyFromGroup(old_properties,
attribute_name_id.c_str());
if (old_attribute_name_prop != nullptr) {
IDP_CopyPropertyContent(attribute_prop, old_attribute_name_prop);
}
}
}
}
if (old_properties != nullptr) {
@ -601,8 +640,31 @@ static void initialize_group_input(NodesModifierData &nmd,
return;
}
init_socket_cpp_value_from_property(
*property, static_cast<eNodeSocketDatatype>(socket.type), r_value);
if (!socket_type_has_attribute_toggle(socket)) {
init_socket_cpp_value_from_property(
*property, static_cast<eNodeSocketDatatype>(socket.type), r_value);
}
const IDProperty *property_use_attribute = IDP_GetPropertyFromGroup(
nmd.settings.properties, (socket.identifier + use_attribute_suffix).c_str());
const IDProperty *property_attribute_name = IDP_GetPropertyFromGroup(
nmd.settings.properties, (socket.identifier + attribute_name_suffix).c_str());
if (property_use_attribute == nullptr || property_attribute_name == nullptr) {
socket.typeinfo->get_geometry_nodes_cpp_value(socket, r_value);
return;
}
const bool use_attribute = IDP_Int(property_use_attribute) != 0;
if (use_attribute) {
const StringRef attribute_name{IDP_String(property_attribute_name)};
auto attribute_input = std::make_shared<blender::bke::AttributeFieldInput>(
attribute_name, *socket.typeinfo->get_base_cpp_type());
new (r_value) blender::fn::GField(std::move(attribute_input), 0);
}
else {
init_socket_cpp_value_from_property(
*property, static_cast<eNodeSocketDatatype>(socket.type), r_value);
}
}
static Vector<SpaceSpreadsheet *> find_spreadsheet_editors(Main *bmain)
@ -912,13 +974,13 @@ static void modifyGeometrySet(ModifierData *md,
* the node socket identifier for the property names, since they are unique, but also having
* the correct label displayed in the UI. */
static void draw_property_for_socket(uiLayout *layout,
NodesModifierData *nmd,
PointerRNA *bmain_ptr,
PointerRNA *md_ptr,
const IDProperty *modifier_props,
const bNodeSocket &socket)
{
/* The property should be created in #MOD_nodes_update_interface with the correct type. */
IDProperty *property = IDP_GetPropertyFromGroup(modifier_props, socket.identifier);
IDProperty *property = IDP_GetPropertyFromGroup(nmd->settings.properties, socket.identifier);
/* IDProperties can be removed with python, so there could be a situation where
* there isn't a property for a socket or it doesn't have the correct type. */
@ -959,8 +1021,38 @@ static void draw_property_for_socket(uiLayout *layout,
uiItemPointerR(layout, md_ptr, rna_path, bmain_ptr, "textures", socket.name, ICON_TEXTURE);
break;
}
default:
uiItemR(layout, md_ptr, rna_path, 0, socket.name, ICON_NONE);
default: {
if (socket_type_has_attribute_toggle(socket) &&
USER_EXPERIMENTAL_TEST(&U, use_geometry_nodes_fields)) {
const std::string rna_path_use_attribute = "[\"" + std::string(socket_id_esc) +
use_attribute_suffix + "\"]";
const std::string rna_path_attribute_name = "[\"" + std::string(socket_id_esc) +
attribute_name_suffix + "\"]";
uiLayout *row = uiLayoutRow(layout, true);
const int use_attribute = RNA_int_get(md_ptr, rna_path_use_attribute.c_str()) != 0;
if (use_attribute) {
uiItemR(row, md_ptr, rna_path_attribute_name.c_str(), 0, socket.name, ICON_NONE);
}
else {
uiItemR(row, md_ptr, rna_path, 0, socket.name, ICON_NONE);
}
PointerRNA props;
uiItemFullO(row,
"object.geometry_nodes_input_attribute_toggle",
"",
ICON_SPREADSHEET,
nullptr,
WM_OP_INVOKE_DEFAULT,
0,
&props);
RNA_string_set(&props, "modifier_name", nmd->modifier.name);
RNA_string_set(&props, "prop_path", rna_path_use_attribute.c_str());
}
else {
uiItemR(layout, md_ptr, rna_path, 0, socket.name, ICON_NONE);
}
}
}
}
@ -991,7 +1083,7 @@ static void panel_draw(const bContext *C, Panel *panel)
RNA_main_pointer_create(bmain, &bmain_ptr);
LISTBASE_FOREACH (bNodeSocket *, socket, &nmd->node_group->inputs) {
draw_property_for_socket(layout, &bmain_ptr, ptr, nmd->settings.properties, *socket);
draw_property_for_socket(layout, nmd, &bmain_ptr, ptr, *socket);
}
}