mujoco-mojo 2.4.2__tar.gz → 2.4.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/PKG-INFO +2 -2
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/pyproject.toml +1 -1
- mujoco_mojo-2.4.4/scripts/check_mjcf_spec_coverage.py +650 -0
- mujoco_mojo-2.4.4/scripts/mjcf_ignore.txt +96 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/__init__.py +52 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/actuator.py +1 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/actuator_attr/adhesion.py +24 -1
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/actuator_attr/base.py +18 -1
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/actuator_attr/motor.py +2 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/actuator_attr/plugin.py +2 -1
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/actuator_attr/velocity.py +1 -1
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/asset_attr/mesh.py +5 -4
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/asset_attr/texture.py +2 -1
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/body.py +4 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/body_attr/camera.py +1 -1
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/body_attr/composite.py +3 -3
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/body_attr/composite_attr/geom.py +23 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/body_attr/composite_attr/joint.py +2 -2
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/body_attr/composite_attr/site.py +3 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/body_attr/composite_attr/skin.py +4 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/body_attr/flexcomp.py +1 -1
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/body_attr/flexcomp_attr/contact.py +4 -1
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/body_attr/free_joint.py +4 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/body_attr/geom.py +2 -1
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/body_attr/inertial.py +21 -19
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/body_attr/joint.py +29 -4
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/body_attr/site.py +2 -1
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/contact_attr/exclude.py +1 -1
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/contact_attr/pair.py +1 -1
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/deformable_attr/flex_attr/contact.py +2 -2
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/equality_attr/connect.py +1 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/equality_attr/equality_base.py +2 -1
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/equality_attr/tendon.py +2 -3
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/equality_attr/weld.py +6 -6
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/option_attr/flag.py +4 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/sensor_attr/base.py +2 -1
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/sensor_attr/base_collision.py +3 -1
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/sensor_attr/contact.py +6 -6
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/sensor_attr/insidesite.py +1 -1
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/sensor_attr/plugin.py +3 -1
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/sensor_attr/tactile.py +6 -2
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/tendon_attr/spatial_attr/geom.py +1 -1
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/tendon_attr/tendon_base.py +2 -1
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/orientation.py +7 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/pose.py +2 -1
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/xml_model.py +36 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/runtime/video_recorder.py +25 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/stochas/__init__.py +42 -2
- mujoco_mojo-2.4.4/src/mujoco_mojo/templates/dojo.sh +8 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/typing.py +3 -3
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/cli.py +6 -6
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/routers/mosaic.py +52 -18
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/partials/trial_viewer/_distributions.html +159 -113
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/partials/trial_viewer/_series_panel.html +10 -10
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/partials/trial_viewer/_trial_logs.html +37 -37
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/static/js/trial-viewer.js +130 -55
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/static/ts/src/lib/format.ts +6 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/static/ts/src/models.ts +3 -1
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/static/ts/src/trial-viewer.ts +156 -58
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/reloaded.py +1 -1
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/proximity.py +40 -16
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/runner.py +26 -1
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/tests/runtime/monte_carlo_bumper.py +288 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/tests/utils/proximity_test.py +36 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/tests/utils/runner_test.py +47 -0
- mujoco_mojo-2.4.2/tests/one_off/proximity/dojo.sh +0 -4
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/.gitattributes +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/.github/workflows/docs.yml +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/.github/workflows/release.yml +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/.gitignore +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/.pre-commit-config.yaml +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/.python-version +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/.vscode/settings.json +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/LICENSE +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/README.md +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/TODO.md +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/docs/about.md +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/docs/assets/dark-hero-logo.png +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/docs/assets/dark-hero-logo.svg +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/docs/assets/dark-logo.svg +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/docs/assets/light-hero-logo.svg +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/docs/assets/light-logo.svg +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/docs/assets/logo.svg +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/docs/assets/mesh/bunny_original.jpg +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/docs/assets/mesh/bunny_with_coacd.jpg +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/docs/assets/mesh/bunny_without_coacd.jpg +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/docs/assets/mesh/frustums.jpg +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/docs/assets/mesh/proximity/convex_hull_wireframe.jpg +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/docs/assets/mesh/proximity/real_wireframe.jpg +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/docs/assets/mesh/with_coacd.jpg +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/docs/assets/mesh/without_coacd.jpg +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/docs/assets/user-guides/dark-export-options.jpg +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/docs/assets/user-guides/dark-monitor-view.jpg +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/docs/assets/user-guides/dark-mosaic-view.jpg +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/docs/assets/user-guides/dark-notes-editor.jpg +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/docs/assets/user-guides/dark-plot-editor.jpg +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/docs/assets/user-guides/dark-selector-bar.jpg +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/docs/assets/user-guides/dark-shapes-editor.jpg +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/docs/assets/user-guides/generate-result.jpg +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/docs/assets/user-guides/light-export-options.jpg +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/docs/assets/user-guides/light-monitor-view.jpg +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/docs/assets/user-guides/light-mosaic-view.jpg +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/docs/assets/user-guides/light-notes-editor.jpg +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/docs/assets/user-guides/light-plot-editor.jpg +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/docs/assets/user-guides/light-selector-bar.jpg +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/docs/assets/user-guides/light-shapes-editor.jpg +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/docs/assets/user-guides/runtime-anim.gif +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/docs/index.md +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/docs/javascripts/favicon-switcher.js +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/docs/javascripts/mojo-highlighter.js +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/docs/javascripts/tablesort.js +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/docs/stylesheets/extra.css +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/docs/user-guides/MOJO_RUNTIME_REPORT.md +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/docs/user-guides/dojo.md +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/docs/user-guides/generate-script.md +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/docs/user-guides/getting-started.md +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/docs/user-guides/init.md +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/docs/user-guides/monitor.md +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/docs/user-guides/monte_carlo_example.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/docs/user-guides/mosaic.md +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/docs/user-guides/optimization.md +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/docs/user-guides/optimization_example.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/docs/user-guides/pose-context.md +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/docs/user-guides/pose_context_example.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/docs/user-guides/reloaded.md +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/docs/user-guides/running-jobs.md +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/docs/user-guides/runtime-script.md +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/examples/boxes_and_springs_monte_carlo.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/examples/boxes_and_springs_optimization.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/examples/tennis_racket_theorem.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/mkdocs.yml +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/requirements.txt +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/scripts/copy_docs_examples_to_examples.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/scripts/fix_smart_quotes.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/scripts/gen_ref_pages.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/scripts/gen_ts_models.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/scripts/templating/filestomake.txt +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/scripts/templating/make_sensors.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/scripts/templating/sensor_template.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/__about__.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/base.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/meta.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mj_state.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/__init__.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/defaults.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/dependency_path.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/extension.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/meta/__init__.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/meta/frame.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/meta/include.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/meta/replicate.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/__init__.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/actuator_attr/__init__.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/actuator_attr/cylinder.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/actuator_attr/damper.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/actuator_attr/dcmotor.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/actuator_attr/general.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/actuator_attr/intvelocity.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/actuator_attr/muscle.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/actuator_attr/position.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/asset.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/asset_attr/__init__.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/asset_attr/hfield.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/asset_attr/material.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/asset_attr/material_attr/__init__.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/asset_attr/material_attr/layer.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/asset_attr/model.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/body_attr/__init__.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/body_attr/attach.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/body_attr/composite_attr/__init__.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/body_attr/flexcomp_attr/__init__.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/body_attr/flexcomp_attr/edge.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/body_attr/flexcomp_attr/elasticity.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/body_attr/flexcomp_attr/pin.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/body_attr/light.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/body_attr/plugin.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/compiler.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/compiler_attr/__init__.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/compiler_attr/lengthrange.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/contact.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/contact_attr/__init__.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/deformable.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/deformable_attr/__init__.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/deformable_attr/flex.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/deformable_attr/flex_attr/__init__.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/deformable_attr/flex_attr/edge.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/deformable_attr/flex_attr/elasticity.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/deformable_attr/skin.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/deformable_attr/skin_attr/__init__.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/deformable_attr/skin_attr/bone.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/equality.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/equality_attr/__init__.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/equality_attr/flex.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/equality_attr/flexstrain.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/equality_attr/flexvert.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/equality_attr/joint.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/keyframe.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/keyframe_attr/__init__.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/keyframe_attr/key.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/option.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/option_attr/__init__.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/sensor.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/sensor_attr/__init__.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/sensor_attr/accelerometer.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/sensor_attr/actuatorfrc.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/sensor_attr/actuatorpos.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/sensor_attr/actuatorvel.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/sensor_attr/ballangvel.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/sensor_attr/ballquat.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/sensor_attr/camprojection.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/sensor_attr/clock.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/sensor_attr/distance.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/sensor_attr/e_kinetic.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/sensor_attr/e_potential.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/sensor_attr/force.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/sensor_attr/frameangacc.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/sensor_attr/frameangvel.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/sensor_attr/framelinacc.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/sensor_attr/framelinvel.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/sensor_attr/framepos.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/sensor_attr/framequat.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/sensor_attr/framexaxis.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/sensor_attr/frameyaxis.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/sensor_attr/framezaxis.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/sensor_attr/fromto.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/sensor_attr/gyro.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/sensor_attr/jointactuatorfrc.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/sensor_attr/jointlimitfrc.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/sensor_attr/jointlimitpos.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/sensor_attr/jointlimitvel.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/sensor_attr/jointpos.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/sensor_attr/jointvel.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/sensor_attr/magnetometer.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/sensor_attr/normal.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/sensor_attr/rangefinder.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/sensor_attr/subtreeangmom.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/sensor_attr/subtreecom.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/sensor_attr/subtreelinvel.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/sensor_attr/tendonactuatorfrc.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/sensor_attr/tendonlimitfrc.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/sensor_attr/tendonlimitpos.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/sensor_attr/tendonlimitvel.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/sensor_attr/tendonpos.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/sensor_attr/tendonvel.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/sensor_attr/torque.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/sensor_attr/touch.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/sensor_attr/user.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/sensor_attr/velocimeter.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/size.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/statistic.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/tendon.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/tendon_attr/__init__.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/tendon_attr/fixed.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/tendon_attr/fixed_attr/__init__.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/tendon_attr/fixed_attr/joint.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/tendon_attr/spatial.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/tendon_attr/spatial_attr/__init__.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/tendon_attr/spatial_attr/pulley.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/tendon_attr/spatial_attr/site.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/visual.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/visual_attr/__init__.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/visual_attr/global_.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/visual_attr/headlight.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/visual_attr/map.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/visual_attr/quality.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/visual_attr/rgba.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/mujoco_attr/visual_attr/scale.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/plugin.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/pose_context.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mjcf/position.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/mojo_model.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/runtime/__init__.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/runtime/load.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/runtime/runtime_manager.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/runtime/signal_manager.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/settings.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/templates/__init__.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/templates/monte_carlo.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/templates/optimization.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/templates/reloaded.sh +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/templates/run_mc.sh +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/templates/run_opt.sh +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/__init__.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/color.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/dataframe.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/defaults.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/filters/__init__.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/filters/filters.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/interp.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/__init__.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/__init__.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/lab_executor.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/main.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/plot_config.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/routers/__init__.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/routers/monitor.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/routers/morph.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/routers/sensai.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/sensai/__init__.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/sensai/agent.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/shared.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/__init__.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/base.html +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/error.html +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/monitor.html +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/morph.html +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/mosaic.html +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/partials/_sensai.html +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/partials/trial_viewer/_chart.html +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/partials/trial_viewer/_header.html +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/partials/trial_viewer/_json_editor.html +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/partials/trial_viewer/_lab_trigger.html +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/partials/trial_viewer/_macros.html +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/partials/trial_viewer/_media_player.html +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/partials/trial_viewer/_overlays.html +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/partials/trial_viewer/_signal_lab.html +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/partials/trial_viewer/_trial_status.html +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/static/__init__.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/static/chime.mp3 +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/static/dark-logo.svg +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/static/js/__init__.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/static/js/main.js +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/static/js/monitor.js +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/static/js/mosaic.js +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/static/js/sensai.js +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/static/js/toast.d.ts +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/static/js/toast.d.ts.map +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/static/js/toast.js +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/static/light-logo.svg +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/static/main.css +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/static/ts/build.mjs +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/static/ts/package-lock.json +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/static/ts/package.json +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/static/ts/src/cm-bundle.ts +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/static/ts/src/lib/options.ts +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/static/ts/src/lib/plot-config.generated.ts +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/static/ts/src/lib/resize.ts +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/static/ts/src/lib/schema-validate.ts +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/static/ts/src/lib/toast.ts +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/static/ts/src/monitor.ts +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/static/ts/src/mosaic.ts +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/static/ts/src/sensai.ts +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/static/ts/src/store.ts +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/static/ts/src/types/global.d.ts +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/static/ts/tsconfig.json +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/static/vendored/__init__.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/static/vendored/alpine.min.js +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/static/vendored/codemirror.bundle.js +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/static/vendored/confetti.browser.min.js +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/static/vendored/iro.min.js +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/static/vendored/litegraph.min.css +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/static/vendored/litegraph.min.js +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/static/vendored/lz-string.min.js +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/static/vendored/plotly-3.4.0.min.js +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/static/vendored/tailwind.min.js +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/layers/dojo/templates/trial_viewer.html +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/log.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/proximity_mixin.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/statusing.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/utils/utils.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/src/mujoco_mojo/visualization.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/tests/.gitignore +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/tests/make_skybox.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/tests/meshes/ball.stl +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/tests/meshes/bunny2.stl +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/tests/meshes/cup.stl +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/tests/mjcf/body_test.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/tests/mjcf/equality_test.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/tests/mjcf/inertial_test.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/tests/mjcf/joint_test.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/tests/mjcf/mesh.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/tests/mjcf/mujoco_test.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/tests/mjcf/orientation_test.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/tests/mjcf/pos_test.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/tests/mjcf/pose_graph_test.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/tests/mjcf/pose_test.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/tests/mjcf/site_test.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/tests/mjcf/visualization_test.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/tests/mjcf/xml_model_test.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/tests/model_with_skybox.xml +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/tests/monte_carlo_test/asset_image.png +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/tests/monte_carlo_test/monte_carlo.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/tests/monte_carlo_test/overrides.json +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/tests/monte_carlo_test/running_with_slurm.sh +0 -0
- {mujoco_mojo-2.4.2/src/mujoco_mojo/templates → mujoco_mojo-2.4.4/tests/one_off/proximity}/dojo.sh +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/tests/one_off/proximity/reloaded.sh +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/tests/one_off/proximity/run.sh +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/tests/one_off/proximity/simulation.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/tests/runtime/conftest.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/tests/runtime/loads_test.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/tests/runtime/mojo_model_test.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/tests/runtime/optimizer_bumper.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/tests/runtime/runtime_manager_test.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/tests/runtime/settings_test.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/tests/runtime/signal_manager_test.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/tests/smoke_test.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/tests/test_writer.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/tests/utils/color_test.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/tests/utils/dataframe_test.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/tests/utils/filter_test.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/tests/utils/interp_test.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/tests/utils/load_func_test.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/tests/utils/utils_test.py +0 -0
- {mujoco_mojo-2.4.2 → mujoco_mojo-2.4.4}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mujoco-mojo
|
|
3
|
-
Version: 2.4.
|
|
3
|
+
Version: 2.4.4
|
|
4
4
|
Summary: A complete MJCF lifecycle and trial orchestration suite for MuJoCo, powered by Pydantic v2.
|
|
5
5
|
Project-URL: Homepage, https://github.com/Hydrowelder/mujoco-mojo
|
|
6
6
|
Project-URL: Documentation, https://hydrowelder.github.io/mujoco-mojo/
|
|
@@ -51,7 +51,7 @@ Requires-Dist: rtree>=1.4.1
|
|
|
51
51
|
Requires-Dist: scipy-stubs[scipy]>=1.17.0.2
|
|
52
52
|
Requires-Dist: scipy>=1.17.0
|
|
53
53
|
Requires-Dist: sse-starlette>=3.3.4
|
|
54
|
-
Requires-Dist: stochas>=2.1.
|
|
54
|
+
Requires-Dist: stochas>=2.1.6
|
|
55
55
|
Requires-Dist: tabulate>=0.9.0
|
|
56
56
|
Requires-Dist: tomli-w>=1.2.0
|
|
57
57
|
Requires-Dist: trimesh>=4.11.2
|
|
@@ -0,0 +1,650 @@
|
|
|
1
|
+
"""
|
|
2
|
+
One-off script that cross-references the MJCF XML reference against the XMLModel
|
|
3
|
+
subclasses implemented in mujoco_mojo. By default it fetches the latest reference
|
|
4
|
+
directly from the MuJoCo docs; pass a local path to check against a specific copy
|
|
5
|
+
instead (e.g. a pinned XMLreference.rst.txt).
|
|
6
|
+
|
|
7
|
+
For every MJCF element it reports:
|
|
8
|
+
- whether mujoco_mojo implements a class for it (matched by `tag`)
|
|
9
|
+
- attributes documented in the spec but not covered by any matching class's
|
|
10
|
+
`attributes`/`non_xml_fields` (likely a missed field)
|
|
11
|
+
- attributes whose spec default doesn't match the Python field's default value
|
|
12
|
+
- attributes with no per-field docstring, or one that has drifted from the spec
|
|
13
|
+
- a class-level docstring similarity score, to flag descriptions that may have
|
|
14
|
+
drifted from the spec
|
|
15
|
+
|
|
16
|
+
This is advisory, not a hard gate: false positives are expected (the spec groups
|
|
17
|
+
several distinct elements, e.g. body/joint vs tendon/fixed/joint, under the same bare
|
|
18
|
+
tag name) and docstrings are deliberately reworded in places, so review the output
|
|
19
|
+
rather than treating it as pass/fail.
|
|
20
|
+
|
|
21
|
+
Reviewed, accepted findings can be silenced by adding them to `mjcf_ignore.txt`
|
|
22
|
+
(next to this script) -- see that file's header for the format. This only applies to
|
|
23
|
+
the "Spec elements with no matching implemented tag", "Implemented tags with no
|
|
24
|
+
matching spec element", "Attribute coverage gaps", "Default value mismatches", and
|
|
25
|
+
"Docstring similarity warnings" sections; "Attribute docstring warnings" always shows
|
|
26
|
+
everything, since per-field docstrings are easy to add and shouldn't be silenced.
|
|
27
|
+
|
|
28
|
+
Usage:
|
|
29
|
+
python scripts/check_mjcf_spec_coverage.py # fetch the spec live
|
|
30
|
+
python scripts/check_mjcf_spec_coverage.py path/to/XMLreference.rst.txt # use a local copy
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
from __future__ import annotations
|
|
34
|
+
|
|
35
|
+
import ast
|
|
36
|
+
import functools
|
|
37
|
+
import inspect
|
|
38
|
+
import math
|
|
39
|
+
import re
|
|
40
|
+
import sys
|
|
41
|
+
import textwrap
|
|
42
|
+
from dataclasses import dataclass, field
|
|
43
|
+
from difflib import SequenceMatcher
|
|
44
|
+
from pathlib import Path
|
|
45
|
+
from typing import Literal, get_args, get_origin
|
|
46
|
+
|
|
47
|
+
import requests
|
|
48
|
+
from pydantic_core import PydanticUndefined
|
|
49
|
+
from rich.console import Console
|
|
50
|
+
from rich.markup import escape
|
|
51
|
+
|
|
52
|
+
SPEC_URL = "https://mujoco.readthedocs.io/en/stable/_sources/XMLreference.rst.txt"
|
|
53
|
+
|
|
54
|
+
IGNORE_FILE = Path(__file__).with_name("mjcf_ignore.txt")
|
|
55
|
+
|
|
56
|
+
# below this difflib ratio, a description is flagged as likely drifted from the spec
|
|
57
|
+
DOCSTRING_SIMILARITY_THRESHOLD = 0.2
|
|
58
|
+
|
|
59
|
+
# what's left of a spec attribute description after `_clean` strips a bare
|
|
60
|
+
# `:ref:`Something`` cross-reference with no other content (e.g. most sensors'
|
|
61
|
+
# shared name/noise/cutoff/... attributes just say "See CSensor."); comparing this
|
|
62
|
+
# placeholder against a real python docstring would always read as drift, so it's
|
|
63
|
+
# treated the same as having no spec description to compare against
|
|
64
|
+
SPEC_DESC_PLACEHOLDER = "See ."
|
|
65
|
+
|
|
66
|
+
# soft_wrap avoids breaking long file-path lines mid-string, which would make
|
|
67
|
+
# them harder to click through to from a terminal or IDE
|
|
68
|
+
console = Console(soft_wrap=True)
|
|
69
|
+
|
|
70
|
+
HEADER_RE = re.compile(
|
|
71
|
+
r"^(?:\:el-prefix:`(?P<prefix>\w+)/`\s*\|-\|\s*)?\*\*(?P<name>.+?)\*\*\s*\|[!@?*-]\|\s*$"
|
|
72
|
+
)
|
|
73
|
+
ANCHOR_RE = re.compile(r"^\.\. _[\w-]+:\s*$")
|
|
74
|
+
# plain RST subsection titles (e.g. "collision sensors") use the same `^^^^`/`~~~~` underline
|
|
75
|
+
# convention as real element headers but don't match HEADER_RE; without detecting them, `current`
|
|
76
|
+
# stays pinned to whatever element preceded them, and any shared `:at:` blocks inside get
|
|
77
|
+
# misattributed to that element instead of being ignored
|
|
78
|
+
GENERIC_UNDERLINE_RE = re.compile(r"^[\^~]{3,}\s*$")
|
|
79
|
+
ATTR_LINE_RE = re.compile(r"^:at:`(?P<rest>.+)`\s*(?::\s*:at-val:`.*`)?\s*$")
|
|
80
|
+
# only match names immediately preceded by the `:at:` role marker, so prose lines that
|
|
81
|
+
# happen to start with `:at:`something`` but reference other roles later (e.g. `:el:`general``)
|
|
82
|
+
# don't get misread as also declaring an attribute named "general"
|
|
83
|
+
AT_NAME_RE = re.compile(r":at:`(\w+)`")
|
|
84
|
+
RST_NOISE_RE = re.compile(
|
|
85
|
+
r":(?:ref|el|at|at-val|el-prefix|math):`[^`]*`|``|\|br\||\.\. .*"
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
# attribute names that get merged into a single python field via PoseBase/OrientationBase: `pose`
|
|
89
|
+
# (position + orientation) on most elements, or a bare `orientation` field where pos is separate
|
|
90
|
+
# (e.g. Inertial, which has its own `pos` field alongside `orientation`)
|
|
91
|
+
ORIENTATION_GROUP = {"pos", "quat", "axisangle", "xyaxes", "zaxis", "euler"}
|
|
92
|
+
ORIENTATION_COVERING_FIELDS = {"pose", "orientation"}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@dataclass
|
|
96
|
+
class SpecAttr:
|
|
97
|
+
description: str = ""
|
|
98
|
+
# raw text trailing the first top-level comma in the `:at-val:` body, e.g.
|
|
99
|
+
# '"0 0 1"', "optional", "required", or unparsed prose for the rare multi-clause cases
|
|
100
|
+
default_raw: str = ""
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@dataclass
|
|
104
|
+
class SpecElement:
|
|
105
|
+
name: str
|
|
106
|
+
description: str = ""
|
|
107
|
+
attrs: dict[str, SpecAttr] = field(default_factory=dict)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _clean(text: str) -> str:
|
|
111
|
+
text = RST_NOISE_RE.sub(" ", text)
|
|
112
|
+
return re.sub(r"\s+", " ", text).strip()
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _expand_names(raw_name: str) -> list[str]:
|
|
116
|
+
m = re.match(r"\((\w+)\)(\w+)$", raw_name)
|
|
117
|
+
if m:
|
|
118
|
+
return [m.group(2), m.group(1) + m.group(2)]
|
|
119
|
+
return [raw_name]
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _parse_attr_default(val_text: str) -> str:
|
|
123
|
+
"""
|
|
124
|
+
Splits an `:at-val:` body like `[false, true], "false"` or `real(3), "0 0 0"` into
|
|
125
|
+
its trailing default-value text, by finding the first top-level comma (ignoring
|
|
126
|
+
commas nested inside the type spec's brackets/parens, e.g. `[false, true]`).
|
|
127
|
+
|
|
128
|
+
Returns "" if no top-level comma is found (e.g. shared `:at:` lines whose `:at-val:`
|
|
129
|
+
is empty or absent).
|
|
130
|
+
"""
|
|
131
|
+
depth = 0
|
|
132
|
+
for i, ch in enumerate(val_text):
|
|
133
|
+
if ch in "[(":
|
|
134
|
+
depth += 1
|
|
135
|
+
elif ch in "])":
|
|
136
|
+
depth -= 1
|
|
137
|
+
elif ch == "," and depth == 0:
|
|
138
|
+
return val_text[i + 1 :].strip()
|
|
139
|
+
return ""
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def parse_spec(text: str) -> list[SpecElement]:
|
|
143
|
+
lines = text.splitlines()
|
|
144
|
+
elements: list[SpecElement] = []
|
|
145
|
+
# a header can expand to more than one name (e.g. "(world)body" -> "body" and
|
|
146
|
+
# "worldbody"); every name in the group shares the same attrs/description, since
|
|
147
|
+
# they're documenting the same element under different aliases
|
|
148
|
+
current_group: list[SpecElement] = []
|
|
149
|
+
pending_attr_names: list[str] = []
|
|
150
|
+
pending_default_raw = ""
|
|
151
|
+
buffer: list[str] = []
|
|
152
|
+
|
|
153
|
+
def flush_attr() -> None:
|
|
154
|
+
if current_group and pending_attr_names:
|
|
155
|
+
desc = _clean(" ".join(buffer))
|
|
156
|
+
for el in current_group:
|
|
157
|
+
for name in pending_attr_names:
|
|
158
|
+
el.attrs[name] = SpecAttr(
|
|
159
|
+
description=desc, default_raw=pending_default_raw
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
def flush_description() -> None:
|
|
163
|
+
# first non-empty buffer wins; later prose between attribute blocks (e.g. trailing
|
|
164
|
+
# notes before the next anchor) shouldn't clobber the element's intro description
|
|
165
|
+
if current_group and not pending_attr_names and buffer:
|
|
166
|
+
desc = _clean(" ".join(buffer))
|
|
167
|
+
for el in current_group:
|
|
168
|
+
if not el.description:
|
|
169
|
+
el.description = desc
|
|
170
|
+
|
|
171
|
+
i = 0
|
|
172
|
+
while i < len(lines):
|
|
173
|
+
line = lines[i]
|
|
174
|
+
header = HEADER_RE.match(line)
|
|
175
|
+
if header:
|
|
176
|
+
flush_attr()
|
|
177
|
+
flush_description()
|
|
178
|
+
pending_attr_names = []
|
|
179
|
+
pending_default_raw = ""
|
|
180
|
+
buffer = []
|
|
181
|
+
|
|
182
|
+
current_group = []
|
|
183
|
+
for name in _expand_names(header.group("name")):
|
|
184
|
+
el = SpecElement(name=name)
|
|
185
|
+
elements.append(el)
|
|
186
|
+
current_group.append(el)
|
|
187
|
+
i += 1
|
|
188
|
+
continue
|
|
189
|
+
|
|
190
|
+
if (
|
|
191
|
+
line.strip()
|
|
192
|
+
and i + 1 < len(lines)
|
|
193
|
+
and GENERIC_UNDERLINE_RE.match(lines[i + 1])
|
|
194
|
+
and not ANCHOR_RE.match(line)
|
|
195
|
+
and not ATTR_LINE_RE.match(line)
|
|
196
|
+
):
|
|
197
|
+
flush_attr()
|
|
198
|
+
flush_description()
|
|
199
|
+
current_group = []
|
|
200
|
+
pending_attr_names = []
|
|
201
|
+
pending_default_raw = ""
|
|
202
|
+
buffer = []
|
|
203
|
+
i += 2
|
|
204
|
+
continue
|
|
205
|
+
|
|
206
|
+
if ANCHOR_RE.match(line):
|
|
207
|
+
flush_attr()
|
|
208
|
+
flush_description()
|
|
209
|
+
pending_attr_names = []
|
|
210
|
+
pending_default_raw = ""
|
|
211
|
+
buffer = []
|
|
212
|
+
i += 1
|
|
213
|
+
continue
|
|
214
|
+
|
|
215
|
+
attr_match = ATTR_LINE_RE.match(line)
|
|
216
|
+
if attr_match:
|
|
217
|
+
flush_attr()
|
|
218
|
+
# only look for `name` tokens before ":at-val:" so type/default values
|
|
219
|
+
# (e.g. :at-val:`string`) aren't mistaken for additional attribute names
|
|
220
|
+
name_part, _, val_part = line.partition(":at-val:")
|
|
221
|
+
pending_attr_names = AT_NAME_RE.findall(name_part)
|
|
222
|
+
# val_part looks like "`real(3), "0 0 1"`" when an :at-val: is present,
|
|
223
|
+
# or "" when this line only lists attribute names with no value of its own
|
|
224
|
+
# (e.g. a line that just cross-references attributes documented elsewhere)
|
|
225
|
+
val_text = val_part.strip()
|
|
226
|
+
if val_text.startswith("`") and val_text.endswith("`"):
|
|
227
|
+
val_text = val_text[1:-1]
|
|
228
|
+
pending_default_raw = _parse_attr_default(val_text)
|
|
229
|
+
buffer = []
|
|
230
|
+
i += 1
|
|
231
|
+
continue
|
|
232
|
+
|
|
233
|
+
buffer.append(line)
|
|
234
|
+
i += 1
|
|
235
|
+
|
|
236
|
+
flush_attr()
|
|
237
|
+
flush_description()
|
|
238
|
+
# keep every real header match, even ones with zero attributes of their own (e.g.
|
|
239
|
+
# pure container elements like <actuator>/<asset>, or elements like actuator/motor
|
|
240
|
+
# whose attributes are documented by reference under a shared element instead of
|
|
241
|
+
# being restated) -- dropping them caused them to be falsely reported as unimplemented
|
|
242
|
+
return elements
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def merge_by_bare_name(elements: list[SpecElement]) -> dict[str, SpecElement]:
|
|
246
|
+
merged: dict[str, SpecElement] = {}
|
|
247
|
+
for el in elements:
|
|
248
|
+
bucket = merged.setdefault(el.name, SpecElement(name=el.name))
|
|
249
|
+
bucket.attrs.update(el.attrs)
|
|
250
|
+
if not bucket.description:
|
|
251
|
+
bucket.description = el.description
|
|
252
|
+
return merged
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def normalize_field_name(name: str) -> str:
|
|
256
|
+
if name.endswith("_") and len(name) > 1:
|
|
257
|
+
return name[:-1]
|
|
258
|
+
return name
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def load_ignore_file(path: Path = IGNORE_FILE) -> dict[str, set[str]]:
|
|
262
|
+
"""
|
|
263
|
+
Parses `mjcf_ignore.txt`-style lines of the form `<category>:<key>` into
|
|
264
|
+
{category: {key, ...}}. Blank lines and lines starting with `#` are skipped.
|
|
265
|
+
Missing file just means nothing is ignored.
|
|
266
|
+
"""
|
|
267
|
+
ignored: dict[str, set[str]] = {}
|
|
268
|
+
if not path.exists():
|
|
269
|
+
return ignored
|
|
270
|
+
for line in path.read_text(encoding="utf-8").splitlines():
|
|
271
|
+
line = line.strip()
|
|
272
|
+
if not line or line.startswith("#"):
|
|
273
|
+
continue
|
|
274
|
+
category, _, key = line.partition(":")
|
|
275
|
+
ignored.setdefault(category, set()).add(key)
|
|
276
|
+
return ignored
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
@functools.cache
|
|
280
|
+
def _extract_field_docstrings(cls: type) -> dict[str, str]:
|
|
281
|
+
"""
|
|
282
|
+
Parses the class's source to recover the bare string-literal "docstrings" this
|
|
283
|
+
project writes directly after each field assignment. These are a convention for
|
|
284
|
+
human/doc-tool readers only -- the interpreter discards them as statement
|
|
285
|
+
expressions, so they aren't reachable via normal runtime introspection.
|
|
286
|
+
|
|
287
|
+
Only looks at `cls`'s own body; fields inherited from a base class (e.g. the
|
|
288
|
+
`name`/`noise`/`cutoff`/... fields most sensor subclasses get from `SensorBase`)
|
|
289
|
+
are not found here -- see `_field_docstrings_for_class` for the MRO-aware version.
|
|
290
|
+
"""
|
|
291
|
+
try:
|
|
292
|
+
source = inspect.getsource(cls)
|
|
293
|
+
except (OSError, TypeError):
|
|
294
|
+
return {}
|
|
295
|
+
try:
|
|
296
|
+
tree = ast.parse(textwrap.dedent(source))
|
|
297
|
+
except SyntaxError:
|
|
298
|
+
return {}
|
|
299
|
+
if not tree.body or not isinstance(tree.body[0], ast.ClassDef):
|
|
300
|
+
return {}
|
|
301
|
+
|
|
302
|
+
body = tree.body[0].body
|
|
303
|
+
docs: dict[str, str] = {}
|
|
304
|
+
for i, node in enumerate(body):
|
|
305
|
+
name = None
|
|
306
|
+
if isinstance(node, ast.AnnAssign) and isinstance(node.target, ast.Name):
|
|
307
|
+
name = node.target.id
|
|
308
|
+
elif (
|
|
309
|
+
isinstance(node, ast.Assign)
|
|
310
|
+
and len(node.targets) == 1
|
|
311
|
+
and isinstance(node.targets[0], ast.Name)
|
|
312
|
+
):
|
|
313
|
+
name = node.targets[0].id
|
|
314
|
+
if name is None or i + 1 >= len(body):
|
|
315
|
+
continue
|
|
316
|
+
following = body[i + 1]
|
|
317
|
+
if (
|
|
318
|
+
isinstance(following, ast.Expr)
|
|
319
|
+
and isinstance(following.value, ast.Constant)
|
|
320
|
+
and isinstance(following.value.value, str)
|
|
321
|
+
):
|
|
322
|
+
docs[name] = following.value.value
|
|
323
|
+
return docs
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def _field_docstrings_for_class(cls: type) -> dict[str, str]:
|
|
327
|
+
"""
|
|
328
|
+
Merges `_extract_field_docstrings` across the whole MRO, so fields defined on a
|
|
329
|
+
base class (e.g. `SensorBase.name`) are attributed to every concrete subclass that
|
|
330
|
+
inherits them. Walking base-to-derived means a subclass's own docstring for a
|
|
331
|
+
field wins over an inherited one of the same name, if it redefines it.
|
|
332
|
+
"""
|
|
333
|
+
docs: dict[str, str] = {}
|
|
334
|
+
for base in reversed(cls.__mro__):
|
|
335
|
+
docs.update(_extract_field_docstrings(base))
|
|
336
|
+
return docs
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def collect_xml_model_classes():
|
|
340
|
+
import mujoco_mojo # noqa: F401
|
|
341
|
+
from mujoco_mojo.mjcf.xml_model import XMLModel
|
|
342
|
+
|
|
343
|
+
seen: set[type] = set()
|
|
344
|
+
stack = [XMLModel]
|
|
345
|
+
classes: list[type] = []
|
|
346
|
+
while stack:
|
|
347
|
+
cls = stack.pop()
|
|
348
|
+
for sub in cls.__subclasses__():
|
|
349
|
+
if sub not in seen:
|
|
350
|
+
seen.add(sub)
|
|
351
|
+
classes.append(sub)
|
|
352
|
+
stack.append(sub)
|
|
353
|
+
return classes
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def _class_location(cls: type) -> str:
|
|
357
|
+
try:
|
|
358
|
+
file = inspect.getsourcefile(cls)
|
|
359
|
+
_, line = inspect.getsourcelines(cls)
|
|
360
|
+
except (OSError, TypeError):
|
|
361
|
+
return "<unknown location>"
|
|
362
|
+
if not file:
|
|
363
|
+
return "<unknown location>"
|
|
364
|
+
# show a path relative to the cwd when the file is underneath it, so the
|
|
365
|
+
# output stays clickable/short when run from within the repo
|
|
366
|
+
path = Path(file)
|
|
367
|
+
try:
|
|
368
|
+
path = path.relative_to(Path.cwd())
|
|
369
|
+
except ValueError:
|
|
370
|
+
pass
|
|
371
|
+
return f"{path}:{line}"
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def build_python_registry() -> dict[str, dict]:
|
|
375
|
+
registry: dict[str, dict] = {}
|
|
376
|
+
for cls in collect_xml_model_classes():
|
|
377
|
+
tag = getattr(cls, "tag", "")
|
|
378
|
+
if not tag:
|
|
379
|
+
continue
|
|
380
|
+
bucket = registry.setdefault(
|
|
381
|
+
tag,
|
|
382
|
+
{
|
|
383
|
+
"classes": [],
|
|
384
|
+
"covered": set(),
|
|
385
|
+
"docstrings": [],
|
|
386
|
+
# field name -> list of per-class docstrings (collected even for
|
|
387
|
+
# fields outside `attributes`/`non_xml_fields`; harmless since
|
|
388
|
+
# only the ones matching a spec attribute name are ever read)
|
|
389
|
+
"field_docs": {},
|
|
390
|
+
# fields any class declares as a single-value `Literal[...]`
|
|
391
|
+
# discriminator (e.g. MeshSphere.builtin = Literal["sphere"]); these
|
|
392
|
+
# are fixed tag values, not documentable XML attributes, so they're
|
|
393
|
+
# exempt from the missing-docstring check below. A multi-value Literal
|
|
394
|
+
# (e.g. `condim: Literal[1, 3, 4, 6]`) is a real constrained attribute
|
|
395
|
+
# and still needs a docstring, so it doesn't count here
|
|
396
|
+
"literal_fields": set(),
|
|
397
|
+
},
|
|
398
|
+
)
|
|
399
|
+
bucket["classes"].append((cls.__name__, _class_location(cls), cls))
|
|
400
|
+
names = {
|
|
401
|
+
normalize_field_name(n) for n in (*cls.attributes, *cls.non_xml_fields)
|
|
402
|
+
}
|
|
403
|
+
bucket["covered"] |= names
|
|
404
|
+
if cls.__doc__:
|
|
405
|
+
bucket["docstrings"].append(cls.__doc__)
|
|
406
|
+
for raw_name, doc in _field_docstrings_for_class(cls).items():
|
|
407
|
+
bucket["field_docs"].setdefault(normalize_field_name(raw_name), []).append(
|
|
408
|
+
doc
|
|
409
|
+
)
|
|
410
|
+
for raw_name, field_info in cls.model_fields.items():
|
|
411
|
+
annotation = field_info.annotation
|
|
412
|
+
if get_origin(annotation) is Literal and len(get_args(annotation)) == 1:
|
|
413
|
+
bucket["literal_fields"].add(normalize_field_name(raw_name))
|
|
414
|
+
return registry
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def _python_field_default(cls: type, field_name: str) -> tuple[str, str | None]:
|
|
418
|
+
"""
|
|
419
|
+
Classifies a Python field's default into ("required", None) when no default is
|
|
420
|
+
set, ("optional", None) when the default is None, ("composite", None) when the
|
|
421
|
+
default is a nested XMLModel (e.g. `pos: Pos = Pos(...)`, flattened into XML
|
|
422
|
+
attributes by a separate code path that's out of scope for this comparison),
|
|
423
|
+
("discriminator", formatted_value) when the field is a single-value `Literal[...]`
|
|
424
|
+
(e.g. `type: Literal[GeomType.SPHERE]` on a discriminated geom subclass -- its
|
|
425
|
+
fixed value isn't meant to match the spec's generic default for the merged
|
|
426
|
+
element), or ("literal", formatted_value) otherwise, formatted the same way
|
|
427
|
+
`XMLModel.to_xml` formats it for the real XML output.
|
|
428
|
+
"""
|
|
429
|
+
from mujoco_mojo.mjcf.xml_model import XMLModel, _format_value
|
|
430
|
+
|
|
431
|
+
field_info = cls.model_fields[field_name]
|
|
432
|
+
default = field_info.get_default(call_default_factory=True)
|
|
433
|
+
if default is PydanticUndefined:
|
|
434
|
+
return "required", None
|
|
435
|
+
if default is None:
|
|
436
|
+
return "optional", None
|
|
437
|
+
if isinstance(default, XMLModel):
|
|
438
|
+
return "composite", None
|
|
439
|
+
formatted = _format_value(default)
|
|
440
|
+
annotation = field_info.annotation
|
|
441
|
+
if get_origin(annotation) is Literal and len(get_args(annotation)) == 1:
|
|
442
|
+
return "discriminator", formatted
|
|
443
|
+
return "literal", formatted
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
def _spec_attr_default(spec_attr: SpecAttr) -> tuple[str, str | None]:
|
|
447
|
+
"""Mirrors `_python_field_default`'s (kind, value) shape for the spec side."""
|
|
448
|
+
stripped = spec_attr.default_raw.strip()
|
|
449
|
+
if stripped == "required":
|
|
450
|
+
return "required", None
|
|
451
|
+
if stripped == "optional":
|
|
452
|
+
return "optional", None
|
|
453
|
+
match = re.match(r'^"([^"]*)"', stripped)
|
|
454
|
+
if match:
|
|
455
|
+
return "literal", match.group(1)
|
|
456
|
+
# no `:at-val:` at all, or prose too irregular to parse as a literal/optional/required
|
|
457
|
+
return "unparsed", None
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
def _parse_float_list(text: str) -> list[float] | None:
|
|
461
|
+
try:
|
|
462
|
+
return [float(tok) for tok in text.split()]
|
|
463
|
+
except ValueError:
|
|
464
|
+
return None
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
def _literals_match(spec_value: str, python_value: str) -> bool:
|
|
468
|
+
if spec_value == python_value:
|
|
469
|
+
return True
|
|
470
|
+
# numeric defaults can be formatted differently (e.g. spec "1" vs python "1.0")
|
|
471
|
+
# while still being the same value; compare as numbers when both sides parse
|
|
472
|
+
spec_floats = _parse_float_list(spec_value)
|
|
473
|
+
python_floats = _parse_float_list(python_value)
|
|
474
|
+
if spec_floats is None or python_floats is None:
|
|
475
|
+
return False
|
|
476
|
+
return len(spec_floats) == len(python_floats) and all(
|
|
477
|
+
math.isclose(a, b, abs_tol=1e-9) for a, b in zip(spec_floats, python_floats)
|
|
478
|
+
)
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
def _default_mismatch(spec_attr: SpecAttr, cls: type, field_name: str) -> str | None:
|
|
482
|
+
"""Returns a human-readable mismatch description, or None if the default checks out."""
|
|
483
|
+
spec_kind, spec_value = _spec_attr_default(spec_attr)
|
|
484
|
+
if spec_kind == "unparsed":
|
|
485
|
+
return None
|
|
486
|
+
|
|
487
|
+
python_kind, python_value = _python_field_default(cls, field_name)
|
|
488
|
+
if python_kind in ("composite", "discriminator"):
|
|
489
|
+
return None
|
|
490
|
+
|
|
491
|
+
if spec_kind == python_kind == "required":
|
|
492
|
+
return None
|
|
493
|
+
if spec_kind == python_kind == "optional":
|
|
494
|
+
return None
|
|
495
|
+
if spec_kind == "literal" and python_kind == "literal":
|
|
496
|
+
assert spec_value is not None and python_value is not None
|
|
497
|
+
if _literals_match(spec_value, python_value):
|
|
498
|
+
return None
|
|
499
|
+
return f'spec default "{spec_value}" != python default "{python_value}"'
|
|
500
|
+
|
|
501
|
+
python_desc = f' ("{python_value}")' if python_value is not None else ""
|
|
502
|
+
return f"spec marks attribute as {spec_kind} but python default is {python_kind}{python_desc}"
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
def _print_classes(classes: list[tuple[str, str, type]]) -> None:
|
|
506
|
+
for name, location, *_ in classes:
|
|
507
|
+
console.print(
|
|
508
|
+
f" [bold cyan]{escape(name)}[/bold cyan] [dim]{escape(location)}[/dim]"
|
|
509
|
+
)
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
def main() -> None:
|
|
513
|
+
if len(sys.argv) > 1:
|
|
514
|
+
text = Path(sys.argv[1]).read_text(encoding="utf-8")
|
|
515
|
+
else:
|
|
516
|
+
response = requests.get(SPEC_URL)
|
|
517
|
+
response.raise_for_status()
|
|
518
|
+
text = response.text
|
|
519
|
+
|
|
520
|
+
spec_elements = parse_spec(text)
|
|
521
|
+
spec_by_name = merge_by_bare_name(spec_elements)
|
|
522
|
+
python_registry = build_python_registry()
|
|
523
|
+
ignored = load_ignore_file()
|
|
524
|
+
|
|
525
|
+
spec_names = set(spec_by_name)
|
|
526
|
+
python_tags = set(python_registry)
|
|
527
|
+
|
|
528
|
+
unimplemented = sorted(
|
|
529
|
+
n
|
|
530
|
+
for n in (spec_names - python_tags)
|
|
531
|
+
if n not in ignored.get("unimplemented", ())
|
|
532
|
+
)
|
|
533
|
+
untagged = sorted(
|
|
534
|
+
n for n in (python_tags - spec_names) if n not in ignored.get("untagged", ())
|
|
535
|
+
)
|
|
536
|
+
|
|
537
|
+
console.rule(
|
|
538
|
+
f"[bold]Spec elements with no matching implemented tag[/bold] ({len(unimplemented)})"
|
|
539
|
+
)
|
|
540
|
+
for name in unimplemented:
|
|
541
|
+
console.print(f" [yellow]{escape(name)}[/yellow]")
|
|
542
|
+
|
|
543
|
+
console.print()
|
|
544
|
+
console.rule(
|
|
545
|
+
f"[bold]Implemented tags with no matching spec element[/bold] ({len(untagged)})"
|
|
546
|
+
)
|
|
547
|
+
for name in untagged:
|
|
548
|
+
console.print(f" [yellow]{escape(name)}[/yellow]:")
|
|
549
|
+
_print_classes(python_registry[name]["classes"])
|
|
550
|
+
|
|
551
|
+
console.print()
|
|
552
|
+
console.rule(
|
|
553
|
+
"[bold]Attribute coverage gaps[/bold] (spec attributes not covered by attributes/non_xml_fields)"
|
|
554
|
+
)
|
|
555
|
+
for name in sorted(spec_names & python_tags):
|
|
556
|
+
spec_el = spec_by_name[name]
|
|
557
|
+
covered = python_registry[name]["covered"]
|
|
558
|
+
missing = set()
|
|
559
|
+
for attr in spec_el.attrs:
|
|
560
|
+
if attr in covered:
|
|
561
|
+
continue
|
|
562
|
+
if attr in ORIENTATION_GROUP and covered & ORIENTATION_COVERING_FIELDS:
|
|
563
|
+
continue
|
|
564
|
+
if f"{name}.{attr}" in ignored.get("coverage_gap", ()):
|
|
565
|
+
continue
|
|
566
|
+
missing.add(attr)
|
|
567
|
+
if missing:
|
|
568
|
+
console.print(
|
|
569
|
+
f" [red]{escape(name)}[/red]: missing [bold yellow]{escape(str(sorted(missing)))}[/bold yellow]"
|
|
570
|
+
)
|
|
571
|
+
_print_classes(python_registry[name]["classes"])
|
|
572
|
+
|
|
573
|
+
console.print()
|
|
574
|
+
console.rule(
|
|
575
|
+
"[bold]Default value mismatches[/bold] (spec default doesn't match the Python field default)"
|
|
576
|
+
)
|
|
577
|
+
for name in sorted(spec_names & python_tags):
|
|
578
|
+
spec_el = spec_by_name[name]
|
|
579
|
+
for _, location, cls in python_registry[name]["classes"]:
|
|
580
|
+
for field_name in cls.attributes:
|
|
581
|
+
attr_name = normalize_field_name(field_name)
|
|
582
|
+
if f"{name}.{attr_name}" in ignored.get("default_mismatch", ()):
|
|
583
|
+
continue
|
|
584
|
+
spec_attr = spec_el.attrs.get(attr_name)
|
|
585
|
+
if spec_attr is None:
|
|
586
|
+
continue
|
|
587
|
+
mismatch = _default_mismatch(spec_attr, cls, field_name)
|
|
588
|
+
if mismatch:
|
|
589
|
+
console.print(
|
|
590
|
+
f" [red]{escape(name)}.{escape(attr_name)}[/red]: {escape(mismatch)}"
|
|
591
|
+
)
|
|
592
|
+
console.print(
|
|
593
|
+
f" [bold cyan]{escape(cls.__name__)}[/bold cyan] [dim]{escape(location)}[/dim]"
|
|
594
|
+
)
|
|
595
|
+
|
|
596
|
+
console.print()
|
|
597
|
+
console.rule(
|
|
598
|
+
"[bold]Attribute docstring warnings[/bold] (missing per-field docstring, or drifted from the spec)"
|
|
599
|
+
)
|
|
600
|
+
for name in sorted(spec_names & python_tags):
|
|
601
|
+
spec_el = spec_by_name[name]
|
|
602
|
+
covered = python_registry[name]["covered"]
|
|
603
|
+
field_docs = python_registry[name]["field_docs"]
|
|
604
|
+
literal_fields = python_registry[name]["literal_fields"]
|
|
605
|
+
for attr_name, spec_attr in spec_el.attrs.items():
|
|
606
|
+
if attr_name not in covered:
|
|
607
|
+
continue # already reported as a coverage gap above
|
|
608
|
+
if attr_name in ORIENTATION_GROUP and covered & ORIENTATION_COVERING_FIELDS:
|
|
609
|
+
continue # merged into a `pose`/`orientation` field with no 1:1 docstring
|
|
610
|
+
if attr_name in literal_fields:
|
|
611
|
+
continue # fixed discriminator value, not a documentable attribute
|
|
612
|
+
# dedupe before joining: a field defined once on a shared base class (e.g.
|
|
613
|
+
# GeomBase.name) is re-collected for every subclass sharing this tag, and
|
|
614
|
+
# comparing a short spec description against N copies of the same text
|
|
615
|
+
# would deflate the similarity ratio for no real reason
|
|
616
|
+
py_doc = _clean(" ".join(dict.fromkeys(field_docs.get(attr_name, []))))
|
|
617
|
+
if not py_doc:
|
|
618
|
+
console.print(
|
|
619
|
+
f" [magenta]{escape(name)}.{escape(attr_name)}[/magenta]: no docstring found"
|
|
620
|
+
)
|
|
621
|
+
continue
|
|
622
|
+
if spec_attr.description in ("", SPEC_DESC_PLACEHOLDER):
|
|
623
|
+
continue
|
|
624
|
+
ratio = SequenceMatcher(None, spec_attr.description, py_doc).ratio()
|
|
625
|
+
if ratio < DOCSTRING_SIMILARITY_THRESHOLD:
|
|
626
|
+
console.print(
|
|
627
|
+
f" [magenta]{escape(name)}.{escape(attr_name)}[/magenta]: similarity=[bold]{ratio:.2f}[/bold]"
|
|
628
|
+
)
|
|
629
|
+
|
|
630
|
+
console.print()
|
|
631
|
+
console.rule(
|
|
632
|
+
"[bold]Docstring similarity warnings[/bold] (ratio < 0.2, review for drift)"
|
|
633
|
+
)
|
|
634
|
+
for name in sorted(spec_names & python_tags):
|
|
635
|
+
if name in ignored.get("docstring_class", ()):
|
|
636
|
+
continue
|
|
637
|
+
spec_desc = spec_by_name[name].description
|
|
638
|
+
py_desc = _clean(" ".join(dict.fromkeys(python_registry[name]["docstrings"])))
|
|
639
|
+
if not spec_desc or not py_desc:
|
|
640
|
+
continue
|
|
641
|
+
ratio = SequenceMatcher(None, spec_desc, py_desc).ratio()
|
|
642
|
+
if ratio < DOCSTRING_SIMILARITY_THRESHOLD:
|
|
643
|
+
console.print(
|
|
644
|
+
f" [magenta]{escape(name)}[/magenta]: similarity=[bold]{ratio:.2f}[/bold]"
|
|
645
|
+
)
|
|
646
|
+
_print_classes(python_registry[name]["classes"])
|
|
647
|
+
|
|
648
|
+
|
|
649
|
+
if __name__ == "__main__":
|
|
650
|
+
main()
|