Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSybren A. Stüvel <sybren@blender.org>2021-11-23 14:29:44 +0300
committerSybren A. Stüvel <sybren@blender.org>2021-11-23 14:29:44 +0300
commitcd2849c89b65131cc381a1bf9ab75527afd51a4f (patch)
treebac399cca89d942f7c1fd384ff003975887e03c0
parentb02ac2d8be38d198edfece7347919d67f7cc7275 (diff)
Asset Browser: add operator for installing asset bundles
Add an operator "Copy to Asset Library" for installing asset bundles into already-existing asset libraries. The operator is shown when: - the "Current File" library is selected, - the blend file name matches `*_bundle.blend`, and - the file is not already located in an asset library. The user can select a target asset library, then gets a "Save As" dialogue box to select where in that library the file should be saved. This allows for renaming, saving in a subdirectory, etc. The Asset Catalogs from the asset bundle are merged into the target asset library. The operator will refuse to run when external files are referenced. This is not done in its poll function, as it's quite an extensive operator (it loops over all ID datablocks). Reviewed by: Severin Differential Revision: https://developer.blender.org/D13312
-rw-r--r--source/blender/blenkernel/BKE_asset_catalog.hh14
-rw-r--r--source/blender/blenkernel/intern/asset_catalog.cc26
-rw-r--r--source/blender/blenloader/BLO_writefile.h8
-rw-r--r--source/blender/editors/asset/ED_asset_library.h3
-rw-r--r--source/blender/editors/asset/intern/asset_library_reference_enum.cc33
-rw-r--r--source/blender/editors/asset/intern/asset_ops.cc246
-rw-r--r--source/blender/editors/include/ED_fileselect.h1
-rw-r--r--source/blender/editors/space_file/file_panels.c16
-rw-r--r--source/blender/editors/space_file/filesel.c9
-rw-r--r--source/blender/makesrna/intern/rna_asset.c2
-rw-r--r--source/blender/windowmanager/WM_api.h2
-rw-r--r--source/blender/windowmanager/intern/wm_event_system.c10
12 files changed, 353 insertions, 17 deletions
diff --git a/source/blender/blenkernel/BKE_asset_catalog.hh b/source/blender/blenkernel/BKE_asset_catalog.hh
index 766a3f34a66..bdb77571711 100644
--- a/source/blender/blenkernel/BKE_asset_catalog.hh
+++ b/source/blender/blenkernel/BKE_asset_catalog.hh
@@ -98,6 +98,15 @@ class AssetCatalogService {
bool write_to_disk(const CatalogFilePath &blend_file_path);
/**
+ * Ensure that the next call to #on_blend_save_post() will choose a new location for the CDF
+ * suitable for the location of the blend file (regardless of where the current catalogs come
+ * from), and that catalogs will be merged with already-existing ones in that location.
+ *
+ * Use this for a "Save as..." that has to write the catalogs to the new blend file location,
+ * instead of updating the previously read CDF. */
+ void prepare_to_merge_on_write();
+
+ /**
* Merge on-disk changes into the in-memory asset catalogs.
* This should be called before writing the asset catalogs to disk.
*
@@ -238,6 +247,11 @@ class AssetCatalogService {
*/
void create_missing_catalogs();
+ /**
+ * For every catalog, mark it as "dirty".
+ */
+ void tag_all_catalogs_as_unsaved_changes();
+
/* For access by subclasses, as those will not be marked as friend by #AssetCatalogCollection. */
AssetCatalogDefinitionFile *get_catalog_definition_file();
OwningAssetCatalogMap &get_catalogs();
diff --git a/source/blender/blenkernel/intern/asset_catalog.cc b/source/blender/blenkernel/intern/asset_catalog.cc
index 13f66445c46..33405c9aeae 100644
--- a/source/blender/blenkernel/intern/asset_catalog.cc
+++ b/source/blender/blenkernel/intern/asset_catalog.cc
@@ -103,6 +103,14 @@ bool AssetCatalogService::has_unsaved_changes() const
return catalog_collection_->has_unsaved_changes_;
}
+void AssetCatalogService::tag_all_catalogs_as_unsaved_changes()
+{
+ for (auto &catalog : catalog_collection_->catalogs_.values()) {
+ catalog->flags.has_unsaved_changes = true;
+ }
+ catalog_collection_->has_unsaved_changes_ = true;
+}
+
bool AssetCatalogService::is_empty() const
{
BLI_assert(catalog_collection_);
@@ -491,6 +499,24 @@ bool AssetCatalogService::write_to_disk_ex(const CatalogFilePath &blend_file_pat
return catalog_collection_->catalog_definition_file_->write_to_disk();
}
+void AssetCatalogService::prepare_to_merge_on_write()
+{
+ /* TODO(Sybren): expand to support multiple CDFs. */
+
+ if (!catalog_collection_->catalog_definition_file_) {
+ /* There is no CDF connected, so it's a no-op. */
+ return;
+ }
+
+ /* Remove any association with the CDF, so that a new location will be chosen
+ * when the blend file is saved. */
+ catalog_collection_->catalog_definition_file_.reset();
+
+ /* Mark all in-memory catalogs as "dirty", to force them to be kept around on
+ * the next "load-merge-write" cycle. */
+ tag_all_catalogs_as_unsaved_changes();
+}
+
CatalogFilePath AssetCatalogService::find_suitable_cdf_path_for_writing(
const CatalogFilePath &blend_file_path)
{
diff --git a/source/blender/blenloader/BLO_writefile.h b/source/blender/blenloader/BLO_writefile.h
index 746c663926d..bac0119a368 100644
--- a/source/blender/blenloader/BLO_writefile.h
+++ b/source/blender/blenloader/BLO_writefile.h
@@ -24,6 +24,10 @@
* \brief external writefile function prototypes.
*/
+#ifdef __cplusplus
+extern "C" {
+#endif
+
struct BlendThumbnail;
struct Main;
struct MemFile;
@@ -72,3 +76,7 @@ extern bool BLO_write_file_mem(struct Main *mainvar,
int write_flags);
/** \} */
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/source/blender/editors/asset/ED_asset_library.h b/source/blender/editors/asset/ED_asset_library.h
index 905d097d223..cb055584364 100644
--- a/source/blender/editors/asset/ED_asset_library.h
+++ b/source/blender/editors/asset/ED_asset_library.h
@@ -28,7 +28,8 @@ extern "C" {
int ED_asset_library_reference_to_enum_value(const AssetLibraryReference *library);
AssetLibraryReference ED_asset_library_reference_from_enum_value(int value);
-const struct EnumPropertyItem *ED_asset_library_reference_to_rna_enum_itemf(void);
+const struct EnumPropertyItem *ED_asset_library_reference_to_rna_enum_itemf(
+ bool include_local_library);
#ifdef __cplusplus
}
diff --git a/source/blender/editors/asset/intern/asset_library_reference_enum.cc b/source/blender/editors/asset/intern/asset_library_reference_enum.cc
index c57d121a18f..4c343e1e0aa 100644
--- a/source/blender/editors/asset/intern/asset_library_reference_enum.cc
+++ b/source/blender/editors/asset/intern/asset_library_reference_enum.cc
@@ -99,25 +99,30 @@ AssetLibraryReference ED_asset_library_reference_from_enum_value(int value)
*
* Since this is meant for UI display, skips non-displayable libraries, that is, libraries with an
* empty name or path.
+ *
+ * \param include_local_library whether to include the "Current File" library or not.
*/
-const EnumPropertyItem *ED_asset_library_reference_to_rna_enum_itemf()
+const EnumPropertyItem *ED_asset_library_reference_to_rna_enum_itemf(
+ const bool include_local_library)
{
- const EnumPropertyItem predefined_items[] = {
- /* For the future. */
- // {ASSET_REPO_BUNDLED, "BUNDLED", 0, "Bundled", "Show the default user assets"},
- {ASSET_LIBRARY_LOCAL,
- "LOCAL",
- ICON_BLENDER,
- "Current File",
- "Show the assets currently available in this Blender session"},
- {0, nullptr, 0, nullptr, nullptr},
- };
-
EnumPropertyItem *item = nullptr;
int totitem = 0;
- /* Add predefined items. */
- RNA_enum_items_add(&item, &totitem, predefined_items);
+ if (include_local_library) {
+ const EnumPropertyItem predefined_items[] = {
+ /* For the future. */
+ // {ASSET_REPO_BUNDLED, "BUNDLED", 0, "Bundled", "Show the default user assets"},
+ {ASSET_LIBRARY_LOCAL,
+ "LOCAL",
+ ICON_BLENDER,
+ "Current File",
+ "Show the assets currently available in this Blender session"},
+ {0, nullptr, 0, nullptr, nullptr},
+ };
+
+ /* Add predefined items. */
+ RNA_enum_items_add(&item, &totitem, predefined_items);
+ }
/* Add separator if needed. */
if (!BLI_listbase_is_empty(&U.asset_libraries)) {
diff --git a/source/blender/editors/asset/intern/asset_ops.cc b/source/blender/editors/asset/intern/asset_ops.cc
index d2fd8ab88a4..dccea2c01ea 100644
--- a/source/blender/editors/asset/intern/asset_ops.cc
+++ b/source/blender/editors/asset/intern/asset_ops.cc
@@ -21,16 +21,23 @@
#include "BKE_asset.h"
#include "BKE_asset_catalog.hh"
#include "BKE_asset_library.hh"
+#include "BKE_bpath.h"
#include "BKE_context.h"
+#include "BKE_global.h"
#include "BKE_lib_id.h"
#include "BKE_main.h"
+#include "BKE_preferences.h"
#include "BKE_report.h"
+#include "BLI_fileops.h"
+#include "BLI_fnmatch.h"
+#include "BLI_path_util.h"
#include "BLI_string_ref.hh"
#include "BLI_vector.hh"
#include "ED_asset.h"
#include "ED_asset_catalog.hh"
+#include "ED_util.h"
/* XXX needs access to the file list, should all be done via the asset system in future. */
#include "ED_fileselect.h"
@@ -40,6 +47,10 @@
#include "WM_api.h"
#include "WM_types.h"
+#include "DNA_space_types.h"
+
+#include "BLO_writefile.h"
+
using namespace blender;
/* -------------------------------------------------------------------- */
@@ -650,6 +661,240 @@ static void ASSET_OT_catalogs_save(struct wmOperatorType *ot)
/* -------------------------------------------------------------------- */
+static bool could_be_asset_bundle(const Main *bmain);
+static const bUserAssetLibrary *selected_asset_library(struct wmOperator *op);
+static bool is_contained_in_selected_asset_library(struct wmOperator *op, const char *filepath);
+static bool set_filepath_for_asset_lib(const Main *bmain, struct wmOperator *op);
+static bool has_external_files(Main *bmain, struct ReportList *reports);
+
+static bool asset_bundle_install_poll(bContext *C)
+{
+ /* This operator only works when the asset browser is set to Current File. */
+ const SpaceFile *sfile = CTX_wm_space_file(C);
+ if (!ED_fileselect_is_local_asset_library(sfile)) {
+ return false;
+ }
+
+ const Main *bmain = CTX_data_main(C);
+ if (!could_be_asset_bundle(bmain)) {
+ return false;
+ }
+
+ /* Check whether this file is already located inside any asset library. */
+ const struct bUserAssetLibrary *asset_lib = BKE_preferences_asset_library_containing_path(
+ &U, bmain->name);
+ if (asset_lib) {
+ return false;
+ }
+
+ return true;
+}
+
+static int asset_bundle_install_invoke(struct bContext *C,
+ struct wmOperator *op,
+ const struct wmEvent * /*event*/)
+{
+ Main *bmain = CTX_data_main(C);
+ if (has_external_files(bmain, op->reports)) {
+ return OPERATOR_CANCELLED;
+ }
+
+ WM_event_add_fileselect(C, op);
+
+ /* Make the "Save As" dialog box default to "${ASSET_LIB_ROOT}/${CURRENT_FILE}.blend". */
+ if (!set_filepath_for_asset_lib(bmain, op)) {
+ return OPERATOR_CANCELLED;
+ }
+
+ return OPERATOR_RUNNING_MODAL;
+}
+
+static int asset_bundle_install_exec(bContext *C, wmOperator *op)
+{
+ Main *bmain = CTX_data_main(C);
+ if (has_external_files(bmain, op->reports)) {
+ return OPERATOR_CANCELLED;
+ }
+
+ /* Check file path, copied from #wm_file_write(). */
+ char filepath[PATH_MAX];
+ RNA_string_get(op->ptr, "filepath", filepath);
+ const size_t len = strlen(filepath);
+
+ if (len == 0) {
+ BKE_report(op->reports, RPT_ERROR, "Path is empty, cannot save");
+ return OPERATOR_CANCELLED;
+ }
+
+ if (len >= FILE_MAX) {
+ BKE_report(op->reports, RPT_ERROR, "Path too long, cannot save");
+ return OPERATOR_CANCELLED;
+ }
+
+ /* Check that the destination is actually contained in the selected asset library. */
+ if (!is_contained_in_selected_asset_library(op, filepath)) {
+ BKE_reportf(op->reports, RPT_ERROR, "Selected path is outside of the selected asset library");
+ return OPERATOR_CANCELLED;
+ }
+
+ WM_cursor_wait(true);
+ bke::AssetCatalogService *cat_service = get_catalog_service(C);
+ /* Store undo step, such that on a failed save the 'prepare_to_merge_on_write' call can be
+ * un-done. */
+ cat_service->undo_push();
+ cat_service->prepare_to_merge_on_write();
+
+ const int operator_result = WM_operator_name_call(
+ C, "WM_OT_save_mainfile", WM_OP_EXEC_DEFAULT, op->ptr);
+ WM_cursor_wait(false);
+
+ if (operator_result != OPERATOR_FINISHED) {
+ cat_service->undo();
+ return operator_result;
+ }
+
+ const bUserAssetLibrary *lib = selected_asset_library(op);
+ BLI_assert_msg(lib, "If the asset library is not known, how did we get here?");
+ BKE_reportf(op->reports,
+ RPT_INFO,
+ "Saved \"%s\" to asset library \"%s\"",
+ BLI_path_basename(bmain->name),
+ lib->name);
+ return OPERATOR_FINISHED;
+}
+
+static const EnumPropertyItem *rna_asset_library_reference_itemf(bContext *UNUSED(C),
+ PointerRNA *UNUSED(ptr),
+ PropertyRNA *UNUSED(prop),
+ bool *r_free)
+{
+ const EnumPropertyItem *items = ED_asset_library_reference_to_rna_enum_itemf(false);
+ if (!items) {
+ *r_free = false;
+ }
+
+ *r_free = true;
+ return items;
+}
+
+static void ASSET_OT_bundle_install(struct wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Copy to Asset Library";
+ ot->description =
+ "Copy the current .blend file into an Asset Library. Only works on standalone .blend files "
+ "(i.e. when no other files are referenced)";
+ ot->idname = "ASSET_OT_bundle_install";
+
+ /* api callbacks */
+ ot->exec = asset_bundle_install_exec;
+ ot->invoke = asset_bundle_install_invoke;
+ ot->poll = asset_bundle_install_poll;
+
+ ot->prop = RNA_def_property(ot->srna, "asset_library_ref", PROP_ENUM, PROP_NONE);
+ RNA_def_property_flag(ot->prop, PROP_HIDDEN);
+ RNA_def_enum_funcs(ot->prop, rna_asset_library_reference_itemf);
+
+ WM_operator_properties_filesel(ot,
+ FILE_TYPE_FOLDER | FILE_TYPE_BLENDER,
+ FILE_BLENDER,
+ FILE_SAVE,
+ WM_FILESEL_FILEPATH,
+ FILE_DEFAULTDISPLAY,
+ FILE_SORT_DEFAULT);
+}
+
+/* Cheap check to see if this is an "asset bundle" just by checking main file name.
+ * A proper check will be done in the exec function, to ensure that no external files will be
+ * referenced. */
+static bool could_be_asset_bundle(const Main *bmain)
+{
+ return fnmatch("*_bundle.blend", bmain->name, FNM_CASEFOLD) == 0;
+}
+
+static const bUserAssetLibrary *selected_asset_library(struct wmOperator *op)
+{
+ const int enum_value = RNA_enum_get(op->ptr, "asset_library_ref");
+ const AssetLibraryReference lib_ref = ED_asset_library_reference_from_enum_value(enum_value);
+ const bUserAssetLibrary *lib = BKE_preferences_asset_library_find_from_index(
+ &U, lib_ref.custom_library_index);
+ return lib;
+}
+
+static bool is_contained_in_selected_asset_library(struct wmOperator *op, const char *filepath)
+{
+ const bUserAssetLibrary *lib = selected_asset_library(op);
+ if (!lib) {
+ return false;
+ }
+ return BLI_path_contains(lib->path, filepath);
+}
+
+/**
+ * Set the "filepath" RNA property based on selected "asset_library_ref".
+ * \return true if ok, false if error.
+ */
+static bool set_filepath_for_asset_lib(const Main *bmain, struct wmOperator *op)
+{
+ /* Find the directory path of the selected asset library. */
+ const bUserAssetLibrary *lib = selected_asset_library(op);
+ if (lib == nullptr) {
+ return false;
+ }
+
+ /* Concatenate the filename of the current blend file. */
+ const char *blend_filename = BLI_path_basename(bmain->name);
+ if (blend_filename == NULL || blend_filename[0] == '\0') {
+ return false;
+ }
+
+ char file_path[PATH_MAX];
+ BLI_join_dirfile(file_path, sizeof(file_path), lib->path, blend_filename);
+ RNA_string_set(op->ptr, "filepath", file_path);
+
+ return true;
+}
+
+struct FileCheckCallbackInfo {
+ struct ReportList *reports;
+ bool external_file_found;
+};
+
+static bool external_file_check_callback(void *callback_info_ptr,
+ char * /*path_dst*/,
+ const char *path_src)
+{
+ FileCheckCallbackInfo *callback_info = static_cast<FileCheckCallbackInfo *>(callback_info_ptr);
+ BKE_reportf(callback_info->reports,
+ RPT_ERROR,
+ "Unable to install asset bundle, has external dependency \"%s\"",
+ path_src);
+ callback_info->external_file_found = true;
+ return false;
+}
+
+/**
+ * Do a check on any external files (.blend, textures, etc.) being used.
+ * The "Install asset bundle" operator only works on standalone .blend files
+ * (catalog definition files are fine, though).
+ *
+ * \return true when there are external files, false otherwise.
+ */
+static bool has_external_files(Main *bmain, struct ReportList *reports)
+{
+ struct FileCheckCallbackInfo callback_info = {reports, false};
+
+ BKE_bpath_traverse_main(
+ bmain,
+ &external_file_check_callback,
+ BKE_BPATH_TRAVERSE_SKIP_PACKED /* Packed files are fine. */
+ | BKE_BPATH_TRAVERSE_SKIP_MULTIFILE /* Only report multifiles once, it's enough. */,
+ &callback_info);
+ return callback_info.external_file_found;
+}
+
+/* -------------------------------------------------------------------- */
+
void ED_operatortypes_asset(void)
{
WM_operatortype_append(ASSET_OT_mark);
@@ -661,6 +906,7 @@ void ED_operatortypes_asset(void)
WM_operatortype_append(ASSET_OT_catalog_undo);
WM_operatortype_append(ASSET_OT_catalog_redo);
WM_operatortype_append(ASSET_OT_catalog_undo_push);
+ WM_operatortype_append(ASSET_OT_bundle_install);
WM_operatortype_append(ASSET_OT_list_refresh);
}
diff --git a/source/blender/editors/include/ED_fileselect.h b/source/blender/editors/include/ED_fileselect.h
index 68b6e44371c..c1936b2fde5 100644
--- a/source/blender/editors/include/ED_fileselect.h
+++ b/source/blender/editors/include/ED_fileselect.h
@@ -109,6 +109,7 @@ struct FileSelectParams *ED_fileselect_ensure_active_params(struct SpaceFile *sf
struct FileSelectParams *ED_fileselect_get_active_params(const struct SpaceFile *sfile);
struct FileSelectParams *ED_fileselect_get_file_params(const struct SpaceFile *sfile);
struct FileAssetSelectParams *ED_fileselect_get_asset_params(const struct SpaceFile *sfile);
+bool ED_fileselect_is_local_asset_library(const struct SpaceFile *sfile);
void ED_fileselect_set_params_from_userdef(struct SpaceFile *sfile);
void ED_fileselect_params_to_userdef(struct SpaceFile *sfile,
diff --git a/source/blender/editors/space_file/file_panels.c b/source/blender/editors/space_file/file_panels.c
index 0e468718a04..540d4729ed6 100644
--- a/source/blender/editors/space_file/file_panels.c
+++ b/source/blender/editors/space_file/file_panels.c
@@ -41,6 +41,7 @@
#include "ED_fileselect.h"
#include "UI_interface.h"
+#include "UI_interface_icons.h"
#include "UI_resources.h"
#include "WM_api.h"
@@ -246,7 +247,20 @@ static void file_panel_asset_catalog_buttons_draw(const bContext *C, Panel *pane
RNA_pointer_create(&screen->id, &RNA_FileAssetSelectParams, params, &params_ptr);
uiItemR(row, &params_ptr, "asset_library_ref", 0, "", ICON_NONE);
- if (params->asset_library_ref.type != ASSET_LIBRARY_LOCAL) {
+ if (params->asset_library_ref.type == ASSET_LIBRARY_LOCAL) {
+ bContext *mutable_ctx = CTX_copy(C);
+ if (WM_operator_name_poll(mutable_ctx, "asset.bundle_install")) {
+ uiItemS(col);
+ uiItemMenuEnumO(col,
+ mutable_ctx,
+ "asset.bundle_install",
+ "asset_library_ref",
+ "Copy Bundle to Asset Library...",
+ ICON_IMPORT);
+ }
+ CTX_free(mutable_ctx);
+ }
+ else {
uiItemO(row, "", ICON_FILE_REFRESH, "FILE_OT_asset_library_refresh");
}
diff --git a/source/blender/editors/space_file/filesel.c b/source/blender/editors/space_file/filesel.c
index 11757975a62..c59398e0016 100644
--- a/source/blender/editors/space_file/filesel.c
+++ b/source/blender/editors/space_file/filesel.c
@@ -411,6 +411,15 @@ FileAssetSelectParams *ED_fileselect_get_asset_params(const SpaceFile *sfile)
return (sfile->browse_mode == FILE_BROWSE_MODE_ASSETS) ? sfile->asset_params : NULL;
}
+bool ED_fileselect_is_local_asset_library(const SpaceFile *sfile)
+{
+ const FileAssetSelectParams *asset_params = ED_fileselect_get_asset_params(sfile);
+ if (asset_params == NULL) {
+ return false;
+ }
+ return asset_params->asset_library_ref.type == ASSET_LIBRARY_LOCAL;
+}
+
static void fileselect_refresh_asset_params(FileAssetSelectParams *asset_params)
{
AssetLibraryReference *library = &asset_params->asset_library_ref;
diff --git a/source/blender/makesrna/intern/rna_asset.c b/source/blender/makesrna/intern/rna_asset.c
index 5d83da170b5..0d25a0ba079 100644
--- a/source/blender/makesrna/intern/rna_asset.c
+++ b/source/blender/makesrna/intern/rna_asset.c
@@ -307,7 +307,7 @@ const EnumPropertyItem *rna_asset_library_reference_itemf(bContext *UNUSED(C),
PropertyRNA *UNUSED(prop),
bool *r_free)
{
- const EnumPropertyItem *items = ED_asset_library_reference_to_rna_enum_itemf();
+ const EnumPropertyItem *items = ED_asset_library_reference_to_rna_enum_itemf(true);
if (!items) {
*r_free = false;
}
diff --git a/source/blender/windowmanager/WM_api.h b/source/blender/windowmanager/WM_api.h
index 112d76a3e65..ef81addbb4d 100644
--- a/source/blender/windowmanager/WM_api.h
+++ b/source/blender/windowmanager/WM_api.h
@@ -470,6 +470,8 @@ int WM_operator_repeat(struct bContext *C, struct wmOperator *op);
int WM_operator_repeat_last(struct bContext *C, struct wmOperator *op);
bool WM_operator_repeat_check(const struct bContext *C, struct wmOperator *op);
bool WM_operator_is_repeat(const struct bContext *C, const struct wmOperator *op);
+
+bool WM_operator_name_poll(struct bContext *C, const char *opstring);
int WM_operator_name_call_ptr(struct bContext *C,
struct wmOperatorType *ot,
short context,
diff --git a/source/blender/windowmanager/intern/wm_event_system.c b/source/blender/windowmanager/intern/wm_event_system.c
index 798f60fba3d..b780fef289a 100644
--- a/source/blender/windowmanager/intern/wm_event_system.c
+++ b/source/blender/windowmanager/intern/wm_event_system.c
@@ -1611,6 +1611,16 @@ int WM_operator_name_call(bContext *C, const char *opstring, short context, Poin
return 0;
}
+bool WM_operator_name_poll(bContext *C, const char *opstring)
+{
+ wmOperatorType *ot = WM_operatortype_find(opstring, 0);
+ if (!ot) {
+ return false;
+ }
+
+ return WM_operator_poll(C, ot);
+}
+
int WM_operator_name_call_with_properties(struct bContext *C,
const char *opstring,
short context,