Compare commits
19 Commits
asset-shel
...
node-group
Author | SHA1 | Date | |
---|---|---|---|
3ce788e015 | |||
8be481fbd8 | |||
a385a00821 | |||
677273eb41 | |||
abe86a6a5d | |||
9e01c4f69e | |||
7212f52457 | |||
51d48bdcdb | |||
a10ae5cf05 | |||
19051b863e | |||
1ad98ac65e | |||
96e8f8a002 | |||
4c19994c11 | |||
7c67f8c719 | |||
cf77874cdb | |||
7e748413f9 | |||
41c5490137 | |||
338db40df9 | |||
39dfa015a0 |
@@ -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)
|
||||
|
@@ -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"
|
||||
|
@@ -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
|
||||
|
9
source/blender/editors/curves/intern/curves_intern.hh
Normal file
9
source/blender/editors/curves/intern/curves_intern.hh
Normal 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
|
@@ -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)
|
||||
|
153
source/blender/editors/curves/intern/node_group_operator.cc
Normal file
153
source/blender/editors/curves/intern/node_group_operator.cc
Normal 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
|
@@ -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 {
|
||||
|
@@ -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,
|
||||
|
@@ -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");
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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
|
||||
|
74
source/blender/nodes/NOD_geometry_nodes_execute.hh
Normal file
74
source/blender/nodes/NOD_geometry_nodes_execute.hh
Normal 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
|
@@ -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.
|
||||
|
@@ -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);
|
||||
|
706
source/blender/nodes/intern/geometry_nodes_execute.cc
Normal file
706
source/blender/nodes/intern/geometry_nodes_execute.cc
Normal 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
|
@@ -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);
|
||||
|
Reference in New Issue
Block a user