# 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 MJCF schema using dropdown directives for XMLreference.rst. Generates XMLschema.rst by reading xml_native_reader.cc and producing nested dropdown directives with list-table for attributes. """ import copy import sys import os # Map symbols to icons: # ! = required element, can appear only once -> star (prominent, required) # ? = optional element, can appear only once -> dot (minimal, single) # * = optional element, can appear many times -> None (no icon, most common) # R = optional element, can appear many times recursively -> sync (recursive) SYMBOL_TO_ICON = { '!': 'star', '?': 'dot', '*': None, 'R': 'sync', } ELEMENT_ORDER = [ 'mujoco', 'option', 'compiler', 'size', 'statistic', 'asset', 'body', 'deformable', 'contact', 'equality', 'tendon', 'actuator', 'sensor', 'keyframe', 'visual', 'default', 'custom', 'extension', ] # Special display names for elements (when different from the element name) ELEMENT_DISPLAY_NAME = { 'body': '(world)body', } def generate_dropdown( element_name: str, symbol: str, link_name: str, attributes: list[str], links: list[str], level: int, is_top_level: bool = False, ) -> str: """Generate a dropdown directive for an element with its attributes.""" indent = ' ' * level output = '' display_name = ELEMENT_DISPLAY_NAME.get(element_name, element_name) icon = SYMBOL_TO_ICON.get(symbol) # Element name is a :ref: link. Icon/macro goes on the right side. # Use |*| for * elements (even though it's empty) for future flexibility. element_link = f':ref:`{display_name}<{link_name}>`' if icon: title = f'{element_link} :octicon:`{icon}`' else: title = f'{element_link} |*|' output += f'{indent}.. dropdown:: {title}\n' if is_top_level: output += f'{indent} :open:\n' output += '\n' content_indent = indent + ' ' # Responsive grid for attributes (2-3-4-4: mobile-tablet-desktop-large) if attributes: output += f'{content_indent}.. grid:: 2 3 4 4\n' output += f'{content_indent} :gutter: 0\n' output += '\n' for attr in attributes: att_link_name = f'{link_name}-{attr}' if att_link_name not in links: raise ValueError( f'Link for attribute {att_link_name} not found, update' ' XMLreference.rst' ) output += f'{content_indent} .. grid-item::\n' output += f'{content_indent} :ref:`{attr}<{att_link_name}>`\n' output += '\n' output += '\n' return output def generate() -> str: """Generates XMLschema.rst by parsing mjcf_table.inc. The schema is defined in mjcf_table.inc (generated from mjcf.schema) as a nested structure called MJCF[]. This function parses that structure and generates nested dropdown directives with list-tables for attributes. Returns: RST content with nested dropdown directives for the MJCF schema. """ script_dir = os.path.dirname(os.path.abspath(__file__)) repo_root = os.path.dirname(os.path.dirname(script_dir)) filepath = os.path.join(repo_root, 'src', 'xml', 'generated', 'mjcf_table.inc') xmlfile = os.path.join(repo_root, 'doc', 'XMLreference.rst') # Collect all link targets from XMLreference.rst for validation. links = [] with open(xmlfile, 'r', encoding='utf-8') as file: for line in file: if line.startswith('.. _'): links.append(line.strip()[4:-1]) output = """.. DO NOT EDIT. THIS FILE IS AUTOMATICALLY GENERATED. .. raw:: html