5e99ca6cb8
The writer consumes the same generated rows as the reader. mjXWriter::WriteAttrTable drives the mechanical attributes of an element from its mjXAttr rows: each bound field is compared against the class default at the same offset -- the default object is the same struct type, so the rows carry no comparison values -- and attributes equal to their default are skipped. A null default object means the element has no defaults, and every defined value is written. Ranged-arity rows write with trailing-default trimming, which the reader makes round-trip exact by refilling from the same default. Call sites upcast to the private mjs base (the friend declarations permit it; mjCMesh gains the friendship its siblings had); the comparison object is def->X().spec, a freshly-defaulted struct for the sections, or zero-initialized for size, whose spec defaults (-1, auto) are resolved by compilation. Converted: pair, geom, site, joint, camera, light, material, the equality family, both tendon types (the fixed rows are the spatial rows without appearance attributes -- exactly the tag difference), the actuator, flex with its three sub-elements, mesh, skin, option, the six visual sub-sections, statistic and size. The remnants keep names, files, resolved reference strings (the mjC classes null their private base's string pointers; resolved names live behind accessors), and the writing=custom policies the schema declares: compile directives never saved (fromto, springdamper, fitscale), type-dependent lengths and attributes (sizes, joint pos/axis/limited, shellinertia), and alternatives (mass/density, fovy-versus-intrinsics, the plugin-gated gain/bias family). Compiler keeps its write-if-nonzero policy; keyframe keeps its model-sized vectors. Saved files are canonical: attributes follow schema declaration order with remnants trailing, and sections follow the schema's dependency order (statistic before visual, deformable before the contact and equality sections that name flexes, tendon before the equality constraints that name tendons, custom demoted to the data tail). Uniform behavior fixes fall out: default-equal positionals are dropped, dynprm is trimmed like every other ranged vector, and mesh material -- read into the spec but never written -- now survives save/load round trips. Changelog entries ride along. Verified: full suite, doc_test, and the two-tier A/B harness -- saved XML reorders attributes, and every corpus model reloads to a byte-identical binary. PiperOrigin-RevId: 958255003 Change-Id: I5fe7346014450db88b2f3f8680a8f616f7d31266
101 lines
5.2 KiB
C++
101 lines
5.2 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.
|
|
|
|
#ifndef MUJOCO_SRC_XML_XML_NATIVE_WRITER_H_
|
|
#define MUJOCO_SRC_XML_XML_NATIVE_WRITER_H_
|
|
|
|
#include <cstdlib>
|
|
#include <string>
|
|
#include <string_view>
|
|
|
|
#include <mujoco/mjmodel.h>
|
|
#include <mujoco/mjspec.h>
|
|
#include "user/user_objects.h"
|
|
#include "xml/xml_base.h"
|
|
#include "tinyxml2.h"
|
|
|
|
class mjXWriter : public mjXBase {
|
|
public:
|
|
mjXWriter(); // constructor
|
|
virtual ~mjXWriter() = default; // destructor
|
|
void SetModel(mjSpec* _spec, const mjModel* m = nullptr);
|
|
|
|
// write XML document to string
|
|
std::string Write(char *error, std::size_t error_sz);
|
|
|
|
private:
|
|
// insert end child with given name, return child
|
|
tinyxml2::XMLElement* InsertEnd(tinyxml2::XMLElement* parent, const char* name);
|
|
|
|
// compiled model
|
|
mjCModel* model = 0;
|
|
|
|
// XML section writers
|
|
void Compiler(tinyxml2::XMLElement* root); // compiler section
|
|
void Option(tinyxml2::XMLElement* root); // option section
|
|
void Size(tinyxml2::XMLElement* root); // size section
|
|
void Visual(tinyxml2::XMLElement* root); // visual section
|
|
void Statistic(tinyxml2::XMLElement* root); // statistic section
|
|
void Default(tinyxml2::XMLElement* root, mjCDef* def); // default section
|
|
void Extension(tinyxml2::XMLElement* root); // extension section
|
|
void Custom(tinyxml2::XMLElement* root); // custom section
|
|
void Asset(tinyxml2::XMLElement* root); // asset section
|
|
void Contact(tinyxml2::XMLElement* root); // contact section
|
|
void Deformable(tinyxml2::XMLElement* root); // deformable section
|
|
void Equality(tinyxml2::XMLElement* root); // equality section
|
|
void Tendon(tinyxml2::XMLElement* root); // tendon section
|
|
void Actuator(tinyxml2::XMLElement* root); // actuator section
|
|
void Sensor(tinyxml2::XMLElement* root); // sensor section
|
|
void Keyframe(tinyxml2::XMLElement* root); // keyframe section
|
|
|
|
// body/world section
|
|
void Body(tinyxml2::XMLElement* elem, mjCBody* body, mjCFrame* frame, std::string_view childclass = "");
|
|
|
|
// table-driven attribute writing: the mechanical attributes of an element,
|
|
// driven by the same generated mjXAttr rows the reader uses. obj and def
|
|
// are the element's bound spec struct and its class-default counterpart;
|
|
// each bound field is compared against the default at the same offset, so
|
|
// attributes equal to their default are skipped. Strings, files and
|
|
// custom-read attributes remain in the OneX() remnants.
|
|
template <typename T>
|
|
void WriteAttrTable(tinyxml2::XMLElement* elem, const T* obj, const T* def,
|
|
const struct mjXAttr* rows, int nrow);
|
|
|
|
// single element writers, used in defaults and main body
|
|
void OneFlex(tinyxml2::XMLElement* elem, const mjCFlex* pflex);
|
|
void OneMesh(tinyxml2::XMLElement* elem, const mjCMesh* pmesh, mjCDef* def);
|
|
void OneSkin(tinyxml2::XMLElement* elem, const mjCSkin* pskin);
|
|
void OneMaterial(tinyxml2::XMLElement* elem, const mjCMaterial* pmaterial, mjCDef* def);
|
|
void OneJoint(tinyxml2::XMLElement* elem, const mjCJoint* pjoint, mjCDef* def,
|
|
std::string_view classname = "");
|
|
void OneGeom(tinyxml2::XMLElement* elem, const mjCGeom* pgeom, mjCDef* def,
|
|
std::string_view classname = "");
|
|
void OneSite(tinyxml2::XMLElement* elem, const mjCSite* psite, mjCDef* def,
|
|
std::string_view classname = "");
|
|
void OneCamera(tinyxml2::XMLElement* elem, const mjCCamera* pcamera,
|
|
mjCDef* def, std::string_view classname = "");
|
|
void OneLight(tinyxml2::XMLElement* elem, const mjCLight* plight, mjCDef* def,
|
|
std::string_view classname = "");
|
|
void OnePair(tinyxml2::XMLElement* elem, const mjCPair* ppair, mjCDef* def);
|
|
void OneEquality(tinyxml2::XMLElement* elem, const mjCEquality* pequality, mjCDef* def);
|
|
void OneTendon(tinyxml2::XMLElement* elem, const mjCTendon* ptendon, mjCDef* def);
|
|
void OneActuator(tinyxml2::XMLElement* elem, const mjCActuator* pactuator, mjCDef* def);
|
|
void OnePlugin(tinyxml2::XMLElement* elem, const mjsPlugin* plugin);
|
|
tinyxml2::XMLElement* OneFrame(tinyxml2::XMLElement* elem, mjCFrame* frame);
|
|
|
|
bool writingdefaults; // true during defaults write
|
|
};
|
|
|
|
#endif // MUJOCO_SRC_XML_XML_NATIVE_WRITER_H_
|