Add an Editor panel.
The editor panel allows you to add, remove, and modify spec elements as well as recompiling and loading the spec. Also includes utilities for handling vertically split panels. PiperOrigin-RevId: 875658785 Change-Id: I18b10971d587db5eb1e65a033e1dc042faac7f1d
This commit is contained in:
committed by
Copybara-Service
parent
7232ec3b47
commit
a9675fbaa6
+243
-93
@@ -67,6 +67,7 @@ static void SelectParentPerturb(const mjModel* model, mjvPerturb& perturb) {
|
||||
perturb.active = 0;
|
||||
}
|
||||
}
|
||||
// TODO: update selected element!
|
||||
}
|
||||
|
||||
static constexpr const char* ICON_PLAY = platform::ICON_FA_PLAY;
|
||||
@@ -86,7 +87,9 @@ static constexpr const char* ICON_PREV_FRAME = platform::ICON_FA_CARET_LEFT;
|
||||
static constexpr const char* ICON_NEXT_FRAME = platform::ICON_FA_CARET_RIGHT;
|
||||
static constexpr const char* ICON_CURR_FRAME = platform::ICON_FA_FAST_FORWARD;
|
||||
static constexpr const char* ICON_SPEED = platform::ICON_FA_TACHOMETER;
|
||||
static constexpr const char* ICON_DELETE = platform::ICON_FA_TRASH_CAN;
|
||||
static constexpr const char* ICON_RELOAD_SPEC = platform::ICON_FA_REFRESH;
|
||||
static constexpr const char* ICON_UNDO_SPEC = platform::ICON_FA_UNDO;
|
||||
static constexpr const char* ICON_REDO_SPEC = platform::ICON_FA_REPEAT;
|
||||
|
||||
// UI labels for mjtLabel.
|
||||
static constexpr const char* kLabelNames[] = {
|
||||
@@ -201,6 +204,9 @@ void App::OnModelLoaded(std::string filename, ModelKind model_kind) {
|
||||
const int state_size = mj_stateSize(model, mjSTATE_INTEGRATION);
|
||||
history_.Init(state_size);
|
||||
|
||||
// Create a copy of the spec for editing.
|
||||
CopyLoadedSpecForEditing();
|
||||
|
||||
if (!preserve_camera_on_load_) {
|
||||
const int model_cam = model->vis.global.cameraid;
|
||||
if (model_cam >= 0 && model_cam < model->ncam) {
|
||||
@@ -422,44 +428,54 @@ void App::ProcessPendingLoads() {
|
||||
});
|
||||
}
|
||||
|
||||
void App::SpecSelectElement(mjsElement* element) {
|
||||
tmp_.element = element;
|
||||
if (tmp_.element == nullptr) {
|
||||
tmp_.element_id = -1;
|
||||
} else {
|
||||
tmp_.element_id = mjs_getId(tmp_.element);
|
||||
void App::CopyLoadedSpecForEditing() {
|
||||
if (scratch_spec_ != nullptr) {
|
||||
mj_deleteSpec(scratch_spec_);
|
||||
scratch_spec_ = nullptr;
|
||||
}
|
||||
|
||||
// If we selected a body, then select the same body for perturb.
|
||||
if (tmp_.element->elemtype == mjOBJ_BODY &&
|
||||
perturb_.select != tmp_.element_id) {
|
||||
mjv_defaultPerturb(&perturb_);
|
||||
perturb_.select = tmp_.element_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
scratch_spec_modified_ = false;
|
||||
tmp_.curr_edit_element = nullptr;
|
||||
|
||||
void App::SpecDeleteElement(mjsElement* element) {
|
||||
if (element == nullptr) {
|
||||
return;
|
||||
}
|
||||
// Only bodies can be deleted for now...
|
||||
if (element->elemtype != mjOBJ_BODY) {
|
||||
edit_error_ = "WARNING: Only bodies can be deleted (for now...)";
|
||||
return;
|
||||
}
|
||||
spec_op_ = [this, element]() {
|
||||
mjs_delete(spec(), element);
|
||||
if (tmp_.element == element) {
|
||||
tmp_.element = nullptr;
|
||||
tmp_.element_id = -1;
|
||||
}
|
||||
if (element->elemtype == mjOBJ_BODY) {
|
||||
if (perturb_.select == mjs_getId(element)) {
|
||||
mjv_defaultPerturb(&perturb_);
|
||||
if (has_spec()) {
|
||||
scratch_spec_ = mj_copySpec(spec());
|
||||
|
||||
auto add_ref_elements = [&](mjtObj type) {
|
||||
mjsElement* elem = mjs_firstElement(spec(), type);
|
||||
mjsElement* scratch = mjs_firstElement(scratch_spec_, type);
|
||||
while (elem != nullptr && scratch != nullptr) {
|
||||
scratch_to_spec_[scratch] = elem;
|
||||
spec_to_scratch_[elem] = scratch;
|
||||
elem = mjs_nextElement(spec(), elem);
|
||||
scratch = mjs_nextElement(scratch_spec_, scratch);
|
||||
}
|
||||
}
|
||||
Recompile();
|
||||
};
|
||||
};
|
||||
add_ref_elements(mjOBJ_BODY);
|
||||
add_ref_elements(mjOBJ_XBODY);
|
||||
add_ref_elements(mjOBJ_JOINT);
|
||||
add_ref_elements(mjOBJ_DOF);
|
||||
add_ref_elements(mjOBJ_GEOM);
|
||||
add_ref_elements(mjOBJ_SITE);
|
||||
add_ref_elements(mjOBJ_CAMERA);
|
||||
add_ref_elements(mjOBJ_LIGHT);
|
||||
add_ref_elements(mjOBJ_FLEX);
|
||||
add_ref_elements(mjOBJ_MESH);
|
||||
add_ref_elements(mjOBJ_SKIN);
|
||||
add_ref_elements(mjOBJ_HFIELD);
|
||||
add_ref_elements(mjOBJ_TEXTURE);
|
||||
add_ref_elements(mjOBJ_MATERIAL);
|
||||
add_ref_elements(mjOBJ_PAIR);
|
||||
add_ref_elements(mjOBJ_EXCLUDE);
|
||||
add_ref_elements(mjOBJ_EQUALITY);
|
||||
add_ref_elements(mjOBJ_TENDON);
|
||||
add_ref_elements(mjOBJ_ACTUATOR);
|
||||
add_ref_elements(mjOBJ_SENSOR);
|
||||
add_ref_elements(mjOBJ_NUMERIC);
|
||||
add_ref_elements(mjOBJ_TEXT);
|
||||
add_ref_elements(mjOBJ_TUPLE);
|
||||
add_ref_elements(mjOBJ_KEY);
|
||||
add_ref_elements(mjOBJ_PLUGIN);
|
||||
}
|
||||
}
|
||||
|
||||
void App::HandleWindowEvents() {
|
||||
@@ -555,14 +571,12 @@ void App::HandleMouseEvents() {
|
||||
perturb_.skinselect = picked.skin;
|
||||
|
||||
// Select the corresponding element in the spec.
|
||||
tmp_.element = nullptr;
|
||||
tmp_.element_id = -1;
|
||||
tmp_.curr_element = nullptr;
|
||||
if (has_spec()) {
|
||||
mjsElement* element = mjs_firstElement(spec(), mjOBJ_BODY);
|
||||
while (element) {
|
||||
if (mjs_getId(element) == picked.body) {
|
||||
tmp_.element = element;
|
||||
tmp_.element_id = picked.body;
|
||||
tmp_.curr_element = element;
|
||||
break;
|
||||
}
|
||||
element = mjs_nextElement(spec(), element);
|
||||
@@ -665,7 +679,7 @@ void App::HandleKeyboardEvents() {
|
||||
} else if (ImGui_IsChordJustPressed(ImGuiKey_Backspace)) {
|
||||
ResetPhysics();
|
||||
} else if (ImGui_IsChordJustPressed(ImGuiKey_Delete)) {
|
||||
SpecDeleteElement(tmp_.element);
|
||||
// TODO: SpecDeleteElement(tmp_.curr_element);
|
||||
} else if (ImGui_IsChordJustPressed(ImGuiKey_PageUp)) {
|
||||
SelectParentPerturb(model(), perturb_);
|
||||
} else if (ImGui_IsChordJustPressed(ImGuiKey_F1)) {
|
||||
@@ -917,19 +931,15 @@ void App::BuildGui() {
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
bool explorer_is_open = false;
|
||||
if (ImGui::Begin("Explorer", &tmp_.inspector_panel)) {
|
||||
explorer_is_open = true;
|
||||
SpecExplorerGui();
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
if (explorer_is_open && tmp_.element != nullptr) {
|
||||
if (ImGui::Begin("Properties")) {
|
||||
SpecPropertiesGui();
|
||||
}
|
||||
ImGui::End();
|
||||
if (ImGui::Begin("Editor", &tmp_.inspector_panel)) {
|
||||
SpecEditorGui();
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
if (tmp_.chart_performance) {
|
||||
@@ -1165,56 +1175,196 @@ void App::SpecExplorerGui() {
|
||||
return;
|
||||
}
|
||||
|
||||
auto on_delete = [this](mjsElement* element) { SpecDeleteElement(element); };
|
||||
|
||||
mjsElement* element = tmp_.element;
|
||||
platform::SpecExplorerGui(&element, spec(), on_delete);
|
||||
if (element != tmp_.element) {
|
||||
SpecSelectElement(element);
|
||||
// Initialize the split height.
|
||||
const ImVec2 region = ImGui::GetContentRegionAvail();
|
||||
if (tmp_.explorer_split < 0) {
|
||||
tmp_.explorer_split = region.y * 0.7f;
|
||||
}
|
||||
tmp_.explorer_split = std::clamp(tmp_.explorer_split, 20.f, region.y - 40.f);
|
||||
|
||||
mjsElement* element = tmp_.curr_element;
|
||||
bool open = element != nullptr;
|
||||
if (platform::ImGui_BeginHSplit("SpecExplorerTree",
|
||||
&tmp_.explorer_split, &open)) {
|
||||
platform::SpecTreeGui(&element, spec(), SpecEditMode::kPlay);
|
||||
|
||||
if (element != tmp_.curr_element) {
|
||||
tmp_.curr_element = element;
|
||||
|
||||
// A different element was selected, so select it for perturb.
|
||||
if (element) {
|
||||
open = true;
|
||||
const int element_id = mjs_getId(element);
|
||||
if (element->elemtype == mjOBJ_BODY && perturb_.select != element_id) {
|
||||
mjv_defaultPerturb(&perturb_);
|
||||
perturb_.select = element_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (platform::ImGui_HSplit("SpecExplorerProperties",
|
||||
&tmp_.explorer_split, &open)) {
|
||||
platform::ScopedStyle style;
|
||||
|
||||
ImGui::Text("%s", mju_type2Str(tmp_.curr_element->elemtype));
|
||||
ImGui::SameLine();
|
||||
ImGui::Text("(%d)", mjs_getId(tmp_.curr_element));
|
||||
ImGui::SameLine(ImGui::GetContentRegionAvail().x - 100.0f);
|
||||
|
||||
if (tmp_.spec_prop_mode == SpecPropertiesMode::kSpec) {
|
||||
style.Color(ImGuiCol_Button, ImGuiCol_ButtonActive);
|
||||
}
|
||||
style.Var(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
|
||||
if (ImGui::Button("S", ImVec2(24.0f, 20.0f))) {
|
||||
tmp_.spec_prop_mode = SpecPropertiesMode::kSpec;
|
||||
}
|
||||
ImGui::SetItemTooltip("Spec");
|
||||
style.Reset();
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (tmp_.spec_prop_mode == SpecPropertiesMode::kModel) {
|
||||
style.Color(ImGuiCol_Button, ImGuiCol_ButtonActive);
|
||||
}
|
||||
style.Var(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
|
||||
if (ImGui::Button("M", ImVec2(24.0f, 20.0f))) {
|
||||
tmp_.spec_prop_mode = SpecPropertiesMode::kModel;
|
||||
}
|
||||
ImGui::SetItemTooltip("Model");
|
||||
style.Reset();
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (tmp_.spec_prop_mode == SpecPropertiesMode::kData) {
|
||||
style.Color(ImGuiCol_Button, ImGuiCol_ButtonActive);
|
||||
}
|
||||
style.Var(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
|
||||
if (ImGui::Button("D", ImVec2(24.0f, 20.0f))) {
|
||||
tmp_.spec_prop_mode = SpecPropertiesMode::kData;
|
||||
}
|
||||
ImGui::SetItemTooltip("Data");
|
||||
style.Reset();
|
||||
ImGui::Separator();
|
||||
|
||||
if (tmp_.spec_prop_mode == SpecPropertiesMode::kSpec) {
|
||||
platform::ElementSpecGui(element, element, SpecEditMode::kPlay);
|
||||
} else if (tmp_.spec_prop_mode == SpecPropertiesMode::kModel) {
|
||||
platform::ElementModelGui(model(), tmp_.curr_element);
|
||||
} else {
|
||||
platform::ElementDataGui(data(), tmp_.curr_element);
|
||||
}
|
||||
}
|
||||
if (!open) {
|
||||
tmp_.curr_element = nullptr;
|
||||
}
|
||||
}
|
||||
platform::ImGui_EndHSplit(open);
|
||||
}
|
||||
|
||||
void App::SpecPropertiesGui() {
|
||||
platform::ScopedStyle style;
|
||||
void App::SpecEditorGui() {
|
||||
if (ImGui::BeginChild("SpecEditor", ImVec2(-1, 36))) {
|
||||
if (ImGui::BeginTable("##SpecEditorHeader", 3)) {
|
||||
ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, 70.0f);
|
||||
ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch);
|
||||
ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, 30.0f);
|
||||
|
||||
ImGui::Text("%s", mju_type2Str(tmp_.element->elemtype));
|
||||
ImGui::SameLine();
|
||||
ImGui::Text("(%d)", tmp_.element_id);
|
||||
ImGui::TableNextColumn();
|
||||
if (ImGui::Button(ICON_RELOAD_SPEC)) {
|
||||
CopyLoadedSpecForEditing();
|
||||
}
|
||||
ImGui::BeginDisabled(true);
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(ICON_UNDO_SPEC)) {
|
||||
// TODO...
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(ICON_REDO_SPEC)) {
|
||||
// TODO...
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::SameLine(120);
|
||||
if (tmp_.spec_prop_mode == SpecPropertiesMode::kSpec) {
|
||||
style.Color(ImGuiCol_Button, ImGuiCol_ButtonActive);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImColor(40, 180, 40, 255).Value);
|
||||
if (ImGui::Button("Compile and Reload", ImVec2(-1, 0))) {
|
||||
spec_op_ = [this]() {
|
||||
model_holder_ = platform::ModelHolder::FromSpec(scratch_spec_);
|
||||
scratch_spec_ = nullptr;
|
||||
OnModelLoaded(model_name_, model_kind_);
|
||||
};
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
if (ImGui::Button(" + ")) {
|
||||
ImGui::OpenPopupOnItemClick("SpecAddElement", 0);
|
||||
}
|
||||
if (ImGui::BeginPopupContextItem("SpecAddElement")) {
|
||||
auto option = [&](const char* label, auto fn) {
|
||||
if (ImGui::Selectable(label)) {
|
||||
tmp_.curr_edit_element = fn()->element;
|
||||
mjs_setName(tmp_.curr_edit_element,
|
||||
platform::ElementName(tmp_.curr_edit_element).c_str());
|
||||
scratch_spec_modified_ = true;
|
||||
}
|
||||
};
|
||||
option("Actuator", [&]() { return mjs_addActuator(scratch_spec_, 0); });
|
||||
option("Equality", [&]() { return mjs_addEquality(scratch_spec_, 0); });
|
||||
option("Exclude", [&]() { return mjs_addExclude(scratch_spec_); });
|
||||
option("Flex", [&]() { return mjs_addFlex(scratch_spec_); });
|
||||
option("Height Field", [&]() { return mjs_addHField(scratch_spec_); });
|
||||
option("Key", [&]() { return mjs_addKey(scratch_spec_); });
|
||||
option("Material", [&]() { return mjs_addMaterial(scratch_spec_, 0); });
|
||||
option("Mesh", [&]() { return mjs_addMesh(scratch_spec_, 0); });
|
||||
option("Numeric", [&]() { return mjs_addNumeric(scratch_spec_); });
|
||||
option("Pair", [&]() { return mjs_addPair(scratch_spec_, 0); });
|
||||
option("Sensor", [&]() { return mjs_addSensor(scratch_spec_); });
|
||||
option("Skin", [&]() { return mjs_addSkin(scratch_spec_); });
|
||||
option("Tendon", [&]() { return mjs_addTendon(scratch_spec_, 0); });
|
||||
option("Text", [&]() { return mjs_addText(scratch_spec_); });
|
||||
option("Texture", [&]() { return mjs_addTexture(scratch_spec_); });
|
||||
option("Tuple", [&]() { return mjs_addTuple(scratch_spec_); });
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
}
|
||||
if (ImGui::SmallButton("S")) {
|
||||
tmp_.spec_prop_mode = SpecPropertiesMode::kSpec;
|
||||
ImGui::EndChild();
|
||||
|
||||
// Initialize the split height.
|
||||
const ImVec2 region = ImGui::GetContentRegionAvail();
|
||||
if (tmp_.editor_split < 0) {
|
||||
tmp_.editor_split = region.y * 0.7f;
|
||||
}
|
||||
ImGui::SetItemTooltip("Spec");
|
||||
style.Reset();
|
||||
ImGui::SameLine();
|
||||
if (tmp_.spec_prop_mode == SpecPropertiesMode::kModel) {
|
||||
style.Color(ImGuiCol_Button, ImGuiCol_ButtonActive);
|
||||
}
|
||||
if (ImGui::SmallButton("M")) {
|
||||
tmp_.spec_prop_mode = SpecPropertiesMode::kModel;
|
||||
}
|
||||
ImGui::SetItemTooltip("Model");
|
||||
style.Reset();
|
||||
ImGui::SameLine();
|
||||
if (tmp_.spec_prop_mode == SpecPropertiesMode::kData) {
|
||||
style.Color(ImGuiCol_Button, ImGuiCol_ButtonActive);
|
||||
}
|
||||
if (ImGui::SmallButton("D")) {
|
||||
tmp_.spec_prop_mode = SpecPropertiesMode::kData;
|
||||
}
|
||||
ImGui::SetItemTooltip("Data");
|
||||
style.Reset();
|
||||
ImGui::Separator();
|
||||
if (tmp_.spec_prop_mode == SpecPropertiesMode::kSpec) {
|
||||
platform::ElementSpecGui(spec(), tmp_.element);
|
||||
} else if (tmp_.spec_prop_mode == SpecPropertiesMode::kModel) {
|
||||
platform::ElementModelGui(model(), tmp_.element);
|
||||
} else {
|
||||
platform::ElementDataGui(data(), tmp_.element);
|
||||
tmp_.editor_split = std::clamp(tmp_.editor_split, 20.0f, region.y - 40.0f);
|
||||
|
||||
bool open = tmp_.curr_edit_element != nullptr;
|
||||
if (platform::ImGui_BeginHSplit("SpecEditorTree", &tmp_.editor_split,
|
||||
&open)) {
|
||||
if (platform::SpecTreeGui(&tmp_.curr_edit_element, scratch_spec_,
|
||||
SpecEditMode::kEdit)) {
|
||||
scratch_spec_modified_ = true;
|
||||
}
|
||||
open = tmp_.curr_edit_element != nullptr;
|
||||
|
||||
if (platform::ImGui_HSplit("SpecEditorProperties", &tmp_.editor_split,
|
||||
&open)) {
|
||||
ImGui::Text("%s", mju_type2Str(tmp_.curr_edit_element->elemtype));
|
||||
ImGui::SameLine();
|
||||
ImGui::Text("(%d)", mjs_getId(tmp_.curr_edit_element));
|
||||
ImGui::Separator();
|
||||
|
||||
mjsElement* ref = scratch_to_spec_[tmp_.curr_edit_element];
|
||||
if (platform::ElementSpecGui(tmp_.curr_edit_element, ref,
|
||||
SpecEditMode::kEdit)) {
|
||||
scratch_spec_modified_ = true;
|
||||
}
|
||||
}
|
||||
platform::ImGui_EndHSplit(open);
|
||||
|
||||
if (!open) {
|
||||
tmp_.curr_edit_element = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1725,12 +1875,12 @@ void App::MainMenuGui() {
|
||||
}
|
||||
ImGui::Separator();
|
||||
|
||||
if (ImGui::MenuItem(tmp_.options_panel ? "Hide Options" : "Show Left UI",
|
||||
if (ImGui::MenuItem(tmp_.options_panel ? "Hide Options" : "Show Options",
|
||||
"Tab")) {
|
||||
tmp_.options_panel = !tmp_.options_panel;
|
||||
}
|
||||
if (ImGui::MenuItem(
|
||||
tmp_.inspector_panel ? "Hide Inspector" : "Show Right UI",
|
||||
tmp_.inspector_panel ? "Hide Inspector" : "Show Inspector",
|
||||
"Shift+Tab")) {
|
||||
tmp_.inspector_panel = !tmp_.inspector_panel;
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
|
||||
#include <mujoco/mujoco.h>
|
||||
#include "experimental/platform/gui.h"
|
||||
#include "experimental/platform/gui_spec.h"
|
||||
#include "experimental/platform/interaction.h"
|
||||
#include "experimental/platform/model_holder.h"
|
||||
#include "experimental/platform/picture_gui.h"
|
||||
@@ -84,6 +85,8 @@ class App {
|
||||
void Render();
|
||||
|
||||
private:
|
||||
using SpecEditMode = platform::SpecEditMode;
|
||||
|
||||
// The kind of model that is currently loaded.
|
||||
enum ModelKind {
|
||||
kEmptyModel,
|
||||
@@ -127,6 +130,8 @@ class App {
|
||||
bool style_editor = false;
|
||||
bool imgui_demo = false;
|
||||
bool implot_demo = false;
|
||||
float editor_split = -1;
|
||||
float explorer_split = -1;
|
||||
|
||||
// Controls.
|
||||
bool perturb_active = false;
|
||||
@@ -141,10 +146,10 @@ class App {
|
||||
std::vector<std::string> camera_names;
|
||||
std::vector<std::string> speed_names;
|
||||
|
||||
// Spec Properties.
|
||||
// Spec editing.
|
||||
SpecPropertiesMode spec_prop_mode = SpecPropertiesMode::kSpec;
|
||||
mjsElement* element = nullptr;
|
||||
int element_id = -1;
|
||||
mjsElement* curr_element = nullptr;
|
||||
mjsElement* curr_edit_element = nullptr;
|
||||
|
||||
// State.
|
||||
int state_sig = 0;
|
||||
@@ -216,13 +221,11 @@ class App {
|
||||
void ModelOptionsGui();
|
||||
void DataInspectorGui();
|
||||
void SpecExplorerGui();
|
||||
void SpecPropertiesGui();
|
||||
|
||||
void SpecSelectElement(mjsElement* element);
|
||||
void SpecDeleteElement(mjsElement* element);
|
||||
void SpecEditorGui();
|
||||
|
||||
float GetExpectedLabelWidth();
|
||||
std::vector<const char*> GetCameraNames();
|
||||
void CopyLoadedSpecForEditing();
|
||||
|
||||
mjSpec* spec() { return model_holder_->spec(); }
|
||||
mjModel* model() { return model_holder_->model(); }
|
||||
@@ -244,6 +247,17 @@ class App {
|
||||
std::unique_ptr<platform::Window> window_;
|
||||
std::unique_ptr<platform::Renderer> renderer_;
|
||||
std::unique_ptr<platform::ModelHolder> model_holder_;
|
||||
|
||||
// Spec editing. We keep a separate copy of the loaded spec that we can edit.
|
||||
// Once we're done editing, we will (re)compile the spec and update the
|
||||
// active model and data.
|
||||
mjSpec* scratch_spec_ = nullptr;
|
||||
// Whether or not the scratch spec differs from the loaded spec.
|
||||
bool scratch_spec_modified_ = false;
|
||||
// We keep a mapping of the elements between the loaded spec and the scratch
|
||||
// spec.
|
||||
std::unordered_map<mjsElement*, mjsElement*> spec_to_scratch_;
|
||||
std::unordered_map<mjsElement*, mjsElement*> scratch_to_spec_;
|
||||
std::function<void()> spec_op_;
|
||||
|
||||
platform::StepControl step_control_;
|
||||
|
||||
Reference in New Issue
Block a user