UI: Cleanup Dialog to Manage Orphaned Data #106653
|
@ -228,43 +228,6 @@ class TOPBAR_MT_blender(Menu):
|
|||
layout.menu("TOPBAR_MT_blender_system")
|
||||
|
||||
|
||||
class TOPBAR_MT_file_cleanup(Menu):
|
||||
bl_label = "Clean Up"
|
||||
|
||||
def draw(self, _context):
|
||||
layout = self.layout
|
||||
layout.separator()
|
||||
|
||||
props = layout.operator("outliner.orphans_purge", text="Unused Data-Blocks")
|
||||
props.do_local_ids = True
|
||||
props.do_linked_ids = True
|
||||
props.do_recursive = False
|
||||
props = layout.operator("outliner.orphans_purge", text="Recursive Unused Data-Blocks")
|
||||
props.do_local_ids = True
|
||||
props.do_linked_ids = True
|
||||
props.do_recursive = True
|
||||
|
||||
layout.separator()
|
||||
props = layout.operator("outliner.orphans_purge", text="Unused Linked Data-Blocks")
|
||||
props.do_local_ids = False
|
||||
props.do_linked_ids = True
|
||||
props.do_recursive = False
|
||||
props = layout.operator("outliner.orphans_purge", text="Recursive Unused Linked Data-Blocks")
|
||||
props.do_local_ids = False
|
||||
props.do_linked_ids = True
|
||||
props.do_recursive = True
|
||||
|
||||
layout.separator()
|
||||
props = layout.operator("outliner.orphans_purge", text="Unused Local Data-Blocks")
|
||||
props.do_local_ids = True
|
||||
props.do_linked_ids = False
|
||||
props.do_recursive = False
|
||||
props = layout.operator("outliner.orphans_purge", text="Recursive Unused Local Data-Blocks")
|
||||
props.do_local_ids = True
|
||||
props.do_linked_ids = False
|
||||
props.do_recursive = True
|
||||
|
||||
|
||||
class TOPBAR_MT_file(Menu):
|
||||
bl_label = "File"
|
||||
|
||||
|
@ -303,7 +266,7 @@ class TOPBAR_MT_file(Menu):
|
|||
layout.separator()
|
||||
|
||||
layout.menu("TOPBAR_MT_file_external_data")
|
||||
layout.menu("TOPBAR_MT_file_cleanup")
|
||||
layout.operator("outliner.orphans_cleanup", text="Clean Up...")
|
||||
|
||||
layout.separator()
|
||||
|
||||
|
@ -948,7 +911,6 @@ classes = (
|
|||
TOPBAR_MT_file_import,
|
||||
TOPBAR_MT_file_export,
|
||||
TOPBAR_MT_file_external_data,
|
||||
TOPBAR_MT_file_cleanup,
|
||||
TOPBAR_MT_file_previews,
|
||||
TOPBAR_MT_edit,
|
||||
TOPBAR_MT_render,
|
||||
|
|
|
@ -2245,6 +2245,305 @@ void OUTLINER_OT_orphans_purge(wmOperatorType *ot)
|
|||
"data-blocks remain after execution");
|
||||
}
|
||||
|
||||
static void wm_block_orphans_cancel(bContext *C, void *arg_block, void *arg_data)
|
||||
{
|
||||
UNUSED_VARS_NDEBUG(arg_data);
|
||||
UI_popup_block_close(C, CTX_wm_window(C), (uiBlock *)arg_block);
|
||||
}
|
||||
|
||||
static void wm_block_orphans_cancel_button(uiBlock *block)
|
||||
{
|
||||
uiBut *but = uiDefIconTextBut(
|
||||
block, UI_BTYPE_BUT, 0, 0, IFACE_("Cancel"), 0, 0, 0, UI_UNIT_Y, 0, 0, 0, 0, 0, "");
|
||||
UI_but_func_set(but, wm_block_orphans_cancel, block, NULL);
|
||||
UI_but_drawflag_disable(but, UI_BUT_TEXT_LEFT);
|
||||
UI_but_flag_enable(but, UI_BUT_ACTIVE_DEFAULT);
|
||||
}
|
||||
|
||||
static char orphans_purge_do_local = true;
|
||||
static char orphans_purge_do_linked = false;
|
||||
static char orphans_purge_do_recursive = false;
|
||||
|
||||
static void wm_block_orphans_purge(bContext *C, void *arg_block, void *arg_data)
|
||||
{
|
||||
wmOperator *op = (wmOperator *)arg_data;
|
||||
Main *bmain = CTX_data_main(C);
|
||||
int num_tagged[INDEX_ID_MAX] = {0};
|
||||
Harley marked this conversation as resolved
Outdated
|
||||
|
||||
/* Tag all IDs to delete. */
|
||||
BKE_lib_query_unused_ids_tag(bmain,
|
||||
LIB_TAG_DOIT,
|
||||
orphans_purge_do_local,
|
||||
orphans_purge_do_linked,
|
||||
orphans_purge_do_recursive,
|
||||
num_tagged);
|
||||
|
||||
if (num_tagged[INDEX_ID_NULL] == 0) {
|
||||
BKE_report(op->reports, RPT_INFO, "No orphaned data-blocks to purge");
|
||||
}
|
||||
else {
|
||||
BKE_id_multi_tagged_delete(bmain);
|
||||
BKE_reportf(op->reports, RPT_INFO, "Deleted %d data-block(s)", num_tagged[INDEX_ID_NULL]);
|
||||
DEG_relations_tag_update(bmain);
|
||||
WM_event_add_notifier(C, NC_ID | NA_REMOVED, nullptr);
|
||||
/* Force full redraw of the UI. */
|
||||
WM_main_add_notifier(NC_WINDOW, nullptr);
|
||||
}
|
||||
|
||||
UI_popup_block_close(C, CTX_wm_window(C), (uiBlock *)arg_block);
|
||||
}
|
||||
|
||||
static void wm_block_orphans_keep(bContext *C, void *arg_block, void *arg_data)
|
||||
{
|
||||
wmOperator *op = (wmOperator *)arg_data;
|
||||
Main *bmain = CTX_data_main(C);
|
||||
int num_tagged[INDEX_ID_MAX] = {0};
|
||||
|
||||
/* Tag all IDs that are unused. */
|
||||
BKE_lib_query_unused_ids_tag(bmain,
|
||||
LIB_TAG_DOIT,
|
||||
orphans_purge_do_local,
|
||||
orphans_purge_do_linked,
|
||||
orphans_purge_do_recursive,
|
||||
num_tagged);
|
||||
|
||||
if (num_tagged[INDEX_ID_NULL] == 0) {
|
||||
BKE_report(op->reports, RPT_INFO, "No orphaned data-blocks");
|
||||
}
|
||||
else {
|
||||
ListBase *lbarray[INDEX_ID_MAX];
|
||||
int a;
|
||||
a = set_listbasepointers(bmain, lbarray);
|
||||
while (a--) {
|
||||
LISTBASE_FOREACH (ID *, id, lbarray[a]) {
|
||||
Harley marked this conversation as resolved
Outdated
Hans Goudey
commented
`const bContext *`, `const bool`
|
||||
if (id->tag & LIB_TAG_DOIT) {
|
||||
id_fake_user_set(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BKE_reportf(op->reports, RPT_INFO, "Set fake user on %d data-block(s)", num_tagged[INDEX_ID_NULL]);
|
||||
DEG_relations_tag_update(bmain);
|
||||
WM_event_add_notifier(C, NC_ID | NA_EDITED, nullptr);
|
||||
/* Force full redraw of the UI. */
|
||||
WM_main_add_notifier(NC_WINDOW, nullptr);
|
||||
}
|
||||
|
||||
Harley marked this conversation as resolved
Outdated
Bastien Montagne
commented
No reason to use That kind of formatting could also benefit from using No reason to use `append` on strings afaik, just use `+=` operator?
That kind of formatting could also benefit from using `fmt::format` from our `extern/fmtlib` library.
|
||||
UI_popup_block_close(C, CTX_wm_window(C), (uiBlock *)arg_block);
|
||||
}
|
||||
|
||||
static void wm_block_orphans_manage(bContext *C, void *arg_block, void *arg_data)
|
||||
{
|
||||
wmWindow *win = CTX_wm_window(C);
|
||||
if (WM_window_open(C,
|
||||
IFACE_("Manage Unused Data"),
|
||||
0,
|
||||
0,
|
||||
500 * UI_SCALE_FAC,
|
||||
500 * UI_SCALE_FAC,
|
||||
SPACE_OUTLINER,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
WIN_ALIGN_PARENT_CENTER) != NULL) {
|
||||
SpaceOutliner *soutline = (SpaceOutliner *)CTX_wm_area(C)->spacedata.first;
|
||||
Harley marked this conversation as resolved
Outdated
Hans Goudey
commented
Use Use `static_cast` here instead of C style cast
|
||||
soutline->outlinevis = SO_ID_ORPHANS;
|
||||
}
|
||||
UI_popup_block_close(C, win, (uiBlock *)arg_block);
|
||||
}
|
||||
|
||||
static void wm_block_orphans_purge_button(uiBlock *block, wmOperator *op)
|
||||
{
|
||||
Harley marked this conversation as resolved
Outdated
Brecht Van Lommel
commented
Not really important since it's already working, but I think simple Not really important since it's already working, but I think simple `std::string` would be preferred over `DynStr` for simple cases like this where performance is not an issue.
Brecht Van Lommel
commented
To be clear, I'm not expecting this to be addressed. To be clear, I'm not expecting this to be addressed.
|
||||
uiBut *but = uiDefIconTextBut(
|
||||
block, UI_BTYPE_BUT, 0, 0, IFACE_("Delete"), 0, 0, 0, UI_UNIT_Y, 0, 0, 0, 0, 0, "");
|
||||
UI_but_func_set(but, wm_block_orphans_purge, block, op);
|
||||
UI_but_drawflag_disable(but, UI_BUT_TEXT_LEFT);
|
||||
}
|
||||
|
||||
static void wm_block_orphans_keep_button(uiBlock *block, wmOperator *op)
|
||||
{
|
||||
uiBut *but = uiDefIconTextBut(
|
||||
block, UI_BTYPE_BUT, 0, 0, IFACE_("Protect"), 0, 0, 0, UI_UNIT_Y, 0, 0, 0, 0, 0, "");
|
||||
UI_but_func_set(but, wm_block_orphans_keep, block, op);
|
||||
UI_but_drawflag_disable(but, UI_BUT_TEXT_LEFT);
|
||||
}
|
||||
|
||||
static void wm_block_orphans_manage_button(uiBlock *block, wmOperator *op)
|
||||
{
|
||||
uiBut *but = uiDefIconTextBut(
|
||||
block, UI_BTYPE_BUT, 0, 0, IFACE_("Manage"), 0, 0, 0, UI_UNIT_Y, 0, 0, 0, 0, 0, "");
|
||||
UI_but_func_set(but, wm_block_orphans_manage, block, op);
|
||||
UI_but_drawflag_disable(but, UI_BUT_TEXT_LEFT);
|
||||
}
|
||||
|
||||
static void orphan_desc(bContext *C, DynStr *desc_r, bool local, bool linked, bool recursive)
|
||||
{
|
||||
Main *bmain = CTX_data_main(C);
|
||||
int num_tagged[INDEX_ID_MAX] = {0};
|
||||
|
||||
BKE_lib_query_unused_ids_tag(bmain, LIB_TAG_DOIT, local, linked, recursive, num_tagged);
|
||||
|
||||
bool is_first = true;
|
||||
for (int i = 0; i < INDEX_ID_MAX - 2; i++) {
|
||||
if (num_tagged[i] != 0) {
|
||||
if (!is_first) {
|
||||
BLI_dynstr_append(desc_r, ", ");
|
||||
}
|
||||
else {
|
||||
is_first = false;
|
||||
}
|
||||
BLI_dynstr_appendf(
|
||||
desc_r,
|
||||
"%d %s",
|
||||
num_tagged[i],
|
||||
num_tagged[i] > 1 ?
|
||||
TIP_(BKE_idtype_idcode_to_name_plural(BKE_idtype_idcode_from_index(i))) :
|
||||
TIP_(BKE_idtype_idcode_to_name(BKE_idtype_idcode_from_index(i))));
|
||||
}
|
||||
}
|
||||
Harley marked this conversation as resolved
Outdated
Brecht Van Lommel
commented
Now getting into nitpicky territory... This is using title case while "Local data" and "Linked data" are not. "unused" is implied for "Local data" and "Linked data", so why use it here? Clarifications like "(Recursive)" are not something we normally do in the UI, that seems like it should be in the tooltip. I suggest "Include indirect data". Now getting into nitpicky territory...
This is using title case while "Local data" and "Linked data" are not. "unused" is implied for "Local data" and "Linked data", so why use it here? Clarifications like "(Recursive)" are not something we normally do in the UI, that seems like it should be in the tooltip.
I suggest "Include indirect data".
|
||||
if (BLI_dynstr_get_len(desc_r) == 0) {
|
||||
BLI_dynstr_append(desc_r, "Nothing");
|
||||
}
|
||||
}
|
||||
|
||||
static uiBlock *wm_block_create_orphans_cleanup(bContext *C, ARegion *region, void *arg)
|
||||
{
|
||||
wmOperator *op = (wmOperator *)arg;
|
||||
uiBlock *block = UI_block_begin(C, region, "orphans_remove_popup", UI_EMBOSS);
|
||||
UI_block_flag_enable(
|
||||
block, UI_BLOCK_KEEP_OPEN | UI_BLOCK_LOOP | UI_BLOCK_NO_WIN_CLIP | UI_BLOCK_NUMSELECT);
|
||||
UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP);
|
||||
|
||||
uiLayout *layout = uiItemsAlertBox(block, 40, ALERT_ICON_WARNING);
|
||||
|
||||
/* Title. */
|
||||
uiItemL_ex(layout, TIP_("Clean Up Unused Data in this File"), 0, true, false);
|
||||
|
||||
uiItemS(layout);
|
||||
|
||||
char text[1024];
|
||||
DynStr *dyn_str = BLI_dynstr_new();
|
||||
orphan_desc(C, dyn_str, true, false, false);
|
||||
BLI_snprintf(text, 1024, "Local (%s)", BLI_dynstr_get_cstring(dyn_str));
|
||||
|
||||
uiDefButBitC(block,
|
||||
UI_BTYPE_CHECKBOX,
|
||||
1,
|
||||
0,
|
||||
text,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
UI_UNIT_Y,
|
||||
&orphans_purge_do_local,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
"Delete unused local data-blocks");
|
||||
|
||||
BLI_dynstr_clear(dyn_str);
|
||||
orphan_desc(C, dyn_str, false, true, false);
|
||||
BLI_snprintf(text, 1024, "Linked (%s)", BLI_dynstr_get_cstring(dyn_str));
|
||||
|
||||
Bastien Montagne
commented
Do not invoke modal UI items from an The Further more, any reasons not to use some operator-aware popup system, like That way you can also define the settings (which types of data to purge) in the operator itself - or even better, can just re-use (and extend) existing Do not invoke modal UI items from an `exec` function. This code should be in the `invoke` callback.
The `exec` callback should be basically what `wm_block_orphans_purge` is doing currently.
Further more, any reasons not to use some operator-aware popup system, like `WM_operator_props_dialog_popup` or so?
That way you can also define the settings (which types of data to purge) in the operator itself - or even better, can just re-use (and extend) existing `OUTLINER_OT_orphans_purge`, instead of creating a new one that does the same thing?
Harley Acheson
commented
Thanks! done.
Just unfamiliarity with that, so will definitely will look into that. I'm also hoping to move toward dialogs that have a clear "cancel". I don't mind if some dialogs with complex behavior are more "bespoke". > Do not invoke modal UI items from an exec function. This code should be in the invoke callback
Thanks! done.
> any reasons not to use some operator-aware popup system, like WM_operator_props_dialog_popup or so?
Just unfamiliarity with that, so will definitely will look into that. I'm also hoping to move toward dialogs that have a clear "cancel". I don't mind if some dialogs with complex behavior are more "bespoke".
|
||||
uiDefButBitC(block,
|
||||
Harley marked this conversation as resolved
Outdated
Hans Goudey
commented
Use Use `__func__` instead of manually choosing a string
|
||||
UI_BTYPE_CHECKBOX,
|
||||
1,
|
||||
0,
|
||||
text,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
UI_UNIT_Y,
|
||||
&orphans_purge_do_linked,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
Harley marked this conversation as resolved
Outdated
Brecht Van Lommel
commented
Unused -> unused for the description Unused -> unused for the description
|
||||
0,
|
||||
"Delete unused linked data-blocks");
|
||||
Harley marked this conversation as resolved
Bastien Montagne
commented
Missing Missing `OPTYPE_UNDO` I think? Otherwise, a comment explaining why it's not needed would be good to have.
|
||||
|
||||
BLI_dynstr_clear(dyn_str);
|
||||
orphan_desc(C, dyn_str, false, false, true);
|
||||
BLI_snprintf(text, 1024, "Indirectly Unused (%s)", BLI_dynstr_get_cstring(dyn_str));
|
||||
|
||||
Harley marked this conversation as resolved
Outdated
Bastien Montagne
commented
Would be better to adjust these default values to the available screen space? On some modern screens, 500x500 is a fairly small window... Would be better to adjust these default values to the available screen space? On some modern screens, 500x500 is a fairly small window...
|
||||
uiDefButBitC(block,
|
||||
UI_BTYPE_CHECKBOX,
|
||||
1,
|
||||
0,
|
||||
text,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
UI_UNIT_Y,
|
||||
&orphans_purge_do_recursive,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
"Recursively check for indirectly unused data-blocks, ensuring that no orphaned "
|
||||
"data-blocks remain after execution");
|
||||
|
||||
BLI_dynstr_free(dyn_str);
|
||||
|
||||
uiItemS_ex(layout, 2.0f);
|
||||
|
||||
Harley marked this conversation as resolved
Bastien Montagne
commented
Should return Should return `OPERATOR_CANCELLED` if `WM_window_open` fails...
|
||||
/* Buttons. */
|
||||
#ifdef _WIN32
|
||||
const bool windows_layout = true;
|
||||
#else
|
||||
const bool windows_layout = false;
|
||||
#endif
|
||||
|
||||
uiLayout *split = uiLayoutSplit(layout, 0.0f, true);
|
||||
uiLayoutSetScaleY(split, 1.2f);
|
||||
|
||||
if (windows_layout) {
|
||||
/* Windows standard layout. */
|
||||
uiLayoutColumn(split, false);
|
||||
wm_block_orphans_purge_button(block, op);
|
||||
uiLayoutColumn(split, false);
|
||||
wm_block_orphans_keep_button(block, op);
|
||||
uiLayoutColumn(split, false);
|
||||
wm_block_orphans_manage_button(block, op);
|
||||
uiLayoutColumn(split, false);
|
||||
wm_block_orphans_cancel_button(block);
|
||||
}
|
||||
else {
|
||||
/* Non-Windows layout (macOS and Linux). */
|
||||
uiLayoutColumn(split, false);
|
||||
wm_block_orphans_cancel_button(block);
|
||||
uiLayoutColumn(split, false);
|
||||
wm_block_orphans_manage_button(block, op);
|
||||
uiLayoutColumn(split, false);
|
||||
wm_block_orphans_keep_button(block, op);
|
||||
uiLayoutColumn(split, false);
|
||||
wm_block_orphans_purge_button(block, op);
|
||||
}
|
||||
|
||||
UI_block_bounds_set_centered(block, 14 * UI_SCALE_FAC);
|
||||
return block;
|
||||
}
|
||||
|
||||
static int outliner_orphans_cleanup_exec(bContext *C, wmOperator *op)
|
||||
{
|
||||
UI_popup_block_invoke(C, wm_block_create_orphans_cleanup, op, NULL);
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
void OUTLINER_OT_orphans_cleanup(wmOperatorType *ot)
|
||||
{
|
||||
/* identifiers */
|
||||
ot->idname = "OUTLINER_OT_orphans_cleanup";
|
||||
ot->name = "Clean Up Unused Data...";
|
||||
ot->description = "Clean Up Unused data-blocks in the file";
|
||||
|
||||
/* callbacks */
|
||||
ot->exec = outliner_orphans_cleanup_exec;
|
||||
|
||||
/* flags */
|
||||
ot->flag = OPTYPE_REGISTER;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
} // namespace blender::ed::outliner
|
||||
|
|
|
@ -505,6 +505,7 @@ void OUTLINER_OT_drivers_add_selected(struct wmOperatorType *ot);
|
|||
void OUTLINER_OT_drivers_delete_selected(struct wmOperatorType *ot);
|
||||
|
||||
void OUTLINER_OT_orphans_purge(struct wmOperatorType *ot);
|
||||
void OUTLINER_OT_orphans_cleanup(struct wmOperatorType *ot);
|
||||
|
||||
/* outliner_query.cc ---------------------------------------------- */
|
||||
|
||||
|
|
|
@ -59,6 +59,7 @@ void outliner_operatortypes()
|
|||
WM_operatortype_append(OUTLINER_OT_drivers_delete_selected);
|
||||
|
||||
WM_operatortype_append(OUTLINER_OT_orphans_purge);
|
||||
WM_operatortype_append(OUTLINER_OT_orphans_cleanup);
|
||||
|
||||
WM_operatortype_append(OUTLINER_OT_parent_drop);
|
||||
WM_operatortype_append(OUTLINER_OT_parent_clear);
|
||||
|
|
UI_block_funcN_set
andUI_but_funcN_set
exist to avoid global variables like these. You can pass them an allocated structure with these 3 booleans, and it should be freed for when the block or button is freed.