Shape Keys: support locking to protect from accidental editing. #104463

Merged
Alexander Gavrilov merged 2 commits from angavrilov/blender:pr-shapekey-locking into main 2024-01-18 13:17:35 +01:00
36 changed files with 542 additions and 61 deletions

View File

@ -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.
dr.sybren marked this conversation as resolved Outdated

I read the function name as "there is an active shape key, and it is not locked". This is not what the code implements, though.

Avoid negated booleans. If "locked" is the modeled concept, stick to that. If you are using this function as a query "can the active shape key be edited?", use a name like object_check_active_shape_key_editable. But see below ;-)

With the negation as it is now, I'm struggling to understand the case where there is no active shape key, as in this case it returns True, but that doesn't match with how I read the function name.

And finally, I would recommend importing this function with from bpy_extras import object_utils, and then using object_utils.function_name(). That makes it clear at the call site where the function comes from. It also means that the word object can be removed from the function name, and check can be replaced with is to indicate the boolean nature. That would make the call object_utils.is_active_shape_key_editable(obj). But since all the calls are negating the returned value once again, object_utils.is_active_shape_key_locked(obj) seems like a more appropriate name (which implies negating the returned value, of course).

These comments about inverted booleans also apply to the C++ code.

I read the function name as "there is an active shape key, and it is not locked". This is not what the code implements, though. Avoid negated booleans. If "locked" is the modeled concept, stick to that. If you are using this function as a query "can the active shape key be edited?", use a name like `object_check_active_shape_key_editable`. But see below ;-) With the negation as it is now, I'm struggling to understand the case where there is no active shape key, as in this case it returns `True`, but that doesn't match with how I read the function name. And finally, I would recommend importing this function with `from bpy_extras import object_utils`, and then using `object_utils.function_name()`. That makes it clear at the call site where the function comes from. It also means that the word `object` can be removed from the function name, and `check` can be replaced with `is` to indicate the boolean nature. That would make the call `object_utils.is_active_shape_key_editable(obj)`. But since all the calls are negating the returned value once again, `object_utils.is_active_shape_key_locked(obj)` seems like a more appropriate name (which implies negating the returned value, of course). These comments about inverted booleans also apply to the C++ code.

The function is not a query though, its point is to encapsulate emitting the warning message to avoid having to repeat it, so that operators that need the check can contain just:

if not check(...):
   return

Rather than an 'is' query, it is more akin to 'assert', except it is not for debugging but for an actual behavior check, and uses a return code to signify the need to abort and unwind instead of throwing an exception.

If there is no active shape key, it cannot be locked (remember that unlocked is the default). That situation obviously is normal for any mesh without shape keys.

Edit: trying to make the intent clear, changed naming to "...verify...is_editable".

The function is not a query though, its point is to encapsulate emitting the warning message to avoid having to repeat it, so that operators that need the check can contain just: ``` if not check(...): return ``` Rather than an 'is' query, it is more akin to 'assert', except it is not for debugging but for an actual behavior check, and uses a return code to signify the need to abort and unwind instead of throwing an exception. If there is no active shape key, it cannot be locked (remember that unlocked is the default). That situation obviously is normal for any mesh without shape keys. Edit: trying to make the intent clear, changed naming to "...verify...is_editable".
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.
dr.sybren marked this conversation as resolved Outdated

In which cases does it make sense to call object_check_active_shape_key_unlocked(None)? As far as I can see, if there is no object specified it's a bug, and this function shouldn't hide that.

If there is a good reason to support calling with None, document it and change the code to:

if not obj:
    return True

That way the entire function body can be unindented.

In which cases does it make sense to call `object_check_active_shape_key_unlocked(None)`? As far as I can see, if there is no object specified it's a bug, and this function shouldn't hide that. If there is a good reason to support calling with `None`, document it and change the code to: ```python if not obj: return True ``` That way the entire function body can be unindented.

I was thinking about reducing repetition by allowing to call the function on a null object without explicitly checking, but that's probably going too far, so removed this check.

I was thinking about reducing repetition by allowing to call the function on a null object without explicitly checking, but that's probably going too far, so removed this check.
"""
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

View File

@ -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={

View File

@ -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)

View File

@ -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.

I don't understand the additional comment here. Does this mean that the index should be offset to account for the base key not counting? This should be documented better.

I don't understand the additional comment here. Does this mean that the index should be offset to account for the base key not counting? This should be documented better.

For whatever reason that function is designed to return null instead of the 0th (basis) key.

For whatever reason that function is designed to return null instead of the 0th (basis) key.
*/
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.
*/

View File

@ -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;

View File

@ -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) {

View File

@ -1897,12 +1897,7 @@ KeyBlock *BKE_keyblock_from_object(Object *ob)
{
Key *key = BKE_key_from_object(ob);
if (key) {
KeyBlock *kb = static_cast<KeyBlock *>(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<KeyBlock *>(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<KeyBlock *>(BLI_findlink(&key->block, index));
}
KeyBlock *BKE_keyblock_find_name(Key *key, const char name[])

View File

@ -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);
}
/** \} */

Since this function is called in a for-loop that iterates all objects in edit mode, there are a few usability issues:

  • The message can appear multiple times (once per object), but since it's the same every time, it doesn't provide any extra information beyond the first one.
  • There is no indication of which object the message is referred to.

I think both can be handled in one go by including the object name in the message.

Since this function is called in a `for`-loop that iterates all objects in edit mode, there are a few usability issues: - The message can appear multiple times (once per object), but since it's the same every time, it doesn't provide any extra information beyond the first one. - There is no indication of which object the message is referred to. I think both can be handled in one go by including the object name in the message.
/* -------------------------------------------------------------------- */
@ -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<Curve *>(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<ID *>(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<Curve *>(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<ID *>(obedit->data), 0);
}
MEM_freeN(objects);
return OPERATOR_FINISHED;
return totobjects ? OPERATOR_FINISHED : OPERATOR_CANCELLED;
}
void CURVE_OT_tilt_clear(wmOperatorType *ot)

View File

@ -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.
*/

View File

@ -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);

View File

@ -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.
*/

View File

@ -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,

View File

@ -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);

View File

@ -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);

View File

@ -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;

View File

@ -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);
}

View File

@ -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);

tot_locked is not a boolean, so I'd suggest being consistent & explicit here:

if (tot_selected == 0 && tot_locked == 0) 
`tot_locked` is not a boolean, so I'd suggest being consistent & explicit here: ```cpp if (tot_selected == 0 && tot_locked == 0) ```
if (tot_unselected == objects_len) {
if (tot_selected == 0 && !tot_locked) {
BKE_report(op->reports, RPT_WARNING, "No selected vertex");

I think it would be good to have a warning when all selected vertices were locked, so that you can tell from the warnings why the operator didn't do anything.

In general I think that's a good idea (letting the user know explicitly that nothing happened, and why), but here it's even more important because the original code already had such a warning in place.

Same for the other places where nothing happens because of the lock checks.

I think it would be good to have a warning when all selected vertices were locked, so that you can tell from the warnings why the operator didn't do anything. In general I think that's a good idea (letting the user know explicitly that nothing happened, and why), but here it's even more important because the original code already had such a warning in place. Same for the other places where nothing happens because of the lock checks.

There is no such thing as "nothing happens because of lock checks". Like I said, the whole point of those check functions not being those simple "is..." things you were suggesting is that they emit an error message if the check fails, thus avoiding the need to plaster the duplicated text of that message all over the code.

There is no such thing as "nothing happens because of lock checks". Like I said, the whole point of those check functions not being those simple "is..." things you were suggesting is that they emit an error message if the check fails, thus avoiding the need to plaster the duplicated text of that message all over the code.
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<KeyBlock *>(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<int *>(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)

View File

@ -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<Mesh *>(CTX_data_pointer_get_type(C, "mesh", &RNA_Mesh).data);

View File

@ -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);
}
}

View File

@ -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` */

View File

@ -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);

View File

@ -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;

View File

@ -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<Mesh *>(obedit->data));
break;
case OB_SURF:
case OB_CURVES_LEGACY:
key_block = ED_curve_get_edit_shape_key(static_cast<Curve *>(obedit->data));
break;
case OB_LATTICE:
key_block = ED_lattice_get_edit_shape_key(static_cast<Lattice *>(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<ID *>(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");
}
/** \} */

View File

@ -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;

View File

@ -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,

View File

@ -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);

View File

@ -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(

View File

@ -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);

View File

@ -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;

View File

@ -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) {

View File

@ -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--) {

View File

@ -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.
*

View File

@ -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;

View File

@ -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

View File

@ -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);

View File

@ -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<float(*)[3]>(