Compare commits

...

19 Commits

Author SHA1 Message Date
3ce788e015 Progress on input properties, currently crashes 2023-04-12 16:48:59 -04:00
8be481fbd8 Fix missing update tag 2023-04-12 16:18:46 -04:00
a385a00821 Merge branch 'main' into node-group-operators 2023-04-12 15:58:29 -04:00
677273eb41 Merge branch 'main' into node-group-operators 2023-04-07 17:45:51 -04:00
abe86a6a5d Merge branch 'main' into node-group-operators 2023-04-07 08:14:34 -04:00
9e01c4f69e List groups in menu, some crash fixes 2023-04-05 17:07:56 -04:00
7212f52457 Progress 2023-04-05 16:33:47 -04:00
51d48bdcdb Merge branch 'main' into node-group-operators 2023-04-05 14:14:42 -04:00
a10ae5cf05 Progress 2023-04-05 13:34:31 -04:00
19051b863e Merge branch 'main' into node-group-operators 2023-04-05 11:55:48 -04:00
1ad98ac65e Merge branch 'main' into node-group-operators 2023-04-04 18:02:37 -04:00
96e8f8a002 Some progress 2023-03-31 12:32:59 -04:00
4c19994c11 Merge branch 'main' into node-group-operators 2023-03-31 11:58:07 -04:00
7c67f8c719 Progress 2023-03-30 19:17:05 -04:00
cf77874cdb Merge branch 'main' into node-group-operators 2023-03-30 18:52:25 -04:00
7e748413f9 Add no-op operator base 2023-03-30 18:38:33 -04:00
41c5490137 Merge branch 'main' into node-group-operators 2023-03-30 16:34:46 -04:00
338db40df9 Merge branch 'main' into node-group-operators 2023-03-30 15:25:08 -04:00
39dfa015a0 Geometry Nodes: Add node editor option to edit "Operator" node groups
This functions as a way to avoid having the context affect which node
groups are displayed. This is necessary since there is no concept of an
active operator and most node groups could be used as an operator.

It was considered to make this a more general "No Context" option for
the node editor. That still might happen eventually, but we want to
encourage users to use the modifier context since it allows the
various inspection features to work properly.
2023-03-27 10:02:15 -04:00
16 changed files with 1066 additions and 720 deletions

View File

@@ -136,25 +136,28 @@ class NODE_HT_header(Header):
layout.prop(snode_id, "use_nodes")
elif snode.tree_type == 'GeometryNodeTree':
layout.prop(snode, "geometry_nodes_type", text="")
NODE_MT_editor_menus.draw_collapsible(context, layout)
layout.separator_spacer()
ob = context.object
if snode.geometry_nodes_type == 'MODIFIER':
ob = context.object
row = layout.row()
if snode.pin:
row.enabled = False
row.template_ID(snode, "node_tree", new="node.new_geometry_node_group_assign")
elif ob:
active_modifier = ob.modifiers.active
if active_modifier and active_modifier.type == 'NODES':
if active_modifier.node_group:
row.template_ID(active_modifier, "node_group", new="object.geometry_node_tree_copy_assign")
row = layout.row()
if snode.pin:
row.enabled = False
row.template_ID(snode, "node_tree", new="node.new_geometry_node_group_assign")
elif ob:
active_modifier = ob.modifiers.active
if active_modifier and active_modifier.type == 'NODES':
if active_modifier.node_group:
row.template_ID(active_modifier, "node_group", new="object.geometry_node_tree_copy_assign")
else:
row.template_ID(active_modifier, "node_group", new="node.new_geometry_node_group_assign")
else:
row.template_ID(active_modifier, "node_group", new="node.new_geometry_node_group_assign")
else:
row.template_ID(snode, "node_tree", new="node.new_geometry_nodes_modifier")
row.template_ID(snode, "node_tree", new="node.new_geometry_nodes_modifier")
else:
layout.template_ID(snode, "node_tree", new="node.new_node_tree")
else:
# Custom node tree is edited as independent ID block
NODE_MT_editor_menus.draw_collapsible(context, layout)

View File

@@ -2064,7 +2064,7 @@ class VIEW3D_MT_edit_curves_select_more_less(Menu):
class VIEW3D_MT_select_edit_curves(Menu):
bl_label = "Select"
def draw(self, _context):
def draw(self, context):
layout = self.layout
layout.operator("curves.select_all", text="All").action = 'SELECT'
@@ -5401,6 +5401,10 @@ class VIEW3D_MT_edit_curves(Menu):
layout.separator()
layout.operator("curves.delete")
layout.separator()
for group in bpy.data.node_groups:
layout.operator("curves.node_group", text=group.name).name = group.name
class VIEW3D_MT_object_mode_pie(Menu):
bl_label = "Mode"

View File

@@ -11,6 +11,7 @@ set(INC
../../gpu
../../makesdna
../../makesrna
../../nodes
../../windowmanager
../../../../intern/clog
../../../../intern/guardedalloc
@@ -24,9 +25,11 @@ set(SRC
intern/curves_add.cc
intern/curves_data.cc
intern/curves_edit.cc
intern/curves_intern.hh
intern/curves_ops.cc
intern/curves_selection.cc
intern/curves_undo.cc
intern/node_group_operator.cc
)
set(LIB

View File

@@ -0,0 +1,9 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
struct wmOperatorType;
namespace blender::ed::curves {
void CURVES_OT_node_group(wmOperatorType *ot);
} // namespace blender::ed::curves

View File

@@ -58,6 +58,8 @@
#include "GEO_reverse_uv_sampler.hh"
#include "curves_intern.hh"
/**
* The code below uses a suffix naming convention to indicate the coordinate space:
* `cu`: Local space of the curves object that is being edited.
@@ -1192,6 +1194,7 @@ void ED_operatortypes_curves()
WM_operatortype_append(CURVES_OT_select_less);
WM_operatortype_append(CURVES_OT_surface_set);
WM_operatortype_append(CURVES_OT_delete);
WM_operatortype_append(CURVES_OT_node_group);
}
void ED_keymap_curves(wmKeyConfig *keyconf)

View File

@@ -0,0 +1,153 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edcurves
*/
#include "ED_curves.h"
#include "ED_object.h"
#include "ED_screen.h"
#include "ED_select_utils.h"
#include "ED_view3d.h"
#include "WM_api.h"
#include "BKE_attribute_math.hh"
#include "BKE_compute_contexts.hh"
#include "BKE_context.h"
#include "BKE_curves.hh"
#include "BKE_geometry_set.hh"
#include "BKE_layer.h"
#include "BKE_lib_id.h"
#include "BKE_node_runtime.hh"
#include "BKE_object.h"
#include "BKE_report.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DEG_depsgraph.h"
#include "DEG_depsgraph_query.h"
#include "RNA_access.h"
#include "RNA_define.h"
#include "RNA_enum_types.h"
#include "RNA_prototypes.h"
#include "UI_interface.h"
#include "UI_resources.h"
#include "FN_lazy_function_execute.hh"
#include "NOD_geometry_nodes_execute.hh"
#include "NOD_geometry_nodes_lazy_function.hh"
#include "curves_intern.hh"
namespace blender::ed::curves {
static int run_node_group_exec(bContext *C, wmOperator *op)
{
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
Object *object = CTX_data_active_object(C);
if (!object) {
return OPERATOR_CANCELLED;
}
eObjectMode mode = eObjectMode(object->mode);
char name[MAX_ID_NAME];
RNA_string_get(op->ptr, "name", name);
const ID *id = BKE_libblock_find_name(CTX_data_main(C), ID_NT, name);
if (!id) {
return OPERATOR_CANCELLED;
}
const bNodeTree &node_tree = reinterpret_cast<const bNodeTree &>(*id);
const nodes::GeometryNodesLazyFunctionGraphInfo *lf_graph_info =
nodes::ensure_geometry_nodes_lazy_function_graph(node_tree);
if (lf_graph_info == nullptr) {
BKE_report(op->reports, RPT_ERROR, "Cannot evaluate node group");
return OPERATOR_CANCELLED;
}
uint objects_len = 0;
Object **objects = BKE_view_layer_array_from_objects_in_mode_unique_data(
scene, view_layer, CTX_wm_view3d(C), &objects_len, mode);
for (Object *object : Span(objects, objects_len)) {
Curves &curves_id = *static_cast<Curves *>(object->data);
GeometrySet original_geometry = GeometrySet::create_with_curves(
&curves_id, GeometryOwnershipType::Editable);
nodes::GeoNodesOperatorData operator_eval_data{};
operator_eval_data.depsgraph = depsgraph;
operator_eval_data.self_object = object;
bke::ModifierComputeContext compute_context{nullptr, "actually not a modifier"};
GeometrySet new_geometry = nodes::execute_geometry_nodes(
node_tree,
op->properties,
compute_context,
original_geometry,
[&](nodes::GeoNodesLFUserData &user_data) {
user_data.operator_data = &operator_eval_data;
user_data.log_socket_values = false;
});
if (Curves *new_curves_id = new_geometry.get_curves_for_write()) {
if (new_curves_id != &curves_id) {
curves_id.geometry.wrap() = std::move(new_curves_id->geometry.wrap());
}
}
else {
curves_id.geometry.wrap() = {};
}
DEG_id_tag_update(&curves_id.id, ID_RECALC_GEOMETRY);
WM_event_add_notifier(C, NC_GEOM | ND_DATA, &curves_id);
}
MEM_SAFE_FREE(objects);
return OPERATOR_FINISHED;
}
static int run_node_group_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
char name[MAX_ID_NAME];
RNA_string_get(op->ptr, "name", name);
const ID *id = BKE_libblock_find_name(CTX_data_main(C), ID_NT, name);
if (!id) {
return OPERATOR_CANCELLED;
}
const bNodeTree &node_tree = reinterpret_cast<const bNodeTree &>(*id);
nodes::update_input_properties_from_node_tree(node_tree, op->properties, *op->properties);
nodes::update_output_properties_from_node_tree(node_tree, op->properties, *op->properties);
return run_node_group_exec(C, op);
}
void CURVES_OT_node_group(wmOperatorType *ot)
{
ot->name = "Run Node Group";
ot->idname = __func__;
ot->description = "Execute a node group on curves"; // TODO: Retrieve from node group.
ot->invoke = run_node_group_invoke;
ot->exec = run_node_group_exec;
ot->poll = editable_curves_poll;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
PropertyRNA *prop = RNA_def_string(ot->srna,
"name",
nullptr,
MAX_ID_NAME - 2,
"Name",
"Name of the data-block to use by the operator");
RNA_def_property_flag(prop, PropertyFlag(PROP_SKIP_SAVE | PROP_HIDDEN));
}
} // namespace blender::ed::curves

View File

@@ -127,7 +127,7 @@ static void get_context_path_node_geometry(const bContext &C,
SpaceNode &snode,
Vector<ui::ContextPathItem> &path)
{
if (snode.flag & SNODE_PIN) {
if (snode.flag & SNODE_PIN || snode.geometry_nodes_type == SNODE_GEOMETRY_OPERATOR) {
context_path_add_node_tree_and_node_groups(snode, path);
}
else {

View File

@@ -1600,7 +1600,12 @@ typedef struct SpaceNode {
/** Texture-from object, world or brush (#eSpaceNode_TexFrom). */
short texfrom;
/** Shader from object or world (#eSpaceNode_ShaderFrom). */
short shaderfrom;
char shaderfrom;
/**
* Whether to edit any geometry node group, or follow the active modifier context.
* #SpaceNodeGeometryNodesType.
*/
char geometry_nodes_type;
/** Grease-pencil data. */
struct bGPdata *gpd;
@@ -1644,6 +1649,12 @@ typedef enum eSpaceNode_ShaderFrom {
SNODE_SHADER_LINESTYLE = 2,
} eSpaceNode_ShaderFrom;
/** #SpaceNode.geometry_nodes_type */
typedef enum SpaceNodeGeometryNodesType {
SNODE_GEOMETRY_MODIFIER = 0,
SNODE_GEOMETRY_OPERATOR = 1,
} SpaceNodeGeometryNodesType;
/** #SpaceNode.insert_ofs_dir */
enum {
SNODE_INSERTOFS_DIR_RIGHT = 0,

View File

@@ -7401,6 +7401,12 @@ static void rna_def_space_node(BlenderRNA *brna)
{0, NULL, 0, NULL, NULL},
};
static const EnumPropertyItem geometry_nodes_type_items[] = {
{SNODE_GEOMETRY_MODIFIER, "MODIFIER", 0, "Modifier", ""},
{SNODE_GEOMETRY_OPERATOR, "OPERATOR", 0, "Operator", ""},
{0, NULL, 0, NULL, NULL},
};
static const EnumPropertyItem backdrop_channels_items[] = {
{SNODE_USE_ALPHA,
"COLOR_ALPHA",
@@ -7455,6 +7461,13 @@ static void rna_def_space_node(BlenderRNA *brna)
RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_ID);
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_NODE, NULL);
prop = RNA_def_property(srna, "geometry_nodes_type", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, NULL, "geometry_nodes_type");
RNA_def_property_enum_items(prop, geometry_nodes_type_items);
RNA_def_property_ui_text(prop, "Geometry Nodes Type", "");
RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_ID);
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_NODE, NULL);
prop = RNA_def_property(srna, "id", PROP_POINTER, PROP_NONE);
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
RNA_def_property_ui_text(prop, "ID", "Data-block whose nodes are being edited");

View File

@@ -84,6 +84,7 @@
#include "ED_viewer_path.hh"
#include "NOD_geometry.h"
#include "NOD_geometry_nodes_execute.hh"
#include "NOD_geometry_nodes_lazy_function.hh"
#include "NOD_node_declaration.hh"
@@ -377,363 +378,6 @@ static bool logging_enabled(const ModifierEvalContext *ctx)
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);
}
/**
* \return Whether using an attribute to input values of this type is supported, and the node
* group's input for this socket accepts a field rather than just single values.
*/
static bool input_has_attribute_toggle(const bNodeTree &node_tree, const int socket_index)
{
BLI_assert(node_tree.runtime->field_inferencing_interface);
const nodes::FieldInferencingInterface &field_interface =
*node_tree.runtime->field_inferencing_interface;
return field_interface.inputs[socket_index] != nodes::InputSocketFieldType::None;
}
static std::unique_ptr<IDProperty, bke::idprop::IDPropertyDeleter> id_property_create_from_socket(
const bNodeSocket &socket)
{
switch (socket.type) {
case SOCK_FLOAT: {
const bNodeSocketValueFloat *value = static_cast<const bNodeSocketValueFloat *>(
socket.default_value);
auto property = bke::idprop::create(socket.identifier, value->value);
IDPropertyUIDataFloat *ui_data = (IDPropertyUIDataFloat *)IDP_ui_data_ensure(property.get());
ui_data->base.rna_subtype = value->subtype;
ui_data->soft_min = double(value->min);
ui_data->soft_max = double(value->max);
ui_data->default_value = value->value;
return property;
}
case SOCK_INT: {
const bNodeSocketValueInt *value = static_cast<const bNodeSocketValueInt *>(
socket.default_value);
auto property = bke::idprop::create(socket.identifier, value->value);
IDPropertyUIDataInt *ui_data = (IDPropertyUIDataInt *)IDP_ui_data_ensure(property.get());
ui_data->base.rna_subtype = value->subtype;
ui_data->soft_min = value->min;
ui_data->soft_max = value->max;
ui_data->default_value = value->value;
return property;
}
case SOCK_VECTOR: {
const bNodeSocketValueVector *value = static_cast<const bNodeSocketValueVector *>(
socket.default_value);
auto property = bke::idprop::create(
socket.identifier, Span<float>{value->value[0], value->value[1], value->value[2]});
IDPropertyUIDataFloat *ui_data = (IDPropertyUIDataFloat *)IDP_ui_data_ensure(property.get());
ui_data->base.rna_subtype = value->subtype;
ui_data->soft_min = double(value->min);
ui_data->soft_max = double(value->max);
ui_data->default_array = (double *)MEM_mallocN(sizeof(double[3]), "mod_prop_default");
ui_data->default_array_len = 3;
for (const int i : IndexRange(3)) {
ui_data->default_array[i] = double(value->value[i]);
}
return property;
}
case SOCK_RGBA: {
const bNodeSocketValueRGBA *value = static_cast<const bNodeSocketValueRGBA *>(
socket.default_value);
auto property = bke::idprop::create(
socket.identifier,
Span<float>{value->value[0], value->value[1], value->value[2], value->value[3]});
IDPropertyUIDataFloat *ui_data = (IDPropertyUIDataFloat *)IDP_ui_data_ensure(property.get());
ui_data->base.rna_subtype = PROP_COLOR;
ui_data->default_array = (double *)MEM_mallocN(sizeof(double[4]), __func__);
ui_data->default_array_len = 4;
ui_data->min = 0.0;
ui_data->max = FLT_MAX;
ui_data->soft_min = 0.0;
ui_data->soft_max = 1.0;
for (const int i : IndexRange(4)) {
ui_data->default_array[i] = double(value->value[i]);
}
return property;
}
case SOCK_BOOLEAN: {
const bNodeSocketValueBoolean *value = static_cast<const bNodeSocketValueBoolean *>(
socket.default_value);
auto property = bke::idprop::create_bool(socket.identifier, value->value);
IDPropertyUIDataBool *ui_data = (IDPropertyUIDataBool *)IDP_ui_data_ensure(property.get());
ui_data->default_value = value->value != 0;
return property;
}
case SOCK_STRING: {
const bNodeSocketValueString *value = static_cast<const bNodeSocketValueString *>(
socket.default_value);
auto property = bke::idprop::create(socket.identifier, value->value);
IDPropertyUIDataString *ui_data = (IDPropertyUIDataString *)IDP_ui_data_ensure(
property.get());
ui_data->default_value = BLI_strdup(value->value);
return property;
}
case SOCK_OBJECT: {
const bNodeSocketValueObject *value = static_cast<const bNodeSocketValueObject *>(
socket.default_value);
auto property = bke::idprop::create(socket.identifier, reinterpret_cast<ID *>(value->value));
IDPropertyUIDataID *ui_data = (IDPropertyUIDataID *)IDP_ui_data_ensure(property.get());
ui_data->id_type = ID_OB;
return property;
}
case SOCK_COLLECTION: {
const bNodeSocketValueCollection *value = static_cast<const bNodeSocketValueCollection *>(
socket.default_value);
return bke::idprop::create(socket.identifier, reinterpret_cast<ID *>(value->value));
}
case SOCK_TEXTURE: {
const bNodeSocketValueTexture *value = static_cast<const bNodeSocketValueTexture *>(
socket.default_value);
return bke::idprop::create(socket.identifier, reinterpret_cast<ID *>(value->value));
}
case SOCK_IMAGE: {
const bNodeSocketValueImage *value = static_cast<const bNodeSocketValueImage *>(
socket.default_value);
return bke::idprop::create(socket.identifier, reinterpret_cast<ID *>(value->value));
}
case SOCK_MATERIAL: {
const bNodeSocketValueMaterial *value = static_cast<const bNodeSocketValueMaterial *>(
socket.default_value);
return bke::idprop::create(socket.identifier, reinterpret_cast<ID *>(value->value));
}
}
return nullptr;
}
static bool id_property_type_matches_socket(const bNodeSocket &socket, const IDProperty &property)
{
switch (socket.type) {
case SOCK_FLOAT:
return ELEM(property.type, IDP_FLOAT, IDP_DOUBLE);
case SOCK_INT:
return property.type == IDP_INT;
case SOCK_VECTOR:
return property.type == IDP_ARRAY && property.subtype == IDP_FLOAT && property.len == 3;
case SOCK_RGBA:
return property.type == IDP_ARRAY && property.subtype == IDP_FLOAT && property.len == 4;
case SOCK_BOOLEAN:
return property.type == IDP_BOOLEAN;
case SOCK_STRING:
return property.type == IDP_STRING;
case SOCK_OBJECT:
case SOCK_COLLECTION:
case SOCK_TEXTURE:
case SOCK_IMAGE:
case SOCK_MATERIAL:
return property.type == IDP_ID;
}
BLI_assert_unreachable();
return false;
}
static void init_socket_cpp_value_from_property(const IDProperty &property,
const eNodeSocketDatatype socket_value_type,
void *r_value)
{
switch (socket_value_type) {
case SOCK_FLOAT: {
float value = 0.0f;
if (property.type == IDP_FLOAT) {
value = IDP_Float(&property);
}
else if (property.type == IDP_DOUBLE) {
value = float(IDP_Double(&property));
}
new (r_value) fn::ValueOrField<float>(value);
break;
}
case SOCK_INT: {
int value = IDP_Int(&property);
new (r_value) fn::ValueOrField<int>(value);
break;
}
case SOCK_VECTOR: {
float3 value;
copy_v3_v3(value, (const float *)IDP_Array(&property));
new (r_value) fn::ValueOrField<float3>(value);
break;
}
case SOCK_RGBA: {
ColorGeometry4f value;
copy_v4_v4((float *)value, (const float *)IDP_Array(&property));
new (r_value) fn::ValueOrField<ColorGeometry4f>(value);
break;
}
case SOCK_BOOLEAN: {
const bool value = IDP_Bool(&property);
new (r_value) fn::ValueOrField<bool>(value);
break;
}
case SOCK_STRING: {
std::string value = IDP_String(&property);
new (r_value) fn::ValueOrField<std::string>(std::move(value));
break;
}
case SOCK_OBJECT: {
ID *id = IDP_Id(&property);
Object *object = (id && GS(id->name) == ID_OB) ? (Object *)id : nullptr;
*(Object **)r_value = object;
break;
}
case SOCK_COLLECTION: {
ID *id = IDP_Id(&property);
Collection *collection = (id && GS(id->name) == ID_GR) ? (Collection *)id : nullptr;
*(Collection **)r_value = collection;
break;
}
case SOCK_TEXTURE: {
ID *id = IDP_Id(&property);
Tex *texture = (id && GS(id->name) == ID_TE) ? (Tex *)id : nullptr;
*(Tex **)r_value = texture;
break;
}
case SOCK_IMAGE: {
ID *id = IDP_Id(&property);
Image *image = (id && GS(id->name) == ID_IM) ? (Image *)id : nullptr;
*(Image **)r_value = image;
break;
}
case SOCK_MATERIAL: {
ID *id = IDP_Id(&property);
Material *material = (id && GS(id->name) == ID_MA) ? (Material *)id : nullptr;
*(Material **)r_value = material;
break;
}
default: {
BLI_assert_unreachable();
break;
}
}
}
static void update_input_properties_from_node_tree(const bNodeTree &tree,
const IDProperty *old_properties,
IDProperty &properties)
{
tree.ensure_topology_cache();
const Span<const bNodeSocket *> tree_inputs = tree.interface_inputs();
for (const int i : tree_inputs.index_range()) {
const bNodeSocket &socket = *tree_inputs[i];
IDProperty *new_prop = id_property_create_from_socket(socket).release();
if (new_prop == nullptr) {
/* Out of the set of supported input sockets, only
* geometry sockets aren't added to the modifier. */
BLI_assert(socket.type == SOCK_GEOMETRY);
continue;
}
new_prop->flag |= IDP_FLAG_OVERRIDABLE_LIBRARY;
if (socket.description[0] != '\0') {
IDPropertyUIData *ui_data = IDP_ui_data_ensure(new_prop);
ui_data->description = BLI_strdup(socket.description);
}
IDP_AddToGroup(&properties, new_prop);
if (old_properties != nullptr) {
const IDProperty *old_prop = IDP_GetPropertyFromGroup(old_properties, socket.identifier);
if (old_prop != nullptr) {
if (id_property_type_matches_socket(socket, *old_prop)) {
/* #IDP_CopyPropertyContent replaces the UI data as well, which we don't (we only
* want to replace the values). So release it temporarily and replace it after. */
IDPropertyUIData *ui_data = new_prop->ui_data;
new_prop->ui_data = nullptr;
IDP_CopyPropertyContent(new_prop, old_prop);
if (new_prop->ui_data != nullptr) {
IDP_ui_data_free(new_prop);
}
new_prop->ui_data = ui_data;
}
else if (old_prop->type == IDP_INT && new_prop->type == IDP_BOOLEAN) {
/* Support versioning from integer to boolean property values. The actual value is stored
* in the same variable for both types. */
new_prop->data.val = old_prop->data.val != 0;
}
}
}
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(&properties, use_attribute_prop);
IDProperty *attribute_prop = IDP_New(IDP_STRING, &idprop, attribute_name_id.c_str());
IDP_AddToGroup(&properties, attribute_prop);
if (old_properties == nullptr) {
if (socket.default_attribute_name && socket.default_attribute_name[0] != '\0') {
IDP_AssignString(attribute_prop, socket.default_attribute_name, MAX_NAME);
IDP_Int(use_attribute_prop) = 1;
}
}
else {
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);
}
}
}
}
}
static void update_output_properties_from_node_tree(const bNodeTree &tree,
const IDProperty *old_properties,
IDProperty &properties)
{
tree.ensure_topology_cache();
const Span<const bNodeSocket *> tree_outputs = tree.interface_outputs();
for (const int i : tree_outputs.index_range()) {
const bNodeSocket &socket = *tree_outputs[i];
if (!socket_type_has_attribute_toggle(socket)) {
continue;
}
const std::string idprop_name = socket.identifier + attribute_name_suffix;
IDProperty *new_prop = IDP_NewString("", idprop_name.c_str(), MAX_NAME);
if (socket.description[0] != '\0') {
IDPropertyUIData *ui_data = IDP_ui_data_ensure(new_prop);
ui_data->description = BLI_strdup(socket.description);
}
IDP_AddToGroup(&properties, new_prop);
if (old_properties == nullptr) {
if (socket.default_attribute_name && socket.default_attribute_name[0] != '\0') {
IDP_AssignString(new_prop, socket.default_attribute_name, MAX_NAME);
}
}
else {
IDProperty *old_prop = IDP_GetPropertyFromGroup(old_properties, idprop_name.c_str());
if (old_prop != nullptr) {
/* #IDP_CopyPropertyContent replaces the UI data as well, which we don't (we only
* want to replace the values). So release it temporarily and replace it after. */
IDPropertyUIData *ui_data = new_prop->ui_data;
new_prop->ui_data = nullptr;
IDP_CopyPropertyContent(new_prop, old_prop);
if (new_prop->ui_data != nullptr) {
IDP_ui_data_free(new_prop);
}
new_prop->ui_data = ui_data;
}
}
}
}
} // namespace blender
void MOD_nodes_update_interface(Object *object, NodesModifierData *nmd)
@@ -754,8 +398,9 @@ void MOD_nodes_update_interface(Object *object, NodesModifierData *nmd)
}
IDProperty *new_properties = nmd->settings.properties;
update_input_properties_from_node_tree(*nmd->node_group, old_properties, *new_properties);
update_output_properties_from_node_tree(*nmd->node_group, old_properties, *new_properties);
nodes::update_input_properties_from_node_tree(*nmd->node_group, old_properties, *new_properties);
nodes::update_output_properties_from_node_tree(
*nmd->node_group, old_properties, *new_properties);
if (old_properties != nullptr) {
IDP_FreeProperty(old_properties);
@@ -766,61 +411,6 @@ void MOD_nodes_update_interface(Object *object, NodesModifierData *nmd)
namespace blender {
static void initialize_group_input(const bNodeTree &tree,
const IDProperty *properties,
const int input_index,
void *r_value)
{
const bNodeSocket &io_input = *tree.interface_inputs()[input_index];
const bNodeSocketType &socket_type = *io_input.typeinfo;
const eNodeSocketDatatype socket_data_type = static_cast<eNodeSocketDatatype>(io_input.type);
if (properties == nullptr) {
socket_type.get_geometry_nodes_cpp_value(io_input, r_value);
return;
}
const IDProperty *property = IDP_GetPropertyFromGroup(properties, io_input.identifier);
if (property == nullptr) {
socket_type.get_geometry_nodes_cpp_value(io_input, r_value);
return;
}
if (!id_property_type_matches_socket(io_input, *property)) {
socket_type.get_geometry_nodes_cpp_value(io_input, r_value);
return;
}
if (!input_has_attribute_toggle(tree, input_index)) {
init_socket_cpp_value_from_property(*property, socket_data_type, r_value);
return;
}
const IDProperty *property_use_attribute = IDP_GetPropertyFromGroup(
properties, (io_input.identifier + use_attribute_suffix).c_str());
const IDProperty *property_attribute_name = IDP_GetPropertyFromGroup(
properties, (io_input.identifier + attribute_name_suffix).c_str());
if (property_use_attribute == nullptr || property_attribute_name == nullptr) {
init_socket_cpp_value_from_property(*property, socket_data_type, 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)};
if (!bke::allow_procedural_attribute_access(attribute_name)) {
init_socket_cpp_value_from_property(*property, socket_data_type, r_value);
return;
}
fn::GField attribute_field = bke::AttributeFieldInput::Create(attribute_name,
*socket_type.base_cpp_type);
const auto *value_or_field_cpp_type = fn::ValueOrFieldCPPType::get_from_self(
*socket_type.geometry_nodes_cpp_type);
BLI_assert(value_or_field_cpp_type != nullptr);
value_or_field_cpp_type->construct_from_field(r_value, std::move(attribute_field));
}
else {
init_socket_cpp_value_from_property(*property, socket_data_type, r_value);
}
}
static const lf::FunctionNode *find_viewer_lf_node(const bNode &viewer_bnode)
{
if (const nodes::GeometryNodesLazyFunctionGraphInfo *lf_graph_info =
@@ -963,287 +553,9 @@ static void clear_runtime_data(NodesModifierData *nmd)
}
}
struct OutputAttributeInfo {
fn::GField field;
StringRefNull name;
};
struct OutputAttributeToStore {
GeometryComponentType component_type;
eAttrDomain domain;
StringRefNull name;
GMutableSpan data;
};
/**
* The output attributes are organized based on their domain, because attributes on the same domain
* can be evaluated together.
*/
static MultiValueMap<eAttrDomain, OutputAttributeInfo> find_output_attributes_to_store(
const bNodeTree &tree,
const IDProperty *properties,
const bNode &output_node,
Span<GMutablePointer> output_values)
{
MultiValueMap<eAttrDomain, OutputAttributeInfo> outputs_by_domain;
for (const bNodeSocket *socket : output_node.input_sockets().drop_front(1).drop_back(1)) {
if (!socket_type_has_attribute_toggle(*socket)) {
continue;
}
const std::string prop_name = socket->identifier + attribute_name_suffix;
const IDProperty *prop = IDP_GetPropertyFromGroup(properties, prop_name.c_str());
if (prop == nullptr) {
continue;
}
const StringRefNull attribute_name = IDP_String(prop);
if (attribute_name.is_empty()) {
continue;
}
if (!bke::allow_procedural_attribute_access(attribute_name)) {
continue;
}
const int index = socket->index();
const GPointer value = output_values[index];
const auto *value_or_field_type = fn::ValueOrFieldCPPType::get_from_self(*value.type());
BLI_assert(value_or_field_type != nullptr);
const fn::GField field = value_or_field_type->as_field(value.get());
const bNodeSocket *interface_socket = (const bNodeSocket *)BLI_findlink(&tree.outputs, index);
const eAttrDomain domain = (eAttrDomain)interface_socket->attribute_domain;
OutputAttributeInfo output_info;
output_info.field = std::move(field);
output_info.name = attribute_name;
outputs_by_domain.add(domain, std::move(output_info));
}
return outputs_by_domain;
}
/**
* The computed values are stored in newly allocated arrays. They still have to be moved to the
* actual geometry.
*/
static Vector<OutputAttributeToStore> compute_attributes_to_store(
const GeometrySet &geometry,
const MultiValueMap<eAttrDomain, OutputAttributeInfo> &outputs_by_domain)
{
Vector<OutputAttributeToStore> attributes_to_store;
for (const GeometryComponentType component_type : {GEO_COMPONENT_TYPE_MESH,
GEO_COMPONENT_TYPE_POINT_CLOUD,
GEO_COMPONENT_TYPE_CURVE,
GEO_COMPONENT_TYPE_INSTANCES}) {
if (!geometry.has(component_type)) {
continue;
}
const GeometryComponent &component = *geometry.get_component_for_read(component_type);
const bke::AttributeAccessor attributes = *component.attributes();
for (const auto item : outputs_by_domain.items()) {
const eAttrDomain domain = item.key;
const Span<OutputAttributeInfo> outputs_info = item.value;
if (!attributes.domain_supported(domain)) {
continue;
}
const int domain_size = attributes.domain_size(domain);
bke::GeometryFieldContext field_context{component, domain};
fn::FieldEvaluator field_evaluator{field_context, domain_size};
for (const OutputAttributeInfo &output_info : outputs_info) {
const CPPType &type = output_info.field.cpp_type();
const bke::AttributeValidator validator = attributes.lookup_validator(output_info.name);
OutputAttributeToStore store{
component_type,
domain,
output_info.name,
GMutableSpan{
type, MEM_malloc_arrayN(domain_size, type.size(), __func__), domain_size}};
fn::GField field = validator.validate_field_if_necessary(output_info.field);
field_evaluator.add_with_destination(std::move(field), store.data);
attributes_to_store.append(store);
}
field_evaluator.evaluate();
}
}
return attributes_to_store;
}
static void store_computed_output_attributes(
GeometrySet &geometry, const Span<OutputAttributeToStore> attributes_to_store)
{
for (const OutputAttributeToStore &store : attributes_to_store) {
GeometryComponent &component = geometry.get_component_for_write(store.component_type);
bke::MutableAttributeAccessor attributes = *component.attributes_for_write();
const eCustomDataType data_type = bke::cpp_type_to_custom_data_type(store.data.type());
const std::optional<bke::AttributeMetaData> meta_data = attributes.lookup_meta_data(
store.name);
/* Attempt to remove the attribute if it already exists but the domain and type don't match.
* Removing the attribute won't succeed if it is built in and non-removable. */
if (meta_data.has_value() &&
(meta_data->domain != store.domain || meta_data->data_type != data_type)) {
attributes.remove(store.name);
}
/* Try to create the attribute reusing the stored buffer. This will only succeed if the
* attribute didn't exist before, or if it existed but was removed above. */
if (attributes.add(store.name,
store.domain,
bke::cpp_type_to_custom_data_type(store.data.type()),
bke::AttributeInitMoveArray(store.data.data()))) {
continue;
}
bke::GAttributeWriter attribute = attributes.lookup_or_add_for_write(
store.name, store.domain, data_type);
if (attribute) {
attribute.varray.set_all(store.data.data());
attribute.finish();
}
/* We were unable to reuse the data, so it must be destructed and freed. */
store.data.type().destruct_n(store.data.data(), store.data.size());
MEM_freeN(store.data.data());
}
}
static void store_output_attributes(GeometrySet &geometry,
const bNodeTree &tree,
const IDProperty *properties,
const bNode &output_node,
Span<GMutablePointer> output_values)
{
/* All new attribute values have to be computed before the geometry is actually changed. This is
* necessary because some fields might depend on attributes that are overwritten. */
MultiValueMap<eAttrDomain, OutputAttributeInfo> outputs_by_domain =
find_output_attributes_to_store(tree, properties, output_node, output_values);
Vector<OutputAttributeToStore> attributes_to_store = compute_attributes_to_store(
geometry, outputs_by_domain);
store_computed_output_attributes(geometry, attributes_to_store);
}
/**
* Evaluate a node group to compute the output geometry.
*/
static GeometrySet compute_geometry(const bNodeTree &btree,
const nodes::GeometryNodesLazyFunctionGraphInfo &lf_graph_info,
const bNode &output_node,
GeometrySet input_geometry_set,
NodesModifierData *nmd,
const ModifierEvalContext *ctx)
{
const nodes::GeometryNodeLazyFunctionGraphMapping &mapping = lf_graph_info.mapping;
Vector<const lf::OutputSocket *> graph_inputs = mapping.group_input_sockets;
graph_inputs.extend(mapping.group_output_used_sockets);
graph_inputs.extend(mapping.attribute_set_by_geometry_output.values().begin(),
mapping.attribute_set_by_geometry_output.values().end());
Vector<const lf::InputSocket *> graph_outputs = mapping.standard_group_output_sockets;
Array<GMutablePointer> param_inputs(graph_inputs.size());
Array<GMutablePointer> param_outputs(graph_outputs.size());
Array<std::optional<lf::ValueUsage>> param_input_usages(graph_inputs.size());
Array<lf::ValueUsage> param_output_usages(graph_outputs.size(), lf::ValueUsage::Used);
Array<bool> param_set_outputs(graph_outputs.size(), false);
nodes::GeometryNodesLazyFunctionLogger lf_logger(lf_graph_info);
nodes::GeometryNodesLazyFunctionSideEffectProvider lf_side_effect_provider;
lf::GraphExecutor graph_executor{
lf_graph_info.graph, graph_inputs, graph_outputs, &lf_logger, &lf_side_effect_provider};
nodes::GeoNodesModifierData geo_nodes_modifier_data;
geo_nodes_modifier_data.depsgraph = ctx->depsgraph;
geo_nodes_modifier_data.self_object = ctx->object;
auto eval_log = std::make_unique<geo_log::GeoModifierLog>();
Set<ComputeContextHash> socket_log_contexts;
if (logging_enabled(ctx)) {
geo_nodes_modifier_data.eval_log = eval_log.get();
find_socket_log_contexts(*nmd, *ctx, socket_log_contexts);
geo_nodes_modifier_data.socket_log_contexts = &socket_log_contexts;
}
MultiValueMap<ComputeContextHash, const lf::FunctionNode *> r_side_effect_nodes;
find_side_effect_nodes(*nmd, *ctx, r_side_effect_nodes);
geo_nodes_modifier_data.side_effect_nodes = &r_side_effect_nodes;
nodes::GeoNodesLFUserData user_data;
user_data.modifier_data = &geo_nodes_modifier_data;
bke::ModifierComputeContext modifier_compute_context{nullptr, nmd->modifier.name};
user_data.compute_context = &modifier_compute_context;
LinearAllocator<> allocator;
Vector<GMutablePointer> inputs_to_destruct;
const IDProperty *properties = nmd->settings.properties;
int input_index = -1;
for (const int i : btree.interface_inputs().index_range()) {
input_index++;
const bNodeSocket &interface_socket = *btree.interface_inputs()[i];
if (interface_socket.type == SOCK_GEOMETRY && input_index == 0) {
param_inputs[input_index] = &input_geometry_set;
continue;
}
const CPPType *type = interface_socket.typeinfo->geometry_nodes_cpp_type;
BLI_assert(type != nullptr);
void *value = allocator.allocate(type->size(), type->alignment());
initialize_group_input(btree, properties, i, value);
param_inputs[input_index] = {type, value};
inputs_to_destruct.append({type, value});
}
Array<bool> output_used_inputs(btree.interface_outputs().size(), true);
for (const int i : btree.interface_outputs().index_range()) {
input_index++;
param_inputs[input_index] = &output_used_inputs[i];
}
Array<bke::AnonymousAttributeSet> attributes_to_propagate(
mapping.attribute_set_by_geometry_output.size());
for (const int i : attributes_to_propagate.index_range()) {
input_index++;
param_inputs[input_index] = &attributes_to_propagate[i];
}
for (const int i : graph_outputs.index_range()) {
const lf::InputSocket &socket = *graph_outputs[i];
const CPPType &type = socket.type();
void *buffer = allocator.allocate(type.size(), type.alignment());
param_outputs[i] = {type, buffer};
}
lf::Context lf_context;
lf_context.storage = graph_executor.init_storage(allocator);
lf_context.user_data = &user_data;
lf::BasicParams lf_params{graph_executor,
param_inputs,
param_outputs,
param_input_usages,
param_output_usages,
param_set_outputs};
graph_executor.execute(lf_params, lf_context);
graph_executor.destruct_storage(lf_context.storage);
for (GMutablePointer &ptr : inputs_to_destruct) {
ptr.destruct();
}
GeometrySet output_geometry_set = std::move(*static_cast<GeometrySet *>(param_outputs[0].get()));
store_output_attributes(output_geometry_set, btree, properties, output_node, param_outputs);
for (GMutablePointer &ptr : param_outputs) {
ptr.destruct();
}
if (logging_enabled(ctx)) {
NodesModifierData *nmd_orig = reinterpret_cast<NodesModifierData *>(
BKE_modifier_get_original(ctx->object, &nmd->modifier));
delete static_cast<geo_log::GeoModifierLog *>(nmd_orig->runtime_eval_log);
nmd_orig->runtime_eval_log = eval_log.release();
}
return output_geometry_set;
}
/**
* \note This could be done in #initialize_group_input, though that would require adding the
@@ -1274,7 +586,7 @@ static void check_property_socket_sync(const Object *ob, ModifierData *md)
continue;
}
if (!id_property_type_matches_socket(*socket, *property)) {
if (!nodes::id_property_type_matches_socket(*socket, *property)) {
BKE_modifier_set_error(
ob, md, "Property type does not match input socket \"(%s)\"", socket->name);
continue;
@@ -1340,8 +652,35 @@ static void modifyGeometry(ModifierData *md,
use_orig_index_polys = CustomData_has_layer(&mesh->pdata, CD_ORIGINDEX);
}
geometry_set = compute_geometry(
tree, *lf_graph_info, *output_node, std::move(geometry_set), nmd, ctx);
NodesModifierData *nmd_orig = reinterpret_cast<NodesModifierData *>(
BKE_modifier_get_original(ctx->object, &nmd->modifier));
delete static_cast<geo_log::GeoModifierLog *>(nmd_orig->runtime_eval_log);
if (logging_enabled(ctx)) {
nmd_orig->runtime_eval_log = new geo_log::GeoModifierLog();
}
bke::ModifierComputeContext modifier_compute_context{nullptr, nmd->modifier.name};
MultiValueMap<ComputeContextHash, const lf::FunctionNode *> side_effect_nodes;
Set<ComputeContextHash> socket_log_contexts;
nodes::GeoNodesModifierData modifier_eval_data{};
modifier_eval_data.depsgraph = ctx->depsgraph;
modifier_eval_data.self_object = ctx->object;
modifier_eval_data.eval_log = static_cast<geo_log::GeoModifierLog *>(nmd_orig->runtime_eval_log);
if (modifier_eval_data.eval_log) {
find_side_effect_nodes(*nmd, *ctx, side_effect_nodes);
modifier_eval_data.side_effect_nodes = &side_effect_nodes;
find_socket_log_contexts(*nmd, *ctx, socket_log_contexts);
modifier_eval_data.socket_log_contexts = &socket_log_contexts;
}
geometry_set = nodes::execute_geometry_nodes(tree,
nmd->settings.properties,
modifier_compute_context,
std::move(geometry_set),
[&](nodes::GeoNodesLFUserData &user_data) {
user_data.modifier_data = &modifier_eval_data;
});
if (use_orig_index_verts || use_orig_index_edges || use_orig_index_polys) {
if (Mesh *mesh = geometry_set.get_mesh_for_write()) {
@@ -1637,7 +976,7 @@ static void draw_property_for_socket(const bContext &C,
/* 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. */
if (property == nullptr || !id_property_type_matches_socket(socket, *property)) {
if (property == nullptr || !nodes::id_property_type_matches_socket(socket, *property)) {
return;
}
@@ -1676,7 +1015,7 @@ static void draw_property_for_socket(const bContext &C,
break;
}
default: {
if (input_has_attribute_toggle(*nmd->node_group, socket_index)) {
if (nodes::input_has_attribute_toggle(*nmd->node_group, socket_index)) {
add_attribute_search_or_value_buttons(C, row, *nmd, md_ptr, socket);
}
else {
@@ -1684,7 +1023,7 @@ static void draw_property_for_socket(const bContext &C,
}
}
}
if (!input_has_attribute_toggle(*nmd->node_group, socket_index)) {
if (!nodes::input_has_attribute_toggle(*nmd->node_group, socket_index)) {
uiItemL(row, "", ICON_BLANK1);
}
}
@@ -1772,7 +1111,7 @@ static void output_attribute_panel_draw(const bContext *C, Panel *panel)
bool has_output_attribute = false;
if (nmd->node_group != nullptr && nmd->settings.properties != nullptr) {
LISTBASE_FOREACH (bNodeSocket *, socket, &nmd->node_group->outputs) {
if (socket_type_has_attribute_toggle(*socket)) {
if (nodes::socket_type_has_attribute_toggle(*socket)) {
has_output_attribute = true;
draw_property_for_output_socket(*C, layout, *nmd, ptr, *socket);
}

View File

@@ -41,6 +41,7 @@ set(INC
set(SRC
intern/add_node_search.cc
intern/derived_node_tree.cc
intern/geometry_nodes_execute.cc
intern/geometry_nodes_lazy_function.cc
intern/geometry_nodes_log.cc
intern/math_functions.cc
@@ -61,6 +62,7 @@ set(SRC
NOD_derived_node_tree.hh
NOD_geometry.h
NOD_geometry_exec.hh
NOD_geometry_nodes_execute.hh
NOD_geometry_nodes_lazy_function.hh
NOD_geometry_nodes_log.hh
NOD_math_functions.hh

View File

@@ -0,0 +1,74 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/**
* For evaluation, geometry node groups are converted to a lazy-function graph. The generated graph
* is cached per node group, so it only has to be generated once after a change.
*
* Node groups are *not* inlined into the lazy-function graph. This could be added in the future as
* it might improve performance in some cases, but generally does not seem necessary. Inlining node
* groups also has disadvantages like making per-node-group caches less useful, resulting in more
* overhead.
*
* Instead, group nodes are just like all other nodes in the lazy-function graph. What makes them
* special is that they reference the lazy-function graph of the group they reference.
*
* During lazy-function graph generation, a mapping between the #bNodeTree and
* #lazy_function::Graph is build that can be used when evaluating the graph (e.g. for logging).
*/
#include "BLI_compute_context.hh"
#include "BLI_function_ref.hh"
#include "BLI_multi_value_map.hh"
#include "FN_lazy_function_graph.hh"
#include "FN_lazy_function_graph_executor.hh"
#include "BKE_idprop.hh"
struct bNodeTree;
struct bNodeSocket;
struct Depsgraph;
struct IDProperty;
struct Object;
namespace blender::nodes {
struct GeoNodesLFUserData;
namespace geo_eval_log {
class GeoModifierLog;
} // namespace geo_eval_log
} // namespace blender::nodes
namespace blender::nodes {
/**
* \return Whether using an attribute to input values of this type is supported.
*/
bool socket_type_has_attribute_toggle(const bNodeSocket &socket);
/**
* \return Whether using an attribute to input values of this type is supported, and the node
* group's input for this socket accepts a field rather than just single values.
*/
bool input_has_attribute_toggle(const bNodeTree &node_tree, const int socket_index);
bool id_property_type_matches_socket(const bNodeSocket &socket, const IDProperty &property);
std::unique_ptr<IDProperty, bke::idprop::IDPropertyDeleter> id_property_create_from_socket(
const bNodeSocket &socket);
GeometrySet execute_geometry_nodes(const bNodeTree &btree,
const IDProperty *properties,
const ComputeContext &base_compute_context,
GeometrySet input_geometry,
FunctionRef<void(nodes::GeoNodesLFUserData &)> fill_user_data);
void update_input_properties_from_node_tree(const bNodeTree &tree,
const IDProperty *old_properties,
IDProperty &properties);
void update_output_properties_from_node_tree(const bNodeTree &tree,
const IDProperty *old_properties,
IDProperty &properties);
} // namespace blender::nodes

View File

@@ -59,6 +59,13 @@ struct GeoNodesModifierData {
const Set<ComputeContextHash> *socket_log_contexts = nullptr;
};
struct GeoNodesOperatorData {
/** The object currently effected by the operator. */
const Object *self_object = nullptr;
/** Current evaluated depsgraph. */
Depsgraph *depsgraph = nullptr;
};
/**
* Custom user data that is passed to every geometry nodes related lazy-function evaluation.
*/
@@ -67,6 +74,10 @@ struct GeoNodesLFUserData : public lf::UserData {
* Data from the modifier that is being evaluated.
*/
GeoNodesModifierData *modifier_data = nullptr;
/**
*
*/
GeoNodesOperatorData *operator_data = nullptr;
/**
* Current compute context. This is different depending in the (nested) node group that is being
* evaluated.

View File

@@ -31,6 +31,12 @@ bNodeTreeType *ntreeType_Geometry;
static void geometry_node_tree_get_from_context(
const bContext *C, bNodeTreeType * /*treetype*/, bNodeTree **r_ntree, ID **r_id, ID **r_from)
{
const SpaceNode *snode = CTX_wm_space_node(C);
if (snode->geometry_nodes_type == SNODE_GEOMETRY_OPERATOR) {
*r_ntree = snode->nodetree;
return;
}
const Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
BKE_view_layer_synced_ensure(scene, view_layer);

View File

@@ -0,0 +1,706 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup nodes
*
* This file mainly converts a #bNodeTree into a lazy-function graph. This generally works by
* creating a lazy-function for every node, which is then put into the lazy-function graph. Then
* the nodes in the new graph are linked based on links in the original #bNodeTree. Some additional
* nodes are inserted for things like type conversions and multi-input sockets.
*
* Currently, lazy-functions are even created for nodes that don't strictly require it, like
* reroutes or muted nodes. In the future we could avoid that at the cost of additional code
* complexity. So far, this does not seem to be a performance issue.
*/
#include "NOD_geometry_exec.hh"
#include "NOD_geometry_nodes_execute.hh"
#include "NOD_geometry_nodes_lazy_function.hh"
#include "NOD_multi_function.hh"
#include "NOD_node_declaration.hh"
#include "BLI_cpp_types.hh"
#include "BLI_dot_export.hh"
#include "BLI_hash.h"
#include "BLI_lazy_threading.hh"
#include "BLI_map.hh"
#include "DNA_ID.h"
#include "BKE_compute_contexts.hh"
#include "BKE_geometry_set.hh"
#include "BKE_idprop.hh"
#include "BKE_type_conversions.hh"
#include "FN_field_cpp_type.hh"
#include "FN_lazy_function_execute.hh"
#include "FN_lazy_function_graph_executor.hh"
#include "DEG_depsgraph_query.h"
namespace lf = blender::fn::lazy_function;
namespace geo_log = blender::nodes::geo_eval_log;
namespace blender::nodes {
static const std::string use_attribute_suffix = "_use_attribute";
static const std::string attribute_name_suffix = "_attribute_name";
bool socket_type_has_attribute_toggle(const bNodeSocket &socket)
{
return ELEM(socket.type, SOCK_FLOAT, SOCK_VECTOR, SOCK_BOOLEAN, SOCK_RGBA, SOCK_INT);
}
bool input_has_attribute_toggle(const bNodeTree &node_tree, const int socket_index)
{
BLI_assert(node_tree.runtime->field_inferencing_interface);
const nodes::FieldInferencingInterface &field_interface =
*node_tree.runtime->field_inferencing_interface;
return field_interface.inputs[socket_index] != nodes::InputSocketFieldType::None;
}
std::unique_ptr<IDProperty, bke::idprop::IDPropertyDeleter> id_property_create_from_socket(
const bNodeSocket &socket)
{
switch (socket.type) {
case SOCK_FLOAT: {
const bNodeSocketValueFloat *value = static_cast<const bNodeSocketValueFloat *>(
socket.default_value);
auto property = bke::idprop::create(socket.identifier, value->value);
IDPropertyUIDataFloat *ui_data = (IDPropertyUIDataFloat *)IDP_ui_data_ensure(property.get());
ui_data->base.rna_subtype = value->subtype;
ui_data->soft_min = double(value->min);
ui_data->soft_max = double(value->max);
ui_data->default_value = value->value;
return property;
}
case SOCK_INT: {
const bNodeSocketValueInt *value = static_cast<const bNodeSocketValueInt *>(
socket.default_value);
auto property = bke::idprop::create(socket.identifier, value->value);
IDPropertyUIDataInt *ui_data = (IDPropertyUIDataInt *)IDP_ui_data_ensure(property.get());
ui_data->base.rna_subtype = value->subtype;
ui_data->soft_min = value->min;
ui_data->soft_max = value->max;
ui_data->default_value = value->value;
return property;
}
case SOCK_VECTOR: {
const bNodeSocketValueVector *value = static_cast<const bNodeSocketValueVector *>(
socket.default_value);
auto property = bke::idprop::create(
socket.identifier, Span<float>{value->value[0], value->value[1], value->value[2]});
IDPropertyUIDataFloat *ui_data = (IDPropertyUIDataFloat *)IDP_ui_data_ensure(property.get());
ui_data->base.rna_subtype = value->subtype;
ui_data->soft_min = double(value->min);
ui_data->soft_max = double(value->max);
ui_data->default_array = (double *)MEM_mallocN(sizeof(double[3]), "mod_prop_default");
ui_data->default_array_len = 3;
for (const int i : IndexRange(3)) {
ui_data->default_array[i] = double(value->value[i]);
}
return property;
}
case SOCK_RGBA: {
const bNodeSocketValueRGBA *value = static_cast<const bNodeSocketValueRGBA *>(
socket.default_value);
auto property = bke::idprop::create(
socket.identifier,
Span<float>{value->value[0], value->value[1], value->value[2], value->value[3]});
IDPropertyUIDataFloat *ui_data = (IDPropertyUIDataFloat *)IDP_ui_data_ensure(property.get());
ui_data->base.rna_subtype = PROP_COLOR;
ui_data->default_array = (double *)MEM_mallocN(sizeof(double[4]), __func__);
ui_data->default_array_len = 4;
ui_data->min = 0.0;
ui_data->max = FLT_MAX;
ui_data->soft_min = 0.0;
ui_data->soft_max = 1.0;
for (const int i : IndexRange(4)) {
ui_data->default_array[i] = double(value->value[i]);
}
return property;
}
case SOCK_BOOLEAN: {
const bNodeSocketValueBoolean *value = static_cast<const bNodeSocketValueBoolean *>(
socket.default_value);
auto property = bke::idprop::create_bool(socket.identifier, value->value);
IDPropertyUIDataBool *ui_data = (IDPropertyUIDataBool *)IDP_ui_data_ensure(property.get());
ui_data->default_value = value->value != 0;
return property;
}
case SOCK_STRING: {
const bNodeSocketValueString *value = static_cast<const bNodeSocketValueString *>(
socket.default_value);
auto property = bke::idprop::create(socket.identifier, value->value);
IDPropertyUIDataString *ui_data = (IDPropertyUIDataString *)IDP_ui_data_ensure(
property.get());
ui_data->default_value = BLI_strdup(value->value);
return property;
}
case SOCK_OBJECT: {
const bNodeSocketValueObject *value = static_cast<const bNodeSocketValueObject *>(
socket.default_value);
auto property = bke::idprop::create(socket.identifier, reinterpret_cast<ID *>(value->value));
IDPropertyUIDataID *ui_data = (IDPropertyUIDataID *)IDP_ui_data_ensure(property.get());
ui_data->id_type = ID_OB;
return property;
}
case SOCK_COLLECTION: {
const bNodeSocketValueCollection *value = static_cast<const bNodeSocketValueCollection *>(
socket.default_value);
return bke::idprop::create(socket.identifier, reinterpret_cast<ID *>(value->value));
}
case SOCK_TEXTURE: {
const bNodeSocketValueTexture *value = static_cast<const bNodeSocketValueTexture *>(
socket.default_value);
return bke::idprop::create(socket.identifier, reinterpret_cast<ID *>(value->value));
}
case SOCK_IMAGE: {
const bNodeSocketValueImage *value = static_cast<const bNodeSocketValueImage *>(
socket.default_value);
return bke::idprop::create(socket.identifier, reinterpret_cast<ID *>(value->value));
}
case SOCK_MATERIAL: {
const bNodeSocketValueMaterial *value = static_cast<const bNodeSocketValueMaterial *>(
socket.default_value);
return bke::idprop::create(socket.identifier, reinterpret_cast<ID *>(value->value));
}
}
return nullptr;
}
bool id_property_type_matches_socket(const bNodeSocket &socket, const IDProperty &property)
{
switch (socket.type) {
case SOCK_FLOAT:
return ELEM(property.type, IDP_FLOAT, IDP_DOUBLE);
case SOCK_INT:
return property.type == IDP_INT;
case SOCK_VECTOR:
return property.type == IDP_ARRAY && property.subtype == IDP_FLOAT && property.len == 3;
case SOCK_RGBA:
return property.type == IDP_ARRAY && property.subtype == IDP_FLOAT && property.len == 4;
case SOCK_BOOLEAN:
return property.type == IDP_BOOLEAN;
case SOCK_STRING:
return property.type == IDP_STRING;
case SOCK_OBJECT:
case SOCK_COLLECTION:
case SOCK_TEXTURE:
case SOCK_IMAGE:
case SOCK_MATERIAL:
return property.type == IDP_ID;
}
BLI_assert_unreachable();
return false;
}
static void init_socket_cpp_value_from_property(const IDProperty &property,
const eNodeSocketDatatype socket_value_type,
void *r_value)
{
switch (socket_value_type) {
case SOCK_FLOAT: {
float value = 0.0f;
if (property.type == IDP_FLOAT) {
value = IDP_Float(&property);
}
else if (property.type == IDP_DOUBLE) {
value = float(IDP_Double(&property));
}
new (r_value) fn::ValueOrField<float>(value);
break;
}
case SOCK_INT: {
int value = IDP_Int(&property);
new (r_value) fn::ValueOrField<int>(value);
break;
}
case SOCK_VECTOR: {
float3 value = (const float *)IDP_Array(&property);
new (r_value) fn::ValueOrField<float3>(value);
break;
}
case SOCK_RGBA: {
ColorGeometry4f value = (const float *)IDP_Array(&property);
new (r_value) fn::ValueOrField<ColorGeometry4f>(value);
break;
}
case SOCK_BOOLEAN: {
const bool value = IDP_Bool(&property);
new (r_value) fn::ValueOrField<bool>(value);
break;
}
case SOCK_STRING: {
std::string value = IDP_String(&property);
new (r_value) fn::ValueOrField<std::string>(std::move(value));
break;
}
case SOCK_OBJECT: {
ID *id = IDP_Id(&property);
Object *object = (id && GS(id->name) == ID_OB) ? (Object *)id : nullptr;
*(Object **)r_value = object;
break;
}
case SOCK_COLLECTION: {
ID *id = IDP_Id(&property);
Collection *collection = (id && GS(id->name) == ID_GR) ? (Collection *)id : nullptr;
*(Collection **)r_value = collection;
break;
}
case SOCK_TEXTURE: {
ID *id = IDP_Id(&property);
Tex *texture = (id && GS(id->name) == ID_TE) ? (Tex *)id : nullptr;
*(Tex **)r_value = texture;
break;
}
case SOCK_IMAGE: {
ID *id = IDP_Id(&property);
Image *image = (id && GS(id->name) == ID_IM) ? (Image *)id : nullptr;
*(Image **)r_value = image;
break;
}
case SOCK_MATERIAL: {
ID *id = IDP_Id(&property);
Material *material = (id && GS(id->name) == ID_MA) ? (Material *)id : nullptr;
*(Material **)r_value = material;
break;
}
default: {
BLI_assert_unreachable();
break;
}
}
}
static void initialize_group_input(const bNodeTree &tree,
const IDProperty *properties,
const int input_index,
void *r_value)
{
const bNodeSocket &io_input = *tree.interface_inputs()[input_index];
const bNodeSocketType &socket_type = *io_input.typeinfo;
const eNodeSocketDatatype socket_data_type = static_cast<eNodeSocketDatatype>(io_input.type);
if (properties == nullptr) {
socket_type.get_geometry_nodes_cpp_value(io_input, r_value);
return;
}
const IDProperty *property = IDP_GetPropertyFromGroup(properties, io_input.identifier);
if (property == nullptr) {
socket_type.get_geometry_nodes_cpp_value(io_input, r_value);
return;
}
if (!id_property_type_matches_socket(io_input, *property)) {
socket_type.get_geometry_nodes_cpp_value(io_input, r_value);
return;
}
if (!input_has_attribute_toggle(tree, input_index)) {
init_socket_cpp_value_from_property(*property, socket_data_type, r_value);
return;
}
const IDProperty *property_use_attribute = IDP_GetPropertyFromGroup(
properties, (io_input.identifier + use_attribute_suffix).c_str());
const IDProperty *property_attribute_name = IDP_GetPropertyFromGroup(
properties, (io_input.identifier + attribute_name_suffix).c_str());
if (property_use_attribute == nullptr || property_attribute_name == nullptr) {
init_socket_cpp_value_from_property(*property, socket_data_type, 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)};
if (!bke::allow_procedural_attribute_access(attribute_name)) {
init_socket_cpp_value_from_property(*property, socket_data_type, r_value);
return;
}
fn::GField attribute_field = bke::AttributeFieldInput::Create(attribute_name,
*socket_type.base_cpp_type);
const auto *value_or_field_cpp_type = fn::ValueOrFieldCPPType::get_from_self(
*socket_type.geometry_nodes_cpp_type);
BLI_assert(value_or_field_cpp_type != nullptr);
value_or_field_cpp_type->construct_from_field(r_value, std::move(attribute_field));
}
else {
init_socket_cpp_value_from_property(*property, socket_data_type, r_value);
}
}
struct OutputAttributeInfo {
fn::GField field;
StringRefNull name;
};
struct OutputAttributeToStore {
GeometryComponentType component_type;
eAttrDomain domain;
StringRefNull name;
GMutableSpan data;
};
/**
* The output attributes are organized based on their domain, because attributes on the same domain
* can be evaluated together.
*/
static MultiValueMap<eAttrDomain, OutputAttributeInfo> find_output_attributes_to_store(
const bNodeTree &tree, const IDProperty *properties, Span<GMutablePointer> output_values)
{
const bNode &output_node = *tree.group_output_node();
MultiValueMap<eAttrDomain, OutputAttributeInfo> outputs_by_domain;
for (const bNodeSocket *socket : output_node.input_sockets().drop_front(1).drop_back(1)) {
if (!socket_type_has_attribute_toggle(*socket)) {
continue;
}
const std::string prop_name = socket->identifier + attribute_name_suffix;
const IDProperty *prop = IDP_GetPropertyFromGroup(properties, prop_name.c_str());
if (prop == nullptr) {
continue;
}
const StringRefNull attribute_name = IDP_String(prop);
if (attribute_name.is_empty()) {
continue;
}
if (!bke::allow_procedural_attribute_access(attribute_name)) {
continue;
}
const int index = socket->index();
const GPointer value = output_values[index];
const auto *value_or_field_type = fn::ValueOrFieldCPPType::get_from_self(*value.type());
BLI_assert(value_or_field_type != nullptr);
const fn::GField field = value_or_field_type->as_field(value.get());
const bNodeSocket *interface_socket = (const bNodeSocket *)BLI_findlink(&tree.outputs, index);
const eAttrDomain domain = (eAttrDomain)interface_socket->attribute_domain;
OutputAttributeInfo output_info;
output_info.field = std::move(field);
output_info.name = attribute_name;
outputs_by_domain.add(domain, std::move(output_info));
}
return outputs_by_domain;
}
/**
* The computed values are stored in newly allocated arrays. They still have to be moved to the
* actual geometry.
*/
static Vector<OutputAttributeToStore> compute_attributes_to_store(
const GeometrySet &geometry,
const MultiValueMap<eAttrDomain, OutputAttributeInfo> &outputs_by_domain)
{
Vector<OutputAttributeToStore> attributes_to_store;
for (const GeometryComponentType component_type : {GEO_COMPONENT_TYPE_MESH,
GEO_COMPONENT_TYPE_POINT_CLOUD,
GEO_COMPONENT_TYPE_CURVE,
GEO_COMPONENT_TYPE_INSTANCES}) {
if (!geometry.has(component_type)) {
continue;
}
const GeometryComponent &component = *geometry.get_component_for_read(component_type);
const bke::AttributeAccessor attributes = *component.attributes();
for (const auto item : outputs_by_domain.items()) {
const eAttrDomain domain = item.key;
const Span<OutputAttributeInfo> outputs_info = item.value;
if (!attributes.domain_supported(domain)) {
continue;
}
const int domain_size = attributes.domain_size(domain);
bke::GeometryFieldContext field_context{component, domain};
fn::FieldEvaluator field_evaluator{field_context, domain_size};
for (const OutputAttributeInfo &output_info : outputs_info) {
const CPPType &type = output_info.field.cpp_type();
const bke::AttributeValidator validator = attributes.lookup_validator(output_info.name);
OutputAttributeToStore store{
component_type,
domain,
output_info.name,
GMutableSpan{
type, MEM_malloc_arrayN(domain_size, type.size(), __func__), domain_size}};
fn::GField field = validator.validate_field_if_necessary(output_info.field);
field_evaluator.add_with_destination(std::move(field), store.data);
attributes_to_store.append(store);
}
field_evaluator.evaluate();
}
}
return attributes_to_store;
}
static void store_computed_output_attributes(
GeometrySet &geometry, const Span<OutputAttributeToStore> attributes_to_store)
{
for (const OutputAttributeToStore &store : attributes_to_store) {
GeometryComponent &component = geometry.get_component_for_write(store.component_type);
bke::MutableAttributeAccessor attributes = *component.attributes_for_write();
const eCustomDataType data_type = bke::cpp_type_to_custom_data_type(store.data.type());
const std::optional<bke::AttributeMetaData> meta_data = attributes.lookup_meta_data(
store.name);
/* Attempt to remove the attribute if it already exists but the domain and type don't match.
* Removing the attribute won't succeed if it is built in and non-removable. */
if (meta_data.has_value() &&
(meta_data->domain != store.domain || meta_data->data_type != data_type)) {
attributes.remove(store.name);
}
/* Try to create the attribute reusing the stored buffer. This will only succeed if the
* attribute didn't exist before, or if it existed but was removed above. */
if (attributes.add(store.name,
store.domain,
bke::cpp_type_to_custom_data_type(store.data.type()),
bke::AttributeInitMoveArray(store.data.data()))) {
continue;
}
bke::GAttributeWriter attribute = attributes.lookup_or_add_for_write(
store.name, store.domain, data_type);
if (attribute) {
attribute.varray.set_all(store.data.data());
attribute.finish();
}
/* We were unable to reuse the data, so it must be destructed and freed. */
store.data.type().destruct_n(store.data.data(), store.data.size());
MEM_freeN(store.data.data());
}
}
static void store_output_attributes(GeometrySet &geometry,
const bNodeTree &tree,
const IDProperty *properties,
Span<GMutablePointer> output_values)
{
/* All new attribute values have to be computed before the geometry is actually changed. This is
* necessary because some fields might depend on attributes that are overwritten. */
MultiValueMap<eAttrDomain, OutputAttributeInfo> outputs_by_domain =
find_output_attributes_to_store(tree, properties, output_values);
Vector<OutputAttributeToStore> attributes_to_store = compute_attributes_to_store(
geometry, outputs_by_domain);
store_computed_output_attributes(geometry, attributes_to_store);
}
GeometrySet execute_geometry_nodes(
const bNodeTree &btree,
const IDProperty *properties,
const ComputeContext &base_compute_context,
GeometrySet input_geometry,
const FunctionRef<void(nodes::GeoNodesLFUserData &)> fill_user_data)
{
const nodes::GeometryNodesLazyFunctionGraphInfo &lf_graph_info =
*nodes::ensure_geometry_nodes_lazy_function_graph(btree);
const nodes::GeometryNodeLazyFunctionGraphMapping &mapping = lf_graph_info.mapping;
Vector<const lf::OutputSocket *> graph_inputs = mapping.group_input_sockets;
graph_inputs.extend(mapping.group_output_used_sockets);
graph_inputs.extend(mapping.attribute_set_by_geometry_output.values().begin(),
mapping.attribute_set_by_geometry_output.values().end());
Vector<const lf::InputSocket *> graph_outputs = mapping.standard_group_output_sockets;
Array<GMutablePointer> param_inputs(graph_inputs.size());
Array<GMutablePointer> param_outputs(graph_outputs.size());
Array<std::optional<lf::ValueUsage>> param_input_usages(graph_inputs.size());
Array<lf::ValueUsage> param_output_usages(graph_outputs.size(), lf::ValueUsage::Used);
Array<bool> param_set_outputs(graph_outputs.size(), false);
nodes::GeometryNodesLazyFunctionLogger lf_logger(lf_graph_info);
nodes::GeometryNodesLazyFunctionSideEffectProvider lf_side_effect_provider;
lf::GraphExecutor graph_executor{
lf_graph_info.graph, graph_inputs, graph_outputs, &lf_logger, &lf_side_effect_provider};
nodes::GeoNodesLFUserData user_data;
fill_user_data(user_data);
user_data.compute_context = &base_compute_context;
LinearAllocator<> allocator;
Vector<GMutablePointer> inputs_to_destruct;
int input_index = -1;
for (const int i : btree.interface_inputs().index_range()) {
input_index++;
const bNodeSocket &interface_socket = *btree.interface_inputs()[i];
if (interface_socket.type == SOCK_GEOMETRY && input_index == 0) {
param_inputs[input_index] = &input_geometry;
continue;
}
const CPPType *type = interface_socket.typeinfo->geometry_nodes_cpp_type;
BLI_assert(type != nullptr);
void *value = allocator.allocate(type->size(), type->alignment());
initialize_group_input(btree, properties, i, value);
param_inputs[input_index] = {type, value};
inputs_to_destruct.append({type, value});
}
Array<bool> output_used_inputs(btree.interface_outputs().size(), true);
for (const int i : btree.interface_outputs().index_range()) {
input_index++;
param_inputs[input_index] = &output_used_inputs[i];
}
Array<bke::AnonymousAttributeSet> attributes_to_propagate(
mapping.attribute_set_by_geometry_output.size());
for (const int i : attributes_to_propagate.index_range()) {
input_index++;
param_inputs[input_index] = &attributes_to_propagate[i];
}
for (const int i : graph_outputs.index_range()) {
const lf::InputSocket &socket = *graph_outputs[i];
const CPPType &type = socket.type();
void *buffer = allocator.allocate(type.size(), type.alignment());
param_outputs[i] = {type, buffer};
}
lf::Context lf_context;
lf_context.storage = graph_executor.init_storage(allocator);
lf_context.user_data = &user_data;
lf::BasicParams lf_params{graph_executor,
param_inputs,
param_outputs,
param_input_usages,
param_output_usages,
param_set_outputs};
graph_executor.execute(lf_params, lf_context);
graph_executor.destruct_storage(lf_context.storage);
for (GMutablePointer &ptr : inputs_to_destruct) {
ptr.destruct();
}
GeometrySet output_geometry = std::move(*param_outputs[0].get<GeometrySet>());
store_output_attributes(output_geometry, btree, properties, param_outputs);
for (GMutablePointer &ptr : param_outputs) {
ptr.destruct();
}
return output_geometry;
}
void update_input_properties_from_node_tree(const bNodeTree &tree,
const IDProperty *old_properties,
IDProperty &properties)
{
tree.ensure_topology_cache();
const Span<const bNodeSocket *> tree_inputs = tree.interface_inputs();
for (const int i : tree_inputs.index_range()) {
const bNodeSocket &socket = *tree_inputs[i];
IDProperty *new_prop = nodes::id_property_create_from_socket(socket).release();
if (new_prop == nullptr) {
/* Out of the set of supported input sockets, only
* geometry sockets aren't added to the modifier. */
BLI_assert(socket.type == SOCK_GEOMETRY);
continue;
}
new_prop->flag |= IDP_FLAG_OVERRIDABLE_LIBRARY;
if (socket.description[0] != '\0') {
IDPropertyUIData *ui_data = IDP_ui_data_ensure(new_prop);
ui_data->description = BLI_strdup(socket.description);
}
IDP_AddToGroup(&properties, new_prop);
if (old_properties != nullptr) {
const IDProperty *old_prop = IDP_GetPropertyFromGroup(old_properties, socket.identifier);
if (old_prop != nullptr) {
if (nodes::id_property_type_matches_socket(socket, *old_prop)) {
/* #IDP_CopyPropertyContent replaces the UI data as well, which we don't (we only
* want to replace the values). So release it temporarily and replace it after. */
IDPropertyUIData *ui_data = new_prop->ui_data;
new_prop->ui_data = nullptr;
IDP_CopyPropertyContent(new_prop, old_prop);
if (new_prop->ui_data != nullptr) {
IDP_ui_data_free(new_prop);
}
new_prop->ui_data = ui_data;
}
else if (old_prop->type == IDP_INT && new_prop->type == IDP_BOOLEAN) {
/* Support versioning from integer to boolean property values. The actual value is stored
* in the same variable for both types. */
new_prop->data.val = old_prop->data.val != 0;
}
}
}
if (nodes::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(&properties, use_attribute_prop);
IDProperty *attribute_prop = IDP_New(IDP_STRING, &idprop, attribute_name_id.c_str());
IDP_AddToGroup(&properties, attribute_prop);
if (old_properties == nullptr) {
if (socket.default_attribute_name && socket.default_attribute_name[0] != '\0') {
IDP_AssignString(attribute_prop, socket.default_attribute_name, MAX_NAME);
IDP_Int(use_attribute_prop) = 1;
}
}
else {
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);
}
}
}
}
}
void update_output_properties_from_node_tree(const bNodeTree &tree,
const IDProperty *old_properties,
IDProperty &properties)
{
tree.ensure_topology_cache();
const Span<const bNodeSocket *> tree_outputs = tree.interface_outputs();
for (const int i : tree_outputs.index_range()) {
const bNodeSocket &socket = *tree_outputs[i];
if (!nodes::socket_type_has_attribute_toggle(socket)) {
continue;
}
const std::string idprop_name = socket.identifier + attribute_name_suffix;
IDProperty *new_prop = IDP_NewString("", idprop_name.c_str(), MAX_NAME);
if (socket.description[0] != '\0') {
IDPropertyUIData *ui_data = IDP_ui_data_ensure(new_prop);
ui_data->description = BLI_strdup(socket.description);
}
IDP_AddToGroup(&properties, new_prop);
if (old_properties == nullptr) {
if (socket.default_attribute_name && socket.default_attribute_name[0] != '\0') {
IDP_AssignString(new_prop, socket.default_attribute_name, MAX_NAME);
}
}
else {
IDProperty *old_prop = IDP_GetPropertyFromGroup(old_properties, idprop_name.c_str());
if (old_prop != nullptr) {
/* #IDP_CopyPropertyContent replaces the UI data as well, which we don't (we only
* want to replace the values). So release it temporarily and replace it after. */
IDPropertyUIData *ui_data = new_prop->ui_data;
new_prop->ui_data = nullptr;
IDP_CopyPropertyContent(new_prop, old_prop);
if (new_prop->ui_data != nullptr) {
IDP_ui_data_free(new_prop);
}
new_prop->ui_data = ui_data;
}
}
}
}
} // namespace blender::nodes

View File

@@ -28,6 +28,7 @@
#include "BKE_compute_contexts.hh"
#include "BKE_geometry_set.hh"
#include "BKE_idprop.hh"
#include "BKE_type_conversions.hh"
#include "FN_field_cpp_type.hh"
@@ -172,10 +173,12 @@ class LazyFunctionForGeometryNode : public LazyFunction {
node_.typeinfo->geometry_node_execute(geo_params);
geo_eval_log::TimePoint end_time = geo_eval_log::Clock::now();
if (geo_eval_log::GeoModifierLog *modifier_log = user_data->modifier_data->eval_log) {
geo_eval_log::GeoTreeLogger &tree_logger = modifier_log->get_local_tree_logger(
*user_data->compute_context);
tree_logger.node_execution_times.append({node_.identifier, start_time, end_time});
if (user_data->log_socket_values && user_data->modifier_data) {
if (geo_eval_log::GeoModifierLog *modifier_log = user_data->modifier_data->eval_log) {
geo_eval_log::GeoTreeLogger &tree_logger = modifier_log->get_local_tree_logger(
*user_data->compute_context);
tree_logger.node_execution_times.append({node_.identifier, start_time, end_time});
}
}
}
@@ -2867,6 +2870,9 @@ void GeometryNodesLazyFunctionLogger::log_socket_value(
if (!user_data->log_socket_values) {
return;
}
if (!user_data->modifier_data) {
return;
}
if (user_data->modifier_data->eval_log == nullptr) {
return;
}
@@ -2932,6 +2938,9 @@ Vector<const lf::FunctionNode *> GeometryNodesLazyFunctionSideEffectProvider::
{
GeoNodesLFUserData *user_data = dynamic_cast<GeoNodesLFUserData *>(context.user_data);
BLI_assert(user_data != nullptr);
if (!user_data->modifier_data) {
return {};
}
const ComputeContextHash &context_hash = user_data->compute_context->hash();
const GeoNodesModifierData &modifier_data = *user_data->modifier_data;
return modifier_data.side_effect_nodes->lookup(context_hash);