Files
Mujoco_WASM/src/xml/xml_native_writer.cc
T
Yuval Tassa b475bb6f36 Move schema related generated files into their own directory
PiperOrigin-RevId: 958296165
Change-Id: I48cacc72c7df5994f5f816489ba069a5813845a1
2026-08-03 04:23:56 -07:00

2319 lines
74 KiB
C++

// 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_writer.h"
#include <array>
#include <cstddef>
#include <cstdio>
#include <string>
#include <string_view>
#include <unordered_set>
#include <vector>
#include <mujoco/mjmodel.h>
#include <mujoco/mjplugin.h>
#include <mujoco/mjspec.h>
#include <mujoco/mujoco.h>
#include "engine/engine_io.h"
#include "engine/engine_plugin.h"
#include "engine/engine_support.h"
#include "engine/engine_util_errmem.h"
#include "engine/engine_util_misc.h"
#include "user/user_model.h"
#include "user/user_objects.h"
#include "user/user_util.h"
#include "xml/xml_base.h"
#include "xml/xml_util.h"
#include "tinyxml2.h"
// typed attribute rows, generated from mjcf.schema; shared with the reader
#include "xml/generated/mjcf_read_table.inc"
namespace {
using std::string;
using std::string_view;
using tinyxml2::XMLComment;
using tinyxml2::XMLDocument;
using tinyxml2::XMLElement;
using mujoco::user::VectorToString;
} // namespace
// custom XML indentation: 2 spaces rather than the default 4
class mj_XMLPrinter : public tinyxml2::XMLPrinter {
using tinyxml2::XMLPrinter::XMLPrinter;
public:
void PrintSpace( int depth ) {
for (int i=0; i < depth; ++i) {
Write( " " );
}
}
};
// save XML file using custom 2-space indentation
static string WriteDoc(XMLDocument& doc, char *error, size_t error_sz) {
doc.ClearError();
mj_XMLPrinter stream(nullptr, /*compact=*/false);
doc.Print(&stream);
if (doc.ErrorID()) {
mjCopyError(error, doc.ErrorStr(), error_sz);
return "";
}
string str = string(stream.CStr());
// top level sections
std::array<string, 17> sections = {
"<actuator", "<asset", "<compiler", "<contact>", "<custom",
"<default>", "<deformable", "<equality", "<extension", "<keyframe",
"<option", "<sensor", "<size", "<statistic", "<tendon",
"<visual", "<worldbody"};
// position of newline before first section
size_t first_pos = string::npos;
// insert newlines before section headers
for (const string& section : sections) {
std::size_t pos = 0;
while ((pos = str.find(section, pos)) != string::npos) {
// find newline before this section
std::size_t line_pos = str.rfind('\n', pos);
// save position of first section
if (line_pos < first_pos) first_pos = line_pos;
// insert another newline
if (line_pos != string::npos) {
str.insert(line_pos + 1, 1, '\n');
pos++; // account for inserted newline
}
// advance
pos += section.length();
}
}
// remove added newline before the first section
if (first_pos != string::npos) {
str.erase(first_pos, 1);
}
return str;
}
// insert end child with given name, return child
XMLElement* mjXWriter::InsertEnd(XMLElement* parent, const char* name) {
XMLElement* result = parent->GetDocument()->NewElement(name);
parent->InsertEndChild(result);
return result;
}
//---------------------------------- class mjXWriter: one-element writers --------------------------
// write flex
void mjXWriter::OneFlex(XMLElement* elem, const mjCFlex* flex) {
string text;
mjCFlex defflex;
// common attributes
WriteAttrTxt(elem, "name", flex->name);
WriteAttrTable(elem, static_cast<const mjsFlex*>(flex),
static_cast<const mjsFlex*>(&defflex), kFlexAttrs,
kFlexAttrsN);
if (flex->get_material() != defflex.get_material()) {
WriteAttrTxt(elem, "material", flex->get_material());
}
WriteAttr(elem, "cellcount", 3, flex->spec.cellcount, defflex.spec.cellcount);
if (flex->spec.order != defflex.spec.order) {
string dof_str = "full";
if (flex->spec.order == 1) dof_str = "trilinear";
else if (flex->spec.order == 2) dof_str = "quadratic";
WriteAttrTxt(elem, "dof", dof_str);
}
// data vectors
if (!flex->get_vertbody().empty()) {
text = VectorToString(flex->get_vertbody());
WriteAttrTxt(elem, "body", text);
}
if (!flex->get_vert().empty()) {
text = VectorToString(flex->get_vert());
WriteAttrTxt(elem, "vertex", text);
}
if (!flex->get_elem().empty()) {
text = VectorToString(flex->get_elem());
WriteAttrTxt(elem, "element", text);
}
if (!flex->get_texcoord().empty()) {
text = VectorToString(flex->get_texcoord());
WriteAttrTxt(elem, "texcoord", text);
}
if (!flex->get_elemtexcoord().empty()) {
text = VectorToString(flex->get_elemtexcoord());
WriteAttrTxt(elem, "elemtexcoord", text);
}
if (!flex->get_nodebody().empty()) {
text = VectorToString(flex->get_nodebody());
WriteAttrTxt(elem, "node", text);
}
// contact subelement
XMLElement* cont = InsertEnd(elem, "contact");
WriteAttrTable(cont, static_cast<const mjsFlex*>(flex),
static_cast<const mjsFlex*>(&defflex), kFlexcomp_contactAttrs,
kFlexcomp_contactAttrsN);
// remove contact is no attributes
if (!cont->FirstAttribute()) {
elem->DeleteChild(cont);
}
// elasticity subelement
XMLElement* elastic = InsertEnd(elem, "elasticity");
WriteAttrTable(elastic, static_cast<const mjsFlex*>(flex),
static_cast<const mjsFlex*>(&defflex), kElasticityAttrs,
kElasticityAttrsN);
// edge subelement
XMLElement* edge = InsertEnd(elem, "edge");
WriteAttrTable(edge, static_cast<const mjsFlex*>(flex),
static_cast<const mjsFlex*>(&defflex), kFlex_edgeAttrs,
kFlex_edgeAttrsN);
// remove edge if no attributes
if (!edge->FirstAttribute()) {
elem->DeleteChild(edge);
}
}
// write mesh
void mjXWriter::OneMesh(XMLElement* elem, const mjCMesh* mesh, mjCDef* def) {
string text;
// regular
if (!writingdefaults) {
WriteAttrTxt(elem, "name", mesh->name);
if (mesh->classname != "main") {
WriteAttrTxt(elem, "class", mesh->classname);
}
WriteAttrTxt(elem, "content_type", mesh->ContentType());
WriteAttrTxt(elem, "file", mesh->File());
// write vertex data
if (!mesh->UserVert().empty()) {
text = VectorToString(mesh->UserVert());
WriteAttrTxt(elem, "vertex", text);
}
// write normal data
if (!mesh->UserNormal().empty()) {
text = VectorToString(mesh->UserNormal());
WriteAttrTxt(elem, "normal", text);
}
// write texcoord data
if (!mesh->UserTexcoord().empty()) {
text = VectorToString(mesh->UserTexcoord());
WriteAttrTxt(elem, "texcoord", text);
}
// write face data
if (!mesh->UserFace().empty()) {
text = VectorToString(mesh->UserFace());
WriteAttrTxt(elem, "face", text);
}
}
// defaults and regular
WriteAttrTable(elem, static_cast<const mjsMesh*>(mesh), &def->Mesh().spec,
kMeshAttrs, kMeshAttrsN);
if (mesh->Material() != def->Mesh().Material()) {
WriteAttrTxt(elem, "material", mesh->Material());
}
}
// write skin
void mjXWriter::OneSkin(XMLElement* elem, const mjCSkin* skin) {
string text;
mjCSkin defskin;
// write attributes
WriteAttrTxt(elem, "name", skin->name);
WriteAttrTxt(elem, "file", skin->File());
WriteAttrTxt(elem, "material", skin->get_material());
WriteAttrInt(elem, "group", skin->group, 0);
WriteAttrTable(elem, static_cast<const mjsSkin*>(skin),
static_cast<const mjsSkin*>(&defskin), kSkinAttrs,
kSkinAttrsN);
// write data if no file
if (skin->File().empty()) {
// mesh vert
text = VectorToString(skin->get_vert());
WriteAttrTxt(elem, "vertex", text);
// mesh texcoord
if (!skin->get_texcoord().empty()) {
text = VectorToString(skin->get_texcoord());
WriteAttrTxt(elem, "texcoord", text);
}
// mesh face
text = VectorToString(skin->get_face());
WriteAttrTxt(elem, "face", text);
// bones
for (size_t i=0; i < skin->get_bodyname().size(); i++) {
// make bone
XMLElement* bone = InsertEnd(elem, "bone");
// write attributes
WriteAttrTxt(bone, "body", skin->get_bodyname()[i]);
WriteAttr(bone, "bindpos", 3, skin->get_bindpos().data()+3*i);
WriteAttr(bone, "bindquat", 4, skin->get_bindquat().data()+4*i);
// write vertid
text = VectorToString(skin->get_vertid()[i]);
WriteAttrTxt(bone, "vertid", text);
// write vertweight
text = VectorToString(skin->get_vertweight()[i]);
WriteAttrTxt(bone, "vertweight", text);
}
}
}
// write material
void mjXWriter::OneMaterial(XMLElement* elem, const mjCMaterial* material, mjCDef* def) {
// regular
if (!writingdefaults) {
WriteAttrTxt(elem, "name", material->name);
if (material->classname != "main") {
WriteAttrTxt(elem, "class", material->classname);
}
}
// defaults and regular
// check if we have non-rgb textures
bool has_non_rgb = false;
for (int i=1; i < mjNTEXROLE; i++) {
if (!material->textures_[i].empty()) {
if (i != mjTEXROLE_RGB) {
has_non_rgb = true;
}
}
}
// if we have non-rgb textures, write them as layers
if (has_non_rgb) {
for (int i=1; i < mjNTEXROLE; i++) {
if (!material->textures_[i].empty()) {
XMLElement * child_elem = InsertEnd(elem, "layer");
WriteAttrTxt(child_elem, "texture", material->textures_[i]);
WriteAttrTxt(child_elem, "role", FindValue(texrole_map, 9, i));
}
}
} else {
if (material->textures_[mjTEXROLE_RGB] != def->Material().textures_[mjTEXROLE_RGB]) {
WriteAttrTxt(elem, "texture", material->get_texture(mjTEXROLE_RGB));
}
}
WriteAttrTable(elem, static_cast<const mjsMaterial*>(material),
&def->Material().spec, kMaterialAttrs, kMaterialAttrsN);
}
// write joint
void mjXWriter::OneJoint(XMLElement* elem, const mjCJoint* joint, mjCDef* def,
string_view classname) {
// regular
if (!writingdefaults) {
WriteAttrTxt(elem, "name", joint->name);
if (classname != joint->classname && joint->classname != "main") {
WriteAttrTxt(elem, "class", joint->classname);
}
if (joint->type != mjJNT_FREE) {
WriteAttr(elem, "pos", 3, joint->pos);
}
if (joint->type != mjJNT_FREE && joint->type != mjJNT_BALL) {
WriteAttr(elem, "axis", 3, joint->axis);
}
}
// defaults and regular
WriteAttrTable(elem, static_cast<const mjsJoint*>(joint), &def->Joint().spec,
kJointAttrs, kJointAttrsN);
if (joint->type != mjJNT_FREE) {
WriteAttrKey(elem, "limited", FalseTrueAuto_map, 3, joint->limited, def->Joint().limited);
}
if (joint->type != mjJNT_FREE && joint->type != mjJNT_BALL) {
WriteAttrKey(elem, "actuatorfrclimited", FalseTrueAuto_map, 3, joint->actfrclimited,
def->Joint().actfrclimited);
}
// userdata
if (writingdefaults) {
WriteVector(elem, "user", joint->get_userdata());
} else {
WriteVector(elem, "user", joint->get_userdata(), def->Joint().get_userdata());
}
}
// write geom
void mjXWriter::OneGeom(XMLElement* elem, const mjCGeom* geom, mjCDef* def, string_view classname) {
double unitq[4] = {1, 0, 0, 0};
double mass = 0;
// regular
if (!writingdefaults) {
WriteAttrTxt(elem, "name", geom->name);
if (classname != geom->classname && geom->classname != "main") {
WriteAttrTxt(elem, "class", geom->classname);
}
if (mjGEOMINFO[geom->type]) {
WriteAttr(elem, "size", mjGEOMINFO[geom->type], geom->size, def->Geom().size);
}
if (mjuu_defined(geom->mass)) {
mass = geom->GetVolume() * def->Geom().density;
}
// mesh geom
if (geom->type == mjGEOM_MESH || geom->type == mjGEOM_SDF) {
mjCMesh* mesh = geom->mesh;
// write pos/quat if there is a difference
if (!SameVector(geom->pos, mesh->GetPosPtr(), 3) ||
!SameVector(geom->quat, mesh->GetQuatPtr(), 4)) {
// recover geom pos/quat before mesh frame transformation
double p[3], q[4];
mjuu_copyvec(p, geom->pos, 3);
mjuu_copyvec(q, geom->quat, 4);
mjuu_frameaccuminv(p, q, mesh->GetPosPtr(),
mesh->GetQuatPtr());
// write
WriteAttr(elem, "pos", 3, p, unitq+1);
WriteAttr(elem, "quat", 4, q, unitq);
}
}
// non-mesh geom
else {
WriteAttr(elem, "pos", 3, geom->pos, unitq+1);
WriteAttr(elem, "quat", 4, geom->quat, unitq);
}
} else {
WriteAttr(elem, "size", 3, geom->size, def->Geom().size);
}
// defaults and regular
WriteAttrTable(elem, static_cast<const mjsGeom*>(geom), &def->Geom().spec,
kGeomAttrs, kGeomAttrsN);
WriteAttrKey(elem, "fluidshape",
fluidshape_map, 2, geom->fluid_ellipsoid, def->Geom().fluid_ellipsoid);
if (geom->type != mjGEOM_MESH) {
WriteAttrKey(elem, "shellinertia", bool_map, 2, geom->typeinertia,
def->Geom().typeinertia);
}
if (mjuu_defined(geom->mass)) {
WriteAttr(elem, "mass", 1, &geom->mass_, &mass);
} else {
WriteAttr(elem, "density", 1, &geom->density, &def->Geom().density);
}
if (geom->get_material() != def->Geom().get_material()) {
WriteAttrTxt(elem, "material", geom->get_material());
}
// hfield and mesh attributes
if (geom->type == mjGEOM_HFIELD) {
WriteAttrTxt(elem, "hfield", geom->get_hfieldname());
}
if (geom->type == mjGEOM_MESH || geom->type == mjGEOM_SDF) {
WriteAttrTxt(elem, "mesh", geom->get_meshname());
}
// userdata
if (writingdefaults) {
WriteVector(elem, "user", geom->get_userdata());
} else {
WriteVector(elem, "user", geom->get_userdata(), def->Geom().get_userdata());
}
// write plugin
if (geom->plugin.active) {
OnePlugin(InsertEnd(elem, "plugin"), &geom->plugin);
}
}
// write site
void mjXWriter::OneSite(XMLElement* elem, const mjCSite* site, mjCDef* def, string_view classname) {
double unitq[4] = {1, 0, 0, 0};
// regular
if (!writingdefaults) {
WriteAttrTxt(elem, "name", site->name);
if (classname != site->classname && site->classname != "main") {
WriteAttrTxt(elem, "class", site->classname);
}
WriteAttr(elem, "quat", 4, site->quat, unitq);
if (mjGEOMINFO[site->type]) {
WriteAttr(elem, "size", mjGEOMINFO[site->type], site->size, def->Site().size);
}
} else {
WriteAttr(elem, "size", 3, site->size, def->Site().size);
}
// defaults and regular
WriteAttrTable(elem, static_cast<const mjsSite*>(site), &def->Site().spec,
kSiteAttrs, kSiteAttrsN);
if (site->get_material() != def->Site().get_material()) {
WriteAttrTxt(elem, "material", site->get_material());
}
// userdata
if (writingdefaults) {
WriteVector(elem, "user", site->get_userdata());
} else {
WriteVector(elem, "user", site->get_userdata(), def->Site().get_userdata());
}
}
// write camera
void mjXWriter::OneCamera(XMLElement* elem, const mjCCamera* camera, mjCDef* def,
string_view classname) {
double unitq[4] = {1, 0, 0, 0};
// regular
if (!writingdefaults) {
WriteAttrTxt(elem, "name", camera->name);
if (classname != camera->classname && camera->classname != "main") {
WriteAttrTxt(elem, "class", camera->classname);
}
WriteAttrTxt(elem, "target", camera->get_targetbody());
WriteAttr(elem, "quat", 4, camera->quat, unitq);
}
// defaults and regular
WriteAttrTable(elem, static_cast<const mjsCamera*>(camera),
&def->Camera().spec, kCameraAttrs, kCameraAttrsN);
// camera intrinsics if specified
if (camera->sensor_size[0] > 0 && camera->sensor_size[1] > 0) {
WriteAttr(elem, "sensorsize", 2, camera->sensor_size);
WriteAttr(elem, "focal", 2, camera->focal_length, def->Camera().focal_length);
WriteAttr(elem, "focalpixel", 2, camera->focal_pixel, def->Camera().focal_pixel);
WriteAttr(elem, "principal", 2, camera->principal_length, def->Camera().principal_length);
WriteAttr(elem, "principalpixel", 2, camera->principal_pixel, def->Camera().principal_pixel);
} else {
WriteAttr(elem, "fovy", 1, &camera->fovy, &def->Camera().fovy);
}
// userdata
if (writingdefaults) {
WriteVector(elem, "user", camera->get_userdata());
} else {
WriteVector(elem, "user", camera->get_userdata(), def->Camera().get_userdata());
}
}
// write light
void mjXWriter::OneLight(XMLElement* elem, const mjCLight* light, mjCDef* def,
string_view classname) {
// regular
if (!writingdefaults) {
WriteAttrTxt(elem, "name", light->name);
if (classname != light->classname && light->classname != "main") {
WriteAttrTxt(elem, "class", light->classname);
}
WriteAttrTxt(elem, "target", light->get_targetbody());
}
// defaults and regular
WriteAttrTable(elem, static_cast<const mjsLight*>(light), &def->Light().spec,
kLightAttrs, kLightAttrsN);
WriteAttrKey(elem, "type", lighttype_map, lighttype_sz, light->type, def->Light().type);
WriteAttrTxt(elem, "texture", light->get_texture());
}
// write pair
// write the mechanical attributes of an element, driven by the same
// generated rows the reader uses; see the declaration for the contract
template <typename T>
void mjXWriter::WriteAttrTable(XMLElement* elem, const T* obj, const T* def,
const mjXAttr* rows, int nrow) {
const char* live = reinterpret_cast<const char*>(obj);
const char* dflt = reinterpret_cast<const char*>(def);
for (int i = 0; i < nrow; i++) {
const mjXAttr& row = rows[i];
if (row.handwrite || (writingdefaults && row.nodefault)) {
continue;
}
const char* base = live + row.offset;
// a null default object means the element has no defaults to compare
// against: every defined value is written
const char* dbase = dflt ? dflt + row.offset : nullptr;
const int dkey = dflt ? 0 : -12345; // WriteAttrKey's write-always default
switch (row.kind) {
case mjXAttr::kInt:
WriteAttr(elem, row.attr, row.len, (const int*)base, (const int*)dbase,
/*trim=*/!row.exact);
break;
case mjXAttr::kDouble:
WriteAttr(elem, row.attr, row.len, (const double*)base,
(const double*)dbase, /*trim=*/!row.exact);
break;
case mjXAttr::kNum:
WriteAttr(elem, row.attr, row.len, (const mjtNum*)base,
(const mjtNum*)dbase, /*trim=*/!row.exact);
break;
case mjXAttr::kFloat:
WriteAttr(elem, row.attr, row.len, (const float*)base,
(const float*)dbase, /*trim=*/!row.exact);
break;
case mjXAttr::kEnum:
WriteAttrKey(elem, row.attr, row.map, row.mapsz, *(const int*)base,
dbase ? *(const int*)dbase : dkey);
break;
case mjXAttr::kEnumByte:
WriteAttrKey(elem, row.attr, row.map, row.mapsz, *(const mjtByte*)base,
dbase ? *(const mjtByte*)dbase : dkey);
break;
case mjXAttr::kBool:
WriteAttrKey(elem, row.attr, bool_map, 2, *(const mjtByte*)base,
dbase ? *(const mjtByte*)dbase : dkey);
break;
case mjXAttr::kFlags:
if (!dbase || *(const int*)base != *(const int*)dbase) {
int value = *(const int*)base;
std::vector<int> data;
for (int j = 0; j < row.mapsz; j++) {
if (value & row.map[j].value) {
data.push_back(row.map[j].value);
}
}
if (!data.empty()) {
WriteAttrKeys(elem, row.attr, row.map, row.mapsz, data.data(),
data.size(), 0);
}
}
break;
default:
// names, strings, files and custom-read attributes: OneX() remnant
break;
}
}
}
void mjXWriter::OnePair(XMLElement* elem, const mjCPair* pair, mjCDef* def) {
// regular
if (!writingdefaults) {
if (pair->classname != "main") {
WriteAttrTxt(elem, "class", pair->classname);
}
WriteAttrTxt(elem, "geom1", pair->get_geomname1());
WriteAttrTxt(elem, "geom2", pair->get_geomname2());
}
// defaults and regular
WriteAttrTxt(elem, "name", pair->name);
WriteAttrTable(elem, static_cast<const mjsPair*>(pair), &def->Pair().spec,
kPairAttrs, kPairAttrsN);
}
// write equality
void mjXWriter::OneEquality(XMLElement* elem, const mjCEquality* equality, mjCDef* def) {
// regular
if (!writingdefaults) {
WriteAttrTxt(elem, "name", equality->name);
if (equality->classname != "main") {
WriteAttrTxt(elem, "class", equality->classname);
}
switch (equality->type) {
case mjEQ_CONNECT:
if (equality->objtype == mjOBJ_BODY) {
WriteAttrTxt(elem, "body1", mjs_getString(equality->name1));
WriteAttrTxt(elem, "body2", mjs_getString(equality->name2));
WriteAttr(elem, "anchor", 3, equality->data);
} else {
WriteAttrTxt(elem, "site1", mjs_getString(equality->name1));
WriteAttrTxt(elem, "site2", mjs_getString(equality->name2));
}
break;
case mjEQ_WELD:
if (equality->objtype == mjOBJ_BODY) {
WriteAttrTxt(elem, "body1", mjs_getString(equality->name1));
WriteAttrTxt(elem, "body2", mjs_getString(equality->name2));
WriteAttr(elem, "anchor", 3, equality->data);
WriteAttr(elem, "relpose", 7, equality->data+3);
} else {
WriteAttrTxt(elem, "site1", mjs_getString(equality->name1));
WriteAttrTxt(elem, "site2", mjs_getString(equality->name2));
}
WriteAttr(elem, "torquescale", 1, equality->data+10);
break;
case mjEQ_JOINT:
WriteAttrTxt(elem, "joint1", mjs_getString(equality->name1));
WriteAttrTxt(elem, "joint2", mjs_getString(equality->name2));
WriteAttr(elem, "polycoef", 5, equality->data);
break;
case mjEQ_TENDON:
WriteAttrTxt(elem, "tendon1", mjs_getString(equality->name1));
WriteAttrTxt(elem, "tendon2", mjs_getString(equality->name2));
WriteAttr(elem, "polycoef", 5, equality->data);
break;
case mjEQ_FLEX:
case mjEQ_FLEXVERT:
WriteAttrTxt(elem, "flex", mjs_getString(equality->name1));
break;
case mjEQ_FLEXSTRAIN:
WriteAttrTxt(elem, "flex", mjs_getString(equality->name1));
WriteAttr(elem, "cell", 3, equality->data);
break;
default:
mju_error("mjXWriter: unknown equality type.");
}
}
// defaults and regular
WriteAttrTable(elem, static_cast<const mjsEquality*>(equality),
&def->Equality().spec, kEqualityBaseAttrs,
kEqualityBaseAttrsN);
}
// write tendon
void mjXWriter::OneTendon(XMLElement* elem, const mjCTendon* tendon, mjCDef* def) {
bool fixed = (tendon->GetWrap(0) && tendon->GetWrap(0)->Type() == mjWRAP_JOINT);
// regular
if (!writingdefaults) {
WriteAttrTxt(elem, "name", tendon->name);
if (tendon->classname != "main") {
WriteAttrTxt(elem, "class", tendon->classname);
}
}
// defaults and regular; the fixed rows are the spatial rows without the
// appearance attributes, which is exactly the tag difference
if (fixed) {
WriteAttrTable(elem, static_cast<const mjsTendon*>(tendon),
&def->Tendon().spec, kFixedAttrs, kFixedAttrsN);
} else {
WriteAttrTable(elem, static_cast<const mjsTendon*>(tendon),
&def->Tendon().spec, kSpatialAttrs, kSpatialAttrsN);
}
if (tendon->springlength[0] != tendon->springlength[1] ||
def->Tendon().springlength[0] != def->Tendon().springlength[1]) {
WriteAttr(elem, "springlength", 2, tendon->springlength, def->Tendon().springlength);
} else {
WriteAttr(elem, "springlength", 1, tendon->springlength, def->Tendon().springlength);
}
if (!fixed && tendon->get_material() != def->Tendon().get_material()) {
WriteAttrTxt(elem, "material", tendon->get_material());
}
// userdata
if (writingdefaults) {
WriteVector(elem, "user", tendon->get_userdata());
} else {
WriteVector(elem, "user", tendon->get_userdata(), def->Tendon().get_userdata());
}
}
// write actuator
void mjXWriter::OneActuator(XMLElement* elem, const mjCActuator* actuator, mjCDef* def) {
// regular
if (!writingdefaults) {
WriteAttrTxt(elem, "name", actuator->name);
if (actuator->classname != "main") {
WriteAttrTxt(elem, "class", actuator->classname);
}
// transmission target
switch (actuator->trntype) {
case mjTRN_JOINT:
WriteAttrTxt(elem, "joint", actuator->get_target());
break;
case mjTRN_JOINTINPARENT:
WriteAttrTxt(elem, "jointinparent", actuator->get_target());
break;
case mjTRN_TENDON:
WriteAttrTxt(elem, "tendon", actuator->get_target());
break;
case mjTRN_SLIDERCRANK:
WriteAttrTxt(elem, "cranksite", actuator->get_target());
WriteAttrTxt(elem, "slidersite", actuator->get_slidersite());
break;
case mjTRN_SITE:
WriteAttrTxt(elem, "site", actuator->get_target());
WriteAttrTxt(elem, "refsite", actuator->get_refsite());
break;
case mjTRN_BODY:
WriteAttrTxt(elem, "body", actuator->get_target());
break;
default: // SHOULD NOT OCCUR
break;
}
}
// defaults and regular
WriteAttrTable(elem, static_cast<const mjsActuator*>(actuator),
&def->Actuator().spec, kGeneralAttrs, kGeneralAttrsN);
WriteAttr(elem, "cranklength", 1, &actuator->cranklength, &def->Actuator().cranklength);
// special handling of actdim which has default value of -1
if (writingdefaults) {
WriteAttrInt(elem, "actdim", actuator->actdim, def->Actuator().actdim);
} else {
int default_actdim = (actuator->dyntype != mjDYN_NONE && actuator->dyntype != mjDYN_DCMOTOR);
WriteAttrInt(elem, "actdim", actuator->actdim, default_actdim);
}
// plugins: write config attributes
if (actuator->plugin.active) {
OnePlugin(elem, &actuator->plugin);
}
// non-plugins: write actuator parameters
else {
WriteAttrKey(elem, "gaintype", gain_map, gain_sz, actuator->gaintype, def->Actuator().gaintype);
if (actuator->gaintype == mjGAIN_SO3) {
WriteAttrKey(elem, "input", inputchart_map, inputchart_sz, actuator->ctrlspec,
def->Actuator().ctrlspec);
} else if (actuator->ctrlspec != def->Actuator().ctrlspec) {
std::string tokens;
for (int k=0; k < inputbit_sz; k++) {
if (actuator->ctrlspec & inputbit_map[k].value) {
tokens += std::string(tokens.empty() ? "" : " ") + inputbit_map[k].key;
}
}
WriteAttrTxt(elem, "input", tokens);
}
WriteAttrKey(elem, "biastype", bias_map, bias_sz, actuator->biastype, def->Actuator().biastype);
WriteAttr(elem, "gainprm", mjNGAIN, actuator->gainprm, def->Actuator().gainprm, true);
WriteAttr(elem, "biasprm", mjNBIAS, actuator->biasprm, def->Actuator().biasprm, true);
}
// userdata
if (writingdefaults) {
WriteVector(elem, "user", actuator->get_userdata());
} else {
WriteVector(elem, "user", actuator->get_userdata(), def->Actuator().get_userdata());
}
}
// write plugin
void mjXWriter::OnePlugin(XMLElement* elem, const mjsPlugin* plugin) {
const string instance_name = string(mjs_getString(plugin->name));
const string plugin_name = string(mjs_getString(plugin->plugin_name));
if (!instance_name.empty()) {
WriteAttrTxt(elem, "instance", instance_name);
} else {
WriteAttrTxt(elem, "plugin", plugin_name);
const mjpPlugin* pplugin = mjp_getPluginAtSlot(
static_cast<mjCPlugin*>(plugin->element)->plugin_slot);
const char* c = &(static_cast<mjCPlugin*>(plugin->element)->flattened_attributes[0]);
for (int i = 0; i < pplugin->nattribute; ++i) {
string value(c);
if (!value.empty()) {
XMLElement* config_elem = InsertEnd(elem, "config");
WriteAttrTxt(config_elem, "key", pplugin->attributes[i]);
WriteAttrTxt(config_elem, "value", value);
c += value.size();
}
++c;
}
}
}
//---------------------------------- class mjXWriter: top-level API --------------------------------
// constructor
mjXWriter::mjXWriter(void) {
writingdefaults = false;
}
// cast model
void mjXWriter::SetModel(mjSpec* _spec, const mjModel* m) {
if (_spec) {
model = static_cast<mjCModel*>(_spec->element);
}
if (m) {
mj_copyBack(&model->spec, m);
}
}
// save existing model in MJCF canonical format, must be compiled
string mjXWriter::Write(char *error, size_t error_sz) {
// check model
if (!model || !model->IsCompiled()) {
mjCopyError(error, "XML Write error: Only compiled model can be written", error_sz);
return "";
}
// create document and root
XMLDocument doc;
XMLElement* root = doc.NewElement("mujoco");
root->SetAttribute("model", mjs_getString(model->modelname));
// insert root
doc.InsertFirstChild(root);
// write comment if present
string text = mjs_getString(model->comment);
if (!text.empty()) {
XMLComment* comment = doc.NewComment(text.c_str());
root->LinkEndChild(comment);
}
// create DOM
Compiler(root);
Option(root);
Size(root);
Statistic(root);
Visual(root);
writingdefaults = true;
Default(root, model->Default());
writingdefaults = false;
Extension(root);
Asset(root);
Body(InsertEnd(root, "worldbody"), model->GetWorld(), nullptr);
Deformable(root);
Contact(root);
Tendon(root);
Equality(root);
Actuator(root);
Sensor(root);
Custom(root);
Keyframe(root);
return WriteDoc(doc, error, error_sz);
}
// compiler section
void mjXWriter::Compiler(XMLElement* root) {
XMLElement* section = InsertEnd(root, "compiler");
// settings
WriteAttrTxt(section, "angle", "radian");
if (!model->get_meshdir().empty()) {
WriteAttrTxt(section, "meshdir", model->get_meshdir());
}
if (!model->get_texturedir().empty()) {
WriteAttrTxt(section, "texturedir", model->get_texturedir());
}
if (!model->compiler.usethread) {
WriteAttrTxt(section, "usethread", "false");
}
if (model->compiler.boundmass) {
WriteAttr(section, "boundmass", 1, &model->compiler.boundmass);
}
if (model->compiler.boundinertia) {
WriteAttr(section, "boundinertia", 1, &model->compiler.boundinertia);
}
if (model->compiler.alignfree) {
WriteAttrTxt(section, "alignfree", "true");
}
if (!model->compiler.autolimits) {
WriteAttrTxt(section, "autolimits", "false");
}
WriteAttrKey(section, "conflict", conflict_map, conflict_sz,
model->compiler.conflict, mjCONFLICT_WARNING);
}
// option section
void mjXWriter::Option(XMLElement* root) {
mjOption opt;
mj_defaultOption(&opt);
XMLElement* section = InsertEnd(root, "option");
// option; the freshly-defaulted struct is the comparison object
WriteAttrTable(section, &model->option, &opt, kOptionAttrs, kOptionAttrsN);
// actuator group disable
int disabled_groups[31];
int ndisabled = 0;
for (int i = 0; i < 31; ++i) {
if (model->option.disableactuator & (1 << i)) {
disabled_groups[ndisabled++] = i;
}
}
WriteAttr(section, "actuatorgroupdisable", ndisabled, disabled_groups);
// write disable/enable flags if any of them are set; invert while writing
if (model->option.disableflags || model->option.enableflags) {
XMLElement* sub = InsertEnd(section, "flag");
#define WRITEDSBL(NAME, MASK) \
if (model->option.disableflags & MASK) \
WriteAttrKey(sub, NAME, enable_map, 2, 0);
WRITEDSBL("constraint", mjDSBL_CONSTRAINT)
WRITEDSBL("equality", mjDSBL_EQUALITY)
WRITEDSBL("frictionloss", mjDSBL_FRICTIONLOSS)
WRITEDSBL("limit", mjDSBL_LIMIT)
WRITEDSBL("contact", mjDSBL_CONTACT)
WRITEDSBL("spring", mjDSBL_SPRING)
WRITEDSBL("damper", mjDSBL_DAMPER)
WRITEDSBL("gravity", mjDSBL_GRAVITY)
WRITEDSBL("clampctrl", mjDSBL_CLAMPCTRL)
WRITEDSBL("warmstart", mjDSBL_WARMSTART)
WRITEDSBL("filterparent", mjDSBL_FILTERPARENT)
WRITEDSBL("actuation", mjDSBL_ACTUATION)
WRITEDSBL("refsafe", mjDSBL_REFSAFE)
WRITEDSBL("sensor", mjDSBL_SENSOR)
WRITEDSBL("midphase", mjDSBL_MIDPHASE)
WRITEDSBL("eulerdamp", mjDSBL_EULERDAMP)
WRITEDSBL("autoreset", mjDSBL_AUTORESET)
WRITEDSBL("nativeccd", mjDSBL_NATIVECCD)
WRITEDSBL("island", mjDSBL_ISLAND)
WRITEDSBL("multiccd", mjDSBL_MULTICCD)
#undef WRITEDSBL
#define WRITEENBL(NAME, MASK) \
if (model->option.enableflags & MASK) \
WriteAttrKey(sub, NAME, enable_map, 2, 1);
WRITEENBL("override", mjENBL_OVERRIDE)
WRITEENBL("energy", mjENBL_ENERGY)
WRITEENBL("fwdinv", mjENBL_FWDINV)
WRITEENBL("invdiscrete", mjENBL_INVDISCRETE)
WRITEENBL("sleep", mjENBL_SLEEP)
WRITEENBL("diagexact", mjENBL_DIAGEXACT)
#undef WRITEENBL
}
// remove entire section if no attributes or elements
if (!section->FirstAttribute() && !section->FirstChildElement()) {
root->DeleteChild(section);
}
}
// size section
void mjXWriter::Size(XMLElement* root) {
XMLElement* section = InsertEnd(root, "size");
// write memory
if (model->memory != -1) {
WriteAttrTxt(section, "memory", mju_writeNumBytes(model->memory));
}
// deprecated sizes, hand-read into locals with range checks
WriteAttrInt(section, "njmax", model->njmax, -1);
WriteAttrInt(section, "nconmax", model->nconmax, -1);
WriteAttrInt(section, "nstack", model->nstack, -1);
// write sizes; the spec defaults are -1 (auto), but compilation resolves
// them to the actual counts, so the comparison object is zero-initialized
mjSpec zerospec = {};
WriteAttrTable(section, static_cast<const mjSpec*>(model), &zerospec,
kSizeAttrs, kSizeAttrsN);
// remove entire section if no attributes
if (!section->FirstAttribute()) root->DeleteChild(section);
}
// statistic section
void mjXWriter::Statistic(XMLElement* root) {
XMLElement* section = InsertEnd(root, "statistic");
// statistics are unset (mjNAN) rather than defaulted: there is nothing to
// compare against, and WriteAttr skips the undefined values by itself
WriteAttrTable(section, &model->stat, (const mjStatistic*)nullptr,
kStatisticAttrs, kStatisticAttrsN);
// remove entire section if no attributes
if (!section->FirstAttribute()) root->DeleteChild(section);
}
// visual section
void mjXWriter::Visual(XMLElement* root) {
mjVisual visdef, *vis = &model->visual;
mj_defaultVisual(&visdef);
XMLElement* section = InsertEnd(root, "visual");
// the sub-sections are projections into mjVisual: their rows carry
// member-path offsets, so one struct pair drives them all
struct { const char* tag; const mjXAttr* rows; int n; } subs[] = {
{"global", kGlobalAttrs, kGlobalAttrsN},
{"quality", kQualityAttrs, kQualityAttrsN},
{"headlight", kHeadlightAttrs, kHeadlightAttrsN},
{"map", kMapAttrs, kMapAttrsN},
{"scale", kScaleAttrs, kScaleAttrsN},
{"rgba", kRgbaAttrs, kRgbaAttrsN},
};
for (const auto& sub : subs) {
XMLElement* elem = InsertEnd(section, sub.tag);
WriteAttrTable(elem, vis, &visdef, sub.rows, sub.n);
if (!elem->FirstAttribute()) {
section->DeleteChild(elem);
}
}
// remove entire section if no elements
if (!section->FirstChildElement()) {
root->DeleteChild(section);
}
}
// default section
void mjXWriter::Default(XMLElement* root, mjCDef* def) {
XMLElement* elem;
XMLElement* section;
// pointer to parent defaults
mjCDef* parent;
if (def->parent) {
parent = def->parent;
} else {
parent = new mjCDef;
}
// create section, write class name
section = InsertEnd(root, "default");
if (def->name != "main") {
WriteAttrTxt(section, "class", def->name);
}
// mesh
elem = InsertEnd(section, "mesh");
OneMesh(elem, &def->Mesh(), parent);
if (!elem->FirstAttribute()) section->DeleteChild(elem);
// material
elem = InsertEnd(section, "material");
OneMaterial(elem, &def->Material(), parent);
if (!elem->FirstAttribute()) section->DeleteChild(elem);
// joint
elem = InsertEnd(section, "joint");
OneJoint(elem, &def->Joint(), parent);
if (!elem->FirstAttribute()) section->DeleteChild(elem);
// geom
elem = InsertEnd(section, "geom");
OneGeom(elem, &def->Geom(), parent);
if (!elem->FirstAttribute()) section->DeleteChild(elem);
// site
elem = InsertEnd(section, "site");
OneSite(elem, &def->Site(), parent);
if (!elem->FirstAttribute()) section->DeleteChild(elem);
// camera
elem = InsertEnd(section, "camera");
OneCamera(elem, &def->Camera(), parent);
if (!elem->FirstAttribute()) section->DeleteChild(elem);
// light
elem = InsertEnd(section, "light");
OneLight(elem, &def->Light(), parent);
if (!elem->FirstAttribute()) section->DeleteChild(elem);
// pair
elem = InsertEnd(section, "pair");
OnePair(elem, &def->Pair(), parent);
if (!elem->FirstAttribute()) section->DeleteChild(elem);
// equality
elem = InsertEnd(section, "equality");
OneEquality(elem, &def->Equality(), parent);
if (!elem->FirstAttribute()) section->DeleteChild(elem);
// tendon
elem = InsertEnd(section, "tendon");
OneTendon(elem, &def->Tendon(), parent);
if (!elem->FirstAttribute()) section->DeleteChild(elem);
// actuator
elem = InsertEnd(section, "general");
OneActuator(elem, &def->Actuator(), parent);
if (!elem->FirstAttribute()) section->DeleteChild(elem);
// if top-level class has no members or children, delete it and return
if (!def->parent && section->NoChildren() && def->child.empty()) {
root->DeleteChild(section);
delete parent;
return;
}
// add children recursively
for (int i=0; i < (int)def->child.size(); i++) {
Default(section, def->child[i]);
}
// delete parent defaults if allocated here
if (!def->parent) {
delete parent;
}
}
// extension section
void mjXWriter::Extension(XMLElement* root) {
// skip section if there is no required plugin
if (model->ActivePlugins().empty()) {
return;
}
// create section
XMLElement* section = InsertEnd(root, "extension");
// keep track of plugins whose <plugin> section have been created
std::unordered_set<const mjpPlugin*> seen_plugins;
// write all plugins
const mjpPlugin* last_plugin = nullptr;
XMLElement* plugin_elem = nullptr;
for (int i = 0; i < model->Plugins().size(); ++i) {
mjCPlugin* pp = static_cast<mjCPlugin*>(model->GetObject(mjOBJ_PLUGIN, i));
if (pp->name.empty()) {
// reached the first unnamed plugin instance, meaning that it was created through an
// "implicit" plugin element, e.g. sensor or actuator
break;
}
// check if we need to open a new <plugin> section
const mjpPlugin* plugin = mjp_getPluginAtSlot(pp->plugin_slot);
if (plugin != last_plugin) {
plugin_elem = InsertEnd(section, "plugin");
WriteAttrTxt(plugin_elem, "plugin", plugin->name);
seen_plugins.insert(plugin);
last_plugin = plugin;
}
// write instance element
XMLElement* elem = InsertEnd(plugin_elem, "instance");
WriteAttrTxt(elem, "name", pp->name);
// write plugin config attributes
const char* c = &pp->flattened_attributes[0];
for (int i = 0; i < plugin->nattribute; ++i) {
string value(c);
if (!value.empty()) {
XMLElement* config_elem = InsertEnd(elem, "config");
WriteAttrTxt(config_elem, "key", plugin->attributes[i]);
WriteAttrTxt(config_elem, "value", value);
c += value.size();
}
++c;
}
}
// write <plugin> elements for plugins without explicit instances
for (const auto& [plugin, slot] : model->ActivePlugins()) {
if (seen_plugins.find(plugin) == seen_plugins.end()) {
plugin_elem = InsertEnd(section, "plugin");
WriteAttrTxt(plugin_elem, "plugin", plugin->name);
}
}
}
// custom section
void mjXWriter::Custom(XMLElement* root) {
XMLElement* elem;
// get sizes, skip section if empty
int nnum = model->NumObjects(mjOBJ_NUMERIC);
int ntxt = model->NumObjects(mjOBJ_TEXT);
int ntup = model->NumObjects(mjOBJ_TUPLE);
// skip section if empty
if (nnum == 0 && ntxt == 0 && ntup == 0) {
return;
}
// create section
XMLElement* section = InsertEnd(root, "custom");
// write all numerics
for (int i=0; i < nnum; i++) {
mjCNumeric* numeric = (mjCNumeric*)model->GetObject(mjOBJ_NUMERIC, i);
elem = InsertEnd(section, "numeric");
WriteAttrTxt(elem, "name", numeric->name);
WriteAttrInt(elem, "size", numeric->size);
WriteAttr(elem, "data", numeric->size, numeric->data_.data());
}
// write all texts
for (int i=0; i < ntxt; i++) {
mjCText* text = (mjCText*)model->GetObject(mjOBJ_TEXT, i);
elem = InsertEnd(section, "text");
WriteAttrTxt(elem, "name", text->name);
WriteAttrTxt(elem, "data", text->data_.c_str());
}
// write all tuples
for (int i=0; i < ntup; i++) {
mjCTuple* tuple = (mjCTuple*)model->GetObject(mjOBJ_TUPLE, i);
elem = InsertEnd(section, "tuple");
WriteAttrTxt(elem, "name", tuple->name);
// write objects in tuple
for (int j=0; j < (int)tuple->objtype_.size(); j++) {
XMLElement* obj = InsertEnd(elem, "element");
WriteAttrTxt(obj, "objtype", mju_type2Str((int)tuple->objtype_[j]));
WriteAttrTxt(obj, "objname", tuple->objname_[j].c_str());
double oprm = tuple->objprm_[j];
if (oprm != 0) {
WriteAttr(obj, "prm", 1, &oprm);
}
}
}
}
// asset section
void mjXWriter::Asset(XMLElement* root) {
XMLElement* elem;
// get sizes
int ntex = model->NumObjects(mjOBJ_TEXTURE);
int nmat = model->NumObjects(mjOBJ_MATERIAL);
int nmesh = model->NumObjects(mjOBJ_MESH);
int nhfield = model->NumObjects(mjOBJ_HFIELD);
// return if empty
if (ntex == 0 && nmat == 0 && nmesh == 0 && nhfield == 0) {
return;
}
// create section
XMLElement* section = InsertEnd(root, "asset");
// write textures
mjCTexture deftex(0);
for (int i=0; i < ntex; i++) {
// create element
mjCTexture* texture = (mjCTexture*)model->GetObject(mjOBJ_TEXTURE, i);
elem = InsertEnd(section, "texture");
// write common attributes
WriteAttrKey(elem, "type", texture_map, texture_sz, texture->type);
WriteAttrKey(elem, "colorspace", colorspace_map, colorspace_sz, texture->colorspace);
WriteAttrTxt(elem, "name", texture->name);
// write builtin
if (texture->builtin != mjBUILTIN_NONE) {
WriteAttrKey(elem, "builtin", builtin_map, builtin_sz, texture->builtin);
WriteAttrKey(elem, "mark", mark_map, mark_sz, texture->mark, deftex.mark);
WriteAttr(elem, "rgb1", 3, texture->rgb1, deftex.rgb1);
WriteAttr(elem, "rgb2", 3, texture->rgb2, deftex.rgb2);
WriteAttr(elem, "markrgb", 3, texture->markrgb, deftex.markrgb);
WriteAttr(elem, "random", 1, &texture->random, &deftex.random);
WriteAttrInt(elem, "width", texture->width);
WriteAttrInt(elem, "height", texture->height);
}
// write buffer
else if (texture->get_cubefiles()[0].empty() && texture->get_cubefiles()[1].empty() &&
texture->get_cubefiles()[2].empty() && texture->get_cubefiles()[3].empty() &&
texture->get_cubefiles()[4].empty() && texture->get_cubefiles()[5].empty() &&
texture->File().empty() && texture->gridsize[0] == 1 && texture->gridsize[1] == 1) {
throw mjXError(0, "no support for buffer textures.");
}
// write textures loaded from files
else {
// write single file
WriteAttrTxt(elem, "content_type", texture->get_content_type());
WriteAttrTxt(elem, "file", texture->File());
// write separate files
WriteAttrTxt(elem, "fileright", texture->get_cubefiles()[0]);
WriteAttrTxt(elem, "fileleft", texture->get_cubefiles()[1]);
WriteAttrTxt(elem, "fileup", texture->get_cubefiles()[2]);
WriteAttrTxt(elem, "filedown", texture->get_cubefiles()[3]);
WriteAttrTxt(elem, "filefront", texture->get_cubefiles()[4]);
WriteAttrTxt(elem, "fileback", texture->get_cubefiles()[5]);
if (texture->hflip) {
WriteAttrKey(elem, "hflip", bool_map, 2, 1);
}
if (texture->vflip) {
WriteAttrKey(elem, "vflip", bool_map, 2, 1);
}
// write grid
if (texture->gridsize[0] != 1 || texture->gridsize[1] != 1) {
double gsize[2] = { (double)texture->gridsize[0], (double)texture->gridsize[1] };
WriteAttr(elem, "gridsize", 2, gsize);
WriteAttrTxt(elem, "gridlayout", texture->gridlayout);
}
}
}
// write materials
for (int i=0; i < nmat; i++) {
// create element and write
mjCMaterial* material = (mjCMaterial*)model->GetObject(mjOBJ_MATERIAL, i);
elem = InsertEnd(section, "material");
OneMaterial(elem, material, model->def_map[material->classname]);
}
// write meshes
for (int i=0; i < nmesh; i++) {
// create element and write
mjCMesh* mesh = (mjCMesh*)model->GetObject(mjOBJ_MESH, i);
if (mesh->Plugin().active) {
elem = InsertEnd(section, "mesh");
WriteAttrTxt(elem, "name", mesh->name);
WriteAttrTxt(elem, "file", mesh->File());
OnePlugin(InsertEnd(elem, "plugin"), &mesh->Plugin());
} else{
elem = InsertEnd(section, "mesh");
OneMesh(elem, mesh, model->def_map[mesh->classname]);
}
}
// write hfields
for (int i=0; i < nhfield; i++) {
// create element
mjCHField* hfield = (mjCHField*)model->GetObject(mjOBJ_HFIELD, i);
elem = InsertEnd(section, "hfield");
// write attributes
WriteAttrTxt(elem, "name", hfield->name);
WriteAttr(elem, "size", 4, hfield->size);
if (!hfield->file_.empty()) {
WriteAttrTxt(elem, "content_type", hfield->content_type_);
WriteAttrTxt(elem, "file", hfield->file_);
} else {
int nrow = hfield->nrow;
int ncol = hfield->ncol;
WriteAttrInt(elem, "nrow", nrow);
WriteAttrInt(elem, "ncol", ncol);
if (!hfield->get_userdata().empty()) {
// copy in reverse row order, so XML string is top-to-bottom
std::vector<float> flipped(nrow * ncol);
const std::vector<float>& userdata = hfield->get_userdata();
for (int i = 0; i < nrow; i++) {
int flip = nrow - 1 - i;
for (int j = 0; j < ncol; j++) {
flipped[i * ncol + j] = userdata[flip * ncol + j];
}
}
string text;
Vector2String(text, flipped, ncol);
WriteAttrTxt(elem, "elevation", text);
}
}
}
}
XMLElement* mjXWriter::OneFrame(XMLElement* elem, mjCFrame* frame) {
if (!frame) {
return elem;
}
// TODO: empty classname should not occur (but does)
if (frame->name.empty() && (frame->classname.empty() || frame->classname == "main")) {
return elem;
}
XMLElement* frame_elem = InsertEnd(elem, "frame");
WriteAttrTxt(frame_elem, "name", frame->name);
if (frame->classname != "main") {
WriteAttrTxt(frame_elem, "childclass", frame->classname);
}
return frame_elem;
}
// recursive body and frame writer
void mjXWriter::Body(XMLElement* elem, mjCBody* body, mjCFrame* frame, string_view childclass) {
double unitq[4] = {1, 0, 0, 0};
if (!body) {
throw mjXError(0, "missing body in XML write"); // SHOULD NOT OCCUR
}
// write body attributes and inertial
else if (!frame && body != model->GetWorld()) {
WriteAttrTxt(elem, "name", body->name);
if (childclass != body->classname && body->classname != "main") {
WriteAttrTxt(elem, "childclass", body->classname);
}
// write pos if it's not {0, 0, 0}
if (body->pos[0] || body->pos[1] || body->pos[2]) {
WriteAttr(elem, "pos", 3, body->pos);
}
WriteAttr(elem, "quat", 4, body->quat, unitq);
if (body->mocap) {
WriteAttrKey(elem, "mocap", bool_map, 2, 1);
}
// gravity compensation
if (body->gravcomp) {
WriteAttr(elem, "gravcomp", 1, &body->gravcomp);
}
// sleep policy
if (body->sleep != mjSLEEP_AUTO &&
body->sleep != mjSLEEP_AUTO_NEVER &&
body->sleep != mjSLEEP_AUTO_ALLOWED) {
WriteAttrKey(elem, "sleep", bodysleep_map, bodysleep_sz, body->sleep);
}
// simple optimization
WriteAttrKey(elem, "simple", FalseAuto_map, 2, body->simple, 1);
// userdata
WriteVector(elem, "user", body->get_userdata());
// write inertial
if (model->compiler.saveinertial ||
(body->explicitinertial && model->compiler.inertiafromgeom != mjINERTIAFROMGEOM_TRUE)) {
XMLElement* inertial = InsertEnd(elem, "inertial");
WriteAttr(inertial, "pos", 3, body->ipos);
WriteAttr(inertial, "quat", 4, body->iquat, unitq);
WriteAttr(inertial, "mass", 1, &body->mass);
WriteAttr(inertial, "diaginertia", 3, body->inertia);
}
}
// joints in this frame
for (int i = 0; i < body->joints.size(); i++) {
if (body->joints[i]->frame != frame) {
continue;
}
string classname = body->joints[i]->frame && !body->joints[i]->frame->classname.empty()
? body->joints[i]->frame->classname
: body->classname;
OneJoint(InsertEnd(elem, "joint"), body->joints[i],
model->def_map[body->joints[i]->classname],
classname.empty() ? childclass : classname);
}
// geoms in this frame
for (int i = 0; i < body->geoms.size(); i++) {
if (body->geoms[i]->frame != frame) {
continue;
}
string classname = body->geoms[i]->frame && !body->geoms[i]->frame->classname.empty()
? body->geoms[i]->frame->classname
: body->classname;
OneGeom(InsertEnd(elem, "geom"), body->geoms[i],
model->def_map[body->geoms[i]->classname],
classname.empty() ? childclass : classname);
}
// sites in this frame
for (int i = 0; i < body->sites.size(); i++) {
if (body->sites[i]->frame != frame) {
continue;
}
string classname = body->sites[i]->frame && !body->sites[i]->frame->classname.empty()
? body->sites[i]->frame->classname
: body->classname;
OneSite(InsertEnd(elem, "site"), body->sites[i],
model->def_map[body->sites[i]->classname],
classname.empty() ? childclass : classname);
}
// cameras in this frame
for (int i = 0; i < body->cameras.size(); i++) {
if (body->cameras[i]->frame != frame) {
continue;
}
string classname = body->cameras[i]->frame && !body->cameras[i]->frame->classname.empty()
? body->cameras[i]->frame->classname
: body->classname;
OneCamera(InsertEnd(elem, "camera"), body->cameras[i],
model->def_map[body->cameras[i]->classname],
classname.empty() ? childclass : classname);
}
// lights in this frame
for (int i = 0; i < body->lights.size(); i++) {
if (body->lights[i]->frame != frame) {
continue;
}
string classname = body->lights[i]->frame && !body->lights[i]->frame->classname.empty()
? body->lights[i]->frame->classname
: body->classname;
OneLight(InsertEnd(elem, "light"), body->lights[i],
model->def_map[body->lights[i]->classname],
classname.empty() ? childclass : classname);
}
// write plugin
if (body->plugin.active) {
OnePlugin(InsertEnd(elem, "plugin"), &body->plugin);
}
// write children recursively
int i = 0, j = 0;
while (i < body->bodies.size() || body->bodies.empty()) {
mjCFrame* bframe = body->bodies.empty() ? nullptr : body->bodies[i]->frame;
// write body if its frame matches the current frame, avoid access if there are no bodies
if (bframe == frame && !body->bodies.empty()) {
string classname = bframe && !bframe->classname.empty()
? bframe->classname
: body->classname;
Body(InsertEnd(elem, "body"), body->bodies[i], nullptr,
classname.empty() ? childclass : classname);
}
i++;
// do not go to frames until we reach a body with a frame or we are done with bodies
if (!bframe && i < body->bodies.size()) {
continue;
}
// loop over the remaining frames in the current body
while (j < body->frames.size()) {
mjCFrame* fframe = body->frames[j++];
// write frame if its frame matches the current frame
if (fframe->frame == frame) {
string classname = fframe && !fframe->classname.empty()
? fframe->classname
: body->classname;
Body(OneFrame(elem, fframe), body, fframe, childclass);
}
}
// if there are no bodies, we only want to run the loop once
if (body->bodies.empty()) {
break;
}
}
}
// collision section
void mjXWriter::Contact(XMLElement* root) {
XMLElement* elem;
// get number of pairs of each type
int npair = model->NumObjects(mjOBJ_PAIR);
int nexclude = model->NumObjects(mjOBJ_EXCLUDE);
// skip if section is empty
if (npair == 0 && nexclude == 0) {
return;
}
// create section
XMLElement* section = InsertEnd(root, "contact");
// write all geom pairs
for (int i=0; i < npair; i++) {
// create element and write
mjCPair* pair = (mjCPair*)model->GetObject(mjOBJ_PAIR, i);
elem = InsertEnd(section, "pair");
OnePair(elem, pair, model->def_map[pair->classname]);
}
// write all exclude pairs
for (int i=0; i < nexclude; i++) {
// create element
mjCBodyPair* exclude = (mjCBodyPair*)model->GetObject(mjOBJ_EXCLUDE, i);
elem = InsertEnd(section, "exclude");
// write attributes
WriteAttrTxt(elem, "name", exclude->name);
WriteAttrTxt(elem, "body1", exclude->get_bodyname1());
WriteAttrTxt(elem, "body2", exclude->get_bodyname2());
}
}
// constraint section
void mjXWriter::Equality(XMLElement* root) {
// skip section if empty
int num;
if ((num=model->NumObjects(mjOBJ_EQUALITY)) == 0) {
return;
}
// create section
XMLElement* section = InsertEnd(root, "equality");
// write all constraints
for (int i=0; i < num; i++) {
mjCEquality* equality = (mjCEquality*)model->GetObject(mjOBJ_EQUALITY, i);
XMLElement* elem = InsertEnd(section,
FindValue(equality_map, equality_sz, equality->type).c_str());
OneEquality(elem, equality, model->def_map[equality->classname]);
}
}
// deformable section
void mjXWriter::Deformable(XMLElement* root) {
XMLElement* elem;
// get sizes
int nflex = model->NumObjects(mjOBJ_FLEX);
int nskin = model->NumObjects(mjOBJ_SKIN);
// return if empty
if (nflex == 0 && nskin == 0) {
return;
}
// create section
XMLElement* section = InsertEnd(root, "deformable");
// write flexes
for (int i=0; i < nflex; i++) {
// create element and write
mjCFlex* flex = (mjCFlex*)model->GetObject(mjOBJ_FLEX, i);
elem = InsertEnd(section, "flex");
OneFlex(elem, flex);
}
// write skins
for (int i=0; i < nskin; i++) {
// create element and write
mjCSkin* skin = (mjCSkin*)model->GetObject(mjOBJ_SKIN, i);
elem = InsertEnd(section, "skin");
OneSkin(elem, skin);
}
}
// tendon section
void mjXWriter::Tendon(XMLElement* root) {
// skip section if empty
int num;
if ((num=model->NumObjects(mjOBJ_TENDON)) == 0) {
return;
}
// create section
XMLElement* section = InsertEnd(root, "tendon");
// write all tendons
for (int i=0; i < num; i++) {
// write tendon element and attributes
mjCTendon* tendon = (mjCTendon*)model->GetObject(mjOBJ_TENDON, i);
if (!tendon->NumWraps()) { // SHOULD NOT OCCUR
continue;
}
XMLElement* elem = InsertEnd(section,
tendon->GetWrap(0)->Type() == mjWRAP_JOINT ? "fixed" : "spatial");
OneTendon(elem, tendon, model->def_map[tendon->classname]);
// write wraps
XMLElement* wrapelem;
for (int j=0; j < tendon->NumWraps(); j++) {
const mjCWrap* wrap = tendon->GetWrap(j);
switch (wrap->Type()) {
case mjWRAP_JOINT:
wrapelem = InsertEnd(elem, "joint");
WriteAttrTxt(wrapelem, "joint", wrap->obj->name);
WriteAttr(wrapelem, "coef", 1, &wrap->prm);
break;
case mjWRAP_SITE:
wrapelem = InsertEnd(elem, "site");
WriteAttrTxt(wrapelem, "site", wrap->obj->name);
break;
case mjWRAP_SPHERE:
case mjWRAP_CYLINDER:
wrapelem = InsertEnd(elem, "geom");
WriteAttrTxt(wrapelem, "geom", wrap->obj->name);
if (!wrap->sidesite.empty()) {
WriteAttrTxt(wrapelem, "sidesite", wrap->sidesite);
}
break;
case mjWRAP_PULLEY:
wrapelem = InsertEnd(elem, "pulley");
WriteAttr(wrapelem, "divisor", 1, &wrap->prm);
break;
default:
break;
}
}
}
}
// actuator section
void mjXWriter::Actuator(XMLElement* root) {
// skip section if empty
int num;
if ((num=model->NumObjects(mjOBJ_ACTUATOR)) == 0) {
return;
}
// create section
XMLElement* section = InsertEnd(root, "actuator");
// write all actuators
for (int i=0; i < num; i++) {
mjCActuator* actuator = (mjCActuator*)model->GetObject(mjOBJ_ACTUATOR, i);
XMLElement* elem;
if (actuator->plugin.active) {
elem = InsertEnd(section, "plugin");
} else {
elem = InsertEnd(section, "general");
}
OneActuator(elem, actuator, model->def_map[actuator->classname]);
}
}
// sensor section
void mjXWriter::Sensor(XMLElement* root) {
double zero = 0;
// skip section if empty
int num;
if ((num=model->NumObjects(mjOBJ_SENSOR)) == 0) {
return;
}
// create section
XMLElement* section = InsertEnd(root, "sensor");
// write all sensors
for (int i=0; i < num; i++) {
XMLElement* elem = 0;
mjCSensor* sensor = model->Sensors()[i];
string instance_name = "";
string plugin_name = "";
// write sensor type and type-specific attributes
switch (sensor->type) {
// common robotic sensors, attached to a site
case mjSENS_TOUCH:
elem = InsertEnd(section, "touch");
WriteAttrTxt(elem, "site", sensor->get_objname());
break;
case mjSENS_ACCELEROMETER:
elem = InsertEnd(section, "accelerometer");
WriteAttrTxt(elem, "site", sensor->get_objname());
break;
case mjSENS_VELOCIMETER:
elem = InsertEnd(section, "velocimeter");
WriteAttrTxt(elem, "site", sensor->get_objname());
break;
case mjSENS_GYRO:
elem = InsertEnd(section, "gyro");
WriteAttrTxt(elem, "site", sensor->get_objname());
break;
case mjSENS_FORCE:
elem = InsertEnd(section, "force");
WriteAttrTxt(elem, "site", sensor->get_objname());
break;
case mjSENS_TORQUE:
elem = InsertEnd(section, "torque");
WriteAttrTxt(elem, "site", sensor->get_objname());
break;
case mjSENS_MAGNETOMETER:
elem = InsertEnd(section, "magnetometer");
WriteAttrTxt(elem, "site", sensor->get_objname());
break;
case mjSENS_RANGEFINDER:
{
elem = InsertEnd(section, "rangefinder");
if (sensor->objtype == mjOBJ_SITE) {
WriteAttrTxt(elem, "site", sensor->get_objname());
} else {
WriteAttrTxt(elem, "camera", sensor->get_objname());
}
int dataspec = sensor->intprm[0];
int data[mjNRAYDATA];
int ndata = 0;
for (int i=0; i < mjNRAYDATA; i++) {
if (dataspec & (1 << i)) {
data[ndata++] = i;
}
}
WriteAttrKeys(elem, "data", raydata_map, mjNRAYDATA, data, ndata, 0);
}
break;
case mjSENS_CAMPROJECTION:
elem = InsertEnd(section, "camprojection");
WriteAttrTxt(elem, "site", sensor->get_objname());
WriteAttrTxt(elem, "camera", sensor->get_refname());
break;
// sensors related to scalar joints, tendons, actuators
case mjSENS_JOINTPOS:
elem = InsertEnd(section, "jointpos");
WriteAttrTxt(elem, "joint", sensor->get_objname());
break;
case mjSENS_JOINTVEL:
elem = InsertEnd(section, "jointvel");
WriteAttrTxt(elem, "joint", sensor->get_objname());
break;
case mjSENS_TENDONPOS:
elem = InsertEnd(section, "tendonpos");
WriteAttrTxt(elem, "tendon", sensor->get_objname());
break;
case mjSENS_TENDONVEL:
elem = InsertEnd(section, "tendonvel");
WriteAttrTxt(elem, "tendon", sensor->get_objname());
break;
case mjSENS_ACTUATORPOS:
elem = InsertEnd(section, "actuatorpos");
WriteAttrTxt(elem, "actuator", sensor->get_objname());
break;
case mjSENS_ACTUATORVEL:
elem = InsertEnd(section, "actuatorvel");
WriteAttrTxt(elem, "actuator", sensor->get_objname());
break;
case mjSENS_ACTUATORFRC:
elem = InsertEnd(section, "actuatorfrc");
WriteAttrTxt(elem, "actuator", sensor->get_objname());
break;
case mjSENS_JOINTACTFRC:
elem = InsertEnd(section, "jointactuatorfrc");
WriteAttrTxt(elem, "joint", sensor->get_objname());
break;
case mjSENS_TENDONACTFRC:
elem = InsertEnd(section, "tendonactuatorfrc");
WriteAttrTxt(elem, "tendon", sensor->get_objname());
break;
// sensors related to ball joints
case mjSENS_BALLQUAT:
elem = InsertEnd(section, "ballquat");
WriteAttrTxt(elem, "joint", sensor->get_objname());
break;
case mjSENS_BALLANGVEL:
elem = InsertEnd(section, "ballangvel");
WriteAttrTxt(elem, "joint", sensor->get_objname());
break;
// joint and tendon limit sensors
case mjSENS_JOINTLIMITPOS:
elem = InsertEnd(section, "jointlimitpos");
WriteAttrTxt(elem, "joint", sensor->get_objname());
break;
case mjSENS_JOINTLIMITVEL:
elem = InsertEnd(section, "jointlimitvel");
WriteAttrTxt(elem, "joint", sensor->get_objname());
break;
case mjSENS_JOINTLIMITFRC:
elem = InsertEnd(section, "jointlimitfrc");
WriteAttrTxt(elem, "joint", sensor->get_objname());
break;
case mjSENS_TENDONLIMITPOS:
elem = InsertEnd(section, "tendonlimitpos");
WriteAttrTxt(elem, "tendon", sensor->get_objname());
break;
case mjSENS_TENDONLIMITVEL:
elem = InsertEnd(section, "tendonlimitvel");
WriteAttrTxt(elem, "tendon", sensor->get_objname());
break;
case mjSENS_TENDONLIMITFRC:
elem = InsertEnd(section, "tendonlimitfrc");
WriteAttrTxt(elem, "tendon", sensor->get_objname());
break;
// sensors attached to an object with spatial frame: (x)body, geom, site, camera
case mjSENS_FRAMEPOS:
elem = InsertEnd(section, "framepos");
WriteAttrTxt(elem, "objtype", mju_type2Str(sensor->objtype));
WriteAttrTxt(elem, "objname", sensor->get_objname());
if (sensor->reftype != mjOBJ_UNKNOWN) {
WriteAttrTxt(elem, "reftype", mju_type2Str(sensor->reftype));
WriteAttrTxt(elem, "refname", sensor->get_refname());
}
break;
case mjSENS_FRAMEQUAT:
elem = InsertEnd(section, "framequat");
WriteAttrTxt(elem, "objtype", mju_type2Str(sensor->objtype));
WriteAttrTxt(elem, "objname", sensor->get_objname());
if (sensor->reftype != mjOBJ_UNKNOWN) {
WriteAttrTxt(elem, "reftype", mju_type2Str(sensor->reftype));
WriteAttrTxt(elem, "refname", sensor->get_refname());
}
break;
case mjSENS_FRAMEXAXIS:
elem = InsertEnd(section, "framexaxis");
WriteAttrTxt(elem, "objtype", mju_type2Str(sensor->objtype));
WriteAttrTxt(elem, "objname", sensor->get_objname());
if (sensor->reftype != mjOBJ_UNKNOWN) {
WriteAttrTxt(elem, "reftype", mju_type2Str(sensor->reftype));
WriteAttrTxt(elem, "refname", sensor->get_refname());
}
break;
case mjSENS_FRAMEYAXIS:
elem = InsertEnd(section, "frameyaxis");
WriteAttrTxt(elem, "objtype", mju_type2Str(sensor->objtype));
WriteAttrTxt(elem, "objname", sensor->get_objname());
if (sensor->reftype != mjOBJ_UNKNOWN) {
WriteAttrTxt(elem, "reftype", mju_type2Str(sensor->reftype));
WriteAttrTxt(elem, "refname", sensor->get_refname());
}
break;
case mjSENS_FRAMEZAXIS:
elem = InsertEnd(section, "framezaxis");
WriteAttrTxt(elem, "objtype", mju_type2Str(sensor->objtype));
WriteAttrTxt(elem, "objname", sensor->get_objname());
if (sensor->reftype != mjOBJ_UNKNOWN) {
WriteAttrTxt(elem, "reftype", mju_type2Str(sensor->reftype));
WriteAttrTxt(elem, "refname", sensor->get_refname());
}
break;
case mjSENS_FRAMELINVEL:
elem = InsertEnd(section, "framelinvel");
WriteAttrTxt(elem, "objtype", mju_type2Str(sensor->objtype));
WriteAttrTxt(elem, "objname", sensor->get_objname());
if (sensor->reftype != mjOBJ_UNKNOWN) {
WriteAttrTxt(elem, "reftype", mju_type2Str(sensor->reftype));
WriteAttrTxt(elem, "refname", sensor->get_refname());
}
break;
case mjSENS_FRAMEANGVEL:
elem = InsertEnd(section, "frameangvel");
WriteAttrTxt(elem, "objtype", mju_type2Str(sensor->objtype));
WriteAttrTxt(elem, "objname", sensor->get_objname());
if (sensor->reftype != mjOBJ_UNKNOWN) {
WriteAttrTxt(elem, "reftype", mju_type2Str(sensor->reftype));
WriteAttrTxt(elem, "refname", sensor->get_refname());
}
break;
case mjSENS_FRAMELINACC:
elem = InsertEnd(section, "framelinacc");
WriteAttrTxt(elem, "objtype", mju_type2Str(sensor->objtype));
WriteAttrTxt(elem, "objname", sensor->get_objname());
if (sensor->reftype != mjOBJ_UNKNOWN) {
WriteAttrTxt(elem, "reftype", mju_type2Str(sensor->reftype));
WriteAttrTxt(elem, "refname", sensor->get_refname());
}
break;
case mjSENS_FRAMEANGACC:
elem = InsertEnd(section, "frameangacc");
WriteAttrTxt(elem, "objtype", mju_type2Str(sensor->objtype));
WriteAttrTxt(elem, "objname", sensor->get_objname());
if (sensor->reftype != mjOBJ_UNKNOWN) {
WriteAttrTxt(elem, "reftype", mju_type2Str(sensor->reftype));
WriteAttrTxt(elem, "refname", sensor->get_refname());
}
break;
// sensors related to kinematic subtrees; attached to a body (which is the subtree root)
case mjSENS_SUBTREECOM:
elem = InsertEnd(section, "subtreecom");
WriteAttrTxt(elem, "body", sensor->get_objname());
break;
case mjSENS_SUBTREELINVEL:
elem = InsertEnd(section, "subtreelinvel");
WriteAttrTxt(elem, "body", sensor->get_objname());
break;
case mjSENS_SUBTREEANGMOM:
elem = InsertEnd(section, "subtreeangmom");
WriteAttrTxt(elem, "body", sensor->get_objname());
break;
case mjSENS_INSIDESITE:
elem = InsertEnd(section, "insidesite");
WriteAttrTxt(elem, "objtype", mju_type2Str(sensor->objtype));
WriteAttrTxt(elem, "objname", sensor->get_objname());
WriteAttrTxt(elem, "site", sensor->get_refname());
break;
case mjSENS_GEOMDIST:
elem = InsertEnd(section, "distance");
WriteAttrTxt(elem, sensor->objtype == mjOBJ_BODY ? "body1" : "geom1", sensor->get_objname());
WriteAttrTxt(elem, sensor->reftype == mjOBJ_BODY ? "body2" : "geom2", sensor->get_refname());
break;
case mjSENS_GEOMNORMAL:
elem = InsertEnd(section, "normal");
WriteAttrTxt(elem, sensor->objtype == mjOBJ_BODY ? "body1" : "geom1", sensor->get_objname());
WriteAttrTxt(elem, sensor->reftype == mjOBJ_BODY ? "body2" : "geom2", sensor->get_refname());
break;
case mjSENS_GEOMFROMTO:
elem = InsertEnd(section, "fromto");
WriteAttrTxt(elem, sensor->objtype == mjOBJ_BODY ? "body1" : "geom1", sensor->get_objname());
WriteAttrTxt(elem, sensor->reftype == mjOBJ_BODY ? "body2" : "geom2", sensor->get_refname());
break;
case mjSENS_CONTACT:
{
elem = InsertEnd(section, "contact");
if (sensor->objtype == mjOBJ_BODY) {
WriteAttrTxt(elem, "body1", sensor->get_objname());
} else if (sensor->objtype == mjOBJ_XBODY) {
WriteAttrTxt(elem, "subtree1", sensor->get_objname());
} else if (sensor->objtype == mjOBJ_GEOM) {
WriteAttrTxt(elem, "geom1", sensor->get_objname());
} else if (sensor->objtype == mjOBJ_SITE) {
WriteAttrTxt(elem, "site", sensor->get_objname());
}
if (sensor->reftype == mjOBJ_BODY) {
WriteAttrTxt(elem, "body2", sensor->get_refname());
} else if (sensor->reftype == mjOBJ_XBODY) {
WriteAttrTxt(elem, "subtree2", sensor->get_refname());
} else if (sensor->reftype == mjOBJ_GEOM) {
WriteAttrTxt(elem, "geom2", sensor->get_refname());
}
int dataspec = sensor->intprm[0];
WriteAttrInt(elem, "num", sensor->dim / mju_condataSize(dataspec), 1);
int data[mjNCONDATA];
int ndata = 0;
for (int i=0; i < mjNCONDATA; i++) {
if (dataspec & (1 << i)) {
data[ndata++] = i;
}
}
WriteAttrKeys(elem, "data", condata_map, mjNCONDATA, data, ndata, 0);
WriteAttrKey(elem, "reduce", reduce_map, reduce_sz, sensor->intprm[1], 0);
}
break;
case mjSENS_TACTILE:
elem = InsertEnd(section, "tactile");
WriteAttrTxt(elem, "geom", sensor->get_refname());
WriteAttrTxt(elem, "mesh", sensor->get_objname());
break;
// global sensors
case mjSENS_E_POTENTIAL:
elem = InsertEnd(section, "potential");
break;
case mjSENS_E_KINETIC:
elem = InsertEnd(section, "kinetic");
break;
case mjSENS_CLOCK:
elem = InsertEnd(section, "clock");
break;
// plugin-controlled sensor
case mjSENS_PLUGIN:
elem = InsertEnd(section, "plugin");
if (sensor->objtype != mjOBJ_UNKNOWN) {
WriteAttrTxt(elem, "objtype", mju_type2Str(sensor->objtype));
WriteAttrTxt(elem, "objname", sensor->get_objname());
}
OnePlugin(elem, &sensor->plugin);
break;
// user-defined sensor
case mjSENS_USER:
elem = InsertEnd(section, "user");
if (mju_type2Str(sensor->objtype)) {
WriteAttrTxt(elem, "objtype", mju_type2Str(sensor->objtype));
}
WriteAttrTxt(elem, "objname", sensor->get_objname());
WriteAttrInt(elem, "dim", sensor->dim);
WriteAttrKey(elem, "needstage", stage_map, stage_sz, (int)sensor->needstage);
WriteAttrKey(elem, "datatype", datatype_map, datatype_sz, (int)sensor->datatype);
break;
default:
mju_error("Unknown sensor type in XML write");
}
// write name, noise, userdata
WriteAttrTxt(elem, "name", sensor->name);
WriteAttr(elem, "cutoff", 1, &sensor->cutoff, &zero);
if (sensor->type != mjSENS_PLUGIN) {
WriteAttr(elem, "noise", 1, &sensor->noise, &zero);
}
WriteAttrInt(elem, "nsample", sensor->nsample, 0);
WriteAttrKey(elem, "interp", interp_map, interp_sz, sensor->interp, 0);
WriteAttr(elem, "delay", 1, &sensor->delay, &zero);
double zeros[2] = {0, 0};
WriteAttr(elem, "interval", 2, sensor->interval, zeros);
WriteVector(elem, "user", sensor->get_userdata());
}
// remove section if empty
if (!section->FirstChildElement()) {
root->DeleteChild(section);
}
}
// keyframe section
void mjXWriter::Keyframe(XMLElement* root) {
// create section
XMLElement* section = InsertEnd(root, "keyframe");
if (!model->key_pending_.empty()) {
throw mjXError(0, "Model has pending keyframes. It must be (re)compiled before writing XML.");
}
// write all keyframes
for (int i=0; i < model->nkey; i++) {
XMLElement* elem = InsertEnd(section, "key");
bool change = false;
mjCKey* key = model->Keys()[i];
// check name and write
if (!key->name.empty()) {
WriteAttrTxt(elem, "name", key->name);
change = true;
}
// check time and write
if (key->time != 0) {
WriteAttr(elem, "time", 1, &key->time);
change = true;
}
// check qpos and write
for (int j=0; j < model->nq; j++) {
if (key->qpos_[j] != model->qpos0[j]) {
WriteAttr(elem, "qpos", model->nq, key->qpos_.data());
change = true;
break;
}
}
// check qvel and write
for (int j=0; j < model->nv; j++) {
if (key->qvel_[j] != 0) {
WriteAttr(elem, "qvel", model->nv, key->qvel_.data());
change = true;
break;
}
}
// check act and write
for (int j=0; j < model->na; j++) {
if (key->act_[j] != 0) {
WriteAttr(elem, "act", model->na, key->act_.data());
change = true;
break;
}
}
// check mpos and write
if (model->nmocap) {
for (int j=0; j < model->nbody; j++) {
if (model->Bodies()[j]->mocap) {
mjCBody* body = model->Bodies()[j];
int id = body->mocapid;
if (body->pos[0] != key->mpos_[3*id] ||
body->pos[1] != key->mpos_[3*id+1] ||
body->pos[2] != key->mpos_[3*id+2]) {
WriteAttr(elem, "mpos", 3*model->nmocap, key->mpos_.data());
change = true;
break;
}
}
}
}
// check mquat and write
if (model->nmocap) {
for (int j=0; j < model->nbody; j++) {
if (model->Bodies()[j]->mocap) {
mjCBody* body = model->Bodies()[j];
int id = body->mocapid;
if (body->quat[0] != key->mquat_[4*id] ||
body->quat[1] != key->mquat_[4*id+1] ||
body->quat[2] != key->mquat_[4*id+2] ||
body->quat[3] != key->mquat_[4*id+3]) {
WriteAttr(elem, "mquat", 4*model->nmocap, key->mquat_.data());
change = true;
break;
}
}
}
}
// check ctrl and write
for (int j=0; j < model->nu; j++) {
if (key->ctrl_[j] != 0) {
WriteAttr(elem, "ctrl", model->nu, key->ctrl_.data());
change = true;
break;
}
}
// remove elem if empty
if (!change) {
section->DeleteChild(elem);
}
}
// remove section if empty
if (!section->FirstChildElement()) {
root->DeleteChild(section);
}
}