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,137 @@
# SPDX-FileCopyrightText: 2023 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later
set(INC
../include
../space_sequencer
../../makesrna
../../nodes
# RNA_prototypes.hh
${CMAKE_BINARY_DIR}/source/blender/makesrna
)
set(INC_SYS
)
set(SRC
ed_draw.cc
ed_transverts.cc
ed_util.cc
ed_util_imbuf.cc
ed_util_ops.cc
ed_viewer_path.cc
gizmo_utils.cc
numinput.cc
select_utils.cc
# general includes
../include/BIF_glutil.hh
../include/ED_anim_api.hh
../include/ED_anim_transformable.hh
../include/ED_armature.hh
../include/ED_asset.hh
../include/ED_asset_menu_utils.hh
../include/ED_buttons.hh
../include/ED_clip.hh
../include/ED_curve.hh
../include/ED_curves.hh
../include/ED_curves_sculpt.hh
../include/ED_datafiles.h
../include/ED_file_indexer.hh
../include/ED_fileselect.hh
../include/ED_geometry.hh
../include/ED_gizmo_library.hh
../include/ED_gizmo_utils.hh
../include/ED_gpencil_legacy.hh
../include/ED_grease_pencil.hh
../include/ED_id_management.hh
../include/ED_image.hh
../include/ED_info.hh
../include/ED_keyframes_draw.hh
../include/ED_keyframes_edit.hh
../include/ED_keyframes_keylist.hh
../include/ED_keyframing.hh
../include/ED_lattice.hh
../include/ED_markers.hh
../include/ED_mask.hh
../include/ED_mball.hh
../include/ED_mesh.hh
../include/ED_node.hh
../include/ED_node_c.hh
../include/ED_node_preview.hh
../include/ED_numinput.hh
../include/ED_object.hh
../include/ED_object_vgroup.hh
../include/ED_outliner.hh
../include/ED_paint.hh
../include/ED_particle.hh
../include/ED_physics.hh
../include/ED_pointcloud.hh
../include/ED_render.hh
../include/ED_scene.hh
../include/ED_screen.hh
../include/ED_screen_types.hh
../include/ED_sculpt.hh
../include/ED_select_utils.hh
../include/ED_sequencer.hh
../include/ED_sound.hh
../include/ED_space_api.hh
../include/ED_spreadsheet.hh
../include/ED_text.hh
../include/ED_time_scrub_ui.hh
../include/ED_transform.hh
../include/ED_transform_snap_object_context.hh
../include/ED_transverts.hh
../include/ED_undo.hh
../include/ED_userpref.hh
../include/ED_util.hh
../include/ED_util_imbuf.hh
../include/ED_uvedit.hh
../include/ED_view3d.hh
../include/ED_view3d_offscreen.hh
../include/ED_viewer_path.hh
../include/UI_abstract_view.hh
../include/UI_grid_view.hh
../include/UI_icons.hh
../include/UI_interface.hh
../include/UI_interface_c.hh
../include/UI_interface_icons.hh
../include/UI_interface_layout.hh
../include/UI_interface_types.hh
../include/UI_resources.hh
../include/UI_string_search.hh
../include/UI_tree_view.hh
../include/UI_view2d.hh
)
set(LIB
PRIVATE bf::animrig
PRIVATE bf::asset_system
PRIVATE bf::blenfont
PRIVATE bf::blenkernel
PRIVATE bf::blenlib
PRIVATE bf::blentranslation
PRIVATE bf::bmesh
PRIVATE bf::depsgraph
PRIVATE bf::dna
PRIVATE bf::gpu
PRIVATE bf::imbuf
PRIVATE bf::intern::clog
PRIVATE bf::intern::guardedalloc
PRIVATE bf::sequencer
PRIVATE bf::windowmanager
)
if(WITH_PYTHON)
add_definitions(-DWITH_PYTHON)
list(APPEND INC
../../python
)
endif()
blender_add_lib(bf_editor_util "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
# RNA_prototypes.hh
add_dependencies(bf_editor_util bf_rna)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,584 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edutil
*/
#include "DNA_mesh_types.h"
#include "MEM_guardedalloc.h"
#include "DNA_armature_types.h"
#include "DNA_curve_types.h"
#include "DNA_curves_types.h"
#include "DNA_lattice_types.h"
#include "DNA_meta_types.h"
#include "DNA_object_types.h"
#include "DNA_pointcloud_types.h"
#include "DNA_scene_types.h"
#include "BLI_listbase.h"
#include "BLI_utildefines.h"
#include "BKE_armature.hh"
#include "BKE_context.hh"
#include "BKE_curve.hh"
#include "BKE_editmesh.hh"
#include "BKE_lattice.hh"
#include "BKE_mesh_iterators.hh"
#include "BKE_mesh_types.hh"
#include "BKE_object.hh"
#include "DEG_depsgraph.hh"
#include "DEG_depsgraph_query.hh"
#include "ED_armature.hh"
#include "ED_curves.hh"
#include "ED_pointcloud.hh"
#include "ANIM_armature.hh"
#include "ED_transverts.hh" /* own include */
namespace blender {
void ED_transverts_update_obedit(TransVertStore *tvs, Object *obedit)
{
/* NOTE: copied from `editobject.c`, now uses (almost) proper depsgraph. */
const int mode = tvs->mode;
BLI_assert(ED_transverts_check_obedit(obedit) == true);
DEG_id_tag_update(obedit->data, ID_RECALC_GEOMETRY);
if (obedit->type == OB_MESH) {
BMEditMesh *em = BKE_editmesh_from_object(obedit);
BM_mesh_normals_update(em->bm);
}
else if (ELEM(obedit->type, OB_CURVES_LEGACY, OB_SURF)) {
Curve *cu = id_cast<Curve *>(obedit->data);
ListBaseT<Nurb> *nurbs = BKE_curve_editNurbs_get(cu);
Nurb *nu = static_cast<Nurb *>(nurbs->first);
/* #ED_transverts_create_from_obedit fills a single contiguous array spanning all nurbs,
* so advance over every nurb's verts instead of resetting per nurb. */
TransVert *tv = tvs->transverts;
while (nu) {
/* keep handles' vectors unchanged */
if (nu->bezt && (mode & TM_SKIP_HANDLES)) {
int a = nu->pntsu;
BezTriple *bezt = nu->bezt;
while (a--) {
if (bezt->hide == 0) {
bool skip_handle = false;
if (bezt->f2 & SELECT) {
skip_handle = (mode & TM_SKIP_HANDLES) != 0;
}
if ((bezt->f1 & SELECT) && !skip_handle) {
BLI_assert(tv->loc == bezt->vec[0]);
tv++;
}
if (bezt->f2 & SELECT) {
float v[3];
if (((bezt->f1 & SELECT) && !skip_handle) == 0) {
sub_v3_v3v3(v, tv->loc, tv->oldloc);
add_v3_v3(bezt->vec[0], v);
}
if (((bezt->f3 & SELECT) && !skip_handle) == 0) {
sub_v3_v3v3(v, tv->loc, tv->oldloc);
add_v3_v3(bezt->vec[2], v);
}
BLI_assert(tv->loc == bezt->vec[1]);
tv++;
}
if ((bezt->f3 & SELECT) && !skip_handle) {
BLI_assert(tv->loc == bezt->vec[2]);
tv++;
}
}
bezt++;
}
}
else if (nu->bp && (mode & TM_SKIP_HANDLES)) {
/* No handles to keep unchanged, but the `tv` array must be kept in sync. */
int a = nu->pntsu * nu->pntsv;
BPoint *bp = nu->bp;
while (a--) {
if ((bp->hide == 0) && (bp->f1 & SELECT)) {
tv++;
}
bp++;
}
}
if (CU_IS_2D(cu)) {
BKE_nurb_project_2d(nu);
}
BKE_nurb_handles_test(nu, NURB_HANDLE_TEST_EACH, false); /* test for bezier too */
nu = nu->next;
}
}
else if (obedit->type == OB_ARMATURE) {
bArmature *arm = id_cast<bArmature *>(obedit->data);
TransVert *tv = tvs->transverts;
int a = 0;
/* Ensure all bone tails are correctly adjusted */
for (EditBone &ebo : *arm->edbo) {
if (!animrig::bone_is_visible(arm, &ebo)) {
continue;
}
/* adjust tip if both ends selected */
if ((ebo.flag & BONE_ROOTSEL) && (ebo.flag & BONE_TIPSEL)) {
if (tv) {
float diffvec[3];
sub_v3_v3v3(diffvec, tv->loc, tv->oldloc);
add_v3_v3(ebo.tail, diffvec);
a++;
if (a < tvs->transverts_tot) {
tv++;
}
}
}
}
/* Ensure all bones are correctly adjusted */
for (EditBone &ebo : *arm->edbo) {
if ((ebo.flag & BONE_CONNECTED) && ebo.parent) {
/* If this bone has a parent tip that has been moved */
if (animrig::bone_is_visible(arm, ebo.parent) && (ebo.parent->flag & BONE_TIPSEL)) {
copy_v3_v3(ebo.head, ebo.parent->tail);
}
/* If this bone has a parent tip that has NOT been moved */
else {
copy_v3_v3(ebo.parent->tail, ebo.head);
}
}
}
if (arm->flag & ARM_MIRROR_EDIT) {
ED_armature_edit_transform_mirror_update(obedit);
}
}
else if (obedit->type == OB_LATTICE) {
Lattice *lt = id_cast<Lattice *>(obedit->data);
if (lt->editlatt->latt->flag & LT_OUTSIDE) {
outside_lattice(lt->editlatt->latt);
}
}
else if (obedit->type == OB_CURVES) {
Curves *curves_id = id_cast<Curves *>(obedit->data);
ed::curves::transverts_update_curves(
curves_id->geometry.wrap(), tvs, (mode & TM_SKIP_HANDLES) != 0);
}
else if (obedit->type == OB_POINTCLOUD) {
PointCloud *pointcloud = id_cast<PointCloud *>(obedit->data);
pointcloud->tag_positions_changed();
}
}
static void set_mapped_co(void *vuserdata, int index, const float co[3], const float /*no*/[3])
{
void **userdata = static_cast<void **>(vuserdata);
BMEditMesh *em = static_cast<BMEditMesh *>(userdata[0]);
TransVert *tv = static_cast<TransVert *>(userdata[1]);
BMVert *eve = BM_vert_at_index(em->bm, index);
if (BM_elem_index_get(eve) != TM_INDEX_SKIP) {
tv = &tv[BM_elem_index_get(eve)];
/* Be clever, get the closest vertex to the original,
* behaves most logically when the mirror modifier is used for eg #33051. */
if ((tv->flag & TX_VERT_USE_MAPLOC) == 0) {
/* first time */
copy_v3_v3(tv->maploc, co);
tv->flag |= TX_VERT_USE_MAPLOC;
}
else {
/* find best location to use */
if (len_squared_v3v3(eve->co, co) < len_squared_v3v3(eve->co, tv->maploc)) {
copy_v3_v3(tv->maploc, co);
}
}
}
}
bool ED_transverts_check_obedit(const Object *obedit)
{
return ELEM(obedit->type,
OB_ARMATURE,
OB_LATTICE,
OB_MESH,
OB_SURF,
OB_CURVES_LEGACY,
OB_MBALL,
OB_CURVES,
OB_POINTCLOUD);
}
void ED_transverts_create_from_obedit(TransVertStore *tvs, const Object *obedit, const int mode)
{
BLI_assert(DEG_is_evaluated(obedit));
Nurb *nu;
BezTriple *bezt;
BPoint *bp;
TransVert *tv = nullptr;
MetaElem *ml;
BMVert *eve;
int a;
tvs->transverts_tot = 0;
if (obedit->type == OB_MESH) {
const Object *object_orig = DEG_get_original(obedit);
const Mesh &mesh = *id_cast<Mesh *>(object_orig->data);
BMEditMesh *em = mesh.runtime->edit_mesh.get();
BMesh *bm = em->bm;
BMIter iter;
void *userdata[2] = {em, nullptr};
// int proptrans = 0; /*UNUSED*/
/* abuses vertex index all over, set, just set dirty here,
* perhaps this could use its own array instead? - campbell */
/* transform now requires awareness for select mode, so we tag the f1 flags in verts */
tvs->transverts_tot = 0;
if (em->selectmode & SCE_SELECT_VERTEX) {
BM_ITER_MESH (eve, &iter, bm, BM_VERTS_OF_MESH) {
if (!BM_elem_flag_test(eve, BM_ELEM_HIDDEN) && BM_elem_flag_test(eve, BM_ELEM_SELECT)) {
BM_elem_index_set(eve, TM_INDEX_ON); /* set_dirty! */
tvs->transverts_tot++;
}
else {
BM_elem_index_set(eve, TM_INDEX_OFF); /* set_dirty! */
}
}
}
else if (em->selectmode & SCE_SELECT_EDGE) {
BMEdge *eed;
BM_ITER_MESH (eve, &iter, bm, BM_VERTS_OF_MESH) {
BM_elem_index_set(eve, TM_INDEX_OFF); /* set_dirty! */
}
BM_ITER_MESH (eed, &iter, bm, BM_EDGES_OF_MESH) {
if (!BM_elem_flag_test(eed, BM_ELEM_HIDDEN) && BM_elem_flag_test(eed, BM_ELEM_SELECT)) {
BM_elem_index_set(eed->v1, TM_INDEX_ON); /* set_dirty! */
BM_elem_index_set(eed->v2, TM_INDEX_ON); /* set_dirty! */
}
}
BM_ITER_MESH (eve, &iter, bm, BM_VERTS_OF_MESH) {
if (BM_elem_index_get(eve) == TM_INDEX_ON) {
tvs->transverts_tot++;
}
}
}
else {
BMFace *efa;
BM_ITER_MESH (eve, &iter, bm, BM_VERTS_OF_MESH) {
BM_elem_index_set(eve, TM_INDEX_OFF); /* set_dirty! */
}
BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
if (!BM_elem_flag_test(efa, BM_ELEM_HIDDEN) && BM_elem_flag_test(efa, BM_ELEM_SELECT)) {
BMIter liter;
BMLoop *l;
BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
BM_elem_index_set(l->v, TM_INDEX_ON); /* set_dirty! */
}
}
}
BM_ITER_MESH (eve, &iter, bm, BM_VERTS_OF_MESH) {
if (BM_elem_index_get(eve) == TM_INDEX_ON) {
tvs->transverts_tot++;
}
}
}
/* for any of the 3 loops above which all dirty the indices */
bm->elem_index_dirty |= BM_VERT;
/* and now make transverts */
if (tvs->transverts_tot) {
tv = tvs->transverts = MEM_new_array_zeroed<TransVert>(tvs->transverts_tot, __func__);
a = 0;
BM_ITER_MESH (eve, &iter, bm, BM_VERTS_OF_MESH) {
if (BM_elem_index_get(eve)) {
BM_elem_index_set(eve, a); /* set_dirty! */
copy_v3_v3(tv->oldloc, eve->co);
tv->loc = eve->co;
tv->flag = (BM_elem_index_get(eve) == TM_INDEX_ON) ? SELECT : 0;
if (mode & TM_CALC_NORMALS) {
tv->flag |= TX_VERT_USE_NORMAL;
copy_v3_v3(tv->normal, eve->no);
}
tv++;
a++;
}
else {
BM_elem_index_set(eve, TM_INDEX_SKIP); /* set_dirty! */
}
}
/* set dirty already, above */
userdata[1] = tvs->transverts;
}
if (mode & TM_CALC_MAPLOC) {
const Mesh *editmesh_eval_cage = BKE_object_get_editmesh_eval_cage(obedit);
if (tvs->transverts && editmesh_eval_cage) {
BM_mesh_elem_table_ensure(bm, BM_VERT);
BKE_mesh_foreach_mapped_vert(
editmesh_eval_cage, set_mapped_co, userdata, MESH_FOREACH_NOP);
}
}
}
else if (obedit->type == OB_ARMATURE) {
bArmature *arm = id_cast<bArmature *>(obedit->data);
int totmalloc = arm->edbo->count();
totmalloc *= 2; /* probably overkill but bones can have 2 trans verts each */
tv = tvs->transverts = MEM_new_array_zeroed<TransVert>(totmalloc, __func__);
for (EditBone &ebo : *arm->edbo) {
if (animrig::bone_is_visible(arm, &ebo)) {
const bool tipsel = (ebo.flag & BONE_TIPSEL) != 0;
const bool rootsel = (ebo.flag & BONE_ROOTSEL) != 0;
const bool rootok = !(
ebo.parent && (ebo.flag & BONE_CONNECTED) &&
(animrig::bone_is_visible(arm, ebo.parent) && (ebo.parent->flag & BONE_TIPSEL)));
if ((tipsel && rootsel) || (rootsel)) {
/* Don't add the tip (unless mode & TM_ALL_JOINTS, for getting all joints),
* otherwise we get zero-length bones as tips will snap to the same
* location as heads.
*/
if (rootok) {
copy_v3_v3(tv->oldloc, ebo.head);
tv->loc = ebo.head;
tv->flag = SELECT;
tv++;
tvs->transverts_tot++;
}
if ((mode & TM_ALL_JOINTS) && (tipsel)) {
copy_v3_v3(tv->oldloc, ebo.tail);
tv->loc = ebo.tail;
tv->flag = SELECT;
tv++;
tvs->transverts_tot++;
}
}
else if (tipsel) {
copy_v3_v3(tv->oldloc, ebo.tail);
tv->loc = ebo.tail;
tv->flag = SELECT;
tv++;
tvs->transverts_tot++;
}
}
}
}
else if (ELEM(obedit->type, OB_CURVES_LEGACY, OB_SURF)) {
Curve *cu = id_cast<Curve *>(obedit->data);
int totmalloc = 0;
ListBaseT<Nurb> *nurbs = BKE_curve_editNurbs_get(cu);
for (Nurb &nu : *nurbs) {
if (nu.type == CU_BEZIER) {
totmalloc += 3 * nu.pntsu;
}
else {
totmalloc += nu.pntsu * nu.pntsv;
}
}
tv = tvs->transverts = MEM_new_array_zeroed<TransVert>(totmalloc, __func__);
nu = static_cast<Nurb *>(nurbs->first);
while (nu) {
if (nu->type == CU_BEZIER) {
a = nu->pntsu;
bezt = nu->bezt;
while (a--) {
if (bezt->hide == 0) {
bool skip_handle = false;
if (bezt->f2 & SELECT) {
skip_handle = (mode & TM_SKIP_HANDLES) != 0;
}
if ((bezt->f1 & SELECT) && !skip_handle) {
copy_v3_v3(tv->oldloc, bezt->vec[0]);
tv->loc = bezt->vec[0];
tv->flag = bezt->f1 & SELECT;
if (mode & TM_CALC_NORMALS) {
tv->flag |= TX_VERT_USE_NORMAL;
BKE_nurb_bezt_calc_plane(nu, bezt, tv->normal);
}
tv++;
tvs->transverts_tot++;
}
if (bezt->f2 & SELECT) {
copy_v3_v3(tv->oldloc, bezt->vec[1]);
tv->loc = bezt->vec[1];
tv->flag = bezt->f2 & SELECT;
if (mode & TM_CALC_NORMALS) {
tv->flag |= TX_VERT_USE_NORMAL;
BKE_nurb_bezt_calc_plane(nu, bezt, tv->normal);
}
tv++;
tvs->transverts_tot++;
}
if ((bezt->f3 & SELECT) && !skip_handle) {
copy_v3_v3(tv->oldloc, bezt->vec[2]);
tv->loc = bezt->vec[2];
tv->flag = bezt->f3 & SELECT;
if (mode & TM_CALC_NORMALS) {
tv->flag |= TX_VERT_USE_NORMAL;
BKE_nurb_bezt_calc_plane(nu, bezt, tv->normal);
}
tv++;
tvs->transverts_tot++;
}
}
bezt++;
}
}
else {
a = nu->pntsu * nu->pntsv;
bp = nu->bp;
while (a--) {
if (bp->hide == 0) {
if (bp->f1 & SELECT) {
copy_v3_v3(tv->oldloc, bp->vec);
tv->loc = bp->vec;
tv->flag = bp->f1 & SELECT;
tv++;
tvs->transverts_tot++;
}
}
bp++;
}
}
nu = nu->next;
}
}
else if (obedit->type == OB_MBALL) {
MetaBall *mb = id_cast<MetaBall *>(obedit->data);
int totmalloc = mb->editelems->count();
tv = tvs->transverts = MEM_new_array_zeroed<TransVert>(totmalloc, __func__);
ml = static_cast<MetaElem *>(mb->editelems->first);
while (ml) {
if (ml->flag & SELECT) {
tv->loc = &ml->x;
copy_v3_v3(tv->oldloc, tv->loc);
tv->flag = SELECT;
tv++;
tvs->transverts_tot++;
}
ml = ml->next;
}
}
else if (obedit->type == OB_LATTICE) {
const Object *object_orig = DEG_get_original(obedit);
Lattice *lt = id_cast<Lattice *>(object_orig->data);
bp = lt->editlatt->latt->def;
a = lt->editlatt->latt->pntsu * lt->editlatt->latt->pntsv * lt->editlatt->latt->pntsw;
tv = tvs->transverts = MEM_new_array_zeroed<TransVert>(a, __func__);
while (a--) {
if (bp->f1 & SELECT) {
if (bp->hide == 0) {
copy_v3_v3(tv->oldloc, bp->vec);
tv->loc = bp->vec;
tv->flag = bp->f1 & SELECT;
tv++;
tvs->transverts_tot++;
}
}
bp++;
}
}
else if (obedit->type == OB_CURVES) {
Curves *curves_id = id_cast<Curves *>(obedit->data);
ed::curves::transverts_from_curves_positions_create(
curves_id->geometry.wrap(), tvs, ((mode & TM_SKIP_HANDLES) != 0));
}
else if (obedit->type == OB_POINTCLOUD) {
PointCloud *pointcloud = id_cast<PointCloud *>(obedit->data);
IndexMaskMemory memory;
const IndexMask selection = ed::pointcloud::retrieve_selected_points(*pointcloud, memory);
MutableSpan<float3> positions = pointcloud->positions_for_write();
tvs->transverts = MEM_new_array_zeroed<TransVert>(selection.size(), __func__);
tvs->transverts_tot = selection.size();
selection.foreach_index(
[&](const int64_t i, const int64_t pos) {
TransVert &tv = tvs->transverts[pos];
tv.loc = positions[i];
tv.flag = SELECT;
copy_v3_v3(tv.oldloc, tv.loc);
},
exec_mode::grain_size(1024));
}
if (!tvs->transverts_tot && tvs->transverts) {
/* Prevent memory leak. happens for curves/lattices due to
* difficult condition of adding points to trans data. */
MEM_delete(tvs->transverts);
tvs->transverts = nullptr;
}
tvs->mode = mode;
}
void ED_transverts_free(TransVertStore *tvs)
{
MEM_SAFE_DELETE(tvs->transverts);
tvs->transverts_tot = 0;
}
bool ED_transverts_poll(bContext *C)
{
Object *obedit = CTX_data_edit_object(C);
if (obedit) {
if (ED_transverts_check_obedit(obedit)) {
return true;
}
}
return false;
}
} // namespace blender

View File

@@ -0,0 +1,475 @@
/* SPDX-FileCopyrightText: 2008 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edutil
*/
#include <cmath>
#include <cstdlib>
#include <cstring>
#include "BLI_listbase.h"
#include "BLI_path_utils.hh"
#include "BLI_string_utf8.h"
#include "BLT_translation.hh"
#include "BKE_collection.hh"
#include "BKE_global.hh"
#include "BKE_layer.hh"
#include "BKE_lib_id.hh"
#include "BKE_lib_remap.hh"
#include "BKE_main.hh"
#include "BKE_material.hh"
#include "BKE_multires.hh"
#include "BKE_object.hh"
#include "BKE_object_types.hh"
#include "BKE_packedFile.hh"
#include "BKE_paint.hh"
#include "BKE_scene.hh"
#include "BKE_screen.hh"
#include "BKE_undo_system.hh"
#include "DEG_depsgraph.hh"
#include "ED_armature.hh"
#include "ED_asset.hh"
#include "ED_gpencil_legacy.hh"
#include "ED_image.hh"
#include "ED_mesh.hh"
#include "ED_object.hh"
#include "ED_paint.hh"
#include "ED_screen.hh"
#include "ED_screen_types.hh"
#include "ED_sculpt.hh"
#include "ED_space_api.hh"
#include "ED_util.hh"
#include "ED_view3d.hh"
#include "UI_interface.hh"
#include "UI_interface_layout.hh"
#include "UI_resources.hh"
#include "RNA_access.hh"
#include "WM_api.hh"
#include "WM_types.hh"
namespace blender {
/* ********* general editor util functions, not BKE stuff please! ********* */
void ED_editors_init_for_undo(Main *bmain)
{
wmWindowManager *wm = static_cast<wmWindowManager *>(bmain->wm.first);
for (wmWindow &win : wm->windows) {
Scene *scene = WM_window_get_active_scene(&win);
ViewLayer *view_layer = WM_window_get_active_view_layer(&win);
BKE_view_layer_synced_ensure(*bmain, scene, view_layer);
Object *ob = BKE_view_layer_active_object_get(view_layer);
if (ob && (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);
}
/* Stop animation from playing.
* TODO: There might be a way to keep the animation from playing, but sad->scene and
* sad->view_layer pointers are outdated and would need to be updated somehow. */
bScreen *animscreen = ED_screen_animation_playing(wm);
if (animscreen && animscreen->animtimer) {
WM_event_timer_remove(wm, &win, animscreen->animtimer);
animscreen->animtimer = nullptr;
}
/* UI Updates. */
/* Flag local View3D's to check and exit if they are empty. */
for (bScreen &screen : bmain->screens) {
for (ScrArea &area : screen.areabase) {
for (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;
}
}
}
}
}
}
}
void ED_editors_init(bContext *C)
{
using namespace blender::ed;
Depsgraph *depsgraph = CTX_data_expect_evaluated_depsgraph(C);
Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
wmWindowManager *wm = CTX_wm_manager(C);
/* This is called during initialization, so we don't want to store any reports */
ReportList *reports = CTX_wm_reports(C);
int reports_flag_prev = reports->flag & ~RPT_STORE;
std::swap(reports->flag, reports_flag_prev);
/* Don't do undo pushes when calling an operator. */
wm->op_undo_depth++;
/* toggle on modes for objects that were saved with these enabled. for
* e.g. linked objects we have to ensure that they are actually the
* active object in this scene. */
Object *obact = CTX_data_active_object(C);
for (Object &ob : bmain->objects) {
eObjectMode mode = ob.mode;
if (mode == OB_MODE_OBJECT) {
continue;
}
if (BKE_object_has_mode_data(&ob, mode)) {
/* For multi-edit mode we may already have mode data. */
continue;
}
/* Reset object to Object mode, so that code below can properly re-switch it to its
* previous mode if possible, re-creating its mode data, etc. */
ID *ob_data = ob.data;
ob.mode = OB_MODE_OBJECT;
DEG_id_tag_update(&ob.id, ID_RECALC_SYNC_TO_EVAL);
/* Object mode is enforced if there is no active object, or if the active object's type is
* different. */
if (obact == nullptr || ob.type != obact->type) {
continue;
}
/* Object mode is enforced for non-editable data (or their obdata). */
if (!BKE_id_is_editable(bmain, &ob.id) ||
(ob_data != nullptr && !BKE_id_is_editable(bmain, ob_data)))
{
continue;
}
/* Pose mode is very similar to Object one, we can apply it even on objects not in current
* scene. */
if (mode == OB_MODE_POSE) {
ED_object_posemode_enter_ex(bmain, &ob);
}
/* Other edit/paint/etc. modes are only settable for objects visible in active scene currently.
* Otherwise, they (and their obdata) may not be (fully) evaluated, which is mandatory for some
* modes like Sculpt.
* Ref. #98225. */
if (!BKE_collection_has_object_recursive(scene->master_collection, &ob) ||
!BKE_scene_has_object(*bmain, scene, &ob) || (ob.visibility_flag & OB_HIDE_VIEWPORT) != 0)
{
continue;
}
if (mode == OB_MODE_EDIT) {
object::editmode_enter_ex(bmain, scene, &ob, 0);
}
else if (mode & OB_MODE_ALL_SCULPT) {
if (obact == &ob) {
if (mode == OB_MODE_SCULPT) {
ed::sculpt_paint::object_sculpt_mode_enter(
*bmain, *depsgraph, *scene, ob, true, reports);
}
else if (mode == OB_MODE_VERTEX_PAINT) {
ED_object_vpaintmode_enter_ex(*bmain, *depsgraph, *scene, ob);
}
else if (mode == OB_MODE_WEIGHT_PAINT) {
ED_object_wpaintmode_enter_ex(*bmain, *depsgraph, *scene, ob);
}
else {
BLI_assert_unreachable();
}
}
else {
/* Create data for non-active objects which need it for
* mode-switching but don't yet support multi-editing. */
if (mode & OB_MODE_ALL_SCULPT) {
ob.mode = mode;
BKE_object_sculpt_data_create(&ob);
}
}
}
else {
/* TODO(@ideasman42): avoid operator calls. */
if (obact == &ob) {
object::mode_set(C, mode);
}
}
}
/* image editor paint mode */
if (scene) {
ED_space_image_paint_update(bmain, wm, scene);
}
/* Enforce a full redraw for the first time areas/regions get drawn. Further region init/refresh
* just triggers non-rebuild redraws (#RGN_DRAW_NO_REBUILD). Usually a full redraw would be
* triggered by a `NC_WM | ND_FILEREAD` notifier, but if a startup script calls an operator that
* redraws the window, notifiers are not handled before the operator runs. See #98461. */
for (wmWindow &win : wm->windows) {
const bScreen *screen = WM_window_get_active_screen(&win);
ED_screen_areas_iter (&win, screen, area) {
ED_area_tag_redraw(area);
}
}
asset::list::storage_tag_main_data_dirty();
std::swap(reports->flag, reports_flag_prev);
wm->op_undo_depth--;
}
void ED_editors_exit(Main *bmain, bool do_undo_system)
{
using namespace blender::ed;
if (!bmain) {
return;
}
/* Frees all edit-mode undo-steps. */
if (do_undo_system && G_MAIN->wm.first) {
wmWindowManager *wm = static_cast<wmWindowManager *>(G_MAIN->wm.first);
/* normally we don't check for null undo stack,
* do here since it may run in different context. */
if (wm->runtime->undo_stack) {
BKE_undosys_stack_destroy(wm->runtime->undo_stack);
wm->runtime->undo_stack = nullptr;
}
}
/* On undo, tag for update so the depsgraph doesn't use stale edit-mode data,
* this is possible when mixing edit-mode and memory-file undo.
*
* By convention, objects are not left in edit-mode - so this isn't often problem in practice,
* since exiting edit-mode will tag the objects too.
*
* However there is no guarantee the active object _never_ changes while in edit-mode.
* Python for example can do this, some callers to #object::base_activate
* don't handle modes either (doing so isn't always practical).
*
* To reproduce the problem where stale data is used, see: #84920. */
for (Object &ob : bmain->objects) {
if (object::editmode_free_ex(bmain, &ob)) {
if (do_undo_system == false) {
DEG_id_tag_update(&ob.id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
}
}
}
/* global in meshtools... */
ED_mesh_mirror_spatial_table_end(nullptr);
ED_mesh_mirror_topo_table_end(nullptr);
}
bool ED_editors_flush_edits_for_object_ex(Main *bmain,
Object *ob,
bool for_render,
bool check_needs_flush)
{
using namespace blender::ed;
bool has_edited = false;
if (ob->mode & OB_MODE_SCULPT) {
/* Don't allow flushing while in the middle of a stroke (frees data in use).
* Auto-save prevents this from happening but scripts
* may cause a flush on saving: #53986. */
if (ob->runtime->sculpt_session != nullptr && ob->runtime->sculpt_session->cache == nullptr) {
if (check_needs_flush && !ob->runtime->sculpt_session->needs_flush_to_id) {
return false;
}
ob->runtime->sculpt_session->needs_flush_to_id = false;
/* flush multires changes (for sculpt) */
multires_flush_sculpt_updates(ob);
has_edited = true;
if (for_render) {
/* flush changes from dynamic topology sculpt */
BKE_sculptsession_bm_to_me_for_render(ob);
}
else {
/* Set reorder=false so that saving the file doesn't reorder
* the BMesh's elements */
BKE_sculptsession_bm_to_me(ob);
}
}
}
else if (ob->mode & OB_MODE_EDIT) {
char *needs_flush_ptr = BKE_object_data_editmode_flush_ptr_get(ob->data);
if (needs_flush_ptr != nullptr) {
if (check_needs_flush && (*needs_flush_ptr == 0)) {
return false;
}
*needs_flush_ptr = 0;
}
/* get editmode results */
has_edited = true;
object::editmode_load(bmain, ob);
}
return has_edited;
}
bool ED_editors_flush_edits_for_object(Main *bmain, Object *ob)
{
return ED_editors_flush_edits_for_object_ex(bmain, ob, false, false);
}
bool ED_editors_flush_edits_ex(Main *bmain, bool for_render, bool check_needs_flush)
{
bool has_edited = false;
/* loop through all data to find edit mode or object mode, because during
* exiting we might not have a context for edit object and multiple sculpt
* objects can exist at the same time */
for (Object &ob : bmain->objects) {
has_edited |= ED_editors_flush_edits_for_object_ex(bmain, &ob, for_render, check_needs_flush);
}
bmain->is_memfile_undo_flush_needed = false;
return has_edited;
}
bool ED_editors_flush_edits(Main *bmain)
{
return ED_editors_flush_edits_ex(bmain, false, false);
}
/* ***** XXX: functions are using old blender names, cleanup later ***** */
void apply_keyb_grid(
bool shift, bool ctrl, float *val, float fac1, float fac2, float fac3, int invert)
{
/* fac1 is for 'nothing', fac2 for CTRL, fac3 for SHIFT */
if (invert) {
ctrl = !ctrl;
}
if (ctrl && shift) {
if (fac3 != 0.0f) {
*val = fac3 * floorf(*val / fac3 + 0.5f);
}
}
else if (ctrl) {
if (fac2 != 0.0f) {
*val = fac2 * floorf(*val / fac2 + 0.5f);
}
}
else {
if (fac1 != 0.0f) {
*val = fac1 * floorf(*val / fac1 + 0.5f);
}
}
}
void unpack_menu(bContext *C,
const char *opname,
const char *id_name,
const char *abs_name,
const char *folder,
PackedFile *pf)
{
Main *bmain = CTX_data_main(C);
char line[FILE_MAX + 100];
wmOperatorType *ot = WM_operatortype_find(opname, true);
const char *blendfile_path = BKE_main_blendfile_path(bmain);
ui::PopupMenu *pup = ui::popup_menu_begin(C, IFACE_("Unpack File"), ICON_NONE);
ui::Layout &layout = *popup_menu_layout(pup);
PointerRNA props_ptr = layout.op(
ot, IFACE_("Remove Pack"), ICON_NONE, wm::OpCallContext::ExecDefault, UI_ITEM_NONE);
RNA_enum_set(&props_ptr, "method", PF_REMOVE);
RNA_string_set(&props_ptr, "id", id_name);
if (blendfile_path[0] != '\0') {
char local_name[FILE_MAXDIR + FILE_MAX], fi[FILE_MAX];
BLI_path_split_file_part(abs_name, fi, sizeof(fi));
BLI_path_join(local_name, sizeof(local_name), "//", folder, fi);
if (!STREQ(abs_name, local_name)) {
switch (BKE_packedfile_compare_to_file(blendfile_path, local_name, pf)) {
case PF_CMP_NOFILE:
SNPRINTF_UTF8(line, IFACE_("Create %s"), local_name);
props_ptr = layout.op(ot, line, ICON_NONE, wm::OpCallContext::ExecDefault, UI_ITEM_NONE);
RNA_enum_set(&props_ptr, "method", PF_WRITE_LOCAL);
RNA_string_set(&props_ptr, "id", id_name);
break;
case PF_CMP_EQUAL:
SNPRINTF_UTF8(line, IFACE_("Use %s (identical)"), local_name);
props_ptr = layout.op(ot, line, ICON_NONE, wm::OpCallContext::ExecDefault, UI_ITEM_NONE);
RNA_enum_set(&props_ptr, "method", PF_USE_LOCAL);
RNA_string_set(&props_ptr, "id", id_name);
break;
case PF_CMP_DIFFERS:
SNPRINTF_UTF8(line, IFACE_("Use %s (differs)"), local_name);
props_ptr = layout.op(ot, line, ICON_NONE, wm::OpCallContext::ExecDefault, UI_ITEM_NONE);
RNA_enum_set(&props_ptr, "method", PF_USE_LOCAL);
RNA_string_set(&props_ptr, "id", id_name);
SNPRINTF_UTF8(line, IFACE_("Overwrite %s"), local_name);
props_ptr = layout.op(ot, line, ICON_NONE, wm::OpCallContext::ExecDefault, UI_ITEM_NONE);
RNA_enum_set(&props_ptr, "method", PF_WRITE_LOCAL);
RNA_string_set(&props_ptr, "id", id_name);
break;
}
}
}
switch (BKE_packedfile_compare_to_file(blendfile_path, abs_name, pf)) {
case PF_CMP_NOFILE:
SNPRINTF_UTF8(line, IFACE_("Create %s"), abs_name);
props_ptr = layout.op(ot, line, ICON_NONE, wm::OpCallContext::ExecDefault, UI_ITEM_NONE);
RNA_enum_set(&props_ptr, "method", PF_WRITE_ORIGINAL);
RNA_string_set(&props_ptr, "id", id_name);
break;
case PF_CMP_EQUAL:
SNPRINTF_UTF8(line, IFACE_("Use %s (identical)"), abs_name);
props_ptr = layout.op(ot, line, ICON_NONE, wm::OpCallContext::ExecDefault, UI_ITEM_NONE);
RNA_enum_set(&props_ptr, "method", PF_USE_ORIGINAL);
RNA_string_set(&props_ptr, "id", id_name);
break;
case PF_CMP_DIFFERS:
SNPRINTF_UTF8(line, IFACE_("Use %s (differs)"), abs_name);
props_ptr = layout.op(ot, line, ICON_NONE, wm::OpCallContext::ExecDefault, UI_ITEM_NONE);
RNA_enum_set(&props_ptr, "method", PF_USE_ORIGINAL);
RNA_string_set(&props_ptr, "id", id_name);
SNPRINTF_UTF8(line, IFACE_("Overwrite %s"), abs_name);
props_ptr = layout.op(ot, line, ICON_NONE, wm::OpCallContext::ExecDefault, UI_ITEM_NONE);
RNA_enum_set(&props_ptr, "method", PF_WRITE_ORIGINAL);
RNA_string_set(&props_ptr, "id", id_name);
break;
}
popup_menu_end(C, pup);
}
void ED_spacedata_id_remap(ScrArea *area, SpaceLink *sl, const bke::id::IDRemapper &mappings)
{
SpaceType *st = BKE_spacetype_from_id(sl->spacetype);
if (st && st->id_remap) {
st->id_remap(area, sl, mappings);
}
}
void ED_spacedata_id_remap_single(ScrArea *area, SpaceLink *sl, ID *old_id, ID *new_id)
{
SpaceType *st = BKE_spacetype_from_id(sl->spacetype);
if (st && st->id_remap) {
bke::id::IDRemapper mappings;
mappings.add(old_id, new_id);
st->id_remap(area, sl, mappings);
}
}
} // namespace blender

View File

@@ -0,0 +1,559 @@
/* SPDX-FileCopyrightText: 2008 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edutil
*/
#include <algorithm>
#include "MEM_guardedalloc.h"
#include "BLI_math_vector_types.hh"
#include "BLI_rect.h"
#include "BKE_colortools.hh"
#include "BKE_context.hh"
#include "BKE_image.hh"
#include "BKE_screen.hh"
#include "ED_image.hh"
#include "ED_screen.hh"
#include "ED_space_api.hh"
#include "GPU_immediate.hh"
#include "GPU_state.hh"
#include "IMB_colormanagement.hh"
#include "IMB_imbuf.hh"
#include "IMB_imbuf_types.hh"
#include "SEQ_render.hh"
#include "SEQ_sequencer.hh"
#include "UI_view2d.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "sequencer_intern.hh"
/* Own define. */
#include "ED_util_imbuf.hh"
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Image Pixel Sample Struct (Operator Custom Data)
* \{ */
struct ImageSampleInfo {
ARegionType *art;
void *draw_handle;
int x, y;
int channels;
int width, height;
int sample_size;
uchar col[4];
float colf[4];
float linearcol[4];
int z;
float zf;
uchar *colp;
const float *colfp;
int *zp;
float *zfp;
bool draw;
bool color_manage;
int use_default_view;
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Image Pixel Sample
* \{ */
static void image_sample_pixel_color_ubyte(const ImBuf *ibuf,
const int coord[2],
uchar r_col[4],
float r_col_linear[4])
{
const uchar *cp = ibuf->byte_data() + 4 * (coord[1] * ibuf->x + coord[0]);
copy_v4_v4_uchar(r_col, cp);
rgba_uchar_to_float(r_col_linear, r_col);
IMB_colormanagement_colorspace_to_scene_linear_v4(
r_col_linear, false, ibuf->byte_buffer.colorspace);
}
static void image_sample_pixel_color_float(ImBuf *ibuf, const int coord[2], float r_col[4])
{
const float *cp = ibuf->float_data() + (ibuf->channels) * (coord[1] * ibuf->x + coord[0]);
copy_v4_v4(r_col, cp);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Image Pixel Region Sample
* \{ */
static void image_sample_rect_color_ubyte(const ImBuf *ibuf,
const rcti *rect,
uchar r_col[4],
float r_col_linear[4])
{
uint col_accum_ub[4] = {0, 0, 0, 0};
zero_v4(r_col_linear);
int col_tot = 0;
int coord[2];
for (coord[0] = rect->xmin; coord[0] <= rect->xmax; coord[0]++) {
for (coord[1] = rect->ymin; coord[1] <= rect->ymax; coord[1]++) {
float col_temp_fl[4];
uchar col_temp_ub[4];
image_sample_pixel_color_ubyte(ibuf, coord, col_temp_ub, col_temp_fl);
add_v4_v4(r_col_linear, col_temp_fl);
col_accum_ub[0] += uint(col_temp_ub[0]);
col_accum_ub[1] += uint(col_temp_ub[1]);
col_accum_ub[2] += uint(col_temp_ub[2]);
col_accum_ub[3] += uint(col_temp_ub[3]);
col_tot += 1;
}
}
mul_v4_fl(r_col_linear, 1.0 / float(col_tot));
r_col[0] = std::min<uchar>(col_accum_ub[0] / col_tot, 255);
r_col[1] = std::min<uchar>(col_accum_ub[1] / col_tot, 255);
r_col[2] = std::min<uchar>(col_accum_ub[2] / col_tot, 255);
r_col[3] = std::min<uchar>(col_accum_ub[3] / col_tot, 255);
}
static void image_sample_rect_color_float(ImBuf *ibuf, const rcti *rect, float r_col[4])
{
zero_v4(r_col);
int col_tot = 0;
int coord[2];
for (coord[0] = rect->xmin; coord[0] <= rect->xmax; coord[0]++) {
for (coord[1] = rect->ymin; coord[1] <= rect->ymax; coord[1]++) {
float col_temp_fl[4];
image_sample_pixel_color_float(ibuf, coord, col_temp_fl);
add_v4_v4(r_col, col_temp_fl);
col_tot += 1;
}
}
mul_v4_fl(r_col, 1.0 / float(col_tot));
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Image Pixel Sample (Internal Utilities)
* \{ */
static void image_sample_apply(bContext *C, wmOperator *op, const wmEvent *event)
{
SpaceImage *sima = CTX_wm_space_image(C);
ARegion *region = CTX_wm_region(C);
Image *image = ED_space_image(sima);
float uv[2];
ui::view2d_region_to_view(&region->v2d, event->mval[0], event->mval[1], &uv[0], &uv[1]);
int tile = BKE_image_get_tile_from_pos(sima->image, uv, uv, nullptr);
void *lock;
ImBuf *ibuf = ED_space_image_acquire_buffer(sima, &lock, tile, true);
ImageSampleInfo *info = static_cast<ImageSampleInfo *>(op->customdata);
Scene *scene = CTX_data_scene(C);
CurveMapping *curve_mapping = scene->view_settings.curve_mapping;
if (ibuf == nullptr) {
ED_space_image_release_buffer(sima, ibuf, lock);
info->draw = false;
return;
}
const float2 offset = flag_is_set(ibuf->flags, ImBufFlags::HasDisplayWindow) ?
float2(ibuf->display_offset) :
float2(0.0f);
int x = int(uv[0] * ibuf->x), y = int(uv[1] * ibuf->y);
if (x >= offset[0] && y >= offset[1] && x < (ibuf->x + offset[0]) && y < (ibuf->y + offset[1])) {
info->width = ibuf->x;
info->height = ibuf->y;
info->x = x;
info->y = y;
info->draw = true;
info->channels = ibuf->channels;
info->colp = nullptr;
info->colfp = nullptr;
info->zp = nullptr;
info->zfp = nullptr;
info->use_default_view = (image->flag & IMA_VIEW_AS_RENDER) ? false : true;
rcti sample_rect;
sample_rect.xmin = max_ii(0, x - offset[0] - info->sample_size / 2);
sample_rect.ymin = max_ii(0, y - offset[1] - info->sample_size / 2);
sample_rect.xmax = min_ii(ibuf->x - 1, x - offset[0] + info->sample_size / 2);
sample_rect.ymax = min_ii(ibuf->y - 1, y - offset[1] + info->sample_size / 2);
if (ibuf->byte_data()) {
image_sample_rect_color_ubyte(ibuf, &sample_rect, info->col, info->linearcol);
rgba_uchar_to_float(info->colf, info->col);
info->colp = info->col;
info->colfp = info->colf;
info->color_manage = true;
}
if (ibuf->float_data()) {
image_sample_rect_color_float(ibuf, &sample_rect, info->colf);
if (ibuf->channels == 4) {
/* pass */
}
else if (ibuf->channels == 3) {
info->colf[3] = 1.0f;
}
else {
info->colf[1] = info->colf[0];
info->colf[2] = info->colf[0];
info->colf[3] = 1.0f;
}
info->colfp = info->colf;
copy_v4_v4(info->linearcol, info->colf);
info->color_manage = true;
}
if (curve_mapping && ibuf->channels == 4) {
/* we reuse this callback for set curves point operators */
if (RNA_struct_find_property(op->ptr, "point")) {
int point = RNA_enum_get(op->ptr, "point");
if (point == 1) {
BKE_curvemapping_set_black_white(curve_mapping, nullptr, info->linearcol);
}
else if (point == 0) {
BKE_curvemapping_set_black_white(curve_mapping, info->linearcol, nullptr);
}
WM_event_add_notifier(C, NC_WINDOW, nullptr);
}
}
/* XXX node curve integration. */
#if 0
{
ScrArea *area, *cur = curarea;
node_curvemap_sample(fp); /* sends global to node editor */
for (area = G.curscreen->areabase.first; area; area = area->next) {
if (area->spacetype == SPACE_NODE) {
areawinset(area->win);
scrarea_do_windraw(area);
}
}
node_curvemap_sample(nullptr); /* clears global in node editor */
curarea = cur;
}
#endif
}
else {
info->draw = false;
}
ED_space_image_release_buffer(sima, ibuf, lock);
ED_area_tag_redraw(CTX_wm_area(C));
}
static void sequencer_sample_apply(bContext *C, wmOperator *op, const wmEvent *event)
{
Scene *scene = CTX_data_sequencer_scene(C);
ARegion *region = CTX_wm_region(C);
ImBuf *ibuf = ed::vse::sequencer_ibuf_get(C, scene->r.cfra, nullptr);
ImageSampleInfo *info = static_cast<ImageSampleInfo *>(op->customdata);
float fx, fy;
if (ibuf == nullptr) {
info->draw = false;
return;
}
ui::view2d_region_to_view(&region->v2d, event->mval[0], event->mval[1], &fx, &fy);
fx /= scene->r.xasp / scene->r.yasp;
fx += float(scene->r.xsch) / 2.0f;
fy += float(scene->r.ysch) / 2.0f;
fx *= float(ibuf->x) / float(scene->r.xsch);
fy *= float(ibuf->y) / float(scene->r.ysch);
if (fx >= 0.0f && fy >= 0.0f && fx < ibuf->x && fy < ibuf->y) {
const float *fp;
const uchar *cp;
int x = int(fx), y = int(fy);
info->x = x;
info->y = y;
info->draw = true;
info->channels = ibuf->channels;
info->colp = nullptr;
info->colfp = nullptr;
if (ibuf->byte_data()) {
cp = ibuf->byte_data() + 4 * (y * ibuf->x + x);
info->col[0] = cp[0];
info->col[1] = cp[1];
info->col[2] = cp[2];
info->col[3] = cp[3];
info->colp = info->col;
info->colf[0] = float(cp[0]) / 255.0f;
info->colf[1] = float(cp[1]) / 255.0f;
info->colf[2] = float(cp[2]) / 255.0f;
info->colf[3] = float(cp[3]) / 255.0f;
info->colfp = info->colf;
copy_v4_v4(info->linearcol, info->colf);
IMB_colormanagement_colorspace_to_scene_linear_v4(
info->linearcol, false, ibuf->byte_buffer.colorspace);
info->color_manage = true;
}
if (ibuf->float_data()) {
fp = (ibuf->float_data() + (ibuf->channels) * (y * ibuf->x + x));
info->colf[0] = fp[0];
info->colf[1] = fp[1];
info->colf[2] = fp[2];
info->colf[3] = fp[3];
info->colfp = info->colf;
copy_v4_v4(info->linearcol, info->colf);
IMB_colormanagement_colorspace_to_scene_linear_v4(
info->linearcol, true, ibuf->float_buffer.colorspace);
info->color_manage = true;
}
}
else {
info->draw = false;
}
IMB_freeImBuf(ibuf);
ED_area_tag_redraw(CTX_wm_area(C));
}
static void ed_imbuf_sample_apply(bContext *C, wmOperator *op, const wmEvent *event)
{
ScrArea *area = CTX_wm_area(C);
if (area == nullptr) {
return;
}
switch (area->spacetype) {
case SPACE_IMAGE: {
image_sample_apply(C, op, event);
break;
}
case SPACE_SEQ: {
sequencer_sample_apply(C, op, event);
break;
}
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Image Pixel Sample (Public Operator Callback)
*
* Callbacks for the sample operator, used by sequencer and image spaces.
* \{ */
void ED_imbuf_sample_draw(const bContext *C, ARegion *region, void *arg_info)
{
ImageSampleInfo *info = static_cast<ImageSampleInfo *>(arg_info);
if (!info->draw) {
return;
}
Scene *scene = CTX_data_scene(C);
ED_image_draw_info(scene,
region,
info->color_manage,
info->use_default_view,
info->channels,
info->x,
info->y,
info->colp,
info->colfp,
info->linearcol);
if (info->sample_size > 1) {
ScrArea *area = CTX_wm_area(C);
if (area && area->spacetype == SPACE_IMAGE) {
const wmWindow *win = CTX_wm_window(C);
const wmEvent *event = win->runtime->eventstate;
SpaceImage *sima = CTX_wm_space_image(C);
GPUVertFormat *format = immVertexFormat();
uint pos = GPU_vertformat_attr_add(format, "pos", gpu::VertAttrType::SFLOAT_32_32);
const float color[3] = {1, 1, 1};
immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR);
immUniformColor3fv(color);
/* TODO(@ideasman42): lock to pixels. */
rctf sample_rect_fl;
BLI_rctf_init_pt_radius(&sample_rect_fl,
float2{float(event->xy[0] - region->winrct.xmin),
float(event->xy[1] - region->winrct.ymin)},
float(info->sample_size / 2.0f) * sima->zoom);
GPU_logic_op_xor_set(true);
GPU_line_width(1.0f);
imm_draw_box_wire_2d(
pos, sample_rect_fl.xmin, sample_rect_fl.ymin, sample_rect_fl.xmax, sample_rect_fl.ymax);
GPU_logic_op_xor_set(false);
immUnbindProgram();
}
}
}
void ED_imbuf_sample_exit(bContext *C, wmOperator *op)
{
ImageSampleInfo *info = static_cast<ImageSampleInfo *>(op->customdata);
ED_region_draw_cb_exit(info->art, info->draw_handle);
ED_area_tag_redraw(CTX_wm_area(C));
MEM_delete(info);
}
wmOperatorStatus ED_imbuf_sample_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
ARegion *region = CTX_wm_region(C);
ScrArea *area = CTX_wm_area(C);
if (area) {
switch (area->spacetype) {
case SPACE_IMAGE: {
SpaceImage *sima = static_cast<SpaceImage *>(area->spacedata.first);
if (region->regiontype == RGN_TYPE_WINDOW) {
if (ED_space_image_show_cache_and_mval_over(sima, region, event->mval)) {
return OPERATOR_PASS_THROUGH;
}
}
if (!ED_space_image_has_buffer(sima)) {
return OPERATOR_CANCELLED;
}
break;
}
case SPACE_SEQ: {
/* Sequencer checks could be added. */
break;
}
}
}
ImageSampleInfo *info = MEM_new_zeroed<ImageSampleInfo>("ImageSampleInfo");
info->art = region->runtime->type;
info->draw_handle = ED_region_draw_cb_activate(
region->runtime->type, ED_imbuf_sample_draw, info, REGION_DRAW_POST_PIXEL);
info->sample_size = RNA_int_get(op->ptr, "size");
op->customdata = info;
ed_imbuf_sample_apply(C, op, event);
WM_event_add_modal_handler(C, op);
return OPERATOR_RUNNING_MODAL;
}
wmOperatorStatus ED_imbuf_sample_modal(bContext *C, wmOperator *op, const wmEvent *event)
{
switch (event->type) {
case LEFTMOUSE:
case RIGHTMOUSE: /* XXX hardcoded */
if (event->val == KM_RELEASE) {
ED_imbuf_sample_exit(C, op);
return OPERATOR_CANCELLED;
}
break;
case MOUSEMOVE:
ed_imbuf_sample_apply(C, op, event);
break;
default: {
break;
}
}
return OPERATOR_RUNNING_MODAL;
}
void ED_imbuf_sample_cancel(bContext *C, wmOperator *op)
{
ED_imbuf_sample_exit(C, op);
}
bool ED_imbuf_sample_poll(bContext *C)
{
ScrArea *area = CTX_wm_area(C);
if (area == nullptr) {
return false;
}
switch (area->spacetype) {
case SPACE_IMAGE: {
SpaceImage *sima = static_cast<SpaceImage *>(area->spacedata.first);
Object *obedit = CTX_data_edit_object(C);
if (obedit) {
/* Disable when UV editing so it doesn't swallow all click events
* (use for setting cursor). */
if (ED_space_image_show_uvedit(sima, obedit)) {
return false;
}
}
else if (sima->mode != SI_MODE_VIEW) {
return false;
}
return true;
}
case SPACE_SEQ: {
SpaceSeq *sseq = static_cast<SpaceSeq *>(area->spacedata.first);
if (sseq->mainb != SEQ_DRAW_IMG_IMBUF) {
return false;
}
if (seq::editing_get(CTX_data_sequencer_scene(C)) == nullptr) {
return false;
}
ARegion *region = CTX_wm_region(C);
if (!(region && (region->regiontype == RGN_TYPE_PREVIEW))) {
return false;
}
return true;
}
}
return false;
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,605 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edutil
*
* Utility operators for UI data or for the UI to use.
*/
#include <cstring>
#include "DNA_space_types.h"
#include "DNA_windowmanager_types.h"
#include "BLI_fileops.h"
#include "BLI_utildefines.h"
#include "BLI_vector.hh"
#include "BLT_translation.hh"
#include "BKE_context.hh"
#include "BKE_global.hh"
#include "BKE_lib_id.hh"
#include "BKE_lib_override.hh"
#include "BKE_library.hh"
#include "BKE_preview_image.hh"
#include "BKE_report.hh"
#include "ED_asset.hh"
#include "ED_render.hh"
#include "ED_undo.hh"
#include "ED_util.hh"
#include "RNA_access.hh"
#include "RNA_prototypes.hh"
#include "UI_interface.hh"
#include "WM_api.hh"
#include "WM_types.hh"
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Context Query Helpers
* \{ */
Vector<PointerRNA> ED_operator_single_id_from_context_as_vec(const bContext *C)
{
Vector<PointerRNA> ids;
PointerRNA idptr = CTX_data_pointer_get_type(C, "id", RNA_ID);
if (idptr.data) {
ids.append(idptr);
}
return ids;
}
Vector<PointerRNA> ED_operator_get_ids_from_context_as_vec(const bContext *C)
{
Vector<PointerRNA> ids;
/* "selected_ids" context member. */
CTX_data_selected_ids(C, &ids);
if (!ids.is_empty()) {
return ids;
}
/* "id" context member. */
return ED_operator_single_id_from_context_as_vec(C);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name ID Previews
* \{ */
static bool lib_id_preview_editing_poll_ex(const ID *id, const char **r_disabled_hint)
{
if (!id) {
return false;
}
if (!ID_IS_EDITABLE(id)) {
if (r_disabled_hint) {
*r_disabled_hint = RPT_("Cannot edit external library data");
}
return false;
}
if (ID_IS_OVERRIDE_LIBRARY(id)) {
if (r_disabled_hint) {
*r_disabled_hint = RPT_("Cannot edit previews of overridden library data");
}
return false;
}
if (!BKE_previewimg_id_get_p(id)) {
if (r_disabled_hint) {
*r_disabled_hint = RPT_("Data-block does not support previews");
}
return false;
}
return true;
}
static bool lib_id_preview_editing_poll(bContext *C)
{
const PointerRNA idptr = CTX_data_pointer_get(C, "id");
BLI_assert(!idptr.data || RNA_struct_is_ID(idptr.type));
const ID *id = static_cast<ID *>(idptr.data);
const char *disabled_hint = nullptr;
if (!lib_id_preview_editing_poll_ex(id, &disabled_hint)) {
CTX_wm_operator_poll_msg_set(C, disabled_hint);
return false;
}
return true;
}
static ID *lib_id_load_custom_preview_id_get(bContext *C, const wmOperator *op)
{
/* #invoke() gets the ID from context and saves it in the custom data. */
if (op->customdata) {
return static_cast<ID *>(op->customdata);
}
PointerRNA idptr = CTX_data_pointer_get(C, "id");
return static_cast<ID *>(idptr.data);
}
static wmOperatorStatus lib_id_load_custom_preview_exec(bContext *C, wmOperator *op)
{
char filepath[FILE_MAX];
RNA_string_get(op->ptr, "filepath", filepath);
if (!BLI_is_file(filepath)) {
BKE_reportf(op->reports, RPT_ERROR, "File not found '%s'", filepath);
return OPERATOR_CANCELLED;
}
ID *id = lib_id_load_custom_preview_id_get(C, op);
if (!id) {
BKE_report(
op->reports, RPT_ERROR, "Failed to set preview: no ID in context (incorrect context?)");
return OPERATOR_CANCELLED;
}
BKE_previewimg_id_custom_set(id, filepath);
WM_event_add_notifier(C, NC_ASSET | NA_EDITED, nullptr);
return OPERATOR_FINISHED;
}
/**
* Obtain the ID from context, and spawn a File Browser to select the preview image. The
* File Browser may re-use the Asset Browser under the cursor, and clear the file-list on
* confirmation, leading to failure to obtain the ID at that point. So get it before spawning the
* File Browser (store it in the operator custom data).
*/
static wmOperatorStatus lib_id_load_custom_preview_invoke(bContext *C,
wmOperator *op,
const wmEvent *event)
{
op->customdata = lib_id_load_custom_preview_id_get(C, op);
return WM_operator_filesel(C, op, event);
}
static void ED_OT_lib_id_load_custom_preview(wmOperatorType *ot)
{
ot->name = "Load Custom Preview";
ot->description = "Choose an image to help identify the data-block visually";
ot->idname = "ED_OT_lib_id_load_custom_preview";
/* API callbacks. */
ot->poll = lib_id_preview_editing_poll;
ot->exec = lib_id_load_custom_preview_exec;
ot->invoke = lib_id_load_custom_preview_invoke;
/* flags */
ot->flag = OPTYPE_UNDO | OPTYPE_INTERNAL;
WM_operator_properties_filesel(ot,
FILE_TYPE_FOLDER | FILE_TYPE_IMAGE,
FILE_SPECIAL,
FILE_OPENFILE,
WM_FILESEL_FILEPATH,
FILE_DEFAULTDISPLAY,
FILE_SORT_DEFAULT);
}
/**
* Helper for batch editing previews. Gets selected or active IDs from context and calls \a
* foreach_id for each ID that supports previews.
*/
static void lib_id_batch_edit_previews(bContext *C, FunctionRef<void(ID *)> foreach_id)
{
Vector<PointerRNA> id_pointers = ED_operator_get_ids_from_context_as_vec(C);
for (PointerRNA &idptr : id_pointers) {
ID *id = static_cast<ID *>(idptr.data);
if (lib_id_preview_editing_poll_ex(id, nullptr)) {
foreach_id(id);
}
}
}
/**
* Helper for batch editing previews. Check if at least one of the selected or active IDs supports
* previews, setting a disabled hint if not. Note that only one disabled hint can be set, this
* simply uses the first one set while polling individual IDs. That's more useful than a generic
* message still.
*
* \param additional_condition: When set, IDs need to additionally pass this check (return true) to
* be considered as supporting this operation.
*/
static bool lib_id_batch_editing_preview_poll(
bContext *C,
FunctionRef<bool(const ID *, const char **r_disabled_hint)> additional_condition = nullptr)
{
Vector<PointerRNA> id_pointers = ED_operator_get_ids_from_context_as_vec(C);
if (id_pointers.is_empty()) {
CTX_wm_operator_poll_msg_set(C, "No data-block selected or active");
return false;
}
const char *disabled_hint = nullptr;
for (const PointerRNA &idptr : id_pointers) {
const ID *id = static_cast<const ID *>(idptr.data);
const char *iter_disabled_hint = nullptr;
if (lib_id_preview_editing_poll_ex(id, &iter_disabled_hint) &&
(!additional_condition || additional_condition(id, &iter_disabled_hint)))
{
/* Operator can run if there's at least one ID supporting previews. */
return true;
}
if (iter_disabled_hint && !disabled_hint) {
disabled_hint = iter_disabled_hint;
}
}
/* Will only hold the first disabled hint set. That often gives some more specific information,
* so it's more useful than a generic message. */
if (disabled_hint) {
CTX_wm_operator_poll_msg_set(C, disabled_hint);
}
else {
CTX_wm_operator_poll_msg_set(C, "None of the selected data-blocks supports previews");
}
return false;
}
static bool lib_id_generate_preview_poll(bContext *C)
{
/* Requires GPU for viewport off-screen drawing. */
if (G.background) {
return false;
}
return lib_id_batch_editing_preview_poll(C, [](const ID *id, const char **r_disabled_hint) {
return ED_preview_id_render_is_supported(id, r_disabled_hint);
});
}
static wmOperatorStatus lib_id_generate_preview_exec(bContext *C, wmOperator * /*op*/)
{
using namespace blender::ed;
ED_preview_kill_jobs(CTX_wm_manager(C), CTX_data_main(C));
lib_id_batch_edit_previews(C, [&](ID *id) {
if (ED_preview_id_render_is_supported(id, nullptr)) {
PreviewImage *preview = BKE_previewimg_id_get(id);
if (preview) {
BKE_previewimg_clear(preview);
}
ui::icon_render_id(C, nullptr, id, ICON_SIZE_PREVIEW, true);
}
});
WM_event_add_notifier(C, NC_ASSET | NA_EDITED, nullptr);
asset::list::storage_tag_main_data_dirty();
return OPERATOR_FINISHED;
}
static void ED_OT_lib_id_generate_preview(wmOperatorType *ot)
{
ot->name = "Generate Preview";
ot->description = "Create an automatic preview for the selected data-block";
ot->idname = "ED_OT_lib_id_generate_preview";
/* API callbacks. */
ot->poll = lib_id_generate_preview_poll;
ot->exec = lib_id_generate_preview_exec;
/* flags */
ot->flag = OPTYPE_INTERNAL | OPTYPE_REGISTER | OPTYPE_UNDO;
}
static bool lib_id_generate_preview_from_object_poll(bContext *C)
{
if (G.background) {
/* Unsupported as it requires GPU for off-screen viewport drawing. */
return false;
}
/* This already checks if the IDs in context (e.g. selected in the Asset browser) can generate
* previews... */
if (!lib_id_batch_editing_preview_poll(C)) {
return false;
}
/* ... but we also need to check this for the active object (since this is what is being
* rendered). */
Object *object_to_render = CTX_data_active_object(C);
if (object_to_render == nullptr) {
return false;
}
const char *disabled_hint = nullptr;
if (!ED_preview_id_render_is_supported(&object_to_render->id, &disabled_hint)) {
CTX_wm_operator_poll_msg_set(C, disabled_hint);
return false;
}
return true;
}
static wmOperatorStatus lib_id_generate_preview_from_object_exec(bContext *C, wmOperator * /*op*/)
{
using namespace blender::ed;
ED_preview_kill_jobs(CTX_wm_manager(C), CTX_data_main(C));
Object *object_to_render = CTX_data_active_object(C);
lib_id_batch_edit_previews(C, [&](ID *id) {
BKE_previewimg_id_free(id);
PreviewImage *preview_image = BKE_previewimg_id_ensure(id);
ui::icon_render_id_ex(
C, nullptr, &object_to_render->id, ICON_SIZE_PREVIEW, true, preview_image);
});
WM_event_add_notifier(C, NC_ASSET | NA_EDITED, nullptr);
asset::list::storage_tag_main_data_dirty();
return OPERATOR_FINISHED;
}
static void ED_OT_lib_id_generate_preview_from_object(wmOperatorType *ot)
{
ot->name = "Generate Preview from Object";
ot->description = "Create a preview for this asset by rendering the active object";
ot->idname = "ED_OT_lib_id_generate_preview_from_object";
/* API callbacks. */
ot->poll = lib_id_generate_preview_from_object_poll;
ot->exec = lib_id_generate_preview_from_object_exec;
/* flags */
ot->flag = OPTYPE_INTERNAL | OPTYPE_REGISTER | OPTYPE_UNDO;
}
static bool lib_id_remove_preview_poll(bContext *C)
{
if (!lib_id_batch_editing_preview_poll(C)) {
return false;
}
bool has_any_removable = false;
lib_id_batch_edit_previews(C, [&](ID *id) {
if (BKE_previewimg_id_get(id)) {
has_any_removable = true;
}
});
if (!has_any_removable) {
CTX_wm_operator_poll_msg_set(C, "No preview available to remove");
return false;
}
return true;
}
static wmOperatorStatus lib_id_remove_preview_exec(bContext *C, wmOperator * /*op*/)
{
lib_id_batch_edit_previews(C, [&](ID *id) { BKE_previewimg_id_free(id); });
WM_event_add_notifier(C, NC_ASSET | NA_EDITED, nullptr);
return OPERATOR_FINISHED;
}
static void ED_OT_lib_id_remove_preview(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Remove Preview";
ot->description = "Remove the preview of this data-block";
ot->idname = "ED_OT_lib_id_remove_preview";
/* API callbacks. */
ot->poll = lib_id_remove_preview_poll;
ot->exec = lib_id_remove_preview_exec;
/* flags */
ot->flag = OPTYPE_UNDO | OPTYPE_INTERNAL;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Generic ID Operators
* \{ */
static wmOperatorStatus lib_id_fake_user_toggle_exec(bContext *C, wmOperator *op)
{
PropertyPointerRNA pprop;
PointerRNA idptr = PointerRNA_NULL;
ui::context_active_but_prop_get_templateID(C, &pprop.ptr, &pprop.prop);
if (pprop.prop) {
idptr = RNA_property_pointer_get(&pprop.ptr, pprop.prop);
}
if ((pprop.prop == nullptr) || RNA_pointer_is_null(&idptr) || !RNA_struct_is_ID(idptr.type)) {
BKE_report(
op->reports, RPT_ERROR, "Incorrect context for running data-block fake user toggling");
return OPERATOR_CANCELLED;
}
ID *id = static_cast<ID *>(idptr.data);
if (!BKE_id_is_editable(CTX_data_main(C), id) ||
ELEM(GS(id->name), ID_GR, ID_SCE, ID_SCR, ID_TXT, ID_OB, ID_WS))
{
BKE_report(op->reports, RPT_ERROR, "Data-block type does not support fake user");
return OPERATOR_CANCELLED;
}
if (ID_FAKE_USERS(id)) {
id_fake_user_clear(id);
}
else {
id_fake_user_set(id);
}
return OPERATOR_FINISHED;
}
static void ED_OT_lib_id_fake_user_toggle(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Toggle Fake User";
ot->description = "Save this data-block even if it has no users";
ot->idname = "ED_OT_lib_id_fake_user_toggle";
/* API callbacks. */
ot->exec = lib_id_fake_user_toggle_exec;
/* flags */
ot->flag = OPTYPE_UNDO | OPTYPE_INTERNAL;
}
static wmOperatorStatus lib_id_unlink_exec(bContext *C, wmOperator *op)
{
PropertyPointerRNA pprop;
PointerRNA idptr;
ui::context_active_but_prop_get_templateID(C, &pprop.ptr, &pprop.prop);
if (pprop.prop) {
idptr = RNA_property_pointer_get(&pprop.ptr, pprop.prop);
}
if ((pprop.prop == nullptr) || RNA_pointer_is_null(&idptr) || !RNA_struct_is_ID(idptr.type)) {
BKE_report(
op->reports, RPT_ERROR, "Incorrect context for running data-block fake user toggling");
return OPERATOR_CANCELLED;
}
idptr = {};
RNA_property_pointer_set(&pprop.ptr, pprop.prop, idptr, nullptr);
RNA_property_update(C, &pprop.ptr, pprop.prop);
return OPERATOR_FINISHED;
}
static void ED_OT_lib_id_unlink(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Unlink Data-Block";
ot->description = "Remove a usage of a data-block, clearing the assignment";
ot->idname = "ED_OT_lib_id_unlink";
/* API callbacks. */
ot->exec = lib_id_unlink_exec;
/* flags */
ot->flag = OPTYPE_UNDO | OPTYPE_INTERNAL;
}
static bool lib_id_override_editable_toggle_poll(bContext *C)
{
const PointerRNA id_ptr = CTX_data_pointer_get_type(C, "id", RNA_ID);
const ID *id = static_cast<ID *>(id_ptr.data);
return id && ID_IS_OVERRIDE_LIBRARY_REAL(id) && !ID_IS_LINKED(id);
}
static wmOperatorStatus lib_id_override_editable_toggle_exec(bContext *C, wmOperator * /*op*/)
{
Main *bmain = CTX_data_main(C);
const PointerRNA id_ptr = CTX_data_pointer_get_type(C, "id", RNA_ID);
ID *id = static_cast<ID *>(id_ptr.data);
const bool is_system_override = BKE_lib_override_library_is_system_defined(bmain, id);
if (is_system_override) {
/* A system override is not editable. Make it an editable (non-system-defined) one. */
id->override_library->flag &= ~LIBOVERRIDE_FLAG_SYSTEM_DEFINED;
}
else {
/* Reset override, which makes it non-editable (i.e. a system define override). */
BKE_lib_override_library_id_reset(bmain, id, true);
WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, nullptr);
WM_event_add_notifier(C, NC_WINDOW, nullptr);
}
WM_main_add_notifier(NC_WM | ND_LIB_OVERRIDE_CHANGED, nullptr);
return OPERATOR_FINISHED;
}
static void ED_OT_lib_id_override_editable_toggle(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Toggle Library Override Editable";
ot->description = "Set if this library override data-block can be edited";
ot->idname = "ED_OT_lib_id_override_editable_toggle";
/* API callbacks. */
ot->poll = lib_id_override_editable_toggle_poll;
ot->exec = lib_id_override_editable_toggle_exec;
/* flags */
ot->flag = OPTYPE_UNDO | OPTYPE_INTERNAL;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name General editor utils.
* \{ */
static wmOperatorStatus ed_flush_edits_exec(bContext *C, wmOperator * /*op*/)
{
Main *bmain = CTX_data_main(C);
ED_editors_flush_edits(bmain);
return OPERATOR_FINISHED;
}
static void ED_OT_flush_edits(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Flush Edits";
ot->description = "Flush edit data from active editing modes";
ot->idname = "ED_OT_flush_edits";
/* API callbacks. */
ot->exec = ed_flush_edits_exec;
/* flags */
ot->flag = OPTYPE_INTERNAL;
}
/** \} */
void ED_operatortypes_edutils()
{
WM_operatortype_append(ED_OT_lib_id_load_custom_preview);
WM_operatortype_append(ED_OT_lib_id_generate_preview);
WM_operatortype_append(ED_OT_lib_id_generate_preview_from_object);
WM_operatortype_append(ED_OT_lib_id_remove_preview);
WM_operatortype_append(ED_OT_lib_id_fake_user_toggle);
WM_operatortype_append(ED_OT_lib_id_unlink);
WM_operatortype_append(ED_OT_lib_id_override_editable_toggle);
WM_operatortype_append(ED_OT_flush_edits);
WM_operatortype_append(ED_OT_undo);
WM_operatortype_append(ED_OT_undo_push);
WM_operatortype_append(ED_OT_redo);
WM_operatortype_append(ED_OT_undo_redo);
WM_operatortype_append(ED_OT_undo_history);
}
} // namespace blender

View File

@@ -0,0 +1,572 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "ED_viewer_path.hh"
#include "ED_node.hh"
#include "ED_screen.hh"
#include "BKE_compute_context_cache.hh"
#include "BKE_compute_contexts.hh"
#include "BKE_context.hh"
#include "BKE_lib_id.hh"
#include "BKE_main.hh"
#include "BKE_node_legacy_types.hh"
#include "BKE_node_runtime.hh"
#include "BKE_node_tree_zones.hh"
#include "BKE_viewer_path.hh"
#include "BKE_workspace.hh"
#include "BLI_listbase.h"
#include "BLI_string.h"
#include "BLI_vector.hh"
#include "DNA_modifier_types.h"
#include "DNA_node_types.h"
#include "DNA_windowmanager_types.h"
#include "DEG_depsgraph.hh"
#include "DEG_depsgraph_query.hh"
#include "WM_api.hh"
namespace blender::ed::viewer_path {
using bke::bNodeTreeZone;
using bke::bNodeTreeZones;
ViewerPathElem *viewer_path_elem_for_compute_context(const ComputeContext &compute_context)
{
if (const auto *context = dynamic_cast<const bke::DataBlockComputeContext *>(&compute_context)) {
IDViewerPathElem *elem = BKE_viewer_path_elem_new_id();
elem->id = const_cast<ID *>(context->id());
BLI_assert(elem->id);
elem->base.ui_name = BLI_strdup(BKE_id_name(*elem->id));
return &elem->base;
}
if (const auto *context = dynamic_cast<const bke::ModifierComputeContext *>(&compute_context)) {
ModifierViewerPathElem *elem = BKE_viewer_path_elem_new_modifier();
elem->modifier_uid = context->modifier_uid();
if (const NodesModifierData *nmd = context->nmd()) {
elem->base.ui_name = BLI_strdup(nmd->modifier.name);
}
return &elem->base;
}
if (const auto *context = dynamic_cast<const bke::GroupNodeComputeContext *>(&compute_context)) {
GroupNodeViewerPathElem *elem = BKE_viewer_path_elem_new_group_node();
elem->node_id = context->node_id();
if (const bNode *caller_node = context->node()) {
if (const bNodeTree *group = reinterpret_cast<const bNodeTree *>(caller_node->id)) {
elem->base.ui_name = BLI_strdup(BKE_id_name(group->id));
}
}
return &elem->base;
}
if (const auto *context = dynamic_cast<const bke::SimulationZoneComputeContext *>(
&compute_context))
{
SimulationZoneViewerPathElem *elem = BKE_viewer_path_elem_new_simulation_zone();
elem->sim_output_node_id = context->output_node_id();
return &elem->base;
}
if (const auto *context = dynamic_cast<const bke::RepeatZoneComputeContext *>(&compute_context))
{
RepeatZoneViewerPathElem *elem = BKE_viewer_path_elem_new_repeat_zone();
elem->repeat_output_node_id = context->output_node_id();
elem->iteration = context->iteration();
return &elem->base;
}
if (const auto *context = dynamic_cast<const bke::ForeachGeometryElementZoneComputeContext *>(
&compute_context))
{
ForeachGeometryElementZoneViewerPathElem *elem =
BKE_viewer_path_elem_new_foreach_geometry_element_zone();
elem->zone_output_node_id = context->output_node_id();
elem->index = context->index();
return &elem->base;
}
if (const auto *context = dynamic_cast<const bke::EvaluateClosureComputeContext *>(
&compute_context))
{
EvaluateClosureNodeViewerPathElem *elem = BKE_viewer_path_elem_new_evaluate_closure();
elem->evaluate_node_id = context->node_id();
if (const std::optional<nodes::ClosureSourceLocation> &source =
context->closure_source_location())
{
elem->source_output_node_id = source->closure_output_node_id;
BLI_assert(DEG_is_original(source->tree));
elem->source_node_tree = const_cast<bNodeTree *>(source->tree);
}
return &elem->base;
}
return nullptr;
}
static void viewer_path_for_geometry_node(const SpaceNode &snode,
const bNode &node,
ViewerPath &r_dst)
{
/* Only valid if the node space has a context object. */
BLI_assert(snode.id != nullptr && GS(snode.id->name) == ID_OB);
snode.edittree->ensure_topology_cache();
BKE_viewer_path_init(&r_dst);
bke::ComputeContextCache compute_context_cache;
const ComputeContext *socket_context = space_node::compute_context_for_edittree_socket(
snode, compute_context_cache, node.input_socket(0));
if (!socket_context) {
return;
}
for (const ComputeContext *context = socket_context; context; context = context->parent()) {
ViewerPathElem *elem = viewer_path_elem_for_compute_context(*context);
if (!elem) {
BKE_viewer_path_clear(&r_dst);
return;
}
BLI_addhead(&r_dst.path, elem);
}
ViewerNodeViewerPathElem *viewer_node_elem = BKE_viewer_path_elem_new_viewer_node();
viewer_node_elem->node_id = node.identifier;
viewer_node_elem->base.ui_name = BLI_strdup(bke::node_label(*snode.edittree, node).c_str());
BLI_addtail(&r_dst.path, viewer_node_elem);
}
void activate_geometry_node(Main &bmain,
SpaceNode &snode,
bNode &node,
std::optional<int> item_identifier)
{
wmWindowManager *wm = static_cast<wmWindowManager *>(bmain.wm.first);
if (wm == nullptr) {
return;
}
for (bNode *iter_node : snode.edittree->all_nodes()) {
if (iter_node->type_legacy == GEO_NODE_VIEWER) {
SET_FLAG_FROM_TEST(iter_node->flag, iter_node == &node, NODE_DO_OUTPUT);
}
}
ViewerPath new_viewer_path{};
BLI_SCOPED_DEFER([&]() { BKE_viewer_path_clear(&new_viewer_path); });
if (snode.id != nullptr && GS(snode.id->name) == ID_OB) {
viewer_path_for_geometry_node(snode, node, new_viewer_path);
}
bool found_view3d_with_enabled_viewer = false;
View3D *any_view3d_without_viewer = nullptr;
for (wmWindow &window : wm->windows) {
WorkSpace *workspace = BKE_workspace_active_get(window.workspace_hook);
bScreen *screen = BKE_workspace_active_screen_get(window.workspace_hook);
for (ScrArea &area : screen->areabase) {
SpaceLink *sl = static_cast<SpaceLink *>(area.spacedata.first);
if (sl->spacetype == SPACE_SPREADSHEET) {
SpaceSpreadsheet &sspreadsheet = *reinterpret_cast<SpaceSpreadsheet *>(sl);
if (!(sspreadsheet.flag & SPREADSHEET_FLAG_PINNED)) {
SpreadsheetTableIDGeometry &table_id = sspreadsheet.geometry_id;
table_id.object_eval_state = SPREADSHEET_OBJECT_EVAL_STATE_VIEWER_NODE;
if (item_identifier) {
table_id.viewer_item_identifier = *item_identifier;
}
MEM_SAFE_DELETE(table_id.viewer_item_bundle_path.bundle_path);
table_id.viewer_item_bundle_path.bundle_path_num = 0;
table_id.viewer_item_bundle_path.closure_input_output = SPREADSHEET_CLOSURE_NONE;
}
}
else if (sl->spacetype == SPACE_VIEW3D) {
View3D &v3d = *reinterpret_cast<View3D *>(sl);
if (v3d.flag2 & V3D_SHOW_VIEWER) {
found_view3d_with_enabled_viewer = true;
}
else {
any_view3d_without_viewer = &v3d;
}
}
}
/* Enable viewer in one viewport if it is disabled in all of them. */
if (!found_view3d_with_enabled_viewer && any_view3d_without_viewer != nullptr) {
any_view3d_without_viewer->flag2 |= V3D_SHOW_VIEWER;
}
BKE_viewer_path_clear(&workspace->viewer_path);
BKE_viewer_path_copy(&workspace->viewer_path, &new_viewer_path);
/* Make sure the viewed data becomes available. */
DEG_id_tag_update(snode.id, ID_RECALC_GEOMETRY);
WM_main_add_notifier(NC_VIEWER_PATH, nullptr);
}
}
Object *parse_object_only(const ViewerPath &viewer_path)
{
if (viewer_path.path.count() != 1) {
return nullptr;
}
const ViewerPathElem *elem = static_cast<ViewerPathElem *>(viewer_path.path.first);
if (elem->type != VIEWER_PATH_ELEM_TYPE_ID) {
return nullptr;
}
ID *id = reinterpret_cast<const IDViewerPathElem *>(elem)->id;
if (id == nullptr) {
return nullptr;
}
if (GS(id->name) != ID_OB) {
return nullptr;
}
return reinterpret_cast<Object *>(id);
}
std::optional<ViewerPathForGeometryNodesViewer> parse_geometry_nodes_viewer(
const ViewerPath &viewer_path)
{
Vector<const ViewerPathElem *, 16> elems_vec;
for (const ViewerPathElem &item : viewer_path.path) {
elems_vec.append(&item);
}
if (elems_vec.size() < 3) {
/* Need at least the object, modifier and viewer node name. */
return std::nullopt;
}
Span<const ViewerPathElem *> remaining_elems = elems_vec;
const ViewerPathElem &id_elem = *remaining_elems[0];
if (id_elem.type != VIEWER_PATH_ELEM_TYPE_ID) {
return std::nullopt;
}
ID *root_id = reinterpret_cast<const IDViewerPathElem &>(id_elem).id;
if (root_id == nullptr) {
return std::nullopt;
}
if (GS(root_id->name) != ID_OB) {
return std::nullopt;
}
Object *root_ob = reinterpret_cast<Object *>(root_id);
remaining_elems = remaining_elems.drop_front(1);
const ViewerPathElem &modifier_elem = *remaining_elems[0];
if (modifier_elem.type != VIEWER_PATH_ELEM_TYPE_MODIFIER) {
return std::nullopt;
}
const int modifier_uid =
reinterpret_cast<const ModifierViewerPathElem &>(modifier_elem).modifier_uid;
remaining_elems = remaining_elems.drop_front(1);
Vector<const ViewerPathElem *> node_path;
for (const ViewerPathElem *elem : remaining_elems.drop_back(1)) {
if (!ELEM(elem->type,
VIEWER_PATH_ELEM_TYPE_GROUP_NODE,
VIEWER_PATH_ELEM_TYPE_SIMULATION_ZONE,
VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE,
VIEWER_PATH_ELEM_TYPE_FOREACH_GEOMETRY_ELEMENT_ZONE,
VIEWER_PATH_ELEM_TYPE_EVALUATE_CLOSURE))
{
return std::nullopt;
}
node_path.append(elem);
}
const ViewerPathElem *last_elem = remaining_elems.last();
if (last_elem->type != VIEWER_PATH_ELEM_TYPE_VIEWER_NODE) {
return std::nullopt;
}
const int32_t viewer_node_id =
reinterpret_cast<const ViewerNodeViewerPathElem *>(last_elem)->node_id;
return ViewerPathForGeometryNodesViewer{root_ob, modifier_uid, node_path, viewer_node_id};
}
bool exists_geometry_nodes_viewer(const ViewerPathForGeometryNodesViewer &parsed_viewer_path)
{
const NodesModifierData *modifier = nullptr;
for (const ModifierData &md : parsed_viewer_path.object->modifiers) {
if (md.type != eModifierType_Nodes) {
continue;
}
if (md.persistent_uid != parsed_viewer_path.modifier_uid) {
continue;
}
modifier = reinterpret_cast<const NodesModifierData *>(&md);
break;
}
if (modifier == nullptr) {
return false;
}
if (modifier->node_group == nullptr || ID_MISSING(modifier->node_group)) {
return false;
}
const bNodeTree *ngroup = modifier->node_group;
const bNodeTreeZone *zone = nullptr;
for (const ViewerPathElem *path_elem : parsed_viewer_path.node_path) {
ngroup->ensure_topology_cache();
const bNodeTreeZones *tree_zones = ngroup->zones();
switch (path_elem->type) {
case VIEWER_PATH_ELEM_TYPE_SIMULATION_ZONE: {
const auto &typed_elem = *reinterpret_cast<const SimulationZoneViewerPathElem *>(
path_elem);
const bNodeTreeZone *next_zone = tree_zones->get_zone_by_node(
typed_elem.sim_output_node_id);
if (next_zone == nullptr) {
return false;
}
if (next_zone->parent_zone != zone) {
return false;
}
zone = next_zone;
break;
}
case VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE: {
const auto &typed_elem = *reinterpret_cast<const RepeatZoneViewerPathElem *>(path_elem);
const bNodeTreeZone *next_zone = tree_zones->get_zone_by_node(
typed_elem.repeat_output_node_id);
if (next_zone == nullptr) {
return false;
}
if (next_zone->parent_zone != zone) {
return false;
}
zone = next_zone;
break;
}
case VIEWER_PATH_ELEM_TYPE_FOREACH_GEOMETRY_ELEMENT_ZONE: {
const auto &typed_elem =
*reinterpret_cast<const ForeachGeometryElementZoneViewerPathElem *>(path_elem);
const bNodeTreeZone *next_zone = tree_zones->get_zone_by_node(
typed_elem.zone_output_node_id);
if (next_zone == nullptr) {
return false;
}
if (next_zone->parent_zone != zone) {
return false;
}
zone = next_zone;
break;
}
case VIEWER_PATH_ELEM_TYPE_GROUP_NODE: {
const auto &typed_elem = *reinterpret_cast<const GroupNodeViewerPathElem *>(path_elem);
const bNode *group_node = ngroup->node_by_id(typed_elem.node_id);
if (group_node == nullptr) {
return false;
}
const bNodeTreeZone *parent_zone = tree_zones->get_zone_by_node(typed_elem.node_id);
if (parent_zone != zone) {
return false;
}
if (group_node->id == nullptr) {
return false;
}
ngroup = reinterpret_cast<const bNodeTree *>(group_node->id);
zone = nullptr;
break;
}
case VIEWER_PATH_ELEM_TYPE_EVALUATE_CLOSURE: {
const auto &typed_elem = *reinterpret_cast<const EvaluateClosureNodeViewerPathElem *>(
path_elem);
const bNode *evaluate_node = ngroup->node_by_id(typed_elem.evaluate_node_id);
if (evaluate_node == nullptr) {
return false;
}
const bNodeTreeZone *parent_zone = tree_zones->get_zone_by_node(
typed_elem.evaluate_node_id);
if (parent_zone != zone) {
return false;
}
if (!typed_elem.source_node_tree) {
return false;
}
const bNode *closure_output_node = typed_elem.source_node_tree->node_by_id(
typed_elem.source_output_node_id);
if (!closure_output_node) {
return false;
}
ngroup = typed_elem.source_node_tree;
const bNodeTreeZones *closure_tree_zones = typed_elem.source_node_tree->zones();
if (!closure_tree_zones) {
return false;
}
zone = closure_tree_zones->get_zone_by_node(typed_elem.source_output_node_id);
break;
}
default: {
BLI_assert_unreachable();
}
}
}
const bNode *viewer_node = ngroup->node_by_id(parsed_viewer_path.viewer_node_id);
if (viewer_node == nullptr) {
return false;
}
const bNodeTreeZones *tree_zones = ngroup->zones();
if (tree_zones == nullptr) {
return false;
}
if (tree_zones->get_zone_by_node(viewer_node->identifier) != zone) {
return false;
}
return true;
}
UpdateActiveGeometryNodesViewerResult update_active_geometry_nodes_viewer(const bContext &C,
ViewerPath &viewer_path)
{
if (viewer_path.path.is_empty()) {
return UpdateActiveGeometryNodesViewerResult::NotActive;
}
const ViewerPathElem *last_elem = static_cast<ViewerPathElem *>(viewer_path.path.last);
if (last_elem->type != VIEWER_PATH_ELEM_TYPE_VIEWER_NODE) {
return UpdateActiveGeometryNodesViewerResult::NotActive;
}
const int32_t viewer_node_id =
reinterpret_cast<const ViewerNodeViewerPathElem *>(last_elem)->node_id;
const Main *bmain = CTX_data_main(&C);
const wmWindowManager *wm = static_cast<wmWindowManager *>(bmain->wm.first);
if (wm == nullptr) {
return UpdateActiveGeometryNodesViewerResult::NotActive;
}
for (const wmWindow &window : wm->windows) {
const bScreen *active_screen = BKE_workspace_active_screen_get(window.workspace_hook);
Vector<const bScreen *> screens = {active_screen};
if (ELEM(active_screen->state, SCREENMAXIMIZED, SCREENFULL)) {
const ScrArea *area = static_cast<ScrArea *>(active_screen->areabase.first);
screens.append(area->full);
}
for (const bScreen *screen : screens) {
for (const ScrArea &area : screen->areabase) {
const SpaceLink *sl = static_cast<SpaceLink *>(area.spacedata.first);
if (sl == nullptr) {
continue;
}
if (sl->spacetype != SPACE_NODE) {
continue;
}
const SpaceNode &snode = *reinterpret_cast<const SpaceNode *>(sl);
if (snode.edittree == nullptr) {
continue;
}
if (snode.edittree->type != NTREE_GEOMETRY) {
continue;
}
if (!snode.id) {
continue;
}
snode.edittree->ensure_topology_cache();
const bNode *viewer_node = snode.edittree->node_by_id(viewer_node_id);
if (viewer_node == nullptr) {
continue;
}
if (!(viewer_node->flag & NODE_DO_OUTPUT)) {
continue;
}
ViewerPath tmp_viewer_path{};
BLI_SCOPED_DEFER([&]() { BKE_viewer_path_clear(&tmp_viewer_path); });
viewer_path_for_geometry_node(snode, *viewer_node, tmp_viewer_path);
if (!BKE_viewer_path_equal(
&viewer_path, &tmp_viewer_path, VIEWER_PATH_EQUAL_FLAG_IGNORE_ITERATION))
{
continue;
}
if (!BKE_viewer_path_equal(&viewer_path, &tmp_viewer_path)) {
std::swap(viewer_path, tmp_viewer_path);
/* Make sure the viewed data becomes available. */
DEG_id_tag_update(snode.id, ID_RECALC_GEOMETRY);
return UpdateActiveGeometryNodesViewerResult::Updated;
}
if (!BKE_viewer_path_equal(
&viewer_path, &tmp_viewer_path, VIEWER_PATH_EQUAL_FLAG_CONSIDER_UI_NAME))
{
/* Only swap, without triggering a depsgraph update. */
std::swap(viewer_path, tmp_viewer_path);
return UpdateActiveGeometryNodesViewerResult::Updated;
}
return UpdateActiveGeometryNodesViewerResult::StillActive;
}
}
}
return UpdateActiveGeometryNodesViewerResult::NotActive;
}
bNode *find_geometry_nodes_viewer(const ViewerPath &viewer_path, SpaceNode &snode)
{
/* Viewer path is only valid if the context object is set. */
if (snode.id == nullptr || GS(snode.id->name) != ID_OB) {
return nullptr;
}
const std::optional<ViewerPathForGeometryNodesViewer> parsed_viewer_path =
parse_geometry_nodes_viewer(viewer_path);
if (!parsed_viewer_path.has_value()) {
return nullptr;
}
snode.edittree->ensure_topology_cache();
bNode *possible_viewer = snode.edittree->node_by_id(parsed_viewer_path->viewer_node_id);
if (possible_viewer == nullptr) {
return nullptr;
}
ViewerPath tmp_viewer_path;
BLI_SCOPED_DEFER([&]() { BKE_viewer_path_clear(&tmp_viewer_path); });
viewer_path_for_geometry_node(snode, *possible_viewer, tmp_viewer_path);
if (BKE_viewer_path_equal(&viewer_path, &tmp_viewer_path)) {
return possible_viewer;
}
return nullptr;
}
[[nodiscard]] const ComputeContext *compute_context_for_viewer_path_elem(
const ViewerPathElem &elem_generic,
bke::ComputeContextCache &compute_context_cache,
const ComputeContext *parent_compute_context)
{
switch (ViewerPathElemType(elem_generic.type)) {
case VIEWER_PATH_ELEM_TYPE_VIEWER_NODE: {
return nullptr;
}
case VIEWER_PATH_ELEM_TYPE_ID: {
const auto &elem = reinterpret_cast<const IDViewerPathElem &>(elem_generic);
if (elem.id == nullptr) {
return nullptr;
}
return &compute_context_cache.for_data_block(parent_compute_context, *elem.id);
}
case VIEWER_PATH_ELEM_TYPE_MODIFIER: {
const auto &elem = reinterpret_cast<const ModifierViewerPathElem &>(elem_generic);
return &compute_context_cache.for_modifier(parent_compute_context, elem.modifier_uid);
}
case VIEWER_PATH_ELEM_TYPE_GROUP_NODE: {
const auto &elem = reinterpret_cast<const GroupNodeViewerPathElem &>(elem_generic);
return &compute_context_cache.for_group_node(parent_compute_context, elem.node_id);
}
case VIEWER_PATH_ELEM_TYPE_SIMULATION_ZONE: {
const auto &elem = reinterpret_cast<const SimulationZoneViewerPathElem &>(elem_generic);
return &compute_context_cache.for_simulation_zone(parent_compute_context,
elem.sim_output_node_id);
}
case VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE: {
const auto &elem = reinterpret_cast<const RepeatZoneViewerPathElem &>(elem_generic);
return &compute_context_cache.for_repeat_zone(
parent_compute_context, elem.repeat_output_node_id, elem.iteration);
}
case VIEWER_PATH_ELEM_TYPE_FOREACH_GEOMETRY_ELEMENT_ZONE: {
const auto &elem = reinterpret_cast<const ForeachGeometryElementZoneViewerPathElem &>(
elem_generic);
return &compute_context_cache.for_foreach_geometry_element_zone(
parent_compute_context, elem.zone_output_node_id, elem.index);
}
case VIEWER_PATH_ELEM_TYPE_EVALUATE_CLOSURE: {
const auto &elem = reinterpret_cast<const EvaluateClosureNodeViewerPathElem &>(elem_generic);
std::optional<nodes::ClosureSourceLocation> source_location;
if (elem.source_node_tree) {
source_location = nodes::ClosureSourceLocation{
elem.source_node_tree,
elem.source_output_node_id,
parent_compute_context ? parent_compute_context->hash() : ComputeContextHash{}};
}
return &compute_context_cache.for_evaluate_closure(
parent_compute_context, elem.evaluate_node_id, nullptr, source_location);
}
}
return nullptr;
}
} // namespace blender::ed::viewer_path

View File

@@ -0,0 +1,68 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edutil
*
* \name Generic Gizmo Utilities.
*/
#include <cstring>
#include "BLI_utildefines.h"
#include "BKE_context.hh"
#include "DNA_workspace_types.h"
#include "WM_api.hh"
#include "WM_toolsystem.hh"
#include "WM_types.hh"
#include "ED_gizmo_utils.hh"
namespace blender {
bool ED_gizmo_poll_or_unlink_delayed_from_operator(const bContext *C,
wmGizmoGroupType *gzgt,
const char *idname)
{
#if 0
/* Causes selection to continue showing the last gizmo. */
wmOperator *op = WM_operator_last_redo(C);
#else
wmWindowManager *wm = CTX_wm_manager(C);
wmOperator *op = static_cast<wmOperator *>(wm->runtime->operators.last);
#endif
if (op == nullptr || !STREQ(op->type->idname, idname)) {
WM_gizmo_group_type_unlink_delayed_ptr(gzgt);
return false;
}
return true;
}
bool ED_gizmo_poll_or_unlink_delayed_from_tool_ex(const bContext *C,
wmGizmoGroupType *gzgt,
const char *gzgt_idname)
{
bToolRef_Runtime *tref_rt = WM_toolsystem_runtime_from_context(const_cast<bContext *>(C));
if ((tref_rt == nullptr) || !STREQ(gzgt_idname, tref_rt->gizmo_group)) {
ScrArea *area = CTX_wm_area(C);
wmGizmoMapType *gzmap_type = WM_gizmomaptype_ensure(&gzgt->gzmap_params);
WM_gizmo_group_unlink_delayed_ptr_from_space(gzgt, gzmap_type, area);
if (gzgt->users == 0) {
WM_gizmo_group_type_unlink_delayed_ptr(gzgt);
}
return false;
}
return true;
}
bool ED_gizmo_poll_or_unlink_delayed_from_tool(const bContext *C, wmGizmoGroupType *gzgt)
{
return ED_gizmo_poll_or_unlink_delayed_from_tool_ex(C, gzgt, gzgt->idname);
}
} // namespace blender

View File

@@ -0,0 +1,679 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edutil
*/
#include <algorithm>
#include <limits>
#include "MEM_guardedalloc.h"
#include "BLI_math_rotation.h"
#include "BLI_math_vector.h"
#include "BLI_string.h"
#include "BLI_string_cursor_utf8.h"
#include "BLI_string_utf8.h"
#include "BLI_utildefines.h"
#include "BLT_translation.hh"
#include "BKE_context.hh"
#include "BKE_report.hh"
#include "BKE_unit.hh"
#include "DNA_scene_types.h"
#include "WM_api.hh"
#include "WM_types.hh"
#ifdef WITH_PYTHON
# include "BPY_extern_run.hh"
#endif
#include "ED_numinput.hh"
#include "UI_interface.hh"
namespace blender {
/* Numeric input which isn't allowing full numeric editing. */
#define USE_FAKE_EDIT
/**
* #NumInput.flag
* (1 << 8) and below are reserved for public flags!
*/
enum {
/** Enable full editing, with units and math operators support. */
NUM_EDIT_FULL = (1 << 9),
#ifdef USE_FAKE_EDIT
/** Fake edited state (temp, avoids issue with backspace). */
NUM_FAKE_EDITED = (1 << 10),
#endif
};
/* NumInput.val_flag[] */
enum {
/* (1 << 8) and below are reserved for public flags! */
/** User has edited this value somehow. */
NUM_EDITED = (1 << 9), /** Current expression for this value is invalid. */
NUM_INVALID = (1 << 10),
#ifdef USE_FAKE_EDIT
/** Current expression's result has to be negated. */
NUM_NEGATE = (1 << 11), /** Current expression's result has to be inverted. */
NUM_INVERSE = (1 << 12),
#endif
};
/* ************************** Functions *************************** */
bool ED_numinput_double_is_int(double value)
{
value = std::abs(value);
/* Use `>=` because `uint64_t::max` rounds up to 2^64 when converted to double. */
if (!isfinite(value) || value >= double(std::numeric_limits<uint64_t>::max())) {
return false;
}
return value == double(uint64_t(value));
}
/* ************************** NUMINPUT **************************** */
void initNumInput(NumInput *n)
{
n->idx_max = 0;
n->unit_sys = USER_UNIT_NONE;
std::fill_n(n->unit_type, NUM_MAX_ELEMENTS, B_UNIT_NONE);
n->unit_use_radians = false;
n->flag = 0;
std::fill_n(n->val_flag, NUM_MAX_ELEMENTS, 0);
zero_v3(n->val);
std::fill_n(n->val_no_units, NUM_MAX_ELEMENTS, 0.0);
std::fill_n(n->val_org, NUM_MAX_ELEMENTS, 0.0f);
std::fill_n(n->val_inc, NUM_MAX_ELEMENTS, 1.0f);
n->idx = 0;
n->str[0] = '\0';
n->str_cur = 0;
}
void outputNumInput(NumInput *n, char *str, const UnitSettings &unit_settings)
{
short j;
const int ln = NUM_STR_REP_LEN;
int prec = 2; /* draw-only, and avoids too much issues with radian->degrees conversion. */
for (j = 0; j <= n->idx_max; j++) {
/* if AFFECTALL and no number typed and cursor not on number, use first number */
const short i = (n->flag & NUM_AFFECT_ALL && n->idx != j && !(n->val_flag[j] & NUM_EDITED)) ?
0 :
j;
/* Use scale_length if needed! */
const float fac = float(BKE_unit_value_scale(unit_settings, n->unit_type[j], 1.0));
if (n->val_flag[i] & NUM_EDITED) {
/* Get the best precision, allows us to draw '10.0001' as '10' instead! */
prec = ui::calc_float_precision(prec, double(n->val[i]));
if (i == n->idx) {
const char *heading_exp = "", *trailing_exp = "";
char before_cursor[NUM_STR_REP_LEN];
char val[16];
#ifdef USE_FAKE_EDIT
if (n->val_flag[i] & NUM_NEGATE) {
heading_exp = (n->val_flag[i] & NUM_INVERSE) ? "-1/(" : "-(";
trailing_exp = ")";
}
else if (n->val_flag[i] & NUM_INVERSE) {
heading_exp = "1/(";
trailing_exp = ")";
}
#endif
if (n->val_flag[i] & NUM_INVALID) {
STRNCPY_UTF8(val, RPT_("Invalid"));
}
else {
BKE_unit_value_as_string_adaptive(val,
sizeof(val),
double(n->val[i] * fac),
prec,
n->unit_sys,
n->unit_type[i],
true,
false,
true);
}
/* +1 because of trailing '\0' */
BLI_strncpy_utf8(before_cursor, n->str, n->str_cur + 1);
BLI_snprintf_utf8(&str[j * ln],
ln,
"[%s%s|%s%s] = %s",
heading_exp,
before_cursor,
&n->str[n->str_cur],
trailing_exp,
val);
}
else {
const char *cur = (i == n->idx) ? "|" : "";
if (n->unit_use_radians && n->unit_type[i] == B_UNIT_ROTATION) {
/* Radian exception... */
BLI_snprintf_utf8(&str[j * ln], ln, "%s%.6gr%s", cur, n->val[i], cur);
}
else {
char tstr[NUM_STR_REP_LEN];
BKE_unit_value_as_string_adaptive(
tstr, ln, double(n->val[i]), prec, n->unit_sys, n->unit_type[i], true, false, true);
BLI_snprintf_utf8(&str[j * ln], ln, "%s%s%s", cur, tstr, cur);
}
}
}
else {
const char *cur = (i == n->idx) ? "|" : "";
BLI_snprintf_utf8(&str[j * ln], ln, "%sNONE%s", cur, cur);
}
/* We might have cut some multi-bytes UTF8 chars
* (e.g. trailing degrees symbol values can become only 'A'). */
BLI_str_utf8_invalid_strip(&str[j * ln], strlen(&str[j * ln]));
}
}
bool hasNumInput(const NumInput *n)
{
short i;
#ifdef USE_FAKE_EDIT
if (n->flag & NUM_FAKE_EDITED) {
return true;
}
#endif
for (i = 0; i <= n->idx_max; i++) {
if (n->val_flag[i] & NUM_EDITED) {
return true;
}
}
return false;
}
bool applyNumInput(NumInput *n, float *vec)
{
short i, j;
float val;
if (hasNumInput(n)) {
for (j = 0; j <= n->idx_max; j++) {
#ifdef USE_FAKE_EDIT
if (n->flag & NUM_FAKE_EDITED) {
val = n->val[j];
}
else
#endif
{
/* if AFFECTALL and no number typed and cursor not on number, use first number */
i = (n->flag & NUM_AFFECT_ALL && n->idx != j && !(n->val_flag[j] & NUM_EDITED)) ? 0 : j;
val = (!(n->val_flag[i] & NUM_EDITED) && n->val_flag[i] & NUM_NULL_ONE) ? 1.0f : n->val[i];
if (n->val_flag[i] & NUM_NO_NEGATIVE && val < 0.0f) {
val = 0.0f;
}
if (n->val_flag[i] & NUM_NO_FRACTION && val != floorf(val)) {
val = floorf(val + 0.5f);
if (n->val_flag[i] & NUM_NO_ZERO && val == 0.0f) {
val = 1.0f;
}
}
else if (n->val_flag[i] & NUM_NO_ZERO && val == 0.0f) {
val = 0.0001f;
}
}
vec[j] = val;
}
#ifdef USE_FAKE_EDIT
n->flag &= ~NUM_FAKE_EDITED;
#endif
return true;
}
/* Else, we set the 'org' values for numinput! */
for (j = 0; j <= n->idx_max; j++) {
n->val[j] = n->val_org[j] = vec[j];
}
return false;
}
static void value_to_editstr(NumInput *n, int idx)
{
const int prec = 6; /* editing, higher precision needed. */
n->str_cur = BKE_unit_value_as_string_adaptive(n->str,
NUM_STR_REP_LEN,
double(n->val[idx]),
prec,
n->unit_sys,
n->unit_type[idx],
true,
false,
true);
}
static bool editstr_insert_at_cursor(NumInput *n, const char *buf, const int buf_len)
{
int cur = n->str_cur;
int len = strlen(&n->str[cur]) + 1; /* +1 for the trailing '\0'. */
int n_cur = cur + buf_len;
if (n_cur + len >= NUM_STR_REP_LEN) {
return false;
}
memmove(&n->str[n_cur], &n->str[cur], len);
memcpy(&n->str[cur], buf, sizeof(char) * buf_len);
n->str_cur = n_cur;
return true;
}
bool user_string_to_number(bContext *C,
const char *str,
const UnitSettings &unit,
int type,
double *r_value,
double *r_value_no_units,
const bool use_single_line_error,
char **r_error)
{
#ifdef WITH_PYTHON
BPy_RunErrInfo err_info{};
err_info.use_single_line_error = use_single_line_error;
err_info.r_string = r_error;
const double unit_scale = BKE_unit_value_scale(unit, type, 1.0);
if (BKE_unit_string_contains_unit(str, type)) {
char str_unit_convert[256];
STRNCPY_UTF8(str_unit_convert, str);
BKE_unit_replace_string(
str_unit_convert, sizeof(str_unit_convert), str, unit_scale, unit.system, type);
bool success = BPY_run_string_as_number(C, nullptr, str_unit_convert, &err_info, r_value);
if (r_value_no_units) {
/* When explicit units are in the string, the pre-scale value isn't directly recoverable.
* Store the post-conversion value; callers relying on integer detection will naturally
* reject non-integer results (e.g. "90deg" evaluates to ~1.5708 radians). */
*r_value_no_units = *r_value;
}
return success;
}
bool success = BPY_run_string_as_number(C, nullptr, str, &err_info, r_value);
if (r_value_no_units) {
*r_value_no_units = *r_value;
}
*r_value = BKE_unit_apply_preferred_unit(unit, type, *r_value);
*r_value /= unit_scale;
return success;
#else
UNUSED_VARS(C, unit, type, use_single_line_error, r_error);
*r_value = atof(str);
if (r_value_no_units) {
*r_value_no_units = *r_value;
}
return true;
#endif
}
static bool editstr_is_simple_numinput(const char ascii)
{
if (ascii >= '0' && ascii <= '9') {
return true;
}
if (ascii == '.') {
return true;
}
return false;
}
bool handleNumInput(bContext *C, NumInput *n, const wmEvent *event)
{
const char *utf8_buf = nullptr;
const char event_ascii = WM_event_utf8_to_ascii(event);
char ascii[2] = {'\0', '\0'};
bool updated = false;
short idx = n->idx, idx_max = n->idx_max;
short dir = STRCUR_DIR_NEXT, mode = STRCUR_JUMP_NONE;
int cur;
#ifdef USE_FAKE_EDIT
if (U.flag & USER_FLAG_NUMINPUT_ADVANCED)
#endif
{
if (((event->modifier & (KM_CTRL | KM_ALT)) == 0) && (event_ascii != '\0') &&
strchr("01234567890@%^&*-+/{}()[]<>.|", event_ascii))
{
if (!(n->flag & NUM_EDIT_FULL)) {
n->flag |= NUM_EDITED;
n->flag |= NUM_EDIT_FULL;
n->val_flag[idx] |= NUM_EDITED;
}
}
}
#ifdef USE_FAKE_EDIT
/* XXX Hack around keyboards without direct access to '=' nor '*'... */
if (ELEM(event_ascii, '=', '*')) {
if (!(n->flag & NUM_EDIT_FULL)) {
n->flag |= NUM_EDIT_FULL;
n->val_flag[idx] |= NUM_EDITED;
return true;
}
if (event->modifier & KM_CTRL) {
n->flag &= ~NUM_EDIT_FULL;
return true;
}
}
#endif
switch (event->type) {
case EVT_MODAL_MAP:
if (ELEM(event->val, NUM_MODAL_INCREMENT_UP, NUM_MODAL_INCREMENT_DOWN)) {
n->val[idx] += (event->val == NUM_MODAL_INCREMENT_UP) ? n->val_inc[idx] : -n->val_inc[idx];
value_to_editstr(n, idx);
n->val_flag[idx] |= NUM_EDITED;
updated = true;
}
else {
/* might be a char too... */
utf8_buf = event->utf8_buf;
ascii[0] = event_ascii;
}
break;
case EVT_BACKSPACEKEY:
/* Part specific to backspace... */
if (!(n->val_flag[idx] & NUM_EDITED)) {
copy_v3_v3(n->val, n->val_org);
n->val_flag[0] &= ~NUM_EDITED;
n->val_flag[1] &= ~NUM_EDITED;
n->val_flag[2] &= ~NUM_EDITED;
#ifdef USE_FAKE_EDIT
n->flag |= NUM_FAKE_EDITED;
#else
n->flag |= NUM_EDIT_FULL;
#endif
updated = true;
break;
}
else if ((event->modifier & KM_SHIFT) || !n->str[0]) {
n->val[idx] = n->val_org[idx];
n->val_flag[idx] &= ~NUM_EDITED;
n->str[0] = '\0';
n->str_cur = 0;
updated = true;
break;
}
/* Else, common behavior with DELKEY,
* only difference is remove char(s) before/after the cursor. */
dir = STRCUR_DIR_PREV;
ATTR_FALLTHROUGH;
case EVT_DELKEY:
if ((n->val_flag[idx] & NUM_EDITED) && n->str[0]) {
int t_cur = cur = n->str_cur;
if (event->modifier & KM_CTRL) {
mode = STRCUR_JUMP_DELIM;
}
BLI_str_cursor_step_utf8(n->str,
strlen(n->str),
&t_cur,
eStrCursorJumpDirection(dir),
eStrCursorJumpType(mode),
true);
if (t_cur != cur) {
if (t_cur < cur) {
std::swap(t_cur, cur);
n->str_cur = cur;
}
/* +1 for trailing '\0'. */
memmove(&n->str[cur], &n->str[t_cur], strlen(&n->str[t_cur]) + 1);
updated = true;
}
if (!n->str[0]) {
n->val[idx] = n->val_org[idx];
}
}
else {
return false;
}
break;
case EVT_LEFTARROWKEY:
dir = STRCUR_DIR_PREV;
ATTR_FALLTHROUGH;
case EVT_RIGHTARROWKEY:
cur = n->str_cur;
if (event->modifier & KM_CTRL) {
mode = STRCUR_JUMP_DELIM;
}
BLI_str_cursor_step_utf8(n->str,
strlen(n->str),
&cur,
eStrCursorJumpDirection(dir),
eStrCursorJumpType(mode),
true);
if (cur != n->str_cur) {
n->str_cur = cur;
return true;
}
return false;
case EVT_HOMEKEY:
if (n->str[0]) {
n->str_cur = 0;
return true;
}
return false;
case EVT_ENDKEY:
if (n->str[0]) {
n->str_cur = strlen(n->str);
return true;
}
return false;
case EVT_TABKEY:
#ifdef USE_FAKE_EDIT
n->val_flag[idx] &= ~(NUM_NEGATE | NUM_INVERSE);
#endif
idx = (idx + idx_max + ((event->modifier & KM_CTRL) ? 0 : 2)) % (idx_max + 1);
n->idx = idx;
if (n->val_flag[idx] & NUM_EDITED) {
value_to_editstr(n, idx);
}
else {
n->str[0] = '\0';
n->str_cur = 0;
}
return true;
case EVT_PADPERIOD:
case EVT_PERIODKEY:
/* Force number-pad "." since some OS's/countries generate a comma char, see: #37992 */
ascii[0] = '.';
utf8_buf = ascii;
break;
#if 0
/* Those keys are not directly accessible in all layouts,
* preventing to generate matching events.
* So we use a hack (ASCII value) instead, see below. */
case EQUALKEY:
case PADASTERKEY:
if (!(n->flag & NUM_EDIT_FULL)) {
n->flag |= NUM_EDIT_FULL;
n->val_flag[idx] |= NUM_EDITED;
return true;
}
else if (event->modifier & KM_CTRL) {
n->flag &= ~NUM_EDIT_FULL;
return true;
}
break;
#endif
#ifdef USE_FAKE_EDIT
case EVT_PADMINUS:
case EVT_MINUSKEY:
if ((event->modifier & KM_CTRL) || !(n->flag & NUM_EDIT_FULL)) {
n->val_flag[idx] ^= NUM_NEGATE;
updated = true;
}
break;
case EVT_PADSLASHKEY:
case EVT_SLASHKEY:
if ((event->modifier & KM_CTRL) || !(n->flag & NUM_EDIT_FULL)) {
n->val_flag[idx] ^= NUM_INVERSE;
updated = true;
}
break;
#endif
case EVT_CKEY:
if (event->modifier & KM_CTRL) {
/* Copy current `str` to the copy/paste buffer. */
WM_clipboard_text_set(n->str, false);
updated = true;
}
break;
case EVT_VKEY:
if (event->modifier & KM_CTRL) {
/* extract the first line from the clipboard */
int pbuf_len;
char *pbuf = WM_clipboard_text_get_firstline(false, true, &pbuf_len);
if (pbuf) {
const bool success = editstr_insert_at_cursor(n, pbuf, pbuf_len);
MEM_delete(pbuf);
if (!success) {
return false;
}
n->val_flag[idx] |= NUM_EDITED;
}
updated = true;
}
break;
default:
break;
}
if (!updated && !utf8_buf && event->utf8_buf[0]) {
utf8_buf = event->utf8_buf;
ascii[0] = event_ascii;
}
/* Up to this point, if we have a ctrl modifier, skip.
* This allows to still access most of modals' shortcuts even in numinput mode.
*/
if (!updated && (event->modifier & KM_CTRL)) {
return false;
}
if ((!utf8_buf || !utf8_buf[0]) && ascii[0]) {
/* Fall back to ascii. */
utf8_buf = ascii;
}
if (utf8_buf && utf8_buf[0]) {
if (!(n->flag & NUM_EDIT_FULL)) {
/* In simple edit mode, we only keep a few chars as valid! */
/* no need to decode unicode, ASCII is first char only. */
if (!editstr_is_simple_numinput(utf8_buf[0])) {
return false;
}
}
const int utf8_buf_len = BLI_str_utf8_size_or_error(utf8_buf);
BLI_assert(utf8_buf_len != -1);
if (!editstr_insert_at_cursor(n, utf8_buf, utf8_buf_len)) {
return false;
}
n->val_flag[idx] |= NUM_EDITED;
}
else if (!updated) {
return false;
}
/* At this point, our value has changed, try to interpret it with python
* (if str is not empty!). */
if (n->str[0]) {
const float val_prev = n->val[idx];
Scene *sce = CTX_data_scene(C);
char *error = nullptr;
double val = 0.0;
double val_no_units = 0.0;
int success = user_string_to_number(
C, n->str, sce->unit, n->unit_type[idx], &val, &val_no_units, false, &error);
if (error) {
ReportList *reports = CTX_wm_reports(C);
printf("%s\n", error);
BKE_report(reports, RPT_ERROR, error);
BKE_report(reports, RPT_ERROR, "Numeric input evaluation");
MEM_delete(error);
}
if (success) {
n->val[idx] = float(val);
n->val_flag[idx] &= ~NUM_INVALID;
/* Store the pre-unit-scale value and check if it's an integer. */
n->val_no_units[idx] = val_no_units;
if (ED_numinput_double_is_int(val_no_units)) {
n->val_flag[idx] |= NUM_INT_INPUT_VALUE;
}
else {
n->val_flag[idx] &= ~NUM_INT_INPUT_VALUE;
}
}
else {
n->val_flag[idx] |= NUM_INVALID;
n->val_flag[idx] &= ~NUM_INT_INPUT_VALUE;
}
#ifdef USE_FAKE_EDIT
if (n->val_flag[idx] & NUM_NEGATE) {
n->val[idx] = -n->val[idx];
n->val_no_units[idx] = -n->val_no_units[idx];
}
if (n->val_flag[idx] & NUM_INVERSE) {
val = n->val[idx];
/* If we invert on radians when user is in degrees,
* you get unexpected results... See #53463. */
if (!n->unit_use_radians && n->unit_type[idx] == B_UNIT_ROTATION) {
val = RAD2DEG(val);
}
val = 1.0 / val;
if (!n->unit_use_radians && n->unit_type[idx] == B_UNIT_ROTATION) {
val = DEG2RAD(val);
}
n->val[idx] = float(val);
n->val_no_units[idx] = 1.0 / n->val_no_units[idx];
/* Inverse transforms the value to 1/x, invalidating the integer property. */
n->val_flag[idx] &= ~NUM_INT_INPUT_VALUE;
}
#endif
if (UNLIKELY(!isfinite(n->val[idx]))) {
n->val[idx] = val_prev;
n->val_flag[idx] |= NUM_INVALID;
}
}
/* REDRAW SINCE NUMBERS HAVE CHANGED */
return true;
}
} // namespace blender

View File

@@ -0,0 +1,223 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edutil
*/
#include <cfloat>
#include <cmath>
#include <cstring>
#include "BLI_kdtree.hh"
#include "BLT_translation.hh"
#include "DNA_windowmanager_types.h"
#include "RNA_access.hh"
#include "WM_types.hh"
#include "ED_select_utils.hh"
namespace blender {
int ED_select_op_action(const eSelectOp sel_op, const bool is_select, const bool is_inside)
{
switch (sel_op) {
case SEL_OP_ADD:
return (!is_select && (is_inside)) ? 1 : -1;
case SEL_OP_SUB:
return (is_select && is_inside) ? 0 : -1;
case SEL_OP_SET:
return is_inside ? 1 : 0;
case SEL_OP_AND:
return (is_select && is_inside) ? -1 : (is_select ? 0 : -1);
case SEL_OP_XOR:
return (is_select && is_inside) ? 0 : ((!is_select && is_inside) ? 1 : -1);
}
BLI_assert_msg(0, "invalid sel_op");
return -1;
}
int ED_select_op_action_deselected(const eSelectOp sel_op,
const bool is_select,
const bool is_inside)
{
switch (sel_op) {
case SEL_OP_ADD:
return (!is_select && is_inside) ? 1 : -1;
case SEL_OP_SUB:
return (is_select && is_inside) ? 0 : -1;
case SEL_OP_SET:
/* Only difference w/ function above. */
return is_inside ? 1 : -1;
case SEL_OP_AND:
return (is_select && is_inside) ? -1 : (is_select ? 0 : -1);
case SEL_OP_XOR:
return (is_select && is_inside) ? 0 : ((!is_select && is_inside) ? 1 : -1);
}
BLI_assert_msg(0, "invalid sel_op");
return -1;
}
eSelectOp ED_select_op_modal(const eSelectOp sel_op, const bool is_first)
{
if (sel_op == SEL_OP_SET) {
if (is_first == false) {
return SEL_OP_ADD;
}
}
return sel_op;
}
bool ED_select_similar_compare_float(const float delta,
const float thresh,
const eSimilarCmp compare)
{
BLI_assert(thresh >= 0.0f);
switch (compare) {
case SIM_CMP_EQ:
return (fabsf(delta) <= thresh);
case SIM_CMP_GT:
return ((delta + thresh) >= 0.0f);
case SIM_CMP_LT:
return ((delta - thresh) <= 0.0f);
default:
BLI_assert_unreachable();
return false;
}
}
bool ED_select_similar_compare_float_tree(const KDTree<float> *tree,
const float length,
const float thresh,
const eSimilarCmp compare)
{
BLI_assert(compare == SIM_CMP_EQ || length >= 0.0f); /* See precision note below. */
/* Length of the edge we want to compare against. */
float nearest_edge_length;
switch (compare) {
case SIM_CMP_EQ:
/* Compare to the edge closest to the current edge. */
nearest_edge_length = length;
break;
case SIM_CMP_GT:
/* Compare against the shortest edge. */
/* -FLT_MAX leads to some precision issues and the wrong edge being selected.
* For example, in a tree with 1, 2 and 3, which is stored squared as 1, 4, 9,
* it returns as the nearest length/node the "4" instead of "1". */
nearest_edge_length = -1.0f;
break;
case SIM_CMP_LT:
/* Compare against the longest edge. */
nearest_edge_length = FLT_MAX;
break;
default:
BLI_assert_unreachable();
return false;
}
KDTreeNearest<float> nearest;
if (kdtree_find_nearest<float>(tree, nearest_edge_length, &nearest) != -1) {
BLI_assert(compare == SIM_CMP_EQ || nearest.co >= 0.0f); /* See precision note above. */
float delta = length - nearest.co;
return ED_select_similar_compare_float(delta, thresh, compare);
}
return false;
}
eSelectOp ED_select_op_from_operator(PointerRNA *ptr)
{
const bool extend = RNA_boolean_get(ptr, "extend");
const bool deselect = RNA_boolean_get(ptr, "deselect");
const bool toggle = RNA_boolean_get(ptr, "toggle");
if (extend) {
return SEL_OP_ADD;
}
if (deselect) {
return SEL_OP_SUB;
}
if (toggle) {
return SEL_OP_XOR;
}
return SEL_OP_SET;
}
SelectPick_Params ED_select_pick_params_from_operator(PointerRNA *ptr)
{
SelectPick_Params params = {};
params.sel_op = ED_select_op_from_operator(ptr);
params.deselect_all = RNA_boolean_get(ptr, "deselect_all");
params.select_passthrough = RNA_boolean_get(ptr, "select_passthrough");
return params;
}
/* -------------------------------------------------------------------- */
/** \name Operator Naming Callbacks
* \{ */
std::string ED_select_pick_get_name(wmOperatorType * /*ot*/, PointerRNA *ptr)
{
PropertyRNA *prop = RNA_struct_find_property(ptr, "enumerate");
const bool enumerate = (prop && RNA_property_boolean_get(ptr, prop));
const SelectPick_Params params = ED_select_pick_params_from_operator(ptr);
switch (params.sel_op) {
case SEL_OP_ADD:
if (enumerate) {
return CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Select Extend (List)");
}
return CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Select Extend");
case SEL_OP_SUB:
if (enumerate) {
return CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Deselect (List)");
}
return CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Deselect");
case SEL_OP_XOR:
if (enumerate) {
return CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Select Toggle (List)");
}
return CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Select Toggle");
case SEL_OP_AND:
BLI_assert_unreachable();
ATTR_FALLTHROUGH;
case SEL_OP_SET:
break;
}
if (enumerate) {
return CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Select (List)");
}
return CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Select");
}
std::string ED_select_circle_get_name(wmOperatorType * /*ot*/, PointerRNA *ptr)
{
/* Matches options in #WM_operator_properties_select_operation_simple */
const eSelectOp sel_op = eSelectOp(RNA_enum_get(ptr, "mode"));
switch (sel_op) {
case SEL_OP_ADD:
return CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Circle Select (Extend)");
case SEL_OP_SUB:
return CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Circle Select (Deselect)");
case SEL_OP_XOR:
ATTR_FALLTHROUGH;
case SEL_OP_AND:
BLI_assert_unreachable();
ATTR_FALLTHROUGH;
case SEL_OP_SET:
break;
}
return CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Circle Select");
}
/** \} */
} // namespace blender