Changeset View
Changeset View
Standalone View
Standalone View
source/blender/modifiers/intern/MOD_nodes.cc
| Show All 26 Lines | |||||
| #include "MEM_guardedalloc.h" | #include "MEM_guardedalloc.h" | ||||
| #include "BLI_float3.hh" | #include "BLI_float3.hh" | ||||
| #include "BLI_listbase.h" | #include "BLI_listbase.h" | ||||
| #include "BLI_multi_value_map.hh" | #include "BLI_multi_value_map.hh" | ||||
| #include "BLI_set.hh" | #include "BLI_set.hh" | ||||
| #include "BLI_string.h" | #include "BLI_string.h" | ||||
| #include "BLI_string_search.h" | |||||
| #include "BLI_utildefines.h" | #include "BLI_utildefines.h" | ||||
| #include "DNA_collection_types.h" | #include "DNA_collection_types.h" | ||||
| #include "DNA_defaults.h" | #include "DNA_defaults.h" | ||||
| #include "DNA_material_types.h" | #include "DNA_material_types.h" | ||||
| #include "DNA_mesh_types.h" | #include "DNA_mesh_types.h" | ||||
| #include "DNA_meshdata_types.h" | #include "DNA_meshdata_types.h" | ||||
| #include "DNA_modifier_types.h" | #include "DNA_modifier_types.h" | ||||
| Show All 36 Lines | |||||
| #include "DEG_depsgraph_query.h" | #include "DEG_depsgraph_query.h" | ||||
| #include "MOD_modifiertypes.h" | #include "MOD_modifiertypes.h" | ||||
| #include "MOD_nodes.h" | #include "MOD_nodes.h" | ||||
| #include "MOD_nodes_evaluator.hh" | #include "MOD_nodes_evaluator.hh" | ||||
| #include "MOD_ui_common.h" | #include "MOD_ui_common.h" | ||||
| #include "ED_spreadsheet.h" | #include "ED_spreadsheet.h" | ||||
| #include "ED_undo.h" | |||||
| #include "NOD_derived_node_tree.hh" | #include "NOD_derived_node_tree.hh" | ||||
| #include "NOD_geometry.h" | #include "NOD_geometry.h" | ||||
| #include "NOD_geometry_nodes_eval_log.hh" | #include "NOD_geometry_nodes_eval_log.hh" | ||||
| #include "NOD_node_declaration.hh" | #include "NOD_node_declaration.hh" | ||||
| #include "FN_field.hh" | #include "FN_field.hh" | ||||
| #include "FN_multi_function.hh" | #include "FN_multi_function.hh" | ||||
| ▲ Show 20 Lines • Show All 836 Lines • ▼ Show 20 Lines | for (const NodeRef *group_input_node : group_input_nodes) { | ||||
| for (const OutputSocketRef *socket : remaining_input_sockets) { | for (const OutputSocketRef *socket : remaining_input_sockets) { | ||||
| const CPPType &cpp_type = *socket->typeinfo()->get_geometry_nodes_cpp_type(); | const CPPType &cpp_type = *socket->typeinfo()->get_geometry_nodes_cpp_type(); | ||||
| void *value_in = allocator.allocate(cpp_type.size(), cpp_type.alignment()); | void *value_in = allocator.allocate(cpp_type.size(), cpp_type.alignment()); | ||||
| initialize_group_input(*nmd, *socket, value_in); | initialize_group_input(*nmd, *socket, value_in); | ||||
| group_inputs.add_new({root_context, socket}, {cpp_type, value_in}); | group_inputs.add_new({root_context, socket}, {cpp_type, value_in}); | ||||
| } | } | ||||
| } | } | ||||
| /* Don't keep a reference to the input geometry components to avoid copies during evaluation. */ | |||||
| input_geometry_set.clear(); | |||||
| Vector<DInputSocket> group_outputs; | Vector<DInputSocket> group_outputs; | ||||
| for (const InputSocketRef *socket_ref : output_node.inputs().drop_back(1)) { | for (const InputSocketRef *socket_ref : output_node.inputs().drop_back(1)) { | ||||
| group_outputs.append({root_context, socket_ref}); | group_outputs.append({root_context, socket_ref}); | ||||
| } | } | ||||
| std::optional<geo_log::GeoLogger> geo_logger; | std::optional<geo_log::GeoLogger> geo_logger; | ||||
| blender::modifiers::geometry_nodes::GeometryNodesEvaluationParams eval_params; | blender::modifiers::geometry_nodes::GeometryNodesEvaluationParams eval_params; | ||||
| if (logging_enabled(ctx)) { | if (logging_enabled(ctx)) { | ||||
| Set<DSocket> preview_sockets; | Set<DSocket> preview_sockets; | ||||
| find_sockets_to_preview(nmd, ctx, tree, preview_sockets); | find_sockets_to_preview(nmd, ctx, tree, preview_sockets); | ||||
| eval_params.force_compute_sockets.extend(preview_sockets.begin(), preview_sockets.end()); | eval_params.force_compute_sockets.extend(preview_sockets.begin(), preview_sockets.end()); | ||||
| geo_logger.emplace(std::move(preview_sockets)); | geo_logger.emplace(std::move(preview_sockets)); | ||||
| geo_logger->log_input_geometry(input_geometry_set); | |||||
| } | } | ||||
| /* Don't keep a reference to the input geometry components to avoid copies during evaluation. */ | |||||
| input_geometry_set.clear(); | |||||
| eval_params.input_values = group_inputs; | eval_params.input_values = group_inputs; | ||||
| eval_params.output_sockets = group_outputs; | eval_params.output_sockets = group_outputs; | ||||
| eval_params.mf_by_node = &mf_by_node; | eval_params.mf_by_node = &mf_by_node; | ||||
| eval_params.modifier_ = nmd; | eval_params.modifier_ = nmd; | ||||
| eval_params.depsgraph = ctx->depsgraph; | eval_params.depsgraph = ctx->depsgraph; | ||||
| eval_params.self_object = ctx->object; | eval_params.self_object = ctx->object; | ||||
| eval_params.geo_logger = geo_logger.has_value() ? &*geo_logger : nullptr; | eval_params.geo_logger = geo_logger.has_value() ? &*geo_logger : nullptr; | ||||
| blender::modifiers::geometry_nodes::evaluate_geometry_nodes(eval_params); | blender::modifiers::geometry_nodes::evaluate_geometry_nodes(eval_params); | ||||
| GeometrySet output_geometry_set = eval_params.r_output_values[0].relocate_out<GeometrySet>(); | |||||
| if (geo_logger.has_value()) { | if (geo_logger.has_value()) { | ||||
| geo_logger->log_output_geometry(output_geometry_set); | |||||
| NodesModifierData *nmd_orig = (NodesModifierData *)BKE_modifier_get_original(&nmd->modifier); | NodesModifierData *nmd_orig = (NodesModifierData *)BKE_modifier_get_original(&nmd->modifier); | ||||
| clear_runtime_data(nmd_orig); | clear_runtime_data(nmd_orig); | ||||
| nmd_orig->runtime_eval_log = new geo_log::ModifierLog(*geo_logger); | nmd_orig->runtime_eval_log = new geo_log::ModifierLog(*geo_logger); | ||||
| } | } | ||||
| GeometrySet output_geometry_set = eval_params.r_output_values[0].relocate_out<GeometrySet>(); | |||||
| for (const InputSocketRef *socket : output_node.inputs().drop_front(1).drop_back(1)) { | for (const InputSocketRef *socket : output_node.inputs().drop_front(1).drop_back(1)) { | ||||
| GMutablePointer socket_value = eval_params.r_output_values[socket->index()]; | GMutablePointer socket_value = eval_params.r_output_values[socket->index()]; | ||||
| store_output_value_in_geometry(output_geometry_set, nmd, *socket, socket_value); | store_output_value_in_geometry(output_geometry_set, nmd, *socket, socket_value); | ||||
| socket_value.destruct(); | socket_value.destruct(); | ||||
| } | } | ||||
| return output_geometry_set; | return output_geometry_set; | ||||
| } | } | ||||
| ▲ Show 20 Lines • Show All 105 Lines • ▼ Show 20 Lines | |||||
| static void modifyGeometrySet(ModifierData *md, | static void modifyGeometrySet(ModifierData *md, | ||||
| const ModifierEvalContext *ctx, | const ModifierEvalContext *ctx, | ||||
| GeometrySet *geometry_set) | GeometrySet *geometry_set) | ||||
| { | { | ||||
| modifyGeometry(md, ctx, *geometry_set); | modifyGeometry(md, ctx, *geometry_set); | ||||
| } | } | ||||
| using blender::IndexRange; | |||||
JacquesLucke: It's a bit sad that there is so much code duplication with `node_geometry_attribute_search.cc`. | |||||
| using blender::Map; | |||||
Not Done Inline ActionsIt's fine here, but just wanted to mention that I'm usually careful with using references in these trivial structs. That is because with references the implicit copy/move assignment operators are not generated. JacquesLucke: It's fine here, but just wanted to mention that I'm usually careful with using references in… | |||||
Done Inline ActionsIf I can get away with it, I like using references a little more, it just looks nicer. Thanks for the warning though. HooglyBoogly: If I can get away with it, I like using references a little more, it just looks nicer. Thanks… | |||||
| using blender::Set; | |||||
| using blender::StringRef; | |||||
| namespace geo_log = blender::nodes::geometry_nodes_eval_log; | |||||
| using geo_log::GeometryAttributeInfo; | |||||
| struct AttributeSearchData { | |||||
| const geo_log::ModifierLog &modifier_log; | |||||
| IDProperty &name_property; | |||||
| bool is_output; | |||||
| }; | |||||
| /* This class must not have a destructor, since it is used by buttons and freed with #MEM_freeN. */ | |||||
| BLI_STATIC_ASSERT(std::is_trivially_destructible_v<AttributeSearchData>, ""); | |||||
| static StringRef attribute_data_type_string(const CustomDataType type) | |||||
| { | |||||
| const char *name = nullptr; | |||||
| RNA_enum_name_from_value(rna_enum_attribute_type_items, type, &name); | |||||
| return StringRef(IFACE_(name)); | |||||
| } | |||||
| static StringRef attribute_domain_string(const AttributeDomain domain) | |||||
| { | |||||
| const char *name = nullptr; | |||||
| RNA_enum_name_from_value(rna_enum_attribute_domain_items, domain, &name); | |||||
| return StringRef(IFACE_(name)); | |||||
| } | |||||
| /* Unicode arrow. */ | |||||
| #define MENU_SEP "\xe2\x96\xb6" | |||||
| static bool attribute_search_item_add(uiSearchItems *items, const GeometryAttributeInfo &item) | |||||
| { | |||||
| const StringRef data_type_name = attribute_data_type_string(item.data_type); | |||||
| const StringRef domain_name = attribute_domain_string(item.domain); | |||||
| std::string search_item_text = domain_name + " " + MENU_SEP + item.name + UI_SEP_CHAR + | |||||
| data_type_name; | |||||
| return UI_search_item_add( | |||||
| items, search_item_text.c_str(), (void *)&item, ICON_NONE, UI_BUT_HAS_SEP_CHAR, 0); | |||||
| } | |||||
| static GeometryAttributeInfo &get_dummy_item_info() | |||||
| { | |||||
| static GeometryAttributeInfo info; | |||||
| return info; | |||||
| } | |||||
| static void attribute_search_update_fn(const bContext *UNUSED(C), | |||||
| void *arg, | |||||
| const char *str, | |||||
| uiSearchItems *items, | |||||
| const bool is_first) | |||||
| { | |||||
| AttributeSearchData *data = static_cast<AttributeSearchData *>(arg); | |||||
| const geo_log::GeometryValueLog &geometry_log = data->is_output ? | |||||
| data->modifier_log.output_geometry_log() : | |||||
| data->modifier_log.input_geometry_log(); | |||||
| Span<GeometryAttributeInfo> infos = geometry_log.attributes(); | |||||
| GeometryAttributeInfo &dummy_info = get_dummy_item_info(); | |||||
| /* Any string may be valid, so add the current search string along with the hints. */ | |||||
| if (str[0] != '\0') { | |||||
| bool contained = false; | |||||
| for (const GeometryAttributeInfo &attribute_info : infos) { | |||||
| if (attribute_info.name == str) { | |||||
| contained = true; | |||||
| break; | |||||
| } | |||||
| } | |||||
| if (!contained && data->is_output) { | |||||
| dummy_info.name = str; | |||||
| UI_search_item_add(items, str, &dummy_info, ICON_ADD, 0, 0); | |||||
| } | |||||
| } | |||||
| if (str[0] == '\0' && !is_first) { | |||||
| /* Allow clearing the text field when the string is empty, but not on the first pass, | |||||
| * or opening an attribute field for the first time would show this search item. */ | |||||
| dummy_info.name = str; | |||||
| UI_search_item_add(items, str, &dummy_info, ICON_X, 0, 0); | |||||
| } | |||||
| /* Don't filter when the menu is first opened, but still run the search | |||||
| * so the items are in the same order they will appear in while searching. */ | |||||
| const char *string = is_first ? "" : str; | |||||
| StringSearch *search = BLI_string_search_new(); | |||||
| for (const GeometryAttributeInfo &item : infos) { | |||||
| BLI_string_search_add(search, item.name.c_str(), (void *)&item); | |||||
| } | |||||
| GeometryAttributeInfo **filtered_items; | |||||
| const int filtered_amount = BLI_string_search_query(search, string, (void ***)&filtered_items); | |||||
| for (const int i : IndexRange(filtered_amount)) { | |||||
| const GeometryAttributeInfo *item = filtered_items[i]; | |||||
| if (!attribute_search_item_add(items, *item)) { | |||||
| break; | |||||
| } | |||||
| } | |||||
| MEM_freeN(filtered_items); | |||||
| BLI_string_search_free(search); | |||||
| } | |||||
| static void attribute_search_exec_fn(bContext *C, void *data_v, void *item_v) | |||||
| { | |||||
| if (item_v == nullptr) { | |||||
| return; | |||||
| } | |||||
| AttributeSearchData *data = static_cast<AttributeSearchData *>(data_v); | |||||
| GeometryAttributeInfo *item = (GeometryAttributeInfo *)item_v; | |||||
| IDProperty &name_property = data->name_property; | |||||
| BLI_assert(name_property.type == IDP_STRING); | |||||
| name_property.data.pointer = BLI_strdup(item->name.c_str()); | |||||
| ED_undo_push(C, "Assign Attribute Name"); | |||||
| } | |||||
| static void add_attribute_toggle_operator_button(uiLayout *layout, | |||||
| const NodesModifierData &nmd, | |||||
| StringRefNull rna_path_use_attribute) | |||||
| { | |||||
| PointerRNA props; | |||||
| uiItemFullO(layout, | |||||
| "object.geometry_nodes_input_attribute_toggle", | |||||
| "", | |||||
| ICON_SPREADSHEET, | |||||
| nullptr, | |||||
| WM_OP_INVOKE_DEFAULT, | |||||
| 0, | |||||
| &props); | |||||
| RNA_string_set(&props, "modifier_name", nmd.modifier.name); | |||||
| RNA_string_set(&props, "prop_path", rna_path_use_attribute.c_str()); | |||||
| } | |||||
| static void add_attribute_search_button(uiLayout *layout, | |||||
| const NodesModifierData &nmd, | |||||
| const std::string &rna_path_use_attribute, | |||||
| const std::string &rna_path_attribute_name, | |||||
| PointerRNA *md_ptr, | |||||
| const bNodeSocket &socket, | |||||
| IDProperty &property, | |||||
| const bool is_output) | |||||
| { | |||||
| if (nmd.runtime_eval_log == nullptr) { | |||||
| uiItemR(layout, md_ptr, rna_path_attribute_name.c_str(), 0, socket.name, ICON_NONE); | |||||
| return; | |||||
| } | |||||
| const geo_log::ModifierLog &log = *static_cast<geo_log::ModifierLog *>(nmd.runtime_eval_log); | |||||
| uiLayout *col = uiItemL_respect_property_split(layout, socket.name, ICON_NONE); | |||||
| uiLayout *row = uiLayoutRow(col, true); | |||||
| add_attribute_toggle_operator_button(row, nmd, rna_path_use_attribute); | |||||
| uiBlock *block = uiLayoutGetBlock(row); | |||||
| uiBut *but = uiDefIconTextButR(block, | |||||
| UI_BTYPE_SEARCH_MENU, | |||||
| 0, | |||||
| ICON_NONE, | |||||
| "", | |||||
| 0, | |||||
| 0, | |||||
| 10 * UI_UNIT_X, /* Dummy value, replaced by layout system. */ | |||||
| UI_UNIT_Y, | |||||
| md_ptr, | |||||
| rna_path_attribute_name.c_str(), | |||||
| 0, | |||||
| 0.0f, | |||||
| 0.0f, | |||||
| 0.0f, | |||||
| 0.0f, | |||||
| ""); | |||||
| AttributeSearchData *data = OBJECT_GUARDED_NEW(AttributeSearchData, {log, property, is_output}); | |||||
| UI_but_func_search_set_results_are_suggestions(but, true); | |||||
| UI_but_func_search_set_sep_string(but, MENU_SEP); | |||||
| UI_but_func_search_set(but, | |||||
| nullptr, | |||||
| attribute_search_update_fn, | |||||
| static_cast<void *>(data), | |||||
| true, | |||||
| nullptr, | |||||
| attribute_search_exec_fn, | |||||
| nullptr); | |||||
| uiItemL(row, "", ICON_BLANK1); | |||||
| } | |||||
| /* Drawing the properties manually with #uiItemR instead of #uiDefAutoButsRNA allows using | /* Drawing the properties manually with #uiItemR instead of #uiDefAutoButsRNA allows using | ||||
| * the node socket identifier for the property names, since they are unique, but also having | * the node socket identifier for the property names, since they are unique, but also having | ||||
| * the correct label displayed in the UI. */ | * the correct label displayed in the UI. */ | ||||
| static void draw_property_for_socket(uiLayout *layout, | static void draw_property_for_socket(uiLayout *layout, | ||||
| NodesModifierData *nmd, | NodesModifierData *nmd, | ||||
| PointerRNA *bmain_ptr, | PointerRNA *bmain_ptr, | ||||
| PointerRNA *md_ptr, | PointerRNA *md_ptr, | ||||
| const bNodeSocket &socket, | const bNodeSocket &socket, | ||||
| ▲ Show 20 Lines • Show All 46 Lines • ▼ Show 20 Lines | default: { | ||||
| const std::string rna_path_use_attribute = "[\"" + std::string(socket_id_esc) + | const std::string rna_path_use_attribute = "[\"" + std::string(socket_id_esc) + | ||||
| use_attribute_suffix + "\"]"; | use_attribute_suffix + "\"]"; | ||||
| const std::string rna_path_attribute_name = "[\"" + std::string(socket_id_esc) + | const std::string rna_path_attribute_name = "[\"" + std::string(socket_id_esc) + | ||||
| attribute_name_suffix + "\"]"; | attribute_name_suffix + "\"]"; | ||||
| uiLayout *row = uiLayoutRow(layout, true); | uiLayout *row = uiLayoutRow(layout, true); | ||||
| const int use_attribute = RNA_int_get(md_ptr, rna_path_use_attribute.c_str()) != 0; | const int use_attribute = RNA_int_get(md_ptr, rna_path_use_attribute.c_str()) != 0; | ||||
| if (use_attribute) { | if (use_attribute) { | ||||
| uiItemR(row, md_ptr, rna_path_attribute_name.c_str(), 0, socket.name, ICON_NONE); | IDProperty *attribute_name_prop = IDP_GetPropertyFromGroup(nmd->settings.properties, | ||||
| socket.identifier); | |||||
| BLI_assert(attribute_name_prop != nullptr); | |||||
| add_attribute_search_button(layout, | |||||
| *nmd, | |||||
| rna_path_use_attribute, | |||||
| rna_path_attribute_name, | |||||
| md_ptr, | |||||
| socket, | |||||
| *attribute_name_prop, | |||||
| false); | |||||
| } | } | ||||
| else { | else { | ||||
| add_attribute_toggle_operator_button(row, *nmd, rna_path_use_attribute); | |||||
| uiItemR(row, md_ptr, rna_path, 0, socket.name, ICON_NONE); | uiItemR(row, md_ptr, rna_path, 0, socket.name, ICON_NONE); | ||||
| } | } | ||||
| PointerRNA props; | |||||
| uiItemFullO(row, | |||||
| "object.geometry_nodes_input_attribute_toggle", | |||||
| "", | |||||
| ICON_SPREADSHEET, | |||||
| nullptr, | |||||
| WM_OP_INVOKE_DEFAULT, | |||||
| 0, | |||||
| &props); | |||||
| RNA_string_set(&props, "modifier_name", nmd->modifier.name); | |||||
| RNA_string_set(&props, "prop_path", rna_path_use_attribute.c_str()); | |||||
| } | } | ||||
| else { | else { | ||||
| uiItemR(layout, md_ptr, rna_path, 0, socket.name, ICON_NONE); | uiItemR(layout, md_ptr, rna_path, 0, socket.name, ICON_NONE); | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| ▲ Show 20 Lines • Show All 191 Lines • Show Last 20 Lines | |||||
It's a bit sad that there is so much code duplication with node_geometry_attribute_search.cc. Can you think about ways to reduce the duplication?