From db4d9067d02c9d1654ba33769e576030d221f5f7 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Tue, 23 Sep 2025 22:12:10 +0200 Subject: [PATCH] Shape Keys: Options to mirror shape keys in update, join operators As described in #135095, this adds a "mirror" option to the "Join as Shapes" and "Update from Objects" operators, and additional menu items with the option enabled. Like the operators, this is a convenience feature that's functionally the same as selecting all shape keys with changed data and running the existing "Flip" operator. Pull Request: https://projects.blender.org/blender/blender/pulls/144098 --- scripts/startup/bl_ui/properties_data_mesh.py | 2 + source/blender/editors/include/ED_mesh.hh | 1 + source/blender/editors/include/ED_object.hh | 2 + source/blender/editors/mesh/meshtools.cc | 42 +++- source/blender/editors/object/object_add.cc | 14 +- .../blender/editors/object/object_shapekey.cc | 185 +++++++++--------- 6 files changed, 151 insertions(+), 95 deletions(-) diff --git a/scripts/startup/bl_ui/properties_data_mesh.py b/scripts/startup/bl_ui/properties_data_mesh.py index 6e47131c4da..e2860b835e3 100644 --- a/scripts/startup/bl_ui/properties_data_mesh.py +++ b/scripts/startup/bl_ui/properties_data_mesh.py @@ -64,7 +64,9 @@ class MESH_MT_shape_key_context_menu(Menu): layout.operator("object.shape_key_transfer", text="Copy from Objects") layout.separator() layout.operator("object.join_shapes", text="New from Objects") + layout.operator("object.join_shapes", text="New from Objects Flipped").use_mirror = True layout.operator("object.update_shapes", icon='FILE_REFRESH') + layout.operator("object.update_shapes", text="Update from Objects Flipped").use_mirror = True layout.separator() layout.operator("object.shape_key_mirror", icon='ARROW_LEFTRIGHT', text="Flip").use_topology = False layout.operator("object.shape_key_mirror", text="Flip (Topology)").use_topology = True diff --git a/source/blender/editors/include/ED_mesh.hh b/source/blender/editors/include/ED_mesh.hh index 3154b18fed7..7d53f0864e8 100644 --- a/source/blender/editors/include/ED_mesh.hh +++ b/source/blender/editors/include/ED_mesh.hh @@ -582,6 +582,7 @@ wmOperatorStatus join_objects_exec(bContext *C, wmOperator *op); wmOperatorStatus ED_mesh_shapes_join_objects_exec(bContext *C, bool ensure_keys_exist, + bool mirror, ReportList *reports); /* Mirror lookup API. */ diff --git a/source/blender/editors/include/ED_object.hh b/source/blender/editors/include/ED_object.hh index 94d76e7c177..b1dbbc52e2e 100644 --- a/source/blender/editors/include/ED_object.hh +++ b/source/blender/editors/include/ED_object.hh @@ -110,6 +110,8 @@ bool shape_key_report_if_any_locked(Object *ob, ReportList *reports); */ bool shape_key_is_selected(const Object &object, const KeyBlock &kb, int keyblock_index); +void shape_key_mirror(Object *ob, KeyBlock *kb, bool use_topology, int &totmirr, int &totfail); + /* `object_utils.cc` */ bool calc_active_center_for_editmode(Object *obedit, bool select_only, float r_center[3]); diff --git a/source/blender/editors/mesh/meshtools.cc b/source/blender/editors/mesh/meshtools.cc index 7809bc96b2d..e0a8546f5e3 100644 --- a/source/blender/editors/mesh/meshtools.cc +++ b/source/blender/editors/mesh/meshtools.cc @@ -70,8 +70,24 @@ using blender::Span; * Add vertex positions of selected meshes as shape keys to the active mesh. * \{ */ +static std::string create_mirrored_name(const blender::StringRefNull object_name, + const bool mirror) +{ + if (!mirror) { + return object_name; + } + if (object_name.endswith(".L")) { + return blender::StringRef(object_name).drop_suffix(2) + ".R"; + } + if (object_name.endswith(".R")) { + return blender::StringRef(object_name).drop_suffix(2) + ".L"; + } + return object_name; +} + wmOperatorStatus ED_mesh_shapes_join_objects_exec(bContext *C, const bool ensure_keys_exist, + const bool mirror, ReportList *reports) { using namespace blender; @@ -144,17 +160,35 @@ wmOperatorStatus ED_mesh_shapes_join_objects_exec(bContext *C, &active_mesh, active_mesh.key, BKE_keyblock_add(active_mesh.key, nullptr)); } + if (mirror) { + for (const ObjectInfo &info : compatible_objects) { + if (!info.name.endswith(".L") && !info.name.endswith(".R")) { + BKE_report(reports, RPT_ERROR, "Selected objects' names must use .L or .R suffix"); + return OPERATOR_CANCELLED; + } + } + } + + int mirror_count = 0; + int mirror_fail_count = 0; int keys_changed = 0; bool any_keys_added = false; for (const ObjectInfo &info : compatible_objects) { + const std::string name = create_mirrored_name(info.name, mirror); if (ensure_keys_exist) { - KeyBlock *kb = BKE_keyblock_add(active_mesh.key, info.name.c_str()); + KeyBlock *kb = BKE_keyblock_add(active_mesh.key, name.c_str()); BKE_keyblock_convert_from_mesh(&info.mesh, active_mesh.key, kb); any_keys_added = true; + if (mirror) { + ed::object::shape_key_mirror(&active_object, kb, false, mirror_count, mirror_fail_count); + } } - else if (KeyBlock *kb = BKE_keyblock_find_name(active_mesh.key, info.name.c_str())) { + else if (KeyBlock *kb = BKE_keyblock_find_name(active_mesh.key, name.c_str())) { keys_changed++; BKE_keyblock_update_from_mesh(&info.mesh, kb); + if (mirror) { + ed::object::shape_key_mirror(&active_object, kb, false, mirror_count, mirror_fail_count); + } } } @@ -166,6 +200,10 @@ wmOperatorStatus ED_mesh_shapes_join_objects_exec(bContext *C, BKE_reportf(reports, RPT_INFO, "Updated %d shape key(s)", keys_changed); } + if (mirror) { + ED_mesh_report_mirror_ex(*reports, mirror_count, mirror_fail_count, SCE_SELECT_VERTEX); + } + DEG_id_tag_update(&active_mesh.id, ID_RECALC_GEOMETRY); WM_main_add_notifier(NC_GEOM | ND_DATA, &active_mesh.id); diff --git a/source/blender/editors/object/object_add.cc b/source/blender/editors/object/object_add.cc index 68e7c7c9831..de833fa423d 100644 --- a/source/blender/editors/object/object_add.cc +++ b/source/blender/editors/object/object_add.cc @@ -5189,7 +5189,8 @@ static bool active_shape_key_editable_poll(bContext *C) static wmOperatorStatus join_shapes_exec(bContext *C, wmOperator *op) { - return ED_mesh_shapes_join_objects_exec(C, true, op->reports); + return ED_mesh_shapes_join_objects_exec( + C, true, RNA_boolean_get(op->ptr, "use_mirror"), op->reports); } void OBJECT_OT_join_shapes(wmOperatorType *ot) @@ -5204,11 +5205,16 @@ void OBJECT_OT_join_shapes(wmOperatorType *ot) ot->poll = active_shape_key_editable_poll; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + PropertyRNA *prop = RNA_def_boolean( + ot->srna, "use_mirror", false, "Mirror", "Mirror the new shape key values"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); } static wmOperatorStatus update_all_shape_keys_exec(bContext *C, wmOperator *op) { - return ED_mesh_shapes_join_objects_exec(C, false, op->reports); + return ED_mesh_shapes_join_objects_exec( + C, false, RNA_boolean_get(op->ptr, "use_mirror"), op->reports); } static bool object_update_shapes_poll(bContext *C) @@ -5237,6 +5243,10 @@ void OBJECT_OT_update_shapes(wmOperatorType *ot) ot->poll = object_update_shapes_poll; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + PropertyRNA *prop = RNA_def_boolean( + ot->srna, "use_mirror", false, "Mirror", "Mirror the new shape key values"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); } /** \} */ diff --git a/source/blender/editors/object/object_shapekey.cc b/source/blender/editors/object/object_shapekey.cc index 825ff502b20..add74bdded5 100644 --- a/source/blender/editors/object/object_shapekey.cc +++ b/source/blender/editors/object/object_shapekey.cc @@ -160,10 +160,101 @@ static void object_shape_key_add(bContext *C, Object *ob, const bool from_mix) /** \name Remove Shape Key Function * \{ */ +void shape_key_mirror( + Object *ob, KeyBlock *kb, const bool use_topology, int &totmirr, int &totfail) +{ + char *tag_elem = MEM_calloc_arrayN(kb->totelem, "shape_key_mirror"); + + if (ob->type == OB_MESH) { + Mesh *mesh = static_cast(ob->data); + int i1, i2; + float *fp1, *fp2; + float tvec[3]; + + ED_mesh_mirror_spatial_table_begin(ob, nullptr, nullptr); + + for (i1 = 0; i1 < mesh->verts_num; i1++) { + i2 = mesh_get_x_mirror_vert(ob, nullptr, i1, use_topology); + if (i2 == i1) { + fp1 = ((float *)kb->data) + i1 * 3; + fp1[0] = -fp1[0]; + tag_elem[i1] = 1; + totmirr++; + } + else if (i2 != -1) { + if (tag_elem[i1] == 0 && tag_elem[i2] == 0) { + fp1 = ((float *)kb->data) + i1 * 3; + fp2 = ((float *)kb->data) + i2 * 3; + + copy_v3_v3(tvec, fp1); + copy_v3_v3(fp1, fp2); + copy_v3_v3(fp2, tvec); + + /* flip x axis */ + fp1[0] = -fp1[0]; + fp2[0] = -fp2[0]; + totmirr++; + } + tag_elem[i1] = tag_elem[i2] = 1; + } + else { + totfail++; + } + } + + ED_mesh_mirror_spatial_table_end(ob); + } + else if (ob->type == OB_LATTICE) { + const Lattice *lt = static_cast(ob->data); + int i1, i2; + float *fp1, *fp2; + int u, v, w; + /* half but found up odd value */ + const int pntsu_half = (lt->pntsu / 2) + (lt->pntsu % 2); + + /* Currently edit-mode isn't supported by mesh so ignore here for now too. */ +#if 0 + if (lt->editlatt) { + lt = lt->editlatt->latt; + } +#endif + + for (w = 0; w < lt->pntsw; w++) { + for (v = 0; v < lt->pntsv; v++) { + for (u = 0; u < pntsu_half; u++) { + int u_inv = (lt->pntsu - 1) - u; + float tvec[3]; + if (u == u_inv) { + i1 = BKE_lattice_index_from_uvw(lt, u, v, w); + fp1 = ((float *)kb->data) + i1 * 3; + fp1[0] = -fp1[0]; + totmirr++; + } + else { + i1 = BKE_lattice_index_from_uvw(lt, u, v, w); + i2 = BKE_lattice_index_from_uvw(lt, u_inv, v, w); + + fp1 = ((float *)kb->data) + i1 * 3; + fp2 = ((float *)kb->data) + i2 * 3; + + copy_v3_v3(tvec, fp1); + copy_v3_v3(fp1, fp2); + copy_v3_v3(fp2, tvec); + fp1[0] = -fp1[0]; + fp2[0] = -fp2[0]; + totmirr++; + } + } + } + } + } + + MEM_freeN(tag_elem); +} + static bool object_shape_key_mirror( bContext *C, Object *ob, int *r_totmirr, int *r_totfail, bool use_topology) { - KeyBlock *kb; Key *key; int totmirr = 0, totfail = 0; @@ -174,96 +265,8 @@ static bool object_shape_key_mirror( return false; } - kb = static_cast(BLI_findlink(&key->block, ob->shapenr - 1)); - - if (kb) { - char *tag_elem = MEM_calloc_arrayN(kb->totelem, "shape_key_mirror"); - - if (ob->type == OB_MESH) { - Mesh *mesh = static_cast(ob->data); - int i1, i2; - float *fp1, *fp2; - float tvec[3]; - - ED_mesh_mirror_spatial_table_begin(ob, nullptr, nullptr); - - for (i1 = 0; i1 < mesh->verts_num; i1++) { - i2 = mesh_get_x_mirror_vert(ob, nullptr, i1, use_topology); - if (i2 == i1) { - fp1 = ((float *)kb->data) + i1 * 3; - fp1[0] = -fp1[0]; - tag_elem[i1] = 1; - totmirr++; - } - else if (i2 != -1) { - if (tag_elem[i1] == 0 && tag_elem[i2] == 0) { - fp1 = ((float *)kb->data) + i1 * 3; - fp2 = ((float *)kb->data) + i2 * 3; - - copy_v3_v3(tvec, fp1); - copy_v3_v3(fp1, fp2); - copy_v3_v3(fp2, tvec); - - /* flip x axis */ - fp1[0] = -fp1[0]; - fp2[0] = -fp2[0]; - totmirr++; - } - tag_elem[i1] = tag_elem[i2] = 1; - } - else { - totfail++; - } - } - - ED_mesh_mirror_spatial_table_end(ob); - } - else if (ob->type == OB_LATTICE) { - const Lattice *lt = static_cast(ob->data); - int i1, i2; - float *fp1, *fp2; - int u, v, w; - /* half but found up odd value */ - const int pntsu_half = (lt->pntsu / 2) + (lt->pntsu % 2); - - /* Currently edit-mode isn't supported by mesh so ignore here for now too. */ -#if 0 - if (lt->editlatt) { - lt = lt->editlatt->latt; - } -#endif - - for (w = 0; w < lt->pntsw; w++) { - for (v = 0; v < lt->pntsv; v++) { - for (u = 0; u < pntsu_half; u++) { - int u_inv = (lt->pntsu - 1) - u; - float tvec[3]; - if (u == u_inv) { - i1 = BKE_lattice_index_from_uvw(lt, u, v, w); - fp1 = ((float *)kb->data) + i1 * 3; - fp1[0] = -fp1[0]; - totmirr++; - } - else { - i1 = BKE_lattice_index_from_uvw(lt, u, v, w); - i2 = BKE_lattice_index_from_uvw(lt, u_inv, v, w); - - fp1 = ((float *)kb->data) + i1 * 3; - fp2 = ((float *)kb->data) + i2 * 3; - - copy_v3_v3(tvec, fp1); - copy_v3_v3(fp1, fp2); - copy_v3_v3(fp2, tvec); - fp1[0] = -fp1[0]; - fp2[0] = -fp2[0]; - totmirr++; - } - } - } - } - } - - MEM_freeN(tag_elem); + if (KeyBlock *kb = static_cast(BLI_findlink(&key->block, ob->shapenr - 1))) { + shape_key_mirror(ob, kb, use_topology, totmirr, totfail); } *r_totmirr = totmirr;