Fix #122961: Get out of 3D local views when they get empty.

This commit covers the case where all objects get removed from local
view, and an undo or blendfile read leads to such empty local views.

It also adds code to remapping/foreachID View3D callbacks to tag
potential local views as 'maybe empty', and use the View3D refresh
callback to actually check and potentially exit the local empty view.

Pull Request: https://projects.blender.org/blender/blender/pulls/123128
This commit is contained in:
Bastien Montagne
2024-07-23 18:23:02 +02:00
parent 544682f617
commit 03652c851c
6 changed files with 127 additions and 2 deletions

View File

@@ -1303,6 +1303,27 @@ void ED_view3d_buttons_region_layout_ex(const bContext *C,
/* `view3d_view.cc` */
/**
* Exit 'local view' of given View3D editor, if it is active and there is nothing to display in it
* anymore.
*
* \param depsgraph Optional, only required for #frame_selected.
* \param frame_selected Frame the newly out-of-local view to show currently visible selected
* objects. Will only do something if a valid #depsgraph pointer is also provided.
* \param smooth_viewtx Smooth transition time (in milliseconds) between current view and final
* view, if changes are hapenning. Currently only used if #frame_selected is enabled.
*
* \return `true` if the local view was actually exited.
*/
bool ED_localview_exit_if_empty(const Depsgraph *depsgraph,
Scene *scene,
ViewLayer *view_layer,
wmWindowManager *wm,
wmWindow *win,
View3D *v3d,
ScrArea *area,
bool frame_selected = true,
int smooth_viewtx = 0);
/**
* See if current UUID is valid, otherwise set a valid UUID to v3d,
* Try to keep the same UUID previously used to allow users to quickly toggle back and forth.

View File

@@ -1112,6 +1112,9 @@ static void view3d_main_region_listener(const wmRegionListenerParams *params)
case ND_LAYER_CONTENT:
ED_region_tag_redraw(region);
WM_gizmomap_tag_refresh(gzmap);
if (v3d->localvd && v3d->localvd->runtime.flag & V3D_RUNTIME_LOCAL_MAYBE_EMPTY) {
ED_area_tag_refresh(area);
}
break;
case ND_LAYER:
if (wmn->reference) {
@@ -1315,6 +1318,11 @@ static void view3d_main_region_listener(const wmRegionListenerParams *params)
break;
case NC_ID:
if (ELEM(wmn->action, NA_RENAME, NA_EDITED, NA_ADDED, NA_REMOVED)) {
if (ELEM(wmn->action, NA_EDITED, NA_REMOVED) && v3d->localvd &&
v3d->localvd->runtime.flag & V3D_RUNTIME_LOCAL_MAYBE_EMPTY)
{
ED_area_tag_refresh(area);
}
ED_region_tag_redraw(region);
WM_gizmomap_tag_refresh(gzmap);
}
@@ -1976,9 +1984,20 @@ static void space_view3d_listener(const wmSpaceTypeListenerParams *params)
static void space_view3d_refresh(const bContext *C, ScrArea *area)
{
UNUSED_VARS(C);
View3D *v3d = (View3D *)area->spacedata.first;
MEM_SAFE_FREE(v3d->runtime.local_stats);
if (v3d->localvd && v3d->localvd->runtime.flag & V3D_RUNTIME_LOCAL_MAYBE_EMPTY) {
ED_localview_exit_if_empty(CTX_data_ensure_evaluated_depsgraph(C),
CTX_data_scene(C),
CTX_data_view_layer(C),
CTX_wm_manager(C),
CTX_wm_window(C),
v3d,
CTX_wm_area(C),
true,
300);
}
}
static void view3d_id_remap_v3d_ob_centers(View3D *v3d,
@@ -2033,6 +2052,9 @@ static void view3d_id_remap(ScrArea *area,
if (view3d->localvd != nullptr) {
/* Object centers in local-view aren't used, see: #52663 */
view3d_id_remap_v3d(area, slink, view3d->localvd, mappings, true);
/* Remapping is potentially modifying ID pointers, and there is a local View3D, mark it for a
* check for emptiness. */
view3d->localvd->runtime.flag |= V3D_RUNTIME_LOCAL_MAYBE_EMPTY;
}
BKE_viewer_path_id_remap(&view3d->viewer_path, mappings);
}
@@ -2045,6 +2067,13 @@ static void view3d_foreach_id(SpaceLink *space_link, LibraryForeachIDData *data)
BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, v3d->ob_center, IDWALK_CB_DIRECT_WEAK_LINK);
if (v3d->localvd) {
BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, v3d->localvd->camera, IDWALK_CB_DIRECT_WEAK_LINK);
/* If potentially modifying ID pointers, and there is a local View3D, mark it for a check for
* emptiness. */
const int flags = BKE_lib_query_foreachid_process_flags_get(data);
if ((flags & IDWALK_READONLY) == 0) {
v3d->localvd->runtime.flag |= V3D_RUNTIME_LOCAL_MAYBE_EMPTY;
}
}
BKE_viewer_path_foreach_id(data, &v3d->viewer_path);
}

View File

@@ -989,7 +989,7 @@ static void view3d_localview_exit(const Depsgraph *depsgraph,
continue;
}
if (frame_selected) {
if (frame_selected && depsgraph) {
Object *camera_old_rv3d, *camera_new_rv3d;
camera_old_rv3d = (rv3d->persp == RV3D_CAMOB) ? camera_old : nullptr;
@@ -1018,6 +1018,34 @@ static void view3d_localview_exit(const Depsgraph *depsgraph,
}
}
bool ED_localview_exit_if_empty(const Depsgraph *depsgraph,
Scene *scene,
ViewLayer *view_layer,
wmWindowManager *wm,
wmWindow *win,
View3D *v3d,
ScrArea *area,
const bool frame_selected,
const int smooth_viewtx)
{
if (v3d->localvd == nullptr) {
return false;
}
v3d->localvd->runtime.flag &= ~V3D_RUNTIME_LOCAL_MAYBE_EMPTY;
BKE_view_layer_synced_ensure(scene, view_layer);
LISTBASE_FOREACH (Base *, base, BKE_view_layer_object_bases_get(view_layer)) {
if (base->local_view_bits & v3d->local_view_uid) {
return false;
}
}
view3d_localview_exit(
depsgraph, wm, win, scene, view_layer, area, frame_selected, smooth_viewtx);
return true;
}
static int localview_exec(bContext *C, wmOperator *op)
{
const Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
@@ -1108,6 +1136,19 @@ static int localview_remove_from_exec(bContext *C, wmOperator *op)
}
}
/* If some object was removed from the local view, exit the local view if it is now empty. */
if (changed) {
ED_localview_exit_if_empty(CTX_data_ensure_evaluated_depsgraph(C),
scene,
view_layer,
CTX_wm_manager(C),
CTX_wm_window(C),
v3d,
CTX_wm_area(C),
true,
WM_operator_smooth_viewtx_get(op));
}
if (changed) {
DEG_tag_on_visible_update(bmain, false);
DEG_id_tag_update(&scene->id, ID_RECALC_SELECT);

View File

@@ -29,6 +29,7 @@
#include "BKE_object.hh"
#include "BKE_packedFile.h"
#include "BKE_paint.hh"
#include "BKE_scene.hh"
#include "BKE_screen.hh"
#include "BKE_undo_system.hh"
@@ -47,6 +48,7 @@
#include "ED_sculpt.hh"
#include "ED_space_api.hh"
#include "ED_util.hh"
#include "ED_view3d.hh"
#include "GPU_immediate.hh"
@@ -71,6 +73,21 @@ void ED_editors_init_for_undo(Main *bmain)
BKE_texpaint_slots_refresh_object(scene, ob);
ED_paint_proj_mesh_data_check(*scene, *ob, nullptr, nullptr, nullptr, nullptr);
}
/* UI Updates. */
/* Flag local View3D's to check and exit if they are empty. */
LISTBASE_FOREACH (bScreen *, screen, &bmain->screens) {
LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
LISTBASE_FOREACH (SpaceLink *, sl, &area->spacedata) {
if (sl->spacetype == SPACE_VIEW3D) {
View3D *v3d = reinterpret_cast<View3D *>(sl);
if (v3d->localvd) {
v3d->localvd->runtime.flag |= V3D_RUNTIME_LOCAL_MAYBE_EMPTY;
}
}
}
}
}
}
}

View File

@@ -389,6 +389,8 @@ enum {
V3D_RUNTIME_XR_SESSION_ROOT = (1 << 0),
/** Some operators override the depth buffer for dedicated occlusion operations. */
V3D_RUNTIME_DEPTHBUF_OVERRIDDEN = (1 << 1),
/** Local view may have become empty, and may need to be exited. */
V3D_RUNTIME_LOCAL_MAYBE_EMPTY = (1 << 2),
};
/** #RegionView3D::persp */

View File

@@ -452,6 +452,21 @@ static void wm_file_read_setup_wm_finalize(bContext *C,
/* Else just using the new WM read from file, nothing to do. */
BLI_assert(wm_setup_data->old_wm == nullptr);
MEM_delete(wm_setup_data);
/* UI Updates. */
/* Flag local View3D's to check and exit if they are empty. */
LISTBASE_FOREACH (bScreen *, screen, &bmain->screens) {
LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
LISTBASE_FOREACH (SpaceLink *, sl, &area->spacedata) {
if (sl->spacetype == SPACE_VIEW3D) {
View3D *v3d = reinterpret_cast<View3D *>(sl);
if (v3d->localvd) {
v3d->localvd->runtime.flag |= V3D_RUNTIME_LOCAL_MAYBE_EMPTY;
}
}
}
}
}
}
/** \} */