Shape Keys: Apply to Basis operator

As described in #150299, this operator applies the deformations
of the selected shape keys to the basis key and then removes them.

Pull Request: https://projects.blender.org/blender/blender/pulls/150540
This commit is contained in:
Hans Goudey 2026-02-05 16:24:18 +01:00 • committed by Hans Goudey
parent 7022cfd007
commit 56049e5a23
7 changed files with 168 additions and 16 deletions

View file

@ -75,6 +75,7 @@ class MESH_MT_shape_key_context_menu(Menu):
layout.operator("object.shape_key_lock", icon='UNLOCKED', text="Unlock All").action = 'UNLOCK'
layout.separator()
layout.operator("object.shape_key_make_basis", text="Make Basis")
layout.operator("object.shape_key_apply_to_basis", text="Apply to Basis")
layout.separator()
props = layout.operator("object.shape_key_remove", text="Apply All")
props.all = True
@ -90,6 +91,7 @@ class MESH_MT_shape_key_tree_context_menu(Menu):
def draw(self, _context):
layout = self.layout
layout.operator("object.shape_key_make_basis", text="Make Basis")
layout.operator("object.shape_key_apply_to_basis", text="Apply to Basis")
layout.operator("object.shape_key_copy", icon='DUPLICATE', text="Duplicate")
layout.separator()
layout.operator("object.shape_key_move", icon='TRIA_UP_BAR', text="Move After Basis").type = 'TOP'

View file

@ -52,9 +52,14 @@ void key_curve_normal_weights(float t, float data[4], KeyInterpolationType type)
* Returns key coordinates (+ tilt) when key applied, NULL otherwise.
*
* \param obdata: if given, also update that geometry with the result of the shape keys evaluation.
* \param keys_to_process: If provided, only evaluate the keys at indices set to true.
*/
float *BKE_key_evaluate_object_ex(
Object *ob, int *r_totelem, float *arr, size_t arr_size, ID *obdata);
float *BKE_key_evaluate_object_ex(Object *ob,
int *r_totelem,
float *arr,
size_t arr_size,
std::optional<Span<bool>> keys_to_process,
ID *obdata);
float *BKE_key_evaluate_object(Object *ob, int *r_totelem);
/**

View file

@ -674,6 +674,7 @@ static void copy_key_float3_weighted(const int vertex_count,
*/
static void key_evaluate_relative_float3(Key *key,
KeyBlock *active_keyblock,
const std::optional<Span<bool>> keys_to_process,
const int vertex_count,
float **per_keyblock_weights,
float *target_data)
@ -688,6 +689,9 @@ static void key_evaluate_relative_float3(Key *key,
if (&kb == key->refkey) {
continue;
}
if (keys_to_process && !(*keys_to_process)[enumerator.first]) {
continue;
}
/* No difference in vertex count allowed. */
if (kb.flag & KEYBLOCK_MUTE || kb.totelem != vertex_count) {
continue;
@ -892,7 +896,11 @@ static void keyblock_free_per_block_weights(Key *key,
MEM_delete(per_keyblock_weights);
}
static void do_mesh_key(Object *ob, Key *key, char *out, const int tot)
static void do_mesh_key(Object *ob,
Key *key,
const std::optional<Span<bool>> keys_to_process,
char *out,
const int tot)
{
KeyBlock *actkb = BKE_keyblock_from_object(ob);
@ -901,7 +909,7 @@ static void do_mesh_key(Object *ob, Key *key, char *out, const int tot)
float **per_keyblock_weights;
per_keyblock_weights = keyblock_get_per_block_weights(ob, key, &cache);
key_evaluate_relative_float3(
key, actkb, tot, per_keyblock_weights, reinterpret_cast<float *>(out));
key, actkb, keys_to_process, tot, per_keyblock_weights, reinterpret_cast<float *>(out));
keyblock_free_per_block_weights(key, per_keyblock_weights, &cache);
}
else {
@ -920,12 +928,17 @@ static void do_mesh_key(Object *ob, Key *key, char *out, const int tot)
}
}
static void do_curve_key(Object *ob, Key *key, char *out, const int tot)
static void do_curve_key(Object *ob,
Key *key,
const std::optional<Span<bool>> keys_to_process,
char *out,
const int tot)
{
KeyBlock *actkb = BKE_keyblock_from_object(ob);
if (key->type == KEY_RELATIVE) {
key_evaluate_relative_float3(key, actkb, tot, nullptr, reinterpret_cast<float *>(out));
key_evaluate_relative_float3(
key, actkb, keys_to_process, tot, nullptr, reinterpret_cast<float *>(out));
}
else {
const float ctime_scaled = key->ctime / 100.0f;
@ -943,7 +956,11 @@ static void do_curve_key(Object *ob, Key *key, char *out, const int tot)
}
}
static void do_latt_key(Object *ob, Key *key, char *out, const int tot)
static void do_latt_key(Object *ob,
Key *key,
const std::optional<Span<bool>> keys_to_process,
char *out,
const int tot)
{
Lattice *lt = id_cast<Lattice *>(ob->data);
KeyBlock *actkb = BKE_keyblock_from_object(ob);
@ -952,7 +969,7 @@ static void do_latt_key(Object *ob, Key *key, char *out, const int tot)
float **per_keyblock_weights;
per_keyblock_weights = keyblock_get_per_block_weights(ob, key, nullptr);
key_evaluate_relative_float3(
key, actkb, tot, per_keyblock_weights, reinterpret_cast<float *>(out));
key, actkb, keys_to_process, tot, per_keyblock_weights, reinterpret_cast<float *>(out));
keyblock_free_per_block_weights(key, per_keyblock_weights, nullptr);
}
else {
@ -982,8 +999,12 @@ static void keyblock_data_convert_to_curve(const float *fp,
ListBaseT<Nurb> *nurb,
const int totpoint);
float *BKE_key_evaluate_object_ex(
Object *ob, int *r_totelem, float *arr, size_t arr_size, ID *obdata)
float *BKE_key_evaluate_object_ex(Object *ob,
int *r_totelem,
float *arr,
size_t arr_size,
const std::optional<Span<bool>> keys_to_process,
ID *obdata)
{
Key *key = BKE_key_from_object(ob);
KeyBlock *actkb = BKE_keyblock_from_object(ob);
@ -1058,16 +1079,16 @@ float *BKE_key_evaluate_object_ex(
}
else {
if (ob->type == OB_MESH) {
do_mesh_key(ob, key, out, tot);
do_mesh_key(ob, key, keys_to_process, out, tot);
}
else if (ob->type == OB_LATTICE) {
do_latt_key(ob, key, out, tot);
do_latt_key(ob, key, keys_to_process, out, tot);
}
else if (ob->type == OB_CURVES_LEGACY) {
do_curve_key(ob, key, out, tot);
do_curve_key(ob, key, keys_to_process, out, tot);
}
else if (ob->type == OB_SURF) {
do_curve_key(ob, key, out, tot);
do_curve_key(ob, key, keys_to_process, out, tot);
}
}
@ -1108,7 +1129,7 @@ float *BKE_key_evaluate_object_ex(
float *BKE_key_evaluate_object(Object *ob, int *r_totelem)
{
return BKE_key_evaluate_object_ex(ob, r_totelem, nullptr, 0, nullptr);
return BKE_key_evaluate_object_ex(ob, r_totelem, nullptr, 0, std::nullopt, nullptr);
}
int BKE_keyblock_element_count_from_shape(const Key *key, const int shape_index)

View file

@ -330,6 +330,7 @@ void OBJECT_OT_shape_key_mirror(wmOperatorType *ot);
void OBJECT_OT_shape_key_move(wmOperatorType *ot);
void OBJECT_OT_shape_key_lock(wmOperatorType *ot);
void OBJECT_OT_shape_key_make_basis(wmOperatorType *ot);
void OBJECT_OT_shape_key_apply_to_basis(wmOperatorType *ot);
/* `object_collection.cc` */

View file

@ -233,6 +233,7 @@ void operatortypes_object()
WM_operatortype_append(OBJECT_OT_shape_key_move);
WM_operatortype_append(OBJECT_OT_shape_key_lock);
WM_operatortype_append(OBJECT_OT_shape_key_make_basis);
WM_operatortype_append(OBJECT_OT_shape_key_apply_to_basis);
WM_operatortype_append(OBJECT_OT_collection_add);
WM_operatortype_append(OBJECT_OT_collection_link);

View file

@ -418,7 +418,7 @@ static wmOperatorStatus shape_key_remove_exec(bContext *C, wmOperator *op)
}
if (RNA_boolean_get(op->ptr, "apply_mix")) {
float *arr = BKE_key_evaluate_object_ex(ob, nullptr, nullptr, 0, ob->data);
float *arr = BKE_key_evaluate_object_ex(ob, nullptr, nullptr, 0, std::nullopt, ob->data);
MEM_delete(arr);
}
changed = BKE_object_shapekey_free(bmain, ob);
@ -921,4 +921,125 @@ void OBJECT_OT_shape_key_make_basis(wmOperatorType *ot)
/** \} */
/* -------------------------------------------------------------------- */
/** \name Shape Key Make Basis Operator
* \{ */
static bool shape_key_apply_to_basis_poll(bContext *C)
{
if (!shape_key_exists_poll(C)) {
return false;
}
Object *ob = context_object(C);
if (ob->type != OB_MESH) {
return false;
}
/* 0 = nothing active, 1 = basis key active. */
return ob->shapenr > 1;
}
static void add_arrays(const MutableSpan<float3> a, const Span<float3> b)
{
BLI_assert(a.size() == b.size());
threading::parallel_for(a.index_range(), 2048, [&](const IndexRange range) {
for (const int i : range) {
a[i] += b[i];
}
});
}
static wmOperatorStatus shape_key_apply_to_basis_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
Object *ob = CTX_data_active_object(C);
Key *key = BKE_key_from_object(ob);
KeyBlock *basis_key = static_cast<KeyBlock *>(key->block.first);
MutableSpan<float3> basis_data(static_cast<float3 *>(basis_key->data), basis_key->totelem);
Mesh &mesh = id_cast<Mesh &>(*ob->data);
MutableSpan<float3> positions = mesh.vert_positions_for_write();
int locked_count = 0;
Array<bool> keys_to_process(BLI_listbase_count(&key->block), false);
for (const auto [i, kb] : key->block.enumerate()) {
if (!shape_key_is_selected(*ob, kb, i)) {
continue;
}
if (kb.flag & KEYBLOCK_LOCKED_SHAPE) {
locked_count++;
continue;
}
keys_to_process[i] = true;
}
if (locked_count != 0) {
BKE_reportf(op->reports, RPT_INFO, "Skipped %d locked shape keys", locked_count);
}
if (!keys_to_process.as_span().contains(true)) {
return OPERATOR_CANCELLED;
}
int totelem_dummy;
BKE_key_evaluate_object_ex(ob,
&totelem_dummy,
positions.cast<float>().data(),
sizeof(float3) * positions.size(),
keys_to_process,
nullptr);
Array<float3> translations(positions.size());
for (const int i : positions.index_range()) {
translations[i] = positions[i] - basis_data[i];
}
basis_data.copy_from(positions);
Set<KeyBlock *> processed_keys;
for (const auto [i, kb] : key->block.enumerate()) {
if (keys_to_process[i]) {
processed_keys.add_new(&kb);
}
}
for (KeyBlock *kb : processed_keys) {
BKE_object_shapekey_remove(bmain, ob, kb);
}
if (const std::optional<Array<bool>> dependent = BKE_keyblock_get_dependent_keys(key, 0)) {
for (const auto [i, kb] : key->block.enumerate()) {
if (&kb == basis_key) {
continue;
}
if (!(*dependent)[i]) {
continue;
}
MutableSpan<float3> kb_data(static_cast<float3 *>(kb.data), kb.totelem);
add_arrays(kb_data, translations);
}
}
/* Make the basis key active. */
ob->shapenr = 1;
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob);
return OPERATOR_FINISHED;
}
void OBJECT_OT_shape_key_apply_to_basis(wmOperatorType *ot)
{
ot->name = "Apply to Basis Key";
ot->idname = "OBJECT_OT_shape_key_apply_to_basis";
ot->description = "Appply deformations of selected shape keys to the basis key, removing them";
ot->poll = shape_key_apply_to_basis_poll;
ot->exec = shape_key_apply_to_basis_exec;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/** \} */
} // namespace blender::ed::object

View file

@ -36,6 +36,7 @@ static void deform_verts(ModifierData * /*md*/,
&deformedVerts_tot,
reinterpret_cast<float *>(positions.data()),
sizeof(float3) * positions.size(),
std::nullopt,
nullptr);
}
}