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 All 14 Lines | |||||
| using blender::fn::GMutablePointer; | using blender::fn::GMutablePointer; | ||||
| using blender::fn::GPointer; | using blender::fn::GPointer; | ||||
| using blender::nodes::FieldInferencingInterface; | using blender::nodes::FieldInferencingInterface; | ||||
| using blender::nodes::GeoNodeExecParams; | using blender::nodes::GeoNodeExecParams; | ||||
| using blender::nodes::InputSocketFieldType; | using blender::nodes::InputSocketFieldType; | ||||
| using blender::threading::EnumerableThreadSpecific; | using blender::threading::EnumerableThreadSpecific; | ||||
| using namespace blender::fn::multi_function_types; | using namespace blender::fn::multi_function_types; | ||||
| using namespace blender::nodes::derived_node_tree_types; | using namespace blender::nodes::derived_node_tree_types; | ||||
| using blender::nodes::geometry_nodes_eval_log::GeometryAttributeInfo; | |||||
| // namespace geo_log = blender::nodes::geometry_nodes_eval_log; | |||||
| static void initData(ModifierData *md) | static void initData(ModifierData *md) | ||||
| { | { | ||||
| NodesModifierData *nmd = (NodesModifierData *)md; | NodesModifierData *nmd = (NodesModifierData *)md; | ||||
| BLI_assert(MEMCMP_STRUCT_AFTER_IS_ZERO(nmd, modifier)); | BLI_assert(MEMCMP_STRUCT_AFTER_IS_ZERO(nmd, modifier)); | ||||
| MEMCPY_STRUCT_AFTER(nmd, DNA_struct_default_get(NodesModifierData), modifier); | MEMCPY_STRUCT_AFTER(nmd, DNA_struct_default_get(NodesModifierData), modifier); | ||||
| ▲ Show 20 Lines • Show All 806 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); | ||||
| } | } | ||||
| struct AttributeSearchData { | |||||
JacquesLucke: It's a bit sad that there is so much code duplication with `node_geometry_attribute_search.cc`. | |||||
| const geo_log::ModifierLog &modifier_log; | |||||
JacquesLuckeUnsubmitted 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… | |||||
HooglyBooglyAuthorUnsubmitted 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… | |||||
| 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); | |||||
| const std::string text = domain_name + " " + MENU_SEP + item.name + UI_SEP_CHAR + data_type_name; | |||||
| return UI_search_item_add(items, text.c_str(), (void *)&item, ICON_NONE, UI_BUT_HAS_SEP_CHAR, 0); | |||||
| } | |||||
| 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(); | |||||
| static GeometryAttributeInfo dummy_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); | |||||
| const GeometryAttributeInfo &item = *static_cast<const GeometryAttributeInfo *>(item_v); | |||||
| IDProperty &name_property = data.name_property; | |||||
| BLI_assert(name_property.type == IDP_STRING); | |||||
| IDP_AssignString(&name_property, item.name.c_str(), 0); | |||||
| ED_undo_push(C, "Assign Attribute Name"); | |||||
| } | |||||
| static void add_attribute_search_button(uiLayout *layout, | |||||
| const NodesModifierData &nmd, | |||||
| PointerRNA *md_ptr, | |||||
| const StringRefNull rna_path_attribute_name, | |||||
| const bNodeSocket &socket, | |||||
| const bool is_output) | |||||
| { | |||||
| const geo_log::ModifierLog *log = static_cast<geo_log::ModifierLog *>(nmd.runtime_eval_log); | |||||
| if (log == nullptr) { | |||||
| uiItemR(layout, md_ptr, rna_path_attribute_name.c_str(), 0, "", ICON_NONE); | |||||
| return; | |||||
| } | |||||
| uiBlock *block = uiLayoutGetBlock(layout); | |||||
| 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, | |||||
| ""); | |||||
| const std::string use_attribute_prop_name = socket.identifier + attribute_name_suffix; | |||||
| IDProperty *property = IDP_GetPropertyFromGroup(nmd.settings.properties, | |||||
| use_attribute_prop_name.c_str()); | |||||
| BLI_assert(property != nullptr); | |||||
| if (property == nullptr) { | |||||
| return; | |||||
| } | |||||
| 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); | |||||
| } | |||||
| static void add_attribute_search_or_value_buttons(uiLayout *layout, | |||||
| const NodesModifierData &nmd, | |||||
| PointerRNA *md_ptr, | |||||
| const bNodeSocket &socket) | |||||
| { | |||||
| char socket_id_esc[sizeof(socket.identifier) * 2]; | |||||
| BLI_str_escape(socket_id_esc, socket.identifier, sizeof(socket_id_esc)); | |||||
| const std::string rna_path = "[\"" + std::string(socket_id_esc) + "\"]"; | |||||
| const std::string rna_path_use_attribute = "[\"" + std::string(socket_id_esc) + | |||||
| use_attribute_suffix + "\"]"; | |||||
| const std::string rna_path_attribute_name = "[\"" + std::string(socket_id_esc) + | |||||
| attribute_name_suffix + "\"]"; | |||||
| uiLayout *split = uiLayoutSplit(layout, 0.4f, false); | |||||
| uiLayout *name_row = uiLayoutRow(split, false); | |||||
| uiLayoutSetAlignment(name_row, UI_LAYOUT_ALIGN_RIGHT); | |||||
| uiItemL(name_row, socket.name, ICON_NONE); | |||||
| uiLayout *row = uiLayoutRow(split, true); | |||||
| 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()); | |||||
| const int use_attribute = RNA_int_get(md_ptr, rna_path_use_attribute.c_str()) != 0; | |||||
| if (use_attribute) { | |||||
| add_attribute_search_button(row, nmd, md_ptr, rna_path_attribute_name, socket, false); | |||||
| uiItemL(row, "", ICON_BLANK1); | |||||
| } | |||||
| else { | |||||
| uiItemR(row, md_ptr, rna_path.c_str(), 0, "", ICON_NONE); | |||||
| uiItemDecoratorR(row, md_ptr, rna_path.c_str(), 0); | |||||
| } | |||||
| } | |||||
| /* 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 All 38 Lines | case SOCK_MATERIAL: { | ||||
| break; | break; | ||||
| } | } | ||||
| case SOCK_TEXTURE: { | case SOCK_TEXTURE: { | ||||
| uiItemPointerR(layout, md_ptr, rna_path, bmain_ptr, "textures", socket.name, ICON_TEXTURE); | uiItemPointerR(layout, md_ptr, rna_path, bmain_ptr, "textures", socket.name, ICON_TEXTURE); | ||||
| break; | break; | ||||
| } | } | ||||
| default: { | default: { | ||||
| if (input_has_attribute_toggle(*nmd->node_group, socket_index)) { | if (input_has_attribute_toggle(*nmd->node_group, socket_index)) { | ||||
| const std::string rna_path_use_attribute = "[\"" + std::string(socket_id_esc) + | add_attribute_search_or_value_buttons(layout, *nmd, md_ptr, socket); | ||||
| use_attribute_suffix + "\"]"; | |||||
| const std::string rna_path_attribute_name = "[\"" + std::string(socket_id_esc) + | |||||
| attribute_name_suffix + "\"]"; | |||||
| uiLayout *row = uiLayoutRow(layout, true); | |||||
| const int use_attribute = RNA_int_get(md_ptr, rna_path_use_attribute.c_str()) != 0; | |||||
| if (use_attribute) { | |||||
| uiItemR(row, md_ptr, rna_path_attribute_name.c_str(), 0, socket.name, ICON_NONE); | |||||
| } | } | ||||
| else { | else { | ||||
| uiLayout *row = uiLayoutRow(layout, false); | |||||
| uiItemR(row, md_ptr, rna_path, 0, socket.name, ICON_NONE); | uiItemR(row, md_ptr, rna_path, 0, socket.name, ICON_NONE); | ||||
| } | uiItemDecoratorR(row, md_ptr, rna_path, 0); | ||||
| PointerRNA props; | |||||
| uiItemFullO(row, | |||||
| "object.geometry_nodes_input_attribute_toggle", | |||||
| "", | |||||
| ICON_SPREADSHEET, | |||||
| nullptr, | |||||
| WM_OP_INVOKE_DEFAULT, | |||||
| 0, | |||||
| &props); | |||||
| RNA_string_set(&props, "modifier_name", nmd->modifier.name); | |||||
| RNA_string_set(&props, "prop_path", rna_path_use_attribute.c_str()); | |||||
| } | |||||
| else { | |||||
| uiItemR(layout, md_ptr, rna_path, 0, socket.name, ICON_NONE); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| static void draw_property_for_output_socket(uiLayout *layout, | static void draw_property_for_output_socket(uiLayout *layout, | ||||
| const NodesModifierData &nmd, | |||||
| PointerRNA *md_ptr, | PointerRNA *md_ptr, | ||||
| const bNodeSocket &socket) | const bNodeSocket &socket) | ||||
| { | { | ||||
| char socket_id_esc[sizeof(socket.identifier) * 2]; | char socket_id_esc[sizeof(socket.identifier) * 2]; | ||||
| BLI_str_escape(socket_id_esc, socket.identifier, sizeof(socket_id_esc)); | BLI_str_escape(socket_id_esc, socket.identifier, sizeof(socket_id_esc)); | ||||
| const std::string rna_path_attribute_name = "[\"" + StringRef(socket_id_esc) + | const std::string rna_path_attribute_name = "[\"" + StringRef(socket_id_esc) + | ||||
| attribute_name_suffix + "\"]"; | attribute_name_suffix + "\"]"; | ||||
| uiItemR(layout, md_ptr, rna_path_attribute_name.c_str(), 0, socket.name, ICON_NONE); | uiLayout *split = uiLayoutSplit(layout, 0.4f, false); | ||||
| uiLayout *name_row = uiLayoutRow(split, false); | |||||
| uiLayoutSetAlignment(name_row, UI_LAYOUT_ALIGN_RIGHT); | |||||
| uiItemL(name_row, socket.name, ICON_NONE); | |||||
| uiLayout *row = uiLayoutRow(split, true); | |||||
| add_attribute_search_button(row, nmd, md_ptr, rna_path_attribute_name, socket, true); | |||||
| } | } | ||||
| static void panel_draw(const bContext *C, Panel *panel) | static void panel_draw(const bContext *C, Panel *panel) | ||||
| { | { | ||||
| uiLayout *layout = panel->layout; | uiLayout *layout = panel->layout; | ||||
| Main *bmain = CTX_data_main(C); | Main *bmain = CTX_data_main(C); | ||||
| PointerRNA *ptr = modifier_panel_get_property_pointers(panel, nullptr); | PointerRNA *ptr = modifier_panel_get_property_pointers(panel, nullptr); | ||||
| NodesModifierData *nmd = static_cast<NodesModifierData *>(ptr->data); | NodesModifierData *nmd = static_cast<NodesModifierData *>(ptr->data); | ||||
| uiLayoutSetPropSep(layout, true); | uiLayoutSetPropSep(layout, true); | ||||
| uiLayoutSetPropDecorate(layout, true); | /* Decorators are added manually for supported properties because the | ||||
| * attribute/value toggle requires a manually built layout anyway. */ | |||||
| uiLayoutSetPropDecorate(layout, false); | |||||
| uiTemplateID(layout, | uiTemplateID(layout, | ||||
| C, | C, | ||||
| ptr, | ptr, | ||||
| "node_group", | "node_group", | ||||
| "node.new_geometry_node_group_assign", | "node.new_geometry_node_group_assign", | ||||
| nullptr, | nullptr, | ||||
| nullptr, | nullptr, | ||||
| ▲ Show 20 Lines • Show All 46 Lines • ▼ Show 20 Lines | static void output_attribute_panel_draw(const bContext *UNUSED(C), Panel *panel) | ||||
| NodesModifierData *nmd = static_cast<NodesModifierData *>(ptr->data); | NodesModifierData *nmd = static_cast<NodesModifierData *>(ptr->data); | ||||
| uiLayoutSetPropSep(layout, true); | uiLayoutSetPropSep(layout, true); | ||||
| uiLayoutSetPropDecorate(layout, true); | uiLayoutSetPropDecorate(layout, true); | ||||
| if (nmd->node_group != nullptr && nmd->settings.properties != nullptr) { | if (nmd->node_group != nullptr && nmd->settings.properties != nullptr) { | ||||
| LISTBASE_FOREACH (bNodeSocket *, socket, &nmd->node_group->outputs) { | LISTBASE_FOREACH (bNodeSocket *, socket, &nmd->node_group->outputs) { | ||||
| if (socket_type_has_attribute_toggle(*socket)) { | if (socket_type_has_attribute_toggle(*socket)) { | ||||
| draw_property_for_output_socket(layout, ptr, *socket); | draw_property_for_output_socket(layout, *nmd, ptr, *socket); | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| static void panelRegister(ARegionType *region_type) | static void panelRegister(ARegionType *region_type) | ||||
| { | { | ||||
| PanelType *panel_type = modifier_panel_register(region_type, eModifierType_Nodes, panel_draw); | PanelType *panel_type = modifier_panel_register(region_type, eModifierType_Nodes, panel_draw); | ||||
| ▲ Show 20 Lines • Show All 98 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?