From 6da4b87661f0fbbe7fc567b3cc3b6eb320bf87a2 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Tue, 5 Sep 2023 14:47:18 +0200 Subject: [PATCH] Geometry Nodes: Extend add modifier menu with node group assets Implements part of #111538. Change the modifier add button to create a menu with submenus. Extend the submenus dynamically with geometry node group assets. This makes it much simpler to share and use custom modifiers. Node groups get a new "Is Modifier" property, which is controllable in a popover in the node editor header when the group is an asset. The built in modifier can be rearranged in different categories in a next step. For now the existing organization is used, except for the geometry nodes modifier, which is called "Empty Modifier" and put in the root menu. The changes in !110855 and !110828 will be important to improve interaction speed with the new UI. Those are planned for 4.0 as well. Pull Request: https://projects.blender.org/blender/blender/pulls/111717 --- .../startup/bl_operators/geometry_nodes.py | 2 + .../startup/bl_ui/properties_data_modifier.py | 150 ++++++++- scripts/startup/bl_ui/space_node.py | 29 +- source/blender/editors/asset/CMakeLists.txt | 1 + .../blender/editors/asset/ED_asset_catalog.hh | 17 - .../blender/editors/asset/ED_asset_handle.h | 10 - .../editors/asset/intern/asset_catalog.cc | 20 -- .../editors/asset/intern/asset_handle.cc | 13 - .../editors/asset/intern/asset_menu_utils.cc | 186 +++++++++++ .../blender/editors/asset/intern/asset_ops.cc | 2 + .../editors/geometry/node_group_operator.cc | 141 +------- .../editors/include/ED_asset_menu_utils.hh | 60 ++++ source/blender/editors/include/ED_object.hh | 8 + source/blender/editors/object/CMakeLists.txt | 2 + .../editors/object/add_modifier_assets.cc | 314 ++++++++++++++++++ source/blender/editors/object/object_intern.h | 10 + source/blender/editors/object/object_ops.cc | 3 + .../editors/space_node/add_menu_assets.cc | 27 +- source/blender/editors/util/CMakeLists.txt | 1 + source/blender/makesdna/DNA_node_types.h | 1 + .../blender/makesrna/intern/rna_nodetree.cc | 17 + source/blender/makesrna/intern/rna_ui_api.cc | 17 + 22 files changed, 809 insertions(+), 222 deletions(-) create mode 100644 source/blender/editors/asset/intern/asset_menu_utils.cc create mode 100644 source/blender/editors/include/ED_asset_menu_utils.hh create mode 100644 source/blender/editors/object/add_modifier_assets.cc diff --git a/scripts/startup/bl_operators/geometry_nodes.py b/scripts/startup/bl_operators/geometry_nodes.py index 75f11611598..110f2ab3c16 100644 --- a/scripts/startup/bl_operators/geometry_nodes.py +++ b/scripts/startup/bl_operators/geometry_nodes.py @@ -230,6 +230,7 @@ class NewGeometryNodesModifier(Operator): return {'CANCELLED'} group = geometry_node_group_empty_new() + group.is_modifier = True modifier.node_group = group return {'FINISHED'} @@ -257,6 +258,7 @@ class NewGeometryNodeTreeAssign(Operator): if not modifier: return {'CANCELLED'} group = geometry_node_group_empty_new() + group.is_modifier = True modifier.node_group = group return {'FINISHED'} diff --git a/scripts/startup/bl_ui/properties_data_modifier.py b/scripts/startup/bl_ui/properties_data_modifier.py index 32cb44a0706..a66fc3a233e 100644 --- a/scripts/startup/bl_ui/properties_data_modifier.py +++ b/scripts/startup/bl_ui/properties_data_modifier.py @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: GPL-2.0-or-later -from bpy.types import Panel +from bpy.types import Panel, Menu class ModifierButtonsPanel: @@ -22,10 +22,151 @@ class DATA_PT_modifiers(ModifierButtonsPanel, Panel): def draw(self, _context): layout = self.layout - layout.operator_menu_enum("object.modifier_add", "type") + layout.operator("wm.call_menu", text="Add Modifier", icon='ADD').name="OBJECT_MT_modifier_add" layout.template_modifiers() +class OBJECT_MT_modifier_add(Menu): + bl_label = "Add Modifier" + + def draw(self, context): + layout = self.layout + ob_type = context.object.type + geometry_nodes_supported = ob_type in {'MESH', 'CURVE', 'CURVES', 'FONT', 'SURFACE', 'VOLUME', 'POINTCLOUD'} + if geometry_nodes_supported: + layout.operator("object.modifier_add", text="Empty Modifier").type='NODES' + if ob_type in {'MESH', 'CURVE', 'FONT', 'SURFACE', 'LATTICE'}: + layout.menu("OBJECT_MT_modifier_add_edit") + if ob_type in {'MESH', 'CURVE', 'FONT', 'SURFACE', 'VOLUME'}: + layout.menu("OBJECT_MT_modifier_add_generate") + if ob_type in {'MESH', 'CURVE', 'FONT', 'SURFACE', 'LATTICE', 'VOLUME'}: + layout.menu("OBJECT_MT_modifier_add_deform") + if ob_type in {'MESH', 'CURVE', 'FONT', 'SURFACE', 'LATTICE'}: + layout.menu("OBJECT_MT_modifier_add_physics") + + if geometry_nodes_supported: + layout.menu_contents("OBJECT_MT_modifier_add_root_catalogs") + + +class OBJECT_MT_modifier_add_edit(Menu): + bl_label = "Edit" + + def draw(self, context): + layout = self.layout + ob_type = context.object.type + if ob_type == 'MESH': + layout.operator("object.modifier_add", text="Data Transfer", icon='MOD_DATA_TRANSFER').type='DATA_TRANSFER' + if ob_type in {'MESH', 'CURVE', 'FONT', 'SURFACE', 'LATTICE'}: + layout.operator("object.modifier_add", text="Mesh Cache", icon='MOD_MESHDEFORM').type='MESH_CACHE' + if ob_type in {'MESH', 'CURVE', 'FONT', 'SURFACE'}: + layout.operator("object.modifier_add", text="Mesh Sequence Cache", icon='MOD_MESHDEFORM').type='MESH_SEQUENCE_CACHE' + if ob_type == 'MESH': + layout.operator("object.modifier_add", text="Normal Edit", icon='MOD_NORMALEDIT').type='NORMAL_EDIT' + layout.operator("object.modifier_add", text="Weighted Normal", icon='MOD_NORMALEDIT').type='WEIGHTED_NORMAL' + layout.operator("object.modifier_add", text="UV Project", icon='MOD_UVPROJECT').type='UV_PROJECT' + layout.operator("object.modifier_add", text="UV Warp", icon='MOD_UVPROJECT').type='UV_WARP' + layout.operator("object.modifier_add", text="Vertex Weight Edit", icon='MOD_VERTEX_WEIGHT').type='VERTEX_WEIGHT_EDIT' + layout.operator("object.modifier_add", text="Vertex Weight Mix", icon='MOD_VERTEX_WEIGHT').type='VERTEX_WEIGHT_MIX' + layout.operator("object.modifier_add", text="Vertex Weight Proximity", icon='MOD_VERTEX_WEIGHT').type='VERTEX_WEIGHT_PROXIMITY' + layout.template_modifier_asset_menu_items(catalog_path=self.bl_label) + + +class OBJECT_MT_modifier_add_generate(Menu): + bl_label = "Generate" + + def draw(self, context): + layout = self.layout + ob_type = context.object.type + if ob_type in {'MESH', 'CURVE', 'FONT', 'SURFACE'}: + layout.operator("object.modifier_add", text="Array", icon='MOD_ARRAY').type='ARRAY' + layout.operator("object.modifier_add", text="Bevel", icon='MOD_BEVEL').type='BEVEL' + if ob_type == 'MESH': + layout.operator("object.modifier_add", text="Boolean", icon='MOD_BOOLEAN').type='BOOLEAN' + if ob_type in {'MESH', 'CURVE', 'FONT', 'SURFACE'}: + layout.operator("object.modifier_add", text="Build", icon='MOD_BUILD').type='BUILD' + layout.operator("object.modifier_add", text="Decimate", icon='MOD_DECIM').type='DECIMATE' + layout.operator("object.modifier_add", text="Edge Split", icon='MOD_EDGESPLIT').type='EDGE_SPLIT' + if ob_type == 'MESH': + layout.operator("object.modifier_add", text="Mask", icon='MOD_MASK').type='MASK' + if ob_type in {'MESH', 'CURVE', 'FONT', 'SURFACE'}: + layout.operator("object.modifier_add", text="Mirror", icon='MOD_MIRROR').type='MIRROR' + if ob_type == 'VOLUME': + layout.operator("object.modifier_add", text="Mesh to Volume", icon='VOLUME_DATA').type='MESH_TO_VOLUME' + if ob_type == 'MESH': + layout.operator("object.modifier_add", text="Multiresolution", icon='MOD_MULTIRES').type='MULTIRES' + if ob_type in {'MESH', 'CURVE', 'FONT', 'SURFACE'}: + layout.operator("object.modifier_add", text="Remesh", icon='MOD_REMESH').type='REMESH' + layout.operator("object.modifier_add", text="Screw", icon='MOD_SCREW').type='SCREW' + if ob_type == 'MESH': + layout.operator("object.modifier_add", text="Skin", icon='MOD_SKIN').type='SKIN' + if ob_type in {'MESH', 'CURVE', 'FONT', 'SURFACE'}: + layout.operator("object.modifier_add", text="Solidify", icon='MOD_SOLIDIFY').type='SOLIDIFY' + layout.operator("object.modifier_add", text="Subdivision Surface", icon='MOD_SUBSURF').type='SUBSURF' + layout.operator("object.modifier_add", text="Triangulate", icon='MOD_TRIANGULATE').type='TRIANGULATE' + if ob_type == 'MESH': + layout.operator("object.modifier_add", text="Volume to Mesh", icon='VOLUME_DATA').type='VOLUME_TO_MESH' + if ob_type in {'MESH', 'CURVE', 'FONT', 'SURFACE'}: + layout.operator("object.modifier_add", text="Weld", icon='AUTOMERGE_OFF').type='WELD' + if ob_type == 'MESH': + layout.operator("object.modifier_add", text="Wireframe", icon='MOD_WIREFRAME').type='WIREFRAME' + layout.template_modifier_asset_menu_items(catalog_path=self.bl_label) + + +class OBJECT_MT_modifier_add_deform(Menu): + bl_label = "Deform" + + def draw(self, context): + layout = self.layout + ob_type = context.object.type + if ob_type in {'MESH', 'CURVE', 'FONT', 'SURFACE', 'LATTICE'}: + layout.operator("object.modifier_add", text="Armature", icon='MOD_ARMATURE').type='ARMATURE' + layout.operator("object.modifier_add", text="Cast", icon='MOD_CAST').type='CAST' + layout.operator("object.modifier_add", text="Curve", icon='MOD_CURVE').type='CURVE' + if ob_type == 'MESH': + layout.operator("object.modifier_add", text="Displace", icon='MOD_DISPLACE').type='DISPLACE' + if ob_type in {'MESH', 'CURVE', 'FONT', 'SURFACE', 'LATTICE'}: + layout.operator("object.modifier_add", text="Hook", icon='HOOK').type='HOOK' + if ob_type == 'MESH': + layout.operator("object.modifier_add", text="Laplacian Deform", icon='MOD_MESHDEFORM').type='LAPLACIANDEFORM' + if ob_type in {'MESH', 'CURVE', 'FONT', 'SURFACE', 'LATTICE'}: + layout.operator("object.modifier_add", text="Lattice", icon='MOD_LATTICE').type='LATTICE' + layout.operator("object.modifier_add", text="Mesh Deform", icon='MOD_MESHDEFORM').type='MESH_DEFORM' + layout.operator("object.modifier_add", text="Shrinkwrap", icon='MOD_SHRINKWRAP').type='SHRINKWRAP' + layout.operator("object.modifier_add", text="Simple Deform", icon='MOD_SIMPLEDEFORM').type='SIMPLE_DEFORM' + if ob_type in {'MESH', 'CURVE', 'FONT', 'SURFACE'}: + layout.operator("object.modifier_add", text="Smooth", icon='MOD_SMOOTH').type='SMOOTH' + if ob_type == 'MESH': + layout.operator("object.modifier_add", text="Smooth Corrective", icon='MOD_SMOOTH').type='CORRECTIVE_SMOOTH' + layout.operator("object.modifier_add", text="Smooth Laplacian", icon='MOD_SMOOTH').type='LAPLACIANSMOOTH' + layout.operator("object.modifier_add", text="Surface Deform", icon='MOD_MESHDEFORM').type='SURFACE_DEFORM' + if ob_type in {'MESH', 'CURVE', 'FONT', 'SURFACE', 'LATTICE'}: + layout.operator("object.modifier_add", text="Warp", icon='MOD_WARP').type='WARP' + layout.operator("object.modifier_add", text="Wave", icon='MOD_WAVE').type='WAVE' + if ob_type == 'VOLUME': + layout.operator("object.modifier_add", text="Volume Displace", icon='VOLUME_DATA').type='VOLUME_DISPLACE' + layout.template_modifier_asset_menu_items(catalog_path=self.bl_label) + + +class OBJECT_MT_modifier_add_physics(Menu): + bl_label = "Physics" + + def draw(self, context): + layout = self.layout + ob_type = context.object.type + if ob_type == 'MESH': + layout.operator("object.modifier_add", text="Cloth", icon='MOD_CLOTH').type='CLOTH' + layout.operator("object.modifier_add", text="Collision", icon='MOD_PHYSICS').type='COLLISION' + layout.operator("object.modifier_add", text="Dynamic Paint", icon='MOD_DYNAMICPAINT').type='DYNAMIC_PAINT' + layout.operator("object.modifier_add", text="Explode", icon='MOD_EXPLODE').type='EXPLODE' + layout.operator("object.modifier_add", text="Fluid", icon='MOD_FLUIDSIM').type='FLUID' + layout.operator("object.modifier_add", text="Ocean", icon='MOD_OCEAN').type='OCEAN' + layout.operator("object.modifier_add", text="Particle Instance", icon='MOD_PARTICLE_INSTANCE').type='PARTICLE_INSTANCE' + layout.operator("object.modifier_add", text="Particle System", icon='MOD_PARTICLES').type='PARTICLE_SYSTEM' + if ob_type in {'MESH', 'CURVE', 'FONT', 'SURFACE', 'LATTICE'}: + layout.operator("object.modifier_add", text="Soft Body", icon='MOD_SOFT').type='SOFT_BODY' + layout.template_modifier_asset_menu_items(catalog_path=self.bl_label) + + class DATA_PT_gpencil_modifiers(ModifierButtonsPanel, Panel): bl_label = "Modifiers" @@ -42,6 +183,11 @@ class DATA_PT_gpencil_modifiers(ModifierButtonsPanel, Panel): classes = ( DATA_PT_modifiers, + OBJECT_MT_modifier_add, + OBJECT_MT_modifier_add_edit, + OBJECT_MT_modifier_add_generate, + OBJECT_MT_modifier_add_deform, + OBJECT_MT_modifier_add_physics, DATA_PT_gpencil_modifiers, ) diff --git a/scripts/startup/bl_ui/space_node.py b/scripts/startup/bl_ui/space_node.py index a36449d5d85..45c597db07e 100644 --- a/scripts/startup/bl_ui/space_node.py +++ b/scripts/startup/bl_ui/space_node.py @@ -160,6 +160,8 @@ class NODE_HT_header(Header): 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") + if snode.node_tree and snode.node_tree.asset_data: + layout.popover(panel="NODE_PT_geometry_node_asset_traits") else: layout.template_ID(snode, "node_tree", new="node.new_geometry_node_group_tool") if snode.node_tree and snode.node_tree.asset_data: @@ -448,18 +450,21 @@ class NODE_PT_geometry_node_asset_traits(Panel): snode = context.space_data group = snode.node_tree - col = layout.column(heading="Type") - col.prop(group, "is_tool") - col = layout.column(heading="Mode") - col.active = group.is_tool - col.prop(group, "is_mode_edit") - col.prop(group, "is_mode_sculpt") - col = layout.column(heading="Geometry") - col.active = group.is_tool - col.prop(group, "is_type_mesh") - col.prop(group, "is_type_curve") - if context.preferences.experimental.use_new_point_cloud_type: - col.prop(group, "is_type_point_cloud") + if snode.geometry_nodes_type == 'MODIFIER': + layout.prop(group, "is_modifier") + else: + col = layout.column(heading="Type") + col.prop(group, "is_tool") + col = layout.column(heading="Mode") + col.active = group.is_tool + col.prop(group, "is_mode_edit") + col.prop(group, "is_mode_sculpt") + col = layout.column(heading="Geometry") + col.active = group.is_tool + col.prop(group, "is_type_mesh") + col.prop(group, "is_type_curve") + if context.preferences.experimental.use_new_point_cloud_type: + col.prop(group, "is_type_point_cloud") class NODE_PT_node_color_presets(PresetPanel, Panel): diff --git a/source/blender/editors/asset/CMakeLists.txt b/source/blender/editors/asset/CMakeLists.txt index f1ecc6ac1c5..0d66dfc1994 100644 --- a/source/blender/editors/asset/CMakeLists.txt +++ b/source/blender/editors/asset/CMakeLists.txt @@ -31,6 +31,7 @@ set(SRC intern/asset_library_reference_enum.cc intern/asset_list.cc intern/asset_mark_clear.cc + intern/asset_menu_utils.cc intern/asset_ops.cc intern/asset_shelf.cc intern/asset_shelf_asset_view.cc diff --git a/source/blender/editors/asset/ED_asset_catalog.hh b/source/blender/editors/asset/ED_asset_catalog.hh index 7a848dc31ee..4d7495e2b73 100644 --- a/source/blender/editors/asset/ED_asset_catalog.hh +++ b/source/blender/editors/asset/ED_asset_catalog.hh @@ -26,10 +26,6 @@ struct AssetLibrary; struct bScreen; -namespace blender::asset_system { -class AssetCatalogTreeItem; -} - /** * Returns if the catalogs of \a library are allowed to be editable, or if the UI should forbid * edits. @@ -59,16 +55,3 @@ void ED_asset_catalog_move( AssetLibrary *library, blender::asset_system::CatalogID src_catalog_id, std::optional dst_parent_catalog_id = std::nullopt); - -namespace blender::ed::asset { - -/** - * Some code needs to pass catalog paths to context and for this they need persistent pointers to - * the paths. Rather than keeping some local path storage, get a pointer into the asset system - * directly, which is persistent until the library is reloaded and can safely be held by context. - */ -PointerRNA persistent_catalog_path_rna_pointer(const bScreen &owner_screen, - const asset_system::AssetLibrary &library, - const asset_system::AssetCatalogTreeItem &item); - -} // namespace blender::ed::asset diff --git a/source/blender/editors/asset/ED_asset_handle.h b/source/blender/editors/asset/ED_asset_handle.h index 2dbc1b81dd2..2506dc15ab6 100644 --- a/source/blender/editors/asset/ED_asset_handle.h +++ b/source/blender/editors/asset/ED_asset_handle.h @@ -47,13 +47,3 @@ void ED_asset_handle_get_full_library_path( #ifdef __cplusplus } #endif - -#ifdef __cplusplus - -namespace blender::ed::asset { - -PointerRNA create_asset_rna_ptr(const asset_system::AssetRepresentation *asset); - -} - -#endif diff --git a/source/blender/editors/asset/intern/asset_catalog.cc b/source/blender/editors/asset/intern/asset_catalog.cc index 8d17ef5478a..5a139ccb6a9 100644 --- a/source/blender/editors/asset/intern/asset_catalog.cc +++ b/source/blender/editors/asset/intern/asset_catalog.cc @@ -206,23 +206,3 @@ bool ED_asset_catalogs_get_save_catalogs_when_file_is_saved() { return asset_system::AssetLibrary::save_catalogs_when_file_is_saved; } - -namespace blender::ed::asset { - -PointerRNA persistent_catalog_path_rna_pointer(const bScreen &owner_screen, - const asset_system::AssetLibrary &library, - const asset_system::AssetCatalogTreeItem &item) -{ - const asset_system::AssetCatalog *catalog = library.catalog_service->find_catalog_by_path( - item.catalog_path()); - if (!catalog) { - return PointerRNA_NULL; - } - - const asset_system::AssetCatalogPath &path = catalog->path; - return {&const_cast(owner_screen.id), - &RNA_AssetCatalogPath, - const_cast(&path)}; -} - -} // namespace blender::ed::asset diff --git a/source/blender/editors/asset/intern/asset_handle.cc b/source/blender/editors/asset/intern/asset_handle.cc index dedffe94202..684ef1a0b52 100644 --- a/source/blender/editors/asset/intern/asset_handle.cc +++ b/source/blender/editors/asset/intern/asset_handle.cc @@ -57,16 +57,3 @@ void ED_asset_handle_get_full_library_path(const AssetHandle *asset_handle, BLI_strncpy(r_full_lib_path, library_path.c_str(), FILE_MAX); } - -namespace blender::ed::asset { - -PointerRNA create_asset_rna_ptr(const asset_system::AssetRepresentation *asset) -{ - PointerRNA ptr{}; - ptr.owner_id = nullptr; - ptr.type = &RNA_AssetRepresentation; - ptr.data = const_cast(asset); - return ptr; -} - -} // namespace blender::ed::asset diff --git a/source/blender/editors/asset/intern/asset_menu_utils.cc b/source/blender/editors/asset/intern/asset_menu_utils.cc new file mode 100644 index 00000000000..69a53d2bffd --- /dev/null +++ b/source/blender/editors/asset/intern/asset_menu_utils.cc @@ -0,0 +1,186 @@ +/* SPDX-FileCopyrightText: 2023 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup edasset + */ + +#include "AS_asset_catalog.hh" +#include "AS_asset_catalog_tree.hh" +#include "AS_asset_library.hh" +#include "AS_asset_representation.hh" + +#include "DNA_screen_types.h" + +#include "BKE_asset.h" +#include "BKE_report.h" + +#include "BLT_translation.h" + +#include "WM_api.hh" + +#include "RNA_access.hh" +#include "RNA_define.hh" +#include "RNA_enum_types.hh" +#include "RNA_prototypes.h" + +#include "ED_asset_list.h" +#include "ED_asset_list.hh" +#include "ED_asset_menu_utils.hh" + +#include "UI_interface.hh" + +namespace blender::ed::asset { + +void operator_asset_reference_props_register(StructRNA &srna) +{ + PropertyRNA *prop; + prop = RNA_def_enum(&srna, + "asset_library_type", + rna_enum_aset_library_type_items, + ASSET_LIBRARY_LOCAL, + "Asset Library Type", + ""); + RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); + prop = RNA_def_string( + &srna, "asset_library_identifier", nullptr, 0, "Asset Library Identifier", ""); + RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); + prop = RNA_def_string( + &srna, "relative_asset_identifier", nullptr, 0, "Relative Asset Identifier", ""); + RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); +} + +void operator_asset_reference_props_set(const asset_system::AssetRepresentation &asset, + PointerRNA &ptr) +{ + AssetWeakReference *weak_ref = asset.make_weak_reference(); + RNA_enum_set(&ptr, "asset_library_type", weak_ref->asset_library_type); + RNA_string_set(&ptr, "asset_library_identifier", weak_ref->asset_library_identifier); + RNA_string_set(&ptr, "relative_asset_identifier", weak_ref->relative_asset_identifier); + BKE_asset_weak_reference_free(&weak_ref); +} + +/** + * #AssetLibrary::resolve_asset_weak_reference_to_full_path() currently does not support local + * assets. + */ +static const asset_system::AssetRepresentation *get_local_asset_from_relative_identifier( + const bContext &C, const StringRefNull relative_identifier, ReportList *reports) +{ + AssetLibraryReference library_ref{}; + library_ref.type = ASSET_LIBRARY_LOCAL; + ED_assetlist_storage_fetch(&library_ref, &C); + ED_assetlist_ensure_previews_job(&library_ref, &C); + + const asset_system::AssetRepresentation *matching_asset = nullptr; + ED_assetlist_iterate(library_ref, [&](asset_system::AssetRepresentation &asset) { + if (asset.get_identifier().library_relative_identifier() == relative_identifier) { + matching_asset = &asset; + return false; + } + return true; + }); + + if (reports && !matching_asset) { + if (ED_assetlist_is_loaded(&library_ref)) { + BKE_reportf( + reports, RPT_ERROR, "No asset found at path \"%s\"", relative_identifier.c_str()); + } + else { + BKE_report(reports, RPT_WARNING, "Asset loading is unfinished"); + } + } + return matching_asset; +} + +static const asset_system::AssetRepresentation *find_asset_from_weak_ref( + const bContext &C, const AssetWeakReference &weak_ref, ReportList *reports) +{ + if (weak_ref.asset_library_type == ASSET_LIBRARY_LOCAL) { + return get_local_asset_from_relative_identifier( + C, weak_ref.relative_asset_identifier, reports); + } + + const AssetLibraryReference library_ref = asset_system::all_library_reference(); + ED_assetlist_storage_fetch(&library_ref, &C); + ED_assetlist_ensure_previews_job(&library_ref, &C); + asset_system::AssetLibrary *all_library = ED_assetlist_library_get_once_available( + asset_system::all_library_reference()); + if (!all_library) { + BKE_report(reports, RPT_WARNING, "Asset loading is unfinished"); + } + + const std::string full_path = all_library->resolve_asset_weak_reference_to_full_path(weak_ref); + + const asset_system::AssetRepresentation *matching_asset = nullptr; + ED_assetlist_iterate(library_ref, [&](asset_system::AssetRepresentation &asset) { + if (asset.get_identifier().full_path() == full_path) { + matching_asset = &asset; + return false; + } + return true; + }); + + if (reports && !matching_asset) { + if (ED_assetlist_is_loaded(&library_ref)) { + BKE_reportf(reports, RPT_ERROR, "No asset found at path \"%s\"", full_path.c_str()); + } + } + return matching_asset; +} + +const asset_system::AssetRepresentation *operator_asset_reference_props_get_asset_from_all_library( + const bContext &C, PointerRNA &ptr, ReportList *reports) +{ + AssetWeakReference weak_ref{}; + weak_ref.asset_library_type = RNA_enum_get(&ptr, "asset_library_type"); + weak_ref.asset_library_identifier = RNA_string_get_alloc( + &ptr, "asset_library_identifier", nullptr, 0, nullptr); + weak_ref.relative_asset_identifier = RNA_string_get_alloc( + &ptr, "relative_asset_identifier", nullptr, 0, nullptr); + return find_asset_from_weak_ref(C, weak_ref, reports); +} + +PointerRNA persistent_catalog_path_rna_pointer(const bScreen &owner_screen, + const asset_system::AssetLibrary &library, + const asset_system::AssetCatalogTreeItem &item) +{ + const asset_system::AssetCatalog *catalog = library.catalog_service->find_catalog_by_path( + item.catalog_path()); + if (!catalog) { + return PointerRNA_NULL; + } + + const asset_system::AssetCatalogPath &path = catalog->path; + return {&const_cast(owner_screen.id), + &RNA_AssetCatalogPath, + const_cast(&path)}; +} + +PointerRNA create_asset_rna_ptr(const asset_system::AssetRepresentation *asset) +{ + PointerRNA ptr{}; + ptr.owner_id = nullptr; + ptr.type = &RNA_AssetRepresentation; + ptr.data = const_cast(asset); + return ptr; +} + +void draw_menu_for_catalog(const bScreen &owner_screen, + const asset_system::AssetLibrary &library, + const asset_system::AssetCatalogTreeItem &item, + const StringRefNull menu_name, + uiLayout &layout) +{ + PointerRNA path_ptr = asset::persistent_catalog_path_rna_pointer(owner_screen, library, item); + if (path_ptr.data == nullptr) { + return; + } + + uiLayout *col = uiLayoutColumn(&layout, false); + uiLayoutSetContextPointer(col, "asset_catalog_path", &path_ptr); + uiItemM(col, menu_name.c_str(), IFACE_(item.get_name().c_str()), ICON_NONE); +} + +} // namespace blender::ed::asset diff --git a/source/blender/editors/asset/intern/asset_ops.cc b/source/blender/editors/asset/intern/asset_ops.cc index fb3b2704676..85ad911f3af 100644 --- a/source/blender/editors/asset/intern/asset_ops.cc +++ b/source/blender/editors/asset/intern/asset_ops.cc @@ -10,6 +10,7 @@ #include "AS_asset_library.hh" #include "AS_asset_representation.hh" +#include "BKE_asset.h" #include "BKE_bpath.h" #include "BKE_context.h" #include "BKE_lib_id.h" @@ -33,6 +34,7 @@ #include "RNA_access.hh" #include "RNA_define.hh" +#include "RNA_enum_types.hh" #include "RNA_prototypes.h" #include "WM_api.hh" diff --git a/source/blender/editors/geometry/node_group_operator.cc b/source/blender/editors/geometry/node_group_operator.cc index 048e7873542..14ea9848014 100644 --- a/source/blender/editors/geometry/node_group_operator.cc +++ b/source/blender/editors/geometry/node_group_operator.cc @@ -42,12 +42,12 @@ #include "RNA_access.hh" #include "RNA_define.hh" -#include "RNA_enum_types.hh" #include "UI_interface.hh" #include "UI_resources.hh" #include "ED_asset.hh" +#include "ED_asset_menu_utils.hh" #include "ED_geometry.hh" #include "ED_mesh.hh" @@ -72,92 +72,10 @@ namespace blender::ed::geometry { /** \name Operator * \{ */ -/** - * #AssetLibrary::resolve_asset_weak_reference_to_full_path() currently does not support local - * assets. - */ -static const asset_system::AssetRepresentation *get_local_asset_from_relative_identifier( - const bContext &C, const StringRefNull relative_identifier, ReportList *reports) -{ - AssetLibraryReference library_ref{}; - library_ref.type = ASSET_LIBRARY_LOCAL; - ED_assetlist_storage_fetch(&library_ref, &C); - ED_assetlist_ensure_previews_job(&library_ref, &C); - - const asset_system::AssetRepresentation *matching_asset = nullptr; - ED_assetlist_iterate(library_ref, [&](asset_system::AssetRepresentation &asset) { - if (asset.get_identifier().library_relative_identifier() == relative_identifier) { - matching_asset = &asset; - return false; - } - return true; - }); - - if (reports && !matching_asset) { - if (ED_assetlist_is_loaded(&library_ref)) { - BKE_reportf( - reports, RPT_ERROR, "No asset found at path \"%s\"", relative_identifier.c_str()); - } - else { - BKE_report(reports, RPT_WARNING, "Asset loading is unfinished"); - } - } - return matching_asset; -} - -static const asset_system::AssetRepresentation *find_asset_from_weak_ref( - const bContext &C, const AssetWeakReference &weak_ref, ReportList *reports) -{ - if (weak_ref.asset_library_type == ASSET_LIBRARY_LOCAL) { - return get_local_asset_from_relative_identifier( - C, weak_ref.relative_asset_identifier, reports); - } - - const AssetLibraryReference library_ref = asset_system::all_library_reference(); - ED_assetlist_storage_fetch(&library_ref, &C); - ED_assetlist_ensure_previews_job(&library_ref, &C); - asset_system::AssetLibrary *all_library = ED_assetlist_library_get_once_available( - asset_system::all_library_reference()); - if (!all_library) { - BKE_report(reports, RPT_WARNING, "Asset loading is unfinished"); - } - - const std::string full_path = all_library->resolve_asset_weak_reference_to_full_path(weak_ref); - - const asset_system::AssetRepresentation *matching_asset = nullptr; - ED_assetlist_iterate(library_ref, [&](asset_system::AssetRepresentation &asset) { - if (asset.get_identifier().full_path() == full_path) { - matching_asset = &asset; - return false; - } - return true; - }); - - if (reports && !matching_asset) { - if (ED_assetlist_is_loaded(&library_ref)) { - BKE_reportf(reports, RPT_ERROR, "No asset found at path \"%s\"", full_path.c_str()); - } - } - return matching_asset; -} - -/** \note Does not check asset type or meta data. */ -static const asset_system::AssetRepresentation *get_asset(const bContext &C, - PointerRNA &ptr, - ReportList *reports) -{ - AssetWeakReference weak_ref{}; - weak_ref.asset_library_type = RNA_enum_get(&ptr, "asset_library_type"); - weak_ref.asset_library_identifier = RNA_string_get_alloc( - &ptr, "asset_library_identifier", nullptr, 0, nullptr); - weak_ref.relative_asset_identifier = RNA_string_get_alloc( - &ptr, "relative_asset_identifier", nullptr, 0, nullptr); - return find_asset_from_weak_ref(C, weak_ref, reports); -} - static const bNodeTree *get_node_group(const bContext &C, PointerRNA &ptr, ReportList *reports) { - const asset_system::AssetRepresentation *asset = get_asset(C, ptr, reports); + const asset_system::AssetRepresentation *asset = + asset::operator_asset_reference_props_get_asset_from_all_library(C, ptr, reports); if (!asset) { return nullptr; } @@ -376,7 +294,8 @@ static std::string run_node_group_get_description(bContext *C, wmOperatorType * /*ot*/, PointerRNA *ptr) { - const asset_system::AssetRepresentation *asset = get_asset(*C, *ptr, nullptr); + const asset_system::AssetRepresentation *asset = + asset::operator_asset_reference_props_get_asset_from_all_library(*C, *ptr, nullptr); if (!asset) { return ""; } @@ -534,20 +453,7 @@ void GEOMETRY_OT_execute_node_group(wmOperatorType *ot) ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - PropertyRNA *prop; - prop = RNA_def_enum(ot->srna, - "asset_library_type", - rna_enum_aset_library_type_items, - ASSET_LIBRARY_LOCAL, - "Asset Library Type", - ""); - RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); - prop = RNA_def_string( - ot->srna, "asset_library_identifier", nullptr, 0, "Asset Library Identifier", ""); - RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); - prop = RNA_def_string( - ot->srna, "relative_asset_identifier", nullptr, 0, "Relative Asset Identifier", ""); - RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); + asset::operator_asset_reference_props_register(*ot->srna); } /** \} */ @@ -706,7 +612,6 @@ static void node_add_catalog_assets_draw(const bContext *C, Menu *menu) for (const asset_system::AssetRepresentation *asset : assets) { uiLayout *col = uiLayoutColumn(layout, false); wmOperatorType *ot = WM_operatortype_find("GEOMETRY_OT_execute_node_group", true); - AssetWeakReference *weak_ref = asset->make_weak_reference(); PointerRNA props_ptr; uiItemFullO_ptr(col, ot, @@ -716,11 +621,7 @@ static void node_add_catalog_assets_draw(const bContext *C, Menu *menu) WM_OP_INVOKE_DEFAULT, UI_ITEM_NONE, &props_ptr); - RNA_enum_set(&props_ptr, "asset_library_type", weak_ref->asset_library_type); - RNA_string_set(&props_ptr, "asset_library_identifier", weak_ref->asset_library_identifier); - RNA_string_set(&props_ptr, "relative_asset_identifier", weak_ref->relative_asset_identifier); - - BKE_asset_weak_reference_free(&weak_ref); + asset::operator_asset_reference_props_set(*asset, props_ptr); } asset_system::AssetLibrary *all_library = ED_assetlist_library_get_once_available( @@ -729,18 +630,9 @@ static void node_add_catalog_assets_draw(const bContext *C, Menu *menu) return; } - catalog_item->foreach_child([&](asset_system::AssetCatalogTreeItem &child_item) { - PointerRNA path_ptr = asset::persistent_catalog_path_rna_pointer( - screen, *all_library, child_item); - if (path_ptr.data == nullptr) { - return; - } - uiLayout *col = uiLayoutColumn(layout, false); - uiLayoutSetContextPointer(col, "asset_catalog_path", &path_ptr); - uiItemM(col, - "GEO_MT_node_operator_catalog_assets", - IFACE_(child_item.get_name().c_str()), - ICON_NONE); + catalog_item->foreach_child([&](asset_system::AssetCatalogTreeItem &item) { + asset::draw_menu_for_catalog( + screen, *all_library, item, "GEO_MT_node_operator_catalog_assets", *layout); }); } @@ -809,17 +701,10 @@ void ui_template_node_operator_asset_root_items(uiLayout &layout, bContext &C) eObjectMode(active_object->mode)); tree->catalogs.foreach_root_item([&](asset_system::AssetCatalogTreeItem &item) { - if (builtin_menus.contains(item.get_name())) { - return; + if (!builtin_menus.contains(item.get_name())) { + asset::draw_menu_for_catalog( + screen, *all_library, item, "GEO_MT_node_operator_catalog_assets", layout); } - PointerRNA path_ptr = asset::persistent_catalog_path_rna_pointer(screen, *all_library, item); - if (path_ptr.data == nullptr) { - return; - } - uiLayout *col = uiLayoutColumn(&layout, false); - uiLayoutSetContextPointer(col, "asset_catalog_path", &path_ptr); - const char *text = IFACE_(item.get_name().c_str()); - uiItemM(col, "GEO_MT_node_operator_catalog_assets", text, ICON_NONE); }); } diff --git a/source/blender/editors/include/ED_asset_menu_utils.hh b/source/blender/editors/include/ED_asset_menu_utils.hh new file mode 100644 index 00000000000..91860c72b0a --- /dev/null +++ b/source/blender/editors/include/ED_asset_menu_utils.hh @@ -0,0 +1,60 @@ +/* SPDX-FileCopyrightText: 2023 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup edasset + * + * Code for dealing with dynamic asset menus and passing assets to operators with RNA properties. + */ + +#pragma once + +#include "BLI_string_ref.hh" + +#include "RNA_types.hh" + +struct AssetLibrary; +struct bScreen; +struct uiLayout; + +namespace blender::asset_system { +class AssetCatalogTreeItem; +class AssetLibrary; +class AssetRepresentation; +} // namespace blender::asset_system + +namespace blender::ed::asset { + +/** + * Some code needs to pass catalog paths to context and for this they need persistent pointers to + * the paths. Rather than keeping some local path storage, get a pointer into the asset system + * directly, which is persistent until the library is reloaded and can safely be held by context. + */ +PointerRNA persistent_catalog_path_rna_pointer(const bScreen &owner_screen, + const asset_system::AssetLibrary &library, + const asset_system::AssetCatalogTreeItem &item); + +void draw_menu_for_catalog(const bScreen &owner_screen, + const asset_system::AssetLibrary &library, + const asset_system::AssetCatalogTreeItem &item, + StringRefNull menu_name, + uiLayout &layout); + +PointerRNA create_asset_rna_ptr(const asset_system::AssetRepresentation *asset); + +void operator_asset_reference_props_set(const asset_system::AssetRepresentation &asset, + PointerRNA &ptr); +void operator_asset_reference_props_register(StructRNA &srna); + +/** + * Load all asset libraries to find an asset from the #operator_asset_reference_props_register + * properties. The loading happens in the background, so there may be no result immediately. In + * that case an "Asset loading is unfinished" report is added. + * + * \note Does not check asset type or meta data. + */ +const asset_system::AssetRepresentation *operator_asset_reference_props_get_asset_from_all_library( + const bContext &C, PointerRNA &ptr, ReportList *reports); + +} // namespace blender::ed::asset diff --git a/source/blender/editors/include/ED_object.hh b/source/blender/editors/include/ED_object.hh index 6e8a0a28fca..44cf76814a3 100644 --- a/source/blender/editors/include/ED_object.hh +++ b/source/blender/editors/include/ED_object.hh @@ -9,6 +9,8 @@ #pragma once #include "BLI_compiler_attrs.h" +#include "BLI_string_ref.hh" + #include "DNA_object_enums.h" #include "DNA_userdef_enums.h" #include "DNA_windowmanager_types.h" @@ -636,3 +638,9 @@ void ED_object_data_xform_by_mat4(XFormObjectData *xod, const float mat[4][4]); void ED_object_data_xform_restore(XFormObjectData *xod); void ED_object_data_xform_tag_update(XFormObjectData *xod); + +namespace blender::ed::object { + +void ui_template_modifier_asset_menu_items(uiLayout &layout, bContext &C, StringRef catalog_path); + +} diff --git a/source/blender/editors/object/CMakeLists.txt b/source/blender/editors/object/CMakeLists.txt index f1085ce21f1..d3d1fe4da90 100644 --- a/source/blender/editors/object/CMakeLists.txt +++ b/source/blender/editors/object/CMakeLists.txt @@ -4,6 +4,7 @@ set(INC ../include + ../../asset_system ../../blenfont ../../blenkernel ../../blentranslation @@ -30,6 +31,7 @@ set(INC_SYS ) set(SRC + add_modifier_assets.cc object_add.cc object_bake.cc object_bake_api.cc diff --git a/source/blender/editors/object/add_modifier_assets.cc b/source/blender/editors/object/add_modifier_assets.cc new file mode 100644 index 00000000000..4222410b980 --- /dev/null +++ b/source/blender/editors/object/add_modifier_assets.cc @@ -0,0 +1,314 @@ +/* SPDX-FileCopyrightText: 2023 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "AS_asset_catalog.hh" +#include "AS_asset_catalog_tree.hh" +#include "AS_asset_library.hh" +#include "AS_asset_representation.hh" + +#include "BLI_multi_value_map.hh" +#include "BLI_string.h" + +#include "DNA_modifier_types.h" +#include "DNA_screen_types.h" +#include "DNA_space_types.h" + +#include "BKE_asset.h" +#include "BKE_context.h" +#include "BKE_idprop.h" +#include "BKE_lib_id.h" +#include "BKE_report.h" +#include "BKE_screen.h" + +#include "BLT_translation.h" + +#include "RNA_access.hh" + +#include "ED_asset.hh" +#include "ED_asset_menu_utils.hh" +#include "ED_object.hh" +#include "ED_screen.hh" + +#include "MOD_nodes.hh" + +#include "UI_interface.hh" + +#include "WM_api.hh" + +#include "object_intern.h" + +namespace blender::ed::object { + +static bool all_loading_finished() +{ + AssetLibraryReference all_library_ref = asset_system::all_library_reference(); + return ED_assetlist_is_loaded(&all_library_ref); +} + +static asset::AssetItemTree build_catalog_tree(const bContext &C) +{ + AssetFilterSettings type_filter{}; + type_filter.id_types = FILTER_ID_NT; + 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) { + return false; + } + const IDProperty *traits_flag = BKE_asset_metadata_idprop_find( + &meta_data, "geometry_node_asset_traits_flag"); + if (traits_flag == nullptr || !(IDP_Int(traits_flag) & GEO_NODE_ASSET_MODIFIER)) { + return false; + } + return true; + }; + const AssetLibraryReference library = asset_system::all_library_reference(); + return asset::build_filtered_all_catalog_tree(library, C, type_filter, meta_data_filter); +} + +static asset::AssetItemTree *get_static_item_tree() +{ + static asset::AssetItemTree tree; + return &tree; +} + +static void catalog_assets_draw(const bContext *C, Menu *menu) +{ + bScreen &screen = *CTX_wm_screen(C); + asset::AssetItemTree &tree = *get_static_item_tree(); + + const PointerRNA menu_path_ptr = CTX_data_pointer_get(C, "asset_catalog_path"); + if (RNA_pointer_is_null(&menu_path_ptr)) { + return; + } + const asset_system::AssetCatalogPath &menu_path = + *static_cast(menu_path_ptr.data); + + const Span assets = tree.assets_per_path.lookup(menu_path); + asset_system::AssetCatalogTreeItem *catalog_item = tree.catalogs.find_item(menu_path); + BLI_assert(catalog_item != nullptr); + + if (assets.is_empty() && !catalog_item->has_children()) { + return; + } + + uiLayout *layout = menu->layout; + uiItemS(layout); + + for (const asset_system::AssetRepresentation *asset : assets) { + uiLayout *col = uiLayoutColumn(layout, false); + wmOperatorType *ot = WM_operatortype_find("OBJECT_OT_modifier_add_asset", true); + PointerRNA props_ptr; + uiItemFullO_ptr(col, + ot, + IFACE_(asset->get_name().c_str()), + ICON_NONE, + nullptr, + WM_OP_INVOKE_DEFAULT, + UI_ITEM_NONE, + &props_ptr); + asset::operator_asset_reference_props_set(*asset, props_ptr); + } + + asset_system::AssetLibrary *all_library = ED_assetlist_library_get_once_available( + asset_system::all_library_reference()); + if (!all_library) { + return; + } + + catalog_item->foreach_child([&](asset_system::AssetCatalogTreeItem &item) { + asset::draw_menu_for_catalog( + screen, *all_library, item, "OBJECT_MT_add_modifier_catalog_assets", *layout); + }); +} + +static void root_catalogs_draw(const bContext *C, Menu *menu) +{ + const Object *object = ED_object_active_context(C); + if (!object) { + return; + } + bScreen &screen = *CTX_wm_screen(C); + uiLayout *layout = menu->layout; + + const bool loading_finished = all_loading_finished(); + + asset::AssetItemTree &tree = *get_static_item_tree(); + tree = build_catalog_tree(*C); + if (tree.catalogs.is_empty() && loading_finished) { + return; + } + + uiItemS(layout); + + if (!loading_finished) { + uiItemL(layout, IFACE_("Loading Asset Libraries"), ICON_INFO); + } + + static Set all_builtin_menus = [&]() { + Set menus; + if (ELEM(object->type, OB_MESH, OB_CURVES_LEGACY, OB_FONT, OB_SURF, OB_LATTICE)) { + menus.add_new("Edit"); + } + if (ELEM(object->type, OB_MESH, OB_CURVES_LEGACY, OB_FONT, OB_SURF, OB_VOLUME)) { + menus.add_new("Generate"); + } + if (ELEM(object->type, OB_MESH, OB_CURVES_LEGACY, OB_FONT, OB_SURF, OB_LATTICE, OB_VOLUME)) { + menus.add_new("Deform"); + } + if (ELEM(object->type, OB_MESH, OB_CURVES_LEGACY, OB_FONT, OB_SURF, OB_LATTICE)) { + menus.add_new("Physics"); + } + return menus; + }(); + + asset_system::AssetLibrary *all_library = ED_assetlist_library_get_once_available( + asset_system::all_library_reference()); + if (!all_library) { + return; + } + + tree.catalogs.foreach_root_item([&](asset_system::AssetCatalogTreeItem &item) { + if (!all_builtin_menus.contains(item.get_name())) { + asset::draw_menu_for_catalog( + screen, *all_library, item, "OBJECT_MT_add_modifier_catalog_assets", *layout); + } + }); +} + +static bNodeTree *get_node_group(const bContext &C, PointerRNA &ptr, ReportList *reports) +{ + const asset_system::AssetRepresentation *asset = + asset::operator_asset_reference_props_get_asset_from_all_library(C, ptr, reports); + if (!asset) { + return nullptr; + } + Main &bmain = *CTX_data_main(&C); + bNodeTree *node_group = reinterpret_cast( + asset::asset_local_id_ensure_imported(bmain, *asset)); + if (!node_group) { + return nullptr; + } + if (node_group->type != NTREE_GEOMETRY) { + if (reports) { + BKE_report(reports, RPT_ERROR, "Asset is not a geometry node group"); + } + return nullptr; + } + return node_group; +} + +static int modifier_add_asset_exec(bContext *C, wmOperator *op) +{ + Main *bmain = CTX_data_main(C); + Scene *scene = CTX_data_scene(C); + Object *object = ED_object_active_context(C); + + NodesModifierData *nmd = reinterpret_cast( + ED_object_modifier_add(op->reports, bmain, scene, object, nullptr, eModifierType_Nodes)); + if (!nmd) { + return OPERATOR_CANCELLED; + } + + bNodeTree *node_group = get_node_group(*C, *op->ptr, op->reports); + if (!node_group) { + return OPERATOR_CANCELLED; + } + + nmd->node_group = node_group; + id_us_plus(&node_group->id); + MOD_nodes_update_interface(object, nmd); + + STRNCPY(nmd->modifier.name, DATA_(node_group->id.name + 2)); + + WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, object); + + return OPERATOR_FINISHED; +} + +static std::string modifier_add_asset_get_description(bContext *C, + wmOperatorType * /*ot*/, + PointerRNA *ptr) +{ + const asset_system::AssetRepresentation *asset = + asset::operator_asset_reference_props_get_asset_from_all_library(*C, *ptr, nullptr); + if (!asset) { + return ""; + } + if (!asset->get_metadata().description) { + return ""; + } + return TIP_(asset->get_metadata().description); +} + +static void OBJECT_OT_modifier_add_asset(wmOperatorType *ot) +{ + ot->name = "Add Modifier"; + ot->description = "Add a procedural operation/effect to the active object"; + ot->idname = "OBJECT_OT_modifier_add_asset"; + + ot->exec = modifier_add_asset_exec; + ot->poll = ED_operator_object_active_editable; + ot->get_description = modifier_add_asset_get_description; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + asset::operator_asset_reference_props_register(*ot->srna); +} + +static MenuType modifier_add_catalog_assets_menu_type() +{ + MenuType type{}; + STRNCPY(type.idname, "OBJECT_MT_add_modifier_catalog_assets"); + type.draw = catalog_assets_draw; + type.listener = asset::asset_reading_region_listen_fn; + type.context_dependent = true; + return type; +} + +static MenuType modifier_add_root_catalogs_menu_type() +{ + MenuType type{}; + STRNCPY(type.idname, "OBJECT_MT_modifier_add_root_catalogs"); + type.draw = root_catalogs_draw; + type.listener = asset::asset_reading_region_listen_fn; + type.context_dependent = true; + return type; +} + +void object_modifier_add_asset_register() +{ + WM_menutype_add(MEM_new(__func__, modifier_add_catalog_assets_menu_type())); + WM_menutype_add(MEM_new(__func__, modifier_add_root_catalogs_menu_type())); + WM_operatortype_append(OBJECT_OT_modifier_add_asset); +} + +void ui_template_modifier_asset_menu_items(uiLayout &layout, + bContext &C, + const StringRef catalog_path) +{ + using namespace blender; + using namespace blender::ed; + using namespace blender::ed::object; + bScreen &screen = *CTX_wm_screen(&C); + asset::AssetItemTree &tree = *get_static_item_tree(); + const asset_system::AssetCatalogTreeItem *item = tree.catalogs.find_root_item(catalog_path); + if (!item) { + return; + } + asset_system::AssetLibrary *all_library = ED_assetlist_library_get_once_available( + asset_system::all_library_reference()); + if (!all_library) { + return; + } + PointerRNA path_ptr = asset::persistent_catalog_path_rna_pointer(screen, *all_library, *item); + if (path_ptr.data == nullptr) { + return; + } + uiItemS(&layout); + uiLayout *col = uiLayoutColumn(&layout, false); + uiLayoutSetContextPointer(col, "asset_catalog_path", &path_ptr); + uiItemMContents(col, "OBJECT_MT_add_modifier_catalog_assets"); +} + +} // namespace blender::ed::object diff --git a/source/blender/editors/object/object_intern.h b/source/blender/editors/object/object_intern.h index dd24e8804d1..102b879b215 100644 --- a/source/blender/editors/object/object_intern.h +++ b/source/blender/editors/object/object_intern.h @@ -369,3 +369,13 @@ void OBJECT_OT_datalayout_transfer(struct wmOperatorType *ot); #ifdef __cplusplus } #endif + +#ifdef __cplusplus + +namespace blender::ed::object { + +void object_modifier_add_asset_register(); + +} + +#endif diff --git a/source/blender/editors/object/object_ops.cc b/source/blender/editors/object/object_ops.cc index 998e21ff1d9..bbfc3628261 100644 --- a/source/blender/editors/object/object_ops.cc +++ b/source/blender/editors/object/object_ops.cc @@ -28,6 +28,7 @@ void ED_operatortypes_object() { + using namespace blender::ed::object; WM_operatortype_append(OBJECT_OT_location_clear); WM_operatortype_append(OBJECT_OT_rotation_clear); WM_operatortype_append(OBJECT_OT_scale_clear); @@ -288,6 +289,8 @@ void ED_operatortypes_object() WM_operatortype_append(OBJECT_OT_light_linking_blockers_link); WM_operatortype_append(OBJECT_OT_light_linking_unlink_from_collection); + + object_modifier_add_asset_register(); } void ED_operatormacros_object() diff --git a/source/blender/editors/space_node/add_menu_assets.cc b/source/blender/editors/space_node/add_menu_assets.cc index 4cfe6d950d0..97a2528d54a 100644 --- a/source/blender/editors/space_node/add_menu_assets.cc +++ b/source/blender/editors/space_node/add_menu_assets.cc @@ -22,6 +22,7 @@ #include "RNA_access.hh" #include "ED_asset.hh" +#include "ED_asset_menu_utils.hh" #include "ED_screen.hh" #include "node_intern.hh" @@ -99,17 +100,9 @@ static void node_add_catalog_assets_draw(const bContext *C, Menu *menu) return; } - catalog_item->foreach_child([&](asset_system::AssetCatalogTreeItem &child_item) { - PointerRNA path_ptr = asset::persistent_catalog_path_rna_pointer( - screen, *all_library, child_item); - if (path_ptr.data == nullptr) { - return; - } - - uiLayout *col = uiLayoutColumn(layout, false); - uiLayoutSetContextPointer(col, "asset_catalog_path", &path_ptr); - uiItemM( - col, "NODE_MT_node_add_catalog_assets", IFACE_(child_item.get_name().c_str()), ICON_NONE); + catalog_item->foreach_child([&](asset_system::AssetCatalogTreeItem &item) { + asset::draw_menu_for_catalog( + screen, *all_library, item, "NODE_MT_node_add_catalog_assets", *layout); }); } @@ -181,16 +174,10 @@ static void add_root_catalogs_draw(const bContext *C, Menu *menu) } tree.catalogs.foreach_root_item([&](asset_system::AssetCatalogTreeItem &item) { - if (all_builtin_menus.contains(item.get_name())) { - return; + if (!all_builtin_menus.contains(item.get_name())) { + asset::draw_menu_for_catalog( + screen, *all_library, item, "NODE_MT_node_add_catalog_assets", *layout); } - PointerRNA path_ptr = asset::persistent_catalog_path_rna_pointer(screen, *all_library, item); - if (path_ptr.data == nullptr) { - return; - } - uiLayout *col = uiLayoutColumn(layout, false); - uiLayoutSetContextPointer(col, "asset_catalog_path", &path_ptr); - uiItemM(col, "NODE_MT_node_add_catalog_assets", IFACE_(item.get_name().c_str()), ICON_NONE); }); } diff --git a/source/blender/editors/util/CMakeLists.txt b/source/blender/editors/util/CMakeLists.txt index 6d801f08667..2f3d384f2b6 100644 --- a/source/blender/editors/util/CMakeLists.txt +++ b/source/blender/editors/util/CMakeLists.txt @@ -40,6 +40,7 @@ set(SRC ../include/ED_anim_api.hh ../include/ED_armature.hh ../include/ED_asset.hh + ../include/ED_asset_menu_utils.hh ../include/ED_buttons.hh ../include/ED_clip.hh ../include/ED_curve.hh diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index 8b6a9e34928..ca227d2a3a9 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -897,6 +897,7 @@ typedef enum GeometryNodeAssetTraitFlag { GEO_NODE_ASSET_MESH = (1 << 3), GEO_NODE_ASSET_CURVE = (1 << 4), GEO_NODE_ASSET_POINT_CLOUD = (1 << 5), + GEO_NODE_ASSET_MODIFIER = (1 << 6), } GeometryNodeAssetTraitFlag; ENUM_OPERATORS(GeometryNodeAssetTraitFlag, GEO_NODE_ASSET_POINT_CLOUD); diff --git a/source/blender/makesrna/intern/rna_nodetree.cc b/source/blender/makesrna/intern/rna_nodetree.cc index fdde2e9dd1e..ecbe876b7d7 100644 --- a/source/blender/makesrna/intern/rna_nodetree.cc +++ b/source/blender/makesrna/intern/rna_nodetree.cc @@ -1785,6 +1785,15 @@ static void rna_GeometryNodeTree_is_tool_set(PointerRNA *ptr, bool value) geometry_node_asset_trait_flag_set(ptr, GEO_NODE_ASSET_TOOL, value); } +static bool rna_GeometryNodeTree_is_modifier_get(PointerRNA *ptr) +{ + return geometry_node_asset_trait_flag_get(ptr, GEO_NODE_ASSET_MODIFIER); +} +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_edit_get(PointerRNA *ptr) { return geometry_node_asset_trait_flag_get(ptr, GEO_NODE_ASSET_EDIT); @@ -10308,6 +10317,14 @@ static void rna_def_geometry_nodetree(BlenderRNA *brna) prop, "rna_GeometryNodeTree_is_tool_get", "rna_GeometryNodeTree_is_tool_set"); RNA_def_property_update(prop, NC_NODE | ND_DISPLAY, "rna_NodeTree_update"); + prop = RNA_def_property(srna, "is_modifier", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, nullptr, "flag", GEO_NODE_ASSET_MODIFIER); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_ui_text(prop, "Modifier", "The node group is used as a geometry modifier"); + RNA_def_property_boolean_funcs( + prop, "rna_GeometryNodeTree_is_modifier_get", "rna_GeometryNodeTree_is_modifier_set"); + RNA_def_property_update(prop, NC_NODE | ND_DISPLAY, "rna_NodeTree_update"); + 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/makesrna/intern/rna_ui_api.cc b/source/blender/makesrna/intern/rna_ui_api.cc index d1c4d031179..434f3e68a8a 100644 --- a/source/blender/makesrna/intern/rna_ui_api.cc +++ b/source/blender/makesrna/intern/rna_ui_api.cc @@ -38,6 +38,7 @@ const EnumPropertyItem rna_enum_icon_items[] = { # include "DNA_asset_types.h" # include "ED_geometry.hh" +# include "ED_object.hh" const char *rna_translate_ui_text( const char *text, const char *text_ctxt, StructRNA *type, PropertyRNA *prop, bool translate) @@ -786,6 +787,16 @@ static void rna_uiLayout_template_node_operator_asset_menu_items(uiLayout *layou } } +static void rna_uiLayout_template_modifier_asset_menu_items(uiLayout *layout, + bContext *C, + const char *catalog_path) +{ + if (U.experimental.use_node_group_operators) { + blender::ed::object::ui_template_modifier_asset_menu_items( + *layout, *C, blender::StringRef(catalog_path)); + } +} + static void rna_uiLayout_template_node_operator_root_items(uiLayout *layout, bContext *C) { if (U.experimental.use_node_group_operators) { @@ -1893,6 +1904,12 @@ void RNA_api_ui_layout(StructRNA *srna) RNA_def_function_flag(func, FUNC_USE_CONTEXT); parm = RNA_def_string(func, "catalog_path", nullptr, 0, "", ""); + func = RNA_def_function(srna, + "template_modifier_asset_menu_items", + "rna_uiLayout_template_modifier_asset_menu_items"); + RNA_def_function_flag(func, FUNC_USE_CONTEXT); + parm = RNA_def_string(func, "catalog_path", nullptr, 0, "", ""); + func = RNA_def_function(srna, "template_node_operator_asset_menu_items", "rna_uiLayout_template_node_operator_asset_menu_items");