diff --git a/doc/changelog.rst b/doc/changelog.rst index e42fc33c..3254f40f 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -13,6 +13,8 @@ General grammar table, presence constraints, keyword maps, typed attribute bindings and save policies are generated from it and gated by tests, as are the schema's enum keywords and declared defaults against the C headers and default-constructors. +- An XSD schema derived from the main schema is provided in + `model/mjcf.xsd `__. Actuation ^^^^^^^^^ diff --git a/doc/generate/generate_xsd.py b/doc/generate/generate_xsd.py new file mode 100644 index 00000000..f2037f1a --- /dev/null +++ b/doc/generate/generate_xsd.py @@ -0,0 +1,375 @@ +# 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 published XML Schema (mjcf.xsd) from mjcf.schema. + +The XSD is deliberately permissive: it must never reject a legal model. +XSD 1.0 cannot express MJCF's order-insensitive children with per-child +cardinality (xs:all forbids maxOccurs > 1), so every content model is an +unbounded xs:choice, and presence constraints (exclusive/together/requires/ +oneof) are not expressible at all. Both are carried as xs:documentation +annotations. + +Element tags repeat across contexts with different content (e.g. joint under +body, default and equality), so every element is declared locally inside its +parent's complexType; the named complexTypes carry the schema element names. +Inside , children use projected types (attributes minus name/class +minus nodefault), mirroring the grammar-table projection. + +The generated file is checked in as src/xml/generated/mjcf.xsd and gated by +test/doc/doc_test.py, which regenerates it from the schema and diffs. +""" + +import os +import re +import sys +from xml.sax.saxutils import escape + +_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') +MJMODEL_H_PATH = os.path.join(_REPO_ROOT, 'include', 'mujoco', 'mjmodel.h') + +# schema scalar type -> XSD base type +SCALAR_XSD = {'int': 'xs:int', 'double': 'xs:double', 'float': 'xs:float', + 'string': 'xs:string', 'file': 'xs:string'} + +_CONSTRAINT_TEXT = { + 'exclusive': 'at most one of', + 'together': 'together or absent', + 'requires': 'first requires second', + 'oneof': 'at least one of', +} + + +def parse_dims(): + """Return {name: value} for the mjN* dimension macros in mjmodel.h.""" + with open(MJMODEL_H_PATH, encoding='utf-8') as f: + text = f.read() + return {m.group(1): int(m.group(2)) + for m in re.finditer(r'^#define\s+(mjN\w+)\s+(\d+)', text, re.M)} + + +def _num(value): + """Format a numeric default the shortest exact way: 2.0 -> '2'.""" + if isinstance(value, float) and value == int(value) and abs(value) < 1e15: + return str(int(value)) + return repr(value) + + +def _default_str(attr): + """Return the attribute default as an XSD default string, or None.""" + d = attr.default + if d is None: + return None + if isinstance(d, tuple): + return ' '.join(_num(v) for v in d) + if isinstance(d, (int, float)): + return _num(d) + return str(d) + + +class _Emitter: + """Accumulates the XSD document with shared simple types deduped.""" + + def __init__(self, schema): + self.schema = schema + self.dims = parse_dims() + self.lines = [] + self.vector_types = {} # type name -> definition lines + self.emitted = set() # (element name, projected) complexTypes emitted + self.pending = [] + + def out(self, indent, text): + """Appends a line of text at the specified indentation level.""" + self.lines.append(' ' * indent + text) + + def doc(self, indent, texts): + """Emit an annotation block for the given documentation lines.""" + texts = [t for t in texts if t] + if not texts: + return + self.out(indent, '') + for t in texts: + self.out(indent + 2, f'{escape(t)}') + self.out(indent, '') + + def resolve(self, bound): + """Resolve an arity bound: int, mjN* macro name, or None.""" + if isinstance(bound, str): + return self.dims[bound] + return bound + + def vector_type(self, base, lo, hi): + """Return (and register) the named list type for a vector attribute.""" + if hi is None: + name = f'{base}list' + elif lo == hi: + name = f'{base}{hi}' + else: + name = f'{base}{lo}to{hi}' + if name not in self.vector_types: + lines = [f''] + if hi is None and lo <= 1: + lines += [f' '] + else: + lines += [' ', + ' ', + f' ', + ' '] + if lo == hi: + lines += [f' '] + else: + if lo > 0: + lines += [f' '] + if hi is not None: + lines += [f' '] + lines += [' '] + lines += [''] + self.vector_types[name] = lines + return name + + def attr_type(self, attr, ctx): + """Return (type name or None, inline restriction lines) for an attribute. + + A None type name with inline lines means the attribute carries an + anonymous simpleType restriction. + """ + numeric_facets = [f for f in ('min', 'max', 'positive') + if f in attr.facets or attr.facets.get(f)] + if attr.type == 'bool': + return 'kw_bool', [] + if attr.type == 'enum': + return f'kw_{attr.target}', [] + if attr.type == 'flags': + return f'kwlist_{attr.target}', [] + if attr.type in ('string', 'file', 'ref', 'id'): + return 'xs:string', [] + if attr.type == 'chars': + lo, hi = attr.arity.lo, attr.arity.hi + lines = ['', ' '] + if 'pattern' in attr.facets: + lines += [f' '] + elif lo == hi: + lines += [f' '] + else: + lines += [f' ', + f' '] + lines += [' ', ''] + return None, lines + # numeric scalars and vectors + base = SCALAR_XSD[attr.type] + lo, hi = attr.arity.lo, self.resolve(attr.arity.hi) + if (lo, hi) == (1, 1): + if not numeric_facets: + return base, [] + lines = ['', f' '] + if 'min' in attr.facets: + lines += [f' '] + if 'max' in attr.facets: + lines += [f' '] + if attr.facets.get('positive'): + lines += [' '] + lines += [' ', ''] + return None, lines + if numeric_facets: + raise ValueError(f'{ctx}.{attr.name}: numeric facets on a vector ' + 'attribute are not supported by the XSD emitter') + return self.vector_type(attr.type, lo, hi), [] + + def emit_attr(self, indent, attr, ctx): + """Emit one xs:attribute.""" + tname, inline = self.attr_type(attr, ctx) + parts = [f'name="{attr.name}"'] + if tname: + parts.append(f'type="{tname}"') + if attr.facets.get('required'): + parts.append('use="required"') + default = _default_str(attr) + if default is not None: + parts.append(f'default="{escape(default)}"') + head = f'') + return + self.out(indent, head + '>') + self.doc(indent + 2, [attr.doc]) + for line in inline: + self.out(indent + 2, line) + self.out(indent, '') + + def type_name(self, element_name, projected): + return f'default_{element_name}' if projected else element_name + + def constraint_docs(self, element): + """Presence constraints as documentation lines (not XSD-expressible).""" + import generate_mjcf_table + docs = [] + for con in generate_mjcf_table._element_constraints(self.schema, element): + bundles = ['+'.join(b) for b in con.bundles] + docs.append(f'constraint: {_CONSTRAINT_TEXT[con.kind]}: ' + f'{", ".join(bundles)}') + return docs + + def emit_complex_type(self, name, projected): + """Emit the complexType for one schema element (possibly projected).""" + element = self.schema.elements[name] + tname = self.type_name(name, projected) + self.out(2, f'') + docs = [element.doc] if not projected else [] + docs += self.constraint_docs(element) + cards = [f'{c.name} ({c.card})' for c in element.children()] + if cards: + docs.append('children, with cardinality the XSD cannot enforce ' + '(? at most one, ! exactly one, * any number, R recursive): ' + + ', '.join(cards)) + self.doc(4, docs) + + children = list(element.children()) + if projected: + children = [c for c in children if c.name != 'plugin'] + if children: + self.out(4, '') + for child in children: + target = self.schema.elements[child.name] + tag = target.xml_name() + if name == 'mujoco' and child.name == 'body': + # the top-level body is spelled worldbody (mjXSchema::NameMatch) + target = self.schema.elements['worldbody'] + tag = 'worldbody' + child_projected = (projected or + (name == 'default' and + not child.name.startswith('default_') and + child.name != 'default')) + ctype = self.type_name(target.name, child_projected) + self.out(6, f'') + self.queue(target.name, child_projected) + # the include directive is spliced before parsing and may appear + # anywhere; admit it in every content model + self.out(6, '') + self.out(4, '') + + attrs = self.schema.expanded_attrs(element) + if projected: + attrs = [a for a in attrs + if a.name not in ('name', 'class') + and not a.facets.get('nodefault')] + for attr in attrs: + self.emit_attr(4, attr, tname) + self.out(2, '') + self.out(0, '') + + def queue(self, name, projected): + """Enqueues an element name and projection flag for processing.""" + self.pending.append((name, projected)) + + def generate(self): + """Return the complete XSD document as a string.""" + schema = self.schema + self.out(0, '') + self.out(0, '') + self.out(0, '') + self.out(0, '') + + # keyword simple types: bool, then one per schema enum + self.out(2, '') + self.out(4, '') + for kw in ('false', 'true'): + self.out(6, f'') + self.out(4, '') + self.out(2, '') + self.out(0, '') + flags_targets = set() + for element in schema.elements.values(): + for attr in schema.expanded_attrs(element): + if attr.type == 'flags': + flags_targets.add(attr.target) + for enum in schema.enums.values(): + self.out(2, f'') + self.doc(4, [enum.doc]) + self.out(4, '') + for kw, _ in enum.items: + self.out(6, f'') + self.out(4, '') + self.out(2, '') + if enum.name in flags_targets: + self.out(2, f'') + self.out(4, f'') + self.out(2, '') + self.out(0, '') + + # complexTypes, walked from the root so projections are discovered; + # vector simple types are collected on the way and emitted after + body_mark = len(self.lines) + self.pending = [('mujoco', False)] + while self.pending: + name, projected = self.pending.pop(0) + if (name, projected) in self.emitted: + continue + self.emitted.add((name, projected)) + self.emit_complex_type(name, projected) + + unreached = set(schema.elements) - {n for n, _ in self.emitted} + if unreached: + raise ValueError(f'elements unreachable from mujoco: {unreached}') + + # the include directive: spliced before parsing, not part of mjcf.schema + self.out(2, '') + self.doc(4, ['includes another MJCF file; resolved before parsing']) + self.out(4, '') + self.out(2, '') + self.out(0, '') + + vec_lines = [] + for tname in sorted(self.vector_types): + for line in self.vector_types[tname]: + vec_lines.append(' ' + line) + vec_lines.append('') + self.lines[body_mark:body_mark] = vec_lines + + self.out(2, '') + self.out(0, '') + return '\n'.join(self.lines) + '\n' + + +def generate(): + """Generate the mjcf.xsd content as a string.""" + schema = mjcf_schema.parse_file(SCHEMA_PATH) + return _Emitter(schema).generate() + + +def main(): + """CLI entry point: generate mjcf.xsd to stdout or a file.""" + if len(sys.argv) > 2: + sys.exit('usage: generate_xsd.py [output.xsd]') + text = generate() + if len(sys.argv) == 2: + with open(sys.argv[1], 'w', encoding='utf-8') as f: + f.write(text) + else: + sys.stdout.write(text) + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/src/xml/generated/mjcf.xsd b/src/xml/generated/mjcf.xsd new file mode 100644 index 00000000..02ff7965 --- /dev/null +++ b/src/xml/generated/mjcf.xsd @@ -0,0 +1,4074 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + bitflags: keywords combine bitwise + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + object kinds with a spatial frame + + + + + + + + + + + + + bitflags: keywords combine bitwise + + + + + + + + + + + + + + + + + + bitflags: keywords combine bitwise + + + + + + + + + + + + + + + + + bitflags: keywords combine bitwise + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + vestigial: composite pruning left a single kind + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + children, with cardinality the XSD cannot enforce (? at most one, ! exactly one, * any number, R recursive): compiler (*), option (*), size (*), statistic (*), visual (*), default (R), extension (*), asset (*), body (R), deformable (*), contact (*), tendon (*), equality (*), actuator (*), sensor (*), custom (*), keyframe (*) + + + + + + + + + + + + + + + + + + + + + + + + + + + children, with cardinality the XSD cannot enforce (? at most one, ! exactly one, * any number, R recursive): lengthrange (?) + + + + + + + + + + + + + stored on the spec + + + + + deprecation error + + + + + + + + + + + + + + + + + + + + + + fans out to mesh/texturedir + + + + + + + + + children, with cardinality the XSD cannot enforce (? at most one, ! exactly one, * any number, R recursive): flag (?) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + bits of disableactuator + + + + + + + constraint: at most one of: memory, nstack + constraint: at most one of: memory, njmax + + + + suffixed byte count + + + + + range/exclusivity checks + + + + + range check + + + + + range/exclusivity checks + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + when defined + + + + + + + + + + + + + children, with cardinality the XSD cannot enforce (? at most one, ! exactly one, * any number, R recursive): global (?), quality (?), headlight (?), map (?), scale (?), rgba (?) + + + + + + + + + + + + + + + children, with cardinality the XSD cannot enforce (? at most one, ! exactly one, * any number, R recursive): default (R), mesh (?), material (?), joint (?), geom (?), site (?), camera (?), light (?), pair (?), default_equality (?), default_tendon (?), general (?), motor (?), position (?), velocity (?), intvelocity (?), orientation (?), pid (?), damper (?), cylinder (?), muscle (?), adhesion (?), dcmotor (?) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + children, with cardinality the XSD cannot enforce (? at most one, ! exactly one, * any number, R recursive): extension_plugin (*) + + + + + + + + + + children, with cardinality the XSD cannot enforce (? at most one, ! exactly one, * any number, R recursive): mesh (*), hfield (*), skin (*), texture (*), material (*), model (*) + + + + + + + + + + + + + + + children, with cardinality the XSD cannot enforce (? at most one, ! exactly one, * any number, R recursive): body (*), frame (*), replicate (*), geom (*), attach (*), site (*), camera (*), light (*), plugin (*), composite (*), flexcomp (*) + + + + + + + + + + + + + + + + + + + + children, with cardinality the XSD cannot enforce (? at most one, ! exactly one, * any number, R recursive): flex (*), skin (*) + + + + + + + + + + + children, with cardinality the XSD cannot enforce (? at most one, ! exactly one, * any number, R recursive): pair (*), exclude (*) + + + + + + + + + + + children, with cardinality the XSD cannot enforce (? at most one, ! exactly one, * any number, R recursive): spatial (*), fixed (*) + + + + + + + + + + + children, with cardinality the XSD cannot enforce (? at most one, ! exactly one, * any number, R recursive): connect (*), weld (*), equality_joint (*), equality_tendon (*), equality_flex (*), flexvert (*), flexstrain (*) + + + + + + + + + + + + + + + + children, with cardinality the XSD cannot enforce (? at most one, ! exactly one, * any number, R recursive): general (*), motor (*), position (*), velocity (*), intvelocity (*), orientation (*), pid (*), damper (*), cylinder (*), muscle (*), adhesion (*), dcmotor (*), actuator_plugin (*) + + + + + + + + + + + + + + + + + + + + + + children, with cardinality the XSD cannot enforce (? at most one, ! exactly one, * any number, R recursive): touch (*), accelerometer (*), velocimeter (*), gyro (*), force (*), torque (*), magnetometer (*), camprojection (*), rangefinder (*), 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 (*), subtreecom (*), subtreelinvel (*), subtreeangmom (*), insidesite (*), distance (*), normal (*), fromto (*), sensor_contact (*), e_potential (*), e_kinetic (*), clock (*), tactile (*), user (*), sensor_plugin (*) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + children, with cardinality the XSD cannot enforce (? at most one, ! exactly one, * any number, R recursive): numeric (*), text (*), tuple (*) + + + + + + + + + + + + children, with cardinality the XSD cannot enforce (? at most one, ! exactly one, * any number, R recursive): key (*) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + constraint: at most one of: builtin, file + constraint: at most one of: builtin, vertex + children, with cardinality the XSD cannot enforce (? at most one, ! exactly one, * any number, R recursive): plugin (*) + + + + + + + + + children, with cardinality the XSD cannot enforce (? at most one, ! exactly one, * any number, R recursive): layer (*) + + + + + + + + + + + + + + + + + + + + + + + saved unless free + + + + + saved for slide/hinge + + + + + compile directive: saved as stiffness/damping + + + + + saved unless free + + + + + saved for slide/hinge + + + + + + + + + spring polynomial, 1+mjNPOLY coefficients + + + + + + + + + + + + damper polynomial, 1+mjNPOLY coefficients + + + + + + + + + children, with cardinality the XSD cannot enforce (? at most one, ! exactly one, * any number, R recursive): plugin (*) + + + + + + + + + + saved length is type-dependent + + + + + + + mass/density: one is saved + + + + + + saved unless mesh + + + + + + + + + + + + compile directive: saved as pos/quat/size + + + + + saved in the mesh-corrected frame + + + + + + + + + + + + compile directive: not saved + + + + + + + + + + + + + + + + + + + + + saved length is type-dependent + + + + + compile directive: saved as pos/quat/size + + + + + + + + + constraint: at most one of: fovy, sensorsize + + + + + fovy or the intrinsics family is saved + + + + + + + + + + + + + + + + + + + + + + + constraint: at most one of: directional, type + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + damper polynomial, 1+mjNPOLY coefficients + + + + + + slidercrank-only, validated + + + + + saved default depends on dyntype + + + + + so3 chart keyword, or servo token subset + + + + + + + + gain/bias family is not + + + + + saved for plugin actuators + + + + + + + + + + + + + + + + + + + + + + damper polynomial, 1+mjNPOLY coefficients + + + + + + slidercrank-only, validated + + + + + + + + + + + + + + + + + + + damper polynomial, 1+mjNPOLY coefficients + + + + + + slidercrank-only, validated + + + + + + + + + + + + + + + + + + + + + + damper polynomial, 1+mjNPOLY coefficients + + + + + + slidercrank-only, validated + + + + + + + + + + + + + + + + + + + + + + damper polynomial, 1+mjNPOLY coefficients + + + + + + slidercrank-only, validated + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + alias: the position-setpoint range + + + + + + + + + + damper polynomial, 1+mjNPOLY coefficients + + + + + + slidercrank-only, validated + + + + + + + + + + + + + + + + + + + + + + + + damper polynomial, 1+mjNPOLY coefficients + + + + + + slidercrank-only, validated + + + + + + + + + + + + + + + + + + + damper polynomial, 1+mjNPOLY coefficients + + + + + + slidercrank-only, validated + + + + + + + + + + + + + + + + + + + + + + damper polynomial, 1+mjNPOLY coefficients + + + + + + slidercrank-only, validated + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + damper polynomial, 1+mjNPOLY coefficients + + + + + + slidercrank-only, validated + + + + + + + + + + + + + + + + + children, with cardinality the XSD cannot enforce (? at most one, ! exactly one, * any number, R recursive): instance (*) + + + + + + + + + + + constraint: at most one of: builtin, file + constraint: at most one of: builtin, vertex + children, with cardinality the XSD cannot enforce (? at most one, ! exactly one, * any number, R recursive): plugin (*) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + flipped and zero-filled + + + + + + + children, with cardinality the XSD cannot enforce (? at most one, ! exactly one, * any number, R recursive): bone (*) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + length must equal the gridsize product + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + children, with cardinality the XSD cannot enforce (? at most one, ! exactly one, * any number, R recursive): layer (*) + + + + + + + + + + + + + + + + + + + + + + + + + + + + children, with cardinality the XSD cannot enforce (? at most one, ! exactly one, * any number, R recursive): body (R), frame (R), replicate (*), inertial (?), joint (*), freejoint (*), geom (*), attach (*), site (*), camera (*), light (*), plugin (*), composite (*), flexcomp (*) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + children, with cardinality the XSD cannot enforce (? at most one, ! exactly one, * any number, R recursive): body (R), frame (R), replicate (*), inertial (?), joint (*), freejoint (*), geom (*), attach (*), site (*), camera (*), light (*), plugin (*), composite (*), flexcomp (*) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + children, with cardinality the XSD cannot enforce (? at most one, ! exactly one, * any number, R recursive): body (*), frame (*), replicate (R), inertial (?), joint (*), geom (*), attach (*), site (*), camera (*), light (*), plugin (*), composite (*) + + + + + + + + + + + + + + + + + + + + + + + + + + children, with cardinality the XSD cannot enforce (? at most one, ! exactly one, * any number, R recursive): plugin (*) + + + + + + + + + + + + + + + + saved length is type-dependent + + + + + + + mass/density: one is saved + + + + + + saved unless mesh + + + + + + + + + + + + compile directive: saved as pos/quat/size + + + + + saved in the mesh-corrected frame + + + + + + + + + + + + compile directive: not saved + + + + + + + + + + + constraint: at most one of: body, frame + + + + + + + + + + + + + + + + + + + + + + saved length is type-dependent + + + + + compile directive: saved as pos/quat/size + + + + + + + + + constraint: at most one of: fovy, sensorsize + + + + + + + fovy or the intrinsics family is saved + + + + + + + + + + + + + + + + + + + + + + + + constraint: at most one of: directional, type + + + + + + + + + + + + + + + + + + + + + + + + + + children, with cardinality the XSD cannot enforce (? at most one, ! exactly one, * any number, R recursive): config (*) + + + + + + + + + + + + children, with cardinality the XSD cannot enforce (? at most one, ! exactly one, * any number, R recursive): composite_joint (*), composite_skin (?), composite_geom (?), composite_site (?), plugin (*) + + + + + + + + + + + + + + + + + + + + + + + children, with cardinality the XSD cannot enforce (? at most one, ! exactly one, * any number, R recursive): flexcomp_edge (?), elasticity (?), flexcomp_contact (?), pin (*), plugin (*) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + children, with cardinality the XSD cannot enforce (? at most one, ! exactly one, * any number, R recursive): flexcomp_contact (?), flex_edge (?), elasticity (?) + + + + + + + + + + + + + + + + + space-separated body names + + + + + + + + + space-separated body names + + + + + seeded to {1,1,1} before reading + + + + + lowers to interpolation order + + + + + + + + + + + + + + + + + + + + + + + + + + + + children, with cardinality the XSD cannot enforce (? at most one, ! exactly one, * any number, R recursive): spatial_site (*), spatial_geom (*), pulley (*) + + + + + + + + + + + + + + + + + + + + + + one value: copied to both + + + + + + + + spring polynomial, 1+mjNPOLY coefficients + + + + + damper polynomial, 1+mjNPOLY coefficients + + + + + + + + + + children, with cardinality the XSD cannot enforce (? at most one, ! exactly one, * any number, R recursive): fixed_joint (*) + + + + + + + + + + + + + + + + + + + + one value: copied to both + + + + + + spring polynomial, 1+mjNPOLY coefficients + + + + + damper polynomial, 1+mjNPOLY coefficients + + + + + + + + + constraint: at most one of: site1+site2, body1+body2+anchor + constraint: at least one of: site1+site2, body1+anchor + constraint: together or absent: site1, site2 + + + + + + + + + + + + + + + + constraint: at most one of: site1+site2, body1+body2+anchor+relpose + constraint: at least one of: site1+site2, body1 + constraint: together or absent: site1, site2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + damper polynomial, 1+mjNPOLY coefficients + + + + + + slidercrank-only, validated + + + + + + + + + + + + transmission target + + + + + saved default depends on dyntype + + + + + so3 chart keyword, or servo token subset + + + + + + + + gain/bias family is not + + + + + saved for plugin actuators + + + + + + + + + + + + + + + + + + + + + + + + + damper polynomial, 1+mjNPOLY coefficients + + + + + + slidercrank-only, validated + + + + + + + + + + + + + + + + + + + + + + + + + + + + + damper polynomial, 1+mjNPOLY coefficients + + + + + + slidercrank-only, validated + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + damper polynomial, 1+mjNPOLY coefficients + + + + + + slidercrank-only, validated + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + damper polynomial, 1+mjNPOLY coefficients + + + + + + slidercrank-only, validated + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + alias: the position-setpoint range + + + + + + + + + + + damper polynomial, 1+mjNPOLY coefficients + + + + + + slidercrank-only, validated + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + damper polynomial, 1+mjNPOLY coefficients + + + + + + slidercrank-only, validated + + + + + + + + + + + + + + + + + + + + + + + + + + + + + damper polynomial, 1+mjNPOLY coefficients + + + + + + slidercrank-only, validated + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + damper polynomial, 1+mjNPOLY coefficients + + + + + + slidercrank-only, validated + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + damper polynomial, 1+mjNPOLY coefficients + + + + + + slidercrank-only, validated + + + + + + + + + + + + + + + + + + + + + + + + children, with cardinality the XSD cannot enforce (? at most one, ! exactly one, * any number, R recursive): config (*) + + + + + + + + + + + + + + + + + + + + + + + + + damper polynomial, 1+mjNPOLY coefficients + + + + + + slidercrank-only, validated + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + constraint: at most one of: site, camera + constraint: at least one of: site, camera + + + + + + + + + + + + + + ordering-checked + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + constraint: together or absent: reftype, refname + + + + + + + + + + + + + + + + + + constraint: together or absent: reftype, refname + + + + + + + + + + + + + + + + + + constraint: together or absent: reftype, refname + + + + + + + + + + + + + + + + + + constraint: together or absent: reftype, refname + + + + + + + + + + + + + + + + + + constraint: together or absent: reftype, refname + + + + + + + + + + + + + + + + + + constraint: together or absent: reftype, refname + + + + + + + + + + + + + + + + + + constraint: together or absent: reftype, refname + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + constraint: at most one of: geom1, body1 + constraint: at least one of: geom1, body1 + constraint: at most one of: geom2, body2 + constraint: at least one of: geom2, body2 + + + + + + + + + + + + + + + + + + constraint: at most one of: geom1, body1 + constraint: at least one of: geom1, body1 + constraint: at most one of: geom2, body2 + constraint: at least one of: geom2, body2 + + + + + + + + + + + + + + + + + + constraint: at most one of: geom1, body1 + constraint: at least one of: geom1, body1 + constraint: at most one of: geom2, body2 + constraint: at least one of: geom2, body2 + + + + + + + + + + + + + + + + + + constraint: at most one of: geom1, body1, subtree1, site + constraint: at most one of: geom2, body2, subtree2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + constraint: together or absent: objtype, objname + + + + + + + + + + + + + + + children, with cardinality the XSD cannot enforce (? at most one, ! exactly one, * any number, R recursive): config (*) + + + + + + + + + + + + objtype/objname, reftype/refname: pairwise + + + + + co-occurrence enforced by the reader + + + + + + + + + + + + + + + + + + + + + children, with cardinality the XSD cannot enforce (? at most one, ! exactly one, * any number, R recursive): element (*) + + + + + + + + + + + + set even when absent + + + + + + + + + + + + + + + + + + + children, with cardinality the XSD cannot enforce (? at most one, ! exactly one, * any number, R recursive): config (*) + + + + + + + + + + + + + + + + + + + + + + + + projects into the parent body's i-frame + constraint: at most one of: fullinertia, quat, axisangle, xyaxes, zaxis, euler + + + + + + + + + + + + + + + + + + + + saved unless free + + + + + saved for slide/hinge + + + + + compile directive: saved as stiffness/damping + + + + + saved unless free + + + + + saved for slide/hinge + + + + + + + + + spring polynomial, 1+mjNPOLY coefficients + + + + + + + + + + + + damper polynomial, 1+mjNPOLY coefficients + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + includes another MJCF file; resolved before parsing + + + + + + diff --git a/test/doc/doc_test.py b/test/doc/doc_test.py index a666bb70..f213c51f 100644 --- a/test/doc/doc_test.py +++ b/test/doc/doc_test.py @@ -29,6 +29,7 @@ import generate_mjcf_map import generate_mjcf_table import generate_read_table import generate_schema +import generate_xsd import mjcf_schema # Functions in headers that are intentionally not in functions.rst. @@ -128,6 +129,14 @@ class DocTest(googletest.TestCase): generate_read_table.generate(), ) + def test_xsd(self): + """Checks that mjcf.xsd matches the schema-generated output.""" + _check_up_to_date( + self, + 'src/xml/generated/mjcf.xsd', + generate_xsd.generate(), + ) + def test_read_table_consumed(self): """Checks that every generated row array is consumed, and none is stale.