Anim: Indicate Parent Inverse Matrix State in UI

Show the Parent Inverse matrix in the Object properties, Transform
panel. The matrix is shown decomposed as location/rotation/scale.

Pull Request: https://projects.blender.org/blender/blender/pulls/113364
This commit is contained in:
Andrej730
2025-10-08 11:13:24 +02:00
committed by Sybren A. Stüvel
parent 3432c0b238
commit dec032e12e
8 changed files with 327 additions and 2 deletions

View File

@@ -108,6 +108,27 @@ class OBJECT_PT_delta_transform(ObjectButtonsPanel, Panel):
col.prop(ob, "delta_scale", text="Scale")
class OBJECT_PT_parent_inverse_transform(ObjectButtonsPanel, Panel):
bl_label = "Parent Inverse Transform"
bl_parent_id = "OBJECT_PT_transform"
bl_options = {'DEFAULT_CLOSED'}
@classmethod
def poll(cls, context):
ob = context.object
return ob and ob.parent
def draw(self, context):
layout = self.layout
layout.use_property_split = True
ob = context.object
layout.template_matrix(ob, "matrix_parent_inverse")
props = layout.operator("object.parent_clear", text="Clear Parent Inverse Transform")
props.type = 'CLEAR_INVERSE'
class OBJECT_PT_relations(ObjectButtonsPanel, Panel):
bl_label = "Relations"
bl_options = {'DEFAULT_CLOSED'}
@@ -605,6 +626,7 @@ classes = (
OBJECT_PT_context_object,
OBJECT_PT_transform,
OBJECT_PT_delta_transform,
OBJECT_PT_parent_inverse_transform,
OBJECT_PT_relations,
COLLECTION_MT_context_menu,
OBJECT_PT_collections,

View File

@@ -297,6 +297,7 @@ bool is_orthogonal_m4(const float m[4][4]);
bool is_orthonormal_m3(const float m[3][3]);
bool is_orthonormal_m4(const float m[4][4]);
bool is_identity_m4(const float m[4][4]);
bool is_uniform_scaled_m3(const float m[3][3]);
bool is_uniform_scaled_m4(const float m[4][4]);

View File

@@ -1672,6 +1672,19 @@ bool is_orthonormal_m4(const float m[4][4])
return false;
}
bool is_identity_m4(const float m[4][4])
{
for (int row = 0; row < 4; row++) {
for (int col = 0; col < 4; col++) {
if (m[row][col] != (row == col ? 1.0f : 0.0f)) {
return false;
}
}
}
return true;
}
bool is_uniform_scaled_m3(const float m[3][3])
{
const float eps = 1e-7f;

View File

@@ -2307,6 +2307,7 @@ void uiTemplateIDPreview(uiLayout *layout,
int cols,
int filter = UI_TEMPLATE_ID_FILTER_ALL,
bool hide_buttons = false);
void uiTemplateMatrix(uiLayout *layout, PointerRNA *ptr, blender::StringRefNull propname);
/**
* Version of #uiTemplateID using tabs.
*/

View File

@@ -74,6 +74,7 @@ set(SRC
templates/interface_template_layers.cc
templates/interface_template_light_linking.cc
templates/interface_template_list.cc
templates/interface_template_matrix.cc
templates/interface_template_modifiers.cc
templates/interface_template_node_inputs.cc
templates/interface_template_node_tree_interface.cc

View File

@@ -0,0 +1,229 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edinterface
*/
#include <fmt/format.h>
#include "BKE_unit.hh"
#include "BLI_math_matrix.h"
#include "BLI_math_rotation.h"
#include "BLT_translation.hh"
#include "RNA_access.hh"
#include "RNA_enum_types.hh"
#include "interface_intern.hh"
using blender::StringRef;
using blender::StringRefNull;
/* Format translation/rotation value as a string based on Blender unit settings. */
static std::string format_unit_value(float value, PropertySubType subtype, uiLayout *layout)
{
const UnitSettings *unit = layout->block()->unit;
const int unit_type = RNA_SUBTYPE_UNIT(subtype);
/* Change negative zero to regular zero, without altering anything else. */
value += +0.0f;
double value_scaled = BKE_unit_value_scale(*unit, unit_type, value);
char new_str[UI_MAX_DRAW_STR];
BKE_unit_value_as_string(new_str,
sizeof(new_str),
value_scaled,
RNA_TRANSLATION_PREC_DEFAULT,
RNA_SUBTYPE_UNIT_VALUE(unit_type),
*unit,
true);
return std::string(new_str);
}
/* Format unitless value as a string. */
static std::string format_coefficient(float value)
{
/* Change negative zero to regular zero, without altering anything else. */
value += +0.0f;
/* Same precision that we use in `Object.scale`. */
const int RNA_SCALE_PREC_DEFAULT = 3;
return fmt::format("{:.{}f}", value, RNA_SCALE_PREC_DEFAULT);
}
/* Static variable to store rotation mode button state at runtime.
* Defaults to XYZ Euler. */
static int rotation_mode_index = ROT_MODE_EUL;
static void rotation_mode_menu_callback(bContext *, uiLayout *layout, void *)
{
for (size_t i = 0; i < RNA_enum_items_count(rna_enum_object_rotation_mode_items); i++) {
const EnumPropertyItem &mode_info = rna_enum_object_rotation_mode_items[i];
const int yco = -1.5f * UI_UNIT_Y;
const int width = 9 * UI_UNIT_X;
uiBut *but = uiDefButI(layout->block(),
ButType::Row,
0,
IFACE_(mode_info.name),
0,
yco,
width / 2,
UI_UNIT_Y,
&rotation_mode_index,
i,
i,
TIP_(mode_info.description));
UI_but_flag_disable(but, UI_BUT_UNDO);
if (i == rotation_mode_index) {
UI_but_flag_enable(but, UI_SELECT_DRAW);
}
}
}
static void draw_matrix_template(uiLayout &layout, PointerRNA &ptr, PropertyRNA &prop)
{
/* Matrix template UI is mirroring Object's Transform UI for better UX. */
uiLayout *row, *col;
uiLayout *layout_ = &layout.box();
float m4[4][4];
RNA_property_float_get_array(&ptr, &prop, &m4[0][0]);
/* Show a warning as a matrix with a shear cannot be represented fully
* by a decomposition.
* Use the 3x3 matrix, as shear in the 4x4 homogeneous matrix
* is expected due to the translation component. */
float m3[3][3];
copy_m3_m4(m3, m4);
if (!is_orthogonal_m3(m3)) {
layout_->label(RPT_("Matrix has a shear"), ICON_ERROR);
}
float loc[3], quat[4], size[3];
mat4_decompose(loc, quat, size, m4);
/* Translation. */
col = &layout_->column(true);
col->use_property_split_set(true);
row = &col->row(true);
uiItemL_respect_property_split(row, IFACE_("Location X"), ICON_NONE);
row->label(format_unit_value(loc[0], PROP_TRANSLATION, layout_), ICON_NONE);
row = &col->row(true);
uiItemL_respect_property_split(row, IFACE_("Y"), ICON_NONE);
row->label(format_unit_value(loc[1], PROP_TRANSLATION, layout_), ICON_NONE);
row = &col->row(true);
uiItemL_respect_property_split(row, IFACE_("Z"), ICON_NONE);
row->label(format_unit_value(loc[2], PROP_TRANSLATION, layout_), ICON_NONE);
/* Rotation. */
float eul[3], axis[3];
float angle;
const EnumPropertyItem &mode_info = rna_enum_object_rotation_mode_items[rotation_mode_index];
col = &layout_->column(true);
col->use_property_split_set(true);
if (mode_info.value == ROT_MODE_QUAT) {
row = &col->row(true);
uiItemL_respect_property_split(row, IFACE_("Rotation W"), ICON_NONE);
row->label(format_coefficient(quat[0]), ICON_NONE);
row = &col->row(true);
uiItemL_respect_property_split(row, IFACE_("X"), ICON_NONE);
row->label(format_coefficient(quat[1]), ICON_NONE);
row = &col->row(true);
uiItemL_respect_property_split(row, IFACE_("Y"), ICON_NONE);
row->label(format_coefficient(quat[2]), ICON_NONE);
row = &col->row(true);
uiItemL_respect_property_split(row, IFACE_("Z"), ICON_NONE);
row->label(format_coefficient(quat[3]), ICON_NONE);
}
else if (mode_info.value == ROT_MODE_AXISANGLE) {
quat_to_axis_angle(axis, &angle, quat);
row = &col->row(true);
uiItemL_respect_property_split(row, IFACE_("Rotation W"), ICON_NONE);
row->label(format_unit_value(angle, PROP_ANGLE, layout_), ICON_NONE);
row = &col->row(true);
uiItemL_respect_property_split(row, IFACE_("X"), ICON_NONE);
row->label(format_coefficient(axis[0]), ICON_NONE);
row = &col->row(true);
uiItemL_respect_property_split(row, IFACE_("Y"), ICON_NONE);
row->label(format_coefficient(axis[1]), ICON_NONE);
row = &col->row(true);
uiItemL_respect_property_split(row, IFACE_("Z"), ICON_NONE);
row->label(format_coefficient(axis[2]), ICON_NONE);
}
else { /* Euler modes. */
quat_to_eulO(eul, mode_info.value, quat);
row = &col->row(true);
uiItemL_respect_property_split(row, IFACE_("Rotation X"), ICON_NONE);
row->label(format_unit_value(eul[0], PROP_ANGLE, layout_), ICON_NONE);
row = &col->row(true);
uiItemL_respect_property_split(row, IFACE_("Y"), ICON_NONE);
row->label(format_unit_value(eul[1], PROP_ANGLE, layout_), ICON_NONE);
row = &col->row(true);
uiItemL_respect_property_split(row, IFACE_("Z"), ICON_NONE);
row->label(format_unit_value(eul[2], PROP_ANGLE, layout_), ICON_NONE);
}
/* Mirror RNA enum property dropdown UI - with menu triangle an dropdown items. */
row = &layout_->row(true);
uiItemL_respect_property_split(row, IFACE_("Mode"), ICON_NONE);
uiBlock *block = row->block();
uiBut *but = uiDefMenuBut(block,
rotation_mode_menu_callback,
nullptr,
mode_info.name,
0,
0,
UI_UNIT_X * 10,
UI_UNIT_Y,
TIP_("Rotation mode.\n\nOnly affects the way "
"rotation is displayed, rotation itself is unaffected."));
UI_but_type_set_menu_from_pulldown(but);
/* Scale. */
col = &layout_->column(true);
col->use_property_split_set(true);
row = &col->row(true);
uiItemL_respect_property_split(row, IFACE_("Scale X"), ICON_NONE);
row->label(format_coefficient(size[0]), ICON_NONE);
row = &col->row(true);
uiItemL_respect_property_split(row, IFACE_("Y"), ICON_NONE);
row->label(format_coefficient(size[1]), ICON_NONE);
row = &col->row(true);
uiItemL_respect_property_split(row, IFACE_("Z"), ICON_NONE);
row->label(format_coefficient(size[2]), ICON_NONE);
}
void uiTemplateMatrix(uiLayout *layout, PointerRNA *ptr, const StringRefNull propname)
{
PropertyRNA *prop = RNA_struct_find_property(ptr, propname.c_str());
if (!prop || RNA_property_type(prop) != PROP_FLOAT ||
RNA_property_subtype(prop) != PROP_MATRIX || RNA_property_array_length(ptr, prop) != 16)
{
RNA_warning("4x4 Matrix property not found: %s.%s",
RNA_struct_identifier(ptr->type),
propname.c_str());
return;
}
draw_matrix_template(*layout, *ptr, *prop);
}

View File

@@ -957,6 +957,34 @@ static wmOperatorStatus childof_clear_inverse_invoke(bContext *C,
return OPERATOR_CANCELLED;
}
static bool childof_clear_inverse_poll(bContext *C)
{
if (!edit_constraint_liboverride_allowed_poll(C)) {
return false;
}
PointerRNA ptr = CTX_data_pointer_get_type(C, "constraint", &RNA_Constraint);
bConstraint *con = static_cast<bConstraint *>(ptr.data);
/* Allow workflows with unset context's constraint.
* The constraint can also be provided as an operator's property. */
if (con == nullptr) {
return true;
}
if (con->type != CONSTRAINT_TYPE_CHILDOF) {
return false;
}
bChildOfConstraint *data = static_cast<bChildOfConstraint *>(con->data);
if (is_identity_m4(data->invmat)) {
CTX_wm_operator_poll_msg_set(C, "No inverse correction is set, so there is nothing to clear");
return false;
}
return true;
}
void CONSTRAINT_OT_childof_clear_inverse(wmOperatorType *ot)
{
/* identifiers */
@@ -967,7 +995,7 @@ void CONSTRAINT_OT_childof_clear_inverse(wmOperatorType *ot)
/* callbacks */
ot->invoke = childof_clear_inverse_invoke;
ot->exec = childof_clear_inverse_exec;
ot->poll = edit_constraint_liboverride_allowed_poll;
ot->poll = childof_clear_inverse_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
@@ -1216,6 +1244,27 @@ static wmOperatorStatus objectsolver_clear_inverse_invoke(bContext *C,
return OPERATOR_CANCELLED;
}
static bool objectsolver_clear_inverse_poll(bContext *C)
{
if (!edit_constraint_poll(C)) {
return false;
}
PointerRNA ptr = CTX_data_pointer_get_type(C, "constraint", &RNA_Constraint);
bConstraint *con = static_cast<bConstraint *>(ptr.data);
if (con == nullptr) {
return true;
}
bObjectSolverConstraint *data = (bObjectSolverConstraint *)con->data;
if (is_identity_m4(data->invmat)) {
CTX_wm_operator_poll_msg_set(C, "No inverse correction is set, so there is nothing to clear");
return false;
}
return true;
}
void CONSTRAINT_OT_objectsolver_clear_inverse(wmOperatorType *ot)
{
/* identifiers */
@@ -1226,7 +1275,7 @@ void CONSTRAINT_OT_objectsolver_clear_inverse(wmOperatorType *ot)
/* callbacks */
ot->invoke = objectsolver_clear_inverse_invoke;
ot->exec = objectsolver_clear_inverse_exec;
ot->poll = edit_constraint_poll;
ot->poll = objectsolver_clear_inverse_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;

View File

@@ -1697,6 +1697,15 @@ void RNA_api_ui_layout(StructRNA *srna)
"Optionally limit the items which can be selected");
RNA_def_boolean(func, "hide_buttons", false, "", "Show only list, no buttons");
func = RNA_def_function(srna, "template_matrix", "uiTemplateMatrix");
RNA_def_function_ui_description(
func,
"Insert a readonly Matrix UI. "
"The UI displays the matrix components - translation, rotation and scale. "
"The **property** argument must be the identifier of an existing 4x4 float vector "
"property of subtype 'MATRIX'.");
api_ui_item_rna_common(func);
func = RNA_def_function(srna, "template_any_ID", "rna_uiTemplateAnyID");
parm = RNA_def_pointer(func, "data", "AnyType", "", "Data from which to take property");
RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED | PARM_RNAPTR);