Files
test2/source/blender/editors/interface/interface_ops_test.cc
Campbell Barton e8f9e2d1d1 Cleanup: use UTF8 string functions in editors & related logic
Use UTF8 aware functions unless raw bytes are expected.
2025-07-27 16:41:19 +10:00

291 lines
10 KiB
C++

/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include <cstring>
#include "BKE_action.hh"
#include "BKE_anim_data.hh"
#include "BKE_fcurve.hh"
#include "BKE_idtype.hh"
#include "BKE_lib_id.hh"
#include "BKE_main.hh"
#include "BKE_object.hh"
#include "DNA_anim_types.h"
#include "RNA_access.hh"
#include "RNA_prototypes.hh"
#include "BLI_listbase.h"
#include "BLI_string_utf8.h"
#include "ED_keyframing.hh"
#include "ANIM_action.hh"
#include "ANIM_animdata.hh"
#include "ANIM_fcurve.hh"
#include "interface_intern.hh"
#include "CLG_log.h"
#include "testing/testing.h"
namespace blender::interface::tests {
class CopyDriversToSelected : public testing::Test {
public:
Main *bmain;
Object *cube;
Object *suzanne;
PointerRNA cube_ptr;
PropertyRNA *cube_quaternion_prop;
PropertyRNA *cube_rotation_mode_prop;
PointerRNA suzanne_ptr;
PropertyRNA *suzanne_quaternion_prop;
PropertyRNA *suzanne_rotation_mode_prop;
static void SetUpTestSuite()
{
/* BKE_id_free() hits a code path that uses CLOG, which crashes if not initialized properly. */
CLG_init();
/* To make id_can_have_animdata() and friends work, the `id_types` array needs to be set up. */
BKE_idtype_init();
}
static void TearDownTestSuite()
{
CLG_exit();
}
void SetUp() override
{
bmain = BKE_main_new();
cube = BKE_object_add_only_object(bmain, OB_EMPTY, "OBCube");
suzanne = BKE_object_add_only_object(bmain, OB_EMPTY, "OBSuzanne");
cube_ptr = RNA_pointer_create_discrete(&cube->id, &RNA_Object, &cube->id);
cube_quaternion_prop = RNA_struct_find_property(&cube_ptr, "rotation_quaternion");
cube_rotation_mode_prop = RNA_struct_find_property(&cube_ptr, "rotation_mode");
suzanne_ptr = RNA_pointer_create_discrete(&suzanne->id, &RNA_Object, &suzanne->id);
suzanne_quaternion_prop = RNA_struct_find_property(&suzanne_ptr, "rotation_quaternion");
suzanne_rotation_mode_prop = RNA_struct_find_property(&suzanne_ptr, "rotation_mode");
AnimData *adt_cube = BKE_animdata_ensure_id(&cube->id);
AnimData *adt_suzanne = BKE_animdata_ensure_id(&suzanne->id);
ReportList tmp_report_list;
/* Set up cube drivers. */
ANIM_add_driver(&tmp_report_list, &cube->id, "rotation_quaternion", 0, 0, DRIVER_TYPE_PYTHON);
ANIM_add_driver(&tmp_report_list, &cube->id, "rotation_quaternion", 1, 0, DRIVER_TYPE_PYTHON);
FCurve *cube_quat_0_driver = static_cast<FCurve *>(BLI_findlink(&adt_cube->drivers, 0));
FCurve *cube_quat_1_driver = static_cast<FCurve *>(BLI_findlink(&adt_cube->drivers, 1));
STRNCPY_UTF8(cube_quat_0_driver->driver->expression, "0.0");
STRNCPY_UTF8(cube_quat_1_driver->driver->expression, "1.0");
/* Set up suzanne drivers. */
ANIM_add_driver(
&tmp_report_list, &suzanne->id, "rotation_quaternion", 0, 0, DRIVER_TYPE_PYTHON);
ANIM_add_driver(
&tmp_report_list, &suzanne->id, "rotation_quaternion", 2, 0, DRIVER_TYPE_PYTHON);
ANIM_add_driver(
&tmp_report_list, &suzanne->id, "rotation_quaternion", 3, 0, DRIVER_TYPE_PYTHON);
ANIM_add_driver(&tmp_report_list, &suzanne->id, "rotation_mode", 0, 0, DRIVER_TYPE_PYTHON);
FCurve *suzanne_quat_0_driver = static_cast<FCurve *>(BLI_findlink(&adt_suzanne->drivers, 0));
FCurve *suzanne_quat_2_driver = static_cast<FCurve *>(BLI_findlink(&adt_suzanne->drivers, 1));
FCurve *suzanne_quat_3_driver = static_cast<FCurve *>(BLI_findlink(&adt_suzanne->drivers, 2));
FCurve *suzanne_rotation_mode_driver = static_cast<FCurve *>(
BLI_findlink(&adt_suzanne->drivers, 3));
STRNCPY_UTF8(suzanne_quat_0_driver->driver->expression, "0.5");
STRNCPY_UTF8(suzanne_quat_2_driver->driver->expression, "2.5");
STRNCPY_UTF8(suzanne_quat_3_driver->driver->expression, "3.5");
STRNCPY_UTF8(suzanne_rotation_mode_driver->driver->expression, "4");
/* Add animation to cube's fourth quaternion element. */
PointerRNA cube_ptr = RNA_pointer_create_discrete(&cube->id, &RNA_Object, &cube->id);
bAction *act = animrig::id_action_ensure(bmain, &cube->id);
FCurve *fcu = animrig::action_fcurve_ensure_ex(
bmain, act, "Object Transforms", &cube_ptr, {"rotation_quaternion", 3});
animrig::KeyframeSettings keyframe_settings = {BEZT_KEYTYPE_KEYFRAME, HD_AUTO, BEZT_IPO_BEZ};
insert_vert_fcurve(fcu, {1.0, 1.0}, keyframe_settings, INSERTKEY_NOFLAGS);
}
void TearDown() override
{
BKE_main_free(bmain);
}
};
TEST_F(CopyDriversToSelected, get_property_drivers)
{
/* Cube quaternion: get all drivers. */
{
bool is_array_prop;
Vector<FCurve *> drivers = internal::get_property_drivers(
&cube_ptr, cube_quaternion_prop, true, -1, &is_array_prop);
EXPECT_EQ(is_array_prop, true);
EXPECT_EQ(drivers.size(), 4);
EXPECT_NE(drivers[0], nullptr);
EXPECT_NE(drivers[1], nullptr);
EXPECT_EQ(drivers[2], nullptr);
EXPECT_EQ(drivers[3], nullptr);
EXPECT_STREQ(drivers[0]->driver->expression, "0.0");
EXPECT_STREQ(drivers[1]->driver->expression, "1.0");
}
/* Cube quaternion: get first element driver. */
{
bool is_array_prop;
Vector<FCurve *> drivers = internal::get_property_drivers(
&cube_ptr, cube_quaternion_prop, false, 0, &is_array_prop);
EXPECT_EQ(is_array_prop, true);
EXPECT_EQ(drivers.size(), 4);
EXPECT_NE(drivers[0], nullptr);
EXPECT_EQ(drivers[1], nullptr);
EXPECT_EQ(drivers[2], nullptr);
EXPECT_EQ(drivers[3], nullptr);
EXPECT_STREQ(drivers[0]->driver->expression, "0.0");
}
/* Cube quaternion: try to get fourth element driver. Since there is none, we
* should get back an empty vector, indicating that no drivers were found. */
{
bool is_array_prop;
Vector<FCurve *> drivers = internal::get_property_drivers(
&cube_ptr, cube_quaternion_prop, false, 3, &is_array_prop);
EXPECT_EQ(drivers.size(), 0);
}
/* Cube rotation mode: get driver. Since there is none, we should get back an
* empty vector, indicating that no drivers were found. */
{
bool is_array_prop;
Vector<FCurve *> drivers = internal::get_property_drivers(
&cube_ptr, cube_rotation_mode_prop, false, 0, &is_array_prop);
EXPECT_EQ(drivers.size(), 0);
}
/* Suzanne quaternion: get all drivers. */
{
bool is_array_prop;
Vector<FCurve *> drivers = internal::get_property_drivers(
&suzanne_ptr, suzanne_quaternion_prop, true, -1, &is_array_prop);
EXPECT_EQ(is_array_prop, true);
EXPECT_EQ(drivers.size(), 4);
EXPECT_NE(drivers[0], nullptr);
EXPECT_EQ(drivers[1], nullptr);
EXPECT_NE(drivers[2], nullptr);
EXPECT_NE(drivers[3], nullptr);
EXPECT_STREQ(drivers[0]->driver->expression, "0.5");
EXPECT_STREQ(drivers[2]->driver->expression, "2.5");
EXPECT_STREQ(drivers[3]->driver->expression, "3.5");
}
/* Suzanne quaternion: get first element driver. */
{
bool is_array_prop;
Vector<FCurve *> drivers = internal::get_property_drivers(
&suzanne_ptr, suzanne_quaternion_prop, false, 0, &is_array_prop);
EXPECT_EQ(is_array_prop, true);
EXPECT_EQ(drivers.size(), 4);
EXPECT_NE(drivers[0], nullptr);
EXPECT_EQ(drivers[1], nullptr);
EXPECT_EQ(drivers[2], nullptr);
EXPECT_EQ(drivers[3], nullptr);
EXPECT_STREQ(drivers[0]->driver->expression, "0.5");
}
/* Suzanne quaternion: get second element driver. Since there is none, we
* should get back an empty vector, indicating that no drivers were found. */
{
bool is_array_prop;
Vector<FCurve *> drivers = internal::get_property_drivers(
&suzanne_ptr, suzanne_quaternion_prop, false, 1, &is_array_prop);
EXPECT_EQ(drivers.size(), 0);
}
/* Suzanne rotation mode: get driver. */
{
bool is_array_prop;
Vector<FCurve *> drivers = internal::get_property_drivers(
&suzanne_ptr, suzanne_rotation_mode_prop, false, 0, &is_array_prop);
EXPECT_EQ(is_array_prop, false);
EXPECT_EQ(drivers.size(), 1);
EXPECT_STREQ(drivers[0]->driver->expression, "4");
}
}
TEST_F(CopyDriversToSelected, paste_property_drivers)
{
/* Copy all quaternion channel drivers from Suzanne to Cube. The result on
* Cube should be the following:
*
* - [0]: overwritten by the driver from Suzanne.
* - [1]: Cube's driver remains, since there was no driver here on Suzanne.
* - [2]: Suzanne's driver is pasted. There was no driver on Cube before.
* - [3]: remains without a driver. Cube has animation on this channel,
* preventing driver pasting.
*/
{
bool is_array_prop;
Vector<FCurve *> suzanne_location_drivers = internal::get_property_drivers(
&suzanne_ptr, suzanne_quaternion_prop, true, -1, &is_array_prop);
internal::paste_property_drivers(
suzanne_location_drivers.as_span(), is_array_prop, &cube_ptr, cube_quaternion_prop);
Vector<FCurve *> cube_location_drivers = internal::get_property_drivers(
&cube_ptr, cube_quaternion_prop, true, -1, &is_array_prop);
EXPECT_NE(cube_location_drivers[0], nullptr);
EXPECT_NE(cube_location_drivers[1], nullptr);
EXPECT_NE(cube_location_drivers[2], nullptr);
EXPECT_EQ(cube_location_drivers[3], nullptr);
EXPECT_STREQ(cube_location_drivers[0]->driver->expression, "0.5");
EXPECT_STREQ(cube_location_drivers[1]->driver->expression, "1.0");
EXPECT_STREQ(cube_location_drivers[2]->driver->expression, "2.5");
}
/* Copy the rotation_mode driver from Suzanne to Cube. */
{
bool is_array_prop;
Vector<FCurve *> suzanne_rotation_mode_driver = internal::get_property_drivers(
&suzanne_ptr, suzanne_rotation_mode_prop, false, 0, &is_array_prop);
internal::paste_property_drivers(
suzanne_rotation_mode_driver.as_span(), is_array_prop, &cube_ptr, cube_rotation_mode_prop);
Vector<FCurve *> cube_rotation_mode_drivers = internal::get_property_drivers(
&cube_ptr, cube_rotation_mode_prop, false, 0, &is_array_prop);
EXPECT_NE(cube_rotation_mode_drivers[0], nullptr);
EXPECT_STREQ(cube_rotation_mode_drivers[0]->driver->expression, "4");
}
}
} // namespace blender::interface::tests