11#include <gtest/gtest.h>
19namespace fs = std::filesystem;
21static std::string gen3_path() {
return ex::find_asset(
"kinova_gen3/gen3.xml"); }
23static mj_kdl::SceneSpec arm_scene(
const std::string &mjcf,
const std::string &prefix =
"")
42 for (
int k = 0; k < 3; ++k) cube.
size[k] = 0.02;
43 for (
int k = 0; k < 4; ++k) cube.
rgba[k] = 1.0f;
44 for (
int k = 0; k < 3; ++k) cube.
friction[k] = 0.5;
59 const std::string mjcf = gen3_path();
60 if (!fs::exists(mjcf)) GTEST_SKIP() << mjcf <<
" not found";
61 sc_ = arm_scene(mjcf);
66 int joint(
int i)
const {
return mj_name2id(
env_.model, mjOBJ_JOINT,
s.joint_names[i].c_str()); }
71 for (
int a = 0; a <
env_.model->nu; ++a) {
72 if (
env_.model->actuator_trnid[2 * a] ==
joint(i) &&
env_.model->actuator_group[a] == 1)
81 EXPECT_EQ(s.n_joints, 7) <<
"expected 7 KDL joints, got " << s.n_joints;
82 EXPECT_EQ(env_.robots.size(), 1u) <<
"init_robot_from_mjcf() registers the robot";
88 const double t0 = env_.data->time;
90 EXPECT_NEAR(env_.data->time - t0, 100 * sc_.timestep, 1e-9);
95 ASSERT_GT(env_.model->nkey, 0) <<
"the bundled Gen3 has a home keyframe";
97 for (
unsigned i = 0; i < 7; ++i) q_displaced(i) += 0.3;
102 for (
int i = 0; i < s.n_joints; ++i) {
103 const int adr = env_.model->jnt_qposadr[joint(i)];
104 EXPECT_DOUBLE_EQ(qpos(i), env_.model->key_qpos[adr]) <<
"joint " << i;
106 EXPECT_EQ(env_.data->time, 0.0);
111 ASSERT_GE(env_.model->nkey, 2) <<
"the bundled Gen3 has home and retract keyframes";
117 for (
int i = 0; i < s.n_joints; ++i) {
118 const int adr = env_.model->jnt_qposadr[joint(i)];
119 EXPECT_DOUBLE_EQ(qpos(i), env_.model->key_qpos[env_.model->nq + adr]) <<
"joint " << i;
120 EXPECT_DOUBLE_EQ(s.jnt_pos_msr[i], qpos(i));
127 for (
int i = 0; i < s.n_joints; ++i) {
128 const int adr = env_.model->jnt_qposadr[joint(i)];
129 EXPECT_DOUBLE_EQ(qpos(i), env_.model->qpos0[adr]) <<
"joint " << i;
135 for (
int i = 0; i < s.n_joints; ++i) {
136 s.jnt_pos_cmd[i] = 99.0;
137 s.jnt_trq_cmd[i] = 42.0;
142 for (
int i = 0; i < s.n_joints; ++i) {
143 EXPECT_DOUBLE_EQ(s.jnt_pos_cmd[i], qpos(i));
144 EXPECT_DOUBLE_EQ(s.jnt_trq_cmd[i], 0.0);
150 for (
int i = 0; i < s.n_joints; ++i) {
151 s.jnt_pos_msr[i] = 7.0;
152 s.jnt_vel_msr[i] = 7.0;
153 s.jnt_vel_cmd[i] = 3.0;
154 s.jnt_trq_cmd[i] = 3.0;
155 s.jnt_saturated[i] = 1;
160 for (
int i = 0; i < s.n_joints; ++i) {
161 EXPECT_DOUBLE_EQ(s.jnt_pos_msr[i], qpos(i));
162 EXPECT_DOUBLE_EQ(s.jnt_vel_msr[i], env_.data->qvel[dof(i)]);
163 EXPECT_DOUBLE_EQ(s.jnt_pos_cmd[i], qpos(i));
164 EXPECT_EQ(s.jnt_vel_cmd[i], 0.0);
165 EXPECT_EQ(s.jnt_trq_cmd[i], 0.0);
166 EXPECT_EQ(s.jnt_saturated[i], 0);
176 EXPECT_EQ(call_count, 1) <<
"on_reset was not called by reset()";
181 ASSERT_FALSE(env_.on_reset);
182 s.jnt_pos_cmd[0] = 99.0;
185 EXPECT_DOUBLE_EQ(s.jnt_pos_cmd[0], qpos(0));
193 EXPECT_EQ(ctx->env, &env_);
194 EXPECT_EQ(ctx->model, env_.model);
195 EXPECT_EQ(ctx->data, env_.data);
198 for (
int i = 0; i < s.n_joints; ++i) {
199 s.jnt_pos_cmd[i] = 99.0;
200 s.jnt_trq_cmd[i] = 42.0;
201 env_.data->qfrc_applied[dof(i)] = 12.0;
206 EXPECT_EQ(call_count, 1);
209 for (
int i = 0; i < s.n_joints; ++i) {
210 EXPECT_DOUBLE_EQ(s.jnt_pos_msr[i], qpos(i));
211 EXPECT_DOUBLE_EQ(s.jnt_pos_cmd[i], qpos(i));
212 EXPECT_DOUBLE_EQ(s.jnt_trq_cmd[i], 0.0);
213 EXPECT_DOUBLE_EQ(env_.data->qfrc_applied[dof(i)], 0.0);
224 EXPECT_TRUE(env_.model->opt.disableactuator & (1 << 1)) <<
"POSITION group off";
225 EXPECT_FALSE(env_.model->opt.disableactuator & (1 << 2)) <<
"TORQUE group on";
231 s.jnt_trq_cmd[0] = 1.5;
232 ctx->data->qpos[ctx->model->jnt_qposadr[joint(1)]] += 0.2;
235 EXPECT_DOUBLE_EQ(s.jnt_trq_cmd[0], 1.5) <<
"primed in the hook, not overwritten";
236 EXPECT_DOUBLE_EQ(s.jnt_pos_msr[1], qpos(1)) <<
"a move in the hook is read back";
241 const int a = servo(0);
244 EXPECT_TRUE(env_.robots.empty());
246 env_.data->ctrl[a] = 0.123;
247 s.jnt_pos_cmd.assign(7, 1.0);
249 EXPECT_EQ(env_.data->ctrl[a], 0.123) <<
"update() no longer commands the robot";
256 EXPECT_EQ(env_.robots.size(), 2u);
257 EXPECT_EQ(given.
n_joints, s.n_joints);
264 for (
int i = 0; i < 7; ++i) {
266 EXPECT_DOUBLE_EQ(s.jnt_pos_msr[i], q(i)) <<
"both robots read the same joints";
269 std::vector<std::string> short_list(s.joint_names.begin(), s.joint_names.end() - 1);
275TEST(TwoEnvs, StepIndependently)
277 const std::string mjcf = gen3_path();
278 if (!fs::exists(mjcf)) GTEST_SKIP() << mjcf <<
" not found";
298 EXPECT_GT((fa.
p - fb.
p).Norm(), 0.05) <<
"each Env computes its own frames";
301 EXPECT_GT(a.
data->time, 0.0);
302 EXPECT_EQ(b.
data->time, 0.0) <<
"stepping one Env leaves the other alone";
309TEST(EnvSpec, OwnsItsStringsAcrossARebuild)
311 const std::string mjcf = gen3_path();
312 if (!fs::exists(mjcf)) GTEST_SKIP() << mjcf <<
" not found";
323 ASSERT_TRUE(added) << added.
error;
326 for (
int k = 0; k < 10; ++k) ASSERT_TRUE(
mj_kdl::step(&env));
328 EXPECT_GT(env.
data->time, 0.0);
333TEST(EnvAdopt, RunsOnTheCallersPairAndNeverFreesIt)
335 const std::string mjcf = gen3_path();
336 if (!fs::exists(mjcf)) GTEST_SKIP() << mjcf <<
" not found";
338 std::vector<std::pair<mjModel *, mjData *>> owned;
341 env.
adopt = [&](mjModel *m, mjData *d) {
342 mjModel *om = mj_copyModel(
nullptr, m);
343 mjData *od = mj_makeData(om);
344 mj_copyData(od, m, d);
346 owned.emplace_back(om, od);
352 ASSERT_EQ(owned.size(), 1u);
353 EXPECT_EQ(env.
model, owned[0].first);
355 for (
int k = 0; k < 10; ++k) ASSERT_TRUE(
mj_kdl::step(&env));
358 ASSERT_EQ(owned.size(), 2u);
359 EXPECT_EQ(env.
model, owned[1].first);
360 EXPECT_EQ(robot.
model, owned[1].first);
361 EXPECT_GT(owned[1].first->nbody, owned[0].first->nbody);
362 for (
int k = 0; k < 10; ++k) ASSERT_TRUE(
mj_kdl::step(&env));
364 const fs::path xml = fs::temp_directory_path() /
"mj_kdl_adopted.xml";
370 for (
auto &[m, d] : owned) {
377TEST(SceneFloor, PlacedAtFloorZ)
385 mjModel *model =
nullptr;
386 mjData *data =
nullptr;
392 for (
int i = 0; i < model->ngeom; ++i) {
393 if (model->geom_type[i] == mjGEOM_PLANE) floor_id = i;
395 ASSERT_GE(floor_id, 0);
396 EXPECT_DOUBLE_EQ(model->geom_pos[3 * floor_id + 2], -0.72);
401TEST(Recorder, OutputPathReachesFfmpegVerbatim)
410 const fs::path dir = fs::temp_directory_path() /
"mj_kdl_rec_test";
412 fs::create_directories(dir);
413 const fs::path out = dir /
"a \"quoted\" $(touch injected) name.mp4";
414 const fs::path marker = dir /
"injected";
418 GTEST_SKIP() <<
"no EGL or ffmpeg";
422 EXPECT_TRUE(fs::exists(out)) << out;
423 EXPECT_GT(fs::file_size(out), 0u);
424 EXPECT_FALSE(fs::exists(marker)) <<
"the path went through a shell";
425 EXPECT_FALSE(fs::exists(fs::current_path() /
"injected"));
429TEST(Offscreen, RendersTheSceneIntoABuffer)
438 constexpr int kW = 64, kH = 48;
441 std::vector<std::uint8_t> rgb(kW * kH * 3, 0);
446 for (std::uint8_t c : rgb) lit += c != 0;
447 EXPECT_GT(lit, rgb.size() / 2) <<
"the floor and sky fill the frame";
455 EXPECT_NE(s.
error.find(
"no_such_body"), std::string::npos) << s.
error;
458 cube.
name =
"unweighed_cube";
462 EXPECT_NE(s.
error.find(
"unweighed_cube"), std::string::npos) << s.
error;
465 EXPECT_NE(s.
error.find(
"no_such_object"), std::string::npos) << s.
error;
468TEST(SceneSpecRequired, AnUnsetFieldFailsTheBuild)
471 mjModel *model =
nullptr;
472 mjData *data =
nullptr;
489 EXPECT_TRUE(builds(complete));
493 EXPECT_FALSE(builds(no_mass));
496 no_friction.
objects[0].friction[1] = NAN;
497 EXPECT_FALSE(builds(no_friction));
501 cam.
pos[0] = cam.
pos[1] = 0.0;
504 no_fovy.
cameras.push_back(cam);
505 EXPECT_FALSE(builds(no_fovy));
509 EXPECT_FALSE(builds(no_timestep));
512int main(
int argc,
char *argv[])
514 testing::InitGoogleTest(&argc, argv);
515 return RUN_ALL_TESTS();
unsigned int getNrOfSegments() const
ResetInfo reset(Env *env, const ResetOptions *options=nullptr)
Status init_env(Env *env, const SceneSpec *spec)
Status init_offscreen(VideoRecorder *vr, mjModel *model, int width, int height)
Status init_video_recorder(VideoRecorder *vr, mjModel *model, const char *out_path, int width=1280, int height=720, int fps=60)
bool record_frame(VideoRecorder *vr, Env *env)
bool render_rgb(VideoRecorder *vr, Env *env, std::uint8_t *out)
Status init_robot_from_mjcf(Robot *r, Env *env, const char *base_body, const char *tip_body, const char *prefix="", const ToolFrameSpec *tool=nullptr)
void set_joint_pos(Robot *r, const KDL::JntArray &q)
Status init_robot_from_chain(Robot *r, Env *env, const KDL::Chain &chain, const std::vector< std::string > &joint_names, const char *prefix="", const ToolFrameSpec *tool=nullptr)
bool get_body_frame(Env *env, const char *body_name, KDL::Frame *out)
Status build_scene(mjModel **out_model, mjData **out_data, const SceneSpec *spec)
Status scene_remove_object(Env *env, const std::string &name)
void destroy_scene(mjModel *model, mjData *data)
Status scene_add_object(Env *env, const SceneObject &obj)
Status save_model_xml(const mjModel *model, const char *path)
IMETHOD bool Equal(const FrameAcc &r1, const FrameAcc &r2, double eps=epsilon)
std::string find_asset(const fs::path &relative)
KDL::JntArray home_q(unsigned n)
std::function< std::pair< mjModel *, mjData * >(mjModel *, mjData *)> adopt
std::vector< double > jnt_pos_msr
std::vector< RobotSpec > robots
std::vector< SceneObject > objects
std::vector< CameraSpec > cameras
int main(int argc, char *argv[])
TEST(TwoEnvs, StepIndependently)
TEST_F(InitTest, BasicDOF)