Skip to content

Commit 87d333c

Browse files
haroonqcopybara-github
authored andcommitted
Allow MJB and XML content to be loaded directly.
PiperOrigin-RevId: 843180720 Change-Id: Id4f2bf81507b971dfbbf250d775d7fdfc76517df
1 parent 1ff74ba commit 87d333c

File tree

3 files changed

+62
-40
lines changed

3 files changed

+62
-40
lines changed

src/experimental/studio/app.cc

Lines changed: 49 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -199,45 +199,53 @@ void App::ClearModel() {
199199
error_ = "";
200200
}
201201

202-
void App::LoadModel(std::string model_file) {
203-
pending_load_ = std::move(model_file);
204-
}
205-
206-
void App::ProcessPendingLoad() {
207-
if (!pending_load_.has_value()) {
208-
return;
202+
void App::RequestModelLoad(std::string model_file) {
203+
if (model_file.starts_with('[') || model_file.ends_with(']')) {
204+
pending_load_ = "";
205+
} else {
206+
pending_load_ = std::move(model_file);
209207
}
208+
}
210209

211-
// Note that a non-empty model_file_ implies that a model was successfully
212-
// loaded.
213-
model_file_ = std::move(pending_load_.value());
214-
pending_load_.reset();
215-
210+
void App::LoadModel(std::string data, ContentType type) {
216211
// Delete the existing mjModel and mjData.
217212
ClearModel();
218213

219-
// Try to load the requested mjModel.
220214
char err[1000] = "";
221-
if (model_file_.ends_with(".mjb")) {
222-
model_ = mj_loadModel(model_file_.c_str(), 0);
223-
} else if (model_file_.ends_with(".xml")) {
224-
spec_ = mj_parseXML(model_file_.c_str(), nullptr, err, sizeof(err));
215+
if (type == ContentType::kFilepath) {
216+
// Store the file path as the model name. Note that we use this model name
217+
// to perform reload operations.
218+
model_name_ = std::move(data);
219+
if (model_name_.ends_with(".mjb")) {
220+
model_ = mj_loadModel(model_name_.c_str(), 0);
221+
} else if (model_name_.ends_with(".xml")) {
222+
spec_ = mj_parseXML(model_name_.c_str(), nullptr, err, sizeof(err));
223+
if (spec_ && err[0] == 0) {
224+
model_ = mj_compile(spec_, nullptr);
225+
}
226+
} else {
227+
error_ = "Unknown model file type; expected .mjb or .xml.";
228+
}
229+
} else if (type == ContentType::kModelXml) {
230+
model_name_ = "[xml]";
231+
spec_ = mj_parseXMLString(data.c_str(), nullptr, err, sizeof(err));
225232
if (spec_ && err[0] == 0) {
226233
model_ = mj_compile(spec_, nullptr);
227234
}
228-
} else {
229-
error_ = "Unknown model file type; expected .mjb or .xml.";
235+
} else if (type == ContentType::kModelMjb) {
236+
model_name_ = "[mjb]";
237+
model_ = mj_loadModelBuffer(data.data(), data.size());
230238
}
239+
231240
if (err[0]) {
232241
error_ = err;
233-
fprintf(stderr, "Error loading model: %s\n", error_.c_str());
234242
}
235243

236244
// If no mjModel was loaded, load an empty mjModel.
237-
if (model_file_.empty() || model_ == nullptr) {
245+
if (model_name_.empty() || model_ == nullptr) {
238246
spec_ = mj_makeSpec();
239247
model_ = mj_compile(spec_, 0);
240-
model_file_ = "";
248+
model_name_ = "";
241249
}
242250
if (!model_) {
243251
mju_error("Error loading model: %s", error_.c_str());
@@ -270,11 +278,11 @@ void App::ProcessPendingLoad() {
270278
// to the loaded model.
271279
std::string base_path = "/";
272280
std::string model_name = "model";
273-
if (!model_file_.empty() &&
274-
(model_file_.ends_with(".xml") || model_file_.ends_with(".mjb"))) {
275-
window_->SetTitle("MuJoCo Studio : " + model_file_);
276-
tmp_.last_load_file = std::string(model_file_);
277-
std::filesystem::path path(model_file_);
281+
if (!model_name_.empty() &&
282+
(model_name_.ends_with(".xml") || model_name_.ends_with(".mjb"))) {
283+
window_->SetTitle("MuJoCo Studio : " + model_name_);
284+
tmp_.last_load_file = std::string(model_name_);
285+
std::filesystem::path path(model_name_);
278286
base_path = path.parent_path().string() + "/";
279287
model_name = path.stem().string();
280288
} else {
@@ -289,7 +297,7 @@ void App::ProcessPendingLoad() {
289297
tmp_.last_save_screenshot_file = base_path + "screenshot.webp";
290298
}
291299

292-
bool App::IsModelLoaded() const { return !model_file_.empty(); }
300+
bool App::IsModelLoaded() const { return !model_name_.empty(); }
293301

294302
void App::ResetPhysics() {
295303
mj_resetData(model_, data_);
@@ -298,7 +306,11 @@ void App::ResetPhysics() {
298306
}
299307

300308
void App::UpdatePhysics() {
301-
ProcessPendingLoad();
309+
if (pending_load_.has_value()) {
310+
std::string model_file = std::move(pending_load_.value());
311+
pending_load_.reset();
312+
LoadModel(model_file, ContentType::kFilepath);
313+
}
302314
if (!IsModelLoaded()) {
303315
return;
304316
}
@@ -372,7 +384,7 @@ bool App::Update() {
372384
// Check to see if a model was dropped on the window.
373385
const std::string drop_file = window_->GetDropFile();
374386
if (!drop_file.empty()) {
375-
LoadModel(drop_file);
387+
RequestModelLoad(drop_file);
376388
}
377389

378390
// Only update the simulation if a popup window is not open. Note that the
@@ -564,7 +576,7 @@ void App::HandleKeyboardEvents() {
564576
std::string keyframe = platform::KeyframeToString(model_, data_, false);
565577
platform::MaybeSaveToClipboard(keyframe);
566578
} else if (ImGui_IsChordJustPressed(ImGuiKey_L | ImGuiMod_Ctrl)) {
567-
LoadModel(model_file_);
579+
RequestModelLoad(model_name_);
568580
} else if (ImGui_IsChordJustPressed(ImGuiKey_Q | ImGuiMod_Ctrl)) {
569581
tmp_.should_exit = true;
570582
} else if (ImGui_IsChordJustPressed(ImGuiKey_A | ImGuiMod_Ctrl)) {
@@ -1208,14 +1220,14 @@ void App::ToolBarGui() {
12081220
// Reset/Reload/Unload.
12091221
style.Color(ImGuiCol_ButtonHovered, ImColor(220, 40, 40, 255));
12101222
if (ImGui::Button(ICON_UNLOAD_MODEL, ImVec2(48, 32))) {
1211-
LoadModel("");
1223+
RequestModelLoad("");
12121224
}
12131225
ImGui::SetItemTooltip("%s", "Unload");
12141226
style.Reset();
12151227

12161228
ImGui::SameLine();
12171229
if (ImGui::Button(ICON_RELOAD_MODEL, ImVec2(48, 32))) {
1218-
LoadModel(model_file_);
1230+
RequestModelLoad(model_name_);
12191231
}
12201232
ImGui::SetItemTooltip("%s", "Reload");
12211233

@@ -1324,7 +1336,7 @@ void App::StatusBarGui() {
13241336

13251337
ImGui::TableNextColumn();
13261338

1327-
if (model_file_.empty()) {
1339+
if (!IsModelLoaded()) {
13281340
ImGui::Text("Not loaded");
13291341
} else if (model_ == nullptr) {
13301342
ImGui::Text("Not loaded");
@@ -1416,7 +1428,7 @@ void App::MainMenuGui() {
14161428
}
14171429
ImGui::Separator();
14181430
if (ImGui::MenuItem("Unload", "Ctrl+U")) {
1419-
LoadModel("");
1431+
RequestModelLoad("");
14201432
}
14211433
ImGui::Separator();
14221434
if (ImGui::MenuItem("Quit", "Ctrl+Q")) {
@@ -1432,7 +1444,7 @@ void App::MainMenuGui() {
14321444
ResetPhysics();
14331445
}
14341446
if (ImGui::MenuItem("Reload", "Ctrl+L")) {
1435-
LoadModel(model_file_);
1447+
RequestModelLoad(model_name_);
14361448
}
14371449
ImGui::Separator();
14381450
if (ImGui::BeginMenu("Keyframes")) {
@@ -1561,7 +1573,7 @@ void App::FileDialogGui() {
15611573
if (ImGui::BeginPopupModal("LoadModel", NULL,
15621574
ImGuiWindowFlags_AlwaysAutoResize)) {
15631575
if (platform::ImGui_FileDialog(tmp_.filename, sizeof(tmp_.filename))) {
1564-
LoadModel(tmp_.filename);
1576+
RequestModelLoad(tmp_.filename);
15651577
tmp_.last_load_file = tmp_.filename;
15661578
}
15671579
ImGui::EndPopup();

src/experimental/studio/app.h

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,17 @@ class App {
4848
App(int width, int height, std::string ini_path,
4949
const platform::LoadAssetFn& load_asset_fn);
5050

51+
enum ContentType {
52+
kFilepath, // Path to a model file.
53+
kModelXml, // XML model string.
54+
kModelMjb, // Binary model payload.
55+
};
56+
5157
// Loads a model into the simulation.
52-
void LoadModel(std::string model_file);
58+
//
59+
// Note: Do not call this function from within Update() (i.e. while drawing
60+
// the UX). Call RequestModelLoad() instead.
61+
void LoadModel(std::string data, ContentType type);
5362

5463
// Processes window events and advances the state of the simulation.
5564
bool Update();
@@ -132,6 +141,7 @@ class App {
132141
void ClearModel();
133142
void ProcessPendingLoad();
134143
bool IsModelLoaded() const;
144+
void RequestModelLoad(std::string model_file);
135145

136146
void ResetPhysics();
137147
void UpdatePhysics();
@@ -164,7 +174,7 @@ class App {
164174

165175
std::string error_;
166176
std::string ini_path_;
167-
std::string model_file_;
177+
std::string model_name_;
168178
std::optional<std::string> pending_load_;
169179

170180
std::unique_ptr<platform::Window> window_;

src/experimental/studio/main.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ int main(int argc, char** argv, char** envp) {
5656
// If the model file is not specified, try to load it from the first argument
5757
std::string model_file = absl::GetFlag(FLAGS_model_file);
5858
if (model_file.empty() && argc > 1 && argv[1][0] != '-') model_file = argv[1];
59-
app.LoadModel(model_file);
59+
app.LoadModel(model_file, mujoco::studio::App::ContentType::kFilepath);
6060
while (app.Update()) {
6161
app.BuildGui();
6262
app.Render();

0 commit comments

Comments
 (0)