Fix #114024: "Mark as Asset" doesn't work in 3D View sidebar ID selector

Choosing "Mark as Asset" from the context menu of the ID selector (ID
template) would always use the selected objects instead of the ID set in
the ID selector. This is because since f22e2bab72, multiple selected IDs
have priority over a single active ID (to give batch editing priority),
and the 3D view context exposes both.

Adds variants of the mark and clear asset operators that only work on a
single ID ("id" context member). Context menus for buttons representing
an ID use this instead. Using an operator property resulted in too
complicated code. Plus the poll function would succeed in cases where
the operator wouldn't be able to succeed. Separate operators keep things
simple and more reliable.
This commit is contained in:
Julian Eisel
2023-10-23 15:27:26 +02:00
parent 3a483004c8
commit ad71b77f25
4 changed files with 93 additions and 34 deletions

View File

@@ -95,7 +95,8 @@ void ED_assets_pre_save(Main *bmain)
bool ED_asset_can_mark_single_from_context(const bContext *C)
{
/* Context needs a "id" pointer to be set for #ASSET_OT_mark()/#ASSET_OT_clear() to use. */
/* Context needs a "id" pointer to be set for #ASSET_OT_mark()/#ASSET_OT_mark_single() and
* #ASSET_OT_clear()/#ASSET_OT_clear_single() to use. */
const ID *id = static_cast<ID *>(CTX_data_pointer_get_type_silent(C, "id", &RNA_ID).data);
if (!id) {
return false;

View File

@@ -47,6 +47,16 @@ using namespace blender;
using PointerRNAVec = blender::Vector<PointerRNA>;
static PointerRNAVec get_single_id_vec_from_context(const bContext *C)
{
PointerRNAVec ids;
PointerRNA idptr = CTX_data_pointer_get_type(C, "id", &RNA_ID);
if (idptr.data) {
ids.append(idptr);
}
return ids;
}
/**
* Return the IDs to operate on as PointerRNA vector. Prioritizes multiple selected ones
* ("selected_ids" context member) over a single active one ("id" context member), since usually
@@ -71,12 +81,7 @@ static PointerRNAVec asset_operation_get_ids_from_context(const bContext *C)
}
/* "id" context member. */
PointerRNA idptr = CTX_data_pointer_get_type(C, "id", &RNA_ID);
if (idptr.data) {
ids.append(idptr);
}
return ids;
return get_single_id_vec_from_context(C);
}
/**
@@ -93,14 +98,13 @@ struct IDVecStats {
* Helper to report stats about the IDs in context. Operator polls use this, also to report a
* helpful disabled hint to the user.
*/
static IDVecStats asset_operation_get_id_vec_stats_from_context(const bContext *C)
static IDVecStats asset_operation_get_id_vec_stats_from_ids(const PointerRNAVec &id_pointers)
{
PointerRNAVec pointers = asset_operation_get_ids_from_context(C);
IDVecStats stats;
stats.is_single = pointers.size() == 1;
stats.is_single = id_pointers.size() == 1;
for (PointerRNA &ptr : pointers) {
for (const PointerRNA &ptr : id_pointers) {
BLI_assert(RNA_struct_is_ID(ptr.type));
ID *id = static_cast<ID *>(ptr.data);
@@ -130,7 +134,7 @@ static const char *asset_operation_unsupported_type_msg(const bool is_single)
class AssetMarkHelper {
public:
void operator()(const bContext &C, PointerRNAVec &ids);
void operator()(const bContext &C, const PointerRNAVec &ids);
void reportResults(ReportList &reports) const;
bool wasSuccessful() const;
@@ -145,9 +149,9 @@ class AssetMarkHelper {
Stats stats;
};
void AssetMarkHelper::operator()(const bContext &C, PointerRNAVec &ids)
void AssetMarkHelper::operator()(const bContext &C, const PointerRNAVec &ids)
{
for (PointerRNA &ptr : ids) {
for (const PointerRNA &ptr : ids) {
BLI_assert(RNA_struct_is_ID(ptr.type));
ID *id = static_cast<ID *>(ptr.data);
@@ -195,10 +199,8 @@ void AssetMarkHelper::reportResults(ReportList &reports) const
}
}
static int asset_mark_exec(bContext *C, wmOperator *op)
static int asset_mark_exec(const bContext *C, const wmOperator *op, const PointerRNAVec &ids)
{
PointerRNAVec ids = asset_operation_get_ids_from_context(C);
AssetMarkHelper mark_helper;
mark_helper(*C, ids);
mark_helper.reportResults(*op->reports);
@@ -213,9 +215,9 @@ static int asset_mark_exec(bContext *C, wmOperator *op)
return OPERATOR_FINISHED;
}
static bool asset_mark_poll(bContext *C)
static bool asset_mark_poll(bContext *C, const PointerRNAVec &ids)
{
IDVecStats ctx_stats = asset_operation_get_id_vec_stats_from_context(C);
IDVecStats ctx_stats = asset_operation_get_id_vec_stats_from_ids(ids);
if (!ctx_stats.has_supported_type) {
CTX_wm_operator_poll_msg_set(C, asset_operation_unsupported_type_msg(ctx_stats.is_single));
@@ -233,8 +235,33 @@ static void ASSET_OT_mark(wmOperatorType *ot)
"customizable metadata (like previews, descriptions and tags)";
ot->idname = "ASSET_OT_mark";
ot->exec = asset_mark_exec;
ot->poll = asset_mark_poll;
ot->exec = [](bContext *C, wmOperator *op) -> int {
return asset_mark_exec(C, op, asset_operation_get_ids_from_context(C));
};
ot->poll = [](bContext *C) -> bool {
return asset_mark_poll(C, asset_operation_get_ids_from_context(C));
};
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/**
* Variant of #ASSET_OT_mark that only works on the "id" context member.
*/
static void ASSET_OT_mark_single(wmOperatorType *ot)
{
ot->name = "Mark as Single Asset";
ot->description =
"Enable easier reuse of a data-block through the Asset Browser, with the help of "
"customizable metadata (like previews, descriptions and tags)";
ot->idname = "ASSET_OT_mark_single";
ot->exec = [](bContext *C, wmOperator *op) -> int {
return asset_mark_exec(C, op, get_single_id_vec_from_context(C));
};
ot->poll = [](bContext *C) -> bool {
return asset_mark_poll(C, get_single_id_vec_from_context(C));
};
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
@@ -247,7 +274,7 @@ class AssetClearHelper {
public:
AssetClearHelper(const bool set_fake_user) : set_fake_user_(set_fake_user) {}
void operator()(PointerRNAVec &ids);
void operator()(const PointerRNAVec &ids);
void reportResults(const bContext *C, ReportList &reports) const;
bool wasSuccessful() const;
@@ -261,9 +288,9 @@ class AssetClearHelper {
Stats stats;
};
void AssetClearHelper::operator()(PointerRNAVec &ids)
void AssetClearHelper::operator()(const PointerRNAVec &ids)
{
for (PointerRNA &ptr : ids) {
for (const PointerRNA &ptr : ids) {
BLI_assert(RNA_struct_is_ID(ptr.type));
ID *id = static_cast<ID *>(ptr.data);
@@ -314,10 +341,8 @@ bool AssetClearHelper::wasSuccessful() const
return stats.tot_cleared > 0;
}
static int asset_clear_exec(bContext *C, wmOperator *op)
static int asset_clear_exec(const bContext *C, const wmOperator *op, const PointerRNAVec &ids)
{
PointerRNAVec ids = asset_operation_get_ids_from_context(C);
const bool set_fake_user = RNA_boolean_get(op->ptr, "set_fake_user");
AssetClearHelper clear_helper(set_fake_user);
clear_helper(ids);
@@ -333,9 +358,9 @@ static int asset_clear_exec(bContext *C, wmOperator *op)
return OPERATOR_FINISHED;
}
static bool asset_clear_poll(bContext *C)
static bool asset_clear_poll(bContext *C, const PointerRNAVec &ids)
{
IDVecStats ctx_stats = asset_operation_get_id_vec_stats_from_context(C);
IDVecStats ctx_stats = asset_operation_get_id_vec_stats_from_ids(ids);
if (!ctx_stats.has_asset) {
const char *msg_single = TIP_("Data-block is not marked as asset");
@@ -364,6 +389,9 @@ static std::string asset_clear_get_description(bContext * /*C*/,
"data-blocks, and set Fake User to ensure the data-blocks will still be saved");
}
/**
* Variant of #ASSET_OT_clear that only works on the "id" context member.
*/
static void ASSET_OT_clear(wmOperatorType *ot)
{
ot->name = "Clear Asset";
@@ -373,8 +401,36 @@ static void ASSET_OT_clear(wmOperatorType *ot)
ot->get_description = asset_clear_get_description;
ot->idname = "ASSET_OT_clear";
ot->exec = asset_clear_exec;
ot->poll = asset_clear_poll;
ot->exec = [](bContext *C, wmOperator *op) -> int {
return asset_clear_exec(C, op, asset_operation_get_ids_from_context(C));
};
ot->poll = [](bContext *C) -> bool {
return asset_clear_poll(C, asset_operation_get_ids_from_context(C));
};
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
RNA_def_boolean(ot->srna,
"set_fake_user",
false,
"Set Fake User",
"Ensure the data-block is saved, even when it is no longer marked as asset");
}
static void ASSET_OT_clear_single(wmOperatorType *ot)
{
ot->name = "Clear Single Asset";
ot->description =
"Delete all asset metadata and turn the asset data-block back into a normal data-block";
ot->get_description = asset_clear_get_description;
ot->idname = "ASSET_OT_clear_single";
ot->exec = [](bContext *C, wmOperator *op) -> int {
return asset_clear_exec(C, op, get_single_id_vec_from_context(C));
};
ot->poll = [](bContext *C) -> bool {
return asset_clear_poll(C, get_single_id_vec_from_context(C));
};
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
@@ -955,7 +1011,9 @@ static bool has_external_files(Main *bmain, ReportList *reports)
void ED_operatortypes_asset()
{
WM_operatortype_append(ASSET_OT_mark);
WM_operatortype_append(ASSET_OT_mark_single);
WM_operatortype_append(ASSET_OT_clear);
WM_operatortype_append(ASSET_OT_clear_single);
WM_operatortype_append(ASSET_OT_catalog_new);
WM_operatortype_append(ASSET_OT_catalog_delete);

View File

@@ -979,10 +979,10 @@ bool ui_popup_context_menu_for_button(bContext *C, uiBut *but, const wmEvent *ev
* which isn't cheap to check. */
uiLayout *sub = uiLayoutColumn(layout, true);
uiLayoutSetEnabled(sub, !id->asset_data);
uiItemO(sub, nullptr, ICON_ASSET_MANAGER, "ASSET_OT_mark");
uiItemO(sub, IFACE_("Mark as Asset"), ICON_ASSET_MANAGER, "ASSET_OT_mark_single");
sub = uiLayoutColumn(layout, true);
uiLayoutSetEnabled(sub, id->asset_data);
uiItemO(sub, nullptr, ICON_NONE, "ASSET_OT_clear");
uiItemO(sub, IFACE_("Clear Asset"), ICON_NONE, "ASSET_OT_clear_single");
uiItemS(layout);
}

View File

@@ -1471,7 +1471,7 @@ static void template_ID(const bContext *C,
uiDefIconButO(block,
/* Using `_N` version allows us to get the 'active' state by default. */
UI_BTYPE_ICON_TOGGLE_N,
"ASSET_OT_clear",
"ASSET_OT_clear_single",
WM_OP_INVOKE_DEFAULT,
/* 'active' state of a toggle button uses icon + 1, so to get proper asset
* icon we need to pass its value - 1 here. */