diff --git a/src/user/user_model.cc b/src/user/user_model.cc index 60e066703d..e0dc7e77f0 100644 --- a/src/user/user_model.cc +++ b/src/user/user_model.cc @@ -304,6 +304,8 @@ void mjCModel::SaveDofOffsets(bool computesize) { } } + + template void mjCModel::CopyPlugin(std::vector& dest, const std::vector& source, @@ -336,6 +338,21 @@ void mjCModel::CopyPlugin(std::vector& dest, } } + + +// return true if the plugin is already in the list of active plugins +static bool IsPluginActive( + const mjpPlugin* plugin, + const std::vector>& active_plugins) { + return std::find_if( + active_plugins.begin(), active_plugins.end(), + [&plugin](const std::pair& element) { + return element.first == plugin; + }) != active_plugins.end(); +} + + + mjCModel& mjCModel::operator+=(const mjCModel& other) { // TODO: use compiler settings stored in specs_ during compilation std::string msg = "cannot attach mjSpecs with incompatible compiler/"; @@ -387,8 +404,10 @@ mjCModel& mjCModel::operator+=(const mjCModel& other) { CopyPlugin(plugins_, other.plugins_, meshes_); CopyPlugin(plugins_, other.plugins_, actuators_); CopyPlugin(plugins_, other.plugins_, sensors_); - for (const auto& active_plugin : other.active_plugins_) { - active_plugins_.emplace_back(active_plugin); + for (const auto& [plugin, slot] : other.active_plugins_) { + if (!IsPluginActive(plugin, active_plugins_)) { + active_plugins_.emplace_back(std::make_pair(plugin, slot)); + } } // restore to the original state diff --git a/test/user/user_api_test.cc b/test/user/user_api_test.cc index 8404dc47c2..22bb76e64b 100644 --- a/test/user/user_api_test.cc +++ b/test/user/user_api_test.cc @@ -293,6 +293,39 @@ TEST_F(PluginTest, AttachPlugin) { mj_deleteSpec(spec_2); } +TEST_F(PluginTest, ReplicatePlugin) { + static constexpr char xml[] = R"( + + + + + + + + + + + + + + + + + + + + )"; + + std::array err; + mjSpec* spec = mj_parseXMLString(xml, 0, err.data(), err.size()); + ASSERT_THAT(spec, NotNull()) << err.data(); + mjModel* model = mj_compile(spec, nullptr); + EXPECT_THAT(model, NotNull()); + EXPECT_THAT(model->nplugin, 1); + mj_deleteSpec(spec); + mj_deleteModel(model); +} + TEST_F(MujocoTest, RecompileFails) { mjSpec* spec = mj_makeSpec(); mjsBody* body = mjs_addBody(mjs_findBody(spec, "world"), 0);