2023-05-31 16:19:06 +02:00
|
|
|
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
|
|
|
|
|
*
|
|
|
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
2009-01-13 15:18:41 +00:00
|
|
|
|
2019-02-18 08:08:12 +11:00
|
|
|
/** \file
|
|
|
|
|
* \ingroup edobj
|
2011-02-27 20:29:51 +00:00
|
|
|
*/
|
|
|
|
|
|
2023-07-22 11:27:25 +10:00
|
|
|
#include <cstring>
|
2009-01-13 15:18:41 +00:00
|
|
|
|
|
|
|
|
#ifndef WIN32
|
|
|
|
|
# include <unistd.h>
|
|
|
|
|
#else
|
|
|
|
|
# include <io.h>
|
2018-06-04 09:31:30 +02:00
|
|
|
#endif
|
2009-01-13 15:18:41 +00:00
|
|
|
|
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
|
|
2025-02-11 16:59:42 +01:00
|
|
|
#include "BLI_listbase.h"
|
Cleanup: reduce amount of math-related includes
Using ClangBuildAnalyzer on the whole Blender build, it was pointing
out that BLI_math.h is the heaviest "header hub" (i.e. non tiny file
that is included a lot).
However, there's very little (actually zero) source files in Blender
that need "all the math" (base, colors, vectors, matrices,
quaternions, intersection, interpolation, statistics, solvers and
time). A common use case is source files needing just vectors, or
just vectors & matrices, or just colors etc. Actually, 181 files
were including the whole math thing without needing it at all.
This change removes BLI_math.h completely, and instead in all the
places that need it, includes BLI_math_vector.h or BLI_math_color.h
and so on.
Change from that:
- BLI_math_color.h was included 1399 times -> now 408 (took 114.0sec
to parse -> now 36.3sec)
- BLI_simd.h 1403 -> 418 (109.7sec -> 34.9sec).
Full rebuild of Blender (Apple M1, Xcode, RelWithDebInfo) is not
affected much (342sec -> 334sec). Most of benefit would be when
someone's changing BLI_simd.h or BLI_math_color.h or similar files,
that now there's 3x fewer files result in a recompile.
Pull Request #110944
2023-08-09 11:39:20 +03:00
|
|
|
#include "BLI_math_vector.h"
|
2011-01-07 18:36:47 +00:00
|
|
|
#include "BLI_utildefines.h"
|
2009-01-13 15:18:41 +00:00
|
|
|
|
2024-02-09 18:59:42 +01:00
|
|
|
#include "BLT_translation.hh"
|
2022-07-20 10:45:23 +02:00
|
|
|
|
2009-01-13 15:18:41 +00:00
|
|
|
#include "DNA_key_types.h"
|
|
|
|
|
#include "DNA_lattice_types.h"
|
|
|
|
|
#include "DNA_mesh_types.h"
|
2010-08-04 04:01:27 +00:00
|
|
|
#include "DNA_object_types.h"
|
2009-01-13 15:18:41 +00:00
|
|
|
|
2023-11-16 11:41:55 +01:00
|
|
|
#include "BKE_context.hh"
|
2024-01-30 14:42:07 -05:00
|
|
|
#include "BKE_key.hh"
|
2023-11-16 11:41:55 +01:00
|
|
|
#include "BKE_lattice.hh"
|
2025-02-07 17:47:16 +01:00
|
|
|
#include "BKE_library.hh"
|
2023-10-09 23:41:53 +02:00
|
|
|
#include "BKE_object.hh"
|
2024-02-10 18:34:29 +01:00
|
|
|
#include "BKE_report.hh"
|
2009-01-13 15:18:41 +00:00
|
|
|
|
2023-09-22 03:18:17 +02:00
|
|
|
#include "DEG_depsgraph.hh"
|
|
|
|
|
#include "DEG_depsgraph_build.hh"
|
2017-06-08 10:14:53 +02:00
|
|
|
|
2023-07-14 20:57:28 +03:00
|
|
|
#include "ED_curve.hh"
|
|
|
|
|
#include "ED_lattice.hh"
|
2023-08-05 02:57:52 +02:00
|
|
|
#include "ED_mesh.hh"
|
|
|
|
|
#include "ED_object.hh"
|
2009-01-13 15:18:41 +00:00
|
|
|
|
2023-08-10 22:40:27 +02:00
|
|
|
#include "RNA_access.hh"
|
|
|
|
|
#include "RNA_define.hh"
|
2009-07-01 22:25:49 +00:00
|
|
|
|
2023-08-04 23:11:22 +02:00
|
|
|
#include "WM_api.hh"
|
|
|
|
|
#include "WM_types.hh"
|
2009-07-01 22:25:49 +00:00
|
|
|
|
2024-03-26 20:34:48 -04:00
|
|
|
#include "object_intern.hh"
|
2009-01-13 15:18:41 +00:00
|
|
|
|
2024-03-28 01:30:38 +01:00
|
|
|
namespace blender::ed::object {
|
|
|
|
|
|
2023-07-14 20:57:28 +03:00
|
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
|
/** \name Shape Key Lock Checks
|
|
|
|
|
* \{ */
|
|
|
|
|
|
2024-03-28 01:30:38 +01:00
|
|
|
bool shape_key_report_if_locked(const Object *obedit, ReportList *reports)
|
2023-07-14 20:57:28 +03:00
|
|
|
{
|
|
|
|
|
KeyBlock *key_block;
|
|
|
|
|
|
|
|
|
|
switch (obedit->type) {
|
|
|
|
|
case OB_MESH:
|
|
|
|
|
key_block = ED_mesh_get_edit_shape_key(static_cast<Mesh *>(obedit->data));
|
|
|
|
|
break;
|
|
|
|
|
case OB_SURF:
|
|
|
|
|
case OB_CURVES_LEGACY:
|
|
|
|
|
key_block = ED_curve_get_edit_shape_key(static_cast<Curve *>(obedit->data));
|
|
|
|
|
break;
|
|
|
|
|
case OB_LATTICE:
|
|
|
|
|
key_block = ED_lattice_get_edit_shape_key(static_cast<Lattice *>(obedit->data));
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (key_block && (key_block->flag & KEYBLOCK_LOCKED_SHAPE) != 0) {
|
|
|
|
|
if (reports) {
|
|
|
|
|
BKE_reportf(reports, RPT_ERROR, "The active shape key of %s is locked", obedit->id.name + 2);
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-28 01:30:38 +01:00
|
|
|
bool shape_key_report_if_active_locked(Object *ob, ReportList *reports)
|
2023-07-14 20:57:28 +03:00
|
|
|
{
|
|
|
|
|
const KeyBlock *kb = BKE_keyblock_from_object(ob);
|
|
|
|
|
|
|
|
|
|
if (kb && (kb->flag & KEYBLOCK_LOCKED_SHAPE) != 0) {
|
|
|
|
|
if (reports) {
|
|
|
|
|
BKE_reportf(reports, RPT_ERROR, "The active shape key of %s is locked", ob->id.name + 2);
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool object_is_any_shape_key_locked(Object *ob)
|
|
|
|
|
{
|
|
|
|
|
const Key *key = BKE_key_from_object(ob);
|
|
|
|
|
|
|
|
|
|
if (key) {
|
|
|
|
|
LISTBASE_FOREACH (const KeyBlock *, kb, &key->block) {
|
|
|
|
|
if (kb->flag & KEYBLOCK_LOCKED_SHAPE) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-28 01:30:38 +01:00
|
|
|
bool shape_key_report_if_any_locked(Object *ob, ReportList *reports)
|
2023-07-14 20:57:28 +03:00
|
|
|
{
|
|
|
|
|
if (object_is_any_shape_key_locked(ob)) {
|
|
|
|
|
if (reports) {
|
|
|
|
|
BKE_reportf(reports, RPT_ERROR, "The object %s has locked shape keys", ob->id.name + 2);
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-22 09:06:58 +02:00
|
|
|
bool shape_key_is_selected(const Object &object, const KeyBlock &kb, const int keyblock_index)
|
|
|
|
|
{
|
|
|
|
|
/* The active shape key is always considered selected. */
|
|
|
|
|
return (kb.flag & KEYBLOCK_SEL) || keyblock_index == object.shapenr - 1;
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-14 20:57:28 +03:00
|
|
|
/** \} */
|
|
|
|
|
|
2020-09-15 15:50:38 +10:00
|
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
|
/** \name Add Shape Key Function
|
|
|
|
|
* \{ */
|
2009-07-03 15:23:33 +00:00
|
|
|
|
2025-03-31 11:21:17 -04:00
|
|
|
static void object_shape_key_add(bContext *C, Object *ob, const bool from_mix)
|
2009-07-03 15:23:33 +00:00
|
|
|
{
|
2018-06-12 12:53:27 +02:00
|
|
|
Main *bmain = CTX_data_main(C);
|
2025-01-26 20:08:00 +01:00
|
|
|
KeyBlock *kb = BKE_object_shapekey_insert(bmain, ob, nullptr, from_mix);
|
|
|
|
|
if (kb) {
|
2025-08-01 15:43:31 +02:00
|
|
|
/* Shapekeys created via this operator should get default value 1.0. */
|
|
|
|
|
kb->curval = 1.0f;
|
|
|
|
|
|
2012-09-19 10:12:07 +00:00
|
|
|
Key *key = BKE_key_from_object(ob);
|
2012-04-05 05:51:26 +00:00
|
|
|
/* for absolute shape keys, new keys may not be added last */
|
|
|
|
|
ob->shapenr = BLI_findindex(&key->block, kb) + 1;
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2012-04-28 15:42:27 +00:00
|
|
|
WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob);
|
2009-12-28 15:26:36 +00:00
|
|
|
}
|
2009-07-03 15:23:33 +00:00
|
|
|
}
|
|
|
|
|
|
2020-09-15 15:50:38 +10:00
|
|
|
/** \} */
|
|
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
|
/** \name Remove Shape Key Function
|
|
|
|
|
* \{ */
|
2009-07-03 15:23:33 +00:00
|
|
|
|
2013-06-05 05:58:51 +00:00
|
|
|
static bool object_shape_key_mirror(
|
2013-06-28 17:13:09 +00:00
|
|
|
bContext *C, Object *ob, int *r_totmirr, int *r_totfail, bool use_topology)
|
2009-10-16 13:04:59 +00:00
|
|
|
{
|
|
|
|
|
KeyBlock *kb;
|
|
|
|
|
Key *key;
|
2013-06-05 05:58:51 +00:00
|
|
|
int totmirr = 0, totfail = 0;
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2013-06-05 05:58:51 +00:00
|
|
|
*r_totmirr = *r_totfail = 0;
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2012-09-19 10:12:07 +00:00
|
|
|
key = BKE_key_from_object(ob);
|
2023-07-05 20:03:41 +02:00
|
|
|
if (key == nullptr) {
|
2023-07-22 11:36:59 +10:00
|
|
|
return false;
|
2019-04-22 09:19:45 +10:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2023-07-05 20:03:41 +02:00
|
|
|
kb = static_cast<KeyBlock *>(BLI_findlink(&key->block, ob->shapenr - 1));
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2012-03-24 06:38:07 +00:00
|
|
|
if (kb) {
|
2025-04-12 17:17:24 +02:00
|
|
|
char *tag_elem = MEM_calloc_arrayN<char>(kb->totelem, "shape_key_mirror");
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2012-04-28 15:42:27 +00:00
|
|
|
if (ob->type == OB_MESH) {
|
2023-12-08 16:40:06 -05:00
|
|
|
Mesh *mesh = static_cast<Mesh *>(ob->data);
|
2012-10-12 14:35:10 +00:00
|
|
|
int i1, i2;
|
|
|
|
|
float *fp1, *fp2;
|
|
|
|
|
float tvec[3];
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2023-07-05 20:03:41 +02:00
|
|
|
ED_mesh_mirror_spatial_table_begin(ob, nullptr, nullptr);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2023-12-20 02:21:48 +01:00
|
|
|
for (i1 = 0; i1 < mesh->verts_num; i1++) {
|
2023-07-05 20:03:41 +02:00
|
|
|
i2 = mesh_get_x_mirror_vert(ob, nullptr, i1, use_topology);
|
2012-04-28 15:42:27 +00:00
|
|
|
if (i2 == i1) {
|
|
|
|
|
fp1 = ((float *)kb->data) + i1 * 3;
|
2009-10-16 16:09:57 +00:00
|
|
|
fp1[0] = -fp1[0];
|
2012-04-28 15:42:27 +00:00
|
|
|
tag_elem[i1] = 1;
|
2013-06-05 05:58:51 +00:00
|
|
|
totmirr++;
|
2009-10-16 16:09:57 +00:00
|
|
|
}
|
2012-03-24 06:38:07 +00:00
|
|
|
else if (i2 != -1) {
|
2012-04-28 15:42:27 +00:00
|
|
|
if (tag_elem[i1] == 0 && tag_elem[i2] == 0) {
|
|
|
|
|
fp1 = ((float *)kb->data) + i1 * 3;
|
|
|
|
|
fp2 = ((float *)kb->data) + i2 * 3;
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2012-04-28 15:42:27 +00:00
|
|
|
copy_v3_v3(tvec, fp1);
|
|
|
|
|
copy_v3_v3(fp1, fp2);
|
|
|
|
|
copy_v3_v3(fp2, tvec);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2009-10-16 13:04:59 +00:00
|
|
|
/* flip x axis */
|
|
|
|
|
fp1[0] = -fp1[0];
|
|
|
|
|
fp2[0] = -fp2[0];
|
2013-06-05 05:58:51 +00:00
|
|
|
totmirr++;
|
2009-10-16 13:04:59 +00:00
|
|
|
}
|
2012-04-28 15:42:27 +00:00
|
|
|
tag_elem[i1] = tag_elem[i2] = 1;
|
2009-10-16 13:04:59 +00:00
|
|
|
}
|
2013-06-05 05:58:51 +00:00
|
|
|
else {
|
|
|
|
|
totfail++;
|
2019-04-17 06:17:24 +02:00
|
|
|
}
|
2013-06-05 05:58:51 +00:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2020-04-03 21:47:56 +11:00
|
|
|
ED_mesh_mirror_spatial_table_end(ob);
|
2009-10-16 13:04:59 +00:00
|
|
|
}
|
2011-06-06 06:40:09 +00:00
|
|
|
else if (ob->type == OB_LATTICE) {
|
2024-05-02 16:11:03 +10:00
|
|
|
const Lattice *lt = static_cast<const Lattice *>(ob->data);
|
2011-06-06 06:40:09 +00:00
|
|
|
int i1, i2;
|
|
|
|
|
float *fp1, *fp2;
|
|
|
|
|
int u, v, w;
|
|
|
|
|
/* half but found up odd value */
|
2012-02-27 10:35:39 +00:00
|
|
|
const int pntsu_half = (lt->pntsu / 2) + (lt->pntsu % 2);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2023-07-07 15:11:19 +10:00
|
|
|
/* Currently edit-mode isn't supported by mesh so ignore here for now too. */
|
|
|
|
|
#if 0
|
|
|
|
|
if (lt->editlatt) {
|
|
|
|
|
lt = lt->editlatt->latt;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2012-04-28 15:42:27 +00:00
|
|
|
for (w = 0; w < lt->pntsw; w++) {
|
|
|
|
|
for (v = 0; v < lt->pntsv; v++) {
|
|
|
|
|
for (u = 0; u < pntsu_half; u++) {
|
|
|
|
|
int u_inv = (lt->pntsu - 1) - u;
|
2011-06-06 06:40:09 +00:00
|
|
|
float tvec[3];
|
2012-03-24 06:38:07 +00:00
|
|
|
if (u == u_inv) {
|
2013-06-24 13:45:35 +00:00
|
|
|
i1 = BKE_lattice_index_from_uvw(lt, u, v, w);
|
2012-04-28 15:42:27 +00:00
|
|
|
fp1 = ((float *)kb->data) + i1 * 3;
|
|
|
|
|
fp1[0] = -fp1[0];
|
2013-06-05 05:58:51 +00:00
|
|
|
totmirr++;
|
2011-06-06 06:40:09 +00:00
|
|
|
}
|
|
|
|
|
else {
|
2013-06-24 13:45:35 +00:00
|
|
|
i1 = BKE_lattice_index_from_uvw(lt, u, v, w);
|
|
|
|
|
i2 = BKE_lattice_index_from_uvw(lt, u_inv, v, w);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2012-04-28 15:42:27 +00:00
|
|
|
fp1 = ((float *)kb->data) + i1 * 3;
|
|
|
|
|
fp2 = ((float *)kb->data) + i2 * 3;
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2011-06-06 06:40:09 +00:00
|
|
|
copy_v3_v3(tvec, fp1);
|
|
|
|
|
copy_v3_v3(fp1, fp2);
|
|
|
|
|
copy_v3_v3(fp2, tvec);
|
2012-04-28 15:42:27 +00:00
|
|
|
fp1[0] = -fp1[0];
|
|
|
|
|
fp2[0] = -fp2[0];
|
2013-06-05 05:58:51 +00:00
|
|
|
totmirr++;
|
2011-06-06 06:40:09 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2009-10-16 13:04:59 +00:00
|
|
|
MEM_freeN(tag_elem);
|
|
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2013-06-05 05:58:51 +00:00
|
|
|
*r_totmirr = totmirr;
|
|
|
|
|
*r_totfail = totfail;
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2018-12-06 17:52:37 +01:00
|
|
|
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
|
2012-04-28 15:42:27 +00:00
|
|
|
WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2023-07-22 11:36:59 +10:00
|
|
|
return true;
|
2009-10-16 13:04:59 +00:00
|
|
|
}
|
|
|
|
|
|
2020-09-15 15:50:38 +10:00
|
|
|
/** \} */
|
|
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
|
/** \name Shared Poll Functions
|
|
|
|
|
* \{ */
|
2009-07-01 22:25:49 +00:00
|
|
|
|
2020-06-30 17:51:41 +02:00
|
|
|
static bool shape_key_poll(bContext *C)
|
2009-10-22 16:35:51 +00:00
|
|
|
{
|
2024-03-28 01:30:38 +01:00
|
|
|
Object *ob = context_object(C);
|
2023-07-05 20:03:41 +02:00
|
|
|
ID *data = static_cast<ID *>((ob) ? ob->data : nullptr);
|
2020-06-30 17:51:41 +02:00
|
|
|
|
2024-05-16 14:53:09 +02:00
|
|
|
return (ob != nullptr && ID_IS_EDITABLE(ob) && !ID_IS_OVERRIDE_LIBRARY(ob) && data != nullptr &&
|
|
|
|
|
ID_IS_EDITABLE(data) && !ID_IS_OVERRIDE_LIBRARY(data));
|
2009-10-22 16:35:51 +00:00
|
|
|
}
|
|
|
|
|
|
2023-07-14 20:57:28 +03:00
|
|
|
static bool shape_key_exists_poll(bContext *C)
|
|
|
|
|
{
|
2024-03-28 01:30:38 +01:00
|
|
|
Object *ob = context_object(C);
|
2023-07-14 20:57:28 +03:00
|
|
|
|
|
|
|
|
return (shape_key_poll(C) &&
|
|
|
|
|
/* check a keyblock exists */
|
|
|
|
|
(BKE_keyblock_from_object(ob) != nullptr));
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-30 17:51:41 +02:00
|
|
|
static bool shape_key_mode_poll(bContext *C)
|
2013-10-14 21:03:18 +00:00
|
|
|
{
|
2024-03-28 01:30:38 +01:00
|
|
|
Object *ob = context_object(C);
|
2013-10-14 21:03:18 +00:00
|
|
|
|
2020-06-30 17:51:41 +02:00
|
|
|
return (shape_key_poll(C) && ob->mode != OB_MODE_EDIT);
|
2013-10-14 21:03:18 +00:00
|
|
|
}
|
|
|
|
|
|
2020-06-30 17:51:41 +02:00
|
|
|
static bool shape_key_mode_exists_poll(bContext *C)
|
2014-10-21 11:59:14 +02:00
|
|
|
{
|
2024-03-28 01:30:38 +01:00
|
|
|
Object *ob = context_object(C);
|
2014-10-21 11:59:14 +02:00
|
|
|
|
2020-06-30 17:51:41 +02:00
|
|
|
return (shape_key_mode_poll(C) &&
|
|
|
|
|
/* check a keyblock exists */
|
2023-07-05 20:03:41 +02:00
|
|
|
(BKE_keyblock_from_object(ob) != nullptr));
|
2014-10-21 11:59:14 +02:00
|
|
|
}
|
|
|
|
|
|
2020-06-30 17:51:41 +02:00
|
|
|
static bool shape_key_move_poll(bContext *C)
|
2.5
Notifiers
---------
Various fixes for wrong use of notifiers, and some new notifiers
to make things a bit more clear and consistent, with two notable
changes:
* Geometry changes are now done with NC_GEOM, rather than
NC_OBJECT|ND_GEOM_, so an object does need to be available.
* Space data now use NC_SPACE|ND_SPACE_*, instead of data
notifiers or even NC_WINDOW in some cases. Note that NC_SPACE
should only be used for notifying about changes in space data,
we don't want to go back to allqueue(REDRAW..).
Depsgraph
---------
The dependency graph now has a different flush call:
DAG_object_flush_update(scene, ob, flag)
is replaced by:
DAG_id_flush_update(id, flag)
It still works basically the same, one difference is that it now
also accepts object data (e.g. Mesh), again to avoid requiring an
Object to be available. Other ID types will simply do nothing at
the moment.
Docs
----
I made some guidelines for how/when to do which kinds of updates
and notifiers. I can't specify totally exact how to make these
decisions, but these are basically the guidelines I use. So, new
and updated docs are here:
http://wiki.blender.org/index.php/BlenderDev/Blender2.5/NotifiersUpdates
http://wiki.blender.org/index.php/BlenderDev/Blender2.5/DataNotifiers
2009-09-04 20:51:09 +00:00
|
|
|
{
|
2020-06-30 17:51:41 +02:00
|
|
|
/* Same as shape_key_mode_exists_poll above, but ensure we have at least two shapes! */
|
2024-03-28 01:30:38 +01:00
|
|
|
Object *ob = context_object(C);
|
2020-06-30 17:51:41 +02:00
|
|
|
Key *key = BKE_key_from_object(ob);
|
|
|
|
|
|
2023-07-05 20:03:41 +02:00
|
|
|
return (shape_key_mode_poll(C) && key != nullptr && key->totkey > 1);
|
2.5
Notifiers
---------
Various fixes for wrong use of notifiers, and some new notifiers
to make things a bit more clear and consistent, with two notable
changes:
* Geometry changes are now done with NC_GEOM, rather than
NC_OBJECT|ND_GEOM_, so an object does need to be available.
* Space data now use NC_SPACE|ND_SPACE_*, instead of data
notifiers or even NC_WINDOW in some cases. Note that NC_SPACE
should only be used for notifying about changes in space data,
we don't want to go back to allqueue(REDRAW..).
Depsgraph
---------
The dependency graph now has a different flush call:
DAG_object_flush_update(scene, ob, flag)
is replaced by:
DAG_id_flush_update(id, flag)
It still works basically the same, one difference is that it now
also accepts object data (e.g. Mesh), again to avoid requiring an
Object to be available. Other ID types will simply do nothing at
the moment.
Docs
----
I made some guidelines for how/when to do which kinds of updates
and notifiers. I can't specify totally exact how to make these
decisions, but these are basically the guidelines I use. So, new
and updated docs are here:
http://wiki.blender.org/index.php/BlenderDev/Blender2.5/NotifiersUpdates
http://wiki.blender.org/index.php/BlenderDev/Blender2.5/DataNotifiers
2009-09-04 20:51:09 +00:00
|
|
|
}
|
|
|
|
|
|
2020-09-15 15:50:38 +10:00
|
|
|
/** \} */
|
|
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
|
/** \name Shape Key Add Operator
|
|
|
|
|
* \{ */
|
|
|
|
|
|
2025-03-20 21:11:06 +00:00
|
|
|
static wmOperatorStatus shape_key_add_exec(bContext *C, wmOperator *op)
|
2009-07-01 22:25:49 +00:00
|
|
|
{
|
2024-03-28 01:30:38 +01:00
|
|
|
Object *ob = context_object(C);
|
2014-02-03 18:55:59 +11:00
|
|
|
const bool from_mix = RNA_boolean_get(op->ptr, "from_mix");
|
2009-07-01 22:25:49 +00:00
|
|
|
|
2025-03-31 11:21:17 -04:00
|
|
|
object_shape_key_add(C, ob, from_mix);
|
2009-12-28 18:03:04 +00:00
|
|
|
|
2018-12-06 17:52:37 +01:00
|
|
|
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
|
2017-06-08 10:14:53 +02:00
|
|
|
DEG_relations_tag_update(CTX_data_main(C));
|
2015-03-20 15:50:56 +11:00
|
|
|
|
2009-07-01 22:25:49 +00:00
|
|
|
return OPERATOR_FINISHED;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OBJECT_OT_shape_key_add(wmOperatorType *ot)
|
|
|
|
|
{
|
|
|
|
|
/* identifiers */
|
2012-03-22 07:26:09 +00:00
|
|
|
ot->name = "Add Shape Key";
|
|
|
|
|
ot->idname = "OBJECT_OT_shape_key_add";
|
|
|
|
|
ot->description = "Add shape key to the object";
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2025-05-17 09:18:03 +10:00
|
|
|
/* API callbacks. */
|
2012-03-22 07:26:09 +00:00
|
|
|
ot->poll = shape_key_mode_poll;
|
|
|
|
|
ot->exec = shape_key_add_exec;
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2009-07-01 22:25:49 +00:00
|
|
|
/* flags */
|
2012-04-28 15:42:27 +00:00
|
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2009-12-28 15:26:36 +00:00
|
|
|
/* properties */
|
Fix T39897: shape keys created while the Relative checkbox is unchecked start out with frame=0
So! First, frame for absolute shape keys: never allow a new key to have the same pos as an
existing one (this does not make sense). This way, the two workflows are possible (create
all keys and then animate ctime, or animate ctime and then create keys where you need them).
Also, fixed UIList for shapekeys, the "absolute" test was wrong, and better to show frame
value, even though not editable, than nothing in case of absolute keys.
And finally, add getter to RNA 'frame' readonly value, so that we output real frame values,
and not dummy internal ones (which are /100) in our API.
2014-05-18 22:05:21 +02:00
|
|
|
RNA_def_boolean(ot->srna,
|
|
|
|
|
"from_mix",
|
|
|
|
|
true,
|
|
|
|
|
"From Mix",
|
|
|
|
|
"Create the new shape key from the existing mix of keys");
|
2009-07-01 22:25:49 +00:00
|
|
|
}
|
|
|
|
|
|
2020-09-15 15:50:38 +10:00
|
|
|
/** \} */
|
|
|
|
|
|
2025-04-01 18:24:00 +02:00
|
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
|
/** \name Shape Key Duplicate Operator
|
|
|
|
|
* \{ */
|
|
|
|
|
|
|
|
|
|
static wmOperatorStatus shape_key_copy_exec(bContext *C, wmOperator * /*op*/)
|
|
|
|
|
{
|
|
|
|
|
Object *ob = context_object(C);
|
|
|
|
|
Key *key = BKE_key_from_object(ob);
|
2025-04-25 12:28:37 -04:00
|
|
|
KeyBlock *kb_src = BKE_keyblock_from_object(ob);
|
|
|
|
|
KeyBlock *kb_new = BKE_keyblock_duplicate(key, kb_src);
|
|
|
|
|
ob->shapenr = BLI_findindex(&key->block, kb_new) + 1;
|
2025-04-01 18:24:00 +02:00
|
|
|
WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob);
|
|
|
|
|
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
|
|
|
|
|
DEG_relations_tag_update(CTX_data_main(C));
|
|
|
|
|
return OPERATOR_FINISHED;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OBJECT_OT_shape_key_copy(wmOperatorType *ot)
|
|
|
|
|
{
|
|
|
|
|
ot->name = "Duplicate Shape Key";
|
|
|
|
|
ot->idname = "OBJECT_OT_shape_key_copy";
|
2025-05-19 22:12:17 +02:00
|
|
|
ot->description = "Duplicate the active shape key";
|
2025-04-01 18:24:00 +02:00
|
|
|
|
|
|
|
|
ot->poll = shape_key_mode_exists_poll;
|
|
|
|
|
ot->exec = shape_key_copy_exec;
|
|
|
|
|
|
|
|
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** \} */
|
|
|
|
|
|
2020-09-15 15:50:38 +10:00
|
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
|
/** \name Shape Key Remove Operator
|
|
|
|
|
* \{ */
|
|
|
|
|
|
2025-03-20 21:11:06 +00:00
|
|
|
static wmOperatorStatus shape_key_remove_exec(bContext *C, wmOperator *op)
|
2009-07-01 22:25:49 +00:00
|
|
|
{
|
2013-06-05 06:34:18 +00:00
|
|
|
Main *bmain = CTX_data_main(C);
|
2024-03-28 01:30:38 +01:00
|
|
|
Object *ob = context_object(C);
|
2013-11-26 06:39:14 +11:00
|
|
|
bool changed = false;
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2013-06-05 06:34:18 +00:00
|
|
|
if (RNA_boolean_get(op->ptr, "all")) {
|
2024-03-28 01:30:38 +01:00
|
|
|
if (shape_key_report_if_any_locked(ob, op->reports)) {
|
2023-07-14 20:57:28 +03:00
|
|
|
return OPERATOR_CANCELLED;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-20 10:45:23 +02:00
|
|
|
if (RNA_boolean_get(op->ptr, "apply_mix")) {
|
2023-07-05 20:03:41 +02:00
|
|
|
float *arr = BKE_key_evaluate_object_ex(
|
|
|
|
|
ob, nullptr, nullptr, 0, static_cast<ID *>(ob->data));
|
2022-07-20 10:45:23 +02:00
|
|
|
MEM_freeN(arr);
|
|
|
|
|
}
|
2015-06-08 19:49:01 +10:00
|
|
|
changed = BKE_object_shapekey_free(bmain, ob);
|
2013-06-05 06:34:18 +00:00
|
|
|
}
|
|
|
|
|
else {
|
2025-07-25 12:49:05 +02:00
|
|
|
int num_selected_but_locked = 0;
|
2025-07-22 09:06:58 +02:00
|
|
|
/* This could be moved into a function of its own at some point. Right now it's only used here,
|
|
|
|
|
* though, since its inner structure is taylored for allowing shapekey deletion. */
|
2025-07-25 12:49:05 +02:00
|
|
|
Key &key = *BKE_key_from_object(ob);
|
|
|
|
|
LISTBASE_FOREACH_MUTABLE (KeyBlock *, kb, &key.block) {
|
|
|
|
|
/* Always try to find the keyblock again, as the previous one may have been deleted. For
|
|
|
|
|
* the same reason, ob->shapenr has to be re-evaluated on every loop iteration. */
|
|
|
|
|
const int cur_index = BLI_findindex(&key.block, kb);
|
|
|
|
|
if (!shape_key_is_selected(*ob, *kb, cur_index)) {
|
|
|
|
|
continue;
|
2025-07-22 09:06:58 +02:00
|
|
|
}
|
2025-07-25 12:49:05 +02:00
|
|
|
if (kb->flag & KEYBLOCK_LOCKED_SHAPE) {
|
2025-07-22 09:06:58 +02:00
|
|
|
num_selected_but_locked++;
|
2025-07-25 12:49:05 +02:00
|
|
|
continue;
|
2025-07-22 09:06:58 +02:00
|
|
|
}
|
|
|
|
|
|
2025-07-25 12:49:05 +02:00
|
|
|
changed |= BKE_object_shapekey_remove(bmain, ob, kb);
|
|
|
|
|
|
|
|
|
|
/* When `BKE_object_shapekey_remove()` deletes the active shapekey, the active shapekeyindex
|
|
|
|
|
* is updated as well. It usually decrements, which means that even when the same index is
|
|
|
|
|
* re-visited, we don't see the active one any more. However, when the basis key (index=0) is
|
|
|
|
|
* deleted AND there are keys remaning, the active index remains set to 0, and so every
|
|
|
|
|
* iteration sees "the active shapekey", effectively deleting all of them. */
|
|
|
|
|
if (cur_index == 0) {
|
|
|
|
|
ob->shapenr = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-07-22 09:06:58 +02:00
|
|
|
|
|
|
|
|
if (num_selected_but_locked) {
|
|
|
|
|
BKE_reportf(op->reports,
|
|
|
|
|
changed ? RPT_WARNING : RPT_ERROR,
|
|
|
|
|
"Could not delete %d locked shape key(s)",
|
|
|
|
|
num_selected_but_locked);
|
|
|
|
|
}
|
2013-06-05 06:34:18 +00:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2025-07-25 12:49:05 +02:00
|
|
|
/* Ensure that there is still a shapekey active, if there are any. See the comment above. Be
|
|
|
|
|
* extra careful here, because the deletion of the last shapekey can delete the entire Key ID,
|
|
|
|
|
* making our `key` reference (from the code above) invalid. */
|
|
|
|
|
if (ob->shapenr == 0) {
|
|
|
|
|
Key *key = BKE_key_from_object(ob);
|
|
|
|
|
if (key && key->totkey > 0) {
|
|
|
|
|
ob->shapenr = 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-26 06:39:14 +11:00
|
|
|
if (changed) {
|
2018-12-06 17:52:37 +01:00
|
|
|
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
|
2017-06-08 10:14:53 +02:00
|
|
|
DEG_relations_tag_update(CTX_data_main(C));
|
2013-06-05 06:34:18 +00:00
|
|
|
WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2013-06-05 06:34:18 +00:00
|
|
|
return OPERATOR_FINISHED;
|
|
|
|
|
}
|
2020-07-03 15:42:22 +02:00
|
|
|
return OPERATOR_CANCELLED;
|
2009-07-01 22:25:49 +00:00
|
|
|
}
|
|
|
|
|
|
2023-07-05 20:03:41 +02:00
|
|
|
static bool shape_key_remove_poll_property(const bContext * /*C*/,
|
2022-07-20 10:45:23 +02:00
|
|
|
wmOperator *op,
|
|
|
|
|
const PropertyRNA *prop)
|
|
|
|
|
{
|
|
|
|
|
const char *prop_id = RNA_property_identifier(prop);
|
|
|
|
|
const bool do_all = RNA_enum_get(op->ptr, "all");
|
|
|
|
|
|
|
|
|
|
/* Only show seed for randomize action! */
|
|
|
|
|
if (STREQ(prop_id, "apply_mix") && !do_all) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-11 19:11:27 +02:00
|
|
|
static std::string shape_key_remove_get_description(bContext * /*C*/,
|
|
|
|
|
wmOperatorType * /*ot*/,
|
|
|
|
|
PointerRNA *ptr)
|
2022-07-20 10:45:23 +02:00
|
|
|
{
|
|
|
|
|
const bool do_apply_mix = RNA_boolean_get(ptr, "apply_mix");
|
|
|
|
|
if (do_apply_mix) {
|
2023-08-11 19:11:27 +02:00
|
|
|
return TIP_("Apply current visible shape to the object data, and delete all shape keys");
|
2022-07-20 10:45:23 +02:00
|
|
|
}
|
|
|
|
|
|
2023-08-11 19:11:27 +02:00
|
|
|
return "";
|
2022-07-20 10:45:23 +02:00
|
|
|
}
|
|
|
|
|
|
2009-07-01 22:25:49 +00:00
|
|
|
void OBJECT_OT_shape_key_remove(wmOperatorType *ot)
|
|
|
|
|
{
|
|
|
|
|
/* identifiers */
|
2012-03-22 07:26:09 +00:00
|
|
|
ot->name = "Remove Shape Key";
|
|
|
|
|
ot->idname = "OBJECT_OT_shape_key_remove";
|
|
|
|
|
ot->description = "Remove shape key from the object";
|
2018-06-04 09:31:30 +02:00
|
|
|
|
2025-05-17 09:18:03 +10:00
|
|
|
/* API callbacks. */
|
2013-10-14 21:03:18 +00:00
|
|
|
ot->poll = shape_key_mode_exists_poll;
|
2012-03-22 07:26:09 +00:00
|
|
|
ot->exec = shape_key_remove_exec;
|
2022-07-20 10:45:23 +02:00
|
|
|
ot->poll_property = shape_key_remove_poll_property;
|
|
|
|
|
ot->get_description = shape_key_remove_get_description;
|
2009-07-01 22:25:49 +00:00
|
|
|
|
|
|
|
|
/* flags */
|
2012-04-28 15:42:27 +00:00
|
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
2013-06-05 06:34:18 +00:00
|
|
|
|
|
|
|
|
/* properties */
|
2022-07-20 10:45:23 +02:00
|
|
|
RNA_def_boolean(ot->srna, "all", false, "All", "Remove all shape keys");
|
|
|
|
|
RNA_def_boolean(ot->srna,
|
|
|
|
|
"apply_mix",
|
|
|
|
|
false,
|
|
|
|
|
"Apply Mix",
|
|
|
|
|
"Apply current mix of shape keys to the geometry before removing them");
|
2009-07-01 22:25:49 +00:00
|
|
|
}
|
|
|
|
|
|
2020-09-15 15:50:38 +10:00
|
|
|
/** \} */
|
|
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
|
/** \name Shape Key Clear Operator
|
|
|
|
|
* \{ */
|
|
|
|
|
|
2025-03-20 21:11:06 +00:00
|
|
|
static wmOperatorStatus shape_key_clear_exec(bContext *C, wmOperator * /*op*/)
|
2009-10-16 10:29:41 +00:00
|
|
|
{
|
2024-03-28 01:30:38 +01:00
|
|
|
Object *ob = context_object(C);
|
2012-09-19 10:12:07 +00:00
|
|
|
Key *key = BKE_key_from_object(ob);
|
2009-10-16 10:29:41 +00:00
|
|
|
|
2024-03-29 10:13:06 +11:00
|
|
|
if (!key || BLI_listbase_is_empty(&key->block)) {
|
2009-10-16 10:29:41 +00:00
|
|
|
return OPERATOR_CANCELLED;
|
2019-04-22 09:19:45 +10:00
|
|
|
}
|
2018-06-04 09:31:30 +02:00
|
|
|
|
2023-08-04 08:51:13 +10:00
|
|
|
LISTBASE_FOREACH (KeyBlock *, kb, &key->block) {
|
2024-07-05 10:53:28 +02:00
|
|
|
kb->curval = clamp_f(0.0f, kb->slidermin, kb->slidermax);
|
2019-04-22 09:19:45 +10:00
|
|
|
}
|
2009-10-16 10:29:41 +00:00
|
|
|
|
2018-12-06 17:52:37 +01:00
|
|
|
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
|
2012-04-28 15:42:27 +00:00
|
|
|
WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob);
|
2018-06-04 09:31:30 +02:00
|
|
|
|
2009-10-16 10:29:41 +00:00
|
|
|
return OPERATOR_FINISHED;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OBJECT_OT_shape_key_clear(wmOperatorType *ot)
|
|
|
|
|
{
|
|
|
|
|
/* identifiers */
|
2012-03-22 07:26:09 +00:00
|
|
|
ot->name = "Clear Shape Keys";
|
2024-07-05 10:53:28 +02:00
|
|
|
ot->description =
|
|
|
|
|
"Reset the weights of all shape keys to 0 or to the closest value respecting the limits";
|
2012-03-22 07:26:09 +00:00
|
|
|
ot->idname = "OBJECT_OT_shape_key_clear";
|
2018-06-04 09:31:30 +02:00
|
|
|
|
2025-05-17 09:18:03 +10:00
|
|
|
/* API callbacks. */
|
2012-03-22 07:26:09 +00:00
|
|
|
ot->poll = shape_key_poll;
|
|
|
|
|
ot->exec = shape_key_clear_exec;
|
2009-10-16 10:29:41 +00:00
|
|
|
|
|
|
|
|
/* flags */
|
2012-04-28 15:42:27 +00:00
|
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
2009-10-16 10:29:41 +00:00
|
|
|
}
|
|
|
|
|
|
2012-04-05 06:10:15 +00:00
|
|
|
/* starting point and step size could be optional */
|
2025-03-20 21:11:06 +00:00
|
|
|
static wmOperatorStatus shape_key_retime_exec(bContext *C, wmOperator * /*op*/)
|
2012-04-05 06:10:15 +00:00
|
|
|
{
|
2024-03-28 01:30:38 +01:00
|
|
|
Object *ob = context_object(C);
|
2012-09-19 10:12:07 +00:00
|
|
|
Key *key = BKE_key_from_object(ob);
|
2012-04-05 06:10:15 +00:00
|
|
|
float cfra = 0.0f;
|
|
|
|
|
|
2024-03-29 10:13:06 +11:00
|
|
|
if (!key || BLI_listbase_is_empty(&key->block)) {
|
2012-04-05 06:10:15 +00:00
|
|
|
return OPERATOR_CANCELLED;
|
2019-04-22 09:19:45 +10:00
|
|
|
}
|
2012-04-05 06:10:15 +00:00
|
|
|
|
2023-08-04 08:51:13 +10:00
|
|
|
LISTBASE_FOREACH (KeyBlock *, kb, &key->block) {
|
2018-07-31 18:42:22 +10:00
|
|
|
kb->pos = cfra;
|
|
|
|
|
cfra += 0.1f;
|
|
|
|
|
}
|
2012-04-05 06:10:15 +00:00
|
|
|
|
2018-12-06 17:52:37 +01:00
|
|
|
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
|
2012-04-28 15:42:27 +00:00
|
|
|
WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob);
|
2012-04-05 06:10:15 +00:00
|
|
|
|
|
|
|
|
return OPERATOR_FINISHED;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OBJECT_OT_shape_key_retime(wmOperatorType *ot)
|
|
|
|
|
{
|
|
|
|
|
/* identifiers */
|
|
|
|
|
ot->name = "Re-Time Shape Keys";
|
|
|
|
|
ot->description = "Resets the timing for absolute shape keys";
|
|
|
|
|
ot->idname = "OBJECT_OT_shape_key_retime";
|
|
|
|
|
|
2025-05-17 09:18:03 +10:00
|
|
|
/* API callbacks. */
|
2012-04-05 06:10:15 +00:00
|
|
|
ot->poll = shape_key_poll;
|
|
|
|
|
ot->exec = shape_key_retime_exec;
|
|
|
|
|
|
|
|
|
|
/* flags */
|
2012-04-28 15:42:27 +00:00
|
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
2012-04-05 06:10:15 +00:00
|
|
|
}
|
|
|
|
|
|
2020-09-15 15:50:38 +10:00
|
|
|
/** \} */
|
|
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
|
/** \name Shape Key Mirror Operator
|
|
|
|
|
* \{ */
|
|
|
|
|
|
2025-03-20 21:11:06 +00:00
|
|
|
static wmOperatorStatus shape_key_mirror_exec(bContext *C, wmOperator *op)
|
2009-10-16 13:04:59 +00:00
|
|
|
{
|
2024-03-28 01:30:38 +01:00
|
|
|
Object *ob = context_object(C);
|
2013-06-05 05:58:51 +00:00
|
|
|
int totmirr = 0, totfail = 0;
|
2013-06-28 17:13:09 +00:00
|
|
|
bool use_topology = RNA_boolean_get(op->ptr, "use_topology");
|
2009-10-16 13:04:59 +00:00
|
|
|
|
2024-03-28 01:30:38 +01:00
|
|
|
if (shape_key_report_if_active_locked(ob, op->reports)) {
|
2023-07-14 20:57:28 +03:00
|
|
|
return OPERATOR_CANCELLED;
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-22 09:19:45 +10:00
|
|
|
if (!object_shape_key_mirror(C, ob, &totmirr, &totfail, use_topology)) {
|
2009-10-16 13:04:59 +00:00
|
|
|
return OPERATOR_CANCELLED;
|
2019-04-22 09:19:45 +10:00
|
|
|
}
|
2009-10-16 13:04:59 +00:00
|
|
|
|
2013-06-05 05:58:51 +00:00
|
|
|
ED_mesh_report_mirror(op, totmirr, totfail);
|
|
|
|
|
|
2009-10-16 13:04:59 +00:00
|
|
|
return OPERATOR_FINISHED;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OBJECT_OT_shape_key_mirror(wmOperatorType *ot)
|
|
|
|
|
{
|
|
|
|
|
/* identifiers */
|
2012-03-22 07:26:09 +00:00
|
|
|
ot->name = "Mirror Shape Key";
|
|
|
|
|
ot->idname = "OBJECT_OT_shape_key_mirror";
|
2012-05-05 17:10:51 +00:00
|
|
|
ot->description = "Mirror the current shape key along the local X axis";
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2025-05-17 09:18:03 +10:00
|
|
|
/* API callbacks. */
|
2025-07-08 11:39:10 +02:00
|
|
|
ot->poll = shape_key_mode_exists_poll;
|
2012-03-22 07:26:09 +00:00
|
|
|
ot->exec = shape_key_mirror_exec;
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2009-10-16 13:04:59 +00:00
|
|
|
/* flags */
|
2012-04-28 15:42:27 +00:00
|
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2013-06-28 17:13:09 +00:00
|
|
|
/* properties */
|
|
|
|
|
RNA_def_boolean(
|
|
|
|
|
ot->srna,
|
|
|
|
|
"use_topology",
|
2023-07-22 11:36:59 +10:00
|
|
|
false,
|
2013-06-28 17:13:09 +00:00
|
|
|
"Topology Mirror",
|
|
|
|
|
"Use topology based mirroring (for when both sides of mesh have matching, unique topology)");
|
2009-10-16 13:04:59 +00:00
|
|
|
}
|
|
|
|
|
|
2020-09-15 15:50:38 +10:00
|
|
|
/** \} */
|
|
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
|
/** \name Shape Key Move (Re-Order) Operator
|
|
|
|
|
* \{ */
|
|
|
|
|
|
2025-07-30 11:42:37 +02:00
|
|
|
enum KeyBlockMove {
|
2014-10-21 11:59:14 +02:00
|
|
|
KB_MOVE_TOP = -2,
|
|
|
|
|
KB_MOVE_UP = -1,
|
|
|
|
|
KB_MOVE_DOWN = 1,
|
|
|
|
|
KB_MOVE_BOTTOM = 2,
|
|
|
|
|
};
|
|
|
|
|
|
2025-03-20 21:11:06 +00:00
|
|
|
static wmOperatorStatus shape_key_move_exec(bContext *C, wmOperator *op)
|
2009-10-21 14:33:52 +00:00
|
|
|
{
|
2024-03-28 01:30:38 +01:00
|
|
|
Object *ob = context_object(C);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2025-07-30 11:42:37 +02:00
|
|
|
const Key &key = *BKE_key_from_object(ob);
|
|
|
|
|
const KeyBlockMove type = KeyBlockMove(RNA_enum_get(op->ptr, "type"));
|
|
|
|
|
const int totkey = key.totkey;
|
|
|
|
|
int new_index = 0;
|
|
|
|
|
bool changed = false;
|
|
|
|
|
|
|
|
|
|
if (type < 0) { /* Moving upwards. */
|
|
|
|
|
/* Don't move above the position of the basis key */
|
|
|
|
|
int top_index = 1;
|
|
|
|
|
/* Start from index 1 to ignore basis key from being able to move above. */
|
|
|
|
|
for (int index = 1; index < totkey; index++) {
|
|
|
|
|
const KeyBlock &kb = *static_cast<KeyBlock *>(BLI_findlink(&key.block, index));
|
|
|
|
|
if (!shape_key_is_selected(*ob, kb, index)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
switch (type) {
|
|
|
|
|
case KB_MOVE_TOP:
|
|
|
|
|
new_index = top_index;
|
|
|
|
|
break;
|
|
|
|
|
case KB_MOVE_UP:
|
|
|
|
|
new_index = max_ii(index - 1, top_index);
|
|
|
|
|
break;
|
|
|
|
|
case KB_MOVE_BOTTOM:
|
|
|
|
|
case KB_MOVE_DOWN:
|
|
|
|
|
BLI_assert_unreachable();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
top_index++;
|
|
|
|
|
if (new_index < 0) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
changed |= BKE_keyblock_move(ob, index, new_index);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else { /* Moving downwards. */
|
|
|
|
|
int bottom_index = totkey - 1;
|
|
|
|
|
/* Skip basis key to prevent it from moving downwards. */
|
|
|
|
|
for (int index = totkey - 1; index >= 1; index--) {
|
|
|
|
|
const KeyBlock &kb = *static_cast<KeyBlock *>(BLI_findlink(&key.block, index));
|
|
|
|
|
if (!shape_key_is_selected(*ob, kb, index)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
switch (type) {
|
|
|
|
|
case KB_MOVE_BOTTOM:
|
|
|
|
|
new_index = bottom_index;
|
|
|
|
|
break;
|
|
|
|
|
case KB_MOVE_DOWN:
|
|
|
|
|
new_index = min_ii(index + 1, bottom_index);
|
|
|
|
|
break;
|
|
|
|
|
case KB_MOVE_TOP:
|
|
|
|
|
case KB_MOVE_UP:
|
|
|
|
|
BLI_assert_unreachable();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
bottom_index--;
|
|
|
|
|
changed |= BKE_keyblock_move(ob, index, new_index);
|
|
|
|
|
}
|
2014-08-10 11:40:35 +02:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2025-07-30 11:42:37 +02:00
|
|
|
if (!changed) {
|
2014-10-21 11:59:14 +02:00
|
|
|
return OPERATOR_CANCELLED;
|
2013-05-09 07:02:51 +00:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2018-12-06 17:52:37 +01:00
|
|
|
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
|
2012-04-28 15:42:27 +00:00
|
|
|
WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2009-10-21 14:33:52 +00:00
|
|
|
return OPERATOR_FINISHED;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OBJECT_OT_shape_key_move(wmOperatorType *ot)
|
|
|
|
|
{
|
2017-10-18 15:07:26 +11:00
|
|
|
static const EnumPropertyItem slot_move[] = {
|
2014-10-21 11:59:14 +02:00
|
|
|
{KB_MOVE_TOP, "TOP", 0, "Top", "Top of the list"},
|
|
|
|
|
{KB_MOVE_UP, "UP", 0, "Up", ""},
|
|
|
|
|
{KB_MOVE_DOWN, "DOWN", 0, "Down", ""},
|
|
|
|
|
{KB_MOVE_BOTTOM, "BOTTOM", 0, "Bottom", "Bottom of the list"},
|
2023-07-05 20:03:41 +02:00
|
|
|
{0, nullptr, 0, nullptr, nullptr}};
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2009-10-21 14:33:52 +00:00
|
|
|
/* identifiers */
|
2012-03-22 07:26:09 +00:00
|
|
|
ot->name = "Move Shape Key";
|
|
|
|
|
ot->idname = "OBJECT_OT_shape_key_move";
|
2025-07-30 11:42:37 +02:00
|
|
|
ot->description = "Move selected shape keys up/down in the list";
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2025-05-17 09:18:03 +10:00
|
|
|
/* API callbacks. */
|
2014-10-21 11:59:14 +02:00
|
|
|
ot->poll = shape_key_move_poll;
|
2012-03-22 07:26:09 +00:00
|
|
|
ot->exec = shape_key_move_exec;
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2009-10-21 14:33:52 +00:00
|
|
|
/* flags */
|
2012-04-28 15:42:27 +00:00
|
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2009-10-21 14:33:52 +00:00
|
|
|
RNA_def_enum(ot->srna, "type", slot_move, 0, "Type", "");
|
|
|
|
|
}
|
2020-09-15 15:50:38 +10:00
|
|
|
|
|
|
|
|
/** \} */
|
2023-07-14 20:57:28 +03:00
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
|
/** \name Shape Key Lock (Unlock) Operator
|
|
|
|
|
* \{ */
|
|
|
|
|
|
|
|
|
|
enum {
|
|
|
|
|
SHAPE_KEY_LOCK,
|
|
|
|
|
SHAPE_KEY_UNLOCK,
|
|
|
|
|
};
|
|
|
|
|
|
2025-03-20 21:11:06 +00:00
|
|
|
static wmOperatorStatus shape_key_lock_exec(bContext *C, wmOperator *op)
|
2023-07-14 20:57:28 +03:00
|
|
|
{
|
|
|
|
|
Object *ob = CTX_data_active_object(C);
|
|
|
|
|
const int action = RNA_enum_get(op->ptr, "action");
|
|
|
|
|
const Key *keys = BKE_key_from_object(ob);
|
|
|
|
|
|
|
|
|
|
if (!keys || BLI_listbase_is_empty(&keys->block)) {
|
|
|
|
|
return OPERATOR_CANCELLED;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LISTBASE_FOREACH (KeyBlock *, kb, &keys->block) {
|
|
|
|
|
switch (action) {
|
|
|
|
|
case SHAPE_KEY_LOCK:
|
|
|
|
|
kb->flag |= KEYBLOCK_LOCKED_SHAPE;
|
|
|
|
|
break;
|
|
|
|
|
case SHAPE_KEY_UNLOCK:
|
|
|
|
|
kb->flag &= ~KEYBLOCK_LOCKED_SHAPE;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
BLI_assert(0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob);
|
|
|
|
|
|
|
|
|
|
return OPERATOR_FINISHED;
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-24 14:08:33 +10:00
|
|
|
static std::string shape_key_lock_get_description(bContext * /*C*/,
|
|
|
|
|
wmOperatorType * /*op*/,
|
|
|
|
|
PointerRNA *ptr)
|
2023-07-14 20:57:28 +03:00
|
|
|
{
|
2024-05-24 14:08:33 +10:00
|
|
|
const int action = RNA_enum_get(ptr, "action");
|
2023-07-14 20:57:28 +03:00
|
|
|
|
|
|
|
|
switch (action) {
|
|
|
|
|
case SHAPE_KEY_LOCK:
|
|
|
|
|
return TIP_("Lock all shape keys of the active object");
|
|
|
|
|
break;
|
|
|
|
|
case SHAPE_KEY_UNLOCK:
|
|
|
|
|
return TIP_("Unlock all shape keys of the active object");
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OBJECT_OT_shape_key_lock(wmOperatorType *ot)
|
|
|
|
|
{
|
|
|
|
|
static const EnumPropertyItem shape_key_lock_actions[] = {
|
|
|
|
|
{SHAPE_KEY_LOCK, "LOCK", 0, "Lock", "Lock all shape keys"},
|
|
|
|
|
{SHAPE_KEY_UNLOCK, "UNLOCK", 0, "Unlock", "Unlock all shape keys"},
|
|
|
|
|
{0, nullptr, 0, nullptr, nullptr},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/* identifiers */
|
|
|
|
|
ot->name = "Change the Lock On Shape Keys";
|
|
|
|
|
ot->idname = "OBJECT_OT_shape_key_lock";
|
|
|
|
|
ot->description = "Change the lock state of all shape keys of active object";
|
|
|
|
|
|
2025-05-17 09:18:03 +10:00
|
|
|
/* API callbacks. */
|
2023-07-14 20:57:28 +03:00
|
|
|
ot->poll = shape_key_exists_poll;
|
|
|
|
|
ot->exec = shape_key_lock_exec;
|
2024-05-24 14:08:33 +10:00
|
|
|
ot->get_description = shape_key_lock_get_description;
|
2023-07-14 20:57:28 +03:00
|
|
|
|
|
|
|
|
/* flags */
|
|
|
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
|
|
|
|
|
|
RNA_def_enum(ot->srna,
|
|
|
|
|
"action",
|
|
|
|
|
shape_key_lock_actions,
|
|
|
|
|
SHAPE_KEY_LOCK,
|
|
|
|
|
"Action",
|
|
|
|
|
"Lock action to execute on vertex groups");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** \} */
|
2024-03-28 01:30:38 +01:00
|
|
|
|
2025-07-28 15:28:21 +02:00
|
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
|
/** \name Shape Key Make Basis Operator
|
|
|
|
|
* \{ */
|
|
|
|
|
|
|
|
|
|
static bool shape_key_make_basis_poll(bContext *C)
|
|
|
|
|
{
|
|
|
|
|
if (!shape_key_exists_poll(C)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Object *ob = context_object(C);
|
|
|
|
|
/* 0 = nothing active, 1 = basis key active. */
|
|
|
|
|
return ob->shapenr > 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static wmOperatorStatus shape_key_make_basis_exec(bContext *C, wmOperator * /*op*/)
|
|
|
|
|
{
|
|
|
|
|
Object *ob = CTX_data_active_object(C);
|
2025-07-28 15:56:23 +02:00
|
|
|
Key *key = BKE_key_from_object(ob);
|
|
|
|
|
KeyBlock *old_basis_key = static_cast<KeyBlock *>(key->block.first);
|
2025-07-28 15:28:21 +02:00
|
|
|
|
|
|
|
|
/* Make the new basis by moving the active key to index 0. */
|
|
|
|
|
const int from_index = -1; /* Interpreted as "the active key". */
|
|
|
|
|
const int to_index = 0; /* Offset by 1 compared to ob->shapenr. */
|
|
|
|
|
const bool changed = BKE_keyblock_move(ob, from_index, to_index);
|
|
|
|
|
|
|
|
|
|
if (!changed) {
|
|
|
|
|
/* The poll function should have prevented this operator from being called
|
|
|
|
|
* on the current basis key. */
|
|
|
|
|
BLI_assert_unreachable();
|
|
|
|
|
return OPERATOR_CANCELLED;
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-28 15:56:23 +02:00
|
|
|
/* Make the old & new basis keys "Relative to" the new basis key. For the new key it doesn't
|
|
|
|
|
* matter much, as it's treated as special anyway, but keeping it relative to another key makes
|
|
|
|
|
* no sense. For the old basis key (which just became a normal key), it would otherwise still be
|
|
|
|
|
* relative to itself, effectively disabling it. */
|
|
|
|
|
KeyBlock *new_basis_key = static_cast<KeyBlock *>(key->block.first);
|
|
|
|
|
new_basis_key->relative = 0;
|
|
|
|
|
old_basis_key->relative = 0;
|
|
|
|
|
|
2025-07-28 15:28:21 +02:00
|
|
|
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
|
|
|
|
|
WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob);
|
|
|
|
|
|
|
|
|
|
return OPERATOR_FINISHED;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OBJECT_OT_shape_key_make_basis(wmOperatorType *ot)
|
|
|
|
|
{
|
|
|
|
|
/* identifiers */
|
|
|
|
|
ot->name = "Make Shape Key the Basis Key";
|
|
|
|
|
ot->idname = "OBJECT_OT_shape_key_make_basis";
|
2025-07-28 15:59:54 +02:00
|
|
|
ot->description =
|
|
|
|
|
"Make this shape key the new basis key, effectively applying it to the mesh. Note that this "
|
|
|
|
|
"applies the shape key at its 100% value";
|
2025-07-28 15:28:21 +02:00
|
|
|
|
|
|
|
|
/* API callbacks. */
|
|
|
|
|
ot->poll = shape_key_make_basis_poll;
|
|
|
|
|
ot->exec = shape_key_make_basis_exec;
|
|
|
|
|
|
|
|
|
|
/* flags */
|
|
|
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** \} */
|
|
|
|
|
|
2024-03-28 01:30:38 +01:00
|
|
|
} // namespace blender::ed::object
|