Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
46f925a79This file is pinned to the selected release. Download the original to open it in your design tool.
// Copyright 2021 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 "xml/xml_native_reader.h"
#include <array>
#include <cstddef>
#include <cstdio>
#include <cstring>
#include <functional>
#include <limits>
#include <map>
#include <optional>
#include <sstream>
#include <string>
#include <string_view>
#include <type_traits>
#include <utility>
#include <vector>
#include <mujoco/mjmodel.h>
#include <mujoco/mjspec.h>
#include <mujoco/mjtype.h>
#include <mujoco/mjvisualize.h>
#include <mujoco/mujoco.h>
#include "engine/engine_util_misc.h"
#include "user/user_api.h"
#include "user/user_composite.h"
#include "user/user_flexcomp.h"
#include "user/user_objects.h"
#include "user/user_util.h"
#include "xml/xml_base.h"
#include "xml/xml_util.h"
#include "tinyxml2.h"
namespace {
using mujoco::user::FilePath;
using std::string;
using std::string_view;
using std::vector;
using tinyxml2::XMLElement;
using tinyxml2::XMLText;
//---------------------------------- helper utilities ----------------------------------------------
// GetAttrPtr: overload for scalar and pointer fields
template <typename T>
inline auto GetAttrPtr(T& val) -> std::enable_if_t<!std::is_array_v<T>, decltype(&val)> {
if constexpr (std::is_pointer_v<T>) {
return val;
} else {
return &val;
}
}
// GetAttrPtr: overload for array fields
template <typename T, size_t N>
inline T* GetAttrPtr(T (&arr)[N]) {
return arr;
}
// helper class for reading attributes while recording authored bits
struct Reader {
Reader(XMLElement* xml_node, const void* elem) : xml_node_(xml_node), elem_(elem) {}
template <typename T>
int operator()(const char* attr, int len, T& data, bool required = false, bool exact = true) {
int res = mjXReader::ReadAttr(xml_node_, attr, len, GetAttrPtr(data), text_, required, exact);
if (res) mjs_setAuthored(elem_, &data, 1);
return res;
}
template <typename T>
bool operator()(const char* attr, T& data, const mjMap* map, int mapsz, bool required = false) {
int map_val_temp;
bool res = mjXReader::MapValue(xml_node_, attr, &map_val_temp, map, mapsz, required);
if (res) {
data = static_cast<T>(map_val_temp);
mjs_setAuthored(elem_, &data, 1);
}
return res;
}
bool operator()(const char* attr, int& data, bool required = false) {
bool res = mjXUtil::ReadAttrInt(xml_node_, attr, &data, required);
if (res) mjs_setAuthored(elem_, &data, 1);
return res;
}
bool operator()(const char* attr, mjString* target) {
std::string txt_temp;
bool res = mjXUtil::ReadAttrTxt(xml_node_, attr, txt_temp);
if (res) {
mjs_setString(target, txt_temp.c_str());
mjs_setAuthored(elem_, target, 1);
}
return res;
}
template <typename T>
bool txt(const char* attr, T& target, void (&set_func)(T&, const char*)) {
std::string txt_temp;
bool res = mjXUtil::ReadAttrTxt(xml_node_, attr, txt_temp);
if (res) {
set_func(target, txt_temp.c_str());
mjs_setAuthored(elem_, &target, 1);
}
return res;
}
void set_node(XMLElement* node) { xml_node_ = node; }
XMLElement* xml_node_;
const void* elem_;
std::string text_;
};
void ReadPluginConfigs(tinyxml2::XMLElement* elem, mjsPlugin* p) {
std::map<string, string, std::less<> > config_attribs;
XMLElement* child = FirstChildElement(elem);
while (child) {
string_view name = child->Value();
if (name == "config") {
string key, value;
mjXUtil::ReadAttrTxt(child, "key", key, /* required = */ true);
if (config_attribs.find(key) != config_attribs.end()) {
string err = "duplicate config key: " + key;
throw mjXError(child, "%s", err.c_str());
}
mjXUtil::ReadAttrTxt(child, "value", value, /* required = */ true);
config_attribs[key] = value;
}
child = NextSiblingElement(child);
}
if (!p && !config_attribs.empty()) {
throw mjXError(elem,
"plugin configuration attributes cannot be used in an "
"element that references a predefined plugin instance");
} else if (p) {
mjs_setPluginAttributes(p, &config_attribs);
}
}
static void UpdateString(string& psuffix, int count, int i) {
int ndigits = std::to_string(count).length();
string i_string = std::to_string(i);
string prefix = "";
while (ndigits-- > i_string.length()) { prefix += '0'; }
psuffix += prefix + i_string;
}
} // namespace
//---------------------------------- MJCF schema ---------------------------------------------------
#include "xml/generated/mjcf_table.inc"
//---------------------------------- class mjXReader implementation --------------------------------
// typed attribute rows, generated from mjcf.schema; the keyword maps the
// rows reference are generated into mjcf_map.h
#include "xml/generated/mjcf_read_table.inc"
mjXReader::mjXReader() : schema(MJCF, nMJCF, MJCF_constraints, nMJCF_constraints) {
readingdefaults = false;
}
// print schema
void mjXReader::PrintSchema(std::stringstream& str, bool html, bool pad) {
if (html) {
schema.PrintHTML(str, 0, pad);
} else {
schema.Print(str, 0);
}
}
// main entry point for XML parser
// mjCModel is allocated here; caller is responsible for deallocation
void mjXReader::Parse(XMLElement* root, const mjVFS* vfs) {
// check schema
if (!schema.GetError().empty()) {
throw mjXError(0, "XML Schema Construction Error: %s", schema.GetError().c_str());
}
// validate
XMLElement* bad = 0;
if ((bad = schema.Check(root, 0))) {
throw mjXError(bad, "Schema violation: %s", schema.GetError().c_str());
}
// get model name
string modelname;
if (ReadAttrTxt(root, "model", modelname)) { mjs_setString(spec->modelname, modelname.c_str()); }
// get comment
if (root->FirstChild() && root->FirstChild()->ToComment()) {
mjs_setString(spec->comment, root->FirstChild()->Value());
} else {
mjs_setString(spec->comment, "");
}
//------------------- parse MuJoCo sections embedded in all XML formats
for (XMLElement* section = FirstChildElement(root, "compiler"); section;
section = NextSiblingElement(section, "compiler")) {
Compiler(section, spec);
}
for (XMLElement* section = FirstChildElement(root, "option"); section;
section = NextSiblingElement(section, "option")) {
Option(section, spec, &spec->option);
}
for (XMLElement* section = FirstChildElement(root, "size"); section;
section = NextSiblingElement(section, "size")) {
Size(section, spec);
}
//------------------ parse MJCF-specific sections
for (XMLElement* section = FirstChildElement(root, "visual"); section;
section = NextSiblingElement(section, "visual")) {
Visual(section);
}
for (XMLElement* section = FirstChildElement(root, "statistic"); section;
section = NextSiblingElement(section, "statistic")) {
Statistic(section);
}
readingdefaults = true;
for (XMLElement* section = FirstChildElement(root, "default"); section;
section = NextSiblingElement(section, "default")) {
Default(section, nullptr, vfs);
}
readingdefaults = false;
for (XMLElement* section = FirstChildElement(root, "extension"); section;
section = NextSiblingElement(section, "extension")) {
Extension(section);
}
for (XMLElement* section = FirstChildElement(root, "custom"); section;
section = NextSiblingElement(section, "custom")) {
Custom(section);
}
for (XMLElement* section = FirstChildElement(root, "asset"); section;
section = NextSiblingElement(section, "asset")) {
Asset(section, vfs);
}
for (XMLElement* section = FirstChildElement(root, "contact"); section;
section = NextSiblingElement(section, "contact")) {
Contact(section);
}
for (XMLElement* section = FirstChildElement(root, "deformable"); section;
section = NextSiblingElement(section, "deformable")) {
Deformable(section, vfs);
}
for (XMLElement* section = FirstChildElement(root, "equality"); section;
section = NextSiblingElement(section, "equality")) {
Equality(section);
}
for (XMLElement* section = FirstChildElement(root, "tendon"); section;
section = NextSiblingElement(section, "tendon")) {
Tendon(section);
}
for (XMLElement* section = FirstChildElement(root, "actuator"); section;
section = NextSiblingElement(section, "actuator")) {
Actuator(section);
}
for (XMLElement* section = FirstChildElement(root, "sensor"); section;
section = NextSiblingElement(section, "sensor")) {
Sensor(section);
}
for (XMLElement* section = FirstChildElement(root, "keyframe"); section;
section = NextSiblingElement(section, "keyframe")) {
Keyframe(section);
}
// set deepcopy flag to true to copy child specs during attach calls
mjs_setDeepCopy(spec, true);
for (XMLElement* section = FirstChildElement(root, "worldbody"); section;
section = NextSiblingElement(section, "worldbody")) {
Body(section, mjs_findBody(spec, "world"), nullptr, vfs);
}
// set deepcopy flag to false to disable copying during attach in all future calls
mjs_setDeepCopy(spec, false);
}
// compiler section parser
void mjXReader::Compiler(XMLElement* section, mjSpec* s) {
Reader read(section, s);
// strippath is stored on the spec, not the compiler options
read("strippath", s->strippath, bool_map, 2);
// global coordinates are no longer supported
if (int n = 0; MapValue(section, "coordinate", &n, coordinate_map, 2)) {
if (n == 1) {
throw mjXError(section,
"global coordinates no longer supported. To convert existing models, "
"load and save them in MuJoCo 2.3.3 or older");
}
}
if (ReadAttrTxt(section, "assetdir", read.text_)) {
mjs_setString(s->compiler.meshdir, read.text_.c_str());
mjs_setString(s->compiler.texturedir, read.text_.c_str());
}
// mechanical attributes; meshdir/texturedir override assetdir above
ReadAttrTableCore(section,
&s->compiler,
kCompilerAttrs,
kCompilerAttrsN,
/*skipnodefault=*/false,
/*authored=*/s);
// lengthrange subelement
XMLElement* elem = FindSubElem(section, "lengthrange");
if (elem) {
ReadAttrTableCore(elem,
&(s->compiler.LRopt),
kLengthrangeAttrs,
kLengthrangeAttrsN,
/*skipnodefault=*/false);
}
}
// option section parser
void mjXReader::Option(XMLElement* section, mjSpec* s, mjOption* opt) {
Reader read(section, s);
// mechanical attributes
ReadAttrTableCore(section,
opt,
kOptionAttrs,
kOptionAttrsN,
/*skipnodefault=*/false,
/*authored=*/s);
// actuatorgroupdisable
constexpr int num_bitflags = 31;
int disabled_act_groups[num_bitflags];
int num_found = read("actuatorgroupdisable", num_bitflags, disabled_act_groups, false, false);
for (int i = 0; i < num_found; i++) {
int group = disabled_act_groups[i];
if (group < 0) {
throw mjXError(section, "disabled actuator group value must be non-negative");
}
if (group > num_bitflags - 1) {
throw mjXError(section, "disabled actuator group value cannot exceed 30");
}
opt->disableactuator |= (1 << group);
s->authored.disableactuator |= (1 << group);
}
// read disable sub-element
XMLElement* elem = FindSubElem(section, "flag");
if (elem) {
int n = 0;
#define READDSBL(NAME, MASK) \
if (MapValue(elem, NAME, &n, enable_map, 2)) { \
opt->disableflags ^= (opt->disableflags & MASK); \
opt->disableflags |= (n ? 0 : MASK); \
s->authored.disableflags |= MASK; \
}
// clang-format off
READDSBL("constraint", mjDSBL_CONSTRAINT)
READDSBL("equality", mjDSBL_EQUALITY)
READDSBL("frictionloss", mjDSBL_FRICTIONLOSS)
READDSBL("limit", mjDSBL_LIMIT)
READDSBL("contact", mjDSBL_CONTACT)
READDSBL("spring", mjDSBL_SPRING)
READDSBL("damper", mjDSBL_DAMPER)
READDSBL("gravity", mjDSBL_GRAVITY)
READDSBL("clampctrl", mjDSBL_CLAMPCTRL)
READDSBL("warmstart", mjDSBL_WARMSTART)
READDSBL("filterparent", mjDSBL_FILTERPARENT)
READDSBL("actuation", mjDSBL_ACTUATION)
READDSBL("refsafe", mjDSBL_REFSAFE)
READDSBL("sensor", mjDSBL_SENSOR)
READDSBL("midphase", mjDSBL_MIDPHASE)
READDSBL("eulerdamp", mjDSBL_EULERDAMP)
READDSBL("autoreset", mjDSBL_AUTORESET)
READDSBL("nativeccd", mjDSBL_NATIVECCD)
READDSBL("island", mjDSBL_ISLAND)
READDSBL("multiccd", mjDSBL_MULTICCD)
// clang-format on
#undef READDSBL
#define READENBL(NAME, MASK) \
if (MapValue(elem, NAME, &n, enable_map, 2)) { \
opt->enableflags ^= (opt->enableflags & MASK); \
opt->enableflags |= (n ? MASK : 0); \
s->authored.enableflags |= MASK; \
}
// clang-format off
READENBL("override", mjENBL_OVERRIDE)
READENBL("energy", mjENBL_ENERGY)
READENBL("fwdinv", mjENBL_FWDINV)
READENBL("invdiscrete", mjENBL_INVDISCRETE)
READENBL("sleep", mjENBL_SLEEP)
READENBL("diagexact", mjENBL_DIAGEXACT)
READENBL("ipc", mjENBL_IPC)
// clang-format on
#undef READENBL
}
}
// size section parser
void mjXReader::Size(XMLElement* section, mjSpec* s) {
// read memory bytes
{
constexpr char err_msg[] =
"unsigned integer with an optional suffix {K,M,G,T,P,E} is expected in "
"attribute 'memory' (or the size specified is too big)";
auto memory = [&]() -> std::optional<std::size_t> {
const char* pstr = section->Attribute("memory");
if (!pstr) { return std::nullopt; }
// trim entire string
string trimmed;
{
std::istringstream strm((string(pstr)));
strm >> trimmed;
string trailing;
strm >> trailing;
if (!trailing.empty() || !strm.eof()) { throw mjXError(section, "%s", err_msg); }
// allow explicit specification of the default "-1" value
if (trimmed == "-1") { return std::nullopt; }
}
std::istringstream strm(trimmed);
// check that the number is not negative
if (strm.peek() == '-') { throw mjXError(section, "%s", err_msg); }
std::size_t base_size;
strm >> base_size;
if (strm.fail()) {
// either not an integer or the number without the suffix is already bigger than size_t
throw mjXError(section, "%s", err_msg);
}
// parse the multiplier suffix
int multiplier_bit = 0;
if (!strm.eof()) {
char suffix = strm.get();
if (suffix == 'K' || suffix == 'k') {
multiplier_bit = 10;
} else if (suffix == 'M' || suffix == 'm') {
multiplier_bit = 20;
} else if (suffix == 'G' || suffix == 'g') {
multiplier_bit = 30;
} else if (suffix == 'T' || suffix == 't') {
multiplier_bit = 40;
} else if (suffix == 'P' || suffix == 'p') {
multiplier_bit = 50;
} else if (suffix == 'E' || suffix == 'e') {
multiplier_bit = 60;
}
// check for invalid suffix, or suffix longer than one character
strm.get();
if (!multiplier_bit || !strm.eof()) { throw mjXError(section, "%s", err_msg); }
}
// check that the specified suffix isn't bigger than size_t
if (multiplier_bit + 1 > std::numeric_limits<std::size_t>::digits) {
throw mjXError(section, "%s", err_msg);
}
// check that the suffix won't take the total size beyond size_t
const std::size_t max_base_size =
(std::numeric_limits<std::size_t>::max() << multiplier_bit) >> multiplier_bit;
if (base_size > max_base_size) { throw mjXError(section, "%s", err_msg); }
const std::size_t total_size = base_size << multiplier_bit;
return total_size;
}();
if (memory.has_value()) {
if (*memory / sizeof(mjtNum) > std::numeric_limits<std::size_t>::max()) {
throw mjXError(section, "%s", err_msg);
}
s->memory = *memory;
}
}
// mechanical attributes
ReadAttrTableCore(section,
s,
kSizeAttrs,
kSizeAttrsN,
/*skipnodefault=*/false);
ReadAttrInt(section, "nconmax", &s->nconmax);
if (s->nconmax < -1) throw mjXError(section, "nconmax must be >= -1");
// memory/nstack and memory/njmax exclusivity is enforced by the schema
{
int nstack = -1;
if (ReadAttrInt(section, "nstack", &nstack)) {
if (nstack < -1) throw mjXError(section, "nstack must be >= -1");
s->nstack = nstack;
}
}
if (ReadAttrInt(section, "njmax", &s->njmax)) {
if (s->njmax < -1) throw mjXError(section, "njmax must be >= -1");
}
if (s->nuser_body < -1) throw mjXError(section, "nuser_body must be >= -1");
if (s->nuser_jnt < -1) throw mjXError(section, "nuser_jnt must be >= -1");
if (s->nuser_geom < -1) throw mjXError(section, "nuser_geom must be >= -1");
if (s->nuser_site < -1) throw mjXError(section, "nuser_site must be >= -1");
if (s->nuser_cam < -1) throw mjXError(section, "nuser_cam must be >= -1");
if (s->nuser_tendon < -1) throw mjXError(section, "nuser_tendon must be >= -1");
if (s->nuser_actuator < -1) throw mjXError(section, "nuser_actuator must be >= -1");
if (s->nuser_sensor < -1) throw mjXError(section, "nuser_sensor must be >= -1");
}
// statistic section parser
void mjXReader::Statistic(XMLElement* section) {
// mechanical attributes
ReadAttrTableCore(section,
&spec->stat,
kStatisticAttrs,
kStatisticAttrsN,
/*skipnodefault=*/false);
if (mjuu_defined(spec->stat.extent) && spec->stat.extent <= 0) {
throw mjXError(section, "extent must be strictly positive");
}
}
//---------------------------------- one-element parsers -------------------------------------------
// flex element parser
void mjXReader::OneFlex(XMLElement* elem, mjsFlex* flex) {
string text;
int n;
// mechanical attributes
ReadAttrTable(elem, flex, flex->element, kFlexAttrs, kFlexAttrsN);
// cellcount is seeded before reading
flex->cellcount[0] = 1;
flex->cellcount[1] = 1;
flex->cellcount[2] = 1;
ReadAttr(elem, "cellcount", 3, flex->cellcount, text);
// dof lowers to interpolation order
flex->order = 0;
if (MapValue(elem, "dof", &n, fdof_map, mjNFCOMPDOFS)) {
flex->order = (n == mjFCOMPDOF_QUADRATIC) ? 2 : (n == mjFCOMPDOF_TRILINEAR ? 1 : 0);
}
// sub-elements project into the flex
XMLElement* cont = FirstChildElement(elem, "contact");
if (cont) {
ReadAttrTable(cont, flex, flex->element, kFlexcomp_contactAttrs, kFlexcomp_contactAttrsN);
}
XMLElement* edge = FirstChildElement(elem, "edge");
if (edge) { ReadAttrTable(edge, flex, flex->element, kFlex_edgeAttrs, kFlex_edgeAttrsN); }
XMLElement* elasticity = FirstChildElement(elem, "elasticity");
if (elasticity) {
ReadAttrTable(elasticity, flex, flex->element, kElasticityAttrs, kElasticityAttrsN);
}
// write error info
mjs_setString(flex->info, ("line " + std::to_string(elem->GetLineNum())).c_str());
}
// mesh element parser
void mjXReader::OneMesh(XMLElement* elem, mjsMesh* mesh, const mjVFS* vfs) {
int n;
string text;
// mechanical attributes
ReadAttrTable(elem, mesh, mesh->element, kMeshAttrs, kMeshAttrsN);
// file, resolved against the mesh directory
auto file = ReadAttrFile(elem, "file", vfs, MeshDir());
if (file) { mjs_setString(mesh->file, file->c_str()); }
// plugin sub-element
XMLElement* eplugin = FirstChildElement(elem, "plugin");
if (eplugin) { OnePlugin(eplugin, &mesh->plugin); }
// maxhullvert with validation
if (ReadAttrInt(elem, "maxhullvert", &n)) {
if (n != -1 && n < 4) throw mjXError(elem, "maxhullvert must be larger than 3");
mesh->maxhullvert = n;
}
// builtin options
if (MapValue(elem, "builtin", &n, meshbuiltin_map, meshbuiltin_sz)) {
std::vector<double> params;
int nparams = ReadVector(elem, "params", params, text, /*required*/ true);
if (mjs_makeMesh(mesh, (mjtMeshBuiltin)n, params.data(), nparams)) {
throw mjXError(elem, "%s", mjs_getError(spec));
}
}
// write error info
mjs_setString(mesh->info, ("line " + std::to_string(elem->GetLineNum())).c_str());
}
// skin element parser
void mjXReader::OneSkin(XMLElement* elem, mjsSkin* skin, const mjVFS* vfs) {
string text;
float data[4];
// mechanical attributes
ReadAttrTable(elem, skin, skin->element, kSkinAttrs, kSkinAttrsN);
// file, resolved against the asset directory
auto file = ReadAttrFile(elem, "file", vfs, AssetDir());
if (file.has_value()) { mjs_setString(skin->file, file->c_str()); }
// group with range validation
ReadAttrInt(elem, "group", &skin->group);
if (skin->group < 0 || skin->group >= mjNGROUP) {
throw mjXError(elem, "skin group must be between 0 and 5");
}
// read bones
XMLElement* bone = FirstChildElement(elem, "bone");
std::vector<float> bindpos;
std::vector<float> bindquat;
while (bone) {
// read body
ReadAttrTxt(bone, "body", text, true);
mjs_appendString(skin->bodyname, text.c_str());
// read bindpos
ReadAttr(bone, "bindpos", 3, data, text, true);
bindpos.push_back(data[0]);
bindpos.push_back(data[1]);
bindpos.push_back(data[2]);
// read bindquat
ReadAttr(bone, "bindquat", 4, data, text, true);
bindquat.push_back(data[0]);
bindquat.push_back(data[1]);
bindquat.push_back(data[2]);
bindquat.push_back(data[3]);
// read vertid
auto tempid = ReadAttrVec<int>(bone, "vertid", true);
if (tempid.has_value()) { mjs_appendIntVec(skin->vertid, tempid->data(), tempid->size()); }
// read vertweight
auto tempweight = ReadAttrVec<float>(bone, "vertweight", true);
if (tempweight.has_value()) {
mjs_appendFloatVec(skin->vertweight, tempweight->data(), tempweight->size());
}
// advance to next bone
bone = NextSiblingElement(bone, "bone");
}
// set bind vectors
mjs_setFloat(skin->bindpos, bindpos.data(), bindpos.size());
mjs_setFloat(skin->bindquat, bindquat.data(), bindquat.size());
// write error info
mjs_setString(skin->info, ("line " + std::to_string(elem->GetLineNum())).c_str());
}
// material element parser
void mjXReader::OneMaterial(XMLElement* elem, mjsMaterial* material) {
string text, texture;
// mechanical attributes
ReadAttrTable(elem, material, material->element, kMaterialAttrs, kMaterialAttrsN);
// the texture attribute and layer sub-elements are mutually exclusive
bool tex_attributes_found = false;
if (ReadAttrTxt(elem, "texture", texture)) {
mjs_setInStringVec(material->textures, mjTEXROLE_RGB, texture.c_str());
tex_attributes_found = true;
}
XMLElement* layer = FirstChildElement(elem);
while (layer) {
if (tex_attributes_found) {
throw mjXError(layer, "A material with a texture attribute cannot have layer sub-elements");
}
// layer sub-element
ReadAttrTxt(layer, "role", text, true);
int role = FindKey(texrole_map, texrole_sz, text);
ReadAttrTxt(layer, "texture", text, true);
mjs_setInStringVec(material->textures, role, text.c_str());
layer = NextSiblingElement(layer);
}
// write error info
mjs_setString(material->info, ("line " + std::to_string(elem->GetLineNum())).c_str());
}
// joint element parser
void mjXReader::OneJoint(XMLElement* elem, mjsJoint* joint) {
// mechanical attributes
ReadAttrTable(elem, joint, joint->element, kJointAttrs, kJointAttrsN);
// write error info
mjs_setString(joint->info, ("line " + std::to_string(elem->GetLineNum())).c_str());
}
// geom element parser
void mjXReader::OneGeom(XMLElement* elem, mjsGeom* geom) {
string text;
int n;
// mechanical attributes
ReadAttrTable(elem, geom, geom->element, kGeomAttrs, kGeomAttrsN);
// fluid interaction model: keyword lowers to on/off
if (MapValue(elem, "fluidshape", &n, fluidshape_map, 2)) { geom->fluid_ellipsoid = (n == 1); }
// orientation alternatives
ReadQuat(elem, "quat", geom->quat, text);
ReadAlternative(elem, geom->alt);
// plugin sub-element
XMLElement* eplugin = FirstChildElement(elem, "plugin");
if (eplugin) { OnePlugin(eplugin, &geom->plugin); }
// write error info
mjs_setString(geom->info, ("line " + std::to_string(elem->GetLineNum())).c_str());
}
// site element parser
void mjXReader::OneSite(XMLElement* elem, mjsSite* site) {
string text;
// mechanical attributes
ReadAttrTable(elem, site, site->element, kSiteAttrs, kSiteAttrsN);
// orientation alternatives
ReadQuat(elem, "quat", site->quat, text);
ReadAlternative(elem, site->alt);
// write error info
mjs_setString(site->info, ("line " + std::to_string(elem->GetLineNum())).c_str());
}
// camera element parser
void mjXReader::OneCamera(XMLElement* elem, mjsCamera* camera) {
string text;
// mechanical attributes
ReadAttrTable(elem, camera, camera->element, kCameraAttrs, kCameraAttrsN);
// orientation alternatives
ReadQuat(elem, "quat", camera->quat, text);
ReadAlternative(elem, camera->alt);
// write error info
mjs_setString(camera->info, ("line " + std::to_string(elem->GetLineNum())).c_str());
}
// light element parser
void mjXReader::OneLight(XMLElement* elem, mjsLight* light) {
int n;
// mechanical attributes
ReadAttrTable(elem, light, light->element, kLightAttrs, kLightAttrsN);
// directional is a legacy alias for type; the pair is mutually exclusive
if (MapValue(elem, "directional", &n, bool_map, 2)) {
light->type = (n == 1) ? mjLIGHT_DIRECTIONAL : mjLIGHT_SPOT;
}
if (MapValue(elem, "type", &n, lighttype_map, lighttype_sz)) { light->type = (mjtLightType)n; }
// write error info
mjs_setString(light->info, ("line " + std::to_string(elem->GetLineNum())).c_str());
}
// pair element parser
// read the mechanical attributes of an element, driven by its generated rows
void mjXReader::ReadAttrTable(
XMLElement* elem, void* obj, mjsElement* el, const mjXAttr* rows, int nrow) {
// element names need the spec for error context; the core handles the rest
string text;
for (int i = 0; i < nrow; i++) {
const mjXAttr& row = rows[i];
if (row.kind != mjXAttr::kName || (readingdefaults && row.nodefault)) { continue; }
if (ReadAttrTxt(elem, row.attr, text, row.required) && mjs_setName(el, text.c_str())) {
throw mjXError(elem, "%s", mjs_getError(spec));
}
}
ReadAttrTableCore(elem, obj, rows, nrow, readingdefaults);
}
// static core: every row kind except element names
void mjXReader::ReadAttrTableCore(XMLElement* elem,
void* obj,
const mjXAttr* rows,
int nrow,
bool skipnodefault,
const void* authored) {
string text;
for (int i = 0; i < nrow; i++) {
const mjXAttr& row = rows[i];
if (skipnodefault && row.nodefault) { continue; }
char* base = (char*)obj + row.offset;
bool got = false;
switch (row.kind) {
case mjXAttr::kName:
break; // handled by the member wrapper
case mjXAttr::kString:
if (ReadAttrTxt(elem, row.attr, text, row.required)) {
mjs_setString(*(mjString**)base, text.c_str());
got = true;
}
break;
case mjXAttr::kStringVec:
if (ReadAttrTxt(elem, row.attr, text, row.required)) {
mjs_setStringVec(*(mjStringVec**)base, text.c_str());
got = true;
}
break;
case mjXAttr::kInt:
got = ReadAttr(elem, row.attr, row.len, (int*)base, text, row.required, row.exact) > 0;
break;
case mjXAttr::kDouble:
got = ReadAttr(elem, row.attr, row.len, (double*)base, text, row.required, row.exact) > 0;
break;
case mjXAttr::kNum:
got = ReadAttr(elem, row.attr, row.len, (mjtNum*)base, text, row.required, row.exact) > 0;
break;
case mjXAttr::kFloat:
got = ReadAttr(elem, row.attr, row.len, (float*)base, text, row.required, row.exact) > 0;
break;
case mjXAttr::kEnum: {
int value;
if (MapValue(elem, row.attr, &value, row.map, row.mapsz, row.required)) {
*(int*)base = value; // enum fields are int-sized
got = true;
}
break;
}
case mjXAttr::kFlags: {
std::vector<int> values(row.mapsz);
int nvalues = MapValues(elem, row.attr, values.data(), row.map, row.mapsz);
if (nvalues) {
int combined = 0;
for (int j = 0; j < nvalues; j++) { combined |= values[j]; }
*(int*)base = combined;
got = true;
}
break;
}
case mjXAttr::kEnumByte: {
int value;
if (MapValue(elem, row.attr, &value, row.map, row.mapsz, row.required)) {
*(mjtByte*)base = (mjtByte)value;
got = true;
}
break;
}
case mjXAttr::kBool: {
int value;
if (MapValue(elem, row.attr, &value, bool_map, 2, row.required)) {
*(mjtBool*)base = (value == 1);
got = true;
}
break;
}
case mjXAttr::kConst:
*(int*)base = row.value; // enum fields are int-sized
break;
case mjXAttr::kDoubleVec: {
std::vector<double> values;
if (ReadVector(elem, row.attr, values, text)) {
mjs_setDouble(*(mjDoubleVec**)base, values.data(), values.size());
got = true;
}
break;
}
case mjXAttr::kFloatVec: {
auto values = ReadAttrVec<float>(elem, row.attr, row.required);
if (values.has_value()) {
mjs_setFloat(*(mjFloatVec**)base, values->data(), values->size());
got = true;
}
break;
}
case mjXAttr::kIntVec: {
auto values = ReadAttrVec<int>(elem, row.attr, row.required);
if (values.has_value()) {
mjs_setInt(*(mjIntVec**)base, values->data(), values->size());
got = true;
}
break;
}
case mjXAttr::kChars:
if (ReadAttrTxt(elem, row.attr, text, row.required)) {
if (row.exact && (int)text.size() != row.len) {
throw mjXError(elem,
"attribute '%s' must have exactly %d "
"characters",
row.attr,
row.len);
}
if ((int)text.size() > row.len) {
throw mjXError(elem,
"attribute '%s' may have at most %d "
"characters",
row.attr,
row.len);
}
std::memcpy(base, text.data(), text.size());
got = true;
}
break;
}
// record XML-authored fields for attach conflict resolution
if (got && authored) { mjs_setAuthored(authored, base, 1); }
}
}
void mjXReader::OnePair(XMLElement* elem, mjsPair* pair) {
ReadAttrTable(elem, pair, pair->element, kPairAttrs, kPairAttrsN);
// write error info
mjs_setString(pair->info, ("line " + std::to_string(elem->GetLineNum())).c_str());
}
// equality element parser
void mjXReader::OneEquality(XMLElement* elem, mjsEquality* equality) {
string text, name1, name2;
// read type (bad keywords already detected by schema)
text = elem->Value();
equality->type = (mjtEq)FindKey(equality_map, equality_sz, text);
// common attributes
ReadAttrTable(elem, equality, equality->element, kEqualityBaseAttrs, kEqualityBaseAttrsN);
// regular only
if (!readingdefaults) {
switch (equality->type) {
case mjEQ_CONNECT: {
auto maybe_site1 = ReadAttrStr(elem, "site1");
auto maybe_site2 = ReadAttrStr(elem, "site2");
auto maybe_body1 = ReadAttrStr(elem, "body1");
auto maybe_body2 = ReadAttrStr(elem, "body2");
bool has_anchor = ReadAttr(elem, "anchor", 3, equality->data, text);
bool body_semantic = maybe_body1.has_value() && has_anchor;
if (body_semantic) {
name1 = maybe_body1.value();
if (maybe_body2.has_value()) { name2 = maybe_body2.value(); }
equality->objtype = mjOBJ_BODY;
} else {
name1 = maybe_site1.value();
name2 = maybe_site2.value();
equality->objtype = mjOBJ_SITE;
}
} break;
case mjEQ_WELD: {
auto maybe_site1 = ReadAttrStr(elem, "site1");
auto maybe_site2 = ReadAttrStr(elem, "site2");
auto maybe_body1 = ReadAttrStr(elem, "body1");
auto maybe_body2 = ReadAttrStr(elem, "body2");
bool has_anchor = ReadAttr(elem, "anchor", 3, equality->data, text);
ReadAttr(elem, "relpose", 7, equality->data + 3, text);
bool body_semantic = maybe_body1.has_value();
if (body_semantic) {
name1 = maybe_body1.value();
if (maybe_body2.has_value()) { name2 = maybe_body2.value(); }
equality->objtype = mjOBJ_BODY;
if (!has_anchor) { mjuu_zerovec(equality->data, 3); }
} else {
name1 = maybe_site1.value();
name2 = maybe_site2.value();
equality->objtype = mjOBJ_SITE;
}
ReadAttr(elem, "torquescale", 1, equality->data + 10, text);
} break;
case mjEQ_JOINT:
ReadAttrTxt(elem, "joint1", name1, true);
ReadAttrTxt(elem, "joint2", name2);
ReadAttr(elem, "polycoef", 5, equality->data, text, false, false);
break;
case mjEQ_TENDON:
ReadAttrTxt(elem, "tendon1", name1, true);
ReadAttrTxt(elem, "tendon2", name2);
ReadAttr(elem, "polycoef", 5, equality->data, text, false, false);
break;
case mjEQ_FLEX:
case mjEQ_FLEXVERT:
ReadAttrTxt(elem, "flex", name1, true);
break;
case mjEQ_FLEXSTRAIN:
ReadAttrTxt(elem, "flex", name1, true);
ReadAttr(elem, "cell", 3, equality->data, text);
break;
case mjEQ_DISTANCE:
throw mjXError(elem,
"support for distance equality constraints was removed in MuJoCo 2.2.2");
break;
default: // SHOULD NOT OCCUR
throw mjXError(elem, "unrecognized equality constraint type");
}
mjs_setString(equality->name1, name1.c_str());
if (!name2.empty()) { mjs_setString(equality->name2, name2.c_str()); }
}
// write error info
mjs_setString(equality->info, ("line " + std::to_string(elem->GetLineNum())).c_str());
}
// tendon element parser
void mjXReader::OneTendon(XMLElement* elem, mjsTendon* tendon) {
string text;
// mechanical attributes; fixed tendons read a subset of the spatial rows
ReadAttrTable(elem, tendon, tendon->element, kSpatialAttrs, kSpatialAttrsN);
// read springlength, either one or two values; if one, copy to second value
if (ReadAttr(elem, "springlength", 2, tendon->springlength, text, false, false) == 1) {
tendon->springlength[1] = tendon->springlength[0];
}
// write error info
mjs_setString(tendon->info, ("line " + std::to_string(elem->GetLineNum())).c_str());
}
// read the "input" attribute: so3 chart keyword, the "none" keyword (empty signature),
// or a servo input token list, required to be in canonical order [pos, vel, ff, voltage]
static bool ReadInputSpec(tinyxml2::XMLElement* elem, int* ctrlspec) {
std::string text;
if (!mjXUtil::ReadAttrTxt(elem, "input", text)) { return false; }
// so3 chart keyword
int chart = mjXUtil::FindKey(inputchart_map, inputchart_sz, text);
if (chart >= 0) {
*ctrlspec = chart;
return true;
}
// empty-signature keyword
int keyword = mjXUtil::FindKey(inputkeyword_map, inputkeyword_sz, text);
if (keyword >= 0) {
*ctrlspec = keyword;
return true;
}
// servo input tokens; strictly ascending bits = canonical order, no duplicates
int bits[inputbit_sz];
int nbit = mjXUtil::MapValues(elem, "input", bits, inputbit_map, inputbit_sz);
int spec = 0;
for (int k = 0; k < nbit; k++) {
if (bits[k] <= (k ? bits[k - 1] : 0)) {
throw mjXError(elem, "inputs must be listed in canonical order [pos, vel, ff, voltage]");
}
spec |= bits[k];
}
*ctrlspec = spec;
return true;
}
// actuator element parser
void mjXReader::OneActuator(XMLElement* elem, mjsActuator* actuator) {
string text, type, target, slidersite, refsite;
// mechanical attributes; per-tag legality is enforced by the schema check
ReadAttrTable(elem, actuator, actuator->element, kGeneralAttrs, kGeneralAttrsN);
// transmission target and type
if (ReadAttrTxt(elem, "joint", target)) {
mjs_setString(actuator->target, target.c_str());
actuator->trntype = mjTRN_JOINT;
}
if (ReadAttrTxt(elem, "jointinparent", target)) {
mjs_setString(actuator->target, target.c_str());
actuator->trntype = mjTRN_JOINTINPARENT;
}
if (ReadAttrTxt(elem, "tendon", target)) {
mjs_setString(actuator->target, target.c_str());
actuator->trntype = mjTRN_TENDON;
}
if (ReadAttrTxt(elem, "cranksite", target)) {
mjs_setString(actuator->target, target.c_str());
actuator->trntype = mjTRN_SLIDERCRANK;
}
if (ReadAttrTxt(elem, "site", target)) {
mjs_setString(actuator->target, target.c_str());
actuator->trntype = mjTRN_SITE;
}
if (ReadAttrTxt(elem, "body", target)) {
mjs_setString(actuator->target, target.c_str());
actuator->trntype = mjTRN_BODY;
}
// slidercrank-specific parameters
int r1 = ReadAttr(elem, "cranklength", 1, &actuator->cranklength, text);
int r2 = ReadAttrTxt(elem, "slidersite", slidersite);
if (r2) { mjs_setString(actuator->slidersite, slidersite.c_str()); }
if ((r1 || r2) &&
actuator->trntype != mjTRN_SLIDERCRANK &&
actuator->trntype != mjTRN_UNDEFINED) {
throw mjXError(elem, "cranklength and slidersite can only be used in slidercrank transmission");
}
// site-specific parameters (refsite)
int r3 = ReadAttrTxt(elem, "refsite", refsite);
if (r3) { mjs_setString(actuator->refsite, refsite.c_str()); }
if (r3 && actuator->trntype != mjTRN_SITE && actuator->trntype != mjTRN_UNDEFINED) {
throw mjXError(elem, "refsite can only be used with site transmission");
}
// get predefined type
type = elem->Value();
// explicit attributes
string err;
if (type == "general") {
// so3 chart keyword or servo token subset; dcmotor accepts the voltage keyword
ReadInputSpec(elem, &actuator->ctrlspec);
}
// direct drive motor
else if (type == "motor") {
err = mjs_setToMotor(actuator);
}
// position or integrated velocity servo
else if (type == "position" || type == "intvelocity") {
double kp = actuator->gainprm[0];
ReadAttr(elem, "kp", 1, &kp, text);
// read kv
double kv_data;
double* kv = &kv_data;
if (!ReadAttr(elem, "kv", 1, kv, text)) { kv = nullptr; }
// read dampratio
double dampratio_data;
double* dampratio = &dampratio_data;
if (!ReadAttr(elem, "dampratio", 1, dampratio, text)) { dampratio = nullptr; }
// read timeconst, set dyntype
double timeconst_data;
double* timeconst = &timeconst_data;
if (!ReadAttr(elem, "timeconst", 1, timeconst, text)) { timeconst = nullptr; }
// handle inheritrange
double inheritrange = actuator->inheritrange;
ReadAttr(elem, "inheritrange", 1, &inheritrange, text);
if (type == "position") {
err = mjs_setToPosition(actuator, kp, kv, dampratio, timeconst, inheritrange);
} else {
err = mjs_setToIntVelocity(actuator, kp, kv, dampratio, timeconst, inheritrange);
}
}
// orientation servo: geodesic PD on an SO3 transmission
else if (type == "orientation") {
double kp = actuator->gainprm[0];
ReadAttr(elem, "kp", 1, &kp, text);
double kv_data;
double* kv = &kv_data;
if (!ReadAttr(elem, "kv", 1, kv, text)) { kv = nullptr; }
double dampratio_data;
double* dampratio = &dampratio_data;
if (!ReadAttr(elem, "dampratio", 1, dampratio, text)) { dampratio = nullptr; }
// input chart: expmap (default) or quat
ReadInputSpec(elem, &actuator->ctrlspec);
err = mjs_setToOrientation(actuator, kp, kv, dampratio, actuator->ctrlspec);
}
// PID servo: inputs are position and velocity setpoints
else if (type == "pid") {
// kp: default inherited via -biasprm[1]
double kp = -actuator->biasprm[1];
ReadAttr(elem, "kp", 1, &kp, text);
double kv_data;
double* kv = &kv_data;
if (!ReadAttr(elem, "kv", 1, kv, text)) { kv = nullptr; }
double dampratio_data;
double* dampratio = &dampratio_data;
if (!ReadAttr(elem, "dampratio", 1, dampratio, text)) { dampratio = nullptr; }
// controller parameters: ki (gainprm[0]), imax (dynprm[0]), slewmax (dynprm[1]); inherited
double ki = actuator->gainprm[0] * (actuator->dyntype == mjDYN_PID);
ReadAttr(elem, "ki", 1, &ki, text);
double imax = actuator->dynprm[0] * (actuator->dyntype == mjDYN_PID);
ReadAttr(elem, "imax", 1, &imax, text);
double slewmax = actuator->dynprm[1] * (actuator->dyntype == mjDYN_PID);
ReadAttr(elem, "slewmax", 1, &slewmax, text);
// input subset selection
ReadInputSpec(elem, &actuator->ctrlspec);
// posrange is an alias of ctrlrange (the position-setpoint input);
// velrange and ffrange are read by the shared rows
ReadAttr(elem, "posrange", 2, actuator->ctrlrange, text);
// handle inheritrange
double inheritrange = actuator->inheritrange;
ReadAttr(elem, "inheritrange", 1, &inheritrange, text);
err = mjs_setToPID(actuator,
kp,
kv,
dampratio,
&ki,
&imax,
&slewmax,
inheritrange,
actuator->ctrlspec);
}
// velocity servo
else if (type == "velocity") {
double kv = actuator->gainprm[0];
ReadAttr(elem, "kv", 1, &kv, text);
err = mjs_setToVelocity(actuator, kv);
}
// damper
else if (type == "damper") {
bool inherited = (actuator->gaintype == mjGAIN_AFFINE);
double kv = inherited ? -actuator->gainprm[2] : 0;
ReadAttr(elem, "kv", 1, &kv, text);
err = mjs_setToDamper(actuator, kv);
}
// cylinder
else if (type == "cylinder") {
double timeconst = actuator->dynprm[0];
double bias[3] = {actuator->biasprm[0], actuator->biasprm[1], actuator->biasprm[2]};
double area = actuator->gainprm[0];
double diameter = -1;
ReadAttr(elem, "timeconst", 1, &timeconst, text);
ReadAttr(elem, "bias", 3, bias, text);
ReadAttr(elem, "area", 1, &area, text);
ReadAttr(elem, "diameter", 1, &diameter, text);
err = mjs_setToCylinder(actuator, timeconst, bias[0], area, diameter);
actuator->biasprm[1] = bias[1];
actuator->biasprm[2] = bias[2];
}
// muscle
else if (type == "muscle") {
double tausmooth = actuator->dynprm[2];
double force = -1, scale = -1, lmin = -1, lmax = -1, vmax = -1, fpmax = -1, fvmax = -1;
double range[2] = {-1, -1}, timeconst[2] = {-1, -1};
ReadAttr(elem, "timeconst", 2, timeconst, text);
ReadAttr(elem, "tausmooth", 1, &tausmooth, text);
ReadAttr(elem, "range", 2, range, text);
ReadAttr(elem, "force", 1, &force, text);
ReadAttr(elem, "scale", 1, &scale, text);
ReadAttr(elem, "lmin", 1, &lmin, text);
ReadAttr(elem, "lmax", 1, &lmax, text);
ReadAttr(elem, "vmax", 1, &vmax, text);
ReadAttr(elem, "fpmax", 1, &fpmax, text);
ReadAttr(elem, "fvmax", 1, &fvmax, text);
err = mjs_setToMuscle(actuator,
timeconst,
tausmooth,
range,
force,
scale,
lmin,
lmax,
vmax,
fpmax,
fvmax);
}
// adhesion
else if (type == "adhesion") {
double gain = actuator->gainprm[0];
ReadAttr(elem, "gain", 1, &gain, text);
ReadAttr(elem, "ctrlrange", 2, actuator->ctrlrange, text);
err = mjs_setToAdhesion(actuator, gain);
}
// DC motor
else if (type == "dcmotor") {
bool inherited = (actuator->gaintype == mjGAIN_DCMOTOR);
double motorconst[2] = {inherited ? actuator->gainprm[1] : 0, 0};
double resistance = inherited ? actuator->gainprm[0] : 0;
double nominal[3] = {0, 0, 0};
double saturation[3] = {0, 0, inherited ? actuator->dynprm[1] : 0};
double controller[6] = {inherited ? actuator->gainprm[4] : 0,
inherited ? actuator->gainprm[5] : 0,
inherited ? actuator->gainprm[6] : 0,
inherited ? actuator->dynprm[7] : 0,
inherited ? actuator->dynprm[8] : 0,
inherited ? actuator->gainprm[7] : 0};
double inductance[2] = {0, inherited ? actuator->dynprm[0] : 0};
double cogging[3] = {inherited ? actuator->biasprm[0] : 0,
inherited ? actuator->biasprm[1] : 0,
inherited ? actuator->biasprm[2] : 0};
double thermal[6] = {inherited ? actuator->dynprm[2] : 0,
inherited ? actuator->dynprm[3] : 0,
0,
inherited ? actuator->gainprm[2] : 0,
inherited ? actuator->gainprm[3] : 0,
inherited ? actuator->dynprm[4] : 0};
double lugre[5] = {inherited ? actuator->dynprm[5] : 0,
inherited ? actuator->dynprm[6] : 0,
inherited ? actuator->biasprm[3] : 0,
inherited ? actuator->biasprm[4] : 0,
inherited ? actuator->biasprm[5] : 0};
int ctrlspec = inherited ? actuator->ctrlspec : 0;
ReadAttr(elem, "motorconst", 2, motorconst, text, false, false);
ReadAttr(elem, "resistance", 1, &resistance, text);
ReadAttr(elem, "nominal", 3, nominal, text, false, false);
ReadAttr(elem, "saturation", 3, saturation, text, false, false);
ReadAttr(elem, "inductance", 2, inductance, text, false, false);
ReadAttr(elem, "cogging", 3, cogging, text, false, false);
ReadAttr(elem, "controller", 6, controller, text, false, false);
ReadAttr(elem, "thermal", 6, thermal, text, false, false);
ReadAttr(elem, "lugre", 5, lugre, text, false, false);
ReadInputSpec(elem, &ctrlspec);
err = mjs_setToDCMotor(actuator,
motorconst,
resistance,
nominal,
saturation,
inductance,
cogging,
controller,
thermal,
lugre,
ctrlspec);
}
else if (type == "plugin") {
OnePlugin(elem, &actuator->plugin);
}
else { // SHOULD NOT OCCUR
throw mjXError(elem, "unrecognized actuator type: %s", type.c_str());
}
// throw error if any of the above failed
if (!err.empty()) { throw mjXError(elem, err.c_str()); }
// write info
mjs_setString(actuator->info, ("line " + std::to_string(elem->GetLineNum())).c_str());
}
// make composite
void mjXReader::OneComposite(XMLElement* elem,
mjsBody* body,
mjsFrame* frame,
const mjsDefault* def) {
string text;
int n;
// create out-of-DOM element
mjCComposite comp;
// common properties
ReadAttrTxt(elem, "prefix", comp.prefix);
if (MapValue(elem, "type", &n, comp_map, mjNCOMPTYPES, true)) { comp.type = (mjtCompType)n; }
ReadAttr(elem, "count", 3, comp.count, text, false, false);
ReadAttr(elem, "offset", 3, comp.offset, text);
ReadAttr(elem, "quat", 4, comp.quat, text);
comp.frame = frame;
// plugin
XMLElement* eplugin = FirstChildElement(elem, "plugin");
if (eplugin) { OnePlugin(eplugin, &comp.plugin); }
// cable
string curves;
ReadAttrTxt(elem, "curve", curves);
if (ReadAttrTxt(elem, "initial", comp.initial) &&
FindKey(initial_map, initial_sz, comp.initial) < 0) {
throw mjXError(elem, "invalid keyword: '%s'", comp.initial.c_str());
}
ReadAttr(elem, "size", 3, comp.size, text, false, false);
auto uservert = ReadAttrVec<float>(elem, "vertex");
if (uservert.has_value()) { comp.uservert = std::move(uservert.value()); }
// process curve string
std::istringstream iss(curves);
int i = 0;
while (iss) {
if (curves.empty()) { break; }
iss >> text;
if (i > 2) { throw mjXError(elem, "The curve array must have a maximum of 3 components"); }
comp.curve[i++] = (mjtCompShape)FindKey(shape_map, mjNCOMPSHAPES, text);
if (comp.curve[i - 1] == -1) {
throw mjXError(elem, "The curve array contains an invalid shape");
}
if (iss.eof()) { break; }
};
// skin
XMLElement* eskin = FirstChildElement(elem, "skin");
if (eskin) {
comp.skin = true;
if (MapValue(eskin, "texcoord", &n, bool_map, 2)) { comp.skintexcoord = (n == 1); }
ReadAttrTxt(eskin, "material", comp.skinmaterial);
ReadAttr(eskin, "rgba", 4, comp.skinrgba, text);
ReadAttr(eskin, "inflate", 1, &comp.skininflate, text);
ReadAttrInt(eskin, "subgrid", &comp.skinsubgrid);
ReadAttrInt(eskin, "group", &comp.skingroup, 0);
if (comp.skingroup < 0 || comp.skingroup >= mjNGROUP) {
throw mjXError(eskin, "skin group must be between 0 and 5");
}
}
// set type-specific defaults
comp.SetDefault();
// geom
XMLElement* egeom = FirstChildElement(elem, "geom");
if (egeom) {
string material;
mjsGeom& dgeom = *comp.def[0].spec.geom;
if (MapValue(egeom, "type", &n, geomtype_map, mjNGEOMTYPES)) { dgeom.type = (mjtGeom)n; }
ReadAttr(egeom, "size", 3, dgeom.size, text, false, false);
ReadAttrInt(egeom, "contype", &dgeom.contype);
ReadAttrInt(egeom, "conaffinity", &dgeom.conaffinity);
ReadAttrInt(egeom, "condim", &dgeom.condim);
ReadAttrInt(egeom, "group", &dgeom.group);
ReadAttrInt(egeom, "priority", &dgeom.priority);
ReadAttr(egeom, "friction", 3, dgeom.friction, text, false, false);
ReadAttr(egeom, "solmix", 1, &dgeom.solmix, text);
ReadAttr(egeom, "solref", mjNREF, dgeom.solref, text, false, false);
ReadAttr(egeom, "solimp", mjNIMP, dgeom.solimp, text, false, false);
ReadAttr(egeom, "margin", 1, &dgeom.margin, text);
ReadAttr(egeom, "gap", 1, &dgeom.gap, text);
ReadAttr(egeom, "surfacevel", 6, dgeom.surfacevel, text, false, false);
ReadAttr(egeom, "adhesion", 1, &dgeom.adhesion, text);
if (ReadAttrTxt(egeom, "material", material)) {
mjs_setString(dgeom.material, material.c_str());
}
ReadAttr(egeom, "rgba", 4, dgeom.rgba, text);
ReadAttr(egeom, "mass", 1, &dgeom.mass, text);
ReadAttr(egeom, "density", 1, &dgeom.density, text);
}
// site
XMLElement* esite = FirstChildElement(elem, "site");
if (esite) {
string material;
mjsSite& dsite = *comp.def[0].spec.site;
ReadAttr(esite, "size", 3, dsite.size, text, false, false);
ReadAttrInt(esite, "group", &dsite.group);
ReadAttrTxt(esite, "material", material);
ReadAttr(esite, "rgba", 4, dsite.rgba, text);
mjs_setString(dsite.material, material.c_str());
}
// joint
XMLElement* ejnt = FirstChildElement(elem, "joint");
while (ejnt) {
// kind
int kind;
MapValue(ejnt, "kind", &kind, jkind_map, 1, true);
// create a new element if this kind already exists
if (comp.add[kind]) {
char error[200];
if (!comp.AddDefaultJoint(error, 200)) { throw mjXError(elem, "%s", error); }
}
comp.add[kind] = true;
// get element
mjsDefault* dspec = &comp.defjoint[(mjtCompKind)kind].back().spec;
mjsJoint& djoint = *dspec->joint;
mjsEquality& dequality = *dspec->equality;
// particle joint
if (MapValue(ejnt, "type", &n, jointtype_map, jointtype_sz)) { djoint.type = (mjtJoint)n; }
ReadAttr(ejnt, "axis", 3, djoint.axis, text);
// solreffix, solimpfix
ReadAttr(ejnt, "solreffix", mjNREF, dequality.solref, text, false, false);
ReadAttr(ejnt, "solimpfix", mjNIMP, dequality.solimp, text, false, false);
// joint attributes
MapValue(elem, "limited", &djoint.limited, FalseTrueAuto_map, 3);
ReadAttrInt(ejnt, "group", &djoint.group);
ReadAttr(ejnt, "solreflimit", mjNREF, djoint.solref_limit, text, false, false);
ReadAttr(ejnt, "solimplimit", mjNIMP, djoint.solimp_limit, text, false, false);
ReadAttr(ejnt, "solreffriction", mjNREF, djoint.solref_friction, text, false, false);
ReadAttr(ejnt, "solimpfriction", mjNIMP, djoint.solimp_friction, text, false, false);
ReadAttr(ejnt, "stiffness", 1, djoint.stiffness, text);
ReadAttr(ejnt, "range", 2, djoint.range, text);
ReadAttr(ejnt, "margin", 1, &djoint.margin, text);
ReadAttr(ejnt, "armature", 1, &djoint.armature, text);
ReadAttr(ejnt, "damping", 1, djoint.damping, text);
ReadAttr(ejnt, "frictionloss", 1, &djoint.frictionloss, text);
// advance
ejnt = NextSiblingElement(ejnt, "joint");
}
// make composite
char error[200];
bool res = comp.Make(spec, body, error, 200);
// throw error
if (!res) { throw mjXError(elem, "%s", error); }
}
// make flexcomp
void mjXReader::OneFlexcomp(XMLElement* elem, mjsBody* body, const mjVFS* vfs) {
string text, material;
int n;
// create out-of-DOM element
mjCFlexcomp fcomp;
mjsFlex& dflex = *fcomp.def.spec.flex;
// common properties
ReadAttrTxt(elem, "name", fcomp.name, true);
if (MapValue(elem, "type", &n, fcomp_map, mjNFCOMPTYPES)) { fcomp.type = (mjtFcompType)n; }
ReadAttr(elem, "count", 3, fcomp.count, text);
ReadAttr(elem, "cellcount", 3, fcomp.cellcount, text);
ReadAttr(elem, "spacing", 3, fcomp.spacing, text);
ReadAttr(elem, "scale", 3, fcomp.scale, text);
ReadAttr(elem, "mass", 1, &fcomp.mass, text);
ReadAttr(elem, "inertiabox", 1, &fcomp.inertiabox, text);
auto maybe_file = ReadAttrFile(elem, "file", vfs, modelfiledir_);
if (maybe_file.has_value()) {
fcomp.file = std::move(maybe_file.value().Str());
} else {
fcomp.file = "";
}
if (ReadAttrTxt(elem, "material", material)) { mjs_setString(dflex.material, material.c_str()); }
ReadAttr(elem, "rgba", 4, dflex.rgba, text);
if (MapValue(elem, "flatskin", &n, bool_map, 2)) { dflex.flatskin = (n == 1); }
ReadAttrInt(elem, "dim", &dflex.dim);
ReadAttr(elem, "radius", 1, &dflex.radius, text);
ReadAttrInt(elem, "group", &dflex.group);
if (!ReadAttr(elem, "origin", 3, fcomp.origin, text) &&
fcomp.type == mjFCOMPTYPE_MESH &&
dflex.dim == 3) {
throw mjXError(elem, "origin must be specified for mesh flexcomps if dim=3");
}
// pose
ReadAttr(elem, "pos", 3, fcomp.pos, text);
ReadAttr(elem, "quat", 4, fcomp.quat, text);
ReadAlternative(elem, fcomp.alt);
// user or internal
if (MapValue(elem, "rigid", &n, bool_map, 2)) { fcomp.rigid = (n == 1); }
auto point = ReadAttrVec<double>(elem, "point");
if (point.has_value()) { fcomp.point = std::move(point.value()); }
auto element = ReadAttrVec<int>(elem, "element");
if (element.has_value()) { fcomp.element = std::move(element.value()); }
auto texcoord = ReadAttrVec<float>(elem, "texcoord");
if (texcoord.has_value()) { fcomp.texcoord = std::move(texcoord.value()); }
// dof type
if (MapValue(elem, "dof", &n, fdof_map, mjNFCOMPDOFS)) { fcomp.doftype = (mjtDof)n; }
// edge
XMLElement* edge = FirstChildElement(elem, "edge");
if (edge) {
MapValue(edge, "equality", &fcomp.equality, flexeq_map, 4);
ReadAttr(edge, "solref", mjNREF, fcomp.def.spec.equality->solref, text, false, false);
ReadAttr(edge, "solimp", mjNIMP, fcomp.def.spec.equality->solimp, text, false, false);
ReadAttr(edge, "stiffness", 1, &dflex.edgestiffness, text);
ReadAttr(edge, "damping", 1, &dflex.edgedamping, text);
}
// elasticity
XMLElement* elasticity = FirstChildElement(elem, "elasticity");
if (elasticity) {
ReadAttr(elasticity, "young", 1, &dflex.young, text);
ReadAttr(elasticity, "poisson", 1, &dflex.poisson, text);
ReadAttr(elasticity, "damping", 1, &dflex.damping, text);
ReadAttr(elasticity, "thickness", 1, &dflex.thickness, text);
MapValue(elasticity, "elastic2d", &dflex.elastic2d, elastic2d_map, 4);
}
// contact
XMLElement* cont = FirstChildElement(elem, "contact");
if (cont) {
ReadAttrInt(cont, "contype", &dflex.contype);
ReadAttrInt(cont, "conaffinity", &dflex.conaffinity);
ReadAttrInt(cont, "condim", &dflex.condim);
ReadAttrInt(cont, "priority", &dflex.priority);
ReadAttr(cont, "friction", 3, dflex.friction, text, false, false);
ReadAttr(cont, "solmix", 1, &dflex.solmix, text);
ReadAttr(cont, "solref", mjNREF, dflex.solref, text, false, false);
ReadAttr(cont, "solimp", mjNIMP, dflex.solimp, text, false, false);
ReadAttr(cont, "margin", 1, &dflex.margin, text);
ReadAttr(cont, "gap", 1, &dflex.gap, text);
MapValue(cont, "selfcollide", &dflex.selfcollide, flexself_map, 5);
if (MapValue(cont, "passive", &n, bool_map, 2)) { dflex.passive = (n == 1); }
ReadAttrInt(cont, "activelayers", &dflex.activelayers);
}
// pin
XMLElement* epin = FirstChildElement(elem, "pin");
while (epin) {
auto id = ReadAttrVec<int>(epin, "id");
if (id.has_value()) { fcomp.pinid.insert(fcomp.pinid.end(), id->begin(), id->end()); }
auto range = ReadAttrVec<int>(epin, "range");
if (range.has_value()) {
fcomp.pinrange.insert(fcomp.pinrange.end(), range->begin(), range->end());
}
auto grid = ReadAttrVec<int>(epin, "grid");
if (grid.has_value()) { fcomp.pingrid.insert(fcomp.pingrid.end(), grid->begin(), grid->end()); }
auto gridrange = ReadAttrVec<int>(epin, "gridrange");
if (gridrange.has_value()) {
fcomp.pingridrange.insert(fcomp.pingridrange.end(), gridrange->begin(), gridrange->end());
}
// advance
epin = NextSiblingElement(epin, "pin");
}
// plugin
XMLElement* eplugin = FirstChildElement(elem, "plugin");
if (eplugin) { OnePlugin(eplugin, &fcomp.plugin); }
// make flexcomp
char error[200];
bool res = fcomp.Make(body, error, 200, vfs);
// throw error
if (!res) { throw mjXError(elem, "%s", error); }
}
// add plugin
void mjXReader::OnePlugin(XMLElement* elem, mjsPlugin* plugin) {
plugin->active = true;
string name = "";
string instance_name = "";
ReadAttrTxt(elem, "plugin", name);
ReadAttrTxt(elem, "instance", instance_name);
mjs_setString(plugin->plugin_name, name.c_str());
mjs_setString(plugin->name, instance_name.c_str());
if (instance_name.empty()) {
plugin->element = mjs_addPlugin(spec)->element;
ReadPluginConfigs(elem, plugin);
} else {
spec->hasImplicitPluginElem = true;
}
}
//------------------ MJCF-specific sections --------------------------------------------------------
// default section parser
void mjXReader::Default(XMLElement* section, const mjsDefault* def, const mjVFS* vfs) {
XMLElement* elem;
string text, name;
// create new default, except at top level (already added in mjCModel constructor)
text.clear();
ReadAttrTxt(section, "class", text);
if (text.empty()) {
if (def) { throw mjXError(section, "empty class name"); }
}
if (def) {
def = mjs_addDefault(spec, text.c_str(), def);
if (!def) { throw mjXError(section, "repeated default class name"); }
} else {
def = mjs_getSpecDefault(spec);
if (!text.empty() && text != "main") {
throw mjXError(section, "top-level default class 'main' cannot be renamed");
}
}
// iterate over elements other than nested defaults
elem = FirstChildElement(section);
while (elem) {
// get element name
name = elem->Value();
// read mesh
if (name == "mesh") OneMesh(elem, def->mesh, vfs);
// read material
else if (name == "material")
OneMaterial(elem, def->material);
// read joint
else if (name == "joint")
OneJoint(elem, def->joint);
// read geom
else if (name == "geom")
OneGeom(elem, def->geom);
// read site
else if (name == "site")
OneSite(elem, def->site);
// read camera
else if (name == "camera")
OneCamera(elem, def->camera);
// read light
else if (name == "light")
OneLight(elem, def->light);
// read pair
else if (name == "pair")
OnePair(elem, def->pair);
// read equality
else if (name == "equality")
OneEquality(elem, def->equality);
// read tendon
else if (name == "tendon")
OneTendon(elem, def->tendon);
// read actuator
else if (name == "general" ||
name == "motor" ||
name == "position" ||
name == "velocity" ||
name == "damper" ||
name == "intvelocity" ||
name == "orientation" ||
name == "pid" ||
name == "cylinder" ||
name == "muscle" ||
name == "adhesion" ||
name == "dcmotor") {
OneActuator(elem, def->actuator);
}
// advance
elem = NextSiblingElement(elem);
}
// iterate over nested defaults
elem = FirstChildElement(section);
while (elem) {
// get element name
name = elem->Value();
// read default
if (name == "default") { Default(elem, def, vfs); }
// advance
elem = NextSiblingElement(elem);
}
}
// extension section parser
void mjXReader::Extension(XMLElement* section) {
XMLElement* elem = FirstChildElement(section);
while (elem) {
// get sub-element name
string_view name = elem->Value();
if (name == "plugin") {
string plugin_name;
ReadAttrTxt(elem, "plugin", plugin_name, /* required = */ true);
if (mjs_activatePlugin(spec, plugin_name.c_str())) {
throw mjXError(elem, "plugin %s not found", plugin_name.c_str());
}
XMLElement* child = FirstChildElement(elem);
while (child) {
if (string(child->Value()) == "instance") {
if (spec->hasImplicitPluginElem) {
throw mjXError(child,
"explicit plugin instance must appear before implicit plugin elements");
}
string name;
mjsPlugin* p = mjs_addPlugin(spec);
mjs_setString(p->plugin_name, plugin_name.c_str());
mjs_setString(p->info, ("line " + std::to_string(elem->GetLineNum())).c_str());
ReadAttrTxt(child, "name", name, /* required = */ true);
mjs_setString(p->name, name.c_str());
if (!p->name) { throw mjXError(child, "plugin instance must have a name"); }
ReadPluginConfigs(child, p);
}
child = NextSiblingElement(child);
}
}
// advance to next element
elem = NextSiblingElement(elem);
}
}
// custom section parser
void mjXReader::Custom(XMLElement* section) {
string str, name;
XMLElement* elem;
double data[500];
// iterate over child elements
elem = FirstChildElement(section);
while (elem) {
// get sub-element name
name = elem->Value();
string elname;
// numeric
if (name == "numeric") {
// create custom
mjsNumeric* numeric = mjs_addNumeric(spec);
// write error info
mjs_setString(numeric->info, ("line " + std::to_string(elem->GetLineNum())).c_str());
// read attributes
ReadAttrTxt(elem, "name", elname, true);
if (mjs_setName(numeric->element, elname.c_str())) {
throw mjXError(elem, "%s", mjs_getError(spec));
}
if (ReadAttrInt(elem, "size", &numeric->size)) {
int sz = numeric->size < 500 ? numeric->size : 500;
for (int i = 0; i < sz; i++) { data[i] = 0; }
} else {
numeric->size = 501;
}
int len = ReadAttr(elem, "data", numeric->size, data, str, false, false);
if (numeric->size == 501) { numeric->size = len; }
if (numeric->size < 1 || numeric->size > 500) {
throw mjXError(elem, "custom field size must be between 1 and 500");
}
// copy data
mjs_setDouble(numeric->data, data, numeric->size);
}
// text
else if (name == "text") {
// create custom
mjsText* text = mjs_addText(spec);
// write error info
mjs_setString(text->info, ("line " + std::to_string(elem->GetLineNum())).c_str());
// read attributes
ReadAttrTxt(elem, "name", elname, true);
if (mjs_setName(text->element, elname.c_str())) {
throw mjXError(elem, "%s", mjs_getError(spec));
}
string attr_val;
bool has_attr = ReadAttrTxt(elem, "data", attr_val, false);
// check for CDATA block
const XMLText* cdata_node = nullptr;
for (const tinyxml2::XMLNode* child = elem->FirstChild(); child;
child = child->NextSibling()) {
if (const XMLText* text_node = child->ToText()) {
if (text_node->CData()) {
if (cdata_node) {
throw mjXError(elem, "text field cannot have multiple CDATA sections");
}
cdata_node = text_node;
}
}
}
// read CDATA
if (has_attr && cdata_node) {
throw mjXError(elem, "text field data cannot be specified as both attribute and CDATA");
}
if (has_attr) {
str = attr_val;
} else if (cdata_node && cdata_node->Value()) {
str = cdata_node->Value();
} else {
str.clear();
}
if (str.empty()) { throw mjXError(elem, "text field cannot be empty"); }
// copy data
mjs_setString(text->data, str.c_str());
}
// tuple
else if (name == "tuple") {
// create custom
mjsTuple* tuple = mjs_addTuple(spec);
// write error info
mjs_setString(tuple->info, ("line " + std::to_string(elem->GetLineNum())).c_str());
// read attributes
ReadAttrTxt(elem, "name", elname, true);
if (mjs_setName(tuple->element, elname.c_str())) {
throw mjXError(elem, "%s", mjs_getError(spec));
}
// read objects and add
XMLElement* obj = FirstChildElement(elem);
std::vector<int> objtype;
string objname = "";
std::vector<double> objprm;
while (obj) {
// get sub-element name
name = obj->Value();
// new object
if (name == "element") {
// read type, check and assign
ReadAttrTxt(obj, "objtype", str, true);
mjtObj otype = (mjtObj)mju_str2Type(str.c_str());
if (otype == mjOBJ_UNKNOWN) { throw mjXError(obj, "unknown object type"); }
objtype.push_back(otype);
// read name and assign
ReadAttrTxt(obj, "objname", str, true);
objname += " " + str;
// read parameter and assign
double oprm = 0;
ReadAttr(obj, "prm", 1, &oprm, str);
objprm.push_back(oprm);
}
// advance to next object
obj = NextSiblingElement(obj);
}
mjs_setInt(tuple->objtype, objtype.data(), objtype.size());
mjs_setStringVec(tuple->objname, objname.c_str());
mjs_setDouble(tuple->objprm, objprm.data(), objprm.size());
}
// advance to next element
elem = NextSiblingElement(elem);
}
}
// visual section parser
void mjXReader::Visual(XMLElement* section) {
string name;
XMLElement* elem;
mjVisual* vis = &spec->visual;
// iterate over child elements
elem = FirstChildElement(section);
while (elem) {
// get sub-element name
name = elem->Value();
// global sub-element
if (name == "global") {
ReadAttrTableCore(elem,
vis,
kGlobalAttrs,
kGlobalAttrsN,
/*skipnodefault=*/false,
/*authored=*/spec);
if (vis->global.realtime <= 0) { throw mjXError(elem, "realtime must be greater than 0"); }
}
// quality sub-element
else if (name == "quality") {
ReadAttrTableCore(elem,
vis,
kQualityAttrs,
kQualityAttrsN,
false,
/*authored=*/spec);
}
// headlight sub-element
else if (name == "headlight") {
ReadAttrTableCore(elem,
vis,
kHeadlightAttrs,
kHeadlightAttrsN,
false,
/*authored=*/spec);
}
// map sub-element
else if (name == "map") {
ReadAttrTableCore(elem,
vis,
kMapAttrs,
kMapAttrsN,
false,
/*authored=*/spec);
if (vis->map.znear <= 0) { throw mjXError(elem, "znear must be strictly positive"); }
}
// scale sub-element
else if (name == "scale") {
ReadAttrTableCore(elem,
vis,
kScaleAttrs,
kScaleAttrsN,
false,
/*authored=*/spec);
}
// rgba sub-element
else if (name == "rgba") {
ReadAttrTableCore(elem,
vis,
kRgbaAttrs,
kRgbaAttrsN,
false,
/*authored=*/spec);
}
// advance to next element
elem = NextSiblingElement(elem);
}
}
// asset section parser
void mjXReader::Asset(XMLElement* section, const mjVFS* vfs) {
string text, name;
XMLElement* elem;
// iterate over child elements
elem = FirstChildElement(section);
while (elem) {
// get sub-element name
name = elem->Value();
// get class if specified, otherwise use default0
const mjsDefault* def = GetClass(elem);
if (!def) { def = mjs_getSpecDefault(spec); }
// texture sub-element
if (name == "texture") {
// create texture
mjsTexture* texture = mjs_addTexture(spec);
// write error info
mjs_setString(texture->info, ("line " + std::to_string(elem->GetLineNum())).c_str());
// mechanical attributes
ReadAttrTable(elem, texture, texture->element, kTextureAttrs, kTextureAttrsN);
// file, resolved against the texture directory
auto file = ReadAttrFile(elem, "file", vfs, TextureDir());
if (file.has_value()) { mjs_setString(texture->file, file->c_str()); }
// gridlayout length must equal the gridsize product (value-conditional)
if (ReadAttrTxt(elem, "gridlayout", text) &&
text.length() != texture->gridsize[0] * texture->gridsize[1]) {
throw mjXError(elem, "gridlayout length must match gridsize");
}
// separate files
std::vector<string> cubefiles(6);
std::vector<string> cubefile_names =
{"fileright", "fileleft", "fileup", "filedown", "filefront", "fileback"};
for (int i = 0; i < cubefiles.size(); i++) {
auto maybe_file = ReadAttrFile(elem, cubefile_names[i].c_str(), vfs, TextureDir());
if (maybe_file.has_value()) {
cubefiles[i] = maybe_file.value().Str();
} else {
cubefiles[i] = "";
}
mjs_setInStringVec(texture->cubefiles, i, cubefiles[i].c_str());
}
}
// material sub-element
else if (name == "material") {
// create material and parse
mjsMaterial* material = mjs_addMaterial(spec, def);
OneMaterial(elem, material);
}
// mesh sub-element
else if (name == "mesh") {
// create mesh and parse
mjsMesh* mesh = mjs_addMesh(spec, def);
OneMesh(elem, mesh, vfs);
}
// skin sub-element... deprecate ???
else if (name == "skin") {
// create skin and parse
mjsSkin* skin = mjs_addSkin(spec);
OneSkin(elem, skin, vfs);
}
// hfield sub-element
else if (name == "hfield") {
// create hfield
mjsHField* hfield = mjs_addHField(spec);
// write error info
mjs_setString(hfield->info, ("line " + std::to_string(elem->GetLineNum())).c_str());
// mechanical attributes
ReadAttrTable(elem, hfield, hfield->element, kHfieldAttrs, kHfieldAttrsN);
// file, resolved against the asset directory
auto file = ReadAttrFile(elem, "file", vfs, AssetDir());
if (file.has_value()) { mjs_setString(hfield->file, file->c_str()); }
// allocate buffer for dynamic hfield, copy user data if given
if (!file.has_value() && hfield->nrow > 0 && hfield->ncol > 0) {
int nrow = hfield->nrow;
int ncol = hfield->ncol;
// read user data
auto userdata = ReadAttrVec<float>(elem, "elevation");
// user data given, copy into data
if (userdata.has_value()) {
if (userdata->size() != nrow * ncol) {
throw mjXError(elem, "elevation data length must match nrow*ncol");
}
// copy in reverse row order, so XML string is top-to-bottom
std::vector<float> flipped(nrow * ncol);
for (int i = 0; i < nrow; i++) {
int flip = nrow - 1 - i;
for (int j = 0; j < ncol; j++) {
flipped[flip * ncol + j] = userdata->data()[i * ncol + j];
}
}
mjs_setFloat(hfield->userdata, flipped.data(), flipped.size());
}
// user data not given, set to 0
else {
std::vector<float> zero(nrow * ncol);
mjs_setFloat(hfield->userdata, zero.data(), zero.size());
}
}
}
// model sub-element
else if (name == "model") {
std::string content_type;
ReadAttrTxt(elem, "content_type", content_type);
// parse the child
std::array<char, 1024> error;
auto filename = modelfiledir_ + ReadAttrFile(elem, "file", vfs).value();
mjSpec* child =
mj_parse(filename.c_str(), content_type.c_str(), vfs, error.data(), error.size());
if (!child) {
throw mjXError(elem, "could not parse model file with error: %s", error.data());
}
// overwrite model name if given
string modelname = "";
if (ReadAttrTxt(elem, "name", modelname)) {
mjs_setString(child->modelname, modelname.c_str());
}
// store child spec in model
mjs_addSpec(spec, child);
}
// advance to next element
elem = NextSiblingElement(elem);
}
}
// strip the "Error: " prefix from compiler/spec error messages
static const char* stripError(const char* err) {
if (err && std::strncmp(err, "Error: ", 7) == 0) { return err + 7; }
return err;
}
// body/world section parser; recursive
void mjXReader::Body(XMLElement* section, mjsBody* body, mjsFrame* frame, const mjVFS* vfs) {
string text, name;
XMLElement* elem;
// sanity check
if (!body) { throw mjXError(section, "null body pointer"); }
// no attributes allowed in world body
if (mjs_getId(body->element) == 0 && section->FirstAttribute() && !frame) {
throw mjXError(section, "World body cannot have attributes");
}
// iterate over sub-elements; attributes set while parsing parent body
elem = FirstChildElement(section);
while (elem) {
// get sub-element name
name = elem->Value();
// get class if specified, otherwise use body
const mjsDefault* def = GetClass(elem);
if (!def) { def = mjs_getDefault(frame ? frame->element : body->element); }
// inertial sub-element
if (name == "inertial") {
// no inertia allowed in world body
if (mjs_getId(body->element) == 0) { throw mjXError(elem, "World body cannot have inertia"); }
body->explicitinertial = true;
ReadAttrTable(elem, body, body->element, kInertialAttrs, kInertialAttrsN);
ReadQuat(elem, "quat", body->iquat, text);
ReadAlternative(elem, body->ialt);
ReadAttr(elem, "fullinertia", 6, body->fullinertia, text);
// the inertial frame is relative to the enclosing frame
mjCBody* bodyC = static_cast<mjCBody*>(body->element);
bodyC->iframe = frame ? static_cast<mjCFrame*>(frame->element) : nullptr;
}
// joint sub-element
else if (name == "joint") {
// no joints allowed in world body
if (mjs_getId(body->element) == 0) { throw mjXError(elem, "World body cannot have joints"); }
// create joint and parse
mjsJoint* joint = mjs_addJoint(body, def);
OneJoint(elem, joint);
mjs_setFrame(joint->element, frame);
}
// freejoint sub-element
else if (name == "freejoint") {
// no joints allowed in world body
if (mjs_getId(body->element) == 0) { throw mjXError(elem, "World body cannot have joints"); }
// create free joint without defaults
mjsJoint* joint = mjs_addFreeJoint(body);
mjs_setFrame(joint->element, frame);
// save defaults after creation, to make sure writing is ok
mjs_setDefault(joint->element, def);
// read attributes
string name;
if (ReadAttrTxt(elem, "name", name)) {
if (mjs_setName(joint->element, name.c_str())) {
throw mjXError(elem, "%s", mjs_getError(spec));
}
}
ReadAttrInt(elem, "group", &joint->group);
MapValue(elem, "align", &joint->align, FalseTrueAuto_map, 3);
}
// geom sub-element
else if (name == "geom") {
// create geom and parse
mjsGeom* geom = mjs_addGeom(body, def);
OneGeom(elem, geom);
mjs_setFrame(geom->element, frame);
}
// site sub-element
else if (name == "site") {
// create site and parse
mjsSite* site = mjs_addSite(body, def);
OneSite(elem, site);
mjs_setFrame(site->element, frame);
}
// camera sub-element
else if (name == "camera") {
// create camera and parse
mjsCamera* camera = mjs_addCamera(body, def);
OneCamera(elem, camera);
mjs_setFrame(camera->element, frame);
}
// light sub-element
else if (name == "light") {
// create light and parse
mjsLight* light = mjs_addLight(body, def);
OneLight(elem, light);
mjs_setFrame(light->element, frame);
}
// plugin sub-element
else if (name == "plugin") {
OnePlugin(elem, &(body->plugin));
}
// composite sub-element
else if (name == "composite") {
// parse composite
OneComposite(elem, body, frame, def);
}
// flexcomp sub-element
else if (name == "flexcomp") {
// parse flexcomp
OneFlexcomp(elem, body, vfs);
}
// frame sub-element
else if (name == "frame") {
// read childdef
bool has_childclass = ReadAttrTxt(elem, "childclass", text);
const mjsDefault* childdef = has_childclass ? mjs_findDefault(spec, text.c_str()) : nullptr;
if (has_childclass && !childdef) { throw mjXError(elem, "unknown default childclass"); }
// create frame
mjsFrame* pframe = mjs_addFrame(body, frame);
mjs_setString(pframe->info, ("line " + std::to_string(elem->GetLineNum())).c_str());
mjs_setDefault(pframe->element, childdef ? childdef : def);
// read attributes
string name, childclass;
if (ReadAttrTxt(elem, "name", name)) {
if (mjs_setName(pframe->element, name.c_str())) {
throw mjXError(elem, "%s", mjs_getError(spec));
}
}
if (ReadAttrTxt(elem, "childclass", childclass)) {
mjs_setString(pframe->childclass, childclass.c_str());
}
ReadAttr(elem, "pos", 3, pframe->pos, text);
ReadQuat(elem, "quat", pframe->quat, text);
ReadAlternative(elem, pframe->alt);
Body(elem, body, pframe, vfs);
}
// replicate sub-element
else if (name == "replicate") {
int count;
double offset[3] = {0, 0, 0};
double euler[3] = {0, 0, 0};
string separator = "";
ReadAttr(elem, "count", 1, &count, text, true);
ReadAttr(elem, "offset", 3, offset, text);
ReadAttr(elem, "euler", 3, euler, text);
ReadAttrTxt(elem, "sep", separator);
// store rotation difference
mjsOrientation alt;
mjs_defaultOrientation(&alt);
alt.type = mjORIENTATION_EULER;
mjuu_copyvec(alt.euler, euler, 3);
double rotation[4] = {1, 0, 0, 0};
mjs_resolveOrientation(rotation, spec->compiler.degree, spec->compiler.eulerseq, &alt);
// read childdef
bool has_childclass = ReadAttrTxt(elem, "childclass", text);
const mjsDefault* childdef = has_childclass ? mjs_findDefault(spec, text.c_str()) : nullptr;
if (has_childclass && !childdef) { throw mjXError(elem, "unknown default childclass"); }
// create subtree
mjsBody* subtree = mjs_addBody(body, childdef);
double pos[3] = {0, 0, 0};
double quat[4] = {1, 0, 0, 0};
// parent frame that will be used to attach the subtree
mjsFrame* pframe = mjs_addFrame(subtree, frame);
mjs_setDefault(pframe->element, childdef ? childdef : def);
mjs_setString(pframe->info, ("line = " + std::to_string(elem->GetLineNum())).c_str());
// parse subtree
Body(elem, subtree, pframe, vfs);
// update pframe and attach
for (int i = 0; i < count; i++) {
// overwrite orientation to increase precision
alt.euler[0] = i * euler[0];
alt.euler[1] = i * euler[1];
alt.euler[2] = i * euler[2];
mjs_resolveOrientation(quat, spec->compiler.degree, spec->compiler.eulerseq, &alt);
// set position and orientation
mjuu_setvec(pframe->pos, pos[0], pos[1], pos[2]);
mjuu_setvec(pframe->quat, quat[0], quat[1], quat[2], quat[3]);
// accumulate rotation
mjuu_frameaccum(pos, quat, offset, rotation);
// process suffix
string suffix = separator;
UpdateString(suffix, count, i);
// attach to parent
if (!mjs_attach(body->element, pframe->element, /*prefix=*/"", suffix.c_str())) {
throw mjXError(elem, "%s", stripError(mjs_getError(spec)));
}
}
// delete subtree
if (mjs_delete(spec, subtree->element)) {
throw mjXError(elem, "%s", stripError(mjs_getError(spec)));
}
}
// body sub-element
else if (name == "body") {
// read childdef
bool has_childclass = ReadAttrTxt(elem, "childclass", text);
const mjsDefault* childdef = has_childclass ? mjs_findDefault(spec, text.c_str()) : nullptr;
if (has_childclass && !childdef) { throw mjXError(elem, "unknown default childclass"); }
// create child body
mjsBody* child = mjs_addBody(body, childdef);
mjs_setString(child->info, string("line " + std::to_string(elem->GetLineNum())).c_str());
// set default from class or childclass
mjs_setDefault(child->element, childdef ? childdef : def);
// mechanical attributes
ReadAttrTable(elem, child, child->element, kBodyAttrs, kBodyAttrsN);
string childclass;
if (ReadAttrTxt(elem, "childclass", childclass)) {
mjs_setString(child->childclass, childclass.c_str());
}
// orientation alternatives
ReadQuat(elem, "quat", child->quat, text);
ReadAlternative(elem, child->alt);
// add frame
mjs_setFrame(child->element, frame);
// make recursive call
Body(elem, child, nullptr, vfs);
}
// attachment
else if (name == "attach") {
string model_name, child_name, prefix;
bool has_model = ReadAttrTxt(elem, "model", model_name, /*required=*/false);
bool has_body = ReadAttrTxt(elem, "body", child_name, /*required=*/false);
bool has_frame = ReadAttrTxt(elem, "frame", child_name, /*required=*/false);
ReadAttrTxt(elem, "prefix", prefix, /*required=*/true);
mjtObj type = mjOBJ_UNKNOWN;
if (has_body)
type = mjOBJ_BODY;
else if (has_frame)
type = mjOBJ_FRAME;
mjsElement* source_elem = nullptr;
if (!has_model) { // Self-attach
if (type == mjOBJ_UNKNOWN) {
throw mjXError(elem,
"either 'body' or 'frame' attribute must be specified for self-attach");
}
// check for name collision in the current spec
string full_name = prefix + child_name;
if (mjs_findElement(spec, type, full_name.c_str())) {
throw mjXError(elem, "cannot self-attach: element %s already exists", full_name.c_str());
}
source_elem = mjs_findElement(spec, type, child_name.c_str());
if (!source_elem) {
throw mjXError(elem,
"%s",
(string("could not find ") +
mju_type2Str(type) +
" '" +
child_name +
"' in the current model for self-attachment")
.c_str());
}
} else { // Attach from external model asset
// Check for name collision in the current spec
if (!child_name.empty()) {
string full_name = prefix + child_name;
if (mjs_findElement(spec, type, full_name.c_str())) {
throw mjXError(elem,
"%s",
(string("cannot attach: element ") +
child_name +
" already exists with prefix " +
prefix)
.c_str());
}
}
mjSpec* asset = mjs_findSpec(spec, model_name.c_str());
if (!asset) { throw mjXError(elem, "could not find model '%s'", model_name.c_str()); }
if (type == mjOBJ_UNKNOWN) { // Attach world body contents
source_elem = asset->element;
} else { // Attach specific body or frame
source_elem = mjs_findElement(asset, type, child_name.c_str());
if (!source_elem) {
throw mjXError(elem,
"%s",
(string("could not find ") +
mju_type2Str(type) +
" '" +
child_name +
"' in model asset '" +
model_name +
"'")
.c_str());
}
}
}
mjsFrame* pframe = frame ? frame : mjs_addFrame(body, nullptr);
// Set default for the new frame from the current context
mjs_setDefault(pframe->element, mjs_getDefault(frame ? frame->element : body->element));
mjs_setString(pframe->info, ("line = " + std::to_string(elem->GetLineNum())).c_str());
if (!mjs_attach(pframe->element, source_elem, prefix.c_str(), "")) {
throw mjXError(elem, "%s", stripError(mjs_getError(spec)));
}
}
// no match
else {
throw mjXError(elem, "unrecognized model element '%s'", name.c_str());
}
// advance to next element
elem = NextSiblingElement(elem);
}
}
// contact section parser
void mjXReader::Contact(XMLElement* section) {
string text, name;
XMLElement* elem;
// iterate over child elements
elem = FirstChildElement(section);
while (elem) {
// get sub-element name
name = elem->Value();
// get class if specified, otherwise use default0
const mjsDefault* def = GetClass(elem);
if (!def) { def = mjs_getSpecDefault(spec); }
// geom pair to include
if (name == "pair") {
// create pair and parse
mjsPair* pair = mjs_addPair(spec, def);
OnePair(elem, pair);
}
// body pair to exclude
else if (name == "exclude") {
mjsExclude* exclude = mjs_addExclude(spec);
// write error info
mjs_setString(exclude->info, ("line " + std::to_string(elem->GetLineNum())).c_str());
// read attributes
ReadAttrTable(elem, exclude, exclude->element, kExcludeAttrs, kExcludeAttrsN);
}
// advance to next element
elem = NextSiblingElement(elem);
}
}
// constraint section parser
void mjXReader::Equality(XMLElement* section) {
XMLElement* elem;
// iterate over child elements
elem = FirstChildElement(section);
while (elem) {
// get class if specified, otherwise use default0
const mjsDefault* def = GetClass(elem);
if (!def) { def = mjs_getSpecDefault(spec); }
// create equality constraint and parse
mjsEquality* equality = mjs_addEquality(spec, def);
OneEquality(elem, equality);
// advance to next element
elem = NextSiblingElement(elem);
}
}
// deformable section parser
void mjXReader::Deformable(XMLElement* section, const mjVFS* vfs) {
string name;
XMLElement* elem;
// iterate over child elements
elem = FirstChildElement(section);
while (elem) {
// get sub-element name
name = elem->Value();
// get class if specified, otherwise use default0
const mjsDefault* def = GetClass(elem);
if (!def) { def = mjs_getSpecDefault(spec); }
// flex sub-element
if (name == "flex") {
// create flex and parse
mjsFlex* flex = mjs_addFlex(spec);
OneFlex(elem, flex);
}
// skin sub-element
else if (name == "skin") {
// create skin and parse
mjsSkin* skin = mjs_addSkin(spec);
OneSkin(elem, skin, vfs);
}
// advance to next element
elem = NextSiblingElement(elem);
}
}
// tendon section parser
void mjXReader::Tendon(XMLElement* section) {
string text, text1;
XMLElement* elem;
double data;
// iterate over child elements
elem = FirstChildElement(section);
while (elem) {
// get class if specified, otherwise use default0
const mjsDefault* def = GetClass(elem);
if (!def) { def = mjs_getSpecDefault(spec); }
// create tendon and parse
mjsTendon* tendon = mjs_addTendon(spec, def);
OneTendon(elem, tendon);
// process wrap sub-elements
XMLElement* sub = FirstChildElement(elem);
while (sub) {
// get wrap type
string type = sub->Value();
mjsWrap* wrap;
;
// read attributes depending on type
if (type == "site") {
ReadAttrTxt(sub, "site", text, true);
wrap = mjs_wrapSite(tendon, text.c_str());
}
else if (type == "geom") {
ReadAttrTxt(sub, "geom", text, true);
if (!ReadAttrTxt(sub, "sidesite", text1)) { text1.clear(); }
wrap = mjs_wrapGeom(tendon, text.c_str(), text1.c_str());
}
else if (type == "pulley") {
ReadAttr(sub, "divisor", 1, &data, text, true);
wrap = mjs_wrapPulley(tendon, data);
}
else if (type == "joint") {
ReadAttrTxt(sub, "joint", text, true);
ReadAttr(sub, "coef", 1, &data, text1, true);
wrap = mjs_wrapJoint(tendon, text.c_str(), data);
}
else {
throw mjXError(sub, "unknown wrap type"); // SHOULD NOT OCCUR
}
mjs_setString(wrap->info, ("line " + std::to_string(sub->GetLineNum())).c_str());
// advance to next sub-element
sub = NextSiblingElement(sub);
}
// advance to next element
elem = NextSiblingElement(elem);
}
}
// actuator section parser
void mjXReader::Actuator(XMLElement* section) {
XMLElement* elem;
// iterate over child elements
elem = FirstChildElement(section);
while (elem) {
// get class if specified, otherwise use default0
const mjsDefault* def = GetClass(elem);
if (!def) { def = mjs_getSpecDefault(spec); }
// create actuator and parse
mjsActuator* actuator = mjs_addActuator(spec, def);
OneActuator(elem, actuator);
// advance to next element
elem = NextSiblingElement(elem);
}
}
// sensor section parser
void mjXReader::Sensor(XMLElement* section) {
int n;
XMLElement* elem = FirstChildElement(section);
while (elem) {
// create sensor, get string type
mjsSensor* sensor = mjs_addSensor(spec);
string type = elem->Value();
string text, objname, refname;
// mechanical attributes shared by all sensor types
ReadAttrTable(elem, sensor, sensor->element, kSensorBaseAttrs, kSensorBaseAttrsN);
// common robotic sensors, attached to a site
// sensors fully described by the schema: constants and references
bool dispatched = false;
for (int i = 0; i < kSensorDispatchN; i++) {
if (type == kSensorDispatch[i].tag) {
ReadAttrTable(elem, sensor, sensor->element, kSensorDispatch[i].rows, kSensorDispatch[i].n);
dispatched = true;
break;
}
}
if (dispatched) {
} else if (type == "rangefinder") {
sensor->type = mjSENS_RANGEFINDER;
bool use_site = ReadAttrTxt(elem, "site", objname, false);
ReadAttrTxt(elem, "camera", objname, false);
sensor->objtype = use_site ? mjOBJ_SITE : mjOBJ_CAMERA;
// process data specification (intprm[0])
std::vector<int> raydata(mjNRAYDATA);
int dataspec = 1 << mjRAYDATA_DIST;
int nkeys = MapValues(elem, "data", raydata.data(), raydata_map, mjNRAYDATA);
if (nkeys) {
dataspec = 1 << raydata[0];
// check ordering while adding bits to dataspec
for (int i = 1; i < nkeys; ++i) {
if (raydata[i] <= raydata[i - 1]) {
std::string correct_order;
for (int j = 0; j < mjNRAYDATA; ++j) {
correct_order += raydata_map[j].key;
if (j < mjNRAYDATA - 1) correct_order += ", ";
}
throw mjXError(elem, "data attributes must be in order: %s", correct_order.c_str());
}
dataspec |= 1 << raydata[i];
}
}
sensor->intprm[0] = dataspec;
} else if (type == "distance" || type == "normal" || type == "fromto") {
bool has_body1 = ReadAttrTxt(elem, "body1", objname);
ReadAttrTxt(elem, "geom1", objname);
sensor->objtype = has_body1 ? mjOBJ_BODY : mjOBJ_GEOM;
bool has_body2 = ReadAttrTxt(elem, "body2", refname);
ReadAttrTxt(elem, "geom2", refname);
sensor->reftype = has_body2 ? mjOBJ_BODY : mjOBJ_GEOM;
if (type == "distance") {
sensor->type = mjSENS_GEOMDIST;
} else if (type == "normal") {
sensor->type = mjSENS_GEOMNORMAL;
} else {
sensor->type = mjSENS_GEOMFROMTO;
}
} else if (type == "contact") {
// first matching criterion
bool has_site = ReadAttrTxt(elem, "site", objname);
bool has_body1 = ReadAttrTxt(elem, "body1", objname);
bool has_subtree1 = ReadAttrTxt(elem, "subtree1", objname);
bool has_geom1 = ReadAttrTxt(elem, "geom1", objname);
if (has_site) {
sensor->objtype = mjOBJ_SITE;
} else if (has_body1) {
sensor->objtype = mjOBJ_BODY;
} else if (has_subtree1) {
sensor->objtype = mjOBJ_XBODY;
} else if (has_geom1) {
sensor->objtype = mjOBJ_GEOM;
} else {
sensor->objtype = mjOBJ_UNKNOWN;
}
// second matching criterion
bool has_body2 = ReadAttrTxt(elem, "body2", refname);
bool has_subtree2 = ReadAttrTxt(elem, "subtree2", refname);
bool has_geom2 = ReadAttrTxt(elem, "geom2", refname);
if (has_body2) {
sensor->reftype = mjOBJ_BODY;
} else if (has_subtree2) {
sensor->reftype = mjOBJ_XBODY;
} else if (has_geom2) {
sensor->reftype = mjOBJ_GEOM;
} else {
sensor->reftype = mjOBJ_UNKNOWN;
}
// process data specification (intprm[0])
int dataspec = 1 << mjCONDATA_FOUND;
std::vector<int> condata(mjNCONDATA);
int nkeys = MapValues(elem, "data", condata.data(), condata_map, mjNCONDATA);
if (nkeys) {
dataspec = 1 << condata[0];
// check ordering while adding bits to dataspec
for (int i = 1; i < nkeys; ++i) {
if (condata[i] <= condata[i - 1]) {
std::string correct_order;
for (int j = 0; j < mjNCONDATA; ++j) {
correct_order += condata_map[j].key;
if (j < mjNCONDATA - 1) correct_order += ", ";
}
throw mjXError(elem, "data attributes must be in order: %s", correct_order.c_str());
}
dataspec |= 1 << condata[i];
}
}
sensor->intprm[0] = dataspec;
// reduction type (intprm[1])
sensor->intprm[1] = 0;
if (MapValue(elem, "reduce", &n, reduce_map, reduce_sz)) { sensor->intprm[1] = n; }
// number of contacts (intprm[2])
sensor->intprm[2] = 1;
ReadAttrInt(elem, "num", &sensor->intprm[2]);
if (sensor->intprm[2] <= 0) { throw mjXError(elem, "'num' must be positive in sensor"); }
// sensor type
sensor->type = mjSENS_CONTACT;
} else if (type == "user") {
sensor->type = mjSENS_USER;
ReadAttrTxt(elem, "objname", objname);
if (ReadAttrTxt(elem, "objtype", text)) {
sensor->objtype = (mjtObj)mju_str2Type(text.c_str());
}
ReadAttrInt(elem, "dim", &sensor->dim, true);
// keywords
if (MapValue(elem, "needstage", &n, stage_map, stage_sz)) { sensor->needstage = (mjtStage)n; }
if (MapValue(elem, "datatype", &n, datatype_map, datatype_sz)) {
sensor->datatype = (mjtDataType)n;
}
}
// tactile sensor
if (type == "tactile") {
sensor->type = mjSENS_TACTILE;
sensor->reftype = mjOBJ_GEOM;
ReadAttrTxt(elem, "geom", refname, /*required=*/true);
// associate the sensor with a mesh
sensor->objtype = mjOBJ_MESH;
ReadAttrTxt(elem, "mesh", objname, /*required=*/true);
mjs_setString(sensor->objname, objname.c_str());
}
else if (type == "plugin") {
sensor->type = mjSENS_PLUGIN;
OnePlugin(elem, &sensor->plugin);
ReadAttrTxt(elem, "objtype", text);
sensor->objtype = (mjtObj)mju_str2Type(text.c_str());
ReadAttrTxt(elem, "objname", objname);
if (sensor->objtype != mjOBJ_UNKNOWN && objname.empty()) {
throw mjXError(elem, "objtype is specified but objname is not");
}
if (sensor->objtype == mjOBJ_UNKNOWN && !objname.empty()) {
throw mjXError(elem, "objname is specified but objtype is not");
}
if (ReadAttrTxt(elem, "reftype", text)) {
sensor->reftype = (mjtObj)mju_str2Type(text.c_str());
}
ReadAttrTxt(elem, "refname", refname);
if (sensor->reftype != mjOBJ_UNKNOWN && refname.empty()) {
throw mjXError(elem, "reftype is specified but refname is not");
}
if (sensor->reftype == mjOBJ_UNKNOWN && !refname.empty()) {
throw mjXError(elem, "refname is specified but reftype is not");
}
}
if (!objname.empty()) { mjs_setString(sensor->objname, objname.c_str()); }
if (!refname.empty()) { mjs_setString(sensor->refname, refname.c_str()); }
// write info
mjs_setString(sensor->info, ("line " + std::to_string(elem->GetLineNum())).c_str());
// advance to next element
elem = NextSiblingElement(elem);
}
}
// keyframe section parser
void mjXReader::Keyframe(XMLElement* section) {
XMLElement* elem;
// iterate over child elements
elem = FirstChildElement(section);
while (elem) {
string name = "";
// add keyframe
mjsKey* key = mjs_addKey(spec);
// read name: set even when the attribute is absent
ReadAttrTxt(elem, "name", name);
if (mjs_setName(key->element, name.c_str())) { throw mjXError(elem, "%s", mjs_getError(spec)); }
// mechanical attributes
ReadAttrTable(elem, key, key->element, kKeyAttrs, kKeyAttrsN);
// advance to next element
elem = NextSiblingElement(elem);
}
}
// get defaults class
const mjsDefault* mjXReader::GetClass(XMLElement* section) {
string text;
if (!ReadAttrTxt(section, "class", text)) { return nullptr; }
const mjsDefault* def = mjs_findDefault(spec, text.c_str());
if (!def) {
throw mjXError(section, string("unknown default class name '" + text + "'").c_str());
}
return def;
}
void mjXReader::SetModelFileDir(const string& modelfiledir) {
modelfiledir_ = FilePath(modelfiledir);
}
void mjXReader::SetAssetDir(const string& assetdir) {
assetdir_ = FilePath(assetdir);
}
void mjXReader::SetMeshDir(const string& meshdir) {
meshdir_ = FilePath(meshdir);
}
void mjXReader::SetTextureDir(const string& texturedir) {
texturedir_ = FilePath(texturedir);
}
FilePath mjXReader::AssetDir() const {
return modelfiledir_ + assetdir_;
}
FilePath mjXReader::MeshDir() const {
if (meshdir_.empty()) { return AssetDir(); }
return modelfiledir_ + meshdir_;
}
FilePath mjXReader::TextureDir() const {
if (texturedir_.empty()) { return AssetDir(); }
return modelfiledir_ + texturedir_;
}