From c02d4da5a247b2007e0a524d9a78e5803e74a3ef Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 14 Jul 2023 20:57:28 +0300 Subject: [PATCH 1/2] Shape Keys: replace the BKE_keyblock_from_key function with find_index. The function for retrieving a shape key by its index is named somewhat confusingly, and effectively reimplements BLI_findlink. However, more importantly, for some reason it is coded to return null for the index 0 instead of the basis shape key. This severely limits its usability in some cases. This refactor replaces the function with a simple strongly typed wrapper around BLI_findlink, using a different name, and updating the call sites to check that the index is not 0 where necessary. --- source/blender/blenkernel/BKE_key.h | 4 ++-- .../blender/blenkernel/intern/DerivedMesh.cc | 6 ++--- source/blender/blenkernel/intern/ipo.cc | 2 +- source/blender/blenkernel/intern/key.cc | 23 ++++--------------- source/blender/modifiers/intern/MOD_cloth.cc | 4 ++-- 5 files changed, 13 insertions(+), 26 deletions(-) diff --git a/source/blender/blenkernel/BKE_key.h b/source/blender/blenkernel/BKE_key.h index fbc3a1a8b3e..81bbfd40466 100644 --- a/source/blender/blenkernel/BKE_key.h +++ b/source/blender/blenkernel/BKE_key.h @@ -88,9 +88,9 @@ struct KeyBlock *BKE_keyblock_add(struct Key *key, const char *name); */ struct KeyBlock *BKE_keyblock_add_ctime(struct Key *key, const char *name, bool do_force); /** - * Get the appropriate #KeyBlock given an index. + * Get the appropriate #KeyBlock given an index (0 refers to the basis key). Key may be null. */ -struct KeyBlock *BKE_keyblock_from_key(struct Key *key, int index); +struct KeyBlock *BKE_keyblock_find_by_index(struct Key *key, int index); /** * Get the appropriate #KeyBlock given a name to search for. */ diff --git a/source/blender/blenkernel/intern/DerivedMesh.cc b/source/blender/blenkernel/intern/DerivedMesh.cc index 6f3994d0954..7b3e15002aa 100644 --- a/source/blender/blenkernel/intern/DerivedMesh.cc +++ b/source/blender/blenkernel/intern/DerivedMesh.cc @@ -357,9 +357,9 @@ static float (*get_orco_coords(Object *ob, BMEditMesh *em, int layer, int *free) if (!em) { ClothModifierData *clmd = (ClothModifierData *)BKE_modifiers_findby_type( ob, eModifierType_Cloth); - if (clmd) { - KeyBlock *kb = BKE_keyblock_from_key(BKE_key_from_object(ob), - clmd->sim_parms->shapekey_rest); + if (clmd && clmd->sim_parms->shapekey_rest) { + KeyBlock *kb = BKE_keyblock_find_by_index(BKE_key_from_object(ob), + clmd->sim_parms->shapekey_rest); if (kb && kb->data) { return (float(*)[3])kb->data; diff --git a/source/blender/blenkernel/intern/ipo.cc b/source/blender/blenkernel/intern/ipo.cc index 0fd62b05ade..7c71206a88e 100644 --- a/source/blender/blenkernel/intern/ipo.cc +++ b/source/blender/blenkernel/intern/ipo.cc @@ -443,7 +443,7 @@ static char *shapekey_adrcodes_to_paths(ID *id, int adrcode, int * /*r_array_ind else { /* Find the name of the ShapeKey (i.e. KeyBlock) to look for */ Key *key = (Key *)id; - KeyBlock *kb = BKE_keyblock_from_key(key, adrcode); + KeyBlock *kb = BKE_keyblock_find_by_index(key, adrcode); /* setting that we alter is the "value" (i.e. keyblock.curval) */ if (kb) { diff --git a/source/blender/blenkernel/intern/key.cc b/source/blender/blenkernel/intern/key.cc index ad469116792..c5656880eac 100644 --- a/source/blender/blenkernel/intern/key.cc +++ b/source/blender/blenkernel/intern/key.cc @@ -1897,12 +1897,7 @@ KeyBlock *BKE_keyblock_from_object(Object *ob) { Key *key = BKE_key_from_object(ob); - if (key) { - KeyBlock *kb = static_cast(BLI_findlink(&key->block, ob->shapenr - 1)); - return kb; - } - - return nullptr; + return BKE_keyblock_find_by_index(key, ob->shapenr - 1); } KeyBlock *BKE_keyblock_from_object_reference(Object *ob) @@ -1916,21 +1911,13 @@ KeyBlock *BKE_keyblock_from_object_reference(Object *ob) return nullptr; } -KeyBlock *BKE_keyblock_from_key(Key *key, int index) +KeyBlock *BKE_keyblock_find_by_index(Key *key, int index) { - if (key) { - KeyBlock *kb = static_cast(key->block.first); - - for (int i = 1; i < key->totkey; i++) { - kb = kb->next; - - if (index == i) { - return kb; - } - } + if (!key) { + return nullptr; } - return nullptr; + return static_cast(BLI_findlink(&key->block, index)); } KeyBlock *BKE_keyblock_find_name(Key *key, const char name[]) diff --git a/source/blender/modifiers/intern/MOD_cloth.cc b/source/blender/modifiers/intern/MOD_cloth.cc index 36d0d1e3044..646ba49e1f8 100644 --- a/source/blender/modifiers/intern/MOD_cloth.cc +++ b/source/blender/modifiers/intern/MOD_cloth.cc @@ -100,8 +100,8 @@ static void deform_verts(ModifierData *md, * Also hopefully new cloth system will arrive soon.. */ if (mesh == nullptr && clmd->sim_parms->shapekey_rest) { - KeyBlock *kb = BKE_keyblock_from_key(BKE_key_from_object(ctx->object), - clmd->sim_parms->shapekey_rest); + KeyBlock *kb = BKE_keyblock_find_by_index(BKE_key_from_object(ctx->object), + clmd->sim_parms->shapekey_rest); if (kb && kb->data != nullptr) { float(*layerorco)[3]; if (!(layerorco = static_cast( -- 2.30.2 From fcf2083f128ef7e00621e1a0fc7ce91b3bcab6dd Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 14 Jul 2023 20:57:28 +0300 Subject: [PATCH 2/2] Shape Keys: support locking to protect from accidental editing. It is very common for graphical editors with layers to support locking individual layers to protect them from accidental edits due to misclicks. Blender itself already supports locking vertex groups. This adds lock toggles for shape keys, with lock/unlock all operators. The flags are checked by sculpt brushes, edit mode transform tools, and Smooth, Propagate and Blend From Shape operators. This selection aims to cover operations that only deform the mesh, where the shape key selection matters. Topology changing operations always apply to all keys, and thus incorrect shape key selection is less impactful. Excluding them from the new feature greatly reduces the patch size. --- scripts/modules/bpy_extras/object_utils.py | 24 +++ scripts/startup/bl_operators/view3d.py | 17 +- scripts/startup/bl_ui/properties_data_mesh.py | 4 + source/blender/editors/curve/editcurve.cc | 81 +++++++- source/blender/editors/include/ED_curve.hh | 3 + source/blender/editors/include/ED_lattice.hh | 4 + source/blender/editors/include/ED_mesh.hh | 3 + source/blender/editors/include/ED_object.hh | 23 +++ source/blender/editors/include/ED_sculpt.hh | 8 + source/blender/editors/include/ED_view3d.hh | 2 + .../editors/lattice/editlattice_tools.cc | 11 +- source/blender/editors/lattice/lattice_ops.cc | 10 + source/blender/editors/mesh/editmesh_tools.cc | 77 ++++++-- source/blender/editors/mesh/mesh_data.cc | 8 + source/blender/editors/object/object_add.cc | 2 +- source/blender/editors/object/object_intern.h | 1 + source/blender/editors/object/object_ops.cc | 1 + .../blender/editors/object/object_random.cc | 5 + .../blender/editors/object/object_shapekey.cc | 185 ++++++++++++++++++ source/blender/editors/object/object_warp.cc | 5 + source/blender/editors/sculpt_paint/sculpt.cc | 21 ++ .../editors/sculpt_paint/sculpt_cloth.cc | 6 + .../sculpt_paint/sculpt_filter_mesh.cc | 6 + .../editors/sculpt_paint/sculpt_intern.hh | 6 + .../editors/space_view3d/view3d_snap.cc | 18 +- .../transform/transform_convert_curve.cc | 7 + .../transform/transform_convert_lattice.cc | 7 + .../transform/transform_convert_mesh.cc | 6 + .../transform/transform_convert_sculpt.cc | 5 + source/blender/makesdna/DNA_key_types.h | 1 + source/blender/makesrna/intern/rna_key.cc | 7 + 31 files changed, 529 insertions(+), 35 deletions(-) diff --git a/scripts/modules/bpy_extras/object_utils.py b/scripts/modules/bpy_extras/object_utils.py index b78d256f99d..de5a93784fc 100644 --- a/scripts/modules/bpy_extras/object_utils.py +++ b/scripts/modules/bpy_extras/object_utils.py @@ -11,6 +11,7 @@ __all__ = ( "object_add_grid_scale", "object_add_grid_scale_apply_operator", "world_to_camera_view", + "object_verify_active_shape_key_is_editable", ) @@ -263,3 +264,26 @@ def world_to_camera_view(scene, obj, coord): y = (co_local.y - min_y) / (max_y - min_y) return Vector((x, y, z)) + + +def object_report_if_active_shape_key_is_locked(obj, operator): + """ + Checks if the active shape key of the specified object is locked, and reports an error if so. + + If the object has no shape keys, there is nothing to lock, and the function returns False. + + :arg obj: Object to check. + :type obj: :class:`bpy.types.Object` + :arg operator: Currently running operator to report the error through. Use None to suppress emitting the message. + :type operator: :class:`bpy.types.Operator` + :return: True if the shape key was locked. + """ + key = obj.active_shape_key + + if key and key.lock_shape: + if operator: + operator.report({'ERROR'}, "The active shape key of %s is locked" % obj.name) + + return True + + return False diff --git a/scripts/startup/bl_operators/view3d.py b/scripts/startup/bl_operators/view3d.py index aa71b1f710b..0042617f367 100644 --- a/scripts/startup/bl_operators/view3d.py +++ b/scripts/startup/bl_operators/view3d.py @@ -8,6 +8,7 @@ from bpy.props import ( BoolProperty, EnumProperty, ) +from bpy_extras.object_utils import object_report_if_active_shape_key_is_locked class VIEW3D_OT_edit_mesh_extrude_individual_move(Operator): @@ -21,6 +22,9 @@ class VIEW3D_OT_edit_mesh_extrude_individual_move(Operator): return (obj is not None and obj.mode == 'EDIT') def execute(self, context): + if object_report_if_active_shape_key_is_locked(context.object, self): + return {'CANCELLED'} + mesh = context.object.data select_mode = context.tool_settings.mesh_select_mode @@ -83,7 +87,10 @@ class VIEW3D_OT_edit_mesh_extrude_move(Operator): return (obj is not None and obj.mode == 'EDIT') @staticmethod - def extrude_region(context, use_vert_normals, dissolve_and_intersect): + def extrude_region(operator, context, use_vert_normals, dissolve_and_intersect): + if object_report_if_active_shape_key_is_locked(context.object, operator): + return {'CANCELLED'} + mesh = context.object.data totface = mesh.total_face_sel @@ -146,7 +153,7 @@ class VIEW3D_OT_edit_mesh_extrude_move(Operator): def execute(self, context): return VIEW3D_OT_edit_mesh_extrude_move.extrude_region( - context, False, self.dissolve_and_intersect) + self, context, False, self.dissolve_and_intersect) def invoke(self, context, _event): return self.execute(context) @@ -163,7 +170,7 @@ class VIEW3D_OT_edit_mesh_extrude_shrink_fatten(Operator): return (obj is not None and obj.mode == 'EDIT') def execute(self, context): - return VIEW3D_OT_edit_mesh_extrude_move.extrude_region(context, True, False) + return VIEW3D_OT_edit_mesh_extrude_move.extrude_region(self, context, True, False) def invoke(self, context, _event): return self.execute(context) @@ -179,7 +186,9 @@ class VIEW3D_OT_edit_mesh_extrude_manifold_normal(Operator): obj = context.active_object return (obj is not None and obj.mode == 'EDIT') - def execute(self, _context): + def execute(self, context): + if object_report_if_active_shape_key_is_locked(context.object, self): + return {'CANCELLED'} bpy.ops.mesh.extrude_manifold( 'INVOKE_REGION_WIN', MESH_OT_extrude_region={ diff --git a/scripts/startup/bl_ui/properties_data_mesh.py b/scripts/startup/bl_ui/properties_data_mesh.py index d8a9488bc4e..a1b1cfd5cb4 100644 --- a/scripts/startup/bl_ui/properties_data_mesh.py +++ b/scripts/startup/bl_ui/properties_data_mesh.py @@ -73,6 +73,9 @@ class MESH_MT_shape_key_context_menu(Menu): props.all = True props.apply_mix = True layout.separator() + layout.operator("object.shape_key_lock", icon='LOCKED', text="Lock All").action = 'LOCK' + layout.operator("object.shape_key_lock", icon='UNLOCKED', text="Unlock All").action = 'UNLOCK' + layout.separator() layout.operator("object.shape_key_move", icon='TRIA_UP_BAR', text="Move to Top").type = 'TOP' layout.operator("object.shape_key_move", icon='TRIA_DOWN_BAR', text="Move to Bottom").type = 'BOTTOM' @@ -132,6 +135,7 @@ class MESH_UL_shape_keys(UIList): else: row.label(text="") row.prop(key_block, "mute", text="", emboss=False) + row.prop(key_block, "lock_shape", text="", emboss=False) elif self.layout_type == 'GRID': layout.alignment = 'CENTER' layout.label(text="", icon_value=icon) diff --git a/source/blender/editors/curve/editcurve.cc b/source/blender/editors/curve/editcurve.cc index ad64c671772..c917d2be897 100644 --- a/source/blender/editors/curve/editcurve.cc +++ b/source/blender/editors/curve/editcurve.cc @@ -86,6 +86,13 @@ ListBase *object_editcurve_get(Object *ob) return nullptr; } +KeyBlock *ED_curve_get_edit_shape_key(const Curve *cu) +{ + BLI_assert(cu->editnurb); + + return BKE_keyblock_find_by_index(cu->key, cu->editnurb->shapenr - 1); +} + /** \} */ /* -------------------------------------------------------------------- */ @@ -2701,8 +2708,17 @@ static int set_radius_exec(bContext *C, wmOperator *op) Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( scene, view_layer, CTX_wm_view3d(C), &objects_len); + int totobjects = 0; + for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; + + if (ED_object_edit_report_if_shape_key_is_locked(obedit, op->reports)) { + continue; + } + + totobjects++; + ListBase *editnurb = object_editcurve_get(obedit); BezTriple *bezt; BPoint *bp; @@ -2732,7 +2748,7 @@ static int set_radius_exec(bContext *C, wmOperator *op) MEM_freeN(objects); - return OPERATOR_FINISHED; + return totobjects ? OPERATOR_FINISHED : OPERATOR_CANCELLED; } void CURVE_OT_radius_set(wmOperatorType *ot) @@ -2804,7 +2820,7 @@ static void smooth_single_bp(BPoint *bp, } } -static int smooth_exec(bContext *C, wmOperator * /*op*/) +static int smooth_exec(bContext *C, wmOperator *op) { const float factor = 1.0f / 6.0f; const Scene *scene = CTX_data_scene(C); @@ -2813,8 +2829,17 @@ static int smooth_exec(bContext *C, wmOperator * /*op*/) Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( scene, view_layer, CTX_wm_view3d(C), &objects_len); + int totobjects = 0; + for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; + + if (ED_object_edit_report_if_shape_key_is_locked(obedit, op->reports)) { + continue; + } + + totobjects++; + ListBase *editnurb = object_editcurve_get(obedit); int a, a_end; @@ -2892,7 +2917,7 @@ static int smooth_exec(bContext *C, wmOperator * /*op*/) MEM_freeN(objects); - return OPERATOR_FINISHED; + return totobjects ? OPERATOR_FINISHED : OPERATOR_CANCELLED; } void CURVE_OT_smooth(wmOperatorType *ot) @@ -3146,7 +3171,7 @@ void CURVE_OT_smooth_weight(wmOperatorType *ot) /** \name Smooth Radius Operator * \{ */ -static int curve_smooth_radius_exec(bContext *C, wmOperator * /*op*/) +static int curve_smooth_radius_exec(bContext *C, wmOperator *op) { const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); @@ -3154,8 +3179,17 @@ static int curve_smooth_radius_exec(bContext *C, wmOperator * /*op*/) Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( scene, view_layer, CTX_wm_view3d(C), &objects_len); + int totobjects = 0; + for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; + + if (ED_object_edit_report_if_shape_key_is_locked(obedit, op->reports)) { + continue; + } + + totobjects++; + ListBase *editnurb = object_editcurve_get(obedit); curve_smooth_value(editnurb, offsetof(BezTriple, radius), offsetof(BPoint, radius)); @@ -3166,7 +3200,7 @@ static int curve_smooth_radius_exec(bContext *C, wmOperator * /*op*/) MEM_freeN(objects); - return OPERATOR_FINISHED; + return totobjects ? OPERATOR_FINISHED : OPERATOR_CANCELLED; } void CURVE_OT_smooth_radius(wmOperatorType *ot) @@ -3190,7 +3224,7 @@ void CURVE_OT_smooth_radius(wmOperatorType *ot) /** \name Smooth Tilt Operator * \{ */ -static int curve_smooth_tilt_exec(bContext *C, wmOperator * /*op*/) +static int curve_smooth_tilt_exec(bContext *C, wmOperator *op) { const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); @@ -3198,8 +3232,17 @@ static int curve_smooth_tilt_exec(bContext *C, wmOperator * /*op*/) Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( scene, view_layer, CTX_wm_view3d(C), &objects_len); + int totobjects = 0; + for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; + + if (ED_object_edit_report_if_shape_key_is_locked(obedit, op->reports)) { + continue; + } + + totobjects++; + ListBase *editnurb = object_editcurve_get(obedit); curve_smooth_value(editnurb, offsetof(BezTriple, tilt), offsetof(BPoint, tilt)); @@ -3210,7 +3253,7 @@ static int curve_smooth_tilt_exec(bContext *C, wmOperator * /*op*/) MEM_freeN(objects); - return OPERATOR_FINISHED; + return totobjects ? OPERATOR_FINISHED : OPERATOR_CANCELLED; } void CURVE_OT_smooth_tilt(wmOperatorType *ot) @@ -4047,6 +4090,9 @@ static int curve_normals_make_consistent_exec(bContext *C, wmOperator *op) uint objects_len; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( scene, view_layer, CTX_wm_view3d(C), &objects_len); + + int totobjects = 0; + for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; Curve *cu = static_cast(obedit->data); @@ -4055,6 +4101,12 @@ static int curve_normals_make_consistent_exec(bContext *C, wmOperator *op) continue; } + if (ED_object_edit_report_if_shape_key_is_locked(obedit, op->reports)) { + continue; + } + + totobjects++; + ListBase *editnurb = object_editcurve_get(obedit); BKE_nurbList_handles_recalculate(editnurb, calc_length, SELECT); @@ -4062,7 +4114,7 @@ static int curve_normals_make_consistent_exec(bContext *C, wmOperator *op) DEG_id_tag_update(static_cast(obedit->data), 0); } MEM_freeN(objects); - return OPERATOR_FINISHED; + return totobjects ? OPERATOR_FINISHED : OPERATOR_CANCELLED; } void CURVE_OT_normals_make_consistent(wmOperatorType *ot) @@ -7042,7 +7094,7 @@ int ED_curve_join_objects_exec(bContext *C, wmOperator *op) /** \name Clear Tilt Operator * \{ */ -static int clear_tilt_exec(bContext *C, wmOperator * /*op*/) +static int clear_tilt_exec(bContext *C, wmOperator *op) { const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); @@ -7051,6 +7103,9 @@ static int clear_tilt_exec(bContext *C, wmOperator * /*op*/) uint objects_len; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( scene, view_layer, CTX_wm_view3d(C), &objects_len); + + int totobjects = 0; + for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; Curve *cu = static_cast(obedit->data); @@ -7059,6 +7114,12 @@ static int clear_tilt_exec(bContext *C, wmOperator * /*op*/) continue; } + if (ED_object_edit_report_if_shape_key_is_locked(obedit, op->reports)) { + continue; + } + + totobjects++; + ListBase *editnurb = object_editcurve_get(obedit); BezTriple *bezt; BPoint *bp; @@ -7091,7 +7152,7 @@ static int clear_tilt_exec(bContext *C, wmOperator * /*op*/) DEG_id_tag_update(static_cast(obedit->data), 0); } MEM_freeN(objects); - return OPERATOR_FINISHED; + return totobjects ? OPERATOR_FINISHED : OPERATOR_CANCELLED; } void CURVE_OT_tilt_clear(wmOperatorType *ot) diff --git a/source/blender/editors/include/ED_curve.hh b/source/blender/editors/include/ED_curve.hh index 18db65b033f..b160f4e53ea 100644 --- a/source/blender/editors/include/ED_curve.hh +++ b/source/blender/editors/include/ED_curve.hh @@ -13,6 +13,7 @@ struct Base; struct BezTriple; struct Curve; struct EditNurb; +struct KeyBlock; struct ListBase; struct Main; struct Nurb; @@ -35,6 +36,8 @@ void ED_keymap_curve(wmKeyConfig *keyconf); ListBase *object_editcurve_get(Object *ob); +KeyBlock *ED_curve_get_edit_shape_key(const Curve *cu); + /** * Load editNurb in object. */ diff --git a/source/blender/editors/include/ED_lattice.hh b/source/blender/editors/include/ED_lattice.hh index 0ec7aca044f..834688b8543 100644 --- a/source/blender/editors/include/ED_lattice.hh +++ b/source/blender/editors/include/ED_lattice.hh @@ -9,6 +9,8 @@ #pragma once struct Base; +struct KeyBlock; +struct Lattice; struct Object; struct SelectPick_Params; struct UndoType; @@ -19,6 +21,8 @@ struct wmKeyConfig; void ED_operatortypes_lattice(); void ED_keymap_lattice(wmKeyConfig *keyconf); +KeyBlock *ED_lattice_get_edit_shape_key(const Lattice *latt); + /* `editlattice_select.cc` */ bool ED_lattice_flags_set(Object *obedit, int flag); diff --git a/source/blender/editors/include/ED_mesh.hh b/source/blender/editors/include/ED_mesh.hh index 77ed9671c81..b85cbf233d2 100644 --- a/source/blender/editors/include/ED_mesh.hh +++ b/source/blender/editors/include/ED_mesh.hh @@ -23,6 +23,7 @@ struct BMeshNormalsUpdate_Params; struct Base; struct Depsgraph; struct ID; +struct KeyBlock; struct MDeformVert; struct Mesh; struct Object; @@ -554,6 +555,8 @@ int ED_mesh_color_add( void ED_mesh_report_mirror(wmOperator *op, int totmirr, int totfail); void ED_mesh_report_mirror_ex(wmOperator *op, int totmirr, int totfail, char selectmode); +KeyBlock *ED_mesh_get_edit_shape_key(const Mesh *me); + /** * Returns the pinned mesh, the mesh from the pinned object, or the mesh from the active object. */ diff --git a/source/blender/editors/include/ED_object.hh b/source/blender/editors/include/ED_object.hh index 3b0820df312..3ef569d8899 100644 --- a/source/blender/editors/include/ED_object.hh +++ b/source/blender/editors/include/ED_object.hh @@ -63,6 +63,29 @@ Object **ED_object_array_in_mode_or_selected(bContext *C, void *filter_user_data, uint *r_objects_len); +/* `object_shapekey.cc` */ + +/** + * Checks if the currently active Edit Mode on the object is targeting a locked shape key, + * and produces an error message if so (unless \a reports is null). + * \return true if the shape key was locked. + */ +bool ED_object_edit_report_if_shape_key_is_locked(const Object *obedit, ReportList *reports); + +/** + * Checks if the active shape key of the object is locked, and produces an error message + * if so (unless \a reports is null). + * \return true if the shape key was locked. + */ +bool ED_object_report_if_active_shape_key_is_locked(Object *ob, ReportList *reports); + +/** + * Checks if any of the shape keys of the object are locked, and produces an error message if so + * (unless \a reports is null). + * \return true if a shape key was locked. + */ +bool ED_object_report_if_any_shape_key_is_locked(Object *ob, ReportList *reports); + /* `object_utils.cc` */ bool ED_object_calc_active_center_for_editmode(Object *obedit, diff --git a/source/blender/editors/include/ED_sculpt.hh b/source/blender/editors/include/ED_sculpt.hh index 2d2ee973b95..abae81d6c94 100644 --- a/source/blender/editors/include/ED_sculpt.hh +++ b/source/blender/editors/include/ED_sculpt.hh @@ -10,6 +10,7 @@ struct ARegion; struct Object; +struct ReportList; struct UndoType; struct ViewContext; struct bContext; @@ -19,6 +20,13 @@ struct wmKeyConfig; /* sculpt.cc */ +/** + * Checks if the currently active Sculpt Mode on the object is targeting a locked shape key, + * and produces an error message if so (unless \a reports is null). + * \return true if the shape key was locked. + */ +bool ED_sculpt_report_if_shape_key_is_locked(const Object *ob, ReportList *reports); + void ED_operatortypes_sculpt(); void ED_keymap_sculpt(wmKeyConfig *keyconf); diff --git a/source/blender/editors/include/ED_view3d.hh b/source/blender/editors/include/ED_view3d.hh index 449262d8ed9..d3cd26de431 100644 --- a/source/blender/editors/include/ED_view3d.hh +++ b/source/blender/editors/include/ED_view3d.hh @@ -53,6 +53,7 @@ struct rcti; struct wmEvent; struct wmGizmo; struct wmKeyMapItem; +struct wmOperator; struct wmWindow; struct wmWindowManager; @@ -288,6 +289,7 @@ ENUM_OPERATORS(eV3DProjTest, V3D_PROJ_TEST_CLIP_CONTENT); /* `view3d_snap.cc` */ bool ED_view3d_snap_selected_to_location(bContext *C, + wmOperator *op, const float snap_target_global[3], int pivot_point); diff --git a/source/blender/editors/lattice/editlattice_tools.cc b/source/blender/editors/lattice/editlattice_tools.cc index 035e11f5cdb..12ce3c1c9b9 100644 --- a/source/blender/editors/lattice/editlattice_tools.cc +++ b/source/blender/editors/lattice/editlattice_tools.cc @@ -25,6 +25,7 @@ #include "DEG_depsgraph.hh" +#include "ED_object.hh" #include "ED_screen.hh" #include "WM_api.hh" @@ -48,7 +49,7 @@ static bool make_regular_poll(bContext *C) return (ob && ob->type == OB_LATTICE); } -static int make_regular_exec(bContext *C, wmOperator * /*op*/) +static int make_regular_exec(bContext *C, wmOperator *op) { const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); @@ -67,6 +68,10 @@ static int make_regular_exec(bContext *C, wmOperator * /*op*/) continue; } + if (ED_object_edit_report_if_shape_key_is_locked(ob, op->reports)) { + continue; + } + BKE_lattice_resize(lt->editlatt->latt, lt->pntsu, lt->pntsv, lt->pntsw, nullptr); DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); @@ -219,6 +224,10 @@ static int lattice_flip_exec(bContext *C, wmOperator *op) lt = (Lattice *)obedit->data; lt = lt->editlatt->latt; + if (ED_object_edit_report_if_shape_key_is_locked(obedit, op->reports)) { + continue; + } + numU = lt->pntsu; numV = lt->pntsv; numW = lt->pntsw; diff --git a/source/blender/editors/lattice/lattice_ops.cc b/source/blender/editors/lattice/lattice_ops.cc index cc1cfe36029..695be650210 100644 --- a/source/blender/editors/lattice/lattice_ops.cc +++ b/source/blender/editors/lattice/lattice_ops.cc @@ -6,8 +6,11 @@ * \ingroup edlattice */ +#include "DNA_lattice_types.h" #include "DNA_scene_types.h" +#include "BKE_key.h" + #include "WM_api.hh" #include "ED_lattice.hh" @@ -32,3 +35,10 @@ void ED_keymap_lattice(wmKeyConfig *keyconf) wmKeyMap *keymap = WM_keymap_ensure(keyconf, "Lattice", SPACE_EMPTY, RGN_TYPE_WINDOW); keymap->poll = ED_operator_editlattice; } + +KeyBlock *ED_lattice_get_edit_shape_key(const Lattice *latt) +{ + BLI_assert(latt->editlatt); + + return BKE_keyblock_find_by_index(latt->key, latt->editlatt->shapenr - 1); +} diff --git a/source/blender/editors/mesh/editmesh_tools.cc b/source/blender/editors/mesh/editmesh_tools.cc index 7ca62eaa271..15bec5861ab 100644 --- a/source/blender/editors/mesh/editmesh_tools.cc +++ b/source/blender/editors/mesh/editmesh_tools.cc @@ -1804,13 +1804,22 @@ static int edbm_face_make_planar_exec(bContext *C, wmOperator *op) const int repeat = RNA_int_get(op->ptr, "repeat"); const float fac = RNA_float_get(op->ptr, "factor"); + int totobjects = 0; + for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); + if (em->bm->totfacesel == 0) { continue; } + if (ED_object_edit_report_if_shape_key_is_locked(obedit, op->reports)) { + continue; + } + + totobjects++; + if (!EDBM_op_callf( em, op, "planar_faces faces=%hf iterations=%i factor=%f", BM_ELEM_SELECT, repeat, fac)) { @@ -1825,7 +1834,7 @@ static int edbm_face_make_planar_exec(bContext *C, wmOperator *op) } MEM_freeN(objects); - return OPERATOR_FINISHED; + return totobjects ? OPERATOR_FINISHED : OPERATOR_CANCELLED; } void MESH_OT_face_make_planar(wmOperatorType *ot) @@ -2691,6 +2700,7 @@ static int edbm_do_smooth_vertex_exec(bContext *C, wmOperator *op) const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); + int tot_selected = 0, tot_locked = 0; uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( scene, view_layer, CTX_wm_view3d(C), &objects_len); @@ -2706,6 +2716,13 @@ static int edbm_do_smooth_vertex_exec(bContext *C, wmOperator *op) continue; } + if (ED_object_edit_report_if_shape_key_is_locked(obedit, op->reports)) { + tot_locked++; + continue; + } + + tot_selected++; + /* mirror before smooth */ if (((Mesh *)obedit->data)->symmetry & ME_SYMMETRY_X) { EDBM_verts_mirror_cache_begin(em, 0, false, true, false, use_topology); @@ -2768,7 +2785,11 @@ static int edbm_do_smooth_vertex_exec(bContext *C, wmOperator *op) } MEM_freeN(objects); - return OPERATOR_FINISHED; + if (tot_selected == 0 && !tot_locked) { + BKE_report(op->reports, RPT_WARNING, "No selected vertex"); + } + + return tot_selected ? OPERATOR_FINISHED : OPERATOR_CANCELLED; } void MESH_OT_vertices_smooth(wmOperatorType *ot) @@ -2808,7 +2829,7 @@ void MESH_OT_vertices_smooth(wmOperatorType *ot) static int edbm_do_smooth_laplacian_vertex_exec(bContext *C, wmOperator *op) { - int tot_unselected = 0; + int tot_selected = 0, tot_locked = 0; const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); @@ -2834,10 +2855,16 @@ static int edbm_do_smooth_laplacian_vertex_exec(bContext *C, wmOperator *op) bool use_topology = (mesh->editflag & ME_EDIT_MIRROR_TOPO) != 0; if (em->bm->totvertsel == 0) { - tot_unselected++; continue; } + if (ED_object_edit_report_if_shape_key_is_locked(obedit, op->reports)) { + tot_locked++; + continue; + } + + tot_selected++; + /* Mirror before smooth. */ if (((Mesh *)obedit->data)->symmetry & ME_SYMMETRY_X) { EDBM_verts_mirror_cache_begin(em, 0, false, true, false, use_topology); @@ -2879,12 +2906,11 @@ static int edbm_do_smooth_laplacian_vertex_exec(bContext *C, wmOperator *op) } MEM_freeN(objects); - if (tot_unselected == objects_len) { + if (tot_selected == 0 && !tot_locked) { BKE_report(op->reports, RPT_WARNING, "No selected vertex"); - return OPERATOR_CANCELLED; } - return OPERATOR_FINISHED; + return tot_selected ? OPERATOR_FINISHED : OPERATOR_CANCELLED; } void MESH_OT_vertices_smooth_laplacian(wmOperatorType *ot) @@ -3746,6 +3772,7 @@ static int edbm_shape_propagate_to_all_exec(bContext *C, wmOperator *op) ViewLayer *view_layer = CTX_data_view_layer(C); int tot_shapekeys = 0; int tot_selected_verts_objects = 0; + int tot_locked = 0; uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( @@ -3758,6 +3785,13 @@ static int edbm_shape_propagate_to_all_exec(bContext *C, wmOperator *op) if (em->bm->totvertsel == 0) { continue; } + + /* Check for locked shape keys. */ + if (ED_object_report_if_any_shape_key_is_locked(obedit, op->reports)) { + tot_locked++; + continue; + } + tot_selected_verts_objects++; const bool use_symmetry = (mesh->symmetry & ME_SYMMETRY_X) != 0; @@ -3785,7 +3819,9 @@ static int edbm_shape_propagate_to_all_exec(bContext *C, wmOperator *op) MEM_freeN(objects); if (tot_selected_verts_objects == 0) { - BKE_report(op->reports, RPT_ERROR, "No selected vertex"); + if (!tot_locked) { + BKE_report(op->reports, RPT_ERROR, "No selected vertex"); + } return OPERATOR_CANCELLED; } if (tot_shapekeys == 0) { @@ -3853,7 +3889,7 @@ static int edbm_blend_from_shape_exec(bContext *C, wmOperator *op) kb_ref = static_cast(BLI_findlink(&key_ref->block, shape_ref)); } - int tot_selected_verts_objects = 0; + int tot_selected_verts_objects = 0, tot_locked = 0; uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( scene, view_layer, CTX_wm_view3d(C), &objects_len); @@ -3868,6 +3904,12 @@ static int edbm_blend_from_shape_exec(bContext *C, wmOperator *op) if (em->bm->totvertsel == 0) { continue; } + + if (ED_object_edit_report_if_shape_key_is_locked(obedit, op->reports)) { + tot_locked++; + continue; + } + tot_selected_verts_objects++; if (!key) { @@ -3924,12 +3966,11 @@ static int edbm_blend_from_shape_exec(bContext *C, wmOperator *op) } MEM_freeN(objects); - if (tot_selected_verts_objects == 0) { + if (tot_selected_verts_objects == 0 && !tot_locked) { BKE_report(op->reports, RPT_ERROR, "No selected vertex"); - return OPERATOR_CANCELLED; } - return OPERATOR_FINISHED; + return tot_selected_verts_objects ? OPERATOR_FINISHED : OPERATOR_CANCELLED; } static const EnumPropertyItem *shape_itemf(bContext *C, @@ -8010,7 +8051,7 @@ static int mesh_symmetry_snap_exec(bContext *C, wmOperator *op) const int axis_dir = RNA_enum_get(op->ptr, "direction"); /* Vertices stats (total over all selected objects). */ - int totvertfound = 0, totvertmirr = 0, totvertfail = 0; + int totvertfound = 0, totvertmirr = 0, totvertfail = 0, totobjects = 0; /* Axis. */ int axis = axis_dir % 3; @@ -8031,6 +8072,12 @@ static int mesh_symmetry_snap_exec(bContext *C, wmOperator *op) continue; } + if (ED_object_edit_report_if_shape_key_is_locked(obedit, op->reports)) { + continue; + } + + totobjects++; + /* Only allocate memory after checking whether to skip object. */ int *index = static_cast(MEM_mallocN(bm->totvert * sizeof(*index), __func__)); @@ -8116,7 +8163,7 @@ static int mesh_symmetry_snap_exec(bContext *C, wmOperator *op) totvertmirr, totvertfail); } - else { + else if (totobjects) { BKE_reportf(op->reports, RPT_INFO, "%d already symmetrical, %d pairs mirrored", @@ -8124,7 +8171,7 @@ static int mesh_symmetry_snap_exec(bContext *C, wmOperator *op) totvertmirr); } - return OPERATOR_FINISHED; + return totobjects ? OPERATOR_FINISHED : OPERATOR_CANCELLED; } void MESH_OT_symmetry_snap(wmOperatorType *ot) diff --git a/source/blender/editors/mesh/mesh_data.cc b/source/blender/editors/mesh/mesh_data.cc index e17c310ee06..4dda9f78fed 100644 --- a/source/blender/editors/mesh/mesh_data.cc +++ b/source/blender/editors/mesh/mesh_data.cc @@ -19,6 +19,7 @@ #include "BKE_context.hh" #include "BKE_customdata.hh" #include "BKE_editmesh.hh" +#include "BKE_key.h" #include "BKE_mesh.hh" #include "BKE_mesh_runtime.hh" #include "BKE_report.h" @@ -1111,6 +1112,13 @@ void ED_mesh_report_mirror(wmOperator *op, int totmirr, int totfail) ED_mesh_report_mirror_ex(op, totmirr, totfail, SCE_SELECT_VERTEX); } +KeyBlock *ED_mesh_get_edit_shape_key(const Mesh *me) +{ + BLI_assert(me->edit_mesh && me->edit_mesh->bm); + + return BKE_keyblock_find_by_index(me->key, me->edit_mesh->bm->shapenr - 1); +} + Mesh *ED_mesh_context(bContext *C) { Mesh *mesh = static_cast(CTX_data_pointer_get_type(C, "mesh", &RNA_Mesh).data); diff --git a/source/blender/editors/object/object_add.cc b/source/blender/editors/object/object_add.cc index ebfc42e2fb8..87b2a83de40 100644 --- a/source/blender/editors/object/object_add.cc +++ b/source/blender/editors/object/object_add.cc @@ -4228,7 +4228,7 @@ static int object_transform_to_mouse_exec(bContext *C, wmOperator *op) * * The caller is responsible for ensuring the selection state gives useful results. * Link/append does this using #FILE_AUTOSELECT. */ - ED_view3d_snap_selected_to_location(C, cursor, V3D_AROUND_ACTIVE); + ED_view3d_snap_selected_to_location(C, op, cursor, V3D_AROUND_ACTIVE); } } diff --git a/source/blender/editors/object/object_intern.h b/source/blender/editors/object/object_intern.h index 618ccea9707..1d0c62c5300 100644 --- a/source/blender/editors/object/object_intern.h +++ b/source/blender/editors/object/object_intern.h @@ -329,6 +329,7 @@ void OBJECT_OT_shape_key_clear(struct wmOperatorType *ot); void OBJECT_OT_shape_key_retime(struct wmOperatorType *ot); void OBJECT_OT_shape_key_mirror(struct wmOperatorType *ot); void OBJECT_OT_shape_key_move(struct wmOperatorType *ot); +void OBJECT_OT_shape_key_lock(struct wmOperatorType *ot); /* `object_collection.cc` */ diff --git a/source/blender/editors/object/object_ops.cc b/source/blender/editors/object/object_ops.cc index 92e60622018..df916ed2f34 100644 --- a/source/blender/editors/object/object_ops.cc +++ b/source/blender/editors/object/object_ops.cc @@ -240,6 +240,7 @@ void ED_operatortypes_object() WM_operatortype_append(OBJECT_OT_shape_key_retime); WM_operatortype_append(OBJECT_OT_shape_key_mirror); WM_operatortype_append(OBJECT_OT_shape_key_move); + WM_operatortype_append(OBJECT_OT_shape_key_lock); WM_operatortype_append(OBJECT_OT_collection_add); WM_operatortype_append(OBJECT_OT_collection_link); diff --git a/source/blender/editors/object/object_random.cc b/source/blender/editors/object/object_random.cc index 007f3fc29c4..589c0fef0bf 100644 --- a/source/blender/editors/object/object_random.cc +++ b/source/blender/editors/object/object_random.cc @@ -24,6 +24,7 @@ #include "WM_api.hh" #include "WM_types.hh" +#include "ED_object.hh" #include "ED_transverts.hh" #include "object_intern.h" @@ -105,6 +106,10 @@ static int object_rand_verts_exec(bContext *C, wmOperator *op) mode |= TX_VERT_USE_NORMAL; } + if (ED_object_edit_report_if_shape_key_is_locked(ob_iter, op->reports)) { + continue; + } + ED_transverts_create_from_obedit(&tvs, ob_iter, mode); if (tvs.transverts_tot == 0) { continue; diff --git a/source/blender/editors/object/object_shapekey.cc b/source/blender/editors/object/object_shapekey.cc index 6e769c75be7..930149cb1c4 100644 --- a/source/blender/editors/object/object_shapekey.cc +++ b/source/blender/editors/object/object_shapekey.cc @@ -42,6 +42,8 @@ #include "BLI_sys_types.h" /* for intptr_t support */ +#include "ED_curve.hh" +#include "ED_lattice.hh" #include "ED_mesh.hh" #include "ED_object.hh" @@ -53,6 +55,82 @@ #include "object_intern.h" +/* -------------------------------------------------------------------- */ +/** \name Shape Key Lock Checks + * \{ */ + +bool ED_object_edit_report_if_shape_key_is_locked(const Object *obedit, ReportList *reports) +{ + KeyBlock *key_block; + + switch (obedit->type) { + case OB_MESH: + key_block = ED_mesh_get_edit_shape_key(static_cast(obedit->data)); + break; + case OB_SURF: + case OB_CURVES_LEGACY: + key_block = ED_curve_get_edit_shape_key(static_cast(obedit->data)); + break; + case OB_LATTICE: + key_block = ED_lattice_get_edit_shape_key(static_cast(obedit->data)); + break; + default: + return false; + } + + if (key_block && (key_block->flag & KEYBLOCK_LOCKED_SHAPE) != 0) { + if (reports) { + BKE_reportf(reports, RPT_ERROR, "The active shape key of %s is locked", obedit->id.name + 2); + } + return true; + } + + return false; +} + +bool ED_object_report_if_active_shape_key_is_locked(Object *ob, ReportList *reports) +{ + const KeyBlock *kb = BKE_keyblock_from_object(ob); + + if (kb && (kb->flag & KEYBLOCK_LOCKED_SHAPE) != 0) { + if (reports) { + BKE_reportf(reports, RPT_ERROR, "The active shape key of %s is locked", ob->id.name + 2); + } + return true; + } + + return false; +} + +static bool object_is_any_shape_key_locked(Object *ob) +{ + const Key *key = BKE_key_from_object(ob); + + if (key) { + LISTBASE_FOREACH (const KeyBlock *, kb, &key->block) { + if (kb->flag & KEYBLOCK_LOCKED_SHAPE) { + return true; + } + } + } + + return false; +} + +bool ED_object_report_if_any_shape_key_is_locked(Object *ob, ReportList *reports) +{ + if (object_is_any_shape_key_locked(ob)) { + if (reports) { + BKE_reportf(reports, RPT_ERROR, "The object %s has locked shape keys", ob->id.name + 2); + } + return true; + } + + return false; +} + +/** \} */ + /* -------------------------------------------------------------------- */ /** \name Add Shape Key Function * \{ */ @@ -224,6 +302,15 @@ static bool shape_key_poll(bContext *C) !ID_IS_LINKED(data) && !ID_IS_OVERRIDE_LIBRARY(data)); } +static bool shape_key_exists_poll(bContext *C) +{ + Object *ob = ED_object_context(C); + + return (shape_key_poll(C) && + /* check a keyblock exists */ + (BKE_keyblock_from_object(ob) != nullptr)); +} + static bool shape_key_mode_poll(bContext *C) { Object *ob = ED_object_context(C); @@ -303,6 +390,10 @@ static int shape_key_remove_exec(bContext *C, wmOperator *op) bool changed = false; if (RNA_boolean_get(op->ptr, "all")) { + if (ED_object_report_if_any_shape_key_is_locked(ob, op->reports)) { + return OPERATOR_CANCELLED; + } + if (RNA_boolean_get(op->ptr, "apply_mix")) { float *arr = BKE_key_evaluate_object_ex( ob, nullptr, nullptr, 0, static_cast(ob->data)); @@ -311,6 +402,10 @@ static int shape_key_remove_exec(bContext *C, wmOperator *op) changed = BKE_object_shapekey_free(bmain, ob); } else { + if (ED_object_report_if_active_shape_key_is_locked(ob, op->reports)) { + return OPERATOR_CANCELLED; + } + changed = object_shapekey_remove(bmain, ob); } @@ -466,6 +561,10 @@ static int shape_key_mirror_exec(bContext *C, wmOperator *op) int totmirr = 0, totfail = 0; bool use_topology = RNA_boolean_get(op->ptr, "use_topology"); + if (ED_object_report_if_active_shape_key_is_locked(ob, op->reports)) { + return OPERATOR_CANCELLED; + } + if (!object_shape_key_mirror(C, ob, &totmirr, &totfail, use_topology)) { return OPERATOR_CANCELLED; } @@ -571,3 +670,89 @@ void OBJECT_OT_shape_key_move(wmOperatorType *ot) } /** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Shape Key Lock (Unlock) Operator + * \{ */ + +enum { + SHAPE_KEY_LOCK, + SHAPE_KEY_UNLOCK, +}; + +static int shape_key_lock_exec(bContext *C, wmOperator *op) +{ + Object *ob = CTX_data_active_object(C); + const int action = RNA_enum_get(op->ptr, "action"); + const Key *keys = BKE_key_from_object(ob); + + if (!keys || BLI_listbase_is_empty(&keys->block)) { + return OPERATOR_CANCELLED; + } + + LISTBASE_FOREACH (KeyBlock *, kb, &keys->block) { + switch (action) { + case SHAPE_KEY_LOCK: + kb->flag |= KEYBLOCK_LOCKED_SHAPE; + break; + case SHAPE_KEY_UNLOCK: + kb->flag &= ~KEYBLOCK_LOCKED_SHAPE; + break; + default: + BLI_assert(0); + } + } + + WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); + + return OPERATOR_FINISHED; +} + +static std::string shape_key_lock_description(bContext * /*C*/, + wmOperatorType * /*op*/, + PointerRNA *params) +{ + const int action = RNA_enum_get(params, "action"); + + switch (action) { + case SHAPE_KEY_LOCK: + return TIP_("Lock all shape keys of the active object"); + break; + case SHAPE_KEY_UNLOCK: + return TIP_("Unlock all shape keys of the active object"); + break; + default: + return ""; + } +} + +void OBJECT_OT_shape_key_lock(wmOperatorType *ot) +{ + static const EnumPropertyItem shape_key_lock_actions[] = { + {SHAPE_KEY_LOCK, "LOCK", 0, "Lock", "Lock all shape keys"}, + {SHAPE_KEY_UNLOCK, "UNLOCK", 0, "Unlock", "Unlock all shape keys"}, + {0, nullptr, 0, nullptr, nullptr}, + }; + + /* identifiers */ + ot->name = "Change the Lock On Shape Keys"; + ot->idname = "OBJECT_OT_shape_key_lock"; + ot->description = "Change the lock state of all shape keys of active object"; + + /* api callbacks */ + ot->poll = shape_key_exists_poll; + ot->exec = shape_key_lock_exec; + ot->get_description = shape_key_lock_description; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + RNA_def_enum(ot->srna, + "action", + shape_key_lock_actions, + SHAPE_KEY_LOCK, + "Action", + "Lock action to execute on vertex groups"); +} + +/** \} */ diff --git a/source/blender/editors/object/object_warp.cc b/source/blender/editors/object/object_warp.cc index 297e8a01a0b..e8083b17f2f 100644 --- a/source/blender/editors/object/object_warp.cc +++ b/source/blender/editors/object/object_warp.cc @@ -22,6 +22,7 @@ #include "WM_api.hh" #include "WM_types.hh" +#include "ED_object.hh" #include "ED_transverts.hh" #include "object_intern.h" @@ -168,6 +169,10 @@ static int object_warp_verts_exec(bContext *C, wmOperator *op) float min, max; + if (ED_object_edit_report_if_shape_key_is_locked(obedit, op->reports)) { + return OPERATOR_CANCELLED; + } + ED_transverts_create_from_obedit(&tvs, obedit, TM_ALL_JOINTS | TM_SKIP_HANDLES); if (tvs.transverts == nullptr) { return OPERATOR_CANCELLED; diff --git a/source/blender/editors/sculpt_paint/sculpt.cc b/source/blender/editors/sculpt_paint/sculpt.cc index bfebfc4c8de..22d5e8e3fc1 100644 --- a/source/blender/editors/sculpt_paint/sculpt.cc +++ b/source/blender/editors/sculpt_paint/sculpt.cc @@ -102,6 +102,22 @@ static float sculpt_calc_radius(ViewContext *vc, } } +bool ED_sculpt_report_if_shape_key_is_locked(const Object *ob, ReportList *reports) +{ + SculptSession *ss = ob->sculpt; + + BLI_assert(ss); + + if (ss->shapekey_active && (ss->shapekey_active->flag & KEYBLOCK_LOCKED_SHAPE) != 0) { + if (reports) { + BKE_reportf(reports, RPT_ERROR, "The active shape key of %s is locked", ob->id.name + 2); + } + return true; + } + + return false; +} + /* -------------------------------------------------------------------- */ /** \name Sculpt PBVH Abstraction API * @@ -5648,6 +5664,11 @@ static int sculpt_brush_stroke_invoke(bContext *C, wmOperator *op, const wmEvent MultiresModifierData *mmd = BKE_sculpt_multires_active(ss->scene, ob); BKE_sculpt_mask_layers_ensure(CTX_data_depsgraph_pointer(C), CTX_data_main(C), ob, mmd); } + if (!SCULPT_tool_is_attribute_only(brush->sculpt_tool) && + ED_sculpt_report_if_shape_key_is_locked(ob, op->reports)) + { + return OPERATOR_CANCELLED; + } stroke = paint_stroke_new(C, op, diff --git a/source/blender/editors/sculpt_paint/sculpt_cloth.cc b/source/blender/editors/sculpt_paint/sculpt_cloth.cc index 675ded70075..8640538e620 100644 --- a/source/blender/editors/sculpt_paint/sculpt_cloth.cc +++ b/source/blender/editors/sculpt_paint/sculpt_cloth.cc @@ -36,6 +36,8 @@ #include "WM_api.hh" #include "WM_types.hh" +#include "ED_sculpt.hh" + #include "sculpt_intern.hh" #include "RNA_access.hh" @@ -1538,6 +1540,10 @@ static int sculpt_cloth_filter_invoke(bContext *C, wmOperator *op, const wmEvent /* Needs mask data to be available as it is used when solving the constraints. */ BKE_sculpt_update_object_for_edit(depsgraph, ob, false); + if (ED_sculpt_report_if_shape_key_is_locked(ob, op->reports)) { + return OPERATOR_CANCELLED; + } + SCULPT_stroke_id_next(ob); undo::push_begin(ob, op); diff --git a/source/blender/editors/sculpt_paint/sculpt_filter_mesh.cc b/source/blender/editors/sculpt_paint/sculpt_filter_mesh.cc index d37e4d37b48..456b8a961dd 100644 --- a/source/blender/editors/sculpt_paint/sculpt_filter_mesh.cc +++ b/source/blender/editors/sculpt_paint/sculpt_filter_mesh.cc @@ -34,6 +34,7 @@ #include "WM_types.hh" #include "ED_screen.hh" +#include "ED_sculpt.hh" #include "ED_util.hh" #include "ED_view3d.hh" @@ -967,6 +968,11 @@ static int sculpt_mesh_filter_start(bContext *C, wmOperator *op) const bool needs_topology_info = sculpt_mesh_filter_needs_pmap(filter_type) || use_automasking; BKE_sculpt_update_object_for_edit(depsgraph, ob, false); + + if (ED_sculpt_report_if_shape_key_is_locked(ob, op->reports)) { + return OPERATOR_CANCELLED; + } + SculptSession *ss = ob->sculpt; const eMeshFilterDeformAxis deform_axis = eMeshFilterDeformAxis( diff --git a/source/blender/editors/sculpt_paint/sculpt_intern.hh b/source/blender/editors/sculpt_paint/sculpt_intern.hh index 1aaeccb2c51..23f6f789f15 100644 --- a/source/blender/editors/sculpt_paint/sculpt_intern.hh +++ b/source/blender/editors/sculpt_paint/sculpt_intern.hh @@ -1859,6 +1859,12 @@ inline bool SCULPT_tool_is_mask(int tool) return ELEM(tool, SCULPT_TOOL_MASK); } +BLI_INLINE bool SCULPT_tool_is_attribute_only(int tool) +{ + return SCULPT_tool_is_paint(tool) || SCULPT_tool_is_mask(tool) || + ELEM(tool, SCULPT_TOOL_DRAW_FACE_SETS); +} + void SCULPT_stroke_id_ensure(Object *ob); void SCULPT_stroke_id_next(Object *ob); diff --git a/source/blender/editors/space_view3d/view3d_snap.cc b/source/blender/editors/space_view3d/view3d_snap.cc index 1c2d3abdb2c..0e272644570 100644 --- a/source/blender/editors/space_view3d/view3d_snap.cc +++ b/source/blender/editors/space_view3d/view3d_snap.cc @@ -62,7 +62,7 @@ static bool snap_calc_active_center(bContext *C, const bool select_only, float r * \{ */ /** Snaps every individual object center to its nearest point on the grid. */ -static int snap_sel_to_grid_exec(bContext *C, wmOperator * /*op*/) +static int snap_sel_to_grid_exec(bContext *C, wmOperator *op) { Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); ViewLayer *view_layer_eval = DEG_get_evaluated_view_layer(depsgraph); @@ -93,6 +93,10 @@ static int snap_sel_to_grid_exec(bContext *C, wmOperator * /*op*/) } } + if (ED_object_edit_report_if_shape_key_is_locked(obedit, op->reports)) { + continue; + } + if (ED_transverts_check_obedit(obedit)) { ED_transverts_create_from_obedit(&tvs, obedit, 0); } @@ -298,6 +302,7 @@ void VIEW3D_OT_snap_selected_to_grid(wmOperatorType *ot) * or if every object origin should be snapped to the given location. */ static bool snap_selected_to_location(bContext *C, + wmOperator *op, const float snap_target_global[3], const bool use_offset, const int pivot_point, @@ -341,6 +346,10 @@ static bool snap_selected_to_location(bContext *C, } } + if (ED_object_edit_report_if_shape_key_is_locked(obedit, op->reports)) { + continue; + } + if (ED_transverts_check_obedit(obedit)) { ED_transverts_create_from_obedit(&tvs, obedit, 0); } @@ -568,6 +577,7 @@ static bool snap_selected_to_location(bContext *C, } bool ED_view3d_snap_selected_to_location(bContext *C, + wmOperator *op, const float snap_target_global[3], const int pivot_point) { @@ -578,7 +588,7 @@ bool ED_view3d_snap_selected_to_location(bContext *C, * so this can be used as a low level function. */ const bool use_toolsettings = false; return snap_selected_to_location( - C, snap_target_global, use_offset, pivot_point, use_toolsettings); + C, op, snap_target_global, use_offset, pivot_point, use_toolsettings); } /** \} */ @@ -596,7 +606,7 @@ static int snap_selected_to_cursor_exec(bContext *C, wmOperator *op) const float *snap_target_global = scene->cursor.location; const int pivot_point = scene->toolsettings->transform_pivot_point; - if (snap_selected_to_location(C, snap_target_global, use_offset, pivot_point, true)) { + if (snap_selected_to_location(C, op, snap_target_global, use_offset, pivot_point, true)) { return OPERATOR_FINISHED; } return OPERATOR_CANCELLED; @@ -640,7 +650,7 @@ static int snap_selected_to_active_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } - if (!snap_selected_to_location(C, snap_target_global, false, -1, true)) { + if (!snap_selected_to_location(C, op, snap_target_global, false, -1, true)) { return OPERATOR_CANCELLED; } return OPERATOR_FINISHED; diff --git a/source/blender/editors/transform/transform_convert_curve.cc b/source/blender/editors/transform/transform_convert_curve.cc index 096ae8560cb..cf7f68d4925 100644 --- a/source/blender/editors/transform/transform_convert_curve.cc +++ b/source/blender/editors/transform/transform_convert_curve.cc @@ -18,6 +18,8 @@ #include "BKE_context.hh" #include "BKE_curve.hh" +#include "ED_object.hh" + #include "transform.hh" #include "transform_snap.hh" @@ -86,6 +88,11 @@ static void createTransCurveVerts(bContext * /*C*/, TransInfo *t) int count = 0, countsel = 0; int count_pt = 0, countsel_pt = 0; + /* Avoid editing locked shapes. */ + if (t->mode != TFM_DUMMY && ED_object_edit_report_if_shape_key_is_locked(tc->obedit, t->reports)) { + continue; + } + /* count total of vertices, check identical as in 2nd loop for making transdata! */ ListBase *nurbs = BKE_curve_editNurbs_get(cu); LISTBASE_FOREACH (Nurb *, nu, nurbs) { diff --git a/source/blender/editors/transform/transform_convert_lattice.cc b/source/blender/editors/transform/transform_convert_lattice.cc index e6c56434c43..23c817af09d 100644 --- a/source/blender/editors/transform/transform_convert_lattice.cc +++ b/source/blender/editors/transform/transform_convert_lattice.cc @@ -17,6 +17,8 @@ #include "BKE_context.hh" #include "BKE_lattice.hh" +#include "ED_object.hh" + #include "transform.hh" #include "transform_snap.hh" @@ -40,6 +42,11 @@ static void createTransLatticeVerts(bContext * /*C*/, TransInfo *t) const bool is_prop_edit = (t->flag & T_PROP_EDIT) != 0; const bool is_prop_connected = (t->flag & T_PROP_CONNECTED) != 0; + /* Avoid editing locked shapes. */ + if (t->mode != TFM_DUMMY && ED_object_edit_report_if_shape_key_is_locked(tc->obedit, t->reports)) { + continue; + } + bp = latt->def; a = latt->pntsu * latt->pntsv * latt->pntsw; while (a--) { diff --git a/source/blender/editors/transform/transform_convert_mesh.cc b/source/blender/editors/transform/transform_convert_mesh.cc index c3821e124a7..f9a637f1fab 100644 --- a/source/blender/editors/transform/transform_convert_mesh.cc +++ b/source/blender/editors/transform/transform_convert_mesh.cc @@ -27,6 +27,7 @@ #include "BKE_scene.h" #include "ED_mesh.hh" +#include "ED_object.hh" #include "DEG_depsgraph_query.hh" @@ -1501,6 +1502,11 @@ static void createTransEditVerts(bContext * /*C*/, TransInfo *t) TransMirrorData mirror_data = {nullptr}; TransMeshDataCrazySpace crazyspace_data = {}; + /* Avoid editing locked shapes. */ + if (t->mode != TFM_DUMMY && ED_object_edit_report_if_shape_key_is_locked(tc->obedit, t->reports)) { + continue; + } + /** * Quick check if we can transform. * diff --git a/source/blender/editors/transform/transform_convert_sculpt.cc b/source/blender/editors/transform/transform_convert_sculpt.cc index 404de477b80..5d57902e5bd 100644 --- a/source/blender/editors/transform/transform_convert_sculpt.cc +++ b/source/blender/editors/transform/transform_convert_sculpt.cc @@ -41,6 +41,11 @@ static void createTransSculpt(bContext *C, TransInfo *t) Object *ob = BKE_view_layer_active_object_get(t->view_layer); SculptSession *ss = ob->sculpt; + /* Avoid editing locked shapes. */ + if (t->mode != TFM_DUMMY && ED_sculpt_report_if_shape_key_is_locked(ob, t->reports)) { + return; + } + { BLI_assert(t->data_container_len == 1); TransDataContainer *tc = t->data_container; diff --git a/source/blender/makesdna/DNA_key_types.h b/source/blender/makesdna/DNA_key_types.h index 047a5d49544..180e9da272a 100644 --- a/source/blender/makesdna/DNA_key_types.h +++ b/source/blender/makesdna/DNA_key_types.h @@ -131,6 +131,7 @@ enum { KEYBLOCK_MUTE = (1 << 0), KEYBLOCK_SEL = (1 << 1), KEYBLOCK_LOCKED = (1 << 2), + KEYBLOCK_LOCKED_SHAPE = (1 << 3), }; #define KEYELEM_FLOAT_LEN_COORD 3 diff --git a/source/blender/makesrna/intern/rna_key.cc b/source/blender/makesrna/intern/rna_key.cc index 32cec593c60..4d12da264cd 100644 --- a/source/blender/makesrna/intern/rna_key.cc +++ b/source/blender/makesrna/intern/rna_key.cc @@ -953,6 +953,13 @@ static void rna_def_keyblock(BlenderRNA *brna) RNA_def_property_ui_icon(prop, ICON_CHECKBOX_HLT, -1); RNA_def_property_update(prop, 0, "rna_Key_update_data"); + prop = RNA_def_property(srna, "lock_shape", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", KEYBLOCK_LOCKED_SHAPE); + RNA_def_property_ui_text( + prop, "Lock Shape", "Protect the shape key from accidental sculpting and editing"); + RNA_def_property_ui_icon(prop, ICON_UNLOCKED, 1); + RNA_def_property_update(prop, 0, "rna_Key_update_data"); + prop = RNA_def_property(srna, "slider_min", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, nullptr, "slidermin"); RNA_def_property_range(prop, -10.0f, 10.0f); -- 2.30.2