IDManagement: Update the Purge operator to display an interactive popup. #117304

Merged
Bastien Montagne merged 2 commits from mont29/blender:tmp-purge-as-popup into main 2024-02-09 17:01:42 +01:00
7 changed files with 399 additions and 157 deletions

View File

@ -240,33 +240,6 @@ class TOPBAR_MT_file_cleanup(Menu):
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):

View File

@ -19,10 +19,12 @@
* - `BKE_lib_query_` should be used for functions in that file.
*/
#include "DNA_ID.h"
#include "BLI_sys_types.h"
struct ID;
struct IDProperty;
#include <array>
struct LibraryForeachIDData;
struct Main;
@ -316,27 +318,81 @@ void BKE_library_ID_test_usages(Main *bmain,
bool *r_is_used_local,
bool *r_is_used_linked);
/** Parameters and result data structure for the 'unused IDs' functions below. */
struct LibQueryUnusedIDsData {
/** Process local data-blocks. */
bool do_local_ids;
/** Process linked data-blocks. */
bool do_linked_ids;
/**
* Process all actually unused data-blocks, including these that are currently only used by
* other unused data-blocks, and 'dependency islands' of several data-blocks using each-other,
* without any external valid user.
*/
bool do_recursive;
/**
* Amount of detected as unused data-blocks, per type and total as the last value of the array
* (#INDEX_ID_NULL).
*
* \note: Return value, set by the executed function.
*/
std::array<int, INDEX_ID_MAX> num_total;
/**
* Amount of detected as unused local data-blocks, per type and total as the last value of the
* array (#INDEX_ID_NULL).
*
* \note: Return value, set by the executed function.
*/
std::array<int, INDEX_ID_MAX> num_local;
/**
* Amount of detected as unused linked data-blocks, per type and total as the last value of the
* array (#INDEX_ID_NULL).
*
* \note: Return value, set by the executed function.
*/
std::array<int, INDEX_ID_MAX> num_linked;
};
/**
* Tag all unused IDs (a.k.a 'orphaned').
* Compute amount of unused IDs (a.k.a 'orphaned').
*
* By default only tag IDs with `0` user count.
* If `do_tag_recursive` is set, it will check dependencies to detect all IDs that are not actually
* By default only consider IDs with `0` user count.
* If `do_recursive` is set, it will check dependencies to detect all IDs that are not actually
* used in current file, including 'archipelagos` (i.e. set of IDs referencing each other in
* loops, but without any 'external' valid usages.
*
* Valid usages here are defined as ref-counting usages, which are not towards embedded or
* loop-back data.
*
* \param r_num_tagged: If non-NULL, must be a zero-initialized array of #INDEX_ID_MAX integers.
* Number of tagged-as-unused IDs is then set for each type, and as total in
* \param r_num_total: A zero-initialized array of #INDEX_ID_MAX integers. Number of IDs detected
* as unused from given parameters, per ID type in the matching index, and as total in
* #INDEX_ID_NULL item.
* \param r_num_local: A zero-initialized array of #INDEX_ID_MAX integers. Number of local IDs
* detected as unused from given parameters (but assuming \a do_local_ids is true), per ID type in
* the matching index, and as total in #INDEX_ID_NULL item.
* \param r_num_linked: A zero-initialized array of #INDEX_ID_MAX integers. Number of linked IDs
* detected as unused from given parameters (but assuming \a do_linked_ids is true), per ID type in
* the matching index, and as total in #INDEX_ID_NULL item.
*/
void BKE_lib_query_unused_ids_amounts(Main *bmain, LibQueryUnusedIDsData &parameters);
/**
* Tag all unused IDs (a.k.a 'orphaned').
*
* By default only tag IDs with `0` user count.
* If `do_recursive` is set, it will check dependencies to detect all IDs that are not actually
* used in current file, including 'archipelagos` (i.e. set of IDs referencing each other in
* loops, but without any 'external' valid usages.
*
* Valid usages here are defined as ref-counting usages, which are not towards embedded or
* loop-back data.
*
* \param tag: the ID tag to use to mark the ID as unused. Should never be `0`.
* \param r_num_tagged_total: A zero-initialized array of #INDEX_ID_MAX integers. Number of IDs
* tagged as unused from given parameters, per ID type in the matching index, and as total in
* #INDEX_ID_NULL item.
*/
void BKE_lib_query_unused_ids_tag(Main *bmain,
int tag,
bool do_local_ids,
bool do_linked_ids,
bool do_tag_recursive,
int *r_num_tagged);
void BKE_lib_query_unused_ids_tag(Main *bmain, int tag, LibQueryUnusedIDsData &parameters);
/**
* Detect orphaned linked data blocks (i.e. linked data not used (directly or indirectly)

View File

@ -13,6 +13,7 @@
#include "BLI_ghash.h"
#include "BLI_linklist_stack.h"
#include "BLI_listbase.h"
#include "BLI_set.hh"
#include "BLI_utildefines.h"
#include "BKE_anim_data.h"
@ -692,20 +693,73 @@ void BKE_library_ID_test_usages(Main *bmain,
/* ***** IDs usages.checking/tagging. ***** */
/* Internal data for the common processing of the 'unused IDs' query functions.
*

This can be removed now.

This can be removed now.

No, this is internal data not exposed in the header/public functions, it serves a different purpose as the public LibQueryUnusedIDsData one.

Will add a comment about it.

No, this is internal data not exposed in the header/public functions, it serves a different purpose as the public `LibQueryUnusedIDsData` one. Will add a comment about it.
* While #LibQueryUnusedIDsData is a subset of this internal struct, they need to be kept separate,
* since this struct is used with partially 'enforced' values for some parameters by the
* #BKE_lib_query_unused_ids_amounts code. This allows the computation of predictive amounts for
* user feedback ('what would be the amounts of IDs detected as unused if this option was
* enabled').
*/
struct UnusedIdsData {
Main *bmain;
const int id_tag;
mont29 marked this conversation as resolved Outdated

Maybe these 6 members could be moved into a struct that is defined in the BKE header? I don't feel particularly strongly about it, just would simplify argument passing a bit.

Maybe these 6 members could be moved into a struct that is defined in the BKE header? I don't feel particularly strongly about it, just would simplify argument passing a bit.

I was thinking you'd reference LibQueryUnusedIDsData in this struct directly.

I was thinking you'd reference `LibQueryUnusedIDsData` in this struct directly.
bool do_local_ids;
bool do_linked_ids;
bool do_recursive;
std::array<int, INDEX_ID_MAX> *num_total;
std::array<int, INDEX_ID_MAX> *num_local;
std::array<int, INDEX_ID_MAX> *num_linked;
blender::Set<ID *> unused_ids{};
mont29 marked this conversation as resolved Outdated

I think it would be more clear to call BKE_main_relations_tag_set outside of this. To me it's more clear if this struct just does counting, it's a bit unexpected to me that reset does more.

I think it would be more clear to call `BKE_main_relations_tag_set` outside of this. To me it's more clear if this struct just does counting, it's a bit unexpected to me that reset does more.
void reset(const bool do_local_ids,
const bool do_linked_ids,
const bool do_recursive,
std::array<int, INDEX_ID_MAX> &num_total,
std::array<int, INDEX_ID_MAX> &num_local,
std::array<int, INDEX_ID_MAX> &num_linked)
{
unused_ids.clear();
this->do_local_ids = do_local_ids;
this->do_linked_ids = do_linked_ids;
this->do_recursive = do_recursive;
this->num_total = &num_total;
this->num_local = &num_local;
this->num_linked = &num_linked;
}
};
static void lib_query_unused_ids_tag_id(ID *id, UnusedIdsData &data)
{
id->tag |= data.id_tag;
data.unused_ids.add(id);
const int id_code = BKE_idtype_idcode_to_index(GS(id->name));
(*data.num_total)[INDEX_ID_NULL]++;
(*data.num_total)[id_code]++;
if (ID_IS_LINKED(id)) {
(*data.num_linked)[INDEX_ID_NULL]++;
(*data.num_linked)[id_code]++;
}
else {
(*data.num_local)[INDEX_ID_NULL]++;
(*data.num_local)[id_code]++;
}
}
/* Returns `true` if given ID is detected as part of at least one dependency loop, false otherwise.
*/
static bool lib_query_unused_ids_tag_recurse(Main *bmain,
const int tag,
const bool do_local_ids,
const bool do_linked_ids,
ID *id,
int *r_num_tagged)
static bool lib_query_unused_ids_tag_recurse(ID *id, UnusedIdsData &data)
{
/* We should never deal with embedded, not-in-main IDs here. */
BLI_assert((id->flag & LIB_EMBEDDED_DATA) == 0);
MainIDRelationsEntry *id_relations = static_cast<MainIDRelationsEntry *>(
BLI_ghash_lookup(bmain->relations->relations_from_pointers, id));
BLI_ghash_lookup(data.bmain->relations->relations_from_pointers, id));
if ((id_relations->tags & MAINIDRELATIONS_ENTRY_TAGS_PROCESSED) != 0) {
return false;
@ -716,12 +770,12 @@ static bool lib_query_unused_ids_tag_recurse(Main *bmain,
return true;
}
if ((!do_linked_ids && ID_IS_LINKED(id)) || (!do_local_ids && !ID_IS_LINKED(id))) {
if ((!data.do_linked_ids && ID_IS_LINKED(id)) || (!data.do_local_ids && !ID_IS_LINKED(id))) {
id_relations->tags |= MAINIDRELATIONS_ENTRY_TAGS_PROCESSED;
return false;
}
if ((id->tag & tag) != 0) {
if (data.unused_ids.contains(id)) {
id_relations->tags |= MAINIDRELATIONS_ENTRY_TAGS_PROCESSED;
return false;
}
@ -779,26 +833,20 @@ static bool lib_query_unused_ids_tag_recurse(Main *bmain,
BLI_assert(id_from != nullptr);
}
if (lib_query_unused_ids_tag_recurse(
bmain, tag, do_local_ids, do_linked_ids, id_from, r_num_tagged))
{
if (lib_query_unused_ids_tag_recurse(id_from, data)) {
/* Dependency loop case, ignore the `id_from` tag value here (as it should not be considered
* as valid yet), and presume that this is a 'valid user' case for now. */
is_part_of_dependency_loop = true;
continue;
}
if ((id_from->tag & tag) == 0) {
if (!data.unused_ids.contains(id_from)) {
has_valid_from_users = true;
break;
}
}
if (!has_valid_from_users && !is_part_of_dependency_loop) {
/* Tag the ID as unused, only in case it is not part of a dependency loop. */
id->tag |= tag;
if (r_num_tagged != nullptr) {
r_num_tagged[INDEX_ID_NULL]++;
r_num_tagged[BKE_idtype_idcode_to_index(GS(id->name))]++;
}
lib_query_unused_ids_tag_id(id, data);
}
/* This ID is not being processed anymore.
@ -817,42 +865,33 @@ static bool lib_query_unused_ids_tag_recurse(Main *bmain,
return is_part_of_dependency_loop;
}
void BKE_lib_query_unused_ids_tag(Main *bmain,
const int tag,
const bool do_local_ids,
const bool do_linked_ids,
const bool do_tag_recursive,
int *r_num_tagged)
static void lib_query_unused_ids_tag(UnusedIdsData &data)
{
BKE_main_relations_tag_set(data.bmain, MAINIDRELATIONS_ENTRY_TAGS_PROCESSED, false);
/* First loop, to only check for immediately unused IDs (those with 0 user count).
* NOTE: It also takes care of clearing given tag for used IDs. */
ID *id;
FOREACH_MAIN_ID_BEGIN (bmain, id) {
if ((!do_linked_ids && ID_IS_LINKED(id)) || (!do_local_ids && !ID_IS_LINKED(id))) {
id->tag &= ~tag;
FOREACH_MAIN_ID_BEGIN (data.bmain, id) {
if ((!data.do_linked_ids && ID_IS_LINKED(id)) || (!data.do_local_ids && !ID_IS_LINKED(id))) {
id->tag &= ~data.id_tag;
}
else if (id->us == 0) {
id->tag |= tag;
if (r_num_tagged != nullptr) {
r_num_tagged[INDEX_ID_NULL]++;
r_num_tagged[BKE_idtype_idcode_to_index(GS(id->name))]++;
}
lib_query_unused_ids_tag_id(id, data);
}
else {
id->tag &= ~tag;
id->tag &= ~data.id_tag;
}
}
FOREACH_MAIN_ID_END;
if (!do_tag_recursive) {
if (!data.do_recursive) {
return;
}
BLI_assert(data.bmain->relations != nullptr);
BKE_main_relations_create(bmain, 0);
FOREACH_MAIN_ID_BEGIN (bmain, id) {
if (lib_query_unused_ids_tag_recurse(
bmain, tag, do_local_ids, do_linked_ids, id, r_num_tagged))
{
FOREACH_MAIN_ID_BEGIN (data.bmain, id) {
if (lib_query_unused_ids_tag_recurse(id, data)) {
/* This root processed ID is part of one or more dependency loops.
*
* If it was not tagged, and its matching relations entry is not marked as processed, it
@ -861,16 +900,12 @@ void BKE_lib_query_unused_ids_tag(Main *bmain,
* relations to the current Blender file (like being part of a scene, etc.).
*
* So the entry can be tagged as processed, and the ID tagged as unused. */
if ((id->tag & tag) == 0) {
if (!data.unused_ids.contains(id)) {
MainIDRelationsEntry *id_relations = static_cast<MainIDRelationsEntry *>(
BLI_ghash_lookup(bmain->relations->relations_from_pointers, id));
BLI_ghash_lookup(data.bmain->relations->relations_from_pointers, id));
if ((id_relations->tags & MAINIDRELATIONS_ENTRY_TAGS_PROCESSED) == 0) {
id_relations->tags |= MAINIDRELATIONS_ENTRY_TAGS_PROCESSED;
id->tag |= tag;
if (r_num_tagged != nullptr) {
r_num_tagged[INDEX_ID_NULL]++;
r_num_tagged[BKE_idtype_idcode_to_index(GS(id->name))]++;
}
lib_query_unused_ids_tag_id(id, data);
}
}
}
@ -878,15 +913,97 @@ void BKE_lib_query_unused_ids_tag(Main *bmain,
#ifndef NDEBUG
/* Relation entry for the root processed ID should always be marked as processed now. */
MainIDRelationsEntry *id_relations = static_cast<MainIDRelationsEntry *>(
BLI_ghash_lookup(bmain->relations->relations_from_pointers, id));
if ((id_relations->tags & MAINIDRELATIONS_ENTRY_TAGS_PROCESSED) == 0) {
BLI_assert((id_relations->tags & MAINIDRELATIONS_ENTRY_TAGS_PROCESSED) != 0);
}
BLI_ghash_lookup(data.bmain->relations->relations_from_pointers, id));
BLI_assert((id_relations->tags & MAINIDRELATIONS_ENTRY_TAGS_PROCESSED) != 0);
BLI_assert((id_relations->tags & MAINIDRELATIONS_ENTRY_TAGS_INPROGRESS) == 0);
#endif
}
FOREACH_MAIN_ID_END;
BKE_main_relations_free(bmain);
}
void BKE_lib_query_unused_ids_amounts(Main *bmain, LibQueryUnusedIDsData &parameters)
{
std::array<int, INDEX_ID_MAX> num_dummy{0};
if (parameters.do_recursive) {
BKE_main_relations_create(bmain, 0);
}
parameters.num_total.fill(0);
parameters.num_local.fill(0);
parameters.num_linked.fill(0);
/* The complex fiddling with the two calls, which data they each get, based on the `do_local_ids`
* and `do_linked_ids`, is here to reduce as much as possible the extra processing:
*
* If both local and linked options are enabled, a single call with all given parameters gives
* all required data about unused IDs.
*
* If both local and linked options are disabled, total amount is left at zero, and each local
* and linked amounts are computed separately.
*
* If local is disabled and linked is enabled, the first call will compute the amount of local
* IDs that would be unused if the local option was enabled. Therefore, only the local amount can
* be kept from this call. The second call will compute valid values for both linked, and total
* data.
*
* If local is enabled and linked is disabled, the first call will compute valid values for both
* local, and total data. The second call will compute the amount of linked IDs that would be
* unused if the linked option was enabled. Therefore, only the linked amount can be kept from
* this call.
*/
UnusedIdsData data{
bmain,
0,
true,
parameters.do_linked_ids,
parameters.do_recursive,
parameters.do_local_ids ? &parameters.num_total : &num_dummy,
&parameters.num_local,
(parameters.do_local_ids && parameters.do_linked_ids) ? &parameters.num_linked : &num_dummy};
lib_query_unused_ids_tag(data);
if (!(parameters.do_local_ids && parameters.do_linked_ids)) {
/* In case a second run is required, clear runtime data and update settings for linked data. */
data.reset(parameters.do_local_ids,
true,
parameters.do_recursive,
(!parameters.do_local_ids && parameters.do_linked_ids) ? parameters.num_total :
num_dummy,
num_dummy,
parameters.num_linked);
lib_query_unused_ids_tag(data);
}
if (parameters.do_recursive) {
BKE_main_relations_free(bmain);
}
}
void BKE_lib_query_unused_ids_tag(Main *bmain, const int tag, LibQueryUnusedIDsData &parameters)
{
BLI_assert(tag != 0);
parameters.num_total.fill(0);
parameters.num_local.fill(0);
parameters.num_linked.fill(0);
UnusedIdsData data{bmain,
tag,
parameters.do_local_ids,
parameters.do_linked_ids,
parameters.do_recursive,
&parameters.num_total,
&parameters.num_local,
&parameters.num_linked};
if (parameters.do_recursive) {
BKE_main_relations_create(bmain, 0);
}
lib_query_unused_ids_tag(data);
if (parameters.do_recursive) {
BKE_main_relations_free(bmain);
}
}
static int foreach_libblock_used_linked_data_tag_clear_cb(LibraryIDLinkCallbackData *cb_data)

View File

@ -4,6 +4,7 @@
set(INC
../include
../../blenfont
../../blenkernel
../../blenloader
../../blentranslation
@ -13,6 +14,8 @@ set(INC
../../sequencer
../../windowmanager
../../../../extern/fmtlib/include
# RNA_prototypes.h
${CMAKE_BINARY_DIR}/source/blender/makesrna
)
@ -137,6 +140,7 @@ set(LIB
bf_editor_undo
PRIVATE bf::intern::clog
PRIVATE bf::intern::guardedalloc
extern_fmtlib
)

View File

@ -7,6 +7,10 @@
*/
#include <cstring>
#include <iostream>
#include <ostream>
#include <fmt/format.h>
#include "MEM_guardedalloc.h"
@ -23,6 +27,8 @@
#include "BLT_translation.h"
#include "BLF_api.hh"
#include "BKE_action.h"
#include "BKE_animsys.h"
#include "BKE_appdir.hh"
@ -2124,51 +2130,92 @@ static bool ed_operator_outliner_id_orphans_active(bContext *C)
return true;
}
static int outliner_orphans_purge_invoke(bContext *C, wmOperator *op, const wmEvent * /*event*/)
static void unused_message_gen(std::string &message,
const std::array<int, INDEX_ID_MAX> &num_tagged)
{
Main *bmain = CTX_data_main(C);
int num_tagged[INDEX_ID_MAX] = {0};
const bool do_local_ids = RNA_boolean_get(op->ptr, "do_local_ids");
const bool do_linked_ids = RNA_boolean_get(op->ptr, "do_linked_ids");
const bool do_recursive_cleanup = RNA_boolean_get(op->ptr, "do_recursive");
/* Tag all IDs to delete. */
BKE_lib_query_unused_ids_tag(
bmain, LIB_TAG_DOIT, do_local_ids, do_linked_ids, do_recursive_cleanup, num_tagged);
RNA_int_set(op->ptr, "num_deleted", num_tagged[INDEX_ID_NULL]);
bool is_first = true;
if (num_tagged[INDEX_ID_NULL] == 0) {
BKE_report(op->reports, RPT_INFO, "No orphaned data-blocks to purge");
return OPERATOR_CANCELLED;
message += IFACE_("None");
return;
}
DynStr *dyn_str = BLI_dynstr_new();
BLI_dynstr_appendf(dyn_str, RPT_("Purging %d unused data-blocks ("), num_tagged[INDEX_ID_NULL]);
bool is_first = true;
for (int i = 0; i < INDEX_ID_MAX - 2; i++) {
/* NOTE: Index is looped in reversed order, since typically 'higher level' IDs (like Collections
* or Objects) have a higher index than 'lower level' ones like object data, materials, etc.
*
* It makes more sense to present to the user the deleted numbers of Collections or Objects
* before the ones for object data or Materials. */
for (int i = INDEX_ID_MAX - 2; i >= 0; i--) {
if (num_tagged[i] != 0) {
if (!is_first) {
BLI_dynstr_append(dyn_str, ", ");
}
else {
is_first = false;
}
BLI_dynstr_appendf(dyn_str,
"%d %s",
num_tagged[i],
RPT_(BKE_idtype_idcode_to_name_plural(BKE_idtype_idcode_from_index(i))));
message += fmt::format(
"{}{} {}",
(is_first) ? "" : ", ",
num_tagged[i],
(num_tagged[i] > 1) ?
IFACE_(BKE_idtype_idcode_to_name_plural(BKE_idtype_idcode_from_index(i))) :
IFACE_(BKE_idtype_idcode_to_name(BKE_idtype_idcode_from_index(i))));
is_first = false;
}
}
BLI_dynstr_append(dyn_str, RPT_("). Click here to proceed..."));
}
char *message = BLI_dynstr_get_cstring(dyn_str);
int ret = WM_operator_confirm_message(C, op, message);
static int unused_message_popup_width_compute(bContext *C)
{
/* Computation of unused data amounts, with all options ON.
* Used to estimate the maximum required witdh for the dialog. */
Main *bmain = CTX_data_main(C);
LibQueryUnusedIDsData data = {true, true, true, {}, {}, {}};
BKE_lib_query_unused_ids_amounts(bmain, data);
MEM_freeN(message);
BLI_dynstr_free(dyn_str);
return ret;
std::string unused_message = "";
const uiStyle *style = UI_style_get_dpi();
unused_message_gen(unused_message, data.num_local);
float max_messages_width = BLF_width(
style->widget.uifont_id, unused_message.c_str(), BLF_DRAW_STR_DUMMY_MAX);
unused_message = "";
unused_message_gen(unused_message, data.num_linked);
max_messages_width = std::max(
max_messages_width,
BLF_width(style->widget.uifont_id, unused_message.c_str(), BLF_DRAW_STR_DUMMY_MAX));
return int(std::max(max_messages_width, 300.0f));
mont29 marked this conversation as resolved Outdated

Use 300 like other dialogs, this looks too narrow.

Use 300 like other dialogs, this looks too narrow.
}
static void outliner_orphans_purge_cleanup(wmOperator *op)
{
if (op->customdata) {
MEM_delete(static_cast<LibQueryUnusedIDsData *>(op->customdata));
op->customdata = nullptr;
}
}
static bool outliner_orphans_purge_check(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
LibQueryUnusedIDsData &data = *static_cast<LibQueryUnusedIDsData *>(op->customdata);
data.do_local_ids = RNA_boolean_get(op->ptr, "do_local_ids");
data.do_linked_ids = RNA_boolean_get(op->ptr, "do_linked_ids");
data.do_recursive = RNA_boolean_get(op->ptr, "do_recursive");
BKE_lib_query_unused_ids_amounts(bmain, data);
/* Always assume count changed, and request a redraw. */
return true;
}
static int outliner_orphans_purge_invoke(bContext *C, wmOperator *op, const wmEvent * /*event*/)
{
op->customdata = MEM_new<LibQueryUnusedIDsData>(__func__);
/* Compute expected amounts of deleted IDs and store them in 'cached' operator properties. */
outliner_orphans_purge_check(C, op);
return WM_operator_props_dialog_popup(C,
op,
unused_message_popup_width_compute(C),
IFACE_("Purge Unused Data From This File"),
IFACE_("Delete"));
}
static int outliner_orphans_purge_exec(bContext *C, wmOperator *op)
@ -2176,26 +2223,27 @@ static int outliner_orphans_purge_exec(bContext *C, wmOperator *op)
Main *bmain = CTX_data_main(C);
ScrArea *area = CTX_wm_area(C);
SpaceOutliner *space_outliner = CTX_wm_space_outliner(C);
int num_tagged[INDEX_ID_MAX] = {0};
if ((num_tagged[INDEX_ID_NULL] = RNA_int_get(op->ptr, "num_deleted")) == 0) {
const bool do_local_ids = RNA_boolean_get(op->ptr, "do_local_ids");
const bool do_linked_ids = RNA_boolean_get(op->ptr, "do_linked_ids");
const bool do_recursive_cleanup = RNA_boolean_get(op->ptr, "do_recursive");
if (!op->customdata) {
op->customdata = MEM_new<LibQueryUnusedIDsData>(__func__);
}
LibQueryUnusedIDsData &data = *static_cast<LibQueryUnusedIDsData *>(op->customdata);
/* Tag all IDs to delete. */
BKE_lib_query_unused_ids_tag(
bmain, LIB_TAG_DOIT, do_local_ids, do_linked_ids, do_recursive_cleanup, num_tagged);
data.do_local_ids = RNA_boolean_get(op->ptr, "do_local_ids");
data.do_linked_ids = RNA_boolean_get(op->ptr, "do_linked_ids");
data.do_recursive = RNA_boolean_get(op->ptr, "do_recursive");
if (num_tagged[INDEX_ID_NULL] == 0) {
BKE_report(op->reports, RPT_INFO, "No orphaned data-blocks to purge");
return OPERATOR_CANCELLED;
}
/* Tag all IDs to delete. */
BKE_lib_query_unused_ids_tag(bmain, LIB_TAG_DOIT, data);
if (data.num_total[INDEX_ID_NULL] == 0) {
BKE_report(op->reports, RPT_INFO, "No orphaned data-blocks to purge");
return OPERATOR_CANCELLED;
}
BKE_id_multi_tagged_delete(bmain);
BKE_reportf(op->reports, RPT_INFO, "Deleted %d data-block(s)", num_tagged[INDEX_ID_NULL]);
BKE_reportf(op->reports, RPT_INFO, "Deleted %d data-block(s)", data.num_total[INDEX_ID_NULL]);
/* XXX: tree management normally happens from draw_outliner(), but when
* you're clicking to fast on Delete object from context menu in
@ -2211,9 +2259,53 @@ static int outliner_orphans_purge_exec(bContext *C, wmOperator *op)
/* Force full redraw of the UI. */
WM_main_add_notifier(NC_WINDOW, nullptr);
outliner_orphans_purge_cleanup(op);
return OPERATOR_FINISHED;
}
static void outliner_orphans_purge_cancel(bContext * /*C*/, wmOperator *op)
{
outliner_orphans_purge_cleanup(op);
}
static void outliner_orphans_purge_ui(bContext * /*C*/, wmOperator *op)
{
uiLayout *layout = op->layout;
PointerRNA *ptr = op->ptr;
if (!op->customdata) {
/* This should only happen on 'adjust last operation' case, since `invoke` will not have been
* called then before showing the UI (the 'redo panel' UI uses WM-stored operator properties
* and a newly-created operator).
*
* Since that operator is not 'registered' for adjusting from undo stack, this should never
* happen currently. */
mont29 marked this conversation as resolved Outdated

I don't think this total count is needed.

If it's needed to show recursive IDs as well, I think those should be behind that option.

I don't think this total count is needed. If it's needed to show recursive IDs as well, I think those should be behind that option.

Now, these are just the sum of the local and linked numbers, so indeed not really that useful.

Now, these are just the sum of the local and linked numbers, so indeed not really that useful.
BLI_assert_unreachable();
op->customdata = MEM_new<LibQueryUnusedIDsData>(__func__);
}
LibQueryUnusedIDsData &data = *static_cast<LibQueryUnusedIDsData *>(op->customdata);
uiItemS_ex(layout, 0.5f);
std::string unused_message = "";
unused_message_gen(unused_message, data.num_local);
uiLayout *column = uiLayoutColumn(layout, true);
uiItemR(column, ptr, "do_local_ids", UI_ITEM_NONE, nullptr, ICON_NONE);
uiLayout *row = uiLayoutRow(column, true);
uiItemS_ex(row, 2.67f);
mont29 marked this conversation as resolved Outdated

Actually testing, I don't think this is needed. The text is already darker than the checkbox text without this, so no need.

Actually testing, I don't think this is needed. The text is already darker than the checkbox text without this, so no need.
uiItemL(row, unused_message.c_str(), ICON_NONE);
unused_message = "";
unused_message_gen(unused_message, data.num_linked);
column = uiLayoutColumn(layout, true);
uiItemR(column, ptr, "do_linked_ids", UI_ITEM_NONE, nullptr, ICON_NONE);
row = uiLayoutRow(column, true);
uiItemS_ex(row, 2.67f);
uiItemL(row, unused_message.c_str(), ICON_NONE);
mont29 marked this conversation as resolved Outdated

Same.

Same.
uiItemR(layout, ptr, "do_recursive", UI_ITEM_NONE, nullptr, ICON_NONE);
}
void OUTLINER_OT_orphans_purge(wmOperatorType *ot)
{
/* identifiers */
@ -2224,15 +2316,17 @@ void OUTLINER_OT_orphans_purge(wmOperatorType *ot)
/* callbacks */
ot->invoke = outliner_orphans_purge_invoke;
ot->exec = outliner_orphans_purge_exec;
ot->cancel = outliner_orphans_purge_cancel;
ot->poll = ed_operator_outliner_id_orphans_active;
ot->check = outliner_orphans_purge_check;
ot->ui = outliner_orphans_purge_ui;
mont29 marked this conversation as resolved Outdated

This code can be removed now?

This code can be removed now?
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* properties */
PropertyRNA *prop = RNA_def_int(ot->srna, "num_deleted", 0, 0, INT_MAX, "", "", 0, INT_MAX);
RNA_def_property_flag(prop, (PropertyFlag)(PROP_SKIP_SAVE | PROP_HIDDEN));
/* NOTE: No #OPTYPE_REGISTER, since this operator should not be 'adjustable'. */
ot->flag = OPTYPE_UNDO;
/* Actual user-visibla settings. */
RNA_def_boolean(ot->srna,
"do_local_ids",
true,

View File

@ -20,7 +20,7 @@
#ifdef UNIT_TEST
# define RNA_MAX_ARRAY_LENGTH 64
#else
# define RNA_MAX_ARRAY_LENGTH 32
# define RNA_MAX_ARRAY_LENGTH 64
mont29 marked this conversation as resolved Outdated

This change would also no longer be needed.

This change would also no longer be needed.
#endif
#define RNA_MAX_ARRAY_DIMENSION 3

View File

@ -383,11 +383,10 @@ static PyObject *bpy_orphans_purge(PyObject * /*self*/, PyObject *args, PyObject
Main *bmain = G_MAIN; /* XXX Ugly, but should work! */
#endif
int num_tagged[INDEX_ID_MAX] = {0};
bool do_local_ids = true;
bool do_linked_ids = true;
bool do_recursive_cleanup = false;
LibQueryUnusedIDsData unused_ids_data;
unused_ids_data.do_local_ids = true;
unused_ids_data.do_linked_ids = true;
unused_ids_data.do_recursive = false;
static const char *_keywords[] = {"do_local_ids", "do_linked_ids", "do_recursive", nullptr};
static _PyArg_Parser _parser = {
@ -404,20 +403,19 @@ static PyObject *bpy_orphans_purge(PyObject * /*self*/, PyObject *args, PyObject
kwds,
&_parser,
PyC_ParseBool,
&do_local_ids,
&unused_ids_data.do_local_ids,
PyC_ParseBool,
&do_linked_ids,
&unused_ids_data.do_linked_ids,
PyC_ParseBool,
&do_recursive_cleanup))
&unused_ids_data.do_recursive))
{
return nullptr;
}
/* Tag all IDs to delete. */
BKE_lib_query_unused_ids_tag(
bmain, LIB_TAG_DOIT, do_local_ids, do_linked_ids, do_recursive_cleanup, num_tagged);
BKE_lib_query_unused_ids_tag(bmain, LIB_TAG_DOIT, unused_ids_data);
if (num_tagged[INDEX_ID_NULL] == 0) {
if (unused_ids_data.num_total[INDEX_ID_NULL] == 0) {
return PyLong_FromSize_t(0);
}