7cdf180641
These flexes use only 24 DOFs (3 per vertex of the bounding box), while colliding with the full high resolution mesh. On an 8x8x8 cube, the performance using DOFs at all vertices is ``` Simulation time : 18.74 s Steps per second : 533 Realtime factor : 0.53 x Time per step : 1874.4 µs Contacts per step : 114.88 Constraints per step : 3322.51 Degrees of freedom : 1536 ``` With the new implementation, it is the following: ``` Simulation time : 1.82 s Steps per second : 5507 Realtime factor : 5.51 x Time per step : 181.6 µs Contacts per step : 38.84 Constraints per step : 155.36 Degrees of freedom : 24 ``` PiperOrigin-RevId: 721008829 Change-Id: I833df027527db578d86667cc4b24295bcf6f7d22
2325 lines
81 KiB
C++
2325 lines
81 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_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"
|
|
|
|
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, "\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);
|
|
WriteAttr(elem, "radius", 1, &flex->radius, &defflex.radius);
|
|
if (flex->get_material() != defflex.get_material()) {
|
|
WriteAttrTxt(elem, "material", flex->get_material());
|
|
}
|
|
WriteAttr(elem, "rgba", 4, flex->rgba, defflex.rgba);
|
|
WriteAttrKey(elem, "flatskin", bool_map, 2, flex->flatskin, defflex.flatskin);
|
|
WriteAttrInt(elem, "dim", flex->dim, defflex.dim);
|
|
WriteAttrInt(elem, "group", flex->group, defflex.group);
|
|
|
|
// 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_nodebody().empty()) {
|
|
text = VectorToString(flex->get_nodebody());
|
|
WriteAttrTxt(elem, "node", text);
|
|
}
|
|
|
|
// contact subelement
|
|
XMLElement* cont = InsertEnd(elem, "contact");
|
|
WriteAttrInt(cont, "contype", flex->contype, defflex.contype);
|
|
WriteAttrInt(cont, "conaffinity", flex->conaffinity, defflex.conaffinity);
|
|
WriteAttrInt(cont, "condim", flex->condim, defflex.condim);
|
|
WriteAttrInt(cont, "priority", flex->priority, defflex.priority);
|
|
WriteAttr(cont, "friction", 3, flex->friction, defflex.friction);
|
|
WriteAttr(cont, "solmix", 1, &flex->solmix, &defflex.solmix);
|
|
WriteAttr(cont, "solref", mjNREF, flex->solref, defflex.solref);
|
|
WriteAttr(cont, "solimp", mjNIMP, flex->solimp, defflex.solimp);
|
|
WriteAttr(cont, "margin", 1, &flex->margin, &defflex.margin);
|
|
WriteAttr(cont, "gap", 1, &flex->gap, &defflex.gap);
|
|
WriteAttrKey(cont, "internal", bool_map, 2, flex->internal, defflex.internal);
|
|
WriteAttrKey(cont, "selfcollide", flexself_map, 5, flex->selfcollide, defflex.selfcollide);
|
|
WriteAttrInt(cont, "activelayers", flex->activelayers, defflex.activelayers);
|
|
|
|
// remove contact is no attributes
|
|
if (!cont->FirstAttribute()) {
|
|
elem->DeleteChild(cont);
|
|
}
|
|
|
|
// elasticity subelement
|
|
XMLElement* elastic = InsertEnd(elem, "elasticity");
|
|
WriteAttr(elastic, "young", 1, &flex->young, &defflex.young);
|
|
WriteAttr(elastic, "poisson", 1, &flex->poisson, &defflex.poisson);
|
|
WriteAttr(elastic, "thickness", 1, &flex->thickness, &defflex.thickness);
|
|
WriteAttr(elastic, "damping", 1, &flex->damping, &defflex.damping);
|
|
|
|
// edge subelement
|
|
XMLElement* edge = InsertEnd(elem, "edge");
|
|
WriteAttr(edge, "stiffness", 1, &flex->edgestiffness, &defflex.edgestiffness);
|
|
WriteAttr(edge, "damping", 1, &flex->edgedamping, &defflex.edgedamping);
|
|
|
|
// 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());
|
|
if (mesh->Inertia() != def->Mesh().Inertia()) {
|
|
WriteAttrTxt(elem, "inertia", FindValue(meshinertia_map, 3, mesh->Inertia()));
|
|
}
|
|
|
|
// 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
|
|
WriteAttr(elem, "refpos", 3, mesh->Refpos(), def->Mesh().Refpos());
|
|
WriteAttr(elem, "refquat", 4, mesh->Refquat(), def->Mesh().Refquat());
|
|
WriteAttr(elem, "scale", 3, mesh->Scale(), def->Mesh().Scale());
|
|
WriteAttrKey(elem, "smoothnormal", bool_map, 2, mesh->SmoothNormal(),
|
|
def->Mesh().SmoothNormal());
|
|
}
|
|
|
|
|
|
|
|
// write skin
|
|
void mjXWriter::OneSkin(XMLElement* elem, const mjCSkin* skin) {
|
|
string text;
|
|
mjCDef mydef;
|
|
float zero = 0;
|
|
|
|
// write attributes
|
|
WriteAttrTxt(elem, "name", skin->name);
|
|
WriteAttrTxt(elem, "file", skin->File());
|
|
WriteAttrTxt(elem, "material", skin->get_material());
|
|
WriteAttrInt(elem, "group", skin->group, 0);
|
|
WriteAttr(elem, "rgba", 4, skin->rgba, mydef.Geom().rgba);
|
|
WriteAttr(elem, "inflate", 1, &skin->inflate, &zero);
|
|
|
|
// 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
|
|
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 (material->textures_[i] != def->Material().textures_[i]) {
|
|
WriteAttrTxt(elem, "texture", material->get_texture(i));
|
|
}
|
|
}
|
|
if (has_non_rgb) {
|
|
// // TODO elem = InsertEnd(section, "role");
|
|
throw mjXError(0, "no support for non-RGB textures.");
|
|
}
|
|
WriteAttrKey(elem, "texuniform", bool_map, 2, material->texuniform, def->Material().texuniform);
|
|
WriteAttr(elem, "texrepeat", 2, material->texrepeat, def->Material().texrepeat);
|
|
WriteAttr(elem, "emission", 1, &material->emission, &def->Material().emission);
|
|
WriteAttr(elem, "specular", 1, &material->specular, &def->Material().specular);
|
|
WriteAttr(elem, "shininess", 1, &material->shininess, &def->Material().shininess);
|
|
WriteAttr(elem, "reflectance", 1, &material->reflectance, &def->Material().reflectance);
|
|
WriteAttr(elem, "metallic", 1, &material->metallic, &def->Material().metallic);
|
|
WriteAttr(elem, "roughness", 1, &material->roughness, &def->Material().roughness);
|
|
WriteAttr(elem, "rgba", 4, material->rgba, def->Material().rgba);
|
|
}
|
|
|
|
|
|
|
|
// write joint
|
|
void mjXWriter::OneJoint(XMLElement* elem, const mjCJoint* joint, mjCDef* def,
|
|
string_view classname) {
|
|
double zero = 0;
|
|
|
|
// 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
|
|
if (joint->type != def->Joint().type) {
|
|
WriteAttrTxt(elem, "type", FindValue(joint_map, joint_sz, joint->type));
|
|
}
|
|
WriteAttrInt(elem, "group", joint->group, def->Joint().group);
|
|
WriteAttr(elem, "ref", 1, &joint->ref, &zero);
|
|
WriteAttr(elem, "springref", 1, &joint->springref, &zero);
|
|
WriteAttr(elem, "solreflimit", mjNREF, joint->solref_limit, def->Joint().solref_limit, true);
|
|
WriteAttr(elem, "solimplimit", mjNIMP, joint->solimp_limit, def->Joint().solimp_limit, true);
|
|
WriteAttr(elem, "solreffriction", mjNREF, joint->solref_friction, def->Joint().solref_friction,
|
|
true);
|
|
WriteAttr(elem, "solimpfriction", mjNIMP, joint->solimp_friction, def->Joint().solimp_friction,
|
|
true);
|
|
WriteAttr(elem, "stiffness", 1, &joint->stiffness, &def->Joint().stiffness);
|
|
WriteAttrKey(elem, "limited", TFAuto_map, 3, joint->limited, def->Joint().limited);
|
|
WriteAttr(elem, "range", 2, joint->range, def->Joint().range);
|
|
WriteAttrKey(elem, "actuatorfrclimited", TFAuto_map, 3, joint->actfrclimited,
|
|
def->Joint().actfrclimited);
|
|
WriteAttrKey(elem, "actuatorgravcomp", bool_map, 2, joint->actgravcomp, def->Joint().actgravcomp);
|
|
WriteAttr(elem, "actuatorfrcrange", 2, joint->actfrcrange, def->Joint().actfrcrange);
|
|
WriteAttr(elem, "margin", 1, &joint->margin, &def->Joint().margin);
|
|
WriteAttr(elem, "armature", 1, &joint->armature, &def->Joint().armature);
|
|
WriteAttr(elem, "damping", 1, &joint->damping, &def->Joint().damping);
|
|
WriteAttr(elem, "frictionloss", 1, &joint->frictionloss, &def->Joint().frictionloss);
|
|
|
|
// 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(geom->typeinertia), 3) ||
|
|
!SameVector(geom->quat, mesh->GetQuatPtr(geom->typeinertia), 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(geom->typeinertia),
|
|
mesh->GetQuatPtr(geom->typeinertia));
|
|
|
|
// 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
|
|
WriteAttrKey(elem, "type", geom_map, mjNGEOMTYPES, geom->type, def->Geom().type);
|
|
WriteAttrInt(elem, "contype", geom->contype, def->Geom().contype);
|
|
WriteAttrInt(elem, "conaffinity", geom->conaffinity, def->Geom().conaffinity);
|
|
WriteAttrInt(elem, "condim", geom->condim, def->Geom().condim);
|
|
WriteAttrInt(elem, "group", geom->group, def->Geom().group);
|
|
WriteAttrInt(elem, "priority", geom->priority, def->Geom().priority);
|
|
WriteAttr(elem, "friction", 3, geom->friction, def->Geom().friction, true);
|
|
WriteAttr(elem, "solmix", 1, &geom->solmix, &def->Geom().solmix);
|
|
WriteAttr(elem, "solref", mjNREF, geom->solref, def->Geom().solref, true);
|
|
WriteAttr(elem, "solimp", mjNIMP, geom->solimp, def->Geom().solimp, true);
|
|
WriteAttr(elem, "margin", 1, &geom->margin, &def->Geom().margin);
|
|
WriteAttr(elem, "gap", 1, &geom->gap, &def->Geom().gap);
|
|
WriteAttr(elem, "gap", 1, &geom->gap, &def->Geom().gap);
|
|
WriteAttrKey(elem, "fluidshape", fluid_map, 2, geom->fluid_ellipsoid, def->Geom().fluid_ellipsoid);
|
|
WriteAttr(elem, "fluidcoef", 5, geom->fluid_coefs, def->Geom().fluid_coefs);
|
|
WriteAttrKey(elem, "shellinertia", meshtype_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());
|
|
}
|
|
WriteAttr(elem, "rgba", 4, geom->rgba, def->Geom().rgba);
|
|
|
|
// 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, "pos", 3, site->pos);
|
|
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
|
|
WriteAttrInt(elem, "group", site->group, def->Site().group);
|
|
WriteAttrKey(elem, "type", geom_map, mjNGEOMTYPES, site->type, def->Site().type);
|
|
if (site->get_material() != def->Site().get_material()) {
|
|
WriteAttrTxt(elem, "material", site->get_material());
|
|
}
|
|
WriteAttr(elem, "rgba", 4, site->rgba, def->Site().rgba);
|
|
|
|
// 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, "pos", 3, camera->pos);
|
|
WriteAttr(elem, "quat", 4, camera->quat, unitq);
|
|
}
|
|
|
|
// defaults and regular
|
|
WriteAttr(elem, "ipd", 1, &camera->ipd, &def->Camera().ipd);
|
|
WriteAttrKey(elem, "mode", camlight_map, camlight_sz, camera->mode, def->Camera().mode);
|
|
WriteAttr(elem, "resolution", 2, camera->resolution, def->Camera().resolution);
|
|
WriteAttrKey(elem, "orthographic", bool_map, 2, camera->orthographic, def->Camera().orthographic);
|
|
|
|
// 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());
|
|
WriteAttr(elem, "pos", 3, light->pos);
|
|
WriteAttr(elem, "dir", 3, light->dir);
|
|
}
|
|
|
|
// defaults and regular
|
|
WriteAttr(elem, "bulbradius", 1, &light->bulbradius, &def->Light().bulbradius);
|
|
WriteAttrKey(elem, "directional", bool_map, 2, light->directional, def->Light().directional);
|
|
WriteAttrKey(elem, "castshadow", bool_map, 2, light->castshadow, def->Light().castshadow);
|
|
WriteAttrKey(elem, "active", bool_map, 2, light->active, def->Light().active);
|
|
WriteAttr(elem, "attenuation", 3, light->attenuation, def->Light().attenuation);
|
|
WriteAttr(elem, "cutoff", 1, &light->cutoff, &def->Light().cutoff);
|
|
WriteAttr(elem, "exponent", 1, &light->exponent, &def->Light().exponent);
|
|
WriteAttr(elem, "ambient", 3, light->ambient, def->Light().ambient);
|
|
WriteAttr(elem, "diffuse", 3, light->diffuse, def->Light().diffuse);
|
|
WriteAttr(elem, "specular", 3, light->specular, def->Light().specular);
|
|
WriteAttrKey(elem, "mode", camlight_map, camlight_sz, light->mode, def->Light().mode);
|
|
}
|
|
|
|
// write pair
|
|
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);
|
|
WriteAttrInt(elem, "condim", pair->condim, def->Pair().spec.condim);
|
|
WriteAttr(elem, "margin", 1, &pair->margin, &def->Pair().spec.margin);
|
|
WriteAttr(elem, "gap", 1, &pair->gap, &def->Pair().spec.gap);
|
|
WriteAttr(elem, "solref", mjNREF, pair->solref, def->Pair().spec.solref, true);
|
|
WriteAttr(elem, "solreffriction", mjNREF, pair->solreffriction, def->Pair().spec.solreffriction,
|
|
true);
|
|
WriteAttr(elem, "solimp", mjNIMP, pair->solimp, def->Pair().spec.solimp, true);
|
|
WriteAttr(elem, "friction", 5, pair->friction, def->Pair().spec.friction); // all 5 values
|
|
}
|
|
|
|
|
|
|
|
// 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:
|
|
WriteAttrTxt(elem, "flex", mjs_getString(equality->name1));
|
|
break;
|
|
|
|
default:
|
|
mju_error("mjXWriter: unknown equality type.");
|
|
}
|
|
}
|
|
|
|
// defaults and regular
|
|
WriteAttrKey(elem, "active", bool_map, 2, equality->active, def->Equality().active);
|
|
WriteAttr(elem, "solref", mjNREF, equality->solref, def->Equality().solref, true);
|
|
WriteAttr(elem, "solimp", mjNIMP, equality->solimp, def->Equality().solimp, true);
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
WriteAttrInt(elem, "group", tendon->group, def->Tendon().group);
|
|
WriteAttr(elem, "solreflimit", mjNREF, tendon->solref_limit, def->Tendon().solref_limit, true);
|
|
WriteAttr(elem, "solimplimit", mjNIMP, tendon->solimp_limit, def->Tendon().solimp_limit, true);
|
|
WriteAttr(elem, "solreffriction", mjNREF, tendon->solref_friction, def->Tendon().solref_friction,
|
|
true);
|
|
WriteAttr(elem, "solimpfriction", mjNIMP, tendon->solimp_friction, def->Tendon().solimp_friction,
|
|
true);
|
|
WriteAttrKey(elem, "limited", TFAuto_map, 3, tendon->limited, def->Tendon().limited);
|
|
WriteAttr(elem, "range", 2, tendon->range, def->Tendon().range);
|
|
WriteAttr(elem, "margin", 1, &tendon->margin, &def->Tendon().margin);
|
|
WriteAttr(elem, "stiffness", 1, &tendon->stiffness, &def->Tendon().stiffness);
|
|
WriteAttr(elem, "damping", 1, &tendon->damping, &def->Tendon().damping);
|
|
WriteAttr(elem, "frictionloss", 1, &tendon->frictionloss, &def->Tendon().frictionloss);
|
|
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);
|
|
}
|
|
// spatial only
|
|
if (!fixed) {
|
|
if (tendon->get_material()!=def->Tendon().get_material()) {
|
|
WriteAttrTxt(elem, "material", tendon->get_material());
|
|
}
|
|
WriteAttr(elem, "width", 1, &tendon->width, &def->Tendon().width);
|
|
WriteAttr(elem, "rgba", 4, tendon->rgba, def->Tendon().rgba);
|
|
}
|
|
|
|
// 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
|
|
WriteAttrInt(elem, "group", actuator->group, def->Actuator().group);
|
|
WriteAttrKey(elem, "ctrllimited", TFAuto_map, 3, actuator->ctrllimited, def->Actuator().ctrllimited);
|
|
WriteAttr(elem, "ctrlrange", 2, actuator->ctrlrange, def->Actuator().ctrlrange);
|
|
WriteAttrKey(elem, "forcelimited", TFAuto_map, 3, actuator->forcelimited, def->Actuator().forcelimited);
|
|
WriteAttr(elem, "forcerange", 2, actuator->forcerange, def->Actuator().forcerange);
|
|
WriteAttrKey(elem, "actlimited", TFAuto_map, 3, actuator->actlimited, def->Actuator().actlimited);
|
|
WriteAttr(elem, "actrange", 2, actuator->actrange, def->Actuator().actrange);
|
|
WriteAttr(elem, "lengthrange", 2, actuator->lengthrange, def->Actuator().lengthrange);
|
|
WriteAttr(elem, "gear", 6, actuator->gear, def->Actuator().gear);
|
|
WriteAttr(elem, "cranklength", 1, &actuator->cranklength, &def->Actuator().cranklength);
|
|
WriteAttrKey(elem, "actearly", bool_map, 2, actuator->actearly,
|
|
def->Actuator().actearly);
|
|
// 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 ? 0 : 1;
|
|
WriteAttrInt(elem, "actdim", actuator->actdim, default_actdim);
|
|
}
|
|
WriteAttrKey(elem, "dyntype", dyn_map, dyn_sz, actuator->dyntype, def->Actuator().dyntype);
|
|
WriteAttr(elem, "dynprm", mjNDYN, actuator->dynprm, def->Actuator().dynprm);
|
|
|
|
// 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);
|
|
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(const mjSpec* _spec, const mjModel* m) {
|
|
if (_spec) {
|
|
model = static_cast<mjCModel*>(_spec->element);
|
|
}
|
|
if (m) {
|
|
model->CopyBack(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);
|
|
Visual(root);
|
|
Statistic(root);
|
|
writingdefaults = true;
|
|
Default(root, model->Default());
|
|
writingdefaults = false;
|
|
Extension(root);
|
|
Custom(root);
|
|
Asset(root);
|
|
Body(InsertEnd(root, "worldbody"), model->GetWorld(), nullptr);
|
|
Contact(root);
|
|
Deformable(root);
|
|
Equality(root);
|
|
Tendon(root);
|
|
Actuator(root);
|
|
Sensor(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");
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// option section
|
|
void mjXWriter::Option(XMLElement* root) {
|
|
mjOption opt;
|
|
mj_defaultOption(&opt);
|
|
|
|
XMLElement* section = InsertEnd(root, "option");
|
|
|
|
// option
|
|
WriteAttr(section, "timestep", 1, &model->option.timestep, &opt.timestep);
|
|
WriteAttr(section, "apirate", 1, &model->option.apirate, &opt.apirate);
|
|
WriteAttr(section, "impratio", 1, &model->option.impratio, &opt.impratio);
|
|
WriteAttr(section, "tolerance", 1, &model->option.tolerance, &opt.tolerance);
|
|
WriteAttr(section, "ls_tolerance", 1, &model->option.ls_tolerance, &opt.ls_tolerance);
|
|
WriteAttr(section, "noslip_tolerance", 1, &model->option.noslip_tolerance, &opt.noslip_tolerance);
|
|
WriteAttr(section, "ccd_tolerance", 1, &model->option.ccd_tolerance, &opt.ccd_tolerance);
|
|
WriteAttr(section, "gravity", 3, model->option.gravity, opt.gravity);
|
|
WriteAttr(section, "wind", 3, model->option.wind, opt.wind);
|
|
WriteAttr(section, "magnetic", 3, model->option.magnetic, opt.magnetic);
|
|
WriteAttr(section, "density", 1, &model->option.density, &opt.density);
|
|
WriteAttr(section, "viscosity", 1, &model->option.viscosity, &opt.viscosity);
|
|
|
|
WriteAttr(section, "o_margin", 1, &model->option.o_margin, &opt.o_margin);
|
|
WriteAttr(section, "o_solref", mjNREF, model->option.o_solref, opt.o_solref);
|
|
WriteAttr(section, "o_solimp", mjNIMP, model->option.o_solimp, opt.o_solimp);
|
|
WriteAttr(section, "o_friction", 5, model->option.o_friction, opt.o_friction);
|
|
|
|
WriteAttrKey(section, "integrator", integrator_map, integrator_sz,
|
|
model->option.integrator, opt.integrator);
|
|
WriteAttrKey(section, "cone", cone_map, cone_sz,
|
|
model->option.cone, opt.cone);
|
|
WriteAttrKey(section, "jacobian", jac_map, jac_sz,
|
|
model->option.jacobian, opt.jacobian);
|
|
WriteAttrKey(section, "solver", solver_map, solver_sz,
|
|
model->option.solver, opt.solver);
|
|
WriteAttrInt(section, "iterations", model->option.iterations, opt.iterations);
|
|
WriteAttrInt(section, "ls_iterations", model->option.ls_iterations, opt.ls_iterations);
|
|
WriteAttrInt(section, "noslip_iterations", model->option.noslip_iterations, opt.noslip_iterations);
|
|
WriteAttrInt(section, "ccd_iterations", model->option.ccd_iterations, opt.ccd_iterations);
|
|
WriteAttrInt(section, "sdf_iterations", model->option.sdf_iterations, opt.sdf_iterations);
|
|
WriteAttrInt(section, "sdf_initpoints", model->option.sdf_initpoints, opt.sdf_initpoints);
|
|
|
|
// 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("passive", mjDSBL_PASSIVE)
|
|
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)
|
|
#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("multiccd", mjENBL_MULTICCD)
|
|
WRITEENBL("island", mjENBL_ISLAND)
|
|
WRITEENBL("nativeccd", mjENBL_NATIVECCD)
|
|
#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));
|
|
}
|
|
|
|
// write sizes
|
|
WriteAttrInt(section, "njmax", model->njmax, -1);
|
|
WriteAttrInt(section, "nconmax", model->nconmax, -1);
|
|
WriteAttrInt(section, "nstack", model->nstack, -1);
|
|
WriteAttrInt(section, "nuserdata", model->nuserdata, 0);
|
|
WriteAttrInt(section, "nkey", model->nkey, 0);
|
|
WriteAttrInt(section, "nuser_body", model->nuser_body, 0);
|
|
WriteAttrInt(section, "nuser_jnt", model->nuser_jnt, 0);
|
|
WriteAttrInt(section, "nuser_geom", model->nuser_geom, 0);
|
|
WriteAttrInt(section, "nuser_site", model->nuser_site, 0);
|
|
WriteAttrInt(section, "nuser_cam", model->nuser_cam, 0);
|
|
WriteAttrInt(section, "nuser_tendon", model->nuser_tendon, 0);
|
|
WriteAttrInt(section, "nuser_actuator", model->nuser_actuator, 0);
|
|
WriteAttrInt(section, "nuser_sensor", model->nuser_sensor, 0);
|
|
|
|
// remove entire section if no attributes
|
|
if (!section->FirstAttribute()) root->DeleteChild(section);
|
|
}
|
|
|
|
|
|
|
|
// statistic section
|
|
void mjXWriter::Statistic(XMLElement* root) {
|
|
XMLElement* section = InsertEnd(root, "statistic");
|
|
mjStatistic* s = &model->stat;
|
|
|
|
if (mjuu_defined(s->meaninertia)) WriteAttr(section, "meaninertia", 1, &s->meaninertia);
|
|
if (mjuu_defined(s->meanmass)) WriteAttr(section, "meanmass", 1, &s->meanmass);
|
|
if (mjuu_defined(s->meansize)) WriteAttr(section, "meansize", 1, &s->meansize);
|
|
if (mjuu_defined(s->extent)) WriteAttr(section, "extent", 1, &s->extent);
|
|
if (mjuu_defined(s->center[0])) WriteAttr(section, "center", 3, s->center);
|
|
|
|
// 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* elem;
|
|
|
|
XMLElement* section = InsertEnd(root, "visual");
|
|
|
|
// global
|
|
elem = InsertEnd(section, "global");
|
|
WriteAttrKey(elem, "orthographic", bool_map, 2, vis->global.orthographic, visdef.global.orthographic);
|
|
WriteAttr(elem, "fovy", 1, &vis->global.fovy, &visdef.global.fovy);
|
|
WriteAttr(elem, "ipd", 1, &vis->global.ipd, &visdef.global.ipd);
|
|
WriteAttr(elem, "azimuth", 1, &vis->global.azimuth, &visdef.global.azimuth);
|
|
WriteAttr(elem, "elevation", 1, &vis->global.elevation, &visdef.global.elevation);
|
|
WriteAttr(elem, "linewidth", 1, &vis->global.linewidth, &visdef.global.linewidth);
|
|
WriteAttr(elem, "glow", 1, &vis->global.glow, &visdef.global.glow);
|
|
WriteAttr(elem, "realtime", 1, &vis->global.realtime, &visdef.global.realtime);
|
|
WriteAttrInt(elem, "offwidth", vis->global.offwidth, visdef.global.offwidth);
|
|
WriteAttrInt(elem, "offheight", vis->global.offheight, visdef.global.offheight);
|
|
WriteAttrKey(elem, "ellipsoidinertia", bool_map, 2, vis->global.ellipsoidinertia, visdef.global.ellipsoidinertia);
|
|
WriteAttrKey(elem, "bvactive", bool_map, 2, vis->global.bvactive, visdef.global.bvactive);
|
|
if (!elem->FirstAttribute()) {
|
|
section->DeleteChild(elem);
|
|
}
|
|
|
|
// quality
|
|
elem = InsertEnd(section, "quality");
|
|
WriteAttrInt(elem, "shadowsize", vis->quality.shadowsize, visdef.quality.shadowsize);
|
|
WriteAttrInt(elem, "offsamples", vis->quality.offsamples, visdef.quality.offsamples);
|
|
WriteAttrInt(elem, "numslices", vis->quality.numslices, visdef.quality.numslices);
|
|
WriteAttrInt(elem, "numstacks", vis->quality.numstacks, visdef.quality.numstacks);
|
|
WriteAttrInt(elem, "numquads", vis->quality.numquads, visdef.quality.numquads);
|
|
if (!elem->FirstAttribute()) {
|
|
section->DeleteChild(elem);
|
|
}
|
|
|
|
// headlight
|
|
elem = InsertEnd(section, "headlight");
|
|
WriteAttr(elem, "ambient", 3, vis->headlight.ambient, visdef.headlight.ambient);
|
|
WriteAttr(elem, "diffuse", 3, vis->headlight.diffuse, visdef.headlight.diffuse);
|
|
WriteAttr(elem, "specular", 3, vis->headlight.specular, visdef.headlight.specular);
|
|
WriteAttrInt(elem, "active", vis->headlight.active, visdef.headlight.active);
|
|
if (!elem->FirstAttribute()) {
|
|
section->DeleteChild(elem);
|
|
}
|
|
|
|
// map
|
|
elem = InsertEnd(section, "map");
|
|
WriteAttr(elem, "stiffness", 1, &vis->map.stiffness, &visdef.map.stiffness);
|
|
WriteAttr(elem, "stiffnessrot", 1, &vis->map.stiffnessrot, &visdef.map.stiffnessrot);
|
|
WriteAttr(elem, "force", 1, &vis->map.force, &visdef.map.force);
|
|
WriteAttr(elem, "torque", 1, &vis->map.torque, &visdef.map.torque);
|
|
WriteAttr(elem, "alpha", 1, &vis->map.alpha, &visdef.map.alpha);
|
|
WriteAttr(elem, "fogstart", 1, &vis->map.fogstart, &visdef.map.fogstart);
|
|
WriteAttr(elem, "fogend", 1, &vis->map.fogend, &visdef.map.fogend);
|
|
WriteAttr(elem, "znear", 1, &vis->map.znear, &visdef.map.znear);
|
|
WriteAttr(elem, "zfar", 1, &vis->map.zfar, &visdef.map.zfar);
|
|
WriteAttr(elem, "haze", 1, &vis->map.haze, &visdef.map.haze);
|
|
WriteAttr(elem, "shadowclip", 1, &vis->map.shadowclip, &visdef.map.shadowclip);
|
|
WriteAttr(elem, "shadowscale", 1, &vis->map.shadowscale, &visdef.map.shadowscale);
|
|
WriteAttr(elem, "actuatortendon", 1, &vis->map.actuatortendon, &visdef.map.actuatortendon);
|
|
if (!elem->FirstAttribute()) {
|
|
section->DeleteChild(elem);
|
|
}
|
|
|
|
// scale
|
|
elem = InsertEnd(section, "scale");
|
|
WriteAttr(elem, "forcewidth", 1, &vis->scale.forcewidth, &visdef.scale.forcewidth);
|
|
WriteAttr(elem, "contactwidth", 1, &vis->scale.contactwidth, &visdef.scale.contactwidth);
|
|
WriteAttr(elem, "contactheight", 1, &vis->scale.contactheight, &visdef.scale.contactheight);
|
|
WriteAttr(elem, "connect", 1, &vis->scale.connect, &visdef.scale.connect);
|
|
WriteAttr(elem, "com", 1, &vis->scale.com, &visdef.scale.com);
|
|
WriteAttr(elem, "camera", 1, &vis->scale.camera, &visdef.scale.camera);
|
|
WriteAttr(elem, "light", 1, &vis->scale.light, &visdef.scale.light);
|
|
WriteAttr(elem, "selectpoint", 1, &vis->scale.selectpoint, &visdef.scale.selectpoint);
|
|
WriteAttr(elem, "jointlength", 1, &vis->scale.jointlength, &visdef.scale.jointlength);
|
|
WriteAttr(elem, "jointwidth", 1, &vis->scale.jointwidth, &visdef.scale.jointwidth);
|
|
WriteAttr(elem, "actuatorlength", 1, &vis->scale.actuatorlength, &visdef.scale.actuatorlength);
|
|
WriteAttr(elem, "actuatorwidth", 1, &vis->scale.actuatorwidth, &visdef.scale.actuatorwidth);
|
|
WriteAttr(elem, "framelength", 1, &vis->scale.framelength, &visdef.scale.framelength);
|
|
WriteAttr(elem, "framewidth", 1, &vis->scale.framewidth, &visdef.scale.framewidth);
|
|
WriteAttr(elem, "constraint", 1, &vis->scale.constraint, &visdef.scale.constraint);
|
|
WriteAttr(elem, "slidercrank", 1, &vis->scale.slidercrank, &visdef.scale.slidercrank);
|
|
WriteAttr(elem, "frustum", 1, &vis->scale.frustum, &visdef.scale.frustum);
|
|
if (!elem->FirstAttribute()) {
|
|
section->DeleteChild(elem);
|
|
}
|
|
|
|
// rgba
|
|
elem = InsertEnd(section, "rgba");
|
|
WriteAttr(elem, "fog", 4, vis->rgba.fog, visdef.rgba.fog);
|
|
WriteAttr(elem, "haze", 4, vis->rgba.haze, visdef.rgba.haze);
|
|
WriteAttr(elem, "force", 4, vis->rgba.force, visdef.rgba.force);
|
|
WriteAttr(elem, "inertia", 4, vis->rgba.inertia, visdef.rgba.inertia);
|
|
WriteAttr(elem, "joint", 4, vis->rgba.joint, visdef.rgba.joint);
|
|
WriteAttr(elem, "actuator", 4, vis->rgba.actuator, visdef.rgba.actuator);
|
|
WriteAttr(elem, "actuatornegative", 4, vis->rgba.actuatornegative, visdef.rgba.actuatornegative);
|
|
WriteAttr(elem, "actuatorpositive", 4, vis->rgba.actuatorpositive, visdef.rgba.actuatorpositive);
|
|
WriteAttr(elem, "com", 4, vis->rgba.com, visdef.rgba.com);
|
|
WriteAttr(elem, "camera", 4, vis->rgba.camera, visdef.rgba.camera);
|
|
WriteAttr(elem, "light", 4, vis->rgba.light, visdef.rgba.light);
|
|
WriteAttr(elem, "selectpoint", 4, vis->rgba.selectpoint, visdef.rgba.selectpoint);
|
|
WriteAttr(elem, "connect", 4, vis->rgba.connect, visdef.rgba.connect);
|
|
WriteAttr(elem, "contactpoint", 4, vis->rgba.contactpoint, visdef.rgba.contactpoint);
|
|
WriteAttr(elem, "contactforce", 4, vis->rgba.contactforce, visdef.rgba.contactforce);
|
|
WriteAttr(elem, "contactfriction", 4, vis->rgba.contactfriction, visdef.rgba.contactfriction);
|
|
WriteAttr(elem, "contacttorque", 4, vis->rgba.contacttorque, visdef.rgba.contacttorque);
|
|
WriteAttr(elem, "contactgap", 4, vis->rgba.contactgap, visdef.rgba.contactgap);
|
|
WriteAttr(elem, "rangefinder", 4, vis->rgba.rangefinder, visdef.rgba.rangefinder);
|
|
WriteAttr(elem, "constraint", 4, vis->rgba.constraint, visdef.rgba.constraint);
|
|
WriteAttr(elem, "slidercrank", 4, vis->rgba.slidercrank, visdef.rgba.slidercrank);
|
|
WriteAttr(elem, "crankbroken", 4, vis->rgba.crankbroken, visdef.rgba.crankbroken);
|
|
WriteAttr(elem, "frustum", 4, vis->rgba.frustum, visdef.rgba.frustum);
|
|
WriteAttr(elem, "bv", 4, vis->rgba.bv, visdef.rgba.bv);
|
|
WriteAttr(elem, "bvactive", 4, vis->rgba.bvactive, visdef.rgba.bvactive);
|
|
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);
|
|
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);
|
|
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 {
|
|
WriteAttrInt(elem, "nrow", hfield->nrow);
|
|
WriteAttrInt(elem, "ncol", hfield->ncol);
|
|
if (!hfield->get_userdata().empty()) {
|
|
string text;
|
|
Vector2String(text, hfield->get_userdata(), hfield->ncol);
|
|
WriteAttrTxt(elem, "elevation", text);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
XMLElement* mjXWriter::OneFrame(XMLElement* elem, mjCFrame* frame) {
|
|
if (!frame) {
|
|
return elem;
|
|
}
|
|
|
|
if (frame->name.empty() && frame->classname.empty()) {
|
|
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);
|
|
}
|
|
// userdata
|
|
WriteVector(elem, "user", body->get_userdata());
|
|
|
|
// write inertial
|
|
if (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");
|
|
WriteAttrTxt(elem, "site", sensor->get_objname());
|
|
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;
|
|
|
|
// 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_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;
|
|
|
|
// 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);
|
|
}
|
|
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);
|
|
}
|
|
}
|