diff --git a/.gitattributes b/.gitattributes index 33e7dae9..bf1727ba 100644 --- a/.gitattributes +++ b/.gitattributes @@ -11,3 +11,5 @@ *.stl filter=lfs diff=lfs merge=lfs -text *.npz filter=lfs diff=lfs merge=lfs -text *.onnx filter=lfs diff=lfs merge=lfs -text +*.bin filter=lfs diff=lfs merge=lfs -text +*.svg filter=lfs diff=lfs merge=lfs -text diff --git a/.github/workflows/doc.yml b/.github/workflows/doc.yml new file mode 100644 index 00000000..47caffae --- /dev/null +++ b/.github/workflows/doc.yml @@ -0,0 +1,38 @@ +name: Documentation +on: + push: + branches: + - main +jobs: + ai_doc: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Python 3.12 + uses: actions/setup-python@v5 + with: + python-version: "3.12" + cache: 'pip' # caching pip dependencies + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pydoc-markdown + - name: Generate documentation and examples + run: | + pydoc-markdown -I src -p reachy_mini --no-render-toc > doc.md + VERSION=$(grep '^version =' pyproject.toml | head -n1 | cut -d'"' -f2) + DATE=$(date -u +'%Y-%m-%d %H:%M UTC') + echo -e "# Reachy Mini Documentation\n\n**Version:** $VERSION\n**Generated:** $DATE\n" > doc_reachy_mini_full.md + cat README.md doc.md $(find docs -type f -name '*.md' | sort) >> doc_reachy_mini_full.md + echo -e "\n# Examples\n" >> doc_reachy_mini_full.md + for f in $(find examples -type f -name '*.py' | sort); do + echo -e "\n## $(basename "$f")\n" >> doc_reachy_mini_full.md + cat "$f" >> doc_reachy_mini_full.md + done + - name: Install GitHub CLI + run: sudo apt-get update && sudo apt-get install -y gh + - name: Upload doc_reachy_mini_full.md to Gist + env: + GH_TOKEN: ${{ secrets.GH_TOKEN }} + run: | + gh gist edit 919e1d7468fb16e70dbe984bdc277bba doc_reachy_mini_full.md --desc "Reachy Mini documentation and examples" diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 6a924191..980524f6 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,4 +1,4 @@ -name: Ruff +name: Linters on: [ push, pull_request ] jobs: ruff: @@ -8,3 +8,20 @@ jobs: - uses: astral-sh/ruff-action@v3 with: version: "0.12.0" + + mypy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Python 3.10 + uses: actions/setup-python@v5 + with: + python-version: "3.10" + cache: 'pip' # caching pip dependencies + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install .[dev] + - name : Lint with mypy + run : | + mypy --install-types --non-interactive diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 79c57651..1a8c75fd 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -23,6 +23,6 @@ jobs: pip install .[dev] - name: Run tests run: | - pytest -v -m 'not audio and not video and not audio_gstreamer and not video_gstreamer' --tb=short + pytest -vv -m 'not audio and not video and not audio_gstreamer and not video_gstreamer and not wireless and not wireless_gstreamer' --tb=short env: MUJOCO_GL: disable diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml new file mode 100644 index 00000000..dca1e95c --- /dev/null +++ b/.github/workflows/wheels.yml @@ -0,0 +1,26 @@ +name: Publish + +on: + release: + types: + - created + +jobs: + publish: + runs-on: ubuntu-latest + permissions: + id-token: write + steps: + - uses: actions/checkout@v5 + with: + lfs: true + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.10" + - name: Install build dependencies + run: pip install build + - name: Build distribution + run: python -m build + - name: Publish + uses: pypa/gh-action-pypi-publish@v1.13.0 \ No newline at end of file diff --git a/.github/workflows/wheels.yml.bak b/.github/workflows/wheels.yml.bak new file mode 100644 index 00000000..256d8da0 --- /dev/null +++ b/.github/workflows/wheels.yml.bak @@ -0,0 +1,181 @@ +name: Build and Release Wheels + +on: + push: + tags: + - "*" + workflow_dispatch: + +jobs: + linux: + runs-on: ${{ matrix.platform.runner }} + if: ${{ startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' }} + strategy: + matrix: + platform: + - runner: ubuntu-22.04 + target: x86_64 + - runner: ubuntu-22.04 + target: x86 + - runner: ubuntu-22.04 + target: aarch64 + - runner: ubuntu-22.04 + target: armv7 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: | + 3.8 + 3.9 + 3.10 + 3.11 + 3.12 + 3.13 + - name: Build wheels + run: | + pip install build + python -m build --wheel --outdir dist + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: wheels-linux-${{ matrix.platform.target }} + path: dist/*.whl + + musllinux: + runs-on: ${{ matrix.platform.runner }} + if: ${{ startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' }} + strategy: + matrix: + platform: + - runner: ubuntu-22.04 + target: x86_64 + - runner: ubuntu-22.04 + target: x86 + - runner: ubuntu-22.04 + target: aarch64 + - runner: ubuntu-22.04 + target: armv7 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: | + 3.8 + 3.9 + 3.10 + 3.11 + 3.12 + 3.13 + - name: Build wheels + run: | + pip install build + python -m build --wheel --outdir dist + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: wheels-musllinux-${{ matrix.platform.target }} + path: dist/*.whl + + windows: + runs-on: ${{ matrix.platform.runner }} + if: ${{ startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' }} + strategy: + matrix: + platform: + - runner: windows-latest + target: x64 + - runner: windows-latest + target: x86 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: | + 3.8 + 3.9 + 3.10 + 3.11 + 3.12 + 3.13 + architecture: ${{ matrix.platform.target }} + - name: Build wheels + run: | + pip install build + python -m build --wheel --outdir dist + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: wheels-windows-${{ matrix.platform.target }} + path: dist/*.whl + + macos: + runs-on: ${{ matrix.platform.runner }} + if: ${{ startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' }} + strategy: + matrix: + platform: + - runner: macos-13 + target: x86_64 + - runner: macos-14 + target: aarch64 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: | + 3.8 + 3.9 + 3.10 + 3.11 + 3.12 + 3.13 + - name: Build wheels + run: | + pip install build + python -m build --wheel --outdir dist + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: wheels-macos-${{ matrix.platform.target }} + path: dist/*.whl + + sdist: + runs-on: ubuntu-latest + if: ${{ startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' }} + steps: + - uses: actions/checkout@v4 + - name: Build sdist + run: | + pip install build + python -m build --sdist --outdir dist + - name: Upload sdist + uses: actions/upload-artifact@v4 + with: + name: wheels-sdist + path: dist/*.tar.gz + + release: + name: Release + runs-on: ubuntu-latest + if: ${{ startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' }} + needs: [linux, musllinux, windows, macos, sdist] + permissions: + id-token: write + contents: write + attestations: write + steps: + - uses: actions/download-artifact@v4 + with: + pattern: wheels-* + merge-multiple: true + path: dist + - name: Generate artifact attestation + uses: actions/attest-build-provenance@v1 + with: + subject-path: 'dist/*' + - name: Publish to PyPI + if: ${{ startsWith(github.ref, 'refs/tags/') }} + uses: pypa/gh-action-pypi-publish@release/v1 + with: + packages-dir: dist/ \ No newline at end of file diff --git a/.gitignore b/.gitignore index 3af80fcd..511e8976 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,256 @@ +# Byte-compiled / optimized / DLL files __pycache__/ -reachy_mini.egg-info/ +*.py[codz] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ *.egg-info/ -build/* -dist/* +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py.cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +# Pipfile.lock + +# UV +# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# uv.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +# poetry.lock +# poetry.toml + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python. +# https://pdm-project.org/en/latest/usage/project/#working-with-version-control +# pdm.lock +# pdm.toml +.pdm-python +.pdm-build/ + +# pixi +# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control. +# pixi.lock +# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one +# in the .venv directory. It is recommended not to include this directory in version control. +.pixi + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# Redis +*.rdb +*.aof +*.pid + +# RabbitMQ +mnesia/ +rabbitmq/ +rabbitmq-data/ + +# ActiveMQ +activemq-data/ + +# SageMath parsed files +*.sage.py + +# Environments +.env +.envrc +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml +.idea/**/contentModel.xml + +# Abstra +# Abstra is an AI-powered process automation framework. +# Ignore directories containing user credentials, local state, and settings. +# Learn more at https://abstra.io/docs +.abstra/ + +# Visual Studio Code +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets +!*.code-workspace + +# Ruff stuff: +.ruff_cache/ + +# PyPI configuration file +.pypirc + +# Marimo +marimo/_static/ +marimo/_lsp/ +__marimo__/ + +# Streamlit +.streamlit/secrets.toml + +# General for macOS +.DS_Store +__MACOSX/ +.AppleDouble +.LSOverride +Icon[] + +# General for Linux +*~ +.fuse_hidden* +.directory +.Trash-* +.nfs* +nohup.out + +# General for Windows +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db +[Dd]esktop.ini +*.stackdump +$RECYCLE.BIN/ +*.lnk + +# Project specific ignores src/reachy_mini_dashboard/installed_apps/* examples/debug/measures/* +.DS_Store \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..2bae156b --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2025 Pollen Robotics + + 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. diff --git a/README.md b/README.md index 6fbd2312..a16e0de1 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,37 @@ # Reachy Mini -[Reachy Mini](https://www.pollen-robotics.com/reachy-mini/) is the first open-source desktop robot designed to explore human-robot interaction and creative custom applications. We made it to be affordable, easy to use, hackable and cute, so that you can focus on building cool AI applications! +[![Ask on HuggingChat](https://img.shields.io/badge/Ask_on-HuggingChat-yellow?logo=huggingface&logoColor=yellow&style=for-the-badge)](https://huggingface.co/chat/?attachments=https%3A%2F%2Fgist.githubusercontent.com%2FFabienDanieau%2F919e1d7468fb16e70dbe984bdc277bba%2Fraw%2Fdoc_reachy_mini_full.md&prompt=Read%20this%20documentation%20about%20Reachy%20Mini%20so%20I%20can%20ask%20questions%20about%20it.) + +> ⚠️ Reachy Mini is still in beta. Expect bugs, some of them we won't fix right away if they are not a priority. + +[Reachy Mini](https://www.pollen-robotics.com/reachy-mini/) is an expressive, open-source robot designed for human-robot interaction, creative coding, and AI experimentation. We made it to be affordable, easy to use, hackable and cute, so that you can focus on building cool AI applications! [![Reachy Mini Hello](/docs/assets/reachy_mini_hello.gif)](https://www.pollen-robotics.com/reachy-mini/) +### Versions Lite & Wireless + +Reachy Mini's hardware comes in two flavors: +- **Reachy Mini lite**: where the robot is directly connected to your computer via USB. And the code that controls the robot (the daemon) runs on your computer. +- **Reachy Mini wireless**: where an Raspberry Pi is embedded in the robot, and the code that controls the robot (the daemon) runs on the Raspberry Pi. You can connect to it via Wi-Fi from your computer (see [Wireless Setup](./docs/wireless-version.md)). + +There is also a simulated version of Reachy Mini in [MuJoCo](https://mujoco.org) that you can use to prototype your applications before deploying them on the real robot. It behaves like the lite version where the daemon runs on your computer. + +## Assembly guide + +📖 Follow our step-by-step [Assembly Guide](https://huggingface.co/spaces/pollen-robotics/Reachy_Mini_Assembly_Guide). + +Most builders finish in about 3 hours, our current speed record is 43 minutes. The guide walks you through every step with clear visuals so you can assemble Reachy Mini confidently from start to finish. Enjoy the build! + +▶️ View the [Assembly Video](https://youtu.be/_r0cHySFbeY?si=6Mw4js8HSUs4cwoJ). + +## Software overview + This repository provides everything you need to control Reachy Mini, both in simulation and on the real robot. It consists of two main parts: -- **Daemon**: A background service that manages communication with the robot's motors and sensors, or with the simulation environment. -- **Python API**: A simple to use API to control the robot's features (head, antennas, camera, speakers, microphone, etc.) from your own Python scripts that you can connect with your AI experimentation. +- **The 😈 Daemon 😈**: A background service that manages communication with the robot's motors and sensors, or with the simulation environment. It should be running before you can control the robot. It can run either for the simulation (MuJoCo) or for the real robot. +- **🐍 SDK & 🕸️ API** to control the robot's main features (head, antennas, camera, speakers, microphone, etc.) and connect with your AI experimentation. Depending on your preferences and needs, there is a [Python SDK](#using-the-python-sdk) and a [HTTP REST API](#using-the-rest-api). -Making your robot move should only require a few lines of code, as illustrated in the example below: +Using the [Python SDK](#using-the-python-sdk), making your robot move only require a few lines of code, as illustrated in the example below: ```python from reachy_mini import ReachyMini @@ -25,12 +47,30 @@ with ReachyMini() as reachy_mini: reachy_mini.goto_target(head=pose, duration=2.0) ``` -## Installation +and using the [REST API](#using-the-rest-api), reading the current state of the robot: + +```bash +curl 'http://localhost:8000/api/state/full' +``` + +Those two examples above assume that the daemon is already running (either in simulation or on the real robot) locally. + +## Installation of the daemon and Python SDK -We support and test on Linux and macOS. We are working on Windows support, but it is not yet available. Any Python 3.8+ environment should work. +As mentioned above, before being able to use the robot, you need to run the daemon that will handle the communication with the motors. + +We support and test on Linux and macOS. It's also working on Windows, but it is less tested at the moment. Do not hesitate to open an issue if you encounter any problem. + +The daemon is built in Python, so you need to have Python installed on your computer (versions from 3.10 to 3.13 are supported). We recommend using a virtual environment to avoid dependency conflicts with your other Python projects. You can install Reachy Mini from the source code or from PyPI. +First, make sure `git-lfs` is installed on your system: + +- On Linux: `sudo apt install git-lfs` +- On macOS: `brew install git-lfs` +- On Windows: [Follow the instructions here](https://docs.github.com/en/repositories/working-with-files/managing-large-files/installing-git-large-file-storage?platform=windows) + From PyPI, you can install the package with: ```bash @@ -44,7 +84,36 @@ git clone https://github.com/pollen-robotics/reachy_mini pip install -e ./reachy_mini ``` -It requires Python 3.10 or later. +*Note that uv users can directly run the daemon with:* +```bash +uv run reachy-mini-daemon +``` + +The same package provides both the daemon and the Python SDK. + +### Linux udev rules setup + +On Linux systems, you need to set up udev rules to allow non-root access to the Reachy Mini hardware. Create the udev rules file with: + +```bash +echo 'SUBSYSTEM=="tty", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="55d3", MODE="0666", GROUP="dialout" #Reachy Mini +SUBSYSTEM=="tty", ATTRS{idVendor}=="38fb", ATTRS{idProduct}=="1001", MODE="0666", GROUP="dialout" #Reachy Mini soundcard' \ +| sudo tee /etc/udev/rules.d/99-reachy-mini.rules +``` + +After saving the file, refresh the udev rules: + +```bash +sudo udevadm control --reload-rules && sudo udevadm trigger +``` + +Finally, add your current user to the `dialout` group: + +```bash +sudo usermod -aG dialout $USER +``` + +You may need to log out and log back in for the group changes to take effect. ## Run the reachy mini daemon @@ -57,7 +126,7 @@ reachy-mini-daemon or run it via the Python module: ```bash -python -m reachy_mini.daemon.cli +python -m reachy_mini.daemon.app.main ``` Additional argument for both simulation and real robot: @@ -74,6 +143,14 @@ or ### In simulation ([MuJoCo](https://mujoco.org)) +You first have to install the optional dependency `mujoco`. + +```bash +pip install reachy-mini[mujoco] +``` + +Then run the daemon with the `--sim` argument. + ```bash reachy-mini-daemon --sim ``` @@ -90,10 +167,10 @@ Additional arguments: *Note: On OSX in order to run mujoco, you need to use mjpython (see [here](https://mujoco.readthedocs.io/en/stable/python.html#passive-viewer)). So, you should run the daemon with:* ```bash - mjpython -m reachy_mini.daemon.cli --sim + mjpython -m reachy_mini.daemon.app.main --sim ``` -### On the real robot +### For the lite version (connected via USB) It should automatically detect the serial port of the robot. If it does not, you can specify it manually with the `-p` option: @@ -101,29 +178,27 @@ It should automatically detect the serial port of the robot. If it does not, you reachy-mini-daemon -p ``` -## Run the examples +### Usage -Once the daemon is running, you can run the examples. +For more information about the daemon and its options, you can run: + +```bash +reachy-mini-daemon --help +``` -* To show the camera feed of the robot in a window, you can run: +### Dashboard - ```bash - python examples/camera_viewer.py - ``` +You can access a simple dashboard to monitor the robot's status at [http://localhost:8000/](http://localhost:8000/) when the daemon is running. This lets you turn your robot on and off, run some basic movements, and browse spaces for Reachy Mini! -* To show an example on how to use the look_at method to make the robot look at a point in 2D space, you can run: +![Reachy Mini Dashboard](docs/assets/dashboard.png) - ```bash - python examples/look_at_image.py - ``` +## Run the demo & awesome apps -* To illustrate the differences between the interpolation methods when running the goto method (linear, minimum jerk, cartoon, etc). +Conversational demo for the Reachy Mini robot combining LLM realtime APIs, vision pipelines, and choreographed motion libraries: [reachy_mini_conversation_demo](https://github.com/pollen-robotics/reachy_mini_conversation_demo). - ```bash - python examples/goto_interpolation_playground.py - ``` +You can find more awesome apps and demos for Reachy Mini on [Hugging Face spaces](https://huggingface.co/spaces?q=reachy_mini)! -## Reachy Mini's API +## Using the Python SDK The API is designed to be simple and intuitive. You can control the robot's features such as the head, antennas, camera, speakers, and microphone. For instance, to move the head of the robot, you can use the `goto_target` method as shown in the example below: @@ -141,10 +216,27 @@ with ReachyMini() as reachy_mini: reachy_mini.goto_target(head=pose, duration=2.0) ``` -For a full description of the SDK, please refer to the [documentation](./docs/API.md). +For a full description of the SDK, please refer to the [Python SDK documentation](./docs/python-sdk.md). +## Using the REST API -## Contribute +The daemon also provides a REST API via [fastapi](https://fastapi.tiangolo.com/) that you can use to control the robot and get its state. The API is accessible via HTTP and WebSocket. + +By default, the API server runs on `http://localhost:8000`. The API is documented using OpenAPI, and you can access the documentation at `http://localhost:8000/docs` when the daemon is running. + +More information about the API can be found in the [HTTP API documentation](./docs/rest-api.md). + +## Open source & contribution + +This project is actively developed and maintained by the [Pollen Robotics team](https://www.pollen-robotics.com) and the [Hugging Face team](https://huggingface.co/). + +We welcome contributions from the community! If you want to report a bug or request a feature, please open an issue on GitHub. If you want to contribute code, please fork the repository and submit a pull request. + +### 3D models + +TODO + +### Contributing Development tools are available in the optional dependencies. @@ -161,10 +253,19 @@ pre-commit run --all-files Checks are performed by Ruff. You may want to [configure your IDE to support it](https://docs.astral.sh/ruff/editors/setup/). +## Troubleshooting + +see [dedicated section](docs/troubleshooting.md) + +## License + +This project is licensed under the Apache 2.0 License - see the [LICENSE](LICENSE) file for details. + +The robot design files are licensed under the [TODO](TODO) license. + ### Simulation model used - https://polyhaven.com/a/food_apple_01 - https://polyhaven.com/a/croissant - https://polyhaven.com/a/wooden_table_02 - https://polyhaven.com/a/rubber_duck_toy - diff --git a/docs/RPI.md b/docs/RPI.md new file mode 100644 index 00000000..2d05fb5b --- /dev/null +++ b/docs/RPI.md @@ -0,0 +1,84 @@ +# Raspberry Pi Installation + +## RPI Lite OS + +Follow the [official documentation](https://www.raspberrypi.com/documentation/computers/getting-started.html#installing-the-operating-system) to install the Raspberry Pi OS Lite (64 bits). + +It is recommended to setup a wifi password and a ssh connection. + +## Gstreamer + +```bash +sudo apt-get install libgstreamer-plugins-bad1.0-dev libgstreamer-plugins-base1.0-dev libgstreamer1.0-dev libglib2.0-dev libssl-dev git libgirepository1.0-dev libcairo2-dev libportaudio2 gstreamer1.0-libcamera librpicam-app1 libssl-dev libnice10 gstreamer1.0-plugins-good gstreamer1.0-alsa gstreamer1.0-plugins-bad gstreamer1.0-nice +``` + +## Install Rust + +```bash +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +``` +## Webrtc plugin + +```bash +git clone https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs.git + +cd gst-plugins-rs + +git checkout 0.14.1 + +cargo install cargo-c + +sudo mkdir /opt/gst-plugins-rs + +sudo chown reachy /opt/gst-plugins-rs + +cargo cinstall -p gst-plugin-webrtc --prefix=/opt/gst-plugins-rs --release + +echo 'export GST_PLUGIN_PATH=/opt/gst-plugins-rs/lib/aarch64-linux-gnu/' >> ~/.bashrc +``` + +## Install Daemon + +Install with gstreamer extra dependencies + +pip install -e .[gstreamer] + +## Usage + +### Daemon + +The webrtc streaming will start automatically with the wireless option: + +```bash +reachy-mini-daemon --wireless-version +``` + +### Client + +This should open view of the camera, and play back the sound. + +```bash +python examples/debug/gstreamer_client.py --signaling-host +``` + +It is assumed that gstreamer is installed in your machine. For Linux users you may want to follow the above procedure. For MacOS, please install via [brew](https://gstreamer.freedesktop.org/download/#macos). *ToDo* For Windows please make a conda environement. + + +## Unit tests + +### Manually create the webrtcsrc server + +```bash +gst-launch-1.0 webrtcsink run-signalling-server=true meta="meta,name=reachymini" name=ws libcamerasrc ! capsfilter caps=video/x-raw,width=1280,height=720,framerate=60/1,format=YUY2,colorimetry=bt709,interlace-mode=progressive ! queue ! v4l2h264enc extra-controls="controls,repeat_sequence_header=1" ! 'video/x-h264,level=(string)4' ! ws. alsasrc device=hw:4 ! queue ! audioconvert ! audioresample ! opusenc ! audio/x-opus, rate=48000, channels=2 ! ws. +``` + +### Send sound to Reachy Mini + +Send an audio RTP stream to the port 5000 + +```bash +gst-launch-1.0 audiotestsrc ! audioconvert ! audioresample ! opusenc ! audio/x-opus, rate=48000, channels=2 ! rtpopuspay pt=96 ! udpsink host=10.0.1.38 port=5000 +``` + + + diff --git a/docs/assets/dashboard.png b/docs/assets/dashboard.png new file mode 100644 index 00000000..5151cf3e --- /dev/null +++ b/docs/assets/dashboard.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fbc9f4b65f99579120616350cf4edbd5f917df4c5b70a10c0560d3b9222d933e +size 240845 diff --git a/docs/assets/head_frame.png b/docs/assets/head_frame.png new file mode 100644 index 00000000..1e64ebc3 --- /dev/null +++ b/docs/assets/head_frame.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:10bfc85054bdcf06fc5fd82a858fde16a62c440e239979223f657f6355a03892 +size 580909 diff --git a/docs/assets/qrcode-ap.png b/docs/assets/qrcode-ap.png new file mode 100644 index 00000000..71add297 --- /dev/null +++ b/docs/assets/qrcode-ap.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8de9ece109bb4befadeabc4991f42c56cb367555b06074e0e688f96070e07fec +size 3820 diff --git a/docs/assets/reachy_mini_hello.gif b/docs/assets/reachy_mini_hello.gif index 08cca1c6..94fe3933 100644 --- a/docs/assets/reachy_mini_hello.gif +++ b/docs/assets/reachy_mini_hello.gif @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e7c8bf70e2673387ff2b0ae0333a64dca5c2d322c19ca3237a254ca398cf3016 -size 7096144 +oid sha256:eeaa08cd92c11329cec8e72937eaecf4ad41ca8adc56572272cdfb8d2d65020a +size 7134867 diff --git a/docs/assets/world_frame.png b/docs/assets/world_frame.png new file mode 100644 index 00000000..4a15939f --- /dev/null +++ b/docs/assets/world_frame.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2dbd3cec472b6dafd9064541ad9c494785632c7be1d312f859c0a649773c33b5 +size 570645 diff --git a/docs/awesome-apps.md b/docs/awesome-apps.md new file mode 100644 index 00000000..c1169a20 --- /dev/null +++ b/docs/awesome-apps.md @@ -0,0 +1,11 @@ +# Awesome Reachy Mini Apps + +This repository contains a collection of awesome applications and demos built for the Reachy Mini robot. These applications showcase the capabilities of Reachy Mini in various domains, including robotics, AI, and human-robot interaction. + +Feel free to explore and contribute to this list! + +## Applications and Demos + +- **[Conversational Demo](https://github.com/pollen-robotics/reachy_mini_conversation_demo)**: by Pollen Robotics. A demo that combines LLM realtime APIs, vision pipelines, and choreographed motion libraries to enable natural conversations with Reachy Mini. + +- **[Reachy Mini Dancer](https://github.com/LAURA-agent/reachy_mini_dancer)**: by @Townie. A desktop viewer with daemon UI and choreography system. \ No newline at end of file diff --git a/docs/API.md b/docs/python-sdk.md similarity index 82% rename from docs/API.md rename to docs/python-sdk.md index 09a128d5..edb9c3b8 100644 --- a/docs/API.md +++ b/docs/python-sdk.md @@ -1,6 +1,6 @@ # Reachy Mini API Documentation -*⚠️ All examples shown below suppose that you have already started the Reachy Mini daemon, either by running `reachy-mini-daemon` or by using the Python module `reachy_mini.daemon.cli`. ⚠️* +*⚠️ All examples shown below suppose that you have already started the Reachy Mini daemon, either by running `reachy-mini-daemon` or by using the Python module `reachy_mini.daemon.app.main`. ⚠️* ## ReachyMini @@ -27,11 +27,15 @@ with ReachyMini() as mini: ### Moving the robot -Then, the next step is to show how to move the robot. The `ReachyMini` class provides methods called `set_target` and `goto_target` that allows you to move the robot's joints to a specific target position. You can control: +Then, the next step is to show how to move the robot. The `ReachyMini` class provides methods called `set_target` and `goto_target` that allows you to move the robot's head to a specific target position. You can control: * the head's position and orientation * the body's rotation angle * the antennas' position +The `head_frame` is positioned at the base of the head, as you can see in the image below. This is the frame you are controling when using `set_target` and `goto_target`. + +[![Reachy Mini Head Frame](/docs/assets/head_frame.png)]() + For instance, to move the head of the robot slightly to the left then go back, you can use the following code: ```python @@ -99,7 +103,7 @@ with ReachyMini() as reachy: reachy.goto_target(antennas=[0, 0], duration=1.0) ``` -You need to pass the angles in radians, so you can use `numpy.deg2rad` to convert degrees to radians. The first value in the list corresponds to the left antenna, and the second value corresponds to the right antenna. +You need to pass the angles in radians, so you can use `numpy.deg2rad` to convert degrees to radians. The first value in the list corresponds to the right antenna, and the second value corresponds to the left antenna. You can also move the head, the body and the antennas at the same time by passing all three arguments to the `goto_target` method: @@ -206,10 +210,13 @@ The robot will not be able to follow the command exactly due to the safety limit Reachy mini will not throw an error if you exceed these limits, but it will move to the closest valid position within the limits. You can check the current position of the head using the `get_current_head_pose` method of the `ReachyMini` instance. For example: ```python +import time + from reachy_mini import ReachyMini from reachy_mini.utils import create_head_pose -import time + reachy = ReachyMini() + # construct a head pose with roll -20 degrees pose = create_head_pose(roll=-20, degrees=True) reachy.goto_target(head=pose) @@ -228,7 +235,9 @@ The `look_at_image` method allows the robot to look at a point in the image coor You can see the example in [look_at_image.py](../examples/look_at_image.py). -There is also a `look_at_world` method that allows the robot to look at a point in the world coordinates. The world coordinates are defined as a 3D point in the robot's coordinate system. TODO add a schematic of this coordinate system. +There is also a `look_at_world` method that allows the robot to look at a point in the world coordinates. The world coordinates are defined as a 3D point in the robot's coordinate system. + +[![Reachy Mini World Frame](/docs/assets/world_frame.png)]() ### Enable/disable motors and compliancy @@ -247,6 +256,18 @@ For example, this could be a teleoperation script, and you could be recording cu You can create another ReachyMini client (or use the same) and simply use `start_recording()` and `stop_recording()`. Below is the snippet of code that show how to start a recording, stop the recording and unpack the data: ```python +import time + +from reachy_mini import ReachyMini + + +with ReachyMini() as mini: + data = { + "description": "TODO: describe what you captured.", + "time": [], + "set_target_data": [], + } + try: # The daemon will start recording the set_target() calls mini.start_recording() @@ -254,21 +275,23 @@ Below is the snippet of code that show how to start a recording, stop the record while True: # Keep the script alive to listen for Ctrl+C - time.sleep(0.01) + time.sleep(0.01) except KeyboardInterrupt: # Stop recording and retrieve the logged data recorded_motion = mini.stop_recording() - - data = {} - for frame in recorded_motion: - data["time"].append(frame.get("time")) - pose_info = { - 'head': frame.get('head'), - 'antennas': frame.get('antennas'), - 'body_yaw': frame.get('body_yaw'), - } - data["set_target_data"].append(pose_info) + + for frame in recorded_motion: + data["time"].append(frame.get("time")) + pose_info = { + "head": frame.get("head"), + "antennas": frame.get("antennas"), + "body_yaw": frame.get("body_yaw"), + } + data["set_target_data"].append(pose_info) ``` + +We also provide [tools](https://github.com/pollen-robotics/reachy_mini_toolbox/tree/main/tools/moves) to record and upload a dataset to the hub, that can be easily replayed later. See [This section](#playing-moves) + ## Accessing the sensors Reachy Mini comes with several sensors (camera, microphone, speaker) that are connected to your computer via USB through the robot. These devices are accessible via the `reachy_mini.media` object. @@ -279,13 +302,14 @@ A camera frame can be easily retrieved as follows: ```python from reachy_mini import ReachyMini + with ReachyMini() as reachy_mini: while True: frame = reachy_mini.media.get_frame() # frame is a numpy array as output by opencv ``` -Please refer to the examples: [look_at_image](../examples/look_at_images.py) and [security_camera](../examples/security_camera.py). +Please refer to the example: [look_at_image](../examples/look_at_image.py). ### Microphone @@ -293,10 +317,11 @@ Audio samples from the microphone can be obtained as follows: ```python from reachy_mini import ReachyMini -with ReachyMini() as reachy_mini: + +with ReachyMini() as mini: while True: sample = mini.media.get_audio_sample() - # sample is a numpy array as output by souddevice + # sample is a numpy array as output by sounddevice ``` Please refer to the example [sound_record](../examples/debug/sound_record.py). @@ -307,10 +332,11 @@ Audio samples can be pushed to the speaker as follows: ```python from reachy_mini import ReachyMini -with ReachyMini() as reachy_mini: + +with ReachyMini() as mini: while True: # get audio chunk from mic / live source / file - mini.media.push_audio_sample(chunk) + mini.media.push_audio_sample(chunk) ``` Please refer to the example: [sound_play](../examples/debug/sound_play.py). @@ -321,48 +347,48 @@ To enable the use of the GStreamer backend, the package should be installed as f ```bash -pip install -e .[gstreamer] +pip install -e ".[gstreamer]" ``` It is assumed that the [gstreamer binaires](https://gstreamer.freedesktop.org/download/) are properly installed on your system. You will likely want to configure your own pipeline. See [gstreamer camera](../src/reachy_mini/media/camera_gstreamer.py) for an example. ## Playing moves -You can also play predefined moves simply. You can find an example on how to do this in the [Dance Player](../examples/minimal_dance_player.py) example. +You can also play predefined moves simply. You can find an example on how to do this in the [recorded moves example](../examples/recorded_moves_example.py) examples. -Basically, you just need to load the move from the dataset you want. And run the `play_on` method on a `ReachyMini` instance. +Basically, you just need to load the move from the dataset you want. And run the `play_move` method on a `ReachyMini` instance. ```python -from reachy_mini.motion.dance_move import DanceMove -from reachy_mini.reachy_mini import ReachyMini - -recorded_moves = RecordedMoves("pollen-robotics/reachy-mini-dances-library") +from reachy_mini import ReachyMini +from reachy_mini.motion.recorded_move import RecordedMoves with ReachyMini() as mini: - move = recorded_moves.get("dizzy_spin") - move.play_on(mini) + recorded_moves = RecordedMoves("pollen-robotics/reachy-mini-dances-library") + print(recorded_moves.list_moves()) + + for move_name in recorded_moves.list_moves(): + print(f"Playing move: {move_name}") + mini.play_move(recorded_moves.get(move_name), initial_goto_duration=1.0) ``` -You can also list the available moves in a collection as follows: +The `initial_goto_duration` argument for `play_move()` allows you to smoothly go to the starting position of the move before starting to execute it. -```python -from reachy_mini.motion.collection.dance import AVAILABLE_MOVES +The datasets are hosted on the Hugging Face Hub ([for example](https://huggingface.co/datasets/pollen-robotics/reachy-mini-emotions-library)) -print("Available moves:", list(AVAILABLE_MOVES.keys())) - ->>> Available moves: ['simple_nod', 'head_tilt_roll', 'side_to_side_sway', 'dizzy_spin', 'stumble_and_recover', 'headbanger_combo', 'interwoven_spirals', 'sharp_side_tilt', 'side_peekaboo', 'yeah_nod', 'uh_huh_tilt', 'neck_recoil', 'chin_lead', 'groovy_sway_and_roll', 'chicken_peck', 'side_glance_flick', 'polyrhythm_combo', 'grid_snap', 'pendulum_swing', 'jackson_square'] -``` +We provide tools to record and upload a dataset [here](https://github.com/pollen-robotics/reachy_mini_toolbox/tree/main/tools/moves). ## Writing an App We provide a simple way to wrap your behavior in an application that can be run as a standalone script. This is useful to properly manage the start/stop of the app and to add discovery/install mechanisms to allow users to easily run your app. We are also working on a dashboard to manage the apps and their installation. +**It is also a good way to share your app with the community and make it easily discoverable and installable!** See the [HF Spaces Reachy Mini Apps](https://huggingface.co/spaces/pollen-robotics/Reachy_Mini_Apps) for examples of apps shared by the community. + To write your app, you simply need to define a class that inherits from `ReachyMiniApp` and implements the `run` method. This method will be called when the app is started, and you can use it to interact with the robot. ```python import threading -from reachy_mini.app import ReachyMiniApp +from reachy_mini.apps.app import ReachyMiniApp from reachy_mini import ReachyMini @@ -374,6 +400,13 @@ class MyApp(ReachyMiniApp): The `stop_event` is a threading event that you should check periodically to know if the app should stop. You can use it to gracefully stop the app when the user wants to stop it. +### Using the space template + +Directly go to [HF Spaces Reachy Mini App Example](https://huggingface.co/spaces/pollen-robotics/reachy_mini_app_example) and follow the instruction! + + +### Using the app template generator + We also provide a script to make all the basic boilerplate for you. You can run the following command to create a new app: ```bash diff --git a/docs/rest-api.md b/docs/rest-api.md new file mode 100644 index 00000000..2d03d7ed --- /dev/null +++ b/docs/rest-api.md @@ -0,0 +1,25 @@ +# HTTP API documentation + +The Reachy Mini daemon provides a REST API that you can use to control the robot and get its state and even control the daemon itself. The API is implemented using [FastAPI](https://fastapi.tiangolo.com/) and [pydantic](https://docs.pydantic.dev/latest/) models. + +It should provide you all the necessary endpoints to interact with the robot, including: +- Getting the state of the robot (joints positions, motor status, etc.) +- Moving the robot's joints or setting specific poses + +The API is documented using OpenAPI, and you can access all available routes and test them at [http://localhost:8000/docs](http://localhost:8000/docs) when the daemon is running. +You can also access the raw OpenAPI schema at [http://localhost:8000/openapi.json](http://localhost:8000/openapi.json). + +This can be useful if you want to generate client code for your preferred programming language or framework, connect it to your AI application, or even to create your MCP server. + +### WebSocket support + +The API also supports WebSocket connections for real-time updates. For instance, you can subscribe to joint state updates: + +```js +let ws = new WebSocket(`ws://127.0.0.1:8000/api/state/ws/full`); + +ws.onmessage = (event) => { + const data = JSON.parse(event.data); + console.log(data); +}; +``` \ No newline at end of file diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md new file mode 100644 index 00000000..d1be564f --- /dev/null +++ b/docs/troubleshooting.md @@ -0,0 +1,43 @@ +# Troubleshooting + + +## No Microphone Input +*For beta only* + +There is a known issue where the microphone may not initialize correctly. Please update to [firmware 2.1.3](../src/reachy_mini/assets/firmware/reachymini_ua_io16_lin_v2.1.3.bin). You may need to run the [update script](../src/reachy_mini/assets/firmware/update.sh). Linux users may require to run the command as *sudo*. + +Afterwards, run [examples/debug/sound_record.py](../examples/debug/sound_record.py) to check that everything is working properly. + +If the problem persists, check the connection of the flex cables ([see slides 45 to 47](https://huggingface.co/spaces/pollen-robotics/Reachy_Mini_Assembly_Guide)). + + +## Sound Direction of Arrival Not Working +*For beta only* + +The microphone array requires firmware version 2.1.0 or higher to support this feature. The firmware files are located in `src/reachy_mini/assets/firmware/*.bin`. + +A [helper script](../src/reachy_mini/assets/firmware/update.sh) is available for Unix users (see above). Refer to the [Seeed documentation](https://wiki.seeedstudio.com/respeaker_xvf3800_introduction/#update-firmware) for more details on the upgrade process. + + +## Volume Is Too Low +*Linux only* + +Check in `alsamixer` that PCM1 is set to 100%. Then use PCM,0 to adjust the volume. + +To make this change permanent: +```bash +CARD=$(aplay -l | grep -i "reSpeaker XVF3800 4-Mic Array" | head -n1 | sed -n 's/^card \([0-9]*\):.*/\1/p') +amixer -c "$CARD" set PCM,1 100% +sudo alsactl store "$CARD" +``` + + +## Circular Buffer Overrun Warning + +When starting a client with `with ReachyMini() as mini:` in Mujoco (--sim mode), you may see the following warning: + +```bash +Circular buffer overrun. To avoid, increase fifo_size URL option. To survive in such case, use overrun_nonfatal option +``` + +This message comes from FFmpeg (embedded in OpenCV) while consuming the UDP video stream. It appears because the frames are not being used, causing the buffer to fill up. If you do not intend to use the frames, set `ReachyMini(media_backend="no_media")` or `ReachyMini(media_backend="default_no_video")`. \ No newline at end of file diff --git a/docs/wireless-version.md b/docs/wireless-version.md new file mode 100644 index 00000000..d38723f2 --- /dev/null +++ b/docs/wireless-version.md @@ -0,0 +1,13 @@ +# Set up your Reachy Mini wireless + +## Connect to a Wi-Fi network + +1. Power on your Reachy Mini. +2. Reachy Mini will create its own access point: "reachy-mini-ap". It should appear in the list of available Wi-Fi networks on your computer or smartphone after a few moments. +3. Connect your computer to the `reachy-mini-ap` Wi-Fi network (password: `reachy-mini`). Or you can directly scan the QR-code below to join the network: + + ![QR-Code reachy-mini-ap](./assets/qrcode-ap.png) + +4. Open a web browser and go to [http://reachy-mini.local:8000/settings](http://reachy-mini.local:8000/settings) to access the configuration page. +5. Enter your Wi-Fi network credentials (SSID and password) and click "Connect". +6. Wait a few moments for Reachy Mini to connect to your Wi-Fi network. The access point will disappear once connected. If the connection fails, Reachy Mini will restart the access point, and you can try again. diff --git a/examples/debug/gstreamer_client.py b/examples/debug/gstreamer_client.py new file mode 100644 index 00000000..d462d27b --- /dev/null +++ b/examples/debug/gstreamer_client.py @@ -0,0 +1,165 @@ +"""Simple gstreamer webrtc consumer example.""" + +import argparse + +import gi +from gst_signalling.utils import find_producer_peer_id_by_name + +gi.require_version("Gst", "1.0") +from gi.repository import GLib, Gst # noqa: E402 + + +class GstConsumer: + """Gstreamer webrtc consumer class.""" + + def __init__( + self, + signalling_host: str, + signalling_port: int, + peer_name: str, + ) -> None: + """Initialize the consumer with signalling server details and peer name.""" + Gst.init(None) + + self.pipeline = Gst.Pipeline.new("webRTC-consumer") + self.source = Gst.ElementFactory.make("webrtcsrc") + + if not self.pipeline: + print("Pipeline could be created.") + exit(-1) + + if not self.source: + print( + "webrtcsrc component could not be created. Please make sure that the plugin is installed \ + (see https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/tree/main/net/webrtc)" + ) + exit(-1) + + self.pipeline.add(self.source) + + peer_id = find_producer_peer_id_by_name( + signalling_host, signalling_port, peer_name + ) + print(f"found peer id: {peer_id}") + + self.source.connect("pad-added", self.webrtcsrc_pad_added_cb) + signaller = self.source.get_property("signaller") + signaller.set_property("producer-peer-id", peer_id) + signaller.set_property("uri", f"ws://{signalling_host}:{signalling_port}") + + def dump_latency(self) -> None: + """Dump the current pipeline latency.""" + query = Gst.Query.new_latency() + self.pipeline.query(query) + print(f"Pipeline latency {query.parse_latency()}") + + def _configure_webrtcbin(self, webrtcsrc: Gst.Element) -> None: + if isinstance(webrtcsrc, Gst.Bin): + webrtcbin_name = "webrtcbin0" + webrtcbin = webrtcsrc.get_by_name(webrtcbin_name) + assert webrtcbin is not None + # jitterbuffer has a default 200 ms buffer. + webrtcbin.set_property("latency", 50) + + def webrtcsrc_pad_added_cb(self, webrtcsrc: Gst.Element, pad: Gst.Pad) -> None: + """Add webrtcsrc elements when a new pad is added.""" + self._configure_webrtcbin(webrtcsrc) + if pad.get_name().startswith("video"): # type: ignore[union-attr] + # webrtcsrc automatically decodes and convert the video + sink = Gst.ElementFactory.make("fpsdisplaysink") + assert sink is not None + self.pipeline.add(sink) + pad.link(sink.get_static_pad("sink")) # type: ignore[arg-type] + sink.sync_state_with_parent() + + elif pad.get_name().startswith("audio"): # type: ignore[union-attr] + # webrtcsrc automatically decodes and convert the audio + sink = Gst.ElementFactory.make("autoaudiosink") + assert sink is not None + self.pipeline.add(sink) + pad.link(sink.get_static_pad("sink")) # type: ignore[arg-type] + sink.sync_state_with_parent() + + GLib.timeout_add_seconds(5, self.dump_latency) + + def __del__(self) -> None: + """Destructor to clean up GStreamer resources.""" + Gst.deinit() + + def get_bus(self) -> Gst.Bus: + """Get the GStreamer bus for the pipeline.""" + return self.pipeline.get_bus() + + def play(self) -> None: + """Start the GStreamer pipeline.""" + ret = self.pipeline.set_state(Gst.State.PLAYING) + if ret == Gst.StateChangeReturn.FAILURE: + print("Error starting playback.") + exit(-1) + print("playing ... (ctrl+c to quit)") + + def stop(self) -> None: + """Stop the GStreamer pipeline.""" + print("stopping") + self.pipeline.send_event(Gst.Event.new_eos()) + self.pipeline.set_state(Gst.State.NULL) + + +def process_msg(bus: Gst.Bus, pipeline: Gst.Pipeline) -> bool: + """Process messages from the GStreamer bus.""" + msg = bus.timed_pop_filtered(10 * Gst.MSECOND, Gst.MessageType.ANY) + if msg: + if msg.type == Gst.MessageType.ERROR: + err, debug = msg.parse_error() + print(f"Error: {err}, {debug}") + return False + elif msg.type == Gst.MessageType.EOS: + print("End-Of-Stream reached.") + return False + elif msg.type == Gst.MessageType.LATENCY: + if pipeline: + try: + pipeline.recalculate_latency() + except Exception as e: + print("failed to recalculate warning, exception: %s" % str(e)) + # else: + # print(f"Message: {msg.type}") + return True + + +def main() -> None: + """Run the main function.""" + parser = argparse.ArgumentParser(description="webrtc gstreamer simple consumer") + parser.add_argument( + "--signaling-host", + default="127.0.0.1", + help="Gstreamer signaling host - Reachy Mini ip", + ) + parser.add_argument( + "--signaling-port", default=8443, help="Gstreamer signaling port" + ) + + args = parser.parse_args() + + consumer = GstConsumer( + args.signaling_host, + args.signaling_port, + "reachymini", + ) + consumer.play() + + # Wait until error or EOS + bus = consumer.get_bus() + try: + while True: + if not process_msg(bus, consumer.pipeline): + break + + except KeyboardInterrupt: + print("User exit") + finally: + consumer.stop() + + +if __name__ == "__main__": + main() diff --git a/examples/debug/measure_tracking.py b/examples/debug/measure_tracking.py index e4ccb28c..ccdf765f 100644 --- a/examples/debug/measure_tracking.py +++ b/examples/debug/measure_tracking.py @@ -27,15 +27,15 @@ import matplotlib.pyplot as plt import numpy as np +from reachy_mini_dances_library.collection.dance import AVAILABLE_MOVES from scipy.spatial.transform import Rotation as R from reachy_mini import ReachyMini, utils -from reachy_mini.motion.collection.dance import AVAILABLE_MOVES from reachy_mini.utils.interpolation import distance_between_poses # ---------------- Configuration (tweak as needed) ---------------- BPM: float = 120.0 # tempo for all moves -BEATS_PER_MOVE: float = 8.0 # duration per move +BEATS_PER_MOVE: float = 30.0 # duration per move SAMPLE_HZ: float = 200.0 # control + measurement rate NEUTRAL_POS = np.array([0.0, 0.0, 0.0]) # meters NEUTRAL_EUL = np.zeros(3) # radians @@ -64,6 +64,7 @@ def plot_errors_stack( magic_mm: np.ndarray, title_suffix: str, out_png: Path, + beat_period_s: float | None = None, ) -> None: """Create a 3-row vertical stack with shared X axis and save as PNG.""" if t_abs.size == 0: @@ -93,6 +94,8 @@ def plot_errors_stack( ax.grid(True, which="both", alpha=0.3) ax.legend() + _draw_period_markers(axes, t, beat_period_s) + fig.suptitle(f"Pose tracking errors vs time - {title_suffix}", fontsize=14) fig.savefig(out_png, dpi=150) plt.close(fig) @@ -106,6 +109,7 @@ def plot_xyzrpy_stack( present_rpy_deg: np.ndarray, title_suffix: str, out_png: Path, + beat_period_s: float | None = None, ) -> None: """Create a 6-row vertical stack (X/Y/Z in mm, Roll/Pitch/Yaw in deg), goal vs present.""" if t_abs.size == 0: @@ -157,11 +161,35 @@ def plot_xyzrpy_stack( ax.legend() axes[-1].set_xlabel("Time [s]") + _draw_period_markers(axes, t, beat_period_s) fig.suptitle(f"Head position and orientation vs time - {title_suffix}", fontsize=14) fig.savefig(out_png, dpi=150) plt.close(fig) +def _draw_period_markers( + axes: np.ndarray, t: np.ndarray, beat_period_s: float | None +) -> None: + if beat_period_s is None or beat_period_s <= 0.0 or t.size == 0: + return + duration = float(t[-1]) + if duration <= 0.0: + return + markers = np.arange(0.0, duration + 1e-9, beat_period_s) + if markers.size == 0: + markers = np.array([0.0]) + for ax in np.atleast_1d(axes): + for marker in markers: + ax.axvline( + marker, + color="tab:purple", + linewidth=1.2, + alpha=0.6, + linestyle="--", + zorder=3.0, + ) + + def estimate_present_update_rate( t: np.ndarray, present_pos_m: np.ndarray, pos_tol_m: float = 1e-5 ) -> float: @@ -389,6 +417,7 @@ def main() -> None: present_rpy_deg, ) t, _target, _current, trans_mm, ang_deg, magic_mm = data + beat_period_s = 60.0 / BPM if BPM > 0 else None plot_errors_stack( t_abs=t, @@ -397,6 +426,7 @@ def main() -> None: magic_mm=magic_mm, title_suffix=move_name, out_png=png_errors, + beat_period_s=beat_period_s, ) plot_xyzrpy_stack( t_abs=t, @@ -406,6 +436,7 @@ def main() -> None: present_rpy_deg=present_rpy_deg, title_suffix=move_name, out_png=png_xyzrpy, + beat_period_s=beat_period_s, ) logging.info("Saved %s, %s and %s", npz_path, png_errors, png_xyzrpy) diff --git a/examples/debug/sound_doa.py b/examples/debug/sound_doa.py new file mode 100644 index 00000000..3f552247 --- /dev/null +++ b/examples/debug/sound_doa.py @@ -0,0 +1,57 @@ +"""Reachy Mini sound playback example. + +This script demonstrates how to use the microphone array to detect the +Direction of Arrival (DoA) of speech. It calculates the position of the +sound source relative to the head, transforms it into world coordinates, +and commands the robot to look towards the speaker. +""" + +import logging +import time + +import numpy as np + +from reachy_mini import ReachyMini + + +def main() -> None: + """Continuously monitor audio input and orient the head toward the speaker.""" + logging.basicConfig( + level=logging.DEBUG, format="%(asctime)s [%(levelname)s] %(message)s" + ) + + with ReachyMini(log_level="DEBUG", automatic_body_yaw=True) as mini: + last_doa = -1 + THRESHOLD = 0.004 # ~2 degrees + while True: + doa = mini.media.audio.get_DoA() + print(f"DOA: {doa}") + if doa[1] and np.abs(doa[0] - last_doa) > THRESHOLD: + print(f" Speech detected at {doa[0]:.1f} radians") + p_head = [np.sin(doa[0]), np.cos(doa[0]), 0.0] + print( + f" Pointing to x={p_head[0]:.2f}, y={p_head[1]:.2f}, z={p_head[2]:.2f}" + ) + T_world_head = mini.get_current_head_pose() + R_world_head = T_world_head[:3, :3] + p_world = R_world_head @ p_head + print( + f" In world coordinates: x={p_world[0]:.2f}, y={p_world[1]:.2f}, z={p_world[2]:.2f}" + ) + mini.look_at_world(*p_world, duration=0.5) + last_doa = doa[0] + else: + if not doa[1]: + print(" No speech detected") + else: + print( + f" Small change in DOA: {doa[0]:.1f}° (last was {last_doa:.1f}°). Not moving." + ) + time.sleep(0.5) + + +if __name__ == "__main__": + try: + main() + except KeyboardInterrupt: + print("Exiting...") diff --git a/examples/debug/sound_play.py b/examples/debug/sound_play.py index d1ed9916..4f183fdb 100644 --- a/examples/debug/sound_play.py +++ b/examples/debug/sound_play.py @@ -10,24 +10,35 @@ import os import time -import librosa +import numpy as np +import scipy +import soundfile as sf from reachy_mini import ReachyMini from reachy_mini.utils.constants import ASSETS_ROOT_PATH -INPUT_FILE = os.path.join(ASSETS_ROOT_PATH, "proud2.wav") +INPUT_FILE = os.path.join(ASSETS_ROOT_PATH, "wake_up.wav") -def main(backend: str): +def main(backend: str) -> None: """Play a wav file by pushing samples to the audio device.""" logging.basicConfig( level=logging.DEBUG, format="%(asctime)s [%(levelname)s] %(message)s" ) with ReachyMini(log_level="DEBUG", media_backend=backend) as mini: - data, _ = librosa.load( - INPUT_FILE, sr=mini.media.get_audio_samplerate(), mono=True - ) + data, samplerate_in = sf.read(INPUT_FILE, dtype="float32") + + if samplerate_in != mini.media.get_output_audio_samplerate(): + data = scipy.signal.resample( + data, + int( + len(data) + * (mini.media.get_output_audio_samplerate() / samplerate_in) + ), + ) + if data.ndim > 1: # convert to mono + data = np.mean(data, axis=1) mini.media.start_playing() print("Playing audio...") @@ -35,8 +46,6 @@ def main(backend: str): chunk_size = 1024 for i in range(0, len(data), chunk_size): chunk = data[i : i + chunk_size] - if backend == "gstreamer": - chunk = chunk.tobytes() mini.media.push_audio_sample(chunk) time.sleep(1) # wait a bit to ensure all samples are played @@ -51,7 +60,7 @@ def main(backend: str): parser.add_argument( "--backend", type=str, - choices=["default", "gstreamer"], + choices=["default", "gstreamer", "webrtc"], default="default", help="Media backend to use.", ) diff --git a/examples/debug/sound_record.py b/examples/debug/sound_record.py index 4200c3ed..6664e618 100644 --- a/examples/debug/sound_record.py +++ b/examples/debug/sound_record.py @@ -13,22 +13,21 @@ OUTPUT_FILE = "recorded_audio.wav" -def main(backend: str): +def main(backend: str) -> None: """Record audio for 5 seconds and save to a WAV file.""" logging.basicConfig( level=logging.DEBUG, format="%(asctime)s [%(levelname)s] %(message)s" ) - with ReachyMini(log_level="DEBUG", media_backend=backend) as mini: + with ReachyMini(log_level="INFO", media_backend=backend) as mini: print(f"Recording for {DURATION} seconds...") audio_samples = [] t0 = time.time() mini.media.start_recording() while time.time() - t0 < DURATION: sample = mini.media.get_audio_sample() + if sample is not None: - if backend == "gstreamer": - sample = np.frombuffer(sample, dtype=np.int16).reshape(-1, 1) audio_samples.append(sample) else: print("No audio data available yet...") @@ -39,7 +38,7 @@ def main(backend: str): # Concatenate all samples and save if audio_samples: audio_data = np.concatenate(audio_samples, axis=0) - samplerate = mini.media.get_audio_samplerate() + samplerate = mini.media.get_input_audio_samplerate() sf.write(OUTPUT_FILE, audio_data, samplerate) print(f"Audio saved to {OUTPUT_FILE}") else: @@ -53,7 +52,7 @@ def main(backend: str): parser.add_argument( "--backend", type=str, - choices=["default", "gstreamer"], + choices=["default", "gstreamer", "webrtc"], default="default", help="Media backend to use.", ) diff --git a/examples/goto_interpolation_playground.py b/examples/goto_interpolation_playground.py index b0e613cc..bd894907 100644 --- a/examples/goto_interpolation_playground.py +++ b/examples/goto_interpolation_playground.py @@ -15,36 +15,39 @@ def main(): """Run the different interpolation methods.""" - with ReachyMini() as mini: - for method in InterpolationTechnique: - print(f"Testing method: {method}") - - pose = create_head_pose(x=0, y=0, z=0, yaw=0) - mini.goto_target(pose, duration=1.0, method=method) - - for _ in range(3): - pose = create_head_pose( - x=0.0, y=0.03, z=0, roll=5, yaw=-10, degrees=True - ) - mini.goto_target( - pose, - antennas=np.deg2rad([-20, 20]), - duration=1.0, - method=method, - ) - - pose = create_head_pose( - x=0.0, y=-0.03, z=0, roll=-5, yaw=10, degrees=True - ) - mini.goto_target( - pose, - antennas=np.deg2rad([20, -20]), - duration=1.0, - method=method, - ) - - pose = create_head_pose(x=0, y=0, z=0, yaw=0) - mini.goto_target(pose, duration=1.0, antennas=[0, 0], method=method) + with ReachyMini(media_backend="no_media") as mini: + try: + for method in InterpolationTechnique: + print(f"Testing method: {method}") + + pose = create_head_pose(x=0, y=0, z=0, yaw=0) + mini.goto_target(pose, duration=1.0, method=method) + + for _ in range(3): + pose = create_head_pose( + x=0.0, y=0.03, z=0, roll=5, yaw=-10, degrees=True + ) + mini.goto_target( + pose, + antennas=np.deg2rad([-20, 20]), + duration=1.0, + method=method, + ) + + pose = create_head_pose( + x=0.0, y=-0.03, z=0, roll=-5, yaw=10, degrees=True + ) + mini.goto_target( + pose, + antennas=np.deg2rad([20, -20]), + duration=1.0, + method=method, + ) + + pose = create_head_pose(x=0, y=0, z=0, yaw=0) + mini.goto_target(pose, duration=1.0, antennas=[0, 0], method=method) + except KeyboardInterrupt: + pass if __name__ == "__main__": diff --git a/examples/gui_demos/mini_head_lookat_gui.py b/examples/gui_demos/mini_head_lookat_gui.py new file mode 100644 index 00000000..990a82be --- /dev/null +++ b/examples/gui_demos/mini_head_lookat_gui.py @@ -0,0 +1,104 @@ +"""Reachy Mini Head Position GUI Example.""" + +import time +import tkinter as tk + +import numpy as np + +from reachy_mini import ReachyMini +from reachy_mini.utils import create_head_pose + + +def main(): + """Run a GUI to set the head position and orientation of Reachy Mini.""" + with ReachyMini(media_backend="no_media") as mini: + t0 = time.time() + + root = tk.Tk() + root.title("Set Look At XYZ Position") + + # Add sliders for X, Y, Z position + x_var = tk.DoubleVar(value=0.0) + y_var = tk.DoubleVar(value=0.0) + z_var = tk.DoubleVar(value=0.0) + + tk.Label(root, text="X (m):").grid(row=0, column=0) + tk.Scale( + root, + variable=x_var, + from_=-0.2, + to=0.2, + resolution=0.001, + orient=tk.HORIZONTAL, + length=200, + ).grid(row=0, column=1) + tk.Label(root, text="Y (m):").grid(row=1, column=0) + tk.Scale( + root, + variable=y_var, + from_=-0.2, + to=0.2, + resolution=0.001, + orient=tk.HORIZONTAL, + length=200, + ).grid(row=1, column=1) + tk.Label(root, text="Z (m):").grid(row=2, column=0) + tk.Scale( + root, + variable=z_var, + from_=-0.2, + to=0.2, + resolution=0.001, + orient=tk.HORIZONTAL, + length=200, + ).grid(row=2, column=1) + + tk.Label(root, text="Body Yaw (deg):").grid(row=3, column=0) + body_yaw_var = tk.DoubleVar(value=0.0) + tk.Scale( + root, + variable=body_yaw_var, + from_=-180, + to=180, + resolution=1.0, + orient=tk.HORIZONTAL, + length=200, + ).grid(row=3, column=1) + + mini.goto_target(create_head_pose(), antennas=[0.0, 0.0], duration=1.0) + + # Run the GUI in a non-blocking way + root.update() + + try: + while True: + t = time.time() - t0 + target = np.deg2rad(30) * np.sin(2 * np.pi * 0.5 * t) + + head = np.eye(4) + head[:3, 3] = [0, 0, 0.0] + + head = mini.look_at_world( + x_var.get(), + y_var.get(), + z_var.get(), + duration=0.3, + perform_movement=False, + ) + + root.update() + + mini.set_target( + head=head, + body_yaw=np.deg2rad(body_yaw_var.get()), + antennas=np.array([target, -target]), + ) + + except KeyboardInterrupt: + pass + finally: + root.destroy() + + +if __name__ == "__main__": + main() diff --git a/examples/mini_head_position_gui.py b/examples/gui_demos/mini_head_position_gui.py similarity index 69% rename from examples/mini_head_position_gui.py rename to examples/gui_demos/mini_head_position_gui.py index 6a0db488..dd4df4ec 100644 --- a/examples/mini_head_position_gui.py +++ b/examples/gui_demos/mini_head_position_gui.py @@ -7,11 +7,12 @@ from scipy.spatial.transform import Rotation as R from reachy_mini import ReachyMini +from reachy_mini.utils import create_head_pose def main(): """Run a GUI to set the head position and orientation of Reachy Mini.""" - with ReachyMini() as mini: + with ReachyMini(media_backend="no_media") as mini: t0 = time.time() root = tk.Tk() @@ -82,38 +83,39 @@ def main(): length=200, ).grid(row=6, column=1) - # add a checkbox to enable/disable collision checking - collision_check_var = tk.BooleanVar(value=False) - tk.Checkbutton(root, text="Check Collision", variable=collision_check_var).grid( - row=7, column=1 - ) + mini.goto_target(create_head_pose(), antennas=[0.0, 0.0], duration=1.0) # Run the GUI in a non-blocking way root.update() - while True: - t = time.time() - t0 - target = np.deg2rad(30) * np.sin(2 * np.pi * 0.5 * t) - - head = np.eye(4) - head[:3, 3] = [0, 0, 0.0] - - # Read values from the GUI - roll = np.deg2rad(roll_var.get()) - pitch = np.deg2rad(pitch_var.get()) - yaw = np.deg2rad(yaw_var.get()) - head[:3, :3] = R.from_euler( - "xyz", [roll, pitch, yaw], degrees=False - ).as_matrix() - head[:3, 3] = [x_var.get(), y_var.get(), z_var.get()] - - root.update() - - mini.set_target( - head=head, - body_yaw=np.deg2rad(body_yaw_var.get()), - antennas=np.array([target, -target]), - ) + try: + while True: + t = time.time() - t0 + target = np.deg2rad(30) * np.sin(2 * np.pi * 0.5 * t) + + head = np.eye(4) + head[:3, 3] = [0, 0, 0.0] + + # Read values from the GUI + roll = np.deg2rad(roll_var.get()) + pitch = np.deg2rad(pitch_var.get()) + yaw = np.deg2rad(yaw_var.get()) + head[:3, :3] = R.from_euler( + "xyz", [roll, pitch, yaw], degrees=False + ).as_matrix() + head[:3, 3] = [x_var.get(), y_var.get(), z_var.get()] + + root.update() + + mini.set_target( + head=head, + body_yaw=np.deg2rad(body_yaw_var.get()), + antennas=np.array([target, -target]), + ) + except KeyboardInterrupt: + pass + finally: + root.destroy() if __name__ == "__main__": diff --git a/examples/look_at_image.py b/examples/look_at_image.py index 7c6fdb10..7fd6f834 100644 --- a/examples/look_at_image.py +++ b/examples/look_at_image.py @@ -10,7 +10,6 @@ import argparse import cv2 -import numpy as np from reachy_mini import ReachyMini @@ -23,7 +22,7 @@ def click(event, x, y, flags, param): param["y"] = y -def main(backend): +def main(backend: str) -> None: """Show the camera feed from Reachy Mini and make it look at clicked points.""" state = {"x": 0, "y": 0, "just_clicked": False} @@ -32,27 +31,27 @@ def main(backend): print("Click on the image to make ReachyMini look at that point.") print("Press 'q' to quit the camera feed.") - with ReachyMini(use_sim=False, media_backend=backend) as reachy_mini: - while True: - frame = reachy_mini.media.get_frame() + with ReachyMini(media_backend=backend) as reachy_mini: + try: + while True: + frame = reachy_mini.media.get_frame() - if frame is None: - print("Failed to grab frame.") - continue + if frame is None: + print("Failed to grab frame.") + continue - if backend == "gstreamer": - frame = cv2.imdecode( - np.frombuffer(frame, dtype=np.uint8), cv2.IMREAD_COLOR - ) + cv2.imshow("Reachy Mini Camera", frame) + if cv2.waitKey(1) & 0xFF == ord("q"): + print("Exiting...") + break - cv2.imshow("Reachy Mini Camera", frame) - if cv2.waitKey(1) & 0xFF == ord("q"): - print("Exiting...") - break - - if state["just_clicked"]: - reachy_mini.look_at_image(state["x"], state["y"], duration=0.3) - state["just_clicked"] = False + if state["just_clicked"]: + reachy_mini.look_at_image(state["x"], state["y"], duration=0.3) + state["just_clicked"] = False + except KeyboardInterrupt: + print("Interrupted. Closing viewer...") + finally: + cv2.destroyAllWindows() if __name__ == "__main__": @@ -62,7 +61,7 @@ def main(backend): parser.add_argument( "--backend", type=str, - choices=["default", "gstreamer"], + choices=["default", "gstreamer", "webrtc"], default="default", help="Media backend to use.", ) diff --git a/examples/minimal_dance_player.py b/examples/minimal_dance_player.py deleted file mode 100644 index a8b13689..00000000 --- a/examples/minimal_dance_player.py +++ /dev/null @@ -1,35 +0,0 @@ -"""Demonstrate and play all available dance moves for Reachy Mini. - ----------------------------------- - -This script iterates through the `AVAILABLE_MOVES` dictionary and executes -each move once on the connected Reachy Mini robot in an infinite loop. -""" - -from reachy_mini import ReachyMini -from reachy_mini.motion.recorded_move import RecordedMove, RecordedMoves - - -def main() -> None: - """Connect to Reachy and run the main demonstration loop.""" - recorded_moves = RecordedMoves("pollen-robotics/reachy-mini-dances-library") - - print("Connecting to Reachy Mini...") - with ReachyMini() as reachy: - print("Connection successful! Starting dance sequence...\n") - try: - while True: - # recorded_moves.moves is a dict, iterate inside the dict: - - for move_name in recorded_moves.moves: - move: RecordedMove = recorded_moves.get(move_name) - print(f"Playing move: {move_name}: {move.description}\n") - # print(f"params: {move.move_params}") - reachy.play_move(move, initial_goto_duration=1.0) - - except KeyboardInterrupt: - print("\nDance sequence interrupted by user. Shutting down.") - - -if __name__ == "__main__": - main() diff --git a/examples/minimal_demo.py b/examples/minimal_demo.py new file mode 100644 index 00000000..5052246c --- /dev/null +++ b/examples/minimal_demo.py @@ -0,0 +1,28 @@ +"""Minimal demo for Reachy Mini.""" + +import time + +import numpy as np + +from reachy_mini import ReachyMini +from reachy_mini.utils import create_head_pose + +with ReachyMini(media_backend="no_media") as mini: + mini.goto_target(create_head_pose(), antennas=[0.0, 0.0], duration=1.0) + try: + while True: + t = time.time() + + antennas_offset = np.deg2rad(20 * np.sin(2 * np.pi * 0.5 * t)) + pitch = np.deg2rad(10 * np.sin(2 * np.pi * 0.5 * t)) + + head_pose = create_head_pose( + roll=0.0, + pitch=pitch, + yaw=0.0, + degrees=False, + mm=False, + ) + mini.set_target(head=head_pose, antennas=(antennas_offset, antennas_offset)) + except KeyboardInterrupt: + pass diff --git a/examples/reachy_compliant_demo.py b/examples/reachy_compliant_demo.py index 30d283f9..598c5d07 100644 --- a/examples/reachy_compliant_demo.py +++ b/examples/reachy_compliant_demo.py @@ -10,7 +10,10 @@ from reachy_mini import ReachyMini -with ReachyMini() as mini: +print( + "This demo currently only works with Placo as the kinematics engine. Start the daemon with:\nreachy-mini-daemon --kinematics-engine Placo" +) +with ReachyMini(media_backend="no_media") as mini: try: mini.enable_gravity_compensation() @@ -18,7 +21,8 @@ while True: # do nothing, just keep the program running time.sleep(0.02) - except KeyboardInterrupt: + pass + finally: mini.disable_gravity_compensation() print("Exiting... Reachy Mini is stiff again.") diff --git a/examples/recorded_moves_example.py b/examples/recorded_moves_example.py index e6053e2e..02fe994f 100644 --- a/examples/recorded_moves_example.py +++ b/examples/recorded_moves_example.py @@ -1,10 +1,47 @@ -from reachy_mini import ReachyMini # noqa: D100 -from reachy_mini.motion.recorded_move import RecordedMoves +"""Demonstrate and play all available moves from a dataset for Reachy Mini. -with ReachyMini() as mini: - recorded_moves = RecordedMoves("pollen-robotics/reachy-mini-emotions-library") - print(recorded_moves.list_moves()) +Run : - for move_name in recorded_moves.list_moves(): - print(f"Playing move: {move_name}") - mini.play_move(recorded_moves.get(move_name), initial_goto_duration=1.0) \ No newline at end of file +python3 recorded_moves_example.py -l [dance, emotions] +""" + +import argparse + +from reachy_mini import ReachyMini +from reachy_mini.motion.recorded_move import RecordedMove, RecordedMoves + + +def main(dataset_path: str) -> None: + """Connect to Reachy and run the main demonstration loop.""" + recorded_moves = RecordedMoves(dataset_path) + + print("Connecting to Reachy Mini...") + with ReachyMini(use_sim=False, media_backend="no_media") as reachy: + print("Connection successful! Starting dance sequence...\n") + try: + while True: + for move_name in recorded_moves.list_moves(): + move: RecordedMove = recorded_moves.get(move_name) + print(f"Playing move: {move_name}: {move.description}\n") + # print(f"params: {move.move_params}") + reachy.play_move(move, initial_goto_duration=1.0) + + except KeyboardInterrupt: + print("\n Sequence interrupted by user. Shutting down.") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Demonstrate and play all available dance moves for Reachy Mini." + ) + parser.add_argument( + "-l", "--library", type=str, default="dance", choices=["dance", "emotions"] + ) + args = parser.parse_args() + + dataset_path = ( + "pollen-robotics/reachy-mini-dances-library" + if args.library == "dance" + else "pollen-robotics/reachy-mini-emotions-library" + ) + main(dataset_path) diff --git a/examples/rerun_viewer.py b/examples/rerun_viewer.py new file mode 100644 index 00000000..994de7ab --- /dev/null +++ b/examples/rerun_viewer.py @@ -0,0 +1,42 @@ +"""Reachy Mini sound playback example. + +Open a wav and push samples to the speaker. This is a toy example, in real +conditions output from a microphone or a text-to-speech engine would be + pushed to the speaker instead. + +It requires the 'rerun-loader-urdf' package to be installed. It's not on PyPI, +so you need to install it from the GitHub repository: pip install git+https://github.com/rerun-io/rerun-loader-python-example-urdf.git +""" + +import logging +import time + +from reachy_mini import ReachyMini +from reachy_mini.utils.rerun import Rerun + + +def main(): + """Play a wav file by pushing samples to the audio device.""" + logging.basicConfig( + level=logging.DEBUG, format="%(asctime)s [%(levelname)s] %(message)s" + ) + + with ReachyMini(log_level="DEBUG") as mini: + try: + mini.enable_gravity_compensation() + rerun = Rerun(mini) + rerun.start() + + print("Reachy Mini is now compliant. Press Ctrl+C to exit.") + while True: + # do nothing, just keep the program running + time.sleep(0.02) + + except KeyboardInterrupt: + mini.disable_gravity_compensation() + rerun.stop() + print("Exiting... Reachy Mini is stiff again.") + + +if __name__ == "__main__": + main() diff --git a/examples/security_camera.py b/examples/security_camera.py deleted file mode 100644 index 4b2f3995..00000000 --- a/examples/security_camera.py +++ /dev/null @@ -1,99 +0,0 @@ -"""Security Camera Example. - -Detect motion by comparing two consecutive frames from a video feed. -Look at the center of the detected motion bounding box. -(Doesn't work very well for now :) ) -""" - -import time - -import cv2 -import numpy as np - -from reachy_mini import ReachyMini - - -def detect_motion( - img1, - img2, - *, - gaussian_kernel: tuple[int, int] = (5, 5), - threshold_value: int = 200, - min_blob_area: int = 100, -): - """Detect motion between two images.""" - blur1 = cv2.GaussianBlur(img1, gaussian_kernel, 0) - blur2 = cv2.GaussianBlur(img2, gaussian_kernel, 0) - gray1 = cv2.cvtColor(blur1, cv2.COLOR_BGR2GRAY) - gray2 = cv2.cvtColor(blur2, cv2.COLOR_BGR2GRAY) - - diff = cv2.absdiff(gray1, gray2) - diff = cv2.normalize(diff, None, 0, 255, cv2.NORM_MINMAX) - - _, mask = cv2.threshold(diff, threshold_value, 255, cv2.THRESH_BINARY) - kernel = np.ones((3, 3), np.uint8) - motion_mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel, iterations=2) - motion_mask = cv2.dilate(motion_mask, kernel, iterations=1) - - contours, _ = cv2.findContours( - motion_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE - ) - bboxes = [] - overlay = img2.copy() - for cnt in contours: - if cv2.contourArea(cnt) < min_blob_area: - continue - x, y, w, h = cv2.boundingRect(cnt) - bboxes.append((x, y, w, h)) - cv2.rectangle(overlay, (x, y), (x + w, y + h), (0, 0, 255), 2) - - return overlay, motion_mask, bboxes - - -def im_diff(prev, current): - """Compute the difference between two images.""" - prev_gray = cv2.cvtColor(prev, cv2.COLOR_BGR2GRAY) - current_gray = cv2.cvtColor(current, cv2.COLOR_BGR2GRAY) - - prev_gray = prev_gray / 255 - current_gray = current_gray / 255 - - diff = np.bitwise_not - return diff - - -time_interval = 0.05 -prev_frame = None -last_move = time.time() -with ReachyMini(use_sim=False) as reachy_mini: - while True: - frame = reachy_mini.mediaget_frame() - if frame is None: - print("Failed to grab frame.") - continue - - if prev_frame is not None: - overlay, mask, boxes = detect_motion(prev_frame, frame) - - cv2.imshow("motion_overlay", overlay) - cv2.imshow("motion_mask", mask) - - if len(boxes) > 0: - target_box = boxes[0] - u = int(np.mean([target_box[0], target_box[0] + target_box[2]])) - v = int(np.mean([target_box[1], target_box[1] + target_box[3]])) - cv2.circle( - frame, center=(u, v), radius=50, color=(0, 255, 0), thickness=2 - ) - if time.time() - last_move > 2.0: - reachy_mini.look_at_image(u, v, duration=0.5) - last_move = time.time() - prev_frame = None - - cv2.imshow("Frame", frame) - cv2.waitKey(int(time_interval * 1000)) - - if time.time() - last_move < 2.0: - prev_frame = None - - prev_frame = frame.copy() diff --git a/examples/sequence.py b/examples/sequence.py index 24712c9b..e6fcec28 100644 --- a/examples/sequence.py +++ b/examples/sequence.py @@ -7,7 +7,8 @@ from reachy_mini import ReachyMini -with ReachyMini() as reachy_mini: +with ReachyMini(media_backend="no_media") as reachy_mini: + reachy_mini.goto_target(np.eye(4), antennas=[0.0, 0.0], duration=1.0) try: while True: pose = np.eye(4) diff --git a/examples/the_worst_game.py b/examples/the_worst_game.py deleted file mode 100644 index ae845795..00000000 --- a/examples/the_worst_game.py +++ /dev/null @@ -1,521 +0,0 @@ -#!/usr/bin/env python3 -"""Reachy Mini head‑pose game with groove feedback, coloured bars and CLI. - -Default‑mode ------------- -Speed‑run: clear **N** targets (default 4) as fast as possible. -A target is “hit” when the *score* (translation mm + rotation °) drops -below the chosen **difficulty threshold**: - -* easy  → 25 -* medium  → 12  (default) -* hard  → 6 - -Your game score is the elapsed time. - -Other flags ------------ ---precision   30 s countdown – aim for the lowest score. ---cheats      Print per‑axis signed errors each frame instead of bars. ---levels N    Number of targets in speed‑run (default 4). - -Visual HUD ----------- -Three moving bars every frame (green < ½ thr | orange < thr | red ≥ thr): - -1. **l2 (mm)** max‑range 40 mm -2. **angle (°)** max‑range 40 ° -3. **score** against the difficulty threshold -""" - -import argparse -import queue -import random -import sys -import threading -import time -from collections import deque -from typing import Deque, Tuple - -import numpy as np -import simpleaudio as sa -from scipy.spatial.transform import Rotation as R - -from reachy_mini import ReachyMini -from reachy_mini.utils.interpolation import distance_between_poses - -# ────────────────────────────── audio constants ──────────────────────────── -RATE: int = 44_100 -SLICE_SEC: float = 0.05 -BPM: int = 96 -BAR_SEC: float = 60 / BPM * 4 - -BASE_DIST_VOL: float = 0.15 -MAX_DIST_GAIN: float = 0.55 -MAX_ANG_GAIN: float = 0.45 - -state_q: queue.Queue[Tuple[float, float]] = queue.Queue() # (l2_mm, ang_deg) - - -# ────────────────────────── jingle helpers ──────────────────────────── -def _env(length_s: float, decay: float = 6.0) -> np.ndarray: - """Exponential decay envelope.""" - t = np.linspace(0.0, length_s, int(RATE * length_s), endpoint=False) - return np.exp(-decay * t) - - -def _play(buf: np.ndarray) -> None: # helper to fire and block - sa.play_buffer((buf * 32_767).astype(np.int16), 1, 2, RATE).wait_done() - - -# ───────────────────────── satisfying victory jingles ────────────────── -def jingle_arpeggio() -> None: - """Up‑arpeggiated triad • octave drop • little twinkle.""" - root = 392 # G4 - tones = [root, root * 2 ** (4 / 12), root * 2 ** (7 / 12), root * 2] - part = [] - for f in tones: - t = np.linspace(0, 0.12, int(RATE * 0.12), endpoint=False) - seg = 0.5 * np.sin(2 * np.pi * f * t) * _env(0.12) - part.append(seg) - # final twinkle - t = np.linspace(0, 0.25, int(RATE * 0.25), endpoint=False) - twinkle = ( - 0.3 - * (np.sin(2 * np.pi * 2 * root * t) + 0.5 * np.sin(2 * np.pi * 3 * root * t)) - * _env(0.25, 10) - ) - buf = np.concatenate(part + [twinkle]) - _play(buf) - - -def jingle_minor_to_major() -> None: - """Start on a dramatic minor triad, flips to major, ends with a bell.""" - root = 349 # F4 - t1 = np.linspace(0, 0.25, int(RATE * 0.25), endpoint=False) - minor = ( - ( - np.sin(2 * np.pi * root * t1) - + np.sin(2 * np.pi * root * 2 ** (3 / 12) * t1) - + np.sin(2 * np.pi * root * 2 ** (7 / 12) * t1) - ) - * 0.35 - * _env(0.25, 5) - ) - - t2 = np.linspace(0, 0.25, int(RATE * 0.25), endpoint=False) - major = ( - ( - np.sin(2 * np.pi * root * 2 * t2) - + np.sin(2 * np.pi * root * 2 ** (4 / 12) * 2 * t2) - + np.sin(2 * np.pi * root * 2 ** (7 / 12) * 2 * t2) - ) - * 0.35 - * _env(0.25, 5) - ) - - t3 = np.linspace(0, 0.18, int(RATE * 0.18), endpoint=False) - bell = 0.45 * np.sin(2 * np.pi * root * 3 * t3) * _env(0.18, 10) - - _play(np.concatenate((minor, major, bell))) - - -def jingle_brass_fanfare() -> None: - """Quick ascending brass‑style fanfare ending on a long tonic.""" - root = 392 # G4 - steps = [0, 2, 4, 5, 7, 9, 12] # major scale climb - buf_list: list[np.ndarray] = [] - - for step in steps: - f = root * 2 ** (step / 12) - t = np.linspace(0, 0.07, int(RATE * 0.07), endpoint=False) - tone = ( - 0.4 - * (np.sin(2 * np.pi * f * t) * 0.7 + 0.3 * np.sin(2 * np.pi * f * 2 * t)) - * _env(0.07, 8) - ) - buf_list.append(tone) - - # Final long tonic - t_final = np.linspace(0, 0.4, int(RATE * 0.4), endpoint=False) - final = ( - 0.5 - * ( - np.sin(2 * np.pi * root * 2 * t_final) * 0.6 - + 0.4 * np.sin(2 * np.pi * root * 3 * t_final) - ) - * _env(0.4, 3) - ) - - _play(np.concatenate(buf_list + [final])) - - -# ────────────────────────── drum synth helpers ───────────────────────────── -def _exp_env(n: int, tau: float) -> np.ndarray: - return np.exp(-np.arange(n) / RATE / tau, dtype=np.float32) - - -def _kick() -> np.ndarray: - n = int(RATE * 0.25) - t = np.arange(n) / RATE - body = np.sin(2 * np.pi * 80 * t * (1 - 0.5 * t / 0.25)) * _exp_env(n, 0.15) - click = 0.14 * np.sin(2 * np.pi * 180 * t) * _exp_env(n, 0.004) - return body + click - - -def _snare() -> np.ndarray: - n = int(RATE * 0.18) - noise = np.random.randn(n).astype(np.float32) * _exp_env(n, 0.05) - tone = 0.3 * np.sin(2 * np.pi * 180 * np.arange(n) / RATE) * _exp_env(n, 0.12) - return noise + tone - - -def _hat() -> np.ndarray: - n = int(RATE * 0.05) - return np.random.randn(n).astype(np.float32) * _exp_env(n, 0.008) - - -def _rim() -> np.ndarray: - n = int(RATE * 0.06) - t = np.arange(n) / RATE - body = 0.6 * np.sin(2 * np.pi * 1200 * t) * _exp_env(n, 0.01) - noise = 0.3 * np.random.randn(n).astype(np.float32) * _exp_env(n, 0.01) - return body + noise - - -def _build_loop(base_only: bool = False) -> np.ndarray: - """Build a one‑bar loop: base groove or angle layer.""" - buf = np.zeros(int(RATE * BAR_SEC), np.float32) - q = 60 / BPM - - def put(sample: np.ndarray, pos: float) -> None: - i = int(pos * RATE) - buf[i : i + sample.size] += sample[: max(0, buf.size - i)] - - put(_kick(), 0) - put(_kick(), 1.5 * q) - put(_kick(), 2.5 * q) - put(_snare(), 1 * q) - put(_snare(), 3 * q) - - if not base_only: - for off in np.arange(0, 4 * q, q / 2): - put(_hat(), off) - for off in [0.75 * q, 2.25 * q]: - put(_rim(), off) - shaker = np.random.randn(int(RATE * 0.04)).astype(np.float32) * _exp_env( - int(RATE * 0.04), 0.005 - ) - for off in np.arange(0, 4 * q, q / 4): - put(shaker, off) - - return buf / np.max(np.abs(buf)) - - -DIST_LOOP = _build_loop(base_only=True) -ANG_LOOP = _build_loop(base_only=False) - DIST_LOOP -DLEN, ALEN = DIST_LOOP.size, ANG_LOOP.size - - -# ─────────────────────────── success jingle ───────────────────────────────- -def _make_jingle(level: int) -> np.ndarray: - """Deterministic 0.4 s arpeggio for `level`.""" - root = 330 + level * 40 - freqs = [root, root * 2 ** (4 / 12), root * 2 ** (7 / 12)] - seg_len = int(RATE * 0.4 / len(freqs)) - buf = np.zeros(seg_len * len(freqs), np.float32) - for i, f in enumerate(freqs): - t = np.arange(seg_len) / RATE - segment = 0.45 * np.sin(2 * np.pi * f * t) * np.exp(-6 * t) - buf[i * seg_len : (i + 1) * seg_len] = segment - return (buf * 32_767).astype(np.int16) - - -def _play_jingle(level: int) -> None: - sa.play_buffer(_make_jingle(level), 1, 2, RATE) - - -# ─────────────────────────── pose utilities ─────────────────────────────── -def random_target_pose() -> np.ndarray: - """Generate a random target pose for Reachy Mini.""" - x = random.uniform(-8, 5) - y = random.uniform(-10, 10) - z = random.uniform(-15, 5) - roll = random.uniform(-10, 10) - pitch = random.uniform(-10, 10) - yaw = random.uniform(-10, 10) - - pose = np.eye(4) - pose[:3, :3] = R.from_euler("xyz", [roll, pitch, yaw], degrees=True).as_matrix() - pose[:3, 3] = np.array([x, y, z]) / 1_000 # mm → m - return pose - - -def _pose_error_components( - target: np.ndarray, pose: np.ndarray -) -> Tuple[np.ndarray, np.ndarray]: - d_xyz = (pose[:3, 3] - target[:3, 3]) * 1_000 - rot_err = R.from_matrix(target[:3, :3]).inv() * R.from_matrix(pose[:3, :3]) - d_rpy = rot_err.as_euler("xyz", degrees=True) - return d_xyz, d_rpy - - -# ────────────────────────── coloured bars ───────────────────────────────── -def _bar(value: float, colour_thresh: float, scale_max: float, width: int = 20) -> str: - """Return an ANSI colour bar.""" - ratio = min(value / scale_max, 1.0) - filled = int(ratio * width) - empty = width - filled - if value < 0.5 * colour_thresh: - colour = "32" # green - elif value < colour_thresh: - colour = "33" # orange - else: - colour = "31" # red - return f"\x1b[0;{colour}m" + "█" * filled + "-" * empty + "\x1b[0m" - - -# ─────────────────────────── audio thread ───────────────────────────────── -def _audio_thread() -> None: - """Stream 50 ms audio slices; keep PlayObjects alive.""" - l2_mm, ang_deg = 40.0, 40.0 - d_idx = a_idx = 0 - playing: Deque[sa.PlayObject] = deque(maxlen=32) - - while True: - try: - while True: - l2_mm, ang_deg = state_q.get_nowait() - except queue.Empty: - pass - - dist_gain = (max(0.0, 1 - l2_mm / 40)) ** 0.7 - ang_gain = (max(0.0, 1 - ang_deg / 40)) ** 0.7 - - ns = int(RATE * SLICE_SEC) - - def seg(loop: np.ndarray, idx: int) -> np.ndarray: - return ( - loop[idx : idx + ns] - if idx + ns < loop.size - else np.concatenate((loop[idx:], loop[: (idx + ns) % loop.size])) - ) - - dist_slice = seg(DIST_LOOP, d_idx) * (BASE_DIST_VOL + dist_gain * MAX_DIST_GAIN) - ang_slice = seg(ANG_LOOP, a_idx) * (ang_gain * MAX_ANG_GAIN) - - d_idx = (d_idx + ns) % DLEN - a_idx = (a_idx + ns) % ALEN - - mix = dist_slice + ang_slice - peak = np.max(np.abs(mix)) - if peak > 1: - mix /= peak - - playing.append(sa.play_buffer((mix * 32_767).astype(np.int16), 1, 2, RATE)) - time.sleep(SLICE_SEC * 0.9) - - -# ─────────────────────────── game loops ─────────────────────────────────── -def speed_run( - threshold: float, cheats: bool, n_levels: int, compensate_gravity: bool = False -) -> None: - """Speed‑run game: clear *n_levels* targets as fast as possible.""" - targets = [np.eye(4)] + [random_target_pose() for _ in range(n_levels - 1)] - current = 0 - best_score = float("inf") - start = time.monotonic() - - if cheats: - xyz, rpy = ( - _pose_error_components(np.eye(4), targets[0])[0], - R.from_matrix(targets[0][:3, :3]).as_euler("xyz", degrees=True), - ) - print("Target 1 (x mm y mm z mm roll ° pitch ° yaw °):", *xyz, *rpy) - - print("\nLevel 1!\n") - - with ReachyMini() as reachy: - if compensate_gravity: - reachy.enable_motors() - reachy.make_motors_compliant(head=True, antennas=True) - else: - reachy.disable_motors() - first_loop = True - while current < n_levels: - if compensate_gravity: - reachy.compensate_gravity() - - pose = reachy.get_current_head_pose() - - t_dist, a_dist, score = distance_between_poses(targets[current], pose) - best_score = min(best_score, score) - l2_mm = t_dist * 1_000 - ang_deg = np.degrees(a_dist) - state_q.put((l2_mm, ang_deg)) - - if cheats: - d_xyz, d_rpy = _pose_error_components(targets[current], pose) - print( - f"\rΔx={d_xyz[0]:6.1f} Δy={d_xyz[1]:6.1f} Δz={d_xyz[2]:6.1f} mm | " - f"Δr={d_rpy[0]:6.1f} Δp={d_rpy[1]:6.1f} Δy={d_rpy[2]:6.1f} °", - end="", - ) - else: - bars = ( - _bar(l2_mm, threshold, 40), - _bar(ang_deg, threshold, 40), - _bar(score, threshold, threshold * 2), - ) - if first_loop: - print("\nDistance error Angle error Score") - first_loop = False - print( - f"\r{bars[0]} {bars[1]} {bars[2]} " - f"score={score:6.2f} best={best_score:6.2f}", - end="", - ) - - if score < threshold: - _play_jingle(current + 1) - current += 1 - best_score = float("inf") - if cheats and current < n_levels: - xyz, rpy = ( - _pose_error_components(np.eye(4), targets[current])[0], - R.from_matrix(targets[current][:3, :3]).as_euler( - "xyz", degrees=True - ), - ) - print("\nTarget", current + 1, ":", *xyz, *rpy) - elif current < n_levels: - print("\nLevel", current + 1, "!") - - time.sleep(0.02) - - print(f"\n🏁 Time for {n_levels} targets: {time.monotonic() - start:.2f} s") - - if compensate_gravity: - reachy.make_motors_compliant(head=False, antennas=False) - reachy.disable_motors() - - -def precision_mode(compensate_gravity: bool = False) -> None: - """30 s countdown; try for the lowest possible score.""" - deadline = time.monotonic() + 30 - best = float("inf") - - with ReachyMini() as reachy: - if compensate_gravity: - reachy.enable_motors() - reachy.make_motors_compliant(head=True, antennas=True) - else: - reachy.disable_motors() - first_loop = True - while (remain := deadline - time.monotonic()) > 0: - if compensate_gravity: - reachy.compensate_gravity() - - pose = reachy.get_current_head_pose() - - t_dist, a_dist, score = distance_between_poses(np.eye(4), pose) - best = min(best, score) - state_q.put((t_dist * 1_000, np.degrees(a_dist))) - - bars = ( - _bar(t_dist * 1_000, 40, 40), - _bar(np.degrees(a_dist), 40, 40), - _bar(score, 40, 40), - ) - if first_loop: - print("\nDistance error Angle error Score") - first_loop = False - print( - f"\r{bars[0]} {bars[1]} {bars[2]} " - f"score={score:6.2f} best={best:6.2f} time_left={remain:4.1f}s", - end="", - ) - time.sleep(0.02) - - print(f"\n⌛️ Precision best score = {best:.2f}") - - if compensate_gravity: - reachy.make_motors_compliant(head=False, antennas=False) - reachy.disable_motors() - - -# ─────────────────────────────── CLI ─────────────────────────────────────── -def parse_args() -> argparse.Namespace: - """Parse command‑line arguments.""" - parser = argparse.ArgumentParser(description="Reachy Mini head‑pose game") - parser.add_argument( - "--campaign", action="store_true", help="campaign mode with levels to clear" - ) - parser.add_argument("--cheats", action="store_true", help="print per‑axis errors") - parser.add_argument( - "--difficulty", - "-d", - choices=("easy", "medium", "hard"), - default="medium", - help="threshold: 25|12|6 (default medium)", - ) - parser.add_argument( - "--levels", - "-n", - type=int, - default=5, - help="number of targets in speed‑run (default 5)", - ) - parser.add_argument( - "--no-gravity", - action="store_true", - help="enable motors and compensate gravity (default off)", - ) - return parser.parse_args() - - -# ─────────────────────────────── main ────────────────────────────────────── -def main() -> None: - """Start audio thread and launch the chosen game mode.""" - print( - "\nGrab your Reachy Mini's head and try to get as close as possible to the target!\n" - ) - - with ReachyMini() as reachy: - # Goes to a predefined pose to avoid clearing targets at start - reachy.enable_motors() - pose = np.eye(4) - pose[:3, 3] = [0.0, 0.0, -0.02] - reachy.goto_target( - head=pose, - antennas=np.array([0.0, 0.0]), - body_yaw=0.0, - duration=1.0, - ) - state_q.put((40.0, 40.0)) # neutral seed for audio - threading.Thread(target=_audio_thread, daemon=True).start() - - args = parse_args() - if not args.campaign: - precision_mode() - sys.exit() - - thresholds = {"easy": 25.0, "medium": 12.0, "hard": 6.0} - speed_run( - thresholds[args.difficulty], - cheats=args.cheats, - n_levels=args.levels, - compensate_gravity=args.no_gravity, - ) - if args.difficulty == "hard": - jingle_brass_fanfare() - elif args.difficulty == "medium": - jingle_arpeggio() - else: - jingle_minor_to_major() - - time.sleep(1.0) - - -if __name__ == "__main__": - main() diff --git a/pyproject.toml b/pyproject.toml index 948c945c..9e0c8fab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,15 +4,15 @@ build-backend = "setuptools.build_meta" [project] name = "reachy_mini" -version = "0.1.0" +version = "1.1.3" authors = [{ name = "Pollen Robotics", email = "contact@pollen-robotics.com" }] description = "" readme = "README.md" requires-python = ">=3.10" dependencies = [ - "numpy>=2.2.5,<2.3.0", - "scipy==1.15.3", - "reachy_mini_motor_controller>=0.6.3", + "numpy>=2.2.5", + "scipy>=1.15.3, <2.0.0", + "reachy_mini_motor_controller>=1.4.2", "eclipse-zenoh>=1.4.0", "opencv-python<=5.0", "cv2_enumerate_cameras>=1.2.1", @@ -24,32 +24,51 @@ dependencies = [ "huggingface-hub==0.34.4", "sounddevice==0.5.1", "soundfile==0.13.1", - "librosa==0.11.0", - "reachy-mini-rust-kinematics==0.1.1", + "reachy-mini-rust-kinematics>=1.0.3", "asgiref", "aiohttp", + "log-throttling==0.0.3", + "pyusb>=1.2.1", + "libusb_package>=1.0.26.3", + "pip>=25", + "rich", + "questionary", + "websockets>=12,<16", ] [project.optional-dependencies] dev = [ - "pytest", + "pytest", "pytest-asyncio", - "ruff==0.12.0", + "ruff==0.12.0", "onshape-to-robot==1.7.6", "mujoco==3.3.0", "placo==0.9.14", "pre-commit", + "mypy==1.18.2", + "rustypot>=1.4.0", + "types-requests", + "rerun-sdk>=0.27.2", + "urdf-parser-py==0.0.4", ] examples = ["pynput"] mujoco = ["mujoco==3.3.0"] nn_kinematics = ["onnxruntime==1.22.1"] placo_kinematics = ["placo==0.9.14"] -gstreamer = ["PyGObject>=3.42.2,<=3.46.0"] +gstreamer = ["PyGObject>=3.42.2,<=3.46.0", "gst-signalling>=1.1.2"] +rerun = ["rerun-sdk>=0.27.2", "urdf-parser-py==0.0.4"] +wireless-version = [ + "semver>=3,<4", + "nmcli>=1.5", + "pollen_BMI088_imu_library", + "gpiozero>=2.0.0", + "lgpio>=0.2.2.0", +] [project.scripts] reachy-mini-daemon = "reachy_mini.daemon.app.main:main" -reachy-mini-make-app = "reachy_mini.apps.apps.app:main" +reachy-mini-app-assistant = "reachy_mini.apps.app:main" [tool.setuptools] package-dir = { "" = "src" } @@ -81,5 +100,15 @@ lint.ignore = [ testpaths = ["tests"] markers = [ "audio: mark test as requiring audio hardware", + "audio_gstreamer: mark test as requiring GStreamer for audio", "video: mark test as requiring video hardware", + "video_gstreamer: mark test as requiring GStreamer for video", + "wireless: mark test as requiring wireless Reachy Mini", + "wireless_gstreamer: mark test as requiring GStreamer for wireless Reachy Mini", ] + +[tool.mypy] +python_version = "3.10" +files = ["src/"] +ignore_missing_imports = true +strict = true diff --git a/src/reachy_mini/apps/__init__.py b/src/reachy_mini/apps/__init__.py index 6c4d0f8b..94c3e5fc 100644 --- a/src/reachy_mini/apps/__init__.py +++ b/src/reachy_mini/apps/__init__.py @@ -2,12 +2,15 @@ from dataclasses import dataclass, field from enum import Enum +from typing import Any, Dict class SourceKind(str, Enum): """Kinds of app source.""" HF_SPACE = "hf_space" + DASHBOARD_SELECTION = "dashboard_selection" + LOCAL = "local" INSTALLED = "installed" @@ -19,4 +22,4 @@ class AppInfo: source_kind: SourceKind description: str = "" url: str | None = None - extra: dict = field(default_factory=dict) + extra: Dict[str, Any] = field(default_factory=dict) diff --git a/src/reachy_mini/apps/app.py b/src/reachy_mini/apps/app.py index 3d52f44e..7637c3d2 100644 --- a/src/reachy_mini/apps/app.py +++ b/src/reachy_mini/apps/app.py @@ -7,11 +7,18 @@ It uses Jinja2 templates to generate the necessary files for the app project. """ +import argparse +import importlib import threading +import traceback from abc import ABC, abstractmethod from pathlib import Path +from typing import Any +from urllib.parse import urlparse -from jinja2 import Environment, FileSystemLoader +from fastapi import FastAPI +from fastapi.responses import FileResponse +from fastapi.staticfiles import StaticFiles from reachy_mini.reachy_mini import ReachyMini @@ -19,18 +26,70 @@ class ReachyMiniApp(ABC): """Base class for Reachy Mini applications.""" + custom_app_url: str | None = None + dont_start_webserver: bool = False + def __init__(self) -> None: """Initialize the Reachy Mini app.""" self.stop_event = threading.Event() + self.error: str = "" + + self.settings_app: FastAPI | None = None + if self.custom_app_url is not None and not self.dont_start_webserver: + self.settings_app = FastAPI() + + static_dir = self._get_instance_path().parent / "static" + if static_dir.exists(): + self.settings_app.mount( + "/static", StaticFiles(directory=static_dir), name="static" + ) - def wrapped_run(self) -> None: + index_file = static_dir / "index.html" + if index_file.exists(): + + @self.settings_app.get("/") + async def index() -> FileResponse: + """Serve the settings app index page.""" + return FileResponse(index_file) + + def wrapped_run(self, *args: Any, **kwargs: Any) -> None: """Wrap the run method with Reachy Mini context management.""" + settings_app_t: threading.Thread | None = None + if self.settings_app is not None: + import uvicorn + + assert self.custom_app_url is not None + url = urlparse(self.custom_app_url) + assert url.hostname is not None and url.port is not None + + config = uvicorn.Config( + self.settings_app, + host=url.hostname, + port=url.port, + ) + server = uvicorn.Server(config) + + def _server_run() -> None: + """Run the settings FastAPI app.""" + t = threading.Thread(target=server.run) + t.start() + self.stop_event.wait() + server.should_exit = True + t.join() + + settings_app_t = threading.Thread(target=_server_run) + settings_app_t.start() + try: - with ReachyMini() as reachy_mini: + with ReachyMini(*args, **kwargs) as reachy_mini: self.run(reachy_mini, self.stop_event) - except Exception as e: - print(f"An error occurred: {e}") + except Exception: + self.error = traceback.format_exc() raise + finally: + if settings_app_t is not None: + self.stop_event.set() + settings_app_t.join() @abstractmethod def run(self, reachy_mini: ReachyMini, stop_event: threading.Event) -> None: @@ -43,75 +102,102 @@ def run(self, reachy_mini: ReachyMini, stop_event: threading.Event) -> None: """ pass - def stop(self): + def stop(self) -> None: """Stop the app gracefully.""" self.stop_event.set() print("App is stopping...") + def _get_instance_path(self) -> Path: + """Get the file path of the app instance.""" + module_name = type(self).__module__ + mod = importlib.import_module(module_name) + assert mod.__file__ is not None -def make_app_project(app_name: str, path: Path) -> None: - """Create a new Reachy Mini app project with the given name at the specified path. + return Path(mod.__file__).resolve() - Args: - app_name (str): The name of the app to create. - path (Path): The directory where the app project will be created. - """ - TEMPLATE_DIR = Path(__file__).parent / "templates" - env = Environment(loader=FileSystemLoader(TEMPLATE_DIR)) - - def render_template(filename, context): - template = env.get_template(filename) - return template.render(context) - - base_path = Path(path).resolve() / app_name - if base_path.exists(): - print(f"❌ Folder {base_path} already exists.") - return - - module_name = app_name.replace("-", "_") - class_name = "".join(word.capitalize() for word in module_name.split("_")) +def parse_args() -> argparse.Namespace: + """Parse command-line arguments.""" + parser = argparse.ArgumentParser( + description="App creation and publishing assistant for Reachy Mini." + ) + # create/check/publish + subparsers = parser.add_subparsers( + dest="command", help="Available commands", required=True + ) - base_path.mkdir() - (base_path / module_name).mkdir() + create_parser = subparsers.add_parser("create", help="Create a new app project") + create_parser.add_argument( + "app_name", + type=str, + nargs="?", + default=None, + help="Name of the app to create.", + ) + create_parser.add_argument( + "path", + type=Path, + nargs="?", + default=None, + help="Path where the app project will be created.", + ) - # Generate files - context = { - "app_name": app_name, - "package_name": app_name, - "module_name": module_name, - "class_name": class_name, - } + check_parser = subparsers.add_parser("check", help="Check an existing app project") + check_parser.add_argument( + "app_path", + type=str, + nargs="?", + default=None, + help="Local path to the app to check.", + ) - (base_path / module_name / "__init__.py").touch() - (base_path / module_name / "main.py").write_text( - render_template("main.py.j2", context) + publish_parser = subparsers.add_parser( + "publish", help="Publish the app to the Reachy Mini app store" + ) + publish_parser.add_argument( + "app_path", + type=str, + nargs="?", + default=None, + help="Local path to the app to publish.", ) - (base_path / "pyproject.toml").write_text( - render_template("pyproject.toml.j2", context) + publish_parser.add_argument( + "commit_message", + type=str, + nargs="?", + default=None, + help="Commit message for the app publish.", + ) + publish_parser.add_argument( + "--official", + action="store_true", + required=False, + default=False, + help="Request to publish the app as an official Reachy Mini app.", ) - (base_path / "README.md").write_text(render_template("README.md.j2", context)) - print(f"✅ Created app in {base_path}/") + return parser.parse_args() -def main(): - """Run the command line interface to create a new Reachy Mini app project.""" - import argparse +def main() -> None: + """Entry point for the app assistant.""" + from rich.console import Console - parser = argparse.ArgumentParser( - description="Create a new Reachy Mini app project." - ) - parser.add_argument("app_name", type=str, help="Name of the app to create.") - parser.add_argument( - "--path", - type=Path, - default=Path.cwd(), - help="Path where the app project will be created.", - ) + from . import assistant - args = parser.parse_args() - make_app_project(args.app_name, args.path) + args = parse_args() + console = Console() + if args.command == "create": + assistant.create(console, app_name=args.app_name, app_path=args.path) + elif args.command == "check": + assistant.check(console, app_path=args.app_path) + elif args.command == "publish": + assistant.publish( + console, + app_path=args.app_path, + commit_message=args.commit_message, + official=args.official, + ) if __name__ == "__main__": diff --git a/src/reachy_mini/apps/assistant.py b/src/reachy_mini/apps/assistant.py new file mode 100644 index 00000000..fc9d215b --- /dev/null +++ b/src/reachy_mini/apps/assistant.py @@ -0,0 +1,331 @@ +"""Reachy Mini app assistant functions.""" + +import json +import os +import sys +import tempfile +from pathlib import Path +from typing import Dict + +import questionary +from huggingface_hub import CommitOperationAdd, HfApi, get_repo_discussions, whoami +from jinja2 import Environment, FileSystemLoader +from rich.console import Console + + +def create_gui( + console: Console, app_name: str | None, app_path: Path | None +) -> tuple[str, str, Path]: + """Create a new Reachy Mini app project using a GUI.""" + if app_name is None: + # 1) App name + console.print("$ What is the name of your app ?") + app_name = questionary.text( + ">", + default="", + validate=lambda text: bool(text.strip()) or "App name cannot be empty.", + ).ask() + + if app_name is None: + console.print("[red]Aborted.[/red]") + exit() + app_name = app_name.strip() + + # 2) Language + console.print("\n$ Choose the language of your app") + language = questionary.select( + ">", + choices=["python", "js"], + default="python", + ).ask() + if language is None: + console.print("[red]Aborted.[/red]") + exit() + + # js is not supported yet + if language != "python": + console.print("[red]Currently only Python apps are supported. Aborted.[/red]") + exit() + + if app_path is None: + # 3) App path + console.print("\n$ Where do you want to create your app project ?") + app_path = questionary.path( + ">", + default="", + ).ask() + if app_path is None: + console.print("[red]Aborted.[/red]") + exit() + app_path = Path(app_path).expanduser().resolve() + + name_of_repo = Path(app_path).name + if name_of_repo == "reachy_mini": + console.print( + "[red] Safeguard : You can't store your apps in the reachy_mini repo as it is already a git repo. Aborted.[/red]" + ) + exit() + + return app_name, language, app_path + + +def create(console: Console, app_name: str, app_path: Path) -> None: + """Create a new Reachy Mini app project with the given name at the specified path. + + Args: + console (Console): The console object for printing messages. + app_name (str): The name of the app to create. + app_path (Path): The directory where the app project will be created. + + """ + app_name, language, app_path = create_gui(console, app_name, app_path) + TEMPLATE_DIR = Path(__file__).parent / "templates" + env = Environment(loader=FileSystemLoader(TEMPLATE_DIR)) + + def render_template(filename: str, context: Dict[str, str]) -> str: + template = env.get_template(filename) + return template.render(context) + + base_path = Path(app_path).resolve() / app_name + if base_path.exists(): + console.print(f"❌ Folder {base_path} already exists.", style="bold red") + exit() + + module_name = app_name.replace("-", "_") + class_name = "".join(word.capitalize() for word in module_name.split("_")) + class_name_display = " ".join(word.capitalize() for word in module_name.split("_")) + + base_path.mkdir() + (base_path / module_name).mkdir() + (base_path / module_name / "static").mkdir() + + # Generate files + context = { + "app_name": app_name, + "package_name": app_name, + "module_name": module_name, + "class_name": class_name, + "class_name_display": class_name_display, + } + + (base_path / module_name / "__init__.py").touch() + (base_path / module_name / "main.py").write_text( + render_template("main.py.j2", context) + ) + (base_path / module_name / "static" / "index.html").write_text( + render_template("static/index.html.j2", context) + ) + (base_path / module_name / "static" / "style.css").write_text( + render_template("static/style.css.j2", context) + ) + (base_path / module_name / "static" / "main.js").write_text( + render_template("static/main.js.j2", context) + ) + + (base_path / "pyproject.toml").write_text( + render_template("pyproject.toml.j2", context) + ) + (base_path / "README.md").write_text(render_template("README.md.j2", context)) + + (base_path / "index.html").write_text(render_template("index.html.j2", context)) + (base_path / "style.css").write_text(render_template("style.css.j2", context)) + (base_path / ".gitignore").write_text(render_template("gitignore.j2", context)) + + # TODO assets dir with a .gif ? + + console.print(f"✅ Created app '{app_name}' in {base_path}/", style="bold green") + + +def check(console: Console, app_path: str) -> None: + """Check an existing Reachy Mini app project. + + Args: + console (Console): The console object for printing messages. + app_path (str): Local path to the app to check. + + """ + if not os.path.exists(app_path): + console.print(f"[red]App path {app_path} does not exist.[/red]") + exit() + # Placeholder for checking logic + print(f"Checking app at path '{app_path}'") + pass + + +def request_app_addition(new_app_repo_id: str) -> bool: + """Request to add the new app to the official Reachy Mini app store.""" + api = HfApi() + + repo_id = "pollen-robotics/reachy-mini-official-app-store" + file_path = "app-list.json" + + # 0. Detect current HF user + user = whoami()["name"] + + # 1. Check if there is already an open PR by this user for this app + # (we used commit_message=f"Add {new_app_repo_id} to app-list.json", + # which becomes the PR title) + existing_prs = get_repo_discussions( + repo_id=repo_id, + repo_type="dataset", + author=user, + discussion_type="pull_request", + discussion_status="open", + ) + + for pr in existing_prs: + if new_app_repo_id in pr.title: + print( + f"An open PR already exists for {new_app_repo_id} by {user}: " + f"https://huggingface.co/{repo_id}/discussions/{pr.num}" + ) + return False + + # 2. Download current file from the dataset repo + local_downloaded = api.hf_hub_download( + repo_id=repo_id, + filename=file_path, + repo_type="dataset", + ) + + with open(local_downloaded, "r") as f: + app_list = json.load(f) + + # 3. Modify JSON (append if not already present) + if new_app_repo_id not in app_list: + app_list.append(new_app_repo_id) + else: + print(f"{new_app_repo_id} is already in the app list.") + # You might still want to continue and create the PR, or early-return here. + return False + + # 4. Save updated JSON to a temporary path + with tempfile.TemporaryDirectory() as tmpdir: + updated_path = os.path.join(tmpdir, file_path) + os.makedirs(os.path.dirname(updated_path), exist_ok=True) + with open(updated_path, "w") as f: + json.dump(app_list, f, indent=4) + f.write("\n") + + # 5. Commit with create_pr=True + commit_info = api.create_commit( + repo_id=repo_id, + repo_type="dataset", + commit_message=f"Add {new_app_repo_id} to app-list.json", + commit_description=( + f"Append `{new_app_repo_id}` to the list of Reachy Mini apps." + ), + operations=[ + CommitOperationAdd( + path_in_repo=file_path, + path_or_fileobj=updated_path, + ) + ], + create_pr=True, + ) + + print("Commit URL:", commit_info.commit_url) + print("PR URL:", commit_info.pr_url) # None if no PR was opened + return True + + +def publish( + console: Console, app_path: str, commit_message: str, official: bool = False +) -> None: + """Publish the app to the Reachy Mini app store. + + Args: + console (Console): The console object for printing messages. + app_path (str): Local path to the app to publish. + commit_message (str): Commit message for the app publish. + official (bool): Request to publish the app as an official Reachy Mini app. + + """ + import huggingface_hub as hf + + if app_path is None: + console.print("\n$ What is the local path to the app you want to publish?") + app_path = questionary.path( + ">", + default="", + ).ask() + if app_path is None: + console.print("[red]Aborted.[/red]") + exit() + name_of_repo = Path(app_path).name + if name_of_repo == "reachy_mini": + console.print( + "[red] Safeguard : You may have selected reachy_mini repo as your app. Aborted.[/red]" + ) + exit() + app_path = Path(app_path).expanduser().resolve() + if not os.path.exists(app_path): + console.print(f"[red]App path {app_path} does not exist.[/red]") + sys.exit() + if not hf.get_token(): + console.print( + "[red]You need to be logged in to Hugging Face to publish an app.[/red]" + ) + # Do you want to login now (will run hf auth login) + if questionary.confirm("Do you want to login now?").ask(): + console.print("Generate a token at https://huggingface.co/settings/tokens") + hf.login() + else: + console.print("[red]Aborted.[/red]") + exit() + + username = hf.whoami()["name"] + repo_path = f"{username}/{Path(app_path).name}" + repo_url = f"https://huggingface.co/spaces/{repo_path}" + + if hf.repo_exists(repo_path, repo_type="space"): + os.system(f"cd {app_path} && git pull {repo_url} main") + console.print("App already exists on Hugging Face Spaces. Updating...") + commit_message = questionary.text( + "\n$ Enter a commit message for the update:", + default="Update app", + ).ask() + if commit_message is None: + console.print("[red]Aborted.[/red]") + exit() + os.system( + f"cd {app_path} && git add . && git commit -m '{commit_message}' && git push HEAD:main" + ) + console.print("✅ App updated successfully.") + else: + console.print("Do you want your space to be created private or public?") + privacy = questionary.select( + ">", + choices=["private", "public"], + default="public", + ).ask() + + hf.create_repo( + repo_path, + repo_type="space", + private=(privacy == "private"), + exist_ok=False, + space_sdk="static", + ) + os.system( + f"cd {app_path} && git init && git remote add space {repo_url} && git add . && git commit -m 'Initial commit' && git push --set-upstream -f space HEAD:main" + ) + + console.print("✅ App published successfully.", style="bold green") + + if official: + # ask for confirmation + if not questionary.confirm( + "Are you sure you want to ask to publish this app as an official Reachy Mini app?" + ).ask(): + console.print("[red]Aborted.[/red]") + exit() + + worked = request_app_addition(repo_path) + if worked: + console.print( + "\nYou have requested to publish your app as an official Reachy Mini app." + ) + console.print( + "The Pollen and Hugging Face teams will review your app. Thank you for your contribution!" + ) diff --git a/src/reachy_mini/apps/manager.py b/src/reachy_mini/apps/manager.py index 404a1cfa..070c857d 100644 --- a/src/reachy_mini/apps/manager.py +++ b/src/reachy_mini/apps/manager.py @@ -6,7 +6,7 @@ from enum import Enum from importlib.metadata import entry_points from threading import Thread -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any, Optional from pydantic import BaseModel @@ -52,7 +52,7 @@ def __init__(self) -> None: self.current_app = None # type: RunningApp | None self.logger = logging.getLogger("reachy_mini.apps.manager") - async def close(self): + async def close(self) -> None: """Clean up the AppManager, stopping any running app.""" if self.is_app_running(): await self.stop_current_app() @@ -67,7 +67,7 @@ def is_app_running(self) -> bool: AppState.ERROR, ) - async def start_app(self, app_name: str) -> AppStatus: + async def start_app(self, app_name: str, *args: Any, **kwargs: Any) -> AppStatus: """Start the app, raises RuntimeError if an app is already running.""" if self.is_app_running(): raise RuntimeError("An app is already running") @@ -75,21 +75,24 @@ async def start_app(self, app_name: str) -> AppStatus: (ep,) = entry_points(group="reachy_mini_apps", name=app_name) app = ep.load()() - def wrapped_run(): + def wrapped_run() -> None: assert self.current_app is not None try: self.current_app.status.state = AppState.RUNNING self.logger.getChild("runner").info(f"App {app_name} is running") - app.wrapped_run() + app.wrapped_run(*args, **kwargs) self.current_app.status.state = AppState.DONE self.logger.getChild("runner").info(f"App {app_name} finished") except Exception as e: self.logger.getChild("runner").error( f"An error occurred in the app {app_name}: {e}" ) + self.logger.getChild("runner").error( + f"Exception details: '{app.error}'", + ) self.current_app.status.state = AppState.ERROR - self.current_app.status.error = str(e) + self.current_app.status.error = str(app.error) self.current_app = RunningApp( status=AppStatus( @@ -105,7 +108,7 @@ def wrapped_run(): return self.current_app.status - async def stop_current_app(self, timeout: float | None = 5.0): + async def stop_current_app(self, timeout: float | None = 5.0) -> None: """Stop the current app.""" if not self.is_app_running(): raise RuntimeError("No app is currently running") @@ -142,10 +145,11 @@ async def restart_current_app(self) -> AppStatus: return self.current_app.status - async def current_app_status(self) -> AppStatus | None: + async def current_app_status(self) -> Optional[AppStatus]: """Get the current status of the app.""" if self.current_app is not None: return self.current_app.status + return None # Apps management interface async def list_all_available_apps(self) -> list[AppInfo]: @@ -158,9 +162,13 @@ async def list_all_available_apps(self) -> list[AppInfo]: async def list_available_apps(self, source: SourceKind) -> list[AppInfo]: """List available apps for given source kind.""" if source == SourceKind.HF_SPACE: + return await hf_space.list_all_apps() + elif source == SourceKind.DASHBOARD_SELECTION: return await hf_space.list_available_apps() elif source == SourceKind.INSTALLED: return await local_common_venv.list_available_apps() + elif source == SourceKind.LOCAL: + return [] else: raise NotImplementedError(f"Unknown source kind: {source}") diff --git a/src/reachy_mini/apps/sources/hf_space.py b/src/reachy_mini/apps/sources/hf_space.py index 7a6df853..23dc5d27 100644 --- a/src/reachy_mini/apps/sources/hf_space.py +++ b/src/reachy_mini/apps/sources/hf_space.py @@ -1,25 +1,105 @@ """Hugging Face Spaces app source.""" +import asyncio +import json +from typing import Any, Dict + import aiohttp from .. import AppInfo, SourceKind +# Constants +AUTHORIZED_APP_LIST_URL = "https://huggingface.co/datasets/pollen-robotics/reachy-mini-official-app-store/raw/main/app-list.json" +HF_SPACES_API_URL = "https://huggingface.co/api/spaces" +HF_SPACES_FILTER_URL = "https://huggingface.co/api/spaces?filter=reachy_mini&sort=likes&direction=-1&limit=50&full=true" +REQUEST_TIMEOUT = aiohttp.ClientTimeout(total=30) + + +async def _fetch_space_data( + session: aiohttp.ClientSession, space_id: str +) -> Dict[str, Any] | None: + """Fetch data for a single space from Hugging Face API.""" + url = f"{HF_SPACES_API_URL}/{space_id}" + try: + async with session.get(url, timeout=REQUEST_TIMEOUT) as response: + if response.status == 200: + data: Dict[str, Any] = await response.json() + return data + else: + return None + except (aiohttp.ClientError, asyncio.TimeoutError): + return None + async def list_available_apps() -> list[AppInfo]: """List apps available on Hugging Face Spaces.""" - url = "https://huggingface.co/api/spaces?filter=reachy_mini&sort=likes&direction=-1&limit=50&full=true" - async with aiohttp.ClientSession() as session: - async with session.get(url) as response: - data = await response.json() - apps = [] - for item in data: - apps.append( - AppInfo( - name=item["id"].split("/")[-1], - description=item["cardData"].get("short_description", ""), - url=f"https://huggingface.co/spaces/{item['id']}", - source_kind=SourceKind.HF_SPACE, - extra=item, + async with aiohttp.ClientSession(timeout=REQUEST_TIMEOUT) as session: + # Fetch the list of authorized app IDs + try: + async with session.get(AUTHORIZED_APP_LIST_URL) as response: + response.raise_for_status() + text = await response.text() + authorized_ids = json.loads(text) + except (aiohttp.ClientError, json.JSONDecodeError): + return [] + + if not isinstance(authorized_ids, list): + return [] + + # Filter to only string elements + authorized_ids = [ + space_id for space_id in authorized_ids if isinstance(space_id, str) + ] + + # Fetch data for each space in parallel + tasks = [_fetch_space_data(session, space_id) for space_id in authorized_ids] + spaces_data = await asyncio.gather(*tasks) + + # Build AppInfo list from fetched data + apps = [] + for item in spaces_data: + if item is None or "id" not in item: + continue + + apps.append( + AppInfo( + name=item["id"].split("/")[-1], + description=item.get("cardData", {}).get("short_description", ""), + url=f"https://huggingface.co/spaces/{item['id']}", + source_kind=SourceKind.HF_SPACE, + extra=item, + ) ) - ) - return apps + + return apps + + +async def list_all_apps() -> list[AppInfo]: + """List all apps available on Hugging Face Spaces (including unofficial ones).""" + async with aiohttp.ClientSession(timeout=REQUEST_TIMEOUT) as session: + try: + async with session.get(HF_SPACES_FILTER_URL) as response: + response.raise_for_status() + data: list[Dict[str, Any]] = await response.json() + except (aiohttp.ClientError, json.JSONDecodeError, asyncio.TimeoutError): + return [] + + if not isinstance(data, list): + return [] + + apps = [] + for item in data: + if item is None or "id" not in item: + continue + + apps.append( + AppInfo( + name=item["id"].split("/")[-1], + description=item.get("cardData", {}).get("short_description", ""), + url=f"https://huggingface.co/spaces/{item['id']}", + source_kind=SourceKind.HF_SPACE, + extra=item, + ) + ) + + return apps diff --git a/src/reachy_mini/apps/sources/local_common_venv.py b/src/reachy_mini/apps/sources/local_common_venv.py index 7f0f3ebb..9100adb1 100644 --- a/src/reachy_mini/apps/sources/local_common_venv.py +++ b/src/reachy_mini/apps/sources/local_common_venv.py @@ -11,15 +11,38 @@ async def list_available_apps() -> list[AppInfo]: """List apps available from entry points.""" entry_point_apps = list(entry_points(group="reachy_mini_apps")) - return [ - AppInfo(name=ep.name, source_kind=SourceKind.INSTALLED) - for ep in entry_point_apps - ] + + apps = [] + + for ep in entry_point_apps: + custom_app_url = None + try: + app = ep.load() + custom_app_url = app.custom_app_url + except Exception as e: + logging.getLogger("reachy_mini.apps").warning( + f"Could not load app '{ep.name}' from entry point: {e}" + ) + apps.append( + AppInfo( + name=ep.name, + source_kind=SourceKind.INSTALLED, + extra={"custom_app_url": custom_app_url}, + ) + ) + + return apps async def install_package(app: AppInfo, logger: logging.Logger) -> int: """Install a package given an AppInfo object, streaming logs.""" - target = f"git+{app.url}" if app.url is not None else app.name + if app.source_kind == SourceKind.HF_SPACE: + target = f"git+{app.url}" if app.url is not None else app.name + elif app.source_kind == SourceKind.LOCAL: + target = app.extra.get("path", app.name) + else: + raise ValueError(f"Cannot install app from source kind '{app.source_kind}'") + return await running_command( [sys.executable, "-m", "pip", "install", target], logger=logger, diff --git a/src/reachy_mini/apps/templates/README.md.j2 b/src/reachy_mini/apps/templates/README.md.j2 index ce43eebc..d7380693 100644 --- a/src/reachy_mini/apps/templates/README.md.j2 +++ b/src/reachy_mini/apps/templates/README.md.j2 @@ -1 +1,12 @@ -# {{ app_name }} - A ReachyMini application \ No newline at end of file +--- +title: {{ class_name_display }} +emoji: 👋 +colorFrom: red +colorTo: blue +sdk: static +pinned: false +short_description: Write your description here +tags: + - reachy_mini + - reachy_mini_python_app +--- \ No newline at end of file diff --git a/src/reachy_mini/apps/templates/gitignore.j2 b/src/reachy_mini/apps/templates/gitignore.j2 new file mode 100644 index 00000000..e482470a --- /dev/null +++ b/src/reachy_mini/apps/templates/gitignore.j2 @@ -0,0 +1,2 @@ +__pycache__/ +*.egg-info/ \ No newline at end of file diff --git a/src/reachy_mini/apps/templates/index.html.j2 b/src/reachy_mini/apps/templates/index.html.j2 new file mode 100644 index 00000000..2f0ce85c --- /dev/null +++ b/src/reachy_mini/apps/templates/index.html.j2 @@ -0,0 +1,235 @@ + + + + + + + {{ class_name_display }} + + + + +
+
+
🤖⚡
+

{{ class_name_display }}

+

Enter your tagline here

+
+
+ +
+
+
+
+
🛠️
+
+
+
+
+ +
+
+

Install This App

+ +
+ + +
+ + + +
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/src/reachy_mini/apps/templates/main.py.j2 b/src/reachy_mini/apps/templates/main.py.j2 index 2272f6e8..ffaef6d1 100644 --- a/src/reachy_mini/apps/templates/main.py.j2 +++ b/src/reachy_mini/apps/templates/main.py.j2 @@ -1,46 +1,69 @@ import threading - from reachy_mini import ReachyMini, ReachyMiniApp from reachy_mini.utils import create_head_pose +import numpy as np +import time +from pydantic import BaseModel class {{ class_name }}(ReachyMiniApp): + # Optional: URL to a custom configuration page for the app + # eg. "http://localhost:8042" + custom_app_url: str | None = "http://0.0.0.0:8042" + def run(self, reachy_mini: ReachyMini, stop_event: threading.Event): - # Write your code here - # ReachyMini is already initialized and connected - # Check the stop_event to gracefully exit the loop - # Example: - # import time - # import numpy as np - # - # t0 = time.time() - # - # while not stop_event.is_set(): - # t = time.time() - t0 - # - # yaw = 30 * np.sin(2 * np.pi * 0.5 * t) - # head_pose = create_head_pose(yaw=yaw, degrees=True) - # - # reachy_mini.set_target(head=head_pose) - # - # time.sleep(0.01) - - print("This is a placeholder for your app logic.") + t0 = time.time() + antennas_enabled = True + sound_play_requested = False -if __name__ == "__main__": - # You can run the app directly from this script - with ReachyMini() as mini: - app = {{ class_name }}() + # You can ignore this part if you don't want to add settings to your app. If you set custom_app_url to None, you have to remove this part as well. + # === vvv === + class AntennaState(BaseModel): + enabled: bool + + @self.settings_app.post("/antennas") + def update_antennas_state(state: AntennaState): + nonlocal antennas_enabled + antennas_enabled = state.enabled + return {"antennas_enabled": antennas_enabled} + + @self.settings_app.post("/play_sound") + def request_sound_play(): + nonlocal sound_play_requested + sound_play_requested = True + + # === ^^^ === + + # Main control loop + while not stop_event.is_set(): + t = time.time() - t0 - stop = threading.Event() + yaw_deg = 30.0 * np.sin(2.0 * np.pi * 0.2 * t) + head_pose = create_head_pose(yaw=yaw_deg, degrees=True) - try: - print("Running '{{ app_name }}' a ReachyMiniApp...") - print("Press Ctrl+C to stop the app.") - app.run(mini, stop) - print("App has stopped.") + if antennas_enabled: + amp_deg = 25.0 + a = amp_deg * np.sin(2.0 * np.pi * 0.5 * t) + antennas_deg = np.array([a, -a]) + else: + antennas_deg = np.array([0.0, 0.0]) - except KeyboardInterrupt: - print("Stopping the app...") - stop.set() + if sound_play_requested: + print("Playing sound...") + reachy_mini.media.play_sound("wake_up.wav") + sound_play_requested = False + + antennas_rad = np.deg2rad(antennas_deg) + + reachy_mini.set_target( + head=head_pose, + antennas=antennas_rad, + ) + + time.sleep(0.02) + + +if __name__ == "__main__": + app = {{ class_name }}() + app.wrapped_run() \ No newline at end of file diff --git a/src/reachy_mini/apps/templates/pyproject.toml.j2 b/src/reachy_mini/apps/templates/pyproject.toml.j2 index 8fcd12ea..1dab526c 100644 --- a/src/reachy_mini/apps/templates/pyproject.toml.j2 +++ b/src/reachy_mini/apps/templates/pyproject.toml.j2 @@ -8,11 +8,21 @@ name = "{{ app_name }}" version = "0.1.0" description = "Add your description here" readme = "README.md" -requires-python = ">=3.8" +requires-python = ">=3.10" dependencies = [ "reachy-mini" ] keywords = ["reachy-mini-app"] [project.entry-points."reachy_mini_apps"] -{{ app_name }} = "{{ module_name }}.main:{{ class_name }}" \ No newline at end of file +{{ app_name }} = "{{ module_name }}.main:{{ class_name }}" + +[tool.setuptools] +package-dir = { "" = "." } +include-package-data = true + +[tool.setuptools.packages.find] +where = ["."] + +[tool.setuptools.package-data] +{{ app_name }} = ["**/*"] # Also include all non-.py files \ No newline at end of file diff --git a/src/reachy_mini/apps/templates/static/index.html.j2 b/src/reachy_mini/apps/templates/static/index.html.j2 new file mode 100644 index 00000000..71fc5395 --- /dev/null +++ b/src/reachy_mini/apps/templates/static/index.html.j2 @@ -0,0 +1,27 @@ + + + + + + Reachy Mini example app template + + + + + +

Reachy Mini – Control Panel

+ +
+ + + +
+ +
Antennas status: running
+ + + + \ No newline at end of file diff --git a/src/reachy_mini/apps/templates/static/main.js.j2 b/src/reachy_mini/apps/templates/static/main.js.j2 new file mode 100644 index 00000000..8781327f --- /dev/null +++ b/src/reachy_mini/apps/templates/static/main.js.j2 @@ -0,0 +1,47 @@ +let antennasEnabled = true; + +async function updateAntennasState(enabled) { + try { + const resp = await fetch("/antennas", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ enabled }), + }); + const data = await resp.json(); + antennasEnabled = data.antennas_enabled; + updateUI(); + } catch (e) { + document.getElementById("status").textContent = "Backend error"; + } +} + +async function playSound() { + try { + await fetch("/play_sound", { method: "POST" }); + } catch (e) { + console.error("Error triggering sound:", e); + } +} + +function updateUI() { + const checkbox = document.getElementById("antenna-checkbox"); + const status = document.getElementById("status"); + + checkbox.checked = antennasEnabled; + + if (antennasEnabled) { + status.textContent = "Antennas status: running"; + } else { + status.textContent = "Antennas status: stopped"; + } +} + +document.getElementById("antenna-checkbox").addEventListener("change", (e) => { + updateAntennasState(e.target.checked); +}); + +document.getElementById("sound-btn").addEventListener("click", () => { + playSound(); +}); + +updateUI(); diff --git a/src/reachy_mini/apps/templates/static/style.css.j2 b/src/reachy_mini/apps/templates/static/style.css.j2 new file mode 100644 index 00000000..ff47f9b2 --- /dev/null +++ b/src/reachy_mini/apps/templates/static/style.css.j2 @@ -0,0 +1,25 @@ +body { + font-family: sans-serif; + margin: 24px; +} + +#sound-btn { + padding: 10px 20px; + border: none; + color: white; + cursor: pointer; + font-size: 16px; + border-radius: 6px; + background-color: #3498db; +} + +#status { + margin-top: 16px; + font-weight: bold; +} + +#controls { + display: flex; + align-items: center; + gap: 20px; +} \ No newline at end of file diff --git a/src/reachy_mini/apps/templates/style.css.j2 b/src/reachy_mini/apps/templates/style.css.j2 new file mode 100644 index 00000000..64fab914 --- /dev/null +++ b/src/reachy_mini/apps/templates/style.css.j2 @@ -0,0 +1,411 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + line-height: 1.6; + color: #333; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + min-height: 100vh; +} + +.hero { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + padding: 4rem 2rem; + text-align: center; +} + +.hero-content { + max-width: 800px; + margin: 0 auto; +} + +.app-icon { + font-size: 4rem; + margin-bottom: 1rem; + display: inline-block; +} + +.hero h1 { + font-size: 3rem; + font-weight: 700; + margin-bottom: 1rem; + background: linear-gradient(45deg, #fff, #f0f9ff); + background-clip: text; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; +} + +.tagline { + font-size: 1.25rem; + opacity: 0.9; + max-width: 600px; + margin: 0 auto; +} + +.container { + max-width: 1200px; + margin: 0 auto; + padding: 0 2rem; + position: relative; + z-index: 2; +} + +.main-card { + background: white; + border-radius: 20px; + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1); + margin-top: -2rem; + overflow: hidden; + margin-bottom: 3rem; +} + +.app-preview { + background: linear-gradient(135deg, #1e3a8a, #3b82f6); + padding: 3rem; + color: white; + text-align: center; + position: relative; +} + +.preview-image { + background: #000; + border-radius: 15px; + padding: 2rem; + max-width: 500px; + margin: 0 auto; + position: relative; + overflow: hidden; +} + +.camera-feed { + font-size: 4rem; + margin-bottom: 1rem; + opacity: 0.7; +} + +.detection-overlay { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 100%; +} + +.bbox { + background: rgba(34, 197, 94, 0.9); + color: white; + padding: 0.5rem 1rem; + border-radius: 8px; + font-size: 0.9rem; + font-weight: 600; + margin: 0.5rem; + display: inline-block; + border: 2px solid #22c55e; +} + +.app-details { + padding: 3rem; +} + +.app-details h2 { + font-size: 2rem; + color: #1e293b; + margin-bottom: 2rem; + text-align: center; +} + +.template-info { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 2rem; + margin-bottom: 3rem; +} + +.info-box { + background: #f0f9ff; + border: 2px solid #e0f2fe; + border-radius: 12px; + padding: 2rem; +} + +.info-box h3 { + color: #0c4a6e; + margin-bottom: 1rem; + font-size: 1.2rem; +} + +.info-box p { + color: #0369a1; + line-height: 1.6; +} + +.how-to-use { + background: #fefce8; + border: 2px solid #fde047; + border-radius: 12px; + padding: 2rem; + margin-top: 3rem; +} + +.how-to-use h3 { + color: #a16207; + margin-bottom: 1.5rem; + font-size: 1.3rem; + text-align: center; +} + +.steps { + display: flex; + flex-direction: column; + gap: 1.5rem; +} + +.step { + display: flex; + align-items: flex-start; + gap: 1rem; +} + +.step-number { + background: #eab308; + color: white; + width: 2rem; + height: 2rem; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-weight: bold; + flex-shrink: 0; +} + +.step h4 { + color: #a16207; + margin-bottom: 0.5rem; + font-size: 1.1rem; +} + +.step p { + color: #ca8a04; +} + +.download-card { + background: white; + border-radius: 20px; + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1); + padding: 3rem; + text-align: center; +} + +.download-card h2 { + font-size: 2rem; + color: #1e293b; + margin-bottom: 1rem; +} + +.download-card>p { + color: #64748b; + font-size: 1.1rem; + margin-bottom: 2rem; +} + +.dashboard-config { + margin-bottom: 2rem; + text-align: left; + max-width: 400px; + margin-left: auto; + margin-right: auto; +} + +.dashboard-config label { + display: block; + color: #374151; + font-weight: 600; + margin-bottom: 0.5rem; +} + +.dashboard-config input { + width: 100%; + padding: 0.75rem 1rem; + border: 2px solid #e5e7eb; + border-radius: 8px; + font-size: 0.95rem; + transition: border-color 0.2s; +} + +.dashboard-config input:focus { + outline: none; + border-color: #667eea; +} + +.install-btn { + background: linear-gradient(135deg, #667eea, #764ba2); + color: white; + border: none; + padding: 1.25rem 3rem; + border-radius: 16px; + font-size: 1.2rem; + font-weight: 700; + cursor: pointer; + transition: all 0.3s ease; + display: inline-flex; + align-items: center; + gap: 0.75rem; + margin-bottom: 2rem; + box-shadow: 0 8px 25px rgba(102, 126, 234, 0.3); +} + +.install-btn:hover:not(:disabled) { + transform: translateY(-3px); + box-shadow: 0 15px 35px rgba(102, 126, 234, 0.4); +} + +.install-btn:disabled { + opacity: 0.7; + cursor: not-allowed; + transform: none; +} + +.manual-option { + background: #f8fafc; + border-radius: 12px; + padding: 2rem; + margin-top: 2rem; +} + +.manual-option h3 { + color: #1e293b; + margin-bottom: 1rem; + font-size: 1.2rem; +} + +.manual-option>p { + color: #64748b; + margin-bottom: 1rem; +} + +.btn-icon { + font-size: 1.1rem; +} + +.install-status { + padding: 1rem; + border-radius: 8px; + font-size: 0.9rem; + text-align: center; + display: none; + margin-top: 1rem; +} + +.install-status.success { + background: #dcfce7; + color: #166534; + border: 1px solid #bbf7d0; +} + +.install-status.error { + background: #fef2f2; + color: #dc2626; + border: 1px solid #fecaca; +} + +.install-status.loading { + background: #dbeafe; + color: #1d4ed8; + border: 1px solid #bfdbfe; +} + +.install-status.info { + background: #e0f2fe; + color: #0369a1; + border: 1px solid #7dd3fc; +} + +.manual-install { + background: #1f2937; + border-radius: 8px; + padding: 1rem; + margin-bottom: 1rem; + display: flex; + align-items: center; + gap: 1rem; +} + +.manual-install code { + color: #10b981; + font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', monospace; + font-size: 0.85rem; + flex: 1; + overflow-x: auto; +} + +.copy-btn { + background: #374151; + color: white; + border: none; + padding: 0.5rem 1rem; + border-radius: 6px; + font-size: 0.8rem; + cursor: pointer; + transition: background-color 0.2s; +} + +.copy-btn:hover { + background: #4b5563; +} + +.manual-steps { + color: #6b7280; + font-size: 0.9rem; + line-height: 1.8; +} + +.footer { + text-align: center; + padding: 2rem; + color: white; + opacity: 0.8; +} + +.footer a { + color: white; + text-decoration: none; + font-weight: 600; +} + +.footer a:hover { + text-decoration: underline; +} + +/* Responsive Design */ +@media (max-width: 768px) { + .hero { + padding: 2rem 1rem; + } + + .hero h1 { + font-size: 2rem; + } + + .container { + padding: 0 1rem; + } + + .app-details, + .download-card { + padding: 2rem; + } + + .features-grid { + grid-template-columns: 1fr; + } + + .download-options { + grid-template-columns: 1fr; + } +} \ No newline at end of file diff --git a/src/reachy_mini/assets/config/hardware_config.yaml b/src/reachy_mini/assets/config/hardware_config.yaml new file mode 100644 index 00000000..18feed39 --- /dev/null +++ b/src/reachy_mini/assets/config/hardware_config.yaml @@ -0,0 +1,155 @@ +version: beta + +serial: + baudrate: 1000000 + +# Limits measured on the robot (in degrees) +# s1 +# lower : -48° : -546 +# upper : +80° : 910 + +# s2 +# lower: +70° : 796 +# upper: -80° : -910 + +# s3 +# lower: -48° : -546 +# upper: +80° : 910 + +# s4 +# lower: +48° : 546 +# upper: -80° : -910 + +# s5 +# lower: -70° : -796 +# upper: +80° : 910 + +# s6 +# lower : +48° : 546 +# upper : -80° : -910 + +# s1: +# lower: 2048 - 546 : 1502 +# upper: 2048 + 910 : 2958 + +# s2: +# lower: 2048 - 910 : 1138 +# upper: 2048 + 796 : 2844 + +# s3: +# lower: 2048 - 546 : 1502 +# upper: 2048 + 910 : 2958 + +# s4: +# lower: 2048 - 910 : 1138 +# upper: 2048 + 546 : 2594 + +# s5: +# lower: 2048 - 796 : 1252 +# upper: 2048 + 910 : 2958 + +# s6: +# lower: 2048 - 910 : 1138 +# upper: 2048 + 546 : 2594 + + +motors: + - body_rotation: + id: 10 + offset: 0 + lower_limit: 0 + upper_limit: 4095 + return_delay_time: 0 + shutdown_error: 52 + pid: + - 400 + - 0 + - 0 + - stewart_1: + id: 11 + offset: 1024 + lower_limit: 1502 + upper_limit: 2958 + return_delay_time: 0 + shutdown_error: 52 + pid: + - 400 + - 0 + - 0 + - stewart_2: + id: 12 + offset: -1024 + lower_limit: 1138 + upper_limit: 2844 + return_delay_time: 0 + shutdown_error: 52 + pid: + - 400 + - 0 + - 0 + - stewart_3: + id: 13 + offset: 1024 + lower_limit: 1502 + upper_limit: 2958 + return_delay_time: 0 + shutdown_error: 52 + pid: + - 400 + - 0 + - 0 + - stewart_4: + id: 14 + offset: -1024 + lower_limit: 1138 + upper_limit: 2594 + return_delay_time: 0 + shutdown_error: 52 + pid: + - 400 + - 0 + - 0 + - stewart_5: + id: 15 + offset: 1024 + lower_limit: 1252 + upper_limit: 2958 + return_delay_time: 0 + shutdown_error: 52 + pid: + - 400 + - 0 + - 0 + - stewart_6: + id: 16 + offset: -1024 + lower_limit: 1138 + upper_limit: 2594 + return_delay_time: 0 + shutdown_error: 52 + pid: + - 400 + - 0 + - 0 + - right_antenna: + id: 17 + offset: 0 + lower_limit: 0 + upper_limit: 4095 + return_delay_time: 0 + shutdown_error: 52 + pid: + - 400 + - 0 + - 400 + - left_antenna: + id: 18 + offset: 0 + lower_limit: 0 + upper_limit: 4095 + return_delay_time: 0 + shutdown_error: 52 + pid: + - 400 + - 0 + - 400 diff --git a/src/reachy_mini/assets/firmware/CHANGELOG.md b/src/reachy_mini/assets/firmware/CHANGELOG.md new file mode 100644 index 00000000..db473c46 --- /dev/null +++ b/src/reachy_mini/assets/firmware/CHANGELOG.md @@ -0,0 +1,15 @@ +# Changelog of Reachy Mini Audio Firmware + + +## 2.1.3 + +*For Beta units only* + +Fixes the initialization issue on Reachy Mini beta hardware. An additional 2-second delay is added during initialization to prevent the XMOS chip from starting before the other components. + +There is no need to apply this firmware to the Lite and Wireless versions, as the issue is fixed at the hardware level. + +## 2.1.2 + +Improved parameters for acoustic echo cancellation (AEC). +PP_DTSENSITIVE is set to 1 by default. \ No newline at end of file diff --git a/src/reachy_mini/assets/firmware/reachymini_ua_io16_lin_v2.1.2.bin b/src/reachy_mini/assets/firmware/reachymini_ua_io16_lin_v2.1.2.bin new file mode 100644 index 00000000..6314af04 --- /dev/null +++ b/src/reachy_mini/assets/firmware/reachymini_ua_io16_lin_v2.1.2.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:661429a93b155cdbfed1e6c214820fd42e3372a08c3930f14fcd4e6db5be6796 +size 933888 diff --git a/src/reachy_mini/assets/firmware/reachymini_ua_io16_lin_v2.1.3.bin b/src/reachy_mini/assets/firmware/reachymini_ua_io16_lin_v2.1.3.bin new file mode 100644 index 00000000..680a9970 --- /dev/null +++ b/src/reachy_mini/assets/firmware/reachymini_ua_io16_lin_v2.1.3.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0e35fa9a43a4e75890fd6299e62e85310d8081087e6a9702aaef0c81bd3af283 +size 933888 diff --git a/src/reachy_mini/assets/firmware/update.sh b/src/reachy_mini/assets/firmware/update.sh new file mode 100755 index 00000000..e6f0f324 --- /dev/null +++ b/src/reachy_mini/assets/firmware/update.sh @@ -0,0 +1,9 @@ +#!/bin/bash +# Firmware update script for Reachy Mini +# Usage: ./update.sh +firmware="$1" +if [ -z "$firmware" ]; then + echo "Usage: $0 " + exit 1 +fi +dfu-util -R -e -a 1 -D "$firmware" \ No newline at end of file diff --git a/src/reachy_mini/assets/kinematics_data.json b/src/reachy_mini/assets/kinematics_data.json index 2e2b2971..c20b364f 100644 --- a/src/reachy_mini/assets/kinematics_data.json +++ b/src/reachy_mini/assets/kinematics_data.json @@ -1,31 +1,31 @@ { - "motor_arm_length": 0.03799999999999999, - "rod_length": 0.09000029999999995, + "motor_arm_length": 0.04000000000000001, + "rod_length": 0.08499999999999995, "head_z_offset": 0.177, "motors": [ { - "name": "1", + "name": "stewart_1", "branch_frame": "closing_1_2", "offset": 0, "solution": 0, "T_motor_world": [ [ - 0.8660247915798892, - -0.5000010603626024, - 2.2980696749787093e-06, - -0.010249870803819337 + 0.8660247915798898, + -0.5000010603626028, + -2.298079077119539e-06, + -0.009999848080267933 ], [ - -8.169833472480465e-07, - 3.181076937306543e-06, - 0.999999999994606, - -0.07095015644698092 + 4.490195936008854e-06, + 3.1810770986818273e-06, + 0.999999999984859, + -0.07663346037245178 ], [ - -0.5000010603672165, + -0.500001060347722, -0.8660247915770963, - 2.3463989512861495e-06, - 0.03491131119632033 + 4.999994360718464e-06, + 0.03666015757925319 ], [ 0.0, @@ -35,38 +35,38 @@ ] ], "branch_position": [ - 0.029923302914194896, - 0.0249723116230494, - -0.0012000917284306634 + 0.020648178337122566, + 0.021763723638894568, + 1.0345743467476964e-07 ], "limits": [ - -1.39626, - 1.39626 + -3.141592653589793, + 3.141592653589793 ] }, { - "name": "2", + "name": "stewart_2", "branch_frame": "closing_2_2", "offset": 0, "solution": 1, "T_motor_world": [ [ - -0.8660284647694133, - 0.4999946981757419, - -2.298079422430893e-06, - -0.01024979830729949 + -0.8660211183436269, + 0.5000074225224785, + 2.298069723064582e-06, + -0.01000055227585102 ], [ - 8.170067173757841e-07, - -3.1810904294669822e-06, - -0.9999999999946061, - 0.07095015644692688 + -4.490219645842903e-06, + -3.181063409649239e-06, + -0.999999999984859, + 0.07663346037219607 ], [ - -0.49999469818035597, - -0.8660284647666201, - 2.346415833955053e-06, - 0.03491129648463168 + -0.5000074225075973, + -0.8660211183408337, + 5.00001124330122e-06, + 0.03666008712637943 ], [ 0.0, @@ -76,38 +76,38 @@ ] ], "branch_position": [ - 0.018664936330971712, - 0.03147236950257582, - -0.0012001156044763872 + 0.00852381571767217, + 0.028763668526131346, + 1.183437210727778e-07 ], "limits": [ - -1.39626, - 1.39626 + -3.141592653589793, + 3.141592653589793 ] }, { - "name": "3", + "name": "stewart_3", "branch_frame": "closing_3_2", "offset": 0, "solution": 0, "T_motor_world": [ [ - -3.6732051028920366e-06, - 0.9999999999932537, - -5.550981191998246e-17, - -0.010249999999999974 + 6.326794896519466e-06, + 0.9999999999799852, + -7.0550646912150425e-12, + -0.009999884140839245 ], [ - -6.32679489691732e-06, - -2.323955979305758e-11, - 0.9999999999799852, - -0.0709501564463867 + -1.0196153102346142e-06, + 1.3505961633338446e-11, + 0.9999999999994795, + -0.07663346037438698 ], [ - 0.9999999999732392, - 3.6732051032496763e-06, - 6.326794896960002e-06, - 0.034911149429116516 + 0.9999999999794655, + -6.326794896940104e-06, + 1.0196153098685706e-06, + 0.036660683387545835 ], [ 0.0, @@ -117,38 +117,38 @@ ] ], "branch_position": [ - -0.02458835044705289, - 0.0065000597966325315, - -0.0012000238760609128 + -0.029172011376922807, + 0.0069999429399361995, + 4.0290270064691214e-08 ], "limits": [ - -1.39626, - 1.39626 + -3.141592653589793, + 3.141592653589793 ] }, { - "name": "4", + "name": "stewart_4", "branch_frame": "closing_4_2", "offset": 0, "solution": 1, "T_motor_world": [ [ - 6.3267948965194915e-06, - -0.9999999999799852, - 7.045760515816813e-12, - -0.010249886979833239 + -3.673205069955933e-06, + -0.9999999999932537, + -6.767968877969483e-14, + -0.010000000000897517 ], [ - 6.326794896868612e-06, - 3.2982573151899846e-11, - -0.9999999999799852, - 0.07095015644648657 + 1.0196153102837198e-06, + -3.6775764393585005e-12, + -0.9999999999994795, + 0.0766334603742898 ], [ - 0.9999999999599714, - 6.3267948968685995e-06, - 6.3267948965194915e-06, - 0.03491117662826198 + 0.9999999999927336, + -3.673205070385213e-06, + 1.0196153102903487e-06, + 0.03666065685180194 ], [ 0.0, @@ -158,38 +158,38 @@ ] ], "branch_position": [ - -0.024588312654416057, - -0.006500000609238337, - -0.0011999761241727191 + -0.029172040355214434, + -0.0069999960097160766, + -3.1608172912367394e-08 ], "limits": [ - -1.39626, - 1.39626 + -3.141592653589793, + 3.141592653589793 ] }, { - "name": "5", + "name": "stewart_5", "branch_frame": "closing_5_2", "offset": 0, "solution": 0, "T_motor_world": [ [ - -0.8660211183436267, - -0.5000074225224783, - -2.2980696739062076e-06, - -0.01025011120207279 + -0.8660284647694133, + -0.4999946981757419, + 2.298079429767357e-06, + -0.010000231529504576 ], [ - -8.169599766242217e-07, - -3.181082938921205e-06, - 0.9999999999946059, - -0.07095015644665029 + 4.490172883391843e-06, + -3.1811099293773187e-06, + 0.9999999999848591, + -0.07663346037246624 ], [ - -0.5000074225270917, - 0.8660211183408332, - 2.346398951648971e-06, - 0.03491122118425945 + -0.4999946981608617, + 0.8660284647666201, + 4.999994384073154e-06, + 0.03666016059492482 ], [ 0.0, @@ -199,38 +199,38 @@ ] ], "branch_position": [ - 0.018664989140337103, - -0.0314722975004655, - -0.001199884395804346 + 0.008523809101930114, + -0.028763713010385224, + -1.4344916837716326e-07 ], "limits": [ - -1.39626, - 1.39626 + -3.141592653589793, + 3.141592653589793 ] }, { - "name": "6", + "name": "stewart_6", "branch_frame": "passive_7_link_y", "offset": 0, "solution": 1, "T_motor_world": [ [ 0.8660247915798897, - 0.5000010603626028, - 2.2980794226529372e-06, - -0.010250183698700644 + 0.5000010603626025, + -2.298069644866714e-06, + -0.009999527331574583 ], [ - 8.169833477585368e-07, - 3.1810964318917192e-06, - -0.9999999999946063, - 0.07095015644670434 + -4.490196220318687e-06, + 3.1810964558725514e-06, + -0.9999999999848591, + 0.07663346037272492 ], [ - -0.5000010603672164, - 0.8660247915770962, - 2.346415834339648e-06, - 0.0349112358954155 + -0.500001060347722, + 0.8660247915770967, + 5.000011266610794e-06, + 0.036660231042625266 ], [ 0.0, @@ -240,13 +240,13 @@ ] ], "branch_position": [ - 0.02992331793092344, - -0.02497229497412101, - -0.0011999082716467047 + 0.020648186722822436, + -0.02176369606185343, + -8.957920105689965e-08 ], "limits": [ - -1.39626, - 1.39626 + -3.141592653589793, + 3.141592653589793 ] } ] diff --git a/src/reachy_mini/assets/proud2.wav b/src/reachy_mini/assets/wake_up.wav similarity index 100% rename from src/reachy_mini/assets/proud2.wav rename to src/reachy_mini/assets/wake_up.wav diff --git a/src/reachy_mini/daemon/app/bg_job_register.py b/src/reachy_mini/daemon/app/bg_job_register.py new file mode 100644 index 00000000..ec4ae598 --- /dev/null +++ b/src/reachy_mini/daemon/app/bg_job_register.py @@ -0,0 +1,142 @@ +"""Background jobs management for Reachy Mini Daemon.""" + +import asyncio +import logging +import threading +import uuid +from dataclasses import dataclass +from enum import Enum +from typing import Any, Awaitable, Callable + +from fastapi import WebSocket, WebSocketDisconnect +from pydantic import BaseModel + + +class JobStatus(Enum): + """Enum for job status.""" + + PENDING = "pending" + IN_PROGRESS = "in_progress" + DONE = "done" + FAILED = "failed" + + +class JobInfo(BaseModel): + """Pydantic model for install job status.""" + + command: str + status: JobStatus + logs: list[str] + + +@dataclass +class JobHandler: + """Handler for background jobs.""" + + uuid: str + info: JobInfo + new_log_evt: dict[str, asyncio.Event] + + +register: dict[str, JobHandler] = {} + + +def run_command( + command: str, + coro_func: Callable[..., Awaitable[None]], + *args: Any, +) -> str: + """Start a background job, with a custom logger and return its job_id.""" + job_uuid = str(uuid.uuid4()) + + jh = JobHandler( + uuid=job_uuid, + info=JobInfo(command=command, status=JobStatus.PENDING, logs=[]), + new_log_evt={}, + ) + register[job_uuid] = jh + + start_evt = threading.Event() + + async def wrapper() -> None: + jh.info.status = JobStatus.IN_PROGRESS + + class JobLogger(logging.Handler): + def emit(self, record: logging.LogRecord) -> None: + jh.info.logs.append(self.format(record)) + for ws in jh.new_log_evt.values(): + ws.set() + + logger = logging.getLogger(f"logs_job_{job_uuid}") + logger.setLevel(logging.INFO) + logger.handlers.clear() + logger.addHandler(JobLogger()) + + start_evt.set() + + try: + await coro_func(*args, logger=logger) + jh.info.status = JobStatus.DONE + logger.info(f"Job '{command}' completed successfully") + except Exception as e: + jh.info.status = JobStatus.FAILED + logger.error(f"Job '{command}' failed with error: {e}") + + t = threading.Thread(target=lambda: asyncio.run(wrapper())) + t.start() + # background_tasks.add_task(wrapper) + start_evt.wait() + + return job_uuid + + +def get_info(job_id: str) -> JobInfo: + """Get the info of a job by its ID.""" + job = register.get(job_id) + + if not job: + raise ValueError("Job ID not found") + + return job.info + + +async def ws_poll_info(websocket: WebSocket, job_uuid: str) -> None: + """WebSocket endpoint to stream job logs in real time.""" + job = register.get(job_uuid) + if not job: + await websocket.send_json({"error": "Job ID not found"}) + await websocket.close() + return + + assert job is not None + + ws_uuid = str(uuid.uuid4()) + last_log_len = 0 + + try: + job.new_log_evt[ws_uuid] = asyncio.Event() + + while True: + await job.new_log_evt[ws_uuid].wait() + job.new_log_evt[ws_uuid].clear() + + new_logs = job.info.logs[last_log_len:] + + if new_logs: + for log_entry in new_logs: + await websocket.send_text(log_entry) + last_log_len = len(job.info.logs) + + await websocket.send_text( + JobInfo( + command=job.info.command, + status=job.info.status, + logs=new_logs, + ).model_dump_json() + ) + if job.info.status in (JobStatus.DONE, JobStatus.FAILED): + break + except WebSocketDisconnect: + pass + finally: + job.new_log_evt.pop(ws_uuid, None) diff --git a/src/reachy_mini/daemon/app/dashboard/apps_manager.html b/src/reachy_mini/daemon/app/dashboard/apps_manager.html deleted file mode 100644 index 581a51cf..00000000 --- a/src/reachy_mini/daemon/app/dashboard/apps_manager.html +++ /dev/null @@ -1,202 +0,0 @@ - - - - - - Apps Manager - - - - -

Apps Manager

-
-

Available Apps

-
    -
    -
    -

    Installed Apps

    -
      -
      -
      -

      Job Status

      -
      
      -    
      - - - - \ No newline at end of file diff --git a/src/reachy_mini/daemon/app/dashboard/js/dashboard.js b/src/reachy_mini/daemon/app/dashboard/js/dashboard.js deleted file mode 100644 index c8346ad9..00000000 --- a/src/reachy_mini/daemon/app/dashboard/js/dashboard.js +++ /dev/null @@ -1,121 +0,0 @@ -async function fetchDaemonStatus() { - try { - const res = await fetch('/api/daemon/status'); - const status = await res.json(); - document.getElementById('daemon-status').textContent = `State: ${status.state}${status.error ? ' | Error: ' + status.error : ''}`; - if (status.backend_status) { - document.getElementById('daemon-status').innerHTML += `
      Backend status: ${JSON.stringify(status.backend_status)}`; - } - // Enable/disable buttons based on daemon state - const startBtn = document.getElementById('start-daemon'); - const stopBtn = document.getElementById('stop-daemon'); - const restartBtn = document.getElementById('restart-daemon'); - if (status.state === 'not_initialized' || status.state === 'stopped' || status.state === 'error') { - startBtn.disabled = false; - stopBtn.disabled = true; - restartBtn.disabled = true; - } else if (status.state === 'running') { - startBtn.disabled = true; - stopBtn.disabled = false; - restartBtn.disabled = false; - } else if (status.state === 'starting') { - startBtn.disabled = true; - stopBtn.disabled = true; - restartBtn.disabled = true; - } else if (status.state === 'stopping') { - startBtn.disabled = true; - stopBtn.disabled = true; - restartBtn.disabled = true; - } else { - startBtn.disabled = false; - stopBtn.disabled = true; - restartBtn.disabled = true; - } - } catch (e) { - document.getElementById('daemon-status').textContent = 'Error fetching daemon status.'; - } -} -fetchDaemonStatus(); -setInterval(fetchDaemonStatus, 2000); - -document.getElementById('start-daemon').onclick = async function () { - const wakeUp = document.getElementById('wake-up-on-start').checked; - await fetch('/api/daemon/start?wake_up=' + wakeUp, { - method: 'POST', - }); - fetchDaemonStatus(); -}; -document.getElementById('stop-daemon').onclick = async function () { - const gotoSleep = document.getElementById('goto-sleep-on-stop').checked; - await fetch('/api/daemon/stop?goto_sleep=' + gotoSleep, { - method: 'POST', - }); - fetchDaemonStatus(); -}; -document.getElementById('restart-daemon').onclick = async function () { - await fetch('/api/daemon/restart', { method: 'POST' }); - fetchDaemonStatus(); -}; - - - -function onConnectVideo() { - const ip = document.getElementById('video-ip').value; - console.log('Connect to video stream at IP:', ip); - - const signalingProtocol = window.location.protocol.startsWith("https") ? "wss" : "ws"; - const gstWebRTCConfig = { - meta: { name: `WebClient-${Date.now()}` }, - signalingServerUrl: `${signalingProtocol}://${ip}:8443`, - }; - const api = new GstWebRTCAPI(gstWebRTCConfig); - - const listener = { - producerAdded: (producer) => { - console.log("Found producer: ", producer); - if (producer.meta.name !== "reachymini") { - console.log("Ignoring producer with name: ", producer.meta.name); - return; - } - - const session = api.createConsumerSession(producer.id); - console.log("Created session: ", session); - - session.addEventListener("error", (event) => { - if (entryElement._consumerSession === session) { - console.error(event.message, event.error); - } - }); - - session.addEventListener("streamsChanged", () => { - console.log("Streams changed: ", session); - if (session.streams.length > 0) { - // Do something with the active streams - const videoElement = document.getElementById("video"); - videoElement.srcObject = session.streams[0]; - videoElement.play(); - } - }); - session.addEventListener("remoteControllerChanged", () => { - console.log("Remote controller changed: ", session); - }); - - session.addEventListener("closed", (event) => { - console.log("Session closed: ", session); - }); - - session.connect(); - }, - producerRemoved: (producer) => { - console.log("Producer removed: ", producer); - }, - }; - - api.registerProducersListener(listener); - for (const producer of api.getAvailableProducers()) { - console.log("Found producer: ", producer); - listener.producerAdded(producer); - } -} - -document.getElementById('connect-video').onclick = onConnectVideo; diff --git a/src/reachy_mini/daemon/app/dashboard/live_robot_state.html b/src/reachy_mini/daemon/app/dashboard/live_robot_state.html deleted file mode 100644 index f24eabab..00000000 --- a/src/reachy_mini/daemon/app/dashboard/live_robot_state.html +++ /dev/null @@ -1,242 +0,0 @@ - - - - - - Live Robot State & Control - - - - - - -

      Live Robot State & Control

      -
      -

      Head Pose (x, y, z, roll, pitch, yaw)

      - -
      -
      -

      Move Commands

      -
      -
      - Goto - - -
      - - - - Use degrees
      -
      - - - -
      -
      - - - -
      -
      - - - - - - \ No newline at end of file diff --git a/src/reachy_mini/daemon/app/dashboard/static/assets/KO-cartoon-static.svg b/src/reachy_mini/daemon/app/dashboard/static/assets/KO-cartoon-static.svg new file mode 100644 index 00000000..98262b9c --- /dev/null +++ b/src/reachy_mini/daemon/app/dashboard/static/assets/KO-cartoon-static.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1ad9c44f9971b0d5129493f099f182d6ea42d790f9d9e25e79b31acecba5e414 +size 5677 diff --git a/src/reachy_mini/daemon/app/dashboard/static/assets/awake-cartoon-static.svg b/src/reachy_mini/daemon/app/dashboard/static/assets/awake-cartoon-static.svg new file mode 100644 index 00000000..a9c0a539 --- /dev/null +++ b/src/reachy_mini/daemon/app/dashboard/static/assets/awake-cartoon-static.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c752c76e3786b07cc3c25b97dc09da49d7388fdf2cb044d3747aa38f56535ae4 +size 544172 diff --git a/src/reachy_mini/daemon/app/dashboard/static/assets/awake-cartoon.svg b/src/reachy_mini/daemon/app/dashboard/static/assets/awake-cartoon.svg new file mode 100644 index 00000000..a03162a4 --- /dev/null +++ b/src/reachy_mini/daemon/app/dashboard/static/assets/awake-cartoon.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8867ef0f9bd9edd64c2863e99d722fbf45f9f0bb232c4a9df2e63c8c2c2a3213 +size 46179 diff --git a/src/reachy_mini/daemon/app/dashboard/static/assets/go-to-sleep-cartoon.svg b/src/reachy_mini/daemon/app/dashboard/static/assets/go-to-sleep-cartoon.svg new file mode 100644 index 00000000..e38bf619 --- /dev/null +++ b/src/reachy_mini/daemon/app/dashboard/static/assets/go-to-sleep-cartoon.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c6c68f0fdae00154dc687e68684c945dd4a8ffc9e626add679150174974a818d +size 46008 diff --git a/src/reachy_mini/daemon/app/dashboard/static/assets/no-wifi-cartoon-static.svg b/src/reachy_mini/daemon/app/dashboard/static/assets/no-wifi-cartoon-static.svg new file mode 100644 index 00000000..6a83dd99 --- /dev/null +++ b/src/reachy_mini/daemon/app/dashboard/static/assets/no-wifi-cartoon-static.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2803946419f768faa7b9bc5c63a513fc677069df7c463981ac64f31e89f40cec +size 7648 diff --git a/src/reachy_mini/daemon/app/dashboard/static/assets/no-wifi-cartoon.svg b/src/reachy_mini/daemon/app/dashboard/static/assets/no-wifi-cartoon.svg new file mode 100644 index 00000000..c9509e78 --- /dev/null +++ b/src/reachy_mini/daemon/app/dashboard/static/assets/no-wifi-cartoon.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c5ff91ca4030d428fd71f9e572bd5a97be6c0704e98fcaecd5d985ee15a7b053 +size 47411 diff --git a/src/reachy_mini/daemon/app/dashboard/static/assets/reachy-mini-awake.svg b/src/reachy_mini/daemon/app/dashboard/static/assets/reachy-mini-awake.svg new file mode 100644 index 00000000..7c5bd3e8 --- /dev/null +++ b/src/reachy_mini/daemon/app/dashboard/static/assets/reachy-mini-awake.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dc04ba0461049d0ddb26bc18dd24d61a56743f7ae0a6b8967cf3c03e660187e4 +size 2541 diff --git a/src/reachy_mini/daemon/app/dashboard/static/assets/reachy-mini-connection-lost-animation.svg b/src/reachy_mini/daemon/app/dashboard/static/assets/reachy-mini-connection-lost-animation.svg new file mode 100644 index 00000000..7217dc20 --- /dev/null +++ b/src/reachy_mini/daemon/app/dashboard/static/assets/reachy-mini-connection-lost-animation.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b2bba63d3b2c480f92d551e1fdb0d372672793803caacd3aaeb896967c1a64cb +size 44803 diff --git a/src/reachy_mini/daemon/app/dashboard/static/assets/reachy-mini-go-to-sleep-animation.svg b/src/reachy_mini/daemon/app/dashboard/static/assets/reachy-mini-go-to-sleep-animation.svg new file mode 100644 index 00000000..52e8e938 --- /dev/null +++ b/src/reachy_mini/daemon/app/dashboard/static/assets/reachy-mini-go-to-sleep-animation.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e6adbf645766a903289ec673ff3b04df5731691c0ab0c6a52cbb9bcd9f47f621 +size 43776 diff --git a/src/reachy_mini/daemon/app/dashboard/static/assets/reachy-mini-ko-animation.svg b/src/reachy_mini/daemon/app/dashboard/static/assets/reachy-mini-ko-animation.svg new file mode 100644 index 00000000..b5b16f34 --- /dev/null +++ b/src/reachy_mini/daemon/app/dashboard/static/assets/reachy-mini-ko-animation.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c3c13332ae04bbb408be3f10bd176d9a6487c4daf110af95bb17f4526a5df510 +size 43684 diff --git a/src/reachy_mini/daemon/app/dashboard/static/assets/reachy-mini-ko.svg b/src/reachy_mini/daemon/app/dashboard/static/assets/reachy-mini-ko.svg new file mode 100644 index 00000000..14a872bf --- /dev/null +++ b/src/reachy_mini/daemon/app/dashboard/static/assets/reachy-mini-ko.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d98d1352eb846fd1ae44453ca082e382b9fae43690159d4660edd6a1dd15c739 +size 3005 diff --git a/src/reachy_mini/daemon/app/dashboard/static/assets/reachy-mini-sleeping-static.svg b/src/reachy_mini/daemon/app/dashboard/static/assets/reachy-mini-sleeping-static.svg new file mode 100644 index 00000000..a8f7f0f3 --- /dev/null +++ b/src/reachy_mini/daemon/app/dashboard/static/assets/reachy-mini-sleeping-static.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:80dda399747505fba019b9392a151a90c812731fe31d5a06bb17276c61ad19d2 +size 6163 diff --git a/src/reachy_mini/daemon/app/dashboard/static/assets/reachy-mini-sleeping.svg b/src/reachy_mini/daemon/app/dashboard/static/assets/reachy-mini-sleeping.svg new file mode 100644 index 00000000..9d96eadf --- /dev/null +++ b/src/reachy_mini/daemon/app/dashboard/static/assets/reachy-mini-sleeping.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e36a601426886c7ee278b4e97e35a70f6616ec3583f9c89062bc39a868b8240d +size 2520 diff --git a/src/reachy_mini/daemon/app/dashboard/static/assets/reachy-mini-wake-up-animation.svg b/src/reachy_mini/daemon/app/dashboard/static/assets/reachy-mini-wake-up-animation.svg new file mode 100644 index 00000000..3872e016 --- /dev/null +++ b/src/reachy_mini/daemon/app/dashboard/static/assets/reachy-mini-wake-up-animation.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fb5546e131073597f6b1d0f06aabfca05639432b3af8d7667904ed827fc15809 +size 44077 diff --git a/src/reachy_mini/daemon/app/dashboard/static/css/app.css b/src/reachy_mini/daemon/app/dashboard/static/css/app.css new file mode 100644 index 00000000..8d5a9c7a --- /dev/null +++ b/src/reachy_mini/daemon/app/dashboard/static/css/app.css @@ -0,0 +1,1433 @@ +/*! tailwindcss v4.1.16 | MIT License | https://tailwindcss.com */ +@layer properties; +@layer theme, base, components, utilities; +@layer theme { + :root, :host { + --font-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", + "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", + "Courier New", monospace; + --color-red-300: oklch(80.8% 0.114 19.571); + --color-red-400: oklch(70.4% 0.191 22.216); + --color-red-500: oklch(63.7% 0.237 25.331); + --color-red-600: oklch(57.7% 0.245 27.325); + --color-red-700: oklch(50.5% 0.213 27.518); + --color-red-800: oklch(44.4% 0.177 26.899); + --color-yellow-500: oklch(79.5% 0.184 86.047); + --color-green-300: oklch(87.1% 0.15 154.449); + --color-green-400: oklch(79.2% 0.209 151.711); + --color-green-500: oklch(72.3% 0.219 149.579); + --color-green-600: oklch(62.7% 0.194 149.214); + --color-green-700: oklch(52.7% 0.154 150.069); + --color-green-800: oklch(44.8% 0.119 151.328); + --color-blue-300: oklch(80.9% 0.105 251.813); + --color-blue-500: oklch(62.3% 0.214 259.815); + --color-blue-600: oklch(54.6% 0.245 262.881); + --color-blue-700: oklch(48.8% 0.243 264.376); + --color-blue-800: oklch(42.4% 0.199 265.638); + --color-gray-50: oklch(98.5% 0.002 247.839); + --color-gray-100: oklch(96.7% 0.003 264.542); + --color-gray-200: oklch(92.8% 0.006 264.531); + --color-gray-300: oklch(87.2% 0.01 258.338); + --color-gray-400: oklch(70.7% 0.022 261.325); + --color-gray-500: oklch(55.1% 0.027 264.364); + --color-gray-600: oklch(44.6% 0.03 256.802); + --color-gray-700: oklch(37.3% 0.034 259.733); + --color-gray-900: oklch(21% 0.034 264.665); + --color-white: #fff; + --spacing: 0.25rem; + --container-md: 28rem; + --container-2xl: 42rem; + --text-xs: 0.75rem; + --text-xs--line-height: calc(1 / 0.75); + --text-sm: 0.875rem; + --text-sm--line-height: calc(1.25 / 0.875); + --text-base: 1rem; + --text-base--line-height: calc(1.5 / 1); + --text-lg: 1.125rem; + --text-lg--line-height: calc(1.75 / 1.125); + --text-xl: 1.25rem; + --text-xl--line-height: calc(1.75 / 1.25); + --font-weight-medium: 500; + --font-weight-semibold: 600; + --radius-lg: 0.5rem; + --default-transition-duration: 150ms; + --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + --default-font-family: var(--font-sans); + --default-mono-font-family: var(--font-mono); + } +} +@layer base { + *, ::after, ::before, ::backdrop, ::file-selector-button { + box-sizing: border-box; + margin: 0; + padding: 0; + border: 0 solid; + } + html, :host { + line-height: 1.5; + -webkit-text-size-adjust: 100%; + tab-size: 4; + font-family: var(--default-font-family, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"); + font-feature-settings: var(--default-font-feature-settings, normal); + font-variation-settings: var(--default-font-variation-settings, normal); + -webkit-tap-highlight-color: transparent; + } + hr { + height: 0; + color: inherit; + border-top-width: 1px; + } + abbr:where([title]) { + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; + } + h1, h2, h3, h4, h5, h6 { + font-size: inherit; + font-weight: inherit; + } + a { + color: inherit; + -webkit-text-decoration: inherit; + text-decoration: inherit; + } + b, strong { + font-weight: bolder; + } + code, kbd, samp, pre { + font-family: var(--default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace); + font-feature-settings: var(--default-mono-font-feature-settings, normal); + font-variation-settings: var(--default-mono-font-variation-settings, normal); + font-size: 1em; + } + small { + font-size: 80%; + } + sub, sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; + } + sub { + bottom: -0.25em; + } + sup { + top: -0.5em; + } + table { + text-indent: 0; + border-color: inherit; + border-collapse: collapse; + } + :-moz-focusring { + outline: auto; + } + progress { + vertical-align: baseline; + } + summary { + display: list-item; + } + ol, ul, menu { + list-style: none; + } + img, svg, video, canvas, audio, iframe, embed, object { + display: block; + vertical-align: middle; + } + img, video { + max-width: 100%; + height: auto; + } + button, input, select, optgroup, textarea, ::file-selector-button { + font: inherit; + font-feature-settings: inherit; + font-variation-settings: inherit; + letter-spacing: inherit; + color: inherit; + border-radius: 0; + background-color: transparent; + opacity: 1; + } + :where(select:is([multiple], [size])) optgroup { + font-weight: bolder; + } + :where(select:is([multiple], [size])) optgroup option { + padding-inline-start: 20px; + } + ::file-selector-button { + margin-inline-end: 4px; + } + ::placeholder { + opacity: 1; + } + @supports (not (-webkit-appearance: -apple-pay-button)) or (contain-intrinsic-size: 1px) { + ::placeholder { + color: currentcolor; + @supports (color: color-mix(in lab, red, red)) { + color: color-mix(in oklab, currentcolor 50%, transparent); + } + } + } + textarea { + resize: vertical; + } + ::-webkit-search-decoration { + -webkit-appearance: none; + } + ::-webkit-date-and-time-value { + min-height: 1lh; + text-align: inherit; + } + ::-webkit-datetime-edit { + display: inline-flex; + } + ::-webkit-datetime-edit-fields-wrapper { + padding: 0; + } + ::-webkit-datetime-edit, ::-webkit-datetime-edit-year-field, ::-webkit-datetime-edit-month-field, ::-webkit-datetime-edit-day-field, ::-webkit-datetime-edit-hour-field, ::-webkit-datetime-edit-minute-field, ::-webkit-datetime-edit-second-field, ::-webkit-datetime-edit-millisecond-field, ::-webkit-datetime-edit-meridiem-field { + padding-block: 0; + } + ::-webkit-calendar-picker-indicator { + line-height: 1; + } + :-moz-ui-invalid { + box-shadow: none; + } + button, input:where([type="button"], [type="reset"], [type="submit"]), ::file-selector-button { + appearance: button; + } + ::-webkit-inner-spin-button, ::-webkit-outer-spin-button { + height: auto; + } + [hidden]:where(:not([hidden="until-found"])) { + display: none !important; + } +} +@layer utilities { + .pointer-events-none { + pointer-events: none; + } + .sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip-path: inset(50%); + white-space: nowrap; + border-width: 0; + } + .absolute { + position: absolute; + } + .fixed { + position: fixed; + } + .relative { + position: relative; + } + .static { + position: static; + } + .inset-0 { + inset: calc(var(--spacing) * 0); + } + .start-1\.5 { + inset-inline-start: calc(var(--spacing) * 1.5); + } + .end-2\.5 { + inset-inline-end: calc(var(--spacing) * 2.5); + } + .top-0 { + top: calc(var(--spacing) * 0); + } + .top-0\.5 { + top: calc(var(--spacing) * 0.5); + } + .top-1\/2 { + top: calc(1/2 * 100%); + } + .right-0 { + right: calc(var(--spacing) * 0); + } + .left-0 { + left: calc(var(--spacing) * 0); + } + .left-0\.5 { + left: calc(var(--spacing) * 0.5); + } + .z-50 { + z-index: 50; + } + .row-span-2 { + grid-row: span 2 / span 2; + } + .container { + width: 100%; + @media (width >= 40rem) { + max-width: 40rem; + } + @media (width >= 48rem) { + max-width: 48rem; + } + @media (width >= 64rem) { + max-width: 64rem; + } + @media (width >= 80rem) { + max-width: 80rem; + } + @media (width >= 96rem) { + max-width: 96rem; + } + } + .mx-auto { + margin-inline: auto; + } + .my-1 { + margin-block: calc(var(--spacing) * 1); + } + .my-2 { + margin-block: calc(var(--spacing) * 2); + } + .mt-2 { + margin-top: calc(var(--spacing) * 2); + } + .mt-3 { + margin-top: calc(var(--spacing) * 3); + } + .mt-4 { + margin-top: calc(var(--spacing) * 4); + } + .mr-2 { + margin-right: calc(var(--spacing) * 2); + } + .mb-1 { + margin-bottom: calc(var(--spacing) * 1); + } + .mb-2 { + margin-bottom: calc(var(--spacing) * 2); + } + .mb-4 { + margin-bottom: calc(var(--spacing) * 4); + } + .ml-2 { + margin-left: calc(var(--spacing) * 2); + } + .block { + display: block; + } + .flex { + display: flex; + } + .grid { + display: grid; + } + .hidden { + display: none; + } + .inline { + display: inline; + } + .inline-block { + display: inline-block; + } + .size-7 { + width: calc(var(--spacing) * 7); + height: calc(var(--spacing) * 7); + } + .h-2 { + height: calc(var(--spacing) * 2); + } + .h-4 { + height: calc(var(--spacing) * 4); + } + .h-5 { + height: calc(var(--spacing) * 5); + } + .h-7 { + height: calc(var(--spacing) * 7); + } + .h-8 { + height: calc(var(--spacing) * 8); + } + .h-auto { + height: auto; + } + .h-full { + height: 100%; + } + .max-h-48 { + max-height: calc(var(--spacing) * 48); + } + .max-h-96 { + max-height: calc(var(--spacing) * 96); + } + .max-h-full { + max-height: 100%; + } + .min-h-screen { + min-height: 100vh; + } + .w-4 { + width: calc(var(--spacing) * 4); + } + .w-5 { + width: calc(var(--spacing) * 5); + } + .w-7 { + width: calc(var(--spacing) * 7); + } + .w-12 { + width: calc(var(--spacing) * 12); + } + .w-16 { + width: calc(var(--spacing) * 16); + } + .w-20 { + width: calc(var(--spacing) * 20); + } + .w-28 { + width: calc(var(--spacing) * 28); + } + .w-full { + width: 100%; + } + .max-w-2xl { + max-width: var(--container-2xl); + } + .max-w-md { + max-width: var(--container-md); + } + .min-w-\[50\%\] { + min-width: 50%; + } + .flex-1 { + flex: 1; + } + .flex-grow { + flex-grow: 1; + } + .-translate-x-4 { + --tw-translate-x: calc(var(--spacing) * -4); + translate: var(--tw-translate-x) var(--tw-translate-y); + } + .-translate-x-8 { + --tw-translate-x: calc(var(--spacing) * -8); + translate: var(--tw-translate-x) var(--tw-translate-y); + } + .-translate-y-1 { + --tw-translate-y: calc(var(--spacing) * -1); + translate: var(--tw-translate-x) var(--tw-translate-y); + } + .-translate-y-1\/2 { + --tw-translate-y: calc(calc(1/2 * 100%) * -1); + translate: var(--tw-translate-x) var(--tw-translate-y); + } + .transform { + transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,); + } + .cursor-not-allowed { + cursor: not-allowed; + } + .cursor-pointer { + cursor: pointer; + } + .list-inside { + list-style-position: inside; + } + .list-disc { + list-style-type: disc; + } + .appearance-none { + appearance: none; + } + .grid-cols-\[2rem_auto_8rem\] { + grid-template-columns: 2rem auto 8rem; + } + .grid-cols-\[auto_6rem_2rem\] { + grid-template-columns: auto 6rem 2rem; + } + .flex-col { + flex-direction: column; + } + .flex-row { + flex-direction: row; + } + .items-center { + align-items: center; + } + .justify-between { + justify-content: space-between; + } + .justify-center { + justify-content: center; + } + .justify-stretch { + justify-content: stretch; + } + .gap-2 { + gap: calc(var(--spacing) * 2); + } + .gap-4 { + gap: calc(var(--spacing) * 4); + } + .space-y-4 { + :where(& > :not(:last-child)) { + --tw-space-y-reverse: 0; + margin-block-start: calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse)); + margin-block-end: calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse))); + } + } + .gap-x-2 { + column-gap: calc(var(--spacing) * 2); + } + .gap-x-6 { + column-gap: calc(var(--spacing) * 6); + } + .overflow-y-auto { + overflow-y: auto; + } + .rounded { + border-radius: 0.25rem; + } + .rounded-full { + border-radius: calc(infinity * 1px); + } + .rounded-lg { + border-radius: var(--radius-lg); + } + .rounded-t { + border-top-left-radius: 0.25rem; + border-top-right-radius: 0.25rem; + } + .rounded-b { + border-bottom-right-radius: 0.25rem; + border-bottom-left-radius: 0.25rem; + } + .border { + border-style: var(--tw-border-style); + border-width: 1px; + } + .border-t { + border-top-style: var(--tw-border-style); + border-top-width: 1px; + } + .border-b { + border-bottom-style: var(--tw-border-style); + border-bottom-width: 1px; + } + .border-l-2 { + border-left-style: var(--tw-border-style); + border-left-width: 2px; + } + .border-gray-200 { + border-color: var(--color-gray-200); + } + .border-gray-300 { + border-color: var(--color-gray-300); + } + .border-red-600 { + border-color: var(--color-red-600); + } + .bg-blue-500 { + background-color: var(--color-blue-500); + } + .bg-blue-600 { + background-color: var(--color-blue-600); + } + .bg-blue-700 { + background-color: var(--color-blue-700); + } + .bg-gray-50 { + background-color: var(--color-gray-50); + } + .bg-gray-100 { + background-color: var(--color-gray-100); + } + .bg-gray-200 { + background-color: var(--color-gray-200); + } + .bg-gray-300 { + background-color: var(--color-gray-300); + } + .bg-gray-400 { + background-color: var(--color-gray-400); + } + .bg-green-400 { + background-color: var(--color-green-400); + } + .bg-green-500 { + background-color: var(--color-green-500); + } + .bg-green-700 { + background-color: var(--color-green-700); + } + .bg-red-500 { + background-color: var(--color-red-500); + } + .bg-red-700 { + background-color: var(--color-red-700); + } + .bg-white { + background-color: var(--color-white); + } + .bg-yellow-500 { + background-color: var(--color-yellow-500); + } + .p-1 { + padding: calc(var(--spacing) * 1); + } + .p-2\.5 { + padding: calc(var(--spacing) * 2.5); + } + .p-4 { + padding: calc(var(--spacing) * 4); + } + .px-2 { + padding-inline: calc(var(--spacing) * 2); + } + .px-4 { + padding-inline: calc(var(--spacing) * 4); + } + .px-5 { + padding-inline: calc(var(--spacing) * 5); + } + .py-1 { + padding-block: calc(var(--spacing) * 1); + } + .py-2 { + padding-block: calc(var(--spacing) * 2); + } + .py-2\.5 { + padding-block: calc(var(--spacing) * 2.5); + } + .py-4 { + padding-block: calc(var(--spacing) * 4); + } + .pt-1 { + padding-top: calc(var(--spacing) * 1); + } + .pt-4 { + padding-top: calc(var(--spacing) * 4); + } + .pr-4 { + padding-right: calc(var(--spacing) * 4); + } + .pb-2 { + padding-bottom: calc(var(--spacing) * 2); + } + .pl-4 { + padding-left: calc(var(--spacing) * 4); + } + .text-center { + text-align: center; + } + .align-middle { + vertical-align: middle; + } + .font-mono { + font-family: var(--font-mono); + } + .text-base { + font-size: var(--text-base); + line-height: var(--tw-leading, var(--text-base--line-height)); + } + .text-lg { + font-size: var(--text-lg); + line-height: var(--tw-leading, var(--text-lg--line-height)); + } + .text-sm { + font-size: var(--text-sm); + line-height: var(--tw-leading, var(--text-sm--line-height)); + } + .text-xl { + font-size: var(--text-xl); + line-height: var(--tw-leading, var(--text-xl--line-height)); + } + .text-xs { + font-size: var(--text-xs); + line-height: var(--tw-leading, var(--text-xs--line-height)); + } + .font-medium { + --tw-font-weight: var(--font-weight-medium); + font-weight: var(--font-weight-medium); + } + .font-semibold { + --tw-font-weight: var(--font-weight-semibold); + font-weight: var(--font-weight-semibold); + } + .text-blue-700 { + color: var(--color-blue-700); + } + .text-gray-400 { + color: var(--color-gray-400); + } + .text-gray-500 { + color: var(--color-gray-500); + } + .text-gray-600 { + color: var(--color-gray-600); + } + .text-gray-700 { + color: var(--color-gray-700); + } + .text-gray-900 { + color: var(--color-gray-900); + } + .text-green-700 { + color: var(--color-green-700); + } + .text-white { + color: var(--color-white); + } + .overline { + text-decoration-line: overline; + } + .opacity-50 { + opacity: 50%; + } + .shadow { + --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + } + .shadow-sm { + --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + } + .transition-all { + transition-property: all; + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); + transition-duration: var(--tw-duration, var(--default-transition-duration)); + } + .transition-colors { + transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to; + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); + transition-duration: var(--tw-duration, var(--default-transition-duration)); + } + .duration-150 { + --tw-duration: 150ms; + transition-duration: 150ms; + } + .duration-200 { + --tw-duration: 200ms; + transition-duration: 200ms; + } + .select-none { + -webkit-user-select: none; + user-select: none; + } + .peer-checked\:bg-blue-800 { + &:is(:where(.peer):checked ~ *) { + background-color: var(--color-blue-800); + } + } + .peer-checked\:bg-green-400 { + &:is(:where(.peer):checked ~ *) { + background-color: var(--color-green-400); + } + } + .peer-checked\:text-gray-400 { + &:is(:where(.peer):checked ~ *) { + color: var(--color-gray-400); + } + } + .peer-checked\:text-gray-900 { + &:is(:where(.peer):checked ~ *) { + color: var(--color-gray-900); + } + } + .peer-checked\:text-white { + &:is(:where(.peer):checked ~ *) { + color: var(--color-white); + } + } + .peer-focus\:ring-4 { + &:is(:where(.peer):focus ~ *) { + --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + } + } + .peer-focus\:ring-gray-100 { + &:is(:where(.peer):focus ~ *) { + --tw-ring-color: var(--color-gray-100); + } + } + .peer-focus\:outline-none { + &:is(:where(.peer):focus ~ *) { + --tw-outline-style: none; + outline-style: none; + } + } + .after\:absolute { + &::after { + content: var(--tw-content); + position: absolute; + } + } + .after\:start-0\.5 { + &::after { + content: var(--tw-content); + inset-inline-start: calc(var(--spacing) * 0.5); + } + } + .after\:top-0\.5 { + &::after { + content: var(--tw-content); + top: calc(var(--spacing) * 0.5); + } + } + .after\:h-7 { + &::after { + content: var(--tw-content); + height: calc(var(--spacing) * 7); + } + } + .after\:w-7 { + &::after { + content: var(--tw-content); + width: calc(var(--spacing) * 7); + } + } + .after\:rounded-full { + &::after { + content: var(--tw-content); + border-radius: calc(infinity * 1px); + } + } + .after\:border { + &::after { + content: var(--tw-content); + border-style: var(--tw-border-style); + border-width: 1px; + } + } + .after\:border-gray-300 { + &::after { + content: var(--tw-content); + border-color: var(--color-gray-300); + } + } + .after\:bg-white { + &::after { + content: var(--tw-content); + background-color: var(--color-white); + } + } + .after\:transition-all { + &::after { + content: var(--tw-content); + transition-property: all; + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); + transition-duration: var(--tw-duration, var(--default-transition-duration)); + } + } + .after\:content-\[\'\'\] { + &::after { + --tw-content: ''; + content: var(--tw-content); + } + } + .peer-checked\:after\:translate-x-\[46px\] { + &:is(:where(.peer):checked ~ *) { + &::after { + content: var(--tw-content); + --tw-translate-x: 46px; + translate: var(--tw-translate-x) var(--tw-translate-y); + } + } + } + .peer-checked\:after\:border-white { + &:is(:where(.peer):checked ~ *) { + &::after { + content: var(--tw-content); + border-color: var(--color-white); + } + } + } + .hover\:bg-blue-600 { + &:hover { + @media (hover: hover) { + background-color: var(--color-blue-600); + } + } + } + .hover\:bg-blue-700 { + &:hover { + @media (hover: hover) { + background-color: var(--color-blue-700); + } + } + } + .hover\:bg-blue-800 { + &:hover { + @media (hover: hover) { + background-color: var(--color-blue-800); + } + } + } + .hover\:bg-gray-200 { + &:hover { + @media (hover: hover) { + background-color: var(--color-gray-200); + } + } + } + .hover\:bg-gray-300 { + &:hover { + @media (hover: hover) { + background-color: var(--color-gray-300); + } + } + } + .hover\:bg-green-500 { + &:hover { + @media (hover: hover) { + background-color: var(--color-green-500); + } + } + } + .hover\:bg-green-800 { + &:hover { + @media (hover: hover) { + background-color: var(--color-green-800); + } + } + } + .hover\:bg-red-600 { + &:hover { + @media (hover: hover) { + background-color: var(--color-red-600); + } + } + } + .hover\:bg-red-800 { + &:hover { + @media (hover: hover) { + background-color: var(--color-red-800); + } + } + } + .hover\:text-gray-900 { + &:hover { + @media (hover: hover) { + color: var(--color-gray-900); + } + } + } + .focus\:border-blue-500 { + &:focus { + border-color: var(--color-blue-500); + } + } + .focus\:ring-4 { + &:focus { + --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + } + } + .focus\:ring-blue-300 { + &:focus { + --tw-ring-color: var(--color-blue-300); + } + } + .focus\:ring-blue-500 { + &:focus { + --tw-ring-color: var(--color-blue-500); + } + } + .focus\:ring-green-300 { + &:focus { + --tw-ring-color: var(--color-green-300); + } + } + .focus\:ring-red-300 { + &:focus { + --tw-ring-color: var(--color-red-300); + } + } + .focus\:outline-none { + &:focus { + --tw-outline-style: none; + outline-style: none; + } + } + .disabled\:bg-gray-300 { + &:disabled { + background-color: var(--color-gray-300); + } + } + .disabled\:text-gray-500 { + &:disabled { + color: var(--color-gray-500); + } + } + .sm\:w-auto { + @media (width >= 40rem) { + width: auto; + } + } + .sm\:px-8 { + @media (width >= 40rem) { + padding-inline: calc(var(--spacing) * 8); + } + } + .md\:w-2\/3 { + @media (width >= 48rem) { + width: calc(2/3 * 100%); + } + } + .md\:flex-row { + @media (width >= 48rem) { + flex-direction: row; + } + } + .md\:p-5 { + @media (width >= 48rem) { + padding: calc(var(--spacing) * 5); + } + } + .md\:px-8 { + @media (width >= 48rem) { + padding-inline: calc(var(--spacing) * 8); + } + } + .lg\:px-8 { + @media (width >= 64rem) { + padding-inline: calc(var(--spacing) * 8); + } + } + .xl\:px-8 { + @media (width >= 80rem) { + padding-inline: calc(var(--spacing) * 8); + } + } + .rtl\:peer-checked\:after\:-translate-x-full { + &:where(:dir(rtl), [dir="rtl"], [dir="rtl"] *) { + &:is(:where(.peer):checked ~ *) { + &::after { + content: var(--tw-content); + --tw-translate-x: -100%; + translate: var(--tw-translate-x) var(--tw-translate-y); + } + } + } + } + .dark\:border-gray-600 { + @media (prefers-color-scheme: dark) { + border-color: var(--color-gray-600); + } + } + .dark\:bg-blue-600 { + @media (prefers-color-scheme: dark) { + background-color: var(--color-blue-600); + } + } + .dark\:bg-gray-400 { + @media (prefers-color-scheme: dark) { + background-color: var(--color-gray-400); + } + } + .dark\:bg-gray-700 { + @media (prefers-color-scheme: dark) { + background-color: var(--color-gray-700); + } + } + .dark\:bg-green-600 { + @media (prefers-color-scheme: dark) { + background-color: var(--color-green-600); + } + } + .dark\:bg-red-400 { + @media (prefers-color-scheme: dark) { + background-color: var(--color-red-400); + } + } + .dark\:bg-red-600 { + @media (prefers-color-scheme: dark) { + background-color: var(--color-red-600); + } + } + .dark\:text-gray-400 { + @media (prefers-color-scheme: dark) { + color: var(--color-gray-400); + } + } + .dark\:text-white { + @media (prefers-color-scheme: dark) { + color: var(--color-white); + } + } + .dark\:peer-checked\:bg-blue-800 { + @media (prefers-color-scheme: dark) { + &:is(:where(.peer):checked ~ *) { + background-color: var(--color-blue-800); + } + } + } + .dark\:peer-checked\:bg-green-400 { + @media (prefers-color-scheme: dark) { + &:is(:where(.peer):checked ~ *) { + background-color: var(--color-green-400); + } + } + } + .dark\:peer-focus\:ring-gray-100 { + @media (prefers-color-scheme: dark) { + &:is(:where(.peer):focus ~ *) { + --tw-ring-color: var(--color-gray-100); + } + } + } + .dark\:hover\:bg-blue-700 { + @media (prefers-color-scheme: dark) { + &:hover { + @media (hover: hover) { + background-color: var(--color-blue-700); + } + } + } + } + .dark\:hover\:bg-green-700 { + @media (prefers-color-scheme: dark) { + &:hover { + @media (hover: hover) { + background-color: var(--color-green-700); + } + } + } + } + .dark\:hover\:bg-red-700 { + @media (prefers-color-scheme: dark) { + &:hover { + @media (hover: hover) { + background-color: var(--color-red-700); + } + } + } + } + .dark\:focus\:ring-blue-800 { + @media (prefers-color-scheme: dark) { + &:focus { + --tw-ring-color: var(--color-blue-800); + } + } + } + .dark\:focus\:ring-green-800 { + @media (prefers-color-scheme: dark) { + &:focus { + --tw-ring-color: var(--color-green-800); + } + } + } + .dark\:focus\:ring-red-800 { + @media (prefers-color-scheme: dark) { + &:focus { + --tw-ring-color: var(--color-red-800); + } + } + } +} +@font-face { + font-family: 'Asap'; + src: url('/static/fonts/Asap-VariableFont_wdth,wght.ttf') format('truetype'); + font-weight: 100 900; + font-style: normal; +} +@font-face { + font-family: 'Asap'; + src: url('/static/fonts/Asap-Italic-VariableFont_wdth,wght.ttf') format('truetype'); + font-weight: 100 900; + font-style: italic; +} +@font-face { + font-family: 'Archivo'; + src: url('/static/fonts/Archivo-VariableFont_wdth,wght.ttf') format('truetype'); + font-weight: 100 900; + font-style: normal; +} +@font-face { + font-family: 'Archivo'; + src: url('/static/fonts/Archivo-Italic-VariableFont_wdth,wght.ttf') format('truetype'); + font-weight: 100 900; + font-style: italic; +} +html { + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} +body { + font-family: 'Archivo', 'Asap', sans-serif; +} +.daemon-status-title { + font-size: 36px; + font-weight: 500; + letter-spacing: 0.3px; +} +.daemon-status-collapse { + font-family: 'Asap', sans-serif; + font-weight: 400; + color: rgba(0, 0, 0, 0.7); +} +.app-section { + border-radius: 8px; + border-width: 1px; + padding: 16px; + gap: 16px; + background: #FFFFFF; +} +.installed-app-title { + font-weight: 600; +} +.app-section-title { + font-weight: 500; + font-size: 24px; + letter-spacing: 0px; +} +.app-list-item { + padding: 4px; +} +.hf-app-icon { + width: 42px; + height: 42px; + border-radius: 10px; + text-align: center; + font-size: 31px; + background-color: rgba(0, 0, 0, 0.1); +} +.hf-app-title { + font-weight: 500; + font-size: 18px; +} +.hf-app-description { + font-family: 'Asap', sans-serif; + font-weight: 400; + font-size: 16px; + line-height: 75%; + color: rgba(0, 0, 0, 0.4); +} +.hf-app-install-button { + font-weight: 500; + font-size: 20px; + font-style: Bold; + text-align: center; + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; +} +.wifi-status { + font-family: 'Asap', sans-serif; + font-weight: 500; + color: rgba(0, 0, 0, 0.9); +} +.wifi-status-info { + font-family: 'Asap', sans-serif; + font-weight: 400; + color: rgba(0, 0, 0, 0.7); +} +.notification-modal { + position: fixed; + bottom: 24px; + right: 24px; + z-index: 9999; + min-width: 300px; + max-width: 400px; + background: #fff; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2); + border-radius: 8px; + display: none; + animation: fadeInUp 0.3s; +} +@keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(40px); + } + to { + opacity: 1; + transform: translateY(0); + } +} +.notification-content { + padding: 20px 28px; + border-radius: 8px; + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.15); + display: flex; + align-items: center; + justify-content: space-between; +} +.notification-content.info { + background: #fff; + border: 1px solid #e0e0e0; +} +.notification-content.error { + background: #ffeaea; + border: 1px solid #ff4d4f; + color: #b71c1c; +} +.close-btn { + background: none; + border: none; + font-size: 1.5em; + cursor: pointer; + color: #888; +} +@property --tw-translate-x { + syntax: "*"; + inherits: false; + initial-value: 0; +} +@property --tw-translate-y { + syntax: "*"; + inherits: false; + initial-value: 0; +} +@property --tw-translate-z { + syntax: "*"; + inherits: false; + initial-value: 0; +} +@property --tw-rotate-x { + syntax: "*"; + inherits: false; +} +@property --tw-rotate-y { + syntax: "*"; + inherits: false; +} +@property --tw-rotate-z { + syntax: "*"; + inherits: false; +} +@property --tw-skew-x { + syntax: "*"; + inherits: false; +} +@property --tw-skew-y { + syntax: "*"; + inherits: false; +} +@property --tw-space-y-reverse { + syntax: "*"; + inherits: false; + initial-value: 0; +} +@property --tw-border-style { + syntax: "*"; + inherits: false; + initial-value: solid; +} +@property --tw-font-weight { + syntax: "*"; + inherits: false; +} +@property --tw-shadow { + syntax: "*"; + inherits: false; + initial-value: 0 0 #0000; +} +@property --tw-shadow-color { + syntax: "*"; + inherits: false; +} +@property --tw-shadow-alpha { + syntax: ""; + inherits: false; + initial-value: 100%; +} +@property --tw-inset-shadow { + syntax: "*"; + inherits: false; + initial-value: 0 0 #0000; +} +@property --tw-inset-shadow-color { + syntax: "*"; + inherits: false; +} +@property --tw-inset-shadow-alpha { + syntax: ""; + inherits: false; + initial-value: 100%; +} +@property --tw-ring-color { + syntax: "*"; + inherits: false; +} +@property --tw-ring-shadow { + syntax: "*"; + inherits: false; + initial-value: 0 0 #0000; +} +@property --tw-inset-ring-color { + syntax: "*"; + inherits: false; +} +@property --tw-inset-ring-shadow { + syntax: "*"; + inherits: false; + initial-value: 0 0 #0000; +} +@property --tw-ring-inset { + syntax: "*"; + inherits: false; +} +@property --tw-ring-offset-width { + syntax: ""; + inherits: false; + initial-value: 0px; +} +@property --tw-ring-offset-color { + syntax: "*"; + inherits: false; + initial-value: #fff; +} +@property --tw-ring-offset-shadow { + syntax: "*"; + inherits: false; + initial-value: 0 0 #0000; +} +@property --tw-duration { + syntax: "*"; + inherits: false; +} +@property --tw-content { + syntax: "*"; + initial-value: ""; + inherits: false; +} +@layer properties { + @supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) { + *, ::before, ::after, ::backdrop { + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-translate-z: 0; + --tw-rotate-x: initial; + --tw-rotate-y: initial; + --tw-rotate-z: initial; + --tw-skew-x: initial; + --tw-skew-y: initial; + --tw-space-y-reverse: 0; + --tw-border-style: solid; + --tw-font-weight: initial; + --tw-shadow: 0 0 #0000; + --tw-shadow-color: initial; + --tw-shadow-alpha: 100%; + --tw-inset-shadow: 0 0 #0000; + --tw-inset-shadow-color: initial; + --tw-inset-shadow-alpha: 100%; + --tw-ring-color: initial; + --tw-ring-shadow: 0 0 #0000; + --tw-inset-ring-color: initial; + --tw-inset-ring-shadow: 0 0 #0000; + --tw-ring-inset: initial; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-offset-shadow: 0 0 #0000; + --tw-duration: initial; + --tw-content: ""; + } + } +} diff --git a/src/reachy_mini/daemon/app/dashboard/static/fonts/Archivo-Italic-VariableFont_wdth,wght.ttf b/src/reachy_mini/daemon/app/dashboard/static/fonts/Archivo-Italic-VariableFont_wdth,wght.ttf new file mode 100644 index 00000000..2f831577 Binary files /dev/null and b/src/reachy_mini/daemon/app/dashboard/static/fonts/Archivo-Italic-VariableFont_wdth,wght.ttf differ diff --git a/src/reachy_mini/daemon/app/dashboard/static/fonts/Archivo-VariableFont_wdth,wght.ttf b/src/reachy_mini/daemon/app/dashboard/static/fonts/Archivo-VariableFont_wdth,wght.ttf new file mode 100644 index 00000000..5c794ee0 Binary files /dev/null and b/src/reachy_mini/daemon/app/dashboard/static/fonts/Archivo-VariableFont_wdth,wght.ttf differ diff --git a/src/reachy_mini/daemon/app/dashboard/static/fonts/Asap-Italic-VariableFont_wdth,wght.ttf b/src/reachy_mini/daemon/app/dashboard/static/fonts/Asap-Italic-VariableFont_wdth,wght.ttf new file mode 100644 index 00000000..d30c05b1 Binary files /dev/null and b/src/reachy_mini/daemon/app/dashboard/static/fonts/Asap-Italic-VariableFont_wdth,wght.ttf differ diff --git a/src/reachy_mini/daemon/app/dashboard/static/fonts/Asap-VariableFont_wdth,wght.ttf b/src/reachy_mini/daemon/app/dashboard/static/fonts/Asap-VariableFont_wdth,wght.ttf new file mode 100644 index 00000000..32824411 Binary files /dev/null and b/src/reachy_mini/daemon/app/dashboard/static/fonts/Asap-VariableFont_wdth,wght.ttf differ diff --git a/src/reachy_mini/daemon/app/dashboard/js/3rdparty/gstwebrtc-api-2.0.0.min.js b/src/reachy_mini/daemon/app/dashboard/static/js/3rdparty/gstwebrtc-api-2.0.0.min.js similarity index 100% rename from src/reachy_mini/daemon/app/dashboard/js/3rdparty/gstwebrtc-api-2.0.0.min.js rename to src/reachy_mini/daemon/app/dashboard/static/js/3rdparty/gstwebrtc-api-2.0.0.min.js diff --git a/src/reachy_mini/daemon/app/dashboard/static/js/apps.js b/src/reachy_mini/daemon/app/dashboard/static/js/apps.js new file mode 100644 index 00000000..3fff7ad0 --- /dev/null +++ b/src/reachy_mini/daemon/app/dashboard/static/js/apps.js @@ -0,0 +1,323 @@ +const installedApps = { + refreshAppList: async () => { + const appsData = await installedApps.fetchInstalledApps(); + await installedApps.displayInstalledApps(appsData); + }, + + currentlyRunningApp: null, + busy: false, + toggles: {}, + + startApp: async (appName) => { + if (installedApps.busy) { + console.log(`Another app is currently being started or stopped.`); + return; + } + installedApps.setBusy(true); + + console.log(`Current running app: ${installedApps.currentlyRunningApp}`); + + if (installedApps.currentlyRunningApp) { + console.log(`Stopping currently running app: ${installedApps.currentlyRunningApp}...`); + await installedApps.stopApp(installedApps.currentlyRunningApp, true); + } + + console.log(`Starting app: ${appName}...`); + const endpoint = `/api/apps/start-app/${appName}`; + const resp = await fetch(endpoint, { method: 'POST' }); + if (!resp.ok) { + console.error(`Failed to staret app ${appName}: ${resp.statusText}`); + installedApps.toggles[appName].setChecked(false); + installedApps.setBusy(false); + return; + } else { + console.log(`App ${appName} started successfully.`); + } + + installedApps.currentlyRunningApp = appName; + installedApps.setBusy(false); + }, + + stopApp: async (appName, force = false) => { + if (installedApps.busy && !force) { + console.log(`Another app is currently being started or stopped.`); + return; + } + installedApps.setBusy(true); + + console.log(`Stopping app: ${appName}...`); + + if (force) { + console.log(`Force stopping app: ${appName}...`); + installedApps.toggles[appName].setChecked(false); + } + + const endpoint = `/api/apps/stop-current-app`; + const resp = await fetch(endpoint, { method: 'POST' }); + if (!resp.ok) { + console.error(`Failed to stop app ${appName}: ${resp.statusText}`); + installedApps.setBusy(false); + return; + } else { + console.log(`App ${appName} stopped successfully.`); + installedApps.toggles[appName].setChecked(false); + } + + if (installedApps.currentlyRunningApp === appName) { + installedApps.currentlyRunningApp = null; + } + installedApps.setBusy(false); + }, + + setBusy: (isBusy) => { + installedApps.busy = isBusy; + for (const toggle of Object.values(installedApps.toggles)) { + if (isBusy) { + toggle.disable(); + } else { + toggle.enable(); + } + } + }, + + fetchInstalledApps: async () => { + const resp = await fetch('/api/apps/list-available/installed'); + const appsData = await resp.json(); + return appsData; + }, + + displayInstalledApps: async (appsData) => { + const appsListElement = document.getElementById('installed-apps'); + appsListElement.innerHTML = ''; + + if (!appsData || appsData.length === 0) { + appsListElement.innerHTML = '
    • No installed apps found.
    • '; + return; + } + + const runningApp = await installedApps.getRunningApp(); + + installedApps.toggles = {}; + appsData.forEach(app => { + const li = document.createElement('li'); + li.className = 'app-list-item'; + const isRunning = (app.name === runningApp); + li.appendChild(installedApps.createAppElement(app, isRunning)); + appsListElement.appendChild(li); + }); + }, + + createAppElement: (app, isRunning) => { + const container = document.createElement('div'); + container.className = 'grid grid-cols-[auto_6rem_2rem] justify-stretch gap-x-2'; + + const title = document.createElement('div'); + const titleSpan = document.createElement('span'); + titleSpan.className = 'installed-app-title top-1/2 '; + titleSpan.innerHTML = app.name; + title.appendChild(titleSpan); + if (app.extra && app.extra.custom_app_url) { + const settingsLink = document.createElement('a'); + settingsLink.className = 'installed-app-settings ml-2 text-gray-500 cursor-pointer'; + settingsLink.innerHTML = '⚙️'; + + const url = new URL(app.extra.custom_app_url); + url.hostname = window.location.host.split(':')[0]; + + settingsLink.href = url.toString(); + settingsLink.target = '_blank'; + settingsLink.rel = 'noopener noreferrer'; + title.appendChild(settingsLink); + } + container.appendChild(title); + const slider = document.createElement('div'); + const toggle = new ToggleSlider({ + checked: isRunning, + onChange: (checked) => { + if (installedApps.busy) { + toggle.setChecked(!checked); + return; + } + if (checked) { + installedApps.startApp(app.name); + } else { + installedApps.stopApp(app.name); + } + } + }); + installedApps.toggles[app.name] = toggle; + slider.appendChild(toggle.element); + container.appendChild(slider); + + const remove = document.createElement('button'); + remove.innerHTML = '🗑️'; + remove.className = '-translate-y-1 text-xl'; + container.appendChild(remove); + remove.onclick = async () => { + console.log(`Removing ${app.name}...`); + const resp = await fetch(`/api/apps/remove/${app.name}`, { method: 'POST' }); + const data = await resp.json(); + const jobId = data.job_id; + + installedApps.appUninstallLogHandler(app.name, jobId); + }; + + return container; + }, + + getRunningApp: async () => { + const resp = await fetch('/api/apps/current-app-status'); + const data = await resp.json(); + if (!data) { + return null; + } + installedApps.currentlyRunningApp = data.info.name; + return data.info.name; + }, + + appUninstallLogHandler: async (appName, jobId) => { + const installModal = document.getElementById('install-modal'); + const modalTitle = installModal.querySelector('#modal-title'); + modalTitle.textContent = `Uninstalling ${appName}...`; + installModal.classList.remove('hidden'); + + const logsDiv = document.getElementById('install-logs'); + logsDiv.textContent = ''; + + const closeButton = document.getElementById('modal-close-button'); + closeButton.onclick = () => { + installModal.classList.add('hidden'); + }; + closeButton.classList = "hidden"; + closeButton.textContent = ''; + + const ws = new WebSocket(`ws://${location.host}/api/apps/ws/apps-manager/${jobId}`); + ws.onmessage = (event) => { + try { + if (event.data.startsWith('{') && event.data.endsWith('}')) { + + const data = JSON.parse(event.data); + + if (data.status === "failed") { + closeButton.classList = "text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:outline-none focus:ring-red-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-red-600 dark:hover:bg-red-700 dark:focus:ring-red-800"; + closeButton.textContent = 'Close'; + console.error(`Uninstallation of ${appName} failed.`); + } else if (data.status === "done") { + closeButton.classList = "text-white bg-green-700 hover:bg-green-800 focus:ring-4 focus:outline-none focus:ring-green-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-green-600 dark:hover:bg-green-700 dark:focus:ring-green-800"; + closeButton.textContent = 'Uninstall done'; + console.log(`Uninstallation of ${appName} completed.`); + + } + } else { + logsDiv.innerHTML += event.data + '\n'; + logsDiv.scrollTop = logsDiv.scrollHeight; + } + } catch { + logsDiv.innerHTML += event.data + '\n'; + logsDiv.scrollTop = logsDiv.scrollHeight; + } + }; + ws.onclose = async () => { + hfAppsStore.refreshAppList(); + installedApps.refreshAppList(); + }; + }, +}; + +class ToggleSlider { + constructor({ checked = false, onChange = null } = {}) { + this.label = document.createElement('label'); + this.label.className = 'relative inline-block w-28 h-8 cursor-pointer'; + + this.input = document.createElement('input'); + this.input.type = 'checkbox'; + this.input.className = 'sr-only peer'; + this.input.checked = checked; + this.label.appendChild(this.input); + + // Off label + this.offLabel = document.createElement('span'); + this.offLabel.textContent = 'Off'; + this.offLabel.className = 'absolute left-0 top-1/2 -translate-x-8 -translate-y-1/2 text-base select-none transition-colors duration-200 text-gray-900 peer-checked:text-gray-400'; + this.label.appendChild(this.offLabel); + + this.track = document.createElement('div'); + this.track.className = 'absolute top-0 left-0 w-16 h-8 bg-gray-200 rounded-full transition-colors duration-200 peer-checked:bg-blue-800 dark:bg-gray-400 dark:peer-checked:bg-blue-800'; + this.label.appendChild(this.track); + + this.thumb = document.createElement('div'); + this.thumb.className = 'absolute top-0.5 left-0.5 w-7 h-7 bg-white border border-gray-300 rounded-full transition-all duration-200'; + this.track.appendChild(this.thumb); + + // On label + this.onLabel = document.createElement('span'); + this.onLabel.textContent = 'On'; + this.onLabel.className = 'absolute right-0 top-1/2 -translate-y-1/2 -translate-x-4 text-base select-none transition-colors duration-200 text-gray-400 peer-checked:text-gray-900'; + this.label.appendChild(this.onLabel); + + + this.input.addEventListener('change', () => { + if (this.input.checked) { + this.thumb.style.transform = 'translateX(31px)'; + this.onLabel.classList.remove('text-gray-400'); + this.onLabel.classList.add('text-gray-900'); + this.offLabel.classList.remove('text-gray-900'); + this.offLabel.classList.add('text-gray-400'); + } else { + this.thumb.style.transform = 'translateX(0)'; + this.onLabel.classList.remove('text-gray-900'); + this.onLabel.classList.add('text-gray-400'); + this.offLabel.classList.remove('text-gray-400'); + this.offLabel.classList.add('text-gray-900'); + } + if (onChange) onChange(this.input.checked); + }); + + // Set initial thumb and label color + if (checked) { + this.thumb.style.transform = 'translateX(31px)'; + this.onLabel.classList.remove('text-gray-400'); + this.onLabel.classList.add('text-gray-900'); + } else { + this.onLabel.classList.remove('text-gray-900'); + this.onLabel.classList.add('text-gray-400'); + } + + this.element = this.label; + } + + setChecked(val) { + this.input.checked = val; + if (this.input.checked) { + this.thumb.style.transform = 'translateX(48px)'; + this.onLabel.classList.remove('text-gray-400'); + this.onLabel.classList.add('text-gray-900'); + this.offLabel.classList.remove('text-gray-900'); + this.offLabel.classList.add('text-gray-400'); + } else { + this.thumb.style.transform = 'translateX(0)'; + this.onLabel.classList.remove('text-gray-900'); + this.onLabel.classList.add('text-gray-400'); + this.offLabel.classList.remove('text-gray-400'); + this.offLabel.classList.add('text-gray-900'); + } + } + + getChecked() { + return this.input.checked; + } + + disable() { + this.input.disabled = true; + this.label.classList.add('opacity-50', 'pointer-events-none'); + } + + enable() { + this.input.disabled = false; + this.label.classList.remove('opacity-50', 'pointer-events-none'); + } +}; + +window.addEventListener('load', async () => { + await installedApps.refreshAppList(); +}); \ No newline at end of file diff --git a/src/reachy_mini/daemon/app/dashboard/static/js/appstore.js b/src/reachy_mini/daemon/app/dashboard/static/js/appstore.js new file mode 100644 index 00000000..e570fd73 --- /dev/null +++ b/src/reachy_mini/daemon/app/dashboard/static/js/appstore.js @@ -0,0 +1,157 @@ + +const hfAppsStore = { + refreshAppList: async () => { + const appsData = await hfAppsStore.fetchAvailableApps(); + await hfAppsStore.displayAvailableApps(appsData); + }, + fetchAvailableApps: async () => { + const resAvailable = await fetch('/api/apps/list-available/dashboard_selection'); + const appsData = await resAvailable.json(); + return appsData; + }, + + isInstalling: false, + + installApp: async (app) => { + if (hfAppsStore.isInstalling) { + console.warn('An installation is already in progress.'); + return; + } + hfAppsStore.isInstalling = true; + + const appName = app.extra.cardData.title || app.name; + console.log(`Installing ${app.name}...`); + + const resp = await fetch('/api/apps/install', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(app) + }); + const data = await resp.json(); + const jobId = data.job_id; + + hfAppsStore.appInstallLogHandler(appName, jobId); + }, + + displayAvailableApps: async (appsData) => { + const appsListElement = document.getElementById('hf-available-apps'); + appsListElement.innerHTML = ''; + + if (!appsData || appsData.length === 0) { + appsListElement.innerHTML = '
    • No available apps found.
    • '; + return; + } + + const hfApps = appsData.filter(app => app.source_kind === 'hf_space'); + const installedApps = await fetch('/api/apps/list-available/installed').then(res => res.json()); + + hfApps.forEach(app => { + const li = document.createElement('li'); + li.className = 'app-list-item'; + const isInstalled = installedApps.some(installedApp => installedApp.name === app.name); + li.appendChild(hfAppsStore.createAppElement(app, isInstalled)); + appsListElement.appendChild(li); + }); + }, + + createAppElement: (app, isInstalled) => { + const container = document.createElement('div'); + container.className = 'grid grid-cols-[2rem_auto_8rem] justify-stretch gap-x-6'; + + const iconDiv = document.createElement('div'); + iconDiv.className = 'hf-app-icon row-span-2 my-1'; + iconDiv.textContent = app.extra.cardData.emoji || '📦'; + container.appendChild(iconDiv); + + const nameDiv = document.createElement('div'); + nameDiv.className = 'flex flex-col'; + + const titleDiv = document.createElement('a'); + titleDiv.href = app.url; + titleDiv.target = '_blank'; + titleDiv.rel = 'noopener noreferrer'; + titleDiv.className = 'hf-app-title'; + titleDiv.textContent = app.extra.cardData.title || app.name; + nameDiv.appendChild(titleDiv); + const descriptionDiv = document.createElement('span'); + descriptionDiv.className = 'hf-app-description'; + descriptionDiv.textContent = app.description || 'No description available.'; + nameDiv.appendChild(descriptionDiv); + container.appendChild(nameDiv); + + const installButtonDiv = document.createElement('button'); + installButtonDiv.className = 'row-span-2 my-2 hf-app-install-button'; + + if (isInstalled) { + installButtonDiv.classList.add('bg-gray-400', 'cursor-not-allowed'); + installButtonDiv.textContent = 'Installed'; + installButtonDiv.disabled = true; + } else { + installButtonDiv.classList.add('border', 'border-red-600'); + installButtonDiv.textContent = 'Install'; + installButtonDiv.onclick = async () => { + hfAppsStore.installApp(app); + }; + } + + container.appendChild(installButtonDiv); + + return container; + }, + + appInstallLogHandler: async (appName, jobId) => { + const installModal = document.getElementById('install-modal'); + const modalTitle = installModal.querySelector('#modal-title'); + modalTitle.textContent = `Installing ${appName}...`; + installModal.classList.remove('hidden'); + + const logsDiv = document.getElementById('install-logs'); + logsDiv.textContent = ''; + + const closeButton = document.getElementById('modal-close-button'); + closeButton.onclick = () => { + installModal.classList.add('hidden'); + }; + closeButton.classList = "hidden"; + closeButton.textContent = ''; + + const ws = new WebSocket(`ws://${location.host}/api/apps/ws/apps-manager/${jobId}`); + ws.onmessage = (event) => { + try { + if (event.data.startsWith('{') && event.data.endsWith('}')) { + const data = JSON.parse(event.data); + + if (data.status === "failed") { + closeButton.classList = "text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:outline-none focus:ring-red-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-red-600 dark:hover:bg-red-700 dark:focus:ring-red-800"; + closeButton.textContent = 'Close'; + console.error(`Installation of ${appName} failed.`); + } else if (data.status === "done") { + closeButton.classList = "text-white bg-green-700 hover:bg-green-800 focus:ring-4 focus:outline-none focus:ring-green-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-green-600 dark:hover:bg-green-700 dark:focus:ring-green-800"; + closeButton.textContent = 'Install done'; + console.log(`Installation of ${appName} completed.`); + + } + } + else { + logsDiv.innerHTML += event.data + '\n'; + logsDiv.scrollTop = logsDiv.scrollHeight; + } + + + } catch { + logsDiv.innerHTML += event.data + '\n'; + logsDiv.scrollTop = logsDiv.scrollHeight; + } + }; + ws.onclose = async () => { + hfAppsStore.isInstalling = false; + hfAppsStore.refreshAppList(); + installedApps.refreshAppList(); + }; + }, +}; + + +window.addEventListener('load', async () => { + await hfAppsStore.refreshAppList(); +}); \ No newline at end of file diff --git a/src/reachy_mini/daemon/app/dashboard/static/js/daemon.js b/src/reachy_mini/daemon/app/dashboard/static/js/daemon.js new file mode 100644 index 00000000..4bcb67c7 --- /dev/null +++ b/src/reachy_mini/daemon/app/dashboard/static/js/daemon.js @@ -0,0 +1,169 @@ + + + +const daemon = { + currentStatus: { + state: null, + }, + + start: async (wakeUp) => { + await fetch(`/api/daemon/start?wake_up=${wakeUp}`, { + method: 'POST', + }) + .then((response) => { + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return response.json(); + }) + .then(async (data) => { + await daemon.checkStatusUpdate(); + }) + .catch((error) => { + console.error('Error starting daemon:', error); + }); + }, + + stop: async (gotoSleep) => { + await fetch(`/api/daemon/stop?goto_sleep=${gotoSleep}`, { + method: 'POST', + }) + .then((response) => { + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return response.json(); + }) + .then(async (data) => { + await daemon.checkStatusUpdate(); + }) + .catch((error) => { + console.error('Error stopping daemon:', error); + }); + }, + + getStatus: async () => { + await fetch('/api/daemon/status') + .then((response) => response.json()) + .then(async (data) => { + let currentState = daemon.currentStatus.state; + let newState = data.state || null; + + daemon.currentStatus = data; + + if (currentState === null || currentState !== newState) { + await daemon.updateUI(); + } + + }) + .catch((error) => { + console.error('Error fetching daemon status:', error); + }); + }, + + checkStatusUpdate: async (initialState) => { + await daemon.getStatus(); + + if (!initialState) { + initialState = daemon.currentStatus.state; + } + + let currentState = daemon.currentStatus.state; + + if (currentState !== "error" && (currentState === initialState || currentState === "starting" || currentState === "stopping")) { + setTimeout(() => { + daemon.checkStatusUpdate(initialState); + }, 500); + } + }, + + toggleSwitch: async () => { + const toggleDaemonSwitch = document.getElementById('daemon-toggle'); + + if (toggleDaemonSwitch.checked) { + console.log('Toggle switched ON. Starting daemon...'); + await daemon.start(true); + } else { + console.log('Toggle switched OFF. Stopping daemon...'); + await daemon.stop(true); + } + + await daemon.updateToggle(); + }, + + updateUI: async () => { + const daemonStatusAnim = document.getElementById('daemon-status-anim'); + const toggleDaemonSwitch = document.getElementById('daemon-toggle'); + const backendStatusIcon = document.getElementById('backend-status-icon'); + const backendStatusText = document.getElementById('backend-status-text'); + + let daemonState = daemon.currentStatus.state; + + toggleDaemonSwitch.disabled = false; + backendStatusIcon.classList.remove('bg-green-500', 'bg-yellow-500', 'bg-red-500'); + + if (daemonState === 'starting') { + // daemonStatusAnim.setAttribute('data', '/static/assets/reachy-mini-wake-up-animation.svg'); + daemonStatusAnim.setAttribute('data', '/static/assets/awake-cartoon.svg'); + toggleDaemonSwitch.disabled = true; + toggleDaemonSwitch.checked = true; + backendStatusIcon.classList.add('bg-yellow-500'); + backendStatusText.textContent = 'Waking up...'; + } + else if (daemonState === 'running') { + // daemonStatusAnim.setAttribute('data', '/static/assets/reachy-mini-awake.svg'); + daemonStatusAnim.setAttribute('data', '/static/assets/awake-cartoon-static.svg'); + toggleDaemonSwitch.checked = true; + backendStatusIcon.classList.add('bg-green-500'); + backendStatusText.textContent = 'Up and ready'; + } + else if (daemonState === 'stopping') { + // daemonStatusAnim.setAttribute('data', '/static/assets/reachy-mini-go-to-sleep-animation.svg'); + daemonStatusAnim.setAttribute('data', '/static/assets/go-to-sleep-cartoon.svg'); + toggleDaemonSwitch.disabled = true; + toggleDaemonSwitch.checked = false; + backendStatusIcon.classList.add('bg-yellow-500'); + backendStatusText.textContent = 'Going to sleep...'; + } + else if (daemonState === 'stopped' || daemonState === 'not_initialized') { + // daemonStatusAnim.setAttribute('data', '/static/assets/reachy-mini-sleeping.svg'); + daemonStatusAnim.setAttribute('data', '/static/assets/reachy-mini-sleeping-static.svg'); + toggleDaemonSwitch.checked = false; + backendStatusIcon.classList.add('bg-yellow-500'); + backendStatusText.textContent = 'Stopped'; + } + else if (daemonState === 'error') { + daemonStatusAnim.setAttribute('data', '/static/assets/no-wifi-cartoon.svg'); + toggleDaemonSwitch.checked = false; + backendStatusIcon.classList.add('bg-red-500'); + backendStatusText.textContent = 'Error occurred'; + + notificationCenter.showError(daemon.currentStatus.error); + } + + await daemon.updateToggle(); + }, + + updateToggle: async () => { + const toggle = document.getElementById('daemon-toggle'); + const toggleSlider = document.getElementById('daemon-toggle-slider'); + const toggleOnLabel = document.getElementById('daemon-toggle-on'); + const toggleOffLabel = document.getElementById('daemon-toggle-off'); + + toggleSlider.classList.remove('hidden'); + + if (toggle.checked) { + toggleOnLabel.classList.remove('hidden'); + toggleOffLabel.classList.add('hidden'); + } else { + toggleOnLabel.classList.add('hidden'); + toggleOffLabel.classList.remove('hidden'); + } + }, +}; + + +window.addEventListener('load', async () => { + document.getElementById('daemon-toggle').onchange = daemon.toggleSwitch; + await daemon.getStatus(); +}); \ No newline at end of file diff --git a/src/reachy_mini/daemon/app/dashboard/static/js/health_check.js b/src/reachy_mini/daemon/app/dashboard/static/js/health_check.js new file mode 100644 index 00000000..e0514b27 --- /dev/null +++ b/src/reachy_mini/daemon/app/dashboard/static/js/health_check.js @@ -0,0 +1,9 @@ +window.addEventListener("load", () => { + const healthCheckInterval = setInterval(async () => { + const response = await fetch("/health-check", { method: "POST" }); + if (!response.ok) { + console.error("Health check failed"); + clearInterval(healthCheckInterval); + } + }, 2500); +}); diff --git a/src/reachy_mini/daemon/app/dashboard/static/js/move_player.js b/src/reachy_mini/daemon/app/dashboard/static/js/move_player.js new file mode 100644 index 00000000..322a94e7 --- /dev/null +++ b/src/reachy_mini/daemon/app/dashboard/static/js/move_player.js @@ -0,0 +1,132 @@ +const updateMoveItems = async (dataset) => { + fetch(`/api/move/recorded-move-datasets/list/${dataset}`) + .then(response => response.json()) + .then(data => { + // Update the UI with the list of recorded moves + const moveSelectToggle = document.getElementById('move-select-toggle'); + moveSelectToggle.innerHTML = ''; + + data.forEach(moveName => { + const option = document.createElement('option'); + option.value = moveName; + option.textContent = moveName; + moveSelectToggle.appendChild(option); + }); + + }) + .catch(error => { + console.error(`Error fetching recorded moves for dataset '${dataset}':`, error); + }); +}; + +const movePlayer = { + playing: false, + currentMove: null, + + playRecordedMove: async (dataset, move) => { + console.log(`Requesting play move '${move}' from dataset '${dataset}'`); + + // movePlayer.playing = true; + // movePlayer.updateUI(); + + await fetch(`/api/move/play/recorded-move-dataset/${dataset}/${move}`, { + method: 'POST' + }).then(response => { + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return response.json(); + } + ).then(data => { + movePlayer.currentMove = data.uuid; + }).catch(error => { + console.error(`Error playing move '${move}' from dataset '${dataset}':`, error); + // movePlayer.playing = false; + // movePlayer.currentMove = null; + // movePlayer.updateUI(); + }); + }, + + stopMove: async () => { + console.log(`Requesting stop of current move`); + + await fetch(`/api/move/stop`, { + method: 'POST', + body: JSON.stringify({ uuid: movePlayer.currentMove }), + headers: { + 'Content-Type': 'application/json' + } + }); + }, + + updateUI: () => { + const movePlayBtn = document.getElementById('move-play-btn'); + const moveStopBtn = document.getElementById('move-stop-btn'); + + if (movePlayer.playing) { + movePlayBtn.disabled = true; + moveStopBtn.disabled = false; + } else { + movePlayBtn.disabled = false; + moveStopBtn.disabled = true; + } + }, + + checkMoveStatus: async () => { + let ws = new WebSocket(`ws://${window.location.host}/api/move/ws/updates`); + + ws.onmessage = (event) => { + const data = JSON.parse(event.data); + if (data.type === 'move_started') { + movePlayer.playing = true; + movePlayer.currentMove = data.move_id; + } + else if (data.type === 'move_completed') { + movePlayer.playing = false; + movePlayer.currentMove = null; + } + else if (data.type === 'move_failed' || data.type === 'move_cancelled') { + movePlayer.playing = false; + movePlayer.currentMove = null; + } + + movePlayer.updateUI(); + }; + + ws.onclose = () => { + console.log('Move status WebSocket closed, reconnecting in 1 second...'); + setTimeout(() => { + movePlayer.checkMoveStatus(); + }, 1000); + }; + } +}; + +window.addEventListener('DOMContentLoaded', (event) => { + const moveDatasetToggle = document.getElementById('move-dataset-toggle'); + + moveDatasetToggle.addEventListener('change', (_) => { + const selectedDataset = moveDatasetToggle.value; + updateMoveItems(selectedDataset); + }); + + const movePlayBtn = document.getElementById('move-play-btn'); + movePlayBtn.addEventListener('click', async () => { + const selectedDataset = moveDatasetToggle.value; + const moveSelectToggle = document.getElementById('move-select-toggle'); + const selectedMove = moveSelectToggle.value; + + await movePlayer.playRecordedMove(selectedDataset, selectedMove); + }); + + const moveStopBtn = document.getElementById('move-stop-btn'); + moveStopBtn.addEventListener('click', async () => { + await movePlayer.stopMove(); + }); + + // Initialize move items on page load + updateMoveItems(moveDatasetToggle.value); + + movePlayer.checkMoveStatus(); + movePlayer.updateUI(); +}); \ No newline at end of file diff --git a/src/reachy_mini/daemon/app/dashboard/static/js/notification.js b/src/reachy_mini/daemon/app/dashboard/static/js/notification.js new file mode 100644 index 00000000..69e11995 --- /dev/null +++ b/src/reachy_mini/daemon/app/dashboard/static/js/notification.js @@ -0,0 +1,41 @@ +const notificationCenter = { + + showInfo: (message, duration = 5000, autoclose = true) => { + notificationCenter.showNotification(message, false, duration, autoclose); + }, + showError: (message, duration = 5000, autoclose = false) => { + message = notificationCenter.errorTranslation(message); + notificationCenter.showNotification(message, true, duration, autoclose); + }, + + showNotification: (message, error, duration = 5000, autoclose = true) => { + document.getElementById('notification-message').textContent = message; + document.getElementById('notification-modal').style.display = 'block'; + + if (error) { + document.getElementById('notification-content').classList.add('error'); + document.getElementById('notification-content').classList.remove('info'); + } else { + document.getElementById('notification-content').classList.remove('error'); + document.getElementById('notification-content').classList.add('info'); + } + + if (autoclose) { + setTimeout(notificationCenter.closeNotification, duration); + } + }, + + closeNotification: () => { + document.getElementById('notification-modal').style.display = 'none'; + }, + + errorTranslation: (errorMessage) => { + if (errorMessage.includes('No Reachy Mini serial port found.')) { + return 'Reachy Mini not detected on USB. Please check that the USB cable is properly connected.'; + } + + console.log('No translation found for error message:', errorMessage); + return errorMessage; + }, + +}; \ No newline at end of file diff --git a/src/reachy_mini/daemon/app/dashboard/static/js/update.js b/src/reachy_mini/daemon/app/dashboard/static/js/update.js new file mode 100644 index 00000000..a1e68128 --- /dev/null +++ b/src/reachy_mini/daemon/app/dashboard/static/js/update.js @@ -0,0 +1,132 @@ +const updateManager = { + busy: false, + preRelease: false, + + checkForUpdate: async () => { + await updateManager.updateUI(); // Clear previous state + + await fetch('/update/available?pre_release=' + updateManager.preRelease) + .then(async response => { + if (!response.ok) { + return false; + } + const data = await response.json(); + await updateManager.updateUI(data); + }).catch(error => { + console.error('Error checking for updates:', error); + }); + }, + + startUpdate: async () => { + if (updateManager.busy) { + console.warn('An update is already in progress.'); + return; + } + updateManager.busy = true; + + fetch('/update/start?pre_release=' + updateManager.preRelease, { method: 'POST' }) + .then(response => { + if (response.ok) { + return response.json(); + } else { + return response.json().then(data => { + throw new Error(data.detail || 'Error starting update'); + }); + } + }) + .then(data => { + const jobId = data.job_id; + updateManager.connectLogsWebSocket(jobId); + }) + .catch(error => { + console.error('Error triggering update:', error); + updateManager.busy = false; + }); + }, + + connectLogsWebSocket: (jobId) => { + const updateModal = document.getElementById('update-modal'); + const updateModalTitle = document.getElementById('update-modal-title'); + const logsDiv = document.getElementById('update-logs'); + const closeButton = document.getElementById('update-modal-close-button'); + + updateModalTitle.textContent = 'Updating...'; + + closeButton.onclick = () => { + updateModal.classList.add('hidden'); + }; + + updateModal.classList.remove('hidden'); + + const ws = new WebSocket(`ws://${location.host}/update/ws/logs?job_id=${jobId}`); + + ws.onmessage = (event) => { + // console.log('Update log:', event); + logsDiv.innerHTML += event.data + '
      '; + logsDiv.scrollTop = logsDiv.scrollHeight; + }; + ws.onclose = async () => { + console.log('Update logs WebSocket closed'); + closeButton.classList.remove('hidden'); + closeButton.textContent = 'Close'; + updateModalTitle.textContent = 'Update Completed ✅'; + + updateManager.busy = false; + await updateManager.checkForUpdate(); + }; + }, + + updateUI: async (update) => { + // updateManager.updateMainPage(isUpdateAvailable); + updateManager.updateUpdatePage(update); + }, + + // updateMainPage: async (update) => { + // const daemonUpdateBtn = document.getElementById('daemon-update-btn'); + // if (!daemonUpdateBtn) return; + + // if (isUpdateAvailable) { + // daemonUpdateBtn.innerHTML = 'Update 1'; + // } else { + // daemonUpdateBtn.innerHTML = 'Update'; + // } + // }, + updateUpdatePage: async (data) => { + const statusElem = document.getElementById('update-status'); + if (!statusElem) return; + + const currentVersionElem = document.getElementById('current-version'); + const availableVersionElem = document.getElementById('available-version'); + const availableVersionContainer = document.getElementById('available-version-container'); + const startUpdateBtn = document.getElementById('start-update-btn'); + + if (!data || !data.update || !data.update.reachy_mini) { + statusElem.innerHTML = 'Checking for updates...'; + if (currentVersionElem) currentVersionElem.textContent = ''; + if (availableVersionElem) availableVersionElem.textContent = ''; + return; + } + + const updateInfo = data.update.reachy_mini; + const isUpdateAvailable = updateInfo.is_available; + const currentVersion = updateInfo.current_version || '-'; + const availableVersion = updateInfo.available_version || '-'; + + if (currentVersionElem) currentVersionElem.textContent = `Current version: ${currentVersion}`; + if (availableVersionElem) availableVersionElem.textContent = `Available version: ${availableVersion}`; + + if (isUpdateAvailable) { + statusElem.innerHTML = 'An update is available!'; + if (availableVersionContainer) availableVersionContainer.classList.remove('hidden'); + startUpdateBtn.classList.remove('hidden'); + } else { + statusElem.innerHTML = 'Your system is up to date.'; + if (availableVersionContainer) availableVersionContainer.classList.add('hidden'); + startUpdateBtn.classList.add('hidden'); + } + } +}; + +window.addEventListener('load', async () => { + await updateManager.checkForUpdate(); +}); \ No newline at end of file diff --git a/src/reachy_mini/daemon/app/dashboard/static/js/volume_control.js b/src/reachy_mini/daemon/app/dashboard/static/js/volume_control.js new file mode 100644 index 00000000..0b6fdbd0 --- /dev/null +++ b/src/reachy_mini/daemon/app/dashboard/static/js/volume_control.js @@ -0,0 +1,252 @@ +const volumeControl = { + currentVolume: 50, + device: 'unknown', + platform: 'unknown', + isUpdating: false, + + init: async () => { + const slider = document.getElementById('volume-slider'); + const valueLabel = document.getElementById('volume-value'); + const deviceInfo = document.getElementById('volume-device-info'); + + if (!slider || !valueLabel || !deviceInfo) { + console.warn('Volume control elements not found in DOM'); + return; + } + + try { + await volumeControl.loadCurrentVolume(); + } catch (error) { + console.error('Error loading current volume:', error); + deviceInfo.textContent = 'Error loading volume'; + } + + slider.addEventListener('input', (e) => { + const v = Number(e.target.value); + valueLabel.textContent = String(v); + }); + + slider.addEventListener('change', async (e) => { + const newVolume = Number(e.target.value); + if (!Number.isFinite(newVolume)) return; + await volumeControl.setVolume(newVolume); + }); + }, + + loadCurrentVolume: async () => { + try { + const response = await fetch('/api/volume/current'); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const data = await response.json(); + + const volume = Number(data.volume); + if (!Number.isFinite(volume)) { + throw new Error('Invalid volume in response'); + } + + volumeControl.currentVolume = volume; + volumeControl.device = data.device ?? 'unknown'; + volumeControl.platform = data.platform ?? 'unknown'; + + const slider = document.getElementById('volume-slider'); + const valueLabel = document.getElementById('volume-value'); + const deviceInfo = document.getElementById('volume-device-info'); + + if (slider) slider.value = String(volume); + if (valueLabel) valueLabel.textContent = String(volume); + if (deviceInfo) { + deviceInfo.textContent = `${volumeControl.platform} - ${volumeControl.device}`; + } + + console.log('Loaded volume:', data); + } catch (error) { + console.error('Error loading current volume:', error); + throw error; + } + }, + + setVolume: async (volume) => { + if (!Number.isFinite(volume)) { + console.warn('Ignoring invalid volume:', volume); + return; + } + + const safeVolume = Math.max(0, Math.min(100, volume)); + if (volumeControl.isUpdating) { + console.log('Volume update already in progress, skipping...'); + return; + } + + volumeControl.isUpdating = true; + const slider = document.getElementById('volume-slider'); + + if (slider) slider.disabled = true; + + try { + const response = await fetch('/api/volume/set', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ volume: safeVolume }), + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + const serverVolume = Number(data.volume); + + if (Number.isFinite(serverVolume)) { + volumeControl.currentVolume = serverVolume; + const s = document.getElementById('volume-slider'); + const valueLabel = document.getElementById('volume-value'); + if (s) s.value = String(serverVolume); + if (valueLabel) valueLabel.textContent = String(serverVolume); + } + + console.log('Volume set to:', serverVolume); + } catch (error) { + console.error('Error setting volume:', error); + try { + await volumeControl.loadCurrentVolume(); + } catch (loadError) { + console.error('Also failed to reload volume:', loadError); + } + } finally { + volumeControl.isUpdating = false; + const s = document.getElementById('volume-slider'); + if (s) s.disabled = false; + } + }, +}; + +const microphoneControl = { + currentVolume: 50, + device: 'unknown', + platform: 'unknown', + isUpdating: false, + + init: async () => { + const slider = document.getElementById('microphone-slider'); + const valueLabel = document.getElementById('microphone-value'); + const deviceInfo = document.getElementById('microphone-device-info'); + + if (!slider || !valueLabel || !deviceInfo) { + console.warn('Microphone control elements not found in DOM'); + return; + } + + try { + await microphoneControl.loadCurrentVolume(); + } catch (error) { + console.error('Error loading current microphone volume:', error); + deviceInfo.textContent = 'Error loading microphone'; + } + + slider.addEventListener('input', (e) => { + const v = Number(e.target.value); + valueLabel.textContent = String(v); + }); + + slider.addEventListener('change', async (e) => { + const newVolume = Number(e.target.value); + if (!Number.isFinite(newVolume)) return; + await microphoneControl.setVolume(newVolume); + }); + }, + + loadCurrentVolume: async () => { + try { + const response = await fetch('/api/volume/microphone/current'); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const data = await response.json(); + + const volume = Number(data.volume); + if (!Number.isFinite(volume)) { + throw new Error('Invalid microphone volume in response'); + } + + microphoneControl.currentVolume = volume; + microphoneControl.device = data.device ?? 'unknown'; + microphoneControl.platform = data.platform ?? 'unknown'; + + const slider = document.getElementById('microphone-slider'); + const valueLabel = document.getElementById('microphone-value'); + const deviceInfo = document.getElementById('microphone-device-info'); + + if (slider) slider.value = String(volume); + if (valueLabel) valueLabel.textContent = String(volume); + if (deviceInfo) { + deviceInfo.textContent = `${microphoneControl.platform} - ${microphoneControl.device}`; + } + + console.log('Loaded microphone volume:', data); + } catch (error) { + console.error('Error loading current microphone volume:', error); + throw error; + } + }, + + setVolume: async (volume) => { + if (!Number.isFinite(volume)) { + console.warn('Ignoring invalid microphone volume:', volume); + return; + } + + const safeVolume = Math.max(0, Math.min(100, volume)); + if (microphoneControl.isUpdating) { + console.log('Microphone volume update already in progress, skipping...'); + return; + } + + microphoneControl.isUpdating = true; + const slider = document.getElementById('microphone-slider'); + + if (slider) slider.disabled = true; + + try { + const response = await fetch('/api/volume/microphone/set', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ volume: safeVolume }), + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + const serverVolume = Number(data.volume); + + if (Number.isFinite(serverVolume)) { + microphoneControl.currentVolume = serverVolume; + const s = document.getElementById('microphone-slider'); + const valueLabel = document.getElementById('microphone-value'); + if (s) s.value = String(serverVolume); + if (valueLabel) valueLabel.textContent = String(serverVolume); + } + + console.log('Microphone volume set to:', serverVolume); + } catch (error) { + console.error('Error setting microphone volume:', error); + try { + await microphoneControl.loadCurrentVolume(); + } catch (loadError) { + console.error('Also failed to reload microphone volume:', loadError); + } + } finally { + microphoneControl.isUpdating = false; + const s = document.getElementById('microphone-slider'); + if (s) s.disabled = false; + } + }, +}; + +window.addEventListener('DOMContentLoaded', () => { + volumeControl.init(); + microphoneControl.init(); +}); diff --git a/src/reachy_mini/daemon/app/dashboard/static/js/wifi.js b/src/reachy_mini/daemon/app/dashboard/static/js/wifi.js new file mode 100644 index 00000000..3a620a40 --- /dev/null +++ b/src/reachy_mini/daemon/app/dashboard/static/js/wifi.js @@ -0,0 +1,176 @@ + +const getStatus = async () => { + return await fetch('/wifi/status') + .then(response => response.json()) + .catch(error => { + console.error('Error fetching WiFi status:', error); + return { mode: 'error' }; + }); +}; + +const refreshStatus = async () => { + const status = await getStatus(); + handleStatus(status); + + await fetch('/wifi/error') + .then(response => response.json()) + .then(data => { + if (data.error !== null) { + console.log('Error data:', data); + alert(`Error while trying to connect: ${data.error}.\n Switching back to hotspot mode.`); + fetch('/wifi/reset_error', { method: 'POST' }); + } + }) + .catch(error => { + console.error('Error fetching WiFi error:', error); + }); +}; + +const scanAndListWifiNetworks = async () => { + await fetch('/wifi/scan_and_list', { method: 'POST' }) + .then(response => response.json()) + .then(data => { + const ssidSelect = document.getElementById('ssid'); + data.forEach(ssid => { + const option = document.createElement('option'); + option.value = ssid; + option.textContent = ssid; + ssidSelect.appendChild(option); + }); + }) + .catch(() => { + const ssidSelect = document.getElementById('ssid'); + const option = document.createElement('option'); + option.value = ""; + option.textContent = "Unable to load networks"; + ssidSelect.appendChild(option); + }); +}; + +const connectToWifi = (_) => { + const ssid = document.getElementById('ssid').value; + const password = document.getElementById('password').value; + + if (!ssid) { + alert('Please enter an SSID.'); + return; + } + + fetch(`/wifi/connect?ssid=${encodeURIComponent(ssid)}&password=${encodeURIComponent(password)}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + }) + .then(response => { + if (!response.ok) { + return response.json().then(errData => { + throw new Error(errData.detail || 'Failed to connect to WiFi'); + }); + } + + // Clear the form fields + document.getElementById('ssid').value = ''; + document.getElementById('password').value = ''; + + return response.json(); + }) + .then(data => { + handleStatus({ mode: 'busy' }); + }) + .catch(error => { + console.error('Error connecting to WiFi:', error); + alert(`Error connecting to WiFi: ${error.message}`); + }); + return false; // Prevent form submission +}; + +let currentMode = null; + +const handleStatus = (status) => { + const statusDiv = document.getElementById('wifi-status'); + + const knownNetworksDiv = document.getElementById('known-networks'); + const knownNetworksList = document.getElementById('known-networks-list'); + knownNetworksDiv.classList.remove('hidden'); + + const mode = status.mode; + + knownNetworksList.innerHTML = ''; + if (status.known_networks !== undefined && Array.isArray(status.known_networks)) { + status.known_networks.forEach((network) => { + const li = document.createElement('li'); + li.classList = 'flex flex-row items-center mb-1 gap-4 justify-left'; + + const nameSpan = document.createElement('span'); + nameSpan.innerText = network; + li.appendChild(nameSpan); + + // const removeBtn = document.createElement('span'); + // removeBtn.innerText = ' (remove ❌)'; + // removeBtn.style.cursor = 'pointer'; + // removeBtn.title = 'Remove network'; + // removeBtn.onclick = async () => { + // if (confirm(`Remove network '${network}'?`)) { + // removeNetwork(network); + // } + // }; + // li.appendChild(removeBtn); + + knownNetworksList.appendChild(li); + }); + } + + if (mode == 'hotspot') { + statusDiv.innerText = 'Hotspot mode active. 🔌'; + + } else if (mode == 'wlan') { + if (currentMode !== null && currentMode !== 'wlan') { + alert(`Successfully connected to WiFi network: ${status.connected_network} ✅`); + } + + statusDiv.innerText = `Connected to WiFi (SSID: ${status.connected_network}). 📶`; + + } else if (mode == 'disconnected') { + statusDiv.innerText = 'WiFi disconnected. ❌'; + } else if (mode == 'busy') { + statusDiv.innerText = 'Changing your WiFi configuration... Please wait ⏳'; + } else if (mode == 'error') { + statusDiv.innerText = 'Error connecting to WiFi. ⚠️'; + } else { + console.warn(`Unknown status: ${status}`); + } + + currentMode = mode; +}; + +const removeNetwork = async (ssid) => { + const status = await getStatus(); + + // TODO: + // if ssid !== status.connected_network: + // remove connection + // else: + // refresh nmcli? go back to hotspot if needed? +}; + +const cleanAndRefresh = async () => { + const statusDiv = document.getElementById('wifi-status'); + statusDiv.innerText = 'Checking WiFi configuration...'; + + const knownNetworksDiv = document.getElementById('known-networks'); + knownNetworksDiv.classList.add('hidden'); + + const addWifi = document.getElementById('add-wifi'); + addWifi.classList.add('hidden'); + + await scanAndListWifiNetworks(); + await refreshStatus(); + + addWifi.classList.remove('hidden'); +}; + +window.addEventListener('load', async () => { + await cleanAndRefresh(); + setInterval(refreshStatus, 1000); +}); \ No newline at end of file diff --git a/src/reachy_mini/daemon/app/dashboard/style.css b/src/reachy_mini/daemon/app/dashboard/style.css deleted file mode 100644 index d59674bd..00000000 --- a/src/reachy_mini/daemon/app/dashboard/style.css +++ /dev/null @@ -1,37 +0,0 @@ -body { - font-family: Arial, sans-serif; - background: #f7f7f7; - margin: 0; - padding: 0; -} - -h1 { - background: #2d3e50; - color: #fff; - padding: 1em; - margin: 0 0 1em 0; -} - -ul#examples-list { - list-style: none; - padding: 0; - margin: 2em; -} - -ul#examples-list li { - background: #fff; - margin-bottom: 1em; - padding: 1em; - border-radius: 6px; - box-shadow: 0 2px 6px rgba(0, 0, 0, 0.07); -} - -ul#examples-list li a { - color: #2d3e50; - text-decoration: none; - font-weight: bold; -} - -ul#examples-list li a:hover { - text-decoration: underline; -} \ No newline at end of file diff --git a/src/reachy_mini/daemon/app/dashboard/tailwindcss/.gitignore b/src/reachy_mini/daemon/app/dashboard/tailwindcss/.gitignore new file mode 100644 index 00000000..b512c09d --- /dev/null +++ b/src/reachy_mini/daemon/app/dashboard/tailwindcss/.gitignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/src/reachy_mini/daemon/app/dashboard/tailwindcss/README.md b/src/reachy_mini/daemon/app/dashboard/tailwindcss/README.md new file mode 100644 index 00000000..3d7fed0f --- /dev/null +++ b/src/reachy_mini/daemon/app/dashboard/tailwindcss/README.md @@ -0,0 +1,15 @@ +# Generate static Tailwind CSS + +## Requirements + +(From this folder) + +```bash +pnpm install -D tailwindcss @tailwindcss/cli +``` + +## Generate CSS + +```bash +npx @tailwindcss/cli -i ./styles/app.css -o ../static/css/app.css +``` \ No newline at end of file diff --git a/src/reachy_mini/daemon/app/dashboard/tailwindcss/package-lock.json b/src/reachy_mini/daemon/app/dashboard/tailwindcss/package-lock.json new file mode 100644 index 00000000..a3f0c764 --- /dev/null +++ b/src/reachy_mini/daemon/app/dashboard/tailwindcss/package-lock.json @@ -0,0 +1,1127 @@ +{ + "name": "tailwindcss", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "devDependencies": { + "@tailwindcss/cli": "^4.1.16", + "tailwindcss": "^4.1.16" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", + "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.1", + "@parcel/watcher-darwin-arm64": "2.5.1", + "@parcel/watcher-darwin-x64": "2.5.1", + "@parcel/watcher-freebsd-x64": "2.5.1", + "@parcel/watcher-linux-arm-glibc": "2.5.1", + "@parcel/watcher-linux-arm-musl": "2.5.1", + "@parcel/watcher-linux-arm64-glibc": "2.5.1", + "@parcel/watcher-linux-arm64-musl": "2.5.1", + "@parcel/watcher-linux-x64-glibc": "2.5.1", + "@parcel/watcher-linux-x64-musl": "2.5.1", + "@parcel/watcher-win32-arm64": "2.5.1", + "@parcel/watcher-win32-ia32": "2.5.1", + "@parcel/watcher-win32-x64": "2.5.1" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", + "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", + "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", + "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", + "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", + "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", + "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", + "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", + "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", + "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", + "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", + "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", + "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", + "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/cli": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/cli/-/cli-4.1.16.tgz", + "integrity": "sha512-dsnANPrh2ZooHyZ/8uJhc9ecpcYtufToc21NY09NS9vF16rxPCjJ8dP7TUAtPqlUJTHSmRkN2hCdoYQIlgh4fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/watcher": "^2.5.1", + "@tailwindcss/node": "4.1.16", + "@tailwindcss/oxide": "4.1.16", + "enhanced-resolve": "^5.18.3", + "mri": "^1.2.0", + "picocolors": "^1.1.1", + "tailwindcss": "4.1.16" + }, + "bin": { + "tailwindcss": "dist/index.mjs" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.16.tgz", + "integrity": "sha512-BX5iaSsloNuvKNHRN3k2RcCuTEgASTo77mofW0vmeHkfrDWaoFAFvNHpEgtu0eqyypcyiBkDWzSMxJhp3AUVcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "enhanced-resolve": "^5.18.3", + "jiti": "^2.6.1", + "lightningcss": "1.30.2", + "magic-string": "^0.30.19", + "source-map-js": "^1.2.1", + "tailwindcss": "4.1.16" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.16.tgz", + "integrity": "sha512-2OSv52FRuhdlgyOQqgtQHuCgXnS8nFSYRp2tJ+4WZXKgTxqPy7SMSls8c3mPT5pkZ17SBToGM5LHEJBO7miEdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.1.16", + "@tailwindcss/oxide-darwin-arm64": "4.1.16", + "@tailwindcss/oxide-darwin-x64": "4.1.16", + "@tailwindcss/oxide-freebsd-x64": "4.1.16", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.16", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.16", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.16", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.16", + "@tailwindcss/oxide-linux-x64-musl": "4.1.16", + "@tailwindcss/oxide-wasm32-wasi": "4.1.16", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.16", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.16" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.16.tgz", + "integrity": "sha512-8+ctzkjHgwDJ5caq9IqRSgsP70xhdhJvm+oueS/yhD5ixLhqTw9fSL1OurzMUhBwE5zK26FXLCz2f/RtkISqHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.16.tgz", + "integrity": "sha512-C3oZy5042v2FOALBZtY0JTDnGNdS6w7DxL/odvSny17ORUnaRKhyTse8xYi3yKGyfnTUOdavRCdmc8QqJYwFKA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.16.tgz", + "integrity": "sha512-vjrl/1Ub9+JwU6BP0emgipGjowzYZMjbWCDqwA2Z4vCa+HBSpP4v6U2ddejcHsolsYxwL5r4bPNoamlV0xDdLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.16.tgz", + "integrity": "sha512-TSMpPYpQLm+aR1wW5rKuUuEruc/oOX3C7H0BTnPDn7W/eMw8W+MRMpiypKMkXZfwH8wqPIRKppuZoedTtNj2tg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.16.tgz", + "integrity": "sha512-p0GGfRg/w0sdsFKBjMYvvKIiKy/LNWLWgV/plR4lUgrsxFAoQBFrXkZ4C0w8IOXfslB9vHK/JGASWD2IefIpvw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.16.tgz", + "integrity": "sha512-DoixyMmTNO19rwRPdqviTrG1rYzpxgyYJl8RgQvdAQUzxC1ToLRqtNJpU/ATURSKgIg6uerPw2feW0aS8SNr/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.16.tgz", + "integrity": "sha512-H81UXMa9hJhWhaAUca6bU2wm5RRFpuHImrwXBUvPbYb+3jo32I9VIwpOX6hms0fPmA6f2pGVlybO6qU8pF4fzQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.16.tgz", + "integrity": "sha512-ZGHQxDtFC2/ruo7t99Qo2TTIvOERULPl5l0K1g0oK6b5PGqjYMga+FcY1wIUnrUxY56h28FxybtDEla+ICOyew==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.16.tgz", + "integrity": "sha512-Oi1tAaa0rcKf1Og9MzKeINZzMLPbhxvm7rno5/zuP1WYmpiG0bEHq4AcRUiG2165/WUzvxkW4XDYCscZWbTLZw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.16.tgz", + "integrity": "sha512-B01u/b8LteGRwucIBmCQ07FVXLzImWESAIMcUU6nvFt/tYsQ6IHz8DmZ5KtvmwxD+iTYBtM1xwoGXswnlu9v0Q==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.5.0", + "@emnapi/runtime": "^1.5.0", + "@emnapi/wasi-threads": "^1.1.0", + "@napi-rs/wasm-runtime": "^1.0.7", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.16.tgz", + "integrity": "sha512-zX+Q8sSkGj6HKRTMJXuPvOcP8XfYON24zJBRPlszcH1Np7xuHXhWn8qfFjIujVzvH3BHU+16jBXwgpl20i+v9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.16.tgz", + "integrity": "sha512-m5dDFJUEejbFqP+UXVstd4W/wnxA4F61q8SoL+mqTypId2T2ZpuxosNSgowiCnLp2+Z+rivdU0AqpfgiD7yCBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.18.3", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", + "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/lightningcss": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", + "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.30.2", + "lightningcss-darwin-arm64": "1.30.2", + "lightningcss-darwin-x64": "1.30.2", + "lightningcss-freebsd-x64": "1.30.2", + "lightningcss-linux-arm-gnueabihf": "1.30.2", + "lightningcss-linux-arm64-gnu": "1.30.2", + "lightningcss-linux-arm64-musl": "1.30.2", + "lightningcss-linux-x64-gnu": "1.30.2", + "lightningcss-linux-x64-musl": "1.30.2", + "lightningcss-win32-arm64-msvc": "1.30.2", + "lightningcss-win32-x64-msvc": "1.30.2" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", + "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", + "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", + "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", + "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", + "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", + "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz", + "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", + "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", + "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", + "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz", + "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss/node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tailwindcss": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.16.tgz", + "integrity": "sha512-pONL5awpaQX4LN5eiv7moSiSPd/DLDzKVRJz8Q9PgzmAdd1R4307GQS2ZpfiN7ZmekdQrfhZZiSE5jkLR4WNaA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + } + } +} diff --git a/src/reachy_mini/daemon/app/dashboard/tailwindcss/package.json b/src/reachy_mini/daemon/app/dashboard/tailwindcss/package.json new file mode 100644 index 00000000..9e9ee28e --- /dev/null +++ b/src/reachy_mini/daemon/app/dashboard/tailwindcss/package.json @@ -0,0 +1,6 @@ +{ + "devDependencies": { + "@tailwindcss/cli": "^4.1.16", + "tailwindcss": "^4.1.16" + } +} diff --git a/src/reachy_mini/daemon/app/dashboard/tailwindcss/pnpm-lock.yaml b/src/reachy_mini/daemon/app/dashboard/tailwindcss/pnpm-lock.yaml new file mode 100644 index 00000000..1211930a --- /dev/null +++ b/src/reachy_mini/daemon/app/dashboard/tailwindcss/pnpm-lock.yaml @@ -0,0 +1,608 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + '@tailwindcss/cli': + specifier: ^4.1.16 + version: 4.1.16 + tailwindcss: + specifier: ^4.1.16 + version: 4.1.16 + +packages: + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@parcel/watcher-android-arm64@2.5.1': + resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [android] + + '@parcel/watcher-darwin-arm64@2.5.1': + resolution: {integrity: sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [darwin] + + '@parcel/watcher-darwin-x64@2.5.1': + resolution: {integrity: sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [darwin] + + '@parcel/watcher-freebsd-x64@2.5.1': + resolution: {integrity: sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [freebsd] + + '@parcel/watcher-linux-arm-glibc@2.5.1': + resolution: {integrity: sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + + '@parcel/watcher-linux-arm-musl@2.5.1': + resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + + '@parcel/watcher-linux-arm64-glibc@2.5.1': + resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + + '@parcel/watcher-linux-arm64-musl@2.5.1': + resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + + '@parcel/watcher-linux-x64-glibc@2.5.1': + resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + + '@parcel/watcher-linux-x64-musl@2.5.1': + resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + + '@parcel/watcher-win32-arm64@2.5.1': + resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [win32] + + '@parcel/watcher-win32-ia32@2.5.1': + resolution: {integrity: sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==} + engines: {node: '>= 10.0.0'} + cpu: [ia32] + os: [win32] + + '@parcel/watcher-win32-x64@2.5.1': + resolution: {integrity: sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [win32] + + '@parcel/watcher@2.5.1': + resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==} + engines: {node: '>= 10.0.0'} + + '@tailwindcss/cli@4.1.16': + resolution: {integrity: sha512-dsnANPrh2ZooHyZ/8uJhc9ecpcYtufToc21NY09NS9vF16rxPCjJ8dP7TUAtPqlUJTHSmRkN2hCdoYQIlgh4fw==} + hasBin: true + + '@tailwindcss/node@4.1.16': + resolution: {integrity: sha512-BX5iaSsloNuvKNHRN3k2RcCuTEgASTo77mofW0vmeHkfrDWaoFAFvNHpEgtu0eqyypcyiBkDWzSMxJhp3AUVcw==} + + '@tailwindcss/oxide-android-arm64@4.1.16': + resolution: {integrity: sha512-8+ctzkjHgwDJ5caq9IqRSgsP70xhdhJvm+oueS/yhD5ixLhqTw9fSL1OurzMUhBwE5zK26FXLCz2f/RtkISqHA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.1.16': + resolution: {integrity: sha512-C3oZy5042v2FOALBZtY0JTDnGNdS6w7DxL/odvSny17ORUnaRKhyTse8xYi3yKGyfnTUOdavRCdmc8QqJYwFKA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.1.16': + resolution: {integrity: sha512-vjrl/1Ub9+JwU6BP0emgipGjowzYZMjbWCDqwA2Z4vCa+HBSpP4v6U2ddejcHsolsYxwL5r4bPNoamlV0xDdLg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.1.16': + resolution: {integrity: sha512-TSMpPYpQLm+aR1wW5rKuUuEruc/oOX3C7H0BTnPDn7W/eMw8W+MRMpiypKMkXZfwH8wqPIRKppuZoedTtNj2tg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.16': + resolution: {integrity: sha512-p0GGfRg/w0sdsFKBjMYvvKIiKy/LNWLWgV/plR4lUgrsxFAoQBFrXkZ4C0w8IOXfslB9vHK/JGASWD2IefIpvw==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.16': + resolution: {integrity: sha512-DoixyMmTNO19rwRPdqviTrG1rYzpxgyYJl8RgQvdAQUzxC1ToLRqtNJpU/ATURSKgIg6uerPw2feW0aS8SNr/w==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-musl@4.1.16': + resolution: {integrity: sha512-H81UXMa9hJhWhaAUca6bU2wm5RRFpuHImrwXBUvPbYb+3jo32I9VIwpOX6hms0fPmA6f2pGVlybO6qU8pF4fzQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-gnu@4.1.16': + resolution: {integrity: sha512-ZGHQxDtFC2/ruo7t99Qo2TTIvOERULPl5l0K1g0oK6b5PGqjYMga+FcY1wIUnrUxY56h28FxybtDEla+ICOyew==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-musl@4.1.16': + resolution: {integrity: sha512-Oi1tAaa0rcKf1Og9MzKeINZzMLPbhxvm7rno5/zuP1WYmpiG0bEHq4AcRUiG2165/WUzvxkW4XDYCscZWbTLZw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-wasm32-wasi@4.1.16': + resolution: {integrity: sha512-B01u/b8LteGRwucIBmCQ07FVXLzImWESAIMcUU6nvFt/tYsQ6IHz8DmZ5KtvmwxD+iTYBtM1xwoGXswnlu9v0Q==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.16': + resolution: {integrity: sha512-zX+Q8sSkGj6HKRTMJXuPvOcP8XfYON24zJBRPlszcH1Np7xuHXhWn8qfFjIujVzvH3BHU+16jBXwgpl20i+v9A==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.1.16': + resolution: {integrity: sha512-m5dDFJUEejbFqP+UXVstd4W/wnxA4F61q8SoL+mqTypId2T2ZpuxosNSgowiCnLp2+Z+rivdU0AqpfgiD7yCBg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.1.16': + resolution: {integrity: sha512-2OSv52FRuhdlgyOQqgtQHuCgXnS8nFSYRp2tJ+4WZXKgTxqPy7SMSls8c3mPT5pkZ17SBToGM5LHEJBO7miEdg==} + engines: {node: '>= 10'} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + detect-libc@1.0.3: + resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==} + engines: {node: '>=0.10'} + hasBin: true + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + enhanced-resolve@5.18.3: + resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} + engines: {node: '>=10.13.0'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + hasBin: true + + lightningcss-android-arm64@1.30.2: + resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.30.2: + resolution: {integrity: sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.30.2: + resolution: {integrity: sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.30.2: + resolution: {integrity: sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.30.2: + resolution: {integrity: sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.30.2: + resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.30.2: + resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.30.2: + resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.30.2: + resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.30.2: + resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.30.2: + resolution: {integrity: sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.30.2: + resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==} + engines: {node: '>= 12.0.0'} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mri@1.2.0: + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} + engines: {node: '>=4'} + + node-addon-api@7.1.1: + resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + tailwindcss@4.1.16: + resolution: {integrity: sha512-pONL5awpaQX4LN5eiv7moSiSPd/DLDzKVRJz8Q9PgzmAdd1R4307GQS2ZpfiN7ZmekdQrfhZZiSE5jkLR4WNaA==} + + tapable@2.3.0: + resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} + engines: {node: '>=6'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + +snapshots: + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@parcel/watcher-android-arm64@2.5.1': + optional: true + + '@parcel/watcher-darwin-arm64@2.5.1': + optional: true + + '@parcel/watcher-darwin-x64@2.5.1': + optional: true + + '@parcel/watcher-freebsd-x64@2.5.1': + optional: true + + '@parcel/watcher-linux-arm-glibc@2.5.1': + optional: true + + '@parcel/watcher-linux-arm-musl@2.5.1': + optional: true + + '@parcel/watcher-linux-arm64-glibc@2.5.1': + optional: true + + '@parcel/watcher-linux-arm64-musl@2.5.1': + optional: true + + '@parcel/watcher-linux-x64-glibc@2.5.1': + optional: true + + '@parcel/watcher-linux-x64-musl@2.5.1': + optional: true + + '@parcel/watcher-win32-arm64@2.5.1': + optional: true + + '@parcel/watcher-win32-ia32@2.5.1': + optional: true + + '@parcel/watcher-win32-x64@2.5.1': + optional: true + + '@parcel/watcher@2.5.1': + dependencies: + detect-libc: 1.0.3 + is-glob: 4.0.3 + micromatch: 4.0.8 + node-addon-api: 7.1.1 + optionalDependencies: + '@parcel/watcher-android-arm64': 2.5.1 + '@parcel/watcher-darwin-arm64': 2.5.1 + '@parcel/watcher-darwin-x64': 2.5.1 + '@parcel/watcher-freebsd-x64': 2.5.1 + '@parcel/watcher-linux-arm-glibc': 2.5.1 + '@parcel/watcher-linux-arm-musl': 2.5.1 + '@parcel/watcher-linux-arm64-glibc': 2.5.1 + '@parcel/watcher-linux-arm64-musl': 2.5.1 + '@parcel/watcher-linux-x64-glibc': 2.5.1 + '@parcel/watcher-linux-x64-musl': 2.5.1 + '@parcel/watcher-win32-arm64': 2.5.1 + '@parcel/watcher-win32-ia32': 2.5.1 + '@parcel/watcher-win32-x64': 2.5.1 + + '@tailwindcss/cli@4.1.16': + dependencies: + '@parcel/watcher': 2.5.1 + '@tailwindcss/node': 4.1.16 + '@tailwindcss/oxide': 4.1.16 + enhanced-resolve: 5.18.3 + mri: 1.2.0 + picocolors: 1.1.1 + tailwindcss: 4.1.16 + + '@tailwindcss/node@4.1.16': + dependencies: + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.18.3 + jiti: 2.6.1 + lightningcss: 1.30.2 + magic-string: 0.30.21 + source-map-js: 1.2.1 + tailwindcss: 4.1.16 + + '@tailwindcss/oxide-android-arm64@4.1.16': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.1.16': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.1.16': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.1.16': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.16': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.16': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.1.16': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.1.16': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.1.16': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.1.16': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.16': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.1.16': + optional: true + + '@tailwindcss/oxide@4.1.16': + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.1.16 + '@tailwindcss/oxide-darwin-arm64': 4.1.16 + '@tailwindcss/oxide-darwin-x64': 4.1.16 + '@tailwindcss/oxide-freebsd-x64': 4.1.16 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.16 + '@tailwindcss/oxide-linux-arm64-gnu': 4.1.16 + '@tailwindcss/oxide-linux-arm64-musl': 4.1.16 + '@tailwindcss/oxide-linux-x64-gnu': 4.1.16 + '@tailwindcss/oxide-linux-x64-musl': 4.1.16 + '@tailwindcss/oxide-wasm32-wasi': 4.1.16 + '@tailwindcss/oxide-win32-arm64-msvc': 4.1.16 + '@tailwindcss/oxide-win32-x64-msvc': 4.1.16 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + detect-libc@1.0.3: {} + + detect-libc@2.1.2: {} + + enhanced-resolve@5.18.3: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.0 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + graceful-fs@4.2.11: {} + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + jiti@2.6.1: {} + + lightningcss-android-arm64@1.30.2: + optional: true + + lightningcss-darwin-arm64@1.30.2: + optional: true + + lightningcss-darwin-x64@1.30.2: + optional: true + + lightningcss-freebsd-x64@1.30.2: + optional: true + + lightningcss-linux-arm-gnueabihf@1.30.2: + optional: true + + lightningcss-linux-arm64-gnu@1.30.2: + optional: true + + lightningcss-linux-arm64-musl@1.30.2: + optional: true + + lightningcss-linux-x64-gnu@1.30.2: + optional: true + + lightningcss-linux-x64-musl@1.30.2: + optional: true + + lightningcss-win32-arm64-msvc@1.30.2: + optional: true + + lightningcss-win32-x64-msvc@1.30.2: + optional: true + + lightningcss@1.30.2: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.30.2 + lightningcss-darwin-arm64: 1.30.2 + lightningcss-darwin-x64: 1.30.2 + lightningcss-freebsd-x64: 1.30.2 + lightningcss-linux-arm-gnueabihf: 1.30.2 + lightningcss-linux-arm64-gnu: 1.30.2 + lightningcss-linux-arm64-musl: 1.30.2 + lightningcss-linux-x64-gnu: 1.30.2 + lightningcss-linux-x64-musl: 1.30.2 + lightningcss-win32-arm64-msvc: 1.30.2 + lightningcss-win32-x64-msvc: 1.30.2 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mri@1.2.0: {} + + node-addon-api@7.1.1: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + source-map-js@1.2.1: {} + + tailwindcss@4.1.16: {} + + tapable@2.3.0: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 diff --git a/src/reachy_mini/daemon/app/dashboard/tailwindcss/postcss.config.js b/src/reachy_mini/daemon/app/dashboard/tailwindcss/postcss.config.js new file mode 100644 index 00000000..33ad091d --- /dev/null +++ b/src/reachy_mini/daemon/app/dashboard/tailwindcss/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/src/reachy_mini/daemon/app/dashboard/tailwindcss/styles/app.css b/src/reachy_mini/daemon/app/dashboard/tailwindcss/styles/app.css new file mode 100644 index 00000000..8c28fb10 --- /dev/null +++ b/src/reachy_mini/daemon/app/dashboard/tailwindcss/styles/app.css @@ -0,0 +1,185 @@ +@import "tailwindcss"; + +@source "../../templates/**/*.html"; +@source "../../static/js/**/*.js"; + +@font-face { + font-family: 'Asap'; + src: url('/static/fonts/Asap-VariableFont_wdth,wght.ttf') format('truetype'); + font-weight: 100 900; + font-style: normal; +} + +@font-face { + font-family: 'Asap'; + src: url('/static/fonts/Asap-Italic-VariableFont_wdth,wght.ttf') format('truetype'); + font-weight: 100 900; + font-style: italic; +} + +@font-face { + font-family: 'Archivo'; + src: url('/static/fonts/Archivo-VariableFont_wdth,wght.ttf') format('truetype'); + font-weight: 100 900; + font-style: normal; +} + +@font-face { + font-family: 'Archivo'; + src: url('/static/fonts/Archivo-Italic-VariableFont_wdth,wght.ttf') format('truetype'); + font-weight: 100 900; + font-style: italic; +} + + + +html { + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +body { + font-family: 'Archivo', 'Asap', sans-serif; +} + +.daemon-status-title { + font-size: 36px; + font-weight: 500; + letter-spacing: 0.3px; +} + +.daemon-status-collapse { + font-family: 'Asap', sans-serif; + font-weight: 400; + color: rgba(0, 0, 0, 0.7); +} + +/* APPs styles */ + +.app-section { + border-radius: 8px; + border-width: 1px; + padding: 16px; + gap: 16px; + background: #FFFFFF; +} + +.installed-app-title { + font-weight: 600; +} + +/* Hugging Face App Store Styles */ + +.app-section-title { + font-weight: 500; + font-size: 24px; + letter-spacing: 0px; +} + +.app-list-item { + padding: 4px; +} + +.hf-app-icon { + width: 42px; + height: 42px; + border-radius: 10px; + text-align: center; + font-size: 31px; + background-color: rgba(0, 0, 0, 0.1); + +} + +.hf-app-title { + font-weight: 500; + font-size: 18px; +} + +.hf-app-description { + font-family: 'Asap', sans-serif; + font-weight: 400; + font-size: 16px; + line-height: 75%; + color: rgba(0, 0, 0, 0.4); +} + +.hf-app-install-button { + font-weight: 500; + font-size: 20px; + font-style: Bold; + text-align: center; + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; +} + +.wifi-status { + font-family: 'Asap', sans-serif; + font-weight: 500; + color: rgba(0, 0, 0, 0.9); + +} + +.wifi-status-info { + font-family: 'Asap', sans-serif; + font-weight: 400; + color: rgba(0, 0, 0, 0.7); +} + + +.notification-modal { + position: fixed; + bottom: 24px; + right: 24px; + z-index: 9999; + min-width: 300px; + max-width: 400px; + background: #fff; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2); + border-radius: 8px; + display: none; + animation: fadeInUp 0.3s; +} + +@keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(40px); + } + + to { + opacity: 1; + transform: translateY(0); + } +} + +.notification-content { + padding: 20px 28px; + border-radius: 8px; + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.15); + display: flex; + align-items: center; + justify-content: space-between; +} + +/* Notification variants */ +.notification-content.info { + background: #fff; + border: 1px solid #e0e0e0; +} + +.notification-content.error { + background: #ffeaea; + border: 1px solid #ff4d4f; + color: #b71c1c; +} + + +.close-btn { + background: none; + border: none; + font-size: 1.5em; + cursor: pointer; + color: #888; +} \ No newline at end of file diff --git a/src/reachy_mini/daemon/app/dashboard/tailwindcss/tailwind.config.js b/src/reachy_mini/daemon/app/dashboard/tailwindcss/tailwind.config.js new file mode 100644 index 00000000..e5d481b8 --- /dev/null +++ b/src/reachy_mini/daemon/app/dashboard/tailwindcss/tailwind.config.js @@ -0,0 +1,11 @@ +module.exports = { + purge: [], + darkMode: false, // or 'media' or 'class' + theme: { + extend: {}, + }, + variants: { + extend: {}, + }, + plugins: [], +} diff --git a/src/reachy_mini/daemon/app/dashboard/templates/base.html b/src/reachy_mini/daemon/app/dashboard/templates/base.html new file mode 100644 index 00000000..06f7fb80 --- /dev/null +++ b/src/reachy_mini/daemon/app/dashboard/templates/base.html @@ -0,0 +1,21 @@ + + + + + + + {% block title %}Reachy Mini dashboard{% endblock %} + + + {% block extra_js %}{% endblock %} + + + + + +
      + {% block content %}{% endblock %} +
      + + + \ No newline at end of file diff --git a/src/reachy_mini/daemon/app/dashboard/templates/index.html b/src/reachy_mini/daemon/app/dashboard/templates/index.html new file mode 100644 index 00000000..36e31060 --- /dev/null +++ b/src/reachy_mini/daemon/app/dashboard/templates/index.html @@ -0,0 +1,20 @@ +{% extends "base.html" %} + +{% block content %} + +
      + {% include "sections/daemon.html" %} + {% include "sections/apps.html" %} + {% include "sections/appstore.html" %} + {% include "sections/notification.html" %} +
      +{% endblock %} + +{% block extra_js %} + + + + + + +{% endblock %} \ No newline at end of file diff --git a/src/reachy_mini/daemon/app/dashboard/templates/sections/apps.html b/src/reachy_mini/daemon/app/dashboard/templates/sections/apps.html new file mode 100644 index 00000000..d1b4601e --- /dev/null +++ b/src/reachy_mini/daemon/app/dashboard/templates/sections/apps.html @@ -0,0 +1,8 @@ +
      +
      +
      Applications
      +
      +
        +
        +
        +
        \ No newline at end of file diff --git a/src/reachy_mini/daemon/app/dashboard/templates/sections/appstore.html b/src/reachy_mini/daemon/app/dashboard/templates/sections/appstore.html new file mode 100644 index 00000000..2bc25425 --- /dev/null +++ b/src/reachy_mini/daemon/app/dashboard/templates/sections/appstore.html @@ -0,0 +1,29 @@ +
        + + + + +
        Install from 🤗 Hugging Face
        +
        +
          Loading app from 🤗...
        +
        +
        \ No newline at end of file diff --git a/src/reachy_mini/daemon/app/dashboard/templates/sections/daemon.html b/src/reachy_mini/daemon/app/dashboard/templates/sections/daemon.html new file mode 100644 index 00000000..26235844 --- /dev/null +++ b/src/reachy_mini/daemon/app/dashboard/templates/sections/daemon.html @@ -0,0 +1,45 @@ +
        + +
        + +
        +
        +
        +
        Reachy Mini
        + + + + +
        + +
        +
        + + +
        +
        + +
        + {% include 'sections/move_player.html' %} +
        + {% if args.wireless_version %} + + {% endif %} +
        \ No newline at end of file diff --git a/src/reachy_mini/daemon/app/dashboard/templates/sections/move_player.html b/src/reachy_mini/daemon/app/dashboard/templates/sections/move_player.html new file mode 100644 index 00000000..941e6843 --- /dev/null +++ b/src/reachy_mini/daemon/app/dashboard/templates/sections/move_player.html @@ -0,0 +1,121 @@ +
        +
        +
        Move player
        +
        + + +
        +
        +
        + + +
        +
        + +
        +
        +
        Volume control
        +
        Loading...
        +
        + +
        +
        + + + + + + + + + + + + + + + + 50 +
        + + +
        + + Advanced settings + +
        + +
        +
        +
        Microphone
        +
        Loading...
        +
        +
        + + + + + + + + + + + + + + + + + + 50 +
        +
        +
        +
        +
        +
        diff --git a/src/reachy_mini/daemon/app/dashboard/templates/sections/notification.html b/src/reachy_mini/daemon/app/dashboard/templates/sections/notification.html new file mode 100644 index 00000000..97dd7b9e --- /dev/null +++ b/src/reachy_mini/daemon/app/dashboard/templates/sections/notification.html @@ -0,0 +1,19 @@ + +
        +
        + + + +
        +
        + + \ No newline at end of file diff --git a/src/reachy_mini/daemon/app/dashboard/templates/sections/update.html b/src/reachy_mini/daemon/app/dashboard/templates/sections/update.html new file mode 100644 index 00000000..e42595a4 --- /dev/null +++ b/src/reachy_mini/daemon/app/dashboard/templates/sections/update.html @@ -0,0 +1,54 @@ +
        +
        Reachy Mini Update
        + +
        + + +
        + +
        +
        +
        +
        +
        +
        +
        +
        + +
        + +
        +
        +
        + + + + \ No newline at end of file diff --git a/src/reachy_mini/daemon/app/dashboard/templates/sections/wifi.html b/src/reachy_mini/daemon/app/dashboard/templates/sections/wifi.html new file mode 100644 index 00000000..9d55b418 --- /dev/null +++ b/src/reachy_mini/daemon/app/dashboard/templates/sections/wifi.html @@ -0,0 +1,46 @@ +
        +
        +
        Setup your WiFi
        + +
        + +
        +
        Checking WiFi configuration...
        + +
        + +
        \ No newline at end of file diff --git a/src/reachy_mini/daemon/app/dashboard/templates/settings.html b/src/reachy_mini/daemon/app/dashboard/templates/settings.html new file mode 100644 index 00000000..42c83981 --- /dev/null +++ b/src/reachy_mini/daemon/app/dashboard/templates/settings.html @@ -0,0 +1,14 @@ +{% extends "base.html" %} + +{% block content %} + +
        + {% include "sections/update.html" %} + {% include "sections/wifi.html" %} +
        +{% endblock %} + +{% block extra_js %} + + +{% endblock %} \ No newline at end of file diff --git a/src/reachy_mini/daemon/app/dependencies.py b/src/reachy_mini/daemon/app/dependencies.py index 11da81d9..8299f776 100644 --- a/src/reachy_mini/daemon/app/dependencies.py +++ b/src/reachy_mini/daemon/app/dependencies.py @@ -9,6 +9,7 @@ def get_daemon(request: Request) -> Daemon: """Get the daemon as request dependency.""" + assert isinstance(request.app.state.daemon, Daemon) return request.app.state.daemon @@ -16,17 +17,25 @@ def get_backend(request: Request) -> Backend: """Get the backend as request dependency.""" backend = request.app.state.daemon.backend - if not backend.ready.is_set(): + if backend is None or not backend.ready.is_set(): raise HTTPException(status_code=503, detail="Backend not running") + assert isinstance(backend, Backend) return backend def get_app_manager(request: Request) -> "AppManager": """Get the app manager as request dependency.""" + assert isinstance(request.app.state.app_manager, AppManager) return request.app.state.app_manager def ws_get_backend(websocket: WebSocket) -> Backend: """Get the backend as websocket dependency.""" - return websocket.app.state.daemon.backend + backend = websocket.app.state.daemon.backend + + if backend is None or not backend.ready.is_set(): + raise HTTPException(status_code=503, detail="Backend not running") + + assert isinstance(backend, Backend) + return backend diff --git a/src/reachy_mini/daemon/app/main.py b/src/reachy_mini/daemon/app/main.py index 60be7a45..a4b8166e 100644 --- a/src/reachy_mini/daemon/app/main.py +++ b/src/reachy_mini/daemon/app/main.py @@ -8,43 +8,59 @@ """ import argparse +import asyncio import logging -import os from contextlib import asynccontextmanager from dataclasses import dataclass from pathlib import Path +from typing import AsyncGenerator import uvicorn from fastapi import APIRouter, FastAPI, Request from fastapi.middleware.cors import CORSMiddleware +from fastapi.responses import HTMLResponse from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates from reachy_mini.apps.manager import AppManager -from reachy_mini.daemon.app.routers import apps, daemon, kinematics, motors, move, state +from reachy_mini.daemon.app.routers import ( + apps, + daemon, + kinematics, + motors, + move, + state, + volume, +) from reachy_mini.daemon.daemon import Daemon -DASHBOARD_PAGES = Path(__file__).parent / "dashboard" -TEMPLATES_DIR = Path(__file__).parent / "templates" -templates = Jinja2Templates(directory=str(TEMPLATES_DIR)) - @dataclass class Args: """Arguments for configuring the Reachy Mini daemon.""" log_level: str = "INFO" + log_file: str | None = None + + wireless_version: bool = False + + stream: bool = False serialport: str = "auto" + hardware_config_filepath: str | None = None sim: bool = False scene: str = "empty" headless: bool = False + websocket_uri: str | None = None + stream_media: bool = False + use_audio: bool = True kinematics_engine: str = "AnalyticalKinematics" check_collision: bool = False autostart: bool = True + timeout_health_check: float | None = None wake_up_on_start: bool = True goto_sleep_on_stop: bool = True @@ -52,47 +68,64 @@ class Args: fastapi_host: str = "0.0.0.0" fastapi_port: int = 8000 - localhost_only: bool = True + localhost_only: bool | None = None -def create_app(args: Args) -> FastAPI: +def create_app(args: Args, health_check_event: asyncio.Event | None = None) -> FastAPI: """Create and configure the FastAPI application.""" + localhost_only = ( + args.localhost_only + if args.localhost_only is not None + else (False if args.wireless_version else True) + ) @asynccontextmanager - async def lifespan(app: FastAPI): + async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]: """Lifespan context manager for the FastAPI application.""" args = app.state.args # type: Args - - if args.autostart: - await app.state.daemon.start( - serialport=args.serialport, - sim=args.sim, - scene=args.scene, - headless=args.headless, - kinematics_engine=args.kinematics_engine, - check_collision=args.check_collision, - wake_up_on_start=args.wake_up_on_start, - localhost_only=args.localhost_only, - ) - yield - await app.state.app_manager.close() - await app.state.daemon.stop( - goto_sleep_on_stop=args.goto_sleep_on_stop, - ) + + try: + if args.autostart: + await app.state.daemon.start( + serialport=args.serialport, + sim=args.sim, + scene=args.scene, + headless=args.headless, + websocket_uri=args.websocket_uri, + stream_media=args.stream_media, + use_audio=args.use_audio, + kinematics_engine=args.kinematics_engine, + check_collision=args.check_collision, + wake_up_on_start=args.wake_up_on_start, + localhost_only=localhost_only, + hardware_config_filepath=args.hardware_config_filepath, + ) + yield + finally: + # Ensure cleanup happens even if there's an exception + try: + logging.info("Shutting down app manager...") + await app.state.app_manager.close() + except Exception as e: + logging.error(f"Error closing app manager: {e}") + + try: + logging.info("Shutting down daemon...") + await app.state.daemon.stop( + goto_sleep_on_stop=args.goto_sleep_on_stop, + ) + except Exception as e: + logging.error(f"Error stopping daemon: {e}") app = FastAPI( lifespan=lifespan, ) - app.state.args = args - app.state.daemon = Daemon() - app.state.app_manager = AppManager() - app.add_middleware( - CORSMiddleware, - allow_origins=["*"], # or restrict to your HF domain - allow_methods=["*"], - allow_headers=["*"], + app.state.args = args + app.state.daemon = Daemon( + wireless_version=args.wireless_version, stream=args.stream ) + app.state.app_manager = AppManager() router = APIRouter(prefix="/api") router.include_router(apps.router) @@ -101,40 +134,128 @@ async def lifespan(app: FastAPI): router.include_router(motors.router) router.include_router(move.router) router.include_router(state.router) + router.include_router(volume.router) + + if args.wireless_version: + from .routers import update, wifi_config + + app.include_router(update.router) + app.include_router(wifi_config.router) app.include_router(router) - # Route to list available HTML/JS/CSS examples with links using Jinja2 template + if health_check_event is not None: + + @app.post("/health-check") + async def health_check() -> dict[str, str]: + """Health check endpoint to reset the health check timer.""" + health_check_event.set() + return {"status": "ok"} + + app.add_middleware( + CORSMiddleware, + allow_origins=["*"], # or restrict to your HF domain + allow_methods=["*"], + allow_headers=["*"], + ) + + STATIC_DIR = Path(__file__).parent / "dashboard" / "static" + TEMPLATES_DIR = Path(__file__).parent / "dashboard" / "templates" + + app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static") + templates = Jinja2Templates(directory=str(TEMPLATES_DIR)) + @app.get("/") - async def list_examples(request: Request): + async def dashboard(request: Request) -> HTMLResponse: """Render the dashboard.""" - files = [f for f in os.listdir(DASHBOARD_PAGES) if f.endswith(".html")] return templates.TemplateResponse( - "dashboard.html", {"request": request, "files": files} + "index.html", {"request": request, "args": args} ) - app.mount( - "/dashboard", - StaticFiles(directory=str(DASHBOARD_PAGES), html=True), - name="dashboard", - ) + if args.wireless_version: + + @app.get("/settings") + async def settings(request: Request) -> HTMLResponse: + """Render the settings page.""" + return templates.TemplateResponse("settings.html", {"request": request}) return app -def run_app(args: Args): +def run_app(args: Args) -> None: """Run the FastAPI app with Uvicorn.""" logging.basicConfig(level=logging.INFO) - app = create_app(args) - uvicorn.run(app, host=args.fastapi_host, port=args.fastapi_port) - - -def main(): + async def run_server() -> None: + health_check_event = asyncio.Event() + app = create_app(args, health_check_event) + + config = uvicorn.Config(app, host=args.fastapi_host, port=args.fastapi_port) + server = uvicorn.Server(config) + + health_check_task = None + + async def health_check_timeout(timeout_seconds: float) -> None: + while True: + try: + await asyncio.wait_for( + health_check_event.wait(), + timeout=timeout_seconds, + ) + health_check_event.clear() + except asyncio.TimeoutError: + logging.warning("Health check timeout reached, stopping app.") + server.should_exit = True + break + except asyncio.CancelledError: + logging.info("Health check task cancelled.") + break + + try: + if args.timeout_health_check is not None: + health_check_task = asyncio.create_task( + health_check_timeout(args.timeout_health_check) + ) + await server.serve() + except KeyboardInterrupt: + logging.info("Received Ctrl-C, shutting down gracefully.") + finally: + # Cancel health check task if it exists + if health_check_task and not health_check_task.done(): + health_check_task.cancel() + try: + await health_check_task + except asyncio.CancelledError: + pass + + try: + asyncio.run(run_server()) + except KeyboardInterrupt: + logging.info("Shutdown complete.") + except Exception as e: + logging.error(f"Error during shutdown: {e}") + raise + + +def main() -> None: """Run the FastAPI app with Uvicorn.""" default_args = Args() parser = argparse.ArgumentParser(description="Run the Reachy Mini daemon.") + parser.add_argument( + "--wireless-version", + action="store_true", + default=default_args.wireless_version, + help="Use the wireless version of Reachy Mini (default: False).", + ) + + parser.add_argument( + "--stream", + action="store_true", + default=default_args.stream, + help="Enable webrtc streaming. For wireless version only (default: False).", + ) + # Real robot mode parser.add_argument( "-p", @@ -143,6 +264,20 @@ def main(): default=default_args.serialport, help="Serial port for real motors (default: will try to automatically find the port).", ) + default_hw_config_path = str( + ( + Path(__file__).parent.parent.parent + / "assets" + / "config" + / "hardware_config.yaml" + ).resolve() + ) + parser.add_argument( + "--hardware-config-filepath", + type=str, + default=default_hw_config_path, + help=f"Path to the hardware configuration YAML file (default: {default_hw_config_path}).", + ) # Simulation mode parser.add_argument( "--sim", @@ -162,6 +297,25 @@ def main(): default=default_args.headless, help="Run the daemon in headless mode (default: False).", ) + parser.add_argument( + "--websocket-uri", + type=str, + default=default_args.websocket_uri, + help="WebSocket URI for remote control and streaming of the robot (default: None). Example: ws://localhost:8000", + ) + parser.add_argument( + "--stream-media", + action="store_true", + default=default_args.stream_media, + help="Stream media to the WebSocket. Requires a WebSocket URI to be set. (default: False).", + ) + parser.add_argument( + "--deactivate-audio", + action="store_false", + dest="use_audio", + default=default_args.use_audio, + help="Deactivate audio (default: True).", + ) # Daemon options parser.add_argument( "--autostart", @@ -175,6 +329,12 @@ def main(): dest="autostart", help="Do not automatically start the daemon on launch (default: False).", ) + parser.add_argument( + "--timeout-health-check", + type=float, + default=None, + help="Set the health check timeout in seconds (default: None).", + ) parser.add_argument( "--wake-up-on-start", action="store_true", @@ -246,10 +406,24 @@ def main(): choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"], help="Set the logging level (default: INFO).", ) + parser.add_argument( + "--log-file", + type=str, + default=default_args.log_file, + help="Path to a file to write logs to.", + ) args = parser.parse_args() - args = Args(**vars(args)) - run_app(args) + + if args.log_file: + file_handler = logging.FileHandler(args.log_file, mode="a") + file_handler.setFormatter( + logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") + ) + logging.getLogger().addHandler(file_handler) + logging.getLogger().setLevel(args.log_level) + + run_app(Args(**vars(args))) if __name__ == "__main__": diff --git a/src/reachy_mini/daemon/app/models.py b/src/reachy_mini/daemon/app/models.py index af7abc6b..19a5310a 100644 --- a/src/reachy_mini/daemon/app/models.py +++ b/src/reachy_mini/daemon/app/models.py @@ -3,6 +3,7 @@ from datetime import datetime import numpy as np +from numpy.typing import NDArray from pydantic import BaseModel from scipy.spatial.transform import Rotation as R @@ -32,12 +33,30 @@ class Matrix4x4Pose(BaseModel): ] @classmethod - def from_pose_array(cls, arr: np.ndarray) -> "Matrix4x4Pose": + def from_pose_array(cls, arr: NDArray[np.float64]) -> "Matrix4x4Pose": """Create a Matrix4x4 pose representation from a 4x4 pose array.""" assert arr.shape == (4, 4), "Array must be of shape (4, 4)" - return cls(m=tuple(arr.flatten().tolist())) - - def to_pose_array(self) -> np.ndarray: + m: tuple[ + float, + float, + float, + float, + float, + float, + float, + float, + float, + float, + float, + float, + float, + float, + float, + float, + ] = tuple(arr.flatten().tolist()) # type: ignore [assignment] + return cls(m=m) + + def to_pose_array(self) -> NDArray[np.float64]: """Convert the Matrix4x4Pose to a 4x4 numpy array.""" return np.array(self.m).reshape((4, 4)) @@ -53,7 +72,7 @@ class XYZRPYPose(BaseModel): yaw: float = 0.0 @classmethod - def from_pose_array(cls, arr: np.ndarray) -> "XYZRPYPose": + def from_pose_array(cls, arr: NDArray[np.float64]) -> "XYZRPYPose": """Create an XYZRPYPose representation from a 4x4 pose array.""" assert arr.shape == (4, 4), "Array must be of shape (4, 4)" @@ -69,7 +88,7 @@ def from_pose_array(cls, arr: np.ndarray) -> "XYZRPYPose": yaw=yaw, ) - def to_pose_array(self) -> np.ndarray: + def to_pose_array(self) -> NDArray[np.float64]: """Convert the XYZRPYPose to a 4x4 numpy array.""" rotation = R.from_euler("xyz", [self.roll, self.pitch, self.yaw]) pose_matrix = np.eye(4) @@ -81,7 +100,7 @@ def to_pose_array(self) -> np.ndarray: AnyPose = XYZRPYPose | Matrix4x4Pose -def as_any_pose(pose, use_matrix) -> AnyPose: +def as_any_pose(pose: NDArray[np.float64], use_matrix: bool) -> AnyPose: """Convert a numpy array to an AnyPose representation.""" return ( Matrix4x4Pose.from_pose_array(pose) @@ -95,8 +114,28 @@ class FullBodyTarget(BaseModel): target_head_pose: AnyPose | None = None target_antennas: tuple[float, float] | None = None + target_body_yaw: float | None = None timestamp: datetime | None = None + model_config = { + "json_schema_extra": { + "examples": [ + { + "target_head_pose": { + "x": 0.0, + "y": 0.0, + "z": 0.0, + "roll": 0.0, + "pitch": 0.0, + "yaw": 0.0, + }, + "target_antennas": [0.0, 0.0], + "target_body_yaw": 0.0, + } + ] + } + } + class FullState(BaseModel): """Represent the full state of the robot including all joint positions and poses.""" @@ -107,3 +146,4 @@ class FullState(BaseModel): body_yaw: float | None = None antennas_position: list[float] | None = None timestamp: datetime | None = None + passive_joints: list[float] | None = None diff --git a/src/reachy_mini/daemon/app/routers/apps.py b/src/reachy_mini/daemon/app/routers/apps.py index 562ec437..163b58f5 100644 --- a/src/reachy_mini/daemon/app/routers/apps.py +++ b/src/reachy_mini/daemon/app/routers/apps.py @@ -1,46 +1,20 @@ """Apps router for apps management.""" -import asyncio -import logging -import uuid -from dataclasses import dataclass - from fastapi import ( APIRouter, - BackgroundTasks, Depends, HTTPException, WebSocket, - WebSocketDisconnect, ) -from pydantic import BaseModel from reachy_mini.apps import AppInfo, SourceKind from reachy_mini.apps.manager import AppManager, AppStatus +from reachy_mini.daemon.app import bg_job_register from reachy_mini.daemon.app.dependencies import get_app_manager router = APIRouter(prefix="/apps") -class JobStatus(BaseModel): - """Pydantic model for install job status.""" - - command: str - status: str - logs: list[str] - - -@dataclass -class JobHandler: - """Handler for background jobs.""" - - status: JobStatus - new_log_evt: dict[str, asyncio.Event] - - -jobs: dict[str, JobHandler] = {} - - @router.get("/list-available/{source_kind}") async def list_available_apps( source_kind: SourceKind, @@ -61,12 +35,11 @@ async def list_all_available_apps( @router.post("/install") async def install_app( app_info: AppInfo, - background_tasks: BackgroundTasks, app_manager: "AppManager" = Depends(get_app_manager), -): +) -> dict[str, str]: """Install a new app by its info (background, returns job_id).""" - job_id = start_bg_job( - "install", background_tasks, app_manager.install_new_app, app_info + job_id = bg_job_register.run_command( + "install", app_manager.install_new_app, app_info ) return {"job_id": job_id} @@ -74,65 +47,29 @@ async def install_app( @router.post("/remove/{app_name}") async def remove_app( app_name: str, - background_tasks: BackgroundTasks, app_manager: "AppManager" = Depends(get_app_manager), -): +) -> dict[str, str]: """Remove an installed app by its name (background, returns job_id).""" - job_id = start_bg_job("remove", background_tasks, app_manager.remove_app, app_name) + job_id = bg_job_register.run_command("remove", app_manager.remove_app, app_name) return {"job_id": job_id} @router.get("/job-status/{job_id}") -async def job_status(job_id: str) -> JobStatus: +async def job_status(job_id: str) -> bg_job_register.JobInfo: """Get status/logs for a job.""" - job = jobs.get(job_id) - if not job: - raise HTTPException(status_code=404, detail="Job not found") - return job.status + try: + return bg_job_register.get_info(job_id) + except Exception as e: + raise HTTPException(status_code=404, detail=str(e)) # WebSocket route for live job status/logs @router.websocket("/ws/apps-manager/{job_id}") -async def ws_apps_manager(websocket: WebSocket, job_id: str): +async def ws_apps_manager(websocket: WebSocket, job_id: str) -> None: """WebSocket route to stream live job status/logs for a job, sending updates as soon as new logs are available.""" await websocket.accept() - last_log_len = 0 - - job = jobs.get(job_id) - if not job: - await websocket.send_json({"error": "Job not found"}) - await websocket.close() - return - - assert job is not None # for mypy - - ws_uuid = str(uuid.uuid4()) - - try: - job.new_log_evt[ws_uuid] = asyncio.Event() - while True: - await job.new_log_evt[ws_uuid].wait() - job.new_log_evt[ws_uuid].clear() - new_logs = job.status.logs[last_log_len:] - last_log_len = len(job.status.logs) - await websocket.send_json( - { - "command": job.status.command, - "status": job.status.model_dump_json(), - "logs": new_logs, - } - ) - if job.status.status.startswith("done") or job.status.status.startswith( - "error" - ): - await websocket.close() - break - except WebSocketDisconnect: - pass - - finally: - if ws_uuid in job.new_log_evt: - del job.new_log_evt[ws_uuid] + await bg_job_register.ws_poll_info(websocket, job_id) + await websocket.close() @router.post("/start-app/{app_name}") @@ -175,42 +112,3 @@ async def current_app_status( ) -> AppStatus | None: """Get the status of the currently running app, if any.""" return await app_manager.current_app_status() - - -def start_bg_job( - command: str, background_tasks: BackgroundTasks, coro_func, *args -) -> str: - """Start a background job, with a custom logger and return its job_id.""" - job_id = str(uuid.uuid4()) - jobs[job_id] = JobHandler( - status=JobStatus( - command=command, - status="pending", - logs=[], - ), - new_log_evt={}, - ) - - async def run_command(): - jobs[job_id].status.status = "running" - - class JobLogger(logging.Handler): - def emit(self, record): - jobs[job_id].status.logs.append(self.format(record)) - for ws in jobs[job_id].new_log_evt.values(): - ws.set() - - logger = logging.getLogger(f"logs_job_{job_id}") - logger.setLevel(logging.INFO) - logger.handlers.clear() - logger.addHandler(JobLogger()) - try: - await coro_func(*args, logger=logger) - jobs[job_id].status.status = "done" - logger.info(f"Job '{command}' completed successfully") - except Exception as e: - jobs[job_id].status.status = f"error: {e}" - logger.error(f"Job '{command}' failed with error: {e}") - - background_tasks.add_task(run_command) - return job_id diff --git a/src/reachy_mini/daemon/app/routers/daemon.py b/src/reachy_mini/daemon/app/routers/daemon.py index ea640376..981fe41e 100644 --- a/src/reachy_mini/daemon/app/routers/daemon.py +++ b/src/reachy_mini/daemon/app/routers/daemon.py @@ -1,6 +1,11 @@ """Daemon-related API routes.""" -from fastapi import APIRouter, Depends, Request +import logging +import threading + +from fastapi import APIRouter, Depends, HTTPException, Request + +from reachy_mini.daemon.app import bg_job_register from ...daemon import Daemon, DaemonStatus from ..dependencies import get_daemon @@ -8,6 +13,7 @@ router = APIRouter( prefix="/daemon", ) +busy_lock = threading.Lock() @router.post("/start") @@ -15,34 +21,62 @@ async def start_daemon( request: Request, wake_up: bool, daemon: Daemon = Depends(get_daemon), -): +) -> dict[str, str]: """Start the daemon.""" - await daemon.start( - sim=request.app.state.args.sim, - scene=request.app.state.args.scene, - headless=request.app.state.args.headless, - wake_up_on_start=wake_up, - ) - return daemon.status() + if busy_lock.locked(): + raise HTTPException(status_code=409, detail="Daemon is busy.") + + async def start(logger: logging.Logger) -> None: + with busy_lock: + await daemon.start( + sim=request.app.state.args.sim, + serialport=request.app.state.args.serialport, + scene=request.app.state.args.scene, + localhost_only=request.app.state.args.localhost_only, + wake_up_on_start=wake_up, + check_collision=request.app.state.args.check_collision, + kinematics_engine=request.app.state.args.kinematics_engine, + headless=request.app.state.args.headless, + websocket_uri=request.app.state.args.websocket_uri, + stream_media=request.app.state.args.stream_media, + use_audio=request.app.state.args.use_audio, + hardware_config_filepath=request.app.state.args.hardware_config_filepath, + ) + + job_id = bg_job_register.run_command("daemon-start", start) + return {"job_id": job_id} @router.post("/stop") async def stop_daemon( goto_sleep: bool, daemon: Daemon = Depends(get_daemon) -) -> DaemonStatus: +) -> dict[str, str]: """Stop the daemon, optionally putting the robot to sleep.""" - await daemon.stop(goto_sleep_on_stop=goto_sleep) - return daemon.status() + if busy_lock.locked(): + raise HTTPException(status_code=409, detail="Daemon is busy.") + + async def stop(logger: logging.Logger) -> None: + with busy_lock: + await daemon.stop(goto_sleep_on_stop=goto_sleep) + + job_id = bg_job_register.run_command("daemon-stop", stop) + return {"job_id": job_id} @router.post("/restart") -async def restart_daemon(request: Request, daemon: Daemon = Depends(get_daemon)): +async def restart_daemon( + request: Request, daemon: Daemon = Depends(get_daemon) +) -> dict[str, str]: """Restart the daemon.""" - await daemon.restart( - sim=request.app.state.args.sim, - scene=request.app.state.args.scene, - ) - return daemon.status() + if busy_lock.locked(): + raise HTTPException(status_code=409, detail="Daemon is busy.") + + async def restart(logger: logging.Logger) -> None: + with busy_lock: + await daemon.restart() + + job_id = bg_job_register.run_command("daemon-restart", restart) + return {"job_id": job_id} @router.get("/status") diff --git a/src/reachy_mini/daemon/app/routers/kinematics.py b/src/reachy_mini/daemon/app/routers/kinematics.py index 73f3c35e..71834eb1 100644 --- a/src/reachy_mini/daemon/app/routers/kinematics.py +++ b/src/reachy_mini/daemon/app/routers/kinematics.py @@ -5,7 +5,10 @@ and other kinematics-related information. """ -from fastapi import APIRouter, Depends +from pathlib import Path +from typing import Any + +from fastapi import APIRouter, Depends, HTTPException, Response from ....daemon.backend.abstract import Backend from ..dependencies import get_backend @@ -14,9 +17,19 @@ prefix="/kinematics", ) +STL_ASSETS_DIR = ( + Path(__file__).parent.parent.parent.parent + / "descriptions" + / "reachy_mini" + / "urdf" + / "assets" +) + @router.get("/info") -async def get_kinematics_info(backend: Backend = Depends(get_backend)): +async def get_kinematics_info( + backend: Backend = Depends(get_backend), +) -> dict[str, Any]: """Get the current information of the kinematics.""" return { "info": { @@ -27,6 +40,18 @@ async def get_kinematics_info(backend: Backend = Depends(get_backend)): @router.get("/urdf") -async def get_urdf(backend: Backend = Depends(get_backend)): +async def get_urdf(backend: Backend = Depends(get_backend)) -> dict[str, str]: """Get the URDF representation of the robot.""" return {"urdf": backend.get_urdf()} + + +@router.get("/stl/{filename}") +async def get_stl_file(filename: Path) -> Response: + """Get the path to an STL asset file.""" + file_path = STL_ASSETS_DIR / filename + try: + with open(file_path, "rb") as file: + content = file.read() + return Response(content, media_type="model/stl") + except FileNotFoundError: + raise HTTPException(status_code=404, detail=f"STL file not found {file_path}") diff --git a/src/reachy_mini/daemon/app/routers/motors.py b/src/reachy_mini/daemon/app/routers/motors.py index 4c98a5ba..0b8b65a1 100644 --- a/src/reachy_mini/daemon/app/routers/motors.py +++ b/src/reachy_mini/daemon/app/routers/motors.py @@ -34,7 +34,7 @@ async def get_motor_status(backend: Backend = Depends(get_backend)) -> MotorStat async def set_motor_mode( mode: MotorControlMode, backend: Backend = Depends(get_backend), -): +) -> dict[str, str]: """Set the motor control mode.""" backend.set_motor_control_mode(mode) diff --git a/src/reachy_mini/daemon/app/routers/move.py b/src/reachy_mini/daemon/app/routers/move.py index 6417e40d..cebea5fc 100644 --- a/src/reachy_mini/daemon/app/routers/move.py +++ b/src/reachy_mini/daemon/app/routers/move.py @@ -10,10 +10,12 @@ import asyncio import json from enum import Enum -from typing import Coroutine +from typing import Any, Coroutine from uuid import UUID, uuid4 -from fastapi import APIRouter, Depends, WebSocket, WebSocketDisconnect +import numpy as np +from fastapi import APIRouter, Depends, HTTPException, WebSocket, WebSocketDisconnect +from huggingface_hub.errors import RepositoryNotFoundError from pydantic import BaseModel from reachy_mini.motion.recorded_move import RecordedMoves @@ -22,9 +24,11 @@ from ..dependencies import get_backend, ws_get_backend from ..models import AnyPose, FullBodyTarget -router = APIRouter( - prefix="/move", -) +move_tasks: dict[UUID, asyncio.Task[None]] = {} +move_listeners: list[WebSocket] = [] + + +router = APIRouter(prefix="/move") class InterpolationMode(str, Enum): @@ -43,9 +47,36 @@ class GotoModelRequest(BaseModel): head_pose: AnyPose | None = None antennas: tuple[float, float] | None = None + body_yaw: float | None = None duration: float interpolation: InterpolationMode = InterpolationMode.MINJERK + model_config = { + "json_schema_extra": { + "examples": [ + { + "head_pose": { + "x": 0.0, + "y": 0.0, + "z": 0.0, + "roll": 0.0, + "pitch": 0.0, + "yaw": 0.0, + }, + "antennas": [0.0, 0.0], + "body_yaw": 0.0, + "duration": 2.0, + "interpolation": "minjerk", + }, + { + "antennas": [0.0, 0.0], + "duration": 1.0, + "interpolation": "linear", + }, + ], + } + } + class MoveUUID(BaseModel): """Model representing a unique identifier for a move task.""" @@ -53,22 +84,42 @@ class MoveUUID(BaseModel): uuid: UUID -move_tasks: dict[UUID, asyncio.Task] = {} - - -def create_move_task(coro: Coroutine) -> MoveUUID: +def create_move_task(coro: Coroutine[Any, Any, None]) -> MoveUUID: """Create a new move task using async task coroutine.""" uuid = uuid4() - task = asyncio.create_task(coro) - - task.add_done_callback(lambda t: move_tasks.pop(uuid, None)) + async def notify_listeners(message: str, details: str = "") -> None: + for ws in move_listeners: + try: + await ws.send_json( + { + "type": message, + "uuid": str(uuid), + "details": details, + } + ) + except (RuntimeError, WebSocketDisconnect): + move_listeners.remove(ws) + + async def wrap_coro() -> None: + try: + await notify_listeners("move_started") + await coro + await notify_listeners("move_completed") + except Exception as e: + await notify_listeners("move_failed", details=str(e)) + except asyncio.CancelledError: + await notify_listeners("move_cancelled") + finally: + move_tasks.pop(uuid, None) + + task = asyncio.create_task(wrap_coro()) move_tasks[uuid] = task return MoveUUID(uuid=uuid) -async def stop_move_task(uuid: UUID): +async def stop_move_task(uuid: UUID) -> dict[str, str]: """Stop a running move task by cancelling it.""" if uuid not in move_tasks: raise KeyError(f"Running move with UUID {uuid} not found") @@ -102,7 +153,8 @@ async def goto( return create_move_task( backend.goto_target( head=goto_req.head_pose.to_pose_array() if goto_req.head_pose else None, - antennas=list(goto_req.antennas) if goto_req.antennas else None, + antennas=np.array(goto_req.antennas) if goto_req.antennas else None, + body_yaw=goto_req.body_yaw, duration=goto_req.duration, ) ) @@ -120,6 +172,19 @@ async def play_goto_sleep(backend: Backend = Depends(get_backend)) -> MoveUUID: return create_move_task(backend.goto_sleep()) +@router.get("/recorded-move-datasets/list/{dataset_name:path}") +async def list_recorded_move_dataset( + dataset_name: str, +) -> list[str]: + """List available recorded moves in a dataset.""" + try: + moves = RecordedMoves(dataset_name) + except RepositoryNotFoundError as e: + raise HTTPException(status_code=404, detail=str(e)) + + return moves.list_moves() + + @router.post("/play/recorded-move-dataset/{dataset_name:path}/{move_name}") async def play_recorded_move_dataset( dataset_name: str, @@ -127,28 +192,50 @@ async def play_recorded_move_dataset( backend: Backend = Depends(get_backend), ) -> MoveUUID: """Request the robot to play a predefined recorded move from a dataset.""" - move = RecordedMoves(dataset_name).get(move_name) + try: + recorded_moves = RecordedMoves(dataset_name) + except RepositoryNotFoundError as e: + raise HTTPException(status_code=404, detail=str(e)) + try: + move = recorded_moves.get(move_name) + except ValueError as e: + raise HTTPException(status_code=404, detail=str(e)) return create_move_task(backend.play_move(move)) @router.post("/stop") -async def stop_move(uuid: MoveUUID): +async def stop_move(uuid: MoveUUID) -> dict[str, str]: """Stop a running move task.""" return await stop_move_task(uuid.uuid) +@router.websocket("/ws/updates") +async def ws_move_updates( + websocket: WebSocket, +) -> None: + """WebSocket route to stream move updates.""" + await websocket.accept() + try: + move_listeners.append(websocket) + while True: + _ = await websocket.receive_text() + except WebSocketDisconnect: + move_listeners.remove(websocket) + + # --- FullBodyTarget streaming and single set_target --- @router.post("/set_target") async def set_target( target: FullBodyTarget, backend: Backend = Depends(get_backend), -) -> dict: +) -> dict[str, str]: """POST route to set a single FullBodyTarget.""" backend.set_target( head=target.target_head_pose.to_pose_array() if target.target_head_pose else None, - antennas=list(target.target_antennas) if target.target_antennas else None, + antennas=np.array(target.target_antennas) if target.target_antennas else None, + body_yaw=target.target_body_yaw, ) return {"status": "ok"} @@ -156,7 +243,7 @@ async def set_target( @router.websocket("/ws/set_target") async def ws_set_target( websocket: WebSocket, backend: Backend = Depends(ws_get_backend) -): +) -> None: """WebSocket route to stream FullBodyTarget set_target calls.""" await websocket.accept() try: diff --git a/src/reachy_mini/daemon/app/routers/state.py b/src/reachy_mini/daemon/app/routers/state.py index adefc7ab..1ba1aea2 100644 --- a/src/reachy_mini/daemon/app/routers/state.py +++ b/src/reachy_mini/daemon/app/routers/state.py @@ -7,6 +7,7 @@ import asyncio from datetime import datetime, timezone +from typing import Any from fastapi import APIRouter, Depends, WebSocket, WebSocketDisconnect @@ -64,11 +65,12 @@ async def get_full_state( with_target_body_yaw: bool = False, with_antenna_positions: bool = True, with_target_antenna_positions: bool = False, + with_passive_joints: bool = False, use_pose_matrix: bool = False, backend: Backend = Depends(get_backend), ) -> FullState: """Get the full robot state, with optional fields.""" - result = {} + result: dict[str, Any] = {} if with_control_mode: result["control_mode"] = backend.get_motor_control_mode().value @@ -78,6 +80,7 @@ async def get_full_state( result["head_pose"] = as_any_pose(pose, use_pose_matrix) if with_target_head_pose: target_pose = backend.target_head_pose + assert target_pose is not None result["target_head_pose"] = as_any_pose(target_pose, use_pose_matrix) if with_head_joints: result["head_joints"] = backend.get_present_head_joint_positions() @@ -91,6 +94,12 @@ async def get_full_state( result["antennas_position"] = backend.get_present_antenna_joint_positions() if with_target_antenna_positions: result["target_antennas_position"] = backend.target_antenna_joint_positions + if with_passive_joints: + joints = backend.get_present_passive_joint_positions() + if joints is not None: + result["passive_joints"] = list(joints.values()) + else: + result["passive_joints"] = None result["timestamp"] = datetime.now(timezone.utc) return FullState.model_validate(result) @@ -108,9 +117,10 @@ async def ws_full_state( with_target_body_yaw: bool = False, with_antenna_positions: bool = True, with_target_antenna_positions: bool = False, + with_passive_joints: bool = False, use_pose_matrix: bool = False, backend: Backend = Depends(ws_get_backend), -): +) -> None: """WebSocket endpoint to stream the full state of the robot.""" await websocket.accept() period = 1.0 / frequency @@ -126,6 +136,7 @@ async def ws_full_state( with_target_body_yaw=with_target_body_yaw, with_antenna_positions=with_antenna_positions, with_target_antenna_positions=with_target_antenna_positions, + with_passive_joints=with_passive_joints, use_pose_matrix=use_pose_matrix, backend=backend, ) diff --git a/src/reachy_mini/daemon/app/routers/update.py b/src/reachy_mini/daemon/app/routers/update.py new file mode 100644 index 00000000..cd2e4bf8 --- /dev/null +++ b/src/reachy_mini/daemon/app/routers/update.py @@ -0,0 +1,86 @@ +"""Update router for Reachy Mini Daemon API. + +This module provides endpoints to check for updates, start updates, and monitor update status. +""" + +import logging +import threading + +import requests +from fastapi import APIRouter, HTTPException, WebSocket + +from reachy_mini.daemon.app import bg_job_register +from reachy_mini.daemon.app.bg_job_register import JobInfo +from reachy_mini.utils.wireless_version.update import update_reachy_mini +from reachy_mini.utils.wireless_version.update_available import ( + get_local_version, + get_pypi_version, + is_update_available, +) + +router = APIRouter(prefix="/update") +busy_lock = threading.Lock() + + +@router.get("/available") +def available(pre_release: bool = False) -> dict[str, dict[str, dict[str, bool | str]]]: + """Check if an update is available for Reachy Mini Wireless.""" + if busy_lock.locked(): + raise HTTPException(status_code=400, detail="Update is in progress") + + current_version = str(get_local_version("reachy_mini")) + + try: + is_available = is_update_available("reachy_mini", pre_release) + available = str(get_pypi_version("reachy_mini", pre_release)) + except (ConnectionError, requests.exceptions.ConnectionError): + is_available = False + available = "unknown" + + return { + "update": { + "reachy_mini": { + "is_available": is_available, + "current_version": current_version, + "available_version": available, + } + } + } + + +@router.post("/start") +def start_update(pre_release: bool = False) -> dict[str, str]: + """Start the update process for Reachy Mini Wireless version.""" + if busy_lock.locked(): + raise HTTPException(status_code=400, detail="Update already in progress") + + if not is_update_available("reachy_mini", pre_release): + raise HTTPException(status_code=400, detail="No update available") + + async def update_wrapper(logger: logging.Logger) -> None: + with busy_lock: + await update_reachy_mini(pre_release, logger) + + job_uuid = bg_job_register.run_command( + "update_reachy_mini", + update_wrapper, + ) + + return {"job_id": job_uuid} + + +@router.get("/info") +def get_update_info(job_id: str) -> JobInfo: + """Get the info of an update job.""" + try: + return bg_job_register.get_info(job_id) + except ValueError as e: + raise HTTPException(status_code=404, detail=str(e)) + + +@router.websocket("/ws/logs") +async def websocket_logs(websocket: WebSocket, job_id: str) -> None: + """WebSocket endpoint to stream update logs in real time.""" + await websocket.accept() + await bg_job_register.ws_poll_info(websocket, job_id) + await websocket.close() diff --git a/src/reachy_mini/daemon/app/routers/volume.py b/src/reachy_mini/daemon/app/routers/volume.py new file mode 100644 index 00000000..c6806cde --- /dev/null +++ b/src/reachy_mini/daemon/app/routers/volume.py @@ -0,0 +1,416 @@ +"""Volume control API routes. + +This exposes: +- get current volume +- set volume +- same for microphone +- play test sound (optional) +""" + +import logging +import platform +import subprocess +from typing import Optional + +from fastapi import APIRouter, Depends, HTTPException +from pydantic import BaseModel, Field + +from reachy_mini.daemon.app.dependencies import get_backend +from reachy_mini.daemon.backend.abstract import Backend + +router = APIRouter(prefix="/volume") +logger = logging.getLogger(__name__) + +# Constants +AUDIO_COMMAND_TIMEOUT = 2 # Timeout in seconds for audio commands + +# Device-specific card names for amixer +DEVICE_CARD_NAMES = { + "reachy_mini_audio": "Audio", # Reachy Mini Audio device + "respeaker": "Array", # Legacy ReSpeaker device + "default": "Audio", # Default to Reachy Mini Audio +} + + +class VolumeRequest(BaseModel): + """Request model for setting volume.""" + + volume: int = Field(..., ge=0, le=100, description="Volume level (0-100)") + + +class VolumeResponse(BaseModel): + """Response model for volume operations.""" + + volume: int + device: str + platform: str + + +class TestSoundResponse(BaseModel): + """Response model for test sound operations.""" + + status: str + message: str + + +def get_current_platform() -> str: + """Get the current platform.""" + system = platform.system() + if system == "Darwin": + return "macOS" + elif system == "Linux": + return "Linux" + else: + return system + + +def detect_audio_device() -> str: + """Detect the current audio output device.""" + system = platform.system() + + if system == "Linux": + # Try to detect if Reachy Mini Audio or legacy Respeaker is available + try: + result = subprocess.run( + ["aplay", "-l"], + capture_output=True, + text=True, + timeout=AUDIO_COMMAND_TIMEOUT, + ) + output_lower = result.stdout.lower() + if "reachy mini audio" in output_lower: + return "reachy_mini_audio" + elif "respeaker" in output_lower: + return "respeaker" + except (subprocess.TimeoutExpired, FileNotFoundError): + pass + return "default" + elif system == "Darwin": + return "system" + else: + return "unknown" + + +def get_linux_card_name() -> str: + """Get the appropriate card name for Linux amixer commands based on detected device.""" + device = detect_audio_device() + return DEVICE_CARD_NAMES.get(device, DEVICE_CARD_NAMES["default"]) + + +# macOS Volume Control + + +def get_volume_macos() -> Optional[int]: + """Get current system volume on macOS.""" + try: + result = subprocess.run( + ["osascript", "-e", "output volume of (get volume settings)"], + capture_output=True, + text=True, + timeout=AUDIO_COMMAND_TIMEOUT, + ) + if result.returncode == 0: + return int(result.stdout.strip()) + except (subprocess.TimeoutExpired, FileNotFoundError, ValueError) as e: + logger.error(f"Failed to get macOS volume: {e}") + return None + + +def set_volume_macos(volume: int) -> bool: + """Set system volume on macOS using osascript.""" + try: + subprocess.run( + ["osascript", "-e", f"set volume output volume {volume}"], + capture_output=True, + timeout=AUDIO_COMMAND_TIMEOUT, + check=True, + ) + return True + except ( + subprocess.TimeoutExpired, + FileNotFoundError, + subprocess.CalledProcessError, + ) as e: + logger.error(f"Failed to set macOS volume: {e}") + return False + + +# Linux Volume Control + + +def get_volume_linux() -> Optional[int]: + """Get current volume on Linux using amixer.""" + card_name = get_linux_card_name() + try: + result = subprocess.run( + ["amixer", "-c", card_name, "sget", "PCM"], + capture_output=True, + text=True, + timeout=AUDIO_COMMAND_TIMEOUT, + ) + if result.returncode == 0: + # Parse output to extract volume percentage + for line in result.stdout.splitlines(): + if "Left:" in line and "[" in line: + # Extract percentage between brackets + parts = line.split("[") + for part in parts: + if "%" in part: + volume_str = part.split("%")[0] + return int(volume_str) + except (subprocess.TimeoutExpired, FileNotFoundError, ValueError) as e: + logger.error(f"Failed to get Linux volume: {e}") + return None + + +def set_volume_linux(volume: int) -> bool: + """Set current volume on Linux using amixer.""" + card_name = get_linux_card_name() + try: + subprocess.run( + ["amixer", "-c", card_name, "sset", "PCM", f"{volume}%"], + capture_output=True, + text=True, + timeout=AUDIO_COMMAND_TIMEOUT, + check=True, + ) + return True + except ( + subprocess.TimeoutExpired, + FileNotFoundError, + subprocess.CalledProcessError, + ValueError, + ) as e: + logger.error(f"Failed to set Linux volume: {e}") + return False + + +# API Endpoints - Speaker Volume + + +@router.get("/current") +async def get_volume() -> VolumeResponse: + """Get the current volume level.""" + system = get_current_platform() + device = detect_audio_device() + + volume = None + if system == "macOS": + volume = get_volume_macos() + elif system == "Linux": + volume = get_volume_linux() + + if volume is None: + raise HTTPException(status_code=500, detail="Failed to get volume") + + return VolumeResponse(volume=volume, device=device, platform=system) + + +@router.post("/set") +async def set_volume( + volume_req: VolumeRequest, + backend: Backend = Depends(get_backend), +) -> VolumeResponse: + """Set the volume level and play a test sound.""" + system = get_current_platform() + device = detect_audio_device() + + success = False + if system == "macOS": + success = set_volume_macos(volume_req.volume) + elif system == "Linux": + success = set_volume_linux(volume_req.volume) + else: + raise HTTPException( + status_code=501, + detail=f"Volume control not supported on {system}", + ) + + if not success: + raise HTTPException(status_code=500, detail="Failed to set volume") + + # Play test sound + test_sound = "impatient1.wav" + if backend.audio: + try: + backend.audio.play_sound(test_sound) + except Exception as e: + msg = str(e).lower() + if "device unavailable" in msg or "-9985" in msg: + logger.warning( + "Test sound not played: audio device busy (likely GStreamer): %s", + e, + ) + else: + logger.warning("Failed to play test sound: %s", e) + else: + logger.warning("No audio backend available, skipping test sound.") + + return VolumeResponse(volume=volume_req.volume, device=device, platform=system) + + +@router.post("/test-sound") +async def play_test_sound(backend: Backend = Depends(get_backend)) -> TestSoundResponse: + """Play a test sound.""" + test_sound = "impatient1.wav" + + if not backend.audio: + raise HTTPException(status_code=503, detail="Audio device not available") + + try: + backend.audio.play_sound(test_sound) + return TestSoundResponse(status="ok", message="Test sound played") + except Exception as e: + msg = str(e).lower() + + # Special-case ALSA / PortAudio device-busy situation + if "device unavailable" in msg or "-9985" in msg: + logger.warning( + "Test sound request while audio device is busy (likely GStreamer): %s", + e, + ) + # Still 200, but tell the caller it was skipped + return TestSoundResponse( + status="busy", + message="Audio device is currently in use, test sound was skipped.", + ) + + # Any other error is treated as a real failure + logger.error("Failed to play test sound: %s", e, exc_info=True) + raise HTTPException( + status_code=500, + detail="Failed to play test sound (see logs for details)", + ) + + +# macOS Microphone Control + + +def get_microphone_volume_macos() -> Optional[int]: + """Get current microphone input volume on macOS.""" + try: + result = subprocess.run( + ["osascript", "-e", "input volume of (get volume settings)"], + capture_output=True, + text=True, + timeout=AUDIO_COMMAND_TIMEOUT, + ) + if result.returncode == 0: + return int(result.stdout.strip()) + except (subprocess.TimeoutExpired, FileNotFoundError, ValueError) as e: + logger.error(f"Failed to get macOS microphone volume: {e}") + return None + + +def set_microphone_volume_macos(volume: int) -> bool: + """Set microphone input volume on macOS using osascript.""" + try: + subprocess.run( + ["osascript", "-e", f"set volume input volume {volume}"], + capture_output=True, + timeout=AUDIO_COMMAND_TIMEOUT, + check=True, + ) + return True + except ( + subprocess.TimeoutExpired, + FileNotFoundError, + subprocess.CalledProcessError, + ) as e: + logger.error(f"Failed to set macOS microphone volume: {e}") + return False + + +# Linux Microphone Control + + +def get_microphone_volume_linux() -> Optional[int]: + """Get current microphone input volume on Linux using amixer.""" + card_name = get_linux_card_name() + try: + result = subprocess.run( + ["amixer", "-c", card_name, "sget", "Headset"], + capture_output=True, + text=True, + timeout=AUDIO_COMMAND_TIMEOUT, + ) + if result.returncode == 0: + # Parse output to extract volume percentage + for line in result.stdout.splitlines(): + if "Left:" in line and "[" in line: + parts = line.split("[") + for part in parts: + if "%" in part: + volume_str = part.split("%")[0] + return int(volume_str) + except (subprocess.TimeoutExpired, FileNotFoundError, ValueError) as e: + logger.error(f"Failed to get Linux microphone volume: {e}") + return None + + +def set_microphone_volume_linux(volume: int) -> bool: + """Set microphone input volume on Linux using amixer.""" + card_name = get_linux_card_name() + try: + subprocess.run( + ["amixer", "-c", card_name, "sset", "Headset", f"{volume}%"], + capture_output=True, + text=True, + timeout=AUDIO_COMMAND_TIMEOUT, + check=True, + ) + return True + except ( + subprocess.TimeoutExpired, + FileNotFoundError, + subprocess.CalledProcessError, + ValueError, + ) as e: + logger.error(f"Failed to set Linux microphone volume: {e}") + return False + + +# API Endpoints - Microphone Volume + + +@router.get("/microphone/current") +async def get_microphone_volume() -> VolumeResponse: + """Get the current microphone input volume level.""" + system = get_current_platform() + device = detect_audio_device() + + volume = None + if system == "macOS": + volume = get_microphone_volume_macos() + elif system == "Linux": + volume = get_microphone_volume_linux() + + if volume is None: + raise HTTPException(status_code=500, detail="Failed to get microphone volume") + + return VolumeResponse(volume=volume, device=device, platform=system) + + +@router.post("/microphone/set") +async def set_microphone_volume( + volume_req: VolumeRequest, +) -> VolumeResponse: + """Set the microphone input volume level.""" + system = get_current_platform() + device = detect_audio_device() + + success = False + if system == "macOS": + success = set_microphone_volume_macos(volume_req.volume) + elif system == "Linux": + success = set_microphone_volume_linux(volume_req.volume) + else: + raise HTTPException( + status_code=501, + detail=f"Microphone volume control not supported on {system}", + ) + + if not success: + raise HTTPException(status_code=500, detail="Failed to set microphone volume") + + return VolumeResponse(volume=volume_req.volume, device=device, platform=system) diff --git a/src/reachy_mini/daemon/app/routers/wifi_config.py b/src/reachy_mini/daemon/app/routers/wifi_config.py new file mode 100644 index 00000000..0285ecd3 --- /dev/null +++ b/src/reachy_mini/daemon/app/routers/wifi_config.py @@ -0,0 +1,219 @@ +"""WiFi Configuration Routers.""" + +import logging +from enum import Enum +from threading import Lock, Thread + +import nmcli +from fastapi import APIRouter, HTTPException +from pydantic import BaseModel + +HOTSPOT_SSID = "reachy-mini-ap" +HOTSPOT_PASSWORD = "reachy-mini" + + +router = APIRouter( + prefix="/wifi", +) + +busy_lock = Lock() +error: Exception | None = None +logger = logging.getLogger(__name__) + + +class WifiMode(Enum): + """WiFi possible modes.""" + + HOTSPOT = "hotspot" + WLAN = "wlan" + DISCONNECTED = "disconnected" + BUSY = "busy" + + +class WifiStatus(BaseModel): + """WiFi status model.""" + + mode: WifiMode + known_networks: list[str] + connected_network: str | None + + +def get_current_wifi_mode() -> WifiMode: + """Get the current WiFi mode.""" + if busy_lock.locked(): + return WifiMode.BUSY + + conn = get_wifi_connections() + if check_if_connection_active("Hotspot"): + return WifiMode.HOTSPOT + elif any(c.device != "--" for c in conn): + return WifiMode.WLAN + else: + return WifiMode.DISCONNECTED + + +@router.get("/status") +def get_wifi_status() -> WifiStatus: + """Get the current WiFi status.""" + mode = get_current_wifi_mode() + + connections = get_wifi_connections() + known_networks = [c.name for c in connections if c.name != "Hotspot"] + + connected_network = next((c.name for c in connections if c.device != "--"), None) + + return WifiStatus( + mode=mode, + known_networks=known_networks, + connected_network=connected_network, + ) + + +@router.get("/error") +def get_last_wifi_error() -> dict[str, str | None]: + """Get the last WiFi error.""" + global error + if error is None: + return {"error": None} + return {"error": str(error)} + + +@router.post("/reset_error") +def reset_last_wifi_error() -> dict[str, str]: + """Reset the last WiFi error.""" + global error + error = None + return {"status": "ok"} + + +@router.post("/setup_hotspot") +def setup_hotspot( + ssid: str = HOTSPOT_SSID, + password: str = HOTSPOT_PASSWORD, +) -> None: + """Set up a WiFi hotspot. It will create a new hotspot using nmcli if one does not already exist.""" + if busy_lock.locked(): + raise HTTPException(status_code=409, detail="Another operation is in progress.") + + def hotspot() -> None: + with busy_lock: + setup_wifi_connection( + name="Hotspot", ssid=ssid, password=password, is_hotspot=True + ) + + Thread(target=hotspot).start() + # TODO: wait for it to be really started + + +@router.post("/connect") +def connect_to_wifi_network( + ssid: str, + password: str, +) -> None: + """Connect to a WiFi network. It will create a new connection using nmcli if the specified SSID is not already configured.""" + logger.warning(f"Request to connect to WiFi network '{ssid}' received.") + + if busy_lock.locked(): + raise HTTPException(status_code=409, detail="Another operation is in progress.") + + def connect() -> None: + global error + with busy_lock: + try: + error = None + setup_wifi_connection(name=ssid, ssid=ssid, password=password) + except Exception as e: + error = e + logger.error(f"Failed to connect to WiFi network '{ssid}': {e}") + logger.info("Reverting to hotspot...") + remove_connection(name=ssid) + setup_wifi_connection( + name="Hotspot", + ssid=HOTSPOT_SSID, + password=HOTSPOT_PASSWORD, + is_hotspot=True, + ) + + Thread(target=connect).start() + # TODO: wait for it to be really connected + + +@router.post("/scan_and_list") +def scan_wifi() -> list[str]: + """Scan for available WiFi networks ordered by signal power.""" + wifi = scan_available_wifi() + + seen = set() + ssids = [x.ssid for x in wifi if x.ssid not in seen and not seen.add(x.ssid)] # type: ignore + + return ssids + + +# NMCLI WRAPPERS +def scan_available_wifi() -> list[nmcli.data.device.DeviceWifi]: + """Scan for available WiFi networks.""" + nmcli.device.wifi_rescan() + devices: list[nmcli.data.device.DeviceWifi] = nmcli.device.wifi() + return devices + + +def get_wifi_connections() -> list[nmcli.data.connection.Connection]: + """Get the list of WiFi connection.""" + return [conn for conn in nmcli.connection() if conn.conn_type == "wifi"] + + +def check_if_connection_exists(name: str) -> bool: + """Check if a WiFi connection with the given SSID already exists.""" + return any(c.name == name for c in get_wifi_connections()) + + +def check_if_connection_active(name: str) -> bool: + """Check if a WiFi connection with the given SSID is currently active.""" + return any(c.name == name and c.device != "--" for c in get_wifi_connections()) + + +def setup_wifi_connection( + name: str, ssid: str, password: str, is_hotspot: bool = False +) -> None: + """Set up a WiFi connection using nmcli.""" + logger.info(f"Setting up WiFi connection (ssid='{ssid}')...") + + if not check_if_connection_exists(name): + logger.info("WiFi configuration does not exist. Creating...") + if is_hotspot: + nmcli.device.wifi_hotspot(ssid=ssid, password=password) + else: + nmcli.device.wifi_connect(ssid=ssid, password=password) + return + + logger.info("WiFi configuration already exists.") + if not check_if_connection_active(name): + logger.info("WiFi is not active. Activating...") + nmcli.connection.up(name) + return + + logger.info(f"Connection {name} is already active.") + + +def remove_connection(name: str) -> None: + """Remove a WiFi connection using nmcli.""" + if check_if_connection_exists(name): + logger.info(f"Removing WiFi connection '{name}'...") + nmcli.connection.delete(name) + + +# Setup WiFi connection on startup + +# This make sure the wlan0 is up and running +scan_available_wifi() + +# On startup, if no WiFi connection is active, set up the default hotspot +if get_current_wifi_mode() == WifiMode.DISCONNECTED: + logger.info("No WiFi connection active. Setting up hotspot...") + + setup_wifi_connection( + name="Hotspot", + ssid=HOTSPOT_SSID, + password=HOTSPOT_PASSWORD, + is_hotspot=True, + ) diff --git a/src/reachy_mini/daemon/app/services/__init__.py b/src/reachy_mini/daemon/app/services/__init__.py new file mode 100644 index 00000000..c7775ec9 --- /dev/null +++ b/src/reachy_mini/daemon/app/services/__init__.py @@ -0,0 +1 @@ +"""Services package.""" diff --git a/src/reachy_mini/daemon/app/services/bluetooth/bluetooth_service.py b/src/reachy_mini/daemon/app/services/bluetooth/bluetooth_service.py new file mode 100644 index 00000000..15dc4399 --- /dev/null +++ b/src/reachy_mini/daemon/app/services/bluetooth/bluetooth_service.py @@ -0,0 +1,454 @@ +#!/usr/bin/env python3 +"""Bluetooth service for Reachy Mini using direct DBus API. + +Includes a fixed NoInputNoOutput agent for automatic Just Works pairing. +""" +# mypy: ignore-errors + +import logging +import os +import subprocess +from typing import Callable + +import dbus +import dbus.mainloop.glib +import dbus.service +from gi.repository import GLib + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +# Service and Characteristic UUIDs +SERVICE_UUID = "12345678-1234-5678-1234-56789abcdef0" +COMMAND_CHAR_UUID = "12345678-1234-5678-1234-56789abcdef1" +RESPONSE_CHAR_UUID = "12345678-1234-5678-1234-56789abcdef2" + +BLUEZ_SERVICE_NAME = "org.bluez" +GATT_MANAGER_IFACE = "org.bluez.GattManager1" +DBUS_OM_IFACE = "org.freedesktop.DBus.ObjectManager" +DBUS_PROP_IFACE = "org.freedesktop.DBus.Properties" +GATT_SERVICE_IFACE = "org.bluez.GattService1" +GATT_CHRC_IFACE = "org.bluez.GattCharacteristic1" +LE_ADVERTISING_MANAGER_IFACE = "org.bluez.LEAdvertisingManager1" +LE_ADVERTISEMENT_IFACE = "org.bluez.LEAdvertisement1" +AGENT_PATH = "/org/bluez/agent" + + +# ======================= +# BLE Agent for Just Works +# ======================= +class NoInputAgent(dbus.service.Object): + """BLE Agent for Just Works pairing.""" + + @dbus.service.method("org.bluez.Agent1", in_signature="", out_signature="") + def Release(self, *args): + """Handle release of the agent.""" + logger.info("Agent released") + + @dbus.service.method("org.bluez.Agent1", in_signature="", out_signature="s") + def RequestPinCode(self, *args): + """Automatically provide an empty pin code for Just Works pairing.""" + logger.info(f"RequestPinCode called with args: {args}, returning empty") + return "" + + @dbus.service.method("org.bluez.Agent1", in_signature="", out_signature="u") + def RequestPasskey(self, *args): + """Automatically provide a passkey of 0 for Just Works pairing.""" + logger.info(f"RequestPasskey called with args: {args}, returning 0") + return dbus.UInt32(0) + + @dbus.service.method("org.bluez.Agent1", in_signature="", out_signature="") + def RequestConfirmation(self, *args): + """Automatically confirm the pairing request.""" + logger.info( + f"RequestConfirmation called with args: {args}, accepting automatically" + ) + return + + @dbus.service.method("org.bluez.Agent1", in_signature="", out_signature="") + def DisplayPinCode(self, *args): + """Handle displaying the pin code (not used in Just Works).""" + logger.info(f"DisplayPinCode called with args: {args}") + + @dbus.service.method("org.bluez.Agent1", in_signature="", out_signature="") + def DisplayPasskey(self, *args): + """Handle displaying the passkey (not used in Just Works).""" + logger.info(f"DisplayPasskey called with args: {args}") + + @dbus.service.method("org.bluez.Agent1", in_signature="", out_signature="") + def AuthorizeService(self, *args): + """Handle service authorization requests.""" + logger.info(f"AuthorizeService called with args: {args}") + + @dbus.service.method("org.bluez.Agent1", in_signature="", out_signature="") + def Cancel(self, *args): + """Handle cancellation of the agent request.""" + logger.info("Agent request canceled") + + +# ======================= +# BLE Advertisement +# ======================= +class Advertisement(dbus.service.Object): + """BLE Advertisement.""" + + PATH_BASE = "/org/bluez/advertisement" + + def __init__(self, bus, index, advertising_type, local_name): + """Initialize the Advertisement.""" + self.path = self.PATH_BASE + str(index) + self.bus = bus + self.ad_type = advertising_type + self.local_name = local_name + self.service_uuids = None + self.include_tx_power = False + dbus.service.Object.__init__(self, bus, self.path) + + def get_properties(self): + """Return the properties of the advertisement.""" + props = {"Type": self.ad_type} + if self.local_name: + props["LocalName"] = dbus.String(self.local_name) + if self.service_uuids: + props["ServiceUUIDs"] = dbus.Array(self.service_uuids, signature="s") + props["Appearance"] = dbus.UInt16(0x0000) + props["Duration"] = dbus.UInt16(0) + props["Timeout"] = dbus.UInt16(0) + return {LE_ADVERTISEMENT_IFACE: props} + + def get_path(self): + """Return the object path.""" + return dbus.ObjectPath(self.path) + + @dbus.service.method(DBUS_PROP_IFACE, in_signature="s", out_signature="a{sv}") + def GetAll(self, interface): + """Return all properties of the advertisement.""" + if interface != LE_ADVERTISEMENT_IFACE: + raise dbus.exceptions.DBusException( + "org.freedesktop.DBus.Error.InvalidArgs", + "Unknown interface " + interface, + ) + return self.get_properties()[LE_ADVERTISEMENT_IFACE] + + @dbus.service.method(LE_ADVERTISEMENT_IFACE, in_signature="", out_signature="") + def Release(self): + """Handle release of the advertisement.""" + logger.info("Advertisement released") + + +# ======================= +# BLE Characteristics & Service +# ======================= +class Characteristic(dbus.service.Object): + """GATT Characteristic.""" + + def __init__(self, bus, index, uuid, flags, service): + """Initialize the Characteristic.""" + self.path = service.path + "/char" + str(index) + self.bus = bus + self.uuid = uuid + self.service = service + self.flags = flags + self.value = [] + dbus.service.Object.__init__(self, bus, self.path) + + def get_properties(self): + """Return the properties of the characteristic.""" + return { + GATT_CHRC_IFACE: { + "Service": self.service.get_path(), + "UUID": self.uuid, + "Flags": self.flags, + } + } + + def get_path(self): + """Return the object path.""" + return dbus.ObjectPath(self.path) + + @dbus.service.method(DBUS_PROP_IFACE, in_signature="s", out_signature="a{sv}") + def GetAll(self, interface): + """Return all properties of the characteristic.""" + if interface != GATT_CHRC_IFACE: + raise dbus.exceptions.DBusException( + "org.freedesktop.DBus.Error.InvalidArgs", "Unknown interface" + ) + return self.get_properties()[GATT_CHRC_IFACE] + + @dbus.service.method(GATT_CHRC_IFACE, in_signature="a{sv}", out_signature="ay") + def ReadValue(self, options): + """Handle read from the characteristic.""" + return dbus.Array(self.value, signature="y") + + @dbus.service.method(GATT_CHRC_IFACE, in_signature="aya{sv}") + def WriteValue(self, value, options): + """Handle write to the characteristic.""" + self.value = value + + +class CommandCharacteristic(Characteristic): + """Command Characteristic.""" + + def __init__(self, bus, index, service, command_handler: Callable[[bytes], str]): + """Initialize the Command Characteristic.""" + super().__init__(bus, index, COMMAND_CHAR_UUID, ["write"], service) + self.command_handler = command_handler + + def WriteValue(self, value, options): + """Handle write to the Command Characteristic.""" + command_bytes = bytes(value) + response = self.command_handler(command_bytes) + self.service.response_char.value = [ + dbus.Byte(b) for b in response.encode("utf-8") + ] + logger.info(f"Command received: {response}") + + +class ResponseCharacteristic(Characteristic): + """Response Characteristic.""" "" + + def __init__(self, bus, index, service): + """Initialize the Response Characteristic.""" + super().__init__(bus, index, RESPONSE_CHAR_UUID, ["read", "notify"], service) + + +class Service(dbus.service.Object): + """GATT Service.""" + + PATH_BASE = "/org/bluez/service" + + def __init__( + self, bus, index, uuid, primary, command_handler: Callable[[bytes], str] + ): + """Initialize the GATT Service.""" + self.path = self.PATH_BASE + str(index) + self.bus = bus + self.uuid = uuid + self.primary = primary + self.characteristics = [] + dbus.service.Object.__init__(self, bus, self.path) + # Response characteristic first + self.response_char = ResponseCharacteristic(bus, 1, self) + self.add_characteristic(self.response_char) + # Command characteristic + self.add_characteristic(CommandCharacteristic(bus, 0, self, command_handler)) + + def get_properties(self): + """Return the properties of the service.""" + return { + GATT_SERVICE_IFACE: { + "UUID": self.uuid, + "Primary": self.primary, + "Characteristics": [ch.get_path() for ch in self.characteristics], + } + } + + def get_path(self): + """Return the object path.""" + return dbus.ObjectPath(self.path) + + def add_characteristic(self, ch): + """Add a characteristic to the service.""" + self.characteristics.append(ch) + + @dbus.service.method(DBUS_PROP_IFACE, in_signature="s", out_signature="a{sv}") + def GetAll(self, interface): + """Return all properties of the service.""" + if interface != GATT_SERVICE_IFACE: + raise dbus.exceptions.DBusException( + "org.freedesktop.DBus.Error.InvalidArgs", "Unknown interface" + ) + return self.get_properties()[GATT_SERVICE_IFACE] + + +class Application(dbus.service.Object): + """GATT Application.""" + + def __init__(self, bus, command_handler: Callable[[bytes], str]): + """Initialize the GATT Application.""" + self.path = "/" + self.services = [] + dbus.service.Object.__init__(self, bus, self.path) + self.services.append(Service(bus, 0, SERVICE_UUID, True, command_handler)) + + def get_path(self): + """Return the object path.""" + return dbus.ObjectPath(self.path) + + @dbus.service.method(DBUS_OM_IFACE, out_signature="a{oa{sa{sv}}}") + def GetManagedObjects(self): + """Return a dictionary of all managed objects.""" + resp = {} + for service in self.services: + resp[service.get_path()] = service.get_properties() + for ch in service.characteristics: + resp[ch.get_path()] = ch.get_properties() + return resp + + +# ======================= +# Bluetooth Command Server +# ======================= +class BluetoothCommandService: + """Bluetooth Command Service.""" + + def __init__(self, device_name="ReachyMini", pin_code="00000"): + """Initialize the Bluetooth Command Service.""" + self.device_name = device_name + self.pin_code = pin_code + self.connected = False + self.bus = None + self.app = None + self.adv = None + self.mainloop = None + + def _handle_command(self, value: bytes) -> str: + command_str = value.decode("utf-8").strip() + logger.info(f"Received command: {command_str}") + # Custom command handling + if command_str.upper() == "PING": + return "PONG" + elif command_str.upper() == "STATUS": + # exec a "sudo ls" command and print the result + try: + result = subprocess.run(["sudo", "ls"], capture_output=True, text=True) + logger.info(f"Command output: {result.stdout}") + except Exception as e: + logger.error(f"Error executing command: {e}") + return "OK: System running" + elif command_str.startswith("PIN_"): + pin = command_str[4:].strip() + if pin == self.pin_code: + self.connected = True + return "OK: Connected" + else: + return "ERROR: Incorrect PIN" + + # else if command starts with "CMD_xxxxx" check if commands directory contains the said named script command xxxx.sh and run its, show output or/and send to read + elif command_str.startswith("CMD_"): + if not self.connected: + return "ERROR: Not connected. Please authenticate first." + try: + script_name = command_str[4:].strip() + ".sh" + script_path = os.path.join("commands", script_name) + if os.path.isfile(script_path): + try: + result = subprocess.run( + ["sudo", script_path], capture_output=True, text=True + ) + logger.info(f"Command output: {result.stdout}") + except Exception as e: + logger.error(f"Error executing command: {e}") + else: + return f"ERROR: Command '{script_name}' not found" + except Exception as e: + logger.error(f"Error processing command: {e}") + return "ERROR: Command execution failed" + finally: + self.connected = False # reset connection after command + else: + return f"ECHO: {command_str}" + + def start(self): + """Start the Bluetooth Command Service.""" + dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) + self.bus = dbus.SystemBus() + + # BLE Agent registration + agent_manager = dbus.Interface( + self.bus.get_object("org.bluez", "/org/bluez"), "org.bluez.AgentManager1" + ) + # agent = NoInputAgent(self.bus, AGENT_PATH) + agent_manager.RegisterAgent(AGENT_PATH, "NoInputNoOutput") + agent_manager.RequestDefaultAgent(AGENT_PATH) + logger.info("BLE Agent registered for Just Works pairing") + + # Find adapter + adapter = self._find_adapter() + if not adapter: + raise Exception("Bluetooth adapter not found") + + adapter_props = dbus.Interface(adapter, DBUS_PROP_IFACE) + adapter_props.Set("org.bluez.Adapter1", "Powered", dbus.Boolean(True)) + adapter_props.Set("org.bluez.Adapter1", "Discoverable", dbus.Boolean(True)) + adapter_props.Set("org.bluez.Adapter1", "DiscoverableTimeout", dbus.UInt32(0)) + adapter_props.Set("org.bluez.Adapter1", "Pairable", dbus.Boolean(True)) + + # Register GATT application + service_manager = dbus.Interface(adapter, GATT_MANAGER_IFACE) + self.app = Application(self.bus, self._handle_command) + service_manager.RegisterApplication( + self.app.get_path(), + {}, + reply_handler=lambda: logger.info("GATT app registered"), + error_handler=lambda e: logger.error(f"Failed to register GATT app: {e}"), + ) + + # Register advertisement + ad_manager = dbus.Interface(adapter, LE_ADVERTISING_MANAGER_IFACE) + self.adv = Advertisement(self.bus, 0, "peripheral", self.device_name) + self.adv.service_uuids = [SERVICE_UUID] + ad_manager.RegisterAdvertisement( + self.adv.get_path(), + {}, + reply_handler=lambda: logger.info("Advertisement registered"), + error_handler=lambda e: logger.error( + f"Failed to register advertisement: {e}" + ), + ) + + logger.info(f"✓ Bluetooth service started as '{self.device_name}'") + + def _find_adapter(self): + remote_om = dbus.Interface( + self.bus.get_object(BLUEZ_SERVICE_NAME, "/"), DBUS_OM_IFACE + ) + objects = remote_om.GetManagedObjects() + for path, props in objects.items(): + if GATT_MANAGER_IFACE in props and LE_ADVERTISING_MANAGER_IFACE in props: + return self.bus.get_object(BLUEZ_SERVICE_NAME, path) + return None + + def run(self): + """Run the Bluetooth Command Service.""" + self.start() + self.mainloop = GLib.MainLoop() + try: + logger.info("Running. Press Ctrl+C to exit...") + self.mainloop.run() + except KeyboardInterrupt: + logger.info("Shutting down...") + self.mainloop.quit() + + +def get_pin() -> str: + """Extract the last 5 digits of the serial number from dfu-util -l output.""" + default_pin = "46879" + try: + result = subprocess.run(["dfu-util", "-l"], capture_output=True, text=True) + lines = result.stdout.splitlines() + for line in lines: + if "serial=" in line: + # Extract serial number + serial_part = line.split("serial=")[-1].strip().strip('"') + if len(serial_part) >= 5: + return serial_part[-5:] + return default_pin # fallback if not found + except Exception as e: + logger.error(f"Error getting pin from serial: {e}") + return default_pin + + +# ======================= +# Main +# ======================= +def main(): + """Run the Bluetooth Command Service.""" + pin = get_pin() + + bt_service = BluetoothCommandService(device_name="ReachyMini", pin_code=pin) + bt_service.run() + + +if __name__ == "__main__": + main() diff --git a/src/reachy_mini/daemon/app/services/bluetooth/commands/HOTSPOT.sh b/src/reachy_mini/daemon/app/services/bluetooth/commands/HOTSPOT.sh new file mode 100755 index 00000000..7d64481d --- /dev/null +++ b/src/reachy_mini/daemon/app/services/bluetooth/commands/HOTSPOT.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +nmcli device disconnect wlan0 +sleep 5 +rfkill unblock wifi +systemctl restart reachy-mini-daemon.service + diff --git a/src/reachy_mini/daemon/app/services/bluetooth/commands/RESTART_DAEMON.sh b/src/reachy_mini/daemon/app/services/bluetooth/commands/RESTART_DAEMON.sh new file mode 100755 index 00000000..3cf28b38 --- /dev/null +++ b/src/reachy_mini/daemon/app/services/bluetooth/commands/RESTART_DAEMON.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + + systemctl restart reachy-mini-daemon.service + diff --git a/src/reachy_mini/daemon/app/services/bluetooth/commands/SOFTWARE_RESET.sh b/src/reachy_mini/daemon/app/services/bluetooth/commands/SOFTWARE_RESET.sh new file mode 100755 index 00000000..d1ae109a --- /dev/null +++ b/src/reachy_mini/daemon/app/services/bluetooth/commands/SOFTWARE_RESET.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +rm -rf /venvs/ +cp -r /restore/venvs/ / +chown -R pollen:pollen /venvs +systemctl restart reachy-mini-daemon.service + diff --git a/src/reachy_mini/daemon/app/services/bluetooth/install_service_bluetooth.sh b/src/reachy_mini/daemon/app/services/bluetooth/install_service_bluetooth.sh new file mode 100755 index 00000000..fcd3ae24 --- /dev/null +++ b/src/reachy_mini/daemon/app/services/bluetooth/install_service_bluetooth.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +# Get the directory where this script is located +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +SERVICE_NAME="reachy-mini-bluetooth" +SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service" +LAUNCHER_PATH="$SCRIPT_DIR/bluetooth_service.py" +COMMANDS_DIR="$SCRIPT_DIR/commands" +SERVICE_PATH=/bluetooth/bluetooth_service.py + +sudo cp "$LAUNCHER_PATH" "$SERVICE_PATH" +sudo cp -r "$COMMANDS_DIR" /bluetooth/commands +# Ensure Python script is executable +sudo chmod +x "$SERVICE_PATH" + +# Create the systemd service file +cat < /dev/null +[Unit] +Description=Reachy Mini Bluetooth GATT Service +After=network.target bluetooth.target +Requires=bluetooth.target + +[Service] +Type=simple +ExecStart=/bin/bash -c 'sudo /usr/sbin/rfkill unblock bluetooth && sleep 2 && /usr/bin/python3 /bluetooth/bluetooth_service.py' +Restart=on-failure +User=$(whoami) +WorkingDirectory=$(dirname "$SERVICE_PATH") + +[Install] +WantedBy=multi-user.target +EOF + +# Reload systemd, enable and start the service +sudo systemctl daemon-reload +sudo systemctl enable --now "$SERVICE_NAME" + +echo "Service '$SERVICE_NAME' installed and started." diff --git a/src/reachy_mini/daemon/app/services/gpio_shutdown/__init__.py b/src/reachy_mini/daemon/app/services/gpio_shutdown/__init__.py new file mode 100644 index 00000000..b912651b --- /dev/null +++ b/src/reachy_mini/daemon/app/services/gpio_shutdown/__init__.py @@ -0,0 +1 @@ +"""GPIO shutdown service package.""" diff --git a/src/reachy_mini/daemon/app/services/gpio_shutdown/install_service.sh b/src/reachy_mini/daemon/app/services/gpio_shutdown/install_service.sh new file mode 100644 index 00000000..4d8e10fa --- /dev/null +++ b/src/reachy_mini/daemon/app/services/gpio_shutdown/install_service.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +# Get the directory where this script is located +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +SERVICE_NAME="gpio-shutdown-daemon" +SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service" +LAUNCHER_PATH="$SCRIPT_DIR/launcher.sh" + +# Create the service file +cat < /dev/null +[Unit] +Description=Reachy Mini GPIO Shutdown Daemon +After=multi-user.target + +[Service] +Type=simple +ExecStart=$LAUNCHER_PATH +Restart=on-failure +User=$(whoami) +WorkingDirectory=$(dirname "$LAUNCHER_PATH") + +[Install] +WantedBy=multi-user.target +EOF + +chmod +x $LAUNCHER_PATH + +# Reload systemd, enable and start the service +sudo systemctl daemon-reload +sudo systemctl enable --now $SERVICE_NAME + +echo "Service '$SERVICE_NAME' installed and started." \ No newline at end of file diff --git a/src/reachy_mini/daemon/app/services/gpio_shutdown/launcher.sh b/src/reachy_mini/daemon/app/services/gpio_shutdown/launcher.sh new file mode 100755 index 00000000..7d62c512 --- /dev/null +++ b/src/reachy_mini/daemon/app/services/gpio_shutdown/launcher.sh @@ -0,0 +1,3 @@ +#!/bin/bash +source /venvs/mini_daemon/bin/activate +python -m reachy_mini.daemon.app.services.gpio_shutdown.shutdown_monitor diff --git a/src/reachy_mini/daemon/app/services/gpio_shutdown/shutdown_monitor.py b/src/reachy_mini/daemon/app/services/gpio_shutdown/shutdown_monitor.py new file mode 100644 index 00000000..8c9788a4 --- /dev/null +++ b/src/reachy_mini/daemon/app/services/gpio_shutdown/shutdown_monitor.py @@ -0,0 +1,20 @@ +"""Monitor GPIO24 for shutdown signal.""" + +from signal import pause +from subprocess import call + +from gpiozero import Button + +shutdown_button = Button(23, pull_up=False) + + +def released() -> None: + """Handle shutdown button released.""" + print("Shutdown button released, shutting down...") + call(["sudo", "shutdown", "-h", "now"]) + + +shutdown_button.when_released = released + +print("Monitoring GPIO23 for shutdown signal...") +pause() diff --git a/src/reachy_mini/daemon/app/services/wireless/install_service.sh b/src/reachy_mini/daemon/app/services/wireless/install_service.sh new file mode 100755 index 00000000..9e9e9b1c --- /dev/null +++ b/src/reachy_mini/daemon/app/services/wireless/install_service.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +# Get the directory where this script is located +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +SERVICE_NAME="reachy-mini-daemon" +SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service" +LAUNCHER_PATH="$SCRIPT_DIR/launcher.sh" + +# Create the service file +cat < /dev/null +[Unit] +Description=Reachy Mini AP Launcher Service +After=network.target + +[Service] +Type=simple +ExecStart=$LAUNCHER_PATH +Restart=on-failure +User=$(whoami) +WorkingDirectory=$(dirname "$LAUNCHER_PATH") + +[Install] +WantedBy=multi-user.target +EOF + +chmod +x $LAUNCHER_PATH + +# Reload systemd, enable and start the service +sudo systemctl daemon-reload +sudo systemctl enable --now $SERVICE_NAME + +echo "Service '$SERVICE_NAME' installed and started." \ No newline at end of file diff --git a/src/reachy_mini/daemon/app/services/wireless/launcher.sh b/src/reachy_mini/daemon/app/services/wireless/launcher.sh new file mode 100755 index 00000000..43674af8 --- /dev/null +++ b/src/reachy_mini/daemon/app/services/wireless/launcher.sh @@ -0,0 +1,4 @@ +#!/bin/bash +source /venvs/mini_daemon/bin/activate +export GST_PLUGIN_PATH=$GST_PLUGIN_PATH:/opt/gst-plugins-rs/lib/aarch64-linux-gnu/ +python -m reachy_mini.daemon.app.main --wireless-version --stream --no-autostart diff --git a/src/reachy_mini/daemon/app/templates/dashboard.html b/src/reachy_mini/daemon/app/templates/dashboard.html deleted file mode 100644 index f667c514..00000000 --- a/src/reachy_mini/daemon/app/templates/dashboard.html +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - Dashboard - - - - -

        Dashboard

        -
        -

        Daemon Control

        -
        Loading daemon status...
        - - - - - -
        -
        -

        Video stream

        -
        -
        - -
        -
        - - -

        Examples

        -
          - {% for fname in files %} -
        • {{ fname }}
        • - {% endfor %} -
        - - - - - \ No newline at end of file diff --git a/src/reachy_mini/daemon/backend/abstract.py b/src/reachy_mini/daemon/backend/abstract.py index 742f6a8c..2d1a0535 100644 --- a/src/reachy_mini/daemon/backend/abstract.py +++ b/src/reachy_mini/daemon/backend/abstract.py @@ -13,14 +13,21 @@ import logging import threading import time +import typing from abc import abstractmethod from enum import Enum from pathlib import Path -from typing import List +from typing import Annotated, Any, Dict, Optional import numpy as np +import zenoh +from numpy.typing import NDArray from scipy.spatial.transform import Rotation as R +if typing.TYPE_CHECKING: + from reachy_mini.daemon.backend.mujoco.backend import MujocoBackendStatus + from reachy_mini.daemon.backend.robot.backend import RobotBackendStatus + from reachy_mini.kinematics import AnyKinematics from reachy_mini.media.audio_sounddevice import SoundDeviceAudio from reachy_mini.motion.goto import GotoMove from reachy_mini.motion.move import Move @@ -48,11 +55,14 @@ def __init__( log_level: str = "INFO", check_collision: bool = False, kinematics_engine: str = "AnalyticalKinematics", + use_audio: bool = True, ) -> None: """Initialize the backend.""" self.logger = logging.getLogger(__name__) self.logger.setLevel(log_level) + self.use_audio = use_audio + self.should_stop = threading.Event() self.ready = threading.Event() @@ -78,7 +88,7 @@ def __init__( if self.kinematics_engine == "Placo": from reachy_mini.kinematics import PlacoKinematics - self.head_kinematics = PlacoKinematics( + self.head_kinematics: AnyKinematics = PlacoKinematics( URDF_ROOT_PATH, check_collision=self.check_collision ) elif self.kinematics_engine == "NN": @@ -94,27 +104,46 @@ def __init__( f"Unknown kinematics engine: {self.kinematics_engine}. Use 'Placo', 'NN' or 'AnalyticalKinematics'." ) - self.current_head_pose = None # 4x4 pose matrix - self.target_head_pose = None # 4x4 pose matrix - self.target_body_yaw = None # Last body yaw used in IK computations - - self.target_head_joint_positions = None # [yaw, 0, 1, 2, 3, 4, 5] - self.current_head_joint_positions = None # [yaw, 0, 1, 2, 3, 4, 5] - self.target_antenna_joint_positions = None # [0, 1] - self.current_antenna_joint_positions = None # [0, 1] + self.current_head_pose: Annotated[NDArray[np.float64], (4, 4)] | None = ( + None # 4x4 pose matrix + ) + self.target_head_pose: Annotated[NDArray[np.float64], (4, 4)] | None = ( + None # 4x4 pose matrix + ) + self.target_body_yaw: float | None = ( + None # Last body yaw used in IK computations + ) - self.joint_positions_publisher = None # Placeholder for a publisher object - self.pose_publisher = None # Placeholder for a pose publisher object - self.recording_publisher = None # Placeholder for a recording publisher object - self.error = None # To store any error that occurs during execution + self.target_head_joint_positions: ( + Annotated[NDArray[np.float64], (7,)] | None + ) = None # [yaw, 0, 1, 2, 3, 4, 5] + self.current_head_joint_positions: ( + Annotated[NDArray[np.float64], (7,)] | None + ) = None # [yaw, 0, 1, 2, 3, 4, 5] + self.target_antenna_joint_positions: ( + Annotated[NDArray[np.float64], (2,)] | None + ) = None # [0, 1] + self.current_antenna_joint_positions: ( + Annotated[NDArray[np.float64], (2,)] | None + ) = None # [0, 1] + + self.joint_positions_publisher: zenoh.Publisher | None = None + self.pose_publisher: zenoh.Publisher | None = None + self.recording_publisher: zenoh.Publisher | None = None + self.error: str | None = None # To store any error that occurs during execution self.is_recording = False # Flag to indicate if recording is active - self.recorded_data = [] # List to store recorded data + self.recorded_data: list[dict[str, Any]] = [] # List to store recorded data # variables to store the last computed head joint positions and pose - self._last_target_body_yaw = None # Last body yaw used in IK computations - self._last_target_head_pose = None # Last head pose used in IK computations - self.target_head_joint_current = None # Placeholder for head joint torque - self.target_head_operation_mode = None # Placeholder for head operation mode + self._last_target_body_yaw: float | None = ( + None # Last body yaw used in IK computations + ) + self._last_target_head_pose: Annotated[NDArray[np.float64], (4, 4)] | None = ( + None # Last head pose used in IK computations + ) + self.target_head_joint_current: Annotated[NDArray[np.float64], (7,)] | None = ( + None # Placeholder for head joint torque + ) self.ik_required = False # Flag to indicate if IK computation is required self.is_shutting_down = False @@ -133,10 +162,13 @@ def __init__( # Recording lock to guard buffer swaps and appends self._rec_lock = threading.Lock() - self.audio = SoundDeviceAudio(log_level=log_level) + if self.use_audio: + self.audio = SoundDeviceAudio(log_level=log_level) + else: + self.audio = None # type: ignore # Life cycle methods - def wrapped_run(self): + def wrapped_run(self) -> None: """Run the backend in a try-except block to store errors.""" try: self.run() @@ -145,7 +177,7 @@ def wrapped_run(self): self.close() raise e - def run(self): + def run(self) -> None: """Run the backend. This method is a placeholder and should be overridden by subclasses. @@ -161,7 +193,7 @@ def close(self) -> None: "The method close should be overridden by subclasses." ) - def get_status(self): + def get_status(self) -> "RobotBackendStatus | MujocoBackendStatus": """Return backend statistics. This method is a placeholder and should be overridden by subclasses. @@ -171,7 +203,7 @@ def get_status(self): ) # Present/Target joint positions - def set_joint_positions_publisher(self, publisher) -> None: + def set_joint_positions_publisher(self, publisher: zenoh.Publisher) -> None: """Set the publisher for joint positions. Args: @@ -180,7 +212,7 @@ def set_joint_positions_publisher(self, publisher) -> None: """ self.joint_positions_publisher = publisher - def set_pose_publisher(self, publisher) -> None: + def set_pose_publisher(self, publisher: zenoh.Publisher) -> None: """Set the publisher for head pose. Args: @@ -190,7 +222,9 @@ def set_pose_publisher(self, publisher) -> None: self.pose_publisher = publisher def update_target_head_joints_from_ik( - self, pose: np.ndarray | None = None, body_yaw: float | None = None + self, + pose: Annotated[NDArray[np.float64], (4, 4)] | None = None, + body_yaw: float | None = None, ) -> None: """Update the target head joint positions from inverse kinematics. @@ -222,21 +256,32 @@ def update_target_head_joints_from_ik( def set_target_head_pose( self, - pose: np.ndarray, - body_yaw: float = 0.0, + pose: Annotated[NDArray[np.float64], (4, 4)], ) -> None: """Set the target head pose for the robot. Args: pose (np.ndarray): 4x4 pose matrix representing the head pose. - body_yaw (float): The yaw angle of the body, used to adjust the head pose. """ self.target_head_pose = pose - self.target_body_yaw = body_yaw self.ik_required = True - def set_target_head_joint_positions(self, positions: List[float]) -> None: + def set_target_body_yaw(self, body_yaw: float) -> None: + """Set the target body yaw for the robot. + + Only used when doing a set_target() with a standalone body_yaw (no head pose). + + Args: + body_yaw (float): The yaw angle of the body + + """ + self.target_body_yaw = body_yaw + self.ik_required = True # Do we need that here? + + def set_target_head_joint_positions( + self, positions: Annotated[NDArray[np.float64], (7,)] | None + ) -> None: """Set the head joint positions. Args: @@ -248,24 +293,24 @@ def set_target_head_joint_positions(self, positions: List[float]) -> None: def set_target( self, - head: np.ndarray | None = None, # 4x4 pose matrix - antennas: np.ndarray - | list[float] - | None = None, # [left_angle, right_angle] (in rads) - body_yaw: float = 0.0, # Body yaw angle in radians + head: Annotated[NDArray[np.float64], (4, 4)] | None = None, # 4x4 pose matrix + antennas: Annotated[NDArray[np.float64], (2,)] + | None = None, # [right_angle, left_angle] (in rads) + body_yaw: float | None = None, # Body yaw angle in radians ) -> None: - """Set the target head pose and/or antenna positions.""" + """Set the target head pose and/or antenna positions and/or body_yaw.""" if head is not None: - self.set_target_head_pose(head, body_yaw) + self.set_target_head_pose(head) + + if body_yaw is not None: + self.set_target_body_yaw(body_yaw) if antennas is not None: - if isinstance(antennas, np.ndarray): - antennas = antennas.tolist() self.set_target_antenna_joint_positions(antennas) def set_target_antenna_joint_positions( self, - positions: List[float], + positions: Annotated[NDArray[np.float64], (2,)], ) -> None: """Set the antenna joint positions. @@ -275,11 +320,14 @@ def set_target_antenna_joint_positions( """ self.target_antenna_joint_positions = positions - def set_target_head_joint_current(self, current: List[float]) -> None: + def set_target_head_joint_current( + self, + current: Annotated[NDArray[np.float64], (7,)], + ) -> None: """Set the head joint current. Args: - current (List[float]): A list of current values for the head motors. + current (Annotated[NDArray[np.float64], (7,)]): A list of current values for the head motors. """ self.target_head_joint_current = current @@ -317,12 +365,11 @@ async def play_move( head, antennas, body_yaw = move.evaluate(t) if head is not None: - self.set_target_head_pose( - head, - body_yaw=body_yaw if body_yaw is not None else 0.0, - ) + self.set_target_head_pose(head) + if body_yaw is not None: + self.set_target_body_yaw(body_yaw) if antennas is not None: - self.set_target_antenna_joint_positions(list(antennas)) + self.set_target_antenna_joint_positions(antennas) elapsed = time.time() - t0 - t if elapsed < sleep_period: @@ -332,14 +379,13 @@ async def play_move( async def goto_target( self, - head: np.ndarray | None = None, # 4x4 pose matrix - antennas: np.ndarray - | list[float] - | None = None, # [left_angle, right_angle] (in rads) + head: Annotated[NDArray[np.float64], (4, 4)] | None = None, # 4x4 pose matrix + antennas: Annotated[NDArray[np.float64], (2,)] + | None = None, # [right_angle, left_angle] (in rads) duration: float = 0.5, # Duration in seconds for the movement, default is 0.5 seconds. method: InterpolationTechnique = InterpolationTechnique.MIN_JERK, # can be "linear", "minjerk", "ease" or "cartoon", default is "minjerk" - body_yaw: float = 0.0, # Body yaw angle in radians - ): + body_yaw: float | None = 0.0, # Body yaw angle in radians + ) -> None: """Asynchronously go to a target head pose and/or antennas position using task space interpolation, in "duration" seconds. Args: @@ -347,7 +393,7 @@ async def goto_target( antennas (np.ndarray | list[float] | None): 1D array with two elements representing the angles of the antennas in radians. duration (float): Duration of the movement in seconds. method (str): Interpolation method to use ("linear", "minjerk", "ease", "cartoon"). Default is "minjerk". - body_yaw (float): Body yaw angle in radians. + body_yaw (float | None): Body yaw angle in radians. Raises: ValueError: If neither head nor antennas are provided, or if duration is not positive. @@ -371,7 +417,7 @@ async def goto_joint_positions( head_joint_positions: list[float] | None = None, # [yaw, stewart_platform x 6] length 7 antennas_joint_positions: list[float] - | None = None, # [left_angle, right_angle] length 2 + | None = None, # [right_angle, left_angle] length 2 duration: float = 0.5, # Duration in seconds for the movement method: InterpolationTechnique = InterpolationTechnique.MIN_JERK, # can be "linear", "minjerk", "ease" or "cartoon", default is "minjerk" ) -> None: @@ -419,11 +465,11 @@ async def goto_joint_positions( start_antennas + (target_antennas - start_antennas) * interp_time ) - self.set_target_head_joint_positions(head_joint.tolist()) - self.set_target_antenna_joint_positions(antennas_joint.tolist()) + self.set_target_head_joint_positions(head_joint) + self.set_target_antenna_joint_positions(antennas_joint) await asyncio.sleep(0.01) - def set_recording_publisher(self, publisher) -> None: + def set_recording_publisher(self, publisher: zenoh.Publisher) -> None: """Set the publisher for recording data. Args: @@ -432,7 +478,7 @@ def set_recording_publisher(self, publisher) -> None: """ self.recording_publisher = publisher - def append_record(self, record: dict) -> None: + def append_record(self, record: dict[str, Any]) -> None: """Append a record to the recorded data. Args: @@ -466,7 +512,7 @@ def stop_recording(self) -> None: "stop_recording called but recording_publisher is not set; dropping data." ) - def get_present_head_joint_positions(self) -> List[float]: + def get_present_head_joint_positions(self) -> Annotated[NDArray[np.float64], (7,)]: """Return the present head joint positions. This method is a placeholder and should be overridden by subclasses. @@ -477,20 +523,23 @@ def get_present_head_joint_positions(self) -> List[float]: def get_present_body_yaw(self) -> float: """Return the present body yaw.""" - return self.get_present_head_joint_positions()[0] + yaw: float = self.get_present_head_joint_positions()[0] + return yaw - def get_present_head_pose(self) -> np.ndarray: + def get_present_head_pose(self) -> Annotated[NDArray[np.float64], (4, 4)]: """Return the present head pose as a 4x4 matrix.""" assert self.current_head_pose is not None, ( "The current head pose is not set. Please call the update_head_kinematics_model method first." ) return self.current_head_pose - def get_current_head_pose(self) -> np.ndarray: + def get_current_head_pose(self) -> Annotated[NDArray[np.float64], (4, 4)]: """Return the present head pose as a 4x4 matrix.""" return self.get_present_head_pose() - def get_present_antenna_joint_positions(self) -> List[float]: + def get_present_antenna_joint_positions( + self, + ) -> Annotated[NDArray[np.float64], (2,)]: """Return the present antenna joint positions. This method is a placeholder and should be overridden by subclasses. @@ -502,8 +551,8 @@ def get_present_antenna_joint_positions(self) -> List[float]: # Kinematics methods def update_head_kinematics_model( self, - head_joint_positions: List[float] | None = None, - antennas_joint_positions: List[float] | None = None, + head_joint_positions: Annotated[NDArray[np.float64], (7,)] | None = None, + antennas_joint_positions: Annotated[NDArray[np.float64], (2,)] | None = None, ) -> None: """Update the placo kinematics of the robot. @@ -543,18 +592,18 @@ def update_head_kinematics_model( if antennas_joint_positions is not None: self.current_antenna_joint_positions = antennas_joint_positions - def set_automatic_body_yaw(self, body_yaw: float) -> None: + def set_automatic_body_yaw(self, body_yaw: bool) -> None: """Set the automatic body yaw. Args: - body_yaw (float): The yaw angle of the body. + body_yaw (bool): The yaw angle of the body. """ - self.head_kinematics.start_body_yaw = body_yaw + self.head_kinematics.set_automatic_body_yaw(automatic_body_yaw=body_yaw) def get_urdf(self) -> str: """Get the URDF representation of the robot.""" - urdf_path = Path(self.urdf_root_path) / "robot.urdf" + urdf_path = Path(URDF_ROOT_PATH) / "robot.urdf" with open(urdf_path, "r") as f: return f.read() @@ -566,10 +615,12 @@ def play_sound(self, sound_file: str) -> None: If the file is not found in the assets directory, try to load the path itself. Args: - sound_file (str): The name of the sound file to play (e.g., "proud2.wav"). + sound_file (str): The name of the sound file to play (e.g., "wake_up.wav"). """ - self.audio.play_sound(sound_file) + if self.audio: + self.audio.start_playing() + self.audio.play_sound(sound_file) # Basic move definitions INIT_HEAD_POSE = np.eye(4) @@ -584,7 +635,7 @@ def play_sound(self, sound_file: str) -> None: 1.0032234352772091, ] - SLEEP_ANTENNAS_JOINT_POSITIONS = [3.05, -3.05] + SLEEP_ANTENNAS_JOINT_POSITIONS = np.array((-3.05, 3.05)) SLEEP_HEAD_POSE = np.array( [ [0.911, 0.004, 0.413, -0.021], @@ -604,13 +655,13 @@ async def wake_up(self) -> None: await self.goto_target( self.INIT_HEAD_POSE, - antennas=[0.0, 0.0], + antennas=np.array((0.0, 0.0)), duration=magic_distance * 20 / 1000, # ms_per_magic_mm = 10 ) await asyncio.sleep(0.1) # Toudoum - self.play_sound("proud2.wav") + self.play_sound("wake_up.wav") # Roll 20° to the left pose = self.INIT_HEAD_POSE.copy() @@ -619,6 +670,8 @@ async def wake_up(self) -> None: # Go back to the initial position await self.goto_target(self.INIT_HEAD_POSE, duration=0.2) + if self.audio: + self.audio.stop_playing() async def goto_sleep(self) -> None: """Put the robot to sleep by moving the head and antennas to a predefined sleep position. @@ -642,7 +695,7 @@ async def goto_sleep(self) -> None: if dist_to_init_pose > 30: # Move to the initial position await self.goto_target( - self.INIT_HEAD_POSE, antennas=[0.0, 0.0], duration=1 + self.INIT_HEAD_POSE, antennas=np.array((0.0, 0.0)), duration=1 ) await asyncio.sleep(0.2) @@ -672,3 +725,41 @@ def get_motor_control_mode(self) -> MotorControlMode: def set_motor_control_mode(self, mode: MotorControlMode) -> None: """Set the motor control mode.""" pass + + @abstractmethod + def set_motor_torque_ids(self, ids: list[str], on: bool) -> None: + """Set the motor torque for specific motor names.""" + pass + + def get_present_passive_joint_positions(self) -> Optional[Dict[str, float]]: + """Get the present passive joint positions. + + Requires the Placo kinematics engine. + """ + # This is would be better, and fix mypy issues, but Placo is dynamically imported + # if not isinstance(self.head_kinematics, PlacoKinematics): + if self.kinematics_engine != "Placo": + return None + return { + "passive_1_x": self.head_kinematics.get_joint("passive_1_x"), # type: ignore [union-attr] + "passive_1_y": self.head_kinematics.get_joint("passive_1_y"), # type: ignore [union-attr] + "passive_1_z": self.head_kinematics.get_joint("passive_1_z"), # type: ignore [union-attr] + "passive_2_x": self.head_kinematics.get_joint("passive_2_x"), # type: ignore [union-attr] + "passive_2_y": self.head_kinematics.get_joint("passive_2_y"), # type: ignore [union-attr] + "passive_2_z": self.head_kinematics.get_joint("passive_2_z"), # type: ignore [union-attr] + "passive_3_x": self.head_kinematics.get_joint("passive_3_x"), # type: ignore [union-attr] + "passive_3_y": self.head_kinematics.get_joint("passive_3_y"), # type: ignore [union-attr] + "passive_3_z": self.head_kinematics.get_joint("passive_3_z"), # type: ignore [union-attr] + "passive_4_x": self.head_kinematics.get_joint("passive_4_x"), # type: ignore [union-attr] + "passive_4_y": self.head_kinematics.get_joint("passive_4_y"), # type: ignore [union-attr] + "passive_4_z": self.head_kinematics.get_joint("passive_4_z"), # type: ignore [union-attr] + "passive_5_x": self.head_kinematics.get_joint("passive_5_x"), # type: ignore [union-attr] + "passive_5_y": self.head_kinematics.get_joint("passive_5_y"), # type: ignore [union-attr] + "passive_5_z": self.head_kinematics.get_joint("passive_5_z"), # type: ignore [union-attr] + "passive_6_x": self.head_kinematics.get_joint("passive_6_x"), # type: ignore [union-attr] + "passive_6_y": self.head_kinematics.get_joint("passive_6_y"), # type: ignore [union-attr] + "passive_6_z": self.head_kinematics.get_joint("passive_6_z"), # type: ignore [union-attr] + "passive_7_x": self.head_kinematics.get_joint("passive_7_x"), # type: ignore [union-attr] + "passive_7_y": self.head_kinematics.get_joint("passive_7_y"), # type: ignore [union-attr] + "passive_7_z": self.head_kinematics.get_joint("passive_7_z"), # type: ignore [union-attr] + } diff --git a/src/reachy_mini/daemon/backend/mujoco/__init__.py b/src/reachy_mini/daemon/backend/mujoco/__init__.py index abbc7278..78ed8a96 100644 --- a/src/reachy_mini/daemon/backend/mujoco/__init__.py +++ b/src/reachy_mini/daemon/backend/mujoco/__init__.py @@ -6,8 +6,8 @@ import mujoco # noqa: F401 from reachy_mini.daemon.backend.mujoco.backend import ( - MujocoBackend, # noqa: F401 - MujocoBackendStatus, # noqa: F401 + MujocoBackend, + MujocoBackendStatus, ) except ImportError: @@ -15,7 +15,7 @@ class MujocoMockupBackend: """Mockup class to avoid import errors when MuJoCo is not installed.""" - def __init__(self, *args, **kwargs) -> None: + def __init__(self, *args, **kwargs) -> None: # type: ignore[no-untyped-def] """Raise ImportError when trying to instantiate the class.""" raise ImportError( "MuJoCo is not installed. MuJoCo backend is not available." @@ -23,7 +23,7 @@ def __init__(self, *args, **kwargs) -> None: " with 'pip install reachy_mini[mujoco]'." ) - MujocoBackend = MujocoMockupBackend + MujocoBackend = MujocoMockupBackend # type: ignore[assignment, misc] @dataclass class MujocoMockupBackendStatus: @@ -31,4 +31,6 @@ class MujocoMockupBackendStatus: pass - MujocoBackendStatus = MujocoMockupBackendStatus + MujocoBackendStatus = MujocoMockupBackendStatus # type: ignore[assignment, misc] + +__all__ = ["MujocoBackend", "MujocoBackendStatus"] diff --git a/src/reachy_mini/daemon/backend/mujoco/backend.py b/src/reachy_mini/daemon/backend/mujoco/backend.py index 5586f79b..9ca2129f 100644 --- a/src/reachy_mini/daemon/backend/mujoco/backend.py +++ b/src/reachy_mini/daemon/backend/mujoco/backend.py @@ -11,12 +11,17 @@ from dataclasses import dataclass from importlib.resources import files from threading import Thread +from typing import Annotated, Any, Optional +import cv2 +import log_throttling import mujoco import mujoco.viewer import numpy as np +import numpy.typing as npt import reachy_mini +from reachy_mini.io.video_ws import AsyncWebSocketFrameSender from ..abstract import Backend, MotorControlMode from .utils import ( @@ -26,17 +31,23 @@ ) from .video_udp import UDPJPEGFrameSender +CAMERA_REACHY = 'eye_camera' +CAMERA_STUDIO_CLOSE = 'studio_close' +CAMERA_SIZES = {CAMERA_REACHY: (1280, 720), CAMERA_STUDIO_CLOSE: (640, 640)} + class MujocoBackend(Backend): """Simulated Reachy Mini using MuJoCo.""" def __init__( self, - scene="empty", + scene: str = "empty", check_collision: bool = False, kinematics_engine: str = "AnalyticalKinematics", headless: bool = False, - ): + use_audio: bool = False, + websocket_uri: Optional[str] = None, + ) -> None: """Initialize the MujocoBackend with a specified scene. Args: @@ -44,64 +55,51 @@ def __init__( check_collision (bool): If True, enable collision checking. Default is False. kinematics_engine (str): Kinematics engine to use. Defaults to "AnalyticalKinematics". headless (bool): If True, run Mujoco in headless mode (no GUI). Default is False. + use_audio (bool): If True, use audio. Default is False. + websocket_uri (Optional[str]): If set, allow streaming of the robot view through a WebSocket connection to the specified uri. Defaults to None. """ super().__init__( - check_collision=check_collision, kinematics_engine=kinematics_engine + check_collision=check_collision, + kinematics_engine=kinematics_engine, + use_audio=use_audio, ) self.headless = headless + self.websocket_uri = websocket_uri from reachy_mini.reachy_mini import ( SLEEP_ANTENNAS_JOINT_POSITIONS, SLEEP_HEAD_JOINT_POSITIONS, ) - self._SLEEP_ANTENNAS_JOINT_POSITIONS = SLEEP_ANTENNAS_JOINT_POSITIONS + # Real robot convention for the order of the antennas joints is [right, left], but in mujoco it's [left, right] + self._SLEEP_ANTENNAS_JOINT_POSITIONS = [ + SLEEP_ANTENNAS_JOINT_POSITIONS[1], + SLEEP_ANTENNAS_JOINT_POSITIONS[0], + ] self._SLEEP_HEAD_JOINT_POSITIONS = SLEEP_HEAD_JOINT_POSITIONS mjcf_root_path = str( files(reachy_mini).joinpath("descriptions/reachy_mini/mjcf/") ) - self.model = mujoco.MjModel.from_xml_path( # type: ignore + self.model = mujoco.MjModel.from_xml_path( f"{mjcf_root_path}/scenes/{scene}.xml" ) - self.data = mujoco.MjData(self.model) # type: ignore + self.data = mujoco.MjData(self.model) self.model.opt.timestep = 0.002 # s, simulation timestep, 500hz self.decimation = 10 # -> 50hz control loop self.rendering_timestep = 0.04 # s, rendering loop # 25Hz - - self.camera_id = mujoco.mj_name2id( # type: ignore - self.model, - mujoco.mjtObj.mjOBJ_CAMERA, # type: ignore - "eye_camera", - ) - - self.head_id = mujoco.mj_name2id( # type: ignore + self.streaming_timestep = 0.04 # s, streaming loop # 25Hz + + self.head_site_id = mujoco.mj_name2id( self.model, - mujoco.mjtObj.mjOBJ_BODY, # type: ignore - "pp01063_stewart_plateform", + mujoco.mjtObj.mjOBJ_SITE, + "head", ) - self.platform_to_head_transform = np.array( - [ - [8.66025292e-01, 5.00000194e-01, -1.83660327e-06, -1.34282000e-02], - [5.55111512e-16, -3.67320510e-06, -1.00000000e00, -1.20000000e-03], - [-5.00000194e-01, 8.66025292e-01, -3.18108852e-06, 3.65883000e-02], - [0, 0, 0, 1.00000000e00], - ] - ) - # remove_z_offset = np.eye(4) - # remove_z_offset[2, 3] = -0.177 - # self.platform_to_head_transform = self.platform_to_head_transform @ remove_z_offset - self.current_head_pose = np.eye(4) - # print("Joints in the model:") - # for i in range(self.model.njoint): - # name = mujoco.mj_id2joint(self.model, i) - # print(f" {i}: {name}") - self.joint_names = get_actuator_names(self.model) self.joint_ids = [ @@ -111,70 +109,137 @@ def __init__( get_joint_addr_from_name(self.model, n) for n in self.joint_names ] - def rendering_loop(self): + self.col_inds = [] + for i, type in enumerate(self.model.geom_contype): + if type != 0: + self.col_inds.append(i) + self.model.geom_contype[i] = 0 + self.model.geom_conaffinity[i] = 0 + + def _get_camera_id(self, camera_name: str) -> Any: + """Get the id of the virtual camera.""" + return mujoco.mj_name2id( + self.model, + mujoco.mjtObj.mjOBJ_CAMERA, + camera_name, + ) + + def _get_renderer(self, camera_name: str) -> mujoco.Renderer: + """Get the renderer for the virtual camera.""" + camera_size = CAMERA_SIZES[camera_name] + return mujoco.Renderer(self.model, height=camera_size[1], width=camera_size[0]) + + def streaming_loop(self, camera_name: str, ws_uri: str) -> None: + """Streaming loop for the Mujoco simulation over WebSocket. + + Capture the image from the virtual camera and send it over WebSocket to the ws_uri. + """ + streamer = AsyncWebSocketFrameSender(ws_uri=ws_uri + "/video_stream") + offscreen_renderer = self._get_renderer(camera_name) + camera_id = self._get_camera_id(camera_name) + + while not self.should_stop.is_set(): + start_t = time.time() + offscreen_renderer.update_scene(self.data, camera_id) + + # OPTIMIZATION: Disable expensive rendering effects on the scene + offscreen_renderer.scene.flags[mujoco.mjtRndFlag.mjRND_SHADOW] = 0 + offscreen_renderer.scene.flags[mujoco.mjtRndFlag.mjRND_REFLECTION] = 0 + + im = offscreen_renderer.render() + + im = cv2.cvtColor(im, cv2.COLOR_RGB2BGR) + streamer.send_frame(im) + + took = time.time() - start_t + time.sleep(max(0, self.streaming_timestep - took)) + + def rendering_loop(self, camera_name: str, port: int) -> None: """Offline Rendering loop for the Mujoco simulation. - Capture the image from the virtual Reachy's camera and send it over UDP. + Capture the image from the virtual camera_name and send it over UDP to the port or over WebSocket to the ws_uri. """ - streamer_udp = UDPJPEGFrameSender() - camera_size = (1280, 720) - offscreen_renderer = mujoco.Renderer( - self.model, height=camera_size[1], width=camera_size[0] - ) + streamer = UDPJPEGFrameSender(dest_port=port) + offscreen_renderer = self._get_renderer(camera_name) + camera_id = self._get_camera_id(camera_name) + while not self.should_stop.is_set(): start_t = time.time() - offscreen_renderer.update_scene(self.data, self.camera_id) + offscreen_renderer.update_scene(self.data, camera_id) + im = offscreen_renderer.render() - streamer_udp.send_frame(im) + streamer.send_frame(im) took = time.time() - start_t time.sleep(max(0, self.rendering_timestep - took)) - def run(self): + def run(self) -> None: """Run the Mujoco simulation with a viewer. This method initializes the viewer and enters the main simulation loop. It updates the joint positions at a rate and publishes the joint positions. """ step = 1 + if self.websocket_uri: + robot_view_streaming_thread = Thread(target=self.streaming_loop, args=(CAMERA_STUDIO_CLOSE, self.websocket_uri), daemon=True) + robot_view_streaming_thread.start() + if not self.headless: viewer = mujoco.viewer.launch_passive( self.model, self.data, show_left_ui=False, show_right_ui=False ) with viewer.lock(): - viewer.cam.type = mujoco.mjtCamera.mjCAMERA_FREE # type: ignore + viewer.cam.type = mujoco.mjtCamera.mjCAMERA_FREE viewer.cam.distance = 0.8 # ≃ ||pos - lookat|| viewer.cam.azimuth = 160 # degrees viewer.cam.elevation = -20 # degrees viewer.cam.lookat[:] = [0, 0, 0.15] # force one render with your new camera - mujoco.mj_step(self.model, self.data) # type: ignore + mujoco.mj_step(self.model, self.data) viewer.sync() # im = self.get_camera() # self.streamer_udp.send_frame(im) - self.data.qpos[self.joint_qpos_addr] = np.array( - self._SLEEP_HEAD_JOINT_POSITIONS - + self._SLEEP_ANTENNAS_JOINT_POSITIONS - ).reshape(-1, 1) - self.data.ctrl[:] = np.array( - self._SLEEP_HEAD_JOINT_POSITIONS - + self._SLEEP_ANTENNAS_JOINT_POSITIONS - ) + self.data.qpos[self.joint_qpos_addr] = np.array( + self._SLEEP_HEAD_JOINT_POSITIONS + + self._SLEEP_ANTENNAS_JOINT_POSITIONS + ).reshape(-1, 1) + self.data.ctrl[:] = np.array( + self._SLEEP_HEAD_JOINT_POSITIONS + + self._SLEEP_ANTENNAS_JOINT_POSITIONS + ) + + # recompute all kinematics, collisions, etc. + mujoco.mj_forward(self.model, self.data) + + for i in range(100): + mujoco.mj_step(self.model, self.data) + + # enable collisions + for i in self.col_inds: + self.model.geom_contype[i] = 1 + self.model.geom_conaffinity[i] = 1 - # recompute all kinematics, collisions, etc. - mujoco.mj_forward(self.model, self.data) # type: ignore + for i in range(100): + mujoco.mj_step(self.model, self.data) # one more frame so the viewer shows your startup pose - mujoco.mj_step(self.model, self.data) # type: ignore + mujoco.mj_step(self.model, self.data) if not self.headless: viewer.sync() - rendering_thread = Thread(target=self.rendering_loop, daemon=True) + rendering_thread = Thread(target=self.rendering_loop, args=(CAMERA_REACHY, 5005), daemon=True) rendering_thread.start() + # Update the internal states of the IK and FK to the current configuration + # This is important to avoid jumps when starting the robot (beore wake-up) + self.head_kinematics.ik(self.get_mj_present_head_pose(), no_iterations=20) + self.head_kinematics.fk( + self.get_present_head_joint_positions(), no_iterations=20 + ) + # 3) now enter your normal loop while not self.should_stop.is_set(): start_t = time.time() @@ -187,6 +252,11 @@ def run(self): self.current_antenna_joint_positions = ( self.get_present_antenna_joint_positions() ) + # Update the Placo kinematics model to recompute passive joints + self.update_head_kinematics_model( + self.current_head_joint_positions, + self.current_antenna_joint_positions, + ) self.current_head_pose = self.get_mj_present_head_pose() # Update the target head joint positions from IK if necessary @@ -197,12 +267,14 @@ def run(self): self.target_head_pose, self.target_body_yaw ) except ValueError as e: - self.logger.warning(f"IK error: {e}") + log_throttling.by_time(self.logger, interval=0.5).warning( + f"IK error: {e}" + ) if self.target_head_joint_positions is not None: self.data.ctrl[:7] = self.target_head_joint_positions if self.target_antenna_joint_positions is not None: - self.data.ctrl[-2:] = self.target_antenna_joint_positions + self.data.ctrl[-2:] = -self.target_antenna_joint_positions if ( self.joint_positions_publisher is not None @@ -212,8 +284,8 @@ def run(self): self.joint_positions_publisher.put( json.dumps( { - "head_joint_positions": self.current_head_joint_positions, - "antennas_joint_positions": self.current_antenna_joint_positions, + "head_joint_positions": self.current_head_joint_positions.tolist(), + "antennas_joint_positions": self.current_antenna_joint_positions.tolist(), } ).encode("utf-8") ) @@ -229,17 +301,20 @@ def run(self): if not self.headless: viewer.sync() - mujoco.mj_step(self.model, self.data) # type: ignore + mujoco.mj_step(self.model, self.data) took = time.time() - start_t time.sleep(max(0, self.model.opt.timestep - took)) - # print(f"Step {step}: took {took*1000:.1f}ms") + # print(f"Step {step}: took {took*1e6:.1f}us") step += 1 if not self.headless: viewer.close() + rendering_thread.join() + if self.websocket_uri: + robot_view_streaming_thread.join() - def get_mj_present_head_pose(self) -> np.ndarray: + def get_mj_present_head_pose(self) -> Annotated[npt.NDArray[np.float64], (4, 4)]: """Get the current head pose from the Mujoco simulation. Returns: @@ -247,9 +322,11 @@ def get_mj_present_head_pose(self) -> np.ndarray: """ mj_current_head_pose = np.eye(4) - mj_current_head_pose[:3, :3] = self.data.xmat[self.head_id].reshape(3, 3) - mj_current_head_pose[:3, 3] = self.data.xpos[self.head_id] - mj_current_head_pose = mj_current_head_pose @ self.platform_to_head_transform + + mj_current_head_pose[:3, :3] = self.data.site_xmat[self.head_site_id].reshape( + 3, 3 + ) + mj_current_head_pose[:3, 3] = self.data.site_xpos[self.head_site_id] mj_current_head_pose[2, 3] -= 0.177 return mj_current_head_pose @@ -267,13 +344,23 @@ def get_status(self) -> "MujocoBackendStatus": """ return MujocoBackendStatus(motor_control_mode=self.get_motor_control_mode()) - def get_present_head_joint_positions(self): + def get_present_head_joint_positions( + self, + ) -> Annotated[npt.NDArray[np.float64], (7,)]: """Get the current joint positions of the head.""" - return self.data.qpos[self.joint_qpos_addr[:7]].flatten().tolist() + pos: npt.NDArray[np.float64] = self.data.qpos[ + self.joint_qpos_addr[:7] + ].flatten() + return pos - def get_present_antenna_joint_positions(self): + def get_present_antenna_joint_positions( + self, + ) -> Annotated[npt.NDArray[np.float64], (2,)]: """Get the current joint positions of the antennas.""" - return self.data.qpos[self.joint_qpos_addr[-2:]].flatten().tolist() + pos: npt.NDArray[np.float64] = self.data.qpos[ + self.joint_qpos_addr[-2:] + ].flatten() + return -pos def get_motor_control_mode(self) -> MotorControlMode: """Get the motor control mode.""" @@ -283,6 +370,10 @@ def set_motor_control_mode(self, mode: MotorControlMode) -> None: """Set the motor control mode.""" pass + def set_motor_torque_ids(self, ids: list[str], on: bool) -> None: + """Set the motor torque state for specific motor names.""" + pass + @dataclass class MujocoBackendStatus: diff --git a/src/reachy_mini/daemon/backend/mujoco/utils.py b/src/reachy_mini/daemon/backend/mujoco/utils.py index 2cacbb66..c85b7643 100644 --- a/src/reachy_mini/daemon/backend/mujoco/utils.py +++ b/src/reachy_mini/daemon/backend/mujoco/utils.py @@ -4,16 +4,20 @@ homogeneous transformation matrices, joint positions, and actuator names. """ +from typing import Annotated + import mujoco import numpy as np +import numpy.typing as npt +from mujoco._structs import MjData, MjModel from scipy.spatial.transform import Rotation as R def get_homogeneous_matrix_from_euler( - position: tuple = (0, 0, 0), # (x, y, z) meters - euler_angles: tuple = (0, 0, 0), # (roll, pitch, yaw) + position: tuple[float, float, float] = (0, 0, 0), # (x, y, z) meters + euler_angles: tuple[float, float, float] = (0, 0, 0), # (roll, pitch, yaw) degrees: bool = False, -): +) -> Annotated[npt.NDArray[np.float64], (4, 4)]: """Return a homogeneous transformation matrix from position and Euler angles.""" homogeneous_matrix = np.eye(4) homogeneous_matrix[:3, :3] = R.from_euler( @@ -23,10 +27,10 @@ def get_homogeneous_matrix_from_euler( return homogeneous_matrix -def get_joint_qpos(model, data, joint_name) -> float: +def get_joint_qpos(model: MjModel, data: MjData, joint_name: str) -> float: """Return the qpos (rad) of a specified joint in the model.""" # Get the joint id - joint_id = mujoco.mj_name2id(model, mujoco.mjtObj.mjOBJ_JOINT, joint_name) # type: ignore + joint_id = mujoco.mj_name2id(model, mujoco.mjtObj.mjOBJ_JOINT, joint_name) if joint_id == -1: raise ValueError(f"Joint '{joint_name}' not found.") @@ -34,20 +38,22 @@ def get_joint_qpos(model, data, joint_name) -> float: qpos_addr = model.jnt_qposadr[joint_id] # Get the qpos value - return data.qpos[qpos_addr] + qpos: float = data.qpos[qpos_addr] + return qpos -def get_joint_id_from_name(model, name: str) -> int: +def get_joint_id_from_name(model: MjModel, name: str) -> int: """Return the id of a specified joint.""" return mujoco.mj_name2id(model, mujoco.mjtObj.mjOBJ_JOINT, name) # type: ignore -def get_joint_addr_from_name(model, name: str) -> int: +def get_joint_addr_from_name(model: MjModel, name: str) -> int: """Return the address of a specified joint.""" - return model.joint(name).qposadr + addr: int = model.joint(name).qposadr + return addr -def get_actuator_names(model): +def get_actuator_names(model: MjModel) -> list[str]: """Return the list of the actuators names from the MuJoCo model.""" actuator_names = [model.actuator(k).name for k in range(0, model.nu)] return actuator_names diff --git a/src/reachy_mini/daemon/backend/mujoco/video_udp.py b/src/reachy_mini/daemon/backend/mujoco/video_udp.py index 4477ed39..f1f95589 100644 --- a/src/reachy_mini/daemon/backend/mujoco/video_udp.py +++ b/src/reachy_mini/daemon/backend/mujoco/video_udp.py @@ -8,6 +8,7 @@ import cv2 import numpy as np +import numpy.typing as npt class UDPJPEGFrameSender: @@ -31,16 +32,16 @@ def __init__( self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.max_packet_size = max_packet_size - def send_frame(self, frame: np.ndarray) -> None: + def send_frame(self, frame: npt.NDArray[np.uint8]) -> None: """Send a frame as a JPEG image over UDP. Args: frame (np.ndarray): The frame to be sent, in RGB format. """ - frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR) + frame_cvt = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR) ret, jpeg_bytes = cv2.imencode( - ".jpg", frame, [int(cv2.IMWRITE_JPEG_QUALITY), 80] + ".jpg", frame_cvt, [int(cv2.IMWRITE_JPEG_QUALITY), 80] ) data = jpeg_bytes.tobytes() total_size = len(data) @@ -50,3 +51,7 @@ def send_frame(self, frame: np.ndarray) -> None: start = i * self.max_packet_size end = min(start + self.max_packet_size, total_size) self.sock.sendto(data[start:end], self.addr) + + def close(self) -> None: + """Close the socket.""" + self.sock.close() diff --git a/src/reachy_mini/daemon/backend/robot/__init__.py b/src/reachy_mini/daemon/backend/robot/__init__.py index 3d91dfb1..1b6ffe66 100644 --- a/src/reachy_mini/daemon/backend/robot/__init__.py +++ b/src/reachy_mini/daemon/backend/robot/__init__.py @@ -1,6 +1,8 @@ """Real robot backend for Reachy Mini.""" from reachy_mini.daemon.backend.robot.backend import ( - RobotBackend, # noqa: F401 - RobotBackendStatus, # noqa: F401 + RobotBackend, + RobotBackendStatus, ) + +__all__ = ["RobotBackend", "RobotBackendStatus"] diff --git a/src/reachy_mini/daemon/backend/robot/backend.py b/src/reachy_mini/daemon/backend/robot/backend.py index 2543ec51..dc080e7b 100644 --- a/src/reachy_mini/daemon/backend/robot/backend.py +++ b/src/reachy_mini/daemon/backend/robot/backend.py @@ -7,14 +7,20 @@ import json import logging +import struct import time from dataclasses import dataclass from datetime import timedelta from multiprocessing import Event # It seems to be more accurate than threading.Event +from typing import Annotated, Any +import log_throttling import numpy as np +import numpy.typing as npt from reachy_mini_motor_controller import ReachyMiniPyControlLoop +from reachy_mini.utils.hardware_config.parser import parse_yaml_config + from ..abstract import Backend, MotorControlMode @@ -27,6 +33,9 @@ def __init__( log_level: str = "INFO", check_collision: bool = False, kinematics_engine: str = "AnalyticalKinematics", + hardware_error_check_frequency: float = 1.0, + use_audio: bool = True, + hardware_config_filepath: str | None = None, ): """Initialize the RobotBackend. @@ -35,19 +44,24 @@ def __init__( log_level (str): The logging level for the backend. Default is "INFO". check_collision (bool): If True, enable collision checking. Default is False. kinematics_engine (str): Kinematics engine to use. Defaults to "AnalyticalKinematics". + hardware_error_check_frequency (float): Frequency in seconds to check for hardware errors. Default is 1.0. + use_audio (bool): If True, use audio. Default is True. + hardware_config_filepath (str | None): Path to the hardware configuration YAML file. Default is None. Tries to connect to the Reachy Mini motor controller and initializes the control loop. """ super().__init__( - check_collision=check_collision, kinematics_engine=kinematics_engine + check_collision=check_collision, + kinematics_engine=kinematics_engine, + use_audio=use_audio, ) self.logger = logging.getLogger(__name__) self.logger.setLevel(log_level) self.control_loop_frequency = 50.0 # Hz - self.c = ReachyMiniPyControlLoop( + self.c: ReachyMiniPyControlLoop | None = ReachyMiniPyControlLoop( serialport, read_position_loop_period=timedelta( seconds=1.0 / self.control_loop_frequency @@ -56,10 +70,22 @@ def __init__( stats_pub_period=None, ) + self.name2id = self.c.get_motor_name_id() + if hardware_config_filepath is not None: + config = parse_yaml_config(hardware_config_filepath) + for motor_name, motor_conf in config.motors.items(): + if motor_conf.pid is not None: + motor_id = self.name2id[motor_name] + p, i, d = motor_conf.pid + self.logger.info( + f"Setting PID gains for motor '{motor_name}' (ID: {motor_id}): P={p}, I={i}, D={d}" + ) + self.c.async_write_pid_gains(motor_id, p, i, d) + self.motor_control_mode = self._infer_control_mode() self._torque_enabled = self.motor_control_mode != MotorControlMode.Disabled self.logger.info(f"Motor control mode: {self.motor_control_mode}") - self.last_alive = None + self.last_alive: float | None = None self._status = RobotBackendStatus( motor_control_mode=self.motor_control_mode, @@ -68,7 +94,7 @@ def __init__( control_loop_stats={}, ) self._stats_record_period = 1.0 # seconds - self._stats = { + self._stats: dict[str, Any] = { "timestamps": [], "nb_error": 0, "record_period": self._stats_record_period, @@ -79,7 +105,9 @@ def __init__( self.target_antenna_joint_current = None # Placeholder for antenna joint torque self.target_head_joint_current = None # Placeholder for head joint torque - def run(self): + self.hardware_error_check_frequency = hardware_error_check_frequency # seconds + + def run(self) -> None: """Run the control loop for the robot backend. This method continuously updates the motor controller at a specified frequency. @@ -93,6 +121,8 @@ def run(self): self.retries = 5 self.stats_record_t0 = time.time() + self.last_hardware_error_check_time = time.time() + next_call_event = Event() # Compute the forward kinematics to get the initial head pose @@ -100,7 +130,7 @@ def run(self): head_positions, _ = self.get_all_joint_positions() # make sure to converge fully (a lot of iterations) self.current_head_pose = self.head_kinematics.fk( - head_positions, + np.array(head_positions), no_iterations=20, ) assert self.current_head_pose is not None @@ -123,14 +153,14 @@ def run(self): next_call_event.clear() next_call_event.wait(sleep_time) - def _update(self): + def _update(self) -> None: assert self.c is not None, "Motor controller not initialized or already closed." if self._torque_enabled: if self._current_head_operation_mode != 0: # if position control mode if self.target_head_joint_positions is not None: self.c.set_stewart_platform_position( - self.target_head_joint_positions[1:] + self.target_head_joint_positions[1:].tolist() ) self.c.set_body_rotation(self.target_head_joint_positions[0]) else: # it's in torque control mode @@ -149,7 +179,9 @@ def _update(self): if self._current_antennas_operation_mode != 0: # if position control mode if self.target_antenna_joint_positions is not None: - self.c.set_antennas_positions(self.target_antenna_joint_positions) + self.c.set_antennas_positions( + self.target_antenna_joint_positions.tolist() + ) # Antenna torque control is not supported with feetech motors # else: # if self.target_antenna_joint_current is not None: @@ -165,7 +197,10 @@ def _update(self): head_positions, antenna_positions = self.get_all_joint_positions() # Update the head kinematics model with the current head positions - self.update_head_kinematics_model(head_positions, antenna_positions) + self.update_head_kinematics_model( + np.array(head_positions), + np.array(antenna_positions), + ) # Update the target head joint positions from IK if necessary # - does nothing if the targets did not change @@ -175,7 +210,9 @@ def _update(self): self.target_head_pose, self.target_body_yaw ) except ValueError as e: - self.logger.warning(f"IK error: {e}") + log_throttling.by_time(self.logger, interval=0.5).warning( + f"IK error: {e}" + ) if not self.is_shutting_down: self.joint_positions_publisher.put( @@ -199,7 +236,6 @@ def _update(self): self.ready.set() # Mark the backend as ready except RuntimeError as e: self._stats["nb_error"] += 1 - # self.logger.warning(f"Error reading positions: {e}") assert self.last_alive is not None @@ -230,9 +266,22 @@ def _update(self): self._stats["nb_error"] = 0 self.stats_record_t0 = time.time() + if ( + time.time() - self.last_hardware_error_check_time + > self.hardware_error_check_frequency + ): + hardware_errors = self.read_hardware_errors() + if hardware_errors: + for motor_name, errors in hardware_errors.items(): + self.logger.error( + f"Motor '{motor_name}' hardware errors: {errors}" + ) + self.last_hardware_error_check_time = time.time() + def close(self) -> None: """Close the motor controller connection.""" - self.c.close() + if self.c is not None: + self.c.close() self.c = None def get_status(self) -> "RobotBackendStatus": @@ -292,9 +341,13 @@ def set_head_operation_mode(self, mode: int) -> None: # if the mode is not torque control, we need to set the head joint positions # to the current positions to avoid sudden movements motor_pos = self.c.get_last_position() - self.target_head_joint_positions = [motor_pos.body_yaw] + motor_pos.stewart + self.target_head_joint_positions = np.array( + [motor_pos.body_yaw] + motor_pos.stewart + ) - self.c.set_stewart_platform_position(self.target_head_joint_positions[1:]) + self.c.set_stewart_platform_position( + self.target_head_joint_positions[1:].tolist() + ) self.c.set_body_rotation(self.target_head_joint_positions[0]) self.c.enable_body_rotation(True) self.c.set_body_rotation_operating_mode(0) @@ -330,17 +383,19 @@ def set_antennas_operation_mode(self, mode: int) -> None: if mode != 0: # if the mode is not torque control, we need to set the head joint positions # to the current positions to avoid sudden movements - self.target_antenna_joint_positions = ( + self.target_antenna_joint_positions = np.array( self.c.get_last_position().antennas ) - self.c.set_antennas_positions(self.target_antenna_joint_positions) + self.c.set_antennas_positions( + self.target_antenna_joint_positions.tolist() + ) self.c.enable_antennas(True) else: self.c.enable_antennas(False) self._current_antennas_operation_mode = mode - def get_all_joint_positions(self) -> tuple[list, list]: + def get_all_joint_positions(self) -> tuple[list[float], list[float]]: """Get the current joint positions of the robot. Returns: @@ -357,23 +412,27 @@ def get_all_joint_positions(self) -> tuple[list, list]: return [yaw] + list(dofs), list(antennas) - def get_present_head_joint_positions(self) -> list: + def get_present_head_joint_positions( + self, + ) -> Annotated[npt.NDArray[np.float64], (7,)]: """Get the current joint positions of the head. Returns: list: A list of joint positions for the head, including the body rotation. """ - return self.get_all_joint_positions()[0] + return np.array(self.get_all_joint_positions()[0]) - def get_present_antenna_joint_positions(self) -> list: + def get_present_antenna_joint_positions( + self, + ) -> Annotated[npt.NDArray[np.float64], (2,)]: """Get the current joint positions of the antennas. Returns: list: A list of joint positions for the antennas. """ - return self.get_all_joint_positions()[1] + return np.array(self.get_all_joint_positions()[1]) def compensate_head_gravity(self) -> None: """Calculate the currents necessary to compensate for gravity.""" @@ -390,18 +449,18 @@ def compensate_head_gravity(self) -> None: # Conversion factor from Nm to mA for the Stewart platform motors # The torque constant is not linear, so we need to use a correction factor # This is a magic number that should be determined experimentally - # For currents under 30mA, the constant is around 3 + # For currents under 30mA, the constant is around 4.0 # Then it drops to 1.0 for currents above 1.5A - correction_factor = 3.0 + correction_factor = 4.0 # Get the current head joint positions head_joints = self.get_present_head_joint_positions() - gravity_torque = self.head_kinematics.compute_gravity_torque( + gravity_torque = self.head_kinematics.compute_gravity_torque( # type: ignore [union-attr] np.array(head_joints) ) # Convert the torque from Nm to mA current = gravity_torque * from_Nm_to_mA / correction_factor # Set the head joint current - self.set_target_head_joint_current(current.tolist()) + self.set_target_head_joint_current(current) def get_motor_control_mode(self) -> MotorControlMode: """Get the motor control mode.""" @@ -441,6 +500,25 @@ def set_motor_control_mode(self, mode: MotorControlMode) -> None: self.motor_control_mode = mode + def set_motor_torque_ids(self, ids: list[str], on: bool) -> None: + """Set the torque state for specific motor names. + + Args: + ids (list[int]): List of motor IDs to set the torque state for. + on (bool): True to enable torque, False to disable. + + """ + assert self.c is not None, "Motor controller not initialized or already closed." + + assert ids is not None and len(ids) > 0, "IDs list cannot be empty or None." + + ids_int = [self.name2id[name] for name in ids] + + if on: + self.c.enable_torque_on_ids(ids_int) + else: + self.c.disable_torque_on_ids(ids_int) + def _infer_control_mode(self) -> MotorControlMode: assert self.c is not None, "Motor controller not initialized or already closed." @@ -457,6 +535,53 @@ def _infer_control_mode(self) -> MotorControlMode: else: raise ValueError(f"Unknown motor control mode: {mode}") + def read_hardware_errors(self) -> dict[str, list[str]]: + """Read hardware errors from the motor controller.""" + if self.c is None: + return {} + + def decode_hardware_error_byte(err_byte: int) -> list[str]: + # https://emanual.robotis.com/docs/en/dxl/x/xl330-m288/#hardware-error-status + bits_to_error = { + 0: "Input Voltage Error", + 2: "Overheating Error", + 4: "Electrical Shock Error", + 5: "Overload Error", + } + err_bits = [i for i in range(8) if (err_byte & (1 << i)) != 0] + return [bits_to_error[b] for b in err_bits if b in bits_to_error] + + def voltage_ok( + id: int, + allowed_max_voltage: float = 7.3, + ) -> bool: + assert self.c is not None, ( + "Motor controller not initialized or already closed." + ) + # https://emanual.robotis.com/docs/en/dxl/x/xl330-m288/#present-input-voltage + resp_bytes = self.c.async_read_raw_bytes(id, 144, 2) + resp = struct.unpack("h", bytes(resp_bytes))[0] + voltage: float = resp / 10.0 # in Volts + + return voltage <= allowed_max_voltage + + errors = {} + for name, id in self.c.get_motor_name_id().items(): + # https://emanual.robotis.com/docs/en/dxl/x/xl330-m288/#hardware-error-status + err_byte = self.c.async_read_raw_bytes(id, 70, 1) + assert len(err_byte) == 1 + err = decode_hardware_error_byte(err_byte[0]) + if err: + if "Input Voltage Error" in err: + if voltage_ok(id): + err.remove("Input Voltage Error") + + # To avoid logging empty errors like "Motor 1: []" + if len(err) > 0: + errors[name] = err + + return errors + @dataclass class RobotBackendStatus: @@ -465,5 +590,5 @@ class RobotBackendStatus: ready: bool motor_control_mode: MotorControlMode last_alive: float | None - control_loop_stats: dict + control_loop_stats: dict[str, Any] error: str | None = None diff --git a/src/reachy_mini/daemon/daemon.py b/src/reachy_mini/daemon/daemon.py index 9aedcc6e..c5b1802e 100644 --- a/src/reachy_mini/daemon/daemon.py +++ b/src/reachy_mini/daemon/daemon.py @@ -5,17 +5,30 @@ It also provides a command-line interface for easy interaction. """ +import asyncio +import json import logging -from dataclasses import dataclass +import time +from dataclasses import asdict, dataclass from enum import Enum -from threading import Thread -from typing import Optional - -import serial.tools.list_ports +from importlib.metadata import PackageNotFoundError, version +from threading import Event, Thread +from typing import Any, Optional from reachy_mini.daemon.backend.abstract import MotorControlMode +from reachy_mini.daemon.utils import ( + convert_enum_to_dict, + find_serial_port, + get_ip_address, +) +from reachy_mini.io import ( + AsyncWebSocketAudioStreamer, + AsyncWebSocketController, + AsyncWebSocketFrameSender, + ZenohServer, +) +from reachy_mini.media.media_manager import MediaManager -from ..io import Server from .backend.mujoco import MujocoBackend, MujocoBackendStatus from .backend.robot import RobotBackend, RobotBackendStatus @@ -26,19 +39,50 @@ class Daemon: Runs the server with the appropriate backend (Mujoco for simulation or RobotBackend for real hardware). """ - def __init__(self, log_level: str = "INFO"): + def __init__( + self, + log_level: str = "INFO", + wireless_version: bool = False, + stream: bool = False, + ) -> None: """Initialize the Reachy Mini daemon.""" self.log_level = log_level self.logger = logging.getLogger(__name__) self.logger.setLevel(self.log_level) - self.backend = None + self.wireless_version = wireless_version + + self.backend: "RobotBackend | MujocoBackend | None" = None + # Get package version + try: + package_version = version("reachy_mini") + self.logger.info(f"Daemon version: {package_version}") + except PackageNotFoundError: + package_version = None + self.logger.warning("Could not determine daemon version") + self._status = DaemonStatus( state=DaemonState.NOT_INITIALIZED, + wireless_version=wireless_version, simulation_enabled=None, backend_status=None, error=None, + wlan_ip=None, + version=package_version, + ) + self._thread_event_publish_status = Event() + + self._webrtc: Optional[Any] = ( + None # type GstWebRTC imported for wireless version only ) + if stream: + if not wireless_version: + raise RuntimeError( + "WebRTC streaming is only supported for wireless version. Use --wireless-version flag." + ) + from reachy_mini.media.webrtc_daemon import GstWebRTC + + self._webrtc = GstWebRTC(log_level) async def start( self, @@ -50,6 +94,10 @@ async def start( check_collision: bool = False, kinematics_engine: str = "AnalyticalKinematics", headless: bool = False, + use_audio: bool = True, + websocket_uri: Optional[str] = None, + stream_media: bool = False, + hardware_config_filepath: str | None = None, ) -> "DaemonState": """Start the Reachy Mini daemon. @@ -62,6 +110,10 @@ async def start( check_collision (bool): If True, enable collision checking. Defaults to False. kinematics_engine (str): Kinematics engine to use. Defaults to "AnalyticalKinematics". headless (bool): If True, run Mujoco in headless mode (no GUI). Defaults to False. + websocket_uri (Optional[str]): If set, allow remote control and streaming of the robot through a WebSocket connection to the specified uri. Defaults to None. + use_audio (bool): If True, enable audio. Defaults to True. + stream_media (bool): If True, stream media to the WebSocket. Defaults to False. + hardware_config_filepath (str | None): Path to the hardware configuration YAML file. Defaults to None. Returns: DaemonState: The current state of the daemon after attempting to start it. @@ -71,14 +123,24 @@ async def start( self.logger.warning("Daemon is already running.") return self._status.state + self.logger.info( + f"Daemon start parameters: sim={sim}, serialport={serialport}, scene={scene}, localhost_only={localhost_only}, wake_up_on_start={wake_up_on_start}, check_collision={check_collision}, kinematics_engine={kinematics_engine}, headless={headless}, hardware_config_filepath={hardware_config_filepath}" + ) + self._status.simulation_enabled = sim + if not localhost_only: + self._status.wlan_ip = get_ip_address() + self._start_params = { "sim": sim, "serialport": serialport, "headless": headless, + "websocket_uri": websocket_uri, + "use_audio": use_audio, "scene": scene, "localhost_only": localhost_only, + "stream_media": stream_media, } self.logger.info("Starting Reachy Mini daemon...") @@ -86,22 +148,47 @@ async def start( try: self.backend = self._setup_backend( + wireless_version=self.wireless_version, sim=sim, serialport=serialport, scene=scene, check_collision=check_collision, kinematics_engine=kinematics_engine, headless=headless, + websocket_uri=websocket_uri, + use_audio=use_audio, + hardware_config_filepath=hardware_config_filepath, ) except Exception as e: self._status.state = DaemonState.ERROR self._status.error = str(e) raise e - self.server = Server(self.backend, localhost_only=localhost_only) - self.server.start() - - def backend_wrapped_run(): + self.zenoh_server = ZenohServer(self.backend, localhost_only=localhost_only) + self.zenoh_server.start() + self._thread_publish_status = Thread(target=self._publish_status, daemon=True) + self._thread_publish_status.start() + + self.websocket_server: Optional[AsyncWebSocketController] = None + if websocket_uri is not None: + self.websocket_server = AsyncWebSocketController(ws_uri=websocket_uri + "/robot", backend=self.backend) + + if stream_media: + if websocket_uri is None: + raise ValueError("WebSocket URI is required when streaming media.") + self.media_manager = MediaManager() + self.websocket_frame_sender = AsyncWebSocketFrameSender(ws_uri=websocket_uri + "/video_stream") + self._thread_publish_frames = Thread(target=self._publish_frames, daemon=True) + self._thread_event_publish_frames = Event() + self._thread_publish_frames.start() + self.websocket_audio_sender = AsyncWebSocketAudioStreamer(ws_uri=websocket_uri + "/audio_stream") + self._thread_publish_audio = Thread(target=self._publish_audio, daemon=True) + self._thread_event_publish_audio = Event() + self._thread_publish_audio.start() + self.media_manager.start_recording() + self.media_manager.start_playing() + + def backend_wrapped_run() -> None: assert self.backend is not None, ( "Backend should be initialized before running." ) @@ -112,7 +199,19 @@ def backend_wrapped_run(): self.logger.error(f"Backend encountered an error: {e}") self._status.state = DaemonState.ERROR self._status.error = str(e) - self.server.stop() + self.zenoh_server.stop() + if self.websocket_server is not None: + self.websocket_server.stop() + if self._thread_publish_frames is not None and self._thread_publish_frames.is_alive(): + self._thread_event_publish_frames.set() + self._thread_publish_frames.join(timeout=2.0) + if self._thread_publish_audio is not None and self._thread_publish_audio.is_alive(): + self._thread_event_publish_audio.set() + self._thread_publish_audio.join(timeout=2.0) + if self.websocket_frame_sender is not None and self.websocket_frame_sender.connected.is_set(): + self.websocket_frame_sender.stop_flag = True + if self.websocket_audio_sender is not None and self.websocket_audio_sender.connected.is_set(): + self.websocket_audio_sender.stop_flag = True self.backend = None self.backend_run_thread = Thread(target=backend_wrapped_run) @@ -141,10 +240,35 @@ def backend_wrapped_run(): self._status.state = DaemonState.STOPPING return self._status.state + if self._webrtc: + await asyncio.sleep( + 0.2 + ) # Give some time for the backend to release the audio device + self._webrtc.start() + self.logger.info("Daemon started successfully.") self._status.state = DaemonState.RUNNING return self._status.state + def _publish_frames(self) -> None: + """Publish the media to the WebSocket.""" + while self._thread_event_publish_frames.is_set() is False: + frame = self.media_manager.get_frame() + if frame is not None: + self.websocket_frame_sender.send_frame(frame) + time.sleep(0.04) + + def _publish_audio(self) -> None: + """Publish the audio to the WebSocket.""" + while self._thread_event_publish_audio.is_set() is False: + audio = self.media_manager.get_audio_sample() + if audio is not None: + self.websocket_audio_sender.send_audio_chunk(audio) + received_audio = self.websocket_audio_sender.get_audio_chunk() + if received_audio is not None: + self.media_manager.push_audio_sample(received_audio) + time.sleep(0.05) + async def stop(self, goto_sleep_on_stop: bool = True) -> "DaemonState": """Stop the Reachy Mini daemon. @@ -159,9 +283,10 @@ async def stop(self, goto_sleep_on_stop: bool = True) -> "DaemonState": self.logger.warning("Daemon is already stopped.") return self._status.state - assert self.backend is not None, ( - "Backend should be initialized before stopping." - ) + if self.backend is None: + self.logger.info("Daemon backend is not initialized.") + self._status.state = DaemonState.STOPPED + return self._status.state try: if self._status.state in (DaemonState.STOPPING, DaemonState.ERROR): @@ -170,11 +295,13 @@ async def stop(self, goto_sleep_on_stop: bool = True) -> "DaemonState": self.logger.info("Stopping Reachy Mini daemon...") self._status.state = DaemonState.STOPPING self.backend.is_shutting_down = True - self.server.stop() + self._thread_event_publish_status.set() + self.zenoh_server.stop() + if self.websocket_server is not None: + self.websocket_server.stop() - if not hasattr(self, "backend"): - self._status.state = DaemonState.STOPPED - return self._status.state + if self._webrtc: + self._webrtc.stop() if goto_sleep_on_stop: try: @@ -209,11 +336,12 @@ async def stop(self, goto_sleep_on_stop: bool = True) -> "DaemonState": except KeyboardInterrupt: self.logger.warning("Daemon already stopping...") - backend_status = self.backend.get_status() - if backend_status.error: - self._status.state = DaemonState.ERROR + if self.backend is not None: + backend_status = self.backend.get_status() + if backend_status.error: + self._status.state = DaemonState.ERROR - self.backend = None + self.backend = None return self._status.state @@ -223,6 +351,9 @@ async def restart( serialport: Optional[str] = None, scene: Optional[str] = None, headless: Optional[bool] = None, + use_audio: Optional[bool] = None, + websocket_uri: Optional[str] = None, + stream_media: Optional[bool] = None, localhost_only: Optional[bool] = None, wake_up_on_start: Optional[bool] = None, goto_sleep_on_stop: Optional[bool] = None, @@ -234,6 +365,9 @@ async def restart( serialport (str): Serial port for real motors. Defaults to None (uses the previous value). scene (str): Name of the scene to load in simulation mode ("empty" or "minimal"). Defaults to None (uses the previous value). headless (bool): If True, run Mujoco in headless mode (no GUI). Defaults to None (uses the previous value). + use_audio (bool): If True, enable audio. Defaults to None (uses the previous value). + websocket_uri (Optional[str]): If set, allow remote control and streaming of the robot through a WebSocket connection to the specified uri. Defaults to None (uses the previous value). + stream_media (bool): If True, stream media to the WebSocket. Defaults to None (uses the previous value). localhost_only (bool): If True, restrict the server to localhost only clients. Defaults to None (uses the previous value). wake_up_on_start (bool): If True, wake up Reachy Mini on start. Defaults to None (don't wake up). goto_sleep_on_stop (bool): If True, put Reachy Mini to sleep on stop. Defaults to None (don't go to sleep). @@ -254,7 +388,7 @@ async def restart( if goto_sleep_on_stop is not None else False ) - params = { + params: dict[str, Any] = { "sim": sim if sim is not None else self._start_params["sim"], "serialport": serialport if serialport is not None @@ -263,6 +397,15 @@ async def restart( "headless": headless if headless is not None else self._start_params["headless"], + "use_audio": use_audio + if use_audio is not None + else self._start_params["use_audio"], + "websocket_uri": websocket_uri + if websocket_uri is not None + else self._start_params["websocket_uri"], + "stream_media": stream_media + if stream_media is not None + else self._start_params["stream_media"], "localhost_only": localhost_only if localhost_only is not None else self._start_params["localhost_only"], @@ -294,6 +437,15 @@ def status(self) -> "DaemonStatus": return self._status + def _publish_status(self) -> None: + self._thread_event_publish_status.clear() + while self._thread_event_publish_status.is_set() is False: + json_str = json.dumps( + asdict(self.status(), dict_factory=convert_enum_to_dict) + ) + self.zenoh_server.pub_status.put(json_str) + time.sleep(1) + async def run4ever( self, sim: bool = False, @@ -305,7 +457,10 @@ async def run4ever( check_collision: bool = False, kinematics_engine: str = "AnalyticalKinematics", headless: bool = False, - ): + use_audio: bool = True, + websocket_uri: Optional[str] = None, + stream_media: bool = False, + ) -> None: """Run the Reachy Mini daemon indefinitely. First, it starts the daemon, then it keeps checking the status and allows for graceful shutdown on user interrupt (Ctrl+C). @@ -320,6 +475,9 @@ async def run4ever( check_collision (bool): If True, enable collision checking. Defaults to False. kinematics_engine (str): Kinematics engine to use. Defaults to "AnalyticalKinematics". headless (bool): If True, run Mujoco in headless mode (no GUI). Defaults to False. + use_audio (bool): If True, enable audio. Defaults to True. + websocket_uri (Optional[str]): If set, allow remote control and streaming of the robot through a WebSocket connection to the specified uri. Defaults to None. + stream_media (bool): If True, stream media to the WebSocket. Defaults to False. """ await self.start( @@ -331,6 +489,9 @@ async def run4ever( check_collision=check_collision, kinematics_engine=kinematics_engine, headless=headless, + websocket_uri=websocket_uri, + use_audio=use_audio, + stream_media=stream_media, ) if self._status.state == DaemonState.RUNNING: @@ -353,7 +514,17 @@ async def run4ever( await self.stop(goto_sleep_on_stop) def _setup_backend( - self, sim, serialport, scene, check_collision, kinematics_engine, headless + self, + wireless_version: bool, + sim: bool, + serialport: str, + scene: str, + check_collision: bool, + kinematics_engine: str, + headless: bool, + use_audio: bool, + websocket_uri: Optional[str], + hardware_config_filepath: str | None = None, ) -> "RobotBackend | MujocoBackend": if sim: return MujocoBackend( @@ -361,10 +532,12 @@ def _setup_backend( check_collision=check_collision, kinematics_engine=kinematics_engine, headless=headless, + use_audio=use_audio, + websocket_uri=websocket_uri, ) else: if serialport == "auto": - ports = find_serial_port() + ports = find_serial_port(wireless_version=wireless_version) if len(ports) == 0: raise RuntimeError( @@ -381,11 +554,16 @@ def _setup_backend( serialport = ports[0] self.logger.info(f"Found Reachy Mini serial port: {serialport}") + self.logger.info( + f"Creating RobotBackend with parameters: serialport={serialport}, check_collision={check_collision}, kinematics_engine={kinematics_engine}" + ) return RobotBackend( serialport=serialport, log_level=self.log_level, check_collision=check_collision, kinematics_engine=kinematics_engine, + use_audio=use_audio, + hardware_config_filepath=hardware_config_filepath, ) @@ -405,22 +583,9 @@ class DaemonStatus: """Dataclass representing the status of the Reachy Mini daemon.""" state: DaemonState + wireless_version: bool simulation_enabled: Optional[bool] backend_status: Optional[RobotBackendStatus | MujocoBackendStatus] error: Optional[str] = None - - -def find_serial_port(vid: str = "1a86", pid: str = "55d3") -> list[str]: - """Find the serial port for Reachy Mini based on VID and PID. - - Args: - vid (str): Vendor ID of the device. (eg. "1a86"). - pid (str): Product ID of the device. (eg. "55d3"). - - """ - ports = serial.tools.list_ports.comports() - - vid = vid.upper() - pid = pid.upper() - - return [p.device for p in ports if f"USB VID:PID={vid}:{pid}" in p.hwid] + wlan_ip: Optional[str] = None + version: Optional[str] = None diff --git a/src/reachy_mini/daemon/utils.py b/src/reachy_mini/daemon/utils.py index d974f3e5..c70c0e3b 100644 --- a/src/reachy_mini/daemon/utils.py +++ b/src/reachy_mini/daemon/utils.py @@ -1,22 +1,30 @@ """Utilities for managing the Reachy Mini daemon.""" import os +import socket +import struct import subprocess import time +from enum import Enum +from typing import Any, List import psutil +import serial.tools.list_ports -def daemon_check(spawn_daemon, use_sim): +def daemon_check(spawn_daemon: bool, use_sim: bool) -> None: """Check if the Reachy Mini daemon is running and spawn it if necessary.""" - def is_python_script_running(script_name): + def is_python_script_running( + script_name: str, + ) -> tuple[bool, int | None, bool | None]: """Check if a specific Python script is running.""" found_script = False simluation_enabled = False for proc in psutil.process_iter(["pid", "name", "cmdline"]): try: - for cmd in proc.info["cmdline"]: + safe_cmdline = proc.info.get("cmdline") or [] + for cmd in safe_cmdline: if script_name in cmd: found_script = True if "--sim" in cmd: @@ -49,3 +57,60 @@ def is_python_script_running(script_name): ["reachy-mini-daemon", "--sim"] if use_sim else ["reachy-mini-daemon"], start_new_session=True, ) + + +def find_serial_port( + wireless_version: bool = False, + vid: str = "1a86", + pid: str = "55d3", + pi_uart: str = "/dev/ttyAMA3", +) -> list[str]: + """Find the serial port for Reachy Mini based on VID and PID or the Raspberry Pi UART for the wireless version. + + Args: + wireless_version (bool): Whether to look for the wireless version using the Raspberry Pi UART. + vid (str): Vendor ID of the device. (eg. "1a86"). + pid (str): Product ID of the device. (eg. "55d3"). + pi_uart (str): Path to the Raspberry Pi UART device. (eg. "/dev/ttyAMA3"). + + """ + # If it's a wireless version, we should use the Raspberry Pi UART + if wireless_version: + return [pi_uart] if os.path.exists(pi_uart) else [] + + # If it's a lite version, we should find it using the VID and PID + ports = serial.tools.list_ports.comports() + + vid = vid.upper() + pid = pid.upper() + + return [p.device for p in ports if f"USB VID:PID={vid}:{pid}" in p.hwid] + + +def get_ip_address(ifname: str = "wlan0") -> str | None: + """Get the IP address of a specific network interface (Linux Only).""" + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + try: + import fcntl + + return socket.inet_ntoa( + fcntl.ioctl( + s.fileno(), + 0x8915, # SIOCGIFADDR + struct.pack("256s", ifname[:15].encode("utf-8")), + )[20:24] + ) + except OSError: + print(f"Could not get IP address for interface {ifname}.") + return None + + +def convert_enum_to_dict(data: List[Any]) -> dict[str, Any]: + """Convert a dataclass containing Enums to a dictionary with enum values.""" + + def convert_value(obj: Any) -> Any: + if isinstance(obj, Enum): + return obj.value + return obj + + return dict((k, convert_value(v)) for k, v in data) diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/5w_speaker.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/5w_speaker.part new file mode 100644 index 00000000..6f80a7f4 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/5w_speaker.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "ac3fd0c16468a1e3078617de", + "elementId": "48f0e86376312f8ca242a082", + "fullConfiguration": "default", + "id": "MUIOPFRAXdyRYA5/y", + "isStandardContent": false, + "name": "5W_SPEAKER <1>", + "partId": "JFD", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/5w_speaker.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/5w_speaker.stl new file mode 100644 index 00000000..00a29236 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/5w_speaker.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:89c220ac5fa6dd559fcd2f3faccb7197904ab6b882ccf5d2ad04b17a09d58b98 +size 229884 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/antenna.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/antenna.part new file mode 100644 index 00000000..8ed4aa9f --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/antenna.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "ac3fd0c16468a1e3078617de", + "elementId": "d5df0bcac25cddb763b3a090", + "fullConfiguration": "default", + "id": "MT+KnYpqaZ58+3raS", + "isStandardContent": false, + "name": "antenna <2>", + "partId": "JFD", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/antenna.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/antenna.stl new file mode 100644 index 00000000..ebaf6395 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/antenna.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:01f894d2eb9cdc94e7c45944cabb6fa84074e33fb45c3ab9974744bc0e43a9cb +size 1048584 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/antenna_body_3dprint.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/antenna_body_3dprint.part new file mode 100644 index 00000000..39083713 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/antenna_body_3dprint.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "ac3fd0c16468a1e3078617de", + "elementId": "41e19b342cf83e0d866fde9b", + "fullConfiguration": "default", + "id": "M6OWFp5hfkfES9FmB", + "isStandardContent": false, + "name": "Antenna_body_3DPrint <2>", + "partId": "JF/", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/antenna_body_3dprint.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/antenna_body_3dprint.stl new file mode 100644 index 00000000..57603870 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/antenna_body_3dprint.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3f760ace60b1a368ba7c51bcd01a152a85e66a587fb24d55886cb9d9ffb9008f +size 241784 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/antenna_holder_l_3dprint.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/antenna_holder_l_3dprint.part new file mode 100644 index 00000000..1cd2c17f --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/antenna_holder_l_3dprint.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "ac3fd0c16468a1e3078617de", + "elementId": "41e19b342cf83e0d866fde9b", + "fullConfiguration": "default", + "id": "MHVB1f7lyvjCWrFJe", + "isStandardContent": false, + "name": "Antenna_Holder-L_3DPrint <1>", + "partId": "KFDB", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/antenna_holder_l_3dprint.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/antenna_holder_l_3dprint.stl new file mode 100644 index 00000000..1307d198 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/antenna_holder_l_3dprint.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5c2f0efae648fa0174dbc374fb63f4b48f55dea18f16f407540d255d8c91a426 +size 449784 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/antenna_holder_r_3dprint.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/antenna_holder_r_3dprint.part new file mode 100644 index 00000000..923b5835 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/antenna_holder_r_3dprint.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "ac3fd0c16468a1e3078617de", + "elementId": "41e19b342cf83e0d866fde9b", + "fullConfiguration": "default", + "id": "MX6Uc6wQ4cFgySRks", + "isStandardContent": false, + "name": "Antenna_Holder-R_3DPrint <1>", + "partId": "JFv", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/antenna_holder_r_3dprint.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/antenna_holder_r_3dprint.stl new file mode 100644 index 00000000..6320e622 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/antenna_holder_r_3dprint.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c7cd59ec8fd200294880c062b9190f223eb7fc7c207caecbb5de28fd4accbbe1 +size 465984 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/antenna_interface_3dprint.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/antenna_interface_3dprint.part new file mode 100644 index 00000000..861e1e4b --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/antenna_interface_3dprint.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "ac3fd0c16468a1e3078617de", + "elementId": "41e19b342cf83e0d866fde9b", + "fullConfiguration": "default", + "id": "MObKCzX1Fco0wvr0/", + "isStandardContent": false, + "name": "Antenna_interface_3DPrint <2>", + "partId": "JF7", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/antenna_interface_3dprint.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/antenna_interface_3dprint.stl new file mode 100644 index 00000000..15b32f15 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/antenna_interface_3dprint.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:662384ed2de2c37fff6d5bab1ba0ccb76ee03f5d1680043d255e33e879b481bb +size 179984 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/arducam.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/arducam.part new file mode 100644 index 00000000..f024ffab --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/arducam.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "ac3fd0c16468a1e3078617de", + "elementId": "ae2c63bc6f578a46d35cd365", + "fullConfiguration": "default", + "id": "MDupaBfND/yK9FERm", + "isStandardContent": false, + "name": "Arducam <1>", + "partId": "JGD", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/arducam.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/arducam.stl new file mode 100644 index 00000000..40bd218a --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/arducam.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dedb2f78b82216790ad43483c832744b86108dfab076a0f21938e6931beb1927 +size 1048584 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/arm.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/arm.part deleted file mode 100644 index 12cadf4d..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/arm.part +++ /dev/null @@ -1,13 +0,0 @@ -{ - "configuration": "default", - "documentId": "e305b26a84339af20c593973", - "documentMicroversion": "3b470b91d3cefec477725d38", - "elementId": "f05b326e391ef37011cd1007", - "fullConfiguration": "default", - "id": "MHbJVGQ/2CguoKbZz", - "isStandardContent": false, - "name": "Arm <1>", - "partId": "JsD", - "suppressed": false, - "type": "Part" -} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/arm.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/arm.stl deleted file mode 100644 index b104f0ad..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/arm.stl +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f1466fc2854bf4d3eb92b5f5f595e2c1a1dd90f8f9bbdcc581f88c541e72a47f -size 104784 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/b3b_eh.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/b3b_eh.part new file mode 100644 index 00000000..2af31cda --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/b3b_eh.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "ac3fd0c16468a1e3078617de", + "elementId": "2cfe27202252cfce42ed7ee4", + "fullConfiguration": "default", + "id": "Md1pPF2974d9JOL2l", + "isStandardContent": false, + "name": "B3B-EH <1>", + "partId": "JFr", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/b3b_eh.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/b3b_eh.stl new file mode 100644 index 00000000..29565d9e --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/b3b_eh.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:388ed3ff01359dac77cad8f1a14c4babeb4e2b72b5c1c60333fa2682bf62ac17 +size 9884 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/b3b_eh_1.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/b3b_eh_1.part new file mode 100644 index 00000000..fdf8c80d --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/b3b_eh_1.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "ac3fd0c16468a1e3078617de", + "elementId": "2cfe27202252cfce42ed7ee4", + "fullConfiguration": "default", + "id": "MMuPyh0yjSzJko9Po", + "isStandardContent": false, + "name": "B3B-EH_1 <1>", + "partId": "JFT", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/b3b_eh_1.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/b3b_eh_1.stl new file mode 100644 index 00000000..268d4d96 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/b3b_eh_1.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a741e87c71a9ff662a24ab5d6c7b8397afc6a7f7506910992814506d98d8e21f +size 9884 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/ball.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/ball.part deleted file mode 100644 index cdeff374..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/ball.part +++ /dev/null @@ -1,13 +0,0 @@ -{ - "configuration": "default", - "documentId": "e305b26a84339af20c593973", - "documentMicroversion": "4913859d0d2ce0a13b3adb26", - "elementId": "0aa833fd983f27ea7fb43256", - "fullConfiguration": "default", - "id": "Msm1EC3X/D1/s/Uus", - "isStandardContent": false, - "name": "Ball <13>", - "partId": "JSD", - "suppressed": false, - "type": "Part" -} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/ball.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/ball.stl deleted file mode 100644 index 6261467e..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/ball.stl +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fa04fda74ce21c562e6ad5645c619c425ce2644f29463cef38ad791c3e98b1c7 -size 158484 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/bearing_85x110x13.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/bearing_85x110x13.part new file mode 100644 index 00000000..bdee0386 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/bearing_85x110x13.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "ac3fd0c16468a1e3078617de", + "elementId": "d5ff0fe74f14f26be2deb5d4", + "fullConfiguration": "default", + "id": "MYk5LvI8gIvlMtfS7", + "isStandardContent": false, + "name": "Bearing_85x110x13 <1>", + "partId": "JGD", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/bearing_85x110x13.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/bearing_85x110x13.stl new file mode 100644 index 00000000..abbeb37f --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/bearing_85x110x13.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f79e3b8a1cf2da7be17907f6761acfa202fab9f759da196431712b7e20938fed +size 1048584 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/big_lens.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/big_lens.part deleted file mode 100644 index b5f2505e..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/big_lens.part +++ /dev/null @@ -1,13 +0,0 @@ -{ - "configuration": "default", - "documentId": "e305b26a84339af20c593973", - "documentMicroversion": "4913859d0d2ce0a13b3adb26", - "elementId": "1aa49f5cc5d823754d8b1fec", - "fullConfiguration": "default", - "id": "MpioSzVOUQtkaeTVH", - "isStandardContent": false, - "name": "big_lens <1>", - "partId": "J0D", - "suppressed": false, - "type": "Part" -} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/big_lens.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/big_lens.stl deleted file mode 100644 index 5528ca0d..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/big_lens.stl +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:28b05e56f4c9fcf6458ef7ae0a49b016251afbb463199095922eaac0d597bbe5 -size 266484 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/big_lens_d40.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/big_lens_d40.part new file mode 100644 index 00000000..d72edfb7 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/big_lens_d40.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "ac3fd0c16468a1e3078617de", + "elementId": "f67f6ec9755019fa8d4cde38", + "fullConfiguration": "default", + "id": "M6vM68+58boAYt2AP", + "isStandardContent": false, + "name": "Big Lens D40 <1>", + "partId": "JFD", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/big_lens_d40.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/big_lens_d40.stl new file mode 100644 index 00000000..afa55360 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/big_lens_d40.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4df711c8e29a677f62622d57c5cac01257ae9a44f46adb67fefb54b8fe52a6eb +size 151284 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/body_down_3dprint.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/body_down_3dprint.part new file mode 100644 index 00000000..da6757eb --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/body_down_3dprint.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "ac3fd0c16468a1e3078617de", + "elementId": "41e19b342cf83e0d866fde9b", + "fullConfiguration": "default", + "id": "M40W4a7sQuI8QEDbK", + "isStandardContent": false, + "name": "BODY-DOWN_3DPrint <1>", + "partId": "JFP", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/body_down_3dprint.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/body_down_3dprint.stl new file mode 100644 index 00000000..03cab0fe --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/body_down_3dprint.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:737e674299655be49d6b37a196d21cf8b9fd95c2837667cf4e4ed8bc4a8e89fa +size 1048584 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/body_foot_3dprint.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/body_foot_3dprint.part new file mode 100644 index 00000000..b51fd683 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/body_foot_3dprint.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "ac3fd0c16468a1e3078617de", + "elementId": "41e19b342cf83e0d866fde9b", + "fullConfiguration": "default", + "id": "MFPx9mgqcSNR4iAYO", + "isStandardContent": false, + "name": "BODY-FOOT_3DPrint <1>", + "partId": "JFH", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/body_foot_3dprint.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/body_foot_3dprint.stl new file mode 100644 index 00000000..b7f44565 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/body_foot_3dprint.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f021859da8f2b5dd663f36b7f4dd9b5e1c52485556a106b1ad66a3fed83281b8 +size 639584 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/body_top_3dprint.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/body_top_3dprint.part new file mode 100644 index 00000000..769dd36e --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/body_top_3dprint.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "ac3fd0c16468a1e3078617de", + "elementId": "41e19b342cf83e0d866fde9b", + "fullConfiguration": "default", + "id": "MfqCGZgMWtuWpureN", + "isStandardContent": false, + "name": "BODY_TOP_3DPrint <1>", + "partId": "JFb", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/body_top_3dprint.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/body_top_3dprint.stl new file mode 100644 index 00000000..aae367ae --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/body_top_3dprint.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:60f406a0ff783d18bd20c0441d1e03243d11403a71e7f37ee6aa444da9110039 +size 1048584 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/body_turning_3dprint.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/body_turning_3dprint.part new file mode 100644 index 00000000..3697b3fb --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/body_turning_3dprint.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "ac3fd0c16468a1e3078617de", + "elementId": "41e19b342cf83e0d866fde9b", + "fullConfiguration": "default", + "id": "MttvYsoT0rlrLKDwS", + "isStandardContent": false, + "name": "BODY-TURNING_3DPrint <1>", + "partId": "JFL", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/body_turning_3dprint.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/body_turning_3dprint.stl new file mode 100644 index 00000000..aff15d1a --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/body_turning_3dprint.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:805db06f0b88bd1d2bef6bd9c5b8afeaa364681fa8eff02465ceb14298283ae6 +size 393084 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/bottom_body.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/bottom_body.part deleted file mode 100644 index dd11f4aa..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/bottom_body.part +++ /dev/null @@ -1,13 +0,0 @@ -{ - "configuration": "default", - "documentId": "e305b26a84339af20c593973", - "documentMicroversion": "3b470b91d3cefec477725d38", - "elementId": "63ac215c84389096e60ba3ce", - "fullConfiguration": "default", - "id": "MgqLgbONW+9OAHLGG", - "isStandardContent": false, - "name": "bottom_body <1>", - "partId": "RcDD", - "suppressed": false, - "type": "Part" -} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/bottom_body.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/bottom_body.stl deleted file mode 100644 index acf684ae..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/bottom_body.stl +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:950dd3d805049a5c6ea1902c5c49cb675cb04c78c1f1b9c77b27e3ab25019243 -size 104884 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/bts2_m2_6x8.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/bts2_m2_6x8.part new file mode 100644 index 00000000..307fb8ac --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/bts2_m2_6x8.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "ac3fd0c16468a1e3078617de", + "elementId": "2cfe27202252cfce42ed7ee4", + "fullConfiguration": "default", + "id": "MHzA6RZ/kEAMV93SN", + "isStandardContent": false, + "name": "BTS2_M2_6X8 <1>", + "partId": "JFL", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/bts2_m2_6x8.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/bts2_m2_6x8.stl new file mode 100644 index 00000000..c9d6877c --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/bts2_m2_6x8.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:303a706276ef0a3dcf8b6deeabf73969d2c48cb6628209bf0fed825b622deccb +size 269184 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/collision/README.m b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/collision/README.m new file mode 100644 index 00000000..edda0bc7 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/collision/README.m @@ -0,0 +1 @@ +Created with: https://github.com/pollen-robotics/reachy_mini_stl_convexify \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/collision/coarse/body_top_3dprint_collider_back_0.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/collision/coarse/body_top_3dprint_collider_back_0.stl new file mode 100644 index 00000000..b134982c --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/collision/coarse/body_top_3dprint_collider_back_0.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8f92539090c8fbef3a292ffbdc6a1019828ad5e33a5b87354ad08032a59d0c35 +size 4884 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/collision/coarse/body_top_3dprint_collider_back_1.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/collision/coarse/body_top_3dprint_collider_back_1.stl new file mode 100644 index 00000000..dc98ea93 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/collision/coarse/body_top_3dprint_collider_back_1.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9186fdc67813c3b7f3fba721cbfbb14d5e6f20bcabe1897d488913b6879bfd78 +size 4784 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/collision/coarse/body_top_3dprint_collider_back_2.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/collision/coarse/body_top_3dprint_collider_back_2.stl new file mode 100644 index 00000000..592ecb1e --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/collision/coarse/body_top_3dprint_collider_back_2.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2db1d2726afd373e145a7b3cb8340983eeff49c07f8e4057e2d4eec7f91c35df +size 4884 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/collision/coarse/body_top_3dprint_collider_back_3.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/collision/coarse/body_top_3dprint_collider_back_3.stl new file mode 100644 index 00000000..2bea29e7 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/collision/coarse/body_top_3dprint_collider_back_3.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3e7916bfcdccd254c51143774575c7a0924a1a1939fd9a4755ae3b8f2c920bdc +size 4884 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/collision/coarse/body_top_3dprint_collider_front_0.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/collision/coarse/body_top_3dprint_collider_front_0.stl new file mode 100644 index 00000000..728659d3 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/collision/coarse/body_top_3dprint_collider_front_0.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:94d9c386d10344ae4fa03baae3ea0ca935c8ab2a5d41352a4b4c59c3ac2fd9e1 +size 4884 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/collision/coarse/body_top_3dprint_collider_front_1.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/collision/coarse/body_top_3dprint_collider_front_1.stl new file mode 100644 index 00000000..d296a17e --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/collision/coarse/body_top_3dprint_collider_front_1.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e893cc8cd9581722ffe7798031076190cdc2264edc70694d925be5cf326b8bc2 +size 4884 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/collision/coarse/body_top_3dprint_collider_front_2.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/collision/coarse/body_top_3dprint_collider_front_2.stl new file mode 100644 index 00000000..d990c548 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/collision/coarse/body_top_3dprint_collider_front_2.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1ec16d901b544cd898eb1836d4f61fc16855919bd856c82f0fd23ac180e684af +size 4884 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/collision/coarse/head_one_3dprint_collider_0.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/collision/coarse/head_one_3dprint_collider_0.stl new file mode 100644 index 00000000..b5a03639 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/collision/coarse/head_one_3dprint_collider_0.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9ba0e5a4dc95869e16212e69fa3712ea7f021378cf76db0b1a8d2300eaab3c64 +size 6884 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/collision/fine/body_top_3dprint_collider_back_0.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/collision/fine/body_top_3dprint_collider_back_0.stl new file mode 100644 index 00000000..6e405fc6 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/collision/fine/body_top_3dprint_collider_back_0.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7c22bb171ab47155a4fe2946cd267f8e43cba901dd11d3106082bbe1848f90b4 +size 190084 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/collision/fine/body_top_3dprint_collider_back_1.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/collision/fine/body_top_3dprint_collider_back_1.stl new file mode 100644 index 00000000..34019f99 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/collision/fine/body_top_3dprint_collider_back_1.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9a2c6189ab48a1b296ab5e0695363ab20021154dc7823d3eef8a188b992c1682 +size 195784 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/collision/fine/body_top_3dprint_collider_back_2.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/collision/fine/body_top_3dprint_collider_back_2.stl new file mode 100644 index 00000000..67603dad --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/collision/fine/body_top_3dprint_collider_back_2.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cbdf0d34da912a29174148bd78b90e3541755f9bc20d2a5cc624e3565aef3d94 +size 78684 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/collision/fine/body_top_3dprint_collider_back_3.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/collision/fine/body_top_3dprint_collider_back_3.stl new file mode 100644 index 00000000..37d62560 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/collision/fine/body_top_3dprint_collider_back_3.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4e7c26eb2033477e711d6ea535b955a91f443b74d21a4d8e5ed1bfb72c10c401 +size 245084 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/collision/fine/body_top_3dprint_collider_front_0.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/collision/fine/body_top_3dprint_collider_front_0.stl new file mode 100644 index 00000000..f0f3c8da --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/collision/fine/body_top_3dprint_collider_front_0.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:27dcf3bcfc55a11f5fe3a6a5f01fe253fb5e9cf9d736aa9b83a44d286aeef38c +size 185384 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/collision/fine/body_top_3dprint_collider_front_1.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/collision/fine/body_top_3dprint_collider_front_1.stl new file mode 100644 index 00000000..ce58be8b --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/collision/fine/body_top_3dprint_collider_front_1.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:89940a91f8e1bf1d1b7c4844bc58a8a03f5c3983cfc174573e8f60f6a47c1109 +size 182484 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/collision/fine/body_top_3dprint_collider_front_2.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/collision/fine/body_top_3dprint_collider_front_2.stl new file mode 100644 index 00000000..8964aa93 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/collision/fine/body_top_3dprint_collider_front_2.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6ccf48cd480051da28caee941ad1e130f74cebfd2c474d5791d5e014e10fcdfd +size 219184 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/collision/fine/head_one_3dprint_collider_0.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/collision/fine/head_one_3dprint_collider_0.stl new file mode 100644 index 00000000..1d636e5f --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/collision/fine/head_one_3dprint_collider_0.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5a28add53d3bc49c5b7f6d9e8f8f0884bbfa1ad9fe81af5ca58f01d08a072ed9 +size 1359584 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/dc15_a01_case_b_dummy.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/dc15_a01_case_b_dummy.part index d010123c..f4706645 100644 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/dc15_a01_case_b_dummy.part +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/dc15_a01_case_b_dummy.part @@ -1,13 +1,13 @@ { "configuration": "default", - "documentId": "e305b26a84339af20c593973", - "documentMicroversion": "4913859d0d2ce0a13b3adb26", - "elementId": "f05b326e391ef37011cd1007", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "ac3fd0c16468a1e3078617de", + "elementId": "2cfe27202252cfce42ed7ee4", "fullConfiguration": "default", - "id": "MhsjpiYGf8ZgBO855", + "id": "M0s224h4t0HqzV/u4", "isStandardContent": false, - "name": "DC15_A01_CASE_B_DUMMY <2>", - "partId": "JbL", + "name": "DC15_A01_CASE_B_DUMMY <1>", + "partId": "JFf", "suppressed": false, "type": "Part" } \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/dc15_a01_case_b_dummy.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/dc15_a01_case_b_dummy.stl index d11c09c9..be16e3d8 100644 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/dc15_a01_case_b_dummy.stl +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/dc15_a01_case_b_dummy.stl @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8771ec35e39954efed45a4d5f83e112ffa08d39f90264f15d35f96bb8220a6e5 +oid sha256:0b108f9390168a9052b408b292e316112f28e11d5ca51e28a374f29ce9ef2790 size 248884 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/dc15_a01_case_f_dummy.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/dc15_a01_case_f_dummy.part index a8258862..a129e338 100644 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/dc15_a01_case_f_dummy.part +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/dc15_a01_case_f_dummy.part @@ -1,13 +1,13 @@ { "configuration": "default", - "documentId": "e305b26a84339af20c593973", - "documentMicroversion": "4913859d0d2ce0a13b3adb26", - "elementId": "f05b326e391ef37011cd1007", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "ac3fd0c16468a1e3078617de", + "elementId": "2cfe27202252cfce42ed7ee4", "fullConfiguration": "default", - "id": "MzNAW97t7HwMX98P9", + "id": "MJyRwhvvOMia59Q6o", "isStandardContent": false, "name": "DC15_A01_CASE_F_DUMMY <1>", - "partId": "JbH", + "partId": "JFj", "suppressed": false, "type": "Part" } \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/dc15_a01_case_f_dummy.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/dc15_a01_case_f_dummy.stl index 8b61192c..5a208745 100644 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/dc15_a01_case_f_dummy.stl +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/dc15_a01_case_f_dummy.stl @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1fa27c05b5b93e0982729f241bbca8a5aba21c1312b8e0cf758ae99c083a8b45 -size 314484 +oid sha256:6ba4e280134154b64d2c5dd2f78f95c27e682a69e77f47895ac1fb3221d8c296 +size 326984 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/dc15_a01_case_m_dummy.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/dc15_a01_case_m_dummy.part index c1137513..52cb5e02 100644 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/dc15_a01_case_m_dummy.part +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/dc15_a01_case_m_dummy.part @@ -1,13 +1,13 @@ { "configuration": "default", - "documentId": "e305b26a84339af20c593973", - "documentMicroversion": "4913859d0d2ce0a13b3adb26", - "elementId": "f05b326e391ef37011cd1007", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "ac3fd0c16468a1e3078617de", + "elementId": "2cfe27202252cfce42ed7ee4", "fullConfiguration": "default", - "id": "MrR/9ODeIeBdYgmiv", + "id": "M6qXbiravwBh/M2FZ", "isStandardContent": false, "name": "DC15_A01_CASE_M_DUMMY <1>", - "partId": "JbD", + "partId": "JFn", "suppressed": false, "type": "Part" } \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/dc15_a01_case_m_dummy.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/dc15_a01_case_m_dummy.stl index 5a82664c..409e7a84 100644 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/dc15_a01_case_m_dummy.stl +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/dc15_a01_case_m_dummy.stl @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fe05cd55be2275578b86c3ac6b20657d530efecfb6ebe0fb09e7db0a6f50e528 +oid sha256:716c7f05cb780b5eb1dc7e614b8376e587683f2922fcb386d0b8152a73b5af68 size 162184 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/dc15_a01_horn_dummy.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/dc15_a01_horn_dummy.part index 3916c91b..9b338ce8 100644 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/dc15_a01_horn_dummy.part +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/dc15_a01_horn_dummy.part @@ -1,13 +1,13 @@ { "configuration": "default", - "documentId": "e305b26a84339af20c593973", - "documentMicroversion": "4913859d0d2ce0a13b3adb26", - "elementId": "f05b326e391ef37011cd1007", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "ac3fd0c16468a1e3078617de", + "elementId": "2cfe27202252cfce42ed7ee4", "fullConfiguration": "default", - "id": "Mq6TJGsdH+ga51RhW", + "id": "M/hbODfuW0gbGU1TK", "isStandardContent": false, - "name": "DC15_A01_HORN_DUMMY <5>", - "partId": "JbP", + "name": "DC15_A01_HORN_DUMMY <9>", + "partId": "JFX", "suppressed": false, "type": "Part" } \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/dc15_a01_horn_dummy.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/dc15_a01_horn_dummy.stl index fb7d0627..a793ae59 100644 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/dc15_a01_horn_dummy.stl +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/dc15_a01_horn_dummy.stl @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c4971912f3c6f863394b1d54522cc3ec41cb734a6cf96ab26ee3e1562f8befef -size 314584 +oid sha256:e8990dd416d5d6603e10b6d0fd05d8f552ef045fcebdcdeebf589d881c0185fd +size 445084 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/dc15_a01_led_cap2_dummy.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/dc15_a01_led_cap2_dummy.part new file mode 100644 index 00000000..077ed94c --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/dc15_a01_led_cap2_dummy.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "ac3fd0c16468a1e3078617de", + "elementId": "2cfe27202252cfce42ed7ee4", + "fullConfiguration": "default", + "id": "MFvbXu4svYiIz5iLO", + "isStandardContent": false, + "name": "DC15_A01_LED_CAP2_DUMMY <1>", + "partId": "JFv", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/dc15_a01_led_cap2_dummy.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/dc15_a01_led_cap2_dummy.stl new file mode 100644 index 00000000..b8ff0e55 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/dc15_a01_led_cap2_dummy.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3c8ea014b723e253c28fa3bf5062c794b18470cd7b5d6b33e723a9638d974f25 +size 1684 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/drive_palonier__configuration_default.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/drive_palonier__configuration_default.part deleted file mode 100644 index c1c9d504..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/drive_palonier__configuration_default.part +++ /dev/null @@ -1,14 +0,0 @@ -{ - "configuration": "List_RLLNj5hPLm22K3=Default", - "documentId": "4e2472e743f2deb9dbfd3f29", - "documentMicroversion": "9cbc0f0673222989756dc3bd", - "documentVersion": "ab0d611cff58eb03c4b43922", - "elementId": "cb09afde2287e85126e219d6", - "fullConfiguration": "List_RLLNj5hPLm22K3=Default", - "id": "MQEuvqp2Q/VgvHc2b", - "isStandardContent": false, - "name": "drive_palonier <1>", - "partId": "JFD", - "suppressed": false, - "type": "Part" -} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/drive_palonier__configuration_default.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/drive_palonier__configuration_default.stl deleted file mode 100644 index 655a93f7..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/drive_palonier__configuration_default.stl +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:161932a6a7d146aaa0e1c469154f8d9ee40bca2ba32356c1646f67a2ec47bcd7 -size 102584 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/drive_palonier__configuration_simple_axe.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/drive_palonier__configuration_simple_axe.part deleted file mode 100644 index fd4edf4f..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/drive_palonier__configuration_simple_axe.part +++ /dev/null @@ -1,14 +0,0 @@ -{ - "configuration": "List_RLLNj5hPLm22K3=Simple_Axe", - "documentId": "4e2472e743f2deb9dbfd3f29", - "documentMicroversion": "562fbad41f5c94b4e8929753", - "documentVersion": "b2b50e6fca74f541285598d2", - "elementId": "cb09afde2287e85126e219d6", - "fullConfiguration": "List_RLLNj5hPLm22K3=Simple_Axe", - "id": "MBlf2CAGFrM+vvrAc", - "isStandardContent": false, - "name": "drive_palonier <1>", - "partId": "JFD", - "suppressed": false, - "type": "Part" -} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/drive_palonier__configuration_simple_axe.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/drive_palonier__configuration_simple_axe.stl deleted file mode 100644 index 655a93f7..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/drive_palonier__configuration_simple_axe.stl +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:161932a6a7d146aaa0e1c469154f8d9ee40bca2ba32356c1646f67a2ec47bcd7 -size 102584 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/eye_support.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/eye_support.part deleted file mode 100644 index 22b3ff3e..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/eye_support.part +++ /dev/null @@ -1,13 +0,0 @@ -{ - "configuration": "default", - "documentId": "e305b26a84339af20c593973", - "documentMicroversion": "3b470b91d3cefec477725d38", - "elementId": "1aa49f5cc5d823754d8b1fec", - "fullConfiguration": "default", - "id": "MiwRHVEzZiAIrPHOH", - "isStandardContent": false, - "name": "eye_support <1>", - "partId": "JXD", - "suppressed": false, - "type": "Part" -} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/eye_support.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/eye_support.stl deleted file mode 100644 index 2e3afdd8..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/eye_support.stl +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:532e98b07d4b276853ce92806e65f9c4a3fbe2d792e962316f2ed60f687417f0 -size 104884 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/foot.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/foot.part deleted file mode 100644 index 84d4fc80..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/foot.part +++ /dev/null @@ -1,13 +0,0 @@ -{ - "configuration": "default", - "documentId": "e305b26a84339af20c593973", - "documentMicroversion": "3b470b91d3cefec477725d38", - "elementId": "f05b326e391ef37011cd1007", - "fullConfiguration": "default", - "id": "MtAQxXNWlY7iX7UeA", - "isStandardContent": false, - "name": "Foot <1>", - "partId": "RICD", - "suppressed": false, - "type": "Part" -} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/foot.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/foot.stl deleted file mode 100644 index cec7d18f..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/foot.stl +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:764a08b710160df9e80eb56cde1a68d93627e776a504e8089b8bbc4e46834c9d -size 104784 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/glasses_dolder_3dprint.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/glasses_dolder_3dprint.part new file mode 100644 index 00000000..20da7026 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/glasses_dolder_3dprint.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "ac3fd0c16468a1e3078617de", + "elementId": "41e19b342cf83e0d866fde9b", + "fullConfiguration": "default", + "id": "MsCmmfoZrFsTVz4Vs", + "isStandardContent": false, + "name": "GLASSES-DOLDER_3DPrint <1>", + "partId": "JFf", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/glasses_dolder_3dprint.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/glasses_dolder_3dprint.stl new file mode 100644 index 00000000..ca722275 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/glasses_dolder_3dprint.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:92f37eb75055678325c86ee614e3aef8aaa2bbdbaba518824045818edff3c1b9 +size 607884 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/head_back_3dprint.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/head_back_3dprint.part new file mode 100644 index 00000000..4ce359cc --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/head_back_3dprint.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "ac3fd0c16468a1e3078617de", + "elementId": "41e19b342cf83e0d866fde9b", + "fullConfiguration": "default", + "id": "MDdoiC6yCX9ILARl+", + "isStandardContent": false, + "name": "HEAD-BACK_3DPrint <1>", + "partId": "JFr", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/head_back_3dprint.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/head_back_3dprint.stl new file mode 100644 index 00000000..1c2b6222 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/head_back_3dprint.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:724385740aee0c18d3ccfe5df1b79049ff84c736cf652b53f5625ac5cc83c95c +size 1048584 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/head_front_3dprint.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/head_front_3dprint.part new file mode 100644 index 00000000..25baf8ac --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/head_front_3dprint.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "ac3fd0c16468a1e3078617de", + "elementId": "41e19b342cf83e0d866fde9b", + "fullConfiguration": "default", + "id": "MsTE6+BbSl0w4pt9R", + "isStandardContent": false, + "name": "HEAD-FRONT_3DPrint <1>", + "partId": "JFD", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/head_front_3dprint.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/head_front_3dprint.stl new file mode 100644 index 00000000..cc5c788e --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/head_front_3dprint.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6b4938a455866612ec3915da422d5e01037844fb3424c55200eea8e056363f81 +size 1048584 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/head_head_back.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/head_head_back.part deleted file mode 100644 index 67e57f0c..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/head_head_back.part +++ /dev/null @@ -1,13 +0,0 @@ -{ - "configuration": "default", - "documentId": "e305b26a84339af20c593973", - "documentMicroversion": "3b470b91d3cefec477725d38", - "elementId": "63ac215c84389096e60ba3ce", - "fullConfiguration": "default", - "id": "M9OOCqJWvxnZHaK2L", - "isStandardContent": false, - "name": "head_head_back <1>", - "partId": "JsH", - "suppressed": false, - "type": "Part" -} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/head_head_back.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/head_head_back.stl deleted file mode 100644 index 7acf9ec2..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/head_head_back.stl +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:106ab6a5983028815da362f680c050606277704e167c1840939ba69af2817fa7 -size 104884 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/head_interface.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/head_interface.part deleted file mode 100644 index 0034a82b..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/head_interface.part +++ /dev/null @@ -1,13 +0,0 @@ -{ - "configuration": "default", - "documentId": "e305b26a84339af20c593973", - "documentMicroversion": "3b470b91d3cefec477725d38", - "elementId": "63ac215c84389096e60ba3ce", - "fullConfiguration": "default", - "id": "Men72YbmKf99vvpY3", - "isStandardContent": false, - "name": "head_interface <1>", - "partId": "R/ED", - "suppressed": false, - "type": "Part" -} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/head_interface.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/head_interface.stl deleted file mode 100644 index c7afd7b9..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/head_interface.stl +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:463bfc9c01aa004dd4519919c315d06bb2eff75e54fe47ba93d30cc61637a548 -size 104884 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/head_mic_3dprint.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/head_mic_3dprint.part new file mode 100644 index 00000000..bef9da6d --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/head_mic_3dprint.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "ac3fd0c16468a1e3078617de", + "elementId": "41e19b342cf83e0d866fde9b", + "fullConfiguration": "default", + "id": "MuZC9z8GQmMdOw95U", + "isStandardContent": false, + "name": "HEAD-MIC_3DPrint <1>", + "partId": "JFj", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/head_mic_3dprint.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/head_mic_3dprint.stl new file mode 100644 index 00000000..7ac015f5 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/head_mic_3dprint.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ac3f4978f945345336481bde7b87aea91aeb97c3ce7e9433df8340923f5665bd +size 662084 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/head_shell_front.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/head_shell_front.part deleted file mode 100644 index 2300e551..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/head_shell_front.part +++ /dev/null @@ -1,13 +0,0 @@ -{ - "configuration": "default", - "documentId": "e305b26a84339af20c593973", - "documentMicroversion": "3b470b91d3cefec477725d38", - "elementId": "63ac215c84389096e60ba3ce", - "fullConfiguration": "default", - "id": "M326QNbEXm/j3BFCs", - "isStandardContent": false, - "name": "head_shell_front <1>", - "partId": "JsD", - "suppressed": false, - "type": "Part" -} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/head_shell_front.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/head_shell_front.stl deleted file mode 100644 index 71fa228c..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/head_shell_front.stl +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:373c98bef2cfe2a1b994058a279ebc22e113ce4d2bc497de56e1d4e3f733d0d3 -size 104884 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/lens_cap_d30_3dprint.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/lens_cap_d30_3dprint.part new file mode 100644 index 00000000..4b543e80 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/lens_cap_d30_3dprint.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "ac3fd0c16468a1e3078617de", + "elementId": "41e19b342cf83e0d866fde9b", + "fullConfiguration": "default", + "id": "MJpFg+P4UMBokBVex", + "isStandardContent": false, + "name": "LENS-CAP-D30_3DPrint <1>", + "partId": "JFn", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/lens_cap_d30_3dprint.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/lens_cap_d30_3dprint.stl new file mode 100644 index 00000000..bc190353 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/lens_cap_d30_3dprint.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:564787bc9073bc68c1598b4674ce751e7a7ed883d305a5cb947d3398360d3737 +size 94284 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/lens_cap_d40_3dprint.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/lens_cap_d40_3dprint.part new file mode 100644 index 00000000..12de378b --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/lens_cap_d40_3dprint.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "ac3fd0c16468a1e3078617de", + "elementId": "41e19b342cf83e0d866fde9b", + "fullConfiguration": "default", + "id": "MVC/sOmzNQLy4mD6b", + "isStandardContent": false, + "name": "LENS-CAP-D40_3DPrint <1>", + "partId": "JF3", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/lens_cap_d40_3dprint.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/lens_cap_d40_3dprint.stl new file mode 100644 index 00000000..d7cb1d4d --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/lens_cap_d40_3dprint.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e5a6364fba94a5a82d8a6512a7e042ccc78a0766756b190af055b0f08ef56471 +size 93684 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/m12_fisheye_lens_1_8mm.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/m12_fisheye_lens_1_8mm.part new file mode 100644 index 00000000..90f8ac49 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/m12_fisheye_lens_1_8mm.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "ac3fd0c16468a1e3078617de", + "elementId": "05dc7ef344fe4c18be495b6e", + "fullConfiguration": "default", + "id": "MvKFrBJz+CmxF4Ta4", + "isStandardContent": false, + "name": "M12 fisheye lens 1.8mm <2>", + "partId": "JGD", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/m12_fisheye_lens_1_8mm.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/m12_fisheye_lens_1_8mm.stl new file mode 100644 index 00000000..4ee95bc2 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/m12_fisheye_lens_1_8mm.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bf2e973bd164cd66b747f16e0ad6c4aafc9492e0b053fd40c283f4beba1b6015 +size 259284 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/m12_lens.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/m12_lens.part deleted file mode 100644 index eb39b918..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/m12_lens.part +++ /dev/null @@ -1,13 +0,0 @@ -{ - "configuration": "default", - "documentId": "e305b26a84339af20c593973", - "documentMicroversion": "4913859d0d2ce0a13b3adb26", - "elementId": "1aa49f5cc5d823754d8b1fec", - "fullConfiguration": "default", - "id": "M8ZONSAd4C6sMYKK/", - "isStandardContent": false, - "name": "M12_lens <1>", - "partId": "RZBD", - "suppressed": false, - "type": "Part" -} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/m12_lens.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/m12_lens.stl deleted file mode 100644 index dd67d13e..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/m12_lens.stl +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b0225297313cb77de3d8d0efc2b83499fdab9448e479174a5ca83bef24479b85 -size 93684 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/main_plate.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/main_plate.part deleted file mode 100644 index 436f4ac5..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/main_plate.part +++ /dev/null @@ -1,13 +0,0 @@ -{ - "configuration": "default", - "documentId": "e305b26a84339af20c593973", - "documentMicroversion": "3b470b91d3cefec477725d38", - "elementId": "f05b326e391ef37011cd1007", - "fullConfiguration": "default", - "id": "M0rtL1h+tUfVmZG6t", - "isStandardContent": false, - "name": "Main Plate <1>", - "partId": "RjBD", - "suppressed": false, - "type": "Part" -} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/main_plate.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/main_plate.stl deleted file mode 100644 index 648c6df7..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/main_plate.stl +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a900cdb05046319e1d6821350c785e2bc5a35f83a5392a01876b4cc90a9279eb -size 104884 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/mid_plate.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/mid_plate.part deleted file mode 100644 index fae5c69f..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/mid_plate.part +++ /dev/null @@ -1,13 +0,0 @@ -{ - "configuration": "default", - "documentId": "e305b26a84339af20c593973", - "documentMicroversion": "3b470b91d3cefec477725d38", - "elementId": "f05b326e391ef37011cd1007", - "fullConfiguration": "default", - "id": "Mxq3x/8HYdblICsdh", - "isStandardContent": false, - "name": "Mid Plate <1>", - "partId": "R9BD", - "suppressed": false, - "type": "Part" -} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/mid_plate.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/mid_plate.stl deleted file mode 100644 index 17bcdb03..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/mid_plate.stl +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bf5afed640fabcd2093f495a1a5d2c71c421b253fb1d22a282d38b866ed3d0c6 -size 104884 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/mp01062_stewart_arm_3.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/mp01062_stewart_arm_3.part new file mode 100644 index 00000000..dc9fca5c --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/mp01062_stewart_arm_3.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "ac3fd0c16468a1e3078617de", + "elementId": "41e19b342cf83e0d866fde9b", + "fullConfiguration": "default", + "id": "Mw5N9cfkbCC/CpCKR", + "isStandardContent": false, + "name": "MP01062_STEWART_ARM_3 <1>", + "partId": "KFHB", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/mp01062_stewart_arm_3.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/mp01062_stewart_arm_3.stl new file mode 100644 index 00000000..4fd08b22 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/mp01062_stewart_arm_3.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7b64f2de2a35b6feb9ed8ef728219f1a9600dfe6fcb70256a2e555cd6c49401e +size 155584 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/neck_reference_3dprint.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/neck_reference_3dprint.part new file mode 100644 index 00000000..d902ba9a --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/neck_reference_3dprint.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "ac3fd0c16468a1e3078617de", + "elementId": "41e19b342cf83e0d866fde9b", + "fullConfiguration": "default", + "id": "M7r6qwCMs4B7OGi6t", + "isStandardContent": false, + "name": "NECK_REFERENCE_3DPrint <1>", + "partId": "JFz", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/neck_reference_3dprint.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/neck_reference_3dprint.stl new file mode 100644 index 00000000..4452bc3d --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/neck_reference_3dprint.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5e619d224f39690c029b9e44bee7882e3650a8828e554d25c469bce3ab371ce5 +size 605084 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/phs_1_7x20_5_dc10.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/phs_1_7x20_5_dc10.part new file mode 100644 index 00000000..a9b55c13 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/phs_1_7x20_5_dc10.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "ac3fd0c16468a1e3078617de", + "elementId": "2cfe27202252cfce42ed7ee4", + "fullConfiguration": "default", + "id": "MydIXO1yH59xsOSOQ", + "isStandardContent": false, + "name": "PHS_1_7X20_5_DC10 <1>", + "partId": "JFP", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/phs_1_7x20_5_dc10.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/phs_1_7x20_5_dc10.stl new file mode 100644 index 00000000..a0f51417 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/phs_1_7x20_5_dc10.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c3e9d4bddfe4a99c206feba0fe648c1e5caf0aefca6347c7a91ff8f425b58d35 +size 253184 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/phs_1_7x20_5_dc10_1.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/phs_1_7x20_5_dc10_1.part new file mode 100644 index 00000000..c2ac2773 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/phs_1_7x20_5_dc10_1.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "ac3fd0c16468a1e3078617de", + "elementId": "2cfe27202252cfce42ed7ee4", + "fullConfiguration": "default", + "id": "MyXqeYka8V5tcbv+v", + "isStandardContent": false, + "name": "PHS_1_7X20_5_DC10_1 <1>", + "partId": "JFb", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/phs_1_7x20_5_dc10_1.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/phs_1_7x20_5_dc10_1.stl new file mode 100644 index 00000000..f58ca099 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/phs_1_7x20_5_dc10_1.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f9cd84a44f18b39aae56220bfcdfdbf8e39a809c57e4d07c3d7a718c7ab5669e +size 253184 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/phs_1_7x20_5_dc10_2.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/phs_1_7x20_5_dc10_2.part new file mode 100644 index 00000000..2f1cff9e --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/phs_1_7x20_5_dc10_2.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "ac3fd0c16468a1e3078617de", + "elementId": "2cfe27202252cfce42ed7ee4", + "fullConfiguration": "default", + "id": "M0HCg1i79OnE6gOgz", + "isStandardContent": false, + "name": "PHS_1_7X20_5_DC10_2 <1>", + "partId": "JFD", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/phs_1_7x20_5_dc10_2.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/phs_1_7x20_5_dc10_2.stl new file mode 100644 index 00000000..9a284934 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/phs_1_7x20_5_dc10_2.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ceba1b5c64923e7b29c5a686d753748b8a99496890ebe850694cba50d306d690 +size 253184 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/phs_1_7x20_5_dc10_3.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/phs_1_7x20_5_dc10_3.part new file mode 100644 index 00000000..779dda54 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/phs_1_7x20_5_dc10_3.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "ac3fd0c16468a1e3078617de", + "elementId": "2cfe27202252cfce42ed7ee4", + "fullConfiguration": "default", + "id": "MwHtRhbbnn9zJhJOY", + "isStandardContent": false, + "name": "PHS_1_7X20_5_DC10_3 <1>", + "partId": "JFH", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/phs_1_7x20_5_dc10_3.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/phs_1_7x20_5_dc10_3.stl new file mode 100644 index 00000000..e6535203 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/phs_1_7x20_5_dc10_3.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3b446022c08e14a7d7e18ddfc5a5e979347abe1f3165d7d161b630f107964b26 +size 253184 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/plateform.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/plateform.part deleted file mode 100644 index 85efa495..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/plateform.part +++ /dev/null @@ -1,13 +0,0 @@ -{ - "configuration": "default", - "documentId": "e305b26a84339af20c593973", - "documentMicroversion": "3b470b91d3cefec477725d38", - "elementId": "f05b326e391ef37011cd1007", - "fullConfiguration": "default", - "id": "MwlYHRbOjWaQ7Zawb", - "isStandardContent": false, - "name": "Plateform <1>", - "partId": "J4D", - "suppressed": false, - "type": "Part" -} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/plateform.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/plateform.stl deleted file mode 100644 index 939d8dc9..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/plateform.stl +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:470e7d22f7218cd5e71866947887e2793d52b6301276e005e313e30a875637f7 -size 104884 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp00xxx_stewart_rod.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp00xxx_stewart_rod.part deleted file mode 100644 index f882825b..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp00xxx_stewart_rod.part +++ /dev/null @@ -1,13 +0,0 @@ -{ - "configuration": "default", - "documentId": "e305b26a84339af20c593973", - "documentMicroversion": "4913859d0d2ce0a13b3adb26", - "elementId": "f05b326e391ef37011cd1007", - "fullConfiguration": "default", - "id": "MO1xdetqkz/QRPtZr", - "isStandardContent": false, - "name": "PP00XXX_Stewart Rod <6>", - "partId": "RcCD", - "suppressed": false, - "type": "Part" -} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp00xxx_stewart_rod.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp00xxx_stewart_rod.stl deleted file mode 100644 index 06bde5f4..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp00xxx_stewart_rod.stl +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:411e6736ad0961062f5bea0283762f654bb728fef310d0993f9f7634c8b5449f -size 28884 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01062_stewart_arm.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01062_stewart_arm.part deleted file mode 100644 index c469af37..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01062_stewart_arm.part +++ /dev/null @@ -1,13 +0,0 @@ -{ - "configuration": "default", - "documentId": "e305b26a84339af20c593973", - "documentMicroversion": "4913859d0d2ce0a13b3adb26", - "elementId": "f05b326e391ef37011cd1007", - "fullConfiguration": "default", - "id": "M9NEGUdCOXFxFyRZI", - "isStandardContent": false, - "name": "PP01062_Stewart Arm <5>", - "partId": "JpD", - "suppressed": false, - "type": "Part" -} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01062_stewart_arm.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01062_stewart_arm.stl deleted file mode 100644 index 3afe074d..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01062_stewart_arm.stl +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1ca4060f4c178a84d4ef862270aa95c40646f2fc77913ac38e1bed40fc1d883c -size 125184 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01063_stewart_plateform.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01063_stewart_plateform.part deleted file mode 100644 index 7e0cd065..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01063_stewart_plateform.part +++ /dev/null @@ -1,13 +0,0 @@ -{ - "configuration": "default", - "documentId": "e305b26a84339af20c593973", - "documentMicroversion": "4913859d0d2ce0a13b3adb26", - "elementId": "f05b326e391ef37011cd1007", - "fullConfiguration": "default", - "id": "MdkzMSXOA7GnM40pG", - "isStandardContent": false, - "name": "PP01063_Stewart Plateform <1>", - "partId": "J2D", - "suppressed": false, - "type": "Part" -} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01063_stewart_plateform.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01063_stewart_plateform.stl deleted file mode 100644 index e27cf3db..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01063_stewart_plateform.stl +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0de1f6657863b4f31f0f2492efc158fddc720272f4a4e38af17f2c10642ffbca -size 314584 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01064_stewart_main_plate.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01064_stewart_main_plate.part deleted file mode 100644 index 4d7a2a30..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01064_stewart_main_plate.part +++ /dev/null @@ -1,13 +0,0 @@ -{ - "configuration": "default", - "documentId": "e305b26a84339af20c593973", - "documentMicroversion": "4913859d0d2ce0a13b3adb26", - "elementId": "f05b326e391ef37011cd1007", - "fullConfiguration": "default", - "id": "MwQmuwlK0NX0T4TgA", - "isStandardContent": false, - "name": "PP01064_Stewart Main Plate <1>", - "partId": "RBCD", - "suppressed": false, - "type": "Part" -} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01064_stewart_main_plate.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01064_stewart_main_plate.stl deleted file mode 100644 index 328e8ca5..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01064_stewart_main_plate.stl +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:63b53dfef166197bc6c1f4b9d797b9009672fb963b9a5b6e65acc9f16e1b6219 -size 314584 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01065_stewart_side_plate.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01065_stewart_side_plate.part deleted file mode 100644 index 270b9c19..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01065_stewart_side_plate.part +++ /dev/null @@ -1,13 +0,0 @@ -{ - "configuration": "default", - "documentId": "e305b26a84339af20c593973", - "documentMicroversion": "4913859d0d2ce0a13b3adb26", - "elementId": "f05b326e391ef37011cd1007", - "fullConfiguration": "default", - "id": "MzgaD5MojzhSvLjc9", - "isStandardContent": false, - "name": "PP01065_Stewart Side Plate <2>", - "partId": "RgBD", - "suppressed": false, - "type": "Part" -} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01065_stewart_side_plate.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01065_stewart_side_plate.stl deleted file mode 100644 index bee55789..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01065_stewart_side_plate.stl +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:999d01af4da296fa318009982fb2354c5d51eb7810737aef5affca8f3d83daca -size 293584 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01066_stewart_mid_plate.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01066_stewart_mid_plate.part deleted file mode 100644 index dae987e0..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01066_stewart_mid_plate.part +++ /dev/null @@ -1,13 +0,0 @@ -{ - "configuration": "default", - "documentId": "e305b26a84339af20c593973", - "documentMicroversion": "4913859d0d2ce0a13b3adb26", - "elementId": "f05b326e391ef37011cd1007", - "fullConfiguration": "default", - "id": "Mj2rCiqdYnLvw/uK1", - "isStandardContent": false, - "name": "PP01066_Stewart Mid Plate <1>", - "partId": "RgCD", - "suppressed": false, - "type": "Part" -} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01066_stewart_mid_plate.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01066_stewart_mid_plate.stl deleted file mode 100644 index 4a18b059..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01066_stewart_mid_plate.stl +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:486214efae3c0d3b72460ba09f0088d33a7ad841c10180b54108427278780789 -size 176784 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01067_bottom_body.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01067_bottom_body.part deleted file mode 100644 index 15c8dbc2..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01067_bottom_body.part +++ /dev/null @@ -1,13 +0,0 @@ -{ - "configuration": "default", - "documentId": "e305b26a84339af20c593973", - "documentMicroversion": "4913859d0d2ce0a13b3adb26", - "elementId": "63ac215c84389096e60ba3ce", - "fullConfiguration": "default", - "id": "M1iw9QOQmsFr6efSd", - "isStandardContent": false, - "name": "PP01067_bottom_body <1>", - "partId": "RjDD", - "suppressed": false, - "type": "Part" -} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01067_bottom_body.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01067_bottom_body.stl deleted file mode 100644 index 8ba8324b..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01067_bottom_body.stl +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ca83667a9ff45433ca41b5e20edaabd38cb99f97d19a99576dfece8edf92d78c -size 314584 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01068_top_body.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01068_top_body.part deleted file mode 100644 index 93b4d715..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01068_top_body.part +++ /dev/null @@ -1,13 +0,0 @@ -{ - "configuration": "default", - "documentId": "e305b26a84339af20c593973", - "documentMicroversion": "4913859d0d2ce0a13b3adb26", - "elementId": "63ac215c84389096e60ba3ce", - "fullConfiguration": "default", - "id": "MLhpIAfnvnDl50gJ1", - "isStandardContent": false, - "name": "PP01068_top_body <1>", - "partId": "RjDH", - "suppressed": false, - "type": "Part" -} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01068_top_body.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01068_top_body.stl deleted file mode 100644 index 9e230bdd..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01068_top_body.stl +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:701c4ee80d16aa5257e706fa9cc0b8bbae2dd26138a6fd7860b99c7455ae3af7 -size 314584 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01069_head_shell_front.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01069_head_shell_front.part deleted file mode 100644 index eb09cbb5..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01069_head_shell_front.part +++ /dev/null @@ -1,13 +0,0 @@ -{ - "configuration": "default", - "documentId": "e305b26a84339af20c593973", - "documentMicroversion": "4913859d0d2ce0a13b3adb26", - "elementId": "63ac215c84389096e60ba3ce", - "fullConfiguration": "default", - "id": "MbnmqHXEBd3bXe5NJ", - "isStandardContent": false, - "name": "PP01069_head_shell_front <1>", - "partId": "JsD", - "suppressed": false, - "type": "Part" -} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01069_head_shell_front.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01069_head_shell_front.stl deleted file mode 100644 index 70ee3670..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01069_head_shell_front.stl +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:19ceb0df3c6655315ea012d4cb2622245d28eb302cbb690c859170ebd4b07425 -size 314584 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01070_head_head_back.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01070_head_head_back.part deleted file mode 100644 index 7a9bfa9f..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01070_head_head_back.part +++ /dev/null @@ -1,13 +0,0 @@ -{ - "configuration": "default", - "documentId": "e305b26a84339af20c593973", - "documentMicroversion": "4913859d0d2ce0a13b3adb26", - "elementId": "63ac215c84389096e60ba3ce", - "fullConfiguration": "default", - "id": "MJZxED2VVtdkQeJoM", - "isStandardContent": false, - "name": "PP01070_head_head_back <1>", - "partId": "JsH", - "suppressed": false, - "type": "Part" -} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01070_head_head_back.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01070_head_head_back.stl deleted file mode 100644 index 21b85657..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01070_head_head_back.stl +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:27a4689edc4380df51b18ec336a80755b66a9b2cb7b4653f205d96d026a5a612 -size 314584 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01071_turning_bowl.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01071_turning_bowl.part deleted file mode 100644 index 4969c1b3..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01071_turning_bowl.part +++ /dev/null @@ -1,13 +0,0 @@ -{ - "configuration": "default", - "documentId": "e305b26a84339af20c593973", - "documentMicroversion": "4913859d0d2ce0a13b3adb26", - "elementId": "63ac215c84389096e60ba3ce", - "fullConfiguration": "default", - "id": "MK6xlP0iZUknxGJ1B", - "isStandardContent": false, - "name": "PP01071_Turning bowl <1>", - "partId": "R1DD", - "suppressed": false, - "type": "Part" -} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01071_turning_bowl.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01071_turning_bowl.stl deleted file mode 100644 index 864d7f87..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01071_turning_bowl.stl +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b146eebeeaac1be6fe652808db20f524e729011d3b8f82dc1a8cdc34922a2deb -size 314584 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01072_turning_end.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01072_turning_end.part deleted file mode 100644 index 4c48ab98..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01072_turning_end.part +++ /dev/null @@ -1,13 +0,0 @@ -{ - "configuration": "default", - "documentId": "e305b26a84339af20c593973", - "documentMicroversion": "4913859d0d2ce0a13b3adb26", - "elementId": "63ac215c84389096e60ba3ce", - "fullConfiguration": "default", - "id": "MmSemALQTmY3L94+u", - "isStandardContent": false, - "name": "PP01072_Turning End <1>", - "partId": "RlFD", - "suppressed": false, - "type": "Part" -} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01072_turning_end.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01072_turning_end.stl deleted file mode 100644 index 0e13f4bc..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01072_turning_end.stl +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c5a7353f73938b1023ca3dbbb4ad8a01faf5e144405ed342f535df16b004b418 -size 314484 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01078_glasses.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01078_glasses.part deleted file mode 100644 index 247efb33..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01078_glasses.part +++ /dev/null @@ -1,13 +0,0 @@ -{ - "configuration": "default", - "documentId": "e305b26a84339af20c593973", - "documentMicroversion": "4913859d0d2ce0a13b3adb26", - "elementId": "1aa49f5cc5d823754d8b1fec", - "fullConfiguration": "default", - "id": "Mc2vPy/hERQOFTVCl", - "isStandardContent": false, - "name": "PP01078_glasses <1>", - "partId": "JXD", - "suppressed": false, - "type": "Part" -} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01078_glasses.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01078_glasses.stl deleted file mode 100644 index 1408f21c..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01078_glasses.stl +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8a4b8b4ac202d582be3b2c3f4f9728ba582514625c3e27572262bbd7a2704b40 -size 314584 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01079_back_big_eye.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01079_back_big_eye.part deleted file mode 100644 index b66e78d8..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01079_back_big_eye.part +++ /dev/null @@ -1,13 +0,0 @@ -{ - "configuration": "default", - "documentId": "e305b26a84339af20c593973", - "documentMicroversion": "4913859d0d2ce0a13b3adb26", - "elementId": "1aa49f5cc5d823754d8b1fec", - "fullConfiguration": "default", - "id": "MZ30fBArLgpdr3sZr", - "isStandardContent": false, - "name": "PP01079_back_big_eye <1>", - "partId": "RJBD", - "suppressed": false, - "type": "Part" -} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01079_back_big_eye.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01079_back_big_eye.stl deleted file mode 100644 index 7343b044..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01079_back_big_eye.stl +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4081dc49bd47072d7cb11d725cb26a72d49af70a3fc0fc09d8b711bdbb1c98d1 -size 93684 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01080_back_small_eye.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01080_back_small_eye.part deleted file mode 100644 index d2386d8e..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01080_back_small_eye.part +++ /dev/null @@ -1,13 +0,0 @@ -{ - "configuration": "default", - "documentId": "e305b26a84339af20c593973", - "documentMicroversion": "4913859d0d2ce0a13b3adb26", - "elementId": "1aa49f5cc5d823754d8b1fec", - "fullConfiguration": "default", - "id": "M5wrNragj9mzp/cuf", - "isStandardContent": false, - "name": "PP01080_back_small_eye <1>", - "partId": "RNBD", - "suppressed": false, - "type": "Part" -} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01080_back_small_eye.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01080_back_small_eye.stl deleted file mode 100644 index 7ce1913c..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01080_back_small_eye.stl +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:734c337c5da05b7b1966fbc5b1a2daa64f5e8ddb885ecfae94dd0cf041ca2925 -size 50484 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01102_arducam_carter.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01102_arducam_carter.part new file mode 100644 index 00000000..e5975c45 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01102_arducam_carter.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "ac3fd0c16468a1e3078617de", + "elementId": "41e19b342cf83e0d866fde9b", + "fullConfiguration": "default", + "id": "ME3Sp1txkoCjeAr4W", + "isStandardContent": false, + "name": "PP01102_ArduCam_Carter <1>", + "partId": "KFLB", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01102_arducam_carter.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01102_arducam_carter.stl new file mode 100644 index 00000000..866f890e --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/pp01102_arducam_carter.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:edc115b09647cafaf2fd023cd940817b54c33ce04496e586fe9031bd7582c5c6 +size 109484 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/rod.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/rod.part deleted file mode 100644 index baecc96a..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/rod.part +++ /dev/null @@ -1,13 +0,0 @@ -{ - "configuration": "default", - "documentId": "e305b26a84339af20c593973", - "documentMicroversion": "3b470b91d3cefec477725d38", - "elementId": "f05b326e391ef37011cd1007", - "fullConfiguration": "default", - "id": "MdJ+UkyUvvt4o0cNw", - "isStandardContent": false, - "name": "Rod <6>", - "partId": "R5BD", - "suppressed": false, - "type": "Part" -} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/rod.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/rod.stl deleted file mode 100644 index 77d6b21a..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/rod.stl +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0b1d0060be86f76bb7481fe3f9d6a02bf61386617ade8fb56cd8b5c449708be7 -size 28884 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/shape.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/shape.part deleted file mode 100644 index 81f2babe..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/shape.part +++ /dev/null @@ -1,13 +0,0 @@ -{ - "configuration": "default", - "documentId": "e305b26a84339af20c593973", - "documentMicroversion": "4913859d0d2ce0a13b3adb26", - "elementId": "0aa833fd983f27ea7fb43256", - "fullConfiguration": "default", - "id": "M53IEP0FWpyN7VrPP", - "isStandardContent": false, - "name": "Shape <12>", - "partId": "JHD", - "suppressed": false, - "type": "Part" -} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/shape.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/shape.stl deleted file mode 100644 index 104739a3..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/shape.stl +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6d24f09564c2713b74b392536023b4e055c013af91ee21252a8109ccd1327f8a -size 286284 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/side_plate.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/side_plate.part deleted file mode 100644 index 4b23649b..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/side_plate.part +++ /dev/null @@ -1,13 +0,0 @@ -{ - "configuration": "default", - "documentId": "e305b26a84339af20c593973", - "documentMicroversion": "3b470b91d3cefec477725d38", - "elementId": "f05b326e391ef37011cd1007", - "fullConfiguration": "default", - "id": "MZ6XefFx77VRcACDn", - "isStandardContent": false, - "name": "Side Plate <1>", - "partId": "RUBD", - "suppressed": false, - "type": "Part" -} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/side_plate.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/side_plate.stl deleted file mode 100644 index 09b2c1b4..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/side_plate.stl +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:52878966da6e718df392da535a8d0b5a03b99b242e4e44d9b2303ddce969f7c9 -size 104884 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/small_lens.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/small_lens.part deleted file mode 100644 index 6d8029c1..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/small_lens.part +++ /dev/null @@ -1,13 +0,0 @@ -{ - "configuration": "default", - "documentId": "e305b26a84339af20c593973", - "documentMicroversion": "4913859d0d2ce0a13b3adb26", - "elementId": "1aa49f5cc5d823754d8b1fec", - "fullConfiguration": "default", - "id": "M+k761oFhfeIyvr/G", - "isStandardContent": false, - "name": "small_lens <1>", - "partId": "J1D", - "suppressed": false, - "type": "Part" -} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/small_lens.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/small_lens.stl deleted file mode 100644 index 02df87e1..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/small_lens.stl +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8d284a1f2f79d7df815bcac24427b7167d0fba81bb125c00c7e451215047f71e -size 158484 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/small_lens_d30.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/small_lens_d30.part new file mode 100644 index 00000000..54842c22 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/small_lens_d30.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "ac3fd0c16468a1e3078617de", + "elementId": "f67f6ec9755019fa8d4cde38", + "fullConfiguration": "default", + "id": "MeOBTO0SVYe+Ls79P", + "isStandardContent": false, + "name": "Small Lens D30 <1>", + "partId": "JFH", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/small_lens_d30.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/small_lens_d30.stl new file mode 100644 index 00000000..8094f7e3 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/small_lens_d30.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c3f62174bc76b85b39e7835fc9e005fabde685677576c6c1e40753417fa922ec +size 158484 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/stewart_link_ball.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/stewart_link_ball.part new file mode 100644 index 00000000..ad2b57cc --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/stewart_link_ball.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "ac3fd0c16468a1e3078617de", + "elementId": "0c90fa98e702b6ba866efe06", + "fullConfiguration": "default", + "id": "MLAaNJd+l2AB7zwWF", + "isStandardContent": false, + "name": "stewart_link_ball <7>", + "partId": "JFD", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/stewart_link_ball.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/stewart_link_ball.stl new file mode 100644 index 00000000..1a9a1ea3 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/stewart_link_ball.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d24b6357fe780b9f54a550278fded8886524b732e2ec7765eeed1a8eee0e94bd +size 194484 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/stewart_link_ball__2.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/stewart_link_ball__2.part new file mode 100644 index 00000000..2dc68caf --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/stewart_link_ball__2.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "ac3fd0c16468a1e3078617de", + "elementId": "0c90fa98e702b6ba866efe06", + "fullConfiguration": "default", + "id": "Mzs67Fs/V5a1ifM8W", + "isStandardContent": false, + "name": "stewart_link_ball <10>", + "partId": "JGD", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/stewart_link_ball__2.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/stewart_link_ball__2.stl new file mode 100644 index 00000000..24023fd4 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/stewart_link_ball__2.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:25041196fb7b87a0118d1b283e97bcd56a961aafd08527f1408cc23077e77285 +size 194484 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/stewart_link_rod.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/stewart_link_rod.part new file mode 100644 index 00000000..06ff949e --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/stewart_link_rod.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "ac3fd0c16468a1e3078617de", + "elementId": "0c90fa98e702b6ba866efe06", + "fullConfiguration": "default", + "id": "MHKZQKk3gjSPuG0gI", + "isStandardContent": false, + "name": "stewart_link_rod <6>", + "partId": "JFH", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/stewart_link_rod.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/stewart_link_rod.stl new file mode 100644 index 00000000..6401019d --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/stewart_link_rod.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:93ee70960df8d3a5b5ffa373d11438f0fc415d98d554d4a5e773d004fed40081 +size 1048584 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/stewart_main_plate_3dprint.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/stewart_main_plate_3dprint.part new file mode 100644 index 00000000..aeecbe8a --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/stewart_main_plate_3dprint.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "ac3fd0c16468a1e3078617de", + "elementId": "41e19b342cf83e0d866fde9b", + "fullConfiguration": "default", + "id": "Mi3j82DWKTUoiYXon", + "isStandardContent": false, + "name": "STEWART_MAIN_PLATE_3DPrint <1>", + "partId": "JFT", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/stewart_main_plate_3dprint.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/stewart_main_plate_3dprint.stl new file mode 100644 index 00000000..48a9f748 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/stewart_main_plate_3dprint.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5cfd8e409542b60a8b32579275633aa16ad2b4ffdc68ed5820c4810951f43f0f +size 340584 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/stewart_tricap_3dprint.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/stewart_tricap_3dprint.part new file mode 100644 index 00000000..0cf9143d --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/stewart_tricap_3dprint.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "ac3fd0c16468a1e3078617de", + "elementId": "41e19b342cf83e0d866fde9b", + "fullConfiguration": "default", + "id": "M8OX4JdBklJa3ZAV2", + "isStandardContent": false, + "name": "STEWART_TRICAP_3DPrint <1>", + "partId": "JFX", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/stewart_tricap_3dprint.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/stewart_tricap_3dprint.stl new file mode 100644 index 00000000..3c6c794f --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/stewart_tricap_3dprint.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c2b3dc47028ade99f0d6d671548b3fe3c4a83a3c7e8c26b2ade8d492e8e85156 +size 333184 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/test_antenna.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/test_antenna.part deleted file mode 100644 index 74857f2a..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/test_antenna.part +++ /dev/null @@ -1,13 +0,0 @@ -{ - "configuration": "default", - "documentId": "e305b26a84339af20c593973", - "documentMicroversion": "4913859d0d2ce0a13b3adb26", - "elementId": "63ac215c84389096e60ba3ce", - "fullConfiguration": "default", - "id": "MoUkZchlIKO7O02cZ", - "isStandardContent": false, - "name": "test_antenna <1>", - "partId": "RMCD", - "suppressed": false, - "type": "Part" -} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/test_antenna.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/test_antenna.stl deleted file mode 100644 index a16be17a..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/test_antenna.stl +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f2f1f31344e0b21ad6b433953b86713d3834e54c87e23e4f626e77e390bce6da -size 266484 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/test_antenna_body.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/test_antenna_body.part deleted file mode 100644 index 1ae80574..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/test_antenna_body.part +++ /dev/null @@ -1,13 +0,0 @@ -{ - "configuration": "default", - "documentId": "e305b26a84339af20c593973", - "documentMicroversion": "4913859d0d2ce0a13b3adb26", - "elementId": "63ac215c84389096e60ba3ce", - "fullConfiguration": "default", - "id": "MrCYnP6eA71aF9l+x", - "isStandardContent": false, - "name": "test_antenna_body <1>", - "partId": "RFCD", - "suppressed": false, - "type": "Part" -} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/test_antenna_body.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/test_antenna_body.stl deleted file mode 100644 index 648959fd..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/test_antenna_body.stl +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0f9dd0adbe7a9243b2a527d4c5cc0d960ae2e15148d3ab8882b8a2389864094d -size 314484 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/top_body.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/top_body.part deleted file mode 100644 index 2a350df0..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/top_body.part +++ /dev/null @@ -1,13 +0,0 @@ -{ - "configuration": "default", - "documentId": "e305b26a84339af20c593973", - "documentMicroversion": "3b470b91d3cefec477725d38", - "elementId": "63ac215c84389096e60ba3ce", - "fullConfiguration": "default", - "id": "Ma4FMD0Z/1T41GJcv", - "isStandardContent": false, - "name": "top_body <1>", - "partId": "RcDH", - "suppressed": false, - "type": "Part" -} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/top_body.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/top_body.stl deleted file mode 100644 index c8aeab9b..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/top_body.stl +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e5c560740256a73f6dd3adeae424380fbf75acfe7056fdc1858a100cb7806fb1 -size 104884 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/turning_bowl.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/turning_bowl.part deleted file mode 100644 index bcf990c4..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/turning_bowl.part +++ /dev/null @@ -1,13 +0,0 @@ -{ - "configuration": "default", - "documentId": "e305b26a84339af20c593973", - "documentMicroversion": "3b470b91d3cefec477725d38", - "elementId": "63ac215c84389096e60ba3ce", - "fullConfiguration": "default", - "id": "M8Ekuan84MeJgvaNm", - "isStandardContent": false, - "name": "Turning bowl <1>", - "partId": "RuDD", - "suppressed": false, - "type": "Part" -} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/turning_bowl.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/turning_bowl.stl deleted file mode 100644 index 9521ffb7..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/turning_bowl.stl +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8af1cf9e3fb58111f59b95404a4b6e9f5edcc4cd23d0e5a3e74ad82bcce574c8 -size 104884 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/uc_a37_rev_a_step.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/uc_a37_rev_a_step.part deleted file mode 100644 index ecc0cc16..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/uc_a37_rev_a_step.part +++ /dev/null @@ -1,13 +0,0 @@ -{ - "configuration": "default", - "documentId": "e305b26a84339af20c593973", - "documentMicroversion": "4913859d0d2ce0a13b3adb26", - "elementId": "022097d02976f241add4ce3a", - "fullConfiguration": "default", - "id": "MHLMmdBzC0KnRb5Hr", - "isStandardContent": false, - "name": "UC-A37_Rev.A.step <1>", - "partId": "KF7D", - "suppressed": false, - "type": "Part" -} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/uc_a37_rev_a_step.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/uc_a37_rev_a_step.stl deleted file mode 100644 index 47120af1..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/uc_a37_rev_a_step.stl +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f77f8ca32283c4d1953510273776dd7fdc2d3ae817be8f82750023e68a9cd3df -size 68784 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/wj_wk00_0122topcabinetcase_95__configuration_default.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/wj_wk00_0122topcabinetcase_95__configuration_default.part deleted file mode 100644 index a27769e5..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/wj_wk00_0122topcabinetcase_95__configuration_default.part +++ /dev/null @@ -1,14 +0,0 @@ -{ - "configuration": "List_RLLNj5hPLm22K3=Default", - "documentId": "4e2472e743f2deb9dbfd3f29", - "documentMicroversion": "9cbc0f0673222989756dc3bd", - "documentVersion": "ab0d611cff58eb03c4b43922", - "elementId": "cb09afde2287e85126e219d6", - "fullConfiguration": "List_RLLNj5hPLm22K3=Default", - "id": "MICc+zB1tkivCW+Y6", - "isStandardContent": false, - "name": "WJ-WK00-0122TOPCABINETCASE_95 <1>", - "partId": "JFT", - "suppressed": false, - "type": "Part" -} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/wj_wk00_0122topcabinetcase_95__configuration_default.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/wj_wk00_0122topcabinetcase_95__configuration_default.stl deleted file mode 100644 index f3c50e42..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/wj_wk00_0122topcabinetcase_95__configuration_default.stl +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a28441cb86868c45f3910d7e88a583b9b561f7abd061e4c1064abe8b18523513 -size 314584 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/wj_wk00_0122topcabinetcase_95__configuration_simple_axe.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/wj_wk00_0122topcabinetcase_95__configuration_simple_axe.part deleted file mode 100644 index b0a107ca..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/wj_wk00_0122topcabinetcase_95__configuration_simple_axe.part +++ /dev/null @@ -1,14 +0,0 @@ -{ - "configuration": "List_RLLNj5hPLm22K3=Simple_Axe", - "documentId": "4e2472e743f2deb9dbfd3f29", - "documentMicroversion": "562fbad41f5c94b4e8929753", - "documentVersion": "b2b50e6fca74f541285598d2", - "elementId": "cb09afde2287e85126e219d6", - "fullConfiguration": "List_RLLNj5hPLm22K3=Simple_Axe", - "id": "McPFfHZbW2/EPDp0S", - "isStandardContent": false, - "name": "WJ-WK00-0122TOPCABINETCASE_95 <1>", - "partId": "JFT", - "suppressed": false, - "type": "Part" -} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/wj_wk00_0122topcabinetcase_95__configuration_simple_axe.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/wj_wk00_0122topcabinetcase_95__configuration_simple_axe.stl deleted file mode 100644 index 2ee91f3d..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/wj_wk00_0122topcabinetcase_95__configuration_simple_axe.stl +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8eb77f6be288d16c44f0869824e53124b0fc05d70acf84f257ae0e91aa81bd04 -size 104884 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/wj_wk00_0123middlecase_56__configuration_default.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/wj_wk00_0123middlecase_56__configuration_default.part deleted file mode 100644 index 3c02cc56..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/wj_wk00_0123middlecase_56__configuration_default.part +++ /dev/null @@ -1,14 +0,0 @@ -{ - "configuration": "List_RLLNj5hPLm22K3=Default", - "documentId": "4e2472e743f2deb9dbfd3f29", - "documentMicroversion": "9cbc0f0673222989756dc3bd", - "documentVersion": "ab0d611cff58eb03c4b43922", - "elementId": "cb09afde2287e85126e219d6", - "fullConfiguration": "List_RLLNj5hPLm22K3=Default", - "id": "MX9cSWxeY0rpuKITc", - "isStandardContent": false, - "name": "WJ-WK00-0123MIDDLECASE_56 <1>", - "partId": "JFP", - "suppressed": false, - "type": "Part" -} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/wj_wk00_0123middlecase_56__configuration_default.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/wj_wk00_0123middlecase_56__configuration_default.stl deleted file mode 100644 index eaaf8c5b..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/wj_wk00_0123middlecase_56__configuration_default.stl +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a4d233ef0122e27951d21461d8c30e90285708ede5672962e0340fe0185fc0ac -size 49684 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/wj_wk00_0123middlecase_56__configuration_simple_axe.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/wj_wk00_0123middlecase_56__configuration_simple_axe.part deleted file mode 100644 index bfdf0e98..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/wj_wk00_0123middlecase_56__configuration_simple_axe.part +++ /dev/null @@ -1,14 +0,0 @@ -{ - "configuration": "List_RLLNj5hPLm22K3=Simple_Axe", - "documentId": "4e2472e743f2deb9dbfd3f29", - "documentMicroversion": "562fbad41f5c94b4e8929753", - "documentVersion": "b2b50e6fca74f541285598d2", - "elementId": "cb09afde2287e85126e219d6", - "fullConfiguration": "List_RLLNj5hPLm22K3=Simple_Axe", - "id": "MJpNLRylDLT4+V3p5", - "isStandardContent": false, - "name": "WJ-WK00-0123MIDDLECASE_56 <1>", - "partId": "JFP", - "suppressed": false, - "type": "Part" -} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/wj_wk00_0123middlecase_56__configuration_simple_axe.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/wj_wk00_0123middlecase_56__configuration_simple_axe.stl deleted file mode 100644 index 4dce18fb..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/wj_wk00_0123middlecase_56__configuration_simple_axe.stl +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3c459b2adcabec2a270ceec40a983cb9c640f01a9736992f276f8487f0d4ffb8 -size 49684 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/wj_wk00_0124bottomcase_45__configuration_default.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/wj_wk00_0124bottomcase_45__configuration_default.part deleted file mode 100644 index e03ef9ac..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/wj_wk00_0124bottomcase_45__configuration_default.part +++ /dev/null @@ -1,14 +0,0 @@ -{ - "configuration": "List_RLLNj5hPLm22K3=Default", - "documentId": "4e2472e743f2deb9dbfd3f29", - "documentMicroversion": "9cbc0f0673222989756dc3bd", - "documentVersion": "ab0d611cff58eb03c4b43922", - "elementId": "cb09afde2287e85126e219d6", - "fullConfiguration": "List_RLLNj5hPLm22K3=Default", - "id": "Mp+b1niKgywMWznYQ", - "isStandardContent": false, - "name": "WJ-WK00-0124BOTTOMCASE_45 <1>", - "partId": "JFH", - "suppressed": false, - "type": "Part" -} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/wj_wk00_0124bottomcase_45__configuration_default.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/wj_wk00_0124bottomcase_45__configuration_default.stl deleted file mode 100644 index 2d0274d1..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/wj_wk00_0124bottomcase_45__configuration_default.stl +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:651f10e580d0390ab84c1fbd3de339eabad094d5da678d4d62465abce8431178 -size 314584 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/wj_wk00_0124bottomcase_45__configuration_simple_axe.part b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/wj_wk00_0124bottomcase_45__configuration_simple_axe.part deleted file mode 100644 index dc46c65e..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/wj_wk00_0124bottomcase_45__configuration_simple_axe.part +++ /dev/null @@ -1,14 +0,0 @@ -{ - "configuration": "List_RLLNj5hPLm22K3=Simple_Axe", - "documentId": "4e2472e743f2deb9dbfd3f29", - "documentMicroversion": "562fbad41f5c94b4e8929753", - "documentVersion": "b2b50e6fca74f541285598d2", - "elementId": "cb09afde2287e85126e219d6", - "fullConfiguration": "List_RLLNj5hPLm22K3=Simple_Axe", - "id": "MGhQLzn6iUDbdNEjQ", - "isStandardContent": false, - "name": "WJ-WK00-0124BOTTOMCASE_45 <1>", - "partId": "JFH", - "suppressed": false, - "type": "Part" -} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/wj_wk00_0124bottomcase_45__configuration_simple_axe.stl b/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/wj_wk00_0124bottomcase_45__configuration_simple_axe.stl deleted file mode 100644 index 2709632d..00000000 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/assets/wj_wk00_0124bottomcase_45__configuration_simple_axe.stl +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f3b671571125f05c7069b1497e2c1b3f7db9ab579000e7c105b8f0a4b36808fe -size 104884 diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/config.json b/src/reachy_mini/descriptions/reachy_mini/mjcf/config.json index 2a3405a2..20c7c8d4 100644 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/config.json +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/config.json @@ -1,10 +1,10 @@ { - "url": "https://cad.onshape.com/documents/e305b26a84339af20c593973/w/ec7765eccdaa9a5960507125/e/251023ea04190ab16854eb80", + "url": "https://cad.onshape.com/documents/7836ed39be931c6ece17e007/w/39d557f31a0ba179c9e7bd5b/e/c82436d68c7d00e142abc581", "outputFormat": "mujoco", "robot_name": "reachy_mini", "output_filename": "reachy_mini", "simplify_stls": true, - "max_stl_size": 0.3, + "max_stl_size": 1.0, "ignore": { "*": "collision", "!turning_bowl": "collision" diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/joints_properties.xml b/src/reachy_mini/descriptions/reachy_mini/mjcf/joints_properties.xml index 3cb07f37..7a166408 100644 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/joints_properties.xml +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/joints_properties.xml @@ -2,8 +2,8 @@ - - + + diff --git a/src/reachy_mini/descriptions/reachy_mini/mjcf/reachy_mini.xml b/src/reachy_mini/descriptions/reachy_mini/mjcf/reachy_mini.xml index a97dbc09..e9b230a7 100644 --- a/src/reachy_mini/descriptions/reachy_mini/mjcf/reachy_mini.xml +++ b/src/reachy_mini/descriptions/reachy_mini/mjcf/reachy_mini.xml @@ -1,6 +1,6 @@ - + @@ -11,7 +11,7 @@ - + @@ -19,9 +19,8 @@ - - - + + @@ -53,309 +52,473 @@ - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + - - - - - - - - - - - + + + + + + + + + + + + + - - - + + + + + + + - - - - - - - + + + + + + + + + + + + + - - - + + + + + + + + + + + + + + + + + + + + + - + + + + + + + - + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - + - - - - - + + + + + + + + + + + + + + + + + + + + + - - - + + + + + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - + + + - + - - - - - - - - - - - - + + + + + + + + + + + + + + - - - - - - - - - + + + + + - - - - - - - - - - - - + + + + + + + + + + + + + + - - - - - - - - - + + + + + - - - - - - - - - - - - + + + + + + + + + + + + + + - - - - - - - + + + - + - - - - - - - - - - - - + + + + + + + + + + + + + + - - - - - - - + + + - + - - - - - - - - - - - - + + + + + + + + + + + + + + - - - - - - - - - - + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + + + + + - + - - - + + + - + + + + + + + + + + + + + + + + + + - - - - - + + + - - - - - - - - - - - - - - - + + + + + + + + + @@ -364,79 +527,110 @@ + + + + + + + + + + - - - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - + + + + + + + + diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/5w_speaker.part b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/5w_speaker.part new file mode 100644 index 00000000..323829c4 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/5w_speaker.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "22df16c0b36fccefd62a3ccb", + "elementId": "48f0e86376312f8ca242a082", + "fullConfiguration": "default", + "id": "MUIOPFRAXdyRYA5/y", + "isStandardContent": false, + "name": "5W_SPEAKER <1>", + "partId": "JFD", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/5w_speaker.stl b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/5w_speaker.stl new file mode 100644 index 00000000..00a29236 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/5w_speaker.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:89c220ac5fa6dd559fcd2f3faccb7197904ab6b882ccf5d2ad04b17a09d58b98 +size 229884 diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/antenna.part b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/antenna.part new file mode 100644 index 00000000..3e6670e8 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/antenna.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "22df16c0b36fccefd62a3ccb", + "elementId": "d5df0bcac25cddb763b3a090", + "fullConfiguration": "default", + "id": "MT+KnYpqaZ58+3raS", + "isStandardContent": false, + "name": "antenna <2>", + "partId": "JFD", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/antenna.stl b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/antenna.stl new file mode 100644 index 00000000..1dee4a7c --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/antenna.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ebe995bfc23aa8f832e86065c364ddf5bf0c1341d6cd4da30790631c7e15a275 +size 314584 diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/antenna_body_3dprint.part b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/antenna_body_3dprint.part new file mode 100644 index 00000000..7b7ceb21 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/antenna_body_3dprint.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "22df16c0b36fccefd62a3ccb", + "elementId": "41e19b342cf83e0d866fde9b", + "fullConfiguration": "default", + "id": "M6OWFp5hfkfES9FmB", + "isStandardContent": false, + "name": "Antenna_body_3DPrint <2>", + "partId": "JF/", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/antenna_body_3dprint.stl b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/antenna_body_3dprint.stl new file mode 100644 index 00000000..57603870 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/antenna_body_3dprint.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3f760ace60b1a368ba7c51bcd01a152a85e66a587fb24d55886cb9d9ffb9008f +size 241784 diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/antenna_holder_l_3dprint.part b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/antenna_holder_l_3dprint.part new file mode 100644 index 00000000..01fa39d2 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/antenna_holder_l_3dprint.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "22df16c0b36fccefd62a3ccb", + "elementId": "41e19b342cf83e0d866fde9b", + "fullConfiguration": "default", + "id": "MHVB1f7lyvjCWrFJe", + "isStandardContent": false, + "name": "Antenna_Holder-L_3DPrint <1>", + "partId": "KFDB", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/antenna_holder_l_3dprint.stl b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/antenna_holder_l_3dprint.stl new file mode 100644 index 00000000..661802d1 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/antenna_holder_l_3dprint.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2163af81f112d0e1503f284213e831ff7692960cbcb7085ad6db8b63912fd466 +size 314584 diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/antenna_holder_r_3dprint.part b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/antenna_holder_r_3dprint.part new file mode 100644 index 00000000..047b5202 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/antenna_holder_r_3dprint.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "22df16c0b36fccefd62a3ccb", + "elementId": "41e19b342cf83e0d866fde9b", + "fullConfiguration": "default", + "id": "MX6Uc6wQ4cFgySRks", + "isStandardContent": false, + "name": "Antenna_Holder-R_3DPrint <1>", + "partId": "JFv", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/antenna_holder_r_3dprint.stl b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/antenna_holder_r_3dprint.stl new file mode 100644 index 00000000..2a3548a2 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/antenna_holder_r_3dprint.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3f516056c535ac6bc8ec94983e48945bfca75ccf17d058263bfe67c4883d8932 +size 314584 diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/antenna_interface_3dprint.part b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/antenna_interface_3dprint.part new file mode 100644 index 00000000..49e9dfb4 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/antenna_interface_3dprint.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "22df16c0b36fccefd62a3ccb", + "elementId": "41e19b342cf83e0d866fde9b", + "fullConfiguration": "default", + "id": "MObKCzX1Fco0wvr0/", + "isStandardContent": false, + "name": "Antenna_interface_3DPrint <2>", + "partId": "JF7", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/antenna_interface_3dprint.stl b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/antenna_interface_3dprint.stl new file mode 100644 index 00000000..15b32f15 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/antenna_interface_3dprint.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:662384ed2de2c37fff6d5bab1ba0ccb76ee03f5d1680043d255e33e879b481bb +size 179984 diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/arducam.part b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/arducam.part new file mode 100644 index 00000000..6268a27d --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/arducam.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "22df16c0b36fccefd62a3ccb", + "elementId": "ae2c63bc6f578a46d35cd365", + "fullConfiguration": "default", + "id": "MDupaBfND/yK9FERm", + "isStandardContent": false, + "name": "Arducam <1>", + "partId": "JGD", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/arducam.stl b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/arducam.stl new file mode 100644 index 00000000..7badfd2f --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/arducam.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8916e1a5fe5ffd8c0c3eded5f173871be269a2c26a437db7f7039e6c8cc7a020 +size 314584 diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/b3b_eh.part b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/b3b_eh.part new file mode 100644 index 00000000..afa468c7 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/b3b_eh.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "22df16c0b36fccefd62a3ccb", + "elementId": "2cfe27202252cfce42ed7ee4", + "fullConfiguration": "default", + "id": "Md1pPF2974d9JOL2l", + "isStandardContent": false, + "name": "B3B-EH <1>", + "partId": "JFr", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/b3b_eh.stl b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/b3b_eh.stl new file mode 100644 index 00000000..29565d9e --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/b3b_eh.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:388ed3ff01359dac77cad8f1a14c4babeb4e2b72b5c1c60333fa2682bf62ac17 +size 9884 diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/b3b_eh_1.part b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/b3b_eh_1.part new file mode 100644 index 00000000..a30748b8 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/b3b_eh_1.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "22df16c0b36fccefd62a3ccb", + "elementId": "2cfe27202252cfce42ed7ee4", + "fullConfiguration": "default", + "id": "MMuPyh0yjSzJko9Po", + "isStandardContent": false, + "name": "B3B-EH_1 <1>", + "partId": "JFT", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/b3b_eh_1.stl b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/b3b_eh_1.stl new file mode 100644 index 00000000..268d4d96 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/b3b_eh_1.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a741e87c71a9ff662a24ab5d6c7b8397afc6a7f7506910992814506d98d8e21f +size 9884 diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/bearing_85x110x13.part b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/bearing_85x110x13.part new file mode 100644 index 00000000..c80cc1b7 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/bearing_85x110x13.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "22df16c0b36fccefd62a3ccb", + "elementId": "d5ff0fe74f14f26be2deb5d4", + "fullConfiguration": "default", + "id": "MYk5LvI8gIvlMtfS7", + "isStandardContent": false, + "name": "Bearing_85x110x13 <1>", + "partId": "JGD", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/bearing_85x110x13.stl b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/bearing_85x110x13.stl new file mode 100644 index 00000000..751ac994 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/bearing_85x110x13.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1d6052d367e0a9f36063a3be16c77e34d8c5da3bb001346a317b75f19b9e60fe +size 314584 diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/big_lens_d40.part b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/big_lens_d40.part new file mode 100644 index 00000000..d866fd19 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/big_lens_d40.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "22df16c0b36fccefd62a3ccb", + "elementId": "f67f6ec9755019fa8d4cde38", + "fullConfiguration": "default", + "id": "M6vM68+58boAYt2AP", + "isStandardContent": false, + "name": "Big Lens D40 <1>", + "partId": "JFD", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/big_lens_d40.stl b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/big_lens_d40.stl new file mode 100644 index 00000000..afa55360 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/big_lens_d40.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4df711c8e29a677f62622d57c5cac01257ae9a44f46adb67fefb54b8fe52a6eb +size 151284 diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/body_down_3dprint.part b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/body_down_3dprint.part new file mode 100644 index 00000000..a35e3a29 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/body_down_3dprint.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "22df16c0b36fccefd62a3ccb", + "elementId": "41e19b342cf83e0d866fde9b", + "fullConfiguration": "default", + "id": "M40W4a7sQuI8QEDbK", + "isStandardContent": false, + "name": "BODY-DOWN_3DPrint <1>", + "partId": "JFP", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/body_down_3dprint.stl b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/body_down_3dprint.stl new file mode 100644 index 00000000..172d7baa --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/body_down_3dprint.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b3d46f953d49182f85fa818a5d3935c688afffdf4d7a0119aa58f9292a8e3707 +size 314584 diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/body_foot_3dprint.part b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/body_foot_3dprint.part new file mode 100644 index 00000000..07ac3aaa --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/body_foot_3dprint.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "22df16c0b36fccefd62a3ccb", + "elementId": "41e19b342cf83e0d866fde9b", + "fullConfiguration": "default", + "id": "MFPx9mgqcSNR4iAYO", + "isStandardContent": false, + "name": "BODY-FOOT_3DPrint <1>", + "partId": "JFH", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/body_foot_3dprint.stl b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/body_foot_3dprint.stl new file mode 100644 index 00000000..5f68209f --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/body_foot_3dprint.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eab9440d4d6745c99445dba3887f4668b48780e8a10aa1efc05e3f9481b40caf +size 314584 diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/body_top_3dprint.part b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/body_top_3dprint.part new file mode 100644 index 00000000..17722be8 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/body_top_3dprint.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "22df16c0b36fccefd62a3ccb", + "elementId": "41e19b342cf83e0d866fde9b", + "fullConfiguration": "default", + "id": "MfqCGZgMWtuWpureN", + "isStandardContent": false, + "name": "BODY_TOP_3DPrint <1>", + "partId": "JFb", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/body_top_3dprint.stl b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/body_top_3dprint.stl new file mode 100644 index 00000000..067b40f1 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/body_top_3dprint.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b985a9288abfc9894335c609bf24943551ae185e855f40c9a9f89c6a7c0eb831 +size 314584 diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/body_turning_3dprint.part b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/body_turning_3dprint.part new file mode 100644 index 00000000..a967bb56 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/body_turning_3dprint.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "22df16c0b36fccefd62a3ccb", + "elementId": "41e19b342cf83e0d866fde9b", + "fullConfiguration": "default", + "id": "MttvYsoT0rlrLKDwS", + "isStandardContent": false, + "name": "BODY-TURNING_3DPrint <1>", + "partId": "JFL", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/body_turning_3dprint.stl b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/body_turning_3dprint.stl new file mode 100644 index 00000000..0cd5f031 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/body_turning_3dprint.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0c9757573938050cb1c0a4c7fdf37d8bbb3f9cf28dc19444c3487c1c5765d264 +size 314584 diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/bts2_m2_6x8.part b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/bts2_m2_6x8.part new file mode 100644 index 00000000..afd31549 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/bts2_m2_6x8.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "22df16c0b36fccefd62a3ccb", + "elementId": "2cfe27202252cfce42ed7ee4", + "fullConfiguration": "default", + "id": "MHzA6RZ/kEAMV93SN", + "isStandardContent": false, + "name": "BTS2_M2_6X8 <1>", + "partId": "JFL", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/bts2_m2_6x8.stl b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/bts2_m2_6x8.stl new file mode 100644 index 00000000..c9d6877c --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/bts2_m2_6x8.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:303a706276ef0a3dcf8b6deeabf73969d2c48cb6628209bf0fed825b622deccb +size 269184 diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/collision/README.m b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/collision/README.m new file mode 100644 index 00000000..edda0bc7 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/collision/README.m @@ -0,0 +1 @@ +Created with: https://github.com/pollen-robotics/reachy_mini_stl_convexify \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/collision/coarse/body_top_3dprint_collider_back_0.stl b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/collision/coarse/body_top_3dprint_collider_back_0.stl new file mode 100644 index 00000000..b134982c --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/collision/coarse/body_top_3dprint_collider_back_0.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8f92539090c8fbef3a292ffbdc6a1019828ad5e33a5b87354ad08032a59d0c35 +size 4884 diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/collision/coarse/body_top_3dprint_collider_back_1.stl b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/collision/coarse/body_top_3dprint_collider_back_1.stl new file mode 100644 index 00000000..dc98ea93 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/collision/coarse/body_top_3dprint_collider_back_1.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9186fdc67813c3b7f3fba721cbfbb14d5e6f20bcabe1897d488913b6879bfd78 +size 4784 diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/collision/coarse/body_top_3dprint_collider_back_2.stl b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/collision/coarse/body_top_3dprint_collider_back_2.stl new file mode 100644 index 00000000..592ecb1e --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/collision/coarse/body_top_3dprint_collider_back_2.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2db1d2726afd373e145a7b3cb8340983eeff49c07f8e4057e2d4eec7f91c35df +size 4884 diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/collision/coarse/body_top_3dprint_collider_back_3.stl b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/collision/coarse/body_top_3dprint_collider_back_3.stl new file mode 100644 index 00000000..2bea29e7 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/collision/coarse/body_top_3dprint_collider_back_3.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3e7916bfcdccd254c51143774575c7a0924a1a1939fd9a4755ae3b8f2c920bdc +size 4884 diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/collision/coarse/body_top_3dprint_collider_front_0.stl b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/collision/coarse/body_top_3dprint_collider_front_0.stl new file mode 100644 index 00000000..728659d3 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/collision/coarse/body_top_3dprint_collider_front_0.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:94d9c386d10344ae4fa03baae3ea0ca935c8ab2a5d41352a4b4c59c3ac2fd9e1 +size 4884 diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/collision/coarse/body_top_3dprint_collider_front_1.stl b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/collision/coarse/body_top_3dprint_collider_front_1.stl new file mode 100644 index 00000000..d296a17e --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/collision/coarse/body_top_3dprint_collider_front_1.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e893cc8cd9581722ffe7798031076190cdc2264edc70694d925be5cf326b8bc2 +size 4884 diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/collision/coarse/body_top_3dprint_collider_front_2.stl b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/collision/coarse/body_top_3dprint_collider_front_2.stl new file mode 100644 index 00000000..d990c548 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/collision/coarse/body_top_3dprint_collider_front_2.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1ec16d901b544cd898eb1836d4f61fc16855919bd856c82f0fd23ac180e684af +size 4884 diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/collision/coarse/head_one_3dprint_collider_0.stl b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/collision/coarse/head_one_3dprint_collider_0.stl new file mode 100644 index 00000000..b5a03639 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/collision/coarse/head_one_3dprint_collider_0.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9ba0e5a4dc95869e16212e69fa3712ea7f021378cf76db0b1a8d2300eaab3c64 +size 6884 diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/collision/fine/body_top_3dprint_collider_back_0.stl b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/collision/fine/body_top_3dprint_collider_back_0.stl new file mode 100644 index 00000000..6e405fc6 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/collision/fine/body_top_3dprint_collider_back_0.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7c22bb171ab47155a4fe2946cd267f8e43cba901dd11d3106082bbe1848f90b4 +size 190084 diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/collision/fine/body_top_3dprint_collider_back_1.stl b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/collision/fine/body_top_3dprint_collider_back_1.stl new file mode 100644 index 00000000..34019f99 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/collision/fine/body_top_3dprint_collider_back_1.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9a2c6189ab48a1b296ab5e0695363ab20021154dc7823d3eef8a188b992c1682 +size 195784 diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/collision/fine/body_top_3dprint_collider_back_2.stl b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/collision/fine/body_top_3dprint_collider_back_2.stl new file mode 100644 index 00000000..67603dad --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/collision/fine/body_top_3dprint_collider_back_2.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cbdf0d34da912a29174148bd78b90e3541755f9bc20d2a5cc624e3565aef3d94 +size 78684 diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/collision/fine/body_top_3dprint_collider_back_3.stl b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/collision/fine/body_top_3dprint_collider_back_3.stl new file mode 100644 index 00000000..37d62560 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/collision/fine/body_top_3dprint_collider_back_3.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4e7c26eb2033477e711d6ea535b955a91f443b74d21a4d8e5ed1bfb72c10c401 +size 245084 diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/collision/fine/body_top_3dprint_collider_front_0.stl b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/collision/fine/body_top_3dprint_collider_front_0.stl new file mode 100644 index 00000000..f0f3c8da --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/collision/fine/body_top_3dprint_collider_front_0.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:27dcf3bcfc55a11f5fe3a6a5f01fe253fb5e9cf9d736aa9b83a44d286aeef38c +size 185384 diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/collision/fine/body_top_3dprint_collider_front_1.stl b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/collision/fine/body_top_3dprint_collider_front_1.stl new file mode 100644 index 00000000..ce58be8b --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/collision/fine/body_top_3dprint_collider_front_1.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:89940a91f8e1bf1d1b7c4844bc58a8a03f5c3983cfc174573e8f60f6a47c1109 +size 182484 diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/collision/fine/body_top_3dprint_collider_front_2.stl b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/collision/fine/body_top_3dprint_collider_front_2.stl new file mode 100644 index 00000000..8964aa93 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/collision/fine/body_top_3dprint_collider_front_2.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6ccf48cd480051da28caee941ad1e130f74cebfd2c474d5791d5e014e10fcdfd +size 219184 diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/collision/fine/head_one_3dprint_collider_0.stl b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/collision/fine/head_one_3dprint_collider_0.stl new file mode 100644 index 00000000..1d636e5f --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/collision/fine/head_one_3dprint_collider_0.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5a28add53d3bc49c5b7f6d9e8f8f0884bbfa1ad9fe81af5ca58f01d08a072ed9 +size 1359584 diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/dc15_a01_case_b_dummy.part b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/dc15_a01_case_b_dummy.part index d010123c..420f5df5 100644 --- a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/dc15_a01_case_b_dummy.part +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/dc15_a01_case_b_dummy.part @@ -1,13 +1,13 @@ { "configuration": "default", - "documentId": "e305b26a84339af20c593973", - "documentMicroversion": "4913859d0d2ce0a13b3adb26", - "elementId": "f05b326e391ef37011cd1007", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "22df16c0b36fccefd62a3ccb", + "elementId": "2cfe27202252cfce42ed7ee4", "fullConfiguration": "default", - "id": "MhsjpiYGf8ZgBO855", + "id": "M0s224h4t0HqzV/u4", "isStandardContent": false, - "name": "DC15_A01_CASE_B_DUMMY <2>", - "partId": "JbL", + "name": "DC15_A01_CASE_B_DUMMY <1>", + "partId": "JFf", "suppressed": false, "type": "Part" } \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/dc15_a01_case_b_dummy.stl b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/dc15_a01_case_b_dummy.stl index d11c09c9..be16e3d8 100644 --- a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/dc15_a01_case_b_dummy.stl +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/dc15_a01_case_b_dummy.stl @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8771ec35e39954efed45a4d5f83e112ffa08d39f90264f15d35f96bb8220a6e5 +oid sha256:0b108f9390168a9052b408b292e316112f28e11d5ca51e28a374f29ce9ef2790 size 248884 diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/dc15_a01_case_f_dummy.part b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/dc15_a01_case_f_dummy.part index a8258862..95fecb12 100644 --- a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/dc15_a01_case_f_dummy.part +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/dc15_a01_case_f_dummy.part @@ -1,13 +1,13 @@ { "configuration": "default", - "documentId": "e305b26a84339af20c593973", - "documentMicroversion": "4913859d0d2ce0a13b3adb26", - "elementId": "f05b326e391ef37011cd1007", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "22df16c0b36fccefd62a3ccb", + "elementId": "2cfe27202252cfce42ed7ee4", "fullConfiguration": "default", - "id": "MzNAW97t7HwMX98P9", + "id": "MJyRwhvvOMia59Q6o", "isStandardContent": false, "name": "DC15_A01_CASE_F_DUMMY <1>", - "partId": "JbH", + "partId": "JFj", "suppressed": false, "type": "Part" } \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/dc15_a01_case_f_dummy.stl b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/dc15_a01_case_f_dummy.stl index 8b61192c..366b36df 100644 --- a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/dc15_a01_case_f_dummy.stl +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/dc15_a01_case_f_dummy.stl @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1fa27c05b5b93e0982729f241bbca8a5aba21c1312b8e0cf758ae99c083a8b45 +oid sha256:6de99b6af2361d2fdb751bcb78768e7565bc5b40fbdd6cda3e2c92f277135d33 size 314484 diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/dc15_a01_case_m_dummy.part b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/dc15_a01_case_m_dummy.part index c1137513..c3f47359 100644 --- a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/dc15_a01_case_m_dummy.part +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/dc15_a01_case_m_dummy.part @@ -1,13 +1,13 @@ { "configuration": "default", - "documentId": "e305b26a84339af20c593973", - "documentMicroversion": "4913859d0d2ce0a13b3adb26", - "elementId": "f05b326e391ef37011cd1007", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "22df16c0b36fccefd62a3ccb", + "elementId": "2cfe27202252cfce42ed7ee4", "fullConfiguration": "default", - "id": "MrR/9ODeIeBdYgmiv", + "id": "M6qXbiravwBh/M2FZ", "isStandardContent": false, "name": "DC15_A01_CASE_M_DUMMY <1>", - "partId": "JbD", + "partId": "JFn", "suppressed": false, "type": "Part" } \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/dc15_a01_case_m_dummy.stl b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/dc15_a01_case_m_dummy.stl index 5a82664c..409e7a84 100644 --- a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/dc15_a01_case_m_dummy.stl +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/dc15_a01_case_m_dummy.stl @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fe05cd55be2275578b86c3ac6b20657d530efecfb6ebe0fb09e7db0a6f50e528 +oid sha256:716c7f05cb780b5eb1dc7e614b8376e587683f2922fcb386d0b8152a73b5af68 size 162184 diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/dc15_a01_horn_dummy.part b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/dc15_a01_horn_dummy.part index 3916c91b..5e293963 100644 --- a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/dc15_a01_horn_dummy.part +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/dc15_a01_horn_dummy.part @@ -1,13 +1,13 @@ { "configuration": "default", - "documentId": "e305b26a84339af20c593973", - "documentMicroversion": "4913859d0d2ce0a13b3adb26", - "elementId": "f05b326e391ef37011cd1007", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "22df16c0b36fccefd62a3ccb", + "elementId": "2cfe27202252cfce42ed7ee4", "fullConfiguration": "default", - "id": "Mq6TJGsdH+ga51RhW", + "id": "M/hbODfuW0gbGU1TK", "isStandardContent": false, - "name": "DC15_A01_HORN_DUMMY <5>", - "partId": "JbP", + "name": "DC15_A01_HORN_DUMMY <9>", + "partId": "JFX", "suppressed": false, "type": "Part" } \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/dc15_a01_horn_dummy.stl b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/dc15_a01_horn_dummy.stl index fb7d0627..bee16056 100644 --- a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/dc15_a01_horn_dummy.stl +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/dc15_a01_horn_dummy.stl @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c4971912f3c6f863394b1d54522cc3ec41cb734a6cf96ab26ee3e1562f8befef +oid sha256:f1fb77ee29fb48c09c125ae3a3cd4641673a08f513d3a5002f1453580a41c4cb size 314584 diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/dc15_a01_led_cap2_dummy.part b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/dc15_a01_led_cap2_dummy.part new file mode 100644 index 00000000..76e2a26f --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/dc15_a01_led_cap2_dummy.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "22df16c0b36fccefd62a3ccb", + "elementId": "2cfe27202252cfce42ed7ee4", + "fullConfiguration": "default", + "id": "MFvbXu4svYiIz5iLO", + "isStandardContent": false, + "name": "DC15_A01_LED_CAP2_DUMMY <1>", + "partId": "JFv", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/dc15_a01_led_cap2_dummy.stl b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/dc15_a01_led_cap2_dummy.stl new file mode 100644 index 00000000..b8ff0e55 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/dc15_a01_led_cap2_dummy.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3c8ea014b723e253c28fa3bf5062c794b18470cd7b5d6b33e723a9638d974f25 +size 1684 diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/glasses_dolder_3dprint.part b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/glasses_dolder_3dprint.part new file mode 100644 index 00000000..a5ca077d --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/glasses_dolder_3dprint.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "22df16c0b36fccefd62a3ccb", + "elementId": "41e19b342cf83e0d866fde9b", + "fullConfiguration": "default", + "id": "MsCmmfoZrFsTVz4Vs", + "isStandardContent": false, + "name": "GLASSES-DOLDER_3DPrint <1>", + "partId": "JFf", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/glasses_dolder_3dprint.stl b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/glasses_dolder_3dprint.stl new file mode 100644 index 00000000..66835911 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/glasses_dolder_3dprint.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:77786d52ead05d3f8c181d0b1bfbe3eaa8f42e0ae81e31847129bb031cbfe6c5 +size 314584 diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/head_back_3dprint.part b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/head_back_3dprint.part new file mode 100644 index 00000000..d48bd915 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/head_back_3dprint.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "22df16c0b36fccefd62a3ccb", + "elementId": "41e19b342cf83e0d866fde9b", + "fullConfiguration": "default", + "id": "MDdoiC6yCX9ILARl+", + "isStandardContent": false, + "name": "HEAD-BACK_3DPrint <1>", + "partId": "JFr", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/head_back_3dprint.stl b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/head_back_3dprint.stl new file mode 100644 index 00000000..fac35aaa --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/head_back_3dprint.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8e6e60fd980a2a48209471a0ddaac905b5d95c7081646e29c5af15cf3f1a681f +size 314584 diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/head_front_3dprint.part b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/head_front_3dprint.part new file mode 100644 index 00000000..e23c54fe --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/head_front_3dprint.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "22df16c0b36fccefd62a3ccb", + "elementId": "41e19b342cf83e0d866fde9b", + "fullConfiguration": "default", + "id": "MsTE6+BbSl0w4pt9R", + "isStandardContent": false, + "name": "HEAD-FRONT_3DPrint <1>", + "partId": "JFD", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/head_front_3dprint.stl b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/head_front_3dprint.stl new file mode 100644 index 00000000..2189569d --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/head_front_3dprint.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3474bd1025c6b037e42d6d51428a1370ef88674b9a3988cd6b34e0be0d0049c2 +size 314584 diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/head_mic_3dprint.part b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/head_mic_3dprint.part new file mode 100644 index 00000000..29d99893 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/head_mic_3dprint.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "22df16c0b36fccefd62a3ccb", + "elementId": "41e19b342cf83e0d866fde9b", + "fullConfiguration": "default", + "id": "MuZC9z8GQmMdOw95U", + "isStandardContent": false, + "name": "HEAD-MIC_3DPrint <1>", + "partId": "JFj", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/head_mic_3dprint.stl b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/head_mic_3dprint.stl new file mode 100644 index 00000000..1c74e833 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/head_mic_3dprint.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9ea18530c4b9305199577d5301583e73f8c8f42694c6e8c50c8103f147da1be8 +size 314584 diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/lens_cap_d30_3dprint.part b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/lens_cap_d30_3dprint.part new file mode 100644 index 00000000..0c203f78 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/lens_cap_d30_3dprint.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "22df16c0b36fccefd62a3ccb", + "elementId": "41e19b342cf83e0d866fde9b", + "fullConfiguration": "default", + "id": "MJpFg+P4UMBokBVex", + "isStandardContent": false, + "name": "LENS-CAP-D30_3DPrint <1>", + "partId": "JFn", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/lens_cap_d30_3dprint.stl b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/lens_cap_d30_3dprint.stl new file mode 100644 index 00000000..bc190353 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/lens_cap_d30_3dprint.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:564787bc9073bc68c1598b4674ce751e7a7ed883d305a5cb947d3398360d3737 +size 94284 diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/lens_cap_d40_3dprint.part b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/lens_cap_d40_3dprint.part new file mode 100644 index 00000000..072d2e78 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/lens_cap_d40_3dprint.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "22df16c0b36fccefd62a3ccb", + "elementId": "41e19b342cf83e0d866fde9b", + "fullConfiguration": "default", + "id": "MVC/sOmzNQLy4mD6b", + "isStandardContent": false, + "name": "LENS-CAP-D40_3DPrint <1>", + "partId": "JF3", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/lens_cap_d40_3dprint.stl b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/lens_cap_d40_3dprint.stl new file mode 100644 index 00000000..d7cb1d4d --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/lens_cap_d40_3dprint.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e5a6364fba94a5a82d8a6512a7e042ccc78a0766756b190af055b0f08ef56471 +size 93684 diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/m12_fisheye_lens_1_8mm.part b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/m12_fisheye_lens_1_8mm.part new file mode 100644 index 00000000..0b8ca9e1 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/m12_fisheye_lens_1_8mm.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "22df16c0b36fccefd62a3ccb", + "elementId": "05dc7ef344fe4c18be495b6e", + "fullConfiguration": "default", + "id": "MvKFrBJz+CmxF4Ta4", + "isStandardContent": false, + "name": "M12 fisheye lens 1.8mm <2>", + "partId": "JGD", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/m12_fisheye_lens_1_8mm.stl b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/m12_fisheye_lens_1_8mm.stl new file mode 100644 index 00000000..4ee95bc2 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/m12_fisheye_lens_1_8mm.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bf2e973bd164cd66b747f16e0ad6c4aafc9492e0b053fd40c283f4beba1b6015 +size 259284 diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/mp01062_stewart_arm_3.part b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/mp01062_stewart_arm_3.part new file mode 100644 index 00000000..ac721a20 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/mp01062_stewart_arm_3.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "22df16c0b36fccefd62a3ccb", + "elementId": "41e19b342cf83e0d866fde9b", + "fullConfiguration": "default", + "id": "Mw5N9cfkbCC/CpCKR", + "isStandardContent": false, + "name": "MP01062_STEWART_ARM_3 <1>", + "partId": "KFHB", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/mp01062_stewart_arm_3.stl b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/mp01062_stewart_arm_3.stl new file mode 100644 index 00000000..4fd08b22 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/mp01062_stewart_arm_3.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7b64f2de2a35b6feb9ed8ef728219f1a9600dfe6fcb70256a2e555cd6c49401e +size 155584 diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/neck_reference_3dprint.part b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/neck_reference_3dprint.part new file mode 100644 index 00000000..16ce78dc --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/neck_reference_3dprint.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "22df16c0b36fccefd62a3ccb", + "elementId": "41e19b342cf83e0d866fde9b", + "fullConfiguration": "default", + "id": "M7r6qwCMs4B7OGi6t", + "isStandardContent": false, + "name": "NECK_REFERENCE_3DPrint <1>", + "partId": "JFz", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/neck_reference_3dprint.stl b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/neck_reference_3dprint.stl new file mode 100644 index 00000000..fb2153b5 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/neck_reference_3dprint.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8de811dedf772b757b9b414f3af15154c97267050a84c21cd6be3e355fc23eb8 +size 314584 diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/phs_1_7x20_5_dc10.part b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/phs_1_7x20_5_dc10.part new file mode 100644 index 00000000..4034f3f6 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/phs_1_7x20_5_dc10.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "22df16c0b36fccefd62a3ccb", + "elementId": "2cfe27202252cfce42ed7ee4", + "fullConfiguration": "default", + "id": "MydIXO1yH59xsOSOQ", + "isStandardContent": false, + "name": "PHS_1_7X20_5_DC10 <1>", + "partId": "JFP", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/phs_1_7x20_5_dc10.stl b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/phs_1_7x20_5_dc10.stl new file mode 100644 index 00000000..a0f51417 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/phs_1_7x20_5_dc10.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c3e9d4bddfe4a99c206feba0fe648c1e5caf0aefca6347c7a91ff8f425b58d35 +size 253184 diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/phs_1_7x20_5_dc10_1.part b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/phs_1_7x20_5_dc10_1.part new file mode 100644 index 00000000..04f5f190 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/phs_1_7x20_5_dc10_1.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "22df16c0b36fccefd62a3ccb", + "elementId": "2cfe27202252cfce42ed7ee4", + "fullConfiguration": "default", + "id": "MyXqeYka8V5tcbv+v", + "isStandardContent": false, + "name": "PHS_1_7X20_5_DC10_1 <1>", + "partId": "JFb", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/phs_1_7x20_5_dc10_1.stl b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/phs_1_7x20_5_dc10_1.stl new file mode 100644 index 00000000..f58ca099 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/phs_1_7x20_5_dc10_1.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f9cd84a44f18b39aae56220bfcdfdbf8e39a809c57e4d07c3d7a718c7ab5669e +size 253184 diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/phs_1_7x20_5_dc10_2.part b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/phs_1_7x20_5_dc10_2.part new file mode 100644 index 00000000..c4fbc1e8 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/phs_1_7x20_5_dc10_2.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "22df16c0b36fccefd62a3ccb", + "elementId": "2cfe27202252cfce42ed7ee4", + "fullConfiguration": "default", + "id": "M0HCg1i79OnE6gOgz", + "isStandardContent": false, + "name": "PHS_1_7X20_5_DC10_2 <1>", + "partId": "JFD", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/phs_1_7x20_5_dc10_2.stl b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/phs_1_7x20_5_dc10_2.stl new file mode 100644 index 00000000..9a284934 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/phs_1_7x20_5_dc10_2.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ceba1b5c64923e7b29c5a686d753748b8a99496890ebe850694cba50d306d690 +size 253184 diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/phs_1_7x20_5_dc10_3.part b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/phs_1_7x20_5_dc10_3.part new file mode 100644 index 00000000..8c06bdd0 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/phs_1_7x20_5_dc10_3.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "22df16c0b36fccefd62a3ccb", + "elementId": "2cfe27202252cfce42ed7ee4", + "fullConfiguration": "default", + "id": "MwHtRhbbnn9zJhJOY", + "isStandardContent": false, + "name": "PHS_1_7X20_5_DC10_3 <1>", + "partId": "JFH", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/phs_1_7x20_5_dc10_3.stl b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/phs_1_7x20_5_dc10_3.stl new file mode 100644 index 00000000..e6535203 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/phs_1_7x20_5_dc10_3.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3b446022c08e14a7d7e18ddfc5a5e979347abe1f3165d7d161b630f107964b26 +size 253184 diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/pp01102_arducam_carter.part b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/pp01102_arducam_carter.part new file mode 100644 index 00000000..0e49bdf2 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/pp01102_arducam_carter.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "22df16c0b36fccefd62a3ccb", + "elementId": "41e19b342cf83e0d866fde9b", + "fullConfiguration": "default", + "id": "ME3Sp1txkoCjeAr4W", + "isStandardContent": false, + "name": "PP01102_ArduCam_Carter <1>", + "partId": "KFLB", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/pp01102_arducam_carter.stl b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/pp01102_arducam_carter.stl new file mode 100644 index 00000000..866f890e --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/pp01102_arducam_carter.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:edc115b09647cafaf2fd023cd940817b54c33ce04496e586fe9031bd7582c5c6 +size 109484 diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/small_lens_d30.part b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/small_lens_d30.part new file mode 100644 index 00000000..3d88ba9f --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/small_lens_d30.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "22df16c0b36fccefd62a3ccb", + "elementId": "f67f6ec9755019fa8d4cde38", + "fullConfiguration": "default", + "id": "MeOBTO0SVYe+Ls79P", + "isStandardContent": false, + "name": "Small Lens D30 <1>", + "partId": "JFH", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/small_lens_d30.stl b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/small_lens_d30.stl new file mode 100644 index 00000000..8094f7e3 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/small_lens_d30.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c3f62174bc76b85b39e7835fc9e005fabde685677576c6c1e40753417fa922ec +size 158484 diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/stewart_link_ball.part b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/stewart_link_ball.part new file mode 100644 index 00000000..d08be9b4 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/stewart_link_ball.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "22df16c0b36fccefd62a3ccb", + "elementId": "0c90fa98e702b6ba866efe06", + "fullConfiguration": "default", + "id": "MLAaNJd+l2AB7zwWF", + "isStandardContent": false, + "name": "stewart_link_ball <7>", + "partId": "JFD", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/stewart_link_ball.stl b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/stewart_link_ball.stl new file mode 100644 index 00000000..1a9a1ea3 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/stewart_link_ball.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d24b6357fe780b9f54a550278fded8886524b732e2ec7765eeed1a8eee0e94bd +size 194484 diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/stewart_link_ball__2.part b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/stewart_link_ball__2.part new file mode 100644 index 00000000..f6c45dff --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/stewart_link_ball__2.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "22df16c0b36fccefd62a3ccb", + "elementId": "0c90fa98e702b6ba866efe06", + "fullConfiguration": "default", + "id": "Mzs67Fs/V5a1ifM8W", + "isStandardContent": false, + "name": "stewart_link_ball <10>", + "partId": "JGD", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/stewart_link_ball__2.stl b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/stewart_link_ball__2.stl new file mode 100644 index 00000000..24023fd4 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/stewart_link_ball__2.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:25041196fb7b87a0118d1b283e97bcd56a961aafd08527f1408cc23077e77285 +size 194484 diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/stewart_link_rod.part b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/stewart_link_rod.part new file mode 100644 index 00000000..e64de22e --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/stewart_link_rod.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "22df16c0b36fccefd62a3ccb", + "elementId": "0c90fa98e702b6ba866efe06", + "fullConfiguration": "default", + "id": "MHKZQKk3gjSPuG0gI", + "isStandardContent": false, + "name": "stewart_link_rod <6>", + "partId": "JFH", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/stewart_link_rod.stl b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/stewart_link_rod.stl new file mode 100644 index 00000000..cea3397c --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/stewart_link_rod.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ecacaefa0d9407ee902fcddac86927a27b09a352c4539347dbe8dfcd827af986 +size 314584 diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/stewart_main_plate_3dprint.part b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/stewart_main_plate_3dprint.part new file mode 100644 index 00000000..08c274a4 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/stewart_main_plate_3dprint.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "22df16c0b36fccefd62a3ccb", + "elementId": "41e19b342cf83e0d866fde9b", + "fullConfiguration": "default", + "id": "Mi3j82DWKTUoiYXon", + "isStandardContent": false, + "name": "STEWART_MAIN_PLATE_3DPrint <1>", + "partId": "JFT", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/stewart_main_plate_3dprint.stl b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/stewart_main_plate_3dprint.stl new file mode 100644 index 00000000..f90d2950 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/stewart_main_plate_3dprint.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9577be3c17eeb3447e5bb7e3a2ffab3f40d951b2b341ca25e06e74c35997780d +size 314484 diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/stewart_tricap_3dprint.part b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/stewart_tricap_3dprint.part new file mode 100644 index 00000000..b5c08005 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/stewart_tricap_3dprint.part @@ -0,0 +1,13 @@ +{ + "configuration": "default", + "documentId": "7836ed39be931c6ece17e007", + "documentMicroversion": "22df16c0b36fccefd62a3ccb", + "elementId": "41e19b342cf83e0d866fde9b", + "fullConfiguration": "default", + "id": "M8OX4JdBklJa3ZAV2", + "isStandardContent": false, + "name": "STEWART_TRICAP_3DPrint <1>", + "partId": "JFX", + "suppressed": false, + "type": "Part" +} \ No newline at end of file diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/assets/stewart_tricap_3dprint.stl b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/stewart_tricap_3dprint.stl new file mode 100644 index 00000000..73766e4d --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/assets/stewart_tricap_3dprint.stl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:44c9acd2445753459253e6a1a8cd8b5036e8d9d98becde750cdeb950a01500d4 +size 314484 diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/config.json b/src/reachy_mini/descriptions/reachy_mini/urdf/config.json index a4d44896..10016f3b 100644 --- a/src/reachy_mini/descriptions/reachy_mini/urdf/config.json +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/config.json @@ -1,5 +1,5 @@ { - "url": "https://cad.onshape.com/documents/e305b26a84339af20c593973/w/ec7765eccdaa9a5960507125/e/251023ea04190ab16854eb80", + "url": "https://cad.onshape.com/documents/7836ed39be931c6ece17e007/w/39d557f31a0ba179c9e7bd5b/e/c82436d68c7d00e142abc581", "outputFormat": "urdf", "robot_name": "robot", "output_filename": "robot", diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/robot.urdf b/src/reachy_mini/descriptions/reachy_mini/urdf/robot.urdf index d4fd6818..f141d8a4 100644 --- a/src/reachy_mini/descriptions/reachy_mini/urdf/robot.urdf +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/robot.urdf @@ -1,185 +1,185 @@ - + - - + + - - - + + + - + - + - + - - + + - + - + - + - + - + - + - + - + - - - - - - - - - + - + - + - - + + - + - + - + + + + + + + + + - + - + - + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - + - + - + - + @@ -188,701 +188,1739 @@ - + - + - + - + - + - + - + - + - + - + - - + + - + - + - + - + - + - + - + - + - + - + - + - - + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - + + - + - + - + - + - + - + - + - + - + - + - + - - + + - + - + - + - + - + - + - + - + - + - + - + - - + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - + + - + - + - + + + + + + + + + + + + + + + + + - + - + - + - + - + - + - - + + - + - + - - - - - - - - - + - + - + - - + + - + - + - + - + - + - - + + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + - + - - + + - + - + - + - + - + - - + + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -890,158 +1928,142 @@ - - - - + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - + - - + + - - - + + + - + - + - + - - + + - + - + - + - + - + - - + + + + + + + + + + + + + + + + + + - + - + - - + + - - + + - - + + - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + - + - - + + - + - + - - + + @@ -1049,767 +2071,849 @@ - - - - + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - + - - + + - - - + + + - + - + - + - - + + - + - + - + - + - + - - + + + + + + + + + + + + + + + + + + - + - + - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - - + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + - + - + - + - + - + - + - + - - + + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + - + - - + + - + - + - + - + - + - - + + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - + - + - + - - + + - + - + - + - + - + - + - + - + - + - + - + - - + + - + - + - + - + - + - + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + @@ -1823,13 +2927,13 @@ - - + + - - + + @@ -1837,14 +2941,14 @@ - - - - + + + + - - + + @@ -1852,10 +2956,10 @@ - - - - + + + + @@ -1868,8 +2972,8 @@ - - + + @@ -1883,13 +2987,13 @@ - - + + - - + + @@ -1897,10 +3001,10 @@ - - - - + + + + @@ -1913,13 +3017,13 @@ - - + + - - + + @@ -1927,187 +3031,250 @@ - - - - + + + + - - + + - - - + + + - + + + + + + + + + + + + + + + + + - + - + - + - + + + + + + + + + + + + + + + + + - + - + - + - + - + - + - + - - - - - + + + + + - + - - + + - - - + + + - + - + - + - + - + - + - + - + - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - + + + + + - + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - - - - - + + + + + - + - - - - - - + + + + + - + - + - - + + diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/robot.urdf.bak b/src/reachy_mini/descriptions/reachy_mini/urdf/robot.urdf.bak index a45fe1bb..65c5c15b 100644 --- a/src/reachy_mini/descriptions/reachy_mini/urdf/robot.urdf.bak +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/robot.urdf.bak @@ -1,920 +1,2069 @@ - + - - + + - - - + + + - + - + - + - - + + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - - + + + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + + + + + + + + + + + + + + + + + - + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + + + + + + + + + + + + + + + + + - + - - + + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - - + + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - - - - - - - - - + - + - + - - + + - + - + - + - + - + - - + + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + - + - - + + - + - + - + - + - + - - + + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -922,878 +2071,849 @@ - - - - + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - + - - + + - - - + + + - + + + + + + + + + + + + + + + + + - + - + - - + + - + - + - + - + - + - - + + - + - + - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - + + + + + + + + + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - - + + - + - + - + - + - + - - + + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + - + - - + + - + - + - + - + - + - - + + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + - + - - + + - + - + - + - + - + - - + + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + @@ -1807,8 +2927,8 @@ - - + + @@ -1822,8 +2942,8 @@ - - + + @@ -1837,8 +2957,8 @@ - - + + @@ -1852,8 +2972,8 @@ - - + + @@ -1867,11 +2987,41 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1882,169 +3032,251 @@ - - + + - - + + - - - + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - - + + - + - + - + - + - + - - + + - + - + - - - - - + + + + + - - + + - - - + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - - + + - + - + - + - + - + - - + + - + - + - - - - - + + + + + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - - - - - + + + + + + + + + + + + + - + - - - - - + + + + + + + + + + + + + + + + - diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/robot_no_collision.urdf b/src/reachy_mini/descriptions/reachy_mini/urdf/robot_no_collision.urdf index 94feb46a..4317f7e5 100644 --- a/src/reachy_mini/descriptions/reachy_mini/urdf/robot_no_collision.urdf +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/robot_no_collision.urdf @@ -1,65 +1,135 @@ - + - - + + - - - + + + - + - + - + - - + + - + - + - + - + - - - + + + + + + + + + + + + + - - - + + + - + + + + + + + + + + + + + + + + + + + + + - + - + - + - + + + + + + + + + + + - + - + - + - + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + @@ -67,9 +137,29 @@ - + + + + + + + + + + + + + + + + + + + + + - + @@ -77,59 +167,69 @@ - + - + - + - + - + - + - + - + - + - + - + - - + + - + - + - + - + - + - + - + - + + + + + + + + + + + - + - + @@ -137,19 +237,39 @@ - + - + - + - - + + + + + + + + + + + + + + + + + + + + + + - + - + @@ -157,39 +277,69 @@ - + - + - + - - + + - + - + - + - + - + - + - + - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -197,19 +347,109 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - - + + + + + + + + + + + + - + - + @@ -217,9 +457,39 @@ - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -227,79 +497,109 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - + + + + + + + + + + + + - + - + - + - + - + - + - - + + - + - + + + + + + + + + + + + + + + + + + + + + @@ -307,49 +607,79 @@ - + - + - + - + - + - + - + - + - + - + - + - - + + - + - + - + - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -357,19 +687,29 @@ - + - + - + - - + + - + - + + + + + + + + + + + @@ -377,45 +717,205 @@ - + - + - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + - + - + - + - - + + - + - + - + - - + + + + + + + + + + + + - + @@ -432,44 +932,24 @@ - - + + - - - + + + - + - + - + - - + + - - - - - - - - - - - - - - - - - - - - - + @@ -480,71 +960,81 @@ - - + + - + - + - + - + - + - - + + - - - - - + + + + + - + - - + + - - - + + + - + - + - + - - + + - + - + - + - - + + + + + + + + + + + + - + @@ -561,46 +1051,26 @@ - - + + - - - + + + - + - + - + - - + + - - - - - - - - - - - - - - - - - - - - - - - + + + @@ -608,72 +1078,82 @@ - - - - + + + + - + - + - + - + - + - - + + - - - - - + + + + + - + - - + + - - - + + + - + - + - + - - + + - + - + - + - - + + - + + + + + + + + + + + @@ -690,46 +1170,26 @@ - - + + - - - + + + - - - - - - - - - - - - - - - - - - - - - + - + - + - - + + - - - + + + @@ -737,72 +1197,82 @@ - - - - + + + + - + - + - + - + - + - - + + - - - - - + + + + + - + - - + + - - - + + + - + - + - + - - + + - + - + - + - - + + + + + + + + + + + + - + @@ -819,44 +1289,24 @@ - - + + - - - + + + - + - + - + - - + + - - - - - - - - - - - - - - - - - - - - - + @@ -867,71 +1317,81 @@ - - + + - + - + - + - + - + - - + + - - - - - + + + + + - + - - + + - - - + + + - + - + - + - - + + - + - + - + - - + + - + + + + + + + + + + + @@ -944,48 +1404,28 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + - + - + - - + + - + @@ -996,71 +1436,81 @@ - - + + - + - + - + - + - + - - + + - - - - - + + + + + - + - - + + - - - + + + - + + + + + + + + + + + - + - + - - + + - + - + - + - - + + - + @@ -1077,44 +1527,24 @@ - - + + - - - + + + - + - + - + - - - - - - - - - - - - - - - - - - - - - - + + - + @@ -1131,184 +1561,444 @@ - - + + - - - + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - - + + - + - + - + - + + + + + + + + + + + - + - + - + - - + + - + - + - + - + - + - + - + - - + + - + - + - + - - + + - + - + - + - - + + - + - + - + - - + + - + - + - + - - + + - + - + - + - - + + - + - + - + - - + + - + - + - + - - + + - + - + - + - - + + - + - + - + - - + + - + - + - + - - + + - + - + - + - - + + - + - + - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -1319,13 +2009,13 @@ - - + + - - + + @@ -1333,14 +2023,14 @@ - - - - + + + + - - + + @@ -1348,10 +2038,10 @@ - - - - + + + + @@ -1364,8 +2054,8 @@ - - + + @@ -1379,13 +2069,13 @@ - - + + - - + + @@ -1393,10 +2083,10 @@ - - - - + + + + @@ -1409,13 +2099,13 @@ - - + + - - + + @@ -1423,150 +2113,189 @@ - - - - + + + + - - + + - - - + + + - + + + + + + + + + + + - + - + - + - + + + + + + + + + + + - + - + - + - - - - - - + + + + + + - + - - + + - - - + + + - + - + - + - + - + - + - + - + - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - - - - - + + + + + - + - - - - - - + + + + + - + - + @@ -1579,7 +2308,8 @@ - + + diff --git a/src/reachy_mini/descriptions/reachy_mini/urdf/robot_simple_collision.urdf b/src/reachy_mini/descriptions/reachy_mini/urdf/robot_simple_collision.urdf new file mode 100644 index 00000000..1326b3e7 --- /dev/null +++ b/src/reachy_mini/descriptions/reachy_mini/urdf/robot_simple_collision.urdf @@ -0,0 +1,2399 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/reachy_mini/io/__init__.py b/src/reachy_mini/io/__init__.py index c07b98b3..c5031f78 100644 --- a/src/reachy_mini/io/__init__.py +++ b/src/reachy_mini/io/__init__.py @@ -1,7 +1,9 @@ -"""Provide the Zenoh client and server as default implementation for the Reachy Mini project.""" +"""IO module.""" +from .audio_ws import AsyncWebSocketAudioStreamer +from .video_ws import AsyncWebSocketFrameSender +from .ws_controller import AsyncWebSocketController from .zenoh_client import ZenohClient from .zenoh_server import ZenohServer -Client = ZenohClient -Server = ZenohServer +__all__ = ["AsyncWebSocketAudioStreamer", "AsyncWebSocketFrameSender", "AsyncWebSocketController", "ZenohClient", "ZenohServer"] \ No newline at end of file diff --git a/src/reachy_mini/io/abstract.py b/src/reachy_mini/io/abstract.py index 4cb3a15e..24b16ab7 100644 --- a/src/reachy_mini/io/abstract.py +++ b/src/reachy_mini/io/abstract.py @@ -16,12 +16,12 @@ class AbstractServer(ABC): """Base class for server implementations.""" @abstractmethod - def start(self): + def start(self) -> None: """Start the server.""" pass @abstractmethod - def stop(self): + def stop(self) -> None: """Stop the server.""" pass @@ -35,7 +35,7 @@ class AbstractClient(ABC): """Base class for client implementations.""" @abstractmethod - def wait_for_connection(self): + def wait_for_connection(self) -> None: """Wait for the client to connect to the server.""" pass @@ -45,12 +45,12 @@ def is_connected(self) -> bool: pass @abstractmethod - def disconnect(self): + def disconnect(self) -> None: """Disconnect the client from the server.""" pass @abstractmethod - def send_command(self, command: str): + def send_command(self, command: str) -> None: """Send a command to the server.""" pass @@ -65,6 +65,6 @@ def send_task_request(self, task_req: AnyTaskRequest) -> UUID: pass @abstractmethod - def wait_for_task_completion(self, task_uid: UUID, timeout: float = 5.0): + def wait_for_task_completion(self, task_uid: UUID, timeout: float = 5.0) -> None: """Wait for the specified task to complete.""" pass diff --git a/src/reachy_mini/io/audio_ws.py b/src/reachy_mini/io/audio_ws.py new file mode 100644 index 00000000..85e6b80f --- /dev/null +++ b/src/reachy_mini/io/audio_ws.py @@ -0,0 +1,234 @@ +"""Async WebSocket Audio Streamer.""" +import asyncio +import logging +import threading +import time +from queue import Empty, Queue +from typing import Optional, Union + +import numpy as np +import numpy.typing as npt +from websockets.asyncio.client import ClientConnection, connect + +logger = logging.getLogger("reachy_mini.io.audio_ws") + + +class AsyncWebSocketAudioStreamer: + """Async WebSocket audio streamer with send and receive support.""" + + ws_uri: str + send_queue: "Queue[bytes]" + recv_queue: "Queue[bytes]" + loop: asyncio.AbstractEventLoop + thread: threading.Thread + connected: threading.Event + stop_flag: bool + keep_alive_interval: float + + # --- CONFIGURATION --- + # Target ~2048 samples per packet (approx 128ms) + # 2048 samples * 2 bytes (int16) = 4096 bytes + BATCH_SIZE_BYTES = 4096 + # Don't hold audio longer than 200ms even if buffer isn't full + BATCH_TIMEOUT = 0.2 + + def __init__(self, ws_uri: str, keep_alive_interval: float = 2.0) -> None: + """Initialize the WebSocket audio streamer. + + Args: + ws_uri: WebSocket URI to connect to. + keep_alive_interval: Interval in seconds to send keep-alive pings + when no audio is flowing. + + """ + self.ws_uri = ws_uri + self.send_queue = Queue() + self.recv_queue = Queue() + self.loop = asyncio.new_event_loop() + self.thread = threading.Thread(target=self._run_loop, daemon=True) + self.connected = threading.Event() + self.stop_flag = False + self.keep_alive_interval = keep_alive_interval + self.thread.start() + + def _run_loop(self) -> None: + """Run the WebSocket streamer loop in a background thread.""" + asyncio.set_event_loop(self.loop) + self.loop.run_until_complete(self._run()) + + async def _run(self) -> None: + """Run the main reconnect loop.""" + while not self.stop_flag: + try: + async with connect(self.ws_uri) as ws: + logger.info("[WS-AUDIO] Connected to Space") + self.connected.set() + + send_task = asyncio.create_task(self._send_loop(ws)) + recv_task = asyncio.create_task(self._recv_loop(ws)) + + done, pending = await asyncio.wait( + {send_task, recv_task}, + return_when=asyncio.FIRST_EXCEPTION, + ) + + # Cancel the other task if one fails or finishes + for task in pending: + task.cancel() + try: + await task + except Exception: + pass + + except Exception as e: + logger.info(f"[WS-AUDIO] Connection failed: {e}") + await asyncio.sleep(1.0) + + self.connected.clear() + + async def _send_loop(self, ws: ClientConnection) -> None: + """Send outgoing audio chunks and keep-alive pings. + + To avoid audible artifacts, this method aggregates small chunks into larger batches before sending. + """ + last_activity = time.time() + + # Buffer to hold small chunks + batch_buffer = bytearray() + # Track when we started filling this specific batch + batch_start_time = time.time() + + while not self.stop_flag: + try: + # 1. Try to pull data from the queue + # Use a short timeout so we can check time-based conditions frequently + chunk = self.send_queue.get(timeout=0.01) + + # If this is the first chunk in the buffer, reset timer + if len(batch_buffer) == 0: + batch_start_time = time.time() + + batch_buffer.extend(chunk) + + except Empty: + pass + except Exception as e: + logger.info(f"[WS-AUDIO] Queue error: {e}") + break + + # 2. Check if we should send the batch + now = time.time() + + # Condition A: Buffer is full enough (Size based) + is_full = len(batch_buffer) >= self.BATCH_SIZE_BYTES + + # Condition B: Buffer has data, but it's getting too old (Time based) + # This ensures that if the robot says a short word, it sends it + # after 100ms instead of waiting forever for more data. + is_timed_out = (len(batch_buffer) > 0) and ((now - batch_start_time) > self.BATCH_TIMEOUT) + if is_full or is_timed_out: + try: + # Send the aggregated buffer + await ws.send(batch_buffer) # type: ignore + + # Reset + batch_buffer = bytearray() + last_activity = now + except Exception as e: + logger.info(f"[WS-AUDIO] Send error: {e}") + break + + # 3. Keep-Alive Ping (Only if completely idle) + # We only ping if the buffer is empty AND we haven't sent anything recently + if len(batch_buffer) == 0 and (now - last_activity) > self.keep_alive_interval: + try: + await ws.send("ping") + last_activity = now + logger.debug("[WS-AUDIO] Sent keep-alive ping") + except Exception as e: + logger.info(f"[WS-AUDIO] Ping failed: {e}") + break + + # Tiny sleep to yield control if we are just spinning + if len(batch_buffer) == 0: + await asyncio.sleep(0.001) + + async def _recv_loop(self, ws: ClientConnection) -> None: + """Receive incoming audio chunks.""" + while not self.stop_flag: + try: + msg = await ws.recv() + except Exception as e: + logger.info(f"[WS-AUDIO] Receive error: {e}") + break + + if isinstance(msg, bytes): + try: + self.recv_queue.put_nowait(msg) + except Exception as e: + logger.debug(f"[WS-AUDIO] Failed to enqueue received audio: {e}") + else: + logger.debug(f"[WS-AUDIO] Received non-binary message: {msg}") + + # ------------------------ + # Public API + # ------------------------ + + def send_audio_chunk( + self, + audio: Union[bytes, npt.NDArray[np.int16], npt.NDArray[np.float32]], + ) -> None: + """Queue an audio chunk to be sent. + + Args: + audio: Either raw bytes or a numpy array of int16 or float32. + Float32 arrays are assumed to be in [-1, 1] and will + be converted to int16 PCM. + + """ + if self.stop_flag: + return + + if isinstance(audio, bytes): + data = audio + else: + # Convert only if needed + arr = np.asarray(audio) + # Handle stereo if accidentally passed (take channel 0) + if arr.ndim > 1: + arr = arr[:, 0] + + if arr.dtype == np.float32 or arr.dtype == np.float64: + # If any value is above 1 or below -1, scale the entire array so the max abs value is 1 or less + max_abs = np.max(np.abs(arr)) + if max_abs > 1.0: + arr = arr / max_abs + # Convert float audio [-1,1] to int16 PCM + arr = np.clip(arr, -1.0, 1.0) + arr = (arr * 32767.0).astype(np.int16) # type: ignore + elif arr.dtype != np.int16: + arr = arr.astype(np.int16) + + data = arr.tobytes() + + self.send_queue.put(data) + + def get_audio_chunk(self, timeout: Optional[float] = 0.01) -> Optional[npt.NDArray[np.float32]]: + """Retrieve a received audio chunk, if any.""" + try: + if timeout == 0: + audio_bytes = self.recv_queue.get_nowait() + else: + audio_bytes = self.recv_queue.get(timeout=timeout) + # bytes -> int16 -> float32 in [-1, 1] + int16_arr = np.frombuffer(audio_bytes, dtype=np.int16) + float_arr = (int16_arr.astype(np.float32) / 32767.0) + return float_arr + except Empty: + return None + + def close(self) -> None: + """Close the WebSocket audio streamer.""" + self.stop_flag = True + if self.loop.is_running(): + self.loop.call_soon_threadsafe(self.loop.stop) \ No newline at end of file diff --git a/src/reachy_mini/io/protocol.py b/src/reachy_mini/io/protocol.py index 34f422e6..b1ecfdd2 100644 --- a/src/reachy_mini/io/protocol.py +++ b/src/reachy_mini/io/protocol.py @@ -12,10 +12,10 @@ class GotoTaskRequest(BaseModel): """Class to represent a goto target task.""" head: list[float] | None # 4x4 flatten pose matrix - antennas: list[float] | None # [left_angle, right_angle] (in rads) + antennas: list[float] | None # [right_angle, left_angle] (in rads) duration: float method: InterpolationTechnique - body_yaw: float + body_yaw: float | None class PlayMoveTaskRequest(BaseModel): diff --git a/src/reachy_mini/io/video_ws.py b/src/reachy_mini/io/video_ws.py new file mode 100644 index 00000000..677832ef --- /dev/null +++ b/src/reachy_mini/io/video_ws.py @@ -0,0 +1,129 @@ +"""Async WebSocket Frame Sender.""" +import asyncio +import logging +import threading +from queue import Empty, Full, Queue +from typing import Optional + +import cv2 +import numpy as np +import numpy.typing as npt +from websockets.asyncio.client import ClientConnection, connect +from websockets.exceptions import ConnectionClosed + +logger = logging.getLogger("reachy_mini.io.video_ws") + +class AsyncWebSocketFrameSender: + """Async WebSocket frame sender.""" + + ws_uri: str + queue: "Queue[bytes]" + loop: asyncio.AbstractEventLoop + thread: threading.Thread + connected: threading.Event + stop_flag: bool + _last_frame: Optional[npt.NDArray[np.uint8]] + + def __init__(self, ws_uri: str) -> None: + """Initialize the WebSocket frame sender.""" + self.ws_uri = ws_uri + self.queue = Queue(maxsize=2) + self.loop = asyncio.new_event_loop() + self.thread = threading.Thread(target=self._run_loop, daemon=True) + self.connected = threading.Event() + self.stop_flag = False + self._last_frame = None + self.thread.start() + + def _run_loop(self) -> None: + asyncio.set_event_loop(self.loop) + self.loop.run_until_complete(self._run()) + + def _clear_queue(self) -> None: + """Empty the queue so we don't send 10 seconds of old video on reconnect.""" + while not self.queue.empty(): + try: + self.queue.get_nowait() + except Empty: + break + + async def _run(self) -> None: + """Run the WebSocket frame sender loop.""" + while not self.stop_flag: + try: + ws: ClientConnection + async with connect( + self.ws_uri, + ping_interval=5, # Every 5 seconds is plenty + ping_timeout=10, # Give it 10s to respond + close_timeout=1, # Don't wait long for polite closes + ) as ws: + logger.info("[WS Video] Connected to Space") + self.connected.set() + self._clear_queue() # Ensure we start fresh + + while not self.stop_flag: + try: + frame = self.queue.get_nowait() + + await ws.send(frame) + + except Empty: + # Queue is empty, just yield to event loop + await asyncio.sleep(0.05) + continue + + except Exception as e: + logger.error(f"[WS Video] Send error: {e}") + break + + except (ConnectionClosed) as e: + # Common network errors, retry quickly + logger.error(f"[WS Video] Connection lost ({type(e).__name__}). Retrying...") + self.connected.clear() + self._last_frame = None + await asyncio.sleep(0.5) # Wait briefly before reconnecting + + except Exception as e: + logger.error(f"[WS Video] Unexpected error: {e}") + await asyncio.sleep(1) + + self.connected.clear() + self._last_frame = None + + def send_frame(self, frame: npt.NDArray[np.uint8]) -> None: + """Send a frame to the WebSocket (Non-blocking).""" + if not self.stop_flag: + # 1. Frame Deduplication + if self._last_frame is not None: + if np.array_equal(frame, self._last_frame): + return + self._last_frame = frame.copy() + + # 2. Encode + ok, jpeg_bytes = cv2.imencode( + ".jpg", + frame, + [int(cv2.IMWRITE_JPEG_QUALITY), 80], + ) + if not ok: + return + + data = jpeg_bytes.tobytes() + + # If queue is full (network lagging), remove the old frame and put the new one. + try: + self.queue.put_nowait(data) + except Full: + # Queue is full, network is likely slower than camera. + # Drop the OLDEST frame to make room for the NEWEST. + try: + self.queue.get_nowait() # Pop old frame + self.queue.put_nowait(data) # Push new frame + except Empty: + logger.error("[WS Video] Queue is full and empty, this should not happen.") + + def close(self) -> None: + """Close the WebSocket frame sender.""" + self.stop_flag = True + self.loop.call_soon_threadsafe(self.loop.stop) \ No newline at end of file diff --git a/src/reachy_mini/io/ws_controller.py b/src/reachy_mini/io/ws_controller.py new file mode 100644 index 00000000..372bc2e3 --- /dev/null +++ b/src/reachy_mini/io/ws_controller.py @@ -0,0 +1,118 @@ +"""Async WebSocket Controller for remote control and streaming of the robot.""" +import asyncio +import json +import logging +import threading +from dataclasses import dataclass +from typing import Any, Dict, Optional + +import numpy as np +from websockets.asyncio.client import ClientConnection, connect + +from reachy_mini.daemon.backend.abstract import Backend + +logger = logging.getLogger("reachy_mini.ws_controller") + +@dataclass +class Movement: + """Movement data for the WebSocket controller.""" + + name: str + x: float = 0 + y: float = 0 + z: float = 0 + roll: float = 0 + pitch: float = 0 + yaw: float = 0 + body_yaw: float = 0 + left_antenna: Optional[float] = None + right_antenna: Optional[float] = None + duration: float = 1.0 + + +class AsyncWebSocketController: + """WebSocket controller for remote control and streaming of the robot.""" + + ws_uri: str + backend: Backend + loop: asyncio.AbstractEventLoop + thread: threading.Thread + stop_flag: bool + + def __init__(self, ws_uri: str, backend: Backend) -> None: + """Initialize the WebSocket controller.""" + self.ws_uri = ws_uri + self.backend = backend + self.loop = asyncio.new_event_loop() + self.thread = threading.Thread(target=self._run_loop, daemon=True) + self.stop_flag = False + self.thread.start() + + def _run_loop(self) -> None: + """Run the WebSocket controller loop.""" + asyncio.set_event_loop(self.loop) + self.loop.run_until_complete(self._run()) + + async def on_command(self, cmd: Dict[str, Any]) -> None: + """Handle a command from the WebSocket.""" + typ = cmd.get("type") + + if typ == "movement": + logger.debug("[Daemon] Movement command received") + mov = cmd.get("movement", {}) + logger.debug("[Daemon] Movement command: %s", mov) + + head = mov.get("head") + if head is not None: + head_arr = np.array(head, dtype=float).reshape(4, 4) + else: + head_arr = None + + antennas = mov.get("antennas") + if antennas is not None: + antennas_arr = np.array(antennas, dtype=float) + else: + antennas_arr = None + + try: + await self.backend.goto_target( + head=head_arr, + antennas=antennas_arr, + duration=mov.get("duration", 1.0), + body_yaw=mov.get("body_yaw", 0.0), + ) + except Exception as e: + logger.debug("[Daemon] Error in goto_target: %s", e) + elif typ == "ping": + logger.debug("[Daemon] Ping received") + return + else: + logger.debug("[Daemon] Unknown command type: %s", typ) + + async def _run(self) -> None: + """Run the WebSocket controller loop.""" + while not self.stop_flag: + try: + ws: ClientConnection + async with connect(self.ws_uri, ping_interval=5, ping_timeout=10) as ws: + logger.info("[WS] Connected to Space") + async for msg in ws: + try: + data = json.loads(msg) + except Exception as e: + logger.debug("[WS] Bad JSON: %s raw: %s", e, msg) + continue + + # Now this is awaited inside the same loop + await self.on_command(data) + + except Exception as e: + logger.info("[WS] Connection failed: %s", e) + # small backoff before reconnect + await asyncio.sleep(1) + + def stop(self) -> None: + """Stop the WebSocket controller.""" + self.stop_flag = True + if self.loop.is_running(): + self.loop.call_soon_threadsafe(lambda: None) diff --git a/src/reachy_mini/io/zenoh_client.py b/src/reachy_mini/io/zenoh_client.py index f40d2e05..6ae2b74f 100644 --- a/src/reachy_mini/io/zenoh_client.py +++ b/src/reachy_mini/io/zenoh_client.py @@ -9,9 +9,11 @@ import time from dataclasses import dataclass from datetime import datetime +from typing import Any, Dict, List, Optional from uuid import UUID, uuid4 import numpy as np +import numpy.typing as npt import zenoh from reachy_mini.io.abstract import AbstractClient @@ -22,7 +24,12 @@ class ZenohClient(AbstractClient): """Zenoh client for Reachy Mini.""" def __init__(self, localhost_only: bool = True): - """Initialize the Zenoh client.""" + """Initialize the Zenoh client. + + Args: + localhost_only: If True, connect to localhost only + + """ if localhost_only: c = zenoh.Config.from_json5( json.dumps( @@ -41,6 +48,7 @@ def __init__(self, localhost_only: bool = True): self.joint_position_received = threading.Event() self.head_pose_received = threading.Event() + self.status_received = threading.Event() self.session = zenoh.open(c) self.cmd_pub = self.session.declare_publisher("reachy_mini/command") @@ -59,12 +67,21 @@ def __init__(self, localhost_only: bool = True): "reachy_mini/recorded_data", self._handle_recorded_data, ) + + self.status_sub = self.session.declare_subscriber( + "reachy_mini/daemon_status", + self._handle_status, + ) + self._last_head_joint_positions = None self._last_antennas_joint_positions = None - self._last_head_pose = None - self._recorded_data = None + self._last_head_pose: Optional[npt.NDArray[np.float64]] = None + self._recorded_data: Optional[ + List[Dict[str, float | List[float] | List[List[float]]]] + ] = None self._recorded_data_ready = threading.Event() self._is_alive = False + self._last_status: Dict[str, Any] = {} # contains a DaemonStatus self.tasks: dict[UUID, TaskState] = {} self.task_request_pub = self.session.declare_publisher("reachy_mini/task") @@ -73,7 +90,7 @@ def __init__(self, localhost_only: bool = True): self._handle_task_progress, ) - def wait_for_connection(self, timeout: float = 5.0): + def wait_for_connection(self, timeout: float = 5.0) -> None: """Wait for the client to connect to the server. Args: @@ -95,12 +112,14 @@ def wait_for_connection(self, timeout: float = 5.0): print("Waiting for connection with the server...") self._is_alive = True + self._check_alive_evt = threading.Event() threading.Thread(target=self.check_alive, daemon=True).start() def check_alive(self) -> None: """Periodically check if the client is still connected to the server.""" while True: self._is_alive = self.is_connected() + self._check_alive_evt.set() time.sleep(1.0) def is_connected(self) -> bool: @@ -111,18 +130,18 @@ def is_connected(self) -> bool: timeout=1.0 ) and self.head_pose_received.wait(timeout=1.0) - def disconnect(self): + def disconnect(self) -> None: """Disconnect the client from the server.""" - self.session.close() + self.session.close() # type: ignore[no-untyped-call] - def send_command(self, command: str): + def send_command(self, command: str) -> None: """Send a command to the server.""" if not self._is_alive: raise ConnectionError("Lost connection with the server.") self.cmd_pub.put(command.encode("utf-8")) - def _handle_joint_positions(self, sample): + def _handle_joint_positions(self, sample: zenoh.Sample) -> None: """Handle incoming joint positions.""" if sample.payload: positions = json.loads(sample.payload.to_string()) @@ -132,14 +151,22 @@ def _handle_joint_positions(self, sample): ) self.joint_position_received.set() - def _handle_recorded_data(self, sample): + def _handle_recorded_data(self, sample: zenoh.Sample) -> None: """Handle incoming recorded data.""" print("Received recorded data.") if sample.payload: data = json.loads(sample.payload.to_string()) self._recorded_data = data self._recorded_data_ready.set() - print(f"Recorded data: {len(self._recorded_data)} frames received.") + if self._recorded_data is not None: + print(f"Recorded data: {len(self._recorded_data)} frames received.") + + def _handle_status(self, sample: zenoh.Sample) -> None: + """Handle incoming status updates.""" + if sample.payload: + status = json.loads(sample.payload.to_string()) + self._last_status = status + self.status_received.set() def get_current_joints(self) -> tuple[list[float], list[float]]: """Get the current joint positions.""" @@ -156,7 +183,9 @@ def wait_for_recorded_data(self, timeout: float = 5.0) -> bool: """Block until the daemon publishes the frames (or timeout).""" return self._recorded_data_ready.wait(timeout) - def get_recorded_data(self, wait: bool = True, timeout: float = 5.0) -> list[dict]: + def get_recorded_data( + self, wait: bool = True, timeout: float = 5.0 + ) -> Optional[List[Dict[str, float | List[float] | List[List[float]]]]]: """Return the cached recording, optionally blocking until it arrives. Raises `TimeoutError` if nothing shows up in time. @@ -164,16 +193,25 @@ def get_recorded_data(self, wait: bool = True, timeout: float = 5.0) -> list[dic if wait and not self._recorded_data_ready.wait(timeout): raise TimeoutError("Recording not received in time.") self._recorded_data_ready.clear() # ready for next run - return self._recorded_data.copy() - - def _handle_head_pose(self, sample): + if self._recorded_data is not None: + return self._recorded_data.copy() + return None + + def get_status(self, wait: bool = True, timeout: float = 5.0) -> Dict[str, Any]: + """Get the last received status. Returns DaemonStatus as a dict.""" + if wait and not self.status_received.wait(timeout): + raise TimeoutError("Status not received in time.") + self.status_received.clear() # ready for next run + return self._last_status + + def _handle_head_pose(self, sample: zenoh.Sample) -> None: """Handle incoming head pose.""" if sample.payload: pose = json.loads(sample.payload.to_string()) self._last_head_pose = np.array(pose.get("head_pose")).reshape(4, 4) self.head_pose_received.set() - def get_current_head_pose(self) -> np.ndarray: + def get_current_head_pose(self) -> npt.NDArray[np.float64]: """Get the current head pose.""" assert self._last_head_pose is not None, "No head pose received yet." return self._last_head_pose.copy() @@ -191,7 +229,7 @@ def send_task_request(self, task_req: AnyTaskRequest) -> UUID: return task.uuid - def wait_for_task_completion(self, task_uid: UUID, timeout: float = 5.0): + def wait_for_task_completion(self, task_uid: UUID, timeout: float = 5.0) -> None: """Wait for the specified task to complete.""" if task_uid not in self.tasks: raise ValueError("Task not found.") @@ -205,7 +243,7 @@ def wait_for_task_completion(self, task_uid: UUID, timeout: float = 5.0): del self.tasks[task_uid] - def _handle_task_progress(self, sample): + def _handle_task_progress(self, sample: zenoh.Sample) -> None: if sample.payload: progress = TaskProgress.model_validate_json(sample.payload.to_string()) assert progress.uuid in self.tasks, "Unknown task UUID." diff --git a/src/reachy_mini/io/zenoh_server.py b/src/reachy_mini/io/zenoh_server.py index 59534539..42a96a3e 100644 --- a/src/reachy_mini/io/zenoh_server.py +++ b/src/reachy_mini/io/zenoh_server.py @@ -28,7 +28,7 @@ class ZenohServer(AbstractServer): """Zenoh server for Reachy Mini.""" - def __init__(self, backend: Backend, localhost_only: bool = True): # type: ignore + def __init__(self, backend: Backend, localhost_only: bool = True): """Initialize the Zenoh server.""" self.localhost_only = localhost_only self.backend = backend @@ -36,7 +36,7 @@ def __init__(self, backend: Backend, localhost_only: bool = True): # type: igno self._lock = threading.Lock() self._cmd_event = threading.Event() - def start(self): + def start(self) -> None: """Start the Zenoh server.""" if self.localhost_only: c = zenoh.Config.from_json5( @@ -85,35 +85,43 @@ def start(self): "reachy_mini/task_progress" ) - def stop(self): + self.pub_status = self.session.declare_publisher("reachy_mini/daemon_status") + + def stop(self) -> None: """Stop the Zenoh server.""" - self.session.close() + self.session.close() # type: ignore[no-untyped-call] def command_received_event(self) -> threading.Event: """Wait for a new command and return it.""" return self._cmd_event - def _handle_command(self, sample: zenoh.Sample): + def _handle_command(self, sample: zenoh.Sample) -> None: data = sample.payload.to_string() command = json.loads(data) with self._lock: if "torque" in command: - if command["torque"]: - self.backend.set_motor_control_mode(MotorControlMode.Enabled) + if ( + command["ids"] is not None + ): # If specific motor IDs are provided, just set torque for those motors + self.backend.set_motor_torque_ids(command["ids"], command["torque"]) else: - self.backend.set_motor_control_mode(MotorControlMode.Disabled) + if command["torque"]: + self.backend.set_motor_control_mode(MotorControlMode.Enabled) + else: + self.backend.set_motor_control_mode(MotorControlMode.Disabled) if "head_joint_positions" in command: self.backend.set_target_head_joint_positions( - command["head_joint_positions"] + np.array(command["head_joint_positions"]) ) if "head_pose" in command: self.backend.set_target_head_pose( - np.array(command["head_pose"]).reshape(4, 4), - command["body_yaw"], + np.array(command["head_pose"]).reshape(4, 4) ) + if "body_yaw" in command: + self.backend.set_target_body_yaw(command["body_yaw"]) if "antennas_joint_positions" in command: self.backend.set_target_antenna_joint_positions( - command["antennas_joint_positions"], + np.array(command["antennas_joint_positions"]), ) if "gravity_compensation" in command: try: @@ -138,17 +146,17 @@ def _handle_command(self, sample: zenoh.Sample): self.backend.stop_recording() self._cmd_event.set() - def _handle_task_request(self, sample: zenoh.Sample): + def _handle_task_request(self, sample: zenoh.Sample) -> None: task_req = TaskRequest.model_validate_json(sample.payload.to_string()) if isinstance(task_req.req, GotoTaskRequest): req = task_req.req - def task(): + def task() -> None: asyncio.run( self.backend.goto_target( head=np.array(req.head).reshape(4, 4) if req.head else None, - antennas=req.antennas, + antennas=np.array(req.antennas) if req.antennas else None, duration=req.duration, method=req.method, body_yaw=req.body_yaw, @@ -156,13 +164,13 @@ def task(): ) elif isinstance(task_req.req, PlayMoveTaskRequest): - def task(): + def task() -> None: print("PLAY MOVE") else: assert False, f"Unknown task request type {task_req.req.__class__.__name__}" - def wrapped_task(): + def wrapped_task() -> None: error = None try: task() diff --git a/src/reachy_mini/kinematics/__init__.py b/src/reachy_mini/kinematics/__init__.py index dcb385c9..176bfa7f 100644 --- a/src/reachy_mini/kinematics/__init__.py +++ b/src/reachy_mini/kinematics/__init__.py @@ -1,5 +1,10 @@ """Try to import kinematics engines, and provide mockup classes if they are not available.""" +from typing import Annotated + +import numpy as np +import numpy.typing as npt + try: from reachy_mini.kinematics.nn_kinematics import NNKinematics # noqa: F401 except ImportError: @@ -7,13 +12,25 @@ class MockupNNKinematics: """Mockup class for NNKinematics.""" - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: # type: ignore[no-untyped-def] """Raise ImportError when trying to instantiate the class.""" raise ImportError( "NNKinematics could not be imported. Make sure you run pip install reachy_mini[nn_kinematics]." ) - NNKinematics = MockupNNKinematics + def ik(self, *args, **kwargs) -> Annotated[npt.NDArray[np.float64], (7,)]: # type: ignore[no-untyped-def] + """Mockup method for ik.""" + raise ImportError( + "NNKinematics could not be imported. Make sure you run pip install reachy_mini[nn_kinematics]." + ) + + def fk(self, *args, **kwargs) -> Annotated[npt.NDArray[np.float64], (4, 4)]: # type: ignore[no-untyped-def] + """Mockup method for fk.""" + raise ImportError( + "NNKinematics could not be imported. Make sure you run pip install reachy_mini[nn_kinematics]." + ) + + NNKinematics = MockupNNKinematics # type: ignore[assignment, misc] try: from reachy_mini.kinematics.placo_kinematics import PlacoKinematics # noqa: F401 @@ -22,15 +39,30 @@ def __init__(self, *args, **kwargs): class MockupPlacoKinematics: """Mockup class for PlacoKinematics.""" - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: # type: ignore[no-untyped-def] """Raise ImportError when trying to instantiate the class.""" raise ImportError( "PlacoKinematics could not be imported. Make sure you run pip install reachy_mini[placo_kinematics]." ) - PlacoKinematics = MockupPlacoKinematics + def ik(self, *args, **kwargs) -> Annotated[npt.NDArray[np.float64], (7,)]: # type: ignore[no-untyped-def] + """Mockup method for ik.""" + raise ImportError( + "PlacoKinematics could not be imported. Make sure you run pip install reachy_mini[placo_kinematics]." + ) + + def fk(self, *args, **kwargs) -> Annotated[npt.NDArray[np.float64], (4, 4)]: # type: ignore[no-untyped-def] + """Mockup method for fk.""" + raise ImportError( + "PlacoKinematics could not be imported. Make sure you run pip install reachy_mini[placo_kinematics]." + ) + + PlacoKinematics = MockupPlacoKinematics # type: ignore[assignment, misc] from reachy_mini.kinematics.analytical_kinematics import ( # noqa: F401 AnalyticalKinematics, ) + +AnyKinematics = NNKinematics | PlacoKinematics | AnalyticalKinematics +__all__ = ["NNKinematics", "PlacoKinematics", "AnalyticalKinematics"] diff --git a/src/reachy_mini/kinematics/analytical_kinematics.py b/src/reachy_mini/kinematics/analytical_kinematics.py index de014eb7..ca7674e8 100644 --- a/src/reachy_mini/kinematics/analytical_kinematics.py +++ b/src/reachy_mini/kinematics/analytical_kinematics.py @@ -5,10 +5,14 @@ """ import json +import logging from importlib.resources import files +from typing import Annotated -import numpy as np # noqa: D100 +import numpy as np +from numpy.typing import NDArray from reachy_mini_rust_kinematics import ReachyMiniRustKinematics +from scipy.spatial.transform import Rotation as R import reachy_mini @@ -26,11 +30,16 @@ class AnalyticalKinematics: """Reachy Mini Analytical Kinematics class, implemented in Rust with python bindings.""" - def __init__(self): + def __init__( + self, + automatic_body_yaw: bool = True + ) -> None: """Initialize.""" assets_root_path: str = str(files(reachy_mini).joinpath("assets/")) data_path = assets_root_path + "/kinematics_data.json" data = json.load(open(data_path, "rb")) + + self.automatic_body_yaw = automatic_body_yaw self.head_z_offset = data["head_z_offset"] @@ -38,50 +47,106 @@ def __init__(self): data["motor_arm_length"], data["rod_length"] ) + self.start_body_yaw = 0.0 + self.motors = data["motors"] for motor in self.motors: self.kin.add_branch( - np.array(motor["branch_position"]), - np.linalg.inv(motor["T_motor_world"]), + motor["branch_position"], + np.linalg.inv(motor["T_motor_world"]), # type: ignore[arg-type] 1 if motor["solution"] else -1, ) + # TODO test with init head pose instead of sleep pose sleep_head_pose = SLEEP_HEAD_POSE.copy() sleep_head_pose[:3, 3][2] += self.head_z_offset - self.kin.reset_forward_kinematics(sleep_head_pose) + self.kin.reset_forward_kinematics(sleep_head_pose) # type: ignore[arg-type] + + self.logger = logging.getLogger(__name__) + # self.logger.setLevel(logging.WARNING) def ik( self, - pose: np.ndarray, + pose: Annotated[NDArray[np.float64], (4, 4)], body_yaw: float = 0.0, check_collision: bool = False, no_iterations: int = 0, - ): + ) -> Annotated[NDArray[np.float64], (7,)]: """Compute the inverse kinematics for a given head pose. check_collision and no_iterations are not used by AnalyticalKinematics. We keep them for compatibility with the other kinematics engines """ _pose = pose.copy() _pose[:3, 3][2] += self.head_z_offset - return [body_yaw] + list(self.kin.inverse_kinematics(_pose)) + + reachy_joints = [] + if self.automatic_body_yaw: + # inverse kinematics solution that modulates the body yaw to + # stay within the mechanical limits (max_body_yaw) + # additionally it makes sure the the relative yaw between the body and the head + # stays within the mechanical limits (max_relative_yaw) + reachy_joints = self.kin.inverse_kinematics_safe(_pose, # type: ignore[arg-type] + body_yaw = body_yaw, + max_relative_yaw = np.deg2rad(65), + max_body_yaw = np.deg2rad(160)) + else: + # direct inverse kinematics solution with given body yaw + # it does not modify the body yaw + stewart_joints = self.kin.inverse_kinematics(_pose, body_yaw) # type: ignore[arg-type] + reachy_joints = [body_yaw] + stewart_joints + + return np.array(reachy_joints) def fk( self, - joint_angles: list, + joint_angles: Annotated[NDArray[np.float64], (7,)], check_collision: bool = False, no_iterations: int = 3, - ): + ) -> Annotated[NDArray[np.float64], (4, 4)]: """Compute the forward kinematics for a given set of joint angles. check_collision is not used by AnalyticalKinematics. - For now, ignores the body yaw (first joint angle). """ - _joint_angles = joint_angles[1:] - - for _ in range(no_iterations): - T_world_platform = np.array( - self.kin.forward_kinematics(np.double(_joint_angles)) + body_yaw = joint_angles[0] + + _joint_angles = joint_angles[1:].tolist() + + if no_iterations < 1: + raise ValueError("no_iterations must be at least 1") + + T_world_platform = None + ok = False + while not ok: + for _ in range(no_iterations): + T_world_platform = np.array( + self.kin.forward_kinematics(_joint_angles, body_yaw) + ) + assert T_world_platform is not None + euler = R.from_matrix(T_world_platform[:3, :3]).as_euler( + "xyz", degrees=True ) + # check that head is upright. Recompute with epsilon adjustments if not + if not (euler[0] > 90 or euler[0] < -90 or euler[1] > 90 or euler[1] < -90): + ok = True + else: + self.logger.warning("Head is not upright, recomputing FK") + body_yaw += 0.001 + _joint_angles = list(np.array(_joint_angles) + 0.001) + tmp = np.eye(4) + tmp[:3, 3][2] += self.head_z_offset + self.kin.reset_forward_kinematics(tmp) # type: ignore[arg-type] + + assert T_world_platform is not None T_world_platform[:3, 3][2] -= self.head_z_offset + return T_world_platform + + def set_automatic_body_yaw(self, automatic_body_yaw: bool) -> None: + """Set the automatic body yaw. + + Args: + automatic_body_yaw (bool): Whether to enable automatic body yaw. + + """ + self.automatic_body_yaw = automatic_body_yaw \ No newline at end of file diff --git a/src/reachy_mini/kinematics/nn_kinematics.py b/src/reachy_mini/kinematics/nn_kinematics.py index cd1f9f24..3bab1912 100644 --- a/src/reachy_mini/kinematics/nn_kinematics.py +++ b/src/reachy_mini/kinematics/nn_kinematics.py @@ -1,9 +1,10 @@ """Neural Network based FK/IK.""" import time -from typing import List +from typing import Annotated import numpy as np +import numpy.typing as npt import onnxruntime from scipy.spatial.transform import Rotation as R @@ -18,15 +19,15 @@ def __init__(self, models_root_path: str): self.fk_infer = OnnxInfer(self.fk_model_path) self.ik_infer = OnnxInfer(self.ik_model_path) - self.start_body_yaw = 0.0 # No used, kept for compatibility + self.automatic_body_yaw = False # No used, kept for canaompatibility def ik( self, - pose: np.ndarray, + pose: Annotated[npt.NDArray[np.float64], (4, 4)], body_yaw: float = 0.0, check_collision: bool = False, no_iterations: int = 0, - ): + ) -> Annotated[npt.NDArray[np.float64], (7,)]: """check_collision and no_iterations are not used by NNKinematics. We keep them for compatibility with the other kinematics engines @@ -35,7 +36,7 @@ def ik( roll, pitch, yaw = R.from_matrix(pose[:3, :3]).as_euler("xyz") yaw += body_yaw - input = [x, y, z, roll, pitch, yaw] + input = np.array([x, y, z, roll, pitch, yaw]) joints = self.ik_infer.infer(input) joints[0] += body_yaw @@ -44,10 +45,10 @@ def ik( def fk( self, - joint_angles: List[float], + joint_angles: Annotated[npt.NDArray[np.float64], (7,)], check_collision: bool = False, no_iterations: int = 0, - ): + ) -> Annotated[npt.NDArray[np.float64], (4, 4)]: """check_collision and no_iterations are not used by NNKinematics. We keep them for compatibility with the other kinematics engines @@ -59,20 +60,32 @@ def fk( return pose + def set_automatic_body_yaw(self, automatic_body_yaw: bool) -> None: + """Set the automatic body yaw. + + Args: + automatic_body_yaw (bool): Whether to enable automatic body yaw. + + """ + self.automatic_body_yaw = automatic_body_yaw + class OnnxInfer: """Infer an onnx model.""" - def __init__(self, onnx_model_path): + def __init__(self, onnx_model_path: str) -> None: """Initialize.""" self.onnx_model_path = onnx_model_path self.ort_session = onnxruntime.InferenceSession( self.onnx_model_path, providers=["CPUExecutionProvider"] ) - def infer(self, input): + def infer(self, input: npt.NDArray[np.float64]) -> npt.NDArray[np.float64]: """Run inference on the input.""" outputs = self.ort_session.run(None, {"input": [input]}) - return outputs[0][0] + res: npt.NDArray[np.float64] = outputs[0][0] + return res + + if __name__ == "__main__": @@ -80,11 +93,11 @@ def infer(self, input): "assets/models", ) - times_fk = [] - times_ik = [] + times_fk: list[float] = [] + times_ik: list[float] = [] for i in range(1000): - fk_input = np.random.random(7).astype(np.float32) - # ik_input = np.random.random(6).astype(np.float32) + fk_input = np.random.random(7).astype(np.float64) + # ik_input = np.random.random(6).astype(np.float64) fk_s = time.time() fk_output = nn_kin.fk(fk_input) diff --git a/src/reachy_mini/kinematics/placo_kinematics.py b/src/reachy_mini/kinematics/placo_kinematics.py index b13bdb79..e842fbd4 100644 --- a/src/reachy_mini/kinematics/placo_kinematics.py +++ b/src/reachy_mini/kinematics/placo_kinematics.py @@ -4,11 +4,13 @@ """ import logging -from typing import List, Optional +from typing import Annotated, List, Optional import numpy as np +import numpy.typing as npt import pinocchio as pin import placo +from scipy.spatial.transform import Rotation as R class PlacoKinematics: @@ -22,7 +24,7 @@ def __init__( urdf_path: str, dt: float = 0.02, automatic_body_yaw: bool = False, - check_collision=False, + check_collision: bool = False, log_level: str = "INFO", ) -> None: """Initialize the PlacoKinematics class. @@ -40,14 +42,18 @@ def __init__( ) # 0.1 degrees tolerance for the FK reached condition if not urdf_path.endswith(".urdf"): - urdf_path = f"{urdf_path}/{'robot.urdf' if check_collision else 'robot_no_collision.urdf'}" + urdf_path = f"{urdf_path}/{'robot_simple_collision.urdf' if check_collision else 'robot_no_collision.urdf'}" self.robot = placo.RobotWrapper( - urdf_path, placo.Flags.ignore_collisions + placo.Flags.collision_as_visual + urdf_path, placo.Flags.collision_as_visual + placo.Flags.ignore_collisions ) - self.robot_ik = placo.RobotWrapper( - urdf_path, placo.Flags.ignore_collisions + placo.Flags.collision_as_visual + + flags = ( + 0 + if check_collision + else placo.Flags.ignore_collisions + placo.Flags.collision_as_visual ) + self.robot_ik = placo.RobotWrapper(urdf_path, flags) self.ik_solver = placo.KinematicsSolver(self.robot_ik) self.ik_solver.mask_fbase(True) @@ -88,7 +94,7 @@ def __init__( # This will allow independent control of the torso and the head yaw # until this constraint is reached yaw_constraint = self.ik_solver.add_yaw_constraint( - "dummy_torso_yaw", "head", np.deg2rad(65.0) + "dummy_torso_yaw", "head", np.deg2rad(55.0) ) yaw_constraint.configure("rel_yaw", "hard") @@ -97,25 +103,25 @@ def __init__( # Not really constraining because the this 180 pose is almost not # reachable with the real robot anyway yaw_constraint_abs = self.ik_solver.add_yaw_constraint( - "pp01071_turning_bowl", "head", np.deg2rad(179.0) + "body_foot_3dprint", "head", np.deg2rad(179.0) ) yaw_constraint_abs.configure("abs_yaw", "hard") # Add a cone constraint for the head to not exceed a certain angle # This is to avoid the head from looking too far up or down self.fk_cone = self.ik_solver.add_cone_constraint( - "pp01071_turning_bowl", "head", np.deg2rad(40.0) + "body_foot_3dprint", "head", np.deg2rad(35.0) ) self.fk_cone.configure("cone", "hard") self.fk_yaw_constraint = self.fk_solver.add_yaw_constraint( - "dummy_torso_yaw", "head", np.deg2rad(65.0) + "dummy_torso_yaw", "head", np.deg2rad(55.0) ) self.fk_yaw_constraint.configure("rel_yaw", "hard") # Add a cone constraint for the head to not exceed a certain angle # This is to avoid the head from looking too far up or down fk_cone = self.fk_solver.add_cone_constraint( - "pp01071_turning_bowl", "head", np.deg2rad(40.0) + "body_foot_3dprint", "head", np.deg2rad(35.0) ) fk_cone.configure("cone", "hard") @@ -140,12 +146,12 @@ def __init__( # regularization self.ik_yaw_joint_task = self.ik_solver.add_joints_task() - self.ik_yaw_joint_task.set_joints({"all_yaw": 0}) - if self.automatic_body_yaw: + self.ik_yaw_joint_task.set_joints({"yaw_body": 0}) + if not self.automatic_body_yaw: self.ik_yaw_joint_task.configure("joints", "soft", 5e-5) else: - self.ik_yaw_joint_task.configure("joints", "soft", 1.0) - + self.ik_yaw_joint_task.configure("joints", "soft", 3.0) + # joint limit tasks (values form URDF) self.ik_solver.enable_velocity_limits(True) self.ik_solver.enable_joint_limits(True) @@ -161,13 +167,13 @@ def __init__( # Actuated DoFs self.joints_names = [ - "all_yaw", - "1", - "2", - "3", - "4", - "5", - "6", + "yaw_body", + "stewart_1", + "stewart_2", + "stewart_3", + "stewart_4", + "stewart_5", + "stewart_6", ] # Passive DoFs to eliminate with constraint jacobian @@ -213,12 +219,12 @@ def __init__( # to enable faster convergence of the IK/FK solver max_vel = 13.0 # rad/s for joint_name in self.joints_names: - if joint_name != "all_yaw": + if joint_name != "yaw_body": self.robot.set_velocity_limit(joint_name, max_vel) self.robot_ik.set_velocity_limit(joint_name, max_vel) - self.robot.set_joint_limits("all_yaw", -2.8, 2.8) - self.robot_ik.set_joint_limits("all_yaw", -2.8, 2.8) + self.robot.set_joint_limits("yaw_body", -2.8, 2.8) + self.robot_ik.set_joint_limits("yaw_body", -2.8, 2.8) # initial state self._inital_q = self.robot.state.q.copy() @@ -239,6 +245,11 @@ def __init__( self.robot.update_kinematics() if self.check_collision: + ik_col = self.ik_solver.add_avoid_self_collisions_constraint() + ik_col.self_collisions_margin = 0.001 # 1mm + ik_col.self_collisions_trigger = 0.002 # 2mm + ik_col.configure("avoid_self_collisions", "hard") + # setup the collision model self.config_collision_model() @@ -256,7 +267,7 @@ def _update_state_to_initial(self, robot: placo.RobotWrapper) -> None: robot.state.qdd = self._inital_qdd def _pose_distance( - self, pose1: np.ndarray, pose2: np.ndarray + self, pose1: npt.NDArray[np.float64], pose2: npt.NDArray[np.float64] ) -> tuple[float, float]: """Compute the orientation distance between two poses. @@ -268,11 +279,11 @@ def _pose_distance( float: The Euler distance between the two poses. """ - euler1 = pin.rpy.matrixToRpy(pose1[:3, :3]) - euler2 = pin.rpy.matrixToRpy(pose2[:3, :3]) + euler1 = R.from_matrix(pose1[:3, :3]).as_euler("xyz") + euler2 = R.from_matrix(pose2[:3, :3]).as_euler("xyz") p1 = pose1[:3, 3] p2 = pose2[:3, 3] - return np.linalg.norm(euler1 - euler2), np.linalg.norm(p1 - p2) + return float(np.linalg.norm(euler1 - euler2)), float(np.linalg.norm(p1 - p2)) def _closed_loop_constraints_valid( self, robot: placo.RobotWrapper, tol: float = 1e-2 @@ -312,10 +323,10 @@ def _get_joint_values(self, robot: placo.RobotWrapper) -> List[float]: def ik( self, - pose: np.ndarray, + pose: npt.NDArray[np.float64], body_yaw: float = 0.0, no_iterations: int = 2, - ) -> Optional[List[float]]: + ) -> Annotated[npt.NDArray[np.float64], (7,)] | None: """Compute the inverse kinematics for the head for a given pose. Args: @@ -333,7 +344,7 @@ def ik( _pose[:3, 3][2] += self.head_z_offset # offset the height of the head self.head_frame.T_world_frame = _pose # update the body_yaw task - self.ik_yaw_joint_task.set_joints({"all_yaw": body_yaw}) + self.ik_yaw_joint_task.set_joints({"yaw_body": body_yaw}) # check the starting configuration # if the poses are too far start from the initial configuration @@ -341,7 +352,7 @@ def ik( _pose, self.robot_ik.get_T_world_frame("head") ) # if distance too small 0.1mm and 0.1 deg and the QP has converged (almost 0 velocity) - _dist_by = np.abs(body_yaw - self.robot_ik.get_joint("all_yaw")) + _dist_by = np.abs(body_yaw - self.robot_ik.get_joint("yaw_body")) if ( _dist_p < 0.1e-4 and _dist_o < np.deg2rad(0.01) @@ -349,7 +360,9 @@ def ik( and np.linalg.norm(self.robot_ik.state.qd) < 1e-4 ): # no need to recalculate - return the current joint values - return self._get_joint_values(self.robot_ik) # no need to solve IK + return np.array( + self._get_joint_values(self.robot_ik) + ) # no need to solve IK if _dist_o >= np.pi: # distance too big between the current and the target pose # start the optim from zero position @@ -394,23 +407,14 @@ def ik( return None self.robot_ik.update_kinematics() - # verify that there is no collision - if self.check_collision and self.compute_collision(): - self._logger.warning( - "IK: Collision detected, using the previous configuration..." - ) - self._update_state_to_initial(self.robot_ik) # revert to the inital state - self.robot_ik.update_kinematics() - return None - # Get the joint angles - return self._get_joint_values(self.robot_ik) + return np.array(self._get_joint_values(self.robot_ik)) def fk( self, - joints_angles: List[float], + joints_angles: Annotated[npt.NDArray[np.float64], (7,)], no_iterations: int = 2, - ) -> Optional[np.ndarray]: + ) -> Optional[npt.NDArray[np.float64]]: """Compute the forward kinematics for the head given joint angles. Args: @@ -431,7 +435,7 @@ def fk( and self.robot.state.qd.max() < 1e-4 ): # no need to compute FK - T_world_head = self.robot.get_T_world_frame("head") + T_world_head: npt.NDArray[np.float64] = self.robot.get_T_world_frame("head") T_world_head[:3, 3][2] -= ( self.head_z_offset ) # offset the height of the head @@ -440,19 +444,16 @@ def fk( # update the main task self.head_joints_task.set_joints( { - "all_yaw": joints_angles[0], - "1": joints_angles[1], - "2": joints_angles[2], - "3": joints_angles[3], - "4": joints_angles[4], - "5": joints_angles[5], - "6": joints_angles[6], + "yaw_body": joints_angles[0], + "stewart_1": joints_angles[1], + "stewart_2": joints_angles[2], + "stewart_3": joints_angles[3], + "stewart_4": joints_angles[4], + "stewart_5": joints_angles[5], + "stewart_6": joints_angles[6], } ) - # save the initial configuration - q = self.robot.state.q.copy() - done = True # do the inital ik with 2 iterations for i in range(no_iterations): @@ -484,41 +485,28 @@ def fk( return None self.robot.update_kinematics() - if self.check_collision and self.compute_collision(): - self._logger.warning("FK: Collision detected, using the previous config...") - self._update_state_to_initial(self.robot) # revert to the previous state - self.robot.state.q = q - self.robot.update_kinematics() - # return None - # Get the head frame transformation T_world_head = self.robot.get_T_world_frame("head") T_world_head[:3, 3][2] -= self.head_z_offset # offset the height of the head return T_world_head - def config_collision_model(self): + def config_collision_model(self) -> None: """Configure the collision model for the robot. Add collision pairs between the torso and the head colliders. """ - geom_model = self.robot.collision_model + geom_model = self.robot_ik.collision_model - # name_torso_collider = "dc15_a01_case_b_dummy_10" - # names_head_colliders = ["pp01063_stewart_plateform_7", "pp01063_stewart_plateform_11"] + id_torso_colliders = list(range(len(geom_model.geometryObjects) - 1)) + id_head_collider = len(geom_model.geometryObjects) - 1 - id_torso_collider = 12 # geom_model.getGeometryObjectId(name_torso_collider) - id_head_colliders = [ - 74, - 78, - ] # [geom_model.getGeometryObjectId(name) for name in names_head_colliders] - - for i in id_head_colliders: + for i in id_torso_colliders: geom_model.addCollisionPair( - pin.CollisionPair(id_torso_collider, i) + pin.CollisionPair(id_head_collider, i) ) # torso with head colliders - def compute_collision(self, margin=0.005): + def compute_collision(self, margin: float = 0.005) -> bool: """Compute the collision between the robot and the environment. Args: @@ -528,16 +516,16 @@ def compute_collision(self, margin=0.005): True if there is a collision, False otherwise. """ - collision_data = self.robot.collision_model.createData() - data = self.robot.model.createData() + collision_data = self.robot_ik.collision_model.createData() + data = self.robot_ik.model.createData() # pin.computeCollisions( pin.computeDistances( - self.robot.model, + self.robot_ik.model, data, - self.robot.collision_model, + self.robot_ik.collision_model, collision_data, - self.robot.state.q, + self.robot_ik.state.q, ) # Iterate over all collision pairs @@ -547,7 +535,9 @@ def compute_collision(self, margin=0.005): return False # Safe - def compute_jacobian(self, q: Optional[np.ndarray] = None) -> np.ndarray: + def compute_jacobian( + self, q: Optional[npt.NDArray[np.float64]] = None + ) -> npt.NDArray[np.float64]: """Compute the Jacobian of the head frame with respect to the actuated DoFs. The jacobian in local world aligned. @@ -565,7 +555,9 @@ def compute_jacobian(self, q: Optional[np.ndarray] = None) -> np.ndarray: # Computing the platform Jacobian # dx = Jp.dq - Jp = self.robot.frame_jacobian("head", "local_world_aligned") + Jp: npt.NDArray[np.float64] = self.robot.frame_jacobian( + "head", "local_world_aligned" + ) # Computing the constraints Jacobian # 0 = Jc.dq @@ -592,11 +584,13 @@ def compute_jacobian(self, q: Optional[np.ndarray] = None) -> np.ndarray: # dq_p = - (Jc_p)^(⁻1) @ Jc_a @ dq_a # Then we can substitute dq_p in the first equation and get # This new jacobian - J = Jp_a - Jp_p @ np.linalg.inv(Jc_p) @ Jc_a + J: npt.NDArray[np.float64] = Jp_a - Jp_p @ np.linalg.inv(Jc_p) @ Jc_a return J[:, self.actuated_idx_in_active] - def compute_gravity_torque(self, q: Optional[np.ndarray] = None) -> np.ndarray: + def compute_gravity_torque( + self, q: Optional[npt.NDArray[np.float64]] = None + ) -> npt.NDArray[np.float64]: """Compute the gravity torque vector for the actuated joints of the robot. This method uses the static gravity compensation torques from the robot's dictionary. @@ -617,7 +611,7 @@ def compute_gravity_torque(self, q: Optional[np.ndarray] = None) -> np.ndarray: grav_torque_all_joints = np.array( list( self.robot.static_gravity_compensation_torques_dict( - "pp01071_turning_bowl" + "body_foot_3dprint" ).values() ) ) @@ -630,24 +624,33 @@ def compute_gravity_torque(self, q: Optional[np.ndarray] = None) -> np.ndarray: # wrench_eq = np.linalg.pinv(J_all_joints.T) @ torque_all_joints # And then we can compute the actuated torques as: # torque_actuated = J_actuated.T @ wrench_eq - J_all_joints = self.robot.frame_jacobian("head", "local_world_aligned")[ - :, 6: - ] # all joints except the mobile base 6dofs + J_all_joints: npt.NDArray[np.float64] = self.robot.frame_jacobian( + "head", "local_world_aligned" + )[:, 6:] # all joints except the mobile base 6dofs J_actuated = self.compute_jacobian() # using a single matrix G to compute the actuated torques G = J_actuated.T @ np.linalg.pinv(J_all_joints.T) # torques of actuated joints - grav_torque_actuated = G @ grav_torque_all_joints + grav_torque_actuated: npt.NDArray[np.float64] = G @ grav_torque_all_joints # Compute the gravity torque return grav_torque_actuated - def set_automatic_body_yaw(self, body_yaw: float) -> None: + def set_automatic_body_yaw(self, automatic_body_yaw: bool) -> None: """Set the automatic body yaw. Args: - body_yaw (float): The yaw angle of the body. + automatic_body_yaw (bool): Whether to enable automatic body yaw. """ - self.start_body_yaw = body_yaw + self.automatic_body_yaw = automatic_body_yaw + + if not self.automatic_body_yaw: + self.ik_yaw_joint_task.configure("joints", "soft", 3.0) + else: + self.ik_yaw_joint_task.configure("joints", "soft", 5e-5) + + def get_joint(self, joint_name: str) -> float: + """Get the joint object by its name.""" + return float(self.robot.get_joint(joint_name)) diff --git a/src/reachy_mini/media/audio_base.py b/src/reachy_mini/media/audio_base.py index 333a69d1..4f705be0 100644 --- a/src/reachy_mini/media/audio_base.py +++ b/src/reachy_mini/media/audio_base.py @@ -6,56 +6,106 @@ import logging from abc import ABC, abstractmethod -from enum import Enum +from typing import Optional +import numpy as np +import numpy.typing as npt -class AudioBackend(Enum): - """Audio backends.""" - - SOUNDDEVICE = "sounddevice" - GSTREAMER = "gstreamer" +from reachy_mini.media.audio_control_utils import ReSpeaker, init_respeaker_usb class AudioBase(ABC): """Abstract class for opening and managing audio devices.""" - def __init__(self, backend: AudioBackend, log_level: str = "INFO") -> None: + SAMPLE_RATE = 16000 # respeaker samplerate + CHANNELS = 2 # respeaker channels + + def __init__(self, log_level: str = "INFO") -> None: """Initialize the audio device.""" self.logger = logging.getLogger(__name__) self.logger.setLevel(log_level) - self.backend = backend + self._respeaker: Optional[ReSpeaker] = init_respeaker_usb() + + def __del__(self) -> None: + """Destructor to ensure resources are released.""" + if self._respeaker: + self._respeaker.close() @abstractmethod - def start_recording(self): + def start_recording(self) -> None: """Start recording audio.""" pass @abstractmethod - def get_audio_sample(self): + def get_audio_sample(self) -> Optional[npt.NDArray[np.float32]]: """Read audio data from the device. Returns the data or None if error.""" pass - @abstractmethod - def get_audio_samplerate(self) -> int: - """Return the samplerate of the audio device.""" - pass + def get_input_audio_samplerate(self) -> int: + """Get the input samplerate of the audio device.""" + return self.SAMPLE_RATE + + def get_output_audio_samplerate(self) -> int: + """Get the outputsamplerate of the audio device.""" + return self.SAMPLE_RATE + + def get_input_channels(self) -> int: + """Get the number of input channels of the audio device.""" + return self.CHANNELS + + def get_output_channels(self) -> int: + """Get the number of output channels of the audio device.""" + return self.CHANNELS @abstractmethod - def stop_recording(self): + def stop_recording(self) -> None: """Close the audio device and release resources.""" pass @abstractmethod - def start_playing(self): + def start_playing(self) -> None: """Start playing audio.""" pass @abstractmethod - def push_audio_sample(self, data): + def push_audio_sample(self, data: npt.NDArray[np.float32]) -> None: """Push audio data to the output device.""" pass @abstractmethod - def stop_playing(self): + def stop_playing(self) -> None: """Stop playing audio and release resources.""" pass + + @abstractmethod + def play_sound(self, sound_file: str) -> None: + """Play a sound file. + + Args: + sound_file (str): Path to the sound file to play. + + """ + pass + + def get_DoA(self) -> tuple[float, bool] | None: + """Get the Direction of Arrival (DoA) value from the ReSpeaker device. + + The spatial angle is given in radians: + 0 radians is left, π/2 radians is front/back, π radians is right. + + Note: The microphone array requires firmware version 2.1.0 or higher to support this feature. + The firmware is located in src/reachy_mini/assets/firmware/*.bin. + Refer to https://wiki.seeedstudio.com/respeaker_xvf3800_introduction/#update-firmware for the upgrade process. + + Returns: + tuple: A tuple containing the DoA value as a float (radians) and the speech detection as a bool, or None if the device is not found. + + """ + if not self._respeaker: + self.logger.warning("ReSpeaker device not found.") + return None + + result = self._respeaker.read("DOA_VALUE_RADIANS") + if result is None: + return None + return float(result[0]), bool(result[1]) diff --git a/src/reachy_mini/media/audio_control_utils.py b/src/reachy_mini/media/audio_control_utils.py new file mode 100644 index 00000000..ef4b0d32 --- /dev/null +++ b/src/reachy_mini/media/audio_control_utils.py @@ -0,0 +1,447 @@ +"""Allows tuning of the XMOS XVF3800 chip. + +Example usage: + + # Read a parameter + python reachy_host.py AUDIO_MGR_OP_L + # Output: + # ReadCMD: cmdid: 143, resid: 35, response: array('B', [0, 8, 0]) + # AUDIO_MGR_OP_L: [0, 8, 0] + + # Write a parameter + python reachy_host.py AUDIO_MGR_OP_L --values 3 0 + # Output: + # Writing to AUDIO_MGR_OP_L with values: [3, 0] + # WriteCMD: cmdid: 15, resid: 35, payload: [3, 0] + # Write operation completed successfully + +More details about the parameters is available at: +https://www.xmos.com/documentation/XM-014888-PC/html/modules/fwk_xvf/doc/user_guide/AA_control_command_appendix.html +""" + +import argparse +import logging +import struct +import sys +import time +from typing import Any, Optional + +import usb.core +import usb.util +from libusb_package import get_libusb1_backend + +CONTROL_SUCCESS = 0 +SERVICER_COMMAND_RETRY = 64 + +# name, resid, cmdid, length, type +PARAMETERS = { + # APPLICATION_SERVICER_RESID commands + "VERSION": (48, 0, 3, "ro", "uint8"), + "BLD_MSG": (48, 1, 50, "ro", "char"), + "BLD_HOST": (48, 2, 30, "ro", "char"), + "BLD_REPO_HASH": (48, 3, 40, "ro", "char"), + "BLD_MODIFIED": (48, 4, 6, "ro", "char"), + "BOOT_STATUS": (48, 5, 3, "ro", "char"), + "TEST_CORE_BURN": (48, 6, 1, "rw", "uint8"), + "REBOOT": (48, 7, 1, "wo", "uint8"), + "USB_BIT_DEPTH": (48, 8, 2, "rw", "uint8"), + "SAVE_CONFIGURATION": (48, 9, 1, "wo", "uint8"), + "CLEAR_CONFIGURATION": (48, 10, 1, "wo", "uint8"), + # AEC_RESID commands + "SHF_BYPASS": (33, 70, 1, "rw", "uint8"), + "AEC_NUM_MICS": (33, 71, 1, "ro", "int32"), + "AEC_NUM_FARENDS": (33, 72, 1, "ro", "int32"), + "AEC_MIC_ARRAY_TYPE": (33, 73, 1, "ro", "int32"), + "AEC_MIC_ARRAY_GEO": (33, 74, 12, "ro", "float"), + "AEC_AZIMUTH_VALUES": (33, 75, 4, "ro", "radians"), + "TEST_AEC_DISABLE_CONTROL": (33, 76, 1, "wo", "uint32"), + "AEC_CURRENT_IDLE_TIME": (33, 77, 1, "ro", "uint32"), + "AEC_MIN_IDLE_TIME": (33, 78, 1, "ro", "uint32"), + "AEC_RESET_MIN_IDLE_TIME": (33, 79, 1, "wo", "uint32"), + "AEC_SPENERGY_VALUES": (33, 80, 4, "ro", "float"), + "AEC_FIXEDBEAMSAZIMUTH_VALUES": (33, 81, 2, "rw", "radians"), + "AEC_FIXEDBEAMSELEVATION_VALUES": (33, 82, 2, "rw", "radians"), + "AEC_FIXEDBEAMSGATING": (33, 83, 1, "rw", "uint8"), + "SPECIAL_CMD_AEC_FAR_MIC_INDEX": (33, 90, 2, "wo", "int32"), + "SPECIAL_CMD_AEC_FILTER_COEFF_START_OFFSET": (33, 91, 1, "rw", "int32"), + "SPECIAL_CMD_AEC_FILTER_COEFFS": (33, 92, 15, "rw", "float"), + "SPECIAL_CMD_AEC_FILTER_LENGTH": (33, 93, 1, "ro", "int32"), + "AEC_FILTER_CMD_ABORT": (33, 94, 1, "wo", "int32"), + "AEC_AECPATHCHANGE": (33, 0, 1, "ro", "int32"), + "AEC_HPFONOFF": (33, 1, 1, "rw", "int32"), + "AEC_AECSILENCELEVEL": (33, 2, 2, "rw", "float"), + "AEC_AECCONVERGED": (33, 3, 1, "ro", "int32"), + "AEC_AECEMPHASISONOFF": (33, 4, 1, "rw", "int32"), + "AEC_FAR_EXTGAIN": (33, 5, 1, "rw", "float"), + "AEC_PCD_COUPLINGI": (33, 6, 1, "rw", "float"), + "AEC_PCD_MINTHR": (33, 7, 1, "rw", "float"), + "AEC_PCD_MAXTHR": (33, 8, 1, "rw", "float"), + "AEC_RT60": (33, 9, 1, "ro", "float"), + "AEC_ASROUTONOFF": (33, 35, 1, "rw", "int32"), + "AEC_ASROUTGAIN": (33, 36, 1, "rw", "float"), + "AEC_FIXEDBEAMSONOFF": (33, 37, 1, "rw", "int32"), + "AEC_FIXEDBEAMNOISETHR": (33, 38, 2, "rw", "float"), + # AUDIO_MGR_RESID commands + "AUDIO_MGR_MIC_GAIN": (35, 0, 1, "rw", "float"), + "AUDIO_MGR_REF_GAIN": (35, 1, 1, "rw", "float"), + "AUDIO_MGR_CURRENT_IDLE_TIME": (35, 2, 1, "ro", "int32"), + "AUDIO_MGR_MIN_IDLE_TIME": (35, 3, 1, "ro", "int32"), + "AUDIO_MGR_RESET_MIN_IDLE_TIME": (35, 4, 1, "wo", "int32"), + "MAX_CONTROL_TIME": (35, 5, 1, "ro", "int32"), + "RESET_MAX_CONTROL_TIME": (35, 6, 1, "wo", "int32"), + "I2S_CURRENT_IDLE_TIME": (35, 7, 1, "ro", "int32"), + "I2S_MIN_IDLE_TIME": (35, 8, 1, "ro", "int32"), + "I2S_RESET_MIN_IDLE_TIME": (35, 9, 1, "wo", "int32"), + "I2S_INPUT_PACKED": (35, 10, 1, "rw", "uint8"), + "AUDIO_MGR_SELECTED_AZIMUTHS": (35, 11, 2, "ro", "radians"), + "AUDIO_MGR_SELECTED_CHANNELS": (35, 12, 2, "rw", "uint8"), + "AUDIO_MGR_OP_PACKED": (35, 13, 2, "rw", "uint8"), + "AUDIO_MGR_OP_UPSAMPLE": (35, 14, 2, "rw", "uint8"), + "AUDIO_MGR_OP_L": (35, 15, 2, "rw", "uint8"), + "AUDIO_MGR_OP_L_PK0": (35, 16, 2, "rw", "uint8"), + "AUDIO_MGR_OP_L_PK1": (35, 17, 2, "rw", "uint8"), + "AUDIO_MGR_OP_L_PK2": (35, 18, 2, "rw", "uint8"), + "AUDIO_MGR_OP_R": (35, 19, 2, "rw", "uint8"), + "AUDIO_MGR_OP_R_PK0": (35, 20, 2, "rw", "uint8"), + "AUDIO_MGR_OP_R_PK1": (35, 21, 2, "rw", "uint8"), + "AUDIO_MGR_OP_R_PK2": (35, 22, 2, "rw", "uint8"), + "AUDIO_MGR_OP_ALL": (35, 23, 12, "rw", "uint8"), + "I2S_INACTIVE": (35, 24, 1, "ro", "uint8"), + "AUDIO_MGR_FAR_END_DSP_ENABLE": (35, 25, 1, "rw", "uint8"), + "AUDIO_MGR_SYS_DELAY": (35, 26, 1, "rw", "int32"), + "I2S_DAC_DSP_ENABLE": (35, 27, 1, "rw", "uint8"), + # GPO_SERVICER_RESID commands + "GPO_READ_VALUES": (20, 0, 5, "ro", "uint8"), + "GPO_WRITE_VALUE": (20, 1, 2, "wo", "uint8"), + "GPO_PORT_PIN_INDEX": (20, 2, 2, "rw", "uint32"), + "GPO_PIN_VAL": (20, 3, 3, "wo", "uint8"), + "GPO_PIN_ACTIVE_LEVEL": (20, 4, 1, "rw", "uint32"), + "GPO_PIN_PWM_DUTY": (20, 5, 1, "rw", "uint8"), + "GPO_PIN_FLASH_MASK": (20, 6, 1, "rw", "uint32"), + "LED_EFFECT": (20, 12, 1, "rw", "uint8"), + "LED_BRIGHTNESS": (20, 13, 1, "rw", "uint8"), + "LED_GAMMIFY": (20, 14, 1, "rw", "uint8"), + "LED_SPEED": (20, 15, 1, "rw", "uint8"), + "LED_COLOR": (20, 16, 1, "rw", "uint32"), + "LED_DOA_COLOR": (20, 17, 2, "rw", "uint32"), + "DOA_VALUE": (20, 18, 2, "ro", "uint32"), + "DOA_VALUE_RADIANS": (20, 19, 2, "ro", "radians"), + # PP_RESID commands + "PP_CURRENT_IDLE_TIME": (17, 70, 1, "ro", "uint32"), + "PP_MIN_IDLE_TIME": (17, 71, 1, "ro", "uint32"), + "PP_RESET_MIN_IDLE_TIME": (17, 72, 1, "wo", "uint32"), + "SPECIAL_CMD_PP_NLMODEL_NROW_NCOL": (17, 90, 2, "ro", "int32"), + "SPECIAL_CMD_NLMODEL_START": (17, 91, 1, "wo", "int32"), + "SPECIAL_CMD_NLMODEL_COEFF_START_OFFSET": (17, 92, 1, "rw", "int32"), + "SPECIAL_CMD_PP_NLMODEL": (17, 93, 15, "rw", "float"), + "PP_NL_MODEL_CMD_ABORT": (17, 94, 1, "wo", "int32"), + "SPECIAL_CMD_PP_NLMODEL_BAND": (17, 95, 1, "rw", "uint8"), + "SPECIAL_CMD_PP_EQUALIZATION_NUM_BANDS": (17, 96, 1, "ro", "int32"), + "SPECIAL_CMD_EQUALIZATION_START": (17, 97, 1, "wo", "int32"), + "SPECIAL_CMD_EQUALIZATION_COEFF_START_OFFSET": (17, 98, 1, "rw", "int32"), + "SPECIAL_CMD_PP_EQUALIZATION": (17, 99, 15, "rw", "float"), + "PP_EQUALIZATION_CMD_ABORT": (17, 100, 1, "wo", "int32"), + "PP_AGCONOFF": (17, 10, 1, "rw", "int32"), + "PP_AGCMAXGAIN": (17, 11, 1, "rw", "float"), + "PP_AGCDESIREDLEVEL": (17, 12, 1, "rw", "float"), + "PP_AGCGAIN": (17, 13, 1, "rw", "float"), + "PP_AGCTIME": (17, 14, 1, "rw", "float"), + "PP_AGCFASTTIME": (17, 15, 1, "rw", "float"), + "PP_AGCALPHAFASTGAIN": (17, 16, 1, "rw", "float"), + "PP_AGCALPHASLOW": (17, 17, 1, "rw", "float"), + "PP_AGCALPHAFAST": (17, 18, 1, "rw", "float"), + "PP_LIMITONOFF": (17, 19, 1, "rw", "int32"), + "PP_LIMITPLIMIT": (17, 20, 1, "rw", "float"), + "PP_MIN_NS": (17, 21, 1, "rw", "float"), + "PP_MIN_NN": (17, 22, 1, "rw", "float"), + "PP_ECHOONOFF": (17, 23, 1, "rw", "int32"), + "PP_GAMMA_E": (17, 24, 1, "rw", "float"), + "PP_GAMMA_ETAIL": (17, 25, 1, "rw", "float"), + "PP_GAMMA_ENL": (17, 26, 1, "rw", "float"), + "PP_NLATTENONOFF": (17, 27, 1, "rw", "int32"), + "PP_NLAEC_MODE": (17, 28, 1, "rw", "int32"), + "PP_MGSCALE": (17, 29, 3, "rw", "float"), + "PP_FMIN_SPEINDEX": (17, 30, 1, "rw", "float"), + "PP_DTSENSITIVE": (17, 31, 1, "rw", "int32"), + "PP_ATTNS_MODE": (17, 32, 1, "rw", "int32"), + "PP_ATTNS_NOMINAL": (17, 33, 1, "rw", "float"), + "PP_ATTNS_SLOPE": (17, 34, 1, "rw", "float"), +} + + +class ReSpeaker: + """Class to interface with the ReSpeaker XVF3800 USB device.""" + + TIMEOUT = 100000 + + def __init__(self, dev: usb.core.Device) -> None: + """Initialize the ReSpeaker interface with the given USB device.""" + self.dev = dev + + def write(self, name: str, data_list: Any) -> None: + """Write data to a specified parameter on the ReSpeaker device.""" + try: + data = PARAMETERS[name] + except KeyError: + return + + if data[3] == "ro": + raise ValueError("{} is read-only".format(name)) + + if len(data_list) != data[2]: + raise ValueError("{} value count is not {}".format(name, data[2])) + + windex = data[0] # resid + wvalue = data[1] # cmdid + data_cnt = data[2] # cnt + data_type = data[4] # data type + payload = [] # type: ignore[var-annotated] + + if data_type == "float" or data_type == "radians": + for i in range(data_cnt): + payload += struct.pack(b"f", float(data_list[i])) + elif data_type == "char": + # For char arrays, convert string to bytes + payload = ( + bytearray(data_list, "utf-8") # type: ignore[assignment] + if isinstance(data_list, str) + else bytearray(data_list) + ) + elif data_type == "uint8": + for i in range(data_cnt): + payload += data_list[i].to_bytes(1, byteorder="little") + elif data_type == "uint32" or data_type == "int32": + for i in range(data_cnt): + payload += struct.pack( + b"I" if data_type == "uint32" else b"i", data_list[i] + ) + else: + # Default to int32 for other types + for i in range(data_cnt): + payload += struct.pack(b"i", data_list[i]) + + logging.debug( + "WriteCMD: cmdid: {}, resid: {}, payload: {}".format( + wvalue, windex, payload + ) + ) + + self.dev.ctrl_transfer( + usb.util.CTRL_OUT + | usb.util.CTRL_TYPE_VENDOR + | usb.util.CTRL_RECIPIENT_DEVICE, + 0, + wvalue, + windex, + payload, + self.TIMEOUT, + ) + + def read(self, name: str) -> Any: + """Read data from a specified parameter on the ReSpeaker device.""" + try: + data = PARAMETERS[name] + except KeyError: + return + + read_attempts = 1 + windex = data[0] # resid + wvalue = 0x80 | data[1] # cmdid + data_cnt = data[2] # cnt + data_type = data[4] # data type + if data_type == "uint8" or data_type == "char": + length = data_cnt + 1 # 1 byte for status + elif ( + data_type == "float" + or data_type == "radians" + or data_type == "uint32" + or data_type == "int32" + ): + length = data_cnt * 4 + 1 # 1 byte for status + + response = self.dev.ctrl_transfer( + usb.util.CTRL_IN + | usb.util.CTRL_TYPE_VENDOR + | usb.util.CTRL_RECIPIENT_DEVICE, + 0, + wvalue, + windex, + length, + self.TIMEOUT, + ) + while True: + if read_attempts > 100: + raise ValueError("Read attempt exceeds 100 times") + if response[0] == CONTROL_SUCCESS: + break + elif response[0] == SERVICER_COMMAND_RETRY: + read_attempts += 1 + response = self.dev.ctrl_transfer( + usb.util.CTRL_IN + | usb.util.CTRL_TYPE_VENDOR + | usb.util.CTRL_RECIPIENT_DEVICE, + 0, + wvalue, + windex, + length, + self.TIMEOUT, + ) + else: + raise ValueError("Unknown status code: {}".format(response[0])) + time.sleep(0.01) + + logging.info( + "ReadCMD: cmdid: {}, resid: {}, response: {}".format( + wvalue, windex, response + ) + ) + + if data_type == "uint8": + result = response.tolist() + elif data_type == "char": + # For char arrays, convert bytes to string + byte_data = response.tobytes() + # Remove status byte and null terminators + result = byte_data[1:].rstrip(b"\x00").decode("utf-8", errors="ignore") + elif data_type == "radians" or data_type == "float": + byte_data = response.tobytes() + match_str = "<" + for i in range(data_cnt): + match_str += "f" + result = struct.unpack(match_str, byte_data[1:]) + elif data_type == "uint32" or data_type == "int32": + result = response.tolist() + + return result + + def close(self) -> None: + """Close the interface.""" + usb.util.dispose_resources(self.dev) + + +def find(vid: int = 0x2886, pid: int = 0x001A) -> ReSpeaker | None: + """Find and return the ReSpeaker USB device with the given Vendor ID and Product ID.""" + dev = usb.core.find(idVendor=vid, idProduct=pid, backend=get_libusb1_backend()) + if not dev: + return None + + return ReSpeaker(dev) + + +def init_respeaker_usb() -> Optional[ReSpeaker]: + """Initialize the ReSpeaker USB device. Looks for both new and beta device IDs.""" + try: + # Try new firmware first + dev = usb.core.find( + idVendor=0x38FB, idProduct=0x1001, backend=get_libusb1_backend() + ) + + # If not found, try old firmware + if dev is None: + dev = usb.core.find( + idVendor=0x2886, idProduct=0x001A, backend=get_libusb1_backend() + ) + if dev is not None: + logging.warning("Old firmware detected. Please update the firmware!") + + # If still not found, raise error + if dev is None: + logging.error("No Reachy Mini Audio USB device found!") + return None + + return ReSpeaker(dev) + + except usb.core.NoBackendError: + logging.error( + "No USB backend was found! Make sure libusb_package is correctly installed with `pip install libusb_package`." + ) + return None + + +def main() -> None: + """Parse arguments and execute read/write commands.""" + parser = argparse.ArgumentParser( + description="Reachy Mini Audio Host Control Script" + ) + parser.add_argument( + "command", + choices=PARAMETERS.keys(), + help="Command to execute (e.g., VERSION, DOA_VALUE, etc.)", + ) + parser.add_argument( + "--vid", + type=lambda x: int(x, 0), + default=0x38FB, + help="Vendor ID (default: 0x38FB)", + ) + parser.add_argument( + "--pid", + type=lambda x: int(x, 0), + default=0x1001, + help="Product ID (default: 0x1001)", + ) + parser.add_argument( + "--values", + nargs="+", + type=float, + help="Values for write commands (only for write operations)", + ) + + args = parser.parse_args() + + # Allow user overrides if provided, else use known defaults + if args.vid is not None and args.pid is not None: + dev = find(vid=args.vid, pid=args.pid) + else: + dev = init_respeaker_usb() + + if not dev: + print("No device found") + sys.exit(1) + + try: + if args.values: + if PARAMETERS[args.command][3] == "ro": + print(f"Error: {args.command} is read-only and cannot be written to") + sys.exit(1) + + if ( + PARAMETERS[args.command][4] != "float" + and PARAMETERS[args.command][4] != "radians" + ): + args.values = [int(v) for v in args.values] + + if PARAMETERS[args.command][2] != len(args.values): + print( + f"Error: {args.command} value count is {PARAMETERS[args.command][2]}, but {len(args.values)} values provided" + ) + sys.exit(1) + + print(f"Writing to {args.command} with values: {args.values}") + dev.write(args.command, args.values) + time.sleep(0.1) + print("Write operation completed successfully") + else: + if PARAMETERS[args.command][3] == "wo": + print(f"Error: {args.command} is write-only and cannot be read") + sys.exit(1) + + result = dev.read(args.command) + print(f"{args.command}: {result}") + + except Exception as e: + error_msg = f"Error executing command {args.command}: {e}" + print(error_msg) + + # Check if it's a permission error, so far only seen on Linux + if "Errno 13" in str(e) or "Access denied" in str(e) or "insufficient permissions" in str(e): + print("\nThis looks like a permissions error.") + print("\n - You are most likely on Linux and need to adjust udev rules for USB permissions.") + print("\n - If you are not on Linux or have additional questions contact the team.") + sys.exit(1) + finally: + dev.close() + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/src/reachy_mini/media/audio_gstreamer.py b/src/reachy_mini/media/audio_gstreamer.py index 5b3e0aa9..7b09e848 100644 --- a/src/reachy_mini/media/audio_gstreamer.py +++ b/src/reachy_mini/media/audio_gstreamer.py @@ -7,6 +7,11 @@ from threading import Thread from typing import Optional +import numpy as np +import numpy.typing as npt + +from reachy_mini.media.audio_utils import get_respeaker_card_number + try: import gi except ImportError as e: @@ -21,7 +26,7 @@ from gi.repository import GLib, Gst, GstApp # noqa: E402 -from .audio_base import AudioBackend, AudioBase # noqa: E402 +from .audio_base import AudioBase # noqa: E402 class GStreamerAudio(AudioBase): @@ -29,13 +34,13 @@ class GStreamerAudio(AudioBase): def __init__(self, log_level: str = "INFO") -> None: """Initialize the GStreamer audio.""" - super().__init__(backend=AudioBackend.GSTREAMER, log_level=log_level) + super().__init__(log_level=log_level) Gst.init(None) self._loop = GLib.MainLoop() self._thread_bus_calls = Thread(target=lambda: self._loop.run(), daemon=True) self._thread_bus_calls.start() - self._samplerate = 24000 + self._id_audio_card = get_respeaker_card_number() self._pipeline_record = Gst.Pipeline.new("audio_recorder") self._appsink_audio: Optional[GstApp] = None @@ -53,52 +58,53 @@ def __init__(self, log_level: str = "INFO") -> None: GLib.PRIORITY_DEFAULT, self._on_bus_message, self._loop ) - def _init_pipeline_record(self, pipeline): + def _init_pipeline_record(self, pipeline: Gst.Pipeline) -> None: self._appsink_audio = Gst.ElementFactory.make("appsink") caps = Gst.Caps.from_string( - f"audio/x-raw,channels=1,rate={self._samplerate},format=S16LE" + f"audio/x-raw,rate={self.SAMPLE_RATE},channels={self.CHANNELS},format=F32LE,layout=interleaved" ) self._appsink_audio.set_property("caps", caps) self._appsink_audio.set_property("drop", True) # avoid overflow self._appsink_audio.set_property("max-buffers", 200) - autoaudiosrc = Gst.ElementFactory.make("autoaudiosrc") # use default mic - # caps_respeaker = Gst.Caps.from_string( - # "audio/x-raw, layout=interleaved, format=S16LE, rate=16000, channels=2" - # ) - # autoaudiosrc.set_property("filter-caps", caps_respeaker) + audiosrc: Optional[Gst.Element] = None + if self._id_audio_card == -1: + audiosrc = Gst.ElementFactory.make("autoaudiosrc") # use default mic + else: + audiosrc = Gst.ElementFactory.make("alsasrc") + audiosrc.set_property("device", f"hw:{self._id_audio_card},0") + queue = Gst.ElementFactory.make("queue") audioconvert = Gst.ElementFactory.make("audioconvert") audioresample = Gst.ElementFactory.make("audioresample") - if not all( - [autoaudiosrc, queue, audioconvert, audioresample, self._appsink_audio] - ): + if not all([audiosrc, queue, audioconvert, audioresample, self._appsink_audio]): raise RuntimeError("Failed to create GStreamer elements") - pipeline.add(autoaudiosrc) + pipeline.add(audiosrc) pipeline.add(queue) pipeline.add(audioconvert) pipeline.add(audioresample) pipeline.add(self._appsink_audio) - autoaudiosrc.link(queue) + audiosrc.link(queue) queue.link(audioconvert) audioconvert.link(audioresample) audioresample.link(self._appsink_audio) - def __del__(self): + def __del__(self) -> None: """Destructor to ensure gstreamer resources are released.""" + super().__del__() self._loop.quit() self._bus_record.remove_watch() self._bus_playback.remove_watch() - def _init_pipeline_playback(self, pipeline): + def _init_pipeline_playback(self, pipeline: Gst.Pipeline) -> None: self._appsrc = Gst.ElementFactory.make("appsrc") self._appsrc.set_property("format", Gst.Format.TIME) self._appsrc.set_property("is-live", True) caps = Gst.Caps.from_string( - f"audio/x-raw,format=F32LE,channels=1,rate={self._samplerate},layout=interleaved" + f"audio/x-raw,format=F32LE,channels={self.CHANNELS},rate={self.SAMPLE_RATE},layout=interleaved" ) self._appsrc.set_property("caps", caps) @@ -106,7 +112,12 @@ def _init_pipeline_playback(self, pipeline): audioresample = Gst.ElementFactory.make("audioresample") queue = Gst.ElementFactory.make("queue") - audiosink = Gst.ElementFactory.make("autoaudiosink") # use default speaker + audiosink: Optional[Gst.Element] = None + if self._id_audio_card == -1: + audiosink = Gst.ElementFactory.make("autoaudiosink") # use default speaker + else: + audiosink = Gst.ElementFactory.make("alsasink") + audiosink.set_property("device", f"hw:{self._id_audio_card},0") pipeline.add(queue) pipeline.add(audiosink) @@ -132,11 +143,11 @@ def _on_bus_message(self, bus: Gst.Bus, msg: Gst.Message, loop) -> bool: # type return True - def start_recording(self): + def start_recording(self) -> None: """Open the audio card using GStreamer.""" self._pipeline_record.set_state(Gst.State.PLAYING) - def _get_sample(self, appsink): + def _get_sample(self, appsink: GstApp.AppSink) -> Optional[bytes]: sample = appsink.try_pull_sample(20_000_000) if sample is None: return None @@ -149,37 +160,74 @@ def _get_sample(self, appsink): data = buf.extract_dup(0, buf.get_size()) return data - def get_audio_sample(self) -> Optional[bytes]: + def get_audio_sample(self) -> Optional[npt.NDArray[np.float32]]: """Read a sample from the audio card. Returns the sample or None if error. Returns: - Optional[bytes]: The captured sample in raw format, or None if error. + Optional[npt.NDArray[np.float32]]: The captured sample in raw format, or None if error. """ - return self._get_sample(self._appsink_audio) + sample = self._get_sample(self._appsink_audio) + if sample is None: + return None + return np.frombuffer(sample, dtype=np.float32).reshape(-1, 2) + + def get_input_audio_samplerate(self) -> int: + """Get the input samplerate of the audio device.""" + return self.SAMPLE_RATE + + def get_output_audio_samplerate(self) -> int: + """Get the output samplerate of the audio device.""" + return self.SAMPLE_RATE + + def get_input_channels(self) -> int: + """Get the number of input channels of the audio device.""" + return self.CHANNELS - def get_audio_samplerate(self) -> int: - """Return the samplerate of the audio device.""" - return self._samplerate + def get_output_channels(self) -> int: + """Get the number of output channels of the audio device.""" + return self.CHANNELS - def stop_recording(self): + def stop_recording(self) -> None: """Release the camera resource.""" self._pipeline_record.set_state(Gst.State.NULL) - def start_playing(self): + def start_playing(self) -> None: """Open the audio output using GStreamer.""" self._pipeline_playback.set_state(Gst.State.PLAYING) - def stop_playing(self): + def stop_playing(self) -> None: """Stop playing audio and release resources.""" self._pipeline_playback.set_state(Gst.State.NULL) - def push_audio_sample(self, data: bytes): + def push_audio_sample(self, data: npt.NDArray[np.float32]) -> None: """Push audio data to the output device.""" if self._appsrc is not None: - buf = Gst.Buffer.new_wrapped(data) + buf = Gst.Buffer.new_wrapped(data.tobytes()) self._appsrc.push_buffer(buf) else: self.logger.warning( "AppSrc is not initialized. Call start_playing() first." ) + + def play_sound(self, sound_file: str) -> None: + """Play a sound file. + + Args: + sound_file (str): Path to the sound file to play. + + """ + self.logger.warning("play_sound is not implemented for GStreamerAudio.") + + def clear_player(self) -> None: + """Flush the player's appsrc to drop any queued audio immediately.""" + if self._appsrc is not None: + self._pipeline_playback.set_state(Gst.State.PAUSED) + self._appsrc.send_event(Gst.Event.new_flush_start()) + self._appsrc.send_event(Gst.Event.new_flush_stop(reset_time=True)) + self._pipeline_playback.set_state(Gst.State.PLAYING) + self.logger.info("Cleared player queue") + else: + self.logger.warning( + "AppSrc is not initialized. Call start_playing() first." + ) diff --git a/src/reachy_mini/media/audio_sounddevice.py b/src/reachy_mini/media/audio_sounddevice.py index 8ded4f79..396fd5c1 100644 --- a/src/reachy_mini/media/audio_sounddevice.py +++ b/src/reachy_mini/media/audio_sounddevice.py @@ -1,97 +1,223 @@ """Audio implementation using sounddevice backend.""" import os +import threading +from collections import deque +from typing import Deque, List, Optional -import librosa import numpy as np +import numpy.typing as npt +import scipy import sounddevice as sd +import soundfile as sf from reachy_mini.utils.constants import ASSETS_ROOT_PATH -from .audio_base import AudioBackend, AudioBase +from .audio_base import AudioBase +MAX_INPUT_CHANNELS = 4 +MAX_INPUT_QUEUE_SECONDS = 60.0 class SoundDeviceAudio(AudioBase): """Audio device implementation using sounddevice.""" def __init__( self, - frames_per_buffer=1024, - log_level="INFO", - device=None, - ): + log_level: str = "INFO", + ) -> None: """Initialize the SoundDevice audio device.""" - super().__init__(backend=AudioBackend.SOUNDDEVICE, log_level=log_level) - self.frames_per_buffer = frames_per_buffer - self.device = device - self.stream = None + super().__init__(log_level=log_level) + self._input_stream = None self._output_stream = None - self._buffer = None - self._device_id = self.get_output_device_id("respeaker") - self._samplerate = ( - -1 - ) # will be set on first use to avoid issues if device is not present (CI) + self._input_lock = threading.Lock() + self._output_lock = threading.Lock() + self._input_buffer: Deque[npt.NDArray[np.float32]] = deque() + self._output_buffer: List[npt.NDArray[np.float32]] = [] + self._input_max_queue_seconds: float = MAX_INPUT_QUEUE_SECONDS + self._input_queued_samples: int = 0 - def start_recording(self): + self._output_device_id = self._get_device_id( + ["Reachy Mini Audio", "respeaker"], device_io_type="output" + ) + self._input_device_id = self._get_device_id( + ["Reachy Mini Audio", "respeaker"], device_io_type="input" + ) + + self._logs = { + "input_underflows": 0, + "input_overflows": 0, + } + + @property + def _input_max_queue_samples(self) -> int: + return int(self._input_max_queue_seconds * self.get_input_audio_samplerate()) + + @property + def _is_recording(self) -> bool: + return self._input_stream is not None and self._input_stream.active + + def start_recording(self) -> None: """Open the audio input stream, using ReSpeaker card if available.""" - self.stream = sd.InputStream( - blocksize=self.frames_per_buffer, - device=self._device_id, - callback=self._callback, + if self._is_recording: + self.stop_recording() + + self._input_stream = sd.InputStream( + device=self._input_device_id, + samplerate=self.get_input_audio_samplerate(), + callback=self._input_callback, ) - self._buffer = [] - self.stream.start() - self.logger.info("SoundDevice audio stream opened.") + if self._input_stream is None: + raise RuntimeError("Failed to open SoundDevice audio input stream.") - def _callback(self, indata, frames, time, status): - if status: - self.logger.warning(f"SoundDevice status: {status}") - self._buffer.append(indata.copy()) + self._input_buffer.clear() + self._input_queued_samples = 0 + self._input_stream.start() + self.logger.info("SoundDevice audio input stream opened.") + + def _input_callback( + self, + indata: npt.NDArray[np.float32], + frames: int, + time: int, + status: sd.CallbackFlags, + ) -> None: + if status and status.input_underflow: + self._logs["input_underflows"] += 1 + if self._logs["input_underflows"] % 10 == 1: + self.logger.debug(f"Audio input underflow count: {self._logs['input_underflows']}") + + with self._input_lock: + if self._input_queued_samples + indata.shape[0] > self._input_max_queue_samples: + while self._input_queued_samples + indata.shape[0] > self._input_max_queue_samples and len(self._input_buffer) > 0: + dropped = self._input_buffer.popleft() + self._input_queued_samples -= dropped.shape[0] + self._logs["input_overflows"] += 1 + self.logger.warning( + "Audio input buffer overflowed, dropped old chunks !" + ) + self._input_buffer.append(indata[:, :MAX_INPUT_CHANNELS].copy()) + self._input_queued_samples += indata.shape[0] - def get_audio_sample(self): + def get_audio_sample(self) -> Optional[npt.NDArray[np.float32]]: """Read audio data from the buffer. Returns numpy array or None if empty.""" - if self._buffer and len(self._buffer) > 0: - data = np.concatenate(self._buffer, axis=0) - self._buffer.clear() - return data - self.logger.warning("No audio data available in buffer.") + with self._input_lock: + if self._input_buffer and len(self._input_buffer) > 0: + data: npt.NDArray[np.float32] = np.concatenate(self._input_buffer, axis=0) + self._input_buffer.clear() + self._input_queued_samples = 0 + return data + self.logger.debug("No audio data available in buffer.") return None - def get_audio_samplerate(self) -> int: - """Return the samplerate of the audio device.""" - if self._samplerate == -1: - self._samplerate = int( - sd.query_devices(self._device_id)["default_samplerate"] - ) - return self._samplerate + def get_input_audio_samplerate(self) -> int: + """Get the input samplerate of the audio device.""" + return int( + sd.query_devices(self._input_device_id, "input")["default_samplerate"] + ) + + def get_output_audio_samplerate(self) -> int: + """Get the output samplerate of the audio device.""" + return int( + sd.query_devices(self._output_device_id, "output")["default_samplerate"] + ) + + def get_input_channels(self) -> int: + """Get the number of input channels of the audio device.""" + return min( + int(sd.query_devices(self._input_device_id, "input")["max_input_channels"]), + MAX_INPUT_CHANNELS + ) + + def get_output_channels(self) -> int: + """Get the number of output channels of the audio device.""" + return int( + sd.query_devices(self._output_device_id, "output")["max_output_channels"] + ) - def stop_recording(self): + def stop_recording(self) -> None: """Close the audio stream and release resources.""" - if self.stream is not None: - self.stream.stop() - self.stream.close() - self.stream = None + if self._is_recording: + self._input_stream.stop() # type: ignore[attr-defined] + self._input_stream.close() # type: ignore[attr-defined] + self._input_stream = None self.logger.info("SoundDevice audio stream closed.") - def push_audio_sample(self, data): + def push_audio_sample(self, data: npt.NDArray[np.float32]) -> None: """Push audio data to the output device.""" if self._output_stream is not None: - self._output_stream.write(data) + with self._output_lock: + self._output_buffer.append(data.copy()) else: self.logger.warning( "Output stream is not open. Call start_playing() first." ) - def start_playing(self): + def start_playing(self) -> None: """Open the audio output stream.""" + self._output_buffer.clear() # Clear any old data + if self._output_stream is not None: + self.stop_playing() self._output_stream = sd.OutputStream( - samplerate=self.get_audio_samplerate(), - device=self._device_id, - channels=1, + samplerate=self.get_output_audio_samplerate(), + device=self._output_device_id, + callback=self._output_callback, ) + if self._output_stream is None: + raise RuntimeError("Failed to open SoundDevice audio output stream.") self._output_stream.start() + self.logger.info("SoundDevice audio output stream opened.") - def stop_playing(self): + def _output_callback( + self, + outdata: npt.NDArray[np.float32], + frames: int, + time: int, + status: sd.CallbackFlags, + ) -> None: + """Handle audio output stream callback.""" + if status: + self.logger.warning(f"SoundDevice output status: {status}") + + with self._output_lock: + if self._output_buffer: + # Get the first chunk from the buffer + chunk = self._output_buffer[0] + available = len(chunk) + chunk = self.ensure_chunk_shape(chunk, outdata.shape) + + if available >= frames: + # We have enough data for this callback + outdata[:] = chunk[:frames] + # Remove the used portion + if available > frames: + self._output_buffer[0] = chunk[frames:] + else: + self._output_buffer.pop(0) + else: + # Not enough data, fill what we can and pad with zeros + outdata[:available] = chunk + outdata[available:] = 0 + self._output_buffer.pop(0) + else: + # No data available, output silence + outdata.fill(0) + + def ensure_chunk_shape(self, chunk: npt.NDArray[np.float32], target_shape: tuple[int, ...]) -> npt.NDArray[np.float32]: + """Ensure chunk has the shape (frames, num_channels) as required by outdata. + + - If chunk is 1D, tile to required num_channels. + - If chunk is 2D with mismatched channels, use column 0. + - If chunk is already correct, return as-is. + """ + num_channels = target_shape[1] if len(target_shape) > 1 else 1 + if chunk.ndim == 1: + return np.tile(chunk[:, None], (1, num_channels)) + elif chunk.shape[1] != num_channels: + # Broadcast first channel only + return np.tile(chunk[:, [0]], (1, num_channels)) + return chunk + + def stop_playing(self) -> None: """Close the audio output stream.""" if self._output_stream is not None: self._output_stream.stop() @@ -100,33 +226,75 @@ def stop_playing(self): self.logger.info("SoundDevice audio output stream closed.") def play_sound(self, sound_file: str) -> None: - """Play a sound file from the assets directory or a given path using sounddevice and soundfile.""" - file_path = f"{ASSETS_ROOT_PATH}/{sound_file}" - if not os.path.exists(file_path): - raise FileNotFoundError(f"Sound file {file_path} not found.") + """Play a sound file. - data, samplerate_in = librosa.load( - file_path, sr=self.get_audio_samplerate(), mono=True - ) + Args: + sound_file (str): Path to the sound file to play. May be given relative to the assets directory or as an absolute path. + + """ + if not os.path.exists(sound_file): + file_path = f"{ASSETS_ROOT_PATH}/{sound_file}" + if not os.path.exists(file_path): + raise FileNotFoundError( + f"Sound file {sound_file} not found in assets directory or given path." + ) + else: + file_path = sound_file + + data, samplerate_in = sf.read(file_path, dtype="float32") + samplerate_out = self.get_output_audio_samplerate() + + if samplerate_in != samplerate_out: + data = scipy.signal.resample( + data, int(len(data) * (samplerate_out / samplerate_in)) + ) + if data.ndim > 1: # convert to mono + data = np.mean(data, axis=1) self.logger.debug(f"Playing sound '{file_path}' at {samplerate_in} Hz") - sd.play( - data, self.get_audio_samplerate(), device=self._device_id, blocking=False - ) + if self._output_stream is not None: + self.push_audio_sample(data) + else: + self.logger.warning("Output stream wasn't open. We are opening it and leaving it open.") + self.start_playing() + self.push_audio_sample(data) - def get_output_device_id(self, name_contains: str) -> int: - """Return the output device id whose name contains the given string (case-insensitive). + def _get_device_id( + self, names_contains: List[str], device_io_type: str = "output" + ) -> int: + """Return the output device id whose name contains the given strings (case-insensitive). + + Args: + names_contains (List[str]): List of strings that should be contained in the device name. + device_io_type (str): 'input' or 'output' to specify device type. If not found, return the default output device id. + """ devices = sd.query_devices() for idx, dev in enumerate(devices): - if name_contains.lower() in dev["name"].lower(): - return idx + for name_contains in names_contains: + if ( + name_contains.lower() in dev["name"].lower() + and dev[f"max_{device_io_type}_channels"] > 0 + ): + return idx # Return default output device if not found self.logger.warning( - f"No output device found containing '{name_contains}', using default." + f"No {device_io_type} device found containing '{names_contains}', using default." ) - return sd.default.device[1] + return self._safe_query_device(device_io_type) + + def _safe_query_device(self, kind: str) -> int: + try: + return int(sd.query_devices(None, kind)["index"]) + except sd.PortAudioError: + return ( + int(sd.default.device[1]) + if kind == "input" + else int(sd.default.device[0]) + ) + except IndexError: + return 0 diff --git a/src/reachy_mini/media/audio_utils.py b/src/reachy_mini/media/audio_utils.py index 0cd3de9b..53dbbf17 100644 --- a/src/reachy_mini/media/audio_utils.py +++ b/src/reachy_mini/media/audio_utils.py @@ -14,12 +14,20 @@ def get_respeaker_card_number() -> int: lines = output.split("\n") for line in lines: - if "ReSpeaker" in line and "card" in line: + if "reachy mini audio" in line.lower() and "card" in line: card_number = line.split(":")[0].split("card ")[1].strip() - logging.debug(f"Found ReSpeaker sound card: {card_number}") + logging.debug(f"Found Reachy Mini Audio sound card: {card_number}") + return int(card_number) + elif "respeaker" in line.lower() and "card" in line: + card_number = line.split(":")[0].split("card ")[1].strip() + logging.warning( + f"Found ReSpeaker sound card: {card_number}. Please update firmware!" + ) return int(card_number) - logging.warning("ReSpeaker sound card not found. Returning default card") + logging.warning( + "Reachy Mini Audio sound card not found. Returning default card" + ) return 0 # default sound card except subprocess.CalledProcessError as e: diff --git a/src/reachy_mini/media/camera_base.py b/src/reachy_mini/media/camera_base.py index 1af00567..53b4a5b6 100644 --- a/src/reachy_mini/media/camera_base.py +++ b/src/reachy_mini/media/camera_base.py @@ -6,36 +6,95 @@ import logging from abc import ABC, abstractmethod -from enum import Enum +from typing import Optional +import numpy as np +import numpy.typing as npt -class CameraBackend(Enum): - """Camera backends.""" - - OPENCV = "opencv" - GSTREAMER = "gstreamer" +from reachy_mini.media.camera_constants import ( + CameraResolution, + CameraSpecs, + MujocoCameraSpecs, +) class CameraBase(ABC): """Abstract class for opening and managing a camera.""" - def __init__(self, backend: CameraBackend, log_level: str = "INFO") -> None: + def __init__( + self, + log_level: str = "INFO", + ) -> None: """Initialize the camera.""" self.logger = logging.getLogger(__name__) self.logger.setLevel(log_level) - self.backend = backend + self._resolution: Optional[CameraResolution] = None + self.camera_specs: Optional[CameraSpecs] = None + self.resized_K: Optional[npt.NDArray[np.float64]] = None + + @property + def resolution(self) -> tuple[int, int]: + """Get the current camera resolution as a tuple (width, height).""" + if self._resolution is None: + raise RuntimeError("Camera resolution is not set.") + return (self._resolution.value[0], self._resolution.value[1]) + + @property + def framerate(self) -> int: + """Get the current camera frames per second.""" + if self._resolution is None: + raise RuntimeError("Camera resolution is not set.") + return int(self._resolution.value[2]) + + @property + def K(self) -> Optional[npt.NDArray[np.float64]]: + """Get the camera intrinsic matrix for the current resolution.""" + return self.resized_K + + @property + def D(self) -> Optional[npt.NDArray[np.float64]]: + """Get the camera distortion coefficients.""" + if self.camera_specs is not None: + return self.camera_specs.D + return None + + def set_resolution(self, resolution: CameraResolution) -> None: + """Set the camera resolution.""" + if self.camera_specs is None: + raise RuntimeError( + "Camera specs not set. Open the camera before setting the resolution." + ) + + if isinstance(self.camera_specs, MujocoCameraSpecs): + raise RuntimeError( + "Cannot change resolution of Mujoco simulated camera for now." + ) + + if resolution not in self.camera_specs.available_resolutions: + raise ValueError( + f"Resolution not supported by the camera. Available resolutions are : {self.camera_specs.available_resolutions}" + ) + + w_ratio = resolution.value[0] / self.camera_specs.default_resolution.value[0] + h_ratio = resolution.value[1] / self.camera_specs.default_resolution.value[1] + self.resized_K = self.camera_specs.K.copy() + + self.resized_K[0, 0] *= w_ratio + self.resized_K[1, 1] *= h_ratio + self.resized_K[0, 2] *= w_ratio + self.resized_K[1, 2] *= h_ratio @abstractmethod - def open(self): + def open(self) -> None: """Open the camera.""" pass @abstractmethod - def read(self): + def read(self) -> Optional[npt.NDArray[np.uint8]]: """Read an image from the camera. Returns the image or None if error.""" pass @abstractmethod - def close(self): + def close(self) -> None: """Close the camera and release resources.""" pass diff --git a/src/reachy_mini/media/camera_constants.py b/src/reachy_mini/media/camera_constants.py new file mode 100644 index 00000000..ec08e902 --- /dev/null +++ b/src/reachy_mini/media/camera_constants.py @@ -0,0 +1,152 @@ +"""Camera constants for Reachy Mini.""" + +from dataclasses import dataclass, field +from enum import Enum +from typing import List + +import numpy as np +import numpy.typing as npt + + +class CameraResolution(Enum): + """Base class for camera resolutions.""" + + R1536x864at40fps = (1536, 864, 40) + + R1280x720at60fps = (1280, 720, 60) + R1280x720at30fps = (1280, 720, 30) + + R1920x1080at30fps = (1920, 1080, 30) + R1920x1080at60fps = (1920, 1080, 60) + + R2304x1296at30fps = (2304, 1296, 30) + R1600x1200at30fps = (1600, 1200, 30) + + R3264x2448at30fps = (3264, 2448, 30) + R3264x2448at10fps = (3264, 2448, 10) + + R3840x2592at30fps = (3840, 2592, 30) + R3840x2592at10fps = (3840, 2592, 10) + R3840x2160at30fps = (3840, 2160, 30) + R3840x2160at10fps = (3840, 2160, 10) + + R3072x1728at10fps = (3072, 1728, 10) + + R4608x2592at10fps = (4608, 2592, 10) + + +@dataclass +class CameraSpecs: + """Base camera specifications.""" + + name: str = "" + available_resolutions: List[CameraResolution] = field(default_factory=list) + default_resolution: CameraResolution = CameraResolution.R1280x720at30fps + vid = 0 + pid = 0 + K: npt.NDArray[np.float64] = field(default_factory=lambda: np.eye(3)) + D: npt.NDArray[np.float64] = field(default_factory=lambda: np.zeros((5,))) + + +@dataclass +class ArducamSpecs(CameraSpecs): + """Arducam camera specifications.""" + + name = "arducam" + available_resolutions = [ + CameraResolution.R2304x1296at30fps, + CameraResolution.R4608x2592at10fps, + CameraResolution.R1920x1080at30fps, + CameraResolution.R1600x1200at30fps, + CameraResolution.R1280x720at30fps, + ] + default_resolution = CameraResolution.R1280x720at30fps + vid = 0x0C45 + pid = 0x636D + K = np.array([[550.3564, 0.0, 638.0112], [0.0, 549.1653, 364.589], [0.0, 0.0, 1.0]]) + D = np.array([-0.0694, 0.1565, -0.0004, 0.0003, -0.0983]) + + +@dataclass +class ReachyMiniLiteCamSpecs(CameraSpecs): + """Reachy Mini Lite camera specifications.""" + + name = "lite" + available_resolutions = [ + CameraResolution.R1920x1080at60fps, + CameraResolution.R3840x2592at30fps, + CameraResolution.R3840x2160at30fps, + CameraResolution.R3264x2448at30fps, + ] + default_resolution = CameraResolution.R1920x1080at60fps + vid = 0x38FB + pid = 0x1002 + K = np.array( + [ + [821.515, 0.0, 962.241], + [0.0, 820.830, 542.459], + [0.0, 0.0, 1.0], + ] + ) + + D = np.array( + [ + -2.94475669e-02, + 6.00511974e-02, + 3.57813971e-06, + -2.96459394e-04, + -3.79243988e-02, + ] + ) + + +@dataclass +class ReachyMiniWirelessCamSpecs(ReachyMiniLiteCamSpecs): + """Reachy Mini Wireless camera specifications.""" + + name = "wireless" + available_resolutions = [ + CameraResolution.R1920x1080at30fps, + CameraResolution.R1280x720at60fps, + CameraResolution.R3840x2592at10fps, + CameraResolution.R3840x2160at10fps, + CameraResolution.R3264x2448at10fps, + CameraResolution.R3072x1728at10fps, + ] + default_resolution = CameraResolution.R1920x1080at30fps + + +@dataclass +class OlderRPiCamSpecs(ReachyMiniLiteCamSpecs): + """Older Raspberry Pi camera specifications. Keeping for compatibility.""" + + name = "older_rpi" + vid = 0x1BCF + pid = 0x28C4 + + +@dataclass +class MujocoCameraSpecs(CameraSpecs): + """Mujoco simulated camera specifications.""" + + available_resolutions = [ + CameraResolution.R1280x720at60fps, + ] + default_resolution = CameraResolution.R1280x720at60fps + # ideal camera matrix + K = np.array( + [ + [ + CameraResolution.R1280x720at60fps.value[0], + 0.0, + CameraResolution.R1280x720at60fps.value[0] / 2, + ], + [ + 0.0, + CameraResolution.R1280x720at60fps.value[1], + CameraResolution.R1280x720at60fps.value[1] / 2, + ], + [0.0, 0.0, 1.0], + ] + ) + D = np.zeros((5,)) # no distortion diff --git a/src/reachy_mini/media/camera_gstreamer.py b/src/reachy_mini/media/camera_gstreamer.py index 433f99a7..3d0dae75 100644 --- a/src/reachy_mini/media/camera_gstreamer.py +++ b/src/reachy_mini/media/camera_gstreamer.py @@ -5,7 +5,18 @@ """ from threading import Thread -from typing import Optional +from typing import Optional, Tuple, cast + +import numpy as np +import numpy.typing as npt + +from reachy_mini.media.camera_constants import ( + ArducamSpecs, + CameraResolution, + CameraSpecs, + ReachyMiniLiteCamSpecs, + ReachyMiniWirelessCamSpecs, +) try: import gi @@ -21,39 +32,69 @@ from gi.repository import GLib, Gst, GstApp # noqa: E402 -from .camera_base import CameraBackend, CameraBase # noqa: E402 +from .camera_base import CameraBase # noqa: E402 class GStreamerCamera(CameraBase): """Camera implementation using GStreamer.""" - def __init__(self, log_level: str = "INFO") -> None: + def __init__( + self, + log_level: str = "INFO", + ) -> None: """Initialize the GStreamer camera.""" - super().__init__(backend=CameraBackend.GSTREAMER, log_level=log_level) + super().__init__(log_level=log_level) Gst.init(None) self._loop = GLib.MainLoop() self._thread_bus_calls: Optional[Thread] = None self.pipeline = Gst.Pipeline.new("camera_recorder") + cam_path, self.camera_specs = self.get_video_device() + + if self.camera_specs is None: + raise RuntimeError("Camera specs not set") + self._resolution = self.camera_specs.default_resolution + self.resized_K = self.camera_specs.K + + if self._resolution is None: + raise RuntimeError("Failed to get default camera resolution.") + + # note for some applications the jpeg image could be directly used self._appsink_video: GstApp = Gst.ElementFactory.make("appsink") - caps_video = Gst.Caps.from_string( - "image/jpeg, width=1280, height=720, framerate=30/1" - ) - self._appsink_video.set_property("caps", caps_video) + self.set_resolution(self._resolution) self._appsink_video.set_property("drop", True) # avoid overflow self._appsink_video.set_property("max-buffers", 1) # keep last image only self.pipeline.add(self._appsink_video) - cam_path = self.get_arducam_video_device() if cam_path == "": self.logger.warning("Recording pipeline set without camera.") self.pipeline.remove(self._appsink_video) + elif cam_path == "imx708": + camsrc = Gst.ElementFactory.make("libcamerasrc") + self.pipeline.add(camsrc) + queue = Gst.ElementFactory.make("queue") + self.pipeline.add(queue) + videoconvert = Gst.ElementFactory.make("videoconvert") + self.pipeline.add(videoconvert) + camsrc.link(queue) + queue.link(videoconvert) + videoconvert.link(self._appsink_video) else: camsrc = Gst.ElementFactory.make("v4l2src") camsrc.set_property("device", cam_path) self.pipeline.add(camsrc) - camsrc.link(self._appsink_video) + queue = Gst.ElementFactory.make("queue") + self.pipeline.add(queue) + # use vaapijpegdec or nvjpegdec for hardware acceleration + jpegdec = Gst.ElementFactory.make("jpegdec") + self.pipeline.add(jpegdec) + videoconvert = Gst.ElementFactory.make("videoconvert") + self.pipeline.add(videoconvert) + camsrc.link(queue) + queue.link(jpegdec) + jpegdec.link(videoconvert) + videoconvert.link(self._appsink_video) def _on_bus_message(self, bus: Gst.Bus, msg: Gst.Message, loop) -> bool: # type: ignore[no-untyped-def] t = msg.type @@ -72,17 +113,33 @@ def _handle_bus_calls(self) -> None: self.logger.debug("starting bus message loop") bus = self.pipeline.get_bus() bus.add_watch(GLib.PRIORITY_DEFAULT, self._on_bus_message, self._loop) - self._loop.run() # type: ignore[no-untyped-call] + self._loop.run() bus.remove_watch() self.logger.debug("bus message loop stopped") - def open(self): + def set_resolution(self, resolution: CameraResolution) -> None: + """Set the camera resolution.""" + super().set_resolution(resolution) + + # Check if pipeline is not playing before changing resolution + if self.pipeline.get_state(0).state == Gst.State.PLAYING: + raise RuntimeError( + "Cannot change resolution while the camera is streaming. Please close the camera first." + ) + + self._resolution = resolution + caps_video = Gst.Caps.from_string( + f"video/x-raw,format=BGR, width={self._resolution.value[0]},height={self._resolution.value[1]},framerate={self.framerate}/1" + ) + self._appsink_video.set_property("caps", caps_video) + + def open(self) -> None: """Open the camera using GStreamer.""" self.pipeline.set_state(Gst.State.PLAYING) self._thread_bus_calls = Thread(target=self._handle_bus_calls, daemon=True) self._thread_bus_calls.start() - def _get_sample(self, appsink): + def _get_sample(self, appsink: GstApp.AppSink) -> Optional[bytes]: sample = appsink.try_pull_sample(20_000_000) if sample is None: return None @@ -95,22 +152,29 @@ def _get_sample(self, appsink): data = buf.extract_dup(0, buf.get_size()) return data - def read(self) -> Optional[bytes]: + def read(self) -> Optional[npt.NDArray[np.uint8]]: """Read a frame from the camera. Returns the frame or None if error. Returns: - Optional[bytes]: The captured frame in JPEG format, or None if error. + Optional[npt.NDArray[np.uint8]]: The captured BGR frame as a NumPy array, or None if error. """ - return self._get_sample(self._appsink_video) + data = self._get_sample(self._appsink_video) + if data is None: + return None + + arr = np.frombuffer(data, dtype=np.uint8).reshape( + (self.resolution[1], self.resolution[0], 3) + ) + return arr - def close(self): + def close(self) -> None: """Release the camera resource.""" self._loop.quit() self.pipeline.set_state(Gst.State.NULL) - def get_arducam_video_device(self) -> str: - """Use Gst.DeviceMonitor to find the unix camera path /dev/videoX of the Arducam_12MP webcam. + def get_video_device(self) -> Tuple[str, Optional[CameraSpecs]]: + """Use Gst.DeviceMonitor to find the unix camera path /dev/videoX. Returns the device path (e.g., '/dev/video2'), or '' if not found. """ @@ -118,16 +182,30 @@ def get_arducam_video_device(self) -> str: monitor.add_filter("Video/Source") monitor.start() + cam_names = ["Reachy", "Arducam_12MP", "imx708"] + devices = monitor.get_devices() - for device in devices: - name = device.get_display_name() - device_props = device.get_properties() - if name and "Arducam_12MP" in name: - if device_props and device_props.has_field("api.v4l2.path"): - device_path = device_props.get_string("api.v4l2.path") - self.logger.debug(f"Found Arducam_12MP at {device_path}") - monitor.stop() - return device_path + for cam_name in cam_names: + for device in devices: + name = device.get_display_name() + device_props = device.get_properties() + + if cam_name in name: + if device_props and device_props.has_field("api.v4l2.path"): + device_path = device_props.get_string("api.v4l2.path") + camera_specs = ( + cast(CameraSpecs, ArducamSpecs) + if cam_name == "Arducam_12MP" + else cast(CameraSpecs, ReachyMiniLiteCamSpecs) + ) + self.logger.debug(f"Found {cam_name} camera at {device_path}") + monitor.stop() + return str(device_path), camera_specs + elif cam_name == "imx708": + camera_specs = cast(CameraSpecs, ReachyMiniWirelessCamSpecs) + self.logger.debug(f"Found {cam_name} camera") + monitor.stop() + return cam_name, camera_specs monitor.stop() - self.logger.warning("Arducam_12MP webcam not found.") - return "" + self.logger.warning("No camera found.") + return "", None diff --git a/src/reachy_mini/media/camera_opencv.py b/src/reachy_mini/media/camera_opencv.py index ee5164b8..03b75c85 100644 --- a/src/reachy_mini/media/camera_opencv.py +++ b/src/reachy_mini/media/camera_opencv.py @@ -3,41 +3,86 @@ This module provides an implementation of the CameraBase class using OpenCV. """ +from typing import Optional, cast + import cv2 +import numpy as np +import numpy.typing as npt +from reachy_mini.media.camera_constants import ( + CameraResolution, + CameraSpecs, + MujocoCameraSpecs, +) from reachy_mini.media.camera_utils import find_camera -from .camera_base import CameraBackend, CameraBase +from .camera_base import CameraBase class OpenCVCamera(CameraBase): """Camera implementation using OpenCV.""" - def __init__(self, log_level: str = "INFO") -> None: + def __init__( + self, + log_level: str = "INFO", + ) -> None: """Initialize the OpenCV camera.""" - super().__init__(backend=CameraBackend.OPENCV, log_level=log_level) - self.cap = None + super().__init__(log_level=log_level) + self.cap: Optional[cv2.VideoCapture] = None + + def set_resolution(self, resolution: CameraResolution) -> None: + """Set the camera resolution.""" + super().set_resolution(resolution) + + self._resolution = resolution + if self.cap is not None: + self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, self._resolution.value[0]) + self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, self._resolution.value[1]) - def open(self, udp_camera: str = None): + def open(self, udp_camera: Optional[str] = None) -> None: """Open the camera using OpenCV VideoCapture.""" if udp_camera: self.cap = cv2.VideoCapture(udp_camera) + self.camera_specs = cast(CameraSpecs, MujocoCameraSpecs) + self._resolution = self.camera_specs.default_resolution else: - self.cap = find_camera() + self.cap, self.camera_specs = find_camera() + if self.cap is None or self.camera_specs is None: + raise RuntimeError("Camera not found") + + self._resolution = self.camera_specs.default_resolution + if self._resolution is None: + raise RuntimeError("Failed to get default camera resolution.") + + self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, self._resolution.value[0]) + self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, self._resolution.value[1]) + + self.resized_K = self.camera_specs.K if not self.cap.isOpened(): raise RuntimeError("Failed to open camera") - def read(self): - """Read a frame from the camera. Returns the frame or None if error.""" + def read(self) -> Optional[npt.NDArray[np.uint8]]: + """Read a frame from the camera. + + Returns: + The frame as a uint8 numpy array, or None if no frame could be read. + + Raises: + RuntimeError: If the camera is not opened. + + """ if self.cap is None: raise RuntimeError("Camera is not opened.") ret, frame = self.cap.read() if not ret: return None - return frame + # Ensure uint8 dtype + if frame.dtype != np.uint8: + frame = frame.astype(np.uint8, copy=False) + return cast(npt.NDArray[np.uint8], frame) - def close(self): + def close(self) -> None: """Release the camera resource.""" if self.cap is not None: self.cap.release() diff --git a/src/reachy_mini/media/camera_utils.py b/src/reachy_mini/media/camera_utils.py index 9825e1a4..22ba6a7c 100644 --- a/src/reachy_mini/media/camera_utils.py +++ b/src/reachy_mini/media/camera_utils.py @@ -1,21 +1,73 @@ """Camera utility for Reachy Mini.""" import platform +from typing import Optional, Tuple, cast import cv2 from cv2_enumerate_cameras import enumerate_cameras +from reachy_mini.media.camera_constants import ( + ArducamSpecs, + CameraSpecs, + OlderRPiCamSpecs, + ReachyMiniLiteCamSpecs, +) + def find_camera( - vid: int = 0x0C45, - pid: int = 0x636D, + apiPreference: int = cv2.CAP_ANY, no_cap: bool = False +) -> Tuple[Optional[cv2.VideoCapture], Optional[CameraSpecs]]: + """Find and return the Reachy Mini camera. + + Looks for the Reachy Mini camera first, then Arducam, then older Raspberry Pi Camera. Returns None if no camera is found. + + Args: + apiPreference (int): Preferred API backend for the camera. Default is cv2.CAP_ANY. + no_cap (bool): If True, close the camera after finding it. Default is False. + + Returns: + cv2.VideoCapture | None: A VideoCapture object if the camera is found and opened successfully, otherwise None. + + """ + cap = find_camera_by_vid_pid( + ReachyMiniLiteCamSpecs.vid, ReachyMiniLiteCamSpecs.pid, apiPreference + ) + if cap is not None: + fourcc = cv2.VideoWriter_fourcc("M", "J", "P", "G") # type: ignore + cap.set(cv2.CAP_PROP_FOURCC, fourcc) + if no_cap: + cap.release() + return cap, cast(CameraSpecs, ReachyMiniLiteCamSpecs) + + cap = find_camera_by_vid_pid( + OlderRPiCamSpecs.vid, OlderRPiCamSpecs.pid, apiPreference + ) + if cap is not None: + fourcc = cv2.VideoWriter_fourcc("M", "J", "P", "G") # type: ignore + cap.set(cv2.CAP_PROP_FOURCC, fourcc) + if no_cap: + cap.release() + return cap, cast(CameraSpecs, OlderRPiCamSpecs) + + cap = find_camera_by_vid_pid(ArducamSpecs.vid, ArducamSpecs.pid, apiPreference) + if cap is not None: + if no_cap: + cap.release() + return cap, cast(CameraSpecs, ArducamSpecs) + + return None, None + + +def find_camera_by_vid_pid( + vid: int = ReachyMiniLiteCamSpecs.vid, + pid: int = ReachyMiniLiteCamSpecs.pid, apiPreference: int = cv2.CAP_ANY, ) -> cv2.VideoCapture | None: """Find and return a camera with the specified VID and PID. Args: - vid (int): Vendor ID of the camera. Default is 0x0C45 (Arducam). - pid (int): Product ID of the camera. Default is 0x636D (Arducam). + vid (int): Vendor ID of the camera. Default is ReachyMiniCamera + pid (int): Product ID of the camera. Default is ReachyMiniCamera apiPreference (int): Preferred API backend for the camera. Default is cv2.CAP_ANY. Returns: @@ -40,16 +92,20 @@ def find_camera( if __name__ == "__main__": - cam = find_camera() + from reachy_mini.media.camera_constants import CameraResolution + cam, _ = find_camera() if cam is None: - print("Camera not found") - else: - while True: - ret, frame = cam.read() - if not ret: - print("Failed to grab frame") - break - cv2.imshow("Camera Feed", frame) - if cv2.waitKey(1) & 0xFF == ord("q"): - break + exit("Camera not found") + + cam.set(cv2.CAP_PROP_FRAME_WIDTH, CameraResolution.R1280x720at30fps.value[0]) + cam.set(cv2.CAP_PROP_FRAME_HEIGHT, CameraResolution.R1280x720at30fps.value[1]) + + while True: + ret, frame = cam.read() + if not ret: + print("Failed to grab frame") + break + cv2.imshow("Camera Feed", frame) + if cv2.waitKey(1) & 0xFF == ord("q"): + break diff --git a/src/reachy_mini/media/media_manager.py b/src/reachy_mini/media/media_manager.py index 06c79013..05b43d9d 100644 --- a/src/reachy_mini/media/media_manager.py +++ b/src/reachy_mini/media/media_manager.py @@ -8,6 +8,7 @@ from typing import Optional import numpy as np +import numpy.typing as npt from reachy_mini.media.audio_base import AudioBase from reachy_mini.media.camera_base import CameraBase @@ -22,6 +23,7 @@ class MediaBackend(Enum): DEFAULT = "default" DEFAULT_NO_VIDEO = "default_no_video" GSTREAMER = "gstreamer" + WEBRTC = "webrtc" class MediaManager: @@ -32,24 +34,53 @@ def __init__( backend: MediaBackend = MediaBackend.DEFAULT, log_level: str = "INFO", use_sim: bool = False, + signalling_host: str = "localhost", ) -> None: """Initialize the audio device.""" self.logger = logging.getLogger(__name__) self.logger.setLevel(log_level) self.backend = backend self.camera: Optional[CameraBase] = None - if ( - not backend == MediaBackend.DEFAULT_NO_VIDEO - and not backend == MediaBackend.NO_MEDIA - ): - self._init_camera( - use_sim, log_level - ) # Todo: automatically detect simulation self.audio: Optional[AudioBase] = None - if not backend == MediaBackend.NO_MEDIA: - self._init_audio(log_level) - - def _init_camera(self, use_sim: bool, log_level: str) -> None: + + match backend: + case MediaBackend.NO_MEDIA: + self.logger.info("No media backend selected.") + case MediaBackend.DEFAULT: + self.logger.info("Using default media backend (OpenCV + SoundDevice).") + self._init_camera(use_sim, log_level) + self._init_audio(log_level) + case MediaBackend.DEFAULT_NO_VIDEO: + self.logger.info("Using default media backend (SoundDevice only).") + self._init_audio(log_level) + case MediaBackend.GSTREAMER: + self.logger.info("Using GStreamer media backend.") + self._init_camera(use_sim, log_level) + self._init_audio(log_level) + case MediaBackend.WEBRTC: + self.logger.info("Using WebRTC GStreamer backend.") + self._init_webrtc(log_level, signalling_host, 8443) + self._init_audio(log_level) + case _: + raise NotImplementedError(f"Media backend {backend} not implemented.") + + def close(self) -> None: + """Close the media manager and release resources.""" + if self.camera is not None: + self.camera.close() + if self.audio is not None: + self.audio.stop_recording() + self.audio.stop_playing() + + def __del__(self) -> None: + """Destructor to ensure resources are released.""" + self.close() + + def _init_camera( + self, + use_sim: bool, + log_level: str, + ) -> None: """Initialize the camera.""" self.logger.debug("Initializing camera...") if self.backend == MediaBackend.DEFAULT: @@ -72,11 +103,11 @@ def _init_camera(self, use_sim: bool, log_level: str) -> None: else: raise NotImplementedError(f"Camera backend {self.backend} not implemented.") - def get_frame(self) -> Optional[np.ndarray]: + def get_frame(self) -> Optional[npt.NDArray[np.uint8]]: """Get a frame from the camera. Returns: - Optional[np.ndarray]: The captured frame, or None if the camera is not available. + Optional[npt.NDArray[np.uint8]]: The captured BGR frame, or None if the camera is not available. """ if self.camera is None: @@ -103,6 +134,29 @@ def _init_audio(self, log_level: str) -> None: else: raise NotImplementedError(f"Audio backend {self.backend} not implemented.") + def _init_webrtc( + self, log_level: str, signalling_host: str, signalling_port: int + ) -> None: + """Initialize the WebRTC system (not implemented yet).""" + from gst_signalling.utils import find_producer_peer_id_by_name + + from reachy_mini.media.webrtc_client_gstreamer import GstWebRTCClient + + peer_id = find_producer_peer_id_by_name( + signalling_host, signalling_port, "reachymini" + ) + + webrtc_media: GstWebRTCClient = GstWebRTCClient( + log_level=log_level, + peer_id=peer_id, + signaling_host=signalling_host, + signaling_port=signalling_port, + ) + + self.camera = webrtc_media + self.audio = webrtc_media # GstWebRTCClient handles both audio and video + self.camera.open() + def play_sound(self, sound_file: str) -> None: """Play a sound file. @@ -117,9 +171,12 @@ def play_sound(self, sound_file: str) -> None: def start_recording(self) -> None: """Start recording audio.""" + if self.audio is None: + self.logger.warning("Audio system is not initialized.") + return self.audio.start_recording() - def get_audio_sample(self) -> Optional[np.ndarray]: + def get_audio_sample(self) -> Optional[bytes | npt.NDArray[np.float32]]: """Get an audio sample from the audio device. Returns: @@ -131,17 +188,33 @@ def get_audio_sample(self) -> Optional[np.ndarray]: return None return self.audio.get_audio_sample() - def get_audio_samplerate(self) -> int: - """Get the samplerate of the audio device. + def get_input_audio_samplerate(self) -> int: + """Get the input samplerate of the audio device.""" + if self.audio is None: + self.logger.warning("Audio system is not initialized.") + return -1 + return self.audio.get_input_audio_samplerate() - Returns: - int: The samplerate of the audio device. + def get_output_audio_samplerate(self) -> int: + """Get the output samplerate of the audio device.""" + if self.audio is None: + self.logger.warning("Audio system is not initialized.") + return -1 + return self.audio.get_output_audio_samplerate() - """ + def get_input_channels(self) -> int: + """Get the number of input channels of the audio device.""" if self.audio is None: self.logger.warning("Audio system is not initialized.") return -1 - return self.audio.get_audio_samplerate() + return self.audio.get_input_channels() + + def get_output_channels(self) -> int: + """Get the number of output channels of the audio device.""" + if self.audio is None: + self.logger.warning("Audio system is not initialized.") + return -1 + return self.audio.get_output_channels() def stop_recording(self) -> None: """Stop recording audio.""" @@ -157,16 +230,38 @@ def start_playing(self) -> None: return self.audio.start_playing() - def push_audio_sample(self, data) -> None: + def push_audio_sample(self, data: npt.NDArray[np.float32]) -> None: """Push audio data to the output device. Args: - data: The audio data to push to the output device. + data (npt.NDArray[np.float32]): The audio data to push to the output device (mono format). """ if self.audio is None: self.logger.warning("Audio system is not initialized.") return + + if data.ndim > 2 or data.ndim == 0: + self.logger.warning(f"Audio samples arrays must have at most 2 dimensions and at least 1 dimension, got {data.ndim}") + return + + # Transpose data to match sounddevice channels last convention + if data.ndim == 2 and data.shape[1] > data.shape[0]: + data = data.T + + # Fit data to match output stream channels + output_channels = self.get_output_channels() + + # Mono input to multiple channels output : duplicate to fit + if data.ndim == 1 and output_channels > 1: + data = np.column_stack((data,) * output_channels) + # Lower channels input to higher channels output : reduce to mono and duplicate to fit + elif data.ndim == 2 and data.shape[1] < output_channels: + data = np.column_stack((data[:,0],) * output_channels) + # Higher channels input to lower channels output : crop to fit + elif data.ndim == 2 and data.shape[1] > output_channels: + data = data[:, :output_channels] + self.audio.push_audio_sample(data) def stop_playing(self) -> None: diff --git a/src/reachy_mini/media/webrtc_client_gstreamer.py b/src/reachy_mini/media/webrtc_client_gstreamer.py new file mode 100644 index 00000000..feea77ec --- /dev/null +++ b/src/reachy_mini/media/webrtc_client_gstreamer.py @@ -0,0 +1,288 @@ +"""GStreamer WebRTC client implementation. + +The class is a client for the webrtc server hosted on the Reachy Mini Wireless robot. +""" + +from threading import Thread +from typing import Optional, cast + +import gi +import numpy as np +import numpy.typing as npt + +from reachy_mini.media.audio_base import AudioBase +from reachy_mini.media.camera_base import CameraBase +from reachy_mini.media.camera_constants import ( + CameraResolution, + CameraSpecs, + ReachyMiniWirelessCamSpecs, +) + +gi.require_version("Gst", "1.0") +gi.require_version("GstApp", "1.0") +from gi.repository import GLib, Gst, GstApp # noqa: E402, F401 + + +class GstWebRTCClient(CameraBase, AudioBase): + """GStreamer WebRTC client implementation.""" + + def __init__( + self, + log_level: str = "INFO", + peer_id: str = "", + signaling_host: str = "", + signaling_port: int = 8443, + ): + """Initialize the GStreamer WebRTC client.""" + super().__init__(log_level=log_level) + Gst.init(None) + self._loop = GLib.MainLoop() + self._thread_bus_calls = Thread(target=lambda: self._loop.run(), daemon=True) + self._thread_bus_calls.start() + + self._pipeline_record = Gst.Pipeline.new("audio_recorder") + self._bus_record = self._pipeline_record.get_bus() + self._bus_record.add_watch( + GLib.PRIORITY_DEFAULT, self._on_bus_message, self._loop + ) + + self._appsink_audio = Gst.ElementFactory.make("appsink") + caps = Gst.Caps.from_string( + f"audio/x-raw,rate={self.SAMPLE_RATE},channels=2,format=F32LE,layout=interleaved" + ) + self._appsink_audio.set_property("caps", caps) + self._appsink_audio.set_property("drop", True) # avoid overflow + self._appsink_audio.set_property("max-buffers", 500) + self._pipeline_record.add(self._appsink_audio) + + self.camera_specs = cast(CameraSpecs, ReachyMiniWirelessCamSpecs) + self.set_resolution(CameraResolution.R1920x1080at30fps) + + self._appsink_video = Gst.ElementFactory.make("appsink") + caps_video = Gst.Caps.from_string("video/x-raw,format=BGR") + self._appsink_video.set_property("caps", caps_video) + self._appsink_video.set_property("drop", True) # avoid overflow + self._appsink_video.set_property("max-buffers", 1) # keep last image only + self._pipeline_record.add(self._appsink_video) + + webrtcsrc = self._configure_webrtcsrc(signaling_host, signaling_port, peer_id) + self._pipeline_record.add(webrtcsrc) + + self._pipeline_playback = Gst.Pipeline.new("audio_player") + self._init_pipeline_playback( + self._pipeline_playback, signaling_host, signaling_port + ) + self._bus_playback = self._pipeline_playback.get_bus() + self._bus_playback.add_watch( + GLib.PRIORITY_DEFAULT, self._on_bus_message, self._loop + ) + + def __del__(self) -> None: + """Destructor to ensure gstreamer resources are released.""" + super().__del__() + self._loop.quit() + self._bus_record.remove_watch() + self._bus_playback.remove_watch() + + def set_resolution(self, resolution: CameraResolution) -> None: + """Set the camera resolution.""" + super().set_resolution(resolution) + self._resolution = resolution + + def _configure_webrtcsrc( + self, signaling_host: str, signaling_port: int, peer_id: str + ) -> Gst.Element: + source = Gst.ElementFactory.make("webrtcsrc") + if not source: + raise RuntimeError( + "Failed to create webrtcsrc element. Is the GStreamer webrtc rust plugin installed?" + ) + + source.connect("pad-added", self._webrtcsrc_pad_added_cb) + signaller = source.get_property("signaller") + signaller.set_property("producer-peer-id", peer_id) + signaller.set_property("uri", f"ws://{signaling_host}:{signaling_port}") + return source + + def _configure_webrtcbin(self, webrtcsrc: Gst.Element) -> None: + if isinstance(webrtcsrc, Gst.Bin): + webrtcbin_name = "webrtcbin0" + webrtcbin = webrtcsrc.get_by_name(webrtcbin_name) + assert webrtcbin is not None + # jitterbuffer has a default 200 ms buffer. Should be ok to lower this in localnetwork config + webrtcbin.set_property("latency", 100) + + def _webrtcsrc_pad_added_cb(self, webrtcsrc: Gst.Element, pad: Gst.Pad) -> None: + self._configure_webrtcbin(webrtcsrc) + if pad.get_name().startswith("video"): + queue = Gst.ElementFactory.make("queue") + + videoconvert = Gst.ElementFactory.make("videoconvert") + videoscale = Gst.ElementFactory.make("videoscale") + videorate = Gst.ElementFactory.make("videorate") + + self._pipeline_record.add(queue) + self._pipeline_record.add(videoconvert) + self._pipeline_record.add(videoscale) + self._pipeline_record.add(videorate) + pad.link(queue.get_static_pad("sink")) + + queue.link(videoconvert) + videoconvert.link(videoscale) + videoscale.link(videorate) + videorate.link(self._appsink_video) + + queue.sync_state_with_parent() + videoconvert.sync_state_with_parent() + videoscale.sync_state_with_parent() + videorate.sync_state_with_parent() + self._appsink_video.sync_state_with_parent() + + elif pad.get_name().startswith("audio"): + audioconvert = Gst.ElementFactory.make("audioconvert") + audioresample = Gst.ElementFactory.make("audioresample") + self._pipeline_record.add(audioconvert) + self._pipeline_record.add(audioresample) + + pad.link(audioconvert.get_static_pad("sink")) + audioconvert.link(audioresample) + audioresample.link(self._appsink_audio) + + self._appsink_audio.sync_state_with_parent() + audioconvert.sync_state_with_parent() + audioresample.sync_state_with_parent() + + def _on_bus_message(self, bus: Gst.Bus, msg: Gst.Message, loop) -> bool: # type: ignore[no-untyped-def] + t = msg.type + if t == Gst.MessageType.EOS: + self.logger.warning("End-of-stream") + return False + + elif t == Gst.MessageType.ERROR: + err, debug = msg.parse_error() + self.logger.error(f"Error: {err} {debug}") + return False + + return True + + def open(self) -> None: + """Open the video stream.""" + self._pipeline_record.set_state(Gst.State.PLAYING) + + def _get_sample(self, appsink: GstApp.AppSink) -> Optional[bytes]: + sample = appsink.try_pull_sample(20_000_000) + if sample is None: + return None + data = None + if isinstance(sample, Gst.Sample): + buf = sample.get_buffer() + if buf is None: + self.logger.warning("Buffer is None") + + data = buf.extract_dup(0, buf.get_size()) + return data + + def get_audio_sample(self) -> Optional[npt.NDArray[np.float32]]: + """Read a sample from the audio card. Returns the sample or None if error. + + Returns: + Optional[npt.NDArray[np.float32]]: The captured sample in raw format, or None if error. + + """ + sample = self._get_sample(self._appsink_audio) + if sample is None: + return None + return np.frombuffer(sample, dtype=np.float32).reshape(-1, 2) + + def read(self) -> Optional[npt.NDArray[np.uint8]]: + """Read a frame from the camera. Returns the frame or None if error. + + Returns: + Optional[npt.NDArray[np.uint8]]: The captured frame in BGR format, or None if error. + + """ + data = self._get_sample(self._appsink_video) + if data is None: + return None + + arr = np.frombuffer(data, dtype=np.uint8).reshape( + (self.resolution[1], self.resolution[0], 3) + ) + return arr + + def close(self) -> None: + """Stop the pipeline.""" + # self._loop.quit() + self._pipeline_record.set_state(Gst.State.NULL) + + def start_recording(self) -> None: + """Open the audio card using GStreamer.""" + pass # already started in open() + + def stop_recording(self) -> None: + """Release the camera resource.""" + pass # managed in close() + + def _init_pipeline_playback( + self, pipeline: Gst.Pipeline, signaling_host: str, signaling_port: int + ) -> None: + """Initialize the audio playback pipeline.""" + self._appsrc = Gst.ElementFactory.make("appsrc") + self._appsrc.set_property("format", Gst.Format.TIME) + self._appsrc.set_property("is-live", True) + caps = Gst.Caps.from_string( + f"audio/x-raw,format=F32LE,channels=1,rate={self.SAMPLE_RATE},layout=interleaved" + ) + self._appsrc.set_property("caps", caps) + audioconvert = Gst.ElementFactory.make("audioconvert") + audioresample = Gst.ElementFactory.make("audioresample") + opusenc = Gst.ElementFactory.make("opusenc") + queue = Gst.ElementFactory.make("queue") + rtpopuspay = Gst.ElementFactory.make("rtpopuspay") + udpsink = Gst.ElementFactory.make("udpsink") + + udpsink.set_property("host", signaling_host) + udpsink.set_property("port", 5000) + + pipeline.add(self._appsrc) + pipeline.add(audioconvert) + pipeline.add(audioresample) + pipeline.add(opusenc) + pipeline.add(queue) + pipeline.add(rtpopuspay) + pipeline.add(udpsink) + + self._appsrc.link(audioconvert) + audioconvert.link(audioresample) + audioresample.link(opusenc) + opusenc.link(queue) + queue.link(rtpopuspay) + rtpopuspay.link(udpsink) + + def start_playing(self) -> None: + """Open the audio output using GStreamer.""" + self._pipeline_playback.set_state(Gst.State.PLAYING) + + def stop_playing(self) -> None: + """Stop playing audio and release resources.""" + self._pipeline_playback.set_state(Gst.State.NULL) + + def push_audio_sample(self, data: npt.NDArray[np.float32]) -> None: + """Push audio data to the output device.""" + if self._appsrc is not None: + buf = Gst.Buffer.new_wrapped(data.tobytes()) + self._appsrc.push_buffer(buf) + + else: + self.logger.warning( + "AppSrc is not initialized. Call start_playing() first." + ) + + def play_sound(self, sound_file: str) -> None: + """Play a sound file. + + Args: + sound_file (str): Path to the sound file to play. + + """ + self.logger.warning("Audio playback not implemented in WebRTC client.") diff --git a/src/reachy_mini/media/webrtc_daemon.py b/src/reachy_mini/media/webrtc_daemon.py new file mode 100644 index 00000000..d50d7927 --- /dev/null +++ b/src/reachy_mini/media/webrtc_daemon.py @@ -0,0 +1,271 @@ +"""WebRTC daemon. + +Starts a gstreamer webrtc pipeline to stream video and audio. +""" + +import logging +from threading import Thread + +import gi + +from reachy_mini.media.audio_utils import get_respeaker_card_number +from reachy_mini.media.camera_constants import CameraResolution + +# from reachy_mini.media.camera_utils import get_video_device + +gi.require_version("Gst", "1.0") +gi.require_version("GstApp", "1.0") + +from gi.repository import GLib, Gst # noqa: E402 + + +class GstWebRTC: + """WebRTC pipeline using GStreamer.""" + + def __init__( + self, + log_level: str = "INFO", + resolution: CameraResolution = CameraResolution.R1920x1080at30fps, + ) -> None: + """Initialize the GStreamer WebRTC pipeline.""" + self._logger = logging.getLogger(__name__) + self._logger.setLevel(log_level) + Gst.init(None) + self._loop = GLib.MainLoop() + self._thread_bus_calls = Thread(target=lambda: self._loop.run(), daemon=True) + self._thread_bus_calls.start() + + # _, self.camera_specs = get_video_device(self._logger) + # self._resolution = self.camera_specs.default_resolution + self._resolution = resolution + + self._id_audio_card = get_respeaker_card_number() + + self._pipeline_sender = Gst.Pipeline.new("reachymini_webrtc_sender") + self._bus_sender = self._pipeline_sender.get_bus() + self._bus_sender.add_watch( + GLib.PRIORITY_DEFAULT, self._on_bus_message, self._loop + ) + + webrtcsink = self._configure_webrtc(self._pipeline_sender) + + self._configure_video(self._pipeline_sender, webrtcsink) + self._configure_audio(self._pipeline_sender, webrtcsink) + + self._pipeline_receiver = Gst.Pipeline.new("reachymini_webrtc_receiver") + self._bus_receiver = self._pipeline_receiver.get_bus() + self._bus_receiver.add_watch( + GLib.PRIORITY_DEFAULT, self._on_bus_message, self._loop + ) + self._configure_receiver(self._pipeline_receiver) + + def __del__(self) -> None: + """Destructor to ensure gstreamer resources are released.""" + self._loop.quit() + self._bus_sender.remove_watch() + self._bus_receiver.remove_watch() + + def _configure_webrtc(self, pipeline: Gst.Pipeline) -> Gst.Element: + self._logger.debug("Configuring WebRTC") + webrtcsink = Gst.ElementFactory.make("webrtcsink") + if not webrtcsink: + raise RuntimeError( + "Failed to create webrtcsink element. Is the GStreamer webrtc rust plugin installed?" + ) + + meta_structure = Gst.Structure.new_empty("meta") + meta_structure.set_value("name", "reachymini") + webrtcsink.set_property("meta", meta_structure) + webrtcsink.set_property("run-signalling-server", True) + + pipeline.add(webrtcsink) + + return webrtcsink + + def _configure_receiver(self, pipeline: Gst.Pipeline) -> None: + udpsrc = Gst.ElementFactory.make("udpsrc") + udpsrc.set_property("port", 5000) + caps = Gst.Caps.from_string( + "application/x-rtp,media=audio,encoding-name=OPUS,payload=96" + ) + capsfilter = Gst.ElementFactory.make("capsfilter") + capsfilter.set_property("caps", caps) + rtpjitterbuffer = Gst.ElementFactory.make("rtpjitterbuffer") + rtpjitterbuffer.set_property( + "latency", 200 + ) # configure latency depending on network conditions + rtpopusdepay = Gst.ElementFactory.make("rtpopusdepay") + opusdec = Gst.ElementFactory.make("opusdec") + queue = Gst.ElementFactory.make("queue") + audioconvert = Gst.ElementFactory.make("audioconvert") + audioresample = Gst.ElementFactory.make("audioresample") + alsasink = Gst.ElementFactory.make("alsasink") + alsasink.set_property("device", f"hw:{self._id_audio_card},0") + alsasink.set_property("sync", False) + + pipeline.add(udpsrc) + pipeline.add(capsfilter) + pipeline.add(rtpjitterbuffer) + pipeline.add(rtpopusdepay) + pipeline.add(opusdec) + pipeline.add(queue) + pipeline.add(audioconvert) + pipeline.add(audioresample) + pipeline.add(alsasink) + + udpsrc.link(capsfilter) + capsfilter.link(rtpjitterbuffer) + rtpjitterbuffer.link(rtpopusdepay) + rtpopusdepay.link(opusdec) + opusdec.link(queue) + queue.link(audioconvert) + audioconvert.link(audioresample) + audioresample.link(alsasink) + + @property + def resolution(self) -> tuple[int, int]: + """Get the current camera resolution as a tuple (width, height).""" + return (self._resolution.value[0], self._resolution.value[1]) + + @property + def framerate(self) -> int: + """Get the current camera framerate.""" + return self._resolution.value[2] + + def _configure_video(self, pipeline: Gst.Pipeline, webrtcsink: Gst.Element) -> None: + self._logger.debug("Configuring video") + libcamerasrc = Gst.ElementFactory.make("libcamerasrc") + + caps = Gst.Caps.from_string( + f"video/x-raw,width={self.resolution[0]},height={self.resolution[1]},framerate={self.framerate}/1,format=YUY2,colorimetry=bt709,interlace-mode=progressive" + ) + capsfilter = Gst.ElementFactory.make("capsfilter") + capsfilter.set_property("caps", caps) + queue = Gst.ElementFactory.make("queue") + v4l2h264enc = Gst.ElementFactory.make("v4l2h264enc") + extra_controls_structure = Gst.Structure.new_empty("extra-controls") + extra_controls_structure.set_value("repeat_sequence_header", 1) + extra_controls_structure.set_value("video_bitrate", 5_000_000) + v4l2h264enc.set_property("extra-controls", extra_controls_structure) + caps_h264 = Gst.Caps.from_string( + "video/x-h264,stream-format=byte-stream,alignment=au,level=(string)4" + ) + capsfilter_h264 = Gst.ElementFactory.make("capsfilter") + capsfilter_h264.set_property("caps", caps_h264) + + if not all( + [ + libcamerasrc, + capsfilter, + queue, + v4l2h264enc, + capsfilter_h264, + ] + ): + raise RuntimeError("Failed to create GStreamer video elements") + + pipeline.add(libcamerasrc) + pipeline.add(capsfilter) + pipeline.add(queue) + pipeline.add(v4l2h264enc) + pipeline.add(capsfilter_h264) + + libcamerasrc.link(capsfilter) + capsfilter.link(queue) + queue.link(v4l2h264enc) + v4l2h264enc.link(capsfilter_h264) + capsfilter_h264.link(webrtcsink) + + def _configure_audio(self, pipeline: Gst.Pipeline, webrtcsink: Gst.Element) -> None: + self._logger.debug("Configuring audio") + alsasrc = Gst.ElementFactory.make("alsasrc") + alsasrc.set_property("device", f"hw:{self._id_audio_card},0") + queue = Gst.ElementFactory.make("queue") + audioconvert = Gst.ElementFactory.make("audioconvert") + audioresample = Gst.ElementFactory.make("audioresample") + opusenc = Gst.ElementFactory.make("opusenc") + opusenc.set_property("audio-type", "restricted-lowdelay") + caps = Gst.Caps.from_string("audio/x-opus,channels=1,rate=48000") + capsfilter = Gst.ElementFactory.make("capsfilter") + capsfilter.set_property("caps", caps) + + if not all( + [ + alsasrc, + queue, + audioconvert, + audioresample, + opusenc, + capsfilter, + ] + ): + raise RuntimeError("Failed to create GStreamer audio elements") + + pipeline.add(alsasrc) + pipeline.add(queue) + pipeline.add(audioconvert) + pipeline.add(audioresample) + pipeline.add(opusenc) + pipeline.add(capsfilter) + + alsasrc.link(queue) + queue.link(audioconvert) + audioconvert.link(audioresample) + audioresample.link(opusenc) + opusenc.link(capsfilter) + capsfilter.link(webrtcsink) + + def _on_bus_message(self, bus: Gst.Bus, msg: Gst.Message, loop) -> bool: # type: ignore[no-untyped-def] + t = msg.type + if t == Gst.MessageType.EOS: + self._logger.warning("End-of-stream") + return False + + elif t == Gst.MessageType.ERROR: + err, debug = msg.parse_error() + self._logger.error(f"Error: {err} {debug}") + return False + + else: + # self._logger.warning(f"Unhandled message type: {t}") + pass + + return True + + def start(self) -> None: + """Start the WebRTC pipeline.""" + self._logger.debug("Starting WebRTC") + self._pipeline_sender.set_state(Gst.State.PLAYING) + self._pipeline_receiver.set_state(Gst.State.PLAYING) + + def pause(self) -> None: + """Pause the WebRTC pipeline.""" + self._logger.debug("Pausing WebRTC") + self._pipeline_sender.set_state(Gst.State.PAUSED) + self._pipeline_receiver.set_state(Gst.State.PAUSED) + + def stop(self) -> None: + """Stop the WebRTC pipeline.""" + self._logger.debug("Stopping WebRTC") + + self._pipeline_sender.set_state(Gst.State.NULL) + self._pipeline_receiver.set_state(Gst.State.NULL) + + +if __name__ == "__main__": + import time + + logging.basicConfig( + level=logging.DEBUG, + format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", + ) + + webrtc = GstWebRTC(log_level="DEBUG") + webrtc.start() + try: + while True: + time.sleep(1) + except KeyboardInterrupt: + logging.info("User interrupted") + finally: + webrtc.stop() diff --git a/src/reachy_mini/motion/__init__.py b/src/reachy_mini/motion/__init__.py index 427474d2..79c18705 100644 --- a/src/reachy_mini/motion/__init__.py +++ b/src/reachy_mini/motion/__init__.py @@ -2,5 +2,3 @@ This module contains both utilities to create and play moves, as well as utilities to download datasets of recorded moves. """ - -from reachy_mini.motion.move import Move # noqa: F401 diff --git a/src/reachy_mini/motion/goto.py b/src/reachy_mini/motion/goto.py index ddf77832..ddb9e5cb 100644 --- a/src/reachy_mini/motion/goto.py +++ b/src/reachy_mini/motion/goto.py @@ -1,6 +1,7 @@ """A goto move to a target head pose and/or antennas position.""" -from numpy import ndarray +import numpy as np +import numpy.typing as npt from reachy_mini.utils.interpolation import ( InterpolationTechnique, @@ -16,10 +17,10 @@ class GotoMove(Move): def __init__( self, - start_head_pose: ndarray, - target_head_pose: ndarray | None, - start_antennas: ndarray, - target_antennas: ndarray | None, + start_head_pose: npt.NDArray[np.float64], + target_head_pose: npt.NDArray[np.float64] | None, + start_antennas: npt.NDArray[np.float64], + target_antennas: npt.NDArray[np.float64] | None, start_body_yaw: float, target_body_yaw: float | None, duration: float, @@ -47,7 +48,11 @@ def duration(self) -> float: """Duration of the goto in seconds.""" return self._duration - def evaluate(self, t: float) -> tuple[ndarray | None, ndarray | None, float | None]: + def evaluate( + self, t: float + ) -> tuple[ + npt.NDArray[np.float64] | None, npt.NDArray[np.float64] | None, float | None + ]: """Evaluate the goto at time t.""" interp_time = time_trajectory(t / self.duration, method=self.method) diff --git a/src/reachy_mini/motion/move.py b/src/reachy_mini/motion/move.py index 957e4b37..f6481384 100644 --- a/src/reachy_mini/motion/move.py +++ b/src/reachy_mini/motion/move.py @@ -3,6 +3,7 @@ from abc import ABC, abstractmethod import numpy as np +import numpy.typing as npt class Move(ABC): @@ -18,7 +19,9 @@ def duration(self) -> float: def evaluate( self, t: float, - ) -> tuple[np.ndarray | None, np.ndarray | None, float | None]: + ) -> tuple[ + npt.NDArray[np.float64] | None, npt.NDArray[np.float64] | None, float | None + ]: """Evaluate the move at time t, typically called at a high-frequency (eg. 100Hz). Arguments: diff --git a/src/reachy_mini/motion/recorded_move.py b/src/reachy_mini/motion/recorded_move.py index 3e5e421e..48304047 100644 --- a/src/reachy_mini/motion/recorded_move.py +++ b/src/reachy_mini/motion/recorded_move.py @@ -2,74 +2,46 @@ import json from glob import glob from pathlib import Path +from typing import Any, Dict, List import numpy as np +import numpy.typing as npt from huggingface_hub import snapshot_download +from reachy_mini.motion.move import Move from reachy_mini.utils.interpolation import linear_pose_interpolation -from . import Move - -def lerp(v0, v1, alpha): +def lerp(v0: float, v1: float, alpha: float) -> float: """Linear interpolation between two values.""" return v0 + alpha * (v1 - v0) -class RecordedMoves: - """Load a library of recorded moves from a HuggingFace dataset.""" - - def __init__(self, hf_dataset_name: str): - """Initialize RecordedMoves.""" - self.hf_dataset_name = hf_dataset_name - self.local_path = snapshot_download(self.hf_dataset_name, repo_type="dataset") - self.moves = {} - - self.process() - - def process(self): - """Populate recorded moves and sounds.""" - move_paths = glob(f"{self.local_path}/*.json") - move_paths = [Path(move_path) for move_path in move_paths] - for move_path in move_paths: - move_name = move_path.stem - - move = json.load(open(move_path, "r")) - self.moves[move_name] = move - - def get(self, move_name): - """Get a recorded move by name.""" - if move_name not in self.moves: - raise ValueError( - f"Move {move_name} not found in recorded moves library {self.hf_dataset_name}" - ) - - return RecordedMove(self.moves[move_name]) - - def list_moves(self): - """List all moves in the loaded library.""" - return list(self.moves.keys()) - - class RecordedMove(Move): """Represent a recorded move.""" - def __init__(self, move): + def __init__(self, move: Dict[str, Any]) -> None: """Initialize RecordedMove.""" self.move = move - self.description = self.move["description"] - self.timestamps = self.move["time"] - self.trajectory = self.move["set_target_data"] + self.description: str = self.move["description"] + self.timestamps: List[float] = self.move["time"] + self.trajectory: List[Dict[str, List[List[float]] | List[float] | float]] = ( + self.move["set_target_data"] + ) - self.dt = (self.timestamps[-1] - self.timestamps[0]) / len(self.timestamps) + self.dt: float = (self.timestamps[-1] - self.timestamps[0]) / len( + self.timestamps + ) @property def duration(self) -> float: """Get the duration of the recorded move.""" return len(self.trajectory) * self.dt - def evaluate(self, t: float) -> tuple[np.ndarray, np.ndarray, float]: + def evaluate( + self, t: float + ) -> tuple[npt.NDArray[np.float64], npt.NDArray[np.float64], float]: """Evaluate the move at time t. Returns: @@ -98,25 +70,63 @@ def evaluate(self, t: float) -> tuple[np.ndarray, np.ndarray, float]: else: alpha = (t - t_prev) / (t_next - t_prev) - head_prev = np.array(self.trajectory[idx_prev]["head"]) - head_next = np.array(self.trajectory[idx_next]["head"]) - antennas_prev = self.trajectory[idx_prev]["antennas"] - antennas_next = self.trajectory[idx_next]["antennas"] - body_yaw_prev = self.trajectory[idx_prev].get("body_yaw", 0.0) - body_yaw_next = self.trajectory[idx_next].get("body_yaw", 0.0) + head_prev = np.array(self.trajectory[idx_prev]["head"], dtype=np.float64) + head_next = np.array(self.trajectory[idx_next]["head"], dtype=np.float64) + antennas_prev: List[float] = self.trajectory[idx_prev]["antennas"] # type: ignore[assignment] + antennas_next: List[float] = self.trajectory[idx_next]["antennas"] # type: ignore[assignment] + body_yaw_prev: float = self.trajectory[idx_prev].get("body_yaw", 0.0) # type: ignore[assignment] + body_yaw_next: float = self.trajectory[idx_next].get("body_yaw", 0.0) # type: ignore[assignment] # check_collision = self.trajectory[idx_prev].get("check_collision", False) # Interpolate to infer a better position at the current time. # Joint interpolations are easy: + antennas_joints = np.array( [ lerp(pos_prev, pos_next, alpha) for pos_prev, pos_next in zip(antennas_prev, antennas_next) - ] + ], + dtype=np.float64, ) + body_yaw = lerp(body_yaw_prev, body_yaw_next, alpha) # Head position interpolation is more complex: head_pose = linear_pose_interpolation(head_prev, head_next, alpha) return head_pose, antennas_joints, body_yaw + + +class RecordedMoves: + """Load a library of recorded moves from a HuggingFace dataset.""" + + def __init__(self, hf_dataset_name: str): + """Initialize RecordedMoves.""" + self.hf_dataset_name = hf_dataset_name + self.local_path = snapshot_download(self.hf_dataset_name, repo_type="dataset") + self.moves: Dict[str, Any] = {} + + self.process() + + def process(self) -> None: + """Populate recorded moves and sounds.""" + move_paths_tmp = glob(f"{self.local_path}/*.json") + move_paths = [Path(move_path) for move_path in move_paths_tmp] + for move_path in move_paths: + move_name = move_path.stem + + move = json.load(open(move_path, "r")) + self.moves[move_name] = move + + def get(self, move_name: str) -> RecordedMove: + """Get a recorded move by name.""" + if move_name not in self.moves: + raise ValueError( + f"Move {move_name} not found in recorded moves library {self.hf_dataset_name}" + ) + + return RecordedMove(self.moves[move_name]) + + def list_moves(self) -> List[str]: + """List all moves in the loaded library.""" + return list(self.moves.keys()) diff --git a/src/reachy_mini/reachy_mini.py b/src/reachy_mini/reachy_mini.py index f9fc8574..711a5d8b 100644 --- a/src/reachy_mini/reachy_mini.py +++ b/src/reachy_mini/reachy_mini.py @@ -14,12 +14,13 @@ import cv2 import numpy as np +import numpy.typing as npt from asgiref.sync import async_to_sync from scipy.spatial.transform import Rotation as R from reachy_mini.daemon.utils import daemon_check -from reachy_mini.io import Client from reachy_mini.io.protocol import GotoTaskRequest +from reachy_mini.io.zenoh_client import ZenohClient from reachy_mini.media.media_manager import MediaBackend, MediaManager from reachy_mini.motion.move import Move from reachy_mini.utils.interpolation import InterpolationTechnique, minimum_jerk @@ -38,7 +39,7 @@ ] -SLEEP_ANTENNAS_JOINT_POSITIONS = [3.05, -3.05] +SLEEP_ANTENNAS_JOINT_POSITIONS = [-3.05, 3.05] SLEEP_HEAD_POSE = np.array( [ [0.911, 0.004, 0.413, -0.021], @@ -48,8 +49,6 @@ ] ) -IMAGE_SIZE = (1280, 720) # Width, Height in pixels - class ReachyMini: """Reachy Mini class for controlling a simulated or real Reachy Mini robot. @@ -65,9 +64,9 @@ def __init__( self, localhost_only: bool = True, spawn_daemon: bool = False, - use_sim: bool = True, + use_sim: bool = False, timeout: float = 5.0, - automatic_body_yaw: bool = False, + automatic_body_yaw: bool = True, log_level: str = "INFO", media_backend: str = "default", ) -> None: @@ -80,7 +79,7 @@ def __init__( timeout (float): Timeout for the client connection, defaults to 5.0 seconds. automatic_body_yaw (bool): If True, the body yaw will be used to compute the IK and FK. Default is False. log_level (str): Logging level, defaults to "INFO". - media_backend (str): Media backend to use, either "default" (OpenCV) or "gstreamer", defaults to "default". + media_backend (str): Media backend to use, either "default" (OpenCV), "gstreamer" or "webrtc", defaults to "default". It will try to connect to the daemon, and if it fails, it will raise an exception. @@ -88,17 +87,12 @@ def __init__( self.logger = logging.getLogger(__name__) self.logger.setLevel(log_level) daemon_check(spawn_daemon, use_sim) - self.client = Client(localhost_only) + self.client = ZenohClient(localhost_only) self.client.wait_for_connection(timeout=timeout) self.set_automatic_body_yaw(automatic_body_yaw) - self._last_head_pose = None + self._last_head_pose: Optional[npt.NDArray[np.float64]] = None self.is_recording = False - self.K = np.array( - [[550.3564, 0.0, 638.0112], [0.0, 549.1653, 364.589], [0.0, 0.0, 1.0]] - ) - self.D = np.array([-0.0694, 0.1565, -0.0004, 0.0003, -0.0983]) - self.T_head_cam = np.eye(4) self.T_head_cam[:3, 3][:] = [0.0437, 0, 0.0512] self.T_head_cam[:3, :3] = np.array( @@ -109,25 +103,9 @@ def __init__( ] ) - mbackend = MediaBackend.DEFAULT - if media_backend.lower() == "gstreamer": - mbackend = MediaBackend.GSTREAMER - elif media_backend.lower() == "default": - mbackend = MediaBackend.DEFAULT - elif media_backend.lower() == "no_media": - mbackend = MediaBackend.NO_MEDIA - elif media_backend.lower() == "default_no_video": - mbackend = MediaBackend.DEFAULT_NO_VIDEO - else: - raise ValueError( - f"Invalid media_backend '{media_backend}'. Supported values are 'default', 'gstreamer', 'no_media', and 'default_no_video'." - ) - - self.media_manager = MediaManager( - use_sim=use_sim, backend=mbackend, log_level=log_level - ) + self.media_manager = self._configure_mediamanager(media_backend, log_level) - def __del__(self): + def __del__(self) -> None: """Destroy the Reachy Mini instance. The client is disconnected explicitly to avoid a thread pending issue. @@ -139,8 +117,9 @@ def __enter__(self) -> "ReachyMini": """Context manager entry point for Reachy Mini.""" return self - def __exit__(self, exc_type, exc_value, traceback) -> None: + def __exit__(self, exc_type, exc_value, traceback) -> None: # type: ignore [no-untyped-def] """Context manager exit point for Reachy Mini.""" + self.media_manager.close() self.client.disconnect() @property @@ -148,13 +127,46 @@ def media(self) -> MediaManager: """Expose the MediaManager instance used by ReachyMini.""" return self.media_manager + def _configure_mediamanager( + self, media_backend: str, log_level: str + ) -> MediaManager: + mbackend = MediaBackend.DEFAULT + match media_backend.lower(): + case "webrtc": + if self.client.get_status()["wireless_version"]: + mbackend = MediaBackend.WEBRTC + else: + self.logger.warning( + "Non-wireless version detected, daemon should use the flag '--wireless-version'. Reverting to default" + ) + mbackend = MediaBackend.DEFAULT + case "gstreamer": + mbackend = MediaBackend.GSTREAMER + case "default": + mbackend = MediaBackend.DEFAULT + case "no_media": + mbackend = MediaBackend.NO_MEDIA + case "default_no_video": + mbackend = MediaBackend.DEFAULT_NO_VIDEO + case _: + raise ValueError( + f"Invalid media_backend '{media_backend}'. Supported values are 'default', 'gstreamer', 'no_media', 'default_no_video', and 'webrtc'." + ) + + return MediaManager( + use_sim=self.client.get_status()["simulation_enabled"], + backend=mbackend, + log_level=log_level, + signalling_host=self.client.get_status()["wlan_ip"], + ) + def set_target( self, - head: Optional[np.ndarray] = None, # 4x4 pose matrix + head: Optional[npt.NDArray[np.float64]] = None, # 4x4 pose matrix antennas: Optional[ - Union[np.ndarray, List[float]] - ] = None, # [left_angle, right_angle] (in rads) - body_yaw: float = 0.0, # Body yaw angle in radians + Union[npt.NDArray[np.float64], List[float]] + ] = None, # [right_angle, left_angle] (in rads) + body_yaw: Optional[float] = None, # Body yaw angle in radians ) -> None: """Set the target pose of the head and/or the target position of the antennas. @@ -167,8 +179,10 @@ def set_target( ValueError: If neither head nor antennas are provided, or if the shape of head is not (4, 4), or if antennas is not a 1D array with two elements. """ - if head is None and antennas is None: - raise ValueError("At least one of head or antennas must be provided.") + if head is None and antennas is None and body_yaw is None: + raise ValueError( + "At least one of head, antennas or body_yaw must be provided." + ) if head is not None and not head.shape == (4, 4): raise ValueError(f"Head pose must be a 4x4 matrix, got shape {head.shape}.") @@ -178,32 +192,45 @@ def set_target( "Antennas must be a list or 1D np array with two elements." ) - if antennas is not None: - self._set_joint_positions( - antennas_joint_positions=list(antennas), - ) + if body_yaw is not None and not isinstance(body_yaw, (int, float)): + raise ValueError("body_yaw must be a float.") + if head is not None: - self.set_target_head_pose(head, body_yaw) + self.set_target_head_pose(head) + + if antennas is not None: + self.set_target_antenna_joint_positions(list(antennas)) + # self._set_joint_positions( + # antennas_joint_positions=list(antennas), + # ) + + if body_yaw is not None: + self.set_target_body_yaw(body_yaw) + self._last_head_pose = head - record = { + record: Dict[str, float | List[float] | List[List[float]]] = { "time": time.time(), - "head": head.tolist() if head is not None else None, - "antennas": list(antennas) if antennas is not None else None, - "body_yaw": body_yaw, + "body_yaw": body_yaw if body_yaw is not None else 0.0, } + if head is not None: + record["head"] = head.tolist() + if antennas is not None: + record["antennas"] = list(antennas) + if body_yaw is not None: + record["body_yaw"] = body_yaw self._set_record_data(record) def goto_target( self, - head: Optional[np.ndarray] = None, # 4x4 pose matrix + head: Optional[npt.NDArray[np.float64]] = None, # 4x4 pose matrix antennas: Optional[ - Union[np.ndarray, List[float]] - ] = None, # [left_angle, right_angle] (in rads) + Union[npt.NDArray[np.float64], List[float]] + ] = None, # [right_angle, left_angle] (in rads) duration: float = 0.5, # Duration in seconds for the movement, default is 0.5 seconds. - method=InterpolationTechnique.MIN_JERK, # can be "linear", "minjerk", "ease" or "cartoon", default is "minjerk") - body_yaw: float = 0.0, # Body yaw angle in radians - ): + method: InterpolationTechnique = InterpolationTechnique.MIN_JERK, # can be "linear", "minjerk", "ease" or "cartoon", default is "minjerk") + body_yaw: float | None = 0.0, # Body yaw angle in radians + ) -> None: """Go to a target head pose and/or antennas position using task space interpolation, in "duration" seconds. Args: @@ -211,14 +238,16 @@ def goto_target( antennas (Optional[Union[np.ndarray, List[float]]]): 1D array with two elements representing the angles of the antennas in radians. duration (float): Duration of the movement in seconds. method (InterpolationTechnique): Interpolation method to use ("linear", "minjerk", "ease", "cartoon"). Default is "minjerk". - body_yaw (float): Body yaw angle in radians. + body_yaw (float | None): Body yaw angle in radians. Use None to keep the current yaw. Raises: ValueError: If neither head nor antennas are provided, or if duration is not positive. """ - if head is None and antennas is None: - raise ValueError("At least one of head or antennas must be provided.") + if head is None and antennas is None and body_yaw is None: + raise ValueError( + "At least one of head, antennas or body_yaw must be provided." + ) if duration <= 0.0: raise ValueError( @@ -246,7 +275,7 @@ def wake_up(self) -> None: time.sleep(0.1) # Toudoum - self.media.play_sound("proud2.wav") + self.media.play_sound("wake_up.wav") # Roll 20° to the left pose = INIT_HEAD_POSE.copy() @@ -290,7 +319,7 @@ def goto_sleep(self) -> None: def look_at_image( self, u: int, v: int, duration: float = 1.0, perform_movement: bool = True - ) -> np.ndarray: + ) -> npt.NDArray[np.float64]: """Make the robot head look at a point defined by a pixel position (u,v). # TODO image of reachy mini coordinate system @@ -308,13 +337,29 @@ def look_at_image( ValueError: If duration is negative. """ - assert 0 < u < IMAGE_SIZE[0], f"u must be in [0, {IMAGE_SIZE[0]}], got {u}." - assert 0 < v < IMAGE_SIZE[1], f"v must be in [0, {IMAGE_SIZE[1]}], got {v}." + if self.media_manager.camera is None: + raise RuntimeError("Camera is not initialized.") + + # TODO this is false for the raspicam for now + assert 0 < u < self.media_manager.camera.resolution[0], ( + f"u must be in [0, {self.media_manager.camera.resolution[0]}], got {u}." + ) + assert 0 < v < self.media_manager.camera.resolution[1], ( + f"v must be in [0, {self.media_manager.camera.resolution[1]}], got {v}." + ) if duration < 0: raise ValueError("Duration can't be negative.") - x_n, y_n = cv2.undistortPoints(np.float32([[[u, v]]]), self.K, self.D)[0, 0] # type: ignore + if self.media.camera is None or self.media.camera.camera_specs is None: + raise RuntimeError("Camera specs not set.") + + points = np.array([[[u, v]]], dtype=np.float32) + x_n, y_n = cv2.undistortPoints( + points, + self.media.camera.K, # type: ignore + self.media.camera.D, # type: ignore + )[0, 0] ray_cam = np.array([x_n, y_n, 1.0]) ray_cam /= np.linalg.norm(ray_cam) @@ -330,7 +375,11 @@ def look_at_image( P_world = t_wc + ray_world return self.look_at_world( - *P_world, duration=duration, perform_movement=perform_movement + x=P_world[0], + y=P_world[1], + z=P_world[2], + duration=duration, + perform_movement=perform_movement, ) def look_at_world( @@ -340,7 +389,7 @@ def look_at_world( z: float, duration: float = 1.0, perform_movement: bool = True, - ) -> np.ndarray: + ) -> npt.NDArray[np.float64]: """Look at a specific point in 3D space in Reachy Mini's reference frame. TODO include image of reachy mini coordinate system @@ -414,7 +463,7 @@ def _goto_joint_positions( ] = None, # [yaw, stewart_platform x 6] length 7 antennas_joint_positions: Optional[ List[float] - ] = None, # [left_angle, right_angle] length 2 + ] = None, # [right_angle, left_angle] length 2 duration: float = 0.5, # Duration in seconds for the movement ) -> None: """Go to a target head joint positions and/or antennas joint positions using joint space interpolation, in "duration" seconds. @@ -448,9 +497,7 @@ def _goto_joint_positions( else: target.extend(cur_antennas) - current = np.array(current) - target = np.array(target) - traj = minimum_jerk(current, target, duration) + traj = minimum_jerk(np.array(current), np.array(target), duration) t0 = time.time() while time.time() - t0 < duration: @@ -487,7 +534,7 @@ def get_present_antenna_joint_positions(self) -> list[float]: """ return self.get_current_joint_positions()[1] - def get_current_head_pose(self) -> np.ndarray: + def get_current_head_pose(self) -> npt.NDArray[np.float64]: """Get the current head pose as a 4x4 matrix. Get the current head pose as a 4x4 matrix. @@ -502,7 +549,7 @@ def _set_joint_positions( self, head_joint_positions: list[float] | None = None, antennas_joint_positions: list[float] | None = None, - ): + ) -> None: """Set the joint positions of the head and/or antennas. [Internal] Set the joint positions of the head and/or antennas. @@ -532,11 +579,7 @@ def _set_joint_positions( self.client.send_command(json.dumps(cmd)) - def set_target_head_pose( - self, - pose: np.ndarray, - body_yaw: float = 0.0, - ) -> None: + def set_target_head_pose(self, pose: npt.NDArray[np.float64]) -> None: """Set the head pose to a specific 4x4 matrix. Args: @@ -557,8 +600,6 @@ def set_target_head_pose( else: raise ValueError("Pose must be provided as a 4x4 matrix.") - cmd["body_yaw"] = body_yaw - self.client.send_command(json.dumps(cmd)) def set_target_antenna_joint_positions(self, antennas: List[float]) -> None: @@ -566,12 +607,24 @@ def set_target_antenna_joint_positions(self, antennas: List[float]) -> None: cmd = {"antennas_joint_positions": antennas} self.client.send_command(json.dumps(cmd)) + def set_target_body_yaw(self, body_yaw: float) -> None: + """Set the target body yaw. + + Args: + body_yaw (float): The yaw angle of the body in radians. + + """ + cmd = {"body_yaw": body_yaw} + self.client.send_command(json.dumps(cmd)) + def start_recording(self) -> None: """Start recording data.""" self.client.send_command(json.dumps({"start_recording": True})) self.is_recording = True - def stop_recording(self) -> List[Dict]: + def stop_recording( + self, + ) -> Optional[List[Dict[str, float | List[float] | List[List[float]]]]]: """Stop recording data and return the recorded data.""" self.client.send_command(json.dumps({"stop_recording": True})) self.is_recording = False @@ -581,7 +634,9 @@ def stop_recording(self) -> List[Dict]: return recorded_data - def _set_record_data(self, record: Dict) -> None: + def _set_record_data( + self, record: Dict[str, float | List[float] | List[List[float]]] + ) -> None: """Set the record data to be logged by the backend. Args: @@ -594,16 +649,26 @@ def _set_record_data(self, record: Dict) -> None: # Send the record data to the backend self.client.send_command(json.dumps({"set_target_record": record})) - def enable_motors(self) -> None: - """Enable the motors.""" - self._set_torque(True) + def enable_motors(self, ids: List[str] | None = None) -> None: + """Enable the motors. + + Args: + ids (List[str] | None): List of motor names to enable. If None, all motors will be enabled. + + """ + self._set_torque(True, ids=ids) + + def disable_motors(self, ids: List[str] | None = None) -> None: + """Disable the motors. - def disable_motors(self) -> None: - """Disable the motors.""" - self._set_torque(False) + Args: + ids (List[str] | None): List of motor names to disable. If None, all motors will be disabled. - def _set_torque(self, on: bool): - self.client.send_command(json.dumps({"torque": on})) + """ + self._set_torque(False, ids=ids) + + def _set_torque(self, on: bool, ids: List[str] | None = None) -> None: + self.client.send_command(json.dumps({"torque": on, "ids": ids})) def enable_gravity_compensation(self) -> None: """Enable gravity compensation for the head motors.""" @@ -651,14 +716,13 @@ async def async_play_move( t0 = time.time() while time.time() - t0 < move.duration: - t = time.time() - t0 + t = min(time.time() - t0, move.duration - 1e-2) head, antennas, body_yaw = move.evaluate(t) if head is not None: - self.set_target_head_pose( - head, - body_yaw=body_yaw if body_yaw is not None else 0.0, - ) + self.set_target_head_pose(head) + if body_yaw is not None: + self.set_target_body_yaw(body_yaw) if antennas is not None: self.set_target_antenna_joint_positions(list(antennas)) diff --git a/src/reachy_mini/utils/__init__.py b/src/reachy_mini/utils/__init__.py index 9f60d83a..1166e2b9 100644 --- a/src/reachy_mini/utils/__init__.py +++ b/src/reachy_mini/utils/__init__.py @@ -6,6 +6,7 @@ """ import numpy as np +import numpy.typing as npt from scipy.spatial.transform import Rotation as R @@ -18,7 +19,7 @@ def create_head_pose( yaw: float = 0, mm: bool = False, degrees: bool = True, -) -> np.ndarray: +) -> npt.NDArray[np.float64]: """Create a homogeneous transformation matrix representing a pose in 6D space (position and orientation). Args: diff --git a/src/reachy_mini/utils/hardware_config/__init__.py b/src/reachy_mini/utils/hardware_config/__init__.py new file mode 100644 index 00000000..89a11e95 --- /dev/null +++ b/src/reachy_mini/utils/hardware_config/__init__.py @@ -0,0 +1 @@ +"""Hardware configuration utilities.""" diff --git a/src/reachy_mini/utils/hardware_config/parser.py b/src/reachy_mini/utils/hardware_config/parser.py new file mode 100644 index 00000000..e370c9ee --- /dev/null +++ b/src/reachy_mini/utils/hardware_config/parser.py @@ -0,0 +1,63 @@ +"""Module to parse Reachy Mini hardware configuration from a YAML file.""" + +from dataclasses import dataclass + +import yaml + + +@dataclass +class MotorConfig: + """Motor configuration.""" + + id: int + offset: int + angle_limit_min: int + angle_limit_max: int + return_delay_time: int + shutdown_error: int + pid: tuple[int, int, int] | None = None + + +@dataclass +class SerialConfig: + """Serial configuration.""" + + baudrate: int + + +@dataclass +class ReachyMiniConfig: + """Reachy Mini configuration.""" + + version: str + serial: SerialConfig + motors: dict[str, MotorConfig] + + +def parse_yaml_config(filename: str) -> ReachyMiniConfig: + """Parse the YAML configuration file and return a ReachyMiniConfig.""" + with open(filename, "r") as file: + conf = yaml.load(file, Loader=yaml.FullLoader) + + version = conf["version"] + + motor_ids = {} + for motor in conf["motors"]: + for name, params in motor.items(): + motor_ids[name] = MotorConfig( + id=params["id"], + offset=params["offset"], + angle_limit_min=params["lower_limit"], + angle_limit_max=params["upper_limit"], + return_delay_time=params["return_delay_time"], + shutdown_error=params["shutdown_error"], + pid=params.get("pid"), + ) + + serial = SerialConfig(baudrate=conf["serial"]["baudrate"]) + + return ReachyMiniConfig( + version=version, + serial=serial, + motors=motor_ids, + ) diff --git a/src/reachy_mini/utils/interpolation.py b/src/reachy_mini/utils/interpolation.py index 79714156..1c39c031 100644 --- a/src/reachy_mini/utils/interpolation.py +++ b/src/reachy_mini/utils/interpolation.py @@ -4,20 +4,20 @@ from typing import Callable, Optional, Tuple import numpy as np -from numpy.typing import NDArray +import numpy.typing as npt from scipy.spatial.transform import Rotation as R -InterpolationFunc = Callable[[float], np.ndarray] +InterpolationFunc = Callable[[float], npt.NDArray[np.float64]] def minimum_jerk( - starting_position: np.ndarray, - goal_position: np.ndarray, + starting_position: npt.NDArray[np.float64], + goal_position: npt.NDArray[np.float64], duration: float, - starting_velocity: Optional[np.ndarray] = None, - starting_acceleration: Optional[np.ndarray] = None, - final_velocity: Optional[np.ndarray] = None, - final_acceleration: Optional[np.ndarray] = None, + starting_velocity: Optional[npt.NDArray[np.float64]] = None, + starting_acceleration: Optional[npt.NDArray[np.float64]] = None, + final_velocity: Optional[npt.NDArray[np.float64]] = None, + final_acceleration: Optional[npt.NDArray[np.float64]] = None, ) -> InterpolationFunc: """Compute the mimimum jerk interpolation function from starting position to goal position.""" if starting_velocity is None: @@ -47,17 +47,17 @@ def minimum_jerk( coeffs = [a0, a1, a2, X[0], X[1], X[2]] - def f(t: float) -> np.ndarray: + def f(t: float) -> npt.NDArray[np.float64]: if t > duration: return goal_position - return np.sum([c * t**i for i, c in enumerate(coeffs)], axis=0) + return np.sum([c * t**i for i, c in enumerate(coeffs)], axis=0) # type: ignore[no-any-return] return f def linear_pose_interpolation( - start_pose: np.ndarray, target_pose: np.ndarray, t: float -): + start_pose: npt.NDArray[np.float64], target_pose: npt.NDArray[np.float64], t: float +) -> npt.NDArray[np.float64]: """Linearly interpolate between two poses in 6D space.""" # Extract rotations rot_start = R.from_matrix(start_pose[:3, :3]) @@ -133,7 +133,7 @@ def time_trajectory( def delta_angle_between_mat_rot( - P: NDArray[np.float64], Q: NDArray[np.float64] + P: npt.NDArray[np.float64], Q: npt.NDArray[np.float64] ) -> float: """Compute the angle (in radians) between two 3x3 rotation matrices `P` and `Q`. @@ -155,11 +155,11 @@ def delta_angle_between_mat_rot( R = np.dot(P, Q.T) tr = (np.trace(R) - 1) / 2 tr = np.clip(tr, -1.0, 1.0) # Ensure numerical stability - return np.arccos(tr) + return float(np.arccos(tr)) def distance_between_poses( - pose1: NDArray[np.float64], pose2: NDArray[np.float64] + pose1: npt.NDArray[np.float64], pose2: npt.NDArray[np.float64] ) -> Tuple[float, float, float]: """Compute three types of distance between two 4x4 homogeneous transformation matrices. @@ -177,7 +177,7 @@ def distance_between_poses( - unhinged distance in magic-mm (translation in mm + rotation in degrees). """ - distance_translation = np.linalg.norm(pose1[:3, 3] - pose2[:3, 3]) + distance_translation = float(np.linalg.norm(pose1[:3, 3] - pose2[:3, 3])) distance_angle = delta_angle_between_mat_rot(pose1[:3, :3], pose2[:3, :3]) unhinged_distance = distance_translation * 1000 + np.rad2deg(distance_angle) @@ -185,8 +185,10 @@ def distance_between_poses( def compose_world_offset( - T_abs: np.ndarray, T_off_world: np.ndarray, reorthonormalize: bool = False -) -> np.ndarray: + T_abs: npt.NDArray[np.float64], + T_off_world: npt.NDArray[np.float64], + reorthonormalize: bool = False, +) -> npt.NDArray[np.float64]: """Compose an absolute world-frame pose with a world-frame offset. - translations add in world: t_final = t_abs + t_off diff --git a/src/reachy_mini/utils/parse_urdf_for_kinematics.py b/src/reachy_mini/utils/parse_urdf_for_kinematics.py index 0e2799e2..8a38883f 100644 --- a/src/reachy_mini/utils/parse_urdf_for_kinematics.py +++ b/src/reachy_mini/utils/parse_urdf_for_kinematics.py @@ -5,15 +5,16 @@ import json from importlib.resources import files +from typing import Any, Dict import numpy as np # noqa: D100 from placo_utils.tf import tf import reachy_mini -from reachy_mini.kinematics import PlacoKinematics +from reachy_mini.kinematics.placo_kinematics import PlacoKinematics -def get_data(): +def get_data() -> Dict[str, Any]: """Generate the urdf_kinematics.json file.""" urdf_root_path: str = str( files(reachy_mini).joinpath("descriptions/reachy_mini/urdf") @@ -22,12 +23,12 @@ def get_data(): placo_kinematics = PlacoKinematics(urdf_root_path, 0.02) robot = placo_kinematics.robot - placo_kinematics.fk([0.0] * 7, no_iterations=20) + placo_kinematics.fk(np.array([0.0] * 7), no_iterations=20) robot.update_kinematics() # Measuring lengths for the arm and branch (constants could be used) T_world_head_home = robot.get_T_world_frame("head").copy() - T_world_1 = robot.get_T_world_frame("1") + T_world_1 = robot.get_T_world_frame("stewart_1") T_world_arm1 = robot.get_T_world_frame("passive_1_link_x") T_1_arm1 = np.linalg.inv(T_world_1) @ T_world_arm1 arm_z = T_1_arm1[2, 3] @@ -38,13 +39,38 @@ def get_data(): rod_length = np.linalg.norm(T_arm1_branch1[:3, 3]) motors = [ - {"name": "1", "branch_frame": "closing_1_2", "offset": 0, "solution": 0}, - {"name": "2", "branch_frame": "closing_2_2", "offset": 0, "solution": 1}, - {"name": "3", "branch_frame": "closing_3_2", "offset": 0, "solution": 0}, - {"name": "4", "branch_frame": "closing_4_2", "offset": 0, "solution": 1}, - {"name": "5", "branch_frame": "closing_5_2", "offset": 0, "solution": 0}, { - "name": "6", + "name": "stewart_1", + "branch_frame": "closing_1_2", + "offset": 0, + "solution": 0, + }, + { + "name": "stewart_2", + "branch_frame": "closing_2_2", + "offset": 0, + "solution": 1, + }, + { + "name": "stewart_3", + "branch_frame": "closing_3_2", + "offset": 0, + "solution": 0, + }, + { + "name": "stewart_4", + "branch_frame": "closing_4_2", + "offset": 0, + "solution": 1, + }, + { + "name": "stewart_5", + "branch_frame": "closing_5_2", + "offset": 0, + "solution": 0, + }, + { + "name": "stewart_6", "branch_frame": "passive_7_link_y", "offset": 0, "solution": 1, @@ -71,7 +97,7 @@ def get_data(): return data -def main(): +def main() -> None: """Generate the urdf_kinematics.json file.""" assets_root_path: str = str(files(reachy_mini).joinpath("assets/")) data = get_data() diff --git a/src/reachy_mini/utils/rerun.py b/src/reachy_mini/utils/rerun.py new file mode 100644 index 00000000..eeab8e7b --- /dev/null +++ b/src/reachy_mini/utils/rerun.py @@ -0,0 +1,320 @@ +"""Rerun logging for Reachy Mini. + +This module provides functionality to log the state of the Reachy Mini robot to Rerun, + a tool for visualizing and debugging robotic systems. + +It includes methods to log joint positions, camera images, and other relevant data. +""" + +import json +import logging +import os +import tempfile +import time +from threading import Event, Thread +from typing import Dict, List, Optional + +import numpy as np +import requests +import rerun as rr +from scipy.spatial.transform import Rotation as R +from urdf_parser_py import urdf + +from reachy_mini.kinematics.placo_kinematics import PlacoKinematics +from reachy_mini.media.media_manager import MediaBackend +from reachy_mini.reachy_mini import ReachyMini + + +class Rerun: + """Rerun logging for Reachy Mini.""" + + def __init__( + self, + reachymini: ReachyMini, + app_id: str = "reachy_mini_rerun", + spawn: bool = True, + ): + """Initialize the Rerun logging for Reachy Mini. + + Args: + reachymini (ReachyMini): The Reachy Mini instance to log. + app_id (str): The application ID for Rerun. Defaults to reachy_mini_daemon. + spawn (bool): If True, spawn the Rerun server. Defaults to True. + + """ + rr.init(app_id, spawn=spawn) + self.app_id = app_id + self._reachymini = reachymini + self.logger = logging.getLogger(__name__) + self.logger.setLevel(reachymini.logger.getEffectiveLevel()) + + self._robot_ip = "localhost" + if self._reachymini.client.get_status()["wireless_version"]: + self._robot_ip = self._reachymini.client.get_status()["wlan_ip"] + + self.recording = rr.get_global_data_recording() + + script_dir = os.path.dirname(os.path.abspath(__file__)) + + urdf_path = os.path.join( + script_dir, "../descriptions/reachy_mini/urdf/robot_no_collision.urdf" + ) + asset_path = os.path.join(script_dir, "../descriptions/reachy_mini/urdf") + + fixed_urdf = self.set_absolute_path_to_urdf(urdf_path, asset_path) + self.logger.debug( + f"Using URDF file: {fixed_urdf} with absolute paths for Rerun." + ) + + self.head_kinematics = PlacoKinematics(fixed_urdf) + + self.urdf_model = urdf.URDF.from_xml_file(fixed_urdf) + self._joints_by_name: Dict[str, urdf.Joint] = { + joint.name: joint for joint in self.urdf_model.joints + } + self._entity_paths = UrdfEntityPaths(self.urdf_model, "ReachyMini") + + rr.set_time("reachymini", timestamp=time.time(), recording=self.recording) + + # Use the native URDF loader in Rerun to visualize Reachy Mini's model + # Logging as non-static to allow updating joint positions + rr.log_file_from_path( + fixed_urdf, + static=False, + entity_path_prefix=self._entity_paths.prefix, + recording=self.recording, + ) + + self.running = Event() + self.thread_log_camera: Optional[Thread] = None + if ( + reachymini.media.backend == MediaBackend.GSTREAMER + or reachymini.media.backend == MediaBackend.DEFAULT + ): + self.thread_log_camera = Thread(target=self.log_camera, daemon=True) + self.thread_log_movements = Thread(target=self.log_movements, daemon=True) + + def set_absolute_path_to_urdf(self, urdf_path: str, abs_path: str) -> str: + """Set the absolute paths in the URDF file. Rerun cannot read the "package://" paths.""" + with open(urdf_path, "r") as f: + urdf_content = f.read() + urdf_content_mod = urdf_content.replace("package://", f"file://{abs_path}/") + + with tempfile.NamedTemporaryFile("w", delete=False, suffix=".urdf") as tmp_file: + tmp_file.write(urdf_content_mod) + return tmp_file.name + + def start(self) -> None: + """Start the Rerun logging thread.""" + if self.thread_log_camera is not None: + self.thread_log_camera.start() + self.thread_log_movements.start() + + def stop(self) -> None: + """Stop the Rerun logging thread.""" + self.running.set() + + def _get_joint(self, joint_name: str) -> urdf.Joint: + try: + return self._joints_by_name[joint_name] + except KeyError as exc: + raise RuntimeError(f"Invalid joint name: {joint_name}") from exc + + def _joint_entity_path(self, joint: urdf.Joint) -> str: + return self._entity_paths.joint_paths[joint.name] + + def log_camera(self) -> None: + """Log the camera image to Rerun.""" + if self._reachymini.media.camera is None: + self.logger.warning("Camera is not initialized.") + return + + self.logger.info("Starting camera logging to Rerun.") + + while not self.running.is_set(): + rr.set_time("reachymini", timestamp=time.time(), recording=self.recording) + frame = self._reachymini.media.get_frame() + if frame is not None: + if isinstance(frame, bytes): + self.logger.warning( + "Received frame is jpeg. Please use default backend." + ) + return + + else: + return + + K = np.array( + [ + [550.3564, 0.0, 638.0112], + [0.0, 549.1653, 364.589], + [0.0, 0.0, 1.0], + ] + ) + + cam_joint = self._get_joint("camera_optical_frame") + cam_path = self._joint_entity_path(cam_joint) + rr.log( + f"{cam_path}/image", + rr.Pinhole( + image_from_camera=rr.datatypes.Mat3x3(K), + width=frame.shape[1], + height=frame.shape[0], + image_plane_distance=0.8, + camera_xyz=rr.ViewCoordinates.RDF, + ), + rr.Image(frame, color_model="bgr").compress(), + recording=self.recording, + ) + + time.sleep(0.03) # ~30fps + + def _log_joint_angle( + self, + joint_name: str, + angle: float, + axis: list[float] | None = None, + ) -> None: + """Log the joint angle to Rerun.""" + joint = self._get_joint(joint_name) + joint_path = self._joint_entity_path(joint) + + base_euler = joint.origin.rotation or [0.0, 0.0, 0.0] + + # if we specify an axis override, use it; otherwise, use the joint's defined axis + effective_axis = ( + np.array(axis) + if axis is not None + else np.array(joint.axis or [1.0, 0.0, 0.0]) + ) + + target_euler = np.array(base_euler) + (effective_axis * angle) + target_translation = joint.origin.xyz or [0.0, 0.0, 0.0] + + rr.log( + joint_path, + rr.Transform3D( + translation=target_translation, + quaternion=R.from_euler("xyz", target_euler).as_quat(), + ), + recording=self.recording, + ) + + def log_movements(self) -> None: + """Log the movement data to Rerun.""" + url = f"http://{self._robot_ip}:8000/api/state/full" + + params = { + "with_control_mode": "false", + "with_head_pose": "false", + "with_target_head_pose": "false", + "with_head_joints": "true", + "with_target_head_joints": "false", + "with_body_yaw": "false", # already in head_joints + "with_target_body_yaw": "false", + "with_antenna_positions": "true", + "with_target_antenna_positions": "false", + "use_pose_matrix": "false", + "with_passive_joints": "true", + } + + while not self.running.is_set(): + msg = requests.get(url, params=params) + + if msg.status_code != 200: + self.logger.error( + f"Request failed with status {msg.status_code}: {msg.text}" + ) + time.sleep(0.1) + continue + try: + data = json.loads(msg.text) + self.logger.debug(f"Data: {data}") + except Exception: + continue + + rr.set_time("reachymini", timestamp=time.time(), recording=self.recording) + + if "antennas_position" in data and data["antennas_position"] is not None: + antennas = data["antennas_position"] + if antennas is not None: + self._log_joint_angle("left_antenna", antennas[0]) + self._log_joint_angle("right_antenna", antennas[1]) + + if "head_joints" in data and data["head_joints"] is not None: + head_joints = data["head_joints"] + + # The joint axis definitions in the URDF do not match the real axis of rotation + # due to URDF not supporting ball joints properly. + self._log_joint_angle("yaw_body", -head_joints[0], axis=[0, 0, 1]) + self._log_joint_angle("stewart_1", -head_joints[1], axis=[0, 1, 0]) + self._log_joint_angle("stewart_2", head_joints[2], axis=[0, 1, 0]) + self._log_joint_angle("stewart_3", -head_joints[3], axis=[0, 1, 0]) + self._log_joint_angle("stewart_4", head_joints[4], axis=[0, 1, 0]) + self._log_joint_angle("stewart_5", -head_joints[5], axis=[0, 1, 0]) + self._log_joint_angle("stewart_6", head_joints[6], axis=[0, 1, 0]) + + if "passive_joints" in data and data["passive_joints"] is not None: + passive_joints = data["passive_joints"] + + for axis_idx, axis in enumerate(["x", "y", "z"]): + for i in range(1, 8): + joint_name = f"passive_{i}_{axis}" + value_index = (i - 1) * 3 + axis_idx + + override_axis = [0.0, 0.0, 0.0] + override_axis[axis_idx] = 1.0 + self._log_joint_angle( + joint_name, + passive_joints[value_index], + axis=override_axis, + ) + + time.sleep(0.1) + + +class UrdfEntityPaths: + """Helper for constructing link/joint entity paths that match the native URDF logger.""" + + def __init__(self, model: urdf.URDF, entity_path_prefix: Optional[str]) -> None: + """Construct a new `UrdfEntityPaths` instance. + + Args: + model (urdf.URDF): The URDF model. + entity_path_prefix (Optional[str]): The prefix for entity paths. + + """ + self._model = model + self.prefix = entity_path_prefix + self.link_paths: Dict[str, str] = {} + self.joint_paths: Dict[str, str] = {} + + base_parts = [part for part in (self.prefix, model.name) if part] + self._base_path = "/".join(base_parts) + + children_map: Dict[str, List[urdf.Joint]] = {} + for joint in model.joints: + children_map.setdefault(joint.parent, []).append(joint) + + root_link = model.get_root() + self._build_paths(root_link, self._base_path, children_map) + + def _build_paths( + self, + link_name: str, + parent_path: str, + children_map: Dict[str, List[urdf.Joint]], + ) -> None: + link_path = self._join(parent_path, link_name) + self.link_paths[link_name] = link_path + + for joint in children_map.get(link_name, []): + joint_path = self._join(link_path, joint.name) + self.joint_paths[joint.name] = joint_path + self._build_paths(joint.child, joint_path, children_map) + + @staticmethod + def _join(parent: str, child: str) -> str: + if parent: + return f"{parent}/{child}" + return child diff --git a/src/reachy_mini/utils/wireless_version/__init__.py b/src/reachy_mini/utils/wireless_version/__init__.py new file mode 100644 index 00000000..8db2704a --- /dev/null +++ b/src/reachy_mini/utils/wireless_version/__init__.py @@ -0,0 +1 @@ +"""Utility functions for working with wireless version.""" diff --git a/src/reachy_mini/utils/wireless_version/update.py b/src/reachy_mini/utils/wireless_version/update.py new file mode 100644 index 00000000..b1236701 --- /dev/null +++ b/src/reachy_mini/utils/wireless_version/update.py @@ -0,0 +1,20 @@ +"""Module to handle software updates for the Reachy Mini wireless.""" + +import logging + +from .utils import call_logger_wrapper + + +async def update_reachy_mini(pre_release: bool, logger: logging.Logger) -> None: + """Perform a software update by upgrading the reachy_mini package and restarting the daemon.""" + extra_args = [] + if pre_release: + extra_args.append("--pre") + + await call_logger_wrapper( + ["pip", "install", "--upgrade", "reachy_mini[wireless-version]"] + extra_args, + logger, + ) + await call_logger_wrapper( + ["sudo", "systemctl", "restart", "reachy-mini-daemon"], logger + ) diff --git a/src/reachy_mini/utils/wireless_version/update_available.py b/src/reachy_mini/utils/wireless_version/update_available.py new file mode 100644 index 00000000..b8320b42 --- /dev/null +++ b/src/reachy_mini/utils/wireless_version/update_available.py @@ -0,0 +1,61 @@ +"""Check if an update is available for Reachy Mini Wireless. + +For now, this only checks if a new version of "reachy_mini" is available on PyPI. +""" + +from importlib.metadata import version + +import requests +import semver + + +def is_update_available(package_name: str, pre_release: bool) -> bool: + """Check if an update is available for the given package.""" + pypi_version = get_pypi_version(package_name, pre_release) + local_version = get_local_version(package_name) + + is_update_available = pypi_version > local_version + assert isinstance(is_update_available, bool) + + return is_update_available + + +def get_pypi_version(package_name: str, pre_release: bool) -> semver.Version: + """Get the latest version of a package from PyPI.""" + url = f"https://pypi.org/pypi/{package_name}/json" + response = requests.get(url) + response.raise_for_status() + data = response.json() + + version = data["info"]["version"] + + if pre_release: + releases = list(data["releases"].keys()) + pre_version = _semver_version(releases[-1]) + if pre_version > version: + return pre_version + + return _semver_version(version) + + +def get_local_version(package_name: str) -> semver.Version: + """Get the currently installed version of a package.""" + return _semver_version(version(package_name)) + + +def _semver_version(v: str) -> semver.Version: + """Convert a version string to a semver.Version object, handling pypi pre-release formats.""" + try: + return semver.Version.parse(v) + except ValueError: + version_parts = v.split(".") + if len(version_parts) < 3: + raise ValueError(f"Invalid version string: {v}") + + patch_part = version_parts[2] + if "rc" in patch_part: + patch, rc = patch_part.split("rc", 1) + v_clean = f"{version_parts[0]}.{version_parts[1]}.{patch}-rc.{rc}" + return semver.Version.parse(v_clean) + + raise ValueError(f"Invalid version string: {v}") diff --git a/src/reachy_mini/utils/wireless_version/utils.py b/src/reachy_mini/utils/wireless_version/utils.py new file mode 100644 index 00000000..dff1b365 --- /dev/null +++ b/src/reachy_mini/utils/wireless_version/utils.py @@ -0,0 +1,39 @@ +"""Utility functions for running shell commands asynchronously with real-time logging.""" + +import asyncio +import logging +from typing import Callable + + +async def call_logger_wrapper(command: list[str], logger: logging.Logger) -> None: + """Run a command asynchronously, streaming stdout and stderr to logger in real time. + + Args: + command: list or tuple of command arguments (not a string) + logger: logger object with .info and .error methods + + """ + process = await asyncio.create_subprocess_exec( + *command, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + + async def stream_output( + stream: asyncio.StreamReader, + log_func: Callable[[str], None], + ) -> None: + while True: + line = await stream.readline() + if not line: + break + log_func(line.decode().rstrip()) + + tasks = [] + if process.stdout is not None: + tasks.append(asyncio.create_task(stream_output(process.stdout, logger.info))) + if process.stderr is not None: + tasks.append(asyncio.create_task(stream_output(process.stderr, logger.error))) + + await asyncio.gather(*tasks) + await process.wait() diff --git a/tests/faulty_app/README.md b/tests/faulty_app/README.md new file mode 100644 index 00000000..63915a99 --- /dev/null +++ b/tests/faulty_app/README.md @@ -0,0 +1 @@ +# faulty_app - A ReachyMini application \ No newline at end of file diff --git a/tests/faulty_app/build/lib/faulty_app/__init__.py b/tests/faulty_app/build/lib/faulty_app/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/faulty_app/build/lib/faulty_app/main.py b/tests/faulty_app/build/lib/faulty_app/main.py new file mode 100644 index 00000000..7834aefc --- /dev/null +++ b/tests/faulty_app/build/lib/faulty_app/main.py @@ -0,0 +1,9 @@ +import threading + +from reachy_mini import ReachyMini, ReachyMiniApp + + +class FaultyApp(ReachyMiniApp): + def run(self, reachy_mini: ReachyMini, stop_event: threading.Event): + raise RuntimeError("This is a faulty app for testing purposes.") + diff --git a/tests/faulty_app/faulty_app/__init__.py b/tests/faulty_app/faulty_app/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/faulty_app/faulty_app/main.py b/tests/faulty_app/faulty_app/main.py new file mode 100644 index 00000000..7834aefc --- /dev/null +++ b/tests/faulty_app/faulty_app/main.py @@ -0,0 +1,9 @@ +import threading + +from reachy_mini import ReachyMini, ReachyMiniApp + + +class FaultyApp(ReachyMiniApp): + def run(self, reachy_mini: ReachyMini, stop_event: threading.Event): + raise RuntimeError("This is a faulty app for testing purposes.") + diff --git a/tests/faulty_app/pyproject.toml b/tests/faulty_app/pyproject.toml new file mode 100644 index 00000000..5fe572f7 --- /dev/null +++ b/tests/faulty_app/pyproject.toml @@ -0,0 +1,18 @@ +[build-system] +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta" + + +[project] +name = "faulty_app" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.8" +dependencies = [ + "reachy-mini" +] +keywords = ["reachy-mini-app"] + +[project.entry-points."reachy_mini_apps"] +faulty_app = "faulty_app.main:FaultyApp" \ No newline at end of file diff --git a/tests/ok_app/README.md b/tests/ok_app/README.md new file mode 100644 index 00000000..3297f129 --- /dev/null +++ b/tests/ok_app/README.md @@ -0,0 +1 @@ +# ok_app - A ReachyMini application \ No newline at end of file diff --git a/tests/ok_app/build/lib/ok_app/__init__.py b/tests/ok_app/build/lib/ok_app/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/ok_app/build/lib/ok_app/main.py b/tests/ok_app/build/lib/ok_app/main.py new file mode 100644 index 00000000..80e245f3 --- /dev/null +++ b/tests/ok_app/build/lib/ok_app/main.py @@ -0,0 +1,10 @@ +import threading +import time + +from reachy_mini import ReachyMini, ReachyMiniApp + + +class OkApp(ReachyMiniApp): + def run(self, reachy_mini: ReachyMini, stop_event: threading.Event): + while not stop_event.is_set(): + time.sleep(0.5) \ No newline at end of file diff --git a/tests/ok_app/ok_app/__init__.py b/tests/ok_app/ok_app/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/ok_app/ok_app/main.py b/tests/ok_app/ok_app/main.py new file mode 100644 index 00000000..80e245f3 --- /dev/null +++ b/tests/ok_app/ok_app/main.py @@ -0,0 +1,10 @@ +import threading +import time + +from reachy_mini import ReachyMini, ReachyMiniApp + + +class OkApp(ReachyMiniApp): + def run(self, reachy_mini: ReachyMini, stop_event: threading.Event): + while not stop_event.is_set(): + time.sleep(0.5) \ No newline at end of file diff --git a/tests/ok_app/pyproject.toml b/tests/ok_app/pyproject.toml new file mode 100644 index 00000000..58ff6f6d --- /dev/null +++ b/tests/ok_app/pyproject.toml @@ -0,0 +1,18 @@ +[build-system] +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta" + + +[project] +name = "ok_app" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.8" +dependencies = [ + "reachy-mini" +] +keywords = ["reachy-mini-app"] + +[project.entry-points."reachy_mini_apps"] +ok_app = "ok_app.main:OkApp" \ No newline at end of file diff --git a/tests/test_analytical_kinematics.py b/tests/test_analytical_kinematics.py new file mode 100644 index 00000000..810006a9 --- /dev/null +++ b/tests/test_analytical_kinematics.py @@ -0,0 +1,19 @@ +from reachy_mini.kinematics import AnalyticalKinematics +import numpy as np + +def test_analytical_kinematics(): + ak = AnalyticalKinematics() + pose = np.eye(4) + sol = ak.ik(pose) + assert sol is not None, "IK solution should be found" + fk_pose = ak.fk(sol, no_iterations=10) + assert np.allclose(fk_pose, pose, atol=1e-2), "FK should match the original pose" + +def test_analytical_kinematics_with_yaw(): + ak = AnalyticalKinematics() + pose = np.eye(4) + body_yaw = np.pi / 4 # 45 degrees + sol = ak.ik(pose, body_yaw=body_yaw) + assert sol is not None, "IK solution should be found with body yaw" + fk_pose = ak.fk(sol, no_iterations=10) + assert np.allclose(fk_pose, pose, atol=1e-2), "FK should match the original pose with body yaw" \ No newline at end of file diff --git a/tests/test_app.py b/tests/test_app.py new file mode 100644 index 00000000..58eb7c6d --- /dev/null +++ b/tests/test_app.py @@ -0,0 +1,114 @@ + +import asyncio +from pathlib import Path +from threading import Event +import time +import pytest + +from reachy_mini import ReachyMiniApp +from reachy_mini.apps import AppInfo, SourceKind +from reachy_mini.apps.manager import AppManager, AppState +from reachy_mini.daemon.daemon import Daemon +from reachy_mini.reachy_mini import ReachyMini + + +@pytest.mark.asyncio +async def test_app() -> None: + class MockApp(ReachyMiniApp): + def run(self, reachy_mini: ReachyMini, stop_event: Event) -> None: + time.sleep(1) # Simulate some processing time + + daemon = Daemon() + await daemon.start( + sim=True, + headless=True, + wake_up_on_start=False, + ) + + stop = Event() + + with ReachyMini(media_backend="no_media") as mini: + app = MockApp() + app.run(mini, stop) + + await daemon.stop(goto_sleep_on_stop=False) + + +@pytest.mark.asyncio +async def test_app_manager() -> None: + daemon = Daemon() + await daemon.start( + sim=True, + headless=True, + wake_up_on_start=False, + ) + + app_mngr = AppManager() + + before_installed_apps = await app_mngr.list_available_apps(SourceKind.INSTALLED) + + app_info = AppInfo( + name="ok_app", + source_kind=SourceKind.LOCAL, + extra={"path": str(Path(__file__).parent / "ok_app")}, + ) + await app_mngr.install_new_app(app_info, daemon.logger) + + after_installed_apps = await app_mngr.list_available_apps(SourceKind.INSTALLED) + + assert len(after_installed_apps) == len(before_installed_apps) + 1 + + status = await app_mngr.start_app("ok_app", media_backend="no_media") + assert status is not None and status.state in (AppState.STARTING, AppState.RUNNING) + assert app_mngr.is_app_running() + status = await app_mngr.current_app_status() + assert status is not None and status.state in (AppState.STARTING, AppState.RUNNING) + + await app_mngr.stop_current_app() + assert not app_mngr.is_app_running() + status = await app_mngr.current_app_status() + assert status is None + + await app_mngr.remove_app("ok_app", daemon.logger) + after_uninstalled_apps = await app_mngr.list_available_apps(SourceKind.INSTALLED) + + assert len(after_uninstalled_apps) == len(before_installed_apps) + + await daemon.stop(goto_sleep_on_stop=False) + + + +@pytest.mark.asyncio +async def test_faulty_app() -> None: + daemon = Daemon() + await daemon.start( + sim=True, + headless=True, + wake_up_on_start=False, + ) + + app_mngr = AppManager() + + app_info = AppInfo( + name="faulty_app", + source_kind=SourceKind.LOCAL, + extra={"path": str(Path(__file__).parent / "faulty_app")}, + ) + await app_mngr.install_new_app(app_info, daemon.logger) + + + status = await app_mngr.start_app("faulty_app", media_backend="no_media") + + for _ in range(10): + status = await app_mngr.current_app_status() + if status is None or status.state in (AppState.STARTING, AppState.RUNNING): + await asyncio.sleep(1.0) + continue + assert status is not None and status.state == AppState.ERROR + break + else: + pytest.fail("Faulty app did not reach ERROR state in time") + + await app_mngr.remove_app("faulty_app", daemon.logger) + + await daemon.stop(goto_sleep_on_stop=False) diff --git a/tests/test_audio.py b/tests/test_audio.py index 9d58ae4a..36036dcb 100644 --- a/tests/test_audio.py +++ b/tests/test_audio.py @@ -4,13 +4,14 @@ import pytest import soundfile as sf from reachy_mini.media.media_manager import MediaManager, MediaBackend +import numpy as np @pytest.mark.audio -def test_play_sound_default_backend(): +def test_play_sound_default_backend() -> None: """Test playing a sound with the default backend.""" media = MediaManager(backend=MediaBackend.DEFAULT_NO_VIDEO) # Use a short sound file present in your assets directory - sound_file = "proud2.wav" # Change to a valid file if needed + sound_file = "wake_up.wav" # Change to a valid file if needed media.play_sound(sound_file) print("Playing sound with default backend...") # Wait a bit to let the sound play (non-blocking backend) @@ -19,32 +20,107 @@ def test_play_sound_default_backend(): # Sound should be audible if the audio device is correctly set up. @pytest.mark.audio -def test_record_audio_and_file_exists(): +def test_push_audio_sample_default_backend() -> None: + """Test pushing an audio sample with the default backend.""" + media = MediaManager(backend=MediaBackend.DEFAULT_NO_VIDEO) + media.start_playing() + + #Stereo, channels last + data = np.random.random((media.get_output_audio_samplerate(), 2)).astype(np.float32) + media.push_audio_sample(data) + time.sleep(1) + + #Mono, channels last + data = np.random.random((media.get_output_audio_samplerate(), 1)).astype(np.float32) + media.push_audio_sample(data) + time.sleep(1) + + #Multiple channels, channels last + data = np.random.random((media.get_output_audio_samplerate(), 10)).astype(np.float32) + media.push_audio_sample(data) + time.sleep(1) + + #Stereo, channels first + data = np.random.random((2, media.get_output_audio_samplerate())).astype(np.float32) + media.push_audio_sample(data) + time.sleep(1) + + # No assertion: test passes if no exception is raised. + # Sound should be audible if the audio device is correctly set up. + + data = np.array(0).astype(np.float32) + media.push_audio_sample(data) + time.sleep(1) + + data = np.random.random((media.get_output_audio_samplerate(), 2, 2)).astype(np.float32) + media.push_audio_sample(data) + time.sleep(1) + + # No assertion: test passes if no exception is raised. + # No sound should be audible if the audio device is correctly set up. + + media.stop_playing() + +@pytest.mark.audio +def test_record_audio_and_file_exists() -> None: """Test recording audio and check that the file exists and is not empty.""" media = MediaManager(backend=MediaBackend.DEFAULT_NO_VIDEO) - duration = 2 # seconds + DURATION = 2 # seconds tmpfile = tempfile.NamedTemporaryFile(suffix='.wav', delete=False) tmpfile.close() media.start_recording() - time.sleep(duration) + time.sleep(DURATION) media.stop_recording() audio = media.get_audio_sample() - samplerate = media.get_audio_samplerate() - if audio is not None: - sf.write(tmpfile.name, audio, samplerate) + samplerate = media.get_input_audio_samplerate() + assert audio is not None + sf.write(tmpfile.name, audio, samplerate) assert os.path.exists(tmpfile.name) assert os.path.getsize(tmpfile.name) > 0 # comment the following line if you want to keep the file for inspection os.remove(tmpfile.name) - # print(f"Recorded audio saved to {tmpfile.name}") + #print(f"Recorded audio saved to {tmpfile.name}") + +@pytest.mark.audio +def test_record_audio_without_start_recording() -> None: + """Test recording audio without starting recording.""" + media = MediaManager(backend=MediaBackend.DEFAULT_NO_VIDEO) + audio = media.get_audio_sample() + assert audio is None + +@pytest.mark.audio +def test_record_audio_above_max_queue_seconds() -> None: + """Test recording audio and check that the maximum queue seconds is respected.""" + media = MediaManager(backend=MediaBackend.DEFAULT_NO_VIDEO) + media.audio._input_max_queue_seconds = 1 + media.start_recording() + time.sleep(5) + audio = media.get_audio_sample() + media.stop_recording() + + assert audio is not None + assert audio.shape[0] < media.audio._input_max_queue_samples + +@pytest.mark.audio +def test_DoA() -> None: + """Test Direction of Arrival (DoA) estimation.""" + media = MediaManager(backend=MediaBackend.DEFAULT_NO_VIDEO) + doa = media.audio.get_DoA() + assert doa is not None + assert isinstance(doa, tuple) + assert len(doa) == 2 + assert isinstance(doa[0], float) + assert isinstance(doa[1], bool) + ''' @pytest.mark.audio_gstreamer -def test_play_sound_gstreamer_backend(): +def test_play_sound_gstreamer_backend() -> None: """Test playing a sound with the GStreamer backend.""" media = MediaManager(backend=MediaBackend.GSTREAMER) + time.sleep(2) # Give some time for the audio system to initialize # Use a short sound file present in your assets directory - sound_file = "proud2.wav" # Change to a valid file if needed + sound_file = "wake_up.wav" # Change to a valid file if needed media.play_sound(sound_file) print("Playing sound with GStreamer backend...") # Wait a bit to let the sound play (non-blocking backend) @@ -52,24 +128,42 @@ def test_play_sound_gstreamer_backend(): # No assertion: test passes if no exception is raised. # Sound should be audible if the audio device is correctly set up. ''' + @pytest.mark.audio_gstreamer -def test_record_audio_and_file_exists(): +def test_record_audio_and_file_exists_gstreamer() -> None: """Test recording audio and check that the file exists and is not empty.""" media = MediaManager(backend=MediaBackend.GSTREAMER) - duration = 2 # seconds + DURATION = 2 # seconds tmpfile = tempfile.NamedTemporaryFile(suffix='.wav', delete=False) tmpfile.close() + audio_samples = [] + t0 = time.time() media.start_recording() - time.sleep(duration) - audio = media.get_audio_sample() - assert audio is not None + + while time.time() - t0 < DURATION: + sample = media.get_audio_sample() + + if sample is not None: + audio_samples.append(sample) + media.stop_recording() + + assert len(audio_samples) > 0 + audio_data = np.concatenate(audio_samples, axis=0) + assert audio_data.ndim == 2 and audio_data.shape[1] == 2 + samplerate = media.get_input_audio_samplerate() + sf.write(tmpfile.name, audio_data, samplerate) + assert os.path.exists(tmpfile.name) + assert os.path.getsize(tmpfile.name) > 0 + #os.remove(tmpfile.name) + print(f"Recorded audio saved to {tmpfile.name}") -def test_no_media(): +def test_no_media() -> None: """Test that methods handle uninitialized media gracefully.""" media = MediaManager(backend=MediaBackend.NO_MEDIA) assert media.get_frame() is None assert media.get_audio_sample() is None - assert media.get_audio_samplerate() == -1 + assert media.get_input_audio_samplerate() == -1 + assert media.get_output_audio_samplerate() == -1 diff --git a/tests/test_daemon.py b/tests/test_daemon.py index 53c03af3..589f1c09 100644 --- a/tests/test_daemon.py +++ b/tests/test_daemon.py @@ -6,7 +6,7 @@ from reachy_mini.reachy_mini import ReachyMini @pytest.mark.asyncio -async def test_daemon_start_stop(): +async def test_daemon_start_stop() -> None: from reachy_mini.daemon.daemon import Daemon daemon = Daemon() @@ -19,7 +19,20 @@ async def test_daemon_start_stop(): @pytest.mark.asyncio -async def test_daemon_client_disconnection(): +async def test_daemon_multiple_start_stop() -> None: + daemon = Daemon() + + for _ in range(3): + await daemon.start( + sim=True, + headless=True, + wake_up_on_start=False, + ) + await daemon.stop(goto_sleep_on_stop=False) + + +@pytest.mark.asyncio +async def test_daemon_client_disconnection() -> None: daemon = Daemon() await daemon.start( sim=True, @@ -29,18 +42,25 @@ async def test_daemon_client_disconnection(): client_connected = asyncio.Event() - async def simple_client(): - with ReachyMini(media_backend="no_media"): + async def simple_client() -> None: + with ReachyMini(media_backend="no_media") as mini: + status = mini.client.get_status() + assert status['state'] == "running" + assert status['simulation_enabled'] + assert status['error'] is None + assert status['backend_status']['motor_control_mode'] == "enabled" + assert status['backend_status']['error'] is None + assert status['wlan_ip'] is None client_connected.set() - async def wait_for_client(): + async def wait_for_client() -> None: await client_connected.wait() await daemon.stop(goto_sleep_on_stop=False) await asyncio.gather(simple_client(), wait_for_client()) @pytest.mark.asyncio -async def test_daemon_early_stop(): +async def test_daemon_early_stop() -> None: daemon = Daemon() await daemon.start( sim=True, @@ -51,22 +71,22 @@ async def test_daemon_early_stop(): client_connected = asyncio.Event() daemon_stopped = asyncio.Event() - async def client_bg(): + async def client_bg() -> None: with ReachyMini(media_backend="no_media") as reachy: client_connected.set() await daemon_stopped.wait() # Make sure the keep-alive check runs at least once - await asyncio.sleep(1.1) + reachy.client._check_alive_evt.clear() + reachy.client._check_alive_evt.wait(timeout=100.0) with pytest.raises(ConnectionError, match="Lost connection with the server."): reachy.set_target(head=np.eye(4)) - - async def will_stop_soon(): + + async def will_stop_soon() -> None: await client_connected.wait() await daemon.stop(goto_sleep_on_stop=False) daemon_stopped.set() await asyncio.gather(client_bg(), will_stop_soon()) - diff --git a/tests/test_video.py b/tests/test_video.py index 3373750e..b629d865 100644 --- a/tests/test_video.py +++ b/tests/test_video.py @@ -1,34 +1,86 @@ +from reachy_mini.media.camera_constants import ReachyMiniLiteCamSpecs, CameraResolution, MujocoCameraSpecs from reachy_mini.media.media_manager import MediaManager, MediaBackend import numpy as np import pytest import time # import tempfile -# import tempfile # import cv2 @pytest.mark.video -def test_get_frame_exists(): +def test_get_frame_exists() -> None: """Test that a frame can be retrieved from the camera and is not None.""" media = MediaManager(backend=MediaBackend.DEFAULT) frame = media.get_frame() assert frame is not None, "No frame was retrieved from the camera." assert isinstance(frame, np.ndarray), "Frame is not a numpy array." assert frame.size > 0, "Frame is empty." + assert frame.shape[0] == media.camera.resolution[1] and frame.shape[1] == media.camera.resolution[0], f"Frame has incorrect dimensions: {frame.shape}" # with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as tmp_file: # cv2.imwrite(tmp_file.name, frame) - # print(f"Frame saved for inspection: {tmp_file.name}") + # print(f"Frame saved for inspection: {tmp_file.name}") + +@pytest.mark.video +def test_get_frame_exists_all_resolutions() -> None: + """Test that a frame can be retrieved from the camera for all supported resolutions.""" + media = MediaManager(backend=MediaBackend.DEFAULT) + for resolution in media.camera.camera_specs.available_resolutions: + media.camera.set_resolution(resolution) + frame = media.get_frame() + assert frame is not None, f"No frame was retrieved from the camera at resolution {resolution}." + assert isinstance(frame, np.ndarray), f"Frame is not a numpy array at resolution {resolution}." + assert frame.size > 0, f"Frame is empty at resolution {resolution}." + assert frame.shape[0] == resolution.value[1] and frame.shape[1] == resolution.value[0], f"Frame has incorrect dimensions at resolution {resolution}: {frame.shape}" + +@pytest.mark.video +def test_change_resolution_errors() -> None: + """Test that changing resolution raises a runtime error if not allowed.""" + media = MediaManager(backend=MediaBackend.DEFAULT) + media.camera.camera_specs = None + with pytest.raises(RuntimeError): + media.camera.set_resolution(CameraResolution.R1280x720) + + media.camera.camera_specs = MujocoCameraSpecs() + with pytest.raises(RuntimeError): + media.camera.set_resolution(CameraResolution.R1280x720) + media.camera.camera_specs = ReachyMiniLiteCamSpecs() + with pytest.raises(ValueError): + media.camera.set_resolution(CameraResolution.R1280x720) + @pytest.mark.video_gstreamer -def test_get_frame_exists_gstreamer(): +def test_get_frame_exists_gstreamer() -> None: """Test that a frame can be retrieved from the camera and is not None.""" media = MediaManager(backend=MediaBackend.GSTREAMER) time.sleep(2) # Give some time for the camera to initialize frame = media.get_frame() assert frame is not None, "No frame was retrieved from the camera." - assert isinstance(frame, bytes), "Frame is not a bytes object." - assert len(frame) > 0, "Frame is empty." - # with tempfile.NamedTemporaryFile(suffix='.jpg', delete=False) as tmp_file: - # tmp_file.write(frame) - # print(f"Frame saved for inspection: {tmp_file.name}") \ No newline at end of file + assert isinstance(frame, np.ndarray), "Frame is not a numpy array." + assert frame.size > 0, "Frame is empty." + assert frame.shape[0] == media.camera.resolution[1] and frame.shape[1] == media.camera.resolution[0], f"Frame has incorrect dimensions: {frame.shape}" + +@pytest.mark.video_gstreamer +def test_get_frame_exists_all_resolutions_gstreamer() -> None: + """Test that a frame can be retrieved from the camera for all supported resolutions.""" + media = MediaManager(backend=MediaBackend.GSTREAMER) + time.sleep(2) # Give some time for the camera to initialize + + for resolution in media.camera.camera_specs.available_resolutions: + media.camera.close() + media.camera.set_resolution(resolution) + media.camera.open() + time.sleep(2) # Give some time for the camera to adjust to new resolution + frame = media.get_frame() + assert frame is not None, f"No frame was retrieved from the camera at resolution {resolution}." + assert isinstance(frame, np.ndarray), f"Frame is not a numpy array at resolution {resolution}." + assert frame.size > 0, f"Frame is empty at resolution {resolution}." + assert frame.shape[0] == resolution.value[1] and frame.shape[1] == resolution.value[0], f"Frame has incorrect dimensions at resolution {resolution}: {frame.shape}" + +@pytest.mark.video_gstreamer +def test_change_resolution_errors_gstreamer() -> None: + """Test that changing resolution raises a runtime error if not allowed.""" + media = MediaManager(backend=MediaBackend.GSTREAMER) + time.sleep(1) # Give some time for the camera to initialize + with pytest.raises(RuntimeError): + media.camera.set_resolution(media.camera.camera_specs.available_resolutions[0]) diff --git a/tests/test_wireless.py b/tests/test_wireless.py new file mode 100644 index 00000000..10abb4df --- /dev/null +++ b/tests/test_wireless.py @@ -0,0 +1,30 @@ +import pytest + + +from reachy_mini.reachy_mini import ReachyMini +import time +import numpy as np +from reachy_mini.media.camera_constants import CameraResolution + +@pytest.mark.wireless +def test_daemon_wireless_client_disconnection() -> None: + with ReachyMini(media_backend="no_media", localhost_only=False) as mini: + status = mini.client.get_status() + assert status['state'] == "running" + assert status['wireless_version'] is True + assert not status['simulation_enabled'] + assert status['error'] is None + assert status['backend_status']['motor_control_mode'] == "enabled" + assert status['backend_status']['error'] is None + assert isinstance(status['wlan_ip'], str) + assert status['wlan_ip'].count('.') == 3 + assert all(0 <= int(part) <= 255 for part in status['wlan_ip'].split('.') if part.isdigit()) + +@pytest.mark.wireless_gstreamer +def test_daemon_wireless_gstreamer() -> None: + with ReachyMini(media_backend="gstreamer") as mini: + time.sleep(3) # Give some time for the camera to initialize + frame = mini.media.get_frame() + assert frame is not None, "No frame was retrieved from the camera." + assert isinstance(frame, np.ndarray), "Frame is not a numpy array." + assert frame.shape[0] == CameraResolution.R1280x720.value[1] and frame.shape[1] == CameraResolution.R1280x720.value[0], f"Frame has incorrect dimensions: {frame.shape}" diff --git a/tools/setup_motor.py b/tools/setup_motor.py new file mode 100644 index 00000000..32b74b0c --- /dev/null +++ b/tools/setup_motor.py @@ -0,0 +1,389 @@ +"""Motor setup script for the Reachy Mini robot. + +This script allows to configure the motors of the Reachy Mini robot by setting their ID, baudrate, offset, angle limits, return delay time, and removing the input voltage error. + +The motor needs to be configured one by one, so you will need to connect only one motor at a time to the serial port. You can specify which motor to configure by passing its name as an argument. + +If not specified, it assumes the motor is in the factory settings (ID 1 and baudrate 57600). If it's not the case, you will need to use a tool like Dynamixel Wizard to first reset it or manually specify the ID and baudrate. + +Please note that all values given in the configuration file are in the motor's raw units. +""" + +import argparse +import time +from pathlib import Path + +from rustypot import Xl330PyController + +from reachy_mini.utils.hardware_config.parser import MotorConfig, parse_yaml_config + +FACTORY_DEFAULT_ID = 1 +FACTORY_DEFAULT_BAUDRATE = 57600 +SERIAL_TIMEOUT = 0.01 # seconds +MOTOR_SETUP_DELAY = 0.1 # seconds + +XL_BAUDRATE_CONV_TABLE = { + 9600: 0, + 57600: 1, + 115200: 2, + 1000000: 3, + 2000000: 4, + 3000000: 5, + 4000000: 6, +} + + +def setup_motor( + motor_config: MotorConfig, + serial_port: str, + from_baudrate: int, + target_baudrate: int, + from_id: int, +): + """Set up the motor with the given configuration.""" + if not lookup_for_motor( + serial_port, + from_id, + from_baudrate, + ): + raise RuntimeError( + f"No motor found on port {serial_port}. " + f"Make sure the motor is in factory settings (ID {from_id} and baudrate {from_baudrate}) and connected to the specified port." + ) + + # Make sure the torque is disabled to be able to write EEPROM + disable_torque(serial_port, from_id, from_baudrate) + try: + if from_baudrate != target_baudrate: + change_baudrate( + serial_port, + id=from_id, + base_baudrate=from_baudrate, + target_baudrate=target_baudrate, + ) + time.sleep(MOTOR_SETUP_DELAY) + + if from_id != motor_config.id: + change_id( + serial_port, + current_id=from_id, + new_id=motor_config.id, + baudrate=target_baudrate, + ) + time.sleep(MOTOR_SETUP_DELAY) + + change_offset( + serial_port, + id=motor_config.id, + offset=motor_config.offset, + baudrate=target_baudrate, + ) + + time.sleep(MOTOR_SETUP_DELAY) + + change_angle_limits( + serial_port, + id=motor_config.id, + angle_limit_min=motor_config.angle_limit_min, + angle_limit_max=motor_config.angle_limit_max, + baudrate=target_baudrate, + ) + + time.sleep(MOTOR_SETUP_DELAY) + + change_shutdown_error( + serial_port, + id=motor_config.id, + baudrate=target_baudrate, + shutdown_error=motor_config.shutdown_error, + ) + + time.sleep(MOTOR_SETUP_DELAY) + + change_return_delay_time( + serial_port, + id=motor_config.id, + return_delay_time=motor_config.return_delay_time, + baudrate=target_baudrate, + ) + + time.sleep(MOTOR_SETUP_DELAY) + except Exception as e: + print(f"Error while setting up motor ID {from_id}: {e}") + raise e + + +def lookup_for_motor( + serial_port: str, id: int, baudrate: int, silent: bool = False +) -> bool: + """Check if a motor with the given ID is reachable on the specified serial port.""" + if not silent: + print( + f"Looking for motor with ID {id} on port {serial_port}...", + end="", + flush=True, + ) + c = Xl330PyController(serial_port, baudrate=baudrate, timeout=SERIAL_TIMEOUT) + ret = c.ping(id) + if not silent: + print(f"{'✅' if ret else '❌'}") + return ret + + +def disable_torque(serial_port: str, id: int, baudrate: int): + """Disable the torque of the motor with the given ID on the specified serial port.""" + print(f"Disabling torque for motor with ID {id}...", end="", flush=True) + c = Xl330PyController(serial_port, baudrate=baudrate, timeout=SERIAL_TIMEOUT) + c.write_torque_enable(id, False) + print("✅") + + +def change_baudrate( + serial_port: str, id: int, base_baudrate: int, target_baudrate: int +): + """Change the baudrate of the motor with the given ID on the specified serial port.""" + print(f"Changing baudrate to {target_baudrate}...", end="", flush=True) + c = Xl330PyController(serial_port, baudrate=base_baudrate, timeout=SERIAL_TIMEOUT) + c.write_baud_rate(id, XL_BAUDRATE_CONV_TABLE[target_baudrate]) + print("✅") + + +def change_id(serial_port: str, current_id: int, new_id: int, baudrate: int): + """Change the ID of the motor with the given current ID on the specified serial port.""" + print(f"Changing ID from {current_id} to {new_id}...", end="", flush=True) + c = Xl330PyController(serial_port, baudrate=baudrate, timeout=SERIAL_TIMEOUT) + c.write_id(current_id, new_id) + print("✅") + + +def change_offset(serial_port: str, id: int, offset: int, baudrate: int): + """Change the offset of the motor with the given ID on the specified serial port.""" + print(f"Changing offset for motor with ID {id} to {offset}...", end="", flush=True) + c = Xl330PyController(serial_port, baudrate=baudrate, timeout=SERIAL_TIMEOUT) + c.write_homing_offset(id, offset) + print("✅") + + +def change_angle_limits( + serial_port: str, + id: int, + angle_limit_min: int, + angle_limit_max: int, + baudrate: int, +): + """Change the angle limits of the motor with the given ID on the specified serial port.""" + print( + f"Changing angle limits for motor with ID {id} to [{angle_limit_min}, {angle_limit_max}]...", + end="", + flush=True, + ) + c = Xl330PyController(serial_port, baudrate=baudrate, timeout=SERIAL_TIMEOUT) + c.write_raw_min_position_limit(id, angle_limit_min) + c.write_raw_max_position_limit(id, angle_limit_max) + print("✅") + + +def change_shutdown_error( + serial_port: str, id: int, baudrate: int, shutdown_error: int +): + """Change the shutdown error of the motor with the given ID on the specified serial port.""" + print( + f"Changing shutdown error for motor with ID {id} to {shutdown_error}...", + end="", + flush=True, + ) + c = Xl330PyController(serial_port, baudrate=baudrate, timeout=SERIAL_TIMEOUT) + c.write_shutdown(id, shutdown_error) + print("✅") + + +def change_return_delay_time( + serial_port: str, id: int, return_delay_time: int, baudrate: int +): + """Change the return delay time of the motor with the given ID on the specified serial port.""" + print( + f"Changing return delay time for motor with ID {id} to {return_delay_time}...", + end="", + flush=True, + ) + c = Xl330PyController(serial_port, baudrate=baudrate, timeout=SERIAL_TIMEOUT) + c.write_return_delay_time(id, return_delay_time) + print("✅") + + +def light_led_up(serial_port: str, id: int, baudrate: int): + """Light the LED of the motor with the given ID on the specified serial port.""" + c = Xl330PyController(serial_port, baudrate=baudrate, timeout=SERIAL_TIMEOUT) + + trials = 0 + + while trials < 3: + try: + c.write_led(id, 1) + break + except RuntimeError as e: + print(f"Error while turning on LED for motor ID {id}: {e}") + trials += 1 + + +def light_led_down(serial_port: str, id: int, baudrate: int): + """Light the LED of the motor with the given ID on the specified serial port.""" + c = Xl330PyController(serial_port, baudrate=baudrate, timeout=SERIAL_TIMEOUT) + trials = 0 + + while trials < 3: + try: + c.write_led(id, 0) + break + except RuntimeError as e: + print(f"Error while turning off LED for motor ID {id}: {e}") + trials += 1 + + +def check_configuration(motor_config: MotorConfig, serial_port: str, baudrate: int): + """Check the configuration of the motor with the given ID on the specified serial port.""" + c = Xl330PyController(serial_port, baudrate=baudrate, timeout=SERIAL_TIMEOUT) + + print("Checking configuration...") + + # Check if there is a motor with the desired ID + if not c.ping(motor_config.id): + raise RuntimeError(f"No motor with ID {motor_config.id} found, cannot proceed") + print(f"Found motor with ID {motor_config.id} ✅.") + + # Read return delay time + return_delay = c.read_return_delay_time(motor_config.id)[0] + if return_delay != motor_config.return_delay_time: + raise RuntimeError( + f"Return delay time is {return_delay}, expected {motor_config.return_delay_time}" + ) + print(f"Return delay time is correct: {return_delay} ✅.") + + # Read angle limits + angle_limit_min = c.read_raw_min_position_limit(motor_config.id)[0] + angle_limit_max = c.read_raw_max_position_limit(motor_config.id)[0] + if angle_limit_min != motor_config.angle_limit_min: + raise RuntimeError( + f"Angle limit min is {angle_limit_min}, expected {motor_config.angle_limit_min}" + ) + if angle_limit_max != motor_config.angle_limit_max: + raise RuntimeError( + f"Angle limit max is {angle_limit_max}, expected {motor_config.angle_limit_max}" + ) + print( + f"Angle limits are correct: [{motor_config.angle_limit_min}, {motor_config.angle_limit_max}] ✅." + ) + + # Read homing offset + offset = c.read_homing_offset(motor_config.id)[0] + if offset != motor_config.offset: + raise RuntimeError(f"Homing offset is {offset}, expected {motor_config.offset}") + print(f"Homing offset is correct: {offset} ✅.") + + # Read shutdown + shutdown = c.read_shutdown(motor_config.id)[0] + if shutdown != motor_config.shutdown_error: + raise RuntimeError( + f"Shutdown is {shutdown}, expected {motor_config.shutdown_error}" + ) + print(f"Shutdown error is correct: {shutdown} ✅.") + + print("Configuration is correct ✅!") + + +def run(args): + """Entry point for the Reachy Mini motor configuration tool.""" + config = parse_yaml_config(args.config_file) + + if args.motor_name == "all": + motors = list(config.motors.keys()) + else: + motors = [args.motor_name] + + for motor_name in motors: + motor_config = config.motors[motor_name] + + if args.update_config: + args.from_id = motor_config.id + args.from_baudrate = config.serial.baudrate + + if not args.check_only: + setup_motor( + motor_config, + args.serialport, + from_id=args.from_id, + from_baudrate=args.from_baudrate, + target_baudrate=config.serial.baudrate, + ) + + try: + check_configuration( + motor_config, + args.serialport, + baudrate=config.serial.baudrate, + ) + except RuntimeError as e: + print(f"❌ Configuration check failed for motor '{motor_name}': {e}") + return + + light_led_up( + args.serialport, + motor_config.id, + baudrate=config.serial.baudrate, + ) + + +if __name__ == "__main__": + """Entry point for the Reachy Mini motor configuration tool.""" + parser = argparse.ArgumentParser(description="Motor Configuration tool") + parser.add_argument( + "config_file", + type=Path, + help="Path to the hardware configuration file (default: hardware_config.yaml).", + ) + parser.add_argument( + "motor_name", + type=str, + help="Name of the motor to configure.", + choices=[ + "body_rotation", + "stewart_1", + "stewart_2", + "stewart_3", + "stewart_4", + "stewart_5", + "stewart_6", + "right_antenna", + "left_antenna", + "all", + ], + ) + parser.add_argument( + "serialport", + type=str, + help="Serial port for communication with the motor.", + ) + parser.add_argument( + "--check-only", + action="store_true", + help="Only check the configuration without applying changes.", + ) + parser.add_argument( + "--from-id", + type=int, + default=FACTORY_DEFAULT_ID, + help=f"Current ID of the motor (default: {FACTORY_DEFAULT_ID}).", + ) + parser.add_argument( + "--from-baudrate", + type=int, + default=FACTORY_DEFAULT_BAUDRATE, + help=f"Current baudrate of the motor (default: {FACTORY_DEFAULT_BAUDRATE}).", + ) + parser.add_argument( + "--update-config", + action="store_true", + help="Update a specific motor (assumes it already has the correct id and baudrate).", + ) + args = parser.parse_args() + run(args) diff --git a/tools/setup_motor_rpi.py b/tools/setup_motor_rpi.py new file mode 100644 index 00000000..6d865de3 --- /dev/null +++ b/tools/setup_motor_rpi.py @@ -0,0 +1,108 @@ +"""You need to `pip install gpiozero lgpio`.""" + +import argparse +import os +import time +from typing import List + +import numpy as np +from gpiozero import DigitalOutputDevice +from setup_motor import ( + FACTORY_DEFAULT_BAUDRATE, + FACTORY_DEFAULT_ID, + light_led_down, + lookup_for_motor, + parse_yaml_config, + run, +) + +assets_root_path = "../src/reachy_mini/assets/" + + +UART_PORT = "/dev/ttyAMA3" +CONFIG_FILE_PATH = os.path.join(assets_root_path, "config", "hardware_config.yaml") + +ID_TO_CHANNEL = { + 10: 0, + 11: 1, + 12: 2, + 13: 3, + 14: 4, + 15: 5, + 16: 6, + 17: 7, + 18: 8, +} +CHANNEL_TO_ID = {v: k for k, v in ID_TO_CHANNEL.items()} + +S0 = DigitalOutputDevice(25) +S1 = DigitalOutputDevice(8) +S2 = DigitalOutputDevice(7) +S3 = DigitalOutputDevice(1) + + +def get_channel_binary(channel) -> List[int]: + """Convert channel number (0-8) to 4-bit binary representation.""" + assert channel in np.arange(9), "Channel must be between 0 and 8" + bits = [int(b) for b in f"{channel:04b}"] # 4-bit binary + return bits[::-1] # flip the order + + +def select_channel(channel: int): + """Select a channel on the multiplexer.""" + bits = get_channel_binary(channel) + S0.on() if bits[0] else S0.off() + S1.on() if bits[1] else S1.off() + S2.on() if bits[2] else S2.off() + S3.on() if bits[3] else S3.off() + + +def main(): + """Scan all channels of the multiplexer to find motors in factory default state, and set them up one by one.""" + config = parse_yaml_config(CONFIG_FILE_PATH) + motor_name_to_id = {m: config.motors[m].id for m in config.motors} + id_to_motor_name = {v: k for k, v in motor_name_to_id.items()} + + print("Starting motor setup...") + current_channel = 0 + while True: + current_channel = (current_channel + 1) % 9 + select_channel(current_channel) + target_id = CHANNEL_TO_ID[current_channel] + target_name = id_to_motor_name[target_id] + if lookup_for_motor( + UART_PORT, FACTORY_DEFAULT_ID, FACTORY_DEFAULT_BAUDRATE, silent=True + ): + print(f"Found motor on channel {current_channel}!") + args = argparse.Namespace( + config_file=CONFIG_FILE_PATH, + motor_name=target_name, + serialport=UART_PORT, + check_only=False, + from_id=FACTORY_DEFAULT_ID, + from_baudrate=FACTORY_DEFAULT_BAUDRATE, + update_config=False, + ) + run(args) + + elif lookup_for_motor(UART_PORT, current_channel + 10, 1000000, silent=True): + print(f"Motor on channel {current_channel} already set up.") + # light_led_up(UART_PORT, current_channel+10, 1000000) + light_led_down(UART_PORT, current_channel + 10, 1000000) + args = argparse.Namespace( + config_file=CONFIG_FILE_PATH, + motor_name=target_name, + serialport=UART_PORT, + check_only=False, + from_id=current_channel + 10, + from_baudrate=1000000, + update_config=False, + ) + run(args) + time.sleep(2) + + time.sleep(0.01) + + +if __name__ == "__main__": + main() diff --git a/uv.lock b/uv.lock new file mode 100644 index 00000000..c88e5f22 --- /dev/null +++ b/uv.lock @@ -0,0 +1,3988 @@ +version = 1 +revision = 3 +requires-python = ">=3.10" +resolution-markers = [ + "python_full_version >= '3.13'", + "python_full_version >= '3.11' and python_full_version < '3.13'", + "python_full_version < '3.11'", +] + +[[package]] +name = "absl-py" +version = "2.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/10/2a/c93173ffa1b39c1d0395b7e842bbdc62e556ca9d8d3b5572926f3e4ca752/absl_py-2.3.1.tar.gz", hash = "sha256:a97820526f7fbfd2ec1bce83f3f25e3a14840dac0d8e02a0b71cd75db3f77fc9", size = 116588, upload-time = "2025-07-03T09:31:44.05Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/aa/ba0014cc4659328dc818a28827be78e6d97312ab0cb98105a770924dc11e/absl_py-2.3.1-py3-none-any.whl", hash = "sha256:eeecf07f0c2a93ace0772c92e596ace6d3d3996c042b2128459aaae2a76de11d", size = 135811, upload-time = "2025-07-03T09:31:42.253Z" }, +] + +[[package]] +name = "ahrs" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docutils" }, + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dc/29/1232576b1f6769bf7d1436e5ae5f7234b56bb7bbc255b68a483165090cf3/ahrs-0.4.0.tar.gz", hash = "sha256:4b632b9e53b9cfd1cecadefa543a3534574baeafeef048db703ab33a07dcca21", size = 530456, upload-time = "2025-10-13T11:05:58.675Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/76/931b879e1ad5e287c4ed9b60ccea807e63e06cd257829e154c79394abb66/ahrs-0.4.0-py3-none-any.whl", hash = "sha256:7be83963d8021ae326d20b646c05a2218401e559f645bb5c2e40ff7dd54245d7", size = 244694, upload-time = "2025-10-13T11:05:57.097Z" }, +] + +[[package]] +name = "aiohappyeyeballs" +version = "2.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760, upload-time = "2025-03-12T01:42:48.764Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265, upload-time = "2025-03-12T01:42:47.083Z" }, +] + +[[package]] +name = "aiohttp" +version = "3.12.15" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohappyeyeballs" }, + { name = "aiosignal" }, + { name = "async-timeout", marker = "python_full_version < '3.11'" }, + { name = "attrs" }, + { name = "frozenlist" }, + { name = "multidict" }, + { name = "propcache" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9b/e7/d92a237d8802ca88483906c388f7c201bbe96cd80a165ffd0ac2f6a8d59f/aiohttp-3.12.15.tar.gz", hash = "sha256:4fc61385e9c98d72fcdf47e6dd81833f47b2f77c114c29cd64a361be57a763a2", size = 7823716, upload-time = "2025-07-29T05:52:32.215Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/dc/ef9394bde9080128ad401ac7ede185267ed637df03b51f05d14d1c99ad67/aiohttp-3.12.15-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b6fc902bff74d9b1879ad55f5404153e2b33a82e72a95c89cec5eb6cc9e92fbc", size = 703921, upload-time = "2025-07-29T05:49:43.584Z" }, + { url = "https://files.pythonhosted.org/packages/8f/42/63fccfc3a7ed97eb6e1a71722396f409c46b60a0552d8a56d7aad74e0df5/aiohttp-3.12.15-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:098e92835b8119b54c693f2f88a1dec690e20798ca5f5fe5f0520245253ee0af", size = 480288, upload-time = "2025-07-29T05:49:47.851Z" }, + { url = "https://files.pythonhosted.org/packages/9c/a2/7b8a020549f66ea2a68129db6960a762d2393248f1994499f8ba9728bbed/aiohttp-3.12.15-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:40b3fee496a47c3b4a39a731954c06f0bd9bd3e8258c059a4beb76ac23f8e421", size = 468063, upload-time = "2025-07-29T05:49:49.789Z" }, + { url = "https://files.pythonhosted.org/packages/8f/f5/d11e088da9176e2ad8220338ae0000ed5429a15f3c9dfd983f39105399cd/aiohttp-3.12.15-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ce13fcfb0bb2f259fb42106cdc63fa5515fb85b7e87177267d89a771a660b79", size = 1650122, upload-time = "2025-07-29T05:49:51.874Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6b/b60ce2757e2faed3d70ed45dafee48cee7bfb878785a9423f7e883f0639c/aiohttp-3.12.15-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3beb14f053222b391bf9cf92ae82e0171067cc9c8f52453a0f1ec7c37df12a77", size = 1624176, upload-time = "2025-07-29T05:49:53.805Z" }, + { url = "https://files.pythonhosted.org/packages/dd/de/8c9fde2072a1b72c4fadecf4f7d4be7a85b1d9a4ab333d8245694057b4c6/aiohttp-3.12.15-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c39e87afe48aa3e814cac5f535bc6199180a53e38d3f51c5e2530f5aa4ec58c", size = 1696583, upload-time = "2025-07-29T05:49:55.338Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ad/07f863ca3d895a1ad958a54006c6dafb4f9310f8c2fdb5f961b8529029d3/aiohttp-3.12.15-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5f1b4ce5bc528a6ee38dbf5f39bbf11dd127048726323b72b8e85769319ffc4", size = 1738896, upload-time = "2025-07-29T05:49:57.045Z" }, + { url = "https://files.pythonhosted.org/packages/20/43/2bd482ebe2b126533e8755a49b128ec4e58f1a3af56879a3abdb7b42c54f/aiohttp-3.12.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1004e67962efabbaf3f03b11b4c43b834081c9e3f9b32b16a7d97d4708a9abe6", size = 1643561, upload-time = "2025-07-29T05:49:58.762Z" }, + { url = "https://files.pythonhosted.org/packages/23/40/2fa9f514c4cf4cbae8d7911927f81a1901838baf5e09a8b2c299de1acfe5/aiohttp-3.12.15-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8faa08fcc2e411f7ab91d1541d9d597d3a90e9004180edb2072238c085eac8c2", size = 1583685, upload-time = "2025-07-29T05:50:00.375Z" }, + { url = "https://files.pythonhosted.org/packages/b8/c3/94dc7357bc421f4fb978ca72a201a6c604ee90148f1181790c129396ceeb/aiohttp-3.12.15-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:fe086edf38b2222328cdf89af0dde2439ee173b8ad7cb659b4e4c6f385b2be3d", size = 1627533, upload-time = "2025-07-29T05:50:02.306Z" }, + { url = "https://files.pythonhosted.org/packages/bf/3f/1f8911fe1844a07001e26593b5c255a685318943864b27b4e0267e840f95/aiohttp-3.12.15-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:79b26fe467219add81d5e47b4a4ba0f2394e8b7c7c3198ed36609f9ba161aecb", size = 1638319, upload-time = "2025-07-29T05:50:04.282Z" }, + { url = "https://files.pythonhosted.org/packages/4e/46/27bf57a99168c4e145ffee6b63d0458b9c66e58bb70687c23ad3d2f0bd17/aiohttp-3.12.15-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b761bac1192ef24e16706d761aefcb581438b34b13a2f069a6d343ec8fb693a5", size = 1613776, upload-time = "2025-07-29T05:50:05.863Z" }, + { url = "https://files.pythonhosted.org/packages/0f/7e/1d2d9061a574584bb4ad3dbdba0da90a27fdc795bc227def3a46186a8bc1/aiohttp-3.12.15-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e153e8adacfe2af562861b72f8bc47f8a5c08e010ac94eebbe33dc21d677cd5b", size = 1693359, upload-time = "2025-07-29T05:50:07.563Z" }, + { url = "https://files.pythonhosted.org/packages/08/98/bee429b52233c4a391980a5b3b196b060872a13eadd41c3a34be9b1469ed/aiohttp-3.12.15-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:fc49c4de44977aa8601a00edbf157e9a421f227aa7eb477d9e3df48343311065", size = 1716598, upload-time = "2025-07-29T05:50:09.33Z" }, + { url = "https://files.pythonhosted.org/packages/57/39/b0314c1ea774df3392751b686104a3938c63ece2b7ce0ba1ed7c0b4a934f/aiohttp-3.12.15-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2776c7ec89c54a47029940177e75c8c07c29c66f73464784971d6a81904ce9d1", size = 1644940, upload-time = "2025-07-29T05:50:11.334Z" }, + { url = "https://files.pythonhosted.org/packages/1b/83/3dacb8d3f8f512c8ca43e3fa8a68b20583bd25636ffa4e56ee841ffd79ae/aiohttp-3.12.15-cp310-cp310-win32.whl", hash = "sha256:2c7d81a277fa78b2203ab626ced1487420e8c11a8e373707ab72d189fcdad20a", size = 429239, upload-time = "2025-07-29T05:50:12.803Z" }, + { url = "https://files.pythonhosted.org/packages/eb/f9/470b5daba04d558c9673ca2034f28d067f3202a40e17804425f0c331c89f/aiohttp-3.12.15-cp310-cp310-win_amd64.whl", hash = "sha256:83603f881e11f0f710f8e2327817c82e79431ec976448839f3cd05d7afe8f830", size = 452297, upload-time = "2025-07-29T05:50:14.266Z" }, + { url = "https://files.pythonhosted.org/packages/20/19/9e86722ec8e835959bd97ce8c1efa78cf361fa4531fca372551abcc9cdd6/aiohttp-3.12.15-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d3ce17ce0220383a0f9ea07175eeaa6aa13ae5a41f30bc61d84df17f0e9b1117", size = 711246, upload-time = "2025-07-29T05:50:15.937Z" }, + { url = "https://files.pythonhosted.org/packages/71/f9/0a31fcb1a7d4629ac9d8f01f1cb9242e2f9943f47f5d03215af91c3c1a26/aiohttp-3.12.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:010cc9bbd06db80fe234d9003f67e97a10fe003bfbedb40da7d71c1008eda0fe", size = 483515, upload-time = "2025-07-29T05:50:17.442Z" }, + { url = "https://files.pythonhosted.org/packages/62/6c/94846f576f1d11df0c2e41d3001000527c0fdf63fce7e69b3927a731325d/aiohttp-3.12.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3f9d7c55b41ed687b9d7165b17672340187f87a773c98236c987f08c858145a9", size = 471776, upload-time = "2025-07-29T05:50:19.568Z" }, + { url = "https://files.pythonhosted.org/packages/f8/6c/f766d0aaafcee0447fad0328da780d344489c042e25cd58fde566bf40aed/aiohttp-3.12.15-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc4fbc61bb3548d3b482f9ac7ddd0f18c67e4225aaa4e8552b9f1ac7e6bda9e5", size = 1741977, upload-time = "2025-07-29T05:50:21.665Z" }, + { url = "https://files.pythonhosted.org/packages/17/e5/fb779a05ba6ff44d7bc1e9d24c644e876bfff5abe5454f7b854cace1b9cc/aiohttp-3.12.15-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7fbc8a7c410bb3ad5d595bb7118147dfbb6449d862cc1125cf8867cb337e8728", size = 1690645, upload-time = "2025-07-29T05:50:23.333Z" }, + { url = "https://files.pythonhosted.org/packages/37/4e/a22e799c2035f5d6a4ad2cf8e7c1d1bd0923192871dd6e367dafb158b14c/aiohttp-3.12.15-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:74dad41b3458dbb0511e760fb355bb0b6689e0630de8a22b1b62a98777136e16", size = 1789437, upload-time = "2025-07-29T05:50:25.007Z" }, + { url = "https://files.pythonhosted.org/packages/28/e5/55a33b991f6433569babb56018b2fb8fb9146424f8b3a0c8ecca80556762/aiohttp-3.12.15-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b6f0af863cf17e6222b1735a756d664159e58855da99cfe965134a3ff63b0b0", size = 1828482, upload-time = "2025-07-29T05:50:26.693Z" }, + { url = "https://files.pythonhosted.org/packages/c6/82/1ddf0ea4f2f3afe79dffed5e8a246737cff6cbe781887a6a170299e33204/aiohttp-3.12.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5b7fe4972d48a4da367043b8e023fb70a04d1490aa7d68800e465d1b97e493b", size = 1730944, upload-time = "2025-07-29T05:50:28.382Z" }, + { url = "https://files.pythonhosted.org/packages/1b/96/784c785674117b4cb3877522a177ba1b5e4db9ce0fd519430b5de76eec90/aiohttp-3.12.15-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6443cca89553b7a5485331bc9bedb2342b08d073fa10b8c7d1c60579c4a7b9bd", size = 1668020, upload-time = "2025-07-29T05:50:30.032Z" }, + { url = "https://files.pythonhosted.org/packages/12/8a/8b75f203ea7e5c21c0920d84dd24a5c0e971fe1e9b9ebbf29ae7e8e39790/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6c5f40ec615e5264f44b4282ee27628cea221fcad52f27405b80abb346d9f3f8", size = 1716292, upload-time = "2025-07-29T05:50:31.983Z" }, + { url = "https://files.pythonhosted.org/packages/47/0b/a1451543475bb6b86a5cfc27861e52b14085ae232896a2654ff1231c0992/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:2abbb216a1d3a2fe86dbd2edce20cdc5e9ad0be6378455b05ec7f77361b3ab50", size = 1711451, upload-time = "2025-07-29T05:50:33.989Z" }, + { url = "https://files.pythonhosted.org/packages/55/fd/793a23a197cc2f0d29188805cfc93aa613407f07e5f9da5cd1366afd9d7c/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:db71ce547012a5420a39c1b744d485cfb823564d01d5d20805977f5ea1345676", size = 1691634, upload-time = "2025-07-29T05:50:35.846Z" }, + { url = "https://files.pythonhosted.org/packages/ca/bf/23a335a6670b5f5dfc6d268328e55a22651b440fca341a64fccf1eada0c6/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ced339d7c9b5030abad5854aa5413a77565e5b6e6248ff927d3e174baf3badf7", size = 1785238, upload-time = "2025-07-29T05:50:37.597Z" }, + { url = "https://files.pythonhosted.org/packages/57/4f/ed60a591839a9d85d40694aba5cef86dde9ee51ce6cca0bb30d6eb1581e7/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:7c7dd29c7b5bda137464dc9bfc738d7ceea46ff70309859ffde8c022e9b08ba7", size = 1805701, upload-time = "2025-07-29T05:50:39.591Z" }, + { url = "https://files.pythonhosted.org/packages/85/e0/444747a9455c5de188c0f4a0173ee701e2e325d4b2550e9af84abb20cdba/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:421da6fd326460517873274875c6c5a18ff225b40da2616083c5a34a7570b685", size = 1718758, upload-time = "2025-07-29T05:50:41.292Z" }, + { url = "https://files.pythonhosted.org/packages/36/ab/1006278d1ffd13a698e5dd4bfa01e5878f6bddefc296c8b62649753ff249/aiohttp-3.12.15-cp311-cp311-win32.whl", hash = "sha256:4420cf9d179ec8dfe4be10e7d0fe47d6d606485512ea2265b0d8c5113372771b", size = 428868, upload-time = "2025-07-29T05:50:43.063Z" }, + { url = "https://files.pythonhosted.org/packages/10/97/ad2b18700708452400278039272032170246a1bf8ec5d832772372c71f1a/aiohttp-3.12.15-cp311-cp311-win_amd64.whl", hash = "sha256:edd533a07da85baa4b423ee8839e3e91681c7bfa19b04260a469ee94b778bf6d", size = 453273, upload-time = "2025-07-29T05:50:44.613Z" }, + { url = "https://files.pythonhosted.org/packages/63/97/77cb2450d9b35f517d6cf506256bf4f5bda3f93a66b4ad64ba7fc917899c/aiohttp-3.12.15-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:802d3868f5776e28f7bf69d349c26fc0efadb81676d0afa88ed00d98a26340b7", size = 702333, upload-time = "2025-07-29T05:50:46.507Z" }, + { url = "https://files.pythonhosted.org/packages/83/6d/0544e6b08b748682c30b9f65640d006e51f90763b41d7c546693bc22900d/aiohttp-3.12.15-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2800614cd560287be05e33a679638e586a2d7401f4ddf99e304d98878c29444", size = 476948, upload-time = "2025-07-29T05:50:48.067Z" }, + { url = "https://files.pythonhosted.org/packages/3a/1d/c8c40e611e5094330284b1aea8a4b02ca0858f8458614fa35754cab42b9c/aiohttp-3.12.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8466151554b593909d30a0a125d638b4e5f3836e5aecde85b66b80ded1cb5b0d", size = 469787, upload-time = "2025-07-29T05:50:49.669Z" }, + { url = "https://files.pythonhosted.org/packages/38/7d/b76438e70319796bfff717f325d97ce2e9310f752a267bfdf5192ac6082b/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e5a495cb1be69dae4b08f35a6c4579c539e9b5706f606632102c0f855bcba7c", size = 1716590, upload-time = "2025-07-29T05:50:51.368Z" }, + { url = "https://files.pythonhosted.org/packages/79/b1/60370d70cdf8b269ee1444b390cbd72ce514f0d1cd1a715821c784d272c9/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6404dfc8cdde35c69aaa489bb3542fb86ef215fc70277c892be8af540e5e21c0", size = 1699241, upload-time = "2025-07-29T05:50:53.628Z" }, + { url = "https://files.pythonhosted.org/packages/a3/2b/4968a7b8792437ebc12186db31523f541943e99bda8f30335c482bea6879/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3ead1c00f8521a5c9070fcb88f02967b1d8a0544e6d85c253f6968b785e1a2ab", size = 1754335, upload-time = "2025-07-29T05:50:55.394Z" }, + { url = "https://files.pythonhosted.org/packages/fb/c1/49524ed553f9a0bec1a11fac09e790f49ff669bcd14164f9fab608831c4d/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6990ef617f14450bc6b34941dba4f12d5613cbf4e33805932f853fbd1cf18bfb", size = 1800491, upload-time = "2025-07-29T05:50:57.202Z" }, + { url = "https://files.pythonhosted.org/packages/de/5e/3bf5acea47a96a28c121b167f5ef659cf71208b19e52a88cdfa5c37f1fcc/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd736ed420f4db2b8148b52b46b88ed038d0354255f9a73196b7bbce3ea97545", size = 1719929, upload-time = "2025-07-29T05:50:59.192Z" }, + { url = "https://files.pythonhosted.org/packages/39/94/8ae30b806835bcd1cba799ba35347dee6961a11bd507db634516210e91d8/aiohttp-3.12.15-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c5092ce14361a73086b90c6efb3948ffa5be2f5b6fbcf52e8d8c8b8848bb97c", size = 1635733, upload-time = "2025-07-29T05:51:01.394Z" }, + { url = "https://files.pythonhosted.org/packages/7a/46/06cdef71dd03acd9da7f51ab3a9107318aee12ad38d273f654e4f981583a/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:aaa2234bb60c4dbf82893e934d8ee8dea30446f0647e024074237a56a08c01bd", size = 1696790, upload-time = "2025-07-29T05:51:03.657Z" }, + { url = "https://files.pythonhosted.org/packages/02/90/6b4cfaaf92ed98d0ec4d173e78b99b4b1a7551250be8937d9d67ecb356b4/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6d86a2fbdd14192e2f234a92d3b494dd4457e683ba07e5905a0b3ee25389ac9f", size = 1718245, upload-time = "2025-07-29T05:51:05.911Z" }, + { url = "https://files.pythonhosted.org/packages/2e/e6/2593751670fa06f080a846f37f112cbe6f873ba510d070136a6ed46117c6/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a041e7e2612041a6ddf1c6a33b883be6a421247c7afd47e885969ee4cc58bd8d", size = 1658899, upload-time = "2025-07-29T05:51:07.753Z" }, + { url = "https://files.pythonhosted.org/packages/8f/28/c15bacbdb8b8eb5bf39b10680d129ea7410b859e379b03190f02fa104ffd/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5015082477abeafad7203757ae44299a610e89ee82a1503e3d4184e6bafdd519", size = 1738459, upload-time = "2025-07-29T05:51:09.56Z" }, + { url = "https://files.pythonhosted.org/packages/00/de/c269cbc4faa01fb10f143b1670633a8ddd5b2e1ffd0548f7aa49cb5c70e2/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:56822ff5ddfd1b745534e658faba944012346184fbfe732e0d6134b744516eea", size = 1766434, upload-time = "2025-07-29T05:51:11.423Z" }, + { url = "https://files.pythonhosted.org/packages/52/b0/4ff3abd81aa7d929b27d2e1403722a65fc87b763e3a97b3a2a494bfc63bc/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b2acbbfff69019d9014508c4ba0401822e8bae5a5fdc3b6814285b71231b60f3", size = 1726045, upload-time = "2025-07-29T05:51:13.689Z" }, + { url = "https://files.pythonhosted.org/packages/71/16/949225a6a2dd6efcbd855fbd90cf476052e648fb011aa538e3b15b89a57a/aiohttp-3.12.15-cp312-cp312-win32.whl", hash = "sha256:d849b0901b50f2185874b9a232f38e26b9b3d4810095a7572eacea939132d4e1", size = 423591, upload-time = "2025-07-29T05:51:15.452Z" }, + { url = "https://files.pythonhosted.org/packages/2b/d8/fa65d2a349fe938b76d309db1a56a75c4fb8cc7b17a398b698488a939903/aiohttp-3.12.15-cp312-cp312-win_amd64.whl", hash = "sha256:b390ef5f62bb508a9d67cb3bba9b8356e23b3996da7062f1a57ce1a79d2b3d34", size = 450266, upload-time = "2025-07-29T05:51:17.239Z" }, + { url = "https://files.pythonhosted.org/packages/f2/33/918091abcf102e39d15aba2476ad9e7bd35ddb190dcdd43a854000d3da0d/aiohttp-3.12.15-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9f922ffd05034d439dde1c77a20461cf4a1b0831e6caa26151fe7aa8aaebc315", size = 696741, upload-time = "2025-07-29T05:51:19.021Z" }, + { url = "https://files.pythonhosted.org/packages/b5/2a/7495a81e39a998e400f3ecdd44a62107254803d1681d9189be5c2e4530cd/aiohttp-3.12.15-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2ee8a8ac39ce45f3e55663891d4b1d15598c157b4d494a4613e704c8b43112cd", size = 474407, upload-time = "2025-07-29T05:51:21.165Z" }, + { url = "https://files.pythonhosted.org/packages/49/fc/a9576ab4be2dcbd0f73ee8675d16c707cfc12d5ee80ccf4015ba543480c9/aiohttp-3.12.15-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3eae49032c29d356b94eee45a3f39fdf4b0814b397638c2f718e96cfadf4c4e4", size = 466703, upload-time = "2025-07-29T05:51:22.948Z" }, + { url = "https://files.pythonhosted.org/packages/09/2f/d4bcc8448cf536b2b54eed48f19682031ad182faa3a3fee54ebe5b156387/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b97752ff12cc12f46a9b20327104448042fce5c33a624f88c18f66f9368091c7", size = 1705532, upload-time = "2025-07-29T05:51:25.211Z" }, + { url = "https://files.pythonhosted.org/packages/f1/f3/59406396083f8b489261e3c011aa8aee9df360a96ac8fa5c2e7e1b8f0466/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:894261472691d6fe76ebb7fcf2e5870a2ac284c7406ddc95823c8598a1390f0d", size = 1686794, upload-time = "2025-07-29T05:51:27.145Z" }, + { url = "https://files.pythonhosted.org/packages/dc/71/164d194993a8d114ee5656c3b7ae9c12ceee7040d076bf7b32fb98a8c5c6/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5fa5d9eb82ce98959fc1031c28198b431b4d9396894f385cb63f1e2f3f20ca6b", size = 1738865, upload-time = "2025-07-29T05:51:29.366Z" }, + { url = "https://files.pythonhosted.org/packages/1c/00/d198461b699188a93ead39cb458554d9f0f69879b95078dce416d3209b54/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0fa751efb11a541f57db59c1dd821bec09031e01452b2b6217319b3a1f34f3d", size = 1788238, upload-time = "2025-07-29T05:51:31.285Z" }, + { url = "https://files.pythonhosted.org/packages/85/b8/9e7175e1fa0ac8e56baa83bf3c214823ce250d0028955dfb23f43d5e61fd/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5346b93e62ab51ee2a9d68e8f73c7cf96ffb73568a23e683f931e52450e4148d", size = 1710566, upload-time = "2025-07-29T05:51:33.219Z" }, + { url = "https://files.pythonhosted.org/packages/59/e4/16a8eac9df39b48ae102ec030fa9f726d3570732e46ba0c592aeeb507b93/aiohttp-3.12.15-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:049ec0360f939cd164ecbfd2873eaa432613d5e77d6b04535e3d1fbae5a9e645", size = 1624270, upload-time = "2025-07-29T05:51:35.195Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f8/cd84dee7b6ace0740908fd0af170f9fab50c2a41ccbc3806aabcb1050141/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b52dcf013b57464b6d1e51b627adfd69a8053e84b7103a7cd49c030f9ca44461", size = 1677294, upload-time = "2025-07-29T05:51:37.215Z" }, + { url = "https://files.pythonhosted.org/packages/ce/42/d0f1f85e50d401eccd12bf85c46ba84f947a84839c8a1c2c5f6e8ab1eb50/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:9b2af240143dd2765e0fb661fd0361a1b469cab235039ea57663cda087250ea9", size = 1708958, upload-time = "2025-07-29T05:51:39.328Z" }, + { url = "https://files.pythonhosted.org/packages/d5/6b/f6fa6c5790fb602538483aa5a1b86fcbad66244997e5230d88f9412ef24c/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ac77f709a2cde2cc71257ab2d8c74dd157c67a0558a0d2799d5d571b4c63d44d", size = 1651553, upload-time = "2025-07-29T05:51:41.356Z" }, + { url = "https://files.pythonhosted.org/packages/04/36/a6d36ad545fa12e61d11d1932eef273928b0495e6a576eb2af04297fdd3c/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:47f6b962246f0a774fbd3b6b7be25d59b06fdb2f164cf2513097998fc6a29693", size = 1727688, upload-time = "2025-07-29T05:51:43.452Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c8/f195e5e06608a97a4e52c5d41c7927301bf757a8e8bb5bbf8cef6c314961/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:760fb7db442f284996e39cf9915a94492e1896baac44f06ae551974907922b64", size = 1761157, upload-time = "2025-07-29T05:51:45.643Z" }, + { url = "https://files.pythonhosted.org/packages/05/6a/ea199e61b67f25ba688d3ce93f63b49b0a4e3b3d380f03971b4646412fc6/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad702e57dc385cae679c39d318def49aef754455f237499d5b99bea4ef582e51", size = 1710050, upload-time = "2025-07-29T05:51:48.203Z" }, + { url = "https://files.pythonhosted.org/packages/b4/2e/ffeb7f6256b33635c29dbed29a22a723ff2dd7401fff42ea60cf2060abfb/aiohttp-3.12.15-cp313-cp313-win32.whl", hash = "sha256:f813c3e9032331024de2eb2e32a88d86afb69291fbc37a3a3ae81cc9917fb3d0", size = 422647, upload-time = "2025-07-29T05:51:50.718Z" }, + { url = "https://files.pythonhosted.org/packages/1b/8e/78ee35774201f38d5e1ba079c9958f7629b1fd079459aea9467441dbfbf5/aiohttp-3.12.15-cp313-cp313-win_amd64.whl", hash = "sha256:1a649001580bdb37c6fdb1bebbd7e3bc688e8ec2b5c6f52edbb664662b17dc84", size = 449067, upload-time = "2025-07-29T05:51:52.549Z" }, +] + +[[package]] +name = "aiosignal" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "frozenlist" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload-time = "2025-07-03T22:54:43.528Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "anyio" +version = "4.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "idna" }, + { name = "sniffio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f1/b4/636b3b65173d3ce9a38ef5f0522789614e590dab6a8d505340a4efe4c567/anyio-4.10.0.tar.gz", hash = "sha256:3f3fae35c96039744587aa5b8371e7e8e603c0702999535961dd336026973ba6", size = 213252, upload-time = "2025-08-04T08:54:26.451Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6f/12/e5e0282d673bb9746bacfb6e2dba8719989d3660cdb2ea79aee9a9651afb/anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1", size = 107213, upload-time = "2025-08-04T08:54:24.882Z" }, +] + +[[package]] +name = "asgiref" +version = "3.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/90/61/0aa957eec22ff70b830b22ff91f825e70e1ef732c06666a805730f28b36b/asgiref-3.9.1.tar.gz", hash = "sha256:a5ab6582236218e5ef1648f242fd9f10626cfd4de8dc377db215d5d5098e3142", size = 36870, upload-time = "2025-07-08T09:07:43.344Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/3c/0464dcada90d5da0e71018c04a140ad6349558afb30b3051b4264cc5b965/asgiref-3.9.1-py3-none-any.whl", hash = "sha256:f3bba7092a48005b5f5bacd747d36ee4a5a61f4a269a6df590b43144355ebd2c", size = 23790, upload-time = "2025-07-08T09:07:41.548Z" }, +] + +[[package]] +name = "asttokens" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4a/e7/82da0a03e7ba5141f05cce0d302e6eed121ae055e0456ca228bf693984bc/asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7", size = 61978, upload-time = "2024-11-30T04:30:14.439Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2", size = 26918, upload-time = "2024-11-30T04:30:10.946Z" }, +] + +[[package]] +name = "async-timeout" +version = "5.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/ae/136395dfbfe00dfc94da3f3e136d0b13f394cba8f4841120e34226265780/async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3", size = 9274, upload-time = "2024-11-06T16:41:39.6Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/ba/e2081de779ca30d473f21f5b30e0e737c438205440784c7dfc81efc2b029/async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c", size = 6233, upload-time = "2024-11-06T16:41:37.9Z" }, +] + +[[package]] +name = "attrs" +version = "25.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032, upload-time = "2025-03-13T11:10:22.779Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" }, +] + +[[package]] +name = "backports-asyncio-runner" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/ff/70dca7d7cb1cbc0edb2c6cc0c38b65cba36cccc491eca64cabd5fe7f8670/backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162", size = 69893, upload-time = "2025-07-02T02:27:15.685Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/59/76ab57e3fe74484f48a53f8e337171b4a2349e506eabe136d7e01d059086/backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5", size = 12313, upload-time = "2025-07-02T02:27:14.263Z" }, +] + +[[package]] +name = "certifi" +version = "2025.8.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", size = 162386, upload-time = "2025-08-03T03:07:47.08Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216, upload-time = "2025-08-03T03:07:45.777Z" }, +] + +[[package]] +name = "cffi" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/d7/516d984057745a6cd96575eea814fe1edd6646ee6efd552fb7b0921dec83/cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44", size = 184283, upload-time = "2025-09-08T23:22:08.01Z" }, + { url = "https://files.pythonhosted.org/packages/9e/84/ad6a0b408daa859246f57c03efd28e5dd1b33c21737c2db84cae8c237aa5/cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49", size = 180504, upload-time = "2025-09-08T23:22:10.637Z" }, + { url = "https://files.pythonhosted.org/packages/50/bd/b1a6362b80628111e6653c961f987faa55262b4002fcec42308cad1db680/cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c", size = 208811, upload-time = "2025-09-08T23:22:12.267Z" }, + { url = "https://files.pythonhosted.org/packages/4f/27/6933a8b2562d7bd1fb595074cf99cc81fc3789f6a6c05cdabb46284a3188/cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb", size = 216402, upload-time = "2025-09-08T23:22:13.455Z" }, + { url = "https://files.pythonhosted.org/packages/05/eb/b86f2a2645b62adcfff53b0dd97e8dfafb5c8aa864bd0d9a2c2049a0d551/cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0", size = 203217, upload-time = "2025-09-08T23:22:14.596Z" }, + { url = "https://files.pythonhosted.org/packages/9f/e0/6cbe77a53acf5acc7c08cc186c9928864bd7c005f9efd0d126884858a5fe/cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4", size = 203079, upload-time = "2025-09-08T23:22:15.769Z" }, + { url = "https://files.pythonhosted.org/packages/98/29/9b366e70e243eb3d14a5cb488dfd3a0b6b2f1fb001a203f653b93ccfac88/cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453", size = 216475, upload-time = "2025-09-08T23:22:17.427Z" }, + { url = "https://files.pythonhosted.org/packages/21/7a/13b24e70d2f90a322f2900c5d8e1f14fa7e2a6b3332b7309ba7b2ba51a5a/cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495", size = 218829, upload-time = "2025-09-08T23:22:19.069Z" }, + { url = "https://files.pythonhosted.org/packages/60/99/c9dc110974c59cc981b1f5b66e1d8af8af764e00f0293266824d9c4254bc/cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5", size = 211211, upload-time = "2025-09-08T23:22:20.588Z" }, + { url = "https://files.pythonhosted.org/packages/49/72/ff2d12dbf21aca1b32a40ed792ee6b40f6dc3a9cf1644bd7ef6e95e0ac5e/cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb", size = 218036, upload-time = "2025-09-08T23:22:22.143Z" }, + { url = "https://files.pythonhosted.org/packages/e2/cc/027d7fb82e58c48ea717149b03bcadcbdc293553edb283af792bd4bcbb3f/cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a", size = 172184, upload-time = "2025-09-08T23:22:23.328Z" }, + { url = "https://files.pythonhosted.org/packages/33/fa/072dd15ae27fbb4e06b437eb6e944e75b068deb09e2a2826039e49ee2045/cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739", size = 182790, upload-time = "2025-09-08T23:22:24.752Z" }, + { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" }, + { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" }, + { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" }, + { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" }, + { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" }, + { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" }, + { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" }, + { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" }, + { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" }, + { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" }, + { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, + { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, + { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, + { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, + { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, + { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, + { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, + { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, + { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, + { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, + { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, + { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, + { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, + { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, + { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, + { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, + { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, + { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, + { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, +] + +[[package]] +name = "cfgv" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114, upload-time = "2023-08-12T20:38:17.776Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249, upload-time = "2023-08-12T20:38:16.269Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/83/2d/5fd176ceb9b2fc619e63405525573493ca23441330fcdaee6bef9460e924/charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14", size = 122371, upload-time = "2025-08-09T07:57:28.46Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d6/98/f3b8013223728a99b908c9344da3aa04ee6e3fa235f19409033eda92fb78/charset_normalizer-3.4.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72", size = 207695, upload-time = "2025-08-09T07:55:36.452Z" }, + { url = "https://files.pythonhosted.org/packages/21/40/5188be1e3118c82dcb7c2a5ba101b783822cfb413a0268ed3be0468532de/charset_normalizer-3.4.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe", size = 147153, upload-time = "2025-08-09T07:55:38.467Z" }, + { url = "https://files.pythonhosted.org/packages/37/60/5d0d74bc1e1380f0b72c327948d9c2aca14b46a9efd87604e724260f384c/charset_normalizer-3.4.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:07a0eae9e2787b586e129fdcbe1af6997f8d0e5abaa0bc98c0e20e124d67e601", size = 160428, upload-time = "2025-08-09T07:55:40.072Z" }, + { url = "https://files.pythonhosted.org/packages/85/9a/d891f63722d9158688de58d050c59dc3da560ea7f04f4c53e769de5140f5/charset_normalizer-3.4.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:74d77e25adda8581ffc1c720f1c81ca082921329452eba58b16233ab1842141c", size = 157627, upload-time = "2025-08-09T07:55:41.706Z" }, + { url = "https://files.pythonhosted.org/packages/65/1a/7425c952944a6521a9cfa7e675343f83fd82085b8af2b1373a2409c683dc/charset_normalizer-3.4.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0e909868420b7049dafd3a31d45125b31143eec59235311fc4c57ea26a4acd2", size = 152388, upload-time = "2025-08-09T07:55:43.262Z" }, + { url = "https://files.pythonhosted.org/packages/f0/c9/a2c9c2a355a8594ce2446085e2ec97fd44d323c684ff32042e2a6b718e1d/charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c6f162aabe9a91a309510d74eeb6507fab5fff92337a15acbe77753d88d9dcf0", size = 150077, upload-time = "2025-08-09T07:55:44.903Z" }, + { url = "https://files.pythonhosted.org/packages/3b/38/20a1f44e4851aa1c9105d6e7110c9d020e093dfa5836d712a5f074a12bf7/charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4ca4c094de7771a98d7fbd67d9e5dbf1eb73efa4f744a730437d8a3a5cf994f0", size = 161631, upload-time = "2025-08-09T07:55:46.346Z" }, + { url = "https://files.pythonhosted.org/packages/a4/fa/384d2c0f57edad03d7bec3ebefb462090d8905b4ff5a2d2525f3bb711fac/charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:02425242e96bcf29a49711b0ca9f37e451da7c70562bc10e8ed992a5a7a25cc0", size = 159210, upload-time = "2025-08-09T07:55:47.539Z" }, + { url = "https://files.pythonhosted.org/packages/33/9e/eca49d35867ca2db336b6ca27617deed4653b97ebf45dfc21311ce473c37/charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:78deba4d8f9590fe4dae384aeff04082510a709957e968753ff3c48399f6f92a", size = 153739, upload-time = "2025-08-09T07:55:48.744Z" }, + { url = "https://files.pythonhosted.org/packages/2a/91/26c3036e62dfe8de8061182d33be5025e2424002125c9500faff74a6735e/charset_normalizer-3.4.3-cp310-cp310-win32.whl", hash = "sha256:d79c198e27580c8e958906f803e63cddb77653731be08851c7df0b1a14a8fc0f", size = 99825, upload-time = "2025-08-09T07:55:50.305Z" }, + { url = "https://files.pythonhosted.org/packages/e2/c6/f05db471f81af1fa01839d44ae2a8bfeec8d2a8b4590f16c4e7393afd323/charset_normalizer-3.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:c6e490913a46fa054e03699c70019ab869e990270597018cef1d8562132c2669", size = 107452, upload-time = "2025-08-09T07:55:51.461Z" }, + { url = "https://files.pythonhosted.org/packages/7f/b5/991245018615474a60965a7c9cd2b4efbaabd16d582a5547c47ee1c7730b/charset_normalizer-3.4.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b", size = 204483, upload-time = "2025-08-09T07:55:53.12Z" }, + { url = "https://files.pythonhosted.org/packages/c7/2a/ae245c41c06299ec18262825c1569c5d3298fc920e4ddf56ab011b417efd/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64", size = 145520, upload-time = "2025-08-09T07:55:54.712Z" }, + { url = "https://files.pythonhosted.org/packages/3a/a4/b3b6c76e7a635748c4421d2b92c7b8f90a432f98bda5082049af37ffc8e3/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91", size = 158876, upload-time = "2025-08-09T07:55:56.024Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e6/63bb0e10f90a8243c5def74b5b105b3bbbfb3e7bb753915fe333fb0c11ea/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f", size = 156083, upload-time = "2025-08-09T07:55:57.582Z" }, + { url = "https://files.pythonhosted.org/packages/87/df/b7737ff046c974b183ea9aa111b74185ac8c3a326c6262d413bd5a1b8c69/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07", size = 150295, upload-time = "2025-08-09T07:55:59.147Z" }, + { url = "https://files.pythonhosted.org/packages/61/f1/190d9977e0084d3f1dc169acd060d479bbbc71b90bf3e7bf7b9927dec3eb/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30", size = 148379, upload-time = "2025-08-09T07:56:00.364Z" }, + { url = "https://files.pythonhosted.org/packages/4c/92/27dbe365d34c68cfe0ca76f1edd70e8705d82b378cb54ebbaeabc2e3029d/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14", size = 160018, upload-time = "2025-08-09T07:56:01.678Z" }, + { url = "https://files.pythonhosted.org/packages/99/04/baae2a1ea1893a01635d475b9261c889a18fd48393634b6270827869fa34/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c", size = 157430, upload-time = "2025-08-09T07:56:02.87Z" }, + { url = "https://files.pythonhosted.org/packages/2f/36/77da9c6a328c54d17b960c89eccacfab8271fdaaa228305330915b88afa9/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae", size = 151600, upload-time = "2025-08-09T07:56:04.089Z" }, + { url = "https://files.pythonhosted.org/packages/64/d4/9eb4ff2c167edbbf08cdd28e19078bf195762e9bd63371689cab5ecd3d0d/charset_normalizer-3.4.3-cp311-cp311-win32.whl", hash = "sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849", size = 99616, upload-time = "2025-08-09T07:56:05.658Z" }, + { url = "https://files.pythonhosted.org/packages/f4/9c/996a4a028222e7761a96634d1820de8a744ff4327a00ada9c8942033089b/charset_normalizer-3.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c", size = 107108, upload-time = "2025-08-09T07:56:07.176Z" }, + { url = "https://files.pythonhosted.org/packages/e9/5e/14c94999e418d9b87682734589404a25854d5f5d0408df68bc15b6ff54bb/charset_normalizer-3.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1", size = 205655, upload-time = "2025-08-09T07:56:08.475Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a8/c6ec5d389672521f644505a257f50544c074cf5fc292d5390331cd6fc9c3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884", size = 146223, upload-time = "2025-08-09T07:56:09.708Z" }, + { url = "https://files.pythonhosted.org/packages/fc/eb/a2ffb08547f4e1e5415fb69eb7db25932c52a52bed371429648db4d84fb1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018", size = 159366, upload-time = "2025-08-09T07:56:11.326Z" }, + { url = "https://files.pythonhosted.org/packages/82/10/0fd19f20c624b278dddaf83b8464dcddc2456cb4b02bb902a6da126b87a1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392", size = 157104, upload-time = "2025-08-09T07:56:13.014Z" }, + { url = "https://files.pythonhosted.org/packages/16/ab/0233c3231af734f5dfcf0844aa9582d5a1466c985bbed6cedab85af9bfe3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f", size = 151830, upload-time = "2025-08-09T07:56:14.428Z" }, + { url = "https://files.pythonhosted.org/packages/ae/02/e29e22b4e02839a0e4a06557b1999d0a47db3567e82989b5bb21f3fbbd9f/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154", size = 148854, upload-time = "2025-08-09T07:56:16.051Z" }, + { url = "https://files.pythonhosted.org/packages/05/6b/e2539a0a4be302b481e8cafb5af8792da8093b486885a1ae4d15d452bcec/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491", size = 160670, upload-time = "2025-08-09T07:56:17.314Z" }, + { url = "https://files.pythonhosted.org/packages/31/e7/883ee5676a2ef217a40ce0bffcc3d0dfbf9e64cbcfbdf822c52981c3304b/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93", size = 158501, upload-time = "2025-08-09T07:56:18.641Z" }, + { url = "https://files.pythonhosted.org/packages/c1/35/6525b21aa0db614cf8b5792d232021dca3df7f90a1944db934efa5d20bb1/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f", size = 153173, upload-time = "2025-08-09T07:56:20.289Z" }, + { url = "https://files.pythonhosted.org/packages/50/ee/f4704bad8201de513fdc8aac1cabc87e38c5818c93857140e06e772b5892/charset_normalizer-3.4.3-cp312-cp312-win32.whl", hash = "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37", size = 99822, upload-time = "2025-08-09T07:56:21.551Z" }, + { url = "https://files.pythonhosted.org/packages/39/f5/3b3836ca6064d0992c58c7561c6b6eee1b3892e9665d650c803bd5614522/charset_normalizer-3.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc", size = 107543, upload-time = "2025-08-09T07:56:23.115Z" }, + { url = "https://files.pythonhosted.org/packages/65/ca/2135ac97709b400c7654b4b764daf5c5567c2da45a30cdd20f9eefe2d658/charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe", size = 205326, upload-time = "2025-08-09T07:56:24.721Z" }, + { url = "https://files.pythonhosted.org/packages/71/11/98a04c3c97dd34e49c7d247083af03645ca3730809a5509443f3c37f7c99/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8", size = 146008, upload-time = "2025-08-09T07:56:26.004Z" }, + { url = "https://files.pythonhosted.org/packages/60/f5/4659a4cb3c4ec146bec80c32d8bb16033752574c20b1252ee842a95d1a1e/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9", size = 159196, upload-time = "2025-08-09T07:56:27.25Z" }, + { url = "https://files.pythonhosted.org/packages/86/9e/f552f7a00611f168b9a5865a1414179b2c6de8235a4fa40189f6f79a1753/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31", size = 156819, upload-time = "2025-08-09T07:56:28.515Z" }, + { url = "https://files.pythonhosted.org/packages/7e/95/42aa2156235cbc8fa61208aded06ef46111c4d3f0de233107b3f38631803/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f", size = 151350, upload-time = "2025-08-09T07:56:29.716Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a9/3865b02c56f300a6f94fc631ef54f0a8a29da74fb45a773dfd3dcd380af7/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927", size = 148644, upload-time = "2025-08-09T07:56:30.984Z" }, + { url = "https://files.pythonhosted.org/packages/77/d9/cbcf1a2a5c7d7856f11e7ac2d782aec12bdfea60d104e60e0aa1c97849dc/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9", size = 160468, upload-time = "2025-08-09T07:56:32.252Z" }, + { url = "https://files.pythonhosted.org/packages/f6/42/6f45efee8697b89fda4d50580f292b8f7f9306cb2971d4b53f8914e4d890/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5", size = 158187, upload-time = "2025-08-09T07:56:33.481Z" }, + { url = "https://files.pythonhosted.org/packages/70/99/f1c3bdcfaa9c45b3ce96f70b14f070411366fa19549c1d4832c935d8e2c3/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc", size = 152699, upload-time = "2025-08-09T07:56:34.739Z" }, + { url = "https://files.pythonhosted.org/packages/a3/ad/b0081f2f99a4b194bcbb1934ef3b12aa4d9702ced80a37026b7607c72e58/charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce", size = 99580, upload-time = "2025-08-09T07:56:35.981Z" }, + { url = "https://files.pythonhosted.org/packages/9a/8f/ae790790c7b64f925e5c953b924aaa42a243fb778fed9e41f147b2a5715a/charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef", size = 107366, upload-time = "2025-08-09T07:56:37.339Z" }, + { url = "https://files.pythonhosted.org/packages/8e/91/b5a06ad970ddc7a0e513112d40113e834638f4ca1120eb727a249fb2715e/charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15", size = 204342, upload-time = "2025-08-09T07:56:38.687Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ec/1edc30a377f0a02689342f214455c3f6c2fbedd896a1d2f856c002fc3062/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db", size = 145995, upload-time = "2025-08-09T07:56:40.048Z" }, + { url = "https://files.pythonhosted.org/packages/17/e5/5e67ab85e6d22b04641acb5399c8684f4d37caf7558a53859f0283a650e9/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d", size = 158640, upload-time = "2025-08-09T07:56:41.311Z" }, + { url = "https://files.pythonhosted.org/packages/f1/e5/38421987f6c697ee3722981289d554957c4be652f963d71c5e46a262e135/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096", size = 156636, upload-time = "2025-08-09T07:56:43.195Z" }, + { url = "https://files.pythonhosted.org/packages/a0/e4/5a075de8daa3ec0745a9a3b54467e0c2967daaaf2cec04c845f73493e9a1/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa", size = 150939, upload-time = "2025-08-09T07:56:44.819Z" }, + { url = "https://files.pythonhosted.org/packages/02/f7/3611b32318b30974131db62b4043f335861d4d9b49adc6d57c1149cc49d4/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049", size = 148580, upload-time = "2025-08-09T07:56:46.684Z" }, + { url = "https://files.pythonhosted.org/packages/7e/61/19b36f4bd67f2793ab6a99b979b4e4f3d8fc754cbdffb805335df4337126/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0", size = 159870, upload-time = "2025-08-09T07:56:47.941Z" }, + { url = "https://files.pythonhosted.org/packages/06/57/84722eefdd338c04cf3030ada66889298eaedf3e7a30a624201e0cbe424a/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92", size = 157797, upload-time = "2025-08-09T07:56:49.756Z" }, + { url = "https://files.pythonhosted.org/packages/72/2a/aff5dd112b2f14bcc3462c312dce5445806bfc8ab3a7328555da95330e4b/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16", size = 152224, upload-time = "2025-08-09T07:56:51.369Z" }, + { url = "https://files.pythonhosted.org/packages/b7/8c/9839225320046ed279c6e839d51f028342eb77c91c89b8ef2549f951f3ec/charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce", size = 100086, upload-time = "2025-08-09T07:56:52.722Z" }, + { url = "https://files.pythonhosted.org/packages/ee/7a/36fbcf646e41f710ce0a563c1c9a343c6edf9be80786edeb15b6f62e17db/charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c", size = 107400, upload-time = "2025-08-09T07:56:55.172Z" }, + { url = "https://files.pythonhosted.org/packages/8a/1f/f041989e93b001bc4e44bb1669ccdcf54d3f00e628229a85b08d330615c5/charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", size = 53175, upload-time = "2025-08-09T07:57:26.864Z" }, +] + +[[package]] +name = "click" +version = "8.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" }, +] + +[[package]] +name = "cmeel" +version = "0.57.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6e/ef/0990977bdcb1ffde571f8a0d70c8ad350abe4e398cc09fecabb3e13a8954/cmeel-0.57.3.tar.gz", hash = "sha256:7794a5c3d3365b5ed1db8ab0c152fabfc73af79a3edd6ff8bc2f1405c667dd7f", size = 14884, upload-time = "2025-03-19T15:29:10.298Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/df/634f09c1d2712a6e7b804a5630b0f16f219f6f9c8e1286f3265ca8bdf1ca/cmeel-0.57.3-py3-none-any.whl", hash = "sha256:231d24bb97beae67e3682f026a53e49e227c174e6a3329a9dcb850e5566529b0", size = 20986, upload-time = "2025-03-19T15:29:09.145Z" }, +] + +[[package]] +name = "cmeel-assimp" +version = "5.4.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cmeel" }, + { name = "cmeel-zlib" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/79/ac/f01a4e8c93e7e76f04788bad60bc5eaf7d4862eef24c11dd38bc8b61e819/cmeel_assimp-5.4.3.1.tar.gz", hash = "sha256:bd663a65ed143e71fe4723e787cbf1480e42cea4d2450c15cb5d9d4223d24f3d", size = 53703174, upload-time = "2025-02-11T15:04:22.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/eb/ed8bdfb61ac66a71f623900a7288b465230f6735120689529846c47131fa/cmeel_assimp-5.4.3.1-0-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:cfd834c910402d0a40ec677ec46f2ad25f096f0146faa23b27d9757fa51adb46", size = 10403810, upload-time = "2025-02-11T15:03:46.824Z" }, + { url = "https://files.pythonhosted.org/packages/ca/27/fed4d700c3bb35a86a2e563da3ad2ef26e00fc35da6e152ff82cbd022c97/cmeel_assimp-5.4.3.1-0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:9aa68541f68df3ad64c29a1195c154e0a00e33daaf59a5a7ceb1a6f34e69842f", size = 9530204, upload-time = "2025-02-11T15:03:50.44Z" }, + { url = "https://files.pythonhosted.org/packages/bb/f1/085c683b6d2871f6a3ba3c3159d73042231be7e71a63c251b9e919fed1b8/cmeel_assimp-5.4.3.1-0-py3-none-manylinux_2_17_i686.whl", hash = "sha256:673fdeef00e0a018c4b5d21345dca06769b693902a48060bb6b7e53b6b65a0f7", size = 14047356, upload-time = "2025-02-11T15:03:56.314Z" }, + { url = "https://files.pythonhosted.org/packages/8b/85/816c94a00eea68faac68f613f169c0f7023e407b6320193521fccb156e35/cmeel_assimp-5.4.3.1-0-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:6be2a9b69a8ccfce575a2b42fb956c0b6c3e3ec2140f63a5cfa194a9d848449d", size = 13367287, upload-time = "2025-02-11T15:03:59.441Z" }, + { url = "https://files.pythonhosted.org/packages/a1/ca/2a90a07d23f0c540fdcc1b5a50e3b424760db880e13bdf246e8b35abfaba/cmeel_assimp-5.4.3.1-0-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:ec259b1aad47c3f5b386004570b65b2b06a4d2a4d00b9959469e5528d029b31b", size = 14445192, upload-time = "2025-02-11T15:04:03.823Z" }, + { url = "https://files.pythonhosted.org/packages/72/08/d3c3c5efbcb7039892a79efed07439716107768f261eed7c1bfd01a50f11/cmeel_assimp-5.4.3.1-0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:d43933475fb0d76eee49de6b9a6d8e70492a89308afbde4d17ee4666fc15cfc0", size = 13396207, upload-time = "2025-02-11T15:04:08.58Z" }, + { url = "https://files.pythonhosted.org/packages/6a/36/9eef6d28a693ea021186d3456abfe98c5eafb5a9f4b6952b726309550f41/cmeel_assimp-5.4.3.1-0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2a22037a03d8756018c659e06ca2273b51dd1f67f057642df28e6f475cd798a8", size = 14795228, upload-time = "2025-02-11T15:04:12.785Z" }, + { url = "https://files.pythonhosted.org/packages/50/fe/9235a07a66d6d3bc1901360a17ed40b97b0d3904d7cc9f1ac2cc80661bf9/cmeel_assimp-5.4.3.1-0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:2c4ef463d29a30190262c1e48660d07aab744b583f90deccce24c1fecbf442b5", size = 14285514, upload-time = "2025-02-11T15:04:16.198Z" }, +] + +[[package]] +name = "cmeel-boost" +version = "1.87.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cmeel" }, + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/ee/a4c9eff9efe87945b770a3b773630cd72cb5f178de2daec719507e0f6024/cmeel_boost-1.87.0.1.tar.gz", hash = "sha256:c1b706e1f0c878693a96f6c92072596dcdf27bd14833b1cbbffd65a8b7e8d00f", size = 4075, upload-time = "2025-02-13T03:46:52.858Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/9c/f13fd327659b49464cc87b9134d70c7d98d833dabbec3a8e50d8e4867422/cmeel_boost-1.87.0.1-0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8a3a6c1646c6b4cc0b5b7d108b5f83c26b7ec9b0b63b6924e9f3373cca76d8ea", size = 30514323, upload-time = "2025-02-13T03:37:37.054Z" }, + { url = "https://files.pythonhosted.org/packages/d5/39/b5e7b972ac2c80ae98f19af2bb0abbbe7321283b19dbc75c1b2becbf0ea8/cmeel_boost-1.87.0.1-0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6e1b325137363c9dd6b36fd3e21cb3d6386273bc67990b23372c99ccc12648a2", size = 30405820, upload-time = "2025-02-13T03:43:49.338Z" }, + { url = "https://files.pythonhosted.org/packages/e4/71/83db946b7aa223205b104d47bf395e55324ac03ab3593e0c291dd290997b/cmeel_boost-1.87.0.1-0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:33b1c5dd758d0ec32973519c1e3a99fcf492c5d5f0c12ed4b5958b6c74fc72e9", size = 35647645, upload-time = "2025-02-13T03:43:56.397Z" }, + { url = "https://files.pythonhosted.org/packages/23/d3/4a0d6493fa9de78296aa0549ad2b25ea856f93469ddb470c72cf8df62b18/cmeel_boost-1.87.0.1-0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a7939a286ffb85d286f8110678fc6bebf329e2d2c1e8c185735719838288e0ce", size = 35941593, upload-time = "2025-02-13T03:44:03.163Z" }, + { url = "https://files.pythonhosted.org/packages/48/47/bd07149dbbdc9b25f9e1e6028edada2534a053db4075cf230feba6bc5f2f/cmeel_boost-1.87.0.1-0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5a20ae91166381b7eb38b05c315257910f288bcdf36e6a4f13e0c22cfff86557", size = 35983814, upload-time = "2025-02-13T03:44:09.419Z" }, + { url = "https://files.pythonhosted.org/packages/83/29/8cf019d94c17e99987541dfac4fc71125160ba092d66b1534eba38f9c66d/cmeel_boost-1.87.0.1-0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fe63ed4275dfa22f7d23d76074b6e2ef3e02554365a1ba4cf75ebd45e714c4e7", size = 30514344, upload-time = "2025-02-13T03:44:15.034Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5e/9b097f3b07df2f07d280b2ced9a979867df61f719e808293c5e104fc81c0/cmeel_boost-1.87.0.1-0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:28d0edd1b49a76563d07b9523fd3d7b37ad54f13644d2507c84e12fc3ba140f5", size = 30405860, upload-time = "2025-02-13T03:44:20.324Z" }, + { url = "https://files.pythonhosted.org/packages/ab/62/d1a88b84e45b3e40009375b43bd103b511899f5070da342a8e71d4d2a12b/cmeel_boost-1.87.0.1-0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:e0577a13e99187a66859daa2a873ec5cda852341c27219eb8bbbff6c0be59476", size = 35647178, upload-time = "2025-02-13T03:44:26.095Z" }, + { url = "https://files.pythonhosted.org/packages/9c/75/0b56a0cdfc9e12e1436a24e164f83d4358d89fce73290f32f937e24fbbc2/cmeel_boost-1.87.0.1-0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:4d8f490c3dd7f09a2ff18d8273805371d39efba02ccad711c0417eaba22a5cd8", size = 35941273, upload-time = "2025-02-13T03:44:33.076Z" }, + { url = "https://files.pythonhosted.org/packages/8b/34/5521df709a2337f67a965fd2c5bde52b27c6ccd1a46e54aa8169206ed52a/cmeel_boost-1.87.0.1-0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8309755cf220a7f76cb019253c211a7d40cc98b04258b5979af1125f5f24b2df", size = 35983443, upload-time = "2025-02-13T03:44:39.667Z" }, + { url = "https://files.pythonhosted.org/packages/e8/9e/c53b934ee85485366388d5b3902f92ff4c54ce091058c20a92908e1402dc/cmeel_boost-1.87.0.1-0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e8c6ad4bc187152e44caa9f9952c32fa12b3f03d9d6087bff1071c46ee7c84c4", size = 30518387, upload-time = "2025-02-13T03:44:45.267Z" }, + { url = "https://files.pythonhosted.org/packages/f8/7d/751d9fb25f2f29e1bb247c7ddc79cd8d2bc41678a32b413968443c5904eb/cmeel_boost-1.87.0.1-0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ec7a59c722794ddc205b541ed7abe237b72d2fdfdbfe971defbd1920bd58d42e", size = 30407414, upload-time = "2025-02-13T03:44:51.471Z" }, + { url = "https://files.pythonhosted.org/packages/0b/00/5df4db6c596a4da8c8e35c76af79ea8c0d00badee2d64d8baf56659bca79/cmeel_boost-1.87.0.1-0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:d3bd5a9855cda2ae8402b487fe028649a50c241e975bce765f33c87dba07324e", size = 35648012, upload-time = "2025-02-13T03:44:57.398Z" }, + { url = "https://files.pythonhosted.org/packages/ca/48/d4145d17b4abede4df4300b49749efe00e00aae4b654b799dc6c4895e177/cmeel_boost-1.87.0.1-0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:fd09a404663bbf610d0a6d786e0ed53a656f2d3dd50b30cf73324b982dd04753", size = 35946214, upload-time = "2025-02-13T03:45:03.378Z" }, + { url = "https://files.pythonhosted.org/packages/ca/10/d405fed8b0157a6fb7100f9dc883d5cd0cdc3840fa0fa652bb5ca7654a5d/cmeel_boost-1.87.0.1-0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1bf5bbe225134829ee57b8d3e0941fcc6868f921bf1b00d02ca9456bcf6cd006", size = 35988693, upload-time = "2025-02-13T03:45:09.774Z" }, + { url = "https://files.pythonhosted.org/packages/d3/56/3b9e23dd342bf1f5f141575a5bfbf08fe1577d1d1a14217dc2e757bd3cdd/cmeel_boost-1.87.0.1-0-cp313-cp313-macosx_10_9_x86_64.whl", hash = "sha256:3417b8bd016d9e4ddd01afdb7c9cc8fc9806d56e569801b64d90d79a81955727", size = 30518407, upload-time = "2025-02-13T03:45:16.622Z" }, + { url = "https://files.pythonhosted.org/packages/17/0f/d97add23b2f0708cb48d06b4d4cc3c006983491390b84396424b777eb5f5/cmeel_boost-1.87.0.1-0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9193cbb28c4dd5e61f7d0ca472333fb280685ec754813ef15a60bd1bf3ac566f", size = 30407454, upload-time = "2025-02-13T03:45:24.561Z" }, + { url = "https://files.pythonhosted.org/packages/3a/8d/db157dced18cc57429e4646039e9a54a8d817499666db9984182a30c58e9/cmeel_boost-1.87.0.1-0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:84aa37b8131050f5a291ebf44c9d92bb6f45102a082aad30dcc4e2cc01be250c", size = 35648325, upload-time = "2025-02-13T03:45:30.698Z" }, + { url = "https://files.pythonhosted.org/packages/0d/69/3250e0e3957c36e660bc49add1bdb61f52ab5af32fb0876647b4364f624d/cmeel_boost-1.87.0.1-0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:b947b10632518066b2cd6fae72cbb8e251a15e76dbeef221e99eeeb61d4b50f2", size = 35946299, upload-time = "2025-02-13T03:45:38.901Z" }, + { url = "https://files.pythonhosted.org/packages/62/de/8c12c0ea36ac9c17302289453f2e1f99f679163cf7ccdc5f27e12d18cba4/cmeel_boost-1.87.0.1-0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bcf7c2ef54898cf52bea781907262d86f884f5c26de9e8ce125220e062e83113", size = 35988899, upload-time = "2025-02-13T03:45:45.468Z" }, +] + +[[package]] +name = "cmeel-console-bridge" +version = "1.0.2.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cmeel" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/83/13/2e9e9d23db8548aef975564055bdb4fb6da8a397a1e7df8cb61f5afebefb/cmeel_console_bridge-1.0.2.3.tar.gz", hash = "sha256:3b2837da7ab408e9d1a775c83c0a7772356062b3a3672e4ce247f2da71a8ecd9", size = 262061, upload-time = "2025-03-19T18:22:06.845Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/a7/527fa060e5881acb3b0a07bf1d803ccb831cb87739abb62b6bcd14f5aed3/cmeel_console_bridge-1.0.2.3-0-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:7aa19b2d006073a1fad55d32968c7d0c7136749e06f98405f4f73a71038a5c41", size = 21341, upload-time = "2025-03-19T18:21:56.834Z" }, + { url = "https://files.pythonhosted.org/packages/bb/db/f8643a8766e8909e0dbfcda6191ca92454cf9a3fadd89be417db261601a1/cmeel_console_bridge-1.0.2.3-0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c47d8c97cb120feed1c01f30845d16c67e4e8205941e3977951018972b9b8721", size = 21286, upload-time = "2025-03-19T18:21:57.984Z" }, + { url = "https://files.pythonhosted.org/packages/a4/b4/9c79177152a220ab2e4ffa0140722165035f6a5c2abbed2912352bd7e7b9/cmeel_console_bridge-1.0.2.3-0-py3-none-manylinux_2_17_i686.whl", hash = "sha256:cad9723ac44ab563cd23bf361b604733623d11847c4edf2a2b4ebd1d984ade09", size = 23740, upload-time = "2025-03-19T18:21:59.683Z" }, + { url = "https://files.pythonhosted.org/packages/92/65/5741de6f550fe701d0780546d97b283306676315a3e1f379a6038e8c0ab0/cmeel_console_bridge-1.0.2.3-0-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:372942e9c44f681bfff377fba25b348801283aa6f3826a00e4195089bda9737a", size = 25762, upload-time = "2025-03-19T18:22:01.055Z" }, + { url = "https://files.pythonhosted.org/packages/50/a5/70e23c5570506bb39b56aa4d0f3a4a414e38082ddb33e86a48b546620121/cmeel_console_bridge-1.0.2.3-0-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:5bb1115ed38441b2396e732e10ec63d1e68445674f9f5d321f7985eb10e9aeef", size = 24477, upload-time = "2025-03-19T18:22:02.091Z" }, + { url = "https://files.pythonhosted.org/packages/47/36/bfd5a255348902e39243ccc6eba693bce714b891cd3be5603a9bd50c6de5/cmeel_console_bridge-1.0.2.3-0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:2b8d084b797f592942208c2040b08e06b82f8832aa6c5e582ba6f1a4a653505b", size = 24970, upload-time = "2025-03-19T18:22:03.075Z" }, + { url = "https://files.pythonhosted.org/packages/b3/02/3ae074e9acb9e150a4d5d97f341c2064573cd5fe9e5af20ab58bf8c0020a/cmeel_console_bridge-1.0.2.3-0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:fb6753a9864217d969c4965389d66a476ac978136c03eadf1063b1619c359220", size = 24689, upload-time = "2025-03-19T18:22:04.084Z" }, + { url = "https://files.pythonhosted.org/packages/69/d0/321f74b7d4167a6c59bb7714a6899ba402d9fad611f62573b9d646107320/cmeel_console_bridge-1.0.2.3-0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:9d446c0fc541413d8d2ceea3c1cfb9cbfd57938d6659c113121eca6c245caafe", size = 24404, upload-time = "2025-03-19T18:22:05.232Z" }, +] + +[[package]] +name = "cmeel-octomap" +version = "1.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cmeel" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/89/ab/2fed2dbee13e4b39949591685419f1dbb691295e32a6bbbaf87edc005922/cmeel_octomap-1.10.0.tar.gz", hash = "sha256:bd79d1d17adede534de242e42e13ef0d9f04bdd27daf7d56c57f7c43670c9b05", size = 1694189, upload-time = "2025-01-06T17:57:05.477Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/22/ea67d35df31ec4bb2ed6e594b173c572c72dbd2a87e96906eac67b4af930/cmeel_octomap-1.10.0-4-py3-none-macosx_14_0_arm64.whl", hash = "sha256:c116eb151920d26ee2b2c1f656cd7526862006739817205f11f9366ab0ef6cb4", size = 639956, upload-time = "2025-01-06T17:56:56.826Z" }, + { url = "https://files.pythonhosted.org/packages/b2/da/07725a8c11224881f536ad252e97a3d9801b48e5e776017d5f00fb39b17f/cmeel_octomap-1.10.0-4-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:76cc42553f54bae97584aaf0c7bc33753ff287e2738aa2ecac4820121101dd46", size = 1044402, upload-time = "2025-01-06T17:56:58.553Z" }, + { url = "https://files.pythonhosted.org/packages/a2/15/9617b7039afd6d17d3148f6f970d953f5e265d7736f8fdbca09c86e976a0/cmeel_octomap-1.10.0-4-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:5fdb04546fff3accac5f8626c3fc15c3b99e94ab887793565e0b92cedaf96468", size = 1105037, upload-time = "2025-01-06T17:57:01.287Z" }, + { url = "https://files.pythonhosted.org/packages/51/69/88c1d1eca1abf2387ee8263ac7e12708c8b1b5b70b46a0bd9f43b485165b/cmeel_octomap-1.10.0-4-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:84a7376cfced954bb7e3e347afbd02bdc1c83066b995afbdd0fb1e2d9f57ebec", size = 1108359, upload-time = "2025-01-06T17:57:04.085Z" }, + { url = "https://files.pythonhosted.org/packages/f5/59/57b3b38cf7a382855902b9d24266c283c29d977706438e6b7af62df74e2b/cmeel_octomap-1.10.0-5-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:042b4a21b5e5e19ee78a9a7db78e1b06fb8a287c832031788aec0d3fcabbfecd", size = 748832, upload-time = "2025-02-12T11:57:34.252Z" }, + { url = "https://files.pythonhosted.org/packages/55/8b/f5ec7676808a48c0185e216c0da700e34cb13ba233f13a4557a5ec56324a/cmeel_octomap-1.10.0-5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:79c15a0ece5ca3746170088ef2a377dfb3df8326fafde9bdba688852219758b9", size = 706924, upload-time = "2025-02-12T11:57:35.846Z" }, + { url = "https://files.pythonhosted.org/packages/09/50/56de5a4d9f8ca58100146f16f42c4e2fbb49c0957bfe40d3fd2bc910afe4/cmeel_octomap-1.10.0-5-py3-none-manylinux_2_17_i686.whl", hash = "sha256:e2923bf593ebdafed86b6f3890a122c62fbd9cc9f325d60dbecb72b6b60d78fb", size = 1073973, upload-time = "2025-02-12T11:57:37.914Z" }, + { url = "https://files.pythonhosted.org/packages/d3/f5/8dddf5cdd31176288acd85cc8bf0262b7c3de81d5cb2cb33aa6646f44eb1/cmeel_octomap-1.10.0-5-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:d9e6f9c826905e8de632e9df8cc20e59ce2eb5d1e0b368d8d4abbbc5c0829c1a", size = 1044533, upload-time = "2025-02-12T11:57:39.672Z" }, + { url = "https://files.pythonhosted.org/packages/d6/14/b85bd33bb05c9bb7e87b9ac8401793c12a80a6d594b3ca4bcb5e971a24b7/cmeel_octomap-1.10.0-5-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:b0b54fac180dce4f483afe7029c29cc55f6f2b21be8413e8e2275845b0c204d7", size = 1105199, upload-time = "2025-02-12T11:57:41.286Z" }, + { url = "https://files.pythonhosted.org/packages/99/ee/fe3360441159974ebdbb4c013a92ad0425d5f8bf414868d5161060e40660/cmeel_octomap-1.10.0-5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c8691e665bab7c12b6f51e6c5fbbb83ee6f91dce9d15d9d0387553950e7fb5ee", size = 1092962, upload-time = "2025-02-12T11:57:43.297Z" }, + { url = "https://files.pythonhosted.org/packages/82/a6/074166544cc0ce3a5d7844f97dfd13d1b3ec7bff6a6e2cfb18d66a671a7f/cmeel_octomap-1.10.0-5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:735c0ad84dacbbcc8c4237f127c57244c236b7d6c7500b5c45a4c225e19daac1", size = 1083321, upload-time = "2025-02-12T11:57:46.121Z" }, + { url = "https://files.pythonhosted.org/packages/c7/a3/b19ea0d30837369091141b248936b0757ee17f58b809007399bad0b398e4/cmeel_octomap-1.10.0-5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5f86a83f6bd60de290cd327f0374d525328369e76591e3ab2ad1bc0b183678c4", size = 1109207, upload-time = "2025-02-12T11:57:48.709Z" }, +] + +[[package]] +name = "cmeel-qhull" +version = "8.0.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cmeel" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/51/dd/8d0bcfb18771b2ea02bf85dfbbc587c97b274496fb5419b72134eb69430b/cmeel_qhull-8.0.2.1.tar.gz", hash = "sha256:68e8d41d95f61830f2d460af1e4d760f0dbe4d46413d7c736f0ed701153ebe52", size = 1308055, upload-time = "2023-11-17T14:21:06.003Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/b4/d72ebd5e9ee711b68ad466e7bd4c0edcb45b0c2c8a358fdcdb64b092666a/cmeel_qhull-8.0.2.1-0-py3-none-macosx_12_0_arm64.whl", hash = "sha256:39f5183a6e026754c3c043239bac005bf1825240d72e1d8fdf090a0f3ea27307", size = 2804225, upload-time = "2023-11-17T14:15:39.958Z" }, + { url = "https://files.pythonhosted.org/packages/29/dc/4bfb8d51a09401cf740e66d10bdb388eacd7c73bae12ef78149cbbc93e83/cmeel_qhull-8.0.2.1-0-py3-none-macosx_12_0_x86_64.whl", hash = "sha256:f135c5a4f4c8ed53f061bc86b794aaca2c0c34761c9269c06b71329c9da56f82", size = 2972481, upload-time = "2023-11-17T14:20:58.418Z" }, + { url = "https://files.pythonhosted.org/packages/0a/7c/74b5c781cbfc8e4a9bb73b71659cc595bc0163223fd700b18133dbcf2831/cmeel_qhull-8.0.2.1-0-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:17f519106df79aed9fc5ec92833d4958d132d23021f02a78a9564cdf83a36c7c", size = 3078962, upload-time = "2023-11-17T14:21:00.183Z" }, + { url = "https://files.pythonhosted.org/packages/b4/16/ef7b6201835ba2492753c9c91b266d047b6664507be42ec858e2b24673b5/cmeel_qhull-8.0.2.1-0-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:c513abafa40e2b8eb7cd3640e3f92d5391fbd6ec0f4182dbf9536934d8a8ea3e", size = 3194917, upload-time = "2023-11-17T14:21:01.879Z" }, + { url = "https://files.pythonhosted.org/packages/4b/ae/200bdf257507e2c95d0656bf02278cd666d49f0a9e2e6d281ea76d7d085c/cmeel_qhull-8.0.2.1-0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:20a69cb34b6250aee1f018412989734c9ddcad6ff66717a7c5516fc19f55d5ff", size = 3290068, upload-time = "2023-11-17T14:21:03.828Z" }, + { url = "https://files.pythonhosted.org/packages/01/1b/de3fa6091ef58ab40f02653e777c8943acf7cec486184d6007885123571d/cmeel_qhull-8.0.2.1-1-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:b5d47b113c1cb8f519bc813cf015d0d01f8ce5b08912733a24a6018f7caa6e96", size = 2902499, upload-time = "2025-02-12T11:51:16.999Z" }, + { url = "https://files.pythonhosted.org/packages/05/0c/5e5d9a033c683eb272508ccf560c03ac6bf5d397b038fe05f896a2283eaf/cmeel_qhull-8.0.2.1-1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:33a0169f4ee37d093c450195b0ef73d4fe0d9d62abb7899ebe79f778b36e1f36", size = 2773563, upload-time = "2025-02-12T11:51:19.893Z" }, + { url = "https://files.pythonhosted.org/packages/52/9b/00c73069348e60fbbdf6a5a10de046083f7d1ad36844958bbf12163ac688/cmeel_qhull-8.0.2.1-1-py3-none-manylinux_2_17_i686.whl", hash = "sha256:a577e76ac94d128f2966b137ead9f088749513df63749728e2b588f4564b7fdf", size = 3228684, upload-time = "2025-02-12T11:51:21.888Z" }, + { url = "https://files.pythonhosted.org/packages/c0/4a/81b8c88b444935a64d8c83b41e662f696c36dd5937c3ca687113ac4778d0/cmeel_qhull-8.0.2.1-1-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:fd0b2d4ce749b102c3cdead4588249befd34f1a660628f6bfc090ce942925aac", size = 3156051, upload-time = "2025-02-12T11:51:24.594Z" }, + { url = "https://files.pythonhosted.org/packages/4f/c1/44874cd8bfc1e3f7cb15678c836c7a1d5537f34f5a727a0207e01f395598/cmeel_qhull-8.0.2.1-1-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:2371a7c80a14f3e874876359ae3e3094861f081fcdd7a03987c3e880d14e07b9", size = 3262508, upload-time = "2025-02-12T11:51:27.147Z" }, + { url = "https://files.pythonhosted.org/packages/54/0e/425d9ce1f2a831025d39fa5b6479b856bd4d73614c9caa690ac72bbfca04/cmeel_qhull-8.0.2.1-1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:197c14c2006dbeba8f5a5771700a7afea72c1a441aab7cdeaaf10b4ed8c1137d", size = 3172646, upload-time = "2025-02-12T11:51:28.967Z" }, + { url = "https://files.pythonhosted.org/packages/00/c1/e973e287a7d793911b8e6497b17586e601a678f2379ba2c615f72bd76480/cmeel_qhull-8.0.2.1-1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:886d1be24b31842286ae42755af5c312a43a4199632826e4110185ec36dc5c6a", size = 3530837, upload-time = "2025-02-12T11:51:31.651Z" }, + { url = "https://files.pythonhosted.org/packages/fd/65/c6cd54f04b5fcaa4ec52f5b57692c1dcef812ff9ee86545e5607369d365e/cmeel_qhull-8.0.2.1-1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1a49ce7f8492c9a8b49f930e34cce75b5e9b9843b015033dd0a25421441159fc", size = 3301908, upload-time = "2025-02-12T11:51:34.53Z" }, +] + +[[package]] +name = "cmeel-tinyxml2" +version = "10.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cmeel" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/9f/030eca702c485f7a641f975f167fa93164911b3329f005fb0730ff5e793f/cmeel_tinyxml2-10.0.0.tar.gz", hash = "sha256:00252aefc1c94a55b89f25ad08ee79fda2da8d1d94703e051598ddb52a9088fe", size = 645297, upload-time = "2025-02-06T10:29:00.106Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/5d/bc3a932eb7996a0a789979426a9bb8a3948bf57f3f17bab87dddbef62433/cmeel_tinyxml2-10.0.0-0-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:924499bb1b60b9a17bd001d12a9af88ddbee4ca888638ae684ba7f0f3ce49e87", size = 111913, upload-time = "2025-02-06T10:28:45.723Z" }, + { url = "https://files.pythonhosted.org/packages/92/bf/67d11e123313c034712896e94038291fe506bb099bdb75a136392002ffd0/cmeel_tinyxml2-10.0.0-0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:26a1eb30c2a00bfc172e89ed015a18b8efb2b383546252ca8859574aed684686", size = 109487, upload-time = "2025-02-06T10:28:47.546Z" }, + { url = "https://files.pythonhosted.org/packages/ca/48/d8c81ce19b4b278ed0e8f81f93ae8670209bf3a9ac20141b9c386bb40cc7/cmeel_tinyxml2-10.0.0-0-py3-none-manylinux_2_17_i686.whl", hash = "sha256:53d86e02864c712f51f9a9adfcd8b6046b2ed51d44a0c34a8438d93b72b48325", size = 160118, upload-time = "2025-02-06T10:28:49.627Z" }, + { url = "https://files.pythonhosted.org/packages/87/4e/62193e27c9581f8ba7aeaeca7805632a64f2f4a824b1db37ad02ee953e8a/cmeel_tinyxml2-10.0.0-0-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:74112e2e9473afbf6ee2d25c9942553e9f6a40465e714533db72db48bc7658e1", size = 158477, upload-time = "2025-02-06T10:28:51.667Z" }, + { url = "https://files.pythonhosted.org/packages/14/f9/d0420c39e9ade99beeec61cd3abc68880fe6e14d85e9df292af8fabe65c8/cmeel_tinyxml2-10.0.0-0-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:ecd6e99caa2a06ac0d4b333b740c20fca526d0ca426f99eb5c0a0039117afdb6", size = 147025, upload-time = "2025-02-06T10:28:53.944Z" }, + { url = "https://files.pythonhosted.org/packages/66/9e/df63147fc162ab487217fa5596778ab7a81a82d9b3ce4236fd3a1e48cecb/cmeel_tinyxml2-10.0.0-0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:30993fffb7032a45d5d3b1e5670cb879dad667a13144cd68c8f4e0371a8a3d2e", size = 150958, upload-time = "2025-02-06T10:28:55.301Z" }, + { url = "https://files.pythonhosted.org/packages/0e/a8/b03567275fd83f5af33ddb61de942689dec72c5b21bec01e6a5b11101aa5/cmeel_tinyxml2-10.0.0-0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:8c09ede51784af54211a6225884dc7ddbb02ea1681656d173060c7ad2a5b9a3c", size = 160300, upload-time = "2025-02-06T10:28:57.189Z" }, + { url = "https://files.pythonhosted.org/packages/3a/ec/2781635b66c1059ca1243ae0f5a0410e171a5d8b8a71be3e34cb172f9f2d/cmeel_tinyxml2-10.0.0-0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3bd511d6d0758224efdebc23d3ead6e94f0755b04141ebf7d5493377829e8332", size = 149184, upload-time = "2025-02-06T10:28:58.734Z" }, +] + +[[package]] +name = "cmeel-urdfdom" +version = "4.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cmeel" }, + { name = "cmeel-console-bridge" }, + { name = "cmeel-tinyxml2" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/31/09/be81a5e7db56f34b6ccdbe7afe855c95a18c8439e173519e0146e9276a8c/cmeel_urdfdom-4.0.1.tar.gz", hash = "sha256:2e3f41e8483889e195b574acb326a4464cf11a3c0a8724031ac28bcda2223efc", size = 291511, upload-time = "2025-02-12T12:07:09.699Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/d0/20147dd6bb723afc44a58d89ea624df2bad1bed7b898a2df112aaca4a479/cmeel_urdfdom-4.0.1-0-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:2fe56939c6b47f6ec57021aac154123da47ecdcd79a217f3a5e3c4b705a07dee", size = 300860, upload-time = "2025-02-12T12:06:58.536Z" }, + { url = "https://files.pythonhosted.org/packages/8e/98/f832bca347e2d987c6b0ebb6930caf7b2c402535324aeed466b6aa2c4513/cmeel_urdfdom-4.0.1-0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:00a0aba78b68c428b27abeed1db58d73e65319ed966911a0e97b37367442e756", size = 300616, upload-time = "2025-02-12T12:07:00.556Z" }, + { url = "https://files.pythonhosted.org/packages/cf/10/bf5765b6f388037cff166a754a0958ac2fee34ca3c0975ef64d0324e4647/cmeel_urdfdom-4.0.1-0-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:a701a8f9671331f11b18ecf37a6537db546a21e6a0e5d0ff53341fea0693ed7f", size = 385951, upload-time = "2025-02-12T12:07:02.556Z" }, + { url = "https://files.pythonhosted.org/packages/c3/82/cb3f8f587d293a17bdbea15b50cdaa4a1e28e04583eb4cb4821685b89466/cmeel_urdfdom-4.0.1-0-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:12e39fc388c077d79fc9b3841d3d972a1da90b90de754d3363194c1540e18abf", size = 399619, upload-time = "2025-02-12T12:07:04.388Z" }, + { url = "https://files.pythonhosted.org/packages/24/77/322d7ac92c692d8dfaeda9de2d937087d15e2b564dc457d656e5fde3991d/cmeel_urdfdom-4.0.1-0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c4a83925df1d5923c4485c3eb2b80b3a61b14f119ab724fb5bd04cec494690ee", size = 373969, upload-time = "2025-02-12T12:07:06.222Z" }, + { url = "https://files.pythonhosted.org/packages/9f/63/bdc6b55cc8bd99bb9dce6be801b30feffaa1c3841ecb7f4fe4d137424518/cmeel_urdfdom-4.0.1-0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:4c4f44270971b3d05c45a4e21b1fb2df7e05a750363ae918f59532bff0bfe0e1", size = 388237, upload-time = "2025-02-12T12:07:08.326Z" }, + { url = "https://files.pythonhosted.org/packages/1d/2d/8463fc23230612daf4da1e31d3229f47708381f3ae4d1500f0f007ac0f92/cmeel_urdfdom-4.0.1-1-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:f7535158f45992eb2ba79e90d9db1bf9adc3846d9c7ed3e7a8c1c4d5343afa37", size = 301006, upload-time = "2025-02-13T11:42:08.8Z" }, + { url = "https://files.pythonhosted.org/packages/0f/d5/c8cdf500e49300d85624cbc3ef804107ddcdc9c541b1d3f726bfb58a9fc1/cmeel_urdfdom-4.0.1-1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fef2a01a00d61d41b3d35dd4958bba973e9025c26eea1d3c9880932f4dba89a5", size = 300758, upload-time = "2025-02-13T11:42:10.449Z" }, + { url = "https://files.pythonhosted.org/packages/cf/b3/2f7bac1544113a7f8e0f6d8b1fab5e75c6a3d27ffbb584b03267251b2165/cmeel_urdfdom-4.0.1-1-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:7a52eb36950ce982014d99a55717ca29985da056e3705f20746f15d3244c1f7a", size = 386043, upload-time = "2025-02-13T11:42:11.923Z" }, + { url = "https://files.pythonhosted.org/packages/86/03/8bdeb36ba6a3e8125d523ecfc010403049e463fe589f9896858d4bdcaf1e/cmeel_urdfdom-4.0.1-1-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:9f3b9c80b10d7246821ff61c2573f799e3da23d483e6f7367ddcad8a48baf58f", size = 399719, upload-time = "2025-02-13T11:42:14.325Z" }, + { url = "https://files.pythonhosted.org/packages/3f/ed/43f99e7512460294cd8acc5753ba25f8a20bdf28d62e143eaf3ec7a28bb6/cmeel_urdfdom-4.0.1-1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:2de69f47e8312cc09157624802d5bdaad6406443f863fb4b9ec62a19b4de3c72", size = 374073, upload-time = "2025-02-13T11:42:17.907Z" }, + { url = "https://files.pythonhosted.org/packages/17/c6/2e9bde6d7c02c1cf203ea896f8ce1afd441412f09b44830f1ee4a96d77de/cmeel_urdfdom-4.0.1-1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7708c1402de450fbeab21f7ca264a9a4676ed4c1cdf8d84d840bc5d057aac920", size = 388337, upload-time = "2025-02-13T11:42:19.657Z" }, +] + +[[package]] +name = "cmeel-zlib" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cmeel" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5a/74/b458f2fbfb652479c06400937cd67022e50d312033221602a9eca75022bc/cmeel_zlib-1.3.1.tar.gz", hash = "sha256:ebb34c54d1b7921dee5e7cd7003c9203b3297a5ba9d93983f1b7d3bb04976c3a", size = 3051, upload-time = "2025-02-11T12:20:39.574Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/dd/1bc2bc50c4ea217a993b2c9d3a7dd5959f839bc2b941556326b1ce71b961/cmeel_zlib-1.3.1-0-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:810779922c64d8074a3d12fcc471b1f62255e4402a1ca5f91f5749cc89214b93", size = 268796, upload-time = "2025-02-11T12:20:26.953Z" }, + { url = "https://files.pythonhosted.org/packages/a1/94/cf7e4554b7e2e4348da3f456be3c495774d1972a8dba384b6558b8f0e66b/cmeel_zlib-1.3.1-0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:2ccfac8fc80c6ee94ac61a9991f2ac18a5ea3a6cc2e753c221eb7c82729e839d", size = 191024, upload-time = "2025-02-11T12:20:28.737Z" }, + { url = "https://files.pythonhosted.org/packages/a2/cf/92d5a06071326ce3208f6cabc6d07d6c285b415df67e7ea9b87f0b46d44b/cmeel_zlib-1.3.1-0-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:f59862cde12d0dcd51fc8f35c408a51e0f279f9d8d9103d5497fe82572e194e4", size = 286338, upload-time = "2025-02-11T12:20:30.784Z" }, + { url = "https://files.pythonhosted.org/packages/21/10/13b53ce0f693085cbad31be9fceb1b6a2b4e3bae5851c1f114c3e7b3c447/cmeel_zlib-1.3.1-0-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:7f95b4ed5090fb0fef195f52485f3719dd60213e67a4c07ac4718660bd24da25", size = 282556, upload-time = "2025-02-11T12:20:32.337Z" }, + { url = "https://files.pythonhosted.org/packages/2b/2e/58b295975403b147e5df681e3e3470ba1802feed06a836843f02386d6506/cmeel_zlib-1.3.1-0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:2864a55ab1dad1d86749c8410693f3bca6e866cbb5ac16286be686aedb781f6e", size = 287625, upload-time = "2025-02-11T12:20:34.471Z" }, + { url = "https://files.pythonhosted.org/packages/56/f3/4da9d5c5308ef2019ab65a8a9f519ac95004446902d01e859f9ac6b8cdd6/cmeel_zlib-1.3.1-0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1e36ac8dccca22ff1f6e4df428ae5597f6288d9e6f85b08c9b767dc63e90fb55", size = 285662, upload-time = "2025-02-11T12:20:37.298Z" }, +] + +[[package]] +name = "coal-library" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cmeel" }, + { name = "cmeel-assimp" }, + { name = "cmeel-boost" }, + { name = "cmeel-octomap" }, + { name = "cmeel-qhull" }, + { name = "eigenpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d0/20/ad159b6d9eb7f4158c7dfa71e335cd89c35288de1ae23be06e24a986a6d0/coal_library-3.0.1.tar.gz", hash = "sha256:40aa6d4e7d6dbe259d3637e697380c8fdec0dcec89886e9cf0cbd36d2a9df2e5", size = 3349502, upload-time = "2025-02-12T19:25:48.349Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/ee/14464beb631396090028267fe4cd167a38ee382bb6f7184961eeb2ab88e7/coal_library-3.0.1-0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9d44f1311f165603eab3f4abc96aed9f5739310305638937fbe6dd5cc563be", size = 4530582, upload-time = "2025-02-12T19:24:38.93Z" }, + { url = "https://files.pythonhosted.org/packages/3a/e3/07c5e05cf2d0f2feef5b9e226f30d11cbca29f5c30173ec8a3fc93f01e59/coal_library-3.0.1-0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e1bdc7575afcdda27a7487af19b7ffc42272fa1859d56f74b40b9512fb23931", size = 4123196, upload-time = "2025-02-12T19:24:41.966Z" }, + { url = "https://files.pythonhosted.org/packages/e6/49/0529dddc58fe7d435d43e50b33852ec5f8121f57e87cce1d7bfce4929a23/coal_library-3.0.1-0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:566a61a913b037e9e6118a42cc1a1170c4b126dec4f13c5628d523e6a93a473a", size = 6388701, upload-time = "2025-02-12T19:24:44.421Z" }, + { url = "https://files.pythonhosted.org/packages/c8/0c/165082211ab7df98ab1aacae6f7354f1b3e6c67cc966ec8ef6ea27c8e229/coal_library-3.0.1-0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:c939380c0dc8a3cbb177ddd14c5ddac5f8beeb3d6b8b1e89d4a7d533567532b3", size = 6437127, upload-time = "2025-02-12T19:24:47.545Z" }, + { url = "https://files.pythonhosted.org/packages/c4/fe/8511aba274acbc724678c9b7944cca5c298e038b4c1316e501a4cfca2b0c/coal_library-3.0.1-0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e26cfc2c3953d263cf4c9ba873794e0b79736a5d391fdfababcc161929bf5ed7", size = 4530584, upload-time = "2025-02-12T19:24:53.524Z" }, + { url = "https://files.pythonhosted.org/packages/01/98/8090cc15e4733b14de32f54523f1c2f007b826cbdb0b9a2aa864186cb0b8/coal_library-3.0.1-0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8a7ba0b7f51d1e419466b5b1778730547bb8b240fb3f5e373dd9ec9ec372ecb0", size = 4123201, upload-time = "2025-02-12T19:24:55.669Z" }, + { url = "https://files.pythonhosted.org/packages/4b/38/62d5effedab4ad9312e38b91e462b15bca42fbb6a02f2400ef67dbb618eb/coal_library-3.0.1-0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:64129fe9fa2374134a8d18b83051da229ff9987773df7cf0d2a8bd1dbbe6139e", size = 6388488, upload-time = "2025-02-12T19:25:00.058Z" }, + { url = "https://files.pythonhosted.org/packages/68/40/4ae9c8f09532174c49bd152c46efc71f4ee5596236eaef9901ba643b33d5/coal_library-3.0.1-0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a00492012bb43d3386041872cf98d7fdd7cc546a6bb53cba4a478de8c70a0fa4", size = 6436691, upload-time = "2025-02-12T19:25:02.124Z" }, + { url = "https://files.pythonhosted.org/packages/f4/29/469f0b0b086576413e92f68b83bec3ca29b2e0f17c27f2f7c24cd0700d32/coal_library-3.0.1-0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f7f0c01e839c455712b540e15d8719ff986e726beed306c7f73424c37021e705", size = 4549472, upload-time = "2025-02-12T19:25:06.779Z" }, + { url = "https://files.pythonhosted.org/packages/3c/d6/d6b2f7256775ec71886d3ffbc3d00bed3594bef23314adaa6e3c23853e5d/coal_library-3.0.1-0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1def214c7aaee3e45dbcdc9049c28096cb945b194b5eb5cad867d5811430255e", size = 4129424, upload-time = "2025-02-12T19:25:08.584Z" }, + { url = "https://files.pythonhosted.org/packages/dd/b5/3014b8f9382539f72ab30f32cd263ee4c885b9926180f79ed7d7853f4a8b/coal_library-3.0.1-0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:ad7e8ecd9421b02190a2d4f504d8c53a63f91ab14182deadb508a7e44efba367", size = 6360948, upload-time = "2025-02-12T19:25:12.518Z" }, + { url = "https://files.pythonhosted.org/packages/3f/4c/b8c6c3627f1fe0be7972e38fb50899af8de87745dc8ceb40af82badf185e/coal_library-3.0.1-0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:5701232b34d2c70b622066c8796079ecda9bac77a60044650daed25da77637ef", size = 6421379, upload-time = "2025-02-12T19:25:14.593Z" }, + { url = "https://files.pythonhosted.org/packages/5a/c2/b45fb444004d4026d936297b2c5183fca7c42ad31b1399020ce6c57ba84f/coal_library-3.0.1-0-cp313-cp313-macosx_10_9_x86_64.whl", hash = "sha256:8594b26e115d1a78b13db4dbcd2b25e834517734f104decd355d09e3b494d191", size = 4549475, upload-time = "2025-02-12T19:25:17.645Z" }, + { url = "https://files.pythonhosted.org/packages/d8/a9/1c6c3a9d480b8b97c3eafc3c964454f4046d2ea6b780dc0dbc801f9bd41f/coal_library-3.0.1-0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:61a33e9e6d2bd695da718468d0cd3d9b6e20d4dafced90a38b1c17399b3ab73d", size = 4129423, upload-time = "2025-02-12T19:25:21.518Z" }, + { url = "https://files.pythonhosted.org/packages/c4/2a/ba0c897358597b6976aee28e5e849f87ddae132cce04d9e8415003473a0a/coal_library-3.0.1-0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:1013d326633664f3701f91007b9738d8d79465da5a15cd75c65e696304fff366", size = 6360950, upload-time = "2025-02-12T19:25:25.579Z" }, + { url = "https://files.pythonhosted.org/packages/10/f4/615449e99cb23a258a9a9178ea7b56e634b3b1e4b4f07dce79708d3ad245/coal_library-3.0.1-0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:96ce11ffe98215ce6845c435322a1e55297c97f532e0e22ef41e815887d09a41", size = 6421379, upload-time = "2025-02-12T19:25:27.936Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "coloredlogs" +version = "15.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "humanfriendly" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cc/c7/eed8f27100517e8c0e6b923d5f0845d0cb99763da6fdee00478f91db7325/coloredlogs-15.0.1.tar.gz", hash = "sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0", size = 278520, upload-time = "2021-06-11T10:22:45.202Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/06/3d6badcf13db419e25b07041d9c7b4a2c331d3f4e7134445ec5df57714cd/coloredlogs-15.0.1-py2.py3-none-any.whl", hash = "sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934", size = 46018, upload-time = "2021-06-11T10:22:42.561Z" }, +] + +[[package]] +name = "colorzero" +version = "2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "setuptools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/ca/688824a06e8c4d04c7d2fd2af2d8da27bed51af20ee5f094154e1d680334/colorzero-2.0.tar.gz", hash = "sha256:e7d5a5c26cd0dc37b164ebefc609f388de24f8593b659191e12d85f8f9d5eb58", size = 25382, upload-time = "2021-03-15T23:42:23.261Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/a6/ddd0f130e44a7593ac6c55aa93f6e256d2270fd88e9d1b64ab7f22ab8fde/colorzero-2.0-py2.py3-none-any.whl", hash = "sha256:0e60d743a6b8071498a56465f7719c96a5e92928f858bab1be2a0d606c9aa0f8", size = 26573, upload-time = "2021-03-15T23:42:21.757Z" }, +] + +[[package]] +name = "commentjson" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "lark-parser" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/76/c4aa9e408dbacee3f4de8e6c5417e5f55de7e62fb5a50300e1233a2c9cb5/commentjson-0.9.0.tar.gz", hash = "sha256:42f9f231d97d93aff3286a4dc0de39bfd91ae823d1d9eba9fa901fe0c7113dd4", size = 8653, upload-time = "2020-10-05T18:49:06.524Z" } + +[[package]] +name = "cv2-enumerate-cameras" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-framework-avfoundation", marker = "sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/67/e9/1ed371073498f6a4be7cf32dd19605e7fa067497328668a1083e85a3ae71/cv2_enumerate_cameras-1.2.2.tar.gz", hash = "sha256:7dce1335605a4d19fc402c2b3b7d45d5bd80143c20b77be3655f96f30ad76510", size = 11942, upload-time = "2025-07-03T06:05:00.027Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/37/a198560e077b82092f8d7df438e5c27fc5cc97a721456368f248c6cbf62f/cv2_enumerate_cameras-1.2.2-cp32-abi3-win32.whl", hash = "sha256:5068197f58ec5803da84dd9caa23dd016e09d0c2e6a656d888c5c29abb4ec226", size = 19866, upload-time = "2025-07-03T06:04:56.418Z" }, + { url = "https://files.pythonhosted.org/packages/35/ad/efd8779737ccb88596f9a06f4c46389f4c72aca3696b4b8260d18687d8d6/cv2_enumerate_cameras-1.2.2-cp32-abi3-win_amd64.whl", hash = "sha256:b5c2a765161a4187c5af3dbf2dd4fdf5acd486afde0bda2375eed877852117e5", size = 20751, upload-time = "2025-07-03T06:04:57.513Z" }, + { url = "https://files.pythonhosted.org/packages/f5/d5/be643f4c9542fa07752874820daff2a825c9c67432dfab6853662384385f/cv2_enumerate_cameras-1.2.2-py3-none-any.whl", hash = "sha256:0e54301a65a44a69873f4b4300c37119ae0ed1ea0266d96e1504fcbad678a8d5", size = 11024, upload-time = "2025-07-03T06:04:58.862Z" }, +] + +[[package]] +name = "decorator" +version = "5.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711, upload-time = "2025-02-24T04:41:34.073Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190, upload-time = "2025-02-24T04:41:32.565Z" }, +] + +[[package]] +name = "distlib" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, +] + +[[package]] +name = "docutils" +version = "0.22.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d9/02/111134bfeb6e6c7ac4c74594e39a59f6c0195dc4846afbeac3cba60f1927/docutils-0.22.3.tar.gz", hash = "sha256:21486ae730e4ca9f622677b1412b879af1791efcfba517e4c6f60be543fc8cdd", size = 2290153, upload-time = "2025-11-06T02:35:55.655Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/a8/c6a4b901d17399c77cd81fb001ce8961e9f5e04d3daf27e8925cb012e163/docutils-0.22.3-py3-none-any.whl", hash = "sha256:bd772e4aca73aff037958d44f2be5229ded4c09927fcf8690c577b66234d6ceb", size = 633032, upload-time = "2025-11-06T02:35:52.391Z" }, +] + +[[package]] +name = "eclipse-zenoh" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/59/46/501fa9a336c12a0c6f59a296233e24c00e7f30189d75cb692cab90e0b39f/eclipse_zenoh-1.5.1.tar.gz", hash = "sha256:5525adfe368e548e9a80947dbb930eedb4ae3df139bafc70358306483949b591", size = 126895, upload-time = "2025-09-04T13:53:02.988Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/86/63/d3beeb3bc882562bb6ea0a4caecd84a71d88662625bf20949b92371b2046/eclipse_zenoh-1.5.1-cp38-abi3-linux_armv6l.whl", hash = "sha256:c1d494012ef3c693a92980129857a73a4c3f3f0e2af0d0c0274c3615cd48ffda", size = 9084619, upload-time = "2025-09-04T13:52:46.991Z" }, + { url = "https://files.pythonhosted.org/packages/7c/76/05de0c17972dc40adf083a23ea05b094a7ed238cb9f01af926d4f1947b99/eclipse_zenoh-1.5.1-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:7f124d00868a03640afaa63fdddbf9befc2ea385c83e8944ea62e029363941c4", size = 16904800, upload-time = "2025-09-04T13:52:49.111Z" }, + { url = "https://files.pythonhosted.org/packages/ed/40/6106308e28595cf78bbc3d91fcc389cf6def17b36e5eded29c1db0945e60/eclipse_zenoh-1.5.1-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:5dcaa3a16dd0e8d517c7879f4b1dd6147ba48a15ffd12cc41ecb9b89e618f50b", size = 8652691, upload-time = "2025-09-04T13:52:51.382Z" }, + { url = "https://files.pythonhosted.org/packages/8f/be/2128b27738576d9e80452690682170922cf576e782db8226d9309705bdf2/eclipse_zenoh-1.5.1-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8e9f2fc126fce777e12842c4cb62235e854fa6df73afea71c04a28d7471e0b0a", size = 8974218, upload-time = "2025-09-04T13:52:52.964Z" }, + { url = "https://files.pythonhosted.org/packages/d3/fa/4eda37827209c7856d06e6df86f48b53c77291b158baaef4702c90fc3fe7/eclipse_zenoh-1.5.1-cp38-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:21239d912c758a0fe3dd8a76454d48f493b169de7795f050cf8045c5d8ea335f", size = 9771132, upload-time = "2025-09-04T13:52:55.008Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4f/a9150d237f6ef71489395127d9c5ddd77fe5fb64864b92e0746eb04dc8a8/eclipse_zenoh-1.5.1-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76781bbfa61fdc1e9626a03202c7dd8fd103f34761dc7b94b5a2d99f5aae629e", size = 9264787, upload-time = "2025-09-04T13:52:56.761Z" }, + { url = "https://files.pythonhosted.org/packages/a5/1d/9309130f890c6eb783703b5aa2c4718846900f810d123a1228c45416d871/eclipse_zenoh-1.5.1-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:0f142a36241e50eb3b86f9c3b0149138d2d1d7b6dd2038589fe4c62e12e776ea", size = 9089753, upload-time = "2025-09-04T13:52:59.851Z" }, + { url = "https://files.pythonhosted.org/packages/d4/8a/bec6c2e6fe808afab7e3c7c75eba3e3fa7e549d5028b1bdd7f983cd6d2eb/eclipse_zenoh-1.5.1-cp38-abi3-win_amd64.whl", hash = "sha256:eab374670fa9054e2500060ce33c8248f079208476defa5bb9f9a4b8317d6aac", size = 7581657, upload-time = "2025-09-04T13:53:01.467Z" }, +] + +[[package]] +name = "eigenpy" +version = "3.10.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cmeel" }, + { name = "cmeel-boost" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/39/d3/ffe0d1b75223013420bf8645aa06e29745366e6f8777e88d78947ef47846/eigenpy-3.10.3.tar.gz", hash = "sha256:95fc73d29ee9d175d8a35484f1cdc79349e54f9b3da5a1a695889c37b0c5d2e4", size = 6529791, upload-time = "2025-02-12T00:19:52.366Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/91/c13f031e420854dfb9d33185423fa7d8ab81bd55340dcfc745182fe0e51e/eigenpy-3.10.3-0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9f8d960640f5c498bbb05ab07d23fd544cc198c18cbc4af624eb17009543fabb", size = 4912581, upload-time = "2025-02-12T00:18:55.012Z" }, + { url = "https://files.pythonhosted.org/packages/82/91/5b74cd05f204f5c63734d0ddf27f3ddf2c7468911c527fe99006f17f55d2/eigenpy-3.10.3-0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c6f64b81fbb53a06fca5538b47cbef9b5f6cc7de239e4e8b85456938db568a9b", size = 4256333, upload-time = "2025-02-12T00:18:59.314Z" }, + { url = "https://files.pythonhosted.org/packages/a9/52/b5276d9ce6785595200b442a797c718110d1c5e65a1ed7287b56e5a011d1/eigenpy-3.10.3-0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:c585788efef343beb797e4ec2a09e7145f2324aa1bf51847e5729ad6d182bc89", size = 5205940, upload-time = "2025-02-12T00:19:01.838Z" }, + { url = "https://files.pythonhosted.org/packages/48/47/69fc45199bc759b0f686098ddc8a983b6b5784b1cdaa6b2cdab8883d187b/eigenpy-3.10.3-0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:af04ee75774b5e7b3fde4203e15d3fd23de178d70f7a3bdff6fba5a5395ad64a", size = 4939339, upload-time = "2025-02-12T00:19:06.264Z" }, + { url = "https://files.pythonhosted.org/packages/57/b1/08e4a09011cae328c3b00a427b9834b48c75a88bfe34c84d371e1d5870fa/eigenpy-3.10.3-0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d1967cdff87dd49828a41ce0336dfaeb5efb26103d047ddafa35a42dc3cd889a", size = 4912589, upload-time = "2025-02-12T00:19:08.911Z" }, + { url = "https://files.pythonhosted.org/packages/28/3c/6ab509f3765a38f62205505694bba5ed018e1c37047a9d82a32d0b582ae7/eigenpy-3.10.3-0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:12a174ab89aa7608ff33f7ce139846d24250e9402a32c257b917dce8dac082c8", size = 4256296, upload-time = "2025-02-12T00:19:10.633Z" }, + { url = "https://files.pythonhosted.org/packages/02/8a/4a572050d33d959ff8fd9ad9e4f9fbf9c373cdf5b0b25369fc1fe8afce8a/eigenpy-3.10.3-0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:b044ec5c1320fa6863b717e302db45220088c3467483fead92ed6effea573982", size = 5205135, upload-time = "2025-02-12T00:19:13.337Z" }, + { url = "https://files.pythonhosted.org/packages/0c/06/f3ddc152d7f453b309547118ff0b92f6b3ab6d40b287240e5726f5e2b7ce/eigenpy-3.10.3-0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:2da3284867d224c067b04f784a1c35ff1350a540556076bffcf39d8b52f7615f", size = 4939523, upload-time = "2025-02-12T00:19:15.224Z" }, + { url = "https://files.pythonhosted.org/packages/f1/de/b96e78da9b94470f984a6dff57a59f09b14020517dd2ff8c85d4cae99f4d/eigenpy-3.10.3-0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c758bd224df13a0e6176162b929aec8026beba01edf416ae34bf86eed6293f50", size = 4901896, upload-time = "2025-02-12T00:19:17.159Z" }, + { url = "https://files.pythonhosted.org/packages/33/36/9a9dd442c6daf6e2a03803766b5ce9675f9015d4aac3dc0c0789b3e941f3/eigenpy-3.10.3-0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9a681e6151e98a67af2b1b30c61686097d3089203ac8627e32ebd68f75627baf", size = 4264524, upload-time = "2025-02-12T00:19:19.715Z" }, + { url = "https://files.pythonhosted.org/packages/d7/06/06b05cce5ba8502206ed0951a15e83f6c015e9a3a3d296dd47effef6c3f9/eigenpy-3.10.3-0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:5e00483d44edf961556536f7557dbd84106154cbe84cb1056249c7c2b719e67f", size = 5196136, upload-time = "2025-02-12T00:19:22.457Z" }, + { url = "https://files.pythonhosted.org/packages/93/0c/3efa6ec843e530d07436513972c92c5dc0ed9e2f774ab265b782beb4eeab/eigenpy-3.10.3-0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:f151f6fe4f5b575429d69411e12881622b1e76f6fd3805b4081c397fe46cf229", size = 4944229, upload-time = "2025-02-12T00:19:25.356Z" }, + { url = "https://files.pythonhosted.org/packages/ea/25/3299d29624fdc2f8e218756e5823f699209b0a8710d3afecb3f33df2d560/eigenpy-3.10.3-0-cp313-cp313-macosx_10_9_x86_64.whl", hash = "sha256:c3874aaef6562d4e482f591b650ed48d6f5a81045a268d6498a4a3506b838893", size = 4901894, upload-time = "2025-02-12T00:19:27.735Z" }, + { url = "https://files.pythonhosted.org/packages/25/34/2283ff0b753528b924b420889a38a37524ed540d8bde63f60fd935139f04/eigenpy-3.10.3-0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9e717de8660cee84412832d3f41c3a2769b0cb21f8471bacbde3b6b37cadeeb9", size = 4264518, upload-time = "2025-02-12T00:19:30.417Z" }, + { url = "https://files.pythonhosted.org/packages/e5/d9/8e54eb7e6908a9445c304b4655503ca0b8eb258156c968ceeade3b527a85/eigenpy-3.10.3-0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:d047e63716138ddda51efc7c71c7f7c422489af8585f9f012bd0f31d74c443e2", size = 5196137, upload-time = "2025-02-12T00:19:33.215Z" }, + { url = "https://files.pythonhosted.org/packages/d5/e7/38f1de666adb78de94d2a549bdf4db48d2c447d4d052a3c7e9755d7b71e5/eigenpy-3.10.3-0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:ece421651d974a702f99333dec4d2a5b47c2a67bb17913c288fe3bb4bcf99744", size = 4944218, upload-time = "2025-02-12T00:19:35.233Z" }, +] + +[[package]] +name = "eiquadprog" +version = "1.2.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cmeel" }, + { name = "cmeel-boost" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c1/14/3e7fa16567e08e43e6e58aaeba9299977dd0d1c4ac5415e3b9a4a366a64f/eiquadprog-1.2.9.tar.gz", hash = "sha256:7fc82c4c476f636ff1aea4fd3c0baf301b4a6d349a0f3333d1600913d228ca3c", size = 879999, upload-time = "2025-02-17T19:00:17.851Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/a0/a2bfde96870eb65029ce85a46a74a331e143454640f6d3266536d3d94997/eiquadprog-1.2.9-0-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:ffcdb57617894fbd6693aee309f8bc20acb5f810071091b38e22c6c92690dead", size = 102743, upload-time = "2025-02-17T19:00:05.699Z" }, + { url = "https://files.pythonhosted.org/packages/b5/87/63ef7a51df432f4f3a81de5f68c0655c866e86268862d62e6c11eb8c1a8c/eiquadprog-1.2.9-0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:0b88425ca352a9480cca6991303551c9bff5607d3d2d2e31bb2293a06de266ff", size = 90721, upload-time = "2025-02-17T19:00:07.101Z" }, + { url = "https://files.pythonhosted.org/packages/f6/c9/21a8b3630749f418223e7c55e1479652ce3e482f4d070e675245172e01f0/eiquadprog-1.2.9-0-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:a45d8e59ffb7512d07ddea261a95beb4a7e13e7bc65b2df3dd5a49f611b79bdb", size = 106935, upload-time = "2025-02-17T19:00:08.931Z" }, + { url = "https://files.pythonhosted.org/packages/4d/cd/c80c605afde9c7f4a369721faea9f4bf61f1b9addcba6f6ad365a00d54cb/eiquadprog-1.2.9-0-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:439a1e80a60e09d4483f7937c36a3d114c8c8d0534cb53abb7a55bbea496bb03", size = 112873, upload-time = "2025-02-17T19:00:10.805Z" }, + { url = "https://files.pythonhosted.org/packages/bd/98/63beb8f79f68d15d057ced461573ba733e0404dcc4da5a0b170717433bc8/eiquadprog-1.2.9-0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:95ac0a753ceb6d2e232273cfa6e7a3b868782c6e1d4e4632d8e6279eef64459c", size = 108609, upload-time = "2025-02-17T19:00:12.904Z" }, + { url = "https://files.pythonhosted.org/packages/64/cb/809f0c3e4e7bfe78c6dd468631896a8866c3ba853e3c855cc3fa58fae660/eiquadprog-1.2.9-0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:416f4b584ea30072f166b2a6a3e0a63a2a260a378f9bcbd2dfc9cde13b810a50", size = 118538, upload-time = "2025-02-17T19:00:16.297Z" }, +] + +[[package]] +name = "etils" +version = "1.13.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9b/a0/522bbff0f3cdd37968f90dd7f26c7aa801ed87f5ba335f156de7f2b88a48/etils-1.13.0.tar.gz", hash = "sha256:a5b60c71f95bcd2d43d4e9fb3dc3879120c1f60472bb5ce19f7a860b1d44f607", size = 106368, upload-time = "2025-07-15T10:29:10.563Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/98/87b5946356095738cb90a6df7b35ff69ac5750f6e783d5fbcc5cb3b6cbd7/etils-1.13.0-py3-none-any.whl", hash = "sha256:d9cd4f40fbe77ad6613b7348a18132cc511237b6c076dbb89105c0b520a4c6bb", size = 170603, upload-time = "2025-07-15T10:29:09.076Z" }, +] + +[package.optional-dependencies] +epath = [ + { name = "fsspec" }, + { name = "importlib-resources" }, + { name = "typing-extensions" }, + { name = "zipp" }, +] + +[[package]] +name = "evdev" +version = "1.9.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/63/fe/a17c106a1f4061ce83f04d14bcedcfb2c38c7793ea56bfb906a6fadae8cb/evdev-1.9.2.tar.gz", hash = "sha256:5d3278892ce1f92a74d6bf888cc8525d9f68af85dbe336c95d1c87fb8f423069", size = 33301, upload-time = "2025-05-01T19:53:47.69Z" } + +[[package]] +name = "exceptiongroup" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" }, +] + +[[package]] +name = "executing" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/28/c14e053b6762b1044f34a13aab6859bbf40456d37d23aa286ac24cfd9a5d/executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4", size = 1129488, upload-time = "2025-09-01T09:48:10.866Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" }, +] + +[[package]] +name = "fastapi" +version = "0.116.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "starlette" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/d7/6c8b3bfe33eeffa208183ec037fee0cce9f7f024089ab1c5d12ef04bd27c/fastapi-0.116.1.tar.gz", hash = "sha256:ed52cbf946abfd70c5a0dccb24673f0670deeb517a88b3544d03c2a6bf283143", size = 296485, upload-time = "2025-07-11T16:22:32.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/47/d63c60f59a59467fda0f93f46335c9d18526d7071f025cb5b89d5353ea42/fastapi-0.116.1-py3-none-any.whl", hash = "sha256:c46ac7c312df840f0c9e220f7964bada936781bc4e2e6eb71f1c4d7553786565", size = 95631, upload-time = "2025-07-11T16:22:30.485Z" }, +] + +[[package]] +name = "filelock" +version = "3.19.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/40/bb/0ab3e58d22305b6f5440629d20683af28959bf793d98d11950e305c1c326/filelock-3.19.1.tar.gz", hash = "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58", size = 17687, upload-time = "2025-08-14T16:56:03.016Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/14/42b2651a2f46b022ccd948bca9f2d5af0fd8929c4eec235b8d6d844fbe67/filelock-3.19.1-py3-none-any.whl", hash = "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d", size = 15988, upload-time = "2025-08-14T16:56:01.633Z" }, +] + +[[package]] +name = "flatbuffers" +version = "25.2.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/30/eb5dce7994fc71a2f685d98ec33cc660c0a5887db5610137e60d8cbc4489/flatbuffers-25.2.10.tar.gz", hash = "sha256:97e451377a41262f8d9bd4295cc836133415cc03d8cb966410a4af92eb00d26e", size = 22170, upload-time = "2025-02-11T04:26:46.257Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b8/25/155f9f080d5e4bc0082edfda032ea2bc2b8fab3f4d25d46c1e9dd22a1a89/flatbuffers-25.2.10-py2.py3-none-any.whl", hash = "sha256:ebba5f4d5ea615af3f7fd70fc310636fbb2bbd1f566ac0a23d98dd412de50051", size = 30953, upload-time = "2025-02-11T04:26:44.484Z" }, +] + +[[package]] +name = "frozenlist" +version = "1.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/79/b1/b64018016eeb087db503b038296fd782586432b9c077fc5c7839e9cb6ef6/frozenlist-1.7.0.tar.gz", hash = "sha256:2e310d81923c2437ea8670467121cc3e9b0f76d3043cc1d2331d56c7fb7a3a8f", size = 45078, upload-time = "2025-06-09T23:02:35.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/36/0da0a49409f6b47cc2d060dc8c9040b897b5902a8a4e37d9bc1deb11f680/frozenlist-1.7.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cc4df77d638aa2ed703b878dd093725b72a824c3c546c076e8fdf276f78ee84a", size = 81304, upload-time = "2025-06-09T22:59:46.226Z" }, + { url = "https://files.pythonhosted.org/packages/77/f0/77c11d13d39513b298e267b22eb6cb559c103d56f155aa9a49097221f0b6/frozenlist-1.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:716a9973a2cc963160394f701964fe25012600f3d311f60c790400b00e568b61", size = 47735, upload-time = "2025-06-09T22:59:48.133Z" }, + { url = "https://files.pythonhosted.org/packages/37/12/9d07fa18971a44150593de56b2f2947c46604819976784bcf6ea0d5db43b/frozenlist-1.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0fd1bad056a3600047fb9462cff4c5322cebc59ebf5d0a3725e0ee78955001d", size = 46775, upload-time = "2025-06-09T22:59:49.564Z" }, + { url = "https://files.pythonhosted.org/packages/70/34/f73539227e06288fcd1f8a76853e755b2b48bca6747e99e283111c18bcd4/frozenlist-1.7.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3789ebc19cb811163e70fe2bd354cea097254ce6e707ae42e56f45e31e96cb8e", size = 224644, upload-time = "2025-06-09T22:59:51.35Z" }, + { url = "https://files.pythonhosted.org/packages/fb/68/c1d9c2f4a6e438e14613bad0f2973567586610cc22dcb1e1241da71de9d3/frozenlist-1.7.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:af369aa35ee34f132fcfad5be45fbfcde0e3a5f6a1ec0712857f286b7d20cca9", size = 222125, upload-time = "2025-06-09T22:59:52.884Z" }, + { url = "https://files.pythonhosted.org/packages/b9/d0/98e8f9a515228d708344d7c6986752be3e3192d1795f748c24bcf154ad99/frozenlist-1.7.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac64b6478722eeb7a3313d494f8342ef3478dff539d17002f849101b212ef97c", size = 233455, upload-time = "2025-06-09T22:59:54.74Z" }, + { url = "https://files.pythonhosted.org/packages/79/df/8a11bcec5600557f40338407d3e5bea80376ed1c01a6c0910fcfdc4b8993/frozenlist-1.7.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f89f65d85774f1797239693cef07ad4c97fdd0639544bad9ac4b869782eb1981", size = 227339, upload-time = "2025-06-09T22:59:56.187Z" }, + { url = "https://files.pythonhosted.org/packages/50/82/41cb97d9c9a5ff94438c63cc343eb7980dac4187eb625a51bdfdb7707314/frozenlist-1.7.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1073557c941395fdfcfac13eb2456cb8aad89f9de27bae29fabca8e563b12615", size = 212969, upload-time = "2025-06-09T22:59:57.604Z" }, + { url = "https://files.pythonhosted.org/packages/13/47/f9179ee5ee4f55629e4f28c660b3fdf2775c8bfde8f9c53f2de2d93f52a9/frozenlist-1.7.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ed8d2fa095aae4bdc7fdd80351009a48d286635edffee66bf865e37a9125c50", size = 222862, upload-time = "2025-06-09T22:59:59.498Z" }, + { url = "https://files.pythonhosted.org/packages/1a/52/df81e41ec6b953902c8b7e3a83bee48b195cb0e5ec2eabae5d8330c78038/frozenlist-1.7.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:24c34bea555fe42d9f928ba0a740c553088500377448febecaa82cc3e88aa1fa", size = 222492, upload-time = "2025-06-09T23:00:01.026Z" }, + { url = "https://files.pythonhosted.org/packages/84/17/30d6ea87fa95a9408245a948604b82c1a4b8b3e153cea596421a2aef2754/frozenlist-1.7.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:69cac419ac6a6baad202c85aaf467b65ac860ac2e7f2ac1686dc40dbb52f6577", size = 238250, upload-time = "2025-06-09T23:00:03.401Z" }, + { url = "https://files.pythonhosted.org/packages/8f/00/ecbeb51669e3c3df76cf2ddd66ae3e48345ec213a55e3887d216eb4fbab3/frozenlist-1.7.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:960d67d0611f4c87da7e2ae2eacf7ea81a5be967861e0c63cf205215afbfac59", size = 218720, upload-time = "2025-06-09T23:00:05.282Z" }, + { url = "https://files.pythonhosted.org/packages/1a/c0/c224ce0e0eb31cc57f67742071bb470ba8246623c1823a7530be0e76164c/frozenlist-1.7.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:41be2964bd4b15bf575e5daee5a5ce7ed3115320fb3c2b71fca05582ffa4dc9e", size = 232585, upload-time = "2025-06-09T23:00:07.962Z" }, + { url = "https://files.pythonhosted.org/packages/55/3c/34cb694abf532f31f365106deebdeac9e45c19304d83cf7d51ebbb4ca4d1/frozenlist-1.7.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:46d84d49e00c9429238a7ce02dc0be8f6d7cd0cd405abd1bebdc991bf27c15bd", size = 234248, upload-time = "2025-06-09T23:00:09.428Z" }, + { url = "https://files.pythonhosted.org/packages/98/c0/2052d8b6cecda2e70bd81299e3512fa332abb6dcd2969b9c80dfcdddbf75/frozenlist-1.7.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:15900082e886edb37480335d9d518cec978afc69ccbc30bd18610b7c1b22a718", size = 221621, upload-time = "2025-06-09T23:00:11.32Z" }, + { url = "https://files.pythonhosted.org/packages/c5/bf/7dcebae315436903b1d98ffb791a09d674c88480c158aa171958a3ac07f0/frozenlist-1.7.0-cp310-cp310-win32.whl", hash = "sha256:400ddd24ab4e55014bba442d917203c73b2846391dd42ca5e38ff52bb18c3c5e", size = 39578, upload-time = "2025-06-09T23:00:13.526Z" }, + { url = "https://files.pythonhosted.org/packages/8f/5f/f69818f017fa9a3d24d1ae39763e29b7f60a59e46d5f91b9c6b21622f4cd/frozenlist-1.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:6eb93efb8101ef39d32d50bce242c84bcbddb4f7e9febfa7b524532a239b4464", size = 43830, upload-time = "2025-06-09T23:00:14.98Z" }, + { url = "https://files.pythonhosted.org/packages/34/7e/803dde33760128acd393a27eb002f2020ddb8d99d30a44bfbaab31c5f08a/frozenlist-1.7.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:aa51e147a66b2d74de1e6e2cf5921890de6b0f4820b257465101d7f37b49fb5a", size = 82251, upload-time = "2025-06-09T23:00:16.279Z" }, + { url = "https://files.pythonhosted.org/packages/75/a9/9c2c5760b6ba45eae11334db454c189d43d34a4c0b489feb2175e5e64277/frozenlist-1.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9b35db7ce1cd71d36ba24f80f0c9e7cff73a28d7a74e91fe83e23d27c7828750", size = 48183, upload-time = "2025-06-09T23:00:17.698Z" }, + { url = "https://files.pythonhosted.org/packages/47/be/4038e2d869f8a2da165f35a6befb9158c259819be22eeaf9c9a8f6a87771/frozenlist-1.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:34a69a85e34ff37791e94542065c8416c1afbf820b68f720452f636d5fb990cd", size = 47107, upload-time = "2025-06-09T23:00:18.952Z" }, + { url = "https://files.pythonhosted.org/packages/79/26/85314b8a83187c76a37183ceed886381a5f992975786f883472fcb6dc5f2/frozenlist-1.7.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a646531fa8d82c87fe4bb2e596f23173caec9185bfbca5d583b4ccfb95183e2", size = 237333, upload-time = "2025-06-09T23:00:20.275Z" }, + { url = "https://files.pythonhosted.org/packages/1f/fd/e5b64f7d2c92a41639ffb2ad44a6a82f347787abc0c7df5f49057cf11770/frozenlist-1.7.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:79b2ffbba483f4ed36a0f236ccb85fbb16e670c9238313709638167670ba235f", size = 231724, upload-time = "2025-06-09T23:00:21.705Z" }, + { url = "https://files.pythonhosted.org/packages/20/fb/03395c0a43a5976af4bf7534759d214405fbbb4c114683f434dfdd3128ef/frozenlist-1.7.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a26f205c9ca5829cbf82bb2a84b5c36f7184c4316617d7ef1b271a56720d6b30", size = 245842, upload-time = "2025-06-09T23:00:23.148Z" }, + { url = "https://files.pythonhosted.org/packages/d0/15/c01c8e1dffdac5d9803507d824f27aed2ba76b6ed0026fab4d9866e82f1f/frozenlist-1.7.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bcacfad3185a623fa11ea0e0634aac7b691aa925d50a440f39b458e41c561d98", size = 239767, upload-time = "2025-06-09T23:00:25.103Z" }, + { url = "https://files.pythonhosted.org/packages/14/99/3f4c6fe882c1f5514b6848aa0a69b20cb5e5d8e8f51a339d48c0e9305ed0/frozenlist-1.7.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72c1b0fe8fe451b34f12dce46445ddf14bd2a5bcad7e324987194dc8e3a74c86", size = 224130, upload-time = "2025-06-09T23:00:27.061Z" }, + { url = "https://files.pythonhosted.org/packages/4d/83/220a374bd7b2aeba9d0725130665afe11de347d95c3620b9b82cc2fcab97/frozenlist-1.7.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61d1a5baeaac6c0798ff6edfaeaa00e0e412d49946c53fae8d4b8e8b3566c4ae", size = 235301, upload-time = "2025-06-09T23:00:29.02Z" }, + { url = "https://files.pythonhosted.org/packages/03/3c/3e3390d75334a063181625343e8daab61b77e1b8214802cc4e8a1bb678fc/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7edf5c043c062462f09b6820de9854bf28cc6cc5b6714b383149745e287181a8", size = 234606, upload-time = "2025-06-09T23:00:30.514Z" }, + { url = "https://files.pythonhosted.org/packages/23/1e/58232c19608b7a549d72d9903005e2d82488f12554a32de2d5fb59b9b1ba/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:d50ac7627b3a1bd2dcef6f9da89a772694ec04d9a61b66cf87f7d9446b4a0c31", size = 248372, upload-time = "2025-06-09T23:00:31.966Z" }, + { url = "https://files.pythonhosted.org/packages/c0/a4/e4a567e01702a88a74ce8a324691e62a629bf47d4f8607f24bf1c7216e7f/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ce48b2fece5aeb45265bb7a58259f45027db0abff478e3077e12b05b17fb9da7", size = 229860, upload-time = "2025-06-09T23:00:33.375Z" }, + { url = "https://files.pythonhosted.org/packages/73/a6/63b3374f7d22268b41a9db73d68a8233afa30ed164c46107b33c4d18ecdd/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:fe2365ae915a1fafd982c146754e1de6ab3478def8a59c86e1f7242d794f97d5", size = 245893, upload-time = "2025-06-09T23:00:35.002Z" }, + { url = "https://files.pythonhosted.org/packages/6d/eb/d18b3f6e64799a79673c4ba0b45e4cfbe49c240edfd03a68be20002eaeaa/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:45a6f2fdbd10e074e8814eb98b05292f27bad7d1883afbe009d96abdcf3bc898", size = 246323, upload-time = "2025-06-09T23:00:36.468Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f5/720f3812e3d06cd89a1d5db9ff6450088b8f5c449dae8ffb2971a44da506/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:21884e23cffabb157a9dd7e353779077bf5b8f9a58e9b262c6caad2ef5f80a56", size = 233149, upload-time = "2025-06-09T23:00:37.963Z" }, + { url = "https://files.pythonhosted.org/packages/69/68/03efbf545e217d5db8446acfd4c447c15b7c8cf4dbd4a58403111df9322d/frozenlist-1.7.0-cp311-cp311-win32.whl", hash = "sha256:284d233a8953d7b24f9159b8a3496fc1ddc00f4db99c324bd5fb5f22d8698ea7", size = 39565, upload-time = "2025-06-09T23:00:39.753Z" }, + { url = "https://files.pythonhosted.org/packages/58/17/fe61124c5c333ae87f09bb67186d65038834a47d974fc10a5fadb4cc5ae1/frozenlist-1.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:387cbfdcde2f2353f19c2f66bbb52406d06ed77519ac7ee21be0232147c2592d", size = 44019, upload-time = "2025-06-09T23:00:40.988Z" }, + { url = "https://files.pythonhosted.org/packages/ef/a2/c8131383f1e66adad5f6ecfcce383d584ca94055a34d683bbb24ac5f2f1c/frozenlist-1.7.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3dbf9952c4bb0e90e98aec1bd992b3318685005702656bc6f67c1a32b76787f2", size = 81424, upload-time = "2025-06-09T23:00:42.24Z" }, + { url = "https://files.pythonhosted.org/packages/4c/9d/02754159955088cb52567337d1113f945b9e444c4960771ea90eb73de8db/frozenlist-1.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1f5906d3359300b8a9bb194239491122e6cf1444c2efb88865426f170c262cdb", size = 47952, upload-time = "2025-06-09T23:00:43.481Z" }, + { url = "https://files.pythonhosted.org/packages/01/7a/0046ef1bd6699b40acd2067ed6d6670b4db2f425c56980fa21c982c2a9db/frozenlist-1.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3dabd5a8f84573c8d10d8859a50ea2dec01eea372031929871368c09fa103478", size = 46688, upload-time = "2025-06-09T23:00:44.793Z" }, + { url = "https://files.pythonhosted.org/packages/d6/a2/a910bafe29c86997363fb4c02069df4ff0b5bc39d33c5198b4e9dd42d8f8/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa57daa5917f1738064f302bf2626281a1cb01920c32f711fbc7bc36111058a8", size = 243084, upload-time = "2025-06-09T23:00:46.125Z" }, + { url = "https://files.pythonhosted.org/packages/64/3e/5036af9d5031374c64c387469bfcc3af537fc0f5b1187d83a1cf6fab1639/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c193dda2b6d49f4c4398962810fa7d7c78f032bf45572b3e04dd5249dff27e08", size = 233524, upload-time = "2025-06-09T23:00:47.73Z" }, + { url = "https://files.pythonhosted.org/packages/06/39/6a17b7c107a2887e781a48ecf20ad20f1c39d94b2a548c83615b5b879f28/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfe2b675cf0aaa6d61bf8fbffd3c274b3c9b7b1623beb3809df8a81399a4a9c4", size = 248493, upload-time = "2025-06-09T23:00:49.742Z" }, + { url = "https://files.pythonhosted.org/packages/be/00/711d1337c7327d88c44d91dd0f556a1c47fb99afc060ae0ef66b4d24793d/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8fc5d5cda37f62b262405cf9652cf0856839c4be8ee41be0afe8858f17f4c94b", size = 244116, upload-time = "2025-06-09T23:00:51.352Z" }, + { url = "https://files.pythonhosted.org/packages/24/fe/74e6ec0639c115df13d5850e75722750adabdc7de24e37e05a40527ca539/frozenlist-1.7.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0d5ce521d1dd7d620198829b87ea002956e4319002ef0bc8d3e6d045cb4646e", size = 224557, upload-time = "2025-06-09T23:00:52.855Z" }, + { url = "https://files.pythonhosted.org/packages/8d/db/48421f62a6f77c553575201e89048e97198046b793f4a089c79a6e3268bd/frozenlist-1.7.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:488d0a7d6a0008ca0db273c542098a0fa9e7dfaa7e57f70acef43f32b3f69dca", size = 241820, upload-time = "2025-06-09T23:00:54.43Z" }, + { url = "https://files.pythonhosted.org/packages/1d/fa/cb4a76bea23047c8462976ea7b7a2bf53997a0ca171302deae9d6dd12096/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:15a7eaba63983d22c54d255b854e8108e7e5f3e89f647fc854bd77a237e767df", size = 236542, upload-time = "2025-06-09T23:00:56.409Z" }, + { url = "https://files.pythonhosted.org/packages/5d/32/476a4b5cfaa0ec94d3f808f193301debff2ea42288a099afe60757ef6282/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1eaa7e9c6d15df825bf255649e05bd8a74b04a4d2baa1ae46d9c2d00b2ca2cb5", size = 249350, upload-time = "2025-06-09T23:00:58.468Z" }, + { url = "https://files.pythonhosted.org/packages/8d/ba/9a28042f84a6bf8ea5dbc81cfff8eaef18d78b2a1ad9d51c7bc5b029ad16/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e4389e06714cfa9d47ab87f784a7c5be91d3934cd6e9a7b85beef808297cc025", size = 225093, upload-time = "2025-06-09T23:01:00.015Z" }, + { url = "https://files.pythonhosted.org/packages/bc/29/3a32959e68f9cf000b04e79ba574527c17e8842e38c91d68214a37455786/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:73bd45e1488c40b63fe5a7df892baf9e2a4d4bb6409a2b3b78ac1c6236178e01", size = 245482, upload-time = "2025-06-09T23:01:01.474Z" }, + { url = "https://files.pythonhosted.org/packages/80/e8/edf2f9e00da553f07f5fa165325cfc302dead715cab6ac8336a5f3d0adc2/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:99886d98e1643269760e5fe0df31e5ae7050788dd288947f7f007209b8c33f08", size = 249590, upload-time = "2025-06-09T23:01:02.961Z" }, + { url = "https://files.pythonhosted.org/packages/1c/80/9a0eb48b944050f94cc51ee1c413eb14a39543cc4f760ed12657a5a3c45a/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:290a172aae5a4c278c6da8a96222e6337744cd9c77313efe33d5670b9f65fc43", size = 237785, upload-time = "2025-06-09T23:01:05.095Z" }, + { url = "https://files.pythonhosted.org/packages/f3/74/87601e0fb0369b7a2baf404ea921769c53b7ae00dee7dcfe5162c8c6dbf0/frozenlist-1.7.0-cp312-cp312-win32.whl", hash = "sha256:426c7bc70e07cfebc178bc4c2bf2d861d720c4fff172181eeb4a4c41d4ca2ad3", size = 39487, upload-time = "2025-06-09T23:01:06.54Z" }, + { url = "https://files.pythonhosted.org/packages/0b/15/c026e9a9fc17585a9d461f65d8593d281fedf55fbf7eb53f16c6df2392f9/frozenlist-1.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:563b72efe5da92e02eb68c59cb37205457c977aa7a449ed1b37e6939e5c47c6a", size = 43874, upload-time = "2025-06-09T23:01:07.752Z" }, + { url = "https://files.pythonhosted.org/packages/24/90/6b2cebdabdbd50367273c20ff6b57a3dfa89bd0762de02c3a1eb42cb6462/frozenlist-1.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee80eeda5e2a4e660651370ebffd1286542b67e268aa1ac8d6dbe973120ef7ee", size = 79791, upload-time = "2025-06-09T23:01:09.368Z" }, + { url = "https://files.pythonhosted.org/packages/83/2e/5b70b6a3325363293fe5fc3ae74cdcbc3e996c2a11dde2fd9f1fb0776d19/frozenlist-1.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d1a81c85417b914139e3a9b995d4a1c84559afc839a93cf2cb7f15e6e5f6ed2d", size = 47165, upload-time = "2025-06-09T23:01:10.653Z" }, + { url = "https://files.pythonhosted.org/packages/f4/25/a0895c99270ca6966110f4ad98e87e5662eab416a17e7fd53c364bf8b954/frozenlist-1.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cbb65198a9132ebc334f237d7b0df163e4de83fb4f2bdfe46c1e654bdb0c5d43", size = 45881, upload-time = "2025-06-09T23:01:12.296Z" }, + { url = "https://files.pythonhosted.org/packages/19/7c/71bb0bbe0832793c601fff68cd0cf6143753d0c667f9aec93d3c323f4b55/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dab46c723eeb2c255a64f9dc05b8dd601fde66d6b19cdb82b2e09cc6ff8d8b5d", size = 232409, upload-time = "2025-06-09T23:01:13.641Z" }, + { url = "https://files.pythonhosted.org/packages/c0/45/ed2798718910fe6eb3ba574082aaceff4528e6323f9a8570be0f7028d8e9/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6aeac207a759d0dedd2e40745575ae32ab30926ff4fa49b1635def65806fddee", size = 225132, upload-time = "2025-06-09T23:01:15.264Z" }, + { url = "https://files.pythonhosted.org/packages/ba/e2/8417ae0f8eacb1d071d4950f32f229aa6bf68ab69aab797b72a07ea68d4f/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bd8c4e58ad14b4fa7802b8be49d47993182fdd4023393899632c88fd8cd994eb", size = 237638, upload-time = "2025-06-09T23:01:16.752Z" }, + { url = "https://files.pythonhosted.org/packages/f8/b7/2ace5450ce85f2af05a871b8c8719b341294775a0a6c5585d5e6170f2ce7/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04fb24d104f425da3540ed83cbfc31388a586a7696142004c577fa61c6298c3f", size = 233539, upload-time = "2025-06-09T23:01:18.202Z" }, + { url = "https://files.pythonhosted.org/packages/46/b9/6989292c5539553dba63f3c83dc4598186ab2888f67c0dc1d917e6887db6/frozenlist-1.7.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a5c505156368e4ea6b53b5ac23c92d7edc864537ff911d2fb24c140bb175e60", size = 215646, upload-time = "2025-06-09T23:01:19.649Z" }, + { url = "https://files.pythonhosted.org/packages/72/31/bc8c5c99c7818293458fe745dab4fd5730ff49697ccc82b554eb69f16a24/frozenlist-1.7.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8bd7eb96a675f18aa5c553eb7ddc24a43c8c18f22e1f9925528128c052cdbe00", size = 232233, upload-time = "2025-06-09T23:01:21.175Z" }, + { url = "https://files.pythonhosted.org/packages/59/52/460db4d7ba0811b9ccb85af996019f5d70831f2f5f255f7cc61f86199795/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:05579bf020096fe05a764f1f84cd104a12f78eaab68842d036772dc6d4870b4b", size = 227996, upload-time = "2025-06-09T23:01:23.098Z" }, + { url = "https://files.pythonhosted.org/packages/ba/c9/f4b39e904c03927b7ecf891804fd3b4df3db29b9e487c6418e37988d6e9d/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:376b6222d114e97eeec13d46c486facd41d4f43bab626b7c3f6a8b4e81a5192c", size = 242280, upload-time = "2025-06-09T23:01:24.808Z" }, + { url = "https://files.pythonhosted.org/packages/b8/33/3f8d6ced42f162d743e3517781566b8481322be321b486d9d262adf70bfb/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0aa7e176ebe115379b5b1c95b4096fb1c17cce0847402e227e712c27bdb5a949", size = 217717, upload-time = "2025-06-09T23:01:26.28Z" }, + { url = "https://files.pythonhosted.org/packages/3e/e8/ad683e75da6ccef50d0ab0c2b2324b32f84fc88ceee778ed79b8e2d2fe2e/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3fbba20e662b9c2130dc771e332a99eff5da078b2b2648153a40669a6d0e36ca", size = 236644, upload-time = "2025-06-09T23:01:27.887Z" }, + { url = "https://files.pythonhosted.org/packages/b2/14/8d19ccdd3799310722195a72ac94ddc677541fb4bef4091d8e7775752360/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f3f4410a0a601d349dd406b5713fec59b4cee7e71678d5b17edda7f4655a940b", size = 238879, upload-time = "2025-06-09T23:01:29.524Z" }, + { url = "https://files.pythonhosted.org/packages/ce/13/c12bf657494c2fd1079a48b2db49fa4196325909249a52d8f09bc9123fd7/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e2cdfaaec6a2f9327bf43c933c0319a7c429058e8537c508964a133dffee412e", size = 232502, upload-time = "2025-06-09T23:01:31.287Z" }, + { url = "https://files.pythonhosted.org/packages/d7/8b/e7f9dfde869825489382bc0d512c15e96d3964180c9499efcec72e85db7e/frozenlist-1.7.0-cp313-cp313-win32.whl", hash = "sha256:5fc4df05a6591c7768459caba1b342d9ec23fa16195e744939ba5914596ae3e1", size = 39169, upload-time = "2025-06-09T23:01:35.503Z" }, + { url = "https://files.pythonhosted.org/packages/35/89/a487a98d94205d85745080a37860ff5744b9820a2c9acbcdd9440bfddf98/frozenlist-1.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:52109052b9791a3e6b5d1b65f4b909703984b770694d3eb64fad124c835d7cba", size = 43219, upload-time = "2025-06-09T23:01:36.784Z" }, + { url = "https://files.pythonhosted.org/packages/56/d5/5c4cf2319a49eddd9dd7145e66c4866bdc6f3dbc67ca3d59685149c11e0d/frozenlist-1.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a6f86e4193bb0e235ef6ce3dde5cbabed887e0b11f516ce8a0f4d3b33078ec2d", size = 84345, upload-time = "2025-06-09T23:01:38.295Z" }, + { url = "https://files.pythonhosted.org/packages/a4/7d/ec2c1e1dc16b85bc9d526009961953df9cec8481b6886debb36ec9107799/frozenlist-1.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:82d664628865abeb32d90ae497fb93df398a69bb3434463d172b80fc25b0dd7d", size = 48880, upload-time = "2025-06-09T23:01:39.887Z" }, + { url = "https://files.pythonhosted.org/packages/69/86/f9596807b03de126e11e7d42ac91e3d0b19a6599c714a1989a4e85eeefc4/frozenlist-1.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:912a7e8375a1c9a68325a902f3953191b7b292aa3c3fb0d71a216221deca460b", size = 48498, upload-time = "2025-06-09T23:01:41.318Z" }, + { url = "https://files.pythonhosted.org/packages/5e/cb/df6de220f5036001005f2d726b789b2c0b65f2363b104bbc16f5be8084f8/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9537c2777167488d539bc5de2ad262efc44388230e5118868e172dd4a552b146", size = 292296, upload-time = "2025-06-09T23:01:42.685Z" }, + { url = "https://files.pythonhosted.org/packages/83/1f/de84c642f17c8f851a2905cee2dae401e5e0daca9b5ef121e120e19aa825/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f34560fb1b4c3e30ba35fa9a13894ba39e5acfc5f60f57d8accde65f46cc5e74", size = 273103, upload-time = "2025-06-09T23:01:44.166Z" }, + { url = "https://files.pythonhosted.org/packages/88/3c/c840bfa474ba3fa13c772b93070893c6e9d5c0350885760376cbe3b6c1b3/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:acd03d224b0175f5a850edc104ac19040d35419eddad04e7cf2d5986d98427f1", size = 292869, upload-time = "2025-06-09T23:01:45.681Z" }, + { url = "https://files.pythonhosted.org/packages/a6/1c/3efa6e7d5a39a1d5ef0abeb51c48fb657765794a46cf124e5aca2c7a592c/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2038310bc582f3d6a09b3816ab01737d60bf7b1ec70f5356b09e84fb7408ab1", size = 291467, upload-time = "2025-06-09T23:01:47.234Z" }, + { url = "https://files.pythonhosted.org/packages/4f/00/d5c5e09d4922c395e2f2f6b79b9a20dab4b67daaf78ab92e7729341f61f6/frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8c05e4c8e5f36e5e088caa1bf78a687528f83c043706640a92cb76cd6999384", size = 266028, upload-time = "2025-06-09T23:01:48.819Z" }, + { url = "https://files.pythonhosted.org/packages/4e/27/72765be905619dfde25a7f33813ac0341eb6b076abede17a2e3fbfade0cb/frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:765bb588c86e47d0b68f23c1bee323d4b703218037765dcf3f25c838c6fecceb", size = 284294, upload-time = "2025-06-09T23:01:50.394Z" }, + { url = "https://files.pythonhosted.org/packages/88/67/c94103a23001b17808eb7dd1200c156bb69fb68e63fcf0693dde4cd6228c/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:32dc2e08c67d86d0969714dd484fd60ff08ff81d1a1e40a77dd34a387e6ebc0c", size = 281898, upload-time = "2025-06-09T23:01:52.234Z" }, + { url = "https://files.pythonhosted.org/packages/42/34/a3e2c00c00f9e2a9db5653bca3fec306349e71aff14ae45ecc6d0951dd24/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:c0303e597eb5a5321b4de9c68e9845ac8f290d2ab3f3e2c864437d3c5a30cd65", size = 290465, upload-time = "2025-06-09T23:01:53.788Z" }, + { url = "https://files.pythonhosted.org/packages/bb/73/f89b7fbce8b0b0c095d82b008afd0590f71ccb3dee6eee41791cf8cd25fd/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:a47f2abb4e29b3a8d0b530f7c3598badc6b134562b1a5caee867f7c62fee51e3", size = 266385, upload-time = "2025-06-09T23:01:55.769Z" }, + { url = "https://files.pythonhosted.org/packages/cd/45/e365fdb554159462ca12df54bc59bfa7a9a273ecc21e99e72e597564d1ae/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:3d688126c242a6fabbd92e02633414d40f50bb6002fa4cf995a1d18051525657", size = 288771, upload-time = "2025-06-09T23:01:57.4Z" }, + { url = "https://files.pythonhosted.org/packages/00/11/47b6117002a0e904f004d70ec5194fe9144f117c33c851e3d51c765962d0/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:4e7e9652b3d367c7bd449a727dc79d5043f48b88d0cbfd4f9f1060cf2b414104", size = 288206, upload-time = "2025-06-09T23:01:58.936Z" }, + { url = "https://files.pythonhosted.org/packages/40/37/5f9f3c3fd7f7746082ec67bcdc204db72dad081f4f83a503d33220a92973/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1a85e345b4c43db8b842cab1feb41be5cc0b10a1830e6295b69d7310f99becaf", size = 282620, upload-time = "2025-06-09T23:02:00.493Z" }, + { url = "https://files.pythonhosted.org/packages/0b/31/8fbc5af2d183bff20f21aa743b4088eac4445d2bb1cdece449ae80e4e2d1/frozenlist-1.7.0-cp313-cp313t-win32.whl", hash = "sha256:3a14027124ddb70dfcee5148979998066897e79f89f64b13328595c4bdf77c81", size = 43059, upload-time = "2025-06-09T23:02:02.072Z" }, + { url = "https://files.pythonhosted.org/packages/bb/ed/41956f52105b8dbc26e457c5705340c67c8cc2b79f394b79bffc09d0e938/frozenlist-1.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3bf8010d71d4507775f658e9823210b7427be36625b387221642725b515dcf3e", size = 47516, upload-time = "2025-06-09T23:02:03.779Z" }, + { url = "https://files.pythonhosted.org/packages/ee/45/b82e3c16be2182bff01179db177fe144d58b5dc787a7d4492c6ed8b9317f/frozenlist-1.7.0-py3-none-any.whl", hash = "sha256:9a5af342e34f7e97caf8c995864c7a396418ae2859cc6fdf1b1073020d516a7e", size = 13106, upload-time = "2025-06-09T23:02:34.204Z" }, +] + +[[package]] +name = "fsspec" +version = "2025.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/de/e0/bab50af11c2d75c9c4a2a26a5254573c0bd97cea152254401510950486fa/fsspec-2025.9.0.tar.gz", hash = "sha256:19fd429483d25d28b65ec68f9f4adc16c17ea2c7c7bf54ec61360d478fb19c19", size = 304847, upload-time = "2025-09-02T19:10:49.215Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/71/70db47e4f6ce3e5c37a607355f80da8860a33226be640226ac52cb05ef2e/fsspec-2025.9.0-py3-none-any.whl", hash = "sha256:530dc2a2af60a414a832059574df4a6e10cce927f6f4a78209390fe38955cfb7", size = 199289, upload-time = "2025-09-02T19:10:47.708Z" }, +] + +[[package]] +name = "glfw" +version = "2.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/38/97/a2d667c98b8474f6b8294042488c1bd488681fb3cb4c3b9cdac1a9114287/glfw-2.9.0.tar.gz", hash = "sha256:077111a150ff09bc302c5e4ae265a5eb6aeaff0c8b01f727f7fb34e3764bb8e2", size = 31453, upload-time = "2025-04-15T15:39:54.142Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/21/71/13dd8a8d547809543d21de9438a3a76a8728fc7966d01ad9fb54599aebf5/glfw-2.9.0-py2.py27.py3.py30.py31.py32.py33.py34.py35.py36.py37.py38.p39.p310.p311.p312.p313-none-macosx_10_6_intel.whl", hash = "sha256:183da99152f63469e9263146db2eb1b6cc4ee0c4082b280743e57bd1b0a3bd70", size = 105297, upload-time = "2025-04-15T15:39:39.677Z" }, + { url = "https://files.pythonhosted.org/packages/f8/a2/45e6dceec1e0a0ffa8dd3c0ecf1e11d74639a55186243129160c6434d456/glfw-2.9.0-py2.py27.py3.py30.py31.py32.py33.py34.py35.py36.py37.py38.p39.p310.p311.p312.p313-none-macosx_11_0_arm64.whl", hash = "sha256:aef5b555673b9555216e4cd7bc0bdbbb9983f66c620a85ba7310cfcfda5cd38c", size = 102146, upload-time = "2025-04-15T15:39:42.354Z" }, + { url = "https://files.pythonhosted.org/packages/d2/72/b6261ed918e3747c6070fe80636c63a3c8f1c42ce122670315eeeada156f/glfw-2.9.0-py2.py27.py3.py30.py31.py32.py33.py34.py35.py36.py37.py38.p39.p310.p311.p312.p313-none-manylinux2014_aarch64.whl", hash = "sha256:fcc430cb21984afba74945b7df38a5e1a02b36c0b4a2a2bab42b4a26d7cc51d6", size = 230002, upload-time = "2025-04-15T15:39:43.933Z" }, + { url = "https://files.pythonhosted.org/packages/45/d6/7f95786332e8b798569b8e60db2ee081874cec2a62572b8ec55c309d85b7/glfw-2.9.0-py2.py27.py3.py30.py31.py32.py33.py34.py35.py36.py37.py38.p39.p310.p311.p312.p313-none-manylinux2014_x86_64.whl", hash = "sha256:7f85b58546880466ac445fc564c5c831ca93c8a99795ab8eaf0a2d521af293d7", size = 241949, upload-time = "2025-04-15T15:39:45.28Z" }, + { url = "https://files.pythonhosted.org/packages/a1/e6/093ab7874a74bba351e754f6e7748c031bd7276702135da6cbcd00e1f3e2/glfw-2.9.0-py2.py27.py3.py30.py31.py32.py33.py34.py35.py36.py37.py38.p39.p310.p311.p312.p313-none-manylinux_2_28_aarch64.whl", hash = "sha256:2123716c8086b80b797e849a534fc6f21aebca300519e57c80618a65ca8135dc", size = 231016, upload-time = "2025-04-15T15:39:46.669Z" }, + { url = "https://files.pythonhosted.org/packages/7f/ba/de3630757c7d7fc2086aaf3994926d6b869d31586e4d0c14f1666af31b93/glfw-2.9.0-py2.py27.py3.py30.py31.py32.py33.py34.py35.py36.py37.py38.p39.p310.p311.p312.p313-none-manylinux_2_28_x86_64.whl", hash = "sha256:4e11271e49eb9bc53431ade022e284d5a59abeace81fe3b178db1bf3ccc0c449", size = 243489, upload-time = "2025-04-15T15:39:48.321Z" }, + { url = "https://files.pythonhosted.org/packages/32/36/c3bada8503681806231d1705ea1802bac8febf69e4186b9f0f0b9e2e4f7e/glfw-2.9.0-py2.py27.py3.py30.py31.py32.py33.py34.py35.py36.py37.py38.p39.p310.p311.p312.p313-none-win32.whl", hash = "sha256:8e4fbff88e4e953bb969b6813195d5de4641f886530cc8083897e56b00bf2c8e", size = 552655, upload-time = "2025-04-15T15:39:50.029Z" }, + { url = "https://files.pythonhosted.org/packages/cb/70/7f2f052ca20c3b69892818f2ee1fea53b037ea9145ff75b944ed1dc4ff82/glfw-2.9.0-py2.py27.py3.py30.py31.py32.py33.py34.py35.py36.py37.py38.p39.p310.p311.p312.p313-none-win_amd64.whl", hash = "sha256:9aa3ae51601601c53838315bd2a03efb1e6bebecd072b2f64ddbd0b2556d511a", size = 559441, upload-time = "2025-04-15T15:39:52.531Z" }, +] + +[[package]] +name = "gpiozero" +version = "2.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorzero" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e4/47/334b8db8a981eca9a0fb1e7e48e1997a5eaa8f40bb31c504299dcca0e6ff/gpiozero-2.0.1.tar.gz", hash = "sha256:d4ea1952689ec7e331f9d4ebc9adb15f1d01c2c9dcfabb72e752c9869ab7e97e", size = 136176, upload-time = "2024-02-15T11:07:02.919Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/eb/6518a1b00488d48995034226846653c382d676cf5f04be62b3c3fae2c6a1/gpiozero-2.0.1-py3-none-any.whl", hash = "sha256:8f621de357171d574c0b7ea0e358cb66e560818a47b0eeedf41ce1cdbd20c70b", size = 150818, upload-time = "2024-02-15T11:07:00.451Z" }, +] + +[[package]] +name = "gst-signalling" +version = "1.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "pyee" }, + { name = "pygobject" }, + { name = "websockets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3f/77/0c45062d07bb6ad8f940a8a8bc3acd7968cc145be354a566e897d4a6753c/gst_signalling-1.1.2.tar.gz", hash = "sha256:45c7bb3d6801b2704cd4c743130f60d2991b39aa91b6220873ef53ffe9910b98", size = 16467, upload-time = "2025-11-18T13:36:35.935Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/7a/4ac380ce2e7e9ea1e055460648d6c562eb554cdc0dd9b6536b38934b9ef4/gst_signalling-1.1.2-py3-none-any.whl", hash = "sha256:39ebb6eac597933504334d4c1f835abd873cbd42fe31896515e3a0db368db78c", size = 19681, upload-time = "2025-11-18T13:36:34.567Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "hf-xet" +version = "1.1.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/23/0f/5b60fc28ee7f8cc17a5114a584fd6b86e11c3e0a6e142a7f97a161e9640a/hf_xet-1.1.9.tar.gz", hash = "sha256:c99073ce404462e909f1d5839b2d14a3827b8fe75ed8aed551ba6609c026c803", size = 484242, upload-time = "2025-08-27T23:05:19.441Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/12/56e1abb9a44cdef59a411fe8a8673313195711b5ecce27880eb9c8fa90bd/hf_xet-1.1.9-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:a3b6215f88638dd7a6ff82cb4e738dcbf3d863bf667997c093a3c990337d1160", size = 2762553, upload-time = "2025-08-27T23:05:15.153Z" }, + { url = "https://files.pythonhosted.org/packages/3a/e6/2d0d16890c5f21b862f5df3146519c182e7f0ae49b4b4bf2bd8a40d0b05e/hf_xet-1.1.9-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:9b486de7a64a66f9a172f4b3e0dfe79c9f0a93257c501296a2521a13495a698a", size = 2623216, upload-time = "2025-08-27T23:05:13.778Z" }, + { url = "https://files.pythonhosted.org/packages/81/42/7e6955cf0621e87491a1fb8cad755d5c2517803cea174229b0ec00ff0166/hf_xet-1.1.9-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4c5a840c2c4e6ec875ed13703a60e3523bc7f48031dfd750923b2a4d1a5fc3c", size = 3186789, upload-time = "2025-08-27T23:05:12.368Z" }, + { url = "https://files.pythonhosted.org/packages/df/8b/759233bce05457f5f7ec062d63bbfd2d0c740b816279eaaa54be92aa452a/hf_xet-1.1.9-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:96a6139c9e44dad1c52c52520db0fffe948f6bce487cfb9d69c125f254bb3790", size = 3088747, upload-time = "2025-08-27T23:05:10.439Z" }, + { url = "https://files.pythonhosted.org/packages/6c/3c/28cc4db153a7601a996985bcb564f7b8f5b9e1a706c7537aad4b4809f358/hf_xet-1.1.9-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ad1022e9a998e784c97b2173965d07fe33ee26e4594770b7785a8cc8f922cd95", size = 3251429, upload-time = "2025-08-27T23:05:16.471Z" }, + { url = "https://files.pythonhosted.org/packages/84/17/7caf27a1d101bfcb05be85850d4aa0a265b2e1acc2d4d52a48026ef1d299/hf_xet-1.1.9-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:86754c2d6d5afb11b0a435e6e18911a4199262fe77553f8c50d75e21242193ea", size = 3354643, upload-time = "2025-08-27T23:05:17.828Z" }, + { url = "https://files.pythonhosted.org/packages/cd/50/0c39c9eed3411deadcc98749a6699d871b822473f55fe472fad7c01ec588/hf_xet-1.1.9-cp37-abi3-win_amd64.whl", hash = "sha256:5aad3933de6b725d61d51034e04174ed1dce7a57c63d530df0014dea15a40127", size = 2804797, upload-time = "2025-08-27T23:05:20.77Z" }, +] + +[[package]] +name = "httptools" +version = "0.6.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/9a/ce5e1f7e131522e6d3426e8e7a490b3a01f39a6696602e1c4f33f9e94277/httptools-0.6.4.tar.gz", hash = "sha256:4e93eee4add6493b59a5c514da98c939b244fce4a0d8879cd3f466562f4b7d5c", size = 240639, upload-time = "2024-10-16T19:45:08.902Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/6f/972f8eb0ea7d98a1c6be436e2142d51ad2a64ee18e02b0e7ff1f62171ab1/httptools-0.6.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3c73ce323711a6ffb0d247dcd5a550b8babf0f757e86a52558fe5b86d6fefcc0", size = 198780, upload-time = "2024-10-16T19:44:06.882Z" }, + { url = "https://files.pythonhosted.org/packages/6a/b0/17c672b4bc5c7ba7f201eada4e96c71d0a59fbc185e60e42580093a86f21/httptools-0.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:345c288418f0944a6fe67be8e6afa9262b18c7626c3ef3c28adc5eabc06a68da", size = 103297, upload-time = "2024-10-16T19:44:08.129Z" }, + { url = "https://files.pythonhosted.org/packages/92/5e/b4a826fe91971a0b68e8c2bd4e7db3e7519882f5a8ccdb1194be2b3ab98f/httptools-0.6.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:deee0e3343f98ee8047e9f4c5bc7cedbf69f5734454a94c38ee829fb2d5fa3c1", size = 443130, upload-time = "2024-10-16T19:44:09.45Z" }, + { url = "https://files.pythonhosted.org/packages/b0/51/ce61e531e40289a681a463e1258fa1e05e0be54540e40d91d065a264cd8f/httptools-0.6.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca80b7485c76f768a3bc83ea58373f8db7b015551117375e4918e2aa77ea9b50", size = 442148, upload-time = "2024-10-16T19:44:11.539Z" }, + { url = "https://files.pythonhosted.org/packages/ea/9e/270b7d767849b0c96f275c695d27ca76c30671f8eb8cc1bab6ced5c5e1d0/httptools-0.6.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:90d96a385fa941283ebd231464045187a31ad932ebfa541be8edf5b3c2328959", size = 415949, upload-time = "2024-10-16T19:44:13.388Z" }, + { url = "https://files.pythonhosted.org/packages/81/86/ced96e3179c48c6f656354e106934e65c8963d48b69be78f355797f0e1b3/httptools-0.6.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:59e724f8b332319e2875efd360e61ac07f33b492889284a3e05e6d13746876f4", size = 417591, upload-time = "2024-10-16T19:44:15.258Z" }, + { url = "https://files.pythonhosted.org/packages/75/73/187a3f620ed3175364ddb56847d7a608a6fc42d551e133197098c0143eca/httptools-0.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:c26f313951f6e26147833fc923f78f95604bbec812a43e5ee37f26dc9e5a686c", size = 88344, upload-time = "2024-10-16T19:44:16.54Z" }, + { url = "https://files.pythonhosted.org/packages/7b/26/bb526d4d14c2774fe07113ca1db7255737ffbb119315839af2065abfdac3/httptools-0.6.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f47f8ed67cc0ff862b84a1189831d1d33c963fb3ce1ee0c65d3b0cbe7b711069", size = 199029, upload-time = "2024-10-16T19:44:18.427Z" }, + { url = "https://files.pythonhosted.org/packages/a6/17/3e0d3e9b901c732987a45f4f94d4e2c62b89a041d93db89eafb262afd8d5/httptools-0.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0614154d5454c21b6410fdf5262b4a3ddb0f53f1e1721cfd59d55f32138c578a", size = 103492, upload-time = "2024-10-16T19:44:19.515Z" }, + { url = "https://files.pythonhosted.org/packages/b7/24/0fe235d7b69c42423c7698d086d4db96475f9b50b6ad26a718ef27a0bce6/httptools-0.6.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8787367fbdfccae38e35abf7641dafc5310310a5987b689f4c32cc8cc3ee975", size = 462891, upload-time = "2024-10-16T19:44:21.067Z" }, + { url = "https://files.pythonhosted.org/packages/b1/2f/205d1f2a190b72da6ffb5f41a3736c26d6fa7871101212b15e9b5cd8f61d/httptools-0.6.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40b0f7fe4fd38e6a507bdb751db0379df1e99120c65fbdc8ee6c1d044897a636", size = 459788, upload-time = "2024-10-16T19:44:22.958Z" }, + { url = "https://files.pythonhosted.org/packages/6e/4c/d09ce0eff09057a206a74575ae8f1e1e2f0364d20e2442224f9e6612c8b9/httptools-0.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:40a5ec98d3f49904b9fe36827dcf1aadfef3b89e2bd05b0e35e94f97c2b14721", size = 433214, upload-time = "2024-10-16T19:44:24.513Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d2/84c9e23edbccc4a4c6f96a1b8d99dfd2350289e94f00e9ccc7aadde26fb5/httptools-0.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dacdd3d10ea1b4ca9df97a0a303cbacafc04b5cd375fa98732678151643d4988", size = 434120, upload-time = "2024-10-16T19:44:26.295Z" }, + { url = "https://files.pythonhosted.org/packages/d0/46/4d8e7ba9581416de1c425b8264e2cadd201eb709ec1584c381f3e98f51c1/httptools-0.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:288cd628406cc53f9a541cfaf06041b4c71d751856bab45e3702191f931ccd17", size = 88565, upload-time = "2024-10-16T19:44:29.188Z" }, + { url = "https://files.pythonhosted.org/packages/bb/0e/d0b71465c66b9185f90a091ab36389a7352985fe857e352801c39d6127c8/httptools-0.6.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:df017d6c780287d5c80601dafa31f17bddb170232d85c066604d8558683711a2", size = 200683, upload-time = "2024-10-16T19:44:30.175Z" }, + { url = "https://files.pythonhosted.org/packages/e2/b8/412a9bb28d0a8988de3296e01efa0bd62068b33856cdda47fe1b5e890954/httptools-0.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:85071a1e8c2d051b507161f6c3e26155b5c790e4e28d7f236422dbacc2a9cc44", size = 104337, upload-time = "2024-10-16T19:44:31.786Z" }, + { url = "https://files.pythonhosted.org/packages/9b/01/6fb20be3196ffdc8eeec4e653bc2a275eca7f36634c86302242c4fbb2760/httptools-0.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69422b7f458c5af875922cdb5bd586cc1f1033295aa9ff63ee196a87519ac8e1", size = 508796, upload-time = "2024-10-16T19:44:32.825Z" }, + { url = "https://files.pythonhosted.org/packages/f7/d8/b644c44acc1368938317d76ac991c9bba1166311880bcc0ac297cb9d6bd7/httptools-0.6.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16e603a3bff50db08cd578d54f07032ca1631450ceb972c2f834c2b860c28ea2", size = 510837, upload-time = "2024-10-16T19:44:33.974Z" }, + { url = "https://files.pythonhosted.org/packages/52/d8/254d16a31d543073a0e57f1c329ca7378d8924e7e292eda72d0064987486/httptools-0.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec4f178901fa1834d4a060320d2f3abc5c9e39766953d038f1458cb885f47e81", size = 485289, upload-time = "2024-10-16T19:44:35.111Z" }, + { url = "https://files.pythonhosted.org/packages/5f/3c/4aee161b4b7a971660b8be71a92c24d6c64372c1ab3ae7f366b3680df20f/httptools-0.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f9eb89ecf8b290f2e293325c646a211ff1c2493222798bb80a530c5e7502494f", size = 489779, upload-time = "2024-10-16T19:44:36.253Z" }, + { url = "https://files.pythonhosted.org/packages/12/b7/5cae71a8868e555f3f67a50ee7f673ce36eac970f029c0c5e9d584352961/httptools-0.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:db78cb9ca56b59b016e64b6031eda5653be0589dba2b1b43453f6e8b405a0970", size = 88634, upload-time = "2024-10-16T19:44:37.357Z" }, + { url = "https://files.pythonhosted.org/packages/94/a3/9fe9ad23fd35f7de6b91eeb60848986058bd8b5a5c1e256f5860a160cc3e/httptools-0.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ade273d7e767d5fae13fa637f4d53b6e961fb7fd93c7797562663f0171c26660", size = 197214, upload-time = "2024-10-16T19:44:38.738Z" }, + { url = "https://files.pythonhosted.org/packages/ea/d9/82d5e68bab783b632023f2fa31db20bebb4e89dfc4d2293945fd68484ee4/httptools-0.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:856f4bc0478ae143bad54a4242fccb1f3f86a6e1be5548fecfd4102061b3a083", size = 102431, upload-time = "2024-10-16T19:44:39.818Z" }, + { url = "https://files.pythonhosted.org/packages/96/c1/cb499655cbdbfb57b577734fde02f6fa0bbc3fe9fb4d87b742b512908dff/httptools-0.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:322d20ea9cdd1fa98bd6a74b77e2ec5b818abdc3d36695ab402a0de8ef2865a3", size = 473121, upload-time = "2024-10-16T19:44:41.189Z" }, + { url = "https://files.pythonhosted.org/packages/af/71/ee32fd358f8a3bb199b03261f10921716990808a675d8160b5383487a317/httptools-0.6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d87b29bd4486c0093fc64dea80231f7c7f7eb4dc70ae394d70a495ab8436071", size = 473805, upload-time = "2024-10-16T19:44:42.384Z" }, + { url = "https://files.pythonhosted.org/packages/8a/0a/0d4df132bfca1507114198b766f1737d57580c9ad1cf93c1ff673e3387be/httptools-0.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:342dd6946aa6bda4b8f18c734576106b8a31f2fe31492881a9a160ec84ff4bd5", size = 448858, upload-time = "2024-10-16T19:44:43.959Z" }, + { url = "https://files.pythonhosted.org/packages/1e/6a/787004fdef2cabea27bad1073bf6a33f2437b4dbd3b6fb4a9d71172b1c7c/httptools-0.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b36913ba52008249223042dca46e69967985fb4051951f94357ea681e1f5dc0", size = 452042, upload-time = "2024-10-16T19:44:45.071Z" }, + { url = "https://files.pythonhosted.org/packages/4d/dc/7decab5c404d1d2cdc1bb330b1bf70e83d6af0396fd4fc76fc60c0d522bf/httptools-0.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:28908df1b9bb8187393d5b5db91435ccc9c8e891657f9cbb42a2541b44c82fc8", size = 87682, upload-time = "2024-10-16T19:44:46.46Z" }, +] + +[[package]] +name = "huggingface-hub" +version = "0.34.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "fsspec" }, + { name = "hf-xet", marker = "platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/45/c9/bdbe19339f76d12985bc03572f330a01a93c04dffecaaea3061bdd7fb892/huggingface_hub-0.34.4.tar.gz", hash = "sha256:a4228daa6fb001be3f4f4bdaf9a0db00e1739235702848df00885c9b5742c85c", size = 459768, upload-time = "2025-08-08T09:14:52.365Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/7b/bb06b061991107cd8783f300adff3e7b7f284e330fd82f507f2a1417b11d/huggingface_hub-0.34.4-py3-none-any.whl", hash = "sha256:9b365d781739c93ff90c359844221beef048403f1bc1f1c123c191257c3c890a", size = 561452, upload-time = "2025-08-08T09:14:50.159Z" }, +] + +[[package]] +name = "humanfriendly" +version = "10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyreadline3", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cc/3f/2c29224acb2e2df4d2046e4c73ee2662023c58ff5b113c4c1adac0886c43/humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc", size = 360702, upload-time = "2021-09-17T21:40:43.31Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f0/0f/310fb31e39e2d734ccaa2c0fb981ee41f7bd5056ce9bc29b2248bd569169/humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477", size = 86794, upload-time = "2021-09-17T21:40:39.897Z" }, +] + +[[package]] +name = "identify" +version = "2.6.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/c4/62963f25a678f6a050fb0505a65e9e726996171e6dbe1547f79619eefb15/identify-2.6.14.tar.gz", hash = "sha256:663494103b4f717cb26921c52f8751363dc89db64364cd836a9bf1535f53cd6a", size = 99283, upload-time = "2025-09-06T19:30:52.938Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/ae/2ad30f4652712c82f1c23423d79136fbce338932ad166d70c1efb86a5998/identify-2.6.14-py2.py3-none-any.whl", hash = "sha256:11a073da82212c6646b1f39bb20d4483bfb9543bd5566fec60053c4bb309bf2e", size = 99172, upload-time = "2025-09-06T19:30:51.759Z" }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, +] + +[[package]] +name = "importlib-resources" +version = "6.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cf/8c/f834fbf984f691b4f7ff60f50b514cc3de5cc08abfc3295564dd89c5e2e7/importlib_resources-6.5.2.tar.gz", hash = "sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c", size = 44693, upload-time = "2025-01-03T18:51:56.698Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/ed/1f1afb2e9e7f38a545d628f864d562a5ae64fe6f7a10e28ffb9b185b4e89/importlib_resources-6.5.2-py3-none-any.whl", hash = "sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec", size = 37461, upload-time = "2025-01-03T18:51:54.306Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, +] + +[[package]] +name = "ipython" +version = "8.37.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version < '3.11' and sys_platform == 'win32'" }, + { name = "decorator", marker = "python_full_version < '3.11'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "jedi", marker = "python_full_version < '3.11'" }, + { name = "matplotlib-inline", marker = "python_full_version < '3.11'" }, + { name = "pexpect", marker = "python_full_version < '3.11' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit", marker = "python_full_version < '3.11'" }, + { name = "pygments", marker = "python_full_version < '3.11'" }, + { name = "stack-data", marker = "python_full_version < '3.11'" }, + { name = "traitlets", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/85/31/10ac88f3357fc276dc8a64e8880c82e80e7459326ae1d0a211b40abf6665/ipython-8.37.0.tar.gz", hash = "sha256:ca815841e1a41a1e6b73a0b08f3038af9b2252564d01fc405356d34033012216", size = 5606088, upload-time = "2025-05-31T16:39:09.613Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/d0/274fbf7b0b12643cbbc001ce13e6a5b1607ac4929d1b11c72460152c9fc3/ipython-8.37.0-py3-none-any.whl", hash = "sha256:ed87326596b878932dbcb171e3e698845434d8c61b8d8cd474bf663041a9dcf2", size = 831864, upload-time = "2025-05-31T16:39:06.38Z" }, +] + +[[package]] +name = "ipython" +version = "9.5.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.13'", + "python_full_version >= '3.11' and python_full_version < '3.13'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version >= '3.11' and sys_platform == 'win32'" }, + { name = "decorator", marker = "python_full_version >= '3.11'" }, + { name = "ipython-pygments-lexers", marker = "python_full_version >= '3.11'" }, + { name = "jedi", marker = "python_full_version >= '3.11'" }, + { name = "matplotlib-inline", marker = "python_full_version >= '3.11'" }, + { name = "pexpect", marker = "python_full_version >= '3.11' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit", marker = "python_full_version >= '3.11'" }, + { name = "pygments", marker = "python_full_version >= '3.11'" }, + { name = "stack-data", marker = "python_full_version >= '3.11'" }, + { name = "traitlets", marker = "python_full_version >= '3.11'" }, + { name = "typing-extensions", marker = "python_full_version == '3.11.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6e/71/a86262bf5a68bf211bcc71fe302af7e05f18a2852fdc610a854d20d085e6/ipython-9.5.0.tar.gz", hash = "sha256:129c44b941fe6d9b82d36fc7a7c18127ddb1d6f02f78f867f402e2e3adde3113", size = 4389137, upload-time = "2025-08-29T12:15:21.519Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/2a/5628a99d04acb2d2f2e749cdf4ea571d2575e898df0528a090948018b726/ipython-9.5.0-py3-none-any.whl", hash = "sha256:88369ffa1d5817d609120daa523a6da06d02518e582347c29f8451732a9c5e72", size = 612426, upload-time = "2025-08-29T12:15:18.866Z" }, +] + +[[package]] +name = "ipython-pygments-lexers" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pygments", marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393, upload-time = "2025-01-17T11:24:34.505Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074, upload-time = "2025-01-17T11:24:33.271Z" }, +] + +[[package]] +name = "ischedule" +version = "1.2.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/78/46/0d471c8318050c182badc6b6c3d4d05c1705508de57175e6e316de76e43c/ischedule-1.2.7.tar.gz", hash = "sha256:517ac2c717c8de7e39ded69a86ab974348e5273e466a7ad2af6a096668ff9ba5", size = 7421, upload-time = "2024-03-15T19:13:12.606Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/0c/937caa3558fa5787927585cce67af4446fec8996d4c28c88d7efdbad6b14/ischedule-1.2.7-py3-none-any.whl", hash = "sha256:2a327e57e3ea179d3d9675cef1e7a0719f2a936c61a9d1ae288c3a5f5156939d", size = 5273, upload-time = "2024-03-15T19:13:09.105Z" }, +] + +[[package]] +name = "jedi" +version = "0.19.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "parso" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287, upload-time = "2024-11-11T01:41:42.873Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278, upload-time = "2024-11-11T01:41:40.175Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "lark-parser" +version = "0.7.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/34/b8/aa7d6cf2d5efdd2fcd85cf39b33584fe12a0f7086ed451176ceb7fb510eb/lark-parser-0.7.8.tar.gz", hash = "sha256:26215ebb157e6fb2ee74319aa4445b9f3b7e456e26be215ce19fdaaa901c20a4", size = 276204, upload-time = "2019-11-01T12:45:26.558Z" } + +[[package]] +name = "lgpio" +version = "0.2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/56/33/26ec2e8049eaa2f077bf23a12dc61ca559fbfa7bea0516bf263d657ae275/lgpio-0.2.2.0.tar.gz", hash = "sha256:11372e653b200f76a0b3ef8a23a0735c85ec678a9f8550b9893151ed0f863fff", size = 90087, upload-time = "2024-03-29T21:59:55.901Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/b9/d23f9539bbddf47c01a14fc96158a42d9de454fd9d04f7be1347ca6db8fd/lgpio-0.2.2.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:97fe5fb0e888c96031e8899e8c0eca64c63076a6d1f1e774acad8696430b2ff6", size = 376674, upload-time = "2024-03-29T22:00:41.969Z" }, + { url = "https://files.pythonhosted.org/packages/9c/db/fbbade15dbc9febdbd06bd82531c0a78206f96262003145d6953396d9554/lgpio-0.2.2.0-cp310-cp310-manylinux_2_34_aarch64.whl", hash = "sha256:f8f1a2818ed4293182999679ac8559aea70d45743f5a3ae8025837e529d9e0d4", size = 356711, upload-time = "2024-04-01T22:49:43.455Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ca/1c5278b2548e956a52a07efae91ce2300f81c8cf4ad3d7d6b98ce8987e15/lgpio-0.2.2.0-cp310-cp310-manylinux_2_34_x86_64.whl", hash = "sha256:d245f315e4bc5ba1b72df9fd935a16c99e56ccf6b41d9f66a87804c1dfd91c86", size = 362126, upload-time = "2024-04-13T14:08:11.879Z" }, + { url = "https://files.pythonhosted.org/packages/78/4e/5721ae44b29e4fe9175f68c881694e3713066590739a7c87f8cee2835c25/lgpio-0.2.2.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:5b3c403e1fba9c17d178f1bde102726c548fc5c4fc1ccf5ec3e18f3c08e07e04", size = 382992, upload-time = "2024-03-29T22:00:45.039Z" }, + { url = "https://files.pythonhosted.org/packages/88/53/e57a22fe815fc68d0991655c1105b8ed872a68491d32e4e0e7d10ffb5c4d/lgpio-0.2.2.0-cp311-cp311-manylinux_2_34_aarch64.whl", hash = "sha256:a2f71fb95b149d8ac82c7c6bae70f054f6dc42a006ad35c90c7d8e54921fbcf4", size = 364848, upload-time = "2024-04-01T22:49:45.889Z" }, + { url = "https://files.pythonhosted.org/packages/a4/71/11f4e3d76400e4ca43f9f9b014f5a86d9a265340c0bea45cce037277eb34/lgpio-0.2.2.0-cp311-cp311-manylinux_2_34_x86_64.whl", hash = "sha256:e9f4f3915abe5ae0ffdb4b96f485076d80a663876d839e2d3fd9218a71b9873e", size = 370183, upload-time = "2024-04-13T14:08:14.139Z" }, + { url = "https://files.pythonhosted.org/packages/fe/73/e56c9afb845df53492d42bdea01df9895272bccfdd5128f34719c3a07990/lgpio-0.2.2.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:6c65ac42e878764d04a71ed12fe6d46089b36e9e8127722bf29bb2e4bc91de22", size = 383956, upload-time = "2024-03-29T22:00:47.315Z" }, + { url = "https://files.pythonhosted.org/packages/3b/1c/becd00f66d2c65feed9a668ff9d91732394cb6baba7bec505d55de0e30c9/lgpio-0.2.2.0-cp312-cp312-manylinux_2_34_aarch64.whl", hash = "sha256:d907db79292c721c605af08187385ddb3b7af09907e1ffca56cf0cd6558ace0a", size = 366058, upload-time = "2024-04-01T22:49:47.615Z" }, + { url = "https://files.pythonhosted.org/packages/4c/7a/e3b4e5225c9792c4092b2cc07504746acbe62d0a8e4cb023bdf65f6430cf/lgpio-0.2.2.0-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:2aadff092f642fcdada8457c158f87259dfda3a89ec19bae0b99ff22b34aac4b", size = 372103, upload-time = "2024-04-13T14:08:16.351Z" }, +] + +[[package]] +name = "libusb-package" +version = "1.0.26.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "importlib-resources" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/aa/27/c989cd606683102170c2dc2e89771a22dc71b9d88c4d20c3a97f6d23a0a1/libusb_package-1.0.26.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2ffa01d1db3ef9e7faa62b03f409cd077232885ae3fab6f95912db78035a41db", size = 63863, upload-time = "2025-04-01T12:59:12.09Z" }, + { url = "https://files.pythonhosted.org/packages/c2/e4/663a37d23b3a47a641f74556bb42c04b26c46b95fb8a65c11421cb0ccb0d/libusb_package-1.0.26.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:95bbf9872674318a23450cd92053eee01683eeae6b6aa76eba30ee5f37c3765b", size = 59502, upload-time = "2025-04-01T12:59:13.489Z" }, + { url = "https://files.pythonhosted.org/packages/a7/93/c99ea3b13539c501c41e605645693346e08cfcb7747025ee640502f7460d/libusb_package-1.0.26.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36a1c48779c8763fc6bc0331fda668c93b58d55934236d0393d3ec026875f7cd", size = 70247, upload-time = "2025-04-01T14:53:00.28Z" }, + { url = "https://files.pythonhosted.org/packages/2f/c4/84c7af13453e840ba2e3e9f247c9855035077a7214c1f0f273e1df5a845f/libusb_package-1.0.26.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:209efb9a78ac652afc2332b0a63ef2e423202fa3a1bebe5fe3c499e0922afc03", size = 74537, upload-time = "2025-04-01T14:53:01.995Z" }, + { url = "https://files.pythonhosted.org/packages/79/f8/933f70e9a1e05e0d25fb7fb6a5a4512ba7845203b11afc163cfdc98e9b88/libusb_package-1.0.26.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:433e89dd1f9f9a4149b975247cf1d493170454945fec54b4db9fe61c9e6b861f", size = 70652, upload-time = "2025-04-01T14:53:03.346Z" }, + { url = "https://files.pythonhosted.org/packages/ae/96/77f873c2a3a84b93439a973c70ecc53d2b9ae14cb45b4ba710b89d822228/libusb_package-1.0.26.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7e6fcad72db04b30c8495ac0df6a9b1a4ec8705930bfa2160cc9b018f14101a1", size = 71861, upload-time = "2025-04-01T14:53:04.507Z" }, + { url = "https://files.pythonhosted.org/packages/ba/6e/64f83de4274e4a10ad28a03ff7879d3765c5d7efe0e5c833938318a7de20/libusb_package-1.0.26.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:65ee502c4999ded1c71e38769b0a89152c1e03e43b0d35919f3e32a8cbc7cd99", size = 76476, upload-time = "2025-04-01T14:53:05.511Z" }, + { url = "https://files.pythonhosted.org/packages/ad/56/3792e6a41776d85a4113e75c256879355261b5dd1ed22eb55fd8bc924125/libusb_package-1.0.26.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1f6d012df4942c91e6833dd251bd90c1242496a30c81020e43b98df85c66fa30", size = 71037, upload-time = "2025-04-01T14:53:06.642Z" }, + { url = "https://files.pythonhosted.org/packages/ce/f2/99091fdc38e916cc9e254912d1131496ac56ab82506548f6e1fc2eea8429/libusb_package-1.0.26.3-cp310-cp310-win32.whl", hash = "sha256:55c3988f622745a4874ac4face117da19969b82d51250e5334cd176f516bcb57", size = 77642, upload-time = "2025-04-01T12:58:00.114Z" }, + { url = "https://files.pythonhosted.org/packages/c1/9a/7bc9f60e563e535bf80b125d0d7541ad07ecb0160965d48cb8b6dccc2cf6/libusb_package-1.0.26.3-cp310-cp310-win_amd64.whl", hash = "sha256:ba5e87e70833e5fff977d7bf12b7107df427ee21a8021d59520e1fdf14a32368", size = 90594, upload-time = "2025-04-01T12:58:01.799Z" }, + { url = "https://files.pythonhosted.org/packages/c6/bf/3fe9d322e2dcd0437ae2bd6a039117965702ed473ca59d2d6a1c39838009/libusb_package-1.0.26.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:60e15d7d3e4aab31794da95641bc28c4ffec9e24f50891ce33f75794b8f531f3", size = 63864, upload-time = "2025-04-01T12:59:14.567Z" }, + { url = "https://files.pythonhosted.org/packages/bc/70/df0348c11e6aaead4a66cc59840e102ddf64baf8e4b2c1ad5cff1ca83554/libusb_package-1.0.26.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4d93a6137609cf72dc5db69bc337ddf96520231e395beeff69fa77a923090003", size = 59502, upload-time = "2025-04-01T12:59:15.863Z" }, + { url = "https://files.pythonhosted.org/packages/27/67/6c84eebc9fcdf7f26704b5d32b51b3ee5bf4e9090d61286941257bdc8702/libusb_package-1.0.26.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fafb69c5fd42b241fbd20493d014328c507d34e1b7ceb883a20ef14565b26898", size = 70247, upload-time = "2025-04-01T14:53:07.606Z" }, + { url = "https://files.pythonhosted.org/packages/d0/b4/cbcc42ca4b3d8778bf081b96e6e6288a437d82a4cc4e9b982bef40a88856/libusb_package-1.0.26.3-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b0c206cd8a30565a0cede3ba426929e70a37e7b769e41a5ac7f00ca6737dc5d", size = 74537, upload-time = "2025-04-01T14:53:08.61Z" }, + { url = "https://files.pythonhosted.org/packages/c9/0f/fd203fb1fa5eda1d446f345d84205f23533767e6ef837a7c77a2599d5783/libusb_package-1.0.26.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80a2041331c087d5887969405837f86c8422120fe9ba3e6faa44bf4810f07b71", size = 70653, upload-time = "2025-04-01T14:53:09.576Z" }, + { url = "https://files.pythonhosted.org/packages/79/ef/dcc682cb4b29c4d4cdb23df65825c6276753184f6a7b4338c54a59a54c20/libusb_package-1.0.26.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:48b536a1279ee0dbf70b898cffd16cd661774d2c8bbec8ff7178a5bc20196af3", size = 71859, upload-time = "2025-04-01T14:53:10.987Z" }, + { url = "https://files.pythonhosted.org/packages/62/4d/323d5ac4ba339362e4b148c291fbc6e7ee04c6395d5fec967b32432db5c5/libusb_package-1.0.26.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:3f273e33ff1810242f81ea3a0286e25887d99d99019ba83e08be0d1ca456cc05", size = 76476, upload-time = "2025-04-01T14:53:12.019Z" }, + { url = "https://files.pythonhosted.org/packages/a3/3b/506db7f6cbe5dc2f38c14b272b8faf4d43e5559ac99d4dce1a41026ec925/libusb_package-1.0.26.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:67476093601e1ea58a6130426795b906acd8d18d51c84e29a3a69548a5dfcf5d", size = 71037, upload-time = "2025-04-01T14:53:13.42Z" }, + { url = "https://files.pythonhosted.org/packages/3e/40/2538763c06e07bbbe0a5c8830779ef1ed1cea845264a91973bf31b9ecce5/libusb_package-1.0.26.3-cp311-cp311-win32.whl", hash = "sha256:8f3eed2852ee4f08847a221749a98d0f4f3962f8bed967e2253327db1171ba60", size = 77642, upload-time = "2025-04-01T12:58:03.106Z" }, + { url = "https://files.pythonhosted.org/packages/a4/46/0cd5ea91c5bbe6293c0936c96915051e31750f72e9556718af666af3fe45/libusb_package-1.0.26.3-cp311-cp311-win_amd64.whl", hash = "sha256:b48b5f5b17c7ac5e315e233f9ee801f730aac6183eb53a3226b01245d7bcfe00", size = 90592, upload-time = "2025-04-01T12:58:04.103Z" }, + { url = "https://files.pythonhosted.org/packages/2e/f6/83e13936b5799360eae8f0e31b5b298dd092451b91136d7cd13852777954/libusb_package-1.0.26.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c9404298485762a4e73b416e8a3208d33aa3274fb9b870c2a1cacba7e2918f19", size = 62045, upload-time = "2025-04-01T12:59:16.817Z" }, + { url = "https://files.pythonhosted.org/packages/33/97/86ed73880b6734c9383be5f34061b541e8fe5bd0303580b1f5abe2962d58/libusb_package-1.0.26.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8126f6711318dad4cb2805ea20cd47b895a847207087d8fdb032e082dd7a2e24", size = 59502, upload-time = "2025-04-01T12:59:17.72Z" }, + { url = "https://files.pythonhosted.org/packages/95/f7/27b67b8fe63450abf0b0b66aacf75d5d64cdf30317e214409ceb534f34b4/libusb_package-1.0.26.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11c219366e4a2368117b9a9807261f3506b5623531f8b8ce41af5bbaec8156a0", size = 70247, upload-time = "2025-04-01T14:53:14.387Z" }, + { url = "https://files.pythonhosted.org/packages/8c/11/613543f9c6dab5a82eefd0c78d52d08b5d9eb93a0362151fbedf74b32541/libusb_package-1.0.26.3-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8809a50d8ab84297344c54e862027090c0d73b14abef843a8b5f783313f49457", size = 74537, upload-time = "2025-04-01T14:53:15.345Z" }, + { url = "https://files.pythonhosted.org/packages/f0/43/5a2331615693b56221a902869fb2094d9a0b9a764a8706c8ba16e915f77c/libusb_package-1.0.26.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a83067c3dfdbb3856badb4532eaea22e8502b52ce4245f5ab46acf93d7fbd471", size = 70652, upload-time = "2025-04-01T14:53:16.319Z" }, + { url = "https://files.pythonhosted.org/packages/44/1a/186d4ec86421b69feb45e214edb5301fbcb9e8dc9df963678aeff1a447d5/libusb_package-1.0.26.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b56be087ea9cde8e50fb02740a4f0cefb6f63c61ac2e7812a9244487614a3973", size = 71860, upload-time = "2025-04-01T14:53:17.87Z" }, + { url = "https://files.pythonhosted.org/packages/4b/3c/8cebdad822d7bfcb683a77d5fd113fbc6f72516cfb7c1c3a274fefafa8e9/libusb_package-1.0.26.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ea0f6bf40e54b1671e763e40c9dbed46bf7f596a4cd98b7c827e147f176d8c97", size = 76476, upload-time = "2025-04-01T14:53:19.202Z" }, + { url = "https://files.pythonhosted.org/packages/49/5f/30c625b6c4ecd14871644c1d16e97d7c971f82a0f87a9cfa81022f85bcfc/libusb_package-1.0.26.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b40f77df991c6db8621de9575504886eca03a00277e521a4d64b66cbef8f6997", size = 71037, upload-time = "2025-04-01T14:53:21.359Z" }, + { url = "https://files.pythonhosted.org/packages/7f/e9/3aa3ff3242867b7f22ee3ce28d0e93ff88547f170ca1b8a6edc59660d974/libusb_package-1.0.26.3-cp312-cp312-win32.whl", hash = "sha256:6eee99c9fde137443869c8604d0c01b2127a9545ebc59d06a3376cf1d891e786", size = 77642, upload-time = "2025-04-01T12:58:05.471Z" }, + { url = "https://files.pythonhosted.org/packages/15/0e/913ddb1849f828fc385438874c34541939d9b06c0e5616f48f24cddd24de/libusb_package-1.0.26.3-cp312-cp312-win_amd64.whl", hash = "sha256:5e09c0b6b3cd475841cffe78e46e91df58f0c6c02ea105ea1a4d0755a07c8006", size = 90593, upload-time = "2025-04-01T12:58:06.798Z" }, + { url = "https://files.pythonhosted.org/packages/b4/b8/23bc7f3f53b4a5b1027c721ec3eb42324ca1ec56355f0d0851307adc7c6c/libusb_package-1.0.26.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:04c4505e2ca68d3dc6938f116ff9bf82daffb06c1a97aba08293a84715a998da", size = 62045, upload-time = "2025-04-01T12:59:18.698Z" }, + { url = "https://files.pythonhosted.org/packages/a2/f8/e3be96d0604070488ddc5ce5af1976992e1f4a00e6441c94edf807f274d5/libusb_package-1.0.26.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4961cdb3c622aa9f858d3e4f99a58ce5e822a97c22abc77040fd806cb5fa4c66", size = 59502, upload-time = "2025-04-01T12:59:19.632Z" }, + { url = "https://files.pythonhosted.org/packages/24/d5/df1508df5e6776ac8a09a2858991df29bc96ea6a0d1f90240b1c4d59b45d/libusb_package-1.0.26.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16182670e0c23235521b447054c5a01600bd8f1eed3bb08eedbb0d9f8a43249f", size = 70247, upload-time = "2025-04-01T14:53:22.328Z" }, + { url = "https://files.pythonhosted.org/packages/65/01/4cc9eed12b9214c088cfa8055ece3b1db970404400be9d7e3dda68d198f2/libusb_package-1.0.26.3-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:75ea57b2cc903d28ec1d4b909902df442cbf21949d80d5b3d8b9dac36ac45d1a", size = 74537, upload-time = "2025-04-01T14:53:23.306Z" }, + { url = "https://files.pythonhosted.org/packages/99/83/9eb317f706f588f4b6679bddb8abee3b115ce53dc3fa560cca59910f8807/libusb_package-1.0.26.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10d30b51b128ef5112fff73268b4696fea00b5676b3f39a5ee859bd76cb3ace5", size = 70651, upload-time = "2025-04-01T14:53:24.33Z" }, + { url = "https://files.pythonhosted.org/packages/22/49/85d3b307b4a20cf0150ab381e6e0385e5b78cb5dede8bade0a2d655d3fd3/libusb_package-1.0.26.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5c098dcfcfa8000cab42f33e19628c8fdb16111670db381048b2993651f2413b", size = 71860, upload-time = "2025-04-01T14:53:25.752Z" }, + { url = "https://files.pythonhosted.org/packages/da/7a/2271a5ae542d9036d9254415ae745d5c5d01a08d56d13054b2439bf9d392/libusb_package-1.0.26.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:93169aeab0657255fe6c9f757cf408f559db13827a1d122fc89239994d7d51f1", size = 76477, upload-time = "2025-04-01T14:53:27.564Z" }, + { url = "https://files.pythonhosted.org/packages/5c/9d/d06d53994bb164564ec142ef631a4afa31e324994cf223f169ecca127f3a/libusb_package-1.0.26.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:63257653ee1ee06aa836e942f4bb89a1d7a0c6ae3d6183647a9011e585ffa1e3", size = 71036, upload-time = "2025-04-01T14:53:29.011Z" }, + { url = "https://files.pythonhosted.org/packages/32/3d/97f775a1d582548b1eb2a42444c58813e5fd93d568fc3b9ace59f64df527/libusb_package-1.0.26.3-cp313-cp313-win32.whl", hash = "sha256:05db4cc801db2e6373a808725748a701509f9450fecf393fbebab61c45d50b50", size = 77642, upload-time = "2025-04-01T12:58:07.774Z" }, + { url = "https://files.pythonhosted.org/packages/f1/c4/d5234607697ca60593fbef88428a154317ac31f5c58ee23337b8a9360e91/libusb_package-1.0.26.3-cp313-cp313-win_amd64.whl", hash = "sha256:1cd4aec825dac2b4fa5d23b37f6d72e63a1127987e5a073dabeb7b73528623a3", size = 90593, upload-time = "2025-04-01T12:58:08.676Z" }, + { url = "https://files.pythonhosted.org/packages/9f/20/f5293a167b4e910badc64272131a8bb8dbd80f10dfd843eb07846aafaef2/libusb_package-1.0.26.3-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6a62bf7fa20fe704ed0413e74d620b37bdfe6b084478d23cc85b1f10708f2596", size = 62194, upload-time = "2025-04-01T12:59:26.137Z" }, + { url = "https://files.pythonhosted.org/packages/28/e5/1ceae06e6c965847d89be36de58908354c35faf641cd4c6071c9f06a7e9b/libusb_package-1.0.26.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:ab40d6b295bcfbe37f280268ea0d0a1ef4b1d795025fe41b3dda48e07eb0fc8e", size = 59506, upload-time = "2025-04-01T12:59:27.081Z" }, + { url = "https://files.pythonhosted.org/packages/6e/74/8afb1a05fda665abebac3bb44a7738f23437cac11081e44a929b51afee6a/libusb_package-1.0.26.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a125d72cca545950ae357aa6d7f0f33dfb39f16b895691cf3f8c9b772bc7e31", size = 70255, upload-time = "2025-04-01T14:53:48.956Z" }, + { url = "https://files.pythonhosted.org/packages/35/46/5e6be05f302e887055a277bbb5cc1db6be9af01319b35f1a9663211b075c/libusb_package-1.0.26.3-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:229d9f26af5828512828154d6bae4214ef5016a9401dd022477e06d0df5153e7", size = 74543, upload-time = "2025-04-01T14:53:49.979Z" }, + { url = "https://files.pythonhosted.org/packages/16/e4/51a81cc69ba4eefdd9a291cc5e6596a8f7d8c7f2378273917bf64465412d/libusb_package-1.0.26.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b8b862f190c244f29699d783f044e3d816fed84e39bca9a3c3731140f0b1b39", size = 70658, upload-time = "2025-04-01T14:53:51.132Z" }, + { url = "https://files.pythonhosted.org/packages/15/14/2c85379880d475f12ee74a27b02a2ffe435d863f8045fe80e5c246c30f23/libusb_package-1.0.26.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:fadaad1181784713948f9cbb7ad1cab8f2b307e784e2e162ed80ba5d2f745901", size = 90602, upload-time = "2025-04-01T12:58:16.828Z" }, +] + +[[package]] +name = "log-throttling" +version = "0.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/76/be/09fda383d82a7b91f12ff5002bc909e101769c802ff3b1d2cb9f533d1a2f/log-throttling-0.0.3.tar.gz", hash = "sha256:df714e1448cd9cb4c916cf668ab88323032e61ece733548708d996ecf63190b9", size = 3533, upload-time = "2022-03-08T16:29:23.674Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/6e/995ebad4ab08250b4afe1d91fe3086b863ed9bb74539d90485250a50f08a/log_throttling-0.0.3-py3-none-any.whl", hash = "sha256:a82508f489dd44a60b737a0339c784dd8ee2b9db16bdcf587a8ae1012a7f6ffc", size = 4081, upload-time = "2022-03-08T16:29:21.443Z" }, +] + +[[package]] +name = "lxml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/88/262177de60548e5a2bfc46ad28232c9e9cbde697bd94132aeb80364675cb/lxml-6.0.2.tar.gz", hash = "sha256:cd79f3367bd74b317dda655dc8fcfa304d9eb6e4fb06b7168c5cf27f96e0cd62", size = 4073426, upload-time = "2025-09-22T04:04:59.287Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/8a/f8192a08237ef2fb1b19733f709db88a4c43bc8ab8357f01cb41a27e7f6a/lxml-6.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e77dd455b9a16bbd2a5036a63ddbd479c19572af81b624e79ef422f929eef388", size = 8590589, upload-time = "2025-09-22T04:00:10.51Z" }, + { url = "https://files.pythonhosted.org/packages/12/64/27bcd07ae17ff5e5536e8d88f4c7d581b48963817a13de11f3ac3329bfa2/lxml-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5d444858b9f07cefff6455b983aea9a67f7462ba1f6cbe4a21e8bf6791bf2153", size = 4629671, upload-time = "2025-09-22T04:00:15.411Z" }, + { url = "https://files.pythonhosted.org/packages/02/5a/a7d53b3291c324e0b6e48f3c797be63836cc52156ddf8f33cd72aac78866/lxml-6.0.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f952dacaa552f3bb8834908dddd500ba7d508e6ea6eb8c52eb2d28f48ca06a31", size = 4999961, upload-time = "2025-09-22T04:00:17.619Z" }, + { url = "https://files.pythonhosted.org/packages/f5/55/d465e9b89df1761674d8672bb3e4ae2c47033b01ec243964b6e334c6743f/lxml-6.0.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:71695772df6acea9f3c0e59e44ba8ac50c4f125217e84aab21074a1a55e7e5c9", size = 5157087, upload-time = "2025-09-22T04:00:19.868Z" }, + { url = "https://files.pythonhosted.org/packages/62/38/3073cd7e3e8dfc3ba3c3a139e33bee3a82de2bfb0925714351ad3d255c13/lxml-6.0.2-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:17f68764f35fd78d7c4cc4ef209a184c38b65440378013d24b8aecd327c3e0c8", size = 5067620, upload-time = "2025-09-22T04:00:21.877Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d3/1e001588c5e2205637b08985597827d3827dbaaece16348c8822bfe61c29/lxml-6.0.2-cp310-cp310-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:058027e261afed589eddcfe530fcc6f3402d7fd7e89bfd0532df82ebc1563dba", size = 5406664, upload-time = "2025-09-22T04:00:23.714Z" }, + { url = "https://files.pythonhosted.org/packages/20/cf/cab09478699b003857ed6ebfe95e9fb9fa3d3c25f1353b905c9b73cfb624/lxml-6.0.2-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8ffaeec5dfea5881d4c9d8913a32d10cfe3923495386106e4a24d45300ef79c", size = 5289397, upload-time = "2025-09-22T04:00:25.544Z" }, + { url = "https://files.pythonhosted.org/packages/a3/84/02a2d0c38ac9a8b9f9e5e1bbd3f24b3f426044ad618b552e9549ee91bd63/lxml-6.0.2-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:f2e3b1a6bb38de0bc713edd4d612969dd250ca8b724be8d460001a387507021c", size = 4772178, upload-time = "2025-09-22T04:00:27.602Z" }, + { url = "https://files.pythonhosted.org/packages/56/87/e1ceadcc031ec4aa605fe95476892d0b0ba3b7f8c7dcdf88fdeff59a9c86/lxml-6.0.2-cp310-cp310-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d6690ec5ec1cce0385cb20896b16be35247ac8c2046e493d03232f1c2414d321", size = 5358148, upload-time = "2025-09-22T04:00:29.323Z" }, + { url = "https://files.pythonhosted.org/packages/fe/13/5bb6cf42bb228353fd4ac5f162c6a84fd68a4d6f67c1031c8cf97e131fc6/lxml-6.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f2a50c3c1d11cad0ebebbac357a97b26aa79d2bcaf46f256551152aa85d3a4d1", size = 5112035, upload-time = "2025-09-22T04:00:31.061Z" }, + { url = "https://files.pythonhosted.org/packages/e4/e2/ea0498552102e59834e297c5c6dff8d8ded3db72ed5e8aad77871476f073/lxml-6.0.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:3efe1b21c7801ffa29a1112fab3b0f643628c30472d507f39544fd48e9549e34", size = 4799111, upload-time = "2025-09-22T04:00:33.11Z" }, + { url = "https://files.pythonhosted.org/packages/6a/9e/8de42b52a73abb8af86c66c969b3b4c2a96567b6ac74637c037d2e3baa60/lxml-6.0.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:59c45e125140b2c4b33920d21d83681940ca29f0b83f8629ea1a2196dc8cfe6a", size = 5351662, upload-time = "2025-09-22T04:00:35.237Z" }, + { url = "https://files.pythonhosted.org/packages/28/a2/de776a573dfb15114509a37351937c367530865edb10a90189d0b4b9b70a/lxml-6.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:452b899faa64f1805943ec1c0c9ebeaece01a1af83e130b69cdefeda180bb42c", size = 5314973, upload-time = "2025-09-22T04:00:37.086Z" }, + { url = "https://files.pythonhosted.org/packages/50/a0/3ae1b1f8964c271b5eec91db2043cf8c6c0bce101ebb2a633b51b044db6c/lxml-6.0.2-cp310-cp310-win32.whl", hash = "sha256:1e786a464c191ca43b133906c6903a7e4d56bef376b75d97ccbb8ec5cf1f0a4b", size = 3611953, upload-time = "2025-09-22T04:00:39.224Z" }, + { url = "https://files.pythonhosted.org/packages/d1/70/bd42491f0634aad41bdfc1e46f5cff98825fb6185688dc82baa35d509f1a/lxml-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:dacf3c64ef3f7440e3167aa4b49aa9e0fb99e0aa4f9ff03795640bf94531bcb0", size = 4032695, upload-time = "2025-09-22T04:00:41.402Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d0/05c6a72299f54c2c561a6c6cbb2f512e047fca20ea97a05e57931f194ac4/lxml-6.0.2-cp310-cp310-win_arm64.whl", hash = "sha256:45f93e6f75123f88d7f0cfd90f2d05f441b808562bf0bc01070a00f53f5028b5", size = 3680051, upload-time = "2025-09-22T04:00:43.525Z" }, + { url = "https://files.pythonhosted.org/packages/77/d5/becbe1e2569b474a23f0c672ead8a29ac50b2dc1d5b9de184831bda8d14c/lxml-6.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:13e35cbc684aadf05d8711a5d1b5857c92e5e580efa9a0d2be197199c8def607", size = 8634365, upload-time = "2025-09-22T04:00:45.672Z" }, + { url = "https://files.pythonhosted.org/packages/28/66/1ced58f12e804644426b85d0bb8a4478ca77bc1761455da310505f1a3526/lxml-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b1675e096e17c6fe9c0e8c81434f5736c0739ff9ac6123c87c2d452f48fc938", size = 4650793, upload-time = "2025-09-22T04:00:47.783Z" }, + { url = "https://files.pythonhosted.org/packages/11/84/549098ffea39dfd167e3f174b4ce983d0eed61f9d8d25b7bf2a57c3247fc/lxml-6.0.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8ac6e5811ae2870953390452e3476694196f98d447573234592d30488147404d", size = 4944362, upload-time = "2025-09-22T04:00:49.845Z" }, + { url = "https://files.pythonhosted.org/packages/ac/bd/f207f16abf9749d2037453d56b643a7471d8fde855a231a12d1e095c4f01/lxml-6.0.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5aa0fc67ae19d7a64c3fe725dc9a1bb11f80e01f78289d05c6f62545affec438", size = 5083152, upload-time = "2025-09-22T04:00:51.709Z" }, + { url = "https://files.pythonhosted.org/packages/15/ae/bd813e87d8941d52ad5b65071b1affb48da01c4ed3c9c99e40abb266fbff/lxml-6.0.2-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de496365750cc472b4e7902a485d3f152ecf57bd3ba03ddd5578ed8ceb4c5964", size = 5023539, upload-time = "2025-09-22T04:00:53.593Z" }, + { url = "https://files.pythonhosted.org/packages/02/cd/9bfef16bd1d874fbe0cb51afb00329540f30a3283beb9f0780adbb7eec03/lxml-6.0.2-cp311-cp311-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:200069a593c5e40b8f6fc0d84d86d970ba43138c3e68619ffa234bc9bb806a4d", size = 5344853, upload-time = "2025-09-22T04:00:55.524Z" }, + { url = "https://files.pythonhosted.org/packages/b8/89/ea8f91594bc5dbb879734d35a6f2b0ad50605d7fb419de2b63d4211765cc/lxml-6.0.2-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d2de809c2ee3b888b59f995625385f74629707c9355e0ff856445cdcae682b7", size = 5225133, upload-time = "2025-09-22T04:00:57.269Z" }, + { url = "https://files.pythonhosted.org/packages/b9/37/9c735274f5dbec726b2db99b98a43950395ba3d4a1043083dba2ad814170/lxml-6.0.2-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:b2c3da8d93cf5db60e8858c17684c47d01fee6405e554fb55018dd85fc23b178", size = 4677944, upload-time = "2025-09-22T04:00:59.052Z" }, + { url = "https://files.pythonhosted.org/packages/20/28/7dfe1ba3475d8bfca3878365075abe002e05d40dfaaeb7ec01b4c587d533/lxml-6.0.2-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:442de7530296ef5e188373a1ea5789a46ce90c4847e597856570439621d9c553", size = 5284535, upload-time = "2025-09-22T04:01:01.335Z" }, + { url = "https://files.pythonhosted.org/packages/e7/cf/5f14bc0de763498fc29510e3532bf2b4b3a1c1d5d0dff2e900c16ba021ef/lxml-6.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2593c77efde7bfea7f6389f1ab249b15ed4aa5bc5cb5131faa3b843c429fbedb", size = 5067343, upload-time = "2025-09-22T04:01:03.13Z" }, + { url = "https://files.pythonhosted.org/packages/1c/b0/bb8275ab5472f32b28cfbbcc6db7c9d092482d3439ca279d8d6fa02f7025/lxml-6.0.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:3e3cb08855967a20f553ff32d147e14329b3ae70ced6edc2f282b94afbc74b2a", size = 4725419, upload-time = "2025-09-22T04:01:05.013Z" }, + { url = "https://files.pythonhosted.org/packages/25/4c/7c222753bc72edca3b99dbadba1b064209bc8ed4ad448af990e60dcce462/lxml-6.0.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:2ed6c667fcbb8c19c6791bbf40b7268ef8ddf5a96940ba9404b9f9a304832f6c", size = 5275008, upload-time = "2025-09-22T04:01:07.327Z" }, + { url = "https://files.pythonhosted.org/packages/6c/8c/478a0dc6b6ed661451379447cdbec77c05741a75736d97e5b2b729687828/lxml-6.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b8f18914faec94132e5b91e69d76a5c1d7b0c73e2489ea8929c4aaa10b76bbf7", size = 5248906, upload-time = "2025-09-22T04:01:09.452Z" }, + { url = "https://files.pythonhosted.org/packages/2d/d9/5be3a6ab2784cdf9accb0703b65e1b64fcdd9311c9f007630c7db0cfcce1/lxml-6.0.2-cp311-cp311-win32.whl", hash = "sha256:6605c604e6daa9e0d7f0a2137bdc47a2e93b59c60a65466353e37f8272f47c46", size = 3610357, upload-time = "2025-09-22T04:01:11.102Z" }, + { url = "https://files.pythonhosted.org/packages/e2/7d/ca6fb13349b473d5732fb0ee3eec8f6c80fc0688e76b7d79c1008481bf1f/lxml-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e5867f2651016a3afd8dd2c8238baa66f1e2802f44bc17e236f547ace6647078", size = 4036583, upload-time = "2025-09-22T04:01:12.766Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a2/51363b5ecd3eab46563645f3a2c3836a2fc67d01a1b87c5017040f39f567/lxml-6.0.2-cp311-cp311-win_arm64.whl", hash = "sha256:4197fb2534ee05fd3e7afaab5d8bfd6c2e186f65ea7f9cd6a82809c887bd1285", size = 3680591, upload-time = "2025-09-22T04:01:14.874Z" }, + { url = "https://files.pythonhosted.org/packages/f3/c8/8ff2bc6b920c84355146cd1ab7d181bc543b89241cfb1ebee824a7c81457/lxml-6.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a59f5448ba2ceccd06995c95ea59a7674a10de0810f2ce90c9006f3cbc044456", size = 8661887, upload-time = "2025-09-22T04:01:17.265Z" }, + { url = "https://files.pythonhosted.org/packages/37/6f/9aae1008083bb501ef63284220ce81638332f9ccbfa53765b2b7502203cf/lxml-6.0.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e8113639f3296706fbac34a30813929e29247718e88173ad849f57ca59754924", size = 4667818, upload-time = "2025-09-22T04:01:19.688Z" }, + { url = "https://files.pythonhosted.org/packages/f1/ca/31fb37f99f37f1536c133476674c10b577e409c0a624384147653e38baf2/lxml-6.0.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a8bef9b9825fa8bc816a6e641bb67219489229ebc648be422af695f6e7a4fa7f", size = 4950807, upload-time = "2025-09-22T04:01:21.487Z" }, + { url = "https://files.pythonhosted.org/packages/da/87/f6cb9442e4bada8aab5ae7e1046264f62fdbeaa6e3f6211b93f4c0dd97f1/lxml-6.0.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:65ea18d710fd14e0186c2f973dc60bb52039a275f82d3c44a0e42b43440ea534", size = 5109179, upload-time = "2025-09-22T04:01:23.32Z" }, + { url = "https://files.pythonhosted.org/packages/c8/20/a7760713e65888db79bbae4f6146a6ae5c04e4a204a3c48896c408cd6ed2/lxml-6.0.2-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c371aa98126a0d4c739ca93ceffa0fd7a5d732e3ac66a46e74339acd4d334564", size = 5023044, upload-time = "2025-09-22T04:01:25.118Z" }, + { url = "https://files.pythonhosted.org/packages/a2/b0/7e64e0460fcb36471899f75831509098f3fd7cd02a3833ac517433cb4f8f/lxml-6.0.2-cp312-cp312-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:700efd30c0fa1a3581d80a748157397559396090a51d306ea59a70020223d16f", size = 5359685, upload-time = "2025-09-22T04:01:27.398Z" }, + { url = "https://files.pythonhosted.org/packages/b9/e1/e5df362e9ca4e2f48ed6411bd4b3a0ae737cc842e96877f5bf9428055ab4/lxml-6.0.2-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c33e66d44fe60e72397b487ee92e01da0d09ba2d66df8eae42d77b6d06e5eba0", size = 5654127, upload-time = "2025-09-22T04:01:29.629Z" }, + { url = "https://files.pythonhosted.org/packages/c6/d1/232b3309a02d60f11e71857778bfcd4acbdb86c07db8260caf7d008b08f8/lxml-6.0.2-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90a345bbeaf9d0587a3aaffb7006aa39ccb6ff0e96a57286c0cb2fd1520ea192", size = 5253958, upload-time = "2025-09-22T04:01:31.535Z" }, + { url = "https://files.pythonhosted.org/packages/35/35/d955a070994725c4f7d80583a96cab9c107c57a125b20bb5f708fe941011/lxml-6.0.2-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:064fdadaf7a21af3ed1dcaa106b854077fbeada827c18f72aec9346847cd65d0", size = 4711541, upload-time = "2025-09-22T04:01:33.801Z" }, + { url = "https://files.pythonhosted.org/packages/1e/be/667d17363b38a78c4bd63cfd4b4632029fd68d2c2dc81f25ce9eb5224dd5/lxml-6.0.2-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fbc74f42c3525ac4ffa4b89cbdd00057b6196bcefe8bce794abd42d33a018092", size = 5267426, upload-time = "2025-09-22T04:01:35.639Z" }, + { url = "https://files.pythonhosted.org/packages/ea/47/62c70aa4a1c26569bc958c9ca86af2bb4e1f614e8c04fb2989833874f7ae/lxml-6.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6ddff43f702905a4e32bc24f3f2e2edfe0f8fde3277d481bffb709a4cced7a1f", size = 5064917, upload-time = "2025-09-22T04:01:37.448Z" }, + { url = "https://files.pythonhosted.org/packages/bd/55/6ceddaca353ebd0f1908ef712c597f8570cc9c58130dbb89903198e441fd/lxml-6.0.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6da5185951d72e6f5352166e3da7b0dc27aa70bd1090b0eb3f7f7212b53f1bb8", size = 4788795, upload-time = "2025-09-22T04:01:39.165Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e8/fd63e15da5e3fd4c2146f8bbb3c14e94ab850589beab88e547b2dbce22e1/lxml-6.0.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:57a86e1ebb4020a38d295c04fc79603c7899e0df71588043eb218722dabc087f", size = 5676759, upload-time = "2025-09-22T04:01:41.506Z" }, + { url = "https://files.pythonhosted.org/packages/76/47/b3ec58dc5c374697f5ba37412cd2728f427d056315d124dd4b61da381877/lxml-6.0.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:2047d8234fe735ab77802ce5f2297e410ff40f5238aec569ad7c8e163d7b19a6", size = 5255666, upload-time = "2025-09-22T04:01:43.363Z" }, + { url = "https://files.pythonhosted.org/packages/19/93/03ba725df4c3d72afd9596eef4a37a837ce8e4806010569bedfcd2cb68fd/lxml-6.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6f91fd2b2ea15a6800c8e24418c0775a1694eefc011392da73bc6cef2623b322", size = 5277989, upload-time = "2025-09-22T04:01:45.215Z" }, + { url = "https://files.pythonhosted.org/packages/c6/80/c06de80bfce881d0ad738576f243911fccf992687ae09fd80b734712b39c/lxml-6.0.2-cp312-cp312-win32.whl", hash = "sha256:3ae2ce7d6fedfb3414a2b6c5e20b249c4c607f72cb8d2bb7cc9c6ec7c6f4e849", size = 3611456, upload-time = "2025-09-22T04:01:48.243Z" }, + { url = "https://files.pythonhosted.org/packages/f7/d7/0cdfb6c3e30893463fb3d1e52bc5f5f99684a03c29a0b6b605cfae879cd5/lxml-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:72c87e5ee4e58a8354fb9c7c84cbf95a1c8236c127a5d1b7683f04bed8361e1f", size = 4011793, upload-time = "2025-09-22T04:01:50.042Z" }, + { url = "https://files.pythonhosted.org/packages/ea/7b/93c73c67db235931527301ed3785f849c78991e2e34f3fd9a6663ffda4c5/lxml-6.0.2-cp312-cp312-win_arm64.whl", hash = "sha256:61cb10eeb95570153e0c0e554f58df92ecf5109f75eacad4a95baa709e26c3d6", size = 3672836, upload-time = "2025-09-22T04:01:52.145Z" }, + { url = "https://files.pythonhosted.org/packages/53/fd/4e8f0540608977aea078bf6d79f128e0e2c2bba8af1acf775c30baa70460/lxml-6.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9b33d21594afab46f37ae58dfadd06636f154923c4e8a4d754b0127554eb2e77", size = 8648494, upload-time = "2025-09-22T04:01:54.242Z" }, + { url = "https://files.pythonhosted.org/packages/5d/f4/2a94a3d3dfd6c6b433501b8d470a1960a20ecce93245cf2db1706adf6c19/lxml-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6c8963287d7a4c5c9a432ff487c52e9c5618667179c18a204bdedb27310f022f", size = 4661146, upload-time = "2025-09-22T04:01:56.282Z" }, + { url = "https://files.pythonhosted.org/packages/25/2e/4efa677fa6b322013035d38016f6ae859d06cac67437ca7dc708a6af7028/lxml-6.0.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1941354d92699fb5ffe6ed7b32f9649e43c2feb4b97205f75866f7d21aa91452", size = 4946932, upload-time = "2025-09-22T04:01:58.989Z" }, + { url = "https://files.pythonhosted.org/packages/ce/0f/526e78a6d38d109fdbaa5049c62e1d32fdd70c75fb61c4eadf3045d3d124/lxml-6.0.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bb2f6ca0ae2d983ded09357b84af659c954722bbf04dea98030064996d156048", size = 5100060, upload-time = "2025-09-22T04:02:00.812Z" }, + { url = "https://files.pythonhosted.org/packages/81/76/99de58d81fa702cc0ea7edae4f4640416c2062813a00ff24bd70ac1d9c9b/lxml-6.0.2-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb2a12d704f180a902d7fa778c6d71f36ceb7b0d317f34cdc76a5d05aa1dd1df", size = 5019000, upload-time = "2025-09-22T04:02:02.671Z" }, + { url = "https://files.pythonhosted.org/packages/b5/35/9e57d25482bc9a9882cb0037fdb9cc18f4b79d85df94fa9d2a89562f1d25/lxml-6.0.2-cp313-cp313-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:6ec0e3f745021bfed19c456647f0298d60a24c9ff86d9d051f52b509663feeb1", size = 5348496, upload-time = "2025-09-22T04:02:04.904Z" }, + { url = "https://files.pythonhosted.org/packages/a6/8e/cb99bd0b83ccc3e8f0f528e9aa1f7a9965dfec08c617070c5db8d63a87ce/lxml-6.0.2-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:846ae9a12d54e368933b9759052d6206a9e8b250291109c48e350c1f1f49d916", size = 5643779, upload-time = "2025-09-22T04:02:06.689Z" }, + { url = "https://files.pythonhosted.org/packages/d0/34/9e591954939276bb679b73773836c6684c22e56d05980e31d52a9a8deb18/lxml-6.0.2-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ef9266d2aa545d7374938fb5c484531ef5a2ec7f2d573e62f8ce722c735685fd", size = 5244072, upload-time = "2025-09-22T04:02:08.587Z" }, + { url = "https://files.pythonhosted.org/packages/8d/27/b29ff065f9aaca443ee377aff699714fcbffb371b4fce5ac4ca759e436d5/lxml-6.0.2-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:4077b7c79f31755df33b795dc12119cb557a0106bfdab0d2c2d97bd3cf3dffa6", size = 4718675, upload-time = "2025-09-22T04:02:10.783Z" }, + { url = "https://files.pythonhosted.org/packages/2b/9f/f756f9c2cd27caa1a6ef8c32ae47aadea697f5c2c6d07b0dae133c244fbe/lxml-6.0.2-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a7c5d5e5f1081955358533be077166ee97ed2571d6a66bdba6ec2f609a715d1a", size = 5255171, upload-time = "2025-09-22T04:02:12.631Z" }, + { url = "https://files.pythonhosted.org/packages/61/46/bb85ea42d2cb1bd8395484fd72f38e3389611aa496ac7772da9205bbda0e/lxml-6.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8f8d0cbd0674ee89863a523e6994ac25fd5be9c8486acfc3e5ccea679bad2679", size = 5057175, upload-time = "2025-09-22T04:02:14.718Z" }, + { url = "https://files.pythonhosted.org/packages/95/0c/443fc476dcc8e41577f0af70458c50fe299a97bb6b7505bb1ae09aa7f9ac/lxml-6.0.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:2cbcbf6d6e924c28f04a43f3b6f6e272312a090f269eff68a2982e13e5d57659", size = 4785688, upload-time = "2025-09-22T04:02:16.957Z" }, + { url = "https://files.pythonhosted.org/packages/48/78/6ef0b359d45bb9697bc5a626e1992fa5d27aa3f8004b137b2314793b50a0/lxml-6.0.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:dfb874cfa53340009af6bdd7e54ebc0d21012a60a4e65d927c2e477112e63484", size = 5660655, upload-time = "2025-09-22T04:02:18.815Z" }, + { url = "https://files.pythonhosted.org/packages/ff/ea/e1d33808f386bc1339d08c0dcada6e4712d4ed8e93fcad5f057070b7988a/lxml-6.0.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:fb8dae0b6b8b7f9e96c26fdd8121522ce5de9bb5538010870bd538683d30e9a2", size = 5247695, upload-time = "2025-09-22T04:02:20.593Z" }, + { url = "https://files.pythonhosted.org/packages/4f/47/eba75dfd8183673725255247a603b4ad606f4ae657b60c6c145b381697da/lxml-6.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:358d9adae670b63e95bc59747c72f4dc97c9ec58881d4627fe0120da0f90d314", size = 5269841, upload-time = "2025-09-22T04:02:22.489Z" }, + { url = "https://files.pythonhosted.org/packages/76/04/5c5e2b8577bc936e219becb2e98cdb1aca14a4921a12995b9d0c523502ae/lxml-6.0.2-cp313-cp313-win32.whl", hash = "sha256:e8cd2415f372e7e5a789d743d133ae474290a90b9023197fd78f32e2dc6873e2", size = 3610700, upload-time = "2025-09-22T04:02:24.465Z" }, + { url = "https://files.pythonhosted.org/packages/fe/0a/4643ccc6bb8b143e9f9640aa54e38255f9d3b45feb2cbe7ae2ca47e8782e/lxml-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:b30d46379644fbfc3ab81f8f82ae4de55179414651f110a1514f0b1f8f6cb2d7", size = 4010347, upload-time = "2025-09-22T04:02:26.286Z" }, + { url = "https://files.pythonhosted.org/packages/31/ef/dcf1d29c3f530577f61e5fe2f1bd72929acf779953668a8a47a479ae6f26/lxml-6.0.2-cp313-cp313-win_arm64.whl", hash = "sha256:13dcecc9946dca97b11b7c40d29fba63b55ab4170d3c0cf8c0c164343b9bfdcf", size = 3671248, upload-time = "2025-09-22T04:02:27.918Z" }, + { url = "https://files.pythonhosted.org/packages/03/15/d4a377b385ab693ce97b472fe0c77c2b16ec79590e688b3ccc71fba19884/lxml-6.0.2-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:b0c732aa23de8f8aec23f4b580d1e52905ef468afb4abeafd3fec77042abb6fe", size = 8659801, upload-time = "2025-09-22T04:02:30.113Z" }, + { url = "https://files.pythonhosted.org/packages/c8/e8/c128e37589463668794d503afaeb003987373c5f94d667124ffd8078bbd9/lxml-6.0.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4468e3b83e10e0317a89a33d28f7aeba1caa4d1a6fd457d115dd4ffe90c5931d", size = 4659403, upload-time = "2025-09-22T04:02:32.119Z" }, + { url = "https://files.pythonhosted.org/packages/00/ce/74903904339decdf7da7847bb5741fc98a5451b42fc419a86c0c13d26fe2/lxml-6.0.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:abd44571493973bad4598a3be7e1d807ed45aa2adaf7ab92ab7c62609569b17d", size = 4966974, upload-time = "2025-09-22T04:02:34.155Z" }, + { url = "https://files.pythonhosted.org/packages/1f/d3/131dec79ce61c5567fecf82515bd9bc36395df42501b50f7f7f3bd065df0/lxml-6.0.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:370cd78d5855cfbffd57c422851f7d3864e6ae72d0da615fca4dad8c45d375a5", size = 5102953, upload-time = "2025-09-22T04:02:36.054Z" }, + { url = "https://files.pythonhosted.org/packages/3a/ea/a43ba9bb750d4ffdd885f2cd333572f5bb900cd2408b67fdda07e85978a0/lxml-6.0.2-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:901e3b4219fa04ef766885fb40fa516a71662a4c61b80c94d25336b4934b71c0", size = 5055054, upload-time = "2025-09-22T04:02:38.154Z" }, + { url = "https://files.pythonhosted.org/packages/60/23/6885b451636ae286c34628f70a7ed1fcc759f8d9ad382d132e1c8d3d9bfd/lxml-6.0.2-cp314-cp314-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:a4bf42d2e4cf52c28cc1812d62426b9503cdb0c87a6de81442626aa7d69707ba", size = 5352421, upload-time = "2025-09-22T04:02:40.413Z" }, + { url = "https://files.pythonhosted.org/packages/48/5b/fc2ddfc94ddbe3eebb8e9af6e3fd65e2feba4967f6a4e9683875c394c2d8/lxml-6.0.2-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2c7fdaa4d7c3d886a42534adec7cfac73860b89b4e5298752f60aa5984641a0", size = 5673684, upload-time = "2025-09-22T04:02:42.288Z" }, + { url = "https://files.pythonhosted.org/packages/29/9c/47293c58cc91769130fbf85531280e8cc7868f7fbb6d92f4670071b9cb3e/lxml-6.0.2-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:98a5e1660dc7de2200b00d53fa00bcd3c35a3608c305d45a7bbcaf29fa16e83d", size = 5252463, upload-time = "2025-09-22T04:02:44.165Z" }, + { url = "https://files.pythonhosted.org/packages/9b/da/ba6eceb830c762b48e711ded880d7e3e89fc6c7323e587c36540b6b23c6b/lxml-6.0.2-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:dc051506c30b609238d79eda75ee9cab3e520570ec8219844a72a46020901e37", size = 4698437, upload-time = "2025-09-22T04:02:46.524Z" }, + { url = "https://files.pythonhosted.org/packages/a5/24/7be3f82cb7990b89118d944b619e53c656c97dc89c28cfb143fdb7cd6f4d/lxml-6.0.2-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8799481bbdd212470d17513a54d568f44416db01250f49449647b5ab5b5dccb9", size = 5269890, upload-time = "2025-09-22T04:02:48.812Z" }, + { url = "https://files.pythonhosted.org/packages/1b/bd/dcfb9ea1e16c665efd7538fc5d5c34071276ce9220e234217682e7d2c4a5/lxml-6.0.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9261bb77c2dab42f3ecd9103951aeca2c40277701eb7e912c545c1b16e0e4917", size = 5097185, upload-time = "2025-09-22T04:02:50.746Z" }, + { url = "https://files.pythonhosted.org/packages/21/04/a60b0ff9314736316f28316b694bccbbabe100f8483ad83852d77fc7468e/lxml-6.0.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:65ac4a01aba353cfa6d5725b95d7aed6356ddc0a3cd734de00124d285b04b64f", size = 4745895, upload-time = "2025-09-22T04:02:52.968Z" }, + { url = "https://files.pythonhosted.org/packages/d6/bd/7d54bd1846e5a310d9c715921c5faa71cf5c0853372adf78aee70c8d7aa2/lxml-6.0.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:b22a07cbb82fea98f8a2fd814f3d1811ff9ed76d0fc6abc84eb21527596e7cc8", size = 5695246, upload-time = "2025-09-22T04:02:54.798Z" }, + { url = "https://files.pythonhosted.org/packages/fd/32/5643d6ab947bc371da21323acb2a6e603cedbe71cb4c99c8254289ab6f4e/lxml-6.0.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:d759cdd7f3e055d6bc8d9bec3ad905227b2e4c785dc16c372eb5b5e83123f48a", size = 5260797, upload-time = "2025-09-22T04:02:57.058Z" }, + { url = "https://files.pythonhosted.org/packages/33/da/34c1ec4cff1eea7d0b4cd44af8411806ed943141804ac9c5d565302afb78/lxml-6.0.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:945da35a48d193d27c188037a05fec5492937f66fb1958c24fc761fb9d40d43c", size = 5277404, upload-time = "2025-09-22T04:02:58.966Z" }, + { url = "https://files.pythonhosted.org/packages/82/57/4eca3e31e54dc89e2c3507e1cd411074a17565fa5ffc437c4ae0a00d439e/lxml-6.0.2-cp314-cp314-win32.whl", hash = "sha256:be3aaa60da67e6153eb15715cc2e19091af5dc75faef8b8a585aea372507384b", size = 3670072, upload-time = "2025-09-22T04:03:38.05Z" }, + { url = "https://files.pythonhosted.org/packages/e3/e0/c96cf13eccd20c9421ba910304dae0f619724dcf1702864fd59dd386404d/lxml-6.0.2-cp314-cp314-win_amd64.whl", hash = "sha256:fa25afbadead523f7001caf0c2382afd272c315a033a7b06336da2637d92d6ed", size = 4080617, upload-time = "2025-09-22T04:03:39.835Z" }, + { url = "https://files.pythonhosted.org/packages/d5/5d/b3f03e22b3d38d6f188ef044900a9b29b2fe0aebb94625ce9fe244011d34/lxml-6.0.2-cp314-cp314-win_arm64.whl", hash = "sha256:063eccf89df5b24e361b123e257e437f9e9878f425ee9aae3144c77faf6da6d8", size = 3754930, upload-time = "2025-09-22T04:03:41.565Z" }, + { url = "https://files.pythonhosted.org/packages/5e/5c/42c2c4c03554580708fc738d13414801f340c04c3eff90d8d2d227145275/lxml-6.0.2-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:6162a86d86893d63084faaf4ff937b3daea233e3682fb4474db07395794fa80d", size = 8910380, upload-time = "2025-09-22T04:03:01.645Z" }, + { url = "https://files.pythonhosted.org/packages/bf/4f/12df843e3e10d18d468a7557058f8d3733e8b6e12401f30b1ef29360740f/lxml-6.0.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:414aaa94e974e23a3e92e7ca5b97d10c0cf37b6481f50911032c69eeb3991bba", size = 4775632, upload-time = "2025-09-22T04:03:03.814Z" }, + { url = "https://files.pythonhosted.org/packages/e4/0c/9dc31e6c2d0d418483cbcb469d1f5a582a1cd00a1f4081953d44051f3c50/lxml-6.0.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:48461bd21625458dd01e14e2c38dd0aea69addc3c4f960c30d9f59d7f93be601", size = 4975171, upload-time = "2025-09-22T04:03:05.651Z" }, + { url = "https://files.pythonhosted.org/packages/e7/2b/9b870c6ca24c841bdd887504808f0417aa9d8d564114689266f19ddf29c8/lxml-6.0.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:25fcc59afc57d527cfc78a58f40ab4c9b8fd096a9a3f964d2781ffb6eb33f4ed", size = 5110109, upload-time = "2025-09-22T04:03:07.452Z" }, + { url = "https://files.pythonhosted.org/packages/bf/0c/4f5f2a4dd319a178912751564471355d9019e220c20d7db3fb8307ed8582/lxml-6.0.2-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5179c60288204e6ddde3f774a93350177e08876eaf3ab78aa3a3649d43eb7d37", size = 5041061, upload-time = "2025-09-22T04:03:09.297Z" }, + { url = "https://files.pythonhosted.org/packages/12/64/554eed290365267671fe001a20d72d14f468ae4e6acef1e179b039436967/lxml-6.0.2-cp314-cp314t-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:967aab75434de148ec80597b75062d8123cadf2943fb4281f385141e18b21338", size = 5306233, upload-time = "2025-09-22T04:03:11.651Z" }, + { url = "https://files.pythonhosted.org/packages/7a/31/1d748aa275e71802ad9722df32a7a35034246b42c0ecdd8235412c3396ef/lxml-6.0.2-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d100fcc8930d697c6561156c6810ab4a508fb264c8b6779e6e61e2ed5e7558f9", size = 5604739, upload-time = "2025-09-22T04:03:13.592Z" }, + { url = "https://files.pythonhosted.org/packages/8f/41/2c11916bcac09ed561adccacceaedd2bf0e0b25b297ea92aab99fd03d0fa/lxml-6.0.2-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ca59e7e13e5981175b8b3e4ab84d7da57993eeff53c07764dcebda0d0e64ecd", size = 5225119, upload-time = "2025-09-22T04:03:15.408Z" }, + { url = "https://files.pythonhosted.org/packages/99/05/4e5c2873d8f17aa018e6afde417c80cc5d0c33be4854cce3ef5670c49367/lxml-6.0.2-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:957448ac63a42e2e49531b9d6c0fa449a1970dbc32467aaad46f11545be9af1d", size = 4633665, upload-time = "2025-09-22T04:03:17.262Z" }, + { url = "https://files.pythonhosted.org/packages/0f/c9/dcc2da1bebd6275cdc723b515f93edf548b82f36a5458cca3578bc899332/lxml-6.0.2-cp314-cp314t-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b7fc49c37f1786284b12af63152fe1d0990722497e2d5817acfe7a877522f9a9", size = 5234997, upload-time = "2025-09-22T04:03:19.14Z" }, + { url = "https://files.pythonhosted.org/packages/9c/e2/5172e4e7468afca64a37b81dba152fc5d90e30f9c83c7c3213d6a02a5ce4/lxml-6.0.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e19e0643cc936a22e837f79d01a550678da8377d7d801a14487c10c34ee49c7e", size = 5090957, upload-time = "2025-09-22T04:03:21.436Z" }, + { url = "https://files.pythonhosted.org/packages/a5/b3/15461fd3e5cd4ddcb7938b87fc20b14ab113b92312fc97afe65cd7c85de1/lxml-6.0.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:1db01e5cf14345628e0cbe71067204db658e2fb8e51e7f33631f5f4735fefd8d", size = 4764372, upload-time = "2025-09-22T04:03:23.27Z" }, + { url = "https://files.pythonhosted.org/packages/05/33/f310b987c8bf9e61c4dd8e8035c416bd3230098f5e3cfa69fc4232de7059/lxml-6.0.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:875c6b5ab39ad5291588aed6925fac99d0097af0dd62f33c7b43736043d4a2ec", size = 5634653, upload-time = "2025-09-22T04:03:25.767Z" }, + { url = "https://files.pythonhosted.org/packages/70/ff/51c80e75e0bc9382158133bdcf4e339b5886c6ee2418b5199b3f1a61ed6d/lxml-6.0.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:cdcbed9ad19da81c480dfd6dd161886db6096083c9938ead313d94b30aadf272", size = 5233795, upload-time = "2025-09-22T04:03:27.62Z" }, + { url = "https://files.pythonhosted.org/packages/56/4d/4856e897df0d588789dd844dbed9d91782c4ef0b327f96ce53c807e13128/lxml-6.0.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:80dadc234ebc532e09be1975ff538d154a7fa61ea5031c03d25178855544728f", size = 5257023, upload-time = "2025-09-22T04:03:30.056Z" }, + { url = "https://files.pythonhosted.org/packages/0f/85/86766dfebfa87bea0ab78e9ff7a4b4b45225df4b4d3b8cc3c03c5cd68464/lxml-6.0.2-cp314-cp314t-win32.whl", hash = "sha256:da08e7bb297b04e893d91087df19638dc7a6bb858a954b0cc2b9f5053c922312", size = 3911420, upload-time = "2025-09-22T04:03:32.198Z" }, + { url = "https://files.pythonhosted.org/packages/fe/1a/b248b355834c8e32614650b8008c69ffeb0ceb149c793961dd8c0b991bb3/lxml-6.0.2-cp314-cp314t-win_amd64.whl", hash = "sha256:252a22982dca42f6155125ac76d3432e548a7625d56f5a273ee78a5057216eca", size = 4406837, upload-time = "2025-09-22T04:03:34.027Z" }, + { url = "https://files.pythonhosted.org/packages/92/aa/df863bcc39c5e0946263454aba394de8a9084dbaff8ad143846b0d844739/lxml-6.0.2-cp314-cp314t-win_arm64.whl", hash = "sha256:bb4c1847b303835d89d785a18801a883436cdfd5dc3d62947f9c49e24f0f5a2c", size = 3822205, upload-time = "2025-09-22T04:03:36.249Z" }, + { url = "https://files.pythonhosted.org/packages/e7/9c/780c9a8fce3f04690b374f72f41306866b0400b9d0fdf3e17aaa37887eed/lxml-6.0.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e748d4cf8fef2526bb2a589a417eba0c8674e29ffcb570ce2ceca44f1e567bf6", size = 3939264, upload-time = "2025-09-22T04:04:32.892Z" }, + { url = "https://files.pythonhosted.org/packages/f5/5a/1ab260c00adf645d8bf7dec7f920f744b032f69130c681302821d5debea6/lxml-6.0.2-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4ddb1049fa0579d0cbd00503ad8c58b9ab34d1254c77bc6a5576d96ec7853dba", size = 4216435, upload-time = "2025-09-22T04:04:34.907Z" }, + { url = "https://files.pythonhosted.org/packages/f2/37/565f3b3d7ffede22874b6d86be1a1763d00f4ea9fc5b9b6ccb11e4ec8612/lxml-6.0.2-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cb233f9c95f83707dae461b12b720c1af9c28c2d19208e1be03387222151daf5", size = 4325913, upload-time = "2025-09-22T04:04:37.205Z" }, + { url = "https://files.pythonhosted.org/packages/22/ec/f3a1b169b2fb9d03467e2e3c0c752ea30e993be440a068b125fc7dd248b0/lxml-6.0.2-pp310-pypy310_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bc456d04db0515ce3320d714a1eac7a97774ff0849e7718b492d957da4631dd4", size = 4269357, upload-time = "2025-09-22T04:04:39.322Z" }, + { url = "https://files.pythonhosted.org/packages/77/a2/585a28fe3e67daa1cf2f06f34490d556d121c25d500b10082a7db96e3bcd/lxml-6.0.2-pp310-pypy310_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2613e67de13d619fd283d58bda40bff0ee07739f624ffee8b13b631abf33083d", size = 4412295, upload-time = "2025-09-22T04:04:41.647Z" }, + { url = "https://files.pythonhosted.org/packages/7b/d9/a57dd8bcebd7c69386c20263830d4fa72d27e6b72a229ef7a48e88952d9a/lxml-6.0.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:24a8e756c982c001ca8d59e87c80c4d9dcd4d9b44a4cbeb8d9be4482c514d41d", size = 3516913, upload-time = "2025-09-22T04:04:43.602Z" }, + { url = "https://files.pythonhosted.org/packages/0b/11/29d08bc103a62c0eba8016e7ed5aeebbf1e4312e83b0b1648dd203b0e87d/lxml-6.0.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1c06035eafa8404b5cf475bb37a9f6088b0aca288d4ccc9d69389750d5543700", size = 3949829, upload-time = "2025-09-22T04:04:45.608Z" }, + { url = "https://files.pythonhosted.org/packages/12/b3/52ab9a3b31e5ab8238da241baa19eec44d2ab426532441ee607165aebb52/lxml-6.0.2-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c7d13103045de1bdd6fe5d61802565f1a3537d70cd3abf596aa0af62761921ee", size = 4226277, upload-time = "2025-09-22T04:04:47.754Z" }, + { url = "https://files.pythonhosted.org/packages/a0/33/1eaf780c1baad88224611df13b1c2a9dfa460b526cacfe769103ff50d845/lxml-6.0.2-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0a3c150a95fbe5ac91de323aa756219ef9cf7fde5a3f00e2281e30f33fa5fa4f", size = 4330433, upload-time = "2025-09-22T04:04:49.907Z" }, + { url = "https://files.pythonhosted.org/packages/7a/c1/27428a2ff348e994ab4f8777d3a0ad510b6b92d37718e5887d2da99952a2/lxml-6.0.2-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:60fa43be34f78bebb27812ed90f1925ec99560b0fa1decdb7d12b84d857d31e9", size = 4272119, upload-time = "2025-09-22T04:04:51.801Z" }, + { url = "https://files.pythonhosted.org/packages/f0/d0/3020fa12bcec4ab62f97aab026d57c2f0cfd480a558758d9ca233bb6a79d/lxml-6.0.2-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:21c73b476d3cfe836be731225ec3421fa2f048d84f6df6a8e70433dff1376d5a", size = 4417314, upload-time = "2025-09-22T04:04:55.024Z" }, + { url = "https://files.pythonhosted.org/packages/6c/77/d7f491cbc05303ac6801651aabeb262d43f319288c1ea96c66b1d2692ff3/lxml-6.0.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:27220da5be049e936c3aca06f174e8827ca6445a4353a1995584311487fc4e3e", size = 3518768, upload-time = "2025-09-22T04:04:57.097Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/90/d08277ce111dd22f77149fd1a5d4653eeb3b3eaacbdfcbae5afb2600eebd/MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8", size = 14357, upload-time = "2024-10-18T15:20:51.44Z" }, + { url = "https://files.pythonhosted.org/packages/04/e1/6e2194baeae0bca1fae6629dc0cbbb968d4d941469cbab11a3872edff374/MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158", size = 12393, upload-time = "2024-10-18T15:20:52.426Z" }, + { url = "https://files.pythonhosted.org/packages/1d/69/35fa85a8ece0a437493dc61ce0bb6d459dcba482c34197e3efc829aa357f/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579", size = 21732, upload-time = "2024-10-18T15:20:53.578Z" }, + { url = "https://files.pythonhosted.org/packages/22/35/137da042dfb4720b638d2937c38a9c2df83fe32d20e8c8f3185dbfef05f7/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d", size = 20866, upload-time = "2024-10-18T15:20:55.06Z" }, + { url = "https://files.pythonhosted.org/packages/29/28/6d029a903727a1b62edb51863232152fd335d602def598dade38996887f0/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb", size = 20964, upload-time = "2024-10-18T15:20:55.906Z" }, + { url = "https://files.pythonhosted.org/packages/cc/cd/07438f95f83e8bc028279909d9c9bd39e24149b0d60053a97b2bc4f8aa51/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b", size = 21977, upload-time = "2024-10-18T15:20:57.189Z" }, + { url = "https://files.pythonhosted.org/packages/29/01/84b57395b4cc062f9c4c55ce0df7d3108ca32397299d9df00fedd9117d3d/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c", size = 21366, upload-time = "2024-10-18T15:20:58.235Z" }, + { url = "https://files.pythonhosted.org/packages/bd/6e/61ebf08d8940553afff20d1fb1ba7294b6f8d279df9fd0c0db911b4bbcfd/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171", size = 21091, upload-time = "2024-10-18T15:20:59.235Z" }, + { url = "https://files.pythonhosted.org/packages/11/23/ffbf53694e8c94ebd1e7e491de185124277964344733c45481f32ede2499/MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50", size = 15065, upload-time = "2024-10-18T15:21:00.307Z" }, + { url = "https://files.pythonhosted.org/packages/44/06/e7175d06dd6e9172d4a69a72592cb3f7a996a9c396eee29082826449bbc3/MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a", size = 15514, upload-time = "2024-10-18T15:21:01.122Z" }, + { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353, upload-time = "2024-10-18T15:21:02.187Z" }, + { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392, upload-time = "2024-10-18T15:21:02.941Z" }, + { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984, upload-time = "2024-10-18T15:21:03.953Z" }, + { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120, upload-time = "2024-10-18T15:21:06.495Z" }, + { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032, upload-time = "2024-10-18T15:21:07.295Z" }, + { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057, upload-time = "2024-10-18T15:21:08.073Z" }, + { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359, upload-time = "2024-10-18T15:21:09.318Z" }, + { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306, upload-time = "2024-10-18T15:21:10.185Z" }, + { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094, upload-time = "2024-10-18T15:21:11.005Z" }, + { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521, upload-time = "2024-10-18T15:21:12.911Z" }, + { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274, upload-time = "2024-10-18T15:21:13.777Z" }, + { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348, upload-time = "2024-10-18T15:21:14.822Z" }, + { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149, upload-time = "2024-10-18T15:21:15.642Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118, upload-time = "2024-10-18T15:21:17.133Z" }, + { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993, upload-time = "2024-10-18T15:21:18.064Z" }, + { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178, upload-time = "2024-10-18T15:21:18.859Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319, upload-time = "2024-10-18T15:21:19.671Z" }, + { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352, upload-time = "2024-10-18T15:21:20.971Z" }, + { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097, upload-time = "2024-10-18T15:21:22.646Z" }, + { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601, upload-time = "2024-10-18T15:21:23.499Z" }, + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" }, +] + +[[package]] +name = "matplotlib-inline" +version = "0.1.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/99/5b/a36a337438a14116b16480db471ad061c36c3694df7c2084a0da7ba538b7/matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90", size = 8159, upload-time = "2024-04-15T13:44:44.803Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca", size = 9899, upload-time = "2024-04-15T13:44:43.265Z" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + +[[package]] +name = "meshcat" +version = "0.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ipython", version = "8.37.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "ipython", version = "9.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "numpy" }, + { name = "pillow" }, + { name = "pyngrok" }, + { name = "pyzmq" }, + { name = "tornado" }, + { name = "u-msgpack-python" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/af/c2/fae203e8e2119d4c793c9d7d2b8d1fea10752bb39b7b330beb8e67b6117c/meshcat-0.3.2.tar.gz", hash = "sha256:2cfe17cde4fe85d072a3de018a9dabf5626f80c255fff2ce63b26a48d2484ad9", size = 611361, upload-time = "2021-11-07T20:31:55.305Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/c3/5f2b53fd7d43abb8c67015c737569303f39f43aa2b5a4c0add860be5d7e8/meshcat-0.3.2-py3-none-any.whl", hash = "sha256:cba0465d0e29658c48bb65d27f5c8e04ff0a1ac8ce916d65621146c06258a1c7", size = 2640524, upload-time = "2021-11-07T20:31:53.033Z" }, +] + +[[package]] +name = "mpmath" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106, upload-time = "2023-03-07T16:47:11.061Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" }, +] + +[[package]] +name = "mujoco" +version = "3.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "absl-py" }, + { name = "etils", extra = ["epath"] }, + { name = "glfw" }, + { name = "numpy" }, + { name = "pyopengl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8a/c6/76716fd205ccd0afbaef8b355555ace70347bd02c921d14e05e68eab3dbb/mujoco-3.3.0.tar.gz", hash = "sha256:0fb12ab715bc2b6d2394a6330b8c3a1dd95b0f3ba812bfe4f6c91e0a1ca3ad0f", size = 801447, upload-time = "2025-02-27T19:16:18.748Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/f4/21ff1dc849ff6ab8f2fc29d6d6286ab5e03c25ac7cced3bcbbe052c257be/mujoco-3.3.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:676285e5a752e5b937fc79ff56cad6d62021992e6800d8085ab316c67d7b0ee4", size = 6484740, upload-time = "2025-02-27T19:14:36.642Z" }, + { url = "https://files.pythonhosted.org/packages/ca/3b/22acd7de2cf66839a3e49b57cc428ab6816764f128209a80578403eac83d/mujoco-3.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a6991fe6b255eabf5c62c8a3dffa2e04a51bd74d04de42869c322da1c4279562", size = 6512230, upload-time = "2025-02-27T19:14:40.481Z" }, + { url = "https://files.pythonhosted.org/packages/38/5b/b257c5a6435258a4ce908befa7131b94991ecf95639a58e14daf616d5edd/mujoco-3.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1dd0db1edaabf4a1beccdacfe9c2c313eb47b25bc4016d9a2f97771d0e9dfa89", size = 6261748, upload-time = "2025-02-27T19:14:45.42Z" }, + { url = "https://files.pythonhosted.org/packages/71/75/f9c7fcf11f878213c2bd5af69dd6d73914efe3a60ac031c6e3e305a392db/mujoco-3.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:946ee6c90c4f6c4ef26502cc8a469b3dc7bd7286d8efb3dc935fa0bf8a3a11e5", size = 6549401, upload-time = "2025-02-27T19:14:49.209Z" }, + { url = "https://files.pythonhosted.org/packages/f4/b3/aec72a24b473394c4f6ed1cc9734f11721dd8eda354c85a768e3e9bf1019/mujoco-3.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:d84988f3c7e2dc2238a84c565f8c30b7e66caddd9d95b4f0d291902b6af749f3", size = 4835623, upload-time = "2025-02-27T19:14:52.732Z" }, + { url = "https://files.pythonhosted.org/packages/11/aa/a0b20e86e9944fdbdbea004974df1426f4bc532c9711112012e89a56a389/mujoco-3.3.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:d61157a53356c790ef73d4ec918288c2dd7d0da26eaa1de8f56cffbdcd1ca275", size = 6498398, upload-time = "2025-02-27T19:14:59.241Z" }, + { url = "https://files.pythonhosted.org/packages/b5/12/5a158a125114e35409e60dedeb694bf80d7e3a02c99edc8734ed352baff2/mujoco-3.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:60025a554e444a85d3183714115f48ea4582c5c654f120c5f7652e22260b0069", size = 6524031, upload-time = "2025-02-27T19:15:04.301Z" }, + { url = "https://files.pythonhosted.org/packages/81/85/f7dbcc704710e304cd1220fcdb505e5f0d142c741850efaf33950ab5bc51/mujoco-3.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff984aaec2006effe12fbb8bc29fbc0abb82b4332891307d99e637791067e912", size = 6276343, upload-time = "2025-02-27T19:15:08.446Z" }, + { url = "https://files.pythonhosted.org/packages/05/9e/a0cd11d0501421d547d74ad0de092912c2e4952b9663f7807e32a9cbcf1b/mujoco-3.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1403630afb922ab2cb7aa7310b5cb596a5568ee4250f96e663c9f365353cab00", size = 6565173, upload-time = "2025-02-27T19:15:14.112Z" }, + { url = "https://files.pythonhosted.org/packages/03/56/f5d8e2ac2df979044cb18c8862de7be9c2247921f8f7b06d45d3daf99c15/mujoco-3.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:436d863747fea327393a46f04d7aeaea6acf27b961b54dbdde32a823e90355eb", size = 4860052, upload-time = "2025-02-27T19:15:18.631Z" }, + { url = "https://files.pythonhosted.org/packages/11/6f/218bf9c8480340d640d4f8e09c0295bfd02cc08610bd6798fad12ebc9cc0/mujoco-3.3.0-cp312-cp312-macosx_10_16_x86_64.whl", hash = "sha256:32a7671069799e69f51bf515f6f97d8b2c852a0de065129033347e616fe4e9ed", size = 6588848, upload-time = "2025-02-27T19:15:22.124Z" }, + { url = "https://files.pythonhosted.org/packages/42/3b/f17690dee3f3f3771e9953ffe634d0a1649af44c609c8b201325b9a7fbe6/mujoco-3.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f435f34ea8c41711e68c635ada89c7347e9d53a330c2ff748723a96495d8710", size = 6493393, upload-time = "2025-02-27T19:15:26.398Z" }, + { url = "https://files.pythonhosted.org/packages/db/d5/89feffffdc085a6e3a31eea46a86efdd5a4d3421dacfba742f75e3df3319/mujoco-3.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f526587b05879842e7380feda0a207455e78cdc05c8672c77d292383c145ab7e", size = 6283111, upload-time = "2025-02-27T19:15:30.509Z" }, + { url = "https://files.pythonhosted.org/packages/cc/7b/dfedcfd4a8db4d861747e34e47d96dbcbb994db47b6bd626ed3f25935b81/mujoco-3.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:607df8af9e01831d624fc5ffcb6376899fb47c1e999a0e38b8388622da712f64", size = 6639670, upload-time = "2025-02-27T19:15:33.889Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e2/7865204cbe00a2c319bf39a918c975a2cfb931ff2715348a815bdb7b453e/mujoco-3.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:6e8811d854a5f4b3340ba11220781427659d5a87ced2d60688e476db8f3f6188", size = 4905647, upload-time = "2025-02-27T19:15:37.421Z" }, + { url = "https://files.pythonhosted.org/packages/26/bc/0ee22fa1451b7e5b6194973c2427e58ddb347b52519ac1ecd75d7040859f/mujoco-3.3.0-cp313-cp313-macosx_10_16_x86_64.whl", hash = "sha256:f4ffdc689319a8d5e692ef19634893d29e2b88fc76b43493a958463047cd9a05", size = 6589087, upload-time = "2025-02-27T19:15:40.84Z" }, + { url = "https://files.pythonhosted.org/packages/d5/14/7c1bd9cfabf757bcb76dcec2b3ab556243450e587aeb639574653305a4e9/mujoco-3.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a748b12f2f39e381770bbf2674ddc4d3c63c74c3c0020efbc98afe712c8e358e", size = 6493094, upload-time = "2025-02-27T19:15:43.439Z" }, + { url = "https://files.pythonhosted.org/packages/98/f0/38d6a12b44d4636afc189c1eb4c95c49bfe4184801dcc3a54ef15382abd5/mujoco-3.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9de3d8d4c17b7831c4e1d760f9b19d4cf4c072014733489ed0a0eed32aad4348", size = 6283044, upload-time = "2025-02-27T19:15:47.815Z" }, + { url = "https://files.pythonhosted.org/packages/57/92/7753403daa094fefcb8ce586a42170d79cd944c7eb466eb99d9220dec124/mujoco-3.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7955b8b0b9803fb41a1a800eb305fd26612cba3d5fa2beda2f886d9816283894", size = 6640054, upload-time = "2025-02-27T19:15:51.942Z" }, + { url = "https://files.pythonhosted.org/packages/bc/9f/48c8dad4b4edae9188e6bf5afdd614c73eb20f65bc9206be01c982564349/mujoco-3.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:7a0133c7a843e3ad9c48f436b09cd87df934421fbc751283f51eec79647d31e6", size = 4906759, upload-time = "2025-02-27T19:15:56.42Z" }, +] + +[[package]] +name = "multidict" +version = "6.6.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/69/7f/0652e6ed47ab288e3756ea9c0df8b14950781184d4bd7883f4d87dd41245/multidict-6.6.4.tar.gz", hash = "sha256:d2d4e4787672911b48350df02ed3fa3fffdc2f2e8ca06dd6afdf34189b76a9dd", size = 101843, upload-time = "2025-08-11T12:08:48.217Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/6b/86f353088c1358e76fd30b0146947fddecee812703b604ee901e85cd2a80/multidict-6.6.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b8aa6f0bd8125ddd04a6593437bad6a7e70f300ff4180a531654aa2ab3f6d58f", size = 77054, upload-time = "2025-08-11T12:06:02.99Z" }, + { url = "https://files.pythonhosted.org/packages/19/5d/c01dc3d3788bb877bd7f5753ea6eb23c1beeca8044902a8f5bfb54430f63/multidict-6.6.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b9e5853bbd7264baca42ffc53391b490d65fe62849bf2c690fa3f6273dbcd0cb", size = 44914, upload-time = "2025-08-11T12:06:05.264Z" }, + { url = "https://files.pythonhosted.org/packages/46/44/964dae19ea42f7d3e166474d8205f14bb811020e28bc423d46123ddda763/multidict-6.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0af5f9dee472371e36d6ae38bde009bd8ce65ac7335f55dcc240379d7bed1495", size = 44601, upload-time = "2025-08-11T12:06:06.627Z" }, + { url = "https://files.pythonhosted.org/packages/31/20/0616348a1dfb36cb2ab33fc9521de1f27235a397bf3f59338e583afadd17/multidict-6.6.4-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:d24f351e4d759f5054b641c81e8291e5d122af0fca5c72454ff77f7cbe492de8", size = 224821, upload-time = "2025-08-11T12:06:08.06Z" }, + { url = "https://files.pythonhosted.org/packages/14/26/5d8923c69c110ff51861af05bd27ca6783011b96725d59ccae6d9daeb627/multidict-6.6.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:db6a3810eec08280a172a6cd541ff4a5f6a97b161d93ec94e6c4018917deb6b7", size = 242608, upload-time = "2025-08-11T12:06:09.697Z" }, + { url = "https://files.pythonhosted.org/packages/5c/cc/e2ad3ba9459aa34fa65cf1f82a5c4a820a2ce615aacfb5143b8817f76504/multidict-6.6.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a1b20a9d56b2d81e2ff52ecc0670d583eaabaa55f402e8d16dd062373dbbe796", size = 222324, upload-time = "2025-08-11T12:06:10.905Z" }, + { url = "https://files.pythonhosted.org/packages/19/db/4ed0f65701afbc2cb0c140d2d02928bb0fe38dd044af76e58ad7c54fd21f/multidict-6.6.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8c9854df0eaa610a23494c32a6f44a3a550fb398b6b51a56e8c6b9b3689578db", size = 253234, upload-time = "2025-08-11T12:06:12.658Z" }, + { url = "https://files.pythonhosted.org/packages/94/c1/5160c9813269e39ae14b73debb907bfaaa1beee1762da8c4fb95df4764ed/multidict-6.6.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4bb7627fd7a968f41905a4d6343b0d63244a0623f006e9ed989fa2b78f4438a0", size = 251613, upload-time = "2025-08-11T12:06:13.97Z" }, + { url = "https://files.pythonhosted.org/packages/05/a9/48d1bd111fc2f8fb98b2ed7f9a115c55a9355358432a19f53c0b74d8425d/multidict-6.6.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:caebafea30ed049c57c673d0b36238b1748683be2593965614d7b0e99125c877", size = 241649, upload-time = "2025-08-11T12:06:15.204Z" }, + { url = "https://files.pythonhosted.org/packages/85/2a/f7d743df0019408768af8a70d2037546a2be7b81fbb65f040d76caafd4c5/multidict-6.6.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ad887a8250eb47d3ab083d2f98db7f48098d13d42eb7a3b67d8a5c795f224ace", size = 239238, upload-time = "2025-08-11T12:06:16.467Z" }, + { url = "https://files.pythonhosted.org/packages/cb/b8/4f4bb13323c2d647323f7919201493cf48ebe7ded971717bfb0f1a79b6bf/multidict-6.6.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:ed8358ae7d94ffb7c397cecb62cbac9578a83ecefc1eba27b9090ee910e2efb6", size = 233517, upload-time = "2025-08-11T12:06:18.107Z" }, + { url = "https://files.pythonhosted.org/packages/33/29/4293c26029ebfbba4f574febd2ed01b6f619cfa0d2e344217d53eef34192/multidict-6.6.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ecab51ad2462197a4c000b6d5701fc8585b80eecb90583635d7e327b7b6923eb", size = 243122, upload-time = "2025-08-11T12:06:19.361Z" }, + { url = "https://files.pythonhosted.org/packages/20/60/a1c53628168aa22447bfde3a8730096ac28086704a0d8c590f3b63388d0c/multidict-6.6.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:c5c97aa666cf70e667dfa5af945424ba1329af5dd988a437efeb3a09430389fb", size = 248992, upload-time = "2025-08-11T12:06:20.661Z" }, + { url = "https://files.pythonhosted.org/packages/a3/3b/55443a0c372f33cae5d9ec37a6a973802884fa0ab3586659b197cf8cc5e9/multidict-6.6.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:9a950b7cf54099c1209f455ac5970b1ea81410f2af60ed9eb3c3f14f0bfcf987", size = 243708, upload-time = "2025-08-11T12:06:21.891Z" }, + { url = "https://files.pythonhosted.org/packages/7c/60/a18c6900086769312560b2626b18e8cca22d9e85b1186ba77f4755b11266/multidict-6.6.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:163c7ea522ea9365a8a57832dea7618e6cbdc3cd75f8c627663587459a4e328f", size = 237498, upload-time = "2025-08-11T12:06:23.206Z" }, + { url = "https://files.pythonhosted.org/packages/11/3d/8bdd8bcaff2951ce2affccca107a404925a2beafedd5aef0b5e4a71120a6/multidict-6.6.4-cp310-cp310-win32.whl", hash = "sha256:17d2cbbfa6ff20821396b25890f155f40c986f9cfbce5667759696d83504954f", size = 41415, upload-time = "2025-08-11T12:06:24.77Z" }, + { url = "https://files.pythonhosted.org/packages/c0/53/cab1ad80356a4cd1b685a254b680167059b433b573e53872fab245e9fc95/multidict-6.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:ce9a40fbe52e57e7edf20113a4eaddfacac0561a0879734e636aa6d4bb5e3fb0", size = 46046, upload-time = "2025-08-11T12:06:25.893Z" }, + { url = "https://files.pythonhosted.org/packages/cf/9a/874212b6f5c1c2d870d0a7adc5bb4cfe9b0624fa15cdf5cf757c0f5087ae/multidict-6.6.4-cp310-cp310-win_arm64.whl", hash = "sha256:01d0959807a451fe9fdd4da3e139cb5b77f7328baf2140feeaf233e1d777b729", size = 43147, upload-time = "2025-08-11T12:06:27.534Z" }, + { url = "https://files.pythonhosted.org/packages/6b/7f/90a7f01e2d005d6653c689039977f6856718c75c5579445effb7e60923d1/multidict-6.6.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c7a0e9b561e6460484318a7612e725df1145d46b0ef57c6b9866441bf6e27e0c", size = 76472, upload-time = "2025-08-11T12:06:29.006Z" }, + { url = "https://files.pythonhosted.org/packages/54/a3/bed07bc9e2bb302ce752f1dabc69e884cd6a676da44fb0e501b246031fdd/multidict-6.6.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6bf2f10f70acc7a2446965ffbc726e5fc0b272c97a90b485857e5c70022213eb", size = 44634, upload-time = "2025-08-11T12:06:30.374Z" }, + { url = "https://files.pythonhosted.org/packages/a7/4b/ceeb4f8f33cf81277da464307afeaf164fb0297947642585884f5cad4f28/multidict-6.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:66247d72ed62d5dd29752ffc1d3b88f135c6a8de8b5f63b7c14e973ef5bda19e", size = 44282, upload-time = "2025-08-11T12:06:31.958Z" }, + { url = "https://files.pythonhosted.org/packages/03/35/436a5da8702b06866189b69f655ffdb8f70796252a8772a77815f1812679/multidict-6.6.4-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:105245cc6b76f51e408451a844a54e6823bbd5a490ebfe5bdfc79798511ceded", size = 229696, upload-time = "2025-08-11T12:06:33.087Z" }, + { url = "https://files.pythonhosted.org/packages/b6/0e/915160be8fecf1fca35f790c08fb74ca684d752fcba62c11daaf3d92c216/multidict-6.6.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cbbc54e58b34c3bae389ef00046be0961f30fef7cb0dd9c7756aee376a4f7683", size = 246665, upload-time = "2025-08-11T12:06:34.448Z" }, + { url = "https://files.pythonhosted.org/packages/08/ee/2f464330acd83f77dcc346f0b1a0eaae10230291450887f96b204b8ac4d3/multidict-6.6.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:56c6b3652f945c9bc3ac6c8178cd93132b8d82dd581fcbc3a00676c51302bc1a", size = 225485, upload-time = "2025-08-11T12:06:35.672Z" }, + { url = "https://files.pythonhosted.org/packages/71/cc/9a117f828b4d7fbaec6adeed2204f211e9caf0a012692a1ee32169f846ae/multidict-6.6.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b95494daf857602eccf4c18ca33337dd2be705bccdb6dddbfc9d513e6addb9d9", size = 257318, upload-time = "2025-08-11T12:06:36.98Z" }, + { url = "https://files.pythonhosted.org/packages/25/77/62752d3dbd70e27fdd68e86626c1ae6bccfebe2bb1f84ae226363e112f5a/multidict-6.6.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e5b1413361cef15340ab9dc61523e653d25723e82d488ef7d60a12878227ed50", size = 254689, upload-time = "2025-08-11T12:06:38.233Z" }, + { url = "https://files.pythonhosted.org/packages/00/6e/fac58b1072a6fc59af5e7acb245e8754d3e1f97f4f808a6559951f72a0d4/multidict-6.6.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e167bf899c3d724f9662ef00b4f7fef87a19c22b2fead198a6f68b263618df52", size = 246709, upload-time = "2025-08-11T12:06:39.517Z" }, + { url = "https://files.pythonhosted.org/packages/01/ef/4698d6842ef5e797c6db7744b0081e36fb5de3d00002cc4c58071097fac3/multidict-6.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:aaea28ba20a9026dfa77f4b80369e51cb767c61e33a2d4043399c67bd95fb7c6", size = 243185, upload-time = "2025-08-11T12:06:40.796Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c9/d82e95ae1d6e4ef396934e9b0e942dfc428775f9554acf04393cce66b157/multidict-6.6.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:8c91cdb30809a96d9ecf442ec9bc45e8cfaa0f7f8bdf534e082c2443a196727e", size = 237838, upload-time = "2025-08-11T12:06:42.595Z" }, + { url = "https://files.pythonhosted.org/packages/57/cf/f94af5c36baaa75d44fab9f02e2a6bcfa0cd90acb44d4976a80960759dbc/multidict-6.6.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1a0ccbfe93ca114c5d65a2471d52d8829e56d467c97b0e341cf5ee45410033b3", size = 246368, upload-time = "2025-08-11T12:06:44.304Z" }, + { url = "https://files.pythonhosted.org/packages/4a/fe/29f23460c3d995f6a4b678cb2e9730e7277231b981f0b234702f0177818a/multidict-6.6.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:55624b3f321d84c403cb7d8e6e982f41ae233d85f85db54ba6286f7295dc8a9c", size = 253339, upload-time = "2025-08-11T12:06:45.597Z" }, + { url = "https://files.pythonhosted.org/packages/29/b6/fd59449204426187b82bf8a75f629310f68c6adc9559dc922d5abe34797b/multidict-6.6.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:4a1fb393a2c9d202cb766c76208bd7945bc194eba8ac920ce98c6e458f0b524b", size = 246933, upload-time = "2025-08-11T12:06:46.841Z" }, + { url = "https://files.pythonhosted.org/packages/19/52/d5d6b344f176a5ac3606f7a61fb44dc746e04550e1a13834dff722b8d7d6/multidict-6.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:43868297a5759a845fa3a483fb4392973a95fb1de891605a3728130c52b8f40f", size = 242225, upload-time = "2025-08-11T12:06:48.588Z" }, + { url = "https://files.pythonhosted.org/packages/ec/d3/5b2281ed89ff4d5318d82478a2a2450fcdfc3300da48ff15c1778280ad26/multidict-6.6.4-cp311-cp311-win32.whl", hash = "sha256:ed3b94c5e362a8a84d69642dbeac615452e8af9b8eb825b7bc9f31a53a1051e2", size = 41306, upload-time = "2025-08-11T12:06:49.95Z" }, + { url = "https://files.pythonhosted.org/packages/74/7d/36b045c23a1ab98507aefd44fd8b264ee1dd5e5010543c6fccf82141ccef/multidict-6.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:d8c112f7a90d8ca5d20213aa41eac690bb50a76da153e3afb3886418e61cb22e", size = 46029, upload-time = "2025-08-11T12:06:51.082Z" }, + { url = "https://files.pythonhosted.org/packages/0f/5e/553d67d24432c5cd52b49047f2d248821843743ee6d29a704594f656d182/multidict-6.6.4-cp311-cp311-win_arm64.whl", hash = "sha256:3bb0eae408fa1996d87247ca0d6a57b7fc1dcf83e8a5c47ab82c558c250d4adf", size = 43017, upload-time = "2025-08-11T12:06:52.243Z" }, + { url = "https://files.pythonhosted.org/packages/05/f6/512ffd8fd8b37fb2680e5ac35d788f1d71bbaf37789d21a820bdc441e565/multidict-6.6.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0ffb87be160942d56d7b87b0fdf098e81ed565add09eaa1294268c7f3caac4c8", size = 76516, upload-time = "2025-08-11T12:06:53.393Z" }, + { url = "https://files.pythonhosted.org/packages/99/58/45c3e75deb8855c36bd66cc1658007589662ba584dbf423d01df478dd1c5/multidict-6.6.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d191de6cbab2aff5de6c5723101705fd044b3e4c7cfd587a1929b5028b9714b3", size = 45394, upload-time = "2025-08-11T12:06:54.555Z" }, + { url = "https://files.pythonhosted.org/packages/fd/ca/e8c4472a93a26e4507c0b8e1f0762c0d8a32de1328ef72fd704ef9cc5447/multidict-6.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:38a0956dd92d918ad5feff3db8fcb4a5eb7dba114da917e1a88475619781b57b", size = 43591, upload-time = "2025-08-11T12:06:55.672Z" }, + { url = "https://files.pythonhosted.org/packages/05/51/edf414f4df058574a7265034d04c935aa84a89e79ce90fcf4df211f47b16/multidict-6.6.4-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:6865f6d3b7900ae020b495d599fcf3765653bc927951c1abb959017f81ae8287", size = 237215, upload-time = "2025-08-11T12:06:57.213Z" }, + { url = "https://files.pythonhosted.org/packages/c8/45/8b3d6dbad8cf3252553cc41abea09ad527b33ce47a5e199072620b296902/multidict-6.6.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a2088c126b6f72db6c9212ad827d0ba088c01d951cee25e758c450da732c138", size = 258299, upload-time = "2025-08-11T12:06:58.946Z" }, + { url = "https://files.pythonhosted.org/packages/3c/e8/8ca2e9a9f5a435fc6db40438a55730a4bf4956b554e487fa1b9ae920f825/multidict-6.6.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0f37bed7319b848097085d7d48116f545985db988e2256b2e6f00563a3416ee6", size = 242357, upload-time = "2025-08-11T12:07:00.301Z" }, + { url = "https://files.pythonhosted.org/packages/0f/84/80c77c99df05a75c28490b2af8f7cba2a12621186e0a8b0865d8e745c104/multidict-6.6.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:01368e3c94032ba6ca0b78e7ccb099643466cf24f8dc8eefcfdc0571d56e58f9", size = 268369, upload-time = "2025-08-11T12:07:01.638Z" }, + { url = "https://files.pythonhosted.org/packages/0d/e9/920bfa46c27b05fb3e1ad85121fd49f441492dca2449c5bcfe42e4565d8a/multidict-6.6.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8fe323540c255db0bffee79ad7f048c909f2ab0edb87a597e1c17da6a54e493c", size = 269341, upload-time = "2025-08-11T12:07:02.943Z" }, + { url = "https://files.pythonhosted.org/packages/af/65/753a2d8b05daf496f4a9c367fe844e90a1b2cac78e2be2c844200d10cc4c/multidict-6.6.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8eb3025f17b0a4c3cd08cda49acf312a19ad6e8a4edd9dbd591e6506d999402", size = 256100, upload-time = "2025-08-11T12:07:04.564Z" }, + { url = "https://files.pythonhosted.org/packages/09/54/655be13ae324212bf0bc15d665a4e34844f34c206f78801be42f7a0a8aaa/multidict-6.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bbc14f0365534d35a06970d6a83478b249752e922d662dc24d489af1aa0d1be7", size = 253584, upload-time = "2025-08-11T12:07:05.914Z" }, + { url = "https://files.pythonhosted.org/packages/5c/74/ab2039ecc05264b5cec73eb018ce417af3ebb384ae9c0e9ed42cb33f8151/multidict-6.6.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:75aa52fba2d96bf972e85451b99d8e19cc37ce26fd016f6d4aa60da9ab2b005f", size = 251018, upload-time = "2025-08-11T12:07:08.301Z" }, + { url = "https://files.pythonhosted.org/packages/af/0a/ccbb244ac848e56c6427f2392741c06302bbfba49c0042f1eb3c5b606497/multidict-6.6.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4fefd4a815e362d4f011919d97d7b4a1e566f1dde83dc4ad8cfb5b41de1df68d", size = 251477, upload-time = "2025-08-11T12:07:10.248Z" }, + { url = "https://files.pythonhosted.org/packages/0e/b0/0ed49bba775b135937f52fe13922bc64a7eaf0a3ead84a36e8e4e446e096/multidict-6.6.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:db9801fe021f59a5b375ab778973127ca0ac52429a26e2fd86aa9508f4d26eb7", size = 263575, upload-time = "2025-08-11T12:07:11.928Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d9/7fb85a85e14de2e44dfb6a24f03c41e2af8697a6df83daddb0e9b7569f73/multidict-6.6.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a650629970fa21ac1fb06ba25dabfc5b8a2054fcbf6ae97c758aa956b8dba802", size = 259649, upload-time = "2025-08-11T12:07:13.244Z" }, + { url = "https://files.pythonhosted.org/packages/03/9e/b3a459bcf9b6e74fa461a5222a10ff9b544cb1cd52fd482fb1b75ecda2a2/multidict-6.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:452ff5da78d4720d7516a3a2abd804957532dd69296cb77319c193e3ffb87e24", size = 251505, upload-time = "2025-08-11T12:07:14.57Z" }, + { url = "https://files.pythonhosted.org/packages/86/a2/8022f78f041dfe6d71e364001a5cf987c30edfc83c8a5fb7a3f0974cff39/multidict-6.6.4-cp312-cp312-win32.whl", hash = "sha256:8c2fcb12136530ed19572bbba61b407f655e3953ba669b96a35036a11a485793", size = 41888, upload-time = "2025-08-11T12:07:15.904Z" }, + { url = "https://files.pythonhosted.org/packages/c7/eb/d88b1780d43a56db2cba24289fa744a9d216c1a8546a0dc3956563fd53ea/multidict-6.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:047d9425860a8c9544fed1b9584f0c8bcd31bcde9568b047c5e567a1025ecd6e", size = 46072, upload-time = "2025-08-11T12:07:17.045Z" }, + { url = "https://files.pythonhosted.org/packages/9f/16/b929320bf5750e2d9d4931835a4c638a19d2494a5b519caaaa7492ebe105/multidict-6.6.4-cp312-cp312-win_arm64.whl", hash = "sha256:14754eb72feaa1e8ae528468f24250dd997b8e2188c3d2f593f9eba259e4b364", size = 43222, upload-time = "2025-08-11T12:07:18.328Z" }, + { url = "https://files.pythonhosted.org/packages/3a/5d/e1db626f64f60008320aab00fbe4f23fc3300d75892a3381275b3d284580/multidict-6.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f46a6e8597f9bd71b31cc708195d42b634c8527fecbcf93febf1052cacc1f16e", size = 75848, upload-time = "2025-08-11T12:07:19.912Z" }, + { url = "https://files.pythonhosted.org/packages/4c/aa/8b6f548d839b6c13887253af4e29c939af22a18591bfb5d0ee6f1931dae8/multidict-6.6.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:22e38b2bc176c5eb9c0a0e379f9d188ae4cd8b28c0f53b52bce7ab0a9e534657", size = 45060, upload-time = "2025-08-11T12:07:21.163Z" }, + { url = "https://files.pythonhosted.org/packages/eb/c6/f5e97e5d99a729bc2aa58eb3ebfa9f1e56a9b517cc38c60537c81834a73f/multidict-6.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5df8afd26f162da59e218ac0eefaa01b01b2e6cd606cffa46608f699539246da", size = 43269, upload-time = "2025-08-11T12:07:22.392Z" }, + { url = "https://files.pythonhosted.org/packages/dc/31/d54eb0c62516776f36fe67f84a732f97e0b0e12f98d5685bebcc6d396910/multidict-6.6.4-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:49517449b58d043023720aa58e62b2f74ce9b28f740a0b5d33971149553d72aa", size = 237158, upload-time = "2025-08-11T12:07:23.636Z" }, + { url = "https://files.pythonhosted.org/packages/c4/1c/8a10c1c25b23156e63b12165a929d8eb49a6ed769fdbefb06e6f07c1e50d/multidict-6.6.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae9408439537c5afdca05edd128a63f56a62680f4b3c234301055d7a2000220f", size = 257076, upload-time = "2025-08-11T12:07:25.049Z" }, + { url = "https://files.pythonhosted.org/packages/ad/86/90e20b5771d6805a119e483fd3d1e8393e745a11511aebca41f0da38c3e2/multidict-6.6.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:87a32d20759dc52a9e850fe1061b6e41ab28e2998d44168a8a341b99ded1dba0", size = 240694, upload-time = "2025-08-11T12:07:26.458Z" }, + { url = "https://files.pythonhosted.org/packages/e7/49/484d3e6b535bc0555b52a0a26ba86e4d8d03fd5587d4936dc59ba7583221/multidict-6.6.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:52e3c8d43cdfff587ceedce9deb25e6ae77daba560b626e97a56ddcad3756879", size = 266350, upload-time = "2025-08-11T12:07:27.94Z" }, + { url = "https://files.pythonhosted.org/packages/bf/b4/aa4c5c379b11895083d50021e229e90c408d7d875471cb3abf721e4670d6/multidict-6.6.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ad8850921d3a8d8ff6fbef790e773cecfc260bbfa0566998980d3fa8f520bc4a", size = 267250, upload-time = "2025-08-11T12:07:29.303Z" }, + { url = "https://files.pythonhosted.org/packages/80/e5/5e22c5bf96a64bdd43518b1834c6d95a4922cc2066b7d8e467dae9b6cee6/multidict-6.6.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:497a2954adc25c08daff36f795077f63ad33e13f19bfff7736e72c785391534f", size = 254900, upload-time = "2025-08-11T12:07:30.764Z" }, + { url = "https://files.pythonhosted.org/packages/17/38/58b27fed927c07035abc02befacab42491e7388ca105e087e6e0215ead64/multidict-6.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:024ce601f92d780ca1617ad4be5ac15b501cc2414970ffa2bb2bbc2bd5a68fa5", size = 252355, upload-time = "2025-08-11T12:07:32.205Z" }, + { url = "https://files.pythonhosted.org/packages/d0/a1/dad75d23a90c29c02b5d6f3d7c10ab36c3197613be5d07ec49c7791e186c/multidict-6.6.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a693fc5ed9bdd1c9e898013e0da4dcc640de7963a371c0bd458e50e046bf6438", size = 250061, upload-time = "2025-08-11T12:07:33.623Z" }, + { url = "https://files.pythonhosted.org/packages/b8/1a/ac2216b61c7f116edab6dc3378cca6c70dc019c9a457ff0d754067c58b20/multidict-6.6.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:190766dac95aab54cae5b152a56520fd99298f32a1266d66d27fdd1b5ac00f4e", size = 249675, upload-time = "2025-08-11T12:07:34.958Z" }, + { url = "https://files.pythonhosted.org/packages/d4/79/1916af833b800d13883e452e8e0977c065c4ee3ab7a26941fbfdebc11895/multidict-6.6.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:34d8f2a5ffdceab9dcd97c7a016deb2308531d5f0fced2bb0c9e1df45b3363d7", size = 261247, upload-time = "2025-08-11T12:07:36.588Z" }, + { url = "https://files.pythonhosted.org/packages/c5/65/d1f84fe08ac44a5fc7391cbc20a7cedc433ea616b266284413fd86062f8c/multidict-6.6.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:59e8d40ab1f5a8597abcef00d04845155a5693b5da00d2c93dbe88f2050f2812", size = 257960, upload-time = "2025-08-11T12:07:39.735Z" }, + { url = "https://files.pythonhosted.org/packages/13/b5/29ec78057d377b195ac2c5248c773703a6b602e132a763e20ec0457e7440/multidict-6.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:467fe64138cfac771f0e949b938c2e1ada2b5af22f39692aa9258715e9ea613a", size = 250078, upload-time = "2025-08-11T12:07:41.525Z" }, + { url = "https://files.pythonhosted.org/packages/c4/0e/7e79d38f70a872cae32e29b0d77024bef7834b0afb406ddae6558d9e2414/multidict-6.6.4-cp313-cp313-win32.whl", hash = "sha256:14616a30fe6d0a48d0a48d1a633ab3b8bec4cf293aac65f32ed116f620adfd69", size = 41708, upload-time = "2025-08-11T12:07:43.405Z" }, + { url = "https://files.pythonhosted.org/packages/9d/34/746696dffff742e97cd6a23da953e55d0ea51fa601fa2ff387b3edcfaa2c/multidict-6.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:40cd05eaeb39e2bc8939451f033e57feaa2ac99e07dbca8afe2be450a4a3b6cf", size = 45912, upload-time = "2025-08-11T12:07:45.082Z" }, + { url = "https://files.pythonhosted.org/packages/c7/87/3bac136181e271e29170d8d71929cdeddeb77f3e8b6a0c08da3a8e9da114/multidict-6.6.4-cp313-cp313-win_arm64.whl", hash = "sha256:f6eb37d511bfae9e13e82cb4d1af36b91150466f24d9b2b8a9785816deb16605", size = 43076, upload-time = "2025-08-11T12:07:46.746Z" }, + { url = "https://files.pythonhosted.org/packages/64/94/0a8e63e36c049b571c9ae41ee301ada29c3fee9643d9c2548d7d558a1d99/multidict-6.6.4-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:6c84378acd4f37d1b507dfa0d459b449e2321b3ba5f2338f9b085cf7a7ba95eb", size = 82812, upload-time = "2025-08-11T12:07:48.402Z" }, + { url = "https://files.pythonhosted.org/packages/25/1a/be8e369dfcd260d2070a67e65dd3990dd635cbd735b98da31e00ea84cd4e/multidict-6.6.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0e0558693063c75f3d952abf645c78f3c5dfdd825a41d8c4d8156fc0b0da6e7e", size = 48313, upload-time = "2025-08-11T12:07:49.679Z" }, + { url = "https://files.pythonhosted.org/packages/26/5a/dd4ade298674b2f9a7b06a32c94ffbc0497354df8285f27317c66433ce3b/multidict-6.6.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3f8e2384cb83ebd23fd07e9eada8ba64afc4c759cd94817433ab8c81ee4b403f", size = 46777, upload-time = "2025-08-11T12:07:51.318Z" }, + { url = "https://files.pythonhosted.org/packages/89/db/98aa28bc7e071bfba611ac2ae803c24e96dd3a452b4118c587d3d872c64c/multidict-6.6.4-cp313-cp313t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:f996b87b420995a9174b2a7c1a8daf7db4750be6848b03eb5e639674f7963773", size = 229321, upload-time = "2025-08-11T12:07:52.965Z" }, + { url = "https://files.pythonhosted.org/packages/c7/bc/01ddda2a73dd9d167bd85d0e8ef4293836a8f82b786c63fb1a429bc3e678/multidict-6.6.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc356250cffd6e78416cf5b40dc6a74f1edf3be8e834cf8862d9ed5265cf9b0e", size = 249954, upload-time = "2025-08-11T12:07:54.423Z" }, + { url = "https://files.pythonhosted.org/packages/06/78/6b7c0f020f9aa0acf66d0ab4eb9f08375bac9a50ff5e3edb1c4ccd59eafc/multidict-6.6.4-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:dadf95aa862714ea468a49ad1e09fe00fcc9ec67d122f6596a8d40caf6cec7d0", size = 228612, upload-time = "2025-08-11T12:07:55.914Z" }, + { url = "https://files.pythonhosted.org/packages/00/44/3faa416f89b2d5d76e9d447296a81521e1c832ad6e40b92f990697b43192/multidict-6.6.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7dd57515bebffd8ebd714d101d4c434063322e4fe24042e90ced41f18b6d3395", size = 257528, upload-time = "2025-08-11T12:07:57.371Z" }, + { url = "https://files.pythonhosted.org/packages/05/5f/77c03b89af0fcb16f018f668207768191fb9dcfb5e3361a5e706a11db2c9/multidict-6.6.4-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:967af5f238ebc2eb1da4e77af5492219fbd9b4b812347da39a7b5f5c72c0fa45", size = 256329, upload-time = "2025-08-11T12:07:58.844Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e9/ed750a2a9afb4f8dc6f13dc5b67b514832101b95714f1211cd42e0aafc26/multidict-6.6.4-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2a4c6875c37aae9794308ec43e3530e4aa0d36579ce38d89979bbf89582002bb", size = 247928, upload-time = "2025-08-11T12:08:01.037Z" }, + { url = "https://files.pythonhosted.org/packages/1f/b5/e0571bc13cda277db7e6e8a532791d4403dacc9850006cb66d2556e649c0/multidict-6.6.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:7f683a551e92bdb7fac545b9c6f9fa2aebdeefa61d607510b3533286fcab67f5", size = 245228, upload-time = "2025-08-11T12:08:02.96Z" }, + { url = "https://files.pythonhosted.org/packages/f3/a3/69a84b0eccb9824491f06368f5b86e72e4af54c3067c37c39099b6687109/multidict-6.6.4-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:3ba5aaf600edaf2a868a391779f7a85d93bed147854925f34edd24cc70a3e141", size = 235869, upload-time = "2025-08-11T12:08:04.746Z" }, + { url = "https://files.pythonhosted.org/packages/a9/9d/28802e8f9121a6a0804fa009debf4e753d0a59969ea9f70be5f5fdfcb18f/multidict-6.6.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:580b643b7fd2c295d83cad90d78419081f53fd532d1f1eb67ceb7060f61cff0d", size = 243446, upload-time = "2025-08-11T12:08:06.332Z" }, + { url = "https://files.pythonhosted.org/packages/38/ea/6c98add069b4878c1d66428a5f5149ddb6d32b1f9836a826ac764b9940be/multidict-6.6.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:37b7187197da6af3ee0b044dbc9625afd0c885f2800815b228a0e70f9a7f473d", size = 252299, upload-time = "2025-08-11T12:08:07.931Z" }, + { url = "https://files.pythonhosted.org/packages/3a/09/8fe02d204473e14c0af3affd50af9078839dfca1742f025cca765435d6b4/multidict-6.6.4-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e1b93790ed0bc26feb72e2f08299691ceb6da5e9e14a0d13cc74f1869af327a0", size = 246926, upload-time = "2025-08-11T12:08:09.467Z" }, + { url = "https://files.pythonhosted.org/packages/37/3d/7b1e10d774a6df5175ecd3c92bff069e77bed9ec2a927fdd4ff5fe182f67/multidict-6.6.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a506a77ddee1efcca81ecbeae27ade3e09cdf21a8ae854d766c2bb4f14053f92", size = 243383, upload-time = "2025-08-11T12:08:10.981Z" }, + { url = "https://files.pythonhosted.org/packages/50/b0/a6fae46071b645ae98786ab738447de1ef53742eaad949f27e960864bb49/multidict-6.6.4-cp313-cp313t-win32.whl", hash = "sha256:f93b2b2279883d1d0a9e1bd01f312d6fc315c5e4c1f09e112e4736e2f650bc4e", size = 47775, upload-time = "2025-08-11T12:08:12.439Z" }, + { url = "https://files.pythonhosted.org/packages/b2/0a/2436550b1520091af0600dff547913cb2d66fbac27a8c33bc1b1bccd8d98/multidict-6.6.4-cp313-cp313t-win_amd64.whl", hash = "sha256:6d46a180acdf6e87cc41dc15d8f5c2986e1e8739dc25dbb7dac826731ef381a4", size = 53100, upload-time = "2025-08-11T12:08:13.823Z" }, + { url = "https://files.pythonhosted.org/packages/97/ea/43ac51faff934086db9c072a94d327d71b7d8b40cd5dcb47311330929ef0/multidict-6.6.4-cp313-cp313t-win_arm64.whl", hash = "sha256:756989334015e3335d087a27331659820d53ba432befdef6a718398b0a8493ad", size = 45501, upload-time = "2025-08-11T12:08:15.173Z" }, + { url = "https://files.pythonhosted.org/packages/fd/69/b547032297c7e63ba2af494edba695d781af8a0c6e89e4d06cf848b21d80/multidict-6.6.4-py3-none-any.whl", hash = "sha256:27d8f8e125c07cb954e54d75d04905a9bba8a439c1d84aca94949d4d03d8601c", size = 12313, upload-time = "2025-08-11T12:08:46.891Z" }, +] + +[[package]] +name = "mypy" +version = "1.18.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mypy-extensions" }, + { name = "pathspec" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/77/8f0d0001ffad290cef2f7f216f96c814866248a0b92a722365ed54648e7e/mypy-1.18.2.tar.gz", hash = "sha256:06a398102a5f203d7477b2923dda3634c36727fa5c237d8f859ef90c42a9924b", size = 3448846, upload-time = "2025-09-19T00:11:10.519Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/6f/657961a0743cff32e6c0611b63ff1c1970a0b482ace35b069203bf705187/mypy-1.18.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c1eab0cf6294dafe397c261a75f96dc2c31bffe3b944faa24db5def4e2b0f77c", size = 12807973, upload-time = "2025-09-19T00:10:35.282Z" }, + { url = "https://files.pythonhosted.org/packages/10/e9/420822d4f661f13ca8900f5fa239b40ee3be8b62b32f3357df9a3045a08b/mypy-1.18.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7a780ca61fc239e4865968ebc5240bb3bf610ef59ac398de9a7421b54e4a207e", size = 11896527, upload-time = "2025-09-19T00:10:55.791Z" }, + { url = "https://files.pythonhosted.org/packages/aa/73/a05b2bbaa7005f4642fcfe40fb73f2b4fb6bb44229bd585b5878e9a87ef8/mypy-1.18.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:448acd386266989ef11662ce3c8011fd2a7b632e0ec7d61a98edd8e27472225b", size = 12507004, upload-time = "2025-09-19T00:11:05.411Z" }, + { url = "https://files.pythonhosted.org/packages/4f/01/f6e4b9f0d031c11ccbd6f17da26564f3a0f3c4155af344006434b0a05a9d/mypy-1.18.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f9e171c465ad3901dc652643ee4bffa8e9fef4d7d0eece23b428908c77a76a66", size = 13245947, upload-time = "2025-09-19T00:10:46.923Z" }, + { url = "https://files.pythonhosted.org/packages/d7/97/19727e7499bfa1ae0773d06afd30ac66a58ed7437d940c70548634b24185/mypy-1.18.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:592ec214750bc00741af1f80cbf96b5013d81486b7bb24cb052382c19e40b428", size = 13499217, upload-time = "2025-09-19T00:09:39.472Z" }, + { url = "https://files.pythonhosted.org/packages/9f/4f/90dc8c15c1441bf31cf0f9918bb077e452618708199e530f4cbd5cede6ff/mypy-1.18.2-cp310-cp310-win_amd64.whl", hash = "sha256:7fb95f97199ea11769ebe3638c29b550b5221e997c63b14ef93d2e971606ebed", size = 9766753, upload-time = "2025-09-19T00:10:49.161Z" }, + { url = "https://files.pythonhosted.org/packages/88/87/cafd3ae563f88f94eec33f35ff722d043e09832ea8530ef149ec1efbaf08/mypy-1.18.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:807d9315ab9d464125aa9fcf6d84fde6e1dc67da0b6f80e7405506b8ac72bc7f", size = 12731198, upload-time = "2025-09-19T00:09:44.857Z" }, + { url = "https://files.pythonhosted.org/packages/0f/e0/1e96c3d4266a06d4b0197ace5356d67d937d8358e2ee3ffac71faa843724/mypy-1.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:776bb00de1778caf4db739c6e83919c1d85a448f71979b6a0edd774ea8399341", size = 11817879, upload-time = "2025-09-19T00:09:47.131Z" }, + { url = "https://files.pythonhosted.org/packages/72/ef/0c9ba89eb03453e76bdac5a78b08260a848c7bfc5d6603634774d9cd9525/mypy-1.18.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1379451880512ffce14505493bd9fe469e0697543717298242574882cf8cdb8d", size = 12427292, upload-time = "2025-09-19T00:10:22.472Z" }, + { url = "https://files.pythonhosted.org/packages/1a/52/ec4a061dd599eb8179d5411d99775bec2a20542505988f40fc2fee781068/mypy-1.18.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1331eb7fd110d60c24999893320967594ff84c38ac6d19e0a76c5fd809a84c86", size = 13163750, upload-time = "2025-09-19T00:09:51.472Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5f/2cf2ceb3b36372d51568f2208c021870fe7834cf3186b653ac6446511839/mypy-1.18.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ca30b50a51e7ba93b00422e486cbb124f1c56a535e20eff7b2d6ab72b3b2e37", size = 13351827, upload-time = "2025-09-19T00:09:58.311Z" }, + { url = "https://files.pythonhosted.org/packages/c8/7d/2697b930179e7277529eaaec1513f8de622818696857f689e4a5432e5e27/mypy-1.18.2-cp311-cp311-win_amd64.whl", hash = "sha256:664dc726e67fa54e14536f6e1224bcfce1d9e5ac02426d2326e2bb4e081d1ce8", size = 9757983, upload-time = "2025-09-19T00:10:09.071Z" }, + { url = "https://files.pythonhosted.org/packages/07/06/dfdd2bc60c66611dd8335f463818514733bc763e4760dee289dcc33df709/mypy-1.18.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:33eca32dd124b29400c31d7cf784e795b050ace0e1f91b8dc035672725617e34", size = 12908273, upload-time = "2025-09-19T00:10:58.321Z" }, + { url = "https://files.pythonhosted.org/packages/81/14/6a9de6d13a122d5608e1a04130724caf9170333ac5a924e10f670687d3eb/mypy-1.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3c47adf30d65e89b2dcd2fa32f3aeb5e94ca970d2c15fcb25e297871c8e4764", size = 11920910, upload-time = "2025-09-19T00:10:20.043Z" }, + { url = "https://files.pythonhosted.org/packages/5f/a9/b29de53e42f18e8cc547e38daa9dfa132ffdc64f7250e353f5c8cdd44bee/mypy-1.18.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d6c838e831a062f5f29d11c9057c6009f60cb294fea33a98422688181fe2893", size = 12465585, upload-time = "2025-09-19T00:10:33.005Z" }, + { url = "https://files.pythonhosted.org/packages/77/ae/6c3d2c7c61ff21f2bee938c917616c92ebf852f015fb55917fd6e2811db2/mypy-1.18.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01199871b6110a2ce984bde85acd481232d17413868c9807e95c1b0739a58914", size = 13348562, upload-time = "2025-09-19T00:10:11.51Z" }, + { url = "https://files.pythonhosted.org/packages/4d/31/aec68ab3b4aebdf8f36d191b0685d99faa899ab990753ca0fee60fb99511/mypy-1.18.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a2afc0fa0b0e91b4599ddfe0f91e2c26c2b5a5ab263737e998d6817874c5f7c8", size = 13533296, upload-time = "2025-09-19T00:10:06.568Z" }, + { url = "https://files.pythonhosted.org/packages/9f/83/abcb3ad9478fca3ebeb6a5358bb0b22c95ea42b43b7789c7fb1297ca44f4/mypy-1.18.2-cp312-cp312-win_amd64.whl", hash = "sha256:d8068d0afe682c7c4897c0f7ce84ea77f6de953262b12d07038f4d296d547074", size = 9828828, upload-time = "2025-09-19T00:10:28.203Z" }, + { url = "https://files.pythonhosted.org/packages/5f/04/7f462e6fbba87a72bc8097b93f6842499c428a6ff0c81dd46948d175afe8/mypy-1.18.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:07b8b0f580ca6d289e69209ec9d3911b4a26e5abfde32228a288eb79df129fcc", size = 12898728, upload-time = "2025-09-19T00:10:01.33Z" }, + { url = "https://files.pythonhosted.org/packages/99/5b/61ed4efb64f1871b41fd0b82d29a64640f3516078f6c7905b68ab1ad8b13/mypy-1.18.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ed4482847168439651d3feee5833ccedbf6657e964572706a2adb1f7fa4dfe2e", size = 11910758, upload-time = "2025-09-19T00:10:42.607Z" }, + { url = "https://files.pythonhosted.org/packages/3c/46/d297d4b683cc89a6e4108c4250a6a6b717f5fa96e1a30a7944a6da44da35/mypy-1.18.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3ad2afadd1e9fea5cf99a45a822346971ede8685cc581ed9cd4d42eaf940986", size = 12475342, upload-time = "2025-09-19T00:11:00.371Z" }, + { url = "https://files.pythonhosted.org/packages/83/45/4798f4d00df13eae3bfdf726c9244bcb495ab5bd588c0eed93a2f2dd67f3/mypy-1.18.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a431a6f1ef14cf8c144c6b14793a23ec4eae3db28277c358136e79d7d062f62d", size = 13338709, upload-time = "2025-09-19T00:11:03.358Z" }, + { url = "https://files.pythonhosted.org/packages/d7/09/479f7358d9625172521a87a9271ddd2441e1dab16a09708f056e97007207/mypy-1.18.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7ab28cc197f1dd77a67e1c6f35cd1f8e8b73ed2217e4fc005f9e6a504e46e7ba", size = 13529806, upload-time = "2025-09-19T00:10:26.073Z" }, + { url = "https://files.pythonhosted.org/packages/71/cf/ac0f2c7e9d0ea3c75cd99dff7aec1c9df4a1376537cb90e4c882267ee7e9/mypy-1.18.2-cp313-cp313-win_amd64.whl", hash = "sha256:0e2785a84b34a72ba55fb5daf079a1003a34c05b22238da94fcae2bbe46f3544", size = 9833262, upload-time = "2025-09-19T00:10:40.035Z" }, + { url = "https://files.pythonhosted.org/packages/5a/0c/7d5300883da16f0063ae53996358758b2a2df2a09c72a5061fa79a1f5006/mypy-1.18.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:62f0e1e988ad41c2a110edde6c398383a889d95b36b3e60bcf155f5164c4fdce", size = 12893775, upload-time = "2025-09-19T00:10:03.814Z" }, + { url = "https://files.pythonhosted.org/packages/50/df/2cffbf25737bdb236f60c973edf62e3e7b4ee1c25b6878629e88e2cde967/mypy-1.18.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8795a039bab805ff0c1dfdb8cd3344642c2b99b8e439d057aba30850b8d3423d", size = 11936852, upload-time = "2025-09-19T00:10:51.631Z" }, + { url = "https://files.pythonhosted.org/packages/be/50/34059de13dd269227fb4a03be1faee6e2a4b04a2051c82ac0a0b5a773c9a/mypy-1.18.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6ca1e64b24a700ab5ce10133f7ccd956a04715463d30498e64ea8715236f9c9c", size = 12480242, upload-time = "2025-09-19T00:11:07.955Z" }, + { url = "https://files.pythonhosted.org/packages/5b/11/040983fad5132d85914c874a2836252bbc57832065548885b5bb5b0d4359/mypy-1.18.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d924eef3795cc89fecf6bedc6ed32b33ac13e8321344f6ddbf8ee89f706c05cb", size = 13326683, upload-time = "2025-09-19T00:09:55.572Z" }, + { url = "https://files.pythonhosted.org/packages/e9/ba/89b2901dd77414dd7a8c8729985832a5735053be15b744c18e4586e506ef/mypy-1.18.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:20c02215a080e3a2be3aa50506c67242df1c151eaba0dcbc1e4e557922a26075", size = 13514749, upload-time = "2025-09-19T00:10:44.827Z" }, + { url = "https://files.pythonhosted.org/packages/25/bc/cc98767cffd6b2928ba680f3e5bc969c4152bf7c2d83f92f5a504b92b0eb/mypy-1.18.2-cp314-cp314-win_amd64.whl", hash = "sha256:749b5f83198f1ca64345603118a6f01a4e99ad4bf9d103ddc5a3200cc4614adf", size = 9982959, upload-time = "2025-09-19T00:10:37.344Z" }, + { url = "https://files.pythonhosted.org/packages/87/e3/be76d87158ebafa0309946c4a73831974d4d6ab4f4ef40c3b53a385a66fd/mypy-1.18.2-py3-none-any.whl", hash = "sha256:22a1748707dd62b58d2ae53562ffc4d7f8bcc727e8ac7cbc69c053ddc874d47e", size = 2352367, upload-time = "2025-09-19T00:10:15.489Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, +] + +[[package]] +name = "nmcli" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/57/a4b743fe2a06014236c081db3396572b4177896735504fc00ce42ff55a89/nmcli-1.5.0.tar.gz", hash = "sha256:6261dbd6f48b8454b7efc992f4285a5c126613a91a965d2c6c2caf749dec4ddf", size = 14994, upload-time = "2024-12-07T06:40:58.043Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/76/db262236cd1dbb18b1f98762ecba67cc97b572c74409224e61bbf1ac0c6a/nmcli-1.5.0-py3-none-any.whl", hash = "sha256:55e8cba0b4650f8d0741c9c403f8e33c6ad270b66bc23f9dc830214ef37b8429", size = 18909, upload-time = "2024-12-07T06:40:56.205Z" }, +] + +[[package]] +name = "nodeenv" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, +] + +[[package]] +name = "numpy" +version = "2.2.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/b2/ce4b867d8cd9c0ee84938ae1e6a6f7926ebf928c9090d036fc3c6a04f946/numpy-2.2.5.tar.gz", hash = "sha256:a9c0d994680cd991b1cb772e8b297340085466a6fe964bc9d4e80f5e2f43c291", size = 20273920, upload-time = "2025-04-19T23:27:42.561Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/4e/3d9e6d16237c2aa5485695f0626cbba82f6481efca2e9132368dea3b885e/numpy-2.2.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1f4a922da1729f4c40932b2af4fe84909c7a6e167e6e99f71838ce3a29f3fe26", size = 21252117, upload-time = "2025-04-19T22:31:01.142Z" }, + { url = "https://files.pythonhosted.org/packages/38/e4/db91349d4079cd15c02ff3b4b8882a529991d6aca077db198a2f2a670406/numpy-2.2.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b6f91524d31b34f4a5fee24f5bc16dcd1491b668798b6d85585d836c1e633a6a", size = 14424615, upload-time = "2025-04-19T22:31:24.873Z" }, + { url = "https://files.pythonhosted.org/packages/f8/59/6e5b011f553c37b008bd115c7ba7106a18f372588fbb1b430b7a5d2c41ce/numpy-2.2.5-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:19f4718c9012e3baea91a7dba661dcab2451cda2550678dc30d53acb91a7290f", size = 5428691, upload-time = "2025-04-19T22:31:33.998Z" }, + { url = "https://files.pythonhosted.org/packages/a2/58/d5d70ebdac82b3a6ddf409b3749ca5786636e50fd64d60edb46442af6838/numpy-2.2.5-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:eb7fd5b184e5d277afa9ec0ad5e4eb562ecff541e7f60e69ee69c8d59e9aeaba", size = 6965010, upload-time = "2025-04-19T22:31:45.281Z" }, + { url = "https://files.pythonhosted.org/packages/dc/a8/c290394be346d4e7b48a40baf292626fd96ec56a6398ace4c25d9079bc6a/numpy-2.2.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6413d48a9be53e183eb06495d8e3b006ef8f87c324af68241bbe7a39e8ff54c3", size = 14369885, upload-time = "2025-04-19T22:32:06.557Z" }, + { url = "https://files.pythonhosted.org/packages/c2/70/fed13c70aabe7049368553e81d7ca40f305f305800a007a956d7cd2e5476/numpy-2.2.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7451f92eddf8503c9b8aa4fe6aa7e87fd51a29c2cfc5f7dbd72efde6c65acf57", size = 16418372, upload-time = "2025-04-19T22:32:31.716Z" }, + { url = "https://files.pythonhosted.org/packages/04/ab/c3c14f25ddaecd6fc58a34858f6a93a21eea6c266ba162fa99f3d0de12ac/numpy-2.2.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0bcb1d057b7571334139129b7f941588f69ce7c4ed15a9d6162b2ea54ded700c", size = 15883173, upload-time = "2025-04-19T22:32:55.106Z" }, + { url = "https://files.pythonhosted.org/packages/50/18/f53710a19042911c7aca824afe97c203728a34b8cf123e2d94621a12edc3/numpy-2.2.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:36ab5b23915887543441efd0417e6a3baa08634308894316f446027611b53bf1", size = 18206881, upload-time = "2025-04-19T22:33:22.08Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ec/5b407bab82f10c65af5a5fe754728df03f960fd44d27c036b61f7b3ef255/numpy-2.2.5-cp310-cp310-win32.whl", hash = "sha256:422cc684f17bc963da5f59a31530b3936f57c95a29743056ef7a7903a5dbdf88", size = 6609852, upload-time = "2025-04-19T22:33:33.357Z" }, + { url = "https://files.pythonhosted.org/packages/b6/f5/467ca8675c7e6c567f571d8db942cc10a87588bd9e20a909d8af4171edda/numpy-2.2.5-cp310-cp310-win_amd64.whl", hash = "sha256:e4f0b035d9d0ed519c813ee23e0a733db81ec37d2e9503afbb6e54ccfdee0fa7", size = 12944922, upload-time = "2025-04-19T22:33:53.192Z" }, + { url = "https://files.pythonhosted.org/packages/f5/fb/e4e4c254ba40e8f0c78218f9e86304628c75b6900509b601c8433bdb5da7/numpy-2.2.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c42365005c7a6c42436a54d28c43fe0e01ca11eb2ac3cefe796c25a5f98e5e9b", size = 21256475, upload-time = "2025-04-19T22:34:24.174Z" }, + { url = "https://files.pythonhosted.org/packages/81/32/dd1f7084f5c10b2caad778258fdaeedd7fbd8afcd2510672811e6138dfac/numpy-2.2.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:498815b96f67dc347e03b719ef49c772589fb74b8ee9ea2c37feae915ad6ebda", size = 14461474, upload-time = "2025-04-19T22:34:46.578Z" }, + { url = "https://files.pythonhosted.org/packages/0e/65/937cdf238ef6ac54ff749c0f66d9ee2b03646034c205cea9b6c51f2f3ad1/numpy-2.2.5-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:6411f744f7f20081b1b4e7112e0f4c9c5b08f94b9f086e6f0adf3645f85d3a4d", size = 5426875, upload-time = "2025-04-19T22:34:56.281Z" }, + { url = "https://files.pythonhosted.org/packages/25/17/814515fdd545b07306eaee552b65c765035ea302d17de1b9cb50852d2452/numpy-2.2.5-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:9de6832228f617c9ef45d948ec1cd8949c482238d68b2477e6f642c33a7b0a54", size = 6969176, upload-time = "2025-04-19T22:35:07.518Z" }, + { url = "https://files.pythonhosted.org/packages/e5/32/a66db7a5c8b5301ec329ab36d0ecca23f5e18907f43dbd593c8ec326d57c/numpy-2.2.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:369e0d4647c17c9363244f3468f2227d557a74b6781cb62ce57cf3ef5cc7c610", size = 14374850, upload-time = "2025-04-19T22:35:31.347Z" }, + { url = "https://files.pythonhosted.org/packages/ad/c9/1bf6ada582eebcbe8978f5feb26584cd2b39f94ededeea034ca8f84af8c8/numpy-2.2.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:262d23f383170f99cd9191a7c85b9a50970fe9069b2f8ab5d786eca8a675d60b", size = 16430306, upload-time = "2025-04-19T22:35:57.573Z" }, + { url = "https://files.pythonhosted.org/packages/6a/f0/3f741863f29e128f4fcfdb99253cc971406b402b4584663710ee07f5f7eb/numpy-2.2.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:aa70fdbdc3b169d69e8c59e65c07a1c9351ceb438e627f0fdcd471015cd956be", size = 15884767, upload-time = "2025-04-19T22:36:22.245Z" }, + { url = "https://files.pythonhosted.org/packages/98/d9/4ccd8fd6410f7bf2d312cbc98892e0e43c2fcdd1deae293aeb0a93b18071/numpy-2.2.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37e32e985f03c06206582a7323ef926b4e78bdaa6915095ef08070471865b906", size = 18219515, upload-time = "2025-04-19T22:36:49.822Z" }, + { url = "https://files.pythonhosted.org/packages/b1/56/783237243d4395c6dd741cf16eeb1a9035ee3d4310900e6b17e875d1b201/numpy-2.2.5-cp311-cp311-win32.whl", hash = "sha256:f5045039100ed58fa817a6227a356240ea1b9a1bc141018864c306c1a16d4175", size = 6607842, upload-time = "2025-04-19T22:37:01.624Z" }, + { url = "https://files.pythonhosted.org/packages/98/89/0c93baaf0094bdaaaa0536fe61a27b1dce8a505fa262a865ec142208cfe9/numpy-2.2.5-cp311-cp311-win_amd64.whl", hash = "sha256:b13f04968b46ad705f7c8a80122a42ae8f620536ea38cf4bdd374302926424dd", size = 12949071, upload-time = "2025-04-19T22:37:21.098Z" }, + { url = "https://files.pythonhosted.org/packages/e2/f7/1fd4ff108cd9d7ef929b8882692e23665dc9c23feecafbb9c6b80f4ec583/numpy-2.2.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ee461a4eaab4f165b68780a6a1af95fb23a29932be7569b9fab666c407969051", size = 20948633, upload-time = "2025-04-19T22:37:52.4Z" }, + { url = "https://files.pythonhosted.org/packages/12/03/d443c278348371b20d830af155ff2079acad6a9e60279fac2b41dbbb73d8/numpy-2.2.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ec31367fd6a255dc8de4772bd1658c3e926d8e860a0b6e922b615e532d320ddc", size = 14176123, upload-time = "2025-04-19T22:38:15.058Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0b/5ca264641d0e7b14393313304da48b225d15d471250376f3fbdb1a2be603/numpy-2.2.5-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:47834cde750d3c9f4e52c6ca28a7361859fcaf52695c7dc3cc1a720b8922683e", size = 5163817, upload-time = "2025-04-19T22:38:24.885Z" }, + { url = "https://files.pythonhosted.org/packages/04/b3/d522672b9e3d28e26e1613de7675b441bbd1eaca75db95680635dd158c67/numpy-2.2.5-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:2c1a1c6ccce4022383583a6ded7bbcda22fc635eb4eb1e0a053336425ed36dfa", size = 6698066, upload-time = "2025-04-19T22:38:35.782Z" }, + { url = "https://files.pythonhosted.org/packages/a0/93/0f7a75c1ff02d4b76df35079676b3b2719fcdfb39abdf44c8b33f43ef37d/numpy-2.2.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d75f338f5f79ee23548b03d801d28a505198297534f62416391857ea0479571", size = 14087277, upload-time = "2025-04-19T22:38:57.697Z" }, + { url = "https://files.pythonhosted.org/packages/b0/d9/7c338b923c53d431bc837b5b787052fef9ae68a56fe91e325aac0d48226e/numpy-2.2.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a801fef99668f309b88640e28d261991bfad9617c27beda4a3aec4f217ea073", size = 16135742, upload-time = "2025-04-19T22:39:22.689Z" }, + { url = "https://files.pythonhosted.org/packages/2d/10/4dec9184a5d74ba9867c6f7d1e9f2e0fb5fe96ff2bf50bb6f342d64f2003/numpy-2.2.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:abe38cd8381245a7f49967a6010e77dbf3680bd3627c0fe4362dd693b404c7f8", size = 15581825, upload-time = "2025-04-19T22:39:45.794Z" }, + { url = "https://files.pythonhosted.org/packages/80/1f/2b6fcd636e848053f5b57712a7d1880b1565eec35a637fdfd0a30d5e738d/numpy-2.2.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5a0ac90e46fdb5649ab6369d1ab6104bfe5854ab19b645bf5cda0127a13034ae", size = 17899600, upload-time = "2025-04-19T22:40:13.427Z" }, + { url = "https://files.pythonhosted.org/packages/ec/87/36801f4dc2623d76a0a3835975524a84bd2b18fe0f8835d45c8eae2f9ff2/numpy-2.2.5-cp312-cp312-win32.whl", hash = "sha256:0cd48122a6b7eab8f06404805b1bd5856200e3ed6f8a1b9a194f9d9054631beb", size = 6312626, upload-time = "2025-04-19T22:40:25.223Z" }, + { url = "https://files.pythonhosted.org/packages/8b/09/4ffb4d6cfe7ca6707336187951992bd8a8b9142cf345d87ab858d2d7636a/numpy-2.2.5-cp312-cp312-win_amd64.whl", hash = "sha256:ced69262a8278547e63409b2653b372bf4baff0870c57efa76c5703fd6543282", size = 12645715, upload-time = "2025-04-19T22:40:44.528Z" }, + { url = "https://files.pythonhosted.org/packages/e2/a0/0aa7f0f4509a2e07bd7a509042967c2fab635690d4f48c6c7b3afd4f448c/numpy-2.2.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:059b51b658f4414fff78c6d7b1b4e18283ab5fa56d270ff212d5ba0c561846f4", size = 20935102, upload-time = "2025-04-19T22:41:16.234Z" }, + { url = "https://files.pythonhosted.org/packages/7e/e4/a6a9f4537542912ec513185396fce52cdd45bdcf3e9d921ab02a93ca5aa9/numpy-2.2.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:47f9ed103af0bc63182609044b0490747e03bd20a67e391192dde119bf43d52f", size = 14191709, upload-time = "2025-04-19T22:41:38.472Z" }, + { url = "https://files.pythonhosted.org/packages/be/65/72f3186b6050bbfe9c43cb81f9df59ae63603491d36179cf7a7c8d216758/numpy-2.2.5-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:261a1ef047751bb02f29dfe337230b5882b54521ca121fc7f62668133cb119c9", size = 5149173, upload-time = "2025-04-19T22:41:47.823Z" }, + { url = "https://files.pythonhosted.org/packages/e5/e9/83e7a9432378dde5802651307ae5e9ea07bb72b416728202218cd4da2801/numpy-2.2.5-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4520caa3807c1ceb005d125a75e715567806fed67e315cea619d5ec6e75a4191", size = 6684502, upload-time = "2025-04-19T22:41:58.689Z" }, + { url = "https://files.pythonhosted.org/packages/ea/27/b80da6c762394c8ee516b74c1f686fcd16c8f23b14de57ba0cad7349d1d2/numpy-2.2.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d14b17b9be5f9c9301f43d2e2a4886a33b53f4e6fdf9ca2f4cc60aeeee76372", size = 14084417, upload-time = "2025-04-19T22:42:19.897Z" }, + { url = "https://files.pythonhosted.org/packages/aa/fc/ebfd32c3e124e6a1043e19c0ab0769818aa69050ce5589b63d05ff185526/numpy-2.2.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ba321813a00e508d5421104464510cc962a6f791aa2fca1c97b1e65027da80d", size = 16133807, upload-time = "2025-04-19T22:42:44.433Z" }, + { url = "https://files.pythonhosted.org/packages/bf/9b/4cc171a0acbe4666f7775cfd21d4eb6bb1d36d3a0431f48a73e9212d2278/numpy-2.2.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4cbdef3ddf777423060c6f81b5694bad2dc9675f110c4b2a60dc0181543fac7", size = 15575611, upload-time = "2025-04-19T22:43:09.928Z" }, + { url = "https://files.pythonhosted.org/packages/a3/45/40f4135341850df48f8edcf949cf47b523c404b712774f8855a64c96ef29/numpy-2.2.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:54088a5a147ab71a8e7fdfd8c3601972751ded0739c6b696ad9cb0343e21ab73", size = 17895747, upload-time = "2025-04-19T22:43:36.983Z" }, + { url = "https://files.pythonhosted.org/packages/f8/4c/b32a17a46f0ffbde8cc82df6d3daeaf4f552e346df143e1b188a701a8f09/numpy-2.2.5-cp313-cp313-win32.whl", hash = "sha256:c8b82a55ef86a2d8e81b63da85e55f5537d2157165be1cb2ce7cfa57b6aef38b", size = 6309594, upload-time = "2025-04-19T22:47:10.523Z" }, + { url = "https://files.pythonhosted.org/packages/13/ae/72e6276feb9ef06787365b05915bfdb057d01fceb4a43cb80978e518d79b/numpy-2.2.5-cp313-cp313-win_amd64.whl", hash = "sha256:d8882a829fd779f0f43998e931c466802a77ca1ee0fe25a3abe50278616b1471", size = 12638356, upload-time = "2025-04-19T22:47:30.253Z" }, + { url = "https://files.pythonhosted.org/packages/79/56/be8b85a9f2adb688e7ded6324e20149a03541d2b3297c3ffc1a73f46dedb/numpy-2.2.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:e8b025c351b9f0e8b5436cf28a07fa4ac0204d67b38f01433ac7f9b870fa38c6", size = 20963778, upload-time = "2025-04-19T22:44:09.251Z" }, + { url = "https://files.pythonhosted.org/packages/ff/77/19c5e62d55bff507a18c3cdff82e94fe174957bad25860a991cac719d3ab/numpy-2.2.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8dfa94b6a4374e7851bbb6f35e6ded2120b752b063e6acdd3157e4d2bb922eba", size = 14207279, upload-time = "2025-04-19T22:44:31.383Z" }, + { url = "https://files.pythonhosted.org/packages/75/22/aa11f22dc11ff4ffe4e849d9b63bbe8d4ac6d5fae85ddaa67dfe43be3e76/numpy-2.2.5-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:97c8425d4e26437e65e1d189d22dff4a079b747ff9c2788057bfb8114ce1e133", size = 5199247, upload-time = "2025-04-19T22:44:40.361Z" }, + { url = "https://files.pythonhosted.org/packages/4f/6c/12d5e760fc62c08eded0394f62039f5a9857f758312bf01632a81d841459/numpy-2.2.5-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:352d330048c055ea6db701130abc48a21bec690a8d38f8284e00fab256dc1376", size = 6711087, upload-time = "2025-04-19T22:44:51.188Z" }, + { url = "https://files.pythonhosted.org/packages/ef/94/ece8280cf4218b2bee5cec9567629e61e51b4be501e5c6840ceb593db945/numpy-2.2.5-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b4c0773b6ada798f51f0f8e30c054d32304ccc6e9c5d93d46cb26f3d385ab19", size = 14059964, upload-time = "2025-04-19T22:45:12.451Z" }, + { url = "https://files.pythonhosted.org/packages/39/41/c5377dac0514aaeec69115830a39d905b1882819c8e65d97fc60e177e19e/numpy-2.2.5-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55f09e00d4dccd76b179c0f18a44f041e5332fd0e022886ba1c0bbf3ea4a18d0", size = 16121214, upload-time = "2025-04-19T22:45:37.734Z" }, + { url = "https://files.pythonhosted.org/packages/db/54/3b9f89a943257bc8e187145c6bc0eb8e3d615655f7b14e9b490b053e8149/numpy-2.2.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:02f226baeefa68f7d579e213d0f3493496397d8f1cff5e2b222af274c86a552a", size = 15575788, upload-time = "2025-04-19T22:46:01.908Z" }, + { url = "https://files.pythonhosted.org/packages/b1/c4/2e407e85df35b29f79945751b8f8e671057a13a376497d7fb2151ba0d290/numpy-2.2.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c26843fd58f65da9491165072da2cccc372530681de481ef670dcc8e27cfb066", size = 17893672, upload-time = "2025-04-19T22:46:28.585Z" }, + { url = "https://files.pythonhosted.org/packages/29/7e/d0b44e129d038dba453f00d0e29ebd6eaf2f06055d72b95b9947998aca14/numpy-2.2.5-cp313-cp313t-win32.whl", hash = "sha256:1a161c2c79ab30fe4501d5a2bbfe8b162490757cf90b7f05be8b80bc02f7bb8e", size = 6377102, upload-time = "2025-04-19T22:46:39.949Z" }, + { url = "https://files.pythonhosted.org/packages/63/be/b85e4aa4bf42c6502851b971f1c326d583fcc68227385f92089cf50a7b45/numpy-2.2.5-cp313-cp313t-win_amd64.whl", hash = "sha256:d403c84991b5ad291d3809bace5e85f4bbf44a04bdc9a88ed2bb1807b3360bb8", size = 12750096, upload-time = "2025-04-19T22:47:00.147Z" }, + { url = "https://files.pythonhosted.org/packages/35/e4/5ef5ef1d4308f96961198b2323bfc7c7afb0ccc0d623b01c79bc87ab496d/numpy-2.2.5-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b4ea7e1cff6784e58fe281ce7e7f05036b3e1c89c6f922a6bfbc0a7e8768adbe", size = 21083404, upload-time = "2025-04-19T22:48:01.605Z" }, + { url = "https://files.pythonhosted.org/packages/a3/5f/bde9238e8e977652a16a4b114ed8aa8bb093d718c706eeecb5f7bfa59572/numpy-2.2.5-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:d7543263084a85fbc09c704b515395398d31d6395518446237eac219eab9e55e", size = 6828578, upload-time = "2025-04-19T22:48:13.118Z" }, + { url = "https://files.pythonhosted.org/packages/ef/7f/813f51ed86e559ab2afb6a6f33aa6baf8a560097e25e4882a938986c76c2/numpy-2.2.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0255732338c4fdd00996c0421884ea8a3651eea555c3a56b84892b66f696eb70", size = 16234796, upload-time = "2025-04-19T22:48:37.102Z" }, + { url = "https://files.pythonhosted.org/packages/68/67/1175790323026d3337cc285cc9c50eca637d70472b5e622529df74bb8f37/numpy-2.2.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d2e3bdadaba0e040d1e7ab39db73e0afe2c74ae277f5614dad53eadbecbbb169", size = 12859001, upload-time = "2025-04-19T22:48:57.665Z" }, +] + +[[package]] +name = "numpy-stl" +version = "3.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "python-utils" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/80/96366d1b69223d4ca55fca30f158096df169b00aa6b4dd963025980e7ede/numpy_stl-3.2.0.tar.gz", hash = "sha256:5a20c3f79cddaa0abc6a4b99f5486aceed4f88152f29b19a57acc844e183fd4d", size = 824382, upload-time = "2024-11-25T17:15:43.227Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/2c/e17b8814050427929077639d35a42187a006922600d4840475bdc5f64ebb/numpy_stl-3.2.0-py3-none-any.whl", hash = "sha256:697c81b107231362460aaedf4647e81ba54f54f59c896772fea7904c9c439da5", size = 20775, upload-time = "2024-11-25T17:15:41.346Z" }, +] + +[[package]] +name = "onnxruntime" +version = "1.22.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coloredlogs" }, + { name = "flatbuffers" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "protobuf" }, + { name = "sympy" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/b9/664a1ffee62fa51529fac27b37409d5d28cadee8d97db806fcba68339b7e/onnxruntime-1.22.1-cp310-cp310-macosx_13_0_universal2.whl", hash = "sha256:80e7f51da1f5201c1379b8d6ef6170505cd800e40da216290f5e06be01aadf95", size = 34319864, upload-time = "2025-07-10T19:15:15.371Z" }, + { url = "https://files.pythonhosted.org/packages/b9/64/bc7221e92c994931024e22b22401b962c299e991558c3d57f7e34538b4b9/onnxruntime-1.22.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89ddfdbbdaf7e3a59515dee657f6515601d55cb21a0f0f48c81aefc54ff1b73", size = 14472246, upload-time = "2025-07-10T19:15:19.403Z" }, + { url = "https://files.pythonhosted.org/packages/84/57/901eddbfb59ac4d008822b236450d5765cafcd450c787019416f8d3baf11/onnxruntime-1.22.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bddc75868bcf6f9ed76858a632f65f7b1846bdcefc6d637b1e359c2c68609964", size = 16459905, upload-time = "2025-07-10T19:15:21.749Z" }, + { url = "https://files.pythonhosted.org/packages/de/90/d6a1eb9b47e66a18afe7d1cf7cf0b2ef966ffa6f44d9f32d94c2be2860fb/onnxruntime-1.22.1-cp310-cp310-win_amd64.whl", hash = "sha256:01e2f21b2793eb0c8642d2be3cee34cc7d96b85f45f6615e4e220424158877ce", size = 12689001, upload-time = "2025-07-10T19:15:23.848Z" }, + { url = "https://files.pythonhosted.org/packages/82/ff/4a1a6747e039ef29a8d4ee4510060e9a805982b6da906a3da2306b7a3be6/onnxruntime-1.22.1-cp311-cp311-macosx_13_0_universal2.whl", hash = "sha256:f4581bccb786da68725d8eac7c63a8f31a89116b8761ff8b4989dc58b61d49a0", size = 34324148, upload-time = "2025-07-10T19:15:26.584Z" }, + { url = "https://files.pythonhosted.org/packages/0b/05/9f1929723f1cca8c9fb1b2b97ac54ce61362c7201434d38053ea36ee4225/onnxruntime-1.22.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7ae7526cf10f93454beb0f751e78e5cb7619e3b92f9fc3bd51aa6f3b7a8977e5", size = 14473779, upload-time = "2025-07-10T19:15:30.183Z" }, + { url = "https://files.pythonhosted.org/packages/59/f3/c93eb4167d4f36ea947930f82850231f7ce0900cb00e1a53dc4995b60479/onnxruntime-1.22.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f6effa1299ac549a05c784d50292e3378dbbf010346ded67400193b09ddc2f04", size = 16460799, upload-time = "2025-07-10T19:15:33.005Z" }, + { url = "https://files.pythonhosted.org/packages/a8/01/e536397b03e4462d3260aee5387e6f606c8fa9d2b20b1728f988c3c72891/onnxruntime-1.22.1-cp311-cp311-win_amd64.whl", hash = "sha256:f28a42bb322b4ca6d255531bb334a2b3e21f172e37c1741bd5e66bc4b7b61f03", size = 12689881, upload-time = "2025-07-10T19:15:35.501Z" }, + { url = "https://files.pythonhosted.org/packages/48/70/ca2a4d38a5deccd98caa145581becb20c53684f451e89eb3a39915620066/onnxruntime-1.22.1-cp312-cp312-macosx_13_0_universal2.whl", hash = "sha256:a938d11c0dc811badf78e435daa3899d9af38abee950d87f3ab7430eb5b3cf5a", size = 34342883, upload-time = "2025-07-10T19:15:38.223Z" }, + { url = "https://files.pythonhosted.org/packages/29/e5/00b099b4d4f6223b610421080d0eed9327ef9986785c9141819bbba0d396/onnxruntime-1.22.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:984cea2a02fcc5dfea44ade9aca9fe0f7a8a2cd6f77c258fc4388238618f3928", size = 14473861, upload-time = "2025-07-10T19:15:42.911Z" }, + { url = "https://files.pythonhosted.org/packages/0a/50/519828a5292a6ccd8d5cd6d2f72c6b36ea528a2ef68eca69647732539ffa/onnxruntime-1.22.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2d39a530aff1ec8d02e365f35e503193991417788641b184f5b1e8c9a6d5ce8d", size = 16475713, upload-time = "2025-07-10T19:15:45.452Z" }, + { url = "https://files.pythonhosted.org/packages/5d/54/7139d463bb0a312890c9a5db87d7815d4a8cce9e6f5f28d04f0b55fcb160/onnxruntime-1.22.1-cp312-cp312-win_amd64.whl", hash = "sha256:6a64291d57ea966a245f749eb970f4fa05a64d26672e05a83fdb5db6b7d62f87", size = 12690910, upload-time = "2025-07-10T19:15:47.478Z" }, + { url = "https://files.pythonhosted.org/packages/e0/39/77cefa829740bd830915095d8408dce6d731b244e24b1f64fe3df9f18e86/onnxruntime-1.22.1-cp313-cp313-macosx_13_0_universal2.whl", hash = "sha256:d29c7d87b6cbed8fecfd09dca471832384d12a69e1ab873e5effbb94adc3e966", size = 34342026, upload-time = "2025-07-10T19:15:50.266Z" }, + { url = "https://files.pythonhosted.org/packages/d2/a6/444291524cb52875b5de980a6e918072514df63a57a7120bf9dfae3aeed1/onnxruntime-1.22.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:460487d83b7056ba98f1f7bac80287224c31d8149b15712b0d6f5078fcc33d0f", size = 14474014, upload-time = "2025-07-10T19:15:53.991Z" }, + { url = "https://files.pythonhosted.org/packages/87/9d/45a995437879c18beff26eacc2322f4227224d04c6ac3254dce2e8950190/onnxruntime-1.22.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b0c37070268ba4e02a1a9d28560cd00cd1e94f0d4f275cbef283854f861a65fa", size = 16475427, upload-time = "2025-07-10T19:15:56.067Z" }, + { url = "https://files.pythonhosted.org/packages/4c/06/9c765e66ad32a7e709ce4cb6b95d7eaa9cb4d92a6e11ea97c20ffecaf765/onnxruntime-1.22.1-cp313-cp313-win_amd64.whl", hash = "sha256:70980d729145a36a05f74b573435531f55ef9503bcda81fc6c3d6b9306199982", size = 12690841, upload-time = "2025-07-10T19:15:58.337Z" }, + { url = "https://files.pythonhosted.org/packages/52/8c/02af24ee1c8dce4e6c14a1642a7a56cebe323d2fa01d9a360a638f7e4b75/onnxruntime-1.22.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:33a7980bbc4b7f446bac26c3785652fe8730ed02617d765399e89ac7d44e0f7d", size = 14479333, upload-time = "2025-07-10T19:16:00.544Z" }, + { url = "https://files.pythonhosted.org/packages/5d/15/d75fd66aba116ce3732bb1050401394c5ec52074c4f7ee18db8838dd4667/onnxruntime-1.22.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e7e823624b015ea879d976cbef8bfaed2f7e2cc233d7506860a76dd37f8f381", size = 16477261, upload-time = "2025-07-10T19:16:03.226Z" }, +] + +[[package]] +name = "onshape-to-robot" +version = "1.7.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama" }, + { name = "commentjson" }, + { name = "numpy" }, + { name = "numpy-stl" }, + { name = "python-dotenv" }, + { name = "requests" }, + { name = "transforms3d" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c6/99/be262505d2a41a0bb7267c3037b4a4ed58460cf0f84408aa23f998d02c7f/onshape_to_robot-1.7.6.tar.gz", hash = "sha256:71aa33710f58ddba566fd6f2b17b02506794eb9c5032f4adbae34ce7cf168780", size = 48227, upload-time = "2025-06-17T22:09:32.856Z" } + +[[package]] +name = "opencv-python" +version = "4.12.0.88" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/71/25c98e634b6bdeca4727c7f6d6927b056080668c5008ad3c8fc9e7f8f6ec/opencv-python-4.12.0.88.tar.gz", hash = "sha256:8b738389cede219405f6f3880b851efa3415ccd674752219377353f017d2994d", size = 95373294, upload-time = "2025-07-07T09:20:52.389Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/68/3da40142e7c21e9b1d4e7ddd6c58738feb013203e6e4b803d62cdd9eb96b/opencv_python-4.12.0.88-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:f9a1f08883257b95a5764bf517a32d75aec325319c8ed0f89739a57fae9e92a5", size = 37877727, upload-time = "2025-07-07T09:13:31.47Z" }, + { url = "https://files.pythonhosted.org/packages/33/7c/042abe49f58d6ee7e1028eefc3334d98ca69b030e3b567fe245a2b28ea6f/opencv_python-4.12.0.88-cp37-abi3-macosx_13_0_x86_64.whl", hash = "sha256:812eb116ad2b4de43ee116fcd8991c3a687f099ada0b04e68f64899c09448e81", size = 57326471, upload-time = "2025-07-07T09:13:41.26Z" }, + { url = "https://files.pythonhosted.org/packages/62/3a/440bd64736cf8116f01f3b7f9f2e111afb2e02beb2ccc08a6458114a6b5d/opencv_python-4.12.0.88-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:51fd981c7df6af3e8f70b1556696b05224c4e6b6777bdd2a46b3d4fb09de1a92", size = 45887139, upload-time = "2025-07-07T09:13:50.761Z" }, + { url = "https://files.pythonhosted.org/packages/68/1f/795e7f4aa2eacc59afa4fb61a2e35e510d06414dd5a802b51a012d691b37/opencv_python-4.12.0.88-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:092c16da4c5a163a818f120c22c5e4a2f96e0db4f24e659c701f1fe629a690f9", size = 67041680, upload-time = "2025-07-07T09:14:01.995Z" }, + { url = "https://files.pythonhosted.org/packages/02/96/213fea371d3cb2f1d537612a105792aa0a6659fb2665b22cad709a75bd94/opencv_python-4.12.0.88-cp37-abi3-win32.whl", hash = "sha256:ff554d3f725b39878ac6a2e1fa232ec509c36130927afc18a1719ebf4fbf4357", size = 30284131, upload-time = "2025-07-07T09:14:08.819Z" }, + { url = "https://files.pythonhosted.org/packages/fa/80/eb88edc2e2b11cd2dd2e56f1c80b5784d11d6e6b7f04a1145df64df40065/opencv_python-4.12.0.88-cp37-abi3-win_amd64.whl", hash = "sha256:d98edb20aa932fd8ebd276a72627dad9dc097695b3d435a4257557bbb49a79d2", size = 39000307, upload-time = "2025-07-07T09:14:16.641Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "parso" +version = "0.8.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d4/de/53e0bcf53d13e005bd8c92e7855142494f41171b34c2536b86187474184d/parso-0.8.5.tar.gz", hash = "sha256:034d7354a9a018bdce352f48b2a8a450f05e9d6ee85db84764e9b6bd96dafe5a", size = 401205, upload-time = "2025-08-23T15:15:28.028Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl", hash = "sha256:646204b5ee239c396d040b90f9e272e9a8017c630092bf59980beb62fd033887", size = 106668, upload-time = "2025-08-23T15:15:25.663Z" }, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, +] + +[[package]] +name = "pexpect" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ptyprocess" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772, upload-time = "2023-11-25T06:56:14.81Z" }, +] + +[[package]] +name = "pillow" +version = "11.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/d0d6dea55cd152ce3d6767bb38a8fc10e33796ba4ba210cbab9354b6d238/pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523", size = 47113069, upload-time = "2025-07-01T09:16:30.666Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4c/5d/45a3553a253ac8763f3561371432a90bdbe6000fbdcf1397ffe502aa206c/pillow-11.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1b9c17fd4ace828b3003dfd1e30bff24863e0eb59b535e8f80194d9cc7ecf860", size = 5316554, upload-time = "2025-07-01T09:13:39.342Z" }, + { url = "https://files.pythonhosted.org/packages/7c/c8/67c12ab069ef586a25a4a79ced553586748fad100c77c0ce59bb4983ac98/pillow-11.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:65dc69160114cdd0ca0f35cb434633c75e8e7fad4cf855177a05bf38678f73ad", size = 4686548, upload-time = "2025-07-01T09:13:41.835Z" }, + { url = "https://files.pythonhosted.org/packages/2f/bd/6741ebd56263390b382ae4c5de02979af7f8bd9807346d068700dd6d5cf9/pillow-11.3.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7107195ddc914f656c7fc8e4a5e1c25f32e9236ea3ea860f257b0436011fddd0", size = 5859742, upload-time = "2025-07-03T13:09:47.439Z" }, + { url = "https://files.pythonhosted.org/packages/ca/0b/c412a9e27e1e6a829e6ab6c2dca52dd563efbedf4c9c6aa453d9a9b77359/pillow-11.3.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc3e831b563b3114baac7ec2ee86819eb03caa1a2cef0b481a5675b59c4fe23b", size = 7633087, upload-time = "2025-07-03T13:09:51.796Z" }, + { url = "https://files.pythonhosted.org/packages/59/9d/9b7076aaf30f5dd17e5e5589b2d2f5a5d7e30ff67a171eb686e4eecc2adf/pillow-11.3.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f1f182ebd2303acf8c380a54f615ec883322593320a9b00438eb842c1f37ae50", size = 5963350, upload-time = "2025-07-01T09:13:43.865Z" }, + { url = "https://files.pythonhosted.org/packages/f0/16/1a6bf01fb622fb9cf5c91683823f073f053005c849b1f52ed613afcf8dae/pillow-11.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4445fa62e15936a028672fd48c4c11a66d641d2c05726c7ec1f8ba6a572036ae", size = 6631840, upload-time = "2025-07-01T09:13:46.161Z" }, + { url = "https://files.pythonhosted.org/packages/7b/e6/6ff7077077eb47fde78739e7d570bdcd7c10495666b6afcd23ab56b19a43/pillow-11.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:71f511f6b3b91dd543282477be45a033e4845a40278fa8dcdbfdb07109bf18f9", size = 6074005, upload-time = "2025-07-01T09:13:47.829Z" }, + { url = "https://files.pythonhosted.org/packages/c3/3a/b13f36832ea6d279a697231658199e0a03cd87ef12048016bdcc84131601/pillow-11.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:040a5b691b0713e1f6cbe222e0f4f74cd233421e105850ae3b3c0ceda520f42e", size = 6708372, upload-time = "2025-07-01T09:13:52.145Z" }, + { url = "https://files.pythonhosted.org/packages/6c/e4/61b2e1a7528740efbc70b3d581f33937e38e98ef3d50b05007267a55bcb2/pillow-11.3.0-cp310-cp310-win32.whl", hash = "sha256:89bd777bc6624fe4115e9fac3352c79ed60f3bb18651420635f26e643e3dd1f6", size = 6277090, upload-time = "2025-07-01T09:13:53.915Z" }, + { url = "https://files.pythonhosted.org/packages/a9/d3/60c781c83a785d6afbd6a326ed4d759d141de43aa7365725cbcd65ce5e54/pillow-11.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:19d2ff547c75b8e3ff46f4d9ef969a06c30ab2d4263a9e287733aa8b2429ce8f", size = 6985988, upload-time = "2025-07-01T09:13:55.699Z" }, + { url = "https://files.pythonhosted.org/packages/9f/28/4f4a0203165eefb3763939c6789ba31013a2e90adffb456610f30f613850/pillow-11.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:819931d25e57b513242859ce1876c58c59dc31587847bf74cfe06b2e0cb22d2f", size = 2422899, upload-time = "2025-07-01T09:13:57.497Z" }, + { url = "https://files.pythonhosted.org/packages/db/26/77f8ed17ca4ffd60e1dcd220a6ec6d71210ba398cfa33a13a1cd614c5613/pillow-11.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1cd110edf822773368b396281a2293aeb91c90a2db00d78ea43e7e861631b722", size = 5316531, upload-time = "2025-07-01T09:13:59.203Z" }, + { url = "https://files.pythonhosted.org/packages/cb/39/ee475903197ce709322a17a866892efb560f57900d9af2e55f86db51b0a5/pillow-11.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c412fddd1b77a75aa904615ebaa6001f169b26fd467b4be93aded278266b288", size = 4686560, upload-time = "2025-07-01T09:14:01.101Z" }, + { url = "https://files.pythonhosted.org/packages/d5/90/442068a160fd179938ba55ec8c97050a612426fae5ec0a764e345839f76d/pillow-11.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1aa4de119a0ecac0a34a9c8bde33f34022e2e8f99104e47a3ca392fd60e37d", size = 5870978, upload-time = "2025-07-03T13:09:55.638Z" }, + { url = "https://files.pythonhosted.org/packages/13/92/dcdd147ab02daf405387f0218dcf792dc6dd5b14d2573d40b4caeef01059/pillow-11.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:91da1d88226663594e3f6b4b8c3c8d85bd504117d043740a8e0ec449087cc494", size = 7641168, upload-time = "2025-07-03T13:10:00.37Z" }, + { url = "https://files.pythonhosted.org/packages/6e/db/839d6ba7fd38b51af641aa904e2960e7a5644d60ec754c046b7d2aee00e5/pillow-11.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:643f189248837533073c405ec2f0bb250ba54598cf80e8c1e043381a60632f58", size = 5973053, upload-time = "2025-07-01T09:14:04.491Z" }, + { url = "https://files.pythonhosted.org/packages/f2/2f/d7675ecae6c43e9f12aa8d58b6012683b20b6edfbdac7abcb4e6af7a3784/pillow-11.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:106064daa23a745510dabce1d84f29137a37224831d88eb4ce94bb187b1d7e5f", size = 6640273, upload-time = "2025-07-01T09:14:06.235Z" }, + { url = "https://files.pythonhosted.org/packages/45/ad/931694675ede172e15b2ff03c8144a0ddaea1d87adb72bb07655eaffb654/pillow-11.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd8ff254faf15591e724dc7c4ddb6bf4793efcbe13802a4ae3e863cd300b493e", size = 6082043, upload-time = "2025-07-01T09:14:07.978Z" }, + { url = "https://files.pythonhosted.org/packages/3a/04/ba8f2b11fc80d2dd462d7abec16351b45ec99cbbaea4387648a44190351a/pillow-11.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:932c754c2d51ad2b2271fd01c3d121daaa35e27efae2a616f77bf164bc0b3e94", size = 6715516, upload-time = "2025-07-01T09:14:10.233Z" }, + { url = "https://files.pythonhosted.org/packages/48/59/8cd06d7f3944cc7d892e8533c56b0acb68399f640786313275faec1e3b6f/pillow-11.3.0-cp311-cp311-win32.whl", hash = "sha256:b4b8f3efc8d530a1544e5962bd6b403d5f7fe8b9e08227c6b255f98ad82b4ba0", size = 6274768, upload-time = "2025-07-01T09:14:11.921Z" }, + { url = "https://files.pythonhosted.org/packages/f1/cc/29c0f5d64ab8eae20f3232da8f8571660aa0ab4b8f1331da5c2f5f9a938e/pillow-11.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:1a992e86b0dd7aeb1f053cd506508c0999d710a8f07b4c791c63843fc6a807ac", size = 6986055, upload-time = "2025-07-01T09:14:13.623Z" }, + { url = "https://files.pythonhosted.org/packages/c6/df/90bd886fabd544c25addd63e5ca6932c86f2b701d5da6c7839387a076b4a/pillow-11.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:30807c931ff7c095620fe04448e2c2fc673fcbb1ffe2a7da3fb39613489b1ddd", size = 2423079, upload-time = "2025-07-01T09:14:15.268Z" }, + { url = "https://files.pythonhosted.org/packages/40/fe/1bc9b3ee13f68487a99ac9529968035cca2f0a51ec36892060edcc51d06a/pillow-11.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdae223722da47b024b867c1ea0be64e0df702c5e0a60e27daad39bf960dd1e4", size = 5278800, upload-time = "2025-07-01T09:14:17.648Z" }, + { url = "https://files.pythonhosted.org/packages/2c/32/7e2ac19b5713657384cec55f89065fb306b06af008cfd87e572035b27119/pillow-11.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:921bd305b10e82b4d1f5e802b6850677f965d8394203d182f078873851dada69", size = 4686296, upload-time = "2025-07-01T09:14:19.828Z" }, + { url = "https://files.pythonhosted.org/packages/8e/1e/b9e12bbe6e4c2220effebc09ea0923a07a6da1e1f1bfbc8d7d29a01ce32b/pillow-11.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb76541cba2f958032d79d143b98a3a6b3ea87f0959bbe256c0b5e416599fd5d", size = 5871726, upload-time = "2025-07-03T13:10:04.448Z" }, + { url = "https://files.pythonhosted.org/packages/8d/33/e9200d2bd7ba00dc3ddb78df1198a6e80d7669cce6c2bdbeb2530a74ec58/pillow-11.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:67172f2944ebba3d4a7b54f2e95c786a3a50c21b88456329314caaa28cda70f6", size = 7644652, upload-time = "2025-07-03T13:10:10.391Z" }, + { url = "https://files.pythonhosted.org/packages/41/f1/6f2427a26fc683e00d985bc391bdd76d8dd4e92fac33d841127eb8fb2313/pillow-11.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f07ed9f56a3b9b5f49d3661dc9607484e85c67e27f3e8be2c7d28ca032fec7", size = 5977787, upload-time = "2025-07-01T09:14:21.63Z" }, + { url = "https://files.pythonhosted.org/packages/e4/c9/06dd4a38974e24f932ff5f98ea3c546ce3f8c995d3f0985f8e5ba48bba19/pillow-11.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:676b2815362456b5b3216b4fd5bd89d362100dc6f4945154ff172e206a22c024", size = 6645236, upload-time = "2025-07-01T09:14:23.321Z" }, + { url = "https://files.pythonhosted.org/packages/40/e7/848f69fb79843b3d91241bad658e9c14f39a32f71a301bcd1d139416d1be/pillow-11.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3e184b2f26ff146363dd07bde8b711833d7b0202e27d13540bfe2e35a323a809", size = 6086950, upload-time = "2025-07-01T09:14:25.237Z" }, + { url = "https://files.pythonhosted.org/packages/0b/1a/7cff92e695a2a29ac1958c2a0fe4c0b2393b60aac13b04a4fe2735cad52d/pillow-11.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6be31e3fc9a621e071bc17bb7de63b85cbe0bfae91bb0363c893cbe67247780d", size = 6723358, upload-time = "2025-07-01T09:14:27.053Z" }, + { url = "https://files.pythonhosted.org/packages/26/7d/73699ad77895f69edff76b0f332acc3d497f22f5d75e5360f78cbcaff248/pillow-11.3.0-cp312-cp312-win32.whl", hash = "sha256:7b161756381f0918e05e7cb8a371fff367e807770f8fe92ecb20d905d0e1c149", size = 6275079, upload-time = "2025-07-01T09:14:30.104Z" }, + { url = "https://files.pythonhosted.org/packages/8c/ce/e7dfc873bdd9828f3b6e5c2bbb74e47a98ec23cc5c74fc4e54462f0d9204/pillow-11.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a6444696fce635783440b7f7a9fc24b3ad10a9ea3f0ab66c5905be1c19ccf17d", size = 6986324, upload-time = "2025-07-01T09:14:31.899Z" }, + { url = "https://files.pythonhosted.org/packages/16/8f/b13447d1bf0b1f7467ce7d86f6e6edf66c0ad7cf44cf5c87a37f9bed9936/pillow-11.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:2aceea54f957dd4448264f9bf40875da0415c83eb85f55069d89c0ed436e3542", size = 2423067, upload-time = "2025-07-01T09:14:33.709Z" }, + { url = "https://files.pythonhosted.org/packages/1e/93/0952f2ed8db3a5a4c7a11f91965d6184ebc8cd7cbb7941a260d5f018cd2d/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:1c627742b539bba4309df89171356fcb3cc5a9178355b2727d1b74a6cf155fbd", size = 2128328, upload-time = "2025-07-01T09:14:35.276Z" }, + { url = "https://files.pythonhosted.org/packages/4b/e8/100c3d114b1a0bf4042f27e0f87d2f25e857e838034e98ca98fe7b8c0a9c/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:30b7c02f3899d10f13d7a48163c8969e4e653f8b43416d23d13d1bbfdc93b9f8", size = 2170652, upload-time = "2025-07-01T09:14:37.203Z" }, + { url = "https://files.pythonhosted.org/packages/aa/86/3f758a28a6e381758545f7cdb4942e1cb79abd271bea932998fc0db93cb6/pillow-11.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7859a4cc7c9295f5838015d8cc0a9c215b77e43d07a25e460f35cf516df8626f", size = 2227443, upload-time = "2025-07-01T09:14:39.344Z" }, + { url = "https://files.pythonhosted.org/packages/01/f4/91d5b3ffa718df2f53b0dc109877993e511f4fd055d7e9508682e8aba092/pillow-11.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec1ee50470b0d050984394423d96325b744d55c701a439d2bd66089bff963d3c", size = 5278474, upload-time = "2025-07-01T09:14:41.843Z" }, + { url = "https://files.pythonhosted.org/packages/f9/0e/37d7d3eca6c879fbd9dba21268427dffda1ab00d4eb05b32923d4fbe3b12/pillow-11.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7db51d222548ccfd274e4572fdbf3e810a5e66b00608862f947b163e613b67dd", size = 4686038, upload-time = "2025-07-01T09:14:44.008Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b0/3426e5c7f6565e752d81221af9d3676fdbb4f352317ceafd42899aaf5d8a/pillow-11.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2d6fcc902a24ac74495df63faad1884282239265c6839a0a6416d33faedfae7e", size = 5864407, upload-time = "2025-07-03T13:10:15.628Z" }, + { url = "https://files.pythonhosted.org/packages/fc/c1/c6c423134229f2a221ee53f838d4be9d82bab86f7e2f8e75e47b6bf6cd77/pillow-11.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f0f5d8f4a08090c6d6d578351a2b91acf519a54986c055af27e7a93feae6d3f1", size = 7639094, upload-time = "2025-07-03T13:10:21.857Z" }, + { url = "https://files.pythonhosted.org/packages/ba/c9/09e6746630fe6372c67c648ff9deae52a2bc20897d51fa293571977ceb5d/pillow-11.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c37d8ba9411d6003bba9e518db0db0c58a680ab9fe5179f040b0463644bc9805", size = 5973503, upload-time = "2025-07-01T09:14:45.698Z" }, + { url = "https://files.pythonhosted.org/packages/d5/1c/a2a29649c0b1983d3ef57ee87a66487fdeb45132df66ab30dd37f7dbe162/pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13f87d581e71d9189ab21fe0efb5a23e9f28552d5be6979e84001d3b8505abe8", size = 6642574, upload-time = "2025-07-01T09:14:47.415Z" }, + { url = "https://files.pythonhosted.org/packages/36/de/d5cc31cc4b055b6c6fd990e3e7f0f8aaf36229a2698501bcb0cdf67c7146/pillow-11.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:023f6d2d11784a465f09fd09a34b150ea4672e85fb3d05931d89f373ab14abb2", size = 6084060, upload-time = "2025-07-01T09:14:49.636Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ea/502d938cbaeec836ac28a9b730193716f0114c41325db428e6b280513f09/pillow-11.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:45dfc51ac5975b938e9809451c51734124e73b04d0f0ac621649821a63852e7b", size = 6721407, upload-time = "2025-07-01T09:14:51.962Z" }, + { url = "https://files.pythonhosted.org/packages/45/9c/9c5e2a73f125f6cbc59cc7087c8f2d649a7ae453f83bd0362ff7c9e2aee2/pillow-11.3.0-cp313-cp313-win32.whl", hash = "sha256:a4d336baed65d50d37b88ca5b60c0fa9d81e3a87d4a7930d3880d1624d5b31f3", size = 6273841, upload-time = "2025-07-01T09:14:54.142Z" }, + { url = "https://files.pythonhosted.org/packages/23/85/397c73524e0cd212067e0c969aa245b01d50183439550d24d9f55781b776/pillow-11.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bce5c4fd0921f99d2e858dc4d4d64193407e1b99478bc5cacecba2311abde51", size = 6978450, upload-time = "2025-07-01T09:14:56.436Z" }, + { url = "https://files.pythonhosted.org/packages/17/d2/622f4547f69cd173955194b78e4d19ca4935a1b0f03a302d655c9f6aae65/pillow-11.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:1904e1264881f682f02b7f8167935cce37bc97db457f8e7849dc3a6a52b99580", size = 2423055, upload-time = "2025-07-01T09:14:58.072Z" }, + { url = "https://files.pythonhosted.org/packages/dd/80/a8a2ac21dda2e82480852978416cfacd439a4b490a501a288ecf4fe2532d/pillow-11.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4c834a3921375c48ee6b9624061076bc0a32a60b5532b322cc0ea64e639dd50e", size = 5281110, upload-time = "2025-07-01T09:14:59.79Z" }, + { url = "https://files.pythonhosted.org/packages/44/d6/b79754ca790f315918732e18f82a8146d33bcd7f4494380457ea89eb883d/pillow-11.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e05688ccef30ea69b9317a9ead994b93975104a677a36a8ed8106be9260aa6d", size = 4689547, upload-time = "2025-07-01T09:15:01.648Z" }, + { url = "https://files.pythonhosted.org/packages/49/20/716b8717d331150cb00f7fdd78169c01e8e0c219732a78b0e59b6bdb2fd6/pillow-11.3.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1019b04af07fc0163e2810167918cb5add8d74674b6267616021ab558dc98ced", size = 5901554, upload-time = "2025-07-03T13:10:27.018Z" }, + { url = "https://files.pythonhosted.org/packages/74/cf/a9f3a2514a65bb071075063a96f0a5cf949c2f2fce683c15ccc83b1c1cab/pillow-11.3.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f944255db153ebb2b19c51fe85dd99ef0ce494123f21b9db4877ffdfc5590c7c", size = 7669132, upload-time = "2025-07-03T13:10:33.01Z" }, + { url = "https://files.pythonhosted.org/packages/98/3c/da78805cbdbee9cb43efe8261dd7cc0b4b93f2ac79b676c03159e9db2187/pillow-11.3.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f85acb69adf2aaee8b7da124efebbdb959a104db34d3a2cb0f3793dbae422a8", size = 6005001, upload-time = "2025-07-01T09:15:03.365Z" }, + { url = "https://files.pythonhosted.org/packages/6c/fa/ce044b91faecf30e635321351bba32bab5a7e034c60187fe9698191aef4f/pillow-11.3.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05f6ecbeff5005399bb48d198f098a9b4b6bdf27b8487c7f38ca16eeb070cd59", size = 6668814, upload-time = "2025-07-01T09:15:05.655Z" }, + { url = "https://files.pythonhosted.org/packages/7b/51/90f9291406d09bf93686434f9183aba27b831c10c87746ff49f127ee80cb/pillow-11.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a7bc6e6fd0395bc052f16b1a8670859964dbd7003bd0af2ff08342eb6e442cfe", size = 6113124, upload-time = "2025-07-01T09:15:07.358Z" }, + { url = "https://files.pythonhosted.org/packages/cd/5a/6fec59b1dfb619234f7636d4157d11fb4e196caeee220232a8d2ec48488d/pillow-11.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:83e1b0161c9d148125083a35c1c5a89db5b7054834fd4387499e06552035236c", size = 6747186, upload-time = "2025-07-01T09:15:09.317Z" }, + { url = "https://files.pythonhosted.org/packages/49/6b/00187a044f98255225f172de653941e61da37104a9ea60e4f6887717e2b5/pillow-11.3.0-cp313-cp313t-win32.whl", hash = "sha256:2a3117c06b8fb646639dce83694f2f9eac405472713fcb1ae887469c0d4f6788", size = 6277546, upload-time = "2025-07-01T09:15:11.311Z" }, + { url = "https://files.pythonhosted.org/packages/e8/5c/6caaba7e261c0d75bab23be79f1d06b5ad2a2ae49f028ccec801b0e853d6/pillow-11.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:857844335c95bea93fb39e0fa2726b4d9d758850b34075a7e3ff4f4fa3aa3b31", size = 6985102, upload-time = "2025-07-01T09:15:13.164Z" }, + { url = "https://files.pythonhosted.org/packages/f3/7e/b623008460c09a0cb38263c93b828c666493caee2eb34ff67f778b87e58c/pillow-11.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:8797edc41f3e8536ae4b10897ee2f637235c94f27404cac7297f7b607dd0716e", size = 2424803, upload-time = "2025-07-01T09:15:15.695Z" }, + { url = "https://files.pythonhosted.org/packages/73/f4/04905af42837292ed86cb1b1dabe03dce1edc008ef14c473c5c7e1443c5d/pillow-11.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d9da3df5f9ea2a89b81bb6087177fb1f4d1c7146d583a3fe5c672c0d94e55e12", size = 5278520, upload-time = "2025-07-01T09:15:17.429Z" }, + { url = "https://files.pythonhosted.org/packages/41/b0/33d79e377a336247df6348a54e6d2a2b85d644ca202555e3faa0cf811ecc/pillow-11.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0b275ff9b04df7b640c59ec5a3cb113eefd3795a8df80bac69646ef699c6981a", size = 4686116, upload-time = "2025-07-01T09:15:19.423Z" }, + { url = "https://files.pythonhosted.org/packages/49/2d/ed8bc0ab219ae8768f529597d9509d184fe8a6c4741a6864fea334d25f3f/pillow-11.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0743841cabd3dba6a83f38a92672cccbd69af56e3e91777b0ee7f4dba4385632", size = 5864597, upload-time = "2025-07-03T13:10:38.404Z" }, + { url = "https://files.pythonhosted.org/packages/b5/3d/b932bb4225c80b58dfadaca9d42d08d0b7064d2d1791b6a237f87f661834/pillow-11.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2465a69cf967b8b49ee1b96d76718cd98c4e925414ead59fdf75cf0fd07df673", size = 7638246, upload-time = "2025-07-03T13:10:44.987Z" }, + { url = "https://files.pythonhosted.org/packages/09/b5/0487044b7c096f1b48f0d7ad416472c02e0e4bf6919541b111efd3cae690/pillow-11.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41742638139424703b4d01665b807c6468e23e699e8e90cffefe291c5832b027", size = 5973336, upload-time = "2025-07-01T09:15:21.237Z" }, + { url = "https://files.pythonhosted.org/packages/a8/2d/524f9318f6cbfcc79fbc004801ea6b607ec3f843977652fdee4857a7568b/pillow-11.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93efb0b4de7e340d99057415c749175e24c8864302369e05914682ba642e5d77", size = 6642699, upload-time = "2025-07-01T09:15:23.186Z" }, + { url = "https://files.pythonhosted.org/packages/6f/d2/a9a4f280c6aefedce1e8f615baaa5474e0701d86dd6f1dede66726462bbd/pillow-11.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7966e38dcd0fa11ca390aed7c6f20454443581d758242023cf36fcb319b1a874", size = 6083789, upload-time = "2025-07-01T09:15:25.1Z" }, + { url = "https://files.pythonhosted.org/packages/fe/54/86b0cd9dbb683a9d5e960b66c7379e821a19be4ac5810e2e5a715c09a0c0/pillow-11.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:98a9afa7b9007c67ed84c57c9e0ad86a6000da96eaa638e4f8abe5b65ff83f0a", size = 6720386, upload-time = "2025-07-01T09:15:27.378Z" }, + { url = "https://files.pythonhosted.org/packages/e7/95/88efcaf384c3588e24259c4203b909cbe3e3c2d887af9e938c2022c9dd48/pillow-11.3.0-cp314-cp314-win32.whl", hash = "sha256:02a723e6bf909e7cea0dac1b0e0310be9d7650cd66222a5f1c571455c0a45214", size = 6370911, upload-time = "2025-07-01T09:15:29.294Z" }, + { url = "https://files.pythonhosted.org/packages/2e/cc/934e5820850ec5eb107e7b1a72dd278140731c669f396110ebc326f2a503/pillow-11.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:a418486160228f64dd9e9efcd132679b7a02a5f22c982c78b6fc7dab3fefb635", size = 7117383, upload-time = "2025-07-01T09:15:31.128Z" }, + { url = "https://files.pythonhosted.org/packages/d6/e9/9c0a616a71da2a5d163aa37405e8aced9a906d574b4a214bede134e731bc/pillow-11.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:155658efb5e044669c08896c0c44231c5e9abcaadbc5cd3648df2f7c0b96b9a6", size = 2511385, upload-time = "2025-07-01T09:15:33.328Z" }, + { url = "https://files.pythonhosted.org/packages/1a/33/c88376898aff369658b225262cd4f2659b13e8178e7534df9e6e1fa289f6/pillow-11.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:59a03cdf019efbfeeed910bf79c7c93255c3d54bc45898ac2a4140071b02b4ae", size = 5281129, upload-time = "2025-07-01T09:15:35.194Z" }, + { url = "https://files.pythonhosted.org/packages/1f/70/d376247fb36f1844b42910911c83a02d5544ebd2a8bad9efcc0f707ea774/pillow-11.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f8a5827f84d973d8636e9dc5764af4f0cf2318d26744b3d902931701b0d46653", size = 4689580, upload-time = "2025-07-01T09:15:37.114Z" }, + { url = "https://files.pythonhosted.org/packages/eb/1c/537e930496149fbac69efd2fc4329035bbe2e5475b4165439e3be9cb183b/pillow-11.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ee92f2fd10f4adc4b43d07ec5e779932b4eb3dbfbc34790ada5a6669bc095aa6", size = 5902860, upload-time = "2025-07-03T13:10:50.248Z" }, + { url = "https://files.pythonhosted.org/packages/bd/57/80f53264954dcefeebcf9dae6e3eb1daea1b488f0be8b8fef12f79a3eb10/pillow-11.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c96d333dcf42d01f47b37e0979b6bd73ec91eae18614864622d9b87bbd5bbf36", size = 7670694, upload-time = "2025-07-03T13:10:56.432Z" }, + { url = "https://files.pythonhosted.org/packages/70/ff/4727d3b71a8578b4587d9c276e90efad2d6fe0335fd76742a6da08132e8c/pillow-11.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c96f993ab8c98460cd0c001447bff6194403e8b1d7e149ade5f00594918128b", size = 6005888, upload-time = "2025-07-01T09:15:39.436Z" }, + { url = "https://files.pythonhosted.org/packages/05/ae/716592277934f85d3be51d7256f3636672d7b1abfafdc42cf3f8cbd4b4c8/pillow-11.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41342b64afeba938edb034d122b2dda5db2139b9a4af999729ba8818e0056477", size = 6670330, upload-time = "2025-07-01T09:15:41.269Z" }, + { url = "https://files.pythonhosted.org/packages/e7/bb/7fe6cddcc8827b01b1a9766f5fdeb7418680744f9082035bdbabecf1d57f/pillow-11.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:068d9c39a2d1b358eb9f245ce7ab1b5c3246c7c8c7d9ba58cfa5b43146c06e50", size = 6114089, upload-time = "2025-07-01T09:15:43.13Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f5/06bfaa444c8e80f1a8e4bff98da9c83b37b5be3b1deaa43d27a0db37ef84/pillow-11.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a1bc6ba083b145187f648b667e05a2534ecc4b9f2784c2cbe3089e44868f2b9b", size = 6748206, upload-time = "2025-07-01T09:15:44.937Z" }, + { url = "https://files.pythonhosted.org/packages/f0/77/bc6f92a3e8e6e46c0ca78abfffec0037845800ea38c73483760362804c41/pillow-11.3.0-cp314-cp314t-win32.whl", hash = "sha256:118ca10c0d60b06d006be10a501fd6bbdfef559251ed31b794668ed569c87e12", size = 6377370, upload-time = "2025-07-01T09:15:46.673Z" }, + { url = "https://files.pythonhosted.org/packages/4a/82/3a721f7d69dca802befb8af08b7c79ebcab461007ce1c18bd91a5d5896f9/pillow-11.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8924748b688aa210d79883357d102cd64690e56b923a186f35a82cbc10f997db", size = 7121500, upload-time = "2025-07-01T09:15:48.512Z" }, + { url = "https://files.pythonhosted.org/packages/89/c7/5572fa4a3f45740eaab6ae86fcdf7195b55beac1371ac8c619d880cfe948/pillow-11.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:79ea0d14d3ebad43ec77ad5272e6ff9bba5b679ef73375ea760261207fa8e0aa", size = 2512835, upload-time = "2025-07-01T09:15:50.399Z" }, + { url = "https://files.pythonhosted.org/packages/6f/8b/209bd6b62ce8367f47e68a218bffac88888fdf2c9fcf1ecadc6c3ec1ebc7/pillow-11.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3cee80663f29e3843b68199b9d6f4f54bd1d4a6b59bdd91bceefc51238bcb967", size = 5270556, upload-time = "2025-07-01T09:16:09.961Z" }, + { url = "https://files.pythonhosted.org/packages/2e/e6/231a0b76070c2cfd9e260a7a5b504fb72da0a95279410fa7afd99d9751d6/pillow-11.3.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b5f56c3f344f2ccaf0dd875d3e180f631dc60a51b314295a3e681fe8cf851fbe", size = 4654625, upload-time = "2025-07-01T09:16:11.913Z" }, + { url = "https://files.pythonhosted.org/packages/13/f4/10cf94fda33cb12765f2397fc285fa6d8eb9c29de7f3185165b702fc7386/pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e67d793d180c9df62f1f40aee3accca4829d3794c95098887edc18af4b8b780c", size = 4874207, upload-time = "2025-07-03T13:11:10.201Z" }, + { url = "https://files.pythonhosted.org/packages/72/c9/583821097dc691880c92892e8e2d41fe0a5a3d6021f4963371d2f6d57250/pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d000f46e2917c705e9fb93a3606ee4a819d1e3aa7a9b442f6444f07e77cf5e25", size = 6583939, upload-time = "2025-07-03T13:11:15.68Z" }, + { url = "https://files.pythonhosted.org/packages/3b/8e/5c9d410f9217b12320efc7c413e72693f48468979a013ad17fd690397b9a/pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:527b37216b6ac3a12d7838dc3bd75208ec57c1c6d11ef01902266a5a0c14fc27", size = 4957166, upload-time = "2025-07-01T09:16:13.74Z" }, + { url = "https://files.pythonhosted.org/packages/62/bb/78347dbe13219991877ffb3a91bf09da8317fbfcd4b5f9140aeae020ad71/pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:be5463ac478b623b9dd3937afd7fb7ab3d79dd290a28e2b6df292dc75063eb8a", size = 5581482, upload-time = "2025-07-01T09:16:16.107Z" }, + { url = "https://files.pythonhosted.org/packages/d9/28/1000353d5e61498aaeaaf7f1e4b49ddb05f2c6575f9d4f9f914a3538b6e1/pillow-11.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:8dc70ca24c110503e16918a658b869019126ecfe03109b754c402daff12b3d9f", size = 6984596, upload-time = "2025-07-01T09:16:18.07Z" }, + { url = "https://files.pythonhosted.org/packages/9e/e3/6fa84033758276fb31da12e5fb66ad747ae83b93c67af17f8c6ff4cc8f34/pillow-11.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7c8ec7a017ad1bd562f93dbd8505763e688d388cde6e4a010ae1486916e713e6", size = 5270566, upload-time = "2025-07-01T09:16:19.801Z" }, + { url = "https://files.pythonhosted.org/packages/5b/ee/e8d2e1ab4892970b561e1ba96cbd59c0d28cf66737fc44abb2aec3795a4e/pillow-11.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9ab6ae226de48019caa8074894544af5b53a117ccb9d3b3dcb2871464c829438", size = 4654618, upload-time = "2025-07-01T09:16:21.818Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6d/17f80f4e1f0761f02160fc433abd4109fa1548dcfdca46cfdadaf9efa565/pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fe27fb049cdcca11f11a7bfda64043c37b30e6b91f10cb5bab275806c32f6ab3", size = 4874248, upload-time = "2025-07-03T13:11:20.738Z" }, + { url = "https://files.pythonhosted.org/packages/de/5f/c22340acd61cef960130585bbe2120e2fd8434c214802f07e8c03596b17e/pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:465b9e8844e3c3519a983d58b80be3f668e2a7a5db97f2784e7079fbc9f9822c", size = 6583963, upload-time = "2025-07-03T13:11:26.283Z" }, + { url = "https://files.pythonhosted.org/packages/31/5e/03966aedfbfcbb4d5f8aa042452d3361f325b963ebbadddac05b122e47dd/pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5418b53c0d59b3824d05e029669efa023bbef0f3e92e75ec8428f3799487f361", size = 4957170, upload-time = "2025-07-01T09:16:23.762Z" }, + { url = "https://files.pythonhosted.org/packages/cc/2d/e082982aacc927fc2cab48e1e731bdb1643a1406acace8bed0900a61464e/pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:504b6f59505f08ae014f724b6207ff6222662aab5cc9542577fb084ed0676ac7", size = 5581505, upload-time = "2025-07-01T09:16:25.593Z" }, + { url = "https://files.pythonhosted.org/packages/34/e7/ae39f538fd6844e982063c3a5e4598b8ced43b9633baa3a85ef33af8c05c/pillow-11.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c84d689db21a1c397d001aa08241044aa2069e7587b398c8cc63020390b1c1b8", size = 6984598, upload-time = "2025-07-01T09:16:27.732Z" }, +] + +[[package]] +name = "pin" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cmeel" }, + { name = "cmeel-boost" }, + { name = "cmeel-urdfdom" }, + { name = "coal-library" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4c/00/0e2b8344ea7af3185909ddc91876abe0274d5b1257caf191414732495c9d/pin-3.4.0.tar.gz", hash = "sha256:95c8e9c707dad9c412247874ca3f8ac77f0d5f51e49f02856f88146478e2660b", size = 99748596, upload-time = "2025-02-13T01:08:55.83Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/4f/29ec215bab0de9c5b017ad4ad81aca8ba2dbba9d7bcefb38ce18dd1871f6/pin-3.4.0-0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e3ea1fa363908c8ac9f541bd3c136cb02d720fe3e85760459bc0f9152a9018ea", size = 14580514, upload-time = "2025-02-13T01:07:18.987Z" }, + { url = "https://files.pythonhosted.org/packages/64/58/b04a646078c8fba139e8b33314e1b28e5b0e6f00844fee72d25933553d66/pin-3.4.0-0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1c5cc929c8c8849c20a00410426aad002fdf44af9467d077962d75332d2097ab", size = 13218379, upload-time = "2025-02-13T01:07:22.872Z" }, + { url = "https://files.pythonhosted.org/packages/03/fc/d15717bc94a92a561b18834e392b1dced81420dfaf08f197fa6cb8eba205/pin-3.4.0-0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:ace2c10302fccd3d3e9dc7214b6b9e307cb88df84ebe365e46462e71ef3857ae", size = 17434017, upload-time = "2025-02-13T01:07:26.018Z" }, + { url = "https://files.pythonhosted.org/packages/08/89/bba3b9277ab96dc922f0b901befde0e53392e914754942d5ce1a1ef53d97/pin-3.4.0-0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:f0feb5a2325e9156e790d4f39c55cab812937e87c6d73a18f027d4afccc4b997", size = 17813130, upload-time = "2025-02-13T01:07:32.019Z" }, + { url = "https://files.pythonhosted.org/packages/17/7b/5e4e451a5c1106a651009cecaa469408bfec5962be9890aef8a4c6f8efdf/pin-3.4.0-0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:07f06f104c503aa72c134eac308c07ecc704c5f47dc5e4c6edaa31f9b42e5a28", size = 14580686, upload-time = "2025-02-13T01:07:36.383Z" }, + { url = "https://files.pythonhosted.org/packages/34/d7/549c1439d4ec7a505f6a4464743263868c3f2018daebc838af7976291d35/pin-3.4.0-0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:88e2fac5896c23f76e5d5fa49f023581baa9e703bfea26a0bb72750d07313d78", size = 13217948, upload-time = "2025-02-13T01:07:40.473Z" }, + { url = "https://files.pythonhosted.org/packages/18/8a/dc016fa16ae73d2d9fd3828bb6ee654479c155d644356523ffc0f0e2f749/pin-3.4.0-0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:b041f7ef2ef18a4be7cb228662cf0d7061edd2ddc5cf30b54f1344dc0c491d38", size = 17432538, upload-time = "2025-02-13T01:07:43.516Z" }, + { url = "https://files.pythonhosted.org/packages/12/a0/df562a0e531531fc13db2e18d1b71691fc5f8ae174e026b38b4c771f4dc4/pin-3.4.0-0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:8987a784028ade6145c0b5844a20accbb63bc8501f8d7260d67df6a0228678f1", size = 17812580, upload-time = "2025-02-13T01:07:47.452Z" }, + { url = "https://files.pythonhosted.org/packages/47/07/bd33ac55ed1ef54b0301c939d24b32dd3194a72bf3a6e3be2de815cba21e/pin-3.4.0-0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6a20fc936311039698cac062db89e48c5494c4953a989372b7981d741844324a", size = 14653652, upload-time = "2025-02-13T01:07:51.617Z" }, + { url = "https://files.pythonhosted.org/packages/e5/ab/ead22716b69fd6a703c7d81b325c3f20a1af44cdbf5d0e9295a7ffc8a5ba/pin-3.4.0-0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c5abd2c19a6bb63617aa951c722cb3bce7b633bca119a541baab1123a8d48c32", size = 13265144, upload-time = "2025-02-13T01:07:55.375Z" }, + { url = "https://files.pythonhosted.org/packages/59/af/c3ec76c2ba38ae4d419a091c34fd7976ea4f1ee3f36baf6242abb21a19e5/pin-3.4.0-0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:e0547ea1665e8477968a22791216bcd3b70cd0c1c0091d417564ffabd1bb435e", size = 17298971, upload-time = "2025-02-13T01:07:58.72Z" }, + { url = "https://files.pythonhosted.org/packages/db/a4/72ba496b491dc0892af2bb9c98bf615c6ff2ea9690a63f1154f906b960f3/pin-3.4.0-0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:37941641077550264ef8e0f8a529f368414296e27b9f649467e185ab05445960", size = 17711678, upload-time = "2025-02-13T01:08:02.631Z" }, + { url = "https://files.pythonhosted.org/packages/03/3b/1f7021c8c81098dcef3dafaee8f0eae25520a9ea5277d15dff58e55f9796/pin-3.4.0-0-cp313-cp313-macosx_10_9_x86_64.whl", hash = "sha256:964e055b147afb1705263c8ea1a9c574a2babd92d3b637f5255de4abbbdd4970", size = 14653665, upload-time = "2025-02-13T01:08:06.704Z" }, + { url = "https://files.pythonhosted.org/packages/0d/e2/7a05c42df807b2070682cc2c1e86dce2964e6ef443bb385fca943a9c63ef/pin-3.4.0-0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c35af977fb85878ad6281b9b2d06bcfbcf353cd8b1a0f97342de55f2dd3ab70f", size = 13265140, upload-time = "2025-02-13T01:08:09.674Z" }, + { url = "https://files.pythonhosted.org/packages/1b/02/022f77ea0433f7a7153a711681a8b5520ba453b5e56c904274df30540f59/pin-3.4.0-0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:ba80feba64744da94d41ef64e430aeaf358085647061b90b57641d0108326363", size = 17298972, upload-time = "2025-02-13T01:08:14.036Z" }, + { url = "https://files.pythonhosted.org/packages/2c/6a/87ffcfb152f24f8e8e3b0f2b4435f23b70786589f86fc2bb3f03d36c3fa4/pin-3.4.0-0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:66534969e155ac4bbdd9b96e96b076ef45d4d46d98e0c12ea9468d738f7b7ea2", size = 17711687, upload-time = "2025-02-13T01:08:20.651Z" }, +] + +[[package]] +name = "pip" +version = "25.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/6e/74a3f0179a4a73a53d66ce57fdb4de0080a8baa1de0063de206d6167acc2/pip-25.3.tar.gz", hash = "sha256:8d0538dbbd7babbd207f261ed969c65de439f6bc9e5dbd3b3b9a77f25d95f343", size = 1803014, upload-time = "2025-10-25T00:55:41.394Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/3c/d717024885424591d5376220b5e836c2d5293ce2011523c9de23ff7bf068/pip-25.3-py3-none-any.whl", hash = "sha256:9655943313a94722b7774661c21049070f6bbb0a1516bf02f7c8d5d9201514cd", size = 1778622, upload-time = "2025-10-25T00:55:39.247Z" }, +] + +[[package]] +name = "placo" +version = "0.9.14" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cmeel" }, + { name = "eiquadprog" }, + { name = "ischedule" }, + { name = "meshcat" }, + { name = "pin" }, + { name = "rhoban-cmeel-jsoncpp" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4b/45/293c9afc05a018bd2e2d6ec88a44958f09724686c826f576b04775d0d19b/placo-0.9.14.tar.gz", hash = "sha256:2ac04ede402a60fdfff240bd06dbb01fcd7b291bdb7b2d1d5ef595e7ca581b74", size = 136457, upload-time = "2025-07-07T23:43:26.547Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/49/16f21051e19d8adc1bdc1393db05f08bde7663201376ff644b5619b5ea6c/placo-0.9.14-0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ed75678b1c0ca4c0cf614a16a60684247236f64b51781ae445eb941f96c74f79", size = 1684593, upload-time = "2025-07-07T23:42:45.863Z" }, + { url = "https://files.pythonhosted.org/packages/19/10/c77194ac946a6ebb0a283bc43e76758a9edf082c68a33b9b2d8c6e2e5362/placo-0.9.14-0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f2331d03d15dbdbf9a648f98b9434d359737367945e43bd8d8172c10fdbca899", size = 1549964, upload-time = "2025-07-07T23:42:47.965Z" }, + { url = "https://files.pythonhosted.org/packages/94/c5/e13748b59c751ae2e49edf28e8c97728714a7f1972bcb07a3d2d1976109f/placo-0.9.14-0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:a182ff80ed09156d3ed6bd16c15163a44a67e4ab982df4644528703eae0b39f5", size = 2112464, upload-time = "2025-07-07T23:42:49.959Z" }, + { url = "https://files.pythonhosted.org/packages/e4/a9/78e031655a661a5f835387ecf9314b0965daffbcbb02c3f1a9e190fd887b/placo-0.9.14-0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:eb183ec0ec89c186e9b1cef7ee70878a27b0d5567d7673708a34753d6620ccab", size = 2177921, upload-time = "2025-07-07T23:42:51.462Z" }, + { url = "https://files.pythonhosted.org/packages/96/da/d10aaa6e9f596f61fcd08d17ccd58f20572f4361150b0ff557153e510e19/placo-0.9.14-0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1732cdada774c8a9dab70f5f8e8c361d8d2c62928d3b8d103811b2a2319bf1cd", size = 1684579, upload-time = "2025-07-07T23:42:54.211Z" }, + { url = "https://files.pythonhosted.org/packages/e1/94/8650034a283e2ca9b4b3eb95539368cd83d6bb624ed6f08cf4d7badb7345/placo-0.9.14-0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b870f0afe186776438d3f89df4e78618c077a771fa76250026964db67e125f7f", size = 1549950, upload-time = "2025-07-07T23:42:55.765Z" }, + { url = "https://files.pythonhosted.org/packages/33/f9/becef66e3b712cd8613b1132cb2337b53c8ea3714b550cfe558f80dc34a9/placo-0.9.14-0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:97f12f6f29608d301a92c108a948ed9e2a40ca12b5f046969572cdba13e67d52", size = 2111904, upload-time = "2025-07-07T23:42:57.609Z" }, + { url = "https://files.pythonhosted.org/packages/e0/4f/3ec87720b6971af0430de17735666beabae6374066281dc71d908f049f56/placo-0.9.14-0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:878a39f4f45e728785891271a332a26181a0f7fa57a825dc7ac239f35ab4c091", size = 2178050, upload-time = "2025-07-07T23:42:59.123Z" }, + { url = "https://files.pythonhosted.org/packages/95/a8/03f3ab9d44b6aa754b652f1d652dd65ec94a4d3ec0432720d8b88a1605fe/placo-0.9.14-0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e41150e03a303f4c90f8a6138330caca21d520160021b7b9e14b90e2dbbcaf58", size = 1705653, upload-time = "2025-07-07T23:43:00.836Z" }, + { url = "https://files.pythonhosted.org/packages/14/39/e892d5c1ad42302490846dc5e6e3881d9d335bbeac18be1415b3ac4be58e/placo-0.9.14-0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03c1fe072f7d0075ab2a4ea85b3fb62fedf7e2c4d9613f96a156a576d29255e2", size = 1564825, upload-time = "2025-07-07T23:43:02.724Z" }, + { url = "https://files.pythonhosted.org/packages/1f/70/d5a0a079770a8cc4eaa977aa7438ff7861ce2227d0949ea1b5b3651caf0b/placo-0.9.14-0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:ad96c8f3b5b0cf757048daed3e57b83765c402a27e5608a28496ed12cfad9692", size = 2092058, upload-time = "2025-07-07T23:43:04.109Z" }, + { url = "https://files.pythonhosted.org/packages/27/9f/a80f6ffed0bf7b383aea8ae94668bc7d0046f5909692c0af8c0e10ecc9d1/placo-0.9.14-0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:84ed219527c8868b4f520080c97280183ff311428d0e97225c53de8822c3783e", size = 2163101, upload-time = "2025-07-07T23:43:05.649Z" }, + { url = "https://files.pythonhosted.org/packages/09/d6/14fb3749b99be2ccd7d2329df2a20ab7709ce7245dc7930c65babafd6dd6/placo-0.9.14-0-cp313-cp313-macosx_10_9_x86_64.whl", hash = "sha256:3d0d4638f685591d8263c2fb66861255816784b995ba2ba9d7cc6882d957e4cb", size = 1705647, upload-time = "2025-07-07T23:43:07.092Z" }, + { url = "https://files.pythonhosted.org/packages/d7/2e/be6081a1d2aec95e7436700f5e8b008328e5ae2390b07ad650e84687e32c/placo-0.9.14-0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:460a211febf9310074407e59ba3c8be52289148cc4f86868949ba9c462f381d2", size = 1564819, upload-time = "2025-07-07T23:43:08.961Z" }, + { url = "https://files.pythonhosted.org/packages/63/43/b18d82fa0237e0ec6598a7f47f0ffbc007b9c3913d8d76099cffcf9d5de1/placo-0.9.14-0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:7b57831c2db2e808fd63c807149699c312d3ae09e1d6e10c9df3d12b2c66dee7", size = 2092073, upload-time = "2025-07-07T23:43:10.437Z" }, + { url = "https://files.pythonhosted.org/packages/72/a1/d92bf06980fb4c265394e797751059fdb7256ef425e6f8399697172374f4/placo-0.9.14-0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:1d56205d66dd49672de18c23ef8e8fe6e5321b3d88ca4844e7117a47acaf9ce1", size = 2163105, upload-time = "2025-07-07T23:43:12.502Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/23/e8/21db9c9987b0e728855bd57bff6984f67952bea55d6f75e055c46b5383e8/platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf", size = 21634, upload-time = "2025-08-26T14:32:04.268Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/4b/2028861e724d3bd36227adfa20d3fd24c3fc6d52032f4a93c133be5d17ce/platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85", size = 18654, upload-time = "2025-08-26T14:32:02.735Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pollen-bmi088-imu-library" +version = "1.0.0rc1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ahrs" }, + { name = "numpy" }, + { name = "smbus3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/64/ca/f74143ec89fbc502c7d9fc6a4d20a7f56fad822389716daee5e2bfe9e364/pollen_bmi088_imu_library-1.0.0rc1.tar.gz", hash = "sha256:6a82eafa448807a87c7a9e1ed8ef395b0ce5f2f57fa536b5b3a3f428e72d8fcf", size = 3273, upload-time = "2025-10-30T08:57:43.422Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/0a/86991b5db3acb88bbe83ba1e586c1c401830c8dc3b95cb6e0637e60cd26d/pollen_bmi088_imu_library-1.0.0rc1-py3-none-any.whl", hash = "sha256:d13e7297a3042879c36b48f78c6d0e8426af2c5ca31efe312003c7cf74b657c8", size = 3748, upload-time = "2025-10-30T08:57:42.136Z" }, +] + +[[package]] +name = "pre-commit" +version = "4.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ff/29/7cf5bbc236333876e4b41f56e06857a87937ce4bf91e117a6991a2dbb02a/pre_commit-4.3.0.tar.gz", hash = "sha256:499fe450cc9d42e9d58e606262795ecb64dd05438943c62b66f6a8673da30b16", size = 193792, upload-time = "2025-08-09T18:56:14.651Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/a5/987a405322d78a73b66e39e4a90e4ef156fd7141bf71df987e50717c321b/pre_commit-4.3.0-py2.py3-none-any.whl", hash = "sha256:2b0747ad7e6e967169136edffee14c16e148a778a54e4f967921aa1ebf2308d8", size = 220965, upload-time = "2025-08-09T18:56:13.192Z" }, +] + +[[package]] +name = "prompt-toolkit" +version = "3.0.52" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" }, +] + +[[package]] +name = "propcache" +version = "0.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/16/43264e4a779dd8588c21a70f0709665ee8f611211bdd2c87d952cfa7c776/propcache-0.3.2.tar.gz", hash = "sha256:20d7d62e4e7ef05f221e0db2856b979540686342e7dd9973b815599c7057e168", size = 44139, upload-time = "2025-06-09T22:56:06.081Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/14/510deed325e262afeb8b360043c5d7c960da7d3ecd6d6f9496c9c56dc7f4/propcache-0.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:22d9962a358aedbb7a2e36187ff273adeaab9743373a272976d2e348d08c7770", size = 73178, upload-time = "2025-06-09T22:53:40.126Z" }, + { url = "https://files.pythonhosted.org/packages/cd/4e/ad52a7925ff01c1325653a730c7ec3175a23f948f08626a534133427dcff/propcache-0.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0d0fda578d1dc3f77b6b5a5dce3b9ad69a8250a891760a548df850a5e8da87f3", size = 43133, upload-time = "2025-06-09T22:53:41.965Z" }, + { url = "https://files.pythonhosted.org/packages/63/7c/e9399ba5da7780871db4eac178e9c2e204c23dd3e7d32df202092a1ed400/propcache-0.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3def3da3ac3ce41562d85db655d18ebac740cb3fa4367f11a52b3da9d03a5cc3", size = 43039, upload-time = "2025-06-09T22:53:43.268Z" }, + { url = "https://files.pythonhosted.org/packages/22/e1/58da211eb8fdc6fc854002387d38f415a6ca5f5c67c1315b204a5d3e9d7a/propcache-0.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bec58347a5a6cebf239daba9bda37dffec5b8d2ce004d9fe4edef3d2815137e", size = 201903, upload-time = "2025-06-09T22:53:44.872Z" }, + { url = "https://files.pythonhosted.org/packages/c4/0a/550ea0f52aac455cb90111c8bab995208443e46d925e51e2f6ebdf869525/propcache-0.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55ffda449a507e9fbd4aca1a7d9aa6753b07d6166140e5a18d2ac9bc49eac220", size = 213362, upload-time = "2025-06-09T22:53:46.707Z" }, + { url = "https://files.pythonhosted.org/packages/5a/af/9893b7d878deda9bb69fcf54600b247fba7317761b7db11fede6e0f28bd0/propcache-0.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64a67fb39229a8a8491dd42f864e5e263155e729c2e7ff723d6e25f596b1e8cb", size = 210525, upload-time = "2025-06-09T22:53:48.547Z" }, + { url = "https://files.pythonhosted.org/packages/7c/bb/38fd08b278ca85cde36d848091ad2b45954bc5f15cce494bb300b9285831/propcache-0.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9da1cf97b92b51253d5b68cf5a2b9e0dafca095e36b7f2da335e27dc6172a614", size = 198283, upload-time = "2025-06-09T22:53:50.067Z" }, + { url = "https://files.pythonhosted.org/packages/78/8c/9fe55bd01d362bafb413dfe508c48753111a1e269737fa143ba85693592c/propcache-0.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5f559e127134b07425134b4065be45b166183fdcb433cb6c24c8e4149056ad50", size = 191872, upload-time = "2025-06-09T22:53:51.438Z" }, + { url = "https://files.pythonhosted.org/packages/54/14/4701c33852937a22584e08abb531d654c8bcf7948a8f87ad0a4822394147/propcache-0.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:aff2e4e06435d61f11a428360a932138d0ec288b0a31dd9bd78d200bd4a2b339", size = 199452, upload-time = "2025-06-09T22:53:53.229Z" }, + { url = "https://files.pythonhosted.org/packages/16/44/447f2253d859602095356007657ee535e0093215ea0b3d1d6a41d16e5201/propcache-0.3.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:4927842833830942a5d0a56e6f4839bc484785b8e1ce8d287359794818633ba0", size = 191567, upload-time = "2025-06-09T22:53:54.541Z" }, + { url = "https://files.pythonhosted.org/packages/f2/b3/e4756258749bb2d3b46defcff606a2f47410bab82be5824a67e84015b267/propcache-0.3.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:6107ddd08b02654a30fb8ad7a132021759d750a82578b94cd55ee2772b6ebea2", size = 193015, upload-time = "2025-06-09T22:53:56.44Z" }, + { url = "https://files.pythonhosted.org/packages/1e/df/e6d3c7574233164b6330b9fd697beeac402afd367280e6dc377bb99b43d9/propcache-0.3.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:70bd8b9cd6b519e12859c99f3fc9a93f375ebd22a50296c3a295028bea73b9e7", size = 204660, upload-time = "2025-06-09T22:53:57.839Z" }, + { url = "https://files.pythonhosted.org/packages/b2/53/e4d31dd5170b4a0e2e6b730f2385a96410633b4833dc25fe5dffd1f73294/propcache-0.3.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2183111651d710d3097338dd1893fcf09c9f54e27ff1a8795495a16a469cc90b", size = 206105, upload-time = "2025-06-09T22:53:59.638Z" }, + { url = "https://files.pythonhosted.org/packages/7f/fe/74d54cf9fbe2a20ff786e5f7afcfde446588f0cf15fb2daacfbc267b866c/propcache-0.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fb075ad271405dcad8e2a7ffc9a750a3bf70e533bd86e89f0603e607b93aa64c", size = 196980, upload-time = "2025-06-09T22:54:01.071Z" }, + { url = "https://files.pythonhosted.org/packages/22/ec/c469c9d59dada8a7679625e0440b544fe72e99311a4679c279562051f6fc/propcache-0.3.2-cp310-cp310-win32.whl", hash = "sha256:404d70768080d3d3bdb41d0771037da19d8340d50b08e104ca0e7f9ce55fce70", size = 37679, upload-time = "2025-06-09T22:54:03.003Z" }, + { url = "https://files.pythonhosted.org/packages/38/35/07a471371ac89d418f8d0b699c75ea6dca2041fbda360823de21f6a9ce0a/propcache-0.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:7435d766f978b4ede777002e6b3b6641dd229cd1da8d3d3106a45770365f9ad9", size = 41459, upload-time = "2025-06-09T22:54:04.134Z" }, + { url = "https://files.pythonhosted.org/packages/80/8d/e8b436717ab9c2cfc23b116d2c297305aa4cd8339172a456d61ebf5669b8/propcache-0.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0b8d2f607bd8f80ddc04088bc2a037fdd17884a6fcadc47a96e334d72f3717be", size = 74207, upload-time = "2025-06-09T22:54:05.399Z" }, + { url = "https://files.pythonhosted.org/packages/d6/29/1e34000e9766d112171764b9fa3226fa0153ab565d0c242c70e9945318a7/propcache-0.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06766d8f34733416e2e34f46fea488ad5d60726bb9481d3cddf89a6fa2d9603f", size = 43648, upload-time = "2025-06-09T22:54:08.023Z" }, + { url = "https://files.pythonhosted.org/packages/46/92/1ad5af0df781e76988897da39b5f086c2bf0f028b7f9bd1f409bb05b6874/propcache-0.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2dc1f4a1df4fecf4e6f68013575ff4af84ef6f478fe5344317a65d38a8e6dc9", size = 43496, upload-time = "2025-06-09T22:54:09.228Z" }, + { url = "https://files.pythonhosted.org/packages/b3/ce/e96392460f9fb68461fabab3e095cb00c8ddf901205be4eae5ce246e5b7e/propcache-0.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be29c4f4810c5789cf10ddf6af80b041c724e629fa51e308a7a0fb19ed1ef7bf", size = 217288, upload-time = "2025-06-09T22:54:10.466Z" }, + { url = "https://files.pythonhosted.org/packages/c5/2a/866726ea345299f7ceefc861a5e782b045545ae6940851930a6adaf1fca6/propcache-0.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59d61f6970ecbd8ff2e9360304d5c8876a6abd4530cb752c06586849ac8a9dc9", size = 227456, upload-time = "2025-06-09T22:54:11.828Z" }, + { url = "https://files.pythonhosted.org/packages/de/03/07d992ccb6d930398689187e1b3c718339a1c06b8b145a8d9650e4726166/propcache-0.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:62180e0b8dbb6b004baec00a7983e4cc52f5ada9cd11f48c3528d8cfa7b96a66", size = 225429, upload-time = "2025-06-09T22:54:13.823Z" }, + { url = "https://files.pythonhosted.org/packages/5d/e6/116ba39448753b1330f48ab8ba927dcd6cf0baea8a0ccbc512dfb49ba670/propcache-0.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c144ca294a204c470f18cf4c9d78887810d04a3e2fbb30eea903575a779159df", size = 213472, upload-time = "2025-06-09T22:54:15.232Z" }, + { url = "https://files.pythonhosted.org/packages/a6/85/f01f5d97e54e428885a5497ccf7f54404cbb4f906688a1690cd51bf597dc/propcache-0.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5c2a784234c28854878d68978265617aa6dc0780e53d44b4d67f3651a17a9a2", size = 204480, upload-time = "2025-06-09T22:54:17.104Z" }, + { url = "https://files.pythonhosted.org/packages/e3/79/7bf5ab9033b8b8194cc3f7cf1aaa0e9c3256320726f64a3e1f113a812dce/propcache-0.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5745bc7acdafa978ca1642891b82c19238eadc78ba2aaa293c6863b304e552d7", size = 214530, upload-time = "2025-06-09T22:54:18.512Z" }, + { url = "https://files.pythonhosted.org/packages/31/0b/bd3e0c00509b609317df4a18e6b05a450ef2d9a963e1d8bc9c9415d86f30/propcache-0.3.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:c0075bf773d66fa8c9d41f66cc132ecc75e5bb9dd7cce3cfd14adc5ca184cb95", size = 205230, upload-time = "2025-06-09T22:54:19.947Z" }, + { url = "https://files.pythonhosted.org/packages/7a/23/fae0ff9b54b0de4e819bbe559508da132d5683c32d84d0dc2ccce3563ed4/propcache-0.3.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5f57aa0847730daceff0497f417c9de353c575d8da3579162cc74ac294c5369e", size = 206754, upload-time = "2025-06-09T22:54:21.716Z" }, + { url = "https://files.pythonhosted.org/packages/b7/7f/ad6a3c22630aaa5f618b4dc3c3598974a72abb4c18e45a50b3cdd091eb2f/propcache-0.3.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:eef914c014bf72d18efb55619447e0aecd5fb7c2e3fa7441e2e5d6099bddff7e", size = 218430, upload-time = "2025-06-09T22:54:23.17Z" }, + { url = "https://files.pythonhosted.org/packages/5b/2c/ba4f1c0e8a4b4c75910742f0d333759d441f65a1c7f34683b4a74c0ee015/propcache-0.3.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2a4092e8549031e82facf3decdbc0883755d5bbcc62d3aea9d9e185549936dcf", size = 223884, upload-time = "2025-06-09T22:54:25.539Z" }, + { url = "https://files.pythonhosted.org/packages/88/e4/ebe30fc399e98572019eee82ad0caf512401661985cbd3da5e3140ffa1b0/propcache-0.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:85871b050f174bc0bfb437efbdb68aaf860611953ed12418e4361bc9c392749e", size = 211480, upload-time = "2025-06-09T22:54:26.892Z" }, + { url = "https://files.pythonhosted.org/packages/96/0a/7d5260b914e01d1d0906f7f38af101f8d8ed0dc47426219eeaf05e8ea7c2/propcache-0.3.2-cp311-cp311-win32.whl", hash = "sha256:36c8d9b673ec57900c3554264e630d45980fd302458e4ac801802a7fd2ef7897", size = 37757, upload-time = "2025-06-09T22:54:28.241Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2d/89fe4489a884bc0da0c3278c552bd4ffe06a1ace559db5ef02ef24ab446b/propcache-0.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53af8cb6a781b02d2ea079b5b853ba9430fcbe18a8e3ce647d5982a3ff69f39", size = 41500, upload-time = "2025-06-09T22:54:29.4Z" }, + { url = "https://files.pythonhosted.org/packages/a8/42/9ca01b0a6f48e81615dca4765a8f1dd2c057e0540f6116a27dc5ee01dfb6/propcache-0.3.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8de106b6c84506b31c27168582cd3cb3000a6412c16df14a8628e5871ff83c10", size = 73674, upload-time = "2025-06-09T22:54:30.551Z" }, + { url = "https://files.pythonhosted.org/packages/af/6e/21293133beb550f9c901bbece755d582bfaf2176bee4774000bd4dd41884/propcache-0.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:28710b0d3975117239c76600ea351934ac7b5ff56e60953474342608dbbb6154", size = 43570, upload-time = "2025-06-09T22:54:32.296Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c8/0393a0a3a2b8760eb3bde3c147f62b20044f0ddac81e9d6ed7318ec0d852/propcache-0.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce26862344bdf836650ed2487c3d724b00fbfec4233a1013f597b78c1cb73615", size = 43094, upload-time = "2025-06-09T22:54:33.929Z" }, + { url = "https://files.pythonhosted.org/packages/37/2c/489afe311a690399d04a3e03b069225670c1d489eb7b044a566511c1c498/propcache-0.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bca54bd347a253af2cf4544bbec232ab982f4868de0dd684246b67a51bc6b1db", size = 226958, upload-time = "2025-06-09T22:54:35.186Z" }, + { url = "https://files.pythonhosted.org/packages/9d/ca/63b520d2f3d418c968bf596839ae26cf7f87bead026b6192d4da6a08c467/propcache-0.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55780d5e9a2ddc59711d727226bb1ba83a22dd32f64ee15594b9392b1f544eb1", size = 234894, upload-time = "2025-06-09T22:54:36.708Z" }, + { url = "https://files.pythonhosted.org/packages/11/60/1d0ed6fff455a028d678df30cc28dcee7af77fa2b0e6962ce1df95c9a2a9/propcache-0.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:035e631be25d6975ed87ab23153db6a73426a48db688070d925aa27e996fe93c", size = 233672, upload-time = "2025-06-09T22:54:38.062Z" }, + { url = "https://files.pythonhosted.org/packages/37/7c/54fd5301ef38505ab235d98827207176a5c9b2aa61939b10a460ca53e123/propcache-0.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee6f22b6eaa39297c751d0e80c0d3a454f112f5c6481214fcf4c092074cecd67", size = 224395, upload-time = "2025-06-09T22:54:39.634Z" }, + { url = "https://files.pythonhosted.org/packages/ee/1a/89a40e0846f5de05fdc6779883bf46ba980e6df4d2ff8fb02643de126592/propcache-0.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ca3aee1aa955438c4dba34fc20a9f390e4c79967257d830f137bd5a8a32ed3b", size = 212510, upload-time = "2025-06-09T22:54:41.565Z" }, + { url = "https://files.pythonhosted.org/packages/5e/33/ca98368586c9566a6b8d5ef66e30484f8da84c0aac3f2d9aec6d31a11bd5/propcache-0.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7a4f30862869fa2b68380d677cc1c5fcf1e0f2b9ea0cf665812895c75d0ca3b8", size = 222949, upload-time = "2025-06-09T22:54:43.038Z" }, + { url = "https://files.pythonhosted.org/packages/ba/11/ace870d0aafe443b33b2f0b7efdb872b7c3abd505bfb4890716ad7865e9d/propcache-0.3.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b77ec3c257d7816d9f3700013639db7491a434644c906a2578a11daf13176251", size = 217258, upload-time = "2025-06-09T22:54:44.376Z" }, + { url = "https://files.pythonhosted.org/packages/5b/d2/86fd6f7adffcfc74b42c10a6b7db721d1d9ca1055c45d39a1a8f2a740a21/propcache-0.3.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cab90ac9d3f14b2d5050928483d3d3b8fb6b4018893fc75710e6aa361ecb2474", size = 213036, upload-time = "2025-06-09T22:54:46.243Z" }, + { url = "https://files.pythonhosted.org/packages/07/94/2d7d1e328f45ff34a0a284cf5a2847013701e24c2a53117e7c280a4316b3/propcache-0.3.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0b504d29f3c47cf6b9e936c1852246c83d450e8e063d50562115a6be6d3a2535", size = 227684, upload-time = "2025-06-09T22:54:47.63Z" }, + { url = "https://files.pythonhosted.org/packages/b7/05/37ae63a0087677e90b1d14710e532ff104d44bc1efa3b3970fff99b891dc/propcache-0.3.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:ce2ac2675a6aa41ddb2a0c9cbff53780a617ac3d43e620f8fd77ba1c84dcfc06", size = 234562, upload-time = "2025-06-09T22:54:48.982Z" }, + { url = "https://files.pythonhosted.org/packages/a4/7c/3f539fcae630408d0bd8bf3208b9a647ccad10976eda62402a80adf8fc34/propcache-0.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:62b4239611205294cc433845b914131b2a1f03500ff3c1ed093ed216b82621e1", size = 222142, upload-time = "2025-06-09T22:54:50.424Z" }, + { url = "https://files.pythonhosted.org/packages/7c/d2/34b9eac8c35f79f8a962546b3e97e9d4b990c420ee66ac8255d5d9611648/propcache-0.3.2-cp312-cp312-win32.whl", hash = "sha256:df4a81b9b53449ebc90cc4deefb052c1dd934ba85012aa912c7ea7b7e38b60c1", size = 37711, upload-time = "2025-06-09T22:54:52.072Z" }, + { url = "https://files.pythonhosted.org/packages/19/61/d582be5d226cf79071681d1b46b848d6cb03d7b70af7063e33a2787eaa03/propcache-0.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7046e79b989d7fe457bb755844019e10f693752d169076138abf17f31380800c", size = 41479, upload-time = "2025-06-09T22:54:53.234Z" }, + { url = "https://files.pythonhosted.org/packages/dc/d1/8c747fafa558c603c4ca19d8e20b288aa0c7cda74e9402f50f31eb65267e/propcache-0.3.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ca592ed634a73ca002967458187109265e980422116c0a107cf93d81f95af945", size = 71286, upload-time = "2025-06-09T22:54:54.369Z" }, + { url = "https://files.pythonhosted.org/packages/61/99/d606cb7986b60d89c36de8a85d58764323b3a5ff07770a99d8e993b3fa73/propcache-0.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9ecb0aad4020e275652ba3975740f241bd12a61f1a784df044cf7477a02bc252", size = 42425, upload-time = "2025-06-09T22:54:55.642Z" }, + { url = "https://files.pythonhosted.org/packages/8c/96/ef98f91bbb42b79e9bb82bdd348b255eb9d65f14dbbe3b1594644c4073f7/propcache-0.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7f08f1cc28bd2eade7a8a3d2954ccc673bb02062e3e7da09bc75d843386b342f", size = 41846, upload-time = "2025-06-09T22:54:57.246Z" }, + { url = "https://files.pythonhosted.org/packages/5b/ad/3f0f9a705fb630d175146cd7b1d2bf5555c9beaed54e94132b21aac098a6/propcache-0.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1a342c834734edb4be5ecb1e9fb48cb64b1e2320fccbd8c54bf8da8f2a84c33", size = 208871, upload-time = "2025-06-09T22:54:58.975Z" }, + { url = "https://files.pythonhosted.org/packages/3a/38/2085cda93d2c8b6ec3e92af2c89489a36a5886b712a34ab25de9fbca7992/propcache-0.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a544caaae1ac73f1fecfae70ded3e93728831affebd017d53449e3ac052ac1e", size = 215720, upload-time = "2025-06-09T22:55:00.471Z" }, + { url = "https://files.pythonhosted.org/packages/61/c1/d72ea2dc83ac7f2c8e182786ab0fc2c7bd123a1ff9b7975bee671866fe5f/propcache-0.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:310d11aa44635298397db47a3ebce7db99a4cc4b9bbdfcf6c98a60c8d5261cf1", size = 215203, upload-time = "2025-06-09T22:55:01.834Z" }, + { url = "https://files.pythonhosted.org/packages/af/81/b324c44ae60c56ef12007105f1460d5c304b0626ab0cc6b07c8f2a9aa0b8/propcache-0.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c1396592321ac83157ac03a2023aa6cc4a3cc3cfdecb71090054c09e5a7cce3", size = 206365, upload-time = "2025-06-09T22:55:03.199Z" }, + { url = "https://files.pythonhosted.org/packages/09/73/88549128bb89e66d2aff242488f62869014ae092db63ccea53c1cc75a81d/propcache-0.3.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cabf5b5902272565e78197edb682017d21cf3b550ba0460ee473753f28d23c1", size = 196016, upload-time = "2025-06-09T22:55:04.518Z" }, + { url = "https://files.pythonhosted.org/packages/b9/3f/3bdd14e737d145114a5eb83cb172903afba7242f67c5877f9909a20d948d/propcache-0.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0a2f2235ac46a7aa25bdeb03a9e7060f6ecbd213b1f9101c43b3090ffb971ef6", size = 205596, upload-time = "2025-06-09T22:55:05.942Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ca/2f4aa819c357d3107c3763d7ef42c03980f9ed5c48c82e01e25945d437c1/propcache-0.3.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:92b69e12e34869a6970fd2f3da91669899994b47c98f5d430b781c26f1d9f387", size = 200977, upload-time = "2025-06-09T22:55:07.792Z" }, + { url = "https://files.pythonhosted.org/packages/cd/4a/e65276c7477533c59085251ae88505caf6831c0e85ff8b2e31ebcbb949b1/propcache-0.3.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:54e02207c79968ebbdffc169591009f4474dde3b4679e16634d34c9363ff56b4", size = 197220, upload-time = "2025-06-09T22:55:09.173Z" }, + { url = "https://files.pythonhosted.org/packages/7c/54/fc7152e517cf5578278b242396ce4d4b36795423988ef39bb8cd5bf274c8/propcache-0.3.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4adfb44cb588001f68c5466579d3f1157ca07f7504fc91ec87862e2b8e556b88", size = 210642, upload-time = "2025-06-09T22:55:10.62Z" }, + { url = "https://files.pythonhosted.org/packages/b9/80/abeb4a896d2767bf5f1ea7b92eb7be6a5330645bd7fb844049c0e4045d9d/propcache-0.3.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fd3e6019dc1261cd0291ee8919dd91fbab7b169bb76aeef6c716833a3f65d206", size = 212789, upload-time = "2025-06-09T22:55:12.029Z" }, + { url = "https://files.pythonhosted.org/packages/b3/db/ea12a49aa7b2b6d68a5da8293dcf50068d48d088100ac016ad92a6a780e6/propcache-0.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4c181cad81158d71c41a2bce88edce078458e2dd5ffee7eddd6b05da85079f43", size = 205880, upload-time = "2025-06-09T22:55:13.45Z" }, + { url = "https://files.pythonhosted.org/packages/d1/e5/9076a0bbbfb65d1198007059c65639dfd56266cf8e477a9707e4b1999ff4/propcache-0.3.2-cp313-cp313-win32.whl", hash = "sha256:8a08154613f2249519e549de2330cf8e2071c2887309a7b07fb56098f5170a02", size = 37220, upload-time = "2025-06-09T22:55:15.284Z" }, + { url = "https://files.pythonhosted.org/packages/d3/f5/b369e026b09a26cd77aa88d8fffd69141d2ae00a2abaaf5380d2603f4b7f/propcache-0.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e41671f1594fc4ab0a6dec1351864713cb3a279910ae8b58f884a88a0a632c05", size = 40678, upload-time = "2025-06-09T22:55:16.445Z" }, + { url = "https://files.pythonhosted.org/packages/a4/3a/6ece377b55544941a08d03581c7bc400a3c8cd3c2865900a68d5de79e21f/propcache-0.3.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:9a3cf035bbaf035f109987d9d55dc90e4b0e36e04bbbb95af3055ef17194057b", size = 76560, upload-time = "2025-06-09T22:55:17.598Z" }, + { url = "https://files.pythonhosted.org/packages/0c/da/64a2bb16418740fa634b0e9c3d29edff1db07f56d3546ca2d86ddf0305e1/propcache-0.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:156c03d07dc1323d8dacaa221fbe028c5c70d16709cdd63502778e6c3ccca1b0", size = 44676, upload-time = "2025-06-09T22:55:18.922Z" }, + { url = "https://files.pythonhosted.org/packages/36/7b/f025e06ea51cb72c52fb87e9b395cced02786610b60a3ed51da8af017170/propcache-0.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74413c0ba02ba86f55cf60d18daab219f7e531620c15f1e23d95563f505efe7e", size = 44701, upload-time = "2025-06-09T22:55:20.106Z" }, + { url = "https://files.pythonhosted.org/packages/a4/00/faa1b1b7c3b74fc277f8642f32a4c72ba1d7b2de36d7cdfb676db7f4303e/propcache-0.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f066b437bb3fa39c58ff97ab2ca351db465157d68ed0440abecb21715eb24b28", size = 276934, upload-time = "2025-06-09T22:55:21.5Z" }, + { url = "https://files.pythonhosted.org/packages/74/ab/935beb6f1756e0476a4d5938ff44bf0d13a055fed880caf93859b4f1baf4/propcache-0.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1304b085c83067914721e7e9d9917d41ad87696bf70f0bc7dee450e9c71ad0a", size = 278316, upload-time = "2025-06-09T22:55:22.918Z" }, + { url = "https://files.pythonhosted.org/packages/f8/9d/994a5c1ce4389610838d1caec74bdf0e98b306c70314d46dbe4fcf21a3e2/propcache-0.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ab50cef01b372763a13333b4e54021bdcb291fc9a8e2ccb9c2df98be51bcde6c", size = 282619, upload-time = "2025-06-09T22:55:24.651Z" }, + { url = "https://files.pythonhosted.org/packages/2b/00/a10afce3d1ed0287cef2e09506d3be9822513f2c1e96457ee369adb9a6cd/propcache-0.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fad3b2a085ec259ad2c2842666b2a0a49dea8463579c606426128925af1ed725", size = 265896, upload-time = "2025-06-09T22:55:26.049Z" }, + { url = "https://files.pythonhosted.org/packages/2e/a8/2aa6716ffa566ca57c749edb909ad27884680887d68517e4be41b02299f3/propcache-0.3.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:261fa020c1c14deafd54c76b014956e2f86991af198c51139faf41c4d5e83892", size = 252111, upload-time = "2025-06-09T22:55:27.381Z" }, + { url = "https://files.pythonhosted.org/packages/36/4f/345ca9183b85ac29c8694b0941f7484bf419c7f0fea2d1e386b4f7893eed/propcache-0.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:46d7f8aa79c927e5f987ee3a80205c987717d3659f035c85cf0c3680526bdb44", size = 268334, upload-time = "2025-06-09T22:55:28.747Z" }, + { url = "https://files.pythonhosted.org/packages/3e/ca/fcd54f78b59e3f97b3b9715501e3147f5340167733d27db423aa321e7148/propcache-0.3.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:6d8f3f0eebf73e3c0ff0e7853f68be638b4043c65a70517bb575eff54edd8dbe", size = 255026, upload-time = "2025-06-09T22:55:30.184Z" }, + { url = "https://files.pythonhosted.org/packages/8b/95/8e6a6bbbd78ac89c30c225210a5c687790e532ba4088afb8c0445b77ef37/propcache-0.3.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:03c89c1b14a5452cf15403e291c0ccd7751d5b9736ecb2c5bab977ad6c5bcd81", size = 250724, upload-time = "2025-06-09T22:55:31.646Z" }, + { url = "https://files.pythonhosted.org/packages/ee/b0/0dd03616142baba28e8b2d14ce5df6631b4673850a3d4f9c0f9dd714a404/propcache-0.3.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:0cc17efde71e12bbaad086d679ce575268d70bc123a5a71ea7ad76f70ba30bba", size = 268868, upload-time = "2025-06-09T22:55:33.209Z" }, + { url = "https://files.pythonhosted.org/packages/c5/98/2c12407a7e4fbacd94ddd32f3b1e3d5231e77c30ef7162b12a60e2dd5ce3/propcache-0.3.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:acdf05d00696bc0447e278bb53cb04ca72354e562cf88ea6f9107df8e7fd9770", size = 271322, upload-time = "2025-06-09T22:55:35.065Z" }, + { url = "https://files.pythonhosted.org/packages/35/91/9cb56efbb428b006bb85db28591e40b7736847b8331d43fe335acf95f6c8/propcache-0.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4445542398bd0b5d32df908031cb1b30d43ac848e20470a878b770ec2dcc6330", size = 265778, upload-time = "2025-06-09T22:55:36.45Z" }, + { url = "https://files.pythonhosted.org/packages/9a/4c/b0fe775a2bdd01e176b14b574be679d84fc83958335790f7c9a686c1f468/propcache-0.3.2-cp313-cp313t-win32.whl", hash = "sha256:f86e5d7cd03afb3a1db8e9f9f6eff15794e79e791350ac48a8c924e6f439f394", size = 41175, upload-time = "2025-06-09T22:55:38.436Z" }, + { url = "https://files.pythonhosted.org/packages/a4/ff/47f08595e3d9b5e149c150f88d9714574f1a7cbd89fe2817158a952674bf/propcache-0.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9704bedf6e7cbe3c65eca4379a9b53ee6a83749f047808cbb5044d40d7d72198", size = 44857, upload-time = "2025-06-09T22:55:39.687Z" }, + { url = "https://files.pythonhosted.org/packages/cc/35/cc0aaecf278bb4575b8555f2b137de5ab821595ddae9da9d3cd1da4072c7/propcache-0.3.2-py3-none-any.whl", hash = "sha256:98f1ec44fb675f5052cccc8e609c46ed23a35a1cfd18545ad4e29002d858a43f", size = 12663, upload-time = "2025-06-09T22:56:04.484Z" }, +] + +[[package]] +name = "protobuf" +version = "6.32.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c0/df/fb4a8eeea482eca989b51cffd274aac2ee24e825f0bf3cbce5281fa1567b/protobuf-6.32.0.tar.gz", hash = "sha256:a81439049127067fc49ec1d36e25c6ee1d1a2b7be930675f919258d03c04e7d2", size = 440614, upload-time = "2025-08-14T21:21:25.015Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/18/df8c87da2e47f4f1dcc5153a81cd6bca4e429803f4069a299e236e4dd510/protobuf-6.32.0-cp310-abi3-win32.whl", hash = "sha256:84f9e3c1ff6fb0308dbacb0950d8aa90694b0d0ee68e75719cb044b7078fe741", size = 424409, upload-time = "2025-08-14T21:21:12.366Z" }, + { url = "https://files.pythonhosted.org/packages/e1/59/0a820b7310f8139bd8d5a9388e6a38e1786d179d6f33998448609296c229/protobuf-6.32.0-cp310-abi3-win_amd64.whl", hash = "sha256:a8bdbb2f009cfc22a36d031f22a625a38b615b5e19e558a7b756b3279723e68e", size = 435735, upload-time = "2025-08-14T21:21:15.046Z" }, + { url = "https://files.pythonhosted.org/packages/cc/5b/0d421533c59c789e9c9894683efac582c06246bf24bb26b753b149bd88e4/protobuf-6.32.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d52691e5bee6c860fff9a1c86ad26a13afbeb4b168cd4445c922b7e2cf85aaf0", size = 426449, upload-time = "2025-08-14T21:21:16.687Z" }, + { url = "https://files.pythonhosted.org/packages/ec/7b/607764ebe6c7a23dcee06e054fd1de3d5841b7648a90fd6def9a3bb58c5e/protobuf-6.32.0-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:501fe6372fd1c8ea2a30b4d9be8f87955a64d6be9c88a973996cef5ef6f0abf1", size = 322869, upload-time = "2025-08-14T21:21:18.282Z" }, + { url = "https://files.pythonhosted.org/packages/40/01/2e730bd1c25392fc32e3268e02446f0d77cb51a2c3a8486b1798e34d5805/protobuf-6.32.0-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:75a2aab2bd1aeb1f5dc7c5f33bcb11d82ea8c055c9becbb41c26a8c43fd7092c", size = 322009, upload-time = "2025-08-14T21:21:19.893Z" }, + { url = "https://files.pythonhosted.org/packages/9c/f2/80ffc4677aac1bc3519b26bc7f7f5de7fce0ee2f7e36e59e27d8beb32dd1/protobuf-6.32.0-py3-none-any.whl", hash = "sha256:ba377e5b67b908c8f3072a57b63e2c6a4cbd18aea4ed98d2584350dbf46f2783", size = 169287, upload-time = "2025-08-14T21:21:23.515Z" }, +] + +[[package]] +name = "psutil" +version = "7.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2a/80/336820c1ad9286a4ded7e845b2eccfcb27851ab8ac6abece774a6ff4d3de/psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456", size = 497003, upload-time = "2025-02-13T21:54:07.946Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/e6/2d26234410f8b8abdbf891c9da62bee396583f713fb9f3325a4760875d22/psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25", size = 238051, upload-time = "2025-02-13T21:54:12.36Z" }, + { url = "https://files.pythonhosted.org/packages/04/8b/30f930733afe425e3cbfc0e1468a30a18942350c1a8816acfade80c005c4/psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da", size = 239535, upload-time = "2025-02-13T21:54:16.07Z" }, + { url = "https://files.pythonhosted.org/packages/2a/ed/d362e84620dd22876b55389248e522338ed1bf134a5edd3b8231d7207f6d/psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91", size = 275004, upload-time = "2025-02-13T21:54:18.662Z" }, + { url = "https://files.pythonhosted.org/packages/bf/b9/b0eb3f3cbcb734d930fdf839431606844a825b23eaf9a6ab371edac8162c/psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34", size = 277986, upload-time = "2025-02-13T21:54:21.811Z" }, + { url = "https://files.pythonhosted.org/packages/eb/a2/709e0fe2f093556c17fbafda93ac032257242cabcc7ff3369e2cb76a97aa/psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993", size = 279544, upload-time = "2025-02-13T21:54:24.68Z" }, + { url = "https://files.pythonhosted.org/packages/50/e6/eecf58810b9d12e6427369784efe814a1eec0f492084ce8eb8f4d89d6d61/psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99", size = 241053, upload-time = "2025-02-13T21:54:34.31Z" }, + { url = "https://files.pythonhosted.org/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885, upload-time = "2025-02-13T21:54:37.486Z" }, +] + +[[package]] +name = "ptyprocess" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762, upload-time = "2020-12-28T15:15:30.155Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993, upload-time = "2020-12-28T15:15:28.35Z" }, +] + +[[package]] +name = "pure-eval" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752, upload-time = "2024-07-21T12:58:21.801Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842, upload-time = "2024-07-21T12:58:20.04Z" }, +] + +[[package]] +name = "pyarrow" +version = "21.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ef/c2/ea068b8f00905c06329a3dfcd40d0fcc2b7d0f2e355bdb25b65e0a0e4cd4/pyarrow-21.0.0.tar.gz", hash = "sha256:5051f2dccf0e283ff56335760cbc8622cf52264d67e359d5569541ac11b6d5bc", size = 1133487, upload-time = "2025-07-18T00:57:31.761Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/d9/110de31880016e2afc52d8580b397dbe47615defbf09ca8cf55f56c62165/pyarrow-21.0.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:e563271e2c5ff4d4a4cbeb2c83d5cf0d4938b891518e676025f7268c6fe5fe26", size = 31196837, upload-time = "2025-07-18T00:54:34.755Z" }, + { url = "https://files.pythonhosted.org/packages/df/5f/c1c1997613abf24fceb087e79432d24c19bc6f7259cab57c2c8e5e545fab/pyarrow-21.0.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:fee33b0ca46f4c85443d6c450357101e47d53e6c3f008d658c27a2d020d44c79", size = 32659470, upload-time = "2025-07-18T00:54:38.329Z" }, + { url = "https://files.pythonhosted.org/packages/3e/ed/b1589a777816ee33ba123ba1e4f8f02243a844fed0deec97bde9fb21a5cf/pyarrow-21.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:7be45519b830f7c24b21d630a31d48bcebfd5d4d7f9d3bdb49da9cdf6d764edb", size = 41055619, upload-time = "2025-07-18T00:54:42.172Z" }, + { url = "https://files.pythonhosted.org/packages/44/28/b6672962639e85dc0ac36f71ab3a8f5f38e01b51343d7aa372a6b56fa3f3/pyarrow-21.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:26bfd95f6bff443ceae63c65dc7e048670b7e98bc892210acba7e4995d3d4b51", size = 42733488, upload-time = "2025-07-18T00:54:47.132Z" }, + { url = "https://files.pythonhosted.org/packages/f8/cc/de02c3614874b9089c94eac093f90ca5dfa6d5afe45de3ba847fd950fdf1/pyarrow-21.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:bd04ec08f7f8bd113c55868bd3fc442a9db67c27af098c5f814a3091e71cc61a", size = 43329159, upload-time = "2025-07-18T00:54:51.686Z" }, + { url = "https://files.pythonhosted.org/packages/a6/3e/99473332ac40278f196e105ce30b79ab8affab12f6194802f2593d6b0be2/pyarrow-21.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9b0b14b49ac10654332a805aedfc0147fb3469cbf8ea951b3d040dab12372594", size = 45050567, upload-time = "2025-07-18T00:54:56.679Z" }, + { url = "https://files.pythonhosted.org/packages/7b/f5/c372ef60593d713e8bfbb7e0c743501605f0ad00719146dc075faf11172b/pyarrow-21.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:9d9f8bcb4c3be7738add259738abdeddc363de1b80e3310e04067aa1ca596634", size = 26217959, upload-time = "2025-07-18T00:55:00.482Z" }, + { url = "https://files.pythonhosted.org/packages/94/dc/80564a3071a57c20b7c32575e4a0120e8a330ef487c319b122942d665960/pyarrow-21.0.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:c077f48aab61738c237802836fc3844f85409a46015635198761b0d6a688f87b", size = 31243234, upload-time = "2025-07-18T00:55:03.812Z" }, + { url = "https://files.pythonhosted.org/packages/ea/cc/3b51cb2db26fe535d14f74cab4c79b191ed9a8cd4cbba45e2379b5ca2746/pyarrow-21.0.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:689f448066781856237eca8d1975b98cace19b8dd2ab6145bf49475478bcaa10", size = 32714370, upload-time = "2025-07-18T00:55:07.495Z" }, + { url = "https://files.pythonhosted.org/packages/24/11/a4431f36d5ad7d83b87146f515c063e4d07ef0b7240876ddb885e6b44f2e/pyarrow-21.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:479ee41399fcddc46159a551705b89c05f11e8b8cb8e968f7fec64f62d91985e", size = 41135424, upload-time = "2025-07-18T00:55:11.461Z" }, + { url = "https://files.pythonhosted.org/packages/74/dc/035d54638fc5d2971cbf1e987ccd45f1091c83bcf747281cf6cc25e72c88/pyarrow-21.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:40ebfcb54a4f11bcde86bc586cbd0272bac0d516cfa539c799c2453768477569", size = 42823810, upload-time = "2025-07-18T00:55:16.301Z" }, + { url = "https://files.pythonhosted.org/packages/2e/3b/89fced102448a9e3e0d4dded1f37fa3ce4700f02cdb8665457fcc8015f5b/pyarrow-21.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8d58d8497814274d3d20214fbb24abcad2f7e351474357d552a8d53bce70c70e", size = 43391538, upload-time = "2025-07-18T00:55:23.82Z" }, + { url = "https://files.pythonhosted.org/packages/fb/bb/ea7f1bd08978d39debd3b23611c293f64a642557e8141c80635d501e6d53/pyarrow-21.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:585e7224f21124dd57836b1530ac8f2df2afc43c861d7bf3d58a4870c42ae36c", size = 45120056, upload-time = "2025-07-18T00:55:28.231Z" }, + { url = "https://files.pythonhosted.org/packages/6e/0b/77ea0600009842b30ceebc3337639a7380cd946061b620ac1a2f3cb541e2/pyarrow-21.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:555ca6935b2cbca2c0e932bedd853e9bc523098c39636de9ad4693b5b1df86d6", size = 26220568, upload-time = "2025-07-18T00:55:32.122Z" }, + { url = "https://files.pythonhosted.org/packages/ca/d4/d4f817b21aacc30195cf6a46ba041dd1be827efa4a623cc8bf39a1c2a0c0/pyarrow-21.0.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:3a302f0e0963db37e0a24a70c56cf91a4faa0bca51c23812279ca2e23481fccd", size = 31160305, upload-time = "2025-07-18T00:55:35.373Z" }, + { url = "https://files.pythonhosted.org/packages/a2/9c/dcd38ce6e4b4d9a19e1d36914cb8e2b1da4e6003dd075474c4cfcdfe0601/pyarrow-21.0.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:b6b27cf01e243871390474a211a7922bfbe3bda21e39bc9160daf0da3fe48876", size = 32684264, upload-time = "2025-07-18T00:55:39.303Z" }, + { url = "https://files.pythonhosted.org/packages/4f/74/2a2d9f8d7a59b639523454bec12dba35ae3d0a07d8ab529dc0809f74b23c/pyarrow-21.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:e72a8ec6b868e258a2cd2672d91f2860ad532d590ce94cdf7d5e7ec674ccf03d", size = 41108099, upload-time = "2025-07-18T00:55:42.889Z" }, + { url = "https://files.pythonhosted.org/packages/ad/90/2660332eeb31303c13b653ea566a9918484b6e4d6b9d2d46879a33ab0622/pyarrow-21.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b7ae0bbdc8c6674259b25bef5d2a1d6af5d39d7200c819cf99e07f7dfef1c51e", size = 42829529, upload-time = "2025-07-18T00:55:47.069Z" }, + { url = "https://files.pythonhosted.org/packages/33/27/1a93a25c92717f6aa0fca06eb4700860577d016cd3ae51aad0e0488ac899/pyarrow-21.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:58c30a1729f82d201627c173d91bd431db88ea74dcaa3885855bc6203e433b82", size = 43367883, upload-time = "2025-07-18T00:55:53.069Z" }, + { url = "https://files.pythonhosted.org/packages/05/d9/4d09d919f35d599bc05c6950095e358c3e15148ead26292dfca1fb659b0c/pyarrow-21.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:072116f65604b822a7f22945a7a6e581cfa28e3454fdcc6939d4ff6090126623", size = 45133802, upload-time = "2025-07-18T00:55:57.714Z" }, + { url = "https://files.pythonhosted.org/packages/71/30/f3795b6e192c3ab881325ffe172e526499eb3780e306a15103a2764916a2/pyarrow-21.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:cf56ec8b0a5c8c9d7021d6fd754e688104f9ebebf1bf4449613c9531f5346a18", size = 26203175, upload-time = "2025-07-18T00:56:01.364Z" }, + { url = "https://files.pythonhosted.org/packages/16/ca/c7eaa8e62db8fb37ce942b1ea0c6d7abfe3786ca193957afa25e71b81b66/pyarrow-21.0.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:e99310a4ebd4479bcd1964dff9e14af33746300cb014aa4a3781738ac63baf4a", size = 31154306, upload-time = "2025-07-18T00:56:04.42Z" }, + { url = "https://files.pythonhosted.org/packages/ce/e8/e87d9e3b2489302b3a1aea709aaca4b781c5252fcb812a17ab6275a9a484/pyarrow-21.0.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:d2fe8e7f3ce329a71b7ddd7498b3cfac0eeb200c2789bd840234f0dc271a8efe", size = 32680622, upload-time = "2025-07-18T00:56:07.505Z" }, + { url = "https://files.pythonhosted.org/packages/84/52/79095d73a742aa0aba370c7942b1b655f598069489ab387fe47261a849e1/pyarrow-21.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:f522e5709379d72fb3da7785aa489ff0bb87448a9dc5a75f45763a795a089ebd", size = 41104094, upload-time = "2025-07-18T00:56:10.994Z" }, + { url = "https://files.pythonhosted.org/packages/89/4b/7782438b551dbb0468892a276b8c789b8bbdb25ea5c5eb27faadd753e037/pyarrow-21.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:69cbbdf0631396e9925e048cfa5bce4e8c3d3b41562bbd70c685a8eb53a91e61", size = 42825576, upload-time = "2025-07-18T00:56:15.569Z" }, + { url = "https://files.pythonhosted.org/packages/b3/62/0f29de6e0a1e33518dec92c65be0351d32d7ca351e51ec5f4f837a9aab91/pyarrow-21.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:731c7022587006b755d0bdb27626a1a3bb004bb56b11fb30d98b6c1b4718579d", size = 43368342, upload-time = "2025-07-18T00:56:19.531Z" }, + { url = "https://files.pythonhosted.org/packages/90/c7/0fa1f3f29cf75f339768cc698c8ad4ddd2481c1742e9741459911c9ac477/pyarrow-21.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dc56bc708f2d8ac71bd1dcb927e458c93cec10b98eb4120206a4091db7b67b99", size = 45131218, upload-time = "2025-07-18T00:56:23.347Z" }, + { url = "https://files.pythonhosted.org/packages/01/63/581f2076465e67b23bc5a37d4a2abff8362d389d29d8105832e82c9c811c/pyarrow-21.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:186aa00bca62139f75b7de8420f745f2af12941595bbbfa7ed3870ff63e25636", size = 26087551, upload-time = "2025-07-18T00:56:26.758Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ab/357d0d9648bb8241ee7348e564f2479d206ebe6e1c47ac5027c2e31ecd39/pyarrow-21.0.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:a7a102574faa3f421141a64c10216e078df467ab9576684d5cd696952546e2da", size = 31290064, upload-time = "2025-07-18T00:56:30.214Z" }, + { url = "https://files.pythonhosted.org/packages/3f/8a/5685d62a990e4cac2043fc76b4661bf38d06efed55cf45a334b455bd2759/pyarrow-21.0.0-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:1e005378c4a2c6db3ada3ad4c217b381f6c886f0a80d6a316fe586b90f77efd7", size = 32727837, upload-time = "2025-07-18T00:56:33.935Z" }, + { url = "https://files.pythonhosted.org/packages/fc/de/c0828ee09525c2bafefd3e736a248ebe764d07d0fd762d4f0929dbc516c9/pyarrow-21.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:65f8e85f79031449ec8706b74504a316805217b35b6099155dd7e227eef0d4b6", size = 41014158, upload-time = "2025-07-18T00:56:37.528Z" }, + { url = "https://files.pythonhosted.org/packages/6e/26/a2865c420c50b7a3748320b614f3484bfcde8347b2639b2b903b21ce6a72/pyarrow-21.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:3a81486adc665c7eb1a2bde0224cfca6ceaba344a82a971ef059678417880eb8", size = 42667885, upload-time = "2025-07-18T00:56:41.483Z" }, + { url = "https://files.pythonhosted.org/packages/0a/f9/4ee798dc902533159250fb4321267730bc0a107d8c6889e07c3add4fe3a5/pyarrow-21.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:fc0d2f88b81dcf3ccf9a6ae17f89183762c8a94a5bdcfa09e05cfe413acf0503", size = 43276625, upload-time = "2025-07-18T00:56:48.002Z" }, + { url = "https://files.pythonhosted.org/packages/5a/da/e02544d6997037a4b0d22d8e5f66bc9315c3671371a8b18c79ade1cefe14/pyarrow-21.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6299449adf89df38537837487a4f8d3bd91ec94354fdd2a7d30bc11c48ef6e79", size = 44951890, upload-time = "2025-07-18T00:56:52.568Z" }, + { url = "https://files.pythonhosted.org/packages/e5/4e/519c1bc1876625fe6b71e9a28287c43ec2f20f73c658b9ae1d485c0c206e/pyarrow-21.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:222c39e2c70113543982c6b34f3077962b44fca38c0bd9e68bb6781534425c10", size = 26371006, upload-time = "2025-07-18T00:56:56.379Z" }, +] + +[[package]] +name = "pycairo" +version = "1.28.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/40/d9/412da520de9052b7e80bfc810ec10f5cb3dbfa4aa3e23c2820dc61cdb3d0/pycairo-1.28.0.tar.gz", hash = "sha256:26ec5c6126781eb167089a123919f87baa2740da2cca9098be8b3a6b91cc5fbc", size = 662477, upload-time = "2025-04-14T20:11:08.218Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3e/67/6e5d6328c6037f0479017f51e97f5cbb79a68ed6e93f671dac17cb47bf2e/pycairo-1.28.0-cp310-cp310-win32.whl", hash = "sha256:53e6dbc98456f789965dad49ef89ce2c62f9a10fc96c8d084e14da0ffb73d8a6", size = 750438, upload-time = "2025-04-14T20:10:43.722Z" }, + { url = "https://files.pythonhosted.org/packages/00/79/e941186c2275333643504f57711b157ba17e81a2be805346585ad7fd382e/pycairo-1.28.0-cp310-cp310-win_amd64.whl", hash = "sha256:c8ab91a75025f984bc327ada335c787efb61c929ea0512063793cb36cee503d4", size = 841526, upload-time = "2025-04-14T20:10:45.557Z" }, + { url = "https://files.pythonhosted.org/packages/84/4b/3495baabd3c830bf80bf112a0e645342bfe8f3fc05ed6423444adf62d496/pycairo-1.28.0-cp310-cp310-win_arm64.whl", hash = "sha256:e955328c1a5147bf71ee94e206413ce15e12630296a79788fcd246c80e5337b8", size = 691866, upload-time = "2025-04-16T19:30:17.73Z" }, + { url = "https://files.pythonhosted.org/packages/c9/94/47d75f4eaac865f32e0550ed42869f8b3c4036f8e2b1e6163e9548f4d44f/pycairo-1.28.0-cp311-cp311-win32.whl", hash = "sha256:0fee15f5d72b13ba5fd065860312493dc1bca6ff2dce200ee9d704e11c94e60a", size = 750441, upload-time = "2025-04-14T20:10:48.311Z" }, + { url = "https://files.pythonhosted.org/packages/aa/02/1169a2917b043998585f9dd0f36da618947fdf9b6cc0e9ff9852aa4d548b/pycairo-1.28.0-cp311-cp311-win_amd64.whl", hash = "sha256:6339979bfec8b58a06476094a9a5c104bd5a99932ddaff16ca0d9203d2f4482c", size = 841536, upload-time = "2025-04-14T20:10:51.112Z" }, + { url = "https://files.pythonhosted.org/packages/eb/99/13031086fb4d4ea71568d9ce74e03afbb2e18047f1e6b4e8674851f0414f/pycairo-1.28.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6ae15392e28ebfc0b35d8dc05d395d3b6be4bad9ad4caecf0fa12c8e7150225", size = 691895, upload-time = "2025-04-16T19:30:20.724Z" }, + { url = "https://files.pythonhosted.org/packages/a9/d7/206d7945aac5c6c889e7fea4011410d1311455a1d8dfce66106d8d700b7f/pycairo-1.28.0-cp312-cp312-win32.whl", hash = "sha256:c00cfbb7f30eb7ca1d48886712932e2d91e8835a8496f4e423878296ceba573e", size = 750594, upload-time = "2025-04-14T20:10:53.72Z" }, + { url = "https://files.pythonhosted.org/packages/2e/ba/259fc9a4d3afdad1e5b9db52b9d8a5df67f70dd787167185e8ec8a1af007/pycairo-1.28.0-cp312-cp312-win_amd64.whl", hash = "sha256:d50d190f5033992b55050b9f337ee42a45c3568445d5e5d7987bab96c278d8a6", size = 841767, upload-time = "2025-04-14T20:10:56.711Z" }, + { url = "https://files.pythonhosted.org/packages/c0/08/aa0903612ea0e8686ad6af58d4fb9cbab8ee0b0831201cddf7abd578d045/pycairo-1.28.0-cp312-cp312-win_arm64.whl", hash = "sha256:957e0340ee1c279d197d4f7cfa96f6d8b48e453eec711fca999748d752468ff4", size = 691687, upload-time = "2025-04-16T19:30:23.729Z" }, + { url = "https://files.pythonhosted.org/packages/59/a7/c3e5ed55781dfe1b31eb4a2482aeae707671f3d36b0ea53a1722f4a3dfe9/pycairo-1.28.0-cp313-cp313-win32.whl", hash = "sha256:d13352429d8a08a1cb3607767d23d2fb32e4c4f9faa642155383980ec1478c24", size = 750594, upload-time = "2025-04-14T20:10:59.284Z" }, + { url = "https://files.pythonhosted.org/packages/8b/1c/ebadd290748aff3b6bc35431114d41e7a42f40a4b988c2aaf2dfed5d8156/pycairo-1.28.0-cp313-cp313-win_amd64.whl", hash = "sha256:082aef6b3a9dcc328fa648d38ed6b0a31c863e903ead57dd184b2e5f86790140", size = 841774, upload-time = "2025-04-14T20:11:01.79Z" }, + { url = "https://files.pythonhosted.org/packages/3e/ce/a3f5f1946613cd8a4654322b878c59f273c6e9b01dfadadd3f609070e0b9/pycairo-1.28.0-cp313-cp313-win_arm64.whl", hash = "sha256:026afd53b75291917a7412d9fe46dcfbaa0c028febd46ff1132d44a53ac2c8b6", size = 691675, upload-time = "2025-04-16T19:30:26.565Z" }, + { url = "https://files.pythonhosted.org/packages/96/1b/1f5da2b2e44b33a5b269ff28af710b0a7903001d9cf8a40d00fc944a5092/pycairo-1.28.0-cp314-cp314-win32.whl", hash = "sha256:d0ab30585f536101ad6f09052fc3895e2a437ba57531ea07223d0e076248025d", size = 766699, upload-time = "2025-07-24T06:36:07.267Z" }, + { url = "https://files.pythonhosted.org/packages/c9/91/1d5f18bd3ed469b1de8f83bfbf8bd67d23439f075151b66740fd35356190/pycairo-1.28.0-cp314-cp314-win_amd64.whl", hash = "sha256:94f2ed204999ab95a0671a0fa948ffbb9f3d6fb8731fe787917f6d022d9c1c0f", size = 868725, upload-time = "2025-07-24T06:36:09.597Z" }, +] + +[[package]] +name = "pycparser" +version = "2.23" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, +] + +[[package]] +name = "pydantic" +version = "2.11.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db", size = 788350, upload-time = "2025-06-14T08:33:17.137Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782, upload-time = "2025-06-14T08:33:14.905Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.33.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/92/b31726561b5dae176c2d2c2dc43a9c5bfba5d32f96f8b4c0a600dd492447/pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8", size = 2028817, upload-time = "2025-04-23T18:30:43.919Z" }, + { url = "https://files.pythonhosted.org/packages/a3/44/3f0b95fafdaca04a483c4e685fe437c6891001bf3ce8b2fded82b9ea3aa1/pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d", size = 1861357, upload-time = "2025-04-23T18:30:46.372Z" }, + { url = "https://files.pythonhosted.org/packages/30/97/e8f13b55766234caae05372826e8e4b3b96e7b248be3157f53237682e43c/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d", size = 1898011, upload-time = "2025-04-23T18:30:47.591Z" }, + { url = "https://files.pythonhosted.org/packages/9b/a3/99c48cf7bafc991cc3ee66fd544c0aae8dc907b752f1dad2d79b1b5a471f/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d53b22f2032c42eaaf025f7c40c2e3b94568ae077a606f006d206a463bc69572", size = 1982730, upload-time = "2025-04-23T18:30:49.328Z" }, + { url = "https://files.pythonhosted.org/packages/de/8e/a5b882ec4307010a840fb8b58bd9bf65d1840c92eae7534c7441709bf54b/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0405262705a123b7ce9f0b92f123334d67b70fd1f20a9372b907ce1080c7ba02", size = 2136178, upload-time = "2025-04-23T18:30:50.907Z" }, + { url = "https://files.pythonhosted.org/packages/e4/bb/71e35fc3ed05af6834e890edb75968e2802fe98778971ab5cba20a162315/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b25d91e288e2c4e0662b8038a28c6a07eaac3e196cfc4ff69de4ea3db992a1b", size = 2736462, upload-time = "2025-04-23T18:30:52.083Z" }, + { url = "https://files.pythonhosted.org/packages/31/0d/c8f7593e6bc7066289bbc366f2235701dcbebcd1ff0ef8e64f6f239fb47d/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bdfe4b3789761f3bcb4b1ddf33355a71079858958e3a552f16d5af19768fef2", size = 2005652, upload-time = "2025-04-23T18:30:53.389Z" }, + { url = "https://files.pythonhosted.org/packages/d2/7a/996d8bd75f3eda405e3dd219ff5ff0a283cd8e34add39d8ef9157e722867/pydantic_core-2.33.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:efec8db3266b76ef9607c2c4c419bdb06bf335ae433b80816089ea7585816f6a", size = 2113306, upload-time = "2025-04-23T18:30:54.661Z" }, + { url = "https://files.pythonhosted.org/packages/ff/84/daf2a6fb2db40ffda6578a7e8c5a6e9c8affb251a05c233ae37098118788/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:031c57d67ca86902726e0fae2214ce6770bbe2f710dc33063187a68744a5ecac", size = 2073720, upload-time = "2025-04-23T18:30:56.11Z" }, + { url = "https://files.pythonhosted.org/packages/77/fb/2258da019f4825128445ae79456a5499c032b55849dbd5bed78c95ccf163/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:f8de619080e944347f5f20de29a975c2d815d9ddd8be9b9b7268e2e3ef68605a", size = 2244915, upload-time = "2025-04-23T18:30:57.501Z" }, + { url = "https://files.pythonhosted.org/packages/d8/7a/925ff73756031289468326e355b6fa8316960d0d65f8b5d6b3a3e7866de7/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:73662edf539e72a9440129f231ed3757faab89630d291b784ca99237fb94db2b", size = 2241884, upload-time = "2025-04-23T18:30:58.867Z" }, + { url = "https://files.pythonhosted.org/packages/0b/b0/249ee6d2646f1cdadcb813805fe76265745c4010cf20a8eba7b0e639d9b2/pydantic_core-2.33.2-cp310-cp310-win32.whl", hash = "sha256:0a39979dcbb70998b0e505fb1556a1d550a0781463ce84ebf915ba293ccb7e22", size = 1910496, upload-time = "2025-04-23T18:31:00.078Z" }, + { url = "https://files.pythonhosted.org/packages/66/ff/172ba8f12a42d4b552917aa65d1f2328990d3ccfc01d5b7c943ec084299f/pydantic_core-2.33.2-cp310-cp310-win_amd64.whl", hash = "sha256:b0379a2b24882fef529ec3b4987cb5d003b9cda32256024e6fe1586ac45fc640", size = 1955019, upload-time = "2025-04-23T18:31:01.335Z" }, + { url = "https://files.pythonhosted.org/packages/3f/8d/71db63483d518cbbf290261a1fc2839d17ff89fce7089e08cad07ccfce67/pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7", size = 2028584, upload-time = "2025-04-23T18:31:03.106Z" }, + { url = "https://files.pythonhosted.org/packages/24/2f/3cfa7244ae292dd850989f328722d2aef313f74ffc471184dc509e1e4e5a/pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246", size = 1855071, upload-time = "2025-04-23T18:31:04.621Z" }, + { url = "https://files.pythonhosted.org/packages/b3/d3/4ae42d33f5e3f50dd467761304be2fa0a9417fbf09735bc2cce003480f2a/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f", size = 1897823, upload-time = "2025-04-23T18:31:06.377Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f3/aa5976e8352b7695ff808599794b1fba2a9ae2ee954a3426855935799488/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc", size = 1983792, upload-time = "2025-04-23T18:31:07.93Z" }, + { url = "https://files.pythonhosted.org/packages/d5/7a/cda9b5a23c552037717f2b2a5257e9b2bfe45e687386df9591eff7b46d28/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de", size = 2136338, upload-time = "2025-04-23T18:31:09.283Z" }, + { url = "https://files.pythonhosted.org/packages/2b/9f/b8f9ec8dd1417eb9da784e91e1667d58a2a4a7b7b34cf4af765ef663a7e5/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a", size = 2730998, upload-time = "2025-04-23T18:31:11.7Z" }, + { url = "https://files.pythonhosted.org/packages/47/bc/cd720e078576bdb8255d5032c5d63ee5c0bf4b7173dd955185a1d658c456/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef", size = 2003200, upload-time = "2025-04-23T18:31:13.536Z" }, + { url = "https://files.pythonhosted.org/packages/ca/22/3602b895ee2cd29d11a2b349372446ae9727c32e78a94b3d588a40fdf187/pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e", size = 2113890, upload-time = "2025-04-23T18:31:15.011Z" }, + { url = "https://files.pythonhosted.org/packages/ff/e6/e3c5908c03cf00d629eb38393a98fccc38ee0ce8ecce32f69fc7d7b558a7/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d", size = 2073359, upload-time = "2025-04-23T18:31:16.393Z" }, + { url = "https://files.pythonhosted.org/packages/12/e7/6a36a07c59ebefc8777d1ffdaf5ae71b06b21952582e4b07eba88a421c79/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30", size = 2245883, upload-time = "2025-04-23T18:31:17.892Z" }, + { url = "https://files.pythonhosted.org/packages/16/3f/59b3187aaa6cc0c1e6616e8045b284de2b6a87b027cce2ffcea073adf1d2/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf", size = 2241074, upload-time = "2025-04-23T18:31:19.205Z" }, + { url = "https://files.pythonhosted.org/packages/e0/ed/55532bb88f674d5d8f67ab121a2a13c385df382de2a1677f30ad385f7438/pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51", size = 1910538, upload-time = "2025-04-23T18:31:20.541Z" }, + { url = "https://files.pythonhosted.org/packages/fe/1b/25b7cccd4519c0b23c2dd636ad39d381abf113085ce4f7bec2b0dc755eb1/pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab", size = 1952909, upload-time = "2025-04-23T18:31:22.371Z" }, + { url = "https://files.pythonhosted.org/packages/49/a9/d809358e49126438055884c4366a1f6227f0f84f635a9014e2deb9b9de54/pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65", size = 1897786, upload-time = "2025-04-23T18:31:24.161Z" }, + { url = "https://files.pythonhosted.org/packages/18/8a/2b41c97f554ec8c71f2a8a5f85cb56a8b0956addfe8b0efb5b3d77e8bdc3/pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc", size = 2009000, upload-time = "2025-04-23T18:31:25.863Z" }, + { url = "https://files.pythonhosted.org/packages/a1/02/6224312aacb3c8ecbaa959897af57181fb6cf3a3d7917fd44d0f2917e6f2/pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7", size = 1847996, upload-time = "2025-04-23T18:31:27.341Z" }, + { url = "https://files.pythonhosted.org/packages/d6/46/6dcdf084a523dbe0a0be59d054734b86a981726f221f4562aed313dbcb49/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025", size = 1880957, upload-time = "2025-04-23T18:31:28.956Z" }, + { url = "https://files.pythonhosted.org/packages/ec/6b/1ec2c03837ac00886ba8160ce041ce4e325b41d06a034adbef11339ae422/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011", size = 1964199, upload-time = "2025-04-23T18:31:31.025Z" }, + { url = "https://files.pythonhosted.org/packages/2d/1d/6bf34d6adb9debd9136bd197ca72642203ce9aaaa85cfcbfcf20f9696e83/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f", size = 2120296, upload-time = "2025-04-23T18:31:32.514Z" }, + { url = "https://files.pythonhosted.org/packages/e0/94/2bd0aaf5a591e974b32a9f7123f16637776c304471a0ab33cf263cf5591a/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88", size = 2676109, upload-time = "2025-04-23T18:31:33.958Z" }, + { url = "https://files.pythonhosted.org/packages/f9/41/4b043778cf9c4285d59742281a769eac371b9e47e35f98ad321349cc5d61/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1", size = 2002028, upload-time = "2025-04-23T18:31:39.095Z" }, + { url = "https://files.pythonhosted.org/packages/cb/d5/7bb781bf2748ce3d03af04d5c969fa1308880e1dca35a9bd94e1a96a922e/pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b", size = 2100044, upload-time = "2025-04-23T18:31:41.034Z" }, + { url = "https://files.pythonhosted.org/packages/fe/36/def5e53e1eb0ad896785702a5bbfd25eed546cdcf4087ad285021a90ed53/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1", size = 2058881, upload-time = "2025-04-23T18:31:42.757Z" }, + { url = "https://files.pythonhosted.org/packages/01/6c/57f8d70b2ee57fc3dc8b9610315949837fa8c11d86927b9bb044f8705419/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6", size = 2227034, upload-time = "2025-04-23T18:31:44.304Z" }, + { url = "https://files.pythonhosted.org/packages/27/b9/9c17f0396a82b3d5cbea4c24d742083422639e7bb1d5bf600e12cb176a13/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea", size = 2234187, upload-time = "2025-04-23T18:31:45.891Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6a/adf5734ffd52bf86d865093ad70b2ce543415e0e356f6cacabbc0d9ad910/pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290", size = 1892628, upload-time = "2025-04-23T18:31:47.819Z" }, + { url = "https://files.pythonhosted.org/packages/43/e4/5479fecb3606c1368d496a825d8411e126133c41224c1e7238be58b87d7e/pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2", size = 1955866, upload-time = "2025-04-23T18:31:49.635Z" }, + { url = "https://files.pythonhosted.org/packages/0d/24/8b11e8b3e2be9dd82df4b11408a67c61bb4dc4f8e11b5b0fc888b38118b5/pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab", size = 1888894, upload-time = "2025-04-23T18:31:51.609Z" }, + { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688, upload-time = "2025-04-23T18:31:53.175Z" }, + { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808, upload-time = "2025-04-23T18:31:54.79Z" }, + { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580, upload-time = "2025-04-23T18:31:57.393Z" }, + { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859, upload-time = "2025-04-23T18:31:59.065Z" }, + { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810, upload-time = "2025-04-23T18:32:00.78Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498, upload-time = "2025-04-23T18:32:02.418Z" }, + { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611, upload-time = "2025-04-23T18:32:04.152Z" }, + { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924, upload-time = "2025-04-23T18:32:06.129Z" }, + { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196, upload-time = "2025-04-23T18:32:08.178Z" }, + { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389, upload-time = "2025-04-23T18:32:10.242Z" }, + { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223, upload-time = "2025-04-23T18:32:12.382Z" }, + { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" }, + { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" }, + { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" }, + { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162, upload-time = "2025-04-23T18:32:20.188Z" }, + { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560, upload-time = "2025-04-23T18:32:22.354Z" }, + { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" }, + { url = "https://files.pythonhosted.org/packages/30/68/373d55e58b7e83ce371691f6eaa7175e3a24b956c44628eb25d7da007917/pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c4aa4e82353f65e548c476b37e64189783aa5384903bfea4f41580f255fddfa", size = 2023982, upload-time = "2025-04-23T18:32:53.14Z" }, + { url = "https://files.pythonhosted.org/packages/a4/16/145f54ac08c96a63d8ed6442f9dec17b2773d19920b627b18d4f10a061ea/pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d946c8bf0d5c24bf4fe333af284c59a19358aa3ec18cb3dc4370080da1e8ad29", size = 1858412, upload-time = "2025-04-23T18:32:55.52Z" }, + { url = "https://files.pythonhosted.org/packages/41/b1/c6dc6c3e2de4516c0bb2c46f6a373b91b5660312342a0cf5826e38ad82fa/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87b31b6846e361ef83fedb187bb5b4372d0da3f7e28d85415efa92d6125d6e6d", size = 1892749, upload-time = "2025-04-23T18:32:57.546Z" }, + { url = "https://files.pythonhosted.org/packages/12/73/8cd57e20afba760b21b742106f9dbdfa6697f1570b189c7457a1af4cd8a0/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa9d91b338f2df0508606f7009fde642391425189bba6d8c653afd80fd6bb64e", size = 2067527, upload-time = "2025-04-23T18:32:59.771Z" }, + { url = "https://files.pythonhosted.org/packages/e3/d5/0bb5d988cc019b3cba4a78f2d4b3854427fc47ee8ec8e9eaabf787da239c/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2058a32994f1fde4ca0480ab9d1e75a0e8c87c22b53a3ae66554f9af78f2fe8c", size = 2108225, upload-time = "2025-04-23T18:33:04.51Z" }, + { url = "https://files.pythonhosted.org/packages/f1/c5/00c02d1571913d496aabf146106ad8239dc132485ee22efe08085084ff7c/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:0e03262ab796d986f978f79c943fc5f620381be7287148b8010b4097f79a39ec", size = 2069490, upload-time = "2025-04-23T18:33:06.391Z" }, + { url = "https://files.pythonhosted.org/packages/22/a8/dccc38768274d3ed3a59b5d06f59ccb845778687652daa71df0cab4040d7/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1a8695a8d00c73e50bff9dfda4d540b7dee29ff9b8053e38380426a85ef10052", size = 2237525, upload-time = "2025-04-23T18:33:08.44Z" }, + { url = "https://files.pythonhosted.org/packages/d4/e7/4f98c0b125dda7cf7ccd14ba936218397b44f50a56dd8c16a3091df116c3/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fa754d1850735a0b0e03bcffd9d4b4343eb417e47196e4485d9cca326073a42c", size = 2238446, upload-time = "2025-04-23T18:33:10.313Z" }, + { url = "https://files.pythonhosted.org/packages/ce/91/2ec36480fdb0b783cd9ef6795753c1dea13882f2e68e73bce76ae8c21e6a/pydantic_core-2.33.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a11c8d26a50bfab49002947d3d237abe4d9e4b5bdc8846a63537b6488e197808", size = 2066678, upload-time = "2025-04-23T18:33:12.224Z" }, + { url = "https://files.pythonhosted.org/packages/7b/27/d4ae6487d73948d6f20dddcd94be4ea43e74349b56eba82e9bdee2d7494c/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8", size = 2025200, upload-time = "2025-04-23T18:33:14.199Z" }, + { url = "https://files.pythonhosted.org/packages/f1/b8/b3cb95375f05d33801024079b9392a5ab45267a63400bf1866e7ce0f0de4/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593", size = 1859123, upload-time = "2025-04-23T18:33:16.555Z" }, + { url = "https://files.pythonhosted.org/packages/05/bc/0d0b5adeda59a261cd30a1235a445bf55c7e46ae44aea28f7bd6ed46e091/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612", size = 1892852, upload-time = "2025-04-23T18:33:18.513Z" }, + { url = "https://files.pythonhosted.org/packages/3e/11/d37bdebbda2e449cb3f519f6ce950927b56d62f0b84fd9cb9e372a26a3d5/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7", size = 2067484, upload-time = "2025-04-23T18:33:20.475Z" }, + { url = "https://files.pythonhosted.org/packages/8c/55/1f95f0a05ce72ecb02a8a8a1c3be0579bbc29b1d5ab68f1378b7bebc5057/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e", size = 2108896, upload-time = "2025-04-23T18:33:22.501Z" }, + { url = "https://files.pythonhosted.org/packages/53/89/2b2de6c81fa131f423246a9109d7b2a375e83968ad0800d6e57d0574629b/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8", size = 2069475, upload-time = "2025-04-23T18:33:24.528Z" }, + { url = "https://files.pythonhosted.org/packages/b8/e9/1f7efbe20d0b2b10f6718944b5d8ece9152390904f29a78e68d4e7961159/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf", size = 2239013, upload-time = "2025-04-23T18:33:26.621Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b2/5309c905a93811524a49b4e031e9851a6b00ff0fb668794472ea7746b448/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb", size = 2238715, upload-time = "2025-04-23T18:33:28.656Z" }, + { url = "https://files.pythonhosted.org/packages/32/56/8a7ca5d2cd2cda1d245d34b1c9a942920a718082ae8e54e5f3e5a58b7add/pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1", size = 2066757, upload-time = "2025-04-23T18:33:30.645Z" }, +] + +[[package]] +name = "pyee" +version = "11.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/10/88/c9a028db29f009170db6337a5096476a12448ffce437bbf91458fd5b5767/pyee-11.0.1.tar.gz", hash = "sha256:a642c51e3885a33ead087286e35212783a4e9b8d6514a10a5db4e57ac57b2b29", size = 27877, upload-time = "2023-10-14T17:50:00.259Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/40/bfe7fd2cb55ca7dbb4c56e9f0060567c3e84dd9bdf8a782261f1d2d7c32f/pyee-11.0.1-py3-none-any.whl", hash = "sha256:9bcc9647822234f42c228d88de63d0f9ffa881e87a87f9d36ddf5211f6ac977d", size = 15249, upload-time = "2023-10-14T17:49:57.906Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pygobject" +version = "3.46.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycairo" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/4a/f24ddf1d20cc4b56affc7921e29928559a06c922eb60077448392792b914/PyGObject-3.46.0.tar.gz", hash = "sha256:481437b05af0a66b7c366ea052710eb3aacbb979d22d30b797f7ec29347ab1e6", size = 723367, upload-time = "2023-09-10T09:56:26.737Z" } + +[[package]] +name = "pyngrok" +version = "7.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7b/64/e5646daaa4a84ecb368c09d42753f1c6e1a41b38579a36fda8ffe5716d9f/pyngrok-7.3.0.tar.gz", hash = "sha256:f2240e49b9dec9b27eba00f1444cd3cce7ac45d1bcc493a9b76ee63c2da9882c", size = 44266, upload-time = "2025-08-04T16:08:07.015Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ac/6b/664772910199399db3706243488eb3a986bd8435583caef9cf8359eecfaa/pyngrok-7.3.0-py3-none-any.whl", hash = "sha256:708c48276d3a887d4263f0f4bc8171e22d66df6cfa74a4857625c83160603166", size = 25394, upload-time = "2025-08-04T16:08:05.368Z" }, +] + +[[package]] +name = "pynput" +version = "1.8.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "evdev", marker = "'linux' in sys_platform" }, + { name = "pyobjc-framework-applicationservices", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, + { name = "python-xlib", marker = "'linux' in sys_platform" }, + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f0/c3/dccf44c68225046df5324db0cc7d563a560635355b3e5f1d249468268a6f/pynput-1.8.1.tar.gz", hash = "sha256:70d7c8373ee98911004a7c938742242840a5628c004573d84ba849d4601df81e", size = 82289, upload-time = "2025-03-17T17:12:01.481Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/59/4f/ac3fa906ae8a375a536b12794128c5efacade9eaa917a35dfd27ce0c7400/pynput-1.8.1-py2.py3-none-any.whl", hash = "sha256:42dfcf27404459ca16ca889c8fb8ffe42a9fe54f722fd1a3e130728e59e768d2", size = 91693, upload-time = "2025-03-17T17:12:00.094Z" }, +] + +[[package]] +name = "pyobjc-core" +version = "11.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/e9/0b85c81e2b441267bca707b5d89f56c2f02578ef8f3eafddf0e0c0b8848c/pyobjc_core-11.1.tar.gz", hash = "sha256:b63d4d90c5df7e762f34739b39cc55bc63dbcf9fb2fb3f2671e528488c7a87fe", size = 974602, upload-time = "2025-06-14T20:56:34.189Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/c5/9fa74ef6b83924e657c5098d37b36b66d1e16d13bc45c44248c6248e7117/pyobjc_core-11.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4c7536f3e94de0a3eae6bb382d75f1219280aa867cdf37beef39d9e7d580173c", size = 676323, upload-time = "2025-06-14T20:44:44.675Z" }, + { url = "https://files.pythonhosted.org/packages/5a/a7/55afc166d89e3fcd87966f48f8bca3305a3a2d7c62100715b9ffa7153a90/pyobjc_core-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ec36680b5c14e2f73d432b03ba7c1457dc6ca70fa59fd7daea1073f2b4157d33", size = 671075, upload-time = "2025-06-14T20:44:46.594Z" }, + { url = "https://files.pythonhosted.org/packages/c0/09/e83228e878e73bf756749939f906a872da54488f18d75658afa7f1abbab1/pyobjc_core-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:765b97dea6b87ec4612b3212258024d8496ea23517c95a1c5f0735f96b7fd529", size = 677985, upload-time = "2025-06-14T20:44:48.375Z" }, + { url = "https://files.pythonhosted.org/packages/c5/24/12e4e2dae5f85fd0c0b696404ed3374ea6ca398e7db886d4f1322eb30799/pyobjc_core-11.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:18986f83998fbd5d3f56d8a8428b2f3e0754fd15cef3ef786ca0d29619024f2c", size = 676431, upload-time = "2025-06-14T20:44:49.908Z" }, + { url = "https://files.pythonhosted.org/packages/f7/79/031492497624de4c728f1857181b06ce8c56444db4d49418fa459cba217c/pyobjc_core-11.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:8849e78cfe6595c4911fbba29683decfb0bf57a350aed8a43316976ba6f659d2", size = 719330, upload-time = "2025-06-14T20:44:51.621Z" }, + { url = "https://files.pythonhosted.org/packages/ed/7d/6169f16a0c7ec15b9381f8bf33872baf912de2ef68d96c798ca4c6ee641f/pyobjc_core-11.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:8cb9ed17a8d84a312a6e8b665dd22393d48336ea1d8277e7ad20c19a38edf731", size = 667203, upload-time = "2025-06-14T20:44:53.262Z" }, + { url = "https://files.pythonhosted.org/packages/49/0f/f5ab2b0e57430a3bec9a62b6153c0e79c05a30d77b564efdb9f9446eeac5/pyobjc_core-11.1-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:f2455683e807f8541f0d83fbba0f5d9a46128ab0d5cc83ea208f0bec759b7f96", size = 708807, upload-time = "2025-06-14T20:44:54.851Z" }, +] + +[[package]] +name = "pyobjc-framework-applicationservices" +version = "11.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-coretext" }, + { name = "pyobjc-framework-quartz" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/be/3f/b33ce0cecc3a42f6c289dcbf9ff698b0d9e85f5796db2e9cb5dadccffbb9/pyobjc_framework_applicationservices-11.1.tar.gz", hash = "sha256:03fcd8c0c600db98fa8b85eb7b3bc31491701720c795e3f762b54e865138bbaf", size = 224842, upload-time = "2025-06-14T20:56:40.648Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/2b/b46566639b13354d348092f932b4debda2e8604c9b1b416eb3619676e997/pyobjc_framework_applicationservices-11.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:89aa713f16f1de66efd82f3be77c632ad1068e51e0ef0c2b0237ac7c7f580814", size = 30991, upload-time = "2025-06-14T20:45:17.223Z" }, + { url = "https://files.pythonhosted.org/packages/39/2d/9fde6de0b2a95fbb3d77ba11b3cc4f289dd208f38cb3a28389add87c0f44/pyobjc_framework_applicationservices-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cf45d15eddae36dec2330a9992fc852476b61c8f529874b9ec2805c768a75482", size = 30991, upload-time = "2025-06-14T20:45:18.169Z" }, + { url = "https://files.pythonhosted.org/packages/38/ec/46a5c710e2d7edf55105223c34fed5a7b7cc7aba7d00a3a7b0405d6a2d1a/pyobjc_framework_applicationservices-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f4a85ccd78bab84f7f05ac65ff9be117839dfc09d48c39edd65c617ed73eb01c", size = 31056, upload-time = "2025-06-14T20:45:18.925Z" }, + { url = "https://files.pythonhosted.org/packages/c4/06/c2a309e6f37bfa73a2a581d3301321b2033e25b249e2a01e417a3c34e799/pyobjc_framework_applicationservices-11.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:385a89f4d0838c97a331e247519d9e9745aa3f7427169d18570e3c664076a63c", size = 31072, upload-time = "2025-06-14T20:45:19.707Z" }, + { url = "https://files.pythonhosted.org/packages/b4/5f/357bf498c27f1b4d48385860d8374b2569adc1522aabe32befd77089c070/pyobjc_framework_applicationservices-11.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f480fab20f3005e559c9d06c9a3874a1f1c60dde52c6d28a53ab59b45e79d55f", size = 31335, upload-time = "2025-06-14T20:45:20.462Z" }, + { url = "https://files.pythonhosted.org/packages/ab/b6/797fdd81399fe8251196f29a621ba3f3f04d5c579d95fd304489f5558202/pyobjc_framework_applicationservices-11.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:e8dee91c6a14fd042f98819dc0ac4a182e0e816282565534032f0e544bfab143", size = 31196, upload-time = "2025-06-14T20:45:21.555Z" }, + { url = "https://files.pythonhosted.org/packages/68/45/47eba8d7cdf16d778240ed13fb405e8d712464170ed29d0463363a695194/pyobjc_framework_applicationservices-11.1-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:a0ce40a57a9b993793b6f72c4fd93f80618ef54a69d76a1da97b8360a2f3ffc5", size = 31446, upload-time = "2025-06-14T20:45:22.313Z" }, +] + +[[package]] +name = "pyobjc-framework-avfoundation" +version = "11.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-coreaudio" }, + { name = "pyobjc-framework-coremedia" }, + { name = "pyobjc-framework-quartz" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3c/1f/90cdbce1d3b4861cbb17c12adf57daeec32477eb1df8d3f9ab8551bdadfb/pyobjc_framework_avfoundation-11.1.tar.gz", hash = "sha256:6663056cc6ca49af8de6d36a7fff498f51e1a9a7f1bde7afba718a8ceaaa7377", size = 832178, upload-time = "2025-06-14T20:56:46.329Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/b3/be739115eebb03ea38853ad2dcedf8a21da923415b0b6b4dc6f50c737863/pyobjc_framework_avfoundation-11.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:09542590d1f3aa96d4d1a37712b98fd9657e250d9ea06ecdf2a8a59c837a2cb6", size = 70713, upload-time = "2025-06-14T20:45:51.427Z" }, + { url = "https://files.pythonhosted.org/packages/0f/48/31286b2b09a619d8047256d7180e0d511be71ab598e5f54f034977b59bbf/pyobjc_framework_avfoundation-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8a0ccbdba46b69dec1d12eea52eef56fcd63c492f73e41011bb72508b2aa2d0e", size = 70711, upload-time = "2025-06-14T20:45:52.461Z" }, + { url = "https://files.pythonhosted.org/packages/43/30/d5d03dd4a508bdaa2156ff379e9e109020de23cbb6316c5865d341aa6db1/pyobjc_framework_avfoundation-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:94f065db4e87b1baebb5cf9f464cf9d82c5f903fff192001ebc974d9e3132c7e", size = 70746, upload-time = "2025-06-14T20:45:53.253Z" }, + { url = "https://files.pythonhosted.org/packages/3f/8c/b8ced7700b0e931dc37d14b05e2bead28d2598c887832b3d697da55b1845/pyobjc_framework_avfoundation-11.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e204d155a09c186601490e4402dcffb2845a5831079e389b47bd6a341fe5ee63", size = 70773, upload-time = "2025-06-14T20:45:54.059Z" }, + { url = "https://files.pythonhosted.org/packages/d6/4c/086f4713793aaabdb5134debbf1fdc6c7d4ef5a32a6b35529e2e69580ec8/pyobjc_framework_avfoundation-11.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:dd3965aad0b236b8ac12f216d688c1a22b963f63e7e4fdb7107dd6790e80ee12", size = 71352, upload-time = "2025-06-14T20:45:54.871Z" }, + { url = "https://files.pythonhosted.org/packages/a6/5f/d5c4b9812e22c6fdf234421f131efae7c3137e838bb9df9be8bb45cde97b/pyobjc_framework_avfoundation-11.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:1ab2108b652496b13b9758c295f0f6de53b6d12125cf574ddae84ce28044bce1", size = 71208, upload-time = "2025-06-14T20:45:56.057Z" }, + { url = "https://files.pythonhosted.org/packages/29/d0/dec23e1745a81f5576cba577fa7218d665f36250a8507eaaa83a84579abf/pyobjc_framework_avfoundation-11.1-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:5dd6ac6a57f86b7ed5ac0a965ce54328f6ce77816b4a1fbf0d85c06fb251867a", size = 71680, upload-time = "2025-06-14T20:45:57.091Z" }, +] + +[[package]] +name = "pyobjc-framework-cocoa" +version = "11.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4b/c5/7a866d24bc026f79239b74d05e2cf3088b03263da66d53d1b4cf5207f5ae/pyobjc_framework_cocoa-11.1.tar.gz", hash = "sha256:87df76b9b73e7ca699a828ff112564b59251bb9bbe72e610e670a4dc9940d038", size = 5565335, upload-time = "2025-06-14T20:56:59.683Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/8f/67a7e166b615feb96385d886c6732dfb90afed565b8b1f34673683d73cd9/pyobjc_framework_cocoa-11.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b27a5bdb3ab6cdeb998443ff3fce194ffae5f518c6a079b832dbafc4426937f9", size = 388187, upload-time = "2025-06-14T20:46:49.74Z" }, + { url = "https://files.pythonhosted.org/packages/90/43/6841046aa4e257b6276cd23e53cacedfb842ecaf3386bb360fa9cc319aa1/pyobjc_framework_cocoa-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7b9a9b8ba07f5bf84866399e3de2aa311ed1c34d5d2788a995bdbe82cc36cfa0", size = 388177, upload-time = "2025-06-14T20:46:51.454Z" }, + { url = "https://files.pythonhosted.org/packages/68/da/41c0f7edc92ead461cced7e67813e27fa17da3c5da428afdb4086c69d7ba/pyobjc_framework_cocoa-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:806de56f06dfba8f301a244cce289d54877c36b4b19818e3b53150eb7c2424d0", size = 388983, upload-time = "2025-06-14T20:46:52.591Z" }, + { url = "https://files.pythonhosted.org/packages/4e/0b/a01477cde2a040f97e226f3e15e5ffd1268fcb6d1d664885a95ba592eca9/pyobjc_framework_cocoa-11.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:54e93e1d9b0fc41c032582a6f0834befe1d418d73893968f3f450281b11603da", size = 389049, upload-time = "2025-06-14T20:46:53.757Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/64cf2661f6ab7c124d0486ec6d1d01a9bb2838a0d2a46006457d8c5e6845/pyobjc_framework_cocoa-11.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:fd5245ee1997d93e78b72703be1289d75d88ff6490af94462b564892e9266350", size = 393110, upload-time = "2025-06-14T20:46:54.894Z" }, + { url = "https://files.pythonhosted.org/packages/33/87/01e35c5a3c5bbdc93d5925366421e10835fcd7b23347b6c267df1b16d0b3/pyobjc_framework_cocoa-11.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:aede53a1afc5433e1e7d66568cc52acceeb171b0a6005407a42e8e82580b4fc0", size = 392644, upload-time = "2025-06-14T20:46:56.503Z" }, + { url = "https://files.pythonhosted.org/packages/c1/7c/54afe9ffee547c41e1161691e72067a37ed27466ac71c089bfdcd07ca70d/pyobjc_framework_cocoa-11.1-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:1b5de4e1757bb65689d6dc1f8d8717de9ec8587eb0c4831c134f13aba29f9b71", size = 396742, upload-time = "2025-06-14T20:46:57.64Z" }, +] + +[[package]] +name = "pyobjc-framework-coreaudio" +version = "11.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/39/c0/4ab6005cf97e534725b0c14b110d4864b367c282b1c5b0d8f42aad74a83f/pyobjc_framework_coreaudio-11.1.tar.gz", hash = "sha256:b7b89540ae7efc6c1e3208ac838ef2acfc4d2c506dd629d91f6b3b3120e55c1b", size = 141032, upload-time = "2025-06-14T20:57:04.348Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b8/fd/59bcaa6436c27d3da4ea147da4e6f723606317e38e7101f8b191b687176d/pyobjc_framework_coreaudio-11.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:551c8aac6fdfbd34c3e2d4ce90b36a411e81be20581b978fa4da1a495489792d", size = 35380, upload-time = "2025-06-14T20:47:13.306Z" }, + { url = "https://files.pythonhosted.org/packages/54/1d/81339c1087519a9f125396c717b85a05b49c2c54137bdf4ca01c1ccb6239/pyobjc_framework_coreaudio-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:73a46f0db2fa8ca2e8c47c3ddcc2751e67a0f8600246a6718553b15ee0dbbdb6", size = 35383, upload-time = "2025-06-14T20:47:14.234Z" }, + { url = "https://files.pythonhosted.org/packages/3d/fe/c43521642db98a4ec29fa535781c1316342bb52d5fc709696cbb1e8ca6cd/pyobjc_framework_coreaudio-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2538d1242dab4e27efb346eafbad50594e7e95597fa7220f0bab2099c825da55", size = 36765, upload-time = "2025-06-14T20:47:15.344Z" }, + { url = "https://files.pythonhosted.org/packages/82/9b/24d03ace273585de2d04385f06b895ce92caf8f5af430b060618ebce9dbe/pyobjc_framework_coreaudio-11.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f73d996df1e721931d9f78050e1708735a173dbe3a76d9c71fb36e04f7208478", size = 36779, upload-time = "2025-06-14T20:47:16.123Z" }, + { url = "https://files.pythonhosted.org/packages/91/23/aa78365e45d0d04fc37e21cf7d69dc0d11e17b564e83cb5bcd98e89cdf45/pyobjc_framework_coreaudio-11.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:67dae111b78d91c26c753dbfbccc3ea5498cfda3dfe83c6f3778628b435e1e7b", size = 38480, upload-time = "2025-06-14T20:47:16.911Z" }, + { url = "https://files.pythonhosted.org/packages/3e/58/fc6d752a68f28567fa6d6d6a229122c829e2251f79ec7304fe0572e0fdcd/pyobjc_framework_coreaudio-11.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:9527a16a2b88b37bace578d499f21229f9a33b9afdcdd35d4f44374cb8eb9ab6", size = 36910, upload-time = "2025-06-14T20:47:17.69Z" }, + { url = "https://files.pythonhosted.org/packages/9e/4c/c1c5624418dea005d9965ba690d3649afc33371ade213841ab51922af751/pyobjc_framework_coreaudio-11.1-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:6ba8b67f185c0e3f26b17ae525cee3f411bc8d6e9c9a8bfd899a28f594623d2f", size = 38567, upload-time = "2025-06-14T20:47:18.45Z" }, +] + +[[package]] +name = "pyobjc-framework-coremedia" +version = "11.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/5d/81513acd219df77a89176f1574d936b81ad6f6002225cabb64d55efb7e8d/pyobjc_framework_coremedia-11.1.tar.gz", hash = "sha256:82cdc087f61e21b761e677ea618a575d4c0dbe00e98230bf9cea540cff931db3", size = 216389, upload-time = "2025-06-14T20:57:09.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/09/5e/396621c2b29353a3cb6d2caa4116583bf1073aedaf2ef196fec85d983696/pyobjc_framework_coremedia-11.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91231957d25b6d191983166cf218189b5a01e267dadde35eb3a4c359dc473ccb", size = 29113, upload-time = "2025-06-14T20:47:47.978Z" }, + { url = "https://files.pythonhosted.org/packages/32/48/811ccea77d2c0d8156a489e2900298502eb6648d9c041c7f0c514c8f8a29/pyobjc_framework_coremedia-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:aacf47006e1c6bf6124fb2b5016a8d5fd5cf504b6b488f9eba4e389ab0f0a051", size = 29118, upload-time = "2025-06-14T20:47:48.895Z" }, + { url = "https://files.pythonhosted.org/packages/2c/d1/b3d004d6a2d2188d196779d92fe8cfa2533f5722cd216fbc4f0cffc63b24/pyobjc_framework_coremedia-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ea5055298af91e463ffa7977d573530f9bada57b8f2968dcc76a75e339b9a598", size = 29015, upload-time = "2025-06-14T20:47:49.655Z" }, + { url = "https://files.pythonhosted.org/packages/1c/23/cafd29011d14eac27fc55770157ebb8e02ffed9f75e01f24e97616417c4c/pyobjc_framework_coremedia-11.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7ecdb64c743ffe9fd3949c7cc9109891b9f399a0852717fcb969d33c4e7ba527", size = 29031, upload-time = "2025-06-14T20:47:50.395Z" }, + { url = "https://files.pythonhosted.org/packages/de/a6/ca85b7d9d000e8e2748bcacde356278cb90f6ca9aed54dce6a42d1716ba8/pyobjc_framework_coremedia-11.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:969ce357c616f6835f47e27d1e73964374cdb671476571dfd358894a8ced06f2", size = 29094, upload-time = "2025-06-14T20:47:51.318Z" }, + { url = "https://files.pythonhosted.org/packages/b8/3d/56d530cf504a6eef84f51c8f6f845af8b947f6108e41db5e0b5189d5a667/pyobjc_framework_coremedia-11.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:bf1da05c297776c297ab3489ebf18d954efdff530acbdd6e70c32be811e20ec6", size = 29043, upload-time = "2025-06-14T20:47:52.092Z" }, + { url = "https://files.pythonhosted.org/packages/a4/bc/b237ecd4954a0f07450469236ca45412edb7d8715ff7fc175ac519e7c472/pyobjc_framework_coremedia-11.1-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:aa942d9ad0cf5bc4d3ede8779c3fac2f04cf3857687f2fb8505bae3378d04b95", size = 29111, upload-time = "2025-06-14T20:47:53.083Z" }, +] + +[[package]] +name = "pyobjc-framework-coretext" +version = "11.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-quartz" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/65/e9/d3231c4f87d07b8525401fd6ad3c56607c9e512c5490f0a7a6abb13acab6/pyobjc_framework_coretext-11.1.tar.gz", hash = "sha256:a29bbd5d85c77f46a8ee81d381b847244c88a3a5a96ac22f509027ceceaffaf6", size = 274702, upload-time = "2025-06-14T20:57:16.059Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/59/0c/0117d5353b1d18f8f8dd1e0f48374e4819cfcf3e8c34c676353e87320e8f/pyobjc_framework_coretext-11.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:515be6beb48c084ee413c00c4e9fbd6e730c1b8a24270f4c618fc6c7ba0011ce", size = 30072, upload-time = "2025-06-14T20:48:33.341Z" }, + { url = "https://files.pythonhosted.org/packages/4c/59/d6cc5470157cfd328b2d1ee2c1b6f846a5205307fce17291b57236d9f46e/pyobjc_framework_coretext-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b4f4d2d2a6331fa64465247358d7aafce98e4fb654b99301a490627a073d021e", size = 30072, upload-time = "2025-06-14T20:48:34.248Z" }, + { url = "https://files.pythonhosted.org/packages/32/67/9cc5189c366e67dc3e5b5976fac73cc6405841095f795d3fa0d5fc43d76a/pyobjc_framework_coretext-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1597bf7234270ee1b9963bf112e9061050d5fb8e1384b3f50c11bde2fe2b1570", size = 30175, upload-time = "2025-06-14T20:48:35.023Z" }, + { url = "https://files.pythonhosted.org/packages/b0/d1/6ec2ef4f8133177203a742d5db4db90bbb3ae100aec8d17f667208da84c9/pyobjc_framework_coretext-11.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:37e051e8f12a0f47a81b8efc8c902156eb5bc3d8123c43e5bd4cebd24c222228", size = 30180, upload-time = "2025-06-14T20:48:35.766Z" }, + { url = "https://files.pythonhosted.org/packages/0a/84/d4a95e49f6af59503ba257fbed0471b6932f0afe8b3725c018dd3ba40150/pyobjc_framework_coretext-11.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:56a3a02202e0d50be3c43e781c00f9f1859ab9b73a8342ff56260b908e911e37", size = 30768, upload-time = "2025-06-14T20:48:36.869Z" }, + { url = "https://files.pythonhosted.org/packages/64/4c/16e1504e06a5cb23eec6276835ddddb087637beba66cf84b5c587eba99be/pyobjc_framework_coretext-11.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:15650ba99692d00953e91e53118c11636056a22c90d472020f7ba31500577bf5", size = 30155, upload-time = "2025-06-14T20:48:37.948Z" }, + { url = "https://files.pythonhosted.org/packages/ad/a4/cbfa9c874b2770fb1ba5c38c42b0e12a8b5aa177a5a86d0ad49b935aa626/pyobjc_framework_coretext-11.1-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:fb27f66a56660c31bb956191d64b85b95bac99cfb833f6e99622ca0ac4b3ba12", size = 30768, upload-time = "2025-06-14T20:48:38.734Z" }, +] + +[[package]] +name = "pyobjc-framework-quartz" +version = "11.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c7/ac/6308fec6c9ffeda9942fef72724f4094c6df4933560f512e63eac37ebd30/pyobjc_framework_quartz-11.1.tar.gz", hash = "sha256:a57f35ccfc22ad48c87c5932818e583777ff7276605fef6afad0ac0741169f75", size = 3953275, upload-time = "2025-06-14T20:58:17.924Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/62/f8d9bb4cba92d5f220327cf1def2c2c5be324880d54ee57e7bea43aa28b2/pyobjc_framework_quartz-11.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b5ef75c416b0209e25b2eb07a27bd7eedf14a8c6b2f968711969d45ceceb0f84", size = 215586, upload-time = "2025-06-14T20:53:34.018Z" }, + { url = "https://files.pythonhosted.org/packages/77/cb/38172fdb350b3f47e18d87c5760e50f4efbb4da6308182b5e1310ff0cde4/pyobjc_framework_quartz-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2d501fe95ef15d8acf587cb7dc4ab4be3c5a84e2252017da8dbb7df1bbe7a72a", size = 215565, upload-time = "2025-06-14T20:53:35.262Z" }, + { url = "https://files.pythonhosted.org/packages/9b/37/ee6e0bdd31b3b277fec00e5ee84d30eb1b5b8b0e025095e24ddc561697d0/pyobjc_framework_quartz-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9ac806067541917d6119b98d90390a6944e7d9bd737f5c0a79884202327c9204", size = 216410, upload-time = "2025-06-14T20:53:36.346Z" }, + { url = "https://files.pythonhosted.org/packages/bd/27/4f4fc0e6a0652318c2844608dd7c41e49ba6006ee5fb60c7ae417c338357/pyobjc_framework_quartz-11.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:43a1138280571bbf44df27a7eef519184b5c4183a588598ebaaeb887b9e73e76", size = 216816, upload-time = "2025-06-14T20:53:37.358Z" }, + { url = "https://files.pythonhosted.org/packages/b8/8a/1d15e42496bef31246f7401aad1ebf0f9e11566ce0de41c18431715aafbc/pyobjc_framework_quartz-11.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b23d81c30c564adf6336e00b357f355b35aad10075dd7e837cfd52a9912863e5", size = 221941, upload-time = "2025-06-14T20:53:38.34Z" }, + { url = "https://files.pythonhosted.org/packages/32/a8/a3f84d06e567efc12c104799c7fd015f9bea272a75f799eda8b79e8163c6/pyobjc_framework_quartz-11.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:07cbda78b4a8fcf3a2d96e047a2ff01f44e3e1820f46f0f4b3b6d77ff6ece07c", size = 221312, upload-time = "2025-06-14T20:53:39.435Z" }, + { url = "https://files.pythonhosted.org/packages/76/ef/8c08d4f255bb3efe8806609d1f0b1ddd29684ab0f9ffb5e26d3ad7957b29/pyobjc_framework_quartz-11.1-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:39d02a3df4b5e3eee1e0da0fb150259476910d2a9aa638ab94153c24317a9561", size = 226353, upload-time = "2025-06-14T20:53:40.655Z" }, +] + +[[package]] +name = "pyopengl" +version = "3.1.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/16/912b7225d56284859cd9a672827f18be43f8012f8b7b932bc4bd959a298e/pyopengl-3.1.10.tar.gz", hash = "sha256:c4a02d6866b54eb119c8e9b3fb04fa835a95ab802dd96607ab4cdb0012df8335", size = 1915580, upload-time = "2025-08-18T02:33:01.76Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/e4/1ba6f44e491c4eece978685230dde56b14d51a0365bc1b774ddaa94d14cd/pyopengl-3.1.10-py3-none-any.whl", hash = "sha256:794a943daced39300879e4e47bd94525280685f42dbb5a998d336cfff151d74f", size = 3194996, upload-time = "2025-08-18T02:32:59.902Z" }, +] + +[[package]] +name = "pyreadline3" +version = "3.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/49/4cea918a08f02817aabae639e3d0ac046fef9f9180518a3ad394e22da148/pyreadline3-3.5.4.tar.gz", hash = "sha256:8d57d53039a1c75adba8e50dd3d992b28143480816187ea5efbd5c78e6c885b7", size = 99839, upload-time = "2024-09-19T02:40:10.062Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/dc/491b7661614ab97483abf2056be1deee4dc2490ecbf7bff9ab5cdbac86e1/pyreadline3-3.5.4-py3-none-any.whl", hash = "sha256:eaf8e6cc3c49bcccf145fc6067ba8643d1df34d604a1ec0eccbf7a18e6d3fae6", size = 83178, upload-time = "2024-09-19T02:40:08.598Z" }, +] + +[[package]] +name = "pyserial" +version = "3.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1e/7d/ae3f0a63f41e4d2f6cb66a5b57197850f919f59e558159a4dd3a818f5082/pyserial-3.5.tar.gz", hash = "sha256:3c77e014170dfffbd816e6ffc205e9842efb10be9f58ec16d3e8675b4925cddb", size = 159125, upload-time = "2020-11-23T03:59:15.045Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/bc/587a445451b253b285629263eb51c2d8e9bcea4fc97826266d186f96f558/pyserial-3.5-py2.py3-none-any.whl", hash = "sha256:c4451db6ba391ca6ca299fb3ec7bae67a5c55dde170964c7a14ceefec02f2cf0", size = 90585, upload-time = "2020-11-23T03:59:13.41Z" }, +] + +[[package]] +name = "pytest" +version = "8.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, +] + +[[package]] +name = "pytest-asyncio" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "backports-asyncio-runner", marker = "python_full_version < '3.11'" }, + { name = "pytest" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/86/9e3c5f48f7b7b638b216e4b9e645f54d199d7abbbab7a64a13b4e12ba10f/pytest_asyncio-1.2.0.tar.gz", hash = "sha256:c609a64a2a8768462d0c99811ddb8bd2583c33fd33cf7f21af1c142e824ffb57", size = 50119, upload-time = "2025-09-12T07:33:53.816Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/93/2fa34714b7a4ae72f2f8dad66ba17dd9a2c793220719e736dda28b7aec27/pytest_asyncio-1.2.0-py3-none-any.whl", hash = "sha256:8e17ae5e46d8e7efe51ab6494dd2010f4ca8dae51652aa3c8d55acf50bfb2e99", size = 15095, upload-time = "2025-09-12T07:33:52.639Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978, upload-time = "2025-06-24T04:21:07.341Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" }, +] + +[[package]] +name = "python-utils" +version = "3.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/13/4c/ef8b7b1046d65c1f18ca31e5235c7d6627ca2b3f389ab1d44a74d22f5cc9/python_utils-3.9.1.tar.gz", hash = "sha256:eb574b4292415eb230f094cbf50ab5ef36e3579b8f09e9f2ba74af70891449a0", size = 35403, upload-time = "2024-11-26T00:38:58.736Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/69/31c82567719b34d8f6b41077732589104883771d182a9f4ff3e71430999a/python_utils-3.9.1-py2.py3-none-any.whl", hash = "sha256:0273d7363c7ad4b70999b2791d5ba6b55333d6f7a4e4c8b6b39fb82b5fab4613", size = 32078, upload-time = "2024-11-26T00:38:57.488Z" }, +] + +[[package]] +name = "python-xlib" +version = "0.33" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/86/f5/8c0653e5bb54e0cbdfe27bf32d41f27bc4e12faa8742778c17f2a71be2c0/python-xlib-0.33.tar.gz", hash = "sha256:55af7906a2c75ce6cb280a584776080602444f75815a7aff4d287bb2d7018b32", size = 269068, upload-time = "2022-12-25T18:53:00.824Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/b8/ff33610932e0ee81ae7f1269c890f697d56ff74b9f5b2ee5d9b7fa2c5355/python_xlib-0.33-py2.py3-none-any.whl", hash = "sha256:c3534038d42e0df2f1392a1b30a15a4ff5fdc2b86cfa94f072bf11b10a164398", size = 182185, upload-time = "2022-12-25T18:52:58.662Z" }, +] + +[[package]] +name = "pyusb" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/00/6b/ce3727395e52b7b76dfcf0c665e37d223b680b9becc60710d4bc08b7b7cb/pyusb-1.3.1.tar.gz", hash = "sha256:3af070b607467c1c164f49d5b0caabe8ac78dbed9298d703a8dbf9df4052d17e", size = 77281, upload-time = "2025-01-08T23:45:01.866Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/28/b8/27e6312e86408a44fe16bd28ee12dd98608b39f7e7e57884a24e8f29b573/pyusb-1.3.1-py3-none-any.whl", hash = "sha256:bf9b754557af4717fe80c2b07cc2b923a9151f5c08d17bdb5345dac09d6a0430", size = 58465, upload-time = "2025-01-08T23:45:00.029Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199, upload-time = "2024-08-06T20:31:40.178Z" }, + { url = "https://files.pythonhosted.org/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758, upload-time = "2024-08-06T20:31:42.173Z" }, + { url = "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463, upload-time = "2024-08-06T20:31:44.263Z" }, + { url = "https://files.pythonhosted.org/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280, upload-time = "2024-08-06T20:31:50.199Z" }, + { url = "https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239, upload-time = "2024-08-06T20:31:52.292Z" }, + { url = "https://files.pythonhosted.org/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802, upload-time = "2024-08-06T20:31:53.836Z" }, + { url = "https://files.pythonhosted.org/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527, upload-time = "2024-08-06T20:31:55.565Z" }, + { url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052, upload-time = "2024-08-06T20:31:56.914Z" }, + { url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774, upload-time = "2024-08-06T20:31:58.304Z" }, + { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612, upload-time = "2024-08-06T20:32:03.408Z" }, + { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040, upload-time = "2024-08-06T20:32:04.926Z" }, + { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829, upload-time = "2024-08-06T20:32:06.459Z" }, + { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167, upload-time = "2024-08-06T20:32:08.338Z" }, + { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952, upload-time = "2024-08-06T20:32:14.124Z" }, + { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301, upload-time = "2024-08-06T20:32:16.17Z" }, + { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638, upload-time = "2024-08-06T20:32:18.555Z" }, + { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850, upload-time = "2024-08-06T20:32:19.889Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980, upload-time = "2024-08-06T20:32:21.273Z" }, + { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873, upload-time = "2024-08-06T20:32:25.131Z" }, + { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302, upload-time = "2024-08-06T20:32:26.511Z" }, + { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154, upload-time = "2024-08-06T20:32:28.363Z" }, + { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223, upload-time = "2024-08-06T20:32:30.058Z" }, + { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542, upload-time = "2024-08-06T20:32:31.881Z" }, + { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164, upload-time = "2024-08-06T20:32:37.083Z" }, + { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611, upload-time = "2024-08-06T20:32:38.898Z" }, + { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591, upload-time = "2024-08-06T20:32:40.241Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338, upload-time = "2024-08-06T20:32:41.93Z" }, + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload-time = "2024-08-06T20:32:51.188Z" }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload-time = "2024-08-06T20:32:53.019Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload-time = "2024-08-06T20:32:54.708Z" }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" }, +] + +[[package]] +name = "pyzmq" +version = "27.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "implementation_name == 'pypy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/04/0b/3c9baedbdf613ecaa7aa07027780b8867f57b6293b6ee50de316c9f3222b/pyzmq-27.1.0.tar.gz", hash = "sha256:ac0765e3d44455adb6ddbf4417dcce460fc40a05978c08efdf2948072f6db540", size = 281750, upload-time = "2025-09-08T23:10:18.157Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/b9/52aa9ec2867528b54f1e60846728d8b4d84726630874fee3a91e66c7df81/pyzmq-27.1.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:508e23ec9bc44c0005c4946ea013d9317ae00ac67778bd47519fdf5a0e930ff4", size = 1329850, upload-time = "2025-09-08T23:07:26.274Z" }, + { url = "https://files.pythonhosted.org/packages/99/64/5653e7b7425b169f994835a2b2abf9486264401fdef18df91ddae47ce2cc/pyzmq-27.1.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:507b6f430bdcf0ee48c0d30e734ea89ce5567fd7b8a0f0044a369c176aa44556", size = 906380, upload-time = "2025-09-08T23:07:29.78Z" }, + { url = "https://files.pythonhosted.org/packages/73/78/7d713284dbe022f6440e391bd1f3c48d9185673878034cfb3939cdf333b2/pyzmq-27.1.0-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf7b38f9fd7b81cb6d9391b2946382c8237fd814075c6aa9c3b746d53076023b", size = 666421, upload-time = "2025-09-08T23:07:31.263Z" }, + { url = "https://files.pythonhosted.org/packages/30/76/8f099f9d6482450428b17c4d6b241281af7ce6a9de8149ca8c1c649f6792/pyzmq-27.1.0-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03ff0b279b40d687691a6217c12242ee71f0fba28bf8626ff50e3ef0f4410e1e", size = 854149, upload-time = "2025-09-08T23:07:33.17Z" }, + { url = "https://files.pythonhosted.org/packages/59/f0/37fbfff06c68016019043897e4c969ceab18bde46cd2aca89821fcf4fb2e/pyzmq-27.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:677e744fee605753eac48198b15a2124016c009a11056f93807000ab11ce6526", size = 1655070, upload-time = "2025-09-08T23:07:35.205Z" }, + { url = "https://files.pythonhosted.org/packages/47/14/7254be73f7a8edc3587609554fcaa7bfd30649bf89cd260e4487ca70fdaa/pyzmq-27.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dd2fec2b13137416a1c5648b7009499bcc8fea78154cd888855fa32514f3dad1", size = 2033441, upload-time = "2025-09-08T23:07:37.432Z" }, + { url = "https://files.pythonhosted.org/packages/22/dc/49f2be26c6f86f347e796a4d99b19167fc94503f0af3fd010ad262158822/pyzmq-27.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:08e90bb4b57603b84eab1d0ca05b3bbb10f60c1839dc471fc1c9e1507bef3386", size = 1891529, upload-time = "2025-09-08T23:07:39.047Z" }, + { url = "https://files.pythonhosted.org/packages/a3/3e/154fb963ae25be70c0064ce97776c937ecc7d8b0259f22858154a9999769/pyzmq-27.1.0-cp310-cp310-win32.whl", hash = "sha256:a5b42d7a0658b515319148875fcb782bbf118dd41c671b62dae33666c2213bda", size = 567276, upload-time = "2025-09-08T23:07:40.695Z" }, + { url = "https://files.pythonhosted.org/packages/62/b2/f4ab56c8c595abcb26b2be5fd9fa9e6899c1e5ad54964e93ae8bb35482be/pyzmq-27.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:c0bb87227430ee3aefcc0ade2088100e528d5d3298a0a715a64f3d04c60ba02f", size = 632208, upload-time = "2025-09-08T23:07:42.298Z" }, + { url = "https://files.pythonhosted.org/packages/3b/e3/be2cc7ab8332bdac0522fdb64c17b1b6241a795bee02e0196636ec5beb79/pyzmq-27.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:9a916f76c2ab8d045b19f2286851a38e9ac94ea91faf65bd64735924522a8b32", size = 559766, upload-time = "2025-09-08T23:07:43.869Z" }, + { url = "https://files.pythonhosted.org/packages/06/5d/305323ba86b284e6fcb0d842d6adaa2999035f70f8c38a9b6d21ad28c3d4/pyzmq-27.1.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:226b091818d461a3bef763805e75685e478ac17e9008f49fce2d3e52b3d58b86", size = 1333328, upload-time = "2025-09-08T23:07:45.946Z" }, + { url = "https://files.pythonhosted.org/packages/bd/a0/fc7e78a23748ad5443ac3275943457e8452da67fda347e05260261108cbc/pyzmq-27.1.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0790a0161c281ca9723f804871b4027f2e8b5a528d357c8952d08cd1a9c15581", size = 908803, upload-time = "2025-09-08T23:07:47.551Z" }, + { url = "https://files.pythonhosted.org/packages/7e/22/37d15eb05f3bdfa4abea6f6d96eb3bb58585fbd3e4e0ded4e743bc650c97/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c895a6f35476b0c3a54e3eb6ccf41bf3018de937016e6e18748317f25d4e925f", size = 668836, upload-time = "2025-09-08T23:07:49.436Z" }, + { url = "https://files.pythonhosted.org/packages/b1/c4/2a6fe5111a01005fc7af3878259ce17684fabb8852815eda6225620f3c59/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bbf8d3630bf96550b3be8e1fc0fea5cbdc8d5466c1192887bd94869da17a63e", size = 857038, upload-time = "2025-09-08T23:07:51.234Z" }, + { url = "https://files.pythonhosted.org/packages/cb/eb/bfdcb41d0db9cd233d6fb22dc131583774135505ada800ebf14dfb0a7c40/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:15c8bd0fe0dabf808e2d7a681398c4e5ded70a551ab47482067a572c054c8e2e", size = 1657531, upload-time = "2025-09-08T23:07:52.795Z" }, + { url = "https://files.pythonhosted.org/packages/ab/21/e3180ca269ed4a0de5c34417dfe71a8ae80421198be83ee619a8a485b0c7/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:bafcb3dd171b4ae9f19ee6380dfc71ce0390fefaf26b504c0e5f628d7c8c54f2", size = 2034786, upload-time = "2025-09-08T23:07:55.047Z" }, + { url = "https://files.pythonhosted.org/packages/3b/b1/5e21d0b517434b7f33588ff76c177c5a167858cc38ef740608898cd329f2/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e829529fcaa09937189178115c49c504e69289abd39967cd8a4c215761373394", size = 1894220, upload-time = "2025-09-08T23:07:57.172Z" }, + { url = "https://files.pythonhosted.org/packages/03/f2/44913a6ff6941905efc24a1acf3d3cb6146b636c546c7406c38c49c403d4/pyzmq-27.1.0-cp311-cp311-win32.whl", hash = "sha256:6df079c47d5902af6db298ec92151db82ecb557af663098b92f2508c398bb54f", size = 567155, upload-time = "2025-09-08T23:07:59.05Z" }, + { url = "https://files.pythonhosted.org/packages/23/6d/d8d92a0eb270a925c9b4dd039c0b4dc10abc2fcbc48331788824ef113935/pyzmq-27.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:190cbf120fbc0fc4957b56866830def56628934a9d112aec0e2507aa6a032b97", size = 633428, upload-time = "2025-09-08T23:08:00.663Z" }, + { url = "https://files.pythonhosted.org/packages/ae/14/01afebc96c5abbbd713ecfc7469cfb1bc801c819a74ed5c9fad9a48801cb/pyzmq-27.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:eca6b47df11a132d1745eb3b5b5e557a7dae2c303277aa0e69c6ba91b8736e07", size = 559497, upload-time = "2025-09-08T23:08:02.15Z" }, + { url = "https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:452631b640340c928fa343801b0d07eb0c3789a5ffa843f6e1a9cee0ba4eb4fc", size = 1306279, upload-time = "2025-09-08T23:08:03.807Z" }, + { url = "https://files.pythonhosted.org/packages/e8/5e/c3c49fdd0f535ef45eefcc16934648e9e59dace4a37ee88fc53f6cd8e641/pyzmq-27.1.0-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1c179799b118e554b66da67d88ed66cd37a169f1f23b5d9f0a231b4e8d44a113", size = 895645, upload-time = "2025-09-08T23:08:05.301Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e5/b0b2504cb4e903a74dcf1ebae157f9e20ebb6ea76095f6cfffea28c42ecd/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3837439b7f99e60312f0c926a6ad437b067356dc2bc2ec96eb395fd0fe804233", size = 652574, upload-time = "2025-09-08T23:08:06.828Z" }, + { url = "https://files.pythonhosted.org/packages/f8/9b/c108cdb55560eaf253f0cbdb61b29971e9fb34d9c3499b0e96e4e60ed8a5/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43ad9a73e3da1fab5b0e7e13402f0b2fb934ae1c876c51d0afff0e7c052eca31", size = 840995, upload-time = "2025-09-08T23:08:08.396Z" }, + { url = "https://files.pythonhosted.org/packages/c2/bb/b79798ca177b9eb0825b4c9998c6af8cd2a7f15a6a1a4272c1d1a21d382f/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0de3028d69d4cdc475bfe47a6128eb38d8bc0e8f4d69646adfbcd840facbac28", size = 1642070, upload-time = "2025-09-08T23:08:09.989Z" }, + { url = "https://files.pythonhosted.org/packages/9c/80/2df2e7977c4ede24c79ae39dcef3899bfc5f34d1ca7a5b24f182c9b7a9ca/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:cf44a7763aea9298c0aa7dbf859f87ed7012de8bda0f3977b6fb1d96745df856", size = 2021121, upload-time = "2025-09-08T23:08:11.907Z" }, + { url = "https://files.pythonhosted.org/packages/46/bd/2d45ad24f5f5ae7e8d01525eb76786fa7557136555cac7d929880519e33a/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f30f395a9e6fbca195400ce833c731e7b64c3919aa481af4d88c3759e0cb7496", size = 1878550, upload-time = "2025-09-08T23:08:13.513Z" }, + { url = "https://files.pythonhosted.org/packages/e6/2f/104c0a3c778d7c2ab8190e9db4f62f0b6957b53c9d87db77c284b69f33ea/pyzmq-27.1.0-cp312-abi3-win32.whl", hash = "sha256:250e5436a4ba13885494412b3da5d518cd0d3a278a1ae640e113c073a5f88edd", size = 559184, upload-time = "2025-09-08T23:08:15.163Z" }, + { url = "https://files.pythonhosted.org/packages/fc/7f/a21b20d577e4100c6a41795842028235998a643b1ad406a6d4163ea8f53e/pyzmq-27.1.0-cp312-abi3-win_amd64.whl", hash = "sha256:9ce490cf1d2ca2ad84733aa1d69ce6855372cb5ce9223802450c9b2a7cba0ccf", size = 619480, upload-time = "2025-09-08T23:08:17.192Z" }, + { url = "https://files.pythonhosted.org/packages/78/c2/c012beae5f76b72f007a9e91ee9401cb88c51d0f83c6257a03e785c81cc2/pyzmq-27.1.0-cp312-abi3-win_arm64.whl", hash = "sha256:75a2f36223f0d535a0c919e23615fc85a1e23b71f40c7eb43d7b1dedb4d8f15f", size = 552993, upload-time = "2025-09-08T23:08:18.926Z" }, + { url = "https://files.pythonhosted.org/packages/60/cb/84a13459c51da6cec1b7b1dc1a47e6db6da50b77ad7fd9c145842750a011/pyzmq-27.1.0-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:93ad4b0855a664229559e45c8d23797ceac03183c7b6f5b4428152a6b06684a5", size = 1122436, upload-time = "2025-09-08T23:08:20.801Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b6/94414759a69a26c3dd674570a81813c46a078767d931a6c70ad29fc585cb/pyzmq-27.1.0-cp313-cp313-android_24_x86_64.whl", hash = "sha256:fbb4f2400bfda24f12f009cba62ad5734148569ff4949b1b6ec3b519444342e6", size = 1156301, upload-time = "2025-09-08T23:08:22.47Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ad/15906493fd40c316377fd8a8f6b1f93104f97a752667763c9b9c1b71d42d/pyzmq-27.1.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:e343d067f7b151cfe4eb3bb796a7752c9d369eed007b91231e817071d2c2fec7", size = 1341197, upload-time = "2025-09-08T23:08:24.286Z" }, + { url = "https://files.pythonhosted.org/packages/14/1d/d343f3ce13db53a54cb8946594e567410b2125394dafcc0268d8dda027e0/pyzmq-27.1.0-cp313-cp313t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:08363b2011dec81c354d694bdecaef4770e0ae96b9afea70b3f47b973655cc05", size = 897275, upload-time = "2025-09-08T23:08:26.063Z" }, + { url = "https://files.pythonhosted.org/packages/69/2d/d83dd6d7ca929a2fc67d2c3005415cdf322af7751d773524809f9e585129/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d54530c8c8b5b8ddb3318f481297441af102517602b569146185fa10b63f4fa9", size = 660469, upload-time = "2025-09-08T23:08:27.623Z" }, + { url = "https://files.pythonhosted.org/packages/3e/cd/9822a7af117f4bc0f1952dbe9ef8358eb50a24928efd5edf54210b850259/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f3afa12c392f0a44a2414056d730eebc33ec0926aae92b5ad5cf26ebb6cc128", size = 847961, upload-time = "2025-09-08T23:08:29.672Z" }, + { url = "https://files.pythonhosted.org/packages/9a/12/f003e824a19ed73be15542f172fd0ec4ad0b60cf37436652c93b9df7c585/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c65047adafe573ff023b3187bb93faa583151627bc9c51fc4fb2c561ed689d39", size = 1650282, upload-time = "2025-09-08T23:08:31.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4a/e82d788ed58e9a23995cee70dbc20c9aded3d13a92d30d57ec2291f1e8a3/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:90e6e9441c946a8b0a667356f7078d96411391a3b8f80980315455574177ec97", size = 2024468, upload-time = "2025-09-08T23:08:33.543Z" }, + { url = "https://files.pythonhosted.org/packages/d9/94/2da0a60841f757481e402b34bf4c8bf57fa54a5466b965de791b1e6f747d/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:add071b2d25f84e8189aaf0882d39a285b42fa3853016ebab234a5e78c7a43db", size = 1885394, upload-time = "2025-09-08T23:08:35.51Z" }, + { url = "https://files.pythonhosted.org/packages/4f/6f/55c10e2e49ad52d080dc24e37adb215e5b0d64990b57598abc2e3f01725b/pyzmq-27.1.0-cp313-cp313t-win32.whl", hash = "sha256:7ccc0700cfdf7bd487bea8d850ec38f204478681ea02a582a8da8171b7f90a1c", size = 574964, upload-time = "2025-09-08T23:08:37.178Z" }, + { url = "https://files.pythonhosted.org/packages/87/4d/2534970ba63dd7c522d8ca80fb92777f362c0f321900667c615e2067cb29/pyzmq-27.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:8085a9fba668216b9b4323be338ee5437a235fe275b9d1610e422ccc279733e2", size = 641029, upload-time = "2025-09-08T23:08:40.595Z" }, + { url = "https://files.pythonhosted.org/packages/f6/fa/f8aea7a28b0641f31d40dea42d7ef003fded31e184ef47db696bc74cd610/pyzmq-27.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:6bb54ca21bcfe361e445256c15eedf083f153811c37be87e0514934d6913061e", size = 561541, upload-time = "2025-09-08T23:08:42.668Z" }, + { url = "https://files.pythonhosted.org/packages/87/45/19efbb3000956e82d0331bafca5d9ac19ea2857722fa2caacefb6042f39d/pyzmq-27.1.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ce980af330231615756acd5154f29813d553ea555485ae712c491cd483df6b7a", size = 1341197, upload-time = "2025-09-08T23:08:44.973Z" }, + { url = "https://files.pythonhosted.org/packages/48/43/d72ccdbf0d73d1343936296665826350cb1e825f92f2db9db3e61c2162a2/pyzmq-27.1.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1779be8c549e54a1c38f805e56d2a2e5c009d26de10921d7d51cfd1c8d4632ea", size = 897175, upload-time = "2025-09-08T23:08:46.601Z" }, + { url = "https://files.pythonhosted.org/packages/2f/2e/a483f73a10b65a9ef0161e817321d39a770b2acf8bcf3004a28d90d14a94/pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7200bb0f03345515df50d99d3db206a0a6bee1955fbb8c453c76f5bf0e08fb96", size = 660427, upload-time = "2025-09-08T23:08:48.187Z" }, + { url = "https://files.pythonhosted.org/packages/f5/d2/5f36552c2d3e5685abe60dfa56f91169f7a2d99bbaf67c5271022ab40863/pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01c0e07d558b06a60773744ea6251f769cd79a41a97d11b8bf4ab8f034b0424d", size = 847929, upload-time = "2025-09-08T23:08:49.76Z" }, + { url = "https://files.pythonhosted.org/packages/c4/2a/404b331f2b7bf3198e9945f75c4c521f0c6a3a23b51f7a4a401b94a13833/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:80d834abee71f65253c91540445d37c4c561e293ba6e741b992f20a105d69146", size = 1650193, upload-time = "2025-09-08T23:08:51.7Z" }, + { url = "https://files.pythonhosted.org/packages/1c/0b/f4107e33f62a5acf60e3ded67ed33d79b4ce18de432625ce2fc5093d6388/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:544b4e3b7198dde4a62b8ff6685e9802a9a1ebf47e77478a5eb88eca2a82f2fd", size = 2024388, upload-time = "2025-09-08T23:08:53.393Z" }, + { url = "https://files.pythonhosted.org/packages/0d/01/add31fe76512642fd6e40e3a3bd21f4b47e242c8ba33efb6809e37076d9b/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cedc4c68178e59a4046f97eca31b148ddcf51e88677de1ef4e78cf06c5376c9a", size = 1885316, upload-time = "2025-09-08T23:08:55.702Z" }, + { url = "https://files.pythonhosted.org/packages/c4/59/a5f38970f9bf07cee96128de79590bb354917914a9be11272cfc7ff26af0/pyzmq-27.1.0-cp314-cp314t-win32.whl", hash = "sha256:1f0b2a577fd770aa6f053211a55d1c47901f4d537389a034c690291485e5fe92", size = 587472, upload-time = "2025-09-08T23:08:58.18Z" }, + { url = "https://files.pythonhosted.org/packages/70/d8/78b1bad170f93fcf5e3536e70e8fadac55030002275c9a29e8f5719185de/pyzmq-27.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:19c9468ae0437f8074af379e986c5d3d7d7bfe033506af442e8c879732bedbe0", size = 661401, upload-time = "2025-09-08T23:08:59.802Z" }, + { url = "https://files.pythonhosted.org/packages/81/d6/4bfbb40c9a0b42fc53c7cf442f6385db70b40f74a783130c5d0a5aa62228/pyzmq-27.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dc5dbf68a7857b59473f7df42650c621d7e8923fb03fa74a526890f4d33cc4d7", size = 575170, upload-time = "2025-09-08T23:09:01.418Z" }, + { url = "https://files.pythonhosted.org/packages/f3/81/a65e71c1552f74dec9dff91d95bafb6e0d33338a8dfefbc88aa562a20c92/pyzmq-27.1.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c17e03cbc9312bee223864f1a2b13a99522e0dc9f7c5df0177cd45210ac286e6", size = 836266, upload-time = "2025-09-08T23:09:40.048Z" }, + { url = "https://files.pythonhosted.org/packages/58/ed/0202ca350f4f2b69faa95c6d931e3c05c3a397c184cacb84cb4f8f42f287/pyzmq-27.1.0-pp310-pypy310_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f328d01128373cb6763823b2b4e7f73bdf767834268c565151eacb3b7a392f90", size = 800206, upload-time = "2025-09-08T23:09:41.902Z" }, + { url = "https://files.pythonhosted.org/packages/47/42/1ff831fa87fe8f0a840ddb399054ca0009605d820e2b44ea43114f5459f4/pyzmq-27.1.0-pp310-pypy310_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c1790386614232e1b3a40a958454bdd42c6d1811837b15ddbb052a032a43f62", size = 567747, upload-time = "2025-09-08T23:09:43.741Z" }, + { url = "https://files.pythonhosted.org/packages/d1/db/5c4d6807434751e3f21231bee98109aa57b9b9b55e058e450d0aef59b70f/pyzmq-27.1.0-pp310-pypy310_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:448f9cb54eb0cee4732b46584f2710c8bc178b0e5371d9e4fc8125201e413a74", size = 747371, upload-time = "2025-09-08T23:09:45.575Z" }, + { url = "https://files.pythonhosted.org/packages/26/af/78ce193dbf03567eb8c0dc30e3df2b9e56f12a670bf7eb20f9fb532c7e8a/pyzmq-27.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:05b12f2d32112bf8c95ef2e74ec4f1d4beb01f8b5e703b38537f8849f92cb9ba", size = 544862, upload-time = "2025-09-08T23:09:47.448Z" }, + { url = "https://files.pythonhosted.org/packages/4c/c6/c4dcdecdbaa70969ee1fdced6d7b8f60cfabe64d25361f27ac4665a70620/pyzmq-27.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:18770c8d3563715387139060d37859c02ce40718d1faf299abddcdcc6a649066", size = 836265, upload-time = "2025-09-08T23:09:49.376Z" }, + { url = "https://files.pythonhosted.org/packages/3e/79/f38c92eeaeb03a2ccc2ba9866f0439593bb08c5e3b714ac1d553e5c96e25/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:ac25465d42f92e990f8d8b0546b01c391ad431c3bf447683fdc40565941d0604", size = 800208, upload-time = "2025-09-08T23:09:51.073Z" }, + { url = "https://files.pythonhosted.org/packages/49/0e/3f0d0d335c6b3abb9b7b723776d0b21fa7f3a6c819a0db6097059aada160/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53b40f8ae006f2734ee7608d59ed661419f087521edbfc2149c3932e9c14808c", size = 567747, upload-time = "2025-09-08T23:09:52.698Z" }, + { url = "https://files.pythonhosted.org/packages/a1/cf/f2b3784d536250ffd4be70e049f3b60981235d70c6e8ce7e3ef21e1adb25/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f605d884e7c8be8fe1aa94e0a783bf3f591b84c24e4bc4f3e7564c82ac25e271", size = 747371, upload-time = "2025-09-08T23:09:54.563Z" }, + { url = "https://files.pythonhosted.org/packages/01/1b/5dbe84eefc86f48473947e2f41711aded97eecef1231f4558f1f02713c12/pyzmq-27.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c9f7f6e13dff2e44a6afeaf2cf54cee5929ad64afaf4d40b50f93c58fc687355", size = 544862, upload-time = "2025-09-08T23:09:56.509Z" }, +] + +[[package]] +name = "questionary" +version = "2.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "prompt-toolkit" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f6/45/eafb0bba0f9988f6a2520f9ca2df2c82ddfa8d67c95d6625452e97b204a5/questionary-2.1.1.tar.gz", hash = "sha256:3d7e980292bb0107abaa79c68dd3eee3c561b83a0f89ae482860b181c8bd412d", size = 25845, upload-time = "2025-08-28T19:00:20.851Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl", hash = "sha256:a51af13f345f1cdea62347589fbb6df3b290306ab8930713bfae4d475a7d4a59", size = 36753, upload-time = "2025-08-28T19:00:19.56Z" }, +] + +[[package]] +name = "reachy-mini" +version = "1.1.1" +source = { editable = "." } +dependencies = [ + { name = "aiohttp" }, + { name = "asgiref" }, + { name = "cv2-enumerate-cameras" }, + { name = "eclipse-zenoh" }, + { name = "fastapi" }, + { name = "huggingface-hub" }, + { name = "jinja2" }, + { name = "libusb-package" }, + { name = "log-throttling" }, + { name = "numpy" }, + { name = "opencv-python" }, + { name = "pip" }, + { name = "psutil" }, + { name = "pyserial" }, + { name = "pyusb" }, + { name = "questionary" }, + { name = "reachy-mini-motor-controller" }, + { name = "reachy-mini-rust-kinematics" }, + { name = "rich" }, + { name = "scipy" }, + { name = "sounddevice" }, + { name = "soundfile" }, + { name = "uvicorn", extra = ["standard"] }, +] + +[package.optional-dependencies] +dev = [ + { name = "mujoco" }, + { name = "mypy" }, + { name = "onshape-to-robot" }, + { name = "placo" }, + { name = "pre-commit" }, + { name = "pytest" }, + { name = "pytest-asyncio" }, + { name = "rerun-sdk" }, + { name = "ruff" }, + { name = "rustypot" }, + { name = "types-requests" }, + { name = "urdf-parser-py" }, +] +examples = [ + { name = "pynput" }, +] +gstreamer = [ + { name = "gst-signalling" }, + { name = "pygobject" }, +] +mujoco = [ + { name = "mujoco" }, +] +nn-kinematics = [ + { name = "onnxruntime" }, +] +placo-kinematics = [ + { name = "placo" }, +] +rerun = [ + { name = "rerun-sdk" }, + { name = "urdf-parser-py" }, +] +wireless-version = [ + { name = "gpiozero" }, + { name = "lgpio" }, + { name = "nmcli" }, + { name = "pollen-bmi088-imu-library" }, + { name = "semver" }, +] + +[package.metadata] +requires-dist = [ + { name = "aiohttp" }, + { name = "asgiref" }, + { name = "cv2-enumerate-cameras", specifier = ">=1.2.1" }, + { name = "eclipse-zenoh", specifier = ">=1.4.0" }, + { name = "fastapi" }, + { name = "gpiozero", marker = "extra == 'wireless-version'", specifier = ">=2.0.0" }, + { name = "gst-signalling", marker = "extra == 'gstreamer'", specifier = ">=1.1.2" }, + { name = "huggingface-hub", specifier = "==0.34.4" }, + { name = "jinja2" }, + { name = "lgpio", marker = "extra == 'wireless-version'", specifier = ">=0.2.2.0" }, + { name = "libusb-package", specifier = ">=1.0.26.3" }, + { name = "log-throttling", specifier = "==0.0.3" }, + { name = "mujoco", marker = "extra == 'dev'", specifier = "==3.3.0" }, + { name = "mujoco", marker = "extra == 'mujoco'", specifier = "==3.3.0" }, + { name = "mypy", marker = "extra == 'dev'", specifier = "==1.18.2" }, + { name = "nmcli", marker = "extra == 'wireless-version'", specifier = ">=1.5" }, + { name = "numpy", specifier = ">=2.2.5" }, + { name = "onnxruntime", marker = "extra == 'nn-kinematics'", specifier = "==1.22.1" }, + { name = "onshape-to-robot", marker = "extra == 'dev'", specifier = "==1.7.6" }, + { name = "opencv-python", specifier = "<=5.0" }, + { name = "pip", specifier = ">=25" }, + { name = "placo", marker = "extra == 'dev'", specifier = "==0.9.14" }, + { name = "placo", marker = "extra == 'placo-kinematics'", specifier = "==0.9.14" }, + { name = "pollen-bmi088-imu-library", marker = "extra == 'wireless-version'" }, + { name = "pre-commit", marker = "extra == 'dev'" }, + { name = "psutil" }, + { name = "pygobject", marker = "extra == 'gstreamer'", specifier = ">=3.42.2,<=3.46.0" }, + { name = "pynput", marker = "extra == 'examples'" }, + { name = "pyserial" }, + { name = "pytest", marker = "extra == 'dev'" }, + { name = "pytest-asyncio", marker = "extra == 'dev'" }, + { name = "pyusb", specifier = ">=1.2.1" }, + { name = "questionary" }, + { name = "reachy-mini-motor-controller", specifier = ">=1.4.2" }, + { name = "reachy-mini-rust-kinematics", specifier = ">=1.0.2" }, + { name = "rerun-sdk", marker = "extra == 'dev'", specifier = ">=0.27.2" }, + { name = "rerun-sdk", marker = "extra == 'rerun'", specifier = ">=0.27.2" }, + { name = "rich" }, + { name = "ruff", marker = "extra == 'dev'", specifier = "==0.12.0" }, + { name = "rustypot", marker = "extra == 'dev'", specifier = ">=1.4.0" }, + { name = "scipy", specifier = ">=1.15.3,<2.0.0" }, + { name = "semver", marker = "extra == 'wireless-version'", specifier = ">=3,<4" }, + { name = "sounddevice", specifier = "==0.5.1" }, + { name = "soundfile", specifier = "==0.13.1" }, + { name = "types-requests", marker = "extra == 'dev'" }, + { name = "urdf-parser-py", marker = "extra == 'dev'", specifier = "==0.0.4" }, + { name = "urdf-parser-py", marker = "extra == 'rerun'", specifier = "==0.0.4" }, + { name = "uvicorn", extras = ["standard"] }, +] +provides-extras = ["dev", "examples", "mujoco", "nn-kinematics", "placo-kinematics", "gstreamer", "rerun", "wireless-version"] + +[[package]] +name = "reachy-mini-motor-controller" +version = "1.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4a/8d/353b13636ebffae848c0d0b8c4bfc15c4673b059f62f9c310ee98986b0b7/reachy_mini_motor_controller-1.4.2.tar.gz", hash = "sha256:bd6e9f5c13394bc57478a09e76e01c28854ae0c80a9335a3a1b78dc80e0ee150", size = 28248, upload-time = "2025-11-25T17:56:55.263Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/54/1dfb498cea7c568a83878b77348abe4d93a1233dab22be566c45f89e154c/reachy_mini_motor_controller-1.4.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:193a11b9c5a3e85bb21305c09c67990c45c832bbb1d8ab836feb78700dadcd18", size = 586874, upload-time = "2025-11-25T17:56:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/3d/86/ca36d720dcc497c7557e467d6e891e26cd86ce0d44236750d689a6fa306d/reachy_mini_motor_controller-1.4.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8226ed36f5391112a1b0f1ca2658eee24e0e12233b33637b9ece83fa7e0f3e94", size = 572458, upload-time = "2025-11-25T17:56:16.317Z" }, + { url = "https://files.pythonhosted.org/packages/cd/46/41a03359e0c70cc34f73b569d3090d435c15ea248b01bf978a9eee259eaa/reachy_mini_motor_controller-1.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83607921aa369c8e0b3a89bd845c7f6989fc29e1847d46f01eb3af2f27f04b99", size = 641152, upload-time = "2025-11-25T17:55:55.287Z" }, + { url = "https://files.pythonhosted.org/packages/80/cb/9e395e02f47c301bc32ab085f48a9be9c741911b9d2fc0d1131d46a72660/reachy_mini_motor_controller-1.4.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a31e530b41514a81ad43db5ff31d4a77a6f91034d36d7646853beeaafa7a150d", size = 647600, upload-time = "2025-11-25T17:56:01.073Z" }, + { url = "https://files.pythonhosted.org/packages/ee/45/81b45006257c6de17db7b8420d9039f23367db56ba9a29a78962dc6bcf42/reachy_mini_motor_controller-1.4.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a5216ee318c9f20d5bc15e8aa8c96d60b989cf4e4ebf762edf20f6de2ee32ecb", size = 695967, upload-time = "2025-11-25T17:56:06.522Z" }, + { url = "https://files.pythonhosted.org/packages/40/88/4db598e736ab2cf8528ac875982d60477a904949549d80ee25200e3559aa/reachy_mini_motor_controller-1.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e081f5960143880db1e900108a1065ebe789772de5fa2838a1fa9903dbec7d5", size = 662013, upload-time = "2025-11-25T17:56:11.125Z" }, + { url = "https://files.pythonhosted.org/packages/99/00/839e6a9aa5005b239a0b06543f413421471550a0c660c54ab841805fa6ec/reachy_mini_motor_controller-1.4.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:30acdae78c8ef86bbd28b86d644a13c3c3f5c1fafafc14af8de03e509f188c83", size = 824336, upload-time = "2025-11-25T17:56:29.28Z" }, + { url = "https://files.pythonhosted.org/packages/75/64/cb81d550f1c36c2d14405622bb9fd666f2c6c100b504ac48c2af34ef0ad4/reachy_mini_motor_controller-1.4.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:ebb98062d9894790a6028370c35e44e996b67bc5668bd0e4ba2973800056ca64", size = 911952, upload-time = "2025-11-25T17:56:35.665Z" }, + { url = "https://files.pythonhosted.org/packages/af/b8/9187fe5a246d2643a65282e0ecf511e8abe88634054660b3611c883fee50/reachy_mini_motor_controller-1.4.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a4283b91055fa66de52cedcce198f3fb262e4a1404346713c19f93d92a06f21", size = 854328, upload-time = "2025-11-25T17:56:41.999Z" }, + { url = "https://files.pythonhosted.org/packages/35/92/3cd324b1f9934e1ca82af73addb54c0ed2e212e897b4b1e054c887672ce3/reachy_mini_motor_controller-1.4.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:aa3c60cdbe6f7977a5fb6aaa11ced374b931c730f1ae8f68e87345012f810f53", size = 816960, upload-time = "2025-11-25T17:56:48.487Z" }, + { url = "https://files.pythonhosted.org/packages/54/09/29e7bbfbdbb1de9001341219df98314b10887baf53d1580f3c3db51c6eca/reachy_mini_motor_controller-1.4.2-cp310-cp310-win32.whl", hash = "sha256:a391ca67ab2bcd981ea7139b6171f3ba7f5b3b495bd85655c8d804278cc2ea8e", size = 378120, upload-time = "2025-11-25T17:57:01.73Z" }, + { url = "https://files.pythonhosted.org/packages/56/48/0b4a252484518e261377304c77327daa710b93b8a23c1b378a5c92337268/reachy_mini_motor_controller-1.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:38a8c63167b1121a208a3fcae65c17b5222ae95acf80245791c3a51240506fff", size = 401212, upload-time = "2025-11-25T17:56:56.039Z" }, + { url = "https://files.pythonhosted.org/packages/87/20/ac97ac49969c02e0384888c73cccf0b50a7bba48b18842a96b082a8c8e89/reachy_mini_motor_controller-1.4.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:f405d5f948abd26e423f156fc0d165b69ec964d3a4cbfa7d2d952313cc0ff972", size = 586684, upload-time = "2025-11-25T17:56:24.378Z" }, + { url = "https://files.pythonhosted.org/packages/68/21/796b9fd0616600b93d5b74ce2b4eb0f525f2773aae8635a8c0506a4145d7/reachy_mini_motor_controller-1.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:835e1d7e9383615b2684fb97cf18065f80498bb7d7d9ba980f8fe295cda68109", size = 572449, upload-time = "2025-11-25T17:56:17.627Z" }, + { url = "https://files.pythonhosted.org/packages/07/bd/661f6a44bd436c069d38ba0fdc1c1524178c4d039dcd4326fb1918bccf91/reachy_mini_motor_controller-1.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8403bb4b6c23c088493967c8445e5cd1ec4784f5c2ae1dab289fb9ba456055b", size = 642034, upload-time = "2025-11-25T17:55:56.725Z" }, + { url = "https://files.pythonhosted.org/packages/cd/82/12b52b5eeee59387866db9370e43894f66dda8feedc15be38a6963be3e9a/reachy_mini_motor_controller-1.4.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b1cf6d2a6dc10b5f8acc4c61a89e2e201eca84c6be741becbf9775084d29fc6e", size = 647692, upload-time = "2025-11-25T17:56:02.499Z" }, + { url = "https://files.pythonhosted.org/packages/f8/a0/4ad6ca320e303de61135219c3b9953b1ec1f7375181dbaaa98a43b65ecae/reachy_mini_motor_controller-1.4.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a366aa59f9bde4f80f6c03757e431a3cea80f0b18992401169b484cf8928d14", size = 696265, upload-time = "2025-11-25T17:56:07.722Z" }, + { url = "https://files.pythonhosted.org/packages/8f/98/14cb24c497c295f91b11144bf639304116630de6862ba3095fe3a18dc3ff/reachy_mini_motor_controller-1.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df475384deadcea1c8b430839addecb84126002d54649283cce5e6465d51b543", size = 660553, upload-time = "2025-11-25T17:56:12.631Z" }, + { url = "https://files.pythonhosted.org/packages/c1/cb/9bc5c91075d5e551deb2d93fae89062bb94b5edd5f658b66c97c11c460d8/reachy_mini_motor_controller-1.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6e09e99bb325405f414013ce7d13f7ed36af5b9278dce2ee6bda9ea2affe6baf", size = 825331, upload-time = "2025-11-25T17:56:30.458Z" }, + { url = "https://files.pythonhosted.org/packages/b9/2b/fd404636cb980604ed82dea5e7ec9c4de78a76324d36ff9efc55bb7549a7/reachy_mini_motor_controller-1.4.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:2f35c0b54ed396b8d9eb98dfe6dcc6d3b059433c2181030591dc84483531a25b", size = 911925, upload-time = "2025-11-25T17:56:36.802Z" }, + { url = "https://files.pythonhosted.org/packages/8a/1c/6d48c4c3c5c9705e20265db04f040f42d1955cb0ded817e8915684999386/reachy_mini_motor_controller-1.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:af23d02daff28470be0e775ce63666bea06ad24ce948649751f7122996d5363d", size = 854410, upload-time = "2025-11-25T17:56:43.296Z" }, + { url = "https://files.pythonhosted.org/packages/fc/b8/55955b7b47c8ed65bda69a221cd4805bad3d07c3c95b7002e67deec43dff/reachy_mini_motor_controller-1.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:610d74c3cfc28854137d00d206a6b4c4942a371376611781a244376fe3175277", size = 815823, upload-time = "2025-11-25T17:56:50.274Z" }, + { url = "https://files.pythonhosted.org/packages/12/7c/20672ab40d1d25cdddf49cb969bfa36043d9b760e4e82275a32853846192/reachy_mini_motor_controller-1.4.2-cp311-cp311-win32.whl", hash = "sha256:abe319839f78cc3e4e8ed0ced0bc519f97bfc1e6ff6b320dc8f84b772123237d", size = 377969, upload-time = "2025-11-25T17:57:02.781Z" }, + { url = "https://files.pythonhosted.org/packages/b5/5e/0725df6e698fe9a83eff0f86d61c5e62878c864ce521c1a1c12645409bc5/reachy_mini_motor_controller-1.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:d8f7ea29ca78ee86206989c30066e17b213f4cc8975f0a275f092588cd2debc8", size = 401027, upload-time = "2025-11-25T17:56:57.17Z" }, + { url = "https://files.pythonhosted.org/packages/e6/6a/af2786310d5ccf84a48ff0153c7be2346ef3730ca540b20d998020c36cb8/reachy_mini_motor_controller-1.4.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a41cc1bdc08b2fbba681804c6143319a6b4ef2d30093910f9776db01c5ba9595", size = 583808, upload-time = "2025-11-25T17:56:25.635Z" }, + { url = "https://files.pythonhosted.org/packages/dc/d3/6354fd9b34b89d2b5a639c56ae05f393d2ae7cef187bfcce60a71a40202d/reachy_mini_motor_controller-1.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:af3cdd5197f57108044af81166dd755214f469c368e79221521b9a698da45223", size = 570332, upload-time = "2025-11-25T17:56:18.828Z" }, + { url = "https://files.pythonhosted.org/packages/da/eb/d30bc7517a2f7c860b95978af73beede33819e717978d5560f4b5b8a2953/reachy_mini_motor_controller-1.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed2537a512098664d73a7575af763acd5446fc2325f37cf3b9b84d37368b5f7f", size = 641541, upload-time = "2025-11-25T17:55:58.117Z" }, + { url = "https://files.pythonhosted.org/packages/a4/63/8f9c091d0b210ebc6aa7762fa09155456a088df987d73dc39cda4fb9c241/reachy_mini_motor_controller-1.4.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:554dd7392b9fef86cca8c12d5861d8f17e2eb7e1d69b49c65fbf54e1410f9567", size = 648054, upload-time = "2025-11-25T17:56:03.884Z" }, + { url = "https://files.pythonhosted.org/packages/ac/15/a1bebc7acfcb02c12ba32e00dc1f9346fdf2468e441f983daf86ee4c7df3/reachy_mini_motor_controller-1.4.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e3f6ddd91e0228b3396f2c027f3e55eac0d3c4f280968ec73f2bdac37cdeda0", size = 696773, upload-time = "2025-11-25T17:56:08.895Z" }, + { url = "https://files.pythonhosted.org/packages/06/9f/a3ab6942383198e4c105075582c2355350ef33f90ef4f877105ada181e88/reachy_mini_motor_controller-1.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0b357e49773bcd338e24ed17c4a93fb1890c97513c4cd07eec8c4f3ba06bc48", size = 660676, upload-time = "2025-11-25T17:56:13.77Z" }, + { url = "https://files.pythonhosted.org/packages/4c/09/7da09c39d2722d6323ed719de95f2f7777699b0049b81d25aaff9b0cc2db/reachy_mini_motor_controller-1.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:dd05b4bbf2aa30674b2955edf8fefbdb90b522077b3c549719afd4a1d2dd325b", size = 824841, upload-time = "2025-11-25T17:56:31.967Z" }, + { url = "https://files.pythonhosted.org/packages/97/68/2c4e8160a3d8e0e20a61ad491dccb9d17febbaa434da83e38060a7517fc9/reachy_mini_motor_controller-1.4.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:377d47674a0429a01f509d9fe9b52365a715ca132bfdf4956a3b5bb219723941", size = 912195, upload-time = "2025-11-25T17:56:38.275Z" }, + { url = "https://files.pythonhosted.org/packages/14/5f/a547e59efd62518afbb616c9e3bc2f057662fdd209f75b218ef1917bcd7b/reachy_mini_motor_controller-1.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:742c1e077669c198a4261acf3213789ed679846a25714e74e9774984862bbc7a", size = 855751, upload-time = "2025-11-25T17:56:44.595Z" }, + { url = "https://files.pythonhosted.org/packages/76/58/8720be3e52e302052c6d10e3c0937271e8c376d6e5a10b2dac480c94c7f2/reachy_mini_motor_controller-1.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9b2eb49965f2dba93a1d251b0beb4b22b81f5b18946d53ff8761097011ffaa43", size = 815778, upload-time = "2025-11-25T17:56:51.74Z" }, + { url = "https://files.pythonhosted.org/packages/aa/52/7af3228652ce3503243cbce68d736af00b11afd07f0f96effa6faaca677d/reachy_mini_motor_controller-1.4.2-cp312-cp312-win32.whl", hash = "sha256:e59bd6a1b89b94da835b1fbe3f483470ea0471fdb1b92d0d4bbb93a24ab7a1aa", size = 377018, upload-time = "2025-11-25T17:57:03.815Z" }, + { url = "https://files.pythonhosted.org/packages/a0/7a/090705f6e62ec10107ea20e78fd8b8f26f6421b9db49f7bf5e275084aa02/reachy_mini_motor_controller-1.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:ae99c65942130ce04a01d50dae359694a42fa91ede864ead0defd9a693b30b05", size = 399902, upload-time = "2025-11-25T17:56:58.351Z" }, + { url = "https://files.pythonhosted.org/packages/e2/ac/fc016600786fbcdc8d048f7e42c946aa1a488e382e08b83e39934ee5f381/reachy_mini_motor_controller-1.4.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:43064e6c97be9f6fb2494ebf56dda18daa0366cd70b32ee548818c908d8b3564", size = 583599, upload-time = "2025-11-25T17:56:27.042Z" }, + { url = "https://files.pythonhosted.org/packages/07/c7/c79c33cf46bbe6301fef5520445649ce10a85a30754fce975df41f7b80b7/reachy_mini_motor_controller-1.4.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ebe3f763c011ecd3dc4bd017dfc886ecaf455d9866cab1ce8b81116d22eb6a3d", size = 570309, upload-time = "2025-11-25T17:56:20.391Z" }, + { url = "https://files.pythonhosted.org/packages/78/e0/d301987769c1504549c06cf0ce8e132df48bde0f47d28a8768c25b7ed8ae/reachy_mini_motor_controller-1.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c86146d3631dd73efb77d591f8a33c288b9ad394d3a6805c0685f3c5ec05266f", size = 641472, upload-time = "2025-11-25T17:55:59.645Z" }, + { url = "https://files.pythonhosted.org/packages/ff/52/7884ee1c04b480f8e34b4c97697a0977d314a22f6766fd5e246dfbddb404/reachy_mini_motor_controller-1.4.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:36052e08d331ae768f4b562e950eaa767ae4ee8a91e9334498c1bcbafe841514", size = 648510, upload-time = "2025-11-25T17:56:05.286Z" }, + { url = "https://files.pythonhosted.org/packages/03/56/619bb25bb31f96d6172d1bfd5bbf516b5985b595f6e1c7c82481fff95c55/reachy_mini_motor_controller-1.4.2-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b2ac6712f7a5493bb3be276fa53bb9b9e4770f13783f26ff0a18e7fc6f800d4", size = 697241, upload-time = "2025-11-25T17:56:09.99Z" }, + { url = "https://files.pythonhosted.org/packages/5a/b2/c82a31cf252ff6fb4d4c59a208d8375159cc796326812b7e946eea5c00a4/reachy_mini_motor_controller-1.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3fcb404dc2021175d4339fb3a8f9334cd999bf8a0369e0dacb02c430edf46a0b", size = 660207, upload-time = "2025-11-25T17:56:15.23Z" }, + { url = "https://files.pythonhosted.org/packages/83/6e/b2d9aecc4144cffcee14b023da20b8850a11cd35375561a7e60be7132e10/reachy_mini_motor_controller-1.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f482f61eecdeb72a7722399d0d549872a239023c263968f9e2ffac9ee69687dc", size = 824941, upload-time = "2025-11-25T17:56:33.129Z" }, + { url = "https://files.pythonhosted.org/packages/c0/68/72b7d8a405fe7b1785b2caaaf917958dc6691015f6c15089bfee8b35f65d/reachy_mini_motor_controller-1.4.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:88e1a5c1d4afe4e695d589715f0628536fe7399ec6d2751025dd785f4282a62e", size = 912805, upload-time = "2025-11-25T17:56:39.585Z" }, + { url = "https://files.pythonhosted.org/packages/c0/62/4ce77e08ea5dc428835202723671026f52d46e20ee2642af24ecd2c64858/reachy_mini_motor_controller-1.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2d5c9e2645d7c6b29a8a46b4ecef72540bb2c0f983349cfcaf7c681386e7a002", size = 856256, upload-time = "2025-11-25T17:56:45.894Z" }, + { url = "https://files.pythonhosted.org/packages/71/a6/c031586c9dd8fc735b427c5fcdd20d9360c02a6c61ab3cbb59589ad15d9d/reachy_mini_motor_controller-1.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffb5331150c0cdfac9910d341e82d788bc173e473e893e3f8e54938ede1e3523", size = 815565, upload-time = "2025-11-25T17:56:52.837Z" }, + { url = "https://files.pythonhosted.org/packages/c6/e3/5316b60491ecf3384b1b51db49317bc175561411edcc83fe1398b68736f4/reachy_mini_motor_controller-1.4.2-cp313-cp313-win32.whl", hash = "sha256:6edcf88f5d4c1bf3948328a6f65cd4518fda8cefbe55fb884fda088f363c2b60", size = 377394, upload-time = "2025-11-25T17:57:05.059Z" }, + { url = "https://files.pythonhosted.org/packages/19/c7/41f4efca4abaf31bff5fa891ae0f594623539afd936abea51e0e546a6700/reachy_mini_motor_controller-1.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:602cf3294f54ceae4fa84d006aa0e97dcfcd99f7ee556cbbcf3546ac42fe8fb3", size = 399788, upload-time = "2025-11-25T17:56:59.383Z" }, + { url = "https://files.pythonhosted.org/packages/3a/0b/3b118d1ed20da22c3844c88247a78f6fdee679fff149d0e1ee64fc5d8aa8/reachy_mini_motor_controller-1.4.2-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:aedcbebb270c3e6ad8898aac321f82a3d3060898ee33d75ae181b0f1df278f93", size = 581082, upload-time = "2025-11-25T17:56:28.189Z" }, + { url = "https://files.pythonhosted.org/packages/5a/e7/c24da5194f16714d6f2a67fefaba817e2a22f68f58269500fa4495d6a29e/reachy_mini_motor_controller-1.4.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4d8d7626e3655a96266cc88d9ae1327c8b42512b61e8acaeb782d8d3352125c2", size = 567918, upload-time = "2025-11-25T17:56:21.842Z" }, + { url = "https://files.pythonhosted.org/packages/89/69/cc9a51e04c1523213a8f4121bb1c454293d287f8c0fc4998e2640d053169/reachy_mini_motor_controller-1.4.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8b27d6769f12a4bf75b8b6cb0e0dc20d154bc1422ff9e046f530434050552b86", size = 823910, upload-time = "2025-11-25T17:56:34.482Z" }, + { url = "https://files.pythonhosted.org/packages/cd/76/1f283234ec0fb1b349a24930485a9c584d94f064eb4d3d2e32e5607ec64e/reachy_mini_motor_controller-1.4.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:07118c8cafed873541fb716928a3d4f807ac6ac7f769d7307faa4cd9bead6e6d", size = 910812, upload-time = "2025-11-25T17:56:40.804Z" }, + { url = "https://files.pythonhosted.org/packages/5c/22/771e3e02872c319f999c5e0830787c926d2b74b976c71f041d5429c4f785/reachy_mini_motor_controller-1.4.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:d552fdf890c3040232b2db783fc03ffcee230b633208e62b7a4265fa5d076598", size = 856433, upload-time = "2025-11-25T17:56:47.095Z" }, + { url = "https://files.pythonhosted.org/packages/b0/a3/c897ed4e7a43d205d43a985669dfbbc3943d1df8a101038c5384f34b9617/reachy_mini_motor_controller-1.4.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4b250a7763a72d6864ef4cdcec088d56e4d17947a10493f7347f2001502f05ed", size = 816617, upload-time = "2025-11-25T17:56:54.099Z" }, + { url = "https://files.pythonhosted.org/packages/03/67/63dc13fe0fad1bafe12122b10479b2e22bb4562e58ed45e14f18b3f076c5/reachy_mini_motor_controller-1.4.2-cp314-cp314-win32.whl", hash = "sha256:d9a6be4852cb8cbec2e19d2037234dce39defce653f3af9d4a46cd56d624c617", size = 376862, upload-time = "2025-11-25T17:57:06.232Z" }, + { url = "https://files.pythonhosted.org/packages/32/08/0c4b6fc4a761ad8bd362f571d3693c94aa0c15121e122f1c01e3f38bc873/reachy_mini_motor_controller-1.4.2-cp314-cp314-win_amd64.whl", hash = "sha256:aa7b6ca1b6db09e6249fdb5a31376974e82928292950cd7df4480cbd5707a425", size = 400104, upload-time = "2025-11-25T17:57:00.616Z" }, +] + +[[package]] +name = "reachy-mini-rust-kinematics" +version = "1.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/40/82/485aa2f28973eee6198b2af0db9e76314fd909076f06782228fa686f0e3d/reachy_mini_rust_kinematics-1.0.2.tar.gz", hash = "sha256:ca14f9193c73a53612a32cd0bfb9abecb13343bc94fe5feb95f54736b7f3cd64", size = 17187, upload-time = "2025-10-22T08:35:50.104Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/22/e3733bb138a59790d4355ecefb7ca2fd5cd2cd6af05bf26fa7ad24b6ace5/reachy_mini_rust_kinematics-1.0.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:0137084968573589d1958372e140097441e4a8562ac44b7b94407d2fd1b05aa0", size = 303531, upload-time = "2025-10-22T08:34:53.096Z" }, + { url = "https://files.pythonhosted.org/packages/8f/cf/1297187d79bc8268ca352702dac152a577ca96d25188767c1e4c43e51b5b/reachy_mini_rust_kinematics-1.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0c6457a28dd48a958d9ecf84a119736ae8cd0cfe7d002a518afe90ca8d4c1f4e", size = 279068, upload-time = "2025-10-22T08:34:43.331Z" }, + { url = "https://files.pythonhosted.org/packages/e3/d9/e62dff886e73ecce02ad81dcfaf95e315d32f9134cf1db14c78a0a8dd851/reachy_mini_rust_kinematics-1.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76067037701db3e4ef9bb99dca34d06cf4ffc6952f63d05d815db090ebb49330", size = 312732, upload-time = "2025-10-22T08:34:01.097Z" }, + { url = "https://files.pythonhosted.org/packages/b7/27/6915e8c2e97cb97569742e9f77865b27887b3ad916d5b70c8c034785e2fe/reachy_mini_rust_kinematics-1.0.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4ee7cb4a450cabe1265974be1cbf784ee85bab605d05a3173fe290e008f166e8", size = 321001, upload-time = "2025-10-22T08:34:13.385Z" }, + { url = "https://files.pythonhosted.org/packages/dc/77/b6a7b46bc18f3c042d6591cdd0cf733ffbe938d73993ebf9e172105913cc/reachy_mini_rust_kinematics-1.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d77ace8803a4fabbcf2696c31a9b2ac0136a3c9db83ac83613782934fc6c94d", size = 337679, upload-time = "2025-10-22T08:34:34.543Z" }, + { url = "https://files.pythonhosted.org/packages/05/1d/ff2d5c5c9cc9f992983e07d455316e88f5ab2bea2f0859610c25737c0c87/reachy_mini_rust_kinematics-1.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a1515cc1028a472357cdf8005ee6a4e3ede919db6205af96bee215d6af967920", size = 351670, upload-time = "2025-10-22T08:34:24.985Z" }, + { url = "https://files.pythonhosted.org/packages/b8/4d/e3b34b00019d20b5dedd51e2a03500ea1fcb2bd2957b53ce730a9ac4c4cc/reachy_mini_rust_kinematics-1.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:40b03c1a09a3aef083df78bdc0b160f1b1e18cb660c7f134817b82b3d8a7de88", size = 493329, upload-time = "2025-10-22T08:34:59.959Z" }, + { url = "https://files.pythonhosted.org/packages/6a/75/acab41d132e7237bb06535f2b4a999f7a231fbee37ec368f7567454f73f9/reachy_mini_rust_kinematics-1.0.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:5ff289e4df686ed764b16df1e67d9f94da90422c2784baf0502bf67bd89e27ac", size = 585106, upload-time = "2025-10-22T08:35:13.636Z" }, + { url = "https://files.pythonhosted.org/packages/ef/12/3eead1dd55587c3072827717ec28fbf029a5c8bf7bdf877a8a0dd1292123/reachy_mini_rust_kinematics-1.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3b8c3af089ee5452f29912ec9e41840a7db99f96e482a7d837b8c21a2cdfc8cf", size = 526581, upload-time = "2025-10-22T08:35:26.295Z" }, + { url = "https://files.pythonhosted.org/packages/47/62/5b6f069a373c2c16046094d5054b28932d932cf701675089caed3e7eb7f1/reachy_mini_rust_kinematics-1.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e96d478a9acec92dc1c44e7b490b43e45cf182c327fde44f1e42ca377445aae2", size = 499068, upload-time = "2025-10-22T08:35:37.591Z" }, + { url = "https://files.pythonhosted.org/packages/f2/c2/fe92dbbee923d0b42fb2c44a6c9876490ef71cd6eb74fb63c73358f9726d/reachy_mini_rust_kinematics-1.0.2-cp310-cp310-win32.whl", hash = "sha256:5c2f0c442c3b4f5a351c9e780b5886259c093a711579ea6e59f3658838450bec", size = 176754, upload-time = "2025-10-22T08:35:57.424Z" }, + { url = "https://files.pythonhosted.org/packages/04/ab/c4a3541e681249deba417c7bbec819f4c44ab532bbc244f8e5cb6eea549d/reachy_mini_rust_kinematics-1.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:b3aef57e7ff74e5dac8db7e299f9c15c7a2e9934466ab2456d129bb8244edd08", size = 187313, upload-time = "2025-10-22T08:35:50.932Z" }, + { url = "https://files.pythonhosted.org/packages/2e/fd/c3801f0579da40147c7cadc1226bc3f5b7413cc590b13f53822674a910f7/reachy_mini_rust_kinematics-1.0.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:b9fe481d3ae29415e9be3358493bd6d26add4b494650384c650181595da10204", size = 303080, upload-time = "2025-10-22T08:34:54.167Z" }, + { url = "https://files.pythonhosted.org/packages/ab/5f/1b28508a05fde9bf80277222991273721462afa84a7a0ede6227692b6a75/reachy_mini_rust_kinematics-1.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:58c87c6cb62a020f325caf8344f003ba4ebb783e20f417c6d89e3b63a72bc8a1", size = 278691, upload-time = "2025-10-22T08:34:44.295Z" }, + { url = "https://files.pythonhosted.org/packages/de/22/9b4c4b007085db8fb329656146742414e64051d9deb8e108020fbb3aafa8/reachy_mini_rust_kinematics-1.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:af2f330760b5519dcf556c728ddaa889fbc1b5ef6d76a4f3ef2eee1ddd032cb4", size = 312649, upload-time = "2025-10-22T08:34:02.52Z" }, + { url = "https://files.pythonhosted.org/packages/c1/e7/b50b441d287e72864e32b1049ab6d740158a8330deba5ec2cfa62d5125e0/reachy_mini_rust_kinematics-1.0.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d8bffa448b0a75a570c5b8625344de3a755bca7f3a7450b045df153cef0cb402", size = 321102, upload-time = "2025-10-22T08:34:14.669Z" }, + { url = "https://files.pythonhosted.org/packages/3b/4b/cddf709a7012450d3255d7ab6ee6a1216e761664657ffe68806712d95c42/reachy_mini_rust_kinematics-1.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27ae0811ccc4132f9b034771af061c513bcb0bd18a73dad1d16abc6bad366caa", size = 337443, upload-time = "2025-10-22T08:34:35.544Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6d/908fca70c2dee3e066831fcde011fb0d8c563418c75e052208bc538105cb/reachy_mini_rust_kinematics-1.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5777d235930be71186adf948388580317943a022aa3761d6f78bcffe7dfc5ee4", size = 351864, upload-time = "2025-10-22T08:34:26.064Z" }, + { url = "https://files.pythonhosted.org/packages/4e/e8/ddbfbc71cc2a7246de06cd4a0e934c9613a3a814406afbf8dfcd071813bf/reachy_mini_rust_kinematics-1.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6cbf226d07d34a6a395635b90ebb4d6e083136f07ca2e746d4431c44b00fd2d5", size = 493208, upload-time = "2025-10-22T08:35:01.445Z" }, + { url = "https://files.pythonhosted.org/packages/75/d1/51af83f5b70c2eaf5f871efdf62c7d88116af2d5309898b2de9925857556/reachy_mini_rust_kinematics-1.0.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:bab5d550e2225c9d4d6ee69b98fd13cf75b8b3df4ab9824d7759668e04a6e5bf", size = 585005, upload-time = "2025-10-22T08:35:14.756Z" }, + { url = "https://files.pythonhosted.org/packages/ca/a8/cef7d415d864c8fa04362485ecb1648947a194b90426d147972ce59f4263/reachy_mini_rust_kinematics-1.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7fc0243f9abc3d14aadaaa19b86d4930bf1e3bace0bfbbde73048ed80928b0f5", size = 526789, upload-time = "2025-10-22T08:35:27.367Z" }, + { url = "https://files.pythonhosted.org/packages/d2/4e/98e8e72f3cf0758eb4e6eb7320438dfb9e08d6ff593f6cc3661e726cf9a1/reachy_mini_rust_kinematics-1.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a060b9022becd8decf9868063099fe68e598f7c07406d96795408ca862d208b3", size = 498731, upload-time = "2025-10-22T08:35:38.944Z" }, + { url = "https://files.pythonhosted.org/packages/26/fc/d9b66b439ea27c20d01e6cc841b34b3a8cc17fb0e79acaaa8ba4f5a4c587/reachy_mini_rust_kinematics-1.0.2-cp311-cp311-win32.whl", hash = "sha256:ebf961d3ae3cd0798ed8d4e2bec8aec57f33fe2fb713852ad0ba2de4386c9c1a", size = 176607, upload-time = "2025-10-22T08:35:58.496Z" }, + { url = "https://files.pythonhosted.org/packages/9b/be/848776e83114ee92d9799860860be6dff036f22c9d01e85805c02a6d05fc/reachy_mini_rust_kinematics-1.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:8afa30ed7da49544c8215033d2375c2f76f3311a6a1f380fe9acd2c076f9d45f", size = 187332, upload-time = "2025-10-22T08:35:51.994Z" }, + { url = "https://files.pythonhosted.org/packages/11/39/6c40cca1970ce47d9ae1661c87230cd37a6e2d782f34e0aec745f155d831/reachy_mini_rust_kinematics-1.0.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:356134c5df18a01226432fbd1fb24649aced5a14da3c0406ebe2d1b3b8c002d2", size = 299587, upload-time = "2025-10-22T08:34:55.208Z" }, + { url = "https://files.pythonhosted.org/packages/5c/30/f9ba417bc1782d6a256ef9024eb0329c50cb7a3d12e83afd4b67047a01d6/reachy_mini_rust_kinematics-1.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a232408fb7c58d6da73c40a40a120bae4f51bf9119de108530572723a00280d7", size = 276656, upload-time = "2025-10-22T08:34:46.904Z" }, + { url = "https://files.pythonhosted.org/packages/75/e7/e3bb2597d86f2037a1e4537f2352d7e92ff4699ee725b43a90400f15894e/reachy_mini_rust_kinematics-1.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e7c3d2c73f89bf817d8c3abb8f0b23a6efdfce330a749c6e29e1e0cf6db9d7d", size = 312798, upload-time = "2025-10-22T08:34:04.219Z" }, + { url = "https://files.pythonhosted.org/packages/62/2f/64d2c4b434ef413c948935a0f57b4a502cc7effad68dd97263cdee22df64/reachy_mini_rust_kinematics-1.0.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ba0faba18be71b68026c70ccf4db6d9ea588774053d22487f228b490204a8a00", size = 321023, upload-time = "2025-10-22T08:34:15.628Z" }, + { url = "https://files.pythonhosted.org/packages/c8/0e/f4cbddd5c0ed10cdba7f4baffc68f2e8102eb1d55b309690a52af3b067f8/reachy_mini_rust_kinematics-1.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0bed2ec420ea354ce62572afe8672ae8701a139781d9d8da97932597907019ee", size = 337756, upload-time = "2025-10-22T08:34:36.66Z" }, + { url = "https://files.pythonhosted.org/packages/e3/d5/ac043e65949ba904b7ac901d74b8514ab47203de3af44e96c44dd2da7a94/reachy_mini_rust_kinematics-1.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a79f6517728463294d5ca4945b18d843dd16cc410815ce039b35c1198bdc355d", size = 351251, upload-time = "2025-10-22T08:34:27.06Z" }, + { url = "https://files.pythonhosted.org/packages/2b/ef/80bb31cee07d2336013808ce3b76dee29ae04274f053e1694e3a285da555/reachy_mini_rust_kinematics-1.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5404cf14cc8894962dbbc82594af453588539b4b9458b8f95f43c29bc8bd2e34", size = 493292, upload-time = "2025-10-22T08:35:02.56Z" }, + { url = "https://files.pythonhosted.org/packages/bb/e2/4f6c5a0d2a77111a9806243160b1aa3531ad3024a59f05c7f6157917bcc5/reachy_mini_rust_kinematics-1.0.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:2e1f9d9ce4222d7ecda5cb77c96fed763de3ac94993e60c73adf9749d26e16df", size = 584893, upload-time = "2025-10-22T08:35:15.981Z" }, + { url = "https://files.pythonhosted.org/packages/bb/b6/b0b57c9c46b77aa3c03f4071fca0c92809e23f9b22df5f85446ec5329fe2/reachy_mini_rust_kinematics-1.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:7c29c3d7b70d0576c740969cfa3e35ba98cc8e378c0867a79f53d5fee3568f77", size = 525948, upload-time = "2025-10-22T08:35:28.451Z" }, + { url = "https://files.pythonhosted.org/packages/06/12/f4d94e8c0b1b19b47072c2041f31fb83701cff1a199d0fcf012c0489f32f/reachy_mini_rust_kinematics-1.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5b94ca8e94058efcd89553144b0a0b0f7c4abfe67a4cd60e2993cd0833c9e130", size = 499239, upload-time = "2025-10-22T08:35:40.026Z" }, + { url = "https://files.pythonhosted.org/packages/7a/b6/907870cb3f588a6da58b64357c03241a28dac8a9ec9f42e91c291cb95c51/reachy_mini_rust_kinematics-1.0.2-cp312-cp312-win32.whl", hash = "sha256:edcbcedaa4aa99684588bd897733a33ff8f8d9eed8ae39bd7d0fd691874f0457", size = 176379, upload-time = "2025-10-22T08:35:59.841Z" }, + { url = "https://files.pythonhosted.org/packages/5c/c0/a5c1db0bf8dc55cf6b41a11463285ef440a2efa74d8b75be8443234c1acf/reachy_mini_rust_kinematics-1.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:eb5d6d27ce08e8816496b6049979cf64ffbf3901dd5bb290efe439f5455526c2", size = 187469, upload-time = "2025-10-22T08:35:53.307Z" }, + { url = "https://files.pythonhosted.org/packages/13/5c/fffb02be21f666cf880042804c703de2410f32a44b0412d6df20bb156d15/reachy_mini_rust_kinematics-1.0.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:2aae2b9424a111935e26977fe2ab25c130c766e8049d72073e31a7ad78c84fb5", size = 299868, upload-time = "2025-10-22T08:34:56.48Z" }, + { url = "https://files.pythonhosted.org/packages/a3/33/b7e610c6325ed4fe439bd0be3e96850b5a5b6dc360116741bdf4c3a57ce9/reachy_mini_rust_kinematics-1.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:38ba383417c472ac67945a57c62ba621128b4486bd9d818512929454f47ce76d", size = 276655, upload-time = "2025-10-22T08:34:47.905Z" }, + { url = "https://files.pythonhosted.org/packages/93/bf/a1f579e4a6930b2155c99f80ac5b96d256c0047537c0802c34f0adfcde81/reachy_mini_rust_kinematics-1.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3788d9c5b5315eff026486c3367e97ea23cd4ccd3eb6c7c1e37e0d00ec6b7128", size = 312962, upload-time = "2025-10-22T08:34:05.504Z" }, + { url = "https://files.pythonhosted.org/packages/89/cf/bf2a450adb2e6fbc5a9baa3ad9bece4d7272341723f4e76eff710463b484/reachy_mini_rust_kinematics-1.0.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6983b555c1af6fd2c1ca8b32ae374c2b060239bb04b9b562806771ddafe81abf", size = 320871, upload-time = "2025-10-22T08:34:16.889Z" }, + { url = "https://files.pythonhosted.org/packages/1b/ad/1eab50e3c56a65ac2f14a4890ae5370984586f52adf13cd8a7963ebf423c/reachy_mini_rust_kinematics-1.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca28a8cda1ab4973259ed9aa06c7173ac5899492a3140075c58225a0ed96be52", size = 337653, upload-time = "2025-10-22T08:34:37.679Z" }, + { url = "https://files.pythonhosted.org/packages/a0/ce/8d3802dde54f9595daa9bd7b12a3de42ba4b4b48d92d8abe7a43919d74a8/reachy_mini_rust_kinematics-1.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4c8591616ef354d4f0724affef58eb14374f4a06b649b7947e94b97ec22d2fb9", size = 351553, upload-time = "2025-10-22T08:34:28.126Z" }, + { url = "https://files.pythonhosted.org/packages/3f/8c/5e1de72d044440afb8cb7480af0c9ddcdcd9654b0ac62c11113fedc731d4/reachy_mini_rust_kinematics-1.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:45f4d9f159714e60eef22c9c00232beb34a8d27a89acce7d3ffcec2b86992b06", size = 493556, upload-time = "2025-10-22T08:35:03.963Z" }, + { url = "https://files.pythonhosted.org/packages/6f/8b/1a33d934b9720586dc261349b500fb6a969ff1d3ba7c6f9bb7ba87d61d37/reachy_mini_rust_kinematics-1.0.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:02a525696238dc85e9a1f3260d7d0db25074bb54f1cc2a0a4052849344026806", size = 585019, upload-time = "2025-10-22T08:35:17.344Z" }, + { url = "https://files.pythonhosted.org/packages/b9/5e/8cf4011456e3262f2a67b66ee77838cc56655cdcd7941efd44cb6ff8eb85/reachy_mini_rust_kinematics-1.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:342b4a9adb9714f98d6569121c053800ae0a5b3904a9863ca65603bb3b9e7f0a", size = 526143, upload-time = "2025-10-22T08:35:29.636Z" }, + { url = "https://files.pythonhosted.org/packages/0f/3e/0d1168324c288693c07abd0050b83f682cf12a27c850e42d2601d3da84ef/reachy_mini_rust_kinematics-1.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9b596910a1ba0aaf79fc525ec5a8405dcd41c28946667faa549e789e5c1b93a5", size = 499284, upload-time = "2025-10-22T08:35:41.084Z" }, + { url = "https://files.pythonhosted.org/packages/66/4e/0f69c0287a9289a0bc78d623cbf8cbd652e54ff6e854979b4985eb7fa75e/reachy_mini_rust_kinematics-1.0.2-cp313-cp313-win32.whl", hash = "sha256:d5437b5d7f9093e879859d83da6888738f281fe728fad0a9f1678fe01035b4d3", size = 176631, upload-time = "2025-10-22T08:36:00.974Z" }, + { url = "https://files.pythonhosted.org/packages/94/00/eff3fa6480da3c2b486b8bcec55ea650b4955fb874664e9e7dce205a9885/reachy_mini_rust_kinematics-1.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:c3c813012eacc7e779c81388f3dcb30811bee461672f9f9dc03835c73e3a5bc4", size = 187334, upload-time = "2025-10-22T08:35:54.464Z" }, + { url = "https://files.pythonhosted.org/packages/0a/e2/2c19fbf08a5e3836147ee345b9d75fbe1cd26df8d1f9e83b9cefa4bc80ae/reachy_mini_rust_kinematics-1.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e1f563b31cbb0e0ee9f015801ef2eba31b9c37c306e3ca7737097a482355e0a", size = 312043, upload-time = "2025-10-22T08:34:06.505Z" }, + { url = "https://files.pythonhosted.org/packages/b2/d4/873c4317a598e9709dd2aa68f0a072568b1f2db3616a2ffa76259618889e/reachy_mini_rust_kinematics-1.0.2-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:874b986c44a94e1e1752f8f0b159342b0f60d4610e4df9c4bdf421e2749d949e", size = 320190, upload-time = "2025-10-22T08:34:17.889Z" }, + { url = "https://files.pythonhosted.org/packages/1f/11/64941229b39bfb1c39ea2ccb5fd02998915f87e40e277e4b0b744a2c3a2b/reachy_mini_rust_kinematics-1.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f5060865aba96d3ffe1ac5cb93dc6439154a78ef8fdbb8a4313e4dc33064efb5", size = 492437, upload-time = "2025-10-22T08:35:05.014Z" }, + { url = "https://files.pythonhosted.org/packages/20/47/82704725e0d5cae887a4604e9bbf7688e6a676768ab47202b018ef9f7f32/reachy_mini_rust_kinematics-1.0.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f7ddeef8610a936afbfd7dda1713be18d207032e18604e46963729996c3d1a41", size = 583980, upload-time = "2025-10-22T08:35:18.517Z" }, + { url = "https://files.pythonhosted.org/packages/97/6d/bfcdc6912531c62885450646f629aa581fb116ee5806dba00c01712b02f4/reachy_mini_rust_kinematics-1.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2f498398c933e87a63a5747a2c813df09dcb68ded3ba79f86d092801ced36da8", size = 525181, upload-time = "2025-10-22T08:35:30.819Z" }, + { url = "https://files.pythonhosted.org/packages/e0/6d/4f32bc65f83307a6eba5b62231e276bde8509d26dd8ba1a9997fb6c4b924/reachy_mini_rust_kinematics-1.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6464868b209ec5cc0e50829b25bdad8cf765a48c943a5a7f504a3ec4ed7aedcb", size = 498743, upload-time = "2025-10-22T08:35:42.183Z" }, + { url = "https://files.pythonhosted.org/packages/85/26/2af509eb72c556a5f8628454122f61bcd55ae760ed74c2a1d8cfe21ebafe/reachy_mini_rust_kinematics-1.0.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:419cba0b2ca01b52aba79004681201e1ad16d5a06a2ae9f4f74c45ea82e88f11", size = 276979, upload-time = "2025-10-22T08:34:49.023Z" }, + { url = "https://files.pythonhosted.org/packages/82/62/5fdf253c4293447fe2b95329891a46292fed02fd15f71a7eafbcca842194/reachy_mini_rust_kinematics-1.0.2-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c654f6e06da8c6f8f5f2613c7a8d1841dca730ba2397125418f1b26bfeb46330", size = 337650, upload-time = "2025-10-22T08:34:38.745Z" }, + { url = "https://files.pythonhosted.org/packages/01/2e/eee9bed34de89569f52965fdbf6bd16c914c1b87f4f233e49ffa1f3f0590/reachy_mini_rust_kinematics-1.0.2-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:58a36591b27a80c0836ab08ff21bf605413213f079baeef6fd1e3d6b47d8a341", size = 352082, upload-time = "2025-10-22T08:34:29.451Z" }, + { url = "https://files.pythonhosted.org/packages/06/b2/bf657750a75dfc8b5e12c1f4ca2e09ed4c4c7ea207295fc82a71e34dfad1/reachy_mini_rust_kinematics-1.0.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260bef2b277a63f5ded78b03cdcb98127862ae8eb6c4794ea25a71be5a03cd53", size = 313219, upload-time = "2025-10-22T08:34:10.265Z" }, + { url = "https://files.pythonhosted.org/packages/d8/3d/df94e31f27e1f5e725ae188c6804e9064a78be6a3d052b3a2146857afd5a/reachy_mini_rust_kinematics-1.0.2-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2f73d69afbb6ddf4a0cf24e557a6340e71777a2b6de2ca4ac3243bd3f8abd5", size = 321182, upload-time = "2025-10-22T08:34:21.259Z" }, + { url = "https://files.pythonhosted.org/packages/19/94/35c9fc05d387026594f576b514d964af0a69e2b00712069215a9a735365b/reachy_mini_rust_kinematics-1.0.2-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:111de74d29630f1a2d8d0282328764bc490cb4aa63270b4f58467e9766225bde", size = 493659, upload-time = "2025-10-22T08:35:09.472Z" }, + { url = "https://files.pythonhosted.org/packages/e6/11/505834201862263b5d61beca2bbd43300f669a5960d482edf44ebb8cf835/reachy_mini_rust_kinematics-1.0.2-pp310-pypy310_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:3126d71e7bdaa180eb62ef97cfd56cdf3b21c0f0693535ca44ff2ee12caea75b", size = 585112, upload-time = "2025-10-22T08:35:22.295Z" }, + { url = "https://files.pythonhosted.org/packages/c8/d9/eb0ec95412262132dd88494f85ec40b58f2ae6c83e4908585746edbad3c7/reachy_mini_rust_kinematics-1.0.2-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:89735b9ddb19eda38f4a2ebf1c7d7dbddbc9b39f55d407e87db5e151bbbca4b1", size = 526493, upload-time = "2025-10-22T08:35:34.19Z" }, + { url = "https://files.pythonhosted.org/packages/ac/0b/e403e4ce2d3ffe1d97efc54a5ddb024b1fff6c6f8607a1a306f83a615b47/reachy_mini_rust_kinematics-1.0.2-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:58e4edeec417a64c2508d25ce57a45932253d2339035bd66388a54beaaa91afb", size = 499556, upload-time = "2025-10-22T08:35:46.538Z" }, + { url = "https://files.pythonhosted.org/packages/bc/2d/c4492e19625f81348c812e5e17e97e777c521fe7200006bc41ebc904dd14/reachy_mini_rust_kinematics-1.0.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:305793e3097db1a93eabeeb5bcf7a581dedb859740d5f5164d7f2b695ce90953", size = 312932, upload-time = "2025-10-22T08:34:11.265Z" }, + { url = "https://files.pythonhosted.org/packages/9a/b2/d6cdc7960650c609b3e902734dcd10d6eb44373ba4f0aadaefde401244ba/reachy_mini_rust_kinematics-1.0.2-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dda63846d7196737bad42d9de847b3c4702e68e40fc0a691fed50a87b9ef0898", size = 321183, upload-time = "2025-10-22T08:34:22.468Z" }, + { url = "https://files.pythonhosted.org/packages/51/74/d92143a61237d3f83ba9f6398f7d546cdefd8d43f820677bcf4d7ed3a265/reachy_mini_rust_kinematics-1.0.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b307111f6ff46a1e8dc5b0d600c836ccf237106cf0bb9b87d85e079199fb733", size = 337577, upload-time = "2025-10-22T08:34:42.092Z" }, + { url = "https://files.pythonhosted.org/packages/05/e4/5ab1e64bd201e388f3fc0bb1ee89ef166479c8482d582ee58d603ba1b6ba/reachy_mini_rust_kinematics-1.0.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64649a8ca74f3f528d57adf02f3b5ad6eaab4e5ab40bf837554ad878f0f4ea64", size = 351547, upload-time = "2025-10-22T08:34:33.109Z" }, + { url = "https://files.pythonhosted.org/packages/5f/19/f754c4cac653d3133145694abb6bb2d0da15b2459c1361b35528b3d16287/reachy_mini_rust_kinematics-1.0.2-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:6f0eda0e6cb8d86e80937a78b44e3db5e338039480fc077503aca3c23af008e6", size = 493554, upload-time = "2025-10-22T08:35:10.535Z" }, + { url = "https://files.pythonhosted.org/packages/af/81/14e1848dc1fb662a9dd8108bb3be4161a241055d38bac65eb9ee1fe5926c/reachy_mini_rust_kinematics-1.0.2-pp311-pypy311_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:12b9c538544352ee829338b3ae34623e3903294cd09ebb286f9467cad9882360", size = 585142, upload-time = "2025-10-22T08:35:23.413Z" }, + { url = "https://files.pythonhosted.org/packages/bb/47/a74608d37b58a162a3204ad4a1b7981ca79dc598b6c76533922cdc2bcb48/reachy_mini_rust_kinematics-1.0.2-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:56eb81ad90e2b04506b4adf809b48a449fbd3972e2b32bd49038b745a6ff2d38", size = 526427, upload-time = "2025-10-22T08:35:35.379Z" }, + { url = "https://files.pythonhosted.org/packages/d7/8e/c48f621c8d954849deb081a7a8840c57e38385dad999aaf5b82e10a09f84/reachy_mini_rust_kinematics-1.0.2-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:2af9062ffb5970b3318a85eaa7ce78042e7b023e7c096e34002da5aa083920dd", size = 499551, upload-time = "2025-10-22T08:35:47.616Z" }, +] + +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + +[[package]] +name = "rerun-sdk" +version = "0.27.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "numpy" }, + { name = "pillow" }, + { name = "pyarrow" }, + { name = "typing-extensions" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/7d/ef/38ecd7fbb31b846d749299c2b848a30e85b7a6a7d496ea054373abfb3b08/rerun_sdk-0.27.2-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:e3e180a97085e7d7e1f1aee20c45f96173c87d723ce155573b96441733ef1560", size = 99965645, upload-time = "2025-11-14T14:49:18.53Z" }, + { url = "https://files.pythonhosted.org/packages/e7/67/ce06267c5fc5c7bb1cc027410a75cca0c5232afa1d4072ff2abe685cb923/rerun_sdk-0.27.2-cp310-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:224d168b9673bf9237057db6beb62a3a63223d702602c2e57283089ed77acf42", size = 107368188, upload-time = "2025-11-14T14:49:24.538Z" }, + { url = "https://files.pythonhosted.org/packages/2c/6e/0be74188a53af91572b466e6f672dae476b9058ca71c7d053e753a665087/rerun_sdk-0.27.2-cp310-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:5583f9d67b485e405939ddb1f9da38cc7202b326a15b1110d71dc7d2fdacc28b", size = 114630744, upload-time = "2025-11-14T14:49:30.28Z" }, + { url = "https://files.pythonhosted.org/packages/d4/8b/9fcb34b9d7bde9183372cc3dbaaca8d2ce49a43015e41dfe35bb62dfbef7/rerun_sdk-0.27.2-cp310-abi3-win_amd64.whl", hash = "sha256:d0437b73a807bb4643d3a7fee163e65e0df0459e6228eb2b087692610612ce99", size = 97879453, upload-time = "2025-11-14T14:49:35.062Z" }, +] + +[[package]] +name = "rhoban-cmeel-jsoncpp" +version = "1.9.4.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cmeel" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/0d/8686269eecbea32f314865075521b6305df709fe6bf0793397e31ef7506a/rhoban_cmeel_jsoncpp-1.9.4.9.tar.gz", hash = "sha256:953003fd1ce3b447170ef21747180f70a50ec2e0b81c6e193088adc9a90d37da", size = 220876, upload-time = "2025-06-24T18:54:10.885Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/8c/6006d71a441c2e51427ae2016ef8b227a78f9074717be46fbe2b196c805b/rhoban_cmeel_jsoncpp-1.9.4.9-0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0c032d05ce19aed18bf6b454daa510d4dcd89366745e7bfd062a6a16c3bfd51", size = 289726, upload-time = "2025-06-24T18:53:06.57Z" }, + { url = "https://files.pythonhosted.org/packages/38/fe/080a251ef48c99e1bd272fae8442a65864b7ddba94cd207774e6c84e659d/rhoban_cmeel_jsoncpp-1.9.4.9-0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7b606a2bed978408825be84a4731bed3a10b7f91e6dbeb5dca2b0955ed5453a3", size = 281613, upload-time = "2025-06-24T18:53:07.909Z" }, + { url = "https://files.pythonhosted.org/packages/91/18/f035f099a24bbe0b468387c5eede093ba4ac1636c5b5ef8bcf0c946fba80/rhoban_cmeel_jsoncpp-1.9.4.9-0-cp310-cp310-manylinux_2_17_i686.whl", hash = "sha256:d29850410ee68a3201bc10ec0b0008afd9c7ce70888355fdac14a52efba48baa", size = 491818, upload-time = "2025-06-24T18:53:09.208Z" }, + { url = "https://files.pythonhosted.org/packages/2a/f0/513b12ba87a62272adff2298f22d78185faf54265afc298f1d8c337e7a5f/rhoban_cmeel_jsoncpp-1.9.4.9-0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:41cc0235e0e1132f3332bf0b8ca49b6e01693b04f56110633976dbd9dc33fa57", size = 330615, upload-time = "2025-06-24T18:53:10.758Z" }, + { url = "https://files.pythonhosted.org/packages/4e/a0/79a987ed722365b61051da30484cc7e8c8837b0e4693149b96c0173287ab/rhoban_cmeel_jsoncpp-1.9.4.9-0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:e57547e0d34e18f989ee4966380deb48245d46dc0cc4da28ebaa7641e6615e2a", size = 362586, upload-time = "2025-06-24T18:53:11.854Z" }, + { url = "https://files.pythonhosted.org/packages/07/4b/035c9def4cca5e62b3053e8f728f500e0025e8e0a4c5c2fc70a9b5a065d0/rhoban_cmeel_jsoncpp-1.9.4.9-0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:bf644a2e17f88f509c28e58c0d318a94c3fbae1c09eecf447b1be98f97a42f44", size = 338462, upload-time = "2025-06-24T18:53:13.343Z" }, + { url = "https://files.pythonhosted.org/packages/c8/d7/12e5dcc03c73d13ba546f522481d944f44829758eb6554b4751f49d45b0b/rhoban_cmeel_jsoncpp-1.9.4.9-0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5ff4b6f7a26c89a2aed2242c6b834fc8abdfc099692b683e972f6fc4ed8ad83a", size = 397780, upload-time = "2025-06-24T18:53:14.836Z" }, + { url = "https://files.pythonhosted.org/packages/a4/ae/cf627de3c52a80c65163fd3e214c3f0326d0411f67eb64db6629bc71717b/rhoban_cmeel_jsoncpp-1.9.4.9-0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bc0bb9ff7d0090cf722c72ef4088967336b6dd304fc34db6787345edc16f3be4", size = 363910, upload-time = "2025-06-24T18:53:16.033Z" }, + { url = "https://files.pythonhosted.org/packages/87/63/51136e4fdcaedcbf326fc242753e2291a5e3aeabdc0c0d5a5bab0edc5378/rhoban_cmeel_jsoncpp-1.9.4.9-0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1b3a0fd46c2e2e6fc6912dcae7cb987cd0a61d9caee4f3ca1cb62544eb8aa11a", size = 289728, upload-time = "2025-06-24T18:53:17.427Z" }, + { url = "https://files.pythonhosted.org/packages/77/ee/bc97fd2474f0f28449149211776ac63b9d76a82f7468277e09611e9253cf/rhoban_cmeel_jsoncpp-1.9.4.9-0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ffd64a71c7e74312180c4ff20c55f0ebc235c3d634dba18618063c60175cd06c", size = 281614, upload-time = "2025-06-24T18:53:19.575Z" }, + { url = "https://files.pythonhosted.org/packages/82/a6/5cb9320ffce6ee8f5a62f942aabb7253d03ed38f611d891114a5c1304b7a/rhoban_cmeel_jsoncpp-1.9.4.9-0-cp311-cp311-manylinux_2_17_i686.whl", hash = "sha256:6f4c09ca5ba465769e972f94bd8113927aed0a30932caefc7799dd6b6a5d83fb", size = 491819, upload-time = "2025-06-24T18:53:20.751Z" }, + { url = "https://files.pythonhosted.org/packages/5c/ef/df1cba0fdbe278b45e2eaf617b4aedb29b7670e04795b1dafa5339ad67e9/rhoban_cmeel_jsoncpp-1.9.4.9-0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:cfa5a8f18c3fe4a973ff1e49fe5f1048a81b2f2cc1d22df0770f3d23a91d455c", size = 330615, upload-time = "2025-06-24T18:53:22.243Z" }, + { url = "https://files.pythonhosted.org/packages/54/ca/fca12118933539461dc66cd70fd2dfc38f0dff99075e544ff9afb3924479/rhoban_cmeel_jsoncpp-1.9.4.9-0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:db51d1b31d0d0fe04e5d7fc123a849154689cadc5e43a289c20c8ecf243c5fa5", size = 362584, upload-time = "2025-06-24T18:53:23.831Z" }, + { url = "https://files.pythonhosted.org/packages/24/2f/9829e23758b6df6ade5da3f7a2348adce273c271b386573e15cce201ba88/rhoban_cmeel_jsoncpp-1.9.4.9-0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b885cc44fd59e2070b77f5463324683b3d99035307dc0c9635cdbe4fc1759741", size = 338463, upload-time = "2025-06-24T18:53:24.986Z" }, + { url = "https://files.pythonhosted.org/packages/e7/dd/a5adb0bea0bc8ca19b3631e2d4a66e8b2a66b4497aa24f176e8549b3c4c2/rhoban_cmeel_jsoncpp-1.9.4.9-0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:23d94e38b1266ad987ed42369841839d7299b6e5745890f39882236485b66e08", size = 397781, upload-time = "2025-06-24T18:53:26.633Z" }, + { url = "https://files.pythonhosted.org/packages/37/7f/8f9d90373411e297db923bf5c42c596fc4bc6f4955561aec508fa375b299/rhoban_cmeel_jsoncpp-1.9.4.9-0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b67cd1ff1d3801154e0ca6518f77dae6d75297f83de8246abb20cd8f0813d6bd", size = 363909, upload-time = "2025-06-24T18:53:27.796Z" }, + { url = "https://files.pythonhosted.org/packages/34/c7/70335d8227dbfe222a2362eece6a37ed6526438636d445fa53cee897625b/rhoban_cmeel_jsoncpp-1.9.4.9-0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f0f62d47631ed02be9a83669b24ed2fb870c27e39311b5ff4842473aa61e3ca8", size = 290015, upload-time = "2025-06-24T18:53:28.982Z" }, + { url = "https://files.pythonhosted.org/packages/55/8f/c07df2a93cb2184f2b3cd4edf7ca9c4467d17466cae5dc84166de5365963/rhoban_cmeel_jsoncpp-1.9.4.9-0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78d34f04880cc0ff799832c3f1e2c34f7179c54ac7c428339f35c87e3bb5739b", size = 281613, upload-time = "2025-06-24T18:53:30.392Z" }, + { url = "https://files.pythonhosted.org/packages/ef/04/a7b6df62549654b940dd4737384d2bd15afc570595a1a98fbdcf8aa5fcbf/rhoban_cmeel_jsoncpp-1.9.4.9-0-cp312-cp312-manylinux_2_17_i686.whl", hash = "sha256:3841bb7fed9ca370631bafc44c493b1b4044048e857e1265b24ded9cd6bef385", size = 491819, upload-time = "2025-06-24T18:53:31.59Z" }, + { url = "https://files.pythonhosted.org/packages/e6/7a/ec81041d3bcd1c99ae4ef14505d81cbe8a424d1bcee752bd1f32b6391d24/rhoban_cmeel_jsoncpp-1.9.4.9-0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:a68789f5226bb8df92383140f3f45b94afe07b115fcc3b34a0e1da806d7d1f66", size = 330613, upload-time = "2025-06-24T18:53:32.985Z" }, + { url = "https://files.pythonhosted.org/packages/09/5a/96550200a9aefea3e14f1eaea0d62a649968883bb1c927509666d2ad680c/rhoban_cmeel_jsoncpp-1.9.4.9-0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:a3aa7cc485acf11e4876f0ac20ee5ffcc0c89c9dd7b8eee4d5e078392421e2fa", size = 362585, upload-time = "2025-06-24T18:53:34.14Z" }, + { url = "https://files.pythonhosted.org/packages/bd/e9/1c32193f9a2e0b397417b20877f0c3fd2b3b88a20b85b4f8925ef5e50dfe/rhoban_cmeel_jsoncpp-1.9.4.9-0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:fbcda72ec3aa1935b355df5dbde55f885b3a7aadc7d9b8d1ff1cd274247c9a15", size = 338462, upload-time = "2025-06-24T18:53:35.297Z" }, + { url = "https://files.pythonhosted.org/packages/72/6b/bb9e6e6dee18a32f47213c42f03291901d6214d3a352c7104304e06d684e/rhoban_cmeel_jsoncpp-1.9.4.9-0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:2472911f555b8f39caaddf23601a08befdf218e08c521049b0cbd705c1693772", size = 397779, upload-time = "2025-06-24T18:53:36.413Z" }, + { url = "https://files.pythonhosted.org/packages/c2/ca/5aa50bd53bad9c079e2ceabe17231803c420fcc549894e77c7266f1b3853/rhoban_cmeel_jsoncpp-1.9.4.9-0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8cb67950a3d3b4a6ec23fcce2e7f844c2ddd1c626ff9f4c7991c699d66213be5", size = 363910, upload-time = "2025-06-24T18:53:37.595Z" }, + { url = "https://files.pythonhosted.org/packages/8a/17/43932dd90f1febb6cbc6c8d17df5b0b9f0bd3b21660c4fb3d846770964ee/rhoban_cmeel_jsoncpp-1.9.4.9-0-cp313-cp313-macosx_10_9_x86_64.whl", hash = "sha256:096d147389647ee103066e91b799ec230842ebd2916e0a59069bf598f06020a5", size = 290015, upload-time = "2025-06-24T18:53:39.01Z" }, + { url = "https://files.pythonhosted.org/packages/78/a1/3bb178bdb062bab896aaa5bae91e43a83cf986a155dee1479a27a289fad3/rhoban_cmeel_jsoncpp-1.9.4.9-0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a3f54fe0ea0aaa91c46dba6939a822f08f8f5a2611e17074e9b4cf0273f8ca03", size = 281613, upload-time = "2025-06-24T18:53:40.14Z" }, + { url = "https://files.pythonhosted.org/packages/4c/3e/f8cc82bf48d1c638be0c17a70c6b8a2d84f55e6c84adae88074d30e2d3a8/rhoban_cmeel_jsoncpp-1.9.4.9-0-cp313-cp313-manylinux_2_17_i686.whl", hash = "sha256:f491c23be755f33d04fcd120b2555084bb2b5f8ce6610853f55d0b7ef73d9454", size = 491818, upload-time = "2025-06-24T18:53:41.584Z" }, + { url = "https://files.pythonhosted.org/packages/71/7d/b28c58560b757a1d2e821ddfab9a737f760573dc331d604d2f1a50ad0c59/rhoban_cmeel_jsoncpp-1.9.4.9-0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:5cfa7582e21289214dc6e0eb17e821a463429f4eb9bbe0b1d73c8b6b5070e822", size = 330615, upload-time = "2025-06-24T18:53:42.797Z" }, + { url = "https://files.pythonhosted.org/packages/dc/45/c0a97f844b416ac4bb0701cb5bedcdd714236c60a968f1a17a929acf2030/rhoban_cmeel_jsoncpp-1.9.4.9-0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:0e736f7c65279767558477c73b7ee07d7900878b6489362af90b75bf58ef98e5", size = 362585, upload-time = "2025-06-24T18:53:43.976Z" }, + { url = "https://files.pythonhosted.org/packages/82/d1/e68942c2136ba7f16150d7174376df83f7b5034391605839a280c94b6b93/rhoban_cmeel_jsoncpp-1.9.4.9-0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d2d289decd44461a22561353d26a4ebd94a35d7967de43a094cdf1d68afd47ce", size = 338462, upload-time = "2025-06-24T18:53:45.242Z" }, + { url = "https://files.pythonhosted.org/packages/ba/5b/97acff8d776ef2fca5d4e21893624dbb424095f1a4b66c3c0d1375854341/rhoban_cmeel_jsoncpp-1.9.4.9-0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fe1165ea4390a4cfce698f1018b3305e27abf4f9db7f464116f7252ae27d16ae", size = 397779, upload-time = "2025-06-24T18:53:46.76Z" }, + { url = "https://files.pythonhosted.org/packages/11/7c/4f2cd6df47c6b83652e8f9ffafb25f056a1b01fa0050df4b4abf5f41dc12/rhoban_cmeel_jsoncpp-1.9.4.9-0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f49041be98e0104856f8a0874936f50be435c514a8cd20ed5291b82d81912099", size = 363908, upload-time = "2025-06-24T18:53:48.05Z" }, +] + +[[package]] +name = "rich" +version = "14.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fb/d2/8920e102050a0de7bfabeb4c4614a49248cf8d5d7a8d01885fbb24dc767a/rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4", size = 219990, upload-time = "2025-10-09T14:16:53.064Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd", size = 243393, upload-time = "2025-10-09T14:16:51.245Z" }, +] + +[[package]] +name = "ruff" +version = "0.12.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/90/5255432602c0b196a0da6720f6f76b93eb50baef46d3c9b0025e2f9acbf3/ruff-0.12.0.tar.gz", hash = "sha256:4d047db3662418d4a848a3fdbfaf17488b34b62f527ed6f10cb8afd78135bc5c", size = 4376101, upload-time = "2025-06-17T15:19:26.217Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/fd/b46bb20e14b11ff49dbc74c61de352e0dc07fb650189513631f6fb5fc69f/ruff-0.12.0-py3-none-linux_armv6l.whl", hash = "sha256:5652a9ecdb308a1754d96a68827755f28d5dfb416b06f60fd9e13f26191a8848", size = 10311554, upload-time = "2025-06-17T15:18:45.792Z" }, + { url = "https://files.pythonhosted.org/packages/e7/d3/021dde5a988fa3e25d2468d1dadeea0ae89dc4bc67d0140c6e68818a12a1/ruff-0.12.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:05ed0c914fabc602fc1f3b42c53aa219e5736cb030cdd85640c32dbc73da74a6", size = 11118435, upload-time = "2025-06-17T15:18:49.064Z" }, + { url = "https://files.pythonhosted.org/packages/07/a2/01a5acf495265c667686ec418f19fd5c32bcc326d4c79ac28824aecd6a32/ruff-0.12.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:07a7aa9b69ac3fcfda3c507916d5d1bca10821fe3797d46bad10f2c6de1edda0", size = 10466010, upload-time = "2025-06-17T15:18:51.341Z" }, + { url = "https://files.pythonhosted.org/packages/4c/57/7caf31dd947d72e7aa06c60ecb19c135cad871a0a8a251723088132ce801/ruff-0.12.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e7731c3eec50af71597243bace7ec6104616ca56dda2b99c89935fe926bdcd48", size = 10661366, upload-time = "2025-06-17T15:18:53.29Z" }, + { url = "https://files.pythonhosted.org/packages/e9/ba/aa393b972a782b4bc9ea121e0e358a18981980856190d7d2b6187f63e03a/ruff-0.12.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:952d0630eae628250ab1c70a7fffb641b03e6b4a2d3f3ec6c1d19b4ab6c6c807", size = 10173492, upload-time = "2025-06-17T15:18:55.262Z" }, + { url = "https://files.pythonhosted.org/packages/d7/50/9349ee777614bc3062fc6b038503a59b2034d09dd259daf8192f56c06720/ruff-0.12.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c021f04ea06966b02614d442e94071781c424ab8e02ec7af2f037b4c1e01cc82", size = 11761739, upload-time = "2025-06-17T15:18:58.906Z" }, + { url = "https://files.pythonhosted.org/packages/04/8f/ad459de67c70ec112e2ba7206841c8f4eb340a03ee6a5cabc159fe558b8e/ruff-0.12.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:7d235618283718ee2fe14db07f954f9b2423700919dc688eacf3f8797a11315c", size = 12537098, upload-time = "2025-06-17T15:19:01.316Z" }, + { url = "https://files.pythonhosted.org/packages/ed/50/15ad9c80ebd3c4819f5bd8883e57329f538704ed57bac680d95cb6627527/ruff-0.12.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0c0758038f81beec8cc52ca22de9685b8ae7f7cc18c013ec2050012862cc9165", size = 12154122, upload-time = "2025-06-17T15:19:03.727Z" }, + { url = "https://files.pythonhosted.org/packages/76/e6/79b91e41bc8cc3e78ee95c87093c6cacfa275c786e53c9b11b9358026b3d/ruff-0.12.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:139b3d28027987b78fc8d6cfb61165447bdf3740e650b7c480744873688808c2", size = 11363374, upload-time = "2025-06-17T15:19:05.875Z" }, + { url = "https://files.pythonhosted.org/packages/db/c3/82b292ff8a561850934549aa9dc39e2c4e783ab3c21debe55a495ddf7827/ruff-0.12.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68853e8517b17bba004152aebd9dd77d5213e503a5f2789395b25f26acac0da4", size = 11587647, upload-time = "2025-06-17T15:19:08.246Z" }, + { url = "https://files.pythonhosted.org/packages/2b/42/d5760d742669f285909de1bbf50289baccb647b53e99b8a3b4f7ce1b2001/ruff-0.12.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3a9512af224b9ac4757f7010843771da6b2b0935a9e5e76bb407caa901a1a514", size = 10527284, upload-time = "2025-06-17T15:19:10.37Z" }, + { url = "https://files.pythonhosted.org/packages/19/f6/fcee9935f25a8a8bba4adbae62495c39ef281256693962c2159e8b284c5f/ruff-0.12.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b08df3d96db798e5beb488d4df03011874aff919a97dcc2dd8539bb2be5d6a88", size = 10158609, upload-time = "2025-06-17T15:19:12.286Z" }, + { url = "https://files.pythonhosted.org/packages/37/fb/057febf0eea07b9384787bfe197e8b3384aa05faa0d6bd844b94ceb29945/ruff-0.12.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6a315992297a7435a66259073681bb0d8647a826b7a6de45c6934b2ca3a9ed51", size = 11141462, upload-time = "2025-06-17T15:19:15.195Z" }, + { url = "https://files.pythonhosted.org/packages/10/7c/1be8571011585914b9d23c95b15d07eec2d2303e94a03df58294bc9274d4/ruff-0.12.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1e55e44e770e061f55a7dbc6e9aed47feea07731d809a3710feda2262d2d4d8a", size = 11641616, upload-time = "2025-06-17T15:19:17.6Z" }, + { url = "https://files.pythonhosted.org/packages/6a/ef/b960ab4818f90ff59e571d03c3f992828d4683561095e80f9ef31f3d58b7/ruff-0.12.0-py3-none-win32.whl", hash = "sha256:7162a4c816f8d1555eb195c46ae0bd819834d2a3f18f98cc63819a7b46f474fb", size = 10525289, upload-time = "2025-06-17T15:19:19.688Z" }, + { url = "https://files.pythonhosted.org/packages/34/93/8b16034d493ef958a500f17cda3496c63a537ce9d5a6479feec9558f1695/ruff-0.12.0-py3-none-win_amd64.whl", hash = "sha256:d00b7a157b8fb6d3827b49d3324da34a1e3f93492c1f97b08e222ad7e9b291e0", size = 11598311, upload-time = "2025-06-17T15:19:21.785Z" }, + { url = "https://files.pythonhosted.org/packages/d0/33/4d3e79e4a84533d6cd526bfb42c020a23256ae5e4265d858bd1287831f7d/ruff-0.12.0-py3-none-win_arm64.whl", hash = "sha256:8cd24580405ad8c1cc64d61725bca091d6b6da7eb3d36f72cc605467069d7e8b", size = 10724946, upload-time = "2025-06-17T15:19:23.952Z" }, +] + +[[package]] +name = "rustypot" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/41/ad/22e84d4b3099e3fc109fd65e3af9629987974a227fa566d229c32a0bf654/rustypot-1.4.0.tar.gz", hash = "sha256:5e8607f0cff9f2953f59c567e34cc089f4fd9ff88ebbb41d4b329dd9dffcf0f8", size = 60097, upload-time = "2025-11-19T11:53:43.027Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cd/ab/5e24e49b1472a7ff97a16a3a7606ea29f2feeef018fc404715e510156e2e/rustypot-1.4.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:6b99e6e89028116e9923375c2aaab1b8da8cd5c1dc2dcc682a893765517c7f0d", size = 1657408, upload-time = "2025-11-19T11:53:17.917Z" }, + { url = "https://files.pythonhosted.org/packages/15/af/9208b1c1e350b1ecd986e6782d64b9f8f770e3da65f3707952501db3b56a/rustypot-1.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:91dbf14f0f0e31969df43185bd18cfd6bc7341e6fad815d000175fd5cb1bd7ad", size = 1530195, upload-time = "2025-11-19T11:53:12.142Z" }, + { url = "https://files.pythonhosted.org/packages/02/db/c511c59b0c2428eaa59f2cf3640c677dad83eee5e8f4caf876ebdeb3db09/rustypot-1.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfb9bfce1adea617eb948d5cf410703070ab7aac3b454014e172acbd4c4c1b81", size = 1673673, upload-time = "2025-11-19T11:52:49.553Z" }, + { url = "https://files.pythonhosted.org/packages/82/ac/a7188231bd853be75388eba1f2b6a24922942c9f7a0de0a962e752faa8ca/rustypot-1.4.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dd9896ce88eebbed3f9cd8d98774505cafd23b399256a3aeb3844b2ef1ced49f", size = 1726837, upload-time = "2025-11-19T11:52:54.942Z" }, + { url = "https://files.pythonhosted.org/packages/e6/de/97f330269bcaaccd81b1e1eef2c9410d9e13c16de97450c6b1632400b474/rustypot-1.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8c7f4709fc8f9338199ce0560de230a644f5e7c161f8ac3c1c24530d5abbcc8", size = 1752455, upload-time = "2025-11-19T11:53:06.014Z" }, + { url = "https://files.pythonhosted.org/packages/81/08/480833485a2d960cf998fda6567b919258e08346309c1afca44d407cad97/rustypot-1.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6ecaaf5b555b11db3132c803b79e93ea7db9caa5d08dc6bd67c654f29eb5bb46", size = 1737355, upload-time = "2025-11-19T11:53:00.727Z" }, + { url = "https://files.pythonhosted.org/packages/bf/50/e428c68fedf5c5ac78aebe321ed8ec84e247ae044b3306b36f8c6a74ff8f/rustypot-1.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8f04aaa90dfc2b6c535e8b0897b844200dfc8d267034006a5d0f8bfc8329bb67", size = 1858991, upload-time = "2025-11-19T11:53:23.014Z" }, + { url = "https://files.pythonhosted.org/packages/f4/06/1fd6106dfb9eb1f8d0bdf5ef1ccb4d7e38db8d41fe0984201d67dde7e018/rustypot-1.4.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:f2800cd8b732ae43eb8936d3462c489c2efff43ded92da3612828debbcbd5515", size = 1996042, upload-time = "2025-11-19T11:53:28.339Z" }, + { url = "https://files.pythonhosted.org/packages/43/f3/d40eb22fbab02986bd5d8a6f2c86bae20425577fdb19b3ebbc0cdea62ae4/rustypot-1.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:80626bbeaf05ea3edd112dad541eda2f1771a252c455d381956fa77c95c1e6c0", size = 1823848, upload-time = "2025-11-19T11:53:33.327Z" }, + { url = "https://files.pythonhosted.org/packages/98/7e/620c6b4ef86a83fe42d3bc3d18265ca6c53a3f644e155653ad18df93e215/rustypot-1.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8766222a6ac4df8f8afedcc6c2cf493cc71bf245e4b593e24fad0a61cf83e3dc", size = 1944672, upload-time = "2025-11-19T11:53:38.067Z" }, + { url = "https://files.pythonhosted.org/packages/89/91/2c399e66eb3507a9b1ad8ec2be9695bfda7fb9a7305f787113cf95166770/rustypot-1.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:a162451a69766702a5a36c8055a822d5b7af99420ee131162d93c5926e78d7e7", size = 1671429, upload-time = "2025-11-19T11:53:43.789Z" }, + { url = "https://files.pythonhosted.org/packages/c6/4d/28a3f898567587b1ae6c40d26ec6e9d52d823ad09a3a1dceea71cc2840d2/rustypot-1.4.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:5eb56ef3f4c93c0564d4387e51dabf024c392597bfc101b7d9bfbfa282b936a3", size = 1657433, upload-time = "2025-11-19T11:53:19.191Z" }, + { url = "https://files.pythonhosted.org/packages/15/b8/c27afb0203b0d24930dcb673aacbf1db187d3d5d961401bf5596d95164dd/rustypot-1.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e84c3c06bc25511fec1c475985a6038ad5e1c77574ced3cdcd3b18bec59f3f0c", size = 1530013, upload-time = "2025-11-19T11:53:13.252Z" }, + { url = "https://files.pythonhosted.org/packages/7b/eb/60e6a2b63076a86f1c5fe09edc996439dedf4027bd900290019ec506a61b/rustypot-1.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:852309d8c8af86eafc1c4afd065c219f0ff0f0db19d486a7cb30cdae3bcc1e9d", size = 1673985, upload-time = "2025-11-19T11:52:50.843Z" }, + { url = "https://files.pythonhosted.org/packages/01/0f/61d7f782b9473864eaf46d608f06e3425acbb72d31252a281baec34349a3/rustypot-1.4.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed87d23cb5b0bdb3f12539de9e71f7ba55d6ffb42a69d99082348d357381dc66", size = 1727323, upload-time = "2025-11-19T11:52:56.581Z" }, + { url = "https://files.pythonhosted.org/packages/11/f3/042cb6871a464c251e403f85e31af5a387b68f290c6f7686891af955aaec/rustypot-1.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:042ad6d0e72db7036fc472c0de9be9ee6b035cee234ebcf9b10d75a30618c1c9", size = 1752098, upload-time = "2025-11-19T11:53:07.118Z" }, + { url = "https://files.pythonhosted.org/packages/1f/d9/47c8f992e0a1d7d30de17459724a4c736751b9c53c69b8c04e2942cd7081/rustypot-1.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:26e243a81cedbbe51abf461ca5ec2b9641730340b03ad52f7fd638d922ea300d", size = 1737415, upload-time = "2025-11-19T11:53:01.906Z" }, + { url = "https://files.pythonhosted.org/packages/2a/67/a1745fee19e4e025d51b5ee0eac13a20bf9984792e8dcdc0173bd3a99348/rustypot-1.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:76e9fd0376815c227c915372e34a61366365faf3472f849e3d31de690eef9532", size = 1859533, upload-time = "2025-11-19T11:53:24.437Z" }, + { url = "https://files.pythonhosted.org/packages/b7/8f/2e5db416b6b9428d99888baa2f045faf3fa0662e6d1ea10c7b3b3871f199/rustypot-1.4.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:ecabfa2b34e9821ded4a686df16092530b233ddf560e5920ca11e4ffb3a9c59a", size = 1997475, upload-time = "2025-11-19T11:53:29.845Z" }, + { url = "https://files.pythonhosted.org/packages/e4/69/50de9222c02377ad7e2b0353034dfb04efa644bd220b402a6ea72da78a2c/rustypot-1.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6259a0e202f9d543d329fad83c3f2d9439061f17c9d7f08a5b02cc6123b807a9", size = 1822346, upload-time = "2025-11-19T11:53:34.654Z" }, + { url = "https://files.pythonhosted.org/packages/11/df/da8a96db6d02a35c94c159eb2ff3389d9de5e2ee2beda04df4dce384a2ed/rustypot-1.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:883a95f573ad23a630591428cd8b4228c5bd62884323a079bf6517c588812f90", size = 1943995, upload-time = "2025-11-19T11:53:39.288Z" }, + { url = "https://files.pythonhosted.org/packages/c1/61/c108d25e61cdcb2f6aea984cbbf359952d935ae99115c7746e8dc86371f2/rustypot-1.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:09858b97b004337b8370c0a41b7e08a74c92e1d7530c9bd9106cf9037c090d69", size = 1671721, upload-time = "2025-11-19T11:53:44.918Z" }, + { url = "https://files.pythonhosted.org/packages/47/db/0a1ed93069a7ab86d28fc4c47cb9e7a995211ac5d5d77ec1b5d68ae0e64c/rustypot-1.4.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:c3c133ca8d9a7f0453532bec8d0f9c6f20e64f71d1141a25d9bc31e62b7251b6", size = 1651633, upload-time = "2025-11-19T11:53:20.422Z" }, + { url = "https://files.pythonhosted.org/packages/b9/6e/32fd0e56dbf6dcb29a2d2da8b4692a15beb63557a655e3e22f367cf19db6/rustypot-1.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12942dc5694d2511e12e5d59ea50a41da0bfab29d4cdd6f4718b05874d132145", size = 1517305, upload-time = "2025-11-19T11:53:14.311Z" }, + { url = "https://files.pythonhosted.org/packages/aa/67/4616ac267267ed9b2bbd82bfdce9e7edadeef5d09f7c63df737b870479d5/rustypot-1.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c9962e22adabe744e54b3699cd64099320a74b7ca7b1ed0be425b3b379636e3", size = 1643720, upload-time = "2025-11-19T11:52:52.202Z" }, + { url = "https://files.pythonhosted.org/packages/17/95/9d77beeaf20c2bd13107d84249be010f951e5041e8045fbdac4f80e3d80b/rustypot-1.4.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:67a5d8c04e19022dbfbf6b38a886420b1928d7024d97767bfe5574b7f1d13e74", size = 1694743, upload-time = "2025-11-19T11:52:57.873Z" }, + { url = "https://files.pythonhosted.org/packages/8a/0d/2ce9f220ec672a847fd76e08831d9d17c57a59ba5aa1940951d96dfd61d8/rustypot-1.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c4cbbc072df203697ac5f2346be94a149a792e0ca052b6392504b27cfe2d447", size = 1745176, upload-time = "2025-11-19T11:53:08.476Z" }, + { url = "https://files.pythonhosted.org/packages/3a/33/3fe3be1f95998113926aa38532adeb9964a02511a0071d1680850cfaf74b/rustypot-1.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d73802eec8dc8d47555f05fa7165b87c33e9d3997c841213759cbe72bffa145e", size = 1699759, upload-time = "2025-11-19T11:53:03.064Z" }, + { url = "https://files.pythonhosted.org/packages/22/ba/ef2428a865b5350c649c67573519e7052756286913cd7d5b28a762e063e0/rustypot-1.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d8bf4d6b87add0b275e993748c825751c0d7fcd77b8081e2ff344037bd13f521", size = 1829416, upload-time = "2025-11-19T11:53:25.8Z" }, + { url = "https://files.pythonhosted.org/packages/71/67/b6dae95ec2d169bb8afa489418fe64623fe35b48f57fba6ba7649bdfaaa1/rustypot-1.4.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:4b068f9229a329dbe327feb982f683209900739e2811d60e41a6999eb46fa627", size = 1963364, upload-time = "2025-11-19T11:53:30.972Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a2/946cde0c5d31f7ce7b7c79f79a1785f194b2922d2b4a67c53e6e1b8099e9/rustypot-1.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:519a3ba8bd2d9291436ef2259869ef2c918a9410ac063e33e76178d52e0230e3", size = 1815541, upload-time = "2025-11-19T11:53:35.773Z" }, + { url = "https://files.pythonhosted.org/packages/13/7f/acebd54c52c7ecd0f4d2caaee0b4ea57047443d16cb64c2d9e9e0b359832/rustypot-1.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ba4d42d5dc32e00dbae1eb592eb0a94d8cc8551c1b2285be774e4d9cdfb064d9", size = 1936044, upload-time = "2025-11-19T11:53:40.782Z" }, + { url = "https://files.pythonhosted.org/packages/b8/3e/877cb56347550409a9f33e92e224b7527684d89b4159a96d1e9f3065fe28/rustypot-1.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:acdc44bf55ece4d7205c5f8d68faaea1c7e16f66631d4533929e7c027c7d9583", size = 1671794, upload-time = "2025-11-19T11:53:46.089Z" }, + { url = "https://files.pythonhosted.org/packages/c9/cc/5592dcc870a6ac0c8ff3173c84985da9ebfe039a6c704387cf84247e3c6e/rustypot-1.4.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:6a34cee68caac4dda00ad23dba2fb923dfe47ad8b82816bcd68fd742508dc772", size = 1650940, upload-time = "2025-11-19T11:53:21.857Z" }, + { url = "https://files.pythonhosted.org/packages/96/8f/25b99ff877c82679c9bb6d54c2dd74d72ccf731565022f67ecb6a8fc6e93/rustypot-1.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c05c4500037d6c981324cfb560f3f0b2f9066ac725912de7dbbc22652a8b1a89", size = 1516899, upload-time = "2025-11-19T11:53:15.493Z" }, + { url = "https://files.pythonhosted.org/packages/5f/60/4cc071a6b81c0b7470234fd31dae86aa3a98326ade39ff8e1200a563d72e/rustypot-1.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efe08c3535ef28a65c1fd3fa2f4074dd568b080b468d84c7cfdd6ac47bf3568a", size = 1644159, upload-time = "2025-11-19T11:52:53.521Z" }, + { url = "https://files.pythonhosted.org/packages/a1/6d/ea3778c7673559316361d1c021f6e774e3df449e928416bbe434721cdfa9/rustypot-1.4.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9e222c615a4e859363a9d75f25e9394fe8e3d9634465e7244861cf9dd9568c06", size = 1694143, upload-time = "2025-11-19T11:52:59.329Z" }, + { url = "https://files.pythonhosted.org/packages/e6/f1/cc231247f54d38f8ee0c3c41f768fc2fa1a9c6a782f4c3928ea775af759c/rustypot-1.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:377017dc152f75cf9a73ae6cbe4dae501051bcd2269d345b3f52d526af3388e3", size = 1744386, upload-time = "2025-11-19T11:53:09.629Z" }, + { url = "https://files.pythonhosted.org/packages/ea/c8/c137ba5b77413165d5a0e27b1937b10971e200984c0604b9ce751911f14e/rustypot-1.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5c4a89781b13805d09f03a51665bb41fe6d41a3da466bc71fce7688f5865c7ce", size = 1701060, upload-time = "2025-11-19T11:53:04.548Z" }, + { url = "https://files.pythonhosted.org/packages/9c/e5/a09da32532b5b2416e5faf1708d4a09f25682b984826f8e6b09310ed6d56/rustypot-1.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:624e909936934412ac068601a251e44749ab2acc02fac09eefa75d8b9d88cfb3", size = 1829242, upload-time = "2025-11-19T11:53:27.156Z" }, + { url = "https://files.pythonhosted.org/packages/fe/f9/69214ea8d15755c2a1a7a2a5575b7f675944b09a03abb04e5a063023b7a0/rustypot-1.4.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:3a4fbc587099a96806f30cd15cc3ad20fdfca698083253f29724a516b068fd65", size = 1963269, upload-time = "2025-11-19T11:53:32.193Z" }, + { url = "https://files.pythonhosted.org/packages/dc/c8/da7ba34a1ff0c7b7a98dd281f2900f7c1a9d6e8ffc2935b10d7e74d7cf0d/rustypot-1.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bfc0c8026fa00d00307cbad2e106ba36abb6022884365db0400147786b0c9f07", size = 1816497, upload-time = "2025-11-19T11:53:36.956Z" }, + { url = "https://files.pythonhosted.org/packages/7d/8f/07d9e529cdb9f8c7734f76af1f17f907da770025c7b2b904286cde6089ee/rustypot-1.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c453fb38f4435918c762513a91d89a586a415f6e420f79ab45016b643f5b4db4", size = 1935302, upload-time = "2025-11-19T11:53:41.917Z" }, + { url = "https://files.pythonhosted.org/packages/35/8b/e7c248f46bcd3d9f5d974253b9c1e967ae010897da5af72a831535ae41b5/rustypot-1.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:52a5a208030f956fa079ad4405d24c2215ed6afd1a8bcc6e2a1ee95fafee67b7", size = 1671632, upload-time = "2025-11-19T11:53:47.224Z" }, + { url = "https://files.pythonhosted.org/packages/8d/45/36a3ccfa501d568afe4ef9abe5af728522edc352dc088a4351df1b85cfba/rustypot-1.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:936760476f2576968fa0f2411aee16963306e9a284cec98c24138b760ef0110f", size = 1524411, upload-time = "2025-11-19T11:53:16.836Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c8/fe09616e5632afc1ea261c6ec53ed6e30b1e478112c8891274c3349f523d/rustypot-1.4.0-cp314-cp314-win32.whl", hash = "sha256:36088211b186d438a40ad3013cac3f196e24ec4291904675f4f119738ee0850b", size = 1243384, upload-time = "2025-11-19T11:53:50.65Z" }, + { url = "https://files.pythonhosted.org/packages/6a/3e/3ef1357731787c2eeb1e9c82f2d87e51daa055e24cedf55079c8c1188932/rustypot-1.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:e0befc88f34336d010bbf2ef75d0245a3537d007dfb2f492f6922dc972246681", size = 1680240, upload-time = "2025-11-19T11:53:49.137Z" }, +] + +[[package]] +name = "scipy" +version = "1.15.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0f/37/6964b830433e654ec7485e45a00fc9a27cf868d622838f6b6d9c5ec0d532/scipy-1.15.3.tar.gz", hash = "sha256:eae3cf522bc7df64b42cad3925c876e1b0b6c35c1337c93e12c0f366f55b0eaf", size = 59419214, upload-time = "2025-05-08T16:13:05.955Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/2f/4966032c5f8cc7e6a60f1b2e0ad686293b9474b65246b0c642e3ef3badd0/scipy-1.15.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:a345928c86d535060c9c2b25e71e87c39ab2f22fc96e9636bd74d1dbf9de448c", size = 38702770, upload-time = "2025-05-08T16:04:20.849Z" }, + { url = "https://files.pythonhosted.org/packages/a0/6e/0c3bf90fae0e910c274db43304ebe25a6b391327f3f10b5dcc638c090795/scipy-1.15.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:ad3432cb0f9ed87477a8d97f03b763fd1d57709f1bbde3c9369b1dff5503b253", size = 30094511, upload-time = "2025-05-08T16:04:27.103Z" }, + { url = "https://files.pythonhosted.org/packages/ea/b1/4deb37252311c1acff7f101f6453f0440794f51b6eacb1aad4459a134081/scipy-1.15.3-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:aef683a9ae6eb00728a542b796f52a5477b78252edede72b8327a886ab63293f", size = 22368151, upload-time = "2025-05-08T16:04:31.731Z" }, + { url = "https://files.pythonhosted.org/packages/38/7d/f457626e3cd3c29b3a49ca115a304cebb8cc6f31b04678f03b216899d3c6/scipy-1.15.3-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:1c832e1bd78dea67d5c16f786681b28dd695a8cb1fb90af2e27580d3d0967e92", size = 25121732, upload-time = "2025-05-08T16:04:36.596Z" }, + { url = "https://files.pythonhosted.org/packages/db/0a/92b1de4a7adc7a15dcf5bddc6e191f6f29ee663b30511ce20467ef9b82e4/scipy-1.15.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:263961f658ce2165bbd7b99fa5135195c3a12d9bef045345016b8b50c315cb82", size = 35547617, upload-time = "2025-05-08T16:04:43.546Z" }, + { url = "https://files.pythonhosted.org/packages/8e/6d/41991e503e51fc1134502694c5fa7a1671501a17ffa12716a4a9151af3df/scipy-1.15.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e2abc762b0811e09a0d3258abee2d98e0c703eee49464ce0069590846f31d40", size = 37662964, upload-time = "2025-05-08T16:04:49.431Z" }, + { url = "https://files.pythonhosted.org/packages/25/e1/3df8f83cb15f3500478c889be8fb18700813b95e9e087328230b98d547ff/scipy-1.15.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ed7284b21a7a0c8f1b6e5977ac05396c0d008b89e05498c8b7e8f4a1423bba0e", size = 37238749, upload-time = "2025-05-08T16:04:55.215Z" }, + { url = "https://files.pythonhosted.org/packages/93/3e/b3257cf446f2a3533ed7809757039016b74cd6f38271de91682aa844cfc5/scipy-1.15.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5380741e53df2c566f4d234b100a484b420af85deb39ea35a1cc1be84ff53a5c", size = 40022383, upload-time = "2025-05-08T16:05:01.914Z" }, + { url = "https://files.pythonhosted.org/packages/d1/84/55bc4881973d3f79b479a5a2e2df61c8c9a04fcb986a213ac9c02cfb659b/scipy-1.15.3-cp310-cp310-win_amd64.whl", hash = "sha256:9d61e97b186a57350f6d6fd72640f9e99d5a4a2b8fbf4b9ee9a841eab327dc13", size = 41259201, upload-time = "2025-05-08T16:05:08.166Z" }, + { url = "https://files.pythonhosted.org/packages/96/ab/5cc9f80f28f6a7dff646c5756e559823614a42b1939d86dd0ed550470210/scipy-1.15.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:993439ce220d25e3696d1b23b233dd010169b62f6456488567e830654ee37a6b", size = 38714255, upload-time = "2025-05-08T16:05:14.596Z" }, + { url = "https://files.pythonhosted.org/packages/4a/4a/66ba30abe5ad1a3ad15bfb0b59d22174012e8056ff448cb1644deccbfed2/scipy-1.15.3-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:34716e281f181a02341ddeaad584205bd2fd3c242063bd3423d61ac259ca7eba", size = 30111035, upload-time = "2025-05-08T16:05:20.152Z" }, + { url = "https://files.pythonhosted.org/packages/4b/fa/a7e5b95afd80d24313307f03624acc65801846fa75599034f8ceb9e2cbf6/scipy-1.15.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3b0334816afb8b91dab859281b1b9786934392aa3d527cd847e41bb6f45bee65", size = 22384499, upload-time = "2025-05-08T16:05:24.494Z" }, + { url = "https://files.pythonhosted.org/packages/17/99/f3aaddccf3588bb4aea70ba35328c204cadd89517a1612ecfda5b2dd9d7a/scipy-1.15.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:6db907c7368e3092e24919b5e31c76998b0ce1684d51a90943cb0ed1b4ffd6c1", size = 25152602, upload-time = "2025-05-08T16:05:29.313Z" }, + { url = "https://files.pythonhosted.org/packages/56/c5/1032cdb565f146109212153339f9cb8b993701e9fe56b1c97699eee12586/scipy-1.15.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:721d6b4ef5dc82ca8968c25b111e307083d7ca9091bc38163fb89243e85e3889", size = 35503415, upload-time = "2025-05-08T16:05:34.699Z" }, + { url = "https://files.pythonhosted.org/packages/bd/37/89f19c8c05505d0601ed5650156e50eb881ae3918786c8fd7262b4ee66d3/scipy-1.15.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39cb9c62e471b1bb3750066ecc3a3f3052b37751c7c3dfd0fd7e48900ed52982", size = 37652622, upload-time = "2025-05-08T16:05:40.762Z" }, + { url = "https://files.pythonhosted.org/packages/7e/31/be59513aa9695519b18e1851bb9e487de66f2d31f835201f1b42f5d4d475/scipy-1.15.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:795c46999bae845966368a3c013e0e00947932d68e235702b5c3f6ea799aa8c9", size = 37244796, upload-time = "2025-05-08T16:05:48.119Z" }, + { url = "https://files.pythonhosted.org/packages/10/c0/4f5f3eeccc235632aab79b27a74a9130c6c35df358129f7ac8b29f562ac7/scipy-1.15.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:18aaacb735ab38b38db42cb01f6b92a2d0d4b6aabefeb07f02849e47f8fb3594", size = 40047684, upload-time = "2025-05-08T16:05:54.22Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a7/0ddaf514ce8a8714f6ed243a2b391b41dbb65251affe21ee3077ec45ea9a/scipy-1.15.3-cp311-cp311-win_amd64.whl", hash = "sha256:ae48a786a28412d744c62fd7816a4118ef97e5be0bee968ce8f0a2fba7acf3bb", size = 41246504, upload-time = "2025-05-08T16:06:00.437Z" }, + { url = "https://files.pythonhosted.org/packages/37/4b/683aa044c4162e10ed7a7ea30527f2cbd92e6999c10a8ed8edb253836e9c/scipy-1.15.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6ac6310fdbfb7aa6612408bd2f07295bcbd3fda00d2d702178434751fe48e019", size = 38766735, upload-time = "2025-05-08T16:06:06.471Z" }, + { url = "https://files.pythonhosted.org/packages/7b/7e/f30be3d03de07f25dc0ec926d1681fed5c732d759ac8f51079708c79e680/scipy-1.15.3-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:185cd3d6d05ca4b44a8f1595af87f9c372bb6acf9c808e99aa3e9aa03bd98cf6", size = 30173284, upload-time = "2025-05-08T16:06:11.686Z" }, + { url = "https://files.pythonhosted.org/packages/07/9c/0ddb0d0abdabe0d181c1793db51f02cd59e4901da6f9f7848e1f96759f0d/scipy-1.15.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:05dc6abcd105e1a29f95eada46d4a3f251743cfd7d3ae8ddb4088047f24ea477", size = 22446958, upload-time = "2025-05-08T16:06:15.97Z" }, + { url = "https://files.pythonhosted.org/packages/af/43/0bce905a965f36c58ff80d8bea33f1f9351b05fad4beaad4eae34699b7a1/scipy-1.15.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:06efcba926324df1696931a57a176c80848ccd67ce6ad020c810736bfd58eb1c", size = 25242454, upload-time = "2025-05-08T16:06:20.394Z" }, + { url = "https://files.pythonhosted.org/packages/56/30/a6f08f84ee5b7b28b4c597aca4cbe545535c39fe911845a96414700b64ba/scipy-1.15.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05045d8b9bfd807ee1b9f38761993297b10b245f012b11b13b91ba8945f7e45", size = 35210199, upload-time = "2025-05-08T16:06:26.159Z" }, + { url = "https://files.pythonhosted.org/packages/0b/1f/03f52c282437a168ee2c7c14a1a0d0781a9a4a8962d84ac05c06b4c5b555/scipy-1.15.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:271e3713e645149ea5ea3e97b57fdab61ce61333f97cfae392c28ba786f9bb49", size = 37309455, upload-time = "2025-05-08T16:06:32.778Z" }, + { url = "https://files.pythonhosted.org/packages/89/b1/fbb53137f42c4bf630b1ffdfc2151a62d1d1b903b249f030d2b1c0280af8/scipy-1.15.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6cfd56fc1a8e53f6e89ba3a7a7251f7396412d655bca2aa5611c8ec9a6784a1e", size = 36885140, upload-time = "2025-05-08T16:06:39.249Z" }, + { url = "https://files.pythonhosted.org/packages/2e/2e/025e39e339f5090df1ff266d021892694dbb7e63568edcfe43f892fa381d/scipy-1.15.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0ff17c0bb1cb32952c09217d8d1eed9b53d1463e5f1dd6052c7857f83127d539", size = 39710549, upload-time = "2025-05-08T16:06:45.729Z" }, + { url = "https://files.pythonhosted.org/packages/e6/eb/3bf6ea8ab7f1503dca3a10df2e4b9c3f6b3316df07f6c0ded94b281c7101/scipy-1.15.3-cp312-cp312-win_amd64.whl", hash = "sha256:52092bc0472cfd17df49ff17e70624345efece4e1a12b23783a1ac59a1b728ed", size = 40966184, upload-time = "2025-05-08T16:06:52.623Z" }, + { url = "https://files.pythonhosted.org/packages/73/18/ec27848c9baae6e0d6573eda6e01a602e5649ee72c27c3a8aad673ebecfd/scipy-1.15.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c620736bcc334782e24d173c0fdbb7590a0a436d2fdf39310a8902505008759", size = 38728256, upload-time = "2025-05-08T16:06:58.696Z" }, + { url = "https://files.pythonhosted.org/packages/74/cd/1aef2184948728b4b6e21267d53b3339762c285a46a274ebb7863c9e4742/scipy-1.15.3-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:7e11270a000969409d37ed399585ee530b9ef6aa99d50c019de4cb01e8e54e62", size = 30109540, upload-time = "2025-05-08T16:07:04.209Z" }, + { url = "https://files.pythonhosted.org/packages/5b/d8/59e452c0a255ec352bd0a833537a3bc1bfb679944c4938ab375b0a6b3a3e/scipy-1.15.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8c9ed3ba2c8a2ce098163a9bdb26f891746d02136995df25227a20e71c396ebb", size = 22383115, upload-time = "2025-05-08T16:07:08.998Z" }, + { url = "https://files.pythonhosted.org/packages/08/f5/456f56bbbfccf696263b47095291040655e3cbaf05d063bdc7c7517f32ac/scipy-1.15.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:0bdd905264c0c9cfa74a4772cdb2070171790381a5c4d312c973382fc6eaf730", size = 25163884, upload-time = "2025-05-08T16:07:14.091Z" }, + { url = "https://files.pythonhosted.org/packages/a2/66/a9618b6a435a0f0c0b8a6d0a2efb32d4ec5a85f023c2b79d39512040355b/scipy-1.15.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79167bba085c31f38603e11a267d862957cbb3ce018d8b38f79ac043bc92d825", size = 35174018, upload-time = "2025-05-08T16:07:19.427Z" }, + { url = "https://files.pythonhosted.org/packages/b5/09/c5b6734a50ad4882432b6bb7c02baf757f5b2f256041da5df242e2d7e6b6/scipy-1.15.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9deabd6d547aee2c9a81dee6cc96c6d7e9a9b1953f74850c179f91fdc729cb7", size = 37269716, upload-time = "2025-05-08T16:07:25.712Z" }, + { url = "https://files.pythonhosted.org/packages/77/0a/eac00ff741f23bcabd352731ed9b8995a0a60ef57f5fd788d611d43d69a1/scipy-1.15.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dde4fc32993071ac0c7dd2d82569e544f0bdaff66269cb475e0f369adad13f11", size = 36872342, upload-time = "2025-05-08T16:07:31.468Z" }, + { url = "https://files.pythonhosted.org/packages/fe/54/4379be86dd74b6ad81551689107360d9a3e18f24d20767a2d5b9253a3f0a/scipy-1.15.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f77f853d584e72e874d87357ad70f44b437331507d1c311457bed8ed2b956126", size = 39670869, upload-time = "2025-05-08T16:07:38.002Z" }, + { url = "https://files.pythonhosted.org/packages/87/2e/892ad2862ba54f084ffe8cc4a22667eaf9c2bcec6d2bff1d15713c6c0703/scipy-1.15.3-cp313-cp313-win_amd64.whl", hash = "sha256:b90ab29d0c37ec9bf55424c064312930ca5f4bde15ee8619ee44e69319aab163", size = 40988851, upload-time = "2025-05-08T16:08:33.671Z" }, + { url = "https://files.pythonhosted.org/packages/1b/e9/7a879c137f7e55b30d75d90ce3eb468197646bc7b443ac036ae3fe109055/scipy-1.15.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3ac07623267feb3ae308487c260ac684b32ea35fd81e12845039952f558047b8", size = 38863011, upload-time = "2025-05-08T16:07:44.039Z" }, + { url = "https://files.pythonhosted.org/packages/51/d1/226a806bbd69f62ce5ef5f3ffadc35286e9fbc802f606a07eb83bf2359de/scipy-1.15.3-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:6487aa99c2a3d509a5227d9a5e889ff05830a06b2ce08ec30df6d79db5fcd5c5", size = 30266407, upload-time = "2025-05-08T16:07:49.891Z" }, + { url = "https://files.pythonhosted.org/packages/e5/9b/f32d1d6093ab9eeabbd839b0f7619c62e46cc4b7b6dbf05b6e615bbd4400/scipy-1.15.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:50f9e62461c95d933d5c5ef4a1f2ebf9a2b4e83b0db374cb3f1de104d935922e", size = 22540030, upload-time = "2025-05-08T16:07:54.121Z" }, + { url = "https://files.pythonhosted.org/packages/e7/29/c278f699b095c1a884f29fda126340fcc201461ee8bfea5c8bdb1c7c958b/scipy-1.15.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:14ed70039d182f411ffc74789a16df3835e05dc469b898233a245cdfd7f162cb", size = 25218709, upload-time = "2025-05-08T16:07:58.506Z" }, + { url = "https://files.pythonhosted.org/packages/24/18/9e5374b617aba742a990581373cd6b68a2945d65cc588482749ef2e64467/scipy-1.15.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a769105537aa07a69468a0eefcd121be52006db61cdd8cac8a0e68980bbb723", size = 34809045, upload-time = "2025-05-08T16:08:03.929Z" }, + { url = "https://files.pythonhosted.org/packages/e1/fe/9c4361e7ba2927074360856db6135ef4904d505e9b3afbbcb073c4008328/scipy-1.15.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9db984639887e3dffb3928d118145ffe40eff2fa40cb241a306ec57c219ebbbb", size = 36703062, upload-time = "2025-05-08T16:08:09.558Z" }, + { url = "https://files.pythonhosted.org/packages/b7/8e/038ccfe29d272b30086b25a4960f757f97122cb2ec42e62b460d02fe98e9/scipy-1.15.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:40e54d5c7e7ebf1aa596c374c49fa3135f04648a0caabcb66c52884b943f02b4", size = 36393132, upload-time = "2025-05-08T16:08:15.34Z" }, + { url = "https://files.pythonhosted.org/packages/10/7e/5c12285452970be5bdbe8352c619250b97ebf7917d7a9a9e96b8a8140f17/scipy-1.15.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5e721fed53187e71d0ccf382b6bf977644c533e506c4d33c3fb24de89f5c3ed5", size = 38979503, upload-time = "2025-05-08T16:08:21.513Z" }, + { url = "https://files.pythonhosted.org/packages/81/06/0a5e5349474e1cbc5757975b21bd4fad0e72ebf138c5592f191646154e06/scipy-1.15.3-cp313-cp313t-win_amd64.whl", hash = "sha256:76ad1fb5f8752eabf0fa02e4cc0336b4e8f021e2d5f061ed37d6d264db35e3ca", size = 40308097, upload-time = "2025-05-08T16:08:27.627Z" }, +] + +[[package]] +name = "semver" +version = "3.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/d1/d3159231aec234a59dd7d601e9dd9fe96f3afff15efd33c1070019b26132/semver-3.0.4.tar.gz", hash = "sha256:afc7d8c584a5ed0a11033af086e8af226a9c0b206f313e0301f8dd7b6b589602", size = 269730, upload-time = "2025-01-24T13:19:27.617Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/24/4d91e05817e92e3a61c8a21e08fd0f390f5301f1c448b137c57c4bc6e543/semver-3.0.4-py3-none-any.whl", hash = "sha256:9c824d87ba7f7ab4a1890799cec8596f15c1241cb473404ea1cb0c55e4b04746", size = 17912, upload-time = "2025-01-24T13:19:24.949Z" }, +] + +[[package]] +name = "setuptools" +version = "80.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "smbus3" +version = "0.5.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/75/39/4b7fe2b7cfb42a39e5bb4ef2970a3befc0febbf2b5b8ef85fac04ca6dcb3/smbus3-0.5.5.tar.gz", hash = "sha256:91eed38fb6b7d2f893fbfc3f37006aa41e6913fdda5b3a9a79238b353760f92e", size = 22100, upload-time = "2024-06-29T01:55:18.214Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/cf/a0cfae35dac3e3d0b1b4242c8ba184616a47550f8c727b0df654d4338e2d/smbus3-0.5.5-py3-none-any.whl", hash = "sha256:7f4cc169975543e67e4b7404168ce918e656f6f8daca5de2bd30527298058083", size = 13616, upload-time = "2024-06-29T01:55:16.434Z" }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + +[[package]] +name = "sounddevice" +version = "0.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/80/2d/b04ae180312b81dbb694504bee170eada5372242e186f6298139fd3a0513/sounddevice-0.5.1.tar.gz", hash = "sha256:09ca991daeda8ce4be9ac91e15a9a81c8f81efa6b695a348c9171ea0c16cb041", size = 52896, upload-time = "2024-10-12T09:40:12.24Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/d1/464b5fca3decdd0cfec8c47f7b4161a0b12972453201c1bf03811f367c5e/sounddevice-0.5.1-py3-none-any.whl", hash = "sha256:e2017f182888c3f3c280d9fbac92e5dbddac024a7e3442f6e6116bd79dab8a9c", size = 32276, upload-time = "2024-10-12T09:40:05.605Z" }, + { url = "https://files.pythonhosted.org/packages/6f/f6/6703fe7cf3d7b7279040c792aeec6334e7305956aba4a80f23e62c8fdc44/sounddevice-0.5.1-py3-none-macosx_10_6_x86_64.macosx_10_6_universal2.whl", hash = "sha256:d16cb23d92322526a86a9490c427bf8d49e273d9ccc0bd096feecd229cde6031", size = 107916, upload-time = "2024-10-12T09:40:07.436Z" }, + { url = "https://files.pythonhosted.org/packages/57/a5/78a5e71f5ec0faedc54f4053775d61407bfbd7d0c18228c7f3d4252fd276/sounddevice-0.5.1-py3-none-win32.whl", hash = "sha256:d84cc6231526e7a08e89beff229c37f762baefe5e0cc2747cbe8e3a565470055", size = 312494, upload-time = "2024-10-12T09:40:09.355Z" }, + { url = "https://files.pythonhosted.org/packages/af/9b/15217b04f3b36d30de55fef542389d722de63f1ad81f9c72d8afc98cb6ab/sounddevice-0.5.1-py3-none-win_amd64.whl", hash = "sha256:4313b63f2076552b23ac3e0abd3bcfc0c1c6a696fc356759a13bd113c9df90f1", size = 363634, upload-time = "2024-10-12T09:40:11.065Z" }, +] + +[[package]] +name = "soundfile" +version = "0.13.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi" }, + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e1/41/9b873a8c055582859b239be17902a85339bec6a30ad162f98c9b0288a2cc/soundfile-0.13.1.tar.gz", hash = "sha256:b2c68dab1e30297317080a5b43df57e302584c49e2942defdde0acccc53f0e5b", size = 46156, upload-time = "2025-01-25T09:17:04.831Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/28/e2a36573ccbcf3d57c00626a21fe51989380636e821b341d36ccca0c1c3a/soundfile-0.13.1-py2.py3-none-any.whl", hash = "sha256:a23c717560da2cf4c7b5ae1142514e0fd82d6bbd9dfc93a50423447142f2c445", size = 25751, upload-time = "2025-01-25T09:16:44.235Z" }, + { url = "https://files.pythonhosted.org/packages/ea/ab/73e97a5b3cc46bba7ff8650a1504348fa1863a6f9d57d7001c6b67c5f20e/soundfile-0.13.1-py2.py3-none-macosx_10_9_x86_64.whl", hash = "sha256:82dc664d19831933fe59adad199bf3945ad06d84bc111a5b4c0d3089a5b9ec33", size = 1142250, upload-time = "2025-01-25T09:16:47.583Z" }, + { url = "https://files.pythonhosted.org/packages/a0/e5/58fd1a8d7b26fc113af244f966ee3aecf03cb9293cb935daaddc1e455e18/soundfile-0.13.1-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:743f12c12c4054921e15736c6be09ac26b3b3d603aef6fd69f9dde68748f2593", size = 1101406, upload-time = "2025-01-25T09:16:49.662Z" }, + { url = "https://files.pythonhosted.org/packages/58/ae/c0e4a53d77cf6e9a04179535766b3321b0b9ced5f70522e4caf9329f0046/soundfile-0.13.1-py2.py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:9c9e855f5a4d06ce4213f31918653ab7de0c5a8d8107cd2427e44b42df547deb", size = 1235729, upload-time = "2025-01-25T09:16:53.018Z" }, + { url = "https://files.pythonhosted.org/packages/57/5e/70bdd9579b35003a489fc850b5047beeda26328053ebadc1fb60f320f7db/soundfile-0.13.1-py2.py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:03267c4e493315294834a0870f31dbb3b28a95561b80b134f0bd3cf2d5f0e618", size = 1313646, upload-time = "2025-01-25T09:16:54.872Z" }, + { url = "https://files.pythonhosted.org/packages/fe/df/8c11dc4dfceda14e3003bb81a0d0edcaaf0796dd7b4f826ea3e532146bba/soundfile-0.13.1-py2.py3-none-win32.whl", hash = "sha256:c734564fab7c5ddf8e9be5bf70bab68042cd17e9c214c06e365e20d64f9a69d5", size = 899881, upload-time = "2025-01-25T09:16:56.663Z" }, + { url = "https://files.pythonhosted.org/packages/14/e9/6b761de83277f2f02ded7e7ea6f07828ec78e4b229b80e4ca55dd205b9dc/soundfile-0.13.1-py2.py3-none-win_amd64.whl", hash = "sha256:1e70a05a0626524a69e9f0f4dd2ec174b4e9567f4d8b6c11d38b5c289be36ee9", size = 1019162, upload-time = "2025-01-25T09:16:59.573Z" }, +] + +[[package]] +name = "stack-data" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asttokens" }, + { name = "executing" }, + { name = "pure-eval" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707, upload-time = "2023-09-30T13:58:05.479Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521, upload-time = "2023-09-30T13:58:03.53Z" }, +] + +[[package]] +name = "starlette" +version = "0.47.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/15/b9/cc3017f9a9c9b6e27c5106cc10cc7904653c3eec0729793aec10479dd669/starlette-0.47.3.tar.gz", hash = "sha256:6bc94f839cc176c4858894f1f8908f0ab79dfec1a6b8402f6da9be26ebea52e9", size = 2584144, upload-time = "2025-08-24T13:36:42.122Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/fd/901cfa59aaa5b30a99e16876f11abe38b59a1a2c51ffb3d7142bb6089069/starlette-0.47.3-py3-none-any.whl", hash = "sha256:89c0778ca62a76b826101e7c709e70680a1699ca7da6b44d38eb0a7e61fe4b51", size = 72991, upload-time = "2025-08-24T13:36:40.887Z" }, +] + +[[package]] +name = "sympy" +version = "1.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mpmath" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921, upload-time = "2025-04-27T18:05:01.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" }, +] + +[[package]] +name = "tomli" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077, upload-time = "2024-11-27T22:37:54.956Z" }, + { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429, upload-time = "2024-11-27T22:37:56.698Z" }, + { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067, upload-time = "2024-11-27T22:37:57.63Z" }, + { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030, upload-time = "2024-11-27T22:37:59.344Z" }, + { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898, upload-time = "2024-11-27T22:38:00.429Z" }, + { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894, upload-time = "2024-11-27T22:38:02.094Z" }, + { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319, upload-time = "2024-11-27T22:38:03.206Z" }, + { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273, upload-time = "2024-11-27T22:38:04.217Z" }, + { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310, upload-time = "2024-11-27T22:38:05.908Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309, upload-time = "2024-11-27T22:38:06.812Z" }, + { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762, upload-time = "2024-11-27T22:38:07.731Z" }, + { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453, upload-time = "2024-11-27T22:38:09.384Z" }, + { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486, upload-time = "2024-11-27T22:38:10.329Z" }, + { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349, upload-time = "2024-11-27T22:38:11.443Z" }, + { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159, upload-time = "2024-11-27T22:38:13.099Z" }, + { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243, upload-time = "2024-11-27T22:38:14.766Z" }, + { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645, upload-time = "2024-11-27T22:38:15.843Z" }, + { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload-time = "2024-11-27T22:38:17.645Z" }, + { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload-time = "2024-11-27T22:38:19.159Z" }, + { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload-time = "2024-11-27T22:38:20.064Z" }, + { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708, upload-time = "2024-11-27T22:38:21.659Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582, upload-time = "2024-11-27T22:38:22.693Z" }, + { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543, upload-time = "2024-11-27T22:38:24.367Z" }, + { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691, upload-time = "2024-11-27T22:38:26.081Z" }, + { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170, upload-time = "2024-11-27T22:38:27.921Z" }, + { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530, upload-time = "2024-11-27T22:38:29.591Z" }, + { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666, upload-time = "2024-11-27T22:38:30.639Z" }, + { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954, upload-time = "2024-11-27T22:38:31.702Z" }, + { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724, upload-time = "2024-11-27T22:38:32.837Z" }, + { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383, upload-time = "2024-11-27T22:38:34.455Z" }, + { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, +] + +[[package]] +name = "tornado" +version = "6.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/09/ce/1eb500eae19f4648281bb2186927bb062d2438c2e5093d1360391afd2f90/tornado-6.5.2.tar.gz", hash = "sha256:ab53c8f9a0fa351e2c0741284e06c7a45da86afb544133201c5cc8578eb076a0", size = 510821, upload-time = "2025-08-08T18:27:00.78Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f6/48/6a7529df2c9cc12efd2e8f5dd219516184d703b34c06786809670df5b3bd/tornado-6.5.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:2436822940d37cde62771cff8774f4f00b3c8024fe482e16ca8387b8a2724db6", size = 442563, upload-time = "2025-08-08T18:26:42.945Z" }, + { url = "https://files.pythonhosted.org/packages/f2/b5/9b575a0ed3e50b00c40b08cbce82eb618229091d09f6d14bce80fc01cb0b/tornado-6.5.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:583a52c7aa94ee046854ba81d9ebb6c81ec0fd30386d96f7640c96dad45a03ef", size = 440729, upload-time = "2025-08-08T18:26:44.473Z" }, + { url = "https://files.pythonhosted.org/packages/1b/4e/619174f52b120efcf23633c817fd3fed867c30bff785e2cd5a53a70e483c/tornado-6.5.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0fe179f28d597deab2842b86ed4060deec7388f1fd9c1b4a41adf8af058907e", size = 444295, upload-time = "2025-08-08T18:26:46.021Z" }, + { url = "https://files.pythonhosted.org/packages/95/fa/87b41709552bbd393c85dd18e4e3499dcd8983f66e7972926db8d96aa065/tornado-6.5.2-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b186e85d1e3536d69583d2298423744740986018e393d0321df7340e71898882", size = 443644, upload-time = "2025-08-08T18:26:47.625Z" }, + { url = "https://files.pythonhosted.org/packages/f9/41/fb15f06e33d7430ca89420283a8762a4e6b8025b800ea51796ab5e6d9559/tornado-6.5.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e792706668c87709709c18b353da1f7662317b563ff69f00bab83595940c7108", size = 443878, upload-time = "2025-08-08T18:26:50.599Z" }, + { url = "https://files.pythonhosted.org/packages/11/92/fe6d57da897776ad2e01e279170ea8ae726755b045fe5ac73b75357a5a3f/tornado-6.5.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:06ceb1300fd70cb20e43b1ad8aaee0266e69e7ced38fa910ad2e03285009ce7c", size = 444549, upload-time = "2025-08-08T18:26:51.864Z" }, + { url = "https://files.pythonhosted.org/packages/9b/02/c8f4f6c9204526daf3d760f4aa555a7a33ad0e60843eac025ccfd6ff4a93/tornado-6.5.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:74db443e0f5251be86cbf37929f84d8c20c27a355dd452a5cfa2aada0d001ec4", size = 443973, upload-time = "2025-08-08T18:26:53.625Z" }, + { url = "https://files.pythonhosted.org/packages/ae/2d/f5f5707b655ce2317190183868cd0f6822a1121b4baeae509ceb9590d0bd/tornado-6.5.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b5e735ab2889d7ed33b32a459cac490eda71a1ba6857b0118de476ab6c366c04", size = 443954, upload-time = "2025-08-08T18:26:55.072Z" }, + { url = "https://files.pythonhosted.org/packages/e8/59/593bd0f40f7355806bf6573b47b8c22f8e1374c9b6fd03114bd6b7a3dcfd/tornado-6.5.2-cp39-abi3-win32.whl", hash = "sha256:c6f29e94d9b37a95013bb669616352ddb82e3bfe8326fccee50583caebc8a5f0", size = 445023, upload-time = "2025-08-08T18:26:56.677Z" }, + { url = "https://files.pythonhosted.org/packages/c7/2a/f609b420c2f564a748a2d80ebfb2ee02a73ca80223af712fca591386cafb/tornado-6.5.2-cp39-abi3-win_amd64.whl", hash = "sha256:e56a5af51cc30dd2cae649429af65ca2f6571da29504a07995175df14c18f35f", size = 445427, upload-time = "2025-08-08T18:26:57.91Z" }, + { url = "https://files.pythonhosted.org/packages/5e/4f/e1f65e8f8c76d73658b33d33b81eed4322fb5085350e4328d5c956f0c8f9/tornado-6.5.2-cp39-abi3-win_arm64.whl", hash = "sha256:d6c33dc3672e3a1f3618eb63b7ef4683a7688e7b9e6e8f0d9aa5726360a004af", size = 444456, upload-time = "2025-08-08T18:26:59.207Z" }, +] + +[[package]] +name = "tqdm" +version = "4.67.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737, upload-time = "2024-11-24T20:12:22.481Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" }, +] + +[[package]] +name = "traitlets" +version = "5.14.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/79/72064e6a701c2183016abbbfedaba506d81e30e232a68c9f0d6f6fcd1574/traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", size = 161621, upload-time = "2024-04-19T11:11:49.746Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359, upload-time = "2024-04-19T11:11:46.763Z" }, +] + +[[package]] +name = "transforms3d" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d5/1e/626c2d87c29a35fadc8de5624f4302e1ee56cff380d282d62cb3780e6620/transforms3d-0.4.2.tar.gz", hash = "sha256:e8b5df30eaedbee556e81c6938e55aab5365894e47d0a17615d7db7fd2393680", size = 1368797, upload-time = "2024-06-17T11:43:33.231Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/7a/f38385f1b2d5f54221baf1db3d6371dc6eef8041d95abff39576c694e9d9/transforms3d-0.4.2-py3-none-any.whl", hash = "sha256:1c70399d9e9473ecc23311fd947f727f7c69ed0b063244828c383aa1aefa5941", size = 1376759, upload-time = "2024-06-20T11:09:19.43Z" }, +] + +[[package]] +name = "types-requests" +version = "2.32.4.20250913" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/36/27/489922f4505975b11de2b5ad07b4fe1dca0bca9be81a703f26c5f3acfce5/types_requests-2.32.4.20250913.tar.gz", hash = "sha256:abd6d4f9ce3a9383f269775a9835a4c24e5cd6b9f647d64f88aa4613c33def5d", size = 23113, upload-time = "2025-09-13T02:40:02.309Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/20/9a227ea57c1285986c4cf78400d0a91615d25b24e257fd9e2969606bdfae/types_requests-2.32.4.20250913-py3-none-any.whl", hash = "sha256:78c9c1fffebbe0fa487a418e0fa5252017e9c60d1a2da394077f1780f655d7e1", size = 20658, upload-time = "2025-09-13T02:40:01.115Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726, upload-time = "2025-05-21T18:55:23.885Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" }, +] + +[[package]] +name = "u-msgpack-python" +version = "2.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/36/9d/a40411a475e7d4838994b7f6bcc6bfca9acc5b119ce3a7503608c4428b49/u-msgpack-python-2.8.0.tar.gz", hash = "sha256:b801a83d6ed75e6df41e44518b4f2a9c221dc2da4bcd5380e3a0feda520bc61a", size = 18167, upload-time = "2023-05-18T09:28:12.187Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/5e/512aeb40fd819f4660d00f96f5c7371ee36fc8c6b605128c5ee59e0b28c6/u_msgpack_python-2.8.0-py2.py3-none-any.whl", hash = "sha256:1d853d33e78b72c4228a2025b4db28cda81214076e5b0422ed0ae1b1b2bb586a", size = 10590, upload-time = "2023-05-18T09:28:10.323Z" }, +] + +[[package]] +name = "urdf-parser-py" +version = "0.0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "lxml" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/8b/9dd44781a4e87746a426c56c3368c3da64fd90a130f582340cbf74397f8e/urdf_parser_py-0.0.4.tar.gz", hash = "sha256:e983f637145fded67bcff6a542302069bb975b2edf1b18318c093abba1b794cc", size = 12952, upload-time = "2021-11-11T17:20:42.012Z" } + +[[package]] +name = "urllib3" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, +] + +[[package]] +name = "uvicorn" +version = "0.35.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/42/e0e305207bb88c6b8d3061399c6a961ffe5fbb7e2aa63c9234df7259e9cd/uvicorn-0.35.0.tar.gz", hash = "sha256:bc662f087f7cf2ce11a1d7fd70b90c9f98ef2e2831556dd078d131b96cc94a01", size = 78473, upload-time = "2025-06-28T16:15:46.058Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/e2/dc81b1bd1dcfe91735810265e9d26bc8ec5da45b4c0f6237e286819194c3/uvicorn-0.35.0-py3-none-any.whl", hash = "sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a", size = 66406, upload-time = "2025-06-28T16:15:44.816Z" }, +] + +[package.optional-dependencies] +standard = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "httptools" }, + { name = "python-dotenv" }, + { name = "pyyaml" }, + { name = "uvloop", marker = "platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'" }, + { name = "watchfiles" }, + { name = "websockets" }, +] + +[[package]] +name = "uvloop" +version = "0.21.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/c0/854216d09d33c543f12a44b393c402e89a920b1a0a7dc634c42de91b9cf6/uvloop-0.21.0.tar.gz", hash = "sha256:3bf12b0fda68447806a7ad847bfa591613177275d35b6724b1ee573faa3704e3", size = 2492741, upload-time = "2024-10-14T23:38:35.489Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/76/44a55515e8c9505aa1420aebacf4dd82552e5e15691654894e90d0bd051a/uvloop-0.21.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ec7e6b09a6fdded42403182ab6b832b71f4edaf7f37a9a0e371a01db5f0cb45f", size = 1442019, upload-time = "2024-10-14T23:37:20.068Z" }, + { url = "https://files.pythonhosted.org/packages/35/5a/62d5800358a78cc25c8a6c72ef8b10851bdb8cca22e14d9c74167b7f86da/uvloop-0.21.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:196274f2adb9689a289ad7d65700d37df0c0930fd8e4e743fa4834e850d7719d", size = 801898, upload-time = "2024-10-14T23:37:22.663Z" }, + { url = "https://files.pythonhosted.org/packages/f3/96/63695e0ebd7da6c741ccd4489b5947394435e198a1382349c17b1146bb97/uvloop-0.21.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f38b2e090258d051d68a5b14d1da7203a3c3677321cf32a95a6f4db4dd8b6f26", size = 3827735, upload-time = "2024-10-14T23:37:25.129Z" }, + { url = "https://files.pythonhosted.org/packages/61/e0/f0f8ec84979068ffae132c58c79af1de9cceeb664076beea86d941af1a30/uvloop-0.21.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87c43e0f13022b998eb9b973b5e97200c8b90823454d4bc06ab33829e09fb9bb", size = 3825126, upload-time = "2024-10-14T23:37:27.59Z" }, + { url = "https://files.pythonhosted.org/packages/bf/fe/5e94a977d058a54a19df95f12f7161ab6e323ad49f4dabc28822eb2df7ea/uvloop-0.21.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:10d66943def5fcb6e7b37310eb6b5639fd2ccbc38df1177262b0640c3ca68c1f", size = 3705789, upload-time = "2024-10-14T23:37:29.385Z" }, + { url = "https://files.pythonhosted.org/packages/26/dd/c7179618e46092a77e036650c1f056041a028a35c4d76945089fcfc38af8/uvloop-0.21.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:67dd654b8ca23aed0a8e99010b4c34aca62f4b7fce88f39d452ed7622c94845c", size = 3800523, upload-time = "2024-10-14T23:37:32.048Z" }, + { url = "https://files.pythonhosted.org/packages/57/a7/4cf0334105c1160dd6819f3297f8700fda7fc30ab4f61fbf3e725acbc7cc/uvloop-0.21.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c0f3fa6200b3108919f8bdabb9a7f87f20e7097ea3c543754cabc7d717d95cf8", size = 1447410, upload-time = "2024-10-14T23:37:33.612Z" }, + { url = "https://files.pythonhosted.org/packages/8c/7c/1517b0bbc2dbe784b563d6ab54f2ef88c890fdad77232c98ed490aa07132/uvloop-0.21.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0878c2640cf341b269b7e128b1a5fed890adc4455513ca710d77d5e93aa6d6a0", size = 805476, upload-time = "2024-10-14T23:37:36.11Z" }, + { url = "https://files.pythonhosted.org/packages/ee/ea/0bfae1aceb82a503f358d8d2fa126ca9dbdb2ba9c7866974faec1cb5875c/uvloop-0.21.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9fb766bb57b7388745d8bcc53a359b116b8a04c83a2288069809d2b3466c37e", size = 3960855, upload-time = "2024-10-14T23:37:37.683Z" }, + { url = "https://files.pythonhosted.org/packages/8a/ca/0864176a649838b838f36d44bf31c451597ab363b60dc9e09c9630619d41/uvloop-0.21.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a375441696e2eda1c43c44ccb66e04d61ceeffcd76e4929e527b7fa401b90fb", size = 3973185, upload-time = "2024-10-14T23:37:40.226Z" }, + { url = "https://files.pythonhosted.org/packages/30/bf/08ad29979a936d63787ba47a540de2132169f140d54aa25bc8c3df3e67f4/uvloop-0.21.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:baa0e6291d91649c6ba4ed4b2f982f9fa165b5bbd50a9e203c416a2797bab3c6", size = 3820256, upload-time = "2024-10-14T23:37:42.839Z" }, + { url = "https://files.pythonhosted.org/packages/da/e2/5cf6ef37e3daf2f06e651aae5ea108ad30df3cb269102678b61ebf1fdf42/uvloop-0.21.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4509360fcc4c3bd2c70d87573ad472de40c13387f5fda8cb58350a1d7475e58d", size = 3937323, upload-time = "2024-10-14T23:37:45.337Z" }, + { url = "https://files.pythonhosted.org/packages/8c/4c/03f93178830dc7ce8b4cdee1d36770d2f5ebb6f3d37d354e061eefc73545/uvloop-0.21.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:359ec2c888397b9e592a889c4d72ba3d6befba8b2bb01743f72fffbde663b59c", size = 1471284, upload-time = "2024-10-14T23:37:47.833Z" }, + { url = "https://files.pythonhosted.org/packages/43/3e/92c03f4d05e50f09251bd8b2b2b584a2a7f8fe600008bcc4523337abe676/uvloop-0.21.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f7089d2dc73179ce5ac255bdf37c236a9f914b264825fdaacaded6990a7fb4c2", size = 821349, upload-time = "2024-10-14T23:37:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/a6/ef/a02ec5da49909dbbfb1fd205a9a1ac4e88ea92dcae885e7c961847cd51e2/uvloop-0.21.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:baa4dcdbd9ae0a372f2167a207cd98c9f9a1ea1188a8a526431eef2f8116cc8d", size = 4580089, upload-time = "2024-10-14T23:37:51.703Z" }, + { url = "https://files.pythonhosted.org/packages/06/a7/b4e6a19925c900be9f98bec0a75e6e8f79bb53bdeb891916609ab3958967/uvloop-0.21.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86975dca1c773a2c9864f4c52c5a55631038e387b47eaf56210f873887b6c8dc", size = 4693770, upload-time = "2024-10-14T23:37:54.122Z" }, + { url = "https://files.pythonhosted.org/packages/ce/0c/f07435a18a4b94ce6bd0677d8319cd3de61f3a9eeb1e5f8ab4e8b5edfcb3/uvloop-0.21.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:461d9ae6660fbbafedd07559c6a2e57cd553b34b0065b6550685f6653a98c1cb", size = 4451321, upload-time = "2024-10-14T23:37:55.766Z" }, + { url = "https://files.pythonhosted.org/packages/8f/eb/f7032be105877bcf924709c97b1bf3b90255b4ec251f9340cef912559f28/uvloop-0.21.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:183aef7c8730e54c9a3ee3227464daed66e37ba13040bb3f350bc2ddc040f22f", size = 4659022, upload-time = "2024-10-14T23:37:58.195Z" }, + { url = "https://files.pythonhosted.org/packages/3f/8d/2cbef610ca21539f0f36e2b34da49302029e7c9f09acef0b1c3b5839412b/uvloop-0.21.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bfd55dfcc2a512316e65f16e503e9e450cab148ef11df4e4e679b5e8253a5281", size = 1468123, upload-time = "2024-10-14T23:38:00.688Z" }, + { url = "https://files.pythonhosted.org/packages/93/0d/b0038d5a469f94ed8f2b2fce2434a18396d8fbfb5da85a0a9781ebbdec14/uvloop-0.21.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:787ae31ad8a2856fc4e7c095341cccc7209bd657d0e71ad0dc2ea83c4a6fa8af", size = 819325, upload-time = "2024-10-14T23:38:02.309Z" }, + { url = "https://files.pythonhosted.org/packages/50/94/0a687f39e78c4c1e02e3272c6b2ccdb4e0085fda3b8352fecd0410ccf915/uvloop-0.21.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ee4d4ef48036ff6e5cfffb09dd192c7a5027153948d85b8da7ff705065bacc6", size = 4582806, upload-time = "2024-10-14T23:38:04.711Z" }, + { url = "https://files.pythonhosted.org/packages/d2/19/f5b78616566ea68edd42aacaf645adbf71fbd83fc52281fba555dc27e3f1/uvloop-0.21.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3df876acd7ec037a3d005b3ab85a7e4110422e4d9c1571d4fc89b0fc41b6816", size = 4701068, upload-time = "2024-10-14T23:38:06.385Z" }, + { url = "https://files.pythonhosted.org/packages/47/57/66f061ee118f413cd22a656de622925097170b9380b30091b78ea0c6ea75/uvloop-0.21.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd53ecc9a0f3d87ab847503c2e1552b690362e005ab54e8a48ba97da3924c0dc", size = 4454428, upload-time = "2024-10-14T23:38:08.416Z" }, + { url = "https://files.pythonhosted.org/packages/63/9a/0962b05b308494e3202d3f794a6e85abe471fe3cafdbcf95c2e8c713aabd/uvloop-0.21.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a5c39f217ab3c663dc699c04cbd50c13813e31d917642d459fdcec07555cc553", size = 4660018, upload-time = "2024-10-14T23:38:10.888Z" }, +] + +[[package]] +name = "virtualenv" +version = "20.34.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1c/14/37fcdba2808a6c615681cd216fecae00413c9dab44fb2e57805ecf3eaee3/virtualenv-20.34.0.tar.gz", hash = "sha256:44815b2c9dee7ed86e387b842a84f20b93f7f417f95886ca1996a72a4138eb1a", size = 6003808, upload-time = "2025-08-13T14:24:07.464Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/06/04c8e804f813cf972e3262f3f8584c232de64f0cde9f703b46cf53a45090/virtualenv-20.34.0-py3-none-any.whl", hash = "sha256:341f5afa7eee943e4984a9207c025feedd768baff6753cd660c857ceb3e36026", size = 5983279, upload-time = "2025-08-13T14:24:05.111Z" }, +] + +[[package]] +name = "watchfiles" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2a/9a/d451fcc97d029f5812e898fd30a53fd8c15c7bbd058fd75cfc6beb9bd761/watchfiles-1.1.0.tar.gz", hash = "sha256:693ed7ec72cbfcee399e92c895362b6e66d63dac6b91e2c11ae03d10d503e575", size = 94406, upload-time = "2025-06-15T19:06:59.42Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/dd/579d1dc57f0f895426a1211c4ef3b0cb37eb9e642bb04bdcd962b5df206a/watchfiles-1.1.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:27f30e14aa1c1e91cb653f03a63445739919aef84c8d2517997a83155e7a2fcc", size = 405757, upload-time = "2025-06-15T19:04:51.058Z" }, + { url = "https://files.pythonhosted.org/packages/1c/a0/7a0318cd874393344d48c34d53b3dd419466adf59a29ba5b51c88dd18b86/watchfiles-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3366f56c272232860ab45c77c3ca7b74ee819c8e1f6f35a7125556b198bbc6df", size = 397511, upload-time = "2025-06-15T19:04:52.79Z" }, + { url = "https://files.pythonhosted.org/packages/06/be/503514656d0555ec2195f60d810eca29b938772e9bfb112d5cd5ad6f6a9e/watchfiles-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8412eacef34cae2836d891836a7fff7b754d6bcac61f6c12ba5ca9bc7e427b68", size = 450739, upload-time = "2025-06-15T19:04:54.203Z" }, + { url = "https://files.pythonhosted.org/packages/4e/0d/a05dd9e5f136cdc29751816d0890d084ab99f8c17b86f25697288ca09bc7/watchfiles-1.1.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:df670918eb7dd719642e05979fc84704af913d563fd17ed636f7c4783003fdcc", size = 458106, upload-time = "2025-06-15T19:04:55.607Z" }, + { url = "https://files.pythonhosted.org/packages/f1/fa/9cd16e4dfdb831072b7ac39e7bea986e52128526251038eb481effe9f48e/watchfiles-1.1.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d7642b9bc4827b5518ebdb3b82698ada8c14c7661ddec5fe719f3e56ccd13c97", size = 484264, upload-time = "2025-06-15T19:04:57.009Z" }, + { url = "https://files.pythonhosted.org/packages/32/04/1da8a637c7e2b70e750a0308e9c8e662ada0cca46211fa9ef24a23937e0b/watchfiles-1.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:199207b2d3eeaeb80ef4411875a6243d9ad8bc35b07fc42daa6b801cc39cc41c", size = 597612, upload-time = "2025-06-15T19:04:58.409Z" }, + { url = "https://files.pythonhosted.org/packages/30/01/109f2762e968d3e58c95731a206e5d7d2a7abaed4299dd8a94597250153c/watchfiles-1.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a479466da6db5c1e8754caee6c262cd373e6e6c363172d74394f4bff3d84d7b5", size = 477242, upload-time = "2025-06-15T19:04:59.786Z" }, + { url = "https://files.pythonhosted.org/packages/b5/b8/46f58cf4969d3b7bc3ca35a98e739fa4085b0657a1540ccc29a1a0bc016f/watchfiles-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:935f9edd022ec13e447e5723a7d14456c8af254544cefbc533f6dd276c9aa0d9", size = 453148, upload-time = "2025-06-15T19:05:01.103Z" }, + { url = "https://files.pythonhosted.org/packages/a5/cd/8267594263b1770f1eb76914940d7b2d03ee55eca212302329608208e061/watchfiles-1.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8076a5769d6bdf5f673a19d51da05fc79e2bbf25e9fe755c47595785c06a8c72", size = 626574, upload-time = "2025-06-15T19:05:02.582Z" }, + { url = "https://files.pythonhosted.org/packages/a1/2f/7f2722e85899bed337cba715723e19185e288ef361360718973f891805be/watchfiles-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:86b1e28d4c37e89220e924305cd9f82866bb0ace666943a6e4196c5df4d58dcc", size = 624378, upload-time = "2025-06-15T19:05:03.719Z" }, + { url = "https://files.pythonhosted.org/packages/bf/20/64c88ec43d90a568234d021ab4b2a6f42a5230d772b987c3f9c00cc27b8b/watchfiles-1.1.0-cp310-cp310-win32.whl", hash = "sha256:d1caf40c1c657b27858f9774d5c0e232089bca9cb8ee17ce7478c6e9264d2587", size = 279829, upload-time = "2025-06-15T19:05:04.822Z" }, + { url = "https://files.pythonhosted.org/packages/39/5c/a9c1ed33de7af80935e4eac09570de679c6e21c07070aa99f74b4431f4d6/watchfiles-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:a89c75a5b9bc329131115a409d0acc16e8da8dfd5867ba59f1dd66ae7ea8fa82", size = 292192, upload-time = "2025-06-15T19:05:06.348Z" }, + { url = "https://files.pythonhosted.org/packages/8b/78/7401154b78ab484ccaaeef970dc2af0cb88b5ba8a1b415383da444cdd8d3/watchfiles-1.1.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:c9649dfc57cc1f9835551deb17689e8d44666315f2e82d337b9f07bd76ae3aa2", size = 405751, upload-time = "2025-06-15T19:05:07.679Z" }, + { url = "https://files.pythonhosted.org/packages/76/63/e6c3dbc1f78d001589b75e56a288c47723de28c580ad715eb116639152b5/watchfiles-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:406520216186b99374cdb58bc48e34bb74535adec160c8459894884c983a149c", size = 397313, upload-time = "2025-06-15T19:05:08.764Z" }, + { url = "https://files.pythonhosted.org/packages/6c/a2/8afa359ff52e99af1632f90cbf359da46184207e893a5f179301b0c8d6df/watchfiles-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb45350fd1dc75cd68d3d72c47f5b513cb0578da716df5fba02fff31c69d5f2d", size = 450792, upload-time = "2025-06-15T19:05:09.869Z" }, + { url = "https://files.pythonhosted.org/packages/1d/bf/7446b401667f5c64972a57a0233be1104157fc3abf72c4ef2666c1bd09b2/watchfiles-1.1.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:11ee4444250fcbeb47459a877e5e80ed994ce8e8d20283857fc128be1715dac7", size = 458196, upload-time = "2025-06-15T19:05:11.91Z" }, + { url = "https://files.pythonhosted.org/packages/58/2f/501ddbdfa3fa874ea5597c77eeea3d413579c29af26c1091b08d0c792280/watchfiles-1.1.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bda8136e6a80bdea23e5e74e09df0362744d24ffb8cd59c4a95a6ce3d142f79c", size = 484788, upload-time = "2025-06-15T19:05:13.373Z" }, + { url = "https://files.pythonhosted.org/packages/61/1e/9c18eb2eb5c953c96bc0e5f626f0e53cfef4bd19bd50d71d1a049c63a575/watchfiles-1.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b915daeb2d8c1f5cee4b970f2e2c988ce6514aace3c9296e58dd64dc9aa5d575", size = 597879, upload-time = "2025-06-15T19:05:14.725Z" }, + { url = "https://files.pythonhosted.org/packages/8b/6c/1467402e5185d89388b4486745af1e0325007af0017c3384cc786fff0542/watchfiles-1.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ed8fc66786de8d0376f9f913c09e963c66e90ced9aa11997f93bdb30f7c872a8", size = 477447, upload-time = "2025-06-15T19:05:15.775Z" }, + { url = "https://files.pythonhosted.org/packages/2b/a1/ec0a606bde4853d6c4a578f9391eeb3684a9aea736a8eb217e3e00aa89a1/watchfiles-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe4371595edf78c41ef8ac8df20df3943e13defd0efcb732b2e393b5a8a7a71f", size = 453145, upload-time = "2025-06-15T19:05:17.17Z" }, + { url = "https://files.pythonhosted.org/packages/90/b9/ef6f0c247a6a35d689fc970dc7f6734f9257451aefb30def5d100d6246a5/watchfiles-1.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b7c5f6fe273291f4d414d55b2c80d33c457b8a42677ad14b4b47ff025d0893e4", size = 626539, upload-time = "2025-06-15T19:05:18.557Z" }, + { url = "https://files.pythonhosted.org/packages/34/44/6ffda5537085106ff5aaa762b0d130ac6c75a08015dd1621376f708c94de/watchfiles-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7738027989881e70e3723c75921f1efa45225084228788fc59ea8c6d732eb30d", size = 624472, upload-time = "2025-06-15T19:05:19.588Z" }, + { url = "https://files.pythonhosted.org/packages/c3/e3/71170985c48028fa3f0a50946916a14055e741db11c2e7bc2f3b61f4d0e3/watchfiles-1.1.0-cp311-cp311-win32.whl", hash = "sha256:622d6b2c06be19f6e89b1d951485a232e3b59618def88dbeda575ed8f0d8dbf2", size = 279348, upload-time = "2025-06-15T19:05:20.856Z" }, + { url = "https://files.pythonhosted.org/packages/89/1b/3e39c68b68a7a171070f81fc2561d23ce8d6859659406842a0e4bebf3bba/watchfiles-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:48aa25e5992b61debc908a61ab4d3f216b64f44fdaa71eb082d8b2de846b7d12", size = 292607, upload-time = "2025-06-15T19:05:21.937Z" }, + { url = "https://files.pythonhosted.org/packages/61/9f/2973b7539f2bdb6ea86d2c87f70f615a71a1fc2dba2911795cea25968aea/watchfiles-1.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:00645eb79a3faa70d9cb15c8d4187bb72970b2470e938670240c7998dad9f13a", size = 285056, upload-time = "2025-06-15T19:05:23.12Z" }, + { url = "https://files.pythonhosted.org/packages/f6/b8/858957045a38a4079203a33aaa7d23ea9269ca7761c8a074af3524fbb240/watchfiles-1.1.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9dc001c3e10de4725c749d4c2f2bdc6ae24de5a88a339c4bce32300a31ede179", size = 402339, upload-time = "2025-06-15T19:05:24.516Z" }, + { url = "https://files.pythonhosted.org/packages/80/28/98b222cca751ba68e88521fabd79a4fab64005fc5976ea49b53fa205d1fa/watchfiles-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d9ba68ec283153dead62cbe81872d28e053745f12335d037de9cbd14bd1877f5", size = 394409, upload-time = "2025-06-15T19:05:25.469Z" }, + { url = "https://files.pythonhosted.org/packages/86/50/dee79968566c03190677c26f7f47960aff738d32087087bdf63a5473e7df/watchfiles-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:130fc497b8ee68dce163e4254d9b0356411d1490e868bd8790028bc46c5cc297", size = 450939, upload-time = "2025-06-15T19:05:26.494Z" }, + { url = "https://files.pythonhosted.org/packages/40/45/a7b56fb129700f3cfe2594a01aa38d033b92a33dddce86c8dfdfc1247b72/watchfiles-1.1.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:50a51a90610d0845a5931a780d8e51d7bd7f309ebc25132ba975aca016b576a0", size = 457270, upload-time = "2025-06-15T19:05:27.466Z" }, + { url = "https://files.pythonhosted.org/packages/b5/c8/fa5ef9476b1d02dc6b5e258f515fcaaecf559037edf8b6feffcbc097c4b8/watchfiles-1.1.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc44678a72ac0910bac46fa6a0de6af9ba1355669b3dfaf1ce5f05ca7a74364e", size = 483370, upload-time = "2025-06-15T19:05:28.548Z" }, + { url = "https://files.pythonhosted.org/packages/98/68/42cfcdd6533ec94f0a7aab83f759ec11280f70b11bfba0b0f885e298f9bd/watchfiles-1.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a543492513a93b001975ae283a51f4b67973662a375a403ae82f420d2c7205ee", size = 598654, upload-time = "2025-06-15T19:05:29.997Z" }, + { url = "https://files.pythonhosted.org/packages/d3/74/b2a1544224118cc28df7e59008a929e711f9c68ce7d554e171b2dc531352/watchfiles-1.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ac164e20d17cc285f2b94dc31c384bc3aa3dd5e7490473b3db043dd70fbccfd", size = 478667, upload-time = "2025-06-15T19:05:31.172Z" }, + { url = "https://files.pythonhosted.org/packages/8c/77/e3362fe308358dc9f8588102481e599c83e1b91c2ae843780a7ded939a35/watchfiles-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7590d5a455321e53857892ab8879dce62d1f4b04748769f5adf2e707afb9d4f", size = 452213, upload-time = "2025-06-15T19:05:32.299Z" }, + { url = "https://files.pythonhosted.org/packages/6e/17/c8f1a36540c9a1558d4faf08e909399e8133599fa359bf52ec8fcee5be6f/watchfiles-1.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:37d3d3f7defb13f62ece99e9be912afe9dd8a0077b7c45ee5a57c74811d581a4", size = 626718, upload-time = "2025-06-15T19:05:33.415Z" }, + { url = "https://files.pythonhosted.org/packages/26/45/fb599be38b4bd38032643783d7496a26a6f9ae05dea1a42e58229a20ac13/watchfiles-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7080c4bb3efd70a07b1cc2df99a7aa51d98685be56be6038c3169199d0a1c69f", size = 623098, upload-time = "2025-06-15T19:05:34.534Z" }, + { url = "https://files.pythonhosted.org/packages/a1/e7/fdf40e038475498e160cd167333c946e45d8563ae4dd65caf757e9ffe6b4/watchfiles-1.1.0-cp312-cp312-win32.whl", hash = "sha256:cbcf8630ef4afb05dc30107bfa17f16c0896bb30ee48fc24bf64c1f970f3b1fd", size = 279209, upload-time = "2025-06-15T19:05:35.577Z" }, + { url = "https://files.pythonhosted.org/packages/3f/d3/3ae9d5124ec75143bdf088d436cba39812122edc47709cd2caafeac3266f/watchfiles-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:cbd949bdd87567b0ad183d7676feb98136cde5bb9025403794a4c0db28ed3a47", size = 292786, upload-time = "2025-06-15T19:05:36.559Z" }, + { url = "https://files.pythonhosted.org/packages/26/2f/7dd4fc8b5f2b34b545e19629b4a018bfb1de23b3a496766a2c1165ca890d/watchfiles-1.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:0a7d40b77f07be87c6faa93d0951a0fcd8cbca1ddff60a1b65d741bac6f3a9f6", size = 284343, upload-time = "2025-06-15T19:05:37.5Z" }, + { url = "https://files.pythonhosted.org/packages/d3/42/fae874df96595556a9089ade83be34a2e04f0f11eb53a8dbf8a8a5e562b4/watchfiles-1.1.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5007f860c7f1f8df471e4e04aaa8c43673429047d63205d1630880f7637bca30", size = 402004, upload-time = "2025-06-15T19:05:38.499Z" }, + { url = "https://files.pythonhosted.org/packages/fa/55/a77e533e59c3003d9803c09c44c3651224067cbe7fb5d574ddbaa31e11ca/watchfiles-1.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:20ecc8abbd957046f1fe9562757903f5eaf57c3bce70929fda6c7711bb58074a", size = 393671, upload-time = "2025-06-15T19:05:39.52Z" }, + { url = "https://files.pythonhosted.org/packages/05/68/b0afb3f79c8e832e6571022611adbdc36e35a44e14f129ba09709aa4bb7a/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2f0498b7d2a3c072766dba3274fe22a183dbea1f99d188f1c6c72209a1063dc", size = 449772, upload-time = "2025-06-15T19:05:40.897Z" }, + { url = "https://files.pythonhosted.org/packages/ff/05/46dd1f6879bc40e1e74c6c39a1b9ab9e790bf1f5a2fe6c08b463d9a807f4/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:239736577e848678e13b201bba14e89718f5c2133dfd6b1f7846fa1b58a8532b", size = 456789, upload-time = "2025-06-15T19:05:42.045Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ca/0eeb2c06227ca7f12e50a47a3679df0cd1ba487ea19cf844a905920f8e95/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eff4b8d89f444f7e49136dc695599a591ff769300734446c0a86cba2eb2f9895", size = 482551, upload-time = "2025-06-15T19:05:43.781Z" }, + { url = "https://files.pythonhosted.org/packages/31/47/2cecbd8694095647406645f822781008cc524320466ea393f55fe70eed3b/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12b0a02a91762c08f7264e2e79542f76870c3040bbc847fb67410ab81474932a", size = 597420, upload-time = "2025-06-15T19:05:45.244Z" }, + { url = "https://files.pythonhosted.org/packages/d9/7e/82abc4240e0806846548559d70f0b1a6dfdca75c1b4f9fa62b504ae9b083/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:29e7bc2eee15cbb339c68445959108803dc14ee0c7b4eea556400131a8de462b", size = 477950, upload-time = "2025-06-15T19:05:46.332Z" }, + { url = "https://files.pythonhosted.org/packages/25/0d/4d564798a49bf5482a4fa9416dea6b6c0733a3b5700cb8a5a503c4b15853/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9481174d3ed982e269c090f780122fb59cee6c3796f74efe74e70f7780ed94c", size = 451706, upload-time = "2025-06-15T19:05:47.459Z" }, + { url = "https://files.pythonhosted.org/packages/81/b5/5516cf46b033192d544102ea07c65b6f770f10ed1d0a6d388f5d3874f6e4/watchfiles-1.1.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:80f811146831c8c86ab17b640801c25dc0a88c630e855e2bef3568f30434d52b", size = 625814, upload-time = "2025-06-15T19:05:48.654Z" }, + { url = "https://files.pythonhosted.org/packages/0c/dd/7c1331f902f30669ac3e754680b6edb9a0dd06dea5438e61128111fadd2c/watchfiles-1.1.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:60022527e71d1d1fda67a33150ee42869042bce3d0fcc9cc49be009a9cded3fb", size = 622820, upload-time = "2025-06-15T19:05:50.088Z" }, + { url = "https://files.pythonhosted.org/packages/1b/14/36d7a8e27cd128d7b1009e7715a7c02f6c131be9d4ce1e5c3b73d0e342d8/watchfiles-1.1.0-cp313-cp313-win32.whl", hash = "sha256:32d6d4e583593cb8576e129879ea0991660b935177c0f93c6681359b3654bfa9", size = 279194, upload-time = "2025-06-15T19:05:51.186Z" }, + { url = "https://files.pythonhosted.org/packages/25/41/2dd88054b849aa546dbeef5696019c58f8e0774f4d1c42123273304cdb2e/watchfiles-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:f21af781a4a6fbad54f03c598ab620e3a77032c5878f3d780448421a6e1818c7", size = 292349, upload-time = "2025-06-15T19:05:52.201Z" }, + { url = "https://files.pythonhosted.org/packages/c8/cf/421d659de88285eb13941cf11a81f875c176f76a6d99342599be88e08d03/watchfiles-1.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:5366164391873ed76bfdf618818c82084c9db7fac82b64a20c44d335eec9ced5", size = 283836, upload-time = "2025-06-15T19:05:53.265Z" }, + { url = "https://files.pythonhosted.org/packages/45/10/6faf6858d527e3599cc50ec9fcae73590fbddc1420bd4fdccfebffeedbc6/watchfiles-1.1.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:17ab167cca6339c2b830b744eaf10803d2a5b6683be4d79d8475d88b4a8a4be1", size = 400343, upload-time = "2025-06-15T19:05:54.252Z" }, + { url = "https://files.pythonhosted.org/packages/03/20/5cb7d3966f5e8c718006d0e97dfe379a82f16fecd3caa7810f634412047a/watchfiles-1.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:328dbc9bff7205c215a7807da7c18dce37da7da718e798356212d22696404339", size = 392916, upload-time = "2025-06-15T19:05:55.264Z" }, + { url = "https://files.pythonhosted.org/packages/8c/07/d8f1176328fa9e9581b6f120b017e286d2a2d22ae3f554efd9515c8e1b49/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7208ab6e009c627b7557ce55c465c98967e8caa8b11833531fdf95799372633", size = 449582, upload-time = "2025-06-15T19:05:56.317Z" }, + { url = "https://files.pythonhosted.org/packages/66/e8/80a14a453cf6038e81d072a86c05276692a1826471fef91df7537dba8b46/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a8f6f72974a19efead54195bc9bed4d850fc047bb7aa971268fd9a8387c89011", size = 456752, upload-time = "2025-06-15T19:05:57.359Z" }, + { url = "https://files.pythonhosted.org/packages/5a/25/0853b3fe0e3c2f5af9ea60eb2e781eade939760239a72c2d38fc4cc335f6/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d181ef50923c29cf0450c3cd47e2f0557b62218c50b2ab8ce2ecaa02bd97e670", size = 481436, upload-time = "2025-06-15T19:05:58.447Z" }, + { url = "https://files.pythonhosted.org/packages/fe/9e/4af0056c258b861fbb29dcb36258de1e2b857be4a9509e6298abcf31e5c9/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:adb4167043d3a78280d5d05ce0ba22055c266cf8655ce942f2fb881262ff3cdf", size = 596016, upload-time = "2025-06-15T19:05:59.59Z" }, + { url = "https://files.pythonhosted.org/packages/c5/fa/95d604b58aa375e781daf350897aaaa089cff59d84147e9ccff2447c8294/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5701dc474b041e2934a26d31d39f90fac8a3dee2322b39f7729867f932b1d4", size = 476727, upload-time = "2025-06-15T19:06:01.086Z" }, + { url = "https://files.pythonhosted.org/packages/65/95/fe479b2664f19be4cf5ceeb21be05afd491d95f142e72d26a42f41b7c4f8/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b067915e3c3936966a8607f6fe5487df0c9c4afb85226613b520890049deea20", size = 451864, upload-time = "2025-06-15T19:06:02.144Z" }, + { url = "https://files.pythonhosted.org/packages/d3/8a/3c4af14b93a15ce55901cd7a92e1a4701910f1768c78fb30f61d2b79785b/watchfiles-1.1.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:9c733cda03b6d636b4219625a4acb5c6ffb10803338e437fb614fef9516825ef", size = 625626, upload-time = "2025-06-15T19:06:03.578Z" }, + { url = "https://files.pythonhosted.org/packages/da/f5/cf6aa047d4d9e128f4b7cde615236a915673775ef171ff85971d698f3c2c/watchfiles-1.1.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:cc08ef8b90d78bfac66f0def80240b0197008e4852c9f285907377b2947ffdcb", size = 622744, upload-time = "2025-06-15T19:06:05.066Z" }, + { url = "https://files.pythonhosted.org/packages/2c/00/70f75c47f05dea6fd30df90f047765f6fc2d6eb8b5a3921379b0b04defa2/watchfiles-1.1.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:9974d2f7dc561cce3bb88dfa8eb309dab64c729de85fba32e98d75cf24b66297", size = 402114, upload-time = "2025-06-15T19:06:06.186Z" }, + { url = "https://files.pythonhosted.org/packages/53/03/acd69c48db4a1ed1de26b349d94077cca2238ff98fd64393f3e97484cae6/watchfiles-1.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c68e9f1fcb4d43798ad8814c4c1b61547b014b667216cb754e606bfade587018", size = 393879, upload-time = "2025-06-15T19:06:07.369Z" }, + { url = "https://files.pythonhosted.org/packages/2f/c8/a9a2a6f9c8baa4eceae5887fecd421e1b7ce86802bcfc8b6a942e2add834/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95ab1594377effac17110e1352989bdd7bdfca9ff0e5eeccd8c69c5389b826d0", size = 450026, upload-time = "2025-06-15T19:06:08.476Z" }, + { url = "https://files.pythonhosted.org/packages/fe/51/d572260d98388e6e2b967425c985e07d47ee6f62e6455cefb46a6e06eda5/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fba9b62da882c1be1280a7584ec4515d0a6006a94d6e5819730ec2eab60ffe12", size = 457917, upload-time = "2025-06-15T19:06:09.988Z" }, + { url = "https://files.pythonhosted.org/packages/c6/2d/4258e52917bf9f12909b6ec314ff9636276f3542f9d3807d143f27309104/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3434e401f3ce0ed6b42569128b3d1e3af773d7ec18751b918b89cd49c14eaafb", size = 483602, upload-time = "2025-06-15T19:06:11.088Z" }, + { url = "https://files.pythonhosted.org/packages/84/99/bee17a5f341a4345fe7b7972a475809af9e528deba056f8963d61ea49f75/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa257a4d0d21fcbca5b5fcba9dca5a78011cb93c0323fb8855c6d2dfbc76eb77", size = 596758, upload-time = "2025-06-15T19:06:12.197Z" }, + { url = "https://files.pythonhosted.org/packages/40/76/e4bec1d59b25b89d2b0716b41b461ed655a9a53c60dc78ad5771fda5b3e6/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7fd1b3879a578a8ec2076c7961076df540b9af317123f84569f5a9ddee64ce92", size = 477601, upload-time = "2025-06-15T19:06:13.391Z" }, + { url = "https://files.pythonhosted.org/packages/1f/fa/a514292956f4a9ce3c567ec0c13cce427c158e9f272062685a8a727d08fc/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62cc7a30eeb0e20ecc5f4bd113cd69dcdb745a07c68c0370cea919f373f65d9e", size = 451936, upload-time = "2025-06-15T19:06:14.656Z" }, + { url = "https://files.pythonhosted.org/packages/32/5d/c3bf927ec3bbeb4566984eba8dd7a8eb69569400f5509904545576741f88/watchfiles-1.1.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:891c69e027748b4a73847335d208e374ce54ca3c335907d381fde4e41661b13b", size = 626243, upload-time = "2025-06-15T19:06:16.232Z" }, + { url = "https://files.pythonhosted.org/packages/e6/65/6e12c042f1a68c556802a84d54bb06d35577c81e29fba14019562479159c/watchfiles-1.1.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:12fe8eaffaf0faa7906895b4f8bb88264035b3f0243275e0bf24af0436b27259", size = 623073, upload-time = "2025-06-15T19:06:17.457Z" }, + { url = "https://files.pythonhosted.org/packages/89/ab/7f79d9bf57329e7cbb0a6fd4c7bd7d0cee1e4a8ef0041459f5409da3506c/watchfiles-1.1.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:bfe3c517c283e484843cb2e357dd57ba009cff351edf45fb455b5fbd1f45b15f", size = 400872, upload-time = "2025-06-15T19:06:18.57Z" }, + { url = "https://files.pythonhosted.org/packages/df/d5/3f7bf9912798e9e6c516094db6b8932df53b223660c781ee37607030b6d3/watchfiles-1.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a9ccbf1f129480ed3044f540c0fdbc4ee556f7175e5ab40fe077ff6baf286d4e", size = 392877, upload-time = "2025-06-15T19:06:19.55Z" }, + { url = "https://files.pythonhosted.org/packages/0d/c5/54ec7601a2798604e01c75294770dbee8150e81c6e471445d7601610b495/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba0e3255b0396cac3cc7bbace76404dd72b5438bf0d8e7cefa2f79a7f3649caa", size = 449645, upload-time = "2025-06-15T19:06:20.66Z" }, + { url = "https://files.pythonhosted.org/packages/0a/04/c2f44afc3b2fce21ca0b7802cbd37ed90a29874f96069ed30a36dfe57c2b/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4281cd9fce9fc0a9dbf0fc1217f39bf9cf2b4d315d9626ef1d4e87b84699e7e8", size = 457424, upload-time = "2025-06-15T19:06:21.712Z" }, + { url = "https://files.pythonhosted.org/packages/9f/b0/eec32cb6c14d248095261a04f290636da3df3119d4040ef91a4a50b29fa5/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d2404af8db1329f9a3c9b79ff63e0ae7131986446901582067d9304ae8aaf7f", size = 481584, upload-time = "2025-06-15T19:06:22.777Z" }, + { url = "https://files.pythonhosted.org/packages/d1/e2/ca4bb71c68a937d7145aa25709e4f5d68eb7698a25ce266e84b55d591bbd/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e78b6ed8165996013165eeabd875c5dfc19d41b54f94b40e9fff0eb3193e5e8e", size = 596675, upload-time = "2025-06-15T19:06:24.226Z" }, + { url = "https://files.pythonhosted.org/packages/a1/dd/b0e4b7fb5acf783816bc950180a6cd7c6c1d2cf7e9372c0ea634e722712b/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:249590eb75ccc117f488e2fabd1bfa33c580e24b96f00658ad88e38844a040bb", size = 477363, upload-time = "2025-06-15T19:06:25.42Z" }, + { url = "https://files.pythonhosted.org/packages/69/c4/088825b75489cb5b6a761a4542645718893d395d8c530b38734f19da44d2/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d05686b5487cfa2e2c28ff1aa370ea3e6c5accfe6435944ddea1e10d93872147", size = 452240, upload-time = "2025-06-15T19:06:26.552Z" }, + { url = "https://files.pythonhosted.org/packages/10/8c/22b074814970eeef43b7c44df98c3e9667c1f7bf5b83e0ff0201b0bd43f9/watchfiles-1.1.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:d0e10e6f8f6dc5762adee7dece33b722282e1f59aa6a55da5d493a97282fedd8", size = 625607, upload-time = "2025-06-15T19:06:27.606Z" }, + { url = "https://files.pythonhosted.org/packages/32/fa/a4f5c2046385492b2273213ef815bf71a0d4c1943b784fb904e184e30201/watchfiles-1.1.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:af06c863f152005c7592df1d6a7009c836a247c9d8adb78fef8575a5a98699db", size = 623315, upload-time = "2025-06-15T19:06:29.076Z" }, + { url = "https://files.pythonhosted.org/packages/be/7c/a3d7c55cfa377c2f62c4ae3c6502b997186bc5e38156bafcb9b653de9a6d/watchfiles-1.1.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3a6fd40bbb50d24976eb275ccb55cd1951dfb63dbc27cae3066a6ca5f4beabd5", size = 406748, upload-time = "2025-06-15T19:06:44.2Z" }, + { url = "https://files.pythonhosted.org/packages/38/d0/c46f1b2c0ca47f3667b144de6f0515f6d1c670d72f2ca29861cac78abaa1/watchfiles-1.1.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9f811079d2f9795b5d48b55a37aa7773680a5659afe34b54cc1d86590a51507d", size = 398801, upload-time = "2025-06-15T19:06:45.774Z" }, + { url = "https://files.pythonhosted.org/packages/70/9c/9a6a42e97f92eeed77c3485a43ea96723900aefa3ac739a8c73f4bff2cd7/watchfiles-1.1.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2726d7bfd9f76158c84c10a409b77a320426540df8c35be172444394b17f7ea", size = 451528, upload-time = "2025-06-15T19:06:46.791Z" }, + { url = "https://files.pythonhosted.org/packages/51/7b/98c7f4f7ce7ff03023cf971cd84a3ee3b790021ae7584ffffa0eb2554b96/watchfiles-1.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df32d59cb9780f66d165a9a7a26f19df2c7d24e3bd58713108b41d0ff4f929c6", size = 454095, upload-time = "2025-06-15T19:06:48.211Z" }, + { url = "https://files.pythonhosted.org/packages/8c/6b/686dcf5d3525ad17b384fd94708e95193529b460a1b7bf40851f1328ec6e/watchfiles-1.1.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0ece16b563b17ab26eaa2d52230c9a7ae46cf01759621f4fbbca280e438267b3", size = 406910, upload-time = "2025-06-15T19:06:49.335Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d3/71c2dcf81dc1edcf8af9f4d8d63b1316fb0a2dd90cbfd427e8d9dd584a90/watchfiles-1.1.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:51b81e55d40c4b4aa8658427a3ee7ea847c591ae9e8b81ef94a90b668999353c", size = 398816, upload-time = "2025-06-15T19:06:50.433Z" }, + { url = "https://files.pythonhosted.org/packages/b8/fa/12269467b2fc006f8fce4cd6c3acfa77491dd0777d2a747415f28ccc8c60/watchfiles-1.1.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2bcdc54ea267fe72bfc7d83c041e4eb58d7d8dc6f578dfddb52f037ce62f432", size = 451584, upload-time = "2025-06-15T19:06:51.834Z" }, + { url = "https://files.pythonhosted.org/packages/bd/d3/254cea30f918f489db09d6a8435a7de7047f8cb68584477a515f160541d6/watchfiles-1.1.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:923fec6e5461c42bd7e3fd5ec37492c6f3468be0499bc0707b4bbbc16ac21792", size = 454009, upload-time = "2025-06-15T19:06:52.896Z" }, +] + +[[package]] +name = "wcwidth" +version = "0.2.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301, upload-time = "2024-01-06T02:10:57.829Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166, upload-time = "2024-01-06T02:10:55.763Z" }, +] + +[[package]] +name = "websockets" +version = "11.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/3b/2ed38e52eed4cf277f9df5f0463a99199a04d9e29c9e227cfafa57bd3993/websockets-11.0.3.tar.gz", hash = "sha256:88fc51d9a26b10fc331be344f1781224a375b78488fc343620184e95a4b27016", size = 104235, upload-time = "2023-05-07T14:25:20.083Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/76/88640f8aeac7eb0d058b913e7bb72682f8d569db44c7d30e576ec4777ce1/websockets-11.0.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3ccc8a0c387629aec40f2fc9fdcb4b9d5431954f934da3eaf16cdc94f67dbfac", size = 123714, upload-time = "2023-05-07T14:23:15.309Z" }, + { url = "https://files.pythonhosted.org/packages/b9/6b/26b28115b46e23e74ede76d95792eedfe8c58b21f4daabfff1e9f159c8fe/websockets-11.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d67ac60a307f760c6e65dad586f556dde58e683fab03323221a4e530ead6f74d", size = 120949, upload-time = "2023-05-07T14:23:17.656Z" }, + { url = "https://files.pythonhosted.org/packages/f3/82/2d1f3395d47fab65fa8b801e2251b324300ed8db54753b6fb7919cef0c11/websockets-11.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:84d27a4832cc1a0ee07cdcf2b0629a8a72db73f4cf6de6f0904f6661227f256f", size = 121032, upload-time = "2023-05-07T14:23:20.096Z" }, + { url = "https://files.pythonhosted.org/packages/b2/ec/56bdd12d847e4fc2d0a7ba2d7f1476f79cda50599d11ffb6080b86f21ef1/websockets-11.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffd7dcaf744f25f82190856bc26ed81721508fc5cbf2a330751e135ff1283564", size = 130620, upload-time = "2023-05-07T14:23:21.545Z" }, + { url = "https://files.pythonhosted.org/packages/c9/fb/ae5ed4be3514287cf8f6c348c87e1392a6e3f4d6eadae75c18847a2f84b6/websockets-11.0.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7622a89d696fc87af8e8d280d9b421db5133ef5b29d3f7a1ce9f1a7bf7fcfa11", size = 129628, upload-time = "2023-05-07T14:23:23.105Z" }, + { url = "https://files.pythonhosted.org/packages/58/0a/7570e15661a0a546c3a1152d95fe8c05480459bab36247f0acbf41f01a41/websockets-11.0.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bceab846bac555aff6427d060f2fcfff71042dba6f5fca7dc4f75cac815e57ca", size = 129938, upload-time = "2023-05-07T14:23:24.959Z" }, + { url = "https://files.pythonhosted.org/packages/ba/6c/5c0322b2875e8395e6bf0eff11f43f3e25da7ef5b12f4d908cd3a19ea841/websockets-11.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:54c6e5b3d3a8936a4ab6870d46bdd6ec500ad62bde9e44462c32d18f1e9a8e54", size = 134663, upload-time = "2023-05-07T14:23:26.382Z" }, + { url = "https://files.pythonhosted.org/packages/de/0e/d7274e4d41d7b34f204744c27a23707be2ecefaf6f7df2145655f086ecd7/websockets-11.0.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:41f696ba95cd92dc047e46b41b26dd24518384749ed0d99bea0a941ca87404c4", size = 133900, upload-time = "2023-05-07T14:23:28.307Z" }, + { url = "https://files.pythonhosted.org/packages/82/3c/00f051abcf88aec5e952a8840076749b0b26a30c219dcae8ba70200998aa/websockets-11.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:86d2a77fd490ae3ff6fae1c6ceaecad063d3cc2320b44377efdde79880e11526", size = 134520, upload-time = "2023-05-07T14:23:30.734Z" }, + { url = "https://files.pythonhosted.org/packages/8f/7b/4d4ecd29be7d08486e38f987a6603c491296d1e33fe55127d79aebb0333e/websockets-11.0.3-cp310-cp310-win32.whl", hash = "sha256:2d903ad4419f5b472de90cd2d40384573b25da71e33519a67797de17ef849b69", size = 124152, upload-time = "2023-05-07T14:23:33.183Z" }, + { url = "https://files.pythonhosted.org/packages/98/a7/0ed69892981351e5acf88fac0ff4c801fabca2c3bdef9fca4c7d3fde8c53/websockets-11.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:1d2256283fa4b7f4c7d7d3e84dc2ece74d341bce57d5b9bf385df109c2a1a82f", size = 124674, upload-time = "2023-05-07T14:23:35.331Z" }, + { url = "https://files.pythonhosted.org/packages/16/49/ae616bd221efba84a3d78737b417f704af1ffa36f40dcaba5eb954dd4753/websockets-11.0.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e848f46a58b9fcf3d06061d17be388caf70ea5b8cc3466251963c8345e13f7eb", size = 123748, upload-time = "2023-05-07T14:23:37.977Z" }, + { url = "https://files.pythonhosted.org/packages/0a/84/68b848a373493b58615d6c10e9e8ccbaadfd540f84905421739a807704f8/websockets-11.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aa5003845cdd21ac0dc6c9bf661c5beddd01116f6eb9eb3c8e272353d45b3288", size = 120975, upload-time = "2023-05-07T14:23:40.339Z" }, + { url = "https://files.pythonhosted.org/packages/8c/a8/e81533499f84ef6cdd95d11d5b05fa827c0f097925afd86f16e6a2631d8e/websockets-11.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b58cbf0697721120866820b89f93659abc31c1e876bf20d0b3d03cef14faf84d", size = 121017, upload-time = "2023-05-07T14:23:41.874Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ca/65d6986665888494eca4d5435a9741c822022996f0f4200c57ce4b9242f7/websockets-11.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:660e2d9068d2bedc0912af508f30bbeb505bbbf9774d98def45f68278cea20d3", size = 131200, upload-time = "2023-05-07T14:23:43.309Z" }, + { url = "https://files.pythonhosted.org/packages/c0/a8/a8a582ebeeecc8b5f332997d44c57e241748f8a9856e06a38a5a13b30796/websockets-11.0.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c1f0524f203e3bd35149f12157438f406eff2e4fb30f71221c8a5eceb3617b6b", size = 130195, upload-time = "2023-05-07T14:23:45.337Z" }, + { url = "https://files.pythonhosted.org/packages/a9/5e/b25c60067d700e811dccb4e3c318eeadd3a19d8b3620de9f97434af777a7/websockets-11.0.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:def07915168ac8f7853812cc593c71185a16216e9e4fa886358a17ed0fd9fcf6", size = 130569, upload-time = "2023-05-07T14:23:46.926Z" }, + { url = "https://files.pythonhosted.org/packages/14/fc/5cbbf439c925e1e184a0392ec477a30cee2fabc0e63807c1d4b6d570fb52/websockets-11.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b30c6590146e53149f04e85a6e4fcae068df4289e31e4aee1fdf56a0dead8f97", size = 136015, upload-time = "2023-05-07T14:23:48.43Z" }, + { url = "https://files.pythonhosted.org/packages/0f/d8/a997d3546aef9cc995a1126f7d7ade96c0e16c1a0efb9d2d430aee57c925/websockets-11.0.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:619d9f06372b3a42bc29d0cd0354c9bb9fb39c2cbc1a9c5025b4538738dbffaf", size = 135292, upload-time = "2023-05-07T14:23:50.744Z" }, + { url = "https://files.pythonhosted.org/packages/89/8f/707a05d5725f956c78d252a5fd73b89fa3ac57dd3959381c2d1acb41cb13/websockets-11.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:01f5567d9cf6f502d655151645d4e8b72b453413d3819d2b6f1185abc23e82dd", size = 135890, upload-time = "2023-05-07T14:23:52.707Z" }, + { url = "https://files.pythonhosted.org/packages/b5/94/ac47552208583d5dbcce468430c1eb2ae18962f6b3a694a2b7727cc60d4a/websockets-11.0.3-cp311-cp311-win32.whl", hash = "sha256:e1459677e5d12be8bbc7584c35b992eea142911a6236a3278b9b5ce3326f282c", size = 124149, upload-time = "2023-05-07T14:23:53.848Z" }, + { url = "https://files.pythonhosted.org/packages/e1/7c/0ad6e7ef0a054d73092f616d20d3d9bd3e1b837554cb20a52d8dd9f5b049/websockets-11.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:e7837cb169eca3b3ae94cc5787c4fed99eef74c0ab9506756eea335e0d6f3ed8", size = 124670, upload-time = "2023-05-07T14:23:55.812Z" }, + { url = "https://files.pythonhosted.org/packages/47/96/9d5749106ff57629b54360664ae7eb9afd8302fad1680ead385383e33746/websockets-11.0.3-py3-none-any.whl", hash = "sha256:6681ba9e7f8f3b19440921e99efbb40fc89f26cd71bf539e45d8c8a25c976dc6", size = 118056, upload-time = "2023-05-07T14:25:18.508Z" }, +] + +[[package]] +name = "yarl" +version = "1.20.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "multidict" }, + { name = "propcache" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3c/fb/efaa23fa4e45537b827620f04cf8f3cd658b76642205162e072703a5b963/yarl-1.20.1.tar.gz", hash = "sha256:d017a4997ee50c91fd5466cef416231bb82177b93b029906cefc542ce14c35ac", size = 186428, upload-time = "2025-06-10T00:46:09.923Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/65/7fed0d774abf47487c64be14e9223749468922817b5e8792b8a64792a1bb/yarl-1.20.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6032e6da6abd41e4acda34d75a816012717000fa6839f37124a47fcefc49bec4", size = 132910, upload-time = "2025-06-10T00:42:31.108Z" }, + { url = "https://files.pythonhosted.org/packages/8a/7b/988f55a52da99df9e56dc733b8e4e5a6ae2090081dc2754fc8fd34e60aa0/yarl-1.20.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2c7b34d804b8cf9b214f05015c4fee2ebe7ed05cf581e7192c06555c71f4446a", size = 90644, upload-time = "2025-06-10T00:42:33.851Z" }, + { url = "https://files.pythonhosted.org/packages/f7/de/30d98f03e95d30c7e3cc093759982d038c8833ec2451001d45ef4854edc1/yarl-1.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0c869f2651cc77465f6cd01d938d91a11d9ea5d798738c1dc077f3de0b5e5fed", size = 89322, upload-time = "2025-06-10T00:42:35.688Z" }, + { url = "https://files.pythonhosted.org/packages/e0/7a/f2f314f5ebfe9200724b0b748de2186b927acb334cf964fd312eb86fc286/yarl-1.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62915e6688eb4d180d93840cda4110995ad50c459bf931b8b3775b37c264af1e", size = 323786, upload-time = "2025-06-10T00:42:37.817Z" }, + { url = "https://files.pythonhosted.org/packages/15/3f/718d26f189db96d993d14b984ce91de52e76309d0fd1d4296f34039856aa/yarl-1.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:41ebd28167bc6af8abb97fec1a399f412eec5fd61a3ccbe2305a18b84fb4ca73", size = 319627, upload-time = "2025-06-10T00:42:39.937Z" }, + { url = "https://files.pythonhosted.org/packages/a5/76/8fcfbf5fa2369157b9898962a4a7d96764b287b085b5b3d9ffae69cdefd1/yarl-1.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:21242b4288a6d56f04ea193adde174b7e347ac46ce6bc84989ff7c1b1ecea84e", size = 339149, upload-time = "2025-06-10T00:42:42.627Z" }, + { url = "https://files.pythonhosted.org/packages/3c/95/d7fc301cc4661785967acc04f54a4a42d5124905e27db27bb578aac49b5c/yarl-1.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bea21cdae6c7eb02ba02a475f37463abfe0a01f5d7200121b03e605d6a0439f8", size = 333327, upload-time = "2025-06-10T00:42:44.842Z" }, + { url = "https://files.pythonhosted.org/packages/65/94/e21269718349582eee81efc5c1c08ee71c816bfc1585b77d0ec3f58089eb/yarl-1.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f8a891e4a22a89f5dde7862994485e19db246b70bb288d3ce73a34422e55b23", size = 326054, upload-time = "2025-06-10T00:42:47.149Z" }, + { url = "https://files.pythonhosted.org/packages/32/ae/8616d1f07853704523519f6131d21f092e567c5af93de7e3e94b38d7f065/yarl-1.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dd803820d44c8853a109a34e3660e5a61beae12970da479cf44aa2954019bf70", size = 315035, upload-time = "2025-06-10T00:42:48.852Z" }, + { url = "https://files.pythonhosted.org/packages/48/aa/0ace06280861ef055855333707db5e49c6e3a08840a7ce62682259d0a6c0/yarl-1.20.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b982fa7f74c80d5c0c7b5b38f908971e513380a10fecea528091405f519b9ebb", size = 338962, upload-time = "2025-06-10T00:42:51.024Z" }, + { url = "https://files.pythonhosted.org/packages/20/52/1e9d0e6916f45a8fb50e6844f01cb34692455f1acd548606cbda8134cd1e/yarl-1.20.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:33f29ecfe0330c570d997bcf1afd304377f2e48f61447f37e846a6058a4d33b2", size = 335399, upload-time = "2025-06-10T00:42:53.007Z" }, + { url = "https://files.pythonhosted.org/packages/f2/65/60452df742952c630e82f394cd409de10610481d9043aa14c61bf846b7b1/yarl-1.20.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:835ab2cfc74d5eb4a6a528c57f05688099da41cf4957cf08cad38647e4a83b30", size = 338649, upload-time = "2025-06-10T00:42:54.964Z" }, + { url = "https://files.pythonhosted.org/packages/7b/f5/6cd4ff38dcde57a70f23719a838665ee17079640c77087404c3d34da6727/yarl-1.20.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:46b5e0ccf1943a9a6e766b2c2b8c732c55b34e28be57d8daa2b3c1d1d4009309", size = 358563, upload-time = "2025-06-10T00:42:57.28Z" }, + { url = "https://files.pythonhosted.org/packages/d1/90/c42eefd79d0d8222cb3227bdd51b640c0c1d0aa33fe4cc86c36eccba77d3/yarl-1.20.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:df47c55f7d74127d1b11251fe6397d84afdde0d53b90bedb46a23c0e534f9d24", size = 357609, upload-time = "2025-06-10T00:42:59.055Z" }, + { url = "https://files.pythonhosted.org/packages/03/c8/cea6b232cb4617514232e0f8a718153a95b5d82b5290711b201545825532/yarl-1.20.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:76d12524d05841276b0e22573f28d5fbcb67589836772ae9244d90dd7d66aa13", size = 350224, upload-time = "2025-06-10T00:43:01.248Z" }, + { url = "https://files.pythonhosted.org/packages/ce/a3/eaa0ab9712f1f3d01faf43cf6f1f7210ce4ea4a7e9b28b489a2261ca8db9/yarl-1.20.1-cp310-cp310-win32.whl", hash = "sha256:6c4fbf6b02d70e512d7ade4b1f998f237137f1417ab07ec06358ea04f69134f8", size = 81753, upload-time = "2025-06-10T00:43:03.486Z" }, + { url = "https://files.pythonhosted.org/packages/8f/34/e4abde70a9256465fe31c88ed02c3f8502b7b5dead693a4f350a06413f28/yarl-1.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:aef6c4d69554d44b7f9d923245f8ad9a707d971e6209d51279196d8e8fe1ae16", size = 86817, upload-time = "2025-06-10T00:43:05.231Z" }, + { url = "https://files.pythonhosted.org/packages/b1/18/893b50efc2350e47a874c5c2d67e55a0ea5df91186b2a6f5ac52eff887cd/yarl-1.20.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:47ee6188fea634bdfaeb2cc420f5b3b17332e6225ce88149a17c413c77ff269e", size = 133833, upload-time = "2025-06-10T00:43:07.393Z" }, + { url = "https://files.pythonhosted.org/packages/89/ed/b8773448030e6fc47fa797f099ab9eab151a43a25717f9ac043844ad5ea3/yarl-1.20.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d0f6500f69e8402d513e5eedb77a4e1818691e8f45e6b687147963514d84b44b", size = 91070, upload-time = "2025-06-10T00:43:09.538Z" }, + { url = "https://files.pythonhosted.org/packages/e3/e3/409bd17b1e42619bf69f60e4f031ce1ccb29bd7380117a55529e76933464/yarl-1.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a8900a42fcdaad568de58887c7b2f602962356908eedb7628eaf6021a6e435b", size = 89818, upload-time = "2025-06-10T00:43:11.575Z" }, + { url = "https://files.pythonhosted.org/packages/f8/77/64d8431a4d77c856eb2d82aa3de2ad6741365245a29b3a9543cd598ed8c5/yarl-1.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bad6d131fda8ef508b36be3ece16d0902e80b88ea7200f030a0f6c11d9e508d4", size = 347003, upload-time = "2025-06-10T00:43:14.088Z" }, + { url = "https://files.pythonhosted.org/packages/8d/d2/0c7e4def093dcef0bd9fa22d4d24b023788b0a33b8d0088b51aa51e21e99/yarl-1.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:df018d92fe22aaebb679a7f89fe0c0f368ec497e3dda6cb81a567610f04501f1", size = 336537, upload-time = "2025-06-10T00:43:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/f0/f3/fc514f4b2cf02cb59d10cbfe228691d25929ce8f72a38db07d3febc3f706/yarl-1.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f969afbb0a9b63c18d0feecf0db09d164b7a44a053e78a7d05f5df163e43833", size = 362358, upload-time = "2025-06-10T00:43:18.704Z" }, + { url = "https://files.pythonhosted.org/packages/ea/6d/a313ac8d8391381ff9006ac05f1d4331cee3b1efaa833a53d12253733255/yarl-1.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:812303eb4aa98e302886ccda58d6b099e3576b1b9276161469c25803a8db277d", size = 357362, upload-time = "2025-06-10T00:43:20.888Z" }, + { url = "https://files.pythonhosted.org/packages/00/70/8f78a95d6935a70263d46caa3dd18e1f223cf2f2ff2037baa01a22bc5b22/yarl-1.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98c4a7d166635147924aa0bf9bfe8d8abad6fffa6102de9c99ea04a1376f91e8", size = 348979, upload-time = "2025-06-10T00:43:23.169Z" }, + { url = "https://files.pythonhosted.org/packages/cb/05/42773027968968f4f15143553970ee36ead27038d627f457cc44bbbeecf3/yarl-1.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12e768f966538e81e6e7550f9086a6236b16e26cd964cf4df35349970f3551cf", size = 337274, upload-time = "2025-06-10T00:43:27.111Z" }, + { url = "https://files.pythonhosted.org/packages/05/be/665634aa196954156741ea591d2f946f1b78ceee8bb8f28488bf28c0dd62/yarl-1.20.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fe41919b9d899661c5c28a8b4b0acf704510b88f27f0934ac7a7bebdd8938d5e", size = 363294, upload-time = "2025-06-10T00:43:28.96Z" }, + { url = "https://files.pythonhosted.org/packages/eb/90/73448401d36fa4e210ece5579895731f190d5119c4b66b43b52182e88cd5/yarl-1.20.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:8601bc010d1d7780592f3fc1bdc6c72e2b6466ea34569778422943e1a1f3c389", size = 358169, upload-time = "2025-06-10T00:43:30.701Z" }, + { url = "https://files.pythonhosted.org/packages/c3/b0/fce922d46dc1eb43c811f1889f7daa6001b27a4005587e94878570300881/yarl-1.20.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:daadbdc1f2a9033a2399c42646fbd46da7992e868a5fe9513860122d7fe7a73f", size = 362776, upload-time = "2025-06-10T00:43:32.51Z" }, + { url = "https://files.pythonhosted.org/packages/f1/0d/b172628fce039dae8977fd22caeff3eeebffd52e86060413f5673767c427/yarl-1.20.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:03aa1e041727cb438ca762628109ef1333498b122e4c76dd858d186a37cec845", size = 381341, upload-time = "2025-06-10T00:43:34.543Z" }, + { url = "https://files.pythonhosted.org/packages/6b/9b/5b886d7671f4580209e855974fe1cecec409aa4a89ea58b8f0560dc529b1/yarl-1.20.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:642980ef5e0fa1de5fa96d905c7e00cb2c47cb468bfcac5a18c58e27dbf8d8d1", size = 379988, upload-time = "2025-06-10T00:43:36.489Z" }, + { url = "https://files.pythonhosted.org/packages/73/be/75ef5fd0fcd8f083a5d13f78fd3f009528132a1f2a1d7c925c39fa20aa79/yarl-1.20.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:86971e2795584fe8c002356d3b97ef6c61862720eeff03db2a7c86b678d85b3e", size = 371113, upload-time = "2025-06-10T00:43:38.592Z" }, + { url = "https://files.pythonhosted.org/packages/50/4f/62faab3b479dfdcb741fe9e3f0323e2a7d5cd1ab2edc73221d57ad4834b2/yarl-1.20.1-cp311-cp311-win32.whl", hash = "sha256:597f40615b8d25812f14562699e287f0dcc035d25eb74da72cae043bb884d773", size = 81485, upload-time = "2025-06-10T00:43:41.038Z" }, + { url = "https://files.pythonhosted.org/packages/f0/09/d9c7942f8f05c32ec72cd5c8e041c8b29b5807328b68b4801ff2511d4d5e/yarl-1.20.1-cp311-cp311-win_amd64.whl", hash = "sha256:26ef53a9e726e61e9cd1cda6b478f17e350fb5800b4bd1cd9fe81c4d91cfeb2e", size = 86686, upload-time = "2025-06-10T00:43:42.692Z" }, + { url = "https://files.pythonhosted.org/packages/5f/9a/cb7fad7d73c69f296eda6815e4a2c7ed53fc70c2f136479a91c8e5fbdb6d/yarl-1.20.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdcc4cd244e58593a4379fe60fdee5ac0331f8eb70320a24d591a3be197b94a9", size = 133667, upload-time = "2025-06-10T00:43:44.369Z" }, + { url = "https://files.pythonhosted.org/packages/67/38/688577a1cb1e656e3971fb66a3492501c5a5df56d99722e57c98249e5b8a/yarl-1.20.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b29a2c385a5f5b9c7d9347e5812b6f7ab267193c62d282a540b4fc528c8a9d2a", size = 91025, upload-time = "2025-06-10T00:43:46.295Z" }, + { url = "https://files.pythonhosted.org/packages/50/ec/72991ae51febeb11a42813fc259f0d4c8e0507f2b74b5514618d8b640365/yarl-1.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1112ae8154186dfe2de4732197f59c05a83dc814849a5ced892b708033f40dc2", size = 89709, upload-time = "2025-06-10T00:43:48.22Z" }, + { url = "https://files.pythonhosted.org/packages/99/da/4d798025490e89426e9f976702e5f9482005c548c579bdae792a4c37769e/yarl-1.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90bbd29c4fe234233f7fa2b9b121fb63c321830e5d05b45153a2ca68f7d310ee", size = 352287, upload-time = "2025-06-10T00:43:49.924Z" }, + { url = "https://files.pythonhosted.org/packages/1a/26/54a15c6a567aac1c61b18aa0f4b8aa2e285a52d547d1be8bf48abe2b3991/yarl-1.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:680e19c7ce3710ac4cd964e90dad99bf9b5029372ba0c7cbfcd55e54d90ea819", size = 345429, upload-time = "2025-06-10T00:43:51.7Z" }, + { url = "https://files.pythonhosted.org/packages/d6/95/9dcf2386cb875b234353b93ec43e40219e14900e046bf6ac118f94b1e353/yarl-1.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a979218c1fdb4246a05efc2cc23859d47c89af463a90b99b7c56094daf25a16", size = 365429, upload-time = "2025-06-10T00:43:53.494Z" }, + { url = "https://files.pythonhosted.org/packages/91/b2/33a8750f6a4bc224242a635f5f2cff6d6ad5ba651f6edcccf721992c21a0/yarl-1.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255b468adf57b4a7b65d8aad5b5138dce6a0752c139965711bdcb81bc370e1b6", size = 363862, upload-time = "2025-06-10T00:43:55.766Z" }, + { url = "https://files.pythonhosted.org/packages/98/28/3ab7acc5b51f4434b181b0cee8f1f4b77a65919700a355fb3617f9488874/yarl-1.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a97d67108e79cfe22e2b430d80d7571ae57d19f17cda8bb967057ca8a7bf5bfd", size = 355616, upload-time = "2025-06-10T00:43:58.056Z" }, + { url = "https://files.pythonhosted.org/packages/36/a3/f666894aa947a371724ec7cd2e5daa78ee8a777b21509b4252dd7bd15e29/yarl-1.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8570d998db4ddbfb9a590b185a0a33dbf8aafb831d07a5257b4ec9948df9cb0a", size = 339954, upload-time = "2025-06-10T00:43:59.773Z" }, + { url = "https://files.pythonhosted.org/packages/f1/81/5f466427e09773c04219d3450d7a1256138a010b6c9f0af2d48565e9ad13/yarl-1.20.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:97c75596019baae7c71ccf1d8cc4738bc08134060d0adfcbe5642f778d1dca38", size = 365575, upload-time = "2025-06-10T00:44:02.051Z" }, + { url = "https://files.pythonhosted.org/packages/2e/e3/e4b0ad8403e97e6c9972dd587388940a032f030ebec196ab81a3b8e94d31/yarl-1.20.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1c48912653e63aef91ff988c5432832692ac5a1d8f0fb8a33091520b5bbe19ef", size = 365061, upload-time = "2025-06-10T00:44:04.196Z" }, + { url = "https://files.pythonhosted.org/packages/ac/99/b8a142e79eb86c926f9f06452eb13ecb1bb5713bd01dc0038faf5452e544/yarl-1.20.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4c3ae28f3ae1563c50f3d37f064ddb1511ecc1d5584e88c6b7c63cf7702a6d5f", size = 364142, upload-time = "2025-06-10T00:44:06.527Z" }, + { url = "https://files.pythonhosted.org/packages/34/f2/08ed34a4a506d82a1a3e5bab99ccd930a040f9b6449e9fd050320e45845c/yarl-1.20.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c5e9642f27036283550f5f57dc6156c51084b458570b9d0d96100c8bebb186a8", size = 381894, upload-time = "2025-06-10T00:44:08.379Z" }, + { url = "https://files.pythonhosted.org/packages/92/f8/9a3fbf0968eac704f681726eff595dce9b49c8a25cd92bf83df209668285/yarl-1.20.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2c26b0c49220d5799f7b22c6838409ee9bc58ee5c95361a4d7831f03cc225b5a", size = 383378, upload-time = "2025-06-10T00:44:10.51Z" }, + { url = "https://files.pythonhosted.org/packages/af/85/9363f77bdfa1e4d690957cd39d192c4cacd1c58965df0470a4905253b54f/yarl-1.20.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:564ab3d517e3d01c408c67f2e5247aad4019dcf1969982aba3974b4093279004", size = 374069, upload-time = "2025-06-10T00:44:12.834Z" }, + { url = "https://files.pythonhosted.org/packages/35/99/9918c8739ba271dcd935400cff8b32e3cd319eaf02fcd023d5dcd487a7c8/yarl-1.20.1-cp312-cp312-win32.whl", hash = "sha256:daea0d313868da1cf2fac6b2d3a25c6e3a9e879483244be38c8e6a41f1d876a5", size = 81249, upload-time = "2025-06-10T00:44:14.731Z" }, + { url = "https://files.pythonhosted.org/packages/eb/83/5d9092950565481b413b31a23e75dd3418ff0a277d6e0abf3729d4d1ce25/yarl-1.20.1-cp312-cp312-win_amd64.whl", hash = "sha256:48ea7d7f9be0487339828a4de0360d7ce0efc06524a48e1810f945c45b813698", size = 86710, upload-time = "2025-06-10T00:44:16.716Z" }, + { url = "https://files.pythonhosted.org/packages/8a/e1/2411b6d7f769a07687acee88a062af5833cf1966b7266f3d8dfb3d3dc7d3/yarl-1.20.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:0b5ff0fbb7c9f1b1b5ab53330acbfc5247893069e7716840c8e7d5bb7355038a", size = 131811, upload-time = "2025-06-10T00:44:18.933Z" }, + { url = "https://files.pythonhosted.org/packages/b2/27/584394e1cb76fb771371770eccad35de400e7b434ce3142c2dd27392c968/yarl-1.20.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:14f326acd845c2b2e2eb38fb1346c94f7f3b01a4f5c788f8144f9b630bfff9a3", size = 90078, upload-time = "2025-06-10T00:44:20.635Z" }, + { url = "https://files.pythonhosted.org/packages/bf/9a/3246ae92d4049099f52d9b0fe3486e3b500e29b7ea872d0f152966fc209d/yarl-1.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f60e4ad5db23f0b96e49c018596707c3ae89f5d0bd97f0ad3684bcbad899f1e7", size = 88748, upload-time = "2025-06-10T00:44:22.34Z" }, + { url = "https://files.pythonhosted.org/packages/a3/25/35afe384e31115a1a801fbcf84012d7a066d89035befae7c5d4284df1e03/yarl-1.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49bdd1b8e00ce57e68ba51916e4bb04461746e794e7c4d4bbc42ba2f18297691", size = 349595, upload-time = "2025-06-10T00:44:24.314Z" }, + { url = "https://files.pythonhosted.org/packages/28/2d/8aca6cb2cabc8f12efcb82749b9cefecbccfc7b0384e56cd71058ccee433/yarl-1.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:66252d780b45189975abfed839616e8fd2dbacbdc262105ad7742c6ae58f3e31", size = 342616, upload-time = "2025-06-10T00:44:26.167Z" }, + { url = "https://files.pythonhosted.org/packages/0b/e9/1312633d16b31acf0098d30440ca855e3492d66623dafb8e25b03d00c3da/yarl-1.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59174e7332f5d153d8f7452a102b103e2e74035ad085f404df2e40e663a22b28", size = 361324, upload-time = "2025-06-10T00:44:27.915Z" }, + { url = "https://files.pythonhosted.org/packages/bc/a0/688cc99463f12f7669eec7c8acc71ef56a1521b99eab7cd3abb75af887b0/yarl-1.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e3968ec7d92a0c0f9ac34d5ecfd03869ec0cab0697c91a45db3fbbd95fe1b653", size = 359676, upload-time = "2025-06-10T00:44:30.041Z" }, + { url = "https://files.pythonhosted.org/packages/af/44/46407d7f7a56e9a85a4c207724c9f2c545c060380718eea9088f222ba697/yarl-1.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1a4fbb50e14396ba3d375f68bfe02215d8e7bc3ec49da8341fe3157f59d2ff5", size = 352614, upload-time = "2025-06-10T00:44:32.171Z" }, + { url = "https://files.pythonhosted.org/packages/b1/91/31163295e82b8d5485d31d9cf7754d973d41915cadce070491778d9c9825/yarl-1.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11a62c839c3a8eac2410e951301309426f368388ff2f33799052787035793b02", size = 336766, upload-time = "2025-06-10T00:44:34.494Z" }, + { url = "https://files.pythonhosted.org/packages/b4/8e/c41a5bc482121f51c083c4c2bcd16b9e01e1cf8729e380273a952513a21f/yarl-1.20.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:041eaa14f73ff5a8986b4388ac6bb43a77f2ea09bf1913df7a35d4646db69e53", size = 364615, upload-time = "2025-06-10T00:44:36.856Z" }, + { url = "https://files.pythonhosted.org/packages/e3/5b/61a3b054238d33d70ea06ebba7e58597891b71c699e247df35cc984ab393/yarl-1.20.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:377fae2fef158e8fd9d60b4c8751387b8d1fb121d3d0b8e9b0be07d1b41e83dc", size = 360982, upload-time = "2025-06-10T00:44:39.141Z" }, + { url = "https://files.pythonhosted.org/packages/df/a3/6a72fb83f8d478cb201d14927bc8040af901811a88e0ff2da7842dd0ed19/yarl-1.20.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1c92f4390e407513f619d49319023664643d3339bd5e5a56a3bebe01bc67ec04", size = 369792, upload-time = "2025-06-10T00:44:40.934Z" }, + { url = "https://files.pythonhosted.org/packages/7c/af/4cc3c36dfc7c077f8dedb561eb21f69e1e9f2456b91b593882b0b18c19dc/yarl-1.20.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d25ddcf954df1754ab0f86bb696af765c5bfaba39b74095f27eececa049ef9a4", size = 382049, upload-time = "2025-06-10T00:44:42.854Z" }, + { url = "https://files.pythonhosted.org/packages/19/3a/e54e2c4752160115183a66dc9ee75a153f81f3ab2ba4bf79c3c53b33de34/yarl-1.20.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:909313577e9619dcff8c31a0ea2aa0a2a828341d92673015456b3ae492e7317b", size = 384774, upload-time = "2025-06-10T00:44:45.275Z" }, + { url = "https://files.pythonhosted.org/packages/9c/20/200ae86dabfca89060ec6447649f219b4cbd94531e425e50d57e5f5ac330/yarl-1.20.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:793fd0580cb9664548c6b83c63b43c477212c0260891ddf86809e1c06c8b08f1", size = 374252, upload-time = "2025-06-10T00:44:47.31Z" }, + { url = "https://files.pythonhosted.org/packages/83/75/11ee332f2f516b3d094e89448da73d557687f7d137d5a0f48c40ff211487/yarl-1.20.1-cp313-cp313-win32.whl", hash = "sha256:468f6e40285de5a5b3c44981ca3a319a4b208ccc07d526b20b12aeedcfa654b7", size = 81198, upload-time = "2025-06-10T00:44:49.164Z" }, + { url = "https://files.pythonhosted.org/packages/ba/ba/39b1ecbf51620b40ab402b0fc817f0ff750f6d92712b44689c2c215be89d/yarl-1.20.1-cp313-cp313-win_amd64.whl", hash = "sha256:495b4ef2fea40596bfc0affe3837411d6aa3371abcf31aac0ccc4bdd64d4ef5c", size = 86346, upload-time = "2025-06-10T00:44:51.182Z" }, + { url = "https://files.pythonhosted.org/packages/43/c7/669c52519dca4c95153c8ad96dd123c79f354a376346b198f438e56ffeb4/yarl-1.20.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f60233b98423aab21d249a30eb27c389c14929f47be8430efa7dbd91493a729d", size = 138826, upload-time = "2025-06-10T00:44:52.883Z" }, + { url = "https://files.pythonhosted.org/packages/6a/42/fc0053719b44f6ad04a75d7f05e0e9674d45ef62f2d9ad2c1163e5c05827/yarl-1.20.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6f3eff4cc3f03d650d8755c6eefc844edde99d641d0dcf4da3ab27141a5f8ddf", size = 93217, upload-time = "2025-06-10T00:44:54.658Z" }, + { url = "https://files.pythonhosted.org/packages/4f/7f/fa59c4c27e2a076bba0d959386e26eba77eb52ea4a0aac48e3515c186b4c/yarl-1.20.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:69ff8439d8ba832d6bed88af2c2b3445977eba9a4588b787b32945871c2444e3", size = 92700, upload-time = "2025-06-10T00:44:56.784Z" }, + { url = "https://files.pythonhosted.org/packages/2f/d4/062b2f48e7c93481e88eff97a6312dca15ea200e959f23e96d8ab898c5b8/yarl-1.20.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cf34efa60eb81dd2645a2e13e00bb98b76c35ab5061a3989c7a70f78c85006d", size = 347644, upload-time = "2025-06-10T00:44:59.071Z" }, + { url = "https://files.pythonhosted.org/packages/89/47/78b7f40d13c8f62b499cc702fdf69e090455518ae544c00a3bf4afc9fc77/yarl-1.20.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8e0fe9364ad0fddab2688ce72cb7a8e61ea42eff3c7caeeb83874a5d479c896c", size = 323452, upload-time = "2025-06-10T00:45:01.605Z" }, + { url = "https://files.pythonhosted.org/packages/eb/2b/490d3b2dc66f52987d4ee0d3090a147ea67732ce6b4d61e362c1846d0d32/yarl-1.20.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f64fbf81878ba914562c672024089e3401974a39767747691c65080a67b18c1", size = 346378, upload-time = "2025-06-10T00:45:03.946Z" }, + { url = "https://files.pythonhosted.org/packages/66/ad/775da9c8a94ce925d1537f939a4f17d782efef1f973039d821cbe4bcc211/yarl-1.20.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6342d643bf9a1de97e512e45e4b9560a043347e779a173250824f8b254bd5ce", size = 353261, upload-time = "2025-06-10T00:45:05.992Z" }, + { url = "https://files.pythonhosted.org/packages/4b/23/0ed0922b47a4f5c6eb9065d5ff1e459747226ddce5c6a4c111e728c9f701/yarl-1.20.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56dac5f452ed25eef0f6e3c6a066c6ab68971d96a9fb441791cad0efba6140d3", size = 335987, upload-time = "2025-06-10T00:45:08.227Z" }, + { url = "https://files.pythonhosted.org/packages/3e/49/bc728a7fe7d0e9336e2b78f0958a2d6b288ba89f25a1762407a222bf53c3/yarl-1.20.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7d7f497126d65e2cad8dc5f97d34c27b19199b6414a40cb36b52f41b79014be", size = 329361, upload-time = "2025-06-10T00:45:10.11Z" }, + { url = "https://files.pythonhosted.org/packages/93/8f/b811b9d1f617c83c907e7082a76e2b92b655400e61730cd61a1f67178393/yarl-1.20.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:67e708dfb8e78d8a19169818eeb5c7a80717562de9051bf2413aca8e3696bf16", size = 346460, upload-time = "2025-06-10T00:45:12.055Z" }, + { url = "https://files.pythonhosted.org/packages/70/fd/af94f04f275f95da2c3b8b5e1d49e3e79f1ed8b6ceb0f1664cbd902773ff/yarl-1.20.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:595c07bc79af2494365cc96ddeb772f76272364ef7c80fb892ef9d0649586513", size = 334486, upload-time = "2025-06-10T00:45:13.995Z" }, + { url = "https://files.pythonhosted.org/packages/84/65/04c62e82704e7dd0a9b3f61dbaa8447f8507655fd16c51da0637b39b2910/yarl-1.20.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7bdd2f80f4a7df852ab9ab49484a4dee8030023aa536df41f2d922fd57bf023f", size = 342219, upload-time = "2025-06-10T00:45:16.479Z" }, + { url = "https://files.pythonhosted.org/packages/91/95/459ca62eb958381b342d94ab9a4b6aec1ddec1f7057c487e926f03c06d30/yarl-1.20.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c03bfebc4ae8d862f853a9757199677ab74ec25424d0ebd68a0027e9c639a390", size = 350693, upload-time = "2025-06-10T00:45:18.399Z" }, + { url = "https://files.pythonhosted.org/packages/a6/00/d393e82dd955ad20617abc546a8f1aee40534d599ff555ea053d0ec9bf03/yarl-1.20.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:344d1103e9c1523f32a5ed704d576172d2cabed3122ea90b1d4e11fe17c66458", size = 355803, upload-time = "2025-06-10T00:45:20.677Z" }, + { url = "https://files.pythonhosted.org/packages/9e/ed/c5fb04869b99b717985e244fd93029c7a8e8febdfcffa06093e32d7d44e7/yarl-1.20.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:88cab98aa4e13e1ade8c141daeedd300a4603b7132819c484841bb7af3edce9e", size = 341709, upload-time = "2025-06-10T00:45:23.221Z" }, + { url = "https://files.pythonhosted.org/packages/24/fd/725b8e73ac2a50e78a4534ac43c6addf5c1c2d65380dd48a9169cc6739a9/yarl-1.20.1-cp313-cp313t-win32.whl", hash = "sha256:b121ff6a7cbd4abc28985b6028235491941b9fe8fe226e6fdc539c977ea1739d", size = 86591, upload-time = "2025-06-10T00:45:25.793Z" }, + { url = "https://files.pythonhosted.org/packages/94/c3/b2e9f38bc3e11191981d57ea08cab2166e74ea770024a646617c9cddd9f6/yarl-1.20.1-cp313-cp313t-win_amd64.whl", hash = "sha256:541d050a355bbbc27e55d906bc91cb6fe42f96c01413dd0f4ed5a5240513874f", size = 93003, upload-time = "2025-06-10T00:45:27.752Z" }, + { url = "https://files.pythonhosted.org/packages/b4/2d/2345fce04cfd4bee161bf1e7d9cdc702e3e16109021035dbb24db654a622/yarl-1.20.1-py3-none-any.whl", hash = "sha256:83b8eb083fe4683c6115795d9fc1cfaf2cbbefb19b3a1cb68f6527460f483a77", size = 46542, upload-time = "2025-06-10T00:46:07.521Z" }, +] + +[[package]] +name = "zipp" +version = "3.23.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, +]