Files
test2/source/blender/editors/animation/anim_asset_ops.cc
Damien Picard 90e2dfd2e3 I18n: Translate dynamic operator descriptions
The descriptions for `POSELIB_OT_asset_modify` and
`GEOMETRY_OT_execute_node_group` are dynamic. They were already
extracted, but the translation did not happen in the description
function.

This commit adds the appropriate `TIP_` translation macro.

Reported by Ye Gui in #43295.
2025-10-15 16:26:46 +02:00

815 lines
27 KiB
C++

/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_listbase.h"
#include "BKE_asset.hh"
#include "BKE_asset_edit.hh"
#include "BKE_context.hh"
#include "BKE_fcurve.hh"
#include "BKE_global.hh"
#include "BKE_icons.h"
#include "BKE_lib_id.hh"
#include "BKE_preferences.h"
#include "BKE_report.hh"
#include "BKE_screen.hh"
#include "WM_api.hh"
#include "RNA_access.hh"
#include "RNA_define.hh"
#include "RNA_prototypes.hh"
#include "ED_asset.hh"
#include "ED_asset_library.hh"
#include "ED_asset_list.hh"
#include "ED_asset_mark_clear.hh"
#include "ED_asset_menu_utils.hh"
#include "ED_asset_shelf.hh"
#include "ED_fileselect.hh"
#include "ED_screen.hh"
#include "UI_interface_icons.hh"
#include "UI_resources.hh"
#include "BLT_translation.hh"
#include "ANIM_action.hh"
#include "ANIM_action_iterators.hh"
#include "ANIM_armature.hh"
#include "ANIM_keyframing.hh"
#include "ANIM_pose.hh"
#include "ANIM_rna.hh"
#include "AS_asset_catalog.hh"
#include "AS_asset_catalog_tree.hh"
#include "AS_asset_library.hh"
#include "AS_asset_representation.hh"
#include "anim_intern.hh"
namespace blender::ed::animrig {
static const EnumPropertyItem *rna_asset_library_reference_itemf(bContext * /*C*/,
PointerRNA * /*ptr*/,
PropertyRNA * /*prop*/,
bool *r_free)
{
const EnumPropertyItem *items = blender::ed::asset::library_reference_to_rna_enum_itemf(false,
true);
*r_free = true;
BLI_assert(items != nullptr);
return items;
}
static Vector<RNAPath> construct_pose_rna_paths(const PointerRNA &bone_pointer)
{
BLI_assert(bone_pointer.type == &RNA_PoseBone);
blender::Vector<RNAPath> paths;
paths.append({"location"});
paths.append({"scale"});
bPoseChannel *pose_bone = static_cast<bPoseChannel *>(bone_pointer.data);
switch (pose_bone->rotmode) {
case ROT_MODE_QUAT:
paths.append({"rotation_quaternion"});
break;
case ROT_MODE_AXISANGLE:
paths.append({"rotation_axis_angle"});
break;
case ROT_MODE_XYZ:
case ROT_MODE_XZY:
case ROT_MODE_YXZ:
case ROT_MODE_YZX:
case ROT_MODE_ZXY:
case ROT_MODE_ZYX:
paths.append({"rotation_euler"});
default:
break;
}
paths.extend({{"bbone_curveinx"},
{"bbone_curveoutx"},
{"bbone_curveinz"},
{"bbone_curveoutz"},
{"bbone_rollin"},
{"bbone_rollout"},
{"bbone_scalein"},
{"bbone_scaleout"},
{"bbone_easein"},
{"bbone_easeout"}});
paths.extend(blender::animrig::get_keyable_id_property_paths(bone_pointer));
return paths;
}
static blender::animrig::Action &extract_pose(Main &bmain,
const blender::Span<Object *> pose_objects)
{
/* This currently only looks at the pose and not other things that could go onto different
* slots on the same action. */
using namespace blender::animrig;
Action &action = action_add(bmain, "pose_create");
Layer &layer = action.layer_add("pose");
Strip &strip = layer.strip_add(action, Strip::Type::Keyframe);
StripKeyframeData &strip_data = strip.data<StripKeyframeData>(action);
const KeyframeSettings key_settings = {BEZT_KEYTYPE_KEYFRAME, HD_AUTO, BEZT_IPO_BEZ};
for (Object *pose_object : pose_objects) {
BLI_assert(pose_object->pose);
Slot &slot = action.slot_add_for_id(pose_object->id);
const bArmature *armature = static_cast<bArmature *>(pose_object->data);
Set<RNAPath> existing_paths;
if (pose_object->adt && pose_object->adt->action &&
pose_object->adt->slot_handle != Slot::unassigned)
{
Action &pose_object_action = pose_object->adt->action->wrap();
const slot_handle_t pose_object_slot = pose_object->adt->slot_handle;
foreach_fcurve_in_action_slot(pose_object_action, pose_object_slot, [&](FCurve &fcurve) {
RNAPath existing_path = {fcurve.rna_path, std::nullopt, fcurve.array_index};
existing_paths.add(existing_path);
});
}
LISTBASE_FOREACH (bPoseChannel *, pose_bone, &pose_object->pose->chanbase) {
if (!blender::animrig::bone_is_selected(armature, pose_bone)) {
continue;
}
PointerRNA bone_pointer = RNA_pointer_create_discrete(
&pose_object->id, &RNA_PoseBone, pose_bone);
Vector<RNAPath> rna_paths = construct_pose_rna_paths(bone_pointer);
for (const RNAPath &rna_path : rna_paths) {
PointerRNA resolved_pointer;
PropertyRNA *resolved_property;
if (!RNA_path_resolve(
&bone_pointer, rna_path.path.c_str(), &resolved_pointer, &resolved_property))
{
continue;
}
const Vector<float> values = blender::animrig::get_rna_values(&resolved_pointer,
resolved_property);
const std::optional<std::string> rna_path_id_to_prop = RNA_path_from_ID_to_property(
&resolved_pointer, resolved_property);
if (!rna_path_id_to_prop.has_value()) {
continue;
}
for (const int i : values.index_range()) {
if (RNA_property_is_idprop(resolved_property) &&
!existing_paths.contains({rna_path_id_to_prop.value(), std::nullopt, i}))
{
/* Skipping custom properties without animation. */
continue;
}
strip_data.keyframe_insert(
&bmain, slot, {rna_path_id_to_prop.value(), i}, {1, values[i]}, key_settings);
}
}
}
}
return action;
}
/**
* Check that the newly created asset is visible SOMEWHERE in Blender. If not already visible,
* open the asset shelf on the current 3D view. The reason for not always doing that is that it
* might be annoying in case you have 2 3D viewports open, but you want the asset shelf on only one
* of them, or you work out of the asset browser.
*/
static void ensure_asset_ui_visible(bContext &C)
{
ScrArea *current_area = CTX_wm_area(&C);
if (!current_area || current_area->type->spaceid != SPACE_VIEW3D) {
/* Opening the asset shelf will only work from the 3D viewport. */
return;
}
wmWindowManager *wm = CTX_wm_manager(&C);
LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
const bScreen *screen = WM_window_get_active_screen(win);
LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
if (area->type->spaceid == SPACE_FILE) {
SpaceFile *sfile = reinterpret_cast<SpaceFile *>(area->spacedata.first);
if (sfile->browse_mode == FILE_BROWSE_MODE_ASSETS) {
/* Asset Browser is open. */
return;
}
continue;
}
const ARegion *shelf_region = BKE_area_find_region_type(area, RGN_TYPE_ASSET_SHELF);
if (!shelf_region) {
continue;
}
if (shelf_region->runtime->visible) {
/* A visible asset shelf was found. */
return;
}
}
}
/* At this point, no asset shelf or asset browser was visible anywhere. */
ARegion *shelf_region = BKE_area_find_region_type(current_area, RGN_TYPE_ASSET_SHELF);
if (!shelf_region) {
return;
}
shelf_region->flag &= ~RGN_FLAG_HIDDEN;
ED_region_visibility_change_update(&C, CTX_wm_area(&C), shelf_region);
}
static blender::Vector<Object *> get_selected_pose_objects(bContext *C)
{
blender::Vector<PointerRNA> selected_objects;
CTX_data_selected_objects(C, &selected_objects);
blender::Vector<Object *> selected_pose_objects;
for (const PointerRNA &ptr : selected_objects) {
Object *object = reinterpret_cast<Object *>(ptr.owner_id);
if (!object->pose) {
continue;
}
selected_pose_objects.append(object);
}
Object *active_object = CTX_data_active_object(C);
/* The active object may not be selected, it should be added because you can still switch to pose
* mode. */
if (active_object && active_object->pose && !selected_pose_objects.contains(active_object)) {
selected_pose_objects.append(active_object);
}
return selected_pose_objects;
}
static wmOperatorStatus create_pose_asset_local(bContext *C,
wmOperator *op,
const StringRefNull name,
const AssetLibraryReference lib_ref)
{
blender::Vector<Object *> selected_pose_objects = get_selected_pose_objects(C);
if (selected_pose_objects.is_empty()) {
return OPERATOR_CANCELLED;
}
Main *bmain = CTX_data_main(C);
/* Extract the pose into a new action. */
blender::animrig::Action &pose_action = extract_pose(*bmain, selected_pose_objects);
asset::mark_id(&pose_action.id);
if (!G.background) {
asset::generate_preview(C, &pose_action.id);
}
BKE_id_rename(*bmain, pose_action.id, name);
/* Add asset to catalog. */
char catalog_path_c[MAX_NAME];
RNA_string_get(op->ptr, "catalog_path", catalog_path_c);
AssetMetaData &meta_data = *pose_action.id.asset_data;
asset_system::AssetLibrary *library = AS_asset_library_load(bmain, lib_ref);
/* NOTE(@ChrisLend): I don't know if a local library can fail to load.
* Just being defensive here. */
BLI_assert(library);
if (catalog_path_c[0] && library) {
const asset_system::AssetCatalogPath catalog_path(catalog_path_c);
asset_system::AssetCatalog &catalog = asset::library_ensure_catalogs_in_path(*library,
catalog_path);
BKE_asset_metadata_catalog_id_set(&meta_data, catalog.catalog_id, catalog.simple_name.c_str());
}
ensure_asset_ui_visible(*C);
asset::shelf::show_catalog_in_visible_shelves(*C, catalog_path_c);
asset::refresh_asset_library(C, lib_ref);
WM_main_add_notifier(NC_ASSET | ND_ASSET_LIST | NA_ADDED, nullptr);
return OPERATOR_FINISHED;
}
static wmOperatorStatus create_pose_asset_user_library(bContext *C,
wmOperator *op,
const char name[MAX_NAME],
const AssetLibraryReference lib_ref)
{
BLI_assert(lib_ref.type == ASSET_LIBRARY_CUSTOM);
Main *bmain = CTX_data_main(C);
const bUserAssetLibrary *user_library = BKE_preferences_asset_library_find_index(
&U, lib_ref.custom_library_index);
BLI_assert_msg(user_library, "The passed lib_ref is expected to be a user library");
if (!user_library) {
return OPERATOR_CANCELLED;
}
asset_system::AssetLibrary *library = AS_asset_library_load(bmain, lib_ref);
if (!library) {
BKE_report(op->reports, RPT_ERROR, "Failed to load asset library");
return OPERATOR_CANCELLED;
}
blender::Vector<Object *> selected_pose_objects = get_selected_pose_objects(C);
if (selected_pose_objects.is_empty()) {
return OPERATOR_CANCELLED;
}
/* Temporary action in current main that will be exported and later deleted. */
blender::animrig::Action &pose_action = extract_pose(*bmain, selected_pose_objects);
asset::mark_id(&pose_action.id);
if (!G.background) {
asset::generate_preview(C, &pose_action.id);
}
/* Add asset to catalog. */
char catalog_path_c[MAX_NAME];
RNA_string_get(op->ptr, "catalog_path", catalog_path_c);
AssetMetaData &meta_data = *pose_action.id.asset_data;
if (catalog_path_c[0]) {
const asset_system::AssetCatalogPath catalog_path(catalog_path_c);
const asset_system::AssetCatalog &catalog = asset::library_ensure_catalogs_in_path(
*library, catalog_path);
BKE_asset_metadata_catalog_id_set(&meta_data, catalog.catalog_id, catalog.simple_name.c_str());
}
AssetWeakReference pose_asset_reference;
const std::optional<std::string> final_full_asset_filepath = bke::asset_edit_id_save_as(
*bmain, pose_action.id, name, *user_library, pose_asset_reference, *op->reports);
library->catalog_service().write_to_disk(*final_full_asset_filepath);
ensure_asset_ui_visible(*C);
asset::shelf::show_catalog_in_visible_shelves(*C, catalog_path_c);
BKE_id_free(bmain, &pose_action.id);
asset::refresh_asset_library(C, lib_ref);
WM_main_add_notifier(NC_ASSET | ND_ASSET_LIST | NA_ADDED, nullptr);
return OPERATOR_FINISHED;
}
static wmOperatorStatus pose_asset_create_exec(bContext *C, wmOperator *op)
{
char name[MAX_NAME] = "";
PropertyRNA *name_prop = RNA_struct_find_property(op->ptr, "pose_name");
if (RNA_property_is_set(op->ptr, name_prop)) {
RNA_property_string_get(op->ptr, name_prop, name);
}
if (name[0] == '\0') {
BKE_report(op->reports, RPT_ERROR, "No name set");
return OPERATOR_CANCELLED;
}
const int enum_value = RNA_enum_get(op->ptr, "asset_library_reference");
const AssetLibraryReference lib_ref = asset::library_reference_from_enum_value(enum_value);
switch (lib_ref.type) {
case ASSET_LIBRARY_LOCAL:
return create_pose_asset_local(C, op, name, lib_ref);
case ASSET_LIBRARY_CUSTOM:
return create_pose_asset_user_library(C, op, name, lib_ref);
default:
/* Only local and custom libraries should be exposed in the enum. */
BLI_assert_unreachable();
break;
}
BKE_report(op->reports, RPT_ERROR, "Unexpected library type. Failed to create pose asset");
return OPERATOR_FINISHED;
}
static wmOperatorStatus pose_asset_create_invoke(bContext *C,
wmOperator *op,
const wmEvent * /*event*/)
{
/* If the library isn't saved from the operator's last execution, use the first library. */
if (!RNA_struct_property_is_set_ex(op->ptr, "asset_library_reference", false)) {
const AssetLibraryReference first_library = asset::user_library_to_library_ref(
*static_cast<const bUserAssetLibrary *>(U.asset_libraries.first));
RNA_enum_set(op->ptr,
"asset_library_reference",
asset::library_reference_to_enum_value(&first_library));
}
return WM_operator_props_dialog_popup(C, op, 400, std::nullopt, IFACE_("Create"));
}
static bool pose_asset_create_poll(bContext *C)
{
if (!ED_operator_posemode_context(C)) {
return false;
}
return true;
}
static void visit_library_prop_catalogs_catalog_for_search_fn(
const bContext *C,
PointerRNA *ptr,
PropertyRNA * /*prop*/,
const char *edit_text,
FunctionRef<void(StringPropertySearchVisitParams)> visit_fn)
{
const int enum_value = RNA_enum_get(ptr, "asset_library_reference");
const AssetLibraryReference lib_ref = asset::library_reference_from_enum_value(enum_value);
asset::visit_library_catalogs_catalog_for_search(
*CTX_data_main(C), lib_ref, edit_text, visit_fn);
}
void POSELIB_OT_create_pose_asset(wmOperatorType *ot)
{
ot->name = "Create Pose Asset...";
ot->description = "Create a new asset from the selected bones in the scene";
ot->idname = "POSELIB_OT_create_pose_asset";
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
ot->exec = pose_asset_create_exec;
ot->invoke = pose_asset_create_invoke;
ot->poll = pose_asset_create_poll;
ot->prop = RNA_def_string(
ot->srna, "pose_name", nullptr, MAX_NAME, "Pose Name", "Name for the new pose asset");
PropertyRNA *prop = RNA_def_property(ot->srna, "asset_library_reference", PROP_ENUM, PROP_NONE);
RNA_def_enum_funcs(prop, rna_asset_library_reference_itemf);
RNA_def_property_ui_text(prop, "Library", "Asset library used to store the new pose");
prop = RNA_def_string(
ot->srna, "catalog_path", nullptr, MAX_NAME, "Catalog", "Catalog to use for the new asset");
RNA_def_property_string_search_func_runtime(
prop, visit_library_prop_catalogs_catalog_for_search_fn, PROP_STRING_SEARCH_SUGGESTION);
}
enum AssetModifyMode {
MODIFY_ADJUST = 0,
MODIFY_REPLACE,
MODIFY_ADD,
MODIFY_REMOVE,
};
static const EnumPropertyItem prop_asset_overwrite_modes[] = {
{MODIFY_ADJUST,
"ADJUST",
0,
"Adjust",
"Update existing channels in the pose asset but don't remove or add any channels"},
{MODIFY_REPLACE,
"REPLACE",
0,
"Replace with Selection",
"Completely replace all channels in the pose asset with the current selection"},
{MODIFY_ADD,
"ADD",
0,
"Add Selected Bones",
"Add channels of the selection to the pose asset. Existing channels will be updated"},
{MODIFY_REMOVE,
"REMOVE",
0,
"Remove Selected Bones",
"Remove channels of the selection from the pose asset"},
{0, nullptr, 0, nullptr, nullptr},
};
/* Gets the selected asset from the given `bContext`. If the asset is an action, returns a pointer
* to that action, else returns a nullptr. */
static bAction *get_action_of_selected_asset(bContext *C)
{
const blender::asset_system::AssetRepresentation *asset = CTX_wm_asset(C);
if (!asset) {
return nullptr;
}
if (asset->get_id_type() != ID_AC) {
return nullptr;
}
AssetWeakReference asset_reference = asset->make_weak_reference();
Main *bmain = CTX_data_main(C);
return reinterpret_cast<bAction *>(
bke::asset_edit_id_from_weak_reference(*bmain, ID_AC, asset_reference));
}
struct PathValue {
RNAPath rna_path;
float value;
};
static Vector<PathValue> generate_path_values(Object &pose_object)
{
Vector<PathValue> path_values;
const bArmature *armature = static_cast<bArmature *>(pose_object.data);
LISTBASE_FOREACH (bPoseChannel *, pose_bone, &pose_object.pose->chanbase) {
if (!blender::animrig::bone_is_selected(armature, pose_bone)) {
continue;
}
PointerRNA bone_pointer = RNA_pointer_create_discrete(
&pose_object.id, &RNA_PoseBone, pose_bone);
Vector<RNAPath> rna_paths = construct_pose_rna_paths(bone_pointer);
for (RNAPath &rna_path : rna_paths) {
PointerRNA resolved_pointer;
PropertyRNA *resolved_property;
if (!RNA_path_resolve(
&bone_pointer, rna_path.path.c_str(), &resolved_pointer, &resolved_property))
{
continue;
}
const std::optional<std::string> rna_path_id_to_prop = RNA_path_from_ID_to_property(
&resolved_pointer, resolved_property);
if (!rna_path_id_to_prop.has_value()) {
continue;
}
Vector<float> values = blender::animrig::get_rna_values(&resolved_pointer,
resolved_property);
int i = 0;
for (const float value : values) {
RNAPath path = {rna_path_id_to_prop.value(), std::nullopt, i};
path_values.append({path, value});
i++;
}
}
}
return path_values;
}
static inline void replace_pose_key(Main &bmain,
blender::animrig::StripKeyframeData &strip_data,
const blender::animrig::Slot &slot,
const float2 time_value,
const blender::animrig::FCurveDescriptor &fcurve_descriptor)
{
using namespace blender::animrig;
Channelbag &channelbag = strip_data.channelbag_for_slot_ensure(slot);
FCurve &fcurve = channelbag.fcurve_ensure(&bmain, fcurve_descriptor);
/* Clearing all keys beforehand in case the pose was not defined on frame defined in
* `time_value`. */
BKE_fcurve_delete_keys_all(&fcurve);
const KeyframeSettings key_settings = {BEZT_KEYTYPE_KEYFRAME, HD_AUTO, BEZT_IPO_BEZ};
insert_vert_fcurve(&fcurve, time_value, key_settings, INSERTKEY_NOFLAGS);
}
static void update_pose_action_from_scene(Main *bmain,
blender::animrig::Action &pose_action,
Object &pose_object,
const AssetModifyMode mode)
{
using namespace blender::animrig;
/* The frame on which an FCurve has a key to define a pose. */
constexpr int pose_frame = 1;
if (pose_action.slot_array_num < 1) {
/* All actions should have slots at this point. */
BLI_assert_unreachable();
return;
}
Slot &slot = blender::animrig::get_best_pose_slot_for_id(pose_object.id, pose_action);
BLI_assert(pose_action.strip_keyframe_data().size() == 1);
BLI_assert(pose_action.layers().size() == 1);
StripKeyframeData *strip_data = pose_action.strip_keyframe_data()[0];
Vector<PathValue> path_values = generate_path_values(pose_object);
Set<RNAPath> existing_paths;
foreach_fcurve_in_action_slot(pose_action, slot.handle, [&](FCurve &fcurve) {
existing_paths.add({fcurve.rna_path, std::nullopt, fcurve.array_index});
});
switch (mode) {
case MODIFY_ADJUST: {
for (const PathValue &path_value : path_values) {
/* Only updating existing channels. */
if (existing_paths.contains(path_value.rna_path)) {
replace_pose_key(*bmain,
*strip_data,
slot,
{pose_frame, path_value.value},
{path_value.rna_path.path, path_value.rna_path.index.value()});
}
}
break;
}
case MODIFY_ADD: {
for (const PathValue &path_value : path_values) {
replace_pose_key(*bmain,
*strip_data,
slot,
{pose_frame, path_value.value},
{path_value.rna_path.path, path_value.rna_path.index.value()});
}
break;
}
case MODIFY_REPLACE: {
Channelbag *channelbag = strip_data->channelbag_for_slot(slot.handle);
if (!channelbag) {
/* No channels to remove. */
return;
}
channelbag->fcurves_clear();
for (const PathValue &path_value : path_values) {
replace_pose_key(*bmain,
*strip_data,
slot,
{pose_frame, path_value.value},
{path_value.rna_path.path, path_value.rna_path.index.value()});
}
break;
}
case MODIFY_REMOVE: {
Channelbag *channelbag = strip_data->channelbag_for_slot(slot.handle);
if (!channelbag) {
/* No channels to remove. */
return;
}
Map<RNAPath, FCurve *> fcurve_map;
foreach_fcurve_in_action_slot(
pose_action, pose_action.slot_array[0]->handle, [&](FCurve &fcurve) {
fcurve_map.add({fcurve.rna_path, std::nullopt, fcurve.array_index}, &fcurve);
});
for (const PathValue &path_value : path_values) {
if (existing_paths.contains(path_value.rna_path)) {
FCurve *fcurve = fcurve_map.lookup(path_value.rna_path);
channelbag->fcurve_remove(*fcurve);
}
}
break;
}
}
}
static wmOperatorStatus pose_asset_modify_exec(bContext *C, wmOperator *op)
{
bAction *action = get_action_of_selected_asset(C);
BLI_assert_msg(action, "Poll should have checked action exists");
/* Get asset now. Asset browser might get tagged for refreshing through operations below, and not
* allow querying items from context until refreshed, see #140781. */
const asset_system::AssetRepresentation *asset = CTX_wm_asset(C);
Main *bmain = CTX_data_main(C);
Object *pose_object = CTX_data_active_object(C);
if (!pose_object || !pose_object->pose) {
return OPERATOR_CANCELLED;
}
AssetModifyMode mode = AssetModifyMode(RNA_enum_get(op->ptr, "mode"));
update_pose_action_from_scene(bmain, action->wrap(), *pose_object, mode);
if (!G.background) {
asset::generate_preview(C, &action->id);
}
if (ID_IS_LINKED(action)) {
/* Not needed for local assets. */
bke::asset_edit_id_save(*bmain, action->id, *op->reports);
}
asset::refresh_asset_library_from_asset(C, *asset);
WM_main_add_notifier(NC_ASSET | ND_ASSET_LIST | NA_EDITED, nullptr);
return OPERATOR_FINISHED;
}
static bool pose_asset_modify_poll(bContext *C)
{
if (!ED_operator_posemode_context(C)) {
CTX_wm_operator_poll_msg_set(C, "Pose assets can only be modified from Pose Mode");
return false;
}
bAction *action = get_action_of_selected_asset(C);
if (!action) {
return false;
}
if (!ID_IS_LINKED(action)) {
return true;
}
if (!bke::asset_edit_id_is_editable(action->id)) {
CTX_wm_operator_poll_msg_set(C, "Action is not editable");
return false;
}
if (!bke::asset_edit_id_is_writable(action->id)) {
CTX_wm_operator_poll_msg_set(C, "Asset blend file is not editable");
return false;
}
return true;
}
static std::string pose_asset_modify_description(bContext * /* C */,
wmOperatorType * /* ot */,
PointerRNA *ptr)
{
const int mode = RNA_enum_get(ptr, "mode");
return TIP_(std::string(prop_asset_overwrite_modes[mode].description));
}
/* Calling it overwrite instead of save because we aren't actually saving an opened asset. */
void POSELIB_OT_asset_modify(wmOperatorType *ot)
{
ot->name = "Modify Pose Asset";
ot->description =
"Update the selected pose asset in the asset library from the currently selected bones. The "
"mode defines how the asset is updated";
ot->idname = "POSELIB_OT_asset_modify";
ot->exec = pose_asset_modify_exec;
ot->poll = pose_asset_modify_poll;
ot->get_description = pose_asset_modify_description;
RNA_def_enum(ot->srna,
"mode",
prop_asset_overwrite_modes,
MODIFY_ADJUST,
"Overwrite Mode",
"Specify which parts of the pose asset are overwritten");
}
static bool pose_asset_delete_poll(bContext *C)
{
bAction *action = get_action_of_selected_asset(C);
if (!action) {
return false;
}
if (!ID_IS_LINKED(action)) {
return true;
}
if (!bke::asset_edit_id_is_editable(action->id)) {
CTX_wm_operator_poll_msg_set(C, "Action is not editable");
return false;
}
if (!bke::asset_edit_id_is_writable(action->id)) {
CTX_wm_operator_poll_msg_set(C, "Asset blend file is not editable");
return false;
}
return true;
}
static wmOperatorStatus pose_asset_delete_exec(bContext *C, wmOperator *op)
{
bAction *action = get_action_of_selected_asset(C);
if (!action) {
return OPERATOR_CANCELLED;
}
const blender::asset_system::AssetRepresentation *asset = CTX_wm_asset(C);
std::optional<AssetLibraryReference> library_ref =
asset->owner_asset_library().library_reference();
if (ID_IS_LINKED(action)) {
bke::asset_edit_id_delete(*CTX_data_main(C), action->id, *op->reports);
}
else {
asset::clear_id(&action->id);
}
asset::refresh_asset_library(C, library_ref.value());
WM_main_add_notifier(NC_ASSET | ND_ASSET_LIST | NA_REMOVED, nullptr);
return OPERATOR_FINISHED;
}
static wmOperatorStatus pose_asset_delete_invoke(bContext *C,
wmOperator *op,
const wmEvent * /*event*/)
{
bAction *action = get_action_of_selected_asset(C);
return WM_operator_confirm_ex(
C,
op,
IFACE_("Delete Pose Asset"),
ID_IS_LINKED(action) ?
IFACE_("Permanently delete pose asset blend file? This cannot be undone.") :
IFACE_("The asset is local to the file. Deleting it will just clear the asset status."),
IFACE_("Delete"),
ALERT_ICON_WARNING,
false);
}
void POSELIB_OT_asset_delete(wmOperatorType *ot)
{
ot->name = "Delete Pose Asset";
ot->description = "Delete the selected Pose Asset";
ot->idname = "POSELIB_OT_asset_delete";
ot->poll = pose_asset_delete_poll;
ot->invoke = pose_asset_delete_invoke;
ot->exec = pose_asset_delete_exec;
}
} // namespace blender::ed::animrig