diff --git a/scripts/startup/bl_ui/space_sequencer.py b/scripts/startup/bl_ui/space_sequencer.py index 2030ecdf031..52b629db9bd 100644 --- a/scripts/startup/bl_ui/space_sequencer.py +++ b/scripts/startup/bl_ui/space_sequencer.py @@ -801,13 +801,15 @@ class SEQUENCER_MT_add_scene(Menu): layout.operator_context = 'INVOKE_REGION_WIN' layout.operator("sequencer.scene_strip_add_new", text="Empty Scene", icon='ADD').type = 'EMPTY' + layout.menu_contents("SEQUENCER_MT_scene_add_root_catalogs") + bpy_data_scenes_len = len(bpy.data.scenes) if bpy_data_scenes_len > 10: - layout.separator() + layout.label(text="Scenes", icon='NONE') layout.operator_context = 'INVOKE_DEFAULT' layout.operator("sequencer.scene_strip_add", text="Scene...", icon='SCENE_DATA') elif bpy_data_scenes_len > 1: - layout.separator() + layout.label(text="Scenes", icon='NONE') scene = context.scene for sc_item in bpy.data.scenes: if sc_item == scene: diff --git a/source/blender/editors/space_sequencer/CMakeLists.txt b/source/blender/editors/space_sequencer/CMakeLists.txt index 27121892c3b..5172b592eb7 100644 --- a/source/blender/editors/space_sequencer/CMakeLists.txt +++ b/source/blender/editors/space_sequencer/CMakeLists.txt @@ -15,6 +15,7 @@ set(INC_SYS set(SRC sequencer_add.cc + sequencer_add_menu_scene_assets.cc sequencer_buttons.cc sequencer_channels_draw.cc sequencer_channels_edit.cc @@ -45,6 +46,7 @@ set(SRC ) set(LIB + PRIVATE bf::asset_system PRIVATE bf::blenfont PRIVATE bf::blenkernel PRIVATE bf::blenlib diff --git a/source/blender/editors/space_sequencer/sequencer_add.cc b/source/blender/editors/space_sequencer/sequencer_add.cc index a5c5713dfe1..c0dc1e3b45a 100644 --- a/source/blender/editors/space_sequencer/sequencer_add.cc +++ b/source/blender/editors/space_sequencer/sequencer_add.cc @@ -10,6 +10,8 @@ #include #include +#include "AS_asset_representation.hh" + #include "DNA_sequence_types.h" #include "MEM_guardedalloc.h" @@ -49,8 +51,9 @@ #include "SEQ_time.hh" #include "SEQ_transform.hh" +#include "ED_asset.hh" +#include "ED_asset_menu_utils.hh" #include "ED_scene.hh" -/* For menu, popup, icons, etc. */ #include "ED_screen.hh" #include "ED_sequencer.hh" #include "ED_time_scrub_ui.hh" @@ -1931,4 +1934,101 @@ void SEQUENCER_OT_effect_strip_add(wmOperatorType *ot) RNA_def_property_subtype(prop, PROP_COLOR_GAMMA); } +static Scene *sequencer_add_scene_asset(const bContext &C, + const asset_system::AssetRepresentation &asset, + ReportList & /*reports*/) +{ + Main &bmain = *CTX_data_main(&C); + Scene *scene_asset = reinterpret_cast( + asset::asset_local_id_ensure_imported(bmain, asset)); + return scene_asset; +} + +static wmOperatorStatus sequencer_add_scene_asset_invoke(bContext *C, + wmOperator *op, + const wmEvent *event) +{ + Main *bmain = CTX_data_main(C); + Scene *scene = CTX_data_sequencer_scene(C); + if (!scene) { + return OPERATOR_CANCELLED; + } + Editing *ed = seq::editing_ensure(scene); + BLI_assert(ed != nullptr); + + sequencer_disable_one_time_properties(C, op); + + sequencer_generic_invoke_xy__internal(C, op, 0, STRIP_TYPE_SCENE, event); + const asset_system::AssetRepresentation *asset = + asset::operator_asset_reference_props_get_asset_from_all_library(*C, *op->ptr, op->reports); + if (!asset) { + return OPERATOR_CANCELLED; + } + + Scene *scene_asset = sequencer_add_scene_asset(*C, *asset, *op->reports); + if (!scene_asset) { + return OPERATOR_CANCELLED; + } + + const char *error_msg; + if (!have_free_channels(C, op, 1, &error_msg)) { + BKE_report(op->reports, RPT_ERROR, error_msg); + return OPERATOR_CANCELLED; + } + + if (RNA_boolean_get(op->ptr, "replace_sel")) { + deselect_all_strips(scene); + } + + seq::LoadData load_data; + load_data_init_from_operator(&load_data, C, op); + load_data.scene = scene_asset; + + Strip *strip = seq::add_scene_strip(scene, ed->seqbasep, &load_data); + seq_load_apply_generic_options(C, op, strip); + + DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS); + DEG_relations_tag_update(bmain); + sequencer_select_do_updates(C, scene); + + if (RNA_boolean_get(op->ptr, "move_strips")) { + move_strips(C); + } + + return OPERATOR_FINISHED; +} + +static std::string sequencer_add_scene_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 ""; + } + const AssetMetaData &asset_data = asset->get_metadata(); + if (!asset_data.description) { + return ""; + } + return TIP_(asset_data.description); +} + +void SEQUENCER_OT_add_scene_strip_from_scene_asset(wmOperatorType *ot) +{ + ot->name = "Add Scene Asset"; + ot->description = "Add a scene strip from a scene asset"; + ot->idname = "SEQUENCER_OT_add_scene_strip_from_scene_asset"; + + ot->invoke = sequencer_add_scene_asset_invoke; + ot->poll = ED_operator_sequencer_active_editable; + ot->get_description = sequencer_add_scene_asset_get_description; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL; + + sequencer_generic_props__internal(ot, SEQPROP_STARTFRAME | SEQPROP_MOVE); + + asset::operator_asset_reference_props_register(*ot->srna); +} + } // namespace blender::ed::vse diff --git a/source/blender/editors/space_sequencer/sequencer_add_menu_scene_assets.cc b/source/blender/editors/space_sequencer/sequencer_add_menu_scene_assets.cc new file mode 100644 index 00000000000..97353c9a751 --- /dev/null +++ b/source/blender/editors/space_sequencer/sequencer_add_menu_scene_assets.cc @@ -0,0 +1,207 @@ +/* SPDX-FileCopyrightText: 2025 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_space_types.h" +#include "DNA_workspace_types.h" + +#include "BKE_asset.hh" +#include "BKE_idprop.hh" +#include "BKE_screen.hh" + +#include "BLT_translation.hh" + +#include "RNA_access.hh" + +#include "ED_asset.hh" +#include "ED_asset_menu_utils.hh" +#include "ED_sequencer.hh" + +#include "UI_interface.hh" +#include "UI_interface_layout.hh" + +#include "sequencer_intern.hh" + +namespace blender::ed::vse { + +static bool sequencer_add_menu_poll(const bContext *C, MenuType * /*mt*/) +{ + /* Add menu is not accessible from the VSE preview. */ + SpaceSeq *sseq = CTX_wm_space_seq(C); + return sseq && ELEM(sseq->view, SEQ_VIEW_SEQUENCE, SEQ_VIEW_SEQUENCE_PREVIEW); +} + +static bool all_loading_finished() +{ + AssetLibraryReference all_library_ref = asset_system::all_library_reference(); + return asset::list::is_loaded(&all_library_ref); +} + +static asset::AssetItemTree build_catalog_tree(const bContext &C) +{ + asset::AssetFilterSettings type_filter{}; + type_filter.id_types = FILTER_ID_SCE; + const AssetLibraryReference library = asset_system::all_library_reference(); + asset_system::all_library_reload_catalogs_if_dirty(); + return asset::build_filtered_all_catalog_tree(library, C, type_filter, {}); +} + +static void sequencer_add_catalog_assets_draw(const bContext *C, Menu *menu) +{ + SpaceSeq *sseq = CTX_wm_space_seq(C); + if (!sseq || !ELEM(sseq->view, SEQ_VIEW_SEQUENCE, SEQ_VIEW_SEQUENCE_PREVIEW)) { + return; + } + if (!sseq->runtime->assets_for_menu) { + sseq->runtime->assets_for_menu = std::make_shared( + build_catalog_tree(*C)); + return; + } + asset::AssetItemTree &tree = *sseq->runtime->assets_for_menu; + + const std::optional menu_path = CTX_data_string_get( + C, "asset_catalog_path"); + if (!menu_path) { + return; + } + const Span assets = tree.assets_per_path.lookup( + menu_path->c_str()); + const asset_system::AssetCatalogTreeItem *catalog_item = tree.catalogs.find_item( + menu_path->c_str()); + BLI_assert(catalog_item != nullptr); + + if (assets.is_empty() && !catalog_item->has_children()) { + return; + } + + uiLayout *layout = menu->layout; + bool add_separator = true; + + for (const asset_system::AssetRepresentation *asset : assets) { + if (add_separator) { + layout->separator(); + add_separator = false; + } + PointerRNA op_ptr = layout->op("SEQUENCER_OT_add_scene_strip_from_scene_asset", + IFACE_(asset->get_name()), + ICON_NONE, + wm::OpCallContext::InvokeRegionWin, + UI_ITEM_NONE); + asset::operator_asset_reference_props_set(*asset, op_ptr); + } + + catalog_item->foreach_child([&](const asset_system::AssetCatalogTreeItem &item) { + if (add_separator) { + layout->separator(); + add_separator = false; + } + asset::draw_menu_for_catalog(item, "SEQUENCER_MT_scene_add_catalog_assets", *layout); + }); +} + +static void sequencer_add_unassigned_assets_draw(const bContext *C, Menu *menu) +{ + SpaceSeq *sseq = CTX_wm_space_seq(C); + if (!sseq || !ELEM(sseq->view, SEQ_VIEW_SEQUENCE, SEQ_VIEW_SEQUENCE_PREVIEW)) { + return; + } + if (!sseq->runtime->assets_for_menu) { + sseq->runtime->assets_for_menu = std::make_shared( + build_catalog_tree(*C)); + return; + } + asset::AssetItemTree &tree = *sseq->runtime->assets_for_menu; + for (const asset_system::AssetRepresentation *asset : tree.unassigned_assets) { + PointerRNA op_ptr = menu->layout->op("SEQUENCER_OT_add_scene_strip_from_scene_asset", + IFACE_(asset->get_name()), + ICON_NONE, + wm::OpCallContext::InvokeRegionWin, + UI_ITEM_NONE); + BLI_assert(op_ptr.data != nullptr); + asset::operator_asset_reference_props_set(*asset, op_ptr); + } +} + +static void sequencer_add_root_catalogs_draw(const bContext *C, Menu *menu) +{ + SpaceSeq *sseq = CTX_wm_space_seq(C); + if (!sseq || !ELEM(sseq->view, SEQ_VIEW_SEQUENCE, SEQ_VIEW_SEQUENCE_PREVIEW)) { + return; + } + + sseq->runtime->assets_for_menu = std::make_shared(build_catalog_tree(*C)); + const bool loading_finished = all_loading_finished(); + + asset::AssetItemTree &tree = *sseq->runtime->assets_for_menu; + if (tree.catalogs.is_empty() && loading_finished && tree.unassigned_assets.is_empty()) { + return; + } + + uiLayout *layout = menu->layout; + + layout->separator(); + + layout->label(IFACE_("Assets"), ICON_NONE); + + if (!loading_finished) { + layout->label(IFACE_("Loading Asset Libraries"), ICON_INFO); + } + + tree.catalogs.foreach_root_item([&](const asset_system::AssetCatalogTreeItem &item) { + asset::draw_menu_for_catalog(item, "SEQUENCER_MT_scene_add_catalog_assets", *layout); + }); + + if (!tree.unassigned_assets.is_empty()) { + layout->separator(); + layout->menu( + "SEQUENCER_MT_scene_add_unassigned_assets", IFACE_("Unassigned"), ICON_FILE_HIDDEN); + } + + /* We expect this to be drawn before another section in the menu. */ + layout->separator(); +} + +MenuType add_catalog_assets_menu_type() +{ + MenuType type{}; + STRNCPY(type.idname, "SEQUENCER_MT_scene_add_catalog_assets"); + type.poll = sequencer_add_menu_poll; + type.draw = sequencer_add_catalog_assets_draw; + type.listener = asset::list::asset_reading_region_listen_fn; + type.flag = MenuTypeFlag::ContextDependent; + return type; +} + +MenuType add_unassigned_assets_menu_type() +{ + MenuType type{}; + STRNCPY(type.idname, "SEQUENCER_MT_scene_add_unassigned_assets"); + type.poll = sequencer_add_menu_poll; + type.draw = sequencer_add_unassigned_assets_draw; + type.listener = asset::list::asset_reading_region_listen_fn; + type.flag = MenuTypeFlag::ContextDependent; + type.description = N_( + "Scene assets not assigned to a catalog.\n" + "Catalogs can be assigned in the Asset Browser"); + return type; +} + +MenuType add_root_catalogs_menu_type() +{ + MenuType type{}; + STRNCPY(type.idname, "SEQUENCER_MT_scene_add_root_catalogs"); + type.poll = sequencer_add_menu_poll; + type.draw = sequencer_add_root_catalogs_draw; + type.listener = asset::list::asset_reading_region_listen_fn; + return type; +} + +} // namespace blender::ed::vse diff --git a/source/blender/editors/space_sequencer/sequencer_intern.hh b/source/blender/editors/space_sequencer/sequencer_intern.hh index 07fe5b99dad..b136ceb8235 100644 --- a/source/blender/editors/space_sequencer/sequencer_intern.hh +++ b/source/blender/editors/space_sequencer/sequencer_intern.hh @@ -47,6 +47,10 @@ struct ScrArea; struct Editing; struct ListBase; +namespace blender::ed::asset { +struct AssetItemTree; +} + namespace blender::ed::vse { class SeqQuadsBatch; @@ -60,6 +64,8 @@ struct SpaceSeq_Runtime : public NonCopyable { SeqScopes scopes; + std::shared_ptr assets_for_menu; + SpaceSeq_Runtime() = default; ~SpaceSeq_Runtime(); }; @@ -286,6 +292,7 @@ void SEQUENCER_OT_mask_strip_add(wmOperatorType *ot); void SEQUENCER_OT_sound_strip_add(wmOperatorType *ot); void SEQUENCER_OT_image_strip_add(wmOperatorType *ot); void SEQUENCER_OT_effect_strip_add(wmOperatorType *ot); +void SEQUENCER_OT_add_scene_strip_from_scene_asset(wmOperatorType *ot); /* `sequencer_drag_drop.cc` */ @@ -398,4 +405,9 @@ blender::Vector sequencer_visible_strips_get(const Scene *scene, const wmOperatorStatus sequencer_clipboard_copy_exec(bContext *C, wmOperator *op); wmOperatorStatus sequencer_clipboard_paste_exec(bContext *C, wmOperator *op); +/* `sequencer_add_menu_scene_assets.cc` */ +MenuType add_catalog_assets_menu_type(); +MenuType add_unassigned_assets_menu_type(); +MenuType add_root_catalogs_menu_type(); + } // namespace blender::ed::vse diff --git a/source/blender/editors/space_sequencer/sequencer_ops.cc b/source/blender/editors/space_sequencer/sequencer_ops.cc index 191fa3cf84d..105629e78c4 100644 --- a/source/blender/editors/space_sequencer/sequencer_ops.cc +++ b/source/blender/editors/space_sequencer/sequencer_ops.cc @@ -116,6 +116,7 @@ void sequencer_operatortypes() WM_operatortype_append(SEQUENCER_OT_sound_strip_add); WM_operatortype_append(SEQUENCER_OT_image_strip_add); WM_operatortype_append(SEQUENCER_OT_effect_strip_add); + WM_operatortype_append(SEQUENCER_OT_add_scene_strip_from_scene_asset); /* sequencer_modifiers.c */ WM_operatortype_append(SEQUENCER_OT_strip_modifier_add); diff --git a/source/blender/editors/space_sequencer/space_sequencer.cc b/source/blender/editors/space_sequencer/space_sequencer.cc index 078399b89cd..cf045a8e854 100644 --- a/source/blender/editors/space_sequencer/space_sequencer.cc +++ b/source/blender/editors/space_sequencer/space_sequencer.cc @@ -1244,6 +1244,10 @@ void ED_spacetype_sequencer() art = ED_area_type_hud(st->spaceid); BLI_addhead(&st->regiontypes, art); + WM_menutype_add(MEM_dupallocN(__func__, add_catalog_assets_menu_type())); + WM_menutype_add(MEM_dupallocN(__func__, add_unassigned_assets_menu_type())); + WM_menutype_add(MEM_dupallocN(__func__, add_root_catalogs_menu_type())); + BKE_spacetype_register(std::move(st)); /* Set the sequencer callback when not in background mode. */