Add a ModelPlugin that can be used to load and update models.
PiperOrigin-RevId: 861077626 Change-Id: I3ef4989cfbfff3632c5915c00ebda1e7a48716e8
This commit is contained in:
committed by
Copybara-Service
parent
e933081ff1
commit
bdb553ff8a
@@ -36,10 +36,26 @@ void ForEachGuiPlugin(const std::function<void(GuiPlugin*)>& fn) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RegisterModelPlugin(const ModelPlugin* plugin) {
|
||||||
|
if (plugin->name == nullptr || plugin->name[0] == '\0') {
|
||||||
|
mju_error("Plugin name must not be empty or null.");
|
||||||
|
}
|
||||||
|
GlobalTable<ModelPlugin>::GetSingleton().AppendIfUnique(*plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ForEachModelPlugin(const std::function<void(ModelPlugin*)>& fn) {
|
||||||
|
auto& table = GlobalTable<ModelPlugin>::GetSingleton();
|
||||||
|
for (int i = 0; i < table.count(); ++i) {
|
||||||
|
const ModelPlugin* plugin = table.GetAtSlot(i);
|
||||||
|
fn(const_cast<ModelPlugin*>(plugin));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace mujoco::platform
|
} // namespace mujoco::platform
|
||||||
|
|
||||||
using mujoco::GlobalTable;
|
using mujoco::GlobalTable;
|
||||||
using GuiPlugin = mujoco::platform::GuiPlugin;
|
using GuiPlugin = mujoco::platform::GuiPlugin;
|
||||||
|
using ModelPlugin = mujoco::platform::ModelPlugin;
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
const char* GlobalTable<GuiPlugin>::HumanReadableTypeName() {
|
const char* GlobalTable<GuiPlugin>::HumanReadableTypeName() {
|
||||||
@@ -61,3 +77,24 @@ bool GlobalTable<GuiPlugin>::CopyObject(GuiPlugin& dst, const GuiPlugin& src, Er
|
|||||||
dst = src;
|
dst = src;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
const char* GlobalTable<ModelPlugin>::HumanReadableTypeName() {
|
||||||
|
return "model plugin";
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
std::string_view GlobalTable<ModelPlugin>::ObjectKey(const ModelPlugin& plugin) {
|
||||||
|
return std::string_view(plugin.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
bool GlobalTable<ModelPlugin>::ObjectEqual(const ModelPlugin& p1, const ModelPlugin& p2) {
|
||||||
|
return CaseInsensitiveEqual(p1.name, p2.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
bool GlobalTable<ModelPlugin>::CopyObject(ModelPlugin& dst, const ModelPlugin& src, ErrorMessage& err) {
|
||||||
|
dst = src;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
#define MUJOCO_SRC_EXPERIMENTAL_PLATFORM_PLUGIN_H_
|
#define MUJOCO_SRC_EXPERIMENTAL_PLATFORM_PLUGIN_H_
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <mujoco/mujoco.h>
|
||||||
|
|
||||||
namespace mujoco::platform {
|
namespace mujoco::platform {
|
||||||
|
|
||||||
@@ -46,6 +47,34 @@ struct GuiPlugin final {
|
|||||||
void* data = nullptr;
|
void* data = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ModelPlugin final {
|
||||||
|
using GetModelToLoadFn = const char* (*)(ModelPlugin * self, int* size,
|
||||||
|
char* content_type,
|
||||||
|
int content_type_size,
|
||||||
|
char* model_name,
|
||||||
|
int model_name_size);
|
||||||
|
using PostModelLoadedFn = void (*)(ModelPlugin* self, const char* model_path);
|
||||||
|
using DoUpdateFn = bool (*)(ModelPlugin* self, mjModel* model, mjData* data);
|
||||||
|
|
||||||
|
// The name of the plugin; must be unique.
|
||||||
|
const char* name = "";
|
||||||
|
|
||||||
|
// Callback for when the plugin wants to load a new model. This function will
|
||||||
|
// return a buffer containing the model data as well as the content type of
|
||||||
|
// the buffer. Returns nullptr if no model needs to be loaded.s
|
||||||
|
GetModelToLoadFn get_model_to_load = nullptr;
|
||||||
|
|
||||||
|
// Callback when a new model is loaded.
|
||||||
|
PostModelLoadedFn post_model_loaded = nullptr;
|
||||||
|
|
||||||
|
// Callback when the physics simulation is updated. Returns true if the
|
||||||
|
// simulation should be stepped.
|
||||||
|
DoUpdateFn do_update = nullptr;
|
||||||
|
|
||||||
|
// Optional data pointer.
|
||||||
|
void* data = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
// Registers a plugin with a global registry. The plugin must have a
|
// Registers a plugin with a global registry. The plugin must have a
|
||||||
// case-insensitive unique name.
|
// case-insensitive unique name.
|
||||||
void RegisterGuiPlugin(const GuiPlugin* plugin);
|
void RegisterGuiPlugin(const GuiPlugin* plugin);
|
||||||
@@ -53,6 +82,13 @@ void RegisterGuiPlugin(const GuiPlugin* plugin);
|
|||||||
// Executes the given function for each registered plugin.
|
// Executes the given function for each registered plugin.
|
||||||
void ForEachGuiPlugin(const std::function<void(GuiPlugin*)>& fn);
|
void ForEachGuiPlugin(const std::function<void(GuiPlugin*)>& fn);
|
||||||
|
|
||||||
|
// Registers a plugin with a global registry. The plugin must have a
|
||||||
|
// case-insensitive unique name.
|
||||||
|
void RegisterModelPlugin(const ModelPlugin* plugin);
|
||||||
|
|
||||||
|
// Executes the given function for each registered plugin.
|
||||||
|
void ForEachModelPlugin(const std::function<void(ModelPlugin*)>& fn);
|
||||||
|
|
||||||
} // namespace mujoco::platform
|
} // namespace mujoco::platform
|
||||||
|
|
||||||
#endif // MUJOCO_SRC_EXPERIMENTAL_PLATFORM_PLUGIN_H_
|
#endif // MUJOCO_SRC_EXPERIMENTAL_PLATFORM_PLUGIN_H_
|
||||||
|
|||||||
@@ -156,7 +156,6 @@ void App::ClearModel() {
|
|||||||
|
|
||||||
void App::RequestModelLoad(std::string model_file) {
|
void App::RequestModelLoad(std::string model_file) {
|
||||||
pending_load_ = std::move(model_file);
|
pending_load_ = std::move(model_file);
|
||||||
model_kind_ = kModelFromFile;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void App::RequestModelReload() {
|
void App::RequestModelReload() {
|
||||||
@@ -277,6 +276,12 @@ void App::InitModel(mjModel* model, mjSpec* spec, mjVFS* vfs,
|
|||||||
SetSpeedIndex(i);
|
SetSpeedIndex(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
platform::ForEachModelPlugin([&](platform::ModelPlugin* plugin) {
|
||||||
|
if (plugin->post_model_loaded) {
|
||||||
|
plugin->post_model_loaded(plugin, model_path_.c_str());
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void App::UpdateFilePaths(const std::string& resolved_path) {
|
void App::UpdateFilePaths(const std::string& resolved_path) {
|
||||||
@@ -309,9 +314,16 @@ void App::UpdatePhysics() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool stepped = false;
|
bool stepped = false;
|
||||||
{
|
platform::ForEachModelPlugin([&](platform::ModelPlugin* plugin) {
|
||||||
|
if (plugin->do_update) {
|
||||||
|
if (plugin->do_update(plugin, model_, data_)) {
|
||||||
|
stepped = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!stepped) {
|
||||||
if (!step_control_.IsPaused()) {
|
if (!step_control_.IsPaused()) {
|
||||||
mju_zero(data_->xfrc_applied, 6 * model_->nbody);
|
mju_zero(data_->xfrc_applied, 6 * model_->nbody);
|
||||||
mjv_applyPerturbPose(model_, data_, &perturb_, 0);
|
mjv_applyPerturbPose(model_, data_, &perturb_, 0);
|
||||||
@@ -370,32 +382,11 @@ void App::LoadHistory(int offset) {
|
|||||||
bool App::Update() {
|
bool App::Update() {
|
||||||
const platform::Window::Status status = window_->NewFrame();
|
const platform::Window::Status status = window_->NewFrame();
|
||||||
|
|
||||||
|
HandleWindowEvents();
|
||||||
HandleMouseEvents();
|
HandleMouseEvents();
|
||||||
HandleKeyboardEvents();
|
HandleKeyboardEvents();
|
||||||
|
|
||||||
// Check to see if a model was dropped on the window.
|
ProcessPendingLoads();
|
||||||
const std::string drop_file = window_->GetDropFile();
|
|
||||||
if (!drop_file.empty()) {
|
|
||||||
RequestModelLoad(drop_file);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check to see if we need to load a new model.
|
|
||||||
if (pending_load_.has_value()) {
|
|
||||||
std::string load_data = std::move(pending_load_.value());
|
|
||||||
pending_load_.reset();
|
|
||||||
if (model_kind_ == kModelFromBuffer) {
|
|
||||||
// TODO(matijak): need to pass the content type and not assume mjb.
|
|
||||||
LoadModelFromBuffer(
|
|
||||||
std::span<const std::byte>(
|
|
||||||
reinterpret_cast<const std::byte*>(load_data.data()),
|
|
||||||
load_data.size()),
|
|
||||||
"application/mjb", model_name_);
|
|
||||||
} else if (model_kind_ == kModelFromFile) {
|
|
||||||
LoadModelFromFile(load_data);
|
|
||||||
} else {
|
|
||||||
InitEmptyModel();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only update the simulation if a popup window is not open. Note that the
|
// Only update the simulation if a popup window is not open. Note that the
|
||||||
// simulation itself will only update if it is not paused.
|
// simulation itself will only update if it is not paused.
|
||||||
@@ -425,6 +416,43 @@ void App::Render() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void App::ProcessPendingLoads() {
|
||||||
|
// Check to see if we need to load a new model.
|
||||||
|
if (pending_load_.has_value()) {
|
||||||
|
std::string load_data = std::move(pending_load_.value());
|
||||||
|
pending_load_.reset();
|
||||||
|
|
||||||
|
if (load_data.empty()) {
|
||||||
|
InitEmptyModel();
|
||||||
|
} else {
|
||||||
|
LoadModelFromFile(load_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check plugins to see if we need to load a new model.
|
||||||
|
platform::ForEachModelPlugin([&](platform::ModelPlugin* plugin) {
|
||||||
|
if (plugin->get_model_to_load) {
|
||||||
|
char model_name[1000] = "";
|
||||||
|
char content_type[1000] = "";
|
||||||
|
int size = 0;
|
||||||
|
const char* buf = plugin->get_model_to_load(
|
||||||
|
plugin, &size, content_type, sizeof(content_type), model_name,
|
||||||
|
sizeof(model_name));
|
||||||
|
if (buf && size) {
|
||||||
|
const std::byte* bytes = reinterpret_cast<const std::byte*>(buf);
|
||||||
|
LoadModelFromBuffer({bytes, bytes + size}, content_type, model_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void App::HandleWindowEvents() {
|
||||||
|
const std::string drop_file = window_->GetDropFile();
|
||||||
|
if (!drop_file.empty()) {
|
||||||
|
RequestModelLoad(drop_file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void App::HandleMouseEvents() {
|
void App::HandleMouseEvents() {
|
||||||
auto& io = ImGui::GetIO();
|
auto& io = ImGui::GetIO();
|
||||||
if (io.WantCaptureMouse) {
|
if (io.WantCaptureMouse) {
|
||||||
|
|||||||
@@ -168,8 +168,12 @@ class App {
|
|||||||
|
|
||||||
void SetSpeedIndex(int idx);
|
void SetSpeedIndex(int idx);
|
||||||
|
|
||||||
|
void HandleWindowEvents();
|
||||||
void HandleMouseEvents();
|
void HandleMouseEvents();
|
||||||
void HandleKeyboardEvents();
|
void HandleKeyboardEvents();
|
||||||
|
|
||||||
|
void ProcessPendingLoads();
|
||||||
|
|
||||||
void MoveCamera(platform::CameraMotion motion, mjtNum reldx, mjtNum reldy);
|
void MoveCamera(platform::CameraMotion motion, mjtNum reldx, mjtNum reldy);
|
||||||
|
|
||||||
void SetupTheme(platform::GuiTheme theme);
|
void SetupTheme(platform::GuiTheme theme);
|
||||||
|
|||||||
Reference in New Issue
Block a user