ShapeKeys: rework 'move skey' code, and add options to move to first/last position.

Main moving logic is moved to new `BKE_keyblock_move()`, which makes it available from anywhere.
In addition, move code was reworked so that it only loops once on whole keyblocks list,
and it accepts arbitrary org and dest indices, not only neighbor ones.

Partly based on work by revzin (Grigory Revzin) in his soc-2014-shapekey GSoC branch, thanks!
This commit is contained in:
Bastien Montagne 2014-10-21 11:59:14 +02:00
parent be4b2e42c6
commit 00ff9da2ee
4 changed files with 138 additions and 78 deletions

View File

@ -58,6 +58,8 @@ class MESH_MT_shape_key_specials(Menu):
layout.operator("object.shape_key_mirror", text="Mirror Shape Key (Topology)", icon='ARROW_LEFTRIGHT').use_topology = True
layout.operator("object.shape_key_add", icon='ZOOMIN', text="New Shape From Mix").from_mix = True
layout.operator("object.shape_key_remove", icon='X', text="Delete All Shapes").all = True
layout.operator("object.shape_key_move", icon='TRIA_UP_BAR', text="Move To Top").type = 'TOP'
layout.operator("object.shape_key_move", icon='TRIA_DOWN_BAR', text="Move To Bottom").type = 'BOTTOM'
class MESH_UL_vgroups(UIList):

View File

@ -97,6 +97,10 @@ float (*BKE_key_convert_to_vertcos(struct Object *ob, struct KeyBlock *kb))[3];
void BKE_key_convert_from_vertcos(struct Object *ob, struct KeyBlock *kb, float (*vertCos)[3]);
void BKE_key_convert_from_offset(struct Object *ob, struct KeyBlock *kb, float (*ofs)[3]);
/* other management */
bool BKE_keyblock_move(struct Object *ob, int org_index, int new_index);
/* key.c */
extern int slurph_opt;

View File

@ -2055,3 +2055,91 @@ void BKE_key_convert_from_offset(Object *ob, KeyBlock *kb, float (*ofs)[3])
}
}
}
/* ==========================================================*/
/** Move shape key from org_index to new_index. Safe, clamps index to valid range, updates reference keys,
* the object's active shape index, the 'frame' value in case of absolute keys, etc.
* Note indices are expected in real values (not 'fake' shapenr +1 ones).
*
* \param org_index if < 0, current object's active shape will be used as skey to move.
* \return true if something was done, else false.
*/
bool BKE_keyblock_move(Object *ob, int org_index, int new_index)
{
Key *key = BKE_key_from_object(ob);
KeyBlock *kb;
const int act_index = ob->shapenr - 1;
const int totkey = key->totkey;
int i;
bool rev, in_range = false;
if (org_index < 0) {
org_index = act_index;
}
CLAMP(new_index, 0, key->totkey - 1);
CLAMP(org_index, 0, key->totkey - 1);
if (new_index == org_index) {
return false;
}
rev = ((new_index - org_index) < 0) ? true : false;
/* We swap 'org' element with its previous/next neighbor (depending on direction of the move) repeatedly,
* until we reach final position.
* This allows us to only loop on the list once! */
for (kb = (rev ? key->block.last : key->block.first), i = (rev ? totkey - 1 : 0);
kb;
kb = (rev ? kb->prev : kb->next), rev ? i-- : i++)
{
if (i == org_index) {
in_range = true; /* Start list items swapping... */
}
else if (i == new_index) {
in_range = false; /* End list items swapping. */
}
if (in_range) {
KeyBlock *other_kb = rev ? kb->prev : kb->next;
/* Swap with previous/next list item. */
BLI_swaplinks(&key->block, kb, other_kb);
/* Swap absolute positions. */
SWAP(float, kb->pos, other_kb->pos);
kb = other_kb;
}
/* Adjust relative indices, this has to be done on the whole list! */
if (kb->relative == org_index) {
kb->relative = new_index;
}
else if (kb->relative < org_index && kb->relative >= new_index) {
/* remove after, insert before this index */
kb->relative++;
}
else if (kb->relative > org_index && kb->relative <= new_index) {
/* remove before, insert after this index */
kb->relative--;
}
}
/* Need to update active shape number if it's affected, same principle as for relative indices above. */
if (org_index == act_index) {
ob->shapenr = new_index + 1;
}
else if (act_index < org_index && act_index >= new_index) {
ob->shapenr++;
}
else if (act_index > org_index && act_index <= new_index) {
ob->shapenr--;
}
/* First key is always refkey, matches interface and BKE_key_sort */
key->refkey = key->block.first;
return true;
}

View File

@ -301,6 +301,16 @@ static int shape_key_mode_exists_poll(bContext *C)
(BKE_keyblock_from_object(ob) != NULL);
}
static int shape_key_move_poll(bContext *C)
{
/* Same as shape_key_mode_exists_poll above, but ensure we have at least two shapes! */
Object *ob = ED_object_context(C);
ID *data = (ob) ? ob->data : NULL;
Key *key = BKE_key_from_object(ob);
return (ob && !ob->id.lib && data && !data->lib && ob->mode != OB_MODE_EDIT && key && key->totkey > 1);
}
static int shape_key_poll(bContext *C)
{
Object *ob = ED_object_context(C);
@ -482,86 +492,40 @@ void OBJECT_OT_shape_key_mirror(wmOperatorType *ot)
}
enum {
KB_MOVE_TOP = -2,
KB_MOVE_UP = -1,
KB_MOVE_DOWN = 1,
KB_MOVE_BOTTOM = 2,
};
static int shape_key_move_exec(bContext *C, wmOperator *op)
{
Object *ob = ED_object_context(C);
Key *key = BKE_key_from_object(ob);
if (!key) {
return OPERATOR_CANCELLED;
Key *key = BKE_key_from_object(ob);
const int type = RNA_enum_get(op->ptr, "type");
const int totkey = key->totkey;
const int act_index = ob->shapenr - 1;
int new_index;
switch (type) {
case KB_MOVE_TOP:
/* Replace the ref key only if we're at the top already (only for relative keys) */
new_index = (ELEM(act_index, 0, 1) || key->type == KEY_NORMAL) ? 0 : 1;
break;
case KB_MOVE_BOTTOM:
new_index = totkey - 1;
break;
case KB_MOVE_UP:
case KB_MOVE_DOWN:
default:
new_index = (totkey + act_index + type) % totkey;
break;
}
{
KeyBlock *kb, *kb_other, *kb_iter;
const int type = RNA_enum_get(op->ptr, "type");
const int shape_tot = key->totkey;
const int shapenr_act = ob->shapenr - 1;
const int shapenr_swap = (shape_tot + shapenr_act + type) % shape_tot;
kb = BLI_findlink(&key->block, shapenr_act);
if (!kb || shape_tot == 1) {
return OPERATOR_CANCELLED;
}
if (type == -1) {
/* move back */
kb_other = kb->prev;
BLI_remlink(&key->block, kb);
BLI_insertlinkbefore(&key->block, kb_other, kb);
}
else {
/* move next */
kb_other = kb->next;
BLI_remlink(&key->block, kb);
BLI_insertlinkafter(&key->block, kb_other, kb);
}
ob->shapenr = shapenr_swap + 1;
/* for relative shape keys */
if (kb_other) {
for (kb_iter = key->block.first; kb_iter; kb_iter = kb_iter->next) {
if (kb_iter->relative == shapenr_act) {
kb_iter->relative = shapenr_swap;
}
else if (kb_iter->relative == shapenr_swap) {
kb_iter->relative = shapenr_act;
}
}
}
/* First key became last, or vice-versa, we have to change all keys' relative value. */
else {
for (kb_iter = key->block.first; kb_iter; kb_iter = kb_iter->next) {
if (kb_iter->relative == shapenr_act) {
kb_iter->relative = shapenr_swap;
}
else {
kb_iter->relative += type;
}
}
}
/* for absolute shape keys */
if (kb_other) {
SWAP(float, kb_other->pos, kb->pos);
}
/* First key became last, or vice-versa, we have to change all keys' pos value. */
else {
float pos = kb->pos;
if (type == -1) {
for (kb_iter = key->block.first; kb_iter; kb_iter = kb_iter->next) {
SWAP(float, kb_iter->pos, pos);
}
}
else {
for (kb_iter = key->block.last; kb_iter; kb_iter = kb_iter->prev) {
SWAP(float, kb_iter->pos, pos);
}
}
}
/* First key is refkey, matches interface and BKE_key_sort */
key->refkey = key->block.first;
if (!BKE_keyblock_move(ob, act_index, new_index)) {
return OPERATOR_CANCELLED;
}
DAG_id_tag_update(&ob->id, OB_RECALC_DATA);
@ -573,9 +537,11 @@ static int shape_key_move_exec(bContext *C, wmOperator *op)
void OBJECT_OT_shape_key_move(wmOperatorType *ot)
{
static EnumPropertyItem slot_move[] = {
{-1, "UP", 0, "Up", ""},
{1, "DOWN", 0, "Down", ""},
{0, NULL, 0, NULL, NULL}
{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"},
{ 0, NULL, 0, NULL, NULL }
};
/* identifiers */
@ -584,7 +550,7 @@ void OBJECT_OT_shape_key_move(wmOperatorType *ot)
ot->description = "Move the active shape key up/down in the list";
/* api callbacks */
ot->poll = shape_key_mode_poll;
ot->poll = shape_key_move_poll;
ot->exec = shape_key_move_exec;
/* flags */