1615 lines
47 KiB
C
1615 lines
47 KiB
C
/*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
* The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
|
|
* All rights reserved.
|
|
* Armature EditMode tools - transforms, chain based editing, and other settings
|
|
*/
|
|
|
|
/** \file
|
|
* \ingroup edarmature
|
|
*/
|
|
|
|
#include "DNA_armature_types.h"
|
|
#include "DNA_constraint_types.h"
|
|
#include "DNA_object_types.h"
|
|
#include "DNA_scene_types.h"
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "BLT_translation.h"
|
|
|
|
#include "BLI_blenlib.h"
|
|
#include "BLI_ghash.h"
|
|
#include "BLI_math.h"
|
|
|
|
#include "BKE_action.h"
|
|
#include "BKE_armature.h"
|
|
#include "BKE_constraint.h"
|
|
#include "BKE_context.h"
|
|
#include "BKE_global.h"
|
|
#include "BKE_layer.h"
|
|
#include "BKE_main.h"
|
|
#include "BKE_object.h"
|
|
#include "BKE_report.h"
|
|
|
|
#include "RNA_access.h"
|
|
#include "RNA_define.h"
|
|
|
|
#include "WM_api.h"
|
|
#include "WM_types.h"
|
|
|
|
#include "ED_armature.h"
|
|
#include "ED_outliner.h"
|
|
#include "ED_screen.h"
|
|
#include "ED_view3d.h"
|
|
|
|
#include "DEG_depsgraph.h"
|
|
|
|
#include "armature_intern.h"
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Object Tools Public API
|
|
* \{ */
|
|
|
|
/* NOTE: these functions are exported to the Object module to be called from the tools there */
|
|
|
|
/**
|
|
* See #BKE_armature_transform for object-mode transform.
|
|
*/
|
|
void ED_armature_edit_transform(bArmature *arm, const float mat[4][4], const bool do_props)
|
|
{
|
|
EditBone *ebone;
|
|
float scale = mat4_to_scale(mat); /* store the scale of the matrix here to use on envelopes */
|
|
float mat3[3][3];
|
|
|
|
copy_m3_m4(mat3, mat);
|
|
normalize_m3(mat3);
|
|
/* Do the rotations */
|
|
for (ebone = arm->edbo->first; ebone; ebone = ebone->next) {
|
|
float tmat[3][3];
|
|
|
|
/* find the current bone's roll matrix */
|
|
ED_armature_ebone_to_mat3(ebone, tmat);
|
|
|
|
/* transform the roll matrix */
|
|
mul_m3_m3m3(tmat, mat3, tmat);
|
|
|
|
/* transform the bone */
|
|
mul_m4_v3(mat, ebone->head);
|
|
mul_m4_v3(mat, ebone->tail);
|
|
|
|
/* apply the transformed roll back */
|
|
mat3_to_vec_roll(tmat, NULL, &ebone->roll);
|
|
|
|
if (do_props) {
|
|
ebone->rad_head *= scale;
|
|
ebone->rad_tail *= scale;
|
|
ebone->dist *= scale;
|
|
|
|
/* we could be smarter and scale by the matrix along the x & z axis */
|
|
ebone->xwidth *= scale;
|
|
ebone->zwidth *= scale;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ED_armature_transform(bArmature *arm, const float mat[4][4], const bool do_props)
|
|
{
|
|
if (arm->edbo) {
|
|
ED_armature_edit_transform(arm, mat, do_props);
|
|
}
|
|
else {
|
|
BKE_armature_transform(arm, mat, do_props);
|
|
}
|
|
}
|
|
|
|
/* exported for use in editors/object/ */
|
|
/* 0 == do center, 1 == center new, 2 == center cursor */
|
|
void ED_armature_origin_set(
|
|
Main *bmain, Object *ob, const float cursor[3], int centermode, int around)
|
|
{
|
|
const bool is_editmode = BKE_object_is_in_editmode(ob);
|
|
EditBone *ebone;
|
|
bArmature *arm = ob->data;
|
|
float cent[3];
|
|
|
|
/* Put the armature into edit-mode. */
|
|
if (is_editmode == false) {
|
|
ED_armature_to_edit(arm);
|
|
}
|
|
|
|
/* Find the center-point. */
|
|
if (centermode == 2) {
|
|
copy_v3_v3(cent, cursor);
|
|
invert_m4_m4(ob->imat, ob->obmat);
|
|
mul_m4_v3(ob->imat, cent);
|
|
}
|
|
else {
|
|
if (around == V3D_AROUND_CENTER_BOUNDS) {
|
|
float min[3], max[3];
|
|
INIT_MINMAX(min, max);
|
|
for (ebone = arm->edbo->first; ebone; ebone = ebone->next) {
|
|
minmax_v3v3_v3(min, max, ebone->head);
|
|
minmax_v3v3_v3(min, max, ebone->tail);
|
|
}
|
|
mid_v3_v3v3(cent, min, max);
|
|
}
|
|
else { /* #V3D_AROUND_CENTER_MEDIAN. */
|
|
int total = 0;
|
|
zero_v3(cent);
|
|
for (ebone = arm->edbo->first; ebone; ebone = ebone->next) {
|
|
total += 2;
|
|
add_v3_v3(cent, ebone->head);
|
|
add_v3_v3(cent, ebone->tail);
|
|
}
|
|
if (total) {
|
|
mul_v3_fl(cent, 1.0f / (float)total);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Do the adjustments */
|
|
for (ebone = arm->edbo->first; ebone; ebone = ebone->next) {
|
|
sub_v3_v3(ebone->head, cent);
|
|
sub_v3_v3(ebone->tail, cent);
|
|
}
|
|
|
|
/* Turn the list into an armature */
|
|
if (is_editmode == false) {
|
|
ED_armature_from_edit(bmain, arm);
|
|
ED_armature_edit_free(arm);
|
|
}
|
|
|
|
/* Adjust object location for new center-point. */
|
|
if (centermode && (is_editmode == false)) {
|
|
mul_mat3_m4_v3(ob->obmat, cent); /* omit translation part */
|
|
add_v3_v3(ob->loc, cent);
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Bone Roll Calculate Operator
|
|
* \{ */
|
|
|
|
/* adjust bone roll to align Z axis with vector
|
|
* vec is in local space and is normalized
|
|
*/
|
|
float ED_armature_ebone_roll_to_vector(const EditBone *bone,
|
|
const float align_axis[3],
|
|
const bool axis_only)
|
|
{
|
|
float mat[3][3], nor[3];
|
|
float vec[3], align_axis_proj[3], roll = 0.0f;
|
|
|
|
BLI_ASSERT_UNIT_V3(align_axis);
|
|
|
|
sub_v3_v3v3(nor, bone->tail, bone->head);
|
|
|
|
/* If tail == head or the bone is aligned with the axis... */
|
|
if (normalize_v3(nor) <= FLT_EPSILON ||
|
|
(fabsf(dot_v3v3(align_axis, nor)) >= (1.0f - FLT_EPSILON))) {
|
|
return roll;
|
|
}
|
|
|
|
vec_roll_to_mat3_normalized(nor, 0.0f, mat);
|
|
|
|
/* project the new_up_axis along the normal */
|
|
project_v3_v3v3_normalized(vec, align_axis, nor);
|
|
sub_v3_v3v3(align_axis_proj, align_axis, vec);
|
|
|
|
if (axis_only) {
|
|
if (angle_v3v3(align_axis_proj, mat[2]) > (float)(M_PI_2)) {
|
|
negate_v3(align_axis_proj);
|
|
}
|
|
}
|
|
|
|
roll = angle_v3v3(align_axis_proj, mat[2]);
|
|
|
|
cross_v3_v3v3(vec, mat[2], align_axis_proj);
|
|
|
|
if (dot_v3v3(vec, nor) < 0.0f) {
|
|
return -roll;
|
|
}
|
|
return roll;
|
|
}
|
|
|
|
/* note, ranges arithmetic is used below */
|
|
typedef enum eCalcRollTypes {
|
|
/* pos */
|
|
CALC_ROLL_POS_X = 0,
|
|
CALC_ROLL_POS_Y,
|
|
CALC_ROLL_POS_Z,
|
|
|
|
CALC_ROLL_TAN_POS_X,
|
|
CALC_ROLL_TAN_POS_Z,
|
|
|
|
/* neg */
|
|
CALC_ROLL_NEG_X,
|
|
CALC_ROLL_NEG_Y,
|
|
CALC_ROLL_NEG_Z,
|
|
|
|
CALC_ROLL_TAN_NEG_X,
|
|
CALC_ROLL_TAN_NEG_Z,
|
|
|
|
/* no sign */
|
|
CALC_ROLL_ACTIVE,
|
|
CALC_ROLL_VIEW,
|
|
CALC_ROLL_CURSOR,
|
|
} eCalcRollTypes;
|
|
|
|
static const EnumPropertyItem prop_calc_roll_types[] = {
|
|
{0, "", 0, N_("Positive"), ""},
|
|
{CALC_ROLL_TAN_POS_X, "POS_X", 0, "Local +X Tangent", ""},
|
|
{CALC_ROLL_TAN_POS_Z, "POS_Z", 0, "Local +Z Tangent", ""},
|
|
|
|
{CALC_ROLL_POS_X, "GLOBAL_POS_X", 0, "Global +X Axis", ""},
|
|
{CALC_ROLL_POS_Y, "GLOBAL_POS_Y", 0, "Global +Y Axis", ""},
|
|
{CALC_ROLL_POS_Z, "GLOBAL_POS_Z", 0, "Global +Z Axis", ""},
|
|
|
|
{0, "", 0, N_("Negative"), ""},
|
|
|
|
{CALC_ROLL_TAN_NEG_X, "NEG_X", 0, "Local -X Tangent", ""},
|
|
{CALC_ROLL_TAN_NEG_Z, "NEG_Z", 0, "Local -Z Tangent", ""},
|
|
|
|
{CALC_ROLL_NEG_X, "GLOBAL_NEG_X", 0, "Global -X Axis", ""},
|
|
{CALC_ROLL_NEG_Y, "GLOBAL_NEG_Y", 0, "Global -Y Axis", ""},
|
|
{CALC_ROLL_NEG_Z, "GLOBAL_NEG_Z", 0, "Global -Z Axis", ""},
|
|
|
|
{0, "", 0, N_("Other"), ""},
|
|
{CALC_ROLL_ACTIVE, "ACTIVE", 0, "Active Bone", ""},
|
|
{CALC_ROLL_VIEW, "VIEW", 0, "View Axis", ""},
|
|
{CALC_ROLL_CURSOR, "CURSOR", 0, "Cursor", ""},
|
|
{0, NULL, 0, NULL, NULL},
|
|
};
|
|
|
|
static int armature_calc_roll_exec(bContext *C, wmOperator *op)
|
|
{
|
|
ViewLayer *view_layer = CTX_data_view_layer(C);
|
|
Object *ob_active = CTX_data_edit_object(C);
|
|
int ret = OPERATOR_FINISHED;
|
|
|
|
eCalcRollTypes type = RNA_enum_get(op->ptr, "type");
|
|
const bool axis_only = RNA_boolean_get(op->ptr, "axis_only");
|
|
/* axis_flip when matching the active bone never makes sense */
|
|
bool axis_flip = ((type >= CALC_ROLL_ACTIVE) ? RNA_boolean_get(op->ptr, "axis_flip") :
|
|
(type >= CALC_ROLL_TAN_NEG_X) ? true : false);
|
|
|
|
uint objects_len = 0;
|
|
Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(
|
|
view_layer, CTX_wm_view3d(C), &objects_len);
|
|
for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
|
|
Object *ob = objects[ob_index];
|
|
bArmature *arm = ob->data;
|
|
bool changed = false;
|
|
|
|
float imat[3][3];
|
|
EditBone *ebone;
|
|
|
|
if ((type >= CALC_ROLL_NEG_X) && (type <= CALC_ROLL_TAN_NEG_Z)) {
|
|
type -= (CALC_ROLL_ACTIVE - CALC_ROLL_NEG_X);
|
|
axis_flip = true;
|
|
}
|
|
|
|
copy_m3_m4(imat, ob->obmat);
|
|
invert_m3(imat);
|
|
|
|
if (type == CALC_ROLL_CURSOR) { /* Cursor */
|
|
Scene *scene = CTX_data_scene(C);
|
|
float cursor_local[3];
|
|
const View3DCursor *cursor = &scene->cursor;
|
|
|
|
invert_m4_m4(ob->imat, ob->obmat);
|
|
copy_v3_v3(cursor_local, cursor->location);
|
|
mul_m4_v3(ob->imat, cursor_local);
|
|
|
|
/* cursor */
|
|
for (ebone = arm->edbo->first; ebone; ebone = ebone->next) {
|
|
if (EBONE_VISIBLE(arm, ebone) && EBONE_EDITABLE(ebone)) {
|
|
float cursor_rel[3];
|
|
sub_v3_v3v3(cursor_rel, cursor_local, ebone->head);
|
|
if (axis_flip) {
|
|
negate_v3(cursor_rel);
|
|
}
|
|
if (normalize_v3(cursor_rel) != 0.0f) {
|
|
ebone->roll = ED_armature_ebone_roll_to_vector(ebone, cursor_rel, axis_only);
|
|
changed = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (ELEM(type, CALC_ROLL_TAN_POS_X, CALC_ROLL_TAN_POS_Z)) {
|
|
for (ebone = arm->edbo->first; ebone; ebone = ebone->next) {
|
|
if (ebone->parent) {
|
|
bool is_edit = (EBONE_VISIBLE(arm, ebone) && EBONE_EDITABLE(ebone));
|
|
bool is_edit_parent = (EBONE_VISIBLE(arm, ebone->parent) &&
|
|
EBONE_EDITABLE(ebone->parent));
|
|
|
|
if (is_edit || is_edit_parent) {
|
|
EditBone *ebone_other = ebone->parent;
|
|
float dir_a[3];
|
|
float dir_b[3];
|
|
float vec[3];
|
|
bool is_vec_zero;
|
|
|
|
sub_v3_v3v3(dir_a, ebone->tail, ebone->head);
|
|
normalize_v3(dir_a);
|
|
|
|
/* find the first bone in the chain with a different direction */
|
|
do {
|
|
sub_v3_v3v3(dir_b, ebone_other->head, ebone_other->tail);
|
|
normalize_v3(dir_b);
|
|
|
|
if (type == CALC_ROLL_TAN_POS_Z) {
|
|
cross_v3_v3v3(vec, dir_a, dir_b);
|
|
}
|
|
else {
|
|
add_v3_v3v3(vec, dir_a, dir_b);
|
|
}
|
|
} while ((is_vec_zero = (normalize_v3(vec) < 0.00001f)) &&
|
|
(ebone_other = ebone_other->parent));
|
|
|
|
if (!is_vec_zero) {
|
|
if (axis_flip) {
|
|
negate_v3(vec);
|
|
}
|
|
|
|
if (is_edit) {
|
|
ebone->roll = ED_armature_ebone_roll_to_vector(ebone, vec, axis_only);
|
|
changed = true;
|
|
}
|
|
|
|
/* parentless bones use cross product with child */
|
|
if (is_edit_parent) {
|
|
if (ebone->parent->parent == NULL) {
|
|
ebone->parent->roll = ED_armature_ebone_roll_to_vector(
|
|
ebone->parent, vec, axis_only);
|
|
changed = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
float vec[3] = {0.0f, 0.0f, 0.0f};
|
|
if (type == CALC_ROLL_VIEW) { /* View */
|
|
RegionView3D *rv3d = CTX_wm_region_view3d(C);
|
|
if (rv3d == NULL) {
|
|
BKE_report(op->reports, RPT_ERROR, "No region view3d available");
|
|
ret = OPERATOR_CANCELLED;
|
|
goto cleanup;
|
|
}
|
|
|
|
copy_v3_v3(vec, rv3d->viewinv[2]);
|
|
mul_m3_v3(imat, vec);
|
|
}
|
|
else if (type == CALC_ROLL_ACTIVE) {
|
|
float mat[3][3];
|
|
bArmature *arm_active = ob_active->data;
|
|
ebone = (EditBone *)arm_active->act_edbone;
|
|
if (ebone == NULL) {
|
|
BKE_report(op->reports, RPT_ERROR, "No active bone set");
|
|
ret = OPERATOR_CANCELLED;
|
|
goto cleanup;
|
|
}
|
|
|
|
ED_armature_ebone_to_mat3(ebone, mat);
|
|
copy_v3_v3(vec, mat[2]);
|
|
}
|
|
else { /* Axis */
|
|
BLI_assert(type <= 5);
|
|
if (type < 3) {
|
|
vec[type] = 1.0f;
|
|
}
|
|
else {
|
|
vec[type - 2] = -1.0f;
|
|
}
|
|
mul_m3_v3(imat, vec);
|
|
normalize_v3(vec);
|
|
}
|
|
|
|
if (axis_flip) {
|
|
negate_v3(vec);
|
|
}
|
|
|
|
for (ebone = arm->edbo->first; ebone; ebone = ebone->next) {
|
|
if (EBONE_VISIBLE(arm, ebone) && EBONE_EDITABLE(ebone)) {
|
|
/* roll func is a callback which assumes that all is well */
|
|
ebone->roll = ED_armature_ebone_roll_to_vector(ebone, vec, axis_only);
|
|
changed = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (arm->flag & ARM_MIRROR_EDIT) {
|
|
for (ebone = arm->edbo->first; ebone; ebone = ebone->next) {
|
|
if ((EBONE_VISIBLE(arm, ebone) && EBONE_EDITABLE(ebone)) == 0) {
|
|
EditBone *ebone_mirr = ED_armature_ebone_get_mirrored(arm->edbo, ebone);
|
|
if (ebone_mirr && (EBONE_VISIBLE(arm, ebone_mirr) && EBONE_EDITABLE(ebone_mirr))) {
|
|
ebone->roll = -ebone_mirr->roll;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (changed) {
|
|
/* note, notifier might evolve */
|
|
WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, ob);
|
|
DEG_id_tag_update(&arm->id, ID_RECALC_SELECT);
|
|
}
|
|
}
|
|
|
|
cleanup:
|
|
MEM_freeN(objects);
|
|
return ret;
|
|
}
|
|
|
|
void ARMATURE_OT_calculate_roll(wmOperatorType *ot)
|
|
{
|
|
/* identifiers */
|
|
ot->name = "Recalculate Roll";
|
|
ot->idname = "ARMATURE_OT_calculate_roll";
|
|
ot->description = "Automatically fix alignment of select bones' axes";
|
|
|
|
/* api callbacks */
|
|
ot->invoke = WM_menu_invoke;
|
|
ot->exec = armature_calc_roll_exec;
|
|
ot->poll = ED_operator_editarmature;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
/* properties */
|
|
ot->prop = RNA_def_enum(ot->srna, "type", prop_calc_roll_types, CALC_ROLL_TAN_POS_X, "Type", "");
|
|
RNA_def_boolean(ot->srna, "axis_flip", 0, "Flip Axis", "Negate the alignment axis");
|
|
RNA_def_boolean(ot->srna,
|
|
"axis_only",
|
|
0,
|
|
"Shortest Rotation",
|
|
"Ignore the axis direction, use the shortest rotation to align");
|
|
}
|
|
|
|
static int armature_roll_clear_exec(bContext *C, wmOperator *op)
|
|
{
|
|
ViewLayer *view_layer = CTX_data_view_layer(C);
|
|
const float roll = RNA_float_get(op->ptr, "roll");
|
|
|
|
uint objects_len = 0;
|
|
Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(
|
|
view_layer, CTX_wm_view3d(C), &objects_len);
|
|
for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
|
|
Object *ob = objects[ob_index];
|
|
bArmature *arm = ob->data;
|
|
bool changed = false;
|
|
|
|
LISTBASE_FOREACH (EditBone *, ebone, arm->edbo) {
|
|
if (EBONE_VISIBLE(arm, ebone) && EBONE_EDITABLE(ebone)) {
|
|
/* Roll func is a callback which assumes that all is well. */
|
|
ebone->roll = roll;
|
|
changed = true;
|
|
}
|
|
}
|
|
|
|
if (arm->flag & ARM_MIRROR_EDIT) {
|
|
LISTBASE_FOREACH (EditBone *, ebone, arm->edbo) {
|
|
if ((EBONE_VISIBLE(arm, ebone) && EBONE_EDITABLE(ebone)) == 0) {
|
|
EditBone *ebone_mirr = ED_armature_ebone_get_mirrored(arm->edbo, ebone);
|
|
if (ebone_mirr && (EBONE_VISIBLE(arm, ebone_mirr) && EBONE_EDITABLE(ebone_mirr))) {
|
|
ebone->roll = -ebone_mirr->roll;
|
|
changed = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (changed) {
|
|
/* Note, notifier might evolve. */
|
|
WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, ob);
|
|
DEG_id_tag_update(&arm->id, ID_RECALC_SELECT);
|
|
}
|
|
}
|
|
MEM_freeN(objects);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void ARMATURE_OT_roll_clear(wmOperatorType *ot)
|
|
{
|
|
/* identifiers */
|
|
ot->name = "Clear Roll";
|
|
ot->idname = "ARMATURE_OT_roll_clear";
|
|
ot->description = "Clear roll for selected bones";
|
|
|
|
/* api callbacks */
|
|
ot->exec = armature_roll_clear_exec;
|
|
ot->poll = ED_operator_editarmature;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
RNA_def_float_rotation(ot->srna,
|
|
"roll",
|
|
0,
|
|
NULL,
|
|
DEG2RADF(-360.0f),
|
|
DEG2RADF(360.0f),
|
|
"Roll",
|
|
"",
|
|
DEG2RADF(-360.0f),
|
|
DEG2RADF(360.0f));
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Chain-Based Tool Utilities
|
|
* \{ */
|
|
|
|
/* temporary data-structure for merge/fill bones */
|
|
typedef struct EditBonePoint {
|
|
struct EditBonePoint *next, *prev;
|
|
|
|
EditBone *head_owner; /* EditBone which uses this point as a 'head' point */
|
|
EditBone *tail_owner; /* EditBone which uses this point as a 'tail' point */
|
|
|
|
float vec[3]; /* the actual location of the point in local/EditMode space */
|
|
} EditBonePoint;
|
|
|
|
/* find chain-tips (i.e. bones without children) */
|
|
static void chains_find_tips(ListBase *edbo, ListBase *list)
|
|
{
|
|
EditBone *curBone, *ebo;
|
|
LinkData *ld;
|
|
|
|
/* note: this is potentially very slow ... there's got to be a better way */
|
|
for (curBone = edbo->first; curBone; curBone = curBone->next) {
|
|
short stop = 0;
|
|
|
|
/* is this bone contained within any existing chain? (skip if so) */
|
|
for (ld = list->first; ld; ld = ld->next) {
|
|
for (ebo = ld->data; ebo; ebo = ebo->parent) {
|
|
if (ebo == curBone) {
|
|
stop = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (stop) {
|
|
break;
|
|
}
|
|
}
|
|
/* skip current bone if it is part of an existing chain */
|
|
if (stop) {
|
|
continue;
|
|
}
|
|
|
|
/* is any existing chain part of the chain formed by this bone? */
|
|
stop = 0;
|
|
for (ebo = curBone->parent; ebo; ebo = ebo->parent) {
|
|
for (ld = list->first; ld; ld = ld->next) {
|
|
if (ld->data == ebo) {
|
|
ld->data = curBone;
|
|
stop = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (stop) {
|
|
break;
|
|
}
|
|
}
|
|
/* current bone has already been added to a chain? */
|
|
if (stop) {
|
|
continue;
|
|
}
|
|
|
|
/* add current bone to a new chain */
|
|
ld = MEM_callocN(sizeof(LinkData), "BoneChain");
|
|
ld->data = curBone;
|
|
BLI_addtail(list, ld);
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Fill Operator
|
|
* \{ */
|
|
|
|
static void fill_add_joint(EditBone *ebo, short eb_tail, ListBase *points)
|
|
{
|
|
EditBonePoint *ebp;
|
|
float vec[3];
|
|
short found = 0;
|
|
|
|
if (eb_tail) {
|
|
copy_v3_v3(vec, ebo->tail);
|
|
}
|
|
else {
|
|
copy_v3_v3(vec, ebo->head);
|
|
}
|
|
|
|
for (ebp = points->first; ebp; ebp = ebp->next) {
|
|
if (equals_v3v3(ebp->vec, vec)) {
|
|
if (eb_tail) {
|
|
if ((ebp->head_owner) && (ebp->head_owner->parent == ebo)) {
|
|
/* so this bone's tail owner is this bone */
|
|
ebp->tail_owner = ebo;
|
|
found = 1;
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
if ((ebp->tail_owner) && (ebo->parent == ebp->tail_owner)) {
|
|
/* so this bone's head owner is this bone */
|
|
ebp->head_owner = ebo;
|
|
found = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* allocate a new point if no existing point was related */
|
|
if (found == 0) {
|
|
ebp = MEM_callocN(sizeof(EditBonePoint), "EditBonePoint");
|
|
|
|
if (eb_tail) {
|
|
copy_v3_v3(ebp->vec, ebo->tail);
|
|
ebp->tail_owner = ebo;
|
|
}
|
|
else {
|
|
copy_v3_v3(ebp->vec, ebo->head);
|
|
ebp->head_owner = ebo;
|
|
}
|
|
|
|
BLI_addtail(points, ebp);
|
|
}
|
|
}
|
|
|
|
/* bone adding between selected joints */
|
|
static int armature_fill_bones_exec(bContext *C, wmOperator *op)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
View3D *v3d = CTX_wm_view3d(C);
|
|
ListBase points = {NULL, NULL};
|
|
EditBone *newbone = NULL;
|
|
int count;
|
|
bool mixed_object_error = false;
|
|
|
|
/* loop over all bones, and only consider if visible */
|
|
bArmature *arm = NULL;
|
|
CTX_DATA_BEGIN_WITH_ID (C, EditBone *, ebone, visible_bones, bArmature *, arm_iter) {
|
|
bool check = false;
|
|
if (!(ebone->flag & BONE_CONNECTED) && (ebone->flag & BONE_ROOTSEL)) {
|
|
fill_add_joint(ebone, 0, &points);
|
|
check = true;
|
|
}
|
|
if (ebone->flag & BONE_TIPSEL) {
|
|
fill_add_joint(ebone, 1, &points);
|
|
check = true;
|
|
}
|
|
|
|
if (check) {
|
|
if (arm && (arm != arm_iter)) {
|
|
mixed_object_error = true;
|
|
}
|
|
arm = arm_iter;
|
|
}
|
|
}
|
|
CTX_DATA_END;
|
|
|
|
/* the number of joints determines how we fill:
|
|
* 1) between joint and cursor (joint=head, cursor=tail)
|
|
* 2) between the two joints (order is dependent on active-bone/hierarchy)
|
|
* 3+) error (a smarter method involving finding chains needs to be worked out
|
|
*/
|
|
count = BLI_listbase_count(&points);
|
|
|
|
if (count == 0) {
|
|
BKE_report(op->reports, RPT_ERROR, "No joints selected");
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
if (mixed_object_error) {
|
|
BKE_report(op->reports, RPT_ERROR, "Bones for different objects selected");
|
|
BLI_freelistN(&points);
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
Object *obedit = NULL;
|
|
{
|
|
ViewLayer *view_layer = CTX_data_view_layer(C);
|
|
FOREACH_OBJECT_IN_EDIT_MODE_BEGIN (view_layer, v3d, ob_iter) {
|
|
if (ob_iter->data == arm) {
|
|
obedit = ob_iter;
|
|
}
|
|
}
|
|
FOREACH_OBJECT_IN_MODE_END;
|
|
}
|
|
BLI_assert(obedit != NULL);
|
|
|
|
if (count == 1) {
|
|
EditBonePoint *ebp;
|
|
float curs[3];
|
|
|
|
/* Get Points - selected joint */
|
|
ebp = points.first;
|
|
|
|
/* Get points - cursor (tail) */
|
|
invert_m4_m4(obedit->imat, obedit->obmat);
|
|
mul_v3_m4v3(curs, obedit->imat, scene->cursor.location);
|
|
|
|
/* Create a bone */
|
|
newbone = add_points_bone(obedit, ebp->vec, curs);
|
|
}
|
|
else if (count == 2) {
|
|
EditBonePoint *ebp_a, *ebp_b;
|
|
float head[3], tail[3];
|
|
short headtail = 0;
|
|
|
|
/* check that the points don't belong to the same bone */
|
|
ebp_a = (EditBonePoint *)points.first;
|
|
ebp_b = ebp_a->next;
|
|
|
|
if (((ebp_a->head_owner == ebp_b->tail_owner) && (ebp_a->head_owner != NULL)) ||
|
|
((ebp_a->tail_owner == ebp_b->head_owner) && (ebp_a->tail_owner != NULL))) {
|
|
BKE_report(op->reports, RPT_ERROR, "Same bone selected...");
|
|
BLI_freelistN(&points);
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
/* find which one should be the 'head' */
|
|
if ((ebp_a->head_owner && ebp_b->head_owner) || (ebp_a->tail_owner && ebp_b->tail_owner)) {
|
|
/* use active, nice predictable */
|
|
if (arm->act_edbone && ELEM(arm->act_edbone, ebp_a->head_owner, ebp_a->tail_owner)) {
|
|
headtail = 1;
|
|
}
|
|
else if (arm->act_edbone && ELEM(arm->act_edbone, ebp_b->head_owner, ebp_b->tail_owner)) {
|
|
headtail = 2;
|
|
}
|
|
else {
|
|
/* rule: whichever one is closer to 3d-cursor */
|
|
float curs[3];
|
|
float dist_sq_a, dist_sq_b;
|
|
|
|
/* get cursor location */
|
|
invert_m4_m4(obedit->imat, obedit->obmat);
|
|
mul_v3_m4v3(curs, obedit->imat, scene->cursor.location);
|
|
|
|
/* get distances */
|
|
dist_sq_a = len_squared_v3v3(ebp_a->vec, curs);
|
|
dist_sq_b = len_squared_v3v3(ebp_b->vec, curs);
|
|
|
|
/* compare distances - closer one therefore acts as direction for bone to go */
|
|
headtail = (dist_sq_a < dist_sq_b) ? 2 : 1;
|
|
}
|
|
}
|
|
else if (ebp_a->head_owner) {
|
|
headtail = 1;
|
|
}
|
|
else if (ebp_b->head_owner) {
|
|
headtail = 2;
|
|
}
|
|
|
|
/* assign head/tail combinations */
|
|
if (headtail == 2) {
|
|
copy_v3_v3(head, ebp_a->vec);
|
|
copy_v3_v3(tail, ebp_b->vec);
|
|
}
|
|
else if (headtail == 1) {
|
|
copy_v3_v3(head, ebp_b->vec);
|
|
copy_v3_v3(tail, ebp_a->vec);
|
|
}
|
|
|
|
/* add new bone and parent it to the appropriate end */
|
|
if (headtail) {
|
|
newbone = add_points_bone(obedit, head, tail);
|
|
|
|
/* do parenting (will need to set connected flag too) */
|
|
if (headtail == 2) {
|
|
/* ebp tail or head - tail gets priority */
|
|
if (ebp_a->tail_owner) {
|
|
newbone->parent = ebp_a->tail_owner;
|
|
}
|
|
else {
|
|
newbone->parent = ebp_a->head_owner;
|
|
}
|
|
}
|
|
else {
|
|
/* ebp_b tail or head - tail gets priority */
|
|
if (ebp_b->tail_owner) {
|
|
newbone->parent = ebp_b->tail_owner;
|
|
}
|
|
else {
|
|
newbone->parent = ebp_b->head_owner;
|
|
}
|
|
}
|
|
|
|
/* don't set for bone connecting two head points of bones */
|
|
if (ebp_a->tail_owner || ebp_b->tail_owner) {
|
|
newbone->flag |= BONE_CONNECTED;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
BKE_reportf(op->reports, RPT_ERROR, "Too many points selected: %d", count);
|
|
BLI_freelistN(&points);
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
if (newbone) {
|
|
ED_armature_edit_deselect_all(obedit);
|
|
arm->act_edbone = newbone;
|
|
newbone->flag |= BONE_TIPSEL;
|
|
}
|
|
|
|
/* updates */
|
|
ED_armature_edit_refresh_layer_used(arm);
|
|
WM_event_add_notifier(C, NC_OBJECT | ND_POSE, obedit);
|
|
DEG_id_tag_update(&arm->id, ID_RECALC_COPY_ON_WRITE);
|
|
|
|
/* free points */
|
|
BLI_freelistN(&points);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void ARMATURE_OT_fill(wmOperatorType *ot)
|
|
{
|
|
/* identifiers */
|
|
ot->name = "Fill Between Joints";
|
|
ot->idname = "ARMATURE_OT_fill";
|
|
ot->description = "Add bone between selected joint(s) and/or 3D cursor";
|
|
|
|
/* callbacks */
|
|
ot->exec = armature_fill_bones_exec;
|
|
ot->poll = ED_operator_editarmature;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Switch Direction Operator
|
|
*
|
|
* Currently, this does not use context loops, as context loops do not make it
|
|
* easy to retrieve any hierarchical/chain relationships which are necessary for
|
|
* this to be done easily.
|
|
* \{ */
|
|
|
|
/* helper to clear BONE_TRANSFORM flags */
|
|
static void armature_clear_swap_done_flags(bArmature *arm)
|
|
{
|
|
EditBone *ebone;
|
|
|
|
for (ebone = arm->edbo->first; ebone; ebone = ebone->next) {
|
|
ebone->flag &= ~BONE_TRANSFORM;
|
|
}
|
|
}
|
|
|
|
static int armature_switch_direction_exec(bContext *C, wmOperator *UNUSED(op))
|
|
{
|
|
ViewLayer *view_layer = CTX_data_view_layer(C);
|
|
uint objects_len = 0;
|
|
Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(
|
|
view_layer, CTX_wm_view3d(C), &objects_len);
|
|
|
|
for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
|
|
Object *ob = objects[ob_index];
|
|
bArmature *arm = ob->data;
|
|
|
|
ListBase chains = {NULL, NULL};
|
|
LinkData *chain;
|
|
|
|
/* get chains of bones (ends on chains) */
|
|
chains_find_tips(arm->edbo, &chains);
|
|
if (BLI_listbase_is_empty(&chains)) {
|
|
continue;
|
|
}
|
|
|
|
/* ensure that mirror bones will also be operated on */
|
|
armature_tag_select_mirrored(arm);
|
|
|
|
/* Clear BONE_TRANSFORM flags
|
|
* - Used to prevent duplicate/canceling operations from occurring T34123.
|
|
* - #BONE_DONE cannot be used here as that's already used for mirroring.
|
|
*/
|
|
armature_clear_swap_done_flags(arm);
|
|
|
|
/* loop over chains, only considering selected and visible bones */
|
|
for (chain = chains.first; chain; chain = chain->next) {
|
|
EditBone *ebo, *child = NULL, *parent = NULL;
|
|
|
|
/* loop over bones in chain */
|
|
for (ebo = chain->data; ebo; ebo = parent) {
|
|
/* parent is this bone's original parent
|
|
* - we store this, as the next bone that is checked is this one
|
|
* but the value of ebo->parent may change here...
|
|
*/
|
|
parent = ebo->parent;
|
|
|
|
/* skip bone if already handled, see T34123. */
|
|
if ((ebo->flag & BONE_TRANSFORM) == 0) {
|
|
/* only if selected and editable */
|
|
if (EBONE_VISIBLE(arm, ebo) && EBONE_EDITABLE(ebo)) {
|
|
/* swap head and tail coordinates */
|
|
swap_v3_v3(ebo->head, ebo->tail);
|
|
|
|
/* do parent swapping:
|
|
* - use 'child' as new parent
|
|
* - connected flag is only set if points are coincidental
|
|
*/
|
|
ebo->parent = child;
|
|
if ((child) && equals_v3v3(ebo->head, child->tail)) {
|
|
ebo->flag |= BONE_CONNECTED;
|
|
}
|
|
else {
|
|
ebo->flag &= ~BONE_CONNECTED;
|
|
}
|
|
|
|
/* get next bones
|
|
* - child will become the new parent of next bone
|
|
*/
|
|
child = ebo;
|
|
}
|
|
else {
|
|
/* not swapping this bone, however, if its 'parent' got swapped, unparent us from it
|
|
* as it will be facing in opposite direction
|
|
*/
|
|
if ((parent) && (EBONE_VISIBLE(arm, parent) && EBONE_EDITABLE(parent))) {
|
|
ebo->parent = NULL;
|
|
ebo->flag &= ~BONE_CONNECTED;
|
|
}
|
|
|
|
/* get next bones
|
|
* - child will become new parent of next bone (not swapping occurred,
|
|
* so set to NULL to prevent infinite-loop)
|
|
*/
|
|
child = NULL;
|
|
}
|
|
|
|
/* tag as done (to prevent double-swaps) */
|
|
ebo->flag |= BONE_TRANSFORM;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* free chains */
|
|
BLI_freelistN(&chains);
|
|
|
|
/* clear temp flags */
|
|
armature_clear_swap_done_flags(arm);
|
|
armature_tag_unselect(arm);
|
|
|
|
/* note, notifier might evolve */
|
|
WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, ob);
|
|
DEG_id_tag_update(&arm->id, ID_RECALC_SELECT);
|
|
}
|
|
MEM_freeN(objects);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void ARMATURE_OT_switch_direction(wmOperatorType *ot)
|
|
{
|
|
/* identifiers */
|
|
ot->name = "Switch Direction";
|
|
ot->idname = "ARMATURE_OT_switch_direction";
|
|
ot->description = "Change the direction that a chain of bones points in (head and tail swap)";
|
|
|
|
/* api callbacks */
|
|
ot->exec = armature_switch_direction_exec;
|
|
ot->poll = ED_operator_editarmature;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Align Operator
|
|
* \{ */
|
|
|
|
/* helper to fix a ebone position if its parent has moved due to alignment*/
|
|
static void fix_connected_bone(EditBone *ebone)
|
|
{
|
|
float diff[3];
|
|
|
|
if (!(ebone->parent) || !(ebone->flag & BONE_CONNECTED) ||
|
|
equals_v3v3(ebone->parent->tail, ebone->head)) {
|
|
return;
|
|
}
|
|
|
|
/* if the parent has moved we translate child's head and tail accordingly */
|
|
sub_v3_v3v3(diff, ebone->parent->tail, ebone->head);
|
|
add_v3_v3(ebone->head, diff);
|
|
add_v3_v3(ebone->tail, diff);
|
|
}
|
|
|
|
/* helper to recursively find chains of connected bones starting at ebone and fix their position */
|
|
static void fix_editbone_connected_children(ListBase *edbo, EditBone *ebone)
|
|
{
|
|
EditBone *selbone;
|
|
|
|
for (selbone = edbo->first; selbone; selbone = selbone->next) {
|
|
if ((selbone->parent) && (selbone->parent == ebone) && (selbone->flag & BONE_CONNECTED)) {
|
|
fix_connected_bone(selbone);
|
|
fix_editbone_connected_children(edbo, selbone);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void bone_align_to_bone(ListBase *edbo, EditBone *selbone, EditBone *actbone)
|
|
{
|
|
float selboneaxis[3], actboneaxis[3], length;
|
|
|
|
sub_v3_v3v3(actboneaxis, actbone->tail, actbone->head);
|
|
normalize_v3(actboneaxis);
|
|
|
|
sub_v3_v3v3(selboneaxis, selbone->tail, selbone->head);
|
|
length = len_v3(selboneaxis);
|
|
|
|
mul_v3_fl(actboneaxis, length);
|
|
add_v3_v3v3(selbone->tail, selbone->head, actboneaxis);
|
|
selbone->roll = actbone->roll;
|
|
|
|
/* if the bone being aligned has connected descendants they must be moved
|
|
* according to their parent new position, otherwise they would be left
|
|
* in an inconsistent state: connected but away from the parent*/
|
|
fix_editbone_connected_children(edbo, selbone);
|
|
}
|
|
|
|
static int armature_align_bones_exec(bContext *C, wmOperator *op)
|
|
{
|
|
Object *ob = CTX_data_edit_object(C);
|
|
bArmature *arm = (bArmature *)ob->data;
|
|
EditBone *actbone = CTX_data_active_bone(C);
|
|
EditBone *actmirb = NULL;
|
|
int num_selected_bones;
|
|
|
|
/* there must be an active bone */
|
|
if (actbone == NULL) {
|
|
BKE_report(op->reports, RPT_ERROR, "Operation requires an active bone");
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
if (arm->flag & ARM_MIRROR_EDIT) {
|
|
/* For X-Axis Mirror Editing option, we may need a mirror copy of actbone
|
|
* - if there's a mirrored copy of selbone, try to find a mirrored copy of actbone
|
|
* (i.e. selbone="child.L" and actbone="parent.L", find "child.R" and "parent.R").
|
|
* This is useful for arm-chains, for example parenting lower arm to upper arm
|
|
* - if there's no mirrored copy of actbone (i.e. actbone = "parent.C" or "parent")
|
|
* then just use actbone. Useful when doing upper arm to spine.
|
|
*/
|
|
actmirb = ED_armature_ebone_get_mirrored(arm->edbo, actbone);
|
|
if (actmirb == NULL) {
|
|
actmirb = actbone;
|
|
}
|
|
}
|
|
|
|
/* if there is only 1 selected bone, we assume that that is the active bone,
|
|
* since a user will need to have clicked on a bone (thus selecting it) to make it active
|
|
*/
|
|
num_selected_bones = CTX_DATA_COUNT(C, selected_editable_bones);
|
|
if (num_selected_bones <= 1) {
|
|
/* When only the active bone is selected, and it has a parent,
|
|
* align it to the parent, as that is the only possible outcome.
|
|
*/
|
|
if (actbone->parent) {
|
|
bone_align_to_bone(arm->edbo, actbone, actbone->parent);
|
|
|
|
if ((arm->flag & ARM_MIRROR_EDIT) && (actmirb->parent)) {
|
|
bone_align_to_bone(arm->edbo, actmirb, actmirb->parent);
|
|
}
|
|
|
|
BKE_reportf(op->reports, RPT_INFO, "Aligned bone '%s' to parent", actbone->name);
|
|
}
|
|
}
|
|
else {
|
|
/* Align 'selected' bones to the active one
|
|
* - the context iterator contains both selected bones and their mirrored copies,
|
|
* so we assume that unselected bones are mirrored copies of some selected bone
|
|
* - since the active one (and/or its mirror) will also be selected, we also need
|
|
* to check that we are not trying to operate on them, since such an operation
|
|
* would cause errors
|
|
*/
|
|
|
|
/* align selected bones to the active one */
|
|
CTX_DATA_BEGIN (C, EditBone *, ebone, selected_editable_bones) {
|
|
if (ELEM(ebone, actbone, actmirb) == 0) {
|
|
if (ebone->flag & BONE_SELECTED) {
|
|
bone_align_to_bone(arm->edbo, ebone, actbone);
|
|
}
|
|
else {
|
|
bone_align_to_bone(arm->edbo, ebone, actmirb);
|
|
}
|
|
}
|
|
}
|
|
CTX_DATA_END;
|
|
|
|
BKE_reportf(
|
|
op->reports, RPT_INFO, "%d bones aligned to bone '%s'", num_selected_bones, actbone->name);
|
|
}
|
|
|
|
/* note, notifier might evolve */
|
|
WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, ob);
|
|
DEG_id_tag_update(&arm->id, ID_RECALC_SELECT);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void ARMATURE_OT_align(wmOperatorType *ot)
|
|
{
|
|
/* identifiers */
|
|
ot->name = "Align Bones";
|
|
ot->idname = "ARMATURE_OT_align";
|
|
ot->description = "Align selected bones to the active bone (or to their parent)";
|
|
|
|
/* api callbacks */
|
|
ot->exec = armature_align_bones_exec;
|
|
ot->poll = ED_operator_editarmature;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Split Operator
|
|
* \{ */
|
|
|
|
static int armature_split_exec(bContext *C, wmOperator *UNUSED(op))
|
|
{
|
|
ViewLayer *view_layer = CTX_data_view_layer(C);
|
|
|
|
uint objects_len = 0;
|
|
Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(
|
|
view_layer, CTX_wm_view3d(C), &objects_len);
|
|
for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
|
|
Object *ob = objects[ob_index];
|
|
bArmature *arm = ob->data;
|
|
|
|
LISTBASE_FOREACH (EditBone *, bone, arm->edbo) {
|
|
if (bone->parent && (bone->flag & BONE_SELECTED) != (bone->parent->flag & BONE_SELECTED)) {
|
|
bone->parent = NULL;
|
|
bone->flag &= ~BONE_CONNECTED;
|
|
}
|
|
}
|
|
LISTBASE_FOREACH (EditBone *, bone, arm->edbo) {
|
|
ED_armature_ebone_select_set(bone, (bone->flag & BONE_SELECTED) != 0);
|
|
}
|
|
|
|
WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, ob);
|
|
DEG_id_tag_update(&arm->id, ID_RECALC_SELECT);
|
|
}
|
|
|
|
MEM_freeN(objects);
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void ARMATURE_OT_split(wmOperatorType *ot)
|
|
{
|
|
/* identifiers */
|
|
ot->name = "Split";
|
|
ot->idname = "ARMATURE_OT_split";
|
|
ot->description = "Split off selected bones from connected unselected bones";
|
|
|
|
/* api callbacks */
|
|
ot->exec = armature_split_exec;
|
|
ot->poll = ED_operator_editarmature;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Delete Operator
|
|
* \{ */
|
|
|
|
static bool armature_delete_ebone_cb(const char *bone_name, void *arm_p)
|
|
{
|
|
bArmature *arm = arm_p;
|
|
EditBone *ebone;
|
|
|
|
ebone = ED_armature_ebone_find_name(arm->edbo, bone_name);
|
|
return (ebone && (ebone->flag & BONE_SELECTED) && (arm->layer & ebone->layer));
|
|
}
|
|
|
|
/* previously delete_armature */
|
|
/* only editmode! */
|
|
static int armature_delete_selected_exec(bContext *C, wmOperator *UNUSED(op))
|
|
{
|
|
EditBone *curBone, *ebone_next;
|
|
bool changed_multi = false;
|
|
|
|
/* cancel if nothing selected */
|
|
if (CTX_DATA_COUNT(C, selected_bones) == 0) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
ViewLayer *view_layer = CTX_data_view_layer(C);
|
|
uint objects_len = 0;
|
|
Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(
|
|
view_layer, CTX_wm_view3d(C), &objects_len);
|
|
for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
|
|
Object *obedit = objects[ob_index];
|
|
bArmature *arm = obedit->data;
|
|
bool changed = false;
|
|
|
|
armature_select_mirrored(arm);
|
|
|
|
BKE_pose_channels_remove(obedit, armature_delete_ebone_cb, arm);
|
|
|
|
for (curBone = arm->edbo->first; curBone; curBone = ebone_next) {
|
|
ebone_next = curBone->next;
|
|
if (arm->layer & curBone->layer) {
|
|
if (curBone->flag & BONE_SELECTED) {
|
|
if (curBone == arm->act_edbone) {
|
|
arm->act_edbone = NULL;
|
|
}
|
|
ED_armature_ebone_remove(arm, curBone);
|
|
changed = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (changed) {
|
|
changed_multi = true;
|
|
|
|
ED_armature_edit_sync_selection(arm->edbo);
|
|
ED_armature_edit_refresh_layer_used(arm);
|
|
BKE_pose_tag_recalc(CTX_data_main(C), obedit->pose);
|
|
WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, obedit);
|
|
DEG_id_tag_update(&arm->id, ID_RECALC_SELECT);
|
|
ED_outliner_select_sync_from_edit_bone_tag(C);
|
|
}
|
|
}
|
|
MEM_freeN(objects);
|
|
|
|
if (!changed_multi) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void ARMATURE_OT_delete(wmOperatorType *ot)
|
|
{
|
|
/* identifiers */
|
|
ot->name = "Delete Selected Bone(s)";
|
|
ot->idname = "ARMATURE_OT_delete";
|
|
ot->description = "Remove selected bones from the armature";
|
|
|
|
/* api callbacks */
|
|
ot->invoke = WM_operator_confirm;
|
|
ot->exec = armature_delete_selected_exec;
|
|
ot->poll = ED_operator_editarmature;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
}
|
|
|
|
static bool armature_dissolve_ebone_cb(const char *bone_name, void *arm_p)
|
|
{
|
|
bArmature *arm = arm_p;
|
|
EditBone *ebone;
|
|
|
|
ebone = ED_armature_ebone_find_name(arm->edbo, bone_name);
|
|
return (ebone && (ebone->flag & BONE_DONE));
|
|
}
|
|
|
|
static int armature_dissolve_selected_exec(bContext *C, wmOperator *UNUSED(op))
|
|
{
|
|
ViewLayer *view_layer = CTX_data_view_layer(C);
|
|
EditBone *ebone, *ebone_next;
|
|
bool changed_multi = false;
|
|
|
|
uint objects_len = 0;
|
|
Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(
|
|
view_layer, CTX_wm_view3d(C), &objects_len);
|
|
for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
|
|
Object *obedit = objects[ob_index];
|
|
bArmature *arm = obedit->data;
|
|
bool changed = false;
|
|
|
|
/* store for mirror */
|
|
GHash *ebone_flag_orig = NULL;
|
|
int ebone_num = 0;
|
|
|
|
for (ebone = arm->edbo->first; ebone; ebone = ebone->next) {
|
|
ebone->temp.p = NULL;
|
|
ebone->flag &= ~BONE_DONE;
|
|
ebone_num++;
|
|
}
|
|
|
|
if (arm->flag & ARM_MIRROR_EDIT) {
|
|
GHashIterator gh_iter;
|
|
|
|
ebone_flag_orig = BLI_ghash_ptr_new_ex(__func__, ebone_num);
|
|
for (ebone = arm->edbo->first; ebone; ebone = ebone->next) {
|
|
union {
|
|
int flag;
|
|
void *p;
|
|
} val = {0};
|
|
val.flag = ebone->flag;
|
|
BLI_ghash_insert(ebone_flag_orig, ebone, val.p);
|
|
}
|
|
|
|
armature_select_mirrored_ex(arm, BONE_SELECTED | BONE_ROOTSEL | BONE_TIPSEL);
|
|
|
|
GHASH_ITER (gh_iter, ebone_flag_orig) {
|
|
union {
|
|
int flag;
|
|
void *p;
|
|
} *val_p = (void *)BLI_ghashIterator_getValue_p(&gh_iter);
|
|
ebone = BLI_ghashIterator_getKey(&gh_iter);
|
|
val_p->flag = ebone->flag & ~val_p->flag;
|
|
}
|
|
}
|
|
|
|
for (ebone = arm->edbo->first; ebone; ebone = ebone->next) {
|
|
if (ebone->parent && ebone->flag & BONE_CONNECTED) {
|
|
if (ebone->parent->temp.ebone == ebone->parent) {
|
|
/* ignore */
|
|
}
|
|
else if (ebone->parent->temp.ebone) {
|
|
/* set ignored */
|
|
ebone->parent->temp.ebone = ebone->parent;
|
|
}
|
|
else {
|
|
/* set child */
|
|
ebone->parent->temp.ebone = ebone;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* cleanup multiple used bones */
|
|
for (ebone = arm->edbo->first; ebone; ebone = ebone->next) {
|
|
if (ebone->temp.ebone == ebone) {
|
|
ebone->temp.ebone = NULL;
|
|
}
|
|
}
|
|
|
|
for (ebone = arm->edbo->first; ebone; ebone = ebone->next) {
|
|
/* break connections for unseen bones */
|
|
if (((arm->layer & ebone->layer) &&
|
|
((ED_armature_ebone_selectflag_get(ebone) & (BONE_TIPSEL | BONE_SELECTED)))) == 0) {
|
|
ebone->temp.ebone = NULL;
|
|
}
|
|
|
|
if (((arm->layer & ebone->layer) &&
|
|
((ED_armature_ebone_selectflag_get(ebone) & (BONE_ROOTSEL | BONE_SELECTED)))) == 0) {
|
|
if (ebone->parent && (ebone->flag & BONE_CONNECTED)) {
|
|
ebone->parent->temp.ebone = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (ebone = arm->edbo->first; ebone; ebone = ebone->next) {
|
|
|
|
if (ebone->parent && (ebone->parent->temp.ebone == ebone)) {
|
|
ebone->flag |= BONE_DONE;
|
|
}
|
|
}
|
|
|
|
BKE_pose_channels_remove(obedit, armature_dissolve_ebone_cb, arm);
|
|
|
|
for (ebone = arm->edbo->first; ebone; ebone = ebone_next) {
|
|
ebone_next = ebone->next;
|
|
|
|
if (ebone->flag & BONE_DONE) {
|
|
copy_v3_v3(ebone->parent->tail, ebone->tail);
|
|
ebone->parent->rad_tail = ebone->rad_tail;
|
|
SET_FLAG_FROM_TEST(ebone->parent->flag, ebone->flag & BONE_TIPSEL, BONE_TIPSEL);
|
|
|
|
ED_armature_ebone_remove_ex(arm, ebone, false);
|
|
changed = true;
|
|
}
|
|
}
|
|
|
|
if (changed) {
|
|
for (ebone = arm->edbo->first; ebone; ebone = ebone->next) {
|
|
if (ebone->parent && ebone->parent->temp.ebone && (ebone->flag & BONE_CONNECTED)) {
|
|
ebone->rad_head = ebone->parent->rad_tail;
|
|
}
|
|
}
|
|
|
|
if (arm->flag & ARM_MIRROR_EDIT) {
|
|
for (ebone = arm->edbo->first; ebone; ebone = ebone->next) {
|
|
union {
|
|
int flag;
|
|
void *p;
|
|
} *val_p = (void *)BLI_ghash_lookup_p(ebone_flag_orig, ebone);
|
|
if (val_p && val_p->flag) {
|
|
ebone->flag &= ~val_p->flag;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (arm->flag & ARM_MIRROR_EDIT) {
|
|
BLI_ghash_free(ebone_flag_orig, NULL, NULL);
|
|
}
|
|
|
|
if (changed) {
|
|
changed_multi = true;
|
|
ED_armature_edit_sync_selection(arm->edbo);
|
|
ED_armature_edit_refresh_layer_used(arm);
|
|
WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, obedit);
|
|
DEG_id_tag_update(&arm->id, ID_RECALC_SELECT);
|
|
ED_outliner_select_sync_from_edit_bone_tag(C);
|
|
}
|
|
}
|
|
MEM_freeN(objects);
|
|
|
|
if (!changed_multi) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void ARMATURE_OT_dissolve(wmOperatorType *ot)
|
|
{
|
|
/* identifiers */
|
|
ot->name = "Dissolve Selected Bone(s)";
|
|
ot->idname = "ARMATURE_OT_dissolve";
|
|
ot->description = "Dissolve selected bones from the armature";
|
|
|
|
/* api callbacks */
|
|
ot->exec = armature_dissolve_selected_exec;
|
|
ot->poll = ED_operator_editarmature;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Hide Operator
|
|
* \{ */
|
|
|
|
static int armature_hide_exec(bContext *C, wmOperator *op)
|
|
{
|
|
ViewLayer *view_layer = CTX_data_view_layer(C);
|
|
const int invert = RNA_boolean_get(op->ptr, "unselected") ? BONE_SELECTED : 0;
|
|
|
|
/* cancel if nothing selected */
|
|
if (CTX_DATA_COUNT(C, selected_bones) == 0) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
uint objects_len = 0;
|
|
Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(
|
|
view_layer, CTX_wm_view3d(C), &objects_len);
|
|
for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
|
|
Object *obedit = objects[ob_index];
|
|
bArmature *arm = obedit->data;
|
|
bool changed = false;
|
|
|
|
LISTBASE_FOREACH (EditBone *, ebone, arm->edbo) {
|
|
if (EBONE_VISIBLE(arm, ebone)) {
|
|
if ((ebone->flag & BONE_SELECTED) != invert) {
|
|
ebone->flag &= ~(BONE_TIPSEL | BONE_SELECTED | BONE_ROOTSEL);
|
|
ebone->flag |= BONE_HIDDEN_A;
|
|
changed = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!changed) {
|
|
continue;
|
|
}
|
|
ED_armature_edit_validate_active(arm);
|
|
ED_armature_edit_sync_selection(arm->edbo);
|
|
|
|
WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, obedit);
|
|
DEG_id_tag_update(&arm->id, ID_RECALC_SELECT);
|
|
}
|
|
MEM_freeN(objects);
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void ARMATURE_OT_hide(wmOperatorType *ot)
|
|
{
|
|
/* identifiers */
|
|
ot->name = "Hide Selected";
|
|
ot->idname = "ARMATURE_OT_hide";
|
|
ot->description = "Tag selected bones to not be visible in Edit Mode";
|
|
|
|
/* api callbacks */
|
|
ot->exec = armature_hide_exec;
|
|
ot->poll = ED_operator_editarmature;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
/* props */
|
|
RNA_def_boolean(ot->srna, "unselected", 0, "Unselected", "Hide unselected rather than selected");
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Reveal Operator
|
|
* \{ */
|
|
|
|
static int armature_reveal_exec(bContext *C, wmOperator *op)
|
|
{
|
|
ViewLayer *view_layer = CTX_data_view_layer(C);
|
|
const bool select = RNA_boolean_get(op->ptr, "select");
|
|
uint objects_len = 0;
|
|
Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(
|
|
view_layer, CTX_wm_view3d(C), &objects_len);
|
|
for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
|
|
Object *obedit = objects[ob_index];
|
|
bArmature *arm = obedit->data;
|
|
bool changed = false;
|
|
|
|
LISTBASE_FOREACH (EditBone *, ebone, arm->edbo) {
|
|
if (arm->layer & ebone->layer) {
|
|
if (ebone->flag & BONE_HIDDEN_A) {
|
|
if (!(ebone->flag & BONE_UNSELECTABLE)) {
|
|
SET_FLAG_FROM_TEST(ebone->flag, select, (BONE_TIPSEL | BONE_SELECTED | BONE_ROOTSEL));
|
|
}
|
|
ebone->flag &= ~BONE_HIDDEN_A;
|
|
changed = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (changed) {
|
|
ED_armature_edit_validate_active(arm);
|
|
ED_armature_edit_sync_selection(arm->edbo);
|
|
|
|
WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, obedit);
|
|
DEG_id_tag_update(&arm->id, ID_RECALC_SELECT);
|
|
}
|
|
}
|
|
MEM_freeN(objects);
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void ARMATURE_OT_reveal(wmOperatorType *ot)
|
|
{
|
|
/* identifiers */
|
|
ot->name = "Reveal Hidden";
|
|
ot->idname = "ARMATURE_OT_reveal";
|
|
ot->description = "Reveal all bones hidden in Edit Mode";
|
|
|
|
/* api callbacks */
|
|
ot->exec = armature_reveal_exec;
|
|
ot->poll = ED_operator_editarmature;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
RNA_def_boolean(ot->srna, "select", true, "Select", "");
|
|
}
|
|
|
|
/** \} */
|