Anim: support per-bone "Display As" overrides

Armature bone display mode (Octahedral, Stick, Envelope, B-Bone,
Wire) could only be set on the whole armature. This adds ability to
override the display mode per-bone (by default bones use the
same display mode as the armature).

Images in the PR.

Pull Request: https://projects.blender.org/blender/blender/pulls/138445
This commit is contained in:
Aras Pranckevicius
2025-05-16 15:06:36 +02:00
committed by Aras Pranckevicius
parent fcc6e022b1
commit 8bf73386f2
10 changed files with 435 additions and 520 deletions

View File

@@ -327,6 +327,7 @@ class BONE_PT_display(BoneButtonsPanel, Panel):
hide_select_sub = col.column()
hide_select_sub.active = not bone.hide
hide_select_sub.prop(bone, "hide_select", invert_checkbox=True)
col.prop(bone, "display_type", text="Display As")
# Figure out the pose bone.
ob = context.object
@@ -359,6 +360,7 @@ class BONE_PT_display(BoneButtonsPanel, Panel):
hide_select_sub = col.column()
hide_select_sub.active = not bone.hide
hide_select_sub.prop(bone, "hide_select", invert_checkbox=True)
col.prop(bone, "display_type", text="Display As")
layout.prop(bone.color, "palette", text="Bone Color")
self.draw_bone_color_ui(layout, bone.color)

View File

@@ -64,6 +64,7 @@ struct EditBone {
*/
int flag;
int layer;
int drawtype; /* eArmature_Drawtype */
char inherit_scale_mode;
/* Envelope distance & weight */

View File

@@ -27,7 +27,7 @@
/* Blender file format version. */
#define BLENDER_FILE_VERSION BLENDER_VERSION
#define BLENDER_FILE_SUBVERSION 71
#define BLENDER_FILE_SUBVERSION 72
/* Minimum Blender version that supports reading file written with the current
* version. Older Blender versions will test this and cancel loading the file, showing a warning to

View File

@@ -4339,6 +4339,15 @@ static void version_convert_sculpt_planar_brushes(Main *bmain)
}
}
static void version_set_default_bone_drawtype(Main *bmain)
{
LISTBASE_FOREACH (bArmature *, arm, &bmain->armatures) {
blender::animrig::ANIM_armature_foreach_bone(
&arm->bonebase, [](Bone *bone) { bone->drawtype = ARM_BONE_DEFAULT; });
BLI_assert_msg(!arm->edbo, "Armatures should not be saved in edit mode");
}
}
void blo_do_versions_450(FileData * /*fd*/, Library * /*lib*/, Main *bmain)
{
@@ -5148,6 +5157,10 @@ void blo_do_versions_450(FileData * /*fd*/, Library * /*lib*/, Main *bmain)
FOREACH_NODETREE_END;
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 405, 72)) {
version_set_default_bone_drawtype(bmain);
}
/* Always run this versioning (keep at the bottom of the function). Meshes are written with the
* legacy format which always needs to be converted to the new format on file load. To be moved
* to a subversion check in 5.0. */

View File

@@ -189,56 +189,6 @@ class UnifiedBonePtr {
}
};
/**
* Bone drawing strategy class.
*
* Depending on the armature display mode, a different subclass is used to
* manage drawing. These subclasses are defined further down in the file. This
* abstract class needs to be defined before any function that uses it, though.
*/
class ArmatureBoneDrawStrategy {
public:
virtual void update_display_matrix(UnifiedBonePtr bone) const = 0;
virtual void draw_context_setup(Armatures::DrawContext *ctx,
const bool is_filled,
const bool do_envelope_dist) const = 0;
virtual void draw_bone(const Armatures::DrawContext *ctx,
const UnifiedBonePtr bone,
const eBone_Flag boneflag,
const int select_id) const = 0;
/** Should the relationship line between this bone and its parent be drawn? */
virtual bool should_draw_relation_to_parent(const UnifiedBonePtr bone,
const eBone_Flag boneflag) const
{
const bool has_parent = bone.has_parent();
if (bone.is_editbone() && has_parent) {
/* Always draw for unconnected bones, regardless of selection,
* since riggers will want to know about the links between bones
*/
return (boneflag & BONE_CONNECTED) == 0;
}
if (bone.is_posebone() && has_parent) {
/* Only draw between unconnected bones. */
if (boneflag & BONE_CONNECTED) {
return false;
}
/* Only draw if bone or its parent is selected - reduces viewport
* complexity with complex rigs */
const bPoseChannel *pchan = bone.as_posebone();
return (boneflag & BONE_SELECTED) ||
(pchan->parent->bone && (pchan->parent->bone->flag & BONE_SELECTED));
}
return false;
}
};
/* -------------------------------------------------------------------- */
/** \name Shading Groups
* \{ */
@@ -992,7 +942,7 @@ static void draw_bone_update_disp_matrix_default(UnifiedBonePtr bone)
float(*disp_tail_mat)[4] = bone.disp_tail_mat();
/* TODO: This should be moved to depsgraph or armature refresh
* and not be tight to the draw pass creation.
* and not be tied to the draw pass creation.
* This would refresh armature without invalidating the draw cache */
if (bone.is_posebone()) {
bPoseChannel *pchan = bone.as_posebone();
@@ -1014,6 +964,38 @@ static void draw_bone_update_disp_matrix_default(UnifiedBonePtr bone)
translate_m4(disp_tail_mat, 0.0f, 1.0f, 0.0f);
}
static void draw_bone_update_disp_matrix_custom_shape(UnifiedBonePtr bone)
{
float bone_scale[3];
float(*bone_mat)[4];
float(*disp_mat)[4];
float(*disp_tail_mat)[4];
float rot_mat[3][3];
/* Custom bone shapes are only supported in pose mode for now. */
bPoseChannel *pchan = bone.as_posebone();
/* TODO: This should be moved to depsgraph or armature refresh
* and not be tied to the draw pass creation.
* This would refresh armature without invalidating the draw cache. */
mul_v3_v3fl(bone_scale, pchan->custom_scale_xyz, PCHAN_CUSTOM_BONE_LENGTH(pchan));
bone_mat = pchan->custom_tx ? pchan->custom_tx->pose_mat : pchan->pose_mat;
disp_mat = bone.disp_mat();
disp_tail_mat = pchan->disp_tail_mat;
eulO_to_mat3(rot_mat, pchan->custom_rotation_euler, ROT_MODE_XYZ);
copy_m4_m4(disp_mat, bone_mat);
translate_m4(disp_mat,
pchan->custom_translation[0],
pchan->custom_translation[1],
pchan->custom_translation[2]);
mul_m4_m4m3(disp_mat, disp_mat, rot_mat);
rescale_m4(disp_mat, bone_scale);
copy_m4_m4(disp_tail_mat, disp_mat);
translate_m4(disp_tail_mat, 0.0f, 1.0f, 0.0f);
}
/* compute connected child pointer for B-Bone drawing */
static void edbo_compute_bbone_child(bArmature *arm)
{
@@ -1153,7 +1135,7 @@ static void draw_bone_update_disp_matrix_bbone(UnifiedBonePtr bone)
short bbone_segments;
/* TODO: This should be moved to depsgraph or armature refresh
* and not be tight to the draw pass creation.
* and not be tied to the draw pass creation.
* This would refresh armature without invalidating the draw cache. */
if (bone.is_posebone()) {
bPoseChannel *pchan = bone.as_posebone();
@@ -1334,6 +1316,285 @@ static void draw_points(const Armatures::DrawContext *ctx,
}
}
static void bone_draw_custom_shape(const Armatures::DrawContext *ctx,
const UnifiedBonePtr bone,
const eBone_Flag boneflag,
const int select_id)
{
const float *col_solid = get_bone_solid_color(ctx, boneflag);
const float *col_wire = get_bone_wire_color(ctx, boneflag);
const float *col_hint = get_bone_hint_color(ctx, boneflag);
const float(*disp_mat)[4] = bone.disp_mat();
/* TODO(fclem): Code after this scope should be removed when we remove the legacy code. */
auto sel_id = ctx->res->select_id(*ctx->ob_ref, select_id | BONESEL_BONE);
/* Custom bone shapes are only supported in pose mode for now. */
const bPoseChannel *pchan = bone.as_posebone();
Object *custom_shape_ob = pchan->custom;
if (custom_shape_ob->type == OB_EMPTY) {
if (custom_shape_ob->empty_drawtype != OB_EMPTY_IMAGE) {
drw_shgroup_bone_custom_empty(
ctx, disp_mat, col_wire, pchan->custom_shape_wire_width, sel_id, pchan->custom);
}
}
else if (boneflag & (BONE_DRAWWIRE | BONE_DRAW_LOCKED_WEIGHT)) {
drw_shgroup_bone_custom_wire(
ctx, disp_mat, col_wire, pchan->custom_shape_wire_width, sel_id, pchan->custom);
}
else {
drw_shgroup_bone_custom_solid(ctx,
disp_mat,
col_solid,
col_hint,
col_wire,
pchan->custom_shape_wire_width,
sel_id,
pchan->custom);
}
}
static void bone_draw_octa(const Armatures::DrawContext *ctx,
const UnifiedBonePtr bone,
const eBone_Flag boneflag,
const int select_id)
{
const float *col_solid = get_bone_solid_with_consts_color(ctx, bone, boneflag);
const float *col_wire = get_bone_wire_color(ctx, boneflag);
const float *col_hint = get_bone_hint_color(ctx, boneflag);
auto sel_id = ctx->res->select_id(*ctx->ob_ref, select_id | BONESEL_BONE);
float4x4 bone_mat = ctx->ob->object_to_world() * float4x4(bone.disp_mat());
if (ctx->is_filled) {
ctx->bone_buf->octahedral_fill_buf.append({bone_mat, col_solid, col_hint}, sel_id);
}
if (col_wire[3] > 0.0f) {
ctx->bone_buf->octahedral_outline_buf.append({bone_mat, col_wire}, sel_id);
}
draw_points(ctx, bone, boneflag, col_solid, select_id);
}
static void bone_draw_line(const Armatures::DrawContext *ctx,
const UnifiedBonePtr bone,
const eBone_Flag boneflag,
const int select_id)
{
const float *col_bone = get_bone_solid_with_consts_color(ctx, bone, boneflag);
const float *col_wire = get_bone_wire_color(ctx, boneflag);
const float no_display[4] = {0.0f, 0.0f, 0.0f, 0.0f};
const float *col_head = no_display;
const float *col_tail = col_bone;
if (ctx->const_color != nullptr) {
col_wire = no_display; /* actually shrink the display. */
col_bone = col_head = col_tail = ctx->const_color;
}
else {
const UniformData &theme = ctx->res->theme;
if (bone.is_editbone() && bone.flag() & BONE_TIPSEL) {
col_tail = &theme.colors.vert_select.x;
}
/* Draw root point if we are not connected to our parent. */
if (!(bone.has_parent() && (boneflag & BONE_CONNECTED))) {
if (bone.is_editbone()) {
col_head = (bone.flag() & BONE_ROOTSEL) ? &theme.colors.vert_select.x : col_bone;
}
else {
col_head = col_bone;
}
}
}
if (select_id == -1) {
/* Not in bone selection mode (can still be object select mode), draw everything at once.
*/
drw_shgroup_bone_stick(
ctx, bone.disp_mat(), col_wire, col_bone, col_head, col_tail, select_id);
}
else {
/* In selection mode, draw bone, root and tip separately. */
drw_shgroup_bone_stick(ctx,
bone.disp_mat(),
col_wire,
col_bone,
no_display,
no_display,
select_id | BONESEL_BONE);
if (col_head[3] > 0.0f) {
drw_shgroup_bone_stick(ctx,
bone.disp_mat(),
col_wire,
no_display,
col_head,
no_display,
select_id | BONESEL_ROOT);
}
drw_shgroup_bone_stick(
ctx, bone.disp_mat(), col_wire, no_display, no_display, col_tail, select_id | BONESEL_TIP);
}
}
static void bone_draw_b_bone(const Armatures::DrawContext *ctx,
const UnifiedBonePtr bone,
const eBone_Flag boneflag,
const int select_id)
{
const float *col_solid = get_bone_solid_with_consts_color(ctx, bone, boneflag);
const float *col_wire = get_bone_wire_color(ctx, boneflag);
const float *col_hint = get_bone_hint_color(ctx, boneflag);
/* NOTE: Cannot reinterpret as float4x4 because of alignment requirement of float4x4.
* This would require a deeper refactor. */
Span<Mat4> bbone_matrices;
if (bone.is_posebone()) {
bbone_matrices = {(Mat4 *)bone.as_posebone()->draw_data->bbone_matrix,
bone.as_posebone()->bone->segments};
}
else {
bbone_matrices = {(Mat4 *)bone.as_editbone()->disp_bbone_mat, bone.as_editbone()->segments};
}
auto sel_id = ctx->res->select_id(*ctx->ob_ref, select_id | BONESEL_BONE);
for (const Mat4 &in_bone_mat : bbone_matrices) {
float4x4 bone_mat = ctx->ob->object_to_world() * float4x4(in_bone_mat.mat);
if (ctx->is_filled) {
ctx->bone_buf->bbones_fill_buf.append({bone_mat, col_solid, col_hint}, sel_id);
}
if (col_wire[3] > 0.0f) {
ctx->bone_buf->bbones_outline_buf.append({bone_mat, col_wire}, sel_id);
}
}
if (ctx->draw_mode == ARM_DRAW_MODE_EDIT) {
draw_points(ctx, bone, boneflag, col_solid, select_id);
}
}
static void bone_draw_envelope(const Armatures::DrawContext *ctx,
const UnifiedBonePtr bone,
const eBone_Flag boneflag,
const int select_id)
{
const float *col_solid = get_bone_solid_with_consts_color(ctx, bone, boneflag);
const float *col_wire = get_bone_wire_color(ctx, boneflag);
const float *col_hint = get_bone_hint_color(ctx, boneflag);
const float *rad_head, *rad_tail, *distance;
if (bone.is_editbone()) {
const EditBone *eBone = bone.as_editbone();
rad_tail = &eBone->rad_tail;
distance = &eBone->dist;
rad_head = (eBone->parent && (boneflag & BONE_CONNECTED)) ? &eBone->parent->rad_tail :
&eBone->rad_head;
}
else {
const bPoseChannel *pchan = bone.as_posebone();
rad_tail = &pchan->bone->rad_tail;
distance = &pchan->bone->dist;
rad_head = (pchan->parent && (boneflag & BONE_CONNECTED)) ? &pchan->parent->bone->rad_tail :
&pchan->bone->rad_head;
}
if ((select_id == -1) && (boneflag & BONE_NO_DEFORM) == 0 &&
((boneflag & BONE_SELECTED) ||
(bone.is_editbone() && (boneflag & (BONE_ROOTSEL | BONE_TIPSEL)))))
{
drw_shgroup_bone_envelope_distance(ctx, bone.disp_mat(), rad_head, rad_tail, distance);
}
drw_shgroup_bone_envelope(ctx,
bone.disp_mat(),
col_solid,
col_hint,
col_wire,
rad_head,
rad_tail,
select_id | BONESEL_BONE);
draw_points(ctx, bone, boneflag, col_solid, select_id);
}
static void bone_draw_wire(const Armatures::DrawContext *ctx,
const UnifiedBonePtr bone,
const eBone_Flag boneflag,
const int select_id)
{
using namespace blender::math;
const float *col_wire = get_bone_wire_color(ctx, boneflag);
auto sel_id = (ctx->bone_buf) ? ctx->res->select_id(*ctx->ob_ref, select_id | BONESEL_BONE) :
draw::select::SelectMap::select_invalid_id();
/* NOTE: Cannot reinterpret as float4x4 because of alignment requirement of float4x4.
* This would require a deeper refactor. */
Span<Mat4> bbone_matrices;
if (bone.is_posebone()) {
bbone_matrices = {(Mat4 *)bone.as_posebone()->draw_data->bbone_matrix,
bone.as_posebone()->bone->segments};
}
else {
bbone_matrices = {(Mat4 *)bone.as_editbone()->disp_bbone_mat, bone.as_editbone()->segments};
}
for (const Mat4 &in_bone_mat : bbone_matrices) {
float4x4 bmat = float4x4(in_bone_mat.mat);
float3 head = transform_point(ctx->ob->object_to_world(), bmat.location());
float3 tail = transform_point(ctx->ob->object_to_world(), bmat.location() + bmat.y_axis());
ctx->bone_buf->wire_buf.append(head, tail, float4(col_wire), sel_id);
}
if (bone.is_editbone()) {
const float *col_solid = get_bone_solid_with_consts_color(ctx, bone, boneflag);
draw_points(ctx, bone, boneflag, col_solid, select_id);
}
}
static void bone_draw(const eArmature_Drawtype drawtype,
const bool use_custom_shape,
const Armatures::DrawContext *ctx,
const UnifiedBonePtr bone,
const eBone_Flag boneflag,
const int select_id)
{
if (use_custom_shape) {
bone_draw_custom_shape(ctx, bone, boneflag, select_id);
return;
}
switch (drawtype) {
case ARM_OCTA:
bone_draw_octa(ctx, bone, boneflag, select_id);
break;
case ARM_LINE:
bone_draw_line(ctx, bone, boneflag, select_id);
break;
case ARM_B_BONE:
bone_draw_b_bone(ctx, bone, boneflag, select_id);
break;
case ARM_ENVELOPE:
bone_draw_envelope(ctx, bone, boneflag, select_id);
break;
case ARM_WIRE:
bone_draw_wire(ctx, bone, boneflag, select_id);
break;
default:
BLI_assert_unreachable();
break;
}
}
/** \} */
/* -------------------------------------------------------------------- */
@@ -1401,6 +1662,34 @@ static void draw_bone_degrees_of_freedom(const Armatures::DrawContext *ctx,
/** \name Draw Relationships
* \{ */
/** Should the relationship line between this bone and its parent be drawn? */
static bool should_draw_relation_to_parent(const UnifiedBonePtr bone, const eBone_Flag boneflag)
{
const bool has_parent = bone.has_parent();
if (bone.is_editbone() && has_parent) {
/* Always draw for unconnected bones, regardless of selection,
* since riggers will want to know about the links between bones
*/
return (boneflag & BONE_CONNECTED) == 0;
}
if (bone.is_posebone() && has_parent) {
/* Only draw between unconnected bones. */
if (boneflag & BONE_CONNECTED) {
return false;
}
/* Only draw if bone or its parent is selected - reduces viewport
* complexity with complex rigs */
const bPoseChannel *pchan = bone.as_posebone();
return (boneflag & BONE_SELECTED) ||
(pchan->parent->bone && (pchan->parent->bone->flag & BONE_SELECTED));
}
return false;
}
static void pchan_draw_ik_lines(const Armatures::DrawContext *ctx,
const bPoseChannel *pchan,
const bool only_temp)
@@ -1495,14 +1784,13 @@ static void draw_bone_bone_relationship_line(const Armatures::DrawContext *ctx,
}
static void draw_bone_relations(const Armatures::DrawContext *ctx,
const ArmatureBoneDrawStrategy &draw_strategy,
const UnifiedBonePtr bone,
const eBone_Flag boneflag)
{
if (ctx->draw_mode == ARM_DRAW_MODE_EDIT) {
const EditBone *ebone = bone.as_editbone();
if (ebone->parent) {
if (ctx->do_relations && draw_strategy.should_draw_relation_to_parent(bone, boneflag)) {
if (ctx->do_relations && should_draw_relation_to_parent(bone, boneflag)) {
draw_bone_bone_relationship_line(
ctx, ebone->head, ebone->parent->head, ebone->parent->tail);
}
@@ -1511,7 +1799,7 @@ static void draw_bone_relations(const Armatures::DrawContext *ctx,
else {
const bPoseChannel *pchan = bone.as_posebone();
if (pchan->parent) {
if (ctx->do_relations && draw_strategy.should_draw_relation_to_parent(bone, boneflag)) {
if (ctx->do_relations && should_draw_relation_to_parent(bone, boneflag)) {
draw_bone_bone_relationship_line(
ctx, pchan->pose_head, pchan->parent->pose_head, pchan->parent->pose_tail);
}
@@ -1566,452 +1854,25 @@ static void draw_bone_name(const Armatures::DrawContext *ctx,
/** \} */
/* -------------------------------------------------------------------- */
/** \name Bone Drawing Strategies
*
* Bone drawing uses a strategy pattern for the different armature drawing modes.
* \{ */
/**
* Bone drawing strategy for unknown draw types.
* This doesn't do anything, except call the default matrix update function.
*/
class ArmatureBoneDrawStrategyEmpty : public ArmatureBoneDrawStrategy {
public:
void update_display_matrix(UnifiedBonePtr bone) const override
{
draw_bone_update_disp_matrix_default(bone);
}
void draw_context_setup(Armatures::DrawContext * /*ctx*/,
const bool /*is_filled*/,
const bool /*do_envelope_dist*/) const override
{
}
void draw_bone(const Armatures::DrawContext * /*ctx*/,
const UnifiedBonePtr /*bone*/,
const eBone_Flag /*boneflag*/,
const int /*select_id*/) const override
{
}
};
/** Bone drawing strategy for custom bone shapes. */
class ArmatureBoneDrawStrategyCustomShape : public ArmatureBoneDrawStrategy {
public:
void update_display_matrix(UnifiedBonePtr bone) const override
{
float bone_scale[3];
float(*bone_mat)[4];
float(*disp_mat)[4];
float(*disp_tail_mat)[4];
float rot_mat[3][3];
/* Custom bone shapes are only supported in pose mode for now. */
bPoseChannel *pchan = bone.as_posebone();
/* TODO: This should be moved to depsgraph or armature refresh
* and not be tight to the draw pass creation.
* This would refresh armature without invalidating the draw cache. */
mul_v3_v3fl(bone_scale, pchan->custom_scale_xyz, PCHAN_CUSTOM_BONE_LENGTH(pchan));
bone_mat = pchan->custom_tx ? pchan->custom_tx->pose_mat : pchan->pose_mat;
disp_mat = bone.disp_mat();
disp_tail_mat = pchan->disp_tail_mat;
eulO_to_mat3(rot_mat, pchan->custom_rotation_euler, ROT_MODE_XYZ);
copy_m4_m4(disp_mat, bone_mat);
translate_m4(disp_mat,
pchan->custom_translation[0],
pchan->custom_translation[1],
pchan->custom_translation[2]);
mul_m4_m4m3(disp_mat, disp_mat, rot_mat);
rescale_m4(disp_mat, bone_scale);
copy_m4_m4(disp_tail_mat, disp_mat);
translate_m4(disp_tail_mat, 0.0f, 1.0f, 0.0f);
}
void draw_context_setup(Armatures::DrawContext * /*ctx*/,
const bool /*is_filled*/,
const bool /*do_envelope_dist*/) const override
{
}
void draw_bone(const Armatures::DrawContext *ctx,
const UnifiedBonePtr bone,
const eBone_Flag boneflag,
const int select_id) const override
{
const float *col_solid = get_bone_solid_color(ctx, boneflag);
const float *col_wire = get_bone_wire_color(ctx, boneflag);
const float *col_hint = get_bone_hint_color(ctx, boneflag);
const float(*disp_mat)[4] = bone.disp_mat();
/* TODO(fclem): Code after this scope should be removed when we remove the legacy code. */
auto sel_id = ctx->res->select_id(*ctx->ob_ref, select_id | BONESEL_BONE);
/* Custom bone shapes are only supported in pose mode for now. */
const bPoseChannel *pchan = bone.as_posebone();
Object *custom_shape_ob = pchan->custom;
if (custom_shape_ob->type == OB_EMPTY) {
if (custom_shape_ob->empty_drawtype != OB_EMPTY_IMAGE) {
drw_shgroup_bone_custom_empty(
ctx, disp_mat, col_wire, pchan->custom_shape_wire_width, sel_id, pchan->custom);
}
}
else if (boneflag & (BONE_DRAWWIRE | BONE_DRAW_LOCKED_WEIGHT)) {
drw_shgroup_bone_custom_wire(
ctx, disp_mat, col_wire, pchan->custom_shape_wire_width, sel_id, pchan->custom);
}
else {
drw_shgroup_bone_custom_solid(ctx,
disp_mat,
col_solid,
col_hint,
col_wire,
pchan->custom_shape_wire_width,
sel_id,
pchan->custom);
}
}
};
/** Bone drawing strategy for ARM_OCTA. */
class ArmatureBoneDrawStrategyOcta : public ArmatureBoneDrawStrategy {
public:
void update_display_matrix(UnifiedBonePtr bone) const override
{
draw_bone_update_disp_matrix_default(bone);
}
void draw_context_setup(Armatures::DrawContext * /*ctx*/,
const bool /*is_filled*/,
const bool /*do_envelope_dist*/) const override
{
}
void draw_bone(const Armatures::DrawContext *ctx,
const UnifiedBonePtr bone,
const eBone_Flag boneflag,
const int select_id) const override
{
const float *col_solid = get_bone_solid_with_consts_color(ctx, bone, boneflag);
const float *col_wire = get_bone_wire_color(ctx, boneflag);
const float *col_hint = get_bone_hint_color(ctx, boneflag);
auto sel_id = ctx->res->select_id(*ctx->ob_ref, select_id | BONESEL_BONE);
float4x4 bone_mat = ctx->ob->object_to_world() * float4x4(bone.disp_mat());
if (ctx->is_filled) {
ctx->bone_buf->octahedral_fill_buf.append({bone_mat, col_solid, col_hint}, sel_id);
}
if (col_wire[3] > 0.0f) {
ctx->bone_buf->octahedral_outline_buf.append({bone_mat, col_wire}, sel_id);
}
draw_points(ctx, bone, boneflag, col_solid, select_id);
}
};
/** Bone drawing strategy for ARM_LINE. */
class ArmatureBoneDrawStrategyLine : public ArmatureBoneDrawStrategy {
public:
void update_display_matrix(UnifiedBonePtr bone) const override
{
draw_bone_update_disp_matrix_default(bone);
}
void draw_context_setup(Armatures::DrawContext * /*ctx*/,
const bool /*is_filled*/,
const bool /*do_envelope_dist*/) const override
{
}
void draw_bone(const Armatures::DrawContext *ctx,
const UnifiedBonePtr bone,
const eBone_Flag boneflag,
const int select_id) const override
{
const float *col_bone = get_bone_solid_with_consts_color(ctx, bone, boneflag);
const float *col_wire = get_bone_wire_color(ctx, boneflag);
const float no_display[4] = {0.0f, 0.0f, 0.0f, 0.0f};
const float *col_head = no_display;
const float *col_tail = col_bone;
if (ctx->const_color != nullptr) {
col_wire = no_display; /* actually shrink the display. */
col_bone = col_head = col_tail = ctx->const_color;
}
else {
const UniformData &theme = ctx->res->theme;
if (bone.is_editbone() && bone.flag() & BONE_TIPSEL) {
col_tail = &theme.colors.vert_select.x;
}
/* Draw root point if we are not connected to our parent. */
if (!(bone.has_parent() && (boneflag & BONE_CONNECTED))) {
if (bone.is_editbone()) {
col_head = (bone.flag() & BONE_ROOTSEL) ? &theme.colors.vert_select.x : col_bone;
}
else {
col_head = col_bone;
}
}
}
if (select_id == -1) {
/* Not in bone selection mode (can still be object select mode), draw everything at once. */
drw_shgroup_bone_stick(
ctx, bone.disp_mat(), col_wire, col_bone, col_head, col_tail, select_id);
}
else {
/* In selection mode, draw bone, root and tip separately. */
drw_shgroup_bone_stick(ctx,
bone.disp_mat(),
col_wire,
col_bone,
no_display,
no_display,
select_id | BONESEL_BONE);
if (col_head[3] > 0.0f) {
drw_shgroup_bone_stick(ctx,
bone.disp_mat(),
col_wire,
no_display,
col_head,
no_display,
select_id | BONESEL_ROOT);
}
drw_shgroup_bone_stick(ctx,
bone.disp_mat(),
col_wire,
no_display,
no_display,
col_tail,
select_id | BONESEL_TIP);
}
}
};
/** Bone drawing strategy for ARM_B_BONE. */
class ArmatureBoneDrawStrategyBBone : public ArmatureBoneDrawStrategy {
public:
void update_display_matrix(UnifiedBonePtr bone) const override
{
draw_bone_update_disp_matrix_bbone(bone);
}
void draw_context_setup(Armatures::DrawContext * /*ctx*/,
const bool /*is_filled*/,
const bool /*do_envelope_dist*/) const override
{
}
void draw_bone(const Armatures::DrawContext *ctx,
const UnifiedBonePtr bone,
const eBone_Flag boneflag,
const int select_id) const override
{
const float *col_solid = get_bone_solid_with_consts_color(ctx, bone, boneflag);
const float *col_wire = get_bone_wire_color(ctx, boneflag);
const float *col_hint = get_bone_hint_color(ctx, boneflag);
/* NOTE: Cannot reinterpret as float4x4 because of alignment requirement of float4x4.
* This would require a deeper refactor. */
Span<Mat4> bbone_matrices;
if (bone.is_posebone()) {
bbone_matrices = {(Mat4 *)bone.as_posebone()->draw_data->bbone_matrix,
bone.as_posebone()->bone->segments};
}
else {
bbone_matrices = {(Mat4 *)bone.as_editbone()->disp_bbone_mat, bone.as_editbone()->segments};
}
auto sel_id = ctx->res->select_id(*ctx->ob_ref, select_id | BONESEL_BONE);
for (const Mat4 &in_bone_mat : bbone_matrices) {
float4x4 bone_mat = ctx->ob->object_to_world() * float4x4(in_bone_mat.mat);
if (ctx->is_filled) {
ctx->bone_buf->bbones_fill_buf.append({bone_mat, col_solid, col_hint}, sel_id);
}
if (col_wire[3] > 0.0f) {
ctx->bone_buf->bbones_outline_buf.append({bone_mat, col_wire}, sel_id);
}
}
if (ctx->draw_mode == ARM_DRAW_MODE_EDIT) {
draw_points(ctx, bone, boneflag, col_solid, select_id);
}
}
};
/** Bone drawing strategy for ARM_ENVELOPE. */
class ArmatureBoneDrawStrategyEnvelope : public ArmatureBoneDrawStrategy {
public:
void update_display_matrix(UnifiedBonePtr bone) const override
{
draw_bone_update_disp_matrix_default(bone);
}
void draw_context_setup(Armatures::DrawContext * /*ctx*/,
const bool /*is_filled*/,
const bool /*do_envelope_dist*/) const override
{
}
void draw_bone(const Armatures::DrawContext *ctx,
const UnifiedBonePtr bone,
const eBone_Flag boneflag,
const int select_id) const override
{
const float *col_solid = get_bone_solid_with_consts_color(ctx, bone, boneflag);
const float *col_wire = get_bone_wire_color(ctx, boneflag);
const float *col_hint = get_bone_hint_color(ctx, boneflag);
const float *rad_head, *rad_tail, *distance;
if (bone.is_editbone()) {
const EditBone *eBone = bone.as_editbone();
rad_tail = &eBone->rad_tail;
distance = &eBone->dist;
rad_head = (eBone->parent && (boneflag & BONE_CONNECTED)) ? &eBone->parent->rad_tail :
&eBone->rad_head;
}
else {
const bPoseChannel *pchan = bone.as_posebone();
rad_tail = &pchan->bone->rad_tail;
distance = &pchan->bone->dist;
rad_head = (pchan->parent && (boneflag & BONE_CONNECTED)) ? &pchan->parent->bone->rad_tail :
&pchan->bone->rad_head;
}
if ((select_id == -1) && (boneflag & BONE_NO_DEFORM) == 0 &&
((boneflag & BONE_SELECTED) ||
(bone.is_editbone() && (boneflag & (BONE_ROOTSEL | BONE_TIPSEL)))))
{
drw_shgroup_bone_envelope_distance(ctx, bone.disp_mat(), rad_head, rad_tail, distance);
}
drw_shgroup_bone_envelope(ctx,
bone.disp_mat(),
col_solid,
col_hint,
col_wire,
rad_head,
rad_tail,
select_id | BONESEL_BONE);
draw_points(ctx, bone, boneflag, col_solid, select_id);
}
};
/** Bone drawing strategy for ARM_WIRE. */
class ArmatureBoneDrawStrategyWire : public ArmatureBoneDrawStrategy {
public:
void update_display_matrix(UnifiedBonePtr bone) const override
{
draw_bone_update_disp_matrix_bbone(bone);
}
void draw_context_setup(Armatures::DrawContext *ctx,
const bool /*is_filled*/,
const bool /*do_envelope_dist*/) const override
{
ctx->const_wire = 1.5f;
}
void draw_bone(const Armatures::DrawContext *ctx,
const UnifiedBonePtr bone,
const eBone_Flag boneflag,
const int select_id) const override
{
using namespace blender::math;
const float *col_wire = get_bone_wire_color(ctx, boneflag);
auto sel_id = (ctx->bone_buf) ? ctx->res->select_id(*ctx->ob_ref, select_id | BONESEL_BONE) :
draw::select::SelectMap::select_invalid_id();
/* NOTE: Cannot reinterpret as float4x4 because of alignment requirement of float4x4.
* This would require a deeper refactor. */
Span<Mat4> bbone_matrices;
if (bone.is_posebone()) {
bbone_matrices = {(Mat4 *)bone.as_posebone()->draw_data->bbone_matrix,
bone.as_posebone()->bone->segments};
}
else {
bbone_matrices = {(Mat4 *)bone.as_editbone()->disp_bbone_mat, bone.as_editbone()->segments};
}
for (const Mat4 &in_bone_mat : bbone_matrices) {
float4x4 bmat = float4x4(in_bone_mat.mat);
float3 head = transform_point(ctx->ob->object_to_world(), bmat.location());
float3 tail = transform_point(ctx->ob->object_to_world(), bmat.location() + bmat.y_axis());
ctx->bone_buf->wire_buf.append(head, tail, float4(col_wire), sel_id);
}
if (bone.is_editbone()) {
const float *col_solid = get_bone_solid_with_consts_color(ctx, bone, boneflag);
draw_points(ctx, bone, boneflag, col_solid, select_id);
}
}
};
namespace {
/**
* Armature drawing strategies.
*
* Declared statically here because they cost almost no memory (no fields in any
* of the structs, so just the virtual function table), and this makes it very
* simple to just pass references to them around.
*
* See the functions below.
*/
ArmatureBoneDrawStrategyOcta strat_octa;
ArmatureBoneDrawStrategyLine strat_line;
ArmatureBoneDrawStrategyBBone strat_b_bone;
ArmatureBoneDrawStrategyEnvelope strat_envelope;
ArmatureBoneDrawStrategyWire strat_wire;
ArmatureBoneDrawStrategyEmpty strat_empty;
}; // namespace
/**
* Return the armature bone drawing strategy for the given draw type.
*
* Note that this does not consider custom bone shapes, as those can be set per bone.
* For those occasions just instance a `ArmatureBoneDrawStrategyCustomShape` and use that.
*/
static ArmatureBoneDrawStrategy &strategy_for_armature_drawtype(const eArmature_Drawtype drawtype)
{
switch (drawtype) {
case ARM_OCTA:
return strat_octa;
case ARM_LINE:
return strat_line;
case ARM_B_BONE:
return strat_b_bone;
case ARM_ENVELOPE:
return strat_envelope;
case ARM_WIRE:
return strat_wire;
}
BLI_assert_unreachable();
return strat_empty;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Main Draw Loops
* \{ */
static void bone_draw_update_display_matrix(const eArmature_Drawtype drawtype,
const bool use_custom_shape,
UnifiedBonePtr bone)
{
if (use_custom_shape) {
draw_bone_update_disp_matrix_custom_shape(bone);
}
else if (ELEM(drawtype, ARM_B_BONE, ARM_WIRE)) {
draw_bone_update_disp_matrix_bbone(bone);
}
else {
draw_bone_update_disp_matrix_default(bone);
}
}
void Armatures::draw_armature_edit(Armatures::DrawContext *ctx)
{
Object *ob = ctx->ob;
@@ -2029,9 +1890,7 @@ void Armatures::draw_armature_edit(Armatures::DrawContext *ctx)
edbo_compute_bbone_child(&arm);
/* Determine drawing strategy. */
const ArmatureBoneDrawStrategy &draw_strat = strategy_for_armature_drawtype(
eArmature_Drawtype(arm.drawtype));
const eArmature_Drawtype arm_drawtype = eArmature_Drawtype(arm.drawtype);
for (eBone = static_cast<EditBone *>(arm.edbo->first),
/* Note: Selection Next handles the object id merging later. */
@@ -2064,11 +1923,14 @@ void Armatures::draw_armature_edit(Armatures::DrawContext *ctx)
}
if (!is_select) {
draw_bone_relations(ctx, draw_strat, bone, boneflag);
draw_bone_relations(ctx, bone, boneflag);
}
draw_strat.update_display_matrix(bone);
draw_strat.draw_bone(ctx, bone, boneflag, select_id);
const eArmature_Drawtype drawtype = eBone->drawtype == ARM_BONE_DEFAULT ?
arm_drawtype :
eArmature_Drawtype(eBone->drawtype);
bone_draw_update_display_matrix(drawtype, false, bone);
bone_draw(drawtype, false, ctx, bone, boneflag, select_id);
if (!is_select) {
if (show_text && (arm.flag & ARM_DRAWNAMES)) {
@@ -2156,9 +2018,7 @@ void Armatures::draw_armature_pose(Armatures::DrawContext *ctx)
}
}
const ArmatureBoneDrawStrategy &draw_strat_normal = strategy_for_armature_drawtype(
eArmature_Drawtype(arm.drawtype));
const ArmatureBoneDrawStrategyCustomShape draw_strat_custom;
const eArmature_Drawtype arm_drawtype = eArmature_Drawtype(arm.drawtype);
for (bPoseChannel *pchan = static_cast<bPoseChannel *>(ob->pose->chanbase.first); pchan;
pchan = pchan->next, index += 0x10000)
@@ -2196,14 +2056,15 @@ void Armatures::draw_armature_pose(Armatures::DrawContext *ctx)
}
const bool use_custom_shape = (pchan->custom) && !(arm.flag & ARM_NO_CUSTOM);
const ArmatureBoneDrawStrategy &draw_strat = use_custom_shape ? draw_strat_custom :
draw_strat_normal;
if (!is_pose_select) {
draw_bone_relations(ctx, draw_strat, bone_ptr, boneflag);
draw_bone_relations(ctx, bone_ptr, boneflag);
}
draw_strat.update_display_matrix(bone_ptr);
draw_strat.draw_bone(ctx, bone_ptr, boneflag, select_id);
const eArmature_Drawtype drawtype = bone->drawtype == ARM_BONE_DEFAULT ?
arm_drawtype :
eArmature_Drawtype(bone->drawtype);
bone_draw_update_display_matrix(drawtype, use_custom_shape, bone_ptr);
bone_draw(drawtype, use_custom_shape, ctx, bone_ptr, boneflag, select_id);
/* Below this point nothing is used for selection queries. */
if (is_pose_select) {

View File

@@ -70,6 +70,7 @@ EditBone *ED_armature_ebone_add(bArmature *arm, const char *name)
BLI_addtail(arm->edbo, bone);
bone->flag |= BONE_TIPSEL;
bone->drawtype = ARM_BONE_DEFAULT;
bone->weight = 1.0f;
bone->dist = 0.25f;
bone->xwidth = 0.1f;
@@ -1668,6 +1669,7 @@ static wmOperatorStatus armature_extrude_exec(bContext *C, wmOperator *op)
}
newbone->color = ebone->color;
newbone->drawtype = ebone->drawtype;
newbone->weight = ebone->weight;
newbone->dist = ebone->dist;

View File

@@ -459,6 +459,7 @@ static EditBone *make_boneList_recursive(ListBase *edbo,
STRNCPY(eBone->name, curBone->name);
eBone->flag = curBone->flag;
eBone->inherit_scale_mode = curBone->inherit_scale_mode;
eBone->drawtype = curBone->drawtype;
/* fix selection flags */
if (eBone->flag & BONE_SELECTED) {
@@ -690,6 +691,7 @@ void ED_armature_from_edit(Main *bmain, bArmature *arm)
newBone->flag = eBone->flag;
newBone->inherit_scale_mode = eBone->inherit_scale_mode;
newBone->drawtype = eBone->drawtype;
if (eBone == arm->act_edbone) {
/* Don't change active selection, this messes up separate which uses

View File

@@ -10,10 +10,6 @@
/* clang-format off */
/* -------------------------------------------------------------------- */
/** \name bArmature Struct
* \{ */
#define _DNA_DEFAULT_bArmature \
{ \
.deformflag = ARM_DEF_VGROUP | ARM_DEF_ENVELOPE, \
@@ -22,6 +18,9 @@
.drawtype = ARM_OCTA, \
}
/** \} */
#define _DNA_DEFAULT_Bone \
{ \
.drawtype = ARM_BONE_DEFAULT, \
}
/* clang-format on */

View File

@@ -77,8 +77,8 @@ typedef struct Bone {
float bone_mat[3][3];
int flag;
char _pad1[4];
int8_t drawtype; /* eArmature_Drawtype */
char _pad1[3];
BoneColor color; /* MUST be named the same as in bPoseChannel and EditBone structs. */
char inherit_scale_mode;
@@ -188,7 +188,7 @@ typedef struct bArmature {
char _pad0[3];
int flag;
int drawtype;
int drawtype; /* eArmature_Drawtype */
short deformflag;
short pathflag;
@@ -374,6 +374,7 @@ typedef enum eArmature_Flag {
/* armature->drawtype */
typedef enum eArmature_Drawtype {
ARM_BONE_DEFAULT = -1, /* Use draw type from Armature (only used on Bones). */
ARM_OCTA = 0,
ARM_LINE = 1,
ARM_B_BONE = 2,

View File

@@ -1363,6 +1363,32 @@ static void rna_def_bone_common(StructRNA *srna, int editbone)
{0, nullptr, 0, nullptr, nullptr},
};
static const EnumPropertyItem prop_drawtype_items[] = {
{ARM_BONE_DEFAULT,
"USE_ARMATURE_SETTING",
0,
"Use Armature Setting",
"Use display mode from armature (default)"},
{ARM_OCTA, "OCTAHEDRAL", 0, "Octahedral", "Display bones as octahedral shape"},
{ARM_LINE, "STICK", 0, "Stick", "Display bones as simple 2D lines with dots"},
{ARM_B_BONE,
"BBONE",
0,
"B-Bone",
"Display bones as boxes, showing subdivision and B-Splines"},
{ARM_ENVELOPE,
"ENVELOPE",
0,
"Envelope",
"Display bones as extruded spheres, showing deformation influence volume"},
{ARM_WIRE,
"WIRE",
0,
"Wire",
"Display bones as thin wires, showing subdivision and B-Splines"},
{0, nullptr, 0, nullptr, nullptr},
};
PropertyRNA *prop;
/* strings */
@@ -1387,6 +1413,13 @@ static void rna_def_bone_common(StructRNA *srna, int editbone)
RNA_def_property_pointer_funcs(prop, "rna_EditBone_color_get", nullptr, nullptr, nullptr);
}
prop = RNA_def_property(srna, "display_type", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, nullptr, "drawtype");
RNA_def_property_enum_items(prop, prop_drawtype_items);
RNA_def_property_ui_text(prop, "Display Type", "");
RNA_def_property_update(prop, 0, "rna_Armature_redraw_data");
RNA_def_property_flag(prop, PROP_LIB_EXCEPTION);
/* flags */
prop = RNA_def_property(srna, "use_connect", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "flag", BONE_CONNECTED);
@@ -2144,6 +2177,7 @@ static void rna_def_armature(BlenderRNA *brna)
"Display bones as thin wires, showing subdivision and B-Splines"},
{0, nullptr, 0, nullptr, nullptr},
};
static const EnumPropertyItem prop_pose_position_items[] = {
{0, "POSE", 0, "Pose Position", "Show armature in posed state"},
{ARM_RESTPOS,