From 46be42f6b16314f59a37ebb430d77d12e7a88461 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 9 Mar 2023 10:46:49 +1100 Subject: [PATCH] PyAPI: extend save/load handlers, optionally take a filepath argument Add a filepath argument to load/save pre/post. Also add save_post_failed and load_post_failed handlers so it's always possible to for the pre handlers to run a matching post action. This makes it possible to know the filepath of the blend file mean loaded/saved as well as supporting running an action when load/save operations fail. When loading and saving the startup-file, the path argument is set to an empty string. Details: New RNA types were added to support storing primitive values in PointerRNA. Primitive{String/Int/Float/Boolean}RNA. These will likely only be used in some limited cases, in the case of BKE_callback_exec it allows strings to be included as part of the PointerRNA **pointers argument. Ref !104769. --- source/blender/blenkernel/BKE_callbacks.h | 3 + source/blender/blenkernel/intern/callbacks.c | 12 +++ source/blender/makesrna/RNA_types.h | 18 ++++ source/blender/makesrna/intern/rna_rna.c | 82 +++++++++++++++++++ .../blender/python/intern/bpy_app_handlers.c | 28 +++++-- source/blender/python/intern/bpy_rna.c | 21 +++++ source/blender/python/intern/bpy_rna.h | 1 + .../blender/windowmanager/intern/wm_files.cc | 81 +++++++++++++----- 8 files changed, 218 insertions(+), 28 deletions(-) diff --git a/source/blender/blenkernel/BKE_callbacks.h b/source/blender/blenkernel/BKE_callbacks.h index 8c65214c78e..0f6ab7383db 100644 --- a/source/blender/blenkernel/BKE_callbacks.h +++ b/source/blender/blenkernel/BKE_callbacks.h @@ -83,8 +83,10 @@ typedef enum { BKE_CB_EVT_RENDER_CANCEL, BKE_CB_EVT_LOAD_PRE, BKE_CB_EVT_LOAD_POST, + BKE_CB_EVT_LOAD_POST_FAIL, BKE_CB_EVT_SAVE_PRE, BKE_CB_EVT_SAVE_POST, + BKE_CB_EVT_SAVE_POST_FAIL, BKE_CB_EVT_UNDO_PRE, BKE_CB_EVT_UNDO_POST, BKE_CB_EVT_REDO_PRE, @@ -123,6 +125,7 @@ void BKE_callback_exec_id_depsgraph(struct Main *bmain, struct ID *id, struct Depsgraph *depsgraph, eCbEvent evt); +void BKE_callback_exec_string(struct Main *bmain, eCbEvent evt, const char *str); void BKE_callback_add(bCallbackFuncStore *funcstore, eCbEvent evt); void BKE_callback_remove(bCallbackFuncStore *funcstore, eCbEvent evt); diff --git a/source/blender/blenkernel/intern/callbacks.c b/source/blender/blenkernel/intern/callbacks.c index 66364186d32..dd23bd6b354 100644 --- a/source/blender/blenkernel/intern/callbacks.c +++ b/source/blender/blenkernel/intern/callbacks.c @@ -69,6 +69,18 @@ void BKE_callback_exec_id_depsgraph(struct Main *bmain, BKE_callback_exec(bmain, pointers, 2, evt); } +void BKE_callback_exec_string(struct Main *bmain, eCbEvent evt, const char *str) +{ + PointerRNA str_ptr; + PrimitiveStringRNA data = {}; + data.value = str; + RNA_pointer_create(NULL, &RNA_PrimitiveString, &data, &str_ptr); + + PointerRNA *pointers[1] = {&str_ptr}; + + BKE_callback_exec(bmain, pointers, 1, evt); +} + void BKE_callback_add(bCallbackFuncStore *funcstore, eCbEvent evt) { ASSERT_CALLBACKS_INITIALIZED(); diff --git a/source/blender/makesrna/RNA_types.h b/source/blender/makesrna/RNA_types.h index 78a23eaaa81..568eea48ec1 100644 --- a/source/blender/makesrna/RNA_types.h +++ b/source/blender/makesrna/RNA_types.h @@ -774,6 +774,24 @@ typedef struct ExtensionRNA { StructFreeFunc free; } ExtensionRNA; +/* Primitive types. */ + +typedef struct PrimitiveStringRNA { + const char *value; +} PrimitiveStringRNA; + +typedef struct PrimitiveIntRNA { + int value; +} PrimitiveIntRNA; + +typedef struct PrimitiveFloatRNA { + float value; +} PrimitiveFloatRNA; + +typedef struct PrimitiveBooleanRNA { + bool value; +} PrimitiveBooleanRNA; + #ifdef __cplusplus } #endif diff --git a/source/blender/makesrna/intern/rna_rna.c b/source/blender/makesrna/intern/rna_rna.c index e2e0e2e3b4f..ec83454c26c 100644 --- a/source/blender/makesrna/intern/rna_rna.c +++ b/source/blender/makesrna/intern/rna_rna.c @@ -2775,6 +2775,52 @@ bool rna_property_override_apply_default(Main *bmain, # undef RNA_PROPERTY_GET_SINGLE # undef RNA_PROPERTY_SET_SINGLE +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Primitive Values + * \{ */ + +/* Primitive String. */ + +static void rna_PrimitiveString_value_get(PointerRNA *ptr, char *result) +{ + const PrimitiveStringRNA *data = ptr->data; + strcpy(result, data->value ? data->value : ""); +} + +static int rna_PrimitiveString_value_length(PointerRNA *ptr) +{ + const PrimitiveStringRNA *data = ptr->data; + return data->value ? strlen(data->value) : 0; +} + +/* Primitive Int. */ + +static int rna_PrimitiveInt_value_get(PointerRNA *ptr) +{ + const PrimitiveIntRNA *data = ptr->data; + return data->value; +} + +/* Primitive Float. */ + +static float rna_PrimitiveFloat_value_get(PointerRNA *ptr) +{ + const PrimitiveFloatRNA *data = ptr->data; + return data->value; +} + +/* Primitive Boolean. */ + +static bool rna_PrimitiveBoolean_value_get(PointerRNA *ptr) +{ + const PrimitiveBooleanRNA *data = ptr->data; + return data->value; +} + +/** \} */ + #else static void rna_def_struct(BlenderRNA *brna) @@ -3367,6 +3413,40 @@ static void rna_def_pointer_property(StructRNA *srna, PropertyType type) RNA_def_property_ui_text(prop, "Pointer Type", "Fixed pointer type, empty if variable type"); } +static void rna_def_rna_primitive(BlenderRNA *brna) +{ + /* Primitive Values, use when passing #PointerRNA is used for primitive types. + * For the rare cases we want to pass a value as RNA which wraps a primitive data. */ + + StructRNA *srna; + PropertyRNA *prop; + + srna = RNA_def_struct(brna, "PrimitiveString", NULL); + RNA_def_struct_ui_text(srna, "String Value", "RNA wrapped string"); + prop = RNA_def_property(srna, "value", PROP_STRING, PROP_BYTESTRING); + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + RNA_def_property_string_funcs( + prop, "rna_PrimitiveString_value_get", "rna_PrimitiveString_value_length", NULL); + + srna = RNA_def_struct(brna, "PrimitiveInt", NULL); + RNA_def_struct_ui_text(srna, "Primitive Int", "RNA wrapped int"); + prop = RNA_def_property(srna, "value", PROP_INT, PROP_NONE); + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + RNA_def_property_int_funcs(prop, "rna_PrimitiveInt_value_get", NULL, NULL); + + srna = RNA_def_struct(brna, "PrimitiveFloat", NULL); + RNA_def_struct_ui_text(srna, "Primitive Float", "RNA wrapped float"); + prop = RNA_def_property(srna, "value", PROP_FLOAT, PROP_NONE); + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + RNA_def_property_float_funcs(prop, "rna_PrimitiveFloat_value_get", NULL, NULL); + + srna = RNA_def_struct(brna, "PrimitiveBoolean", NULL); + RNA_def_struct_ui_text(srna, "Primitive Boolean", "RNA wrapped boolean"); + prop = RNA_def_property(srna, "value", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + RNA_def_property_boolean_funcs(prop, "rna_PrimitiveBoolean_value_get", NULL); +} + void RNA_def_rna(BlenderRNA *brna) { StructRNA *srna; @@ -3451,6 +3531,8 @@ void RNA_def_rna(BlenderRNA *brna) # endif RNA_def_property_ui_text(prop, "Structs", ""); + + rna_def_rna_primitive(brna); } #endif diff --git a/source/blender/python/intern/bpy_app_handlers.c b/source/blender/python/intern/bpy_app_handlers.c index 296d10fa84b..387f1c73b83 100644 --- a/source/blender/python/intern/bpy_app_handlers.c +++ b/source/blender/python/intern/bpy_app_handlers.c @@ -14,7 +14,9 @@ #include "BKE_callbacks.h" #include "RNA_access.h" +#include "RNA_prototypes.h" #include "RNA_types.h" + #include "bpy_app_handlers.h" #include "bpy_rna.h" @@ -29,6 +31,13 @@ void bpy_app_generic_callback(struct Main *main, static PyTypeObject BlenderAppCbType; +#define FILEPATH_SAVE_ARG \ + "Accepts one argument: " \ + "the file being saved, an empty string for the startup-file." +#define FILEPATH_LOAD_ARG \ + "Accepts one argument: " \ + "the file being loaded, an empty string for the startup-file." + /** * See `BKE_callbacks.h` #eCbEvent declaration for the policy on naming. */ @@ -50,10 +59,15 @@ static PyStructSequence_Field app_cb_info_fields[] = { {"render_init", "on initialization of a render job"}, {"render_complete", "on completion of render job"}, {"render_cancel", "on canceling a render job"}, - {"load_pre", "on loading a new blend file (before)"}, - {"load_post", "on loading a new blend file (after)"}, - {"save_pre", "on saving a blend file (before)"}, - {"save_post", "on saving a blend file (after)"}, + + {"load_pre", "on loading a new blend file (before)." FILEPATH_LOAD_ARG}, + {"load_post", "on loading a new blend file (after). " FILEPATH_LOAD_ARG}, + {"load_post_fail", "on failure to load a new blend file (after). " FILEPATH_LOAD_ARG}, + + {"save_pre", "on saving a blend file (before). " FILEPATH_SAVE_ARG}, + {"save_post", "on saving a blend file (after). " FILEPATH_SAVE_ARG}, + {"save_post_fail", "on failure to save a blend file (after). " FILEPATH_SAVE_ARG}, + {"undo_pre", "on loading an undo step (before)"}, {"undo_post", "on loading an undo step (after)"}, {"redo_pre", "on loading a redo step (before)"}, @@ -344,7 +358,8 @@ void bpy_app_generic_callback(struct Main *UNUSED(main), /* setup arguments */ for (int i = 0; i < pointers_num; ++i) { - PyTuple_SET_ITEM(args_all, i, pyrna_struct_CreatePyObject(pointers[i])); + PyTuple_SET_ITEM( + args_all, i, pyrna_struct_CreatePyObject_with_primitive_support(pointers[i])); } for (int i = pointers_num; i < num_arguments; ++i) { PyTuple_SET_ITEM(args_all, i, Py_INCREF_RET(Py_None)); @@ -354,7 +369,8 @@ void bpy_app_generic_callback(struct Main *UNUSED(main), PyTuple_SET_ITEM(args_single, 0, Py_INCREF_RET(Py_None)); } else { - PyTuple_SET_ITEM(args_single, 0, pyrna_struct_CreatePyObject(pointers[0])); + PyTuple_SET_ITEM( + args_single, 0, pyrna_struct_CreatePyObject_with_primitive_support(pointers[0])); } /* Iterate the list and run the callbacks diff --git a/source/blender/python/intern/bpy_rna.c b/source/blender/python/intern/bpy_rna.c index 93d22467970..1a4c124da7b 100644 --- a/source/blender/python/intern/bpy_rna.c +++ b/source/blender/python/intern/bpy_rna.c @@ -7407,6 +7407,27 @@ PyObject *pyrna_struct_CreatePyObject(PointerRNA *ptr) return (PyObject *)pyrna; } +PyObject *pyrna_struct_CreatePyObject_with_primitive_support(PointerRNA *ptr) +{ + if (ptr->type == &RNA_PrimitiveString) { + const PrimitiveStringRNA *data = ptr->data; + return PyC_UnicodeFromBytes(data->value); + } + if (ptr->type == &RNA_PrimitiveInt) { + const PrimitiveIntRNA *data = ptr->data; + return PyLong_FromLong(data->value); + } + if (ptr->type == &RNA_PrimitiveFloat) { + const PrimitiveFloatRNA *data = ptr->data; + return PyFloat_FromDouble(data->value); + } + if (ptr->type == &RNA_PrimitiveBoolean) { + const PrimitiveBooleanRNA *data = ptr->data; + return PyBool_FromLong(data->value); + } + return pyrna_struct_CreatePyObject(ptr); +} + PyObject *pyrna_prop_CreatePyObject(PointerRNA *ptr, PropertyRNA *prop) { BPy_PropertyRNA *pyrna; diff --git a/source/blender/python/intern/bpy_rna.h b/source/blender/python/intern/bpy_rna.h index d7778da6213..6b94c48179e 100644 --- a/source/blender/python/intern/bpy_rna.h +++ b/source/blender/python/intern/bpy_rna.h @@ -173,6 +173,7 @@ void BPY_update_rna_module(void); // PyObject *BPY_rna_doc(void); PyObject *BPY_rna_types(void); +PyObject *pyrna_struct_CreatePyObject_with_primitive_support(PointerRNA *ptr); PyObject *pyrna_struct_CreatePyObject(PointerRNA *ptr); PyObject *pyrna_prop_CreatePyObject(PointerRNA *ptr, PropertyRNA *prop); diff --git a/source/blender/windowmanager/intern/wm_files.cc b/source/blender/windowmanager/intern/wm_files.cc index 9f552a0b4ab..85cc689856e 100644 --- a/source/blender/windowmanager/intern/wm_files.cc +++ b/source/blender/windowmanager/intern/wm_files.cc @@ -632,10 +632,9 @@ void wm_file_read_report(bContext *C, Main *bmain) * \note In the case of #WM_file_read the file may fail to load. * Change here shouldn't cause user-visible changes in that case. */ -static void wm_file_read_pre(bContext *C, bool use_data, bool /*use_userdef*/) +static void wm_file_read_pre(bool use_data, bool /*use_userdef*/) { if (use_data) { - BKE_callback_exec_null(CTX_data_main(C), BKE_CB_EVT_LOAD_PRE); BLI_timer_on_file_load(); } @@ -656,6 +655,10 @@ struct wmFileReadPost_Params { uint is_startup_file : 1; uint is_factory_startup : 1; uint reset_app_template : 1; + + /* Used by #wm_homefile_read_post */ + uint success : 1; + uint is_alloc : 1; }; /** @@ -747,7 +750,6 @@ static void wm_file_read_post(bContext *C, const struct wmFileReadPost_Params *p if (use_data) { /* important to do before nullptr'ing the context */ BKE_callback_exec_null(bmain, BKE_CB_EVT_VERSION_UPDATE); - BKE_callback_exec_null(bmain, BKE_CB_EVT_LOAD_POST); if (is_factory_startup) { BKE_callback_exec_null(bmain, BKE_CB_EVT_LOAD_FACTORY_STARTUP_POST); } @@ -944,6 +946,10 @@ bool WM_file_read(bContext *C, const char *filepath, ReportList *reports) const bool use_data = true; const bool use_userdef = false; + /* NOTE: either #BKE_CB_EVT_LOAD_POST or #BKE_CB_EVT_LOAD_POST_FAIL must run. + * Runs at the end of this function, don't return beforehand. */ + BKE_callback_exec_string(CTX_data_main(C), BKE_CB_EVT_LOAD_PRE, filepath); + /* so we can get the error message */ errno = 0; @@ -968,7 +974,7 @@ bool WM_file_read(bContext *C, const char *filepath, ReportList *reports) bf_reports.duration.whole = PIL_check_seconds_timer(); struct BlendFileData *bfd = BKE_blendfile_read(filepath, ¶ms, &bf_reports); if (bfd != nullptr) { - wm_file_read_pre(C, use_data, use_userdef); + wm_file_read_pre(use_data, use_userdef); /* Put aside screens to match with persistent windows later, * also exit screens and editors. */ @@ -1004,6 +1010,8 @@ bool WM_file_read(bContext *C, const char *filepath, ReportList *reports) read_file_post_params.is_startup_file = false; read_file_post_params.is_factory_startup = false; read_file_post_params.reset_app_template = false; + read_file_post_params.success = true; + read_file_post_params.is_alloc = false; wm_file_read_post(C, &read_file_post_params); bf_reports.duration.whole = PIL_check_seconds_timer() - bf_reports.duration.whole; @@ -1048,7 +1056,11 @@ bool WM_file_read(bContext *C, const char *filepath, ReportList *reports) WM_cursor_wait(false); - BLI_assert(BKE_main_namemap_validate(CTX_data_main(C))); + Main *bmain = CTX_data_main(C); + BKE_callback_exec_string( + bmain, success ? BKE_CB_EVT_LOAD_POST : BKE_CB_EVT_LOAD_POST_FAIL, filepath); + + BLI_assert(BKE_main_namemap_validate(bmain)); return success; } @@ -1178,10 +1190,16 @@ void wm_homefile_read_ex(bContext *C, #endif /* WITH_PYTHON */ } + if (use_data) { + /* NOTE: either #BKE_CB_EVT_LOAD_POST or #BKE_CB_EVT_LOAD_POST_FAIL must run. + * This runs from #wm_homefile_read_post. */ + BKE_callback_exec_string(CTX_data_main(C), BKE_CB_EVT_LOAD_PRE, ""); + } + /* For regular file loading this only runs after the file is successfully read. * In the case of the startup file, the in-memory startup file is used as a fallback * so we know this will work if all else fails. */ - wm_file_read_pre(C, use_data, use_userdef); + wm_file_read_pre(use_data, use_userdef); if (use_data) { /* put aside screens to match with persistent windows later */ @@ -1392,10 +1410,14 @@ void wm_homefile_read_ex(bContext *C, params_file_read_post.is_factory_startup = is_factory_startup; params_file_read_post.reset_app_template = reset_app_template; + params_file_read_post.success = success; + params_file_read_post.is_alloc = false; + if (r_params_file_read_post == nullptr) { - wm_file_read_post(C, ¶ms_file_read_post); + wm_homefile_read_post(C, ¶ms_file_read_post); } else { + params_file_read_post.is_alloc = true; *r_params_file_read_post = static_cast( MEM_mallocN(sizeof(wmFileReadPost_Params), __func__)); **r_params_file_read_post = params_file_read_post; @@ -1417,7 +1439,17 @@ void wm_homefile_read_post(struct bContext *C, const struct wmFileReadPost_Params *params_file_read_post) { wm_file_read_post(C, params_file_read_post); - MEM_freeN((void *)params_file_read_post); + + if (params_file_read_post->use_data) { + BKE_callback_exec_string(CTX_data_main(C), + params_file_read_post->success ? BKE_CB_EVT_LOAD_POST : + BKE_CB_EVT_LOAD_POST_FAIL, + ""); + } + + if (params_file_read_post->is_alloc) { + MEM_freeN((void *)params_file_read_post); + } } /** \} */ @@ -1820,7 +1852,10 @@ static bool wm_file_write(bContext *C, /* Call pre-save callbacks before writing preview, * that way you can generate custom file thumbnail. */ - BKE_callback_exec_null(bmain, BKE_CB_EVT_SAVE_PRE); + + /* NOTE: either #BKE_CB_EVT_SAVE_POST or #BKE_CB_EVT_SAVE_POST_FAIL must run. + * Runs at the end of this function, don't return beforehand. */ + BKE_callback_exec_string(bmain, BKE_CB_EVT_SAVE_PRE, filepath); ED_assets_pre_save(bmain); /* Enforce full override check/generation on file save. */ @@ -1906,8 +1941,6 @@ static bool wm_file_write(bContext *C, wm_history_file_update(); } - BKE_callback_exec_null(bmain, BKE_CB_EVT_SAVE_POST); - /* run this function after because the file can't be written before the blend is */ if (ibuf_thumb) { IMB_thumb_delete(filepath, THB_FAIL); /* without this a failed thumb overrides */ @@ -1921,6 +1954,8 @@ static bool wm_file_write(bContext *C, ok = true; } + BKE_callback_exec_string(bmain, ok ? BKE_CB_EVT_SAVE_POST : BKE_CB_EVT_SAVE_POST_FAIL, filepath); + if (ibuf_thumb) { IMB_freeImBuf(ibuf_thumb); } @@ -2154,7 +2189,9 @@ static int wm_homefile_write_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } - BKE_callback_exec_null(bmain, BKE_CB_EVT_SAVE_PRE); + /* NOTE: either #BKE_CB_EVT_SAVE_POST or #BKE_CB_EVT_SAVE_POST_FAIL must run. + * Runs at the end of this function, don't return beforehand. */ + BKE_callback_exec_string(bmain, BKE_CB_EVT_SAVE_PRE, ""); ED_assets_pre_save(bmain); /* check current window and close it if temp */ @@ -2181,17 +2218,17 @@ static int wm_homefile_write_exec(bContext *C, wmOperator *op) blend_write_params.remap_mode = BLO_WRITE_PATH_REMAP_ABSOLUTE; /* Don't apply any path changes to the current blend file. */ blend_write_params.use_save_as_copy = true; - if (BLO_write_file(bmain, filepath, fileflags, &blend_write_params, op->reports) == 0) { - printf("fail\n"); - return OPERATOR_CANCELLED; + const bool ok = BLO_write_file(bmain, filepath, fileflags, &blend_write_params, op->reports); + + BKE_callback_exec_string(bmain, ok ? BKE_CB_EVT_SAVE_POST : BKE_CB_EVT_SAVE_POST_FAIL, ""); + + if (ok) { + printf("ok\n"); + BKE_report(op->reports, RPT_INFO, "Startup file saved"); + return OPERATOR_FINISHED; } - - printf("ok\n"); - BKE_report(op->reports, RPT_INFO, "Startup file saved"); - - BKE_callback_exec_null(bmain, BKE_CB_EVT_SAVE_POST); - - return OPERATOR_FINISHED; + printf("fail\n"); + return OPERATOR_CANCELLED; } void WM_OT_save_homefile(wmOperatorType *ot)