From 3fcd9c94255f28c3a9d704b2dc1985682d07c419 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Wed, 15 Nov 2023 17:01:18 +0100 Subject: [PATCH] Geometry Nodes: Support node tools in object mode Extend node tools to display tool assets in object mode as well as edit and sculpt modes. For consistency with existing Blender design, selection cannot be set and is just "true" in object mode because it can't be visualized. The visibility of tools can be customized per object type in object mode as well. See #101778 Pull Request: https://projects.blender.org/blender/blender/pulls/114819 --- .../startup/bl_operators/geometry_nodes.py | 6 +- scripts/startup/bl_ui/space_node.py | 1 + scripts/startup/bl_ui/space_view3d.py | 12 + .../editors/geometry/node_group_operator.cc | 235 +++++++++++++----- source/blender/makesdna/DNA_node_types.h | 3 +- .../blender/makesrna/intern/rna_nodetree.cc | 17 ++ .../nodes/NOD_geometry_nodes_lazy_function.hh | 1 + .../geometry/nodes/node_geo_tool_selection.cc | 7 +- .../nodes/node_geo_tool_set_selection.cc | 5 + 9 files changed, 220 insertions(+), 67 deletions(-) diff --git a/scripts/startup/bl_operators/geometry_nodes.py b/scripts/startup/bl_operators/geometry_nodes.py index 088bf09a2fb..183a4ef2d8e 100644 --- a/scripts/startup/bl_operators/geometry_nodes.py +++ b/scripts/startup/bl_operators/geometry_nodes.py @@ -55,11 +55,13 @@ def geometry_node_group_empty_tool_new(context): else: group.is_type_mesh = True - mode = context.object.mode if context.object else 'EDIT' + mode = context.object.mode if context.object else 'OBJECT' if mode in {'SCULPT', 'SCULPT_CURVES'}: group.is_mode_sculpt = True - else: + elif mode == 'EDIT': group.is_mode_edit = True + else: + group.is_mode_object = True return group diff --git a/scripts/startup/bl_ui/space_node.py b/scripts/startup/bl_ui/space_node.py index 35b75057acd..0063eab4725 100644 --- a/scripts/startup/bl_ui/space_node.py +++ b/scripts/startup/bl_ui/space_node.py @@ -472,6 +472,7 @@ class NODE_PT_geometry_node_tool_mode(Panel): group = snode.node_tree modes = ( + ("is_mode_object", "Object Mode", 'OBJECT_DATAMODE'), ("is_mode_edit", "Edit Mode", 'EDITMODE_HLT'), ("is_mode_sculpt", "Sculpt Mode", 'SCULPTMODE_HLT'), ) diff --git a/scripts/startup/bl_ui/space_view3d.py b/scripts/startup/bl_ui/space_view3d.py index 3c822a70316..8af5a1067cf 100644 --- a/scripts/startup/bl_ui/space_view3d.py +++ b/scripts/startup/bl_ui/space_view3d.py @@ -1169,9 +1169,12 @@ class VIEW3D_MT_editor_menus(Menu): layout.menu("VIEW3D_MT_select_sculpt_curves") layout.menu("VIEW3D_MT_sculpt_curves") layout.template_node_operator_asset_root_items() + else: + layout.template_node_operator_asset_root_items() else: layout.menu("VIEW3D_MT_object") + layout.template_node_operator_asset_root_items() # ********** Menu ********** @@ -2743,6 +2746,8 @@ class VIEW3D_MT_object(Menu): layout.operator("object.delete", text="Delete").use_global = False layout.operator("object.delete", text="Delete Global").use_global = True + layout.template_node_operator_asset_menu_items(catalog_path="Object") + class VIEW3D_MT_object_animation(Menu): bl_label = "Animation" @@ -3021,6 +3026,8 @@ class VIEW3D_MT_object_context_menu(Menu): layout.operator_context = 'EXEC_REGION_WIN' layout.operator("object.delete", text="Delete").use_global = False + layout.template_node_operator_asset_menu_items(catalog_path="Object") + class VIEW3D_MT_object_shading(Menu): # XXX, this menu is a place to store shading operator in object mode @@ -3098,6 +3105,8 @@ class VIEW3D_MT_object_apply(Menu): text="Parent Inverse", text_ctxt=i18n_contexts.default) + layout.template_node_operator_asset_menu_items(catalog_path="Object/Apply") + class VIEW3D_MT_object_parent(Menu): bl_label = "Parent" @@ -3180,6 +3189,7 @@ class VIEW3D_MT_object_quick_effects(Menu): layout.operator("object.quick_explode") layout.operator("object.quick_smoke") layout.operator("object.quick_liquid") + layout.template_node_operator_asset_menu_items(catalog_path="Object/Quick Effects") class VIEW3D_MT_object_showhide(Menu): @@ -3271,6 +3281,8 @@ class VIEW3D_MT_object_convert(Menu): if ob and ob.type == 'CURVES': layout.operator("curves.convert_to_particle_system", text="Particle System") + + layout.template_node_operator_asset_menu_items(catalog_path="Object/Convert") class VIEW3D_MT_make_links(Menu): diff --git a/source/blender/editors/geometry/node_group_operator.cc b/source/blender/editors/geometry/node_group_operator.cc index 79b2b85aa32..cbaa4014ddb 100644 --- a/source/blender/editors/geometry/node_group_operator.cc +++ b/source/blender/editors/geometry/node_group_operator.cc @@ -287,6 +287,39 @@ static IDProperty *replace_inputs_evaluated_data_blocks(const IDProperty &op_pro return properties; } +static bool object_has_editable_data(const Main &bmain, const Object &object) +{ + if (!ELEM(object.type, OB_CURVES, OB_POINTCLOUD, OB_MESH)) { + return false; + } + if (!BKE_id_is_editable(&bmain, static_cast(object.data))) { + return false; + } + return true; +} + +static Vector gather_supported_objects(const bContext &C, + const Main &bmain, + const eObjectMode mode) +{ + Vector objects; + Set unique_object_data; + CTX_DATA_BEGIN (&C, Object *, object, selected_objects) { + if (object->mode != mode) { + continue; + } + if (!unique_object_data.add(static_cast(object->data))) { + continue; + } + if (!object_has_editable_data(bmain, *object)) { + continue; + } + objects.append(object); + } + CTX_DATA_END; + return objects; +} + static int run_node_group_exec(bContext *C, wmOperator *op) { Main *bmain = CTX_data_main(C); @@ -296,9 +329,6 @@ static int run_node_group_exec(bContext *C, wmOperator *op) if (!active_object) { return OPERATOR_CANCELLED; } - if (active_object->mode == OB_MODE_OBJECT) { - return OPERATOR_CANCELLED; - } const eObjectMode mode = eObjectMode(active_object->mode); const bNodeTree *node_tree_orig = get_node_group(*C, *op->ptr, op->reports); @@ -306,13 +336,10 @@ static int run_node_group_exec(bContext *C, wmOperator *op) 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); - BLI_SCOPED_DEFER([&]() { MEM_SAFE_FREE(objects); }); + const Vector objects = gather_supported_objects(*C, *bmain, mode); Depsgraph *depsgraph = build_depsgraph_from_indirect_ids( - *bmain, *scene, *view_layer, *node_tree_orig, {objects, objects_len}, *op->properties); + *bmain, *scene, *view_layer, *node_tree_orig, objects, *op->properties); DEG_evaluate_on_refresh(depsgraph); BLI_SCOPED_DEFER([&]() { DEG_graph_free(depsgraph); }); @@ -348,11 +375,9 @@ static int run_node_group_exec(bContext *C, wmOperator *op) OperatorComputeContext compute_context(op->type->idname); - for (Object *object : Span(objects, objects_len)) { - if (!ELEM(object->type, OB_CURVES, OB_POINTCLOUD, OB_MESH)) { - continue; - } + for (Object *object : objects) { nodes::GeoNodesOperatorData operator_eval_data{}; + operator_eval_data.mode = mode; operator_eval_data.depsgraph = depsgraph; operator_eval_data.self_object = DEG_get_evaluated_object(depsgraph, object); operator_eval_data.scene = DEG_get_evaluated_scene(depsgraph); @@ -600,72 +625,138 @@ static bool asset_menu_poll(const bContext *C, MenuType * /*mt*/) return CTX_wm_view3d(C); } -static GeometryNodeAssetTraitFlag asset_flag_for_context(const eContextObjectMode ctx_mode) +static GeometryNodeAssetTraitFlag asset_flag_for_context(const ObjectType type, + const eObjectMode mode) { - switch (ctx_mode) { - case CTX_MODE_EDIT_MESH: - return (GEO_NODE_ASSET_TOOL | GEO_NODE_ASSET_EDIT | GEO_NODE_ASSET_MESH); - case CTX_MODE_EDIT_CURVES: - return (GEO_NODE_ASSET_TOOL | GEO_NODE_ASSET_EDIT | GEO_NODE_ASSET_CURVE); - case CTX_MODE_EDIT_POINT_CLOUD: - return (GEO_NODE_ASSET_TOOL | GEO_NODE_ASSET_EDIT | GEO_NODE_ASSET_POINT_CLOUD); - case CTX_MODE_SCULPT: - return (GEO_NODE_ASSET_TOOL | GEO_NODE_ASSET_SCULPT | GEO_NODE_ASSET_MESH); - case CTX_MODE_SCULPT_CURVES: - return (GEO_NODE_ASSET_TOOL | GEO_NODE_ASSET_SCULPT | GEO_NODE_ASSET_CURVE); + switch (type) { + case OB_MESH: { + switch (mode) { + case OB_MODE_OBJECT: + return (GEO_NODE_ASSET_TOOL | GEO_NODE_ASSET_OBJECT | GEO_NODE_ASSET_MESH); + case OB_MODE_EDIT: + return (GEO_NODE_ASSET_TOOL | GEO_NODE_ASSET_EDIT | GEO_NODE_ASSET_MESH); + case OB_MODE_SCULPT: + return (GEO_NODE_ASSET_TOOL | GEO_NODE_ASSET_SCULPT | GEO_NODE_ASSET_MESH); + default: + break; + } + break; + } + case OB_CURVES: { + switch (mode) { + case OB_MODE_OBJECT: + return (GEO_NODE_ASSET_TOOL | GEO_NODE_ASSET_OBJECT | GEO_NODE_ASSET_CURVE); + case OB_MODE_EDIT: + return (GEO_NODE_ASSET_TOOL | GEO_NODE_ASSET_EDIT | GEO_NODE_ASSET_CURVE); + case OB_MODE_SCULPT_CURVES: + return (GEO_NODE_ASSET_TOOL | GEO_NODE_ASSET_SCULPT | GEO_NODE_ASSET_CURVE); + default: + break; + } + break; + } + case OB_POINTCLOUD: { + switch (mode) { + case OB_MODE_OBJECT: + return (GEO_NODE_ASSET_TOOL | GEO_NODE_ASSET_OBJECT | GEO_NODE_ASSET_POINT_CLOUD); + case OB_MODE_EDIT: + return (GEO_NODE_ASSET_TOOL | GEO_NODE_ASSET_EDIT | GEO_NODE_ASSET_POINT_CLOUD); + default: + break; + } + break; + } default: - BLI_assert_unreachable(); - return GeometryNodeAssetTraitFlag(0); + break; } + BLI_assert_unreachable(); + return GeometryNodeAssetTraitFlag(0); } -static asset::AssetItemTree *get_static_item_tree(const eContextObjectMode mode) +static GeometryNodeAssetTraitFlag asset_flag_for_context(const Object &active_object) { - switch (mode) { - case CTX_MODE_EDIT_MESH: { - static asset::AssetItemTree tree; - return &tree; + return asset_flag_for_context(ObjectType(active_object.type), eObjectMode(active_object.mode)); +} + +static asset::AssetItemTree *get_static_item_tree(const ObjectType type, const eObjectMode mode) +{ + switch (type) { + case OB_MESH: { + switch (mode) { + case OB_MODE_OBJECT: { + static asset::AssetItemTree tree; + return &tree; + } + case OB_MODE_EDIT: { + static asset::AssetItemTree tree; + return &tree; + } + case OB_MODE_SCULPT: { + static asset::AssetItemTree tree; + return &tree; + } + default: + return nullptr; + } } - case CTX_MODE_EDIT_CURVES: { - static asset::AssetItemTree tree; - return &tree; + case OB_CURVES: { + switch (mode) { + case OB_MODE_OBJECT: { + static asset::AssetItemTree tree; + return &tree; + } + case OB_MODE_EDIT: { + static asset::AssetItemTree tree; + return &tree; + } + case OB_MODE_SCULPT_CURVES: { + static asset::AssetItemTree tree; + return &tree; + } + default: + return nullptr; + } } - case CTX_MODE_EDIT_POINT_CLOUD: { - static asset::AssetItemTree tree; - return &tree; - } - case CTX_MODE_SCULPT: { - static asset::AssetItemTree tree; - return &tree; - } - case CTX_MODE_SCULPT_CURVES: { - static asset::AssetItemTree tree; - return &tree; + case OB_POINTCLOUD: { + switch (mode) { + case OB_MODE_OBJECT: { + static asset::AssetItemTree tree; + return &tree; + } + case OB_MODE_EDIT: { + static asset::AssetItemTree tree; + return &tree; + } + default: + return nullptr; + } } default: return nullptr; } } -static asset::AssetItemTree *get_static_item_tree(const bContext &C) +static asset::AssetItemTree *get_static_item_tree(const Object &active_object) { - return get_static_item_tree(eContextObjectMode(CTX_data_mode_enum(&C))); + return get_static_item_tree(ObjectType(active_object.type), eObjectMode(active_object.mode)); } void clear_operator_asset_trees() { - for (const int mode : IndexRange(CTX_MODE_NUM)) { - if (asset::AssetItemTree *tree = get_static_item_tree(eContextObjectMode(mode))) - *tree = {}; + for (const ObjectType type : {OB_MESH, OB_CURVES, OB_POINTCLOUD}) { + for (const eObjectMode mode : {OB_MODE_OBJECT, OB_MODE_EDIT, OB_MODE_SCULPT_CURVES}) { + if (asset::AssetItemTree *tree = get_static_item_tree(type, mode)) { + *tree = {}; + } + } } } -static asset::AssetItemTree build_catalog_tree(const bContext &C) +static asset::AssetItemTree build_catalog_tree(const bContext &C, const Object &active_object) { - const eContextObjectMode ctx_mode = eContextObjectMode(CTX_data_mode_enum(&C)); AssetFilterSettings type_filter{}; type_filter.id_types = FILTER_ID_NT; - const GeometryNodeAssetTraitFlag flag = asset_flag_for_context(ctx_mode); + const GeometryNodeAssetTraitFlag flag = asset_flag_for_context(active_object); auto meta_data_filter = [&](const AssetMetaData &meta_data) { const IDProperty *tree_type = BKE_asset_metadata_idprop_find(&meta_data, "type"); if (tree_type == nullptr || IDP_Int(tree_type) != NTREE_GEOMETRY) { @@ -703,6 +794,15 @@ static Set get_builtin_menus(const ObjectType object_type, const eO break; case OB_MESH: switch (mode) { + case OB_MODE_OBJECT: + menus.add_new("View"); + menus.add_new("Select"); + menus.add_new("Add"); + menus.add_new("Object"); + menus.add_new("Object/Apply"); + menus.add_new("Object/Convert"); + menus.add_new("Object/Quick Effects"); + break; case OB_MODE_EDIT: menus.add_new("View"); menus.add_new("Select"); @@ -752,7 +852,7 @@ static void catalog_assets_draw(const bContext *C, Menu *menu) if (!active_object) { return; } - asset::AssetItemTree *tree = get_static_item_tree(*C); + asset::AssetItemTree *tree = get_static_item_tree(*active_object); if (!tree) { return; } @@ -822,9 +922,11 @@ MenuType node_group_operator_assets_menu() static bool unassigned_local_poll(const bContext &C) { Main &bmain = *CTX_data_main(&C); - const GeometryNodeAssetTraitFlag flag = asset_flag_for_context( - eContextObjectMode(CTX_data_mode_enum(&C))); - + const Object *active_object = CTX_data_active_object(&C); + if (!active_object) { + return false; + } + const GeometryNodeAssetTraitFlag flag = asset_flag_for_context(*active_object); LISTBASE_FOREACH (const bNodeTree *, group, &bmain.nodetrees) { /* Assets are displayed in other menus, and non-local data-blocks aren't added to this menu. */ if (group->id.library_weak_reference || group->id.asset_data) { @@ -841,7 +943,11 @@ static bool unassigned_local_poll(const bContext &C) static void catalog_assets_draw_unassigned(const bContext *C, Menu *menu) { - asset::AssetItemTree *tree = get_static_item_tree(*C); + const Object *active_object = CTX_data_active_object(C); + if (!active_object) { + return; + } + asset::AssetItemTree *tree = get_static_item_tree(*active_object); if (!tree) { return; } @@ -860,8 +966,7 @@ static void catalog_assets_draw_unassigned(const bContext *C, Menu *menu) asset::operator_asset_reference_props_set(*asset, props_ptr); } - const GeometryNodeAssetTraitFlag flag = asset_flag_for_context( - eContextObjectMode(CTX_data_mode_enum(C))); + const GeometryNodeAssetTraitFlag flag = asset_flag_for_context(*active_object); bool first = true; bool add_separator = !tree->unassigned_assets.is_empty(); @@ -919,7 +1024,11 @@ void ui_template_node_operator_asset_menu_items(uiLayout &layout, const StringRef catalog_path) { bScreen &screen = *CTX_wm_screen(&C); - asset::AssetItemTree *tree = get_static_item_tree(C); + const Object *active_object = CTX_data_active_object(&C); + if (!active_object) { + return; + } + asset::AssetItemTree *tree = get_static_item_tree(*active_object); if (!tree) { return; } @@ -948,12 +1057,12 @@ void ui_template_node_operator_asset_root_items(uiLayout &layout, const bContext if (!active_object) { return; } - asset::AssetItemTree *tree = get_static_item_tree(C); + asset::AssetItemTree *tree = get_static_item_tree(*active_object); if (!tree) { return; } if (tree->assets_per_path.size() == 0) { - *tree = build_catalog_tree(C); + *tree = build_catalog_tree(C, *active_object); } asset_system::AssetLibrary *all_library = ED_assetlist_library_get_once_available( diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index ee09b4e858d..cafc23093d7 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -931,8 +931,9 @@ typedef enum GeometryNodeAssetTraitFlag { GEO_NODE_ASSET_CURVE = (1 << 4), GEO_NODE_ASSET_POINT_CLOUD = (1 << 5), GEO_NODE_ASSET_MODIFIER = (1 << 6), + GEO_NODE_ASSET_OBJECT = (1 << 7), } GeometryNodeAssetTraitFlag; -ENUM_OPERATORS(GeometryNodeAssetTraitFlag, GEO_NODE_ASSET_MODIFIER); +ENUM_OPERATORS(GeometryNodeAssetTraitFlag, GEO_NODE_ASSET_OBJECT); /* Data structs, for `node->storage`. */ diff --git a/source/blender/makesrna/intern/rna_nodetree.cc b/source/blender/makesrna/intern/rna_nodetree.cc index 825c97d6fcc..ff5d32cfd12 100644 --- a/source/blender/makesrna/intern/rna_nodetree.cc +++ b/source/blender/makesrna/intern/rna_nodetree.cc @@ -1802,6 +1802,15 @@ static void rna_GeometryNodeTree_is_modifier_set(PointerRNA *ptr, bool value) geometry_node_asset_trait_flag_set(ptr, GEO_NODE_ASSET_MODIFIER, value); } +static bool rna_GeometryNodeTree_is_mode_object_get(PointerRNA *ptr) +{ + return geometry_node_asset_trait_flag_get(ptr, GEO_NODE_ASSET_OBJECT); +} +static void rna_GeometryNodeTree_is_mode_object_set(PointerRNA *ptr, bool value) +{ + geometry_node_asset_trait_flag_set(ptr, GEO_NODE_ASSET_OBJECT, value); +} + static bool rna_GeometryNodeTree_is_mode_edit_get(PointerRNA *ptr) { return geometry_node_asset_trait_flag_get(ptr, GEO_NODE_ASSET_EDIT); @@ -10272,6 +10281,14 @@ static void rna_def_geometry_nodetree(BlenderRNA *brna) prop, "rna_GeometryNodeTree_is_modifier_get", "rna_GeometryNodeTree_is_modifier_set"); RNA_def_property_update(prop, NC_NODE | ND_DISPLAY, "rna_NodeTree_update_asset"); + prop = RNA_def_property(srna, "is_mode_object", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, nullptr, "flag", GEO_NODE_ASSET_EDIT); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_ui_text(prop, "Edit", "The node group is used in object mode"); + RNA_def_property_boolean_funcs( + prop, "rna_GeometryNodeTree_is_mode_object_get", "rna_GeometryNodeTree_is_mode_object_set"); + RNA_def_property_update(prop, NC_NODE | ND_DISPLAY, "rna_NodeTree_update_asset"); + prop = RNA_def_property(srna, "is_mode_edit", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, nullptr, "flag", GEO_NODE_ASSET_EDIT); RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); diff --git a/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh b/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh index 05a08df3c60..2f4b9991853 100644 --- a/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh +++ b/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh @@ -166,6 +166,7 @@ struct GeoNodesModifierData { }; struct GeoNodesOperatorData { + eObjectMode mode; /** The object currently effected by the operator. */ const Object *self_object = nullptr; /** Current evaluated depsgraph. */ diff --git a/source/blender/nodes/geometry/nodes/node_geo_tool_selection.cc b/source/blender/nodes/geometry/nodes/node_geo_tool_selection.cc index 420da2f4803..e90c682919d 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_tool_selection.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_tool_selection.cc @@ -53,7 +53,12 @@ static void node_geo_exec(GeoNodeExecParams params) if (!check_tool_context_and_error(params)) { return; } - params.set_output("Selection", Field(std::make_shared())); + if (params.user_data()->operator_data->mode == OB_MODE_OBJECT) { + params.set_output("Selection", true); + } + else { + params.set_output("Selection", Field(std::make_shared())); + } } static void node_register() diff --git a/source/blender/nodes/geometry/nodes/node_geo_tool_set_selection.cc b/source/blender/nodes/geometry/nodes/node_geo_tool_set_selection.cc index b20ea71dd36..dccc3b0cbb2 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_tool_set_selection.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_tool_set_selection.cc @@ -37,6 +37,11 @@ static void node_geo_exec(GeoNodeExecParams params) if (!check_tool_context_and_error(params)) { return; } + if (params.user_data()->operator_data->mode == OB_MODE_OBJECT) { + params.error_message_add(NodeWarningType::Error, + "Selection control is not supported in object mode"); + return; + } const Field selection = params.extract_input>("Selection"); const eAttrDomain domain = eAttrDomain(params.node().custom1); GeometrySet geometry = params.extract_input("Geometry");