Add Chromium-only Blender WebEngine parity work

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

View File

@@ -0,0 +1,59 @@
# SPDX-FileCopyrightText: 2023 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later
set(INC
../include
../../makesrna
../../../../intern/eigen
# RNA_prototypes.hh
${CMAKE_BINARY_DIR}/source/blender/makesrna
)
set(INC_SYS
)
set(SRC
armature_add.cc
armature_edit.cc
armature_naming.cc
armature_ops.cc
armature_relations.cc
armature_select.cc
armature_skinning.cc
armature_utils.cc
bone_collections.cc
editarmature_undo.cc
meshlaplacian.cc
pose_edit.cc
pose_lib_2.cc
pose_select.cc
pose_slide.cc
pose_transform.cc
pose_utils.cc
armature_intern.hh
meshlaplacian.h
)
set(LIB
PRIVATE bf::animrig
PRIVATE bf::asset_system
PRIVATE bf::blenfont
PRIVATE bf::blenkernel
PRIVATE bf::blenlib
PRIVATE bf::blenloader
PRIVATE bf::blentranslation
PRIVATE bf::depsgraph
PRIVATE bf::dna
PRIVATE bf::gpu
PRIVATE bf::intern::clog
PRIVATE bf::intern::guardedalloc
PRIVATE bf::windowmanager
)
blender_add_lib(bf_editor_armature "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
# RNA_prototypes.hh
add_dependencies(bf_editor_armature bf_rna)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,328 @@
/* SPDX-FileCopyrightText: 2009 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edarmature
*/
#pragma once
#include "DNA_armature_types.h"
#include "DNA_listBase.h"
#include "RNA_types.hh"
#include "BLI_span.hh"
#include "ED_anim_transformable.hh"
namespace blender {
struct Base;
struct Bone;
struct EditBone;
struct GPUSelectResult;
struct IDProperty;
struct LinkData;
struct Object;
struct Scene;
struct bArmature;
struct bContext;
struct bPoseChannel;
struct wmOperatorType;
/* -------------------------------------------------------------------- */
/** \name Armature EditMode Operators
* \{ */
void ARMATURE_OT_bone_primitive_add(wmOperatorType *ot);
void ARMATURE_OT_align(wmOperatorType *ot);
void ARMATURE_OT_calculate_roll(wmOperatorType *ot);
void ARMATURE_OT_roll_clear(wmOperatorType *ot);
void ARMATURE_OT_switch_direction(wmOperatorType *ot);
void ARMATURE_OT_subdivide(wmOperatorType *ot);
void ARMATURE_OT_parent_set(wmOperatorType *ot);
void ARMATURE_OT_parent_clear(wmOperatorType *ot);
void ARMATURE_OT_select_all(wmOperatorType *ot);
void ARMATURE_OT_select_mirror(wmOperatorType *ot);
void ARMATURE_OT_select_more(wmOperatorType *ot);
void ARMATURE_OT_select_less(wmOperatorType *ot);
void ARMATURE_OT_select_hierarchy(wmOperatorType *ot);
void ARMATURE_OT_select_linked_pick(wmOperatorType *ot);
void ARMATURE_OT_select_linked(wmOperatorType *ot);
void ARMATURE_OT_select_similar(wmOperatorType *ot);
void ARMATURE_OT_shortest_path_pick(wmOperatorType *ot);
void ARMATURE_OT_delete(wmOperatorType *ot);
void ARMATURE_OT_dissolve(wmOperatorType *ot);
void ARMATURE_OT_duplicate(wmOperatorType *ot);
void ARMATURE_OT_duplicate_rename(wmOperatorType *ot);
void ARMATURE_OT_symmetrize(wmOperatorType *ot);
void ARMATURE_OT_extrude(wmOperatorType *ot);
void ARMATURE_OT_hide(wmOperatorType *ot);
void ARMATURE_OT_reveal(wmOperatorType *ot);
void ARMATURE_OT_click_extrude(wmOperatorType *ot);
void ARMATURE_OT_fill(wmOperatorType *ot);
void ARMATURE_OT_separate(wmOperatorType *ot);
void ARMATURE_OT_split(wmOperatorType *ot);
void ARMATURE_OT_autoside_names(wmOperatorType *ot);
void ARMATURE_OT_flip_names(wmOperatorType *ot);
void ARMATURE_OT_collection_add(wmOperatorType *ot);
void ARMATURE_OT_collection_remove(wmOperatorType *ot);
void ARMATURE_OT_collection_move(wmOperatorType *ot);
void ARMATURE_OT_collection_assign(wmOperatorType *ot);
void ARMATURE_OT_collection_create_and_assign(wmOperatorType *ot);
void ARMATURE_OT_collection_unassign(wmOperatorType *ot);
void ARMATURE_OT_collection_unassign_named(wmOperatorType *ot);
void ARMATURE_OT_collection_select(wmOperatorType *ot);
void ARMATURE_OT_collection_deselect(wmOperatorType *ot);
void ARMATURE_OT_move_to_collection(wmOperatorType *ot);
void ARMATURE_OT_assign_to_collection(wmOperatorType *ot);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Pose-Mode Operators
* \{ */
void POSE_OT_hide(wmOperatorType *ot);
void POSE_OT_reveal(wmOperatorType *ot);
void POSE_OT_armature_apply(wmOperatorType *ot);
void POSE_OT_visual_transform_apply(wmOperatorType *ot);
void POSE_OT_rot_clear(wmOperatorType *ot);
void POSE_OT_loc_clear(wmOperatorType *ot);
void POSE_OT_scale_clear(wmOperatorType *ot);
void POSE_OT_transforms_clear(wmOperatorType *ot);
void POSE_OT_user_transforms_clear(wmOperatorType *ot);
void POSE_OT_copy(wmOperatorType *ot);
void POSE_OT_paste(wmOperatorType *ot);
void POSE_OT_select_all(wmOperatorType *ot);
void POSE_OT_select_parent(wmOperatorType *ot);
void POSE_OT_select_hierarchy(wmOperatorType *ot);
void POSE_OT_select_linked(wmOperatorType *ot);
void POSE_OT_select_linked_pick(wmOperatorType *ot);
void POSE_OT_select_constraint_target(wmOperatorType *ot);
void POSE_OT_select_grouped(wmOperatorType *ot);
void POSE_OT_select_mirror(wmOperatorType *ot);
void POSE_OT_paths_calculate(wmOperatorType *ot);
void POSE_OT_paths_update(wmOperatorType *ot);
void POSE_OT_paths_clear(wmOperatorType *ot);
void POSE_OT_paths_range_update(wmOperatorType *ot);
void POSE_OT_autoside_names(wmOperatorType *ot);
void POSE_OT_flip_names(wmOperatorType *ot);
void POSE_OT_rotation_mode_set(wmOperatorType *ot);
void POSE_OT_quaternions_flip(wmOperatorType *ot);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Pose Tool Utilities (for PoseLib, Pose Sliding, etc.)
* \{ */
/* `pose_utils.cc` */
/**
* Types of transforms to apply to a tPchanFCurveLink.
*/
enum eAction_TransformFlags {
ACT_TRANS_LOC = (1 << 0),
ACT_TRANS_ROT = (1 << 1),
ACT_TRANS_SCALE = (1 << 2),
/* BBone shape - for all the parameters, provided one is set. */
ACT_TRANS_BBONE = (1 << 3),
ACT_TRANS_PROP = (1 << 4),
ACT_TRANS_ONLY = (ACT_TRANS_LOC | ACT_TRANS_ROT | ACT_TRANS_SCALE),
ACT_TRANS_ALL = (ACT_TRANS_ONLY | ACT_TRANS_PROP),
};
/* Stores values of an RNA property for use at a later date. */
struct PropertySnapshot {
PropertyRNA *property;
/* Non-float properties are also stored as float. The length of the array matches the length of
* the property. */
Array<float> values;
};
/* Temporary struct wrapping data used for pose sliding. */
struct SlideSubject {
SlideSubject *next, *prev;
/** F-Curves for this PoseChannel (wrapped with LinkData) */
/** The AnimTransformable which the data is attached to */
ed::AnimTransformable *transformable;
/* A pointer to the data represented by this link. */
PointerRNA ptr;
/** F-Curves for this AnimTransformable. */
Vector<FCurve *> fcurves;
/* This is used as an optimization to only do blending on transform types that actually have
* animation. */
eAction_TransformFlags transform_flag;
/** Transform values at start of operator (to be restored before each modal step). */
ed::TransformFloats old_loc;
ed::Rotation old_rot;
ed::TransformFloats old_scale;
/* Additional properties of the transformable to affect which are not custom properties. Bones
* use this to store bbone data, e.g. `bbone_rollin`. */
Vector<PropertySnapshot> additional_properties;
/* Custom properties defined via the UI. See ID::properties. */
Vector<PropertySnapshot> properties;
/* User defined properties through addons. See ID::system_properties. */
Vector<PropertySnapshot> system_properties;
};
/* ----------- */
/**
* Build up a list of SlideSubject. The items put into the list depend on the mode of
* the context.
*/
void slide_subjects_get(bContext *C, ListBaseT<SlideSubject> *slide_subjects);
/** Free all slide subjects. */
void slide_subjects_free(ListBaseT<SlideSubject> *slide_subjects);
/**
* Helper for apply() / reset() - refresh the data.
*/
void slide_subjects_refresh(bContext *C, const SlideSubject &slide_subject);
/**
* Reset changes made to current slide subjects back to their stored values.
*/
void slide_subjects_reset(ListBaseT<SlideSubject> *slide_subjects);
/** Perform auto-key-framing after changes were made + confirmed. */
void slide_subjects_autokey(bContext *C,
Scene *scene,
const ListBaseT<SlideSubject> *slide_subjects);
/** \} */
/* -------------------------------------------------------------------- */
/** \name PoseLib
* \{ */
/* `pose_lib_2.cc` */
void POSELIB_OT_apply_pose_asset(wmOperatorType *ot);
void POSELIB_OT_blend_pose_asset(wmOperatorType *ot);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Pose Sliding Tools
* \{ */
/* `pose_slide.cc` */
void POSE_OT_push(wmOperatorType *ot);
void POSE_OT_relax(wmOperatorType *ot);
void POSE_OT_blend_with_rest(wmOperatorType *ot);
void POSE_OT_breakdown(wmOperatorType *ot);
void POSE_OT_blend_to_neighbors(wmOperatorType *ot);
void POSE_OT_propagate(wmOperatorType *ot);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Various Armature Edit/Pose Editing API's
* \{ */
/* Ideally, many of these defines would not be needed as everything would be strictly
* self-contained within each file,
* but some tools still have a bit of overlap which makes things messy -- Feb 2013
*/
EditBone *make_boneList(ListBaseT<EditBone> *edbo, ListBaseT<Bone> *bones, Bone *actBone);
/* Duplicate method. */
EditBone *duplicateEditBone(EditBone *cur_bone,
const char *name,
ListBaseT<EditBone> *editbones,
Object *ob);
/* Duplicate method (cross objects). */
/**
* \param editbones: The target list.
*/
EditBone *duplicateEditBoneObjects(EditBone *cur_bone,
const char *name,
ListBaseT<EditBone> *editbones,
Object *src_ob,
Object *dst_ob);
/** Adds an EditBone between the nominated locations (should be in the right space). */
EditBone *add_points_bone(Object *obedit, float head[3], float tail[3]);
void bone_free(bArmature *arm, EditBone *bone);
void armature_tag_select_mirrored(bArmature *arm);
/**
* Helper function for tools to work on mirrored parts.
* it leaves mirrored bones selected then too, which is a good indication of what happened.
*/
void armature_select_mirrored_ex(bArmature *arm, eBone_Flag flag);
void armature_select_mirrored(bArmature *arm);
/** Only works when tagged. */
void armature_tag_unselect(bArmature *arm);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Selection Picking
* \{ */
EditBone *ED_armature_pick_ebone_from_selectbuffer(Span<Base *> bases,
const GPUSelectResult *hit_results,
int hits,
bool findunsel,
bool do_nearest,
Base **r_base);
bPoseChannel *ED_armature_pick_pchan_from_selectbuffer(Span<Base *> bases,
const GPUSelectResult *hit_results,
int hits,
bool findunsel,
bool do_nearest,
Base **r_base);
Bone *ED_armature_pick_bone_from_selectbuffer(Span<Base *> bases,
const GPUSelectResult *hit_results,
int hits,
bool findunsel,
bool do_nearest,
Base **r_base);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Iteration
* \{ */
/**
* XXX: bone_looper is only to be used when we want to access settings
* (i.e. editability/visibility/selected) that context doesn't offer.
*/
int bone_looper(Object *ob, Bone *bone, void *data, int (*bone_func)(Object *, Bone *, void *));
/** \} */
} // namespace blender

View File

@@ -0,0 +1,629 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edarmature
* Operators and API's for renaming bones both in and out of Edit Mode.
*
* This file contains functions/API's for renaming bones and/or working with them.
*/
#include <cstring>
#include "DNA_armature_types.h"
#include "DNA_camera_types.h"
#include "DNA_constraint_types.h"
#include "DNA_object_types.h"
#include "BLI_ghash.h"
#include "BLI_listbase_wrapper.hh"
#include "BLI_string.h"
#include "BLI_string_utf8.h"
#include "BLI_string_utils.hh"
#include "BLI_utildefines.h"
#include "BLT_translation.hh"
#include "BKE_action.hh"
#include "BKE_animsys.h"
#include "BKE_armature.hh"
#include "BKE_constraint.h"
#include "BKE_context.hh"
#include "BKE_deform.hh"
#include "BKE_grease_pencil.hh"
#include "BKE_layer.hh"
#include "BKE_main.hh"
#include "BKE_modifier.hh"
#include "DEG_depsgraph.hh"
#include "RNA_access.hh"
#include "RNA_define.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "ED_armature.hh"
#include "ED_screen.hh"
#include "ANIM_armature.hh"
#include "armature_intern.hh"
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Unique Bone Name Utility (Edit Mode)
* \{ */
/* NOTE: there's a ed_armature_bone_unique_name() too! */
static bool editbone_unique_check(ListBaseT<EditBone> *ebones,
const StringRefNull name,
EditBone *bone)
{
if (bone) {
/* This indicates that there is a bone to ignore. This means ED_armature_ebone_find_name()
* cannot be used, as it might return the bone we should be ignoring. */
for (EditBone *ebone : ListBaseWrapper<EditBone>(ebones)) {
if (ebone->name == name && ebone != bone) {
return true;
}
}
return false;
}
EditBone *dupli = ED_armature_ebone_find_name(ebones, name.c_str());
return dupli && dupli != bone;
}
void ED_armature_ebone_unique_name(ListBaseT<EditBone> *ebones, char *name, EditBone *bone)
{
BLI_uniquename_cb(
[&](const StringRefNull check_name) {
return editbone_unique_check(ebones, check_name, bone);
},
DATA_(animrig::bone_default_name),
'.',
name,
sizeof(bone->name));
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Unique Bone Name Utility (Object Mode)
* \{ */
static void ed_armature_bone_unique_name(bArmature *arm, char *name)
{
BLI_uniquename_cb(
[&](const StringRefNull check_name) {
return BKE_armature_find_bone_name(arm, check_name.c_str()) != nullptr;
},
DATA_(animrig::bone_default_name),
'.',
name,
sizeof(Bone::name));
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Bone Renaming (Object & Edit Mode API)
* \{ */
/**
* Helper call for `armature_bone_rename()`.
*
* \param rename_ob: The object whose bone was renamed.
* \param constraint_ob: The object that owns the constraints in `conlist`.
*/
static void constraint_bone_name_fix(Object *rename_ob,
Object *constraint_ob,
ListBaseT<bConstraint> *conlist,
const char *oldname,
const char *newname)
{
for (bConstraint &curcon : *conlist) {
ListBaseT<bConstraintTarget> targets = {nullptr, nullptr};
/* constraint targets */
if (BKE_constraint_targets_get(&curcon, &targets)) {
for (bConstraintTarget &ct : targets) {
if (ct.tar == rename_ob) {
if (STREQ(ct.subtarget, oldname)) {
STRNCPY_UTF8(ct.subtarget, newname);
}
}
}
BKE_constraint_targets_flush(&curcon, &targets, false);
}
/* Actions from action constraints.
*
* We only rename channels in the action if the action constraint and the
* bone rename are from the same object. This is because the action of an
* action constraint animates the constrained object/bone, it does not
* animate the constraint target. */
if (curcon.type == CONSTRAINT_TYPE_ACTION && constraint_ob == rename_ob) {
bActionConstraint *actcon = static_cast<bActionConstraint *>(curcon.data);
BKE_action_fix_paths_rename(&rename_ob->id,
actcon->act,
actcon->action_slot_handle,
"pose.bones",
oldname,
newname,
0,
0,
true);
}
}
}
void ED_armature_bone_rename(Main *bmain,
bArmature *arm,
const char *oldnamep,
const char *newnamep)
{
Object *ob;
char newname[MAXBONENAME];
char oldname[MAXBONENAME];
/* names better differ! */
if (!STREQLEN(oldnamep, newnamep, MAXBONENAME)) {
/* we alter newname string... so make copy */
STRNCPY_UTF8(newname, newnamep);
/* we use oldname for search... so make copy */
STRNCPY(oldname, oldnamep); /* Allow non UTF8 encoding for the old name. */
/* now check if we're in editmode, we need to find the unique name */
if (arm->edbo) {
EditBone *eBone = ED_armature_ebone_find_name(arm->edbo, oldname);
if (eBone) {
ED_armature_ebone_unique_name(arm->edbo, newname, nullptr);
STRNCPY_UTF8(eBone->name, newname);
}
else {
return;
}
}
else {
Bone *bone = BKE_armature_find_bone_name(arm, oldname);
if (bone) {
ed_armature_bone_unique_name(arm, newname);
if (arm->bonehash) {
BLI_assert(BLI_ghash_haskey(arm->bonehash, bone->name));
BLI_ghash_remove(arm->bonehash, bone->name, nullptr, nullptr);
}
STRNCPY_UTF8(bone->name, newname);
if (arm->bonehash) {
BLI_ghash_insert(arm->bonehash, bone->name, bone);
}
}
else {
return;
}
}
/* force evaluation copy to update database */
DEG_id_tag_update(&arm->id, ID_RECALC_SYNC_TO_EVAL);
/* do entire dbase - objects */
for (ob = static_cast<Object *>(bmain->objects.first); ob;
ob = static_cast<Object *>(ob->id.next))
{
/* we have the object using the armature */
if (id_cast<const ID *>(arm) == ob->data) {
Object *cob;
/* Rename the pose channel, if it exists */
if (ob->pose) {
bPoseChannel *pchan = BKE_pose_channel_find_name(ob->pose, oldname);
if (pchan) {
GHash *gh = ob->pose->chanhash;
/* remove the old hash entry, and replace with the new name */
if (gh) {
BLI_assert(BLI_ghash_haskey(gh, pchan->name));
BLI_ghash_remove(gh, pchan->name, nullptr, nullptr);
}
STRNCPY_UTF8(pchan->name, newname);
if (gh) {
BLI_ghash_insert(gh, pchan->name, pchan);
}
}
BLI_assert(BKE_pose_channels_is_valid(ob->pose) == true);
}
/* Update any object constraints to use the new bone name */
for (cob = static_cast<Object *>(bmain->objects.first); cob;
cob = static_cast<Object *>(cob->id.next))
{
if (cob->constraints.first) {
constraint_bone_name_fix(ob, cob, &cob->constraints, oldname, newname);
}
if (cob->pose) {
for (bPoseChannel &pchan : cob->pose->chanbase) {
constraint_bone_name_fix(ob, cob, &pchan.constraints, oldname, newname);
}
}
}
}
/* See if an object is parented to this armature */
if (ob->parent && (ob->parent->data == id_cast<const ID *>(arm))) {
if (ob->partype == PARBONE) {
/* bone name in object */
if (STREQ(ob->parsubstr, oldname)) {
STRNCPY_UTF8(ob->parsubstr, newname);
}
}
}
if (BKE_modifiers_uses_armature(ob, arm) && BKE_object_supports_vertex_groups(ob)) {
if (BKE_object_defgroup_find_name(ob, newname)) {
WM_global_reportf(eReportType::RPT_WARNING,
"New bone name collides with an existing vertex "
"group name, vertex group "
"names are unchanged. (%s::%s)",
&ob->id.name[2],
newname);
/* Not renaming vertex group could cause bone to bind to other vertex group, in this case
* deformation could change, so we tag this object for depsgraph update. */
DEG_id_tag_update(ob->data, ID_RECALC_GEOMETRY);
}
else if (bDeformGroup *dg = BKE_object_defgroup_find_name(ob, oldname)) {
STRNCPY_UTF8(dg->name, newname);
if (ob->type == OB_GREASE_PENCIL) {
/* Update vgroup names stored in CurvesGeometry */
BKE_grease_pencil_vgroup_name_update(ob, oldname, dg->name);
}
DEG_id_tag_update(ob->data, ID_RECALC_GEOMETRY);
}
}
/* fix modifiers that might be using this name */
for (ModifierData &md : ob->modifiers) {
switch (md.type) {
case eModifierType_Hook: {
HookModifierData *hmd = reinterpret_cast<HookModifierData *>(&md);
if (hmd->object && (hmd->object->data == id_cast<const ID *>(arm))) {
if (STREQ(hmd->subtarget, oldname)) {
STRNCPY_UTF8(hmd->subtarget, newname);
}
}
break;
}
case eModifierType_UVWarp: {
UVWarpModifierData *umd = reinterpret_cast<UVWarpModifierData *>(&md);
if (umd->object_src && (umd->object_src->data == id_cast<const ID *>(arm))) {
if (STREQ(umd->bone_src, oldname)) {
STRNCPY_UTF8(umd->bone_src, newname);
}
}
if (umd->object_dst && (umd->object_dst->data == id_cast<const ID *>(arm))) {
if (STREQ(umd->bone_dst, oldname)) {
STRNCPY_UTF8(umd->bone_dst, newname);
}
}
break;
}
default:
break;
}
}
/* fix camera focus */
if (ob->type == OB_CAMERA) {
Camera *cam = id_cast<Camera *>(ob->data);
if ((cam->dof.focus_object != nullptr) &&
(cam->dof.focus_object->data == id_cast<const ID *>(arm)))
{
if (STREQ(cam->dof.focus_subtarget, oldname)) {
STRNCPY_UTF8(cam->dof.focus_subtarget, newname);
DEG_id_tag_update(&cam->id, ID_RECALC_SYNC_TO_EVAL);
}
}
}
if (ob->type == OB_GREASE_PENCIL) {
GreasePencil &grease_pencil = *id_cast<GreasePencil *>(ob->data);
for (bke::greasepencil::Layer *layer : grease_pencil.layers_for_write()) {
Object *parent = layer->parent;
if (parent == nullptr) {
continue;
}
StringRefNull bone_name = layer->parent_bone_name();
if (!bone_name.is_empty() && bone_name == StringRef(oldname)) {
layer->set_parent_bone_name(newname);
}
}
}
DEG_id_tag_update(&ob->id, ID_RECALC_SYNC_TO_EVAL);
}
/* Fix all animdata that may refer to this bone -
* we can't just do the ones attached to objects,
* since other ID-blocks may have drivers referring to this bone #29822. */
/* XXX: the ID here is for armatures,
* but most bone drivers are actually on the object instead. */
{
BKE_animdata_fix_paths_rename_all(&arm->id, "bones", oldname, newname);
}
/* correct view locking */
{
bScreen *screen;
for (screen = static_cast<bScreen *>(bmain->screens.first); screen;
screen = static_cast<bScreen *>(screen->id.next))
{
/* add regions */
for (ScrArea &area : screen->areabase) {
for (SpaceLink &sl : area.spacedata) {
if (sl.spacetype == SPACE_VIEW3D) {
View3D *v3d = reinterpret_cast<View3D *>(&sl);
if (v3d->ob_center && v3d->ob_center->data == id_cast<const ID *>(arm)) {
if (STREQ(v3d->ob_center_bone, oldname)) {
STRNCPY_UTF8(v3d->ob_center_bone, newname);
}
}
}
}
}
}
}
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Bone Flipping (Object & Edit Mode API)
* \{ */
struct BoneFlipNameData {
BoneFlipNameData *next, *prev;
char *name;
char name_flip[MAXBONENAME];
};
void ED_armature_bones_flip_names(Main *bmain,
bArmature *arm,
ListBaseT<LinkData> *bones_names,
const bool do_strip_numbers)
{
ListBaseT<BoneFlipNameData> bones_names_conflicts = {nullptr};
BoneFlipNameData *bfn;
/* First pass: generate flip names, and blindly rename.
* If rename did not yield expected result,
* store both bone's name and expected flipped one into temp list for second pass. */
for (LinkData &link : *bones_names) {
char name_flip[MAXBONENAME];
char *name = static_cast<char *>(link.data);
/* WARNING: if do_strip_numbers is set, expect completely mismatched names in cases like
* Bone.R, Bone.R.001, Bone.R.002, etc. */
BLI_string_flip_side_name(name_flip, name, do_strip_numbers, sizeof(name_flip));
ED_armature_bone_rename(bmain, arm, name, name_flip);
if (!STREQ(name, name_flip)) {
bfn = static_cast<BoneFlipNameData *>(alloca(sizeof(BoneFlipNameData)));
bfn->name = name;
STRNCPY_UTF8(bfn->name_flip, name_flip);
BLI_addtail(&bones_names_conflicts, bfn);
}
}
/* Second pass to handle the bones that have naming conflicts with other bones.
* Note that if the other bone was not selected, its name was not flipped,
* so conflict remains and that second rename simply generates a new numbered alternative name.
*/
for (BoneFlipNameData &bfn : bones_names_conflicts) {
ED_armature_bone_rename(bmain, arm, bfn.name, bfn.name_flip);
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Flip Bone Names (Edit Mode Operator)
* \{ */
static wmOperatorStatus armature_flip_names_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
const Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
Object *ob_active = CTX_data_edit_object(C);
const bool do_strip_numbers = RNA_boolean_get(op->ptr, "do_strip_numbers");
Vector<Object *> objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(
*bmain, scene, view_layer, CTX_wm_view3d(C));
for (Object *ob : objects) {
bArmature *arm = id_cast<bArmature *>(ob->data);
/* Paranoia check. */
if (ob_active->pose == nullptr) {
continue;
}
ListBaseT<LinkData> bones_names = {nullptr};
for (EditBone &ebone : *arm->edbo) {
if (animrig::bone_is_selected(arm, &ebone)) {
BLI_addtail(&bones_names, BLI_genericNodeN(ebone.name));
if (arm->flag & ARM_MIRROR_EDIT) {
EditBone *flipbone = ED_armature_ebone_get_mirrored(arm->edbo, &ebone);
if ((flipbone) && !(flipbone->flag & BONE_SELECTED)) {
BLI_addtail(&bones_names, BLI_genericNodeN(flipbone->name));
}
}
}
}
if (bones_names.is_empty()) {
continue;
}
ED_armature_bones_flip_names(bmain, arm, &bones_names, do_strip_numbers);
bones_names.free_no_destruct();
/* since we renamed stuff... */
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
/* copied from #rna_Bone_update_renamed */
/* Redraw Outliner / Dope-sheet. */
WM_event_add_notifier(C, NC_GEOM | ND_DATA | NA_RENAME, ob->data);
/* update animation channels */
WM_event_add_notifier(C, NC_ANIMATION | ND_ANIMCHAN, ob->data);
}
return OPERATOR_FINISHED;
}
void ARMATURE_OT_flip_names(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Flip Names";
ot->idname = "ARMATURE_OT_flip_names";
ot->description = "Flips (and corrects) the axis suffixes of the names of selected bones";
/* API callbacks. */
ot->exec = armature_flip_names_exec;
ot->poll = ED_operator_editarmature;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
RNA_def_boolean(ot->srna,
"do_strip_numbers",
false,
"Strip Numbers",
"Try to remove right-most dot-number from flipped names.\n"
"Warning: May result in incoherent naming in some cases");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Bone Auto Side Names (Edit Mode Operator)
* \{ */
static wmOperatorStatus armature_autoside_names_exec(bContext *C, wmOperator *op)
{
const Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
Main *bmain = CTX_data_main(C);
char newname[MAXBONENAME];
const short axis = RNA_enum_get(op->ptr, "type");
bool changed_multi = false;
Vector<Object *> objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(
*bmain, scene, view_layer, CTX_wm_view3d(C));
for (Object *ob : objects) {
bArmature *arm = id_cast<bArmature *>(ob->data);
bool changed = false;
/* Paranoia checks. */
if (ELEM(nullptr, ob, ob->pose)) {
continue;
}
for (EditBone &ebone : *arm->edbo) {
if (EBONE_EDITABLE(&ebone)) {
/* We first need to do the flipped bone, then the original one.
* Otherwise we can't find the flipped one because of the bone name change. */
if (arm->flag & ARM_MIRROR_EDIT) {
EditBone *flipbone = ED_armature_ebone_get_mirrored(arm->edbo, &ebone);
if ((flipbone) && !(flipbone->flag & BONE_SELECTED)) {
STRNCPY_UTF8(newname, flipbone->name);
if (bone_autoside_name(newname, 1, axis, flipbone->head[axis], flipbone->tail[axis])) {
ED_armature_bone_rename(bmain, arm, flipbone->name, newname);
changed = true;
}
}
}
STRNCPY_UTF8(newname, ebone.name);
if (bone_autoside_name(newname, 1, axis, ebone.head[axis], ebone.tail[axis])) {
ED_armature_bone_rename(bmain, arm, ebone.name, newname);
changed = true;
}
}
}
if (!changed) {
continue;
}
changed_multi = true;
/* Since we renamed stuff... */
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
WM_event_add_notifier(C, NC_GEOM | ND_DATA | NA_RENAME, ob->data);
/* Update animation channels */
WM_event_add_notifier(C, NC_ANIMATION | ND_ANIMCHAN, ob->data);
/* NOTE: notifier might evolve. */
WM_event_add_notifier(C, NC_OBJECT | ND_POSE, ob);
}
return changed_multi ? OPERATOR_FINISHED : OPERATOR_CANCELLED;
}
void ARMATURE_OT_autoside_names(wmOperatorType *ot)
{
static const EnumPropertyItem axis_items[] = {
{0, "XAXIS", 0, "X-Axis", "Left/Right"},
{1, "YAXIS", 0, "Y-Axis", "Front/Back"},
{2, "ZAXIS", 0, "Z-Axis", "Top/Bottom"},
{0, nullptr, 0, nullptr, nullptr},
};
/* identifiers */
ot->name = "Auto-Name by Axis";
ot->idname = "ARMATURE_OT_autoside_names";
ot->description =
"Automatically renames the selected bones according to which side of the target axis they "
"fall on";
/* API callbacks. */
ot->invoke = WM_menu_invoke;
ot->exec = armature_autoside_names_exec;
ot->poll = ED_operator_editarmature;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* settings */
ot->prop = RNA_def_enum(ot->srna, "type", axis_items, 0, "Axis", "Axis to tag names with");
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,181 @@
/* SPDX-FileCopyrightText: 2009 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edarmature
*/
#include "RNA_access.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "ED_armature.hh"
#include "ED_screen.hh"
#include "armature_intern.hh"
namespace blender {
/* ************************** registration **********************************/
void ED_operatortypes_armature()
{
/* Both operators `ARMATURE_OT_*` and `POSE_OT_*` are registered here. */
/* EDIT ARMATURE */
WM_operatortype_append(ARMATURE_OT_bone_primitive_add);
WM_operatortype_append(ARMATURE_OT_align);
WM_operatortype_append(ARMATURE_OT_calculate_roll);
WM_operatortype_append(ARMATURE_OT_roll_clear);
WM_operatortype_append(ARMATURE_OT_switch_direction);
WM_operatortype_append(ARMATURE_OT_subdivide);
WM_operatortype_append(ARMATURE_OT_parent_set);
WM_operatortype_append(ARMATURE_OT_parent_clear);
WM_operatortype_append(ARMATURE_OT_select_all);
WM_operatortype_append(ARMATURE_OT_select_mirror);
WM_operatortype_append(ARMATURE_OT_select_more);
WM_operatortype_append(ARMATURE_OT_select_less);
WM_operatortype_append(ARMATURE_OT_select_hierarchy);
WM_operatortype_append(ARMATURE_OT_select_linked);
WM_operatortype_append(ARMATURE_OT_select_linked_pick);
WM_operatortype_append(ARMATURE_OT_select_similar);
WM_operatortype_append(ARMATURE_OT_shortest_path_pick);
WM_operatortype_append(ARMATURE_OT_delete);
WM_operatortype_append(ARMATURE_OT_dissolve);
WM_operatortype_append(ARMATURE_OT_duplicate);
WM_operatortype_append(ARMATURE_OT_duplicate_rename);
WM_operatortype_append(ARMATURE_OT_symmetrize);
WM_operatortype_append(ARMATURE_OT_extrude);
WM_operatortype_append(ARMATURE_OT_hide);
WM_operatortype_append(ARMATURE_OT_reveal);
WM_operatortype_append(ARMATURE_OT_click_extrude);
WM_operatortype_append(ARMATURE_OT_fill);
WM_operatortype_append(ARMATURE_OT_separate);
WM_operatortype_append(ARMATURE_OT_split);
WM_operatortype_append(ARMATURE_OT_autoside_names);
WM_operatortype_append(ARMATURE_OT_flip_names);
WM_operatortype_append(ARMATURE_OT_collection_add);
WM_operatortype_append(ARMATURE_OT_collection_remove);
WM_operatortype_append(ARMATURE_OT_collection_move);
WM_operatortype_append(ARMATURE_OT_collection_assign);
WM_operatortype_append(ARMATURE_OT_collection_create_and_assign);
WM_operatortype_append(ARMATURE_OT_collection_unassign);
WM_operatortype_append(ARMATURE_OT_collection_unassign_named);
WM_operatortype_append(ARMATURE_OT_collection_select);
WM_operatortype_append(ARMATURE_OT_collection_deselect);
WM_operatortype_append(ARMATURE_OT_move_to_collection);
WM_operatortype_append(ARMATURE_OT_assign_to_collection);
/* POSE */
WM_operatortype_append(POSE_OT_hide);
WM_operatortype_append(POSE_OT_reveal);
WM_operatortype_append(POSE_OT_armature_apply);
WM_operatortype_append(POSE_OT_visual_transform_apply);
WM_operatortype_append(POSE_OT_rot_clear);
WM_operatortype_append(POSE_OT_loc_clear);
WM_operatortype_append(POSE_OT_scale_clear);
WM_operatortype_append(POSE_OT_transforms_clear);
WM_operatortype_append(POSE_OT_user_transforms_clear);
WM_operatortype_append(POSE_OT_copy);
WM_operatortype_append(POSE_OT_paste);
WM_operatortype_append(POSE_OT_select_all);
WM_operatortype_append(POSE_OT_select_parent);
WM_operatortype_append(POSE_OT_select_hierarchy);
WM_operatortype_append(POSE_OT_select_linked);
WM_operatortype_append(POSE_OT_select_linked_pick);
WM_operatortype_append(POSE_OT_select_constraint_target);
WM_operatortype_append(POSE_OT_select_grouped);
WM_operatortype_append(POSE_OT_select_mirror);
WM_operatortype_append(POSE_OT_paths_calculate);
WM_operatortype_append(POSE_OT_paths_update);
WM_operatortype_append(POSE_OT_paths_clear);
WM_operatortype_append(POSE_OT_paths_range_update);
WM_operatortype_append(POSE_OT_autoside_names);
WM_operatortype_append(POSE_OT_flip_names);
WM_operatortype_append(POSE_OT_rotation_mode_set);
WM_operatortype_append(POSE_OT_quaternions_flip);
WM_operatortype_append(POSE_OT_propagate);
/* POSELIB */
WM_operatortype_append(POSELIB_OT_apply_pose_asset);
WM_operatortype_append(POSELIB_OT_blend_pose_asset);
/* POSE SLIDING */
WM_operatortype_append(POSE_OT_push);
WM_operatortype_append(POSE_OT_relax);
WM_operatortype_append(POSE_OT_blend_with_rest);
WM_operatortype_append(POSE_OT_breakdown);
WM_operatortype_append(POSE_OT_blend_to_neighbors);
}
void ED_operatormacros_armature()
{
wmOperatorType *ot;
wmOperatorTypeMacro *otmacro;
ot = WM_operatortype_append_macro(
"ARMATURE_OT_duplicate_move",
"Duplicate",
"Make copies of the selected bones within the same armature and move them",
OPTYPE_UNDO | OPTYPE_REGISTER);
WM_operatortype_macro_define(ot, "ARMATURE_OT_duplicate");
otmacro = WM_operatortype_macro_define(ot, "TRANSFORM_OT_translate");
RNA_boolean_set(otmacro->ptr, "use_proportional_edit", false);
ot = WM_operatortype_append_macro("ARMATURE_OT_extrude_move",
"Extrude",
"Create new bones from the selected joints and move them",
OPTYPE_UNDO | OPTYPE_REGISTER);
otmacro = WM_operatortype_macro_define(ot, "ARMATURE_OT_extrude");
RNA_boolean_set(otmacro->ptr, "forked", false);
otmacro = WM_operatortype_macro_define(ot, "TRANSFORM_OT_translate");
RNA_boolean_set(otmacro->ptr, "use_proportional_edit", false);
/* XXX would it be nicer to just be able to have standard extrude_move,
* but set the forked property separate?
* that would require fixing a properties bug #19733. */
ot = WM_operatortype_append_macro("ARMATURE_OT_extrude_forked",
"Extrude Forked",
"Create new bones from the selected joints and move them",
OPTYPE_UNDO | OPTYPE_REGISTER);
otmacro = WM_operatortype_macro_define(ot, "ARMATURE_OT_extrude");
RNA_boolean_set(otmacro->ptr, "forked", true);
otmacro = WM_operatortype_macro_define(ot, "TRANSFORM_OT_translate");
RNA_boolean_set(otmacro->ptr, "use_proportional_edit", false);
}
void ED_keymap_armature(wmKeyConfig *keyconf)
{
wmKeyMap *keymap;
/* Armature ------------------------ */
/* only set in editmode armature, by space_view3d listener */
keymap = WM_keymap_ensure(keyconf, "Armature", SPACE_EMPTY, RGN_TYPE_WINDOW);
keymap->poll = ED_operator_editarmature;
/* Pose ------------------------ */
/* only set in posemode, by space_view3d listener */
keymap = WM_keymap_ensure(keyconf, "Pose", SPACE_EMPTY, RGN_TYPE_WINDOW);
keymap->poll = ED_operator_posemode;
}
} // namespace blender

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,514 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edarmature
* API's for creating vertex groups from bones
* - Interfaces with heat weighting in meshlaplacian.
*/
#include "DNA_armature_types.h"
#include "DNA_mesh_types.h"
#include "DNA_object_types.h"
#include "MEM_guardedalloc.h"
#include "BLI_math_matrix.h"
#include "BLI_math_vector.h"
#include "BLI_string_utils.hh"
#include "BKE_action.hh"
#include "BKE_armature.hh"
#include "BKE_attribute.hh"
#include "BKE_deform.hh"
#include "BKE_mesh_iterators.hh"
#include "BKE_modifier.hh"
#include "BKE_object.hh"
#include "BKE_object_deform.h"
#include "BKE_report.hh"
#include "BKE_subdiv_mesh.hh"
#include "DEG_depsgraph.hh"
#include "DEG_depsgraph_query.hh"
#include "ED_armature.hh"
#include "ED_mesh.hh"
#include "ED_object_vgroup.hh"
#include "ANIM_bone_collections.hh"
#include "armature_intern.hh"
#include "meshlaplacian.h"
namespace blender {
/* ******************************* Bone Skinning *********************************************** */
static int bone_skinnable_cb(Object * /*ob*/, Bone *bone, void *datap)
{
/* Bones that are deforming
* are regarded to be "skinnable" and are eligible for
* auto-skinning.
*
* This function performs 2 functions:
*
* a) It returns 1 if the bone is skinnable.
* If we loop over all bones with this
* function, we can count the number of
* skinnable bones.
* b) If the pointer data is non null,
* it is treated like a handle to a
* bone pointer -- the bone pointer
* is set to point at this bone, and
* the pointer the handle points to
* is incremented to point to the
* next member of an array of pointers
* to bones. This way we can loop using
* this function to construct an array of
* pointers to bones that point to all
* skinnable bones.
*/
Bone ***hbone;
int a, segments;
struct Arg {
Object *armob;
void *list;
int heat;
bool is_weight_paint;
} *data = static_cast<Arg *>(datap);
bPoseChannel *pose_bone = BKE_pose_channel_find_name(data->armob->pose, bone->name);
if (!pose_bone) {
return 0;
}
if (!(data->is_weight_paint) || !(pose_bone->drawflag & PCHAN_DRAW_HIDDEN)) {
if (!(bone->flag & BONE_NO_DEFORM)) {
if (data->heat && data->armob->pose && pose_bone) {
segments = bone->segments;
}
else {
segments = 1;
}
if (data->list != nullptr) {
hbone = reinterpret_cast<Bone ***>(&data->list);
for (a = 0; a < segments; a++) {
**hbone = bone;
(*hbone)++;
}
}
return segments;
}
}
return 0;
}
static int vgroup_add_unique_bone_cb(Object *ob, Bone *bone, void * /*ptr*/)
{
/* This group creates a vertex group to ob that has the
* same name as bone (provided the bone is skinnable).
* If such a vertex group already exist the routine exits.
*/
if (!(bone->flag & BONE_NO_DEFORM)) {
if (!BKE_object_defgroup_find_name(ob, bone->name)) {
BKE_object_defgroup_add_name(ob, bone->name);
return 1;
}
}
return 0;
}
static int dgroup_skinnable_cb(Object *ob, Bone *bone, void *datap)
{
/* Bones that are deforming
* are regarded to be "skinnable" and are eligible for
* auto-skinning.
*
* This function performs 2 functions:
*
* a) If the bone is skinnable, it creates
* a vertex group for ob that has
* the name of the skinnable bone
* (if one doesn't exist already).
* b) If the pointer data is non null,
* it is treated like a handle to a
* bDeformGroup pointer -- the
* bDeformGroup pointer is set to point
* to the deform group with the bone's
* name, and the pointer the handle
* points to is incremented to point to the
* next member of an array of pointers
* to bDeformGroups. This way we can loop using
* this function to construct an array of
* pointers to bDeformGroups, all with names
* of skinnable bones.
*/
bDeformGroup ***hgroup, *defgroup = nullptr;
int a, segments;
struct Arg {
Object *armob;
void *list;
int heat;
bool is_weight_paint;
} *data = static_cast<Arg *>(datap);
if (bone->flag & BONE_NO_DEFORM) {
return 0;
}
bArmature *arm = id_cast<bArmature *>(data->armob->data);
const bPoseChannel *pose_bone = BKE_pose_channel_find_name(data->armob->pose, bone->name);
if (!pose_bone) {
return 0;
}
if (data->is_weight_paint && (pose_bone->drawflag & PCHAN_DRAW_HIDDEN)) {
return 0;
}
if (data->heat) {
segments = bone->segments;
}
else {
segments = 1;
}
if (!data->is_weight_paint ||
(ANIM_bone_in_visible_collection(arm, bone) && (pose_bone->flag & POSE_SELECTED)))
{
if (!(defgroup = BKE_object_defgroup_find_name(ob, bone->name))) {
defgroup = BKE_object_defgroup_add_name(ob, bone->name);
}
else if (defgroup->flag & DG_LOCK_WEIGHT) {
/* In case vgroup already exists and is locked, do not modify it here. See #43814. */
defgroup = nullptr;
}
}
if (data->list != nullptr) {
hgroup = reinterpret_cast<bDeformGroup ***>(&data->list);
for (a = 0; a < segments; a++) {
**hgroup = defgroup;
(*hgroup)++;
}
}
return segments;
}
static void envelope_bone_weighting(Object *ob,
Mesh *mesh,
const Span<float3> verts,
int numbones,
Bone **bonelist,
bDeformGroup **dgrouplist,
bDeformGroup **dgroupflip,
float (*root)[3],
float (*tip)[3],
const bool *selected,
float scale)
{
/* Create vertex group weights from envelopes */
bool use_topology = (mesh->editflag & ME_EDIT_MIRROR_TOPO) != 0;
bool use_mask = false;
if ((ob->mode & OB_MODE_WEIGHT_PAINT) &&
(mesh->editflag & (ME_EDIT_PAINT_FACE_SEL | ME_EDIT_PAINT_VERT_SEL)))
{
use_mask = true;
}
const bke::AttributeAccessor attributes = mesh->attributes();
const VArray select_vert = *attributes.lookup<bool>(".select_vert", bke::AttrDomain::Point);
/* for each vertex in the mesh */
for (int i = 0; i < mesh->verts_num; i++) {
if (use_mask && !(select_vert && select_vert[i])) {
continue;
}
int iflip = (dgroupflip) ? mesh_get_x_mirror_vert(ob, nullptr, i, use_topology) : -1;
/* for each skinnable bone */
for (int j = 0; j < numbones; j++) {
if (selected[j] == false) {
continue;
}
Bone *bone = bonelist[j];
bDeformGroup *dgroup = dgrouplist[j];
/* store the distance-factor from the vertex to the bone */
float distance = distfactor_to_bone(verts[i],
root[j],
tip[j],
bone->rad_head * scale,
bone->rad_tail * scale,
bone->dist * scale);
/* add the vert to the deform group if (weight != 0.0) */
if (distance != 0.0f) {
ed::object::vgroup_vert_add(ob, dgroup, i, distance, WEIGHT_REPLACE);
}
else {
ed::object::vgroup_vert_remove(ob, dgroup, i);
}
/* do same for mirror */
if (dgroupflip && dgroupflip[j] && iflip != -1) {
if (distance != 0.0f) {
ed::object::vgroup_vert_add(ob, dgroupflip[j], iflip, distance, WEIGHT_REPLACE);
}
else {
ed::object::vgroup_vert_remove(ob, dgroupflip[j], iflip);
}
}
}
}
}
static void add_verts_to_dgroups(
ReportList *reports, Scene * /*scene*/, Object *ob, Object *par, int heat, const bool mirror)
{
/* This functions implements the automatic computation of vertex group
* weights, either through envelopes or using a heat equilibrium.
*
* This function can be called both when parenting a mesh to an armature,
* or in weight-paint + pose-mode. In the latter case selection is taken
* into account and vertex weights can be mirrored.
*
* The mesh vertex positions used are either the final deformed coords
* from the evaluated mesh in weight-paint mode, the final sub-surface coords
* when parenting, or simply the original mesh coords.
*/
bArmature *arm = id_cast<bArmature *>(par->data);
Bone **bonelist, *bone;
bDeformGroup **dgrouplist, **dgroupflip;
bDeformGroup *dgroup;
bPoseChannel *pchan;
Mesh *mesh;
Mat4 bbone_array[MAX_BBONE_SUBDIV], *bbone = nullptr;
float (*root)[3], (*tip)[3];
Array<float3> verts;
bool *selected;
int numbones, vertsfilled = 0, segments = 0;
const bool wpmode = (ob->mode & OB_MODE_WEIGHT_PAINT);
struct {
Object *armob;
void *list;
int heat;
bool is_weight_paint;
} looper_data;
looper_data.armob = par;
looper_data.heat = heat;
looper_data.list = nullptr;
looper_data.is_weight_paint = wpmode;
if (!par->pose) {
BKE_pose_rebuild(nullptr, par, arm, false);
}
BKE_pose_channels_hash_ensure(par->pose);
/* count the number of skinnable bones */
numbones = bone_looper(
ob, static_cast<Bone *>(arm->bonebase.first), &looper_data, bone_skinnable_cb);
if (numbones == 0) {
return;
}
if (BKE_object_defgroup_data_create(ob->data) == nullptr) {
return;
}
/* create an array of pointer to bones that are skinnable
* and fill it with all of the skinnable bones */
bonelist = MEM_new_array_zeroed<Bone *>(numbones, "bonelist");
looper_data.list = bonelist;
bone_looper(ob, static_cast<Bone *>(arm->bonebase.first), &looper_data, bone_skinnable_cb);
/* create an array of pointers to the deform groups that
* correspond to the skinnable bones (creating them
* as necessary). */
dgrouplist = MEM_new_array_zeroed<bDeformGroup *>(numbones, "dgrouplist");
dgroupflip = MEM_new_array_zeroed<bDeformGroup *>(numbones, "dgroupflip");
looper_data.list = dgrouplist;
bone_looper(ob, static_cast<Bone *>(arm->bonebase.first), &looper_data, dgroup_skinnable_cb);
/* create an array of root and tip positions transformed into
* global coords */
root = MEM_new_array_zeroed<float[3]>(numbones, "root");
tip = MEM_new_array_zeroed<float[3]>(numbones, "tip");
selected = MEM_new_array_zeroed<bool>(numbones, "selected");
for (int j = 0; j < numbones; j++) {
bone = bonelist[j];
dgroup = dgrouplist[j];
/* handle bbone */
if (heat) {
if (segments == 0) {
segments = 1;
bbone = nullptr;
if ((par->pose) && (pchan = BKE_pose_channel_find_name(par->pose, bone->name))) {
if (bone->segments > 1) {
segments = bone->segments;
BKE_pchan_bbone_spline_setup({pchan, bone}, *arm, true, false, bbone_array);
bbone = bbone_array;
}
}
}
segments--;
}
/* compute root and tip */
if (bbone) {
mul_v3_m4v3(root[j], bone->arm_mat, bbone[segments].mat[3]);
if ((segments + 1) < bone->segments) {
mul_v3_m4v3(tip[j], bone->arm_mat, bbone[segments + 1].mat[3]);
}
else {
copy_v3_v3(tip[j], bone->arm_tail);
}
}
else {
copy_v3_v3(root[j], bone->arm_head);
copy_v3_v3(tip[j], bone->arm_tail);
}
mul_m4_v3(par->object_to_world().ptr(), root[j]);
mul_m4_v3(par->object_to_world().ptr(), tip[j]);
/* set selected */
if (wpmode) {
if (ANIM_bone_in_visible_collection(arm, bone)) {
if ((pchan = BKE_pose_channel_find_name(par->pose, bone->name))) {
selected[j] = pchan->flag & POSE_SELECTED;
}
}
}
else {
selected[j] = true;
}
/* find flipped group */
if (dgroup && mirror) {
char name_flip[MAXBONENAME];
BLI_string_flip_side_name(name_flip, dgroup->name, false, sizeof(name_flip));
dgroupflip[j] = BKE_object_defgroup_find_name(ob, name_flip);
}
}
/* create verts */
mesh = id_cast<Mesh *>(ob->data);
verts.reinitialize(mesh->verts_num);
if (BKE_modifiers_findby_type(ob, eModifierType_Subsurf)) {
/* Is subdivision-surface on? Lets use the verts on the limit surface then.
* = same amount of vertices as mesh, but vertices moved to the
* subdivision-surfaced position, like for 'optimal'. */
bke::subdiv::calculate_limit_positions(mesh, verts);
vertsfilled = 1;
}
/* transform verts to global space */
const Span<float3> positions = mesh->vert_positions();
for (int i = 0; i < mesh->verts_num; i++) {
if (!vertsfilled) {
copy_v3_v3(verts[i], positions[i]);
}
mul_m4_v3(ob->object_to_world().ptr(), verts[i]);
}
/* compute the weights based on gathered vertices and bones */
if (heat) {
const char *error = nullptr;
heat_bone_weighting(ob,
mesh,
reinterpret_cast<float (*)[3]>(verts.data()),
numbones,
dgrouplist,
dgroupflip,
root,
tip,
selected,
&error);
if (error) {
BKE_report(reports, RPT_WARNING, error);
}
}
else {
envelope_bone_weighting(ob,
mesh,
verts,
numbones,
bonelist,
dgrouplist,
dgroupflip,
root,
tip,
selected,
mat4_to_scale(par->object_to_world().ptr()));
}
/* only generated in some cases but can call anyway */
ED_mesh_mirror_spatial_table_end(ob);
/* free the memory allocated */
MEM_delete(bonelist);
MEM_delete(dgrouplist);
MEM_delete(dgroupflip);
MEM_delete(root);
MEM_delete(tip);
MEM_delete(selected);
}
void ED_object_vgroup_calc_from_armature(ReportList *reports,
Depsgraph * /* depsgraph */,
Scene *scene,
Object *ob,
Object *par,
const int mode,
const bool mirror)
{
/* Lets try to create some vertex groups
* based on the bones of the parent armature.
*/
bArmature *arm = id_cast<bArmature *>(par->data);
if (mode == ARM_GROUPS_NAME) {
const int defbase_tot = BKE_object_defgroup_count(ob);
int defbase_add;
/* Traverse the bone list, trying to create empty vertex
* groups corresponding to the bone.
*/
defbase_add = bone_looper(
ob, static_cast<Bone *>(arm->bonebase.first), nullptr, vgroup_add_unique_bone_cb);
if (defbase_add) {
/* It's possible there are DWeights outside the range of the current
* object's deform groups. In this case the new groups won't be empty #33889. */
ed::object::vgroup_data_clamp_range(ob->data, defbase_tot);
}
}
else if (ELEM(mode, ARM_GROUPS_ENVELOPE, ARM_GROUPS_AUTO)) {
/* Traverse the bone list, trying to create vertex groups
* that are populated with the vertices for which the
* bone is closest.
*/
add_verts_to_dgroups(reports, scene, ob, par, (mode == ARM_GROUPS_AUTO), mirror);
}
}
} // namespace blender

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,330 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edarmature
*/
#include "MEM_guardedalloc.h"
#include "CLG_log.h"
#include "DNA_armature_types.h"
#include "DNA_layer_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "BLI_array_utils.h"
#include "BLI_listbase.h"
#include "BLI_map.hh"
#include "BLI_string.h"
#include "BKE_armature.hh"
#include "BKE_context.hh"
#include "BKE_layer.hh"
#include "BKE_main.hh"
#include "BKE_object.hh"
#include "BKE_undo_system.hh"
#include "DEG_depsgraph.hh"
#include "ED_armature.hh"
#include "ED_object.hh"
#include "ED_undo.hh"
#include "ED_util.hh"
#include "ANIM_bone_collections.hh"
#include "WM_api.hh"
#include "WM_types.hh"
namespace blender {
using namespace blender::animrig;
/** We only need this locally. */
static CLG_LogRef LOG = {"undo.armature"};
/* Utility functions. */
/**
* Remaps edit-bone collection membership.
*
* This is intended to be used in combination with ED_armature_ebone_listbase_copy()
* and ANIM_bonecoll_listbase_copy() to make a full duplicate of both edit
* bones and collections together.
*/
static void remap_ebone_bone_collection_references(
ListBaseT<EditBone> *edit_bones, const Map<BoneCollection *, BoneCollection *> &bcoll_map)
{
for (EditBone &ebone : *edit_bones) {
for (BoneCollectionReference &bcoll_ref : ebone.bone_collections) {
bcoll_ref.bcoll = bcoll_map.lookup(bcoll_ref.bcoll);
}
}
}
/* -------------------------------------------------------------------- */
/** \name Undo Conversion
* \{ */
struct UndoArmature {
EditBone *act_edbone;
char active_collection_name[MAX_NAME];
ListBaseT<EditBone> ebones;
BoneCollection **collection_array;
int collection_array_num;
int collection_root_count;
size_t undo_size;
};
static void undoarm_to_editarm(UndoArmature *uarm, bArmature *arm)
{
/* Copy edit bones. */
ED_armature_ebone_listbase_free(arm->edbo, true);
ED_armature_ebone_listbase_copy(arm->edbo, &uarm->ebones, true);
/* Active bone. */
if (uarm->act_edbone) {
EditBone *ebone;
ebone = uarm->act_edbone;
arm->act_edbone = ebone->temp.ebone;
}
else {
arm->act_edbone = nullptr;
}
ED_armature_ebone_listbase_temp_clear(arm->edbo);
/* Before freeing the old bone collections, copy their 'expanded' flag. This
* flag is not supposed to be restored with any undo steps. */
bonecolls_copy_expanded_flag(Span(uarm->collection_array, uarm->collection_array_num),
arm->collections_span());
/* Copy bone collections. */
ANIM_bonecoll_array_free(&arm->collection_array, &arm->collection_array_num, true);
auto bcoll_map = ANIM_bonecoll_array_copy_no_membership(&arm->collection_array,
&arm->collection_array_num,
uarm->collection_array,
uarm->collection_array_num,
true);
arm->collection_root_count = uarm->collection_root_count;
/* Always do a lookup-by-name and assignment. Even when the name of the active collection is
* still the same, the order may have changed and thus the index needs to be updated. */
BoneCollection *active_bcoll = ANIM_armature_bonecoll_get_by_name(arm,
uarm->active_collection_name);
ANIM_armature_bonecoll_active_set(arm, active_bcoll);
remap_ebone_bone_collection_references(arm->edbo, bcoll_map);
ANIM_armature_runtime_refresh(arm);
}
static void *undoarm_from_editarm(UndoArmature *uarm, bArmature *arm)
{
BLI_assert(BLI_array_is_zeroed(uarm, 1));
/* Copy edit bones. */
ED_armature_ebone_listbase_copy(&uarm->ebones, arm->edbo, false);
/* Active bone. */
if (arm->act_edbone) {
EditBone *ebone = arm->act_edbone;
uarm->act_edbone = ebone->temp.ebone;
}
ED_armature_ebone_listbase_temp_clear(&uarm->ebones);
/* Copy bone collections. */
auto bcoll_map = ANIM_bonecoll_array_copy_no_membership(&uarm->collection_array,
&uarm->collection_array_num,
arm->collection_array,
arm->collection_array_num,
false);
STRNCPY(uarm->active_collection_name, arm->active_collection_name);
uarm->collection_root_count = arm->collection_root_count;
/* Point the new edit bones at the new collections. */
remap_ebone_bone_collection_references(&uarm->ebones, bcoll_map);
/* Undo size.
* TODO: include size of ID-properties. */
uarm->undo_size = 0;
for (EditBone &ebone : uarm->ebones) {
uarm->undo_size += sizeof(EditBone);
uarm->undo_size += sizeof(BoneCollectionReference) * ebone.bone_collections.count();
}
/* Size of the bone collections + the size of the pointers to those
* bone collections in the bone collection array. */
uarm->undo_size += (sizeof(BoneCollection) + sizeof(BoneCollection *)) *
uarm->collection_array_num;
return uarm;
}
static void undoarm_free_data(UndoArmature *uarm)
{
ED_armature_ebone_listbase_free(&uarm->ebones, false);
ANIM_bonecoll_array_free(&uarm->collection_array, &uarm->collection_array_num, false);
}
static Object *editarm_object_from_context(bContext *C)
{
const Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
BKE_view_layer_synced_ensure(*bmain, scene, view_layer);
Object *obedit = BKE_view_layer_edit_object_get(view_layer);
if (obedit && obedit->type == OB_ARMATURE) {
bArmature *arm = id_cast<bArmature *>(obedit->data);
if (arm->edbo != nullptr) {
return obedit;
}
}
return nullptr;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Implements ED Undo System
*
* \note This is similar for all edit-mode types.
* \{ */
struct ArmatureUndoStep_Elem {
ArmatureUndoStep_Elem *next, *prev;
UndoRefID_Object obedit_ref;
UndoArmature data;
};
struct ArmatureUndoStep {
UndoStep step;
/** See #ED_undo_object_editmode_validate_scene_from_windows code comment for details. */
UndoRefID_Scene scene_ref;
ArmatureUndoStep_Elem *elems;
uint elems_len;
};
static bool armature_undosys_poll(bContext *C)
{
return editarm_object_from_context(C) != nullptr;
}
static bool armature_undosys_step_encode(bContext *C, Main *bmain, UndoStep *us_p)
{
ArmatureUndoStep *us = reinterpret_cast<ArmatureUndoStep *>(us_p);
/* Important not to use the 3D view when getting objects because all objects
* outside of this list will be moved out of edit-mode when reading back undo steps. */
Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
Vector<Object *> objects = ED_undo_editmode_objects_from_view_layer(*bmain, scene, view_layer);
us->scene_ref.ptr = scene;
us->elems = MEM_new_array_zeroed<ArmatureUndoStep_Elem>(objects.size(), __func__);
us->elems_len = objects.size();
for (uint i = 0; i < objects.size(); i++) {
Object *ob = objects[i];
ArmatureUndoStep_Elem *elem = &us->elems[i];
elem->obedit_ref.ptr = ob;
bArmature *arm = id_cast<bArmature *>(elem->obedit_ref.ptr->data);
undoarm_from_editarm(&elem->data, arm);
arm->needs_flush_to_id = 1;
us->step.data_size += elem->data.undo_size;
}
bmain->is_memfile_undo_flush_needed = true;
return true;
}
static void armature_undosys_step_decode(
bContext *C, Main *bmain, UndoStep *us_p, const eUndoStepDir /*dir*/, bool /*is_final*/)
{
ArmatureUndoStep *us = reinterpret_cast<ArmatureUndoStep *>(us_p);
Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
ED_undo_object_editmode_validate_scene_from_windows(
CTX_wm_manager(C), us->scene_ref.ptr, &scene, &view_layer);
ED_undo_object_editmode_restore_helper(
scene, view_layer, &us->elems[0].obedit_ref.ptr, us->elems_len, sizeof(*us->elems));
BLI_assert(BKE_object_is_in_editmode(us->elems[0].obedit_ref.ptr));
for (uint i = 0; i < us->elems_len; i++) {
ArmatureUndoStep_Elem *elem = &us->elems[i];
Object *obedit = elem->obedit_ref.ptr;
bArmature *arm = id_cast<bArmature *>(obedit->data);
if (arm->edbo == nullptr) {
/* Should never fail, may not crash but can give odd behavior. */
CLOG_ERROR(&LOG,
"name='%s', failed to enter edit-mode for object '%s', undo state invalid",
us_p->name,
obedit->id.name);
continue;
}
undoarm_to_editarm(&elem->data, arm);
arm->needs_flush_to_id = 1;
DEG_id_tag_update(&arm->id, ID_RECALC_GEOMETRY);
}
/* The first element is always active */
ED_undo_object_set_active_or_warn(
*bmain, scene, view_layer, us->elems[0].obedit_ref.ptr, us_p->name, &LOG);
/* Check after setting active (unless undoing into another scene). */
BLI_assert(armature_undosys_poll(C) || (scene != CTX_data_scene(C)));
bmain->is_memfile_undo_flush_needed = true;
WM_event_add_notifier(C, NC_GEOM | ND_DATA, nullptr);
}
static void armature_undosys_step_free(UndoStep *us_p)
{
ArmatureUndoStep *us = reinterpret_cast<ArmatureUndoStep *>(us_p);
for (uint i = 0; i < us->elems_len; i++) {
ArmatureUndoStep_Elem *elem = &us->elems[i];
undoarm_free_data(&elem->data);
}
MEM_delete(us->elems);
}
static void armature_undosys_foreach_ID_ref(UndoStep *us_p,
UndoTypeForEachIDRefFn foreach_ID_ref_fn,
void *user_data)
{
ArmatureUndoStep *us = reinterpret_cast<ArmatureUndoStep *>(us_p);
foreach_ID_ref_fn(user_data, reinterpret_cast<UndoRefID *>(&us->scene_ref));
for (uint i = 0; i < us->elems_len; i++) {
ArmatureUndoStep_Elem *elem = &us->elems[i];
foreach_ID_ref_fn(user_data, reinterpret_cast<UndoRefID *>(&elem->obedit_ref));
}
}
void ED_armature_undosys_type(UndoType *ut)
{
ut->name = "Edit Armature";
ut->poll = armature_undosys_poll;
ut->step_encode = armature_undosys_step_encode;
ut->step_decode = armature_undosys_step_decode;
ut->step_free = armature_undosys_step_free;
ut->step_foreach_ID_ref = armature_undosys_foreach_ID_ref;
ut->flags = UNDOTYPE_FLAG_NEED_CONTEXT_FOR_ENCODE;
ut->step_size = sizeof(ArmatureUndoStep);
}
/** \} */
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,62 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edarmature
* BIF_meshlaplacian.h: Algorithms using the mesh laplacian.
*/
#pragma once
namespace blender {
// #define RIGID_DEFORM
struct Mesh;
struct Object;
struct bDeformGroup;
#ifdef RIGID_DEFORM
struct EditMesh;
#endif
/* Laplacian System */
struct LaplacianSystem;
struct LaplacianSystem;
void laplacian_add_vertex(LaplacianSystem *sys, float *co, int pinned);
void laplacian_add_triangle(LaplacianSystem *sys, int v1, int v2, int v3);
void laplacian_begin_solve(LaplacianSystem *sys, int index);
void laplacian_add_right_hand_side(LaplacianSystem *sys, int v, float value);
int laplacian_system_solve(LaplacianSystem *sys);
float laplacian_system_get_solution(LaplacianSystem *sys, int v);
/* Heat Weighting */
void heat_bone_weighting(struct Object *ob,
struct Mesh *mesh,
float (*verts)[3],
int numbones,
struct bDeformGroup **dgrouplist,
struct bDeformGroup **dgroupflip,
float (*root)[3],
float (*tip)[3],
const bool *selected,
const char **r_error_str);
#ifdef RIGID_DEFORM
/* As-Rigid-As-Possible Deformation */
void rigid_deform_begin(struct EditMesh *em);
void rigid_deform_iteration(void);
void rigid_deform_end(int cancel);
#endif
/* Harmonic Coordinates */
/* ED_mesh_deform_bind_callback(...) defined in ED_armature.hh */
} // namespace blender

View File

@@ -0,0 +1,808 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edarmature
* Pose Mode API's and Operators for Pose Mode armatures.
*/
#include "BLI_listbase.h"
#include "BLI_math_vector.h"
#include "BLI_string_utf8.h"
#include "BLT_translation.hh"
#include "DNA_armature_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "BKE_anim_visualization.h"
#include "BKE_armature.hh"
#include "BKE_context.hh"
#include "BKE_layer.hh"
#include "BKE_lib_id.hh"
#include "BKE_object.hh"
#include "BKE_report.hh"
#include "BKE_scene.hh"
#include "DEG_depsgraph.hh"
#include "RNA_access.hh"
#include "RNA_define.hh"
#include "RNA_enum_types.hh"
#include "RNA_prototypes.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "ED_anim_api.hh"
#include "ED_armature.hh"
#include "ED_keyframing.hh"
#include "ED_object.hh"
#include "ED_screen.hh"
#include "ANIM_armature.hh"
#include "ANIM_bone_collections.hh"
#include "ANIM_keyframing.hh"
#include "armature_intern.hh"
namespace blender {
#undef DEBUG_TIME
#ifdef DEBUG_TIME
# include "BLI_time_utildefines.h"
#endif
Object *ED_pose_object_from_context(bContext *C)
{
/* NOTE: matches logic with #ED_operator_posemode_context(). */
ScrArea *area = CTX_wm_area(C);
Object *ob;
/* Since this call may also be used from the buttons window,
* we need to check for where to get the object. */
if (area && area->spacetype == SPACE_PROPERTIES) {
ob = ed::object::context_active_object(C);
}
else {
ob = BKE_object_pose_armature_get(CTX_data_active_object(C));
}
return ob;
}
bool ED_object_posemode_enter_ex(Main *bmain, Object *ob)
{
BLI_assert(BKE_id_is_editable(bmain, &ob->id));
bool ok = false;
switch (ob->type) {
case OB_ARMATURE:
ob->restore_mode = ob->mode;
ob->mode |= OB_MODE_POSE;
/* Inform all evaluated versions that we changed the mode. */
DEG_id_tag_update_ex(bmain, &ob->id, ID_RECALC_SYNC_TO_EVAL);
ok = true;
break;
default:
break;
}
return ok;
}
bool ED_object_posemode_enter(bContext *C, Object *ob)
{
ReportList *reports = CTX_wm_reports(C);
Main *bmain = CTX_data_main(C);
if (!BKE_id_is_editable(bmain, &ob->id)) {
BKE_report(reports, RPT_WARNING, "Cannot pose libdata");
return false;
}
bool ok = ED_object_posemode_enter_ex(bmain, ob);
if (ok) {
WM_event_add_notifier(C, NC_SCENE | ND_MODE | NS_MODE_POSE, nullptr);
}
return ok;
}
bool ED_object_posemode_exit_ex(Main *bmain, Object *ob)
{
bool ok = false;
if (ob) {
ob->restore_mode = ob->mode;
ob->mode &= ~OB_MODE_POSE;
/* Inform all evaluated versions that we changed the mode. */
DEG_id_tag_update_ex(bmain, &ob->id, ID_RECALC_SYNC_TO_EVAL);
ok = true;
}
return ok;
}
bool ED_object_posemode_exit(bContext *C, Object *ob)
{
Main *bmain = CTX_data_main(C);
bool ok = ED_object_posemode_exit_ex(bmain, ob);
if (ok) {
WM_event_add_notifier(C, NC_SCENE | ND_MODE | NS_MODE_OBJECT, nullptr);
}
return ok;
}
/* ********************************************** */
/* Motion Paths */
void ED_pose_recalculate_paths(bContext *C, Scene *scene, Object *ob, eAnimvizCalcRange range)
{
/* Transform doesn't always have context available to do update. */
if (C == nullptr) {
return;
}
Main *bmain = CTX_data_main(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
Vector<MPathTarget *> targets;
/* set flag to force recalc, then grab the relevant bones to target */
ob->pose->avs.recalc |= ANIMVIZ_RECALC_PATHS;
animviz_build_motionpath_targets(ob, targets);
/* recalculate paths, then free */
#ifdef DEBUG_TIME
TIMEIT_START(pose_path_calc);
#endif
Depsgraph *depsgraph = animviz_depsgraph_build(bmain, scene, view_layer, targets);
animviz_calc_motionpaths(depsgraph, scene, targets, range);
#ifdef DEBUG_TIME
TIMEIT_END(pose_path_calc);
#endif
animviz_free_motionpath_targets(targets);
DEG_id_tag_update(&ob->id, ID_RECALC_SYNC_TO_EVAL);
/* Free temporary depsgraph. */
DEG_graph_free(depsgraph);
}
/* show popup to determine settings */
static wmOperatorStatus pose_calculate_paths_invoke(bContext *C,
wmOperator *op,
const wmEvent * /*event*/)
{
Object *ob = BKE_object_pose_armature_get(CTX_data_active_object(C));
if (ELEM(nullptr, ob, ob->pose)) {
return OPERATOR_CANCELLED;
}
/* set default settings from existing/stored settings */
{
bAnimVizSettings *avs = &ob->pose->avs;
PointerRNA avs_ptr = RNA_pointer_create_discrete(nullptr, RNA_AnimVizMotionPaths, avs);
RNA_enum_set(op->ptr, "display_type", RNA_enum_get(&avs_ptr, "type"));
RNA_enum_set(op->ptr, "range", RNA_enum_get(&avs_ptr, "range"));
RNA_enum_set(op->ptr, "bake_location", RNA_enum_get(&avs_ptr, "bake_location"));
}
/* show popup dialog to allow editing of range... */
/* FIXME: hard-coded dimensions here are just arbitrary. */
return WM_operator_props_dialog_popup(
C, op, 270, IFACE_("Calculate Paths for the Selected Bones"), IFACE_("Calculate"));
}
/**
* For the object with pose/action: create path curves for selected bones
* This recalculates the WHOLE path within the `pchan->pathsf` and `pchan->pathef` range.
*/
static wmOperatorStatus pose_calculate_paths_exec(bContext *C, wmOperator *op)
{
Object *ob = BKE_object_pose_armature_get(CTX_data_active_object(C));
Scene *scene = CTX_data_scene(C);
if (ELEM(nullptr, ob, ob->pose)) {
return OPERATOR_CANCELLED;
}
/* grab baking settings from operator settings */
{
bAnimVizSettings *avs = &ob->pose->avs;
avs->path_type = eMotionPaths_Types(RNA_enum_get(op->ptr, "display_type"));
avs->path_range = eMotionPath_Ranges(RNA_enum_get(op->ptr, "range"));
animviz_motionpath_compute_range(ob, scene);
PointerRNA avs_ptr = RNA_pointer_create_discrete(nullptr, RNA_AnimVizMotionPaths, avs);
RNA_enum_set(&avs_ptr, "bake_location", RNA_enum_get(op->ptr, "bake_location"));
}
/* set up path data for bones being calculated */
CTX_DATA_BEGIN (C, bPoseChannel *, pchan, selected_pose_bones_from_active_object) {
/* verify makes sure that the selected bone has a bone with the appropriate settings */
animviz_verify_motionpaths(op->reports, scene, ob, pchan);
}
CTX_DATA_END;
#ifdef DEBUG_TIME
TIMEIT_START(recalc_pose_paths);
#endif
/* Calculate the bones that now have motion-paths. */
/* TODO: only make for the selected bones? */
ED_pose_recalculate_paths(C, scene, ob, ANIMVIZ_CALC_RANGE_FULL);
#ifdef DEBUG_TIME
TIMEIT_END(recalc_pose_paths);
#endif
/* notifiers for updates */
WM_event_add_notifier(C, NC_OBJECT | ND_DRAW_ANIMVIZ, ob);
return OPERATOR_FINISHED;
}
void POSE_OT_paths_calculate(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Calculate Bone Paths";
ot->idname = "POSE_OT_paths_calculate";
ot->description = "Calculate paths for the selected bones";
/* API callbacks. */
ot->invoke = pose_calculate_paths_invoke;
ot->exec = pose_calculate_paths_exec;
ot->poll = ED_operator_posemode_exclusive;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* properties */
RNA_def_enum(ot->srna,
"display_type",
rna_enum_motionpath_display_type_items,
MOTIONPATH_TYPE_RANGE,
"Display Type",
"");
RNA_def_enum(ot->srna,
"range",
rna_enum_motionpath_range_items,
MOTIONPATH_RANGE_SCENE,
"Computation Range",
"");
RNA_def_enum(ot->srna,
"bake_location",
rna_enum_motionpath_bake_location_items,
MOTIONPATH_BAKE_HEADS,
"Bake Location",
"Which point on the bones is used when calculating paths");
}
/* --------- */
static bool pose_update_paths_poll(bContext *C)
{
if (ED_operator_posemode_exclusive(C)) {
Object *ob = ed::object::context_active_object(C);
return (ob->pose->avs.path_bakeflag & MOTIONPATH_BAKE_HAS_PATHS) != 0;
}
return false;
}
static wmOperatorStatus pose_update_paths_exec(bContext *C, wmOperator *op)
{
Object *ob = BKE_object_pose_armature_get(CTX_data_active_object(C));
Scene *scene = CTX_data_scene(C);
if (ELEM(nullptr, ob, scene)) {
return OPERATOR_CANCELLED;
}
animviz_motionpath_compute_range(ob, scene);
/* set up path data for bones being calculated */
CTX_DATA_BEGIN (C, bPoseChannel *, pchan, selected_pose_bones_from_active_object) {
animviz_verify_motionpaths(op->reports, scene, ob, pchan);
}
CTX_DATA_END;
/* Calculate the bones that now have motion-paths. */
/* TODO: only make for the selected bones? */
ED_pose_recalculate_paths(C, scene, ob, ANIMVIZ_CALC_RANGE_FULL);
/* notifiers for updates */
WM_event_add_notifier(C, NC_OBJECT | ND_DRAW_ANIMVIZ, ob);
return OPERATOR_FINISHED;
}
void POSE_OT_paths_update(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Update Bone Paths";
ot->idname = "POSE_OT_paths_update";
ot->description = "Recalculate paths for bones that already have them";
/* API callbacks. */
ot->exec = pose_update_paths_exec;
ot->poll = pose_update_paths_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/* --------- */
/* for the object with pose/action: clear path curves for selected bones only */
static void pose_clear_paths(Object *ob, bool only_selected)
{
bool skipped = false;
if (ELEM(nullptr, ob, ob->pose)) {
return;
}
/* free the motionpath blocks for all bones - This is easier for users to quickly clear all */
for (bPoseChannel &pchan : ob->pose->chanbase) {
if (pchan.mpath) {
if ((only_selected == false) || (pchan.flag & POSE_SELECTED)) {
animviz_free_motionpath(pchan.mpath);
pchan.mpath = nullptr;
}
else {
skipped = true;
}
}
}
/* if nothing was skipped, there should be no paths left! */
if (skipped == false) {
ob->pose->avs.path_bakeflag &= ~MOTIONPATH_BAKE_HAS_PATHS;
}
/* tag armature object for copy-on-eval - so removed paths don't still show */
DEG_id_tag_update(&ob->id, ID_RECALC_SYNC_TO_EVAL);
}
/* Operator callback - wrapper for the back-end function. */
static wmOperatorStatus pose_clear_paths_exec(bContext *C, wmOperator *op)
{
Object *ob = BKE_object_pose_armature_get(CTX_data_active_object(C));
bool only_selected = RNA_boolean_get(op->ptr, "only_selected");
/* only continue if there's an object */
if (ELEM(nullptr, ob, ob->pose)) {
return OPERATOR_CANCELLED;
}
/* use the backend function for this */
pose_clear_paths(ob, only_selected);
/* notifiers for updates */
WM_event_add_notifier(C, NC_OBJECT | ND_DRAW_ANIMVIZ, ob);
return OPERATOR_FINISHED;
}
static std::string pose_clear_paths_get_description(bContext * /*C*/,
wmOperatorType * /*ot*/,
PointerRNA *ptr)
{
const bool only_selected = RNA_boolean_get(ptr, "only_selected");
if (only_selected) {
return TIP_("Clear motion paths of selected bones");
}
return TIP_("Clear motion paths of all bones");
}
void POSE_OT_paths_clear(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Clear Bone Paths";
ot->idname = "POSE_OT_paths_clear";
/* API callbacks. */
ot->exec = pose_clear_paths_exec;
ot->poll = ED_operator_posemode_exclusive;
ot->get_description = pose_clear_paths_get_description;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* properties */
ot->prop = RNA_def_boolean(ot->srna,
"only_selected",
false,
"Only Selected",
"Only clear motion paths of selected bones");
RNA_def_property_flag(ot->prop, PROP_SKIP_SAVE);
}
/* --------- */
static wmOperatorStatus pose_update_paths_range_exec(bContext *C, wmOperator * /*op*/)
{
Scene *scene = CTX_data_scene(C);
Object *ob = BKE_object_pose_armature_get(CTX_data_active_object(C));
if (ELEM(nullptr, scene, ob, ob->pose)) {
return OPERATOR_CANCELLED;
}
/* Use Preview Range or Full Frame Range - whichever is in use. */
ob->pose->avs.path_sf = scene->playback_start();
ob->pose->avs.path_ef = scene->playback_end();
/* tag for updates */
DEG_id_tag_update(&ob->id, ID_RECALC_SYNC_TO_EVAL);
WM_event_add_notifier(C, NC_OBJECT | ND_DRAW_ANIMVIZ, ob);
return OPERATOR_FINISHED;
}
void POSE_OT_paths_range_update(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Update Range from Scene";
ot->idname = "POSE_OT_paths_range_update";
ot->description = "Update frame range for motion paths from the Scene's current frame range";
/* callbacks */
ot->exec = pose_update_paths_range_exec;
ot->poll = ED_operator_posemode_exclusive;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/* ********************************************** */
static wmOperatorStatus pose_flip_names_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
const Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
View3D *v3d = CTX_wm_view3d(C);
const bool do_strip_numbers = RNA_boolean_get(op->ptr, "do_strip_numbers");
FOREACH_OBJECT_IN_MODE_BEGIN (bmain, scene, view_layer, v3d, OB_ARMATURE, OB_MODE_POSE, ob) {
bArmature *arm = id_cast<bArmature *>(ob->data);
ListBaseT<LinkData> bones_names = {nullptr};
FOREACH_PCHAN_SELECTED_IN_OBJECT_BEGIN (ob, pchan) {
BLI_addtail(&bones_names, BLI_genericNodeN(pchan->name));
}
FOREACH_PCHAN_SELECTED_IN_OBJECT_END;
ED_armature_bones_flip_names(bmain, arm, &bones_names, do_strip_numbers);
bones_names.free_no_destruct();
/* Since we renamed stuff... */
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
WM_event_add_notifier(C, NC_GEOM | ND_DATA | NA_RENAME, ob->data);
/* Update animation channels */
WM_event_add_notifier(C, NC_ANIMATION | ND_ANIMCHAN, ob->data);
}
FOREACH_OBJECT_IN_MODE_END;
return OPERATOR_FINISHED;
}
void POSE_OT_flip_names(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Flip Names";
ot->idname = "POSE_OT_flip_names";
ot->description = "Flips (and corrects) the axis suffixes of the names of selected bones";
/* API callbacks. */
ot->exec = pose_flip_names_exec;
ot->poll = ED_operator_posemode_local;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
RNA_def_boolean(ot->srna,
"do_strip_numbers",
false,
"Strip Numbers",
"Try to remove right-most dot-number from flipped names.\n"
"Warning: May result in incoherent naming in some cases");
}
/* ------------------ */
static wmOperatorStatus pose_autoside_names_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
char newname[MAXBONENAME];
short axis = RNA_enum_get(op->ptr, "axis");
Object *ob_prev = nullptr;
/* loop through selected bones, auto-naming them */
CTX_DATA_BEGIN_WITH_ID (C, bPoseChannel *, pchan, selected_pose_bones, Object *, ob) {
bArmature *arm = id_cast<bArmature *>(ob->data);
const Bone *bone = pchan->bone_get(*ob);
STRNCPY_UTF8(newname, pchan->name);
if (bone_autoside_name(newname, 1, axis, bone->head[axis], bone->tail[axis])) {
ED_armature_bone_rename(bmain, arm, pchan->name, newname);
}
if (ob_prev != ob) {
/* Since we renamed stuff... */
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
WM_event_add_notifier(C, NC_GEOM | ND_DATA | NA_RENAME, ob->data);
/* Update animation channels */
WM_event_add_notifier(C, NC_ANIMATION | ND_ANIMCHAN, ob->data);
ob_prev = ob;
}
}
CTX_DATA_END;
return OPERATOR_FINISHED;
}
void POSE_OT_autoside_names(wmOperatorType *ot)
{
static const EnumPropertyItem axis_items[] = {
{0, "XAXIS", 0, "X-Axis", "Left/Right"},
{1, "YAXIS", 0, "Y-Axis", "Front/Back"},
{2, "ZAXIS", 0, "Z-Axis", "Top/Bottom"},
{0, nullptr, 0, nullptr, nullptr},
};
/* identifiers */
ot->name = "Auto-Name by Axis";
ot->idname = "POSE_OT_autoside_names";
ot->description =
"Automatically renames the selected bones according to which side of the target axis they "
"fall on";
/* API callbacks. */
ot->invoke = WM_menu_invoke;
ot->exec = pose_autoside_names_exec;
ot->poll = ED_operator_posemode;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* settings */
ot->prop = RNA_def_enum(ot->srna, "axis", axis_items, 0, "Axis", "Axis to tag names with");
}
/* ********************************************** */
static wmOperatorStatus pose_bone_rotmode_exec(bContext *C, wmOperator *op)
{
const eRotationModes mode = eRotationModes(RNA_enum_get(op->ptr, "type"));
Object *prev_ob = nullptr;
/* Set rotation mode of selected bones. */
CTX_DATA_BEGIN_WITH_ID (C, bPoseChannel *, pchan, selected_pose_bones, Object *, ob) {
/* use API Method for conversions... */
BKE_rotMode_change_values(
pchan->quat, pchan->eul, pchan->rotAxis, &pchan->rotAngle, pchan->rotmode, mode);
/* finally, set the new rotation type */
pchan->rotmode = mode;
if (prev_ob != ob) {
/* Notifiers and updates. */
DEG_id_tag_update(reinterpret_cast<ID *>(ob), ID_RECALC_GEOMETRY);
WM_event_add_notifier(C, NC_OBJECT | ND_TRANSFORM, ob);
WM_event_add_notifier(C, NC_OBJECT | ND_POSE, ob);
prev_ob = ob;
}
}
CTX_DATA_END;
return OPERATOR_FINISHED;
}
void POSE_OT_rotation_mode_set(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Set Rotation Mode";
ot->idname = "POSE_OT_rotation_mode_set";
ot->description = "Set the rotation representation used by selected bones";
/* callbacks */
ot->invoke = WM_menu_invoke;
ot->exec = pose_bone_rotmode_exec;
ot->poll = ED_operator_posemode;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* properties */
ot->prop = RNA_def_enum(
ot->srna, "type", rna_enum_object_rotation_mode_items, 0, "Rotation Mode", "");
}
/* ********************************************** */
/* Show/Hide Bones */
/* active object is armature in posemode, poll checked */
static wmOperatorStatus pose_hide_exec(bContext *C, wmOperator *op)
{
const Main *bmain = CTX_data_main(C);
const Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
Vector<Object *> objects = BKE_object_pose_array_get_unique(
*bmain, scene, view_layer, CTX_wm_view3d(C));
bool changed_multi = false;
const int hide_select = !RNA_boolean_get(op->ptr, "unselected");
for (Object *ob_iter : objects) {
bool changed = false;
bArmature *arm = id_cast<bArmature *>(ob_iter->data);
for (bPoseChannel &pchan : ob_iter->pose->chanbase) {
if (!ANIM_bone_in_visible_collection(arm, pchan.bone_get(*ob_iter))) {
continue;
}
if (((pchan.flag & POSE_SELECTED) != 0) != hide_select) {
continue;
}
pchan.drawflag |= PCHAN_DRAW_HIDDEN;
animrig::bone_deselect(&pchan);
changed = true;
}
if (changed) {
changed_multi = true;
WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, ob_iter);
DEG_id_tag_update(&arm->id, ID_RECALC_SYNC_TO_EVAL);
}
}
return changed_multi ? OPERATOR_FINISHED : OPERATOR_CANCELLED;
}
void POSE_OT_hide(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Hide Selected";
ot->idname = "POSE_OT_hide";
ot->description = "Tag selected bones to not be visible in Pose Mode";
/* API callbacks. */
ot->exec = pose_hide_exec;
ot->poll = ED_operator_posemode;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* props */
RNA_def_boolean(ot->srna, "unselected", false, "Unselected", "");
}
/* active object is armature in posemode, poll checked */
static wmOperatorStatus pose_reveal_exec(bContext *C, wmOperator *op)
{
const Main *bmain = CTX_data_main(C);
const Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
Vector<Object *> objects = BKE_object_pose_array_get_unique(
*bmain, scene, view_layer, CTX_wm_view3d(C));
bool changed_multi = false;
const bool select = RNA_boolean_get(op->ptr, "select");
for (Object *ob_iter : objects) {
bArmature *arm = id_cast<bArmature *>(ob_iter->data);
bool changed = false;
for (bPoseChannel &pchan : ob_iter->pose->chanbase) {
const Bone *bone = pchan.bone_get(*ob_iter);
if (!ANIM_bone_in_visible_collection(arm, bone)) {
continue;
}
if ((pchan.drawflag & PCHAN_DRAW_HIDDEN) == 0) {
continue;
}
if (!(bone->flag & BONE_UNSELECTABLE)) {
SET_FLAG_FROM_TEST(pchan.flag, select, POSE_SELECTED);
}
pchan.drawflag &= ~PCHAN_DRAW_HIDDEN;
changed = true;
}
if (changed) {
changed_multi = true;
WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, ob_iter);
DEG_id_tag_update(&arm->id, ID_RECALC_PARAMETERS);
}
}
return changed_multi ? OPERATOR_FINISHED : OPERATOR_CANCELLED;
}
void POSE_OT_reveal(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Reveal Selected";
ot->idname = "POSE_OT_reveal";
ot->description = "Reveal all bones hidden in Pose Mode";
/* API callbacks. */
ot->exec = pose_reveal_exec;
ot->poll = ED_operator_posemode;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
RNA_def_boolean(ot->srna, "select", true, "Select", "");
}
/* -------------------------------------------------------------------- */
/** \name Flip Quaternions
* \{ */
static wmOperatorStatus pose_flip_quats_exec(bContext *C, wmOperator * /*op*/)
{
const Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
bool changed_multi = false;
ViewLayer *view_layer = CTX_data_view_layer(C);
View3D *v3d = CTX_wm_view3d(C);
FOREACH_OBJECT_IN_MODE_BEGIN (bmain, scene, view_layer, v3d, OB_ARMATURE, OB_MODE_POSE, ob_iter)
{
bool changed = false;
/* loop through all selected pchans, flipping and keying (as needed) */
FOREACH_PCHAN_SELECTED_IN_OBJECT_BEGIN (ob_iter, pchan) {
/* only if bone is using quaternion rotation */
if (pchan->rotmode == ROT_MODE_QUAT) {
changed = true;
/* quaternions have 720 degree range */
negate_v4(pchan->quat);
animrig::autokeyframe_pose_channel(
C, scene, ob_iter, pchan, {{"rotation_quaternion"}}, false);
}
}
FOREACH_PCHAN_SELECTED_IN_OBJECT_END;
if (changed) {
changed_multi = true;
/* notifiers and updates */
DEG_id_tag_update(&ob_iter->id, ID_RECALC_GEOMETRY);
WM_event_add_notifier(C, NC_OBJECT | ND_TRANSFORM, ob_iter);
}
}
FOREACH_OBJECT_IN_MODE_END;
return changed_multi ? OPERATOR_FINISHED : OPERATOR_CANCELLED;
}
void POSE_OT_quaternions_flip(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Flip Quaternions";
ot->idname = "POSE_OT_quaternions_flip";
ot->description =
"Flip quaternion values to achieve desired rotations, while maintaining the same "
"orientations";
/* callbacks */
ot->exec = pose_flip_quats_exec;
ot->poll = ED_operator_posemode;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,749 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edarmature
*/
#include <cstring>
#include "AS_asset_representation.hh"
#include "MEM_guardedalloc.h"
#include "BLT_translation.hh"
#include "DNA_armature_types.h"
#include "BKE_action.hh"
#include "BKE_anim_data.hh"
#include "BKE_animsys.h"
#include "BKE_armature.hh"
#include "BKE_context.hh"
#include "BKE_lib_id.hh"
#include "BKE_object.hh"
#include "BKE_pose_backup.h"
#include "BKE_report.hh"
#include "DEG_depsgraph.hh"
#include "RNA_access.hh"
#include "RNA_define.hh"
#include "RNA_prototypes.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "UI_interface.hh"
#include "ED_asset.hh"
#include "ED_asset_menu_utils.hh"
#include "ED_keyframing.hh"
#include "ED_screen.hh"
#include "ED_util.hh"
#include "ANIM_action.hh"
#include "ANIM_action_legacy.hh"
#include "ANIM_armature.hh"
#include "ANIM_keyframing.hh"
#include "ANIM_keyingsets.hh"
#include "ANIM_pose.hh"
#include "ANIM_rna.hh"
#include "armature_intern.hh"
namespace blender {
enum ePoseBlendState {
POSE_BLEND_INIT,
POSE_BLEND_BLENDING,
POSE_BLEND_ORIGINAL,
POSE_BLEND_CONFIRM,
POSE_BLEND_CANCEL,
};
struct PoseBlendData {
ePoseBlendState state;
bool needs_redraw;
struct {
bool use_release_confirm;
int init_event_type;
} release_confirm_info;
/* For temp-loading the Action from the pose library. */
AssetTempIDConsumer *temp_id_consumer;
/* Blend factor for interpolating between current and given pose.
* 1.0 means "100% pose asset". Negative values and values > 1.0 will be used as-is, and can
* cause interesting effects. */
float blend_factor;
bool is_flipped;
PoseBackup *pose_backup;
Vector<Object *> objects; /* Objects to work on. */
bAction *act; /* Pose to blend into the current pose. */
bAction *act_flipped; /* Flipped copy of `act`. */
Scene *scene; /* For auto-keying. */
ScrArea *area; /* For drawing status text. */
tSlider *slider; /* Slider UI and event handling. */
/** Info-text to print in header. */
char headerstr[UI_MAX_DRAW_STR];
};
/**
* Return the bAction that should be blended.
* This is either `pbd->act` or `pbd->act_flipped`, depending on `is_flipped`.
*/
static bAction *poselib_action_to_blend(PoseBlendData *pbd)
{
return pbd->is_flipped ? pbd->act_flipped : pbd->act;
}
/* Makes a copy of the current pose for restoration purposes - doesn't do constraints currently */
static void poselib_backup_posecopy(PoseBlendData *pbd)
{
bAction *action = poselib_action_to_blend(pbd);
pbd->pose_backup = BKE_pose_backup_create_selected_bones(pbd->objects, action);
if (pbd->state == POSE_BLEND_INIT) {
/* Ready for blending now. */
pbd->state = POSE_BLEND_BLENDING;
}
}
/* ---------------------------- */
/* Auto-key/tag bones affected by the pose Action. */
static void poselib_keytag_pose(bContext *C, Scene *scene, PoseBlendData *pbd)
{
for (Object *ob : pbd->objects) {
if (!animrig::autokeyframe_cfra_can_key(scene, &ob->id)) {
return;
}
AnimData *adt = BKE_animdata_from_id(&ob->id);
if (adt != nullptr && adt->action != nullptr &&
!BKE_id_is_editable(CTX_data_main(C), &adt->action->id))
{
/* Changes to linked-in Actions are not allowed. */
return;
}
bPose *pose = ob->pose;
bAction *act = poselib_action_to_blend(pbd);
const bArmature *armature = id_cast<const bArmature *>(ob->data);
animrig::Slot &slot = animrig::get_best_pose_slot_for_id(ob->id, act->wrap());
/* Storing which pose bones were already keyed since multiple FCurves will probably exist per
* pose bone. */
Set<bPoseChannel *> keyed_pose_bones;
auto autokey_pose_bones = [&](FCurve * /* fcu */, const char *bone_name) {
bPoseChannel *pchan = BKE_pose_channel_find_name(pose, bone_name);
if (!pchan) {
/* This bone cannot be found any more. This is fine, this can happen
* when F-Curves for a bone are included in a pose asset, and later the
* bone itself was renamed or removed. */
return;
}
if (BKE_pose_backup_is_selection_relevant(pbd->pose_backup) &&
!animrig::bone_is_selected(armature, {pchan, pchan->bone_get(*ob)}))
{
return;
}
if (keyed_pose_bones.contains(pchan)) {
return;
}
/* This mimics the Whole Character Keying Set that was used here previously. In the future we
* could only key rna paths of FCurves that are actually in the applied pose. */
PointerRNA pose_bone_pointer = RNA_pointer_create_discrete(&ob->id, RNA_PoseBone, pchan);
Vector<RNAPath> rna_paths = animrig::get_keyable_id_property_paths(pose_bone_pointer);
rna_paths.append({"location"});
const StringRefNull rotation_mode_path = animrig::get_rotation_mode_path(
eRotationModes(pchan->rotmode));
rna_paths.append({rotation_mode_path});
rna_paths.append({"scale"});
animrig::autokeyframe_pose_channel(C, scene, ob, pchan, rna_paths, 0);
keyed_pose_bones.add(pchan);
};
bke::BKE_action_find_fcurves_with_bones(act, slot.handle, autokey_pose_bones);
}
/* send notifiers for this */
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, nullptr);
}
/* Apply the relevant changes to the pose */
static void poselib_blend_apply(bContext *C, wmOperator *op)
{
PoseBlendData *pbd = static_cast<PoseBlendData *>(op->customdata);
if (!pbd->needs_redraw) {
return;
}
pbd->needs_redraw = false;
BKE_pose_backup_restore(pbd->pose_backup);
/* The pose needs updating, whether it's for restoring the original pose or for showing the
* result of the blend. */
for (Object *ob : pbd->objects) {
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
WM_event_add_notifier(C, NC_OBJECT | ND_POSE, ob);
}
if (pbd->state != POSE_BLEND_BLENDING) {
return;
}
/* Perform the actual blending. */
Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
AnimationEvalContext anim_eval_context = BKE_animsys_eval_context_construct(depsgraph, 0.0f);
animrig::Action &pose_action = poselib_action_to_blend(pbd)->wrap();
if (pose_action.slot_array_num == 0) {
return;
}
animrig::pose_apply_action(pbd->objects, pose_action, &anim_eval_context, pbd->blend_factor);
}
/* ---------------------------- */
static void poselib_blend_set_factor(PoseBlendData *pbd, const float new_factor)
{
pbd->blend_factor = new_factor;
pbd->needs_redraw = true;
}
static void poselib_toggle_flipped(PoseBlendData *pbd)
{
/* The pose will toggle between flipped and normal. This means the pose
* backup has to change, as it only contains the bones for one side. */
BKE_pose_backup_restore(pbd->pose_backup);
BKE_pose_backup_free(pbd->pose_backup);
pbd->is_flipped = !pbd->is_flipped;
pbd->needs_redraw = true;
poselib_backup_posecopy(pbd);
}
/* Return operator return value. */
static wmOperatorStatus poselib_blend_handle_event(bContext * /*C*/,
wmOperator *op,
const wmEvent *event)
{
PoseBlendData *pbd = static_cast<PoseBlendData *>(op->customdata);
ED_slider_modal(pbd->slider, event);
const float factor = ED_slider_factor_get(pbd->slider);
poselib_blend_set_factor(pbd, factor);
if (event->type == MOUSEMOVE) {
return OPERATOR_RUNNING_MODAL;
}
/* Handle the release confirm event directly, it has priority over others. */
if (pbd->release_confirm_info.use_release_confirm &&
(event->type == pbd->release_confirm_info.init_event_type) && (event->val == KM_RELEASE))
{
pbd->state = POSE_BLEND_CONFIRM;
return OPERATOR_RUNNING_MODAL;
}
/* Ctrl manages the 'flipped' state. It works as a toggle so if the operator started in flipped
* mode, pressing it will unflip the pose. */
if (ELEM(event->val, KM_PRESS, KM_RELEASE) &&
ELEM(event->type, EVT_LEFTCTRLKEY, EVT_RIGHTCTRLKEY))
{
poselib_toggle_flipped(pbd);
}
/* only accept 'press' event, and ignore 'release', so that we don't get double actions */
if (ELEM(event->val, KM_PRESS, KM_NOTHING) == 0) {
return OPERATOR_RUNNING_MODAL;
}
/* NORMAL EVENT HANDLING... */
/* searching takes priority over normal activity */
switch (event->type) {
/* Exit - cancel. */
case EVT_ESCKEY:
case RIGHTMOUSE:
pbd->state = POSE_BLEND_CANCEL;
break;
/* Exit - confirm. */
case LEFTMOUSE:
case EVT_RETKEY:
case EVT_PADENTER:
case EVT_SPACEKEY:
pbd->state = POSE_BLEND_CONFIRM;
break;
/* TODO(Sybren): toggle between original pose and poselib pose. */
case EVT_TABKEY:
pbd->state = pbd->state == POSE_BLEND_BLENDING ? POSE_BLEND_ORIGINAL : POSE_BLEND_BLENDING;
pbd->needs_redraw = true;
break;
default: {
break;
}
}
return OPERATOR_RUNNING_MODAL;
}
/* ---------------------------- */
static Vector<Object *> get_poselib_objects(bContext &C)
{
Vector<PointerRNA> selected_objects;
CTX_data_selected_objects(&C, &selected_objects);
Vector<Object *> selected_pose_objects;
for (const PointerRNA &ptr : selected_objects) {
Object *object = reinterpret_cast<Object *>(ptr.owner_id);
if (!object || !object->pose) {
continue;
}
selected_pose_objects.append(object);
}
Object *active_object = CTX_data_active_object(&C);
/* The active object may not be selected, it should be added because you can still switch to pose
* mode. */
if (active_object && active_object->pose && !selected_pose_objects.contains(active_object)) {
selected_pose_objects.append(active_object);
}
return selected_pose_objects;
}
static void poselib_tempload_exit(PoseBlendData *pbd)
{
using namespace blender::ed;
asset::temp_id_consumer_free(&pbd->temp_id_consumer);
}
static bAction *poselib_blend_init_get_action(bContext *C, wmOperator *op)
{
using namespace blender::ed;
const asset_system::AssetRepresentation *asset = nullptr;
if (asset::operator_asset_reference_props_is_set(*op->ptr)) {
asset = asset::operator_asset_reference_props_get_asset_from_all_library(
*C, *op->ptr, op->reports);
if (!asset) {
/* Explicit asset reference passed, but cannot be found. Error out. */
BKE_reportf(op->reports,
RPT_ERROR,
"Asset not found: '%s'",
RNA_string_get(op->ptr, "relative_asset_identifier").c_str());
return nullptr;
}
}
else {
/* If no explicit asset reference was passed, get asset from context. */
asset = CTX_wm_asset(C);
if (!asset) {
BKE_report(op->reports, RPT_ERROR, "No asset in context");
return nullptr;
}
}
if (asset->get_id_type() != ID_AC) {
BKE_reportf(op->reports,
RPT_ERROR,
"Asset ('%s') is not an action data-block",
asset->get_name().c_str());
return nullptr;
}
if (asset->is_online_only()) {
BKE_reportf(op->reports,
RPT_ERROR,
"Pose '%s' needs downloading before it can be applied (check context menu)",
asset->get_name().c_str());
return nullptr;
}
PoseBlendData *pbd = static_cast<PoseBlendData *>(op->customdata);
pbd->temp_id_consumer = asset::temp_id_consumer_create(asset);
return reinterpret_cast<bAction *>(asset::temp_id_consumer_ensure_local_id(
pbd->temp_id_consumer, ID_AC, CTX_data_main(C), op->reports));
}
static bAction *flip_pose(bContext *C, Span<Object *> objects, bAction *action)
{
bAction *action_copy = reinterpret_cast<bAction *>(
BKE_id_copy_ex(nullptr, &action->id, nullptr, LIB_ID_COPY_LOCALIZE));
/* Lock the window manager while flipping the pose. Flipping requires temporarily modifying the
* pose, which can cause unwanted visual glitches. */
wmWindowManager *wm = CTX_wm_manager(C);
const bool interface_was_locked = CTX_wm_interface_locked(C);
WM_locked_interface_set(wm, true);
BKE_action_flip_with_pose(action_copy, objects);
WM_locked_interface_set(wm, interface_was_locked);
return action_copy;
}
/* Return true on success, false if the context isn't suitable. */
static bool poselib_blend_init_data(bContext *C, wmOperator *op, const wmEvent *event)
{
op->customdata = nullptr;
/* check if valid poselib */
Vector<Object *> selected_pose_objects = get_poselib_objects(*C);
if (selected_pose_objects.is_empty()) {
BKE_report(op->reports, RPT_ERROR, "Pose lib is only for armatures in pose mode");
return false;
}
/* Set up blend state info. */
PoseBlendData *pbd;
op->customdata = pbd = MEM_new<PoseBlendData>("PoseLib Preview Data");
pbd->act = poselib_blend_init_get_action(C, op);
if (pbd->act == nullptr) {
/* No report here. The poll function cannot check if the operator properties have an asset
* reference to determine the asset to operate on, in which case we fallback to getting the
* asset from context. */
return false;
}
if (pbd->act->wrap().slots().size() == 0) {
BKE_report(op->reports, RPT_ERROR, "This pose asset is empty, and thus has no pose");
return false;
}
pbd->is_flipped = RNA_struct_property_is_set(op->ptr, "flipped") ?
RNA_boolean_get(op->ptr, "flipped") :
(event && (event->modifier & KM_CTRL));
pbd->blend_factor = RNA_float_get(op->ptr, "blend_factor");
/* Only construct the flipped pose if there is a chance it's actually needed. */
const bool is_interactive = (event != nullptr);
if (is_interactive || pbd->is_flipped) {
pbd->act_flipped = flip_pose(C, selected_pose_objects, pbd->act);
}
/* Get the basic data. */
pbd->objects = selected_pose_objects;
pbd->scene = CTX_data_scene(C);
pbd->area = CTX_wm_area(C);
pbd->state = POSE_BLEND_INIT;
pbd->needs_redraw = true;
/* Just to avoid a clang-analyzer warning (false positive), it's set properly below. */
pbd->release_confirm_info.use_release_confirm = false;
/* Release confirm data. Only available if there's an event to work with. */
if (is_interactive) {
PropertyRNA *release_confirm_prop = RNA_struct_find_property(op->ptr, "release_confirm");
if (release_confirm_prop && RNA_property_is_set(op->ptr, release_confirm_prop)) {
pbd->release_confirm_info.use_release_confirm = RNA_property_boolean_get(
op->ptr, release_confirm_prop);
}
else {
pbd->release_confirm_info.use_release_confirm = event->val != KM_RELEASE;
}
pbd->slider = ED_slider_create(C);
ED_slider_init(pbd->slider, event);
ED_slider_factor_set(pbd->slider, pbd->blend_factor);
ED_slider_allow_overshoot_set(pbd->slider, true, true);
ED_slider_allow_increments_set(pbd->slider, false);
ED_slider_factor_bounds_set(pbd->slider, -1, 1);
}
if (pbd->release_confirm_info.use_release_confirm) {
BLI_assert(is_interactive);
pbd->release_confirm_info.init_event_type = WM_userdef_event_type_from_keymap_type(
event->type);
}
/* Make backups for blending and restoring the pose. */
poselib_backup_posecopy(pbd);
return true;
}
static void poselib_blend_cleanup(bContext *C, wmOperator *op)
{
PoseBlendData *pbd = static_cast<PoseBlendData *>(op->customdata);
wmWindow *win = CTX_wm_window(C);
/* Redraw the header so that it doesn't show any of our stuff anymore. */
ED_area_status_text(pbd->area, nullptr);
ED_workspace_status_text(C, nullptr);
if (pbd->slider) {
ED_slider_destroy(C, pbd->slider);
}
switch (pbd->state) {
case POSE_BLEND_CONFIRM: {
Scene *scene = pbd->scene;
poselib_keytag_pose(C, scene, pbd);
/* Ensure the redo panel has the actually-used value, instead of the initial value. */
RNA_float_set(op->ptr, "blend_factor", pbd->blend_factor);
RNA_boolean_set(op->ptr, "flipped", pbd->is_flipped);
break;
}
case POSE_BLEND_INIT:
case POSE_BLEND_BLENDING:
case POSE_BLEND_ORIGINAL:
/* Cleanup should not be called directly from these states. */
BLI_assert_msg(0, "poselib_blend_cleanup: unexpected pose blend state");
BKE_report(op->reports, RPT_ERROR, "Internal pose library error, canceling operator");
ATTR_FALLTHROUGH;
case POSE_BLEND_CANCEL:
BKE_pose_backup_restore(pbd->pose_backup);
break;
}
for (Object *ob : pbd->objects) {
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
WM_event_add_notifier(C, NC_OBJECT | ND_POSE, ob);
}
/* Update mouse-hover highlights. */
WM_event_add_mousemove(win);
}
static void poselib_blend_free(wmOperator *op)
{
PoseBlendData *pbd = static_cast<PoseBlendData *>(op->customdata);
if (pbd == nullptr) {
return;
}
if (pbd->act_flipped) {
BKE_id_free(nullptr, pbd->act_flipped);
}
poselib_tempload_exit(pbd);
/* Free temp data for operator */
BKE_pose_backup_free(pbd->pose_backup);
pbd->pose_backup = nullptr;
MEM_delete(pbd);
}
static wmOperatorStatus poselib_blend_exit(bContext *C, wmOperator *op)
{
PoseBlendData *pbd = static_cast<PoseBlendData *>(op->customdata);
const ePoseBlendState exit_state = pbd->state;
poselib_blend_cleanup(C, op);
poselib_blend_free(op);
wmWindow *win = CTX_wm_window(C);
WM_cursor_modal_restore(win);
if (exit_state == POSE_BLEND_CANCEL) {
return OPERATOR_CANCELLED;
}
return OPERATOR_FINISHED;
}
/* Cancel previewing operation (called when exiting Blender) */
static void poselib_blend_cancel(bContext *C, wmOperator *op)
{
PoseBlendData *pbd = static_cast<PoseBlendData *>(op->customdata);
pbd->state = POSE_BLEND_CANCEL;
poselib_blend_exit(C, op);
}
/* Main modal status check. */
static wmOperatorStatus poselib_blend_modal(bContext *C, wmOperator *op, const wmEvent *event)
{
const wmOperatorStatus operator_result = poselib_blend_handle_event(C, op, event);
const PoseBlendData *pbd = static_cast<const PoseBlendData *>(op->customdata);
if (ELEM(pbd->state, POSE_BLEND_CONFIRM, POSE_BLEND_CANCEL)) {
return poselib_blend_exit(C, op);
}
if (pbd->needs_redraw) {
WorkspaceStatus status(C);
if (pbd->state == POSE_BLEND_BLENDING) {
status.item(IFACE_("Show Original Pose"), ICON_EVENT_TAB);
}
else {
status.item(IFACE_("Show Blended Pose"), ICON_EVENT_TAB);
}
ED_slider_status_get(pbd->slider, status);
status.item_bool(IFACE_("Flip Pose"), pbd->is_flipped, ICON_EVENT_CTRL);
poselib_blend_apply(C, op);
}
return operator_result;
}
static wmOperatorStatus poselib_apply_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
if (!poselib_blend_init_data(C, op, event)) {
poselib_blend_free(op);
return OPERATOR_CANCELLED;
}
poselib_blend_apply(C, op);
PoseBlendData *pbd = static_cast<PoseBlendData *>(op->customdata);
pbd->state = POSE_BLEND_CONFIRM;
return poselib_blend_exit(C, op);
}
static wmOperatorStatus poselib_apply_exec(bContext *C, wmOperator *op)
{
return poselib_apply_invoke(C, op, nullptr);
}
/* Modal Operator init. */
static wmOperatorStatus poselib_blend_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
if (!poselib_blend_init_data(C, op, event)) {
poselib_blend_free(op);
return OPERATOR_CANCELLED;
}
wmWindow *win = CTX_wm_window(C);
WM_cursor_modal_set(win, WM_CURSOR_EW_SCROLL);
/* Do initial apply to have something to look at. */
poselib_blend_apply(C, op);
WM_event_add_modal_handler(C, op);
return OPERATOR_RUNNING_MODAL;
}
/* Single-shot apply. */
static wmOperatorStatus poselib_blend_exec(bContext *C, wmOperator *op)
{
return poselib_apply_invoke(C, op, nullptr);
}
/* Poll callback for operators that require existing PoseLib data (with poses) to work. */
static bool poselib_blend_poll(bContext *C)
{
Span<Object *> selected_pose_objects = get_poselib_objects(*C);
if (selected_pose_objects.is_empty()) {
/* Pose lib is only for armatures in pose mode. */
return false;
}
return true;
}
/* Operator properties can set an asset reference to determine the asset to operate on (the pose
* can then be applied via shortcut too, for example). If this isn't set, an active asset from
* context is queried. */
void POSELIB_OT_apply_pose_asset(wmOperatorType *ot)
{
PropertyRNA *prop;
/* Identifiers: */
ot->name = "Apply Pose Asset";
ot->idname = "POSELIB_OT_apply_pose_asset";
ot->description = "Apply the given Pose Action to the rig";
/* Callbacks: */
ot->invoke = poselib_apply_invoke;
ot->exec = poselib_apply_exec;
ot->poll = poselib_blend_poll;
/* Flags: */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* Properties: */
ed::asset::operator_asset_reference_props_register(*ot->srna);
RNA_def_float_factor(ot->srna,
"blend_factor",
1.0f,
-FLT_MAX,
FLT_MAX,
"Blend Factor",
"Amount that the pose is applied on top of the existing poses. A negative "
"value will subtract the pose instead of adding it",
-1.0f,
1.0f);
prop = RNA_def_boolean(ot->srna,
"flipped",
false,
"Apply Flipped",
"When enabled, applies the pose flipped over the X-axis");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
}
/* See comment on #POSELIB_OT_apply_pose_asset. */
void POSELIB_OT_blend_pose_asset(wmOperatorType *ot)
{
PropertyRNA *prop;
/* Identifiers: */
ot->name = "Blend Pose Asset";
ot->idname = "POSELIB_OT_blend_pose_asset";
ot->description = "Blend the given Pose Action to the rig";
/* Callbacks: */
ot->invoke = poselib_blend_invoke;
ot->modal = poselib_blend_modal;
ot->cancel = poselib_blend_cancel;
ot->exec = poselib_blend_exec;
ot->poll = poselib_blend_poll;
/* Flags: */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR_X;
/* Properties: */
ed::asset::operator_asset_reference_props_register(*ot->srna);
prop = RNA_def_float_factor(ot->srna,
"blend_factor",
0.0f,
-FLT_MAX,
FLT_MAX,
"Blend Factor",
"Amount that the pose is applied on top of the existing poses. A "
"negative value will subtract the pose instead of adding it",
-1.0f,
1.0f);
/* Blending should always start at 0%, and not at whatever percentage was last used. This RNA
* property just exists for symmetry with the Apply operator (and thus simplicity of the rest of
* the code, which can assume this property exists). */
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
prop = RNA_def_boolean(ot->srna,
"flipped",
false,
"Apply Flipped",
"When enabled, applies the pose flipped over the X-axis");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
prop = RNA_def_boolean(ot->srna,
"release_confirm",
false,
"Confirm on Release",
"Always confirm operation when releasing button");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
}
} // namespace blender

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,542 @@
/* SPDX-FileCopyrightText: 2009 Blender Authors, Joshua Leung.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edarmature
*/
#include "MEM_guardedalloc.h"
#include "BLI_listbase.h"
#include "BLI_math_rotation.h"
#include "BLI_math_vector.h"
#include "BLI_string.h"
#include "BLI_vector.hh"
#include "DNA_anim_types.h"
#include "DNA_armature_types.h"
#include "DNA_object_types.h"
#include "BKE_action.hh"
#include "BKE_anim_data.hh"
#include "BKE_idprop.hh"
#include "BKE_layer.hh"
#include "BKE_object.hh"
#include "BKE_context.hh"
#include "DEG_depsgraph.hh"
#include "RNA_access.hh"
#include "RNA_path.hh"
#include "RNA_prototypes.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "ED_anim_api.hh"
#include "ED_anim_transformable.hh"
#include "ED_armature.hh"
#include "ED_keyframing.hh"
#include "ED_object.hh"
#include "ANIM_action.hh"
#include "ANIM_action_iterators.hh"
#include "ANIM_keyframing.hh"
#include "ANIM_keyingsets.hh"
#include "ANIM_rna.hh"
#include "armature_intern.hh"
namespace blender {
/* *********************************************** */
/* Contents of this File:
*
* This file contains methods shared between Pose Slide and Pose Lib;
* primarily the functions in question concern Animato <-> Pose
* convenience functions, such as applying/getting pose values
* and/or inserting keyframes for these.
*/
/* *********************************************** */
/* FCurves <-> PoseChannels Links */
/**
* Fills the `r_curves` vector with curves used by the given `ptr`.
* The returned flags indicate which properties are animated.
*/
static eAction_TransformFlags get_item_transform_flags_and_fcurves(ID &id,
PointerRNA &ptr,
Vector<FCurve *> &r_curves)
{
AnimData *adt = BKE_animdata_from_id(&id);
if (!adt || !adt->action) {
return eAction_TransformFlags(0);
}
animrig::Action &action = adt->action->wrap();
short flags = 0;
/* Get the basic path to the properties of interest. */
const std::optional<std::string> path_to_struct = RNA_path_from_ID_to_struct(&ptr);
StringRef base_path;
if (RNA_struct_is_ID(ptr.type)) {
base_path = "";
}
else {
if (!path_to_struct.has_value()) {
BLI_assert_unreachable();
return eAction_TransformFlags(0);
}
base_path = path_to_struct.value();
}
animrig::foreach_fcurve_in_action_slot(action, adt->slot_handle, [&](FCurve &fcurve) {
if (fcurve.rna_path == nullptr) {
return;
}
StringRefNull fcurve_path(fcurve.rna_path);
if (!base_path.is_empty() && !fcurve_path.startswith(base_path)) {
return;
}
StringRef property_name;
if (base_path.is_empty()) {
property_name = fcurve_path;
}
else {
/* Normal properties are separated by a dot, custom properties don't have that. */
if (fcurve_path[base_path.size()] == '.') {
property_name = fcurve_path.substr(base_path.size() + 1);
}
else {
property_name = fcurve_path.substr(base_path.size());
}
}
if (property_name == "location") {
flags |= ACT_TRANS_LOC;
r_curves.append(&fcurve);
return;
}
if (property_name == "scale") {
flags |= ACT_TRANS_SCALE;
r_curves.append(&fcurve);
return;
}
if (property_name == "rotation_euler" || property_name == "rotation_quaternion" ||
property_name == "rotation_axis_angle")
{
flags |= ACT_TRANS_ROT;
r_curves.append(&fcurve);
return;
}
if (property_name.startswith("bbone_")) {
flags |= ACT_TRANS_BBONE;
r_curves.append(&fcurve);
return;
}
/* Custom properties only. */
if (property_name.startswith("[\"")) {
flags |= ACT_TRANS_PROP;
r_curves.append(&fcurve);
return;
}
});
/* return flags found */
return eAction_TransformFlags(flags);
}
/**
* Stores a `PropertySnapshot` of the property with the given `property_name` in the given Vector.
* If the property does not exist in the `ptr` the function doesn't do anything. Also the property
* has to be supported by `ed::rna_property_get_as_float`.
*/
static void store_property_snapshot(PointerRNA &ptr,
const StringRef property_name,
Vector<PropertySnapshot> &snapshots)
{
PropertyRNA *prop = RNA_struct_find_property(&ptr, property_name.data());
if (!prop) {
return;
}
Array<float> property_values = animrig::rna_property_get_as_float(ptr, *prop);
if (property_values.size() == 0) {
/* Unsupported property type. */
return;
}
snapshots.append({prop, std::move(property_values)});
}
static void store_starting_transform(SlideSubject &slide_subject,
ed::AnimTransformable &transformable)
{
slide_subject.old_loc = transformable.get_property(
ed::AnimTransformable::PropertyType::LOCATION);
slide_subject.old_rot = transformable.get_rotation();
slide_subject.old_scale = transformable.get_property(ed::AnimTransformable::PropertyType::SCALE);
}
/**
* `id_properties` and `system_properties` can be a nullptr and are skipped if they are.
*/
static void store_id_properties(SlideSubject &slide_subject,
PointerRNA &ptr,
IDProperty *id_properties,
IDProperty *system_properties)
{
if (id_properties) {
for (const IDProperty &id_prop : id_properties->data.group) {
char name_escaped[MAX_IDPROP_NAME * 2];
BLI_str_escape(name_escaped, id_prop.name, sizeof(name_escaped));
std::string property_name_with_brackets = fmt::format("[\"{}\"]", name_escaped);
store_property_snapshot(ptr, property_name_with_brackets, slide_subject.properties);
}
}
if (system_properties) {
for (const IDProperty &id_prop : system_properties->data.group) {
store_property_snapshot(ptr, id_prop.name, slide_subject.system_properties);
}
}
}
/* helper for slide_subjects_get() -> get the relevant F-Curves per PoseChannel */
static void pchan_to_slide_subject(ListBaseT<SlideSubject> &slide_subjects,
Object &ob,
bPoseChannel &pchan)
{
PointerRNA bone_ptr = RNA_pointer_create_discrete(&ob.id, RNA_PoseBone, &pchan);
Vector<FCurve *> curves;
const eAction_TransformFlags transFlags = get_item_transform_flags_and_fcurves(
ob.id, bone_ptr, curves);
if (!transFlags) {
return;
}
SlideSubject *slide_subject = MEM_new<SlideSubject>("SlideSubject");
BLI_addtail(&slide_subjects, slide_subject);
slide_subject->fcurves = curves;
ed::AnimTransformable *transformable = MEM_new<ed::AnimTransformable>(
"transformable_pose_bone", ob, pchan);
slide_subject->transformable = transformable;
/* Set pchan's transform flags. */
slide_subject->transform_flag = transFlags;
store_starting_transform(*slide_subject, *transformable);
slide_subject->ptr = bone_ptr;
if (transFlags & ACT_TRANS_BBONE) {
store_property_snapshot(bone_ptr, "bbone_rollin", slide_subject->additional_properties);
store_property_snapshot(bone_ptr, "bbone_rollout", slide_subject->additional_properties);
store_property_snapshot(bone_ptr, "bbone_curveinx", slide_subject->additional_properties);
store_property_snapshot(bone_ptr, "bbone_curveoutx", slide_subject->additional_properties);
store_property_snapshot(bone_ptr, "bbone_curveinz", slide_subject->additional_properties);
store_property_snapshot(bone_ptr, "bbone_curveoutz", slide_subject->additional_properties);
store_property_snapshot(bone_ptr, "bbone_easein", slide_subject->additional_properties);
store_property_snapshot(bone_ptr, "bbone_easeout", slide_subject->additional_properties);
store_property_snapshot(bone_ptr, "bbone_scalein", slide_subject->additional_properties);
store_property_snapshot(bone_ptr, "bbone_scaleout", slide_subject->additional_properties);
}
/* Make copy of custom properties. */
if (transFlags & ACT_TRANS_PROP) {
store_id_properties(*slide_subject, bone_ptr, pchan.prop, pchan.system_properties);
}
}
static Object *animated_armature_ob_get(Object *ob_)
{
Object *ob = BKE_object_pose_armature_get(ob_);
if (!ELEM(nullptr, ob, ob->data, ob->adt, ob->adt->action)) {
return ob;
}
return nullptr;
}
static void get_pose_bones_for_slide(bContext *C, ListBaseT<SlideSubject> &slide_subjects)
{
/* For each Pose-Channel which gets affected, get the F-Curves for that channel
* and set the relevant transform flags... */
Object *prev_ob, *ob_pose_armature;
prev_ob = nullptr;
ob_pose_armature = nullptr;
/* Used to avoid duplicates when using mirroring. */
Set<bPoseChannel *> inserted_bones;
CTX_DATA_BEGIN_WITH_ID (C, bPoseChannel *, pchan, selected_pose_bones, Object *, ob) {
BLI_assert(pchan != nullptr);
if (ob != prev_ob) {
prev_ob = ob;
ob_pose_armature = animated_armature_ob_get(ob);
}
if (ob_pose_armature == nullptr) {
continue;
}
if (!ob_pose_armature->adt || !ob_pose_armature->adt->action) {
/* No action means no FCurves. */
continue;
}
if (!inserted_bones.add(pchan)) {
continue;
}
pchan_to_slide_subject(slide_subjects, *ob_pose_armature, *pchan);
if (ob_pose_armature->pose->flag & POSE_MIRROR_EDIT) {
bPoseChannel *pchan_mirror = BKE_pose_channel_get_mirrored(ob->pose, pchan->name);
if (pchan_mirror && inserted_bones.add(pchan_mirror)) {
pchan_to_slide_subject(slide_subjects, *ob_pose_armature, *pchan_mirror);
}
}
}
CTX_DATA_END;
/* If no PoseChannels were found, try a second pass, doing visible ones instead.
* i.e. if nothing selected, do whole pose.
*/
if (slide_subjects.is_empty()) {
prev_ob = nullptr;
ob_pose_armature = nullptr;
CTX_DATA_BEGIN_WITH_ID (C, bPoseChannel *, pchan, visible_pose_bones, Object *, ob) {
BLI_assert(pchan != nullptr);
if (ob != prev_ob) {
prev_ob = ob;
ob_pose_armature = animated_armature_ob_get(ob);
}
if (ob_pose_armature == nullptr) {
continue;
}
if (!ob_pose_armature->adt || !ob_pose_armature->adt->action) {
/* No action means no FCurves. */
continue;
}
pchan_to_slide_subject(slide_subjects, *ob_pose_armature, *pchan);
}
CTX_DATA_END;
}
}
static void get_objects_for_slide(bContext *C, ListBaseT<SlideSubject> &slider_data)
{
CTX_DATA_BEGIN (C, Object *, ob, selected_objects) {
PointerRNA object_ptr = RNA_pointer_create_discrete(&ob->id, RNA_Object, ob);
Vector<FCurve *> curves;
const eAction_TransformFlags transFlags = get_item_transform_flags_and_fcurves(
ob->id, object_ptr, curves);
if (!transFlags) {
continue;
}
SlideSubject *slide_subject = MEM_new<SlideSubject>("TransformableFCurveLink");
BLI_addtail(&slider_data, slide_subject);
slide_subject->fcurves = curves;
ed::AnimTransformable *transformable = MEM_new<ed::AnimTransformable>("transformable_object",
*ob);
slide_subject->transformable = transformable;
slide_subject->transform_flag = transFlags;
store_starting_transform(*slide_subject, *transformable);
slide_subject->ptr = object_ptr;
if (transFlags & ACT_TRANS_PROP) {
store_id_properties(*slide_subject, object_ptr, ob->id.properties, ob->id.system_properties);
}
}
CTX_DATA_END;
}
void slide_subjects_get(bContext *C, ListBaseT<SlideSubject> *r_transformable_list)
{
BLI_assert(r_transformable_list != nullptr);
const eContextObjectMode mode = CTX_data_mode_enum(C);
switch (mode) {
case CTX_MODE_POSE:
case CTX_MODE_PAINT_WEIGHT:
get_pose_bones_for_slide(C, *r_transformable_list);
break;
case CTX_MODE_OBJECT:
get_objects_for_slide(C, *r_transformable_list);
break;
default:
/* Not implemented. */
BLI_assert_unreachable();
break;
}
}
void slide_subjects_free(ListBaseT<SlideSubject> *slide_subjects)
{
SlideSubject *slide_subject, *pfln = nullptr;
/* free the temp pchan links and their data */
for (slide_subject = static_cast<SlideSubject *>(slide_subjects->first); slide_subject;
slide_subject = pfln)
{
pfln = slide_subject->next;
MEM_delete(slide_subject->transformable);
/* We cannot use BLI_freelinkN because that casts the SlideSubject to a C-style
* struct causing MEM_delete to do a C-style delete and not deallocate the Vector. */
BLI_remlink(slide_subjects, slide_subject);
MEM_delete(slide_subject);
}
}
/* ------------------------- */
void slide_subjects_refresh(bContext *C, const SlideSubject &slide_subject)
{
ID *id = slide_subject.ptr.owner_id;
DEG_id_tag_update(id, ID_RECALC_GEOMETRY);
DEG_id_tag_update(id, ID_RECALC_TRANSFORM);
switch (slide_subject.transformable->type()) {
case ed::AnimTransformable::Type::POSE_BONE:
WM_event_add_notifier(C, NC_OBJECT | ND_POSE, id_cast<Object *>(id));
break;
case ed::AnimTransformable::Type::OBJECT:
WM_event_add_notifier(C, NC_OBJECT | ND_TRANSFORM, id_cast<Object *>(id));
break;
}
AnimData *adt = BKE_animdata_from_id(id);
if (adt && adt->action) {
DEG_id_tag_update(&adt->action->id, ID_RECALC_ANIMATION_NO_FLUSH);
}
}
void slide_subjects_reset(ListBaseT<SlideSubject> *slide_subjects)
{
/* Iterate over each transformable affected, restoring all channels to their original values. */
for (SlideSubject &slide_subject : *slide_subjects) {
ed::AnimTransformable *transformable = slide_subject.transformable;
/* just copy all the values over regardless of whether they changed or not */
transformable->set_property(ed::AnimTransformable::PropertyType::LOCATION,
slide_subject.old_loc,
ed::AXIS_MUTABLE_ALL);
transformable->set_rotation(slide_subject.old_rot);
transformable->set_property(
ed::AnimTransformable::PropertyType::SCALE, slide_subject.old_scale, ed::AXIS_MUTABLE_ALL);
for (PropertySnapshot &extra_prop : slide_subject.additional_properties) {
animrig::rna_property_set_as_float(
slide_subject.ptr, *extra_prop.property, extra_prop.values);
}
for (PropertySnapshot &custom_prop : slide_subject.properties) {
animrig::rna_property_set_as_float(
slide_subject.ptr, *custom_prop.property, custom_prop.values);
}
for (PropertySnapshot &custom_prop : slide_subject.system_properties) {
animrig::rna_property_set_as_float(
slide_subject.ptr, *custom_prop.property, custom_prop.values);
}
}
}
void slide_subjects_autokey(bContext *C,
Scene *scene,
const ListBaseT<SlideSubject> *slide_subjects)
{
bool anything_to_key = false;
for (SlideSubject &slide_subject : *slide_subjects) {
if (!animrig::autokeyframe_cfra_can_key(scene, slide_subject.ptr.owner_id)) {
continue;
}
anything_to_key = true;
break;
}
/* If there is nothing to key, return before deselecting any keys. */
if (!anything_to_key) {
return;
}
ANIM_deselect_keys_in_animation_editors(C);
/* Insert keyframes as necessary if auto-key-framing. */
for (SlideSubject &slide_subject : *slide_subjects) {
PointerRNA &ptr = slide_subject.ptr;
if (!animrig::autokeyframe_cfra_can_key(scene, slide_subject.ptr.owner_id)) {
continue;
}
Vector<RNAPath> paths;
/* The transform flags tell us which properties have keys. Properties without keys cannot pose
* slide, so should not be auto keyed. */
if (slide_subject.transform_flag & ACT_TRANS_LOC) {
paths.append({"location"});
}
if (slide_subject.transform_flag & ACT_TRANS_ROT) {
paths.append(
{animrig::get_rotation_mode_path(slide_subject.transformable->get_rotation_mode())});
}
if (slide_subject.transform_flag & ACT_TRANS_SCALE) {
paths.append({"scale"});
}
/* No need to check the transform_flag here, because those vectors are only filled if the flag
* was set in the first place.*/
for (const PropertySnapshot &snapshot : slide_subject.additional_properties) {
paths.append({RNA_property_identifier(snapshot.property)});
}
for (const PropertySnapshot &snapshot : slide_subject.properties) {
char name_escaped[MAX_IDPROP_NAME * 2];
BLI_str_escape(
name_escaped, RNA_property_identifier(snapshot.property), sizeof(name_escaped));
paths.append({fmt::format("[\"{}\"]", name_escaped)});
}
for (const PropertySnapshot &snapshot : slide_subject.system_properties) {
paths.append({RNA_property_identifier(snapshot.property)});
}
switch (slide_subject.transformable->type()) {
case ed::AnimTransformable::Type::POSE_BONE:
animrig::autokeyframe_pose_channel(C,
scene,
id_cast<Object *>(ptr.owner_id),
static_cast<bPoseChannel *>(ptr.data),
paths,
false);
break;
case ed::AnimTransformable::Type::OBJECT:
animrig::autokeyframe_object(C, scene, id_cast<Object *>(ptr.owner_id), paths);
break;
}
}
Vector<Object *> objects;
for (SlideSubject &slide_subject : *slide_subjects) {
ID *owner_id = slide_subject.transformable->owner_id();
if (GS(owner_id->name) != ID_OB) {
continue;
}
objects.append(id_cast<Object *>(owner_id));
}
/* This includes all motion paths for bones. Could be more fine grained in the future to avoid
* needless updates to data that was not changed. */
ed::object::motion_paths_recalc(C, scene, ANIMVIZ_CALC_RANGE_CHANGED, objects);
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_ADDED, nullptr);
}
/* *********************************************** */
} // namespace blender