diff --git a/doc/generate/generate_default_table.py b/doc/generate/generate_default_table.py new file mode 100644 index 00000000..aa5a919e --- /dev/null +++ b/doc/generate/generate_default_table.py @@ -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()) diff --git a/doc/generate/generate_mjcf_map.py b/doc/generate/generate_mjcf_map.py new file mode 100644 index 00000000..5740507a --- /dev/null +++ b/doc/generate/generate_mjcf_map.py @@ -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 +#include +#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()) diff --git a/doc/generate/generate_read_table.py b/doc/generate/generate_read_table.py new file mode 100644 index 00000000..c21fc5c1 --- /dev/null +++ b/doc/generate/generate_read_table.py @@ -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()) diff --git a/src/xml/mjcf_default_table.inc b/src/xml/mjcf_default_table.inc new file mode 100644 index 00000000..e3b97b6e --- /dev/null +++ b/src/xml/mjcf_default_table.inc @@ -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 diff --git a/src/xml/mjcf_map.h b/src/xml/mjcf_map.h new file mode 100644 index 00000000..bff5bb6a --- /dev/null +++ b/src/xml/mjcf_map.h @@ -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 +#include +#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_ diff --git a/src/xml/mjcf_read_table.inc b/src/xml/mjcf_read_table.inc new file mode 100644 index 00000000..5f8cb2bf --- /dev/null +++ b/src/xml/mjcf_read_table.inc @@ -0,0 +1,1017 @@ +// 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 + +// compiler (mjsCompiler) +inline constexpr mjXAttr kCompilerAttrs[] = { + {"autolimits", mjXAttr::kBool, 1, true, false, false, false, (int)offsetof(mjsCompiler, autolimits)}, + {"boundmass", mjXAttr::kDouble, 1, true, false, false, false, (int)offsetof(mjsCompiler, boundmass)}, + {"boundinertia", mjXAttr::kDouble, 1, true, false, false, false, (int)offsetof(mjsCompiler, boundinertia)}, + {"settotalmass", mjXAttr::kDouble, 1, true, false, false, false, (int)offsetof(mjsCompiler, settotalmass)}, + {"balanceinertia", mjXAttr::kBool, 1, true, false, false, false, (int)offsetof(mjsCompiler, balanceinertia)}, + {"angle", mjXAttr::kEnumByte, 1, true, false, false, false, (int)offsetof(mjsCompiler, degree), angle_map, angle_sz}, + {"fitaabb", mjXAttr::kBool, 1, true, false, false, false, (int)offsetof(mjsCompiler, fitaabb)}, + {"eulerseq", mjXAttr::kChars, 3, true, false, false, false, (int)offsetof(mjsCompiler, eulerseq)}, + {"meshdir", mjXAttr::kString, 1, true, false, false, false, (int)offsetof(mjsCompiler, meshdir)}, + {"texturedir", mjXAttr::kString, 1, true, false, false, false, (int)offsetof(mjsCompiler, texturedir)}, + {"discardvisual", mjXAttr::kBool, 1, true, false, false, false, (int)offsetof(mjsCompiler, discardvisual)}, + {"usethread", mjXAttr::kBool, 1, true, false, false, false, (int)offsetof(mjsCompiler, usethread)}, + {"fusestatic", mjXAttr::kBool, 1, true, false, false, false, (int)offsetof(mjsCompiler, fusestatic)}, + {"inertiafromgeom", mjXAttr::kEnum, 1, true, false, false, false, (int)offsetof(mjsCompiler, inertiafromgeom), FalseTrueAuto_map, FalseTrueAuto_sz}, + {"inertiagrouprange", mjXAttr::kInt, 2, true, false, false, false, (int)offsetof(mjsCompiler, inertiagrouprange)}, + {"saveinertial", mjXAttr::kBool, 1, true, false, false, false, (int)offsetof(mjsCompiler, saveinertial)}, + {"alignfree", mjXAttr::kBool, 1, true, false, false, false, (int)offsetof(mjsCompiler, alignfree)}, + {"conflict", mjXAttr::kEnum, 1, true, false, false, false, (int)offsetof(mjsCompiler, conflict), conflict_map, conflict_sz}, +}; +inline constexpr int kCompilerAttrsN = sizeof(kCompilerAttrs) / sizeof(kCompilerAttrs[0]); + +// lengthrange (mjLROpt) +inline constexpr mjXAttr kLengthrangeAttrs[] = { + {"mode", mjXAttr::kEnum, 1, true, false, false, false, (int)offsetof(mjLROpt, mode), lrmode_map, lrmode_sz}, + {"useexisting", mjXAttr::kEnum, 1, true, false, false, false, (int)offsetof(mjLROpt, useexisting), bool_map, 2}, + {"uselimit", mjXAttr::kEnum, 1, true, false, false, false, (int)offsetof(mjLROpt, uselimit), bool_map, 2}, + {"accel", mjXAttr::kNum, 1, true, false, false, false, (int)offsetof(mjLROpt, accel)}, + {"maxforce", mjXAttr::kNum, 1, true, false, false, false, (int)offsetof(mjLROpt, maxforce)}, + {"timeconst", mjXAttr::kNum, 1, true, false, false, false, (int)offsetof(mjLROpt, timeconst)}, + {"timestep", mjXAttr::kNum, 1, true, false, false, false, (int)offsetof(mjLROpt, timestep)}, + {"inttotal", mjXAttr::kNum, 1, true, false, false, false, (int)offsetof(mjLROpt, inttotal)}, + {"interval", mjXAttr::kNum, 1, true, false, false, false, (int)offsetof(mjLROpt, interval)}, + {"tolrange", mjXAttr::kNum, 1, true, false, false, false, (int)offsetof(mjLROpt, tolrange)}, +}; +inline constexpr int kLengthrangeAttrsN = sizeof(kLengthrangeAttrs) / sizeof(kLengthrangeAttrs[0]); + +// option (mjOption) +inline constexpr mjXAttr kOptionAttrs[] = { + {"timestep", mjXAttr::kNum, 1, true, false, false, false, (int)offsetof(mjOption, timestep)}, + {"impratio", mjXAttr::kNum, 1, true, false, false, false, (int)offsetof(mjOption, impratio)}, + {"tolerance", mjXAttr::kNum, 1, true, false, false, false, (int)offsetof(mjOption, tolerance)}, + {"ls_tolerance", mjXAttr::kNum, 1, true, false, false, false, (int)offsetof(mjOption, ls_tolerance)}, + {"noslip_tolerance", mjXAttr::kNum, 1, true, false, false, false, (int)offsetof(mjOption, noslip_tolerance)}, + {"ccd_tolerance", mjXAttr::kNum, 1, true, false, false, false, (int)offsetof(mjOption, ccd_tolerance)}, + {"sleep_tolerance", mjXAttr::kNum, 1, true, false, false, false, (int)offsetof(mjOption, sleep_tolerance)}, + {"gravity", mjXAttr::kNum, 3, true, false, false, false, (int)offsetof(mjOption, gravity)}, + {"wind", mjXAttr::kNum, 3, true, false, false, false, (int)offsetof(mjOption, wind)}, + {"magnetic", mjXAttr::kNum, 3, true, false, false, false, (int)offsetof(mjOption, magnetic)}, + {"density", mjXAttr::kNum, 1, true, false, false, false, (int)offsetof(mjOption, density)}, + {"viscosity", mjXAttr::kNum, 1, true, false, false, false, (int)offsetof(mjOption, viscosity)}, + {"o_margin", mjXAttr::kNum, 1, true, false, false, false, (int)offsetof(mjOption, o_margin)}, + {"o_solref", mjXAttr::kNum, mjNREF, false, false, false, false, (int)offsetof(mjOption, o_solref)}, + {"o_solimp", mjXAttr::kNum, mjNIMP, false, false, false, false, (int)offsetof(mjOption, o_solimp)}, + {"o_friction", mjXAttr::kNum, 5, false, false, false, false, (int)offsetof(mjOption, o_friction)}, + {"integrator", mjXAttr::kEnum, 1, true, false, false, false, (int)offsetof(mjOption, integrator), integrator_map, integrator_sz}, + {"cone", mjXAttr::kEnum, 1, true, false, false, false, (int)offsetof(mjOption, cone), cone_map, cone_sz}, + {"jacobian", mjXAttr::kEnum, 1, true, false, false, false, (int)offsetof(mjOption, jacobian), jacobian_map, jacobian_sz}, + {"solver", mjXAttr::kEnum, 1, true, false, false, false, (int)offsetof(mjOption, solver), solver_map, solver_sz}, + {"iterations", mjXAttr::kInt, 1, true, false, false, false, (int)offsetof(mjOption, iterations)}, + {"ls_iterations", mjXAttr::kInt, 1, true, false, false, false, (int)offsetof(mjOption, ls_iterations)}, + {"noslip_iterations", mjXAttr::kInt, 1, true, false, false, false, (int)offsetof(mjOption, noslip_iterations)}, + {"ccd_iterations", mjXAttr::kInt, 1, true, false, false, false, (int)offsetof(mjOption, ccd_iterations)}, + {"sdf_iterations", mjXAttr::kInt, 1, true, false, false, false, (int)offsetof(mjOption, sdf_iterations)}, + {"sdf_initpoints", mjXAttr::kInt, 1, true, false, false, false, (int)offsetof(mjOption, sdf_initpoints)}, +}; +inline constexpr int kOptionAttrsN = sizeof(kOptionAttrs) / sizeof(kOptionAttrs[0]); + +// size (mjSpec) +inline constexpr mjXAttr kSizeAttrs[] = { + {"nuserdata", mjXAttr::kInt, 1, true, false, false, false, (int)offsetof(mjSpec, nuserdata)}, + {"nkey", mjXAttr::kInt, 1, true, false, false, false, (int)offsetof(mjSpec, nkey)}, + {"nuser_body", mjXAttr::kInt, 1, true, false, false, false, (int)offsetof(mjSpec, nuser_body)}, + {"nuser_jnt", mjXAttr::kInt, 1, true, false, false, false, (int)offsetof(mjSpec, nuser_jnt)}, + {"nuser_geom", mjXAttr::kInt, 1, true, false, false, false, (int)offsetof(mjSpec, nuser_geom)}, + {"nuser_site", mjXAttr::kInt, 1, true, false, false, false, (int)offsetof(mjSpec, nuser_site)}, + {"nuser_cam", mjXAttr::kInt, 1, true, false, false, false, (int)offsetof(mjSpec, nuser_cam)}, + {"nuser_tendon", mjXAttr::kInt, 1, true, false, false, false, (int)offsetof(mjSpec, nuser_tendon)}, + {"nuser_actuator", mjXAttr::kInt, 1, true, false, false, false, (int)offsetof(mjSpec, nuser_actuator)}, + {"nuser_sensor", mjXAttr::kInt, 1, true, false, false, false, (int)offsetof(mjSpec, nuser_sensor)}, +}; +inline constexpr int kSizeAttrsN = sizeof(kSizeAttrs) / sizeof(kSizeAttrs[0]); + +// statistic (mjStatistic) +inline constexpr mjXAttr kStatisticAttrs[] = { + {"meaninertia", mjXAttr::kNum, 1, true, false, false, false, (int)offsetof(mjStatistic, meaninertia)}, + {"meanmass", mjXAttr::kNum, 1, true, false, false, false, (int)offsetof(mjStatistic, meanmass)}, + {"meansize", mjXAttr::kNum, 1, true, false, false, false, (int)offsetof(mjStatistic, meansize)}, + {"extent", mjXAttr::kNum, 1, true, false, false, false, (int)offsetof(mjStatistic, extent)}, + {"center", mjXAttr::kNum, 3, true, false, false, false, (int)offsetof(mjStatistic, center)}, +}; +inline constexpr int kStatisticAttrsN = sizeof(kStatisticAttrs) / sizeof(kStatisticAttrs[0]); + +// global (mjVisual) +inline constexpr mjXAttr kGlobalAttrs[] = { + {"cameraid", mjXAttr::kInt, 1, true, false, false, false, (int)offsetof(mjVisual, global.cameraid)}, + {"orthographic", mjXAttr::kEnum, 1, true, false, false, false, (int)offsetof(mjVisual, global.orthographic), bool_map, 2}, + {"fovy", mjXAttr::kFloat, 1, true, false, false, false, (int)offsetof(mjVisual, global.fovy)}, + {"ipd", mjXAttr::kFloat, 1, true, false, false, false, (int)offsetof(mjVisual, global.ipd)}, + {"azimuth", mjXAttr::kFloat, 1, true, false, false, false, (int)offsetof(mjVisual, global.azimuth)}, + {"elevation", mjXAttr::kFloat, 1, true, false, false, false, (int)offsetof(mjVisual, global.elevation)}, + {"linewidth", mjXAttr::kFloat, 1, true, false, false, false, (int)offsetof(mjVisual, global.linewidth)}, + {"glow", mjXAttr::kFloat, 1, true, false, false, false, (int)offsetof(mjVisual, global.glow)}, + {"offwidth", mjXAttr::kInt, 1, true, false, false, false, (int)offsetof(mjVisual, global.offwidth)}, + {"offheight", mjXAttr::kInt, 1, true, false, false, false, (int)offsetof(mjVisual, global.offheight)}, + {"realtime", mjXAttr::kFloat, 1, true, false, false, false, (int)offsetof(mjVisual, global.realtime)}, + {"ellipsoidinertia", mjXAttr::kEnum, 1, true, false, false, false, (int)offsetof(mjVisual, global.ellipsoidinertia), bool_map, 2}, + {"bvactive", mjXAttr::kEnum, 1, true, false, false, false, (int)offsetof(mjVisual, global.bvactive), bool_map, 2}, +}; +inline constexpr int kGlobalAttrsN = sizeof(kGlobalAttrs) / sizeof(kGlobalAttrs[0]); + +// quality (mjVisual) +inline constexpr mjXAttr kQualityAttrs[] = { + {"shadowsize", mjXAttr::kInt, 1, true, false, false, false, (int)offsetof(mjVisual, quality.shadowsize)}, + {"offsamples", mjXAttr::kInt, 1, true, false, false, false, (int)offsetof(mjVisual, quality.offsamples)}, + {"numslices", mjXAttr::kInt, 1, true, false, false, false, (int)offsetof(mjVisual, quality.numslices)}, + {"numstacks", mjXAttr::kInt, 1, true, false, false, false, (int)offsetof(mjVisual, quality.numstacks)}, + {"numquads", mjXAttr::kInt, 1, true, false, false, false, (int)offsetof(mjVisual, quality.numquads)}, +}; +inline constexpr int kQualityAttrsN = sizeof(kQualityAttrs) / sizeof(kQualityAttrs[0]); + +// headlight (mjVisual) +inline constexpr mjXAttr kHeadlightAttrs[] = { + {"ambient", mjXAttr::kFloat, 3, true, false, false, false, (int)offsetof(mjVisual, headlight.ambient)}, + {"diffuse", mjXAttr::kFloat, 3, true, false, false, false, (int)offsetof(mjVisual, headlight.diffuse)}, + {"specular", mjXAttr::kFloat, 3, true, false, false, false, (int)offsetof(mjVisual, headlight.specular)}, + {"active", mjXAttr::kInt, 1, true, false, false, false, (int)offsetof(mjVisual, headlight.active)}, +}; +inline constexpr int kHeadlightAttrsN = sizeof(kHeadlightAttrs) / sizeof(kHeadlightAttrs[0]); + +// map (mjVisual) +inline constexpr mjXAttr kMapAttrs[] = { + {"stiffness", mjXAttr::kFloat, 1, true, false, false, false, (int)offsetof(mjVisual, map.stiffness)}, + {"stiffnessrot", mjXAttr::kFloat, 1, true, false, false, false, (int)offsetof(mjVisual, map.stiffnessrot)}, + {"force", mjXAttr::kFloat, 1, true, false, false, false, (int)offsetof(mjVisual, map.force)}, + {"torque", mjXAttr::kFloat, 1, true, false, false, false, (int)offsetof(mjVisual, map.torque)}, + {"alpha", mjXAttr::kFloat, 1, true, false, false, false, (int)offsetof(mjVisual, map.alpha)}, + {"fogstart", mjXAttr::kFloat, 1, true, false, false, false, (int)offsetof(mjVisual, map.fogstart)}, + {"fogend", mjXAttr::kFloat, 1, true, false, false, false, (int)offsetof(mjVisual, map.fogend)}, + {"znear", mjXAttr::kFloat, 1, true, false, false, false, (int)offsetof(mjVisual, map.znear)}, + {"zfar", mjXAttr::kFloat, 1, true, false, false, false, (int)offsetof(mjVisual, map.zfar)}, + {"haze", mjXAttr::kFloat, 1, true, false, false, false, (int)offsetof(mjVisual, map.haze)}, + {"shadowclip", mjXAttr::kFloat, 1, true, false, false, false, (int)offsetof(mjVisual, map.shadowclip)}, + {"shadowscale", mjXAttr::kFloat, 1, true, false, false, false, (int)offsetof(mjVisual, map.shadowscale)}, + {"actuatortendon", mjXAttr::kFloat, 1, true, false, false, false, (int)offsetof(mjVisual, map.actuatortendon)}, +}; +inline constexpr int kMapAttrsN = sizeof(kMapAttrs) / sizeof(kMapAttrs[0]); + +// scale (mjVisual) +inline constexpr mjXAttr kScaleAttrs[] = { + {"forcewidth", mjXAttr::kFloat, 1, true, false, false, false, (int)offsetof(mjVisual, scale.forcewidth)}, + {"contactwidth", mjXAttr::kFloat, 1, true, false, false, false, (int)offsetof(mjVisual, scale.contactwidth)}, + {"contactheight", mjXAttr::kFloat, 1, true, false, false, false, (int)offsetof(mjVisual, scale.contactheight)}, + {"connect", mjXAttr::kFloat, 1, true, false, false, false, (int)offsetof(mjVisual, scale.connect)}, + {"com", mjXAttr::kFloat, 1, true, false, false, false, (int)offsetof(mjVisual, scale.com)}, + {"camera", mjXAttr::kFloat, 1, true, false, false, false, (int)offsetof(mjVisual, scale.camera)}, + {"light", mjXAttr::kFloat, 1, true, false, false, false, (int)offsetof(mjVisual, scale.light)}, + {"selectpoint", mjXAttr::kFloat, 1, true, false, false, false, (int)offsetof(mjVisual, scale.selectpoint)}, + {"jointlength", mjXAttr::kFloat, 1, true, false, false, false, (int)offsetof(mjVisual, scale.jointlength)}, + {"jointwidth", mjXAttr::kFloat, 1, true, false, false, false, (int)offsetof(mjVisual, scale.jointwidth)}, + {"actuatorlength", mjXAttr::kFloat, 1, true, false, false, false, (int)offsetof(mjVisual, scale.actuatorlength)}, + {"actuatorwidth", mjXAttr::kFloat, 1, true, false, false, false, (int)offsetof(mjVisual, scale.actuatorwidth)}, + {"framelength", mjXAttr::kFloat, 1, true, false, false, false, (int)offsetof(mjVisual, scale.framelength)}, + {"framewidth", mjXAttr::kFloat, 1, true, false, false, false, (int)offsetof(mjVisual, scale.framewidth)}, + {"constraint", mjXAttr::kFloat, 1, true, false, false, false, (int)offsetof(mjVisual, scale.constraint)}, + {"slidercrank", mjXAttr::kFloat, 1, true, false, false, false, (int)offsetof(mjVisual, scale.slidercrank)}, + {"frustum", mjXAttr::kFloat, 1, true, false, false, false, (int)offsetof(mjVisual, scale.frustum)}, +}; +inline constexpr int kScaleAttrsN = sizeof(kScaleAttrs) / sizeof(kScaleAttrs[0]); + +// rgba (mjVisual) +inline constexpr mjXAttr kRgbaAttrs[] = { + {"fog", mjXAttr::kFloat, 4, true, false, false, false, (int)offsetof(mjVisual, rgba.fog)}, + {"haze", mjXAttr::kFloat, 4, true, false, false, false, (int)offsetof(mjVisual, rgba.haze)}, + {"force", mjXAttr::kFloat, 4, true, false, false, false, (int)offsetof(mjVisual, rgba.force)}, + {"inertia", mjXAttr::kFloat, 4, true, false, false, false, (int)offsetof(mjVisual, rgba.inertia)}, + {"joint", mjXAttr::kFloat, 4, true, false, false, false, (int)offsetof(mjVisual, rgba.joint)}, + {"actuator", mjXAttr::kFloat, 4, true, false, false, false, (int)offsetof(mjVisual, rgba.actuator)}, + {"actuatornegative", mjXAttr::kFloat, 4, true, false, false, false, (int)offsetof(mjVisual, rgba.actuatornegative)}, + {"actuatorpositive", mjXAttr::kFloat, 4, true, false, false, false, (int)offsetof(mjVisual, rgba.actuatorpositive)}, + {"com", mjXAttr::kFloat, 4, true, false, false, false, (int)offsetof(mjVisual, rgba.com)}, + {"camera", mjXAttr::kFloat, 4, true, false, false, false, (int)offsetof(mjVisual, rgba.camera)}, + {"light", mjXAttr::kFloat, 4, true, false, false, false, (int)offsetof(mjVisual, rgba.light)}, + {"selectpoint", mjXAttr::kFloat, 4, true, false, false, false, (int)offsetof(mjVisual, rgba.selectpoint)}, + {"connect", mjXAttr::kFloat, 4, true, false, false, false, (int)offsetof(mjVisual, rgba.connect)}, + {"contactpoint", mjXAttr::kFloat, 4, true, false, false, false, (int)offsetof(mjVisual, rgba.contactpoint)}, + {"contactforce", mjXAttr::kFloat, 4, true, false, false, false, (int)offsetof(mjVisual, rgba.contactforce)}, + {"contactfriction", mjXAttr::kFloat, 4, true, false, false, false, (int)offsetof(mjVisual, rgba.contactfriction)}, + {"contacttorque", mjXAttr::kFloat, 4, true, false, false, false, (int)offsetof(mjVisual, rgba.contacttorque)}, + {"contactgap", mjXAttr::kFloat, 4, true, false, false, false, (int)offsetof(mjVisual, rgba.contactgap)}, + {"rangefinder", mjXAttr::kFloat, 4, true, false, false, false, (int)offsetof(mjVisual, rgba.rangefinder)}, + {"constraint", mjXAttr::kFloat, 4, true, false, false, false, (int)offsetof(mjVisual, rgba.constraint)}, + {"slidercrank", mjXAttr::kFloat, 4, true, false, false, false, (int)offsetof(mjVisual, rgba.slidercrank)}, + {"crankbroken", mjXAttr::kFloat, 4, true, false, false, false, (int)offsetof(mjVisual, rgba.crankbroken)}, + {"frustum", mjXAttr::kFloat, 4, true, false, false, false, (int)offsetof(mjVisual, rgba.frustum)}, + {"bv", mjXAttr::kFloat, 4, true, false, false, false, (int)offsetof(mjVisual, rgba.bv)}, + {"bvactive", mjXAttr::kFloat, 4, true, false, false, false, (int)offsetof(mjVisual, rgba.bvactive)}, +}; +inline constexpr int kRgbaAttrsN = sizeof(kRgbaAttrs) / sizeof(kRgbaAttrs[0]); + +// mesh (mjsMesh) +inline constexpr mjXAttr kMeshAttrs[] = { + {"name", mjXAttr::kName, 1, true, false, true, false, -1}, + {"content_type", mjXAttr::kString, 1, true, false, true, false, (int)offsetof(mjsMesh, content_type)}, + {"vertex", mjXAttr::kFloatVec, 1, true, false, true, false, (int)offsetof(mjsMesh, uservert)}, + {"normal", mjXAttr::kFloatVec, 1, true, false, true, false, (int)offsetof(mjsMesh, usernormal)}, + {"texcoord", mjXAttr::kFloatVec, 1, true, false, true, false, (int)offsetof(mjsMesh, usertexcoord)}, + {"face", mjXAttr::kIntVec, 1, true, false, true, false, (int)offsetof(mjsMesh, userface)}, + {"refpos", mjXAttr::kDouble, 3, true, false, true, false, (int)offsetof(mjsMesh, refpos)}, + {"refquat", mjXAttr::kDouble, 4, true, false, true, false, (int)offsetof(mjsMesh, refquat)}, + {"scale", mjXAttr::kDouble, 3, true, false, false, false, (int)offsetof(mjsMesh, scale)}, + {"smoothnormal", mjXAttr::kBool, 1, true, false, true, false, (int)offsetof(mjsMesh, smoothnormal)}, + {"inertia", mjXAttr::kEnum, 1, true, false, false, false, (int)offsetof(mjsMesh, inertia), meshinertia_map, meshinertia_sz}, + {"material", mjXAttr::kString, 1, true, false, true, false, (int)offsetof(mjsMesh, material)}, +}; +inline constexpr int kMeshAttrsN = sizeof(kMeshAttrs) / sizeof(kMeshAttrs[0]); + +// hfield (mjsHField) +inline constexpr mjXAttr kHfieldAttrs[] = { + {"name", mjXAttr::kName, 1, true, false, true, false, -1}, + {"content_type", mjXAttr::kString, 1, true, false, false, false, (int)offsetof(mjsHField, content_type)}, + {"nrow", mjXAttr::kInt, 1, true, false, false, false, (int)offsetof(mjsHField, nrow)}, + {"ncol", mjXAttr::kInt, 1, true, false, false, false, (int)offsetof(mjsHField, ncol)}, + {"size", mjXAttr::kDouble, 4, true, true, false, false, (int)offsetof(mjsHField, size)}, +}; +inline constexpr int kHfieldAttrsN = sizeof(kHfieldAttrs) / sizeof(kHfieldAttrs[0]); + +// skin (mjsSkin) +inline constexpr mjXAttr kSkinAttrs[] = { + {"name", mjXAttr::kName, 1, true, false, true, false, -1}, + {"material", mjXAttr::kString, 1, true, false, false, false, (int)offsetof(mjsSkin, material)}, + {"rgba", mjXAttr::kFloat, 4, true, false, false, false, (int)offsetof(mjsSkin, rgba)}, + {"inflate", mjXAttr::kFloat, 1, true, false, false, false, (int)offsetof(mjsSkin, inflate)}, + {"vertex", mjXAttr::kFloatVec, 1, true, false, false, false, (int)offsetof(mjsSkin, vert)}, + {"texcoord", mjXAttr::kFloatVec, 1, true, false, false, false, (int)offsetof(mjsSkin, texcoord)}, + {"face", mjXAttr::kIntVec, 1, true, false, false, false, (int)offsetof(mjsSkin, face)}, +}; +inline constexpr int kSkinAttrsN = sizeof(kSkinAttrs) / sizeof(kSkinAttrs[0]); + +// texture (mjsTexture) +inline constexpr mjXAttr kTextureAttrs[] = { + {"name", mjXAttr::kName, 1, true, false, true, false, -1}, + {"type", mjXAttr::kEnum, 1, true, false, false, false, (int)offsetof(mjsTexture, type), texture_map, texture_sz}, + {"colorspace", mjXAttr::kEnum, 1, true, false, false, false, (int)offsetof(mjsTexture, colorspace), colorspace_map, colorspace_sz}, + {"content_type", mjXAttr::kString, 1, true, false, false, false, (int)offsetof(mjsTexture, content_type)}, + {"gridsize", mjXAttr::kInt, 2, true, false, false, false, (int)offsetof(mjsTexture, gridsize)}, + {"gridlayout", mjXAttr::kChars, 12, false, false, false, false, (int)offsetof(mjsTexture, gridlayout)}, + {"builtin", mjXAttr::kEnum, 1, true, false, false, false, (int)offsetof(mjsTexture, builtin), builtin_map, builtin_sz}, + {"rgb1", mjXAttr::kDouble, 3, true, false, false, false, (int)offsetof(mjsTexture, rgb1)}, + {"rgb2", mjXAttr::kDouble, 3, true, false, false, false, (int)offsetof(mjsTexture, rgb2)}, + {"mark", mjXAttr::kEnum, 1, true, false, false, false, (int)offsetof(mjsTexture, mark), mark_map, mark_sz}, + {"markrgb", mjXAttr::kDouble, 3, true, false, false, false, (int)offsetof(mjsTexture, markrgb)}, + {"random", mjXAttr::kDouble, 1, true, false, false, false, (int)offsetof(mjsTexture, random)}, + {"width", mjXAttr::kInt, 1, true, false, false, false, (int)offsetof(mjsTexture, width)}, + {"height", mjXAttr::kInt, 1, true, false, false, false, (int)offsetof(mjsTexture, height)}, + {"hflip", mjXAttr::kBool, 1, true, false, false, false, (int)offsetof(mjsTexture, hflip)}, + {"vflip", mjXAttr::kBool, 1, true, false, false, false, (int)offsetof(mjsTexture, vflip)}, + {"nchannel", mjXAttr::kInt, 1, true, false, false, false, (int)offsetof(mjsTexture, nchannel)}, +}; +inline constexpr int kTextureAttrsN = sizeof(kTextureAttrs) / sizeof(kTextureAttrs[0]); + +// material (mjsMaterial) +inline constexpr mjXAttr kMaterialAttrs[] = { + {"name", mjXAttr::kName, 1, true, false, true, false, -1}, + {"texrepeat", mjXAttr::kFloat, 2, true, false, false, false, (int)offsetof(mjsMaterial, texrepeat)}, + {"texuniform", mjXAttr::kBool, 1, true, false, false, false, (int)offsetof(mjsMaterial, texuniform)}, + {"emission", mjXAttr::kFloat, 1, true, false, false, false, (int)offsetof(mjsMaterial, emission)}, + {"specular", mjXAttr::kFloat, 1, true, false, false, false, (int)offsetof(mjsMaterial, specular)}, + {"shininess", mjXAttr::kFloat, 1, true, false, false, false, (int)offsetof(mjsMaterial, shininess)}, + {"reflectance", mjXAttr::kFloat, 1, true, false, false, false, (int)offsetof(mjsMaterial, reflectance)}, + {"metallic", mjXAttr::kFloat, 1, true, false, false, false, (int)offsetof(mjsMaterial, metallic)}, + {"roughness", mjXAttr::kFloat, 1, true, false, false, false, (int)offsetof(mjsMaterial, roughness)}, + {"rgba", mjXAttr::kFloat, 4, true, false, false, false, (int)offsetof(mjsMaterial, rgba)}, +}; +inline constexpr int kMaterialAttrsN = sizeof(kMaterialAttrs) / sizeof(kMaterialAttrs[0]); + +// body (mjsBody) +inline constexpr mjXAttr kBodyAttrs[] = { + {"name", mjXAttr::kName, 1, true, false, true, false, -1}, + {"pos", mjXAttr::kDouble, 3, true, false, false, false, (int)offsetof(mjsBody, pos)}, + {"mocap", mjXAttr::kBool, 1, true, false, false, false, (int)offsetof(mjsBody, mocap)}, + {"gravcomp", mjXAttr::kDouble, 1, true, false, false, false, (int)offsetof(mjsBody, gravcomp)}, + {"sleep", mjXAttr::kEnum, 1, true, false, false, false, (int)offsetof(mjsBody, sleep), bodysleep_map, bodysleep_sz}, + {"simple", mjXAttr::kEnumByte, 1, true, false, false, false, (int)offsetof(mjsBody, simple), FalseAuto_map, FalseAuto_sz}, + {"user", mjXAttr::kDoubleVec, 1, true, false, false, false, (int)offsetof(mjsBody, userdata)}, +}; +inline constexpr int kBodyAttrsN = sizeof(kBodyAttrs) / sizeof(kBodyAttrs[0]); + +// inertial (mjsBody) +inline constexpr mjXAttr kInertialAttrs[] = { + {"pos", mjXAttr::kDouble, 3, true, true, false, false, (int)offsetof(mjsBody, ipos)}, + {"mass", mjXAttr::kDouble, 1, true, true, false, false, (int)offsetof(mjsBody, mass)}, + {"diaginertia", mjXAttr::kDouble, 3, true, false, false, false, (int)offsetof(mjsBody, inertia)}, +}; +inline constexpr int kInertialAttrsN = sizeof(kInertialAttrs) / sizeof(kInertialAttrs[0]); + +// joint (mjsJoint) +inline constexpr mjXAttr kJointAttrs[] = { + {"name", mjXAttr::kName, 1, true, false, true, false, -1}, + {"type", mjXAttr::kEnum, 1, true, false, false, false, (int)offsetof(mjsJoint, type), jointtype_map, jointtype_sz}, + {"group", mjXAttr::kInt, 1, true, false, false, false, (int)offsetof(mjsJoint, group)}, + {"pos", mjXAttr::kDouble, 3, true, false, false, true, (int)offsetof(mjsJoint, pos)}, + {"axis", mjXAttr::kDouble, 3, true, false, false, true, (int)offsetof(mjsJoint, axis)}, + {"springdamper", mjXAttr::kDouble, 2, true, false, false, true, (int)offsetof(mjsJoint, springdamper)}, + {"limited", mjXAttr::kEnum, 1, true, false, false, true, (int)offsetof(mjsJoint, limited), FalseTrueAuto_map, FalseTrueAuto_sz}, + {"actuatorfrclimited", mjXAttr::kEnum, 1, true, false, false, true, (int)offsetof(mjsJoint, actfrclimited), FalseTrueAuto_map, FalseTrueAuto_sz}, + {"solreflimit", mjXAttr::kNum, mjNREF, false, false, false, false, (int)offsetof(mjsJoint, solref_limit)}, + {"solimplimit", mjXAttr::kNum, mjNIMP, false, false, false, false, (int)offsetof(mjsJoint, solimp_limit)}, + {"solreffriction", mjXAttr::kNum, mjNREF, false, false, false, false, (int)offsetof(mjsJoint, solref_friction)}, + {"solimpfriction", mjXAttr::kNum, mjNIMP, false, false, false, false, (int)offsetof(mjsJoint, solimp_friction)}, + {"stiffness", mjXAttr::kDouble, mjNPOLY+1, false, false, false, false, (int)offsetof(mjsJoint, stiffness)}, + {"range", mjXAttr::kDouble, 2, true, false, false, false, (int)offsetof(mjsJoint, range)}, + {"actuatorfrcrange", mjXAttr::kDouble, 2, true, false, false, false, (int)offsetof(mjsJoint, actfrcrange)}, + {"actuatorgravcomp", mjXAttr::kBool, 1, true, false, false, false, (int)offsetof(mjsJoint, actgravcomp)}, + {"margin", mjXAttr::kDouble, 1, true, false, false, false, (int)offsetof(mjsJoint, margin)}, + {"ref", mjXAttr::kDouble, 1, true, false, false, false, (int)offsetof(mjsJoint, ref)}, + {"springref", mjXAttr::kDouble, 1, true, false, false, false, (int)offsetof(mjsJoint, springref)}, + {"armature", mjXAttr::kDouble, 1, true, false, false, false, (int)offsetof(mjsJoint, armature)}, + {"damping", mjXAttr::kDouble, mjNPOLY+1, false, false, false, false, (int)offsetof(mjsJoint, damping)}, + {"frictionloss", mjXAttr::kDouble, 1, true, false, false, false, (int)offsetof(mjsJoint, frictionloss)}, + {"user", mjXAttr::kDoubleVec, 1, true, false, false, false, (int)offsetof(mjsJoint, userdata)}, +}; +inline constexpr int kJointAttrsN = sizeof(kJointAttrs) / sizeof(kJointAttrs[0]); + +// geom (mjsGeom) +inline constexpr mjXAttr kGeomAttrs[] = { + {"name", mjXAttr::kName, 1, true, false, true, false, -1}, + {"type", mjXAttr::kEnum, 1, true, false, false, false, (int)offsetof(mjsGeom, type), geomtype_map, geomtype_sz}, + {"contype", mjXAttr::kInt, 1, true, false, false, false, (int)offsetof(mjsGeom, contype)}, + {"conaffinity", mjXAttr::kInt, 1, true, false, false, false, (int)offsetof(mjsGeom, conaffinity)}, + {"condim", mjXAttr::kInt, 1, true, false, false, false, (int)offsetof(mjsGeom, condim)}, + {"group", mjXAttr::kInt, 1, true, false, false, false, (int)offsetof(mjsGeom, group)}, + {"priority", mjXAttr::kInt, 1, true, false, false, false, (int)offsetof(mjsGeom, priority)}, + {"size", mjXAttr::kDouble, 3, false, false, false, true, (int)offsetof(mjsGeom, size)}, + {"material", mjXAttr::kString, 1, true, false, false, false, (int)offsetof(mjsGeom, material)}, + {"friction", mjXAttr::kDouble, 3, false, false, false, false, (int)offsetof(mjsGeom, friction)}, + {"mass", mjXAttr::kDouble, 1, true, false, false, true, (int)offsetof(mjsGeom, mass)}, + {"density", mjXAttr::kDouble, 1, true, false, false, true, (int)offsetof(mjsGeom, density)}, + {"shellinertia", mjXAttr::kEnum, 1, true, false, false, true, (int)offsetof(mjsGeom, typeinertia), bool_map, 2}, + {"solmix", mjXAttr::kDouble, 1, true, false, false, false, (int)offsetof(mjsGeom, solmix)}, + {"solref", mjXAttr::kNum, mjNREF, false, false, false, false, (int)offsetof(mjsGeom, solref)}, + {"solimp", mjXAttr::kNum, mjNIMP, false, false, false, false, (int)offsetof(mjsGeom, solimp)}, + {"margin", mjXAttr::kDouble, 1, true, false, false, false, (int)offsetof(mjsGeom, margin)}, + {"gap", mjXAttr::kDouble, 1, true, false, false, false, (int)offsetof(mjsGeom, gap)}, + {"surfacevel", mjXAttr::kDouble, 6, false, false, false, false, (int)offsetof(mjsGeom, surfacevel)}, + {"adhesion", mjXAttr::kDouble, 1, true, false, false, false, (int)offsetof(mjsGeom, adhesion)}, + {"fromto", mjXAttr::kDouble, 6, true, false, false, true, (int)offsetof(mjsGeom, fromto)}, + {"pos", mjXAttr::kDouble, 3, true, false, false, true, (int)offsetof(mjsGeom, pos)}, + {"hfield", mjXAttr::kString, 1, true, false, false, false, (int)offsetof(mjsGeom, hfieldname)}, + {"mesh", mjXAttr::kString, 1, true, false, false, false, (int)offsetof(mjsGeom, meshname)}, + {"fitscale", mjXAttr::kDouble, 1, true, false, false, true, (int)offsetof(mjsGeom, fitscale)}, + {"rgba", mjXAttr::kFloat, 4, true, false, false, false, (int)offsetof(mjsGeom, rgba)}, + {"fluidcoef", mjXAttr::kNum, 5, false, false, false, false, (int)offsetof(mjsGeom, fluid_coefs)}, + {"user", mjXAttr::kDoubleVec, 1, true, false, false, false, (int)offsetof(mjsGeom, userdata)}, +}; +inline constexpr int kGeomAttrsN = sizeof(kGeomAttrs) / sizeof(kGeomAttrs[0]); + +// site (mjsSite) +inline constexpr mjXAttr kSiteAttrs[] = { + {"name", mjXAttr::kName, 1, true, false, true, false, -1}, + {"type", mjXAttr::kEnum, 1, true, false, false, false, (int)offsetof(mjsSite, type), geomtype_map, geomtype_sz}, + {"group", mjXAttr::kInt, 1, true, false, false, false, (int)offsetof(mjsSite, group)}, + {"pos", mjXAttr::kDouble, 3, true, false, false, false, (int)offsetof(mjsSite, pos)}, + {"material", mjXAttr::kString, 1, true, false, false, false, (int)offsetof(mjsSite, material)}, + {"size", mjXAttr::kDouble, 3, false, false, false, true, (int)offsetof(mjsSite, size)}, + {"fromto", mjXAttr::kDouble, 6, true, false, false, true, (int)offsetof(mjsSite, fromto)}, + {"rgba", mjXAttr::kFloat, 4, true, false, false, false, (int)offsetof(mjsSite, rgba)}, + {"user", mjXAttr::kDoubleVec, 1, true, false, false, false, (int)offsetof(mjsSite, userdata)}, +}; +inline constexpr int kSiteAttrsN = sizeof(kSiteAttrs) / sizeof(kSiteAttrs[0]); + +// camera (mjsCamera) +inline constexpr mjXAttr kCameraAttrs[] = { + {"name", mjXAttr::kName, 1, true, false, true, false, -1}, + {"projection", mjXAttr::kEnum, 1, true, false, false, false, (int)offsetof(mjsCamera, proj), projection_map, projection_sz}, + {"fovy", mjXAttr::kDouble, 1, true, false, false, true, (int)offsetof(mjsCamera, fovy)}, + {"ipd", mjXAttr::kDouble, 1, true, false, false, false, (int)offsetof(mjsCamera, ipd)}, + {"resolution", mjXAttr::kInt, 2, true, false, false, false, (int)offsetof(mjsCamera, resolution)}, + {"output", mjXAttr::kFlags, 1, true, false, false, false, (int)offsetof(mjsCamera, output), camout_map, camout_sz}, + {"pos", mjXAttr::kDouble, 3, true, false, false, false, (int)offsetof(mjsCamera, pos)}, + {"mode", mjXAttr::kEnum, 1, true, false, false, false, (int)offsetof(mjsCamera, mode), camlight_map, camlight_sz}, + {"target", mjXAttr::kString, 1, true, false, true, false, (int)offsetof(mjsCamera, targetbody)}, + {"focal", mjXAttr::kFloat, 2, true, false, false, true, (int)offsetof(mjsCamera, focal_length)}, + {"focalpixel", mjXAttr::kFloat, 2, true, false, false, true, (int)offsetof(mjsCamera, focal_pixel)}, + {"principal", mjXAttr::kFloat, 2, true, false, false, true, (int)offsetof(mjsCamera, principal_length)}, + {"principalpixel", mjXAttr::kFloat, 2, true, false, false, true, (int)offsetof(mjsCamera, principal_pixel)}, + {"sensorsize", mjXAttr::kFloat, 2, true, false, false, true, (int)offsetof(mjsCamera, sensor_size)}, + {"user", mjXAttr::kDoubleVec, 1, true, false, false, false, (int)offsetof(mjsCamera, userdata)}, +}; +inline constexpr int kCameraAttrsN = sizeof(kCameraAttrs) / sizeof(kCameraAttrs[0]); + +// light (mjsLight) +inline constexpr mjXAttr kLightAttrs[] = { + {"name", mjXAttr::kName, 1, true, false, true, false, -1}, + {"castshadow", mjXAttr::kBool, 1, true, false, false, false, (int)offsetof(mjsLight, castshadow)}, + {"active", mjXAttr::kBool, 1, true, false, false, false, (int)offsetof(mjsLight, active)}, + {"pos", mjXAttr::kDouble, 3, true, false, false, false, (int)offsetof(mjsLight, pos)}, + {"dir", mjXAttr::kDouble, 3, true, false, false, false, (int)offsetof(mjsLight, dir)}, + {"bulbradius", mjXAttr::kFloat, 1, true, false, false, false, (int)offsetof(mjsLight, bulbradius)}, + {"intensity", mjXAttr::kFloat, 1, true, false, false, false, (int)offsetof(mjsLight, intensity)}, + {"range", mjXAttr::kFloat, 1, true, false, false, false, (int)offsetof(mjsLight, range)}, + {"attenuation", mjXAttr::kFloat, 3, true, false, false, false, (int)offsetof(mjsLight, attenuation)}, + {"cutoff", mjXAttr::kFloat, 1, true, false, false, false, (int)offsetof(mjsLight, cutoff)}, + {"exponent", mjXAttr::kFloat, 1, true, false, false, false, (int)offsetof(mjsLight, exponent)}, + {"ambient", mjXAttr::kFloat, 3, true, false, false, false, (int)offsetof(mjsLight, ambient)}, + {"diffuse", mjXAttr::kFloat, 3, true, false, false, false, (int)offsetof(mjsLight, diffuse)}, + {"specular", mjXAttr::kFloat, 3, true, false, false, false, (int)offsetof(mjsLight, specular)}, + {"mode", mjXAttr::kEnum, 1, true, false, false, false, (int)offsetof(mjsLight, mode), camlight_map, camlight_sz}, + {"target", mjXAttr::kString, 1, true, false, true, false, (int)offsetof(mjsLight, targetbody)}, + {"texture", mjXAttr::kString, 1, true, false, true, false, (int)offsetof(mjsLight, texture)}, +}; +inline constexpr int kLightAttrsN = sizeof(kLightAttrs) / sizeof(kLightAttrs[0]); + +// elasticity (mjsFlex) +inline constexpr mjXAttr kElasticityAttrs[] = { + {"young", mjXAttr::kDouble, 1, true, false, false, false, (int)offsetof(mjsFlex, young)}, + {"poisson", mjXAttr::kDouble, 1, true, false, false, false, (int)offsetof(mjsFlex, poisson)}, + {"damping", mjXAttr::kDouble, 1, true, false, false, false, (int)offsetof(mjsFlex, damping)}, + {"thickness", mjXAttr::kDouble, 1, true, false, false, false, (int)offsetof(mjsFlex, thickness)}, + {"elastic2d", mjXAttr::kEnum, 1, true, false, false, false, (int)offsetof(mjsFlex, elastic2d), elastic2d_map, elastic2d_sz}, +}; +inline constexpr int kElasticityAttrsN = sizeof(kElasticityAttrs) / sizeof(kElasticityAttrs[0]); + +// flexcomp_contact (mjsFlex) +inline constexpr mjXAttr kFlexcomp_contactAttrs[] = { + {"contype", mjXAttr::kInt, 1, true, false, false, false, (int)offsetof(mjsFlex, contype)}, + {"conaffinity", mjXAttr::kInt, 1, true, false, false, false, (int)offsetof(mjsFlex, conaffinity)}, + {"condim", mjXAttr::kInt, 1, true, false, false, false, (int)offsetof(mjsFlex, condim)}, + {"priority", mjXAttr::kInt, 1, true, false, false, false, (int)offsetof(mjsFlex, priority)}, + {"friction", mjXAttr::kDouble, 3, false, false, false, false, (int)offsetof(mjsFlex, friction)}, + {"solmix", mjXAttr::kDouble, 1, true, false, false, false, (int)offsetof(mjsFlex, solmix)}, + {"solref", mjXAttr::kNum, mjNREF, false, false, false, false, (int)offsetof(mjsFlex, solref)}, + {"solimp", mjXAttr::kNum, mjNIMP, false, false, false, false, (int)offsetof(mjsFlex, solimp)}, + {"margin", mjXAttr::kDouble, 1, true, false, false, false, (int)offsetof(mjsFlex, margin)}, + {"gap", mjXAttr::kDouble, 1, true, false, false, false, (int)offsetof(mjsFlex, gap)}, + {"internal", mjXAttr::kBool, 1, true, false, false, false, (int)offsetof(mjsFlex, internal)}, + {"selfcollide", mjXAttr::kEnum, 1, true, false, false, false, (int)offsetof(mjsFlex, selfcollide), flexself_map, flexself_sz}, + {"activelayers", mjXAttr::kInt, 1, true, false, false, false, (int)offsetof(mjsFlex, activelayers)}, + {"passive", mjXAttr::kEnum, 1, true, false, false, false, (int)offsetof(mjsFlex, passive), bool_map, 2}, +}; +inline constexpr int kFlexcomp_contactAttrsN = sizeof(kFlexcomp_contactAttrs) / sizeof(kFlexcomp_contactAttrs[0]); + +// flex (mjsFlex) +inline constexpr mjXAttr kFlexAttrs[] = { + {"name", mjXAttr::kName, 1, true, false, true, false, -1}, + {"group", mjXAttr::kInt, 1, true, false, false, false, (int)offsetof(mjsFlex, group)}, + {"dim", mjXAttr::kInt, 1, true, false, false, false, (int)offsetof(mjsFlex, dim)}, + {"radius", mjXAttr::kDouble, 1, true, false, false, false, (int)offsetof(mjsFlex, radius)}, + {"material", mjXAttr::kString, 1, true, false, false, false, (int)offsetof(mjsFlex, material)}, + {"rgba", mjXAttr::kFloat, 4, true, false, false, false, (int)offsetof(mjsFlex, rgba)}, + {"flatskin", mjXAttr::kBool, 1, true, false, false, false, (int)offsetof(mjsFlex, flatskin)}, + {"body", mjXAttr::kStringVec, 1, true, true, false, false, (int)offsetof(mjsFlex, vertbody)}, + {"vertex", mjXAttr::kDoubleVec, 1, true, false, false, false, (int)offsetof(mjsFlex, vert)}, + {"element", mjXAttr::kIntVec, 1, true, true, false, false, (int)offsetof(mjsFlex, elem)}, + {"texcoord", mjXAttr::kFloatVec, 1, true, false, false, false, (int)offsetof(mjsFlex, texcoord)}, + {"elemtexcoord", mjXAttr::kIntVec, 1, true, false, false, false, (int)offsetof(mjsFlex, elemtexcoord)}, + {"node", mjXAttr::kStringVec, 1, true, false, false, false, (int)offsetof(mjsFlex, nodebody)}, +}; +inline constexpr int kFlexAttrsN = sizeof(kFlexAttrs) / sizeof(kFlexAttrs[0]); + +// flex_edge (mjsFlex) +inline constexpr mjXAttr kFlex_edgeAttrs[] = { + {"stiffness", mjXAttr::kDouble, 1, true, false, false, false, (int)offsetof(mjsFlex, edgestiffness)}, + {"damping", mjXAttr::kDouble, 1, true, false, false, false, (int)offsetof(mjsFlex, edgedamping)}, +}; +inline constexpr int kFlex_edgeAttrsN = sizeof(kFlex_edgeAttrs) / sizeof(kFlex_edgeAttrs[0]); + +// pair (mjsPair) +inline constexpr mjXAttr kPairAttrs[] = { + {"name", mjXAttr::kName, 1, true, false, true, false, -1}, + {"geom1", mjXAttr::kString, 1, true, false, true, false, (int)offsetof(mjsPair, geomname1)}, + {"geom2", mjXAttr::kString, 1, true, false, true, false, (int)offsetof(mjsPair, geomname2)}, + {"condim", mjXAttr::kInt, 1, true, false, false, false, (int)offsetof(mjsPair, condim)}, + {"friction", mjXAttr::kDouble, 5, false, false, false, false, (int)offsetof(mjsPair, friction)}, + {"solref", mjXAttr::kNum, mjNREF, false, false, false, false, (int)offsetof(mjsPair, solref)}, + {"solreffriction", mjXAttr::kNum, mjNREF, false, false, false, false, (int)offsetof(mjsPair, solreffriction)}, + {"solimp", mjXAttr::kNum, mjNIMP, false, false, false, false, (int)offsetof(mjsPair, solimp)}, + {"gap", mjXAttr::kDouble, 1, true, false, false, false, (int)offsetof(mjsPair, gap)}, + {"margin", mjXAttr::kDouble, 1, true, false, false, false, (int)offsetof(mjsPair, margin)}, + {"adhesion", mjXAttr::kDouble, 1, true, false, false, false, (int)offsetof(mjsPair, adhesion)}, +}; +inline constexpr int kPairAttrsN = sizeof(kPairAttrs) / sizeof(kPairAttrs[0]); + +// exclude (mjsExclude) +inline constexpr mjXAttr kExcludeAttrs[] = { + {"name", mjXAttr::kName, 1, true, false, true, false, -1}, + {"body1", mjXAttr::kString, 1, true, true, false, false, (int)offsetof(mjsExclude, bodyname1)}, + {"body2", mjXAttr::kString, 1, true, true, false, false, (int)offsetof(mjsExclude, bodyname2)}, +}; +inline constexpr int kExcludeAttrsN = sizeof(kExcludeAttrs) / sizeof(kExcludeAttrs[0]); + +// spatial (mjsTendon) +inline constexpr mjXAttr kSpatialAttrs[] = { + {"name", mjXAttr::kName, 1, true, false, true, false, -1}, + {"group", mjXAttr::kInt, 1, true, false, false, false, (int)offsetof(mjsTendon, group)}, + {"limited", mjXAttr::kEnum, 1, true, false, false, false, (int)offsetof(mjsTendon, limited), FalseTrueAuto_map, FalseTrueAuto_sz}, + {"actuatorfrclimited", mjXAttr::kEnum, 1, true, false, false, false, (int)offsetof(mjsTendon, actfrclimited), FalseTrueAuto_map, FalseTrueAuto_sz}, + {"range", mjXAttr::kDouble, 2, true, false, false, false, (int)offsetof(mjsTendon, range)}, + {"actuatorfrcrange", mjXAttr::kDouble, 2, true, false, false, false, (int)offsetof(mjsTendon, actfrcrange)}, + {"solreflimit", mjXAttr::kNum, mjNREF, false, false, false, false, (int)offsetof(mjsTendon, solref_limit)}, + {"solimplimit", mjXAttr::kNum, mjNIMP, false, false, false, false, (int)offsetof(mjsTendon, solimp_limit)}, + {"solreffriction", mjXAttr::kNum, mjNREF, false, false, false, false, (int)offsetof(mjsTendon, solref_friction)}, + {"solimpfriction", mjXAttr::kNum, mjNIMP, false, false, false, false, (int)offsetof(mjsTendon, solimp_friction)}, + {"frictionloss", mjXAttr::kDouble, 1, true, false, false, false, (int)offsetof(mjsTendon, frictionloss)}, + {"width", mjXAttr::kDouble, 1, true, false, false, false, (int)offsetof(mjsTendon, width)}, + {"material", mjXAttr::kString, 1, true, false, false, false, (int)offsetof(mjsTendon, material)}, + {"margin", mjXAttr::kDouble, 1, true, false, false, false, (int)offsetof(mjsTendon, margin)}, + {"stiffness", mjXAttr::kDouble, mjNPOLY+1, false, false, false, false, (int)offsetof(mjsTendon, stiffness)}, + {"damping", mjXAttr::kDouble, mjNPOLY+1, false, false, false, false, (int)offsetof(mjsTendon, damping)}, + {"armature", mjXAttr::kDouble, 1, true, false, false, false, (int)offsetof(mjsTendon, armature)}, + {"rgba", mjXAttr::kFloat, 4, true, false, false, false, (int)offsetof(mjsTendon, rgba)}, + {"user", mjXAttr::kDoubleVec, 1, true, false, false, false, (int)offsetof(mjsTendon, userdata)}, +}; +inline constexpr int kSpatialAttrsN = sizeof(kSpatialAttrs) / sizeof(kSpatialAttrs[0]); + +// fixed (mjsTendon) +inline constexpr mjXAttr kFixedAttrs[] = { + {"name", mjXAttr::kName, 1, true, false, true, false, -1}, + {"group", mjXAttr::kInt, 1, true, false, false, false, (int)offsetof(mjsTendon, group)}, + {"limited", mjXAttr::kEnum, 1, true, false, false, false, (int)offsetof(mjsTendon, limited), FalseTrueAuto_map, FalseTrueAuto_sz}, + {"actuatorfrclimited", mjXAttr::kEnum, 1, true, false, false, false, (int)offsetof(mjsTendon, actfrclimited), FalseTrueAuto_map, FalseTrueAuto_sz}, + {"range", mjXAttr::kDouble, 2, true, false, false, false, (int)offsetof(mjsTendon, range)}, + {"actuatorfrcrange", mjXAttr::kDouble, 2, true, false, false, false, (int)offsetof(mjsTendon, actfrcrange)}, + {"solreflimit", mjXAttr::kNum, mjNREF, false, false, false, false, (int)offsetof(mjsTendon, solref_limit)}, + {"solimplimit", mjXAttr::kNum, mjNIMP, false, false, false, false, (int)offsetof(mjsTendon, solimp_limit)}, + {"solreffriction", mjXAttr::kNum, mjNREF, false, false, false, false, (int)offsetof(mjsTendon, solref_friction)}, + {"solimpfriction", mjXAttr::kNum, mjNIMP, false, false, false, false, (int)offsetof(mjsTendon, solimp_friction)}, + {"frictionloss", mjXAttr::kDouble, 1, true, false, false, false, (int)offsetof(mjsTendon, frictionloss)}, + {"margin", mjXAttr::kDouble, 1, true, false, false, false, (int)offsetof(mjsTendon, margin)}, + {"stiffness", mjXAttr::kDouble, mjNPOLY+1, false, false, false, false, (int)offsetof(mjsTendon, stiffness)}, + {"damping", mjXAttr::kDouble, mjNPOLY+1, false, false, false, false, (int)offsetof(mjsTendon, damping)}, + {"armature", mjXAttr::kDouble, 1, true, false, false, false, (int)offsetof(mjsTendon, armature)}, + {"user", mjXAttr::kDoubleVec, 1, true, false, false, false, (int)offsetof(mjsTendon, userdata)}, +}; +inline constexpr int kFixedAttrsN = sizeof(kFixedAttrs) / sizeof(kFixedAttrs[0]); + +// general (mjsActuator) +inline constexpr mjXAttr kGeneralAttrs[] = { + {"name", mjXAttr::kName, 1, true, false, true, false, -1}, + {"group", mjXAttr::kInt, 1, true, false, false, false, (int)offsetof(mjsActuator, group)}, + {"nsample", mjXAttr::kInt, 1, true, false, false, false, (int)offsetof(mjsActuator, nsample)}, + {"interp", mjXAttr::kEnum, 1, true, false, false, false, (int)offsetof(mjsActuator, interp), interp_map, interp_sz}, + {"delay", mjXAttr::kDouble, 1, true, false, false, false, (int)offsetof(mjsActuator, delay)}, + {"ctrlrange", mjXAttr::kDouble, 2, true, false, false, false, (int)offsetof(mjsActuator, ctrlrange)}, + {"user", mjXAttr::kDoubleVec, 1, true, false, false, false, (int)offsetof(mjsActuator, userdata)}, + {"ctrllimited", mjXAttr::kEnum, 1, true, false, false, false, (int)offsetof(mjsActuator, ctrllimited), FalseTrueAuto_map, FalseTrueAuto_sz}, + {"forcelimited", mjXAttr::kEnum, 1, true, false, false, false, (int)offsetof(mjsActuator, forcelimited), FalseTrueAuto_map, FalseTrueAuto_sz}, + {"actlimited", mjXAttr::kEnum, 1, true, false, false, false, (int)offsetof(mjsActuator, actlimited), FalseTrueAuto_map, FalseTrueAuto_sz}, + {"forcerange", mjXAttr::kDouble, 2, true, false, false, false, (int)offsetof(mjsActuator, forcerange)}, + {"actrange", mjXAttr::kDouble, 2, true, false, false, false, (int)offsetof(mjsActuator, actrange)}, + {"lengthrange", mjXAttr::kDouble, 2, true, false, true, false, (int)offsetof(mjsActuator, lengthrange)}, + {"gear", mjXAttr::kDouble, 6, false, false, false, false, (int)offsetof(mjsActuator, gear)}, + {"damping", mjXAttr::kDouble, mjNPOLY+1, false, false, false, false, (int)offsetof(mjsActuator, damping)}, + {"armature", mjXAttr::kDouble, 1, true, false, false, false, (int)offsetof(mjsActuator, armature)}, + {"actdim", mjXAttr::kInt, 1, true, false, false, true, (int)offsetof(mjsActuator, actdim)}, + {"velrange", mjXAttr::kDouble, 2, true, false, false, false, (int)offsetof(mjsActuator, velrange)}, + {"ffrange", mjXAttr::kDouble, 2, true, false, false, false, (int)offsetof(mjsActuator, ffrange)}, + {"dyntype", mjXAttr::kEnum, 1, true, false, false, false, (int)offsetof(mjsActuator, dyntype), dyn_map, dyn_sz}, + {"gaintype", mjXAttr::kEnum, 1, true, false, false, true, (int)offsetof(mjsActuator, gaintype), gain_map, gain_sz}, + {"biastype", mjXAttr::kEnum, 1, true, false, false, true, (int)offsetof(mjsActuator, biastype), bias_map, bias_sz}, + {"dynprm", mjXAttr::kDouble, mjNDYN, false, false, false, false, (int)offsetof(mjsActuator, dynprm)}, + {"gainprm", mjXAttr::kDouble, mjNGAIN, false, false, false, true, (int)offsetof(mjsActuator, gainprm)}, + {"biasprm", mjXAttr::kDouble, mjNBIAS, false, false, false, true, (int)offsetof(mjsActuator, biasprm)}, + {"actearly", mjXAttr::kBool, 1, true, false, false, false, (int)offsetof(mjsActuator, actearly)}, +}; +inline constexpr int kGeneralAttrsN = sizeof(kGeneralAttrs) / sizeof(kGeneralAttrs[0]); + +// touch (mjsSensor) +inline constexpr mjXAttr kTouchAttrs[] = { + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, type), nullptr, 0, mjSENS_TOUCH}, + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, objtype), nullptr, 0, mjOBJ_SITE}, + {"site", mjXAttr::kString, 1, true, true, false, false, (int)offsetof(mjsSensor, objname)}, +}; +inline constexpr int kTouchAttrsN = sizeof(kTouchAttrs) / sizeof(kTouchAttrs[0]); + +// accelerometer (mjsSensor) +inline constexpr mjXAttr kAccelerometerAttrs[] = { + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, type), nullptr, 0, mjSENS_ACCELEROMETER}, + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, objtype), nullptr, 0, mjOBJ_SITE}, + {"site", mjXAttr::kString, 1, true, true, false, false, (int)offsetof(mjsSensor, objname)}, +}; +inline constexpr int kAccelerometerAttrsN = sizeof(kAccelerometerAttrs) / sizeof(kAccelerometerAttrs[0]); + +// velocimeter (mjsSensor) +inline constexpr mjXAttr kVelocimeterAttrs[] = { + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, type), nullptr, 0, mjSENS_VELOCIMETER}, + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, objtype), nullptr, 0, mjOBJ_SITE}, + {"site", mjXAttr::kString, 1, true, true, false, false, (int)offsetof(mjsSensor, objname)}, +}; +inline constexpr int kVelocimeterAttrsN = sizeof(kVelocimeterAttrs) / sizeof(kVelocimeterAttrs[0]); + +// gyro (mjsSensor) +inline constexpr mjXAttr kGyroAttrs[] = { + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, type), nullptr, 0, mjSENS_GYRO}, + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, objtype), nullptr, 0, mjOBJ_SITE}, + {"site", mjXAttr::kString, 1, true, true, false, false, (int)offsetof(mjsSensor, objname)}, +}; +inline constexpr int kGyroAttrsN = sizeof(kGyroAttrs) / sizeof(kGyroAttrs[0]); + +// force (mjsSensor) +inline constexpr mjXAttr kForceAttrs[] = { + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, type), nullptr, 0, mjSENS_FORCE}, + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, objtype), nullptr, 0, mjOBJ_SITE}, + {"site", mjXAttr::kString, 1, true, true, false, false, (int)offsetof(mjsSensor, objname)}, +}; +inline constexpr int kForceAttrsN = sizeof(kForceAttrs) / sizeof(kForceAttrs[0]); + +// torque (mjsSensor) +inline constexpr mjXAttr kTorqueAttrs[] = { + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, type), nullptr, 0, mjSENS_TORQUE}, + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, objtype), nullptr, 0, mjOBJ_SITE}, + {"site", mjXAttr::kString, 1, true, true, false, false, (int)offsetof(mjsSensor, objname)}, +}; +inline constexpr int kTorqueAttrsN = sizeof(kTorqueAttrs) / sizeof(kTorqueAttrs[0]); + +// magnetometer (mjsSensor) +inline constexpr mjXAttr kMagnetometerAttrs[] = { + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, type), nullptr, 0, mjSENS_MAGNETOMETER}, + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, objtype), nullptr, 0, mjOBJ_SITE}, + {"site", mjXAttr::kString, 1, true, true, false, false, (int)offsetof(mjsSensor, objname)}, +}; +inline constexpr int kMagnetometerAttrsN = sizeof(kMagnetometerAttrs) / sizeof(kMagnetometerAttrs[0]); + +// camprojection (mjsSensor) +inline constexpr mjXAttr kCamprojectionAttrs[] = { + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, type), nullptr, 0, mjSENS_CAMPROJECTION}, + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, objtype), nullptr, 0, mjOBJ_SITE}, + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, reftype), nullptr, 0, mjOBJ_CAMERA}, + {"site", mjXAttr::kString, 1, true, true, false, false, (int)offsetof(mjsSensor, objname)}, + {"camera", mjXAttr::kString, 1, true, true, false, false, (int)offsetof(mjsSensor, refname)}, +}; +inline constexpr int kCamprojectionAttrsN = sizeof(kCamprojectionAttrs) / sizeof(kCamprojectionAttrs[0]); + +// jointpos (mjsSensor) +inline constexpr mjXAttr kJointposAttrs[] = { + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, type), nullptr, 0, mjSENS_JOINTPOS}, + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, objtype), nullptr, 0, mjOBJ_JOINT}, + {"joint", mjXAttr::kString, 1, true, true, false, false, (int)offsetof(mjsSensor, objname)}, +}; +inline constexpr int kJointposAttrsN = sizeof(kJointposAttrs) / sizeof(kJointposAttrs[0]); + +// jointvel (mjsSensor) +inline constexpr mjXAttr kJointvelAttrs[] = { + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, type), nullptr, 0, mjSENS_JOINTVEL}, + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, objtype), nullptr, 0, mjOBJ_JOINT}, + {"joint", mjXAttr::kString, 1, true, true, false, false, (int)offsetof(mjsSensor, objname)}, +}; +inline constexpr int kJointvelAttrsN = sizeof(kJointvelAttrs) / sizeof(kJointvelAttrs[0]); + +// tendonpos (mjsSensor) +inline constexpr mjXAttr kTendonposAttrs[] = { + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, type), nullptr, 0, mjSENS_TENDONPOS}, + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, objtype), nullptr, 0, mjOBJ_TENDON}, + {"tendon", mjXAttr::kString, 1, true, true, false, false, (int)offsetof(mjsSensor, objname)}, +}; +inline constexpr int kTendonposAttrsN = sizeof(kTendonposAttrs) / sizeof(kTendonposAttrs[0]); + +// tendonvel (mjsSensor) +inline constexpr mjXAttr kTendonvelAttrs[] = { + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, type), nullptr, 0, mjSENS_TENDONVEL}, + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, objtype), nullptr, 0, mjOBJ_TENDON}, + {"tendon", mjXAttr::kString, 1, true, true, false, false, (int)offsetof(mjsSensor, objname)}, +}; +inline constexpr int kTendonvelAttrsN = sizeof(kTendonvelAttrs) / sizeof(kTendonvelAttrs[0]); + +// actuatorpos (mjsSensor) +inline constexpr mjXAttr kActuatorposAttrs[] = { + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, type), nullptr, 0, mjSENS_ACTUATORPOS}, + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, objtype), nullptr, 0, mjOBJ_ACTUATOR}, + {"actuator", mjXAttr::kString, 1, true, true, false, false, (int)offsetof(mjsSensor, objname)}, +}; +inline constexpr int kActuatorposAttrsN = sizeof(kActuatorposAttrs) / sizeof(kActuatorposAttrs[0]); + +// actuatorvel (mjsSensor) +inline constexpr mjXAttr kActuatorvelAttrs[] = { + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, type), nullptr, 0, mjSENS_ACTUATORVEL}, + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, objtype), nullptr, 0, mjOBJ_ACTUATOR}, + {"actuator", mjXAttr::kString, 1, true, true, false, false, (int)offsetof(mjsSensor, objname)}, +}; +inline constexpr int kActuatorvelAttrsN = sizeof(kActuatorvelAttrs) / sizeof(kActuatorvelAttrs[0]); + +// actuatorfrc (mjsSensor) +inline constexpr mjXAttr kActuatorfrcAttrs[] = { + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, type), nullptr, 0, mjSENS_ACTUATORFRC}, + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, objtype), nullptr, 0, mjOBJ_ACTUATOR}, + {"actuator", mjXAttr::kString, 1, true, true, false, false, (int)offsetof(mjsSensor, objname)}, +}; +inline constexpr int kActuatorfrcAttrsN = sizeof(kActuatorfrcAttrs) / sizeof(kActuatorfrcAttrs[0]); + +// jointactuatorfrc (mjsSensor) +inline constexpr mjXAttr kJointactuatorfrcAttrs[] = { + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, type), nullptr, 0, mjSENS_JOINTACTFRC}, + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, objtype), nullptr, 0, mjOBJ_JOINT}, + {"joint", mjXAttr::kString, 1, true, true, false, false, (int)offsetof(mjsSensor, objname)}, +}; +inline constexpr int kJointactuatorfrcAttrsN = sizeof(kJointactuatorfrcAttrs) / sizeof(kJointactuatorfrcAttrs[0]); + +// tendonactuatorfrc (mjsSensor) +inline constexpr mjXAttr kTendonactuatorfrcAttrs[] = { + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, type), nullptr, 0, mjSENS_TENDONACTFRC}, + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, objtype), nullptr, 0, mjOBJ_TENDON}, + {"tendon", mjXAttr::kString, 1, true, true, false, false, (int)offsetof(mjsSensor, objname)}, +}; +inline constexpr int kTendonactuatorfrcAttrsN = sizeof(kTendonactuatorfrcAttrs) / sizeof(kTendonactuatorfrcAttrs[0]); + +// ballquat (mjsSensor) +inline constexpr mjXAttr kBallquatAttrs[] = { + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, type), nullptr, 0, mjSENS_BALLQUAT}, + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, objtype), nullptr, 0, mjOBJ_JOINT}, + {"joint", mjXAttr::kString, 1, true, true, false, false, (int)offsetof(mjsSensor, objname)}, +}; +inline constexpr int kBallquatAttrsN = sizeof(kBallquatAttrs) / sizeof(kBallquatAttrs[0]); + +// ballangvel (mjsSensor) +inline constexpr mjXAttr kBallangvelAttrs[] = { + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, type), nullptr, 0, mjSENS_BALLANGVEL}, + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, objtype), nullptr, 0, mjOBJ_JOINT}, + {"joint", mjXAttr::kString, 1, true, true, false, false, (int)offsetof(mjsSensor, objname)}, +}; +inline constexpr int kBallangvelAttrsN = sizeof(kBallangvelAttrs) / sizeof(kBallangvelAttrs[0]); + +// jointlimitpos (mjsSensor) +inline constexpr mjXAttr kJointlimitposAttrs[] = { + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, type), nullptr, 0, mjSENS_JOINTLIMITPOS}, + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, objtype), nullptr, 0, mjOBJ_JOINT}, + {"joint", mjXAttr::kString, 1, true, true, false, false, (int)offsetof(mjsSensor, objname)}, +}; +inline constexpr int kJointlimitposAttrsN = sizeof(kJointlimitposAttrs) / sizeof(kJointlimitposAttrs[0]); + +// jointlimitvel (mjsSensor) +inline constexpr mjXAttr kJointlimitvelAttrs[] = { + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, type), nullptr, 0, mjSENS_JOINTLIMITVEL}, + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, objtype), nullptr, 0, mjOBJ_JOINT}, + {"joint", mjXAttr::kString, 1, true, true, false, false, (int)offsetof(mjsSensor, objname)}, +}; +inline constexpr int kJointlimitvelAttrsN = sizeof(kJointlimitvelAttrs) / sizeof(kJointlimitvelAttrs[0]); + +// jointlimitfrc (mjsSensor) +inline constexpr mjXAttr kJointlimitfrcAttrs[] = { + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, type), nullptr, 0, mjSENS_JOINTLIMITFRC}, + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, objtype), nullptr, 0, mjOBJ_JOINT}, + {"joint", mjXAttr::kString, 1, true, true, false, false, (int)offsetof(mjsSensor, objname)}, +}; +inline constexpr int kJointlimitfrcAttrsN = sizeof(kJointlimitfrcAttrs) / sizeof(kJointlimitfrcAttrs[0]); + +// tendonlimitpos (mjsSensor) +inline constexpr mjXAttr kTendonlimitposAttrs[] = { + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, type), nullptr, 0, mjSENS_TENDONLIMITPOS}, + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, objtype), nullptr, 0, mjOBJ_TENDON}, + {"tendon", mjXAttr::kString, 1, true, true, false, false, (int)offsetof(mjsSensor, objname)}, +}; +inline constexpr int kTendonlimitposAttrsN = sizeof(kTendonlimitposAttrs) / sizeof(kTendonlimitposAttrs[0]); + +// tendonlimitvel (mjsSensor) +inline constexpr mjXAttr kTendonlimitvelAttrs[] = { + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, type), nullptr, 0, mjSENS_TENDONLIMITVEL}, + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, objtype), nullptr, 0, mjOBJ_TENDON}, + {"tendon", mjXAttr::kString, 1, true, true, false, false, (int)offsetof(mjsSensor, objname)}, +}; +inline constexpr int kTendonlimitvelAttrsN = sizeof(kTendonlimitvelAttrs) / sizeof(kTendonlimitvelAttrs[0]); + +// tendonlimitfrc (mjsSensor) +inline constexpr mjXAttr kTendonlimitfrcAttrs[] = { + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, type), nullptr, 0, mjSENS_TENDONLIMITFRC}, + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, objtype), nullptr, 0, mjOBJ_TENDON}, + {"tendon", mjXAttr::kString, 1, true, true, false, false, (int)offsetof(mjsSensor, objname)}, +}; +inline constexpr int kTendonlimitfrcAttrsN = sizeof(kTendonlimitfrcAttrs) / sizeof(kTendonlimitfrcAttrs[0]); + +// framepos (mjsSensor) +inline constexpr mjXAttr kFrameposAttrs[] = { + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, type), nullptr, 0, mjSENS_FRAMEPOS}, + {"objtype", mjXAttr::kEnum, 1, true, true, false, false, (int)offsetof(mjsSensor, objtype), frameobj_map, frameobj_sz}, + {"objname", mjXAttr::kString, 1, true, true, false, false, (int)offsetof(mjsSensor, objname)}, + {"reftype", mjXAttr::kEnum, 1, true, false, false, false, (int)offsetof(mjsSensor, reftype), frameobj_map, frameobj_sz}, + {"refname", mjXAttr::kString, 1, true, false, false, false, (int)offsetof(mjsSensor, refname)}, +}; +inline constexpr int kFrameposAttrsN = sizeof(kFrameposAttrs) / sizeof(kFrameposAttrs[0]); + +// framequat (mjsSensor) +inline constexpr mjXAttr kFramequatAttrs[] = { + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, type), nullptr, 0, mjSENS_FRAMEQUAT}, + {"objtype", mjXAttr::kEnum, 1, true, true, false, false, (int)offsetof(mjsSensor, objtype), frameobj_map, frameobj_sz}, + {"objname", mjXAttr::kString, 1, true, true, false, false, (int)offsetof(mjsSensor, objname)}, + {"reftype", mjXAttr::kEnum, 1, true, false, false, false, (int)offsetof(mjsSensor, reftype), frameobj_map, frameobj_sz}, + {"refname", mjXAttr::kString, 1, true, false, false, false, (int)offsetof(mjsSensor, refname)}, +}; +inline constexpr int kFramequatAttrsN = sizeof(kFramequatAttrs) / sizeof(kFramequatAttrs[0]); + +// framexaxis (mjsSensor) +inline constexpr mjXAttr kFramexaxisAttrs[] = { + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, type), nullptr, 0, mjSENS_FRAMEXAXIS}, + {"objtype", mjXAttr::kEnum, 1, true, true, false, false, (int)offsetof(mjsSensor, objtype), frameobj_map, frameobj_sz}, + {"objname", mjXAttr::kString, 1, true, true, false, false, (int)offsetof(mjsSensor, objname)}, + {"reftype", mjXAttr::kEnum, 1, true, false, false, false, (int)offsetof(mjsSensor, reftype), frameobj_map, frameobj_sz}, + {"refname", mjXAttr::kString, 1, true, false, false, false, (int)offsetof(mjsSensor, refname)}, +}; +inline constexpr int kFramexaxisAttrsN = sizeof(kFramexaxisAttrs) / sizeof(kFramexaxisAttrs[0]); + +// frameyaxis (mjsSensor) +inline constexpr mjXAttr kFrameyaxisAttrs[] = { + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, type), nullptr, 0, mjSENS_FRAMEYAXIS}, + {"objtype", mjXAttr::kEnum, 1, true, true, false, false, (int)offsetof(mjsSensor, objtype), frameobj_map, frameobj_sz}, + {"objname", mjXAttr::kString, 1, true, true, false, false, (int)offsetof(mjsSensor, objname)}, + {"reftype", mjXAttr::kEnum, 1, true, false, false, false, (int)offsetof(mjsSensor, reftype), frameobj_map, frameobj_sz}, + {"refname", mjXAttr::kString, 1, true, false, false, false, (int)offsetof(mjsSensor, refname)}, +}; +inline constexpr int kFrameyaxisAttrsN = sizeof(kFrameyaxisAttrs) / sizeof(kFrameyaxisAttrs[0]); + +// framezaxis (mjsSensor) +inline constexpr mjXAttr kFramezaxisAttrs[] = { + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, type), nullptr, 0, mjSENS_FRAMEZAXIS}, + {"objtype", mjXAttr::kEnum, 1, true, true, false, false, (int)offsetof(mjsSensor, objtype), frameobj_map, frameobj_sz}, + {"objname", mjXAttr::kString, 1, true, true, false, false, (int)offsetof(mjsSensor, objname)}, + {"reftype", mjXAttr::kEnum, 1, true, false, false, false, (int)offsetof(mjsSensor, reftype), frameobj_map, frameobj_sz}, + {"refname", mjXAttr::kString, 1, true, false, false, false, (int)offsetof(mjsSensor, refname)}, +}; +inline constexpr int kFramezaxisAttrsN = sizeof(kFramezaxisAttrs) / sizeof(kFramezaxisAttrs[0]); + +// framelinvel (mjsSensor) +inline constexpr mjXAttr kFramelinvelAttrs[] = { + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, type), nullptr, 0, mjSENS_FRAMELINVEL}, + {"objtype", mjXAttr::kEnum, 1, true, true, false, false, (int)offsetof(mjsSensor, objtype), frameobj_map, frameobj_sz}, + {"objname", mjXAttr::kString, 1, true, true, false, false, (int)offsetof(mjsSensor, objname)}, + {"reftype", mjXAttr::kEnum, 1, true, false, false, false, (int)offsetof(mjsSensor, reftype), frameobj_map, frameobj_sz}, + {"refname", mjXAttr::kString, 1, true, false, false, false, (int)offsetof(mjsSensor, refname)}, +}; +inline constexpr int kFramelinvelAttrsN = sizeof(kFramelinvelAttrs) / sizeof(kFramelinvelAttrs[0]); + +// frameangvel (mjsSensor) +inline constexpr mjXAttr kFrameangvelAttrs[] = { + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, type), nullptr, 0, mjSENS_FRAMEANGVEL}, + {"objtype", mjXAttr::kEnum, 1, true, true, false, false, (int)offsetof(mjsSensor, objtype), frameobj_map, frameobj_sz}, + {"objname", mjXAttr::kString, 1, true, true, false, false, (int)offsetof(mjsSensor, objname)}, + {"reftype", mjXAttr::kEnum, 1, true, false, false, false, (int)offsetof(mjsSensor, reftype), frameobj_map, frameobj_sz}, + {"refname", mjXAttr::kString, 1, true, false, false, false, (int)offsetof(mjsSensor, refname)}, +}; +inline constexpr int kFrameangvelAttrsN = sizeof(kFrameangvelAttrs) / sizeof(kFrameangvelAttrs[0]); + +// framelinacc (mjsSensor) +inline constexpr mjXAttr kFramelinaccAttrs[] = { + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, type), nullptr, 0, mjSENS_FRAMELINACC}, + {"objtype", mjXAttr::kEnum, 1, true, true, false, false, (int)offsetof(mjsSensor, objtype), frameobj_map, frameobj_sz}, + {"objname", mjXAttr::kString, 1, true, true, false, false, (int)offsetof(mjsSensor, objname)}, +}; +inline constexpr int kFramelinaccAttrsN = sizeof(kFramelinaccAttrs) / sizeof(kFramelinaccAttrs[0]); + +// frameangacc (mjsSensor) +inline constexpr mjXAttr kFrameangaccAttrs[] = { + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, type), nullptr, 0, mjSENS_FRAMEANGACC}, + {"objtype", mjXAttr::kEnum, 1, true, true, false, false, (int)offsetof(mjsSensor, objtype), frameobj_map, frameobj_sz}, + {"objname", mjXAttr::kString, 1, true, true, false, false, (int)offsetof(mjsSensor, objname)}, +}; +inline constexpr int kFrameangaccAttrsN = sizeof(kFrameangaccAttrs) / sizeof(kFrameangaccAttrs[0]); + +// subtreecom (mjsSensor) +inline constexpr mjXAttr kSubtreecomAttrs[] = { + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, type), nullptr, 0, mjSENS_SUBTREECOM}, + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, objtype), nullptr, 0, mjOBJ_BODY}, + {"body", mjXAttr::kString, 1, true, true, false, false, (int)offsetof(mjsSensor, objname)}, +}; +inline constexpr int kSubtreecomAttrsN = sizeof(kSubtreecomAttrs) / sizeof(kSubtreecomAttrs[0]); + +// subtreelinvel (mjsSensor) +inline constexpr mjXAttr kSubtreelinvelAttrs[] = { + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, type), nullptr, 0, mjSENS_SUBTREELINVEL}, + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, objtype), nullptr, 0, mjOBJ_BODY}, + {"body", mjXAttr::kString, 1, true, true, false, false, (int)offsetof(mjsSensor, objname)}, +}; +inline constexpr int kSubtreelinvelAttrsN = sizeof(kSubtreelinvelAttrs) / sizeof(kSubtreelinvelAttrs[0]); + +// subtreeangmom (mjsSensor) +inline constexpr mjXAttr kSubtreeangmomAttrs[] = { + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, type), nullptr, 0, mjSENS_SUBTREEANGMOM}, + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, objtype), nullptr, 0, mjOBJ_BODY}, + {"body", mjXAttr::kString, 1, true, true, false, false, (int)offsetof(mjsSensor, objname)}, +}; +inline constexpr int kSubtreeangmomAttrsN = sizeof(kSubtreeangmomAttrs) / sizeof(kSubtreeangmomAttrs[0]); + +// insidesite (mjsSensor) +inline constexpr mjXAttr kInsidesiteAttrs[] = { + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, type), nullptr, 0, mjSENS_INSIDESITE}, + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, reftype), nullptr, 0, mjOBJ_SITE}, + {"site", mjXAttr::kString, 1, true, true, false, false, (int)offsetof(mjsSensor, refname)}, + {"objtype", mjXAttr::kEnum, 1, true, true, false, false, (int)offsetof(mjsSensor, objtype), frameobj_map, frameobj_sz}, + {"objname", mjXAttr::kString, 1, true, true, false, false, (int)offsetof(mjsSensor, objname)}, +}; +inline constexpr int kInsidesiteAttrsN = sizeof(kInsidesiteAttrs) / sizeof(kInsidesiteAttrs[0]); + +// e_potential (mjsSensor) +inline constexpr mjXAttr kE_potentialAttrs[] = { + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, type), nullptr, 0, mjSENS_E_POTENTIAL}, + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, objtype), nullptr, 0, mjOBJ_UNKNOWN}, +}; +inline constexpr int kE_potentialAttrsN = sizeof(kE_potentialAttrs) / sizeof(kE_potentialAttrs[0]); + +// e_kinetic (mjsSensor) +inline constexpr mjXAttr kE_kineticAttrs[] = { + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, type), nullptr, 0, mjSENS_E_KINETIC}, + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, objtype), nullptr, 0, mjOBJ_UNKNOWN}, +}; +inline constexpr int kE_kineticAttrsN = sizeof(kE_kineticAttrs) / sizeof(kE_kineticAttrs[0]); + +// clock (mjsSensor) +inline constexpr mjXAttr kClockAttrs[] = { + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, type), nullptr, 0, mjSENS_CLOCK}, + {nullptr, mjXAttr::kConst, 1, true, false, false, false, (int)offsetof(mjsSensor, objtype), nullptr, 0, mjOBJ_UNKNOWN}, +}; +inline constexpr int kClockAttrsN = sizeof(kClockAttrs) / sizeof(kClockAttrs[0]); + +// key (mjsKey) +inline constexpr mjXAttr kKeyAttrs[] = { + {"time", mjXAttr::kDouble, 1, true, false, false, false, (int)offsetof(mjsKey, time)}, + {"qpos", mjXAttr::kDoubleVec, 1, true, false, false, false, (int)offsetof(mjsKey, qpos)}, + {"qvel", mjXAttr::kDoubleVec, 1, true, false, false, false, (int)offsetof(mjsKey, qvel)}, + {"act", mjXAttr::kDoubleVec, 1, true, false, false, false, (int)offsetof(mjsKey, act)}, + {"mpos", mjXAttr::kDoubleVec, 1, true, false, false, false, (int)offsetof(mjsKey, mpos)}, + {"mquat", mjXAttr::kDoubleVec, 1, true, false, false, false, (int)offsetof(mjsKey, mquat)}, + {"ctrl", mjXAttr::kDoubleVec, 1, true, false, false, false, (int)offsetof(mjsKey, ctrl)}, +}; +inline constexpr int kKeyAttrsN = sizeof(kKeyAttrs) / sizeof(kKeyAttrs[0]); + +// sensors fully described by the schema: dispatch by tag +struct mjXSensorEntry { const char* tag; const mjXAttr* rows; int n; }; +inline constexpr mjXSensorEntry kSensorDispatch[] = { + {"touch", kTouchAttrs, kTouchAttrsN}, + {"accelerometer", kAccelerometerAttrs, kAccelerometerAttrsN}, + {"velocimeter", kVelocimeterAttrs, kVelocimeterAttrsN}, + {"gyro", kGyroAttrs, kGyroAttrsN}, + {"force", kForceAttrs, kForceAttrsN}, + {"torque", kTorqueAttrs, kTorqueAttrsN}, + {"magnetometer", kMagnetometerAttrs, kMagnetometerAttrsN}, + {"camprojection", kCamprojectionAttrs, kCamprojectionAttrsN}, + {"jointpos", kJointposAttrs, kJointposAttrsN}, + {"jointvel", kJointvelAttrs, kJointvelAttrsN}, + {"tendonpos", kTendonposAttrs, kTendonposAttrsN}, + {"tendonvel", kTendonvelAttrs, kTendonvelAttrsN}, + {"actuatorpos", kActuatorposAttrs, kActuatorposAttrsN}, + {"actuatorvel", kActuatorvelAttrs, kActuatorvelAttrsN}, + {"actuatorfrc", kActuatorfrcAttrs, kActuatorfrcAttrsN}, + {"jointactuatorfrc", kJointactuatorfrcAttrs, kJointactuatorfrcAttrsN}, + {"tendonactuatorfrc", kTendonactuatorfrcAttrs, kTendonactuatorfrcAttrsN}, + {"ballquat", kBallquatAttrs, kBallquatAttrsN}, + {"ballangvel", kBallangvelAttrs, kBallangvelAttrsN}, + {"jointlimitpos", kJointlimitposAttrs, kJointlimitposAttrsN}, + {"jointlimitvel", kJointlimitvelAttrs, kJointlimitvelAttrsN}, + {"jointlimitfrc", kJointlimitfrcAttrs, kJointlimitfrcAttrsN}, + {"tendonlimitpos", kTendonlimitposAttrs, kTendonlimitposAttrsN}, + {"tendonlimitvel", kTendonlimitvelAttrs, kTendonlimitvelAttrsN}, + {"tendonlimitfrc", kTendonlimitfrcAttrs, kTendonlimitfrcAttrsN}, + {"framepos", kFrameposAttrs, kFrameposAttrsN}, + {"framequat", kFramequatAttrs, kFramequatAttrsN}, + {"framexaxis", kFramexaxisAttrs, kFramexaxisAttrsN}, + {"frameyaxis", kFrameyaxisAttrs, kFrameyaxisAttrsN}, + {"framezaxis", kFramezaxisAttrs, kFramezaxisAttrsN}, + {"framelinvel", kFramelinvelAttrs, kFramelinvelAttrsN}, + {"frameangvel", kFrameangvelAttrs, kFrameangvelAttrsN}, + {"framelinacc", kFramelinaccAttrs, kFramelinaccAttrsN}, + {"frameangacc", kFrameangaccAttrs, kFrameangaccAttrsN}, + {"insidesite", kInsidesiteAttrs, kInsidesiteAttrsN}, + {"subtreecom", kSubtreecomAttrs, kSubtreecomAttrsN}, + {"subtreelinvel", kSubtreelinvelAttrs, kSubtreelinvelAttrsN}, + {"subtreeangmom", kSubtreeangmomAttrs, kSubtreeangmomAttrsN}, + {"e_potential", kE_potentialAttrs, kE_potentialAttrsN}, + {"e_kinetic", kE_kineticAttrs, kE_kineticAttrsN}, + {"clock", kClockAttrs, kClockAttrsN}, +}; +inline constexpr int kSensorDispatchN = sizeof(kSensorDispatch) / sizeof(kSensorDispatch[0]); + +// group equality_base (mjsEquality) +inline constexpr mjXAttr kEqualityBaseAttrs[] = { + {"name", mjXAttr::kName, 1, true, false, true, false, -1}, + {"active", mjXAttr::kBool, 1, true, false, false, false, (int)offsetof(mjsEquality, active)}, + {"solref", mjXAttr::kNum, mjNREF, false, false, false, false, (int)offsetof(mjsEquality, solref)}, + {"solimp", mjXAttr::kNum, mjNIMP, false, false, false, false, (int)offsetof(mjsEquality, solimp)}, +}; +inline constexpr int kEqualityBaseAttrsN = sizeof(kEqualityBaseAttrs) / sizeof(kEqualityBaseAttrs[0]); + +// group sensor_base (mjsSensor) +inline constexpr mjXAttr kSensorBaseAttrs[] = { + {"name", mjXAttr::kName, 1, true, false, true, false, -1}, + {"nsample", mjXAttr::kInt, 1, true, false, false, false, (int)offsetof(mjsSensor, nsample)}, + {"interp", mjXAttr::kEnum, 1, true, false, false, false, (int)offsetof(mjsSensor, interp), interp_map, interp_sz}, + {"delay", mjXAttr::kDouble, 1, true, false, false, false, (int)offsetof(mjsSensor, delay)}, + {"interval", mjXAttr::kDouble, 2, false, false, false, false, (int)offsetof(mjsSensor, interval)}, + {"cutoff", mjXAttr::kDouble, 1, true, false, false, false, (int)offsetof(mjsSensor, cutoff)}, + {"noise", mjXAttr::kDouble, 1, true, false, false, false, (int)offsetof(mjsSensor, noise)}, + {"user", mjXAttr::kDoubleVec, 1, true, false, false, false, (int)offsetof(mjsSensor, userdata)}, +}; +inline constexpr int kSensorBaseAttrsN = sizeof(kSensorBaseAttrs) / sizeof(kSensorBaseAttrs[0]); + +// clang-format on diff --git a/src/xml/xml_base.h b/src/xml/xml_base.h index 2bef9a20..a6bd8e52 100644 --- a/src/xml/xml_base.h +++ b/src/xml/xml_base.h @@ -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_ diff --git a/src/xml/xml_native_reader.cc b/src/xml/xml_native_reader.cc index 4e85a412..d4bfab06 100644 --- a/src/xml/xml_native_reader.cc +++ b/src/xml/xml_native_reader.cc @@ -167,510 +167,13 @@ static void UpdateString(string& psuffix, int count, int i) { //---------------------------------- MJCF schema --------------------------------------------------- #include "mjcf_table.inc" -//---------------------------------- MJCF keywords used in attributes ------------------------------ - -// coordinate type -const mjMap coordinate_map[2] = { - {"local", 0}, - {"global", 1} -}; - - -// angle type -const mjMap angle_map[2] = { - {"radian", 0}, - {"degree", 1} -}; - - -// bool type -const mjMap bool_map[2] = { - {"false", 0}, - {"true", 1} -}; - - -// fluidshape type -const mjMap fluid_map[2] = { - {"none", 0}, - {"ellipsoid", 1} -}; - - -// enable type -const mjMap enable_map[2] = { - {"disable", 0}, - {"enable", 1} -}; - - -// TFAuto type -const mjMap TFAuto_map[3] = { - {"false", 0}, - {"true", 1}, - {"auto", 2} -}; - - -// FAuto type -const mjMap FAuto_map[2] = { - {"false", 0}, - {"auto", 1} -}; - - -// body sleep type -const int bodysleep_sz = 4; -const mjMap bodysleep_map[bodysleep_sz] = { - {"auto", mjSLEEP_AUTO}, - {"never", mjSLEEP_NEVER}, - {"allowed", mjSLEEP_ALLOWED}, - {"init", mjSLEEP_INIT} -}; - -// joint type -const int joint_sz = 4; -const mjMap joint_map[joint_sz] = { - {"free", mjJNT_FREE}, - {"ball", mjJNT_BALL}, - {"slide", mjJNT_SLIDE}, - {"hinge", mjJNT_HINGE} -}; - - -// geom type -const mjMap geom_map[mjNGEOMTYPES] = { - {"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} -}; - - -// projection type -const int projection_sz = 2; -const mjMap projection_map[projection_sz] = { - {"perspective", mjPROJ_PERSPECTIVE}, - {"orthographic", mjPROJ_ORTHOGRAPHIC} -}; - -// camlight type -const int camlight_sz = 5; -const mjMap camlight_map[camlight_sz] = { - {"fixed", mjCAMLIGHT_FIXED}, - {"track", mjCAMLIGHT_TRACK}, - {"trackcom", mjCAMLIGHT_TRACKCOM}, - {"targetbody", mjCAMLIGHT_TARGETBODY}, - {"targetbodycom", mjCAMLIGHT_TARGETBODYCOM} -}; - - -// light type -const int lighttype_sz = 4; -const mjMap lighttype_map[lighttype_sz] = { - {"spot", mjLIGHT_SPOT}, - {"directional", mjLIGHT_DIRECTIONAL}, - {"point", mjLIGHT_POINT}, - {"image", mjLIGHT_IMAGE} -}; - - -// texmat role type -const int texrole_sz = mjNTEXROLE - 1; -const mjMap texrole_map[texrole_sz] = { - {"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}, -}; - - -// integrator type -const int integrator_sz = 4; -const mjMap integrator_map[integrator_sz] = { - {"Euler", mjINT_EULER}, - {"RK4", mjINT_RK4}, - {"implicit", mjINT_IMPLICIT}, - {"implicitfast", mjINT_IMPLICITFAST} -}; - - -// cone type -const int cone_sz = 2; -const mjMap cone_map[cone_sz] = { - {"pyramidal", mjCONE_PYRAMIDAL}, - {"elliptic", mjCONE_ELLIPTIC} -}; - - -// Jacobian type -const int jac_sz = 3; -const mjMap jac_map[jac_sz] = { - {"dense", mjJAC_DENSE}, - {"sparse", mjJAC_SPARSE}, - {"auto", mjJAC_AUTO} -}; - - -// solver type -const int solver_sz = 3; -const mjMap solver_map[solver_sz] = { - {"PGS", mjSOL_PGS}, - {"CG", mjSOL_CG}, - {"Newton", mjSOL_NEWTON} -}; - - -// constraint type -const int equality_sz = 8; -const mjMap equality_map[equality_sz] = { - {"connect", mjEQ_CONNECT}, - {"weld", mjEQ_WELD}, - {"joint", mjEQ_JOINT}, - {"tendon", mjEQ_TENDON}, - {"flex", mjEQ_FLEX}, - {"flexvert", mjEQ_FLEXVERT}, - {"flexstrain", mjEQ_FLEXSTRAIN}, - {"distance", mjEQ_DISTANCE} -}; - - -// type for texture -const int texture_sz = 3; -const mjMap texture_map[texture_sz] = { - {"2d", mjTEXTURE_2D}, - {"cube", mjTEXTURE_CUBE}, - {"skybox", mjTEXTURE_SKYBOX} -}; - - -// colorspace for texture -const int colorspace_sz = 3; -const mjMap colorspace_map[colorspace_sz] = { - {"auto", mjCOLORSPACE_AUTO}, - {"linear", mjCOLORSPACE_LINEAR}, - {"sRGB", mjCOLORSPACE_SRGB} -}; - - -// builtin type for texture -const int builtin_sz = 4; -const mjMap builtin_map[builtin_sz] = { - {"none", mjBUILTIN_NONE}, - {"gradient", mjBUILTIN_GRADIENT}, - {"checker", mjBUILTIN_CHECKER}, - {"flat", mjBUILTIN_FLAT} -}; - - -// mark type for texture -const int mark_sz = 4; -const mjMap mark_map[mark_sz] = { - {"none", mjMARK_NONE}, - {"edge", mjMARK_EDGE}, - {"cross", mjMARK_CROSS}, - {"random", mjMARK_RANDOM} -}; - - -// dyn type -const int dyn_sz = 8; -const mjMap dyn_map[dyn_sz] = { - {"none", mjDYN_NONE}, - {"integrator", mjDYN_INTEGRATOR}, - {"filter", mjDYN_FILTER}, - {"filterexact", mjDYN_FILTEREXACT}, - {"muscle", mjDYN_MUSCLE}, - {"dcmotor", mjDYN_DCMOTOR}, - {"pid", mjDYN_PID}, - {"user", mjDYN_USER} -}; - - -// dcmotor controller input mode -const int dcmotorinput_sz = 3; -const mjMap dcmotorinput_map[dcmotorinput_sz] = { - {"voltage", 0}, - {"position", 1}, - {"velocity", 2} -}; - - -// gain type -const int gain_sz = 7; -const mjMap gain_map[gain_sz] = { - {"fixed", mjGAIN_FIXED}, - {"affine", mjGAIN_AFFINE}, - {"muscle", mjGAIN_MUSCLE}, - {"dcmotor", mjGAIN_DCMOTOR}, - {"so3", mjGAIN_SO3}, - {"pid", mjGAIN_PID}, - {"user", mjGAIN_USER} -}; - - -// so3 input chart -const int inputchart_sz = 2; -const mjMap inputchart_map[inputchart_sz] = { - {"expmap", mjCHART_EXPMAP}, - {"quat", mjCHART_QUAT} -}; - - -// servo-family input tokens -const int inputbit_sz = 3; -const mjMap inputbit_map[inputbit_sz] = { - {"pos", mjINPUT_POS}, - {"vel", mjINPUT_VEL}, - {"ff", mjINPUT_FF} -}; - - -// read the "input" attribute: so3 chart keyword, or servo input token list -static bool ReadInputSpec(tinyxml2::XMLElement* elem, int* ctrlspec) { - std::string text; - if (!mjXUtil::ReadAttrTxt(elem, "input", text)) { - return false; - } - - // so3 chart keyword - int chart = mjXUtil::FindKey(inputchart_map, inputchart_sz, text); - if (chart >= 0) { - *ctrlspec = chart; - return true; - } - - // servo input tokens - int bits[inputbit_sz]; - int nbit = mjXUtil::MapValues(elem, "input", bits, inputbit_map, inputbit_sz); - int spec = 0; - for (int k=0; k < nbit; k++) { - spec |= bits[k]; - } - *ctrlspec = spec; - return true; -} - - -// bias type -const int bias_sz = 6; -const mjMap bias_map[bias_sz] = { - {"none", mjBIAS_NONE}, - {"affine", mjBIAS_AFFINE}, - {"muscle", mjBIAS_MUSCLE}, - {"dcmotor", mjBIAS_DCMOTOR}, - {"so3", mjBIAS_SO3}, - {"user", mjBIAS_USER} -}; - - -// interpolation type -const int interp_sz = 3; -const mjMap interp_map[interp_sz] = { - {"zoh", 0}, - {"linear", 1}, - {"cubic", 2} -}; - - -// stage type -const int stage_sz = 4; -const mjMap stage_map[stage_sz] = { - {"none", mjSTAGE_NONE}, - {"pos", mjSTAGE_POS}, - {"vel", mjSTAGE_VEL}, - {"acc", mjSTAGE_ACC} -}; - - -// data type -const int datatype_sz = 4; -const mjMap datatype_map[datatype_sz] = { - {"real", mjDATATYPE_REAL}, - {"positive", mjDATATYPE_POSITIVE}, - {"axis", mjDATATYPE_AXIS}, - {"quaternion", mjDATATYPE_QUATERNION} -}; - - -// contact data type -const mjMap condata_map[mjNCONDATA] = { - {"found", mjCONDATA_FOUND}, - {"force", mjCONDATA_FORCE}, - {"torque", mjCONDATA_TORQUE}, - {"dist", mjCONDATA_DIST}, - {"pos", mjCONDATA_POS}, - {"normal", mjCONDATA_NORMAL}, - {"tangent", mjCONDATA_TANGENT} -}; - - -// rangefinder data type -const mjMap raydata_map[mjNRAYDATA] = { - {"dist", mjRAYDATA_DIST}, - {"dir", mjRAYDATA_DIR}, - {"origin", mjRAYDATA_ORIGIN}, - {"point", mjRAYDATA_POINT}, - {"normal", mjRAYDATA_NORMAL}, - {"depth", mjRAYDATA_DEPTH} -}; - -// camera output type -const int camout_sz = mjNCAMOUT; -const mjMap camout_map[mjNCAMOUT] = {{"rgb", mjCAMOUT_RGB}, - {"depth", mjCAMOUT_DEPTH}, - {"distance", mjCAMOUT_DIST}, - {"normal", mjCAMOUT_NORMAL}, - {"segmentation", mjCAMOUT_SEG}}; - -// contact reduction type -const int reduce_sz = 4; -const mjMap reduce_map[reduce_sz] = { - {"none", 0}, - {"mindist", 1}, - {"maxforce", 2}, - {"netforce", 3} -}; - -// conflict resolution type -const int conflict_sz = 3; -const mjMap conflict_map[conflict_sz] = {{"warning", mjCONFLICT_WARNING}, - {"merge", mjCONFLICT_MERGE}, - {"error", mjCONFLICT_ERROR}}; - -// LR mode -const int lrmode_sz = 4; -const mjMap lrmode_map[lrmode_sz] = { - {"none", mjLRMODE_NONE}, - {"muscle", mjLRMODE_MUSCLE}, - {"muscleuser", mjLRMODE_MUSCLEUSER}, - {"all", mjLRMODE_ALL} -}; - - -// composite type -const mjMap comp_map[mjNCOMPTYPES] = { - {"particle", mjCOMPTYPE_PARTICLE}, - {"grid", mjCOMPTYPE_GRID}, - {"rope", mjCOMPTYPE_ROPE}, - {"loop", mjCOMPTYPE_LOOP}, - {"cable", mjCOMPTYPE_CABLE}, - {"cloth", mjCOMPTYPE_CLOTH} -}; - - -// composite joint kind -const mjMap jkind_map[1] = { - {"main", mjCOMPKIND_JOINT} -}; - - -// composite rope shape -const mjMap shape_map[mjNCOMPSHAPES] = { - {"s", mjCOMPSHAPE_LINE}, - {"cos(s)", mjCOMPSHAPE_COS}, - {"sin(s)", mjCOMPSHAPE_SIN}, - {"0", mjCOMPSHAPE_ZERO} -}; - - -// mesh type -const mjMap meshtype_map[2] = { - {"false", mjINERTIA_VOLUME}, - {"true", mjINERTIA_SHELL}, -}; - - -// mesh inertia type -const mjMap meshinertia_map[4] = { - {"convex", mjMESH_INERTIA_CONVEX}, - {"legacy", mjMESH_INERTIA_LEGACY}, - {"exact", mjMESH_INERTIA_EXACT}, - {"shell", mjMESH_INERTIA_SHELL} -}; - - -// mesh builtin type -const int meshbuiltin_sz = 8; -const mjMap meshbuiltin_map[meshbuiltin_sz] = { - {"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} -}; - - -// flexcomp type -const mjMap fcomp_map[mjNFCOMPTYPES] = { - {"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} -}; - - -// flexcomp dof type -const mjMap fdof_map[mjNFCOMPDOFS] = { - {"full", mjFCOMPDOF_FULL}, - {"radial", mjFCOMPDOF_RADIAL}, - {"trilinear", mjFCOMPDOF_TRILINEAR}, - {"quadratic", mjFCOMPDOF_QUADRATIC}, - {"2d", mjFCOMPDOF_2D} -}; - - -// flex selfcollide type -const mjMap flexself_map[5] = { - {"none", mjFLEXSELF_NONE}, - {"narrow", mjFLEXSELF_NARROW}, - {"bvh", mjFLEXSELF_BVH}, - {"sap", mjFLEXSELF_SAP}, - {"auto", mjFLEXSELF_AUTO}, -}; - - -// flex elastic 2d type -const mjMap elastic2d_map[5] = { - {"none", 0}, - {"bend", 1}, - {"stretch", 2}, - {"both", 3}, -}; - - -// flex equality type -const mjMap flexeq_map[4] = { - {"false", 0}, - {"true", 1}, - {"vert", 2}, - {"strain", 3}, -}; - - - //---------------------------------- class mjXReader implementation -------------------------------- -// constructor +// typed attribute rows, generated from mjcf.schema; the keyword maps the +// rows reference are generated into mjcf_map.h +#include "mjcf_read_table.inc" + + mjXReader::mjXReader() : schema(MJCF, nMJCF, MJCF_constraints, nMJCF_constraints) { readingdefaults = false; @@ -820,69 +323,29 @@ void mjXReader::Parse(XMLElement* root, const mjVFS* vfs) { void mjXReader::Compiler(XMLElement* section, mjSpec* s) { Reader read(section, s); - // top-level attributes - read("autolimits", s->compiler.autolimits, bool_map, 2); - read("boundmass", 1, s->compiler.boundmass); - read("boundinertia", 1, s->compiler.boundinertia); - read("settotalmass", 1, s->compiler.settotalmass); - read("balanceinertia", s->compiler.balanceinertia, bool_map, 2); + // strippath is stored on the spec, not the compiler options read("strippath", s->strippath, bool_map, 2); - read("fitaabb", s->compiler.fitaabb, bool_map, 2); + + // global coordinates are no longer supported if (int n = 0; MapValue(section, "coordinate", &n, coordinate_map, 2)) { if (n == 1) { throw mjXError(section, "global coordinates no longer supported. To convert existing models, " "load and save them in MuJoCo 2.3.3 or older"); } } - read("angle", s->compiler.degree, angle_map, 2); - if (ReadAttrTxt(section, "eulerseq", read.text_)) { - if (read.text_.size() != 3) { - throw mjXError(section, "euler format must have length 3"); - } - memcpy(s->compiler.eulerseq, read.text_.c_str(), 3); - mjs_setAuthored(s, &s->compiler.eulerseq, 1); - } if (ReadAttrTxt(section, "assetdir", read.text_)) { mjs_setString(s->compiler.meshdir, read.text_.c_str()); mjs_setString(s->compiler.texturedir, read.text_.c_str()); } - // meshdir and texturedir take precedence over assetdir - read("meshdir", s->compiler.meshdir); - read("texturedir", s->compiler.texturedir); - read("discardvisual", s->compiler.discardvisual, bool_map, 2); - read("usethread", s->compiler.usethread, bool_map, 2); - read("fusestatic", s->compiler.fusestatic, bool_map, 2); - read("inertiafromgeom", s->compiler.inertiafromgeom, TFAuto_map, 3); - read("inertiagrouprange", 2, s->compiler.inertiagrouprange); - read("alignfree", s->compiler.alignfree, bool_map, 2); - read("saveinertial", s->compiler.saveinertial, bool_map, 2); - MapValue(section, "conflict", &s->compiler.conflict, conflict_map, - conflict_sz); + // mechanical attributes; meshdir/texturedir override assetdir above + ReadAttrTableCore(section, &s->compiler, kCompilerAttrs, kCompilerAttrsN, + /*skipnodefault=*/false, /*authored=*/s); // lengthrange subelement XMLElement* elem = FindSubElem(section, "lengthrange"); if (elem) { - int n; - mjLROpt* opt = &(s->compiler.LRopt); - - // flags - MapValue(elem, "mode", &opt->mode, lrmode_map, lrmode_sz); - if (MapValue(elem, "useexisting", &n, bool_map, 2)) { - opt->useexisting = (n == 1); - } - if (MapValue(elem, "uselimit", &n, bool_map, 2)) { - opt->uselimit = (n == 1); - } - - // algorithm parameters - string text; - ReadAttr(elem, "accel", 1, &opt->accel, text); - ReadAttr(elem, "maxforce", 1, &opt->maxforce, text); - ReadAttr(elem, "timeconst", 1, &opt->timeconst, text); - ReadAttr(elem, "timestep", 1, &opt->timestep, text); - ReadAttr(elem, "inttotal", 1, &opt->inttotal, text); - ReadAttr(elem, "interval", 1, &opt->interval, text); - ReadAttr(elem, "tolrange", 1, &opt->tolrange, text); + ReadAttrTableCore(elem, &(s->compiler.LRopt), kLengthrangeAttrs, + kLengthrangeAttrsN, /*skipnodefault=*/false); } } @@ -892,35 +355,9 @@ void mjXReader::Compiler(XMLElement* section, mjSpec* s) { void mjXReader::Option(XMLElement* section, mjSpec* s, mjOption* opt) { Reader read(section, s); - // read options - read("timestep", 1, opt->timestep); - read("impratio", 1, opt->impratio); - read("tolerance", 1, opt->tolerance); - read("ls_tolerance", 1, opt->ls_tolerance); - read("noslip_tolerance", 1, opt->noslip_tolerance); - read("ccd_tolerance", 1, opt->ccd_tolerance); - read("sleep_tolerance", 1, opt->sleep_tolerance); - read("gravity", 3, opt->gravity); - read("wind", 3, opt->wind); - read("magnetic", 3, opt->magnetic); - read("density", 1, opt->density); - read("viscosity", 1, opt->viscosity); - - read("o_margin", 1, opt->o_margin); - read("o_solref", mjNREF, opt->o_solref, false, false); - read("o_solimp", mjNIMP, opt->o_solimp, false, false); - read("o_friction", 5, opt->o_friction, false, false); - - read("integrator", opt->integrator, integrator_map, integrator_sz); - read("cone", opt->cone, cone_map, cone_sz); - read("jacobian", opt->jacobian, jac_map, jac_sz); - read("solver", opt->solver, solver_map, solver_sz); - read("iterations", opt->iterations); - read("ls_iterations", opt->ls_iterations); - read("noslip_iterations", opt->noslip_iterations); - read("ccd_iterations", opt->ccd_iterations); - read("sdf_iterations", opt->sdf_iterations); - read("sdf_initpoints", opt->sdf_initpoints); + // mechanical attributes + ReadAttrTableCore(section, opt, kOptionAttrs, kOptionAttrsN, + /*skipnodefault=*/false, /*authored=*/s); // actuatorgroupdisable constexpr int num_bitflags = 31; @@ -1085,9 +522,9 @@ void mjXReader::Size(XMLElement* section, mjSpec* s) { } } - // read sizes - ReadAttrInt(section, "nuserdata", &s->nuserdata); - ReadAttrInt(section, "nkey", &s->nkey); + // mechanical attributes + ReadAttrTableCore(section, s, kSizeAttrs, kSizeAttrsN, + /*skipnodefault=*/false); ReadAttrInt(section, "nconmax", &s->nconmax); if (s->nconmax < -1) throw mjXError(section, "nconmax must be >= -1"); @@ -1104,28 +541,20 @@ void mjXReader::Size(XMLElement* section, mjSpec* s) { if (s->njmax < -1) throw mjXError(section, "njmax must be >= -1"); } - ReadAttrInt(section, "nuser_body", &s->nuser_body); if (s->nuser_body < -1) throw mjXError(section, "nuser_body must be >= -1"); - ReadAttrInt(section, "nuser_jnt", &s->nuser_jnt); if (s->nuser_jnt < -1) throw mjXError(section, "nuser_jnt must be >= -1"); - ReadAttrInt(section, "nuser_geom", &s->nuser_geom); if (s->nuser_geom < -1) throw mjXError(section, "nuser_geom must be >= -1"); - ReadAttrInt(section, "nuser_site", &s->nuser_site); if (s->nuser_site < -1) throw mjXError(section, "nuser_site must be >= -1"); - ReadAttrInt(section, "nuser_cam", &s->nuser_cam); if (s->nuser_cam < -1) throw mjXError(section, "nuser_cam must be >= -1"); - ReadAttrInt(section, "nuser_tendon", &s->nuser_tendon); if (s->nuser_tendon < -1) throw mjXError(section, "nuser_tendon must be >= -1"); - ReadAttrInt(section, "nuser_actuator", &s->nuser_actuator); if (s->nuser_actuator < -1) throw mjXError(section, "nuser_actuator must be >= -1"); - ReadAttrInt(section, "nuser_sensor", &s->nuser_sensor); if (s->nuser_sensor < -1) throw mjXError(section, "nuser_sensor must be >= -1"); } @@ -1133,17 +562,12 @@ void mjXReader::Size(XMLElement* section, mjSpec* s) { // statistic section parser void mjXReader::Statistic(XMLElement* section) { - string text; - - // read statistics - ReadAttr(section, "meaninertia", 1, &spec->stat.meaninertia, text); - ReadAttr(section, "meanmass", 1, &spec->stat.meanmass, text); - ReadAttr(section, "meansize", 1, &spec->stat.meansize, text); - ReadAttr(section, "extent", 1, &spec->stat.extent, text); + // mechanical attributes + ReadAttrTableCore(section, &spec->stat, kStatisticAttrs, kStatisticAttrsN, + /*skipnodefault=*/false); if (mjuu_defined(spec->stat.extent) && spec->stat.extent <= 0) { throw mjXError(section, "extent must be strictly positive"); } - ReadAttr(section, "center", 3, spec->stat.center, text); } @@ -1152,99 +576,40 @@ void mjXReader::Statistic(XMLElement* section) { // flex element parser void mjXReader::OneFlex(XMLElement* elem, mjsFlex* flex) { - string text, name, material, nodebody; + string text; int n; - // read attributes - if (ReadAttrTxt(elem, "name", name)) { - if (mjs_setName(flex->element, name.c_str())) { - throw mjXError(elem, "%s", mjs_getError(spec)); - } - } - if (ReadAttrTxt(elem, "material", material)) { - mjs_setString(flex->material, material.c_str()); - } - - ReadAttr(elem, "radius", 1, &flex->radius, text); - ReadAttr(elem, "rgba", 4, flex->rgba, text); - if (MapValue(elem, "flatskin", &n, bool_map, 2)) { - flex->flatskin = (n == 1); - } - ReadAttrInt(elem, "dim", &flex->dim); - ReadAttrInt(elem, "group", &flex->group); + // mechanical attributes + ReadAttrTable(elem, flex, flex->element, kFlexAttrs, + kFlexAttrsN); + // cellcount is seeded before reading flex->cellcount[0] = 1; flex->cellcount[1] = 1; flex->cellcount[2] = 1; ReadAttr(elem, "cellcount", 3, flex->cellcount, text); + // dof lowers to interpolation order flex->order = 0; if (MapValue(elem, "dof", &n, fdof_map, mjNFCOMPDOFS)) { flex->order = (n == mjFCOMPDOF_QUADRATIC) ? 2 : (n == mjFCOMPDOF_TRILINEAR ? 1 : 0); } - // read data vectors - if (ReadAttrTxt(elem, "body", text, true)) { - mjs_setStringVec(flex->vertbody, text.c_str()); - } - if (ReadAttrTxt(elem, "node", nodebody)) { - mjs_setStringVec(flex->nodebody, nodebody.c_str()); - } - auto vert = ReadAttrVec(elem, "vertex"); - if (vert.has_value()) { - mjs_setDouble(flex->vert, vert->data(), vert->size()); - } - auto element = ReadAttrVec(elem, "element", true); - if (element.has_value()) { - mjs_setInt(flex->elem, element->data(), element->size()); - } - auto texcoord = ReadAttrVec(elem, "texcoord"); - if (texcoord.has_value()) { - mjs_setFloat(flex->texcoord, texcoord->data(), texcoord->size()); - } - auto elemtexcoord = ReadAttrVec(elem, "elemtexcoord"); - if (elemtexcoord.has_value()) { - mjs_setInt(flex->elemtexcoord, elemtexcoord->data(), elemtexcoord->size()); - } - - // contact subelement + // sub-elements project into the flex XMLElement* cont = FirstChildElement(elem, "contact"); if (cont) { - ReadAttrInt(cont, "contype", &flex->contype); - ReadAttrInt(cont, "conaffinity", &flex->conaffinity); - ReadAttrInt(cont, "condim", &flex->condim); - ReadAttrInt(cont, "priority", &flex->priority); - ReadAttr(cont, "friction", 3, flex->friction, text, false, false); - ReadAttr(cont, "solmix", 1, &flex->solmix, text); - ReadAttr(cont, "solref", mjNREF, flex->solref, text, false, false); - ReadAttr(cont, "solimp", mjNIMP, flex->solimp, text, false, false); - ReadAttr(cont, "margin", 1, &flex->margin, text); - ReadAttr(cont, "gap", 1, &flex->gap, text); - if (MapValue(cont, "internal", &n, bool_map, 2)) { - flex->internal = (n == 1); - } - MapValue(cont, "selfcollide", &flex->selfcollide, flexself_map, 5); - if (MapValue(cont, "passive", &n, bool_map, 2)) { - flex->passive = (n == 1); - } - ReadAttrInt(cont, "activelayers", &flex->activelayers); + ReadAttrTable(cont, flex, flex->element, kFlexcomp_contactAttrs, + kFlexcomp_contactAttrsN); } - - // edge subelement XMLElement* edge = FirstChildElement(elem, "edge"); if (edge) { - ReadAttr(edge, "stiffness", 1, &flex->edgestiffness, text); - ReadAttr(edge, "damping", 1, &flex->edgedamping, text); + ReadAttrTable(edge, flex, flex->element, kFlex_edgeAttrs, + kFlex_edgeAttrsN); } - - // elasticity subelement XMLElement* elasticity = FirstChildElement(elem, "elasticity"); if (elasticity) { - ReadAttr(elasticity, "young", 1, &flex->young, text); - ReadAttr(elasticity, "poisson", 1, &flex->poisson, text); - ReadAttr(elasticity, "thickness", 1, &flex->thickness, text); - ReadAttr(elasticity, "damping", 1, &flex->damping, text); - MapValue(elasticity, "elastic2d", &flex->elastic2d, elastic2d_map, 4); + ReadAttrTable(elasticity, flex, flex->element, kElasticityAttrs, + kElasticityAttrsN); } // write error info @@ -1256,75 +621,30 @@ void mjXReader::OneFlex(XMLElement* elem, mjsFlex* flex) { // mesh element parser void mjXReader::OneMesh(XMLElement* elem, mjsMesh* mesh, const mjVFS* vfs) { int n; - string text, name, content_type; + string text; - // read attributes - if (ReadAttrTxt(elem, "name", name)) { - if (mjs_setName(mesh->element, name.c_str())) { - throw mjXError(elem, "%s", mjs_getError(spec)); - } - } - if (ReadAttrTxt(elem, "content_type", content_type)) { - *mesh->content_type = content_type; - } + // mechanical attributes + ReadAttrTable(elem, mesh, mesh->element, kMeshAttrs, kMeshAttrsN); + + // file, resolved against the mesh directory auto file = ReadAttrFile(elem, "file", vfs, MeshDir()); if (file) { mjs_setString(mesh->file, file->c_str()); } - ReadAttr(elem, "refpos", 3, mesh->refpos, text); - ReadAttr(elem, "refquat", 4, mesh->refquat, text); - ReadAttr(elem, "scale", 3, mesh->scale, text); - if (MapValue(elem, "inertia", &n, meshinertia_map, 4)) { - mesh->inertia = (mjtMeshInertia)n; - } + // plugin sub-element XMLElement* eplugin = FirstChildElement(elem, "plugin"); if (eplugin) { OnePlugin(eplugin, &mesh->plugin); } - if (MapValue(elem, "smoothnormal", &n, bool_map, 2)) { - mesh->smoothnormal = (n == 1); - } - + // maxhullvert with validation if (ReadAttrInt(elem, "maxhullvert", &n)) { if (n != -1 && n < 4) throw mjXError(elem, "maxhullvert must be larger than 3"); mesh->maxhullvert = n; } - // read user vertex data - if (ReadAttrTxt(elem, "vertex", text)) { - auto uservert = ReadAttrVec(elem, "vertex"); - if (uservert.has_value()) { - mjs_setFloat(mesh->uservert, uservert->data(), uservert->size()); - } - } - - // read user normal data - if (ReadAttrTxt(elem, "normal", text)) { - auto usernormal = ReadAttrVec(elem, "normal"); - if (usernormal.has_value()) { - mjs_setFloat(mesh->usernormal, usernormal->data(), usernormal->size()); - } - } - - // read user texcoord data - if (ReadAttrTxt(elem, "texcoord", text)) { - auto usertexcoord = ReadAttrVec(elem, "texcoord"); - if (usertexcoord.has_value()) { - mjs_setFloat(mesh->usertexcoord, usertexcoord->data(), usertexcoord->size()); - } - } - - // read user face data - if (ReadAttrTxt(elem, "face", text)) { - auto userface = ReadAttrVec(elem, "face"); - if (userface.has_value()) { - mjs_setInt(mesh->userface, userface->data(), userface->size()); - } - } - - // read builtin options + // builtin options if (MapValue(elem, "builtin", &n, meshbuiltin_map, meshbuiltin_sz)) { std::vector params; int nparams = ReadVector(elem, "params", params, text, /*required*/ true); @@ -1333,11 +653,6 @@ void mjXReader::OneMesh(XMLElement* elem, mjsMesh* mesh, const mjVFS* vfs) { } } - std::string material; - if (ReadAttrTxt(elem, "material", material)) { - mjs_setString(mesh->material, material.c_str()); - } - // write error info mjs_setString(mesh->info, ("line " + std::to_string(elem->GetLineNum())).c_str()); } @@ -1346,46 +661,23 @@ void mjXReader::OneMesh(XMLElement* elem, mjsMesh* mesh, const mjVFS* vfs) { // skin element parser void mjXReader::OneSkin(XMLElement* elem, mjsSkin* skin, const mjVFS* vfs) { - string text, name, material; + string text; float data[4]; - // read attributes - if (ReadAttrTxt(elem, "name", name)) { - if (mjs_setName(skin->element, name.c_str())) { - throw mjXError(elem, "%s", mjs_getError(spec)); - } - } + // mechanical attributes + ReadAttrTable(elem, skin, skin->element, kSkinAttrs, kSkinAttrsN); + + // file, resolved against the asset directory auto file = ReadAttrFile(elem, "file", vfs, AssetDir()); if (file.has_value()) { mjs_setString(skin->file, file->c_str()); } - if (ReadAttrTxt(elem, "material", material)) { - mjs_setString(skin->material, material.c_str()); - } + + // group with range validation ReadAttrInt(elem, "group", &skin->group); if (skin->group < 0 || skin->group >= mjNGROUP) { throw mjXError(elem, "skin group must be between 0 and 5"); } - ReadAttr(elem, "rgba", 4, skin->rgba, text); - ReadAttr(elem, "inflate", 1, &skin->inflate, text); - - // read vertex data - auto vertex = ReadAttrVec(elem, "vertex"); - if (vertex.has_value()) { - mjs_setFloat(skin->vert, vertex->data(), vertex->size()); - } - - // read texcoord data - auto texcoord = ReadAttrVec(elem, "texcoord"); - if (texcoord.has_value()) { - mjs_setFloat(skin->texcoord, texcoord->data(), texcoord->size()); - } - - // read user face data - auto face = ReadAttrVec(elem, "face"); - if (face.has_value()) { - mjs_setInt(skin->face, face->data(), face->size()); - } // read bones XMLElement* bone = FirstChildElement(elem, "bone"); @@ -1438,22 +730,18 @@ void mjXReader::OneSkin(XMLElement* elem, mjsSkin* skin, const mjVFS* vfs) { // material element parser void mjXReader::OneMaterial(XMLElement* elem, mjsMaterial* material) { - string text, name, texture; - int n; + string text, texture; - // read attributes - if (ReadAttrTxt(elem, "name", name)) { - if (mjs_setName(material->element, name.c_str())) { - throw mjXError(elem, "%s", mjs_getError(spec)); - } - } + // mechanical attributes + ReadAttrTable(elem, material, material->element, kMaterialAttrs, + kMaterialAttrsN); + // the texture attribute and layer sub-elements are mutually exclusive bool tex_attributes_found = false; if (ReadAttrTxt(elem, "texture", texture)) { mjs_setInStringVec(material->textures, mjTEXROLE_RGB, texture.c_str()); tex_attributes_found = true; } - XMLElement* layer = FirstChildElement(elem); while (layer) { if (tex_attributes_found) { @@ -1468,18 +756,6 @@ void mjXReader::OneMaterial(XMLElement* elem, mjsMaterial* material) { layer = NextSiblingElement(layer); } - if (MapValue(elem, "texuniform", &n, bool_map, 2)) { - material->texuniform = (n == 1); - } - ReadAttr(elem, "texrepeat", 2, material->texrepeat, text); - ReadAttr(elem, "emission", 1, &material->emission, text); - ReadAttr(elem, "specular", 1, &material->specular, text); - ReadAttr(elem, "shininess", 1, &material->shininess, text); - ReadAttr(elem, "reflectance", 1, &material->reflectance, text); - ReadAttr(elem, "metallic", 1, &material->metallic, text); - ReadAttr(elem, "roughness", 1, &material->roughness, text); - ReadAttr(elem, "rgba", 4, material->rgba, text); - // write error info mjs_setString(material->info, ("line " + std::to_string(elem->GetLineNum())).c_str()); } @@ -1488,50 +764,8 @@ void mjXReader::OneMaterial(XMLElement* elem, mjsMaterial* material) { // joint element parser void mjXReader::OneJoint(XMLElement* elem, mjsJoint* joint) { - string text, name; - std::vector userdata; - int n; - - // read attributes - if (ReadAttrTxt(elem, "name", name)) { - if (mjs_setName(joint->element, name.c_str())) { - throw mjXError(elem, "%s", mjs_getError(spec)); - } - } - if (MapValue(elem, "type", &n, joint_map, joint_sz)) { - joint->type = (mjtJoint)n; - } - MapValue(elem, "limited", &joint->limited, TFAuto_map, 3); - MapValue(elem, "actuatorfrclimited", &joint->actfrclimited, TFAuto_map, 3); - ReadAttrInt(elem, "group", &joint->group); - ReadAttr(elem, "solreflimit", mjNREF, joint->solref_limit, text, false, false); - ReadAttr(elem, "solimplimit", mjNIMP, joint->solimp_limit, text, false, false); - ReadAttr(elem, "solreffriction", mjNREF, joint->solref_friction, text, false, false); - ReadAttr(elem, "solimpfriction", mjNIMP, joint->solimp_friction, text, false, false); - ReadAttr(elem, "pos", 3, joint->pos, text); - ReadAttr(elem, "axis", 3, joint->axis, text); - ReadAttr(elem, "springdamper", 2, joint->springdamper, text); - - ReadAttr(elem, "stiffness", 1+mjNPOLY, joint->stiffness, text, false, false); - - ReadAttr(elem, "range", 2, joint->range, text); - ReadAttr(elem, "actuatorfrcrange", 2, joint->actfrcrange, text); - ReadAttr(elem, "margin", 1, &joint->margin, text); - ReadAttr(elem, "ref", 1, &joint->ref, text); - ReadAttr(elem, "springref", 1, &joint->springref, text); - ReadAttr(elem, "armature", 1, &joint->armature, text); - - ReadAttr(elem, "damping", 1+mjNPOLY, joint->damping, text, false, false); - - ReadAttr(elem, "frictionloss", 1, &joint->frictionloss, text); - if (MapValue(elem, "actuatorgravcomp", &n, bool_map, 2)) { - joint->actgravcomp = (n == 1); - } - - // read userdata - if (ReadVector(elem, "user", userdata, text)) { - mjs_setDouble(joint->userdata, userdata.data(), userdata.size()); - } + // mechanical attributes + ReadAttrTable(elem, joint, joint->element, kJointAttrs, kJointAttrsN); // write error info mjs_setString(joint->info, ("line " + std::to_string(elem->GetLineNum())).c_str()); @@ -1541,54 +775,20 @@ void mjXReader::OneJoint(XMLElement* elem, mjsJoint* joint) { // geom element parser void mjXReader::OneGeom(XMLElement* elem, mjsGeom* geom) { - string text, name; - std::vector userdata; - string hfieldname, meshname, material; + string text; int n; - // read attributes - if (ReadAttrTxt(elem, "name", name)) { - if (mjs_setName(geom->element, name.c_str())) { - throw mjXError(elem, "%s", mjs_getError(spec)); - } - } - if (MapValue(elem, "type", &n, geom_map, mjNGEOMTYPES)) { - geom->type = (mjtGeom)n; - } - ReadAttr(elem, "size", 3, geom->size, text, false, false); - ReadAttrInt(elem, "contype", &geom->contype); - ReadAttrInt(elem, "conaffinity", &geom->conaffinity); - ReadAttrInt(elem, "condim", &geom->condim); - ReadAttrInt(elem, "group", &geom->group); - ReadAttrInt(elem, "priority", &geom->priority); - ReadAttr(elem, "friction", 3, geom->friction, text, false, false); - ReadAttr(elem, "solmix", 1, &geom->solmix, text); - ReadAttr(elem, "solref", mjNREF, geom->solref, text, false, false); - ReadAttr(elem, "solimp", mjNIMP, geom->solimp, text, false, false); - ReadAttr(elem, "margin", 1, &geom->margin, text); - ReadAttr(elem, "gap", 1, &geom->gap, text); - ReadAttr(elem, "surfacevel", 6, geom->surfacevel, text, false, false); - ReadAttr(elem, "adhesion", 1, &geom->adhesion, text); - if (ReadAttrTxt(elem, "hfield", hfieldname)) { - mjs_setString(geom->hfieldname, hfieldname.c_str()); - } - if (ReadAttrTxt(elem, "mesh", meshname)) { - mjs_setString(geom->meshname, meshname.c_str()); - } - ReadAttr(elem, "fitscale", 1, &geom->fitscale, text); - if (ReadAttrTxt(elem, "material", material)) { - mjs_setString(geom->material, material.c_str()); - } - ReadAttr(elem, "rgba", 4, geom->rgba, text); - if (MapValue(elem, "fluidshape", &n, fluid_map, 2)) { + // mechanical attributes + ReadAttrTable(elem, geom, geom->element, kGeomAttrs, kGeomAttrsN); + + // fluid interaction model: keyword lowers to on/off + if (MapValue(elem, "fluidshape", &n, fluidshape_map, 2)) { geom->fluid_ellipsoid = (n == 1); } - ReadAttr(elem, "fluidcoef", 5, geom->fluid_coefs, text, false, false); - // read userdata - if (ReadVector(elem, "user", userdata, text)) { - mjs_setDouble(geom->userdata, userdata.data(), userdata.size()); - } + // orientation alternatives + ReadQuat(elem, "quat", geom->quat, text); + ReadAlternative(elem, geom->alt); // plugin sub-element XMLElement* eplugin = FirstChildElement(elem, "plugin"); @@ -1596,19 +796,6 @@ void mjXReader::OneGeom(XMLElement* elem, mjsGeom* geom) { OnePlugin(eplugin, &geom->plugin); } - // remaining attributes - ReadAttr(elem, "mass", 1, &geom->mass, text); - ReadAttr(elem, "density", 1, &geom->density, text); - ReadAttr(elem, "fromto", 6, geom->fromto, text); - ReadAttr(elem, "pos", 3, geom->pos, text); - ReadQuat(elem, "quat", geom->quat, text); - ReadAlternative(elem, geom->alt); - - // compute inertia using either solid or shell geometry - if (MapValue(elem, "shellinertia", &n, meshtype_map, 2)) { - geom->typeinertia = (mjtGeomInertia)n; - } - // write error info mjs_setString(geom->info, ("line " + std::to_string(elem->GetLineNum())).c_str()); } @@ -1617,33 +804,14 @@ void mjXReader::OneGeom(XMLElement* elem, mjsGeom* geom) { // site element parser void mjXReader::OneSite(XMLElement* elem, mjsSite* site) { - int n; - string text, name; - std::vector userdata; - string material; + string text; - // read attributes - if (ReadAttrTxt(elem, "name", name)) { - if (mjs_setName(site->element, name.c_str())) { - throw mjXError(elem, "%s", mjs_getError(spec)); - } - } - if (MapValue(elem, "type", &n, geom_map, mjNGEOMTYPES)) { - site->type = (mjtGeom)n; - } - ReadAttr(elem, "size", 3, site->size, text, false, false); - ReadAttrInt(elem, "group", &site->group); - ReadAttr(elem, "pos", 3, site->pos, text); + // mechanical attributes + ReadAttrTable(elem, site, site->element, kSiteAttrs, kSiteAttrsN); + + // orientation alternatives ReadQuat(elem, "quat", site->quat, text); - if (ReadAttrTxt(elem, "material", material)) { - mjs_setString(site->material, material.c_str()); - } - ReadAttr(elem, "rgba", 4, site->rgba, text); - ReadAttr(elem, "fromto", 6, site->fromto, text); ReadAlternative(elem, site->alt); - if (ReadVector(elem, "user", userdata, text)) { - mjs_setDouble(site->userdata, userdata.data(), userdata.size()); - } // write error info mjs_setString(site->info, ("line " + std::to_string(elem->GetLineNum())).c_str()); @@ -1653,53 +821,14 @@ void mjXReader::OneSite(XMLElement* elem, mjsSite* site) { // camera element parser void mjXReader::OneCamera(XMLElement* elem, mjsCamera* camera) { - int n; - string text, name, targetbody; - std::vector userdata; + string text; - // read attributes - if (ReadAttrTxt(elem, "name", name)) { - if (mjs_setName(camera->element, name.c_str())) { - throw mjXError(elem, "%s", mjs_getError(spec)); - } - } - if (ReadAttrTxt(elem, "target", targetbody)) { - mjs_setString(camera->targetbody, targetbody.c_str()); - } - if (MapValue(elem, "mode", &n, camlight_map, camlight_sz)) { - camera->mode = (mjtCamLight)n; - } - ReadAttr(elem, "pos", 3, camera->pos, text); + // mechanical attributes + ReadAttrTable(elem, camera, camera->element, kCameraAttrs, kCameraAttrsN); + + // orientation alternatives ReadQuat(elem, "quat", camera->quat, text); ReadAlternative(elem, camera->alt); - ReadAttr(elem, "ipd", 1, &camera->ipd, text); - - if (MapValue(elem, "projection", &n, projection_map, 2)) { - camera->proj = (mjtProjection)n; - } - - ReadAttr(elem, "principalpixel", 2, camera->principal_pixel, text); - ReadAttr(elem, "principal", 2, camera->principal_length, text); - ReadAttr(elem, "focalpixel", 2, camera->focal_pixel, text); - ReadAttr(elem, "focal", 2, camera->focal_length, text); - ReadAttr(elem, "resolution", 2, camera->resolution, text); - - // read output attribute as space-separated bitflags - std::vector outvals(mjNCAMOUT); - int nout = MapValues(elem, "output", outvals.data(), camout_map, mjNCAMOUT); - if (nout) { - camera->output = 0; - for (int i = 0; i < nout; ++i) { - camera->output |= outvals[i]; - } - } - - ReadAttr(elem, "sensorsize", 2, camera->sensor_size, text); - ReadAttr(elem, "fovy", 1, &camera->fovy, text); - - // read userdata - ReadVector(elem, "user", userdata, text); - mjs_setDouble(camera->userdata, userdata.data(), userdata.size()); // write error info mjs_setString(camera->info, ("line " + std::to_string(elem->GetLineNum())).c_str()); @@ -1710,46 +839,17 @@ void mjXReader::OneCamera(XMLElement* elem, mjsCamera* camera) { // light element parser void mjXReader::OneLight(XMLElement* elem, mjsLight* light) { int n; - string text, name, texture, targetbody; - // read attributes - if (ReadAttrTxt(elem, "name", name)) { - if (mjs_setName(light->element, name.c_str())) { - throw mjXError(elem, "%s", mjs_getError(spec)); - } - } - if (ReadAttrTxt(elem, "texture", texture)) { - mjs_setString(light->texture, texture.c_str()); - } - if (ReadAttrTxt(elem, "target", targetbody)) { - mjs_setString(light->targetbody, targetbody.c_str()); - } - if (MapValue(elem, "mode", &n, camlight_map, camlight_sz)) { - light->mode = (mjtCamLight)n; - } + // mechanical attributes + ReadAttrTable(elem, light, light->element, kLightAttrs, kLightAttrsN); + + // directional is a legacy alias for type; the pair is mutually exclusive if (MapValue(elem, "directional", &n, bool_map, 2)) { light->type = (n == 1) ? mjLIGHT_DIRECTIONAL : mjLIGHT_SPOT; } if (MapValue(elem, "type", &n, lighttype_map, lighttype_sz)) { light->type = (mjtLightType)n; } - if (MapValue(elem, "castshadow", &n, bool_map, 2)) { - light->castshadow = (n == 1); - } - if (MapValue(elem, "active", &n, bool_map, 2)) { - light->active = (n == 1); - } - ReadAttr(elem, "pos", 3, light->pos, text); - ReadAttr(elem, "dir", 3, light->dir, text); - ReadAttr(elem, "bulbradius", 1, &light->bulbradius, text); - ReadAttr(elem, "intensity", 1, &light->intensity, text); - ReadAttr(elem, "range", 1, &light->range, text); - ReadAttr(elem, "attenuation", 3, light->attenuation, text); - ReadAttr(elem, "cutoff", 1, &light->cutoff, text); - ReadAttr(elem, "exponent", 1, &light->exponent, text); - ReadAttr(elem, "ambient", 3, light->ambient, text); - ReadAttr(elem, "diffuse", 3, light->diffuse, text); - ReadAttr(elem, "specular", 3, light->specular, text); // write error info mjs_setString(light->info, ("line " + std::to_string(elem->GetLineNum())).c_str()); @@ -1758,33 +858,163 @@ void mjXReader::OneLight(XMLElement* elem, mjsLight* light) { // pair element parser -void mjXReader::OnePair(XMLElement* elem, mjsPair* pair) { - string text, name, geomname1, geomname2; - - // regular only - if (!readingdefaults) { - if (ReadAttrTxt(elem, "geom1", geomname1)) { - mjs_setString(pair->geomname1, geomname1.c_str()); +// read the mechanical attributes of an element, driven by its generated rows +void mjXReader::ReadAttrTable(XMLElement* elem, void* obj, mjsElement* el, + const mjXAttr* rows, int nrow) { + // element names need the spec for error context; the core handles the rest + string text; + for (int i = 0; i < nrow; i++) { + const mjXAttr& row = rows[i]; + if (row.kind != mjXAttr::kName || (readingdefaults && row.nodefault)) { + continue; } - if (ReadAttrTxt(elem, "geom2", geomname2)) { - mjs_setString(pair->geomname2, geomname2.c_str()); - } - } - - // read other parameters - if (ReadAttrTxt(elem, "name", name)) { - if (mjs_setName(pair->element, name.c_str())) { + if (ReadAttrTxt(elem, row.attr, text, row.required) && + mjs_setName(el, text.c_str())) { throw mjXError(elem, "%s", mjs_getError(spec)); } } - ReadAttrInt(elem, "condim", &pair->condim); - ReadAttr(elem, "solref", mjNREF, pair->solref, text, false, false); - ReadAttr(elem, "solreffriction", mjNREF, pair->solreffriction, text, false, false); - ReadAttr(elem, "solimp", mjNIMP, pair->solimp, text, false, false); - ReadAttr(elem, "margin", 1, &pair->margin, text); - ReadAttr(elem, "gap", 1, &pair->gap, text); - ReadAttr(elem, "adhesion", 1, &pair->adhesion, text); - ReadAttr(elem, "friction", 5, pair->friction, text, false, false); + ReadAttrTableCore(elem, obj, rows, nrow, readingdefaults); +} + + + +// static core: every row kind except element names +void mjXReader::ReadAttrTableCore(XMLElement* elem, void* obj, + const mjXAttr* rows, int nrow, + bool skipnodefault, const void* authored) { + string text; + for (int i = 0; i < nrow; i++) { + const mjXAttr& row = rows[i]; + if (skipnodefault && row.nodefault) { + continue; + } + char* base = (char*)obj + row.offset; + bool got = false; + switch (row.kind) { + case mjXAttr::kName: + break; // handled by the member wrapper + case mjXAttr::kString: + if (ReadAttrTxt(elem, row.attr, text, row.required)) { + mjs_setString(*(mjString**)base, text.c_str()); + got = true; + } + break; + case mjXAttr::kStringVec: + if (ReadAttrTxt(elem, row.attr, text, row.required)) { + mjs_setStringVec(*(mjStringVec**)base, text.c_str()); + got = true; + } + break; + case mjXAttr::kInt: + got = ReadAttr(elem, row.attr, row.len, (int*)base, text, row.required, + row.exact) > 0; + break; + case mjXAttr::kDouble: + got = ReadAttr(elem, row.attr, row.len, (double*)base, text, + row.required, row.exact) > 0; + break; + case mjXAttr::kNum: + got = ReadAttr(elem, row.attr, row.len, (mjtNum*)base, text, + row.required, row.exact) > 0; + break; + case mjXAttr::kFloat: + got = ReadAttr(elem, row.attr, row.len, (float*)base, text, + row.required, row.exact) > 0; + break; + case mjXAttr::kEnum: { + int value; + if (MapValue(elem, row.attr, &value, row.map, row.mapsz, + row.required)) { + *(int*)base = value; // enum fields are int-sized + got = true; + } + break; + } + case mjXAttr::kFlags: { + std::vector values(row.mapsz); + int nvalues = MapValues(elem, row.attr, values.data(), row.map, + row.mapsz); + if (nvalues) { + int combined = 0; + for (int j = 0; j < nvalues; j++) { + combined |= values[j]; + } + *(int*)base = combined; + got = true; + } + break; + } + case mjXAttr::kEnumByte: { + int value; + if (MapValue(elem, row.attr, &value, row.map, row.mapsz, + row.required)) { + *(mjtByte*)base = (mjtByte)value; + got = true; + } + break; + } + case mjXAttr::kBool: { + int value; + if (MapValue(elem, row.attr, &value, bool_map, 2, row.required)) { + *(mjtBool*)base = (value == 1); + got = true; + } + break; + } + case mjXAttr::kConst: + *(int*)base = row.value; // enum fields are int-sized + break; + case mjXAttr::kDoubleVec: { + std::vector values; + if (ReadVector(elem, row.attr, values, text)) { + mjs_setDouble(*(mjDoubleVec**)base, values.data(), values.size()); + got = true; + } + break; + } + case mjXAttr::kFloatVec: { + auto values = ReadAttrVec(elem, row.attr, row.required); + if (values.has_value()) { + mjs_setFloat(*(mjFloatVec**)base, values->data(), values->size()); + got = true; + } + break; + } + case mjXAttr::kIntVec: { + auto values = ReadAttrVec(elem, row.attr, row.required); + if (values.has_value()) { + mjs_setInt(*(mjIntVec**)base, values->data(), values->size()); + got = true; + } + break; + } + case mjXAttr::kChars: + if (ReadAttrTxt(elem, row.attr, text, row.required)) { + if (row.exact && (int)text.size() != row.len) { + throw mjXError(elem, "attribute '%s' must have exactly %d " + "characters", row.attr, row.len); + } + if ((int)text.size() > row.len) { + throw mjXError(elem, "attribute '%s' may have at most %d " + "characters", row.attr, row.len); + } + std::memcpy(base, text.data(), text.size()); + got = true; + } + break; + } + + // record XML-authored fields for attach conflict resolution + if (got && authored) { + mjs_setAuthored(authored, base, 1); + } + } +} + + + +void mjXReader::OnePair(XMLElement* elem, mjsPair* pair) { + ReadAttrTable(elem, pair, pair->element, kPairAttrs, kPairAttrsN); // write error info mjs_setString(pair->info, ("line " + std::to_string(elem->GetLineNum())).c_str()); @@ -1794,21 +1024,18 @@ void mjXReader::OnePair(XMLElement* elem, mjsPair* pair) { // equality element parser void mjXReader::OneEquality(XMLElement* elem, mjsEquality* equality) { - int n; - string text, name1, name2, name; + string text, name1, name2; // read type (bad keywords already detected by schema) text = elem->Value(); equality->type = (mjtEq)FindKey(equality_map, equality_sz, text); + // common attributes + ReadAttrTable(elem, equality, equality->element, kEqualityBaseAttrs, + kEqualityBaseAttrsN); + // regular only if (!readingdefaults) { - if (ReadAttrTxt(elem, "name", name)) { - if (mjs_setName(equality->element, name.c_str())) { - throw mjXError(elem, "%s", mjs_getError(spec)); - } - } - switch (equality->type) { case mjEQ_CONNECT: { auto maybe_site1 = ReadAttrStr(elem, "site1"); @@ -1898,13 +1125,6 @@ void mjXReader::OneEquality(XMLElement* elem, mjsEquality* equality) { } } - // read attributes - if (MapValue(elem, "active", &n, bool_map, 2)) { - equality->active = (n == 1); - } - ReadAttr(elem, "solref", mjNREF, equality->solref, text, false, false); - ReadAttr(elem, "solimp", mjNIMP, equality->solimp, text, false, false); - // write error info mjs_setString(equality->info, ("line " + std::to_string(elem->GetLineNum())).c_str()); } @@ -1913,46 +1133,15 @@ void mjXReader::OneEquality(XMLElement* elem, mjsEquality* equality) { // tendon element parser void mjXReader::OneTendon(XMLElement* elem, mjsTendon* tendon) { - string text, name, material; - std::vector userdata; + string text; - // read attributes - if (ReadAttrTxt(elem, "name", name)) { - if (mjs_setName(tendon->element, name.c_str())) { - throw mjXError(elem, "%s", mjs_getError(spec)); - } - } - ReadAttrInt(elem, "group", &tendon->group); - if (ReadAttrTxt(elem, "material", material)) { - mjs_setString(tendon->material, material.c_str()); - } - MapValue(elem, "limited", &tendon->limited, TFAuto_map, 3); - MapValue(elem, "actuatorfrclimited", &tendon->actfrclimited, TFAuto_map, 3); - ReadAttr(elem, "width", 1, &tendon->width, text); - ReadAttr(elem, "solreflimit", mjNREF, tendon->solref_limit, text, false, false); - ReadAttr(elem, "solimplimit", mjNIMP, tendon->solimp_limit, text, false, false); - ReadAttr(elem, "solreffriction", mjNREF, tendon->solref_friction, text, false, false); - ReadAttr(elem, "solimpfriction", mjNIMP, tendon->solimp_friction, text, false, false); - ReadAttr(elem, "range", 2, tendon->range, text); - ReadAttr(elem, "actuatorfrcrange", 2, tendon->actfrcrange, text); - ReadAttr(elem, "margin", 1, &tendon->margin, text); + // mechanical attributes; fixed tendons read a subset of the spatial rows + ReadAttrTable(elem, tendon, tendon->element, kSpatialAttrs, kSpatialAttrsN); - ReadAttr(elem, "stiffness", 1+mjNPOLY, tendon->stiffness, text, false, false); - - ReadAttr(elem, "damping", 1+mjNPOLY, tendon->damping, text, false, false); - - ReadAttr(elem, "armature", 1, &tendon->armature, text); - ReadAttr(elem, "frictionloss", 1, &tendon->frictionloss, text); // read springlength, either one or two values; if one, copy to second value if (ReadAttr(elem, "springlength", 2, tendon->springlength, text, false, false) == 1) { tendon->springlength[1] = tendon->springlength[0]; } - ReadAttr(elem, "rgba", 4, tendon->rgba, text); - - // read userdata - if (ReadVector(elem, "user", userdata, text)) { - mjs_setDouble(tendon->userdata, userdata.data(), userdata.size()); - } // write error info mjs_setString(tendon->info, ("line " + std::to_string(elem->GetLineNum())).c_str()); @@ -1960,30 +1149,39 @@ void mjXReader::OneTendon(XMLElement* elem, mjsTendon* tendon) { +// read the "input" attribute: so3 chart keyword, or servo input token list +static bool ReadInputSpec(tinyxml2::XMLElement* elem, int* ctrlspec) { + std::string text; + if (!mjXUtil::ReadAttrTxt(elem, "input", text)) { + return false; + } + + // so3 chart keyword + int chart = mjXUtil::FindKey(inputchart_map, inputchart_sz, text); + if (chart >= 0) { + *ctrlspec = chart; + return true; + } + + // servo input tokens + int bits[inputbit_sz]; + int nbit = mjXUtil::MapValues(elem, "input", bits, inputbit_map, inputbit_sz); + int spec = 0; + for (int k=0; k < nbit; k++) { + spec |= bits[k]; + } + *ctrlspec = spec; + return true; +} + + // actuator element parser void mjXReader::OneActuator(XMLElement* elem, mjsActuator* actuator) { - string text, type, name, target, slidersite, refsite; + string text, type, target, slidersite, refsite; - // common attributes - if (ReadAttrTxt(elem, "name", name)) { - if (mjs_setName(actuator->element, name.c_str())) { - throw mjXError(elem, "%s", mjs_getError(spec)); - } - } - ReadAttrInt(elem, "group", &actuator->group); - ReadAttrInt(elem, "nsample", &actuator->nsample); - MapValue(elem, "interp", &actuator->interp, interp_map, interp_sz); - ReadAttr(elem, "delay", 1, &actuator->delay, text); - MapValue(elem, "ctrllimited", &actuator->ctrllimited, TFAuto_map, 3); - MapValue(elem, "forcelimited", &actuator->forcelimited, TFAuto_map, 3); - MapValue(elem, "actlimited", &actuator->actlimited, TFAuto_map, 3); - ReadAttr(elem, "ctrlrange", 2, actuator->ctrlrange, text); - ReadAttr(elem, "forcerange", 2, actuator->forcerange, text); - ReadAttr(elem, "actrange", 2, actuator->actrange, text); - ReadAttr(elem, "lengthrange", 2, actuator->lengthrange, text); - ReadAttr(elem, "gear", 6, actuator->gear, text, false, false); - ReadAttr(elem, "damping", 1+mjNPOLY, actuator->damping, text, false, false); - ReadAttr(elem, "armature", 1, &actuator->armature, text, false, false); + // mechanical attributes; per-tag legality is enforced by the schema check + ReadAttrTable(elem, actuator, actuator->element, kGeneralAttrs, + kGeneralAttrsN); // transmission target and type if (ReadAttrTxt(elem, "joint", target)) { @@ -2037,27 +1235,8 @@ void mjXReader::OneActuator(XMLElement* elem, mjsActuator* actuator) { // explicit attributes string err; if (type == "general") { - // explicit attributes - int n; - if (MapValue(elem, "dyntype", &n, dyn_map, dyn_sz)) { - actuator->dyntype = (mjtDyn)n; - } - if (MapValue(elem, "gaintype", &n, gain_map, gain_sz)) { - actuator->gaintype = (mjtGain)n; - } - if (MapValue(elem, "biastype", &n, bias_map, bias_sz)) { - actuator->biastype = (mjtBias)n; - } - if (MapValue(elem, "actearly", &n, bool_map, 2)) { - actuator->actearly = (n == 1); - } - ReadAttr(elem, "dynprm", mjNDYN, actuator->dynprm, text, false, false); - ReadAttr(elem, "gainprm", mjNGAIN, actuator->gainprm, text, false, false); - ReadAttr(elem, "biasprm", mjNBIAS, actuator->biasprm, text, false, false); - ReadAttrInt(elem, "actdim", &actuator->actdim); + // so3 chart keyword or servo token subset ReadInputSpec(elem, &actuator->ctrlspec); - ReadAttr(elem, "velrange", 2, actuator->velrange, text); - ReadAttr(elem, "ffrange", 2, actuator->ffrange, text); } // direct drive motor @@ -2154,10 +1333,9 @@ void mjXReader::OneActuator(XMLElement* elem, mjsActuator* actuator) { // input subset selection ReadInputSpec(elem, &actuator->ctrlspec); - // per-input ranges; posrange is an alias of ctrlrange (input 0) + // posrange is an alias of ctrlrange (the position-setpoint input); + // velrange and ffrange are read by the shared rows ReadAttr(elem, "posrange", 2, actuator->ctrlrange, text); - ReadAttr(elem, "velrange", 2, actuator->velrange, text); - ReadAttr(elem, "ffrange", 2, actuator->ffrange, text); // handle inheritrange double inheritrange = actuator->inheritrange; @@ -2273,15 +1451,6 @@ void mjXReader::OneActuator(XMLElement* elem, mjsActuator* actuator) { else if (type == "plugin") { OnePlugin(elem, &actuator->plugin); - int n; - if (MapValue(elem, "dyntype", &n, dyn_map, dyn_sz)) { - actuator->dyntype = (mjtDyn)n; - } - if (MapValue(elem, "actearly", &n, bool_map, 2)) { - actuator->actearly = (n == 1); - } - ReadAttr(elem, "dynprm", mjNDYN, actuator->dynprm, text, false, false); - ReadAttrInt(elem, "actdim", &actuator->actdim); } else { // SHOULD NOT OCCUR @@ -2293,12 +1462,6 @@ void mjXReader::OneActuator(XMLElement* elem, mjsActuator* actuator) { throw mjXError(elem, err.c_str()); } - // read userdata - std::vector userdata; - if (ReadVector(elem, "user", userdata, text)) { - mjs_setDouble(actuator->userdata, userdata.data(), userdata.size()); - } - // write info mjs_setString(actuator->info, ("line " + std::to_string(elem->GetLineNum())).c_str()); } @@ -2384,7 +1547,7 @@ void mjXReader::OneComposite(XMLElement* elem, mjsBody* body, mjsFrame* frame, c if (egeom) { string material; mjsGeom& dgeom = *comp.def[0].spec.geom; - if (MapValue(egeom, "type", &n, geom_map, mjNGEOMTYPES)) { + if (MapValue(egeom, "type", &n, geomtype_map, mjNGEOMTYPES)) { dgeom.type = (mjtGeom)n; } ReadAttr(egeom, "size", 3, dgeom.size, text, false, false); @@ -2443,7 +1606,7 @@ void mjXReader::OneComposite(XMLElement* elem, mjsBody* body, mjsFrame* frame, c mjsEquality& dequality = *dspec->equality; // particle joint - if (MapValue(ejnt, "type", &n, joint_map, joint_sz)) { + if (MapValue(ejnt, "type", &n, jointtype_map, jointtype_sz)) { djoint.type = (mjtJoint)n; } ReadAttr(ejnt, "axis", 3, djoint.axis, text); @@ -2453,7 +1616,7 @@ void mjXReader::OneComposite(XMLElement* elem, mjsBody* body, mjsFrame* frame, c ReadAttr(ejnt, "solimpfix", mjNIMP, dequality.solimp, text, false, false); // joint attributes - MapValue(elem, "limited", &djoint.limited, TFAuto_map, 3); + MapValue(elem, "limited", &djoint.limited, FalseTrueAuto_map, 3); ReadAttrInt(ejnt, "group", &djoint.group); ReadAttr(ejnt, "solreflimit", mjNREF, djoint.solref_limit, text, false, false); ReadAttr(ejnt, "solimplimit", mjNIMP, djoint.solimp_limit, text, false, false); @@ -2941,122 +2104,54 @@ void mjXReader::Visual(XMLElement* section) { string name; XMLElement* elem; mjVisual* vis = &spec->visual; - Reader read(section, spec); // iterate over child elements elem = FirstChildElement(section); while (elem) { // get sub-element name name = elem->Value(); - read.set_node(elem); // global sub-element if (name == "global") { - read("cameraid", vis->global.cameraid); - read("orthographic", vis->global.orthographic, bool_map, 2); - read("fovy", 1, vis->global.fovy); - read("ipd", 1, vis->global.ipd); - read("azimuth", 1, vis->global.azimuth); - read("elevation", 1, vis->global.elevation); - read("linewidth", 1, vis->global.linewidth); - read("glow", 1, vis->global.glow); - read("offwidth", vis->global.offwidth); - read("offheight", vis->global.offheight); - if (read("realtime", 1, vis->global.realtime)) { - if (vis->global.realtime <= 0) { - throw mjXError(elem, "realtime must be greater than 0"); - } + ReadAttrTableCore(elem, vis, kGlobalAttrs, kGlobalAttrsN, + /*skipnodefault=*/false, + /*authored=*/spec); + if (vis->global.realtime <= 0) { + throw mjXError(elem, "realtime must be greater than 0"); } - read("ellipsoidinertia", vis->global.ellipsoidinertia, bool_map, 2); - read("bvactive", vis->global.bvactive, bool_map, 2); } // quality sub-element else if (name == "quality") { - read("shadowsize", vis->quality.shadowsize); - read("offsamples", vis->quality.offsamples); - read("numslices", vis->quality.numslices); - read("numstacks", vis->quality.numstacks); - read("numquads", vis->quality.numquads); + ReadAttrTableCore(elem, vis, kQualityAttrs, kQualityAttrsN, false, + /*authored=*/spec); } // headlight sub-element else if (name == "headlight") { - read("ambient", 3, vis->headlight.ambient); - read("diffuse", 3, vis->headlight.diffuse); - read("specular", 3, vis->headlight.specular); - read("active", vis->headlight.active); + ReadAttrTableCore(elem, vis, kHeadlightAttrs, kHeadlightAttrsN, false, + /*authored=*/spec); } // map sub-element else if (name == "map") { - read("stiffness", 1, vis->map.stiffness); - read("stiffnessrot", 1, vis->map.stiffnessrot); - read("force", 1, vis->map.force); - read("torque", 1, vis->map.torque); - read("alpha", 1, vis->map.alpha); - read("fogstart", 1, vis->map.fogstart); - read("fogend", 1, vis->map.fogend); - if (read("znear", 1, vis->map.znear)) { - if (vis->map.znear <= 0) { - throw mjXError(elem, "znear must be strictly positive"); - } + ReadAttrTableCore(elem, vis, kMapAttrs, kMapAttrsN, false, + /*authored=*/spec); + if (vis->map.znear <= 0) { + throw mjXError(elem, "znear must be strictly positive"); } - read("zfar", 1, vis->map.zfar); - read("haze", 1, vis->map.haze); - read("shadowclip", 1, vis->map.shadowclip); - read("shadowscale", 1, vis->map.shadowscale); - read("actuatortendon", 1, vis->map.actuatortendon); } // scale sub-element else if (name == "scale") { - read("forcewidth", 1, vis->scale.forcewidth); - read("contactwidth", 1, vis->scale.contactwidth); - read("contactheight", 1, vis->scale.contactheight); - read("connect", 1, vis->scale.connect); - read("com", 1, vis->scale.com); - read("camera", 1, vis->scale.camera); - read("light", 1, vis->scale.light); - read("selectpoint", 1, vis->scale.selectpoint); - read("jointlength", 1, vis->scale.jointlength); - read("jointwidth", 1, vis->scale.jointwidth); - read("actuatorlength", 1, vis->scale.actuatorlength); - read("actuatorwidth", 1, vis->scale.actuatorwidth); - read("framelength", 1, vis->scale.framelength); - read("framewidth", 1, vis->scale.framewidth); - read("constraint", 1, vis->scale.constraint); - read("slidercrank", 1, vis->scale.slidercrank); - read("frustum", 1, vis->scale.frustum); + ReadAttrTableCore(elem, vis, kScaleAttrs, kScaleAttrsN, false, + /*authored=*/spec); } // rgba sub-element else if (name == "rgba") { - read("fog", 4, vis->rgba.fog); - read("haze", 4, vis->rgba.haze); - read("force", 4, vis->rgba.force); - read("inertia", 4, vis->rgba.inertia); - read("joint", 4, vis->rgba.joint); - read("actuator", 4, vis->rgba.actuator); - read("actuatornegative", 4, vis->rgba.actuatornegative); - read("actuatorpositive", 4, vis->rgba.actuatorpositive); - read("com", 4, vis->rgba.com); - read("camera", 4, vis->rgba.camera); - read("light", 4, vis->rgba.light); - read("selectpoint", 4, vis->rgba.selectpoint); - read("connect", 4, vis->rgba.connect); - read("contactpoint", 4, vis->rgba.contactpoint); - read("contactforce", 4, vis->rgba.contactforce); - read("contactfriction", 4, vis->rgba.contactfriction); - read("contacttorque", 4, vis->rgba.contacttorque); - read("contactgap", 4, vis->rgba.contactgap); - read("rangefinder", 4, vis->rgba.rangefinder); - read("constraint", 4, vis->rgba.constraint); - read("slidercrank", 4, vis->rgba.slidercrank); - read("crankbroken", 4, vis->rgba.crankbroken); - read("frustum", 4, vis->rgba.frustum); - read("bv", 4, vis->rgba.bv); - read("bvactive", 4, vis->rgba.bvactive); + ReadAttrTableCore(elem, vis, kRgbaAttrs, kRgbaAttrsN, false, + /*authored=*/spec); } // advance to next element @@ -3066,10 +2161,10 @@ void mjXReader::Visual(XMLElement* section) { + // asset section parser void mjXReader::Asset(XMLElement* section, const mjVFS* vfs) { - int n; - string text, name, texname, content_type; + string text, name; XMLElement* elem; // iterate over child elements @@ -3092,57 +2187,20 @@ void mjXReader::Asset(XMLElement* section, const mjVFS* vfs) { // write error info mjs_setString(texture->info, ("line " + std::to_string(elem->GetLineNum())).c_str()); - // read attributes - if (MapValue(elem, "type", &n, texture_map, texture_sz)) { - texture->type = (mjtTexture)n; - } - if (MapValue(elem, "colorspace", &n, colorspace_map, colorspace_sz)) { - texture->colorspace = (mjtColorSpace)n; - } - if (ReadAttrTxt(elem, "name", texname)) { - if (mjs_setName(texture->element, texname.c_str())) { - throw mjXError(elem, "%s", mjs_getError(spec)); - } - } - if (ReadAttrTxt(elem, "content_type", content_type)) { - mjs_setString(texture->content_type, content_type.c_str()); - } + // mechanical attributes + ReadAttrTable(elem, texture, texture->element, kTextureAttrs, + kTextureAttrsN); + + // file, resolved against the texture directory auto file = ReadAttrFile(elem, "file", vfs, TextureDir()); if (file.has_value()) { mjs_setString(texture->file, file->c_str()); } - ReadAttrInt(elem, "width", &texture->width); - ReadAttrInt(elem, "height", &texture->height); - ReadAttrInt(elem, "nchannel", &texture->nchannel); - ReadAttr(elem, "rgb1", 3, texture->rgb1, text); - ReadAttr(elem, "rgb2", 3, texture->rgb2, text); - ReadAttr(elem, "markrgb", 3, texture->markrgb, text); - ReadAttr(elem, "random", 1, &texture->random, text); - if (MapValue(elem, "builtin", &n, builtin_map, builtin_sz)) { - texture->builtin = (mjtBuiltin)n; - } - if (MapValue(elem, "mark", &n, mark_map, mark_sz)) { - texture->mark = (mjtMark)n; - } - if (MapValue(elem, "hflip", &n, bool_map, 2)) { - texture->hflip = (n != 0); - } - if (MapValue(elem, "vflip", &n, bool_map, 2)) { - texture->vflip = (n != 0); - } - // grid - ReadAttr(elem, "gridsize", 2, texture->gridsize, text); - if (ReadAttrTxt(elem, "gridlayout", text)) { - // check length - if (text.length() > 12) { - throw mjXError(elem, "gridlayout length cannot exceed 12 characters"); - } - if (text.length() != texture->gridsize[0]*texture->gridsize[1]) { - throw mjXError(elem, "gridlayout length must match gridsize"); - } - - memcpy(texture->gridlayout, text.data(), text.length()); + // gridlayout length must equal the gridsize product (value-conditional) + if (ReadAttrTxt(elem, "gridlayout", text) && + text.length() != texture->gridsize[0]*texture->gridsize[1]) { + throw mjXError(elem, "gridlayout length must match gridsize"); } // separate files @@ -3191,23 +2249,15 @@ void mjXReader::Asset(XMLElement* section, const mjVFS* vfs) { // write error info mjs_setString(hfield->info, ("line " + std::to_string(elem->GetLineNum())).c_str()); - // read attributes - string name, content_type; - if (ReadAttrTxt(elem, "name", name)) { - if (mjs_setName(hfield->element, name.c_str())) { - throw mjXError(elem, "%s", mjs_getError(spec)); - } - } - if (ReadAttrTxt(elem, "content_type", content_type)) { - mjs_setString(hfield->content_type, content_type.c_str()); - } + // mechanical attributes + ReadAttrTable(elem, hfield, hfield->element, kHfieldAttrs, + kHfieldAttrsN); + + // file, resolved against the asset directory auto file = ReadAttrFile(elem, "file", vfs, AssetDir()); if (file.has_value()) { mjs_setString(hfield->file, file->c_str()); } - ReadAttrInt(elem, "nrow", &hfield->nrow); - ReadAttrInt(elem, "ncol", &hfield->ncol); - ReadAttr(elem, "size", 4, hfield->size, text, true); // allocate buffer for dynamic hfield, copy user data if given if (!file.has_value() && hfield->nrow > 0 && hfield->ncol > 0) { @@ -3286,7 +2336,6 @@ void mjXReader::Body(XMLElement* section, mjsBody* body, mjsFrame* frame, const mjVFS* vfs) { string text, name; XMLElement* elem; - int n; // sanity check if (!body) { @@ -3317,10 +2366,8 @@ void mjXReader::Body(XMLElement* section, mjsBody* body, mjsFrame* frame, throw mjXError(elem, "World body cannot have inertia"); } body->explicitinertial = true; - ReadAttr(elem, "pos", 3, body->ipos, text, true); + ReadAttrTable(elem, body, body->element, kInertialAttrs, kInertialAttrsN); ReadQuat(elem, "quat", body->iquat, text); - ReadAttr(elem, "mass", 1, &body->mass, text, true); - ReadAttr(elem, "diaginertia", 3, body->inertia, text); ReadAlternative(elem, body->ialt); ReadAttr(elem, "fullinertia", 6, body->fullinertia, text); } @@ -3360,7 +2407,7 @@ void mjXReader::Body(XMLElement* section, mjsBody* body, mjsFrame* frame, } } ReadAttrInt(elem, "group", &joint->group); - MapValue(elem, "align", &joint->align, TFAuto_map, 3); + MapValue(elem, "align", &joint->align, FalseTrueAuto_map, 3); } // geom sub-element @@ -3529,37 +2576,17 @@ void mjXReader::Body(XMLElement* section, mjsBody* body, mjsFrame* frame, // set default from class or childclass mjs_setDefault(child->element, childdef ? childdef : def); - // read attributes - string name, childclass; - if (ReadAttrTxt(elem, "name", name)) { - if (mjs_setName(child->element, name.c_str())) { - throw mjXError(elem, "%s", mjs_getError(spec)); - } - } + // mechanical attributes + ReadAttrTable(elem, child, child->element, kBodyAttrs, kBodyAttrsN); + string childclass; if (ReadAttrTxt(elem, "childclass", childclass)) { mjs_setString(child->childclass, childclass.c_str()); } - ReadAttr(elem, "pos", 3, child->pos, text); + + // orientation alternatives ReadQuat(elem, "quat", child->quat, text); - if (MapValue(elem, "mocap", &n, bool_map, 2)) { - child->mocap = (n == 1); - } ReadAlternative(elem, child->alt); - // gravcomp, sleep policy - ReadAttr(elem, "gravcomp", 1, &child->gravcomp, text); - if (MapValue(elem, "sleep", &n, bodysleep_map, bodysleep_sz)) { - child->sleep = (mjtSleepPolicy) n; - } - if (MapValue(elem, "simple", &n, FAuto_map, 2)) { - child->simple = (mjtByte) n; - } - - // read userdata - std::vector userdata; - ReadVector(elem, "user", userdata, text); - mjs_setDouble(child->userdata, userdata.data(), userdata.size()); - // add frame mjs_setFrame(child->element, frame); @@ -3673,21 +2700,13 @@ void mjXReader::Contact(XMLElement* section) { // body pair to exclude else if (name == "exclude") { mjsExclude* exclude = mjs_addExclude(spec); - string exname, exbody1, exbody2; // write error info mjs_setString(exclude->info, ("line " + std::to_string(elem->GetLineNum())).c_str()); - // read name and body names - if (ReadAttrTxt(elem, "name", exname)) { - if (mjs_setName(exclude->element, exname.c_str())) { - throw mjXError(elem, "%s", mjs_getError(spec)); - } - } - ReadAttrTxt(elem, "body1", exbody1, true); - mjs_setString(exclude->bodyname1, exbody1.c_str()); - ReadAttrTxt(elem, "body2", exbody2, true); - mjs_setString(exclude->bodyname2, exbody2.c_str()); + // read attributes + ReadAttrTable(elem, exclude, exclude->element, kExcludeAttrs, + kExcludeAttrsN); } // advance to next element @@ -3859,61 +2878,25 @@ void mjXReader::Sensor(XMLElement* section) { // create sensor, get string type mjsSensor* sensor = mjs_addSensor(spec); string type = elem->Value(); - string text, name, objname, refname; - std::vector userdata; + string text, objname, refname; - // read name, noise, userdata - if (ReadAttrTxt(elem, "name", name)) { - if (mjs_setName(sensor->element, name.c_str())) { - throw mjXError(elem, "%s", mjs_getError(spec)); - } - } - ReadAttr(elem, "cutoff", 1, &sensor->cutoff, text); - ReadAttr(elem, "noise", 1, &sensor->noise, text); - ReadAttrInt(elem, "nsample", &sensor->nsample); - MapValue(elem, "interp", &sensor->interp, interp_map, interp_sz); - ReadAttr(elem, "delay", 1, &sensor->delay, text); - ReadAttr(elem, "interval", 2, sensor->interval, text, /*required=*/false, /*exact=*/false); - if (ReadVector(elem, "user", userdata, text)) { - mjs_setDouble(sensor->userdata, userdata.data(), userdata.size()); - } + // mechanical attributes shared by all sensor types + ReadAttrTable(elem, sensor, sensor->element, kSensorBaseAttrs, + kSensorBaseAttrsN); // common robotic sensors, attached to a site - if (type == "touch") { - sensor->type = mjSENS_TOUCH; - sensor->objtype = mjOBJ_SITE; - ReadAttrTxt(elem, "site", objname, true); - } else if (type == "accelerometer") { - sensor->type = mjSENS_ACCELEROMETER; - sensor->objtype = mjOBJ_SITE; - ReadAttrTxt(elem, "site", objname, true); - } else if (type == "velocimeter") { - sensor->type = mjSENS_VELOCIMETER; - sensor->objtype = mjOBJ_SITE; - ReadAttrTxt(elem, "site", objname, true); - } else if (type == "gyro") { - sensor->type = mjSENS_GYRO; - sensor->objtype = mjOBJ_SITE; - ReadAttrTxt(elem, "site", objname, true); - } else if (type == "force") { - sensor->type = mjSENS_FORCE; - sensor->objtype = mjOBJ_SITE; - ReadAttrTxt(elem, "site", objname, true); - } else if (type == "torque") { - sensor->type = mjSENS_TORQUE; - sensor->objtype = mjOBJ_SITE; - ReadAttrTxt(elem, "site", objname, true); - } else if (type == "magnetometer") { - sensor->type = mjSENS_MAGNETOMETER; - sensor->objtype = mjOBJ_SITE; - ReadAttrTxt(elem, "site", objname, true); - } else if (type == "camprojection") { - sensor->type = mjSENS_CAMPROJECTION; - sensor->objtype = mjOBJ_SITE; - ReadAttrTxt(elem, "site", objname, true); - ReadAttrTxt(elem, "camera", refname, true); - sensor->reftype = mjOBJ_CAMERA; - } else if (type == "rangefinder") { + // sensors fully described by the schema: constants and references + bool dispatched = false; + for (int i = 0; i < kSensorDispatchN; i++) { + if (type == kSensorDispatch[i].tag) { + ReadAttrTable(elem, sensor, sensor->element, kSensorDispatch[i].rows, + kSensorDispatch[i].n); + dispatched = true; + break; + } + } + if (dispatched) {} + else if (type == "rangefinder") { sensor->type = mjSENS_RANGEFINDER; bool use_site = ReadAttrTxt(elem, "site", objname, false); ReadAttrTxt(elem, "camera", objname, false); @@ -3941,183 +2924,6 @@ void mjXReader::Sensor(XMLElement* section) { } sensor->intprm[0] = dataspec; } - - // sensors related to scalar joints, tendons, actuators - else if (type == "jointpos") { - sensor->type = mjSENS_JOINTPOS; - sensor->objtype = mjOBJ_JOINT; - ReadAttrTxt(elem, "joint", objname, true); - } else if (type == "jointvel") { - sensor->type = mjSENS_JOINTVEL; - sensor->objtype = mjOBJ_JOINT; - ReadAttrTxt(elem, "joint", objname, true); - } else if (type == "tendonpos") { - sensor->type = mjSENS_TENDONPOS; - sensor->objtype = mjOBJ_TENDON; - ReadAttrTxt(elem, "tendon", objname, true); - } else if (type == "tendonvel") { - sensor->type = mjSENS_TENDONVEL; - sensor->objtype = mjOBJ_TENDON; - ReadAttrTxt(elem, "tendon", objname, true); - } else if (type == "actuatorpos") { - sensor->type = mjSENS_ACTUATORPOS; - sensor->objtype = mjOBJ_ACTUATOR; - ReadAttrTxt(elem, "actuator", objname, true); - } else if (type == "actuatorvel") { - sensor->type = mjSENS_ACTUATORVEL; - sensor->objtype = mjOBJ_ACTUATOR; - ReadAttrTxt(elem, "actuator", objname, true); - } else if (type == "actuatorfrc") { - sensor->type = mjSENS_ACTUATORFRC; - sensor->objtype = mjOBJ_ACTUATOR; - ReadAttrTxt(elem, "actuator", objname, true); - } else if (type == "jointactuatorfrc") { - sensor->type = mjSENS_JOINTACTFRC; - sensor->objtype = mjOBJ_JOINT; - ReadAttrTxt(elem, "joint", objname, true); - } else if (type=="tendonactuatorfrc") { - sensor->type = mjSENS_TENDONACTFRC; - sensor->objtype = mjOBJ_TENDON; - ReadAttrTxt(elem, "tendon", objname, true); - } - - // sensors related to ball joints - else if (type == "ballquat") { - sensor->type = mjSENS_BALLQUAT; - sensor->objtype = mjOBJ_JOINT; - ReadAttrTxt(elem, "joint", objname, true); - } else if (type == "ballangvel") { - sensor->type = mjSENS_BALLANGVEL; - sensor->objtype = mjOBJ_JOINT; - ReadAttrTxt(elem, "joint", objname, true); - } - - // joint and tendon limit sensors - else if (type == "jointlimitpos") { - sensor->type = mjSENS_JOINTLIMITPOS; - sensor->objtype = mjOBJ_JOINT; - ReadAttrTxt(elem, "joint", objname, true); - } else if (type == "jointlimitvel") { - sensor->type = mjSENS_JOINTLIMITVEL; - sensor->objtype = mjOBJ_JOINT; - ReadAttrTxt(elem, "joint", objname, true); - } else if (type == "jointlimitfrc") { - sensor->type = mjSENS_JOINTLIMITFRC; - sensor->objtype = mjOBJ_JOINT; - ReadAttrTxt(elem, "joint", objname, true); - } else if (type == "tendonlimitpos") { - sensor->type = mjSENS_TENDONLIMITPOS; - sensor->objtype = mjOBJ_TENDON; - ReadAttrTxt(elem, "tendon", objname, true); - } else if (type == "tendonlimitvel") { - sensor->type = mjSENS_TENDONLIMITVEL; - sensor->objtype = mjOBJ_TENDON; - ReadAttrTxt(elem, "tendon", objname, true); - } else if (type == "tendonlimitfrc") { - sensor->type = mjSENS_TENDONLIMITFRC; - sensor->objtype = mjOBJ_TENDON; - ReadAttrTxt(elem, "tendon", objname, true); - } - - // sensors attached to an object with spatial frame: (x)body, geom, site, camera - else if (type == "framepos") { - sensor->type = mjSENS_FRAMEPOS; - ReadAttrTxt(elem, "objtype", text, true); - sensor->objtype = (mjtObj)mju_str2Type(text.c_str()); - ReadAttrTxt(elem, "objname", objname, true); - if (ReadAttrTxt(elem, "reftype", text)) { - sensor->reftype = (mjtObj)mju_str2Type(text.c_str()); - ReadAttrTxt(elem, "refname", refname, true); - } - } else if (type == "framequat") { - sensor->type = mjSENS_FRAMEQUAT; - ReadAttrTxt(elem, "objtype", text, true); - sensor->objtype = (mjtObj)mju_str2Type(text.c_str()); - ReadAttrTxt(elem, "objname", objname, true); - if (ReadAttrTxt(elem, "reftype", text)) { - sensor->reftype = (mjtObj)mju_str2Type(text.c_str()); - ReadAttrTxt(elem, "refname", refname, true); - } - } else if (type == "framexaxis") { - sensor->type = mjSENS_FRAMEXAXIS; - ReadAttrTxt(elem, "objtype", text, true); - sensor->objtype = (mjtObj)mju_str2Type(text.c_str()); - ReadAttrTxt(elem, "objname", objname, true); - if (ReadAttrTxt(elem, "reftype", text)) { - sensor->reftype = (mjtObj)mju_str2Type(text.c_str()); - ReadAttrTxt(elem, "refname", refname, true); - } - } else if (type == "frameyaxis") { - sensor->type = mjSENS_FRAMEYAXIS; - ReadAttrTxt(elem, "objtype", text, true); - sensor->objtype = (mjtObj)mju_str2Type(text.c_str()); - ReadAttrTxt(elem, "objname", objname, true); - if (ReadAttrTxt(elem, "reftype", text)) { - sensor->reftype = (mjtObj)mju_str2Type(text.c_str()); - ReadAttrTxt(elem, "refname", refname, true); - } - } else if (type == "framezaxis") { - sensor->type = mjSENS_FRAMEZAXIS; - ReadAttrTxt(elem, "objtype", text, true); - sensor->objtype = (mjtObj)mju_str2Type(text.c_str()); - ReadAttrTxt(elem, "objname", objname, true); - if (ReadAttrTxt(elem, "reftype", text)) { - sensor->reftype = (mjtObj)mju_str2Type(text.c_str()); - ReadAttrTxt(elem, "refname", refname, true); - } - } else if (type == "framelinvel") { - sensor->type = mjSENS_FRAMELINVEL; - ReadAttrTxt(elem, "objtype", text, true); - sensor->objtype = (mjtObj)mju_str2Type(text.c_str()); - ReadAttrTxt(elem, "objname", objname, true); - if (ReadAttrTxt(elem, "reftype", text)) { - sensor->reftype = (mjtObj)mju_str2Type(text.c_str()); - ReadAttrTxt(elem, "refname", refname, true); - } - } else if (type == "frameangvel") { - sensor->type = mjSENS_FRAMEANGVEL; - ReadAttrTxt(elem, "objtype", text, true); - sensor->objtype = (mjtObj)mju_str2Type(text.c_str()); - ReadAttrTxt(elem, "objname", objname, true); - if (ReadAttrTxt(elem, "reftype", text)) { - sensor->reftype = (mjtObj)mju_str2Type(text.c_str()); - ReadAttrTxt(elem, "refname", refname, true); - } - } else if (type == "framelinacc") { - sensor->type = mjSENS_FRAMELINACC; - ReadAttrTxt(elem, "objtype", text, true); - sensor->objtype = (mjtObj)mju_str2Type(text.c_str()); - ReadAttrTxt(elem, "objname", objname, true); - } else if (type == "frameangacc") { - sensor->type = mjSENS_FRAMEANGACC; - ReadAttrTxt(elem, "objtype", text, true); - sensor->objtype = (mjtObj)mju_str2Type(text.c_str()); - ReadAttrTxt(elem, "objname", objname, true); - } else if (type == "insidesite") { - sensor->type = mjSENS_INSIDESITE; - sensor->reftype = mjOBJ_SITE; - ReadAttrTxt(elem, "site", refname, true); - ReadAttrTxt(elem, "objtype", text, true); - sensor->objtype = (mjtObj)mju_str2Type(text.c_str()); - ReadAttrTxt(elem, "objname", objname, true); - } - - // sensors related to kinematic subtrees; attached to a body (which is the subtree root) - else if (type == "subtreecom") { - sensor->type = mjSENS_SUBTREECOM; - sensor->objtype = mjOBJ_BODY; - ReadAttrTxt(elem, "body", objname, true); - } else if (type == "subtreelinvel") { - sensor->type = mjSENS_SUBTREELINVEL; - sensor->objtype = mjOBJ_BODY; - ReadAttrTxt(elem, "body", objname, true); - } else if (type == "subtreeangmom") { - sensor->type = mjSENS_SUBTREEANGMOM; - sensor->objtype = mjOBJ_BODY; - ReadAttrTxt(elem, "body", objname, true); - } - - // sensors for geometric distance; attached to geoms or bodies else if (type == "distance" || type == "normal" || type == "fromto") { bool has_body1 = ReadAttrTxt(elem, "body1", objname); ReadAttrTxt(elem, "geom1", objname); @@ -4133,8 +2939,6 @@ void mjXReader::Sensor(XMLElement* section) { sensor->type = mjSENS_GEOMFROMTO; } } - - // sensor for contacts; attached to geoms or bodies or a site else if (type == "contact") { // first matching criterion bool has_site = ReadAttrTxt(elem, "site", objname); @@ -4194,20 +2998,6 @@ void mjXReader::Sensor(XMLElement* section) { // sensor type sensor->type = mjSENS_CONTACT; } - - // global sensors - else if (type == "e_potential") { - sensor->type = mjSENS_E_POTENTIAL; - sensor->objtype = mjOBJ_UNKNOWN; - } else if (type == "e_kinetic") { - sensor->type = mjSENS_E_KINETIC; - sensor->objtype = mjOBJ_UNKNOWN; - } else if (type == "clock") { - sensor->type = mjSENS_CLOCK; - sensor->objtype = mjOBJ_UNKNOWN; - } - - // user-defined sensor else if (type == "user") { sensor->type = mjSENS_USER; ReadAttrTxt(elem, "objname", objname); @@ -4286,53 +3076,19 @@ void mjXReader::Keyframe(XMLElement* section) { // iterate over child elements elem = FirstChildElement(section); while (elem) { - string text, name = ""; + string name = ""; // add keyframe mjsKey* key = mjs_addKey(spec); - // read name, time + // read name: set even when the attribute is absent ReadAttrTxt(elem, "name", name); if (mjs_setName(key->element, name.c_str())) { throw mjXError(elem, "%s", mjs_getError(spec)); } - ReadAttr(elem, "time", 1, &key->time, text); - // read qpos - auto maybe_data = ReadAttrVec(elem, "qpos", false); - if (maybe_data.has_value()) { - mjs_setDouble(key->qpos, maybe_data->data(), maybe_data->size()); - } - - // read qvel - maybe_data = ReadAttrVec(elem, "qvel", false); - if (maybe_data.has_value()) { - mjs_setDouble(key->qvel, maybe_data->data(), maybe_data->size()); - } - - // read act - maybe_data = ReadAttrVec(elem, "act", false); - if (maybe_data.has_value()) { - mjs_setDouble(key->act, maybe_data->data(), maybe_data->size()); - } - - // read mpos - maybe_data = ReadAttrVec(elem, "mpos", false); - if (maybe_data.has_value()) { - mjs_setDouble(key->mpos, maybe_data->data(), maybe_data->size()); - } - - // read mquat - maybe_data = ReadAttrVec(elem, "mquat", false); - if (maybe_data.has_value()) { - mjs_setDouble(key->mquat, maybe_data->data(), maybe_data->size()); - } - - // read ctrl - maybe_data = ReadAttrVec(elem, "ctrl", false); - if (maybe_data.has_value()) { - mjs_setDouble(key->ctrl, maybe_data->data(), maybe_data->size()); - } + // mechanical attributes + ReadAttrTable(elem, key, key->element, kKeyAttrs, kKeyAttrsN); // advance to next element elem = NextSiblingElement(elem); diff --git a/src/xml/xml_native_reader.h b/src/xml/xml_native_reader.h index 3511f31a..d37c44c0 100644 --- a/src/xml/xml_native_reader.h +++ b/src/xml/xml_native_reader.h @@ -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); diff --git a/src/xml/xml_native_writer.cc b/src/xml/xml_native_writer.cc index 0e7ebc7e..d65d697c 100644 --- a/src/xml/xml_native_writer.cc +++ b/src/xml/xml_native_writer.cc @@ -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()); diff --git a/test/doc/doc_test.py b/test/doc/doc_test.py index 3e2b3669..00644785 100644 --- a/test/doc/doc_test.py +++ b/test/doc/doc_test.py @@ -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') diff --git a/test/user/user_objects_test.cc b/test/user/user_objects_test.cc index 03c396f8..fda09851 100644 --- a/test/user/user_objects_test.cc +++ b/test/user/user_objects_test.cc @@ -554,7 +554,7 @@ TEST_F(RelativeFrameSensorParsingTest, BadRefType) { )"; std::array 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")); } diff --git a/test/xml/CMakeLists.txt b/test/xml/CMakeLists.txt index 8aac751e..5d88bef2 100644 --- a/test/xml/CMakeLists.txt +++ b/test/xml/CMakeLists.txt @@ -19,6 +19,8 @@ mujoco_test( ADDITIONAL_LINK_LIBRARIES compare_model ) +mujoco_test(schema_defaults_test) + mujoco_test(xml_utils_test) mujoco_test( diff --git a/test/xml/schema_defaults_test.cc b/test/xml/schema_defaults_test.cc new file mode 100644 index 00000000..f07e7c38 --- /dev/null +++ b/test/xml/schema_defaults_test.cc @@ -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 +#include + +#include +#include +#include +#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 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(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(field)[j]; + break; + case 1: // float: compare at float precision + actual = reinterpret_cast(field)[j]; + expected = static_cast(expected); + break; + case 2: // int-sized, including enums + actual = reinterpret_cast(field)[j]; + break; + case 3: // byte + actual = reinterpret_cast(field)[j]; + break; + case 4: // mjtNum: compare at mjtNum precision + actual = reinterpret_cast(field)[j]; + expected = static_cast(expected); + break; + } + EXPECT_EQ(actual, expected) + << table.structname << "." << entry.attr << "[" << j << "]"; + } + } + } + mj_deleteSpec(spec); +} + +} // namespace +} // namespace mujoco