Fix #141827: Crash when switching from edit to sculpt mode when hidden

When switching modes, the active object visibility is used to determine
whether or not the mode can be entered. Whether or not this object is
null or not is dependent on the current mode. Object Mode uses the
underlying visibility, whereas other modes do not. When
`mode_compat_set` is used, the active object is switched to object mode,
causing `CTX_data_active_base` to be null.

To fix this immediate issue and prevent further refactors from
potentially reintroducing it, this commit makes a number of changes:

* Clarifies the `object::mode_compat_set` function docstring in
  ED_object.hh
* Makes `object::mode_compat_set` the last operator check
* Uses `BKE_view_layer_base_find` instead of `CTX_data_active_base`
  since both necessary parameters are already on-hand in the function.

Pull Request: https://projects.blender.org/blender/blender/pulls/141985
This commit is contained in:
Sean Kim
2025-07-18 00:26:12 +02:00
committed by Sean Kim
parent df6d6c0932
commit 245025480f
2 changed files with 11 additions and 7 deletions

View File

@@ -420,9 +420,13 @@ void constraint_copy_for_pose(Main *bmain, Object *ob_dst, bPoseChannel *pchan,
*/
bool mode_compat_test(const Object *ob, eObjectMode mode);
/**
* Sets the mode to a compatible state (use before entering the mode).
* Set the provided object's mode to one that is compatible with the provided mode.
*
* This is so each mode's exec function can call
* \returns true if the provided object's mode matches the provided mode, or if the function was
* able to set the object back into Object Mode.
*
* This is so each mode toggle operator exec function can call this function to ensure the current
* mode runtime data is cleaned up prior to entering a new mode.
*/
bool mode_compat_set(bContext *C, Object *ob, eObjectMode mode, ReportList *reports);
bool mode_set_ex(bContext *C, eObjectMode mode, bool use_undo, ReportList *reports);

View File

@@ -552,19 +552,19 @@ static wmOperatorStatus sculpt_mode_toggle_exec(bContext *C, wmOperator *op)
const bool is_mode_set = (ob.mode & mode_flag) != 0;
if (!is_mode_set) {
if (!object::mode_compat_set(C, &ob, eObjectMode(mode_flag), op->reports)) {
return OPERATOR_CANCELLED;
}
/* Being in sculpt mode on an invisible object is a confusing state; while switching the
* visibility of the current object shouldn't inherently change the mode, we prevent entering
* sculpt mode on an object that is already invisible to better align with how the mode toggle
* works currently. */
const View3D *v3d = CTX_wm_view3d(C);
const Base *base = CTX_data_active_base(C);
const Base *base = BKE_view_layer_base_find(&view_layer, &ob);
if (!BKE_base_is_visible(v3d, base)) {
return OPERATOR_CANCELLED;
}
if (!object::mode_compat_set(C, &ob, eObjectMode(mode_flag), op->reports)) {
return OPERATOR_CANCELLED;
}
}
if (is_mode_set) {