Add Chromium-only Blender WebEngine parity work

This commit is contained in:
mes123456
2026-08-12 04:47:48 -04:00
commit 9fd26010f6
18225 changed files with 11622124 additions and 0 deletions

View File

@@ -0,0 +1,73 @@
# SPDX-FileCopyrightText: 2023 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later
set(INC
../include
../../makesrna
# RNA_prototypes.hh
${CMAKE_BINARY_DIR}/source/blender/makesrna
)
set(INC_SYS
)
set(SRC
render_internal.cc
render_opengl.cc
render_ops.cc
render_preview.cc
render_shading.cc
render_texture_cache.cc
render_update.cc
render_view.cc
render_intern.hh
)
set(LIB
PRIVATE bf::blenkernel
PRIVATE bf::blenlib
PRIVATE bf::blenloader
PRIVATE bf::blentranslation
PRIVATE bf::bmesh
PRIVATE bf::depsgraph
PRIVATE bf::dna
PRIVATE bf::draw
PRIVATE bf::gpu
PRIVATE bf::imbuf
PRIVATE bf::imbuf::movie
PRIVATE bf::intern::clog
PRIVATE bf::intern::guardedalloc
PRIVATE bf::animrig
PRIVATE bf::nodes
PRIVATE bf::render
PRIVATE bf::sequencer
PRIVATE bf::windowmanager
)
if(WITH_HEADLESS)
add_definitions(-DWITH_HEADLESS)
else()
list(APPEND LIB
bf_editor_datafiles
)
endif()
if(WITH_FREESTYLE)
list(APPEND INC
../../freestyle
)
add_definitions(-DWITH_FREESTYLE)
endif()
if(WITH_CYCLES)
add_definitions(-DWITH_CYCLES)
list(APPEND INC ../../../../intern/cycles/blender)
endif()
blender_add_lib(bf_editor_render "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
# RNA_prototypes.hh
add_dependencies(bf_editor_render bf_rna)

View File

@@ -0,0 +1,110 @@
/* SPDX-FileCopyrightText: 2008 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edrend
*/
#pragma once
namespace blender {
struct ReportList;
struct Scene;
struct ScrArea;
struct bContext;
struct wmOperatorType;
/* `render_shading.cc` */
void OBJECT_OT_material_slot_add(wmOperatorType *ot);
void OBJECT_OT_material_slot_remove(wmOperatorType *ot);
void OBJECT_OT_material_slot_assign(wmOperatorType *ot);
void OBJECT_OT_material_slot_select(wmOperatorType *ot);
void OBJECT_OT_material_slot_deselect(wmOperatorType *ot);
void OBJECT_OT_material_slot_copy(wmOperatorType *ot);
void OBJECT_OT_material_slot_move(wmOperatorType *ot);
void OBJECT_OT_material_slot_remove_unused(wmOperatorType *ot);
void OBJECT_OT_material_slot_remove_all(wmOperatorType *ot);
void MATERIAL_OT_new(wmOperatorType *ot);
void TEXTURE_OT_new(wmOperatorType *ot);
void WORLD_OT_new(wmOperatorType *ot);
void MATERIAL_OT_copy(wmOperatorType *ot);
void MATERIAL_OT_paste(wmOperatorType *ot);
void SCENE_OT_view_layer_add(wmOperatorType *ot);
void SCENE_OT_view_layer_remove(wmOperatorType *ot);
void SCENE_OT_view_layer_add_aov(wmOperatorType *ot);
void SCENE_OT_view_layer_remove_aov(wmOperatorType *ot);
void SCENE_OT_view_layer_add_lightgroup(wmOperatorType *ot);
void SCENE_OT_view_layer_remove_lightgroup(wmOperatorType *ot);
void SCENE_OT_view_layer_add_used_lightgroups(wmOperatorType *ot);
void SCENE_OT_view_layer_remove_unused_lightgroups(wmOperatorType *ot);
void OBJECT_OT_lightprobe_cache_bake(wmOperatorType *ot);
void OBJECT_OT_lightprobe_cache_free(wmOperatorType *ot);
void SCENE_OT_render_view_add(wmOperatorType *ot);
void SCENE_OT_render_view_remove(wmOperatorType *ot);
#ifdef WITH_FREESTYLE
void SCENE_OT_freestyle_module_add(wmOperatorType *ot);
void SCENE_OT_freestyle_module_remove(wmOperatorType *ot);
void SCENE_OT_freestyle_module_move(wmOperatorType *ot);
void SCENE_OT_freestyle_lineset_add(wmOperatorType *ot);
void SCENE_OT_freestyle_lineset_copy(wmOperatorType *ot);
void SCENE_OT_freestyle_lineset_paste(wmOperatorType *ot);
void SCENE_OT_freestyle_lineset_remove(wmOperatorType *ot);
void SCENE_OT_freestyle_lineset_move(wmOperatorType *ot);
void SCENE_OT_freestyle_linestyle_new(wmOperatorType *ot);
void SCENE_OT_freestyle_color_modifier_add(wmOperatorType *ot);
void SCENE_OT_freestyle_alpha_modifier_add(wmOperatorType *ot);
void SCENE_OT_freestyle_thickness_modifier_add(wmOperatorType *ot);
void SCENE_OT_freestyle_geometry_modifier_add(wmOperatorType *ot);
void SCENE_OT_freestyle_modifier_remove(wmOperatorType *ot);
void SCENE_OT_freestyle_modifier_move(wmOperatorType *ot);
void SCENE_OT_freestyle_modifier_copy(wmOperatorType *ot);
void SCENE_OT_freestyle_stroke_material_create(wmOperatorType *ot);
#endif
void TEXTURE_OT_slot_copy(wmOperatorType *ot);
void TEXTURE_OT_slot_paste(wmOperatorType *ot);
void TEXTURE_OT_slot_move(wmOperatorType *ot);
/* `render_texture_cache.cc` */
void RENDER_OT_generate_texture_cache(wmOperatorType *ot);
void RENDER_OT_clear_texture_cache(wmOperatorType *ot);
/* `render_internal.cc` */
/* Base class for all WM_JOB_TYPE_RENDER jobs. */
struct RenderJobBase {
Scene *scene = nullptr;
Scene *current_scene = nullptr;
};
/**
* Contextual render, using current scene, view3d?
*/
void RENDER_OT_render(wmOperatorType *ot);
void RENDER_OT_shutter_curve_preset(wmOperatorType *ot);
/* `render_view.cc` */
/**
* New window uses x,y to set position.
*/
ScrArea *render_view_open(bContext *C, int mx, int my, ReportList *reports);
void RENDER_OT_view_show(wmOperatorType *ot);
void RENDER_OT_view_cancel(wmOperatorType *ot);
/* `render_opengl.cc` */
void RENDER_OT_opengl(wmOperatorType *ot);
} // namespace blender

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,94 @@
/* SPDX-FileCopyrightText: 2009 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edrend
*/
#include <cstdlib>
#include "ED_render.hh"
#include "WM_api.hh"
#include "render_intern.hh" /* own include */
namespace blender {
/***************************** render ***********************************/
void ED_operatortypes_render()
{
WM_operatortype_append(OBJECT_OT_material_slot_add);
WM_operatortype_append(OBJECT_OT_material_slot_remove);
WM_operatortype_append(OBJECT_OT_material_slot_assign);
WM_operatortype_append(OBJECT_OT_material_slot_select);
WM_operatortype_append(OBJECT_OT_material_slot_deselect);
WM_operatortype_append(OBJECT_OT_material_slot_copy);
WM_operatortype_append(OBJECT_OT_material_slot_move);
WM_operatortype_append(OBJECT_OT_material_slot_remove_unused);
WM_operatortype_append(OBJECT_OT_material_slot_remove_all);
WM_operatortype_append(OBJECT_OT_lightprobe_cache_bake);
WM_operatortype_append(OBJECT_OT_lightprobe_cache_free);
WM_operatortype_append(MATERIAL_OT_new);
WM_operatortype_append(TEXTURE_OT_new);
WM_operatortype_append(WORLD_OT_new);
WM_operatortype_append(MATERIAL_OT_copy);
WM_operatortype_append(MATERIAL_OT_paste);
WM_operatortype_append(SCENE_OT_view_layer_add);
WM_operatortype_append(SCENE_OT_view_layer_remove);
WM_operatortype_append(SCENE_OT_view_layer_add_aov);
WM_operatortype_append(SCENE_OT_view_layer_remove_aov);
WM_operatortype_append(SCENE_OT_view_layer_add_lightgroup);
WM_operatortype_append(SCENE_OT_view_layer_remove_lightgroup);
WM_operatortype_append(SCENE_OT_view_layer_add_used_lightgroups);
WM_operatortype_append(SCENE_OT_view_layer_remove_unused_lightgroups);
WM_operatortype_append(SCENE_OT_render_view_add);
WM_operatortype_append(SCENE_OT_render_view_remove);
#ifdef WITH_FREESTYLE
WM_operatortype_append(SCENE_OT_freestyle_module_add);
WM_operatortype_append(SCENE_OT_freestyle_module_remove);
WM_operatortype_append(SCENE_OT_freestyle_module_move);
WM_operatortype_append(SCENE_OT_freestyle_lineset_add);
WM_operatortype_append(SCENE_OT_freestyle_lineset_copy);
WM_operatortype_append(SCENE_OT_freestyle_lineset_paste);
WM_operatortype_append(SCENE_OT_freestyle_lineset_remove);
WM_operatortype_append(SCENE_OT_freestyle_lineset_move);
WM_operatortype_append(SCENE_OT_freestyle_linestyle_new);
WM_operatortype_append(SCENE_OT_freestyle_color_modifier_add);
WM_operatortype_append(SCENE_OT_freestyle_alpha_modifier_add);
WM_operatortype_append(SCENE_OT_freestyle_thickness_modifier_add);
WM_operatortype_append(SCENE_OT_freestyle_geometry_modifier_add);
WM_operatortype_append(SCENE_OT_freestyle_modifier_remove);
WM_operatortype_append(SCENE_OT_freestyle_modifier_move);
WM_operatortype_append(SCENE_OT_freestyle_modifier_copy);
WM_operatortype_append(SCENE_OT_freestyle_stroke_material_create);
#endif
WM_operatortype_append(TEXTURE_OT_slot_copy);
WM_operatortype_append(TEXTURE_OT_slot_paste);
WM_operatortype_append(TEXTURE_OT_slot_move);
#ifdef WITH_CYCLES
WM_operatortype_append(RENDER_OT_generate_texture_cache);
WM_operatortype_append(RENDER_OT_clear_texture_cache);
#endif
/* `render_internal.cc` */
WM_operatortype_append(RENDER_OT_view_show);
WM_operatortype_append(RENDER_OT_render);
WM_operatortype_append(RENDER_OT_view_cancel);
WM_operatortype_append(RENDER_OT_shutter_curve_preset);
/* `render_opengl.cc` */
WM_operatortype_append(RENDER_OT_opengl);
}
} // namespace blender

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,490 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edrend
*/
#ifdef WITH_CYCLES
# include "render_intern.hh" /* own include */
# include <fmt/format.h>
# include <mutex>
# include <string>
# include <utility>
# include "MEM_guardedalloc.h"
# include "DNA_image_types.h"
# include "DNA_userdef_types.h"
# include "DNA_windowmanager_enums.h"
# include "BLI_fileops.h"
# include "BLI_listbase.h"
# include "BLI_path_utils.hh"
# include "BLI_set.hh"
# include "BLI_string.h"
# include "BLI_task.hh"
# include "BLI_utildefines.h"
# include "BLI_vector.hh"
# include "BKE_bpath.hh"
# include "BKE_context.hh"
# include "BKE_global.hh"
# include "BKE_image.hh"
# include "BKE_lib_query.hh"
# include "BKE_main.hh"
# include "BKE_node.hh"
# include "BKE_report.hh"
# include "BLT_translation.hh"
# include "RNA_access.hh"
# include "RNA_define.hh"
# include "UI_interface_icons.hh"
# include "UI_interface_layout.hh"
# include "UI_resources.hh"
# include "WM_api.hh"
# include "CCL_api.h"
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Texture Cache Image Gathering
* \{ */
/* Gather all images referenced by shader node trees. */
static Set<const Image *> gather_cache_images(Main *bmain)
{
Set<const Image *> images;
FOREACH_NODETREE_BEGIN (bmain, ntree, id) {
if (ntree->type != NTREE_SHADER) {
continue;
}
BKE_library_foreach_ID_link(
bmain,
&ntree->id,
[&](LibraryIDLinkCallbackData *cb_data) {
ID *image_id = *cb_data->id_pointer;
if (image_id && GS(image_id->name) == ID_IM) {
Image *image = blender::id_cast<Image *>(image_id);
images.add(image);
}
return IDWALK_RET_NOP;
},
nullptr,
IDWALK_READONLY);
}
FOREACH_NODETREE_END;
return images;
}
/* Gather all source image files to the texture cache, expanding UDIM tiles and
* optionally image sequences. */
static Vector<std::pair<const Image *, std::string>> gather_cache_filepaths(
Main *bmain, const bool include_sequences)
{
Vector<std::pair<const Image *, std::string>> filepaths;
for (const Image *image : gather_cache_images(bmain)) {
if (ELEM(image->source, IMA_SRC_MOVIE, IMA_SRC_GENERATED, IMA_SRC_VIEWER)) {
continue;
}
if (BKE_image_has_packedfile(image)) {
continue;
}
if (image->filepath[0] == '\0') {
continue;
}
/* Get regular absolute path. */
char filepath[FILE_MAX];
STRNCPY(filepath, image->filepath);
BLI_path_abs(filepath, ID_BLEND_PATH_FROM_GLOBAL(&image->id));
BLI_path_normalize(filepath);
/* Handle each UDIM tile. */
if (image->source == IMA_SRC_TILED) {
eUDIM_TILE_FORMAT tile_format = UDIM_TILE_FORMAT_NONE;
char *udim_pattern = BKE_image_get_tile_strformat(filepath, &tile_format);
if (tile_format != UDIM_TILE_FORMAT_NONE) {
for (const ImageTile &tile : image->tiles) {
char tile_filepath[FILE_MAX];
BKE_image_set_filepath_from_tile_number(
tile_filepath, udim_pattern, tile_format, tile.tile_number);
if (BLI_is_file(tile_filepath)) {
filepaths.append({image, tile_filepath});
}
}
MEM_delete(udim_pattern);
continue;
}
}
/* Handle each image sequence frame. */
if (image->source == IMA_SRC_SEQUENCE) {
if (include_sequences) {
BKE_bpath_sequence_filepaths_foreach(filepath, [&](StringRef frame_filepath) {
filepaths.append({image, frame_filepath});
});
}
continue;
}
/* Handle regular image. */
if (BLI_is_file(filepath)) {
filepaths.append({image, filepath});
}
}
return filepaths;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Generate Texture Cache Operator
* \{ */
struct GenerateTextureCacheJob {
Main *bmain;
bool include_sequences;
};
struct GenerateTextureCacheUI {
int sequence_generate_num = 0;
};
static void generate_texture_cache(Main *bmain,
ReportList *reports,
const bool include_sequences,
wmJobWorkerStatus *worker_status = nullptr)
{
const Vector<std::pair<const Image *, std::string>> filepaths = gather_cache_filepaths(
bmain, include_sequences);
/* Determine which texture cache files need to be generated. */
Set<std::string> found_tx;
int uptodate_num = 0;
Vector<std::pair<const Image *, std::string>> outdated;
for (const auto &item : filepaths) {
std::string tx_filepath;
const bool up_to_date = CCL_resolve_texture_cache(
item.first, item.second.c_str(), U.texture_cachedir, tx_filepath);
if (tx_filepath.empty() || !found_tx.add(tx_filepath)) {
continue;
}
if (up_to_date) {
uptodate_num++;
}
else {
outdated.append(item);
}
}
/* Generate texture cache. */
std::atomic<int> completed_num = 0;
std::atomic<int> failed_num = 0;
std::mutex reports_mutex;
blender::threading::parallel_for_each(outdated, [&](const auto &item) {
if (worker_status) {
if (G.is_break || worker_status->stop) {
return;
}
worker_status->progress = (completed_num + failed_num) / float(outdated.size());
worker_status->do_update = true;
}
if (CCL_generate_texture_cache(item.first, item.second.c_str(), U.texture_cachedir)) {
completed_num++;
}
else {
std::scoped_lock lock(reports_mutex);
BKE_reportf(
reports, RPT_ERROR, "Failed to generate texture cache for: %s", item.second.c_str());
failed_num++;
}
});
/* Report stats. */
if (found_tx.is_empty()) {
BKE_report(reports, RPT_INFO, "No image files found to generate tx files");
}
else {
BKE_reportf(reports,
(failed_num) ? RPT_WARNING : RPT_INFO,
"Generated %d tx files, %d failed, %d up to date",
completed_num.load(),
failed_num.load(),
uptodate_num);
}
}
static void generate_texture_cache_startjob(void *customdata, wmJobWorkerStatus *worker_status)
{
GenerateTextureCacheJob *job = static_cast<GenerateTextureCacheJob *>(customdata);
generate_texture_cache(
job->bmain, worker_status->reports, job->include_sequences, worker_status);
}
static wmOperatorStatus generate_texture_cache_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
const bool include_sequences = RNA_boolean_get(op->ptr, "generate_sequences");
/* No dialog, execute immediately without job. */
if (op->customdata == nullptr) {
generate_texture_cache(bmain, op->reports, include_sequences);
return OPERATOR_FINISHED;
}
MEM_delete(static_cast<GenerateTextureCacheUI *>(op->customdata));
op->customdata = nullptr;
wmWindowManager *wm = CTX_wm_manager(C);
wmJob *wm_job = WM_jobs_get(wm,
CTX_wm_window(C),
bmain,
RPT_("Generating texture cache..."),
WM_JOB_PRIORITY | WM_JOB_PROGRESS,
WM_JOB_TYPE_GENERATE_TEXTURE_CACHE);
GenerateTextureCacheJob *job = MEM_new<GenerateTextureCacheJob>(__func__);
job->bmain = bmain;
job->include_sequences = include_sequences;
WM_jobs_customdata_set(wm_job, job, [](void *customdata) {
MEM_delete(static_cast<GenerateTextureCacheJob *>(customdata));
});
WM_jobs_timer(wm_job, 0.2, NC_WM | ND_JOB, 0);
WM_jobs_callbacks(wm_job, generate_texture_cache_startjob, nullptr, nullptr, nullptr);
G.is_break = false;
WM_jobs_start(wm, wm_job);
return OPERATOR_FINISHED;
}
static wmOperatorStatus generate_texture_cache_invoke(bContext *C,
wmOperator *op,
const wmEvent * /*event*/)
{
Main *bmain = CTX_data_main(C);
/* Count files. */
Set<std::string> found_tx;
int regular_generate_num = 0;
int sequence_generate_num = 0;
int uptodate_num = 0;
const bool include_sequences = true;
const Vector<std::pair<const Image *, std::string>> filepaths = gather_cache_filepaths(
bmain, include_sequences);
for (const auto &item : filepaths) {
const bool is_sequence = item.first->source == IMA_SRC_SEQUENCE;
std::string tx_filepath;
const bool up_to_date = CCL_resolve_texture_cache(
item.first, item.second.c_str(), U.texture_cachedir, tx_filepath);
if (tx_filepath.empty() || !found_tx.add(tx_filepath)) {
continue;
}
if (up_to_date) {
uptodate_num++;
}
else if (is_sequence) {
sequence_generate_num++;
}
else {
regular_generate_num++;
}
}
/* Nothing to generate, just execute to show the message. */
if (regular_generate_num == 0 && sequence_generate_num == 0) {
return generate_texture_cache_exec(C, op);
}
/* Show confirmation dialog. */
std::string message;
if (uptodate_num > 0) {
message += fmt::format(fmt::runtime(IFACE_("{} tx files up to date")), uptodate_num);
}
if (regular_generate_num > 0) {
if (!message.empty()) {
message += '\n';
}
message += fmt::format(fmt::runtime(IFACE_("{} tx files to be generated")),
regular_generate_num);
}
GenerateTextureCacheUI *data = MEM_new<GenerateTextureCacheUI>(__func__);
data->sequence_generate_num = sequence_generate_num;
op->customdata = data;
return WM_operator_props_dialog_popup(C,
op,
300,
IFACE_("Generate Texture Cache?"),
IFACE_("Generate"),
false,
std::move(message),
true);
}
static void generate_texture_cache_ui(bContext * /*C*/, wmOperator *op)
{
ui::Layout &layout = *op->layout;
const GenerateTextureCacheUI *data = static_cast<GenerateTextureCacheUI *>(op->customdata);
if (data) {
if (data->sequence_generate_num > 0) {
const std::string label = fmt::format(
fmt::runtime(IFACE_("Include {} image sequence tx files")), data->sequence_generate_num);
layout.prop(op->ptr, "generate_sequences", UI_ITEM_NONE, label, ICON_NONE);
}
}
else {
/* Show unconditionally outside the confirmation dialog, like a redo panel. */
layout.prop(op->ptr, "generate_sequences", UI_ITEM_NONE, nullptr, ICON_NONE);
}
}
static void generate_texture_cache_cancel(bContext * /*C*/, wmOperator *op)
{
if (op->customdata) {
MEM_delete(static_cast<GenerateTextureCacheUI *>(op->customdata));
op->customdata = nullptr;
}
}
void RENDER_OT_generate_texture_cache(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Generate Texture Cache";
ot->idname = "RENDER_OT_generate_texture_cache";
ot->description = "Generate Cycles texture cache files for all images used in shader nodes";
/* API callbacks. */
ot->exec = generate_texture_cache_exec;
ot->invoke = generate_texture_cache_invoke;
ot->ui = generate_texture_cache_ui;
ot->cancel = generate_texture_cache_cancel;
/* flags */
ot->flag = OPTYPE_REGISTER;
/* properties */
RNA_def_boolean(ot->srna,
"generate_sequences",
true,
"Image Sequences",
"Generate texture cache files for all frames of image sequences");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Clear Texture Cache Operator
* \{ */
static Set<std::string> gather_clear_tx_files(Main *bmain)
{
Set<std::string> tx_files;
const bool include_sequences = true;
const Vector<std::pair<const Image *, std::string>> filepaths = gather_cache_filepaths(
bmain, include_sequences);
Set<std::string> source_filepaths;
for (const auto &item : filepaths) {
source_filepaths.add(item.second);
}
for (const auto &item : filepaths) {
BKE_image_texture_cache_filepaths_foreach(
item.second.c_str(), U.texture_cachedir, [&](StringRef cache_filepath) {
/* Defensive check just in case a tx file was referenced directly even
* though this should not be done. */
if (source_filepaths.contains(cache_filepath)) {
return;
}
tx_files.add(cache_filepath);
});
}
return tx_files;
}
static wmOperatorStatus clear_texture_cache_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
const Set<std::string> tx_files = gather_clear_tx_files(bmain);
int deleted_num = 0;
for (const std::string &tx_filepath : tx_files) {
if (BLI_delete(tx_filepath.c_str(), false, false) == 0) {
deleted_num++;
}
else {
BKE_reportf(op->reports, RPT_ERROR, "Failed to delete tx file: %s", tx_filepath.c_str());
}
}
BKE_reportf(op->reports, RPT_INFO, "Deleted %d tx files", deleted_num);
return OPERATOR_FINISHED;
}
static wmOperatorStatus clear_texture_cache_invoke(bContext *C,
wmOperator *op,
const wmEvent * /*event*/)
{
Main *bmain = CTX_data_main(C);
const int used_num = gather_clear_tx_files(bmain).size();
if (used_num == 0) {
BKE_report(op->reports, RPT_INFO, "No tx files to delete");
return OPERATOR_CANCELLED;
}
const std::string message = fmt::format(fmt::runtime(IFACE_("{} tx files to be deleted")),
used_num);
return WM_operator_confirm_ex(C,
op,
IFACE_("Clear Texture Cache?"),
message.c_str(),
IFACE_("Clear Cache"),
ui::AlertIcon::Info,
false);
}
void RENDER_OT_clear_texture_cache(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Clear Texture Cache";
ot->idname = "RENDER_OT_clear_texture_cache";
ot->description = "Delete Cycles texture cache files from disk";
/* API callbacks. */
ot->exec = clear_texture_cache_exec;
ot->invoke = clear_texture_cache_invoke;
/* flags */
ot->flag = OPTYPE_REGISTER;
}
/** \} */
} // namespace blender
#endif

View File

@@ -0,0 +1,396 @@
/* SPDX-FileCopyrightText: 2009 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edrend
*/
#include <cstdlib>
#include <cstring>
#include "DNA_anim_types.h"
#include "DNA_brush_types.h"
#include "DNA_cachefile_types.h"
#include "DNA_light_types.h"
#include "DNA_material_types.h"
#include "DNA_node_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DNA_screen_types.h"
#include "DNA_space_types.h"
#include "DNA_view3d_types.h"
#include "DNA_windowmanager_types.h"
#include "DNA_world_types.h"
#include "DRW_engine.hh"
#include "BLI_listbase.h"
#include "BLI_threads.h"
#include "BKE_brush.hh"
#include "BKE_context.hh"
#include "BKE_icons.hh"
#include "BKE_main.hh"
#include "BKE_main_invariants.hh"
#include "BKE_material.hh"
#include "BKE_node_runtime.hh"
#include "BKE_node_tree_update.hh"
#include "BKE_paint.hh"
#include "BKE_scene.hh"
#include "RE_engine.h"
#include "RE_pipeline.h"
#include "SEQ_animation.hh"
#include "SEQ_prefetch.hh"
#include "SEQ_relations.hh"
#include "SEQ_sequencer.hh"
#include "ED_node.hh"
#include "ED_node_preview.hh"
#include "ED_paint.hh"
#include "ED_render.hh"
#include "ED_view3d.hh"
#include "DEG_depsgraph.hh"
#include "DEG_depsgraph_query.hh"
#include "WM_api.hh"
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Render Engines
* \{ */
void ED_render_view3d_update(Depsgraph *depsgraph,
wmWindow *window,
ScrArea *area,
const bool updated)
{
Main *bmain = DEG_get_bmain(depsgraph);
Scene *scene = DEG_get_input_scene(depsgraph);
for (ARegion &region : area->regionbase) {
if (region.regiontype != RGN_TYPE_WINDOW) {
continue;
}
RegionView3D *rv3d = static_cast<RegionView3D *>(region.regiondata);
RenderEngine *engine = rv3d->view_render ? RE_view_engine_get(rv3d->view_render) : nullptr;
/* call update if the scene changed, or if the render engine
* tagged itself for update (e.g. because it was busy at the
* time of the last update) */
if (engine && (updated || (engine->flag & RE_ENGINE_DO_UPDATE))) {
/* Create temporary context to execute callback in. */
bContext *C = CTX_create();
CTX_data_main_set(C, bmain);
CTX_data_scene_set(C, scene);
CTX_wm_manager_set(C, static_cast<wmWindowManager *>(bmain->wm.first));
CTX_wm_window_set(C, window);
CTX_wm_screen_set(C, WM_window_get_active_screen(window));
CTX_wm_area_set(C, area);
CTX_wm_region_set(C, &region);
engine->flag &= ~RE_ENGINE_DO_UPDATE;
/* NOTE: Important to pass non-updated depsgraph, This is because this function is called
* from inside dependency graph evaluation. Additionally, if we pass fully evaluated one
* we will lose updates stored in the graph. */
engine->type->view_update(engine, C, CTX_data_depsgraph_pointer(C));
CTX_free(C);
}
}
}
static void update_compositor(const DEGEditorUpdateContext *update_context)
{
const Scene *scene = DEG_get_evaluated(update_context->depsgraph, update_context->scene);
const bNodeTree *node_tree = scene->compositing_node_group;
if (!node_tree) {
return;
}
if (node_tree->id.recalc & ID_RECALC_NTREE_OUTPUT) {
ED_node_compositor_job(
update_context->bmain, update_context->scene, update_context->view_layer);
}
}
void ED_render_scene_update(const DEGEditorUpdateContext *update_ctx, const bool updated)
{
Main *bmain = update_ctx->bmain;
static bool recursive_check = false;
/* don't do this render engine update if we're updating the scene from
* other threads doing e.g. rendering or baking jobs */
if (!BLI_thread_is_main()) {
return;
}
/* don't call this recursively for frame updates */
if (recursive_check) {
return;
}
/* Do not call if no WM available, see #42688. */
if (bmain->wm.is_empty()) {
return;
}
recursive_check = true;
wmWindowManager *wm = static_cast<wmWindowManager *>(bmain->wm.first);
for (wmWindow &window : wm->windows) {
bScreen *screen = WM_window_get_active_screen(&window);
for (ScrArea &area : screen->areabase) {
if (area.spacetype == SPACE_VIEW3D) {
ED_render_view3d_update(update_ctx->depsgraph, &window, &area, updated);
}
}
}
update_compositor(update_ctx);
recursive_check = false;
}
void ED_render_engine_area_exit(Main *bmain, ScrArea *area)
{
/* clear all render engines in this area */
wmWindowManager *wm = static_cast<wmWindowManager *>(bmain->wm.first);
if (area->spacetype != SPACE_VIEW3D) {
return;
}
for (ARegion &region : area->regionbase) {
if (region.regiontype != RGN_TYPE_WINDOW || !(region.regiondata)) {
continue;
}
ED_view3d_stop_render_preview(wm, &region);
}
}
void ED_render_engine_changed(Main *bmain, const bool update_scene_data)
{
/* on changing the render engine type, clear all running render engines */
for (bScreen *screen = static_cast<bScreen *>(bmain->screens.first); screen;
screen = static_cast<bScreen *>(screen->id.next))
{
for (ScrArea &area : screen->areabase) {
ED_render_engine_area_exit(bmain, &area);
}
}
/* Stop and invalidate all shader previews. */
ED_preview_kill_jobs(static_cast<wmWindowManager *>(bmain->wm.first), bmain);
for (Material &ma : bmain->materials) {
BKE_material_make_node_previews_dirty(&ma);
}
RE_FreePersistentData(nullptr);
/* Inform all render engines and draw managers. */
DEGEditorUpdateContext update_ctx = {nullptr};
update_ctx.bmain = bmain;
for (Scene *scene = static_cast<Scene *>(bmain->scenes.first); scene;
scene = static_cast<Scene *>(scene->id.next))
{
update_ctx.scene = scene;
for (ViewLayer &view_layer : scene->view_layers) {
/* TDODO(sergey): Iterate over depsgraphs instead? */
update_ctx.depsgraph = BKE_scene_ensure_depsgraph(bmain, scene, &view_layer);
update_ctx.view_layer = &view_layer;
ED_render_id_flush_update(&update_ctx, &scene->id);
}
if (update_scene_data) {
BKE_ntree_update_tag_id_changed(bmain, &scene->id);
BKE_ntree_update(*bmain);
}
}
BKE_main_ensure_invariants(*bmain);
}
void ED_render_view_layer_changed(Main *bmain, bScreen *screen)
{
for (ScrArea &area : screen->areabase) {
ED_render_engine_area_exit(bmain, &area);
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Updates
*
* #ED_render_id_flush_update gets called from #DEG_id_tag_update,
* to do editor level updates when the ID changes.
* When these ID blocks are in the dependency graph,
* we can get rid of the manual dependency checks.
* \{ */
static void material_changed(Main *bmain, Material *ma)
{
/* icons */
BKE_icon_changed(BKE_icon_id_ensure(&ma->id));
ED_previews_tag_dirty_by_id(*bmain, ma->id);
}
static void lamp_changed(Main *bmain, Light *la)
{
/* icons */
BKE_icon_changed(BKE_icon_id_ensure(&la->id));
ED_previews_tag_dirty_by_id(*bmain, la->id);
}
static void texture_changed(Main *bmain, Tex *tex)
{
Scene *scene;
/* icons */
BKE_icon_changed(BKE_icon_id_ensure(&tex->id));
ED_previews_tag_dirty_by_id(*bmain, tex->id);
for (scene = static_cast<Scene *>(bmain->scenes.first); scene;
scene = static_cast<Scene *>(scene->id.next))
{
/* paint overlays */
for (ViewLayer &view_layer : scene->view_layers) {
BKE_paint_invalidate_overlay_tex(*bmain, scene, &view_layer, tex);
}
}
for (Brush &brush : bmain->brushes) {
if (ELEM(tex, brush.mtex.tex, brush.mask_mtex.tex)) {
BKE_brush_tag_unsaved_changes(&brush);
}
}
}
static void world_changed(Main *bmain, World *wo)
{
/* icons */
BKE_icon_changed(BKE_icon_id_ensure(&wo->id));
ED_previews_tag_dirty_by_id(*bmain, wo->id);
}
static void image_changed(Main *bmain, Image *ima)
{
Tex *tex;
/* icons */
BKE_icon_changed(BKE_icon_id_ensure(&ima->id));
ED_previews_tag_dirty_by_id(*bmain, ima->id);
/* textures */
for (tex = static_cast<Tex *>(bmain->textures.first); tex;
tex = static_cast<Tex *>(tex->id.next))
{
if (tex->type == TEX_IMAGE && tex->ima == ima) {
texture_changed(bmain, tex);
}
}
/* Ensure downstream editors are made aware of changes to the Image data. */
WM_main_add_notifier(NC_IMAGE | NA_EDITED, ima);
}
static void scene_changed(Main *bmain, Scene *scene)
{
Object *ob;
/* glsl */
for (ob = static_cast<Object *>(bmain->objects.first); ob;
ob = static_cast<Object *>(ob->id.next))
{
if (ob->mode & OB_MODE_TEXTURE_PAINT) {
BKE_texpaint_slots_refresh_object(scene, ob);
ED_paint_proj_mesh_data_check(*scene, *ob, nullptr, nullptr, nullptr, nullptr);
}
}
}
static void update_sequencer(const DEGEditorUpdateContext *update_ctx, Main *bmain, ID *id)
{
if (ELEM(id->recalc,
0,
ID_RECALC_SELECT,
ID_RECALC_FRAME_CHANGE,
ID_RECALC_AUDIO_FPS,
ID_RECALC_AUDIO_VOLUME,
ID_RECALC_AUDIO_MUTE,
ID_RECALC_AUDIO_LISTENER,
ID_RECALC_AUDIO))
{
return;
}
Scene *changed_scene = update_ctx->scene;
if (GS(id->name) != ID_SCE) {
seq::relations_invalidate_scene_strips(bmain, changed_scene);
}
/* Invalidate rendered VSE caches in `changed_scene`, because strip animation may have been
* updated. */
if (GS(id->name) == ID_AC) {
Editing *ed = seq::editing_get(changed_scene);
if (ed != nullptr && seq::animation_keyframes_exist(changed_scene) &&
&changed_scene->adt->action->id == id)
{
seq::prefetch_stop(changed_scene);
seq::cache_cleanup(changed_scene, seq::CacheCleanup::FinalAndIntra);
}
}
/* Invalidate cache for strips that use this compositing tree. */
if (GS(id->name) == ID_NT) {
const bNodeTree *node_tree = reinterpret_cast<const bNodeTree *>(id);
if (node_tree->type == NTREE_COMPOSIT) {
seq::relations_invalidate_compositor_users(bmain, node_tree);
}
}
}
void ED_render_id_flush_update(const DEGEditorUpdateContext *update_ctx, ID *id)
{
/* this can be called from render or baking thread when a python script makes
* changes, in that case we don't want to do any editor updates, and making
* GPU changes is not possible because OpenGL only works in the main thread */
if (!BLI_thread_is_main()) {
return;
}
Main *bmain = update_ctx->bmain;
/* Internal ID update handlers. */
switch (GS(id->name)) {
case ID_MA:
material_changed(bmain, id_cast<Material *>(id));
break;
case ID_TE:
texture_changed(bmain, id_cast<Tex *>(id));
break;
case ID_WO:
world_changed(bmain, id_cast<World *>(id));
break;
case ID_LA:
lamp_changed(bmain, id_cast<Light *>(id));
break;
case ID_IM:
image_changed(bmain, id_cast<Image *>(id));
break;
case ID_SCE:
scene_changed(bmain, id_cast<Scene *>(id));
break;
case ID_BR:
BKE_brush_tag_unsaved_changes(reinterpret_cast<Brush *>(id));
break;
default:
break;
}
update_sequencer(update_ctx, bmain, id);
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,411 @@
/* SPDX-FileCopyrightText: 2008 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edrend
*/
#include <algorithm>
#include <cstddef>
#include <cstring>
#include "BLI_listbase.h"
#include "BLI_rect.h"
#include "DNA_scene_types.h"
#include "DNA_userdef_types.h"
#include "BKE_context.hh"
#include "BKE_global.hh"
#include "BKE_image.hh"
#include "BKE_report.hh"
#include "BKE_scene.hh"
#include "BKE_screen.hh"
#include "BLT_translation.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "ED_screen.hh"
#include "wm_window.hh"
#include "render_intern.hh"
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Utilities for Finding Areas
* \{ */
/**
* Returns biggest area that is not uv/image editor. Note that it uses buttons
* window as the last possible alternative.
* would use #BKE_screen_find_big_area(...) but this is too specific.
*/
static ScrArea *biggest_non_image_area(bContext *C)
{
bScreen *screen = CTX_wm_screen(C);
ScrArea *big = nullptr;
int size, maxsize = 0, bwmaxsize = 0;
short foundwin = 0;
for (ScrArea &area : screen->areabase) {
if (area.winx > 30 && area.winy > 30) {
size = area.winx * area.winy;
if (!area.full && area.spacetype == SPACE_PROPERTIES) {
if (foundwin == 0 && size > bwmaxsize) {
bwmaxsize = size;
big = &area;
}
}
else if (area.spacetype != SPACE_IMAGE && size > maxsize) {
maxsize = size;
big = &area;
foundwin = 1;
}
}
}
return big;
}
static ScrArea *find_area_showing_render_result(bContext *C, Scene *scene, wmWindow **r_win)
{
wmWindowManager *wm = CTX_wm_manager(C);
ScrArea *area_render = nullptr;
wmWindow *win_render = nullptr;
/* find an image-window showing render result */
for (wmWindow &win : wm->windows) {
if (WM_window_get_active_scene(&win) != scene) {
continue;
}
const bScreen *screen = WM_window_get_active_screen(&win);
for (ScrArea &area : screen->areabase) {
if (area.spacetype == SPACE_IMAGE) {
SpaceImage *sima = static_cast<SpaceImage *>(area.spacedata.first);
if (sima->image && sima->image->type == IMA_TYPE_R_RESULT) {
area_render = &area;
win_render = &win;
break;
}
}
}
if (area_render) {
break;
}
}
*r_win = win_render;
return area_render;
}
static ScrArea *find_area_image_empty(bContext *C)
{
bScreen *screen = CTX_wm_screen(C);
ScrArea *area;
SpaceImage *sima;
/* find an image-window showing render result */
for (area = static_cast<ScrArea *>(screen->areabase.first); area; area = area->next) {
if (area->spacetype == SPACE_IMAGE) {
sima = static_cast<SpaceImage *>(area->spacedata.first);
if ((sima->mode == SI_MODE_VIEW) && !sima->image) {
break;
}
}
}
return area;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Open Image Editor for Render
* \{ */
ScrArea *render_view_open(bContext *C, int mx, int my, ReportList *reports)
{
Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
ScrArea *area = nullptr;
SpaceImage *sima;
bool area_was_image = false;
if (U.render_display_type == USER_RENDER_DISPLAY_NONE) {
return nullptr;
}
if (U.render_display_type == USER_RENDER_DISPLAY_WINDOW) {
int sizex, sizey;
BKE_render_resolution(&scene->r, false, &sizex, &sizey);
sizex += 30 * UI_SCALE_FAC;
sizey += 60 * UI_SCALE_FAC;
/* arbitrary... miniature image window views don't make much sense */
sizex = std::max(sizex, 320);
sizey = std::max(sizey, 256);
WM_window_dpi_set_userdef(CTX_wm_window(C));
rctf *stored_bounds = &U.stored_bounds.image;
const bool bounds_valid = (stored_bounds && (BLI_rctf_size_x(stored_bounds) > 150.0f) &&
(BLI_rctf_size_y(stored_bounds) > 100.0f));
const bool mm_placement = WM_capabilities_flag() & WM_CAPABILITY_MULTIMONITOR_PLACEMENT;
if (bounds_valid && mm_placement) {
mx = int(stored_bounds->xmin * UI_SCALE_FAC);
my = int(stored_bounds->ymin * UI_SCALE_FAC);
}
const rcti window_rect = {
/*xmin*/ mx,
/*xmax*/ mx + sizex,
/*ymin*/ my,
/*ymax*/ my + sizey,
};
/* changes context! */
if (WM_window_open(C,
IFACE_("Blender Render"),
&window_rect,
SPACE_IMAGE,
true,
false,
true,
WIN_ALIGN_ABSOLUTE,
nullptr,
nullptr) == nullptr)
{
BKE_report(reports, RPT_ERROR, "Failed to open window!");
return nullptr;
}
area = CTX_wm_area(C);
if (area->spacedata.is_single() == false) {
sima = static_cast<SpaceImage *>(area->spacedata.first);
sima->flag |= SI_PREVSPACE;
}
}
else if (U.render_display_type == USER_RENDER_DISPLAY_SCREEN) {
area = CTX_wm_area(C);
/* If the active screen is already in full-screen mode, skip this and
* unset the area, so that the full-screen area is just changed later. */
if (area && area->full) {
area = nullptr;
}
else {
if (area && area->spacetype == SPACE_IMAGE) {
area_was_image = true;
}
/* this function returns with changed context */
area = ED_screen_full_newspace(C, area, SPACE_IMAGE);
}
}
if (!area) {
wmWindow *win_show = nullptr;
area = find_area_showing_render_result(C, scene, &win_show);
if (area == nullptr) {
/* No need to set `win_show` as the area selected will be from the active window. */
area = find_area_image_empty(C);
}
/* if area found in other window, we make that one show in front */
if (win_show && win_show != CTX_wm_window(C)) {
wm_window_raise(win_show);
}
if (area == nullptr) {
/* find largest open non-image area */
area = biggest_non_image_area(C);
if (area) {
ED_area_newspace(C, area, SPACE_IMAGE, true);
sima = static_cast<SpaceImage *>(area->spacedata.first);
/* Makes "Escape" go back to previous space. */
sima->flag |= SI_PREVSPACE;
/* We already had a full-screen here -> mark new space as a stacked full-screen. */
if (area->full) {
area->flag |= AREA_FLAG_STACKED_FULLSCREEN;
}
}
else {
/* use any area of decent size */
area = BKE_screen_find_big_area(CTX_wm_screen(C), SPACE_TYPE_ANY, 0);
if (area->spacetype != SPACE_IMAGE) {
// XXX newspace(area, SPACE_IMAGE);
sima = static_cast<SpaceImage *>(area->spacedata.first);
/* Makes "Escape" go back to previous space. */
sima->flag |= SI_PREVSPACE;
}
}
}
}
sima = static_cast<SpaceImage *>(area->spacedata.first);
sima->link_flag |= SPACE_FLAG_TYPE_TEMPORARY;
/* get the correct image, and scale it */
sima->image = BKE_image_ensure_viewer(bmain, IMA_TYPE_R_RESULT, "Render Result");
/* If we're rendering to full screen, set appropriate hints on image editor
* so it can restore properly on pressing escape. */
if (area->full) {
sima->flag |= SI_FULLWINDOW;
/* Tell the image editor to revert to previous space in space list on close
* _only_ if it wasn't already an image editor when the render was invoked */
if (area_was_image == 0) {
sima->flag |= SI_PREVSPACE;
}
else {
/* Leave it alone so the image editor will just go back from
* full screen to the original tiled setup */
}
}
if ((sima->flag & SI_PREVSPACE) && sima->next) {
SpaceLink *old_sl = sima->next;
old_sl->link_flag |= SPACE_FLAG_TYPE_WAS_ACTIVE;
}
return area;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Cancel Render Viewer Operator
* \{ */
static wmOperatorStatus render_view_cancel_exec(bContext *C, wmOperator * /*op*/)
{
wmWindow *win = CTX_wm_window(C);
ScrArea *area = CTX_wm_area(C);
SpaceImage *sima = static_cast<SpaceImage *>(area->spacedata.first);
/* ensure image editor full-screen and area full-screen states are in sync */
if ((sima->flag & SI_FULLWINDOW) && !area->full) {
sima->flag &= ~SI_FULLWINDOW;
}
/* determine if render already shows */
if (sima->flag & SI_PREVSPACE) {
sima->flag &= ~SI_PREVSPACE;
if (sima->flag & SI_FULLWINDOW) {
sima->flag &= ~SI_FULLWINDOW;
ED_screen_full_prevspace(C, area);
}
else {
ED_area_prevspace(C, area);
}
return OPERATOR_FINISHED;
}
if (sima->flag & SI_FULLWINDOW) {
sima->flag &= ~SI_FULLWINDOW;
ED_screen_state_toggle(C, win, area, SCREENMAXIMIZED);
return OPERATOR_FINISHED;
}
if (WM_window_is_temp_screen(win)) {
wm_window_close_request(C, CTX_wm_manager(C), win);
return OPERATOR_FINISHED;
}
return OPERATOR_PASS_THROUGH;
}
void RENDER_OT_view_cancel(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Cancel Render View";
ot->description = "Cancel showing the render view";
ot->idname = "RENDER_OT_view_cancel";
/* API callbacks. */
ot->exec = render_view_cancel_exec;
ot->poll = ED_operator_image_active;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Show Render Viewer Operator
* \{ */
static wmOperatorStatus render_view_show_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
wmWindow *wincur = CTX_wm_window(C);
/* test if we have currently a temp screen active */
if (WM_window_is_temp_screen(wincur)) {
wm_window_lower(wincur);
}
else {
wmWindow *win_show = nullptr;
ScrArea *area = find_area_showing_render_result(C, CTX_data_scene(C), &win_show);
/* is there another window on current scene showing result? */
for (wmWindow &win : CTX_wm_manager(C)->windows) {
const bScreen *screen = WM_window_get_active_screen(&win);
if ((WM_window_is_temp_screen(&win) &&
(static_cast<ScrArea *>(screen->areabase.first))->spacetype == SPACE_IMAGE) ||
(&win == win_show && win_show != wincur))
{
wm_window_raise(&win);
return OPERATOR_FINISHED;
}
}
/* determine if render already shows */
if (area) {
/* but don't close it when rendering */
if (G.is_rendering == false) {
SpaceImage *sima = static_cast<SpaceImage *>(area->spacedata.first);
if (sima->flag & SI_PREVSPACE) {
sima->flag &= ~SI_PREVSPACE;
if (sima->flag & SI_FULLWINDOW) {
sima->flag &= ~SI_FULLWINDOW;
ED_screen_full_prevspace(C, area);
}
else {
ED_area_prevspace(C, area);
}
}
}
}
else {
render_view_open(C, event->xy[0], event->xy[1], op->reports);
}
}
return OPERATOR_FINISHED;
}
void RENDER_OT_view_show(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Show/Hide Render View";
ot->description = "Toggle show render view";
ot->idname = "RENDER_OT_view_show";
/* API callbacks. */
ot->invoke = render_view_show_invoke;
ot->poll = ED_operator_screenactive;
}
/** \} */
} // namespace blender