Display Bodies as a tree in Spec Explorer.
Display all other elements under "Elements" or "Assets" group. Show properties for mjSpec, mjModel, and mjData for selected elements. PiperOrigin-RevId: 874614195 Change-Id: I128c2bec844983ac5d35a3a0b1e58b8a5b0109cb
This commit is contained in:
committed by
Copybara-Service
parent
5fc580a8ba
commit
1d6ff2cece
@@ -42,6 +42,8 @@ target_sources(${MUJOCO_PLATFORM_TARGET_NAME}
|
||||
file_dialog.h
|
||||
gui.cc
|
||||
gui.h
|
||||
gui_spec.cc
|
||||
gui_spec.h
|
||||
helpers.cc
|
||||
helpers.h
|
||||
imgui_widgets.cc
|
||||
|
||||
@@ -470,7 +470,7 @@ void StateGui(const mjModel* model, mjData* data, std::vector<mjtNum>& state,
|
||||
ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * 20))) {
|
||||
ImGui::TableSetupColumn("Index");
|
||||
ImGui::TableSetupColumn("Name");
|
||||
ImGui::TableSetupColumn("Value");
|
||||
ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch);
|
||||
ImGui::TableSetupScrollFreeze(0, 1);
|
||||
ImGui::TableHeadersRow();
|
||||
|
||||
@@ -1107,111 +1107,4 @@ void StatsGui(const mjModel* model, const mjData* data, bool paused,
|
||||
ImGui::Columns();
|
||||
}
|
||||
|
||||
void BodyPropertiesGui(const mjModel* model, const mjData* data,
|
||||
mjsElement* element, int id) {
|
||||
const mjsBody* body = mjs_asBody(element);
|
||||
|
||||
ImGui::Columns(2);
|
||||
ImGui::SetColumnWidth(0, ImGui::GetWindowWidth() * 0.4f);
|
||||
ImGui::SetColumnWidth(1, ImGui::GetWindowWidth() * 0.6f);
|
||||
|
||||
std::string name = *mjs_getName(body->element);
|
||||
if (name.empty()) {
|
||||
name = "(Body " + std::to_string(id) + ")";
|
||||
}
|
||||
|
||||
ImGui::Columns(2);
|
||||
ImGui::SetColumnWidth(0, ImGui::GetWindowWidth() * 0.3f);
|
||||
ImGui::SetColumnWidth(1, ImGui::GetWindowWidth() * 0.7f);
|
||||
|
||||
ImGui::Text("Name");
|
||||
ImGui::Text("xpos[0]");
|
||||
ImGui::Text("xpos[1]");
|
||||
ImGui::Text("xpos[2]");
|
||||
ImGui::Text("xquat[0]");
|
||||
ImGui::Text("xquat[1]");
|
||||
ImGui::Text("xquat[2]");
|
||||
ImGui::Text("xquat[3]");
|
||||
ImGui::Text("mass");
|
||||
|
||||
ImGui::NextColumn();
|
||||
ImGui::Text("%s", name.c_str());
|
||||
ImGui::Text("%f", data->xpos[3*id+0]);
|
||||
ImGui::Text("%f", data->xpos[3*id+1]);
|
||||
ImGui::Text("%f", data->xpos[3*id+2]);
|
||||
ImGui::Text("%f", data->xquat[4*id+0]);
|
||||
ImGui::Text("%f", data->xquat[4*id+1]);
|
||||
ImGui::Text("%f", data->xquat[4*id+2]);
|
||||
ImGui::Text("%f", data->xquat[4*id+3]);
|
||||
ImGui::Text("%f", model->body_mass[id]);
|
||||
}
|
||||
|
||||
void JointPropertiesGui(const mjModel* model, const mjData* data,
|
||||
mjsElement* element, int id) {
|
||||
const mjsJoint* joint = mjs_asJoint(element);
|
||||
|
||||
ImGui::Columns(2);
|
||||
ImGui::SetColumnWidth(0, ImGui::GetWindowWidth() * 0.4f);
|
||||
ImGui::SetColumnWidth(1, ImGui::GetWindowWidth() * 0.6f);
|
||||
|
||||
std::string name = *mjs_getName(joint->element);
|
||||
if (name.empty()) {
|
||||
name = "(Joint " + std::to_string(id) + ")";
|
||||
}
|
||||
|
||||
ImGui::Columns(2);
|
||||
ImGui::SetColumnWidth(0, ImGui::GetWindowWidth() * 0.3f);
|
||||
ImGui::SetColumnWidth(1, ImGui::GetWindowWidth() * 0.7f);
|
||||
ImGui::Text("Name");
|
||||
|
||||
ImGui::NextColumn();
|
||||
ImGui::Text("%s", name.c_str());
|
||||
}
|
||||
|
||||
void SitePropertiesGui(const mjModel* model, const mjData* data,
|
||||
mjsElement* element, int id) {
|
||||
const mjsSite* site = mjs_asSite(element);
|
||||
|
||||
ImGui::Columns(2);
|
||||
ImGui::SetColumnWidth(0, ImGui::GetWindowWidth() * 0.4f);
|
||||
ImGui::SetColumnWidth(1, ImGui::GetWindowWidth() * 0.6f);
|
||||
|
||||
std::string name = *mjs_getName(site->element);
|
||||
if (name.empty()) {
|
||||
name = "(Joint " + std::to_string(id) + ")";
|
||||
}
|
||||
|
||||
ImGui::Columns(2);
|
||||
ImGui::SetColumnWidth(0, ImGui::GetWindowWidth() * 0.3f);
|
||||
ImGui::SetColumnWidth(1, ImGui::GetWindowWidth() * 0.7f);
|
||||
ImGui::Text("Name");
|
||||
ImGui::Text("site_xpos[0]");
|
||||
ImGui::Text("site_xpos[1]");
|
||||
ImGui::Text("site_xpos[2]");
|
||||
ImGui::Text("site_xmat[0]");
|
||||
ImGui::Text("site_xmat[1]");
|
||||
ImGui::Text("site_xmat[2]");
|
||||
ImGui::Text("site_xmat[3]");
|
||||
ImGui::Text("site_xmat[4]");
|
||||
ImGui::Text("site_xmat[5]");
|
||||
ImGui::Text("site_xmat[6]");
|
||||
ImGui::Text("site_xmat[7]");
|
||||
ImGui::Text("site_xmat[8]");
|
||||
|
||||
ImGui::NextColumn();
|
||||
ImGui::Text("%s", name.c_str());
|
||||
ImGui::Text("%f", data->site_xpos[3*id+0]);
|
||||
ImGui::Text("%f", data->site_xpos[3*id+1]);
|
||||
ImGui::Text("%f", data->site_xpos[3*id+2]);
|
||||
ImGui::Text("%f", data->site_xmat[4*id+0]);
|
||||
ImGui::Text("%f", data->site_xmat[4*id+1]);
|
||||
ImGui::Text("%f", data->site_xmat[4*id+2]);
|
||||
ImGui::Text("%f", data->site_xmat[4*id+3]);
|
||||
ImGui::Text("%f", data->site_xmat[4*id+4]);
|
||||
ImGui::Text("%f", data->site_xmat[4*id+5]);
|
||||
ImGui::Text("%f", data->site_xmat[4*id+6]);
|
||||
ImGui::Text("%f", data->site_xmat[4*id+7]);
|
||||
ImGui::Text("%f", data->site_xmat[4*id+8]);
|
||||
}
|
||||
|
||||
} // namespace mujoco::platform
|
||||
|
||||
@@ -116,14 +116,6 @@ void CountsGui(const mjModel* model, mjData* data);
|
||||
// FPS needs to be tracked by the caller and passed here to be displayed.
|
||||
void StatsGui(const mjModel* model, const mjData* data, bool paused, float fps);
|
||||
|
||||
// UX for displaying properties of various mjSpec elements.
|
||||
void BodyPropertiesGui(const mjModel* model, const mjData* data,
|
||||
mjsElement* element, int id);
|
||||
void JointPropertiesGui(const mjModel* model, const mjData* data,
|
||||
mjsElement* element, int id);
|
||||
void SitePropertiesGui(const mjModel* model, const mjData* data,
|
||||
mjsElement* element, int id);
|
||||
|
||||
} // namespace mujoco::platform
|
||||
|
||||
#endif // MUJOCO_SRC_EXPERIMENTAL_PLATFORM_GUI_H_
|
||||
|
||||
@@ -0,0 +1,847 @@
|
||||
// Copyright 2026 DeepMind Technologies Limited
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "experimental/platform/gui_spec.h"
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include <imgui.h>
|
||||
#include <mujoco/mjxmacro.h>
|
||||
#include <mujoco/mujoco.h>
|
||||
#include "experimental/platform/imgui_widgets.h"
|
||||
|
||||
// Define the mujoco X macros to add fields to the ImGui_DataTable.
|
||||
// We limit the fields to the ones with a matching element by comparing the
|
||||
// array size field (e.g. nbody) with the MATCH constexpr value.
|
||||
#define X(TYPE, NAME, NELEM, SIZE) \
|
||||
if constexpr (#NELEM == MATCH) table(#NAME, ptr->NAME, SIZE);
|
||||
|
||||
// Simple wrapper around MJMODEL_POINTERS that prepares values we need for the
|
||||
// X macro above.
|
||||
#define MJMODEL_POINTERS_X(M) \
|
||||
constexpr std::string_view MATCH(#M); \
|
||||
const auto* ptr = model; \
|
||||
MJMODEL_POINTERS
|
||||
|
||||
// Simple wrapper around MJDATA_POINTERS that prepares values we need for the
|
||||
// X macro above.
|
||||
#define MJDATA_POINTERS_X(M) \
|
||||
constexpr std::string_view MATCH(#M); \
|
||||
const auto* ptr = data; \
|
||||
MJDATA_POINTERS
|
||||
|
||||
namespace mujoco::platform {
|
||||
|
||||
// Returns the index of the element in the spec. This is different from
|
||||
// mjs_getId which returns the runtime ID of an element.
|
||||
static int GetElementIndexInSpec(mjsElement* element) {
|
||||
int n = 0;
|
||||
mjSpec* spec = mjs_getSpec(element);
|
||||
mjsElement* iter = mjs_firstElement(spec, element->elemtype);
|
||||
while (iter) {
|
||||
if (iter == element) {
|
||||
return n;
|
||||
}
|
||||
iter = mjs_nextElement(spec, iter);
|
||||
++n;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Returns a name for the element; either the element has a name, or we
|
||||
// construct a unique name from the element's id (using mjs_getId) or index
|
||||
// (using GetElementIndexInSpec).
|
||||
static std::string ElementName(mjsElement* element) {
|
||||
const mjString* name = mjs_getName(element);
|
||||
std::string label = *name;
|
||||
if (label.empty()) {
|
||||
int id = mjs_getId(element);
|
||||
if (id == -1) {
|
||||
id = GetElementIndexInSpec(element);
|
||||
}
|
||||
const char* type_name = mju_type2Str(element->elemtype);
|
||||
label = "(" + std::string(type_name) + " " + std::to_string(id) + ")";
|
||||
}
|
||||
return label;
|
||||
}
|
||||
|
||||
static void QuatOrOrientation(ImGui_DataTable& table, const double quat[4],
|
||||
const mjsOrientation& orientation,
|
||||
const char* quat_name, const char* alt_name) {
|
||||
auto alt =
|
||||
[&](const char* label) { return std::string(alt_name) + "." + label; };
|
||||
|
||||
switch (orientation.type) {
|
||||
case mjORIENTATION_QUAT:
|
||||
table(quat_name, quat, 4);
|
||||
break;
|
||||
case mjORIENTATION_AXISANGLE:
|
||||
table(alt("axisangle").c_str(), orientation.axisangle, 4);
|
||||
break;
|
||||
case mjORIENTATION_XYAXES:
|
||||
table(alt("xyaxes").c_str(), orientation.xyaxes, 6);
|
||||
break;
|
||||
case mjORIENTATION_ZAXIS:
|
||||
table(alt("zaxis").c_str(), orientation.zaxis, 3);
|
||||
break;
|
||||
case mjORIENTATION_EULER:
|
||||
table(alt("euler").c_str(), orientation.euler, 3);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void AddDeleteButton(mjsElement* element,
|
||||
const SpecElementCallbackFn& on_delete) {
|
||||
if (on_delete) {
|
||||
// Right-align the delete button.
|
||||
const float button_width = ImGui::CalcTextSize(ICON_FA_TRASH_CAN).x +
|
||||
ImGui::GetStyle().FramePadding.x * 2.0f;
|
||||
ImGui::SameLine(ImGui::GetWindowContentRegionMax().x - button_width);
|
||||
if (ImGui::SmallButton(ICON_FA_TRASH_CAN)) {
|
||||
on_delete(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void SelectableElement(mjsElement* element,
|
||||
mjsElement** selected_element,
|
||||
const SpecElementCallbackFn& on_delete) {
|
||||
constexpr ImGuiSelectableFlags flags = ImGuiSelectableFlags_AllowOverlap;
|
||||
|
||||
const std::string name = ElementName(element);
|
||||
const bool selected = (element == *selected_element);
|
||||
if (ImGui::Selectable(name.c_str(), selected, flags)) {
|
||||
*selected_element = element;
|
||||
}
|
||||
if (selected) {
|
||||
AddDeleteButton(element, on_delete);
|
||||
}
|
||||
}
|
||||
|
||||
static void BodyChildrenGui(const char* heading, mjtObj type,
|
||||
mjsElement** element, mjsBody* body,
|
||||
const SpecElementCallbackFn& on_delete) {
|
||||
mjsElement* iter = mjs_firstChild(body, type, 0);
|
||||
if (!iter) {
|
||||
return;
|
||||
}
|
||||
|
||||
constexpr ImGuiTreeNodeFlags tree_flags =
|
||||
ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_DrawLinesFull;
|
||||
if (ImGui::TreeNodeEx(heading, tree_flags)) {
|
||||
while (iter) {
|
||||
SelectableElement(iter, element, on_delete);
|
||||
iter = mjs_nextChild(body, iter, 0);
|
||||
}
|
||||
ImGui::TreePop();
|
||||
}
|
||||
}
|
||||
|
||||
static void ElementListGui(const char* heading, mjtObj type,
|
||||
mjsElement** element, mjSpec* spec,
|
||||
const SpecElementCallbackFn& on_delete) {
|
||||
mjsElement* iter = mjs_firstElement(spec, type);
|
||||
if (!iter) {
|
||||
return;
|
||||
}
|
||||
|
||||
constexpr ImGuiTreeNodeFlags tree_flags =
|
||||
ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_Framed;
|
||||
if (ImGui::TreeNodeEx(heading, tree_flags)) {
|
||||
while (iter) {
|
||||
SelectableElement(iter, element, on_delete);
|
||||
iter = mjs_nextElement(spec, iter);
|
||||
}
|
||||
ImGui::TreePop();
|
||||
}
|
||||
}
|
||||
|
||||
static void BodyTreeGuiRecursive(mjsElement** element, mjsBody* body,
|
||||
const SpecElementCallbackFn& on_delete) {
|
||||
const std::string label = ElementName(body->element);
|
||||
|
||||
ImGui::PushID(body);
|
||||
|
||||
ImGuiTreeNodeFlags flags =
|
||||
ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_Framed |
|
||||
ImGuiTreeNodeFlags_DrawLinesFull | ImGuiTreeNodeFlags_AllowOverlap;
|
||||
|
||||
if (*element == body->element) {
|
||||
flags |= ImGuiTreeNodeFlags_Selected;
|
||||
}
|
||||
|
||||
const bool tree_open = ImGui::TreeNodeEx(label.c_str(), flags);
|
||||
if (ImGui::IsItemClicked()) {
|
||||
*element = body->element;
|
||||
}
|
||||
if (*element == body->element) {
|
||||
AddDeleteButton(body->element, on_delete);
|
||||
}
|
||||
|
||||
if (tree_open) {
|
||||
mjsElement* iter = mjs_firstChild(body, mjOBJ_BODY, 0);
|
||||
while (iter) {
|
||||
BodyTreeGuiRecursive(element, mjs_asBody(iter), on_delete);
|
||||
iter = mjs_nextChild(body, iter, 0);
|
||||
}
|
||||
|
||||
BodyChildrenGui("Frames", mjOBJ_FRAME, element, body, on_delete);
|
||||
BodyChildrenGui("Sites", mjOBJ_SITE, element, body, on_delete);
|
||||
BodyChildrenGui("Joints", mjOBJ_JOINT, element, body, on_delete);
|
||||
BodyChildrenGui("Geoms", mjOBJ_GEOM, element, body, on_delete);
|
||||
BodyChildrenGui("Lights", mjOBJ_LIGHT, element, body, on_delete);
|
||||
BodyChildrenGui("Cameras", mjOBJ_CAMERA, element, body, on_delete);
|
||||
|
||||
ImGui::TreePop();
|
||||
}
|
||||
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
void SpecExplorerGui(mjsElement** element, mjSpec* spec,
|
||||
const SpecElementCallbackFn& on_delete) {
|
||||
const ImGuiTreeNodeFlags flags =
|
||||
ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_Framed;
|
||||
|
||||
if (ImGui::TreeNodeEx("Body Tree", flags)) {
|
||||
mjsElement* root = mjs_firstElement(spec, mjOBJ_BODY);
|
||||
if (root) {
|
||||
mjsBody* body = mjs_asBody(root);
|
||||
if (body) {
|
||||
BodyTreeGuiRecursive(element, body, on_delete);
|
||||
}
|
||||
}
|
||||
ImGui::TreePop();
|
||||
}
|
||||
|
||||
auto list = [&](const char* heading, mjtObj type) {
|
||||
ElementListGui(heading, type, element, spec, on_delete);
|
||||
};
|
||||
|
||||
ImGui::PushID(spec);
|
||||
|
||||
// Non-tree elements.
|
||||
if (ImGui::TreeNodeEx("Elements", flags)) {
|
||||
list("Actuators", mjOBJ_ACTUATOR);
|
||||
list("Sensors", mjOBJ_SENSOR);
|
||||
list("Flexes", mjOBJ_FLEX);
|
||||
list("Tendons", mjOBJ_TENDON);
|
||||
list("Pair", mjOBJ_PAIR);
|
||||
list("Exclude", mjOBJ_EXCLUDE);
|
||||
list("Equality", mjOBJ_EQUALITY);
|
||||
list("Numeric", mjOBJ_NUMERIC);
|
||||
list("Text", mjOBJ_TEXT);
|
||||
list("Tuple", mjOBJ_TUPLE);
|
||||
list("Key", mjOBJ_KEY);
|
||||
list("Default", mjOBJ_DEFAULT);
|
||||
ImGui::TreePop();
|
||||
}
|
||||
|
||||
// Assets.
|
||||
if (ImGui::TreeNodeEx("Assets", flags)) {
|
||||
list("Meshes", mjOBJ_MESH);
|
||||
list("Height Fields", mjOBJ_HFIELD);
|
||||
list("Skins", mjOBJ_SKIN);
|
||||
list("Textures", mjOBJ_TEXTURE);
|
||||
list("Materials", mjOBJ_MATERIAL);
|
||||
ImGui::TreePop();
|
||||
}
|
||||
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
void ElementSpecGui(const mjSpec* spec, mjsElement* element) {
|
||||
if (element == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui_DataTable table;
|
||||
table("Name", ElementName(element).c_str(), 1);
|
||||
|
||||
switch (element->elemtype) {
|
||||
case mjOBJ_BODY: {
|
||||
const mjsBody* body = mjs_asBody(element);
|
||||
table("childclass", body->childclass, 1); // childclass name
|
||||
table("pos", body->pos, 3); // frame position
|
||||
QuatOrOrientation(table, body->quat, body->alt, "quat", "alt"); // frame orientation
|
||||
table("ipos", body->ipos, 3); // inertial frame position
|
||||
QuatOrOrientation(table, body->iquat, body->ialt, "iquat", "ialt"); // inertial frame orientation
|
||||
table("mass", body->mass, 1); // mass
|
||||
table("inertia", body->inertia, 3); // diagonal inertia (in i-frame)
|
||||
table("fullinertia", body->fullinertia, 6); // non-axis-aligned inertia matrix
|
||||
table("mocap", body->mocap, 1); // is this a mocap body
|
||||
table("gravcomp", body->gravcomp, 1); // gravity compensation
|
||||
table("explicitinertial", body->explicitinertial, 1); // whether to save the body with explicit inertial clause
|
||||
table("sleep", body->sleep, 1); // sleep policy
|
||||
table("info", body->info, 1); // message appended to compiler errors
|
||||
break;
|
||||
}
|
||||
case mjOBJ_JOINT: {
|
||||
mjsJoint* joint = mjs_asJoint(element);
|
||||
table("pos", joint->pos, 3); // anchor position
|
||||
table("axis", joint->axis, 3); // joint axis
|
||||
table("ref", joint->ref, 1); // value at reference configuration: qpos0
|
||||
table("align", joint->align, 1); // align free joint with body com (mjtAlignFree)
|
||||
table("stiffness", joint->stiffness, 1); // stiffness coefficient
|
||||
table("springref", joint->springref, 1); // spring reference value: qpos_spring
|
||||
table("springdamper", joint->springdamper, 2); // timeconst, dampratio
|
||||
table("limited", joint->limited, 1); // does joint have limits (mjtLimited)
|
||||
table("range", joint->range, 2); // joint limits
|
||||
table("margin", joint->margin, 1); // margin value for joint limit detection
|
||||
table("solref_limit", joint->solref_limit, mjNREF); // solver reference: joint limits
|
||||
table("solimp_limit", joint->solimp_limit, mjNIMP); // solver impedance: joint limits
|
||||
table("actfrclimited", joint->actfrclimited, 1); // are actuator forces on joint limited (mjtLimited)
|
||||
table("actfrcrange", joint->actfrcrange, 2); // actuator force limits
|
||||
table("armature", joint->armature, 1); // armature inertia (mass for slider)
|
||||
table("damping", joint->damping, 1); // damping coefficient
|
||||
table("frictionloss", joint->frictionloss, 1); // friction loss
|
||||
table("solref_friction", joint->solref_friction, mjNREF); // solver reference: dof friction
|
||||
table("solimp_friction", joint->solimp_friction, mjNIMP); // solver impedance: dof friction
|
||||
table("group", joint->group, 1); // group
|
||||
table("actgravcomp", joint->actgravcomp, 1); // is gravcomp force applied via actuators
|
||||
table("info", joint->info, 1); // message appended to compiler errors
|
||||
break;
|
||||
}
|
||||
case mjOBJ_ACTUATOR: {
|
||||
mjsActuator* actuator = mjs_asActuator(element);
|
||||
table("gaintype", actuator->gaintype, 1); // gain type
|
||||
table("gainprm", actuator->gainprm, mjNGAIN); // gain parameters
|
||||
table("biastype", actuator->biastype, 1); // bias type
|
||||
table("biasprm", actuator->biasprm, mjNGAIN); // bias parameters
|
||||
table("dyntype", actuator->dyntype, 1); // dynamics type
|
||||
table("dynprm", actuator->dynprm, mjNDYN); // dynamics parameters
|
||||
table("actdim", actuator->actdim, 1); // number of activation variables
|
||||
table("actearly", actuator->actearly, 1); // apply next activations to qfrc
|
||||
table("trntype", actuator->trntype, 1); // transmission type
|
||||
table("gear", actuator->gear, 6); // length and transmitted force scaling
|
||||
table("target", actuator->target, 1); // name of transmission target
|
||||
table("refsite", actuator->refsite, 1); // reference site, for site transmission
|
||||
table("slidersite", actuator->slidersite, 1); // site defining cylinder, for slider-crank
|
||||
table("cranklength", actuator->cranklength, 1); // crank length, for slider-crank
|
||||
table("lengthrange", actuator->lengthrange, 2); // transmission length range
|
||||
table("inheritrange", actuator->inheritrange, 1); // automatic range setting for position and intvelocity
|
||||
table("ctrllimited", actuator->ctrllimited, 1); // are control limits defined (mjtLimited)
|
||||
table("ctrlrange", actuator->ctrlrange, 2); // control range
|
||||
table("forcelimited", actuator->forcelimited, 1); // are force limits defined (mjtLimited)
|
||||
table("forcerange", actuator->forcerange, 2); // force range
|
||||
table("actlimited", actuator->actlimited, 1); // are activation limits defined (mjtLimited)
|
||||
table("actrange", actuator->actrange, 2); // activation range
|
||||
table("group", actuator->group, 1); // group
|
||||
table("nsample", actuator->nsample, 1); // number of samples in history buffer
|
||||
table("interp", actuator->interp, 1); // interpolation order (0=ZOH, 1=linear, 2=cubic)
|
||||
table("delay", actuator->delay, 1); // delay time in seconds; 0: no delay
|
||||
table("info", actuator->info, 1); // message appended to compiler errors
|
||||
break;
|
||||
}
|
||||
case mjOBJ_SENSOR: {
|
||||
mjsSensor* sensor = mjs_asSensor(element);
|
||||
table("type", sensor->type, 1); // type of sensor
|
||||
table("objtype", sensor->objtype, 1); // type of sensorized object
|
||||
table("objname", sensor->objname, 1); // name of sensorized object
|
||||
table("reftype", sensor->reftype, 1); // type of referenced object
|
||||
table("refname", sensor->refname, 1); // name of referenced object
|
||||
table("intprm", sensor->intprm, mjNSENS); // integer parameters
|
||||
table("datatype", sensor->datatype, 1); // data type for sensor measurement
|
||||
table("needstage", sensor->needstage, 1); // compute stage needed to simulate sensor
|
||||
table("dim", sensor->dim, 1); // number of scalar outputs
|
||||
table("cutoff", sensor->cutoff, 1); // cutoff for real and positive datatypes
|
||||
table("noise", sensor->noise, 1); // noise stdev
|
||||
table("nsample", sensor->nsample, 1); // number of samples in history buffer
|
||||
table("interp", sensor->interp, 1); // interpolation order (0=ZOH, 1=linear, 2=cubic)
|
||||
table("delay", sensor->delay, 1); // delay time in seconds
|
||||
table("interval", sensor->interval, 2); // [period, time_prev] in seconds
|
||||
table("info", sensor->info, 1); // message appended to compiler errors
|
||||
break;
|
||||
}
|
||||
case mjOBJ_SITE: {
|
||||
mjsSite* site = mjs_asSite(element);
|
||||
table("pos", site->pos, 3); // position
|
||||
QuatOrOrientation(table, site->quat, site->alt, "quat", "alt"); // orientation
|
||||
table("fromto", site->fromto, 6); // alternative for capsule, cylinder, box, ellipsoid
|
||||
table("size", site->size, 3); // geom size
|
||||
table("type", site->type, 1); // geom type
|
||||
table("material", site->material, 1); // name of material
|
||||
table("group", site->group, 1); // group
|
||||
table("rgba", site->rgba, 4); // rgba when material is omitted
|
||||
table("info", site->info, 1); // message appended to compiler errors
|
||||
break;
|
||||
}
|
||||
case mjOBJ_FRAME: {
|
||||
mjsFrame* frame = mjs_asFrame(element);
|
||||
table("childclass", frame->childclass, 1); // childclass name
|
||||
table("pos", frame->pos, 3); // position
|
||||
QuatOrOrientation(table, frame->quat, frame->alt, "quat", "alt"); // orientation
|
||||
table("info", frame->info, 1); // message appended to compiler errors
|
||||
break;
|
||||
}
|
||||
case mjOBJ_GEOM: {
|
||||
mjsGeom* geom = mjs_asGeom(element);
|
||||
table("type", geom->type, 1); // geom type
|
||||
table("pos", geom->pos, 3); // position
|
||||
QuatOrOrientation(table, geom->quat, geom->alt, "quat", "alt"); // orientation
|
||||
table("fromto", geom->fromto, 6); // alternative for capsule, cylinder, box, ellipsoid
|
||||
table("size", geom->size, 3); // type-specific size
|
||||
table("contype", geom->contype, 1); // contact type
|
||||
table("conaffinity", geom->conaffinity, 1); // contact affinity
|
||||
table("condim", geom->condim, 1); // contact dimensionality
|
||||
table("priority", geom->priority, 1); // contact priority
|
||||
table("friction", geom->friction, 3); // one-sided friction coefficients: slide, roll, spin
|
||||
table("solmix", geom->solmix, 1); // solver mixing for contact pairs
|
||||
table("solref", geom->solref, mjNREF); // solver reference
|
||||
table("solimp", geom->solimp, mjNIMP); // solver impedance
|
||||
table("margin", geom->margin, 1); // margin for contact detection
|
||||
table("gap", geom->gap, 1); // include in solver if dist < margin-gap
|
||||
table("mass", geom->mass, 1); // used to compute density
|
||||
table("density", geom->density, 1); // used to compute mass and inertia from volume or surface
|
||||
table("typeinertia", geom->typeinertia, 1); // selects between surface and volume inertia
|
||||
table("fluid_ellipsoid", geom->fluid_ellipsoid, 1); // whether ellipsoid-fluid model is active
|
||||
table("fluid_coefs", geom->fluid_coefs, 5); // ellipsoid-fluid interaction coefs
|
||||
table("material", geom->material, 1); // name of material
|
||||
table("rgba", geom->rgba, 4); // rgba when material is omitted
|
||||
table("group", geom->group, 1); // group
|
||||
table("hfieldname", geom->hfieldname, 1); // heightfield attached to geom
|
||||
table("meshname", geom->meshname, 1); // mesh attached to geom
|
||||
table("fitscale", geom->fitscale, 1); // scale mesh uniformly
|
||||
table("info", geom->info, 1); // message appended to compiler errors
|
||||
break;
|
||||
}
|
||||
case mjOBJ_LIGHT: {
|
||||
mjsLight* light = mjs_asLight(element);
|
||||
table("pos", light->pos, 3); // position
|
||||
table("dir", light->dir, 3); // direction
|
||||
table("mode", light->mode, 1); // tracking mode
|
||||
table("targetbody", light->targetbody, 1); // target body for targeting
|
||||
table("active", light->active, 1); // is light active
|
||||
table("type", light->type, 1); // type of light
|
||||
table("texture", light->texture, 1); // texture name for image lights
|
||||
table("castshadow", light->castshadow, 1); // does light cast shadows
|
||||
table("bulbradius", light->bulbradius, 1); // bulb radius, for soft shadows
|
||||
table("intensity", light->intensity, 1); // intensity, in candelas
|
||||
table("range", light->range, 1); // range of effectiveness
|
||||
table("attenuation", light->attenuation, 3); // OpenGL attenuation (quadratic model)
|
||||
table("cutoff", light->cutoff, 1); // OpenGL cutoff
|
||||
table("exponent", light->exponent, 1); // OpenGL exponent
|
||||
table("ambient", light->ambient, 3); // ambient color
|
||||
table("diffuse", light->diffuse, 3); // diffuse color
|
||||
table("specular", light->specular, 3); // specular color
|
||||
table("info", light->info, 1); // message appended to compiler errorsx
|
||||
break;
|
||||
}
|
||||
case mjOBJ_CAMERA: {
|
||||
mjsCamera* camera = mjs_asCamera(element);
|
||||
table("pos", camera->pos, 3); // position
|
||||
QuatOrOrientation(table, camera->quat, camera->alt, "quat", "alt"); // orientation
|
||||
table("mode", camera->mode, 1); // tracking mode
|
||||
table("targetbody", camera->targetbody, 1); // target body for tracking/targeting
|
||||
table("proj", camera->proj, 1); // camera projection type
|
||||
table("resolution", camera->resolution, 2); // resolution (pixel)
|
||||
table("output", camera->output, 1); // bit flags for output type
|
||||
table("fovy", camera->fovy, 1); // y-field of view
|
||||
table("ipd", camera->ipd, 1); // inter-pupillary distance
|
||||
table("intrinsic", camera->intrinsic, 4); // camera intrinsics (length)
|
||||
table("sensor_size", camera->sensor_size, 2); // sensor size (length)
|
||||
table("focal_length", camera->focal_length, 2); // focal length (length)
|
||||
table("focal_pixel", camera->focal_pixel, 2); // focal length (pixel)
|
||||
table("principal_length", camera->principal_length, 2); // principal point (length)
|
||||
table("principal_pixel", camera->principal_pixel, 2); // principal point (pixel)
|
||||
table("info", camera->info, 1); // message appended to compiler errors
|
||||
break;
|
||||
}
|
||||
case mjOBJ_MESH: {
|
||||
mjsMesh* mesh = mjs_asMesh(element);
|
||||
table("content_type", mesh->content_type, 1); // content type of file
|
||||
table("file", mesh->file, 1); // mesh file
|
||||
table("refpos", mesh->refpos, 3); // reference position
|
||||
table("refquat", mesh->refquat, 4); // reference orientation
|
||||
table("scale", mesh->scale, 3); // rescale mesh
|
||||
table("inertia", mesh->inertia, 1); // inertia type (convex, legacy, exact, shell)
|
||||
table("smoothnormal", mesh->smoothnormal, 1); // do not exclude large-angle faces from normals
|
||||
table("needsdf", mesh->needsdf, 1); // compute sdf from mesh
|
||||
table("maxhullvert", mesh->maxhullvert, 1); // maximum vertex count for the convex hull
|
||||
table("material", mesh->material, 1); // name of material
|
||||
table("info", mesh->info, 1); // message appended to compiler errors
|
||||
break;
|
||||
}
|
||||
case mjOBJ_HFIELD: {
|
||||
mjsHField* hfield = mjs_asHField(element);
|
||||
table("content_type", hfield->content_type, 1); // content type of file
|
||||
table("file", hfield->file, 1); // file: (nrow, ncol, [elevation data])
|
||||
table("size", hfield->size, 4); // hfield size (ignore referencing geom size)
|
||||
table("nrow", hfield->nrow, 1); // number of rows
|
||||
table("ncol", hfield->ncol, 1); // number of columns
|
||||
table("info", hfield->info, 1); // message appended to compiler errors
|
||||
break;
|
||||
}
|
||||
case mjOBJ_SKIN: {
|
||||
mjsSkin* skin = mjs_asSkin(element);
|
||||
table("file", skin->file, 1); // skin file
|
||||
table("material", skin->material, 1); // name of material used for rendering
|
||||
table("rgba", skin->rgba, 4); // rgba when material is omitted
|
||||
table("inflate", skin->inflate, 1); // inflate in normal direction
|
||||
table("group", skin->group, 1); // group for visualization
|
||||
table("info", skin->info, 1); // message appended to compiler errors
|
||||
break;
|
||||
}
|
||||
case mjOBJ_FLEX: {
|
||||
mjsFlex* flex = mjs_asFlex(element);
|
||||
table("contype", flex->contype, 1); // contact type
|
||||
table("conaffinity", flex->conaffinity, 1); // contact affinity
|
||||
table("condim", flex->condim, 1); // contact dimensionality
|
||||
table("priority", flex->priority, 1); // contact priority
|
||||
table("friction", flex->friction, 3); // one-sided friction coefficients: slide, roll, spin
|
||||
table("solmix", flex->solmix, 1); // solver mixing for contact pairs
|
||||
table("solref", flex->solref, mjNREF); // solver reference
|
||||
table("solimp", flex->solimp, mjNIMP); // solver impedance
|
||||
table("margin", flex->margin, 1); // margin for contact detection
|
||||
table("gap", flex->gap, 1); // include in solver if dist<margin-gap
|
||||
table("dim", flex->dim, 1); // element dimensionality
|
||||
table("radius", flex->radius, 1); // radius around primitive element
|
||||
table("size", flex->size, 3); // vertex bounding box half sizes in qpos0
|
||||
table("internal", flex->internal, 1); // enable internal collisions
|
||||
table("flatskin", flex->flatskin, 1); // render flex skin with flat shading
|
||||
table("selfcollide", flex->selfcollide, 1); // mode for flex self collision
|
||||
table("vertcollide", flex->vertcollide, 1); // mode for vertex collision
|
||||
table("passive", flex->passive, 1); // mode for passive collisions
|
||||
table("activelayers", flex->activelayers, 1); // number of active element layers in 3D
|
||||
table("group", flex->group, 1); // group for visualization
|
||||
table("edgestiffness", flex->edgestiffness, 1); // edge stiffness
|
||||
table("edgedamping", flex->edgedamping, 1); // edge damping
|
||||
table("rgba", flex->rgba, 4); // rgba when material is omitted
|
||||
table("material", flex->material, 1); // name of material used for rendering
|
||||
table("young", flex->young, 1); // Young's modulus
|
||||
table("poisson", flex->poisson, 1); // Poisson's ratio
|
||||
table("damping", flex->damping, 1); // Rayleigh's damping
|
||||
table("thickness", flex->thickness, 1); // thickness (2D only)
|
||||
table("elastic2d", flex->elastic2d, 1); // 2D passive forces; 0: none, 1: bending, 2: stretching, 3: both
|
||||
table("info", flex->info, 1); // message appended to compiler errors
|
||||
break;
|
||||
}
|
||||
case mjOBJ_TENDON: {
|
||||
mjsTendon* tendon = mjs_asTendon(element);
|
||||
table("stiffness", tendon->stiffness, 1); // stiffness coefficient
|
||||
table("springlength", tendon->springlength, 2); // spring resting length; {-1, -1}: use qpos_spring
|
||||
table("damping", tendon->damping, 1); // damping coefficient
|
||||
table("frictionloss", tendon->frictionloss, 1); // friction loss
|
||||
table("solref_friction", tendon->solref_friction, mjNREF); // solver reference: tendon friction
|
||||
table("solimp_friction", tendon->solimp_friction, mjNIMP); // solver impedance: tendon friction
|
||||
table("armature", tendon->armature, 1); // inertia associated with tendon velocity
|
||||
table("limited", tendon->limited, 1); // does tendon have limits (mjtLimited)
|
||||
table("actfrclimited", tendon->actfrclimited, 1); // does tendon have actuator force limits
|
||||
table("range", tendon->range, 2); // length limits
|
||||
table("actfrcrange", tendon->actfrcrange, 2); // actuator force limits
|
||||
table("margin", tendon->margin, 1); // margin value for tendon limit detection
|
||||
table("solref_limit", tendon->solref_limit, mjNREF); // solver reference: tendon limits
|
||||
table("solimp_limit", tendon->solimp_limit, mjNIMP); // solver impedance: tendon limits
|
||||
table("material", tendon->material, 1); // name of material for rendering
|
||||
table("width", tendon->width, 1); // width for rendering
|
||||
table("rgba", tendon->rgba, 4); // rgba when material is omitted
|
||||
table("group", tendon->group, 1); // group
|
||||
table("info", tendon->info, 1); // message appended to errors
|
||||
break;
|
||||
}
|
||||
case mjOBJ_TEXTURE: {
|
||||
mjsTexture* texture = mjs_asTexture(element);
|
||||
table("type", texture->type, 1); // texture type
|
||||
table("colorspace", texture->colorspace, 1); // colorspace
|
||||
table("builtin", texture->builtin, 1); // builtin type (mjtBuiltin)
|
||||
table("mark", texture->mark, 1); // mark type (mjtMark)
|
||||
table("rgb1", texture->rgb1, 3); // first color for builtin
|
||||
table("rgb2", texture->rgb2, 3); // second color for builtin
|
||||
table("markrgb", texture->markrgb, 3); // mark color
|
||||
table("random", texture->random, 1); // probability of random dots
|
||||
table("height", texture->height, 1); // height in pixels (square for cube and skybox)
|
||||
table("width", texture->width, 1); // width in pixels
|
||||
table("nchannel", texture->nchannel, 1); // number of channels
|
||||
table("content_type", texture->content_type, 1); // content type of file
|
||||
table("file", texture->file, 1); // png file to load; use for all sides of cube
|
||||
table("gridsize", texture->gridsize, 2); // size of grid for composite file; (1,1)-repeat
|
||||
// TODO: table("gridlayout", texture->gridlayout, 12); // row-major: L,R,F,B,U,D for faces; . for unused
|
||||
table("cubefiles", texture->cubefiles, 1); // different file for each side of the cube
|
||||
table("hflip", texture->hflip, 1); // horizontal flip
|
||||
table("vflip", texture->vflip, 1); // vertical flip
|
||||
table("info", texture->info, 1); // message appended to compiler errors
|
||||
break;
|
||||
}
|
||||
case mjOBJ_MATERIAL: {
|
||||
mjsMaterial* material = mjs_asMaterial(element);
|
||||
table("textures", material->textures, 1); // names of textures (empty: none)
|
||||
table("texuniform", material->texuniform, 1); // make texture cube uniform
|
||||
table("texrepeat", material->texrepeat, 2); // texture repetition for 2D mapping
|
||||
table("emission", material->emission, 1); // emission
|
||||
table("specular", material->specular, 1); // specular
|
||||
table("shininess", material->shininess, 1); // shininess
|
||||
table("reflectance", material->reflectance, 1); // reflectance
|
||||
table("metallic", material->metallic, 1); // metallic
|
||||
table("roughness", material->roughness, 1); // roughness
|
||||
table("rgba", material->rgba, 4); // rgba
|
||||
table("info", material->info, 1); // message appended to compiler errors
|
||||
break;
|
||||
}
|
||||
case mjOBJ_PAIR: {
|
||||
mjsPair* pair = mjs_asPair(element);
|
||||
table("geomname1", pair->geomname1, 1); // name of geom 1
|
||||
table("geomname2", pair->geomname2, 1); // name of geom 2
|
||||
table("condim", pair->condim, 1); // contact dimensionality
|
||||
table("solref", pair->solref, mjNREF); // solver reference, normal direction
|
||||
table("solreffriction", pair->solreffriction, mjNREF); // solver reference, frictional directions
|
||||
table("solimp", pair->solimp, mjNIMP); // solver impedance
|
||||
table("margin", pair->margin, 1); // margin for contact detection
|
||||
table("gap", pair->gap, 1); // include in solver if dist<margin-gap
|
||||
table("friction", pair->friction, 5); // full contact friction
|
||||
table("info", pair->info, 1); // message appended to errors
|
||||
break;
|
||||
}
|
||||
case mjOBJ_EQUALITY: {
|
||||
mjsEquality* equality = mjs_asEquality(element);
|
||||
table("type", equality->type, 1); // constraint type
|
||||
table("data", equality->data, mjNEQDATA); // type-dependent data
|
||||
table("active", equality->active, 1); // is equality initially active
|
||||
table("name1", equality->name1, 1); // name of object 1
|
||||
table("name2", equality->name2, 1); // name of object 2
|
||||
table("objtype", equality->objtype, 1); // type of both objects
|
||||
table("solref", equality->solref, mjNREF); // solver reference
|
||||
table("solimp", equality->solimp, mjNIMP); // solver impedance
|
||||
table("info", equality->info, 1); // message appended to errors
|
||||
break;
|
||||
}
|
||||
case mjOBJ_EXCLUDE: {
|
||||
mjsExclude* exclude = mjs_asExclude(element);
|
||||
table("bodyname1", exclude->bodyname1, 1); // name of geom 1
|
||||
table("bodyname2", exclude->bodyname2, 1); // name of geom 2
|
||||
table("info", exclude->info, 1); // message appended to errors
|
||||
break;
|
||||
}
|
||||
case mjOBJ_NUMERIC: {
|
||||
mjsNumeric* numeric = mjs_asNumeric(element);
|
||||
table("data", numeric->data, 1); // initialization data
|
||||
table("size", numeric->size, 1); // array size, can be bigger than data size
|
||||
table("info", numeric->info, 1); // message appended to errors
|
||||
break;
|
||||
}
|
||||
case mjOBJ_TEXT: {
|
||||
mjsText* text = mjs_asText(element);
|
||||
table("data", text->data, 1); // text string
|
||||
table("info", text->info, 1); // message appended to compiler errors
|
||||
break;
|
||||
}
|
||||
case mjOBJ_TUPLE: {
|
||||
mjsTuple* tuple = mjs_asTuple(element);
|
||||
table("objtype", tuple->objtype, 1); // object types
|
||||
table("objname", tuple->objname, 1); // object names
|
||||
table("objprm", tuple->objprm, 1); // object parameters
|
||||
table("info", tuple->info, 1); // message appended to compiler errors
|
||||
break;
|
||||
}
|
||||
case mjOBJ_KEY: {
|
||||
mjsKey* key = mjs_asKey(element);
|
||||
table("time", key->time, 1); // time
|
||||
table("qpos", key->qpos, 1); // qpos
|
||||
table("qvel", key->qvel, 1); // qvel
|
||||
table("act", key->act, 1); // act
|
||||
table("mpos", key->mpos, 1); // mocap pos
|
||||
table("mquat", key->mquat, 1); // mocap quat
|
||||
table("ctrl", key->ctrl, 1); // ctrl
|
||||
table("info", key->info, 1); // message appended to compiler errors
|
||||
break;
|
||||
}
|
||||
case mjOBJ_PLUGIN: {
|
||||
mjsPlugin* plugin = mjs_asPlugin(element);
|
||||
table("name", plugin->name, 1); // instance name
|
||||
table("plugin_name", plugin->plugin_name, 1); // plugin name
|
||||
table("active", plugin->active, 1); // is the plugin active
|
||||
table("info", plugin->info, 1); // message appended to compiler errors
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// ignore other types
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ElementModelGui(const mjModel* model, mjsElement* element) {
|
||||
if (element == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui_DataTable table;
|
||||
table("Name", ElementName(element).c_str(), 1);
|
||||
table.SetArrayIndex(mjs_getId(element));
|
||||
|
||||
MJMODEL_POINTERS_PREAMBLE(model);
|
||||
switch (element->elemtype) {
|
||||
case mjOBJ_BODY: {
|
||||
table.SetPrefix("body_");
|
||||
MJMODEL_POINTERS_X(nbody)
|
||||
break;
|
||||
}
|
||||
case mjOBJ_JOINT: {
|
||||
table.SetPrefix("jnt_");
|
||||
MJMODEL_POINTERS_X(njnt)
|
||||
break;
|
||||
}
|
||||
case mjOBJ_ACTUATOR: {
|
||||
table.SetPrefix("actuator_");
|
||||
MJMODEL_POINTERS_X(nu)
|
||||
break;
|
||||
}
|
||||
case mjOBJ_SENSOR: {
|
||||
table.SetPrefix("sensor_");
|
||||
MJMODEL_POINTERS_X(nsensor)
|
||||
break;
|
||||
}
|
||||
case mjOBJ_SITE: {
|
||||
table.SetPrefix("site_");
|
||||
MJMODEL_POINTERS_X(nsite)
|
||||
break;
|
||||
}
|
||||
case mjOBJ_GEOM: {
|
||||
table.SetPrefix("geom_");
|
||||
MJMODEL_POINTERS_X(ngeom)
|
||||
break;
|
||||
}
|
||||
case mjOBJ_LIGHT: {
|
||||
table.SetPrefix("light_");
|
||||
MJMODEL_POINTERS_X(nlight)
|
||||
break;
|
||||
}
|
||||
case mjOBJ_CAMERA: {
|
||||
table.SetPrefix("cam_");
|
||||
MJMODEL_POINTERS_X(ncam)
|
||||
break;
|
||||
}
|
||||
case mjOBJ_MESH: {
|
||||
table.SetPrefix("mesh_");
|
||||
MJMODEL_POINTERS_X(nmesh)
|
||||
break;
|
||||
}
|
||||
case mjOBJ_HFIELD: {
|
||||
table.SetPrefix("hfield_");
|
||||
MJMODEL_POINTERS_X(nhfield)
|
||||
break;
|
||||
}
|
||||
case mjOBJ_SKIN: {
|
||||
table.SetPrefix("skin_");
|
||||
MJMODEL_POINTERS_X(nskin)
|
||||
break;
|
||||
}
|
||||
case mjOBJ_FLEX: {
|
||||
table.SetPrefix("flex_");
|
||||
MJMODEL_POINTERS_X(nflex)
|
||||
break;
|
||||
}
|
||||
case mjOBJ_TENDON: {
|
||||
table.SetPrefix("tendon_");
|
||||
MJMODEL_POINTERS_X(ntendon)
|
||||
break;
|
||||
}
|
||||
case mjOBJ_TEXTURE: {
|
||||
table.SetPrefix("tex_");
|
||||
MJMODEL_POINTERS_X(ntex)
|
||||
break;
|
||||
}
|
||||
case mjOBJ_MATERIAL: {
|
||||
table.SetPrefix("mat_");
|
||||
MJMODEL_POINTERS_X(nmat)
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// ignore other types
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ElementDataGui(const mjData* data, mjsElement* element) {
|
||||
if (element == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui_DataTable table;
|
||||
table("Name", ElementName(element).c_str(), 1);
|
||||
table.SetArrayIndex(mjs_getId(element));
|
||||
|
||||
switch (element->elemtype) {
|
||||
case mjOBJ_BODY: {
|
||||
MJDATA_POINTERS_X(nbody)
|
||||
break;
|
||||
}
|
||||
case mjOBJ_JOINT: {
|
||||
MJDATA_POINTERS_X(njnt)
|
||||
break;
|
||||
}
|
||||
case mjOBJ_SITE: {
|
||||
table.SetPrefix("site_");
|
||||
MJDATA_POINTERS_X(nsite)
|
||||
break;
|
||||
}
|
||||
case mjOBJ_GEOM: {
|
||||
table.SetPrefix("geom_");
|
||||
MJDATA_POINTERS_X(ngeom)
|
||||
break;
|
||||
}
|
||||
case mjOBJ_CAMERA: {
|
||||
table.SetPrefix("cam_");
|
||||
MJDATA_POINTERS_X(ncam)
|
||||
break;
|
||||
}
|
||||
case mjOBJ_LIGHT: {
|
||||
table.SetPrefix("light_");
|
||||
MJDATA_POINTERS_X(nlight)
|
||||
break;
|
||||
}
|
||||
case mjOBJ_SENSOR: {
|
||||
table.SetPrefix("sensor_");
|
||||
MJDATA_POINTERS_X(nsensor)
|
||||
break;
|
||||
}
|
||||
case mjOBJ_MESH: {
|
||||
table.SetPrefix("mesh_");
|
||||
MJDATA_POINTERS_X(nmesh)
|
||||
break;
|
||||
}
|
||||
case mjOBJ_HFIELD: {
|
||||
table.SetPrefix("hfield_");
|
||||
MJDATA_POINTERS_X(nhfield)
|
||||
break;
|
||||
}
|
||||
case mjOBJ_SKIN: {
|
||||
table.SetPrefix("skin_");
|
||||
MJDATA_POINTERS_X(nskin)
|
||||
break;
|
||||
}
|
||||
case mjOBJ_FLEX: {
|
||||
table.SetPrefix("flex_");
|
||||
MJDATA_POINTERS_X(nflex)
|
||||
break;
|
||||
}
|
||||
case mjOBJ_TENDON: {
|
||||
table.SetPrefix("ten_");
|
||||
MJDATA_POINTERS_X(ntendon)
|
||||
break;
|
||||
}
|
||||
case mjOBJ_TEXTURE: {
|
||||
table.SetPrefix("tex_");
|
||||
MJDATA_POINTERS_X(ntex)
|
||||
break;
|
||||
}
|
||||
case mjOBJ_MATERIAL: {
|
||||
table.SetPrefix("mat_");
|
||||
MJDATA_POINTERS_X(nmat)
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
} // namespace mujoco::platform
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright 2026 DeepMind Technologies Limited
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MUJOCO_SRC_EXPERIMENTAL_PLATFORM_GUI_SPEC_H_
|
||||
#define MUJOCO_SRC_EXPERIMENTAL_PLATFORM_GUI_SPEC_H_
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <mujoco/mujoco.h>
|
||||
|
||||
namespace mujoco::platform {
|
||||
|
||||
using SpecElementCallbackFn = std::function<void(mjsElement*)>;
|
||||
|
||||
// UX for displaying the spec as a tree.
|
||||
void SpecExplorerGui(mjsElement** element, mjSpec* spec,
|
||||
const SpecElementCallbackFn& on_delete);
|
||||
|
||||
// UX for displaying the properties of an mjSpec element.
|
||||
void ElementSpecGui(const mjSpec* spec, mjsElement* element);
|
||||
void ElementDataGui(const mjData* data, mjsElement* element);
|
||||
void ElementModelGui(const mjModel* model, mjsElement* element);
|
||||
|
||||
} // namespace mujoco::platform
|
||||
|
||||
#endif // MUJOCO_SRC_EXPERIMENTAL_PLATFORM_GUI_SPEC_H_
|
||||
@@ -14,9 +14,13 @@
|
||||
|
||||
#include "experimental/platform/imgui_widgets.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include <imgui.h>
|
||||
#include <mujoco/mujoco.h>
|
||||
@@ -57,6 +61,228 @@ KeyValues ReadIniSection(const std::string& contents,
|
||||
return key_values;
|
||||
}
|
||||
|
||||
ImGui_DataTable::ImGui_DataTable(float w1, float w2) {
|
||||
ImGui::BeginTable("##PropertiesTable", 2);
|
||||
const float width = ImGui::GetContentRegionAvail().x;
|
||||
ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, width * w1);
|
||||
ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, width * w2);
|
||||
}
|
||||
ImGui_DataTable::~ImGui_DataTable() { ImGui::EndTable(); }
|
||||
|
||||
void ImGui_DataTable::SetArrayIndex(int index) { index_ = index; }
|
||||
|
||||
void ImGui_DataTable::SetPrefix(const char* prefix) {
|
||||
prefix_ = strlen(prefix);
|
||||
}
|
||||
|
||||
void ImGui_DataTable::operator()(const char* label, const uintptr_t* ptr,
|
||||
int n) {
|
||||
for (int i = 0; i < n; ++i) {
|
||||
MakeLabel(label, i, n);
|
||||
ImGui::Text("(%s)", &ptr[index_ + i] ? "[ptr]" : "null");
|
||||
}
|
||||
}
|
||||
|
||||
void ImGui_DataTable::operator()(const char* label, const char* ptr, int n) {
|
||||
if (n == 1) {
|
||||
MakeLabel(label);
|
||||
ImGui::Text("%s", &ptr[index_]);
|
||||
} else {
|
||||
mju_error("char cannot be converted to a vector");
|
||||
}
|
||||
}
|
||||
|
||||
void ImGui_DataTable::operator()(const char* label, const mjtByte* ptr, int n) {
|
||||
for (int i = 0; i < n; ++i) {
|
||||
MakeLabel(label, i, n);
|
||||
ImGui::Text("%s", ptr[index_ + i] ? "true" : "false");
|
||||
}
|
||||
}
|
||||
|
||||
void ImGui_DataTable::operator()(const char* label, const mjtByte& val, int n) {
|
||||
MakeLabel(label, 0, 1);
|
||||
ImGui::Text("%s", val ? "true" : "false");
|
||||
}
|
||||
|
||||
void ImGui_DataTable::operator()(const char* label, const mjtSize* ptr, int n) {
|
||||
Numeric(label, ptr, n);
|
||||
}
|
||||
|
||||
void ImGui_DataTable::operator()(const char* label, const int* ptr, int n) {
|
||||
Numeric(label, ptr, n);
|
||||
}
|
||||
|
||||
void ImGui_DataTable::operator()(const char* label, const float* ptr, int n) {
|
||||
Numeric(label, ptr, n);
|
||||
}
|
||||
|
||||
void ImGui_DataTable::operator()(const char* label, const double* ptr, int n) {
|
||||
Numeric(label, ptr, n);
|
||||
}
|
||||
|
||||
void ImGui_DataTable::operator()(const char* label, const mjtSize& val, int n) {
|
||||
Scalar(label, val, n);
|
||||
}
|
||||
|
||||
void ImGui_DataTable::operator()(const char* label, const int& val, int n) {
|
||||
Scalar(label, val, n);
|
||||
}
|
||||
|
||||
void ImGui_DataTable::operator()(const char* label, const float& val, int n) {
|
||||
Scalar(label, val, n);
|
||||
}
|
||||
|
||||
void ImGui_DataTable::operator()(const char* label, const double& val, int n) {
|
||||
Scalar(label, val, n);
|
||||
}
|
||||
|
||||
void ImGui_DataTable::operator()(const char* label, const std::string* ptr,
|
||||
int n) {
|
||||
for (int i = 0; i < n; ++i) {
|
||||
MakeLabel(label, i, n);
|
||||
ImGui::Text("%s", ptr[i].c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void ImGui_DataTable::operator()(const char* label,
|
||||
const std::vector<std::string>* ptr, int n) {
|
||||
if (n == 1) {
|
||||
for (int i = 0; i < ptr->size(); ++i) {
|
||||
MakeLabel(label, i, ptr->size());
|
||||
ImGui::Text("%s", ptr->at(i).c_str());
|
||||
}
|
||||
} else {
|
||||
mju_error("data type is vector; cannot also be an array");
|
||||
}
|
||||
}
|
||||
|
||||
void ImGui_DataTable::operator()(const char* label, const std::vector<int>* ptr,
|
||||
int n) {
|
||||
if (n == 1) {
|
||||
const int size = ptr->size();
|
||||
if (size == 0) {
|
||||
(*this)(label, "[empty]", 1);
|
||||
} else {
|
||||
std::string tmp = "[" + std::to_string(size) + " values]";
|
||||
(*this)(label, tmp.c_str(), 1);
|
||||
}
|
||||
} else {
|
||||
mju_error("data type is vector; cannot also be an array");
|
||||
}
|
||||
}
|
||||
|
||||
void ImGui_DataTable::operator()(const char* label,
|
||||
const std::vector<double>* ptr, int n) {
|
||||
if (n == 1) {
|
||||
const int size = ptr->size();
|
||||
if (size == 0) {
|
||||
(*this)(label, "[empty]", 1);
|
||||
} else {
|
||||
std::string tmp = "[" + std::to_string(size) + " values]";
|
||||
(*this)(label, tmp.c_str(), 1);
|
||||
}
|
||||
} else {
|
||||
mju_error("data type is vector; cannot also be an array");
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void ImGui_DataTable::Scalar(const char* label, const T& value, int n) {
|
||||
if (n == 1) {
|
||||
Numeric(label, &value, n);
|
||||
} else {
|
||||
mju_error("scalar cannot be converted to a vector");
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void ImGui_DataTable::Numeric(const char* label, const T* ptr, int n) {
|
||||
const T* addr = ptr + index_ * n;
|
||||
|
||||
using U = std::conditional_t<std::is_floating_point_v<T>, float, int>;
|
||||
|
||||
// special treatment for NaNs.
|
||||
if constexpr (std::is_same_v<U, float>) {
|
||||
if (*addr != *addr) {
|
||||
MakeLabel(label);
|
||||
ImGui::Text("nan");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr const char* fmt1 =
|
||||
std::is_floating_point_v<U> ? "%f" : "%d";
|
||||
constexpr const char* fmt2 =
|
||||
std::is_floating_point_v<U> ? "%f %f" : "%d %d";
|
||||
constexpr const char* fmt3 =
|
||||
std::is_floating_point_v<U> ? "%f %f %f" : "%d %d %d";
|
||||
constexpr const char* fmt4 =
|
||||
std::is_floating_point_v<U> ? "%f %f %f %f" : "%d %d %d %d";
|
||||
|
||||
auto text1 = [&](int offset) {
|
||||
ImGui::Text(fmt1, (U)(addr[offset]));
|
||||
};
|
||||
auto text2 = [&](int offset) {
|
||||
ImGui::Text(fmt2, (U)(addr[offset + 0]), (U)(addr[offset + 1]));
|
||||
};
|
||||
auto text3 = [&](int offset) {
|
||||
ImGui::Text(fmt3, (U)(addr[offset + 0]), (U)(addr[offset + 1]),
|
||||
(U)(addr[offset + 2]));
|
||||
};
|
||||
auto text4 = [&](int offset) {
|
||||
ImGui::Text(fmt4, (U)(addr[offset + 0]), (U)(addr[offset + 1]),
|
||||
(U)(addr[offset + 2]), (U)(addr[offset + 3]));
|
||||
};
|
||||
|
||||
if (n == 1) {
|
||||
MakeLabel(label);
|
||||
text1(0);
|
||||
} else if (n == 2) {
|
||||
MakeLabel(label);
|
||||
text2(0);
|
||||
} else if (n == 3) {
|
||||
MakeLabel(label);
|
||||
text3(0);
|
||||
} else if (n == 4) {
|
||||
MakeLabel(label);
|
||||
text4(0);
|
||||
} else if (n == 6) {
|
||||
MakeLabel(label);
|
||||
text3(0);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::TableNextColumn();
|
||||
text3(3);
|
||||
} else if (n == 9) {
|
||||
MakeLabel(label);
|
||||
text3(0);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::TableNextColumn();
|
||||
text3(3);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::TableNextColumn();
|
||||
text3(6);
|
||||
} else {
|
||||
for (int i = 0; i < n; ++i) {
|
||||
MakeLabel(label, i, n);
|
||||
text1(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ImGui_DataTable::MakeLabel(const char* label, int index, int total) {
|
||||
if (total == 1) {
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%s", &label[prefix_]);
|
||||
ImGui::TableNextColumn();
|
||||
} else {
|
||||
const std::string tmp =
|
||||
std::string(&label[prefix_]) + "[" + std::to_string(index) + "]";
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%s", tmp.c_str());
|
||||
ImGui::TableNextColumn();
|
||||
}
|
||||
}
|
||||
|
||||
bool ImGui_Slider(const char* name, mjtNum* value, mjtNum min, mjtNum max) {
|
||||
float f = *value;
|
||||
const bool res = ImGui::SliderFloat(name, &f, min, max);
|
||||
|
||||
@@ -15,11 +15,13 @@
|
||||
#ifndef MUJOCO_SRC_EXPERIMENTAL_PLATFORM_IMGUI_WIDGETS_H_
|
||||
#define MUJOCO_SRC_EXPERIMENTAL_PLATFORM_IMGUI_WIDGETS_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
@@ -112,6 +114,12 @@ struct ScopedStyle {
|
||||
return *this;
|
||||
}
|
||||
|
||||
ScopedStyle& Color(ImGuiCol col, ImGuiCol col2) {
|
||||
ImGui::PushStyleColor(col, CurrentColor(col2));
|
||||
++num_colors;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ScopedStyle& Var(ImGuiStyleVar var, float value) {
|
||||
ImGui::PushStyleVar(var, value);
|
||||
++num_vars;
|
||||
@@ -124,6 +132,10 @@ struct ScopedStyle {
|
||||
return *this;
|
||||
}
|
||||
|
||||
ImVec4 CurrentColor(ImGuiCol col) {
|
||||
return ImGui::GetStyle().Colors[col];
|
||||
}
|
||||
|
||||
void Reset() {
|
||||
ImGui::PopStyleVar(num_vars);
|
||||
ImGui::PopStyleColor(num_colors);
|
||||
@@ -135,6 +147,74 @@ struct ScopedStyle {
|
||||
int num_vars = 0;
|
||||
};
|
||||
|
||||
// Helper for displaying rows of key/value pairs in an ImGui table.
|
||||
//
|
||||
// Designed specifically to be used to display mjSpec, mjModel, and mjData
|
||||
// values.
|
||||
//
|
||||
// To add a value to the table, call the operator() function with the label,
|
||||
// the value, and (optionally) the dimensionality of the value (e.g. for vectors
|
||||
// and matrices). To support generic code, even scalar values should be passed
|
||||
// to operator() with n = 1.
|
||||
class ImGui_DataTable {
|
||||
public:
|
||||
// Starts the table (i.e. ImGui::BeginTable()) with two columns of the
|
||||
// specified widths.
|
||||
ImGui_DataTable(float w1 = 0.25f, float w2 = 0.75f);
|
||||
|
||||
// Ends the table (e.g. ImGui::EndTable().
|
||||
~ImGui_DataTable();
|
||||
|
||||
ImGui_DataTable(const ImGui_DataTable& other) = delete;
|
||||
ImGui_DataTable& operator=(const ImGui_DataTable& other) = delete;
|
||||
|
||||
// Sets the offset into an array of values (e.g. for pointers in mjModel and
|
||||
// mjData). This is only used for the display functions that take a pointer.
|
||||
void SetArrayIndex(int index);
|
||||
|
||||
// Sets the prefix that will be removed from all labels. Note: that we simply
|
||||
// remove the first N characters of the label without actually comparing
|
||||
// against this prefix.
|
||||
void SetPrefix(const char* prefix);
|
||||
|
||||
// Displays a labelled value in the table.
|
||||
void operator()(const char* label, const uintptr_t* ptr, int n);
|
||||
void operator()(const char* label, const char* ptr, int n);
|
||||
void operator()(const char* label, const mjtByte* ptr, int n);
|
||||
void operator()(const char* label, const mjtSize* ptr, int n);
|
||||
void operator()(const char* label, const int* ptr, int n);
|
||||
void operator()(const char* label, const float* ptr, int n);
|
||||
void operator()(const char* label, const double* ptr, int n);
|
||||
|
||||
// Displays a single scalar value in the table. Assumes n == 1. This should
|
||||
// only be used for mjSpec objects and, therefore, will ignore the array index
|
||||
// if set.
|
||||
void operator()(const char* label, const mjtByte& val, int n);
|
||||
void operator()(const char* label, const mjtSize& val, int n);
|
||||
void operator()(const char* label, const int& val, int n);
|
||||
void operator()(const char* label, const float& val, int n);
|
||||
void operator()(const char* label, const double& val, int n);
|
||||
|
||||
// Overloads for C++ container types. Assumes its only used for mjSpec objects
|
||||
// and, therefore, will ignore the array index if set.
|
||||
void operator()(const char* label, const std::string* ptr, int n);
|
||||
void operator()(const char* label, const std::vector<int>* ptr, int n);
|
||||
void operator()(const char* label, const std::vector<double>* ptr, int n);
|
||||
void operator()(const char* label, const std::vector<std::string>* ptr, int n);
|
||||
|
||||
private:
|
||||
template <typename T>
|
||||
void Numeric(const char* label, const T* ptr, int n);
|
||||
|
||||
template <typename T>
|
||||
void Scalar(const char* label, const T& value, int n);
|
||||
|
||||
void MakeLabel(const char* label, int index = 0, int total = 1);
|
||||
|
||||
int prefix_ = 0;
|
||||
int index_ = 0;
|
||||
};
|
||||
|
||||
// ImGui Slider that supports both float and double types.
|
||||
bool ImGui_Slider(const char* name, mjtNum* value, mjtNum min, mjtNum max);
|
||||
|
||||
|
||||
+86
-108
@@ -36,6 +36,7 @@
|
||||
#include <mujoco/mujoco.h>
|
||||
#include "experimental/platform/file_dialog.h"
|
||||
#include "experimental/platform/gui.h"
|
||||
#include "experimental/platform/gui_spec.h"
|
||||
#include "experimental/platform/helpers.h"
|
||||
#include "experimental/platform/imgui_widgets.h"
|
||||
#include "experimental/platform/interaction.h"
|
||||
@@ -132,6 +133,7 @@ void App::ClearModel() {
|
||||
tmp_ = UiTempState();
|
||||
load_error_ = "";
|
||||
step_error_ = "";
|
||||
edit_error_ = "";
|
||||
}
|
||||
|
||||
void App::Recompile() {
|
||||
@@ -257,6 +259,7 @@ void App::ResetPhysics() {
|
||||
mj_resetData(model(), data());
|
||||
mj_forward(model(), data());
|
||||
step_error_ = "";
|
||||
edit_error_ = "";
|
||||
}
|
||||
|
||||
void App::UpdatePhysics() {
|
||||
@@ -419,15 +422,42 @@ void App::ProcessPendingLoads() {
|
||||
});
|
||||
}
|
||||
|
||||
void App::SpecDeleteSelectedElement() {
|
||||
spec_op_ = [this]() {
|
||||
mjs_delete(spec(), tmp_.element);
|
||||
if (tmp_.element->elemtype == mjOBJ_BODY &&
|
||||
perturb_.select == tmp_.element_id) {
|
||||
mjv_defaultPerturb(&perturb_);
|
||||
}
|
||||
tmp_.element = nullptr;
|
||||
void App::SpecSelectElement(mjsElement* element) {
|
||||
tmp_.element = element;
|
||||
if (tmp_.element == nullptr) {
|
||||
tmp_.element_id = -1;
|
||||
} else {
|
||||
tmp_.element_id = mjs_getId(tmp_.element);
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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_);
|
||||
}
|
||||
}
|
||||
Recompile();
|
||||
};
|
||||
}
|
||||
@@ -635,7 +665,7 @@ void App::HandleKeyboardEvents() {
|
||||
} else if (ImGui_IsChordJustPressed(ImGuiKey_Backspace)) {
|
||||
ResetPhysics();
|
||||
} else if (ImGui_IsChordJustPressed(ImGuiKey_Delete)) {
|
||||
SpecDeleteSelectedElement();
|
||||
SpecDeleteElement(tmp_.element);
|
||||
} else if (ImGui_IsChordJustPressed(ImGuiKey_PageUp)) {
|
||||
SelectParentPerturb(model(), perturb_);
|
||||
} else if (ImGui_IsChordJustPressed(ImGuiKey_F1)) {
|
||||
@@ -896,7 +926,7 @@ void App::BuildGui() {
|
||||
|
||||
if (explorer_is_open && tmp_.element != nullptr) {
|
||||
if (ImGui::Begin("Properties")) {
|
||||
PropertiesGui();
|
||||
SpecPropertiesGui();
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
@@ -1129,117 +1159,62 @@ void App::DataInspectorGui() {
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
void DisplayElementTree(mjsElement* element) {
|
||||
const mjString* name = mjs_getName(element);
|
||||
if (name->empty()) {
|
||||
ImGui::Text("(unnamed)");
|
||||
} else {
|
||||
ImGui::Text("%s", name->c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void App::SpecExplorerGui() {
|
||||
if (!has_spec()) {
|
||||
ImGui::Text("No mjSpec loaded.");
|
||||
return;
|
||||
}
|
||||
|
||||
const ImGuiTreeNodeFlags flags =
|
||||
ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_Framed;
|
||||
auto on_delete = [this](mjsElement* element) { SpecDeleteElement(element); };
|
||||
|
||||
auto display_group = [this](mjtObj type, const std::string& prefix,
|
||||
std::function<void()> delete_callback = {}) {
|
||||
mjsElement* element = mjs_firstElement(spec(), type);
|
||||
while (element) {
|
||||
const int id = mjs_getId(element);
|
||||
|
||||
const mjString* name = mjs_getName(element);
|
||||
std::string label = *name;
|
||||
if (label.empty()) {
|
||||
label = "(" + prefix + " " + std::to_string(id) + ")";
|
||||
}
|
||||
|
||||
const bool selected = (tmp_.element == element);
|
||||
if (ImGui::Selectable(label.c_str(), selected,
|
||||
ImGuiSelectableFlags_AllowOverlap)) {
|
||||
tmp_.element = element;
|
||||
tmp_.element_id = id;
|
||||
}
|
||||
|
||||
if (selected && delete_callback) {
|
||||
// Right-align the delete button.
|
||||
const float button_width = ImGui::CalcTextSize(ICON_DELETE).x +
|
||||
ImGui::GetStyle().FramePadding.x * 2.0f;
|
||||
ImGui::SameLine(ImGui::GetWindowContentRegionMax().x - button_width);
|
||||
if (ImGui::SmallButton(ICON_DELETE)) {
|
||||
delete_callback();
|
||||
}
|
||||
}
|
||||
|
||||
element = mjs_nextElement(spec(), element);
|
||||
}
|
||||
};
|
||||
|
||||
if (ImGui::TreeNodeEx("Bodies", flags)) {
|
||||
display_group(mjOBJ_BODY, "Body", [this] { SpecDeleteSelectedElement(); });
|
||||
ImGui::TreePop();
|
||||
}
|
||||
|
||||
if (ImGui::TreeNodeEx("Joints", flags)) {
|
||||
display_group(mjOBJ_JOINT, "Joint");
|
||||
ImGui::TreePop();
|
||||
}
|
||||
|
||||
if (ImGui::TreeNodeEx("Sites", flags)) {
|
||||
display_group(mjOBJ_SITE, "Site");
|
||||
ImGui::TreePop();
|
||||
}
|
||||
|
||||
// If we selected a body, then select the same body for the perturb object.
|
||||
if (tmp_.element && tmp_.element->elemtype == mjOBJ_BODY &&
|
||||
perturb_.select != tmp_.element_id) {
|
||||
mjv_defaultPerturb(&perturb_);
|
||||
perturb_.select = tmp_.element_id;
|
||||
mjsElement* element = tmp_.element;
|
||||
platform::SpecExplorerGui(&element, spec(), on_delete);
|
||||
if (element != tmp_.element) {
|
||||
SpecSelectElement(element);
|
||||
}
|
||||
}
|
||||
|
||||
void App::PropertiesGui() {
|
||||
if (tmp_.element == nullptr) {
|
||||
ImGui::Text("No element selected.");
|
||||
return;
|
||||
}
|
||||
void App::SpecPropertiesGui() {
|
||||
platform::ScopedStyle style;
|
||||
|
||||
if (ImGui::BeginTable("##PropertiesHeader", 2)) {
|
||||
ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch);
|
||||
ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, 20);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%s", mju_type2Str(tmp_.element->elemtype));
|
||||
ImGui::TableNextColumn();
|
||||
if (tmp_.element->elemtype == mjOBJ_BODY) {
|
||||
if (ImGui::SmallButton(ICON_DELETE)) {
|
||||
SpecDeleteSelectedElement();
|
||||
}
|
||||
}
|
||||
ImGui::EndTable();
|
||||
ImGui::Text("%s", mju_type2Str(tmp_.element->elemtype));
|
||||
ImGui::SameLine();
|
||||
ImGui::Text("(%d)", tmp_.element_id);
|
||||
|
||||
ImGui::SameLine(120);
|
||||
if (tmp_.spec_prop_mode == SpecPropertiesMode::kSpec) {
|
||||
style.Color(ImGuiCol_Button, ImGuiCol_ButtonActive);
|
||||
}
|
||||
if (ImGui::SmallButton("S")) {
|
||||
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);
|
||||
}
|
||||
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();
|
||||
|
||||
switch (tmp_.element->elemtype) {
|
||||
case mjOBJ_BODY:
|
||||
platform::BodyPropertiesGui(model(), data(), tmp_.element,
|
||||
tmp_.element_id);
|
||||
break;
|
||||
case mjOBJ_JOINT:
|
||||
platform::JointPropertiesGui(model(), data(), tmp_.element,
|
||||
tmp_.element_id);
|
||||
break;
|
||||
case mjOBJ_SITE:
|
||||
platform::SitePropertiesGui(model(), data(), tmp_.element,
|
||||
tmp_.element_id);
|
||||
break;
|
||||
default:
|
||||
// ignore other types
|
||||
break;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1617,6 +1592,9 @@ void App::StatusBarGui() {
|
||||
} else if (!load_error_.empty()) {
|
||||
ImGui::SameLine();
|
||||
ImGui::Text(" | Load Error: %s", load_error_.c_str());
|
||||
} else if (!edit_error_.empty()) {
|
||||
ImGui::SameLine();
|
||||
ImGui::Text(" | Edit Error: %s", edit_error_.c_str());
|
||||
}
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
|
||||
@@ -91,6 +91,12 @@ class App {
|
||||
kModelFromBuffer,
|
||||
};
|
||||
|
||||
enum class SpecPropertiesMode {
|
||||
kSpec,
|
||||
kModel,
|
||||
kData,
|
||||
};
|
||||
|
||||
// UI state that is persisted across application runs
|
||||
struct UiState {
|
||||
char watch_field[1000] = "qpos";
|
||||
@@ -136,6 +142,7 @@ class App {
|
||||
std::vector<std::string> speed_names;
|
||||
|
||||
// Spec Properties.
|
||||
SpecPropertiesMode spec_prop_mode = SpecPropertiesMode::kSpec;
|
||||
mjsElement* element = nullptr;
|
||||
int element_id = -1;
|
||||
|
||||
@@ -209,9 +216,10 @@ class App {
|
||||
void ModelOptionsGui();
|
||||
void DataInspectorGui();
|
||||
void SpecExplorerGui();
|
||||
void PropertiesGui();
|
||||
void SpecPropertiesGui();
|
||||
|
||||
void SpecDeleteSelectedElement();
|
||||
void SpecSelectElement(mjsElement* element);
|
||||
void SpecDeleteElement(mjsElement* element);
|
||||
|
||||
float GetExpectedLabelWidth();
|
||||
std::vector<const char*> GetCameraNames();
|
||||
@@ -228,6 +236,7 @@ class App {
|
||||
std::string model_path_;
|
||||
std::string load_error_;
|
||||
std::string step_error_;
|
||||
std::string edit_error_;
|
||||
std::optional<std::string> pending_load_;
|
||||
bool preserve_camera_on_load_ = false;
|
||||
ModelKind model_kind_ = kEmptyModel;
|
||||
|
||||
Reference in New Issue
Block a user