Table-driven attribute reading: rebase the reader on generated rows.
Every mechanical attribute read in MJCF now derives from mjcf.schema. generate_read_table.py emits typed mjXAttr rows (mjcf_read_table.inc, doc_test-gated) binding each attribute to its spec struct field; field offsets are offsetof() expressions, so binding mistakes are compile errors, and the field's C type -- parsed from the headers -- selects the row kind, so mjtNum-versus-double is decided by the struct, not the schema. mjXReader::ReadAttrTable is the generic loop; its static core also serves the section parsers and records XML-authored fields via mjs_setAuthored for attach conflict resolution. Row kinds cover strings, string lists, numeric scalars and vectors (exact and ranged), enums (int- and byte-width), bitwise flag sets, bools, unbounded typed vectors, fixed char arrays, and identity constants declared by 'set'. The rows are inline variables, and carry the writing=custom flag, because the writer will share them. The keyword maps the rows reference are generated too: the ~48 hand-written mjMap tables become mjcf_map.h, one map and size constant per enum as C++17 inline variables, retiring the hand-maintained extern block in xml_base.h. Map names follow the schema enum names (fluid->fluidshape, TFAuto->FalseTrueAuto, FAuto->FalseAuto, joint->jointtype, geom->geomtype, jac->jacobian); all maps are key-order- and value-identical to the hand tables they replace, and bool_map is hand-emitted (the bool type is built in, not a schema enum). The OneX() parsers reduce to genuine irregulars, schema-marked as reading=custom: orientation alternatives, file attributes (VFS and asset-dir context), the actuator shorthand remappings and per-type input maps, springlength's one-value copy, mesh builtin construction, hfield elevation, texture cube files, flexcomp seeding, the memory suffix parse, and the flag bit families. All 41 sensors that are pure identity-plus-references -- including the frame family and insidesite -- dispatch through a generated tag table; frame-sensor objtype/reftype vocabulary tightens from the full mju_str2Type namespace to the documented body/xbody/geom/site/camera subset, so an invalid keyword now fails at parse time instead of compile time. The equality family and both tendon types read shared group rows; the twelve actuator shorthands share the general rows, with per-tag legality enforced by the schema check. Sections bind non-mjs structs, the visual sub-sections reaching their anonymous sub-structs through member paths declared by an element-level field= facet. Latent irregularities surfaced by the migration and preserved via schema declarations or remnants: key's name is set even when absent, eulerseq and gridlayout are fixed char arrays (chars[n], arity in characters), gridlayout's length-must-match-gridsize stays a value-conditional remnant, and constructor-style elements (tendon wraps, asset model, replicate, attach) are annotated as such -- their attributes are arguments, not field writes. Two coherence tests guard the schema against the C sources: every schema enum constant must be a member of the C enum it claims, and every C member must be a keyword, a count sentinel, or a documented exemption; and generate_default_table.py emits one row per defaulted attribute (mjcf_default_table.inc), compared by SchemaDefaultsTest against a freshly-constructed spec -- the schema cannot disagree with the C default-constructors without failing the suite. Verified: doc_test regenerates and diffs every artifact; the full suite; and an A/B harness compiling the model corpus against the pre-migration reader -- saved XML and binary models are byte-identical. PiperOrigin-RevId: 958075724 Change-Id: I9715fe4deeb438eec988fd5084d74ba8b466b10b
This commit is contained in:
committed by
Copybara-Service
parent
790f8fac30
commit
4278c7b0cd
@@ -0,0 +1,186 @@
|
||||
# Copyright 2026 DeepMind Technologies Limited
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ==============================================================================
|
||||
"""Generates the default-value check table from src/xml/mjcf.schema.
|
||||
|
||||
The schema declares attribute defaults, but the defaults that act live in
|
||||
the C default-constructors (mjs_default*, mj_defaultOption, ...). This
|
||||
emits src/xml/mjcf_default_table.inc: one row per defaulted attribute,
|
||||
binding the declared values to the field they describe, consumed by
|
||||
SchemaDefaultsTest, which compares every row against a freshly-constructed
|
||||
spec -- so a schema default that disagrees with the C defaults is a test
|
||||
failure, not documentation drift. Checked in and gated by
|
||||
test/doc/doc_test.py.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
_SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, _SCRIPT_DIR)
|
||||
import generate_read_table
|
||||
import mjcf_schema
|
||||
_REPO_ROOT = os.path.dirname(os.path.dirname(_SCRIPT_DIR))
|
||||
SCHEMA_PATH = os.path.join(_REPO_ROOT, 'src', 'xml', 'mjcf.schema')
|
||||
|
||||
# kind codes shared with the test
|
||||
KIND_BY_CTYPE = {'double': 0, 'float': 1, 'int': 2,
|
||||
'mjtByte': 3, 'mjtBool': 3, 'mjtNum': 4}
|
||||
|
||||
_HEADER = '''\
|
||||
// Copyright 2026 DeepMind Technologies Limited
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// GENERATED FILE, DO NOT EDIT. Generated from src/xml/mjcf.schema by
|
||||
// doc/generate/generate_default_table.py; test/doc/doc_test.py checks
|
||||
// freshness.
|
||||
//
|
||||
// One row per schema attribute with a declared default, binding the declared
|
||||
// values to the bound field. SchemaDefaultsTest compares every row against a
|
||||
// freshly-constructed spec: the schema's defaults must agree with the C
|
||||
// default-constructors. Rows are {attr, offset, kind, len, ndecl, values};
|
||||
// values beyond ndecl are expected to be zero; kind: 0=double 1=float 2=int
|
||||
// 3=byte 4=mjtNum.
|
||||
|
||||
// clang-format off
|
||||
struct mjXDefaultEntry {
|
||||
const char* attr;
|
||||
int offset;
|
||||
int kind;
|
||||
int len;
|
||||
int ndecl;
|
||||
double value[8];
|
||||
};
|
||||
|
||||
struct mjXDefaultTable {
|
||||
const char* structname;
|
||||
const mjXDefaultEntry* entries;
|
||||
int n;
|
||||
};
|
||||
|
||||
'''
|
||||
|
||||
|
||||
def _values(schema, attr, ctype):
|
||||
"""(ndecl, [C value expressions]) for an attribute's declared default."""
|
||||
default = attr.default
|
||||
if attr.type == 'enum':
|
||||
constants = dict(schema.enums[attr.target].items)
|
||||
return 1, [f'(double){constants[default]}']
|
||||
if attr.type == 'bool':
|
||||
return 1, ['1' if default == 'true' else '0']
|
||||
values = default if isinstance(default, tuple) else (default,)
|
||||
return len(values), [repr(v) for v in values]
|
||||
|
||||
|
||||
def collect(schema, structs):
|
||||
"""struct key -> list of row tuples, deduplicated across elements."""
|
||||
tables = {}
|
||||
seen = {}
|
||||
for element in schema.elements.values():
|
||||
if not element.spec:
|
||||
continue
|
||||
sub = element.facets.get('field')
|
||||
key = f'{element.spec}.{sub}' if sub else element.spec
|
||||
fields = structs.get(key)
|
||||
if fields is None:
|
||||
continue
|
||||
prefix = f'{sub}.' if sub else ''
|
||||
for attr in schema.expanded_attrs(element):
|
||||
if attr.default is None or attr.type in ('string', 'file', 'chars',
|
||||
'ref', 'id', 'flags'):
|
||||
continue
|
||||
field = attr.facets.get('field', attr.name)
|
||||
entry = fields.get(field)
|
||||
if entry is None:
|
||||
if 'reading' in attr.facets:
|
||||
continue # custom lowering with no direct binding
|
||||
raise ValueError(f'{element.name}.{attr.name}: no field '
|
||||
f'{element.spec}.{field}')
|
||||
ctype, dim = entry
|
||||
if ctype not in KIND_BY_CTYPE and not ctype.startswith('mjt'):
|
||||
raise ValueError(f'{element.name}.{attr.name}: default bound to '
|
||||
f'field {field} ({ctype})')
|
||||
kind = KIND_BY_CTYPE.get(ctype, 2) # other mjt enums are int-sized
|
||||
length = dim if dim is not None else '1'
|
||||
ndecl, values = _values(schema, attr, ctype)
|
||||
if ndecl > 8:
|
||||
raise ValueError(f'{element.name}.{attr.name}: {ndecl} default '
|
||||
'values exceed the row capacity')
|
||||
row = (attr.name, f'(int)offsetof({element.spec}, {prefix}{field})',
|
||||
kind, str(length), ndecl, values)
|
||||
prior = seen.get((key, field))
|
||||
if prior is not None:
|
||||
if prior != (ndecl, values):
|
||||
raise ValueError(f'{element.name}.{attr.name}: conflicting '
|
||||
f'defaults for {key}.{field}')
|
||||
continue
|
||||
seen[(key, field)] = (ndecl, values)
|
||||
tables.setdefault(key, []).append(row)
|
||||
return tables
|
||||
|
||||
|
||||
def generate() -> str:
|
||||
schema = mjcf_schema.parse_file(SCHEMA_PATH)
|
||||
structs = generate_read_table.parse_spec_structs(
|
||||
generate_read_table.SPEC_H_PATH, generate_read_table.MODEL_H_PATH)
|
||||
tables = collect(schema, structs)
|
||||
out = [_HEADER]
|
||||
for key in sorted(tables):
|
||||
array = 'kDefaults_' + key.replace('.', '_')
|
||||
out.append(f'static const mjXDefaultEntry {array}[] = {{')
|
||||
for attr, offset, kind, length, ndecl, values in tables[key]:
|
||||
vals = ', '.join(values)
|
||||
out.append(f' {{"{attr}", {offset}, {kind}, {length}, {ndecl}, '
|
||||
f'{{{vals}}}}},')
|
||||
out.append('};')
|
||||
out.append('')
|
||||
out.append('static const mjXDefaultTable kDefaultTables[] = {')
|
||||
for key in sorted(tables):
|
||||
array = 'kDefaults_' + key.replace('.', '_')
|
||||
root = key.split('.')[0]
|
||||
out.append(f' {{"{root}", {array}, '
|
||||
f'(int)(sizeof({array}) / sizeof({array}[0]))}},')
|
||||
out.append('};')
|
||||
out.append('static const int kDefaultTablesN = '
|
||||
'(int)(sizeof(kDefaultTables) / sizeof(kDefaultTables[0]));')
|
||||
out.append('// clang-format on')
|
||||
return '\n'.join(out) + '\n'
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if len(sys.argv) > 2:
|
||||
sys.exit('usage: generate_default_table.py [output.inc]')
|
||||
text = generate()
|
||||
if len(sys.argv) == 2:
|
||||
with open(sys.argv[1], 'w', encoding='utf-8') as file:
|
||||
file.write(text)
|
||||
else:
|
||||
sys.stdout.write(text)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,116 @@
|
||||
# Copyright 2026 DeepMind Technologies Limited
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ==============================================================================
|
||||
"""Generates the keyword-map header from src/xml/mjcf.schema.
|
||||
|
||||
Emits src/xml/mjcf_map.h: one mjMap array and size constant per schema
|
||||
enum, as C++17 inline variables, so the reader, the writer and the
|
||||
generated tables share one definition with no extern declarations to
|
||||
maintain. It is checked in and gated by test/doc/doc_test.py, which
|
||||
regenerates it from the schema and diffs.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
_SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, _SCRIPT_DIR)
|
||||
import mjcf_schema
|
||||
_REPO_ROOT = os.path.dirname(os.path.dirname(_SCRIPT_DIR))
|
||||
SCHEMA_PATH = os.path.join(_REPO_ROOT, 'src', 'xml', 'mjcf.schema')
|
||||
_GUARD = 'MUJOCO_SRC_XML_MJCF_MAP_H_'
|
||||
|
||||
_INCLUDES = '''\
|
||||
#include <mujoco/mjspec.h>
|
||||
#include <mujoco/mjtype.h>
|
||||
#include "user/user_composite.h"
|
||||
#include "user/user_flexcomp.h"
|
||||
#include "xml/xml_util.h"'''
|
||||
|
||||
_HEADER = f'''\
|
||||
// Copyright 2026 DeepMind Technologies Limited
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// GENERATED FILE, DO NOT EDIT. Generated from src/xml/mjcf.schema by
|
||||
// doc/generate/generate_mjcf_map.py; test/doc/doc_test.py checks freshness.
|
||||
//
|
||||
// Keyword maps, one per schema enum, shared by the reader, the writer and
|
||||
// the generated tables. Inline variables: including this header is all a
|
||||
// translation unit needs.
|
||||
|
||||
#ifndef HEADER_GUARD_PLACEHOLDER
|
||||
#define HEADER_GUARD_PLACEHOLDER
|
||||
|
||||
{_INCLUDES}
|
||||
|
||||
// clang-format off
|
||||
|
||||
// keywords of the built-in bool type (not a schema enum)
|
||||
inline constexpr mjMap bool_map[] = {{
|
||||
{{"false", 0}},
|
||||
{{"true", 1}},
|
||||
}};
|
||||
|
||||
'''
|
||||
|
||||
_FOOTER = '''\
|
||||
// clang-format on
|
||||
|
||||
#endif // HEADER_GUARD_PLACEHOLDER
|
||||
'''
|
||||
|
||||
|
||||
def generate() -> str:
|
||||
schema = mjcf_schema.parse_file(SCHEMA_PATH)
|
||||
out = []
|
||||
for enum in schema.enums.values():
|
||||
width = max(len(key) for key in enum.keywords()) + 3
|
||||
out.append(f'// enum {enum.name}')
|
||||
out.append(f'inline constexpr mjMap {enum.name}_map[] = {{')
|
||||
for key, value in enum.items:
|
||||
padded = f'"{key}",'.ljust(width + 1)
|
||||
out.append(f' {{{padded} {value}}},')
|
||||
out.append('};')
|
||||
out.append(f'inline constexpr int {enum.name}_sz = {len(enum.items)};')
|
||||
out.append('')
|
||||
header = _HEADER.replace('HEADER_GUARD_PLACEHOLDER', _GUARD)
|
||||
footer = _FOOTER.replace('HEADER_GUARD_PLACEHOLDER', _GUARD)
|
||||
return header + '\n'.join(out) + footer
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if len(sys.argv) > 2:
|
||||
sys.exit('usage: generate_mjcf_map.py [output.h]')
|
||||
text = generate()
|
||||
if len(sys.argv) == 2:
|
||||
with open(sys.argv[1], 'w', encoding='utf-8') as file:
|
||||
file.write(text)
|
||||
else:
|
||||
sys.stdout.write(text)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,451 @@
|
||||
# Copyright 2026 DeepMind Technologies Limited
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ==============================================================================
|
||||
"""Generates keyword maps and typed attribute-read rows from mjcf.schema.
|
||||
|
||||
Emits src/xml/mjcf_read_table.inc: per-element mjXAttr row arrays consumed by
|
||||
mjXReader::ReadAttrTable, plus the kSensorDispatch tag table and shared group
|
||||
arrays.
|
||||
|
||||
Which elements get rows is determined automatically: every schema element with
|
||||
a bound spec struct and at least one table-drivable attribute is included,
|
||||
unless it appears in NOT_TABLE_DRIVEN (elements whose OneX() readers have
|
||||
custom logic). A coverage check in doc_test verifies that every eligible
|
||||
element is accounted for.
|
||||
|
||||
Field offsets are emitted as offsetof() expressions, so binding mistakes are
|
||||
compile errors, and the field's C type (parsed from mjspec.h) selects the row
|
||||
kind, so mjtNum vs double is decided by the struct, not by the schema.
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
_SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, _SCRIPT_DIR)
|
||||
import mjcf_schema
|
||||
_REPO_ROOT = os.path.dirname(os.path.dirname(_SCRIPT_DIR))
|
||||
SCHEMA_PATH = os.path.join(_REPO_ROOT, 'src', 'xml', 'mjcf.schema')
|
||||
SPEC_H_PATH = os.path.join(_REPO_ROOT, 'include', 'mujoco', 'mjspec.h')
|
||||
MODEL_H_PATH = os.path.join(_REPO_ROOT, 'include', 'mujoco', 'mjmodel.h')
|
||||
|
||||
# elements with a bound spec whose OneX() reader is NOT table-driven;
|
||||
# their rows are not emitted. These are the genuinely irregular elements
|
||||
# whose readers have custom logic that can't be factored into ReadAttrTable.
|
||||
# If you add a new schema element with a bound spec, either migrate its
|
||||
# reader to ReadAttrTable (it will be auto-included) or add it here.
|
||||
NOT_TABLE_DRIVEN = {
|
||||
# actuator shorthands: read shared rows + per-tag remappings
|
||||
'motor', 'position', 'velocity', 'intvelocity', 'orientation',
|
||||
'pid', 'damper', 'cylinder', 'muscle', 'adhesion', 'dcmotor',
|
||||
'actuator_plugin',
|
||||
# equality subtypes: read shared equality_base + per-type refs
|
||||
'connect', 'weld', 'equality_joint', 'equality_tendon',
|
||||
'equality_flex', 'flexvert', 'flexstrain',
|
||||
# sensors with custom reading logic
|
||||
'rangefinder', 'distance', 'user', 'normal', 'fromto',
|
||||
'sensor_contact', 'sensor_plugin', 'tactile',
|
||||
# other irregulars
|
||||
'frame', 'plugin', 'numeric', 'text', 'tuple',
|
||||
}
|
||||
|
||||
# sensors whose whole branch derives from the schema (identity constants +
|
||||
# references); their arrays are also collected into kSensorDispatch
|
||||
SENSOR_DISPATCH = [
|
||||
'touch',
|
||||
'accelerometer',
|
||||
'velocimeter',
|
||||
'gyro',
|
||||
'force',
|
||||
'torque',
|
||||
'magnetometer',
|
||||
'camprojection',
|
||||
'jointpos',
|
||||
'jointvel',
|
||||
'tendonpos',
|
||||
'tendonvel',
|
||||
'actuatorpos',
|
||||
'actuatorvel',
|
||||
'actuatorfrc',
|
||||
'jointactuatorfrc',
|
||||
'tendonactuatorfrc',
|
||||
'ballquat',
|
||||
'ballangvel',
|
||||
'jointlimitpos',
|
||||
'jointlimitvel',
|
||||
'jointlimitfrc',
|
||||
'tendonlimitpos',
|
||||
'tendonlimitvel',
|
||||
'tendonlimitfrc',
|
||||
'framepos',
|
||||
'framequat',
|
||||
'framexaxis',
|
||||
'frameyaxis',
|
||||
'framezaxis',
|
||||
'framelinvel',
|
||||
'frameangvel',
|
||||
'framelinacc',
|
||||
'frameangacc',
|
||||
'insidesite',
|
||||
'subtreecom',
|
||||
'subtreelinvel',
|
||||
'subtreeangmom',
|
||||
'e_potential',
|
||||
'e_kinetic',
|
||||
'clock',
|
||||
]
|
||||
|
||||
# shared groups emitted as standalone row arrays, for shared OneX() readers:
|
||||
# group name -> (bound struct, emitted array name)
|
||||
EMIT_GROUPS = {
|
||||
'equality_base': ('mjsEquality', 'kEqualityBaseAttrs'),
|
||||
'sensor_base': ('mjsSensor', 'kSensorBaseAttrs'),
|
||||
}
|
||||
|
||||
# groups whose attributes are hand-read in the OneX() remnant;
|
||||
# their rows are not emitted
|
||||
HAND_GROUPS = ['orientation', 'transmission',
|
||||
'sensor_base'] # read via kSensorBaseAttrs before dispatch
|
||||
|
||||
# mjspec.h array-dimension expressions equivalent to a numeric schema bound;
|
||||
# the expression is emitted as the row length so it tracks the header
|
||||
DIM_EQUIV = {'mjNPOLY+1': '3'}
|
||||
|
||||
# schema scalar/C-type -> mjXAttr kind
|
||||
KIND_BY_CTYPE = {'mjString*': 'kString', 'int': 'kInt', 'double': 'kDouble',
|
||||
'mjtNum': 'kNum', 'float': 'kFloat'}
|
||||
|
||||
_HEADER = '''\
|
||||
// Copyright 2026 DeepMind Technologies Limited
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// GENERATED FILE, DO NOT EDIT. Generated from src/xml/mjcf.schema by
|
||||
// doc/generate/generate_read_table.py; test/doc/doc_test.py checks freshness.
|
||||
//
|
||||
// Typed attribute rows for table-driven reading (mjXReader::ReadAttrTable),
|
||||
// one array per migrated element. Rows are {attr, kind, len, exact,
|
||||
// required, nodefault, handwrite, offset}; keyword maps are defined in
|
||||
// mjcf_map.h.
|
||||
|
||||
// clang-format off
|
||||
'''
|
||||
|
||||
|
||||
def _has_table_attrs(schema, element):
|
||||
"""Check if the element has at least one non-custom table-drivable attr."""
|
||||
for member in element.members:
|
||||
if isinstance(member, mjcf_schema.Attr) and 'reading' not in member.facets:
|
||||
return True
|
||||
if isinstance(member, mjcf_schema.Use):
|
||||
group = schema.groups.get(member.group)
|
||||
if group:
|
||||
for gm in group.members:
|
||||
if (isinstance(gm, mjcf_schema.Attr)
|
||||
and 'reading' not in gm.facets):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def table_driven_elements(schema):
|
||||
"""Return the list of element names whose readers are table-driven.
|
||||
|
||||
An element is table-driven if it has a bound spec struct, at least one
|
||||
table-drivable attribute, and is not in NOT_TABLE_DRIVEN.
|
||||
|
||||
Args:
|
||||
schema: Parsed schema object.
|
||||
"""
|
||||
result = []
|
||||
for name, element in schema.elements.items():
|
||||
if not element.spec:
|
||||
continue
|
||||
if not _has_table_attrs(schema, element):
|
||||
continue
|
||||
if name in NOT_TABLE_DRIVEN:
|
||||
continue
|
||||
result.append(name)
|
||||
return result
|
||||
|
||||
|
||||
def uncovered_elements(schema):
|
||||
"""Return element names eligible for table-driving but not accounted for.
|
||||
|
||||
An element is "eligible" if it has a bound spec struct and at least one
|
||||
table-drivable attribute. Every eligible element should be either
|
||||
auto-included by table_driven_elements() or listed in NOT_TABLE_DRIVEN.
|
||||
This function returns any that fall through the cracks — used by doc_test.
|
||||
|
||||
Args:
|
||||
schema: Parsed schema object.
|
||||
"""
|
||||
eligible = {name for name, el in schema.elements.items()
|
||||
if el.spec and _has_table_attrs(schema, el)}
|
||||
auto = set(table_driven_elements(schema))
|
||||
return eligible - auto - NOT_TABLE_DRIVEN
|
||||
|
||||
|
||||
def parse_spec_structs(*paths):
|
||||
"""Parse struct layouts from C headers.
|
||||
|
||||
Args:
|
||||
*paths: Paths to C header files to parse.
|
||||
|
||||
Returns:
|
||||
A dict mapping struct name -> {field: (ctype, dim-or-None)}.
|
||||
Nested anonymous sub-structs (the mjVisual sections) are exposed as
|
||||
'Outer.subname' pseudo-structs.
|
||||
"""
|
||||
structs = {}
|
||||
field_re = re.compile(
|
||||
r'^\s*([\w<>*]+(?:\s*\*)?)\s+(\w+)(\[([^\]]+)\])?\s*;', re.M)
|
||||
for path in paths:
|
||||
with open(path, encoding='utf-8') as f:
|
||||
text = f.read()
|
||||
for m in re.finditer(r'typedef struct (mj\w+)_ \{(.*?)\n\} \1;', text,
|
||||
re.S):
|
||||
name, body = m.group(1), m.group(2)
|
||||
for sm in re.finditer(r'struct \{(.*?)\} (\w+);', body, re.S):
|
||||
sub_fields = {}
|
||||
for fm in field_re.finditer(sm.group(1)):
|
||||
ctype, fname, _, dim = fm.groups()
|
||||
sub_fields[fname] = (ctype.replace(' ', ''), dim)
|
||||
structs[f'{name}.{sm.group(2)}'] = sub_fields
|
||||
body = re.sub(r'struct \{.*?\} \w+;', '', body, flags=re.S)
|
||||
fields = {}
|
||||
for fm in field_re.finditer(body):
|
||||
ctype, fname, _, dim = fm.groups()
|
||||
fields[fname] = (ctype.replace(' ', ''), dim)
|
||||
structs[name] = fields
|
||||
return structs
|
||||
|
||||
|
||||
def _attr_row(spec, fields, attr, ctx, prefix=''):
|
||||
"""Return a row tuple for one attribute, or None if it has no row."""
|
||||
required = 'true' if attr.facets.get('required') else 'false'
|
||||
nodefault = 'true' if attr.facets.get('nodefault') else 'false'
|
||||
handwrite = 'true' if attr.facets.get('writing') else 'false'
|
||||
if attr.type == 'id' and attr.name == 'name':
|
||||
# names are never settable in default classes
|
||||
return (f'"{attr.name}"', 'mjXAttr::kName', '1', 'true', required,
|
||||
'true', 'false', '-1')
|
||||
if attr.type == 'ref' and attr.target == 'default':
|
||||
return None # class selection is handled by the section parsers
|
||||
if attr.type == 'file':
|
||||
return None # files need VFS/asset-dir context: hand-read in the remnant
|
||||
field = attr.facets.get('field', attr.name)
|
||||
entry = fields.get(field)
|
||||
if entry is None:
|
||||
raise ValueError(f'{ctx}.{attr.name}: no field {spec}.{field}')
|
||||
ctype, dim = entry
|
||||
if attr.type in ('string', 'file', 'ref', 'id'):
|
||||
if ctype == 'mjStringVec*':
|
||||
kind, length, exact = 'kStringVec', '1', 'true'
|
||||
elif ctype != 'mjString*':
|
||||
raise ValueError(f'{ctx}.{attr.name}: bound to non-string '
|
||||
f'field {field} ({ctype})')
|
||||
else:
|
||||
kind, length, exact = 'kString', '1', 'true'
|
||||
elif attr.type == 'enum':
|
||||
if not ctype.startswith('mjt') and ctype != 'int':
|
||||
raise ValueError(f'{ctx}.{attr.name}: enum bound to '
|
||||
f'field {field} ({ctype})')
|
||||
kind = 'kEnumByte' if ctype in ('mjtByte', 'mjtBool') else 'kEnum'
|
||||
length, exact = '1', 'true'
|
||||
elif attr.type == 'flags':
|
||||
if ctype != 'int':
|
||||
raise ValueError(f'{ctx}.{attr.name}: flags bound to '
|
||||
f'field {field} ({ctype})')
|
||||
kind, length, exact = 'kFlags', '1', 'true'
|
||||
elif attr.type == 'bool':
|
||||
if ctype in ('mjtBool', 'mjtByte'):
|
||||
kind, length, exact = 'kBool', '1', 'true'
|
||||
elif ctype == 'int' or ctype.startswith('mjt'):
|
||||
# int-typed flag: read through the keyword map (values are 0/1)
|
||||
row = [f'"{attr.name}"', 'mjXAttr::kEnum', '1', 'true', required,
|
||||
nodefault, handwrite,
|
||||
f'(int)offsetof({spec}, {prefix}{field})', 'bool_map', '2']
|
||||
return tuple(row)
|
||||
else:
|
||||
raise ValueError(f'{ctx}.{attr.name}: bool bound to '
|
||||
f'field {field} ({ctype})')
|
||||
elif attr.type == 'chars':
|
||||
if ctype != 'char' or dim is None:
|
||||
raise ValueError(f'{ctx}.{attr.name}: chars bound to '
|
||||
f'field {field} ({ctype})')
|
||||
lo, hi = attr.arity.lo, attr.arity.hi
|
||||
if str(dim) != str(hi):
|
||||
raise ValueError(f'{ctx}.{attr.name}: chars arity {hi} vs '
|
||||
f'field dim {dim}')
|
||||
kind, length = 'kChars', str(hi)
|
||||
exact = 'true' if lo == hi else 'false'
|
||||
elif attr.type in ('double', 'float', 'int') and attr.arity.hi is None:
|
||||
vec_kinds = {'mjDoubleVec*': 'kDoubleVec', 'mjFloatVec*': 'kFloatVec',
|
||||
'mjIntVec*': 'kIntVec'}
|
||||
kind = vec_kinds.get(ctype)
|
||||
if kind is None:
|
||||
raise ValueError(f'{ctx}.{attr.name}: unbounded vector '
|
||||
f'bound to field {field} ({ctype})')
|
||||
length, exact = '1', 'true'
|
||||
elif attr.type in ('double', 'float', 'int'):
|
||||
kind = KIND_BY_CTYPE.get(ctype)
|
||||
if kind is None or (attr.type == 'int') != (kind == 'kInt'):
|
||||
raise ValueError(f'{ctx}.{attr.name}: schema type '
|
||||
f'{attr.type} vs field {field} ({ctype})')
|
||||
lo, hi = attr.arity.lo, attr.arity.hi
|
||||
length = str(hi)
|
||||
declared = dim if dim is not None else '1'
|
||||
if str(declared) != length:
|
||||
if DIM_EQUIV.get(str(declared)) == length:
|
||||
length = str(declared) # emit the expression: it tracks the header
|
||||
else:
|
||||
raise ValueError(f'{ctx}.{attr.name}: arity {length} vs '
|
||||
f'field dim {declared}')
|
||||
exact = 'true' if lo == hi or (lo == 1 and hi == 1) else 'false'
|
||||
else:
|
||||
raise ValueError(f'{ctx}.{attr.name}: kind {attr.type} is '
|
||||
'not table-drivable yet')
|
||||
offset = f'(int)offsetof({spec}, {prefix}{field})'
|
||||
row = [f'"{attr.name}"', f'mjXAttr::{kind}', length, exact,
|
||||
required, nodefault, handwrite, offset]
|
||||
if kind in ('kEnum', 'kEnumByte', 'kFlags'):
|
||||
row += [f'{attr.target}_map', f'{attr.target}_sz']
|
||||
return tuple(row)
|
||||
|
||||
|
||||
def rows_for(schema, structs, element_name):
|
||||
"""Return (struct name, list of row tuples) for one table-driven element."""
|
||||
element = schema.elements[element_name]
|
||||
if not element.spec:
|
||||
raise ValueError(f'{element_name}: no bound spec struct')
|
||||
sub = element.facets.get('field')
|
||||
key = f'{element.spec}.{sub}' if sub else element.spec
|
||||
fields = structs.get(key)
|
||||
if fields is None:
|
||||
raise ValueError(f'{element_name}: struct {key} not found in headers')
|
||||
prefix = f'{sub}.' if sub else ''
|
||||
|
||||
hand_attrs = set()
|
||||
for gname in HAND_GROUPS:
|
||||
if any(isinstance(m, mjcf_schema.Use) and m.group == gname
|
||||
for m in element.members):
|
||||
hand_attrs |= {m.name for m in schema.groups[gname].members}
|
||||
|
||||
rows = []
|
||||
for const in element.consts():
|
||||
entry = fields.get(const.field)
|
||||
if entry is None:
|
||||
raise ValueError(f'{element_name}: set {const.field}: no field '
|
||||
f'{element.spec}.{const.field}')
|
||||
ctype = entry[0]
|
||||
if not ctype.startswith('mjt') and ctype != 'int':
|
||||
raise ValueError(f'{element_name}: set {const.field}: field is {ctype}')
|
||||
rows.append(('nullptr', 'mjXAttr::kConst', '1', 'true', 'false', 'false',
|
||||
'false',
|
||||
f'(int)offsetof({element.spec}, {prefix}{const.field})',
|
||||
'nullptr', '0', const.value))
|
||||
for attr in schema.expanded_attrs(element):
|
||||
if attr.name in hand_attrs or 'reading' in attr.facets:
|
||||
continue # hand-read in the OneX() remnant
|
||||
row = _attr_row(element.spec, fields, attr, element_name,
|
||||
prefix=prefix)
|
||||
if row is not None:
|
||||
rows.append(row)
|
||||
return element.spec, rows
|
||||
|
||||
|
||||
def rows_for_group(schema, structs, group_name, spec):
|
||||
"""Return list of row tuples for a shared group, bound to the given struct."""
|
||||
fields = structs.get(spec)
|
||||
if fields is None:
|
||||
raise ValueError(f'{group_name}: struct {spec} not in mjspec.h')
|
||||
rows = []
|
||||
for attr in schema.groups[group_name].members:
|
||||
if not isinstance(attr, mjcf_schema.Attr) or 'reading' in attr.facets:
|
||||
continue
|
||||
row = _attr_row(spec, fields, attr, group_name)
|
||||
if row is not None:
|
||||
rows.append(row)
|
||||
return rows
|
||||
|
||||
|
||||
def generate():
|
||||
"""Generate the mjcf_read_table.inc content as a string."""
|
||||
schema = mjcf_schema.parse_file(SCHEMA_PATH)
|
||||
structs = parse_spec_structs(SPEC_H_PATH, MODEL_H_PATH)
|
||||
migrated = table_driven_elements(schema)
|
||||
out = [_HEADER]
|
||||
for name in migrated:
|
||||
struct, rows = rows_for(schema, structs, name)
|
||||
array = f'k{name.capitalize()}Attrs'
|
||||
out.append(f'// {name} ({struct})')
|
||||
out.append(f'inline constexpr mjXAttr {array}[] = {{')
|
||||
for row in rows:
|
||||
out.append(' {' + ', '.join(row) + '},')
|
||||
out.append('};')
|
||||
out.append(f'inline constexpr int {array}N = '
|
||||
f'sizeof({array}) / sizeof({array}[0]);')
|
||||
out.append('')
|
||||
out.append('// sensors fully described by the schema: dispatch by tag')
|
||||
out.append('struct mjXSensorEntry { const char* tag; const mjXAttr* rows;'
|
||||
' int n; };')
|
||||
out.append('inline constexpr mjXSensorEntry kSensorDispatch[] = {')
|
||||
for name in SENSOR_DISPATCH:
|
||||
array = f'k{name.capitalize()}Attrs'
|
||||
tag = schema.elements[name].xml_name()
|
||||
out.append(f' {{"{tag}", {array}, {array}N}},')
|
||||
out.append('};')
|
||||
out.append('inline constexpr int kSensorDispatchN = '
|
||||
'sizeof(kSensorDispatch) / sizeof(kSensorDispatch[0]);')
|
||||
out.append('')
|
||||
for gname, (struct, array) in EMIT_GROUPS.items():
|
||||
rows = rows_for_group(schema, structs, gname, struct)
|
||||
out.append(f'// group {gname} ({struct})')
|
||||
out.append(f'inline constexpr mjXAttr {array}[] = {{')
|
||||
for row in rows:
|
||||
out.append(' {' + ', '.join(row) + '},')
|
||||
out.append('};')
|
||||
out.append(f'inline constexpr int {array}N = '
|
||||
f'sizeof({array}) / sizeof({array}[0]);')
|
||||
out.append('')
|
||||
out.append('// clang-format on')
|
||||
return '\n'.join(out) + '\n'
|
||||
|
||||
|
||||
def main():
|
||||
"""CLI entry point: generate mjcf_read_table.inc to stdout or a file."""
|
||||
if len(sys.argv) > 2:
|
||||
sys.exit('usage: generate_read_table.py [output.inc]')
|
||||
text = generate()
|
||||
if len(sys.argv) == 2:
|
||||
with open(sys.argv[1], 'w', encoding='utf-8') as file:
|
||||
file.write(text)
|
||||
else:
|
||||
sys.stdout.write(text)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,285 @@
|
||||
// Copyright 2026 DeepMind Technologies Limited
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// GENERATED FILE, DO NOT EDIT. Generated from src/xml/mjcf.schema by
|
||||
// doc/generate/generate_default_table.py; test/doc/doc_test.py checks
|
||||
// freshness.
|
||||
//
|
||||
// One row per schema attribute with a declared default, binding the declared
|
||||
// values to the bound field. SchemaDefaultsTest compares every row against a
|
||||
// freshly-constructed spec: the schema's defaults must agree with the C
|
||||
// default-constructors. Rows are {attr, offset, kind, len, ndecl, values};
|
||||
// values beyond ndecl are expected to be zero; kind: 0=double 1=float 2=int
|
||||
// 3=byte 4=mjtNum.
|
||||
|
||||
// clang-format off
|
||||
struct mjXDefaultEntry {
|
||||
const char* attr;
|
||||
int offset;
|
||||
int kind;
|
||||
int len;
|
||||
int ndecl;
|
||||
double value[8];
|
||||
};
|
||||
|
||||
struct mjXDefaultTable {
|
||||
const char* structname;
|
||||
const mjXDefaultEntry* entries;
|
||||
int n;
|
||||
};
|
||||
|
||||
|
||||
static const mjXDefaultEntry kDefaults_mjOption[] = {
|
||||
{"timestep", (int)offsetof(mjOption, timestep), 4, 1, 1, {0.002}},
|
||||
{"impratio", (int)offsetof(mjOption, impratio), 4, 1, 1, {1.0}},
|
||||
{"tolerance", (int)offsetof(mjOption, tolerance), 4, 1, 1, {1e-08}},
|
||||
{"ls_tolerance", (int)offsetof(mjOption, ls_tolerance), 4, 1, 1, {0.01}},
|
||||
{"noslip_tolerance", (int)offsetof(mjOption, noslip_tolerance), 4, 1, 1, {1e-06}},
|
||||
{"ccd_tolerance", (int)offsetof(mjOption, ccd_tolerance), 4, 1, 1, {1e-06}},
|
||||
{"sleep_tolerance", (int)offsetof(mjOption, sleep_tolerance), 4, 1, 1, {0.001}},
|
||||
{"gravity", (int)offsetof(mjOption, gravity), 4, 3, 3, {0.0, 0.0, -9.81}},
|
||||
{"wind", (int)offsetof(mjOption, wind), 4, 3, 3, {0.0, 0.0, 0.0}},
|
||||
{"magnetic", (int)offsetof(mjOption, magnetic), 4, 3, 3, {0.0, -0.5, 0.0}},
|
||||
{"o_friction", (int)offsetof(mjOption, o_friction), 4, 5, 5, {1.0, 1.0, 0.005, 0.0001, 0.0001}},
|
||||
{"integrator", (int)offsetof(mjOption, integrator), 2, 1, 1, {(double)mjINT_EULER}},
|
||||
{"cone", (int)offsetof(mjOption, cone), 2, 1, 1, {(double)mjCONE_PYRAMIDAL}},
|
||||
{"jacobian", (int)offsetof(mjOption, jacobian), 2, 1, 1, {(double)mjJAC_AUTO}},
|
||||
{"solver", (int)offsetof(mjOption, solver), 2, 1, 1, {(double)mjSOL_NEWTON}},
|
||||
{"iterations", (int)offsetof(mjOption, iterations), 2, 1, 1, {100.0}},
|
||||
{"ls_iterations", (int)offsetof(mjOption, ls_iterations), 2, 1, 1, {50.0}},
|
||||
{"ccd_iterations", (int)offsetof(mjOption, ccd_iterations), 2, 1, 1, {35.0}},
|
||||
{"sdf_iterations", (int)offsetof(mjOption, sdf_iterations), 2, 1, 1, {10.0}},
|
||||
{"sdf_initpoints", (int)offsetof(mjOption, sdf_initpoints), 2, 1, 1, {40.0}},
|
||||
};
|
||||
|
||||
static const mjXDefaultEntry kDefaults_mjVisual_global[] = {
|
||||
{"cameraid", (int)offsetof(mjVisual, global.cameraid), 2, 1, 1, {-1.0}},
|
||||
{"orthographic", (int)offsetof(mjVisual, global.orthographic), 2, 1, 1, {0}},
|
||||
{"fovy", (int)offsetof(mjVisual, global.fovy), 1, 1, 1, {45.0}},
|
||||
{"ipd", (int)offsetof(mjVisual, global.ipd), 1, 1, 1, {0.068}},
|
||||
{"azimuth", (int)offsetof(mjVisual, global.azimuth), 1, 1, 1, {90.0}},
|
||||
{"elevation", (int)offsetof(mjVisual, global.elevation), 1, 1, 1, {-45.0}},
|
||||
{"linewidth", (int)offsetof(mjVisual, global.linewidth), 1, 1, 1, {1.0}},
|
||||
{"glow", (int)offsetof(mjVisual, global.glow), 1, 1, 1, {0.3}},
|
||||
{"offwidth", (int)offsetof(mjVisual, global.offwidth), 2, 1, 1, {640.0}},
|
||||
{"offheight", (int)offsetof(mjVisual, global.offheight), 2, 1, 1, {480.0}},
|
||||
{"realtime", (int)offsetof(mjVisual, global.realtime), 1, 1, 1, {1.0}},
|
||||
{"ellipsoidinertia", (int)offsetof(mjVisual, global.ellipsoidinertia), 2, 1, 1, {0}},
|
||||
{"bvactive", (int)offsetof(mjVisual, global.bvactive), 2, 1, 1, {1}},
|
||||
};
|
||||
|
||||
static const mjXDefaultEntry kDefaults_mjVisual_headlight[] = {
|
||||
{"ambient", (int)offsetof(mjVisual, headlight.ambient), 1, 3, 3, {0.1, 0.1, 0.1}},
|
||||
{"diffuse", (int)offsetof(mjVisual, headlight.diffuse), 1, 3, 3, {0.4, 0.4, 0.4}},
|
||||
{"specular", (int)offsetof(mjVisual, headlight.specular), 1, 3, 3, {0.5, 0.5, 0.5}},
|
||||
{"active", (int)offsetof(mjVisual, headlight.active), 2, 1, 1, {1.0}},
|
||||
};
|
||||
|
||||
static const mjXDefaultEntry kDefaults_mjVisual_map[] = {
|
||||
{"stiffness", (int)offsetof(mjVisual, map.stiffness), 1, 1, 1, {100.0}},
|
||||
{"stiffnessrot", (int)offsetof(mjVisual, map.stiffnessrot), 1, 1, 1, {500.0}},
|
||||
{"force", (int)offsetof(mjVisual, map.force), 1, 1, 1, {0.005}},
|
||||
{"torque", (int)offsetof(mjVisual, map.torque), 1, 1, 1, {0.1}},
|
||||
{"alpha", (int)offsetof(mjVisual, map.alpha), 1, 1, 1, {0.3}},
|
||||
{"fogstart", (int)offsetof(mjVisual, map.fogstart), 1, 1, 1, {3.0}},
|
||||
{"fogend", (int)offsetof(mjVisual, map.fogend), 1, 1, 1, {10.0}},
|
||||
{"znear", (int)offsetof(mjVisual, map.znear), 1, 1, 1, {0.01}},
|
||||
{"zfar", (int)offsetof(mjVisual, map.zfar), 1, 1, 1, {50.0}},
|
||||
{"haze", (int)offsetof(mjVisual, map.haze), 1, 1, 1, {0.3}},
|
||||
{"shadowclip", (int)offsetof(mjVisual, map.shadowclip), 1, 1, 1, {1.0}},
|
||||
{"shadowscale", (int)offsetof(mjVisual, map.shadowscale), 1, 1, 1, {0.6}},
|
||||
{"actuatortendon", (int)offsetof(mjVisual, map.actuatortendon), 1, 1, 1, {2.0}},
|
||||
};
|
||||
|
||||
static const mjXDefaultEntry kDefaults_mjVisual_quality[] = {
|
||||
{"shadowsize", (int)offsetof(mjVisual, quality.shadowsize), 2, 1, 1, {4096.0}},
|
||||
{"offsamples", (int)offsetof(mjVisual, quality.offsamples), 2, 1, 1, {4.0}},
|
||||
{"numslices", (int)offsetof(mjVisual, quality.numslices), 2, 1, 1, {28.0}},
|
||||
{"numstacks", (int)offsetof(mjVisual, quality.numstacks), 2, 1, 1, {16.0}},
|
||||
{"numquads", (int)offsetof(mjVisual, quality.numquads), 2, 1, 1, {4.0}},
|
||||
};
|
||||
|
||||
static const mjXDefaultEntry kDefaults_mjVisual_scale[] = {
|
||||
{"forcewidth", (int)offsetof(mjVisual, scale.forcewidth), 1, 1, 1, {0.1}},
|
||||
{"contactwidth", (int)offsetof(mjVisual, scale.contactwidth), 1, 1, 1, {0.3}},
|
||||
{"contactheight", (int)offsetof(mjVisual, scale.contactheight), 1, 1, 1, {0.1}},
|
||||
{"connect", (int)offsetof(mjVisual, scale.connect), 1, 1, 1, {0.2}},
|
||||
{"com", (int)offsetof(mjVisual, scale.com), 1, 1, 1, {0.4}},
|
||||
{"camera", (int)offsetof(mjVisual, scale.camera), 1, 1, 1, {0.3}},
|
||||
{"light", (int)offsetof(mjVisual, scale.light), 1, 1, 1, {0.3}},
|
||||
{"selectpoint", (int)offsetof(mjVisual, scale.selectpoint), 1, 1, 1, {0.2}},
|
||||
{"jointlength", (int)offsetof(mjVisual, scale.jointlength), 1, 1, 1, {1.0}},
|
||||
{"jointwidth", (int)offsetof(mjVisual, scale.jointwidth), 1, 1, 1, {0.1}},
|
||||
{"actuatorlength", (int)offsetof(mjVisual, scale.actuatorlength), 1, 1, 1, {0.7}},
|
||||
{"actuatorwidth", (int)offsetof(mjVisual, scale.actuatorwidth), 1, 1, 1, {0.2}},
|
||||
{"framelength", (int)offsetof(mjVisual, scale.framelength), 1, 1, 1, {1.0}},
|
||||
{"framewidth", (int)offsetof(mjVisual, scale.framewidth), 1, 1, 1, {0.1}},
|
||||
{"constraint", (int)offsetof(mjVisual, scale.constraint), 1, 1, 1, {0.1}},
|
||||
{"slidercrank", (int)offsetof(mjVisual, scale.slidercrank), 1, 1, 1, {0.2}},
|
||||
{"frustum", (int)offsetof(mjVisual, scale.frustum), 1, 1, 1, {10.0}},
|
||||
};
|
||||
|
||||
static const mjXDefaultEntry kDefaults_mjsActuator[] = {
|
||||
{"gear", (int)offsetof(mjsActuator, gear), 0, 6, 6, {1.0, 0.0, 0.0, 0.0, 0.0, 0.0}},
|
||||
{"actdim", (int)offsetof(mjsActuator, actdim), 2, 1, 1, {-1.0}},
|
||||
{"dyntype", (int)offsetof(mjsActuator, dyntype), 2, 1, 1, {(double)mjDYN_NONE}},
|
||||
{"gaintype", (int)offsetof(mjsActuator, gaintype), 2, 1, 1, {(double)mjGAIN_FIXED}},
|
||||
{"biastype", (int)offsetof(mjsActuator, biastype), 2, 1, 1, {(double)mjBIAS_NONE}},
|
||||
{"dynprm", (int)offsetof(mjsActuator, dynprm), 0, mjNDYN, 1, {1.0}},
|
||||
{"gainprm", (int)offsetof(mjsActuator, gainprm), 0, mjNGAIN, 1, {1.0}},
|
||||
};
|
||||
|
||||
static const mjXDefaultEntry kDefaults_mjsBody[] = {
|
||||
{"pos", (int)offsetof(mjsBody, pos), 0, 3, 3, {0.0, 0.0, 0.0}},
|
||||
{"quat", (int)offsetof(mjsBody, quat), 0, 4, 4, {1.0, 0.0, 0.0, 0.0}},
|
||||
{"simple", (int)offsetof(mjsBody, simple), 3, 1, 1, {(double)1}},
|
||||
};
|
||||
|
||||
static const mjXDefaultEntry kDefaults_mjsCamera[] = {
|
||||
{"fovy", (int)offsetof(mjsCamera, fovy), 0, 1, 1, {45.0}},
|
||||
{"ipd", (int)offsetof(mjsCamera, ipd), 0, 1, 1, {0.068}},
|
||||
{"resolution", (int)offsetof(mjsCamera, resolution), 2, 2, 2, {1.0, 1.0}},
|
||||
{"pos", (int)offsetof(mjsCamera, pos), 0, 3, 3, {0.0, 0.0, 0.0}},
|
||||
{"quat", (int)offsetof(mjsCamera, quat), 0, 4, 4, {1.0, 0.0, 0.0, 0.0}},
|
||||
{"mode", (int)offsetof(mjsCamera, mode), 2, 1, 1, {(double)mjCAMLIGHT_FIXED}},
|
||||
};
|
||||
|
||||
static const mjXDefaultEntry kDefaults_mjsEquality[] = {
|
||||
{"active", (int)offsetof(mjsEquality, active), 3, 1, 1, {1}},
|
||||
{"solref", (int)offsetof(mjsEquality, solref), 4, mjNREF, 2, {0.02, 1.0}},
|
||||
{"solimp", (int)offsetof(mjsEquality, solimp), 4, mjNIMP, 5, {0.9, 0.95, 0.001, 0.5, 2.0}},
|
||||
};
|
||||
|
||||
static const mjXDefaultEntry kDefaults_mjsFrame[] = {
|
||||
{"quat", (int)offsetof(mjsFrame, quat), 0, 4, 4, {1.0, 0.0, 0.0, 0.0}},
|
||||
};
|
||||
|
||||
static const mjXDefaultEntry kDefaults_mjsGeom[] = {
|
||||
{"type", (int)offsetof(mjsGeom, type), 2, 1, 1, {(double)mjGEOM_SPHERE}},
|
||||
{"contype", (int)offsetof(mjsGeom, contype), 2, 1, 1, {1.0}},
|
||||
{"conaffinity", (int)offsetof(mjsGeom, conaffinity), 2, 1, 1, {1.0}},
|
||||
{"condim", (int)offsetof(mjsGeom, condim), 2, 1, 1, {3.0}},
|
||||
{"friction", (int)offsetof(mjsGeom, friction), 0, 3, 3, {1.0, 0.005, 0.0001}},
|
||||
{"density", (int)offsetof(mjsGeom, density), 0, 1, 1, {1000.0}},
|
||||
{"solmix", (int)offsetof(mjsGeom, solmix), 0, 1, 1, {1.0}},
|
||||
{"solref", (int)offsetof(mjsGeom, solref), 4, mjNREF, 2, {0.02, 1.0}},
|
||||
{"solimp", (int)offsetof(mjsGeom, solimp), 4, mjNIMP, 5, {0.9, 0.95, 0.001, 0.5, 2.0}},
|
||||
{"pos", (int)offsetof(mjsGeom, pos), 0, 3, 3, {0.0, 0.0, 0.0}},
|
||||
{"quat", (int)offsetof(mjsGeom, quat), 0, 4, 4, {1.0, 0.0, 0.0, 0.0}},
|
||||
{"fitscale", (int)offsetof(mjsGeom, fitscale), 0, 1, 1, {1.0}},
|
||||
{"rgba", (int)offsetof(mjsGeom, rgba), 1, 4, 4, {0.5, 0.5, 0.5, 1.0}},
|
||||
};
|
||||
|
||||
static const mjXDefaultEntry kDefaults_mjsJoint[] = {
|
||||
{"type", (int)offsetof(mjsJoint, type), 2, 1, 1, {(double)mjJNT_HINGE}},
|
||||
{"axis", (int)offsetof(mjsJoint, axis), 0, 3, 3, {0.0, 0.0, 1.0}},
|
||||
};
|
||||
|
||||
static const mjXDefaultEntry kDefaults_mjsLight[] = {
|
||||
{"castshadow", (int)offsetof(mjsLight, castshadow), 3, 1, 1, {1}},
|
||||
{"active", (int)offsetof(mjsLight, active), 3, 1, 1, {1}},
|
||||
{"pos", (int)offsetof(mjsLight, pos), 0, 3, 3, {0.0, 0.0, 0.0}},
|
||||
{"dir", (int)offsetof(mjsLight, dir), 0, 3, 3, {0.0, 0.0, -1.0}},
|
||||
{"bulbradius", (int)offsetof(mjsLight, bulbradius), 1, 1, 1, {0.02}},
|
||||
{"range", (int)offsetof(mjsLight, range), 1, 1, 1, {10.0}},
|
||||
{"attenuation", (int)offsetof(mjsLight, attenuation), 1, 3, 3, {1.0, 0.0, 0.0}},
|
||||
{"cutoff", (int)offsetof(mjsLight, cutoff), 1, 1, 1, {45.0}},
|
||||
{"exponent", (int)offsetof(mjsLight, exponent), 1, 1, 1, {10.0}},
|
||||
{"diffuse", (int)offsetof(mjsLight, diffuse), 1, 3, 3, {0.7, 0.7, 0.7}},
|
||||
{"specular", (int)offsetof(mjsLight, specular), 1, 3, 3, {0.3, 0.3, 0.3}},
|
||||
{"mode", (int)offsetof(mjsLight, mode), 2, 1, 1, {(double)mjCAMLIGHT_FIXED}},
|
||||
};
|
||||
|
||||
static const mjXDefaultEntry kDefaults_mjsMaterial[] = {
|
||||
{"texrepeat", (int)offsetof(mjsMaterial, texrepeat), 1, 2, 2, {1.0, 1.0}},
|
||||
{"specular", (int)offsetof(mjsMaterial, specular), 1, 1, 1, {0.5}},
|
||||
{"shininess", (int)offsetof(mjsMaterial, shininess), 1, 1, 1, {0.5}},
|
||||
{"metallic", (int)offsetof(mjsMaterial, metallic), 1, 1, 1, {-1.0}},
|
||||
{"roughness", (int)offsetof(mjsMaterial, roughness), 1, 1, 1, {-1.0}},
|
||||
{"rgba", (int)offsetof(mjsMaterial, rgba), 1, 4, 4, {1.0, 1.0, 1.0, 1.0}},
|
||||
};
|
||||
|
||||
static const mjXDefaultEntry kDefaults_mjsMesh[] = {
|
||||
{"refquat", (int)offsetof(mjsMesh, refquat), 0, 4, 4, {1.0, 0.0, 0.0, 0.0}},
|
||||
{"scale", (int)offsetof(mjsMesh, scale), 0, 3, 3, {1.0, 1.0, 1.0}},
|
||||
{"maxhullvert", (int)offsetof(mjsMesh, maxhullvert), 2, 1, 1, {-1.0}},
|
||||
{"inertia", (int)offsetof(mjsMesh, inertia), 2, 1, 1, {(double)mjMESH_INERTIA_LEGACY}},
|
||||
};
|
||||
|
||||
static const mjXDefaultEntry kDefaults_mjsPair[] = {
|
||||
{"condim", (int)offsetof(mjsPair, condim), 2, 1, 1, {3.0}},
|
||||
{"friction", (int)offsetof(mjsPair, friction), 0, 5, 5, {1.0, 1.0, 0.005, 0.0001, 0.0001}},
|
||||
{"solref", (int)offsetof(mjsPair, solref), 4, mjNREF, 2, {0.02, 1.0}},
|
||||
{"solimp", (int)offsetof(mjsPair, solimp), 4, mjNIMP, 5, {0.9, 0.95, 0.001, 0.5, 2.0}},
|
||||
};
|
||||
|
||||
static const mjXDefaultEntry kDefaults_mjsSensor[] = {
|
||||
{"datatype", (int)offsetof(mjsSensor, datatype), 2, 1, 1, {(double)mjDATATYPE_REAL}},
|
||||
{"needstage", (int)offsetof(mjsSensor, needstage), 2, 1, 1, {(double)mjSTAGE_ACC}},
|
||||
};
|
||||
|
||||
static const mjXDefaultEntry kDefaults_mjsSite[] = {
|
||||
{"type", (int)offsetof(mjsSite, type), 2, 1, 1, {(double)mjGEOM_SPHERE}},
|
||||
{"pos", (int)offsetof(mjsSite, pos), 0, 3, 3, {0.0, 0.0, 0.0}},
|
||||
{"quat", (int)offsetof(mjsSite, quat), 0, 4, 4, {1.0, 0.0, 0.0, 0.0}},
|
||||
{"size", (int)offsetof(mjsSite, size), 0, 3, 3, {0.005, 0.005, 0.005}},
|
||||
{"rgba", (int)offsetof(mjsSite, rgba), 1, 4, 4, {0.5, 0.5, 0.5, 1.0}},
|
||||
};
|
||||
|
||||
static const mjXDefaultEntry kDefaults_mjsSkin[] = {
|
||||
{"rgba", (int)offsetof(mjsSkin, rgba), 1, 4, 4, {0.5, 0.5, 0.5, 1.0}},
|
||||
};
|
||||
|
||||
static const mjXDefaultEntry kDefaults_mjsTendon[] = {
|
||||
{"springlength", (int)offsetof(mjsTendon, springlength), 0, 2, 2, {-1.0, -1.0}},
|
||||
{"width", (int)offsetof(mjsTendon, width), 0, 1, 1, {0.003}},
|
||||
{"rgba", (int)offsetof(mjsTendon, rgba), 1, 4, 4, {0.5, 0.5, 0.5, 1.0}},
|
||||
};
|
||||
|
||||
static const mjXDefaultEntry kDefaults_mjsTexture[] = {
|
||||
{"type", (int)offsetof(mjsTexture, type), 2, 1, 1, {(double)mjTEXTURE_CUBE}},
|
||||
{"colorspace", (int)offsetof(mjsTexture, colorspace), 2, 1, 1, {(double)mjCOLORSPACE_AUTO}},
|
||||
{"gridsize", (int)offsetof(mjsTexture, gridsize), 2, 2, 2, {1.0, 1.0}},
|
||||
{"rgb1", (int)offsetof(mjsTexture, rgb1), 0, 3, 3, {0.8, 0.8, 0.8}},
|
||||
{"rgb2", (int)offsetof(mjsTexture, rgb2), 0, 3, 3, {0.5, 0.5, 0.5}},
|
||||
{"random", (int)offsetof(mjsTexture, random), 0, 1, 1, {0.01}},
|
||||
{"nchannel", (int)offsetof(mjsTexture, nchannel), 2, 1, 1, {3.0}},
|
||||
};
|
||||
|
||||
static const mjXDefaultTable kDefaultTables[] = {
|
||||
{"mjOption", kDefaults_mjOption, (int)(sizeof(kDefaults_mjOption) / sizeof(kDefaults_mjOption[0]))},
|
||||
{"mjVisual", kDefaults_mjVisual_global, (int)(sizeof(kDefaults_mjVisual_global) / sizeof(kDefaults_mjVisual_global[0]))},
|
||||
{"mjVisual", kDefaults_mjVisual_headlight, (int)(sizeof(kDefaults_mjVisual_headlight) / sizeof(kDefaults_mjVisual_headlight[0]))},
|
||||
{"mjVisual", kDefaults_mjVisual_map, (int)(sizeof(kDefaults_mjVisual_map) / sizeof(kDefaults_mjVisual_map[0]))},
|
||||
{"mjVisual", kDefaults_mjVisual_quality, (int)(sizeof(kDefaults_mjVisual_quality) / sizeof(kDefaults_mjVisual_quality[0]))},
|
||||
{"mjVisual", kDefaults_mjVisual_scale, (int)(sizeof(kDefaults_mjVisual_scale) / sizeof(kDefaults_mjVisual_scale[0]))},
|
||||
{"mjsActuator", kDefaults_mjsActuator, (int)(sizeof(kDefaults_mjsActuator) / sizeof(kDefaults_mjsActuator[0]))},
|
||||
{"mjsBody", kDefaults_mjsBody, (int)(sizeof(kDefaults_mjsBody) / sizeof(kDefaults_mjsBody[0]))},
|
||||
{"mjsCamera", kDefaults_mjsCamera, (int)(sizeof(kDefaults_mjsCamera) / sizeof(kDefaults_mjsCamera[0]))},
|
||||
{"mjsEquality", kDefaults_mjsEquality, (int)(sizeof(kDefaults_mjsEquality) / sizeof(kDefaults_mjsEquality[0]))},
|
||||
{"mjsFrame", kDefaults_mjsFrame, (int)(sizeof(kDefaults_mjsFrame) / sizeof(kDefaults_mjsFrame[0]))},
|
||||
{"mjsGeom", kDefaults_mjsGeom, (int)(sizeof(kDefaults_mjsGeom) / sizeof(kDefaults_mjsGeom[0]))},
|
||||
{"mjsJoint", kDefaults_mjsJoint, (int)(sizeof(kDefaults_mjsJoint) / sizeof(kDefaults_mjsJoint[0]))},
|
||||
{"mjsLight", kDefaults_mjsLight, (int)(sizeof(kDefaults_mjsLight) / sizeof(kDefaults_mjsLight[0]))},
|
||||
{"mjsMaterial", kDefaults_mjsMaterial, (int)(sizeof(kDefaults_mjsMaterial) / sizeof(kDefaults_mjsMaterial[0]))},
|
||||
{"mjsMesh", kDefaults_mjsMesh, (int)(sizeof(kDefaults_mjsMesh) / sizeof(kDefaults_mjsMesh[0]))},
|
||||
{"mjsPair", kDefaults_mjsPair, (int)(sizeof(kDefaults_mjsPair) / sizeof(kDefaults_mjsPair[0]))},
|
||||
{"mjsSensor", kDefaults_mjsSensor, (int)(sizeof(kDefaults_mjsSensor) / sizeof(kDefaults_mjsSensor[0]))},
|
||||
{"mjsSite", kDefaults_mjsSite, (int)(sizeof(kDefaults_mjsSite) / sizeof(kDefaults_mjsSite[0]))},
|
||||
{"mjsSkin", kDefaults_mjsSkin, (int)(sizeof(kDefaults_mjsSkin) / sizeof(kDefaults_mjsSkin[0]))},
|
||||
{"mjsTendon", kDefaults_mjsTendon, (int)(sizeof(kDefaults_mjsTendon) / sizeof(kDefaults_mjsTendon[0]))},
|
||||
{"mjsTexture", kDefaults_mjsTexture, (int)(sizeof(kDefaults_mjsTexture) / sizeof(kDefaults_mjsTexture[0]))},
|
||||
};
|
||||
static const int kDefaultTablesN = (int)(sizeof(kDefaultTables) / sizeof(kDefaultTables[0]));
|
||||
// clang-format on
|
||||
@@ -0,0 +1,489 @@
|
||||
// Copyright 2026 DeepMind Technologies Limited
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// GENERATED FILE, DO NOT EDIT. Generated from src/xml/mjcf.schema by
|
||||
// doc/generate/generate_mjcf_map.py; test/doc/doc_test.py checks freshness.
|
||||
//
|
||||
// Keyword maps, one per schema enum, shared by the reader, the writer and
|
||||
// the generated tables. Inline variables: including this header is all a
|
||||
// translation unit needs.
|
||||
|
||||
#ifndef MUJOCO_SRC_XML_MJCF_MAP_H_
|
||||
#define MUJOCO_SRC_XML_MJCF_MAP_H_
|
||||
|
||||
#include <mujoco/mjspec.h>
|
||||
#include <mujoco/mjtype.h>
|
||||
#include "user/user_composite.h"
|
||||
#include "user/user_flexcomp.h"
|
||||
#include "xml/xml_util.h"
|
||||
|
||||
// clang-format off
|
||||
|
||||
// keywords of the built-in bool type (not a schema enum)
|
||||
inline constexpr mjMap bool_map[] = {
|
||||
{"false", 0},
|
||||
{"true", 1},
|
||||
};
|
||||
|
||||
// enum coordinate
|
||||
inline constexpr mjMap coordinate_map[] = {
|
||||
{"local", 0},
|
||||
{"global", 1},
|
||||
};
|
||||
inline constexpr int coordinate_sz = 2;
|
||||
|
||||
// enum angle
|
||||
inline constexpr mjMap angle_map[] = {
|
||||
{"radian", 0},
|
||||
{"degree", 1},
|
||||
};
|
||||
inline constexpr int angle_sz = 2;
|
||||
|
||||
// enum fluidshape
|
||||
inline constexpr mjMap fluidshape_map[] = {
|
||||
{"none", 0},
|
||||
{"ellipsoid", 1},
|
||||
};
|
||||
inline constexpr int fluidshape_sz = 2;
|
||||
|
||||
// enum enable
|
||||
inline constexpr mjMap enable_map[] = {
|
||||
{"disable", 0},
|
||||
{"enable", 1},
|
||||
};
|
||||
inline constexpr int enable_sz = 2;
|
||||
|
||||
// enum FalseTrueAuto
|
||||
inline constexpr mjMap FalseTrueAuto_map[] = {
|
||||
{"false", 0},
|
||||
{"true", 1},
|
||||
{"auto", 2},
|
||||
};
|
||||
inline constexpr int FalseTrueAuto_sz = 3;
|
||||
|
||||
// enum FalseAuto
|
||||
inline constexpr mjMap FalseAuto_map[] = {
|
||||
{"false", 0},
|
||||
{"auto", 1},
|
||||
};
|
||||
inline constexpr int FalseAuto_sz = 2;
|
||||
|
||||
// enum bodysleep
|
||||
inline constexpr mjMap bodysleep_map[] = {
|
||||
{"auto", mjSLEEP_AUTO},
|
||||
{"never", mjSLEEP_NEVER},
|
||||
{"allowed", mjSLEEP_ALLOWED},
|
||||
{"init", mjSLEEP_INIT},
|
||||
};
|
||||
inline constexpr int bodysleep_sz = 4;
|
||||
|
||||
// enum jointtype
|
||||
inline constexpr mjMap jointtype_map[] = {
|
||||
{"free", mjJNT_FREE},
|
||||
{"ball", mjJNT_BALL},
|
||||
{"slide", mjJNT_SLIDE},
|
||||
{"hinge", mjJNT_HINGE},
|
||||
};
|
||||
inline constexpr int jointtype_sz = 4;
|
||||
|
||||
// enum geomtype
|
||||
inline constexpr mjMap geomtype_map[] = {
|
||||
{"plane", mjGEOM_PLANE},
|
||||
{"hfield", mjGEOM_HFIELD},
|
||||
{"sphere", mjGEOM_SPHERE},
|
||||
{"capsule", mjGEOM_CAPSULE},
|
||||
{"ellipsoid", mjGEOM_ELLIPSOID},
|
||||
{"cylinder", mjGEOM_CYLINDER},
|
||||
{"box", mjGEOM_BOX},
|
||||
{"mesh", mjGEOM_MESH},
|
||||
{"sdf", mjGEOM_SDF},
|
||||
};
|
||||
inline constexpr int geomtype_sz = 9;
|
||||
|
||||
// enum projection
|
||||
inline constexpr mjMap projection_map[] = {
|
||||
{"perspective", mjPROJ_PERSPECTIVE},
|
||||
{"orthographic", mjPROJ_ORTHOGRAPHIC},
|
||||
};
|
||||
inline constexpr int projection_sz = 2;
|
||||
|
||||
// enum camlight
|
||||
inline constexpr mjMap camlight_map[] = {
|
||||
{"fixed", mjCAMLIGHT_FIXED},
|
||||
{"track", mjCAMLIGHT_TRACK},
|
||||
{"trackcom", mjCAMLIGHT_TRACKCOM},
|
||||
{"targetbody", mjCAMLIGHT_TARGETBODY},
|
||||
{"targetbodycom", mjCAMLIGHT_TARGETBODYCOM},
|
||||
};
|
||||
inline constexpr int camlight_sz = 5;
|
||||
|
||||
// enum lighttype
|
||||
inline constexpr mjMap lighttype_map[] = {
|
||||
{"spot", mjLIGHT_SPOT},
|
||||
{"directional", mjLIGHT_DIRECTIONAL},
|
||||
{"point", mjLIGHT_POINT},
|
||||
{"image", mjLIGHT_IMAGE},
|
||||
};
|
||||
inline constexpr int lighttype_sz = 4;
|
||||
|
||||
// enum texrole
|
||||
inline constexpr mjMap texrole_map[] = {
|
||||
{"rgb", mjTEXROLE_RGB},
|
||||
{"occlusion", mjTEXROLE_OCCLUSION},
|
||||
{"roughness", mjTEXROLE_ROUGHNESS},
|
||||
{"metallic", mjTEXROLE_METALLIC},
|
||||
{"normal", mjTEXROLE_NORMAL},
|
||||
{"opacity", mjTEXROLE_OPACITY},
|
||||
{"emissive", mjTEXROLE_EMISSIVE},
|
||||
{"rgba", mjTEXROLE_RGBA},
|
||||
{"orm", mjTEXROLE_ORM},
|
||||
};
|
||||
inline constexpr int texrole_sz = 9;
|
||||
|
||||
// enum integrator
|
||||
inline constexpr mjMap integrator_map[] = {
|
||||
{"Euler", mjINT_EULER},
|
||||
{"RK4", mjINT_RK4},
|
||||
{"implicit", mjINT_IMPLICIT},
|
||||
{"implicitfast", mjINT_IMPLICITFAST},
|
||||
};
|
||||
inline constexpr int integrator_sz = 4;
|
||||
|
||||
// enum cone
|
||||
inline constexpr mjMap cone_map[] = {
|
||||
{"pyramidal", mjCONE_PYRAMIDAL},
|
||||
{"elliptic", mjCONE_ELLIPTIC},
|
||||
};
|
||||
inline constexpr int cone_sz = 2;
|
||||
|
||||
// enum jacobian
|
||||
inline constexpr mjMap jacobian_map[] = {
|
||||
{"dense", mjJAC_DENSE},
|
||||
{"sparse", mjJAC_SPARSE},
|
||||
{"auto", mjJAC_AUTO},
|
||||
};
|
||||
inline constexpr int jacobian_sz = 3;
|
||||
|
||||
// enum solver
|
||||
inline constexpr mjMap solver_map[] = {
|
||||
{"PGS", mjSOL_PGS},
|
||||
{"CG", mjSOL_CG},
|
||||
{"Newton", mjSOL_NEWTON},
|
||||
};
|
||||
inline constexpr int solver_sz = 3;
|
||||
|
||||
// enum equality
|
||||
inline constexpr mjMap equality_map[] = {
|
||||
{"connect", mjEQ_CONNECT},
|
||||
{"weld", mjEQ_WELD},
|
||||
{"joint", mjEQ_JOINT},
|
||||
{"tendon", mjEQ_TENDON},
|
||||
{"flex", mjEQ_FLEX},
|
||||
{"flexvert", mjEQ_FLEXVERT},
|
||||
{"flexstrain", mjEQ_FLEXSTRAIN},
|
||||
{"distance", mjEQ_DISTANCE},
|
||||
};
|
||||
inline constexpr int equality_sz = 8;
|
||||
|
||||
// enum texture
|
||||
inline constexpr mjMap texture_map[] = {
|
||||
{"2d", mjTEXTURE_2D},
|
||||
{"cube", mjTEXTURE_CUBE},
|
||||
{"skybox", mjTEXTURE_SKYBOX},
|
||||
};
|
||||
inline constexpr int texture_sz = 3;
|
||||
|
||||
// enum colorspace
|
||||
inline constexpr mjMap colorspace_map[] = {
|
||||
{"auto", mjCOLORSPACE_AUTO},
|
||||
{"linear", mjCOLORSPACE_LINEAR},
|
||||
{"sRGB", mjCOLORSPACE_SRGB},
|
||||
};
|
||||
inline constexpr int colorspace_sz = 3;
|
||||
|
||||
// enum builtin
|
||||
inline constexpr mjMap builtin_map[] = {
|
||||
{"none", mjBUILTIN_NONE},
|
||||
{"gradient", mjBUILTIN_GRADIENT},
|
||||
{"checker", mjBUILTIN_CHECKER},
|
||||
{"flat", mjBUILTIN_FLAT},
|
||||
};
|
||||
inline constexpr int builtin_sz = 4;
|
||||
|
||||
// enum mark
|
||||
inline constexpr mjMap mark_map[] = {
|
||||
{"none", mjMARK_NONE},
|
||||
{"edge", mjMARK_EDGE},
|
||||
{"cross", mjMARK_CROSS},
|
||||
{"random", mjMARK_RANDOM},
|
||||
};
|
||||
inline constexpr int mark_sz = 4;
|
||||
|
||||
// enum dyn
|
||||
inline constexpr mjMap dyn_map[] = {
|
||||
{"none", mjDYN_NONE},
|
||||
{"integrator", mjDYN_INTEGRATOR},
|
||||
{"filter", mjDYN_FILTER},
|
||||
{"filterexact", mjDYN_FILTEREXACT},
|
||||
{"muscle", mjDYN_MUSCLE},
|
||||
{"dcmotor", mjDYN_DCMOTOR},
|
||||
{"pid", mjDYN_PID},
|
||||
{"user", mjDYN_USER},
|
||||
};
|
||||
inline constexpr int dyn_sz = 8;
|
||||
|
||||
// enum dcmotorinput
|
||||
inline constexpr mjMap dcmotorinput_map[] = {
|
||||
{"voltage", 0},
|
||||
{"position", 1},
|
||||
{"velocity", 2},
|
||||
};
|
||||
inline constexpr int dcmotorinput_sz = 3;
|
||||
|
||||
// enum gain
|
||||
inline constexpr mjMap gain_map[] = {
|
||||
{"fixed", mjGAIN_FIXED},
|
||||
{"affine", mjGAIN_AFFINE},
|
||||
{"muscle", mjGAIN_MUSCLE},
|
||||
{"dcmotor", mjGAIN_DCMOTOR},
|
||||
{"so3", mjGAIN_SO3},
|
||||
{"pid", mjGAIN_PID},
|
||||
{"user", mjGAIN_USER},
|
||||
};
|
||||
inline constexpr int gain_sz = 7;
|
||||
|
||||
// enum inputchart
|
||||
inline constexpr mjMap inputchart_map[] = {
|
||||
{"expmap", mjCHART_EXPMAP},
|
||||
{"quat", mjCHART_QUAT},
|
||||
};
|
||||
inline constexpr int inputchart_sz = 2;
|
||||
|
||||
// enum inputbit
|
||||
inline constexpr mjMap inputbit_map[] = {
|
||||
{"pos", mjINPUT_POS},
|
||||
{"vel", mjINPUT_VEL},
|
||||
{"ff", mjINPUT_FF},
|
||||
};
|
||||
inline constexpr int inputbit_sz = 3;
|
||||
|
||||
// enum bias
|
||||
inline constexpr mjMap bias_map[] = {
|
||||
{"none", mjBIAS_NONE},
|
||||
{"affine", mjBIAS_AFFINE},
|
||||
{"muscle", mjBIAS_MUSCLE},
|
||||
{"dcmotor", mjBIAS_DCMOTOR},
|
||||
{"so3", mjBIAS_SO3},
|
||||
{"user", mjBIAS_USER},
|
||||
};
|
||||
inline constexpr int bias_sz = 6;
|
||||
|
||||
// enum interp
|
||||
inline constexpr mjMap interp_map[] = {
|
||||
{"zoh", 0},
|
||||
{"linear", 1},
|
||||
{"cubic", 2},
|
||||
};
|
||||
inline constexpr int interp_sz = 3;
|
||||
|
||||
// enum stage
|
||||
inline constexpr mjMap stage_map[] = {
|
||||
{"none", mjSTAGE_NONE},
|
||||
{"pos", mjSTAGE_POS},
|
||||
{"vel", mjSTAGE_VEL},
|
||||
{"acc", mjSTAGE_ACC},
|
||||
};
|
||||
inline constexpr int stage_sz = 4;
|
||||
|
||||
// enum datatype
|
||||
inline constexpr mjMap datatype_map[] = {
|
||||
{"real", mjDATATYPE_REAL},
|
||||
{"positive", mjDATATYPE_POSITIVE},
|
||||
{"axis", mjDATATYPE_AXIS},
|
||||
{"quaternion", mjDATATYPE_QUATERNION},
|
||||
};
|
||||
inline constexpr int datatype_sz = 4;
|
||||
|
||||
// enum frameobj
|
||||
inline constexpr mjMap frameobj_map[] = {
|
||||
{"body", mjOBJ_BODY},
|
||||
{"xbody", mjOBJ_XBODY},
|
||||
{"geom", mjOBJ_GEOM},
|
||||
{"site", mjOBJ_SITE},
|
||||
{"camera", mjOBJ_CAMERA},
|
||||
};
|
||||
inline constexpr int frameobj_sz = 5;
|
||||
|
||||
// enum condata
|
||||
inline constexpr mjMap condata_map[] = {
|
||||
{"found", mjCONDATA_FOUND},
|
||||
{"force", mjCONDATA_FORCE},
|
||||
{"torque", mjCONDATA_TORQUE},
|
||||
{"dist", mjCONDATA_DIST},
|
||||
{"pos", mjCONDATA_POS},
|
||||
{"normal", mjCONDATA_NORMAL},
|
||||
{"tangent", mjCONDATA_TANGENT},
|
||||
};
|
||||
inline constexpr int condata_sz = 7;
|
||||
|
||||
// enum raydata
|
||||
inline constexpr mjMap raydata_map[] = {
|
||||
{"dist", mjRAYDATA_DIST},
|
||||
{"dir", mjRAYDATA_DIR},
|
||||
{"origin", mjRAYDATA_ORIGIN},
|
||||
{"point", mjRAYDATA_POINT},
|
||||
{"normal", mjRAYDATA_NORMAL},
|
||||
{"depth", mjRAYDATA_DEPTH},
|
||||
};
|
||||
inline constexpr int raydata_sz = 6;
|
||||
|
||||
// enum camout
|
||||
inline constexpr mjMap camout_map[] = {
|
||||
{"rgb", mjCAMOUT_RGB},
|
||||
{"depth", mjCAMOUT_DEPTH},
|
||||
{"distance", mjCAMOUT_DIST},
|
||||
{"normal", mjCAMOUT_NORMAL},
|
||||
{"segmentation", mjCAMOUT_SEG},
|
||||
};
|
||||
inline constexpr int camout_sz = 5;
|
||||
|
||||
// enum reduce
|
||||
inline constexpr mjMap reduce_map[] = {
|
||||
{"none", 0},
|
||||
{"mindist", 1},
|
||||
{"maxforce", 2},
|
||||
{"netforce", 3},
|
||||
};
|
||||
inline constexpr int reduce_sz = 4;
|
||||
|
||||
// enum conflict
|
||||
inline constexpr mjMap conflict_map[] = {
|
||||
{"warning", mjCONFLICT_WARNING},
|
||||
{"merge", mjCONFLICT_MERGE},
|
||||
{"error", mjCONFLICT_ERROR},
|
||||
};
|
||||
inline constexpr int conflict_sz = 3;
|
||||
|
||||
// enum lrmode
|
||||
inline constexpr mjMap lrmode_map[] = {
|
||||
{"none", mjLRMODE_NONE},
|
||||
{"muscle", mjLRMODE_MUSCLE},
|
||||
{"muscleuser", mjLRMODE_MUSCLEUSER},
|
||||
{"all", mjLRMODE_ALL},
|
||||
};
|
||||
inline constexpr int lrmode_sz = 4;
|
||||
|
||||
// enum comp
|
||||
inline constexpr mjMap comp_map[] = {
|
||||
{"particle", mjCOMPTYPE_PARTICLE},
|
||||
{"grid", mjCOMPTYPE_GRID},
|
||||
{"rope", mjCOMPTYPE_ROPE},
|
||||
{"loop", mjCOMPTYPE_LOOP},
|
||||
{"cable", mjCOMPTYPE_CABLE},
|
||||
{"cloth", mjCOMPTYPE_CLOTH},
|
||||
};
|
||||
inline constexpr int comp_sz = 6;
|
||||
|
||||
// enum jkind
|
||||
inline constexpr mjMap jkind_map[] = {
|
||||
{"main", mjCOMPKIND_JOINT},
|
||||
};
|
||||
inline constexpr int jkind_sz = 1;
|
||||
|
||||
// enum shape
|
||||
inline constexpr mjMap shape_map[] = {
|
||||
{"s", mjCOMPSHAPE_LINE},
|
||||
{"cos(s)", mjCOMPSHAPE_COS},
|
||||
{"sin(s)", mjCOMPSHAPE_SIN},
|
||||
{"0", mjCOMPSHAPE_ZERO},
|
||||
};
|
||||
inline constexpr int shape_sz = 4;
|
||||
|
||||
// enum meshinertia
|
||||
inline constexpr mjMap meshinertia_map[] = {
|
||||
{"convex", mjMESH_INERTIA_CONVEX},
|
||||
{"legacy", mjMESH_INERTIA_LEGACY},
|
||||
{"exact", mjMESH_INERTIA_EXACT},
|
||||
{"shell", mjMESH_INERTIA_SHELL},
|
||||
};
|
||||
inline constexpr int meshinertia_sz = 4;
|
||||
|
||||
// enum meshbuiltin
|
||||
inline constexpr mjMap meshbuiltin_map[] = {
|
||||
{"none", mjMESH_BUILTIN_NONE},
|
||||
{"sphere", mjMESH_BUILTIN_SPHERE},
|
||||
{"hemisphere", mjMESH_BUILTIN_HEMISPHERE},
|
||||
{"cone", mjMESH_BUILTIN_CONE},
|
||||
{"supertorus", mjMESH_BUILTIN_SUPERTORUS},
|
||||
{"supersphere", mjMESH_BUILTIN_SUPERSPHERE},
|
||||
{"wedge", mjMESH_BUILTIN_WEDGE},
|
||||
{"plate", mjMESH_BUILTIN_PLATE},
|
||||
};
|
||||
inline constexpr int meshbuiltin_sz = 8;
|
||||
|
||||
// enum fcomp
|
||||
inline constexpr mjMap fcomp_map[] = {
|
||||
{"grid", mjFCOMPTYPE_GRID},
|
||||
{"box", mjFCOMPTYPE_BOX},
|
||||
{"cylinder", mjFCOMPTYPE_CYLINDER},
|
||||
{"ellipsoid", mjFCOMPTYPE_ELLIPSOID},
|
||||
{"square", mjFCOMPTYPE_SQUARE},
|
||||
{"disc", mjFCOMPTYPE_DISC},
|
||||
{"circle", mjFCOMPTYPE_CIRCLE},
|
||||
{"mesh", mjFCOMPTYPE_MESH},
|
||||
{"gmsh", mjFCOMPTYPE_GMSH},
|
||||
{"direct", mjFCOMPTYPE_DIRECT},
|
||||
};
|
||||
inline constexpr int fcomp_sz = 10;
|
||||
|
||||
// enum fdof
|
||||
inline constexpr mjMap fdof_map[] = {
|
||||
{"full", mjFCOMPDOF_FULL},
|
||||
{"radial", mjFCOMPDOF_RADIAL},
|
||||
{"trilinear", mjFCOMPDOF_TRILINEAR},
|
||||
{"quadratic", mjFCOMPDOF_QUADRATIC},
|
||||
{"2d", mjFCOMPDOF_2D},
|
||||
};
|
||||
inline constexpr int fdof_sz = 5;
|
||||
|
||||
// enum flexself
|
||||
inline constexpr mjMap flexself_map[] = {
|
||||
{"none", mjFLEXSELF_NONE},
|
||||
{"narrow", mjFLEXSELF_NARROW},
|
||||
{"bvh", mjFLEXSELF_BVH},
|
||||
{"sap", mjFLEXSELF_SAP},
|
||||
{"auto", mjFLEXSELF_AUTO},
|
||||
};
|
||||
inline constexpr int flexself_sz = 5;
|
||||
|
||||
// enum elastic2d
|
||||
inline constexpr mjMap elastic2d_map[] = {
|
||||
{"none", 0},
|
||||
{"bend", 1},
|
||||
{"stretch", 2},
|
||||
{"both", 3},
|
||||
};
|
||||
inline constexpr int elastic2d_sz = 4;
|
||||
|
||||
// enum flexeq
|
||||
inline constexpr mjMap flexeq_map[] = {
|
||||
{"false", 0},
|
||||
{"true", 1},
|
||||
{"vert", 2},
|
||||
{"strain", 3},
|
||||
};
|
||||
inline constexpr int flexeq_sz = 4;
|
||||
// clang-format on
|
||||
|
||||
#endif // MUJOCO_SRC_XML_MJCF_MAP_H_
|
||||
File diff suppressed because it is too large
Load Diff
+36
-69
@@ -24,75 +24,8 @@
|
||||
#include "tinyxml2.h"
|
||||
|
||||
|
||||
// keyword maps (defined in implementation files)
|
||||
extern const int joint_sz;
|
||||
extern const int bodysleep_sz;
|
||||
extern const int projection_sz;
|
||||
extern const int camlight_sz;
|
||||
extern const int lighttype_sz;
|
||||
extern const int integrator_sz;
|
||||
extern const int collision_sz;
|
||||
extern const int cone_sz;
|
||||
extern const int jac_sz;
|
||||
extern const int solver_sz;
|
||||
extern const int equality_sz;
|
||||
extern const int texture_sz;
|
||||
extern const int colorspace_sz;
|
||||
extern const int builtin_sz;
|
||||
extern const int mark_sz;
|
||||
extern const int dyn_sz;
|
||||
extern const int inputchart_sz;
|
||||
extern const int inputbit_sz;
|
||||
extern const int gain_sz;
|
||||
extern const int bias_sz;
|
||||
extern const int interp_sz;
|
||||
extern const int stage_sz;
|
||||
extern const int datatype_sz;
|
||||
extern const int camout_sz;
|
||||
extern const int reduce_sz;
|
||||
extern const int conflict_sz;
|
||||
extern const mjMap angle_map[];
|
||||
extern const mjMap enable_map[];
|
||||
extern const mjMap bool_map[];
|
||||
extern const mjMap fluid_map[];
|
||||
extern const mjMap TFAuto_map[];
|
||||
extern const mjMap FAuto_map[];
|
||||
extern const mjMap joint_map[];
|
||||
extern const mjMap bodysleep_map[];
|
||||
extern const mjMap geom_map[];
|
||||
extern const mjMap projection_map[];
|
||||
extern const mjMap camlight_map[];
|
||||
extern const mjMap lighttype_map[];
|
||||
extern const mjMap integrator_map[];
|
||||
extern const mjMap collision_map[];
|
||||
extern const mjMap impedance_map[];
|
||||
extern const mjMap reference_map[];
|
||||
extern const mjMap cone_map[];
|
||||
extern const mjMap jac_map[];
|
||||
extern const mjMap solver_map[];
|
||||
extern const mjMap equality_map[];
|
||||
extern const mjMap texture_map[];
|
||||
extern const mjMap colorspace_map[];
|
||||
extern const mjMap texrole_map[];
|
||||
extern const mjMap builtin_map[];
|
||||
extern const mjMap mark_map[];
|
||||
extern const mjMap dyn_map[];
|
||||
extern const mjMap inputchart_map[];
|
||||
extern const mjMap inputbit_map[];
|
||||
extern const mjMap gain_map[];
|
||||
extern const mjMap bias_map[];
|
||||
extern const mjMap interp_map[];
|
||||
extern const mjMap stage_map[];
|
||||
extern const mjMap datatype_map[];
|
||||
extern const mjMap condata_map[];
|
||||
extern const mjMap raydata_map[];
|
||||
extern const mjMap camout_map[];
|
||||
extern const mjMap reduce_map[];
|
||||
extern const mjMap meshtype_map[];
|
||||
extern const mjMap meshinertia_map[];
|
||||
extern const mjMap flexself_map[];
|
||||
extern const mjMap elastic2d_map[];
|
||||
extern const mjMap conflict_map[];
|
||||
// keyword maps, one per schema enum, generated into mjcf_map.h
|
||||
#include "xml/mjcf_map.h" // IWYU pragma: export
|
||||
|
||||
//---------------------------------- Base XML class ------------------------------------------------
|
||||
|
||||
@@ -119,4 +52,38 @@ class mjXBase : public mjXUtil {
|
||||
mjSpec* spec; // internally-allocated model
|
||||
};
|
||||
|
||||
// typed attribute row for table-driven reading/writing; per-element row arrays are
|
||||
// generated from mjcf.schema into mjcf_read_table.inc
|
||||
struct mjXAttr {
|
||||
enum Kind {
|
||||
kName, // element name, set via mjs_setName
|
||||
kString, // mjString* field, set via mjs_setString
|
||||
kStringVec, // mjStringVec* field: space-separated names
|
||||
kInt, // int field
|
||||
kDouble, // double field, scalar or vector
|
||||
kNum, // mjtNum field, scalar or vector
|
||||
kFloat, // float field, scalar or vector
|
||||
kEnum, // int-sized enum field, keyword mapped through `map`
|
||||
kFlags, // int field ORing several keywords through `map`
|
||||
kEnumByte, // mjtByte enum field, keyword mapped through `map`
|
||||
kBool, // mjtBool field, keywords true/false
|
||||
kConst, // int-sized field set to `value`: what the tag implies
|
||||
kDoubleVec, // mjDoubleVec* field, set via mjs_setDouble
|
||||
kFloatVec, // mjFloatVec* field, set via mjs_setFloat
|
||||
kIntVec, // mjIntVec* field, set via mjs_setInt
|
||||
kChars, // char[len] field: text copied in place, length-checked
|
||||
};
|
||||
const char* attr; // XML attribute name
|
||||
Kind kind;
|
||||
int len; // number of values (1 = scalar); numeric kinds only
|
||||
bool exact; // exactly len values, else up to len
|
||||
bool required;
|
||||
bool nodefault; // not read in default classes
|
||||
bool handwrite; // custom save policy (writing=custom): OneX() remnant
|
||||
int offset; // byte offset of the bound field; -1 for kName
|
||||
const mjMap* map; // keyword map; kEnum only
|
||||
int mapsz; // keyword map size; kEnum only
|
||||
int value; // constant the field takes; kConst only
|
||||
};
|
||||
|
||||
#endif // MUJOCO_SRC_XML_XML_BASE_H_
|
||||
|
||||
+340
-1584
File diff suppressed because it is too large
Load Diff
@@ -66,6 +66,17 @@ class mjXReader : public mjXBase {
|
||||
void Sensor(tinyxml2::XMLElement* section); // sensor section
|
||||
void Keyframe(tinyxml2::XMLElement* section); // keyframe section
|
||||
|
||||
// table-driven attribute reading: the mechanical attributes of an element,
|
||||
// driven by its generated mjXAttr rows (see mjcf_read_table.inc). The
|
||||
// static core handles everything except element names; the member wrapper
|
||||
// adds kName handling and defaults-context awareness.
|
||||
void ReadAttrTable(tinyxml2::XMLElement* elem, void* obj, mjsElement* el,
|
||||
const struct mjXAttr* rows, int nrow);
|
||||
static void ReadAttrTableCore(tinyxml2::XMLElement* elem, void* obj,
|
||||
const struct mjXAttr* rows, int nrow,
|
||||
bool skipnodefault,
|
||||
const void* authored = nullptr);
|
||||
|
||||
// single element parsers, used in defaults and main body
|
||||
void OneFlex(tinyxml2::XMLElement* elem, mjsFlex* pflex);
|
||||
void OneMesh(tinyxml2::XMLElement* elem, mjsMesh* pmesh, const mjVFS* vfs);
|
||||
|
||||
@@ -392,7 +392,7 @@ void mjXWriter::OneJoint(XMLElement* elem, const mjCJoint* joint, mjCDef* def,
|
||||
|
||||
// defaults and regular
|
||||
if (joint->type != def->Joint().type) {
|
||||
WriteAttrTxt(elem, "type", FindValue(joint_map, joint_sz, joint->type));
|
||||
WriteAttrTxt(elem, "type", FindValue(jointtype_map, jointtype_sz, joint->type));
|
||||
}
|
||||
WriteAttrInt(elem, "group", joint->group, def->Joint().group);
|
||||
WriteAttr(elem, "ref", 1, &joint->ref, &zero);
|
||||
@@ -410,11 +410,11 @@ void mjXWriter::OneJoint(XMLElement* elem, const mjCJoint* joint, mjCDef* def,
|
||||
WriteAttr(elem, "stiffness", nstiff, joint->stiffness, def->Joint().stiffness);
|
||||
}
|
||||
if (joint->type != mjJNT_FREE) {
|
||||
WriteAttrKey(elem, "limited", TFAuto_map, 3, joint->limited, def->Joint().limited);
|
||||
WriteAttrKey(elem, "limited", FalseTrueAuto_map, 3, joint->limited, def->Joint().limited);
|
||||
}
|
||||
WriteAttr(elem, "range", 2, joint->range, def->Joint().range);
|
||||
if (joint->type != mjJNT_FREE && joint->type != mjJNT_BALL) {
|
||||
WriteAttrKey(elem, "actuatorfrclimited", TFAuto_map, 3, joint->actfrclimited,
|
||||
WriteAttrKey(elem, "actuatorfrclimited", FalseTrueAuto_map, 3, joint->actfrclimited,
|
||||
def->Joint().actfrclimited);
|
||||
}
|
||||
WriteAttrKey(elem, "actuatorgravcomp", bool_map, 2, joint->actgravcomp, def->Joint().actgravcomp);
|
||||
@@ -485,7 +485,7 @@ void mjXWriter::OneGeom(XMLElement* elem, const mjCGeom* geom, mjCDef* def, stri
|
||||
}
|
||||
|
||||
// defaults and regular
|
||||
WriteAttrKey(elem, "type", geom_map, mjNGEOMTYPES, geom->type, def->Geom().type);
|
||||
WriteAttrKey(elem, "type", geomtype_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);
|
||||
@@ -500,10 +500,10 @@ void mjXWriter::OneGeom(XMLElement* elem, const mjCGeom* geom, mjCDef* def, stri
|
||||
WriteAttr(elem, "surfacevel", 6, geom->surfacevel, def->Geom().surfacevel, true);
|
||||
WriteAttr(elem, "adhesion", 1, &geom->adhesion, &def->Geom().adhesion);
|
||||
WriteAttrKey(elem, "fluidshape",
|
||||
fluid_map, 2, geom->fluid_ellipsoid, def->Geom().fluid_ellipsoid);
|
||||
fluidshape_map, 2, geom->fluid_ellipsoid, def->Geom().fluid_ellipsoid);
|
||||
WriteAttr(elem, "fluidcoef", 5, geom->fluid_coefs, def->Geom().fluid_coefs);
|
||||
if (geom->type != mjGEOM_MESH) {
|
||||
WriteAttrKey(elem, "shellinertia", meshtype_map, 2, geom->typeinertia,
|
||||
WriteAttrKey(elem, "shellinertia", bool_map, 2, geom->typeinertia,
|
||||
def->Geom().typeinertia);
|
||||
}
|
||||
if (mjuu_defined(geom->mass)) {
|
||||
@@ -558,7 +558,7 @@ void mjXWriter::OneSite(XMLElement* elem, const mjCSite* site, mjCDef* def, stri
|
||||
|
||||
// defaults and regular
|
||||
WriteAttrInt(elem, "group", site->group, def->Site().group);
|
||||
WriteAttrKey(elem, "type", geom_map, mjNGEOMTYPES, site->type, def->Site().type);
|
||||
WriteAttrKey(elem, "type", geomtype_map, mjNGEOMTYPES, site->type, def->Site().type);
|
||||
if (site->get_material() != def->Site().get_material()) {
|
||||
WriteAttrTxt(elem, "material", site->get_material());
|
||||
}
|
||||
@@ -772,8 +772,8 @@ void mjXWriter::OneTendon(XMLElement* elem, const mjCTendon* tendon, mjCDef* def
|
||||
true);
|
||||
WriteAttr(elem, "solimpfriction", mjNIMP, tendon->solimp_friction, def->Tendon().solimp_friction,
|
||||
true);
|
||||
WriteAttrKey(elem, "limited", TFAuto_map, 3, tendon->limited, def->Tendon().limited);
|
||||
WriteAttrKey(elem, "actuatorfrclimited", TFAuto_map, 3, tendon->actfrclimited, def->Tendon().actfrclimited);
|
||||
WriteAttrKey(elem, "limited", FalseTrueAuto_map, 3, tendon->limited, def->Tendon().limited);
|
||||
WriteAttrKey(elem, "actuatorfrclimited", FalseTrueAuto_map, 3, tendon->actfrclimited, def->Tendon().actfrclimited);
|
||||
WriteAttr(elem, "range", 2, tendon->range, def->Tendon().range);
|
||||
WriteAttr(elem, "actuatorfrcrange", 2, tendon->actfrcrange, def->Tendon().actfrcrange);
|
||||
WriteAttr(elem, "margin", 1, &tendon->margin, &def->Tendon().margin);
|
||||
@@ -863,11 +863,11 @@ void mjXWriter::OneActuator(XMLElement* elem, const mjCActuator* actuator, mjCDe
|
||||
WriteAttrInt(elem, "nsample", actuator->nsample, def->Actuator().nsample);
|
||||
WriteAttrKey(elem, "interp", interp_map, interp_sz, actuator->interp, def->Actuator().interp);
|
||||
WriteAttr(elem, "delay", 1, &actuator->delay, &def->Actuator().delay);
|
||||
WriteAttrKey(elem, "ctrllimited", TFAuto_map, 3, actuator->ctrllimited, def->Actuator().ctrllimited);
|
||||
WriteAttrKey(elem, "ctrllimited", FalseTrueAuto_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);
|
||||
WriteAttrKey(elem, "forcelimited", FalseTrueAuto_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);
|
||||
WriteAttrKey(elem, "actlimited", FalseTrueAuto_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);
|
||||
@@ -1086,7 +1086,7 @@ void mjXWriter::Option(XMLElement* root) {
|
||||
model->option.integrator, opt.integrator);
|
||||
WriteAttrKey(section, "cone", cone_map, cone_sz,
|
||||
model->option.cone, opt.cone);
|
||||
WriteAttrKey(section, "jacobian", jac_map, jac_sz,
|
||||
WriteAttrKey(section, "jacobian", jacobian_map, jacobian_sz,
|
||||
model->option.jacobian, opt.jacobian);
|
||||
WriteAttrKey(section, "solver", solver_map, solver_sz,
|
||||
model->option.solver, opt.solver);
|
||||
@@ -1743,7 +1743,7 @@ void mjXWriter::Body(XMLElement* elem, mjCBody* body, mjCFrame* frame, string_vi
|
||||
}
|
||||
|
||||
// simple optimization
|
||||
WriteAttrKey(elem, "simple", FAuto_map, 2, body->simple, 1);
|
||||
WriteAttrKey(elem, "simple", FalseAuto_map, 2, body->simple, 1);
|
||||
|
||||
// userdata
|
||||
WriteVector(elem, "user", body->get_userdata());
|
||||
|
||||
@@ -23,8 +23,11 @@ _SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
_REPO_ROOT = os.path.dirname(os.path.dirname(_SCRIPT_DIR))
|
||||
sys.path.insert(0, os.path.join(_REPO_ROOT, 'doc', 'generate'))
|
||||
import generate_api_header
|
||||
import generate_default_table
|
||||
import generate_functions
|
||||
import generate_mjcf_map
|
||||
import generate_mjcf_table
|
||||
import generate_read_table
|
||||
import generate_schema
|
||||
import mjcf_schema
|
||||
|
||||
@@ -91,6 +94,86 @@ class DocTest(googletest.TestCase):
|
||||
if source != file.read():
|
||||
self.fail("The file 'mjcf_table.inc' needs to be updated.")
|
||||
|
||||
def test_default_table(self):
|
||||
"""Checks that mjcf_default_table.inc matches the schema-generated output."""
|
||||
table_file = os.path.join(_REPO_ROOT, 'src', 'xml',
|
||||
'mjcf_default_table.inc')
|
||||
source = generate_default_table.generate()
|
||||
with open(table_file, 'r', encoding='utf-8') as file:
|
||||
if source != file.read():
|
||||
self.fail("The file 'mjcf_default_table.inc' needs to be updated.")
|
||||
|
||||
def test_mjcf_map(self):
|
||||
"""Checks that mjcf_map.h matches the schema-generated output."""
|
||||
map_file = os.path.join(_REPO_ROOT, 'src', 'xml', 'mjcf_map.h')
|
||||
source = generate_mjcf_map.generate()
|
||||
with open(map_file, 'r', encoding='utf-8') as file:
|
||||
if source != file.read():
|
||||
self.fail("The file 'mjcf_map.h' needs to be updated.")
|
||||
|
||||
def test_read_table(self):
|
||||
"""Checks that mjcf_read_table.inc matches the schema-generated output."""
|
||||
table_file = os.path.join(_REPO_ROOT, 'src', 'xml', 'mjcf_read_table.inc')
|
||||
source = generate_read_table.generate()
|
||||
with open(table_file, 'r', encoding='utf-8') as file:
|
||||
if source != file.read():
|
||||
self.fail("The file 'mjcf_read_table.inc' needs to be updated.")
|
||||
|
||||
def test_schema_enum_coverage(self):
|
||||
"""Checks schema enums against the C enums they bind.
|
||||
|
||||
Every schema constant must be a member of the bound C enum, and every C
|
||||
member must be a schema keyword, a count sentinel (mjN*), or a documented
|
||||
exemption -- so adding a C enum member without updating the schema fails
|
||||
here.
|
||||
"""
|
||||
# C members deliberately not exposed as XML keywords
|
||||
exempt = {
|
||||
'bodysleep': { # resolved states of 'auto', not settable
|
||||
'mjSLEEP_AUTO_ALLOWED', 'mjSLEEP_AUTO_NEVER'},
|
||||
'geomtype': { # rendering-only types and the missing-geom sentinel
|
||||
'mjGEOM_ARROW', 'mjGEOM_ARROW1', 'mjGEOM_ARROW2', 'mjGEOM_LINE',
|
||||
'mjGEOM_LINEBOX', 'mjGEOM_FLEX', 'mjGEOM_SKIN', 'mjGEOM_LABEL',
|
||||
'mjGEOM_TRIANGLE', 'mjGEOM_NONE'},
|
||||
'texrole': { # not settable from XML
|
||||
'mjTEXROLE_USER'},
|
||||
}
|
||||
# deliberately partial: keywords are a documented subset of the C enum
|
||||
partial = {'frameobj'}
|
||||
|
||||
enums_c = {}
|
||||
for name in ('mjtype.h', 'mjspec.h'):
|
||||
path = os.path.join(_REPO_ROOT, 'include', 'mujoco', name)
|
||||
with open(path, 'r', encoding='utf-8') as file:
|
||||
content = file.read()
|
||||
for m in re.finditer(r'typedef enum (mjt\w+)\s*\{(.*?)\}\s*\1;',
|
||||
content, re.S):
|
||||
enums_c[m.group(1)] = re.findall(r'^\s*(mj[A-Z]\w+)', m.group(2),
|
||||
re.M)
|
||||
|
||||
schema_path = os.path.join(_REPO_ROOT, 'src', 'xml', 'mjcf.schema')
|
||||
schema = mjcf_schema.parse_file(schema_path)
|
||||
errors = []
|
||||
for name, enum in schema.enums.items():
|
||||
if not enum.ctype:
|
||||
continue
|
||||
if enum.ctype not in enums_c:
|
||||
errors.append(f' {name}: C enum {enum.ctype} not found in headers')
|
||||
continue
|
||||
members = set(enums_c[enum.ctype])
|
||||
constants = {value for _, value in enum.items}
|
||||
for bad in sorted(constants - members):
|
||||
errors.append(f' {name}: {bad} is not a member of {enum.ctype}')
|
||||
if name in partial:
|
||||
continue
|
||||
uncovered = {m for m in members - constants
|
||||
if not re.match(r'mjN[A-Z]', m)} - exempt.get(name, set())
|
||||
for miss in sorted(uncovered):
|
||||
errors.append(f' {name}: {enum.ctype} member {miss} has no keyword '
|
||||
'(add it to the schema or to the exemptions here)')
|
||||
if errors:
|
||||
self.fail('schema enum coverage:\n' + '\n'.join(errors))
|
||||
|
||||
def test_schema(self):
|
||||
"""Checks that XMLschema.rst matches the generated output."""
|
||||
schema_file = os.path.join(_REPO_ROOT, 'doc', 'XMLschema.rst')
|
||||
|
||||
@@ -554,7 +554,7 @@ TEST_F(RelativeFrameSensorParsingTest, BadRefType) {
|
||||
)";
|
||||
std::array<char, 1024> error;
|
||||
LoadModelFromString(xml, error.data(), error.size());
|
||||
EXPECT_THAT(error.data(), HasSubstr("reference frame object must be"));
|
||||
EXPECT_THAT(error.data(), HasSubstr("invalid keyword: 'light'"));
|
||||
EXPECT_THAT(error.data(), HasSubstr("line 8"));
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@ mujoco_test(
|
||||
ADDITIONAL_LINK_LIBRARIES compare_model
|
||||
)
|
||||
|
||||
mujoco_test(schema_defaults_test)
|
||||
|
||||
mujoco_test(xml_utils_test)
|
||||
|
||||
mujoco_test(
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
// Copyright 2026 DeepMind Technologies Limited
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Checks that the attribute defaults declared in mjcf.schema agree with the
|
||||
// C default-constructors: every generated row is compared against a
|
||||
// freshly-constructed spec element.
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <mujoco/mujoco.h>
|
||||
#include <mujoco/mjspec.h>
|
||||
#include "test/fixture.h"
|
||||
|
||||
#include "src/xml/mjcf_default_table.inc"
|
||||
|
||||
namespace mujoco {
|
||||
namespace {
|
||||
|
||||
using SchemaDefaultsTest = MujocoTest;
|
||||
|
||||
TEST_F(SchemaDefaultsTest, DeclaredDefaultsMatchConstructors) {
|
||||
mjSpec* spec = mj_makeSpec();
|
||||
mjsBody* world = mjs_findBody(spec, "world");
|
||||
ASSERT_NE(world, nullptr);
|
||||
mjsBody* body = mjs_addBody(world, nullptr);
|
||||
|
||||
std::map<std::string, const void*> objects = {
|
||||
{"mjOption", &spec->option},
|
||||
{"mjVisual", &spec->visual},
|
||||
{"mjsBody", body},
|
||||
{"mjsFrame", mjs_addFrame(body, nullptr)},
|
||||
{"mjsJoint", mjs_addJoint(body, nullptr)},
|
||||
{"mjsGeom", mjs_addGeom(body, nullptr)},
|
||||
{"mjsSite", mjs_addSite(body, nullptr)},
|
||||
{"mjsCamera", mjs_addCamera(body, nullptr)},
|
||||
{"mjsLight", mjs_addLight(body, nullptr)},
|
||||
{"mjsPair", mjs_addPair(spec, nullptr)},
|
||||
{"mjsEquality", mjs_addEquality(spec, nullptr)},
|
||||
{"mjsTendon", mjs_addTendon(spec, nullptr)},
|
||||
{"mjsActuator", mjs_addActuator(spec, nullptr)},
|
||||
{"mjsSensor", mjs_addSensor(spec)},
|
||||
{"mjsMesh", mjs_addMesh(spec, nullptr)},
|
||||
{"mjsSkin", mjs_addSkin(spec)},
|
||||
{"mjsMaterial", mjs_addMaterial(spec, nullptr)},
|
||||
{"mjsTexture", mjs_addTexture(spec)},
|
||||
};
|
||||
|
||||
for (int t = 0; t < kDefaultTablesN; t++) {
|
||||
const mjXDefaultTable& table = kDefaultTables[t];
|
||||
auto it = objects.find(table.structname);
|
||||
ASSERT_NE(it, objects.end()) << "no factory for " << table.structname;
|
||||
const char* base = static_cast<const char*>(it->second);
|
||||
for (int i = 0; i < table.n; i++) {
|
||||
const mjXDefaultEntry& entry = table.entries[i];
|
||||
const char* field = base + entry.offset;
|
||||
for (int j = 0; j < entry.len; j++) {
|
||||
double expected = j < entry.ndecl ? entry.value[j] : 0;
|
||||
double actual = 0;
|
||||
switch (entry.kind) {
|
||||
case 0: // double
|
||||
actual = reinterpret_cast<const double*>(field)[j];
|
||||
break;
|
||||
case 1: // float: compare at float precision
|
||||
actual = reinterpret_cast<const float*>(field)[j];
|
||||
expected = static_cast<float>(expected);
|
||||
break;
|
||||
case 2: // int-sized, including enums
|
||||
actual = reinterpret_cast<const int*>(field)[j];
|
||||
break;
|
||||
case 3: // byte
|
||||
actual = reinterpret_cast<const unsigned char*>(field)[j];
|
||||
break;
|
||||
case 4: // mjtNum: compare at mjtNum precision
|
||||
actual = reinterpret_cast<const mjtNum*>(field)[j];
|
||||
expected = static_cast<mjtNum>(expected);
|
||||
break;
|
||||
}
|
||||
EXPECT_EQ(actual, expected)
|
||||
<< table.structname << "." << entry.attr << "[" << j << "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
mj_deleteSpec(spec);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace mujoco
|
||||
Reference in New Issue
Block a user